From 1acdd32fd4b65f70f3066d22926b000db3a548aa Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Thu, 10 Feb 2022 15:16:59 -0800 Subject: [PATCH 0001/1310] Accomodate brightness space change for client target Previous the hal reported a white point in nits. Since nits are being removed from the composer interface, SurfaceFlinger converts a dimming ratio reported by composer into a white point instead. Bug: 217961164 Test: builds, boots Change-Id: I9ceec3af80f503df0debe434bb454ec42694811b --- .../include/compositionengine/impl/Display.h | 2 +- .../compositionengine/impl/OutputCompositionState.h | 4 ++-- services/surfaceflinger/CompositionEngine/src/Display.cpp | 7 +++---- services/surfaceflinger/CompositionEngine/src/Output.cpp | 3 ++- .../surfaceflinger/CompositionEngine/tests/DisplayTest.cpp | 2 +- .../surfaceflinger/CompositionEngine/tests/OutputTest.cpp | 5 +++-- .../surfaceflinger/DisplayHardware/AidlComposerHal.cpp | 4 ++-- services/surfaceflinger/DisplayHardware/AidlComposerHal.h | 2 +- services/surfaceflinger/DisplayHardware/ComposerHal.h | 2 +- services/surfaceflinger/DisplayHardware/HWComposer.cpp | 7 +++---- services/surfaceflinger/DisplayHardware/HWComposer.h | 2 +- .../surfaceflinger/DisplayHardware/HidlComposerHal.cpp | 4 ++-- services/surfaceflinger/DisplayHardware/HidlComposerHal.h | 2 +- 13 files changed, 23 insertions(+), 23 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h index ebe112b20b..c7984bd529 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h @@ -78,7 +78,7 @@ public: virtual void applyChangedTypesToLayers(const ChangedTypes&); virtual void applyDisplayRequests(const DisplayRequests&); virtual void applyLayerRequestsToLayers(const LayerRequests&); - virtual void applyClientTargetRequests(const ClientTargetProperty&, float whitePointNits); + virtual void applyClientTargetRequests(const ClientTargetProperty&, float brightness); // Internal virtual void setConfiguration(const compositionengine::DisplayCreationArgs&); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h index cc7c2574b7..66dd825e5b 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h @@ -130,8 +130,8 @@ struct OutputCompositionState { // SDR white point float sdrWhitePointNits{-1.f}; - // White point of the client target - float clientTargetWhitePointNits{-1.f}; + // Brightness of the client target, normalized to display brightness + float clientTargetBrightness{1.f}; // Display brightness that will take effect this frame. // This is slightly distinct from nits, in that nits cannot be passed to hw composer. diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp index a3188f3772..6390025725 100644 --- a/services/surfaceflinger/CompositionEngine/src/Display.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp @@ -266,8 +266,7 @@ void Display::chooseCompositionStrategy() { applyChangedTypesToLayers(changes->changedTypes); applyDisplayRequests(changes->displayRequests); applyLayerRequestsToLayers(changes->layerRequests); - applyClientTargetRequests(changes->clientTargetProperty, - changes->clientTargetWhitePointNits); + applyClientTargetRequests(changes->clientTargetProperty, changes->clientTargetBrightness); } // Determine what type of composition we are doing from the final state @@ -343,13 +342,13 @@ void Display::applyLayerRequestsToLayers(const LayerRequests& layerRequests) { } void Display::applyClientTargetRequests(const ClientTargetProperty& clientTargetProperty, - float whitePointNits) { + float brightness) { if (clientTargetProperty.dataspace == ui::Dataspace::UNKNOWN) { return; } editState().dataspace = clientTargetProperty.dataspace; - editState().clientTargetWhitePointNits = whitePointNits; + editState().clientTargetBrightness = brightness; getRenderSurface()->setBufferDataspace(clientTargetProperty.dataspace); getRenderSurface()->setBufferPixelFormat(clientTargetProperty.pixelFormat); } diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 65f9731afe..63c7f638c2 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -1076,7 +1076,8 @@ std::optional Output::composeSurfaces( : mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance(); clientCompositionDisplay.maxLuminance = mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance(); - clientCompositionDisplay.targetLuminanceNits = outputState.clientTargetWhitePointNits; + clientCompositionDisplay.targetLuminanceNits = + outputState.clientTargetBrightness * outputState.displayBrightnessNits; // Compute the global color transform matrix. if (!outputState.usesDeviceComposition && !getSkipColorTransform()) { diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp index 44d34a3447..cd0323555c 100644 --- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp @@ -839,7 +839,7 @@ TEST_F(DisplayApplyLayerRequestsToLayersTest, applyClientTargetRequests) { auto& state = mDisplay->getState(); EXPECT_EQ(clientTargetProperty.dataspace, state.dataspace); - EXPECT_EQ(kWhitePointNits, state.clientTargetWhitePointNits); + EXPECT_EQ(kWhitePointNits, state.clientTargetBrightness); } /* diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index e72bc9f66d..53c587a469 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -3067,7 +3067,7 @@ struct OutputComposeSurfacesTest : public testing::Test { mOutput.mState.usesDeviceComposition = false; mOutput.mState.reusedClientComposition = false; mOutput.mState.flipClientTarget = false; - mOutput.mState.clientTargetWhitePointNits = kClientTargetLuminanceNits; + mOutput.mState.clientTargetBrightness = kClientTargetBrightness; EXPECT_CALL(mOutput, getCompositionEngine()).WillRepeatedly(ReturnRef(mCompositionEngine)); EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine)); @@ -3103,8 +3103,9 @@ struct OutputComposeSurfacesTest : public testing::Test { static constexpr float kDefaultAvgLuminance = 0.7f; static constexpr float kDefaultMinLuminance = 0.1f; static constexpr float kUnknownLuminance = -1.f; - static constexpr float kDisplayLuminance = 80.f; + static constexpr float kDisplayLuminance = 400.f; static constexpr float kClientTargetLuminanceNits = 200.f; + static constexpr float kClientTargetBrightness = 0.5f; static const Rect kDefaultOutputFrame; static const Rect kDefaultOutputViewport; diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp index 4e8d8c4c21..64cb497f0e 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp @@ -1054,11 +1054,11 @@ Error AidlComposer::getPreferredBootDisplayConfig(Display display, Config* confi Error AidlComposer::getClientTargetProperty( Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty, - float* whitePointNits) { + float* outBrightness) { const auto property = mReader.takeClientTargetProperty(translate(display)); *outClientTargetProperty = translate(property.clientTargetProperty); - *whitePointNits = property.whitePointNits; + *outBrightness = property.brightness; return Error::NONE; } diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h index aad09cbcf3..2ad119cef8 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h @@ -208,7 +208,7 @@ public: std::vector* outKeys) override; Error getClientTargetProperty(Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty, - float* outClientTargetWhitePointNits) override; + float* outBrightness) override; // AIDL Composer HAL Error setLayerBrightness(Display display, Layer layer, float brightness) override; diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h index 6e4d23bc80..fd6f6a4b64 100644 --- a/services/surfaceflinger/DisplayHardware/ComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h @@ -258,7 +258,7 @@ public: virtual Error getClientTargetProperty( Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty, - float* outWhitePointNits) = 0; + float* outBrightness) = 0; // AIDL Composer virtual Error setLayerBrightness(Display display, Layer layer, float brightness) = 0; diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp index 0ed4ec9ccc..956eca2361 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -479,13 +479,12 @@ status_t HWComposer::getDeviceCompositionChanges( RETURN_IF_HWC_ERROR_FOR("getRequests", error, displayId, BAD_INDEX); DeviceRequestedChanges::ClientTargetProperty clientTargetProperty; - float clientTargetWhitePointNits; - error = hwcDisplay->getClientTargetProperty(&clientTargetProperty, &clientTargetWhitePointNits); + float brightness = 1.f; + error = hwcDisplay->getClientTargetProperty(&clientTargetProperty, &brightness); outChanges->emplace(DeviceRequestedChanges{std::move(changedTypes), std::move(displayRequests), std::move(layerRequests), - std::move(clientTargetProperty), - clientTargetWhitePointNits}); + std::move(clientTargetProperty), brightness}); error = hwcDisplay->acceptChanges(); RETURN_IF_HWC_ERROR_FOR("acceptChanges", error, displayId, BAD_INDEX); diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h index 1376f5175e..7c61bf11c1 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.h +++ b/services/surfaceflinger/DisplayHardware/HWComposer.h @@ -85,7 +85,7 @@ public: DisplayRequests displayRequests; LayerRequests layerRequests; ClientTargetProperty clientTargetProperty; - float clientTargetWhitePointNits; + float clientTargetBrightness; }; struct HWCDisplayMode { diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp index c6b8802d89..8680a539eb 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp @@ -1297,9 +1297,9 @@ Error HidlComposer::getPreferredBootDisplayConfig(Display /*displayId*/, Config* Error HidlComposer::getClientTargetProperty( Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty, - float* outWhitePointNits) { + float* outBrightness) { mReader.takeClientTargetProperty(display, outClientTargetProperty); - *outWhitePointNits = -1.f; + *outBrightness = 1.f; return Error::NONE; } diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h index 41bf8255ec..32fe74846d 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h @@ -318,7 +318,7 @@ public: std::vector* outKeys) override; Error getClientTargetProperty(Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty, - float* outWhitePointNits) override; + float* outBrightness) override; // AIDL Composer HAL Error setLayerBrightness(Display display, Layer layer, float brightness) override; -- GitLab From cd5229ed1fb46058d58a9d1cabe4c66ba9a31f43 Mon Sep 17 00:00:00 2001 From: Chethan Kumar R E Date: Tue, 23 Nov 2021 17:45:54 +0530 Subject: [PATCH 0002/1310] Added surfaceflinger_frametracer_fuzzer Test: ./surfaceflinger_frametracer_fuzzer Bug: 189053744 Change-Id: I8319efa5937fd48796f606cb7f41c3de41f7dcf3 --- services/surfaceflinger/fuzzer/Android.bp | 10 ++ services/surfaceflinger/fuzzer/README.md | 14 ++ .../surfaceflinger_frametracer_fuzzer.cpp | 132 ++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp diff --git a/services/surfaceflinger/fuzzer/Android.bp b/services/surfaceflinger/fuzzer/Android.bp index b0b6bf14ab..7350e09cb5 100644 --- a/services/surfaceflinger/fuzzer/Android.bp +++ b/services/surfaceflinger/fuzzer/Android.bp @@ -127,3 +127,13 @@ cc_fuzz { "surfaceflinger_layer_fuzzer.cpp", ], } + +cc_fuzz { + name: "surfaceflinger_frametracer_fuzzer", + defaults: [ + "surfaceflinger_fuzz_defaults", + ], + srcs: [ + "surfaceflinger_frametracer_fuzzer.cpp", + ], +} diff --git a/services/surfaceflinger/fuzzer/README.md b/services/surfaceflinger/fuzzer/README.md index 78a7596ef3..7a5f229ae9 100644 --- a/services/surfaceflinger/fuzzer/README.md +++ b/services/surfaceflinger/fuzzer/README.md @@ -4,6 +4,7 @@ + [DisplayHardware](#DisplayHardware) + [Scheduler](#Scheduler) + [Layer](#Layer) ++ [FrameTracer](#FrameTracer) # Fuzzer for SurfaceFlinger @@ -93,3 +94,16 @@ You can find the possible values in the fuzzer's source code. $ adb sync data $ adb shell /data/fuzz/arm64/surfaceflinger_layer_fuzzer/surfaceflinger_layer_fuzzer ``` + +# Fuzzer for FrameTracer + +#### Steps to run +1. Build the fuzzer +``` + $ mm -j$(nproc) surfaceflinger_frametracer_fuzzer +``` +2. To run on device +``` + $ adb sync data + $ adb shell /data/fuzz/arm64/surfaceflinger_frametracer_fuzzer/surfaceflinger_frametracer_fuzzer +``` diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp new file mode 100644 index 0000000000..a22a778989 --- /dev/null +++ b/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp @@ -0,0 +1,132 @@ +/* + * Copyright 2021 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 +#include + +namespace android::fuzz { + +using namespace google::protobuf; + +constexpr size_t kMaxStringSize = 100; +constexpr size_t kMinLayerIds = 1; +constexpr size_t kMaxLayerIds = 10; +constexpr int32_t kConfigDuration = 500; +constexpr int32_t kBufferSize = 1024; +constexpr int32_t kTimeOffset = 100000; + +class FrameTracerFuzzer { +public: + FrameTracerFuzzer(const uint8_t* data, size_t size) : mFdp(data, size) { + // Fuzzer is single-threaded, so no need to be thread-safe. + static bool wasInitialized = false; + if (!wasInitialized) { + perfetto::TracingInitArgs args; + args.backends = perfetto::kInProcessBackend; + perfetto::Tracing::Initialize(args); + wasInitialized = true; + } + mFrameTracer = std::make_unique(); + } + ~FrameTracerFuzzer() { mFrameTracer.reset(); } + void process(); + +private: + std::unique_ptr getTracingSessionForTest(); + void traceTimestamp(); + std::vector generateLayerIds(size_t numLayerIds); + void traceTimestamp(std::vector layerIds, size_t numLayerIds); + void traceFence(std::vector layerIds, size_t numLayerIds); + std::unique_ptr mFrameTracer = nullptr; + FuzzedDataProvider mFdp; + android::FenceToFenceTimeMap mFenceFactory; +}; + +std::unique_ptr FrameTracerFuzzer::getTracingSessionForTest() { + perfetto::TraceConfig cfg; + cfg.set_duration_ms(kConfigDuration); + cfg.add_buffers()->set_size_kb(kBufferSize); + auto* dsCfg = cfg.add_data_sources()->mutable_config(); + dsCfg->set_name(android::FrameTracer::kFrameTracerDataSource); + + auto tracingSession = perfetto::Tracing::NewTrace(perfetto::kInProcessBackend); + tracingSession->Setup(cfg); + return tracingSession; +} + +std::vector FrameTracerFuzzer::generateLayerIds(size_t numLayerIds) { + std::vector layerIds; + for (size_t i = 0; i < numLayerIds; ++i) { + layerIds.push_back(mFdp.ConsumeIntegral()); + } + return layerIds; +} + +void FrameTracerFuzzer::traceTimestamp(std::vector layerIds, size_t numLayerIds) { + int32_t layerId = layerIds.at(mFdp.ConsumeIntegralInRange(0, numLayerIds - 1)); + mFrameTracer->traceTimestamp(layerId, mFdp.ConsumeIntegral() /*bufferID*/, + mFdp.ConsumeIntegral() /*frameNumber*/, + mFdp.ConsumeIntegral() /*timestamp*/, + android::FrameTracer::FrameEvent::UNSPECIFIED, + mFdp.ConsumeIntegral() /*duration*/); +} + +void FrameTracerFuzzer::traceFence(std::vector layerIds, size_t numLayerIds) { + const nsecs_t signalTime = systemTime(); + const nsecs_t startTime = signalTime + kTimeOffset; + auto fence = mFenceFactory.createFenceTimeForTest(android::Fence::NO_FENCE); + mFenceFactory.signalAllForTest(android::Fence::NO_FENCE, signalTime); + int32_t layerId = layerIds.at(mFdp.ConsumeIntegralInRange(0, numLayerIds - 1)); + mFrameTracer->traceFence(layerId, mFdp.ConsumeIntegral() /*bufferID*/, + mFdp.ConsumeIntegral() /*frameNumber*/, fence, + android::FrameTracer::FrameEvent::ACQUIRE_FENCE, startTime); +} + +void FrameTracerFuzzer::process() { + mFrameTracer->registerDataSource(); + + auto tracingSession = getTracingSessionForTest(); + tracingSession->StartBlocking(); + + size_t numLayerIds = mFdp.ConsumeIntegralInRange(kMinLayerIds, kMaxLayerIds); + std::vector layerIds = generateLayerIds(numLayerIds); + + for (auto it = layerIds.begin(); it != layerIds.end(); ++it) { + mFrameTracer->traceNewLayer(*it /*layerId*/, + mFdp.ConsumeRandomLengthString(kMaxStringSize) /*layerName*/); + } + + traceTimestamp(layerIds, numLayerIds); + traceFence(layerIds, numLayerIds); + + mFenceFactory.signalAllForTest(android::Fence::NO_FENCE, systemTime()); + + tracingSession->StopBlocking(); + + for (auto it = layerIds.begin(); it != layerIds.end(); ++it) { + mFrameTracer->onDestroy(*it); + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FrameTracerFuzzer frameTracerFuzzer(data, size); + frameTracerFuzzer.process(); + return 0; +} + +} // namespace android::fuzz -- GitLab From baf0b1681fb8bf5476eb038cc73d527093e91c4a Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 16 Feb 2022 11:12:36 -0800 Subject: [PATCH 0003/1310] Use std::vector to store slots We are currently using a raw vector allocated via 'new' to store Slots. To prepare for simultaneous touch and stylus functionality, let's store slots in a vector. Bug: TBD Test: atest inputflinger_tests Change-Id: I2506e348e8b93c0ba1c642fb7c1af6268fea72ba --- .../reader/mapper/MultiTouchInputMapper.cpp | 114 +++++++----------- .../reader/mapper/MultiTouchInputMapper.h | 48 ++++---- 2 files changed, 65 insertions(+), 97 deletions(-) diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp index 41a8426e42..8f5dc9bfd5 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp @@ -31,23 +31,15 @@ static constexpr size_t MAX_SLOTS = 32; MultiTouchMotionAccumulator::MultiTouchMotionAccumulator() : mCurrentSlot(-1), - mSlots(nullptr), - mSlotCount(0), mUsingSlotsProtocol(false), mHaveStylus(false) {} -MultiTouchMotionAccumulator::~MultiTouchMotionAccumulator() { - delete[] mSlots; -} - void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol) { - mSlotCount = slotCount; mUsingSlotsProtocol = usingSlotsProtocol; mHaveStylus = deviceContext.hasAbsoluteAxis(ABS_MT_TOOL_TYPE); - delete[] mSlots; - mSlots = new Slot[slotCount]; + mSlots = std::vector(slotCount); } void MultiTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) { @@ -76,10 +68,8 @@ void MultiTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) { } void MultiTouchMotionAccumulator::clearSlots(int32_t initialSlot) { - if (mSlots) { - for (size_t i = 0; i < mSlotCount; i++) { - mSlots[i].clear(); - } + for (Slot& slot : mSlots) { + slot.clear(); } mCurrentSlot = initialSlot; } @@ -96,68 +86,68 @@ void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { mCurrentSlot = 0; } - if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlotCount) { + if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlots.size()) { if (DEBUG_POINTERS) { if (newSlot) { ALOGW("MultiTouch device emitted invalid slot index %d but it " "should be between 0 and %zd; ignoring this slot.", - mCurrentSlot, mSlotCount - 1); + mCurrentSlot, mSlots.size() - 1); } } } else { - Slot* slot = &mSlots[mCurrentSlot]; + Slot& slot = mSlots[mCurrentSlot]; // If mUsingSlotsProtocol is true, it means the raw pointer has axis info of // ABS_MT_TRACKING_ID and ABS_MT_SLOT, so driver should send a valid trackingId while // updating the slot. if (!mUsingSlotsProtocol) { - slot->mInUse = true; + slot.mInUse = true; } switch (rawEvent->code) { case ABS_MT_POSITION_X: - slot->mAbsMTPositionX = rawEvent->value; - warnIfNotInUse(*rawEvent, *slot); + slot.mAbsMTPositionX = rawEvent->value; + warnIfNotInUse(*rawEvent, slot); break; case ABS_MT_POSITION_Y: - slot->mAbsMTPositionY = rawEvent->value; - warnIfNotInUse(*rawEvent, *slot); + slot.mAbsMTPositionY = rawEvent->value; + warnIfNotInUse(*rawEvent, slot); break; case ABS_MT_TOUCH_MAJOR: - slot->mAbsMTTouchMajor = rawEvent->value; + slot.mAbsMTTouchMajor = rawEvent->value; break; case ABS_MT_TOUCH_MINOR: - slot->mAbsMTTouchMinor = rawEvent->value; - slot->mHaveAbsMTTouchMinor = true; + slot.mAbsMTTouchMinor = rawEvent->value; + slot.mHaveAbsMTTouchMinor = true; break; case ABS_MT_WIDTH_MAJOR: - slot->mAbsMTWidthMajor = rawEvent->value; + slot.mAbsMTWidthMajor = rawEvent->value; break; case ABS_MT_WIDTH_MINOR: - slot->mAbsMTWidthMinor = rawEvent->value; - slot->mHaveAbsMTWidthMinor = true; + slot.mAbsMTWidthMinor = rawEvent->value; + slot.mHaveAbsMTWidthMinor = true; break; case ABS_MT_ORIENTATION: - slot->mAbsMTOrientation = rawEvent->value; + slot.mAbsMTOrientation = rawEvent->value; break; case ABS_MT_TRACKING_ID: if (mUsingSlotsProtocol && rawEvent->value < 0) { // The slot is no longer in use but it retains its previous contents, // which may be reused for subsequent touches. - slot->mInUse = false; + slot.mInUse = false; } else { - slot->mInUse = true; - slot->mAbsMTTrackingId = rawEvent->value; + slot.mInUse = true; + slot.mAbsMTTrackingId = rawEvent->value; } break; case ABS_MT_PRESSURE: - slot->mAbsMTPressure = rawEvent->value; + slot.mAbsMTPressure = rawEvent->value; break; case ABS_MT_DISTANCE: - slot->mAbsMTDistance = rawEvent->value; + slot.mAbsMTDistance = rawEvent->value; break; case ABS_MT_TOOL_TYPE: - slot->mAbsMTToolType = rawEvent->value; - slot->mHaveAbsMTToolType = true; + slot.mAbsMTToolType = rawEvent->value; + slot.mHaveAbsMTToolType = true; break; } } @@ -186,28 +176,6 @@ void MultiTouchMotionAccumulator::warnIfNotInUse(const RawEvent& event, const Sl // --- MultiTouchMotionAccumulator::Slot --- -MultiTouchMotionAccumulator::Slot::Slot() { - clear(); -} - -void MultiTouchMotionAccumulator::Slot::clear() { - mInUse = false; - mHaveAbsMTTouchMinor = false; - mHaveAbsMTWidthMinor = false; - mHaveAbsMTToolType = false; - mAbsMTPositionX = 0; - mAbsMTPositionY = 0; - mAbsMTTouchMajor = 0; - mAbsMTTouchMinor = 0; - mAbsMTWidthMajor = 0; - mAbsMTWidthMinor = 0; - mAbsMTOrientation = 0; - mAbsMTTrackingId = -1; - mAbsMTPressure = 0; - mAbsMTDistance = 0; - mAbsMTToolType = 0; -} - int32_t MultiTouchMotionAccumulator::Slot::getToolType() const { if (mHaveAbsMTToolType) { switch (mAbsMTToolType) { @@ -264,14 +232,14 @@ void MultiTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { mHavePointerIds = true; for (size_t inIndex = 0; inIndex < inCount; inIndex++) { - const MultiTouchMotionAccumulator::Slot* inSlot = + const MultiTouchMotionAccumulator::Slot& inSlot = mMultiTouchMotionAccumulator.getSlot(inIndex); - if (!inSlot->isInUse()) { + if (!inSlot.isInUse()) { continue; } - if (inSlot->getToolType() == AMOTION_EVENT_TOOL_TYPE_PALM) { - std::optional id = getActiveBitId(*inSlot); + if (inSlot.getToolType() == AMOTION_EVENT_TOOL_TYPE_PALM) { + std::optional id = getActiveBitId(inSlot); if (id) { outState->rawPointerData.canceledIdBits.markBit(id.value()); } @@ -292,19 +260,19 @@ void MultiTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { } RawPointerData::Pointer& outPointer = outState->rawPointerData.pointers[outCount]; - outPointer.x = inSlot->getX(); - outPointer.y = inSlot->getY(); - outPointer.pressure = inSlot->getPressure(); - outPointer.touchMajor = inSlot->getTouchMajor(); - outPointer.touchMinor = inSlot->getTouchMinor(); - outPointer.toolMajor = inSlot->getToolMajor(); - outPointer.toolMinor = inSlot->getToolMinor(); - outPointer.orientation = inSlot->getOrientation(); - outPointer.distance = inSlot->getDistance(); + outPointer.x = inSlot.getX(); + outPointer.y = inSlot.getY(); + outPointer.pressure = inSlot.getPressure(); + outPointer.touchMajor = inSlot.getTouchMajor(); + outPointer.touchMinor = inSlot.getTouchMinor(); + outPointer.toolMajor = inSlot.getToolMajor(); + outPointer.toolMinor = inSlot.getToolMinor(); + outPointer.orientation = inSlot.getOrientation(); + outPointer.distance = inSlot.getDistance(); outPointer.tiltX = 0; outPointer.tiltY = 0; - outPointer.toolType = inSlot->getToolType(); + outPointer.toolType = inSlot.getToolType(); if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { outPointer.toolType = mTouchButtonAccumulator.getToolType(); if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { @@ -318,12 +286,12 @@ void MultiTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE && (mTouchButtonAccumulator.isHovering() || - (mRawPointerAxes.pressure.valid && inSlot->getPressure() <= 0)); + (mRawPointerAxes.pressure.valid && inSlot.getPressure() <= 0)); outPointer.isHovering = isHovering; // Assign pointer id using tracking id if available. if (mHavePointerIds) { - int32_t trackingId = inSlot->getTrackingId(); + int32_t trackingId = inSlot.getTrackingId(); int32_t id = -1; if (trackingId >= 0) { for (BitSet32 idBits(mPointerIdBits); !idBits.isEmpty();) { diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h index b7c3457285..fe8af5d818 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h @@ -46,29 +46,27 @@ public: private: friend class MultiTouchMotionAccumulator; - bool mInUse; - bool mHaveAbsMTTouchMinor; - bool mHaveAbsMTWidthMinor; - bool mHaveAbsMTToolType; - - int32_t mAbsMTPositionX; - int32_t mAbsMTPositionY; - int32_t mAbsMTTouchMajor; - int32_t mAbsMTTouchMinor; - int32_t mAbsMTWidthMajor; - int32_t mAbsMTWidthMinor; - int32_t mAbsMTOrientation; - int32_t mAbsMTTrackingId; - int32_t mAbsMTPressure; - int32_t mAbsMTDistance; - int32_t mAbsMTToolType; - - Slot(); - void clear(); + bool mInUse = false; + bool mHaveAbsMTTouchMinor = false; + bool mHaveAbsMTWidthMinor = false; + bool mHaveAbsMTToolType = false; + + int32_t mAbsMTPositionX = 0; + int32_t mAbsMTPositionY = 0; + int32_t mAbsMTTouchMajor = 0; + int32_t mAbsMTTouchMinor = 0; + int32_t mAbsMTWidthMajor = 0; + int32_t mAbsMTWidthMinor = 0; + int32_t mAbsMTOrientation = 0; + int32_t mAbsMTTrackingId = -1; + int32_t mAbsMTPressure = 0; + int32_t mAbsMTDistance = 0; + int32_t mAbsMTToolType = 0; + + void clear() { *this = Slot(); } }; MultiTouchMotionAccumulator(); - ~MultiTouchMotionAccumulator(); void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol); void reset(InputDeviceContext& deviceContext); @@ -76,13 +74,15 @@ public: void finishSync(); bool hasStylus() const; - inline size_t getSlotCount() const { return mSlotCount; } - inline const Slot* getSlot(size_t index) const { return &mSlots[index]; } + inline size_t getSlotCount() const { return mSlots.size(); } + inline const Slot& getSlot(size_t index) const { + LOG_ALWAYS_FATAL_IF(index < 0 || index >= mSlots.size(), "Invalid index: %zu", index); + return mSlots[index]; + } private: int32_t mCurrentSlot; - Slot* mSlots; - size_t mSlotCount; + std::vector mSlots; bool mUsingSlotsProtocol; bool mHaveStylus; -- GitLab From f502922b5a4869c399bebd2254039183b58c8a7d Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Thu, 16 Dec 2021 14:33:46 -0800 Subject: [PATCH 0004/1310] Migrate screenshot methods to AIDL Additonal service, named as "SurfaceFlingerAIDL", is added to surfaceflinger during the process of migrating ISurfaceComposer interface to AIDL. New changes are put into namespace, android::gui. Once migration is complete, this service will be deleted. This CL migrates Screenshot methods to AIDL, more will come. Bug: 211037638 Test: screencap Change-Id: Idee91fa2444646639735847b1c76e983af39227f --- libs/gui/ISurfaceComposer.cpp | 62 +------------ libs/gui/LayerState.cpp | 92 ++++++++++--------- libs/gui/SurfaceComposerClient.cpp | 63 +++++++++++-- .../aidl/android/gui/DisplayCaptureArgs.aidl | 19 ++++ .../aidl/android/gui/ISurfaceComposer.aidl | 42 +++++++++ .../aidl/android/gui/LayerCaptureArgs.aidl | 19 ++++ libs/gui/include/gui/DisplayCaptureArgs.h | 71 ++++++++++++++ libs/gui/include/gui/ISurfaceComposer.h | 36 ++------ libs/gui/include/gui/LayerCaptureArgs.h | 37 ++++++++ libs/gui/include/gui/LayerState.h | 52 +---------- libs/gui/include/gui/SurfaceComposerClient.h | 2 + .../gui/include/private/gui/ComposerService.h | 2 +- .../include/private/gui/ComposerServiceAIDL.h | 56 +++++++++++ libs/gui/tests/BLASTBufferQueue_test.cpp | 9 +- libs/gui/tests/Surface_test.cpp | 19 +--- services/surfaceflinger/Android.bp | 1 + services/surfaceflinger/SurfaceFlinger.cpp | 39 +++++++- services/surfaceflinger/SurfaceFlinger.h | 27 +++++- .../surfaceflinger/main_surfaceflinger.cpp | 5 + .../surfaceflinger/tests/LayerState_test.cpp | 10 +- .../tests/utils/ScreenshotUtils.h | 17 ++-- 21 files changed, 455 insertions(+), 225 deletions(-) create mode 100644 libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl create mode 100644 libs/gui/aidl/android/gui/ISurfaceComposer.aidl create mode 100644 libs/gui/aidl/android/gui/LayerCaptureArgs.aidl create mode 100644 libs/gui/include/gui/DisplayCaptureArgs.h create mode 100644 libs/gui/include/gui/LayerCaptureArgs.h create mode 100644 libs/gui/include/private/gui/ComposerServiceAIDL.h diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index 75c5e26fb0..5ab0abc561 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -46,9 +46,11 @@ using namespace aidl::android::hardware::graphics; namespace android { +using gui::DisplayCaptureArgs; using gui::IDisplayEventConnection; using gui::IRegionSamplingListener; using gui::IWindowInfosListener; +using gui::LayerCaptureArgs; using ui::ColorMode; class BpSurfaceComposer : public BpInterface @@ -118,36 +120,6 @@ public: remote()->transact(BnSurfaceComposer::BOOT_FINISHED, data, &reply); } - status_t captureDisplay(const DisplayCaptureArgs& args, - const sp& captureListener) override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(args.write, data); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(captureListener)); - - return remote()->transact(BnSurfaceComposer::CAPTURE_DISPLAY, data, &reply); - } - - status_t captureDisplay(DisplayId displayId, - const sp& captureListener) override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeUint64, displayId.value); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(captureListener)); - - return remote()->transact(BnSurfaceComposer::CAPTURE_DISPLAY_BY_ID, data, &reply); - } - - status_t captureLayers(const LayerCaptureArgs& args, - const sp& captureListener) override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(args.write, data); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(captureListener)); - - return remote()->transact(BnSurfaceComposer::CAPTURE_LAYERS, data, &reply); - } - bool authenticateSurfaceTexture( const sp& bufferProducer) const override { Parcel data, reply; @@ -1451,36 +1423,6 @@ status_t BnSurfaceComposer::onTransact( bootFinished(); return NO_ERROR; } - case CAPTURE_DISPLAY: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - DisplayCaptureArgs args; - sp captureListener; - SAFE_PARCEL(args.read, data); - SAFE_PARCEL(data.readStrongBinder, &captureListener); - - return captureDisplay(args, captureListener); - } - case CAPTURE_DISPLAY_BY_ID: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - uint64_t value; - SAFE_PARCEL(data.readUint64, &value); - const auto id = DisplayId::fromValue(value); - if (!id) return BAD_VALUE; - - sp captureListener; - SAFE_PARCEL(data.readStrongBinder, &captureListener); - - return captureDisplay(*id, captureListener); - } - case CAPTURE_LAYERS: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - LayerCaptureArgs args; - sp captureListener; - SAFE_PARCEL(args.read, data); - SAFE_PARCEL(data.readStrongBinder, &captureListener); - - return captureLayers(args, captureListener); - } case AUTHENTICATE_SURFACE: { CHECK_INTERFACE(ISurfaceComposer, data, reply); sp bufferProducer = diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 9022e7d5a4..6944d38dfe 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -686,85 +686,89 @@ bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrame // ---------------------------------------------------------------------------- -status_t CaptureArgs::write(Parcel& output) const { - SAFE_PARCEL(output.writeInt32, static_cast(pixelFormat)); - SAFE_PARCEL(output.write, sourceCrop); - SAFE_PARCEL(output.writeFloat, frameScaleX); - SAFE_PARCEL(output.writeFloat, frameScaleY); - SAFE_PARCEL(output.writeBool, captureSecureLayers); - SAFE_PARCEL(output.writeInt32, uid); - SAFE_PARCEL(output.writeInt32, static_cast(dataspace)); - SAFE_PARCEL(output.writeBool, allowProtected); - SAFE_PARCEL(output.writeBool, grayscale); +namespace gui { + +status_t CaptureArgs::writeToParcel(Parcel* output) const { + SAFE_PARCEL(output->writeInt32, static_cast(pixelFormat)); + SAFE_PARCEL(output->write, sourceCrop); + SAFE_PARCEL(output->writeFloat, frameScaleX); + SAFE_PARCEL(output->writeFloat, frameScaleY); + SAFE_PARCEL(output->writeBool, captureSecureLayers); + SAFE_PARCEL(output->writeInt32, uid); + SAFE_PARCEL(output->writeInt32, static_cast(dataspace)); + SAFE_PARCEL(output->writeBool, allowProtected); + SAFE_PARCEL(output->writeBool, grayscale); return NO_ERROR; } -status_t CaptureArgs::read(const Parcel& input) { +status_t CaptureArgs::readFromParcel(const Parcel* input) { int32_t value = 0; - SAFE_PARCEL(input.readInt32, &value); + SAFE_PARCEL(input->readInt32, &value); pixelFormat = static_cast(value); - SAFE_PARCEL(input.read, sourceCrop); - SAFE_PARCEL(input.readFloat, &frameScaleX); - SAFE_PARCEL(input.readFloat, &frameScaleY); - SAFE_PARCEL(input.readBool, &captureSecureLayers); - SAFE_PARCEL(input.readInt32, &uid); - SAFE_PARCEL(input.readInt32, &value); + SAFE_PARCEL(input->read, sourceCrop); + SAFE_PARCEL(input->readFloat, &frameScaleX); + SAFE_PARCEL(input->readFloat, &frameScaleY); + SAFE_PARCEL(input->readBool, &captureSecureLayers); + SAFE_PARCEL(input->readInt32, &uid); + SAFE_PARCEL(input->readInt32, &value); dataspace = static_cast(value); - SAFE_PARCEL(input.readBool, &allowProtected); - SAFE_PARCEL(input.readBool, &grayscale); + SAFE_PARCEL(input->readBool, &allowProtected); + SAFE_PARCEL(input->readBool, &grayscale); return NO_ERROR; } -status_t DisplayCaptureArgs::write(Parcel& output) const { - SAFE_PARCEL(CaptureArgs::write, output); +status_t DisplayCaptureArgs::writeToParcel(Parcel* output) const { + SAFE_PARCEL(CaptureArgs::writeToParcel, output); - SAFE_PARCEL(output.writeStrongBinder, displayToken); - SAFE_PARCEL(output.writeUint32, width); - SAFE_PARCEL(output.writeUint32, height); - SAFE_PARCEL(output.writeBool, useIdentityTransform); + SAFE_PARCEL(output->writeStrongBinder, displayToken); + SAFE_PARCEL(output->writeUint32, width); + SAFE_PARCEL(output->writeUint32, height); + SAFE_PARCEL(output->writeBool, useIdentityTransform); return NO_ERROR; } -status_t DisplayCaptureArgs::read(const Parcel& input) { - SAFE_PARCEL(CaptureArgs::read, input); +status_t DisplayCaptureArgs::readFromParcel(const Parcel* input) { + SAFE_PARCEL(CaptureArgs::readFromParcel, input); - SAFE_PARCEL(input.readStrongBinder, &displayToken); - SAFE_PARCEL(input.readUint32, &width); - SAFE_PARCEL(input.readUint32, &height); - SAFE_PARCEL(input.readBool, &useIdentityTransform); + SAFE_PARCEL(input->readStrongBinder, &displayToken); + SAFE_PARCEL(input->readUint32, &width); + SAFE_PARCEL(input->readUint32, &height); + SAFE_PARCEL(input->readBool, &useIdentityTransform); return NO_ERROR; } -status_t LayerCaptureArgs::write(Parcel& output) const { - SAFE_PARCEL(CaptureArgs::write, output); +status_t LayerCaptureArgs::writeToParcel(Parcel* output) const { + SAFE_PARCEL(CaptureArgs::writeToParcel, output); - SAFE_PARCEL(output.writeStrongBinder, layerHandle); - SAFE_PARCEL(output.writeInt32, excludeHandles.size()); + SAFE_PARCEL(output->writeStrongBinder, layerHandle); + SAFE_PARCEL(output->writeInt32, excludeHandles.size()); for (auto el : excludeHandles) { - SAFE_PARCEL(output.writeStrongBinder, el); + SAFE_PARCEL(output->writeStrongBinder, el); } - SAFE_PARCEL(output.writeBool, childrenOnly); + SAFE_PARCEL(output->writeBool, childrenOnly); return NO_ERROR; } -status_t LayerCaptureArgs::read(const Parcel& input) { - SAFE_PARCEL(CaptureArgs::read, input); +status_t LayerCaptureArgs::readFromParcel(const Parcel* input) { + SAFE_PARCEL(CaptureArgs::readFromParcel, input); - SAFE_PARCEL(input.readStrongBinder, &layerHandle); + SAFE_PARCEL(input->readStrongBinder, &layerHandle); int32_t numExcludeHandles = 0; - SAFE_PARCEL_READ_SIZE(input.readInt32, &numExcludeHandles, input.dataSize()); + SAFE_PARCEL_READ_SIZE(input->readInt32, &numExcludeHandles, input->dataSize()); excludeHandles.reserve(numExcludeHandles); for (int i = 0; i < numExcludeHandles; i++) { sp binder; - SAFE_PARCEL(input.readStrongBinder, &binder); + SAFE_PARCEL(input->readStrongBinder, &binder); excludeHandles.emplace(binder); } - SAFE_PARCEL(input.readBool, &childrenOnly); + SAFE_PARCEL(input->readBool, &childrenOnly); return NO_ERROR; } +}; // namespace gui + ReleaseCallbackId BufferData::generateReleaseCallbackId() const { return {buffer->getId(), frameNumber}; } diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 9269c3e5a7..26ccda580a 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -46,6 +46,7 @@ #include #include +#include // This server size should always be smaller than the server cache size #define BUFFER_CACHE_MAX_SIZE 64 @@ -62,6 +63,7 @@ using ui::ColorMode; // --------------------------------------------------------------------------- ANDROID_SINGLETON_STATIC_INSTANCE(ComposerService); +ANDROID_SINGLETON_STATIC_INSTANCE(ComposerServiceAIDL); namespace { // Initialize transaction id counter used to generate transaction ids @@ -120,6 +122,52 @@ void ComposerService::composerServiceDied() mDeathObserver = nullptr; } +ComposerServiceAIDL::ComposerServiceAIDL() : Singleton() { + std::scoped_lock lock(mMutex); + connectLocked(); +} + +bool ComposerServiceAIDL::connectLocked() { + const String16 name("SurfaceFlingerAIDL"); + mComposerService = waitForService(name); + if (mComposerService == nullptr) { + return false; // fatal error or permission problem + } + + // Create the death listener. + class DeathObserver : public IBinder::DeathRecipient { + ComposerServiceAIDL& mComposerService; + virtual void binderDied(const wp& who) { + ALOGW("ComposerService aidl remote (surfaceflinger) died [%p]", who.unsafe_get()); + mComposerService.composerServiceDied(); + } + + public: + explicit DeathObserver(ComposerServiceAIDL& mgr) : mComposerService(mgr) {} + }; + + mDeathObserver = new DeathObserver(*const_cast(this)); + IInterface::asBinder(mComposerService)->linkToDeath(mDeathObserver); + return true; +} + +/*static*/ sp ComposerServiceAIDL::getComposerService() { + ComposerServiceAIDL& instance = ComposerServiceAIDL::getInstance(); + std::scoped_lock lock(instance.mMutex); + if (instance.mComposerService == nullptr) { + if (ComposerServiceAIDL::getInstance().connectLocked()) { + ALOGD("ComposerServiceAIDL reconnected"); + } + } + return instance.mComposerService; +} + +void ComposerServiceAIDL::composerServiceDied() { + std::scoped_lock lock(mMutex); + mComposerService = nullptr; + mDeathObserver = nullptr; +} + class DefaultComposerClient: public Singleton { Mutex mLock; sp mClient; @@ -2267,26 +2315,29 @@ status_t SurfaceComposerClient::removeWindowInfosListener( status_t ScreenshotClient::captureDisplay(const DisplayCaptureArgs& captureArgs, const sp& captureListener) { - sp s(ComposerService::getComposerService()); + sp s(ComposerServiceAIDL::getComposerService()); if (s == nullptr) return NO_INIT; - return s->captureDisplay(captureArgs, captureListener); + binder::Status status = s->captureDisplay(captureArgs, captureListener); + return status.transactionError(); } status_t ScreenshotClient::captureDisplay(DisplayId displayId, const sp& captureListener) { - sp s(ComposerService::getComposerService()); + sp s(ComposerServiceAIDL::getComposerService()); if (s == nullptr) return NO_INIT; - return s->captureDisplay(displayId, captureListener); + binder::Status status = s->captureDisplayById(displayId.value, captureListener); + return status.transactionError(); } status_t ScreenshotClient::captureLayers(const LayerCaptureArgs& captureArgs, const sp& captureListener) { - sp s(ComposerService::getComposerService()); + sp s(ComposerServiceAIDL::getComposerService()); if (s == nullptr) return NO_INIT; - return s->captureLayers(captureArgs, captureListener); + binder::Status status = s->captureLayers(captureArgs, captureListener); + return status.transactionError(); } // --------------------------------------------------------------------------------- diff --git a/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl b/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl new file mode 100644 index 0000000000..2caa2b9f61 --- /dev/null +++ b/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2021 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; + +parcelable DisplayCaptureArgs cpp_header "gui/DisplayCaptureArgs.h"; diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl new file mode 100644 index 0000000000..07921a59a5 --- /dev/null +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -0,0 +1,42 @@ +/* + * Copyright 2021 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.DisplayCaptureArgs; +import android.gui.LayerCaptureArgs; +import android.gui.IScreenCaptureListener; + +/** @hide */ +interface ISurfaceComposer { + /** + * 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. + * + * This function can capture a subregion (the source crop) of the screen. + * The subregion can be optionally rotated. It will also be scaled to + * match the size of the output buffer. + */ + void captureDisplay(in DisplayCaptureArgs args, IScreenCaptureListener listener); + void captureDisplayById(long displayId, IScreenCaptureListener listener); + /** + * Capture a subtree of the layer hierarchy, potentially ignoring the root node. + * This requires READ_FRAME_BUFFER permission. This function will fail if there + * is a secure window on screen + */ + void captureLayers(in LayerCaptureArgs args, IScreenCaptureListener listener); +} diff --git a/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl b/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl new file mode 100644 index 0000000000..f0def5019a --- /dev/null +++ b/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2021 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; + +parcelable LayerCaptureArgs cpp_header "gui/LayerCaptureArgs.h"; diff --git a/libs/gui/include/gui/DisplayCaptureArgs.h b/libs/gui/include/gui/DisplayCaptureArgs.h new file mode 100644 index 0000000000..ec884cfa8c --- /dev/null +++ b/libs/gui/include/gui/DisplayCaptureArgs.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +namespace android::gui { + +struct CaptureArgs : public Parcelable { + const static int32_t UNSET_UID = -1; + virtual ~CaptureArgs() = default; + + ui::PixelFormat pixelFormat{ui::PixelFormat::RGBA_8888}; + Rect sourceCrop; + float frameScaleX{1}; + float frameScaleY{1}; + bool captureSecureLayers{false}; + int32_t uid{UNSET_UID}; + // Force capture to be in a color space. If the value is ui::Dataspace::UNKNOWN, the captured + // result will be in the display's colorspace. + // The display may use non-RGB dataspace (ex. displayP3) that could cause pixel data could be + // different from SRGB (byte per color), and failed when checking colors in tests. + // NOTE: In normal cases, we want the screen to be captured in display's colorspace. + ui::Dataspace dataspace = ui::Dataspace::UNKNOWN; + + // The receiver of the capture can handle protected buffer. A protected buffer has + // GRALLOC_USAGE_PROTECTED usage bit and must not be accessed unprotected behaviour. + // Any read/write access from unprotected context will result in undefined behaviour. + // Protected contents are typically DRM contents. This has no direct implication to the + // secure property of the surface, which is specified by the application explicitly to avoid + // the contents being accessed/captured by screenshot or unsecure display. + bool allowProtected = false; + + bool grayscale = false; + + virtual status_t writeToParcel(Parcel* output) const; + virtual status_t readFromParcel(const Parcel* input); +}; + +struct DisplayCaptureArgs : CaptureArgs { + sp displayToken; + uint32_t width{0}; + uint32_t height{0}; + bool useIdentityTransform{false}; + + status_t writeToParcel(Parcel* output) const override; + status_t readFromParcel(const Parcel* input) override; +}; + +}; // namespace android::gui diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index 0a59f52fcd..4dfc383b57 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -58,11 +58,9 @@ namespace android { struct client_cache_t; struct ComposerState; -struct DisplayCaptureArgs; struct DisplayStatInfo; struct DisplayState; struct InputWindowCommands; -struct LayerCaptureArgs; class LayerDebugInfo; class HdrCapabilities; class IGraphicBufferProducer; @@ -75,6 +73,13 @@ using gui::IRegionSamplingListener; using gui::IScreenCaptureListener; using gui::SpHash; +namespace gui { + +struct DisplayCaptureArgs; +struct LayerCaptureArgs; + +} // namespace gui + namespace ui { struct DisplayMode; @@ -261,27 +266,6 @@ public: */ virtual void setGameContentType(const sp& display, bool on) = 0; - /** - * 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. - * - * This function can capture a subregion (the source crop) of the screen. - * The subregion can be optionally rotated. It will also be scaled to - * match the size of the output buffer. - */ - virtual status_t captureDisplay(const DisplayCaptureArgs&, - const sp&) = 0; - - virtual status_t captureDisplay(DisplayId, const sp&) = 0; - - /** - * Capture a subtree of the layer hierarchy, potentially ignoring the root node. - * This requires READ_FRAME_BUFFER permission. This function will fail if there - * is a secure window on screen - */ - virtual status_t captureLayers(const LayerCaptureArgs&, const sp&) = 0; - /* Clears the frame statistics for animations. * * Requires the ACCESS_SURFACE_FLINGER permission. @@ -621,8 +605,8 @@ public: GET_DISPLAY_MODES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. GET_ACTIVE_DISPLAY_MODE, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. GET_DISPLAY_STATE, - CAPTURE_DISPLAY, - CAPTURE_LAYERS, + CAPTURE_DISPLAY, // Deprecated. Autogenerated by .aidl now. + CAPTURE_LAYERS, // Deprecated. Autogenerated by .aidl now. CLEAR_ANIMATION_FRAME_STATS, GET_ANIMATION_FRAME_STATS, SET_POWER_MODE, @@ -649,7 +633,7 @@ public: GET_DESIRED_DISPLAY_MODE_SPECS, GET_DISPLAY_BRIGHTNESS_SUPPORT, SET_DISPLAY_BRIGHTNESS, - CAPTURE_DISPLAY_BY_ID, + CAPTURE_DISPLAY_BY_ID, // Deprecated. Autogenerated by .aidl now. NOTIFY_POWER_BOOST, SET_GLOBAL_SHADOW_SETTINGS, GET_AUTO_LOW_LATENCY_MODE_SUPPORT, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. diff --git a/libs/gui/include/gui/LayerCaptureArgs.h b/libs/gui/include/gui/LayerCaptureArgs.h new file mode 100644 index 0000000000..05ff9d5b7b --- /dev/null +++ b/libs/gui/include/gui/LayerCaptureArgs.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#include +#include + +namespace android::gui { + +struct LayerCaptureArgs : CaptureArgs { + sp layerHandle; + std::unordered_set, SpHash> excludeHandles; + bool childrenOnly{false}; + + status_t writeToParcel(Parcel* output) const override; + status_t readFromParcel(const Parcel* input) override; +}; + +}; // namespace android::gui diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index f7206193cd..7a36fdaaec 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -29,7 +29,9 @@ #include #include +#include #include +#include #include #include #include @@ -370,56 +372,6 @@ static inline int compare_type(const DisplayState& lhs, const DisplayState& rhs) bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy, const char* functionName, bool privileged = false); -struct CaptureArgs { - const static int32_t UNSET_UID = -1; - virtual ~CaptureArgs() = default; - - ui::PixelFormat pixelFormat{ui::PixelFormat::RGBA_8888}; - Rect sourceCrop; - float frameScaleX{1}; - float frameScaleY{1}; - bool captureSecureLayers{false}; - int32_t uid{UNSET_UID}; - // Force capture to be in a color space. If the value is ui::Dataspace::UNKNOWN, the captured - // result will be in the display's colorspace. - // The display may use non-RGB dataspace (ex. displayP3) that could cause pixel data could be - // different from SRGB (byte per color), and failed when checking colors in tests. - // NOTE: In normal cases, we want the screen to be captured in display's colorspace. - ui::Dataspace dataspace = ui::Dataspace::UNKNOWN; - - // The receiver of the capture can handle protected buffer. A protected buffer has - // GRALLOC_USAGE_PROTECTED usage bit and must not be accessed unprotected behaviour. - // Any read/write access from unprotected context will result in undefined behaviour. - // Protected contents are typically DRM contents. This has no direct implication to the - // secure property of the surface, which is specified by the application explicitly to avoid - // the contents being accessed/captured by screenshot or unsecure display. - bool allowProtected = false; - - bool grayscale = false; - - virtual status_t write(Parcel& output) const; - virtual status_t read(const Parcel& input); -}; - -struct DisplayCaptureArgs : CaptureArgs { - sp displayToken; - uint32_t width{0}; - uint32_t height{0}; - bool useIdentityTransform{false}; - - status_t write(Parcel& output) const override; - status_t read(const Parcel& input) override; -}; - -struct LayerCaptureArgs : CaptureArgs { - sp layerHandle; - std::unordered_set, SpHash> excludeHandles; - bool childrenOnly{false}; - - status_t write(Parcel& output) const override; - status_t read(const Parcel& input) override; -}; - }; // namespace android #endif // ANDROID_SF_LAYER_STATE_H diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 25637efce2..6c79b5bbbc 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -57,7 +57,9 @@ class IGraphicBufferProducer; class ITunnelModeEnabledListener; class Region; +using gui::DisplayCaptureArgs; using gui::IRegionSamplingListener; +using gui::LayerCaptureArgs; struct SurfaceControlStats { SurfaceControlStats(const sp& sc, nsecs_t latchTime, diff --git a/libs/gui/include/private/gui/ComposerService.h b/libs/gui/include/private/gui/ComposerService.h index fa1071a4e3..05ed0a0576 100644 --- a/libs/gui/include/private/gui/ComposerService.h +++ b/libs/gui/include/private/gui/ComposerService.h @@ -37,7 +37,7 @@ class ISurfaceComposer; // Users of this class should not retain the value from // getComposerService() for an extended period. // -// (It's not clear that using Singleton is useful here anymore.) +// (TODO: b/219785927, It's not clear that using Singleton is useful here anymore.) class ComposerService : public Singleton { sp mComposerService; diff --git a/libs/gui/include/private/gui/ComposerServiceAIDL.h b/libs/gui/include/private/gui/ComposerServiceAIDL.h new file mode 100644 index 0000000000..fee37eefe0 --- /dev/null +++ b/libs/gui/include/private/gui/ComposerServiceAIDL.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 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 +#include + +#include + +#include +#include + +namespace android { + +// --------------------------------------------------------------------------- + +// --------------------------------------------------------------------------- + +// This holds our connection to the composer service (i.e. SurfaceFlinger). +// If the remote side goes away, we will re-establish the connection. +// Users of this class should not retain the value from +// getComposerService() for an extended period. +// +// (TODO: b/219785927, It's not clear that using Singleton is useful here anymore.) +class ComposerServiceAIDL : public Singleton { + sp mComposerService; + sp mDeathObserver; + mutable std::mutex mMutex; + + ComposerServiceAIDL(); + bool connectLocked(); + void composerServiceDied(); + friend class Singleton; + +public: + // Get a connection to the Composer Service. This will block until + // a connection is established. Returns null if permission is denied. + static sp getComposerService(); +}; + +// --------------------------------------------------------------------------- +}; // namespace android diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp index 42a32f3b42..179bdd76aa 100644 --- a/libs/gui/tests/BLASTBufferQueue_test.cpp +++ b/libs/gui/tests/BLASTBufferQueue_test.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -283,13 +284,13 @@ protected: static status_t captureDisplay(DisplayCaptureArgs& captureArgs, ScreenCaptureResults& captureResults) { - const auto sf = ComposerService::getComposerService(); + const auto sf = ComposerServiceAIDL::getComposerService(); SurfaceComposerClient::Transaction().apply(true); const sp captureListener = new SyncScreenCaptureListener(); - status_t status = sf->captureDisplay(captureArgs, captureListener); - if (status != NO_ERROR) { - return status; + binder::Status status = sf->captureDisplay(captureArgs, captureListener); + if (status.transactionError() != NO_ERROR) { + return status.transactionError(); } captureResults = captureListener->waitForResults(); return captureResults.result; diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 605628096d..a885e926a3 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -205,13 +206,13 @@ protected: static status_t captureDisplay(DisplayCaptureArgs& captureArgs, ScreenCaptureResults& captureResults) { - const auto sf = ComposerService::getComposerService(); + const auto sf = ComposerServiceAIDL::getComposerService(); SurfaceComposerClient::Transaction().apply(true); const sp captureListener = new SyncScreenCaptureListener(); - status_t status = sf->captureDisplay(captureArgs, captureListener); - if (status != NO_ERROR) { - return status; + binder::Status status = sf->captureDisplay(captureArgs, captureListener); + if (status.transactionError() != NO_ERROR) { + return status.transactionError(); } captureResults = captureListener->waitForResults(); return captureResults.result; @@ -766,16 +767,6 @@ public: void setAutoLowLatencyMode(const sp& /*display*/, bool /*on*/) override {} void setGameContentType(const sp& /*display*/, bool /*on*/) override {} - status_t captureDisplay(const DisplayCaptureArgs&, const sp&) override { - return NO_ERROR; - } - status_t captureDisplay(DisplayId, const sp&) override { - return NO_ERROR; - } - status_t captureLayers(const LayerCaptureArgs&, const sp&) override { - return NO_ERROR; - } - status_t clearAnimationFrameStats() override { return NO_ERROR; } status_t getAnimationFrameStats(FrameStats* /*outStats*/) const override { return NO_ERROR; diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index d9958f31c5..000a2cb0d3 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -76,6 +76,7 @@ cc_defaults { "libaidlcommonsupport", "libcompositionengine", "libframetimeline", + "libgui_aidl_static", "libperfetto_client_experimental", "librenderengine", "libscheduler", diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 59c07b6db1..1227edf7a2 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5472,9 +5472,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case SET_FRAME_RATE: case GET_DISPLAY_BRIGHTNESS_SUPPORT: case GET_DISPLAY_DECORATION_SUPPORT: - // captureLayers and captureDisplay will handle the permission check in the function - case CAPTURE_LAYERS: - case CAPTURE_DISPLAY: case SET_FRAME_TIMELINE_INFO: case GET_GPU_CONTEXT_PRIORITY: case GET_MAX_ACQUIRED_BUFFER_COUNT: { @@ -5509,8 +5506,7 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { } return OK; } - case ADD_TRANSACTION_TRACE_LISTENER: - case CAPTURE_DISPLAY_BY_ID: { + case ADD_TRANSACTION_TRACE_LISTENER: { IPCThreadState* ipc = IPCThreadState::self(); const int uid = ipc->getCallingUid(); if (uid == AID_ROOT || uid == AID_GRAPHICS || uid == AID_SYSTEM || uid == AID_SHELL) { @@ -5540,6 +5536,11 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { } return PERMISSION_DENIED; } + case CAPTURE_LAYERS: + case CAPTURE_DISPLAY: + case CAPTURE_DISPLAY_BY_ID: + LOG_FATAL("Deprecated opcode: %d", code); + return PERMISSION_DENIED; } // These codes are used for the IBinder protocol to either interrogate the recipient @@ -7188,6 +7189,34 @@ bool SurfaceFlinger::commitCreatedLayers() { mLayersAdded = true; return true; } + +// gui::ISurfaceComposer +binder::Status SurfaceComposerAIDL::captureDisplay( + const DisplayCaptureArgs& args, const sp& captureListener) { + status_t status = mFlinger->captureDisplay(args, captureListener); + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::captureDisplayById( + int64_t displayId, const sp& captureListener) { + status_t status; + IPCThreadState* ipc = IPCThreadState::self(); + const int uid = ipc->getCallingUid(); + if (uid == AID_ROOT || uid == AID_GRAPHICS || uid == AID_SYSTEM || uid == AID_SHELL) { + std::optional id = DisplayId::fromValue(static_cast(displayId)); + status = mFlinger->captureDisplay(*id, captureListener); + } else { + status = PERMISSION_DENIED; + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::captureLayers( + const LayerCaptureArgs& args, const sp& captureListener) { + status_t status = mFlinger->captureLayers(args, captureListener); + return binder::Status::fromStatusT(status); +} + } // namespace android #if defined(__gl_h_) diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 3ecce33d33..f09ee5fad0 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -23,6 +23,7 @@ */ #include +#include #include #include #include @@ -106,9 +107,13 @@ class RegionSamplingThread; class RenderArea; class TimeStats; class FrameTracer; +class ScreenCapturer; class WindowInfosListenerInvoker; +using gui::CaptureArgs; +using gui::DisplayCaptureArgs; using gui::IRegionSamplingListener; +using gui::LayerCaptureArgs; using gui::ScreenCaptureResults; namespace frametimeline { @@ -540,9 +545,9 @@ private: ISurfaceComposer::VsyncSource vsyncSource = eVsyncSourceApp, ISurfaceComposer::EventRegistrationFlags eventRegistration = {}) override; - status_t captureDisplay(const DisplayCaptureArgs&, const sp&) override; - status_t captureDisplay(DisplayId, const sp&) override; - status_t captureLayers(const LayerCaptureArgs&, const sp&) override; + status_t captureDisplay(const DisplayCaptureArgs&, const sp&); + status_t captureDisplay(DisplayId, const sp&); + status_t captureLayers(const LayerCaptureArgs&, const sp&); status_t getDisplayStats(const sp& displayToken, DisplayStatInfo* stats) override; status_t getDisplayState(const sp& displayToken, ui::DisplayState*) @@ -1401,6 +1406,22 @@ private: } mPowerHintSessionData GUARDED_BY(SF_MAIN_THREAD); nsecs_t mAnimationTransactionTimeout = s2ns(5); + + friend class SurfaceComposerAIDL; +}; + +class SurfaceComposerAIDL : public gui::BnSurfaceComposer { +public: + SurfaceComposerAIDL(sp sf) { mFlinger = sf; } + + binder::Status captureDisplay(const DisplayCaptureArgs&, + const sp&) override; + binder::Status captureDisplayById(int64_t, const sp&) override; + binder::Status captureLayers(const LayerCaptureArgs&, + const sp&) override; + +private: + sp mFlinger; }; } // namespace android diff --git a/services/surfaceflinger/main_surfaceflinger.cpp b/services/surfaceflinger/main_surfaceflinger.cpp index caeff4ab21..ec180548e1 100644 --- a/services/surfaceflinger/main_surfaceflinger.cpp +++ b/services/surfaceflinger/main_surfaceflinger.cpp @@ -152,6 +152,11 @@ int main(int, char**) { sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false, IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO); + // publish gui::ISurfaceComposer, the new AIDL interface + sp composerAIDL = new SurfaceComposerAIDL(flinger); + sm->addService(String16("SurfaceFlingerAIDL"), composerAIDL, false, + IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO); + startDisplayService(); // dependency on SF getting registered above if (SurfaceFlinger::setSchedFifo(true) != NO_ERROR) { diff --git a/services/surfaceflinger/tests/LayerState_test.cpp b/services/surfaceflinger/tests/LayerState_test.cpp index fa1a5ed6b0..094b0ffc0f 100644 --- a/services/surfaceflinger/tests/LayerState_test.cpp +++ b/services/surfaceflinger/tests/LayerState_test.cpp @@ -22,6 +22,8 @@ #include namespace android { +using gui::DisplayCaptureArgs; +using gui::LayerCaptureArgs; using gui::ScreenCaptureResults; namespace test { @@ -40,11 +42,11 @@ TEST(LayerStateTest, ParcellingDisplayCaptureArgs) { args.grayscale = true; Parcel p; - args.write(p); + args.writeToParcel(&p); p.setDataPosition(0); DisplayCaptureArgs args2; - args2.read(p); + args2.readFromParcel(&p); ASSERT_EQ(args.pixelFormat, args2.pixelFormat); ASSERT_EQ(args.sourceCrop, args2.sourceCrop); @@ -71,11 +73,11 @@ TEST(LayerStateTest, ParcellingLayerCaptureArgs) { args.grayscale = true; Parcel p; - args.write(p); + args.writeToParcel(&p); p.setDataPosition(0); LayerCaptureArgs args2; - args2.read(p); + args2.readFromParcel(&p); ASSERT_EQ(args.pixelFormat, args2.pixelFormat); ASSERT_EQ(args.sourceCrop, args2.sourceCrop); diff --git a/services/surfaceflinger/tests/utils/ScreenshotUtils.h b/services/surfaceflinger/tests/utils/ScreenshotUtils.h index cae76849bf..ee7e92cbf6 100644 --- a/services/surfaceflinger/tests/utils/ScreenshotUtils.h +++ b/services/surfaceflinger/tests/utils/ScreenshotUtils.h @@ -16,6 +16,7 @@ #pragma once #include +#include #include #include #include @@ -31,15 +32,15 @@ class ScreenCapture : public RefBase { public: static status_t captureDisplay(DisplayCaptureArgs& captureArgs, ScreenCaptureResults& captureResults) { - const auto sf = ComposerService::getComposerService(); + const auto sf = ComposerServiceAIDL::getComposerService(); SurfaceComposerClient::Transaction().apply(true); captureArgs.dataspace = ui::Dataspace::V0_SRGB; const sp captureListener = new SyncScreenCaptureListener(); - status_t status = sf->captureDisplay(captureArgs, captureListener); + binder::Status status = sf->captureDisplay(captureArgs, captureListener); - if (status != NO_ERROR) { - return status; + if (status.transactionError() != NO_ERROR) { + return status.transactionError(); } captureResults = captureListener->waitForResults(); return captureResults.result; @@ -64,14 +65,14 @@ public: static status_t captureLayers(LayerCaptureArgs& captureArgs, ScreenCaptureResults& captureResults) { - const auto sf = ComposerService::getComposerService(); + const auto sf = ComposerServiceAIDL::getComposerService(); SurfaceComposerClient::Transaction().apply(true); captureArgs.dataspace = ui::Dataspace::V0_SRGB; const sp captureListener = new SyncScreenCaptureListener(); - status_t status = sf->captureLayers(captureArgs, captureListener); - if (status != NO_ERROR) { - return status; + binder::Status status = sf->captureLayers(captureArgs, captureListener); + if (status.transactionError() != NO_ERROR) { + return status.transactionError(); } captureResults = captureListener->waitForResults(); return captureResults.result; -- GitLab From 083bb215bffd974cc81e9de80ccfd452f845cf19 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 15 Feb 2022 01:46:16 -0800 Subject: [PATCH 0005/1310] Support ANRs from windows that are not tracked by WM Previously, ANRs for gesture monitors were reported using the pid of the owner, and ANRs for all windows were reported using its input channel, which was tracked by WM. Now, there can be input windows that are not tracked by WM. We unify the ANR reporting pipeline so that we first try to report an ANR using the window's input channel. If the ANR reporting fails because the input channel was not tracked by WM, we fall back on reporting ANR via the pid of the window owner. Bug: 210978621 Test: atest inputflinger_tests Change-Id: I7f71186e042b0fc9df7d342a549ef609ff4862ae --- .../benchmarks/InputDispatcher_benchmarks.cpp | 11 +- .../dispatcher/InputDispatcher.cpp | 66 ++--- .../inputflinger/dispatcher/InputDispatcher.h | 8 +- .../include/InputDispatcherPolicyInterface.h | 23 +- .../tests/InputDispatcher_test.cpp | 266 +++++++++--------- 5 files changed, 170 insertions(+), 204 deletions(-) diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp index cd20a646a7..9691ad8a79 100644 --- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp +++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp @@ -61,18 +61,13 @@ private: ALOGE("There is no focused window for %s", applicationHandle->getName().c_str()); } - void notifyWindowUnresponsive(const sp& connectionToken, + void notifyWindowUnresponsive(const sp& connectionToken, std::optional pid, const std::string& reason) override { ALOGE("Window is not responding: %s", reason.c_str()); } - void notifyWindowResponsive(const sp& connectionToken) override {} - - void notifyMonitorUnresponsive(int32_t pid, const std::string& reason) override { - ALOGE("Monitor is not responding: %s", reason.c_str()); - } - - void notifyMonitorResponsive(int32_t pid) override {} + void notifyWindowResponsive(const sp& connectionToken, + std::optional pid) override {} void notifyInputChannelBroken(const sp&) override {} diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 58c93036bb..d982b37759 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -5816,35 +5816,21 @@ void InputDispatcher::doInterceptKeyBeforeDispatchingCommand(const sp& } } -void InputDispatcher::sendMonitorUnresponsiveCommandLocked(int32_t pid, std::string reason) { - auto command = [this, pid, reason = std::move(reason)]() REQUIRES(mLock) { - scoped_unlock unlock(mLock); - mPolicy->notifyMonitorUnresponsive(pid, reason); - }; - postCommandLocked(std::move(command)); -} - void InputDispatcher::sendWindowUnresponsiveCommandLocked(const sp& token, + std::optional pid, std::string reason) { - auto command = [this, token, reason = std::move(reason)]() REQUIRES(mLock) { + auto command = [this, token, pid, reason = std::move(reason)]() REQUIRES(mLock) { scoped_unlock unlock(mLock); - mPolicy->notifyWindowUnresponsive(token, reason); + mPolicy->notifyWindowUnresponsive(token, pid, reason); }; postCommandLocked(std::move(command)); } -void InputDispatcher::sendMonitorResponsiveCommandLocked(int32_t pid) { - auto command = [this, pid]() REQUIRES(mLock) { +void InputDispatcher::sendWindowResponsiveCommandLocked(const sp& token, + std::optional pid) { + auto command = [this, token, pid]() REQUIRES(mLock) { scoped_unlock unlock(mLock); - mPolicy->notifyMonitorResponsive(pid); - }; - postCommandLocked(std::move(command)); -} - -void InputDispatcher::sendWindowResponsiveCommandLocked(const sp& connectionToken) { - auto command = [this, connectionToken]() REQUIRES(mLock) { - scoped_unlock unlock(mLock); - mPolicy->notifyWindowResponsive(connectionToken); + mPolicy->notifyWindowResponsive(token, pid); }; postCommandLocked(std::move(command)); } @@ -5857,22 +5843,21 @@ void InputDispatcher::sendWindowResponsiveCommandLocked(const sp& conne void InputDispatcher::processConnectionUnresponsiveLocked(const Connection& connection, std::string reason) { const sp& connectionToken = connection.inputChannel->getConnectionToken(); + std::optional pid; if (connection.monitor) { ALOGW("Monitor %s is unresponsive: %s", connection.inputChannel->getName().c_str(), reason.c_str()); - std::optional pid = findMonitorPidByTokenLocked(connectionToken); - if (!pid.has_value()) { - ALOGE("Could not find unresponsive monitor for connection %s", - connection.inputChannel->getName().c_str()); - return; + pid = findMonitorPidByTokenLocked(connectionToken); + } else { + // The connection is a window + ALOGW("Window %s is unresponsive: %s", connection.inputChannel->getName().c_str(), + reason.c_str()); + const sp handle = getWindowHandleLocked(connectionToken); + if (handle != nullptr) { + pid = handle->getInfo()->ownerPid; } - sendMonitorUnresponsiveCommandLocked(pid.value(), std::move(reason)); - return; } - // If not a monitor, must be a window - ALOGW("Window %s is unresponsive: %s", connection.inputChannel->getName().c_str(), - reason.c_str()); - sendWindowUnresponsiveCommandLocked(connectionToken, std::move(reason)); + sendWindowUnresponsiveCommandLocked(connectionToken, pid, std::move(reason)); } /** @@ -5880,18 +5865,17 @@ void InputDispatcher::processConnectionUnresponsiveLocked(const Connection& conn */ void InputDispatcher::processConnectionResponsiveLocked(const Connection& connection) { const sp& connectionToken = connection.inputChannel->getConnectionToken(); + std::optional pid; if (connection.monitor) { - std::optional pid = findMonitorPidByTokenLocked(connectionToken); - if (!pid.has_value()) { - ALOGE("Could not find responsive monitor for connection %s", - connection.inputChannel->getName().c_str()); - return; + pid = findMonitorPidByTokenLocked(connectionToken); + } else { + // The connection is a window + const sp handle = getWindowHandleLocked(connectionToken); + if (handle != nullptr) { + pid = handle->getInfo()->ownerPid; } - sendMonitorResponsiveCommandLocked(pid.value()); - return; } - // If not a monitor, must be a window - sendWindowResponsiveCommandLocked(connectionToken); + sendWindowResponsiveCommandLocked(connectionToken, pid); } bool InputDispatcher::afterKeyEventLockedInterruptable(const sp& connection, diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index e162c78b81..d3e171a2d8 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -503,11 +503,11 @@ private: */ void processConnectionResponsiveLocked(const Connection& connection) REQUIRES(mLock); - void sendMonitorUnresponsiveCommandLocked(int32_t pid, std::string reason) REQUIRES(mLock); - void sendWindowUnresponsiveCommandLocked(const sp& connectionToken, std::string reason) + void sendWindowUnresponsiveCommandLocked(const sp& connectionToken, + std::optional pid, std::string reason) REQUIRES(mLock); - void sendMonitorResponsiveCommandLocked(int32_t pid) REQUIRES(mLock); - void sendWindowResponsiveCommandLocked(const sp& connectionToken) REQUIRES(mLock); + void sendWindowResponsiveCommandLocked(const sp& connectionToken, + std::optional pid) REQUIRES(mLock); // Optimization: AnrTracker is used to quickly find which connection is due for a timeout next. // AnrTracker must be kept in-sync with all responsive connection.waitQueues. diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h index 3c1e6370b7..de0b6da884 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h @@ -51,30 +51,19 @@ public: const std::shared_ptr& inputApplicationHandle) = 0; /* Notifies the system that a window just became unresponsive. This indicates that ANR - * should be raised for this window. The window is identified via token. - * The string reason contains information about the input event that we haven't received - * a response for. + * should be raised for this window. The window can be identified via its input token and the + * pid of the owner. The string reason contains information about the input event that we + * haven't received a response for. */ - virtual void notifyWindowUnresponsive(const sp& token, const std::string& reason) = 0; - /* Notifies the system that a monitor just became unresponsive. This indicates that ANR - * should be raised for this monitor. The monitor is identified via its pid. - * The string reason contains information about the input event that we haven't received - * a response for. - */ - virtual void notifyMonitorUnresponsive(int32_t pid, const std::string& reason) = 0; + virtual void notifyWindowUnresponsive(const sp& token, std::optional pid, + const std::string& reason) = 0; /* Notifies the system that a window just became responsive. This is only called after the * window was first marked "unresponsive". This indicates that ANR dialog (if any) should * no longer should be shown to the user. The window is eligible to cause a new ANR in the * future. */ - virtual void notifyWindowResponsive(const sp& token) = 0; - /* Notifies the system that a monitor just became responsive. This is only called after the - * monitor was first marked "unresponsive". This indicates that ANR dialog (if any) should - * no longer should be shown to the user. The monitor is eligible to cause a new ANR in the - * future. - */ - virtual void notifyMonitorResponsive(int32_t pid) = 0; + virtual void notifyWindowResponsive(const sp& token, std::optional pid) = 0; /* Notifies the system that an input channel is unrecoverably broken. */ virtual void notifyInputChannelBroken(const sp& token) = 0; diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index ae8358aa5d..b3f51ee576 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -88,6 +88,8 @@ static void assertMotionAction(int32_t expectedAction, int32_t receivedAction) { class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface { InputDispatcherConfiguration mConfig; + using AnrResult = std::pair, int32_t /*pid*/>; + protected: virtual ~FakeInputDispatcherPolicy() {} @@ -161,122 +163,71 @@ public: void assertNotifyNoFocusedWindowAnrWasCalled( std::chrono::nanoseconds timeout, const std::shared_ptr& expectedApplication) { + std::unique_lock lock(mLock); + android::base::ScopedLockAssertion assumeLocked(mLock); std::shared_ptr application; - { // acquire lock - std::unique_lock lock(mLock); - android::base::ScopedLockAssertion assumeLocked(mLock); - ASSERT_NO_FATAL_FAILURE( - application = getAnrTokenLockedInterruptible(timeout, mAnrApplications, lock)); - } // release lock + ASSERT_NO_FATAL_FAILURE( + application = getAnrTokenLockedInterruptible(timeout, mAnrApplications, lock)); ASSERT_EQ(expectedApplication, application); } void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout, - const sp& expectedConnectionToken) { - sp connectionToken = getUnresponsiveWindowToken(timeout); - ASSERT_EQ(expectedConnectionToken, connectionToken); - } - - void assertNotifyWindowResponsiveWasCalled(const sp& expectedConnectionToken) { - sp connectionToken = getResponsiveWindowToken(); - ASSERT_EQ(expectedConnectionToken, connectionToken); - } - - void assertNotifyMonitorUnresponsiveWasCalled(std::chrono::nanoseconds timeout) { - int32_t pid = getUnresponsiveMonitorPid(timeout); - ASSERT_EQ(MONITOR_PID, pid); + const sp& window) { + LOG_ALWAYS_FATAL_IF(window == nullptr, "window should not be null"); + assertNotifyWindowUnresponsiveWasCalled(timeout, window->getToken(), + window->getInfo()->ownerPid); } - void assertNotifyMonitorResponsiveWasCalled() { - int32_t pid = getResponsiveMonitorPid(); - ASSERT_EQ(MONITOR_PID, pid); - } - - sp getUnresponsiveWindowToken(std::chrono::nanoseconds timeout) { + void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout, + const sp& expectedToken, + int32_t expectedPid) { std::unique_lock lock(mLock); android::base::ScopedLockAssertion assumeLocked(mLock); - return getAnrTokenLockedInterruptible(timeout, mAnrWindowTokens, lock); + AnrResult result; + ASSERT_NO_FATAL_FAILURE(result = + getAnrTokenLockedInterruptible(timeout, mAnrWindows, lock)); + const auto& [token, pid] = result; + ASSERT_EQ(expectedToken, token); + ASSERT_EQ(expectedPid, pid); } - sp getResponsiveWindowToken() { + /** Wrap call with ASSERT_NO_FATAL_FAILURE() to ensure the return value is valid. */ + sp getUnresponsiveWindowToken(std::chrono::nanoseconds timeout) { std::unique_lock lock(mLock); android::base::ScopedLockAssertion assumeLocked(mLock); - return getAnrTokenLockedInterruptible(0s, mResponsiveWindowTokens, lock); + AnrResult result = getAnrTokenLockedInterruptible(timeout, mAnrWindows, lock); + const auto& [token, _] = result; + return token; } - int32_t getUnresponsiveMonitorPid(std::chrono::nanoseconds timeout) { + void assertNotifyWindowResponsiveWasCalled(const sp& expectedToken, + int32_t expectedPid) { std::unique_lock lock(mLock); android::base::ScopedLockAssertion assumeLocked(mLock); - return getAnrTokenLockedInterruptible(timeout, mAnrMonitorPids, lock); + AnrResult result; + ASSERT_NO_FATAL_FAILURE( + result = getAnrTokenLockedInterruptible(0s, mResponsiveWindows, lock)); + const auto& [token, pid] = result; + ASSERT_EQ(expectedToken, token); + ASSERT_EQ(expectedPid, pid); } - int32_t getResponsiveMonitorPid() { + /** Wrap call with ASSERT_NO_FATAL_FAILURE() to ensure the return value is valid. */ + sp getResponsiveWindowToken() { std::unique_lock lock(mLock); android::base::ScopedLockAssertion assumeLocked(mLock); - return getAnrTokenLockedInterruptible(0s, mResponsiveMonitorPids, lock); - } - - // All three ANR-related callbacks behave the same way, so we use this generic function to wait - // for a specific container to become non-empty. When the container is non-empty, return the - // first entry from the container and erase it. - template - T getAnrTokenLockedInterruptible(std::chrono::nanoseconds timeout, std::queue& storage, - std::unique_lock& lock) REQUIRES(mLock) { - // If there is an ANR, Dispatcher won't be idle because there are still events - // in the waitQueue that we need to check on. So we can't wait for dispatcher to be idle - // before checking if ANR was called. - // Since dispatcher is not guaranteed to call notifyNoFocusedWindowAnr right away, we need - // to provide it some time to act. 100ms seems reasonable. - std::chrono::duration timeToWait = timeout + 100ms; // provide some slack - const std::chrono::time_point start = std::chrono::steady_clock::now(); - std::optional token = - getItemFromStorageLockedInterruptible(timeToWait, storage, lock, mNotifyAnr); - if (!token.has_value()) { - ADD_FAILURE() << "Did not receive the ANR callback"; - return {}; - } - - const std::chrono::duration waited = std::chrono::steady_clock::now() - start; - // Ensure that the ANR didn't get raised too early. We can't be too strict here because - // the dispatcher started counting before this function was called - if (std::chrono::abs(timeout - waited) > 100ms) { - ADD_FAILURE() << "ANR was raised too early or too late. Expected " - << std::chrono::duration_cast(timeout).count() - << "ms, but waited " - << std::chrono::duration_cast(waited).count() - << "ms instead"; - } - return *token; - } - - template - std::optional getItemFromStorageLockedInterruptible(std::chrono::nanoseconds timeout, - std::queue& storage, - std::unique_lock& lock, - std::condition_variable& condition) - REQUIRES(mLock) { - condition.wait_for(lock, timeout, - [&storage]() REQUIRES(mLock) { return !storage.empty(); }); - if (storage.empty()) { - ADD_FAILURE() << "Did not receive the expected callback"; - return std::nullopt; - } - T item = storage.front(); - storage.pop(); - return std::make_optional(item); + AnrResult result = getAnrTokenLockedInterruptible(0s, mResponsiveWindows, lock); + const auto& [token, _] = result; + return token; } void assertNotifyAnrWasNotCalled() { std::scoped_lock lock(mLock); ASSERT_TRUE(mAnrApplications.empty()); - ASSERT_TRUE(mAnrWindowTokens.empty()); - ASSERT_TRUE(mAnrMonitorPids.empty()); - ASSERT_TRUE(mResponsiveWindowTokens.empty()) + ASSERT_TRUE(mAnrWindows.empty()); + ASSERT_TRUE(mResponsiveWindows.empty()) << "ANR was not called, but please also consume the 'connection is responsive' " "signal"; - ASSERT_TRUE(mResponsiveMonitorPids.empty()) - << "Monitor ANR was not called, but please also consume the 'monitor is responsive'" - " signal"; } void setKeyRepeatConfiguration(nsecs_t timeout, nsecs_t delay) { @@ -344,10 +295,8 @@ private: // ANR handling std::queue> mAnrApplications GUARDED_BY(mLock); - std::queue> mAnrWindowTokens GUARDED_BY(mLock); - std::queue> mResponsiveWindowTokens GUARDED_BY(mLock); - std::queue mAnrMonitorPids GUARDED_BY(mLock); - std::queue mResponsiveMonitorPids GUARDED_BY(mLock); + std::queue mAnrWindows GUARDED_BY(mLock); + std::queue mResponsiveWindows GUARDED_BY(mLock); std::condition_variable mNotifyAnr; std::queue> mBrokenInputChannels GUARDED_BY(mLock); std::condition_variable mNotifyInputChannelBroken; @@ -355,32 +304,74 @@ private: sp mDropTargetWindowToken GUARDED_BY(mLock); bool mNotifyDropWindowWasCalled GUARDED_BY(mLock) = false; - void notifyConfigurationChanged(nsecs_t when) override { - std::scoped_lock lock(mLock); - mConfigurationChangedTime = when; + // All three ANR-related callbacks behave the same way, so we use this generic function to wait + // for a specific container to become non-empty. When the container is non-empty, return the + // first entry from the container and erase it. + template + T getAnrTokenLockedInterruptible(std::chrono::nanoseconds timeout, std::queue& storage, + std::unique_lock& lock) REQUIRES(mLock) { + // If there is an ANR, Dispatcher won't be idle because there are still events + // in the waitQueue that we need to check on. So we can't wait for dispatcher to be idle + // before checking if ANR was called. + // Since dispatcher is not guaranteed to call notifyNoFocusedWindowAnr right away, we need + // to provide it some time to act. 100ms seems reasonable. + std::chrono::duration timeToWait = timeout + 100ms; // provide some slack + const std::chrono::time_point start = std::chrono::steady_clock::now(); + std::optional token = + getItemFromStorageLockedInterruptible(timeToWait, storage, lock, mNotifyAnr); + if (!token.has_value()) { + ADD_FAILURE() << "Did not receive the ANR callback"; + return {}; + } + + const std::chrono::duration waited = std::chrono::steady_clock::now() - start; + // Ensure that the ANR didn't get raised too early. We can't be too strict here because + // the dispatcher started counting before this function was called + if (std::chrono::abs(timeout - waited) > 100ms) { + ADD_FAILURE() << "ANR was raised too early or too late. Expected " + << std::chrono::duration_cast(timeout).count() + << "ms, but waited " + << std::chrono::duration_cast(waited).count() + << "ms instead"; + } + return *token; } - void notifyWindowUnresponsive(const sp& connectionToken, const std::string&) override { - std::scoped_lock lock(mLock); - mAnrWindowTokens.push(connectionToken); - mNotifyAnr.notify_all(); + template + std::optional getItemFromStorageLockedInterruptible(std::chrono::nanoseconds timeout, + std::queue& storage, + std::unique_lock& lock, + std::condition_variable& condition) + REQUIRES(mLock) { + condition.wait_for(lock, timeout, + [&storage]() REQUIRES(mLock) { return !storage.empty(); }); + if (storage.empty()) { + ADD_FAILURE() << "Did not receive the expected callback"; + return std::nullopt; + } + T item = storage.front(); + storage.pop(); + return std::make_optional(item); } - void notifyMonitorUnresponsive(int32_t pid, const std::string&) override { + void notifyConfigurationChanged(nsecs_t when) override { std::scoped_lock lock(mLock); - mAnrMonitorPids.push(pid); - mNotifyAnr.notify_all(); + mConfigurationChangedTime = when; } - void notifyWindowResponsive(const sp& connectionToken) override { + void notifyWindowUnresponsive(const sp& connectionToken, std::optional pid, + const std::string&) override { std::scoped_lock lock(mLock); - mResponsiveWindowTokens.push(connectionToken); + ASSERT_TRUE(pid.has_value()); + mAnrWindows.push({connectionToken, *pid}); mNotifyAnr.notify_all(); } - void notifyMonitorResponsive(int32_t pid) override { + void notifyWindowResponsive(const sp& connectionToken, + std::optional pid) override { std::scoped_lock lock(mLock); - mResponsiveMonitorPids.push(pid); + ASSERT_TRUE(pid.has_value()); + mResponsiveWindows.push({connectionToken, *pid}); mNotifyAnr.notify_all(); } @@ -1245,6 +1236,8 @@ public: mInfo.ownerUid = ownerUid; } + int32_t getPid() const { return mInfo.ownerPid; } + void destroyReceiver() { mInputReceiver = nullptr; } int getChannelFd() { return mInputReceiver->getChannelFd(); } @@ -4364,13 +4357,13 @@ TEST_F(InputDispatcherSingleWindowAnr, OnPointerDown_BasicAnr) { std::optional sequenceNum = mWindow->receiveEvent(); // ACTION_DOWN ASSERT_TRUE(sequenceNum); const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); - mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken()); + mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow); mWindow->finishEvent(*sequenceNum); mWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, ADISPLAY_ID_DEFAULT, 0 /*flags*/); ASSERT_TRUE(mDispatcher->waitForIdle()); - mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken()); + mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid()); } // Send a key to the app and have the app not respond right away. @@ -4380,7 +4373,7 @@ TEST_F(InputDispatcherSingleWindowAnr, OnKeyDown_BasicAnr) { std::optional sequenceNum = mWindow->receiveEvent(); ASSERT_TRUE(sequenceNum); const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); - mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken()); + mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow); ASSERT_TRUE(mDispatcher->waitForIdle()); } @@ -4485,7 +4478,7 @@ TEST_F(InputDispatcherSingleWindowAnr, Anr_HandlesEventsWithIdenticalTimestamps) // We have now sent down and up. Let's consume first event and then ANR on the second. mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); - mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken()); + mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow); } // A spy window can receive an ANR @@ -4500,13 +4493,13 @@ TEST_F(InputDispatcherSingleWindowAnr, SpyWindowAnr) { std::optional sequenceNum = spy->receiveEvent(); // ACTION_DOWN ASSERT_TRUE(sequenceNum); const std::chrono::duration timeout = spy->getDispatchingTimeout(DISPATCHING_TIMEOUT); - mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, spy->getToken()); + mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, spy); spy->finishEvent(*sequenceNum); spy->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, ADISPLAY_ID_DEFAULT, 0 /*flags*/); ASSERT_TRUE(mDispatcher->waitForIdle()); - mFakePolicy->assertNotifyWindowResponsiveWasCalled(spy->getToken()); + mFakePolicy->assertNotifyWindowResponsiveWasCalled(spy->getToken(), mWindow->getPid()); } // If an app is not responding to a key event, spy windows should continue to receive @@ -4521,7 +4514,7 @@ TEST_F(InputDispatcherSingleWindowAnr, SpyWindowReceivesEventsDuringAppAnrOnKey) // Stuck on the ACTION_UP const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); - mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken()); + mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow); // New tap will go to the spy window, but not to the window tapOnWindow(); @@ -4530,7 +4523,7 @@ TEST_F(InputDispatcherSingleWindowAnr, SpyWindowReceivesEventsDuringAppAnrOnKey) mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT); // still the previous motion mDispatcher->waitForIdle(); - mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken()); + mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid()); mWindow->assertNoEvents(); spy->assertNoEvents(); } @@ -4547,7 +4540,7 @@ TEST_F(InputDispatcherSingleWindowAnr, SpyWindowReceivesEventsDuringAppAnrOnMoti mWindow->consumeMotionDown(); // Stuck on the ACTION_UP const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); - mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken()); + mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow); // New tap will go to the spy window, but not to the window tapOnWindow(); @@ -4556,7 +4549,7 @@ TEST_F(InputDispatcherSingleWindowAnr, SpyWindowReceivesEventsDuringAppAnrOnMoti mWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); // still the previous motion mDispatcher->waitForIdle(); - mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken()); + mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid()); mWindow->assertNoEvents(); spy->assertNoEvents(); } @@ -4574,13 +4567,13 @@ TEST_F(InputDispatcherSingleWindowAnr, UnresponsiveMonitorAnr) { const std::optional consumeSeq = monitor.receiveEvent(); ASSERT_TRUE(consumeSeq); - mFakePolicy->assertNotifyMonitorUnresponsiveWasCalled(30ms); + mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(30ms, monitor.getToken(), MONITOR_PID); monitor.finishEvent(*consumeSeq); monitor.consumeMotionCancel(ADISPLAY_ID_DEFAULT); ASSERT_TRUE(mDispatcher->waitForIdle()); - mFakePolicy->assertNotifyMonitorResponsiveWasCalled(); + mFakePolicy->assertNotifyWindowResponsiveWasCalled(monitor.getToken(), MONITOR_PID); } // If a window is unresponsive, then you get anr. if the window later catches up and starts to @@ -4595,19 +4588,19 @@ TEST_F(InputDispatcherSingleWindowAnr, SameWindow_CanReceiveAnrTwice) { mWindow->consumeMotionDown(); // Block on ACTION_UP const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); - mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken()); + mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow); mWindow->consumeMotionUp(); // Now the connection should be healthy again mDispatcher->waitForIdle(); - mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken()); + mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid()); mWindow->assertNoEvents(); tapOnWindow(); mWindow->consumeMotionDown(); - mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken()); + mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow); mWindow->consumeMotionUp(); mDispatcher->waitForIdle(); - mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken()); + mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid()); mFakePolicy->assertNotifyAnrWasNotCalled(); mWindow->assertNoEvents(); } @@ -4620,7 +4613,7 @@ TEST_F(InputDispatcherSingleWindowAnr, Policy_DoesNotGetDuplicateAnr) { WINDOW_LOCATION)); const std::chrono::duration windowTimeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); - mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(windowTimeout, mWindow->getToken()); + mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(windowTimeout, mWindow); std::this_thread::sleep_for(windowTimeout); // 'notifyConnectionUnresponsive' should only be called once per connection mFakePolicy->assertNotifyAnrWasNotCalled(); @@ -4630,7 +4623,7 @@ TEST_F(InputDispatcherSingleWindowAnr, Policy_DoesNotGetDuplicateAnr) { ADISPLAY_ID_DEFAULT, 0 /*flags*/); mWindow->assertNoEvents(); mDispatcher->waitForIdle(); - mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken()); + mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid()); mFakePolicy->assertNotifyAnrWasNotCalled(); } @@ -4792,7 +4785,7 @@ TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsive) { const std::chrono::duration timeout = mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); - mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow->getToken()); + mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow); // Because we injected two DOWN events in a row, CANCEL is enqueued for the first event // sequence to make it consistent mFocusedWindow->consumeMotionCancel(); @@ -4803,7 +4796,8 @@ TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsive) { mFocusedWindow->assertNoEvents(); mUnfocusedWindow->assertNoEvents(); ASSERT_TRUE(mDispatcher->waitForIdle()); - mFakePolicy->assertNotifyWindowResponsiveWasCalled(mFocusedWindow->getToken()); + mFakePolicy->assertNotifyWindowResponsiveWasCalled(mFocusedWindow->getToken(), + mFocusedWindow->getPid()); mFakePolicy->assertNotifyAnrWasNotCalled(); } @@ -4817,8 +4811,9 @@ TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsiveWithSameTimeout tapOnFocusedWindow(); // we should have ACTION_DOWN/ACTION_UP on focused window and ACTION_OUTSIDE on unfocused window - sp anrConnectionToken1 = mFakePolicy->getUnresponsiveWindowToken(10ms); - sp anrConnectionToken2 = mFakePolicy->getUnresponsiveWindowToken(0ms); + sp anrConnectionToken1, anrConnectionToken2; + ASSERT_NO_FATAL_FAILURE(anrConnectionToken1 = mFakePolicy->getUnresponsiveWindowToken(10ms)); + ASSERT_NO_FATAL_FAILURE(anrConnectionToken2 = mFakePolicy->getUnresponsiveWindowToken(0ms)); // We don't know which window will ANR first. But both of them should happen eventually. ASSERT_TRUE(mFocusedWindow->getToken() == anrConnectionToken1 || @@ -4833,8 +4828,9 @@ TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsiveWithSameTimeout mFocusedWindow->consumeMotionUp(); mUnfocusedWindow->consumeMotionOutside(); - sp responsiveToken1 = mFakePolicy->getResponsiveWindowToken(); - sp responsiveToken2 = mFakePolicy->getResponsiveWindowToken(); + sp responsiveToken1, responsiveToken2; + ASSERT_NO_FATAL_FAILURE(responsiveToken1 = mFakePolicy->getResponsiveWindowToken()); + ASSERT_NO_FATAL_FAILURE(responsiveToken2 = mFakePolicy->getResponsiveWindowToken()); // Both applications should be marked as responsive, in any order ASSERT_TRUE(mFocusedWindow->getToken() == responsiveToken1 || @@ -4858,7 +4854,7 @@ TEST_F(InputDispatcherMultiWindowAnr, DuringAnr_SecondTapIsIgnored) { ASSERT_TRUE(upEventSequenceNum); const std::chrono::duration timeout = mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); - mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow->getToken()); + mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow); // Tap once again // We cannot use "tapOnFocusedWindow" because it asserts the injection result to be success @@ -4879,7 +4875,8 @@ TEST_F(InputDispatcherMultiWindowAnr, DuringAnr_SecondTapIsIgnored) { // The second tap did not go to the focused window mFocusedWindow->assertNoEvents(); // Since all events are finished, connection should be deemed healthy again - mFakePolicy->assertNotifyWindowResponsiveWasCalled(mFocusedWindow->getToken()); + mFakePolicy->assertNotifyWindowResponsiveWasCalled(mFocusedWindow->getToken(), + mFocusedWindow->getPid()); mFakePolicy->assertNotifyAnrWasNotCalled(); } @@ -4991,7 +4988,7 @@ TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) { const std::chrono::duration timeout = mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); - mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow->getToken()); + mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow); mUnfocusedWindow->consumeMotionDown(); mFocusedWindow->consumeMotionDown(); @@ -5010,7 +5007,8 @@ TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) { ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, motionEvent.getAction()); } ASSERT_TRUE(mDispatcher->waitForIdle()); - mFakePolicy->assertNotifyWindowResponsiveWasCalled(mFocusedWindow->getToken()); + mFakePolicy->assertNotifyWindowResponsiveWasCalled(mFocusedWindow->getToken(), + mFocusedWindow->getPid()); mUnfocusedWindow->assertNoEvents(); mFocusedWindow->assertNoEvents(); -- GitLab From 673ce1e6dd1b18a293afacd6fc4e549a2e904f63 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 15 Feb 2022 14:50:16 -0800 Subject: [PATCH 0006/1310] Ensure stale event does not cause ANR Does a stale key event cause 'no focused window' ANR? It shouldn't, but it wasn't already covered by tests. Adding a test for that here to explicitly check the behaviour. Bug: 219401047 Test: atest inputflinger_tests:InputDispatcherSingleWindowAnr Change-Id: I0b1e33a7e705a4b968e3159bd15c70f00eda0191 --- .../dispatcher/InputDispatcher.cpp | 18 +++++----- .../inputflinger/dispatcher/InputDispatcher.h | 7 ++++ .../tests/InputDispatcher_test.cpp | 36 ++++++++++++++++++- 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 58c93036bb..74ccf3fcdf 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -118,10 +118,7 @@ const std::chrono::duration DEFAULT_INPUT_DISPATCHING_TIMEOUT = std::chrono::mil // when an application takes too long to respond and the user has pressed an app switch key. constexpr nsecs_t APP_SWITCH_TIMEOUT = 500 * 1000000LL; // 0.5sec -// Amount of time to allow for an event to be dispatched (measured since its eventTime) -// before considering it stale and dropping it. -const nsecs_t STALE_EVENT_TIMEOUT = 10000 * 1000000LL // 10sec - * HwTimeoutMultiplier(); +const std::chrono::duration STALE_EVENT_TIMEOUT = std::chrono::seconds(10) * HwTimeoutMultiplier(); // Log a warning when an event takes longer than this to process, even if an ANR does not occur. constexpr nsecs_t SLOW_EVENT_PROCESSING_WARNING_TIMEOUT = 2000 * 1000000LL; // 2sec @@ -322,10 +319,6 @@ bool haveSameApplicationToken(const WindowInfo* first, const WindowInfo* second) first->applicationInfo.token == second->applicationInfo.token; } -bool isStaleEvent(nsecs_t currentTime, const EventEntry& entry) { - return currentTime - entry.eventTime >= STALE_EVENT_TIMEOUT; -} - std::unique_ptr createDispatchEntry(const InputTarget& inputTarget, std::shared_ptr eventEntry, int32_t inputTargetFlags) { @@ -526,6 +519,10 @@ bool isPointerFromStylus(const MotionEntry& entry, int32_t pointerIndex) { // --- InputDispatcher --- InputDispatcher::InputDispatcher(const sp& policy) + : InputDispatcher(policy, STALE_EVENT_TIMEOUT) {} + +InputDispatcher::InputDispatcher(const sp& policy, + std::chrono::nanoseconds staleEventTimeout) : mPolicy(policy), mPendingEvent(nullptr), mLastDropReason(DropReason::NOT_DROPPED), @@ -544,6 +541,7 @@ InputDispatcher::InputDispatcher(const sp& polic mMaximumObscuringOpacityForTouch(1.0f), mFocusedDisplayId(ADISPLAY_ID_DEFAULT), mWindowTokenWithPointerCapture(nullptr), + mStaleEventTimeout(staleEventTimeout), mLatencyAggregator(), mLatencyTracker(&mLatencyAggregator) { mLooper = new Looper(false); @@ -901,6 +899,10 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { } } +bool InputDispatcher::isStaleEvent(nsecs_t currentTime, const EventEntry& entry) { + return std::chrono::nanoseconds(currentTime - entry.eventTime) >= mStaleEventTimeout; +} + /** * Return true if the events preceding this incoming motion event should be dropped * Return false otherwise (the default behaviour) diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index e162c78b81..ae1e1d22f9 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -84,6 +84,8 @@ public: static constexpr bool kDefaultInTouchMode = true; explicit InputDispatcher(const sp& policy); + explicit InputDispatcher(const sp& policy, + std::chrono::nanoseconds staleEventTimeout); ~InputDispatcher() override; void dump(std::string& dump) override; @@ -471,6 +473,11 @@ private: */ std::optional mNoFocusedWindowTimeoutTime GUARDED_BY(mLock); + // Amount of time to allow for an event to be dispatched (measured since its eventTime) + // before considering it stale and dropping it. + const std::chrono::nanoseconds mStaleEventTimeout; + bool isStaleEvent(nsecs_t currentTime, const EventEntry& entry); + bool shouldPruneInboundQueueLocked(const MotionEntry& motionEntry) REQUIRES(mLock); /** diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index ae8358aa5d..1ffa875e57 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -60,6 +60,8 @@ static const int32_t INJECTOR_UID = 1001; // An arbitrary pid of the gesture monitor window static constexpr int32_t MONITOR_PID = 2001; +static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 1000ms; + struct PointF { float x; float y; @@ -493,7 +495,7 @@ protected: void SetUp() override { mFakePolicy = new FakeInputDispatcherPolicy(); - mDispatcher = std::make_unique(mFakePolicy); + mDispatcher = std::make_unique(mFakePolicy, STALE_EVENT_TIMEOUT); mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); // Start InputDispatcher thread ASSERT_EQ(OK, mDispatcher->start()); @@ -4410,6 +4412,38 @@ TEST_F(InputDispatcherSingleWindowAnr, FocusedApplication_NoFocusedWindow) { ASSERT_TRUE(mDispatcher->waitForIdle()); } +/** + * Make sure the stale key is dropped before causing an ANR. So even if there's no focused window, + * there will not be an ANR. + */ +TEST_F(InputDispatcherSingleWindowAnr, StaleKeyEventDoesNotAnr) { + mWindow->setFocusable(false); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}}); + mWindow->consumeFocusEvent(false); + + KeyEvent event; + const nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC) - + std::chrono::nanoseconds(STALE_EVENT_TIMEOUT).count(); + + // Define a valid key down event that is stale (too old). + event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE, + INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, /* flags */ 0, AKEYCODE_A, KEY_A, + AMETA_NONE, 1 /*repeatCount*/, eventTime, eventTime); + + const int32_t policyFlags = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER; + + InputEventInjectionResult result = + mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, + InputEventInjectionSync::WAIT_FOR_RESULT, + INJECT_EVENT_TIMEOUT, policyFlags); + ASSERT_EQ(InputEventInjectionResult::FAILED, result) + << "Injection should fail because the event is stale"; + + ASSERT_TRUE(mDispatcher->waitForIdle()); + mFakePolicy->assertNotifyAnrWasNotCalled(); + mWindow->assertNoEvents(); +} + // We have a focused application, but no focused window // Make sure that we don't notify policy twice about the same ANR. TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DoesNotSendDuplicateAnr) { -- GitLab From 1cd3625e4501554868ce1152214461d30b4c736f Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Mon, 21 Feb 2022 17:37:25 -0500 Subject: [PATCH 0007/1310] Fix ref counting bug in AHardwareBuffer_recvHandleFromUnixSocket Follow-on from I443015d63245e49e8cf38847030c9da8142cbe50. If unflatten returns an error, this ensures that the GraphicBuffer is deleted. This matches similar code in this file. Bug: NA Test: make Test: AHardwareBufferTest_SendAndRecvSucceeds Change-Id: Ibcf21347625e3c9df127a0267aa05b046132e0a0 --- libs/nativewindow/AHardwareBuffer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp index 381900e4ba..576941f06a 100644 --- a/libs/nativewindow/AHardwareBuffer.cpp +++ b/libs/nativewindow/AHardwareBuffer.cpp @@ -353,12 +353,12 @@ int AHardwareBuffer_recvHandleFromUnixSocket(int socketFd, AHardwareBuffer** out return INVALID_OPERATION; } - GraphicBuffer* gBuffer = new GraphicBuffer(); + sp gBuffer(new GraphicBuffer()); status_t err = gBuffer->unflatten(data, dataLen, fdData, fdCount); if (err != NO_ERROR) { return err; } - *outBuffer = AHardwareBuffer_from_GraphicBuffer(gBuffer); + *outBuffer = AHardwareBuffer_from_GraphicBuffer(gBuffer.get()); // Ensure the buffer has a positive ref-count. AHardwareBuffer_acquire(*outBuffer); -- GitLab From 890532e49c0e329c0c462fb2733c5ae27a395b7b Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 24 Feb 2022 09:08:54 -0800 Subject: [PATCH 0008/1310] Fix input injection with zero coords In the native MotionEvent class, setting an axis value to 0 is equivalent to removing the axis from the bitfield of valid axes. This is because getting an axis value that is not set in the bitfield will return 0 by default. This means that we cannot rely on the bitfield of valid axes to know exactly which axes are valid, since all axes are always valid with a default value of 0. Rather than transforming only the axies that are set in the bitfield, we add a helper function to MotionEvent to transform the entire PointerCoords. Bug: 219711163 Test: manual, see bug: adb shell input draganddrop 665 531 0 531 1000 Change-Id: I335beebf8263a38f180f2f4c6a788fbd69d15a6f --- include/input/Input.h | 2 ++ libs/input/Input.cpp | 29 +++++++++++++++++++ .../dispatcher/InputDispatcher.cpp | 16 ++-------- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/include/input/Input.h b/include/input/Input.h index 2837add22c..b23a9518f7 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -814,6 +814,8 @@ public: static vec2 calculateTransformedXY(uint32_t source, const ui::Transform&, const vec2& xy); static float calculateTransformedAxisValue(int32_t axis, uint32_t source, const ui::Transform&, const PointerCoords&); + static PointerCoords calculateTransformedCoords(uint32_t source, const ui::Transform&, + const PointerCoords&); protected: int32_t mAction; diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index 3073d94dbe..fe1754c78b 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -846,6 +846,7 @@ vec2 MotionEvent::calculateTransformedXY(uint32_t source, const ui::Transform& t return calculateTransformedXYUnchecked(source, transform, xy); } +// Keep in sync with calculateTransformedCoords. float MotionEvent::calculateTransformedAxisValue(int32_t axis, uint32_t source, const ui::Transform& transform, const PointerCoords& coords) { @@ -874,6 +875,34 @@ float MotionEvent::calculateTransformedAxisValue(int32_t axis, uint32_t source, return coords.getAxisValue(axis); } +// Keep in sync with calculateTransformedAxisValue. This is an optimization of +// calculateTransformedAxisValue for all PointerCoords axes. +PointerCoords MotionEvent::calculateTransformedCoords(uint32_t source, + const ui::Transform& transform, + const PointerCoords& coords) { + if (shouldDisregardTransformation(source)) { + return coords; + } + PointerCoords out = coords; + + const vec2 xy = calculateTransformedXYUnchecked(source, transform, coords.getXYValue()); + out.setAxisValue(AMOTION_EVENT_AXIS_X, xy.x); + out.setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y); + + const vec2 relativeXy = + transformWithoutTranslation(transform, + {coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X), + coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y)}); + out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, relativeXy.x); + out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, relativeXy.y); + + out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, + transformAngle(transform, + coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION))); + + return out; +} + // --- FocusEvent --- void FocusEvent::initialize(int32_t id, bool hasFocus) { diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index cecfc4dc03..99d23af71f 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -4428,19 +4428,9 @@ void InputDispatcher::transformMotionEntryForInjectionLocked( const auto& transformToDisplay = it->second.transform.inverse() * injectedTransform; for (uint32_t i = 0; i < entry.pointerCount; i++) { - PointerCoords& pc = entry.pointerCoords[i]; - // Make a copy of the injected coords. We cannot change them in place because some of them - // are interdependent (for example, X coordinate might depend on the Y coordinate). - PointerCoords injectedCoords = entry.pointerCoords[i]; - - BitSet64 bits(injectedCoords.bits); - while (!bits.isEmpty()) { - const auto axis = static_cast(bits.clearFirstMarkedBit()); - const float value = - MotionEvent::calculateTransformedAxisValue(axis, entry.source, - transformToDisplay, injectedCoords); - pc.setAxisValue(axis, value); - } + entry.pointerCoords[i] = + MotionEvent::calculateTransformedCoords(entry.source, transformToDisplay, + entry.pointerCoords[i]); } } -- GitLab From 07e723660ca6265844be1ebd8c4696977151eeba Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Mon, 14 Feb 2022 14:26:04 -0800 Subject: [PATCH 0009/1310] Migrate display related methods to AIDL This migrates display related methods from ISurfaceComposer.h to the AIDL interface. Bug: 219574942 Test: manual Change-Id: I11f011ce61bdd6dfbd8e0f1a1af8925820e3de58 --- libs/gui/ISurfaceComposer.cpp | 116 ------------------ libs/gui/Surface.cpp | 5 +- libs/gui/SurfaceComposerClient.cpp | 43 +++++-- .../aidl/android/gui/ISurfaceComposer.aidl | 22 ++++ libs/gui/include/gui/ISurfaceComposer.h | 44 +------ .../include/private/gui/ComposerServiceAIDL.h | 21 ++++ libs/gui/tests/Surface_test.cpp | 10 +- services/surfaceflinger/SurfaceFlinger.cpp | 75 ++++++++++- services/surfaceflinger/SurfaceFlinger.h | 34 ++++- 9 files changed, 185 insertions(+), 185 deletions(-) diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index 5ab0abc561..6911d89cdb 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -220,81 +220,6 @@ public: return result; } - sp createDisplay(const String8& displayName, bool secure) override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - status_t status = data.writeString8(displayName); - if (status) { - return nullptr; - } - status = data.writeBool(secure); - if (status) { - return nullptr; - } - - status = remote()->transact(BnSurfaceComposer::CREATE_DISPLAY, data, &reply); - if (status) { - return nullptr; - } - sp display; - status = reply.readNullableStrongBinder(&display); - if (status) { - return nullptr; - } - return display; - } - - void destroyDisplay(const sp& display) override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - data.writeStrongBinder(display); - remote()->transact(BnSurfaceComposer::DESTROY_DISPLAY, data, &reply); - } - - std::vector getPhysicalDisplayIds() const override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (remote()->transact(BnSurfaceComposer::GET_PHYSICAL_DISPLAY_IDS, data, &reply) == - NO_ERROR) { - std::vector rawIds; - if (reply.readUint64Vector(&rawIds) == NO_ERROR) { - std::vector displayIds; - displayIds.reserve(rawIds.size()); - - for (const uint64_t rawId : rawIds) { - if (const auto id = DisplayId::fromValue(rawId)) { - displayIds.push_back(*id); - } - } - return displayIds; - } - } - - return {}; - } - - status_t getPrimaryPhysicalDisplayId(PhysicalDisplayId* displayId) const override { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(remote()->transact, BnSurfaceComposer::GET_PRIMARY_PHYSICAL_DISPLAY_ID, data, - &reply); - uint64_t rawId; - SAFE_PARCEL(reply.readUint64, &rawId); - if (const auto id = DisplayId::fromValue(rawId)) { - *displayId = *id; - return NO_ERROR; - } - return NAME_NOT_FOUND; - } - - sp getPhysicalDisplayToken(PhysicalDisplayId displayId) const override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - data.writeUint64(displayId.value); - remote()->transact(BnSurfaceComposer::GET_PHYSICAL_DISPLAY_TOKEN, data, &reply); - return reply.readStrongBinder(); - } - void setPowerMode(const sp& display, int mode) override { Parcel data, reply; data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); @@ -1461,29 +1386,6 @@ status_t BnSurfaceComposer::onTransact( reply->writeStrongBinder(IInterface::asBinder(connection)); return NO_ERROR; } - case CREATE_DISPLAY: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - String8 displayName; - SAFE_PARCEL(data.readString8, &displayName); - bool secure = false; - SAFE_PARCEL(data.readBool, &secure); - sp display = createDisplay(displayName, secure); - SAFE_PARCEL(reply->writeStrongBinder, display); - return NO_ERROR; - } - case DESTROY_DISPLAY: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp display = data.readStrongBinder(); - destroyDisplay(display); - return NO_ERROR; - } - case GET_PHYSICAL_DISPLAY_TOKEN: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - const auto id = DisplayId::fromValue(data.readUint64()); - if (!id) return BAD_VALUE; - reply->writeStrongBinder(getPhysicalDisplayToken(*id)); - return NO_ERROR; - } case GET_DISPLAY_STATE: { CHECK_INTERFACE(ISurfaceComposer, data, reply); ui::DisplayState state; @@ -1820,24 +1722,6 @@ status_t BnSurfaceComposer::onTransact( } return error; } - case GET_PHYSICAL_DISPLAY_IDS: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - std::vector ids = getPhysicalDisplayIds(); - std::vector rawIds(ids.size()); - std::transform(ids.begin(), ids.end(), rawIds.begin(), - [](PhysicalDisplayId id) { return id.value; }); - return reply->writeUint64Vector(rawIds); - } - case GET_PRIMARY_PHYSICAL_DISPLAY_ID: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - PhysicalDisplayId id; - status_t result = getPrimaryPhysicalDisplayId(&id); - if (result != NO_ERROR) { - ALOGE("getPrimaryPhysicalDisplayId: Failed to get id"); - return result; - } - return reply->writeUint64(id.value); - } case ADD_REGION_SAMPLING_LISTENER: { CHECK_INTERFACE(ISurfaceComposer, data, reply); Rect samplingArea; diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index 20c41460d4..82067580a9 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -45,6 +45,7 @@ #include #include #include +#include namespace android { @@ -343,7 +344,7 @@ status_t Surface::getFrameTimestamps(uint64_t frameNumber, status_t Surface::getWideColorSupport(bool* supported) { ATRACE_CALL(); - const sp display = composerService()->getInternalDisplayToken(); + const sp display = ComposerServiceAIDL::getInstance().getInternalDisplayToken(); if (display == nullptr) { return NAME_NOT_FOUND; } @@ -356,7 +357,7 @@ status_t Surface::getWideColorSupport(bool* supported) { status_t Surface::getHdrSupport(bool* supported) { ATRACE_CALL(); - const sp display = composerService()->getInternalDisplayToken(); + const sp display = ComposerServiceAIDL::getInstance().getInternalDisplayToken(); if (display == nullptr) { return NAME_NOT_FOUND; } diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 26ccda580a..52d89657b5 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -1014,32 +1014,59 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous) { // --------------------------------------------------------------------------- sp SurfaceComposerClient::createDisplay(const String8& displayName, bool secure) { - return ComposerService::getComposerService()->createDisplay(displayName, - secure); + sp display = nullptr; + binder::Status status = + ComposerServiceAIDL::getComposerService()->createDisplay(std::string( + displayName.string()), + secure, &display); + return status.isOk() ? display : nullptr; } void SurfaceComposerClient::destroyDisplay(const sp& display) { - return ComposerService::getComposerService()->destroyDisplay(display); + ComposerServiceAIDL::getComposerService()->destroyDisplay(display); } std::vector SurfaceComposerClient::getPhysicalDisplayIds() { - return ComposerService::getComposerService()->getPhysicalDisplayIds(); + std::vector displayIds; + std::vector physicalDisplayIds; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getPhysicalDisplayIds(&displayIds); + if (status.isOk()) { + physicalDisplayIds.reserve(displayIds.size()); + for (auto item : displayIds) { + auto id = DisplayId::fromValue(static_cast(item)); + physicalDisplayIds.push_back(*id); + } + } + return physicalDisplayIds; } status_t SurfaceComposerClient::getPrimaryPhysicalDisplayId(PhysicalDisplayId* id) { - return ComposerService::getComposerService()->getPrimaryPhysicalDisplayId(id); + int64_t displayId; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getPrimaryPhysicalDisplayId(&displayId); + if (status.isOk()) { + *id = *DisplayId::fromValue(static_cast(displayId)); + } + return status.transactionError(); } std::optional SurfaceComposerClient::getInternalDisplayId() { - return ComposerService::getComposerService()->getInternalDisplayId(); + ComposerServiceAIDL& instance = ComposerServiceAIDL::getInstance(); + return instance.getInternalDisplayId(); } sp SurfaceComposerClient::getPhysicalDisplayToken(PhysicalDisplayId displayId) { - return ComposerService::getComposerService()->getPhysicalDisplayToken(displayId); + sp display = nullptr; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getPhysicalDisplayToken(displayId.value, + &display); + return status.isOk() ? display : nullptr; } sp SurfaceComposerClient::getInternalDisplayToken() { - return ComposerService::getComposerService()->getInternalDisplayToken(); + ComposerServiceAIDL& instance = ComposerServiceAIDL::getInstance(); + return instance.getInternalDisplayToken(); } void SurfaceComposerClient::Transaction::setAnimationTransaction() { diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 07921a59a5..345c47d5b9 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -22,6 +22,28 @@ import android.gui.IScreenCaptureListener; /** @hide */ interface ISurfaceComposer { + + /* create a virtual display + * requires ACCESS_SURFACE_FLINGER permission. + */ + @nullable IBinder createDisplay(@utf8InCpp String displayName, boolean secure); + + /* destroy a virtual display + * requires ACCESS_SURFACE_FLINGER permission. + */ + void destroyDisplay(IBinder display); + + /* get stable IDs for connected physical displays. + */ + long[] getPhysicalDisplayIds(); + + long getPrimaryPhysicalDisplayId(); + + /* get token for a physical display given its stable ID obtained via getPhysicalDisplayIds or a + * DisplayEventReceiver hotplug event. + */ + @nullable IBinder getPhysicalDisplayToken(long displayId); + /** * Capture the specified screen. This requires READ_FRAME_BUFFER * permission. This function will fail if there is a secure window on diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index 4dfc383b57..a2870db2fa 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -137,40 +137,6 @@ public: VsyncSource vsyncSource = eVsyncSourceApp, EventRegistrationFlags eventRegistration = {}) = 0; - /* create a virtual display - * requires ACCESS_SURFACE_FLINGER permission. - */ - virtual sp createDisplay(const String8& displayName, - bool secure) = 0; - - /* destroy a virtual display - * requires ACCESS_SURFACE_FLINGER permission. - */ - virtual void destroyDisplay(const sp& display) = 0; - - /* get stable IDs for connected physical displays. - */ - virtual std::vector getPhysicalDisplayIds() const = 0; - - virtual status_t getPrimaryPhysicalDisplayId(PhysicalDisplayId*) const = 0; - - // TODO(b/74619554): Remove this stopgap once the framework is display-agnostic. - std::optional getInternalDisplayId() const { - const auto displayIds = getPhysicalDisplayIds(); - return displayIds.empty() ? std::nullopt : std::make_optional(displayIds.front()); - } - - /* get token for a physical display given its stable ID obtained via getPhysicalDisplayIds or a - * DisplayEventReceiver hotplug event. - */ - virtual sp getPhysicalDisplayToken(PhysicalDisplayId displayId) const = 0; - - // TODO(b/74619554): Remove this stopgap once the framework is display-agnostic. - sp getInternalDisplayToken() const { - const auto displayId = getInternalDisplayId(); - return displayId ? getPhysicalDisplayToken(*displayId) : nullptr; - } - /* open/close transactions. requires ACCESS_SURFACE_FLINGER permission */ virtual status_t setTransactionState( const FrameTimelineInfo& frameTimelineInfo, const Vector& state, @@ -596,9 +562,9 @@ public: CREATE_CONNECTION, GET_STATIC_DISPLAY_INFO, CREATE_DISPLAY_EVENT_CONNECTION, - CREATE_DISPLAY, - DESTROY_DISPLAY, - GET_PHYSICAL_DISPLAY_TOKEN, + CREATE_DISPLAY, // Deprecated. Autogenerated by .aidl now. + DESTROY_DISPLAY, // Deprecated. Autogenerated by .aidl now. + GET_PHYSICAL_DISPLAY_TOKEN, // Deprecated. Autogenerated by .aidl now. SET_TRANSACTION_STATE, AUTHENTICATE_SURFACE, GET_SUPPORTED_FRAME_TIMESTAMPS, @@ -626,7 +592,7 @@ public: GET_PROTECTED_CONTENT_SUPPORT, IS_WIDE_COLOR_DISPLAY, GET_DISPLAY_NATIVE_PRIMARIES, - GET_PHYSICAL_DISPLAY_IDS, + GET_PHYSICAL_DISPLAY_IDS, // Deprecated. Autogenerated by .aidl now. ADD_REGION_SAMPLING_LISTENER, REMOVE_REGION_SAMPLING_LISTENER, SET_DESIRED_DISPLAY_MODE_SPECS, @@ -658,7 +624,7 @@ public: REMOVE_TUNNEL_MODE_ENABLED_LISTENER, ADD_WINDOW_INFOS_LISTENER, REMOVE_WINDOW_INFOS_LISTENER, - GET_PRIMARY_PHYSICAL_DISPLAY_ID, + GET_PRIMARY_PHYSICAL_DISPLAY_ID, // Deprecated. Autogenerated by .aidl now. GET_DISPLAY_DECORATION_SUPPORT, GET_BOOT_DISPLAY_MODE_SUPPORT, SET_BOOT_DISPLAY_MODE, diff --git a/libs/gui/include/private/gui/ComposerServiceAIDL.h b/libs/gui/include/private/gui/ComposerServiceAIDL.h index fee37eefe0..b32cf2a9c2 100644 --- a/libs/gui/include/private/gui/ComposerServiceAIDL.h +++ b/libs/gui/include/private/gui/ComposerServiceAIDL.h @@ -50,6 +50,27 @@ public: // Get a connection to the Composer Service. This will block until // a connection is established. Returns null if permission is denied. static sp getComposerService(); + + // the following two methods are moved from ISurfaceComposer.h + // TODO(b/74619554): Remove this stopgap once the framework is display-agnostic. + std::optional getInternalDisplayId() const { + std::vector displayIds; + binder::Status status = mComposerService->getPhysicalDisplayIds(&displayIds); + return (!status.isOk() || displayIds.empty()) + ? std::nullopt + : DisplayId::fromValue( + static_cast(displayIds.front())); + } + + // TODO(b/74619554): Remove this stopgap once the framework is display-agnostic. + sp getInternalDisplayToken() const { + const auto displayId = getInternalDisplayId(); + if (!displayId) return nullptr; + sp display; + binder::Status status = + mComposerService->getPhysicalDisplayToken(displayId->value, &display); + return status.isOk() ? display : nullptr; + } }; // --------------------------------------------------------------------------- diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index a885e926a3..07ac2d4065 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -260,9 +260,7 @@ TEST_F(SurfaceTest, ScreenshotsOfProtectedBuffersDontSucceed) { sp anw(mSurface); // Verify the screenshot works with no protected buffers. - sp sf(ComposerService::getComposerService()); - - const sp display = sf->getInternalDisplayToken(); + const sp display = ComposerServiceAIDL::getInstance().getInternalDisplayToken(); ASSERT_FALSE(display == nullptr); DisplayCaptureArgs captureArgs; @@ -696,12 +694,6 @@ public: ISurfaceComposer::VsyncSource, ISurfaceComposer::EventRegistrationFlags) override { return nullptr; } - sp createDisplay(const String8& /*displayName*/, - bool /*secure*/) override { return nullptr; } - void destroyDisplay(const sp& /*display */) override {} - std::vector getPhysicalDisplayIds() const override { return {}; } - status_t getPrimaryPhysicalDisplayId(PhysicalDisplayId*) const override { return NO_ERROR; } - sp getPhysicalDisplayToken(PhysicalDisplayId) const override { return nullptr; } status_t setTransactionState(const FrameTimelineInfo& /*frameTimelineInfo*/, const Vector& /*state*/, const Vector& /*displays*/, uint32_t /*flags*/, diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 815febe53d..d50869ff90 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5396,8 +5396,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { // access to SF. case BOOT_FINISHED: case CLEAR_ANIMATION_FRAME_STATS: - case CREATE_DISPLAY: - case DESTROY_DISPLAY: case GET_ANIMATION_FRAME_STATS: case OVERRIDE_HDR_TYPES: case GET_HDR_CAPABILITIES: @@ -5419,7 +5417,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case REMOVE_TUNNEL_MODE_ENABLED_LISTENER: case NOTIFY_POWER_BOOST: case SET_GLOBAL_SHADOW_SETTINGS: - case GET_PRIMARY_PHYSICAL_DISPLAY_ID: case ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN: { // OVERRIDE_HDR_TYPES is used by CTS tests, which acquire the necessary // permission dynamically. Don't use the permission cache for this check. @@ -5450,8 +5447,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case AUTHENTICATE_SURFACE: case GET_ACTIVE_COLOR_MODE: case GET_ACTIVE_DISPLAY_MODE: - case GET_PHYSICAL_DISPLAY_IDS: - case GET_PHYSICAL_DISPLAY_TOKEN: case GET_DISPLAY_COLOR_MODES: case GET_DISPLAY_NATIVE_PRIMARIES: case GET_STATIC_DISPLAY_INFO: @@ -5537,10 +5532,15 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { } return PERMISSION_DENIED; } + case CREATE_DISPLAY: + case DESTROY_DISPLAY: + case GET_PRIMARY_PHYSICAL_DISPLAY_ID: + case GET_PHYSICAL_DISPLAY_IDS: + case GET_PHYSICAL_DISPLAY_TOKEN: case CAPTURE_LAYERS: case CAPTURE_DISPLAY: case CAPTURE_DISPLAY_BY_ID: - LOG_FATAL("Deprecated opcode: %d", code); + LOG_FATAL("Deprecated opcode: %d, migrated to AIDL", code); return PERMISSION_DENIED; } @@ -7192,6 +7192,59 @@ bool SurfaceFlinger::commitCreatedLayers() { } // gui::ISurfaceComposer + +binder::Status SurfaceComposerAIDL::createDisplay(const std::string& displayName, bool secure, + sp* outDisplay) { + status_t status = checkAccessPermission(); + if (status == OK) { + String8 displayName8 = String8::format("%s", displayName.c_str()); + *outDisplay = mFlinger->createDisplay(displayName8, secure); + return binder::Status::ok(); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::destroyDisplay(const sp& display) { + status_t status = checkAccessPermission(); + if (status == OK) { + mFlinger->destroyDisplay(display); + return binder::Status::ok(); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getPhysicalDisplayIds(std::vector* outDisplayIds) { + std::vector physicalDisplayIds = mFlinger->getPhysicalDisplayIds(); + std::vector displayIds; + displayIds.reserve(physicalDisplayIds.size()); + for (auto item : physicalDisplayIds) { + displayIds.push_back(static_cast(item.value)); + } + *outDisplayIds = displayIds; + return binder::Status::ok(); +} + +binder::Status SurfaceComposerAIDL::getPrimaryPhysicalDisplayId(int64_t* outDisplayId) { + status_t status = checkAccessPermission(); + if (status != OK) { + return binder::Status::fromStatusT(status); + } + + PhysicalDisplayId id; + status = mFlinger->getPrimaryPhysicalDisplayId(&id); + if (status == NO_ERROR) { + *outDisplayId = id.value; + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getPhysicalDisplayToken(int64_t displayId, + sp* outDisplay) { + const auto id = DisplayId::fromValue(static_cast(displayId)); + *outDisplay = mFlinger->getPhysicalDisplayToken(*id); + return binder::Status::ok(); +} + binder::Status SurfaceComposerAIDL::captureDisplay( const DisplayCaptureArgs& args, const sp& captureListener) { status_t status = mFlinger->captureDisplay(args, captureListener); @@ -7218,6 +7271,16 @@ binder::Status SurfaceComposerAIDL::captureLayers( return binder::Status::fromStatusT(status); } +status_t SurfaceComposerAIDL::checkAccessPermission(bool usePermissionCache) { + if (!mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(usePermissionCache)) { + IPCThreadState* ipc = IPCThreadState::self(); + ALOGE("Permission Denial: can't access SurfaceFlinger pid=%d, uid=%d", ipc->getCallingPid(), + ipc->getCallingUid()); + return PERMISSION_DENIED; + } + return OK; +} + } // namespace android #if defined(__gl_h_) diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index f09ee5fad0..3670777713 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -517,17 +517,30 @@ private: bool callingThreadHasUnscopedSurfaceFlingerAccess(bool usePermissionCache = true) EXCLUDES(mStateLock); + // the following two methods are moved from ISurfaceComposer.h + // TODO(b/74619554): Remove this stopgap once the framework is display-agnostic. + std::optional getInternalDisplayId() const { + const auto displayIds = getPhysicalDisplayIds(); + return displayIds.empty() ? std::nullopt : std::make_optional(displayIds.front()); + } + + // TODO(b/74619554): Remove this stopgap once the framework is display-agnostic. + sp getInternalDisplayToken() const { + const auto displayId = getInternalDisplayId(); + return displayId ? getPhysicalDisplayToken(*displayId) : nullptr; + } + // Implements ISurfaceComposer sp createConnection() override; - sp createDisplay(const String8& displayName, bool secure) override; - void destroyDisplay(const sp& displayToken) override; - std::vector getPhysicalDisplayIds() const override EXCLUDES(mStateLock) { + sp createDisplay(const String8& displayName, bool secure); + void destroyDisplay(const sp& displayToken); + std::vector getPhysicalDisplayIds() const EXCLUDES(mStateLock) { Mutex::Autolock lock(mStateLock); return getPhysicalDisplayIdsLocked(); } - status_t getPrimaryPhysicalDisplayId(PhysicalDisplayId*) const override EXCLUDES(mStateLock); + status_t getPrimaryPhysicalDisplayId(PhysicalDisplayId*) const EXCLUDES(mStateLock); - sp getPhysicalDisplayToken(PhysicalDisplayId displayId) const override; + sp getPhysicalDisplayToken(PhysicalDisplayId displayId) const; status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo, const Vector& state, const Vector& displays, uint32_t flags, @@ -1414,12 +1427,23 @@ class SurfaceComposerAIDL : public gui::BnSurfaceComposer { public: SurfaceComposerAIDL(sp sf) { mFlinger = sf; } + binder::Status createDisplay(const std::string& displayName, bool secure, + sp* outDisplay) override; + binder::Status destroyDisplay(const sp& display) override; + binder::Status getPhysicalDisplayIds(std::vector* outDisplayIds) override; + binder::Status getPrimaryPhysicalDisplayId(int64_t* outDisplayId) override; + binder::Status getPhysicalDisplayToken(int64_t displayId, sp* outDisplay) override; + binder::Status captureDisplay(const DisplayCaptureArgs&, const sp&) override; binder::Status captureDisplayById(int64_t, const sp&) override; binder::Status captureLayers(const LayerCaptureArgs&, const sp&) override; +private: + static const constexpr bool kUsePermissionCache = true; + status_t checkAccessPermission(bool usePermissionCache = kUsePermissionCache); + private: sp mFlinger; }; -- GitLab From 37396db2bf50b039a6942c9fd692ff899bd531de Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Tue, 15 Feb 2022 10:43:00 -0800 Subject: [PATCH 0010/1310] Migrate display related methods to AIDL part 2 This migrates more display related methods with simple arguments from ISurfaceComposer.h to the new AIDL interface. Bug: 219574942 Test: atest SurfaceFlinger_test libsurfaceflinger_unittest libgui_test Change-Id: I57f428f6934e784cb234704ad89ef22218e441b4 --- libs/gui/ISurfaceComposer.cpp | 366 ------------------ libs/gui/Surface.cpp | 8 +- libs/gui/SurfaceComposerClient.cpp | 45 ++- .../aidl/android/gui/ISurfaceComposer.aidl | 106 +++++ libs/gui/include/gui/ISurfaceComposer.h | 133 +------ libs/gui/include/gui/Surface.h | 5 + .../include/private/gui/ComposerServiceAIDL.h | 3 +- libs/gui/tests/Surface_test.cpp | 26 -- services/surfaceflinger/SurfaceFlinger.cpp | 152 ++++++-- services/surfaceflinger/SurfaceFlinger.h | 43 +- .../tests/BootDisplayMode_test.cpp | 15 +- 11 files changed, 324 insertions(+), 578 deletions(-) diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index 6911d89cdb..d7ec9ff1d6 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -220,14 +220,6 @@ public: return result; } - void setPowerMode(const sp& display, int mode) override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - data.writeStrongBinder(display); - data.writeInt32(mode); - remote()->transact(BnSurfaceComposer::SET_POWER_MODE, data, &reply); - } - status_t getDisplayState(const sp& display, ui::DisplayState* state) override { Parcel data, reply; data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); @@ -327,29 +319,6 @@ public: return static_cast(reply.readInt32()); } - // TODO(b/213909104) : Add unit tests to verify surface flinger boot time APIs - status_t getBootDisplayModeSupport(bool* outSupport) const override { - Parcel data, reply; - status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (error != NO_ERROR) { - ALOGE("getBootDisplayModeSupport: failed to write interface token: %d", error); - return error; - } - error = remote()->transact(BnSurfaceComposer::GET_BOOT_DISPLAY_MODE_SUPPORT, data, &reply); - if (error != NO_ERROR) { - ALOGE("getBootDisplayModeSupport: failed to transact: %d", error); - return error; - } - bool support; - error = reply.readBool(&support); - if (error != NO_ERROR) { - ALOGE("getBootDisplayModeSupport: failed to read support: %d", error); - return error; - } - *outSupport = support; - return NO_ERROR; - } - status_t setBootDisplayMode(const sp& display, ui::DisplayModeId displayModeId) override { Parcel data, reply; @@ -375,73 +344,6 @@ public: return result; } - status_t clearBootDisplayMode(const sp& display) override { - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("clearBootDisplayMode failed to writeInterfaceToken: %d", result); - return result; - } - result = data.writeStrongBinder(display); - if (result != NO_ERROR) { - ALOGE("clearBootDisplayMode failed to writeStrongBinder: %d", result); - return result; - } - result = remote()->transact(BnSurfaceComposer::CLEAR_BOOT_DISPLAY_MODE, data, &reply); - if (result != NO_ERROR) { - ALOGE("clearBootDisplayMode failed to transact: %d", result); - } - return result; - } - - void setAutoLowLatencyMode(const sp& display, bool on) override { - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("setAutoLowLatencyMode failed to writeInterfaceToken: %d", result); - return; - } - - result = data.writeStrongBinder(display); - if (result != NO_ERROR) { - ALOGE("setAutoLowLatencyMode failed to writeStrongBinder: %d", result); - return; - } - result = data.writeBool(on); - if (result != NO_ERROR) { - ALOGE("setAutoLowLatencyMode failed to writeBool: %d", result); - return; - } - result = remote()->transact(BnSurfaceComposer::SET_AUTO_LOW_LATENCY_MODE, data, &reply); - if (result != NO_ERROR) { - ALOGE("setAutoLowLatencyMode failed to transact: %d", result); - return; - } - } - - void setGameContentType(const sp& display, bool on) override { - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("setGameContentType failed to writeInterfaceToken: %d", result); - return; - } - result = data.writeStrongBinder(display); - if (result != NO_ERROR) { - ALOGE("setGameContentType failed to writeStrongBinder: %d", result); - return; - } - result = data.writeBool(on); - if (result != NO_ERROR) { - ALOGE("setGameContentType failed to writeBool: %d", result); - return; - } - result = remote()->transact(BnSurfaceComposer::SET_GAME_CONTENT_TYPE, data, &reply); - if (result != NO_ERROR) { - ALOGE("setGameContentType failed to transact: %d", result); - } - } - status_t clearAnimationFrameStats() override { Parcel data, reply; status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); @@ -716,26 +618,6 @@ public: return error; } - status_t isWideColorDisplay(const sp& token, - bool* outIsWideColorDisplay) const override { - Parcel data, reply; - status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (error != NO_ERROR) { - return error; - } - error = data.writeStrongBinder(token); - if (error != NO_ERROR) { - return error; - } - - error = remote()->transact(BnSurfaceComposer::IS_WIDE_COLOR_DISPLAY, data, &reply); - if (error != NO_ERROR) { - return error; - } - error = reply.readBool(outIsWideColorDisplay); - return error; - } - status_t addRegionSamplingListener(const Rect& samplingArea, const sp& stopLayerHandle, const sp& listener) override { Parcel data, reply; @@ -968,109 +850,6 @@ public: return reply.readInt32(); } - status_t getDisplayBrightnessSupport(const sp& displayToken, - bool* outSupport) const override { - Parcel data, reply; - status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (error != NO_ERROR) { - ALOGE("getDisplayBrightnessSupport: failed to write interface token: %d", error); - return error; - } - error = data.writeStrongBinder(displayToken); - if (error != NO_ERROR) { - ALOGE("getDisplayBrightnessSupport: failed to write display token: %d", error); - return error; - } - error = remote()->transact(BnSurfaceComposer::GET_DISPLAY_BRIGHTNESS_SUPPORT, data, &reply); - if (error != NO_ERROR) { - ALOGE("getDisplayBrightnessSupport: failed to transact: %d", error); - return error; - } - bool support; - error = reply.readBool(&support); - if (error != NO_ERROR) { - ALOGE("getDisplayBrightnessSupport: failed to read support: %d", error); - return error; - } - *outSupport = support; - return NO_ERROR; - } - - status_t setDisplayBrightness(const sp& displayToken, - const gui::DisplayBrightness& brightness) override { - Parcel data, reply; - status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (error != NO_ERROR) { - ALOGE("setDisplayBrightness: failed to write interface token: %d", error); - return error; - } - error = data.writeStrongBinder(displayToken); - if (error != NO_ERROR) { - ALOGE("setDisplayBrightness: failed to write display token: %d", error); - return error; - } - error = data.writeParcelable(brightness); - if (error != NO_ERROR) { - ALOGE("setDisplayBrightness: failed to write brightness: %d", error); - return error; - } - error = remote()->transact(BnSurfaceComposer::SET_DISPLAY_BRIGHTNESS, data, &reply); - if (error != NO_ERROR) { - ALOGE("setDisplayBrightness: failed to transact: %d", error); - return error; - } - return NO_ERROR; - } - - status_t addHdrLayerInfoListener(const sp& displayToken, - const sp& listener) override { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeStrongBinder, displayToken); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(listener)); - const status_t error = - remote()->transact(BnSurfaceComposer::ADD_HDR_LAYER_INFO_LISTENER, data, &reply); - if (error != OK) { - ALOGE("addHdrLayerInfoListener: Failed to transact; error = %d", error); - } - return error; - } - - status_t removeHdrLayerInfoListener(const sp& displayToken, - const sp& listener) override { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeStrongBinder, displayToken); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(listener)); - const status_t error = - remote()->transact(BnSurfaceComposer::REMOVE_HDR_LAYER_INFO_LISTENER, data, &reply); - if (error != OK) { - ALOGE("removeHdrLayerInfoListener: Failed to transact; error = %d", error); - } - return error; - } - - status_t notifyPowerBoost(int32_t boostId) override { - Parcel data, reply; - status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (error != NO_ERROR) { - ALOGE("notifyPowerBoost: failed to write interface token: %d", error); - return error; - } - error = data.writeInt32(boostId); - if (error != NO_ERROR) { - ALOGE("notifyPowerBoost: failed to write boostId: %d", error); - return error; - } - error = remote()->transact(BnSurfaceComposer::NOTIFY_POWER_BOOST, data, &reply, - IBinder::FLAG_ONEWAY); - if (error != NO_ERROR) { - ALOGE("notifyPowerBoost: failed to transact: %d", error); - return error; - } - return NO_ERROR; - } - status_t setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor, float lightPosY, float lightPosZ, float lightRadius) override { Parcel data, reply; @@ -1469,15 +1248,6 @@ status_t BnSurfaceComposer::onTransact( result = reply->writeInt32(result); return result; } - case GET_BOOT_DISPLAY_MODE_SUPPORT: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - bool support = false; - status_t result = getBootDisplayModeSupport(&support); - if (result == NO_ERROR) { - reply->writeBool(support); - } - return result; - } case SET_BOOT_DISPLAY_MODE: { CHECK_INTERFACE(ISurfaceComposer, data, reply); sp display = nullptr; @@ -1494,50 +1264,6 @@ status_t BnSurfaceComposer::onTransact( } return setBootDisplayMode(display, displayModeId); } - case CLEAR_BOOT_DISPLAY_MODE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp display = nullptr; - status_t result = data.readStrongBinder(&display); - if (result != NO_ERROR) { - ALOGE("clearBootDisplayMode failed to readStrongBinder: %d", result); - return result; - } - return clearBootDisplayMode(display); - } - case SET_AUTO_LOW_LATENCY_MODE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp display = nullptr; - status_t result = data.readStrongBinder(&display); - if (result != NO_ERROR) { - ALOGE("setAutoLowLatencyMode failed to readStrongBinder: %d", result); - return result; - } - bool setAllm = false; - result = data.readBool(&setAllm); - if (result != NO_ERROR) { - ALOGE("setAutoLowLatencyMode failed to readBool: %d", result); - return result; - } - setAutoLowLatencyMode(display, setAllm); - return result; - } - case SET_GAME_CONTENT_TYPE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp display = nullptr; - status_t result = data.readStrongBinder(&display); - if (result != NO_ERROR) { - ALOGE("setGameContentType failed to readStrongBinder: %d", result); - return result; - } - bool setGameContentTypeOn = false; - result = data.readBool(&setGameContentTypeOn); - if (result != NO_ERROR) { - ALOGE("setGameContentType failed to readBool: %d", result); - return result; - } - setGameContentType(display, setGameContentTypeOn); - return result; - } case CLEAR_ANIMATION_FRAME_STATS: { CHECK_INTERFACE(ISurfaceComposer, data, reply); status_t result = clearAnimationFrameStats(); @@ -1552,13 +1278,6 @@ status_t BnSurfaceComposer::onTransact( reply->writeInt32(result); return NO_ERROR; } - case SET_POWER_MODE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp display = data.readStrongBinder(); - int32_t mode = data.readInt32(); - setPowerMode(display, mode); - return NO_ERROR; - } case ENABLE_VSYNC_INJECTIONS: { CHECK_INTERFACE(ISurfaceComposer, data, reply); bool enable = false; @@ -1708,20 +1427,6 @@ status_t BnSurfaceComposer::onTransact( } return error; } - case IS_WIDE_COLOR_DISPLAY: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp display = nullptr; - status_t error = data.readStrongBinder(&display); - if (error != NO_ERROR) { - return error; - } - bool result; - error = isWideColorDisplay(display, &result); - if (error == NO_ERROR) { - reply->writeBool(result); - } - return error; - } case ADD_REGION_SAMPLING_LISTENER: { CHECK_INTERFACE(ISurfaceComposer, data, reply); Rect samplingArea; @@ -1919,77 +1624,6 @@ status_t BnSurfaceComposer::onTransact( reply->writeInt32(result); return result; } - case GET_DISPLAY_BRIGHTNESS_SUPPORT: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp displayToken; - status_t error = data.readNullableStrongBinder(&displayToken); - if (error != NO_ERROR) { - ALOGE("getDisplayBrightnessSupport: failed to read display token: %d", error); - return error; - } - bool support = false; - error = getDisplayBrightnessSupport(displayToken, &support); - reply->writeBool(support); - return error; - } - case SET_DISPLAY_BRIGHTNESS: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp displayToken; - status_t error = data.readNullableStrongBinder(&displayToken); - if (error != NO_ERROR) { - ALOGE("setDisplayBrightness: failed to read display token: %d", error); - return error; - } - gui::DisplayBrightness brightness; - error = data.readParcelable(&brightness); - if (error != NO_ERROR) { - ALOGE("setDisplayBrightness: failed to read brightness: %d", error); - return error; - } - return setDisplayBrightness(displayToken, brightness); - } - case ADD_HDR_LAYER_INFO_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp displayToken; - status_t error = data.readNullableStrongBinder(&displayToken); - if (error != NO_ERROR) { - ALOGE("addHdrLayerInfoListener: Failed to read display token"); - return error; - } - sp listener; - error = data.readNullableStrongBinder(&listener); - if (error != NO_ERROR) { - ALOGE("addHdrLayerInfoListener: Failed to read listener"); - return error; - } - return addHdrLayerInfoListener(displayToken, listener); - } - case REMOVE_HDR_LAYER_INFO_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp displayToken; - status_t error = data.readNullableStrongBinder(&displayToken); - if (error != NO_ERROR) { - ALOGE("removeHdrLayerInfoListener: Failed to read display token"); - return error; - } - sp listener; - error = data.readNullableStrongBinder(&listener); - if (error != NO_ERROR) { - ALOGE("removeHdrLayerInfoListener: Failed to read listener"); - return error; - } - return removeHdrLayerInfoListener(displayToken, listener); - } - case NOTIFY_POWER_BOOST: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - int32_t boostId; - status_t error = data.readInt32(&boostId); - if (error != NO_ERROR) { - ALOGE("notifyPowerBoost: failed to read boostId: %d", error); - return error; - } - return notifyPowerBoost(boostId); - } case SET_GLOBAL_SHADOW_SETTINGS: { CHECK_INTERFACE(ISurfaceComposer, data, reply); diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index 82067580a9..ceb517f2ff 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -126,6 +126,10 @@ sp Surface::composerService() const { return ComposerService::getComposerService(); } +sp Surface::composerServiceAIDL() const { + return ComposerServiceAIDL::getComposerService(); +} + nsecs_t Surface::now() const { return systemTime(); } @@ -350,8 +354,8 @@ status_t Surface::getWideColorSupport(bool* supported) { } *supported = false; - status_t error = composerService()->isWideColorDisplay(display, supported); - return error; + binder::Status status = composerServiceAIDL()->isWideColorDisplay(display, supported); + return status.transactionError(); } status_t Surface::getHdrSupport(bool* supported) { diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 52d89657b5..6b2cda19a0 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2158,7 +2158,9 @@ status_t SurfaceComposerClient::setActiveColorMode(const sp& display, } status_t SurfaceComposerClient::getBootDisplayModeSupport(bool* support) { - return ComposerService::getComposerService()->getBootDisplayModeSupport(support); + binder::Status status = + ComposerServiceAIDL::getComposerService()->getBootDisplayModeSupport(support); + return status.transactionError(); } status_t SurfaceComposerClient::setBootDisplayMode(const sp& display, @@ -2167,7 +2169,9 @@ status_t SurfaceComposerClient::setBootDisplayMode(const sp& display, } status_t SurfaceComposerClient::clearBootDisplayMode(const sp& display) { - return ComposerService::getComposerService()->clearBootDisplayMode(display); + binder::Status status = + ComposerServiceAIDL::getComposerService()->clearBootDisplayMode(display); + return status.transactionError(); } status_t SurfaceComposerClient::setOverrideFrameRate(uid_t uid, float frameRate) { @@ -2175,16 +2179,16 @@ status_t SurfaceComposerClient::setOverrideFrameRate(uid_t uid, float frameRate) } void SurfaceComposerClient::setAutoLowLatencyMode(const sp& display, bool on) { - ComposerService::getComposerService()->setAutoLowLatencyMode(display, on); + ComposerServiceAIDL::getComposerService()->setAutoLowLatencyMode(display, on); } void SurfaceComposerClient::setGameContentType(const sp& display, bool on) { - ComposerService::getComposerService()->setGameContentType(display, on); + ComposerServiceAIDL::getComposerService()->setGameContentType(display, on); } void SurfaceComposerClient::setDisplayPowerMode(const sp& token, int mode) { - ComposerService::getComposerService()->setPowerMode(token, mode); + ComposerServiceAIDL::getComposerService()->setPowerMode(token, mode); } status_t SurfaceComposerClient::getCompositionPreference( @@ -2245,8 +2249,10 @@ status_t SurfaceComposerClient::getDisplayedContentSample(const sp& dis status_t SurfaceComposerClient::isWideColorDisplay(const sp& display, bool* outIsWideColorDisplay) { - return ComposerService::getComposerService()->isWideColorDisplay(display, - outIsWideColorDisplay); + binder::Status status = + ComposerServiceAIDL::getComposerService()->isWideColorDisplay(display, + outIsWideColorDisplay); + return status.transactionError(); } status_t SurfaceComposerClient::addRegionSamplingListener( @@ -2283,28 +2289,39 @@ status_t SurfaceComposerClient::removeTunnelModeEnabledListener( bool SurfaceComposerClient::getDisplayBrightnessSupport(const sp& displayToken) { bool support = false; - ComposerService::getComposerService()->getDisplayBrightnessSupport(displayToken, &support); - return support; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getDisplayBrightnessSupport(displayToken, + &support); + return status.isOk() ? support : false; } status_t SurfaceComposerClient::setDisplayBrightness(const sp& displayToken, const gui::DisplayBrightness& brightness) { - return ComposerService::getComposerService()->setDisplayBrightness(displayToken, brightness); + binder::Status status = + ComposerServiceAIDL::getComposerService()->setDisplayBrightness(displayToken, + brightness); + return status.transactionError(); } status_t SurfaceComposerClient::addHdrLayerInfoListener( const sp& displayToken, const sp& listener) { - return ComposerService::getComposerService()->addHdrLayerInfoListener(displayToken, listener); + binder::Status status = + ComposerServiceAIDL::getComposerService()->addHdrLayerInfoListener(displayToken, + listener); + return status.transactionError(); } status_t SurfaceComposerClient::removeHdrLayerInfoListener( const sp& displayToken, const sp& listener) { - return ComposerService::getComposerService()->removeHdrLayerInfoListener(displayToken, - listener); + binder::Status status = + ComposerServiceAIDL::getComposerService()->removeHdrLayerInfoListener(displayToken, + listener); + return status.transactionError(); } status_t SurfaceComposerClient::notifyPowerBoost(int32_t boostId) { - return ComposerService::getComposerService()->notifyPowerBoost(boostId); + binder::Status status = ComposerServiceAIDL::getComposerService()->notifyPowerBoost(boostId); + return status.transactionError(); } status_t SurfaceComposerClient::setGlobalShadowSettings(const half4& ambientColor, diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 345c47d5b9..526fae8e55 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -17,6 +17,8 @@ package android.gui; import android.gui.DisplayCaptureArgs; +import android.gui.DisplayBrightness; +import android.gui.IHdrLayerInfoListener; import android.gui.LayerCaptureArgs; import android.gui.IScreenCaptureListener; @@ -44,6 +46,47 @@ interface ISurfaceComposer { */ @nullable IBinder getPhysicalDisplayToken(long displayId); + /* set display power mode. depending on the mode, it can either trigger + * screen on, off or low power mode and wait for it to complete. + * requires ACCESS_SURFACE_FLINGER permission. + */ + void setPowerMode(IBinder display, int mode); + + /** + * Clears the user-preferred display mode. The device should now boot in system preferred + * display mode. + */ + void clearBootDisplayMode(IBinder display); + + /** + * Gets whether boot time display mode operations are supported on the device. + * + * outSupport + * An output parameter for whether boot time display mode operations are supported. + * + * Returns NO_ERROR upon success. Otherwise, + * NAME_NOT_FOUND if the display is invalid, or + * BAD_VALUE if the output parameter is invalid. + */ + // TODO(b/213909104) : Add unit tests to verify surface flinger boot time APIs + boolean getBootDisplayModeSupport(); + + /** + * Switches Auto Low Latency Mode on/off on the connected display, if it is + * available. This should only be called if the display supports Auto Low + * Latency Mode as reported in #getDynamicDisplayInfo. + * For more information, see the HDMI 2.1 specification. + */ + void setAutoLowLatencyMode(IBinder display, boolean on); + + /** + * This will start sending infoframes to the connected display with + * ContentType=Game (if on=true). This should only be called if the display + * Game Content Type as reported in #getDynamicDisplayInfo. + * For more information, see the HDMI 1.4 specification. + */ + void setGameContentType(IBinder display, boolean on); + /** * Capture the specified screen. This requires READ_FRAME_BUFFER * permission. This function will fail if there is a secure window on @@ -61,4 +104,67 @@ interface ISurfaceComposer { * is a secure window on screen */ void captureLayers(in LayerCaptureArgs args, IScreenCaptureListener listener); + + /* + * Queries whether the given display is a wide color display. + * Requires the ACCESS_SURFACE_FLINGER permission. + */ + boolean isWideColorDisplay(IBinder token); + + /* + * Gets whether brightness operations are supported on a display. + * + * displayToken + * The token of the display. + * outSupport + * An output parameter for whether brightness operations are supported. + * + * Returns NO_ERROR upon success. Otherwise, + * NAME_NOT_FOUND if the display is invalid, or + * BAD_VALUE if the output parameter is invalid. + */ + boolean getDisplayBrightnessSupport(IBinder displayToken); + + /* + * Sets the brightness of a display. + * + * displayToken + * The token of the display whose brightness is set. + * brightness + * The DisplayBrightness info to set on the desired display. + * + * Returns NO_ERROR upon success. Otherwise, + * NAME_NOT_FOUND if the display is invalid, or + * BAD_VALUE if the brightness is invalid, or + * INVALID_OPERATION if brightness operations are not supported. + */ + void setDisplayBrightness(IBinder displayToken, in DisplayBrightness brightness); + + /* + * Adds a listener that receives HDR layer information. This is used in combination + * with setDisplayBrightness to adjust the display brightness depending on factors such + * as whether or not HDR is in use. + * + * Returns NO_ERROR upon success or NAME_NOT_FOUND if the display is invalid. + */ + void addHdrLayerInfoListener(IBinder displayToken, IHdrLayerInfoListener listener); + + /* + * Removes a listener that was added with addHdrLayerInfoListener. + * + * Returns NO_ERROR upon success, NAME_NOT_FOUND if the display is invalid, and BAD_VALUE if + * the listener wasn't registered. + * + */ + void removeHdrLayerInfoListener(IBinder displayToken, IHdrLayerInfoListener listener); + + /* + * Sends a power boost to the composer. This function is asynchronous. + * + * boostId + * boost id according to android::hardware::power::Boost + * + * Returns NO_ERROR upon success. + */ + void notifyPowerBoost(int boostId); } diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index a2870db2fa..0a2ae35044 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -160,13 +160,6 @@ public: virtual status_t getSupportedFrameTimestamps( std::vector* outSupported) const = 0; - /* set display power mode. depending on the mode, it can either trigger - * screen on, off or low power mode and wait for it to complete. - * requires ACCESS_SURFACE_FLINGER permission. - */ - virtual void setPowerMode(const sp& display, int mode) = 0; - - /* returns display statistics for a given display * intended to be used by the media framework to properly schedule * video frames */ @@ -198,40 +191,6 @@ public: */ virtual status_t setBootDisplayMode(const sp& display, ui::DisplayModeId) = 0; - /** - * Clears the user-preferred display mode. The device should now boot in system preferred - * display mode. - */ - virtual status_t clearBootDisplayMode(const sp& display) = 0; - - /** - * Gets whether boot time display mode operations are supported on the device. - * - * outSupport - * An output parameter for whether boot time display mode operations are supported. - * - * Returns NO_ERROR upon success. Otherwise, - * NAME_NOT_FOUND if the display is invalid, or - * BAD_VALUE if the output parameter is invalid. - */ - virtual status_t getBootDisplayModeSupport(bool* outSupport) const = 0; - - /** - * Switches Auto Low Latency Mode on/off on the connected display, if it is - * available. This should only be called if the display supports Auto Low - * Latency Mode as reported in #getDynamicDisplayInfo. - * For more information, see the HDMI 2.1 specification. - */ - virtual void setAutoLowLatencyMode(const sp& display, bool on) = 0; - - /** - * This will start sending infoframes to the connected display with - * ContentType=Game (if on=true). This should only be called if the display - * Game Content Type as reported in #getDynamicDisplayInfo. - * For more information, see the HDMI 1.4 specification. - */ - virtual void setGameContentType(const sp& display, bool on) = 0; - /* Clears the frame statistics for animations. * * Requires the ACCESS_SURFACE_FLINGER permission. @@ -310,13 +269,6 @@ public: */ virtual status_t getProtectedContentSupport(bool* outSupported) const = 0; - /* - * Queries whether the given display is a wide color display. - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t isWideColorDisplay(const sp& token, - bool* outIsWideColorDisplay) const = 0; - /* Registers a listener to stream median luma updates from SurfaceFlinger. * * The sampling area is bounded by both samplingArea and the given stopLayerHandle @@ -397,65 +349,6 @@ public: float* outPrimaryRefreshRateMax, float* outAppRequestRefreshRateMin, float* outAppRequestRefreshRateMax) = 0; - /* - * Gets whether brightness operations are supported on a display. - * - * displayToken - * The token of the display. - * outSupport - * An output parameter for whether brightness operations are supported. - * - * Returns NO_ERROR upon success. Otherwise, - * NAME_NOT_FOUND if the display is invalid, or - * BAD_VALUE if the output parameter is invalid. - */ - virtual status_t getDisplayBrightnessSupport(const sp& displayToken, - bool* outSupport) const = 0; - - /* - * Sets the brightness of a display. - * - * displayToken - * The token of the display whose brightness is set. - * brightness - * The DisplayBrightness info to set on the desired display. - * - * Returns NO_ERROR upon success. Otherwise, - * NAME_NOT_FOUND if the display is invalid, or - * BAD_VALUE if the brightness is invalid, or - * INVALID_OPERATION if brightness operations are not supported. - */ - virtual status_t setDisplayBrightness(const sp& displayToken, - const gui::DisplayBrightness& brightness) = 0; - - /* - * Adds a listener that receives HDR layer information. This is used in combination - * with setDisplayBrightness to adjust the display brightness depending on factors such - * as whether or not HDR is in use. - * - * Returns NO_ERROR upon success or NAME_NOT_FOUND if the display is invalid. - */ - virtual status_t addHdrLayerInfoListener(const sp& displayToken, - const sp& listener) = 0; - /* - * Removes a listener that was added with addHdrLayerInfoListener. - * - * Returns NO_ERROR upon success, NAME_NOT_FOUND if the display is invalid, and BAD_VALUE if - * the listener wasn't registered. - * - */ - virtual status_t removeHdrLayerInfoListener(const sp& displayToken, - const sp& listener) = 0; - - /* - * Sends a power boost to the composer. This function is asynchronous. - * - * boostId - * boost id according to android::hardware::power::Boost - * - * Returns NO_ERROR upon success. - */ - virtual status_t notifyPowerBoost(int32_t boostId) = 0; /* * Sets the global configuration for all the shadows drawn by SurfaceFlinger. Shadow follows @@ -575,7 +468,7 @@ public: CAPTURE_LAYERS, // Deprecated. Autogenerated by .aidl now. CLEAR_ANIMATION_FRAME_STATS, GET_ANIMATION_FRAME_STATS, - SET_POWER_MODE, + SET_POWER_MODE, // Deprecated. Autogenerated by .aidl now. GET_DISPLAY_STATS, GET_HDR_CAPABILITIES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. GET_DISPLAY_COLOR_MODES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. @@ -590,22 +483,22 @@ public: SET_DISPLAY_CONTENT_SAMPLING_ENABLED, GET_DISPLAYED_CONTENT_SAMPLE, GET_PROTECTED_CONTENT_SUPPORT, - IS_WIDE_COLOR_DISPLAY, + IS_WIDE_COLOR_DISPLAY, // Deprecated. Autogenerated by .aidl now. GET_DISPLAY_NATIVE_PRIMARIES, GET_PHYSICAL_DISPLAY_IDS, // Deprecated. Autogenerated by .aidl now. ADD_REGION_SAMPLING_LISTENER, REMOVE_REGION_SAMPLING_LISTENER, SET_DESIRED_DISPLAY_MODE_SPECS, GET_DESIRED_DISPLAY_MODE_SPECS, - GET_DISPLAY_BRIGHTNESS_SUPPORT, - SET_DISPLAY_BRIGHTNESS, - CAPTURE_DISPLAY_BY_ID, // Deprecated. Autogenerated by .aidl now. - NOTIFY_POWER_BOOST, + GET_DISPLAY_BRIGHTNESS_SUPPORT, // Deprecated. Autogenerated by .aidl now. + SET_DISPLAY_BRIGHTNESS, // Deprecated. Autogenerated by .aidl now. + CAPTURE_DISPLAY_BY_ID, // Deprecated. Autogenerated by .aidl now. + NOTIFY_POWER_BOOST, // Deprecated. Autogenerated by .aidl now. SET_GLOBAL_SHADOW_SETTINGS, GET_AUTO_LOW_LATENCY_MODE_SUPPORT, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. - SET_AUTO_LOW_LATENCY_MODE, - GET_GAME_CONTENT_TYPE_SUPPORT, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. - SET_GAME_CONTENT_TYPE, + SET_AUTO_LOW_LATENCY_MODE, // Deprecated. Autogenerated by .aidl now. + GET_GAME_CONTENT_TYPE_SUPPORT, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. + SET_GAME_CONTENT_TYPE, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. SET_FRAME_RATE, // Deprecated. Use DisplayManager.setShouldAlwaysRespectAppRequestedMode(true); ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN, @@ -617,8 +510,8 @@ public: ADD_FPS_LISTENER, REMOVE_FPS_LISTENER, OVERRIDE_HDR_TYPES, - ADD_HDR_LAYER_INFO_LISTENER, - REMOVE_HDR_LAYER_INFO_LISTENER, + ADD_HDR_LAYER_INFO_LISTENER, // Deprecated. Autogenerated by .aidl now. + REMOVE_HDR_LAYER_INFO_LISTENER, // Deprecated. Autogenerated by .aidl now. ON_PULL_ATOM, ADD_TUNNEL_MODE_ENABLED_LISTENER, REMOVE_TUNNEL_MODE_ENABLED_LISTENER, @@ -626,9 +519,9 @@ public: REMOVE_WINDOW_INFOS_LISTENER, GET_PRIMARY_PHYSICAL_DISPLAY_ID, // Deprecated. Autogenerated by .aidl now. GET_DISPLAY_DECORATION_SUPPORT, - GET_BOOT_DISPLAY_MODE_SUPPORT, + GET_BOOT_DISPLAY_MODE_SUPPORT, // Deprecated. Autogenerated by .aidl now. SET_BOOT_DISPLAY_MODE, - CLEAR_BOOT_DISPLAY_MODE, + CLEAR_BOOT_DISPLAY_MODE, // Deprecated. Autogenerated by .aidl now. SET_OVERRIDE_FRAME_RATE, // Always append new enum to the end. }; diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h index 40d096e1e5..ba9ee6c752 100644 --- a/libs/gui/include/gui/Surface.h +++ b/libs/gui/include/gui/Surface.h @@ -35,6 +35,10 @@ namespace android { +namespace gui { +class ISurfaceComposer; +} // namespace gui + class ISurfaceComposer; /* This is the same as ProducerListener except that onBuffersDiscarded is @@ -196,6 +200,7 @@ protected: // Virtual for testing. virtual sp composerService() const; + virtual sp composerServiceAIDL() const; virtual nsecs_t now() const; private: diff --git a/libs/gui/include/private/gui/ComposerServiceAIDL.h b/libs/gui/include/private/gui/ComposerServiceAIDL.h index b32cf2a9c2..9a96976c0f 100644 --- a/libs/gui/include/private/gui/ComposerServiceAIDL.h +++ b/libs/gui/include/private/gui/ComposerServiceAIDL.h @@ -68,7 +68,8 @@ public: if (!displayId) return nullptr; sp display; binder::Status status = - mComposerService->getPhysicalDisplayToken(displayId->value, &display); + mComposerService->getPhysicalDisplayToken(static_cast(displayId->value), + &display); return status.isOk() ? display : nullptr; } }; diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 07ac2d4065..ec9cba56a2 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -732,7 +732,6 @@ public: return NO_ERROR; } - void setPowerMode(const sp& /*display*/, int /*mode*/) override {} status_t getStaticDisplayInfo(const sp& /*display*/, ui::StaticDisplayInfo*) override { return NO_ERROR; } @@ -751,13 +750,9 @@ public: } status_t setActiveColorMode(const sp& /*display*/, ColorMode /*colorMode*/) override { return NO_ERROR; } - status_t getBootDisplayModeSupport(bool* /*outSupport*/) const override { return NO_ERROR; } status_t setBootDisplayMode(const sp& /*display*/, ui::DisplayModeId /*id*/) override { return NO_ERROR; } - status_t clearBootDisplayMode(const sp& /*display*/) override { return NO_ERROR; } - void setAutoLowLatencyMode(const sp& /*display*/, bool /*on*/) override {} - void setGameContentType(const sp& /*display*/, bool /*on*/) override {} status_t clearAnimationFrameStats() override { return NO_ERROR; } status_t getAnimationFrameStats(FrameStats* /*outStats*/) const override { @@ -804,26 +799,6 @@ public: status_t getColorManagement(bool* /*outGetColorManagement*/) const override { return NO_ERROR; } status_t getProtectedContentSupport(bool* /*outSupported*/) const override { return NO_ERROR; } - status_t isWideColorDisplay(const sp&, bool*) const override { return NO_ERROR; } - status_t getDisplayBrightnessSupport(const sp& /*displayToken*/, - bool* /*outSupport*/) const override { - return NO_ERROR; - } - status_t setDisplayBrightness(const sp& /*displayToken*/, - const gui::DisplayBrightness& /*brightness*/) override { - return NO_ERROR; - } - - status_t addHdrLayerInfoListener(const sp&, - const sp&) override { - return NO_ERROR; - } - - status_t removeHdrLayerInfoListener(const sp&, - const sp&) override { - return NO_ERROR; - } - status_t addRegionSamplingListener(const Rect& /*samplingArea*/, const sp& /*stopLayerHandle*/, const sp& /*listener*/) override { @@ -865,7 +840,6 @@ public: float* /*outAppRequestRefreshRateMax*/) override { return NO_ERROR; }; - status_t notifyPowerBoost(int32_t /*boostId*/) override { return NO_ERROR; } status_t setGlobalShadowSettings(const half4& /*ambientColor*/, const half4& /*spotColor*/, float /*lightPosY*/, float /*lightPosZ*/, diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 779344b299..5ff722b11a 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5418,20 +5418,14 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case SET_DESIRED_DISPLAY_MODE_SPECS: case GET_DESIRED_DISPLAY_MODE_SPECS: case SET_ACTIVE_COLOR_MODE: - case GET_BOOT_DISPLAY_MODE_SUPPORT: case SET_BOOT_DISPLAY_MODE: - case CLEAR_BOOT_DISPLAY_MODE: case GET_AUTO_LOW_LATENCY_MODE_SUPPORT: - case SET_AUTO_LOW_LATENCY_MODE: case GET_GAME_CONTENT_TYPE_SUPPORT: - case SET_GAME_CONTENT_TYPE: - case SET_POWER_MODE: case GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES: case SET_DISPLAY_CONTENT_SAMPLING_ENABLED: case GET_DISPLAYED_CONTENT_SAMPLE: case ADD_TUNNEL_MODE_ENABLED_LISTENER: case REMOVE_TUNNEL_MODE_ENABLED_LISTENER: - case NOTIFY_POWER_BOOST: case SET_GLOBAL_SHADOW_SETTINGS: case ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN: { // OVERRIDE_HDR_TYPES is used by CTS tests, which acquire the necessary @@ -5478,11 +5472,9 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case GET_COLOR_MANAGEMENT: case GET_COMPOSITION_PREFERENCE: case GET_PROTECTED_CONTENT_SUPPORT: - case IS_WIDE_COLOR_DISPLAY: // setFrameRate() is deliberately available for apps to call without any // special permissions. case SET_FRAME_RATE: - case GET_DISPLAY_BRIGHTNESS_SUPPORT: case GET_DISPLAY_DECORATION_SUPPORT: case SET_FRAME_TIMELINE_INFO: case GET_GPU_CONTEXT_PRIORITY: @@ -5490,19 +5482,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { // This is not sensitive information, so should not require permission control. return OK; } - case SET_DISPLAY_BRIGHTNESS: - case ADD_HDR_LAYER_INFO_LISTENER: - case REMOVE_HDR_LAYER_INFO_LISTENER: { - IPCThreadState* ipc = IPCThreadState::self(); - const int pid = ipc->getCallingPid(); - const int uid = ipc->getCallingUid(); - if ((uid != AID_GRAPHICS) && - !PermissionCache::checkPermission(sControlDisplayBrightness, pid, uid)) { - ALOGE("Permission Denial: can't control brightness pid=%d, uid=%d", pid, uid); - return PERMISSION_DENIED; - } - return OK; - } case ADD_FPS_LISTENER: case REMOVE_FPS_LISTENER: case ADD_REGION_SAMPLING_LISTENER: @@ -5553,9 +5532,20 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case GET_PRIMARY_PHYSICAL_DISPLAY_ID: case GET_PHYSICAL_DISPLAY_IDS: case GET_PHYSICAL_DISPLAY_TOKEN: + case SET_POWER_MODE: + case CLEAR_BOOT_DISPLAY_MODE: + case GET_BOOT_DISPLAY_MODE_SUPPORT: + case SET_AUTO_LOW_LATENCY_MODE: + case SET_GAME_CONTENT_TYPE: case CAPTURE_LAYERS: case CAPTURE_DISPLAY: case CAPTURE_DISPLAY_BY_ID: + case IS_WIDE_COLOR_DISPLAY: + case GET_DISPLAY_BRIGHTNESS_SUPPORT: + case SET_DISPLAY_BRIGHTNESS: + case ADD_HDR_LAYER_INFO_LISTENER: + case REMOVE_HDR_LAYER_INFO_LISTENER: + case NOTIFY_POWER_BOOST: LOG_FATAL("Deprecated opcode: %d, migrated to AIDL", code); return PERMISSION_DENIED; } @@ -7212,21 +7202,21 @@ bool SurfaceFlinger::commitCreatedLayers() { binder::Status SurfaceComposerAIDL::createDisplay(const std::string& displayName, bool secure, sp* outDisplay) { status_t status = checkAccessPermission(); - if (status == OK) { - String8 displayName8 = String8::format("%s", displayName.c_str()); - *outDisplay = mFlinger->createDisplay(displayName8, secure); - return binder::Status::ok(); + if (status != OK) { + return binder::Status::fromStatusT(status); } - return binder::Status::fromStatusT(status); + String8 displayName8 = String8::format("%s", displayName.c_str()); + *outDisplay = mFlinger->createDisplay(displayName8, secure); + return binder::Status::ok(); } binder::Status SurfaceComposerAIDL::destroyDisplay(const sp& display) { status_t status = checkAccessPermission(); - if (status == OK) { - mFlinger->destroyDisplay(display); - return binder::Status::ok(); + if (status != OK) { + return binder::Status::fromStatusT(status); } - return binder::Status::fromStatusT(status); + mFlinger->destroyDisplay(display); + return binder::Status::ok(); } binder::Status SurfaceComposerAIDL::getPhysicalDisplayIds(std::vector* outDisplayIds) { @@ -7261,6 +7251,49 @@ binder::Status SurfaceComposerAIDL::getPhysicalDisplayToken(int64_t displayId, return binder::Status::ok(); } +binder::Status SurfaceComposerAIDL::setPowerMode(const sp& display, int mode) { + status_t status = checkAccessPermission(); + if (status != OK) { + return binder::Status::fromStatusT(status); + } + mFlinger->setPowerMode(display, mode); + return binder::Status::ok(); +} + +binder::Status SurfaceComposerAIDL::clearBootDisplayMode(const sp& display) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->clearBootDisplayMode(display); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getBootDisplayModeSupport(bool* outMode) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->getBootDisplayModeSupport(outMode); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::setAutoLowLatencyMode(const sp& display, bool on) { + status_t status = checkAccessPermission(); + if (status != OK) { + return binder::Status::fromStatusT(status); + } + mFlinger->setAutoLowLatencyMode(display, on); + return binder::Status::ok(); +} + +binder::Status SurfaceComposerAIDL::setGameContentType(const sp& display, bool on) { + status_t status = checkAccessPermission(); + if (status != OK) { + return binder::Status::fromStatusT(status); + } + mFlinger->setGameContentType(display, on); + return binder::Status::ok(); +} + binder::Status SurfaceComposerAIDL::captureDisplay( const DisplayCaptureArgs& args, const sp& captureListener) { status_t status = mFlinger->captureDisplay(args, captureListener); @@ -7287,6 +7320,53 @@ binder::Status SurfaceComposerAIDL::captureLayers( return binder::Status::fromStatusT(status); } +binder::Status SurfaceComposerAIDL::isWideColorDisplay(const sp& token, + bool* outIsWideColorDisplay) { + status_t status = mFlinger->isWideColorDisplay(token, outIsWideColorDisplay); + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getDisplayBrightnessSupport(const sp& displayToken, + bool* outSupport) { + status_t status = mFlinger->getDisplayBrightnessSupport(displayToken, outSupport); + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::setDisplayBrightness(const sp& displayToken, + const gui::DisplayBrightness& brightness) { + status_t status = checkControlDisplayBrightnessPermission(); + if (status == OK) { + status = mFlinger->setDisplayBrightness(displayToken, brightness); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::addHdrLayerInfoListener( + const sp& displayToken, const sp& listener) { + status_t status = checkControlDisplayBrightnessPermission(); + if (status == OK) { + status = mFlinger->addHdrLayerInfoListener(displayToken, listener); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::removeHdrLayerInfoListener( + const sp& displayToken, const sp& listener) { + status_t status = checkControlDisplayBrightnessPermission(); + if (status == OK) { + status = mFlinger->removeHdrLayerInfoListener(displayToken, listener); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::notifyPowerBoost(int boostId) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->notifyPowerBoost(boostId); + } + return binder::Status::fromStatusT(status); +} + status_t SurfaceComposerAIDL::checkAccessPermission(bool usePermissionCache) { if (!mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(usePermissionCache)) { IPCThreadState* ipc = IPCThreadState::self(); @@ -7297,6 +7377,18 @@ status_t SurfaceComposerAIDL::checkAccessPermission(bool usePermissionCache) { return OK; } +status_t SurfaceComposerAIDL::checkControlDisplayBrightnessPermission() { + IPCThreadState* ipc = IPCThreadState::self(); + const int pid = ipc->getCallingPid(); + const int uid = ipc->getCallingUid(); + if ((uid != AID_GRAPHICS) && + !PermissionCache::checkPermission(sControlDisplayBrightness, pid, uid)) { + ALOGE("Permission Denial: can't control brightness pid=%d, uid=%d", pid, uid); + return PERMISSION_DENIED; + } + return OK; +} + } // namespace android #if defined(__gl_h_) diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index d7e5207f20..95c07eb054 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -572,12 +572,12 @@ private: status_t getDisplayNativePrimaries(const sp& displayToken, ui::DisplayPrimaries&) override; status_t setActiveColorMode(const sp& displayToken, ui::ColorMode colorMode) override; - status_t getBootDisplayModeSupport(bool* outSupport) const override; + status_t getBootDisplayModeSupport(bool* outSupport) const; status_t setBootDisplayMode(const sp& displayToken, ui::DisplayModeId id) override; - status_t clearBootDisplayMode(const sp& displayToken) override; - void setAutoLowLatencyMode(const sp& displayToken, bool on) override; - void setGameContentType(const sp& displayToken, bool on) override; - void setPowerMode(const sp& displayToken, int mode) override; + status_t clearBootDisplayMode(const sp& displayToken); + void setAutoLowLatencyMode(const sp& displayToken, bool on); + void setGameContentType(const sp& displayToken, bool on); + void setPowerMode(const sp& displayToken, int mode); status_t clearAnimationFrameStats() override; status_t getAnimationFrameStats(FrameStats* outStats) const override; status_t overrideHdrTypes(const sp& displayToken, @@ -600,8 +600,7 @@ private: uint64_t timestamp, DisplayedFrameStats* outStats) const override; status_t getProtectedContentSupport(bool* outSupported) const override; - status_t isWideColorDisplay(const sp& displayToken, - bool* outIsWideColorDisplay) const override; + status_t isWideColorDisplay(const sp& displayToken, bool* outIsWideColorDisplay) const; status_t addRegionSamplingListener(const Rect& samplingArea, const sp& stopLayerHandle, const sp& listener) override; status_t removeRegionSamplingListener(const sp& listener) override; @@ -623,15 +622,14 @@ private: float* outPrimaryRefreshRateMax, float* outAppRequestRefreshRateMin, float* outAppRequestRefreshRateMax) override; - status_t getDisplayBrightnessSupport(const sp& displayToken, - bool* outSupport) const override; + status_t getDisplayBrightnessSupport(const sp& displayToken, bool* outSupport) const; status_t setDisplayBrightness(const sp& displayToken, - const gui::DisplayBrightness& brightness) override; + const gui::DisplayBrightness& brightness); status_t addHdrLayerInfoListener(const sp& displayToken, - const sp& listener) override; + const sp& listener); status_t removeHdrLayerInfoListener(const sp& displayToken, - const sp& listener) override; - status_t notifyPowerBoost(int32_t boostId) override; + const sp& listener); + status_t notifyPowerBoost(int32_t boostId); status_t setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor, float lightPosY, float lightPosZ, float lightRadius) override; status_t getDisplayDecorationSupport( @@ -1439,16 +1437,33 @@ public: binder::Status getPhysicalDisplayIds(std::vector* outDisplayIds) override; binder::Status getPrimaryPhysicalDisplayId(int64_t* outDisplayId) override; binder::Status getPhysicalDisplayToken(int64_t displayId, sp* outDisplay) override; - + binder::Status setPowerMode(const sp& display, int mode) override; + binder::Status clearBootDisplayMode(const sp& display) override; + binder::Status getBootDisplayModeSupport(bool* outMode) override; + binder::Status setAutoLowLatencyMode(const sp& display, bool on) override; + binder::Status setGameContentType(const sp& display, bool on) override; binder::Status captureDisplay(const DisplayCaptureArgs&, const sp&) override; binder::Status captureDisplayById(int64_t, const sp&) override; binder::Status captureLayers(const LayerCaptureArgs&, const sp&) override; + binder::Status isWideColorDisplay(const sp& token, + bool* outIsWideColorDisplay) override; + binder::Status getDisplayBrightnessSupport(const sp& displayToken, + bool* outSupport) override; + binder::Status setDisplayBrightness(const sp& displayToken, + const gui::DisplayBrightness& brightness) override; + binder::Status addHdrLayerInfoListener(const sp& displayToken, + const sp& listener) override; + binder::Status removeHdrLayerInfoListener( + const sp& displayToken, + const sp& listener) override; + binder::Status notifyPowerBoost(int boostId) override; private: static const constexpr bool kUsePermissionCache = true; status_t checkAccessPermission(bool usePermissionCache = kUsePermissionCache); + status_t checkControlDisplayBrightnessPermission(); private: sp mFlinger; diff --git a/services/surfaceflinger/tests/BootDisplayMode_test.cpp b/services/surfaceflinger/tests/BootDisplayMode_test.cpp index abdb16debf..d70908e390 100644 --- a/services/surfaceflinger/tests/BootDisplayMode_test.cpp +++ b/services/surfaceflinger/tests/BootDisplayMode_test.cpp @@ -20,28 +20,33 @@ #include #include +#include #include namespace android { TEST(BootDisplayModeTest, setBootDisplayMode) { sp sf(ComposerService::getComposerService()); + sp sf_aidl(ComposerServiceAIDL::getComposerService()); auto displayToken = SurfaceComposerClient::getInternalDisplayToken(); bool bootModeSupport = false; - ASSERT_NO_FATAL_FAILURE(sf->getBootDisplayModeSupport(&bootModeSupport)); + binder::Status status = sf_aidl->getBootDisplayModeSupport(&bootModeSupport); + ASSERT_NO_FATAL_FAILURE(status.transactionError()); if (bootModeSupport) { ASSERT_EQ(NO_ERROR, sf->setBootDisplayMode(displayToken, 0)); } } TEST(BootDisplayModeTest, clearBootDisplayMode) { - sp sf(ComposerService::getComposerService()); + sp sf(ComposerServiceAIDL::getComposerService()); auto displayToken = SurfaceComposerClient::getInternalDisplayToken(); bool bootModeSupport = false; - ASSERT_NO_FATAL_FAILURE(sf->getBootDisplayModeSupport(&bootModeSupport)); + binder::Status status = sf->getBootDisplayModeSupport(&bootModeSupport); + ASSERT_NO_FATAL_FAILURE(status.transactionError()); if (bootModeSupport) { - ASSERT_EQ(NO_ERROR, sf->clearBootDisplayMode(displayToken)); + status = sf->clearBootDisplayMode(displayToken); + ASSERT_EQ(NO_ERROR, status.transactionError()); } } -} // namespace android \ No newline at end of file +} // namespace android -- GitLab From f50d677e2cd96296a57a76e6ece409e08adae6ba Mon Sep 17 00:00:00 2001 From: Kriti Dang Date: Fri, 18 Feb 2022 15:09:12 +0100 Subject: [PATCH 0011/1310] Map the hwc-id -> SF-id in getPreferredBootDisplayMode and SF-Id -> HWC id in setUserPreferredDisplayMode Bug: 219959797 Test: m Change-Id: I20e69dd50ad06b527a833ebef5e617b2ebd3d236 --- .../include/compositionengine/Display.h | 2 +- .../include/compositionengine/impl/Display.h | 4 ++-- .../include/compositionengine/mock/Display.h | 2 +- .../CompositionEngine/src/Display.cpp | 6 +++--- services/surfaceflinger/DisplayDevice.cpp | 17 ++++++++++++++++- services/surfaceflinger/DisplayDevice.h | 3 +++ services/surfaceflinger/SurfaceFlinger.cpp | 11 +++++++++-- 7 files changed, 35 insertions(+), 10 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h index 47aacc99f6..6a3fcb7307 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h @@ -57,7 +57,7 @@ public: virtual void createClientCompositionCache(uint32_t cacheSize) = 0; // Returns the boot display mode preferred by HWC. - virtual int32_t getPreferredBootModeId() const = 0; + virtual int32_t getPreferredBootHwcConfigId() const = 0; protected: ~Display() = default; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h index ebe112b20b..6c7ba2287a 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h @@ -62,7 +62,7 @@ public: bool isSecure() const override; bool isVirtual() const override; void disconnect() override; - int32_t getPreferredBootModeId() const override; + int32_t getPreferredBootHwcConfigId() const override; void createDisplayColorProfile( const compositionengine::DisplayColorProfileCreationArgs&) override; void createRenderSurface(const compositionengine::RenderSurfaceCreationArgs&) override; @@ -88,7 +88,7 @@ private: DisplayId mId; bool mIsDisconnected = false; Hwc2::PowerAdvisor* mPowerAdvisor = nullptr; - int32_t mPreferredBootDisplayModeId = -1; + int32_t mPreferredBootHwcConfigId = -1; }; // This template factory function standardizes the implementation details of the diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h index 84afb59510..d90cc909ba 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h @@ -34,7 +34,7 @@ public: MOCK_CONST_METHOD0(getId, DisplayId()); MOCK_CONST_METHOD0(isSecure, bool()); MOCK_CONST_METHOD0(isVirtual, bool()); - MOCK_CONST_METHOD0(getPreferredBootModeId, int32_t()); + MOCK_CONST_METHOD0(getPreferredBootHwcConfigId, int32_t()); MOCK_METHOD0(disconnect, void()); diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp index a3188f3772..b9d7983701 100644 --- a/services/surfaceflinger/CompositionEngine/src/Display.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp @@ -66,7 +66,7 @@ void Display::setConfiguration(const compositionengine::DisplayCreationArgs& arg std::optional preferredBootModeId = getCompositionEngine().getHwComposer().getPreferredBootDisplayMode(*physicalId); if (preferredBootModeId.has_value()) { - mPreferredBootDisplayModeId = static_cast(preferredBootModeId.value()); + mPreferredBootHwcConfigId = static_cast(preferredBootModeId.value()); } } @@ -90,8 +90,8 @@ std::optional Display::getDisplayId() const { return mId; } -int32_t Display::getPreferredBootModeId() const { - return mPreferredBootDisplayModeId; +int32_t Display::getPreferredBootHwcConfigId() const { + return mPreferredBootHwcConfigId; } void Display::disconnect() { diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index f542161b93..2b41ef4c07 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -232,6 +232,15 @@ DisplayModePtr DisplayDevice::getMode(DisplayModeId modeId) const { return nullptr; } +DisplayModePtr DisplayDevice::getModefromHwcId(uint32_t hwcId) const { + const auto it = std::find_if(mSupportedModes.begin(), mSupportedModes.end(), + [&](DisplayModePtr mode) { return mode->getHwcId() == hwcId; }); + if (it != mSupportedModes.end()) { + return *it; + } + return nullptr; +} + nsecs_t DisplayDevice::getVsyncPeriodFromHWC() const { const auto physicalId = getPhysicalId(); if (!mHwComposer.isConnected(physicalId)) { @@ -457,7 +466,13 @@ HdrCapabilities DisplayDevice::getHdrCapabilities() const { } ui::DisplayModeId DisplayDevice::getPreferredBootModeId() const { - return mCompositionDisplay->getPreferredBootModeId(); + const auto preferredBootHwcModeId = mCompositionDisplay->getPreferredBootHwcConfigId(); + const auto mode = getModefromHwcId(preferredBootHwcModeId); + if (mode == nullptr) { + ALOGE("%s: invalid display mode (%d)", __FUNCTION__, preferredBootHwcModeId); + return BAD_VALUE; + } + return mode->getId().value(); } void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinnner) { diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index 3cae30fe92..e8bd62dfac 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -227,6 +227,9 @@ public: // set-top boxes after a hotplug reconnect. DisplayModePtr getMode(DisplayModeId) const; + // Returns nullptr if the given mode ID is not supported. + DisplayModePtr getModefromHwcId(uint32_t) const; + // Returns the refresh rate configs for this display. scheduler::RefreshRateConfigs& refreshRateConfigs() const { return *mRefreshRateConfigs; } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 980d1dca54..54a777a30a 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1440,8 +1440,15 @@ status_t SurfaceFlinger::getBootDisplayModeSupport(bool* outSupport) const { status_t SurfaceFlinger::setBootDisplayMode(const sp& displayToken, ui::DisplayModeId id) { auto future = mScheduler->schedule([=]() MAIN_THREAD -> status_t { - if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) { - return getHwComposer().setBootDisplayMode(*displayId, id); + if (const auto displayDevice = getDisplayDeviceLocked(displayToken)) { + const auto mode = displayDevice->getMode(DisplayModeId{id}); + if (mode == nullptr) { + ALOGE("%s: invalid display mode (%d)", __FUNCTION__, id); + return BAD_VALUE; + } + + return getHwComposer().setBootDisplayMode(displayDevice->getPhysicalId(), + mode->getHwcId()); } else { ALOGE("%s: Invalid display token %p", __FUNCTION__, displayToken.get()); return BAD_VALUE; -- GitLab From 8eebba4486626cae984a9ec5ffb1cbe0677585db Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Fri, 25 Feb 2022 07:57:15 -0800 Subject: [PATCH 0012/1310] Sf Tracing: Set a valid/fired acquire fence Fixes a NPE with acquire fence issues when generating layers trace from transaction traces. Bug: 200284593 Test: atest transactiontrace_testsuite Change-Id: I7e07b5a84af097a53927635ec83226f850d5a9e6 --- services/surfaceflinger/Tracing/TransactionProtoParser.cpp | 2 ++ services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp index 1e5c3e70c8..4cd5ace45f 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp @@ -15,6 +15,7 @@ */ #include +#include #include #include "LayerProtoHelper.h" @@ -431,6 +432,7 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta layer.bufferData->frameNumber = bufferProto.frame_number(); layer.bufferData->flags = Flags(bufferProto.flags()); layer.bufferData->cachedBuffer.id = bufferProto.cached_buffer_id(); + layer.bufferData->acquireFence = Fence::NO_FENCE; } if (proto.what() & layer_state_t::eApiChanged) { diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index 947fdcee4e..9cbd257f31 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -181,6 +181,7 @@ public: bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, const char* outputLayersTracePath) { if (traceFile.entry_size() == 0) { + ALOGD("Trace file is empty"); return false; } -- GitLab From aa7fc2e4495398a717a4fe0ffbfd783e3a887cec Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Tue, 15 Feb 2022 10:43:00 -0800 Subject: [PATCH 0013/1310] Migrate display related methods to AIDL part 3 This migrates more display related methods from ISurfaceComposer.h to the new AIDL interface. (1) migrate getDisplaySttas() and getDisplayState() methods (2) add new parcelables, android.gui.DisplayState, anddroid.gui.DisplayStatInfo and other utilities. (3) all parceables are added into libgui, instead of libui, so libui is isolated from binder serialization code,which is cleaner and avoids libbinder linking errors. Bug: 220043617 Bug: 219574942 Test: atest SurfaceFlinger_test libsurfaceflinger_unittest libgui_test Change-Id: Iacdc42dde1608f883c5578aa3d9f9f8ae9f23038 --- libs/gui/ISurfaceComposer.cpp | 50 -------- libs/gui/Surface.cpp | 10 +- libs/gui/SurfaceComposerClient.cpp | 13 +- .../gui/aidl/android/gui/DisplayStatInfo.aidl | 23 ++++ libs/gui/aidl/android/gui/DisplayState.aidl | 27 +++++ .../aidl/android/gui/ISurfaceComposer.aidl | 12 ++ libs/gui/aidl/android/gui/Rect.aidl | 35 ++++++ libs/gui/aidl/android/gui/Rotation.aidl | 26 ++++ libs/gui/aidl/android/gui/Size.aidl | 23 ++++ libs/gui/include/gui/ISurfaceComposer.h | 11 -- libs/gui/tests/Surface_test.cpp | 114 +++++++++++++++++- services/surfaceflinger/SurfaceFlinger.cpp | 28 ++++- services/surfaceflinger/SurfaceFlinger.h | 11 +- 13 files changed, 306 insertions(+), 77 deletions(-) create mode 100644 libs/gui/aidl/android/gui/DisplayStatInfo.aidl create mode 100644 libs/gui/aidl/android/gui/DisplayState.aidl create mode 100644 libs/gui/aidl/android/gui/Rect.aidl create mode 100644 libs/gui/aidl/android/gui/Rotation.aidl create mode 100644 libs/gui/aidl/android/gui/Size.aidl diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index d7ec9ff1d6..3c02e21aff 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -220,18 +220,6 @@ public: return result; } - status_t getDisplayState(const sp& display, ui::DisplayState* state) override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - data.writeStrongBinder(display); - remote()->transact(BnSurfaceComposer::GET_DISPLAY_STATE, data, &reply); - const status_t result = reply.readInt32(); - if (result == NO_ERROR) { - memcpy(state, reply.readInplace(sizeof(ui::DisplayState)), sizeof(ui::DisplayState)); - } - return result; - } - status_t getStaticDisplayInfo(const sp& display, ui::StaticDisplayInfo* info) override { Parcel data, reply; @@ -254,20 +242,6 @@ public: return reply.read(*info); } - status_t getDisplayStats(const sp& display, DisplayStatInfo* stats) override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - data.writeStrongBinder(display); - remote()->transact(BnSurfaceComposer::GET_DISPLAY_STATS, data, &reply); - status_t result = reply.readInt32(); - if (result == NO_ERROR) { - memcpy(stats, - reply.readInplace(sizeof(DisplayStatInfo)), - sizeof(DisplayStatInfo)); - } - return result; - } - status_t getDisplayNativePrimaries(const sp& display, ui::DisplayPrimaries& primaries) override { Parcel data, reply; @@ -1165,18 +1139,6 @@ status_t BnSurfaceComposer::onTransact( reply->writeStrongBinder(IInterface::asBinder(connection)); return NO_ERROR; } - case GET_DISPLAY_STATE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - ui::DisplayState state; - const sp display = data.readStrongBinder(); - const status_t result = getDisplayState(display, &state); - reply->writeInt32(result); - if (result == NO_ERROR) { - memcpy(reply->writeInplace(sizeof(ui::DisplayState)), &state, - sizeof(ui::DisplayState)); - } - return NO_ERROR; - } case GET_STATIC_DISPLAY_INFO: { CHECK_INTERFACE(ISurfaceComposer, data, reply); ui::StaticDisplayInfo info; @@ -1197,18 +1159,6 @@ status_t BnSurfaceComposer::onTransact( SAFE_PARCEL(reply->write, info); return NO_ERROR; } - case GET_DISPLAY_STATS: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - DisplayStatInfo stats; - sp display = data.readStrongBinder(); - status_t result = getDisplayStats(display, &stats); - reply->writeInt32(result); - if (result == NO_ERROR) { - memcpy(reply->writeInplace(sizeof(DisplayStatInfo)), - &stats, sizeof(DisplayStatInfo)); - } - return NO_ERROR; - } case GET_DISPLAY_NATIVE_PRIMARIES: { CHECK_INTERFACE(ISurfaceComposer, data, reply); ui::DisplayPrimaries primaries; diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index ceb517f2ff..0f0a5c8504 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -27,13 +27,13 @@ #include +#include #include #include #include #include -#include #include #include #include @@ -179,10 +179,10 @@ status_t Surface::getLastQueuedBuffer(sp* outBuffer, status_t Surface::getDisplayRefreshCycleDuration(nsecs_t* outRefreshDuration) { ATRACE_CALL(); - DisplayStatInfo stats; - status_t result = composerService()->getDisplayStats(nullptr, &stats); - if (result != NO_ERROR) { - return result; + gui::DisplayStatInfo stats; + binder::Status status = composerServiceAIDL()->getDisplayStats(nullptr, &stats); + if (!status.isOk()) { + return status.transactionError(); } *outRefreshDuration = stats.vsyncPeriod; diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 6b2cda19a0..447b3ef0ab 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -43,6 +44,7 @@ #include #include #include +#include #include #include @@ -2094,7 +2096,16 @@ status_t SurfaceComposerClient::injectVSync(nsecs_t when) { status_t SurfaceComposerClient::getDisplayState(const sp& display, ui::DisplayState* state) { - return ComposerService::getComposerService()->getDisplayState(display, state); + gui::DisplayState ds; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getDisplayState(display, &ds); + if (status.isOk()) { + state->layerStack = ui::LayerStack::fromValue(ds.layerStack); + state->orientation = static_cast(ds.orientation); + state->layerStackSpaceRect = + ui::Size(ds.layerStackSpaceRect.width, ds.layerStackSpaceRect.height); + } + return status.transactionError(); } status_t SurfaceComposerClient::getStaticDisplayInfo(const sp& display, diff --git a/libs/gui/aidl/android/gui/DisplayStatInfo.aidl b/libs/gui/aidl/android/gui/DisplayStatInfo.aidl new file mode 100644 index 0000000000..68f394281e --- /dev/null +++ b/libs/gui/aidl/android/gui/DisplayStatInfo.aidl @@ -0,0 +1,23 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +/** @hide */ +parcelable DisplayStatInfo { + long vsyncTime; + long vsyncPeriod; +} diff --git a/libs/gui/aidl/android/gui/DisplayState.aidl b/libs/gui/aidl/android/gui/DisplayState.aidl new file mode 100644 index 0000000000..9589ab6b1a --- /dev/null +++ b/libs/gui/aidl/android/gui/DisplayState.aidl @@ -0,0 +1,27 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +import android.gui.Rotation; +import android.gui.Size; + +/** @hide */ +parcelable DisplayState { + int layerStack; + Rotation orientation = Rotation.Rotation0; + Size layerStackSpaceRect; +} diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 526fae8e55..a9977b0f45 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -18,6 +18,8 @@ package android.gui; import android.gui.DisplayCaptureArgs; import android.gui.DisplayBrightness; +import android.gui.DisplayState; +import android.gui.DisplayStatInfo; import android.gui.IHdrLayerInfoListener; import android.gui.LayerCaptureArgs; import android.gui.IScreenCaptureListener; @@ -52,6 +54,16 @@ interface ISurfaceComposer { */ void setPowerMode(IBinder display, int mode); + /* returns display statistics for a given display + * intended to be used by the media framework to properly schedule + * video frames */ + DisplayStatInfo getDisplayStats(IBinder display); + + /** + * Get transactional state of given display. + */ + DisplayState getDisplayState(IBinder display); + /** * Clears the user-preferred display mode. The device should now boot in system preferred * display mode. diff --git a/libs/gui/aidl/android/gui/Rect.aidl b/libs/gui/aidl/android/gui/Rect.aidl new file mode 100644 index 0000000000..1b13761392 --- /dev/null +++ b/libs/gui/aidl/android/gui/Rect.aidl @@ -0,0 +1,35 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +// copied from libs/arect/include/android/rect.h +// TODO(b/221473398): +// use hardware/interfaces/graphics/common/aidl/android/hardware/graphics/common/Rect.aidl +/** @hide */ +parcelable Rect { + /// Minimum X coordinate of the rectangle. + int left; + + /// Minimum Y coordinate of the rectangle. + int top; + + /// Maximum X coordinate of the rectangle. + int right; + + /// Maximum Y coordinate of the rectangle. + int bottom; +} diff --git a/libs/gui/aidl/android/gui/Rotation.aidl b/libs/gui/aidl/android/gui/Rotation.aidl new file mode 100644 index 0000000000..451ff45ccf --- /dev/null +++ b/libs/gui/aidl/android/gui/Rotation.aidl @@ -0,0 +1,26 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +/** @hide */ +@Backing(type="int") +enum Rotation { + Rotation0 = 0, + Rotation90 = 1, + Rotation180 = 2, + Rotation270 = 3 +} diff --git a/libs/gui/aidl/android/gui/Size.aidl b/libs/gui/aidl/android/gui/Size.aidl new file mode 100644 index 0000000000..415fa36fee --- /dev/null +++ b/libs/gui/aidl/android/gui/Size.aidl @@ -0,0 +1,23 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +/** @hide */ +parcelable Size { + int width = -1; + int height = -1; +} diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index 0a2ae35044..2e4d6b470c 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -160,17 +160,6 @@ public: virtual status_t getSupportedFrameTimestamps( std::vector* outSupported) const = 0; - /* returns display statistics for a given display - * intended to be used by the media framework to properly schedule - * video frames */ - virtual status_t getDisplayStats(const sp& display, - DisplayStatInfo* stats) = 0; - - /** - * Get transactional state of given display. - */ - virtual status_t getDisplayState(const sp& display, ui::DisplayState*) = 0; - /** * Gets immutable information about given physical display. */ diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index ec9cba56a2..e0b86e02fb 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -739,11 +740,6 @@ public: ui::DynamicDisplayInfo*) override { return NO_ERROR; } - status_t getDisplayState(const sp& /*display*/, ui::DisplayState*) override { - return NO_ERROR; - } - status_t getDisplayStats(const sp& /*display*/, - DisplayStatInfo* /*stats*/) override { return NO_ERROR; } status_t getDisplayNativePrimaries(const sp& /*display*/, ui::DisplayPrimaries& /*primaries*/) override { return NO_ERROR; @@ -891,6 +887,114 @@ private: bool mSupportsPresent{true}; }; +class FakeSurfaceComposerAIDL : public gui::ISurfaceComposer { +public: + ~FakeSurfaceComposerAIDL() override {} + + void setSupportsPresent(bool supportsPresent) { mSupportsPresent = supportsPresent; } + + binder::Status createDisplay(const std::string& /*displayName*/, bool /*secure*/, + sp* /*outDisplay*/) override { + return binder::Status::ok(); + } + + binder::Status destroyDisplay(const sp& /*display*/) override { + return binder::Status::ok(); + } + + binder::Status getPhysicalDisplayIds(std::vector* /*outDisplayIds*/) override { + return binder::Status::ok(); + } + + binder::Status getPrimaryPhysicalDisplayId(int64_t* /*outDisplayId*/) override { + return binder::Status::ok(); + } + + binder::Status getPhysicalDisplayToken(int64_t /*displayId*/, + sp* /*outDisplay*/) override { + return binder::Status::ok(); + } + + binder::Status setPowerMode(const sp& /*display*/, int /*mode*/) override { + return binder::Status::ok(); + } + + binder::Status getDisplayStats(const sp& /*display*/, + gui::DisplayStatInfo* /*outStatInfo*/) override { + return binder::Status::ok(); + } + + binder::Status getDisplayState(const sp& /*display*/, + gui::DisplayState* /*outState*/) override { + return binder::Status::ok(); + } + + binder::Status clearBootDisplayMode(const sp& /*display*/) override { + return binder::Status::ok(); + } + + binder::Status getBootDisplayModeSupport(bool* /*outMode*/) override { + return binder::Status::ok(); + } + + binder::Status setAutoLowLatencyMode(const sp& /*display*/, bool /*on*/) override { + return binder::Status::ok(); + } + + binder::Status setGameContentType(const sp& /*display*/, bool /*on*/) override { + return binder::Status::ok(); + } + + binder::Status captureDisplay(const DisplayCaptureArgs&, + const sp&) override { + return binder::Status::ok(); + } + + binder::Status captureDisplayById(int64_t, const sp&) override { + return binder::Status::ok(); + } + + binder::Status captureLayers(const LayerCaptureArgs&, + const sp&) override { + return binder::Status::ok(); + } + + binder::Status isWideColorDisplay(const sp& /*token*/, + bool* /*outIsWideColorDisplay*/) override { + return binder::Status::ok(); + } + + binder::Status getDisplayBrightnessSupport(const sp& /*displayToken*/, + bool* /*outSupport*/) override { + return binder::Status::ok(); + } + + binder::Status setDisplayBrightness(const sp& /*displayToken*/, + const gui::DisplayBrightness& /*brightness*/) override { + return binder::Status::ok(); + } + + binder::Status addHdrLayerInfoListener( + const sp& /*displayToken*/, + const sp& /*listener*/) override { + return binder::Status::ok(); + } + + binder::Status removeHdrLayerInfoListener( + const sp& /*displayToken*/, + const sp& /*listener*/) override { + return binder::Status::ok(); + } + + binder::Status notifyPowerBoost(int /*boostId*/) override { return binder::Status::ok(); } + +protected: + IBinder* onAsBinder() override { return nullptr; } + +private: + bool mSupportsPresent{true}; +}; + class FakeProducerFrameEventHistory : public ProducerFrameEventHistory { public: explicit FakeProducerFrameEventHistory(FenceToFenceTimeMap* fenceMap) : mFenceMap(fenceMap) {} diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 5ff722b11a..83f3681072 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5462,8 +5462,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case GET_STATIC_DISPLAY_INFO: case GET_DYNAMIC_DISPLAY_INFO: case GET_DISPLAY_MODES: - case GET_DISPLAY_STATE: - case GET_DISPLAY_STATS: case GET_SUPPORTED_FRAME_TIMESTAMPS: // Calling setTransactionState is safe, because you need to have been // granted a reference to Client* and Handle* to do anything with it. @@ -5533,6 +5531,8 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case GET_PHYSICAL_DISPLAY_IDS: case GET_PHYSICAL_DISPLAY_TOKEN: case SET_POWER_MODE: + case GET_DISPLAY_STATE: + case GET_DISPLAY_STATS: case CLEAR_BOOT_DISPLAY_MODE: case GET_BOOT_DISPLAY_MODE_SUPPORT: case SET_AUTO_LOW_LATENCY_MODE: @@ -7260,6 +7260,30 @@ binder::Status SurfaceComposerAIDL::setPowerMode(const sp& display, int return binder::Status::ok(); } +binder::Status SurfaceComposerAIDL::getDisplayStats(const sp& display, + gui::DisplayStatInfo* outStatInfo) { + DisplayStatInfo statInfo; + status_t status = mFlinger->getDisplayStats(display, &statInfo); + if (status == NO_ERROR) { + outStatInfo->vsyncTime = static_cast(statInfo.vsyncTime); + outStatInfo->vsyncPeriod = static_cast(statInfo.vsyncPeriod); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getDisplayState(const sp& display, + gui::DisplayState* outState) { + ui::DisplayState state; + status_t status = mFlinger->getDisplayState(display, &state); + if (status == NO_ERROR) { + outState->layerStack = state.layerStack.id; + outState->orientation = static_cast(state.orientation); + outState->layerStackSpaceRect.width = state.layerStackSpaceRect.width; + outState->layerStackSpaceRect.height = state.layerStackSpaceRect.height; + } + return binder::Status::fromStatusT(status); +} + binder::Status SurfaceComposerAIDL::clearBootDisplayMode(const sp& display) { status_t status = checkAccessPermission(); if (status == OK) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 95c07eb054..d44acffabc 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -7,7 +7,6 @@ * * 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 @@ -24,6 +23,8 @@ #include #include +#include +#include #include #include #include @@ -562,9 +563,9 @@ private: status_t captureDisplay(DisplayId, const sp&); status_t captureLayers(const LayerCaptureArgs&, const sp&); - status_t getDisplayStats(const sp& displayToken, DisplayStatInfo* stats) override; + status_t getDisplayStats(const sp& displayToken, DisplayStatInfo* stats); status_t getDisplayState(const sp& displayToken, ui::DisplayState*) - EXCLUDES(mStateLock) override; + EXCLUDES(mStateLock); status_t getStaticDisplayInfo(const sp& displayToken, ui::StaticDisplayInfo*) EXCLUDES(mStateLock) override; status_t getDynamicDisplayInfo(const sp& displayToken, ui::DynamicDisplayInfo*) @@ -1438,6 +1439,10 @@ public: binder::Status getPrimaryPhysicalDisplayId(int64_t* outDisplayId) override; binder::Status getPhysicalDisplayToken(int64_t displayId, sp* outDisplay) override; binder::Status setPowerMode(const sp& display, int mode) override; + binder::Status getDisplayStats(const sp& display, + gui::DisplayStatInfo* outStatInfo) override; + binder::Status getDisplayState(const sp& display, + gui::DisplayState* outState) override; binder::Status clearBootDisplayMode(const sp& display) override; binder::Status getBootDisplayModeSupport(bool* outMode) override; binder::Status setAutoLowLatencyMode(const sp& display, bool on) override; -- GitLab From 1d530208dcfc94da9f1322ed5cf10dc8981ee0e4 Mon Sep 17 00:00:00 2001 From: Kevin Lubick Date: Sat, 26 Feb 2022 13:48:04 +0000 Subject: [PATCH 0014/1310] [includes] Fix more references to SkRRect Change-Id: If6a86409ca63e5c50e9b97d5e4233726839de7ff --- libs/renderengine/skia/SkiaGLRenderEngine.cpp | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index b467b3538d..d05193a490 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -24,16 +24,34 @@ #include #include #include +#include #include +#include #include #include #include +#include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include +#include #include +#include #include #include #include @@ -51,8 +69,6 @@ #include "../gl/GLExtensions.h" #include "Cache.h" #include "ColorSpaces.h" -#include "SkBlendMode.h" -#include "SkImageInfo.h" #include "filters/BlurFilter.h" #include "filters/GaussianBlurFilter.h" #include "filters/KawaseBlurFilter.h" -- GitLab From a3c8e51901d17f696838d3e8260464f7437468a5 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 10 Feb 2022 19:46:34 -0800 Subject: [PATCH 0015/1310] Block touches when stylus is down Since we haven't yet added simultaneous touch and stylus support, let's improve the user experience by blocking all touch when the stylus is down. Bug: 210159205 Test: atest inputflinger_tests Change-Id: Id6a6467d7feb7c7d91770ddbd63b92583832d504 --- services/inputflinger/Android.bp | 1 + .../PreferStylusOverTouchBlocker.cpp | 107 ++++++++ .../PreferStylusOverTouchBlocker.h | 81 ++++++ .../UnwantedInteractionBlocker.cpp | 13 +- .../inputflinger/UnwantedInteractionBlocker.h | 6 + services/inputflinger/tests/Android.bp | 1 + .../tests/PreferStylusOverTouch_test.cpp | 250 ++++++++++++++++++ 7 files changed, 458 insertions(+), 1 deletion(-) create mode 100644 services/inputflinger/PreferStylusOverTouchBlocker.cpp create mode 100644 services/inputflinger/PreferStylusOverTouchBlocker.h create mode 100644 services/inputflinger/tests/PreferStylusOverTouch_test.cpp diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp index ed9bfd272f..41878e3487 100644 --- a/services/inputflinger/Android.bp +++ b/services/inputflinger/Android.bp @@ -58,6 +58,7 @@ filegroup { srcs: [ "InputClassifier.cpp", "InputCommonConverter.cpp", + "PreferStylusOverTouchBlocker.cpp", "UnwantedInteractionBlocker.cpp", "InputManager.cpp", ], diff --git a/services/inputflinger/PreferStylusOverTouchBlocker.cpp b/services/inputflinger/PreferStylusOverTouchBlocker.cpp new file mode 100644 index 0000000000..ad639b4ef8 --- /dev/null +++ b/services/inputflinger/PreferStylusOverTouchBlocker.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "PreferStylusOverTouchBlocker.h" + +#include + +using android::base::StringPrintf; + +static const char* toString(bool value) { + return value ? "true" : "false"; +} + +namespace android { + +ftl::StaticVector PreferStylusOverTouchBlocker::processMotion( + const NotifyMotionArgs& args) { + const bool isStylusEvent = isFromSource(args.source, AINPUT_SOURCE_STYLUS); + if (isStylusEvent) { + for (size_t i = 0; i < args.pointerCount; i++) { + // Make sure we are canceling stylus pointers + const int32_t toolType = args.pointerProperties[i].toolType; + LOG_ALWAYS_FATAL_IF(toolType != AMOTION_EVENT_TOOL_TYPE_STYLUS && + toolType != AMOTION_EVENT_TOOL_TYPE_ERASER, + "The pointer %zu has toolType=%i, but the source is STYLUS. If " + "simultaneous touch and stylus is supported, " + "'PreferStylusOverTouchBlocker' should be disabled.", + i, toolType); + } + } + const bool isDown = args.action == AMOTION_EVENT_ACTION_DOWN; + const bool isUpOrCancel = + args.action == AMOTION_EVENT_ACTION_UP || args.action == AMOTION_EVENT_ACTION_CANCEL; + if (isStylusEvent) { + if (isDown) { + // Reject all touch while stylus is down + mIsStylusDown = true; + if (mIsTouchDown && !mCurrentTouchIsCanceled) { + // Cancel touch! + mCurrentTouchIsCanceled = true; + mLastTouchEvent.action = AMOTION_EVENT_ACTION_CANCEL; + mLastTouchEvent.flags |= AMOTION_EVENT_FLAG_CANCELED; + mLastTouchEvent.eventTime = systemTime(SYSTEM_TIME_MONOTONIC); + return {mLastTouchEvent, args}; + } + } + if (isUpOrCancel) { + mIsStylusDown = false; + } + // Never drop stylus events + return {args}; + } + + const bool isTouchEvent = + isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN) && !isStylusEvent; + if (isTouchEvent) { + if (mIsStylusDown) { + mCurrentTouchIsCanceled = true; + } + // If we already canceled the current gesture, then continue to drop events from it, even if + // the stylus has been lifted. + if (mCurrentTouchIsCanceled) { + if (isUpOrCancel) { + mCurrentTouchIsCanceled = false; + } + return {}; + } + + // Update state + mLastTouchEvent = args; + if (isDown) { + mIsTouchDown = true; + } + if (isUpOrCancel) { + mIsTouchDown = false; + mCurrentTouchIsCanceled = false; + } + return {args}; + } + + // Not a touch or stylus event + return {args}; +} + +std::string PreferStylusOverTouchBlocker::dump() { + std::string out; + out += StringPrintf("mIsTouchDown: %s\n", toString(mIsTouchDown)); + out += StringPrintf("mIsStylusDown: %s\n", toString(mIsStylusDown)); + out += StringPrintf("mLastTouchEvent: %s\n", mLastTouchEvent.dump().c_str()); + out += StringPrintf("mCurrentTouchIsCanceled: %s\n", toString(mCurrentTouchIsCanceled)); + return out; +} + +} // namespace android diff --git a/services/inputflinger/PreferStylusOverTouchBlocker.h b/services/inputflinger/PreferStylusOverTouchBlocker.h new file mode 100644 index 0000000000..3f5616190b --- /dev/null +++ b/services/inputflinger/PreferStylusOverTouchBlocker.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include "InputListener.h" + +namespace android { + +/** + * When stylus is down, we ignore all touch. + * TODO(b/210159205): delete this when simultaneous stylus and touch is supported + */ +class PreferStylusOverTouchBlocker { +public: + /** + * Process the provided event and emit up to 2 events in response. + * In the majority of cases, the returned result will just be the provided args (array with + * only 1 element), unmodified. + * + * If the gesture should be blocked, the returned result may be: + * + * a) An empty array, if the current event should just be ignored completely + * b) An array of 2 elements, containing an event with ACTION_CANCEL and the current event. + * + * bool is set to 'true'. + * NotifyMotionArgs potentially contains an event that should be used to cancel the existing + * gesture. + * + * If the event should not be blocked, bool contains 'false'. + */ + ftl::StaticVector processMotion(const NotifyMotionArgs& args); + std::string dump(); + +private: + bool mIsTouchDown = false; + bool mIsStylusDown = false; + // Provide some default values for the stored MotionEvent to allow printint the event before + // any real event is received. + NotifyMotionArgs mLastTouchEvent{0 /*id*/, + 0 /*eventTime*/, + 0 /*readTime*/, + 0 /*deviceId*/, + AINPUT_SOURCE_TOUCHSCREEN, + 0 /*displayId*/, + 0 /*policyFlags*/, + 0 /*action*/, + 0 /*actionButton*/, + 0 /*flags*/, + 0 /*metaState*/, + 0 /*buttonState*/, + MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, + 0 /*pointerCount*/, + nullptr /*properties*/, + nullptr /*coords*/, + 0. /*xPrecision*/, + 0. /*yPrecision*/, + AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, + 0 /*downTime*/, + {}}; + bool mCurrentTouchIsCanceled = false; +}; + +} // namespace android \ No newline at end of file diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp index 64dbb8ceb4..f904bfd82e 100644 --- a/services/inputflinger/UnwantedInteractionBlocker.cpp +++ b/services/inputflinger/UnwantedInteractionBlocker.cpp @@ -44,7 +44,8 @@ static std::string toLower(std::string s) { } static bool isFromTouchscreen(int32_t source) { - return isFromSource(source, AINPUT_SOURCE_TOUCHSCREEN); + return isFromSource(source, AINPUT_SOURCE_TOUCHSCREEN) && + !isFromSource(source, AINPUT_SOURCE_STYLUS); } static ::base::TimeTicks toChromeTimestamp(nsecs_t eventTime) { @@ -367,6 +368,14 @@ void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs* args) { } void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs* args) { + ftl::StaticVector processedArgs = + mPreferStylusOverTouchBlocker.processMotion(*args); + for (const NotifyMotionArgs& loopArgs : processedArgs) { + notifyMotionInner(&loopArgs); + } +} + +void UnwantedInteractionBlocker::notifyMotionInner(const NotifyMotionArgs* args) { auto it = mPalmRejectors.find(args->deviceId); const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args->source); if (!sendToPalmRejector) { @@ -440,6 +449,8 @@ void UnwantedInteractionBlocker::notifyInputDevicesChanged( void UnwantedInteractionBlocker::dump(std::string& dump) { dump += "UnwantedInteractionBlocker:\n"; + dump += " mPreferStylusOverTouchBlocker:\n"; + dump += addPrefix(mPreferStylusOverTouchBlocker.dump(), " "); dump += StringPrintf(" mEnablePalmRejection: %s\n", toString(mEnablePalmRejection)); dump += StringPrintf(" isPalmRejectionEnabled (flag value): %s\n", toString(isPalmRejectionEnabled())); diff --git a/services/inputflinger/UnwantedInteractionBlocker.h b/services/inputflinger/UnwantedInteractionBlocker.h index 14068fd878..9e41995ac4 100644 --- a/services/inputflinger/UnwantedInteractionBlocker.h +++ b/services/inputflinger/UnwantedInteractionBlocker.h @@ -23,6 +23,8 @@ #include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h" #include "ui/events/ozone/evdev/touch_filter/palm_detection_filter.h" +#include "PreferStylusOverTouchBlocker.h" + namespace android { // --- Functions for manipulation of event streams @@ -88,9 +90,13 @@ private: InputListenerInterface& mListener; const bool mEnablePalmRejection; + // When stylus is down, ignore touch + PreferStylusOverTouchBlocker mPreferStylusOverTouchBlocker; // Detect and reject unwanted palms on screen // Use a separate palm rejector for every touch device. std::map mPalmRejectors; + // TODO(b/210159205): delete this when simultaneous stylus and touch is supported + void notifyMotionInner(const NotifyMotionArgs* args); }; class SlotState { diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 9d200bdeff..76a7c19086 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -47,6 +47,7 @@ cc_test { "InputReader_test.cpp", "InputFlingerService_test.cpp", "LatencyTracker_test.cpp", + "PreferStylusOverTouch_test.cpp", "TestInputListener.cpp", "UinputDevice.cpp", "UnwantedInteractionBlocker_test.cpp", diff --git a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp new file mode 100644 index 0000000000..70f40aa028 --- /dev/null +++ b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "../PreferStylusOverTouchBlocker.h" + +namespace android { + +constexpr int32_t TOUCH_DEVICE_ID = 3; +constexpr int32_t STYLUS_DEVICE_ID = 4; + +constexpr int DOWN = AMOTION_EVENT_ACTION_DOWN; +constexpr int MOVE = AMOTION_EVENT_ACTION_MOVE; +constexpr int UP = AMOTION_EVENT_ACTION_UP; +constexpr int CANCEL = AMOTION_EVENT_ACTION_CANCEL; +constexpr int32_t TOUCHSCREEN = AINPUT_SOURCE_TOUCHSCREEN; +constexpr int32_t STYLUS = AINPUT_SOURCE_STYLUS; + +struct PointerData { + float x; + float y; +}; + +static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, int32_t action, + const std::vector& points, + uint32_t source) { + size_t pointerCount = points.size(); + if (action == DOWN || action == UP) { + EXPECT_EQ(1U, pointerCount) << "Actions DOWN and UP can only contain a single pointer"; + } + + PointerProperties pointerProperties[pointerCount]; + PointerCoords pointerCoords[pointerCount]; + + const int32_t deviceId = isFromSource(source, TOUCHSCREEN) ? TOUCH_DEVICE_ID : STYLUS_DEVICE_ID; + const int32_t toolType = isFromSource(source, TOUCHSCREEN) ? AMOTION_EVENT_TOOL_TYPE_FINGER + : AMOTION_EVENT_TOOL_TYPE_STYLUS; + for (size_t i = 0; i < pointerCount; i++) { + pointerProperties[i].clear(); + pointerProperties[i].id = i; + pointerProperties[i].toolType = toolType; + + pointerCoords[i].clear(); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, points[i].x); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, points[i].y); + } + + // Currently, can't have STYLUS source without it also being a TOUCH source. Update the source + // accordingly. + if (isFromSource(source, STYLUS)) { + source |= TOUCHSCREEN; + } + + // Define a valid motion event. + NotifyMotionArgs args(/* id */ 0, eventTime, 0 /*readTime*/, deviceId, source, 0 /*displayId*/, + POLICY_FLAG_PASS_TO_USER, action, /* actionButton */ 0, + /* flags */ 0, AMETA_NONE, /* buttonState */ 0, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, + pointerProperties, pointerCoords, /* xPrecision */ 0, /* yPrecision */ 0, + AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, /* videoFrames */ {}); + + return args; +} + +class PreferStylusOverTouchTest : public testing::Test { +protected: + void assertNotBlocked(const NotifyMotionArgs& args) { + ftl::StaticVector processedArgs = mBlocker.processMotion(args); + ASSERT_EQ(1u, processedArgs.size()); + ASSERT_EQ(args, processedArgs[0]); + } + + void assertDropped(const NotifyMotionArgs& args) { + ftl::StaticVector processedArgs = mBlocker.processMotion(args); + ASSERT_TRUE(processedArgs.empty()); + } + + void assertCanceled(const NotifyMotionArgs& args, + std::optional canceledArgs) { + ftl::StaticVector processedArgs = mBlocker.processMotion(args); + ASSERT_EQ(2u, processedArgs.size()); + NotifyMotionArgs& cancelEvent = processedArgs[0]; + ASSERT_EQ(CANCEL, cancelEvent.action); + ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, cancelEvent.flags & AMOTION_EVENT_FLAG_CANCELED); + ASSERT_TRUE(isFromSource(cancelEvent.source, TOUCHSCREEN)); + ASSERT_FALSE(isFromSource(cancelEvent.source, STYLUS)); + + ASSERT_EQ(args, processedArgs[1]); + } + +private: + PreferStylusOverTouchBlocker mBlocker; +}; + +TEST_F(PreferStylusOverTouchTest, TouchGestureIsNotBlocked) { + NotifyMotionArgs args; + + args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + assertNotBlocked(args); + + args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN); + assertNotBlocked(args); + + args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN); + assertNotBlocked(args); +} + +TEST_F(PreferStylusOverTouchTest, StylusGestureIsNotBlocked) { + NotifyMotionArgs args; + + args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, STYLUS); + assertNotBlocked(args); + + args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, STYLUS); + assertNotBlocked(args); + + args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, STYLUS); + assertNotBlocked(args); +} + +/** + * Existing touch gesture should be canceled when stylus goes down. There should be an ACTION_CANCEL + * event generated. + */ +TEST_F(PreferStylusOverTouchTest, TouchIsCanceledWhenStylusGoesDown) { + NotifyMotionArgs args; + + args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + assertNotBlocked(args); + + args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN); + assertNotBlocked(args); + + args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); + NotifyMotionArgs cancelArgs = + generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, CANCEL, {{1, 3}}, TOUCHSCREEN); + assertCanceled(args, cancelArgs); + + // Both stylus and touch events continue. Stylus should be not blocked, and touch should be + // blocked + args = generateMotionArgs(3 /*downTime*/, 4 /*eventTime*/, MOVE, {{10, 31}}, STYLUS); + assertNotBlocked(args); + + args = generateMotionArgs(0 /*downTime*/, 5 /*eventTime*/, MOVE, {{1, 4}}, TOUCHSCREEN); + assertDropped(args); +} + +/** + * New touch events should be simply blocked (dropped) when stylus is down. No CANCEL event should + * be generated. + */ +TEST_F(PreferStylusOverTouchTest, NewTouchIsBlockedWhenStylusIsDown) { + NotifyMotionArgs args; + constexpr nsecs_t stylusDownTime = 0; + constexpr nsecs_t touchDownTime = 1; + + args = generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); + assertNotBlocked(args); + + args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + assertDropped(args); + + // Stylus should continue to work + args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, MOVE, {{10, 31}}, STYLUS); + assertNotBlocked(args); + + // Touch should continue to be blocked + args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN); + assertDropped(args); + + args = generateMotionArgs(0 /*downTime*/, 5 /*eventTime*/, MOVE, {{1, 4}}, TOUCHSCREEN); + assertDropped(args); +} + +/** + * New touch events should be simply blocked (dropped) when stylus is down. No CANCEL event should + * be generated. + */ +TEST_F(PreferStylusOverTouchTest, NewTouchWorksAfterStylusIsLifted) { + NotifyMotionArgs args; + constexpr nsecs_t stylusDownTime = 0; + constexpr nsecs_t touchDownTime = 4; + + // Stylus goes down and up + args = generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); + assertNotBlocked(args); + + args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, MOVE, {{10, 31}}, STYLUS); + assertNotBlocked(args); + + args = generateMotionArgs(stylusDownTime, 3 /*eventTime*/, UP, {{10, 31}}, STYLUS); + assertNotBlocked(args); + + // New touch goes down. It should not be blocked + args = generateMotionArgs(touchDownTime, touchDownTime, DOWN, {{1, 2}}, TOUCHSCREEN); + assertNotBlocked(args); + + args = generateMotionArgs(touchDownTime, 5 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN); + assertNotBlocked(args); + + args = generateMotionArgs(touchDownTime, 6 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN); + assertNotBlocked(args); +} + +/** + * Once a touch gesture is canceled, it should continue to be canceled, even if the stylus has been + * lifted. + */ +TEST_F(PreferStylusOverTouchTest, AfterStylusIsLiftedCurrentTouchIsBlocked) { + NotifyMotionArgs args; + constexpr nsecs_t stylusDownTime = 0; + constexpr nsecs_t touchDownTime = 1; + + assertNotBlocked(generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS)); + + args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + assertDropped(args); + + // Lift the stylus + args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, UP, {{10, 30}}, STYLUS); + assertNotBlocked(args); + + // Touch should continue to be blocked + args = generateMotionArgs(touchDownTime, 3 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN); + assertDropped(args); + + args = generateMotionArgs(touchDownTime, 4 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN); + assertDropped(args); + + // New touch should go through, though. + constexpr nsecs_t newTouchDownTime = 5; + args = generateMotionArgs(newTouchDownTime, 5 /*eventTime*/, DOWN, {{10, 20}}, TOUCHSCREEN); + assertNotBlocked(args); +} + +} // namespace android -- GitLab From 542398ea57f3f55ed16c7bb49e5ec080b7d1f79c Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 28 Feb 2022 14:57:08 -0800 Subject: [PATCH 0016/1310] Avoid using raw pointers in QueuedInputListener The previous code was correct, but we can simplify it by converting to unique_ptr. This should give us a different crash signature next time, and maybe provide us with more clues about the cause. Bug: 217647732 Test: atest inputflinger_tests Change-Id: I700de2bea60a4fc1cac65e9bc6ac082d863e52b1 --- services/inputflinger/InputListener.cpp | 28 ++++++------------- services/inputflinger/include/InputListener.h | 3 +- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp index 73b63e3141..3a4b6c599f 100644 --- a/services/inputflinger/InputListener.cpp +++ b/services/inputflinger/InputListener.cpp @@ -334,60 +334,50 @@ static inline void traceEvent(const char* functionName, int32_t id) { QueuedInputListener::QueuedInputListener(InputListenerInterface& innerListener) : mInnerListener(innerListener) {} -QueuedInputListener::~QueuedInputListener() { - size_t count = mArgsQueue.size(); - for (size_t i = 0; i < count; i++) { - delete mArgsQueue[i]; - } -} - void QueuedInputListener::notifyConfigurationChanged( const NotifyConfigurationChangedArgs* args) { traceEvent(__func__, args->id); - mArgsQueue.push_back(new NotifyConfigurationChangedArgs(*args)); + mArgsQueue.emplace_back(std::make_unique(*args)); } void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) { traceEvent(__func__, args->id); - mArgsQueue.push_back(new NotifyKeyArgs(*args)); + mArgsQueue.emplace_back(std::make_unique(*args)); } void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) { traceEvent(__func__, args->id); - mArgsQueue.push_back(new NotifyMotionArgs(*args)); + mArgsQueue.emplace_back(std::make_unique(*args)); } void QueuedInputListener::notifySwitch(const NotifySwitchArgs* args) { traceEvent(__func__, args->id); - mArgsQueue.push_back(new NotifySwitchArgs(*args)); + mArgsQueue.emplace_back(std::make_unique(*args)); } void QueuedInputListener::notifySensor(const NotifySensorArgs* args) { traceEvent(__func__, args->id); - mArgsQueue.push_back(new NotifySensorArgs(*args)); + mArgsQueue.emplace_back(std::make_unique(*args)); } void QueuedInputListener::notifyVibratorState(const NotifyVibratorStateArgs* args) { traceEvent(__func__, args->id); - mArgsQueue.push_back(new NotifyVibratorStateArgs(*args)); + mArgsQueue.emplace_back(std::make_unique(*args)); } void QueuedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) { traceEvent(__func__, args->id); - mArgsQueue.push_back(new NotifyDeviceResetArgs(*args)); + mArgsQueue.emplace_back(std::make_unique(*args)); } void QueuedInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) { traceEvent(__func__, args->id); - mArgsQueue.push_back(new NotifyPointerCaptureChangedArgs(*args)); + mArgsQueue.emplace_back(std::make_unique(*args)); } void QueuedInputListener::flush() { - size_t count = mArgsQueue.size(); - for (size_t i = 0; i < count; i++) { - NotifyArgs* args = mArgsQueue[i]; + for (const std::unique_ptr& args : mArgsQueue) { args->notify(mInnerListener); - delete args; } mArgsQueue.clear(); } diff --git a/services/inputflinger/include/InputListener.h b/services/inputflinger/include/InputListener.h index dff58949a7..d9822ce156 100644 --- a/services/inputflinger/include/InputListener.h +++ b/services/inputflinger/include/InputListener.h @@ -274,7 +274,6 @@ class QueuedInputListener : public InputListenerInterface { public: explicit QueuedInputListener(InputListenerInterface& innerListener); - virtual ~QueuedInputListener(); virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override; virtual void notifyKey(const NotifyKeyArgs* args) override; @@ -289,7 +288,7 @@ public: private: InputListenerInterface& mInnerListener; - std::vector mArgsQueue; + std::vector> mArgsQueue; }; } // namespace android -- GitLab From cffc379acea4329ae9763e2606ce2fdc5cc6a274 Mon Sep 17 00:00:00 2001 From: Florian Mayer Date: Tue, 1 Mar 2022 23:50:09 +0000 Subject: [PATCH 0017/1310] Fix comment. As far as I can tell, there is no HWC2::Device, and HWC2 is constructed (and passed these members) from HWComposer. Change-Id: Icc33fbcc60e9885fd1061ec762d209a8c3f4c365 --- services/surfaceflinger/DisplayHardware/HWC2.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h index 334d6ecbff..892d8dce03 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.h +++ b/services/surfaceflinger/DisplayHardware/HWC2.h @@ -87,7 +87,7 @@ public: virtual hal::HWDisplayId getId() const = 0; virtual bool isConnected() const = 0; - virtual void setConnected(bool connected) = 0; // For use by Device only + virtual void setConnected(bool connected) = 0; // For use by HWComposer only virtual bool hasCapability( aidl::android::hardware::graphics::composer3::DisplayCapability) const = 0; virtual bool isVsyncPeriodSwitchSupported() const = 0; @@ -246,7 +246,7 @@ public: // Other Display methods hal::HWDisplayId getId() const override { return mId; } bool isConnected() const override { return mIsConnected; } - void setConnected(bool connected) override; // For use by Device only + void setConnected(bool connected) override; bool hasCapability(aidl::android::hardware::graphics::composer3::DisplayCapability) const override EXCLUDES(mDisplayCapabilitiesMutex); bool isVsyncPeriodSwitchSupported() const override; @@ -262,7 +262,7 @@ private: // Member variables - // These are references to data owned by HWC2::Device, which will outlive + // These are references to data owned by HWComposer, which will outlive // this HWC2::Display, so these references are guaranteed to be valid for // the lifetime of this object. android::Hwc2::Composer& mComposer; @@ -380,7 +380,7 @@ public: hal::Error setBlockingRegion(const android::Region& region) override; private: - // These are references to data owned by HWC2::Device, which will outlive + // These are references to data owned by HWComposer, which will outlive // this HWC2::Layer, so these references are guaranteed to be valid for // the lifetime of this object. android::Hwc2::Composer& mComposer; -- GitLab From a56286688aa6b9d0953d6918179a2b7d084829e9 Mon Sep 17 00:00:00 2001 From: Greg Kaiser Date: Mon, 28 Feb 2022 07:18:04 -0800 Subject: [PATCH 0018/1310] Small performance improvement We directly initialize our smart pointer in the initialization list, instead of having it default constructed and then assigned in the constructor body. Test: TreeHugger Bug: 211037638 Change-Id: Ie72830aa8d9a4e5d2ed3a4beea54c24e463236e5 --- services/surfaceflinger/SurfaceFlinger.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 3589e528c3..047a71a935 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1438,7 +1438,7 @@ private: class SurfaceComposerAIDL : public gui::BnSurfaceComposer { public: - SurfaceComposerAIDL(sp sf) { mFlinger = sf; } + SurfaceComposerAIDL(sp sf) : mFlinger(std::move(sf)) {} binder::Status createDisplay(const std::string& displayName, bool secure, sp* outDisplay) override; -- GitLab From f2f827c5dcb38e8788a9b8fb76f615d8a462178a Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 3 Mar 2022 11:33:19 -0800 Subject: [PATCH 0019/1310] Make InputDispatcher logs available dynamically This will allow us to turn on these logs without having to rebuild and make it easier to debug. Bug: 211915757 Test: adb shell setprop log.tag.InputDispatcherFocus DEBUG Test: check that the logs get enabled Change-Id: Ie1b1445e52b71c373933d085367d74bf568b5462 --- .../dispatcher/InputDispatcher.cpp | 82 ++++++++++++++----- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 99d23af71f..503ef13c3c 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -66,35 +66,77 @@ namespace android::inputdispatcher { namespace { -// Log detailed debug messages about each inbound event notification to the dispatcher. -constexpr bool DEBUG_INBOUND_EVENT_DETAILS = false; +/** + * Log detailed debug messages about each inbound event notification to the dispatcher. + * Enable this via "adb shell setprop log.tag.InputDispatcherInboundEvent DEBUG" (requires restart) + */ +const bool DEBUG_INBOUND_EVENT_DETAILS = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundEvent", ANDROID_LOG_INFO); -// Log detailed debug messages about each outbound event processed by the dispatcher. -constexpr bool DEBUG_OUTBOUND_EVENT_DETAILS = false; +/** + * Log detailed debug messages about each outbound event processed by the dispatcher. + * Enable this via "adb shell setprop log.tag.InputDispatcherOutboundEvent DEBUG" (requires restart) + */ +const bool DEBUG_OUTBOUND_EVENT_DETAILS = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "OutboundEvent", ANDROID_LOG_INFO); -// Log debug messages about the dispatch cycle. -constexpr bool DEBUG_DISPATCH_CYCLE = false; +/** + * Log debug messages about the dispatch cycle. + * Enable this via "adb shell setprop log.tag.InputDispatcherDispatchCycle DEBUG" (requires restart) + */ +const bool DEBUG_DISPATCH_CYCLE = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "DispatchCycle", ANDROID_LOG_INFO); -// Log debug messages about channel creation -constexpr bool DEBUG_CHANNEL_CREATION = false; +/** + * Log debug messages about channel creation + * Enable this via "adb shell setprop log.tag.InputDispatcherChannelCreation DEBUG" (requires + * restart) + */ +const bool DEBUG_CHANNEL_CREATION = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "ChannelCreation", ANDROID_LOG_INFO); -// Log debug messages about input event injection. -constexpr bool DEBUG_INJECTION = false; +/** + * Log debug messages about input event injection. + * Enable this via "adb shell setprop log.tag.InputDispatcherInjection DEBUG" (requires restart) + */ +const bool DEBUG_INJECTION = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Injection", ANDROID_LOG_INFO); -// Log debug messages about input focus tracking. -constexpr bool DEBUG_FOCUS = false; +/** + * Log debug messages about input focus tracking. + * Enable this via "adb shell setprop log.tag.InputDispatcherFocus DEBUG" (requires restart) + */ +const bool DEBUG_FOCUS = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Focus", ANDROID_LOG_INFO); -// Log debug messages about touch mode event -constexpr bool DEBUG_TOUCH_MODE = false; +/** + * Log debug messages about touch mode event + * Enable this via "adb shell setprop log.tag.InputDispatcherTouchMode DEBUG" (requires restart) + */ +const bool DEBUG_TOUCH_MODE = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "TouchMode", ANDROID_LOG_INFO); -// Log debug messages about touch occlusion -constexpr bool DEBUG_TOUCH_OCCLUSION = true; +/** + * Log debug messages about touch occlusion + * Enable this via "adb shell setprop log.tag.InputDispatcherTouchOcclusion DEBUG" (requires + * restart) + */ +const bool DEBUG_TOUCH_OCCLUSION = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "TouchOcclusion", ANDROID_LOG_INFO); -// Log debug messages about the app switch latency optimization. -constexpr bool DEBUG_APP_SWITCH = false; +/** + * Log debug messages about the app switch latency optimization. + * Enable this via "adb shell setprop log.tag.InputDispatcherAppSwitch DEBUG" (requires restart) + */ +const bool DEBUG_APP_SWITCH = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "AppSwitch", ANDROID_LOG_INFO); -// Log debug messages about hover events. -constexpr bool DEBUG_HOVER = false; +/** + * Log debug messages about hover events. + * Enable this via "adb shell setprop log.tag.InputDispatcherHover DEBUG" (requires restart) + */ +const bool DEBUG_HOVER = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Hover", ANDROID_LOG_INFO); // Temporarily releases a held mutex for the lifetime of the instance. // Named to match std::scoped_lock -- GitLab From 59b8118f74adc02c882d24254eabef6f488a378a Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 2 Mar 2022 15:26:40 -0800 Subject: [PATCH 0020/1310] Add tests for some split touch cases Test the situations where the first touch is either going into an empty area, or onto a non-touchable window. Bug: 214087836 Test: atest inputflinger_tests Change-Id: Ic734c0508af0d004c499a45067e006e6741e8457 --- .../tests/InputDispatcher_test.cpp | 170 ++++++++++-------- 1 file changed, 95 insertions(+), 75 deletions(-) diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 3fb406c92c..61e004b643 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -53,6 +53,11 @@ static const int32_t DEVICE_ID = 1; static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; static constexpr int32_t SECOND_DISPLAY_ID = 1; +static constexpr int32_t POINTER_1_DOWN = + AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); +static constexpr int32_t POINTER_1_UP = + AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + // An arbitrary injector pid / uid pair that has permission to inject events. static const int32_t INJECTOR_PID = 999; static const int32_t INJECTOR_UID = 1001; @@ -580,11 +585,10 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { // Rejects pointer down with invalid index. event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC, - AMOTION_EVENT_ACTION_POINTER_DOWN | - (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - 0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0, - AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, - identityTransform, ARBITRARY_TIME, ARBITRARY_TIME, + POINTER_1_DOWN, 0, 0, edgeFlags, metaState, 0, classification, + identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME, + ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, @@ -605,11 +609,10 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { // Rejects pointer up with invalid index. event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC, - AMOTION_EVENT_ACTION_POINTER_UP | - (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - 0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0, - AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, - identityTransform, ARBITRARY_TIME, ARBITRARY_TIME, + POINTER_1_UP, 0, 0, edgeFlags, metaState, 0, classification, identityTransform, + 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME, + ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, @@ -1498,6 +1501,10 @@ static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source, int32 return args; } +static NotifyMotionArgs generateTouchArgs(int32_t action, const std::vector& points) { + return generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, points); +} + static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source, int32_t displayId) { return generateMotionArgs(action, source, displayId, {PointF{100, 200}}); } @@ -1733,9 +1740,7 @@ TEST_F(InputDispatcherTest, WallpaperWindow_ReceivesMultiTouch) { // Second finger down on the top window const MotionEvent secondFingerDownEvent = - MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN | - (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - AINPUT_SOURCE_TOUCHSCREEN) + MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) .x(100) @@ -1798,9 +1803,7 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { // Second finger down on the right window const MotionEvent secondFingerDownEvent = - MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN | - (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - AINPUT_SOURCE_TOUCHSCREEN) + MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) .x(100) @@ -1848,6 +1851,61 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { wallpaperWindow->assertNoEvents(); } +/** + * On the display, have a single window, and also an area where there's no window. + * First pointer touches the "no window" area of the screen. Second pointer touches the window. + * Make sure that the window receives the second pointer, and first pointer is simply ignored. + */ +TEST_F(InputDispatcherTest, SplitWorksWhenEmptyAreaIsTouched) { + std::shared_ptr application = std::make_shared(); + sp window = + new FakeWindowHandle(application, mDispatcher, "Window", DISPLAY_ID); + + mDispatcher->setInputWindows({{DISPLAY_ID, {window}}}); + NotifyMotionArgs args; + + // Touch down on the empty space + mDispatcher->notifyMotion(&(args = generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{-1, -1}}))); + + mDispatcher->waitForIdle(); + window->assertNoEvents(); + + // Now touch down on the window with another pointer + mDispatcher->notifyMotion(&(args = generateTouchArgs(POINTER_1_DOWN, {{-1, -1}, {10, 10}}))); + mDispatcher->waitForIdle(); + window->consumeMotionDown(); +} + +/** + * Same test as above, but instead of touching the empty space, the first touch goes to + * non-touchable window. + */ +TEST_F(InputDispatcherTest, SplitWorksWhenNonTouchableWindowIsTouched) { + std::shared_ptr application = std::make_shared(); + sp window1 = + new FakeWindowHandle(application, mDispatcher, "Window1", DISPLAY_ID); + window1->setTouchableRegion(Region{{0, 0, 100, 100}}); + window1->setTouchable(false); + sp window2 = + new FakeWindowHandle(application, mDispatcher, "Window2", DISPLAY_ID); + window2->setTouchableRegion(Region{{100, 0, 200, 100}}); + + mDispatcher->setInputWindows({{DISPLAY_ID, {window1, window2}}}); + + NotifyMotionArgs args; + // Touch down on the non-touchable window + mDispatcher->notifyMotion(&(args = generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}}))); + + mDispatcher->waitForIdle(); + window1->assertNoEvents(); + window2->assertNoEvents(); + + // Now touch down on the window with another pointer + mDispatcher->notifyMotion(&(args = generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}}))); + mDispatcher->waitForIdle(); + window2->consumeMotionDown(); +} + TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { std::shared_ptr application = std::make_shared(); sp windowLeft = @@ -2318,9 +2376,7 @@ TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) { // Send pointer down to the first window NotifyMotionArgs pointerDownMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN | - (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint}); mDispatcher->notifyMotion(&pointerDownMotionArgs); // Only the first window should get the pointer down event @@ -2338,9 +2394,7 @@ TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) { // Send pointer up to the second window NotifyMotionArgs pointerUpMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP | - (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint}); mDispatcher->notifyMotion(&pointerUpMotionArgs); // The first window gets nothing and the second gets pointer up @@ -2400,9 +2454,7 @@ TEST_F(InputDispatcherTest, TransferTouchFocus_TwoPointersSplitTouch) { // Send down to the second window NotifyMotionArgs secondDownMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN | - (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {pointInFirst, pointInSecond}); mDispatcher->notifyMotion(&secondDownMotionArgs); // The first window gets a move and the second a down @@ -2417,9 +2469,7 @@ TEST_F(InputDispatcherTest, TransferTouchFocus_TwoPointersSplitTouch) { // Send pointer up to the second window NotifyMotionArgs pointerUpMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP | - (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {pointInFirst, pointInSecond}); mDispatcher->notifyMotion(&pointerUpMotionArgs); // The first window gets nothing and the second gets pointer up @@ -2468,9 +2518,7 @@ TEST_F(InputDispatcherTest, TransferTouch_TwoPointersSplitTouch) { // Send down to the second window NotifyMotionArgs secondDownMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN | - (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {pointInFirst, pointInSecond}); mDispatcher->notifyMotion(&secondDownMotionArgs); // The first window gets a move and the second a down @@ -2487,9 +2535,7 @@ TEST_F(InputDispatcherTest, TransferTouch_TwoPointersSplitTouch) { // The rest of the dispatch should proceed as normal // Send pointer up to the second window NotifyMotionArgs pointerUpMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP | - (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {pointInFirst, pointInSecond}); mDispatcher->notifyMotion(&pointerUpMotionArgs); // The first window gets MOVE and the second gets pointer up @@ -2706,9 +2752,7 @@ TEST_F(InputDispatcherTest, PointerCancel_SendCancelWhenSplitTouch) { // Send down to the second window NotifyMotionArgs secondDownMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN | - (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {pointInFirst, pointInSecond}); mDispatcher->notifyMotion(&secondDownMotionArgs); // The first window gets a move and the second a down @@ -2717,9 +2761,7 @@ TEST_F(InputDispatcherTest, PointerCancel_SendCancelWhenSplitTouch) { // Send pointer cancel to the second window NotifyMotionArgs pointerUpMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP | - (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {pointInFirst, pointInSecond}); pointerUpMotionArgs.flags |= AMOTION_EVENT_FLAG_CANCELED; mDispatcher->notifyMotion(&pointerUpMotionArgs); @@ -4175,22 +4217,18 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchDifferentTransform touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints); // Touch Window 2 - int32_t actionPointerDown = - AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); touchedPoints.push_back(PointF{150, 150}); expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1])); - touchAndAssertPositions(actionPointerDown, touchedPoints, expectedPoints); + touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints); // Release Window 2 - int32_t actionPointerUp = - AMOTION_EVENT_ACTION_POINTER_UP + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); - touchAndAssertPositions(actionPointerUp, touchedPoints, expectedPoints); + touchAndAssertPositions(POINTER_1_UP, touchedPoints, expectedPoints); expectedPoints.pop_back(); // Update the transform so rotation is set for Window 2 mWindow2->setWindowTransform(0, -1, 1, 0); expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1])); - touchAndAssertPositions(actionPointerDown, touchedPoints, expectedPoints); + touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints); } TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchMoveDifferentTransform) { @@ -4202,12 +4240,10 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchMoveDifferentTrans touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints); // Touch Window 2 - int32_t actionPointerDown = - AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); touchedPoints.push_back(PointF{150, 150}); expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1])); - touchAndAssertPositions(actionPointerDown, touchedPoints, expectedPoints); + touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints); // Move both windows touchedPoints = {{20, 20}, {175, 175}}; @@ -4217,15 +4253,13 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchMoveDifferentTrans touchAndAssertPositions(AMOTION_EVENT_ACTION_MOVE, touchedPoints, expectedPoints); // Release Window 2 - int32_t actionPointerUp = - AMOTION_EVENT_ACTION_POINTER_UP + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); - touchAndAssertPositions(actionPointerUp, touchedPoints, expectedPoints); + touchAndAssertPositions(POINTER_1_UP, touchedPoints, expectedPoints); expectedPoints.pop_back(); // Touch Window 2 mWindow2->setWindowTransform(0, -1, 1, 0); expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1])); - touchAndAssertPositions(actionPointerDown, touchedPoints, expectedPoints); + touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints); // Move both windows touchedPoints = {{20, 20}, {175, 175}}; @@ -4244,12 +4278,10 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleWindowsFirstTouchWithSc touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints); // Touch Window 2 - int32_t actionPointerDown = - AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); touchedPoints.push_back(PointF{150, 150}); expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1])); - touchAndAssertPositions(actionPointerDown, touchedPoints, expectedPoints); + touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints); // Move both windows touchedPoints = {{20, 20}, {175, 175}}; @@ -5012,12 +5044,8 @@ TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) { ADISPLAY_ID_DEFAULT, 0 /*flags*/); // Touch Window 2 - int32_t actionPointerDown = - AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); - - motionArgs = - generateMotionArgs(actionPointerDown, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {FOCUSED_WINDOW_LOCATION, UNFOCUSED_WINDOW_LOCATION}); + motionArgs = generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {FOCUSED_WINDOW_LOCATION, UNFOCUSED_WINDOW_LOCATION}); mDispatcher->notifyMotion(&motionArgs); const std::chrono::duration timeout = @@ -6543,9 +6571,7 @@ TEST_F(InputDispatcherSpyWindowTest, ContinuesToReceiveGestureAfterPilfer) { // Second finger down on the window and spy, but the window should not receive the pointer down. const MotionEvent secondFingerDownEvent = - MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN | - (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - AINPUT_SOURCE_TOUCHSCREEN) + MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) @@ -6603,9 +6629,7 @@ TEST_F(InputDispatcherSpyWindowTest, ReceivesMultiplePointers) { spy->consumeMotionDown(); const MotionEvent secondFingerDownEvent = - MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN | - (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - AINPUT_SOURCE_TOUCHSCREEN) + MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) .pointer( @@ -6638,9 +6662,7 @@ TEST_F(InputDispatcherSpyWindowTest, ReceivesSecondPointerAsDown) { spyRight->assertNoEvents(); const MotionEvent secondFingerDownEvent = - MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN | - (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - AINPUT_SOURCE_TOUCHSCREEN) + MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) .pointer( @@ -6679,9 +6701,7 @@ TEST_F(InputDispatcherSpyWindowTest, SplitIfNoForegroundWindowTouched) { // Second finger down on window, the window should receive touch down. const MotionEvent secondFingerDownEvent = - MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN | - (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - AINPUT_SOURCE_TOUCHSCREEN) + MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) -- GitLab From 8e0ef682d36f4902482aa0c2797f33a50fce0926 Mon Sep 17 00:00:00 2001 From: Kevin Lubick Date: Fri, 4 Mar 2022 10:34:35 -0500 Subject: [PATCH 0021/1310] Fix includes of SkStream and SkData Change-Id: I93e587a39be7e0c88862d0b2d7b0f6df8a02d29b --- libs/renderengine/skia/SkiaGLRenderEngine.h | 2 ++ libs/renderengine/skia/debug/SkiaCapture.cpp | 3 +++ libs/renderengine/skia/debug/SkiaCapture.h | 4 +++- libs/renderengine/skia/filters/BlurFilter.cpp | 1 - libs/renderengine/skia/filters/GaussianBlurFilter.cpp | 1 - libs/renderengine/skia/filters/KawaseBlurFilter.cpp | 1 - 6 files changed, 8 insertions(+), 4 deletions(-) diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h index a650313648..c891a708d9 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.h +++ b/libs/renderengine/skia/SkiaGLRenderEngine.h @@ -41,6 +41,8 @@ #include "filters/LinearEffect.h" #include "filters/StretchShaderFactory.h" +class SkData; + namespace android { namespace renderengine { namespace skia { diff --git a/libs/renderengine/skia/debug/SkiaCapture.cpp b/libs/renderengine/skia/debug/SkiaCapture.cpp index 856fff4b01..b21b01cc1b 100644 --- a/libs/renderengine/skia/debug/SkiaCapture.cpp +++ b/libs/renderengine/skia/debug/SkiaCapture.cpp @@ -27,6 +27,9 @@ #include #include "CommonPool.h" +#include "SkCanvas.h" +#include "SkRect.h" +#include "SkTypeface.h" #include "src/utils/SkMultiPictureDocument.h" namespace android { diff --git a/libs/renderengine/skia/debug/SkiaCapture.h b/libs/renderengine/skia/debug/SkiaCapture.h index f1946290ca..d65a579916 100644 --- a/libs/renderengine/skia/debug/SkiaCapture.h +++ b/libs/renderengine/skia/debug/SkiaCapture.h @@ -19,13 +19,15 @@ #include #include #include +#include +#include #include +#include "tools/SkSharingProc.h" #include #include #include "CaptureTimer.h" -#include "tools/SkSharingProc.h" namespace android { namespace renderengine { diff --git a/libs/renderengine/skia/filters/BlurFilter.cpp b/libs/renderengine/skia/filters/BlurFilter.cpp index 63cc02b7ea..2557ac9770 100644 --- a/libs/renderengine/skia/filters/BlurFilter.cpp +++ b/libs/renderengine/skia/filters/BlurFilter.cpp @@ -17,7 +17,6 @@ #define ATRACE_TAG ATRACE_TAG_GRAPHICS #include "BlurFilter.h" #include -#include #include #include #include diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp index 55867a95cc..f3b6ab9885 100644 --- a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp +++ b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp @@ -18,7 +18,6 @@ #include "GaussianBlurFilter.h" #include -#include #include #include #include diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp index bfde06fd9a..e370c39a94 100644 --- a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp +++ b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp @@ -18,7 +18,6 @@ #include "KawaseBlurFilter.h" #include -#include #include #include #include -- GitLab From 84296558f573761ecad9c858d27917f4624e7663 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 8 Mar 2022 10:31:39 -0800 Subject: [PATCH 0022/1310] Log event information when event is dropped To help debug cases where the windows and displays are misconfigured, let's check where the touch is trying to go when the events are dropped. Bug: 220241282 Test: atest inputflinger_tests Change-Id: I0f678a91954a4ba2a5c6e9aca8d9200e0f368271 --- services/inputflinger/dispatcher/InputDispatcher.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 503ef13c3c..33ef4e45e5 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2320,8 +2320,8 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( return (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) != 0 || touchedWindow.windowHandle->getInfo()->isSpy(); })) { - ALOGI("Dropping event because there is no touched window on display %d to receive it.", - displayId); + ALOGI("Dropping event because there is no touched window on display %d to receive it: %s", + displayId, entry.getDescription().c_str()); injectionResult = InputEventInjectionResult::FAILED; goto Failed; } -- GitLab From 2756839ae9e898d31b138be0f4e7151b76288abb Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 8 Mar 2022 11:06:34 -0800 Subject: [PATCH 0023/1310] Remove PreferStylusOverTouchBlocker To prevent crash on emulator, let's disable PreferStylusOverTouchBlocker. A proper solution might require a re-work of how it operates. Test: atest inputflinger_tests Bug: 222531989 Change-Id: I5481e6baf284be8f254b6caec45be0478f1fe4a7 --- services/inputflinger/UnwantedInteractionBlocker.cpp | 10 ---------- services/inputflinger/UnwantedInteractionBlocker.h | 6 ------ .../tests/UnwantedInteractionBlocker_test.cpp | 8 ++++++++ 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp index f904bfd82e..fb3962e544 100644 --- a/services/inputflinger/UnwantedInteractionBlocker.cpp +++ b/services/inputflinger/UnwantedInteractionBlocker.cpp @@ -368,14 +368,6 @@ void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs* args) { } void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs* args) { - ftl::StaticVector processedArgs = - mPreferStylusOverTouchBlocker.processMotion(*args); - for (const NotifyMotionArgs& loopArgs : processedArgs) { - notifyMotionInner(&loopArgs); - } -} - -void UnwantedInteractionBlocker::notifyMotionInner(const NotifyMotionArgs* args) { auto it = mPalmRejectors.find(args->deviceId); const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args->source); if (!sendToPalmRejector) { @@ -449,8 +441,6 @@ void UnwantedInteractionBlocker::notifyInputDevicesChanged( void UnwantedInteractionBlocker::dump(std::string& dump) { dump += "UnwantedInteractionBlocker:\n"; - dump += " mPreferStylusOverTouchBlocker:\n"; - dump += addPrefix(mPreferStylusOverTouchBlocker.dump(), " "); dump += StringPrintf(" mEnablePalmRejection: %s\n", toString(mEnablePalmRejection)); dump += StringPrintf(" isPalmRejectionEnabled (flag value): %s\n", toString(isPalmRejectionEnabled())); diff --git a/services/inputflinger/UnwantedInteractionBlocker.h b/services/inputflinger/UnwantedInteractionBlocker.h index 9e41995ac4..14068fd878 100644 --- a/services/inputflinger/UnwantedInteractionBlocker.h +++ b/services/inputflinger/UnwantedInteractionBlocker.h @@ -23,8 +23,6 @@ #include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h" #include "ui/events/ozone/evdev/touch_filter/palm_detection_filter.h" -#include "PreferStylusOverTouchBlocker.h" - namespace android { // --- Functions for manipulation of event streams @@ -90,13 +88,9 @@ private: InputListenerInterface& mListener; const bool mEnablePalmRejection; - // When stylus is down, ignore touch - PreferStylusOverTouchBlocker mPreferStylusOverTouchBlocker; // Detect and reject unwanted palms on screen // Use a separate palm rejector for every touch device. std::map mPalmRejectors; - // TODO(b/210159205): delete this when simultaneous stylus and touch is supported - void notifyMotionInner(const NotifyMotionArgs* args); }; class SlotState { diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp index a3220cc63a..b2f8eb37f0 100644 --- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp +++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp @@ -490,6 +490,14 @@ TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenResetHappens) { &(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/, DOWN, {{7, 8, 9}}))); } +TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenStylusSourceWithFingerToolIsReceived) { + mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); + NotifyMotionArgs args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}}); + args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + args.source = AINPUT_SOURCE_STYLUS; + mBlocker->notifyMotion(&args); +} + /** * If input devices have changed, but the important device info that's used by the * UnwantedInteractionBlocker has not changed, there should not be a reset. -- GitLab From e4f0f736d4972c31481fdb01b638f3389c66a9e5 Mon Sep 17 00:00:00 2001 From: Trevor Black Date: Wed, 9 Mar 2022 22:06:55 +0000 Subject: [PATCH 0024/1310] Revert "Make BT709 support conditional on swapchain ext enable" This reverts commit ea5745afb64f0f71ccbc36d617a73d0ce0db2e9f. Reason for revert: Need to land in aosp Change-Id: Id056873193a711de140acb2f1dfd4a734ecaec4b --- vulkan/libvulkan/swapchain.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp index 0be4403731..91a12c19b0 100644 --- a/vulkan/libvulkan/swapchain.cpp +++ b/vulkan/libvulkan/swapchain.cpp @@ -719,8 +719,6 @@ VkResult GetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice pdev, bool wide_color_support = false; uint64_t consumer_usage = 0; - bool swapchain_ext = - instance_data.hook_extensions.test(ProcHook::EXT_swapchain_colorspace); if (surface_handle == VK_NULL_HANDLE) { ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query; bool surfaceless_enabled = @@ -748,7 +746,9 @@ VkResult GetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice pdev, consumer_usage = surface.consumer_usage; } - wide_color_support = wide_color_support && swapchain_ext; + wide_color_support = + wide_color_support && + instance_data.hook_extensions.test(ProcHook::EXT_swapchain_colorspace); AHardwareBuffer_Desc desc = {}; desc.width = 1; @@ -760,12 +760,8 @@ VkResult GetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice pdev, // We must support R8G8B8A8 std::vector all_formats = { {VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}, - {VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}}; - - if (swapchain_ext) { - all_formats.emplace_back(VkSurfaceFormatKHR{ - VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_BT709_LINEAR_EXT}); - } + {VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}, + {VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_BT709_LINEAR_EXT}}; if (wide_color_support) { all_formats.emplace_back(VkSurfaceFormatKHR{ -- GitLab From 192ad4a7d80f2b677ba8a071b8ccda6dd5bf5245 Mon Sep 17 00:00:00 2001 From: Trevor David Black Date: Wed, 9 Mar 2022 22:50:09 +0000 Subject: [PATCH 0025/1310] Revert "Advertise support for BT709 in the WSI" This reverts commit 737b5b495f7b65787bd676187dd9cca178386498. Reason for revert: needs to land in aosp Change-Id: I9ad64d7d8b21cd68c071dc90f7d032671096b0da --- vulkan/libvulkan/swapchain.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp index 91a12c19b0..72696adcc8 100644 --- a/vulkan/libvulkan/swapchain.cpp +++ b/vulkan/libvulkan/swapchain.cpp @@ -760,8 +760,7 @@ VkResult GetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice pdev, // We must support R8G8B8A8 std::vector all_formats = { {VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}, - {VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}, - {VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_BT709_LINEAR_EXT}}; + {VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}}; if (wide_color_support) { all_formats.emplace_back(VkSurfaceFormatKHR{ -- GitLab From b8d9c75a8c3fbc32517dd3a277bc1330612d993d Mon Sep 17 00:00:00 2001 From: Shikha Malhotra Date: Sat, 26 Feb 2022 15:39:53 +0000 Subject: [PATCH 0026/1310] Added support to calculate storage using project ids on devices whose user data is wiped and their add directories are created using project id Bug: b/215154615 Test: atest StorageHostTest Test: atest tests/installd_service_test.cpp Ignore-AOSP-First: It will be picked up in AOSP manually, needs to be in master first to resolve merge conflicts. Change-Id: Idfe262d5606a4f577587e75e4a29fdb55c021a37 --- cmds/installd/InstalldNativeService.cpp | 165 ++++++++++++------ cmds/installd/InstalldNativeService.h | 1 + .../installd/binder/android/os/IInstalld.aidl | 2 +- cmds/installd/utils.cpp | 40 +++++ cmds/installd/utils.h | 2 + 5 files changed, 160 insertions(+), 50 deletions(-) diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp index f54fd642ed..ce73b23fb3 100644 --- a/cmds/installd/InstalldNativeService.cpp +++ b/cmds/installd/InstalldNativeService.cpp @@ -55,6 +55,7 @@ #include #include #include +#include #include // TODO: Move everything to base/logging. #include #include @@ -457,15 +458,37 @@ done: free(after); return res; } +static bool internal_storage_has_project_id() { + // The following path is populated in setFirstBoot, so if this file is present + // then project ids can be used. -static int prepare_app_dir(const std::string& path, mode_t target_mode, uid_t uid, gid_t gid) { + auto using_project_ids = + StringPrintf("%smisc/installd/using_project_ids", android_data_dir.c_str()); + return access(using_project_ids.c_str(), F_OK) == 0; +} + +static int prepare_app_dir(const std::string& path, mode_t target_mode, uid_t uid, gid_t gid, + long project_id) { if (fs_prepare_dir_strict(path.c_str(), target_mode, uid, gid) != 0) { PLOG(ERROR) << "Failed to prepare " << path; return -1; } + if (internal_storage_has_project_id()) { + return set_quota_project_id(path, project_id, true); + } return 0; } +static int prepare_app_cache_dir(const std::string& parent, const char* name, mode_t target_mode, + uid_t uid, gid_t gid, long project_id) { + auto path = StringPrintf("%s/%s", parent.c_str(), name); + int ret = prepare_app_cache_dir(parent, name, target_mode, uid, gid); + if (ret == 0 && internal_storage_has_project_id()) { + return set_quota_project_id(path, project_id, true); + } + return ret; +} + static bool prepare_app_profile_dir(const std::string& packageName, int32_t appId, int32_t userId) { if (!property_get_bool("dalvik.vm.usejitprofiles", false)) { return true; @@ -625,9 +648,11 @@ static binder::Status createAppDataDirs(const std::string& path, int32_t uid, in } // Prepare only the parent app directory - if (prepare_app_dir(path, targetMode, uid, gid) || - prepare_app_cache_dir(path, "cache", 02771, uid, cacheGid) || - prepare_app_cache_dir(path, "code_cache", 02771, uid, cacheGid)) { + long project_id_app = get_project_id(uid, PROJECT_ID_APP_START); + long project_id_cache_app = get_project_id(uid, PROJECT_ID_APP_CACHE_START); + if (prepare_app_dir(path, targetMode, uid, gid, project_id_app) || + prepare_app_cache_dir(path, "cache", 02771, uid, cacheGid, project_id_cache_app) || + prepare_app_cache_dir(path, "code_cache", 02771, uid, cacheGid, project_id_cache_app)) { return error("Failed to prepare " + path); } @@ -773,7 +798,7 @@ binder::Status InstalldNativeService::createSdkSandboxDataPackageDirectory( LOG(DEBUG) << "Creating app-level sdk data directory: " << packagePath; #endif - if (prepare_app_dir(packagePath, 0751, AID_SYSTEM, AID_SYSTEM)) { + if (prepare_app_dir(packagePath, 0751, AID_SYSTEM, AID_SYSTEM, 0)) { return error("Failed to prepare " + packagePath); } @@ -2102,68 +2127,72 @@ static std::string toString(std::vector values) { } #endif +// On devices without sdcardfs, if internal and external are on +// the same volume, a uid such as u0_a123 is used for both +// internal and external storage; therefore, subtract that +// amount from internal to make sure we don't count it double. +// This needs to happen for data, cache and OBB +static void deductDoubleSpaceIfNeeded(stats* stats, int64_t doubleSpaceToBeDeleted, uid_t uid, + const std::string& uuid) { + if (!supports_sdcardfs()) { + stats->dataSize -= doubleSpaceToBeDeleted; + long obbProjectId = get_project_id(uid, PROJECT_ID_EXT_OBB_START); + int64_t appObbSize = GetOccupiedSpaceForProjectId(uuid, obbProjectId); + stats->dataSize -= appObbSize; + } +} + static void collectQuotaStats(const std::string& uuid, int32_t userId, int32_t appId, struct stats* stats, struct stats* extStats) { - int64_t space; + int64_t space, doubleSpaceToBeDeleted = 0; uid_t uid = multiuser_get_uid(userId, appId); - if (stats != nullptr) { - if ((space = GetOccupiedSpaceForUid(uuid, uid)) != -1) { - stats->dataSize += space; - } - - int cacheGid = multiuser_get_cache_gid(userId, appId); - if (cacheGid != -1) { - if ((space = GetOccupiedSpaceForGid(uuid, cacheGid)) != -1) { - stats->cacheSize += space; - } - } - - int sharedGid = multiuser_get_shared_gid(0, appId); - if (sharedGid != -1) { - if ((space = GetOccupiedSpaceForGid(uuid, sharedGid)) != -1) { - stats->codeSize += space; - } - } - } + static const bool supportsProjectId = internal_storage_has_project_id(); if (extStats != nullptr) { - static const bool supportsSdCardFs = supports_sdcardfs(); space = get_occupied_app_space_external(uuid, userId, appId); if (space != -1) { extStats->dataSize += space; - if (!supportsSdCardFs && stats != nullptr) { - // On devices without sdcardfs, if internal and external are on - // the same volume, a uid such as u0_a123 is used for - // application dirs on both internal and external storage; - // therefore, substract that amount from internal to make sure - // we don't count it double. - stats->dataSize -= space; - } + doubleSpaceToBeDeleted += space; } space = get_occupied_app_cache_space_external(uuid, userId, appId); if (space != -1) { extStats->dataSize += space; // cache counts for "data" extStats->cacheSize += space; - if (!supportsSdCardFs && stats != nullptr) { - // On devices without sdcardfs, if internal and external are on - // the same volume, a uid such as u0_a123 is used for both - // internal and external storage; therefore, substract that - // amount from internal to make sure we don't count it double. - stats->dataSize -= space; + doubleSpaceToBeDeleted += space; + } + } + + if (stats != nullptr) { + if (!supportsProjectId) { + if ((space = GetOccupiedSpaceForUid(uuid, uid)) != -1) { + stats->dataSize += space; + } + deductDoubleSpaceIfNeeded(stats, doubleSpaceToBeDeleted, uid, uuid); + int cacheGid = multiuser_get_cache_gid(userId, appId); + if (cacheGid != -1) { + if ((space = GetOccupiedSpaceForGid(uuid, cacheGid)) != -1) { + stats->cacheSize += space; + } + } + } else { + long projectId = get_project_id(uid, PROJECT_ID_APP_START); + if ((space = GetOccupiedSpaceForProjectId(uuid, projectId)) != -1) { + stats->dataSize += space; + } + projectId = get_project_id(uid, PROJECT_ID_APP_CACHE_START); + if ((space = GetOccupiedSpaceForProjectId(uuid, projectId)) != -1) { + stats->cacheSize += space; + stats->dataSize += space; } } - if (!supportsSdCardFs && stats != nullptr) { - // On devices without sdcardfs, the UID of OBBs on external storage - // matches the regular app UID (eg u0_a123); therefore, to avoid - // OBBs being include in stats->dataSize, compute the OBB size for - // this app, and substract it from the size reported on internal - // storage - long obbProjectId = uid - AID_APP_START + PROJECT_ID_EXT_OBB_START; - int64_t appObbSize = GetOccupiedSpaceForProjectId(uuid, obbProjectId); - stats->dataSize -= appObbSize; + int sharedGid = multiuser_get_shared_gid(0, appId); + if (sharedGid != -1) { + if ((space = GetOccupiedSpaceForGid(uuid, sharedGid)) != -1) { + stats->codeSize += space; + } } } } @@ -2286,6 +2315,12 @@ static void collectManualExternalStatsForUser(const std::string& path, struct st fts_close(fts); } static bool ownsExternalStorage(int32_t appId) { + // if project id calculation is supported then, there is no need to + // calculate in a different way and project_id based calculation can work + if (internal_storage_has_project_id()) { + return false; + } + // Fetch external storage owner appid and check if it is the same as the // current appId whose size is calculated struct stat s; @@ -3286,6 +3321,38 @@ binder::Status InstalldNativeService::hashSecondaryDexFile( dexPath, packageName, uid, volumeUuid, storageFlag, _aidl_return); return result ? ok() : error(); } +/** + * Returns true if ioctl feature (F2FS_IOC_FS{GET,SET}XATTR) is supported as + * these were introduced in Linux 4.14, so kernel versions before that will fail + * while setting project id attributes. Only when these features are enabled, + * storage calculation using project_id is enabled + */ +bool check_if_ioctl_feature_is_supported() { + bool result = false; + auto temp_path = StringPrintf("%smisc/installd/ioctl_check", android_data_dir.c_str()); + if (access(temp_path.c_str(), F_OK) != 0) { + int fd = open(temp_path.c_str(), O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0644); + result = set_quota_project_id(temp_path, 0, true) == 0; + close(fd); + // delete the temp file + remove(temp_path.c_str()); + } + return result; +} + +binder::Status InstalldNativeService::setFirstBoot() { + ENFORCE_UID(AID_SYSTEM); + std::lock_guard lock(mMountsLock); + std::string uuid; + if (GetOccupiedSpaceForProjectId(uuid, 0) != -1 && check_if_ioctl_feature_is_supported()) { + auto first_boot_path = + StringPrintf("%smisc/installd/using_project_ids", android_data_dir.c_str()); + if (access(first_boot_path.c_str(), F_OK) != 0) { + close(open(first_boot_path.c_str(), O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0644)); + } + } + return ok(); +} binder::Status InstalldNativeService::invalidateMounts() { ENFORCE_UID(AID_SYSTEM); diff --git a/cmds/installd/InstalldNativeService.h b/cmds/installd/InstalldNativeService.h index 55b05110d4..94621b1213 100644 --- a/cmds/installd/InstalldNativeService.h +++ b/cmds/installd/InstalldNativeService.h @@ -170,6 +170,7 @@ public: int32_t storageFlag, std::vector* _aidl_return); binder::Status invalidateMounts(); + binder::Status setFirstBoot(); binder::Status isQuotaSupported(const std::optional& volumeUuid, bool* _aidl_return); binder::Status tryMountDataMirror(const std::optional& volumeUuid); diff --git a/cmds/installd/binder/android/os/IInstalld.aidl b/cmds/installd/binder/android/os/IInstalld.aidl index e08e9b6634..949367911a 100644 --- a/cmds/installd/binder/android/os/IInstalld.aidl +++ b/cmds/installd/binder/android/os/IInstalld.aidl @@ -20,7 +20,7 @@ package android.os; interface IInstalld { void createUserData(@nullable @utf8InCpp String uuid, int userId, int userSerial, int flags); void destroyUserData(@nullable @utf8InCpp String uuid, int userId, int flags); - + void setFirstBoot(); android.os.CreateAppDataResult createAppData(in android.os.CreateAppDataArgs args); android.os.CreateAppDataResult[] createAppDataBatched(in android.os.CreateAppDataArgs[] args); diff --git a/cmds/installd/utils.cpp b/cmds/installd/utils.cpp index 8cfd12313b..916c27d1de 100644 --- a/cmds/installd/utils.cpp +++ b/cmds/installd/utils.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -435,6 +436,45 @@ std::vector get_known_users(const char* volume_uuid) { return users; } +long get_project_id(uid_t uid, long start_project_id_range) { + return uid - AID_APP_START + start_project_id_range; +} + +int set_quota_project_id(const std::string& path, long project_id, bool set_inherit) { + struct fsxattr fsx; + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC))); + if (fd == -1) { + PLOG(ERROR) << "Failed to open " << path << " to set project id."; + return -1; + } + + if (ioctl(fd, FS_IOC_FSGETXATTR, &fsx) == -1) { + PLOG(ERROR) << "Failed to get extended attributes for " << path << " to get project id."; + return -1; + } + + fsx.fsx_projid = project_id; + if (ioctl(fd, FS_IOC_FSSETXATTR, &fsx) == -1) { + PLOG(ERROR) << "Failed to set project id on " << path; + return -1; + } + if (set_inherit) { + unsigned int flags; + if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) { + PLOG(ERROR) << "Failed to get flags for " << path << " to set project id inheritance."; + return -1; + } + + flags |= FS_PROJINHERIT_FL; + + if (ioctl(fd, FS_IOC_SETFLAGS, &flags) == -1) { + PLOG(ERROR) << "Failed to set flags for " << path << " to set project id inheritance."; + return -1; + } + } + return 0; +} + int calculate_tree_size(const std::string& path, int64_t* size, int32_t include_gid, int32_t exclude_gid, bool exclude_apps) { FTS *fts; diff --git a/cmds/installd/utils.h b/cmds/installd/utils.h index 54d77f95d6..dfc0bd50d4 100644 --- a/cmds/installd/utils.h +++ b/cmds/installd/utils.h @@ -173,6 +173,8 @@ int prepare_app_cache_dir(const std::string& parent, const char* name, mode_t ta uid_t uid, gid_t gid); bool supports_sdcardfs(); +long get_project_id(uid_t uid, long start_project_id_range); +int set_quota_project_id(const std::string& path, long project_id, bool set_inherit); int64_t get_occupied_app_space_external(const std::string& uuid, int32_t userId, int32_t appId); int64_t get_occupied_app_cache_space_external(const std::string& uuid, int32_t userId, int32_t appId); -- GitLab From a06c4bc711fe975f2cdeb7b158bebb89739e5b8e Mon Sep 17 00:00:00 2001 From: Jackal Guo Date: Wed, 16 Mar 2022 16:45:24 +0800 Subject: [PATCH 0027/1310] Remove unused PackageManagerNative APIs These APIs was used by IORAP, and some of them aren't well protected. Since IORAP are removed from Android, removing these APIs to mitigate the vunlerability. Bug: 213903886 Test: build Test: manually using the PoC in the buganizer to ensure the symptom no longer exists. Change-Id: I9c72a5d2490c89f251613a6b561a83a7b3c22ee9 --- libs/binder/Android.bp | 2 -- .../content/pm/IPackageChangeObserver.aidl | 28 ---------------- .../content/pm/IPackageManagerNative.aidl | 13 -------- .../content/pm/PackageChangeEvent.aidl | 32 ------------------- 4 files changed, 75 deletions(-) delete mode 100644 libs/binder/aidl/android/content/pm/IPackageChangeObserver.aidl delete mode 100644 libs/binder/aidl/android/content/pm/PackageChangeEvent.aidl diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp index 63d87dae5a..200586a57e 100644 --- a/libs/binder/Android.bp +++ b/libs/binder/Android.bp @@ -296,9 +296,7 @@ aidl_interface { local_include_dir: "aidl", host_supported: true, srcs: [ - "aidl/android/content/pm/IPackageChangeObserver.aidl", "aidl/android/content/pm/IPackageManagerNative.aidl", - "aidl/android/content/pm/PackageChangeEvent.aidl", "aidl/android/content/pm/IStagedApexObserver.aidl", "aidl/android/content/pm/ApexStagedEvent.aidl", "aidl/android/content/pm/StagedApexInfo.aidl", diff --git a/libs/binder/aidl/android/content/pm/IPackageChangeObserver.aidl b/libs/binder/aidl/android/content/pm/IPackageChangeObserver.aidl deleted file mode 100644 index 6929a6cb49..0000000000 --- a/libs/binder/aidl/android/content/pm/IPackageChangeObserver.aidl +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2020 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.content.pm; - -import android.content.pm.PackageChangeEvent; - -/** - * This is a non-blocking notification when a package has changed. - * - * @hide - */ -oneway interface IPackageChangeObserver { - void onPackageChanged(in PackageChangeEvent event); -} diff --git a/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl b/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl index 7c99f76ec6..f8a8843309 100644 --- a/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl +++ b/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl @@ -17,7 +17,6 @@ package android.content.pm; -import android.content.pm.IPackageChangeObserver; import android.content.pm.IStagedApexObserver; import android.content.pm.StagedApexInfo; @@ -92,18 +91,6 @@ interface IPackageManagerNative { */ @utf8InCpp String getModuleMetadataPackageName(); - /* Returns the names of all packages. */ - @utf8InCpp String[] getAllPackages(); - - /** Register an extra package change observer to receive the multi-cast. */ - void registerPackageChangeObserver(in IPackageChangeObserver observer); - - /** - * Unregister an existing package change observer. - * This does nothing if this observer was not already registered. - */ - void unregisterPackageChangeObserver(in IPackageChangeObserver observer); - /** * Returns true if the package has the SHA 256 version of the signing certificate. * @see PackageManager#hasSigningCertificate(String, byte[], int), where type diff --git a/libs/binder/aidl/android/content/pm/PackageChangeEvent.aidl b/libs/binder/aidl/android/content/pm/PackageChangeEvent.aidl deleted file mode 100644 index e30e9072fc..0000000000 --- a/libs/binder/aidl/android/content/pm/PackageChangeEvent.aidl +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2020 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.content.pm; - -/** - * This event is designed for notification to native code listener about - * any changes on a package including update, deletion and etc. - * - * @hide - */ -parcelable PackageChangeEvent { - @utf8InCpp String packageName; - long version; - long lastUpdateTimeMillis; - boolean newInstalled; - boolean dataRemoved; - boolean isDeleted; -} -- GitLab From 577b3c4e786c8a18e8e50b50925cf6c7ff7215d9 Mon Sep 17 00:00:00 2001 From: Sanjana Sunil Date: Tue, 8 Mar 2022 16:16:18 +0000 Subject: [PATCH 0028/1310] Include SDK sandbox while calculating app and user size SDK sandbox storage has been separated and need to be specially included while getting app size and user size. While calculating app size manually, each of the subdirectories in the sdk sandbox storage (shared, directories for each sdk etc.) are added up separately to the total. This is because each of these subdirectories would contain a cache storage that needs to be added to the cache calculation. Quota calculation for sandbox storage is already present. Bug: 215506889 Test: atest SdkSandboxStorageHostTest#testSdkDataIsAttributedToApp Ignore-AOSP-First: Will cherry pick to AOSP once dependent CLs are also cherry-picked. Change-Id: I94bc30f7e47e06b3b60d28f16a647a2a0daaae93 (cherry picked from commit 08c0ff36367750ae756abe50fefdd56ce45ad1f9) --- cmds/installd/InstalldNativeService.cpp | 36 ++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp index 45c5013d44..bbc5c6f0a5 100644 --- a/cmds/installd/InstalldNativeService.cpp +++ b/cmds/installd/InstalldNativeService.cpp @@ -2239,8 +2239,17 @@ static void collectManualStats(const std::string& path, struct stats* stats) { closedir(d); } +void collectManualStatsForSubDirectories(const std::string& path, struct stats* stats) { + const auto subDirHandler = [&path, &stats](const std::string& subDir) { + auto fullpath = path + "/" + subDir; + collectManualStats(fullpath, stats); + }; + foreach_subdir(path, subDirHandler); +} + static void collectManualStatsForUser(const std::string& path, struct stats* stats, - bool exclude_apps = false) { + bool exclude_apps = false, + bool is_sdk_sandbox_storage = false) { DIR *d; int dfd; struct dirent *de; @@ -2265,6 +2274,11 @@ static void collectManualStatsForUser(const std::string& path, struct stats* sta continue; } else if (exclude_apps && (user_uid >= AID_APP_START && user_uid <= AID_APP_END)) { continue; + } else if (is_sdk_sandbox_storage) { + // In case of sdk sandbox storage (e.g. /data/misc_ce/0/sdksandbox/), + // collect individual stats of each subdirectory (shared, storage of each sdk etc.) + collectManualStatsForSubDirectories(StringPrintf("%s/%s", path.c_str(), name), + stats); } else { collectManualStats(StringPrintf("%s/%s", path.c_str(), name), stats); } @@ -2413,6 +2427,19 @@ binder::Status InstalldNativeService::getAppSize(const std::optional), + // collect individual stats of each subdirectory (shared, storage of each sdk etc.) + if (appId >= AID_APP_START && appId <= AID_APP_END) { + ATRACE_BEGIN("sdksandbox"); + auto sdkSandboxCePath = + create_data_misc_sdk_sandbox_package_path(uuid_, true, userId, pkgname); + collectManualStatsForSubDirectories(sdkSandboxCePath, &stats); + auto sdkSandboxDePath = + create_data_misc_sdk_sandbox_package_path(uuid_, false, userId, pkgname); + collectManualStatsForSubDirectories(sdkSandboxDePath, &stats); + ATRACE_END(); + } + if (!uuid) { ATRACE_BEGIN("profiles"); calculate_tree_size( @@ -2649,6 +2676,13 @@ binder::Status InstalldNativeService::getUserSize(const std::optional Date: Thu, 17 Mar 2022 09:43:28 -0700 Subject: [PATCH 0029/1310] Add dynamic debug logs to VelocityTracker This will allow us to debug any issues related to the switch to the 'impulse' velocitytracker algorithm. In addition, print the lsq2 velocity values for the case when 'impulse' is selected, and when debugging is enabled. This will make it simpler for us to follow up on dogfooder's feedback. Bug: 134179997 Test: atest libinput_tests:VelocityTrackerTest#ThreePointsLinearVelocityTest Test: (enable debug logs and make sure the impulse and ... Test: lsq2 velocities match for the test case above) Change-Id: I3c523b67a0cebe8adaddba8f86d335a2f8f4d8f8 --- libs/input/VelocityTracker.cpp | 50 ++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp index a6465eec24..7f427f2364 100644 --- a/libs/input/VelocityTracker.cpp +++ b/libs/input/VelocityTracker.cpp @@ -15,13 +15,6 @@ */ #define LOG_TAG "VelocityTracker" -//#define LOG_NDEBUG 0 - -// Log debug messages about velocity tracking. -static constexpr bool DEBUG_VELOCITY = false; - -// Log debug messages about the progress of the algorithm itself. -static constexpr bool DEBUG_STRATEGY = false; #include #include @@ -36,6 +29,27 @@ static constexpr bool DEBUG_STRATEGY = false; namespace android { +/** + * Log debug messages about velocity tracking. + * Enable this via "adb shell setprop log.tag.VelocityTrackerVelocity DEBUG" (requires restart) + */ +const bool DEBUG_VELOCITY = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Velocity", ANDROID_LOG_INFO); + +/** + * Log debug messages about the progress of the algorithm itself. + * Enable this via "adb shell setprop log.tag.VelocityTrackerStrategy DEBUG" (requires restart) + */ +const bool DEBUG_STRATEGY = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Strategy", ANDROID_LOG_INFO); + +/** + * Log debug messages about the 'impulse' strategy. + * Enable this via "adb shell setprop log.tag.VelocityTrackerImpulse DEBUG" (requires restart) + */ +const bool DEBUG_IMPULSE = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Impulse", ANDROID_LOG_INFO); + // Nanoseconds per milliseconds. static const nsecs_t NANOS_PER_MS = 1000000; @@ -141,7 +155,7 @@ std::unique_ptr VelocityTracker::createStrategy( return std::make_unique(1); case VelocityTracker::Strategy::LSQ2: - if (DEBUG_STRATEGY) { + if (DEBUG_STRATEGY && !DEBUG_IMPULSE) { ALOGI("Initializing lsq2 strategy"); } return std::make_unique(2); @@ -1172,7 +1186,25 @@ bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, outEstimator->degree = 2; // similar results to 2nd degree fit outEstimator->confidence = 1; if (DEBUG_STRATEGY) { - ALOGD("velocity: (%f, %f)", outEstimator->xCoeff[1], outEstimator->yCoeff[1]); + ALOGD("velocity: (%.1f, %.1f)", outEstimator->xCoeff[1], outEstimator->yCoeff[1]); + } + if (DEBUG_IMPULSE) { + // TODO(b/134179997): delete this block once the switch to 'impulse' is complete. + // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons + VelocityTracker lsq2(VelocityTracker::Strategy::LSQ2); + BitSet32 idBits; + const uint32_t pointerId = 0; + idBits.markBit(pointerId); + for (ssize_t i = m - 1; i >= 0; i--) { + lsq2.addMovement(time[i], idBits, {{x[i], y[i]}}); + } + float outVx = 0, outVy = 0; + const bool computed = lsq2.getVelocity(pointerId, &outVx, &outVy); + if (computed) { + ALOGD("lsq2 velocity: (%.1f, %.1f)", outVx, outVy); + } else { + ALOGD("lsq2 velocity: could not compute velocity"); + } } return true; } -- GitLab From 40de6badf23efd979ad875482031de393bf94274 Mon Sep 17 00:00:00 2001 From: Brian Salomon Date: Tue, 22 Mar 2022 19:38:53 +0000 Subject: [PATCH 0030/1310] Update RenderEngine to use GrGLMakeNativeInterface instead of deprecated GrGLCreateNativeInterface. The only difference is the former returns a sk_sp instead of a bare pointer. Bug: skia:13087 Change-Id: Ia4520dc386cb0584983f42869ce7ab2ba226dc97 --- libs/renderengine/skia/SkiaGLRenderEngine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index 671edaef77..20c4c5bb35 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -330,7 +330,7 @@ SkiaGLRenderEngine::SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGL mProtectedPlaceholderSurface(protectedPlaceholder), mDefaultPixelFormat(static_cast(args.pixelFormat)), mUseColorManagement(args.useColorManagement) { - sk_sp glInterface(GrGLCreateNativeInterface()); + sk_sp glInterface = GrGLMakeNativeInterface(); LOG_ALWAYS_FATAL_IF(!glInterface.get()); GrContextOptions options; -- GitLab From a79ddf4b1532435b07a8f9ff21fb18dec723a230 Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Thu, 17 Feb 2022 00:01:38 -0800 Subject: [PATCH 0031/1310] Convert StaticDisplayInfo to AIDL parcelable And migrate related ISurfaceComposer::getStaticDisplayInfo() method to AIDL. (1) add android::gui::StaticDisplayInfo etc. for serialization (2) remove serialization code from the orignal StaticDisplayInfo and DeviceProductInfo classes (3) convert between ui::StaticDisplayInfo and gui::StaticDisplayInfo Bug: 220073844 Test: atest libgui_test Change-Id: I462e5d4d76f768bc17ea5ca3dd54249b3ee489d9 --- libs/gui/ISurfaceComposer.cpp | 22 ------- libs/gui/SurfaceComposerClient.cpp | 44 +++++++++++++- .../aidl/android/gui/DeviceProductInfo.aidl | 58 +++++++++++++++++++ .../android/gui/DisplayConnectionType.aidl | 24 ++++++++ libs/gui/aidl/android/gui/DisplayModelId.aidl | 26 +++++++++ .../aidl/android/gui/ISurfaceComposer.aidl | 7 ++- .../aidl/android/gui/StaticDisplayInfo.aidl | 30 ++++++++++ libs/gui/include/gui/ISurfaceComposer.h | 8 +-- libs/gui/include/gui/SurfaceComposerClient.h | 1 + libs/gui/tests/Surface_test.cpp | 8 ++- libs/ui/Android.bp | 1 - libs/ui/DeviceProductInfo.cpp | 30 ---------- libs/ui/StaticDisplayInfo.cpp | 57 ------------------ libs/ui/include/ui/DeviceProductInfo.h | 9 +-- libs/ui/include/ui/StaticDisplayInfo.h | 8 +-- services/surfaceflinger/SurfaceFlinger.cpp | 44 +++++++++++++- services/surfaceflinger/SurfaceFlinger.h | 4 +- 17 files changed, 241 insertions(+), 140 deletions(-) create mode 100644 libs/gui/aidl/android/gui/DeviceProductInfo.aidl create mode 100644 libs/gui/aidl/android/gui/DisplayConnectionType.aidl create mode 100644 libs/gui/aidl/android/gui/DisplayModelId.aidl create mode 100644 libs/gui/aidl/android/gui/StaticDisplayInfo.aidl delete mode 100644 libs/ui/StaticDisplayInfo.cpp diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index 24d39fe86a..c5de09a4bf 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -37,7 +37,6 @@ #include #include #include -#include #include // --------------------------------------------------------------------------- @@ -226,17 +225,6 @@ public: return result; } - status_t getStaticDisplayInfo(const sp& display, - ui::StaticDisplayInfo* info) override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - data.writeStrongBinder(display); - remote()->transact(BnSurfaceComposer::GET_STATIC_DISPLAY_INFO, data, &reply); - const status_t result = reply.readInt32(); - if (result != NO_ERROR) return result; - return reply.read(*info); - } - status_t getDynamicDisplayInfo(const sp& display, ui::DynamicDisplayInfo* info) override { Parcel data, reply; @@ -1145,16 +1133,6 @@ status_t BnSurfaceComposer::onTransact( reply->writeStrongBinder(IInterface::asBinder(connection)); return NO_ERROR; } - case GET_STATIC_DISPLAY_INFO: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - ui::StaticDisplayInfo info; - const sp display = data.readStrongBinder(); - const status_t result = getStaticDisplayInfo(display, &info); - SAFE_PARCEL(reply->writeInt32, result); - if (result != NO_ERROR) return result; - SAFE_PARCEL(reply->write, info); - return NO_ERROR; - } case GET_DYNAMIC_DISPLAY_INFO: { CHECK_INTERFACE(ISurfaceComposer, data, reply); ui::DynamicDisplayInfo info; diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index c916abee33..23a94fc21b 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2128,8 +2128,48 @@ status_t SurfaceComposerClient::getDisplayState(const sp& display, } status_t SurfaceComposerClient::getStaticDisplayInfo(const sp& display, - ui::StaticDisplayInfo* info) { - return ComposerService::getComposerService()->getStaticDisplayInfo(display, info); + ui::StaticDisplayInfo* outInfo) { + using Tag = android::gui::DeviceProductInfo::ManufactureOrModelDate::Tag; + gui::StaticDisplayInfo ginfo; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getStaticDisplayInfo(display, &ginfo); + if (status.isOk()) { + // convert gui::StaticDisplayInfo to ui::StaticDisplayInfo + outInfo->connectionType = static_cast(ginfo.connectionType); + outInfo->density = ginfo.density; + outInfo->secure = ginfo.secure; + outInfo->installOrientation = static_cast(ginfo.installOrientation); + + DeviceProductInfo info; + std::optional dpi = ginfo.deviceProductInfo; + gui::DeviceProductInfo::ManufactureOrModelDate& date = dpi->manufactureOrModelDate; + info.name = dpi->name; + if (dpi->manufacturerPnpId.size() > 0) { + // copid from PnpId = std::array in ui/DeviceProductInfo.h + constexpr int kMaxPnpIdSize = 4; + size_t count = std::max(kMaxPnpIdSize, dpi->manufacturerPnpId.size()); + std::copy_n(dpi->manufacturerPnpId.begin(), count, info.manufacturerPnpId.begin()); + } + info.productId = dpi->productId; + if (date.getTag() == Tag::modelYear) { + DeviceProductInfo::ModelYear modelYear; + modelYear.year = static_cast(date.get().year); + info.manufactureOrModelDate = modelYear; + } else if (date.getTag() == Tag::manufactureYear) { + DeviceProductInfo::ManufactureYear manufactureYear; + manufactureYear.year = date.get().modelYear.year; + info.manufactureOrModelDate = manufactureYear; + } else if (date.getTag() == Tag::manufactureWeekAndYear) { + DeviceProductInfo::ManufactureWeekAndYear weekAndYear; + weekAndYear.year = + date.get().manufactureYear.modelYear.year; + weekAndYear.week = date.get().week; + info.manufactureOrModelDate = weekAndYear; + } + + outInfo->deviceProductInfo = info; + } + return status.transactionError(); } status_t SurfaceComposerClient::getDynamicDisplayInfo(const sp& display, diff --git a/libs/gui/aidl/android/gui/DeviceProductInfo.aidl b/libs/gui/aidl/android/gui/DeviceProductInfo.aidl new file mode 100644 index 0000000000..98404cf0fd --- /dev/null +++ b/libs/gui/aidl/android/gui/DeviceProductInfo.aidl @@ -0,0 +1,58 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +// Product-specific information about the display or the directly connected device on the +// display chain. For example, if the display is transitively connected, this field may contain +// product information about the intermediate device. + +/** @hide */ +parcelable DeviceProductInfo { + parcelable ModelYear { + int year; + } + + parcelable ManufactureYear { + ModelYear modelYear; + } + + parcelable ManufactureWeekAndYear { + ManufactureYear manufactureYear; + + // 1-base week number. Week numbering may not be consistent between manufacturers. + int week; + } + + union ManufactureOrModelDate { + ModelYear modelYear; + ManufactureYear manufactureYear; + ManufactureWeekAndYear manufactureWeekAndYear; + } + + // Display name. + @utf8InCpp String name; + + // NULL-terminated Manufacturer plug and play ID. + byte[] manufacturerPnpId; + + // Manufacturer product ID. + @utf8InCpp String productId; + + ManufactureOrModelDate manufactureOrModelDate; + + byte[] relativeAddress; +} diff --git a/libs/gui/aidl/android/gui/DisplayConnectionType.aidl b/libs/gui/aidl/android/gui/DisplayConnectionType.aidl new file mode 100644 index 0000000000..72c4ede7ac --- /dev/null +++ b/libs/gui/aidl/android/gui/DisplayConnectionType.aidl @@ -0,0 +1,24 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +/** @hide */ +@Backing(type="int") +enum DisplayConnectionType { + Internal = 0, + External = 1 +} diff --git a/libs/gui/aidl/android/gui/DisplayModelId.aidl b/libs/gui/aidl/android/gui/DisplayModelId.aidl new file mode 100644 index 0000000000..d75777b815 --- /dev/null +++ b/libs/gui/aidl/android/gui/DisplayModelId.aidl @@ -0,0 +1,26 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +// Product-specific information about the display or the directly connected device on the +// display chain. For example, if the display is transitively connected, this field may contain +// product information about the intermediate device. + +/** @hide */ +parcelable DisplayModelId { + int id; +} diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index a9977b0f45..f6cd5ec44f 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -20,13 +20,13 @@ import android.gui.DisplayCaptureArgs; import android.gui.DisplayBrightness; import android.gui.DisplayState; import android.gui.DisplayStatInfo; +import android.gui.StaticDisplayInfo; import android.gui.IHdrLayerInfoListener; import android.gui.LayerCaptureArgs; import android.gui.IScreenCaptureListener; /** @hide */ interface ISurfaceComposer { - /* create a virtual display * requires ACCESS_SURFACE_FLINGER permission. */ @@ -64,6 +64,11 @@ interface ISurfaceComposer { */ DisplayState getDisplayState(IBinder display); + /** + * Gets immutable information about given physical display. + */ + StaticDisplayInfo getStaticDisplayInfo(IBinder display); + /** * Clears the user-preferred display mode. The device should now boot in system preferred * display mode. diff --git a/libs/gui/aidl/android/gui/StaticDisplayInfo.aidl b/libs/gui/aidl/android/gui/StaticDisplayInfo.aidl new file mode 100644 index 0000000000..0ccda56ef5 --- /dev/null +++ b/libs/gui/aidl/android/gui/StaticDisplayInfo.aidl @@ -0,0 +1,30 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +import android.gui.DisplayConnectionType; +import android.gui.DeviceProductInfo; +import android.gui.Rotation; + +/** @hide */ +parcelable StaticDisplayInfo { + DisplayConnectionType connectionType = DisplayConnectionType.Internal; + float density; + boolean secure; + @nullable DeviceProductInfo deviceProductInfo; + Rotation installOrientation = Rotation.Rotation0; +} diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index 511937b5f6..29e38b880b 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -85,7 +85,6 @@ namespace ui { struct DisplayMode; struct DisplayState; struct DynamicDisplayInfo; -struct StaticDisplayInfo; } // namespace ui @@ -161,11 +160,6 @@ public: virtual status_t getSupportedFrameTimestamps( std::vector* outSupported) const = 0; - /** - * Gets immutable information about given physical display. - */ - virtual status_t getStaticDisplayInfo(const sp& display, ui::StaticDisplayInfo*) = 0; - /** * Gets dynamic information about given physical display. */ @@ -443,7 +437,7 @@ public: // Java by ActivityManagerService. BOOT_FINISHED = IBinder::FIRST_CALL_TRANSACTION, CREATE_CONNECTION, - GET_STATIC_DISPLAY_INFO, + GET_STATIC_DISPLAY_INFO, // Deprecated. Autogenerated by .aidl now. CREATE_DISPLAY_EVENT_CONNECTION, CREATE_DISPLAY, // Deprecated. Autogenerated by .aidl now. DESTROY_DISPLAY, // Deprecated. Autogenerated by .aidl now. diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 9d03f58aa5..b17902d1f1 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -38,6 +38,7 @@ #include #include #include +#include #include #include diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index e0b86e02fb..e02299d123 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -733,9 +733,6 @@ public: return NO_ERROR; } - status_t getStaticDisplayInfo(const sp& /*display*/, ui::StaticDisplayInfo*) override { - return NO_ERROR; - } status_t getDynamicDisplayInfo(const sp& /*display*/, ui::DynamicDisplayInfo*) override { return NO_ERROR; @@ -929,6 +926,11 @@ public: return binder::Status::ok(); } + binder::Status getStaticDisplayInfo(const sp& /*display*/, + gui::StaticDisplayInfo* /*outInfo*/) override { + return binder::Status::ok(); + } + binder::Status clearBootDisplayMode(const sp& /*display*/) override { return binder::Status::ok(); } diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp index a9380c6e79..4af38c5218 100644 --- a/libs/ui/Android.bp +++ b/libs/ui/Android.bp @@ -145,7 +145,6 @@ cc_library_shared { "PixelFormat.cpp", "PublicFormat.cpp", "StaticAsserts.cpp", - "StaticDisplayInfo.cpp", ], include_dirs: [ diff --git a/libs/ui/DeviceProductInfo.cpp b/libs/ui/DeviceProductInfo.cpp index 4d6ce4306a..496e2a872e 100644 --- a/libs/ui/DeviceProductInfo.cpp +++ b/libs/ui/DeviceProductInfo.cpp @@ -17,7 +17,6 @@ #include #include -#include #include #define RETURN_IF_ERROR(op) \ @@ -27,35 +26,6 @@ namespace android { using base::StringAppendF; -size_t DeviceProductInfo::getFlattenedSize() const { - return FlattenableHelpers::getFlattenedSize(name) + - FlattenableHelpers::getFlattenedSize(manufacturerPnpId) + - FlattenableHelpers::getFlattenedSize(productId) + - FlattenableHelpers::getFlattenedSize(manufactureOrModelDate) + - FlattenableHelpers::getFlattenedSize(relativeAddress); -} - -status_t DeviceProductInfo::flatten(void* buffer, size_t size) const { - if (size < getFlattenedSize()) { - return NO_MEMORY; - } - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, name)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, manufacturerPnpId)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, productId)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, manufactureOrModelDate)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, relativeAddress)); - return OK; -} - -status_t DeviceProductInfo::unflatten(void const* buffer, size_t size) { - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &name)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &manufacturerPnpId)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &productId)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &manufactureOrModelDate)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &relativeAddress)); - return OK; -} - void DeviceProductInfo::dump(std::string& result) const { StringAppendF(&result, "{name=%s, ", name.c_str()); StringAppendF(&result, "manufacturerPnpId=%s, ", manufacturerPnpId.data()); diff --git a/libs/ui/StaticDisplayInfo.cpp b/libs/ui/StaticDisplayInfo.cpp deleted file mode 100644 index 03d15e4694..0000000000 --- a/libs/ui/StaticDisplayInfo.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2020 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 - -#include - -#define RETURN_IF_ERROR(op) \ - if (const status_t status = (op); status != OK) return status; - -namespace android::ui { - -size_t StaticDisplayInfo::getFlattenedSize() const { - return FlattenableHelpers::getFlattenedSize(connectionType) + - FlattenableHelpers::getFlattenedSize(density) + - FlattenableHelpers::getFlattenedSize(secure) + - FlattenableHelpers::getFlattenedSize(deviceProductInfo) + - FlattenableHelpers::getFlattenedSize(installOrientation); -} - -status_t StaticDisplayInfo::flatten(void* buffer, size_t size) const { - if (size < getFlattenedSize()) { - return NO_MEMORY; - } - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, connectionType)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, density)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, secure)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, deviceProductInfo)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, installOrientation)); - return OK; -} - -status_t StaticDisplayInfo::unflatten(void const* buffer, size_t size) { - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &connectionType)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &density)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &secure)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &deviceProductInfo)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &installOrientation)); - return OK; -} - -} // namespace android::ui diff --git a/libs/ui/include/ui/DeviceProductInfo.h b/libs/ui/include/ui/DeviceProductInfo.h index 807a5d96a3..879e46fbdc 100644 --- a/libs/ui/include/ui/DeviceProductInfo.h +++ b/libs/ui/include/ui/DeviceProductInfo.h @@ -24,8 +24,6 @@ #include #include -#include - namespace android { // NUL-terminated plug and play ID. @@ -34,7 +32,7 @@ using PnpId = std::array; // Product-specific information about the display or the directly connected device on the // display chain. For example, if the display is transitively connected, this field may contain // product information about the intermediate device. -struct DeviceProductInfo : LightFlattenable { +struct DeviceProductInfo { struct ModelYear { uint32_t year; }; @@ -64,11 +62,6 @@ struct DeviceProductInfo : LightFlattenable { // For example, for HDMI connected device this will be the physical address. std::vector relativeAddress; - bool isFixedSize() const { return false; } - size_t getFlattenedSize() const; - status_t flatten(void* buffer, size_t size) const; - status_t unflatten(void const* buffer, size_t size); - void dump(std::string& result) const; }; diff --git a/libs/ui/include/ui/StaticDisplayInfo.h b/libs/ui/include/ui/StaticDisplayInfo.h index cc7c869b3b..566e4172a1 100644 --- a/libs/ui/include/ui/StaticDisplayInfo.h +++ b/libs/ui/include/ui/StaticDisplayInfo.h @@ -20,24 +20,18 @@ #include #include -#include namespace android::ui { enum class DisplayConnectionType { Internal, External }; // Immutable information about physical display. -struct StaticDisplayInfo : LightFlattenable { +struct StaticDisplayInfo { DisplayConnectionType connectionType = DisplayConnectionType::Internal; float density = 0.f; bool secure = false; std::optional deviceProductInfo; Rotation installOrientation = ROTATION_0; - - bool isFixedSize() const { return false; } - size_t getFlattenedSize() const; - status_t flatten(void* buffer, size_t size) const; - status_t unflatten(void const* buffer, size_t size); }; } // namespace android::ui diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 26bd356529..decb732d8b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -5559,7 +5560,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case GET_ACTIVE_DISPLAY_MODE: case GET_DISPLAY_COLOR_MODES: case GET_DISPLAY_NATIVE_PRIMARIES: - case GET_STATIC_DISPLAY_INFO: case GET_DYNAMIC_DISPLAY_INFO: case GET_DISPLAY_MODES: case GET_SUPPORTED_FRAME_TIMESTAMPS: @@ -5633,6 +5633,7 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case SET_POWER_MODE: case GET_DISPLAY_STATE: case GET_DISPLAY_STATS: + case GET_STATIC_DISPLAY_INFO: case CLEAR_BOOT_DISPLAY_MODE: case GET_BOOT_DISPLAY_MODE_SUPPORT: case SET_AUTO_LOW_LATENCY_MODE: @@ -7435,6 +7436,47 @@ binder::Status SurfaceComposerAIDL::getDisplayState(const sp& display, return binder::Status::fromStatusT(status); } +binder::Status SurfaceComposerAIDL::getStaticDisplayInfo(const sp& display, + gui::StaticDisplayInfo* outInfo) { + using Tag = gui::DeviceProductInfo::ManufactureOrModelDate::Tag; + ui::StaticDisplayInfo info; + status_t status = mFlinger->getStaticDisplayInfo(display, &info); + if (status == NO_ERROR) { + // convert ui::StaticDisplayInfo to gui::StaticDisplayInfo + outInfo->connectionType = static_cast(info.connectionType); + outInfo->density = info.density; + outInfo->secure = info.secure; + outInfo->installOrientation = static_cast(info.installOrientation); + + gui::DeviceProductInfo dinfo; + std::optional dpi = info.deviceProductInfo; + dinfo.name = std::move(dpi->name); + dinfo.manufacturerPnpId = + std::vector(dpi->manufacturerPnpId.begin(), dpi->manufacturerPnpId.end()); + dinfo.productId = dpi->productId; + if (const auto* model = + std::get_if(&dpi->manufactureOrModelDate)) { + gui::DeviceProductInfo::ModelYear modelYear; + modelYear.year = model->year; + dinfo.manufactureOrModelDate.set(modelYear); + } else if (const auto* manufacture = std::get_if( + &dpi->manufactureOrModelDate)) { + gui::DeviceProductInfo::ManufactureYear date; + date.modelYear.year = manufacture->year; + dinfo.manufactureOrModelDate.set(date); + } else if (const auto* manufacture = std::get_if( + &dpi->manufactureOrModelDate)) { + gui::DeviceProductInfo::ManufactureWeekAndYear date; + date.manufactureYear.modelYear.year = manufacture->year; + date.week = manufacture->week; + dinfo.manufactureOrModelDate.set(date); + } + + outInfo->deviceProductInfo = dinfo; + } + return binder::Status::fromStatusT(status); +} + binder::Status SurfaceComposerAIDL::clearBootDisplayMode(const sp& display) { status_t status = checkAccessPermission(); if (status == OK) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 31d9d63344..bb0bc3f188 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -572,7 +572,7 @@ private: status_t getDisplayState(const sp& displayToken, ui::DisplayState*) EXCLUDES(mStateLock); status_t getStaticDisplayInfo(const sp& displayToken, ui::StaticDisplayInfo*) - EXCLUDES(mStateLock) override; + EXCLUDES(mStateLock); status_t getDynamicDisplayInfo(const sp& displayToken, ui::DynamicDisplayInfo*) EXCLUDES(mStateLock) override; status_t getDisplayNativePrimaries(const sp& displayToken, @@ -1465,6 +1465,8 @@ public: gui::DisplayStatInfo* outStatInfo) override; binder::Status getDisplayState(const sp& display, gui::DisplayState* outState) override; + binder::Status getStaticDisplayInfo(const sp& display, + gui::StaticDisplayInfo* outInfo) override; binder::Status clearBootDisplayMode(const sp& display) override; binder::Status getBootDisplayModeSupport(bool* outMode) override; binder::Status setAutoLowLatencyMode(const sp& display, bool on) override; -- GitLab From 1873e93a324fbdb9bc447fd06044c95462ee1e31 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 23 Mar 2022 15:48:41 -0700 Subject: [PATCH 0032/1310] Add benchmark for onWindowInfosChanged This benchmark should not have any socket writes, and will help us detect whether the high variability of results we are seeing in the other two benchmarks could potentially come from that. Results: arm64-v8a inputflinger_benchmarks: Passed: 3, Failed: 0, Ignored: 0, Assumption Failed: 0, -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Benchmark Time CPU Iteration -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- inputflinger_benchmarks: #benchmarkNotifyMotion 125573 ns 90766 ns 5699 #benchmarkInjectMotion 123218 ns 91416 ns 6341 #benchmarkOnWindowInfosChanged 10888 ns 10000 ns 65385 Bug: 210926970 Test: atest inputflinger_benchmarks (see results above) Change-Id: I1d0b6f00c3d4fa74188c7493129ed9515f3246d2 --- .../benchmarks/InputDispatcher_benchmarks.cpp | 64 +++++++++++++------ 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp index 9691ad8a79..32eec291cb 100644 --- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp +++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp @@ -263,15 +263,15 @@ static NotifyMotionArgs generateMotionArgs() { static void benchmarkNotifyMotion(benchmark::State& state) { // Create dispatcher sp fakePolicy = new FakeInputDispatcherPolicy(); - std::unique_ptr dispatcher = std::make_unique(fakePolicy); - dispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); - dispatcher->start(); + InputDispatcher dispatcher(fakePolicy); + dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); + dispatcher.start(); // Create a window that will receive motion events std::shared_ptr application = std::make_shared(); - sp window = new FakeWindowHandle(application, *dispatcher, "Fake Window"); + sp window = new FakeWindowHandle(application, dispatcher, "Fake Window"); - dispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + dispatcher.setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); NotifyMotionArgs motionArgs = generateMotionArgs(); @@ -280,55 +280,79 @@ static void benchmarkNotifyMotion(benchmark::State& state) { motionArgs.action = AMOTION_EVENT_ACTION_DOWN; motionArgs.downTime = now(); motionArgs.eventTime = motionArgs.downTime; - dispatcher->notifyMotion(&motionArgs); + dispatcher.notifyMotion(&motionArgs); // Send ACTION_UP motionArgs.action = AMOTION_EVENT_ACTION_UP; motionArgs.eventTime = now(); - dispatcher->notifyMotion(&motionArgs); + dispatcher.notifyMotion(&motionArgs); window->consumeEvent(); window->consumeEvent(); } - dispatcher->stop(); + dispatcher.stop(); } static void benchmarkInjectMotion(benchmark::State& state) { // Create dispatcher sp fakePolicy = new FakeInputDispatcherPolicy(); - std::unique_ptr dispatcher = std::make_unique(fakePolicy); - dispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); - dispatcher->start(); + InputDispatcher dispatcher(fakePolicy); + dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); + dispatcher.start(); // Create a window that will receive motion events std::shared_ptr application = std::make_shared(); - sp window = new FakeWindowHandle(application, *dispatcher, "Fake Window"); + sp window = new FakeWindowHandle(application, dispatcher, "Fake Window"); - dispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + dispatcher.setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); for (auto _ : state) { MotionEvent event = generateMotionEvent(); // Send ACTION_DOWN - dispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, - InputEventInjectionSync::NONE, INJECT_EVENT_TIMEOUT, - POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER); + dispatcher.injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, + InputEventInjectionSync::NONE, INJECT_EVENT_TIMEOUT, + POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER); // Send ACTION_UP event.setAction(AMOTION_EVENT_ACTION_UP); - dispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, - InputEventInjectionSync::NONE, INJECT_EVENT_TIMEOUT, - POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER); + dispatcher.injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, + InputEventInjectionSync::NONE, INJECT_EVENT_TIMEOUT, + POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER); window->consumeEvent(); window->consumeEvent(); } - dispatcher->stop(); + dispatcher.stop(); +} + +static void benchmarkOnWindowInfosChanged(benchmark::State& state) { + // Create dispatcher + sp fakePolicy = new FakeInputDispatcherPolicy(); + InputDispatcher dispatcher(fakePolicy); + dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); + dispatcher.start(); + + // Create a window + std::shared_ptr application = std::make_shared(); + sp window = new FakeWindowHandle(application, dispatcher, "Fake Window"); + + std::vector windowInfos{*window->getInfo()}; + gui::DisplayInfo info; + info.displayId = window->getInfo()->displayId; + std::vector displayInfos{info}; + + for (auto _ : state) { + dispatcher.onWindowInfosChanged(windowInfos, displayInfos); + dispatcher.onWindowInfosChanged({} /*windowInfos*/, {} /*displayInfos*/); + } + dispatcher.stop(); } BENCHMARK(benchmarkNotifyMotion); BENCHMARK(benchmarkInjectMotion); +BENCHMARK(benchmarkOnWindowInfosChanged); } // namespace android::inputdispatcher -- GitLab From 421ffb0be94bbacb4bed18524ea5f36695061e08 Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Mon, 21 Mar 2022 19:41:33 -0700 Subject: [PATCH 0033/1310] [SurfaceFlinger] Disable HDR dimming when screen rotates. - Disable dimming for screenshot layer Bug: 224860402 Test: check HDR vidoes when rotation, atest libcompositionengine_test Change-Id: Ib07a5af1d4e3e91737b3d5f3e5869c166759563f --- libs/gui/LayerState.cpp | 6 ++++++ libs/gui/SurfaceComposerClient.cpp | 14 ++++++++++++++ libs/gui/include/gui/LayerState.h | 6 ++++-- libs/gui/include/gui/SurfaceComposerClient.h | 1 + services/surfaceflinger/BufferStateLayer.cpp | 7 +++++++ .../compositionengine/LayerFECompositionState.h | 3 +++ .../src/LayerFECompositionState.cpp | 1 + .../CompositionEngine/src/OutputLayer.cpp | 3 ++- .../CompositionEngine/tests/OutputLayerTest.cpp | 7 +++++++ services/surfaceflinger/Layer.cpp | 12 ++++++++++++ services/surfaceflinger/Layer.h | 4 ++++ services/surfaceflinger/SurfaceFlinger.cpp | 3 +++ 12 files changed, 64 insertions(+), 3 deletions(-) diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 49b669eb3f..9d4d99fddc 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -134,6 +134,7 @@ status_t layer_state_t::write(Parcel& output) const SAFE_PARCEL(output.writeByte, changeFrameRateStrategy); SAFE_PARCEL(output.writeUint32, fixedTransformHint); SAFE_PARCEL(output.writeBool, autoRefresh); + SAFE_PARCEL(output.writeBool, dimmingEnabled); SAFE_PARCEL(output.writeUint32, blurRegions.size()); for (auto region : blurRegions) { @@ -243,6 +244,7 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(input.readUint32, &tmpUint32); fixedTransformHint = static_cast(tmpUint32); SAFE_PARCEL(input.readBool, &autoRefresh); + SAFE_PARCEL(input.readBool, &dimmingEnabled); uint32_t numRegions = 0; SAFE_PARCEL(input.readUint32, &numRegions); @@ -598,6 +600,10 @@ void layer_state_t::merge(const layer_state_t& other) { what |= eColorSpaceAgnosticChanged; colorSpaceAgnostic = other.colorSpaceAgnostic; } + if (other.what & eDimmingEnabledChanged) { + what |= eDimmingEnabledChanged; + dimmingEnabled = other.dimmingEnabled; + } 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, diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index c916abee33..7182dc7de0 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -1224,6 +1224,20 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setTrans return *this; } +SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDimmingEnabled( + const sp& sc, bool dimmingEnabled) { + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + s->what |= layer_state_t::eDimmingEnabledChanged; + s->dimmingEnabled = dimmingEnabled; + + registerSurfaceControlForCallback(sc); + return *this; +} + SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setAlpha( const sp& sc, float alpha) { layer_state_t* s = getLayerState(sc); diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 0f37dab53c..4ca8d68142 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -150,7 +150,7 @@ struct layer_state_t { eTransparentRegionChanged = 0x00000020, eFlagsChanged = 0x00000040, eLayerStackChanged = 0x00000080, - /* unused 0x00000400, */ + eDimmingEnabledChanged = 0x00000400, eShadowRadiusChanged = 0x00000800, /* unused 0x00001000, */ eBufferCropChanged = 0x00002000, @@ -187,7 +187,7 @@ struct layer_state_t { eAutoRefreshChanged = 0x1000'00000000, eStretchChanged = 0x2000'00000000, eTrustedOverlayChanged = 0x4000'00000000, - eDropInputModeChanged = 0x8000'00000000, + eDropInputModeChanged = 0x8000'00000000 }; layer_state_t(); @@ -298,6 +298,8 @@ struct layer_state_t { // Force inputflinger to drop all input events for the layer and its children. gui::DropInputMode dropInputMode; + + bool dimmingEnabled; }; struct ComposerState { diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 9d03f58aa5..0cc43d85bf 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -491,6 +491,7 @@ public: uint32_t flags, uint32_t mask); Transaction& setTransparentRegionHint(const sp& sc, const Region& transparentRegion); + Transaction& setDimmingEnabled(const sp& sc, bool dimmingEnabled); Transaction& setAlpha(const sp& sc, float alpha); Transaction& setMatrix(const sp& sc, diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp index bcae8d9564..c5d7a601c5 100644 --- a/services/surfaceflinger/BufferStateLayer.cpp +++ b/services/surfaceflinger/BufferStateLayer.cpp @@ -1098,6 +1098,13 @@ bool BufferStateLayer::simpleBufferUpdate(const layer_state_t& s) const { } } + if (s.what & layer_state_t::eDimmingEnabledChanged) { + if (mDrawingState.dimmingEnabled != s.dimmingEnabled) { + ALOGV("%s: false [eDimmingEnabledChanged changed]", __func__); + return false; + } + } + ALOGV("%s: true", __func__); return true; } diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h index 283fe86f43..974f7c6134 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h @@ -207,6 +207,9 @@ struct LayerFECompositionState { // framerate of the layer as measured by LayerHistory float fps; + // The dimming flag + bool dimmingEnabled{true}; + virtual ~LayerFECompositionState(); // Debugging diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp index ff7d430531..6631a2772c 100644 --- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp +++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp @@ -121,6 +121,7 @@ void LayerFECompositionState::dump(std::string& out) const { dumpVal(out, "isColorspaceAgnostic", isColorspaceAgnostic); dumpVal(out, "dataspace", toString(dataspace), dataspace); dumpVal(out, "hdr metadata types", hdrMetadata.validTypes); + dumpVal(out, "dimming enabled", dimmingEnabled); dumpVal(out, "colorTransform", colorTransform); out.append("\n"); diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp index 723593d7ac..3289d55870 100644 --- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp +++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp @@ -324,9 +324,10 @@ void OutputLayer::updateCompositionState( // For hdr content, treat the white point as the display brightness - HDR content should not be // boosted or dimmed. + // If the layer explicitly requests to disable dimming, then don't dim either. if (isHdrDataspace(state.dataspace) || getOutput().getState().displayBrightnessNits == getOutput().getState().sdrWhitePointNits || - getOutput().getState().displayBrightnessNits == 0.f) { + getOutput().getState().displayBrightnessNits == 0.f || !layerFEState->dimmingEnabled) { state.dimmingRatio = 1.f; state.whitePointNits = getOutput().getState().displayBrightnessNits; } else { diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp index 8eb1946b67..ceee48c1ef 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp @@ -668,6 +668,13 @@ TEST_F(OutputLayerUpdateCompositionStateTest, setsWhitePointNitsAndDimmingRatioC EXPECT_EQ(mOutputState.sdrWhitePointNits / mOutputState.displayBrightnessNits, mOutputLayer.getState().dimmingRatio); + mLayerFEState.dimmingEnabled = false; + mOutputLayer.updateCompositionState(false, false, ui::Transform::RotationFlags::ROT_0); + EXPECT_EQ(mOutputState.displayBrightnessNits, mOutputLayer.getState().whitePointNits); + EXPECT_EQ(1.f, mOutputLayer.getState().dimmingRatio); + + // change dimmingEnabled back to true. + mLayerFEState.dimmingEnabled = true; mLayerFEState.dataspace = ui::Dataspace::BT2020_ITU_PQ; mLayerFEState.isColorspaceAgnostic = false; mOutputLayer.updateCompositionState(false, false, ui::Transform::RotationFlags::ROT_0); diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index aeaf1e1a14..624d11ec13 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -139,6 +139,7 @@ Layer::Layer(const LayerCreationArgs& args) mDrawingState.destinationFrame.makeInvalid(); mDrawingState.isTrustedOverlay = false; mDrawingState.dropInputMode = gui::DropInputMode::NONE; + mDrawingState.dimmingEnabled = true; if (args.flags & ISurfaceComposerClient::eNoColorFill) { // Set an invalid color so there is no color fill. @@ -477,6 +478,7 @@ void Layer::preparePerFrameCompositionState() { compositionState->colorTransformIsIdentity = !hasColorTransform(); compositionState->surfaceDamage = surfaceDamageRegion; compositionState->hasProtectedContent = isProtected(); + compositionState->dimmingEnabled = isDimmingEnabled(); const bool usesRoundedCorners = getRoundedCornerState().radius != 0.f; @@ -1030,6 +1032,16 @@ bool Layer::setColorSpaceAgnostic(const bool agnostic) { return true; } +bool Layer::setDimmingEnabled(const bool dimmingEnabled) { + if (mDrawingState.dimmingEnabled == dimmingEnabled) return false; + + mDrawingState.sequence++; + mDrawingState.dimmingEnabled = dimmingEnabled; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + bool Layer::setFrameRateSelectionPriority(int32_t priority) { if (mDrawingState.frameRateSelectionPriority == priority) return false; mDrawingState.frameRateSelectionPriority = priority; diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 48a9bc50c4..1842da45d5 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -281,6 +281,8 @@ public: gui::DropInputMode dropInputMode; bool autoRefresh = false; + + bool dimmingEnabled = true; }; /* @@ -411,6 +413,7 @@ public: virtual mat4 getColorTransform() const; virtual bool hasColorTransform() const; virtual bool isColorSpaceAgnostic() const { return mDrawingState.colorSpaceAgnostic; } + virtual bool isDimmingEnabled() const { return getDrawingState().dimmingEnabled; }; // Used only to set BufferStateLayer state virtual bool setTransform(uint32_t /*transform*/) { return false; }; @@ -437,6 +440,7 @@ public: } virtual bool setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace); virtual bool setColorSpaceAgnostic(const bool agnostic); + virtual bool setDimmingEnabled(const bool dimmingEnabled); virtual bool setFrameRateSelectionPriority(int32_t priority); virtual bool setFixedTransformHint(ui::Transform::RotationFlags fixedTransformHint); virtual void setAutoRefresh(bool /* autoRefresh */) {} diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index bd7fba48c4..3e70ac6680 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4523,6 +4523,9 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime if (what & layer_state_t::eAutoRefreshChanged) { layer->setAutoRefresh(s.autoRefresh); } + if (what & layer_state_t::eDimmingEnabledChanged) { + if (layer->setDimmingEnabled(s.dimmingEnabled)) flags |= eTraversalNeeded; + } if (what & layer_state_t::eTrustedOverlayChanged) { if (layer->setTrustedOverlay(s.isTrustedOverlay)) { flags |= eTraversalNeeded; -- GitLab From a6a660fc0aa74ea4f5930b74523cf1893b2f9282 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 4 Mar 2022 15:12:16 -0800 Subject: [PATCH 0034/1310] Add PreferStylusOverTouchBlocker and handle multiple devices We removed PreferStylusOverTouchBlocker previously in order to avoid a crash. In this CL, we are adding it back in, and handling the case of input device having "SOURCE_STYLUS", but reporting "finger" tool type. If there's a stylus event with one of the pointers labeled as 'finger', let's assume that the device supports simultaneous touch and stylus. For this situation, simply disable PreferStylusOverTouchBlocker going forward for these devices, and pass through any events coming from there. Currently, this happens on emulator. In their touch driver, they configure stylus properties as well as touch properties, but most of the events that they send are TOOL_TYPE_FINGER. Previously, this triggered a crash in PreferStylusOverTouchBlocker. Bug: 222531989 Test: atest inputflinger_tests Change-Id: Ifbb08858a4dfebc95c30ca19d6e68533855db7e4 --- include/input/PrintTools.h | 61 ++++ libs/input/Android.bp | 4 + libs/input/PrintTools.cpp | 27 ++ services/inputflinger/InputListener.cpp | 6 +- .../PreferStylusOverTouchBlocker.cpp | 207 +++++++++--- .../PreferStylusOverTouchBlocker.h | 62 ++-- .../UnwantedInteractionBlocker.cpp | 12 + .../inputflinger/UnwantedInteractionBlocker.h | 7 + .../tests/PreferStylusOverTouch_test.cpp | 294 ++++++++++++++++-- .../tests/UnwantedInteractionBlocker_test.cpp | 28 ++ 10 files changed, 597 insertions(+), 111 deletions(-) create mode 100644 include/input/PrintTools.h create mode 100644 libs/input/PrintTools.cpp diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h new file mode 100644 index 0000000000..7c3b29b55f --- /dev/null +++ b/include/input/PrintTools.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +namespace android { + +template +std::string constToString(const T& v) { + return std::to_string(v); +} + +/** + * Convert a set of integral types to string. + */ +template +std::string dumpSet(const std::set& v, std::string (*toString)(const T&) = constToString) { + std::string out; + for (const T& entry : v) { + out += out.empty() ? "{" : ", "; + out += toString(entry); + } + return out.empty() ? "{}" : (out + "}"); +} + +/** + * Convert a map to string. Both keys and values of the map should be integral type. + */ +template +std::string dumpMap(const std::map& map, std::string (*keyToString)(const K&) = constToString, + std::string (*valueToString)(const V&) = constToString) { + std::string out; + for (const auto& [k, v] : map) { + if (!out.empty()) { + out += "\n"; + } + out += keyToString(k) + ":" + valueToString(v); + } + return out; +} + +const char* toString(bool value); + +} // namespace android \ No newline at end of file diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 18fb7c1bfb..1d4fc1fc04 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -50,6 +50,7 @@ cc_library { "Keyboard.cpp", "KeyCharacterMap.cpp", "KeyLayoutMap.cpp", + "PrintTools.cpp", "PropertyMap.cpp", "TouchVideoFrame.cpp", "VelocityControl.cpp", @@ -102,6 +103,9 @@ cc_library { sanitize: { misc_undefined: ["integer"], + diag: { + misc_undefined: ["integer"], + }, }, }, host: { diff --git a/libs/input/PrintTools.cpp b/libs/input/PrintTools.cpp new file mode 100644 index 0000000000..5d6ae4ed91 --- /dev/null +++ b/libs/input/PrintTools.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "PrintTools" + +#include + +namespace android { + +const char* toString(bool value) { + return value ? "true" : "false"; +} + +} // namespace android diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp index 3a4b6c599f..2a3924b5f2 100644 --- a/services/inputflinger/InputListener.cpp +++ b/services/inputflinger/InputListener.cpp @@ -202,9 +202,11 @@ std::string NotifyMotionArgs::dump() const { coords += "}"; } return StringPrintf("NotifyMotionArgs(id=%" PRId32 ", eventTime=%" PRId64 ", deviceId=%" PRId32 - ", source=%s, action=%s, pointerCount=%" PRIu32 " pointers=%s)", + ", source=%s, action=%s, pointerCount=%" PRIu32 + " pointers=%s, flags=0x%08x)", id, eventTime, deviceId, inputEventSourceToString(source).c_str(), - MotionEvent::actionToString(action).c_str(), pointerCount, coords.c_str()); + MotionEvent::actionToString(action).c_str(), pointerCount, coords.c_str(), + flags); } void NotifyMotionArgs::notify(InputListenerInterface& listener) const { diff --git a/services/inputflinger/PreferStylusOverTouchBlocker.cpp b/services/inputflinger/PreferStylusOverTouchBlocker.cpp index ad639b4ef8..beec2e162e 100644 --- a/services/inputflinger/PreferStylusOverTouchBlocker.cpp +++ b/services/inputflinger/PreferStylusOverTouchBlocker.cpp @@ -15,78 +15,163 @@ */ #include "PreferStylusOverTouchBlocker.h" +#include -#include +namespace android { -using android::base::StringPrintf; +static std::pair checkToolType(const NotifyMotionArgs& args) { + bool hasStylus = false; + bool hasTouch = false; + for (size_t i = 0; i < args.pointerCount; i++) { + // Make sure we are canceling stylus pointers + const int32_t toolType = args.pointerProperties[i].toolType; + if (toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS || + toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) { + hasStylus = true; + } + if (toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) { + hasTouch = true; + } + } + return std::make_pair(hasTouch, hasStylus); +} -static const char* toString(bool value) { - return value ? "true" : "false"; +/** + * Intersect two sets in-place, storing the result in 'set1'. + * Find elements in set1 that are not present in set2 and delete them, + * relying on the fact that the two sets are ordered. + */ +template +static void intersectInPlace(std::set& set1, const std::set& set2) { + typename std::set::iterator it1 = set1.begin(); + typename std::set::const_iterator it2 = set2.begin(); + while (it1 != set1.end() && it2 != set2.end()) { + const T& element1 = *it1; + const T& element2 = *it2; + if (element1 < element2) { + // This element is not present in set2. Remove it from set1. + it1 = set1.erase(it1); + continue; + } + if (element2 < element1) { + it2++; + } + if (element1 == element2) { + it1++; + it2++; + } + } + // Remove the rest of the elements in set1 because set2 is already exhausted. + set1.erase(it1, set1.end()); } -namespace android { +/** + * Same as above, but prune a map + */ +template +static void intersectInPlace(std::map& map, const std::set& set2) { + typename std::map::iterator it1 = map.begin(); + typename std::set::const_iterator it2 = set2.begin(); + while (it1 != map.end() && it2 != set2.end()) { + const auto& [key, _] = *it1; + const K& element2 = *it2; + if (key < element2) { + // This element is not present in set2. Remove it from map. + it1 = map.erase(it1); + continue; + } + if (element2 < key) { + it2++; + } + if (key == element2) { + it1++; + it2++; + } + } + // Remove the rest of the elements in map because set2 is already exhausted. + map.erase(it1, map.end()); +} + +// -------------------------------- PreferStylusOverTouchBlocker ----------------------------------- -ftl::StaticVector PreferStylusOverTouchBlocker::processMotion( +std::vector PreferStylusOverTouchBlocker::processMotion( const NotifyMotionArgs& args) { - const bool isStylusEvent = isFromSource(args.source, AINPUT_SOURCE_STYLUS); - if (isStylusEvent) { - for (size_t i = 0; i < args.pointerCount; i++) { - // Make sure we are canceling stylus pointers - const int32_t toolType = args.pointerProperties[i].toolType; - LOG_ALWAYS_FATAL_IF(toolType != AMOTION_EVENT_TOOL_TYPE_STYLUS && - toolType != AMOTION_EVENT_TOOL_TYPE_ERASER, - "The pointer %zu has toolType=%i, but the source is STYLUS. If " - "simultaneous touch and stylus is supported, " - "'PreferStylusOverTouchBlocker' should be disabled.", - i, toolType); + const auto [hasTouch, hasStylus] = checkToolType(args); + const bool isUpOrCancel = + args.action == AMOTION_EVENT_ACTION_UP || args.action == AMOTION_EVENT_ACTION_CANCEL; + + if (hasTouch && hasStylus) { + mDevicesWithMixedToolType.insert(args.deviceId); + } + // Handle the case where mixed touch and stylus pointers are reported. Add this device to the + // ignore list, since it clearly supports simultaneous touch and stylus. + if (mDevicesWithMixedToolType.find(args.deviceId) != mDevicesWithMixedToolType.end()) { + // This event comes from device with mixed stylus and touch event. Ignore this device. + if (mCanceledDevices.find(args.deviceId) != mCanceledDevices.end()) { + // If we started to cancel events from this device, continue to do so to keep + // the stream consistent. It should happen at most once per "mixed" device. + if (isUpOrCancel) { + mCanceledDevices.erase(args.deviceId); + mLastTouchEvents.erase(args.deviceId); + } + return {}; } + return {args}; } + + const bool isStylusEvent = hasStylus; const bool isDown = args.action == AMOTION_EVENT_ACTION_DOWN; - const bool isUpOrCancel = - args.action == AMOTION_EVENT_ACTION_UP || args.action == AMOTION_EVENT_ACTION_CANCEL; + if (isStylusEvent) { if (isDown) { // Reject all touch while stylus is down - mIsStylusDown = true; - if (mIsTouchDown && !mCurrentTouchIsCanceled) { - // Cancel touch! - mCurrentTouchIsCanceled = true; - mLastTouchEvent.action = AMOTION_EVENT_ACTION_CANCEL; - mLastTouchEvent.flags |= AMOTION_EVENT_FLAG_CANCELED; - mLastTouchEvent.eventTime = systemTime(SYSTEM_TIME_MONOTONIC); - return {mLastTouchEvent, args}; + mActiveStyli.insert(args.deviceId); + + // Cancel all current touch! + std::vector result; + for (auto& [deviceId, lastTouchEvent] : mLastTouchEvents) { + if (mCanceledDevices.find(deviceId) != mCanceledDevices.end()) { + // Already canceled, go to next one. + continue; + } + // Not yet canceled. Cancel it. + lastTouchEvent.action = AMOTION_EVENT_ACTION_CANCEL; + lastTouchEvent.flags |= AMOTION_EVENT_FLAG_CANCELED; + lastTouchEvent.eventTime = systemTime(SYSTEM_TIME_MONOTONIC); + result.push_back(lastTouchEvent); + mCanceledDevices.insert(deviceId); } + result.push_back(args); + return result; } if (isUpOrCancel) { - mIsStylusDown = false; + mActiveStyli.erase(args.deviceId); } // Never drop stylus events return {args}; } - const bool isTouchEvent = - isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN) && !isStylusEvent; + const bool isTouchEvent = hasTouch; if (isTouchEvent) { - if (mIsStylusDown) { - mCurrentTouchIsCanceled = true; + // Suppress the current gesture if any stylus is still down + if (!mActiveStyli.empty()) { + mCanceledDevices.insert(args.deviceId); + } + + const bool shouldDrop = mCanceledDevices.find(args.deviceId) != mCanceledDevices.end(); + if (isUpOrCancel) { + mCanceledDevices.erase(args.deviceId); + mLastTouchEvents.erase(args.deviceId); } + // If we already canceled the current gesture, then continue to drop events from it, even if // the stylus has been lifted. - if (mCurrentTouchIsCanceled) { - if (isUpOrCancel) { - mCurrentTouchIsCanceled = false; - } + if (shouldDrop) { return {}; } - // Update state - mLastTouchEvent = args; - if (isDown) { - mIsTouchDown = true; - } - if (isUpOrCancel) { - mIsTouchDown = false; - mCurrentTouchIsCanceled = false; + if (!isUpOrCancel) { + mLastTouchEvents[args.deviceId] = args; } return {args}; } @@ -95,12 +180,36 @@ ftl::StaticVector PreferStylusOverTouchBlocker::processMoti return {args}; } -std::string PreferStylusOverTouchBlocker::dump() { +void PreferStylusOverTouchBlocker::notifyInputDevicesChanged( + const std::vector& inputDevices) { + std::set presentDevices; + for (const InputDeviceInfo& device : inputDevices) { + presentDevices.insert(device.getId()); + } + // Only keep the devices that are still present. + intersectInPlace(mDevicesWithMixedToolType, presentDevices); + intersectInPlace(mLastTouchEvents, presentDevices); + intersectInPlace(mCanceledDevices, presentDevices); + intersectInPlace(mActiveStyli, presentDevices); +} + +void PreferStylusOverTouchBlocker::notifyDeviceReset(const NotifyDeviceResetArgs& args) { + mDevicesWithMixedToolType.erase(args.deviceId); + mLastTouchEvents.erase(args.deviceId); + mCanceledDevices.erase(args.deviceId); + mActiveStyli.erase(args.deviceId); +} + +static std::string dumpArgs(const NotifyMotionArgs& args) { + return args.dump(); +} + +std::string PreferStylusOverTouchBlocker::dump() const { std::string out; - out += StringPrintf("mIsTouchDown: %s\n", toString(mIsTouchDown)); - out += StringPrintf("mIsStylusDown: %s\n", toString(mIsStylusDown)); - out += StringPrintf("mLastTouchEvent: %s\n", mLastTouchEvent.dump().c_str()); - out += StringPrintf("mCurrentTouchIsCanceled: %s\n", toString(mCurrentTouchIsCanceled)); + out += "mActiveStyli: " + dumpSet(mActiveStyli) + "\n"; + out += "mLastTouchEvents: " + dumpMap(mLastTouchEvents, constToString, dumpArgs) + "\n"; + out += "mDevicesWithMixedToolType: " + dumpSet(mDevicesWithMixedToolType) + "\n"; + out += "mCanceledDevices: " + dumpSet(mCanceledDevices) + "\n"; return out; } diff --git a/services/inputflinger/PreferStylusOverTouchBlocker.h b/services/inputflinger/PreferStylusOverTouchBlocker.h index 3f5616190b..716dc4d351 100644 --- a/services/inputflinger/PreferStylusOverTouchBlocker.h +++ b/services/inputflinger/PreferStylusOverTouchBlocker.h @@ -16,66 +16,50 @@ #pragma once -#include #include +#include #include "InputListener.h" namespace android { /** - * When stylus is down, we ignore all touch. + * When stylus is down, all touch is ignored. * TODO(b/210159205): delete this when simultaneous stylus and touch is supported */ class PreferStylusOverTouchBlocker { public: /** - * Process the provided event and emit up to 2 events in response. + * Process the provided event and emit 0 or more events that should be used instead of it. * In the majority of cases, the returned result will just be the provided args (array with * only 1 element), unmodified. * * If the gesture should be blocked, the returned result may be: * * a) An empty array, if the current event should just be ignored completely - * b) An array of 2 elements, containing an event with ACTION_CANCEL and the current event. + * b) An array of N elements, containing N-1 events with ACTION_CANCEL and the current event. * - * bool is set to 'true'. - * NotifyMotionArgs potentially contains an event that should be used to cancel the existing - * gesture. - * - * If the event should not be blocked, bool contains 'false'. + * The returned result is intended to be reinjected into the original event stream in + * replacement of the incoming event. */ - ftl::StaticVector processMotion(const NotifyMotionArgs& args); - std::string dump(); + std::vector processMotion(const NotifyMotionArgs& args); + std::string dump() const; + + void notifyInputDevicesChanged(const std::vector& inputDevices); + + void notifyDeviceReset(const NotifyDeviceResetArgs& args); private: - bool mIsTouchDown = false; - bool mIsStylusDown = false; - // Provide some default values for the stored MotionEvent to allow printint the event before - // any real event is received. - NotifyMotionArgs mLastTouchEvent{0 /*id*/, - 0 /*eventTime*/, - 0 /*readTime*/, - 0 /*deviceId*/, - AINPUT_SOURCE_TOUCHSCREEN, - 0 /*displayId*/, - 0 /*policyFlags*/, - 0 /*action*/, - 0 /*actionButton*/, - 0 /*flags*/, - 0 /*metaState*/, - 0 /*buttonState*/, - MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, - 0 /*pointerCount*/, - nullptr /*properties*/, - nullptr /*coords*/, - 0. /*xPrecision*/, - 0. /*yPrecision*/, - AMOTION_EVENT_INVALID_CURSOR_POSITION, - AMOTION_EVENT_INVALID_CURSOR_POSITION, - 0 /*downTime*/, - {}}; - bool mCurrentTouchIsCanceled = false; + // Stores the device id's of styli that are currently down. + std::set mActiveStyli; + // For each device, store the last touch event as long as the touch is down. Upon liftoff, + // the entry is erased. + std::map mLastTouchEvents; + // Device ids of devices for which the current touch gesture is canceled. + std::set mCanceledDevices; + + // Device ids of input devices where we encountered simultaneous touch and stylus + // events. For these devices, we don't do any event processing (nothing is blocked or altered). + std::set mDevicesWithMixedToolType; }; } // namespace android \ No newline at end of file diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp index fb3962e544..b69e16ac85 100644 --- a/services/inputflinger/UnwantedInteractionBlocker.cpp +++ b/services/inputflinger/UnwantedInteractionBlocker.cpp @@ -368,6 +368,14 @@ void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs* args) { } void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs* args) { + const std::vector processedArgs = + mPreferStylusOverTouchBlocker.processMotion(*args); + for (const NotifyMotionArgs& loopArgs : processedArgs) { + notifyMotionInner(&loopArgs); + } +} + +void UnwantedInteractionBlocker::notifyMotionInner(const NotifyMotionArgs* args) { auto it = mPalmRejectors.find(args->deviceId); const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args->source); if (!sendToPalmRejector) { @@ -401,6 +409,7 @@ void UnwantedInteractionBlocker::notifyDeviceReset(const NotifyDeviceResetArgs* mPalmRejectors.emplace(args->deviceId, info); } mListener.notifyDeviceReset(args); + mPreferStylusOverTouchBlocker.notifyDeviceReset(*args); } void UnwantedInteractionBlocker::notifyPointerCaptureChanged( @@ -437,10 +446,13 @@ void UnwantedInteractionBlocker::notifyInputDevicesChanged( auto const& [deviceId, _] = item; return devicesToKeep.find(deviceId) == devicesToKeep.end(); }); + mPreferStylusOverTouchBlocker.notifyInputDevicesChanged(inputDevices); } void UnwantedInteractionBlocker::dump(std::string& dump) { dump += "UnwantedInteractionBlocker:\n"; + dump += " mPreferStylusOverTouchBlocker:\n"; + dump += addPrefix(mPreferStylusOverTouchBlocker.dump(), " "); dump += StringPrintf(" mEnablePalmRejection: %s\n", toString(mEnablePalmRejection)); dump += StringPrintf(" isPalmRejectionEnabled (flag value): %s\n", toString(isPalmRejectionEnabled())); diff --git a/services/inputflinger/UnwantedInteractionBlocker.h b/services/inputflinger/UnwantedInteractionBlocker.h index 14068fd878..8a1cd7265e 100644 --- a/services/inputflinger/UnwantedInteractionBlocker.h +++ b/services/inputflinger/UnwantedInteractionBlocker.h @@ -23,6 +23,8 @@ #include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h" #include "ui/events/ozone/evdev/touch_filter/palm_detection_filter.h" +#include "PreferStylusOverTouchBlocker.h" + namespace android { // --- Functions for manipulation of event streams @@ -88,9 +90,14 @@ private: InputListenerInterface& mListener; const bool mEnablePalmRejection; + // When stylus is down, ignore touch + PreferStylusOverTouchBlocker mPreferStylusOverTouchBlocker; + // Detect and reject unwanted palms on screen // Use a separate palm rejector for every touch device. std::map mPalmRejectors; + // TODO(b/210159205): delete this when simultaneous stylus and touch is supported + void notifyMotionInner(const NotifyMotionArgs* args); }; class SlotState { diff --git a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp index 70f40aa028..8e2ab88e80 100644 --- a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp +++ b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp @@ -20,12 +20,16 @@ namespace android { constexpr int32_t TOUCH_DEVICE_ID = 3; -constexpr int32_t STYLUS_DEVICE_ID = 4; +constexpr int32_t SECOND_TOUCH_DEVICE_ID = 4; +constexpr int32_t STYLUS_DEVICE_ID = 5; +constexpr int32_t SECOND_STYLUS_DEVICE_ID = 6; constexpr int DOWN = AMOTION_EVENT_ACTION_DOWN; constexpr int MOVE = AMOTION_EVENT_ACTION_MOVE; constexpr int UP = AMOTION_EVENT_ACTION_UP; constexpr int CANCEL = AMOTION_EVENT_ACTION_CANCEL; +static constexpr int32_t POINTER_1_DOWN = + AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); constexpr int32_t TOUCHSCREEN = AINPUT_SOURCE_TOUCHSCREEN; constexpr int32_t STYLUS = AINPUT_SOURCE_STYLUS; @@ -78,29 +82,30 @@ static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, class PreferStylusOverTouchTest : public testing::Test { protected: - void assertNotBlocked(const NotifyMotionArgs& args) { - ftl::StaticVector processedArgs = mBlocker.processMotion(args); - ASSERT_EQ(1u, processedArgs.size()); - ASSERT_EQ(args, processedArgs[0]); + void assertNotBlocked(const NotifyMotionArgs& args) { assertResponse(args, {args}); } + + void assertDropped(const NotifyMotionArgs& args) { assertResponse(args, {}); } + + void assertResponse(const NotifyMotionArgs& args, + const std::vector& expected) { + std::vector receivedArgs = mBlocker.processMotion(args); + ASSERT_EQ(expected.size(), receivedArgs.size()); + for (size_t i = 0; i < expected.size(); i++) { + // The 'eventTime' of CANCEL events is dynamically generated. Don't check this field. + if (expected[i].action == CANCEL && receivedArgs[i].action == CANCEL) { + receivedArgs[i].eventTime = expected[i].eventTime; + } + + ASSERT_EQ(expected[i], receivedArgs[i]) + << expected[i].dump() << " vs " << receivedArgs[i].dump(); + } } - void assertDropped(const NotifyMotionArgs& args) { - ftl::StaticVector processedArgs = mBlocker.processMotion(args); - ASSERT_TRUE(processedArgs.empty()); + void notifyInputDevicesChanged(const std::vector& devices) { + mBlocker.notifyInputDevicesChanged(devices); } - void assertCanceled(const NotifyMotionArgs& args, - std::optional canceledArgs) { - ftl::StaticVector processedArgs = mBlocker.processMotion(args); - ASSERT_EQ(2u, processedArgs.size()); - NotifyMotionArgs& cancelEvent = processedArgs[0]; - ASSERT_EQ(CANCEL, cancelEvent.action); - ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, cancelEvent.flags & AMOTION_EVENT_FLAG_CANCELED); - ASSERT_TRUE(isFromSource(cancelEvent.source, TOUCHSCREEN)); - ASSERT_FALSE(isFromSource(cancelEvent.source, STYLUS)); - - ASSERT_EQ(args, processedArgs[1]); - } + void dump() const { ALOGI("Blocker: \n%s\n", mBlocker.dump().c_str()); } private: PreferStylusOverTouchBlocker mBlocker; @@ -148,7 +153,8 @@ TEST_F(PreferStylusOverTouchTest, TouchIsCanceledWhenStylusGoesDown) { args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); NotifyMotionArgs cancelArgs = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, CANCEL, {{1, 3}}, TOUCHSCREEN); - assertCanceled(args, cancelArgs); + cancelArgs.flags |= AMOTION_EVENT_FLAG_CANCELED; + assertResponse(args, {cancelArgs, args}); // Both stylus and touch events continue. Stylus should be not blocked, and touch should be // blocked @@ -159,6 +165,26 @@ TEST_F(PreferStylusOverTouchTest, TouchIsCanceledWhenStylusGoesDown) { assertDropped(args); } +/** + * Stylus goes down after touch gesture. + */ +TEST_F(PreferStylusOverTouchTest, StylusDownAfterTouch) { + NotifyMotionArgs args; + + args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + assertNotBlocked(args); + + args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN); + assertNotBlocked(args); + + args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN); + assertNotBlocked(args); + + // Stylus goes down + args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); + assertNotBlocked(args); +} + /** * New touch events should be simply blocked (dropped) when stylus is down. No CANCEL event should * be generated. @@ -247,4 +273,230 @@ TEST_F(PreferStylusOverTouchTest, AfterStylusIsLiftedCurrentTouchIsBlocked) { assertNotBlocked(args); } +/** + * If an event with mixed stylus and touch pointers is encountered, it should be ignored. Touches + * from such should pass, even if stylus from the same device goes down. + */ +TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchPointersAreIgnored) { + NotifyMotionArgs args; + + // Event from a stylus device, but with finger tool type + args = generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, STYLUS); + // Keep source stylus, but make the tool type touch + args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + assertNotBlocked(args); + + // Second pointer (stylus pointer) goes down, from the same device + args = generateMotionArgs(1 /*downTime*/, 2 /*eventTime*/, POINTER_1_DOWN, {{1, 2}, {10, 20}}, + STYLUS); + // Keep source stylus, but make the tool type touch + args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + assertNotBlocked(args); + + // Second pointer (stylus pointer) goes down, from the same device + args = generateMotionArgs(1 /*downTime*/, 3 /*eventTime*/, MOVE, {{2, 3}, {11, 21}}, STYLUS); + // Keep source stylus, but make the tool type touch + args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + assertNotBlocked(args); +} + +/** + * When there are two touch devices, stylus down should cancel all current touch streams. + */ +TEST_F(PreferStylusOverTouchTest, TouchFromTwoDevicesAndStylus) { + NotifyMotionArgs touch1Down = + generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + assertNotBlocked(touch1Down); + + NotifyMotionArgs touch2Down = + generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{3, 4}}, TOUCHSCREEN); + touch2Down.deviceId = SECOND_TOUCH_DEVICE_ID; + assertNotBlocked(touch2Down); + + NotifyMotionArgs stylusDown = + generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); + NotifyMotionArgs cancelArgs1 = touch1Down; + cancelArgs1.action = CANCEL; + cancelArgs1.flags |= AMOTION_EVENT_FLAG_CANCELED; + NotifyMotionArgs cancelArgs2 = touch2Down; + cancelArgs2.action = CANCEL; + cancelArgs2.flags |= AMOTION_EVENT_FLAG_CANCELED; + assertResponse(stylusDown, {cancelArgs1, cancelArgs2, stylusDown}); +} + +/** + * Touch should be canceled when stylus goes down. After the stylus lifts up, the touch from that + * device should continue to be canceled. + * If one of the devices is already canceled, it should remain canceled, but new touches from a + * different device should go through. + */ +TEST_F(PreferStylusOverTouchTest, AllTouchMustLiftAfterCanceledByStylus) { + // First device touches down + NotifyMotionArgs touch1Down = + generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + assertNotBlocked(touch1Down); + + // Stylus goes down - touch should be canceled + NotifyMotionArgs stylusDown = + generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); + NotifyMotionArgs cancelArgs1 = touch1Down; + cancelArgs1.action = CANCEL; + cancelArgs1.flags |= AMOTION_EVENT_FLAG_CANCELED; + assertResponse(stylusDown, {cancelArgs1, stylusDown}); + + // Stylus goes up + NotifyMotionArgs stylusUp = + generateMotionArgs(2 /*downTime*/, 3 /*eventTime*/, UP, {{10, 30}}, STYLUS); + assertNotBlocked(stylusUp); + + // Touch from the first device remains blocked + NotifyMotionArgs touch1Move = + generateMotionArgs(1 /*downTime*/, 4 /*eventTime*/, MOVE, {{2, 3}}, TOUCHSCREEN); + assertDropped(touch1Move); + + // Second touch goes down. It should not be blocked because stylus has already lifted. + NotifyMotionArgs touch2Down = + generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{31, 32}}, TOUCHSCREEN); + touch2Down.deviceId = SECOND_TOUCH_DEVICE_ID; + assertNotBlocked(touch2Down); + + // First device is lifted up. It's already been canceled, so the UP event should be dropped. + NotifyMotionArgs touch1Up = + generateMotionArgs(1 /*downTime*/, 6 /*eventTime*/, UP, {{2, 3}}, TOUCHSCREEN); + assertDropped(touch1Up); + + // Touch from second device touch should continue to work + NotifyMotionArgs touch2Move = + generateMotionArgs(5 /*downTime*/, 7 /*eventTime*/, MOVE, {{32, 33}}, TOUCHSCREEN); + touch2Move.deviceId = SECOND_TOUCH_DEVICE_ID; + assertNotBlocked(touch2Move); + + // Second touch lifts up + NotifyMotionArgs touch2Up = + generateMotionArgs(5 /*downTime*/, 8 /*eventTime*/, UP, {{32, 33}}, TOUCHSCREEN); + touch2Up.deviceId = SECOND_TOUCH_DEVICE_ID; + assertNotBlocked(touch2Up); + + // Now that all touch has been lifted, new touch from either first or second device should work + NotifyMotionArgs touch3Down = + generateMotionArgs(9 /*downTime*/, 9 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + assertNotBlocked(touch3Down); + + NotifyMotionArgs touch4Down = + generateMotionArgs(10 /*downTime*/, 10 /*eventTime*/, DOWN, {{100, 200}}, TOUCHSCREEN); + touch4Down.deviceId = SECOND_TOUCH_DEVICE_ID; + assertNotBlocked(touch4Down); +} + +/** + * When we don't know that a specific device does both stylus and touch, and we only see touch + * pointers from it, we should treat it as a touch device. That means, the device events should be + * canceled when stylus from another device goes down. When we detect simultaneous touch and stylus + * from this device though, we should just pass this device through without canceling anything. + * + * In this test: + * 1. Start by touching down with device 1 + * 2. Device 2 has stylus going down + * 3. Device 1 should be canceled. + * 4. When we add stylus pointers to the device 1, they should continue to be canceled. + * 5. Device 1 lifts up. + * 6. Subsequent events from device 1 should not be canceled even if stylus is down. + * 7. If a reset happens, and such device is no longer there, then we should + * Therefore, the device 1 is "ignored" and does not participate into "prefer stylus over touch" + * behaviour. + */ +TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchDeviceIsCanceledAtFirst) { + // Touch from device 1 goes down + NotifyMotionArgs touchDown = + generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + touchDown.source = STYLUS; + assertNotBlocked(touchDown); + + // Stylus from device 2 goes down. Touch should be canceled. + NotifyMotionArgs args = + generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{10, 20}}, STYLUS); + NotifyMotionArgs cancelTouchArgs = touchDown; + cancelTouchArgs.action = CANCEL; + cancelTouchArgs.flags |= AMOTION_EVENT_FLAG_CANCELED; + assertResponse(args, {cancelTouchArgs, args}); + + // Introduce a stylus pointer into the device 1 stream. It should be ignored. + args = generateMotionArgs(1 /*downTime*/, 3 /*eventTime*/, POINTER_1_DOWN, {{1, 2}, {3, 4}}, + TOUCHSCREEN); + args.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args.source = STYLUS; + assertDropped(args); + + // Lift up touch from the mixed touch/stylus device + args = generateMotionArgs(1 /*downTime*/, 4 /*eventTime*/, CANCEL, {{1, 2}, {3, 4}}, + TOUCHSCREEN); + args.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args.source = STYLUS; + assertDropped(args); + + // Stylus from device 2 is still down. Since the device 1 is now identified as a mixed + // touch/stylus device, its events should go through, even if they are touch. + args = generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{21, 22}}, TOUCHSCREEN); + touchDown.source = STYLUS; + assertResponse(args, {args}); + + // Reconfigure such that only the stylus device remains + InputDeviceInfo stylusDevice; + stylusDevice.initialize(STYLUS_DEVICE_ID, 1 /*generation*/, 1 /*controllerNumber*/, + {} /*identifier*/, "stylus device", false /*external*/, + false /*hasMic*/); + notifyInputDevicesChanged({stylusDevice}); + // The touchscreen device was removed, so we no longer remember anything about it. We should + // again start blocking touch events from it. + args = generateMotionArgs(6 /*downTime*/, 6 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + args.source = STYLUS; + assertDropped(args); +} + +/** + * If two styli are active at the same time, touch should be blocked until both of them are lifted. + * If one of them lifts, touch should continue to be blocked. + */ +TEST_F(PreferStylusOverTouchTest, TouchIsBlockedWhenTwoStyliAreUsed) { + NotifyMotionArgs args; + + // First stylus is down + assertNotBlocked(generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS)); + + // Second stylus is down + args = generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{20, 40}}, STYLUS); + args.deviceId = SECOND_STYLUS_DEVICE_ID; + assertNotBlocked(args); + + // Touch goes down. It should be ignored. + args = generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + assertDropped(args); + + // Lift the first stylus + args = generateMotionArgs(0 /*downTime*/, 3 /*eventTime*/, UP, {{10, 30}}, STYLUS); + assertNotBlocked(args); + + // Touch should continue to be blocked + args = generateMotionArgs(2 /*downTime*/, 4 /*eventTime*/, UP, {{1, 2}}, TOUCHSCREEN); + assertDropped(args); + + // New touch should be blocked because second stylus is still down + args = generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{5, 6}}, TOUCHSCREEN); + assertDropped(args); + + // Second stylus goes up + args = generateMotionArgs(1 /*downTime*/, 6 /*eventTime*/, UP, {{20, 40}}, STYLUS); + args.deviceId = SECOND_STYLUS_DEVICE_ID; + assertNotBlocked(args); + + // Current touch gesture should continue to be blocked + // Touch should continue to be blocked + args = generateMotionArgs(5 /*downTime*/, 7 /*eventTime*/, UP, {{5, 6}}, TOUCHSCREEN); + assertDropped(args); + + // Now that all styli were lifted, new touch should go through + args = generateMotionArgs(8 /*downTime*/, 8 /*eventTime*/, DOWN, {{7, 8}}, TOUCHSCREEN); + assertNotBlocked(args); +} + } // namespace android diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp index b2f8eb37f0..e378096df5 100644 --- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp +++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp @@ -519,6 +519,34 @@ TEST_F(UnwantedInteractionBlockerTest, NoResetIfDeviceInfoChanges) { &(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/, MOVE, {{7, 8, 9}}))); } +/** + * Send a touch event, and then a stylus event. Make sure that both work. + */ +TEST_F(UnwantedInteractionBlockerTest, StylusAfterTouchWorks) { + NotifyMotionArgs args; + mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); + args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}}); + mBlocker->notifyMotion(&args); + args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{4, 5, 6}}); + mBlocker->notifyMotion(&args); + args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{4, 5, 6}}); + mBlocker->notifyMotion(&args); + + // Now touch down stylus + args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 20, 30}}); + args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args.source |= AINPUT_SOURCE_STYLUS; + mBlocker->notifyMotion(&args); + args = generateMotionArgs(3 /*downTime*/, 4 /*eventTime*/, MOVE, {{40, 50, 60}}); + args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args.source |= AINPUT_SOURCE_STYLUS; + mBlocker->notifyMotion(&args); + args = generateMotionArgs(3 /*downTime*/, 5 /*eventTime*/, UP, {{40, 50, 60}}); + args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args.source |= AINPUT_SOURCE_STYLUS; + mBlocker->notifyMotion(&args); +} + using UnwantedInteractionBlockerTestDeathTest = UnwantedInteractionBlockerTest; /** -- GitLab From 30835f246251cfb6a4cbd588d93191996bd5f9f8 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Fri, 18 Mar 2022 00:58:26 +0000 Subject: [PATCH 0035/1310] Plumb through dimming stage into RenderEngine. If HWC is requesting that dimming happens after OETF is applied, then apply the dimming matrix as part of the display color transform Bug: 218954037 Test: Toggle client composition in adaptive color mode during HDR playback Change-Id: Ib72f3f4b6dfcced02fc330e64fa72546a18fb608 --- libs/renderengine/Android.bp | 1 + libs/renderengine/benchmark/Android.bp | 1 + .../include/renderengine/DisplaySettings.h | 5 + libs/renderengine/skia/SkiaGLRenderEngine.cpp | 28 +++- libs/renderengine/tests/Android.bp | 1 + libs/renderengine/tests/RenderEngineTest.cpp | 91 +++++++++- .../include/compositionengine/impl/Display.h | 2 +- .../impl/OutputCompositionState.h | 5 + .../CompositionEngine/src/Display.cpp | 19 ++- .../CompositionEngine/src/Output.cpp | 1 + .../CompositionEngine/tests/DisplayTest.cpp | 37 +++-- .../CompositionEngine/tests/OutputTest.cpp | 156 ++++++++++++------ .../DisplayHardware/AidlComposerHal.cpp | 18 +- .../DisplayHardware/AidlComposerHal.h | 7 +- .../DisplayHardware/ComposerHal.h | 4 +- .../surfaceflinger/DisplayHardware/HWC2.cpp | 8 +- .../surfaceflinger/DisplayHardware/HWC2.h | 9 +- .../DisplayHardware/HWComposer.cpp | 5 +- .../DisplayHardware/HWComposer.h | 5 +- .../DisplayHardware/HidlComposerHal.cpp | 17 +- .../DisplayHardware/HidlComposerHal.h | 7 +- .../mock/DisplayHardware/MockComposer.h | 6 +- .../unittests/mock/DisplayHardware/MockHWC2.h | 6 +- 23 files changed, 321 insertions(+), 118 deletions(-) diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp index 84e84dddad..cb92df388b 100644 --- a/libs/renderengine/Android.bp +++ b/libs/renderengine/Android.bp @@ -27,6 +27,7 @@ cc_defaults { "-DEGL_EGLEXT_PROTOTYPES", ], shared_libs: [ + "android.hardware.graphics.composer3-V1-ndk", "libbase", "libcutils", "libEGL", diff --git a/libs/renderengine/benchmark/Android.bp b/libs/renderengine/benchmark/Android.bp index 471159f390..249fec5866 100644 --- a/libs/renderengine/benchmark/Android.bp +++ b/libs/renderengine/benchmark/Android.bp @@ -43,6 +43,7 @@ cc_benchmark { ], shared_libs: [ + "android.hardware.graphics.composer3-V1-ndk", "libbase", "libcutils", "libjnigraphics", diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h index 40ba5ad2a8..bf506448f6 100644 --- a/libs/renderengine/include/renderengine/DisplaySettings.h +++ b/libs/renderengine/include/renderengine/DisplaySettings.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include @@ -68,6 +69,10 @@ struct DisplaySettings { // All layers will be dimmed by (max(layer white points) / targetLuminanceNits). // If the target luminance is unknown, then no display-level dimming occurs. float targetLuminanceNits = -1.f; + + // Configures when dimming should be applied for each layer. + aidl::android::hardware::graphics::composer3::DimmingStage dimmingStage = + aidl::android::hardware::graphics::composer3::DimmingStage::NONE; }; static inline bool operator==(const DisplaySettings& lhs, const DisplaySettings& rhs) { diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index a177b1dca1..1a1c3b44e8 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -752,7 +752,9 @@ static SkRRect getBlurRRect(const BlurRegion& region) { return roundedRect; } -static bool equalsWithinMargin(float expected, float value, float margin) { +// Arbitrary default margin which should be close enough to zero. +constexpr float kDefaultMargin = 0.0001f; +static bool equalsWithinMargin(float expected, float value, float margin = kDefaultMargin) { LOG_ALWAYS_FATAL_IF(margin < 0.f, "Margin is negative!"); return std::abs(expected - value) < margin; } @@ -1010,10 +1012,13 @@ void SkiaGLRenderEngine::drawLayersInternal( ? displayDimmingRatio : (layer.whitePointNits / maxLayerWhitePoint) * displayDimmingRatio; + const bool dimInLinearSpace = display.dimmingStage != + aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF; + const bool requiresLinearEffect = layer.colorTransform != mat4() || (mUseColorManagement && needsToneMapping(layer.sourceDataspace, display.outputDataspace)) || - !equalsWithinMargin(1.f, layerDimmingRatio, 0.001f); + (dimInLinearSpace && !equalsWithinMargin(1.f, layerDimmingRatio)); // quick abort from drawing the remaining portion of the layer if (layer.skipContentDraw || @@ -1119,7 +1124,9 @@ void SkiaGLRenderEngine::drawLayersInternal( .undoPremultipliedAlpha = !item.isOpaque && item.usePremultipliedAlpha, .requiresLinearEffect = requiresLinearEffect, - .layerDimmingRatio = layerDimmingRatio})); + .layerDimmingRatio = dimInLinearSpace + ? layerDimmingRatio + : 1.f})); // Turn on dithering when dimming beyond this threshold. static constexpr float kDimmingThreshold = 0.2f; @@ -1186,7 +1193,20 @@ void SkiaGLRenderEngine::drawLayersInternal( // An A8 buffer will already have the proper color filter attached to // its paint, including the displayColorTransform as needed. if (!paint.getColorFilter()) { - paint.setColorFilter(displayColorTransform); + if (!dimInLinearSpace && !equalsWithinMargin(1.0, layerDimmingRatio)) { + // If we don't dim in linear space, then when we gamma correct the dimming ratio we + // can assume a gamma 2.2 transfer function. + static constexpr float kInverseGamma22 = 1.f / 2.2f; + const auto gammaCorrectedDimmingRatio = + std::pow(layerDimmingRatio, kInverseGamma22); + const auto dimmingMatrix = + mat4::scale(vec4(gammaCorrectedDimmingRatio, gammaCorrectedDimmingRatio, + gammaCorrectedDimmingRatio, 1.f)); + paint.setColorFilter(SkColorFilters::Matrix( + toSkColorMatrix(display.colorTransform * dimmingMatrix))); + } else { + paint.setColorFilter(displayColorTransform); + } } if (!roundRectClip.isEmpty()) { diff --git a/libs/renderengine/tests/Android.bp b/libs/renderengine/tests/Android.bp index d91af1ea85..e66fee117c 100644 --- a/libs/renderengine/tests/Android.bp +++ b/libs/renderengine/tests/Android.bp @@ -47,6 +47,7 @@ cc_test { ], shared_libs: [ + "android.hardware.graphics.composer3-V1-ndk", "libbase", "libcutils", "libEGL", diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index 38ae2fd4ed..24932423f4 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -2405,15 +2405,18 @@ TEST_P(RenderEngineTest, testDisableBlendingBuffer) { TEST_P(RenderEngineTest, testDimming) { if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - return; + GTEST_SKIP(); } + initializeRenderEngine(); + const ui::Dataspace dataspace = ui::Dataspace::V0_SRGB_LINEAR; + const auto displayRect = Rect(3, 1); const renderengine::DisplaySettings display{ .physicalDisplay = displayRect, .clip = displayRect, - .outputDataspace = ui::Dataspace::V0_SRGB_LINEAR, + .outputDataspace = dataspace, .targetLuminanceNits = 1000.f, }; @@ -2432,7 +2435,7 @@ TEST_P(RenderEngineTest, testDimming) { }, }, .alpha = 1.0f, - .sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR, + .sourceDataspace = dataspace, .whitePointNits = 200.f, }; @@ -2447,7 +2450,7 @@ TEST_P(RenderEngineTest, testDimming) { }, }, .alpha = 1.0f, - .sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR, + .sourceDataspace = dataspace, .whitePointNits = 1000.f / 51.f, }; @@ -2462,7 +2465,7 @@ TEST_P(RenderEngineTest, testDimming) { }, }, .alpha = 1.0f, - .sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR, + .sourceDataspace = dataspace, // When the white point is not set for a layer, just ignore it and treat it as the same // as the max layer .whitePointNits = -1.f, @@ -2476,6 +2479,84 @@ TEST_P(RenderEngineTest, testDimming) { expectBufferColor(Rect(2, 0, 3, 1), 51, 0, 0, 255, 1); } +TEST_P(RenderEngineTest, testDimming_inGammaSpace) { + if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { + GTEST_SKIP(); + } + initializeRenderEngine(); + + const ui::Dataspace dataspace = static_cast(ui::Dataspace::STANDARD_BT709 | + ui::Dataspace::TRANSFER_GAMMA2_2 | + ui::Dataspace::RANGE_FULL); + + const auto displayRect = Rect(3, 1); + const renderengine::DisplaySettings display{ + .physicalDisplay = displayRect, + .clip = displayRect, + .outputDataspace = dataspace, + .targetLuminanceNits = 1000.f, + .dimmingStage = aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF, + }; + + const auto greenBuffer = allocateAndFillSourceBuffer(1, 1, ubyte4(0, 255, 0, 255)); + const auto blueBuffer = allocateAndFillSourceBuffer(1, 1, ubyte4(0, 0, 255, 255)); + const auto redBuffer = allocateAndFillSourceBuffer(1, 1, ubyte4(255, 0, 0, 255)); + + const renderengine::LayerSettings greenLayer{ + .geometry.boundaries = FloatRect(0.f, 0.f, 1.f, 1.f), + .source = + renderengine::PixelSource{ + .buffer = + renderengine::Buffer{ + .buffer = greenBuffer, + .usePremultipliedAlpha = true, + }, + }, + .alpha = 1.0f, + .sourceDataspace = dataspace, + .whitePointNits = 200.f, + }; + + const renderengine::LayerSettings blueLayer{ + .geometry.boundaries = FloatRect(1.f, 0.f, 2.f, 1.f), + .source = + renderengine::PixelSource{ + .buffer = + renderengine::Buffer{ + .buffer = blueBuffer, + .usePremultipliedAlpha = true, + }, + }, + .alpha = 1.0f, + .sourceDataspace = dataspace, + .whitePointNits = 1000.f / 51.f, + }; + + const renderengine::LayerSettings redLayer{ + .geometry.boundaries = FloatRect(2.f, 0.f, 3.f, 1.f), + .source = + renderengine::PixelSource{ + .buffer = + renderengine::Buffer{ + .buffer = redBuffer, + .usePremultipliedAlpha = true, + }, + }, + .alpha = 1.0f, + .sourceDataspace = dataspace, + // When the white point is not set for a layer, just ignore it and treat it as the same + // as the max layer + .whitePointNits = -1.f, + }; + + std::vector layers{greenLayer, blueLayer, redLayer}; + invokeDraw(display, layers); + + expectBufferColor(Rect(1, 1), 0, 122, 0, 255, 1); + expectBufferColor(Rect(1, 0, 2, 1), 0, 0, 42, 255, 1); + expectBufferColor(Rect(2, 0, 3, 1), 122, 0, 0, 255, 1); +} + TEST_P(RenderEngineTest, testDimming_withoutTargetLuminance) { initializeRenderEngine(); if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h index e12d1b4fdf..8bb8e9244d 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h @@ -77,7 +77,7 @@ public: virtual void applyChangedTypesToLayers(const ChangedTypes&); virtual void applyDisplayRequests(const DisplayRequests&); virtual void applyLayerRequestsToLayers(const LayerRequests&); - virtual void applyClientTargetRequests(const ClientTargetProperty&, float brightness); + virtual void applyClientTargetRequests(const ClientTargetProperty&); // Internal virtual void setConfiguration(const compositionengine::DisplayCreationArgs&); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h index 66dd825e5b..2438f80eb5 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h @@ -17,6 +17,7 @@ #pragma once #include +#include "aidl/android/hardware/graphics/composer3/DimmingStage.h" #include #include @@ -133,6 +134,10 @@ struct OutputCompositionState { // Brightness of the client target, normalized to display brightness float clientTargetBrightness{1.f}; + // Stage in which the client target should apply dimming + aidl::android::hardware::graphics::composer3::DimmingStage clientTargetDimmingStage{ + aidl::android::hardware::graphics::composer3::DimmingStage::NONE}; + // Display brightness that will take effect this frame. // This is slightly distinct from nits, in that nits cannot be passed to hw composer. std::optional displayBrightness = std::nullopt; diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp index 2165e1d77b..54daf387bc 100644 --- a/services/surfaceflinger/CompositionEngine/src/Display.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp @@ -252,7 +252,7 @@ void Display::chooseCompositionStrategy() { applyChangedTypesToLayers(changes->changedTypes); applyDisplayRequests(changes->displayRequests); applyLayerRequestsToLayers(changes->layerRequests); - applyClientTargetRequests(changes->clientTargetProperty, changes->clientTargetBrightness); + applyClientTargetRequests(changes->clientTargetProperty); } // Determine what type of composition we are doing from the final state @@ -327,16 +327,19 @@ void Display::applyLayerRequestsToLayers(const LayerRequests& layerRequests) { } } -void Display::applyClientTargetRequests(const ClientTargetProperty& clientTargetProperty, - float brightness) { - if (clientTargetProperty.dataspace == ui::Dataspace::UNKNOWN) { +void Display::applyClientTargetRequests(const ClientTargetProperty& clientTargetProperty) { + if (static_cast(clientTargetProperty.clientTargetProperty.dataspace) == + ui::Dataspace::UNKNOWN) { return; } - editState().dataspace = clientTargetProperty.dataspace; - editState().clientTargetBrightness = brightness; - getRenderSurface()->setBufferDataspace(clientTargetProperty.dataspace); - getRenderSurface()->setBufferPixelFormat(clientTargetProperty.pixelFormat); + editState().dataspace = + static_cast(clientTargetProperty.clientTargetProperty.dataspace); + editState().clientTargetBrightness = clientTargetProperty.brightness; + editState().clientTargetDimmingStage = clientTargetProperty.dimmingStage; + getRenderSurface()->setBufferDataspace(editState().dataspace); + getRenderSurface()->setBufferPixelFormat( + static_cast(clientTargetProperty.clientTargetProperty.pixelFormat)); } compositionengine::Output::FrameFences Display::presentAndGetFrameFences() { diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 4e67a63c40..25155b96f0 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -1080,6 +1080,7 @@ std::optional Output::composeSurfaces( mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance(); clientCompositionDisplay.targetLuminanceNits = outputState.clientTargetBrightness * outputState.displayBrightnessNits; + clientCompositionDisplay.dimmingStage = outputState.clientTargetDimmingStage; // Compute the global color transform matrix. clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix; diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp index 5cc0f9734b..d2c945c67b 100644 --- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp @@ -634,8 +634,11 @@ TEST_F(DisplayChooseCompositionStrategyTest, normalOperationWithChanges) { {{nullptr, Composition::CLIENT}}, hal::DisplayRequest::FLIP_CLIENT_TARGET, {{nullptr, hal::LayerRequest::CLEAR_CLIENT_TARGET}}, - {hal::PixelFormat::RGBA_8888, hal::Dataspace::UNKNOWN}, - -1.f, + {.clientTargetProperty = + {aidl::android::hardware::graphics::common::PixelFormat::RGBA_8888, + aidl::android::hardware::graphics::common::Dataspace::UNKNOWN}, + .brightness = -1.f, + .dimmingStage = aidl::android::hardware::graphics::composer3::DimmingStage::NONE}, }; // Since two calls are made to anyLayersRequireClientComposition with different return @@ -822,23 +825,37 @@ TEST_F(DisplayApplyLayerRequestsToLayersTest, appliesDeviceLayerRequests) { using DisplayApplyClientTargetRequests = DisplayWithLayersTestCommon; TEST_F(DisplayApplyLayerRequestsToLayersTest, applyClientTargetRequests) { + static constexpr float kWhitePointNits = 800.f; + Display::ClientTargetProperty clientTargetProperty = { - .pixelFormat = hal::PixelFormat::RGB_565, - .dataspace = hal::Dataspace::STANDARD_BT470M, + .clientTargetProperty = + { + .pixelFormat = + aidl::android::hardware::graphics::common::PixelFormat::RGB_565, + .dataspace = aidl::android::hardware::graphics::common::Dataspace:: + STANDARD_BT470M, + }, + .brightness = kWhitePointNits, + .dimmingStage = aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF, }; - static constexpr float kWhitePointNits = 800.f; - mock::RenderSurface* renderSurface = new StrictMock(); mDisplay->setRenderSurfaceForTest(std::unique_ptr(renderSurface)); - EXPECT_CALL(*renderSurface, setBufferPixelFormat(clientTargetProperty.pixelFormat)); - EXPECT_CALL(*renderSurface, setBufferDataspace(clientTargetProperty.dataspace)); - mDisplay->applyClientTargetRequests(clientTargetProperty, kWhitePointNits); + EXPECT_CALL(*renderSurface, + setBufferPixelFormat(static_cast( + clientTargetProperty.clientTargetProperty.pixelFormat))); + EXPECT_CALL(*renderSurface, + setBufferDataspace(static_cast( + clientTargetProperty.clientTargetProperty.dataspace))); + mDisplay->applyClientTargetRequests(clientTargetProperty); auto& state = mDisplay->getState(); - EXPECT_EQ(clientTargetProperty.dataspace, state.dataspace); + EXPECT_EQ(clientTargetProperty.clientTargetProperty.dataspace, + static_cast(state.dataspace)); EXPECT_EQ(kWhitePointNits, state.clientTargetBrightness); + EXPECT_EQ(aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF, + state.clientTargetDimmingStage); } /* diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index dd3858b4d0..66f37536fc 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -3475,6 +3475,15 @@ struct OutputComposeSurfacesTest_UsesExpectedDisplaySettings : public OutputComp : public CallOrderStateMachineHelper { auto withDisplayBrightnessNits(float nits) { getInstance()->mOutput.mState.displayBrightnessNits = nits; + return nextState(); + } + }; + + struct OutputWithDimmingStage + : public CallOrderStateMachineHelper { + auto withDimmingStage( + aidl::android::hardware::graphics::composer3::DimmingStage dimmingStage) { + getInstance()->mOutput.mState.clientTargetDimmingStage = dimmingStage; return nextState(); } }; @@ -3507,16 +3516,20 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrMixedComposi verify().ifMixedCompositionIs(true) .andIfUsesHdr(true) .withDisplayBrightnessNits(kUnknownLuminance) + .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) .andIfSkipColorTransform(false) - .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip, - .clip = kDefaultOutputViewport, - .maxLuminance = kDefaultMaxLuminance, - .currentLuminanceNits = kDefaultMaxLuminance, - .outputDataspace = kDefaultOutputDataspace, - .colorTransform = kDefaultColorTransformMat, - .deviceHandlesColorTransform = true, - .orientation = kDefaultOutputOrientationFlags, - .targetLuminanceNits = kClientTargetLuminanceNits}) + .thenExpectDisplaySettingsUsed( + {.physicalDisplay = kDefaultOutputDestinationClip, + .clip = kDefaultOutputViewport, + .maxLuminance = kDefaultMaxLuminance, + .currentLuminanceNits = kDefaultMaxLuminance, + .outputDataspace = kDefaultOutputDataspace, + .colorTransform = kDefaultColorTransformMat, + .deviceHandlesColorTransform = true, + .orientation = kDefaultOutputOrientationFlags, + .targetLuminanceNits = kClientTargetLuminanceNits, + .dimmingStage = + aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR}) .execute() .expectAFenceWasReturned(); } @@ -3526,16 +3539,45 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, verify().ifMixedCompositionIs(true) .andIfUsesHdr(true) .withDisplayBrightnessNits(kDisplayLuminance) + .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) + + .andIfSkipColorTransform(false) + .thenExpectDisplaySettingsUsed( + {.physicalDisplay = kDefaultOutputDestinationClip, + .clip = kDefaultOutputViewport, + .maxLuminance = kDefaultMaxLuminance, + .currentLuminanceNits = kDisplayLuminance, + .outputDataspace = kDefaultOutputDataspace, + .colorTransform = kDefaultColorTransformMat, + .deviceHandlesColorTransform = true, + .orientation = kDefaultOutputOrientationFlags, + .targetLuminanceNits = kClientTargetLuminanceNits, + .dimmingStage = + aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR}) + .execute() + .expectAFenceWasReturned(); +} + +TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, + forHdrMixedCompositionWithDimmingStage) { + verify().ifMixedCompositionIs(true) + .andIfUsesHdr(true) + .withDisplayBrightnessNits(kUnknownLuminance) + .withDimmingStage( + aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF) + .andIfSkipColorTransform(false) .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip, .clip = kDefaultOutputViewport, .maxLuminance = kDefaultMaxLuminance, - .currentLuminanceNits = kDisplayLuminance, + .currentLuminanceNits = kDefaultMaxLuminance, .outputDataspace = kDefaultOutputDataspace, .colorTransform = kDefaultColorTransformMat, .deviceHandlesColorTransform = true, .orientation = kDefaultOutputOrientationFlags, - .targetLuminanceNits = kClientTargetLuminanceNits}) + .targetLuminanceNits = kClientTargetLuminanceNits, + .dimmingStage = aidl::android::hardware::graphics:: + composer3::DimmingStage::GAMMA_OETF}) .execute() .expectAFenceWasReturned(); } @@ -3544,16 +3586,21 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrMixedComp verify().ifMixedCompositionIs(true) .andIfUsesHdr(false) .withDisplayBrightnessNits(kUnknownLuminance) + .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) + .andIfSkipColorTransform(false) - .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip, - .clip = kDefaultOutputViewport, - .maxLuminance = kDefaultMaxLuminance, - .currentLuminanceNits = kDefaultMaxLuminance, - .outputDataspace = kDefaultOutputDataspace, - .colorTransform = kDefaultColorTransformMat, - .deviceHandlesColorTransform = true, - .orientation = kDefaultOutputOrientationFlags, - .targetLuminanceNits = kClientTargetLuminanceNits}) + .thenExpectDisplaySettingsUsed( + {.physicalDisplay = kDefaultOutputDestinationClip, + .clip = kDefaultOutputViewport, + .maxLuminance = kDefaultMaxLuminance, + .currentLuminanceNits = kDefaultMaxLuminance, + .outputDataspace = kDefaultOutputDataspace, + .colorTransform = kDefaultColorTransformMat, + .deviceHandlesColorTransform = true, + .orientation = kDefaultOutputOrientationFlags, + .targetLuminanceNits = kClientTargetLuminanceNits, + .dimmingStage = + aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR}) .execute() .expectAFenceWasReturned(); } @@ -3562,16 +3609,21 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrOnlyClientCo verify().ifMixedCompositionIs(false) .andIfUsesHdr(true) .withDisplayBrightnessNits(kUnknownLuminance) + .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) + .andIfSkipColorTransform(false) - .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip, - .clip = kDefaultOutputViewport, - .maxLuminance = kDefaultMaxLuminance, - .currentLuminanceNits = kDefaultMaxLuminance, - .outputDataspace = kDefaultOutputDataspace, - .colorTransform = kDefaultColorTransformMat, - .deviceHandlesColorTransform = false, - .orientation = kDefaultOutputOrientationFlags, - .targetLuminanceNits = kClientTargetLuminanceNits}) + .thenExpectDisplaySettingsUsed( + {.physicalDisplay = kDefaultOutputDestinationClip, + .clip = kDefaultOutputViewport, + .maxLuminance = kDefaultMaxLuminance, + .currentLuminanceNits = kDefaultMaxLuminance, + .outputDataspace = kDefaultOutputDataspace, + .colorTransform = kDefaultColorTransformMat, + .deviceHandlesColorTransform = false, + .orientation = kDefaultOutputOrientationFlags, + .targetLuminanceNits = kClientTargetLuminanceNits, + .dimmingStage = + aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR}) .execute() .expectAFenceWasReturned(); } @@ -3580,16 +3632,21 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrOnlyClien verify().ifMixedCompositionIs(false) .andIfUsesHdr(false) .withDisplayBrightnessNits(kUnknownLuminance) + .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) + .andIfSkipColorTransform(false) - .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip, - .clip = kDefaultOutputViewport, - .maxLuminance = kDefaultMaxLuminance, - .currentLuminanceNits = kDefaultMaxLuminance, - .outputDataspace = kDefaultOutputDataspace, - .colorTransform = kDefaultColorTransformMat, - .deviceHandlesColorTransform = false, - .orientation = kDefaultOutputOrientationFlags, - .targetLuminanceNits = kClientTargetLuminanceNits}) + .thenExpectDisplaySettingsUsed( + {.physicalDisplay = kDefaultOutputDestinationClip, + .clip = kDefaultOutputViewport, + .maxLuminance = kDefaultMaxLuminance, + .currentLuminanceNits = kDefaultMaxLuminance, + .outputDataspace = kDefaultOutputDataspace, + .colorTransform = kDefaultColorTransformMat, + .deviceHandlesColorTransform = false, + .orientation = kDefaultOutputOrientationFlags, + .targetLuminanceNits = kClientTargetLuminanceNits, + .dimmingStage = + aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR}) .execute() .expectAFenceWasReturned(); } @@ -3599,16 +3656,21 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, verify().ifMixedCompositionIs(false) .andIfUsesHdr(true) .withDisplayBrightnessNits(kUnknownLuminance) + .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) + .andIfSkipColorTransform(true) - .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip, - .clip = kDefaultOutputViewport, - .maxLuminance = kDefaultMaxLuminance, - .currentLuminanceNits = kDefaultMaxLuminance, - .outputDataspace = kDefaultOutputDataspace, - .colorTransform = kDefaultColorTransformMat, - .deviceHandlesColorTransform = true, - .orientation = kDefaultOutputOrientationFlags, - .targetLuminanceNits = kClientTargetLuminanceNits}) + .thenExpectDisplaySettingsUsed( + {.physicalDisplay = kDefaultOutputDestinationClip, + .clip = kDefaultOutputViewport, + .maxLuminance = kDefaultMaxLuminance, + .currentLuminanceNits = kDefaultMaxLuminance, + .outputDataspace = kDefaultOutputDataspace, + .colorTransform = kDefaultColorTransformMat, + .deviceHandlesColorTransform = true, + .orientation = kDefaultOutputOrientationFlags, + .targetLuminanceNits = kClientTargetLuminanceNits, + .dimmingStage = + aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR}) .execute() .expectAFenceWasReturned(); } diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp index 297a776753..117540d312 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp @@ -41,6 +41,7 @@ using hardware::Return; using aidl::android::hardware::graphics::composer3::BnComposerCallback; using aidl::android::hardware::graphics::composer3::Capability; +using aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness; using aidl::android::hardware::graphics::composer3::PowerMode; using aidl::android::hardware::graphics::composer3::VirtualDisplay; @@ -155,15 +156,6 @@ VsyncPeriodChangeTimeline translate(AidlVsyncPeriodChangeTimeline x) { .refreshTimeNanos = x.refreshTimeNanos, }; } - -template <> -IComposerClient::ClientTargetProperty translate(ClientTargetProperty x) { - return IComposerClient::ClientTargetProperty{ - .pixelFormat = translate(x.pixelFormat), - .dataspace = translate(x.dataspace), - }; -} - mat4 makeMat4(std::vector in) { return mat4(static_cast(in.data())); } @@ -1082,12 +1074,8 @@ Error AidlComposer::getPreferredBootDisplayConfig(Display display, Config* confi } Error AidlComposer::getClientTargetProperty( - Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty, - float* outBrightness) { - const auto property = mReader.takeClientTargetProperty(translate(display)); - *outClientTargetProperty = - translate(property.clientTargetProperty); - *outBrightness = property.brightness; + Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) { + *outClientTargetProperty = mReader.takeClientTargetProperty(translate(display)); return Error::NONE; } diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h index 28ff167811..00990244e3 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h @@ -208,9 +208,10 @@ public: bool mandatory, const std::vector& value) override; V2_4::Error getLayerGenericMetadataKeys( std::vector* outKeys) override; - Error getClientTargetProperty(Display display, - IComposerClient::ClientTargetProperty* outClientTargetProperty, - float* outBrightness) override; + Error getClientTargetProperty( + Display display, + aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness* + outClientTargetProperty) override; // AIDL Composer HAL Error setLayerBrightness(Display display, Layer layer, float brightness) override; diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h index 2dc0830209..fd26e0bfcc 100644 --- a/services/surfaceflinger/DisplayHardware/ComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -264,8 +265,7 @@ public: std::vector* outKeys) = 0; virtual Error getClientTargetProperty( - Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty, - float* outBrightness) = 0; + Display display, V3_0::ClientTargetPropertyWithBrightness* outClientTargetProperty) = 0; // AIDL Composer virtual Error setLayerBrightness(Display display, Layer layer, float brightness) = 0; diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp index c0432bf62a..d8f233441d 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.cpp +++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp @@ -585,10 +585,10 @@ Error Display::setContentType(ContentType contentType) { return static_cast(intError); } -Error Display::getClientTargetProperty(ClientTargetProperty* outClientTargetProperty, - float* outWhitePointNits) { - const auto error = - mComposer.getClientTargetProperty(mId, outClientTargetProperty, outWhitePointNits); +Error Display::getClientTargetProperty( + aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness* + outClientTargetProperty) { + const auto error = mComposer.getClientTargetProperty(mId, outClientTargetProperty); return static_cast(error); } diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h index 4141beb0a6..6d54dd3f05 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.h +++ b/services/surfaceflinger/DisplayHardware/HWC2.h @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -160,7 +161,8 @@ public: std::vector*) const = 0; [[nodiscard]] virtual hal::Error setContentType(hal::ContentType) = 0; [[nodiscard]] virtual hal::Error getClientTargetProperty( - hal::ClientTargetProperty* outClientTargetProperty, float* outWhitePointNits) = 0; + aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness* + outClientTargetProperty) = 0; [[nodiscard]] virtual hal::Error getDisplayDecorationSupport( std::optional* support) = 0; @@ -238,8 +240,9 @@ public: hal::Error getSupportedContentTypes( std::vector* outSupportedContentTypes) const override; hal::Error setContentType(hal::ContentType) override; - hal::Error getClientTargetProperty(hal::ClientTargetProperty* outClientTargetProperty, - float* outWhitePointNits) override; + hal::Error getClientTargetProperty( + aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness* + outClientTargetProperty) override; hal::Error getDisplayDecorationSupport( std::optional* support) override; diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp index 459291a1b7..9580964588 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -479,12 +479,11 @@ status_t HWComposer::getDeviceCompositionChanges( RETURN_IF_HWC_ERROR_FOR("getRequests", error, displayId, BAD_INDEX); DeviceRequestedChanges::ClientTargetProperty clientTargetProperty; - float brightness = 1.f; - error = hwcDisplay->getClientTargetProperty(&clientTargetProperty, &brightness); + error = hwcDisplay->getClientTargetProperty(&clientTargetProperty); outChanges->emplace(DeviceRequestedChanges{std::move(changedTypes), std::move(displayRequests), std::move(layerRequests), - std::move(clientTargetProperty), brightness}); + std::move(clientTargetProperty)}); error = hwcDisplay->acceptChanges(); RETURN_IF_HWC_ERROR_FOR("acceptChanges", error, displayId, BAD_INDEX); diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h index 0e15a7c575..389c3be211 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.h +++ b/services/surfaceflinger/DisplayHardware/HWComposer.h @@ -45,6 +45,7 @@ #include #include +#include #include #include @@ -78,7 +79,8 @@ public: using ChangedTypes = std::unordered_map; - using ClientTargetProperty = hal::ClientTargetProperty; + using ClientTargetProperty = + aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness; using DisplayRequests = hal::DisplayRequest; using LayerRequests = std::unordered_map; @@ -86,7 +88,6 @@ public: DisplayRequests displayRequests; LayerRequests layerRequests; ClientTargetProperty clientTargetProperty; - float clientTargetBrightness; }; struct HWCDisplayMode { diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp index d9af55396e..fd456ffe66 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp @@ -37,6 +37,8 @@ #include using aidl::android::hardware::graphics::composer3::Capability; +using aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness; +using aidl::android::hardware::graphics::composer3::DimmingStage; using aidl::android::hardware::graphics::composer3::DisplayCapability; namespace android { @@ -1302,10 +1304,17 @@ Error HidlComposer::getPreferredBootDisplayConfig(Display /*displayId*/, Config* } Error HidlComposer::getClientTargetProperty( - Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty, - float* outBrightness) { - mReader.takeClientTargetProperty(display, outClientTargetProperty); - *outBrightness = 1.f; + Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) { + IComposerClient::ClientTargetProperty property; + mReader.takeClientTargetProperty(display, &property); + outClientTargetProperty->display = display; + outClientTargetProperty->clientTargetProperty.dataspace = + static_cast<::aidl::android::hardware::graphics::common::Dataspace>(property.dataspace); + outClientTargetProperty->clientTargetProperty.pixelFormat = + static_cast<::aidl::android::hardware::graphics::common::PixelFormat>( + property.pixelFormat); + outClientTargetProperty->brightness = 1.f; + outClientTargetProperty->dimmingStage = DimmingStage::NONE; return Error::NONE; } diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h index 5869ae571b..68c1c1503e 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h @@ -317,9 +317,10 @@ public: bool mandatory, const std::vector& value) override; V2_4::Error getLayerGenericMetadataKeys( std::vector* outKeys) override; - Error getClientTargetProperty(Display display, - IComposerClient::ClientTargetProperty* outClientTargetProperty, - float* outBrightness) override; + Error getClientTargetProperty( + Display display, + aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness* + outClientTargetProperty) override; // AIDL Composer HAL Error setLayerBrightness(Display display, Layer layer, float brightness) override; diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h index c1d41bbc28..e215550669 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h @@ -151,8 +151,10 @@ public: const std::vector&)); MOCK_METHOD1(getLayerGenericMetadataKeys, V2_4::Error(std::vector*)); - MOCK_METHOD3(getClientTargetProperty, - Error(Display, IComposerClient::ClientTargetProperty*, float*)); + MOCK_METHOD2(getClientTargetProperty, + Error(Display, + aidl::android::hardware::graphics::composer3:: + ClientTargetPropertyWithBrightness*)); MOCK_METHOD3(setLayerBrightness, Error(Display, Layer, float)); MOCK_METHOD3(setLayerBlockingRegion, Error(Display, Layer, const std::vector&)); diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h index ac2ab199c9..4c2aa34c87 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h @@ -93,8 +93,10 @@ public: MOCK_METHOD(hal::Error, getSupportedContentTypes, (std::vector *), (const, override)); MOCK_METHOD(hal::Error, setContentType, (hal::ContentType), (override)); - MOCK_METHOD(hal::Error, getClientTargetProperty, (hal::ClientTargetProperty *, float *), - (override)); + MOCK_METHOD( + hal::Error, getClientTargetProperty, + (aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness *), + (override)); MOCK_METHOD( hal::Error, getDisplayDecorationSupport, (std::optional *), -- GitLab From 5df0e9dcfec953a030e2ad75f826d95a7fc73165 Mon Sep 17 00:00:00 2001 From: Hector Dearman Date: Wed, 30 Mar 2022 18:26:02 +0000 Subject: [PATCH 0036/1310] Add printk/console to chmods Bug: 219954846 Change-Id: Iddc5e3f66348e4cd37dbbaf3a0b7b15b5881522d --- cmds/atrace/atrace.rc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc index 1c3a4f201c..13c09a3d87 100644 --- a/cmds/atrace/atrace.rc +++ b/cmds/atrace/atrace.rc @@ -179,6 +179,8 @@ on late-init chmod 0666 /sys/kernel/tracing/events/clk/clk_enable/enable chmod 0666 /sys/kernel/debug/tracing/events/clk/clk_set_rate/enable chmod 0666 /sys/kernel/tracing/events/clk/clk_set_rate/enable + chmod 0666 /sys/kernel/debug/tracing/events/printk/console/enable + chmod 0666 /sys/kernel/tracing/events/printk/console/enable # disk chmod 0666 /sys/kernel/tracing/events/f2fs/f2fs_get_data_block/enable -- GitLab From 38603fd683c5c0910db211d407172475cdf2f579 Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Mon, 21 Feb 2022 14:32:54 -0800 Subject: [PATCH 0037/1310] Convert DynamicDisplayInfo to AIDL parcelable And migrate related ISurfaceComposer::getDynamicDisplayInfo() method to AIDL. (1) add android::gui::DynamicDisplayInfo etc. for serialization (2) remove serialization code from the orignal DynamicDisplayInfo and DisplayMode and HdrCapabilities classes (3) convert between ui::DynamicDisplayInfo and gui::DynamicDisplayInfo Bug: 220074970 Test: manual Change-Id: If3c81c5fd006c281f6d38766bf415a971b0a1925 --- libs/gui/ISurfaceComposer.cpp | 21 ----- libs/gui/Surface.cpp | 9 +- libs/gui/SurfaceComposerClient.cpp | 49 ++++++++++- libs/gui/aidl/android/gui/DisplayMode.aidl | 36 ++++++++ .../aidl/android/gui/DynamicDisplayInfo.aidl | 45 ++++++++++ ...splayModelId.aidl => HdrCapabilities.aidl} | 11 +-- .../aidl/android/gui/ISurfaceComposer.aidl | 6 ++ libs/gui/include/gui/ISurfaceComposer.h | 7 +- libs/gui/tests/Surface_test.cpp | 9 +- libs/ui/Android.bp | 2 - libs/ui/DisplayMode.cpp | 69 --------------- libs/ui/DynamicDisplayInfo.cpp | 43 ---------- libs/ui/HdrCapabilities.cpp | 86 ------------------- libs/ui/include/ui/DisplayMode.h | 7 +- libs/ui/include/ui/DynamicDisplayInfo.h | 8 +- libs/ui/include/ui/HdrCapabilities.h | 10 +-- services/surfaceflinger/SurfaceFlinger.cpp | 53 +++++++++++- services/surfaceflinger/SurfaceFlinger.h | 4 +- 18 files changed, 209 insertions(+), 266 deletions(-) create mode 100644 libs/gui/aidl/android/gui/DisplayMode.aidl create mode 100644 libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl rename libs/gui/aidl/android/gui/{DisplayModelId.aidl => HdrCapabilities.aidl} (70%) delete mode 100644 libs/ui/DisplayMode.cpp delete mode 100644 libs/ui/HdrCapabilities.cpp diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index c5de09a4bf..a9473d4997 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -225,17 +225,6 @@ public: return result; } - status_t getDynamicDisplayInfo(const sp& display, - ui::DynamicDisplayInfo* info) override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - data.writeStrongBinder(display); - remote()->transact(BnSurfaceComposer::GET_DYNAMIC_DISPLAY_INFO, data, &reply); - const status_t result = reply.readInt32(); - if (result != NO_ERROR) return result; - return reply.read(*info); - } - status_t getDisplayNativePrimaries(const sp& display, ui::DisplayPrimaries& primaries) override { Parcel data, reply; @@ -1133,16 +1122,6 @@ status_t BnSurfaceComposer::onTransact( reply->writeStrongBinder(IInterface::asBinder(connection)); return NO_ERROR; } - case GET_DYNAMIC_DISPLAY_INFO: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - ui::DynamicDisplayInfo info; - const sp display = data.readStrongBinder(); - const status_t result = getDynamicDisplayInfo(display, &info); - SAFE_PARCEL(reply->writeInt32, result); - if (result != NO_ERROR) return result; - SAFE_PARCEL(reply->write, info); - return NO_ERROR; - } case GET_DISPLAY_NATIVE_PRIMARIES: { CHECK_INTERFACE(ISurfaceComposer, data, reply); ui::DisplayPrimaries primaries; diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index 0f0a5c8504..bee820dfb1 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -366,12 +366,13 @@ status_t Surface::getHdrSupport(bool* supported) { return NAME_NOT_FOUND; } - ui::DynamicDisplayInfo info; - if (status_t err = composerService()->getDynamicDisplayInfo(display, &info); err != NO_ERROR) { - return err; + gui::DynamicDisplayInfo info; + if (binder::Status status = composerServiceAIDL()->getDynamicDisplayInfo(display, &info); + !status.isOk()) { + return status.transactionError(); } - *supported = !info.hdrCapabilities.getSupportedHdrTypes().empty(); + *supported = !info.hdrCapabilities.supportedHdrTypes.empty(); return NO_ERROR; } diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index b4979d9276..6702fef45e 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2187,8 +2187,53 @@ status_t SurfaceComposerClient::getStaticDisplayInfo(const sp& display, } status_t SurfaceComposerClient::getDynamicDisplayInfo(const sp& display, - ui::DynamicDisplayInfo* info) { - return ComposerService::getComposerService()->getDynamicDisplayInfo(display, info); + ui::DynamicDisplayInfo* outInfo) { + gui::DynamicDisplayInfo ginfo; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getDynamicDisplayInfo(display, &ginfo); + if (status.isOk()) { + // convert gui::DynamicDisplayInfo to ui::DynamicDisplayInfo + outInfo->supportedDisplayModes.clear(); + outInfo->supportedDisplayModes.reserve(ginfo.supportedDisplayModes.size()); + for (const auto& mode : ginfo.supportedDisplayModes) { + ui::DisplayMode outMode; + outMode.id = mode.id; + outMode.resolution.width = mode.resolution.width; + outMode.resolution.height = mode.resolution.height; + outMode.xDpi = mode.xDpi; + outMode.yDpi = mode.yDpi; + outMode.refreshRate = mode.refreshRate; + outMode.appVsyncOffset = mode.appVsyncOffset; + outMode.sfVsyncOffset = mode.sfVsyncOffset; + outMode.presentationDeadline = mode.presentationDeadline; + outMode.group = mode.group; + outInfo->supportedDisplayModes.push_back(outMode); + } + + outInfo->activeDisplayModeId = ginfo.activeDisplayModeId; + + outInfo->supportedColorModes.clear(); + outInfo->supportedColorModes.reserve(ginfo.supportedColorModes.size()); + for (const auto& cmode : ginfo.supportedColorModes) { + outInfo->supportedColorModes.push_back(static_cast(cmode)); + } + + outInfo->activeColorMode = static_cast(ginfo.activeColorMode); + + std::vector types; + types.reserve(ginfo.hdrCapabilities.supportedHdrTypes.size()); + for (const auto& hdr : ginfo.hdrCapabilities.supportedHdrTypes) { + types.push_back(static_cast(hdr)); + } + outInfo->hdrCapabilities = HdrCapabilities(types, ginfo.hdrCapabilities.maxLuminance, + ginfo.hdrCapabilities.maxAverageLuminance, + ginfo.hdrCapabilities.minLuminance); + + outInfo->autoLowLatencyModeSupported = ginfo.autoLowLatencyModeSupported; + outInfo->gameContentTypeSupported = ginfo.gameContentTypeSupported; + outInfo->preferredBootDisplayMode = ginfo.preferredBootDisplayMode; + } + return status.transactionError(); } status_t SurfaceComposerClient::getActiveDisplayMode(const sp& display, diff --git a/libs/gui/aidl/android/gui/DisplayMode.aidl b/libs/gui/aidl/android/gui/DisplayMode.aidl new file mode 100644 index 0000000000..3cd77f82d7 --- /dev/null +++ b/libs/gui/aidl/android/gui/DisplayMode.aidl @@ -0,0 +1,36 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +import android.gui.Size; + +// Mode supported by physical display. +// Make sure to sync with libui DisplayMode.h + +/** @hide */ +parcelable DisplayMode { + int id; + Size resolution; + float xDpi = 0.0f; + float yDpi = 0.0f; + + float refreshRate = 0.0f; + long appVsyncOffset = 0; + long sfVsyncOffset = 0; + long presentationDeadline = 0; + int group = -1; +} diff --git a/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl b/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl new file mode 100644 index 0000000000..57e6081e27 --- /dev/null +++ b/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl @@ -0,0 +1,45 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +import android.gui.DisplayMode; +import android.gui.HdrCapabilities; + +// Information about a physical display which may change on hotplug reconnect. +// Make sure to sync with libui DynamicDisplayInfo.h + +/** @hide */ +parcelable DynamicDisplayInfo { + List supportedDisplayModes; + + int activeDisplayModeId; + + int[] supportedColorModes; + int activeColorMode; + HdrCapabilities hdrCapabilities; + + // True if the display reports support for HDMI 2.1 Auto Low Latency Mode. + // For more information, see the HDMI 2.1 specification. + boolean autoLowLatencyModeSupported; + + // True if the display reports support for Game Content Type. + // For more information, see the HDMI 1.4 specification. + boolean gameContentTypeSupported; + + // The boot display mode preferred by the implementation. + int preferredBootDisplayMode; +} diff --git a/libs/gui/aidl/android/gui/DisplayModelId.aidl b/libs/gui/aidl/android/gui/HdrCapabilities.aidl similarity index 70% rename from libs/gui/aidl/android/gui/DisplayModelId.aidl rename to libs/gui/aidl/android/gui/HdrCapabilities.aidl index d75777b815..9d06da9f27 100644 --- a/libs/gui/aidl/android/gui/DisplayModelId.aidl +++ b/libs/gui/aidl/android/gui/HdrCapabilities.aidl @@ -16,11 +16,12 @@ package android.gui; -// Product-specific information about the display or the directly connected device on the -// display chain. For example, if the display is transitively connected, this field may contain -// product information about the intermediate device. +// Make sure to sync with libui HdrCapabilities.h /** @hide */ -parcelable DisplayModelId { - int id; +parcelable HdrCapabilities { + int[] supportedHdrTypes; + float maxLuminance; + float maxAverageLuminance; + float minLuminance; } diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index f6cd5ec44f..175007d380 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -21,6 +21,7 @@ import android.gui.DisplayBrightness; import android.gui.DisplayState; import android.gui.DisplayStatInfo; import android.gui.StaticDisplayInfo; +import android.gui.DynamicDisplayInfo; import android.gui.IHdrLayerInfoListener; import android.gui.LayerCaptureArgs; import android.gui.IScreenCaptureListener; @@ -69,6 +70,11 @@ interface ISurfaceComposer { */ StaticDisplayInfo getStaticDisplayInfo(IBinder display); + /** + * Gets dynamic information about given physical display. + */ + DynamicDisplayInfo getDynamicDisplayInfo(IBinder display); + /** * Clears the user-preferred display mode. The device should now boot in system preferred * display mode. diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index 29e38b880b..ed8254ae8b 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -160,11 +160,6 @@ public: virtual status_t getSupportedFrameTimestamps( std::vector* outSupported) const = 0; - /** - * Gets dynamic information about given physical display. - */ - virtual status_t getDynamicDisplayInfo(const sp& display, ui::DynamicDisplayInfo*) = 0; - virtual status_t getDisplayNativePrimaries(const sp& display, ui::DisplayPrimaries& primaries) = 0; virtual status_t setActiveColorMode(const sp& display, @@ -490,7 +485,7 @@ public: ADD_TRANSACTION_TRACE_LISTENER, GET_GPU_CONTEXT_PRIORITY, GET_MAX_ACQUIRED_BUFFER_COUNT, - GET_DYNAMIC_DISPLAY_INFO, + GET_DYNAMIC_DISPLAY_INFO, // Deprecated. Autogenerated by .aidl now. ADD_FPS_LISTENER, REMOVE_FPS_LISTENER, OVERRIDE_HDR_TYPES, diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index e02299d123..bf1b43d2c0 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -733,10 +733,6 @@ public: return NO_ERROR; } - status_t getDynamicDisplayInfo(const sp& /*display*/, - ui::DynamicDisplayInfo*) override { - return NO_ERROR; - } status_t getDisplayNativePrimaries(const sp& /*display*/, ui::DisplayPrimaries& /*primaries*/) override { return NO_ERROR; @@ -931,6 +927,11 @@ public: return binder::Status::ok(); } + binder::Status getDynamicDisplayInfo(const sp& /*display*/, + gui::DynamicDisplayInfo* /*outInfo*/) override { + return binder::Status::ok(); + } + binder::Status clearBootDisplayMode(const sp& /*display*/) override { return binder::Status::ok(); } diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp index 4af38c5218..0a85fe02e2 100644 --- a/libs/ui/Android.bp +++ b/libs/ui/Android.bp @@ -129,7 +129,6 @@ cc_library_shared { "DebugUtils.cpp", "DeviceProductInfo.cpp", "DisplayIdentification.cpp", - "DisplayMode.cpp", "DynamicDisplayInfo.cpp", "Fence.cpp", "FenceTime.cpp", @@ -141,7 +140,6 @@ cc_library_shared { "GraphicBuffer.cpp", "GraphicBufferAllocator.cpp", "GraphicBufferMapper.cpp", - "HdrCapabilities.cpp", "PixelFormat.cpp", "PublicFormat.cpp", "StaticAsserts.cpp", diff --git a/libs/ui/DisplayMode.cpp b/libs/ui/DisplayMode.cpp deleted file mode 100644 index cf05dbfb05..0000000000 --- a/libs/ui/DisplayMode.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2021 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 - -#include - -#define RETURN_IF_ERROR(op) \ - if (const status_t status = (op); status != OK) return status; - -namespace android::ui { - -size_t DisplayMode::getFlattenedSize() const { - return FlattenableHelpers::getFlattenedSize(id) + - FlattenableHelpers::getFlattenedSize(resolution) + - FlattenableHelpers::getFlattenedSize(xDpi) + - FlattenableHelpers::getFlattenedSize(yDpi) + - FlattenableHelpers::getFlattenedSize(refreshRate) + - FlattenableHelpers::getFlattenedSize(appVsyncOffset) + - FlattenableHelpers::getFlattenedSize(sfVsyncOffset) + - FlattenableHelpers::getFlattenedSize(presentationDeadline) + - FlattenableHelpers::getFlattenedSize(group); -} - -status_t DisplayMode::flatten(void* buffer, size_t size) const { - if (size < getFlattenedSize()) { - return NO_MEMORY; - } - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, id)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, resolution)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, xDpi)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, yDpi)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, refreshRate)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, appVsyncOffset)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, sfVsyncOffset)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, presentationDeadline)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, group)); - return OK; -} - -status_t DisplayMode::unflatten(const void* buffer, size_t size) { - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &id)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &resolution)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &xDpi)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &yDpi)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &refreshRate)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &appVsyncOffset)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &sfVsyncOffset)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &presentationDeadline)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &group)); - return OK; -} - -} // namespace android::ui diff --git a/libs/ui/DynamicDisplayInfo.cpp b/libs/ui/DynamicDisplayInfo.cpp index 78ba9965b6..f5feea925d 100644 --- a/libs/ui/DynamicDisplayInfo.cpp +++ b/libs/ui/DynamicDisplayInfo.cpp @@ -18,11 +18,6 @@ #include -#include - -#define RETURN_IF_ERROR(op) \ - if (const status_t status = (op); status != OK) return status; - namespace android::ui { std::optional DynamicDisplayInfo::getActiveDisplayMode() const { @@ -34,42 +29,4 @@ std::optional DynamicDisplayInfo::getActiveDisplayMode() const return {}; } -size_t DynamicDisplayInfo::getFlattenedSize() const { - return FlattenableHelpers::getFlattenedSize(supportedDisplayModes) + - FlattenableHelpers::getFlattenedSize(activeDisplayModeId) + - FlattenableHelpers::getFlattenedSize(supportedColorModes) + - FlattenableHelpers::getFlattenedSize(activeColorMode) + - FlattenableHelpers::getFlattenedSize(hdrCapabilities) + - FlattenableHelpers::getFlattenedSize(autoLowLatencyModeSupported) + - FlattenableHelpers::getFlattenedSize(gameContentTypeSupported) + - FlattenableHelpers::getFlattenedSize(preferredBootDisplayMode); -} - -status_t DynamicDisplayInfo::flatten(void* buffer, size_t size) const { - if (size < getFlattenedSize()) { - return NO_MEMORY; - } - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, supportedDisplayModes)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, activeDisplayModeId)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, supportedColorModes)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, activeColorMode)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, hdrCapabilities)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, autoLowLatencyModeSupported)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, gameContentTypeSupported)); - RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, preferredBootDisplayMode)); - return OK; -} - -status_t DynamicDisplayInfo::unflatten(const void* buffer, size_t size) { - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &supportedDisplayModes)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &activeDisplayModeId)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &supportedColorModes)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &activeColorMode)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &hdrCapabilities)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &autoLowLatencyModeSupported)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &gameContentTypeSupported)); - RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &preferredBootDisplayMode)); - return OK; -} - } // namespace android::ui diff --git a/libs/ui/HdrCapabilities.cpp b/libs/ui/HdrCapabilities.cpp deleted file mode 100644 index aec2fac780..0000000000 --- a/libs/ui/HdrCapabilities.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2016 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 - -namespace android { - -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wundefined-reinterpret-cast" -#endif - -size_t HdrCapabilities::getFlattenedSize() const { - return sizeof(mMaxLuminance) + - sizeof(mMaxAverageLuminance) + - sizeof(mMinLuminance) + - sizeof(int32_t) + - mSupportedHdrTypes.size() * sizeof(ui::Hdr); -} - -status_t HdrCapabilities::flatten(void* buffer, size_t size) const { - - if (size < getFlattenedSize()) { - return NO_MEMORY; - } - - int32_t* const buf = static_cast(buffer); - reinterpret_cast(buf[0]) = mMaxLuminance; - reinterpret_cast(buf[1]) = mMaxAverageLuminance; - reinterpret_cast(buf[2]) = mMinLuminance; - buf[3] = static_cast(mSupportedHdrTypes.size()); - for (size_t i = 0, c = mSupportedHdrTypes.size(); i < c; ++i) { - buf[4 + i] = static_cast(mSupportedHdrTypes[i]); - } - return NO_ERROR; -} - -status_t HdrCapabilities::unflatten(void const* buffer, size_t size) { - - size_t minSize = sizeof(mMaxLuminance) + - sizeof(mMaxAverageLuminance) + - sizeof(mMinLuminance) + - sizeof(int32_t); - - if (size < minSize) { - return NO_MEMORY; - } - - int32_t const * const buf = static_cast(buffer); - const size_t itemCount = size_t(buf[3]); - - // check the buffer is large enough - if (size < minSize + itemCount * sizeof(int32_t)) { - return BAD_VALUE; - } - - mMaxLuminance = reinterpret_cast(buf[0]); - mMaxAverageLuminance = reinterpret_cast(buf[1]); - mMinLuminance = reinterpret_cast(buf[2]); - if (itemCount) { - mSupportedHdrTypes.resize(itemCount); - for (size_t i = 0; i < itemCount; ++i) { - mSupportedHdrTypes[i] = static_cast(buf[4 + i]); - } - } - return NO_ERROR; -} - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif - -} // namespace android diff --git a/libs/ui/include/ui/DisplayMode.h b/libs/ui/include/ui/DisplayMode.h index 56f68e7bb2..a2791a6d44 100644 --- a/libs/ui/include/ui/DisplayMode.h +++ b/libs/ui/include/ui/DisplayMode.h @@ -29,7 +29,7 @@ namespace android::ui { using DisplayModeId = int32_t; // Mode supported by physical display. -struct DisplayMode : LightFlattenable { +struct DisplayMode { DisplayModeId id; ui::Size resolution; float xDpi = 0; @@ -40,11 +40,6 @@ struct DisplayMode : LightFlattenable { nsecs_t sfVsyncOffset = 0; nsecs_t presentationDeadline = 0; int32_t group = -1; - - bool isFixedSize() const { return false; } - size_t getFlattenedSize() const; - status_t flatten(void* buffer, size_t size) const; - status_t unflatten(const void* buffer, size_t size); }; } // namespace android::ui diff --git a/libs/ui/include/ui/DynamicDisplayInfo.h b/libs/ui/include/ui/DynamicDisplayInfo.h index ce75a65214..8c9fe4c311 100644 --- a/libs/ui/include/ui/DynamicDisplayInfo.h +++ b/libs/ui/include/ui/DynamicDisplayInfo.h @@ -24,12 +24,11 @@ #include #include -#include namespace android::ui { // Information about a physical display which may change on hotplug reconnect. -struct DynamicDisplayInfo : LightFlattenable { +struct DynamicDisplayInfo { std::vector supportedDisplayModes; // This struct is going to be serialized over binder, so @@ -53,11 +52,6 @@ struct DynamicDisplayInfo : LightFlattenable { ui::DisplayModeId preferredBootDisplayMode; std::optional getActiveDisplayMode() const; - - bool isFixedSize() const { return false; } - size_t getFlattenedSize() const; - status_t flatten(void* buffer, size_t size) const; - status_t unflatten(const void* buffer, size_t size); }; } // namespace android::ui diff --git a/libs/ui/include/ui/HdrCapabilities.h b/libs/ui/include/ui/HdrCapabilities.h index 813addeca6..ae54223585 100644 --- a/libs/ui/include/ui/HdrCapabilities.h +++ b/libs/ui/include/ui/HdrCapabilities.h @@ -22,12 +22,10 @@ #include #include -#include namespace android { -class HdrCapabilities : public LightFlattenable -{ +class HdrCapabilities { public: HdrCapabilities(const std::vector& types, float maxLuminance, float maxAverageLuminance, float minLuminance) @@ -49,12 +47,6 @@ public: float getDesiredMaxAverageLuminance() const { return mMaxAverageLuminance; } float getDesiredMinLuminance() const { return mMinLuminance; } - // Flattenable protocol - bool isFixedSize() const { return false; } - size_t getFlattenedSize() const; - status_t flatten(void* buffer, size_t size) const; - status_t unflatten(void const* buffer, size_t size); - private: std::vector mSupportedHdrTypes; float mMaxLuminance; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 6b312db784..1080bb675d 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5517,7 +5517,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case GET_ACTIVE_DISPLAY_MODE: case GET_DISPLAY_COLOR_MODES: case GET_DISPLAY_NATIVE_PRIMARIES: - case GET_DYNAMIC_DISPLAY_INFO: case GET_DISPLAY_MODES: case GET_SUPPORTED_FRAME_TIMESTAMPS: // Calling setTransactionState is safe, because you need to have been @@ -5591,6 +5590,7 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case GET_DISPLAY_STATE: case GET_DISPLAY_STATS: case GET_STATIC_DISPLAY_INFO: + case GET_DYNAMIC_DISPLAY_INFO: case CLEAR_BOOT_DISPLAY_MODE: case GET_BOOT_DISPLAY_MODE_SUPPORT: case SET_AUTO_LOW_LATENCY_MODE: @@ -7436,6 +7436,57 @@ binder::Status SurfaceComposerAIDL::getStaticDisplayInfo(const sp& disp return binder::Status::fromStatusT(status); } +binder::Status SurfaceComposerAIDL::getDynamicDisplayInfo(const sp& display, + gui::DynamicDisplayInfo* outInfo) { + ui::DynamicDisplayInfo info; + status_t status = mFlinger->getDynamicDisplayInfo(display, &info); + if (status == NO_ERROR) { + // convert ui::DynamicDisplayInfo to gui::DynamicDisplayInfo + outInfo->supportedDisplayModes.clear(); + outInfo->supportedDisplayModes.reserve(info.supportedDisplayModes.size()); + for (const auto& mode : info.supportedDisplayModes) { + gui::DisplayMode outMode; + outMode.id = mode.id; + outMode.resolution.width = mode.resolution.width; + outMode.resolution.height = mode.resolution.height; + outMode.xDpi = mode.xDpi; + outMode.yDpi = mode.yDpi; + outMode.refreshRate = mode.refreshRate; + outMode.appVsyncOffset = mode.appVsyncOffset; + outMode.sfVsyncOffset = mode.sfVsyncOffset; + outMode.presentationDeadline = mode.presentationDeadline; + outMode.group = mode.group; + outInfo->supportedDisplayModes.push_back(outMode); + } + + outInfo->activeDisplayModeId = info.activeDisplayModeId; + + outInfo->supportedColorModes.clear(); + outInfo->supportedColorModes.reserve(info.supportedColorModes.size()); + for (const auto& cmode : info.supportedColorModes) { + outInfo->supportedColorModes.push_back(static_cast(cmode)); + } + + outInfo->activeColorMode = static_cast(info.activeColorMode); + + gui::HdrCapabilities& hdrCapabilities = outInfo->hdrCapabilities; + hdrCapabilities.supportedHdrTypes.clear(); + hdrCapabilities.supportedHdrTypes.reserve( + info.hdrCapabilities.getSupportedHdrTypes().size()); + for (const auto& hdr : info.hdrCapabilities.getSupportedHdrTypes()) { + hdrCapabilities.supportedHdrTypes.push_back(static_cast(hdr)); + } + hdrCapabilities.maxLuminance = info.hdrCapabilities.getDesiredMaxLuminance(); + hdrCapabilities.maxAverageLuminance = info.hdrCapabilities.getDesiredMaxAverageLuminance(); + hdrCapabilities.minLuminance = info.hdrCapabilities.getDesiredMinLuminance(); + + outInfo->autoLowLatencyModeSupported = info.autoLowLatencyModeSupported; + outInfo->gameContentTypeSupported = info.gameContentTypeSupported; + outInfo->preferredBootDisplayMode = info.preferredBootDisplayMode; + } + return binder::Status::fromStatusT(status); +} + binder::Status SurfaceComposerAIDL::clearBootDisplayMode(const sp& display) { status_t status = checkAccessPermission(); if (status == OK) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 9e5d84c915..43174b61a0 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -570,7 +570,7 @@ private: status_t getStaticDisplayInfo(const sp& displayToken, ui::StaticDisplayInfo*) EXCLUDES(mStateLock); status_t getDynamicDisplayInfo(const sp& displayToken, ui::DynamicDisplayInfo*) - EXCLUDES(mStateLock) override; + EXCLUDES(mStateLock); status_t getDisplayNativePrimaries(const sp& displayToken, ui::DisplayPrimaries&) override; status_t setActiveColorMode(const sp& displayToken, ui::ColorMode colorMode) override; @@ -1463,6 +1463,8 @@ public: gui::DisplayState* outState) override; binder::Status getStaticDisplayInfo(const sp& display, gui::StaticDisplayInfo* outInfo) override; + binder::Status getDynamicDisplayInfo(const sp& display, + gui::DynamicDisplayInfo* outInfo) override; binder::Status clearBootDisplayMode(const sp& display) override; binder::Status getBootDisplayModeSupport(bool* outMode) override; binder::Status setAutoLowLatencyMode(const sp& display, bool on) override; -- GitLab From ca3d9a423ae718c62fcda4fb249e0760a6fb3727 Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Tue, 22 Feb 2022 11:07:34 -0800 Subject: [PATCH 0038/1310] Convert DisplayPrimaries to AIDL parcelable And migrate related ISurfaceComposer::getDisplayNativePrimaries() method to AIDL. (1) add android::gui::DisplayNativePrimaries parcelable for serialization (2) convert between ui::DisplayPrimaries and gui::DisplayPrimaries (3) migrate setActiveColorMode (4) migrate setBootDisplayMode Bug: 220894272 Test: atest libgui_test Change-Id: I1371f6ef2c1f52f56db53d437cf919ee7f269b48 --- libs/gui/ISurfaceComposer.cpp | 131 ------------------ libs/gui/SurfaceComposerClient.cpp | 31 ++++- .../aidl/android/gui/DisplayPrimaries.aidl | 33 +++++ .../aidl/android/gui/ISurfaceComposer.aidl | 10 ++ libs/gui/include/gui/ISurfaceComposer.h | 22 +-- libs/gui/tests/Surface_test.cpp | 24 ++-- services/surfaceflinger/SurfaceFlinger.cpp | 48 ++++++- services/surfaceflinger/SurfaceFlinger.h | 11 +- .../tests/BootDisplayMode_test.cpp | 8 +- 9 files changed, 147 insertions(+), 171 deletions(-) create mode 100644 libs/gui/aidl/android/gui/DisplayPrimaries.aidl diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index a9473d4997..768ce2cd07 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -225,82 +225,6 @@ public: return result; } - status_t getDisplayNativePrimaries(const sp& display, - ui::DisplayPrimaries& primaries) override { - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("getDisplayNativePrimaries failed to writeInterfaceToken: %d", result); - return result; - } - result = data.writeStrongBinder(display); - if (result != NO_ERROR) { - ALOGE("getDisplayNativePrimaries failed to writeStrongBinder: %d", result); - return result; - } - result = remote()->transact(BnSurfaceComposer::GET_DISPLAY_NATIVE_PRIMARIES, data, &reply); - if (result != NO_ERROR) { - ALOGE("getDisplayNativePrimaries failed to transact: %d", result); - return result; - } - result = reply.readInt32(); - if (result == NO_ERROR) { - memcpy(&primaries, reply.readInplace(sizeof(ui::DisplayPrimaries)), - sizeof(ui::DisplayPrimaries)); - } - return result; - } - - status_t setActiveColorMode(const sp& display, ColorMode colorMode) override { - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("setActiveColorMode failed to writeInterfaceToken: %d", result); - return result; - } - result = data.writeStrongBinder(display); - if (result != NO_ERROR) { - ALOGE("setActiveColorMode failed to writeStrongBinder: %d", result); - return result; - } - result = data.writeInt32(static_cast(colorMode)); - if (result != NO_ERROR) { - ALOGE("setActiveColorMode failed to writeInt32: %d", result); - return result; - } - result = remote()->transact(BnSurfaceComposer::SET_ACTIVE_COLOR_MODE, data, &reply); - if (result != NO_ERROR) { - ALOGE("setActiveColorMode failed to transact: %d", result); - return result; - } - return static_cast(reply.readInt32()); - } - - status_t setBootDisplayMode(const sp& display, - ui::DisplayModeId displayModeId) override { - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("setBootDisplayMode failed to writeInterfaceToken: %d", result); - return result; - } - result = data.writeStrongBinder(display); - if (result != NO_ERROR) { - ALOGE("setBootDisplayMode failed to writeStrongBinder: %d", result); - return result; - } - result = data.writeInt32(displayModeId); - if (result != NO_ERROR) { - ALOGE("setBootDisplayMode failed to writeIint32: %d", result); - return result; - } - result = remote()->transact(BnSurfaceComposer::SET_BOOT_DISPLAY_MODE, data, &reply); - if (result != NO_ERROR) { - ALOGE("setBootDisplayMode failed to transact: %d", result); - } - return result; - } - status_t clearAnimationFrameStats() override { Parcel data, reply; status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); @@ -1122,61 +1046,6 @@ status_t BnSurfaceComposer::onTransact( reply->writeStrongBinder(IInterface::asBinder(connection)); return NO_ERROR; } - case GET_DISPLAY_NATIVE_PRIMARIES: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - ui::DisplayPrimaries primaries; - sp display = nullptr; - - status_t result = data.readStrongBinder(&display); - if (result != NO_ERROR) { - ALOGE("getDisplayNativePrimaries failed to readStrongBinder: %d", result); - return result; - } - - result = getDisplayNativePrimaries(display, primaries); - reply->writeInt32(result); - if (result == NO_ERROR) { - memcpy(reply->writeInplace(sizeof(ui::DisplayPrimaries)), &primaries, - sizeof(ui::DisplayPrimaries)); - } - - return NO_ERROR; - } - case SET_ACTIVE_COLOR_MODE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp display = nullptr; - status_t result = data.readStrongBinder(&display); - if (result != NO_ERROR) { - ALOGE("getActiveColorMode failed to readStrongBinder: %d", result); - return result; - } - int32_t colorModeInt = 0; - result = data.readInt32(&colorModeInt); - if (result != NO_ERROR) { - ALOGE("setActiveColorMode failed to readInt32: %d", result); - return result; - } - result = setActiveColorMode(display, - static_cast(colorModeInt)); - result = reply->writeInt32(result); - return result; - } - case SET_BOOT_DISPLAY_MODE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp display = nullptr; - status_t result = data.readStrongBinder(&display); - if (result != NO_ERROR) { - ALOGE("setBootDisplayMode failed to readStrongBinder: %d", result); - return result; - } - ui::DisplayModeId displayModeId; - result = data.readInt32(&displayModeId); - if (result != NO_ERROR) { - ALOGE("setBootDisplayMode failed to readInt32: %d", result); - return result; - } - return setBootDisplayMode(display, displayModeId); - } case CLEAR_ANIMATION_FRAME_STATS: { CHECK_INTERFACE(ISurfaceComposer, data, reply); status_t result = clearAnimationFrameStats(); diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 6702fef45e..82f63d3ae8 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2278,12 +2278,35 @@ status_t SurfaceComposerClient::getDesiredDisplayModeSpecs(const sp& di status_t SurfaceComposerClient::getDisplayNativePrimaries(const sp& display, ui::DisplayPrimaries& outPrimaries) { - return ComposerService::getComposerService()->getDisplayNativePrimaries(display, outPrimaries); + gui::DisplayPrimaries primaries; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getDisplayNativePrimaries(display, + &primaries); + if (status.isOk()) { + outPrimaries.red.X = primaries.red.X; + outPrimaries.red.Y = primaries.red.Y; + outPrimaries.red.Z = primaries.red.Z; + + outPrimaries.green.X = primaries.green.X; + outPrimaries.green.Y = primaries.green.Y; + outPrimaries.green.Z = primaries.green.Z; + + outPrimaries.blue.X = primaries.blue.X; + outPrimaries.blue.Y = primaries.blue.Y; + outPrimaries.blue.Z = primaries.blue.Z; + + outPrimaries.white.X = primaries.white.X; + outPrimaries.white.Y = primaries.white.Y; + outPrimaries.white.Z = primaries.white.Z; + } + return status.transactionError(); } status_t SurfaceComposerClient::setActiveColorMode(const sp& display, ColorMode colorMode) { - return ComposerService::getComposerService()->setActiveColorMode(display, colorMode); + binder::Status status = ComposerServiceAIDL::getComposerService() + ->setActiveColorMode(display, static_cast(colorMode)); + return status.transactionError(); } status_t SurfaceComposerClient::getBootDisplayModeSupport(bool* support) { @@ -2294,7 +2317,9 @@ status_t SurfaceComposerClient::getBootDisplayModeSupport(bool* support) { status_t SurfaceComposerClient::setBootDisplayMode(const sp& display, ui::DisplayModeId displayModeId) { - return ComposerService::getComposerService()->setBootDisplayMode(display, displayModeId); + binder::Status status = ComposerServiceAIDL::getComposerService() + ->setBootDisplayMode(display, static_cast(displayModeId)); + return status.transactionError(); } status_t SurfaceComposerClient::clearBootDisplayMode(const sp& display) { diff --git a/libs/gui/aidl/android/gui/DisplayPrimaries.aidl b/libs/gui/aidl/android/gui/DisplayPrimaries.aidl new file mode 100644 index 0000000000..dbf668c629 --- /dev/null +++ b/libs/gui/aidl/android/gui/DisplayPrimaries.aidl @@ -0,0 +1,33 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +// copied from libui ConfigStoreTypes.h + +/** @hide */ +parcelable DisplayPrimaries { + parcelable CieXyz { + float X; + float Y; + float Z; + } + + CieXyz red; + CieXyz green; + CieXyz blue; + CieXyz white; +} diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 175007d380..6b901f14aa 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -18,6 +18,7 @@ package android.gui; import android.gui.DisplayCaptureArgs; import android.gui.DisplayBrightness; +import android.gui.DisplayPrimaries; import android.gui.DisplayState; import android.gui.DisplayStatInfo; import android.gui.StaticDisplayInfo; @@ -75,6 +76,15 @@ interface ISurfaceComposer { */ DynamicDisplayInfo getDynamicDisplayInfo(IBinder display); + DisplayPrimaries getDisplayNativePrimaries(IBinder display); + + void setActiveColorMode(IBinder display, int colorMode); + + /** + * Sets the user-preferred display mode that a device should boot in. + */ + void setBootDisplayMode(IBinder display, int displayModeId); + /** * Clears the user-preferred display mode. The device should now boot in system preferred * display mode. diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index ed8254ae8b..f17070d3ff 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -160,16 +160,6 @@ public: virtual status_t getSupportedFrameTimestamps( std::vector* outSupported) const = 0; - virtual status_t getDisplayNativePrimaries(const sp& display, - ui::DisplayPrimaries& primaries) = 0; - virtual status_t setActiveColorMode(const sp& display, - ui::ColorMode colorMode) = 0; - - /** - * Sets the user-preferred display mode that a device should boot in. - */ - virtual status_t setBootDisplayMode(const sp& display, ui::DisplayModeId) = 0; - /* Clears the frame statistics for animations. * * Requires the ACCESS_SURFACE_FLINGER permission. @@ -452,7 +442,7 @@ public: GET_HDR_CAPABILITIES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. GET_DISPLAY_COLOR_MODES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. GET_ACTIVE_COLOR_MODE, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. - SET_ACTIVE_COLOR_MODE, + SET_ACTIVE_COLOR_MODE, // Deprecated. Autogenerated by .aidl now. ENABLE_VSYNC_INJECTIONS, INJECT_VSYNC, GET_LAYER_DEBUG_INFO, @@ -462,9 +452,9 @@ public: SET_DISPLAY_CONTENT_SAMPLING_ENABLED, GET_DISPLAYED_CONTENT_SAMPLE, GET_PROTECTED_CONTENT_SUPPORT, - IS_WIDE_COLOR_DISPLAY, // Deprecated. Autogenerated by .aidl now. - GET_DISPLAY_NATIVE_PRIMARIES, - GET_PHYSICAL_DISPLAY_IDS, // Deprecated. Autogenerated by .aidl now. + IS_WIDE_COLOR_DISPLAY, // Deprecated. Autogenerated by .aidl now. + GET_DISPLAY_NATIVE_PRIMARIES, // Deprecated. Autogenerated by .aidl now. + GET_PHYSICAL_DISPLAY_IDS, // Deprecated. Autogenerated by .aidl now. ADD_REGION_SAMPLING_LISTENER, REMOVE_REGION_SAMPLING_LISTENER, SET_DESIRED_DISPLAY_MODE_SPECS, @@ -499,8 +489,8 @@ public: GET_PRIMARY_PHYSICAL_DISPLAY_ID, // Deprecated. Autogenerated by .aidl now. GET_DISPLAY_DECORATION_SUPPORT, GET_BOOT_DISPLAY_MODE_SUPPORT, // Deprecated. Autogenerated by .aidl now. - SET_BOOT_DISPLAY_MODE, - CLEAR_BOOT_DISPLAY_MODE, // Deprecated. Autogenerated by .aidl now. + SET_BOOT_DISPLAY_MODE, // Deprecated. Autogenerated by .aidl now. + CLEAR_BOOT_DISPLAY_MODE, // Deprecated. Autogenerated by .aidl now. SET_OVERRIDE_FRAME_RATE, // Always append new enum to the end. }; diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index bf1b43d2c0..a2ab8c1eea 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -733,16 +733,6 @@ public: return NO_ERROR; } - status_t getDisplayNativePrimaries(const sp& /*display*/, - ui::DisplayPrimaries& /*primaries*/) override { - return NO_ERROR; - } - status_t setActiveColorMode(const sp& /*display*/, - ColorMode /*colorMode*/) override { return NO_ERROR; } - status_t setBootDisplayMode(const sp& /*display*/, ui::DisplayModeId /*id*/) override { - return NO_ERROR; - } - status_t clearAnimationFrameStats() override { return NO_ERROR; } status_t getAnimationFrameStats(FrameStats* /*outStats*/) const override { return NO_ERROR; @@ -932,6 +922,20 @@ public: return binder::Status::ok(); } + binder::Status getDisplayNativePrimaries(const sp& /*display*/, + gui::DisplayPrimaries* /*outPrimaries*/) override { + return binder::Status::ok(); + } + + binder::Status setActiveColorMode(const sp& /*display*/, int /*colorMode*/) override { + return binder::Status::ok(); + } + + binder::Status setBootDisplayMode(const sp& /*display*/, + int /*displayModeId*/) override { + return binder::Status::ok(); + } + binder::Status clearBootDisplayMode(const sp& /*display*/) override { return binder::Status::ok(); } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 3d723d4b7a..884b4bc20a 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5472,8 +5472,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case GET_HDR_CAPABILITIES: case SET_DESIRED_DISPLAY_MODE_SPECS: case GET_DESIRED_DISPLAY_MODE_SPECS: - case SET_ACTIVE_COLOR_MODE: - case SET_BOOT_DISPLAY_MODE: case GET_AUTO_LOW_LATENCY_MODE_SUPPORT: case GET_GAME_CONTENT_TYPE_SUPPORT: case GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES: @@ -5513,7 +5511,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case GET_ACTIVE_COLOR_MODE: case GET_ACTIVE_DISPLAY_MODE: case GET_DISPLAY_COLOR_MODES: - case GET_DISPLAY_NATIVE_PRIMARIES: case GET_DISPLAY_MODES: case GET_SUPPORTED_FRAME_TIMESTAMPS: // Calling setTransactionState is safe, because you need to have been @@ -5588,6 +5585,9 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case GET_DISPLAY_STATS: case GET_STATIC_DISPLAY_INFO: case GET_DYNAMIC_DISPLAY_INFO: + case GET_DISPLAY_NATIVE_PRIMARIES: + case SET_ACTIVE_COLOR_MODE: + case SET_BOOT_DISPLAY_MODE: case CLEAR_BOOT_DISPLAY_MODE: case GET_BOOT_DISPLAY_MODE_SUPPORT: case SET_AUTO_LOW_LATENCY_MODE: @@ -7484,6 +7484,48 @@ binder::Status SurfaceComposerAIDL::getDynamicDisplayInfo(const sp& dis return binder::Status::fromStatusT(status); } +binder::Status SurfaceComposerAIDL::getDisplayNativePrimaries(const sp& display, + gui::DisplayPrimaries* outPrimaries) { + ui::DisplayPrimaries primaries; + status_t status = mFlinger->getDisplayNativePrimaries(display, primaries); + if (status == NO_ERROR) { + outPrimaries->red.X = primaries.red.X; + outPrimaries->red.Y = primaries.red.Y; + outPrimaries->red.Z = primaries.red.Z; + + outPrimaries->green.X = primaries.green.X; + outPrimaries->green.Y = primaries.green.Y; + outPrimaries->green.Z = primaries.green.Z; + + outPrimaries->blue.X = primaries.blue.X; + outPrimaries->blue.Y = primaries.blue.Y; + outPrimaries->blue.Z = primaries.blue.Z; + + outPrimaries->white.X = primaries.white.X; + outPrimaries->white.Y = primaries.white.Y; + outPrimaries->white.Z = primaries.white.Z; + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::setActiveColorMode(const sp& display, int colorMode) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->setActiveColorMode(display, static_cast(colorMode)); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::setBootDisplayMode(const sp& display, + int displayModeId) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->setBootDisplayMode(display, + static_cast(displayModeId)); + } + return binder::Status::fromStatusT(status); +} + binder::Status SurfaceComposerAIDL::clearBootDisplayMode(const sp& display) { status_t status = checkAccessPermission(); if (status == OK) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 43174b61a0..5b21412576 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -571,11 +571,10 @@ private: EXCLUDES(mStateLock); status_t getDynamicDisplayInfo(const sp& displayToken, ui::DynamicDisplayInfo*) EXCLUDES(mStateLock); - status_t getDisplayNativePrimaries(const sp& displayToken, - ui::DisplayPrimaries&) override; - status_t setActiveColorMode(const sp& displayToken, ui::ColorMode colorMode) override; + status_t getDisplayNativePrimaries(const sp& displayToken, ui::DisplayPrimaries&); + status_t setActiveColorMode(const sp& displayToken, ui::ColorMode colorMode); status_t getBootDisplayModeSupport(bool* outSupport) const; - status_t setBootDisplayMode(const sp& displayToken, ui::DisplayModeId id) override; + status_t setBootDisplayMode(const sp& displayToken, ui::DisplayModeId id); status_t clearBootDisplayMode(const sp& displayToken); void setAutoLowLatencyMode(const sp& displayToken, bool on); void setGameContentType(const sp& displayToken, bool on); @@ -1465,6 +1464,10 @@ public: gui::StaticDisplayInfo* outInfo) override; binder::Status getDynamicDisplayInfo(const sp& display, gui::DynamicDisplayInfo* outInfo) override; + binder::Status getDisplayNativePrimaries(const sp& display, + gui::DisplayPrimaries* outPrimaries) override; + binder::Status setActiveColorMode(const sp& display, int colorMode) override; + binder::Status setBootDisplayMode(const sp& display, int displayModeId) override; binder::Status clearBootDisplayMode(const sp& display) override; binder::Status getBootDisplayModeSupport(bool* outMode) override; binder::Status setAutoLowLatencyMode(const sp& display, bool on) override; diff --git a/services/surfaceflinger/tests/BootDisplayMode_test.cpp b/services/surfaceflinger/tests/BootDisplayMode_test.cpp index d70908e390..4cd6ef8f1d 100644 --- a/services/surfaceflinger/tests/BootDisplayMode_test.cpp +++ b/services/surfaceflinger/tests/BootDisplayMode_test.cpp @@ -26,14 +26,14 @@ namespace android { TEST(BootDisplayModeTest, setBootDisplayMode) { - sp sf(ComposerService::getComposerService()); - sp sf_aidl(ComposerServiceAIDL::getComposerService()); + sp sf(ComposerServiceAIDL::getComposerService()); auto displayToken = SurfaceComposerClient::getInternalDisplayToken(); bool bootModeSupport = false; - binder::Status status = sf_aidl->getBootDisplayModeSupport(&bootModeSupport); + binder::Status status = sf->getBootDisplayModeSupport(&bootModeSupport); ASSERT_NO_FATAL_FAILURE(status.transactionError()); if (bootModeSupport) { - ASSERT_EQ(NO_ERROR, sf->setBootDisplayMode(displayToken, 0)); + status = sf->setBootDisplayMode(displayToken, 0); + ASSERT_EQ(NO_ERROR, status.transactionError()); } } -- GitLab From 4ed1c91900c539dd91797e89f51e5018fd2ba228 Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Tue, 22 Feb 2022 14:30:01 -0800 Subject: [PATCH 0039/1310] Convert FrameStats to AIDL parcelable And migrate related ISurfaceComposer methods to AIDL. (1) add android::gui::FrameStats parcelable for serialization (2) convert between FrameStats and gui::FrameStats (3) migrate clearAnimationFrameStats (4) migrate getAnimationFrameStats Bug: 220910000 Test: atest libgui_test Change-Id: I7c0aadbd791834e6bd22ccfc75dad39642d08160 --- libs/gui/ISurfaceComposer.cpp | 37 --------------- libs/gui/SurfaceComposerClient.cpp | 23 ++++++++- libs/gui/aidl/android/gui/FrameStats.aidl | 47 +++++++++++++++++++ .../aidl/android/gui/ISurfaceComposer.aidl | 13 +++++ libs/gui/include/gui/ISurfaceComposer.h | 22 ++------- libs/gui/tests/Surface_test.cpp | 10 ++-- services/surfaceflinger/SurfaceFlinger.cpp | 38 ++++++++++++++- services/surfaceflinger/SurfaceFlinger.h | 6 ++- 8 files changed, 132 insertions(+), 64 deletions(-) create mode 100644 libs/gui/aidl/android/gui/FrameStats.aidl diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index 768ce2cd07..a20d428345 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -225,29 +225,6 @@ public: return result; } - status_t clearAnimationFrameStats() override { - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("clearAnimationFrameStats failed to writeInterfaceToken: %d", result); - return result; - } - result = remote()->transact(BnSurfaceComposer::CLEAR_ANIMATION_FRAME_STATS, data, &reply); - if (result != NO_ERROR) { - ALOGE("clearAnimationFrameStats failed to transact: %d", result); - return result; - } - return reply.readInt32(); - } - - status_t getAnimationFrameStats(FrameStats* outStats) const override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - remote()->transact(BnSurfaceComposer::GET_ANIMATION_FRAME_STATS, data, &reply); - reply.read(*outStats); - return reply.readInt32(); - } - virtual status_t overrideHdrTypes(const sp& display, const std::vector& hdrTypes) { Parcel data, reply; @@ -1046,20 +1023,6 @@ status_t BnSurfaceComposer::onTransact( reply->writeStrongBinder(IInterface::asBinder(connection)); return NO_ERROR; } - case CLEAR_ANIMATION_FRAME_STATS: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - status_t result = clearAnimationFrameStats(); - reply->writeInt32(result); - return NO_ERROR; - } - case GET_ANIMATION_FRAME_STATS: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - FrameStats stats; - status_t result = getAnimationFrameStats(&stats); - reply->write(stats); - reply->writeInt32(result); - return NO_ERROR; - } case ENABLE_VSYNC_INJECTIONS: { CHECK_INTERFACE(ISurfaceComposer, data, reply); bool enable = false; diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 82f63d3ae8..be5f338c39 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2360,11 +2360,30 @@ bool SurfaceComposerClient::getProtectedContentSupport() { } status_t SurfaceComposerClient::clearAnimationFrameStats() { - return ComposerService::getComposerService()->clearAnimationFrameStats(); + binder::Status status = ComposerServiceAIDL::getComposerService()->clearAnimationFrameStats(); + return status.transactionError(); } status_t SurfaceComposerClient::getAnimationFrameStats(FrameStats* outStats) { - return ComposerService::getComposerService()->getAnimationFrameStats(outStats); + gui::FrameStats stats; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getAnimationFrameStats(&stats); + if (status.isOk()) { + outStats->refreshPeriodNano = stats.refreshPeriodNano; + outStats->desiredPresentTimesNano.setCapacity(stats.desiredPresentTimesNano.size()); + for (const auto& t : stats.desiredPresentTimesNano) { + outStats->desiredPresentTimesNano.add(t); + } + outStats->actualPresentTimesNano.setCapacity(stats.actualPresentTimesNano.size()); + for (const auto& t : stats.actualPresentTimesNano) { + outStats->actualPresentTimesNano.add(t); + } + outStats->frameReadyTimesNano.setCapacity(stats.frameReadyTimesNano.size()); + for (const auto& t : stats.frameReadyTimesNano) { + outStats->frameReadyTimesNano.add(t); + } + } + return status.transactionError(); } status_t SurfaceComposerClient::overrideHdrTypes(const sp& display, diff --git a/libs/gui/aidl/android/gui/FrameStats.aidl b/libs/gui/aidl/android/gui/FrameStats.aidl new file mode 100644 index 0000000000..a145e74b11 --- /dev/null +++ b/libs/gui/aidl/android/gui/FrameStats.aidl @@ -0,0 +1,47 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +// Make sure to sync with libui FrameStats.h + +/** @hide */ +parcelable FrameStats { + /* + * Approximate refresh time, in nanoseconds. + */ + long refreshPeriodNano; + + /* + * The times in nanoseconds for when the frame contents were posted by the producer (e.g. + * the application). They are either explicitly set or defaulted to the time when + * Surface::queueBuffer() was called. + */ + long[] desiredPresentTimesNano; + + /* + * The times in milliseconds for when the frame contents were presented on the screen. + */ + long[] actualPresentTimesNano; + + /* + * The times in nanoseconds for when the frame contents were ready to be presented. Note that + * a frame can be posted and still it contents being rendered asynchronously in GL. In such a + * case these are the times when the frame contents were completely rendered (i.e. their fences + * signaled). + */ + long[] frameReadyTimesNano; +} diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 6b901f14aa..59e8dd61fb 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -21,6 +21,7 @@ import android.gui.DisplayBrightness; import android.gui.DisplayPrimaries; import android.gui.DisplayState; import android.gui.DisplayStatInfo; +import android.gui.FrameStats; import android.gui.StaticDisplayInfo; import android.gui.DynamicDisplayInfo; import android.gui.IHdrLayerInfoListener; @@ -138,6 +139,18 @@ interface ISurfaceComposer { */ void captureLayers(in LayerCaptureArgs args, IScreenCaptureListener listener); + /* Clears the frame statistics for animations. + * + * Requires the ACCESS_SURFACE_FLINGER permission. + */ + void clearAnimationFrameStats(); + + /* Gets the frame statistics for animations. + * + * Requires the ACCESS_SURFACE_FLINGER permission. + */ + FrameStats getAnimationFrameStats(); + /* * Queries whether the given display is a wide color display. * Requires the ACCESS_SURFACE_FLINGER permission. diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index f17070d3ff..35f6e4d328 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -160,18 +160,6 @@ public: virtual status_t getSupportedFrameTimestamps( std::vector* outSupported) const = 0; - /* Clears the frame statistics for animations. - * - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t clearAnimationFrameStats() = 0; - - /* Gets the frame statistics for animations. - * - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t getAnimationFrameStats(FrameStats* outStats) const = 0; - /* Overrides the supported HDR modes for the given display device. * * Requires the ACCESS_SURFACE_FLINGER permission. @@ -433,11 +421,11 @@ public: GET_DISPLAY_MODES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. GET_ACTIVE_DISPLAY_MODE, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. GET_DISPLAY_STATE, - CAPTURE_DISPLAY, // Deprecated. Autogenerated by .aidl now. - CAPTURE_LAYERS, // Deprecated. Autogenerated by .aidl now. - CLEAR_ANIMATION_FRAME_STATS, - GET_ANIMATION_FRAME_STATS, - SET_POWER_MODE, // Deprecated. Autogenerated by .aidl now. + CAPTURE_DISPLAY, // Deprecated. Autogenerated by .aidl now. + CAPTURE_LAYERS, // Deprecated. Autogenerated by .aidl now. + CLEAR_ANIMATION_FRAME_STATS, // Deprecated. Autogenerated by .aidl now. + GET_ANIMATION_FRAME_STATS, // Deprecated. Autogenerated by .aidl now. + SET_POWER_MODE, // Deprecated. Autogenerated by .aidl now. GET_DISPLAY_STATS, GET_HDR_CAPABILITIES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. GET_DISPLAY_COLOR_MODES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index a2ab8c1eea..6a10305651 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -733,10 +733,6 @@ public: return NO_ERROR; } - status_t clearAnimationFrameStats() override { return NO_ERROR; } - status_t getAnimationFrameStats(FrameStats* /*outStats*/) const override { - return NO_ERROR; - } status_t overrideHdrTypes(const sp& /*display*/, const std::vector& /*hdrTypes*/) override { return NO_ERROR; @@ -966,6 +962,12 @@ public: return binder::Status::ok(); } + binder::Status clearAnimationFrameStats() override { return binder::Status::ok(); } + + binder::Status getAnimationFrameStats(gui::FrameStats* /*outStats*/) override { + return binder::Status::ok(); + } + binder::Status isWideColorDisplay(const sp& /*token*/, bool* /*outIsWideColorDisplay*/) override { return binder::Status::ok(); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 884b4bc20a..58d8acca56 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5466,8 +5466,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { // These methods should at minimum make sure that the client requested // access to SF. case BOOT_FINISHED: - case CLEAR_ANIMATION_FRAME_STATS: - case GET_ANIMATION_FRAME_STATS: case OVERRIDE_HDR_TYPES: case GET_HDR_CAPABILITIES: case SET_DESIRED_DISPLAY_MODE_SPECS: @@ -5595,6 +5593,8 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case CAPTURE_LAYERS: case CAPTURE_DISPLAY: case CAPTURE_DISPLAY_BY_ID: + case CLEAR_ANIMATION_FRAME_STATS: + case GET_ANIMATION_FRAME_STATS: case IS_WIDE_COLOR_DISPLAY: case GET_DISPLAY_BRIGHTNESS_SUPPORT: case SET_DISPLAY_BRIGHTNESS: @@ -7586,6 +7586,40 @@ binder::Status SurfaceComposerAIDL::captureLayers( return binder::Status::fromStatusT(status); } +binder::Status SurfaceComposerAIDL::clearAnimationFrameStats() { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->clearAnimationFrameStats(); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getAnimationFrameStats(gui::FrameStats* outStats) { + status_t status = checkAccessPermission(); + if (status != OK) { + return binder::Status::fromStatusT(status); + } + + FrameStats stats; + status = mFlinger->getAnimationFrameStats(&stats); + if (status == NO_ERROR) { + outStats->refreshPeriodNano = stats.refreshPeriodNano; + outStats->desiredPresentTimesNano.reserve(stats.desiredPresentTimesNano.size()); + for (const auto& t : stats.desiredPresentTimesNano) { + outStats->desiredPresentTimesNano.push_back(t); + } + outStats->actualPresentTimesNano.reserve(stats.actualPresentTimesNano.size()); + for (const auto& t : stats.actualPresentTimesNano) { + outStats->actualPresentTimesNano.push_back(t); + } + outStats->frameReadyTimesNano.reserve(stats.frameReadyTimesNano.size()); + for (const auto& t : stats.frameReadyTimesNano) { + outStats->frameReadyTimesNano.push_back(t); + } + } + return binder::Status::fromStatusT(status); +} + binder::Status SurfaceComposerAIDL::isWideColorDisplay(const sp& token, bool* outIsWideColorDisplay) { status_t status = mFlinger->isWideColorDisplay(token, outIsWideColorDisplay); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 5b21412576..57b48c55f3 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -579,8 +579,8 @@ private: void setAutoLowLatencyMode(const sp& displayToken, bool on); void setGameContentType(const sp& displayToken, bool on); void setPowerMode(const sp& displayToken, int mode); - status_t clearAnimationFrameStats() override; - status_t getAnimationFrameStats(FrameStats* outStats) const override; + status_t clearAnimationFrameStats(); + status_t getAnimationFrameStats(FrameStats* outStats) const; status_t overrideHdrTypes(const sp& displayToken, const std::vector& hdrTypes) override; status_t onPullAtom(const int32_t atomId, std::string* pulledData, bool* success) override; @@ -1477,6 +1477,8 @@ public: binder::Status captureDisplayById(int64_t, const sp&) override; binder::Status captureLayers(const LayerCaptureArgs&, const sp&) override; + binder::Status clearAnimationFrameStats() override; + binder::Status getAnimationFrameStats(gui::FrameStats* outStats) override; binder::Status isWideColorDisplay(const sp& token, bool* outIsWideColorDisplay) override; binder::Status getDisplayBrightnessSupport(const sp& displayToken, -- GitLab From 0a81aa312d9efa3369b31b1212b97f29f01fba74 Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Tue, 22 Feb 2022 16:02:36 -0800 Subject: [PATCH 0040/1310] Migrate getSupportedFrameTimestamps() to AIDL Note that FrameEvent is converted to AIDL enum and some external projects are updated to reflect the changes, refer to the topic for other CLs. Bug: 220935835 Test: atest libgui_test Change-Id: I576360ad0684b1b010b773a2287050c9ba2f62f --- libs/bufferqueueconverter/Android.bp | 1 + libs/gui/Android.bp | 7 +++ libs/gui/ISurfaceComposer.cpp | 62 ------------------- libs/gui/Surface.cpp | 6 +- libs/gui/aidl/android/gui/FrameEvent.aidl | 35 +++++++++++ .../aidl/android/gui/ISurfaceComposer.aidl | 5 ++ libs/gui/include/gui/FrameTimestamps.h | 19 +----- libs/gui/include/gui/ISurfaceComposer.h | 12 +--- libs/gui/tests/Surface_test.cpp | 50 ++++++++------- libs/nativedisplay/Android.bp | 3 +- services/surfaceflinger/SurfaceFlinger.cpp | 14 ++++- services/surfaceflinger/SurfaceFlinger.h | 3 +- 12 files changed, 101 insertions(+), 116 deletions(-) create mode 100644 libs/gui/aidl/android/gui/FrameEvent.aidl diff --git a/libs/bufferqueueconverter/Android.bp b/libs/bufferqueueconverter/Android.bp index c5d3a3207c..5f145a149d 100644 --- a/libs/bufferqueueconverter/Android.bp +++ b/libs/bufferqueueconverter/Android.bp @@ -22,6 +22,7 @@ cc_library_shared { double_loadable: true, srcs: [ + ":libgui_frame_event_aidl", "BufferQueueConverter.cpp", ], diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index d634c58c53..6b64ac8597 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -120,6 +120,12 @@ filegroup { path: "aidl/", } +filegroup { + name: "libgui_frame_event_aidl", + srcs: ["aidl/android/gui/FrameEvent.aidl"], + path: "aidl/", +} + cc_library_static { name: "libgui_aidl_static", vendor_available: true, @@ -405,6 +411,7 @@ cc_library_static { ], srcs: [ + ":libgui_frame_event_aidl", "mock/GraphicBufferConsumer.cpp", "mock/GraphicBufferProducer.cpp", ], diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index a20d428345..a1375684f9 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -159,49 +159,6 @@ public: return result != 0; } - status_t getSupportedFrameTimestamps(std::vector* outSupported) const override { - if (!outSupported) { - return UNEXPECTED_NULL; - } - outSupported->clear(); - - Parcel data, reply; - - status_t err = data.writeInterfaceToken( - ISurfaceComposer::getInterfaceDescriptor()); - if (err != NO_ERROR) { - return err; - } - - err = remote()->transact( - BnSurfaceComposer::GET_SUPPORTED_FRAME_TIMESTAMPS, - data, &reply); - if (err != NO_ERROR) { - return err; - } - - int32_t result = 0; - err = reply.readInt32(&result); - if (err != NO_ERROR) { - return err; - } - if (result != NO_ERROR) { - return result; - } - - std::vector supported; - err = reply.readInt32Vector(&supported); - if (err != NO_ERROR) { - return err; - } - - outSupported->reserve(supported.size()); - for (int32_t s : supported) { - outSupported->push_back(static_cast(s)); - } - return NO_ERROR; - } - sp createDisplayEventConnection( VsyncSource vsyncSource, EventRegistrationFlags eventRegistration) override { Parcel data, reply; @@ -993,25 +950,6 @@ status_t BnSurfaceComposer::onTransact( reply->writeInt32(result); return NO_ERROR; } - case GET_SUPPORTED_FRAME_TIMESTAMPS: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - std::vector supportedTimestamps; - status_t result = getSupportedFrameTimestamps(&supportedTimestamps); - status_t err = reply->writeInt32(result); - if (err != NO_ERROR) { - return err; - } - if (result != NO_ERROR) { - return result; - } - - std::vector supported; - supported.reserve(supportedTimestamps.size()); - for (FrameEvent s : supportedTimestamps) { - supported.push_back(static_cast(s)); - } - return reply->writeInt32Vector(supported); - } case CREATE_DISPLAY_EVENT_CONNECTION: { CHECK_INTERFACE(ISurfaceComposer, data, reply); auto vsyncSource = static_cast(data.readInt32()); diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index bee820dfb1..128552dda3 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -1244,10 +1244,10 @@ void Surface::querySupportedTimestampsLocked() const { mQueriedSupportedTimestamps = true; std::vector supportedFrameTimestamps; - status_t err = composerService()->getSupportedFrameTimestamps( - &supportedFrameTimestamps); + binder::Status status = + composerServiceAIDL()->getSupportedFrameTimestamps(&supportedFrameTimestamps); - if (err != NO_ERROR) { + if (!status.isOk()) { return; } diff --git a/libs/gui/aidl/android/gui/FrameEvent.aidl b/libs/gui/aidl/android/gui/FrameEvent.aidl new file mode 100644 index 0000000000..aaabdb5b54 --- /dev/null +++ b/libs/gui/aidl/android/gui/FrameEvent.aidl @@ -0,0 +1,35 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +// Identifiers for all the events that may be recorded or reported. + +/** @hide */ +@Backing(type="int") +enum FrameEvent { + POSTED = 0, + REQUESTED_PRESENT = 1, + LATCH = 2, + ACQUIRE = 3, + FIRST_REFRESH_START = 4, + LAST_REFRESH_START = 5, + GPU_COMPOSITION_DONE = 6, + DISPLAY_PRESENT = 7, + DEQUEUE_READY = 8, + RELEASE = 9, + EVENT_COUNT = 10 // Not an actual event. +} diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 59e8dd61fb..42e2d9bcf6 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -21,6 +21,7 @@ import android.gui.DisplayBrightness; import android.gui.DisplayPrimaries; import android.gui.DisplayState; import android.gui.DisplayStatInfo; +import android.gui.FrameEvent; import android.gui.FrameStats; import android.gui.StaticDisplayInfo; import android.gui.DynamicDisplayInfo; @@ -51,6 +52,10 @@ interface ISurfaceComposer { */ @nullable IBinder getPhysicalDisplayToken(long displayId); + /* Returns the frame timestamps supported by SurfaceFlinger. + */ + FrameEvent[] getSupportedFrameTimestamps(); + /* set display power mode. depending on the mode, it can either trigger * screen on, off or low power mode and wait for it to complete. * requires ACCESS_SURFACE_FLINGER permission. diff --git a/libs/gui/include/gui/FrameTimestamps.h b/libs/gui/include/gui/FrameTimestamps.h index dd3de58844..f73bc3b329 100644 --- a/libs/gui/include/gui/FrameTimestamps.h +++ b/libs/gui/include/gui/FrameTimestamps.h @@ -17,6 +17,8 @@ #ifndef ANDROID_GUI_FRAMETIMESTAMPS_H #define ANDROID_GUI_FRAMETIMESTAMPS_H +#include + #include #include #include @@ -31,22 +33,7 @@ namespace android { struct FrameEvents; class FrameEventHistoryDelta; - -// Identifiers for all the events that may be recorded or reported. -enum class FrameEvent { - POSTED, - REQUESTED_PRESENT, - LATCH, - ACQUIRE, - FIRST_REFRESH_START, - LAST_REFRESH_START, - GPU_COMPOSITION_DONE, - DISPLAY_PRESENT, - DEQUEUE_READY, - RELEASE, - EVENT_COUNT, // Not an actual event. -}; - +using gui::FrameEvent; // A collection of timestamps corresponding to a single frame. struct FrameEvents { diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index 35f6e4d328..138d2edebd 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -66,7 +66,6 @@ class HdrCapabilities; class IGraphicBufferProducer; class ISurfaceComposerClient; class Rect; -enum class FrameEvent; using gui::IDisplayEventConnection; using gui::IRegionSamplingListener; @@ -155,11 +154,6 @@ public: virtual bool authenticateSurfaceTexture( const sp& surface) const = 0; - /* Returns the frame timestamps supported by SurfaceFlinger. - */ - virtual status_t getSupportedFrameTimestamps( - std::vector* outSupported) const = 0; - /* Overrides the supported HDR modes for the given display device. * * Requires the ACCESS_SURFACE_FLINGER permission. @@ -417,9 +411,9 @@ public: GET_PHYSICAL_DISPLAY_TOKEN, // Deprecated. Autogenerated by .aidl now. SET_TRANSACTION_STATE, AUTHENTICATE_SURFACE, - GET_SUPPORTED_FRAME_TIMESTAMPS, - GET_DISPLAY_MODES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. - GET_ACTIVE_DISPLAY_MODE, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. + GET_SUPPORTED_FRAME_TIMESTAMPS, // Deprecated. Autogenerated by .aidl now. + GET_DISPLAY_MODES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. + GET_ACTIVE_DISPLAY_MODE, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. GET_DISPLAY_STATE, CAPTURE_DISPLAY, // Deprecated. Autogenerated by .aidl now. CAPTURE_LAYERS, // Deprecated. Autogenerated by .aidl now. diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 6a10305651..a644e042c9 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -714,25 +714,6 @@ public: return false; } - status_t getSupportedFrameTimestamps(std::vector* outSupported) - const override { - *outSupported = { - FrameEvent::REQUESTED_PRESENT, - FrameEvent::ACQUIRE, - FrameEvent::LATCH, - FrameEvent::FIRST_REFRESH_START, - FrameEvent::LAST_REFRESH_START, - FrameEvent::GPU_COMPOSITION_DONE, - FrameEvent::DEQUEUE_READY, - FrameEvent::RELEASE - }; - if (mSupportsPresent) { - outSupported->push_back( - FrameEvent::DISPLAY_PRESENT); - } - return NO_ERROR; - } - status_t overrideHdrTypes(const sp& /*display*/, const std::vector& /*hdrTypes*/) override { return NO_ERROR; @@ -898,6 +879,21 @@ public: return binder::Status::ok(); } + binder::Status getSupportedFrameTimestamps(std::vector* outSupported) override { + *outSupported = {FrameEvent::REQUESTED_PRESENT, + FrameEvent::ACQUIRE, + FrameEvent::LATCH, + FrameEvent::FIRST_REFRESH_START, + FrameEvent::LAST_REFRESH_START, + FrameEvent::GPU_COMPOSITION_DONE, + FrameEvent::DEQUEUE_READY, + FrameEvent::RELEASE}; + if (mSupportsPresent) { + outSupported->push_back(FrameEvent::DISPLAY_PRESENT); + } + return binder::Status::ok(); + } + binder::Status getDisplayStats(const sp& /*display*/, gui::DisplayStatInfo* /*outStatInfo*/) override { return binder::Status::ok(); @@ -1042,10 +1038,10 @@ protected: class TestSurface : public Surface { public: - TestSurface(const sp& bufferProducer, - FenceToFenceTimeMap* fenceMap) - : Surface(bufferProducer), - mFakeSurfaceComposer(new FakeSurfaceComposer) { + TestSurface(const sp& bufferProducer, FenceToFenceTimeMap* fenceMap) + : Surface(bufferProducer), + mFakeSurfaceComposer(new FakeSurfaceComposer), + mFakeSurfaceComposerAIDL(new FakeSurfaceComposerAIDL) { mFakeFrameEventHistory = new FakeProducerFrameEventHistory(fenceMap); mFrameEventHistory.reset(mFakeFrameEventHistory); } @@ -1056,6 +1052,10 @@ public: return mFakeSurfaceComposer; } + sp composerServiceAIDL() const override { + return mFakeSurfaceComposerAIDL; + } + nsecs_t now() const override { return mNow; } @@ -1066,6 +1066,7 @@ public: public: sp mFakeSurfaceComposer; + sp mFakeSurfaceComposerAIDL; nsecs_t mNow = 0; // mFrameEventHistory owns the instance of FakeProducerFrameEventHistory, @@ -1432,6 +1433,7 @@ TEST_F(GetFrameTimestampsTest, EnabledSimple) { TEST_F(GetFrameTimestampsTest, QueryPresentSupported) { bool displayPresentSupported = true; mSurface->mFakeSurfaceComposer->setSupportsPresent(displayPresentSupported); + mSurface->mFakeSurfaceComposerAIDL->setSupportsPresent(displayPresentSupported); // Verify supported bits are forwarded. int supportsPresent = -1; @@ -1443,6 +1445,7 @@ TEST_F(GetFrameTimestampsTest, QueryPresentSupported) { TEST_F(GetFrameTimestampsTest, QueryPresentNotSupported) { bool displayPresentSupported = false; mSurface->mFakeSurfaceComposer->setSupportsPresent(displayPresentSupported); + mSurface->mFakeSurfaceComposerAIDL->setSupportsPresent(displayPresentSupported); // Verify supported bits are forwarded. int supportsPresent = -1; @@ -2020,6 +2023,7 @@ TEST_F(GetFrameTimestampsTest, NoReleaseNoSync) { TEST_F(GetFrameTimestampsTest, PresentUnsupportedNoSync) { enableFrameTimestamps(); mSurface->mFakeSurfaceComposer->setSupportsPresent(false); + mSurface->mFakeSurfaceComposerAIDL->setSupportsPresent(false); // Dequeue and queue frame 1. const uint64_t fId1 = getNextFrameId(); diff --git a/libs/nativedisplay/Android.bp b/libs/nativedisplay/Android.bp index ed728dcb45..e3fad3db2a 100644 --- a/libs/nativedisplay/Android.bp +++ b/libs/nativedisplay/Android.bp @@ -33,7 +33,7 @@ license { cc_library_headers { name: "libnativedisplay_headers", - export_include_dirs: ["include",], + export_include_dirs: ["include"], } cc_library_shared { @@ -55,6 +55,7 @@ cc_library_shared { version_script: "libnativedisplay.map.txt", srcs: [ + ":libgui_frame_event_aidl", "AChoreographer.cpp", "ADisplay.cpp", "surfacetexture/surface_texture.cpp", diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 58d8acca56..b6d00b2da9 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5510,7 +5510,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case GET_ACTIVE_DISPLAY_MODE: case GET_DISPLAY_COLOR_MODES: case GET_DISPLAY_MODES: - case GET_SUPPORTED_FRAME_TIMESTAMPS: // Calling setTransactionState is safe, because you need to have been // granted a reference to Client* and Handle* to do anything with it. case SET_TRANSACTION_STATE: @@ -5579,6 +5578,7 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case GET_PHYSICAL_DISPLAY_IDS: case GET_PHYSICAL_DISPLAY_TOKEN: case SET_POWER_MODE: + case GET_SUPPORTED_FRAME_TIMESTAMPS: case GET_DISPLAY_STATE: case GET_DISPLAY_STATS: case GET_STATIC_DISPLAY_INFO: @@ -7368,6 +7368,18 @@ binder::Status SurfaceComposerAIDL::setPowerMode(const sp& display, int return binder::Status::ok(); } +binder::Status SurfaceComposerAIDL::getSupportedFrameTimestamps( + std::vector* outSupported) { + status_t status; + if (!outSupported) { + status = UNEXPECTED_NULL; + } else { + outSupported->clear(); + status = mFlinger->getSupportedFrameTimestamps(outSupported); + } + return binder::Status::fromStatusT(status); +} + binder::Status SurfaceComposerAIDL::getDisplayStats(const sp& display, gui::DisplayStatInfo* outStatInfo) { DisplayStatInfo statInfo; diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 57b48c55f3..1ca36bdf72 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -555,7 +555,7 @@ private: void bootFinished() override; bool authenticateSurfaceTexture( const sp& bufferProducer) const override; - status_t getSupportedFrameTimestamps(std::vector* outSupported) const override; + virtual status_t getSupportedFrameTimestamps(std::vector* outSupported) const; sp createDisplayEventConnection( ISurfaceComposer::VsyncSource vsyncSource = eVsyncSourceApp, ISurfaceComposer::EventRegistrationFlags eventRegistration = {}) override; @@ -1456,6 +1456,7 @@ public: binder::Status getPrimaryPhysicalDisplayId(int64_t* outDisplayId) override; binder::Status getPhysicalDisplayToken(int64_t displayId, sp* outDisplay) override; binder::Status setPowerMode(const sp& display, int mode) override; + binder::Status getSupportedFrameTimestamps(std::vector* outSupported) override; binder::Status getDisplayStats(const sp& display, gui::DisplayStatInfo* outStatInfo) override; binder::Status getDisplayState(const sp& display, -- GitLab From 05539a1ac638f03e070e571937928d2b050ffb1f Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Wed, 23 Feb 2022 10:29:40 -0800 Subject: [PATCH 0041/1310] Migrate 10 methods of ISurfaceComposer to AIDL Ten methods are migrated. LayerDebugInfo uses a c++ wrapper aidl for now due to large amount of existing code, but its namespace is changed to android::gui from android::. Parcelable CompositionPreference and ContentSamplingAttributes are added to pass the out values. Bug: 211009610 Test: atest libgui_test libsurfaceflinger_unittest Change-Id: I876a3394c9883ba3c6539154b95c7ace46f7a260 --- libs/gui/ISurfaceComposer.cpp | 359 ------------------ libs/gui/LayerDebugInfo.cpp | 4 +- libs/gui/SurfaceComposerClient.cpp | 68 +++- .../android/gui/CompositionPreference.aidl | 25 ++ .../gui/ContentSamplingAttributes.aidl | 24 ++ .../aidl/android/gui/ISurfaceComposer.aidl | 104 ++++- libs/gui/aidl/android/gui/LayerDebugInfo.aidl | 19 + libs/gui/aidl/android/gui/PullAtomData.aidl | 23 ++ libs/gui/include/gui/ISurfaceComposer.h | 94 +---- libs/gui/include/gui/LayerDebugInfo.h | 4 +- libs/gui/tests/Surface_test.cpp | 75 ++-- services/surfaceflinger/Layer.cpp | 4 +- services/surfaceflinger/Layer.h | 7 +- services/surfaceflinger/SurfaceFlinger.cpp | 167 ++++++-- services/surfaceflinger/SurfaceFlinger.h | 38 +- .../surfaceflinger/tests/Credentials_test.cpp | 16 +- .../tests/LayerTransactionTest.h | 6 +- .../tests/fakehwc/SFFakeHwc_test.cpp | 10 +- 18 files changed, 483 insertions(+), 564 deletions(-) create mode 100644 libs/gui/aidl/android/gui/CompositionPreference.aidl create mode 100644 libs/gui/aidl/android/gui/ContentSamplingAttributes.aidl create mode 100644 libs/gui/aidl/android/gui/LayerDebugInfo.aidl create mode 100644 libs/gui/aidl/android/gui/PullAtomData.aidl diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index a1375684f9..a10a2f06ba 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -182,206 +181,6 @@ public: return result; } - virtual status_t overrideHdrTypes(const sp& display, - const std::vector& hdrTypes) { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeStrongBinder, display); - - std::vector hdrTypesVector; - for (ui::Hdr i : hdrTypes) { - hdrTypesVector.push_back(static_cast(i)); - } - SAFE_PARCEL(data.writeInt32Vector, hdrTypesVector); - - status_t result = remote()->transact(BnSurfaceComposer::OVERRIDE_HDR_TYPES, data, &reply); - if (result != NO_ERROR) { - ALOGE("overrideHdrTypes failed to transact: %d", result); - return result; - } - return result; - } - - status_t onPullAtom(const int32_t atomId, std::string* pulledData, bool* success) { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeInt32, atomId); - - status_t err = remote()->transact(BnSurfaceComposer::ON_PULL_ATOM, data, &reply); - if (err != NO_ERROR) { - ALOGE("onPullAtom failed to transact: %d", err); - return err; - } - - int32_t size = 0; - SAFE_PARCEL(reply.readInt32, &size); - const void* dataPtr = reply.readInplace(size); - if (dataPtr == nullptr) { - return UNEXPECTED_NULL; - } - pulledData->assign((const char*)dataPtr, size); - SAFE_PARCEL(reply.readBool, success); - return NO_ERROR; - } - - status_t enableVSyncInjections(bool enable) override { - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("enableVSyncInjections failed to writeInterfaceToken: %d", result); - return result; - } - result = data.writeBool(enable); - if (result != NO_ERROR) { - ALOGE("enableVSyncInjections failed to writeBool: %d", result); - return result; - } - result = remote()->transact(BnSurfaceComposer::ENABLE_VSYNC_INJECTIONS, data, &reply, - IBinder::FLAG_ONEWAY); - if (result != NO_ERROR) { - ALOGE("enableVSyncInjections failed to transact: %d", result); - return result; - } - return result; - } - - status_t injectVSync(nsecs_t when) override { - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("injectVSync failed to writeInterfaceToken: %d", result); - return result; - } - result = data.writeInt64(when); - if (result != NO_ERROR) { - ALOGE("injectVSync failed to writeInt64: %d", result); - return result; - } - result = remote()->transact(BnSurfaceComposer::INJECT_VSYNC, data, &reply, - IBinder::FLAG_ONEWAY); - if (result != NO_ERROR) { - ALOGE("injectVSync failed to transact: %d", result); - return result; - } - return result; - } - - status_t getLayerDebugInfo(std::vector* outLayers) override { - if (!outLayers) { - return UNEXPECTED_NULL; - } - - Parcel data, reply; - - status_t err = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (err != NO_ERROR) { - return err; - } - - err = remote()->transact(BnSurfaceComposer::GET_LAYER_DEBUG_INFO, data, &reply); - if (err != NO_ERROR) { - return err; - } - - int32_t result = 0; - err = reply.readInt32(&result); - if (err != NO_ERROR) { - return err; - } - if (result != NO_ERROR) { - return result; - } - - outLayers->clear(); - return reply.readParcelableVector(outLayers); - } - - status_t getCompositionPreference(ui::Dataspace* defaultDataspace, - ui::PixelFormat* defaultPixelFormat, - ui::Dataspace* wideColorGamutDataspace, - ui::PixelFormat* wideColorGamutPixelFormat) const override { - Parcel data, reply; - status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (error != NO_ERROR) { - return error; - } - error = remote()->transact(BnSurfaceComposer::GET_COMPOSITION_PREFERENCE, data, &reply); - if (error != NO_ERROR) { - return error; - } - error = static_cast(reply.readInt32()); - if (error == NO_ERROR) { - *defaultDataspace = static_cast(reply.readInt32()); - *defaultPixelFormat = static_cast(reply.readInt32()); - *wideColorGamutDataspace = static_cast(reply.readInt32()); - *wideColorGamutPixelFormat = static_cast(reply.readInt32()); - } - return error; - } - - status_t getColorManagement(bool* outGetColorManagement) const override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - remote()->transact(BnSurfaceComposer::GET_COLOR_MANAGEMENT, data, &reply); - bool result; - status_t err = reply.readBool(&result); - if (err == NO_ERROR) { - *outGetColorManagement = result; - } - return err; - } - - status_t getDisplayedContentSamplingAttributes(const sp& display, - ui::PixelFormat* outFormat, - ui::Dataspace* outDataspace, - uint8_t* outComponentMask) const override { - if (!outFormat || !outDataspace || !outComponentMask) return BAD_VALUE; - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - data.writeStrongBinder(display); - - status_t error = - remote()->transact(BnSurfaceComposer::GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES, - data, &reply); - if (error != NO_ERROR) { - return error; - } - - uint32_t value = 0; - error = reply.readUint32(&value); - if (error != NO_ERROR) { - return error; - } - *outFormat = static_cast(value); - - error = reply.readUint32(&value); - if (error != NO_ERROR) { - return error; - } - *outDataspace = static_cast(value); - - error = reply.readUint32(&value); - if (error != NO_ERROR) { - return error; - } - *outComponentMask = static_cast(value); - return error; - } - - status_t setDisplayContentSamplingEnabled(const sp& display, bool enable, - uint8_t componentMask, uint64_t maxFrames) override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - data.writeStrongBinder(display); - data.writeBool(enable); - data.writeByte(static_cast(componentMask)); - data.writeUint64(maxFrames); - status_t result = - remote()->transact(BnSurfaceComposer::SET_DISPLAY_CONTENT_SAMPLING_ENABLED, data, - &reply); - return result; - } - status_t getDisplayedContentSample(const sp& display, uint64_t maxFrames, uint64_t timestamp, DisplayedFrameStats* outStats) const override { @@ -421,18 +220,6 @@ public: return result; } - status_t getProtectedContentSupport(bool* outSupported) const override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - status_t error = - remote()->transact(BnSurfaceComposer::GET_PROTECTED_CONTENT_SUPPORT, data, &reply); - if (error != NO_ERROR) { - return error; - } - error = reply.readBool(outSupported); - return error; - } - status_t addRegionSamplingListener(const Rect& samplingArea, const sp& stopLayerHandle, const sp& listener) override { Parcel data, reply; @@ -961,116 +748,6 @@ status_t BnSurfaceComposer::onTransact( reply->writeStrongBinder(IInterface::asBinder(connection)); return NO_ERROR; } - case ENABLE_VSYNC_INJECTIONS: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - bool enable = false; - status_t result = data.readBool(&enable); - if (result != NO_ERROR) { - ALOGE("enableVSyncInjections failed to readBool: %d", result); - return result; - } - return enableVSyncInjections(enable); - } - case INJECT_VSYNC: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - int64_t when = 0; - status_t result = data.readInt64(&when); - if (result != NO_ERROR) { - ALOGE("enableVSyncInjections failed to readInt64: %d", result); - return result; - } - return injectVSync(when); - } - case GET_LAYER_DEBUG_INFO: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - std::vector outLayers; - status_t result = getLayerDebugInfo(&outLayers); - reply->writeInt32(result); - if (result == NO_ERROR) - { - result = reply->writeParcelableVector(outLayers); - } - return result; - } - case GET_COMPOSITION_PREFERENCE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - ui::Dataspace defaultDataspace; - ui::PixelFormat defaultPixelFormat; - ui::Dataspace wideColorGamutDataspace; - ui::PixelFormat wideColorGamutPixelFormat; - status_t error = - getCompositionPreference(&defaultDataspace, &defaultPixelFormat, - &wideColorGamutDataspace, &wideColorGamutPixelFormat); - reply->writeInt32(error); - if (error == NO_ERROR) { - reply->writeInt32(static_cast(defaultDataspace)); - reply->writeInt32(static_cast(defaultPixelFormat)); - reply->writeInt32(static_cast(wideColorGamutDataspace)); - reply->writeInt32(static_cast(wideColorGamutPixelFormat)); - } - return error; - } - case GET_COLOR_MANAGEMENT: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - bool result; - status_t error = getColorManagement(&result); - if (error == NO_ERROR) { - reply->writeBool(result); - } - return error; - } - case GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - - sp display = data.readStrongBinder(); - ui::PixelFormat format; - ui::Dataspace dataspace; - uint8_t component = 0; - auto result = - getDisplayedContentSamplingAttributes(display, &format, &dataspace, &component); - if (result == NO_ERROR) { - reply->writeUint32(static_cast(format)); - reply->writeUint32(static_cast(dataspace)); - reply->writeUint32(static_cast(component)); - } - return result; - } - case SET_DISPLAY_CONTENT_SAMPLING_ENABLED: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - - sp display = nullptr; - bool enable = false; - int8_t componentMask = 0; - uint64_t maxFrames = 0; - status_t result = data.readStrongBinder(&display); - if (result != NO_ERROR) { - ALOGE("setDisplayContentSamplingEnabled failure in reading Display token: %d", - result); - return result; - } - - result = data.readBool(&enable); - if (result != NO_ERROR) { - ALOGE("setDisplayContentSamplingEnabled failure in reading enable: %d", result); - return result; - } - - result = data.readByte(static_cast(&componentMask)); - if (result != NO_ERROR) { - ALOGE("setDisplayContentSamplingEnabled failure in reading component mask: %d", - result); - return result; - } - - result = data.readUint64(&maxFrames); - if (result != NO_ERROR) { - ALOGE("setDisplayContentSamplingEnabled failure in reading max frames: %d", result); - return result; - } - - return setDisplayContentSamplingEnabled(display, enable, - static_cast(componentMask), maxFrames); - } case GET_DISPLAYED_CONTENT_SAMPLE: { CHECK_INTERFACE(ISurfaceComposer, data, reply); @@ -1101,15 +778,6 @@ status_t BnSurfaceComposer::onTransact( } return result; } - case GET_PROTECTED_CONTENT_SUPPORT: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - bool result; - status_t error = getProtectedContentSupport(&result); - if (error == NO_ERROR) { - reply->writeBool(result); - } - return error; - } case ADD_REGION_SAMPLING_LISTENER: { CHECK_INTERFACE(ISurfaceComposer, data, reply); Rect samplingArea; @@ -1413,33 +1081,6 @@ status_t BnSurfaceComposer::onTransact( SAFE_PARCEL(reply->writeInt32, buffers); return NO_ERROR; } - case OVERRIDE_HDR_TYPES: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp display = nullptr; - SAFE_PARCEL(data.readStrongBinder, &display); - - std::vector hdrTypes; - SAFE_PARCEL(data.readInt32Vector, &hdrTypes); - - std::vector hdrTypesVector; - for (int i : hdrTypes) { - hdrTypesVector.push_back(static_cast(i)); - } - return overrideHdrTypes(display, hdrTypesVector); - } - case ON_PULL_ATOM: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - int32_t atomId = 0; - SAFE_PARCEL(data.readInt32, &atomId); - - std::string pulledData; - bool success; - status_t err = onPullAtom(atomId, &pulledData, &success); - SAFE_PARCEL(reply->writeByteArray, pulledData.size(), - reinterpret_cast(pulledData.data())); - SAFE_PARCEL(reply->writeBool, success); - return err; - } case ADD_WINDOW_INFOS_LISTENER: { CHECK_INTERFACE(ISurfaceComposer, data, reply); sp listener; diff --git a/libs/gui/LayerDebugInfo.cpp b/libs/gui/LayerDebugInfo.cpp index ea5fb293a6..15b2221464 100644 --- a/libs/gui/LayerDebugInfo.cpp +++ b/libs/gui/LayerDebugInfo.cpp @@ -27,7 +27,7 @@ using android::base::StringAppendF; #define RETURN_ON_ERROR(X) do {status_t res = (X); if (res != NO_ERROR) return res;} while(false) -namespace android { +namespace android::gui { status_t LayerDebugInfo::writeToParcel(Parcel* parcel) const { RETURN_ON_ERROR(parcel->writeCString(mName.c_str())); @@ -149,4 +149,4 @@ std::string to_string(const LayerDebugInfo& info) { return result; } -} // android +} // namespace android::gui diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index be5f338c39..ca48ce63d0 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2118,13 +2118,15 @@ status_t SurfaceComposerClient::getLayerFrameStats(const sp& token, // ---------------------------------------------------------------------------- status_t SurfaceComposerClient::enableVSyncInjections(bool enable) { - sp sf(ComposerService::getComposerService()); - return sf->enableVSyncInjections(enable); + sp sf(ComposerServiceAIDL::getComposerService()); + binder::Status status = sf->enableVSyncInjections(enable); + return status.transactionError(); } status_t SurfaceComposerClient::injectVSync(nsecs_t when) { - sp sf(ComposerService::getComposerService()); - return sf->injectVSync(when); + sp sf(ComposerServiceAIDL::getComposerService()); + binder::Status status = sf->injectVSync(when); + return status.transactionError(); } status_t SurfaceComposerClient::getDisplayState(const sp& display, @@ -2348,14 +2350,21 @@ void SurfaceComposerClient::setDisplayPowerMode(const sp& token, status_t SurfaceComposerClient::getCompositionPreference( ui::Dataspace* defaultDataspace, ui::PixelFormat* defaultPixelFormat, ui::Dataspace* wideColorGamutDataspace, ui::PixelFormat* wideColorGamutPixelFormat) { - return ComposerService::getComposerService() - ->getCompositionPreference(defaultDataspace, defaultPixelFormat, - wideColorGamutDataspace, wideColorGamutPixelFormat); + gui::CompositionPreference pref; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getCompositionPreference(&pref); + if (status.isOk()) { + *defaultDataspace = static_cast(pref.defaultDataspace); + *defaultPixelFormat = static_cast(pref.defaultPixelFormat); + *wideColorGamutDataspace = static_cast(pref.wideColorGamutDataspace); + *wideColorGamutPixelFormat = static_cast(pref.wideColorGamutPixelFormat); + } + return status.transactionError(); } bool SurfaceComposerClient::getProtectedContentSupport() { bool supported = false; - ComposerService::getComposerService()->getProtectedContentSupport(&supported); + ComposerServiceAIDL::getComposerService()->getProtectedContentSupport(&supported); return supported; } @@ -2388,29 +2397,56 @@ status_t SurfaceComposerClient::getAnimationFrameStats(FrameStats* outStats) { status_t SurfaceComposerClient::overrideHdrTypes(const sp& display, const std::vector& hdrTypes) { - return ComposerService::getComposerService()->overrideHdrTypes(display, hdrTypes); + std::vector hdrTypesVector; + hdrTypesVector.reserve(hdrTypes.size()); + for (auto t : hdrTypes) { + hdrTypesVector.push_back(static_cast(t)); + } + + binder::Status status = + ComposerServiceAIDL::getComposerService()->overrideHdrTypes(display, hdrTypesVector); + return status.transactionError(); } status_t SurfaceComposerClient::onPullAtom(const int32_t atomId, std::string* outData, bool* success) { - return ComposerService::getComposerService()->onPullAtom(atomId, outData, success); + gui::PullAtomData pad; + binder::Status status = ComposerServiceAIDL::getComposerService()->onPullAtom(atomId, &pad); + if (status.isOk()) { + outData->assign((const char*)pad.data.data(), pad.data.size()); + *success = pad.success; + } + return status.transactionError(); } status_t SurfaceComposerClient::getDisplayedContentSamplingAttributes(const sp& display, ui::PixelFormat* outFormat, ui::Dataspace* outDataspace, uint8_t* outComponentMask) { - return ComposerService::getComposerService() - ->getDisplayedContentSamplingAttributes(display, outFormat, outDataspace, - outComponentMask); + if (!outFormat || !outDataspace || !outComponentMask) { + return BAD_VALUE; + } + + gui::ContentSamplingAttributes attrs; + binder::Status status = ComposerServiceAIDL::getComposerService() + ->getDisplayedContentSamplingAttributes(display, &attrs); + if (status.isOk()) { + *outFormat = static_cast(attrs.format); + *outDataspace = static_cast(attrs.dataspace); + *outComponentMask = static_cast(attrs.componentMask); + } + return status.transactionError(); } status_t SurfaceComposerClient::setDisplayContentSamplingEnabled(const sp& display, bool enable, uint8_t componentMask, uint64_t maxFrames) { - return ComposerService::getComposerService()->setDisplayContentSamplingEnabled(display, enable, - componentMask, - maxFrames); + binder::Status status = + ComposerServiceAIDL::getComposerService() + ->setDisplayContentSamplingEnabled(display, enable, + static_cast(componentMask), + static_cast(maxFrames)); + return status.transactionError(); } status_t SurfaceComposerClient::getDisplayedContentSample(const sp& display, diff --git a/libs/gui/aidl/android/gui/CompositionPreference.aidl b/libs/gui/aidl/android/gui/CompositionPreference.aidl new file mode 100644 index 0000000000..b615824a7d --- /dev/null +++ b/libs/gui/aidl/android/gui/CompositionPreference.aidl @@ -0,0 +1,25 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +/** @hide */ +parcelable CompositionPreference { + int /*ui::Dataspace*/ defaultDataspace; + int /*ui::PixelFormat*/ defaultPixelFormat; + int /*ui::Dataspace*/ wideColorGamutDataspace; + int /*ui::PixelFormat*/ wideColorGamutPixelFormat; +} diff --git a/libs/gui/aidl/android/gui/ContentSamplingAttributes.aidl b/libs/gui/aidl/android/gui/ContentSamplingAttributes.aidl new file mode 100644 index 0000000000..5d913b1da6 --- /dev/null +++ b/libs/gui/aidl/android/gui/ContentSamplingAttributes.aidl @@ -0,0 +1,24 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +/** @hide */ +parcelable ContentSamplingAttributes { + int /*ui::PixelFormat*/ format; + int /*ui::Dataspace*/ dataspace; + byte componentMask; +} diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 42e2d9bcf6..dc77416010 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -16,6 +16,8 @@ package android.gui; +import android.gui.CompositionPreference; +import android.gui.ContentSamplingAttributes; import android.gui.DisplayCaptureArgs; import android.gui.DisplayBrightness; import android.gui.DisplayPrimaries; @@ -23,6 +25,8 @@ import android.gui.DisplayState; import android.gui.DisplayStatInfo; import android.gui.FrameEvent; import android.gui.FrameStats; +import android.gui.LayerDebugInfo; +import android.gui.PullAtomData; import android.gui.StaticDisplayInfo; import android.gui.DynamicDisplayInfo; import android.gui.IHdrLayerInfoListener; @@ -31,43 +35,50 @@ import android.gui.IScreenCaptureListener; /** @hide */ interface ISurfaceComposer { - /* create a virtual display + /** + * Create a virtual display * requires ACCESS_SURFACE_FLINGER permission. */ @nullable IBinder createDisplay(@utf8InCpp String displayName, boolean secure); - /* destroy a virtual display + /** + * Destroy a virtual display * requires ACCESS_SURFACE_FLINGER permission. */ void destroyDisplay(IBinder display); - /* get stable IDs for connected physical displays. + /** + * Get stable IDs for connected physical displays. */ long[] getPhysicalDisplayIds(); long getPrimaryPhysicalDisplayId(); - /* get token for a physical display given its stable ID obtained via getPhysicalDisplayIds or a - * DisplayEventReceiver hotplug event. + /** + * Get token for a physical display given its stable ID obtained via getPhysicalDisplayIds or + * a DisplayEventReceiver hotplug event. */ @nullable IBinder getPhysicalDisplayToken(long displayId); - /* Returns the frame timestamps supported by SurfaceFlinger. + /** + * Returns the frame timestamps supported by SurfaceFlinger. */ FrameEvent[] getSupportedFrameTimestamps(); - /* set display power mode. depending on the mode, it can either trigger + /** + * Set display power mode. depending on the mode, it can either trigger * screen on, off or low power mode and wait for it to complete. * requires ACCESS_SURFACE_FLINGER permission. */ void setPowerMode(IBinder display, int mode); - /* returns display statistics for a given display + /** + * Returns display statistics for a given display * intended to be used by the media framework to properly schedule * video frames */ DisplayStatInfo getDisplayStats(IBinder display); - /** + /** * Get transactional state of given display. */ DisplayState getDisplayState(IBinder display); @@ -136,7 +147,9 @@ interface ISurfaceComposer { * match the size of the output buffer. */ void captureDisplay(in DisplayCaptureArgs args, IScreenCaptureListener listener); + void captureDisplayById(long displayId, IScreenCaptureListener listener); + /** * Capture a subtree of the layer hierarchy, potentially ignoring the root node. * This requires READ_FRAME_BUFFER permission. This function will fail if there @@ -144,25 +157,80 @@ interface ISurfaceComposer { */ void captureLayers(in LayerCaptureArgs args, IScreenCaptureListener listener); - /* Clears the frame statistics for animations. + /** + * Clears the frame statistics for animations. * * Requires the ACCESS_SURFACE_FLINGER permission. */ void clearAnimationFrameStats(); - /* Gets the frame statistics for animations. + /** + * Gets the frame statistics for animations. * * Requires the ACCESS_SURFACE_FLINGER permission. */ FrameStats getAnimationFrameStats(); - /* + /** + * Overrides the supported HDR modes for the given display device. + * + * Requires the ACCESS_SURFACE_FLINGER permission. + */ + void overrideHdrTypes(IBinder display, in int[] hdrTypes); + + /** + * Pulls surfaceflinger atoms global stats and layer stats to pipe to statsd. + * + * Requires the calling uid be from system server. + */ + PullAtomData onPullAtom(int atomId); + + oneway void enableVSyncInjections(boolean enable); + + oneway void injectVSync(long when); + + /** + * Gets the list of active layers in Z order for debugging purposes + * + * Requires the ACCESS_SURFACE_FLINGER permission. + */ + List getLayerDebugInfo(); + + boolean getColorManagement(); + + /** + * Gets the composition preference of the default data space and default pixel format, + * as well as the wide color gamut data space and wide color gamut pixel format. + * If the wide color gamut data space is V0_SRGB, then it implies that the platform + * has no wide color gamut support. + * + */ + CompositionPreference getCompositionPreference(); + + /** + * Requires the ACCESS_SURFACE_FLINGER permission. + */ + ContentSamplingAttributes getDisplayedContentSamplingAttributes(IBinder display); + + /** + * Turns on the color sampling engine on the display. + * + * Requires the ACCESS_SURFACE_FLINGER permission. + */ + void setDisplayContentSamplingEnabled(IBinder display, boolean enable, byte componentMask, long maxFrames); + + /** + * Gets whether SurfaceFlinger can support protected content in GPU composition. + */ + boolean getProtectedContentSupport(); + + /** * Queries whether the given display is a wide color display. * Requires the ACCESS_SURFACE_FLINGER permission. */ boolean isWideColorDisplay(IBinder token); - /* + /** * Gets whether brightness operations are supported on a display. * * displayToken @@ -176,7 +244,7 @@ interface ISurfaceComposer { */ boolean getDisplayBrightnessSupport(IBinder displayToken); - /* + /** * Sets the brightness of a display. * * displayToken @@ -191,7 +259,7 @@ interface ISurfaceComposer { */ void setDisplayBrightness(IBinder displayToken, in DisplayBrightness brightness); - /* + /** * Adds a listener that receives HDR layer information. This is used in combination * with setDisplayBrightness to adjust the display brightness depending on factors such * as whether or not HDR is in use. @@ -200,7 +268,7 @@ interface ISurfaceComposer { */ void addHdrLayerInfoListener(IBinder displayToken, IHdrLayerInfoListener listener); - /* + /** * Removes a listener that was added with addHdrLayerInfoListener. * * Returns NO_ERROR upon success, NAME_NOT_FOUND if the display is invalid, and BAD_VALUE if @@ -209,7 +277,7 @@ interface ISurfaceComposer { */ void removeHdrLayerInfoListener(IBinder displayToken, IHdrLayerInfoListener listener); - /* + /** * Sends a power boost to the composer. This function is asynchronous. * * boostId @@ -217,5 +285,5 @@ interface ISurfaceComposer { * * Returns NO_ERROR upon success. */ - void notifyPowerBoost(int boostId); + oneway void notifyPowerBoost(int boostId); } diff --git a/libs/gui/aidl/android/gui/LayerDebugInfo.aidl b/libs/gui/aidl/android/gui/LayerDebugInfo.aidl new file mode 100644 index 0000000000..faca980f3c --- /dev/null +++ b/libs/gui/aidl/android/gui/LayerDebugInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +parcelable LayerDebugInfo cpp_header "gui/LayerDebugInfo.h"; diff --git a/libs/gui/aidl/android/gui/PullAtomData.aidl b/libs/gui/aidl/android/gui/PullAtomData.aidl new file mode 100644 index 0000000000..14d33c6d0b --- /dev/null +++ b/libs/gui/aidl/android/gui/PullAtomData.aidl @@ -0,0 +1,23 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +/** @hide */ +parcelable PullAtomData { + @utf8InCpp String data; + boolean success; +} diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index 138d2edebd..927912463b 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -61,7 +61,6 @@ struct ComposerState; struct DisplayStatInfo; struct DisplayState; struct InputWindowCommands; -class LayerDebugInfo; class HdrCapabilities; class IGraphicBufferProducer; class ISurfaceComposerClient; @@ -76,6 +75,7 @@ namespace gui { struct DisplayCaptureArgs; struct LayerCaptureArgs; +class LayerDebugInfo; } // namespace gui @@ -154,58 +154,6 @@ public: virtual bool authenticateSurfaceTexture( const sp& surface) const = 0; - /* Overrides the supported HDR modes for the given display device. - * - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t overrideHdrTypes(const sp& display, - const std::vector& hdrTypes) = 0; - - /* Pulls surfaceflinger atoms global stats and layer stats to pipe to statsd. - * - * Requires the calling uid be from system server. - */ - virtual status_t onPullAtom(const int32_t atomId, std::string* outData, bool* success) = 0; - - virtual status_t enableVSyncInjections(bool enable) = 0; - - virtual status_t injectVSync(nsecs_t when) = 0; - - /* Gets the list of active layers in Z order for debugging purposes - * - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t getLayerDebugInfo(std::vector* outLayers) = 0; - - virtual status_t getColorManagement(bool* outGetColorManagement) const = 0; - - /* Gets the composition preference of the default data space and default pixel format, - * as well as the wide color gamut data space and wide color gamut pixel format. - * If the wide color gamut data space is V0_SRGB, then it implies that the platform - * has no wide color gamut support. - * - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t getCompositionPreference(ui::Dataspace* defaultDataspace, - ui::PixelFormat* defaultPixelFormat, - ui::Dataspace* wideColorGamutDataspace, - ui::PixelFormat* wideColorGamutPixelFormat) const = 0; - /* - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t getDisplayedContentSamplingAttributes(const sp& display, - ui::PixelFormat* outFormat, - ui::Dataspace* outDataspace, - uint8_t* outComponentMask) const = 0; - - /* Turns on the color sampling engine on the display. - * - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t setDisplayContentSamplingEnabled(const sp& display, bool enable, - uint8_t componentMask, - uint64_t maxFrames) = 0; - /* Returns statistics on the color profile of the last frame displayed for a given display * * Requires the ACCESS_SURFACE_FLINGER permission. @@ -214,12 +162,6 @@ public: uint64_t timestamp, DisplayedFrameStats* outStats) const = 0; - /* - * Gets whether SurfaceFlinger can support protected content in GPU composition. - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t getProtectedContentSupport(bool* outSupported) const = 0; - /* Registers a listener to stream median luma updates from SurfaceFlinger. * * The sampling area is bounded by both samplingArea and the given stopLayerHandle @@ -421,22 +363,22 @@ public: GET_ANIMATION_FRAME_STATS, // Deprecated. Autogenerated by .aidl now. SET_POWER_MODE, // Deprecated. Autogenerated by .aidl now. GET_DISPLAY_STATS, - GET_HDR_CAPABILITIES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. - GET_DISPLAY_COLOR_MODES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. - GET_ACTIVE_COLOR_MODE, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. - SET_ACTIVE_COLOR_MODE, // Deprecated. Autogenerated by .aidl now. - ENABLE_VSYNC_INJECTIONS, - INJECT_VSYNC, - GET_LAYER_DEBUG_INFO, - GET_COMPOSITION_PREFERENCE, - GET_COLOR_MANAGEMENT, - GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES, - SET_DISPLAY_CONTENT_SAMPLING_ENABLED, + GET_HDR_CAPABILITIES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. + GET_DISPLAY_COLOR_MODES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. + GET_ACTIVE_COLOR_MODE, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. + SET_ACTIVE_COLOR_MODE, // Deprecated. Autogenerated by .aidl now. + ENABLE_VSYNC_INJECTIONS, // Deprecated. Autogenerated by .aidl now. + INJECT_VSYNC, // Deprecated. Autogenerated by .aidl now. + GET_LAYER_DEBUG_INFO, // Deprecated. Autogenerated by .aidl now. + GET_COMPOSITION_PREFERENCE, // Deprecated. Autogenerated by .aidl now. + GET_COLOR_MANAGEMENT, // Deprecated. Autogenerated by .aidl now. + GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES, // Deprecated. Autogenerated by .aidl now. + SET_DISPLAY_CONTENT_SAMPLING_ENABLED, // Deprecated. Autogenerated by .aidl now. GET_DISPLAYED_CONTENT_SAMPLE, - GET_PROTECTED_CONTENT_SUPPORT, - IS_WIDE_COLOR_DISPLAY, // Deprecated. Autogenerated by .aidl now. - GET_DISPLAY_NATIVE_PRIMARIES, // Deprecated. Autogenerated by .aidl now. - GET_PHYSICAL_DISPLAY_IDS, // Deprecated. Autogenerated by .aidl now. + GET_PROTECTED_CONTENT_SUPPORT, // Deprecated. Autogenerated by .aidl now. + IS_WIDE_COLOR_DISPLAY, // Deprecated. Autogenerated by .aidl now. + GET_DISPLAY_NATIVE_PRIMARIES, // Deprecated. Autogenerated by .aidl now. + GET_PHYSICAL_DISPLAY_IDS, // Deprecated. Autogenerated by .aidl now. ADD_REGION_SAMPLING_LISTENER, REMOVE_REGION_SAMPLING_LISTENER, SET_DESIRED_DISPLAY_MODE_SPECS, @@ -460,10 +402,10 @@ public: GET_DYNAMIC_DISPLAY_INFO, // Deprecated. Autogenerated by .aidl now. ADD_FPS_LISTENER, REMOVE_FPS_LISTENER, - OVERRIDE_HDR_TYPES, + OVERRIDE_HDR_TYPES, // Deprecated. Autogenerated by .aidl now. ADD_HDR_LAYER_INFO_LISTENER, // Deprecated. Autogenerated by .aidl now. REMOVE_HDR_LAYER_INFO_LISTENER, // Deprecated. Autogenerated by .aidl now. - ON_PULL_ATOM, + ON_PULL_ATOM, // Deprecated. Autogenerated by .aidl now. ADD_TUNNEL_MODE_ENABLED_LISTENER, REMOVE_TUNNEL_MODE_ENABLED_LISTENER, ADD_WINDOW_INFOS_LISTENER, diff --git a/libs/gui/include/gui/LayerDebugInfo.h b/libs/gui/include/gui/LayerDebugInfo.h index af834d78df..1c1bbef123 100644 --- a/libs/gui/include/gui/LayerDebugInfo.h +++ b/libs/gui/include/gui/LayerDebugInfo.h @@ -25,7 +25,7 @@ #include #include -namespace android { +namespace android::gui { /* Class for transporting debug info from SurfaceFlinger to authorized * recipients. The class is intended to be a data container. There are @@ -71,4 +71,4 @@ public: std::string to_string(const LayerDebugInfo& info); -} // namespace android +} // namespace android::gui diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index a644e042c9..58964d6878 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -714,47 +714,12 @@ public: return false; } - status_t overrideHdrTypes(const sp& /*display*/, - const std::vector& /*hdrTypes*/) override { - return NO_ERROR; - } - status_t onPullAtom(const int32_t /*atomId*/, std::string* /*outData*/, - bool* /*success*/) override { - return NO_ERROR; - } - status_t enableVSyncInjections(bool /*enable*/) override { - return NO_ERROR; - } - status_t injectVSync(nsecs_t /*when*/) override { return NO_ERROR; } - status_t getLayerDebugInfo(std::vector* /*layers*/) override { - return NO_ERROR; - } - status_t getCompositionPreference( - ui::Dataspace* /*outDefaultDataspace*/, ui::PixelFormat* /*outDefaultPixelFormat*/, - ui::Dataspace* /*outWideColorGamutDataspace*/, - ui::PixelFormat* /*outWideColorGamutPixelFormat*/) const override { - return NO_ERROR; - } - status_t getDisplayedContentSamplingAttributes(const sp& /*display*/, - ui::PixelFormat* /*outFormat*/, - ui::Dataspace* /*outDataspace*/, - uint8_t* /*outComponentMask*/) const override { - return NO_ERROR; - } - status_t setDisplayContentSamplingEnabled(const sp& /*display*/, bool /*enable*/, - uint8_t /*componentMask*/, - uint64_t /*maxFrames*/) override { - return NO_ERROR; - } status_t getDisplayedContentSample(const sp& /*display*/, uint64_t /*maxFrames*/, uint64_t /*timestamp*/, DisplayedFrameStats* /*outStats*/) const override { return NO_ERROR; } - status_t getColorManagement(bool* /*outGetColorManagement*/) const override { return NO_ERROR; } - status_t getProtectedContentSupport(bool* /*outSupported*/) const override { return NO_ERROR; } - status_t addRegionSamplingListener(const Rect& /*samplingArea*/, const sp& /*stopLayerHandle*/, const sp& /*listener*/) override { @@ -964,6 +929,46 @@ public: return binder::Status::ok(); } + binder::Status overrideHdrTypes(const sp& /*display*/, + const std::vector& /*hdrTypes*/) override { + return binder::Status::ok(); + } + + binder::Status onPullAtom(int32_t /*atomId*/, gui::PullAtomData* /*outPullData*/) override { + return binder::Status::ok(); + } + + binder::Status enableVSyncInjections(bool /*enable*/) override { return binder::Status::ok(); } + + binder::Status injectVSync(int64_t /*when*/) override { return binder::Status::ok(); } + + binder::Status getLayerDebugInfo(std::vector* /*outLayers*/) override { + return binder::Status::ok(); + } + + binder::Status getColorManagement(bool* /*outGetColorManagement*/) override { + return binder::Status::ok(); + } + + binder::Status getCompositionPreference(gui::CompositionPreference* /*outPref*/) override { + return binder::Status::ok(); + } + + binder::Status getDisplayedContentSamplingAttributes( + const sp& /*display*/, gui::ContentSamplingAttributes* /*outAttrs*/) override { + return binder::Status::ok(); + } + + binder::Status setDisplayContentSamplingEnabled(const sp& /*display*/, bool /*enable*/, + int8_t /*componentMask*/, + int64_t /*maxFrames*/) override { + return binder::Status::ok(); + } + + binder::Status getProtectedContentSupport(bool* /*outSupporte*/) override { + return binder::Status::ok(); + } + binder::Status isWideColorDisplay(const sp& /*token*/, bool* /*outIsWideColorDisplay*/) override { return binder::Status::ok(); diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 3d00b90816..1b56cb224f 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -1386,10 +1386,10 @@ void Layer::updateTransformHint(ui::Transform::RotationFlags transformHint) { // ---------------------------------------------------------------------------- // TODO(marissaw): add new layer state info to layer debugging -LayerDebugInfo Layer::getLayerDebugInfo(const DisplayDevice* display) const { +gui::LayerDebugInfo Layer::getLayerDebugInfo(const DisplayDevice* display) const { using namespace std::string_literals; - LayerDebugInfo info; + gui::LayerDebugInfo info; const State& ds = getDrawingState(); info.mName = getName(); sp parent = mDrawingParent.promote(); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 565a6ff726..455920be62 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -69,13 +69,16 @@ class Colorizer; class DisplayDevice; class GraphicBuffer; class SurfaceFlinger; -class LayerDebugInfo; namespace compositionengine { class OutputLayer; struct LayerFECompositionState; } +namespace gui { +class LayerDebugInfo; +} + namespace impl { class SurfaceInterceptor; } @@ -741,7 +744,7 @@ public: inline const State& getDrawingState() const { return mDrawingState; } inline State& getDrawingState() { return mDrawingState; } - LayerDebugInfo getLayerDebugInfo(const DisplayDevice*) const; + gui::LayerDebugInfo getLayerDebugInfo(const DisplayDevice*) const; void miniDump(std::string& result, const DisplayDevice&) const; void dumpFrameStats(std::string& result) const; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index b6d00b2da9..c9afc921af 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1561,7 +1561,7 @@ status_t SurfaceFlinger::injectVSync(nsecs_t when) { : BAD_VALUE; } -status_t SurfaceFlinger::getLayerDebugInfo(std::vector* outLayers) { +status_t SurfaceFlinger::getLayerDebugInfo(std::vector* outLayers) { outLayers->clear(); auto future = mScheduler->schedule([=] { const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()); @@ -5459,21 +5459,14 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { #pragma clang diagnostic push #pragma clang diagnostic error "-Wswitch-enum" switch (static_cast(code)) { - case ENABLE_VSYNC_INJECTIONS: - case INJECT_VSYNC: - if (!hasMockHwc()) return PERMISSION_DENIED; - [[fallthrough]]; // These methods should at minimum make sure that the client requested // access to SF. case BOOT_FINISHED: - case OVERRIDE_HDR_TYPES: case GET_HDR_CAPABILITIES: case SET_DESIRED_DISPLAY_MODE_SPECS: case GET_DESIRED_DISPLAY_MODE_SPECS: case GET_AUTO_LOW_LATENCY_MODE_SUPPORT: case GET_GAME_CONTENT_TYPE_SUPPORT: - case GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES: - case SET_DISPLAY_CONTENT_SAMPLING_ENABLED: case GET_DISPLAYED_CONTENT_SAMPLE: case ADD_TUNNEL_MODE_ENABLED_LISTENER: case REMOVE_TUNNEL_MODE_ENABLED_LISTENER: @@ -5490,16 +5483,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { } return OK; } - case GET_LAYER_DEBUG_INFO: { - IPCThreadState* ipc = IPCThreadState::self(); - const int pid = ipc->getCallingPid(); - const int uid = ipc->getCallingUid(); - if ((uid != AID_SHELL) && !PermissionCache::checkPermission(sDump, pid, uid)) { - ALOGE("Layer debug info permission denied for pid=%d, uid=%d", pid, uid); - return PERMISSION_DENIED; - } - return OK; - } // Used by apps to hook Choreographer to SurfaceFlinger. case CREATE_DISPLAY_EVENT_CONNECTION: // The following calls are currently used by clients that do not @@ -5514,9 +5497,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { // granted a reference to Client* and Handle* to do anything with it. case SET_TRANSACTION_STATE: case CREATE_CONNECTION: - case GET_COLOR_MANAGEMENT: - case GET_COMPOSITION_PREFERENCE: - case GET_PROTECTED_CONTENT_SUPPORT: // setFrameRate() is deliberately available for apps to call without any // special permissions. case SET_FRAME_RATE: @@ -5557,13 +5537,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { } return PERMISSION_DENIED; } - case ON_PULL_ATOM: { - const int uid = IPCThreadState::self()->getCallingUid(); - if (uid == AID_SYSTEM) { - return OK; - } - return PERMISSION_DENIED; - } case ADD_WINDOW_INFOS_LISTENER: case REMOVE_WINDOW_INFOS_LISTENER: { const int uid = IPCThreadState::self()->getCallingUid(); @@ -5595,6 +5568,16 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case CAPTURE_DISPLAY_BY_ID: case CLEAR_ANIMATION_FRAME_STATS: case GET_ANIMATION_FRAME_STATS: + case OVERRIDE_HDR_TYPES: + case ON_PULL_ATOM: + case ENABLE_VSYNC_INJECTIONS: + case INJECT_VSYNC: + case GET_LAYER_DEBUG_INFO: + case GET_COLOR_MANAGEMENT: + case GET_COMPOSITION_PREFERENCE: + case GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES: + case SET_DISPLAY_CONTENT_SAMPLING_ENABLED: + case GET_PROTECTED_CONTENT_SUPPORT: case IS_WIDE_COLOR_DISPLAY: case GET_DISPLAY_BRIGHTNESS_SUPPORT: case SET_DISPLAY_BRIGHTNESS: @@ -7632,6 +7615,134 @@ binder::Status SurfaceComposerAIDL::getAnimationFrameStats(gui::FrameStats* outS return binder::Status::fromStatusT(status); } +binder::Status SurfaceComposerAIDL::overrideHdrTypes(const sp& display, + const std::vector& hdrTypes) { + // overrideHdrTypes is used by CTS tests, which acquire the necessary + // permission dynamically. Don't use the permission cache for this check. + status_t status = checkAccessPermission(false); + if (status != OK) { + return binder::Status::fromStatusT(status); + } + + std::vector hdrTypesVector; + for (int32_t i : hdrTypes) { + hdrTypesVector.push_back(static_cast(i)); + } + status = mFlinger->overrideHdrTypes(display, hdrTypesVector); + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::onPullAtom(int32_t atomId, gui::PullAtomData* outPullData) { + status_t status; + const int uid = IPCThreadState::self()->getCallingUid(); + if (uid != AID_SYSTEM) { + status = PERMISSION_DENIED; + } else { + status = mFlinger->onPullAtom(atomId, &outPullData->data, &outPullData->success); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::enableVSyncInjections(bool enable) { + if (!mFlinger->hasMockHwc()) { + return binder::Status::fromStatusT(PERMISSION_DENIED); + } + + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->enableVSyncInjections(enable); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::injectVSync(int64_t when) { + if (!mFlinger->hasMockHwc()) { + return binder::Status::fromStatusT(PERMISSION_DENIED); + } + + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->injectVSync(when); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getLayerDebugInfo(std::vector* outLayers) { + if (!outLayers) { + return binder::Status::fromStatusT(UNEXPECTED_NULL); + } + + IPCThreadState* ipc = IPCThreadState::self(); + const int pid = ipc->getCallingPid(); + const int uid = ipc->getCallingUid(); + if ((uid != AID_SHELL) && !PermissionCache::checkPermission(sDump, pid, uid)) { + ALOGE("Layer debug info permission denied for pid=%d, uid=%d", pid, uid); + return binder::Status::fromStatusT(PERMISSION_DENIED); + } + status_t status = mFlinger->getLayerDebugInfo(outLayers); + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getColorManagement(bool* outGetColorManagement) { + status_t status = mFlinger->getColorManagement(outGetColorManagement); + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getCompositionPreference(gui::CompositionPreference* outPref) { + ui::Dataspace dataspace; + ui::PixelFormat pixelFormat; + ui::Dataspace wideColorGamutDataspace; + ui::PixelFormat wideColorGamutPixelFormat; + status_t status = + mFlinger->getCompositionPreference(&dataspace, &pixelFormat, &wideColorGamutDataspace, + &wideColorGamutPixelFormat); + if (status == NO_ERROR) { + outPref->defaultDataspace = static_cast(dataspace); + outPref->defaultPixelFormat = static_cast(pixelFormat); + outPref->wideColorGamutDataspace = static_cast(wideColorGamutDataspace); + outPref->wideColorGamutPixelFormat = static_cast(wideColorGamutPixelFormat); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getDisplayedContentSamplingAttributes( + const sp& display, gui::ContentSamplingAttributes* outAttrs) { + status_t status = checkAccessPermission(); + if (status != OK) { + return binder::Status::fromStatusT(status); + } + + ui::PixelFormat format; + ui::Dataspace dataspace; + uint8_t componentMask; + status = mFlinger->getDisplayedContentSamplingAttributes(display, &format, &dataspace, + &componentMask); + if (status == NO_ERROR) { + outAttrs->format = static_cast(format); + outAttrs->dataspace = static_cast(dataspace); + outAttrs->componentMask = static_cast(componentMask); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::setDisplayContentSamplingEnabled(const sp& display, + bool enable, + int8_t componentMask, + int64_t maxFrames) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->setDisplayContentSamplingEnabled(display, enable, + static_cast(componentMask), + static_cast(maxFrames)); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getProtectedContentSupport(bool* outSupported) { + status_t status = mFlinger->getProtectedContentSupport(outSupported); + return binder::Status::fromStatusT(status); +} + binder::Status SurfaceComposerAIDL::isWideColorDisplay(const sp& token, bool* outIsWideColorDisplay) { status_t status = mFlinger->isWideColorDisplay(token, outIsWideColorDisplay); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 1ca36bdf72..b32462dadc 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -582,25 +583,24 @@ private: status_t clearAnimationFrameStats(); status_t getAnimationFrameStats(FrameStats* outStats) const; status_t overrideHdrTypes(const sp& displayToken, - const std::vector& hdrTypes) override; - status_t onPullAtom(const int32_t atomId, std::string* pulledData, bool* success) override; - status_t enableVSyncInjections(bool enable) override; - status_t injectVSync(nsecs_t when) override; - status_t getLayerDebugInfo(std::vector* outLayers) override; - status_t getColorManagement(bool* outGetColorManagement) const override; + const std::vector& hdrTypes); + status_t onPullAtom(const int32_t atomId, std::string* pulledData, bool* success); + status_t enableVSyncInjections(bool enable); + status_t injectVSync(nsecs_t when); + status_t getLayerDebugInfo(std::vector* outLayers); + status_t getColorManagement(bool* outGetColorManagement) const; status_t getCompositionPreference(ui::Dataspace* outDataspace, ui::PixelFormat* outPixelFormat, ui::Dataspace* outWideColorGamutDataspace, - ui::PixelFormat* outWideColorGamutPixelFormat) const override; + ui::PixelFormat* outWideColorGamutPixelFormat) const; status_t getDisplayedContentSamplingAttributes(const sp& displayToken, ui::PixelFormat* outFormat, ui::Dataspace* outDataspace, - uint8_t* outComponentMask) const override; + uint8_t* outComponentMask) const; status_t setDisplayContentSamplingEnabled(const sp& displayToken, bool enable, - uint8_t componentMask, uint64_t maxFrames) override; + uint8_t componentMask, uint64_t maxFrames); status_t getDisplayedContentSample(const sp& displayToken, uint64_t maxFrames, - uint64_t timestamp, - DisplayedFrameStats* outStats) const override; - status_t getProtectedContentSupport(bool* outSupported) const override; + uint64_t timestamp, DisplayedFrameStats* outStats) const; + status_t getProtectedContentSupport(bool* outSupported) const; status_t isWideColorDisplay(const sp& displayToken, bool* outIsWideColorDisplay) const; status_t addRegionSamplingListener(const Rect& samplingArea, const sp& stopLayerHandle, const sp& listener) override; @@ -1480,6 +1480,20 @@ public: const sp&) override; binder::Status clearAnimationFrameStats() override; binder::Status getAnimationFrameStats(gui::FrameStats* outStats) override; + binder::Status overrideHdrTypes(const sp& display, + const std::vector& hdrTypes) override; + binder::Status onPullAtom(int32_t atomId, gui::PullAtomData* outPullData) override; + binder::Status enableVSyncInjections(bool enable) override; + binder::Status injectVSync(int64_t when) override; + binder::Status getLayerDebugInfo(std::vector* outLayers) override; + binder::Status getColorManagement(bool* outGetColorManagement) override; + binder::Status getCompositionPreference(gui::CompositionPreference* outPref) override; + binder::Status getDisplayedContentSamplingAttributes( + const sp& display, gui::ContentSamplingAttributes* outAttrs) override; + binder::Status setDisplayContentSamplingEnabled(const sp& display, bool enable, + int8_t componentMask, + int64_t maxFrames) override; + binder::Status getProtectedContentSupport(bool* outSupporte) override; binder::Status isWideColorDisplay(const sp& token, bool* outIsWideColorDisplay) override; binder::Status getDisplayBrightnessSupport(const sp& displayToken, diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp index d33bc1080c..6549a224e6 100644 --- a/services/surfaceflinger/tests/Credentials_test.cpp +++ b/services/surfaceflinger/tests/Credentials_test.cpp @@ -18,13 +18,13 @@ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wconversion" +#include #include -#include #include #include #include #include -#include +#include #include #include #include @@ -34,6 +34,7 @@ namespace android { using Transaction = SurfaceComposerClient::Transaction; +using gui::LayerDebugInfo; using ui::ColorMode; namespace { @@ -307,23 +308,26 @@ TEST_F(CredentialsTest, CaptureLayersTest) { */ TEST_F(CredentialsTest, GetLayerDebugInfo) { setupBackgroundSurface(); - sp sf(ComposerService::getComposerService()); + sp sf(ComposerServiceAIDL::getComposerService()); // Historically, only root and shell can access the getLayerDebugInfo which // is called when we call dumpsys. I don't see a reason why we should change this. std::vector outLayers; // Check with root. seteuid(AID_ROOT); - ASSERT_EQ(NO_ERROR, sf->getLayerDebugInfo(&outLayers)); + binder::Status status = sf->getLayerDebugInfo(&outLayers); + ASSERT_EQ(NO_ERROR, status.transactionError()); // Check as a shell. seteuid(AID_SHELL); - ASSERT_EQ(NO_ERROR, sf->getLayerDebugInfo(&outLayers)); + status = sf->getLayerDebugInfo(&outLayers); + ASSERT_EQ(NO_ERROR, status.transactionError()); // Check as anyone else. seteuid(AID_ROOT); seteuid(AID_BIN); - ASSERT_EQ(PERMISSION_DENIED, sf->getLayerDebugInfo(&outLayers)); + status = sf->getLayerDebugInfo(&outLayers); + ASSERT_EQ(PERMISSION_DENIED, status.transactionError()); } TEST_F(CredentialsTest, IsWideColorDisplayBasicCorrectness) { diff --git a/services/surfaceflinger/tests/LayerTransactionTest.h b/services/surfaceflinger/tests/LayerTransactionTest.h index 6bd7920a62..43386b2ae7 100644 --- a/services/surfaceflinger/tests/LayerTransactionTest.h +++ b/services/surfaceflinger/tests/LayerTransactionTest.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "BufferGenerator.h" @@ -44,8 +45,9 @@ protected: ASSERT_NO_FATAL_FAILURE(SetUpDisplay()); - sp sf(ComposerService::getComposerService()); - ASSERT_NO_FATAL_FAILURE(sf->getColorManagement(&mColorManagementUsed)); + sp sf(ComposerServiceAIDL::getComposerService()); + binder::Status status = sf->getColorManagement(&mColorManagementUsed); + ASSERT_NO_FATAL_FAILURE(status.transactionError()); mCaptureArgs.displayToken = mDisplay; } diff --git a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp b/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp index b3b4ec15cd..12e5d46a79 100644 --- a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp +++ b/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -992,7 +993,7 @@ using DisplayTest_2_1 = DisplayTest; // Tests that VSYNC injection can be safely toggled while invalidating. TEST_F(DisplayTest_2_1, VsyncInjection) { - const auto flinger = ComposerService::getComposerService(); + const auto flinger = ComposerServiceAIDL::getComposerService(); bool enable = true; for (int i = 0; i < 100; i++) { @@ -1238,9 +1239,10 @@ protected: sFakeComposer->clearFrames(); ASSERT_EQ(0, sFakeComposer->getFrameCount()); - sp sf(ComposerService::getComposerService()); - std::vector layers; - status_t result = sf->getLayerDebugInfo(&layers); + sp sf(ComposerServiceAIDL::getComposerService()); + std::vector layers; + binder::Status status = sf->getLayerDebugInfo(&layers); + status_t result = status.transactionError(); if (result != NO_ERROR) { ALOGE("Failed to get layers %s %d", strerror(-result), result); } else { -- GitLab From 8143711593ca7e8b9435ebe7cb65bd2ec6cec16e Mon Sep 17 00:00:00 2001 From: Shikha Malhotra Date: Fri, 1 Apr 2022 22:13:39 +0000 Subject: [PATCH 0042/1310] Check for IOCTL feature changed to not set inheritence Inheritence flags should only be set on directories and not files, setting inheritence flags on files fails and leads to not using project ids. Also refined the tests to make sure that project ids are indeed used. Bug: b/215154615 Test: atest installd/StorageHostTest Test: atest installd/installd_service_test.cpp Ignore-AOSP-First: It will be picked up in AOSP manually, needs to be in master first to resolve merge conflicts. Change-Id: Iac38ba6f77c14141d8b06b63f52bf75fe0c54c66 Change-Id: Ia5baf127af5a078592d8b2db4b2f3d8c048a8692 --- cmds/installd/InstalldNativeService.cpp | 2 +- cmds/installd/tests/installd_service_test.cpp | 93 ++++++++++++++++++- 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp index 4e12579e07..1c5aa4f1cc 100644 --- a/cmds/installd/InstalldNativeService.cpp +++ b/cmds/installd/InstalldNativeService.cpp @@ -3429,7 +3429,7 @@ bool check_if_ioctl_feature_is_supported() { auto temp_path = StringPrintf("%smisc/installd/ioctl_check", android_data_dir.c_str()); if (access(temp_path.c_str(), F_OK) != 0) { int fd = open(temp_path.c_str(), O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0644); - result = set_quota_project_id(temp_path, 0, true) == 0; + result = set_quota_project_id(temp_path, 0, false) == 0; close(fd); // delete the temp file remove(temp_path.c_str()); diff --git a/cmds/installd/tests/installd_service_test.cpp b/cmds/installd/tests/installd_service_test.cpp index 38cb37046d..162e6680af 100644 --- a/cmds/installd/tests/installd_service_test.cpp +++ b/cmds/installd/tests/installd_service_test.cpp @@ -465,7 +465,7 @@ TEST_F(ServiceTest, CalculateCache) { EXPECT_TRUE(create_cache_path(buf, "/path/to/file.apk", "isa")); EXPECT_EQ("/data/dalvik-cache/isa/path@to@file.apk@classes.dex", std::string(buf)); } -TEST_F(ServiceTest, GetAppSize) { +TEST_F(ServiceTest, GetAppSizeManualForMedia) { struct stat s; std::string externalPicDir = @@ -509,6 +509,97 @@ TEST_F(ServiceTest, GetAppSize) { system(removeCommand.c_str()); } } +TEST_F(ServiceTest, GetAppSizeProjectID_UID) { + struct stat s; + std::string externalPicDir = + StringPrintf("%s/Pictures", create_data_media_path(nullptr, 0).c_str()); + if (stat(externalPicDir.c_str(), &s) == 0) { + // fetch the appId from the uid of the external storage owning app + int32_t externalStorageAppId = multiuser_get_app_id(s.st_uid); + // Fetch Package Name for the external storage owning app uid + std::string pkg = get_package_name(s.st_uid); + + std::vector externalStorageSize, externalStorageSizeAfterAddingCacheFile; + std::vector ceDataInodes; + + std::vector codePaths; + std::vector packageNames; + // set up parameters + packageNames.push_back(pkg); + ceDataInodes.push_back(0); + // initialise the mounts + service->invalidateMounts(); + auto using_project_ids = + StringPrintf("%smisc/installd/using_project_ids", android_data_dir.c_str()); + bool usingProjectIds = access(using_project_ids.c_str(), F_OK) == 0; + if (!usingProjectIds) { + service->setFirstBoot(); + } + + if (access(using_project_ids.c_str(), F_OK) != 0) { + // projectids is not used, so check that ioctl features should be absent + auto temp_path = StringPrintf("%smisc/installd/ioctl_check", android_data_dir.c_str()); + + if (access(temp_path.c_str(), F_OK) != 0) { + open(temp_path.c_str(), O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0644); + bool result = set_quota_project_id(temp_path, 0, false) == 0; + // delete the temp file + // remove the external file + remove(temp_path.c_str()); + // since using_project_ids file is not present, so ioctl settings should be absent + // that is denoted by the result of setting project id flag as false + ASSERT_FALSE(result); + } + } + // call the getAppSize to get the current size of the external storage owning app + service->getAppSize(std::nullopt, packageNames, 0, InstalldNativeService::FLAG_USE_QUOTA, + externalStorageAppId, ceDataInodes, codePaths, &externalStorageSize); + // add a file with 20MB size to the external storage + std::string externalStorageCacheDir = + StringPrintf("%s/%s/cache", create_data_user_ce_path(nullptr, 0).c_str(), + pkg.c_str()); + std::string cacheFileLocation = + StringPrintf("%s/%s", externalStorageCacheDir.c_str(), "External.jpg"); + std::string externalFileContentCommand = + StringPrintf("dd if=/dev/zero of=%s bs=1M count=20", cacheFileLocation.c_str()); + system(externalFileContentCommand.c_str()); + // call the getAppSize again to get the new size of the external storage owning app + service->getAppSize(std::nullopt, packageNames, 0, InstalldNativeService::FLAG_USE_QUOTA, + externalStorageAppId, ceDataInodes, codePaths, + &externalStorageSizeAfterAddingCacheFile); + // check that the size of cache and data increases when cache file is added + int64_t sizeDiffData = externalStorageSizeAfterAddingCacheFile[1] - externalStorageSize[1]; + int64_t sizeDiffCache = externalStorageSizeAfterAddingCacheFile[2] - externalStorageSize[2]; + ASSERT_TRUE(sizeDiffData == sizeDiffCache); + // remove the external file + std::string removeCommand = StringPrintf("rm -f %s", cacheFileLocation.c_str()); + system(removeCommand.c_str()); + // remove the setFirstBoot setting + std::string removeCommand2 = "rm -f /data/misc/installd/using_project_ids"; + system(removeCommand2.c_str()); + // Do now without project id + std::vector sizeWithUID, sizeWithUIDAfterAddingCacheFile; + // call the getAppSize to get the current size of the external storage owning app + service->getAppSize(std::nullopt, packageNames, 0, InstalldNativeService::FLAG_USE_QUOTA, + externalStorageAppId, ceDataInodes, codePaths, &sizeWithUID); + // add a file with 20MB size to the external storage + system(externalFileContentCommand.c_str()); + // call the getAppSize again to get the new size of the external storage owning app + service->getAppSize(std::nullopt, packageNames, 0, InstalldNativeService::FLAG_USE_QUOTA, + externalStorageAppId, ceDataInodes, codePaths, + &sizeWithUIDAfterAddingCacheFile); + // check that the size of cache and data increases when cache file is added + sizeDiffData = sizeWithUIDAfterAddingCacheFile[1] - sizeWithUID[1]; + sizeDiffCache = sizeWithUIDAfterAddingCacheFile[2] - sizeWithUID[2]; + ASSERT_TRUE(sizeDiffData == sizeDiffCache); + // remove the external file + system(removeCommand.c_str()); + // reset the using_project_id if it was initially set + if (usingProjectIds) { + service->setFirstBoot(); + } + } +} TEST_F(ServiceTest, GetAppSizeWrongSizes) { int32_t externalStorageAppId = -1; std::vector externalStorageSize; -- GitLab From ca9685acb48502825d93aad783a6b0c3e203e79c Mon Sep 17 00:00:00 2001 From: Shikha Malhotra Date: Mon, 4 Apr 2022 14:11:43 +0000 Subject: [PATCH 0043/1310] Reverting the test as it might be failing other tests Bug: 228027254 Test: manually test exitsing test cases Change-Id: I1211b8abefc99104eb9135d94d8b961a2d0a8fa0 Ignore-AOSP-First: Post submit tests are fialing on master --- cmds/installd/InstalldNativeService.cpp | 3 +- cmds/installd/tests/installd_service_test.cpp | 90 ------------------- 2 files changed, 2 insertions(+), 91 deletions(-) diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp index 1c5aa4f1cc..83bea7db93 100644 --- a/cmds/installd/InstalldNativeService.cpp +++ b/cmds/installd/InstalldNativeService.cpp @@ -473,7 +473,8 @@ static bool internal_storage_has_project_id() { StringPrintf("%smisc/installd/using_project_ids", android_data_dir.c_str()); sUsingProjectIdsFlag = access(using_project_ids.c_str(), F_OK) == 0; }); - return sUsingProjectIdsFlag; + // return sUsingProjectIdsFlag; + return false; } static int prepare_app_dir(const std::string& path, mode_t target_mode, uid_t uid, gid_t gid, diff --git a/cmds/installd/tests/installd_service_test.cpp b/cmds/installd/tests/installd_service_test.cpp index 162e6680af..f86f1d55fa 100644 --- a/cmds/installd/tests/installd_service_test.cpp +++ b/cmds/installd/tests/installd_service_test.cpp @@ -509,97 +509,7 @@ TEST_F(ServiceTest, GetAppSizeManualForMedia) { system(removeCommand.c_str()); } } -TEST_F(ServiceTest, GetAppSizeProjectID_UID) { - struct stat s; - std::string externalPicDir = - StringPrintf("%s/Pictures", create_data_media_path(nullptr, 0).c_str()); - if (stat(externalPicDir.c_str(), &s) == 0) { - // fetch the appId from the uid of the external storage owning app - int32_t externalStorageAppId = multiuser_get_app_id(s.st_uid); - // Fetch Package Name for the external storage owning app uid - std::string pkg = get_package_name(s.st_uid); - - std::vector externalStorageSize, externalStorageSizeAfterAddingCacheFile; - std::vector ceDataInodes; - - std::vector codePaths; - std::vector packageNames; - // set up parameters - packageNames.push_back(pkg); - ceDataInodes.push_back(0); - // initialise the mounts - service->invalidateMounts(); - auto using_project_ids = - StringPrintf("%smisc/installd/using_project_ids", android_data_dir.c_str()); - bool usingProjectIds = access(using_project_ids.c_str(), F_OK) == 0; - if (!usingProjectIds) { - service->setFirstBoot(); - } - if (access(using_project_ids.c_str(), F_OK) != 0) { - // projectids is not used, so check that ioctl features should be absent - auto temp_path = StringPrintf("%smisc/installd/ioctl_check", android_data_dir.c_str()); - - if (access(temp_path.c_str(), F_OK) != 0) { - open(temp_path.c_str(), O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0644); - bool result = set_quota_project_id(temp_path, 0, false) == 0; - // delete the temp file - // remove the external file - remove(temp_path.c_str()); - // since using_project_ids file is not present, so ioctl settings should be absent - // that is denoted by the result of setting project id flag as false - ASSERT_FALSE(result); - } - } - // call the getAppSize to get the current size of the external storage owning app - service->getAppSize(std::nullopt, packageNames, 0, InstalldNativeService::FLAG_USE_QUOTA, - externalStorageAppId, ceDataInodes, codePaths, &externalStorageSize); - // add a file with 20MB size to the external storage - std::string externalStorageCacheDir = - StringPrintf("%s/%s/cache", create_data_user_ce_path(nullptr, 0).c_str(), - pkg.c_str()); - std::string cacheFileLocation = - StringPrintf("%s/%s", externalStorageCacheDir.c_str(), "External.jpg"); - std::string externalFileContentCommand = - StringPrintf("dd if=/dev/zero of=%s bs=1M count=20", cacheFileLocation.c_str()); - system(externalFileContentCommand.c_str()); - // call the getAppSize again to get the new size of the external storage owning app - service->getAppSize(std::nullopt, packageNames, 0, InstalldNativeService::FLAG_USE_QUOTA, - externalStorageAppId, ceDataInodes, codePaths, - &externalStorageSizeAfterAddingCacheFile); - // check that the size of cache and data increases when cache file is added - int64_t sizeDiffData = externalStorageSizeAfterAddingCacheFile[1] - externalStorageSize[1]; - int64_t sizeDiffCache = externalStorageSizeAfterAddingCacheFile[2] - externalStorageSize[2]; - ASSERT_TRUE(sizeDiffData == sizeDiffCache); - // remove the external file - std::string removeCommand = StringPrintf("rm -f %s", cacheFileLocation.c_str()); - system(removeCommand.c_str()); - // remove the setFirstBoot setting - std::string removeCommand2 = "rm -f /data/misc/installd/using_project_ids"; - system(removeCommand2.c_str()); - // Do now without project id - std::vector sizeWithUID, sizeWithUIDAfterAddingCacheFile; - // call the getAppSize to get the current size of the external storage owning app - service->getAppSize(std::nullopt, packageNames, 0, InstalldNativeService::FLAG_USE_QUOTA, - externalStorageAppId, ceDataInodes, codePaths, &sizeWithUID); - // add a file with 20MB size to the external storage - system(externalFileContentCommand.c_str()); - // call the getAppSize again to get the new size of the external storage owning app - service->getAppSize(std::nullopt, packageNames, 0, InstalldNativeService::FLAG_USE_QUOTA, - externalStorageAppId, ceDataInodes, codePaths, - &sizeWithUIDAfterAddingCacheFile); - // check that the size of cache and data increases when cache file is added - sizeDiffData = sizeWithUIDAfterAddingCacheFile[1] - sizeWithUID[1]; - sizeDiffCache = sizeWithUIDAfterAddingCacheFile[2] - sizeWithUID[2]; - ASSERT_TRUE(sizeDiffData == sizeDiffCache); - // remove the external file - system(removeCommand.c_str()); - // reset the using_project_id if it was initially set - if (usingProjectIds) { - service->setFirstBoot(); - } - } -} TEST_F(ServiceTest, GetAppSizeWrongSizes) { int32_t externalStorageAppId = -1; std::vector externalStorageSize; -- GitLab From 02186fbaa27f4a7e78a0207da49ef38baacd1571 Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Wed, 23 Feb 2022 14:21:54 -0800 Subject: [PATCH 0044/1310] Migrate 13 methods of ISurfaceComposer to AIDL More misc methods are migrated to AIDL. ARect parcelable is added to serialize Rect data structure, defined in libui Rect.h. Bug: 211009610 Test: atest libgui_test libsurfaceflinger_unittest SurfaceFlinger_test Change-Id: I549e06c6f550760974d965d08783338635a5a5fe --- libs/gui/Android.bp | 16 +- libs/gui/BLASTBufferQueue.cpp | 3 +- libs/gui/ISurfaceComposer.cpp | 519 ------------------ libs/gui/SurfaceComposerClient.cpp | 83 ++- libs/gui/TransactionTracing.cpp | 8 +- libs/gui/WindowInfosListenerReporter.cpp | 17 +- .../android/gui/{Rect.aidl => ARect.aidl} | 2 +- .../aidl/android/gui/DisplayModeSpecs.aidl | 27 + .../aidl/android/gui/ISurfaceComposer.aidl | 122 +++- libs/gui/include/gui/ISurfaceComposer.h | 164 +----- libs/gui/include/gui/LayerDebugInfo.h | 2 +- libs/gui/include/gui/SurfaceComposerClient.h | 2 +- .../include/gui/WindowInfosListenerReporter.h | 13 +- .../include/private/gui/ComposerServiceAIDL.h | 1 + libs/gui/tests/RegionSampling_test.cpp | 97 +++- libs/gui/tests/SamplingDemo.cpp | 20 +- libs/gui/tests/Surface_test.cpp | 128 +++-- services/surfaceflinger/SurfaceFlinger.cpp | 221 ++++++-- services/surfaceflinger/SurfaceFlinger.h | 57 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 2 +- .../tests/unittests/TestableSurfaceFlinger.h | 2 +- 21 files changed, 652 insertions(+), 854 deletions(-) rename libs/gui/aidl/android/gui/{Rect.aidl => ARect.aidl} (98%) create mode 100644 libs/gui/aidl/android/gui/DisplayModeSpecs.aidl diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index 6b64ac8597..dd7e082487 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -142,16 +142,24 @@ cc_library_static { "include", ], + include_dirs: [ + "frameworks/native/include", + ], + export_shared_lib_headers: [ "libbinder", ], static_libs: [ "libui-types", + "libgui_window_info_static", ], aidl: { export_aidl_headers: true, + include_dirs: [ + "frameworks/native/libs/gui", + ], }, } @@ -288,10 +296,16 @@ cc_library_static { defaults: ["libgui_bufferqueue-defaults"], srcs: [ + ":libgui_frame_event_aidl", ":inputconstants_aidl", ":libgui_bufferqueue_sources", - ":libgui_aidl", ], + + aidl: { + include_dirs: [ + "frameworks/native/libs/gui", + ], + }, } filegroup { diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index c2793ac5de..bba7387c3a 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -33,6 +33,7 @@ #include #include +#include #include @@ -160,7 +161,7 @@ BLASTBufferQueue::BLASTBufferQueue(const std::string& name, bool updateDestinati mBufferItemConsumer->setFrameAvailableListener(this); mBufferItemConsumer->setBufferFreedListener(this); - ComposerService::getComposerService()->getMaxAcquiredBufferCount(&mMaxAcquiredBuffers); + ComposerServiceAIDL::getComposerService()->getMaxAcquiredBufferCount(&mMaxAcquiredBuffers); mBufferItemConsumer->setMaxAcquiredBufferCount(mMaxAcquiredBuffers); mCurrentMaxAcquiredBufferCount = mMaxAcquiredBuffers; mNumAcquired = 0; diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index a10a2f06ba..c3b33cb595 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -220,238 +220,6 @@ public: return result; } - status_t addRegionSamplingListener(const Rect& samplingArea, const sp& stopLayerHandle, - const sp& listener) override { - Parcel data, reply; - status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (error != NO_ERROR) { - ALOGE("addRegionSamplingListener: Failed to write interface token"); - return error; - } - error = data.write(samplingArea); - if (error != NO_ERROR) { - ALOGE("addRegionSamplingListener: Failed to write sampling area"); - return error; - } - error = data.writeStrongBinder(stopLayerHandle); - if (error != NO_ERROR) { - ALOGE("addRegionSamplingListener: Failed to write stop layer handle"); - return error; - } - error = data.writeStrongBinder(IInterface::asBinder(listener)); - if (error != NO_ERROR) { - ALOGE("addRegionSamplingListener: Failed to write listener"); - return error; - } - error = remote()->transact(BnSurfaceComposer::ADD_REGION_SAMPLING_LISTENER, data, &reply); - if (error != NO_ERROR) { - ALOGE("addRegionSamplingListener: Failed to transact"); - } - return error; - } - - status_t removeRegionSamplingListener(const sp& listener) override { - Parcel data, reply; - status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (error != NO_ERROR) { - ALOGE("removeRegionSamplingListener: Failed to write interface token"); - return error; - } - error = data.writeStrongBinder(IInterface::asBinder(listener)); - if (error != NO_ERROR) { - ALOGE("removeRegionSamplingListener: Failed to write listener"); - return error; - } - error = remote()->transact(BnSurfaceComposer::REMOVE_REGION_SAMPLING_LISTENER, data, - &reply); - if (error != NO_ERROR) { - ALOGE("removeRegionSamplingListener: Failed to transact"); - } - return error; - } - - virtual status_t addFpsListener(int32_t taskId, const sp& listener) { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeInt32, taskId); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(listener)); - const status_t error = - remote()->transact(BnSurfaceComposer::ADD_FPS_LISTENER, data, &reply); - if (error != OK) { - ALOGE("addFpsListener: Failed to transact"); - } - return error; - } - - virtual status_t removeFpsListener(const sp& listener) { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(listener)); - - const status_t error = - remote()->transact(BnSurfaceComposer::REMOVE_FPS_LISTENER, data, &reply); - if (error != OK) { - ALOGE("removeFpsListener: Failed to transact"); - } - return error; - } - - virtual status_t addTunnelModeEnabledListener( - const sp& listener) { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(listener)); - - const status_t error = - remote()->transact(BnSurfaceComposer::ADD_TUNNEL_MODE_ENABLED_LISTENER, data, - &reply); - if (error != NO_ERROR) { - ALOGE("addTunnelModeEnabledListener: Failed to transact"); - } - return error; - } - - virtual status_t removeTunnelModeEnabledListener( - const sp& listener) { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(listener)); - - const status_t error = - remote()->transact(BnSurfaceComposer::REMOVE_TUNNEL_MODE_ENABLED_LISTENER, data, - &reply); - if (error != NO_ERROR) { - ALOGE("removeTunnelModeEnabledListener: Failed to transact"); - } - return error; - } - - status_t setDesiredDisplayModeSpecs(const sp& displayToken, - ui::DisplayModeId defaultMode, bool allowGroupSwitching, - float primaryRefreshRateMin, float primaryRefreshRateMax, - float appRequestRefreshRateMin, - float appRequestRefreshRateMax) override { - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs: failed to writeInterfaceToken: %d", result); - return result; - } - result = data.writeStrongBinder(displayToken); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs: failed to write display token: %d", result); - return result; - } - result = data.writeInt32(defaultMode); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs failed to write defaultMode: %d", result); - return result; - } - result = data.writeBool(allowGroupSwitching); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs failed to write allowGroupSwitching: %d", result); - return result; - } - result = data.writeFloat(primaryRefreshRateMin); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs failed to write primaryRefreshRateMin: %d", result); - return result; - } - result = data.writeFloat(primaryRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs failed to write primaryRefreshRateMax: %d", result); - return result; - } - result = data.writeFloat(appRequestRefreshRateMin); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs failed to write appRequestRefreshRateMin: %d", - result); - return result; - } - result = data.writeFloat(appRequestRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs failed to write appRequestRefreshRateMax: %d", - result); - return result; - } - - result = - remote()->transact(BnSurfaceComposer::SET_DESIRED_DISPLAY_MODE_SPECS, data, &reply); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs failed to transact: %d", result); - return result; - } - return reply.readInt32(); - } - - status_t getDesiredDisplayModeSpecs(const sp& displayToken, - ui::DisplayModeId* outDefaultMode, - bool* outAllowGroupSwitching, - float* outPrimaryRefreshRateMin, - float* outPrimaryRefreshRateMax, - float* outAppRequestRefreshRateMin, - float* outAppRequestRefreshRateMax) override { - if (!outDefaultMode || !outAllowGroupSwitching || !outPrimaryRefreshRateMin || - !outPrimaryRefreshRateMax || !outAppRequestRefreshRateMin || - !outAppRequestRefreshRateMax) { - return BAD_VALUE; - } - Parcel data, reply; - status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs failed to writeInterfaceToken: %d", result); - return result; - } - result = data.writeStrongBinder(displayToken); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs failed to writeStrongBinder: %d", result); - return result; - } - result = - remote()->transact(BnSurfaceComposer::GET_DESIRED_DISPLAY_MODE_SPECS, data, &reply); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs failed to transact: %d", result); - return result; - } - - result = reply.readInt32(outDefaultMode); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs failed to read defaultMode: %d", result); - return result; - } - if (*outDefaultMode < 0) { - ALOGE("%s: defaultMode must be non-negative but it was %d", __func__, *outDefaultMode); - return BAD_VALUE; - } - - result = reply.readBool(outAllowGroupSwitching); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs failed to read allowGroupSwitching: %d", result); - return result; - } - result = reply.readFloat(outPrimaryRefreshRateMin); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs failed to read primaryRefreshRateMin: %d", result); - return result; - } - result = reply.readFloat(outPrimaryRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs failed to read primaryRefreshRateMax: %d", result); - return result; - } - result = reply.readFloat(outAppRequestRefreshRateMin); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs failed to read appRequestRefreshRateMin: %d", result); - return result; - } - result = reply.readFloat(outAppRequestRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs failed to read appRequestRefreshRateMax: %d", result); - return result; - } - return reply.readInt32(); - } - status_t setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor, float lightPosY, float lightPosZ, float lightRadius) override { Parcel data, reply; @@ -573,59 +341,6 @@ public: return reply.readInt32(); } - status_t addTransactionTraceListener( - const sp& listener) override { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(listener)); - - return remote()->transact(BnSurfaceComposer::ADD_TRANSACTION_TRACE_LISTENER, data, &reply); - } - - /** - * Get priority of the RenderEngine in surface flinger. - */ - int getGPUContextPriority() override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - status_t err = - remote()->transact(BnSurfaceComposer::GET_GPU_CONTEXT_PRIORITY, data, &reply); - if (err != NO_ERROR) { - ALOGE("getGPUContextPriority failed to read data: %s (%d)", strerror(-err), err); - return 0; - } - return reply.readInt32(); - } - - status_t getMaxAcquiredBufferCount(int* buffers) const override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - status_t err = - remote()->transact(BnSurfaceComposer::GET_MAX_ACQUIRED_BUFFER_COUNT, data, &reply); - if (err != NO_ERROR) { - ALOGE("getMaxAcquiredBufferCount failed to read data: %s (%d)", strerror(-err), err); - return err; - } - - return reply.readInt32(buffers); - } - - status_t addWindowInfosListener( - const sp& windowInfosListener) const override { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(windowInfosListener)); - return remote()->transact(BnSurfaceComposer::ADD_WINDOW_INFOS_LISTENER, data, &reply); - } - - status_t removeWindowInfosListener( - const sp& windowInfosListener) const override { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(windowInfosListener)); - return remote()->transact(BnSurfaceComposer::REMOVE_WINDOW_INFOS_LISTENER, data, &reply); - } - status_t setOverrideFrameRate(uid_t uid, float frameRate) override { Parcel data, reply; SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); @@ -778,203 +493,6 @@ status_t BnSurfaceComposer::onTransact( } return result; } - case ADD_REGION_SAMPLING_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - Rect samplingArea; - status_t result = data.read(samplingArea); - if (result != NO_ERROR) { - ALOGE("addRegionSamplingListener: Failed to read sampling area"); - return result; - } - sp stopLayerHandle; - result = data.readNullableStrongBinder(&stopLayerHandle); - if (result != NO_ERROR) { - ALOGE("addRegionSamplingListener: Failed to read stop layer handle"); - return result; - } - sp listener; - result = data.readNullableStrongBinder(&listener); - if (result != NO_ERROR) { - ALOGE("addRegionSamplingListener: Failed to read listener"); - return result; - } - return addRegionSamplingListener(samplingArea, stopLayerHandle, listener); - } - case REMOVE_REGION_SAMPLING_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp listener; - status_t result = data.readNullableStrongBinder(&listener); - if (result != NO_ERROR) { - ALOGE("removeRegionSamplingListener: Failed to read listener"); - return result; - } - return removeRegionSamplingListener(listener); - } - case ADD_FPS_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - int32_t taskId; - status_t result = data.readInt32(&taskId); - if (result != NO_ERROR) { - ALOGE("addFpsListener: Failed to read layer handle"); - return result; - } - sp listener; - result = data.readNullableStrongBinder(&listener); - if (result != NO_ERROR) { - ALOGE("addFpsListener: Failed to read listener"); - return result; - } - return addFpsListener(taskId, listener); - } - case REMOVE_FPS_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp listener; - status_t result = data.readNullableStrongBinder(&listener); - if (result != NO_ERROR) { - ALOGE("removeFpsListener: Failed to read listener"); - return result; - } - return removeFpsListener(listener); - } - case ADD_TUNNEL_MODE_ENABLED_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp listener; - status_t result = data.readNullableStrongBinder(&listener); - if (result != NO_ERROR) { - ALOGE("addTunnelModeEnabledListener: Failed to read listener"); - return result; - } - return addTunnelModeEnabledListener(listener); - } - case REMOVE_TUNNEL_MODE_ENABLED_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp listener; - status_t result = data.readNullableStrongBinder(&listener); - if (result != NO_ERROR) { - ALOGE("removeTunnelModeEnabledListener: Failed to read listener"); - return result; - } - return removeTunnelModeEnabledListener(listener); - } - case SET_DESIRED_DISPLAY_MODE_SPECS: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp displayToken = data.readStrongBinder(); - ui::DisplayModeId defaultMode; - status_t result = data.readInt32(&defaultMode); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs: failed to read defaultMode: %d", result); - return result; - } - if (defaultMode < 0) { - ALOGE("%s: defaultMode must be non-negative but it was %d", __func__, defaultMode); - return BAD_VALUE; - } - bool allowGroupSwitching; - result = data.readBool(&allowGroupSwitching); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs: failed to read allowGroupSwitching: %d", result); - return result; - } - float primaryRefreshRateMin; - result = data.readFloat(&primaryRefreshRateMin); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs: failed to read primaryRefreshRateMin: %d", - result); - return result; - } - float primaryRefreshRateMax; - result = data.readFloat(&primaryRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs: failed to read primaryRefreshRateMax: %d", - result); - return result; - } - float appRequestRefreshRateMin; - result = data.readFloat(&appRequestRefreshRateMin); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs: failed to read appRequestRefreshRateMin: %d", - result); - return result; - } - float appRequestRefreshRateMax; - result = data.readFloat(&appRequestRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs: failed to read appRequestRefreshRateMax: %d", - result); - return result; - } - result = setDesiredDisplayModeSpecs(displayToken, defaultMode, allowGroupSwitching, - primaryRefreshRateMin, primaryRefreshRateMax, - appRequestRefreshRateMin, appRequestRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("setDesiredDisplayModeSpecs: failed to call setDesiredDisplayModeSpecs: " - "%d", - result); - return result; - } - reply->writeInt32(result); - return result; - } - case GET_DESIRED_DISPLAY_MODE_SPECS: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp displayToken = data.readStrongBinder(); - ui::DisplayModeId defaultMode; - bool allowGroupSwitching; - float primaryRefreshRateMin; - float primaryRefreshRateMax; - float appRequestRefreshRateMin; - float appRequestRefreshRateMax; - - status_t result = - getDesiredDisplayModeSpecs(displayToken, &defaultMode, &allowGroupSwitching, - &primaryRefreshRateMin, &primaryRefreshRateMax, - &appRequestRefreshRateMin, - &appRequestRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs: failed to get getDesiredDisplayModeSpecs: " - "%d", - result); - return result; - } - - result = reply->writeInt32(defaultMode); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs: failed to write defaultMode: %d", result); - return result; - } - result = reply->writeBool(allowGroupSwitching); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs: failed to write allowGroupSwitching: %d", - result); - return result; - } - result = reply->writeFloat(primaryRefreshRateMin); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs: failed to write primaryRefreshRateMin: %d", - result); - return result; - } - result = reply->writeFloat(primaryRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs: failed to write primaryRefreshRateMax: %d", - result); - return result; - } - result = reply->writeFloat(appRequestRefreshRateMin); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs: failed to write appRequestRefreshRateMin: %d", - result); - return result; - } - result = reply->writeFloat(appRequestRefreshRateMax); - if (result != NO_ERROR) { - ALOGE("getDesiredDisplayModeSpecs: failed to write appRequestRefreshRateMax: %d", - result); - return result; - } - reply->writeInt32(result); - return result; - } case SET_GLOBAL_SHADOW_SETTINGS: { CHECK_INTERFACE(ISurfaceComposer, data, reply); @@ -1058,43 +576,6 @@ status_t BnSurfaceComposer::onTransact( reply->writeInt32(result); return NO_ERROR; } - case ADD_TRANSACTION_TRACE_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp listener; - SAFE_PARCEL(data.readStrongBinder, &listener); - - return addTransactionTraceListener(listener); - } - case GET_GPU_CONTEXT_PRIORITY: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - int priority = getGPUContextPriority(); - SAFE_PARCEL(reply->writeInt32, priority); - return NO_ERROR; - } - case GET_MAX_ACQUIRED_BUFFER_COUNT: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - int buffers = 0; - int err = getMaxAcquiredBufferCount(&buffers); - if (err != NO_ERROR) { - return err; - } - SAFE_PARCEL(reply->writeInt32, buffers); - return NO_ERROR; - } - case ADD_WINDOW_INFOS_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp listener; - SAFE_PARCEL(data.readStrongBinder, &listener); - - return addWindowInfosListener(listener); - } - case REMOVE_WINDOW_INFOS_LISTENER: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp listener; - SAFE_PARCEL(data.readStrongBinder, &listener); - - return removeWindowInfosListener(listener); - } case SET_OVERRIDE_FRAME_RATE: { CHECK_INTERFACE(ISurfaceComposer, data, reply); diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 4facef403d..e54ff49391 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -111,7 +111,6 @@ bool ComposerService::connectLocked() { if (instance.mComposerService == nullptr) { if (ComposerService::getInstance().connectLocked()) { ALOGD("ComposerService reconnected"); - WindowInfosListenerReporter::getInstance()->reconnect(instance.mComposerService); } } return instance.mComposerService; @@ -159,6 +158,7 @@ bool ComposerServiceAIDL::connectLocked() { if (instance.mComposerService == nullptr) { if (ComposerServiceAIDL::getInstance().connectLocked()) { ALOGD("ComposerServiceAIDL reconnected"); + WindowInfosListenerReporter::getInstance()->reconnect(instance.mComposerService); } } return instance.mComposerService; @@ -2261,10 +2261,13 @@ status_t SurfaceComposerClient::setDesiredDisplayModeSpecs( const sp& displayToken, ui::DisplayModeId defaultMode, bool allowGroupSwitching, float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin, float appRequestRefreshRateMax) { - return ComposerService::getComposerService() - ->setDesiredDisplayModeSpecs(displayToken, defaultMode, allowGroupSwitching, - primaryRefreshRateMin, primaryRefreshRateMax, - appRequestRefreshRateMin, appRequestRefreshRateMax); + binder::Status status = + ComposerServiceAIDL::getComposerService() + ->setDesiredDisplayModeSpecs(displayToken, defaultMode, allowGroupSwitching, + primaryRefreshRateMin, primaryRefreshRateMax, + appRequestRefreshRateMin, + appRequestRefreshRateMax); + return status.transactionError(); } status_t SurfaceComposerClient::getDesiredDisplayModeSpecs(const sp& displayToken, @@ -2274,10 +2277,23 @@ status_t SurfaceComposerClient::getDesiredDisplayModeSpecs(const sp& di float* outPrimaryRefreshRateMax, float* outAppRequestRefreshRateMin, float* outAppRequestRefreshRateMax) { - return ComposerService::getComposerService() - ->getDesiredDisplayModeSpecs(displayToken, outDefaultMode, outAllowGroupSwitching, - outPrimaryRefreshRateMin, outPrimaryRefreshRateMax, - outAppRequestRefreshRateMin, outAppRequestRefreshRateMax); + if (!outDefaultMode || !outAllowGroupSwitching || !outPrimaryRefreshRateMin || + !outPrimaryRefreshRateMax || !outAppRequestRefreshRateMin || !outAppRequestRefreshRateMax) { + return BAD_VALUE; + } + gui::DisplayModeSpecs specs; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getDesiredDisplayModeSpecs(displayToken, + &specs); + if (status.isOk()) { + *outDefaultMode = specs.defaultMode; + *outAllowGroupSwitching = specs.allowGroupSwitching; + *outPrimaryRefreshRateMin = specs.primaryRefreshRateMin; + *outPrimaryRefreshRateMax = specs.primaryRefreshRateMax; + *outAppRequestRefreshRateMin = specs.appRequestRefreshRateMin; + *outAppRequestRefreshRateMax = specs.appRequestRefreshRateMax; + } + return status.transactionError(); } status_t SurfaceComposerClient::getDisplayNativePrimaries(const sp& display, @@ -2469,33 +2485,49 @@ status_t SurfaceComposerClient::isWideColorDisplay(const sp& display, status_t SurfaceComposerClient::addRegionSamplingListener( const Rect& samplingArea, const sp& stopLayerHandle, const sp& listener) { - return ComposerService::getComposerService()->addRegionSamplingListener(samplingArea, - stopLayerHandle, - listener); + gui::ARect rect; + rect.left = samplingArea.left; + rect.top = samplingArea.top; + rect.right = samplingArea.right; + rect.bottom = samplingArea.bottom; + binder::Status status = + ComposerServiceAIDL::getComposerService()->addRegionSamplingListener(rect, + stopLayerHandle, + listener); + return status.transactionError(); } status_t SurfaceComposerClient::removeRegionSamplingListener( const sp& listener) { - return ComposerService::getComposerService()->removeRegionSamplingListener(listener); + binder::Status status = + ComposerServiceAIDL::getComposerService()->removeRegionSamplingListener(listener); + return status.transactionError(); } status_t SurfaceComposerClient::addFpsListener(int32_t taskId, const sp& listener) { - return ComposerService::getComposerService()->addFpsListener(taskId, listener); + binder::Status status = + ComposerServiceAIDL::getComposerService()->addFpsListener(taskId, listener); + return status.transactionError(); } status_t SurfaceComposerClient::removeFpsListener(const sp& listener) { - return ComposerService::getComposerService()->removeFpsListener(listener); + binder::Status status = ComposerServiceAIDL::getComposerService()->removeFpsListener(listener); + return status.transactionError(); } status_t SurfaceComposerClient::addTunnelModeEnabledListener( const sp& listener) { - return ComposerService::getComposerService()->addTunnelModeEnabledListener(listener); + binder::Status status = + ComposerServiceAIDL::getComposerService()->addTunnelModeEnabledListener(listener); + return status.transactionError(); } status_t SurfaceComposerClient::removeTunnelModeEnabledListener( const sp& listener) { - return ComposerService::getComposerService()->removeTunnelModeEnabledListener(listener); + binder::Status status = + ComposerServiceAIDL::getComposerService()->removeTunnelModeEnabledListener(listener); + return status.transactionError(); } bool SurfaceComposerClient::getDisplayBrightnessSupport(const sp& displayToken) { @@ -2550,22 +2582,31 @@ std::optional SurfaceComposerClient::getDisplayDecorat return support; } -int SurfaceComposerClient::getGPUContextPriority() { - return ComposerService::getComposerService()->getGPUContextPriority(); +int SurfaceComposerClient::getGpuContextPriority() { + int priority; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getGpuContextPriority(&priority); + if (!status.isOk()) { + status_t err = status.transactionError(); + ALOGE("getGpuContextPriority failed to read data: %s (%d)", strerror(-err), err); + return 0; + } + return priority; } status_t SurfaceComposerClient::addWindowInfosListener( const sp& windowInfosListener, std::pair, std::vector>* outInitialInfo) { return WindowInfosListenerReporter::getInstance() - ->addWindowInfosListener(windowInfosListener, ComposerService::getComposerService(), + ->addWindowInfosListener(windowInfosListener, ComposerServiceAIDL::getComposerService(), outInitialInfo); } status_t SurfaceComposerClient::removeWindowInfosListener( const sp& windowInfosListener) { return WindowInfosListenerReporter::getInstance() - ->removeWindowInfosListener(windowInfosListener, ComposerService::getComposerService()); + ->removeWindowInfosListener(windowInfosListener, + ComposerServiceAIDL::getComposerService()); } // ---------------------------------------------------------------------------- diff --git a/libs/gui/TransactionTracing.cpp b/libs/gui/TransactionTracing.cpp index eedc3df009..59450fb411 100644 --- a/libs/gui/TransactionTracing.cpp +++ b/libs/gui/TransactionTracing.cpp @@ -15,9 +15,9 @@ */ #include "gui/TransactionTracing.h" -#include "gui/ISurfaceComposer.h" +#include "android/gui/ISurfaceComposer.h" -#include +#include namespace android { @@ -32,7 +32,7 @@ sp TransactionTraceListener::getInstance() { if (sInstance == nullptr) { sInstance = new TransactionTraceListener; - sp sf(ComposerService::getComposerService()); + sp sf(ComposerServiceAIDL::getComposerService()); sf->addTransactionTraceListener(sInstance); } @@ -50,4 +50,4 @@ bool TransactionTraceListener::isTracingEnabled() { return mTracingEnabled; } -} // namespace android \ No newline at end of file +} // namespace android diff --git a/libs/gui/WindowInfosListenerReporter.cpp b/libs/gui/WindowInfosListenerReporter.cpp index cfc7dbc463..0ed83f272c 100644 --- a/libs/gui/WindowInfosListenerReporter.cpp +++ b/libs/gui/WindowInfosListenerReporter.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include namespace android { @@ -31,13 +31,14 @@ sp WindowInfosListenerReporter::getInstance() { status_t WindowInfosListenerReporter::addWindowInfosListener( const sp& windowInfosListener, - const sp& surfaceComposer, + const sp& surfaceComposer, std::pair, std::vector>* outInitialInfo) { status_t status = OK; { std::scoped_lock lock(mListenersMutex); if (mWindowInfosListeners.empty()) { - status = surfaceComposer->addWindowInfosListener(this); + binder::Status s = surfaceComposer->addWindowInfosListener(this); + status = s.transactionError(); } if (status == OK) { @@ -55,12 +56,13 @@ status_t WindowInfosListenerReporter::addWindowInfosListener( status_t WindowInfosListenerReporter::removeWindowInfosListener( const sp& windowInfosListener, - const sp& surfaceComposer) { + const sp& surfaceComposer) { status_t status = OK; { std::scoped_lock lock(mListenersMutex); if (mWindowInfosListeners.size() == 1) { - status = surfaceComposer->removeWindowInfosListener(this); + binder::Status s = surfaceComposer->removeWindowInfosListener(this); + status = s.transactionError(); // Clear the last stored state since we're disabling updates and don't want to hold // stale values mLastWindowInfos.clear(); @@ -78,7 +80,8 @@ status_t WindowInfosListenerReporter::removeWindowInfosListener( binder::Status WindowInfosListenerReporter::onWindowInfosChanged( const std::vector& windowInfos, const std::vector& displayInfos, const sp& windowInfosReportedListener) { - std::unordered_set, SpHash> windowInfosListeners; + std::unordered_set, gui::SpHash> + windowInfosListeners; { std::scoped_lock lock(mListenersMutex); @@ -101,7 +104,7 @@ binder::Status WindowInfosListenerReporter::onWindowInfosChanged( return binder::Status::ok(); } -void WindowInfosListenerReporter::reconnect(const sp& composerService) { +void WindowInfosListenerReporter::reconnect(const sp& composerService) { std::scoped_lock lock(mListenersMutex); if (!mWindowInfosListeners.empty()) { composerService->addWindowInfosListener(this); diff --git a/libs/gui/aidl/android/gui/Rect.aidl b/libs/gui/aidl/android/gui/ARect.aidl similarity index 98% rename from libs/gui/aidl/android/gui/Rect.aidl rename to libs/gui/aidl/android/gui/ARect.aidl index 1b13761392..5785907a9c 100644 --- a/libs/gui/aidl/android/gui/Rect.aidl +++ b/libs/gui/aidl/android/gui/ARect.aidl @@ -20,7 +20,7 @@ package android.gui; // TODO(b/221473398): // use hardware/interfaces/graphics/common/aidl/android/hardware/graphics/common/Rect.aidl /** @hide */ -parcelable Rect { +parcelable ARect { /// Minimum X coordinate of the rectangle. int left; diff --git a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl new file mode 100644 index 0000000000..fb4fcdf8e8 --- /dev/null +++ b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl @@ -0,0 +1,27 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +/** @hide */ +parcelable DisplayModeSpecs { + int defaultMode; + boolean allowGroupSwitching; + float primaryRefreshRateMin; + float primaryRefreshRateMax; + float appRequestRefreshRateMin; + float appRequestRefreshRateMax; +} diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index dc77416010..1fed69f88f 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -20,18 +20,25 @@ import android.gui.CompositionPreference; import android.gui.ContentSamplingAttributes; import android.gui.DisplayCaptureArgs; import android.gui.DisplayBrightness; +import android.gui.DisplayModeSpecs; import android.gui.DisplayPrimaries; import android.gui.DisplayState; import android.gui.DisplayStatInfo; +import android.gui.DynamicDisplayInfo; import android.gui.FrameEvent; import android.gui.FrameStats; +import android.gui.IFpsListener; +import android.gui.IHdrLayerInfoListener; +import android.gui.IRegionSamplingListener; +import android.gui.IScreenCaptureListener; +import android.gui.ITransactionTraceListener; +import android.gui.ITunnelModeEnabledListener; +import android.gui.IWindowInfosListener; +import android.gui.LayerCaptureArgs; import android.gui.LayerDebugInfo; import android.gui.PullAtomData; +import android.gui.ARect; import android.gui.StaticDisplayInfo; -import android.gui.DynamicDisplayInfo; -import android.gui.IHdrLayerInfoListener; -import android.gui.LayerCaptureArgs; -import android.gui.IScreenCaptureListener; /** @hide */ interface ISurfaceComposer { @@ -230,6 +237,82 @@ interface ISurfaceComposer { */ boolean isWideColorDisplay(IBinder token); + /** + * Registers a listener to stream median luma updates from SurfaceFlinger. + * + * The sampling area is bounded by both samplingArea and the given stopLayerHandle + * (i.e., only layers behind the stop layer will be captured and sampled). + * + * Multiple listeners may be provided so long as they have independent listeners. + * If multiple listeners are provided, the effective sampling region for each listener will + * be bounded by whichever stop layer has a lower Z value. + * + * Requires the same permissions as captureLayers and captureScreen. + */ + void addRegionSamplingListener(in ARect samplingArea, @nullable IBinder stopLayerHandle, IRegionSamplingListener listener); + + /** + * Removes a listener that was streaming median luma updates from SurfaceFlinger. + */ + void removeRegionSamplingListener(IRegionSamplingListener listener); + + /** + * Registers a listener that streams fps updates from SurfaceFlinger. + * + * The listener will stream fps updates for the layer tree rooted at the layer denoted by the + * task ID, i.e., the layer must have the task ID as part of its layer metadata with key + * METADATA_TASK_ID. If there is no such layer, then no fps is expected to be reported. + * + * Multiple listeners may be supported. + * + * Requires the READ_FRAME_BUFFER permission. + */ + void addFpsListener(int taskId, IFpsListener listener); + + /** + * Removes a listener that was streaming fps updates from SurfaceFlinger. + */ + void removeFpsListener(IFpsListener listener); + + /** + * Registers a listener to receive tunnel mode enabled updates from SurfaceFlinger. + * + * Requires ACCESS_SURFACE_FLINGER permission. + */ + void addTunnelModeEnabledListener(ITunnelModeEnabledListener listener); + + /** + * Removes a listener that was receiving tunnel mode enabled updates from SurfaceFlinger. + * + * Requires ACCESS_SURFACE_FLINGER permission. + */ + void removeTunnelModeEnabledListener(ITunnelModeEnabledListener listener); + + /** + * Sets the refresh rate boundaries for the display. + * + * The primary refresh rate range represents display manager's general guidance on the display + * modes we'll consider when switching refresh rates. Unless we get an explicit signal from an + * app, we should stay within this range. + * + * The app request refresh rate range allows us to consider more display modes when switching + * refresh rates. Although we should generally stay within the primary range, specific + * considerations, such as layer frame rate settings specified via the setFrameRate() api, may + * cause us to go outside the primary range. We never go outside the app request range. The app + * request range will be greater than or equal to the primary refresh rate range, never smaller. + * + * defaultMode is used to narrow the list of display modes SurfaceFlinger will consider + * switching between. Only modes with a mode group and resolution matching defaultMode + * will be considered for switching. The defaultMode corresponds to an ID of mode in the list + * of supported modes returned from getDynamicDisplayInfo(). + */ + void setDesiredDisplayModeSpecs( + IBinder displayToken, int defaultMode, + boolean allowGroupSwitching, float primaryRefreshRateMin, float primaryRefreshRateMax, + float appRequestRefreshRateMin, float appRequestRefreshRateMax); + + DisplayModeSpecs getDesiredDisplayModeSpecs(IBinder displayToken); + /** * Gets whether brightness operations are supported on a display. * @@ -286,4 +369,35 @@ interface ISurfaceComposer { * Returns NO_ERROR upon success. */ oneway void notifyPowerBoost(int boostId); + + /** + * Adds a TransactionTraceListener to listen for transaction tracing state updates. + */ + void addTransactionTraceListener(ITransactionTraceListener listener); + + /** + * Gets priority of the RenderEngine in SurfaceFlinger. + */ + int getGpuContextPriority(); + + /** + * Gets the number of buffers SurfaceFlinger would need acquire. This number + * would be propagated to the client via MIN_UNDEQUEUED_BUFFERS so that the + * client could allocate enough buffers to match SF expectations of the + * pipeline depth. SurfaceFlinger will make sure that it will give the app at + * least the time configured as the 'appDuration' before trying to latch + * the buffer. + * + * The total buffers needed for a given configuration is basically the + * numbers of vsyncs a single buffer is used across the stack. For the default + * configuration a buffer is held ~1 vsync by the app, ~1 vsync by SurfaceFlinger + * and 1 vsync by the display. The extra buffers are calculated as the + * number of additional buffers on top of the 2 buffers already present + * in MIN_UNDEQUEUED_BUFFERS. + */ + int getMaxAcquiredBufferCount(); + + void addWindowInfosListener(IWindowInfosListener windowInfosListener); + + void removeWindowInfosListener(IWindowInfosListener windowInfosListener); } diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index 7f59a5affb..858bd1d55e 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -162,87 +162,6 @@ public: uint64_t timestamp, DisplayedFrameStats* outStats) const = 0; - /* Registers a listener to stream median luma updates from SurfaceFlinger. - * - * The sampling area is bounded by both samplingArea and the given stopLayerHandle - * (i.e., only layers behind the stop layer will be captured and sampled). - * - * Multiple listeners may be provided so long as they have independent listeners. - * If multiple listeners are provided, the effective sampling region for each listener will - * be bounded by whichever stop layer has a lower Z value. - * - * Requires the same permissions as captureLayers and captureScreen. - */ - virtual status_t addRegionSamplingListener(const Rect& samplingArea, - const sp& stopLayerHandle, - const sp& listener) = 0; - - /* - * Removes a listener that was streaming median luma updates from SurfaceFlinger. - */ - virtual status_t removeRegionSamplingListener(const sp& listener) = 0; - - /* Registers a listener that streams fps updates from SurfaceFlinger. - * - * The listener will stream fps updates for the layer tree rooted at the layer denoted by the - * task ID, i.e., the layer must have the task ID as part of its layer metadata with key - * METADATA_TASK_ID. If there is no such layer, then no fps is expected to be reported. - * - * Multiple listeners may be supported. - * - * Requires the READ_FRAME_BUFFER permission. - */ - virtual status_t addFpsListener(int32_t taskId, const sp& listener) = 0; - /* - * Removes a listener that was streaming fps updates from SurfaceFlinger. - */ - virtual status_t removeFpsListener(const sp& listener) = 0; - - /* Registers a listener to receive tunnel mode enabled updates from SurfaceFlinger. - * - * Requires ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t addTunnelModeEnabledListener( - const sp& listener) = 0; - - /* - * Removes a listener that was receiving tunnel mode enabled updates from SurfaceFlinger. - * - * Requires ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t removeTunnelModeEnabledListener( - const sp& listener) = 0; - - /* Sets the refresh rate boundaries for the display. - * - * The primary refresh rate range represents display manager's general guidance on the display - * modes we'll consider when switching refresh rates. Unless we get an explicit signal from an - * app, we should stay within this range. - * - * The app request refresh rate range allows us to consider more display modes when switching - * refresh rates. Although we should generally stay within the primary range, specific - * considerations, such as layer frame rate settings specified via the setFrameRate() api, may - * cause us to go outside the primary range. We never go outside the app request range. The app - * request range will be greater than or equal to the primary refresh rate range, never smaller. - * - * defaultMode is used to narrow the list of display modes SurfaceFlinger will consider - * switching between. Only modes with a mode group and resolution matching defaultMode - * will be considered for switching. The defaultMode corresponds to an ID of mode in the list - * of supported modes returned from getDynamicDisplayInfo(). - */ - virtual status_t setDesiredDisplayModeSpecs( - const sp& displayToken, ui::DisplayModeId defaultMode, - bool allowGroupSwitching, float primaryRefreshRateMin, float primaryRefreshRateMax, - float appRequestRefreshRateMin, float appRequestRefreshRateMax) = 0; - - virtual status_t getDesiredDisplayModeSpecs(const sp& displayToken, - ui::DisplayModeId* outDefaultMode, - bool* outAllowGroupSwitching, - float* outPrimaryRefreshRateMin, - float* outPrimaryRefreshRateMax, - float* outAppRequestRefreshRateMin, - float* outAppRequestRefreshRateMax) = 0; - /* * Sets the global configuration for all the shadows drawn by SurfaceFlinger. Shadow follows * material design guidelines. @@ -302,39 +221,6 @@ public: */ virtual status_t setFrameTimelineInfo(const sp& surface, const FrameTimelineInfo& frameTimelineInfo) = 0; - - /* - * Adds a TransactionTraceListener to listen for transaction tracing state updates. - */ - virtual status_t addTransactionTraceListener( - const sp& listener) = 0; - - /** - * Gets priority of the RenderEngine in SurfaceFlinger. - */ - virtual int getGPUContextPriority() = 0; - - /** - * Gets the number of buffers SurfaceFlinger would need acquire. This number - * would be propagated to the client via MIN_UNDEQUEUED_BUFFERS so that the - * client could allocate enough buffers to match SF expectations of the - * pipeline depth. SurfaceFlinger will make sure that it will give the app at - * least the time configured as the 'appDuration' before trying to latch - * the buffer. - * - * The total buffers needed for a given configuration is basically the - * numbers of vsyncs a single buffer is used across the stack. For the default - * configuration a buffer is held ~1 vsync by the app, ~1 vsync by SurfaceFlinger - * and 1 vsync by the display. The extra buffers are calculated as the - * number of additional buffers on top of the 2 buffers already present - * in MIN_UNDEQUEUED_BUFFERS. - */ - virtual status_t getMaxAcquiredBufferCount(int* buffers) const = 0; - - virtual status_t addWindowInfosListener( - const sp& windowInfosListener) const = 0; - virtual status_t removeWindowInfosListener( - const sp& windowInfosListener) const = 0; }; // ---------------------------------------------------------------------------- @@ -375,18 +261,18 @@ public: GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES, // Deprecated. Autogenerated by .aidl now. SET_DISPLAY_CONTENT_SAMPLING_ENABLED, // Deprecated. Autogenerated by .aidl now. GET_DISPLAYED_CONTENT_SAMPLE, - GET_PROTECTED_CONTENT_SUPPORT, // Deprecated. Autogenerated by .aidl now. - IS_WIDE_COLOR_DISPLAY, // Deprecated. Autogenerated by .aidl now. - GET_DISPLAY_NATIVE_PRIMARIES, // Deprecated. Autogenerated by .aidl now. - GET_PHYSICAL_DISPLAY_IDS, // Deprecated. Autogenerated by .aidl now. - ADD_REGION_SAMPLING_LISTENER, - REMOVE_REGION_SAMPLING_LISTENER, - SET_DESIRED_DISPLAY_MODE_SPECS, - GET_DESIRED_DISPLAY_MODE_SPECS, - GET_DISPLAY_BRIGHTNESS_SUPPORT, // Deprecated. Autogenerated by .aidl now. - SET_DISPLAY_BRIGHTNESS, // Deprecated. Autogenerated by .aidl now. - CAPTURE_DISPLAY_BY_ID, // Deprecated. Autogenerated by .aidl now. - NOTIFY_POWER_BOOST, // Deprecated. Autogenerated by .aidl now. + GET_PROTECTED_CONTENT_SUPPORT, // Deprecated. Autogenerated by .aidl now. + IS_WIDE_COLOR_DISPLAY, // Deprecated. Autogenerated by .aidl now. + GET_DISPLAY_NATIVE_PRIMARIES, // Deprecated. Autogenerated by .aidl now. + GET_PHYSICAL_DISPLAY_IDS, // Deprecated. Autogenerated by .aidl now. + ADD_REGION_SAMPLING_LISTENER, // Deprecated. Autogenerated by .aidl now. + REMOVE_REGION_SAMPLING_LISTENER, // Deprecated. Autogenerated by .aidl now. + SET_DESIRED_DISPLAY_MODE_SPECS, // Deprecated. Autogenerated by .aidl now. + GET_DESIRED_DISPLAY_MODE_SPECS, // Deprecated. Autogenerated by .aidl now. + GET_DISPLAY_BRIGHTNESS_SUPPORT, // Deprecated. Autogenerated by .aidl now. + SET_DISPLAY_BRIGHTNESS, // Deprecated. Autogenerated by .aidl now. + CAPTURE_DISPLAY_BY_ID, // Deprecated. Autogenerated by .aidl now. + NOTIFY_POWER_BOOST, // Deprecated. Autogenerated by .aidl now. SET_GLOBAL_SHADOW_SETTINGS, GET_AUTO_LOW_LATENCY_MODE_SUPPORT, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. SET_AUTO_LOW_LATENCY_MODE, // Deprecated. Autogenerated by .aidl now. @@ -396,21 +282,21 @@ public: // Deprecated. Use DisplayManager.setShouldAlwaysRespectAppRequestedMode(true); ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN, SET_FRAME_TIMELINE_INFO, - ADD_TRANSACTION_TRACE_LISTENER, + ADD_TRANSACTION_TRACE_LISTENER, // Deprecated. Autogenerated by .aidl now. GET_GPU_CONTEXT_PRIORITY, GET_MAX_ACQUIRED_BUFFER_COUNT, - GET_DYNAMIC_DISPLAY_INFO, // Deprecated. Autogenerated by .aidl now. - ADD_FPS_LISTENER, - REMOVE_FPS_LISTENER, - OVERRIDE_HDR_TYPES, // Deprecated. Autogenerated by .aidl now. - ADD_HDR_LAYER_INFO_LISTENER, // Deprecated. Autogenerated by .aidl now. - REMOVE_HDR_LAYER_INFO_LISTENER, // Deprecated. Autogenerated by .aidl now. - ON_PULL_ATOM, // Deprecated. Autogenerated by .aidl now. - ADD_TUNNEL_MODE_ENABLED_LISTENER, - REMOVE_TUNNEL_MODE_ENABLED_LISTENER, - ADD_WINDOW_INFOS_LISTENER, - REMOVE_WINDOW_INFOS_LISTENER, - GET_PRIMARY_PHYSICAL_DISPLAY_ID, // Deprecated. Autogenerated by .aidl now. + GET_DYNAMIC_DISPLAY_INFO, // Deprecated. Autogenerated by .aidl now. + ADD_FPS_LISTENER, // Deprecated. Autogenerated by .aidl now. + REMOVE_FPS_LISTENER, // Deprecated. Autogenerated by .aidl now. + OVERRIDE_HDR_TYPES, // Deprecated. Autogenerated by .aidl now. + ADD_HDR_LAYER_INFO_LISTENER, // Deprecated. Autogenerated by .aidl now. + REMOVE_HDR_LAYER_INFO_LISTENER, // Deprecated. Autogenerated by .aidl now. + ON_PULL_ATOM, // Deprecated. Autogenerated by .aidl now. + ADD_TUNNEL_MODE_ENABLED_LISTENER, // Deprecated. Autogenerated by .aidl now. + REMOVE_TUNNEL_MODE_ENABLED_LISTENER, // Deprecated. Autogenerated by .aidl now. + ADD_WINDOW_INFOS_LISTENER, // Deprecated. Autogenerated by .aidl now. + REMOVE_WINDOW_INFOS_LISTENER, // Deprecated. Autogenerated by .aidl now. + GET_PRIMARY_PHYSICAL_DISPLAY_ID, // Deprecated. Autogenerated by .aidl now. GET_DISPLAY_DECORATION_SUPPORT, GET_BOOT_DISPLAY_MODE_SUPPORT, // Deprecated. Autogenerated by .aidl now. SET_BOOT_DISPLAY_MODE, // Deprecated. Autogenerated by .aidl now. diff --git a/libs/gui/include/gui/LayerDebugInfo.h b/libs/gui/include/gui/LayerDebugInfo.h index 1c1bbef123..dbb80e583c 100644 --- a/libs/gui/include/gui/LayerDebugInfo.h +++ b/libs/gui/include/gui/LayerDebugInfo.h @@ -52,7 +52,7 @@ public: uint32_t mZ = 0 ; int32_t mWidth = -1; int32_t mHeight = -1; - Rect mCrop = Rect::INVALID_RECT; + android::Rect mCrop = android::Rect::INVALID_RECT; half4 mColor = half4(1.0_hf, 1.0_hf, 1.0_hf, 0.0_hf); uint32_t mFlags = 0; PixelFormat mPixelFormat = PIXEL_FORMAT_NONE; diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index a30a3fa731..48b870dd3b 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -216,7 +216,7 @@ public: /** * Gets the context priority of surface flinger's render engine. */ - static int getGPUContextPriority(); + static int getGpuContextPriority(); /** * Uncaches a buffer in ISurfaceComposer. It must be uncached via a transaction so that it is diff --git a/libs/gui/include/gui/WindowInfosListenerReporter.h b/libs/gui/include/gui/WindowInfosListenerReporter.h index 3b4aed442e..2754442a95 100644 --- a/libs/gui/include/gui/WindowInfosListenerReporter.h +++ b/libs/gui/include/gui/WindowInfosListenerReporter.h @@ -17,15 +17,14 @@ #pragma once #include +#include #include #include -#include #include #include #include namespace android { -class ISurfaceComposer; class WindowInfosListenerReporter : public gui::BnWindowInfosListener { public: @@ -33,17 +32,17 @@ public: binder::Status onWindowInfosChanged(const std::vector&, const std::vector&, const sp&) override; - status_t addWindowInfosListener( - const sp& windowInfosListener, const sp&, + const sp& windowInfosListener, + const sp&, std::pair, std::vector>* outInitialInfo); status_t removeWindowInfosListener(const sp& windowInfosListener, - const sp& surfaceComposer); - void reconnect(const sp&); + const sp& surfaceComposer); + void reconnect(const sp&); private: std::mutex mListenersMutex; - std::unordered_set, SpHash> + std::unordered_set, gui::SpHash> mWindowInfosListeners GUARDED_BY(mListenersMutex); std::vector mLastWindowInfos GUARDED_BY(mListenersMutex); diff --git a/libs/gui/include/private/gui/ComposerServiceAIDL.h b/libs/gui/include/private/gui/ComposerServiceAIDL.h index 9a96976c0f..296358329b 100644 --- a/libs/gui/include/private/gui/ComposerServiceAIDL.h +++ b/libs/gui/include/private/gui/ComposerServiceAIDL.h @@ -20,6 +20,7 @@ #include #include +#include #include #include diff --git a/libs/gui/tests/RegionSampling_test.cpp b/libs/gui/tests/RegionSampling_test.cpp index c9106bed4c..e6a9d6caaf 100644 --- a/libs/gui/tests/RegionSampling_test.cpp +++ b/libs/gui/tests/RegionSampling_test.cpp @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include using namespace std::chrono_literals; @@ -242,24 +242,33 @@ protected: }; TEST_F(RegionSamplingTest, invalidLayerHandle_doesNotCrash) { - sp composer = ComposerService::getComposerService(); + sp composer = ComposerServiceAIDL::getComposerService(); sp listener = new Listener(); - const Rect sampleArea{100, 100, 200, 200}; + gui::ARect sampleArea; + sampleArea.left = 100; + sampleArea.top = 100; + sampleArea.right = 200; + sampleArea.bottom = 200; // Passing in composer service as the layer handle should not crash, we'll // treat it as a layer that no longer exists and silently allow sampling to // occur. - status_t status = composer->addRegionSamplingListener(sampleArea, - IInterface::asBinder(composer), listener); - ASSERT_EQ(NO_ERROR, status); + binder::Status status = + composer->addRegionSamplingListener(sampleArea, IInterface::asBinder(composer), + listener); + ASSERT_EQ(NO_ERROR, status.transactionError()); composer->removeRegionSamplingListener(listener); } TEST_F(RegionSamplingTest, DISABLED_CollectsLuma) { fill_render(rgba_green); - sp composer = ComposerService::getComposerService(); + sp composer = ComposerServiceAIDL::getComposerService(); sp listener = new Listener(); - const Rect sampleArea{100, 100, 200, 200}; + gui::ARect sampleArea; + sampleArea.left = 100; + sampleArea.top = 100; + sampleArea.right = 200; + sampleArea.bottom = 200; composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener); EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received"; @@ -271,9 +280,13 @@ TEST_F(RegionSamplingTest, DISABLED_CollectsLuma) { TEST_F(RegionSamplingTest, DISABLED_CollectsChangingLuma) { fill_render(rgba_green); - sp composer = ComposerService::getComposerService(); + sp composer = ComposerServiceAIDL::getComposerService(); sp listener = new Listener(); - const Rect sampleArea{100, 100, 200, 200}; + gui::ARect sampleArea; + sampleArea.left = 100; + sampleArea.top = 100; + sampleArea.right = 200; + sampleArea.bottom = 200; composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener); EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received"; @@ -291,13 +304,21 @@ TEST_F(RegionSamplingTest, DISABLED_CollectsChangingLuma) { TEST_F(RegionSamplingTest, DISABLED_CollectsLumaFromTwoRegions) { fill_render(rgba_green); - sp composer = ComposerService::getComposerService(); + sp composer = ComposerServiceAIDL::getComposerService(); sp greenListener = new Listener(); - const Rect greenSampleArea{100, 100, 200, 200}; + gui::ARect greenSampleArea; + greenSampleArea.left = 100; + greenSampleArea.top = 100; + greenSampleArea.right = 200; + greenSampleArea.bottom = 200; composer->addRegionSamplingListener(greenSampleArea, mTopLayer->getHandle(), greenListener); sp grayListener = new Listener(); - const Rect graySampleArea{500, 100, 600, 200}; + gui::ARect graySampleArea; + graySampleArea.left = 500; + graySampleArea.top = 100; + graySampleArea.right = 600; + graySampleArea.bottom = 200; composer->addRegionSamplingListener(graySampleArea, mTopLayer->getHandle(), grayListener); EXPECT_TRUE(grayListener->wait_event(300ms)) @@ -312,29 +333,46 @@ TEST_F(RegionSamplingTest, DISABLED_CollectsLumaFromTwoRegions) { } TEST_F(RegionSamplingTest, DISABLED_TestIfInvalidInputParameters) { - sp composer = ComposerService::getComposerService(); + sp composer = ComposerServiceAIDL::getComposerService(); sp listener = new Listener(); - const Rect sampleArea{100, 100, 200, 200}; + + gui::ARect invalidRect; + invalidRect.left = Rect::INVALID_RECT.left; + invalidRect.top = Rect::INVALID_RECT.top; + invalidRect.right = Rect::INVALID_RECT.right; + invalidRect.bottom = Rect::INVALID_RECT.bottom; + + gui::ARect sampleArea; + sampleArea.left = 100; + sampleArea.top = 100; + sampleArea.right = 200; + sampleArea.bottom = 200; // Invalid input sampleArea EXPECT_EQ(BAD_VALUE, - composer->addRegionSamplingListener(Rect::INVALID_RECT, mTopLayer->getHandle(), - listener)); + composer->addRegionSamplingListener(invalidRect, mTopLayer->getHandle(), listener) + .transactionError()); listener->reset(); // Invalid input binder - EXPECT_EQ(NO_ERROR, composer->addRegionSamplingListener(sampleArea, NULL, listener)); + EXPECT_EQ(NO_ERROR, + composer->addRegionSamplingListener(sampleArea, NULL, listener).transactionError()); // Invalid input listener EXPECT_EQ(BAD_VALUE, - composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), NULL)); - EXPECT_EQ(BAD_VALUE, composer->removeRegionSamplingListener(NULL)); + composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), NULL) + .transactionError()); + EXPECT_EQ(BAD_VALUE, composer->removeRegionSamplingListener(NULL).transactionError()); // remove the listener composer->removeRegionSamplingListener(listener); } TEST_F(RegionSamplingTest, DISABLED_TestCallbackAfterRemoveListener) { fill_render(rgba_green); - sp composer = ComposerService::getComposerService(); + sp composer = ComposerServiceAIDL::getComposerService(); sp listener = new Listener(); - const Rect sampleArea{100, 100, 200, 200}; + gui::ARect sampleArea; + sampleArea.left = 100; + sampleArea.top = 100; + sampleArea.right = 200; + sampleArea.bottom = 200; composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener); fill_render(rgba_green); @@ -349,13 +387,18 @@ TEST_F(RegionSamplingTest, DISABLED_TestCallbackAfterRemoveListener) { } TEST_F(RegionSamplingTest, DISABLED_CollectsLumaFromMovingLayer) { - sp composer = ComposerService::getComposerService(); + sp composer = ComposerServiceAIDL::getComposerService(); sp listener = new Listener(); Rect sampleArea{100, 100, 200, 200}; + gui::ARect sampleAreaA; + sampleAreaA.left = sampleArea.left; + sampleAreaA.top = sampleArea.top; + sampleAreaA.right = sampleArea.right; + sampleAreaA.bottom = sampleArea.bottom; // Test: listener in (100, 100). See layer before move, no layer after move. fill_render(rgba_blue); - composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener); + composer->addRegionSamplingListener(sampleAreaA, mTopLayer->getHandle(), listener); EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received"; EXPECT_NEAR(listener->luma(), luma_blue, error_margin); listener->reset(); @@ -367,7 +410,11 @@ TEST_F(RegionSamplingTest, DISABLED_CollectsLumaFromMovingLayer) { // Test: listener offset to (600, 600). No layer before move, see layer after move. fill_render(rgba_green); sampleArea.offsetTo(600, 600); - composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener); + sampleAreaA.left = sampleArea.left; + sampleAreaA.top = sampleArea.top; + sampleAreaA.right = sampleArea.right; + sampleAreaA.bottom = sampleArea.bottom; + composer->addRegionSamplingListener(sampleAreaA, mTopLayer->getHandle(), listener); EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received"; EXPECT_NEAR(listener->luma(), luma_gray, error_margin); listener->reset(); diff --git a/libs/gui/tests/SamplingDemo.cpp b/libs/gui/tests/SamplingDemo.cpp index a083a228a6..f98437b4f8 100644 --- a/libs/gui/tests/SamplingDemo.cpp +++ b/libs/gui/tests/SamplingDemo.cpp @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include using namespace std::chrono_literals; @@ -121,10 +121,22 @@ int main(int, const char**) { const Rect backButtonArea{200, 1606, 248, 1654}; sp backButton = new android::Button("BackButton", backButtonArea); - sp composer = ComposerService::getComposerService(); - composer->addRegionSamplingListener(homeButtonArea, homeButton->getStopLayerHandle(), + gui::ARect homeButtonAreaA; + homeButtonAreaA.left = 490; + homeButtonAreaA.top = 1606; + homeButtonAreaA.right = 590; + homeButtonAreaA.bottom = 1654; + + gui::ARect backButtonAreaA; + backButtonAreaA.left = 200; + backButtonAreaA.top = 1606; + backButtonAreaA.right = 248; + backButtonAreaA.bottom = 1654; + + sp composer = ComposerServiceAIDL::getComposerService(); + composer->addRegionSamplingListener(homeButtonAreaA, homeButton->getStopLayerHandle(), homeButton); - composer->addRegionSamplingListener(backButtonArea, backButton->getStopLayerHandle(), + composer->addRegionSamplingListener(backButtonAreaA, backButton->getStopLayerHandle(), backButton); ProcessState::self()->startThreadPool(); diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 58964d6878..1758aba6d4 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -720,48 +720,6 @@ public: return NO_ERROR; } - status_t addRegionSamplingListener(const Rect& /*samplingArea*/, - const sp& /*stopLayerHandle*/, - const sp& /*listener*/) override { - return NO_ERROR; - } - status_t removeRegionSamplingListener( - const sp& /*listener*/) override { - return NO_ERROR; - } - status_t addFpsListener(int32_t /*taskId*/, const sp& /*listener*/) { - return NO_ERROR; - } - status_t removeFpsListener(const sp& /*listener*/) { return NO_ERROR; } - - status_t addTunnelModeEnabledListener(const sp& /*listener*/) { - return NO_ERROR; - } - - status_t removeTunnelModeEnabledListener( - const sp& /*listener*/) { - return NO_ERROR; - } - - status_t setDesiredDisplayModeSpecs(const sp& /*displayToken*/, - ui::DisplayModeId /*defaultMode*/, - bool /*allowGroupSwitching*/, - float /*primaryRefreshRateMin*/, - float /*primaryRefreshRateMax*/, - float /*appRequestRefreshRateMin*/, - float /*appRequestRefreshRateMax*/) { - return NO_ERROR; - } - status_t getDesiredDisplayModeSpecs(const sp& /*displayToken*/, - ui::DisplayModeId* /*outDefaultMode*/, - bool* /*outAllowGroupSwitching*/, - float* /*outPrimaryRefreshRateMin*/, - float* /*outPrimaryRefreshRateMax*/, - float* /*outAppRequestRefreshRateMin*/, - float* /*outAppRequestRefreshRateMax*/) override { - return NO_ERROR; - }; - status_t setGlobalShadowSettings(const half4& /*ambientColor*/, const half4& /*spotColor*/, float /*lightPosY*/, float /*lightPosZ*/, float /*lightRadius*/) override { @@ -784,25 +742,6 @@ public: return NO_ERROR; } - status_t addTransactionTraceListener( - const sp& /*listener*/) override { - return NO_ERROR; - } - - int getGPUContextPriority() override { return 0; }; - - status_t getMaxAcquiredBufferCount(int* /*buffers*/) const override { return NO_ERROR; } - - status_t addWindowInfosListener( - const sp& /*windowInfosListener*/) const override { - return NO_ERROR; - } - - status_t removeWindowInfosListener( - const sp& /*windowInfosListener*/) const override { - return NO_ERROR; - } - status_t setOverrideFrameRate(uid_t /*uid*/, float /*frameRate*/) override { return NO_ERROR; } protected: @@ -974,6 +913,50 @@ public: return binder::Status::ok(); } + binder::Status addRegionSamplingListener( + const gui::ARect& /*samplingArea*/, const sp& /*stopLayerHandle*/, + const sp& /*listener*/) override { + return binder::Status::ok(); + } + + binder::Status removeRegionSamplingListener( + const sp& /*listener*/) override { + return binder::Status::ok(); + } + + binder::Status addFpsListener(int32_t /*taskId*/, + const sp& /*listener*/) override { + return binder::Status::ok(); + } + + binder::Status removeFpsListener(const sp& /*listener*/) override { + return binder::Status::ok(); + } + + binder::Status addTunnelModeEnabledListener( + const sp& /*listener*/) override { + return binder::Status::ok(); + } + + binder::Status removeTunnelModeEnabledListener( + const sp& /*listener*/) override { + return binder::Status::ok(); + } + + binder::Status setDesiredDisplayModeSpecs(const sp& /*displayToken*/, + int32_t /*defaultMode*/, bool /*allowGroupSwitching*/, + float /*primaryRefreshRateMin*/, + float /*primaryRefreshRateMax*/, + float /*appRequestRefreshRateMin*/, + float /*appRequestRefreshRateMax*/) override { + return binder::Status::ok(); + } + + binder::Status getDesiredDisplayModeSpecs(const sp& /*displayToken*/, + gui::DisplayModeSpecs* /*outSpecs*/) override { + return binder::Status::ok(); + } + binder::Status getDisplayBrightnessSupport(const sp& /*displayToken*/, bool* /*outSupport*/) override { return binder::Status::ok(); @@ -998,6 +981,29 @@ public: binder::Status notifyPowerBoost(int /*boostId*/) override { return binder::Status::ok(); } + binder::Status addTransactionTraceListener( + const sp& /*listener*/) override { + return binder::Status::ok(); + } + + binder::Status getGpuContextPriority(int32_t* /*outPriority*/) override { + return binder::Status::ok(); + } + + binder::Status getMaxAcquiredBufferCount(int32_t* /*buffers*/) override { + return binder::Status::ok(); + } + + binder::Status addWindowInfosListener( + const sp& /*windowInfosListener*/) override { + return binder::Status::ok(); + } + + binder::Status removeWindowInfosListener( + const sp& /*windowInfosListener*/) override { + return binder::Status::ok(); + } + protected: IBinder* onAsBinder() override { return nullptr; } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 333e5b0a82..2cd1393085 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5467,13 +5467,9 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { // access to SF. case BOOT_FINISHED: case GET_HDR_CAPABILITIES: - case SET_DESIRED_DISPLAY_MODE_SPECS: - case GET_DESIRED_DISPLAY_MODE_SPECS: case GET_AUTO_LOW_LATENCY_MODE_SUPPORT: case GET_GAME_CONTENT_TYPE_SUPPORT: case GET_DISPLAYED_CONTENT_SAMPLE: - case ADD_TUNNEL_MODE_ENABLED_LISTENER: - case REMOVE_TUNNEL_MODE_ENABLED_LISTENER: case SET_GLOBAL_SHADOW_SETTINGS: case ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN: { // OVERRIDE_HDR_TYPES is used by CTS tests, which acquire the necessary @@ -5505,35 +5501,10 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { // special permissions. case SET_FRAME_RATE: case GET_DISPLAY_DECORATION_SUPPORT: - case SET_FRAME_TIMELINE_INFO: - case GET_GPU_CONTEXT_PRIORITY: - case GET_MAX_ACQUIRED_BUFFER_COUNT: { + case SET_FRAME_TIMELINE_INFO: { // This is not sensitive information, so should not require permission control. return OK; } - case ADD_FPS_LISTENER: - case REMOVE_FPS_LISTENER: - case ADD_REGION_SAMPLING_LISTENER: - case REMOVE_REGION_SAMPLING_LISTENER: { - // codes that require permission check - IPCThreadState* ipc = IPCThreadState::self(); - const int pid = ipc->getCallingPid(); - const int uid = ipc->getCallingUid(); - if ((uid != AID_GRAPHICS) && - !PermissionCache::checkPermission(sReadFramebuffer, pid, uid)) { - ALOGE("Permission Denial: can't read framebuffer pid=%d, uid=%d", pid, uid); - return PERMISSION_DENIED; - } - return OK; - } - case ADD_TRANSACTION_TRACE_LISTENER: { - IPCThreadState* ipc = IPCThreadState::self(); - const int uid = ipc->getCallingUid(); - if (uid == AID_ROOT || uid == AID_GRAPHICS || uid == AID_SYSTEM || uid == AID_SHELL) { - return OK; - } - return PERMISSION_DENIED; - } case SET_OVERRIDE_FRAME_RATE: { const int uid = IPCThreadState::self()->getCallingUid(); if (uid == AID_ROOT || uid == AID_SYSTEM) { @@ -5541,14 +5512,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { } return PERMISSION_DENIED; } - case ADD_WINDOW_INFOS_LISTENER: - case REMOVE_WINDOW_INFOS_LISTENER: { - const int uid = IPCThreadState::self()->getCallingUid(); - if (uid == AID_SYSTEM || uid == AID_GRAPHICS) { - return OK; - } - return PERMISSION_DENIED; - } case CREATE_DISPLAY: case DESTROY_DISPLAY: case GET_PRIMARY_PHYSICAL_DISPLAY_ID: @@ -5583,11 +5546,24 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case SET_DISPLAY_CONTENT_SAMPLING_ENABLED: case GET_PROTECTED_CONTENT_SUPPORT: case IS_WIDE_COLOR_DISPLAY: + case ADD_REGION_SAMPLING_LISTENER: + case REMOVE_REGION_SAMPLING_LISTENER: + case ADD_FPS_LISTENER: + case REMOVE_FPS_LISTENER: + case ADD_TUNNEL_MODE_ENABLED_LISTENER: + case REMOVE_TUNNEL_MODE_ENABLED_LISTENER: + case ADD_WINDOW_INFOS_LISTENER: + case REMOVE_WINDOW_INFOS_LISTENER: + case SET_DESIRED_DISPLAY_MODE_SPECS: + case GET_DESIRED_DISPLAY_MODE_SPECS: case GET_DISPLAY_BRIGHTNESS_SUPPORT: case SET_DISPLAY_BRIGHTNESS: case ADD_HDR_LAYER_INFO_LISTENER: case REMOVE_HDR_LAYER_INFO_LISTENER: case NOTIFY_POWER_BOOST: + case ADD_TRANSACTION_TRACE_LISTENER: + case GET_GPU_CONTEXT_PRIORITY: + case GET_MAX_ACQUIRED_BUFFER_COUNT: LOG_FATAL("Deprecated opcode: %d, migrated to AIDL", code); return PERMISSION_DENIED; } @@ -7103,7 +7079,7 @@ status_t SurfaceFlinger::addTransactionTraceListener( return NO_ERROR; } -int SurfaceFlinger::getGPUContextPriority() { +int SurfaceFlinger::getGpuContextPriority() { return getRenderEngine().getContextPriority(); } @@ -7753,6 +7729,115 @@ binder::Status SurfaceComposerAIDL::isWideColorDisplay(const sp& token, return binder::Status::fromStatusT(status); } +binder::Status SurfaceComposerAIDL::addRegionSamplingListener( + const gui::ARect& samplingArea, const sp& stopLayerHandle, + const sp& listener) { + status_t status = checkReadFrameBufferPermission(); + if (status != OK) { + return binder::Status::fromStatusT(status); + } + android::Rect rect; + rect.left = samplingArea.left; + rect.top = samplingArea.top; + rect.right = samplingArea.right; + rect.bottom = samplingArea.bottom; + status = mFlinger->addRegionSamplingListener(rect, stopLayerHandle, listener); + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::removeRegionSamplingListener( + const sp& listener) { + status_t status = checkReadFrameBufferPermission(); + if (status == OK) { + status = mFlinger->removeRegionSamplingListener(listener); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::addFpsListener(int32_t taskId, + const sp& listener) { + status_t status = checkReadFrameBufferPermission(); + if (status == OK) { + status = mFlinger->addFpsListener(taskId, listener); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::removeFpsListener(const sp& listener) { + status_t status = checkReadFrameBufferPermission(); + if (status == OK) { + status = mFlinger->removeFpsListener(listener); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::addTunnelModeEnabledListener( + const sp& listener) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->addTunnelModeEnabledListener(listener); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::removeTunnelModeEnabledListener( + const sp& listener) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->removeTunnelModeEnabledListener(listener); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::setDesiredDisplayModeSpecs( + const sp& displayToken, int32_t defaultMode, bool allowGroupSwitching, + float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin, + float appRequestRefreshRateMax) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->setDesiredDisplayModeSpecs(displayToken, + static_cast(defaultMode), + allowGroupSwitching, primaryRefreshRateMin, + primaryRefreshRateMax, + appRequestRefreshRateMin, + appRequestRefreshRateMax); + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getDesiredDisplayModeSpecs(const sp& displayToken, + gui::DisplayModeSpecs* outSpecs) { + if (!outSpecs) { + return binder::Status::fromStatusT(BAD_VALUE); + } + + status_t status = checkAccessPermission(); + if (status != OK) { + return binder::Status::fromStatusT(status); + } + + ui::DisplayModeId displayModeId; + bool allowGroupSwitching; + float primaryRefreshRateMin; + float primaryRefreshRateMax; + float appRequestRefreshRateMin; + float appRequestRefreshRateMax; + status = mFlinger->getDesiredDisplayModeSpecs(displayToken, &displayModeId, + &allowGroupSwitching, &primaryRefreshRateMin, + &primaryRefreshRateMax, &appRequestRefreshRateMin, + &appRequestRefreshRateMax); + if (status == NO_ERROR) { + outSpecs->defaultMode = displayModeId; + outSpecs->allowGroupSwitching = allowGroupSwitching; + outSpecs->primaryRefreshRateMin = primaryRefreshRateMin; + outSpecs->primaryRefreshRateMax = primaryRefreshRateMax; + outSpecs->appRequestRefreshRateMin = appRequestRefreshRateMin; + outSpecs->appRequestRefreshRateMax = appRequestRefreshRateMax; + } + + return binder::Status::fromStatusT(status); +} + binder::Status SurfaceComposerAIDL::getDisplayBrightnessSupport(const sp& displayToken, bool* outSupport) { status_t status = mFlinger->getDisplayBrightnessSupport(displayToken, outSupport); @@ -7794,6 +7879,53 @@ binder::Status SurfaceComposerAIDL::notifyPowerBoost(int boostId) { return binder::Status::fromStatusT(status); } +binder::Status SurfaceComposerAIDL::addTransactionTraceListener( + const sp& listener) { + status_t status; + IPCThreadState* ipc = IPCThreadState::self(); + const int uid = ipc->getCallingUid(); + if (uid == AID_ROOT || uid == AID_GRAPHICS || uid == AID_SYSTEM || uid == AID_SHELL) { + status = mFlinger->addTransactionTraceListener(listener); + } else { + status = PERMISSION_DENIED; + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getGpuContextPriority(int32_t* outPriority) { + *outPriority = mFlinger->getGpuContextPriority(); + return binder::Status::ok(); +} + +binder::Status SurfaceComposerAIDL::getMaxAcquiredBufferCount(int32_t* buffers) { + status_t status = mFlinger->getMaxAcquiredBufferCount(buffers); + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::addWindowInfosListener( + const sp& windowInfosListener) { + status_t status; + const int uid = IPCThreadState::self()->getCallingUid(); + if (uid == AID_SYSTEM || uid == AID_GRAPHICS) { + status = mFlinger->addWindowInfosListener(windowInfosListener); + } else { + status = PERMISSION_DENIED; + } + return binder::Status::fromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::removeWindowInfosListener( + const sp& windowInfosListener) { + status_t status; + const int uid = IPCThreadState::self()->getCallingUid(); + if (uid == AID_SYSTEM || uid == AID_GRAPHICS) { + status = mFlinger->removeWindowInfosListener(windowInfosListener); + } else { + status = PERMISSION_DENIED; + } + return binder::Status::fromStatusT(status); +} + status_t SurfaceComposerAIDL::checkAccessPermission(bool usePermissionCache) { if (!mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(usePermissionCache)) { IPCThreadState* ipc = IPCThreadState::self(); @@ -7816,6 +7948,17 @@ status_t SurfaceComposerAIDL::checkControlDisplayBrightnessPermission() { return OK; } +status_t SurfaceComposerAIDL::checkReadFrameBufferPermission() { + IPCThreadState* ipc = IPCThreadState::self(); + const int pid = ipc->getCallingPid(); + const int uid = ipc->getCallingUid(); + if ((uid != AID_GRAPHICS) && !PermissionCache::checkPermission(sReadFramebuffer, pid, uid)) { + ALOGE("Permission Denial: can't read framebuffer pid=%d, uid=%d", pid, uid); + return PERMISSION_DENIED; + } + return OK; +} + } // namespace android #if defined(__gl_h_) diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 8bd5345403..5e4041de41 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -608,26 +608,24 @@ private: status_t getProtectedContentSupport(bool* outSupported) const; status_t isWideColorDisplay(const sp& displayToken, bool* outIsWideColorDisplay) const; status_t addRegionSamplingListener(const Rect& samplingArea, const sp& stopLayerHandle, - const sp& listener) override; - status_t removeRegionSamplingListener(const sp& listener) override; - status_t addFpsListener(int32_t taskId, const sp& listener) override; - status_t removeFpsListener(const sp& listener) override; - status_t addTunnelModeEnabledListener( - const sp& listener) override; - status_t removeTunnelModeEnabledListener( - const sp& listener) override; + const sp& listener); + status_t removeRegionSamplingListener(const sp& listener); + status_t addFpsListener(int32_t taskId, const sp& listener); + status_t removeFpsListener(const sp& listener); + status_t addTunnelModeEnabledListener(const sp& listener); + status_t removeTunnelModeEnabledListener(const sp& listener); status_t setDesiredDisplayModeSpecs(const sp& displayToken, ui::DisplayModeId displayModeId, bool allowGroupSwitching, float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin, - float appRequestRefreshRateMax) override; + float appRequestRefreshRateMax); status_t getDesiredDisplayModeSpecs(const sp& displayToken, ui::DisplayModeId* outDefaultMode, bool* outAllowGroupSwitching, float* outPrimaryRefreshRateMin, float* outPrimaryRefreshRateMax, float* outAppRequestRefreshRateMin, - float* outAppRequestRefreshRateMax) override; + float* outAppRequestRefreshRateMax); status_t getDisplayBrightnessSupport(const sp& displayToken, bool* outSupport) const; status_t setDisplayBrightness(const sp& displayToken, const gui::DisplayBrightness& brightness); @@ -650,17 +648,15 @@ private: status_t setOverrideFrameRate(uid_t uid, float frameRate) override; - status_t addTransactionTraceListener( - const sp& listener) override; + status_t addTransactionTraceListener(const sp& listener); - int getGPUContextPriority() override; + int getGpuContextPriority(); - status_t getMaxAcquiredBufferCount(int* buffers) const override; + status_t getMaxAcquiredBufferCount(int* buffers) const; - status_t addWindowInfosListener( - const sp& windowInfosListener) const override; + status_t addWindowInfosListener(const sp& windowInfosListener) const; status_t removeWindowInfosListener( - const sp& windowInfosListener) const override; + const sp& windowInfosListener) const; // Implements IBinder::DeathRecipient. void binderDied(const wp& who) override; @@ -1501,6 +1497,24 @@ public: binder::Status getProtectedContentSupport(bool* outSupporte) override; binder::Status isWideColorDisplay(const sp& token, bool* outIsWideColorDisplay) override; + binder::Status addRegionSamplingListener( + const gui::ARect& samplingArea, const sp& stopLayerHandle, + const sp& listener) override; + binder::Status removeRegionSamplingListener( + const sp& listener) override; + binder::Status addFpsListener(int32_t taskId, const sp& listener) override; + binder::Status removeFpsListener(const sp& listener) override; + binder::Status addTunnelModeEnabledListener( + const sp& listener) override; + binder::Status removeTunnelModeEnabledListener( + const sp& listener) override; + binder::Status setDesiredDisplayModeSpecs(const sp& displayToken, int32_t defaultMode, + bool allowGroupSwitching, float primaryRefreshRateMin, + float primaryRefreshRateMax, + float appRequestRefreshRateMin, + float appRequestRefreshRateMax) override; + binder::Status getDesiredDisplayModeSpecs(const sp& displayToken, + gui::DisplayModeSpecs* outSpecs) override; binder::Status getDisplayBrightnessSupport(const sp& displayToken, bool* outSupport) override; binder::Status setDisplayBrightness(const sp& displayToken, @@ -1511,11 +1525,20 @@ public: const sp& displayToken, const sp& listener) override; binder::Status notifyPowerBoost(int boostId) override; + binder::Status addTransactionTraceListener( + const sp& listener) override; + binder::Status getGpuContextPriority(int32_t* outPriority) override; + binder::Status getMaxAcquiredBufferCount(int32_t* buffers) override; + binder::Status addWindowInfosListener( + const sp& windowInfosListener) override; + binder::Status removeWindowInfosListener( + const sp& windowInfosListener) override; private: static const constexpr bool kUsePermissionCache = true; status_t checkAccessPermission(bool usePermissionCache = kUsePermissionCache); status_t checkControlDisplayBrightnessPermission(); + status_t checkReadFrameBufferPermission(); private: sp mFlinger; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index a80aca2f73..e90753ab3f 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -782,7 +782,7 @@ public: return mFlinger->onTransact(code, data, reply, flags); } - auto getGPUContextPriority() { return mFlinger->getGPUContextPriority(); } + auto getGpuContextPriority() { return mFlinger->getGpuContextPriority(); } auto calculateMaxAcquiredBufferCount(Fps refreshRate, std::chrono::nanoseconds presentLatency) const { diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index bf2465ff2d..490d00a7a0 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -436,7 +436,7 @@ public: return mFlinger->onTransact(code, data, reply, flags); } - auto getGPUContextPriority() { return mFlinger->getGPUContextPriority(); } + auto getGpuContextPriority() { return mFlinger->getGpuContextPriority(); } auto calculateMaxAcquiredBufferCount(Fps refreshRate, std::chrono::nanoseconds presentLatency) const { -- GitLab From 3bdef86d85259d70529ef56b481279ec834df22c Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Thu, 3 Mar 2022 11:57:19 -0800 Subject: [PATCH 0045/1310] Migrate and clean up methods of ISurfaceComposer Convert FrameTimelineInfo to aidl parcelable. Add Color, DisplayDecorationSupport and DisplayedFrameStats parcelables. Remove the following methods: authenticateSurfaceTexture, setFrameRate and setFrameTimelineInfo, which alway retrun errors for BLAST. Ramp up error handling. Bug: 222537482 Bug: 222763616 Test: atest libgui_test libsurfaceflinger_unittest SurfaceFlinger_test Change-Id: I3b46bae068ac3d482881dac96972a40e46581d34 --- libs/gui/Android.bp | 1 - libs/gui/FrameTimelineInfo.cpp | 68 ---- libs/gui/ISurfaceComposer.cpp | 345 +----------------- libs/gui/Surface.cpp | 42 +-- libs/gui/SurfaceComposerClient.cpp | 173 ++++++--- libs/gui/WindowInfosListenerReporter.cpp | 6 +- libs/gui/aidl/android/gui/Color.aidl | 25 ++ .../android/gui/DisplayDecorationSupport.aidl | 25 ++ .../aidl/android/gui/DisplayedFrameStats.aidl | 40 ++ .../android/gui/FrameTimelineInfo.aidl} | 31 +- .../aidl/android/gui/ISurfaceComposer.aidl | 54 ++- libs/gui/include/gui/AidlStatusUtil.h | 114 ++++++ libs/gui/include/gui/ISurfaceComposer.h | 84 +---- libs/gui/include/gui/Surface.h | 4 +- libs/gui/include/gui/SurfaceComposerClient.h | 2 + libs/gui/include/gui/VsyncEventData.h | 2 +- libs/gui/tests/BLASTBufferQueue_test.cpp | 6 +- libs/gui/tests/RegionSampling_test.cpp | 19 +- libs/gui/tests/Surface_test.cpp | 62 ++-- services/surfaceflinger/SurfaceFlinger.cpp | 295 ++++++++------- services/surfaceflinger/SurfaceFlinger.h | 25 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 1 - .../fuzzer/surfaceflinger_layer_fuzzer.cpp | 6 +- .../tests/BootDisplayMode_test.cpp | 11 +- .../surfaceflinger/tests/Credentials_test.cpp | 8 +- .../tests/LayerCallback_test.cpp | 5 +- .../tests/LayerTransactionTest.h | 3 +- .../tests/fakehwc/SFFakeHwc_test.cpp | 3 +- .../tests/unittests/FrameTimelineTest.cpp | 313 ++++++++++------ .../unittests/TransactionSurfaceFrameTest.cpp | 97 +++-- .../tests/utils/ScreenshotUtils.h | 14 +- 31 files changed, 946 insertions(+), 938 deletions(-) delete mode 100644 libs/gui/FrameTimelineInfo.cpp create mode 100644 libs/gui/aidl/android/gui/Color.aidl create mode 100644 libs/gui/aidl/android/gui/DisplayDecorationSupport.aidl create mode 100644 libs/gui/aidl/android/gui/DisplayedFrameStats.aidl rename libs/gui/{include/gui/FrameTimelineInfo.h => aidl/android/gui/FrameTimelineInfo.aidl} (70%) create mode 100644 libs/gui/include/gui/AidlStatusUtil.h diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index dd7e082487..a3f5a06d3b 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -198,7 +198,6 @@ cc_library_shared { "DebugEGLImageTracker.cpp", "DisplayEventDispatcher.cpp", "DisplayEventReceiver.cpp", - "FrameTimelineInfo.cpp", "GLConsumer.cpp", "IConsumerListener.cpp", "IGraphicBufferConsumer.cpp", diff --git a/libs/gui/FrameTimelineInfo.cpp b/libs/gui/FrameTimelineInfo.cpp deleted file mode 100644 index 3800b88ab0..0000000000 --- a/libs/gui/FrameTimelineInfo.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2021 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 "FrameTimelineInfo" - -#include - -#include -#include -#include -#include -#include - -#include - -using android::os::IInputConstants; - -namespace android { - -status_t FrameTimelineInfo::write(Parcel& output) const { - SAFE_PARCEL(output.writeInt64, vsyncId); - SAFE_PARCEL(output.writeInt32, inputEventId); - SAFE_PARCEL(output.writeInt64, startTimeNanos); - return NO_ERROR; -} - -status_t FrameTimelineInfo::read(const Parcel& input) { - SAFE_PARCEL(input.readInt64, &vsyncId); - SAFE_PARCEL(input.readInt32, &inputEventId); - SAFE_PARCEL(input.readInt64, &startTimeNanos); - return NO_ERROR; -} - -void FrameTimelineInfo::merge(const FrameTimelineInfo& other) { - // When merging vsync Ids we take the oldest valid one - if (vsyncId != INVALID_VSYNC_ID && other.vsyncId != INVALID_VSYNC_ID) { - if (other.vsyncId > vsyncId) { - vsyncId = other.vsyncId; - inputEventId = other.inputEventId; - startTimeNanos = other.startTimeNanos; - } - } else if (vsyncId == INVALID_VSYNC_ID) { - vsyncId = other.vsyncId; - inputEventId = other.inputEventId; - startTimeNanos = other.startTimeNanos; - } -} - -void FrameTimelineInfo::clear() { - vsyncId = INVALID_VSYNC_ID; - inputEventId = IInputConstants::INVALID_INPUT_EVENT_ID; - startTimeNanos = 0; -} - -}; // namespace android diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index c3b33cb595..80e512379f 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -80,7 +80,7 @@ public: Parcel data, reply; data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(frameTimelineInfo.write, data); + frameTimelineInfo.writeToParcel(&data); SAFE_PARCEL(data.writeUint32, static_cast(state.size())); for (const auto& s : state) { @@ -124,40 +124,6 @@ public: remote()->transact(BnSurfaceComposer::BOOT_FINISHED, data, &reply); } - bool authenticateSurfaceTexture( - const sp& bufferProducer) const override { - Parcel data, reply; - int err = NO_ERROR; - err = data.writeInterfaceToken( - ISurfaceComposer::getInterfaceDescriptor()); - if (err != NO_ERROR) { - ALOGE("ISurfaceComposer::authenticateSurfaceTexture: error writing " - "interface descriptor: %s (%d)", strerror(-err), -err); - return false; - } - err = data.writeStrongBinder(IInterface::asBinder(bufferProducer)); - if (err != NO_ERROR) { - ALOGE("ISurfaceComposer::authenticateSurfaceTexture: error writing " - "strong binder to parcel: %s (%d)", strerror(-err), -err); - return false; - } - err = remote()->transact(BnSurfaceComposer::AUTHENTICATE_SURFACE, data, - &reply); - if (err != NO_ERROR) { - ALOGE("ISurfaceComposer::authenticateSurfaceTexture: error " - "performing transaction: %s (%d)", strerror(-err), -err); - return false; - } - int32_t result = 0; - err = reply.readInt32(&result); - if (err != NO_ERROR) { - ALOGE("ISurfaceComposer::authenticateSurfaceTexture: error " - "retrieving result: %s (%d)", strerror(-err), -err); - return false; - } - return result != 0; - } - sp createDisplayEventConnection( VsyncSource vsyncSource, EventRegistrationFlags eventRegistration) override { Parcel data, reply; @@ -180,181 +146,6 @@ public: result = interface_cast(reply.readStrongBinder()); return result; } - - status_t getDisplayedContentSample(const sp& display, uint64_t maxFrames, - uint64_t timestamp, - DisplayedFrameStats* outStats) const override { - if (!outStats) return BAD_VALUE; - - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - data.writeStrongBinder(display); - data.writeUint64(maxFrames); - data.writeUint64(timestamp); - - status_t result = - remote()->transact(BnSurfaceComposer::GET_DISPLAYED_CONTENT_SAMPLE, data, &reply); - - if (result != NO_ERROR) { - return result; - } - - result = reply.readUint64(&outStats->numFrames); - if (result != NO_ERROR) { - return result; - } - - result = reply.readUint64Vector(&outStats->component_0_sample); - if (result != NO_ERROR) { - return result; - } - result = reply.readUint64Vector(&outStats->component_1_sample); - if (result != NO_ERROR) { - return result; - } - result = reply.readUint64Vector(&outStats->component_2_sample); - if (result != NO_ERROR) { - return result; - } - result = reply.readUint64Vector(&outStats->component_3_sample); - return result; - } - - status_t setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor, - float lightPosY, float lightPosZ, float lightRadius) override { - Parcel data, reply; - status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (error != NO_ERROR) { - ALOGE("setGlobalShadowSettings: failed to write interface token: %d", error); - return error; - } - - std::vector shadowConfig = {ambientColor.r, ambientColor.g, ambientColor.b, - ambientColor.a, spotColor.r, spotColor.g, - spotColor.b, spotColor.a, lightPosY, - lightPosZ, lightRadius}; - - error = data.writeFloatVector(shadowConfig); - if (error != NO_ERROR) { - ALOGE("setGlobalShadowSettings: failed to write shadowConfig: %d", error); - return error; - } - - error = remote()->transact(BnSurfaceComposer::SET_GLOBAL_SHADOW_SETTINGS, data, &reply, - IBinder::FLAG_ONEWAY); - if (error != NO_ERROR) { - ALOGE("setGlobalShadowSettings: failed to transact: %d", error); - return error; - } - return NO_ERROR; - } - - status_t getDisplayDecorationSupport( - const sp& displayToken, - std::optional* outSupport) const override { - Parcel data, reply; - status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (error != NO_ERROR) { - ALOGE("getDisplayDecorationSupport: failed to write interface token: %d", error); - return error; - } - error = data.writeStrongBinder(displayToken); - if (error != NO_ERROR) { - ALOGE("getDisplayDecorationSupport: failed to write display token: %d", error); - return error; - } - error = remote()->transact(BnSurfaceComposer::GET_DISPLAY_DECORATION_SUPPORT, data, &reply); - if (error != NO_ERROR) { - ALOGE("getDisplayDecorationSupport: failed to transact: %d", error); - return error; - } - bool support; - error = reply.readBool(&support); - if (error != NO_ERROR) { - ALOGE("getDisplayDecorationSupport: failed to read support: %d", error); - return error; - } - - if (support) { - int32_t format, alphaInterpretation; - error = reply.readInt32(&format); - if (error != NO_ERROR) { - ALOGE("getDisplayDecorationSupport: failed to read format: %d", error); - return error; - } - error = reply.readInt32(&alphaInterpretation); - if (error != NO_ERROR) { - ALOGE("getDisplayDecorationSupport: failed to read alphaInterpretation: %d", error); - return error; - } - outSupport->emplace(); - outSupport->value().format = static_cast(format); - outSupport->value().alphaInterpretation = - static_cast(alphaInterpretation); - } else { - outSupport->reset(); - } - return NO_ERROR; - } - - status_t setFrameRate(const sp& surface, float frameRate, - int8_t compatibility, int8_t changeFrameRateStrategy) override { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(surface)); - SAFE_PARCEL(data.writeFloat, frameRate); - SAFE_PARCEL(data.writeByte, compatibility); - SAFE_PARCEL(data.writeByte, changeFrameRateStrategy); - - status_t err = remote()->transact(BnSurfaceComposer::SET_FRAME_RATE, data, &reply); - if (err != NO_ERROR) { - ALOGE("setFrameRate: failed to transact: %s (%d)", strerror(-err), err); - return err; - } - - return reply.readInt32(); - } - - status_t setFrameTimelineInfo(const sp& surface, - const FrameTimelineInfo& frameTimelineInfo) override { - Parcel data, reply; - status_t err = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - if (err != NO_ERROR) { - ALOGE("%s: failed writing interface token: %s (%d)", __func__, strerror(-err), -err); - return err; - } - - err = data.writeStrongBinder(IInterface::asBinder(surface)); - if (err != NO_ERROR) { - ALOGE("%s: failed writing strong binder: %s (%d)", __func__, strerror(-err), -err); - return err; - } - - SAFE_PARCEL(frameTimelineInfo.write, data); - - err = remote()->transact(BnSurfaceComposer::SET_FRAME_TIMELINE_INFO, data, &reply); - if (err != NO_ERROR) { - ALOGE("%s: failed to transact: %s (%d)", __func__, strerror(-err), err); - return err; - } - - return reply.readInt32(); - } - - status_t setOverrideFrameRate(uid_t uid, float frameRate) override { - Parcel data, reply; - SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor()); - SAFE_PARCEL(data.writeUint32, uid); - SAFE_PARCEL(data.writeFloat, frameRate); - - status_t err = remote()->transact(BnSurfaceComposer::SET_OVERRIDE_FRAME_RATE, data, &reply); - if (err != NO_ERROR) { - ALOGE("setOverrideFrameRate: failed to transact %s (%d)", strerror(-err), err); - return err; - } - - return NO_ERROR; - } }; // Out-of-line virtual method definition to trigger vtable emission in this @@ -379,7 +170,7 @@ status_t BnSurfaceComposer::onTransact( CHECK_INTERFACE(ISurfaceComposer, data, reply); FrameTimelineInfo frameTimelineInfo; - SAFE_PARCEL(frameTimelineInfo.read, data); + frameTimelineInfo.readFromParcel(&data); uint32_t count = 0; SAFE_PARCEL_READ_SIZE(data.readUint32, &count, data.dataSize()); @@ -444,14 +235,6 @@ status_t BnSurfaceComposer::onTransact( bootFinished(); return NO_ERROR; } - case AUTHENTICATE_SURFACE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp bufferProducer = - interface_cast(data.readStrongBinder()); - int32_t result = authenticateSurfaceTexture(bufferProducer) ? 1 : 0; - reply->writeInt32(result); - return NO_ERROR; - } case CREATE_DISPLAY_EVENT_CONNECTION: { CHECK_INTERFACE(ISurfaceComposer, data, reply); auto vsyncSource = static_cast(data.readInt32()); @@ -463,130 +246,6 @@ status_t BnSurfaceComposer::onTransact( reply->writeStrongBinder(IInterface::asBinder(connection)); return NO_ERROR; } - case GET_DISPLAYED_CONTENT_SAMPLE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - - sp display = data.readStrongBinder(); - uint64_t maxFrames = 0; - uint64_t timestamp = 0; - - status_t result = data.readUint64(&maxFrames); - if (result != NO_ERROR) { - ALOGE("getDisplayedContentSample failure in reading max frames: %d", result); - return result; - } - - result = data.readUint64(×tamp); - if (result != NO_ERROR) { - ALOGE("getDisplayedContentSample failure in reading timestamp: %d", result); - return result; - } - - DisplayedFrameStats stats; - result = getDisplayedContentSample(display, maxFrames, timestamp, &stats); - if (result == NO_ERROR) { - reply->writeUint64(stats.numFrames); - reply->writeUint64Vector(stats.component_0_sample); - reply->writeUint64Vector(stats.component_1_sample); - reply->writeUint64Vector(stats.component_2_sample); - reply->writeUint64Vector(stats.component_3_sample); - } - return result; - } - case SET_GLOBAL_SHADOW_SETTINGS: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - - std::vector shadowConfig; - status_t error = data.readFloatVector(&shadowConfig); - if (error != NO_ERROR || shadowConfig.size() != 11) { - ALOGE("setGlobalShadowSettings: failed to read shadowConfig: %d", error); - return error; - } - - half4 ambientColor = {shadowConfig[0], shadowConfig[1], shadowConfig[2], - shadowConfig[3]}; - half4 spotColor = {shadowConfig[4], shadowConfig[5], shadowConfig[6], shadowConfig[7]}; - float lightPosY = shadowConfig[8]; - float lightPosZ = shadowConfig[9]; - float lightRadius = shadowConfig[10]; - return setGlobalShadowSettings(ambientColor, spotColor, lightPosY, lightPosZ, - lightRadius); - } - case GET_DISPLAY_DECORATION_SUPPORT: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp displayToken; - SAFE_PARCEL(data.readNullableStrongBinder, &displayToken); - std::optional support; - auto error = getDisplayDecorationSupport(displayToken, &support); - if (error != NO_ERROR) { - ALOGE("getDisplayDecorationSupport failed with error %d", error); - return error; - } - reply->writeBool(support.has_value()); - if (support) { - reply->writeInt32(static_cast(support.value().format)); - reply->writeInt32(static_cast(support.value().alphaInterpretation)); - } - return error; - } - case SET_FRAME_RATE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp binder; - SAFE_PARCEL(data.readStrongBinder, &binder); - - sp surface = interface_cast(binder); - if (!surface) { - ALOGE("setFrameRate: failed to cast to IGraphicBufferProducer"); - return BAD_VALUE; - } - float frameRate; - SAFE_PARCEL(data.readFloat, &frameRate); - - int8_t compatibility; - SAFE_PARCEL(data.readByte, &compatibility); - - int8_t changeFrameRateStrategy; - SAFE_PARCEL(data.readByte, &changeFrameRateStrategy); - - status_t result = - setFrameRate(surface, frameRate, compatibility, changeFrameRateStrategy); - reply->writeInt32(result); - return NO_ERROR; - } - case SET_FRAME_TIMELINE_INFO: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp binder; - status_t err = data.readStrongBinder(&binder); - if (err != NO_ERROR) { - ALOGE("setFrameTimelineInfo: failed to read strong binder: %s (%d)", strerror(-err), - -err); - return err; - } - sp surface = interface_cast(binder); - if (!surface) { - ALOGE("setFrameTimelineInfo: failed to cast to IGraphicBufferProducer: %s (%d)", - strerror(-err), -err); - return err; - } - - FrameTimelineInfo frameTimelineInfo; - SAFE_PARCEL(frameTimelineInfo.read, data); - - status_t result = setFrameTimelineInfo(surface, frameTimelineInfo); - reply->writeInt32(result); - return NO_ERROR; - } - case SET_OVERRIDE_FRAME_RATE: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - - uid_t uid; - SAFE_PARCEL(data.readUint32, &uid); - - float frameRate; - SAFE_PARCEL(data.readFloat, &frameRate); - - return setOverrideFrameRate(uid, frameRate); - } default: { return BBinder::onTransact(code, data, reply, flags); } diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index a40837ce2a..7a2615f439 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -39,6 +39,7 @@ #include #include +#include #include #include @@ -49,6 +50,7 @@ namespace android { +using gui::aidl_utils::statusTFromBinderStatus; using ui::Dataspace; namespace { @@ -182,7 +184,7 @@ status_t Surface::getDisplayRefreshCycleDuration(nsecs_t* outRefreshDuration) { gui::DisplayStatInfo stats; binder::Status status = composerServiceAIDL()->getDisplayStats(nullptr, &stats); if (!status.isOk()) { - return status.transactionError(); + return statusTFromBinderStatus(status); } *outRefreshDuration = stats.vsyncPeriod; @@ -355,7 +357,7 @@ status_t Surface::getWideColorSupport(bool* supported) { *supported = false; binder::Status status = composerServiceAIDL()->isWideColorDisplay(display, supported); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t Surface::getHdrSupport(bool* supported) { @@ -369,7 +371,7 @@ status_t Surface::getHdrSupport(bool* supported) { gui::DynamicDisplayInfo info; if (binder::Status status = composerServiceAIDL()->getDynamicDisplayInfo(display, &info); !status.isOk()) { - return status.transactionError(); + return statusTFromBinderStatus(status); } *supported = !info.hdrCapabilities.supportedHdrTypes.empty(); @@ -1288,15 +1290,12 @@ int Surface::query(int what, int* value) const { if (err == NO_ERROR) { return NO_ERROR; } - sp surfaceComposer = composerService(); + sp surfaceComposer = composerServiceAIDL(); if (surfaceComposer == nullptr) { return -EPERM; // likely permissions error } - if (surfaceComposer->authenticateSurfaceTexture(mGraphicBufferProducer)) { - *value = 1; - } else { - *value = 0; - } + // ISurfaceComposer no longer supports authenticateSurfaceTexture + *value = 0; return NO_ERROR; } case NATIVE_WINDOW_CONCRETE_TYPE: @@ -1868,7 +1867,11 @@ int Surface::dispatchSetFrameTimelineInfo(va_list args) { auto startTimeNanos = static_cast(va_arg(args, int64_t)); ALOGV("Surface::%s", __func__); - return setFrameTimelineInfo({frameTimelineVsyncId, inputEventId, startTimeNanos}); + FrameTimelineInfo ftlInfo; + ftlInfo.vsyncId = frameTimelineVsyncId; + ftlInfo.inputEventId = inputEventId; + ftlInfo.startTimeNanos = startTimeNanos; + return setFrameTimelineInfo(ftlInfo); } bool Surface::transformToDisplayInverse() const { @@ -2624,22 +2627,17 @@ void Surface::ProducerListenerProxy::onBuffersDiscarded(const std::vectoronBuffersDiscarded(discardedBufs); } -status_t Surface::setFrameRate(float frameRate, int8_t compatibility, - int8_t changeFrameRateStrategy) { +status_t Surface::setFrameRate(float /*frameRate*/, int8_t /*compatibility*/, + int8_t /*changeFrameRateStrategy*/) { ATRACE_CALL(); ALOGV("Surface::setFrameRate"); - - if (!ValidateFrameRate(frameRate, compatibility, changeFrameRateStrategy, - "Surface::setFrameRate")) { - return BAD_VALUE; - } - - return composerService()->setFrameRate(mGraphicBufferProducer, frameRate, compatibility, - changeFrameRateStrategy); + // ISurfaceComposer no longer supports setFrameRate + return BAD_VALUE; } -status_t Surface::setFrameTimelineInfo(const FrameTimelineInfo& frameTimelineInfo) { - return composerService()->setFrameTimelineInfo(mGraphicBufferProducer, frameTimelineInfo); +status_t Surface::setFrameTimelineInfo(const FrameTimelineInfo& /*frameTimelineInfo*/) { + // ISurfaceComposer no longer supports setFrameTimelineInfo + return BAD_VALUE; } sp Surface::getSurfaceControlHandle() const { diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index e54ff49391..065deb6143 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -33,6 +34,7 @@ #include +#include #include #include #include @@ -61,6 +63,7 @@ using gui::IRegionSamplingListener; using gui::WindowInfo; using gui::WindowInfoHandle; using gui::WindowInfosListener; +using gui::aidl_utils::statusTFromBinderStatus; using ui::ColorMode; // --------------------------------------------------------------------------- @@ -645,7 +648,7 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel const int64_t desiredPresentTime = parcel->readInt64(); const bool isAutoTimestamp = parcel->readBool(); FrameTimelineInfo frameTimelineInfo; - SAFE_PARCEL(frameTimelineInfo.read, *parcel); + frameTimelineInfo.readFromParcel(parcel); sp applyToken; parcel->readNullableStrongBinder(&applyToken); @@ -752,7 +755,7 @@ status_t SurfaceComposerClient::Transaction::writeToParcel(Parcel* parcel) const parcel->writeBool(mContainsBuffer); parcel->writeInt64(mDesiredPresentTime); parcel->writeBool(mIsAutoTimestamp); - SAFE_PARCEL(mFrameTimelineInfo.write, *parcel); + mFrameTimelineInfo.writeToParcel(parcel); parcel->writeStrongBinder(mApplyToken); parcel->writeUint32(static_cast(mDisplayStates.size())); for (auto const& displayState : mDisplayStates) { @@ -853,7 +856,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::merge(Tr mEarlyWakeupEnd = mEarlyWakeupEnd || other.mEarlyWakeupEnd; mApplyToken = other.mApplyToken; - mFrameTimelineInfo.merge(other.mFrameTimelineInfo); + mergeFrameTimelineInfo(mFrameTimelineInfo, other.mFrameTimelineInfo); other.clear(); return *this; @@ -872,7 +875,7 @@ void SurfaceComposerClient::Transaction::clear() { mEarlyWakeupEnd = false; mDesiredPresentTime = 0; mIsAutoTimestamp = true; - mFrameTimelineInfo.clear(); + clearFrameTimelineInfo(mFrameTimelineInfo); mApplyToken = nullptr; } @@ -1060,7 +1063,7 @@ status_t SurfaceComposerClient::getPrimaryPhysicalDisplayId(PhysicalDisplayId* i if (status.isOk()) { *id = *DisplayId::fromValue(static_cast(displayId)); } - return status.transactionError(); + return statusTFromBinderStatus(status); } std::optional SurfaceComposerClient::getInternalDisplayId() { @@ -1809,7 +1812,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFixed SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFrameTimelineInfo( const FrameTimelineInfo& frameTimelineInfo) { - mFrameTimelineInfo.merge(frameTimelineInfo); + mergeFrameTimelineInfo(mFrameTimelineInfo, frameTimelineInfo); return *this; } @@ -1968,6 +1971,31 @@ void SurfaceComposerClient::Transaction::setDisplaySize(const sp& token s.what |= DisplayState::eDisplaySizeChanged; } +// copied from FrameTimelineInfo::merge() +void SurfaceComposerClient::Transaction::mergeFrameTimelineInfo(FrameTimelineInfo& t, + const FrameTimelineInfo& other) { + // When merging vsync Ids we take the oldest valid one + if (t.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID && + other.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) { + if (other.vsyncId > t.vsyncId) { + t.vsyncId = other.vsyncId; + t.inputEventId = other.inputEventId; + t.startTimeNanos = other.startTimeNanos; + } + } else if (t.vsyncId == FrameTimelineInfo::INVALID_VSYNC_ID) { + t.vsyncId = other.vsyncId; + t.inputEventId = other.inputEventId; + t.startTimeNanos = other.startTimeNanos; + } +} + +// copied from FrameTimelineInfo::clear() +void SurfaceComposerClient::Transaction::clearFrameTimelineInfo(FrameTimelineInfo& t) { + t.vsyncId = FrameTimelineInfo::INVALID_VSYNC_ID; + t.inputEventId = os::IInputConstants::INVALID_INPUT_EVENT_ID; + t.startTimeNanos = 0; +} + // --------------------------------------------------------------------------- SurfaceComposerClient::SurfaceComposerClient() : mStatus(NO_INIT) {} @@ -2122,13 +2150,13 @@ status_t SurfaceComposerClient::getLayerFrameStats(const sp& token, status_t SurfaceComposerClient::enableVSyncInjections(bool enable) { sp sf(ComposerServiceAIDL::getComposerService()); binder::Status status = sf->enableVSyncInjections(enable); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::injectVSync(nsecs_t when) { sp sf(ComposerServiceAIDL::getComposerService()); binder::Status status = sf->injectVSync(when); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getDisplayState(const sp& display, @@ -2142,7 +2170,7 @@ status_t SurfaceComposerClient::getDisplayState(const sp& display, state->layerStackSpaceRect = ui::Size(ds.layerStackSpaceRect.width, ds.layerStackSpaceRect.height); } - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getStaticDisplayInfo(const sp& display, @@ -2187,7 +2215,7 @@ status_t SurfaceComposerClient::getStaticDisplayInfo(const sp& display, outInfo->deviceProductInfo = info; } - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getDynamicDisplayInfo(const sp& display, @@ -2237,7 +2265,7 @@ status_t SurfaceComposerClient::getDynamicDisplayInfo(const sp& display outInfo->gameContentTypeSupported = ginfo.gameContentTypeSupported; outInfo->preferredBootDisplayMode = ginfo.preferredBootDisplayMode; } - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getActiveDisplayMode(const sp& display, @@ -2267,7 +2295,7 @@ status_t SurfaceComposerClient::setDesiredDisplayModeSpecs( primaryRefreshRateMin, primaryRefreshRateMax, appRequestRefreshRateMin, appRequestRefreshRateMax); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getDesiredDisplayModeSpecs(const sp& displayToken, @@ -2293,7 +2321,7 @@ status_t SurfaceComposerClient::getDesiredDisplayModeSpecs(const sp& di *outAppRequestRefreshRateMin = specs.appRequestRefreshRateMin; *outAppRequestRefreshRateMax = specs.appRequestRefreshRateMax; } - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getDisplayNativePrimaries(const sp& display, @@ -2319,37 +2347,39 @@ status_t SurfaceComposerClient::getDisplayNativePrimaries(const sp& dis outPrimaries.white.Y = primaries.white.Y; outPrimaries.white.Z = primaries.white.Z; } - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::setActiveColorMode(const sp& display, ColorMode colorMode) { binder::Status status = ComposerServiceAIDL::getComposerService() ->setActiveColorMode(display, static_cast(colorMode)); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getBootDisplayModeSupport(bool* support) { binder::Status status = ComposerServiceAIDL::getComposerService()->getBootDisplayModeSupport(support); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::setBootDisplayMode(const sp& display, ui::DisplayModeId displayModeId) { binder::Status status = ComposerServiceAIDL::getComposerService() ->setBootDisplayMode(display, static_cast(displayModeId)); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::clearBootDisplayMode(const sp& display) { binder::Status status = ComposerServiceAIDL::getComposerService()->clearBootDisplayMode(display); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::setOverrideFrameRate(uid_t uid, float frameRate) { - return ComposerService::getComposerService()->setOverrideFrameRate(uid, frameRate); + binder::Status status = + ComposerServiceAIDL::getComposerService()->setOverrideFrameRate(uid, frameRate); + return statusTFromBinderStatus(status); } void SurfaceComposerClient::setAutoLowLatencyMode(const sp& display, bool on) { @@ -2377,7 +2407,7 @@ status_t SurfaceComposerClient::getCompositionPreference( *wideColorGamutDataspace = static_cast(pref.wideColorGamutDataspace); *wideColorGamutPixelFormat = static_cast(pref.wideColorGamutPixelFormat); } - return status.transactionError(); + return statusTFromBinderStatus(status); } bool SurfaceComposerClient::getProtectedContentSupport() { @@ -2388,7 +2418,7 @@ bool SurfaceComposerClient::getProtectedContentSupport() { status_t SurfaceComposerClient::clearAnimationFrameStats() { binder::Status status = ComposerServiceAIDL::getComposerService()->clearAnimationFrameStats(); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getAnimationFrameStats(FrameStats* outStats) { @@ -2410,7 +2440,7 @@ status_t SurfaceComposerClient::getAnimationFrameStats(FrameStats* outStats) { outStats->frameReadyTimesNano.add(t); } } - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::overrideHdrTypes(const sp& display, @@ -2423,7 +2453,7 @@ status_t SurfaceComposerClient::overrideHdrTypes(const sp& display, binder::Status status = ComposerServiceAIDL::getComposerService()->overrideHdrTypes(display, hdrTypesVector); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::onPullAtom(const int32_t atomId, std::string* outData, @@ -2434,7 +2464,7 @@ status_t SurfaceComposerClient::onPullAtom(const int32_t atomId, std::string* ou outData->assign((const char*)pad.data.data(), pad.data.size()); *success = pad.success; } - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getDisplayedContentSamplingAttributes(const sp& display, @@ -2453,7 +2483,7 @@ status_t SurfaceComposerClient::getDisplayedContentSamplingAttributes(const sp(attrs.dataspace); *outComponentMask = static_cast(attrs.componentMask); } - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::setDisplayContentSamplingEnabled(const sp& display, @@ -2464,14 +2494,41 @@ status_t SurfaceComposerClient::setDisplayContentSamplingEnabled(const spsetDisplayContentSamplingEnabled(display, enable, static_cast(componentMask), static_cast(maxFrames)); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getDisplayedContentSample(const sp& display, uint64_t maxFrames, uint64_t timestamp, DisplayedFrameStats* outStats) { - return ComposerService::getComposerService()->getDisplayedContentSample(display, maxFrames, - timestamp, outStats); + if (!outStats) { + return BAD_VALUE; + } + + gui::DisplayedFrameStats stats; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getDisplayedContentSample(display, maxFrames, + timestamp, &stats); + if (status.isOk()) { + // convert gui::DisplayedFrameStats to ui::DisplayedFrameStats + outStats->numFrames = static_cast(stats.numFrames); + outStats->component_0_sample.reserve(stats.component_0_sample.size()); + for (const auto& s : stats.component_0_sample) { + outStats->component_0_sample.push_back(static_cast(s)); + } + outStats->component_1_sample.reserve(stats.component_1_sample.size()); + for (const auto& s : stats.component_1_sample) { + outStats->component_1_sample.push_back(static_cast(s)); + } + outStats->component_2_sample.reserve(stats.component_2_sample.size()); + for (const auto& s : stats.component_2_sample) { + outStats->component_2_sample.push_back(static_cast(s)); + } + outStats->component_3_sample.reserve(stats.component_3_sample.size()); + for (const auto& s : stats.component_3_sample) { + outStats->component_3_sample.push_back(static_cast(s)); + } + } + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::isWideColorDisplay(const sp& display, @@ -2479,7 +2536,7 @@ status_t SurfaceComposerClient::isWideColorDisplay(const sp& display, binder::Status status = ComposerServiceAIDL::getComposerService()->isWideColorDisplay(display, outIsWideColorDisplay); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::addRegionSamplingListener( @@ -2494,40 +2551,40 @@ status_t SurfaceComposerClient::addRegionSamplingListener( ComposerServiceAIDL::getComposerService()->addRegionSamplingListener(rect, stopLayerHandle, listener); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::removeRegionSamplingListener( const sp& listener) { binder::Status status = ComposerServiceAIDL::getComposerService()->removeRegionSamplingListener(listener); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::addFpsListener(int32_t taskId, const sp& listener) { binder::Status status = ComposerServiceAIDL::getComposerService()->addFpsListener(taskId, listener); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::removeFpsListener(const sp& listener) { binder::Status status = ComposerServiceAIDL::getComposerService()->removeFpsListener(listener); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::addTunnelModeEnabledListener( const sp& listener) { binder::Status status = ComposerServiceAIDL::getComposerService()->addTunnelModeEnabledListener(listener); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::removeTunnelModeEnabledListener( const sp& listener) { binder::Status status = ComposerServiceAIDL::getComposerService()->removeTunnelModeEnabledListener(listener); - return status.transactionError(); + return statusTFromBinderStatus(status); } bool SurfaceComposerClient::getDisplayBrightnessSupport(const sp& displayToken) { @@ -2543,7 +2600,7 @@ status_t SurfaceComposerClient::setDisplayBrightness(const sp& displayT binder::Status status = ComposerServiceAIDL::getComposerService()->setDisplayBrightness(displayToken, brightness); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::addHdrLayerInfoListener( @@ -2551,7 +2608,7 @@ status_t SurfaceComposerClient::addHdrLayerInfoListener( binder::Status status = ComposerServiceAIDL::getComposerService()->addHdrLayerInfoListener(displayToken, listener); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::removeHdrLayerInfoListener( @@ -2559,26 +2616,48 @@ status_t SurfaceComposerClient::removeHdrLayerInfoListener( binder::Status status = ComposerServiceAIDL::getComposerService()->removeHdrLayerInfoListener(displayToken, listener); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::notifyPowerBoost(int32_t boostId) { binder::Status status = ComposerServiceAIDL::getComposerService()->notifyPowerBoost(boostId); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor, float lightPosY, float lightPosZ, float lightRadius) { - return ComposerService::getComposerService()->setGlobalShadowSettings(ambientColor, spotColor, - lightPosY, lightPosZ, - lightRadius); + gui::Color ambientColorG, spotColorG; + ambientColorG.r = ambientColor.r; + ambientColorG.g = ambientColor.g; + ambientColorG.b = ambientColor.b; + ambientColorG.a = ambientColor.a; + spotColorG.r = spotColor.r; + spotColorG.g = spotColor.g; + spotColorG.b = spotColor.b; + spotColorG.a = spotColor.a; + binder::Status status = + ComposerServiceAIDL::getComposerService()->setGlobalShadowSettings(ambientColorG, + spotColorG, + lightPosY, lightPosZ, + lightRadius); + return statusTFromBinderStatus(status); } std::optional SurfaceComposerClient::getDisplayDecorationSupport( const sp& displayToken) { + std::optional gsupport; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getDisplayDecorationSupport(displayToken, + &gsupport); std::optional support; - ComposerService::getComposerService()->getDisplayDecorationSupport(displayToken, &support); + if (status.isOk() && gsupport.has_value()) { + support->format = static_cast( + gsupport->format); + support->alphaInterpretation = + static_cast( + gsupport->alphaInterpretation); + } return support; } @@ -2587,7 +2666,7 @@ int SurfaceComposerClient::getGpuContextPriority() { binder::Status status = ComposerServiceAIDL::getComposerService()->getGpuContextPriority(&priority); if (!status.isOk()) { - status_t err = status.transactionError(); + status_t err = statusTFromBinderStatus(status); ALOGE("getGpuContextPriority failed to read data: %s (%d)", strerror(-err), err); return 0; } @@ -2617,7 +2696,7 @@ status_t ScreenshotClient::captureDisplay(const DisplayCaptureArgs& captureArgs, if (s == nullptr) return NO_INIT; binder::Status status = s->captureDisplay(captureArgs, captureListener); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t ScreenshotClient::captureDisplay(DisplayId displayId, @@ -2626,7 +2705,7 @@ status_t ScreenshotClient::captureDisplay(DisplayId displayId, if (s == nullptr) return NO_INIT; binder::Status status = s->captureDisplayById(displayId.value, captureListener); - return status.transactionError(); + return statusTFromBinderStatus(status); } status_t ScreenshotClient::captureLayers(const LayerCaptureArgs& captureArgs, @@ -2635,7 +2714,7 @@ status_t ScreenshotClient::captureLayers(const LayerCaptureArgs& captureArgs, if (s == nullptr) return NO_INIT; binder::Status status = s->captureLayers(captureArgs, captureListener); - return status.transactionError(); + return statusTFromBinderStatus(status); } // --------------------------------------------------------------------------------- diff --git a/libs/gui/WindowInfosListenerReporter.cpp b/libs/gui/WindowInfosListenerReporter.cpp index 0ed83f272c..01e865da6a 100644 --- a/libs/gui/WindowInfosListenerReporter.cpp +++ b/libs/gui/WindowInfosListenerReporter.cpp @@ -15,6 +15,7 @@ */ #include +#include #include namespace android { @@ -23,6 +24,7 @@ using gui::DisplayInfo; using gui::IWindowInfosReportedListener; using gui::WindowInfo; using gui::WindowInfosListener; +using gui::aidl_utils::statusTFromBinderStatus; sp WindowInfosListenerReporter::getInstance() { static sp sInstance = new WindowInfosListenerReporter; @@ -38,7 +40,7 @@ status_t WindowInfosListenerReporter::addWindowInfosListener( std::scoped_lock lock(mListenersMutex); if (mWindowInfosListeners.empty()) { binder::Status s = surfaceComposer->addWindowInfosListener(this); - status = s.transactionError(); + status = statusTFromBinderStatus(s); } if (status == OK) { @@ -62,7 +64,7 @@ status_t WindowInfosListenerReporter::removeWindowInfosListener( std::scoped_lock lock(mListenersMutex); if (mWindowInfosListeners.size() == 1) { binder::Status s = surfaceComposer->removeWindowInfosListener(this); - status = s.transactionError(); + status = statusTFromBinderStatus(s); // Clear the last stored state since we're disabling updates and don't want to hold // stale values mLastWindowInfos.clear(); diff --git a/libs/gui/aidl/android/gui/Color.aidl b/libs/gui/aidl/android/gui/Color.aidl new file mode 100644 index 0000000000..12af066562 --- /dev/null +++ b/libs/gui/aidl/android/gui/Color.aidl @@ -0,0 +1,25 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +/** @hide */ +parcelable Color { + float r; + float g; + float b; + float a; +} diff --git a/libs/gui/aidl/android/gui/DisplayDecorationSupport.aidl b/libs/gui/aidl/android/gui/DisplayDecorationSupport.aidl new file mode 100644 index 0000000000..023049657b --- /dev/null +++ b/libs/gui/aidl/android/gui/DisplayDecorationSupport.aidl @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2022, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +// TODO(b/222607970): +// remove this aidl and use android.hardware.graphics.common.DisplayDecorationSupport +/** @hide */ +parcelable DisplayDecorationSupport { + int format; + int alphaInterpretation; +} diff --git a/libs/gui/aidl/android/gui/DisplayedFrameStats.aidl b/libs/gui/aidl/android/gui/DisplayedFrameStats.aidl new file mode 100644 index 0000000000..f4b6dadc49 --- /dev/null +++ b/libs/gui/aidl/android/gui/DisplayedFrameStats.aidl @@ -0,0 +1,40 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +/** @hide */ +parcelable DisplayedFrameStats { + /* The number of frames represented by this sample. */ + long numFrames = 0; + + /* A histogram counting how many times a pixel of a given value was displayed onscreen for + * FORMAT_COMPONENT_0. The buckets of the histogram are evenly weighted, the number of buckets + * is device specific. eg, for RGBA_8888, if sampleComponent0 is {10, 6, 4, 1} this means that + * 10 red pixels were displayed onscreen in range 0x00->0x3F, 6 red pixels + * were displayed onscreen in range 0x40->0x7F, etc. + */ + long[] component_0_sample; + + /* The same sample definition as sampleComponent0, but for FORMAT_COMPONENT_1. */ + long[] component_1_sample; + + /* The same sample definition as sampleComponent0, but for FORMAT_COMPONENT_2. */ + long[] component_2_sample; + + /* The same sample definition as sampleComponent0, but for FORMAT_COMPONENT_3. */ + long[] component_3_sample; +} diff --git a/libs/gui/include/gui/FrameTimelineInfo.h b/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl similarity index 70% rename from libs/gui/include/gui/FrameTimelineInfo.h rename to libs/gui/aidl/android/gui/FrameTimelineInfo.aidl index 255ce568d2..6ffe466f20 100644 --- a/libs/gui/include/gui/FrameTimelineInfo.h +++ b/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Android Open Source Project + * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,36 +14,23 @@ * limitations under the License. */ -#pragma once +package android.gui; -#include - -#include - -namespace android { - -struct FrameTimelineInfo { +/** @hide */ +parcelable FrameTimelineInfo { // Needs to be in sync with android.graphics.FrameInfo.INVALID_VSYNC_ID in java - static constexpr int64_t INVALID_VSYNC_ID = -1; + const long INVALID_VSYNC_ID = -1; // The vsync id that was used to start the transaction - int64_t vsyncId = INVALID_VSYNC_ID; + long vsyncId = INVALID_VSYNC_ID; // The id of the input event that caused this buffer // Default is android::os::IInputConstants::INVALID_INPUT_EVENT_ID = 0 // We copy the value of the input event ID instead of including the header, because libgui // header libraries containing FrameTimelineInfo must be available to vendors, but libinput is // not directly vendor available. - int32_t inputEventId = 0; + int inputEventId = 0; // The current time in nanoseconds the application started to render the frame. - int64_t startTimeNanos = 0; - - status_t write(Parcel& output) const; - status_t read(const Parcel& input); - - void merge(const FrameTimelineInfo& other); - void clear(); -}; - -} // namespace android + long startTimeNanos = 0; +} diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 1fed69f88f..6ec6f760ae 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -16,10 +16,13 @@ package android.gui; +import android.gui.Color; import android.gui.CompositionPreference; import android.gui.ContentSamplingAttributes; -import android.gui.DisplayCaptureArgs; import android.gui.DisplayBrightness; +import android.gui.DisplayCaptureArgs; +import android.gui.DisplayDecorationSupport; +import android.gui.DisplayedFrameStats; import android.gui.DisplayModeSpecs; import android.gui.DisplayPrimaries; import android.gui.DisplayState; @@ -226,6 +229,13 @@ interface ISurfaceComposer { */ void setDisplayContentSamplingEnabled(IBinder display, boolean enable, byte componentMask, long maxFrames); + /** + * Returns statistics on the color profile of the last frame displayed for a given display + * + * Requires the ACCESS_SURFACE_FLINGER permission. + */ + DisplayedFrameStats getDisplayedContentSample(IBinder display, long maxFrames, long timestamp); + /** * Gets whether SurfaceFlinger can support protected content in GPU composition. */ @@ -370,6 +380,48 @@ interface ISurfaceComposer { */ oneway void notifyPowerBoost(int boostId); + /* + * Sets the global configuration for all the shadows drawn by SurfaceFlinger. Shadow follows + * material design guidelines. + * + * ambientColor + * Color to the ambient shadow. The alpha is premultiplied. + * + * spotColor + * Color to the spot shadow. The alpha is premultiplied. The position of the spot shadow + * depends on the light position. + * + * lightPosY/lightPosZ + * Position of the light used to cast the spot shadow. The X value is always the display + * width / 2. + * + * lightRadius + * Radius of the light casting the shadow. + */ + oneway void setGlobalShadowSettings(in Color ambientColor, in Color spotColor, float lightPosY, float lightPosZ, float lightRadius); + + /** + * Gets whether a display supports DISPLAY_DECORATION layers. + * + * displayToken + * The token of the display. + * outSupport + * An output parameter for whether/how the display supports + * DISPLAY_DECORATION layers. + * + * Returns NO_ERROR upon success. Otherwise, + * NAME_NOT_FOUND if the display is invalid, or + * BAD_VALUE if the output parameter is invalid. + */ + @nullable DisplayDecorationSupport getDisplayDecorationSupport(IBinder displayToken); + + /** + * Set the override frame rate for a specified uid by GameManagerService. + * Passing the frame rate and uid to SurfaceFlinger to update the override mapping + * in the scheduler. + */ + void setOverrideFrameRate(int uid, float frameRate); + /** * Adds a TransactionTraceListener to listen for transaction tracing state updates. */ diff --git a/libs/gui/include/gui/AidlStatusUtil.h b/libs/gui/include/gui/AidlStatusUtil.h new file mode 100644 index 0000000000..55be27bf35 --- /dev/null +++ b/libs/gui/include/gui/AidlStatusUtil.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +// Extracted from frameworks/av/media/libaudioclient/include/media/AidlConversionUtil.h +namespace android::gui::aidl_utils { + +/** + * Return the equivalent Android status_t from a binder exception code. + * + * Generally one should use statusTFromBinderStatus() instead. + * + * Exception codes can be generated from a remote Java service exception, translate + * them for use on the Native side. + * + * Note: for EX_TRANSACTION_FAILED and EX_SERVICE_SPECIFIC a more detailed error code + * can be found from transactionError() or serviceSpecificErrorCode(). + */ +static inline status_t statusTFromExceptionCode(int32_t exceptionCode) { + using namespace ::android::binder; + switch (exceptionCode) { + case Status::EX_NONE: + return OK; + case Status::EX_SECURITY: // Java SecurityException, rethrows locally in Java + return PERMISSION_DENIED; + case Status::EX_BAD_PARCELABLE: // Java BadParcelableException, rethrows in Java + case Status::EX_ILLEGAL_ARGUMENT: // Java IllegalArgumentException, rethrows in Java + case Status::EX_NULL_POINTER: // Java NullPointerException, rethrows in Java + return BAD_VALUE; + case Status::EX_ILLEGAL_STATE: // Java IllegalStateException, rethrows in Java + case Status::EX_UNSUPPORTED_OPERATION: // Java UnsupportedOperationException, rethrows + return INVALID_OPERATION; + case Status::EX_HAS_REPLY_HEADER: // Native strictmode violation + case Status::EX_PARCELABLE: // Java bootclass loader (not standard exception), rethrows + case Status::EX_NETWORK_MAIN_THREAD: // Java NetworkOnMainThreadException, rethrows + case Status::EX_TRANSACTION_FAILED: // Native - see error code + case Status::EX_SERVICE_SPECIFIC: // Java ServiceSpecificException, + // rethrows in Java with integer error code + return UNKNOWN_ERROR; + } + return UNKNOWN_ERROR; +} + +/** + * Return the equivalent Android status_t from a binder status. + * + * Used to handle errors from a AIDL method declaration + * + * [oneway] void method(type0 param0, ...) + * + * or the following (where return_type is not a status_t) + * + * return_type method(type0 param0, ...) + */ +static inline status_t statusTFromBinderStatus(const ::android::binder::Status &status) { + return status.isOk() ? OK // check OK, + : status.serviceSpecificErrorCode() // service-side error, not standard Java exception + // (fromServiceSpecificError) + ?: status.transactionError() // a native binder transaction error (fromStatusT) + ?: statusTFromExceptionCode(status.exceptionCode()); // a service-side error with a + // standard Java exception (fromExceptionCode) +} + +/** + * Return a binder::Status from native service status. + * + * This is used for methods not returning an explicit status_t, + * where Java callers expect an exception, not an integer return value. + */ +static inline ::android::binder::Status binderStatusFromStatusT( + status_t status, const char *optionalMessage = nullptr) { + const char *const emptyIfNull = optionalMessage == nullptr ? "" : optionalMessage; + // From binder::Status instructions: + // Prefer a generic exception code when possible, then a service specific + // code, and finally a status_t for low level failures or legacy support. + // Exception codes and service specific errors map to nicer exceptions for + // Java clients. + + using namespace ::android::binder; + switch (status) { + case OK: + return Status::ok(); + case PERMISSION_DENIED: // throw SecurityException on Java side + return Status::fromExceptionCode(Status::EX_SECURITY, emptyIfNull); + case BAD_VALUE: // throw IllegalArgumentException on Java side + return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, emptyIfNull); + case INVALID_OPERATION: // throw IllegalStateException on Java side + return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, emptyIfNull); + } + + // A service specific error will not show on status.transactionError() so + // be sure to use statusTFromBinderStatus() for reliable error handling. + + // throw a ServiceSpecificException. + return Status::fromServiceSpecificError(status, emptyIfNull); +} + +} // namespace android::gui::aidl_utils diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index 858bd1d55e..8f75d296c4 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include #include #include @@ -28,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -66,6 +66,7 @@ class IGraphicBufferProducer; class ISurfaceComposerClient; class Rect; +using gui::FrameTimelineInfo; using gui::IDisplayEventConnection; using gui::IRegionSamplingListener; using gui::IScreenCaptureListener; @@ -148,79 +149,6 @@ public: * Requires ACCESS_SURFACE_FLINGER permission */ virtual void bootFinished() = 0; - - /* verify that an IGraphicBufferProducer was created by SurfaceFlinger. - */ - virtual bool authenticateSurfaceTexture( - const sp& surface) const = 0; - - /* Returns statistics on the color profile of the last frame displayed for a given display - * - * Requires the ACCESS_SURFACE_FLINGER permission. - */ - virtual status_t getDisplayedContentSample(const sp& display, uint64_t maxFrames, - uint64_t timestamp, - DisplayedFrameStats* outStats) const = 0; - - /* - * Sets the global configuration for all the shadows drawn by SurfaceFlinger. Shadow follows - * material design guidelines. - * - * ambientColor - * Color to the ambient shadow. The alpha is premultiplied. - * - * spotColor - * Color to the spot shadow. The alpha is premultiplied. The position of the spot shadow - * depends on the light position. - * - * lightPosY/lightPosZ - * Position of the light used to cast the spot shadow. The X value is always the display - * width / 2. - * - * lightRadius - * Radius of the light casting the shadow. - */ - virtual status_t setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor, - float lightPosY, float lightPosZ, - float lightRadius) = 0; - - /* - * Gets whether a display supports DISPLAY_DECORATION layers. - * - * displayToken - * The token of the display. - * outSupport - * An output parameter for whether/how the display supports - * DISPLAY_DECORATION layers. - * - * Returns NO_ERROR upon success. Otherwise, - * NAME_NOT_FOUND if the display is invalid, or - * BAD_VALUE if the output parameter is invalid. - */ - virtual status_t getDisplayDecorationSupport( - const sp& displayToken, - std::optional* - outSupport) const = 0; - - /* - * Sets the intended frame rate for a surface. See ANativeWindow_setFrameRate() for more info. - */ - virtual status_t setFrameRate(const sp& surface, float frameRate, - int8_t compatibility, int8_t changeFrameRateStrategy) = 0; - - /* - * Set the override frame rate for a specified uid by GameManagerService. - * Passing the frame rate and uid to SurfaceFlinger to update the override mapping - * in the scheduler. - */ - virtual status_t setOverrideFrameRate(uid_t uid, float frameRate) = 0; - - /* - * Sets the frame timeline vsync info received from choreographer that corresponds to next - * buffer submitted on that surface. - */ - virtual status_t setFrameTimelineInfo(const sp& surface, - const FrameTimelineInfo& frameTimelineInfo) = 0; }; // ---------------------------------------------------------------------------- @@ -238,7 +166,7 @@ public: DESTROY_DISPLAY, // Deprecated. Autogenerated by .aidl now. GET_PHYSICAL_DISPLAY_TOKEN, // Deprecated. Autogenerated by .aidl now. SET_TRANSACTION_STATE, - AUTHENTICATE_SURFACE, + AUTHENTICATE_SURFACE, // Deprecated. Autogenerated by .aidl now. GET_SUPPORTED_FRAME_TIMESTAMPS, // Deprecated. Autogenerated by .aidl now. GET_DISPLAY_MODES, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. GET_ACTIVE_DISPLAY_MODE, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. @@ -278,10 +206,10 @@ public: SET_AUTO_LOW_LATENCY_MODE, // Deprecated. Autogenerated by .aidl now. GET_GAME_CONTENT_TYPE_SUPPORT, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. SET_GAME_CONTENT_TYPE, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead. - SET_FRAME_RATE, + SET_FRAME_RATE, // Deprecated. Autogenerated by .aidl now. // Deprecated. Use DisplayManager.setShouldAlwaysRespectAppRequestedMode(true); ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN, - SET_FRAME_TIMELINE_INFO, + SET_FRAME_TIMELINE_INFO, // Deprecated. Autogenerated by .aidl now. ADD_TRANSACTION_TRACE_LISTENER, // Deprecated. Autogenerated by .aidl now. GET_GPU_CONTEXT_PRIORITY, GET_MAX_ACQUIRED_BUFFER_COUNT, @@ -301,7 +229,7 @@ public: GET_BOOT_DISPLAY_MODE_SUPPORT, // Deprecated. Autogenerated by .aidl now. SET_BOOT_DISPLAY_MODE, // Deprecated. Autogenerated by .aidl now. CLEAR_BOOT_DISPLAY_MODE, // Deprecated. Autogenerated by .aidl now. - SET_OVERRIDE_FRAME_RATE, + SET_OVERRIDE_FRAME_RATE, // Deprecated. Autogenerated by .aidl now. // Always append new enum to the end. }; diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h index ab9ebaa882..267c28fde2 100644 --- a/libs/gui/include/gui/Surface.h +++ b/libs/gui/include/gui/Surface.h @@ -17,8 +17,8 @@ #ifndef ANDROID_GUI_SURFACE_H #define ANDROID_GUI_SURFACE_H +#include #include -#include #include #include #include @@ -41,6 +41,8 @@ class ISurfaceComposer; class ISurfaceComposer; +using gui::FrameTimelineInfo; + /* This is the same as ProducerListener except that onBuffersDiscarded is * called with a vector of graphic buffers instead of buffer slots. */ diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 48b870dd3b..8569a27808 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -397,6 +397,8 @@ public: class Transaction : public Parcelable { private: void releaseBufferIfOverwriting(const layer_state_t& state); + static void mergeFrameTimelineInfo(FrameTimelineInfo& t, const FrameTimelineInfo& other); + static void clearFrameTimelineInfo(FrameTimelineInfo& t); protected: std::unordered_map, ComposerState, IBinderHash> mComposerStates; diff --git a/libs/gui/include/gui/VsyncEventData.h b/libs/gui/include/gui/VsyncEventData.h index 8e99539fe9..dfdae214d2 100644 --- a/libs/gui/include/gui/VsyncEventData.h +++ b/libs/gui/include/gui/VsyncEventData.h @@ -16,7 +16,7 @@ #pragma once -#include +#include #include diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp index cb7e94c932..3a9b2b8dcd 100644 --- a/libs/gui/tests/BLASTBufferQueue_test.cpp +++ b/libs/gui/tests/BLASTBufferQueue_test.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -301,8 +302,9 @@ protected: const sp captureListener = new SyncScreenCaptureListener(); binder::Status status = sf->captureDisplay(captureArgs, captureListener); - if (status.transactionError() != NO_ERROR) { - return status.transactionError(); + status_t err = gui::aidl_utils::statusTFromBinderStatus(status); + if (err != NO_ERROR) { + return err; } captureResults = captureListener->waitForResults(); return captureResults.result; diff --git a/libs/gui/tests/RegionSampling_test.cpp b/libs/gui/tests/RegionSampling_test.cpp index e6a9d6caaf..b18b544257 100644 --- a/libs/gui/tests/RegionSampling_test.cpp +++ b/libs/gui/tests/RegionSampling_test.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -27,6 +28,7 @@ #include using namespace std::chrono_literals; +using android::gui::aidl_utils::statusTFromBinderStatus; namespace android::test { @@ -255,7 +257,7 @@ TEST_F(RegionSamplingTest, invalidLayerHandle_doesNotCrash) { binder::Status status = composer->addRegionSamplingListener(sampleArea, IInterface::asBinder(composer), listener); - ASSERT_EQ(NO_ERROR, status.transactionError()); + ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); composer->removeRegionSamplingListener(listener); } @@ -349,17 +351,20 @@ TEST_F(RegionSamplingTest, DISABLED_TestIfInvalidInputParameters) { sampleArea.bottom = 200; // Invalid input sampleArea EXPECT_EQ(BAD_VALUE, - composer->addRegionSamplingListener(invalidRect, mTopLayer->getHandle(), listener) - .transactionError()); + statusTFromBinderStatus(composer->addRegionSamplingListener(invalidRect, + mTopLayer->getHandle(), + listener))); listener->reset(); // Invalid input binder EXPECT_EQ(NO_ERROR, - composer->addRegionSamplingListener(sampleArea, NULL, listener).transactionError()); + statusTFromBinderStatus( + composer->addRegionSamplingListener(sampleArea, NULL, listener))); // Invalid input listener EXPECT_EQ(BAD_VALUE, - composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), NULL) - .transactionError()); - EXPECT_EQ(BAD_VALUE, composer->removeRegionSamplingListener(NULL).transactionError()); + statusTFromBinderStatus(composer->addRegionSamplingListener(sampleArea, + mTopLayer->getHandle(), + NULL))); + EXPECT_EQ(BAD_VALUE, statusTFromBinderStatus(composer->removeRegionSamplingListener(NULL))); // remove the listener composer->removeRegionSamplingListener(listener); } diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 1758aba6d4..a9d8436e41 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -212,8 +213,9 @@ protected: const sp captureListener = new SyncScreenCaptureListener(); binder::Status status = sf->captureDisplay(captureArgs, captureListener); - if (status.transactionError() != NO_ERROR) { - return status.transactionError(); + status_t err = gui::aidl_utils::statusTFromBinderStatus(status); + if (err != NO_ERROR) { + return err; } captureResults = captureListener->waitForResults(); return captureResults.result; @@ -709,40 +711,6 @@ public: } void bootFinished() override {} - bool authenticateSurfaceTexture( - const sp& /*surface*/) const override { - return false; - } - - status_t getDisplayedContentSample(const sp& /*display*/, uint64_t /*maxFrames*/, - uint64_t /*timestamp*/, - DisplayedFrameStats* /*outStats*/) const override { - return NO_ERROR; - } - - status_t setGlobalShadowSettings(const half4& /*ambientColor*/, const half4& /*spotColor*/, - float /*lightPosY*/, float /*lightPosZ*/, - float /*lightRadius*/) override { - return NO_ERROR; - } - - status_t getDisplayDecorationSupport( - const sp& /*displayToken*/, - std::optional* /*outSupport*/) const override { - return NO_ERROR; - } - - status_t setFrameRate(const sp& /*surface*/, float /*frameRate*/, - int8_t /*compatibility*/, int8_t /*changeFrameRateStrategy*/) override { - return NO_ERROR; - } - - status_t setFrameTimelineInfo(const sp& /*surface*/, - const FrameTimelineInfo& /*frameTimelineInfo*/) override { - return NO_ERROR; - } - - status_t setOverrideFrameRate(uid_t /*uid*/, float /*frameRate*/) override { return NO_ERROR; } protected: IBinder* onAsBinder() override { return nullptr; } @@ -908,6 +876,12 @@ public: return binder::Status::ok(); } + binder::Status getDisplayedContentSample(const sp& /*display*/, int64_t /*maxFrames*/, + int64_t /*timestamp*/, + gui::DisplayedFrameStats* /*outStats*/) override { + return binder::Status::ok(); + } + binder::Status isWideColorDisplay(const sp& /*token*/, bool* /*outIsWideColorDisplay*/) override { return binder::Status::ok(); @@ -981,6 +955,22 @@ public: binder::Status notifyPowerBoost(int /*boostId*/) override { return binder::Status::ok(); } + binder::Status setGlobalShadowSettings(const gui::Color& /*ambientColor*/, + const gui::Color& /*spotColor*/, float /*lightPosY*/, + float /*lightPosZ*/, float /*lightRadius*/) override { + return binder::Status::ok(); + } + + binder::Status getDisplayDecorationSupport( + const sp& /*displayToken*/, + std::optional* /*outSupport*/) override { + return binder::Status::ok(); + } + + binder::Status setOverrideFrameRate(int32_t /*uid*/, float /*frameRate*/) override { + return binder::Status::ok(); + } + binder::Status addTransactionTraceListener( const sp& /*listener*/) override { return binder::Status::ok(); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index d8cfeeebd6..df84167dbb 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -171,6 +172,7 @@ using gui::DisplayInfo; using gui::IDisplayEventConnection; using gui::IWindowInfosListener; using gui::WindowInfo; +using gui::aidl_utils::binderStatusFromStatusT; using ui::ColorMode; using ui::Dataspace; using ui::DisplayPrimaries; @@ -873,17 +875,6 @@ void SurfaceFlinger::startBootAnim() { // ---------------------------------------------------------------------------- -bool SurfaceFlinger::authenticateSurfaceTexture( - const sp& bufferProducer) const { - Mutex::Autolock _l(mStateLock); - return authenticateSurfaceTextureLocked(bufferProducer); -} - -bool SurfaceFlinger::authenticateSurfaceTextureLocked( - const sp& /* bufferProducer */) const { - return false; -} - status_t SurfaceFlinger::getSupportedFrameTimestamps( std::vector* outSupported) const { *outSupported = { @@ -5466,8 +5457,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case GET_HDR_CAPABILITIES: case GET_AUTO_LOW_LATENCY_MODE_SUPPORT: case GET_GAME_CONTENT_TYPE_SUPPORT: - case GET_DISPLAYED_CONTENT_SAMPLE: - case SET_GLOBAL_SHADOW_SETTINGS: case ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN: { // OVERRIDE_HDR_TYPES is used by CTS tests, which acquire the necessary // permission dynamically. Don't use the permission cache for this check. @@ -5485,7 +5474,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { // The following calls are currently used by clients that do not // request necessary permissions. However, they do not expose any secret // information, so it is OK to pass them. - case AUTHENTICATE_SURFACE: case GET_ACTIVE_COLOR_MODE: case GET_ACTIVE_DISPLAY_MODE: case GET_DISPLAY_COLOR_MODES: @@ -5493,27 +5481,16 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { // Calling setTransactionState is safe, because you need to have been // granted a reference to Client* and Handle* to do anything with it. case SET_TRANSACTION_STATE: - case CREATE_CONNECTION: - // setFrameRate() is deliberately available for apps to call without any - // special permissions. - case SET_FRAME_RATE: - case GET_DISPLAY_DECORATION_SUPPORT: - case SET_FRAME_TIMELINE_INFO: { + case CREATE_CONNECTION: { // This is not sensitive information, so should not require permission control. return OK; } - case SET_OVERRIDE_FRAME_RATE: { - const int uid = IPCThreadState::self()->getCallingUid(); - if (uid == AID_ROOT || uid == AID_SYSTEM) { - return OK; - } - return PERMISSION_DENIED; - } case CREATE_DISPLAY: case DESTROY_DISPLAY: case GET_PRIMARY_PHYSICAL_DISPLAY_ID: case GET_PHYSICAL_DISPLAY_IDS: case GET_PHYSICAL_DISPLAY_TOKEN: + case AUTHENTICATE_SURFACE: case SET_POWER_MODE: case GET_SUPPORTED_FRAME_TIMESTAMPS: case GET_DISPLAY_STATE: @@ -5541,6 +5518,7 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case GET_COMPOSITION_PREFERENCE: case GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES: case SET_DISPLAY_CONTENT_SAMPLING_ENABLED: + case GET_DISPLAYED_CONTENT_SAMPLE: case GET_PROTECTED_CONTENT_SUPPORT: case IS_WIDE_COLOR_DISPLAY: case ADD_REGION_SAMPLING_LISTENER: @@ -5558,6 +5536,11 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case ADD_HDR_LAYER_INFO_LISTENER: case REMOVE_HDR_LAYER_INFO_LISTENER: case NOTIFY_POWER_BOOST: + case SET_GLOBAL_SHADOW_SETTINGS: + case GET_DISPLAY_DECORATION_SUPPORT: + case SET_FRAME_RATE: + case SET_OVERRIDE_FRAME_RATE: + case SET_FRAME_TIMELINE_INFO: case ADD_TRANSACTION_TRACE_LISTENER: case GET_GPU_CONTEXT_PRIORITY: case GET_MAX_ACQUIRED_BUFFER_COUNT: @@ -6995,39 +6978,6 @@ const std::unordered_map& SurfaceFlinger::getGenericLayer return genericLayerMetadataKeyMap; } -status_t SurfaceFlinger::setFrameRate(const sp& surface, float frameRate, - int8_t compatibility, int8_t changeFrameRateStrategy) { - if (!ValidateFrameRate(frameRate, compatibility, changeFrameRateStrategy, - "SurfaceFlinger::setFrameRate")) { - return BAD_VALUE; - } - - static_cast(mScheduler->schedule([=] { - Mutex::Autolock lock(mStateLock); - if (authenticateSurfaceTextureLocked(surface)) { - sp layer = (static_cast(surface.get()))->getLayer(); - if (layer == nullptr) { - ALOGE("Attempt to set frame rate on a layer that no longer exists"); - return BAD_VALUE; - } - const auto strategy = - Layer::FrameRate::convertChangeFrameRateStrategy(changeFrameRateStrategy); - if (layer->setFrameRate( - Layer::FrameRate(Fps::fromValue(frameRate), - Layer::FrameRate::convertCompatibility(compatibility), - strategy))) { - setTransactionFlags(eTraversalNeeded); - } - } else { - ALOGE("Attempt to set frame rate on an unrecognized IGraphicBufferProducer"); - return BAD_VALUE; - } - return NO_ERROR; - })); - - return NO_ERROR; -} - status_t SurfaceFlinger::setOverrideFrameRate(uid_t uid, float frameRate) { PhysicalDisplayId displayId = [&]() { Mutex::Autolock lock(mStateLock); @@ -7039,24 +6989,6 @@ status_t SurfaceFlinger::setOverrideFrameRate(uid_t uid, float frameRate) { return NO_ERROR; } -status_t SurfaceFlinger::setFrameTimelineInfo(const sp& surface, - const FrameTimelineInfo& frameTimelineInfo) { - Mutex::Autolock lock(mStateLock); - if (!authenticateSurfaceTextureLocked(surface)) { - ALOGE("Attempt to set frame timeline info on an unrecognized IGraphicBufferProducer"); - return BAD_VALUE; - } - - sp layer = (static_cast(surface.get()))->getLayer(); - if (layer == nullptr) { - ALOGE("Attempt to set frame timeline info on a layer that no longer exists"); - return BAD_VALUE; - } - - layer->setFrameTimelineInfoForBuffer(frameTimelineInfo); - return NO_ERROR; -} - void SurfaceFlinger::enableRefreshRateOverlay(bool enable) { for (const auto& [ignored, display] : mDisplays) { if (display->isInternal()) { @@ -7262,7 +7194,7 @@ binder::Status SurfaceComposerAIDL::createDisplay(const std::string& displayName sp* outDisplay) { status_t status = checkAccessPermission(); if (status != OK) { - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } String8 displayName8 = String8::format("%s", displayName.c_str()); *outDisplay = mFlinger->createDisplay(displayName8, secure); @@ -7272,7 +7204,7 @@ binder::Status SurfaceComposerAIDL::createDisplay(const std::string& displayName binder::Status SurfaceComposerAIDL::destroyDisplay(const sp& display) { status_t status = checkAccessPermission(); if (status != OK) { - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } mFlinger->destroyDisplay(display); return binder::Status::ok(); @@ -7292,7 +7224,7 @@ binder::Status SurfaceComposerAIDL::getPhysicalDisplayIds(std::vector* binder::Status SurfaceComposerAIDL::getPrimaryPhysicalDisplayId(int64_t* outDisplayId) { status_t status = checkAccessPermission(); if (status != OK) { - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } PhysicalDisplayId id; @@ -7300,7 +7232,7 @@ binder::Status SurfaceComposerAIDL::getPrimaryPhysicalDisplayId(int64_t* outDisp if (status == NO_ERROR) { *outDisplayId = id.value; } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getPhysicalDisplayToken(int64_t displayId, @@ -7313,7 +7245,7 @@ binder::Status SurfaceComposerAIDL::getPhysicalDisplayToken(int64_t displayId, binder::Status SurfaceComposerAIDL::setPowerMode(const sp& display, int mode) { status_t status = checkAccessPermission(); if (status != OK) { - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } mFlinger->setPowerMode(display, mode); return binder::Status::ok(); @@ -7328,7 +7260,7 @@ binder::Status SurfaceComposerAIDL::getSupportedFrameTimestamps( outSupported->clear(); status = mFlinger->getSupportedFrameTimestamps(outSupported); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getDisplayStats(const sp& display, @@ -7339,7 +7271,7 @@ binder::Status SurfaceComposerAIDL::getDisplayStats(const sp& display, outStatInfo->vsyncTime = static_cast(statInfo.vsyncTime); outStatInfo->vsyncPeriod = static_cast(statInfo.vsyncPeriod); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getDisplayState(const sp& display, @@ -7352,7 +7284,7 @@ binder::Status SurfaceComposerAIDL::getDisplayState(const sp& display, outState->layerStackSpaceRect.width = state.layerStackSpaceRect.width; outState->layerStackSpaceRect.height = state.layerStackSpaceRect.height; } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getStaticDisplayInfo(const sp& display, @@ -7393,7 +7325,7 @@ binder::Status SurfaceComposerAIDL::getStaticDisplayInfo(const sp& disp outInfo->deviceProductInfo = dinfo; } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getDynamicDisplayInfo(const sp& display, @@ -7444,7 +7376,7 @@ binder::Status SurfaceComposerAIDL::getDynamicDisplayInfo(const sp& dis outInfo->gameContentTypeSupported = info.gameContentTypeSupported; outInfo->preferredBootDisplayMode = info.preferredBootDisplayMode; } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getDisplayNativePrimaries(const sp& display, @@ -7468,7 +7400,7 @@ binder::Status SurfaceComposerAIDL::getDisplayNativePrimaries(const sp& outPrimaries->white.Y = primaries.white.Y; outPrimaries->white.Z = primaries.white.Z; } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::setActiveColorMode(const sp& display, int colorMode) { @@ -7476,7 +7408,7 @@ binder::Status SurfaceComposerAIDL::setActiveColorMode(const sp& displa if (status == OK) { status = mFlinger->setActiveColorMode(display, static_cast(colorMode)); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::setBootDisplayMode(const sp& display, @@ -7486,7 +7418,7 @@ binder::Status SurfaceComposerAIDL::setBootDisplayMode(const sp& displa status = mFlinger->setBootDisplayMode(display, static_cast(displayModeId)); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::clearBootDisplayMode(const sp& display) { @@ -7494,7 +7426,7 @@ binder::Status SurfaceComposerAIDL::clearBootDisplayMode(const sp& disp if (status == OK) { status = mFlinger->clearBootDisplayMode(display); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getBootDisplayModeSupport(bool* outMode) { @@ -7502,13 +7434,13 @@ binder::Status SurfaceComposerAIDL::getBootDisplayModeSupport(bool* outMode) { if (status == OK) { status = mFlinger->getBootDisplayModeSupport(outMode); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::setAutoLowLatencyMode(const sp& display, bool on) { status_t status = checkAccessPermission(); if (status != OK) { - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } mFlinger->setAutoLowLatencyMode(display, on); return binder::Status::ok(); @@ -7517,7 +7449,7 @@ binder::Status SurfaceComposerAIDL::setAutoLowLatencyMode(const sp& dis binder::Status SurfaceComposerAIDL::setGameContentType(const sp& display, bool on) { status_t status = checkAccessPermission(); if (status != OK) { - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } mFlinger->setGameContentType(display, on); return binder::Status::ok(); @@ -7526,7 +7458,7 @@ binder::Status SurfaceComposerAIDL::setGameContentType(const sp& displa binder::Status SurfaceComposerAIDL::captureDisplay( const DisplayCaptureArgs& args, const sp& captureListener) { status_t status = mFlinger->captureDisplay(args, captureListener); - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::captureDisplayById( @@ -7540,13 +7472,13 @@ binder::Status SurfaceComposerAIDL::captureDisplayById( } else { status = PERMISSION_DENIED; } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::captureLayers( const LayerCaptureArgs& args, const sp& captureListener) { status_t status = mFlinger->captureLayers(args, captureListener); - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::clearAnimationFrameStats() { @@ -7554,13 +7486,13 @@ binder::Status SurfaceComposerAIDL::clearAnimationFrameStats() { if (status == OK) { status = mFlinger->clearAnimationFrameStats(); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getAnimationFrameStats(gui::FrameStats* outStats) { status_t status = checkAccessPermission(); if (status != OK) { - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } FrameStats stats; @@ -7580,7 +7512,7 @@ binder::Status SurfaceComposerAIDL::getAnimationFrameStats(gui::FrameStats* outS outStats->frameReadyTimesNano.push_back(t); } } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::overrideHdrTypes(const sp& display, @@ -7589,7 +7521,7 @@ binder::Status SurfaceComposerAIDL::overrideHdrTypes(const sp& display, // permission dynamically. Don't use the permission cache for this check. status_t status = checkAccessPermission(false); if (status != OK) { - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } std::vector hdrTypesVector; @@ -7597,7 +7529,7 @@ binder::Status SurfaceComposerAIDL::overrideHdrTypes(const sp& display, hdrTypesVector.push_back(static_cast(i)); } status = mFlinger->overrideHdrTypes(display, hdrTypesVector); - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::onPullAtom(int32_t atomId, gui::PullAtomData* outPullData) { @@ -7608,36 +7540,36 @@ binder::Status SurfaceComposerAIDL::onPullAtom(int32_t atomId, gui::PullAtomData } else { status = mFlinger->onPullAtom(atomId, &outPullData->data, &outPullData->success); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::enableVSyncInjections(bool enable) { if (!mFlinger->hasMockHwc()) { - return binder::Status::fromStatusT(PERMISSION_DENIED); + return binderStatusFromStatusT(PERMISSION_DENIED); } status_t status = checkAccessPermission(); if (status == OK) { status = mFlinger->enableVSyncInjections(enable); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::injectVSync(int64_t when) { if (!mFlinger->hasMockHwc()) { - return binder::Status::fromStatusT(PERMISSION_DENIED); + return binderStatusFromStatusT(PERMISSION_DENIED); } status_t status = checkAccessPermission(); if (status == OK) { status = mFlinger->injectVSync(when); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getLayerDebugInfo(std::vector* outLayers) { if (!outLayers) { - return binder::Status::fromStatusT(UNEXPECTED_NULL); + return binderStatusFromStatusT(UNEXPECTED_NULL); } IPCThreadState* ipc = IPCThreadState::self(); @@ -7645,15 +7577,15 @@ binder::Status SurfaceComposerAIDL::getLayerDebugInfo(std::vectorgetCallingUid(); if ((uid != AID_SHELL) && !PermissionCache::checkPermission(sDump, pid, uid)) { ALOGE("Layer debug info permission denied for pid=%d, uid=%d", pid, uid); - return binder::Status::fromStatusT(PERMISSION_DENIED); + return binderStatusFromStatusT(PERMISSION_DENIED); } status_t status = mFlinger->getLayerDebugInfo(outLayers); - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getColorManagement(bool* outGetColorManagement) { status_t status = mFlinger->getColorManagement(outGetColorManagement); - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getCompositionPreference(gui::CompositionPreference* outPref) { @@ -7670,14 +7602,14 @@ binder::Status SurfaceComposerAIDL::getCompositionPreference(gui::CompositionPre outPref->wideColorGamutDataspace = static_cast(wideColorGamutDataspace); outPref->wideColorGamutPixelFormat = static_cast(wideColorGamutPixelFormat); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getDisplayedContentSamplingAttributes( const sp& display, gui::ContentSamplingAttributes* outAttrs) { status_t status = checkAccessPermission(); if (status != OK) { - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } ui::PixelFormat format; @@ -7690,7 +7622,7 @@ binder::Status SurfaceComposerAIDL::getDisplayedContentSamplingAttributes( outAttrs->dataspace = static_cast(dataspace); outAttrs->componentMask = static_cast(componentMask); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::setDisplayContentSamplingEnabled(const sp& display, @@ -7703,18 +7635,56 @@ binder::Status SurfaceComposerAIDL::setDisplayContentSamplingEnabled(const sp(componentMask), static_cast(maxFrames)); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getDisplayedContentSample(const sp& display, + int64_t maxFrames, int64_t timestamp, + gui::DisplayedFrameStats* outStats) { + if (!outStats) { + return binderStatusFromStatusT(BAD_VALUE); + } + + status_t status = checkAccessPermission(); + if (status != OK) { + return binderStatusFromStatusT(status); + } + + DisplayedFrameStats stats; + status = mFlinger->getDisplayedContentSample(display, static_cast(maxFrames), + static_cast(timestamp), &stats); + if (status == NO_ERROR) { + // convert from ui::DisplayedFrameStats to gui::DisplayedFrameStats + outStats->numFrames = static_cast(stats.numFrames); + outStats->component_0_sample.reserve(stats.component_0_sample.size()); + for (const auto& s : stats.component_0_sample) { + outStats->component_0_sample.push_back(static_cast(s)); + } + outStats->component_1_sample.reserve(stats.component_1_sample.size()); + for (const auto& s : stats.component_1_sample) { + outStats->component_1_sample.push_back(static_cast(s)); + } + outStats->component_2_sample.reserve(stats.component_2_sample.size()); + for (const auto& s : stats.component_2_sample) { + outStats->component_2_sample.push_back(static_cast(s)); + } + outStats->component_3_sample.reserve(stats.component_3_sample.size()); + for (const auto& s : stats.component_3_sample) { + outStats->component_3_sample.push_back(static_cast(s)); + } + } + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getProtectedContentSupport(bool* outSupported) { status_t status = mFlinger->getProtectedContentSupport(outSupported); - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::isWideColorDisplay(const sp& token, bool* outIsWideColorDisplay) { status_t status = mFlinger->isWideColorDisplay(token, outIsWideColorDisplay); - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::addRegionSamplingListener( @@ -7722,7 +7692,7 @@ binder::Status SurfaceComposerAIDL::addRegionSamplingListener( const sp& listener) { status_t status = checkReadFrameBufferPermission(); if (status != OK) { - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } android::Rect rect; rect.left = samplingArea.left; @@ -7730,7 +7700,7 @@ binder::Status SurfaceComposerAIDL::addRegionSamplingListener( rect.right = samplingArea.right; rect.bottom = samplingArea.bottom; status = mFlinger->addRegionSamplingListener(rect, stopLayerHandle, listener); - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::removeRegionSamplingListener( @@ -7739,7 +7709,7 @@ binder::Status SurfaceComposerAIDL::removeRegionSamplingListener( if (status == OK) { status = mFlinger->removeRegionSamplingListener(listener); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::addFpsListener(int32_t taskId, @@ -7748,7 +7718,7 @@ binder::Status SurfaceComposerAIDL::addFpsListener(int32_t taskId, if (status == OK) { status = mFlinger->addFpsListener(taskId, listener); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::removeFpsListener(const sp& listener) { @@ -7756,7 +7726,7 @@ binder::Status SurfaceComposerAIDL::removeFpsListener(const spremoveFpsListener(listener); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::addTunnelModeEnabledListener( @@ -7765,7 +7735,7 @@ binder::Status SurfaceComposerAIDL::addTunnelModeEnabledListener( if (status == OK) { status = mFlinger->addTunnelModeEnabledListener(listener); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::removeTunnelModeEnabledListener( @@ -7774,7 +7744,7 @@ binder::Status SurfaceComposerAIDL::removeTunnelModeEnabledListener( if (status == OK) { status = mFlinger->removeTunnelModeEnabledListener(listener); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::setDesiredDisplayModeSpecs( @@ -7790,18 +7760,18 @@ binder::Status SurfaceComposerAIDL::setDesiredDisplayModeSpecs( appRequestRefreshRateMin, appRequestRefreshRateMax); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getDesiredDisplayModeSpecs(const sp& displayToken, gui::DisplayModeSpecs* outSpecs) { if (!outSpecs) { - return binder::Status::fromStatusT(BAD_VALUE); + return binderStatusFromStatusT(BAD_VALUE); } status_t status = checkAccessPermission(); if (status != OK) { - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } ui::DisplayModeId displayModeId; @@ -7823,13 +7793,13 @@ binder::Status SurfaceComposerAIDL::getDesiredDisplayModeSpecs(const sp outSpecs->appRequestRefreshRateMax = appRequestRefreshRateMax; } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getDisplayBrightnessSupport(const sp& displayToken, bool* outSupport) { status_t status = mFlinger->getDisplayBrightnessSupport(displayToken, outSupport); - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::setDisplayBrightness(const sp& displayToken, @@ -7838,7 +7808,7 @@ binder::Status SurfaceComposerAIDL::setDisplayBrightness(const sp& disp if (status == OK) { status = mFlinger->setDisplayBrightness(displayToken, brightness); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::addHdrLayerInfoListener( @@ -7847,7 +7817,7 @@ binder::Status SurfaceComposerAIDL::addHdrLayerInfoListener( if (status == OK) { status = mFlinger->addHdrLayerInfoListener(displayToken, listener); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::removeHdrLayerInfoListener( @@ -7856,7 +7826,7 @@ binder::Status SurfaceComposerAIDL::removeHdrLayerInfoListener( if (status == OK) { status = mFlinger->removeHdrLayerInfoListener(displayToken, listener); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::notifyPowerBoost(int boostId) { @@ -7864,7 +7834,56 @@ binder::Status SurfaceComposerAIDL::notifyPowerBoost(int boostId) { if (status == OK) { status = mFlinger->notifyPowerBoost(boostId); } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::setGlobalShadowSettings(const gui::Color& ambientColor, + const gui::Color& spotColor, + float lightPosY, float lightPosZ, + float lightRadius) { + status_t status = checkAccessPermission(); + if (status != OK) { + return binderStatusFromStatusT(status); + } + + half4 ambientColorHalf = {ambientColor.r, ambientColor.g, ambientColor.b, ambientColor.a}; + half4 spotColorHalf = {spotColor.r, spotColor.g, spotColor.b, spotColor.a}; + status = mFlinger->setGlobalShadowSettings(ambientColorHalf, spotColorHalf, lightPosY, + lightPosZ, lightRadius); + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getDisplayDecorationSupport( + const sp& displayToken, std::optional* outSupport) { + std::optional support; + status_t status = mFlinger->getDisplayDecorationSupport(displayToken, &support); + if (status != NO_ERROR) { + ALOGE("getDisplayDecorationSupport failed with error %d", status); + return binderStatusFromStatusT(status); + } + + if (!support || !support.has_value()) { + outSupport->reset(); + } else { + outSupport->emplace(); + outSupport->value().format = static_cast(support->format); + outSupport->value().alphaInterpretation = + static_cast(support->alphaInterpretation); + } + + return binder::Status::ok(); +} + +binder::Status SurfaceComposerAIDL::setOverrideFrameRate(int32_t uid, float frameRate) { + status_t status; + const int c_uid = IPCThreadState::self()->getCallingUid(); + if (c_uid == AID_ROOT || c_uid == AID_SYSTEM) { + status = mFlinger->setOverrideFrameRate(uid, frameRate); + } else { + ALOGE("setOverrideFrameRate() permission denied for uid: %d", c_uid); + status = PERMISSION_DENIED; + } + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::addTransactionTraceListener( @@ -7877,7 +7896,7 @@ binder::Status SurfaceComposerAIDL::addTransactionTraceListener( } else { status = PERMISSION_DENIED; } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::getGpuContextPriority(int32_t* outPriority) { @@ -7887,7 +7906,7 @@ binder::Status SurfaceComposerAIDL::getGpuContextPriority(int32_t* outPriority) binder::Status SurfaceComposerAIDL::getMaxAcquiredBufferCount(int32_t* buffers) { status_t status = mFlinger->getMaxAcquiredBufferCount(buffers); - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::addWindowInfosListener( @@ -7899,7 +7918,7 @@ binder::Status SurfaceComposerAIDL::addWindowInfosListener( } else { status = PERMISSION_DENIED; } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } binder::Status SurfaceComposerAIDL::removeWindowInfosListener( @@ -7911,7 +7930,7 @@ binder::Status SurfaceComposerAIDL::removeWindowInfosListener( } else { status = PERMISSION_DENIED; } - return binder::Status::fromStatusT(status); + return binderStatusFromStatusT(status); } status_t SurfaceComposerAIDL::checkAccessPermission(bool usePermissionCache) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index fc33ade0a2..0d6ac4eb35 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -311,9 +311,6 @@ public: renderengine::RenderEngine& getRenderEngine() const; - bool authenticateSurfaceTextureLocked( - const sp& bufferProducer) const; - void onLayerFirstRef(Layer*); void onLayerDestroyed(Layer*); void onLayerUpdate(); @@ -559,8 +556,6 @@ private: const std::vector& listenerCallbacks, uint64_t transactionId) override; void bootFinished() override; - bool authenticateSurfaceTexture( - const sp& bufferProducer) const override; virtual status_t getSupportedFrameTimestamps(std::vector* outSupported) const; sp createDisplayEventConnection( ISurfaceComposer::VsyncSource vsyncSource = eVsyncSourceApp, @@ -635,18 +630,18 @@ private: const sp& listener); status_t notifyPowerBoost(int32_t boostId); status_t setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor, - float lightPosY, float lightPosZ, float lightRadius) override; + float lightPosY, float lightPosZ, float lightRadius); status_t getDisplayDecorationSupport( const sp& displayToken, std::optional* - outSupport) const override; + outSupport) const; status_t setFrameRate(const sp& surface, float frameRate, - int8_t compatibility, int8_t changeFrameRateStrategy) override; + int8_t compatibility, int8_t changeFrameRateStrategy); status_t setFrameTimelineInfo(const sp& surface, - const FrameTimelineInfo& frameTimelineInfo) override; + const gui::FrameTimelineInfo& frameTimelineInfo); - status_t setOverrideFrameRate(uid_t uid, float frameRate) override; + status_t setOverrideFrameRate(uid_t uid, float frameRate); status_t addTransactionTraceListener(const sp& listener); @@ -1495,6 +1490,9 @@ public: binder::Status setDisplayContentSamplingEnabled(const sp& display, bool enable, int8_t componentMask, int64_t maxFrames) override; + binder::Status getDisplayedContentSample(const sp& display, int64_t maxFrames, + int64_t timestamp, + gui::DisplayedFrameStats* outStats) override; binder::Status getProtectedContentSupport(bool* outSupporte) override; binder::Status isWideColorDisplay(const sp& token, bool* outIsWideColorDisplay) override; @@ -1526,6 +1524,13 @@ public: const sp& displayToken, const sp& listener) override; binder::Status notifyPowerBoost(int boostId) override; + binder::Status setGlobalShadowSettings(const gui::Color& ambientColor, + const gui::Color& spotColor, float lightPosY, + float lightPosZ, float lightRadius) override; + binder::Status getDisplayDecorationSupport( + const sp& displayToken, + std::optional* outSupport) override; + binder::Status setOverrideFrameRate(int32_t uid, float frameRate) override; binder::Status addTransactionTraceListener( const sp& listener) override; binder::Status getGpuContextPriority(int32_t* outPriority) override; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index e90753ab3f..2a4d6ed86f 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -588,7 +588,6 @@ public: sp display = fuzzBoot(&mFdp); sp bufferProducer = sp::make(); - mFlinger->authenticateSurfaceTexture(bufferProducer.get()); mFlinger->createDisplayEventConnection(); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp index 46d52dd221..685b44af6b 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp @@ -58,8 +58,10 @@ Rect LayerFuzzer::getFuzzedRect() { } FrameTimelineInfo LayerFuzzer::getFuzzedFrameTimelineInfo() { - return FrameTimelineInfo{.vsyncId = mFdp.ConsumeIntegral(), - .inputEventId = mFdp.ConsumeIntegral()}; + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = mFdp.ConsumeIntegral(); + ftInfo.inputEventId = mFdp.ConsumeIntegral(); + return ftInfo; } LayerCreationArgs LayerFuzzer::createLayerCreationArgs(TestableSurfaceFlinger* flinger, diff --git a/services/surfaceflinger/tests/BootDisplayMode_test.cpp b/services/surfaceflinger/tests/BootDisplayMode_test.cpp index 4cd6ef8f1d..432e227cae 100644 --- a/services/surfaceflinger/tests/BootDisplayMode_test.cpp +++ b/services/surfaceflinger/tests/BootDisplayMode_test.cpp @@ -18,6 +18,7 @@ #include +#include #include #include #include @@ -25,15 +26,17 @@ namespace android { +using gui::aidl_utils::statusTFromBinderStatus; + TEST(BootDisplayModeTest, setBootDisplayMode) { sp sf(ComposerServiceAIDL::getComposerService()); auto displayToken = SurfaceComposerClient::getInternalDisplayToken(); bool bootModeSupport = false; binder::Status status = sf->getBootDisplayModeSupport(&bootModeSupport); - ASSERT_NO_FATAL_FAILURE(status.transactionError()); + ASSERT_NO_FATAL_FAILURE(statusTFromBinderStatus(status)); if (bootModeSupport) { status = sf->setBootDisplayMode(displayToken, 0); - ASSERT_EQ(NO_ERROR, status.transactionError()); + ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); } } @@ -42,10 +45,10 @@ TEST(BootDisplayModeTest, clearBootDisplayMode) { auto displayToken = SurfaceComposerClient::getInternalDisplayToken(); bool bootModeSupport = false; binder::Status status = sf->getBootDisplayModeSupport(&bootModeSupport); - ASSERT_NO_FATAL_FAILURE(status.transactionError()); + ASSERT_NO_FATAL_FAILURE(statusTFromBinderStatus(status)); if (bootModeSupport) { status = sf->clearBootDisplayMode(displayToken); - ASSERT_EQ(NO_ERROR, status.transactionError()); + ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); } } diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp index 6549a224e6..8a443b823d 100644 --- a/services/surfaceflinger/tests/Credentials_test.cpp +++ b/services/surfaceflinger/tests/Credentials_test.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -35,6 +36,7 @@ namespace android { using Transaction = SurfaceComposerClient::Transaction; using gui::LayerDebugInfo; +using gui::aidl_utils::statusTFromBinderStatus; using ui::ColorMode; namespace { @@ -316,18 +318,18 @@ TEST_F(CredentialsTest, GetLayerDebugInfo) { // Check with root. seteuid(AID_ROOT); binder::Status status = sf->getLayerDebugInfo(&outLayers); - ASSERT_EQ(NO_ERROR, status.transactionError()); + ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); // Check as a shell. seteuid(AID_SHELL); status = sf->getLayerDebugInfo(&outLayers); - ASSERT_EQ(NO_ERROR, status.transactionError()); + ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); // Check as anyone else. seteuid(AID_ROOT); seteuid(AID_BIN); status = sf->getLayerDebugInfo(&outLayers); - ASSERT_EQ(PERMISSION_DENIED, status.transactionError()); + ASSERT_EQ(PERMISSION_DENIED, statusTFromBinderStatus(status)); } TEST_F(CredentialsTest, IsWideColorDisplayBasicCorrectness) { diff --git a/services/surfaceflinger/tests/LayerCallback_test.cpp b/services/surfaceflinger/tests/LayerCallback_test.cpp index 8a2305b365..9c0c788a6d 100644 --- a/services/surfaceflinger/tests/LayerCallback_test.cpp +++ b/services/surfaceflinger/tests/LayerCallback_test.cpp @@ -1040,7 +1040,10 @@ TEST_F(LayerCallbackTest, ExpectedPresentTime) { } const Vsync vsync = waitForNextVsync(); - transaction.setFrameTimelineInfo({vsync.vsyncId, 0}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = vsync.vsyncId; + ftInfo.inputEventId = 0; + transaction.setFrameTimelineInfo(ftInfo); transaction.apply(); ExpectedResult expected; diff --git a/services/surfaceflinger/tests/LayerTransactionTest.h b/services/surfaceflinger/tests/LayerTransactionTest.h index 43386b2ae7..b46db65f84 100644 --- a/services/surfaceflinger/tests/LayerTransactionTest.h +++ b/services/surfaceflinger/tests/LayerTransactionTest.h @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -47,7 +48,7 @@ protected: sp sf(ComposerServiceAIDL::getComposerService()); binder::Status status = sf->getColorManagement(&mColorManagementUsed); - ASSERT_NO_FATAL_FAILURE(status.transactionError()); + ASSERT_NO_FATAL_FAILURE(gui::aidl_utils::statusTFromBinderStatus(status)); mCaptureArgs.displayToken = mDisplay; } diff --git a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp b/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp index 12e5d46a79..67df8b8d61 100644 --- a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp +++ b/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp @@ -29,6 +29,7 @@ #include "MockComposerHal.h" #include +#include #include #include #include @@ -1242,7 +1243,7 @@ protected: sp sf(ComposerServiceAIDL::getComposerService()); std::vector layers; binder::Status status = sf->getLayerDebugInfo(&layers); - status_t result = status.transactionError(); + status_t result = gui::aidl_utils::statusTFromBinderStatus(status); if (result != NO_ERROR) { ALOGE("Failed to get layers %s %d", strerror(-result), result); } else { diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp index f1efa9286d..bc379f2fb5 100644 --- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp +++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp @@ -217,9 +217,12 @@ TEST_F(FrameTimelineTest, createSurfaceFrameForToken_noToken) { TEST_F(FrameTimelineTest, createSurfaceFrameForToken_expiredToken) { int64_t token1 = mTokenManager->generateTokenForPredictions({0, 0, 0}); flushTokens(); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = token1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame = - mFrameTimeline->createSurfaceFrameForToken({token1, sInputEventId}, sPidOne, sUidOne, - sLayerIdOne, sLayerNameOne, sLayerNameOne, + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, /*isBuffer*/ true, sGameMode); EXPECT_EQ(surfaceFrame->getPredictionState(), PredictionState::Expired); @@ -227,9 +230,12 @@ TEST_F(FrameTimelineTest, createSurfaceFrameForToken_expiredToken) { TEST_F(FrameTimelineTest, createSurfaceFrameForToken_validToken) { int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = token1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame = - mFrameTimeline->createSurfaceFrameForToken({token1, sInputEventId}, sPidOne, sUidOne, - sLayerIdOne, sLayerNameOne, sLayerNameOne, + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, /*isBuffer*/ true, sGameMode); EXPECT_EQ(surfaceFrame->getPredictionState(), PredictionState::Valid); @@ -239,9 +245,12 @@ TEST_F(FrameTimelineTest, createSurfaceFrameForToken_validToken) { TEST_F(FrameTimelineTest, createSurfaceFrameForToken_validInputEventId) { int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30}); constexpr int32_t inputEventId = 1; + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = token1; + ftInfo.inputEventId = inputEventId; auto surfaceFrame = - mFrameTimeline->createSurfaceFrameForToken({token1, inputEventId}, sPidOne, sUidOne, - sLayerIdOne, sLayerNameOne, sLayerNameOne, + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, /*isBuffer*/ true, sGameMode); EXPECT_EQ(inputEventId, surfaceFrame->getInputEventId()); @@ -250,9 +259,12 @@ TEST_F(FrameTimelineTest, createSurfaceFrameForToken_validInputEventId) { TEST_F(FrameTimelineTest, presentFenceSignaled_droppedFramesNotUpdated) { auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = token1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({token1, sInputEventId}, sPidOne, sUidOne, - sLayerIdOne, sLayerNameOne, sLayerNameOne, + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, /*isBuffer*/ true, sGameMode); // Set up the display frame @@ -278,14 +290,17 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_presentedFramesUpdated) { auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 30}); int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 30}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); auto surfaceFrame2 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdTwo, sLayerNameTwo, - sLayerNameTwo, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdTwo, + sLayerNameTwo, sLayerNameTwo, + /*isBuffer*/ true, sGameMode); mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11)); surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented); mFrameTimeline->addSurfaceFrame(surfaceFrame1); @@ -324,9 +339,11 @@ TEST_F(FrameTimelineTest, displayFramesSlidingWindowMovesAfterLimit) { {10 + frameTimeFactor, 20 + frameTimeFactor, 30 + frameTimeFactor}); int64_t sfToken = mTokenManager->generateTokenForPredictions( {22 + frameTimeFactor, 26 + frameTimeFactor, 30 + frameTimeFactor}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, sInputEventId}, - sPidOne, sUidOne, sLayerIdOne, + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, sLayerNameOne, sLayerNameOne, /*isBuffer*/ true, sGameMode); mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor, Fps::fromPeriodNsecs(11)); @@ -347,10 +364,13 @@ TEST_F(FrameTimelineTest, displayFramesSlidingWindowMovesAfterLimit) { {10 + frameTimeFactor, 20 + frameTimeFactor, 30 + frameTimeFactor}); int64_t sfToken = mTokenManager->generateTokenForPredictions( {22 + frameTimeFactor, 26 + frameTimeFactor, 30 + frameTimeFactor}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor, Fps::fromPeriodNsecs(11)); surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented); mFrameTimeline->addSurfaceFrame(surfaceFrame); @@ -442,11 +462,14 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_invalidSignalTime) { auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60}); int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate); surfaceFrame1->setAcquireFenceTime(20); surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented); @@ -470,11 +493,14 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_doesNotReportForInvalidTokens) { auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); int64_t surfaceFrameToken1 = -1; int64_t sfToken1 = -1; + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate); surfaceFrame1->setAcquireFenceTime(20); surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented); @@ -495,11 +521,14 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfCpu) { auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60}); int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate); surfaceFrame1->setAcquireFenceTime(20); surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented); @@ -521,11 +550,14 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfGpu) { auto gpuFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60}); int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate); surfaceFrame1->setAcquireFenceTime(20); surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented); @@ -546,11 +578,14 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsDisplayMiss) { auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60}); int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate); surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented); surfaceFrame1->setAcquireFenceTime(20); @@ -570,11 +605,14 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMiss) { auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60}); int64_t sfToken1 = mTokenManager->generateTokenForPredictions({82, 90, 90}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame1->setAcquireFenceTime(45); mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate); @@ -596,11 +634,14 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfScheduling) { auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({40, 60, 92}); int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame1->setAcquireFenceTime(50); mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate); @@ -622,11 +663,14 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfPredictionError) { auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({30, 40, 60}); int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame1->setAcquireFenceTime(40); mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate); @@ -648,11 +692,14 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppBufferStuffing) { auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({30, 40, 58}); int64_t sfToken1 = mTokenManager->generateTokenForPredictions({82, 90, 90}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame1->setAcquireFenceTime(40); mFrameTimeline->setSfWakeUp(sfToken1, 82, refreshRate); @@ -676,11 +723,14 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMissWithRenderRate) { auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60}); int64_t sfToken1 = mTokenManager->generateTokenForPredictions({82, 90, 90}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame1->setAcquireFenceTime(45); mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate); @@ -706,11 +756,14 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_displayFramePredictionExpiredPres auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60}); int64_t sfToken1 = mTokenManager->generateTokenForPredictions({82, 90, 90}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame1->setAcquireFenceTime(45); // Trigger a prediction expiry flushTokens(); @@ -744,9 +797,12 @@ TEST_F(FrameTimelineTest, tracing_noPacketsSentWithoutTraceStart) { auto tracingSession = getTracingSessionForTest(); auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = token1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({token1, sInputEventId}, sPidOne, sUidOne, - sLayerIdOne, sLayerNameOne, sLayerNameOne, + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, /*isBuffer*/ true, sGameMode); // Set up the display frame @@ -771,9 +827,12 @@ TEST_F(FrameTimelineTest, tracing_sanityTest) { tracingSession->StartBlocking(); int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30}); int64_t token2 = mTokenManager->generateTokenForPredictions({40, 50, 60}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = token1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({token1, sInputEventId}, sPidOne, sUidOne, - sLayerIdOne, sLayerNameOne, sLayerNameOne, + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, /*isBuffer*/ true, sGameMode); // Set up the display frame @@ -1133,14 +1192,18 @@ TEST_F(FrameTimelineTest, traceSurfaceFrame_emitsValidTracePacket) { int64_t surfaceFrameToken = mTokenManager->generateTokenForPredictions({10, 25, 40}); int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({30, 35, 40}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken; + ftInfo.inputEventId = sInputEventId; + auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); auto surfaceFrame2 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame1->setActualQueueTime(10); surfaceFrame1->setDropTime(15); @@ -1293,10 +1356,13 @@ TEST_F(FrameTimelineTest, traceSurfaceFrame_predictionExpiredIsAppMissedDeadline // Flush the token so that it would expire flushTokens(); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken; + ftInfo.inputEventId = 0; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, /*inputEventId*/ 0}, - sPidOne, sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame1->setActualQueueTime(appEndTime); surfaceFrame1->setAcquireFenceTime(appEndTime); @@ -1369,10 +1435,13 @@ TEST_F(FrameTimelineTest, traceSurfaceFrame_predictionExpiredDroppedFramesTraced // Flush the token so that it would expire flushTokens(); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken; + ftInfo.inputEventId = 0; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, /*inputEventId*/ 0}, - sPidOne, sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); constexpr nsecs_t sfStartTime = std::chrono::nanoseconds(22ms).count(); constexpr nsecs_t sfEndTime = std::chrono::nanoseconds(30ms).count(); @@ -1438,10 +1507,13 @@ TEST_F(FrameTimelineTest, jankClassification_presentOnTimeDoesNotClassify) { auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); int64_t surfaceFrameToken = mTokenManager->generateTokenForPredictions({10, 20, 30}); int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 30}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11)); surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented); mFrameTimeline->addSurfaceFrame(surfaceFrame); @@ -1654,10 +1726,13 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishEarlyPresen int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 70}); int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 16, 40}); int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({25, 36, 70}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame1->setAcquireFenceTime(16); mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11)); surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented); @@ -1674,10 +1749,13 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishEarlyPresen // Trigger a flush by finalizing the next DisplayFrame auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); + FrameTimelineInfo ftInfo2; + ftInfo2.vsyncId = surfaceFrameToken2; + ftInfo2.inputEventId = sInputEventId; auto surfaceFrame2 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken2, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo2, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame2->setAcquireFenceTime(36); mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(11)); surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented); @@ -1734,10 +1812,13 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishLatePresent int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 70}); int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 16, 40}); int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({25, 36, 70}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame1->setAcquireFenceTime(16); mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11)); surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented); @@ -1754,10 +1835,13 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishLatePresent // Trigger a flush by finalizing the next DisplayFrame auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); + FrameTimelineInfo ftInfo2; + ftInfo2.vsyncId = surfaceFrameToken2; + ftInfo2.inputEventId = sInputEventId; auto surfaceFrame2 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken2, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo2, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame2->setAcquireFenceTime(36); mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(11)); surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented); @@ -1813,10 +1897,13 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameLateFinishEarlyPresent) auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); int64_t sfToken1 = mTokenManager->generateTokenForPredictions({42, 50, 50}); int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 26, 60}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame1->setAcquireFenceTime(40); mFrameTimeline->setSfWakeUp(sfToken1, 42, Fps::fromPeriodNsecs(11)); surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented); @@ -1857,10 +1944,13 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameLateFinishLatePresent) int64_t sfToken2 = mTokenManager->generateTokenForPredictions({42, 50, 50}); int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 16, 30}); int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({25, 36, 50}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame1->setAcquireFenceTime(26); mFrameTimeline->setSfWakeUp(sfToken1, 32, Fps::fromPeriodNsecs(11)); surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented); @@ -1877,10 +1967,13 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameLateFinishLatePresent) // Trigger a flush by finalizing the next DisplayFrame auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); + FrameTimelineInfo ftInfo2; + ftInfo2.vsyncId = surfaceFrameToken2; + ftInfo2.inputEventId = sInputEventId; auto surfaceFrame2 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken2, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo2, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame2->setAcquireFenceTime(40); mFrameTimeline->setSfWakeUp(sfToken2, 43, Fps::fromPeriodNsecs(11)); surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented); @@ -1932,10 +2025,13 @@ TEST_F(FrameTimelineTest, jankClassification_multiJankBufferStuffingAndAppDeadli int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60}); int64_t sfToken2 = mTokenManager->generateTokenForPredictions({112, 120, 120}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame1->setAcquireFenceTime(50); mFrameTimeline->setSfWakeUp(sfToken1, 52, Fps::fromPeriodNsecs(30)); surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented); @@ -1952,10 +2048,13 @@ TEST_F(FrameTimelineTest, jankClassification_multiJankBufferStuffingAndAppDeadli // Trigger a flush by finalizing the next DisplayFrame auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); + FrameTimelineInfo ftInfo2; + ftInfo2.vsyncId = surfaceFrameToken2; + ftInfo2.inputEventId = sInputEventId; auto surfaceFrame2 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken2, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo2, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame2->setAcquireFenceTime(84); mFrameTimeline->setSfWakeUp(sfToken2, 112, Fps::fromPeriodNsecs(30)); surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented, 54); @@ -2010,10 +2109,13 @@ TEST_F(FrameTimelineTest, jankClassification_appDeadlineAdjustedForBufferStuffin int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60}); int64_t sfToken2 = mTokenManager->generateTokenForPredictions({82, 90, 90}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; auto surfaceFrame1 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame1->setAcquireFenceTime(50); mFrameTimeline->setSfWakeUp(sfToken1, 52, Fps::fromPeriodNsecs(30)); surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented); @@ -2030,10 +2132,13 @@ TEST_F(FrameTimelineTest, jankClassification_appDeadlineAdjustedForBufferStuffin // Trigger a flush by finalizing the next DisplayFrame auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); + FrameTimelineInfo ftInfo2; + ftInfo2.vsyncId = surfaceFrameToken2; + ftInfo2.inputEventId = sInputEventId; auto surfaceFrame2 = - mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken2, sInputEventId}, sPidOne, - sUidOne, sLayerIdOne, sLayerNameOne, - sLayerNameOne, /*isBuffer*/ true, sGameMode); + mFrameTimeline->createSurfaceFrameForToken(ftInfo2, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); surfaceFrame2->setAcquireFenceTime(80); mFrameTimeline->setSfWakeUp(sfToken2, 82, Fps::fromPeriodNsecs(30)); // Setting previous latch time to 54, adjusted deadline will be 54 + vsyncTime(30) = 84 diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp index 5bb4c92a8e..6d583036fa 100644 --- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp @@ -100,8 +100,10 @@ public: void PresentedSurfaceFrameForBufferlessTransaction() { sp layer = createBufferStateLayer(); - layer->setFrameTimelineVsyncForBufferlessTransaction({/*vsyncId*/ 1, /*inputEventId*/ 0}, - 10); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = 1; + ftInfo.inputEventId = 0; + layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo, 10); EXPECT_EQ(1u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_TRUE(layer->mDrawingState.bufferSurfaceFrameTX == nullptr); const auto surfaceFrame = layer->mDrawingState.bufferlessSurfaceFramesTX.at(/*token*/ 1); @@ -125,8 +127,10 @@ public: 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, - {/*vsyncId*/ 1, /*inputEventId*/ 0}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = 1; + ftInfo.inputEventId = 0; + layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo); acquireFence->signalForTest(12); commitTransaction(layer.get()); @@ -159,8 +163,10 @@ public: 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, - {/*vsyncId*/ 1, /*inputEventId*/ 0}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = 1; + ftInfo.inputEventId = 0; + layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX; @@ -177,8 +183,7 @@ public: 2ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, - {/*vsyncId*/ 1, /*inputEventId*/ 0}); + layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo); nsecs_t end = systemTime(); acquireFence2->signalForTest(12); @@ -204,9 +209,11 @@ public: void BufferlessSurfaceFramePromotedToBufferSurfaceFrame() { sp layer = createBufferStateLayer(); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = 1; + ftInfo.inputEventId = 0; - layer->setFrameTimelineVsyncForBufferlessTransaction({/*vsyncId*/ 1, /*inputEventId*/ 0}, - 10); + layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo, 10); EXPECT_EQ(1u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_EQ(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); @@ -223,8 +230,7 @@ public: 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, - {/*vsyncId*/ 1, /*inputEventId*/ 0}); + layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo); acquireFence->signalForTest(12); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); @@ -257,28 +263,33 @@ public: 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, - {/*vsyncId*/ 1, /*inputEventId*/ 0}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = 1; + ftInfo.inputEventId = 0; + layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); - layer->setFrameTimelineVsyncForBufferlessTransaction({/*vsyncId*/ 1, /*inputEventId*/ 0}, - 10); + layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo, 10); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); } void MultipleSurfaceFramesPresentedTogether() { sp layer = createBufferStateLayer(); - layer->setFrameTimelineVsyncForBufferlessTransaction({/*vsyncId*/ 1, /*inputEventId*/ 0}, - 10); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = 1; + ftInfo.inputEventId = 0; + layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo, 10); EXPECT_EQ(1u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_EQ(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); const auto bufferlessSurfaceFrame1 = layer->mDrawingState.bufferlessSurfaceFramesTX.at(/*token*/ 1); - layer->setFrameTimelineVsyncForBufferlessTransaction({/*vsyncId*/ 4, /*inputEventId*/ 0}, - 10); + FrameTimelineInfo ftInfo2; + ftInfo2.vsyncId = 4; + ftInfo2.inputEventId = 0; + layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo2, 10); EXPECT_EQ(2u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_EQ(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); const auto bufferlessSurfaceFrame2 = layer->mDrawingState.bufferlessSurfaceFramesTX[4]; @@ -295,8 +306,10 @@ public: 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, - {/*vsyncId*/ 3, /*inputEventId*/ 0}); + FrameTimelineInfo ftInfo3; + ftInfo3.vsyncId = 3; + ftInfo3.inputEventId = 0; + layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo3); EXPECT_EQ(2u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); const auto bufferSurfaceFrameTX = layer->mDrawingState.bufferSurfaceFrameTX; @@ -339,8 +352,10 @@ public: 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, - {/*vsyncId*/ 1, /*inputEventId*/ 0}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = 1; + ftInfo.inputEventId = 0; + layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX; @@ -355,8 +370,7 @@ public: 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, - {/*vsyncId*/ 1, /*inputEventId*/ 0}); + layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo); acquireFence2->signalForTest(12); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); @@ -391,8 +405,10 @@ public: 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, - {/*vsyncId*/ 1, /*inputEventId*/ 0}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = 1; + ftInfo.inputEventId = 0; + layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); const auto droppedSurfaceFrame1 = layer->mDrawingState.bufferSurfaceFrameTX; @@ -409,8 +425,10 @@ public: 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, - {/*vsyncId*/ FrameTimelineInfo::INVALID_VSYNC_ID, /*inputEventId*/ 0}); + FrameTimelineInfo ftInfoInv; + ftInfoInv.vsyncId = FrameTimelineInfo::INVALID_VSYNC_ID; + ftInfoInv.inputEventId = 0; + layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfoInv); auto dropEndTime1 = systemTime(); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); @@ -428,8 +446,10 @@ public: 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture3, bufferData, 10, 20, false, std::nullopt, - {/*vsyncId*/ 2, /*inputEventId*/ 0}); + FrameTimelineInfo ftInfo2; + ftInfo2.vsyncId = 2; + ftInfo2.inputEventId = 0; + layer->setBuffer(externalTexture3, bufferData, 10, 20, false, std::nullopt, ftInfo2); auto dropEndTime2 = systemTime(); acquireFence3->signalForTest(12); @@ -476,11 +496,14 @@ public: 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, - {/*vsyncId*/ 1, /*inputEventId*/ 0}); - layer->setFrameTimelineVsyncForBufferlessTransaction({/*vsyncId*/ 2, - /*inputEventId*/ 0}, - 10); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = 1; + ftInfo.inputEventId = 0; + layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo); + FrameTimelineInfo ftInfo2; + ftInfo2.vsyncId = 2; + ftInfo2.inputEventId = 0; + layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo2, 10); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); EXPECT_EQ(1u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); auto& bufferlessSurfaceFrame = diff --git a/services/surfaceflinger/tests/utils/ScreenshotUtils.h b/services/surfaceflinger/tests/utils/ScreenshotUtils.h index ee7e92cbf6..dfd3b91302 100644 --- a/services/surfaceflinger/tests/utils/ScreenshotUtils.h +++ b/services/surfaceflinger/tests/utils/ScreenshotUtils.h @@ -15,6 +15,7 @@ */ #pragma once +#include #include #include #include @@ -24,6 +25,8 @@ namespace android { +using gui::aidl_utils::statusTFromBinderStatus; + namespace { // A ScreenCapture is a screenshot from SurfaceFlinger that can be used to check @@ -38,9 +41,9 @@ public: captureArgs.dataspace = ui::Dataspace::V0_SRGB; const sp captureListener = new SyncScreenCaptureListener(); binder::Status status = sf->captureDisplay(captureArgs, captureListener); - - if (status.transactionError() != NO_ERROR) { - return status.transactionError(); + status_t err = statusTFromBinderStatus(status); + if (err != NO_ERROR) { + return err; } captureResults = captureListener->waitForResults(); return captureResults.result; @@ -71,8 +74,9 @@ public: captureArgs.dataspace = ui::Dataspace::V0_SRGB; const sp captureListener = new SyncScreenCaptureListener(); binder::Status status = sf->captureLayers(captureArgs, captureListener); - if (status.transactionError() != NO_ERROR) { - return status.transactionError(); + status_t err = statusTFromBinderStatus(status); + if (err != NO_ERROR) { + return err; } captureResults = captureListener->waitForResults(); return captureResults.result; -- GitLab From 3c28887ffc7e7f141fdfa4d54b9f1bd5bfef5edc Mon Sep 17 00:00:00 2001 From: Vova Sharaienko Date: Fri, 8 Apr 2022 05:53:43 +0000 Subject: [PATCH 0046/1310] IStats HAL: Updated AIDL to version 2 Added support for bool type Bug: 183753160 Test: build, flash & run aidl_stats_client Change-Id: Ic1e33abbde56535931a5b33305f11c178df91bcc --- services/stats/Android.bp | 13 +++++++++---- services/stats/StatsAidl.cpp | 4 ++++ ...ice.xml => android.frameworks.stats-service.xml} | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) rename services/stats/{android.frameworks.stats@1.0-service.xml => android.frameworks.stats-service.xml} (93%) diff --git a/services/stats/Android.bp b/services/stats/Android.bp index 7fea6161ff..7d358e1bd2 100644 --- a/services/stats/Android.bp +++ b/services/stats/Android.bp @@ -13,10 +13,13 @@ cc_library_shared { "StatsAidl.cpp", "StatsHal.cpp", ], - cflags: ["-Wall", "-Werror"], + cflags: [ + "-Wall", + "-Werror", + ], shared_libs: [ "android.frameworks.stats@1.0", - "android.frameworks.stats-V1-ndk", + "android.frameworks.stats-V2-ndk", "libbinder_ndk", "libhidlbase", "liblog", @@ -29,10 +32,12 @@ cc_library_shared { ], export_shared_lib_headers: [ "android.frameworks.stats@1.0", - "android.frameworks.stats-V1-ndk", + "android.frameworks.stats-V2-ndk", ], local_include_dirs: [ "include/stats", ], - vintf_fragments: ["android.frameworks.stats@1.0-service.xml"] + vintf_fragments: [ + "android.frameworks.stats-service.xml", + ], } diff --git a/services/stats/StatsAidl.cpp b/services/stats/StatsAidl.cpp index a3b68f1dab..8d6a9bdb5a 100644 --- a/services/stats/StatsAidl.cpp +++ b/services/stats/StatsAidl.cpp @@ -62,6 +62,10 @@ ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) { AStatsEvent_writeString(event, atomValue.get().c_str()); break; + case VendorAtomValue::boolValue: + AStatsEvent_writeBool(event, + atomValue.get()); + break; } } AStatsEvent_build(event); diff --git a/services/stats/android.frameworks.stats@1.0-service.xml b/services/stats/android.frameworks.stats-service.xml similarity index 93% rename from services/stats/android.frameworks.stats@1.0-service.xml rename to services/stats/android.frameworks.stats-service.xml index c564b7be53..7e2635e406 100644 --- a/services/stats/android.frameworks.stats@1.0-service.xml +++ b/services/stats/android.frameworks.stats-service.xml @@ -11,7 +11,7 @@ android.frameworks.stats - 1 + 2 IStats/default -- GitLab From d3d8f8e3501a07d55090c287ac45943d687b7006 Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Tue, 8 Mar 2022 14:48:46 -0800 Subject: [PATCH 0047/1310] Migrate ISurfaceComposerClient to AIDL Migrate and clean up ISurfaceComposerClient to aidl, removed the deprecated createWithParent method, removed non used parameters. Bug: 172002646 Test: atest libgui_test Change-Id: I8ceb7cd90104f2ad9ca72c8025f6298de1fb1ba0 --- libs/binder/include/binder/IInterface.h | 147 +++++++++--------- libs/gui/Android.bp | 1 - libs/gui/BLASTBufferQueue.cpp | 2 +- libs/gui/ISurfaceComposer.cpp | 17 +- libs/gui/ISurfaceComposerClient.cpp | 124 --------------- libs/gui/LayerMetadata.cpp | 4 +- libs/gui/LayerState.cpp | 2 +- libs/gui/SurfaceComposerClient.cpp | 89 +++++------ libs/gui/SurfaceControl.cpp | 15 +- .../aidl/android/gui/CreateSurfaceResult.aidl | 24 +++ .../aidl/android/gui/ISurfaceComposer.aidl | 7 + .../android/gui/ISurfaceComposerClient.aidl | 62 ++++++++ libs/gui/aidl/android/gui/LayerMetadata.aidl | 19 +++ .../aidl/android/gui/MirrorSurfaceResult.aidl | 23 +++ libs/gui/include/gui/ISurfaceComposer.h | 9 +- libs/gui/include/gui/ISurfaceComposerClient.h | 97 ------------ libs/gui/include/gui/LayerMetadata.h | 13 +- libs/gui/include/gui/LayerState.h | 4 +- libs/gui/include/gui/SurfaceComposerClient.h | 19 +-- libs/gui/include/gui/SurfaceControl.h | 9 +- libs/gui/tests/Surface_test.cpp | 6 +- services/surfaceflinger/BufferLayer.h | 2 +- services/surfaceflinger/Client.cpp | 96 +++++++----- services/surfaceflinger/Client.h | 28 ++-- services/surfaceflinger/FpsReporter.cpp | 4 +- services/surfaceflinger/Layer.cpp | 14 +- services/surfaceflinger/Layer.h | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 31 ++-- services/surfaceflinger/SurfaceFlinger.h | 4 +- services/surfaceflinger/TimeStats/TimeStats.h | 2 + .../include/timestatsproto/TimeStatsHelper.h | 3 + .../Tracing/TransactionProtoParser.cpp | 4 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 2 +- .../include/layerproto/LayerProtoParser.h | 2 + .../tests/InvalidHandles_test.cpp | 4 +- .../tests/unittests/FpsReporterTest.cpp | 5 +- .../tests/unittests/GameModeTest.cpp | 4 +- .../tests/unittests/LayerMetadataTest.cpp | 4 +- .../unittests/TransactionProtoParserTest.cpp | 3 +- 39 files changed, 413 insertions(+), 494 deletions(-) delete mode 100644 libs/gui/ISurfaceComposerClient.cpp create mode 100644 libs/gui/aidl/android/gui/CreateSurfaceResult.aidl create mode 100644 libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl create mode 100644 libs/gui/aidl/android/gui/LayerMetadata.aidl create mode 100644 libs/gui/aidl/android/gui/MirrorSurfaceResult.aidl delete mode 100644 libs/gui/include/gui/ISurfaceComposerClient.h diff --git a/libs/binder/include/binder/IInterface.h b/libs/binder/include/binder/IInterface.h index 706783093c..1576c9427d 100644 --- a/libs/binder/include/binder/IInterface.h +++ b/libs/binder/include/binder/IInterface.h @@ -219,80 +219,79 @@ inline IBinder* BpInterface::onAsBinder() namespace internal { constexpr const char* const kManualInterfaces[] = { - "android.app.IActivityManager", - "android.app.IUidObserver", - "android.drm.IDrm", - "android.dvr.IVsyncCallback", - "android.dvr.IVsyncService", - "android.gfx.tests.ICallback", - "android.gfx.tests.IIPCTest", - "android.gfx.tests.ISafeInterfaceTest", - "android.graphicsenv.IGpuService", - "android.gui.IConsumerListener", - "android.gui.IGraphicBufferConsumer", - "android.gui.ITransactionComposerListener", - "android.gui.SensorEventConnection", - "android.gui.SensorServer", - "android.hardware.ICamera", - "android.hardware.ICameraClient", - "android.hardware.ICameraRecordingProxy", - "android.hardware.ICameraRecordingProxyListener", - "android.hardware.ICrypto", - "android.hardware.IOMXObserver", - "android.hardware.IStreamListener", - "android.hardware.IStreamSource", - "android.media.IAudioService", - "android.media.IDataSource", - "android.media.IDrmClient", - "android.media.IMediaCodecList", - "android.media.IMediaDrmService", - "android.media.IMediaExtractor", - "android.media.IMediaExtractorService", - "android.media.IMediaHTTPConnection", - "android.media.IMediaHTTPService", - "android.media.IMediaLogService", - "android.media.IMediaMetadataRetriever", - "android.media.IMediaMetricsService", - "android.media.IMediaPlayer", - "android.media.IMediaPlayerClient", - "android.media.IMediaPlayerService", - "android.media.IMediaRecorder", - "android.media.IMediaRecorderClient", - "android.media.IMediaResourceMonitor", - "android.media.IMediaSource", - "android.media.IRemoteDisplay", - "android.media.IRemoteDisplayClient", - "android.media.IResourceManagerClient", - "android.media.IResourceManagerService", - "android.os.IComplexTypeInterface", - "android.os.IPermissionController", - "android.os.IPingResponder", - "android.os.IProcessInfoService", - "android.os.ISchedulingPolicyService", - "android.os.IStringConstants", - "android.os.storage.IObbActionListener", - "android.os.storage.IStorageEventListener", - "android.os.storage.IStorageManager", - "android.os.storage.IStorageShutdownObserver", - "android.service.vr.IPersistentVrStateCallbacks", - "android.service.vr.IVrManager", - "android.service.vr.IVrStateCallbacks", - "android.ui.ISurfaceComposer", - "android.ui.ISurfaceComposerClient", - "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", - "com.android.internal.os.IShellCallback", - "drm.IDrmManagerService", - "drm.IDrmServiceListener", - "IAAudioClient", - "IAAudioService", - "VtsFuzzer", - nullptr, + "android.app.IActivityManager", + "android.app.IUidObserver", + "android.drm.IDrm", + "android.dvr.IVsyncCallback", + "android.dvr.IVsyncService", + "android.gfx.tests.ICallback", + "android.gfx.tests.IIPCTest", + "android.gfx.tests.ISafeInterfaceTest", + "android.graphicsenv.IGpuService", + "android.gui.IConsumerListener", + "android.gui.IGraphicBufferConsumer", + "android.gui.ITransactionComposerListener", + "android.gui.SensorEventConnection", + "android.gui.SensorServer", + "android.hardware.ICamera", + "android.hardware.ICameraClient", + "android.hardware.ICameraRecordingProxy", + "android.hardware.ICameraRecordingProxyListener", + "android.hardware.ICrypto", + "android.hardware.IOMXObserver", + "android.hardware.IStreamListener", + "android.hardware.IStreamSource", + "android.media.IAudioService", + "android.media.IDataSource", + "android.media.IDrmClient", + "android.media.IMediaCodecList", + "android.media.IMediaDrmService", + "android.media.IMediaExtractor", + "android.media.IMediaExtractorService", + "android.media.IMediaHTTPConnection", + "android.media.IMediaHTTPService", + "android.media.IMediaLogService", + "android.media.IMediaMetadataRetriever", + "android.media.IMediaMetricsService", + "android.media.IMediaPlayer", + "android.media.IMediaPlayerClient", + "android.media.IMediaPlayerService", + "android.media.IMediaRecorder", + "android.media.IMediaRecorderClient", + "android.media.IMediaResourceMonitor", + "android.media.IMediaSource", + "android.media.IRemoteDisplay", + "android.media.IRemoteDisplayClient", + "android.media.IResourceManagerClient", + "android.media.IResourceManagerService", + "android.os.IComplexTypeInterface", + "android.os.IPermissionController", + "android.os.IPingResponder", + "android.os.IProcessInfoService", + "android.os.ISchedulingPolicyService", + "android.os.IStringConstants", + "android.os.storage.IObbActionListener", + "android.os.storage.IStorageEventListener", + "android.os.storage.IStorageManager", + "android.os.storage.IStorageShutdownObserver", + "android.service.vr.IPersistentVrStateCallbacks", + "android.service.vr.IVrManager", + "android.service.vr.IVrStateCallbacks", + "android.ui.ISurfaceComposer", + "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", + "com.android.internal.os.IShellCallback", + "drm.IDrmManagerService", + "drm.IDrmServiceListener", + "IAAudioClient", + "IAAudioService", + "VtsFuzzer", + nullptr, }; constexpr const char* const kDownstreamManualInterfaces[] = { diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index a3f5a06d3b..648bcc33cb 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -204,7 +204,6 @@ cc_library_shared { "IGraphicBufferProducer.cpp", "IProducerListener.cpp", "ISurfaceComposer.cpp", - "ISurfaceComposerClient.cpp", "ITransactionCompletedListener.cpp", "LayerDebugInfo.cpp", "LayerMetadata.cpp", diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index bba7387c3a..ccb901c6a7 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -551,7 +551,7 @@ void BLASTBufferQueue::acquireNextBufferLocked( if (dequeueTime != mDequeueTimestamps.end()) { Parcel p; p.writeInt64(dequeueTime->second); - t->setMetadata(mSurfaceControl, METADATA_DEQUEUE_TIME, p); + t->setMetadata(mSurfaceControl, gui::METADATA_DEQUEUE_TIME, p); mDequeueTimestamps.erase(dequeueTime); } } diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index 80e512379f..54e50b50ac 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -61,14 +60,6 @@ public: virtual ~BpSurfaceComposer(); - virtual sp createConnection() - { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - remote()->transact(BnSurfaceComposer::CREATE_CONNECTION, data, &reply); - return interface_cast(reply.readStrongBinder()); - } - status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo, const Vector& state, const Vector& displays, uint32_t flags, @@ -159,13 +150,7 @@ IMPLEMENT_META_INTERFACE(SurfaceComposer, "android.ui.ISurfaceComposer"); status_t BnSurfaceComposer::onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { - switch(code) { - case CREATE_CONNECTION: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - sp b = IInterface::asBinder(createConnection()); - reply->writeStrongBinder(b); - return NO_ERROR; - } + switch (code) { case SET_TRANSACTION_STATE: { CHECK_INTERFACE(ISurfaceComposer, data, reply); diff --git a/libs/gui/ISurfaceComposerClient.cpp b/libs/gui/ISurfaceComposerClient.cpp deleted file mode 100644 index 5e7a7ec67b..0000000000 --- a/libs/gui/ISurfaceComposerClient.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -// tag as surfaceflinger -#define LOG_TAG "SurfaceFlinger" - -#include - -#include - -#include - -#include - -namespace android { - -namespace { // Anonymous - -enum class Tag : uint32_t { - CREATE_SURFACE = IBinder::FIRST_CALL_TRANSACTION, - CREATE_WITH_SURFACE_PARENT, - CLEAR_LAYER_FRAME_STATS, - GET_LAYER_FRAME_STATS, - MIRROR_SURFACE, - LAST = MIRROR_SURFACE, -}; - -} // Anonymous namespace - -class BpSurfaceComposerClient : public SafeBpInterface { -public: - explicit BpSurfaceComposerClient(const sp& impl) - : SafeBpInterface(impl, "BpSurfaceComposerClient") {} - - ~BpSurfaceComposerClient() override; - - status_t createSurface(const String8& name, uint32_t width, uint32_t height, PixelFormat format, - uint32_t flags, const sp& parent, LayerMetadata metadata, - sp* handle, sp* gbp, - int32_t* outLayerId, uint32_t* outTransformHint) override { - return callRemote(Tag::CREATE_SURFACE, - name, width, height, - format, flags, parent, - std::move(metadata), - handle, gbp, outLayerId, - outTransformHint); - } - - status_t createWithSurfaceParent(const String8& name, uint32_t width, uint32_t height, - PixelFormat format, uint32_t flags, - const sp& parent, - LayerMetadata metadata, sp* handle, - sp* gbp, int32_t* outLayerId, - uint32_t* outTransformHint) override { - return callRemote(Tag::CREATE_WITH_SURFACE_PARENT, - name, width, height, format, - flags, parent, - std::move(metadata), handle, gbp, - outLayerId, outTransformHint); - } - - status_t clearLayerFrameStats(const sp& handle) const override { - return callRemote(Tag::CLEAR_LAYER_FRAME_STATS, - handle); - } - - status_t getLayerFrameStats(const sp& handle, FrameStats* outStats) const override { - return callRemote(Tag::GET_LAYER_FRAME_STATS, handle, - outStats); - } - - status_t mirrorSurface(const sp& mirrorFromHandle, sp* outHandle, - int32_t* outLayerId) override { - return callRemote(Tag::MIRROR_SURFACE, - mirrorFromHandle, - outHandle, outLayerId); - } -}; - -// Out-of-line virtual method definition to trigger vtable emission in this -// translation unit (see clang warning -Wweak-vtables) -BpSurfaceComposerClient::~BpSurfaceComposerClient() {} - -IMPLEMENT_META_INTERFACE(SurfaceComposerClient, "android.ui.ISurfaceComposerClient"); - -// ---------------------------------------------------------------------- - -status_t BnSurfaceComposerClient::onTransact(uint32_t code, const Parcel& data, Parcel* reply, - uint32_t flags) { - if (code < IBinder::FIRST_CALL_TRANSACTION || code > static_cast(Tag::LAST)) { - return BBinder::onTransact(code, data, reply, flags); - } - auto tag = static_cast(code); - switch (tag) { - case Tag::CREATE_SURFACE: - return callLocal(data, reply, &ISurfaceComposerClient::createSurface); - case Tag::CREATE_WITH_SURFACE_PARENT: - return callLocal(data, reply, &ISurfaceComposerClient::createWithSurfaceParent); - case Tag::CLEAR_LAYER_FRAME_STATS: - return callLocal(data, reply, &ISurfaceComposerClient::clearLayerFrameStats); - case Tag::GET_LAYER_FRAME_STATS: - return callLocal(data, reply, &ISurfaceComposerClient::getLayerFrameStats); - case Tag::MIRROR_SURFACE: - return callLocal(data, reply, &ISurfaceComposerClient::mirrorSurface); - } -} - -} // namespace android diff --git a/libs/gui/LayerMetadata.cpp b/libs/gui/LayerMetadata.cpp index 189d51a4c1..4e12fd330c 100644 --- a/libs/gui/LayerMetadata.cpp +++ b/libs/gui/LayerMetadata.cpp @@ -23,7 +23,7 @@ using android::base::StringPrintf; -namespace android { +namespace android::gui { LayerMetadata::LayerMetadata() = default; @@ -144,4 +144,4 @@ std::string LayerMetadata::itemToString(uint32_t key, const char* separator) con } } -} // namespace android +} // namespace android::gui diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 502031c8d8..3ab9e974bc 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -19,10 +19,10 @@ #include #include +#include #include #include #include -#include #include #include #include diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 065deb6143..035aac9530 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -39,7 +40,6 @@ #include #include #include -#include #include #include #include @@ -2004,11 +2004,11 @@ SurfaceComposerClient::SurfaceComposerClient(const sp& c : mStatus(NO_ERROR), mClient(client) {} void SurfaceComposerClient::onFirstRef() { - sp sf(ComposerService::getComposerService()); + sp sf(ComposerServiceAIDL::getComposerService()); if (sf != nullptr && mStatus == NO_INIT) { sp conn; - conn = sf->createConnection(); - if (conn != nullptr) { + binder::Status status = sf->createConnection(&conn); + if (status.isOk() && conn != nullptr) { mClient = conn; mStatus = NO_ERROR; } @@ -2046,7 +2046,7 @@ void SurfaceComposerClient::dispose() { } sp SurfaceComposerClient::createSurface(const String8& name, uint32_t w, uint32_t h, - PixelFormat format, uint32_t flags, + PixelFormat format, int32_t flags, const sp& parentHandle, LayerMetadata metadata, uint32_t* outTransformHint) { @@ -2056,38 +2056,9 @@ sp SurfaceComposerClient::createSurface(const String8& name, uin return s; } -sp SurfaceComposerClient::createWithSurfaceParent(const String8& name, uint32_t w, - uint32_t h, PixelFormat format, - uint32_t flags, Surface* parent, - LayerMetadata metadata, - uint32_t* outTransformHint) { - sp sur; - status_t err = mStatus; - - if (mStatus == NO_ERROR) { - sp handle; - sp parentGbp = parent->getIGraphicBufferProducer(); - sp gbp; - - uint32_t transformHint = 0; - int32_t id = -1; - err = mClient->createWithSurfaceParent(name, w, h, format, flags, parentGbp, - std::move(metadata), &handle, &gbp, &id, - &transformHint); - if (outTransformHint) { - *outTransformHint = transformHint; - } - ALOGE_IF(err, "SurfaceComposerClient::createWithSurfaceParent error %s", strerror(-err)); - if (err == NO_ERROR) { - return new SurfaceControl(this, handle, gbp, id, transformHint); - } - } - return nullptr; -} - status_t SurfaceComposerClient::createSurfaceChecked(const String8& name, uint32_t w, uint32_t h, PixelFormat format, - sp* outSurface, uint32_t flags, + sp* outSurface, int32_t flags, const sp& parentHandle, LayerMetadata metadata, uint32_t* outTransformHint) { @@ -2095,21 +2066,17 @@ status_t SurfaceComposerClient::createSurfaceChecked(const String8& name, uint32 status_t err = mStatus; if (mStatus == NO_ERROR) { - sp handle; - sp gbp; - - uint32_t transformHint = 0; - int32_t id = -1; - err = mClient->createSurface(name, w, h, format, flags, parentHandle, std::move(metadata), - &handle, &gbp, &id, &transformHint); - + gui::CreateSurfaceResult result; + binder::Status status = mClient->createSurface(std::string(name.string()), flags, + parentHandle, std::move(metadata), &result); + err = statusTFromBinderStatus(status); if (outTransformHint) { - *outTransformHint = transformHint; + *outTransformHint = result.transformHint; } ALOGE_IF(err, "SurfaceComposerClient::createSurface error %s", strerror(-err)); if (err == NO_ERROR) { - *outSurface = - new SurfaceControl(this, handle, gbp, id, w, h, format, transformHint, flags); + *outSurface = new SurfaceControl(this, result.handle, result.layerId, w, h, format, + result.transformHint, flags); } } return err; @@ -2120,12 +2087,12 @@ sp SurfaceComposerClient::mirrorSurface(SurfaceControl* mirrorFr return nullptr; } - sp handle; sp mirrorFromHandle = mirrorFromSurface->getHandle(); - int32_t layer_id = -1; - status_t err = mClient->mirrorSurface(mirrorFromHandle, &handle, &layer_id); + gui::MirrorSurfaceResult result; + const binder::Status status = mClient->mirrorSurface(mirrorFromHandle, &result); + const status_t err = statusTFromBinderStatus(status); if (err == NO_ERROR) { - return new SurfaceControl(this, handle, nullptr, layer_id, true /* owned */); + return new SurfaceControl(this, result.handle, result.layerId); } return nullptr; } @@ -2134,7 +2101,8 @@ status_t SurfaceComposerClient::clearLayerFrameStats(const sp& token) c if (mStatus != NO_ERROR) { return mStatus; } - return mClient->clearLayerFrameStats(token); + const binder::Status status = mClient->clearLayerFrameStats(token); + return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getLayerFrameStats(const sp& token, @@ -2142,7 +2110,24 @@ status_t SurfaceComposerClient::getLayerFrameStats(const sp& token, if (mStatus != NO_ERROR) { return mStatus; } - return mClient->getLayerFrameStats(token, outStats); + gui::FrameStats stats; + const binder::Status status = mClient->getLayerFrameStats(token, &stats); + if (status.isOk()) { + outStats->refreshPeriodNano = stats.refreshPeriodNano; + outStats->desiredPresentTimesNano.setCapacity(stats.desiredPresentTimesNano.size()); + for (const auto& t : stats.desiredPresentTimesNano) { + outStats->desiredPresentTimesNano.add(t); + } + outStats->actualPresentTimesNano.setCapacity(stats.actualPresentTimesNano.size()); + for (const auto& t : stats.actualPresentTimesNano) { + outStats->actualPresentTimesNano.add(t); + } + outStats->frameReadyTimesNano.setCapacity(stats.frameReadyTimesNano.size()); + for (const auto& t : stats.frameReadyTimesNano) { + outStats->frameReadyTimesNano.add(t); + } + } + return statusTFromBinderStatus(status); } // ---------------------------------------------------------------------------- diff --git a/libs/gui/SurfaceControl.cpp b/libs/gui/SurfaceControl.cpp index 654fb336fe..84257dee9b 100644 --- a/libs/gui/SurfaceControl.cpp +++ b/libs/gui/SurfaceControl.cpp @@ -49,12 +49,10 @@ namespace android { // ============================================================================ SurfaceControl::SurfaceControl(const sp& client, const sp& handle, - const sp& gbp, int32_t layerId, - uint32_t w, uint32_t h, PixelFormat format, uint32_t transform, - uint32_t flags) + int32_t layerId, uint32_t w, uint32_t h, PixelFormat format, + uint32_t transform, uint32_t flags) : mClient(client), mHandle(handle), - mGraphicBufferProducer(gbp), mLayerId(layerId), mTransformHint(transform), mWidth(w), @@ -65,7 +63,6 @@ SurfaceControl::SurfaceControl(const sp& client, const sp SurfaceControl::SurfaceControl(const sp& other) { mClient = other->mClient; mHandle = other->mHandle; - mGraphicBufferProducer = other->mGraphicBufferProducer; mTransformHint = other->mTransformHint; mLayerId = other->mLayerId; mWidth = other->mWidth; @@ -165,11 +162,11 @@ sp SurfaceControl::createSurface() void SurfaceControl::updateDefaultBufferSize(uint32_t width, uint32_t height) { Mutex::Autolock _l(mLock); - mWidth = width; mHeight = height; + mWidth = width; + mHeight = height; if (mBbq) { mBbq->update(mBbqChild, width, height, mFormat); } - } sp SurfaceControl::getLayerStateHandle() const @@ -245,9 +242,7 @@ status_t SurfaceControl::readFromParcel(const Parcel& parcel, *outSurfaceControl = new SurfaceControl(new SurfaceComposerClient( interface_cast(client)), - handle.get(), nullptr, layerId, - width, height, format, - transformHint); + handle.get(), layerId, width, height, format, transformHint); return NO_ERROR; } diff --git a/libs/gui/aidl/android/gui/CreateSurfaceResult.aidl b/libs/gui/aidl/android/gui/CreateSurfaceResult.aidl new file mode 100644 index 0000000000..39e49167aa --- /dev/null +++ b/libs/gui/aidl/android/gui/CreateSurfaceResult.aidl @@ -0,0 +1,24 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +/** @hide */ +parcelable CreateSurfaceResult { + IBinder handle; + int layerId; + int transformHint; +} diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 6ec6f760ae..26b4e819ee 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -34,6 +34,7 @@ import android.gui.IFpsListener; import android.gui.IHdrLayerInfoListener; import android.gui.IRegionSamplingListener; import android.gui.IScreenCaptureListener; +import android.gui.ISurfaceComposerClient; import android.gui.ITransactionTraceListener; import android.gui.ITunnelModeEnabledListener; import android.gui.IWindowInfosListener; @@ -45,6 +46,12 @@ import android.gui.StaticDisplayInfo; /** @hide */ interface ISurfaceComposer { + + /** + * Create a connection with SurfaceFlinger. + */ + @nullable ISurfaceComposerClient createConnection(); + /** * Create a virtual display * requires ACCESS_SURFACE_FLINGER permission. diff --git a/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl b/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl new file mode 100644 index 0000000000..71933aa6ff --- /dev/null +++ b/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl @@ -0,0 +1,62 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +import android.gui.CreateSurfaceResult; +import android.gui.FrameStats; +import android.gui.LayerMetadata; +import android.gui.MirrorSurfaceResult; + +/** @hide */ +interface ISurfaceComposerClient { + + // flags for createSurface() + // (keep in sync with SurfaceControl.java) + const int eHidden = 0x00000004; + const int eDestroyBackbuffer = 0x00000020; + const int eSkipScreenshot = 0x00000040; + const int eSecure = 0x00000080; + const int eNonPremultiplied = 0x00000100; + const int eOpaque = 0x00000400; + const int eProtectedByApp = 0x00000800; + const int eProtectedByDRM = 0x00001000; + const int eCursorWindow = 0x00002000; + const int eNoColorFill = 0x00004000; + + const int eFXSurfaceBufferQueue = 0x00000000; + const int eFXSurfaceEffect = 0x00020000; + const int eFXSurfaceBufferState = 0x00040000; + const int eFXSurfaceContainer = 0x00080000; + const int eFXSurfaceMask = 0x000F0000; + + /** + * Requires ACCESS_SURFACE_FLINGER permission + */ + CreateSurfaceResult createSurface(@utf8InCpp String name, int flags, @nullable IBinder parent, in LayerMetadata metadata); + + /** + * Requires ACCESS_SURFACE_FLINGER permission + */ + void clearLayerFrameStats(IBinder handle); + + /** + * Requires ACCESS_SURFACE_FLINGER permission + */ + FrameStats getLayerFrameStats(IBinder handle); + + MirrorSurfaceResult mirrorSurface(IBinder mirrorFromHandle); +} diff --git a/libs/gui/aidl/android/gui/LayerMetadata.aidl b/libs/gui/aidl/android/gui/LayerMetadata.aidl new file mode 100644 index 0000000000..1368ac512f --- /dev/null +++ b/libs/gui/aidl/android/gui/LayerMetadata.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +parcelable LayerMetadata cpp_header "gui/LayerMetadata.h"; diff --git a/libs/gui/aidl/android/gui/MirrorSurfaceResult.aidl b/libs/gui/aidl/android/gui/MirrorSurfaceResult.aidl new file mode 100644 index 0000000000..9fac3e8644 --- /dev/null +++ b/libs/gui/aidl/android/gui/MirrorSurfaceResult.aidl @@ -0,0 +1,23 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +/** @hide */ +parcelable MirrorSurfaceResult { + IBinder handle; + int layerId; +} diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index 8f75d296c4..7139e1bb93 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -62,8 +62,6 @@ struct DisplayStatInfo; struct DisplayState; struct InputWindowCommands; class HdrCapabilities; -class IGraphicBufferProducer; -class ISurfaceComposerClient; class Rect; using gui::FrameTimelineInfo; @@ -127,11 +125,6 @@ public: using EventRegistrationFlags = ftl::Flags; - /* - * Create a connection with SurfaceFlinger. - */ - virtual sp createConnection() = 0; - /* return an IDisplayEventConnection */ virtual sp createDisplayEventConnection( VsyncSource vsyncSource = eVsyncSourceApp, @@ -159,7 +152,7 @@ public: // Note: BOOT_FINISHED must remain this value, it is called from // Java by ActivityManagerService. BOOT_FINISHED = IBinder::FIRST_CALL_TRANSACTION, - CREATE_CONNECTION, + CREATE_CONNECTION, // Deprecated. Autogenerated by .aidl now. GET_STATIC_DISPLAY_INFO, // Deprecated. Autogenerated by .aidl now. CREATE_DISPLAY_EVENT_CONNECTION, CREATE_DISPLAY, // Deprecated. Autogenerated by .aidl now. diff --git a/libs/gui/include/gui/ISurfaceComposerClient.h b/libs/gui/include/gui/ISurfaceComposerClient.h deleted file mode 100644 index 9e9e191480..0000000000 --- a/libs/gui/include/gui/ISurfaceComposerClient.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2007 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 -#include -#include -#include - -#include - -namespace android { - -class FrameStats; -class IGraphicBufferProducer; - -class ISurfaceComposerClient : public IInterface { -public: - DECLARE_META_INTERFACE(SurfaceComposerClient) - - // flags for createSurface() - enum { // (keep in sync with SurfaceControl.java) - eHidden = 0x00000004, - eDestroyBackbuffer = 0x00000020, - eSkipScreenshot = 0x00000040, - eSecure = 0x00000080, - eNonPremultiplied = 0x00000100, - eOpaque = 0x00000400, - eProtectedByApp = 0x00000800, - eProtectedByDRM = 0x00001000, - eCursorWindow = 0x00002000, - eNoColorFill = 0x00004000, - - eFXSurfaceBufferQueue = 0x00000000, - eFXSurfaceEffect = 0x00020000, - eFXSurfaceBufferState = 0x00040000, - eFXSurfaceContainer = 0x00080000, - eFXSurfaceMask = 0x000F0000, - }; - - // TODO(b/172002646): Clean up the Surface Creation Arguments - /* - * Requires ACCESS_SURFACE_FLINGER permission - */ - virtual status_t createSurface(const String8& name, uint32_t w, uint32_t h, PixelFormat format, - uint32_t flags, const sp& parent, - LayerMetadata metadata, sp* handle, - sp* gbp, int32_t* outLayerId, - uint32_t* outTransformHint) = 0; - - /* - * Requires ACCESS_SURFACE_FLINGER permission - */ - virtual status_t createWithSurfaceParent(const String8& name, uint32_t w, uint32_t h, - PixelFormat format, uint32_t flags, - const sp& parent, - LayerMetadata metadata, sp* handle, - sp* gbp, int32_t* outLayerId, - uint32_t* outTransformHint) = 0; - - /* - * Requires ACCESS_SURFACE_FLINGER permission - */ - virtual status_t clearLayerFrameStats(const sp& handle) const = 0; - - /* - * Requires ACCESS_SURFACE_FLINGER permission - */ - virtual status_t getLayerFrameStats(const sp& handle, FrameStats* outStats) const = 0; - - virtual status_t mirrorSurface(const sp& mirrorFromHandle, sp* outHandle, - int32_t* outLayerId) = 0; -}; - -class BnSurfaceComposerClient : public SafeBnInterface { -public: - BnSurfaceComposerClient() - : SafeBnInterface("BnSurfaceComposerClient") {} - - status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) override; -}; - -} // namespace android diff --git a/libs/gui/include/gui/LayerMetadata.h b/libs/gui/include/gui/LayerMetadata.h index 27f4d379e9..5af598956b 100644 --- a/libs/gui/include/gui/LayerMetadata.h +++ b/libs/gui/include/gui/LayerMetadata.h @@ -20,7 +20,7 @@ #include -namespace android { +namespace android::gui { enum { METADATA_OWNER_UID = 1, @@ -69,4 +69,13 @@ enum class GameMode : int32_t { ftl_last = Battery }; -} // namespace android +} // namespace android::gui + +using android::gui::METADATA_ACCESSIBILITY_ID; +using android::gui::METADATA_DEQUEUE_TIME; +using android::gui::METADATA_GAME_MODE; +using android::gui::METADATA_MOUSE_CURSOR; +using android::gui::METADATA_OWNER_PID; +using android::gui::METADATA_OWNER_UID; +using android::gui::METADATA_TASK_ID; +using android::gui::METADATA_WINDOW_TYPE; diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 0a9b75a7f1..4cd9a56fcd 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -51,7 +51,9 @@ namespace android { class Parcel; -class ISurfaceComposerClient; + +using gui::ISurfaceComposerClient; +using gui::LayerMetadata; struct client_cache_t { wp token = nullptr; diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 8569a27808..dc9624269b 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -40,6 +40,8 @@ #include #include +#include + #include #include #include @@ -53,14 +55,15 @@ namespace android { class HdrCapabilities; -class ISurfaceComposerClient; class IGraphicBufferProducer; class ITunnelModeEnabledListener; class Region; using gui::DisplayCaptureArgs; using gui::IRegionSamplingListener; +using gui::ISurfaceComposerClient; using gui::LayerCaptureArgs; +using gui::LayerMetadata; struct SurfaceControlStats { SurfaceControlStats(const sp& sc, nsecs_t latchTime, @@ -312,7 +315,7 @@ public: uint32_t w, // width in pixel uint32_t h, // height in pixel PixelFormat format, // pixel-format desired - uint32_t flags = 0, // usage flags + int32_t flags = 0, // usage flags const sp& parentHandle = nullptr, // parentHandle LayerMetadata metadata = LayerMetadata(), // metadata uint32_t* outTransformHint = nullptr); @@ -322,21 +325,11 @@ public: uint32_t h, // height in pixel PixelFormat format, // pixel-format desired sp* outSurface, - uint32_t flags = 0, // usage flags + int32_t flags = 0, // usage flags const sp& parentHandle = nullptr, // parentHandle LayerMetadata metadata = LayerMetadata(), // metadata uint32_t* outTransformHint = nullptr); - //! Create a surface - sp createWithSurfaceParent(const String8& name, // name of the surface - uint32_t w, // width in pixel - uint32_t h, // height in pixel - PixelFormat format, // pixel-format desired - uint32_t flags = 0, // usage flags - Surface* parent = nullptr, // parent - LayerMetadata metadata = LayerMetadata(), // metadata - uint32_t* outTransformHint = nullptr); - // Creates a mirrored hierarchy for the mirrorFromSurface. This returns a SurfaceControl // which is a parent of the root of the mirrored hierarchy. // diff --git a/libs/gui/include/gui/SurfaceControl.h b/libs/gui/include/gui/SurfaceControl.h index b72cf8390e..e4a1350643 100644 --- a/libs/gui/include/gui/SurfaceControl.h +++ b/libs/gui/include/gui/SurfaceControl.h @@ -24,11 +24,12 @@ #include #include +#include + #include #include #include -#include #include namespace android { @@ -93,8 +94,7 @@ public: explicit SurfaceControl(const sp& other); SurfaceControl(const sp& client, const sp& handle, - const sp& gbp, int32_t layerId, - uint32_t width = 0, uint32_t height = 0, PixelFormat format = 0, + int32_t layerId, uint32_t width = 0, uint32_t height = 0, PixelFormat format = 0, uint32_t transformHint = 0, uint32_t flags = 0); sp getParentingLayer(); @@ -115,8 +115,7 @@ private: status_t validate() const; sp mClient; - sp mHandle; - sp mGraphicBufferProducer; + sp mHandle; mutable Mutex mLock; mutable sp mSurfaceData; mutable sp mBbq; diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index a9d8436e41..4ab380c8b8 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -692,7 +692,6 @@ public: mSupportsPresent = supportsPresent; } - sp createConnection() override { return nullptr; } sp createDisplayEventConnection( ISurfaceComposer::VsyncSource, ISurfaceComposer::EventRegistrationFlags) override { return nullptr; @@ -725,6 +724,11 @@ public: void setSupportsPresent(bool supportsPresent) { mSupportsPresent = supportsPresent; } + binder::Status createConnection(sp* outClient) override { + *outClient = nullptr; + return binder::Status::ok(); + } + binder::Status createDisplay(const std::string& /*displayName*/, bool /*secure*/, sp* /*outDisplay*/) override { return binder::Status::ok(); diff --git a/services/surfaceflinger/BufferLayer.h b/services/surfaceflinger/BufferLayer.h index 3e70493101..2f3db7428b 100644 --- a/services/surfaceflinger/BufferLayer.h +++ b/services/surfaceflinger/BufferLayer.h @@ -20,7 +20,7 @@ #include #include -#include +#include #include #include #include diff --git a/services/surfaceflinger/Client.cpp b/services/surfaceflinger/Client.cpp index 6d7b732b36..b27055d57a 100644 --- a/services/surfaceflinger/Client.cpp +++ b/services/surfaceflinger/Client.cpp @@ -21,12 +21,16 @@ #include +#include + #include "Client.h" #include "Layer.h" #include "SurfaceFlinger.h" namespace android { +using gui::aidl_utils::binderStatusFromStatusT; + // --------------------------------------------------------------------------- const String16 sAccessSurfaceFlinger("android.permission.ACCESS_SURFACE_FLINGER"); @@ -72,52 +76,74 @@ sp Client::getLayerUser(const sp& handle) const return lbc; } -status_t Client::createSurface(const String8& name, uint32_t /* w */, uint32_t /* h */, - PixelFormat /* format */, uint32_t flags, - const sp& parentHandle, LayerMetadata metadata, - sp* outHandle, sp* /* gbp */, - int32_t* outLayerId, uint32_t* outTransformHint) { +binder::Status Client::createSurface(const std::string& name, int32_t flags, + const sp& parent, const gui::LayerMetadata& metadata, + gui::CreateSurfaceResult* outResult) { // We rely on createLayer to check permissions. - LayerCreationArgs args(mFlinger.get(), this, name.c_str(), flags, std::move(metadata)); - return mFlinger->createLayer(args, outHandle, parentHandle, outLayerId, nullptr, - outTransformHint); -} - -status_t Client::createWithSurfaceParent(const String8& /* name */, uint32_t /* w */, - uint32_t /* h */, PixelFormat /* format */, - uint32_t /* flags */, - const sp& /* parent */, - LayerMetadata /* metadata */, sp* /* handle */, - sp* /* gbp */, - int32_t* /* outLayerId */, - uint32_t* /* outTransformHint */) { - // This api does not make sense with blast since SF no longer tracks IGBP. This api should be - // removed. - return BAD_VALUE; -} - -status_t Client::mirrorSurface(const sp& mirrorFromHandle, sp* outHandle, - int32_t* outLayerId) { - LayerCreationArgs args(mFlinger.get(), this, "MirrorRoot", 0 /* flags */, LayerMetadata()); - return mFlinger->mirrorLayer(args, mirrorFromHandle, outHandle, outLayerId); + sp handle; + int32_t layerId; + uint32_t transformHint; + LayerCreationArgs args(mFlinger.get(), this, name.c_str(), static_cast(flags), + std::move(metadata)); + const status_t status = + mFlinger->createLayer(args, &handle, parent, &layerId, nullptr, &transformHint); + if (status == NO_ERROR) { + outResult->handle = handle; + outResult->layerId = layerId; + outResult->transformHint = static_cast(transformHint); + } + return binderStatusFromStatusT(status); } -status_t Client::clearLayerFrameStats(const sp& handle) const { +binder::Status Client::clearLayerFrameStats(const sp& handle) { + status_t status; sp layer = getLayerUser(handle); if (layer == nullptr) { - return NAME_NOT_FOUND; + status = NAME_NOT_FOUND; + } else { + layer->clearFrameStats(); + status = NO_ERROR; } - layer->clearFrameStats(); - return NO_ERROR; + return binderStatusFromStatusT(status); } -status_t Client::getLayerFrameStats(const sp& handle, FrameStats* outStats) const { +binder::Status Client::getLayerFrameStats(const sp& handle, gui::FrameStats* outStats) { + status_t status; sp layer = getLayerUser(handle); if (layer == nullptr) { - return NAME_NOT_FOUND; + status = NAME_NOT_FOUND; + } else { + FrameStats stats; + layer->getFrameStats(&stats); + outStats->refreshPeriodNano = stats.refreshPeriodNano; + outStats->desiredPresentTimesNano.reserve(stats.desiredPresentTimesNano.size()); + for (const auto& t : stats.desiredPresentTimesNano) { + outStats->desiredPresentTimesNano.push_back(t); + } + outStats->actualPresentTimesNano.reserve(stats.actualPresentTimesNano.size()); + for (const auto& t : stats.actualPresentTimesNano) { + outStats->actualPresentTimesNano.push_back(t); + } + outStats->frameReadyTimesNano.reserve(stats.frameReadyTimesNano.size()); + for (const auto& t : stats.frameReadyTimesNano) { + outStats->frameReadyTimesNano.push_back(t); + } + status = NO_ERROR; } - layer->getFrameStats(outStats); - return NO_ERROR; + return binderStatusFromStatusT(status); +} + +binder::Status Client::mirrorSurface(const sp& mirrorFromHandle, + gui::MirrorSurfaceResult* outResult) { + sp handle; + int32_t layerId; + LayerCreationArgs args(mFlinger.get(), this, "MirrorRoot", 0 /* flags */, gui::LayerMetadata()); + status_t status = mFlinger->mirrorLayer(args, mirrorFromHandle, &handle, &layerId); + if (status == NO_ERROR) { + outResult->handle = handle; + outResult->layerId = layerId; + } + return binderStatusFromStatusT(status); } // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/Client.h b/services/surfaceflinger/Client.h index 15cd763822..4720d5c43d 100644 --- a/services/surfaceflinger/Client.h +++ b/services/surfaceflinger/Client.h @@ -24,15 +24,14 @@ #include #include -#include +#include namespace android { class Layer; class SurfaceFlinger; -class Client : public BnSurfaceComposerClient -{ +class Client : public gui::BnSurfaceComposerClient { public: explicit Client(const sp& flinger); ~Client() = default; @@ -47,25 +46,18 @@ public: private: // ISurfaceComposerClient interface - virtual status_t createSurface(const String8& name, uint32_t w, uint32_t h, PixelFormat format, - uint32_t flags, const sp& parent, - LayerMetadata metadata, sp* handle, - sp* gbp, int32_t* outLayerId, - uint32_t* outTransformHint = nullptr); - virtual status_t createWithSurfaceParent(const String8& name, uint32_t w, uint32_t h, - PixelFormat format, uint32_t flags, - const sp& parent, - LayerMetadata metadata, sp* handle, - sp* gbp, int32_t* outLayerId, - uint32_t* outTransformHint = nullptr); + binder::Status createSurface(const std::string& name, int32_t flags, const sp& parent, + const gui::LayerMetadata& metadata, + gui::CreateSurfaceResult* outResult) override; - status_t mirrorSurface(const sp& mirrorFromHandle, sp* handle, - int32_t* outLayerId); + binder::Status clearLayerFrameStats(const sp& handle) override; - virtual status_t clearLayerFrameStats(const sp& handle) const; + binder::Status getLayerFrameStats(const sp& handle, + gui::FrameStats* outStats) override; - virtual status_t getLayerFrameStats(const sp& handle, FrameStats* outStats) const; + binder::Status mirrorSurface(const sp& mirrorFromHandle, + gui::MirrorSurfaceResult* outResult) override; // constant sp mFlinger; diff --git a/services/surfaceflinger/FpsReporter.cpp b/services/surfaceflinger/FpsReporter.cpp index e12835f0f6..c89c9ffcd4 100644 --- a/services/surfaceflinger/FpsReporter.cpp +++ b/services/surfaceflinger/FpsReporter.cpp @@ -56,8 +56,8 @@ void FpsReporter::dispatchLayerFps() { mFlinger.mCurrentState.traverse([&](Layer* layer) { auto& currentState = layer->getDrawingState(); - if (currentState.metadata.has(METADATA_TASK_ID)) { - int32_t taskId = currentState.metadata.getInt32(METADATA_TASK_ID, 0); + if (currentState.metadata.has(gui::METADATA_TASK_ID)) { + int32_t taskId = currentState.metadata.getInt32(gui::METADATA_TASK_ID, 0); if (seenTasks.count(taskId) == 0) { // localListeners is expected to be tiny for (TrackedListener& listener : localListeners) { diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index b4f9c4e720..791ab06258 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -85,6 +85,8 @@ constexpr int kDumpTableRowLength = 159; using namespace ftl::flag_operators; using base::StringAppendF; +using gui::GameMode; +using gui::LayerMetadata; using gui::WindowInfo; using PresentState = frametimeline::SurfaceFrame::PresentState; @@ -96,7 +98,8 @@ Layer::Layer(const LayerCreationArgs& args) mFlinger(args.flinger), mName(base::StringPrintf("%s#%d", args.name.c_str(), sequence)), mClientRef(args.client), - mWindowType(static_cast(args.metadata.getInt32(METADATA_WINDOW_TYPE, 0))), + mWindowType(static_cast( + args.metadata.getInt32(gui::METADATA_WINDOW_TYPE, 0))), mLayerCreationFlags(args.flags) { uint32_t layerFlags = 0; if (args.flags & ISurfaceComposerClient::eHidden) layerFlags |= layer_state_t::eLayerHidden; @@ -161,8 +164,8 @@ Layer::Layer(const LayerCreationArgs& args) if (mCallingUid == AID_GRAPHICS || mCallingUid == AID_SYSTEM) { // If the system didn't send an ownerUid, use the callingUid for the ownerUid. - mOwnerUid = args.metadata.getInt32(METADATA_OWNER_UID, mCallingUid); - mOwnerPid = args.metadata.getInt32(METADATA_OWNER_PID, mCallingPid); + mOwnerUid = args.metadata.getInt32(gui::METADATA_OWNER_UID, mCallingUid); + mOwnerPid = args.metadata.getInt32(gui::METADATA_OWNER_PID, mCallingPid); } else { // A create layer request from a non system request cannot specify the owner uid mOwnerUid = mCallingUid; @@ -1576,8 +1579,9 @@ size_t Layer::getChildrenCount() const { void Layer::setGameModeForTree(GameMode gameMode) { const auto& currentState = getDrawingState(); - if (currentState.metadata.has(METADATA_GAME_MODE)) { - gameMode = static_cast(currentState.metadata.getInt32(METADATA_GAME_MODE, 0)); + if (currentState.metadata.has(gui::METADATA_GAME_MODE)) { + gameMode = + static_cast(currentState.metadata.getInt32(gui::METADATA_GAME_MODE, 0)); } setGameMode(gameMode); for (const sp& child : mCurrentChildren) { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 455920be62..c89b254e99 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -17,8 +17,8 @@ #pragma once #include +#include #include -#include #include #include #include diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 3762033942..46b1a60a77 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -171,8 +171,10 @@ using CompositionStrategyPredictionState = android::compositionengine::impl:: using base::StringAppendF; using gui::DisplayInfo; +using gui::GameMode; using gui::IDisplayEventConnection; using gui::IWindowInfosListener; +using gui::LayerMetadata; using gui::WindowInfo; using gui::aidl_utils::binderStatusFromStatusT; using ui::ColorMode; @@ -477,11 +479,6 @@ void SurfaceFlinger::run() { mScheduler->run(); } -sp SurfaceFlinger::createConnection() { - const sp client = new Client(this); - return client->initCheck() == NO_ERROR ? client : nullptr; -} - sp SurfaceFlinger::createDisplay(const String8& displayName, bool secure) { // onTransact already checks for some permissions, but adding an additional check here. // This is to ensure that only system and graphics can request to create a secure @@ -4458,9 +4455,10 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime } std::optional dequeueBufferTimestamp; if (what & layer_state_t::eMetadataChanged) { - dequeueBufferTimestamp = s.metadata.getInt64(METADATA_DEQUEUE_TIME); + dequeueBufferTimestamp = s.metadata.getInt64(gui::METADATA_DEQUEUE_TIME); - if (const int32_t gameMode = s.metadata.getInt32(METADATA_GAME_MODE, -1); gameMode != -1) { + if (const int32_t gameMode = s.metadata.getInt32(gui::METADATA_GAME_MODE, -1); + gameMode != -1) { // The transaction will be received on the Task layer and needs to be applied to all // child layers. Child layers that are added at a later point will obtain the game mode // info through addChild(). @@ -5503,11 +5501,11 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { case GET_DISPLAY_MODES: // Calling setTransactionState is safe, because you need to have been // granted a reference to Client* and Handle* to do anything with it. - case SET_TRANSACTION_STATE: - case CREATE_CONNECTION: { + case SET_TRANSACTION_STATE: { // This is not sensitive information, so should not require permission control. return OK; } + case CREATE_CONNECTION: case CREATE_DISPLAY: case DESTROY_DISPLAY: case GET_PRIMARY_PHYSICAL_DISPLAY_ID: @@ -6995,8 +6993,8 @@ const std::unordered_map& SurfaceFlinger::getGenericLayer // on the work to remove the table in that bug rather than adding more to // it. static const std::unordered_map genericLayerMetadataKeyMap{ - {"org.chromium.arc.V1_0.TaskId", METADATA_TASK_ID}, - {"org.chromium.arc.V1_0.CursorInfo", METADATA_MOUSE_CURSOR}, + {"org.chromium.arc.V1_0.TaskId", gui::METADATA_TASK_ID}, + {"org.chromium.arc.V1_0.CursorInfo", gui::METADATA_MOUSE_CURSOR}, }; return genericLayerMetadataKeyMap; } @@ -7213,6 +7211,17 @@ bool SurfaceFlinger::commitCreatedLayers() { // gui::ISurfaceComposer +binder::Status SurfaceComposerAIDL::createConnection(sp* outClient) { + const sp client = new Client(mFlinger); + if (client->initCheck() == NO_ERROR) { + *outClient = client; + return binder::Status::ok(); + } else { + *outClient = nullptr; + return binderStatusFromStatusT(BAD_VALUE); + } +} + binder::Status SurfaceComposerAIDL::createDisplay(const std::string& displayName, bool secure, sp* outDisplay) { status_t status = checkAccessPermission(); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index df00eeecdf..353df24f7f 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -25,12 +25,12 @@ #include #include #include +#include #include #include #include #include #include -#include #include #include #include @@ -536,7 +536,6 @@ private: } // Implements ISurfaceComposer - sp createConnection() override; sp createDisplay(const String8& displayName, bool secure); void destroyDisplay(const sp& displayToken); std::vector getPhysicalDisplayIds() const EXCLUDES(mStateLock) { @@ -1446,6 +1445,7 @@ class SurfaceComposerAIDL : public gui::BnSurfaceComposer { public: SurfaceComposerAIDL(sp sf) : mFlinger(std::move(sf)) {} + binder::Status createConnection(sp* outClient) override; binder::Status createDisplay(const std::string& displayName, bool secure, sp* outDisplay) override; binder::Status destroyDisplay(const sp& display) override; diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h index 7a159b8eb7..61d7c22a2a 100644 --- a/services/surfaceflinger/TimeStats/TimeStats.h +++ b/services/surfaceflinger/TimeStats/TimeStats.h @@ -34,6 +34,8 @@ #include +using android::gui::GameMode; +using android::gui::LayerMetadata; using namespace android::surfaceflinger; namespace android { diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h index 237ae8d761..60aa810e8b 100644 --- a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h +++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h @@ -24,6 +24,9 @@ #include #include +using android::gui::GameMode; +using android::gui::LayerMetadata; + namespace android { namespace surfaceflinger { diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp index e8ecf2f33b..8ec6c99eb9 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp @@ -459,7 +459,7 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta layer.parentSurfaceControlForChild = new SurfaceControl(SurfaceComposerClient::getDefault(), mMapper->getLayerHandle(static_cast(layerId)), - nullptr, static_cast(layerId)); + static_cast(layerId)); } } if (proto.what() & layer_state_t::eRelativeLayerChanged) { @@ -470,7 +470,7 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta layer.relativeLayerSurfaceControl = new SurfaceControl(SurfaceComposerClient::getDefault(), mMapper->getLayerHandle(static_cast(layerId)), - nullptr, static_cast(layerId)); + static_cast(layerId)); } layer.z = proto.z(); } diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 2a4d6ed86f..c4cd089a5e 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -555,7 +555,7 @@ public: sp fuzzBoot(FuzzedDataProvider *fdp) { mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(fdp->ConsumeBool()); - mFlinger->createConnection(); + const sp client = new Client(mFlinger); DisplayIdGenerator kGenerator; HalVirtualDisplayId halVirtualDisplayId = kGenerator.generateId().value(); diff --git a/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h b/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h index 52503bad3a..cdc2706ee2 100644 --- a/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h +++ b/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h @@ -24,6 +24,8 @@ #include #include +using android::gui::LayerMetadata; + namespace android { namespace surfaceflinger { diff --git a/services/surfaceflinger/tests/InvalidHandles_test.cpp b/services/surfaceflinger/tests/InvalidHandles_test.cpp index d192a2d83d..023133f2c3 100644 --- a/services/surfaceflinger/tests/InvalidHandles_test.cpp +++ b/services/surfaceflinger/tests/InvalidHandles_test.cpp @@ -48,7 +48,7 @@ protected: } sp makeNotSurfaceControl() { - return new SurfaceControl(mScc, new NotALayer(), nullptr, true); + return new SurfaceControl(mScc, new NotALayer(), 1); } }; @@ -64,4 +64,4 @@ TEST_F(InvalidHandleTest, captureLayersInvalidHandle) { } // namespace android // TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" \ No newline at end of file +#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp index bb1f4328b5..9cac7c1723 100644 --- a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp +++ b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp @@ -51,6 +51,7 @@ using android::Hwc2::IComposer; using android::Hwc2::IComposerClient; using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; +using gui::LayerMetadata; struct TestableFpsListener : public gui::BnFpsListener { TestableFpsListener() {} @@ -153,7 +154,7 @@ TEST_F(FpsReporterTest, callsListeners) { mParent = createBufferStateLayer(); constexpr int32_t kTaskId = 12; LayerMetadata targetMetadata; - targetMetadata.setInt32(METADATA_TASK_ID, kTaskId); + targetMetadata.setInt32(gui::METADATA_TASK_ID, kTaskId); mTarget = createBufferStateLayer(targetMetadata); mChild = createBufferStateLayer(); mGrandChild = createBufferStateLayer(); @@ -188,7 +189,7 @@ TEST_F(FpsReporterTest, callsListeners) { TEST_F(FpsReporterTest, rateLimits) { const constexpr int32_t kTaskId = 12; LayerMetadata targetMetadata; - targetMetadata.setInt32(METADATA_TASK_ID, kTaskId); + targetMetadata.setInt32(gui::METADATA_TASK_ID, kTaskId); mTarget = createBufferStateLayer(targetMetadata); mFlinger.mutableCurrentState().layersSortedByZ.add(mTarget); diff --git a/services/surfaceflinger/tests/unittests/GameModeTest.cpp b/services/surfaceflinger/tests/unittests/GameModeTest.cpp index 981ca1d227..b79909ad9d 100644 --- a/services/surfaceflinger/tests/unittests/GameModeTest.cpp +++ b/services/surfaceflinger/tests/unittests/GameModeTest.cpp @@ -34,6 +34,8 @@ using testing::_; using testing::Mock; using testing::Return; using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; +using gui::GameMode; +using gui::LayerMetadata; class GameModeTest : public testing::Test { public: @@ -92,7 +94,7 @@ public: // Mocks the behavior of applying a transaction from WMShell void setGameModeMetadata(sp layer, GameMode gameMode) { - mLayerMetadata.setInt32(METADATA_GAME_MODE, static_cast(gameMode)); + mLayerMetadata.setInt32(gui::METADATA_GAME_MODE, static_cast(gameMode)); layer->setMetadata(mLayerMetadata); layer->setGameModeForTree(gameMode); } diff --git a/services/surfaceflinger/tests/unittests/LayerMetadataTest.cpp b/services/surfaceflinger/tests/unittests/LayerMetadataTest.cpp index 373fd74020..e6e02c1806 100644 --- a/services/surfaceflinger/tests/unittests/LayerMetadataTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerMetadataTest.cpp @@ -27,6 +27,8 @@ #include #include +using android::gui::LayerMetadata; + namespace android { namespace { @@ -113,4 +115,4 @@ TEST_F(LayerMetadataTest, merge) { } // 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/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp index f5e3b77ca6..669fa3a7f0 100644 --- a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp @@ -49,8 +49,7 @@ TEST(TransactionProtoParserTest, parse) { ComposerState s; if (i == 1) { layer.parentSurfaceControlForChild = - new SurfaceControl(SurfaceComposerClient::getDefault(), layerHandle, nullptr, - 42); + new SurfaceControl(SurfaceComposerClient::getDefault(), layerHandle, 42); } s.state = layer; t1.states.add(s); -- GitLab From 1b0c49f83281b9433a6758ceb16ac4bc17a885f8 Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Tue, 15 Mar 2022 19:18:21 -0700 Subject: [PATCH 0048/1310] Migrate bootFinished of ISurfaceComposer to AIDL And createDisplayEventConnection is migrated too. Bug: 221898546 Bug: 211009610 Test: atest libsurfaceflinger_unittest libgui_test SurfaceFlinger_test Change-Id: I2d0968262b86829b4cce13158446f1c85a4e55e4 --- libs/gui/DisplayEventDispatcher.cpp | 13 +++--- libs/gui/DisplayEventReceiver.cpp | 20 +++++---- libs/gui/ISurfaceComposer.cpp | 45 ------------------- .../aidl/android/gui/ISurfaceComposer.aidl | 25 +++++++++++ libs/gui/include/gui/DisplayEventDispatcher.h | 8 ++-- libs/gui/include/gui/DisplayEventReceiver.h | 14 ++++-- libs/gui/include/gui/ISurfaceComposer.h | 37 +++------------ libs/gui/tests/Surface_test.cpp | 15 ++++--- libs/nativedisplay/AChoreographer.cpp | 4 +- .../surfaceflinger/Scheduler/EventThread.cpp | 13 +++--- .../surfaceflinger/Scheduler/EventThread.h | 10 ++--- .../surfaceflinger/Scheduler/Scheduler.cpp | 4 +- services/surfaceflinger/Scheduler/Scheduler.h | 6 ++- services/surfaceflinger/SurfaceFlinger.cpp | 42 +++++++++++++---- services/surfaceflinger/SurfaceFlinger.h | 15 +++++-- .../tests/DisplayEventReceiver_test.cpp | 5 ++- .../tests/SetFrameRateOverride_test.cpp | 8 ++-- .../tests/fakehwc/SFFakeHwc_test.cpp | 5 ++- .../tests/unittests/EventThreadTest.cpp | 22 ++++----- .../tests/unittests/mock/MockEventThread.h | 3 +- 20 files changed, 159 insertions(+), 155 deletions(-) diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp index dfdce20438..501e69ade5 100644 --- a/libs/gui/DisplayEventDispatcher.cpp +++ b/libs/gui/DisplayEventDispatcher.cpp @@ -35,11 +35,14 @@ static const size_t EVENT_BUFFER_SIZE = 100; static constexpr nsecs_t WAITING_FOR_VSYNC_TIMEOUT = ms2ns(300); -DisplayEventDispatcher::DisplayEventDispatcher( - const sp& looper, ISurfaceComposer::VsyncSource vsyncSource, - ISurfaceComposer::EventRegistrationFlags eventRegistration) - : mLooper(looper), mReceiver(vsyncSource, eventRegistration), mWaitingForVsync(false), - mLastVsyncCount(0), mLastScheduleVsyncTime(0) { +DisplayEventDispatcher::DisplayEventDispatcher(const sp& looper, + gui::ISurfaceComposer::VsyncSource vsyncSource, + EventRegistrationFlags eventRegistration) + : mLooper(looper), + mReceiver(vsyncSource, eventRegistration), + mWaitingForVsync(false), + mLastVsyncCount(0), + mLastScheduleVsyncTime(0) { ALOGV("dispatcher %p ~ Initializing display event dispatcher.", this); } diff --git a/libs/gui/DisplayEventReceiver.cpp b/libs/gui/DisplayEventReceiver.cpp index bfb77699c0..c52fb6b7c3 100644 --- a/libs/gui/DisplayEventReceiver.cpp +++ b/libs/gui/DisplayEventReceiver.cpp @@ -19,10 +19,9 @@ #include #include -#include #include -#include +#include #include @@ -32,15 +31,20 @@ namespace android { // --------------------------------------------------------------------------- -DisplayEventReceiver::DisplayEventReceiver( - ISurfaceComposer::VsyncSource vsyncSource, - ISurfaceComposer::EventRegistrationFlags eventRegistration) { - sp sf(ComposerService::getComposerService()); +DisplayEventReceiver::DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource vsyncSource, + EventRegistrationFlags eventRegistration) { + sp sf(ComposerServiceAIDL::getComposerService()); if (sf != nullptr) { - mEventConnection = sf->createDisplayEventConnection(vsyncSource, eventRegistration); + mEventConnection = nullptr; + binder::Status status = + sf->createDisplayEventConnection(vsyncSource, + static_cast< + gui::ISurfaceComposer::EventRegistration>( + eventRegistration.get()), + &mEventConnection); if (mEventConnection != nullptr) { mDataChannel = std::make_unique(); - const auto status = mEventConnection->stealReceiveChannel(mDataChannel.get()); + status = mEventConnection->stealReceiveChannel(mDataChannel.get()); if (!status.isOk()) { ALOGE("stealReceiveChannel failed: %s", status.toString8().c_str()); mInitError = std::make_optional(status.transactionError()); diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index 54e50b50ac..af64b3bd32 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -108,35 +108,6 @@ public: data, &reply); } } - - void bootFinished() override { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); - remote()->transact(BnSurfaceComposer::BOOT_FINISHED, data, &reply); - } - - sp createDisplayEventConnection( - VsyncSource vsyncSource, EventRegistrationFlags eventRegistration) override { - Parcel data, reply; - sp result; - int err = data.writeInterfaceToken( - ISurfaceComposer::getInterfaceDescriptor()); - if (err != NO_ERROR) { - return result; - } - data.writeInt32(static_cast(vsyncSource)); - data.writeUint32(eventRegistration.get()); - err = remote()->transact( - BnSurfaceComposer::CREATE_DISPLAY_EVENT_CONNECTION, - data, &reply); - if (err != NO_ERROR) { - ALOGE("ISurfaceComposer::createDisplayEventConnection: error performing " - "transaction: %s (%d)", strerror(-err), -err); - return result; - } - result = interface_cast(reply.readStrongBinder()); - return result; - } }; // Out-of-line virtual method definition to trigger vtable emission in this @@ -215,22 +186,6 @@ status_t BnSurfaceComposer::onTransact( uncachedBuffer, hasListenerCallbacks, listenerCallbacks, transactionId); } - case BOOT_FINISHED: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - bootFinished(); - return NO_ERROR; - } - case CREATE_DISPLAY_EVENT_CONNECTION: { - CHECK_INTERFACE(ISurfaceComposer, data, reply); - auto vsyncSource = static_cast(data.readInt32()); - EventRegistrationFlags eventRegistration = - static_cast(data.readUint32()); - - sp connection( - createDisplayEventConnection(vsyncSource, eventRegistration)); - reply->writeStrongBinder(IInterface::asBinder(connection)); - return NO_ERROR; - } default: { return BBinder::onTransact(code, data, reply, flags); } diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 26b4e819ee..39833fe06b 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -30,6 +30,7 @@ import android.gui.DisplayStatInfo; import android.gui.DynamicDisplayInfo; import android.gui.FrameEvent; import android.gui.FrameStats; +import android.gui.IDisplayEventConnection; import android.gui.IFpsListener; import android.gui.IHdrLayerInfoListener; import android.gui.IRegionSamplingListener; @@ -47,6 +48,30 @@ import android.gui.StaticDisplayInfo; /** @hide */ interface ISurfaceComposer { + enum VsyncSource { + eVsyncSourceApp = 0, + eVsyncSourceSurfaceFlinger = 1 + } + + enum EventRegistration { + modeChanged = 1 << 0, + frameRateOverride = 1 << 1, + } + + /** + * Signal that we're done booting. + * Requires ACCESS_SURFACE_FLINGER permission + */ + // Note this must be the 1st method, so IBinder::FIRST_CALL_TRANSACTION + // is assigned, as it is called from Java by ActivityManagerService. + void bootFinished(); + + /** + * Create a display event connection + */ + @nullable IDisplayEventConnection createDisplayEventConnection(VsyncSource vsyncSource, + EventRegistration eventRegistration); + /** * Create a connection with SurfaceFlinger. */ diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h index a3425395bf..bf3a07b6b5 100644 --- a/libs/gui/include/gui/DisplayEventDispatcher.h +++ b/libs/gui/include/gui/DisplayEventDispatcher.h @@ -23,10 +23,10 @@ using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride; class DisplayEventDispatcher : public LooperCallback { public: - explicit DisplayEventDispatcher( - const sp& looper, - ISurfaceComposer::VsyncSource vsyncSource = ISurfaceComposer::eVsyncSourceApp, - ISurfaceComposer::EventRegistrationFlags eventRegistration = {}); + explicit DisplayEventDispatcher(const sp& looper, + gui::ISurfaceComposer::VsyncSource vsyncSource = + gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp, + EventRegistrationFlags eventRegistration = {}); status_t initialize(); void dispose(); diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h index cf7a4e5522..0f4907fcb9 100644 --- a/libs/gui/include/gui/DisplayEventReceiver.h +++ b/libs/gui/include/gui/DisplayEventReceiver.h @@ -20,20 +20,26 @@ #include #include +#include + #include #include #include +#include #include -#include #include +#include + // ---------------------------------------------------------------------------- namespace android { // ---------------------------------------------------------------------------- +using EventRegistrationFlags = ftl::Flags; + using gui::IDisplayEventConnection; using gui::ParcelableVsyncEventData; using gui::VsyncEventData; @@ -111,9 +117,9 @@ public: * To receive ModeChanged and/or FrameRateOverrides events specify this in * the constructor. Other events start being delivered immediately. */ - explicit DisplayEventReceiver( - ISurfaceComposer::VsyncSource vsyncSource = ISurfaceComposer::eVsyncSourceApp, - ISurfaceComposer::EventRegistrationFlags eventRegistration = {}); + explicit DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource vsyncSource = + gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp, + EventRegistrationFlags eventRegistration = {}); /* * ~DisplayEventReceiver severs the connection with SurfaceFlinger, new events diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index 7139e1bb93..1e85131386 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include @@ -94,8 +93,6 @@ class ISurfaceComposer: public IInterface { public: DECLARE_META_INTERFACE(SurfaceComposer) - static constexpr size_t MAX_LAYERS = 4096; - // flags for setTransactionState() enum { eSynchronous = 0x01, @@ -113,23 +110,6 @@ public: eOneWay = 0x20 }; - enum VsyncSource { - eVsyncSourceApp = 0, - eVsyncSourceSurfaceFlinger = 1 - }; - - enum class EventRegistration { - modeChanged = 1 << 0, - frameRateOverride = 1 << 1, - }; - - using EventRegistrationFlags = ftl::Flags; - - /* return an IDisplayEventConnection */ - virtual sp createDisplayEventConnection( - VsyncSource vsyncSource = eVsyncSourceApp, - EventRegistrationFlags eventRegistration = {}) = 0; - /* open/close transactions. requires ACCESS_SURFACE_FLINGER permission */ virtual status_t setTransactionState( const FrameTimelineInfo& frameTimelineInfo, const Vector& state, @@ -137,11 +117,6 @@ public: const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, const std::vector& listenerCallbacks, uint64_t transactionId) = 0; - - /* signal that we're done booting. - * Requires ACCESS_SURFACE_FLINGER permission - */ - virtual void bootFinished() = 0; }; // ---------------------------------------------------------------------------- @@ -152,12 +127,12 @@ public: // Note: BOOT_FINISHED must remain this value, it is called from // Java by ActivityManagerService. BOOT_FINISHED = IBinder::FIRST_CALL_TRANSACTION, - CREATE_CONNECTION, // Deprecated. Autogenerated by .aidl now. - GET_STATIC_DISPLAY_INFO, // Deprecated. Autogenerated by .aidl now. - CREATE_DISPLAY_EVENT_CONNECTION, - CREATE_DISPLAY, // Deprecated. Autogenerated by .aidl now. - DESTROY_DISPLAY, // Deprecated. Autogenerated by .aidl now. - GET_PHYSICAL_DISPLAY_TOKEN, // Deprecated. Autogenerated by .aidl now. + CREATE_CONNECTION, // Deprecated. Autogenerated by .aidl now. + GET_STATIC_DISPLAY_INFO, // Deprecated. Autogenerated by .aidl now. + CREATE_DISPLAY_EVENT_CONNECTION, // Deprecated. Autogenerated by .aidl now. + CREATE_DISPLAY, // Deprecated. Autogenerated by .aidl now. + DESTROY_DISPLAY, // Deprecated. Autogenerated by .aidl now. + GET_PHYSICAL_DISPLAY_TOKEN, // Deprecated. Autogenerated by .aidl now. SET_TRANSACTION_STATE, AUTHENTICATE_SURFACE, // Deprecated. Autogenerated by .aidl now. GET_SUPPORTED_FRAME_TIMESTAMPS, // Deprecated. Autogenerated by .aidl now. diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 4ab380c8b8..7eff3b318f 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -692,10 +692,6 @@ public: mSupportsPresent = supportsPresent; } - sp createDisplayEventConnection( - ISurfaceComposer::VsyncSource, ISurfaceComposer::EventRegistrationFlags) override { - return nullptr; - } status_t setTransactionState(const FrameTimelineInfo& /*frameTimelineInfo*/, const Vector& /*state*/, const Vector& /*displays*/, uint32_t /*flags*/, @@ -709,8 +705,6 @@ public: return NO_ERROR; } - void bootFinished() override {} - protected: IBinder* onAsBinder() override { return nullptr; } @@ -724,6 +718,15 @@ public: void setSupportsPresent(bool supportsPresent) { mSupportsPresent = supportsPresent; } + binder::Status bootFinished() override { return binder::Status::ok(); } + + binder::Status createDisplayEventConnection( + VsyncSource /*vsyncSource*/, EventRegistration /*eventRegistration*/, + sp* outConnection) override { + *outConnection = nullptr; + return binder::Status::ok(); + } + binder::Status createConnection(sp* outClient) override { *outClient = nullptr; return binder::Status::ok(); diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp index 8240b08085..539cbaa341 100644 --- a/libs/nativedisplay/AChoreographer.cpp +++ b/libs/nativedisplay/AChoreographer.cpp @@ -18,8 +18,8 @@ //#define LOG_NDEBUG 0 #include +#include #include -#include #include #include #include @@ -198,7 +198,7 @@ Choreographer* Choreographer::getForThread() { } Choreographer::Choreographer(const sp& looper) - : DisplayEventDispatcher(looper, ISurfaceComposer::VsyncSource::eVsyncSourceApp), + : DisplayEventDispatcher(looper, gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp), mLooper(looper), mThreadId(std::this_thread::get_id()) { std::lock_guard _l(gChoreographers.lock); diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index cbea77e8fb..5d9920866f 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -157,9 +157,9 @@ DisplayEventReceiver::Event makeFrameRateOverrideFlushEvent(PhysicalDisplayId di } // namespace -EventThreadConnection::EventThreadConnection( - EventThread* eventThread, uid_t callingUid, ResyncCallback resyncCallback, - ISurfaceComposer::EventRegistrationFlags eventRegistration) +EventThreadConnection::EventThreadConnection(EventThread* eventThread, uid_t callingUid, + ResyncCallback resyncCallback, + EventRegistrationFlags eventRegistration) : resyncCallback(std::move(resyncCallback)), mOwnerUid(callingUid), mEventRegistration(eventRegistration), @@ -283,8 +283,7 @@ void EventThread::setDuration(std::chrono::nanoseconds workDuration, } sp EventThread::createEventConnection( - ResyncCallback resyncCallback, - ISurfaceComposer::EventRegistrationFlags eventRegistration) const { + ResyncCallback resyncCallback, EventRegistrationFlags eventRegistration) const { return new EventThreadConnection(const_cast(this), IPCThreadState::self()->getCallingUid(), std::move(resyncCallback), eventRegistration); @@ -548,7 +547,7 @@ bool EventThread::shouldConsumeEvent(const DisplayEventReceiver::Event& event, case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE: { return connection->mEventRegistration.test( - ISurfaceComposer::EventRegistration::modeChanged); + gui::ISurfaceComposer::EventRegistration::modeChanged); } case DisplayEventReceiver::DISPLAY_EVENT_VSYNC: @@ -581,7 +580,7 @@ bool EventThread::shouldConsumeEvent(const DisplayEventReceiver::Event& event, [[fallthrough]]; case DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH: return connection->mEventRegistration.test( - ISurfaceComposer::EventRegistration::frameRateOverride); + gui::ISurfaceComposer::EventRegistration::frameRateOverride); default: return false; diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h index c406478c17..60ab633075 100644 --- a/services/surfaceflinger/Scheduler/EventThread.h +++ b/services/surfaceflinger/Scheduler/EventThread.h @@ -92,7 +92,7 @@ public: class EventThreadConnection : public gui::BnDisplayEventConnection { public: EventThreadConnection(EventThread*, uid_t callingUid, ResyncCallback, - ISurfaceComposer::EventRegistrationFlags eventRegistration = {}); + EventRegistrationFlags eventRegistration = {}); virtual ~EventThreadConnection(); virtual status_t postEvent(const DisplayEventReceiver::Event& event); @@ -107,7 +107,7 @@ public: VSyncRequest vsyncRequest = VSyncRequest::None; const uid_t mOwnerUid; - const ISurfaceComposer::EventRegistrationFlags mEventRegistration; + const EventRegistrationFlags mEventRegistration; private: virtual void onFirstRef(); @@ -122,8 +122,7 @@ public: virtual ~EventThread(); virtual sp createEventConnection( - ResyncCallback, - ISurfaceComposer::EventRegistrationFlags eventRegistration = {}) const = 0; + ResyncCallback, EventRegistrationFlags eventRegistration = {}) const = 0; // called before the screen is turned off from main thread virtual void onScreenReleased() = 0; @@ -170,8 +169,7 @@ public: ~EventThread(); sp createEventConnection( - ResyncCallback, - ISurfaceComposer::EventRegistrationFlags eventRegistration = {}) const override; + ResyncCallback, EventRegistrationFlags eventRegistration = {}) const override; status_t registerDisplayEventConnection(const sp& connection) override; void setVsyncRate(uint32_t rate, const sp& connection) override; diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 3aa0a5f15c..8300595150 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -208,12 +208,12 @@ ConnectionHandle Scheduler::createConnection(std::unique_ptr eventT } sp Scheduler::createConnectionInternal( - EventThread* eventThread, ISurfaceComposer::EventRegistrationFlags eventRegistration) { + EventThread* eventThread, EventRegistrationFlags eventRegistration) { return eventThread->createEventConnection([&] { resync(); }, eventRegistration); } sp Scheduler::createDisplayEventConnection( - ConnectionHandle handle, ISurfaceComposer::EventRegistrationFlags eventRegistration) { + ConnectionHandle handle, EventRegistrationFlags eventRegistration) { std::lock_guard lock(mConnectionsLock); RETURN_IF_INVALID_HANDLE(handle, nullptr); return createConnectionInternal(mConnections[handle].thread.get(), eventRegistration); diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 0c72124119..98c0eb3bf0 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -32,6 +32,8 @@ #include #pragma clang diagnostic pop // ignored "-Wconversion -Wextra" +#include + #include #include "EventThread.h" @@ -131,7 +133,7 @@ public: impl::EventThread::InterceptVSyncsCallback); sp createDisplayEventConnection( - ConnectionHandle, ISurfaceComposer::EventRegistrationFlags eventRegistration = {}); + ConnectionHandle, EventRegistrationFlags eventRegistration = {}); sp getEventConnection(ConnectionHandle); @@ -248,7 +250,7 @@ private: // Create a connection on the given EventThread. ConnectionHandle createConnection(std::unique_ptr); sp createConnectionInternal( - EventThread*, ISurfaceComposer::EventRegistrationFlags eventRegistration = {}); + EventThread*, EventRegistrationFlags eventRegistration = {}); // Update feature state machine to given state when corresponding timer resets or expires. void kernelIdleTimerCallback(TimerState) EXCLUDES(mRefreshRateConfigsLock); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 46b1a60a77..5afc997ab6 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -406,7 +406,7 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI property_get("ro.sf.blurs_are_expensive", value, "0"); mBlursAreExpensive = atoi(value); - const size_t defaultListSize = ISurfaceComposer::MAX_LAYERS; + const size_t defaultListSize = MAX_LAYERS; auto listSize = property_get_int32("debug.sf.max_igbp_list_size", int32_t(defaultListSize)); mMaxGraphicBufferProducerListSize = (listSize > 0) ? size_t(listSize) : defaultListSize; mGraphicBufferProducerListSizeLogThreshold = @@ -1789,10 +1789,11 @@ status_t SurfaceFlinger::getDisplayDecorationSupport( // ---------------------------------------------------------------------------- sp SurfaceFlinger::createDisplayEventConnection( - ISurfaceComposer::VsyncSource vsyncSource, - ISurfaceComposer::EventRegistrationFlags eventRegistration) { + gui::ISurfaceComposer::VsyncSource vsyncSource, EventRegistrationFlags eventRegistration) { const auto& handle = - vsyncSource == eVsyncSourceSurfaceFlinger ? mSfConnectionHandle : mAppConnectionHandle; + vsyncSource == gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger + ? mSfConnectionHandle + : mAppConnectionHandle; return mScheduler->createDisplayEventConnection(handle, eventRegistration); } @@ -3589,9 +3590,9 @@ bool SurfaceFlinger::latchBuffers() { status_t SurfaceFlinger::addClientLayer(const sp& client, const sp& handle, const sp& layer, const wp& parent, bool addToRoot, uint32_t* outTransformHint) { - if (mNumLayers >= ISurfaceComposer::MAX_LAYERS) { + if (mNumLayers >= MAX_LAYERS) { ALOGE("AddClientLayer failed, mNumLayers (%zu) >= MAX_LAYERS (%zu)", mNumLayers.load(), - ISurfaceComposer::MAX_LAYERS); + MAX_LAYERS); static_cast(mScheduler->schedule([=] { ALOGE("Dumping random sampling of on-screen layers: "); mDrawingState.traverse([&](Layer *layer) { @@ -5474,7 +5475,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { switch (static_cast(code)) { // These methods should at minimum make sure that the client requested // access to SF. - case BOOT_FINISHED: case GET_HDR_CAPABILITIES: case GET_AUTO_LOW_LATENCY_MODE_SUPPORT: case GET_GAME_CONTENT_TYPE_SUPPORT: @@ -5490,8 +5490,6 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { } return OK; } - // Used by apps to hook Choreographer to SurfaceFlinger. - case CREATE_DISPLAY_EVENT_CONNECTION: // The following calls are currently used by clients that do not // request necessary permissions. However, they do not expose any secret // information, so it is OK to pass them. @@ -5505,6 +5503,9 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { // This is not sensitive information, so should not require permission control. return OK; } + case BOOT_FINISHED: + // Used by apps to hook Choreographer to SurfaceFlinger. + case CREATE_DISPLAY_EVENT_CONNECTION: case CREATE_CONNECTION: case CREATE_DISPLAY: case DESTROY_DISPLAY: @@ -7211,6 +7212,29 @@ bool SurfaceFlinger::commitCreatedLayers() { // gui::ISurfaceComposer +binder::Status SurfaceComposerAIDL::bootFinished() { + status_t status = checkAccessPermission(); + if (status != OK) { + return binderStatusFromStatusT(status); + } + mFlinger->bootFinished(); + return binder::Status::ok(); +} + +binder::Status SurfaceComposerAIDL::createDisplayEventConnection( + VsyncSource vsyncSource, EventRegistration eventRegistration, + sp* outConnection) { + sp conn = + mFlinger->createDisplayEventConnection(vsyncSource, eventRegistration); + if (conn == nullptr) { + *outConnection = nullptr; + return binderStatusFromStatusT(BAD_VALUE); + } else { + *outConnection = conn; + return binder::Status::ok(); + } +} + binder::Status SurfaceComposerAIDL::createConnection(sp* outClient) { const sp client = new Client(mFlinger); if (client->initCheck() == NO_ERROR) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 353df24f7f..4bb692dda0 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -516,6 +516,8 @@ private: // Maximum allowed number of display frames that can be set through backdoor static const int MAX_ALLOWED_DISPLAY_FRAMES = 2048; + static const size_t MAX_LAYERS = 4096; + // Implements IBinder. status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) override; status_t dump(int fd, const Vector& args) override { return priorityDump(fd, args); } @@ -554,11 +556,12 @@ private: const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, const std::vector& listenerCallbacks, uint64_t transactionId) override; - void bootFinished() override; + void bootFinished(); virtual status_t getSupportedFrameTimestamps(std::vector* outSupported) const; sp createDisplayEventConnection( - ISurfaceComposer::VsyncSource vsyncSource = eVsyncSourceApp, - ISurfaceComposer::EventRegistrationFlags eventRegistration = {}) override; + gui::ISurfaceComposer::VsyncSource vsyncSource = + gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp, + EventRegistrationFlags eventRegistration = {}); status_t captureDisplay(const DisplayCaptureArgs&, const sp&); status_t captureDisplay(DisplayId, const sp&); @@ -1174,7 +1177,7 @@ private: float mGlobalSaturationFactor = 1.0f; mat4 mClientColorMatrix; - size_t mMaxGraphicBufferProducerListSize = ISurfaceComposer::MAX_LAYERS; + size_t mMaxGraphicBufferProducerListSize = MAX_LAYERS; // If there are more GraphicBufferProducers tracked by SurfaceFlinger than // this threshold, then begin logging. size_t mGraphicBufferProducerListSizeLogThreshold = @@ -1445,6 +1448,10 @@ class SurfaceComposerAIDL : public gui::BnSurfaceComposer { public: SurfaceComposerAIDL(sp sf) : mFlinger(std::move(sf)) {} + binder::Status bootFinished() override; + binder::Status createDisplayEventConnection( + VsyncSource vsyncSource, EventRegistration eventRegistration, + sp* outConnection) override; binder::Status createConnection(sp* outClient) override; binder::Status createDisplay(const std::string& displayName, bool secure, sp* outDisplay) override; diff --git a/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp b/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp index 0e54664f77..0df7e2fafa 100644 --- a/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp +++ b/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp @@ -36,7 +36,8 @@ TEST_F(DisplayEventReceiverTest, getLatestVsyncEventData) { EXPECT_GT(vsyncEventData.frameTimelines[0].deadlineTimestamp, now) << "Deadline timestamp should be greater than frame time"; for (size_t i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) { - EXPECT_NE(FrameTimelineInfo::INVALID_VSYNC_ID, vsyncEventData.frameTimelines[i].vsyncId); + EXPECT_NE(gui::FrameTimelineInfo::INVALID_VSYNC_ID, + vsyncEventData.frameTimelines[i].vsyncId); EXPECT_GT(vsyncEventData.frameTimelines[i].expectedPresentationTime, vsyncEventData.frameTimelines[i].deadlineTimestamp) << "Expected vsync timestamp should be greater than deadline"; @@ -51,4 +52,4 @@ TEST_F(DisplayEventReceiverTest, getLatestVsyncEventData) { } } -} // namespace android \ No newline at end of file +} // namespace android diff --git a/services/surfaceflinger/tests/SetFrameRateOverride_test.cpp b/services/surfaceflinger/tests/SetFrameRateOverride_test.cpp index 4efec7738a..e43ef952d6 100644 --- a/services/surfaceflinger/tests/SetFrameRateOverride_test.cpp +++ b/services/surfaceflinger/tests/SetFrameRateOverride_test.cpp @@ -14,9 +14,9 @@ * limitations under the License. */ +#include #include #include -#include #include #include #include @@ -24,12 +24,14 @@ namespace android { namespace { using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride; +using gui::ISurfaceComposer; class SetFrameRateOverrideTest : public ::testing::Test { protected: void SetUp() override { - const ISurfaceComposer::VsyncSource vsyncSource = ISurfaceComposer::eVsyncSourceApp; - const ISurfaceComposer::EventRegistrationFlags eventRegistration = { + const ISurfaceComposer::VsyncSource vsyncSource = + ISurfaceComposer::VsyncSource::eVsyncSourceApp; + const EventRegistrationFlags eventRegistration = { ISurfaceComposer::EventRegistration::frameRateOverride}; mDisplayEventReceiver = diff --git a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp b/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp index 67df8b8d61..00845d793f 100644 --- a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp +++ b/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp @@ -245,8 +245,9 @@ protected: mComposerClient = new SurfaceComposerClient; ASSERT_EQ(NO_ERROR, mComposerClient->initCheck()); - mReceiver.reset(new DisplayEventReceiver(ISurfaceComposer::eVsyncSourceApp, - ISurfaceComposer::EventRegistration::modeChanged)); + mReceiver.reset( + new DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp, + gui::ISurfaceComposer::EventRegistration::modeChanged)); mLooper = new Looper(false); mLooper->addFd(mReceiver->getFd(), 0, ALOOPER_EVENT_INPUT, processDisplayEvents, this); } diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp index c033af8645..7ee04b0466 100644 --- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp +++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp @@ -72,7 +72,7 @@ protected: public: MockEventThreadConnection(impl::EventThread* eventThread, uid_t callingUid, ResyncCallback&& resyncCallback, - ISurfaceComposer::EventRegistrationFlags eventRegistration) + EventRegistrationFlags eventRegistration) : EventThreadConnection(eventThread, callingUid, std::move(resyncCallback), eventRegistration) {} MOCK_METHOD1(postEvent, status_t(const DisplayEventReceiver::Event& event)); @@ -85,10 +85,9 @@ protected: ~EventThreadTest() override; void createThread(std::unique_ptr); - sp createConnection( - ConnectionEventRecorder& recorder, - ISurfaceComposer::EventRegistrationFlags eventRegistration = {}, - uid_t ownerUid = mConnectionUid); + sp createConnection(ConnectionEventRecorder& recorder, + EventRegistrationFlags eventRegistration = {}, + uid_t ownerUid = mConnectionUid); void expectVSyncSetEnabledCallReceived(bool expectedState); void expectVSyncSetDurationCallReceived(std::chrono::nanoseconds expectedDuration, @@ -149,11 +148,12 @@ EventThreadTest::EventThreadTest() { .WillRepeatedly(Invoke(mVSyncSetDurationCallRecorder.getInvocable())); createThread(std::move(vsyncSource)); - mConnection = createConnection(mConnectionEventCallRecorder, - ISurfaceComposer::EventRegistration::modeChanged | - ISurfaceComposer::EventRegistration::frameRateOverride); + mConnection = + createConnection(mConnectionEventCallRecorder, + gui::ISurfaceComposer::EventRegistration::modeChanged | + gui::ISurfaceComposer::EventRegistration::frameRateOverride); mThrottledConnection = createConnection(mThrottledConnectionEventCallRecorder, - ISurfaceComposer::EventRegistration::modeChanged, + gui::ISurfaceComposer::EventRegistration::modeChanged, mThrottledConnectionUid); // A display must be connected for VSYNC events to be delivered. @@ -190,8 +190,8 @@ void EventThreadTest::createThread(std::unique_ptr source) { } sp EventThreadTest::createConnection( - ConnectionEventRecorder& recorder, - ISurfaceComposer::EventRegistrationFlags eventRegistration, uid_t ownerUid) { + ConnectionEventRecorder& recorder, EventRegistrationFlags eventRegistration, + uid_t ownerUid) { sp connection = new MockEventThreadConnection(mThread.get(), ownerUid, mResyncCallRecorder.getInvocable(), eventRegistration); diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h index c5ca86a651..d30dc42cbb 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h +++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h @@ -28,8 +28,7 @@ public: ~EventThread() override; MOCK_CONST_METHOD2(createEventConnection, - sp(ResyncCallback, - ISurfaceComposer::EventRegistrationFlags)); + sp(ResyncCallback, EventRegistrationFlags)); MOCK_METHOD0(onScreenReleased, void()); MOCK_METHOD0(onScreenAcquired, void()); MOCK_METHOD2(onHotplugReceived, void(PhysicalDisplayId, bool)); -- GitLab From 845fee957a26d3f10754df8b77dc868f6e8d2508 Mon Sep 17 00:00:00 2001 From: Ian Elliott Date: Thu, 21 Apr 2022 12:41:03 -0600 Subject: [PATCH 0049/1310] Change Android platform for when ANGLE is default Allow adb commands to select ANGLE vs the legacy GLES driver regardless of which driver is the system (default) driver, and to select between different versions of ANGLE if multiple versions are installed (e.g. the built-in system driver vs. an updated APK). Test: logcat Bug: 224558229 Change-Id: I83e59002b67181fda4da6070b80ffa595bc25ac4 --- libs/graphicsenv/GraphicsEnv.cpp | 52 +++++++++++++++++-- .../include/graphicsenv/GraphicsEnv.h | 15 +++++- opengl/libs/EGL/Loader.cpp | 47 +++++++++++------ 3 files changed, 92 insertions(+), 22 deletions(-) diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp index 7f0cac5d4f..e4fbd6fe92 100644 --- a/libs/graphicsenv/GraphicsEnv.cpp +++ b/libs/graphicsenv/GraphicsEnv.cpp @@ -364,26 +364,51 @@ bool GraphicsEnv::shouldUseAngle() { return (mUseAngle == YES) ? true : false; } +bool GraphicsEnv::forceLegacyDriver() { + // Make sure we are init'ed + if (mAngleAppName.empty()) { + ALOGV("NOT SETUP YET."); + return false; + } + + return (mAngleIsSystemDriver == YES && mUseAngle == NO) ? true : false; +} + +std::string GraphicsEnv::getLegacySuffix() { + return mLegacyDriverSuffix; +} + void GraphicsEnv::updateUseAngle() { mUseAngle = NO; const char* ANGLE_PREFER_ANGLE = "angle"; + const char* ANGLE_PREFER_LEGACY = "legacy"; + // The following is a deprecated version of "legacy" const char* ANGLE_PREFER_NATIVE = "native"; mUseAngle = NO; if (mAngleDeveloperOptIn == ANGLE_PREFER_ANGLE) { - ALOGV("User set \"Developer Options\" to force the use of ANGLE"); + ALOGI("Using ANGLE, the %s GLES driver for package '%s'", + mAngleIsSystemDriver == YES ? "system" : "optional", mAngleAppName.c_str()); mUseAngle = YES; - } else if (mAngleDeveloperOptIn == ANGLE_PREFER_NATIVE) { - ALOGV("User set \"Developer Options\" to force the use of Native"); + } else if (mAngleDeveloperOptIn == ANGLE_PREFER_LEGACY || + mAngleDeveloperOptIn == ANGLE_PREFER_NATIVE) { + ALOGI("Using the (%s) Legacy GLES driver driver for package '%s'", + mAngleIsSystemDriver == YES ? "optional" : "system", mAngleAppName.c_str()); } else { ALOGV("User set invalid \"Developer Options\": '%s'", mAngleDeveloperOptIn.c_str()); } } void GraphicsEnv::setAngleInfo(const std::string path, const std::string appName, - const std::string developerOptIn, + const bool angleIsSystemDriver, const std::string developerOptIn, const std::vector eglFeatures) { + // Set whether ANGLE is the system driver: + mAngleIsSystemDriver = angleIsSystemDriver ? YES : NO; + + // Note: Given the current logic and lack of the old rules file processing, + // there seems to be little chance that mUseAngle != UNKNOWN. Leave this + // for now, even though it seems outdated. if (mUseAngle != UNKNOWN) { // We've already figured out an answer for this app, so just return. ALOGV("Already evaluated the rules file for '%s': use ANGLE = %s", appName.c_str(), @@ -404,6 +429,25 @@ void GraphicsEnv::setAngleInfo(const std::string path, const std::string appName updateUseAngle(); } +void GraphicsEnv::setLegacyDriverInfo(const std::string appName, const bool angleIsSystemDriver, + const std::string legacyDriverName) { + ALOGV("setting ANGLE app name to '%s'", appName.c_str()); + mAngleAppName = appName; + + // Force the use of the legacy driver instead of ANGLE + const char* ANGLE_PREFER_LEGACY = "legacy"; + mAngleDeveloperOptIn = ANGLE_PREFER_LEGACY; + ALOGV("setting ANGLE application opt-in to 'legacy'"); + + // Set whether ANGLE is the system driver: + mAngleIsSystemDriver = angleIsSystemDriver ? YES : NO; + + mLegacyDriverSuffix = legacyDriverName; + + // Update the current status of whether we should use ANGLE or not + updateUseAngle(); +} + void GraphicsEnv::setLayerPaths(NativeLoaderNamespace* appNamespace, const std::string layerPaths) { if (mLayerPaths.empty()) { mLayerPaths = layerPaths; diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h index 56d1139f57..95117ba0f1 100644 --- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h +++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h @@ -91,17 +91,26 @@ public: bool shouldUseAngle(std::string appName); // Check if this app process should use ANGLE. bool shouldUseAngle(); + // If should use legacy driver instead of a system ANGLE driver + bool forceLegacyDriver(); // Set a search path for loading ANGLE libraries. The path is a list of // directories separated by ':'. A directory can be contained in a zip file // (libraries must be stored uncompressed and page aligned); such elements // in the search path must have a '!' after the zip filename, e.g. // /system/app/ANGLEPrebuilt/ANGLEPrebuilt.apk!/lib/arm64-v8a - void setAngleInfo(const std::string path, const std::string appName, std::string devOptIn, + void setAngleInfo(const std::string path, const std::string appName, + const bool angleIsSystemDriver, std::string devOptIn, const std::vector eglFeatures); + // Set the state so that the legacy driver will be used, and in case ANGLE + // is the system driver, provide the name of the legacy driver. + void setLegacyDriverInfo(const std::string appName, const bool angleIsSystemDriver, + const std::string legacyDriverName); // Get the ANGLE driver namespace. android_namespace_t* getAngleNamespace(); // Get the app name for ANGLE debug message. std::string& getAngleAppName(); + // Get the legacy driver's suffix name. + std::string getLegacySuffix(); const std::vector& getAngleEglFeatures(); @@ -156,6 +165,10 @@ private: std::string mAngleDeveloperOptIn; // ANGLE EGL features; std::vector mAngleEglFeatures; + // ANGLE is System Driver flag. + UseAngle mAngleIsSystemDriver = UNKNOWN; + // Legacy driver name to use when ANGLE is the system driver. + std::string mLegacyDriverSuffix; // Use ANGLE flag. UseAngle mUseAngle = UNKNOWN; // Vulkan debug layers libs. diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp index 76fd7f0f3f..500797807d 100644 --- a/opengl/libs/EGL/Loader.cpp +++ b/opengl/libs/EGL/Loader.cpp @@ -216,8 +216,13 @@ void* Loader::open(egl_connection_t* cnx) return cnx->dso; } - // Firstly, try to load ANGLE driver. - driver_t* hnd = attempt_to_load_angle(cnx); + // Firstly, try to load ANGLE driver, unless we know that we shouldn't. + bool forceLegacyDriver = android::GraphicsEnv::getInstance().forceLegacyDriver(); + driver_t* hnd = nullptr; + if (!forceLegacyDriver) { + hnd = attempt_to_load_angle(cnx); + } + if (!hnd) { // Secondly, try to load from driver apk. hnd = attempt_to_load_updated_driver(cnx); @@ -230,21 +235,29 @@ void* Loader::open(egl_connection_t* cnx) LOG_ALWAYS_FATAL("couldn't find an OpenGL ES implementation from %s", android::GraphicsEnv::getInstance().getDriverPath().c_str()); } - // Finally, try to load system driver, start by searching for the library name appended by - // the system properties of the GLES userspace driver in both locations. - // i.e.: - // libGLES_${prop}.so, or: - // libEGL_${prop}.so, libGLESv1_CM_${prop}.so, libGLESv2_${prop}.so - for (auto key : HAL_SUBNAME_KEY_PROPERTIES) { - auto prop = base::GetProperty(key, ""); - if (prop.empty()) { - continue; - } - hnd = attempt_to_load_system_driver(cnx, prop.c_str(), true); - if (hnd) { - break; - } else if (strcmp(key, DRIVER_SUFFIX_PROPERTY) == 0) { - failToLoadFromDriverSuffixProperty = true; + // Finally, try to load system driver. If ANGLE is the system driver + // (i.e. we are forcing the legacy system driver instead of ANGLE), use + // the driver suffix that was passed down from above. + if (forceLegacyDriver) { + std::string suffix = android::GraphicsEnv::getInstance().getLegacySuffix(); + hnd = attempt_to_load_system_driver(cnx, suffix.c_str(), true); + } else { + // Start by searching for the library name appended by the system + // properties of the GLES userspace driver in both locations. + // i.e.: + // libGLES_${prop}.so, or: + // libEGL_${prop}.so, libGLESv1_CM_${prop}.so, libGLESv2_${prop}.so + for (auto key : HAL_SUBNAME_KEY_PROPERTIES) { + auto prop = base::GetProperty(key, ""); + if (prop.empty()) { + continue; + } + hnd = attempt_to_load_system_driver(cnx, prop.c_str(), true); + if (hnd) { + break; + } else if (strcmp(key, DRIVER_SUFFIX_PROPERTY) == 0) { + failToLoadFromDriverSuffixProperty = true; + } } } } -- GitLab From 2e62815e6a40ca52c004abbfd2095aee22796085 Mon Sep 17 00:00:00 2001 From: Simon Bowden Date: Mon, 25 Apr 2022 15:49:42 +0000 Subject: [PATCH 0050/1310] Revert "Change Android platform for when ANGLE is default" Revert "Change Android platform for when ANGLE is default" Revert submission 17653617-DisableANGLE1 Reason for revert: b/230126358: candidate for test breakage on cf_x86_64_phone-userdebug Reverted Changes: I2446b18e9:Change Android platform for when ANGLE is default I83e59002b:Change Android platform for when ANGLE is default Change-Id: I3b8724abd6eb249c3060fffe0334622cdafdae89 --- libs/graphicsenv/GraphicsEnv.cpp | 52 ++----------------- .../include/graphicsenv/GraphicsEnv.h | 15 +----- opengl/libs/EGL/Loader.cpp | 47 ++++++----------- 3 files changed, 22 insertions(+), 92 deletions(-) diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp index e4fbd6fe92..7f0cac5d4f 100644 --- a/libs/graphicsenv/GraphicsEnv.cpp +++ b/libs/graphicsenv/GraphicsEnv.cpp @@ -364,51 +364,26 @@ bool GraphicsEnv::shouldUseAngle() { return (mUseAngle == YES) ? true : false; } -bool GraphicsEnv::forceLegacyDriver() { - // Make sure we are init'ed - if (mAngleAppName.empty()) { - ALOGV("NOT SETUP YET."); - return false; - } - - return (mAngleIsSystemDriver == YES && mUseAngle == NO) ? true : false; -} - -std::string GraphicsEnv::getLegacySuffix() { - return mLegacyDriverSuffix; -} - void GraphicsEnv::updateUseAngle() { mUseAngle = NO; const char* ANGLE_PREFER_ANGLE = "angle"; - const char* ANGLE_PREFER_LEGACY = "legacy"; - // The following is a deprecated version of "legacy" const char* ANGLE_PREFER_NATIVE = "native"; mUseAngle = NO; if (mAngleDeveloperOptIn == ANGLE_PREFER_ANGLE) { - ALOGI("Using ANGLE, the %s GLES driver for package '%s'", - mAngleIsSystemDriver == YES ? "system" : "optional", mAngleAppName.c_str()); + ALOGV("User set \"Developer Options\" to force the use of ANGLE"); mUseAngle = YES; - } else if (mAngleDeveloperOptIn == ANGLE_PREFER_LEGACY || - mAngleDeveloperOptIn == ANGLE_PREFER_NATIVE) { - ALOGI("Using the (%s) Legacy GLES driver driver for package '%s'", - mAngleIsSystemDriver == YES ? "optional" : "system", mAngleAppName.c_str()); + } else if (mAngleDeveloperOptIn == ANGLE_PREFER_NATIVE) { + ALOGV("User set \"Developer Options\" to force the use of Native"); } else { ALOGV("User set invalid \"Developer Options\": '%s'", mAngleDeveloperOptIn.c_str()); } } void GraphicsEnv::setAngleInfo(const std::string path, const std::string appName, - const bool angleIsSystemDriver, const std::string developerOptIn, + const std::string developerOptIn, const std::vector eglFeatures) { - // Set whether ANGLE is the system driver: - mAngleIsSystemDriver = angleIsSystemDriver ? YES : NO; - - // Note: Given the current logic and lack of the old rules file processing, - // there seems to be little chance that mUseAngle != UNKNOWN. Leave this - // for now, even though it seems outdated. if (mUseAngle != UNKNOWN) { // We've already figured out an answer for this app, so just return. ALOGV("Already evaluated the rules file for '%s': use ANGLE = %s", appName.c_str(), @@ -429,25 +404,6 @@ void GraphicsEnv::setAngleInfo(const std::string path, const std::string appName updateUseAngle(); } -void GraphicsEnv::setLegacyDriverInfo(const std::string appName, const bool angleIsSystemDriver, - const std::string legacyDriverName) { - ALOGV("setting ANGLE app name to '%s'", appName.c_str()); - mAngleAppName = appName; - - // Force the use of the legacy driver instead of ANGLE - const char* ANGLE_PREFER_LEGACY = "legacy"; - mAngleDeveloperOptIn = ANGLE_PREFER_LEGACY; - ALOGV("setting ANGLE application opt-in to 'legacy'"); - - // Set whether ANGLE is the system driver: - mAngleIsSystemDriver = angleIsSystemDriver ? YES : NO; - - mLegacyDriverSuffix = legacyDriverName; - - // Update the current status of whether we should use ANGLE or not - updateUseAngle(); -} - void GraphicsEnv::setLayerPaths(NativeLoaderNamespace* appNamespace, const std::string layerPaths) { if (mLayerPaths.empty()) { mLayerPaths = layerPaths; diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h index 95117ba0f1..56d1139f57 100644 --- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h +++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h @@ -91,26 +91,17 @@ public: bool shouldUseAngle(std::string appName); // Check if this app process should use ANGLE. bool shouldUseAngle(); - // If should use legacy driver instead of a system ANGLE driver - bool forceLegacyDriver(); // Set a search path for loading ANGLE libraries. The path is a list of // directories separated by ':'. A directory can be contained in a zip file // (libraries must be stored uncompressed and page aligned); such elements // in the search path must have a '!' after the zip filename, e.g. // /system/app/ANGLEPrebuilt/ANGLEPrebuilt.apk!/lib/arm64-v8a - void setAngleInfo(const std::string path, const std::string appName, - const bool angleIsSystemDriver, std::string devOptIn, + void setAngleInfo(const std::string path, const std::string appName, std::string devOptIn, const std::vector eglFeatures); - // Set the state so that the legacy driver will be used, and in case ANGLE - // is the system driver, provide the name of the legacy driver. - void setLegacyDriverInfo(const std::string appName, const bool angleIsSystemDriver, - const std::string legacyDriverName); // Get the ANGLE driver namespace. android_namespace_t* getAngleNamespace(); // Get the app name for ANGLE debug message. std::string& getAngleAppName(); - // Get the legacy driver's suffix name. - std::string getLegacySuffix(); const std::vector& getAngleEglFeatures(); @@ -165,10 +156,6 @@ private: std::string mAngleDeveloperOptIn; // ANGLE EGL features; std::vector mAngleEglFeatures; - // ANGLE is System Driver flag. - UseAngle mAngleIsSystemDriver = UNKNOWN; - // Legacy driver name to use when ANGLE is the system driver. - std::string mLegacyDriverSuffix; // Use ANGLE flag. UseAngle mUseAngle = UNKNOWN; // Vulkan debug layers libs. diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp index 500797807d..76fd7f0f3f 100644 --- a/opengl/libs/EGL/Loader.cpp +++ b/opengl/libs/EGL/Loader.cpp @@ -216,13 +216,8 @@ void* Loader::open(egl_connection_t* cnx) return cnx->dso; } - // Firstly, try to load ANGLE driver, unless we know that we shouldn't. - bool forceLegacyDriver = android::GraphicsEnv::getInstance().forceLegacyDriver(); - driver_t* hnd = nullptr; - if (!forceLegacyDriver) { - hnd = attempt_to_load_angle(cnx); - } - + // Firstly, try to load ANGLE driver. + driver_t* hnd = attempt_to_load_angle(cnx); if (!hnd) { // Secondly, try to load from driver apk. hnd = attempt_to_load_updated_driver(cnx); @@ -235,29 +230,21 @@ void* Loader::open(egl_connection_t* cnx) LOG_ALWAYS_FATAL("couldn't find an OpenGL ES implementation from %s", android::GraphicsEnv::getInstance().getDriverPath().c_str()); } - // Finally, try to load system driver. If ANGLE is the system driver - // (i.e. we are forcing the legacy system driver instead of ANGLE), use - // the driver suffix that was passed down from above. - if (forceLegacyDriver) { - std::string suffix = android::GraphicsEnv::getInstance().getLegacySuffix(); - hnd = attempt_to_load_system_driver(cnx, suffix.c_str(), true); - } else { - // Start by searching for the library name appended by the system - // properties of the GLES userspace driver in both locations. - // i.e.: - // libGLES_${prop}.so, or: - // libEGL_${prop}.so, libGLESv1_CM_${prop}.so, libGLESv2_${prop}.so - for (auto key : HAL_SUBNAME_KEY_PROPERTIES) { - auto prop = base::GetProperty(key, ""); - if (prop.empty()) { - continue; - } - hnd = attempt_to_load_system_driver(cnx, prop.c_str(), true); - if (hnd) { - break; - } else if (strcmp(key, DRIVER_SUFFIX_PROPERTY) == 0) { - failToLoadFromDriverSuffixProperty = true; - } + // Finally, try to load system driver, start by searching for the library name appended by + // the system properties of the GLES userspace driver in both locations. + // i.e.: + // libGLES_${prop}.so, or: + // libEGL_${prop}.so, libGLESv1_CM_${prop}.so, libGLESv2_${prop}.so + for (auto key : HAL_SUBNAME_KEY_PROPERTIES) { + auto prop = base::GetProperty(key, ""); + if (prop.empty()) { + continue; + } + hnd = attempt_to_load_system_driver(cnx, prop.c_str(), true); + if (hnd) { + break; + } else if (strcmp(key, DRIVER_SUFFIX_PROPERTY) == 0) { + failToLoadFromDriverSuffixProperty = true; } } } -- GitLab From 9eb4e698f70a6b0a55008671adb57d6052e3ca1c Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 27 Apr 2022 13:19:15 +0000 Subject: [PATCH 0051/1310] Address ghost spots that show up after touchscreen is re-enabled When touches are cancelled as a result of a touchscreen being disabled, the pointers that were down when the touchscreen was disabled show up as touch spots briefly while a new pointer goes down after the touchscreen is enabled. Remove these ghost touches. Bug: 216718625 Test: manual, see bug Change-Id: I4fa381da9ca0e285dff4b405cad644fcea840d9e --- services/inputflinger/reader/mapper/TouchInputMapper.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index c1934ffbbf..637b1cb263 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -1657,9 +1657,8 @@ void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) { dispatchPointerUsage(when, readTime, policyFlags, pointerUsage); } else { - updateTouchSpots(); - if (!mCurrentMotionAborted) { + updateTouchSpots(); dispatchButtonRelease(when, readTime, policyFlags); dispatchHoverExit(when, readTime, policyFlags); dispatchTouches(when, readTime, policyFlags); -- GitLab From 8ac14eb66361ac301cf3b86dad2d88866005d1cc Mon Sep 17 00:00:00 2001 From: ramindani Date: Tue, 26 Apr 2022 20:20:57 +0000 Subject: [PATCH 0052/1310] Return NO_ERROR for setFrameRate We were returning BAD_VALUE for this function and in old code because of some dead code we though BAD_VALUE is correct but the result should be NO_ERROR Note: This code is not used in the framework. BUG: 229965137 Test: Test with the ExoPlayer demo app. and with https: //b.corp.google.com/issues/229965137#comment11 Change-Id: I6c1eb31c71057be1c755b0ab0fe63850e40e99ca --- libs/gui/Surface.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index 7a2615f439..e8aac2fb6f 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -2627,12 +2627,13 @@ void Surface::ProducerListenerProxy::onBuffersDiscarded(const std::vectoronBuffersDiscarded(discardedBufs); } -status_t Surface::setFrameRate(float /*frameRate*/, int8_t /*compatibility*/, - int8_t /*changeFrameRateStrategy*/) { - ATRACE_CALL(); - ALOGV("Surface::setFrameRate"); - // ISurfaceComposer no longer supports setFrameRate - return BAD_VALUE; +[[deprecated]] status_t Surface::setFrameRate(float /*frameRate*/, int8_t /*compatibility*/, + int8_t /*changeFrameRateStrategy*/) { + ALOGI("Surface::setFrameRate is deprecated, setFrameRate hint is dropped as destination is not " + "SurfaceFlinger"); + // ISurfaceComposer no longer supports setFrameRate, we will return NO_ERROR when the api is + // called to avoid apps crashing, as BAD_VALUE can generate fatal exception in apps. + return NO_ERROR; } status_t Surface::setFrameTimelineInfo(const FrameTimelineInfo& /*frameTimelineInfo*/) { -- GitLab From 38fae2ad25efa26f4a3173ee0b8867bbb655ea00 Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Thu, 17 Mar 2022 14:21:39 +0000 Subject: [PATCH 0053/1310] [API Review] Make changes to documentation for getActionButton and getClassification APIs Test: atest android.view.cts.MotionEventTest Bug: 224565087 Change-Id: I23e7941b3372e2eaafbcc136460ec09c94c3ccbf --- include/android/input.h | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/include/android/input.h b/include/android/input.h index fb5e204450..38b27bc587 100644 --- a/include/android/input.h +++ b/include/android/input.h @@ -810,7 +810,7 @@ enum { /** * Constants that identify different gesture classification types. */ -enum { +enum AMotionClassification : uint32_t { /** * Classification constant: None. * @@ -820,7 +820,8 @@ enum { /** * Classification constant: Ambiguous gesture. * - * The user's intent with respect to the current event stream is not yet determined. + * The user's intent with respect to the current event stream is not yet determined. Events + * starting in AMBIGUOUS_GESTURE will eventually resolve into either DEEP_PRESS or NONE. * Gestural actions, such as scrolling, should be inhibited until the classification resolves * to another value or the event stream ends. */ @@ -1357,8 +1358,17 @@ float AMotionEvent_getHistoricalAxisValue(const AInputEvent* motion_event, * Get the action button for the motion event. Returns a valid action button when the * event is associated with a button press or button release action. For other actions * the return value is undefined. + * + * @see #AMOTION_EVENT_BUTTON_PRIMARY + * @see #AMOTION_EVENT_BUTTON_SECONDARY + * @see #AMOTION_EVENT_BUTTON_TERTIARY + * @see #AMOTION_EVENT_BUTTON_BACK + * @see #AMOTION_EVENT_BUTTON_FORWARD + * @see #AMOTION_EVENT_BUTTON_STYLUS_PRIMARY + * @see #AMOTION_EVENT_BUTTON_STYLUS_SECONDARY */ -int32_t AMotionEvent_getActionButton(const AInputEvent* motion_event); +int32_t AMotionEvent_getActionButton(const AInputEvent* motion_event) + __INTRODUCED_IN(__ANDROID_API_T__); /** * Returns the classification for the current gesture. @@ -1368,7 +1378,8 @@ int32_t AMotionEvent_getActionButton(const AInputEvent* motion_event); * @see #AMOTION_EVENT_CLASSIFICATION_AMBIGUOUS_GESTURE * @see #AMOTION_EVENT_CLASSIFICATION_DEEP_PRESS */ -int32_t AMotionEvent_getClassification(const AInputEvent* motion_event); +int32_t AMotionEvent_getClassification(const AInputEvent* motion_event) + __INTRODUCED_IN(__ANDROID_API_T__); /** * Creates a native AInputEvent* object that is a copy of the specified Java -- GitLab From bd9374ea77aa237565204764b0b555df5d911c02 Mon Sep 17 00:00:00 2001 From: Kevin Lubick Date: Fri, 22 Apr 2022 15:09:19 -0400 Subject: [PATCH 0054/1310] Fix Skia Includes Needed to land https://skia-review.googlesource.com/c/skia/+/532456 Change-Id: I5234878a75587cc36d882d39e9b0db99f462fd8d --- libs/renderengine/skia/SkiaGLRenderEngine.cpp | 1 + libs/renderengine/skia/SkiaGLRenderEngine.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index 8943db0294..dec89b9c2f 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h index af70bd4176..59eeb62b56 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.h +++ b/libs/renderengine/skia/SkiaGLRenderEngine.h @@ -43,6 +43,8 @@ class SkData; +struct SkPoint3; + namespace android { namespace renderengine { namespace skia { -- GitLab From 5735a328e01329d7e7c24a668fa88f9bc8e124d7 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 11 Apr 2022 17:23:34 +0000 Subject: [PATCH 0055/1310] Reland: Change input injection security model This reverts commit 4df80f58c42d56facdc79ffa5bcba041300784e9. Changes from revert: None Bug: 207667844 Bug: 194952792 Test: TBD Change-Id: I1bf9bd08fa9974ec54bbdddb27892afdda407331 --- .../android/os/InputEventInjectionResult.aidl | 5 +- .../benchmarks/InputDispatcher_benchmarks.cpp | 22 +- .../dispatcher/InjectionState.cpp | 5 +- .../inputflinger/dispatcher/InjectionState.h | 5 +- .../dispatcher/InputDispatcher.cpp | 168 ++++++------ .../inputflinger/dispatcher/InputDispatcher.h | 5 +- .../include/InputDispatcherInterface.h | 10 +- .../include/InputDispatcherPolicyInterface.h | 9 - .../tests/InputDispatcher_test.cpp | 249 ++++++++++++++---- 9 files changed, 309 insertions(+), 169 deletions(-) diff --git a/libs/input/android/os/InputEventInjectionResult.aidl b/libs/input/android/os/InputEventInjectionResult.aidl index 34f10ec00e..3bc7068f3c 100644 --- a/libs/input/android/os/InputEventInjectionResult.aidl +++ b/libs/input/android/os/InputEventInjectionResult.aidl @@ -29,9 +29,8 @@ enum InputEventInjectionResult { /* Injection succeeded. */ SUCCEEDED = 0, - /* Injection failed because the injector did not have permission to inject - * into the application with input focus. */ - PERMISSION_DENIED = 1, + /* Injection failed because the injected event did not target the appropriate window. */ + TARGET_MISMATCH = 1, /* Injection failed because there were no available input targets. */ FAILED = 2, diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp index 32eec291cb..a2e60c4e6f 100644 --- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp +++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp @@ -31,11 +31,11 @@ using android::os::InputEventInjectionSync; namespace android::inputdispatcher { // An arbitrary device id. -static const int32_t DEVICE_ID = 1; +constexpr int32_t DEVICE_ID = 1; -// An arbitrary injector pid / uid pair that has permission to inject events. -static const int32_t INJECTOR_PID = 999; -static const int32_t INJECTOR_UID = 1001; +// The default pid and uid for windows created by the test. +constexpr int32_t WINDOW_PID = 999; +constexpr int32_t WINDOW_UID = 1001; static constexpr std::chrono::duration INJECT_EVENT_TIMEOUT = 5s; static constexpr std::chrono::nanoseconds DISPATCHING_TIMEOUT = 100ms; @@ -108,8 +108,6 @@ private: void pokeUserActivity(nsecs_t, int32_t, int32_t) override {} - bool checkInjectEventsPermissionNonReentrant(int32_t, int32_t) override { return false; } - void onPointerDownOutsideFocus(const sp& newToken) override {} void setPointerCapture(const PointerCaptureRequest&) override {} @@ -196,8 +194,8 @@ public: mInfo.globalScaleFactor = 1.0; mInfo.touchableRegion.clear(); mInfo.addTouchableRegion(mFrame); - mInfo.ownerPid = INJECTOR_PID; - mInfo.ownerUid = INJECTOR_UID; + mInfo.ownerPid = WINDOW_PID; + mInfo.ownerUid = WINDOW_UID; mInfo.displayId = ADISPLAY_ID_DEFAULT; } @@ -310,14 +308,14 @@ static void benchmarkInjectMotion(benchmark::State& state) { for (auto _ : state) { MotionEvent event = generateMotionEvent(); // Send ACTION_DOWN - dispatcher.injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, - InputEventInjectionSync::NONE, INJECT_EVENT_TIMEOUT, + dispatcher.injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + INJECT_EVENT_TIMEOUT, POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER); // Send ACTION_UP event.setAction(AMOTION_EVENT_ACTION_UP); - dispatcher.injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, - InputEventInjectionSync::NONE, INJECT_EVENT_TIMEOUT, + dispatcher.injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + INJECT_EVENT_TIMEOUT, POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER); window->consumeEvent(); diff --git a/services/inputflinger/dispatcher/InjectionState.cpp b/services/inputflinger/dispatcher/InjectionState.cpp index c8024a61a9..c2d3ad6b9d 100644 --- a/services/inputflinger/dispatcher/InjectionState.cpp +++ b/services/inputflinger/dispatcher/InjectionState.cpp @@ -20,10 +20,9 @@ namespace android::inputdispatcher { -InjectionState::InjectionState(int32_t injectorPid, int32_t injectorUid) +InjectionState::InjectionState(const std::optional& targetUid) : refCount(1), - injectorPid(injectorPid), - injectorUid(injectorUid), + targetUid(targetUid), injectionResult(android::os::InputEventInjectionResult::PENDING), injectionIsAsync(false), pendingForegroundDispatches(0) {} diff --git a/services/inputflinger/dispatcher/InjectionState.h b/services/inputflinger/dispatcher/InjectionState.h index 0bfafb1d19..90cf150ac3 100644 --- a/services/inputflinger/dispatcher/InjectionState.h +++ b/services/inputflinger/dispatcher/InjectionState.h @@ -27,13 +27,12 @@ namespace inputdispatcher { struct InjectionState { mutable int32_t refCount; - int32_t injectorPid; - int32_t injectorUid; + std::optional targetUid; android::os::InputEventInjectionResult injectionResult; // initially PENDING bool injectionIsAsync; // set to true if injection is not waiting for the result int32_t pendingForegroundDispatches; // the number of foreground dispatches in progress - InjectionState(int32_t injectorPid, int32_t injectorUid); + explicit InjectionState(const std::optional& targetUid); void release(); private: diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 1cc4589eb9..b696707c1d 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -578,6 +578,27 @@ bool isWindowOwnedBy(const sp& windowHandle, int32_t pid, int3 return false; } +// Checks targeted injection using the window's owner's uid. +// Returns an empty string if an entry can be sent to the given window, or an error message if the +// entry is a targeted injection whose uid target doesn't match the window owner. +std::optional verifyTargetedInjection(const sp& window, + const EventEntry& entry) { + if (entry.injectionState == nullptr || !entry.injectionState->targetUid) { + // The event was not injected, or the injected event does not target a window. + return {}; + } + const int32_t uid = *entry.injectionState->targetUid; + if (window == nullptr) { + return StringPrintf("No valid window target for injection into uid %d.", uid); + } + if (entry.injectionState->targetUid != window->getInfo()->ownerUid) { + return StringPrintf("Injected event targeted at uid %d would be dispatched to window '%s' " + "owned by uid %d.", + uid, window->getName().c_str(), window->getInfo()->ownerUid); + } + return {}; +} + } // namespace // --- InputDispatcher --- @@ -1036,6 +1057,8 @@ bool InputDispatcher::enqueueInboundEventLocked(std::unique_ptr newE switch (entry.type) { case EventEntry::Type::KEY: { + LOG_ALWAYS_FATAL_IF((entry.policyFlags & POLICY_FLAG_TRUSTED) == 0, + "Unexpected untrusted event."); // Optimize app switch latency. // If the application takes too long to catch up then we drop all events preceding // the app switch key. @@ -1073,6 +1096,8 @@ bool InputDispatcher::enqueueInboundEventLocked(std::unique_ptr newE } case EventEntry::Type::MOTION: { + LOG_ALWAYS_FATAL_IF((entry.policyFlags & POLICY_FLAG_TRUSTED) == 0, + "Unexpected untrusted event."); if (shouldPruneInboundQueueLocked(static_cast(entry))) { mNextUnblockedEvent = mInboundQueue.back(); needWake = true; @@ -1718,8 +1743,7 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr< } setInjectionResult(*entry, injectionResult); - if (injectionResult == InputEventInjectionResult::PERMISSION_DENIED) { - ALOGW("Permission denied, dropping the motion (isPointer=%s)", toString(isPointerEvent)); + if (injectionResult == InputEventInjectionResult::TARGET_MISMATCH) { return true; } if (injectionResult != InputEventInjectionResult::SUCCEEDED) { @@ -1976,9 +2000,10 @@ InputEventInjectionResult InputDispatcher::findFocusedWindowTargetsLocked( // we have a valid, non-null focused window resetNoFocusedWindowTimeoutLocked(); - // Check permissions. - if (!checkInjectionPermission(focusedWindowHandle, entry.injectionState)) { - return InputEventInjectionResult::PERMISSION_DENIED; + // Verify targeted injection. + if (const auto err = verifyTargetedInjection(focusedWindowHandle, entry); err) { + ALOGW("Dropping injected event: %s", (*err).c_str()); + return InputEventInjectionResult::TARGET_MISMATCH; } if (focusedWindowHandle->getInfo()->inputConfig.test( @@ -2044,11 +2069,6 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( nsecs_t currentTime, const MotionEntry& entry, std::vector& inputTargets, nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) { ATRACE_CALL(); - enum InjectionPermission { - INJECTION_PERMISSION_UNKNOWN, - INJECTION_PERMISSION_GRANTED, - INJECTION_PERMISSION_DENIED - }; // For security reasons, we defer updating the touch state until we are sure that // event injection will be allowed. @@ -2058,7 +2078,6 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( // Update the touch state as needed based on the properties of the touch event. InputEventInjectionResult injectionResult = InputEventInjectionResult::PENDING; - InjectionPermission injectionPermission = INJECTION_PERMISSION_UNKNOWN; sp newHoverWindowHandle(mLastHoverWindowHandle); sp newTouchedWindowHandle; @@ -2107,7 +2126,7 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( "in display %" PRId32, displayId); // TODO: test multiple simultaneous input streams. - injectionResult = InputEventInjectionResult::PERMISSION_DENIED; + injectionResult = InputEventInjectionResult::FAILED; switchedDevice = false; wrongDevice = true; goto Failed; @@ -2140,6 +2159,14 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle(); } + // Verify targeted injection. + if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) { + ALOGW("Dropping injected touch event: %s", (*err).c_str()); + injectionResult = os::InputEventInjectionResult::TARGET_MISMATCH; + newTouchedWindowHandle = nullptr; + goto Failed; + } + // Figure out whether splitting will be allowed for this window. if (newTouchedWindowHandle != nullptr) { if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) { @@ -2183,6 +2210,11 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( for (const sp& windowHandle : newTouchedWindows) { const WindowInfo& info = *windowHandle->getInfo(); + // Skip spy window targets that are not valid for targeted injection. + if (const auto err = verifyTargetedInjection(windowHandle, entry); err) { + continue; + } + if (info.inputConfig.test(WindowInfo::InputConfig::PAUSE_DISPATCHING)) { ALOGI("Not sending touch event to %s because it is paused", windowHandle->getName().c_str()); @@ -2276,6 +2308,14 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( newTouchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, isStylus); + // Verify targeted injection. + if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) { + ALOGW("Dropping injected event: %s", (*err).c_str()); + injectionResult = os::InputEventInjectionResult::TARGET_MISMATCH; + newTouchedWindowHandle = nullptr; + goto Failed; + } + // Drop touch events if requested by input feature if (newTouchedWindowHandle != nullptr && shouldDropInput(entry, newTouchedWindowHandle)) { @@ -2367,19 +2407,26 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( goto Failed; } - // Check permission to inject into all touched foreground windows. - if (std::any_of(tempTouchState.windows.begin(), tempTouchState.windows.end(), - [this, &entry](const TouchedWindow& touchedWindow) { - return (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) != 0 && - !checkInjectionPermission(touchedWindow.windowHandle, - entry.injectionState); - })) { - injectionResult = InputEventInjectionResult::PERMISSION_DENIED; - injectionPermission = INJECTION_PERMISSION_DENIED; - goto Failed; + // Ensure that all touched windows are valid for injection. + if (entry.injectionState != nullptr) { + std::string errs; + for (const TouchedWindow& touchedWindow : tempTouchState.windows) { + if (touchedWindow.targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) { + // Allow ACTION_OUTSIDE events generated by targeted injection to be + // dispatched to any uid, since the coords will be zeroed out later. + continue; + } + const auto err = verifyTargetedInjection(touchedWindow.windowHandle, entry); + if (err) errs += "\n - " + *err; + } + if (!errs.empty()) { + ALOGW("Dropping targeted injection: At least one touched window is not owned by uid " + "%d:%s", + *entry.injectionState->targetUid, errs.c_str()); + injectionResult = InputEventInjectionResult::TARGET_MISMATCH; + goto Failed; + } } - // Permission granted to inject into all touched foreground windows. - injectionPermission = INJECTION_PERMISSION_GRANTED; // Check whether windows listening for outside touches are owned by the same UID. If it is // set the policy flag that we will not reveal coordinate information to this window. @@ -2445,19 +2492,6 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( tempTouchState.filterNonAsIsTouchWindows(); Failed: - // Check injection permission once and for all. - if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) { - if (checkInjectionPermission(nullptr, entry.injectionState)) { - injectionPermission = INJECTION_PERMISSION_GRANTED; - } else { - injectionPermission = INJECTION_PERMISSION_DENIED; - } - } - - if (injectionPermission != INJECTION_PERMISSION_GRANTED) { - return injectionResult; - } - // Update final pieces of touch state if the injector had permission. if (!wrongDevice) { if (switchedDevice) { @@ -2655,26 +2689,6 @@ void InputDispatcher::addGlobalMonitoringTargetsLocked(std::vector& } } -bool InputDispatcher::checkInjectionPermission(const sp& windowHandle, - const InjectionState* injectionState) { - if (injectionState && - (windowHandle == nullptr || - windowHandle->getInfo()->ownerUid != injectionState->injectorUid) && - !hasInjectionPermission(injectionState->injectorPid, injectionState->injectorUid)) { - if (windowHandle != nullptr) { - ALOGW("Permission denied: injecting event from pid %d uid %d to window %s " - "owned by uid %d", - injectionState->injectorPid, injectionState->injectorUid, - windowHandle->getName().c_str(), windowHandle->getInfo()->ownerUid); - } else { - ALOGW("Permission denied: injecting event from pid %d uid %d", - injectionState->injectorPid, injectionState->injectorUid); - } - return false; - } - return true; -} - /** * Indicate whether one window handle should be considered as obscuring * another window handle. We only check a few preconditions. Actually @@ -4193,20 +4207,20 @@ void InputDispatcher::notifyPointerCaptureChanged(const NotifyPointerCaptureChan } } -InputEventInjectionResult InputDispatcher::injectInputEvent( - const InputEvent* event, int32_t injectorPid, int32_t injectorUid, - InputEventInjectionSync syncMode, std::chrono::milliseconds timeout, uint32_t policyFlags) { +InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* event, + std::optional targetUid, + InputEventInjectionSync syncMode, + std::chrono::milliseconds timeout, + uint32_t policyFlags) { if (DEBUG_INBOUND_EVENT_DETAILS) { - ALOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, " - "syncMode=%d, timeout=%lld, policyFlags=0x%08x", - event->getType(), injectorPid, injectorUid, syncMode, timeout.count(), policyFlags); + ALOGD("injectInputEvent - eventType=%d, targetUid=%s, syncMode=%d, timeout=%lld, " + "policyFlags=0x%08x", + event->getType(), targetUid ? std::to_string(*targetUid).c_str() : "none", syncMode, + timeout.count(), policyFlags); } nsecs_t endTime = now() + std::chrono::duration_cast(timeout).count(); - policyFlags |= POLICY_FLAG_INJECTED; - if (hasInjectionPermission(injectorPid, injectorUid)) { - policyFlags |= POLICY_FLAG_TRUSTED; - } + policyFlags |= POLICY_FLAG_INJECTED | POLICY_FLAG_TRUSTED; // For all injected events, set device id = VIRTUAL_KEYBOARD_ID. The only exception is events // that have gone through the InputFilter. If the event passed through the InputFilter, assign @@ -4347,7 +4361,7 @@ InputEventInjectionResult InputDispatcher::injectInputEvent( return InputEventInjectionResult::FAILED; } - InjectionState* injectionState = new InjectionState(injectorPid, injectorUid); + InjectionState* injectionState = new InjectionState(targetUid); if (syncMode == InputEventInjectionSync::NONE) { injectionState->injectionIsAsync = true; } @@ -4419,8 +4433,7 @@ InputEventInjectionResult InputDispatcher::injectInputEvent( } // release lock if (DEBUG_INJECTION) { - ALOGD("injectInputEvent - Finished with result %d. injectorPid=%d, injectorUid=%d", - injectionResult, injectorPid, injectorUid); + ALOGD("injectInputEvent - Finished with result %d.", injectionResult); } return injectionResult; @@ -4459,19 +4472,12 @@ std::unique_ptr InputDispatcher::verifyInputEvent(const Inpu return result; } -bool InputDispatcher::hasInjectionPermission(int32_t injectorPid, int32_t injectorUid) { - return injectorUid == 0 || - mPolicy->checkInjectEventsPermissionNonReentrant(injectorPid, injectorUid); -} - void InputDispatcher::setInjectionResult(EventEntry& entry, InputEventInjectionResult injectionResult) { InjectionState* injectionState = entry.injectionState; if (injectionState) { if (DEBUG_INJECTION) { - ALOGD("Setting input event injection result to %d. " - "injectorPid=%d, injectorUid=%d", - injectionResult, injectionState->injectorPid, injectionState->injectorUid); + ALOGD("Setting input event injection result to %d.", injectionResult); } if (injectionState->injectionIsAsync && !(entry.policyFlags & POLICY_FLAG_FILTERED)) { @@ -4480,12 +4486,12 @@ void InputDispatcher::setInjectionResult(EventEntry& entry, case InputEventInjectionResult::SUCCEEDED: ALOGV("Asynchronous input event injection succeeded."); break; + case InputEventInjectionResult::TARGET_MISMATCH: + ALOGV("Asynchronous input event injection target mismatch."); + break; case InputEventInjectionResult::FAILED: ALOGW("Asynchronous input event injection failed."); break; - case InputEventInjectionResult::PERMISSION_DENIED: - ALOGW("Asynchronous input event injection permission denied."); - break; case InputEventInjectionResult::TIMED_OUT: ALOGW("Asynchronous input event injection timed out."); break; diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index f3dac19fa4..d9677eb633 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -104,7 +104,7 @@ public: void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override; android::os::InputEventInjectionResult injectInputEvent( - const InputEvent* event, int32_t injectorPid, int32_t injectorUid, + const InputEvent* event, std::optional targetUid, android::os::InputEventInjectionSync syncMode, std::chrono::milliseconds timeout, uint32_t policyFlags) override; @@ -278,7 +278,6 @@ private: // Event injection and synchronization. std::condition_variable mInjectionResultAvailable; - bool hasInjectionPermission(int32_t injectorPid, int32_t injectorUid); void setInjectionResult(EventEntry& entry, android::os::InputEventInjectionResult injectionResult); void transformMotionEntryForInjectionLocked(MotionEntry&, @@ -554,8 +553,6 @@ private: void addGlobalMonitoringTargetsLocked(std::vector& inputTargets, int32_t displayId) REQUIRES(mLock); void pokeUserActivityLocked(const EventEntry& eventEntry) REQUIRES(mLock); - bool checkInjectionPermission(const sp& windowHandle, - const InjectionState* injectionState); // Enqueue a drag event if needed, and update the touch state. // Uses findTouchedWindowTargetsLocked to make the decision void addDragEventLocked(const MotionEntry& entry) REQUIRES(mLock); diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h index d7bc5fbfe9..67fed8b4f1 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h @@ -68,10 +68,16 @@ public: * input injection to proceed. * Returns one of the INPUT_EVENT_INJECTION_XXX constants. * - * This method may be called on any thread (usually by the input manager). + * If a targetUid is provided, InputDispatcher will only consider injecting the input event into + * windows owned by the provided uid. If the input event is targeted at a window that is not + * owned by the provided uid, input injection will fail. If no targetUid is provided, the input + * event will be dispatched as-is. + * + * This method may be called on any thread (usually by the input manager). The caller must + * perform all necessary permission checks prior to injecting events. */ virtual android::os::InputEventInjectionResult injectInputEvent( - const InputEvent* event, int32_t injectorPid, int32_t injectorUid, + const InputEvent* event, std::optional targetUid, android::os::InputEventInjectionSync syncMode, std::chrono::milliseconds timeout, uint32_t policyFlags) = 0; diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h index de0b6da884..575b3d7059 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h @@ -125,15 +125,6 @@ public: /* Poke user activity for an event dispatched to a window. */ virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) = 0; - /* Checks whether a given application pid/uid has permission to inject input events - * into other applications. - * - * This method is special in that its implementation promises to be non-reentrant and - * is safe to call while holding other locks. (Most other methods make no such guarantees!) - */ - virtual bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid, - int32_t injectorUid) = 0; - /* Notifies the policy that a pointer down event has occurred outside the current focused * window. * diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index a167271c4d..e22ae2bbf3 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -45,10 +45,10 @@ namespace android::inputdispatcher { using namespace ftl::flag_operators; // An arbitrary time value. -static const nsecs_t ARBITRARY_TIME = 1234; +static constexpr nsecs_t ARBITRARY_TIME = 1234; // An arbitrary device id. -static const int32_t DEVICE_ID = 1; +static constexpr int32_t DEVICE_ID = 1; // An arbitrary display id. static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; @@ -61,9 +61,12 @@ static constexpr int32_t POINTER_2_DOWN = static constexpr int32_t POINTER_1_UP = AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); -// An arbitrary injector pid / uid pair that has permission to inject events. -static const int32_t INJECTOR_PID = 999; -static const int32_t INJECTOR_UID = 1001; +// The default pid and uid for windows created by the test. +static constexpr int32_t WINDOW_PID = 999; +static constexpr int32_t WINDOW_UID = 1001; + +// The default policy flags to use for event injection by tests. +static constexpr uint32_t DEFAULT_POLICY_FLAGS = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER; // An arbitrary pid of the gesture monitor window static constexpr int32_t MONITOR_PID = 2001; @@ -472,10 +475,6 @@ private: void pokeUserActivity(nsecs_t, int32_t, int32_t) override {} - bool checkInjectEventsPermissionNonReentrant(int32_t pid, int32_t uid) override { - return pid == INJECTOR_PID && uid == INJECTOR_UID; - } - void onPointerDownOutsideFocus(const sp& newToken) override { std::scoped_lock lock(mLock); mOnPointerDownToken = newToken; @@ -560,8 +559,8 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) { /*action*/ -1, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, - InputEventInjectionSync::NONE, 0ms, 0)) + mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + 0ms, 0)) << "Should reject key events with undefined action."; // Rejects ACTION_MULTIPLE since it is not supported despite being defined in the API. @@ -569,8 +568,8 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) { INVALID_HMAC, AKEY_EVENT_ACTION_MULTIPLE, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, - InputEventInjectionSync::NONE, 0ms, 0)) + mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + 0ms, 0)) << "Should reject key events with ACTION_MULTIPLE."; } @@ -599,8 +598,8 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, - InputEventInjectionSync::NONE, 0ms, 0)) + mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + 0ms, 0)) << "Should reject motion events with undefined action."; // Rejects pointer down with invalid index. @@ -611,8 +610,8 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, - InputEventInjectionSync::NONE, 0ms, 0)) + mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + 0ms, 0)) << "Should reject motion events with pointer down index too large."; event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC, @@ -623,8 +622,8 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { identityTransform, ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, - InputEventInjectionSync::NONE, 0ms, 0)) + mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + 0ms, 0)) << "Should reject motion events with pointer down index too small."; // Rejects pointer up with invalid index. @@ -635,8 +634,8 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, - InputEventInjectionSync::NONE, 0ms, 0)) + mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + 0ms, 0)) << "Should reject motion events with pointer up index too large."; event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC, @@ -647,8 +646,8 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { identityTransform, ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, - InputEventInjectionSync::NONE, 0ms, 0)) + mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + 0ms, 0)) << "Should reject motion events with pointer up index too small."; // Rejects motion events with invalid number of pointers. @@ -659,8 +658,8 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 0, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, - InputEventInjectionSync::NONE, 0ms, 0)) + mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + 0ms, 0)) << "Should reject motion events with 0 pointers."; event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC, @@ -670,8 +669,8 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ MAX_POINTERS + 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, - InputEventInjectionSync::NONE, 0ms, 0)) + mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + 0ms, 0)) << "Should reject motion events with more than MAX_POINTERS pointers."; // Rejects motion events with invalid pointer ids. @@ -683,8 +682,8 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, - InputEventInjectionSync::NONE, 0ms, 0)) + mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + 0ms, 0)) << "Should reject motion events with pointer ids less than 0."; pointerProperties[0].id = MAX_POINTER_ID + 1; @@ -695,8 +694,8 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, - InputEventInjectionSync::NONE, 0ms, 0)) + mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + 0ms, 0)) << "Should reject motion events with pointer ids greater than MAX_POINTER_ID."; // Rejects motion events with duplicate pointer ids. @@ -709,8 +708,8 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 2, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, - InputEventInjectionSync::NONE, 0ms, 0)) + mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + 0ms, 0)) << "Should reject motion events with duplicate pointer ids."; } @@ -1013,8 +1012,8 @@ public: mInfo.globalScaleFactor = 1.0; mInfo.touchableRegion.clear(); mInfo.addTouchableRegion(Rect(0, 0, WIDTH, HEIGHT)); - mInfo.ownerPid = INJECTOR_PID; - mInfo.ownerUid = INJECTOR_UID; + mInfo.ownerPid = WINDOW_PID; + mInfo.ownerUid = WINDOW_UID; mInfo.displayId = displayId; mInfo.inputConfig = WindowInfo::InputConfig::DEFAULT; } @@ -1296,7 +1295,8 @@ static InputEventInjectionResult injectKey( int32_t displayId = ADISPLAY_ID_NONE, InputEventInjectionSync syncMode = InputEventInjectionSync::WAIT_FOR_RESULT, std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT, - bool allowKeyRepeat = true) { + bool allowKeyRepeat = true, std::optional targetUid = {}, + uint32_t policyFlags = DEFAULT_POLICY_FLAGS) { KeyEvent event; nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); @@ -1305,13 +1305,11 @@ static InputEventInjectionResult injectKey( INVALID_HMAC, action, /* flags */ 0, AKEYCODE_A, KEY_A, AMETA_NONE, repeatCount, currentTime, currentTime); - int32_t policyFlags = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER; if (!allowKeyRepeat) { policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT; } // Inject event until dispatch out. - return dispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, syncMode, - injectionTimeout, policyFlags); + return dispatcher->injectInputEvent(&event, targetUid, syncMode, injectionTimeout, policyFlags); } static InputEventInjectionResult injectKeyDown(const std::unique_ptr& dispatcher, @@ -1454,10 +1452,10 @@ private: static InputEventInjectionResult injectMotionEvent( const std::unique_ptr& dispatcher, const MotionEvent& event, std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT, - InputEventInjectionSync injectionMode = InputEventInjectionSync::WAIT_FOR_RESULT) { - return dispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, injectionMode, - injectionTimeout, - POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER); + InputEventInjectionSync injectionMode = InputEventInjectionSync::WAIT_FOR_RESULT, + std::optional targetUid = {}, uint32_t policyFlags = DEFAULT_POLICY_FLAGS) { + return dispatcher->injectInputEvent(&event, targetUid, injectionMode, injectionTimeout, + policyFlags); } static InputEventInjectionResult injectMotionEvent( @@ -1467,7 +1465,8 @@ static InputEventInjectionResult injectMotionEvent( AMOTION_EVENT_INVALID_CURSOR_POSITION}, std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT, InputEventInjectionSync injectionMode = InputEventInjectionSync::WAIT_FOR_RESULT, - nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC)) { + nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC), + std::optional targetUid = {}, uint32_t policyFlags = DEFAULT_POLICY_FLAGS) { MotionEvent event = MotionEventBuilder(action, source) .displayId(displayId) .eventTime(eventTime) @@ -1479,7 +1478,8 @@ static InputEventInjectionResult injectMotionEvent( .build(); // Inject event until dispatch out. - return injectMotionEvent(dispatcher, event, injectionTimeout, injectionMode); + return injectMotionEvent(dispatcher, event, injectionTimeout, injectionMode, targetUid, + policyFlags); } static InputEventInjectionResult injectMotionDown( @@ -3574,8 +3574,8 @@ TEST_F(InputDispatcherTest, DisplayRemoved) { * FLAG_WINDOW_IS_PARTIALLY_OBSCURED. */ TEST_F(InputDispatcherTest, SlipperyWindow_SetsFlagPartiallyObscured) { - constexpr int32_t SLIPPERY_PID = INJECTOR_PID + 1; - constexpr int32_t SLIPPERY_UID = INJECTOR_UID + 1; + constexpr int32_t SLIPPERY_PID = WINDOW_PID + 1; + constexpr int32_t SLIPPERY_UID = WINDOW_UID + 1; std::shared_ptr application = std::make_shared(); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); @@ -4102,7 +4102,7 @@ protected: const int32_t additionalPolicyFlags = POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_DISABLE_KEY_REPEAT; ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, + mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, policyFlags | additionalPolicyFlags)); @@ -4137,7 +4137,7 @@ protected: const int32_t additionalPolicyFlags = POLICY_FLAG_PASS_TO_USER; ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, + mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, policyFlags | additionalPolicyFlags)); @@ -4644,7 +4644,7 @@ TEST_F(InputDispatcherSingleWindowAnr, StaleKeyEventDoesNotAnr) { const int32_t policyFlags = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER; InputEventInjectionResult result = - mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, + mDispatcher->injectInputEvent(&event, {} /* targetUid */, InputEventInjectionSync::WAIT_FOR_RESULT, INJECT_EVENT_TIMEOUT, policyFlags); ASSERT_EQ(InputEventInjectionResult::FAILED, result) @@ -6444,8 +6444,8 @@ protected: mWindow->consumeFocusEvent(true); // Set initial touch mode to InputDispatcher::kDefaultInTouchMode. - if (mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, INJECTOR_PID, - INJECTOR_UID, /* hasPermission */ true)) { + if (mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, WINDOW_PID, + WINDOW_UID, /* hasPermission */ true)) { mWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode); mSecondWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode); } @@ -7076,4 +7076,149 @@ TEST_F(InputDispatcherStylusInterceptorTest, StylusHandwritingScenario) { window->assertNoEvents(); } +struct User { + int32_t mPid; + int32_t mUid; + uint32_t mPolicyFlags{DEFAULT_POLICY_FLAGS}; + std::unique_ptr& mDispatcher; + + User(std::unique_ptr& dispatcher, int32_t pid, int32_t uid) + : mPid(pid), mUid(uid), mDispatcher(dispatcher) {} + + InputEventInjectionResult injectTargetedMotion(int32_t action) const { + return injectMotionEvent(mDispatcher, action, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {100, 200}, + {AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION}, + INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT, + systemTime(SYSTEM_TIME_MONOTONIC), {mUid}, mPolicyFlags); + } + + InputEventInjectionResult injectTargetedKey(int32_t action) const { + return inputdispatcher::injectKey(mDispatcher, action, 0 /* repeatCount*/, ADISPLAY_ID_NONE, + InputEventInjectionSync::WAIT_FOR_RESULT, + INJECT_EVENT_TIMEOUT, false /*allowKeyRepeat*/, {mUid}, + mPolicyFlags); + } + + sp createWindow() const { + std::shared_ptr overlayApplication = + std::make_shared(); + sp window = new FakeWindowHandle(overlayApplication, mDispatcher, + "Owned Window", ADISPLAY_ID_DEFAULT); + window->setOwnerInfo(mPid, mUid); + return window; + } +}; + +using InputDispatcherTargetedInjectionTest = InputDispatcherTest; + +TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoOwnedWindow) { + auto owner = User(mDispatcher, 10, 11); + auto window = owner.createWindow(); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, + owner.injectTargetedMotion(AMOTION_EVENT_ACTION_DOWN)); + window->consumeMotionDown(); + + setFocusedWindow(window); + window->consumeFocusEvent(true); + + EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, + owner.injectTargetedKey(AKEY_EVENT_ACTION_DOWN)); + window->consumeKeyDown(ADISPLAY_ID_NONE); +} + +TEST_F(InputDispatcherTargetedInjectionTest, CannotInjectIntoUnownedWindow) { + auto owner = User(mDispatcher, 10, 11); + auto window = owner.createWindow(); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + auto rando = User(mDispatcher, 20, 21); + EXPECT_EQ(InputEventInjectionResult::TARGET_MISMATCH, + rando.injectTargetedMotion(AMOTION_EVENT_ACTION_DOWN)); + + setFocusedWindow(window); + window->consumeFocusEvent(true); + + EXPECT_EQ(InputEventInjectionResult::TARGET_MISMATCH, + rando.injectTargetedKey(AKEY_EVENT_ACTION_DOWN)); + window->assertNoEvents(); +} + +TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoOwnedSpyWindow) { + auto owner = User(mDispatcher, 10, 11); + auto window = owner.createWindow(); + auto spy = owner.createWindow(); + spy->setSpy(true); + spy->setTrustedOverlay(true); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}}); + + EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, + owner.injectTargetedMotion(AMOTION_EVENT_ACTION_DOWN)); + spy->consumeMotionDown(); + window->consumeMotionDown(); +} + +TEST_F(InputDispatcherTargetedInjectionTest, CannotInjectIntoUnownedSpyWindow) { + auto owner = User(mDispatcher, 10, 11); + auto window = owner.createWindow(); + + auto rando = User(mDispatcher, 20, 21); + auto randosSpy = rando.createWindow(); + randosSpy->setSpy(true); + randosSpy->setTrustedOverlay(true); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {randosSpy, window}}}); + + // The event is targeted at owner's window, so injection should succeed, but the spy should + // not receive the event. + EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, + owner.injectTargetedMotion(AMOTION_EVENT_ACTION_DOWN)); + randosSpy->assertNoEvents(); + window->consumeMotionDown(); +} + +TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoAnyWindowWhenNotTargeting) { + auto owner = User(mDispatcher, 10, 11); + auto window = owner.createWindow(); + + auto rando = User(mDispatcher, 20, 21); + auto randosSpy = rando.createWindow(); + randosSpy->setSpy(true); + randosSpy->setTrustedOverlay(true); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {randosSpy, window}}}); + + // A user that has injection permission can inject into any window. + EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT)); + randosSpy->consumeMotionDown(); + window->consumeMotionDown(); + + setFocusedWindow(randosSpy); + randosSpy->consumeFocusEvent(true); + + EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher)); + randosSpy->consumeKeyDown(ADISPLAY_ID_NONE); + window->assertNoEvents(); +} + +TEST_F(InputDispatcherTargetedInjectionTest, CanGenerateActionOutsideToOtherUids) { + auto owner = User(mDispatcher, 10, 11); + auto window = owner.createWindow(); + + auto rando = User(mDispatcher, 20, 21); + auto randosWindow = rando.createWindow(); + randosWindow->setFrame(Rect{-10, -10, -5, -5}); + randosWindow->setWatchOutsideTouch(true); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {randosWindow, window}}}); + + // We allow generation of ACTION_OUTSIDE events into windows owned by different uids. + EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, + owner.injectTargetedMotion(AMOTION_EVENT_ACTION_DOWN)); + window->consumeMotionDown(); + randosWindow->consumeMotionOutside(); +} + } // namespace android::inputdispatcher -- GitLab From 3ce9c3a9a51d6ba7a9ef1151f6937b18fbb25b0f Mon Sep 17 00:00:00 2001 From: Hani Kazmi Date: Mon, 25 Apr 2022 09:40:23 +0000 Subject: [PATCH 0056/1310] Cleanup: Block untrusted touches in InputDispatcher This change removes the BlockUntrustedTouchesMode enum, and related code, as block_untrusted_touches is now always enforced Fix: 169067926 Test: atest WindowUntrustedTouchTest Change-Id: Ie039fb06c2ee7e03d726545190e4e75f58978066 --- libs/input/Android.bp | 1 - .../android/os/BlockUntrustedTouchesMode.aidl | 35 ----------------- .../benchmarks/InputDispatcher_benchmarks.cpp | 2 - .../dispatcher/InputDispatcher.cpp | 38 +++++-------------- .../inputflinger/dispatcher/InputDispatcher.h | 3 -- .../include/InputDispatcherInterface.h | 9 +---- .../include/InputDispatcherPolicyInterface.h | 3 -- .../tests/InputDispatcher_test.cpp | 2 - 8 files changed, 10 insertions(+), 83 deletions(-) delete mode 100644 libs/input/android/os/BlockUntrustedTouchesMode.aidl diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 5d7874af77..4a3c253951 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -26,7 +26,6 @@ package { filegroup { name: "inputconstants_aidl", srcs: [ - "android/os/BlockUntrustedTouchesMode.aidl", "android/os/IInputConstants.aidl", "android/os/InputEventInjectionResult.aidl", "android/os/InputEventInjectionSync.aidl", diff --git a/libs/input/android/os/BlockUntrustedTouchesMode.aidl b/libs/input/android/os/BlockUntrustedTouchesMode.aidl deleted file mode 100644 index 9504e993f8..0000000000 --- a/libs/input/android/os/BlockUntrustedTouchesMode.aidl +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) 2020, 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.os; - - -/** - * Block untrusted touches feature mode. - * - * @hide - */ -@Backing(type="int") -enum BlockUntrustedTouchesMode { - /** Feature is off. */ - DISABLED, - - /** Untrusted touches are flagged but not blocked. */ - PERMISSIVE, - - /** Untrusted touches are blocked. */ - BLOCK -} diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp index 32eec291cb..249ff117b1 100644 --- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp +++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp @@ -82,8 +82,6 @@ private: void notifyVibratorState(int32_t deviceId, bool isOn) override {} - void notifyUntrustedTouch(const std::string& obscuringPackage) override {} - void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override { *outConfig = mConfig; } diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 1cc4589eb9..4f6c696528 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -57,7 +57,6 @@ using android::gui::FocusRequest; using android::gui::TouchOcclusionMode; using android::gui::WindowInfo; using android::gui::WindowInfoHandle; -using android::os::BlockUntrustedTouchesMode; using android::os::IInputConstants; using android::os::InputEventInjectionResult; using android::os::InputEventInjectionSync; @@ -2198,23 +2197,17 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( } // Drop events that can't be trusted due to occlusion - if (mBlockUntrustedTouchesMode != BlockUntrustedTouchesMode::DISABLED) { - TouchOcclusionInfo occlusionInfo = - computeTouchOcclusionInfoLocked(windowHandle, x, y); - if (!isTouchTrustedLocked(occlusionInfo)) { - if (DEBUG_TOUCH_OCCLUSION) { - ALOGD("Stack of obscuring windows during untrusted touch (%d, %d):", x, y); - for (const auto& log : occlusionInfo.debugInfo) { - ALOGD("%s", log.c_str()); - } - } - sendUntrustedTouchCommandLocked(occlusionInfo.obscuringPackage); - if (mBlockUntrustedTouchesMode == BlockUntrustedTouchesMode::BLOCK) { - ALOGW("Dropping untrusted touch event due to %s/%d", - occlusionInfo.obscuringPackage.c_str(), occlusionInfo.obscuringUid); - continue; + TouchOcclusionInfo occlusionInfo = computeTouchOcclusionInfoLocked(windowHandle, x, y); + if (!isTouchTrustedLocked(occlusionInfo)) { + if (DEBUG_TOUCH_OCCLUSION) { + ALOGD("Stack of obscuring windows during untrusted touch (%d, %d):", x, y); + for (const auto& log : occlusionInfo.debugInfo) { + ALOGD("%s", log.c_str()); } } + ALOGW("Dropping untrusted touch event due to %s/%d", + occlusionInfo.obscuringPackage.c_str(), occlusionInfo.obscuringUid); + continue; } // Drop touch events if requested by input feature @@ -5051,11 +5044,6 @@ void InputDispatcher::setMaximumObscuringOpacityForTouch(float opacity) { mMaximumObscuringOpacityForTouch = opacity; } -void InputDispatcher::setBlockUntrustedTouchesMode(BlockUntrustedTouchesMode mode) { - std::scoped_lock lock(mLock); - mBlockUntrustedTouchesMode = mode; -} - std::pair InputDispatcher::findTouchStateAndWindowLocked( const sp& token) { for (auto& [displayId, state] : mTouchStatesByDisplay) { @@ -5815,14 +5803,6 @@ void InputDispatcher::sendDropWindowCommandLocked(const sp& token, floa postCommandLocked(std::move(command)); } -void InputDispatcher::sendUntrustedTouchCommandLocked(const std::string& obscuringPackage) { - auto command = [this, obscuringPackage]() REQUIRES(mLock) { - scoped_unlock unlock(mLock); - mPolicy->notifyUntrustedTouch(obscuringPackage); - }; - postCommandLocked(std::move(command)); -} - void InputDispatcher::onAnrLocked(const sp& connection) { if (connection == nullptr) { LOG_ALWAYS_FATAL("Caller must check for nullness"); diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index f3dac19fa4..68b84114ca 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -121,7 +121,6 @@ public: void setInputFilterEnabled(bool enabled) override; bool setInTouchMode(bool inTouchMode, int32_t pid, int32_t uid, bool hasPermission) override; void setMaximumObscuringOpacityForTouch(float opacity) override; - void setBlockUntrustedTouchesMode(android::os::BlockUntrustedTouchesMode mode) override; bool transferTouchFocus(const sp& fromToken, const sp& toToken, bool isDragDrop = false) override; @@ -344,7 +343,6 @@ private: bool mInputFilterEnabled GUARDED_BY(mLock); bool mInTouchMode GUARDED_BY(mLock); float mMaximumObscuringOpacityForTouch GUARDED_BY(mLock); - android::os::BlockUntrustedTouchesMode mBlockUntrustedTouchesMode GUARDED_BY(mLock); class DispatcherWindowListener : public gui::WindowInfosListener { public: @@ -654,7 +652,6 @@ private: void sendFocusChangedCommandLocked(const sp& oldToken, const sp& newToken) REQUIRES(mLock); void sendDropWindowCommandLocked(const sp& token, float x, float y) REQUIRES(mLock); - void sendUntrustedTouchCommandLocked(const std::string& obscuringPackage) REQUIRES(mLock); void onAnrLocked(const sp& connection) REQUIRES(mLock); void onAnrLocked(std::shared_ptr application) REQUIRES(mLock); void updateLastAnrStateLocked(const sp& window, diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h index d7bc5fbfe9..c7723d07f3 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h @@ -20,7 +20,7 @@ #include #include #include -#include + #include #include #include @@ -136,13 +136,6 @@ public: */ virtual void setMaximumObscuringOpacityForTouch(float opacity) = 0; - /** - * Sets the mode of the block untrusted touches feature. - * - * TODO(b/169067926): Clean-up feature modes. - */ - virtual void setBlockUntrustedTouchesMode(android::os::BlockUntrustedTouchesMode mode) = 0; - /* Transfers touch focus from one window to another window. * * Returns true on success. False if the window did not actually have touch focus. diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h index de0b6da884..13ed34c42b 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h @@ -75,9 +75,6 @@ public: InputDeviceSensorAccuracy accuracy) = 0; virtual void notifyVibratorState(int32_t deviceId, bool isOn) = 0; - /* Notifies the system that an untrusted touch occurred. */ - virtual void notifyUntrustedTouch(const std::string& obscuringPackage) = 0; - /* Gets the input dispatcher configuration. */ virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) = 0; diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index a167271c4d..71456527ec 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -409,7 +409,6 @@ private: void notifyFocusChanged(const sp&, const sp&) override {} - void notifyUntrustedTouch(const std::string& obscuringPackage) override {} void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType, InputDeviceSensorAccuracy accuracy, nsecs_t timestamp, const std::vector& values) override {} @@ -5696,7 +5695,6 @@ protected: virtual void SetUp() override { InputDispatcherTest::SetUp(); mTouchWindow = getWindow(TOUCHED_APP_UID, "Touched"); - mDispatcher->setBlockUntrustedTouchesMode(android::os::BlockUntrustedTouchesMode::BLOCK); mDispatcher->setMaximumObscuringOpacityForTouch(MAXIMUM_OBSCURING_OPACITY); } -- GitLab From 5279f866e2ba354845770fc09235796b481486b0 Mon Sep 17 00:00:00 2001 From: Ian Elliott Date: Thu, 21 Apr 2022 12:41:03 -0600 Subject: [PATCH 0057/1310] Can switch to legacy GLES driver when ANGLE is system driver Change the sense of the run-time switches so that ANGLE can be an optional GLES driver or the default system GLES driver. When ANGLE is the system GLES driver, also handle the case of there being no legacy GLES driver. Test: logcat Test: atest CtsAngleIntegrationHostTestCases Test: atest CtsEffectTestCases:android.effect.cts.EffectTest#test5_effectCreate Bug: 224558229 Change-Id: I5383966daeea74a9d7c878c8f63f70f7fc7ccc50 --- libs/graphicsenv/GraphicsEnv.cpp | 62 +++++++++++++++++-- .../include/graphicsenv/GraphicsEnv.h | 17 ++++- opengl/libs/EGL/Loader.cpp | 50 +++++++++------ 3 files changed, 106 insertions(+), 23 deletions(-) diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp index 7f0cac5d4f..4a0a839948 100644 --- a/libs/graphicsenv/GraphicsEnv.cpp +++ b/libs/graphicsenv/GraphicsEnv.cpp @@ -364,26 +364,61 @@ bool GraphicsEnv::shouldUseAngle() { return (mUseAngle == YES) ? true : false; } +bool GraphicsEnv::angleIsSystemDriver() { + // Make sure we are init'ed + if (mAngleAppName.empty()) { + ALOGV("App name is empty. setAngleInfo() has not been called to enable ANGLE."); + return false; + } + + return (mAngleIsSystemDriver == YES) ? true : false; +} + +bool GraphicsEnv::shouldForceLegacyDriver() { + // Make sure we are init'ed + if (mAngleAppName.empty()) { + ALOGV("App name is empty. setAngleInfo() has not been called to enable ANGLE."); + return false; + } + + return (mAngleIsSystemDriver == YES && mUseAngle == NO) ? true : false; +} + +std::string GraphicsEnv::getLegacySuffix() { + return mLegacyDriverSuffix; +} + void GraphicsEnv::updateUseAngle() { mUseAngle = NO; const char* ANGLE_PREFER_ANGLE = "angle"; + const char* ANGLE_PREFER_LEGACY = "legacy"; + // The following is a deprecated version of "legacy" const char* ANGLE_PREFER_NATIVE = "native"; mUseAngle = NO; if (mAngleDeveloperOptIn == ANGLE_PREFER_ANGLE) { - ALOGV("User set \"Developer Options\" to force the use of ANGLE"); + ALOGI("Using ANGLE, the %s GLES driver for package '%s'", + mAngleIsSystemDriver == YES ? "system" : "optional", mAngleAppName.c_str()); mUseAngle = YES; - } else if (mAngleDeveloperOptIn == ANGLE_PREFER_NATIVE) { - ALOGV("User set \"Developer Options\" to force the use of Native"); + } else if (mAngleDeveloperOptIn == ANGLE_PREFER_LEGACY || + mAngleDeveloperOptIn == ANGLE_PREFER_NATIVE) { + ALOGI("Using the (%s) Legacy GLES driver for package '%s'", + mAngleIsSystemDriver == YES ? "optional" : "system", mAngleAppName.c_str()); } else { ALOGV("User set invalid \"Developer Options\": '%s'", mAngleDeveloperOptIn.c_str()); } } void GraphicsEnv::setAngleInfo(const std::string path, const std::string appName, - const std::string developerOptIn, + const bool angleIsSystemDriver, const std::string developerOptIn, const std::vector eglFeatures) { + // Set whether ANGLE is the system driver: + mAngleIsSystemDriver = angleIsSystemDriver ? YES : NO; + + // Note: Given the current logic and lack of the old rules file processing, + // there seems to be little chance that mUseAngle != UNKNOWN. Leave this + // for now, even though it seems outdated. if (mUseAngle != UNKNOWN) { // We've already figured out an answer for this app, so just return. ALOGV("Already evaluated the rules file for '%s': use ANGLE = %s", appName.c_str(), @@ -404,6 +439,25 @@ void GraphicsEnv::setAngleInfo(const std::string path, const std::string appName updateUseAngle(); } +void GraphicsEnv::setLegacyDriverInfo(const std::string appName, const bool angleIsSystemDriver, + const std::string legacyDriverName) { + ALOGV("setting legacy app name to '%s'", appName.c_str()); + mAngleAppName = appName; + + // Force the use of the legacy driver instead of ANGLE + const char* ANGLE_PREFER_LEGACY = "legacy"; + mAngleDeveloperOptIn = ANGLE_PREFER_LEGACY; + ALOGV("setting ANGLE application opt-in to 'legacy'"); + + // Set whether ANGLE is the system driver: + mAngleIsSystemDriver = angleIsSystemDriver ? YES : NO; + + mLegacyDriverSuffix = legacyDriverName; + + // Update the current status of whether we should use ANGLE or not + updateUseAngle(); +} + void GraphicsEnv::setLayerPaths(NativeLoaderNamespace* appNamespace, const std::string layerPaths) { if (mLayerPaths.empty()) { mLayerPaths = layerPaths; diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h index 56d1139f57..82a6b6c2c0 100644 --- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h +++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h @@ -91,17 +91,28 @@ public: bool shouldUseAngle(std::string appName); // Check if this app process should use ANGLE. bool shouldUseAngle(); + // If ANGLE is the system GLES driver + bool angleIsSystemDriver(); + // If should use legacy driver instead of a system ANGLE driver + bool shouldForceLegacyDriver(); // Set a search path for loading ANGLE libraries. The path is a list of // directories separated by ':'. A directory can be contained in a zip file // (libraries must be stored uncompressed and page aligned); such elements // in the search path must have a '!' after the zip filename, e.g. // /system/app/ANGLEPrebuilt/ANGLEPrebuilt.apk!/lib/arm64-v8a - void setAngleInfo(const std::string path, const std::string appName, std::string devOptIn, + void setAngleInfo(const std::string path, const std::string appName, + const bool angleIsSystemDriver, std::string devOptIn, const std::vector eglFeatures); + // Set the state so that the legacy driver will be used, and in case ANGLE + // is the system driver, provide the name of the legacy driver. + void setLegacyDriverInfo(const std::string appName, const bool angleIsSystemDriver, + const std::string legacyDriverName); // Get the ANGLE driver namespace. android_namespace_t* getAngleNamespace(); // Get the app name for ANGLE debug message. std::string& getAngleAppName(); + // Get the legacy driver's suffix name. + std::string getLegacySuffix(); const std::vector& getAngleEglFeatures(); @@ -156,6 +167,10 @@ private: std::string mAngleDeveloperOptIn; // ANGLE EGL features; std::vector mAngleEglFeatures; + // ANGLE is System Driver flag. + UseAngle mAngleIsSystemDriver = UNKNOWN; + // Legacy driver name to use when ANGLE is the system driver. + std::string mLegacyDriverSuffix; // Use ANGLE flag. UseAngle mUseAngle = UNKNOWN; // Vulkan debug layers libs. diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp index 76fd7f0f3f..dd14bcfb55 100644 --- a/opengl/libs/EGL/Loader.cpp +++ b/opengl/libs/EGL/Loader.cpp @@ -207,7 +207,8 @@ void* Loader::open(egl_connection_t* cnx) ATRACE_CALL(); const nsecs_t openTime = systemTime(); - if (should_unload_system_driver(cnx)) { + if (!android::GraphicsEnv::getInstance().angleIsSystemDriver() && + should_unload_system_driver(cnx)) { unload_system_driver(cnx); } @@ -216,8 +217,13 @@ void* Loader::open(egl_connection_t* cnx) return cnx->dso; } - // Firstly, try to load ANGLE driver. - driver_t* hnd = attempt_to_load_angle(cnx); + // Firstly, try to load ANGLE driver, unless we know that we shouldn't. + bool shouldForceLegacyDriver = android::GraphicsEnv::getInstance().shouldForceLegacyDriver(); + driver_t* hnd = nullptr; + if (!shouldForceLegacyDriver) { + hnd = attempt_to_load_angle(cnx); + } + if (!hnd) { // Secondly, try to load from driver apk. hnd = attempt_to_load_updated_driver(cnx); @@ -230,21 +236,29 @@ void* Loader::open(egl_connection_t* cnx) LOG_ALWAYS_FATAL("couldn't find an OpenGL ES implementation from %s", android::GraphicsEnv::getInstance().getDriverPath().c_str()); } - // Finally, try to load system driver, start by searching for the library name appended by - // the system properties of the GLES userspace driver in both locations. - // i.e.: - // libGLES_${prop}.so, or: - // libEGL_${prop}.so, libGLESv1_CM_${prop}.so, libGLESv2_${prop}.so - for (auto key : HAL_SUBNAME_KEY_PROPERTIES) { - auto prop = base::GetProperty(key, ""); - if (prop.empty()) { - continue; - } - hnd = attempt_to_load_system_driver(cnx, prop.c_str(), true); - if (hnd) { - break; - } else if (strcmp(key, DRIVER_SUFFIX_PROPERTY) == 0) { - failToLoadFromDriverSuffixProperty = true; + // Finally, try to load system driver. If ANGLE is the system driver + // (i.e. we are forcing the legacy system driver instead of ANGLE), use + // the driver suffix that was passed down from above. + if (shouldForceLegacyDriver) { + std::string suffix = android::GraphicsEnv::getInstance().getLegacySuffix(); + hnd = attempt_to_load_system_driver(cnx, suffix.c_str(), true); + } else { + // Start by searching for the library name appended by the system + // properties of the GLES userspace driver in both locations. + // i.e.: + // libGLES_${prop}.so, or: + // libEGL_${prop}.so, libGLESv1_CM_${prop}.so, libGLESv2_${prop}.so + for (auto key : HAL_SUBNAME_KEY_PROPERTIES) { + auto prop = base::GetProperty(key, ""); + if (prop.empty()) { + continue; + } + hnd = attempt_to_load_system_driver(cnx, prop.c_str(), true); + if (hnd) { + break; + } else if (strcmp(key, DRIVER_SUFFIX_PROPERTY) == 0) { + failToLoadFromDriverSuffixProperty = true; + } } } } -- GitLab From 67dd7126b52bf8ab25d89470392b887bccf0ad6f Mon Sep 17 00:00:00 2001 From: Tianhao Yao Date: Tue, 22 Feb 2022 17:48:33 +0000 Subject: [PATCH 0058/1310] Enable drawing layers' borders in SurfaceFlinger. Provide an API on SurfaceComposerClient::Transaction for enabling a border to be drawn for a specified hierarchy. The caller requests a border to be drawn at a certain Layer and all its children will be included when computing the visible region. The border will draw around the union of all the layers' visible regions, starting from the requested layer. Test: go/wm-smoke Test: LayerBorder_test Bug: 225977175 Change-Id: I7760b51b68cdf01bb9146ec91a270a9d63927995 --- libs/gui/LayerState.cpp | 8 +- libs/gui/SurfaceComposerClient.cpp | 15 ++ libs/gui/include/gui/LayerState.h | 4 +- libs/gui/include/gui/SurfaceComposerClient.h | 2 + .../include/renderengine/BorderRenderInfo.h | 33 +++ .../include/renderengine/DisplaySettings.h | 6 +- libs/renderengine/skia/SkiaGLRenderEngine.cpp | 24 ++ libs/renderengine/tests/RenderEngineTest.cpp | 47 ++++ .../CompositionRefreshArgs.h | 5 + .../include/compositionengine/impl/Output.h | 1 + .../impl/OutputCompositionState.h | 2 + .../CompositionEngine/src/Output.cpp | 38 +++ .../src/OutputCompositionState.cpp | 8 +- services/surfaceflinger/Layer.cpp | 15 +- services/surfaceflinger/Layer.h | 5 + services/surfaceflinger/SurfaceFlinger.cpp | 23 ++ services/surfaceflinger/tests/Android.bp | 1 + .../surfaceflinger/tests/LayerBorder_test.cpp | 247 ++++++++++++++++++ .../surfaceflinger/tests/utils/ColorUtils.h | 12 + 19 files changed, 490 insertions(+), 6 deletions(-) create mode 100644 libs/renderengine/include/renderengine/BorderRenderInfo.h create mode 100644 services/surfaceflinger/tests/LayerBorder_test.cpp diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 3ab9e974bc..0d3b412842 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -66,6 +66,7 @@ layer_state_t::layer_state_t() fixedTransformHint(ui::Transform::ROT_INVALID), autoRefresh(false), isTrustedOverlay(false), + borderEnabled(false), bufferCrop(Rect::INVALID_RECT), destinationFrame(Rect::INVALID_RECT), dropInputMode(gui::DropInputMode::NONE) { @@ -100,7 +101,7 @@ status_t layer_state_t::write(Parcel& output) const SAFE_PARCEL(output.write, transparentRegion); SAFE_PARCEL(output.writeUint32, transform); SAFE_PARCEL(output.writeBool, transformToDisplayInverse); - + SAFE_PARCEL(output.writeBool, borderEnabled); SAFE_PARCEL(output.writeUint32, static_cast(dataspace)); SAFE_PARCEL(output.write, hdrMetadata); SAFE_PARCEL(output.write, surfaceDamageRegion); @@ -200,6 +201,7 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(input.read, transparentRegion); SAFE_PARCEL(input.readUint32, &transform); SAFE_PARCEL(input.readBool, &transformToDisplayInverse); + SAFE_PARCEL(input.readBool, &borderEnabled); uint32_t tmpUint32 = 0; SAFE_PARCEL(input.readUint32, &tmpUint32); @@ -550,6 +552,10 @@ void layer_state_t::merge(const layer_state_t& other) { what |= eShadowRadiusChanged; shadowRadius = other.shadowRadius; } + if (other.what & eRenderBorderChanged) { + what |= eRenderBorderChanged; + borderEnabled = other.borderEnabled; + } if (other.what & eFrameRateSelectionPriority) { what |= eFrameRateSelectionPriority; frameRateSelectionPriority = other.frameRateSelectionPriority; diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 3446b1bc0d..d543e9444c 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -1918,6 +1918,21 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDropI return *this; } +SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::enableBorder( + const sp& sc, bool shouldEnable) { + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + + s->what |= layer_state_t::eRenderBorderChanged; + s->borderEnabled = shouldEnable; + + registerSurfaceControlForCallback(sc); + return *this; +} + // --------------------------------------------------------------------------- DisplayState& SurfaceComposerClient::Transaction::getDisplayState(const sp& token) { diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 4cd9a56fcd..4621d2b24e 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -155,7 +155,7 @@ struct layer_state_t { eLayerStackChanged = 0x00000080, eDimmingEnabledChanged = 0x00000400, eShadowRadiusChanged = 0x00000800, - /* unused 0x00001000, */ + eRenderBorderChanged = 0x00001000, eBufferCropChanged = 0x00002000, eRelativeLayerChanged = 0x00004000, eReparent = 0x00008000, @@ -293,6 +293,8 @@ struct layer_state_t { // should be trusted for input occlusion detection purposes bool isTrustedOverlay; + // Flag to indicate if border needs to be enabled on the layer + bool borderEnabled; // Stretch effect to be applied to this layer StretchEffect stretchEffect; diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index dc9624269b..cdb60c713a 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -625,6 +625,8 @@ public: const Rect& destinationFrame); Transaction& setDropInputMode(const sp& sc, gui::DropInputMode mode); + Transaction& enableBorder(const sp& sc, bool shouldEnable); + status_t setDisplaySurface(const sp& token, const sp& bufferProducer); diff --git a/libs/renderengine/include/renderengine/BorderRenderInfo.h b/libs/renderengine/include/renderengine/BorderRenderInfo.h new file mode 100644 index 0000000000..85d55fc958 --- /dev/null +++ b/libs/renderengine/include/renderengine/BorderRenderInfo.h @@ -0,0 +1,33 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include + +namespace android { +namespace renderengine { + +struct BorderRenderInfo { + Region combinedRegion; + + bool operator==(const BorderRenderInfo& rhs) const { + return (combinedRegion.hasSameRects(rhs.combinedRegion)); + } +}; + +} // namespace renderengine +} // namespace android \ No newline at end of file diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h index 59ef991eec..25fe9f2d8e 100644 --- a/libs/renderengine/include/renderengine/DisplaySettings.h +++ b/libs/renderengine/include/renderengine/DisplaySettings.h @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -79,6 +80,8 @@ struct DisplaySettings { // Configures the rendering intent of the output display. This is used for tonemapping. aidl::android::hardware::graphics::composer3::RenderIntent renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent::TONE_MAP_COLORIMETRIC; + + std::vector borderInfoList; }; static inline bool operator==(const DisplaySettings& lhs, const DisplaySettings& rhs) { @@ -90,7 +93,8 @@ static inline bool operator==(const DisplaySettings& lhs, const DisplaySettings& lhs.deviceHandlesColorTransform == rhs.deviceHandlesColorTransform && lhs.orientation == rhs.orientation && lhs.targetLuminanceNits == rhs.targetLuminanceNits && - lhs.dimmingStage == rhs.dimmingStage && lhs.renderIntent == rhs.renderIntent; + lhs.dimmingStage == rhs.dimmingStage && lhs.renderIntent == rhs.renderIntent && + lhs.borderInfoList == rhs.borderInfoList; } static const char* orientation_to_string(uint32_t orientation) { diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index 11cea1339d..ec9ad54b56 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -1245,6 +1245,30 @@ void SkiaGLRenderEngine::drawLayersInternal( activeSurface->flush(); } } + for (const auto& borderRenderInfo : display.borderInfoList) { + SkPaint p; + // TODO (b/225977175): Use specified color + p.setColor(SkColor4f{.fR = 255 / 255.0f, + .fG = 128 / 255.0f, + .fB = 0 / 255.0f, + .fA = 255 / 255.0f}); + p.setAntiAlias(true); + p.setStyle(SkPaint::kStroke_Style); + // TODO (b/225977175): Use specified width + p.setStrokeWidth(20); + SkRegion sk_region; + SkPath path; + + // Construct a final SkRegion using Regions + for (const auto& r : borderRenderInfo.combinedRegion) { + sk_region.op({r.left, r.top, r.right, r.bottom}, SkRegion::kUnion_Op); + } + + sk_region.getBoundaryPath(&path); + canvas->drawPath(path, p); + path.close(); + } + surfaceAutoSaveRestore.restore(); mCapture->endCapture(); { diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index 7c70a748b5..df1b985bd6 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -2411,6 +2411,53 @@ TEST_P(RenderEngineTest, testDisableBlendingBuffer) { expectBufferColor(rect, 0, 128, 0, 128); } +TEST_P(RenderEngineTest, testBorder) { + if (GetParam()->type() != renderengine::RenderEngine::RenderEngineType::SKIA_GL) { + GTEST_SKIP(); + } + + if (!GetParam()->useColorManagement()) { + GTEST_SKIP(); + } + + initializeRenderEngine(); + + const ui::Dataspace dataspace = ui::Dataspace::V0_SRGB; + + const auto displayRect = Rect(1080, 2280); + renderengine::DisplaySettings display{ + .physicalDisplay = displayRect, + .clip = displayRect, + .outputDataspace = dataspace, + }; + display.borderInfoList.clear(); + renderengine::BorderRenderInfo info; + info.combinedRegion = Region(Rect(99, 99, 199, 199)); + display.borderInfoList.emplace_back(info); + + const auto greenBuffer = allocateAndFillSourceBuffer(1, 1, ubyte4(0, 255, 0, 255)); + const renderengine::LayerSettings greenLayer{ + .geometry.boundaries = FloatRect(0.f, 0.f, 1.f, 1.f), + .source = + renderengine::PixelSource{ + .buffer = + renderengine::Buffer{ + .buffer = greenBuffer, + .usePremultipliedAlpha = true, + }, + }, + .alpha = 1.0f, + .sourceDataspace = dataspace, + .whitePointNits = 200.f, + }; + + std::vector layers; + layers.emplace_back(greenLayer); + invokeDraw(display, layers); + + expectBufferColor(Rect(99, 99, 101, 101), 255, 128, 0, 255, 1); +} + TEST_P(RenderEngineTest, testDimming) { if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { GTEST_SKIP(); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h index f201751d59..8039bbacb9 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h @@ -32,6 +32,9 @@ namespace android::compositionengine { using Layers = std::vector>; using Outputs = std::vector>; +struct BorderRenderInfo { + std::vector layerIds; +}; /** * A parameter object for refreshing a set of outputs */ @@ -90,6 +93,8 @@ struct CompositionRefreshArgs { // If set, a frame has been scheduled for that time. std::optional scheduledFrameTime; + + std::vector borderInfoList; }; } // namespace android::compositionengine diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h index 31c51e6a8d..3ad6e52f26 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h @@ -152,6 +152,7 @@ protected: private: void dirtyEntireOutput(); + void updateCompositionStateForBorder(const compositionengine::CompositionRefreshArgs&); compositionengine::OutputLayer* findLayerRequestingBackgroundComposition() const; void finishPrepareFrame(); ui::Dataspace getBestDataspace(ui::Dataspace*, bool*) const; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h index c65d467b0d..cd56530f37 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h @@ -33,6 +33,7 @@ #pragma clang diagnostic pop // ignored "-Wconversion -Wextra" #include +#include #include #include #include @@ -164,6 +165,7 @@ struct OutputCompositionState { bool treat170mAsSrgb = false; + std::vector borderInfoList; // Debugging void dump(std::string& result) const; }; diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 4c30f99610..430d6734fe 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -733,6 +733,39 @@ void Output::updateCompositionState(const compositionengine::CompositionRefreshA forceClientComposition = false; } } + + updateCompositionStateForBorder(refreshArgs); +} + +void Output::updateCompositionStateForBorder( + const compositionengine::CompositionRefreshArgs& refreshArgs) { + std::unordered_map layerVisibleRegionMap; + // Store a map of layerId to their computed visible region. + for (auto* layer : getOutputLayersOrderedByZ()) { + int layerId = (layer->getLayerFE()).getSequence(); + layerVisibleRegionMap[layerId] = &((layer->getState()).visibleRegion); + } + OutputCompositionState& outputCompositionState = editState(); + outputCompositionState.borderInfoList.clear(); + bool clientComposeTopLayer = false; + for (const auto& borderInfo : refreshArgs.borderInfoList) { + renderengine::BorderRenderInfo info; + for (const auto& id : borderInfo.layerIds) { + info.combinedRegion.orSelf(*(layerVisibleRegionMap[id])); + } + outputCompositionState.borderInfoList.emplace_back(std::move(info)); + clientComposeTopLayer |= !info.combinedRegion.isEmpty(); + } + + // In this situation we must client compose the top layer instead of using hwc + // because we want to draw the border above all else. + // This could potentially cause a bit of a performance regression if the top + // layer would have been rendered using hwc originally. + // TODO(b/227656283): Measure system's performance before enabling the border feature + if (clientComposeTopLayer) { + auto topLayer = getOutputLayerOrderedByZByIndex(getOutputLayerCount() - 1); + (topLayer->editState()).forceClientComposition = true; + } } void Output::planComposition() { @@ -1183,6 +1216,11 @@ std::optional Output::composeSurfaces( // Compute the global color transform matrix. clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix; + for (auto& info : outputState.borderInfoList) { + renderengine::BorderRenderInfo borderInfo; + borderInfo.combinedRegion = info.combinedRegion; + clientCompositionDisplay.borderInfoList.emplace_back(std::move(borderInfo)); + } clientCompositionDisplay.deviceHandlesColorTransform = outputState.usesDeviceComposition || getSkipColorTransform(); diff --git a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp index 948c0c90bf..c512a1e97f 100644 --- a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp +++ b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp @@ -62,14 +62,18 @@ void OutputCompositionState::dump(std::string& out) const { dumpVal(out, "sdrWhitePointNits", sdrWhitePointNits); dumpVal(out, "clientTargetBrightness", clientTargetBrightness); dumpVal(out, "displayBrightness", displayBrightness); - out.append("\n "); dumpVal(out, "compositionStrategyPredictionState", ftl::enum_string(strategyPrediction)); + out.append("\n "); out.append("\n "); dumpVal(out, "treate170mAsSrgb", treat170mAsSrgb); - out += '\n'; + out.append("\n"); + for (const auto& borderRenderInfo : borderInfoList) { + dumpVal(out, "borderRegion", borderRenderInfo.combinedRegion); + } + out.append("\n"); } } // namespace android::compositionengine::impl diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index af3971749b..d47e423ebe 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -101,7 +101,8 @@ Layer::Layer(const LayerCreationArgs& args) mClientRef(args.client), mWindowType(static_cast( args.metadata.getInt32(gui::METADATA_WINDOW_TYPE, 0))), - mLayerCreationFlags(args.flags) { + mLayerCreationFlags(args.flags), + mBorderEnabled(false) { uint32_t layerFlags = 0; if (args.flags & ISurfaceComposerClient::eHidden) layerFlags |= layer_state_t::eLayerHidden; if (args.flags & ISurfaceComposerClient::eOpaque) layerFlags |= layer_state_t::eLayerOpaque; @@ -1156,6 +1157,18 @@ StretchEffect Layer::getStretchEffect() const { return StretchEffect{}; } +bool Layer::enableBorder(bool shouldEnable) { + if (mBorderEnabled == shouldEnable) { + return false; + } + mBorderEnabled = shouldEnable; + return true; +} + +bool Layer::isBorderEnabled() { + return mBorderEnabled; +} + bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* transactionNeeded) { // The frame rate for layer tree is this layer's frame rate if present, or the parent frame rate const auto frameRate = [&] { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index ff18fd0b92..85187e1691 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -895,6 +895,8 @@ public: bool setStretchEffect(const StretchEffect& effect); StretchEffect getStretchEffect() const; + bool enableBorder(bool shouldEnable); + bool isBorderEnabled(); virtual bool setBufferCrop(const Rect& /* bufferCrop */) { return false; } virtual bool setDestinationFrame(const Rect& /* destinationFrame */) { return false; } @@ -1143,7 +1145,10 @@ private: bool mIsAtRoot = false; uint32_t mLayerCreationFlags; + bool findInHierarchy(const sp&); + + bool mBorderEnabled = false; }; std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 040014d9ce..ec87dbef61 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -156,6 +156,10 @@ #define NO_THREAD_SAFETY_ANALYSIS \ _Pragma("GCC error \"Prefer or MutexUtils.h helpers.\"") +// To enable layer borders in the system, change the below flag to true. +#undef DOES_CONTAIN_BORDER +#define DOES_CONTAIN_BORDER false + namespace android { using namespace std::string_literals; @@ -2169,6 +2173,20 @@ void SurfaceFlinger::composite(nsecs_t frameTime, int64_t vsyncId) if (auto layerFE = layer->getCompositionEngineLayerFE()) refreshArgs.layers.push_back(layerFE); }); + + if (DOES_CONTAIN_BORDER) { + refreshArgs.borderInfoList.clear(); + mDrawingState.traverse([&refreshArgs](Layer* layer) { + if (layer->isBorderEnabled()) { + compositionengine::BorderRenderInfo info; + layer->traverse(LayerVector::StateSet::Drawing, [&info](Layer* ilayer) { + info.layerIds.push_back(ilayer->getSequence()); + }); + refreshArgs.borderInfoList.emplace_back(std::move(info)); + } + }); + } + refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size()); for (auto layer : mLayersWithQueuedFrames) { if (auto layerFE = layer->getCompositionEngineLayerFE()) @@ -4429,6 +4447,11 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime if (what & layer_state_t::eBlurRegionsChanged) { if (layer->setBlurRegions(s.blurRegions)) flags |= eTraversalNeeded; } + if (what & layer_state_t::eRenderBorderChanged) { + if (layer->enableBorder(s.borderEnabled)) { + flags |= eTraversalNeeded; + } + } if (what & layer_state_t::eLayerStackChanged) { ssize_t idx = mCurrentState.layersSortedByZ.indexOf(layer); // We only allow setting layer stacks for top level layers, diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp index ceddf2727a..276431e661 100644 --- a/services/surfaceflinger/tests/Android.bp +++ b/services/surfaceflinger/tests/Android.bp @@ -34,6 +34,7 @@ cc_test { "DisplayConfigs_test.cpp", "DisplayEventReceiver_test.cpp", "EffectLayer_test.cpp", + "LayerBorder_test.cpp", "InvalidHandles_test.cpp", "LayerCallback_test.cpp", "LayerRenderTypeTransaction_test.cpp", diff --git a/services/surfaceflinger/tests/LayerBorder_test.cpp b/services/surfaceflinger/tests/LayerBorder_test.cpp new file mode 100644 index 0000000000..4b382140af --- /dev/null +++ b/services/surfaceflinger/tests/LayerBorder_test.cpp @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// TODO(b/129481165): remove the #pragma below and fix conversion issues +// TODO: Amend all tests when screenshots become fully reworked for borders +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wconversion" + +#include // std::chrono::seconds +#include // std::this_thread::sleep_for +#include "LayerTransactionTest.h" + +namespace android { + +class LayerBorderTest : public LayerTransactionTest { +protected: + virtual void SetUp() { + LayerTransactionTest::SetUp(); + ASSERT_EQ(NO_ERROR, mClient->initCheck()); + + toHalf3 = ColorTransformHelper::toHalf3; + toHalf4 = ColorTransformHelper::toHalf4; + + const auto display = SurfaceComposerClient::getInternalDisplayToken(); + ASSERT_FALSE(display == nullptr); + + mParentLayer = createColorLayer("Parent layer", Color::RED); + + mContainerLayer = mClient->createSurface(String8("Container Layer"), 0 /* width */, + 0 /* height */, PIXEL_FORMAT_RGBA_8888, + ISurfaceComposerClient::eFXSurfaceContainer | + ISurfaceComposerClient::eNoColorFill, + mParentLayer->getHandle()); + EXPECT_NE(nullptr, mContainerLayer.get()) << "failed to create container layer"; + + mEffectLayer1 = mClient->createSurface(String8("Effect Layer"), 0 /* width */, + 0 /* height */, PIXEL_FORMAT_RGBA_8888, + ISurfaceComposerClient::eFXSurfaceEffect | + ISurfaceComposerClient::eNoColorFill, + mContainerLayer->getHandle()); + EXPECT_NE(nullptr, mEffectLayer1.get()) << "failed to create effect layer 1"; + + mEffectLayer2 = mClient->createSurface(String8("Effect Layer"), 0 /* width */, + 0 /* height */, PIXEL_FORMAT_RGBA_8888, + ISurfaceComposerClient::eFXSurfaceEffect | + ISurfaceComposerClient::eNoColorFill, + mContainerLayer->getHandle()); + + EXPECT_NE(nullptr, mEffectLayer2.get()) << "failed to create effect layer 2"; + + asTransaction([&](Transaction& t) { + t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK); + t.setLayer(mParentLayer, INT32_MAX - 20).show(mParentLayer); + t.setFlags(mParentLayer, layer_state_t::eLayerOpaque, layer_state_t::eLayerOpaque); + + t.setColor(mEffectLayer1, toHalf3(Color::BLUE)); + + t.setColor(mEffectLayer2, toHalf3(Color::GREEN)); + }); + } + + virtual void TearDown() { + // Uncomment the line right below when running any of the tests + // std::this_thread::sleep_for (std::chrono::seconds(30)); + LayerTransactionTest::TearDown(); + mParentLayer = 0; + } + + std::function toHalf3; + std::function toHalf4; + sp mParentLayer, mContainerLayer, mEffectLayer1, mEffectLayer2; +}; + +TEST_F(LayerBorderTest, OverlappingVisibleRegions) { + asTransaction([&](Transaction& t) { + t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); + t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); + + t.enableBorder(mContainerLayer, true); + t.show(mEffectLayer1); + t.show(mEffectLayer2); + t.show(mContainerLayer); + }); +} + +TEST_F(LayerBorderTest, PartiallyCoveredVisibleRegion) { + asTransaction([&](Transaction& t) { + t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); + t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); + + t.enableBorder(mEffectLayer1, true); + t.show(mEffectLayer1); + t.show(mEffectLayer2); + t.show(mContainerLayer); + }); +} + +TEST_F(LayerBorderTest, NonOverlappingVisibleRegion) { + asTransaction([&](Transaction& t) { + t.setCrop(mEffectLayer1, Rect(0, 0, 200, 200)); + t.setCrop(mEffectLayer2, Rect(400, 400, 600, 600)); + + t.enableBorder(mContainerLayer, true); + t.show(mEffectLayer1); + t.show(mEffectLayer2); + t.show(mContainerLayer); + }); +} + +TEST_F(LayerBorderTest, EmptyVisibleRegion) { + asTransaction([&](Transaction& t) { + t.setCrop(mEffectLayer1, Rect(200, 200, 400, 400)); + t.setCrop(mEffectLayer2, Rect(0, 0, 600, 600)); + + t.enableBorder(mEffectLayer1, true); + t.show(mEffectLayer1); + t.show(mEffectLayer2); + t.show(mContainerLayer); + }); +} + +TEST_F(LayerBorderTest, ZOrderAdjustment) { + asTransaction([&](Transaction& t) { + t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); + t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); + t.setLayer(mParentLayer, 10); + t.setLayer(mEffectLayer1, 30); + t.setLayer(mEffectLayer2, 20); + + t.enableBorder(mEffectLayer1, true); + t.show(mEffectLayer1); + t.show(mEffectLayer2); + t.show(mContainerLayer); + }); +} + +TEST_F(LayerBorderTest, GrandChildHierarchy) { + sp containerLayer2 = + mClient->createSurface(String8("Container Layer"), 0 /* width */, 0 /* height */, + PIXEL_FORMAT_RGBA_8888, + ISurfaceComposerClient::eFXSurfaceContainer | + ISurfaceComposerClient::eNoColorFill, + mContainerLayer->getHandle()); + EXPECT_NE(nullptr, containerLayer2.get()) << "failed to create container layer 2"; + + sp effectLayer3 = + mClient->createSurface(String8("Effect Layer"), 0 /* width */, 0 /* height */, + PIXEL_FORMAT_RGBA_8888, + ISurfaceComposerClient::eFXSurfaceEffect | + ISurfaceComposerClient::eNoColorFill, + containerLayer2->getHandle()); + + asTransaction([&](Transaction& t) { + t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); + t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); + t.setCrop(effectLayer3, Rect(400, 400, 800, 800)); + t.setColor(effectLayer3, toHalf3(Color::BLUE)); + + t.enableBorder(mContainerLayer, true); + t.show(mEffectLayer1); + t.show(mEffectLayer2); + t.show(effectLayer3); + t.show(mContainerLayer); + }); +} + +TEST_F(LayerBorderTest, TransparentAlpha) { + asTransaction([&](Transaction& t) { + t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); + t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); + t.setAlpha(mEffectLayer1, 0.0f); + + t.enableBorder(mEffectLayer1, true); + t.show(mEffectLayer1); + t.show(mEffectLayer2); + t.show(mContainerLayer); + }); +} + +TEST_F(LayerBorderTest, SemiTransparentAlpha) { + asTransaction([&](Transaction& t) { + t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); + t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); + t.setAlpha(mEffectLayer2, 0.5f); + + t.enableBorder(mEffectLayer2, true); + t.show(mEffectLayer1); + t.show(mEffectLayer2); + t.show(mContainerLayer); + }); +} + +TEST_F(LayerBorderTest, InvisibleLayers) { + asTransaction([&](Transaction& t) { + t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); + t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); + + t.enableBorder(mContainerLayer, true); + t.hide(mEffectLayer2); + t.show(mContainerLayer); + }); +} + +TEST_F(LayerBorderTest, BufferStateLayer) { + asTransaction([&](Transaction& t) { + t.hide(mEffectLayer1); + t.hide(mEffectLayer2); + t.show(mContainerLayer); + + sp bufferStateLayer = + mClient->createSurface(String8("BufferState"), 0 /* width */, 0 /* height */, + PIXEL_FORMAT_RGBA_8888, + ISurfaceComposerClient::eFXSurfaceBufferState, + mContainerLayer->getHandle()); + + sp buffer = + new GraphicBuffer(400, 400, PIXEL_FORMAT_RGBA_8888, 1, + BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN | + BufferUsage::COMPOSER_OVERLAY | BufferUsage::GPU_TEXTURE, + "test"); + TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 200, 200), Color::GREEN); + TransactionUtils::fillGraphicBufferColor(buffer, Rect(200, 200, 400, 400), Color::BLUE); + + t.setBuffer(bufferStateLayer, buffer); + t.setPosition(bufferStateLayer, 100, 100); + t.show(bufferStateLayer); + t.enableBorder(mContainerLayer, true); + }); +} + +} // namespace android + +// TODO(b/129481165): remove the #pragma below and fix conversion issues +#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/tests/utils/ColorUtils.h b/services/surfaceflinger/tests/utils/ColorUtils.h index 07916b60a7..38c422a26d 100644 --- a/services/surfaceflinger/tests/utils/ColorUtils.h +++ b/services/surfaceflinger/tests/utils/ColorUtils.h @@ -33,6 +33,10 @@ struct Color { static const Color WHITE; static const Color BLACK; static const Color TRANSPARENT; + + half3 toHalf3() { return half3{r / 255.0f, g / 255.0f, b / 255.0f}; } + + half4 toHalf4() { return half4{r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f}; } }; const Color Color::RED{255, 0, 0, 255}; @@ -81,6 +85,14 @@ public: } color = ret; } + + static half3 toHalf3(const Color& color) { + return half3{color.r / 255.0f, color.g / 255.0f, color.b / 255.0f}; + } + + static half4 toHalf4(const Color& color) { + return half4{color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f}; + } }; } // namespace } // namespace android -- GitLab From 534f6f9068d71e3c930d407c5b600099d2b13db7 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Wed, 4 May 2022 13:20:32 -0400 Subject: [PATCH 0059/1310] Update OutputTest due to DisplaySettings changes In Ifc464779ed368d2edad7fc6adab53e9b368fd7cb, operator== for DisplaySettings was updated to include the remaining fields. Update tests to expect proper equality. Fixes: 231394619 Test: libcompositionengine_test Change-Id: I4d4be852a271d08be130e2c539431eae769021e3 --- .../CompositionEngine/tests/OutputTest.cpp | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index 3a3c91e518..862ab1d400 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -3368,7 +3368,6 @@ struct OutputComposeSurfacesTest : public testing::Test { static constexpr float kDefaultMaxLuminance = 0.9f; static constexpr float kDefaultAvgLuminance = 0.7f; static constexpr float kDefaultMinLuminance = 0.1f; - static constexpr float kUnknownLuminance = -1.f; static constexpr float kDisplayLuminance = 400.f; static constexpr float kClientTargetLuminanceNits = 200.f; static constexpr float kClientTargetBrightness = 0.5f; @@ -3766,7 +3765,7 @@ struct OutputComposeSurfacesTest_UsesExpectedDisplaySettings : public OutputComp TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrMixedComposition) { verify().ifMixedCompositionIs(true) .andIfUsesHdr(true) - .withDisplayBrightnessNits(kUnknownLuminance) + .withDisplayBrightnessNits(kDisplayLuminance) .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) .withRenderIntent( aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC) @@ -3775,7 +3774,7 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrMixedComposi {.physicalDisplay = kDefaultOutputDestinationClip, .clip = kDefaultOutputViewport, .maxLuminance = kDefaultMaxLuminance, - .currentLuminanceNits = kDefaultMaxLuminance, + .currentLuminanceNits = kDisplayLuminance, .outputDataspace = kDefaultOutputDataspace, .colorTransform = kDefaultColorTransformMat, .deviceHandlesColorTransform = true, @@ -3820,7 +3819,7 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrMixedCompositionWithDimmingStage) { verify().ifMixedCompositionIs(true) .andIfUsesHdr(true) - .withDisplayBrightnessNits(kUnknownLuminance) + .withDisplayBrightnessNits(kDisplayLuminance) .withDimmingStage( aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF) .withRenderIntent( @@ -3830,7 +3829,7 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, {.physicalDisplay = kDefaultOutputDestinationClip, .clip = kDefaultOutputViewport, .maxLuminance = kDefaultMaxLuminance, - .currentLuminanceNits = kDefaultMaxLuminance, + .currentLuminanceNits = kDisplayLuminance, .outputDataspace = kDefaultOutputDataspace, .colorTransform = kDefaultColorTransformMat, .deviceHandlesColorTransform = true, @@ -3848,7 +3847,7 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrMixedCompositionWithRenderIntent) { verify().ifMixedCompositionIs(true) .andIfUsesHdr(true) - .withDisplayBrightnessNits(kUnknownLuminance) + .withDisplayBrightnessNits(kDisplayLuminance) .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) .withRenderIntent(aidl::android::hardware::graphics::composer3::RenderIntent::ENHANCE) .andIfSkipColorTransform(false) @@ -3856,7 +3855,7 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, {.physicalDisplay = kDefaultOutputDestinationClip, .clip = kDefaultOutputViewport, .maxLuminance = kDefaultMaxLuminance, - .currentLuminanceNits = kDefaultMaxLuminance, + .currentLuminanceNits = kDisplayLuminance, .outputDataspace = kDefaultOutputDataspace, .colorTransform = kDefaultColorTransformMat, .deviceHandlesColorTransform = true, @@ -3873,7 +3872,7 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrMixedComposition) { verify().ifMixedCompositionIs(true) .andIfUsesHdr(false) - .withDisplayBrightnessNits(kUnknownLuminance) + .withDisplayBrightnessNits(kDisplayLuminance) .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) .withRenderIntent( aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC) @@ -3882,7 +3881,7 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrMixedComp {.physicalDisplay = kDefaultOutputDestinationClip, .clip = kDefaultOutputViewport, .maxLuminance = kDefaultMaxLuminance, - .currentLuminanceNits = kDefaultMaxLuminance, + .currentLuminanceNits = kDisplayLuminance, .outputDataspace = kDefaultOutputDataspace, .colorTransform = kDefaultColorTransformMat, .deviceHandlesColorTransform = true, @@ -3899,7 +3898,7 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrMixedComp TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrOnlyClientComposition) { verify().ifMixedCompositionIs(false) .andIfUsesHdr(true) - .withDisplayBrightnessNits(kUnknownLuminance) + .withDisplayBrightnessNits(kDisplayLuminance) .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) .withRenderIntent( aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC) @@ -3908,7 +3907,7 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrOnlyClientCo {.physicalDisplay = kDefaultOutputDestinationClip, .clip = kDefaultOutputViewport, .maxLuminance = kDefaultMaxLuminance, - .currentLuminanceNits = kDefaultMaxLuminance, + .currentLuminanceNits = kDisplayLuminance, .outputDataspace = kDefaultOutputDataspace, .colorTransform = kDefaultColorTransformMat, .deviceHandlesColorTransform = false, @@ -3925,7 +3924,7 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrOnlyClientCo TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrOnlyClientComposition) { verify().ifMixedCompositionIs(false) .andIfUsesHdr(false) - .withDisplayBrightnessNits(kUnknownLuminance) + .withDisplayBrightnessNits(kDisplayLuminance) .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) .withRenderIntent( aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC) @@ -3934,7 +3933,7 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrOnlyClien {.physicalDisplay = kDefaultOutputDestinationClip, .clip = kDefaultOutputViewport, .maxLuminance = kDefaultMaxLuminance, - .currentLuminanceNits = kDefaultMaxLuminance, + .currentLuminanceNits = kDisplayLuminance, .outputDataspace = kDefaultOutputDataspace, .colorTransform = kDefaultColorTransformMat, .deviceHandlesColorTransform = false, @@ -3952,7 +3951,7 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, usesExpectedDisplaySettingsForHdrOnlyClientCompositionWithSkipClientTransform) { verify().ifMixedCompositionIs(false) .andIfUsesHdr(true) - .withDisplayBrightnessNits(kUnknownLuminance) + .withDisplayBrightnessNits(kDisplayLuminance) .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR) .withRenderIntent( aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC) @@ -3961,7 +3960,7 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, {.physicalDisplay = kDefaultOutputDestinationClip, .clip = kDefaultOutputViewport, .maxLuminance = kDefaultMaxLuminance, - .currentLuminanceNits = kDefaultMaxLuminance, + .currentLuminanceNits = kDisplayLuminance, .outputDataspace = kDefaultOutputDataspace, .colorTransform = kDefaultColorTransformMat, .deviceHandlesColorTransform = true, -- GitLab From 10cea3ca2518916a9bdc2a6f2ead21d760549809 Mon Sep 17 00:00:00 2001 From: Tianhao Yao Date: Wed, 30 Mar 2022 01:37:22 +0000 Subject: [PATCH 0060/1310] Allow using custom widths and colors for layer borders. Update the enableBorder API in SurfaceComposerClient so that width and color can be specified for the border. The information propagates through the pipe all the way to the render engine so it can render the correct color and width for the border Test:go/wm-smoke. Test: LayerBorder_test Bug: 226529222 Change-Id: Id3ab853d5b4d6899a915f729b0d7be701cb5b167 --- libs/gui/LayerState.cpp | 17 ++++++ libs/gui/SurfaceComposerClient.cpp | 4 +- libs/gui/include/gui/LayerState.h | 3 + libs/gui/include/gui/SurfaceComposerClient.h | 3 +- .../include/renderengine/BorderRenderInfo.h | 5 +- libs/renderengine/skia/SkiaGLRenderEngine.cpp | 10 +--- libs/renderengine/tests/RenderEngineTest.cpp | 2 + .../CompositionRefreshArgs.h | 2 + .../CompositionEngine/src/Output.cpp | 11 +++- services/surfaceflinger/Layer.cpp | 14 ++++- services/surfaceflinger/Layer.h | 6 +- services/surfaceflinger/SurfaceFlinger.cpp | 4 +- .../surfaceflinger/tests/LayerBorder_test.cpp | 59 +++++++++++++++---- 13 files changed, 113 insertions(+), 27 deletions(-) diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 0d3b412842..7f0f6381e1 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -102,6 +102,11 @@ status_t layer_state_t::write(Parcel& output) const SAFE_PARCEL(output.writeUint32, transform); SAFE_PARCEL(output.writeBool, transformToDisplayInverse); SAFE_PARCEL(output.writeBool, borderEnabled); + SAFE_PARCEL(output.writeFloat, borderWidth); + SAFE_PARCEL(output.writeFloat, borderColor.r); + SAFE_PARCEL(output.writeFloat, borderColor.g); + SAFE_PARCEL(output.writeFloat, borderColor.b); + SAFE_PARCEL(output.writeFloat, borderColor.a); SAFE_PARCEL(output.writeUint32, static_cast(dataspace)); SAFE_PARCEL(output.write, hdrMetadata); SAFE_PARCEL(output.write, surfaceDamageRegion); @@ -202,6 +207,16 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(input.readUint32, &transform); SAFE_PARCEL(input.readBool, &transformToDisplayInverse); SAFE_PARCEL(input.readBool, &borderEnabled); + SAFE_PARCEL(input.readFloat, &tmpFloat); + borderWidth = tmpFloat; + SAFE_PARCEL(input.readFloat, &tmpFloat); + borderColor.r = tmpFloat; + SAFE_PARCEL(input.readFloat, &tmpFloat); + borderColor.g = tmpFloat; + SAFE_PARCEL(input.readFloat, &tmpFloat); + borderColor.b = tmpFloat; + SAFE_PARCEL(input.readFloat, &tmpFloat); + borderColor.a = tmpFloat; uint32_t tmpUint32 = 0; SAFE_PARCEL(input.readUint32, &tmpUint32); @@ -555,6 +570,8 @@ void layer_state_t::merge(const layer_state_t& other) { if (other.what & eRenderBorderChanged) { what |= eRenderBorderChanged; borderEnabled = other.borderEnabled; + borderWidth = other.borderWidth; + borderColor = other.borderColor; } if (other.what & eFrameRateSelectionPriority) { what |= eFrameRateSelectionPriority; diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index caab506cf4..6fc813b043 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -1940,7 +1940,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDropI } SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::enableBorder( - const sp& sc, bool shouldEnable) { + const sp& sc, bool shouldEnable, float width, const half4& color) { layer_state_t* s = getLayerState(sc); if (!s) { mStatus = BAD_INDEX; @@ -1949,6 +1949,8 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::enableBo s->what |= layer_state_t::eRenderBorderChanged; s->borderEnabled = shouldEnable; + s->borderWidth = width; + s->borderColor = color; registerSurfaceControlForCallback(sc); return *this; diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 4621d2b24e..37a159512f 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -295,6 +295,9 @@ struct layer_state_t { // Flag to indicate if border needs to be enabled on the layer bool borderEnabled; + float borderWidth; + half4 borderColor; + // Stretch effect to be applied to this layer StretchEffect stretchEffect; diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 2b24c3f091..569dbf89c4 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -625,7 +625,8 @@ public: const Rect& destinationFrame); Transaction& setDropInputMode(const sp& sc, gui::DropInputMode mode); - Transaction& enableBorder(const sp& sc, bool shouldEnable); + Transaction& enableBorder(const sp& sc, bool shouldEnable, float width, + const half4& color); status_t setDisplaySurface(const sp& token, const sp& bufferProducer); diff --git a/libs/renderengine/include/renderengine/BorderRenderInfo.h b/libs/renderengine/include/renderengine/BorderRenderInfo.h index 85d55fc958..0ee6661f33 100644 --- a/libs/renderengine/include/renderengine/BorderRenderInfo.h +++ b/libs/renderengine/include/renderengine/BorderRenderInfo.h @@ -22,10 +22,13 @@ namespace android { namespace renderengine { struct BorderRenderInfo { + float width = 0; + half4 color; Region combinedRegion; bool operator==(const BorderRenderInfo& rhs) const { - return (combinedRegion.hasSameRects(rhs.combinedRegion)); + return (width == rhs.width && color == rhs.color && + combinedRegion.hasSameRects(rhs.combinedRegion)); } }; diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index ec9ad54b56..c9f9ec362a 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -1247,15 +1247,11 @@ void SkiaGLRenderEngine::drawLayersInternal( } for (const auto& borderRenderInfo : display.borderInfoList) { SkPaint p; - // TODO (b/225977175): Use specified color - p.setColor(SkColor4f{.fR = 255 / 255.0f, - .fG = 128 / 255.0f, - .fB = 0 / 255.0f, - .fA = 255 / 255.0f}); + p.setColor(SkColor4f{borderRenderInfo.color.r, borderRenderInfo.color.g, + borderRenderInfo.color.b, borderRenderInfo.color.a}); p.setAntiAlias(true); p.setStyle(SkPaint::kStroke_Style); - // TODO (b/225977175): Use specified width - p.setStrokeWidth(20); + p.setStrokeWidth(borderRenderInfo.width); SkRegion sk_region; SkPath path; diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index df1b985bd6..61af698d01 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -2433,6 +2433,8 @@ TEST_P(RenderEngineTest, testBorder) { display.borderInfoList.clear(); renderengine::BorderRenderInfo info; info.combinedRegion = Region(Rect(99, 99, 199, 199)); + info.width = 20.0f; + info.color = half4{1.0f, 128.0f / 255.0f, 0.0f, 1.0f}; display.borderInfoList.emplace_back(info); const auto greenBuffer = allocateAndFillSourceBuffer(1, 1, ubyte4(0, 255, 0, 255)); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h index 8039bbacb9..f861fc97e4 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h @@ -33,6 +33,8 @@ using Layers = std::vector>; using Outputs = std::vector>; struct BorderRenderInfo { + float width = 0; + half4 color; std::vector layerIds; }; /** diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 430d6734fe..c65191c6f5 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -753,8 +753,13 @@ void Output::updateCompositionStateForBorder( for (const auto& id : borderInfo.layerIds) { info.combinedRegion.orSelf(*(layerVisibleRegionMap[id])); } - outputCompositionState.borderInfoList.emplace_back(std::move(info)); - clientComposeTopLayer |= !info.combinedRegion.isEmpty(); + + if (!info.combinedRegion.isEmpty()) { + info.width = borderInfo.width; + info.color = borderInfo.color; + outputCompositionState.borderInfoList.emplace_back(std::move(info)); + clientComposeTopLayer = true; + } } // In this situation we must client compose the top layer instead of using hwc @@ -1218,6 +1223,8 @@ std::optional Output::composeSurfaces( clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix; for (auto& info : outputState.borderInfoList) { renderengine::BorderRenderInfo borderInfo; + borderInfo.width = info.width; + borderInfo.color = info.color; borderInfo.combinedRegion = info.combinedRegion; clientCompositionDisplay.borderInfoList.emplace_back(std::move(borderInfo)); } diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index d47e423ebe..2d3b2375e1 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -1157,11 +1157,13 @@ StretchEffect Layer::getStretchEffect() const { return StretchEffect{}; } -bool Layer::enableBorder(bool shouldEnable) { - if (mBorderEnabled == shouldEnable) { +bool Layer::enableBorder(bool shouldEnable, float width, const half4& color) { + if (mBorderEnabled == shouldEnable && mBorderWidth == width && mBorderColor == color) { return false; } mBorderEnabled = shouldEnable; + mBorderWidth = width; + mBorderColor = color; return true; } @@ -1169,6 +1171,14 @@ bool Layer::isBorderEnabled() { return mBorderEnabled; } +float Layer::getBorderWidth() { + return mBorderWidth; +} + +const half4& Layer::getBorderColor() { + return mBorderColor; +} + bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* transactionNeeded) { // The frame rate for layer tree is this layer's frame rate if present, or the parent frame rate const auto frameRate = [&] { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 85187e1691..60c97f99fd 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -895,8 +895,10 @@ public: bool setStretchEffect(const StretchEffect& effect); StretchEffect getStretchEffect() const; - bool enableBorder(bool shouldEnable); + bool enableBorder(bool shouldEnable, float width, const half4& color); bool isBorderEnabled(); + float getBorderWidth(); + const half4& getBorderColor(); virtual bool setBufferCrop(const Rect& /* bufferCrop */) { return false; } virtual bool setDestinationFrame(const Rect& /* destinationFrame */) { return false; } @@ -1149,6 +1151,8 @@ private: bool findInHierarchy(const sp&); bool mBorderEnabled = false; + float mBorderWidth; + half4 mBorderColor; }; std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 1d5d3539b2..82d8580a9b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2179,6 +2179,8 @@ void SurfaceFlinger::composite(nsecs_t frameTime, int64_t vsyncId) mDrawingState.traverse([&refreshArgs](Layer* layer) { if (layer->isBorderEnabled()) { compositionengine::BorderRenderInfo info; + info.width = layer->getBorderWidth(); + info.color = layer->getBorderColor(); layer->traverse(LayerVector::StateSet::Drawing, [&info](Layer* ilayer) { info.layerIds.push_back(ilayer->getSequence()); }); @@ -4462,7 +4464,7 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime if (layer->setBlurRegions(s.blurRegions)) flags |= eTraversalNeeded; } if (what & layer_state_t::eRenderBorderChanged) { - if (layer->enableBorder(s.borderEnabled)) { + if (layer->enableBorder(s.borderEnabled, s.borderWidth, s.borderColor)) { flags |= eTraversalNeeded; } } diff --git a/services/surfaceflinger/tests/LayerBorder_test.cpp b/services/surfaceflinger/tests/LayerBorder_test.cpp index 4b382140af..f80c7055da 100644 --- a/services/surfaceflinger/tests/LayerBorder_test.cpp +++ b/services/surfaceflinger/tests/LayerBorder_test.cpp @@ -36,7 +36,7 @@ protected: const auto display = SurfaceComposerClient::getInternalDisplayToken(); ASSERT_FALSE(display == nullptr); - + mColorOrange = toHalf4({255, 140, 0, 255}); mParentLayer = createColorLayer("Parent layer", Color::RED); mContainerLayer = mClient->createSurface(String8("Container Layer"), 0 /* width */, @@ -82,6 +82,7 @@ protected: std::function toHalf3; std::function toHalf4; sp mParentLayer, mContainerLayer, mEffectLayer1, mEffectLayer2; + half4 mColorOrange; }; TEST_F(LayerBorderTest, OverlappingVisibleRegions) { @@ -89,7 +90,7 @@ TEST_F(LayerBorderTest, OverlappingVisibleRegions) { t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); - t.enableBorder(mContainerLayer, true); + t.enableBorder(mContainerLayer, true, 20, mColorOrange); t.show(mEffectLayer1); t.show(mEffectLayer2); t.show(mContainerLayer); @@ -101,7 +102,7 @@ TEST_F(LayerBorderTest, PartiallyCoveredVisibleRegion) { t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); - t.enableBorder(mEffectLayer1, true); + t.enableBorder(mEffectLayer1, true, 20, mColorOrange); t.show(mEffectLayer1); t.show(mEffectLayer2); t.show(mContainerLayer); @@ -113,7 +114,7 @@ TEST_F(LayerBorderTest, NonOverlappingVisibleRegion) { t.setCrop(mEffectLayer1, Rect(0, 0, 200, 200)); t.setCrop(mEffectLayer2, Rect(400, 400, 600, 600)); - t.enableBorder(mContainerLayer, true); + t.enableBorder(mContainerLayer, true, 20, mColorOrange); t.show(mEffectLayer1); t.show(mEffectLayer2); t.show(mContainerLayer); @@ -125,7 +126,7 @@ TEST_F(LayerBorderTest, EmptyVisibleRegion) { t.setCrop(mEffectLayer1, Rect(200, 200, 400, 400)); t.setCrop(mEffectLayer2, Rect(0, 0, 600, 600)); - t.enableBorder(mEffectLayer1, true); + t.enableBorder(mEffectLayer1, true, 20, mColorOrange); t.show(mEffectLayer1); t.show(mEffectLayer2); t.show(mContainerLayer); @@ -140,7 +141,7 @@ TEST_F(LayerBorderTest, ZOrderAdjustment) { t.setLayer(mEffectLayer1, 30); t.setLayer(mEffectLayer2, 20); - t.enableBorder(mEffectLayer1, true); + t.enableBorder(mEffectLayer1, true, 20, mColorOrange); t.show(mEffectLayer1); t.show(mEffectLayer2); t.show(mContainerLayer); @@ -169,7 +170,7 @@ TEST_F(LayerBorderTest, GrandChildHierarchy) { t.setCrop(effectLayer3, Rect(400, 400, 800, 800)); t.setColor(effectLayer3, toHalf3(Color::BLUE)); - t.enableBorder(mContainerLayer, true); + t.enableBorder(mContainerLayer, true, 20, mColorOrange); t.show(mEffectLayer1); t.show(mEffectLayer2); t.show(effectLayer3); @@ -183,7 +184,7 @@ TEST_F(LayerBorderTest, TransparentAlpha) { t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); t.setAlpha(mEffectLayer1, 0.0f); - t.enableBorder(mEffectLayer1, true); + t.enableBorder(mContainerLayer, true, 20, mColorOrange); t.show(mEffectLayer1); t.show(mEffectLayer2); t.show(mContainerLayer); @@ -196,7 +197,7 @@ TEST_F(LayerBorderTest, SemiTransparentAlpha) { t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); t.setAlpha(mEffectLayer2, 0.5f); - t.enableBorder(mEffectLayer2, true); + t.enableBorder(mEffectLayer2, true, 20, mColorOrange); t.show(mEffectLayer1); t.show(mEffectLayer2); t.show(mContainerLayer); @@ -208,7 +209,7 @@ TEST_F(LayerBorderTest, InvisibleLayers) { t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); - t.enableBorder(mContainerLayer, true); + t.enableBorder(mContainerLayer, true, 20, mColorOrange); t.hide(mEffectLayer2); t.show(mContainerLayer); }); @@ -237,7 +238,43 @@ TEST_F(LayerBorderTest, BufferStateLayer) { t.setBuffer(bufferStateLayer, buffer); t.setPosition(bufferStateLayer, 100, 100); t.show(bufferStateLayer); - t.enableBorder(mContainerLayer, true); + t.enableBorder(mContainerLayer, true, 20, mColorOrange); + }); +} + +TEST_F(LayerBorderTest, CustomWidth) { + asTransaction([&](Transaction& t) { + t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); + t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); + + t.enableBorder(mContainerLayer, true, 50, mColorOrange); + t.show(mEffectLayer1); + t.show(mEffectLayer2); + t.show(mContainerLayer); + }); +} + +TEST_F(LayerBorderTest, CustomColor) { + asTransaction([&](Transaction& t) { + t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); + t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); + + t.enableBorder(mContainerLayer, true, 20, toHalf4({255, 0, 255, 255})); + t.show(mEffectLayer1); + t.show(mEffectLayer2); + t.show(mContainerLayer); + }); +} + +TEST_F(LayerBorderTest, CustomWidthAndColorAndOpacity) { + asTransaction([&](Transaction& t) { + t.setCrop(mEffectLayer1, Rect(0, 0, 200, 200)); + t.setCrop(mEffectLayer2, Rect(400, 400, 600, 600)); + + t.enableBorder(mContainerLayer, true, 40, toHalf4({255, 255, 0, 128})); + t.show(mEffectLayer1); + t.show(mEffectLayer2); + t.show(mContainerLayer); }); } -- GitLab From 0a66b9204623c5521ac4a75f851ea9a18c44671a Mon Sep 17 00:00:00 2001 From: Samiul Islam Date: Sat, 7 May 2022 23:22:45 +0100 Subject: [PATCH 0061/1310] Include cache files from sdk data when cleaning using freeCache SDK data should be counted towards the app that uses them. As such, even though sdk data is owned by sdk sandbox uid, we convert it back to the app uid so that sdk cache eats away from app's quota. Bug: 230874001 Test: atest SdkSandboxStorageHostTest Ignore-AOSP-First: Test is missing in AOSP. Will cherry-pick to AOSP and TM dev after merging on master. Change-Id: Iea2c6e7d265cd097be34ee2e5299922c5269741f --- cmds/installd/InstalldNativeService.cpp | 30 +++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp index 29ff815b70..0433ac04f0 100644 --- a/cmds/installd/InstalldNativeService.cpp +++ b/cmds/installd/InstalldNativeService.cpp @@ -1960,17 +1960,43 @@ binder::Status InstalldNativeService::freeCache(const std::optional #endif // GRANULAR_LOCKS FTS *fts; FTSENT *p; + + // Create a list of data paths whose children have cache directories auto ce_path = create_data_user_ce_path(uuid_, userId); auto de_path = create_data_user_de_path(uuid_, userId); auto media_path = findDataMediaPath(uuid, userId) + "/Android/data/"; - char *argv[] = { (char*) ce_path.c_str(), (char*) de_path.c_str(), - (char*) media_path.c_str(), nullptr }; + auto ce_sdk_path = create_data_misc_sdk_sandbox_path(uuid_, /*isCeData=*/true, userId); + auto de_sdk_path = create_data_misc_sdk_sandbox_path(uuid_, /*isCeData=*/false, userId); + + std::vector dataPaths = {ce_path, de_path, media_path}; + foreach_subdir(ce_sdk_path, [&ce_sdk_path, &dataPaths](const std::string subDir) { + const auto fullpath = ce_sdk_path + "/" + subDir; + dataPaths.push_back(fullpath); + }); + foreach_subdir(de_sdk_path, [&de_sdk_path, &dataPaths](const std::string subDir) { + const auto fullpath = de_sdk_path + "/" + subDir; + dataPaths.push_back((char*)fullpath.c_str()); + }); + + char* argv[dataPaths.size() + 1]; + for (unsigned int i = 0; i < dataPaths.size(); i++) { + argv[i] = (char*)dataPaths[i].c_str(); + } + argv[dataPaths.size()] = nullptr; + if (!(fts = fts_open(argv, FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV, nullptr))) { return error("Failed to fts_open"); } while ((p = fts_read(fts)) != nullptr) { if (p->fts_info == FTS_D && p->fts_level == 1) { uid_t uid = p->fts_statp->st_uid; + + // If uid belongs to sdk sandbox, then the cache should be attributed to the + // original client app. + const auto client_uid = multiuser_convert_sdk_sandbox_to_app_uid(uid); + const bool isSandboxUid = (client_uid != (uid_t)-1); + if (isSandboxUid) uid = client_uid; + if (multiuser_get_app_id(uid) == AID_MEDIA_RW) { uid = (multiuser_get_app_id(p->fts_statp->st_gid) - AID_EXT_GID_START) + AID_APP_START; -- GitLab From 0ced3cc7a06eb013fa935f59285a0ab043efa3a3 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 21 Nov 2017 15:33:17 -0800 Subject: [PATCH 0062/1310] Add touch resampling native tests Add native tests for touch resampling. These test the resampling logic in InputTransport.cpp by sending a pre-determined set of InputEvents via InputPublisher and read the events back from InputConsumer. The events that are read back are compared to pre-determined set of events. Bug: 35412046 Bug: 68840121 Bug: 119214052 Test: atest libinput_tests Change-Id: I596a6528b8ab27a68d0e9baafa3a8cb6b62d0422 --- include/input/InputTransport.h | 5 +- libs/input/InputTransport.cpp | 20 +- libs/input/tests/Android.bp | 1 + .../tests/InputPublisherAndConsumer_test.cpp | 7 - libs/input/tests/TouchResampling_test.cpp | 562 ++++++++++++++++++ 5 files changed, 583 insertions(+), 12 deletions(-) create mode 100644 libs/input/tests/TouchResampling_test.cpp diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h index 5f9a37d69c..dbc7bfa388 100644 --- a/include/input/InputTransport.h +++ b/include/input/InputTransport.h @@ -452,8 +452,11 @@ private: */ class InputConsumer { public: - /* Creates a consumer associated with an input channel. */ + /* Create a consumer associated with an input channel. */ explicit InputConsumer(const std::shared_ptr& channel); + /* Create a consumer associated with an input channel, override resampling system property */ + explicit InputConsumer(const std::shared_ptr& channel, + bool enableTouchResampling); /* Destroys the consumer and releases its input channel. */ ~InputConsumer(); diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp index 61950522ff..8d8433b973 100644 --- a/libs/input/InputTransport.cpp +++ b/libs/input/InputTransport.cpp @@ -51,7 +51,7 @@ static const nsecs_t NANOS_PER_MS = 1000000; // Latency added during resampling. A few milliseconds doesn't hurt much but // reduces the impact of mispredicted touch positions. -static const nsecs_t RESAMPLE_LATENCY = 5 * NANOS_PER_MS; +const std::chrono::duration RESAMPLE_LATENCY = 5ms; // Minimum time difference between consecutive samples before attempting to resample. static const nsecs_t RESAMPLE_MIN_DELTA = 2 * NANOS_PER_MS; @@ -721,7 +721,11 @@ android::base::Result InputPublisher::receiveC // --- InputConsumer --- InputConsumer::InputConsumer(const std::shared_ptr& channel) - : mResampleTouch(isTouchResamplingEnabled()), mChannel(channel), mMsgDeferred(false) {} + : InputConsumer(channel, isTouchResamplingEnabled()) {} + +InputConsumer::InputConsumer(const std::shared_ptr& channel, + bool enableTouchResampling) + : mResampleTouch(enableTouchResampling), mChannel(channel), mMsgDeferred(false) {} InputConsumer::~InputConsumer() { } @@ -751,7 +755,10 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum // Receive a fresh message. status_t result = mChannel->receiveMessage(&mMsg); if (result == OK) { - mConsumeTimes.emplace(mMsg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC)); + const auto [_, inserted] = + mConsumeTimes.emplace(mMsg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC)); + LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32, + mMsg.header.seq); } if (result) { // Consume the next batched event unless batches are being held for later. @@ -918,7 +925,7 @@ status_t InputConsumer::consumeBatch(InputEventFactoryInterface* factory, nsecs_t sampleTime = frameTime; if (mResampleTouch) { - sampleTime -= RESAMPLE_LATENCY; + sampleTime -= std::chrono::nanoseconds(RESAMPLE_LATENCY).count(); } ssize_t split = findSampleNoLaterThan(batch, sampleTime); if (split < 0) { @@ -1166,6 +1173,11 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, return; } + if (current->eventTime == sampleTime) { + // Prevents having 2 events with identical times and coordinates. + return; + } + // Resample touch coordinates. History oldLastResample; oldLastResample.initializeFrom(touchState.lastResample); diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index 6ffe8518b6..54f7586d0a 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -16,6 +16,7 @@ cc_test { "InputDevice_test.cpp", "InputEvent_test.cpp", "InputPublisherAndConsumer_test.cpp", + "TouchResampling_test.cpp", "TouchVideoFrame_test.cpp", "VelocityTracker_test.cpp", "VerifiedInputEvent_test.cpp", diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp index 05bc0bcbe8..70e4fda662 100644 --- a/libs/input/tests/InputPublisherAndConsumer_test.cpp +++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp @@ -16,17 +16,10 @@ #include "TestHelpers.h" -#include -#include -#include - #include -#include #include #include #include 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 literal 0 HcmV?d00001 diff --git a/services/surfaceflinger/tests/tracing/testdata/transactions_trace_nodisplayfound.winscope b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_nodisplayfound.winscope new file mode 100644 index 0000000000000000000000000000000000000000..cd62ab8ce08615a1fa97d2ff760e87ee3a0e583f GIT binary patch literal 554577 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 literal 0 HcmV?d00001 -- GitLab From 286f4f9e6c32c0b906eba7c103964aa55ba1f74a Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 8 Jun 2022 16:37:39 -0700 Subject: [PATCH 0081/1310] LayerTraceGenerator: Fix duplicate layer ids Layers can be created from API calls on the binder thread or by surfaceflinger on the main thread for mirroring and BSL background layers. Transaction traces keep track of layer creation API calls to recreate layers when generating the layer trace. However, there was a possibility that new layers would be recorded with a set of transactions that were applied with an earlier vsync. This is harmless except we could end up creating layers with duplicate layer ids since layers created via binder would get an injected sequence id while main thread created layers would use the global counter. Test: atest transactiontrace_testsuite Bug: 235376060 Change-Id: Ia11839d1880bc131ab5314779281c93393d6742f --- services/surfaceflinger/SurfaceFlinger.cpp | 22 ++-- services/surfaceflinger/SurfaceFlinger.h | 5 +- .../Tracing/TransactionProtoParser.cpp | 9 +- .../Tracing/TransactionTracing.cpp | 45 ++++++-- .../Tracing/TransactionTracing.h | 6 +- .../Tracing/tools/LayerTraceGenerator.cpp | 2 +- .../tracing/TransactionTraceTestSuite.cpp | 102 +++++++++++++++--- .../unittests/TransactionTracingTest.cpp | 6 +- 8 files changed, 156 insertions(+), 41 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 3043809c8a..79320cdd12 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2056,6 +2056,11 @@ bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expected mGpuFrameMissedCount++; } + if (mTracingEnabledChanged) { + mLayerTracingEnabled = mLayerTracing.isEnabled(); + mTracingEnabledChanged = false; + } + // If we are in the middle of a mode change and the fence hasn't // fired yet just wait for the next commit. if (mSetActiveModePending) { @@ -2104,11 +2109,6 @@ bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expected } } - if (mTracingEnabledChanged) { - mLayerTracingEnabled = mLayerTracing.isEnabled(); - mTracingEnabledChanged = false; - } - if (mRefreshRateOverlaySpinner) { Mutex::Autolock lock(mStateLock); if (const auto display = getDefaultDisplayDeviceLocked()) { @@ -2123,7 +2123,7 @@ bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expected bool needsTraversal = false; if (clearTransactionFlags(eTransactionFlushNeeded)) { - needsTraversal |= commitCreatedLayers(); + needsTraversal |= commitCreatedLayers(vsyncId); needsTraversal |= flushTransactionQueues(vsyncId); } @@ -7154,7 +7154,7 @@ int SurfaceFlinger::getMaxAcquiredBufferCountForRefreshRate(Fps refreshRate) con return calculateMaxAcquiredBufferCount(refreshRate, presentLatency); } -void SurfaceFlinger::handleLayerCreatedLocked(const LayerCreatedState& state) { +void SurfaceFlinger::handleLayerCreatedLocked(const LayerCreatedState& state, int64_t vsyncId) { sp layer = state.layer.promote(); if (!layer) { ALOGD("Layer was destroyed soon after creation %p", state.layer.unsafe_get()); @@ -7184,7 +7184,9 @@ void SurfaceFlinger::handleLayerCreatedLocked(const LayerCreatedState& state) { } layer->updateTransformHint(mActiveDisplayTransformHint); - + if (mTransactionTracing) { + mTransactionTracing->onLayerAddedToDrawingState(layer->getSequence(), vsyncId); + } mInterceptor->saveSurfaceCreation(layer); } @@ -7268,7 +7270,7 @@ std::shared_ptr SurfaceFlinger::getExternalTextur return buffer; } -bool SurfaceFlinger::commitCreatedLayers() { +bool SurfaceFlinger::commitCreatedLayers(int64_t vsyncId) { std::vector createdLayers; { std::scoped_lock lock(mCreatedLayersLock); @@ -7281,7 +7283,7 @@ bool SurfaceFlinger::commitCreatedLayers() { Mutex::Autolock _l(mStateLock); for (const auto& createdLayer : createdLayers) { - handleLayerCreatedLocked(createdLayer); + handleLayerCreatedLocked(createdLayer, vsyncId); } createdLayers.clear(); mLayersAdded = true; diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 41de2b6b59..f7e061fff5 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1405,8 +1405,9 @@ private: // A temporay pool that store the created layers and will be added to current state in main // thread. std::vector mCreatedLayers GUARDED_BY(mCreatedLayersLock); - bool commitCreatedLayers(); - void handleLayerCreatedLocked(const LayerCreatedState& state) REQUIRES(mStateLock); + bool commitCreatedLayers(int64_t vsyncId); + void handleLayerCreatedLocked(const LayerCreatedState& state, int64_t vsyncId) + REQUIRES(mStateLock); std::atomic mActiveDisplayTransformHint; diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp index 8ec6c99eb9..65918f6ab7 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp @@ -498,8 +498,13 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta inputInfo.replaceTouchableRegionWithCrop = windowInfoProto.replace_touchable_region_with_crop(); int64_t layerId = windowInfoProto.crop_layer_id(); - inputInfo.touchableRegionCropHandle = - mMapper->getLayerHandle(static_cast(layerId)); + if (layerId != -1) { + inputInfo.touchableRegionCropHandle = + mMapper->getLayerHandle(static_cast(layerId)); + } else { + inputInfo.touchableRegionCropHandle = nullptr; + } + layer.windowInfoHandle = sp::make(inputInfo); } if (proto.what() & layer_state_t::eBackgroundColorChanged) { diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp index 6381758c7b..e53fecac98 100644 --- a/services/surfaceflinger/Tracing/TransactionTracing.cpp +++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp @@ -152,17 +152,33 @@ void TransactionTracing::addQueuedTransaction(const TransactionState& transactio mTransactionQueue.push(state); } -void TransactionTracing::addCommittedTransactions(std::vector& transactions, - int64_t vsyncId) { +TransactionTracing::CommittedTransactions& +TransactionTracing::findOrCreateCommittedTransactionRecord(int64_t vsyncId) { + for (auto& pendingTransaction : mPendingTransactions) { + if (pendingTransaction.vsyncId == vsyncId) { + return pendingTransaction; + } + } + CommittedTransactions committedTransactions; committedTransactions.vsyncId = vsyncId; committedTransactions.timestamp = systemTime(); + mPendingTransactions.emplace_back(committedTransactions); + return mPendingTransactions.back(); +} + +void TransactionTracing::onLayerAddedToDrawingState(int layerId, int64_t vsyncId) { + CommittedTransactions& committedTransactions = findOrCreateCommittedTransactionRecord(vsyncId); + committedTransactions.createdLayerIds.emplace_back(layerId); +} + +void TransactionTracing::addCommittedTransactions(std::vector& transactions, + int64_t vsyncId) { + CommittedTransactions& committedTransactions = findOrCreateCommittedTransactionRecord(vsyncId); committedTransactions.transactionIds.reserve(transactions.size()); for (const auto& transaction : transactions) { committedTransactions.transactionIds.emplace_back(transaction.id); } - - mPendingTransactions.emplace_back(committedTransactions); tryPushToTracingThread(); } @@ -235,15 +251,24 @@ void TransactionTracing::addEntry(const std::vector& comm for (const CommittedTransactions& entry : committedTransactions) { entryProto.set_elapsed_realtime_nanos(entry.timestamp); entryProto.set_vsync_id(entry.vsyncId); - entryProto.mutable_added_layers()->Reserve(static_cast(mCreatedLayers.size())); - for (auto& newLayer : mCreatedLayers) { - entryProto.mutable_added_layers()->Add(std::move(newLayer)); + entryProto.mutable_added_layers()->Reserve( + static_cast(entry.createdLayerIds.size())); + + for (const int32_t& id : entry.createdLayerIds) { + auto it = mCreatedLayers.find(id); + if (it != mCreatedLayers.end()) { + entryProto.mutable_added_layers()->Add(std::move(it->second)); + mCreatedLayers.erase(it); + } else { + ALOGW("Could not created layer with id %d", id); + } } + entryProto.mutable_removed_layers()->Reserve(static_cast(removedLayers.size())); for (auto& removedLayer : removedLayers) { entryProto.mutable_removed_layers()->Add(removedLayer); + mCreatedLayers.erase(removedLayer); } - mCreatedLayers.clear(); entryProto.mutable_transactions()->Reserve( static_cast(entry.transactionIds.size())); for (const uint64_t& id : entry.transactionIds) { @@ -304,7 +329,7 @@ void TransactionTracing::onLayerAdded(BBinder* layerHandle, int layerId, const s ALOGW("Duplicate handles found. %p", layerHandle); } mLayerHandles[layerHandle] = layerId; - mCreatedLayers.push_back(mProtoParser.toProto(args)); + mCreatedLayers[layerId] = mProtoParser.toProto(args); } void TransactionTracing::onMirrorLayerAdded(BBinder* layerHandle, int layerId, @@ -315,7 +340,7 @@ void TransactionTracing::onMirrorLayerAdded(BBinder* layerHandle, int layerId, ALOGW("Duplicate handles found. %p", layerHandle); } mLayerHandles[layerHandle] = layerId; - mCreatedLayers.emplace_back(mProtoParser.toProto(args)); + mCreatedLayers[layerId] = mProtoParser.toProto(args); } void TransactionTracing::onLayerRemoved(int32_t layerId) { diff --git a/services/surfaceflinger/Tracing/TransactionTracing.h b/services/surfaceflinger/Tracing/TransactionTracing.h index 4c291f960f..2f5ee87039 100644 --- a/services/surfaceflinger/Tracing/TransactionTracing.h +++ b/services/surfaceflinger/Tracing/TransactionTracing.h @@ -64,6 +64,7 @@ public: int mirrorFromId); void onLayerRemoved(int layerId); void onHandleRemoved(BBinder* layerHandle); + void onLayerAddedToDrawingState(int layerId, int64_t vsyncId); void dump(std::string&) const; static constexpr auto CONTINUOUS_TRACING_BUFFER_SIZE = 512 * 1024; static constexpr auto ACTIVE_TRACING_BUFFER_SIZE = 100 * 1024 * 1024; @@ -81,7 +82,7 @@ private: GUARDED_BY(mTraceLock); LocklessStack mTransactionQueue; nsecs_t mStartingTimestamp GUARDED_BY(mTraceLock); - std::vector mCreatedLayers GUARDED_BY(mTraceLock); + std::unordered_map mCreatedLayers GUARDED_BY(mTraceLock); std::unordered_map mLayerHandles GUARDED_BY(mTraceLock); std::vector mRemovedLayerHandles GUARDED_BY(mTraceLock); @@ -100,6 +101,7 @@ private: std::condition_variable mTransactionsAddedToBufferCv; struct CommittedTransactions { std::vector transactionIds; + std::vector createdLayerIds; int64_t vsyncId; int64_t timestamp; }; @@ -117,7 +119,7 @@ private: void tryPushToTracingThread() EXCLUDES(mMainThreadLock); void addStartingStateToProtoLocked(proto::TransactionTraceFile& proto) REQUIRES(mTraceLock); void updateStartingStateLocked(const proto::TransactionTraceEntry& entry) REQUIRES(mTraceLock); - + CommittedTransactions& findOrCreateCommittedTransactionRecord(int64_t vsyncId); // TEST // Wait until all the committed transactions for the specified vsync id are added to the buffer. void flush(int64_t vsyncId) EXCLUDES(mMainThreadLock); diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index 5bb9d2f447..ea33eb5c54 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -248,7 +248,7 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, (dataMapper->mLayerHandles.find(tracingArgs.parentId) == dataMapper->mLayerHandles.end())) { args.addToRoot = false; - } else { + } else if (tracingArgs.parentId != -1) { parentHandle = dataMapper->getLayerHandle(tracingArgs.parentId); } mFlinger.createLayer(args, &outHandle, parentHandle, &outLayerId, diff --git a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp index ac4354cde1..0e214af706 100644 --- a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp +++ b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp @@ -83,6 +83,37 @@ protected: std::vector TransactionTraceTestSuite::sTransactionTraces{}; +struct LayerInfo { + int id; + std::string name; + int parent; + int z; + uint64_t curr_frame; + float x; + float y; +}; + +bool operator==(const LayerInfo& lh, const LayerInfo& rh) { + return std::make_tuple(lh.id, lh.name, lh.parent, lh.z, lh.curr_frame) == + std::make_tuple(rh.id, rh.name, rh.parent, rh.z, rh.curr_frame); +} + +bool compareById(const LayerInfo& a, const LayerInfo& b) { + return a.id < b.id; +} + +inline void PrintTo(const LayerInfo& info, ::std::ostream* os) { + *os << "Layer [" << info.id << "] name=" << info.name << " parent=" << info.parent + << " z=" << info.z << " curr_frame=" << info.curr_frame << " x=" << info.x + << " y=" << info.y; +} + +struct find_id : std::unary_function { + int id; + find_id(int id) : id(id) {} + bool operator()(LayerInfo const& m) const { return m.id == id; } +}; + TEST_P(TransactionTraceTestSuite, validateEndState) { ASSERT_GT(mActualLayersTraceProto.entry_size(), 0); ASSERT_GT(mExpectedLayersTraceProto.entry_size(), 0); @@ -92,19 +123,64 @@ TEST_P(TransactionTraceTestSuite, validateEndState) { auto actualLastEntry = mActualLayersTraceProto.entry(mActualLayersTraceProto.entry_size() - 1); EXPECT_EQ(expectedLastEntry.layers().layers_size(), actualLastEntry.layers().layers_size()); - for (int i = 0; - i < expectedLastEntry.layers().layers_size() && i < actualLastEntry.layers().layers_size(); - i++) { - auto expectedLayer = expectedLastEntry.layers().layers(i); - auto actualLayer = actualLastEntry.layers().layers(i); - EXPECT_EQ(expectedLayer.id(), actualLayer.id()); - EXPECT_EQ(expectedLayer.name(), actualLayer.name()); - EXPECT_EQ(expectedLayer.parent(), actualLayer.parent()); - EXPECT_EQ(expectedLayer.z(), actualLayer.z()); - EXPECT_EQ(expectedLayer.curr_frame(), actualLayer.curr_frame()); - ALOGV("Validating %s[%d] parent=%d z=%d frame=%" PRIu64, expectedLayer.name().c_str(), - expectedLayer.id(), expectedLayer.parent(), expectedLayer.z(), - expectedLayer.curr_frame()); + + std::vector expectedLayers; + expectedLayers.reserve(static_cast(expectedLastEntry.layers().layers_size())); + for (int i = 0; i < expectedLastEntry.layers().layers_size(); i++) { + auto layer = expectedLastEntry.layers().layers(i); + expectedLayers.push_back({layer.id(), layer.name(), layer.parent(), layer.z(), + layer.curr_frame(), + layer.has_position() ? layer.position().x() : -1, + layer.has_position() ? layer.position().y() : -1}); + } + std::sort(expectedLayers.begin(), expectedLayers.end(), compareById); + + std::vector actualLayers; + actualLayers.reserve(static_cast(actualLastEntry.layers().layers_size())); + for (int i = 0; i < actualLastEntry.layers().layers_size(); i++) { + auto layer = actualLastEntry.layers().layers(i); + actualLayers.push_back({layer.id(), layer.name(), layer.parent(), layer.z(), + layer.curr_frame(), + layer.has_position() ? layer.position().x() : -1, + layer.has_position() ? layer.position().y() : -1}); + } + std::sort(actualLayers.begin(), actualLayers.end(), compareById); + + size_t i = 0; + for (; i < actualLayers.size() && i < expectedLayers.size(); i++) { + auto it = std::find_if(actualLayers.begin(), actualLayers.end(), + find_id(expectedLayers[i].id)); + EXPECT_NE(it, actualLayers.end()); + EXPECT_EQ(expectedLayers[i], *it); + ALOGV("Validating %s[%d] parent=%d z=%d frame=%" PRIu64, expectedLayers[i].name.c_str(), + expectedLayers[i].id, expectedLayers[i].parent, expectedLayers[i].z, + expectedLayers[i].curr_frame); + } + + EXPECT_EQ(expectedLayers.size(), actualLayers.size()); + + if (i < actualLayers.size()) { + for (size_t j = 0; j < actualLayers.size(); j++) { + if (std::find_if(expectedLayers.begin(), expectedLayers.end(), + find_id(actualLayers[j].id)) == expectedLayers.end()) { + ALOGD("actualLayers [%d]:%s parent=%d z=%d frame=%" PRIu64, actualLayers[j].id, + actualLayers[j].name.c_str(), actualLayers[j].parent, actualLayers[j].z, + actualLayers[j].curr_frame); + } + } + FAIL(); + } + + if (i < expectedLayers.size()) { + for (size_t j = 0; j < expectedLayers.size(); j++) { + if (std::find_if(actualLayers.begin(), actualLayers.end(), + find_id(expectedLayers[j].id)) == actualLayers.end()) { + ALOGD("expectedLayers [%d]:%s parent=%d z=%d frame=%" PRIu64, expectedLayers[j].id, + expectedLayers[j].name.c_str(), expectedLayers[j].parent, expectedLayers[j].z, + expectedLayers[j].curr_frame); + } + } + FAIL(); } } diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp index 61b72a0b9b..8ec918dfb3 100644 --- a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp @@ -127,6 +127,8 @@ protected: std::vector transactions; transactions.emplace_back(transaction); VSYNC_ID_FIRST_LAYER_CHANGE = ++mVsyncId; + mTracing.onLayerAddedToDrawingState(mParentLayerId, VSYNC_ID_FIRST_LAYER_CHANGE); + mTracing.onLayerAddedToDrawingState(mChildLayerId, VSYNC_ID_FIRST_LAYER_CHANGE); mTracing.addCommittedTransactions(transactions, VSYNC_ID_FIRST_LAYER_CHANGE); flush(VSYNC_ID_FIRST_LAYER_CHANGE); } @@ -238,6 +240,8 @@ protected: const sp fakeMirrorLayerHandle = new BBinder(); mTracing.onMirrorLayerAdded(fakeMirrorLayerHandle->localBinder(), mMirrorLayerId, "Mirror", mLayerId); + mTracing.onLayerAddedToDrawingState(mLayerId, mVsyncId); + mTracing.onLayerAddedToDrawingState(mMirrorLayerId, mVsyncId); // add some layer transaction { @@ -257,7 +261,7 @@ protected: std::vector transactions; transactions.emplace_back(transaction); - mTracing.addCommittedTransactions(transactions, ++mVsyncId); + mTracing.addCommittedTransactions(transactions, mVsyncId); flush(mVsyncId); } } -- GitLab From 3e40cddb03c7da6a33c781c67f3189e19b2a27a7 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 8 Jun 2022 16:50:17 -0700 Subject: [PATCH 0082/1310] LayerTraceGenerator: Fix layer deletion Fixes a couple of issues with layer handle tracking which manifested in the generated trace showing layers which were destroyed. 1. Destroyed handles were not written to proto correctly 2. When a handle was destroyed, it was removed from the tracing handle to layer id map immediately. but we need to access this mapping in the tracing thread when writing the transaction to proto. Test: atest transactiontrace_testsuite Bug: 235376060 Change-Id: I021be9a3864bdfc61422fc2f751f9bfd5e674059 --- .../Tracing/TransactionTracing.cpp | 30 ++++++++++++------- .../Tracing/TransactionTracing.h | 4 ++- .../Tracing/tools/LayerTraceGenerator.cpp | 14 +++++---- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp index e53fecac98..af0594e3b6 100644 --- a/services/surfaceflinger/Tracing/TransactionTracing.cpp +++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp @@ -281,6 +281,14 @@ void TransactionTracing::addEntry(const std::vector& comm } } + entryProto.mutable_removed_layer_handles()->Reserve( + static_cast(mRemovedLayerHandles.size())); + for (auto& [handle, layerId] : mRemovedLayerHandles) { + entryProto.mutable_removed_layer_handles()->Add(layerId); + mLayerHandles.erase(handle); + } + mRemovedLayerHandles.clear(); + std::string serializedProto; entryProto.SerializeToString(&serializedProto); entryProto.Clear(); @@ -288,13 +296,6 @@ void TransactionTracing::addEntry(const std::vector& comm removedEntries.reserve(removedEntries.size() + entries.size()); removedEntries.insert(removedEntries.end(), std::make_move_iterator(entries.begin()), std::make_move_iterator(entries.end())); - - entryProto.mutable_removed_layer_handles()->Reserve( - static_cast(mRemovedLayerHandles.size())); - for (auto& handle : mRemovedLayerHandles) { - entryProto.mutable_removed_layer_handles()->Add(handle); - } - mRemovedLayerHandles.clear(); } proto::TransactionTraceEntry removedEntryProto; @@ -355,9 +356,7 @@ void TransactionTracing::onHandleRemoved(BBinder* layerHandle) { ALOGW("handle not found. %p", layerHandle); return; } - - mRemovedLayerHandles.push_back(it->second); - mLayerHandles.erase(it); + mRemovedLayerHandles.emplace_back(layerHandle, it->second); } void TransactionTracing::tryPushToTracingThread() { @@ -401,10 +400,15 @@ void TransactionTracing::updateStartingStateLocked( } } + for (const int32_t removedLayerHandleId : removedEntry.removed_layer_handles()) { + mRemovedLayerHandlesAtStart.insert(removedLayerHandleId); + } + // Clean up stale starting states since the layer has been removed and the buffer does not // contain any references to the layer. for (const int32_t removedLayerId : removedEntry.removed_layers()) { mStartingStates.erase(removedLayerId); + mRemovedLayerHandlesAtStart.erase(removedLayerId); } } @@ -426,6 +430,12 @@ void TransactionTracing::addStartingStateToProtoLocked(proto::TransactionTraceFi transactionProto.set_vsync_id(0); transactionProto.set_post_time(mStartingTimestamp); entryProto->mutable_transactions()->Add(std::move(transactionProto)); + + entryProto->mutable_removed_layer_handles()->Reserve( + static_cast(mRemovedLayerHandlesAtStart.size())); + for (const int32_t removedLayerHandleId : mRemovedLayerHandlesAtStart) { + entryProto->mutable_removed_layer_handles()->Add(removedLayerHandleId); + } } proto::TransactionTraceFile TransactionTracing::writeToProto() { diff --git a/services/surfaceflinger/Tracing/TransactionTracing.h b/services/surfaceflinger/Tracing/TransactionTracing.h index 2f5ee87039..ae01d3c0cc 100644 --- a/services/surfaceflinger/Tracing/TransactionTracing.h +++ b/services/surfaceflinger/Tracing/TransactionTracing.h @@ -85,8 +85,10 @@ private: std::unordered_map mCreatedLayers GUARDED_BY(mTraceLock); std::unordered_map mLayerHandles GUARDED_BY(mTraceLock); - std::vector mRemovedLayerHandles GUARDED_BY(mTraceLock); + std::vector> mRemovedLayerHandles + GUARDED_BY(mTraceLock); std::map mStartingStates GUARDED_BY(mTraceLock); + std::set mRemovedLayerHandlesAtStart GUARDED_BY(mTraceLock); TransactionProtoParser mProtoParser GUARDED_BY(mTraceLock); // Parses the transaction to proto without holding any tracing locks so we can generate proto // in the binder thread without any contention. diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index ea33eb5c54..e85b0bffb7 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -16,6 +16,7 @@ #undef LOG_TAG #define LOG_TAG "LayerTraceGenerator" +//#define LOG_NDEBUG 0 #include #include @@ -227,9 +228,10 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, for (int i = 0; i < traceFile.entry_size(); i++) { proto::TransactionTraceEntry entry = traceFile.entry(i); ALOGV(" Entry %04d/%04d for time=%" PRId64 " vsyncid=%" PRId64 - " layers +%d -%d transactions=%d", + " layers +%d -%d handles -%d transactions=%d", i, traceFile.entry_size(), entry.elapsed_realtime_nanos(), entry.vsync_id(), - entry.added_layers_size(), entry.removed_layers_size(), entry.transactions_size()); + entry.added_layers_size(), entry.removed_layers_size(), + entry.removed_layer_handles_size(), entry.transactions_size()); for (int j = 0; j < entry.added_layers_size(); j++) { // create layers @@ -275,13 +277,13 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, transaction.listenerCallbacks, transaction.id); } - for (int j = 0; j < entry.removed_layer_handles_size(); j++) { - dataMapper->mLayerHandles.erase(entry.removed_layer_handles(j)); - } - frameTime = entry.elapsed_realtime_nanos(); vsyncId = entry.vsync_id(); mFlinger.commit(frameTime, vsyncId); + + for (int j = 0; j < entry.removed_layer_handles_size(); j++) { + dataMapper->mLayerHandles.erase(entry.removed_layer_handles(j)); + } } flinger->stopLayerTracing(outputLayersTracePath); -- GitLab From b877579db39b7428fe2d3e6dee7e4a61cabda59e Mon Sep 17 00:00:00 2001 From: Pablo Gamito Date: Fri, 10 Jun 2022 15:02:42 +0000 Subject: [PATCH 0083/1310] Dump vSync id in layers trace Used to figure out which transactions where applied in a given layers trace entry to synchronize a transition (through the start/finish transactions) with the layers trace for FaaS. Bug: 230462538 Test: Check vSyncId is dumped in layers trace Change-Id: Ib59311f3ae29196316a064ced98445857e45c5b9 --- services/surfaceflinger/SurfaceFlinger.cpp | 7 ++++--- services/surfaceflinger/Tracing/LayerTracing.cpp | 3 ++- services/surfaceflinger/Tracing/LayerTracing.h | 2 +- services/surfaceflinger/layerproto/layerstrace.proto | 4 +++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index f6f71ae064..446d744694 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2168,7 +2168,7 @@ bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expected if (mLayerTracingEnabled && !mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) { // This will block and tracing should only be enabled for debugging. - mLayerTracing.notify(mVisibleRegionsDirty, frameTime); + mLayerTracing.notify(mVisibleRegionsDirty, frameTime, vsyncId); } persistDisplayBrightness(mustComposite); @@ -2295,7 +2295,7 @@ void SurfaceFlinger::composite(nsecs_t frameTime, int64_t vsyncId) mLayersWithQueuedFrames.clear(); if (mLayerTracingEnabled && mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) { // This will block and should only be used for debugging. - mLayerTracing.notify(mVisibleRegionsDirty, frameTime); + mLayerTracing.notify(mVisibleRegionsDirty, frameTime, vsyncId); } mVisibleRegionsWereDirtyThisFrame = mVisibleRegionsDirty; // Cache value for use in post-comp @@ -5846,7 +5846,8 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r (fixedStartingTime) ? fixedStartingTime : systemTime(); mScheduler ->schedule([&]() FTL_FAKE_GUARD(mStateLock) { - mLayerTracing.notify("start", startingTime); + mLayerTracing.notify(true /* visibleRegionDirty */, + startingTime, -1 /* vsyncId */); }) .wait(); } diff --git a/services/surfaceflinger/Tracing/LayerTracing.cpp b/services/surfaceflinger/Tracing/LayerTracing.cpp index 49554c7dd8..1b80fa3c42 100644 --- a/services/surfaceflinger/Tracing/LayerTracing.cpp +++ b/services/surfaceflinger/Tracing/LayerTracing.cpp @@ -98,7 +98,7 @@ void LayerTracing::dump(std::string& result) const { mBuffer->dump(result); } -void LayerTracing::notify(bool visibleRegionDirty, int64_t time) { +void LayerTracing::notify(bool visibleRegionDirty, int64_t time, int64_t vsyncId) { std::scoped_lock lock(mTraceLock); if (!mEnabled) { return; @@ -130,6 +130,7 @@ void LayerTracing::notify(bool visibleRegionDirty, int64_t time) { } mFlinger.dumpDisplayProto(entry); mBuffer->emplace(std::move(entry)); + entry.set_vsync_id(vsyncId); } } // namespace android diff --git a/services/surfaceflinger/Tracing/LayerTracing.h b/services/surfaceflinger/Tracing/LayerTracing.h index 88a19ecdf1..e73dac62e4 100644 --- a/services/surfaceflinger/Tracing/LayerTracing.h +++ b/services/surfaceflinger/Tracing/LayerTracing.h @@ -47,7 +47,7 @@ public: bool isEnabled() const; status_t writeToFile(); LayersTraceFileProto createTraceFileProto() const; - void notify(bool visibleRegionDirty, int64_t time); + void notify(bool visibleRegionDirty, int64_t time, int64_t vsyncId); enum : uint32_t { TRACE_INPUT = 1 << 1, diff --git a/services/surfaceflinger/layerproto/layerstrace.proto b/services/surfaceflinger/layerproto/layerstrace.proto index 13647b669e..7def02422e 100644 --- a/services/surfaceflinger/layerproto/layerstrace.proto +++ b/services/surfaceflinger/layerproto/layerstrace.proto @@ -40,7 +40,7 @@ message LayersTraceFileProto { repeated LayersTraceProto entry = 2; } -/* one window manager trace entry. */ +/* one layers trace entry. */ message LayersTraceProto { /* required: elapsed realtime in nanos since boot of when this entry was logged */ optional sfixed64 elapsed_realtime_nanos = 1; @@ -60,4 +60,6 @@ message LayersTraceProto { optional uint32 missed_entries = 6; repeated DisplayProto displays = 7; + + optional int64 vsync_id = 8; } -- GitLab From 6be8244a9d05011b30c733ddd5c4b90af3a82fcd Mon Sep 17 00:00:00 2001 From: Pablo Gamito Date: Mon, 13 Jun 2022 11:04:34 +0000 Subject: [PATCH 0084/1310] Dump last committed vSyncId on layers trace start Keep track of the last committed vSyncId to dump it when we start the layers trace Bug: 230462538 Test: Check a vSyncId is dumped in the first entry of the layers trace Change-Id: Ie4c48acf99057cdb8bfebbf5cea3729ab5014bd4 --- services/surfaceflinger/SurfaceFlinger.cpp | 3 ++- services/surfaceflinger/SurfaceFlinger.h | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 446d744694..9abee99708 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2170,6 +2170,7 @@ bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expected // This will block and tracing should only be enabled for debugging. mLayerTracing.notify(mVisibleRegionsDirty, frameTime, vsyncId); } + mLastCommittedVsyncId = vsyncId; persistDisplayBrightness(mustComposite); @@ -5847,7 +5848,7 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r mScheduler ->schedule([&]() FTL_FAKE_GUARD(mStateLock) { mLayerTracing.notify(true /* visibleRegionDirty */, - startingTime, -1 /* vsyncId */); + startingTime, mLastCommittedVsyncId); }) .wait(); } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 41de2b6b59..ca4de73d60 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1266,6 +1266,8 @@ private: const std::unique_ptr mFrameTracer; const std::unique_ptr mFrameTimeline; + int64_t mLastCommittedVsyncId = -1; + // If blurs should be enabled on this device. bool mSupportsBlur = false; // If blurs are considered expensive and should require high GPU frequency. -- GitLab From ddefa7b9f88ca3c0b03f1105725d864f47c94429 Mon Sep 17 00:00:00 2001 From: Greg Kaiser Date: Mon, 13 Jun 2022 06:19:26 -0700 Subject: [PATCH 0085/1310] Set prior to performing std::move() We were calling a set method after a std::move(), which is not assured to work. Test: TreeHugger Bug: 230462538 Change-Id: I4fb18f4ba94f6046c502b850dc0ce6143ad6e3b6 --- services/surfaceflinger/Tracing/LayerTracing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/surfaceflinger/Tracing/LayerTracing.cpp b/services/surfaceflinger/Tracing/LayerTracing.cpp index 1b80fa3c42..afa3e50b15 100644 --- a/services/surfaceflinger/Tracing/LayerTracing.cpp +++ b/services/surfaceflinger/Tracing/LayerTracing.cpp @@ -129,8 +129,8 @@ void LayerTracing::notify(bool visibleRegionDirty, int64_t time, int64_t vsyncId entry.set_excludes_composition_state(true); } mFlinger.dumpDisplayProto(entry); - mBuffer->emplace(std::move(entry)); entry.set_vsync_id(vsyncId); + mBuffer->emplace(std::move(entry)); } } // namespace android -- GitLab From 11acb7381c328ac1b35cd0470f725b729b9ce374 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 13 Jun 2022 11:19:34 -0700 Subject: [PATCH 0086/1310] Specify model name The api for the palm rejection model has changed, and now requires a name to be specified. Send empty string to use the default model. Bug: 223909981 Test: verified on a tablet device Change-Id: Ida4c3a8398a838c7feb2ebd13e8c9994bea5062c --- services/inputflinger/UnwantedInteractionBlocker.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp index f57ff33d50..e8930e37f8 100644 --- a/services/inputflinger/UnwantedInteractionBlocker.cpp +++ b/services/inputflinger/UnwantedInteractionBlocker.cpp @@ -512,6 +512,13 @@ std::string SlotState::dump() const { return out; } +class AndroidPalmRejectionModel : public ::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel { +public: + AndroidPalmRejectionModel() + : ::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel(/*default version*/ "", + std::vector()) {} +}; + PalmRejector::PalmRejector(const AndroidPalmFilterDeviceInfo& info, std::unique_ptr<::ui::PalmDetectionFilter> filter) : mSharedPalmState(std::make_unique<::ui::SharedPalmDetectionFilterState>()), @@ -523,8 +530,7 @@ PalmRejector::PalmRejector(const AndroidPalmFilterDeviceInfo& info, return; } std::unique_ptr<::ui::NeuralStylusPalmDetectionFilterModel> model = - std::make_unique<::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel>( - std::vector()); + std::make_unique(); mPalmDetectionFilter = std::make_unique<::ui::NeuralStylusPalmDetectionFilter>(mDeviceInfo, std::move(model), mSharedPalmState.get()); -- GitLab From d5a4763037fb3c1145c5de306718b313bee9c5b2 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 13 Jun 2022 13:56:56 -0700 Subject: [PATCH 0087/1310] Enable resampling for palm rejection To ensure the model works well for all devices, and not just limited to 120Hz, enable resampling. Bug: 223909981 Test: verified on a tablet device Change-Id: I2e42959134276277037887b7bbb2f83e070b545f --- .../UnwantedInteractionBlocker.cpp | 4 +- .../tests/UnwantedInteractionBlocker_test.cpp | 94 ++++++++++--------- 2 files changed, 52 insertions(+), 46 deletions(-) diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp index e8930e37f8..3ee60a900f 100644 --- a/services/inputflinger/UnwantedInteractionBlocker.cpp +++ b/services/inputflinger/UnwantedInteractionBlocker.cpp @@ -516,7 +516,9 @@ class AndroidPalmRejectionModel : public ::ui::OneDeviceTrainNeuralStylusPalmDet public: AndroidPalmRejectionModel() : ::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel(/*default version*/ "", - std::vector()) {} + std::vector()) { + config_.resample_touch = true; + } }; PalmRejector::PalmRejector(const AndroidPalmFilterDeviceInfo& info, diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp index 0062f426d7..7f14b94dc0 100644 --- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp +++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp @@ -47,6 +47,10 @@ constexpr int MOVE = AMOTION_EVENT_ACTION_MOVE; constexpr int UP = AMOTION_EVENT_ACTION_UP; constexpr int CANCEL = AMOTION_EVENT_ACTION_CANCEL; +static nsecs_t toNs(std::chrono::nanoseconds duration) { + return duration.count(); +} + struct PointerData { float x; float y; @@ -630,41 +634,41 @@ TEST_F(PalmRejectorTestDeathTest, InconsistentEventCausesACrash) { */ TEST_F(PalmRejectorTest, TwoPointersAreCanceled) { std::vector argsList; - constexpr nsecs_t downTime = 255955749837000; + const nsecs_t downTime = toNs(0ms); mPalmRejector->processMotion( generateMotionArgs(downTime, downTime, DOWN, {{1342.0, 613.0, 79.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955759313000, MOVE, {{1406.0, 650.0, 52.0}})); + generateMotionArgs(downTime, toNs(8ms), MOVE, {{1406.0, 650.0, 52.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955766361000, MOVE, {{1429.0, 672.0, 46.0}})); + generateMotionArgs(downTime, toNs(16ms), MOVE, {{1429.0, 672.0, 46.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955775989000, MOVE, {{1417.0, 685.0, 41.0}})); + generateMotionArgs(downTime, toNs(24ms), MOVE, {{1417.0, 685.0, 41.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955775989000, POINTER_1_DOWN, + generateMotionArgs(downTime, toNs(32ms), POINTER_1_DOWN, {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955783039000, MOVE, + generateMotionArgs(downTime, toNs(40ms), MOVE, {{1414.0, 702.0, 41.0}, {1059.0, 731.0, 12.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955792536000, MOVE, + generateMotionArgs(downTime, toNs(48ms), MOVE, {{1415.0, 719.0, 44.0}, {1060.0, 760.0, 11.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955799474000, MOVE, + generateMotionArgs(downTime, toNs(56ms), MOVE, {{1421.0, 733.0, 42.0}, {1065.0, 769.0, 13.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955809177000, MOVE, + generateMotionArgs(downTime, toNs(64ms), MOVE, {{1426.0, 742.0, 43.0}, {1068.0, 771.0, 13.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955816131000, MOVE, + generateMotionArgs(downTime, toNs(72ms), MOVE, {{1430.0, 748.0, 45.0}, {1069.0, 772.0, 13.0}})); argsList = mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955825907000, MOVE, + generateMotionArgs(downTime, toNs(80ms), MOVE, {{1432.0, 750.0, 44.0}, {1069.0, 772.0, 13.0}})); ASSERT_EQ(1u, argsList.size()); ASSERT_EQ(0 /* No FLAG_CANCELED */, argsList[0].flags); argsList = mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955832736000, MOVE, + generateMotionArgs(downTime, toNs(88ms), MOVE, {{1433.0, 751.0, 44.0}, {1070.0, 771.0, 13.0}})); ASSERT_EQ(2u, argsList.size()); ASSERT_EQ(POINTER_0_UP, argsList[0].action); @@ -674,94 +678,94 @@ TEST_F(PalmRejectorTest, TwoPointersAreCanceled) { ASSERT_EQ(0, argsList[1].flags); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955842432000, MOVE, + generateMotionArgs(downTime, toNs(96ms), MOVE, {{1433.0, 751.0, 42.0}, {1071.0, 770.0, 13.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955849380000, MOVE, + generateMotionArgs(downTime, toNs(104ms), MOVE, {{1433.0, 751.0, 45.0}, {1072.0, 769.0, 13.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955859046000, MOVE, + generateMotionArgs(downTime, toNs(112ms), MOVE, {{1433.0, 751.0, 43.0}, {1072.0, 768.0, 13.0}})); argsList = mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955869823000, MOVE, + generateMotionArgs(downTime, toNs(120ms), MOVE, {{1433.0, 751.0, 45.0}, {1072.0, 767.0, 13.0}})); ASSERT_EQ(1u, argsList.size()); ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, argsList[0].action); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955875641000, MOVE, + generateMotionArgs(downTime, toNs(128ms), MOVE, {{1433.0, 751.0, 43.0}, {1072.0, 766.0, 13.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955882693000, MOVE, + generateMotionArgs(downTime, toNs(136ms), MOVE, {{1433.0, 750.0, 44.0}, {1072.0, 765.0, 13.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955892324000, MOVE, + generateMotionArgs(downTime, toNs(144ms), MOVE, {{1433.0, 750.0, 42.0}, {1072.0, 763.0, 14.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955899425000, MOVE, + generateMotionArgs(downTime, toNs(152ms), MOVE, {{1434.0, 750.0, 44.0}, {1073.0, 761.0, 14.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955909400000, MOVE, + generateMotionArgs(downTime, toNs(160ms), MOVE, {{1435.0, 750.0, 43.0}, {1073.0, 759.0, 15.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955915885000, MOVE, + generateMotionArgs(downTime, toNs(168ms), MOVE, {{1436.0, 750.0, 45.0}, {1074.0, 757.0, 15.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955925607000, MOVE, + generateMotionArgs(downTime, toNs(176ms), MOVE, {{1436.0, 750.0, 44.0}, {1074.0, 755.0, 15.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955932580000, MOVE, + generateMotionArgs(downTime, toNs(184ms), MOVE, {{1436.0, 750.0, 45.0}, {1074.0, 753.0, 15.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955942231000, MOVE, + generateMotionArgs(downTime, toNs(192ms), MOVE, {{1436.0, 749.0, 44.0}, {1074.0, 751.0, 15.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955949204000, MOVE, + generateMotionArgs(downTime, toNs(200ms), MOVE, {{1435.0, 748.0, 45.0}, {1074.0, 749.0, 15.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955959103000, MOVE, + generateMotionArgs(downTime, toNs(208ms), MOVE, {{1434.0, 746.0, 44.0}, {1074.0, 747.0, 14.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955965884000, MOVE, + generateMotionArgs(downTime, toNs(216ms), MOVE, {{1433.0, 744.0, 44.0}, {1075.0, 745.0, 14.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955975649000, MOVE, + generateMotionArgs(downTime, toNs(224ms), MOVE, {{1431.0, 741.0, 43.0}, {1075.0, 742.0, 13.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955982537000, MOVE, + generateMotionArgs(downTime, toNs(232ms), MOVE, {{1428.0, 738.0, 43.0}, {1076.0, 739.0, 12.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955992284000, MOVE, + generateMotionArgs(downTime, toNs(240ms), MOVE, {{1400.0, 726.0, 54.0}, {1076.0, 739.0, 13.0}})); argsList = mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955999348000, POINTER_1_UP, + generateMotionArgs(downTime, toNs(248ms), POINTER_1_UP, {{1362.0, 716.0, 55.0}, {1076.0, 739.0, 13.0}})); ASSERT_TRUE(argsList.empty()); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255955999348000, MOVE, {{1362.0, 716.0, 55.0}})); + generateMotionArgs(downTime, toNs(256ms), MOVE, {{1362.0, 716.0, 55.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255956008885000, MOVE, {{1347.0, 707.0, 54.0}})); + generateMotionArgs(downTime, toNs(264ms), MOVE, {{1347.0, 707.0, 54.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255956015791000, MOVE, {{1340.0, 698.0, 54.0}})); + generateMotionArgs(downTime, toNs(272ms), MOVE, {{1340.0, 698.0, 54.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255956025804000, MOVE, {{1338.0, 694.0, 55.0}})); + generateMotionArgs(downTime, toNs(280ms), MOVE, {{1338.0, 694.0, 55.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255956032314000, MOVE, {{1336.0, 690.0, 53.0}})); + generateMotionArgs(downTime, toNs(288ms), MOVE, {{1336.0, 690.0, 53.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255956042329000, MOVE, {{1334.0, 685.0, 47.0}})); + generateMotionArgs(downTime, toNs(296ms), MOVE, {{1334.0, 685.0, 47.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255956048979000, MOVE, {{1333.0, 679.0, 46.0}})); + generateMotionArgs(downTime, toNs(304ms), MOVE, {{1333.0, 679.0, 46.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255956058813000, MOVE, {{1332.0, 672.0, 45.0}})); + generateMotionArgs(downTime, toNs(312ms), MOVE, {{1332.0, 672.0, 45.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255956065592000, MOVE, {{1333.0, 666.0, 40.0}})); + generateMotionArgs(downTime, toNs(320ms), MOVE, {{1333.0, 666.0, 40.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255956075276000, MOVE, {{1336.0, 661.0, 24.0}})); + generateMotionArgs(downTime, toNs(328ms), MOVE, {{1336.0, 661.0, 24.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255956082198000, MOVE, {{1338.0, 656.0, 16.0}})); + generateMotionArgs(downTime, toNs(336ms), MOVE, {{1338.0, 656.0, 16.0}})); mPalmRejector->processMotion( - generateMotionArgs(downTime, 255956092059000, MOVE, {{1341.0, 649.0, 1.0}})); + generateMotionArgs(downTime, toNs(344ms), MOVE, {{1341.0, 649.0, 1.0}})); argsList = mPalmRejector->processMotion( - generateMotionArgs(downTime, 255956098764000, UP, {{1341.0, 649.0, 1.0}})); + generateMotionArgs(downTime, toNs(352ms), UP, {{1341.0, 649.0, 1.0}})); ASSERT_TRUE(argsList.empty()); } -- GitLab From 1290ea25bfb07977bbeb4d5c7d1673fd072edc95 Mon Sep 17 00:00:00 2001 From: Ian Elliott Date: Tue, 14 Jun 2022 19:09:15 -0600 Subject: [PATCH 0088/1310] Comment that VK_SUBOPTIMAL_KHR not returned window size changes Test: Inspection Bug: 236026298 Change-Id: I164db67d71c10e060d76630d34d12d51e1f3b91c --- vulkan/libvulkan/swapchain.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp index 48d6fd0bd5..f86600569b 100644 --- a/vulkan/libvulkan/swapchain.cpp +++ b/vulkan/libvulkan/swapchain.cpp @@ -1843,6 +1843,11 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) { if (swapchain_result != VK_SUCCESS) { OrphanSwapchain(device, &swapchain); } + // Android will only return VK_SUBOPTIMAL_KHR for vkQueuePresentKHR, + // and only when the window's transform/rotation changes. Extent + // changes will not cause VK_SUBOPTIMAL_KHR because of the + // application issues that were caused when the following transform + // change was added. int window_transform_hint; err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT, &window_transform_hint); -- GitLab From f8a873a9172df74b4b985a678b22b328acb08d37 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 15 Jun 2022 17:34:34 +0000 Subject: [PATCH 0089/1310] SF: Remove BufferQueueLayer tests Replace BQL with BSL inside composition engine tests. While we are at it make BQL constructor private and build the world. Bug: 200285148 Test: atest libsurfaceflinger_tests Change-Id: I28d1c5ff73074c8c7a27ca45ce4c2ca2cc2e385f --- libs/gui/GLConsumerUtils.cpp | 11 ++- libs/gui/include/gui/GLConsumer.h | 4 + services/surfaceflinger/BufferLayer.cpp | 12 ++- services/surfaceflinger/BufferQueueLayer.h | 4 +- .../SurfaceFlingerDefaultFactory.cpp | 4 +- .../surfaceflinger_scheduler_fuzzer.cpp | 2 +- .../tests/unittests/CompositionTest.cpp | 81 ++++++------------- 7 files changed, 48 insertions(+), 70 deletions(-) diff --git a/libs/gui/GLConsumerUtils.cpp b/libs/gui/GLConsumerUtils.cpp index 7a06c3d801..a1c69e7d6d 100644 --- a/libs/gui/GLConsumerUtils.cpp +++ b/libs/gui/GLConsumerUtils.cpp @@ -27,6 +27,13 @@ namespace android { void GLConsumer::computeTransformMatrix(float outTransform[16], const sp& buf, const Rect& cropRect, uint32_t transform, bool filtering) { + computeTransformMatrix(outTransform, buf->getWidth(), buf->getHeight(), buf->getPixelFormat(), + cropRect, transform, filtering); +} + +void GLConsumer::computeTransformMatrix(float outTransform[16], float bufferWidth, + float bufferHeight, PixelFormat pixelFormat, + const Rect& cropRect, uint32_t transform, bool filtering) { // Transform matrices static const mat4 mtxFlipH( -1, 0, 0, 0, @@ -60,8 +67,6 @@ void GLConsumer::computeTransformMatrix(float outTransform[16], if (!cropRect.isEmpty()) { float tx = 0.0f, ty = 0.0f, sx = 1.0f, sy = 1.0f; - float bufferWidth = buf->getWidth(); - float bufferHeight = buf->getHeight(); float shrinkAmount = 0.0f; if (filtering) { // In order to prevent bilinear sampling beyond the edge of the @@ -70,7 +75,7 @@ void GLConsumer::computeTransformMatrix(float outTransform[16], // off each end, but because the chroma channels of YUV420 images // are subsampled we may need to shrink the crop region by a whole // texel on each side. - switch (buf->getPixelFormat()) { + switch (pixelFormat) { case PIXEL_FORMAT_RGBA_8888: case PIXEL_FORMAT_RGBX_8888: case PIXEL_FORMAT_RGBA_FP16: diff --git a/libs/gui/include/gui/GLConsumer.h b/libs/gui/include/gui/GLConsumer.h index 2f538ffb86..ba268ab17a 100644 --- a/libs/gui/include/gui/GLConsumer.h +++ b/libs/gui/include/gui/GLConsumer.h @@ -138,6 +138,10 @@ public: const sp& buf, const Rect& cropRect, uint32_t transform, bool filtering); + static void computeTransformMatrix(float outTransform[16], float bufferWidth, + float bufferHeight, PixelFormat pixelFormat, + const Rect& cropRect, uint32_t transform, bool filtering); + // Scale the crop down horizontally or vertically such that it has the // same aspect ratio as the buffer does. static Rect scaleDownCrop(const Rect& crop, uint32_t bufferWidth, diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp index d9c89cd821..fb15f1d06c 100644 --- a/services/surfaceflinger/BufferLayer.cpp +++ b/services/surfaceflinger/BufferLayer.cpp @@ -738,10 +738,14 @@ sp BufferLayer::getBuffer() const { } void BufferLayer::getDrawingTransformMatrix(bool filteringEnabled, float outMatrix[16]) { - GLConsumer::computeTransformMatrix(outMatrix, - mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getBuffer() - : nullptr, - mBufferInfo.mCrop, mBufferInfo.mTransform, filteringEnabled); + sp buffer = getBuffer(); + if (!buffer) { + ALOGE("Buffer should not be null!"); + return; + } + GLConsumer::computeTransformMatrix(outMatrix, buffer->getWidth(), buffer->getHeight(), + buffer->getPixelFormat(), mBufferInfo.mCrop, + mBufferInfo.mTransform, filteringEnabled); } void BufferLayer::setInitialValuesForClone(const sp& clonedFrom) { diff --git a/services/surfaceflinger/BufferQueueLayer.h b/services/surfaceflinger/BufferQueueLayer.h index e1c80d581d..f7a4fd23b7 100644 --- a/services/surfaceflinger/BufferQueueLayer.h +++ b/services/surfaceflinger/BufferQueueLayer.h @@ -35,8 +35,6 @@ class SurfaceFrame; */ class BufferQueueLayer : public BufferLayer { public: - // Only call while mStateLock is held - explicit BufferQueueLayer(const LayerCreationArgs&); ~BufferQueueLayer() override; // Implements Layer. @@ -92,6 +90,8 @@ protected: }; private: + // Goodbye + explicit BufferQueueLayer(const LayerCreationArgs&); bool latchSidebandStream(bool& recomputeVisibleRegions) override; void setTransformHint(ui::Transform::RotationFlags displayTransformHint) override; diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp index b81b445dad..cf97643ba9 100644 --- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp +++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp @@ -109,8 +109,8 @@ sp DefaultFactory::createContainerLayer(const LayerCreationArgs& return new ContainerLayer(args); } -sp DefaultFactory::createBufferQueueLayer(const LayerCreationArgs& args) { - return new BufferQueueLayer(args); +sp DefaultFactory::createBufferQueueLayer(const LayerCreationArgs&) { + return nullptr; } sp DefaultFactory::createBufferStateLayer(const LayerCreationArgs& args) { diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index da60a6981b..1a91a69492 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -319,7 +319,7 @@ void SchedulerFuzzer::fuzzRefreshRateSelection() { LayerCreationArgs args(flinger.flinger(), client, mFdp.ConsumeRandomLengthString(kRandomStringLength) /*name*/, mFdp.ConsumeIntegral() /*layerFlags*/, LayerMetadata()); - sp layer = new BufferQueueLayer(args); + sp layer = new BufferStateLayer(args); layer->setFrameRateSelectionPriority(mFdp.ConsumeIntegral()); } diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index bbfedc7685..19eaa198db 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -37,7 +37,6 @@ #include #include -#include "BufferQueueLayer.h" #include "ContainerLayer.h" #include "DisplayRenderArea.h" #include "EffectLayer.h" @@ -492,65 +491,31 @@ struct BaseLayerProperties { static constexpr IComposerClient::BlendMode BLENDMODE = IComposerClient::BlendMode::PREMULTIPLIED; - static void enqueueBuffer(CompositionTest*, sp layer) { - auto producer = layer->getProducer(); - - IGraphicBufferProducer::QueueBufferOutput qbo; - status_t result = producer->connect(nullptr, NATIVE_WINDOW_API_EGL, false, &qbo); - if (result != NO_ERROR) { - ALOGE("Failed to connect() (%d)", result); - return; - } - - int slot; - sp fence; - result = producer->dequeueBuffer(&slot, &fence, LayerProperties::WIDTH, - LayerProperties::HEIGHT, LayerProperties::FORMAT, - LayerProperties::USAGE, nullptr, nullptr); - if (result != IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) { - ALOGE("Failed to dequeueBuffer() (%d)", result); - return; - } - - sp buffer; - result = producer->requestBuffer(slot, &buffer); - if (result != NO_ERROR) { - ALOGE("Failed to requestBuffer() (%d)", result); - return; - } - - IGraphicBufferProducer::QueueBufferInput qbi(systemTime(), false /* isAutoTimestamp */, - LayerProperties::DATASPACE, - Rect(LayerProperties::WIDTH, - LayerProperties::HEIGHT), - LayerProperties::SCALING_MODE, - LayerProperties::TRANSFORM, Fence::NO_FENCE); - result = producer->queueBuffer(slot, qbi, &qbo); - if (result != NO_ERROR) { - ALOGE("Failed to queueBuffer (%d)", result); - return; - } - } - - static void setupLatchedBuffer(CompositionTest* test, sp layer) { - // TODO: Eliminate the complexity of actually creating a buffer - layer->setSizeForTest(LayerProperties::WIDTH, LayerProperties::HEIGHT); - status_t err = - layer->setDefaultBufferProperties(LayerProperties::WIDTH, LayerProperties::HEIGHT, - LayerProperties::FORMAT); - ASSERT_EQ(NO_ERROR, err); + static void setupLatchedBuffer(CompositionTest* test, sp layer) { Mock::VerifyAndClear(test->mRenderEngine); - EXPECT_CALL(*test->mFlinger.scheduler(), scheduleFrame()).Times(1); - enqueueBuffer(test, layer); - Mock::VerifyAndClearExpectations(test->mFlinger.scheduler()); + const auto buffer = std::make_shared< + renderengine::mock::FakeExternalTexture>(LayerProperties::WIDTH, + LayerProperties::HEIGHT, + DEFAULT_TEXTURE_ID, + LayerProperties::FORMAT, + LayerProperties::USAGE | + GraphicBuffer::USAGE_HW_TEXTURE); + + auto& layerDrawingState = test->mFlinger.mutableLayerDrawingState(layer); + layerDrawingState.crop = Rect(0, 0, LayerProperties::HEIGHT, LayerProperties::WIDTH); + layerDrawingState.buffer = buffer; + layerDrawingState.acquireFence = Fence::NO_FENCE; + layerDrawingState.dataspace = ui::Dataspace::UNKNOWN; + layer->setSurfaceDamageRegion( + Region(Rect(LayerProperties::HEIGHT, LayerProperties::WIDTH))); bool ignoredRecomputeVisibleRegions; layer->latchBuffer(ignoredRecomputeVisibleRegions, 0, 0); Mock::VerifyAndClear(test->mRenderEngine); } - static void setupLayerState(CompositionTest* test, sp layer) { + static void setupLayerState(CompositionTest* test, sp layer) { setupLatchedBuffer(test, layer); } @@ -736,7 +701,7 @@ struct SidebandLayerProperties : public BaseLayerProperties; static constexpr IComposerClient::BlendMode BLENDMODE = IComposerClient::BlendMode::NONE; - static void setupLayerState(CompositionTest* test, sp layer) { + static void setupLayerState(CompositionTest* test, sp layer) { sp stream = NativeHandle::create(reinterpret_cast(DEFAULT_SIDEBAND_STREAM), false); @@ -818,14 +783,14 @@ struct SecureLayerProperties : public CommonSecureLayerProperties { using Base = BaseLayerProperties; - static void setupLayerState(CompositionTest* test, sp layer) { + static void setupLayerState(CompositionTest* test, sp layer) { Base::setupLayerState(test, layer); test->mFlinger.setLayerPotentialCursor(layer, true); } }; struct NoLayerVariant { - using FlingerLayerType = sp; + using FlingerLayerType = sp; static FlingerLayerType createLayer(CompositionTest*) { return FlingerLayerType(); } static void injectLayer(CompositionTest*, FlingerLayerType) {} @@ -932,17 +897,17 @@ struct EffectLayerVariant : public BaseLayerVariant { template struct BufferLayerVariant : public BaseLayerVariant { using Base = BaseLayerVariant; - using FlingerLayerType = sp; + using FlingerLayerType = sp; static FlingerLayerType createLayer(CompositionTest* test) { test->mFlinger.mutableTexturePool().push_back(DEFAULT_TEXTURE_ID); FlingerLayerType layer = - Base::template createLayerWithFactory(test, [test]() { + Base::template createLayerWithFactory(test, [test]() { LayerCreationArgs args(test->mFlinger.flinger(), sp(), "test-layer", LayerProperties::LAYER_FLAGS, LayerMetadata()); args.textureName = test->mFlinger.mutableTexturePool().back(); - return new BufferQueueLayer(args); + return new BufferStateLayer(args); }); LayerProperties::setupLayerState(test, layer); -- GitLab From 37ce3ecd9268adead6d04ae8211443eaded20ae5 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Thu, 16 Jun 2022 22:06:21 +0000 Subject: [PATCH 0090/1310] SurfaceFlinger: Remove BufferQueueLayer Fixes: 200285148 Test: builds Change-Id: I218bfb8110b5aef7a292e805e8508a278b07b684 --- services/surfaceflinger/Android.bp | 1 - services/surfaceflinger/BufferQueueLayer.cpp | 575 ------------------ services/surfaceflinger/BufferQueueLayer.h | 154 ----- services/surfaceflinger/SurfaceFlinger.cpp | 37 -- services/surfaceflinger/SurfaceFlinger.h | 5 - .../SurfaceFlingerDefaultFactory.cpp | 5 - .../SurfaceFlingerDefaultFactory.h | 1 - .../surfaceflinger/SurfaceFlingerFactory.h | 2 - .../Tracing/tools/LayerTraceGenerator.cpp | 4 - .../fuzzer/surfaceflinger_fuzzers_utils.h | 5 - .../tests/unittests/FpsReporterTest.cpp | 1 - .../tests/unittests/TestableSurfaceFlinger.h | 5 - 12 files changed, 795 deletions(-) delete mode 100644 services/surfaceflinger/BufferQueueLayer.cpp delete mode 100644 services/surfaceflinger/BufferQueueLayer.h diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index 000a2cb0d3..1ddf2de3cb 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -143,7 +143,6 @@ filegroup { "BackgroundExecutor.cpp", "BufferLayer.cpp", "BufferLayerConsumer.cpp", - "BufferQueueLayer.cpp", "BufferStateLayer.cpp", "ClientCache.cpp", "Client.cpp", diff --git a/services/surfaceflinger/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp deleted file mode 100644 index bee4de32a6..0000000000 --- a/services/surfaceflinger/BufferQueueLayer.cpp +++ /dev/null @@ -1,575 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - -#undef LOG_TAG -#define LOG_TAG "BufferQueueLayer" -#define ATRACE_TAG ATRACE_TAG_GRAPHICS -#include "BufferQueueLayer.h" - -#include -#include -#include - -#include "LayerRejecter.h" -#include "SurfaceInterceptor.h" - -#include "FrameTracer/FrameTracer.h" -#include "Scheduler/LayerHistory.h" -#include "TimeStats/TimeStats.h" - -namespace android { -using PresentState = frametimeline::SurfaceFrame::PresentState; - -BufferQueueLayer::BufferQueueLayer(const LayerCreationArgs& args) : BufferLayer(args) {} - -BufferQueueLayer::~BufferQueueLayer() { - mContentsChangedListener->abandon(); - mConsumer->abandon(); -} - -// ----------------------------------------------------------------------- -// Interface implementation for Layer -// ----------------------------------------------------------------------- - -void BufferQueueLayer::onLayerDisplayed(ftl::SharedFuture futureFenceResult) { - const sp releaseFence = futureFenceResult.get().value_or(Fence::NO_FENCE); - mConsumer->setReleaseFence(releaseFence); - - // Prevent tracing the same release multiple times. - if (mPreviousFrameNumber != mPreviousReleasedFrameNumber) { - mFlinger->mFrameTracer->traceFence(getSequence(), mPreviousBufferId, mPreviousFrameNumber, - std::make_shared(releaseFence), - FrameTracer::FrameEvent::RELEASE_FENCE); - mPreviousReleasedFrameNumber = mPreviousFrameNumber; - } -} - -void BufferQueueLayer::setTransformHint(ui::Transform::RotationFlags displayTransformHint) { - BufferLayer::setTransformHint(displayTransformHint); - mConsumer->setTransformHint(mTransformHint); -} - -void BufferQueueLayer::releasePendingBuffer(nsecs_t) { - if (!mConsumer->releasePendingBuffer()) { - return; - } -} - -void BufferQueueLayer::setDefaultBufferSize(uint32_t w, uint32_t h) { - mConsumer->setDefaultBufferSize(w, h); -} - -int32_t BufferQueueLayer::getQueuedFrameCount() const { - return mQueuedFrames; -} - -bool BufferQueueLayer::isBufferDue(nsecs_t expectedPresentTime) const { - Mutex::Autolock lock(mQueueItemLock); - - const int64_t addedTime = mQueueItems[0].item.mTimestamp; - - // Ignore timestamps more than a second in the future - const bool isPlausible = addedTime < (expectedPresentTime + s2ns(1)); - ALOGW_IF(!isPlausible, - "[%s] Timestamp %" PRId64 " seems implausible " - "relative to expectedPresent %" PRId64, - getDebugName(), addedTime, expectedPresentTime); - - if (!isPlausible) { - mFlinger->mTimeStats->incrementBadDesiredPresent(getSequence()); - } - - const bool isDue = addedTime < expectedPresentTime; - return isDue || !isPlausible; -} - -// ----------------------------------------------------------------------- -// Interface implementation for BufferLayer -// ----------------------------------------------------------------------- - -bool BufferQueueLayer::fenceHasSignaled() const { - Mutex::Autolock lock(mQueueItemLock); - - if (SurfaceFlinger::enableLatchUnsignaledConfig != LatchUnsignaledConfig::Disabled) { - return true; - } - - if (!hasFrameUpdate()) { - return true; - } - - if (mQueueItems[0].item.mIsDroppable) { - // Even though this buffer's fence may not have signaled yet, it could - // be replaced by another buffer before it has a chance to, which means - // that it's possible to get into a situation where a buffer is never - // able to be latched. To avoid this, grab this buffer anyway. - return true; - } - const bool fenceSignaled = - mQueueItems[0].item.mFenceTime->getSignalTime() != Fence::SIGNAL_TIME_PENDING; - if (!fenceSignaled) { - mFlinger->mTimeStats->incrementLatchSkipped(getSequence(), - TimeStats::LatchSkipReason::LateAcquire); - } - - return fenceSignaled; -} - -bool BufferQueueLayer::framePresentTimeIsCurrent(nsecs_t expectedPresentTime) const { - Mutex::Autolock lock(mQueueItemLock); - - if (!hasFrameUpdate() || isRemovedFromCurrentState()) { - return true; - } - - return mQueueItems[0].item.mTimestamp <= expectedPresentTime; -} - -bool BufferQueueLayer::latchSidebandStream(bool& recomputeVisibleRegions) { - // We need to update the sideband stream if the layer has both a buffer and a sideband stream. - editCompositionState()->sidebandStreamHasFrame = hasFrameUpdate() && mSidebandStream.get(); - - bool sidebandStreamChanged = true; - if (mSidebandStreamChanged.compare_exchange_strong(sidebandStreamChanged, false)) { - // mSidebandStreamChanged was changed to false - mSidebandStream = mConsumer->getSidebandStream(); - auto* layerCompositionState = editCompositionState(); - layerCompositionState->sidebandStream = mSidebandStream; - if (layerCompositionState->sidebandStream != nullptr) { - setTransactionFlags(eTransactionNeeded); - mFlinger->setTransactionFlags(eTraversalNeeded); - } - recomputeVisibleRegions = true; - - return true; - } - return false; -} - -bool BufferQueueLayer::hasFrameUpdate() const { - return mQueuedFrames > 0; -} - -status_t BufferQueueLayer::updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime, - nsecs_t expectedPresentTime) { - // This boolean is used to make sure that SurfaceFlinger's shadow copy - // of the buffer queue isn't modified when the buffer queue is returning - // BufferItem's that weren't actually queued. This can happen in shared - // buffer mode. - bool queuedBuffer = false; - const int32_t layerId = getSequence(); - LayerRejecter r(mDrawingState, getDrawingState(), recomputeVisibleRegions, - getProducerStickyTransform() != 0, mName, - getTransformToDisplayInverse()); - - if (isRemovedFromCurrentState()) { - expectedPresentTime = 0; - } - - // updateTexImage() below might drop the some buffers at the head of the queue if there is a - // buffer behind them which is timely to be presented. However this buffer may not be signaled - // yet. The code below makes sure that this wouldn't happen by setting maxFrameNumber to the - // last buffer that was signaled. - uint64_t lastSignaledFrameNumber = mLastFrameNumberReceived; - { - Mutex::Autolock lock(mQueueItemLock); - for (size_t i = 0; i < mQueueItems.size(); i++) { - bool fenceSignaled = - mQueueItems[i].item.mFenceTime->getSignalTime() != Fence::SIGNAL_TIME_PENDING; - if (!fenceSignaled) { - break; - } - lastSignaledFrameNumber = mQueueItems[i].item.mFrameNumber; - } - } - const uint64_t maxFrameNumberToAcquire = - std::min(mLastFrameNumberReceived.load(), lastSignaledFrameNumber); - - bool autoRefresh; - status_t updateResult = mConsumer->updateTexImage(&r, expectedPresentTime, &autoRefresh, - &queuedBuffer, maxFrameNumberToAcquire); - mDrawingState.autoRefresh = autoRefresh; - if (updateResult == BufferQueue::PRESENT_LATER) { - // Producer doesn't want buffer to be displayed yet. Signal a - // layer update so we check again at the next opportunity. - mFlinger->onLayerUpdate(); - return BAD_VALUE; - } else if (updateResult == BufferLayerConsumer::BUFFER_REJECTED) { - // If the buffer has been rejected, remove it from the shadow queue - // and return early - if (queuedBuffer) { - Mutex::Autolock lock(mQueueItemLock); - if (mQueuedFrames > 0) { - mConsumer->mergeSurfaceDamage(mQueueItems[0].item.mSurfaceDamage); - mFlinger->mTimeStats->removeTimeRecord(layerId, mQueueItems[0].item.mFrameNumber); - if (mQueueItems[0].surfaceFrame) { - addSurfaceFrameDroppedForBuffer(mQueueItems[0].surfaceFrame); - } - mQueueItems.erase(mQueueItems.begin()); - mQueuedFrames--; - } - } - return BAD_VALUE; - } else if (updateResult != NO_ERROR || mUpdateTexImageFailed) { - // This can occur if something goes wrong when trying to create the - // EGLImage for this buffer. If this happens, the buffer has already - // been released, so we need to clean up the queue and bug out - // early. - if (queuedBuffer) { - Mutex::Autolock lock(mQueueItemLock); - for (auto& [item, surfaceFrame] : mQueueItems) { - if (surfaceFrame) { - addSurfaceFrameDroppedForBuffer(surfaceFrame); - } - } - mQueueItems.clear(); - mQueuedFrames = 0; - mFlinger->mTimeStats->onDestroy(layerId); - mFlinger->mFrameTracer->onDestroy(layerId); - } - - // Once we have hit this state, the shadow queue may no longer - // correctly reflect the incoming BufferQueue's contents, so even if - // updateTexImage starts working, the only safe course of action is - // to continue to ignore updates. - mUpdateTexImageFailed = true; - - return BAD_VALUE; - } - - bool more_frames_pending = false; - if (queuedBuffer) { - // Autolock scope - auto currentFrameNumber = mConsumer->getFrameNumber(); - - Mutex::Autolock lock(mQueueItemLock); - - // Remove any stale buffers that have been dropped during - // updateTexImage - while (mQueuedFrames > 0 && mQueueItems[0].item.mFrameNumber != currentFrameNumber) { - mConsumer->mergeSurfaceDamage(mQueueItems[0].item.mSurfaceDamage); - mFlinger->mTimeStats->removeTimeRecord(layerId, mQueueItems[0].item.mFrameNumber); - if (mQueueItems[0].surfaceFrame) { - addSurfaceFrameDroppedForBuffer(mQueueItems[0].surfaceFrame); - } - mQueueItems.erase(mQueueItems.begin()); - mQueuedFrames--; - } - - uint64_t bufferID = mQueueItems[0].item.mGraphicBuffer->getId(); - mFlinger->mTimeStats->setLatchTime(layerId, currentFrameNumber, latchTime); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferID, currentFrameNumber, latchTime, - FrameTracer::FrameEvent::LATCH); - - if (mQueueItems[0].surfaceFrame) { - addSurfaceFramePresentedForBuffer(mQueueItems[0].surfaceFrame, - mQueueItems[0].item.mFenceTime->getSignalTime(), - latchTime); - } - mQueueItems.erase(mQueueItems.begin()); - more_frames_pending = (mQueuedFrames.fetch_sub(1) > 1); - } - - // Decrement the queued-frames count. Signal another event if we - // have more frames pending. - if ((queuedBuffer && more_frames_pending) || mDrawingState.autoRefresh) { - mFlinger->onLayerUpdate(); - } - - return NO_ERROR; -} - -status_t BufferQueueLayer::updateActiveBuffer() { - // update the active buffer - mPreviousBufferId = getCurrentBufferId(); - mBufferInfo.mBuffer = - mConsumer->getCurrentBuffer(&mBufferInfo.mBufferSlot, &mBufferInfo.mFence); - - if (mBufferInfo.mBuffer == nullptr) { - // this can only happen if the very first buffer was rejected. - return BAD_VALUE; - } - return NO_ERROR; -} - -status_t BufferQueueLayer::updateFrameNumber() { - mPreviousFrameNumber = mCurrentFrameNumber; - mCurrentFrameNumber = mConsumer->getFrameNumber(); - return NO_ERROR; -} - -void BufferQueueLayer::setFrameTimelineInfoForBuffer(const FrameTimelineInfo& frameTimelineInfo) { - mFrameTimelineInfo = frameTimelineInfo; -} - -// ----------------------------------------------------------------------- -// Interface implementation for BufferLayerConsumer::ContentsChangedListener -// ----------------------------------------------------------------------- - -void BufferQueueLayer::onFrameDequeued(const uint64_t bufferId) { - const int32_t layerId = getSequence(); - mFlinger->mFrameTracer->traceNewLayer(layerId, getName().c_str()); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, FrameTracer::UNSPECIFIED_FRAME_NUMBER, - systemTime(), FrameTracer::FrameEvent::DEQUEUE); -} - -void BufferQueueLayer::onFrameDetached(const uint64_t bufferId) { - const int32_t layerId = getSequence(); - mFlinger->mFrameTracer->traceNewLayer(layerId, getName().c_str()); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, FrameTracer::UNSPECIFIED_FRAME_NUMBER, - systemTime(), FrameTracer::FrameEvent::DETACH); -} - -void BufferQueueLayer::onFrameCancelled(const uint64_t bufferId) { - const int32_t layerId = getSequence(); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, FrameTracer::UNSPECIFIED_FRAME_NUMBER, - systemTime(), FrameTracer::FrameEvent::CANCEL); -} - -void BufferQueueLayer::onFrameAvailable(const BufferItem& item) { - const int32_t layerId = getSequence(); - const uint64_t bufferId = item.mGraphicBuffer->getId(); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, item.mFrameNumber, systemTime(), - FrameTracer::FrameEvent::QUEUE); - mFlinger->mFrameTracer->traceFence(layerId, bufferId, item.mFrameNumber, - std::make_shared(item.mFence), - FrameTracer::FrameEvent::ACQUIRE_FENCE); - - ATRACE_CALL(); - // Add this buffer from our internal queue tracker - { // Autolock scope - const nsecs_t presentTime = item.mIsAutoTimestamp ? 0 : item.mTimestamp; - - using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType; - mFlinger->mScheduler->recordLayerHistory(this, presentTime, LayerUpdateType::Buffer); - - Mutex::Autolock lock(mQueueItemLock); - // Reset the frame number tracker when we receive the first buffer after - // a frame number reset - if (item.mFrameNumber == 1) { - mLastFrameNumberReceived = 0; - } - - // Ensure that callbacks are handled in order - while (item.mFrameNumber != mLastFrameNumberReceived + 1) { - status_t result = mQueueItemCondition.waitRelative(mQueueItemLock, ms2ns(500)); - if (result != NO_ERROR) { - ALOGE("[%s] Timed out waiting on callback", getDebugName()); - break; - } - } - - auto surfaceFrame = createSurfaceFrameForBuffer(mFrameTimelineInfo, systemTime(), mName); - - mQueueItems.push_back({item, surfaceFrame}); - mQueuedFrames++; - - // Wake up any pending callbacks - mLastFrameNumberReceived = item.mFrameNumber; - mQueueItemCondition.broadcast(); - } - - mFlinger->mInterceptor->saveBufferUpdate(layerId, item.mGraphicBuffer->getWidth(), - item.mGraphicBuffer->getHeight(), item.mFrameNumber); - - mFlinger->onLayerUpdate(); - mConsumer->onBufferAvailable(item); -} - -void BufferQueueLayer::onFrameReplaced(const BufferItem& item) { - ATRACE_CALL(); - { // Autolock scope - Mutex::Autolock lock(mQueueItemLock); - - // Ensure that callbacks are handled in order - while (item.mFrameNumber != mLastFrameNumberReceived + 1) { - status_t result = mQueueItemCondition.waitRelative(mQueueItemLock, ms2ns(500)); - if (result != NO_ERROR) { - ALOGE("[%s] Timed out waiting on callback", getDebugName()); - break; - } - } - - if (!hasFrameUpdate()) { - ALOGE("Can't replace a frame on an empty queue"); - return; - } - - auto surfaceFrame = createSurfaceFrameForBuffer(mFrameTimelineInfo, systemTime(), mName); - mQueueItems[mQueueItems.size() - 1].item = item; - mQueueItems[mQueueItems.size() - 1].surfaceFrame = std::move(surfaceFrame); - - // Wake up any pending callbacks - mLastFrameNumberReceived = item.mFrameNumber; - mQueueItemCondition.broadcast(); - } - - const int32_t layerId = getSequence(); - const uint64_t bufferId = item.mGraphicBuffer->getId(); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, item.mFrameNumber, systemTime(), - FrameTracer::FrameEvent::QUEUE); - mFlinger->mFrameTracer->traceFence(layerId, bufferId, item.mFrameNumber, - std::make_shared(item.mFence), - FrameTracer::FrameEvent::ACQUIRE_FENCE); - mConsumer->onBufferAvailable(item); -} - -void BufferQueueLayer::onSidebandStreamChanged() { - bool sidebandStreamChanged = false; - if (mSidebandStreamChanged.compare_exchange_strong(sidebandStreamChanged, true)) { - // mSidebandStreamChanged was changed to true - mFlinger->onLayerUpdate(); - } -} - -// ----------------------------------------------------------------------- - -void BufferQueueLayer::onFirstRef() { - BufferLayer::onFirstRef(); - - // Creates a custom BufferQueue for SurfaceFlingerConsumer to use - sp producer; - sp consumer; - mFlinger->getFactory().createBufferQueue(&producer, &consumer, true); - mProducer = mFlinger->getFactory().createMonitoredProducer(producer, mFlinger, this); - mConsumer = - mFlinger->getFactory().createBufferLayerConsumer(consumer, mFlinger->getRenderEngine(), - mTextureName, this); - mConsumer->setConsumerUsageBits(getEffectiveUsage(0)); - - mContentsChangedListener = new ContentsChangedListener(this); - mConsumer->setContentsChangedListener(mContentsChangedListener); - mConsumer->setName(String8(mName.data(), mName.size())); - - mProducer->setMaxDequeuedBufferCount(2); -} - -status_t BufferQueueLayer::setDefaultBufferProperties(uint32_t w, uint32_t h, PixelFormat format) { - // never allow a surface larger than what our underlying GL implementation - // can handle. - if (mFlinger->exceedsMaxRenderTargetSize(w, h)) { - ALOGE("dimensions too large %" PRIu32 " x %" PRIu32, w, h); - return BAD_VALUE; - } - - setDefaultBufferSize(w, h); - mConsumer->setDefaultBufferFormat(format); - mConsumer->setConsumerUsageBits(getEffectiveUsage(0)); - - return NO_ERROR; -} - -sp BufferQueueLayer::getProducer() const { - return mProducer; -} - -uint32_t BufferQueueLayer::getProducerStickyTransform() const { - int producerStickyTransform = 0; - int ret = mProducer->query(NATIVE_WINDOW_STICKY_TRANSFORM, &producerStickyTransform); - if (ret != OK) { - ALOGW("%s: Error %s (%d) while querying window sticky transform.", __FUNCTION__, - strerror(-ret), ret); - return 0; - } - return static_cast(producerStickyTransform); -} - -void BufferQueueLayer::gatherBufferInfo() { - BufferLayer::gatherBufferInfo(); - - mBufferInfo.mDesiredPresentTime = mConsumer->getTimestamp(); - mBufferInfo.mFenceTime = mConsumer->getCurrentFenceTime(); - mBufferInfo.mFence = mConsumer->getCurrentFence(); - mBufferInfo.mTransform = mConsumer->getCurrentTransform(); - mBufferInfo.mDataspace = translateDataspace(mConsumer->getCurrentDataSpace()); - mBufferInfo.mCrop = mConsumer->getCurrentCrop(); - mBufferInfo.mScaleMode = mConsumer->getCurrentScalingMode(); - mBufferInfo.mSurfaceDamage = mConsumer->getSurfaceDamage(); - mBufferInfo.mHdrMetadata = mConsumer->getCurrentHdrMetadata(); - mBufferInfo.mApi = mConsumer->getCurrentApi(); - mBufferInfo.mTransformToDisplayInverse = mConsumer->getTransformToDisplayInverse(); -} - -sp BufferQueueLayer::createClone() { - LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata()); - args.textureName = mTextureName; - sp layer = mFlinger->getFactory().createBufferQueueLayer(args); - layer->setInitialValuesForClone(this); - - return layer; -} - -// ----------------------------------------------------------------------- -// Interface implementation for BufferLayerConsumer::ContentsChangedListener -// ----------------------------------------------------------------------- - -void BufferQueueLayer::ContentsChangedListener::onFrameAvailable(const BufferItem& item) { - Mutex::Autolock lock(mMutex); - if (mBufferQueueLayer != nullptr) { - mBufferQueueLayer->onFrameAvailable(item); - } -} - -void BufferQueueLayer::ContentsChangedListener::onFrameReplaced(const BufferItem& item) { - Mutex::Autolock lock(mMutex); - if (mBufferQueueLayer != nullptr) { - mBufferQueueLayer->onFrameReplaced(item); - } -} - -void BufferQueueLayer::ContentsChangedListener::onSidebandStreamChanged() { - Mutex::Autolock lock(mMutex); - if (mBufferQueueLayer != nullptr) { - mBufferQueueLayer->onSidebandStreamChanged(); - } -} - -void BufferQueueLayer::ContentsChangedListener::onFrameDequeued(const uint64_t bufferId) { - Mutex::Autolock lock(mMutex); - if (mBufferQueueLayer != nullptr) { - mBufferQueueLayer->onFrameDequeued(bufferId); - } -} - -void BufferQueueLayer::ContentsChangedListener::onFrameDetached(const uint64_t bufferId) { - Mutex::Autolock lock(mMutex); - if (mBufferQueueLayer != nullptr) { - mBufferQueueLayer->onFrameDetached(bufferId); - } -} - -void BufferQueueLayer::ContentsChangedListener::onFrameCancelled(const uint64_t bufferId) { - Mutex::Autolock lock(mMutex); - if (mBufferQueueLayer != nullptr) { - mBufferQueueLayer->onFrameCancelled(bufferId); - } -} - -void BufferQueueLayer::ContentsChangedListener::abandon() { - Mutex::Autolock lock(mMutex); - mBufferQueueLayer = nullptr; -} - -// ----------------------------------------------------------------------- - -} // namespace android - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/BufferQueueLayer.h b/services/surfaceflinger/BufferQueueLayer.h deleted file mode 100644 index f7a4fd23b7..0000000000 --- a/services/surfaceflinger/BufferQueueLayer.h +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2018 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 "BufferLayer.h" - -#include - -namespace android { - -namespace frametimeline { -class SurfaceFrame; -} - -/* - * A new BufferQueue and a new BufferLayerConsumer are created when the - * BufferLayer is first referenced. - * - * This also implements onFrameAvailable(), which notifies SurfaceFlinger - * that new data has arrived. - */ -class BufferQueueLayer : public BufferLayer { -public: - ~BufferQueueLayer() override; - - // Implements Layer. - const char* getType() const override { return "BufferQueueLayer"; } - - void onLayerDisplayed(ftl::SharedFuture) override; - - // If a buffer was replaced this frame, release the former buffer - void releasePendingBuffer(nsecs_t dequeueReadyTime) override; - - void setDefaultBufferSize(uint32_t w, uint32_t h) override; - - int32_t getQueuedFrameCount() const override; - - // Returns true if the next buffer should be presented at the expected present time - bool isBufferDue(nsecs_t expectedPresentTime) const override; - - // Implements BufferLayer. - bool fenceHasSignaled() const override; - bool framePresentTimeIsCurrent(nsecs_t expectedPresentTime) const override; - - status_t setDefaultBufferProperties(uint32_t w, uint32_t h, PixelFormat format); - sp getProducer() const; - - void setSizeForTest(uint32_t w, uint32_t h) { - mDrawingState.active_legacy.w = w; - mDrawingState.active_legacy.h = h; - } - -protected: - void gatherBufferInfo() override; - - // ----------------------------------------------------------------------- - // Interface implementation for BufferLayerConsumer::ContentsChangedListener - // ----------------------------------------------------------------------- - class ContentsChangedListener : public BufferLayerConsumer::ContentsChangedListener { - public: - ContentsChangedListener(BufferQueueLayer* bufferQueueLayer) - : mBufferQueueLayer(bufferQueueLayer) {} - void abandon(); - - protected: - void onFrameAvailable(const BufferItem& item) override; - void onFrameReplaced(const BufferItem& item) override; - void onSidebandStreamChanged() override; - void onFrameDequeued(const uint64_t bufferId) override; - void onFrameDetached(const uint64_t bufferId) override; - void onFrameCancelled(const uint64_t bufferId) override; - - private: - BufferQueueLayer* mBufferQueueLayer = nullptr; - Mutex mMutex; - }; - -private: - // Goodbye - explicit BufferQueueLayer(const LayerCreationArgs&); - - bool latchSidebandStream(bool& recomputeVisibleRegions) override; - void setTransformHint(ui::Transform::RotationFlags displayTransformHint) override; - - bool hasFrameUpdate() const override; - - status_t updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime, - nsecs_t expectedPresentTime) override; - - status_t updateActiveBuffer() override; - status_t updateFrameNumber() override; - void setFrameTimelineInfoForBuffer(const FrameTimelineInfo& frameTimelineInfo) override; - - sp createClone() override; - - void onFirstRef() override; - - void onFrameAvailable(const BufferItem& item); - void onFrameReplaced(const BufferItem& item); - void onSidebandStreamChanged(); - void onFrameDequeued(const uint64_t bufferId); - void onFrameDetached(const uint64_t bufferId); - void onFrameCancelled(const uint64_t bufferId); - - // Temporary - Used only for LEGACY camera mode. - uint32_t getProducerStickyTransform() const; - - sp mConsumer; - sp mProducer; - - bool mUpdateTexImageFailed{false}; - - uint64_t mPreviousBufferId = 0; - uint64_t mPreviousReleasedFrameNumber = 0; - - // Local copy of the queued contents of the incoming BufferQueue - mutable Mutex mQueueItemLock; - Condition mQueueItemCondition; - - struct BufferData { - BufferData(BufferItem item, std::shared_ptr surfaceFrame) - : item(item), surfaceFrame(surfaceFrame) {} - BufferItem item; - std::shared_ptr surfaceFrame; - }; - std::vector mQueueItems; - std::atomic mLastFrameNumberReceived{0}; - - // thread-safe - std::atomic mQueuedFrames{0}; - - sp mContentsChangedListener; - - // The last vsync info received on this layer. This will be used when we get - // a buffer to correlate the buffer with the vsync id. Can only be accessed - // with the SF state lock held. - FrameTimelineInfo mFrameTimelineInfo; -}; - -} // namespace android diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index a7438f2f23..9f4f0a267e 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -106,7 +106,6 @@ #include #include "BackgroundExecutor.h" #include "BufferLayer.h" -#include "BufferQueueLayer.h" #include "BufferStateLayer.h" #include "Client.h" #include "Colorizer.h" @@ -4758,42 +4757,6 @@ status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, sp* outHa return result; } -status_t SurfaceFlinger::createBufferQueueLayer(LayerCreationArgs& args, PixelFormat& format, - sp* handle, - sp* gbp, - sp* outLayer) { - // initialize the surfaces - switch (format) { - case PIXEL_FORMAT_TRANSPARENT: - case PIXEL_FORMAT_TRANSLUCENT: - format = PIXEL_FORMAT_RGBA_8888; - break; - case PIXEL_FORMAT_OPAQUE: - format = PIXEL_FORMAT_RGBX_8888; - break; - } - - sp layer; - args.textureName = getNewTexture(); - { - // Grab the SF state lock during this since it's the only safe way to access - // RenderEngine when creating a BufferLayerConsumer - // TODO: Check if this lock is still needed here - Mutex::Autolock lock(mStateLock); - layer = getFactory().createBufferQueueLayer(args); - } - - status_t err = layer->setDefaultBufferProperties(0, 0, format); - if (err == NO_ERROR) { - *handle = layer->getHandle(); - *gbp = layer->getProducer(); - *outLayer = layer; - } - - ALOGE_IF(err, "createBufferQueueLayer() failed (%s)", strerror(-err)); - return err; -} - status_t SurfaceFlinger::createBufferStateLayer(LayerCreationArgs& args, sp* handle, sp* outLayer) { args.textureName = getNewTexture(); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 6c7ed00480..5182ed8556 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -373,7 +373,6 @@ protected: private: friend class BufferLayer; - friend class BufferQueueLayer; friend class BufferStateLayer; friend class Client; friend class FpsReporter; @@ -823,10 +822,6 @@ private: const sp& parentLayer = nullptr, uint32_t* outTransformHint = nullptr); - status_t createBufferQueueLayer(LayerCreationArgs& args, PixelFormat& format, - sp* outHandle, sp* outGbp, - sp* outLayer); - status_t createBufferStateLayer(LayerCreationArgs& args, sp* outHandle, sp* outLayer); diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp index cf97643ba9..39a5d0fc05 100644 --- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp +++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp @@ -23,7 +23,6 @@ #include #include "BufferLayerConsumer.h" -#include "BufferQueueLayer.h" #include "BufferStateLayer.h" #include "ContainerLayer.h" #include "DisplayDevice.h" @@ -109,10 +108,6 @@ sp DefaultFactory::createContainerLayer(const LayerCreationArgs& return new ContainerLayer(args); } -sp DefaultFactory::createBufferQueueLayer(const LayerCreationArgs&) { - return nullptr; -} - sp DefaultFactory::createBufferStateLayer(const LayerCreationArgs& args) { return new BufferStateLayer(args); } diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h index 501629d4da..173ca816dd 100644 --- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h +++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h @@ -47,7 +47,6 @@ public: std::unique_ptr createNativeWindowSurface( const sp&) override; std::unique_ptr createCompositionEngine() override; - sp createBufferQueueLayer(const LayerCreationArgs& args) override; sp createBufferStateLayer(const LayerCreationArgs& args) override; sp createEffectLayer(const LayerCreationArgs& args) override; sp createContainerLayer(const LayerCreationArgs& args) override; diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h index 6153e8e354..e117e9694f 100644 --- a/services/surfaceflinger/SurfaceFlingerFactory.h +++ b/services/surfaceflinger/SurfaceFlingerFactory.h @@ -30,7 +30,6 @@ namespace android { typedef int32_t PixelFormat; -class BufferQueueLayer; class BufferLayerConsumer; class BufferStateLayer; class ContainerLayer; @@ -98,7 +97,6 @@ public: virtual std::unique_ptr createCompositionEngine() = 0; - virtual sp createBufferQueueLayer(const LayerCreationArgs& args) = 0; virtual sp createBufferStateLayer(const LayerCreationArgs& args) = 0; virtual sp createEffectLayer(const LayerCreationArgs& args) = 0; virtual sp createContainerLayer(const LayerCreationArgs& args) = 0; diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index e85b0bffb7..5e20b74f9d 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -105,10 +105,6 @@ public: return new EffectLayer(args); } - sp createBufferQueueLayer(const LayerCreationArgs&) override { - return nullptr; - } - std::unique_ptr createFrameTracer() override { return std::make_unique>(); } diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 10a6b1fde4..456a4981c9 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -30,7 +30,6 @@ #include #include -#include "BufferQueueLayer.h" #include "BufferStateLayer.h" #include "ContainerLayer.h" #include "DisplayDevice.h" @@ -356,10 +355,6 @@ public: return compositionengine::impl::createCompositionEngine(); } - sp createBufferQueueLayer(const LayerCreationArgs &) override { - return nullptr; - } - sp createBufferStateLayer(const LayerCreationArgs &) override { return nullptr; } diff --git a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp index 9cac7c1723..3a9b805861 100644 --- a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp +++ b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp @@ -24,7 +24,6 @@ #include #include -#include "BufferQueueLayer.h" #include "BufferStateLayer.h" #include "EffectLayer.h" #include "FpsReporter.h" diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index a4164c0998..3a05e2fb34 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -29,7 +29,6 @@ #include #include -#include "BufferQueueLayer.h" #include "BufferStateLayer.h" #include "ContainerLayer.h" #include "DisplayDevice.h" @@ -134,10 +133,6 @@ public: return compositionengine::impl::createCompositionEngine(); } - sp createBufferQueueLayer(const LayerCreationArgs&) override { - return nullptr; - } - sp createBufferStateLayer(const LayerCreationArgs&) override { return nullptr; } -- GitLab From 456acaae9592985f6424cd244c55de9982537f42 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 15 Jun 2022 14:19:37 -0700 Subject: [PATCH 0091/1310] Check whether pointer has stopped at liftoff Android uses ACTION_UP and ACTION_POINTER_UP events to signal that a pointer has left the screen. These events don't carry new information, and should always have the same coordinates as the last ACTION_MOVE event. However, these events do have a new timestamp. Before this CL: these events are getting ignored completely by VelocityTracker. If there's a large delay before ACTION_UP, the velocity is still reported to be the same as the if there's no delay. In this CL: we will check these events for timestamps. If too much time has passed, we will clear the strategy. Example logs: VelocityTracker: VelocityTracker: stopped for 2970.0 ms, clearing state upon pointer liftoff. Bug: 200900433 Test: atest libinput_tests Change-Id: Ie3e5a61e70b6324ee4aedaf2bce58766f5598718 --- libs/input/VelocityTracker.cpp | 97 +++++++++++++---------- libs/input/tests/VelocityTracker_test.cpp | 73 +++++++++++++++-- 2 files changed, 122 insertions(+), 48 deletions(-) diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp index 7f427f2364..76aaf61da7 100644 --- a/libs/input/VelocityTracker.cpp +++ b/libs/input/VelocityTracker.cpp @@ -27,6 +27,8 @@ #include #include +using std::literals::chrono_literals::operator""ms; + namespace android { /** @@ -57,8 +59,14 @@ static const nsecs_t NANOS_PER_MS = 1000000; // Some input devices do not send ACTION_MOVE events in the case where a pointer has // stopped. We need to detect this case so that we can accurately predict the // velocity after the pointer starts moving again. -static const nsecs_t ASSUME_POINTER_STOPPED_TIME = 40 * NANOS_PER_MS; +static const std::chrono::duration ASSUME_POINTER_STOPPED_TIME = 40ms; +static std::string toString(std::chrono::nanoseconds t) { + std::stringstream stream; + stream.precision(1); + stream << std::fixed << std::chrono::duration(t).count() << " ms"; + return stream.str(); +} static float vectorDot(const float* a, const float* b, uint32_t m) { float r = 0; @@ -146,18 +154,14 @@ std::unique_ptr VelocityTracker::createStrategy( VelocityTracker::Strategy strategy) { switch (strategy) { case VelocityTracker::Strategy::IMPULSE: - if (DEBUG_STRATEGY) { - ALOGI("Initializing impulse strategy"); - } + ALOGI_IF(DEBUG_STRATEGY, "Initializing impulse strategy"); return std::make_unique(); case VelocityTracker::Strategy::LSQ1: return std::make_unique(1); case VelocityTracker::Strategy::LSQ2: - if (DEBUG_STRATEGY && !DEBUG_IMPULSE) { - ALOGI("Initializing lsq2 strategy"); - } + ALOGI_IF(DEBUG_STRATEGY && !DEBUG_IMPULSE, "Initializing lsq2 strategy"); return std::make_unique(2); case VelocityTracker::Strategy::LSQ3: @@ -221,12 +225,11 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, idBits.clearLastMarkedBit(); } - if ((mCurrentPointerIdBits.value & idBits.value) - && eventTime >= mLastEventTime + ASSUME_POINTER_STOPPED_TIME) { - if (DEBUG_VELOCITY) { - ALOGD("VelocityTracker: stopped for %0.3f ms, clearing state.", - (eventTime - mLastEventTime) * 0.000001f); - } + if ((mCurrentPointerIdBits.value & idBits.value) && + std::chrono::nanoseconds(eventTime - mLastEventTime) > ASSUME_POINTER_STOPPED_TIME) { + ALOGD_IF(DEBUG_VELOCITY, "VelocityTracker: stopped for %s, clearing state.", + toString(std::chrono::nanoseconds(eventTime - mLastEventTime)).c_str()); + // We have not received any movements for too long. Assume that all pointers // have stopped. mStrategy->clear(); @@ -281,8 +284,18 @@ void VelocityTracker::addMovement(const MotionEvent* event) { case AMOTION_EVENT_ACTION_MOVE: case AMOTION_EVENT_ACTION_HOVER_MOVE: break; - default: - // Ignore all other actions because they do not convey any new information about + case AMOTION_EVENT_ACTION_POINTER_UP: + case AMOTION_EVENT_ACTION_UP: { + std::chrono::nanoseconds delaySinceLastEvent(event->getEventTime() - mLastEventTime); + if (delaySinceLastEvent > ASSUME_POINTER_STOPPED_TIME) { + ALOGD_IF(DEBUG_VELOCITY, + "VelocityTracker: stopped for %s, clearing state upon pointer liftoff.", + toString(delaySinceLastEvent).c_str()); + // We have not received any movements for too long. Assume that all pointers + // have stopped. + mStrategy->clear(); + } + // These actions because they do not convey any new information about // pointer movement. We also want to preserve the last known velocity of the pointers. // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position // of the pointers that went up. ACTION_POINTER_UP does include the new position of @@ -292,6 +305,10 @@ void VelocityTracker::addMovement(const MotionEvent* event) { // before adding the movement. return; } + default: + // Ignore all other actions. + return; + } size_t pointerCount = event->getPointerCount(); if (pointerCount > MAX_POINTERS) { @@ -438,10 +455,10 @@ void LeastSquaresVelocityTrackerStrategy::addMovement( static bool solveLeastSquares(const std::vector& x, const std::vector& y, const std::vector& w, uint32_t n, float* outB, float* outDet) { const size_t m = x.size(); - if (DEBUG_STRATEGY) { - ALOGD("solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n), - vectorToString(x).c_str(), vectorToString(y).c_str(), vectorToString(w).c_str()); - } + + ALOGD_IF(DEBUG_STRATEGY, "solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n), + vectorToString(x).c_str(), vectorToString(y).c_str(), vectorToString(w).c_str()); + LOG_ALWAYS_FATAL_IF(m != y.size() || m != w.size(), "Mismatched vector sizes"); // Expand the X vector to a matrix A, pre-multiplied by the weights. @@ -452,9 +469,9 @@ static bool solveLeastSquares(const std::vector& x, const std::vector& x, const std::vector& x, const std::vector& x, const std::vector 0.000001f ? 1.0f - (sserr / sstot) : 1; - if (DEBUG_STRATEGY) { - ALOGD(" - sserr=%f", sserr); - ALOGD(" - sstot=%f", sstot); - ALOGD(" - det=%f", *outDet); - } + + ALOGD_IF(DEBUG_STRATEGY, " - sserr=%f", sserr); + ALOGD_IF(DEBUG_STRATEGY, " - sstot=%f", sstot); + ALOGD_IF(DEBUG_STRATEGY, " - det=%f", *outDet); + return true; } @@ -673,11 +687,11 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id, outEstimator->time = newestMovement.eventTime; outEstimator->degree = degree; outEstimator->confidence = xdet * ydet; - if (DEBUG_STRATEGY) { - ALOGD("estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f", - int(outEstimator->degree), vectorToString(outEstimator->xCoeff, n).c_str(), - vectorToString(outEstimator->yCoeff, n).c_str(), outEstimator->confidence); - } + + ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f", + int(outEstimator->degree), vectorToString(outEstimator->xCoeff, n).c_str(), + vectorToString(outEstimator->yCoeff, n).c_str(), outEstimator->confidence); + return true; } } @@ -1185,9 +1199,10 @@ bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, outEstimator->time = newestMovement.eventTime; outEstimator->degree = 2; // similar results to 2nd degree fit outEstimator->confidence = 1; - if (DEBUG_STRATEGY) { - ALOGD("velocity: (%.1f, %.1f)", outEstimator->xCoeff[1], outEstimator->yCoeff[1]); - } + + ALOGD_IF(DEBUG_STRATEGY, "velocity: (%.1f, %.1f)", outEstimator->xCoeff[1], + outEstimator->yCoeff[1]); + if (DEBUG_IMPULSE) { // TODO(b/134179997): delete this block once the switch to 'impulse' is complete. // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp index a87b1873f0..4a445de3ac 100644 --- a/libs/input/tests/VelocityTracker_test.cpp +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -26,7 +26,9 @@ #include #include -using namespace std::chrono_literals; +using std::literals::chrono_literals::operator""ms; +using std::literals::chrono_literals::operator""ns; +using std::literals::chrono_literals::operator""us; using android::base::StringPrintf; namespace android { @@ -149,8 +151,7 @@ static std::vector createMotionEventStream( if (i == 0) { action = AMOTION_EVENT_ACTION_DOWN; EXPECT_EQ(1U, pointerCount) << "First event should only have 1 pointer"; - } else if (i == motions.size() - 1) { - EXPECT_EQ(1U, pointerCount) << "Last event should only have 1 pointer"; + } else if ((i == motions.size() - 1) && pointerCount == 1) { action = AMOTION_EVENT_ACTION_UP; } else { const MotionEventEntry& previousEntry = motions[i-1]; @@ -195,7 +196,7 @@ static std::vector createMotionEventStream( static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, const std::vector& motions, int32_t axis, - float targetVelocity) { + float targetVelocity, uint32_t pointerId = DEFAULT_POINTER_ID) { VelocityTracker vt(strategy); float Vx, Vy; @@ -204,7 +205,7 @@ static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, vt.addMovement(&event); } - vt.getVelocity(DEFAULT_POINTER_ID, &Vx, &Vy); + vt.getVelocity(pointerId, &Vx, &Vy); switch (axis) { case AMOTION_EVENT_AXIS_X: @@ -846,12 +847,70 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_ThreeFi // Velocity should actually be zero, but we expect 0.016 here instead. // This is close enough to zero, and is likely caused by division by a very small number. - computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, -0.016); - computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y, -0.016); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y, 0); computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0); computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_Y, 0); } +/** + * ================= Pointer liftoff =============================================================== + */ + +/** + * The last movement of a pointer is always ACTION_POINTER_UP or ACTION_UP. If there's a short delay + * between the last ACTION_MOVE and the next ACTION_POINTER_UP or ACTION_UP, velocity should not be + * affected by the liftoff. + */ +TEST_F(VelocityTrackerTest, ShortDelayBeforeActionUp) { + std::vector motions = { + {0ms, {{10, 0}}}, {10ms, {{20, 0}}}, {20ms, {{30, 0}}}, {30ms, {{30, 0}}}, // ACTION_UP + }; + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, + 1000); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 1000); +} + +/** + * The last movement of a single pointer is ACTION_UP. If there's a long delay between the last + * ACTION_MOVE and the final ACTION_UP, velocity should be reported as zero because the pointer + * should be assumed to have stopped. + */ +TEST_F(VelocityTrackerTest, LongDelayBeforeActionUp) { + std::vector motions = { + {0ms, {{10, 0}}}, + {10ms, {{20, 0}}}, + {20ms, {{30, 0}}}, + {3000ms, {{30, 0}}}, // ACTION_UP + }; + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0); +} + +/** + * The last movement of a pointer is always ACTION_POINTER_UP or ACTION_UP. If there's a long delay + * before ACTION_POINTER_UP event, the movement should be assumed to have stopped. + * The final velocity should be reported as zero for all pointers. + */ +TEST_F(VelocityTrackerTest, LongDelayBeforeActionPointerUp) { + std::vector motions = { + {0ms, {{10, 0}}}, + {10ms, {{20, 0}, {100, 0}}}, + {20ms, {{30, 0}, {200, 0}}}, + {30ms, {{30, 0}, {300, 0}}}, + {40ms, {{30, 0}, {400, 0}}}, + {3000ms, {{30, 0}}}, // ACTION_POINTER_UP + }; + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0, + /*pointerId*/ 0); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0, + /*pointerId*/ 0); + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0, + /*pointerId*/ 1); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0, + /*pointerId*/ 1); +} + /** * ================== Tests for least squares fitting ============================================== * -- GitLab From 74007943350ce4673e5611f5f3b58d56d9e6a5fc Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 13 Jun 2022 13:57:47 -0700 Subject: [PATCH 0092/1310] Use std::vector when sending keyCodes We are currently sending a raw pointer to keyCodes and a size. The size is also used for outFlags, but it's not well-documented anyways. Eventually, we may want to just return the flags instead of returning the boolean. For now, though, just change the incoming parameter to a vector to make it less errorprone. Bug: 228005926 Test: atest libinput_tests inputflinger_tests Change-Id: I6cc2f5d9b3b7b7c3c120a779ea4cfb4b06e27d1e --- .../inputflinger/include/InputReaderBase.h | 2 +- services/inputflinger/reader/EventHub.cpp | 8 ++-- services/inputflinger/reader/InputDevice.cpp | 8 ++-- services/inputflinger/reader/InputReader.cpp | 14 +++--- .../inputflinger/reader/include/EventHub.h | 4 +- .../inputflinger/reader/include/InputDevice.h | 6 +-- .../inputflinger/reader/include/InputReader.h | 7 +-- .../reader/mapper/InputMapper.cpp | 4 +- .../inputflinger/reader/mapper/InputMapper.h | 4 +- .../reader/mapper/KeyboardInputMapper.cpp | 14 +++--- .../reader/mapper/KeyboardInputMapper.h | 4 +- .../reader/mapper/TouchInputMapper.cpp | 7 +-- .../reader/mapper/TouchInputMapper.h | 2 +- .../inputflinger/tests/InputReader_test.cpp | 44 ++++++++++--------- 14 files changed, 66 insertions(+), 62 deletions(-) diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index 41ecef365d..77c9142131 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -95,7 +95,7 @@ public: /* Determine whether physical keys exist for the given framework-domain key codes. */ virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask, - size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) = 0; + const std::vector& keyCodes, uint8_t* outFlags) = 0; /* Requests that a reconfiguration of all input devices. * The changes flag is a bitfield that indicates what has changed and whether diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index 669d2e1833..6a8ed49db8 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -952,20 +952,20 @@ status_t EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* return -1; } -bool EventHub::markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, +bool EventHub::markSupportedKeyCodes(int32_t deviceId, const std::vector& keyCodes, uint8_t* outFlags) const { std::scoped_lock _l(mLock); Device* device = getDeviceLocked(deviceId); if (device != nullptr && device->keyMap.haveKeyLayout()) { - for (size_t codeIndex = 0; codeIndex < numCodes; codeIndex++) { + for (size_t codeIndex = 0; codeIndex < keyCodes.size(); codeIndex++) { std::vector scanCodes = device->keyMap.keyLayoutMap->findScanCodesForKey(keyCodes[codeIndex]); // check the possible scan codes identified by the layout map against the // map of codes actually emitted by the driver - for (size_t sc = 0; sc < scanCodes.size(); sc++) { - if (device->keyBitmask.test(scanCodes[sc])) { + for (const int32_t scanCode : scanCodes) { + if (device->keyBitmask.test(scanCode)) { outFlags[codeIndex] = 1; break; } diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index ba5083bec3..b67777fc0a 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -465,12 +465,12 @@ int32_t InputDevice::getState(uint32_t sourceMask, int32_t code, GetStateFunc ge return result; } -bool InputDevice::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, - const int32_t* keyCodes, uint8_t* outFlags) { +bool InputDevice::markSupportedKeyCodes(uint32_t sourceMask, const std::vector& keyCodes, + uint8_t* outFlags) { bool result = false; - for_each_mapper([&result, sourceMask, numCodes, keyCodes, outFlags](InputMapper& mapper) { + for_each_mapper([&result, sourceMask, keyCodes, outFlags](InputMapper& mapper) { if (sourcesMatchMask(mapper.getSources(), sourceMask)) { - result |= mapper.markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags); + result |= mapper.markSupportedKeyCodes(sourceMask, keyCodes, outFlags); } }); return result; diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 9c5a129213..79901f03e4 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -561,28 +561,28 @@ void InputReader::toggleCapsLockState(int32_t deviceId) { device->updateMetaState(AKEYCODE_CAPS_LOCK); } -bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask, size_t numCodes, - const int32_t* keyCodes, uint8_t* outFlags) { +bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask, + const std::vector& keyCodes, uint8_t* outFlags) { std::scoped_lock _l(mLock); - memset(outFlags, 0, numCodes); - return markSupportedKeyCodesLocked(deviceId, sourceMask, numCodes, keyCodes, outFlags); + memset(outFlags, 0, keyCodes.size()); + return markSupportedKeyCodesLocked(deviceId, sourceMask, keyCodes, outFlags); } bool InputReader::markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask, - size_t numCodes, const int32_t* keyCodes, + const std::vector& keyCodes, uint8_t* outFlags) { bool result = false; if (deviceId >= 0) { InputDevice* device = findInputDeviceLocked(deviceId); if (device && !device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) { - result = device->markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags); + result = device->markSupportedKeyCodes(sourceMask, keyCodes, outFlags); } } else { for (auto& devicePair : mDevices) { std::shared_ptr& device = devicePair.second; if (!device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) { - result |= device->markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags); + result |= device->markSupportedKeyCodes(sourceMask, keyCodes, outFlags); } } } diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index 130c55639b..5453ebbc9c 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -311,7 +311,7 @@ public: /* * Examine key input devices for specific framework keycode support */ - virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, + virtual bool markSupportedKeyCodes(int32_t deviceId, const std::vector& keyCodes, uint8_t* outFlags) const = 0; virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const = 0; @@ -488,7 +488,7 @@ public: status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const override final; - bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, + bool markSupportedKeyCodes(int32_t deviceId, const std::vector& keyCodes, uint8_t* outFlags) const override final; size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) override final; diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 728020eadc..51872ac095 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -88,7 +88,7 @@ public: int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode); int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const; - bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, const int32_t* keyCodes, + bool markSupportedKeyCodes(uint32_t sourceMask, const std::vector& keyCodes, uint8_t* outFlags); void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token); void cancelVibrate(int32_t token); @@ -324,9 +324,9 @@ public: inline status_t getAbsoluteAxisValue(int32_t code, int32_t* outValue) const { return mEventHub->getAbsoluteAxisValue(mId, code, outValue); } - inline bool markSupportedKeyCodes(size_t numCodes, const int32_t* keyCodes, + inline bool markSupportedKeyCodes(const std::vector& keyCodes, uint8_t* outFlags) const { - return mEventHub->markSupportedKeyCodes(mId, numCodes, keyCodes, outFlags); + return mEventHub->markSupportedKeyCodes(mId, keyCodes, outFlags); } inline bool hasScanCode(int32_t scanCode) const { return mEventHub->hasScanCode(mId, scanCode); diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h index daeaa1dbe3..ae41e01f7e 100644 --- a/services/inputflinger/reader/include/InputReader.h +++ b/services/inputflinger/reader/include/InputReader.h @@ -73,7 +73,7 @@ public: void toggleCapsLockState(int32_t deviceId) override; - bool hasKeys(int32_t deviceId, uint32_t sourceMask, size_t numCodes, const int32_t* keyCodes, + bool hasKeys(int32_t deviceId, uint32_t sourceMask, const std::vector& keyCodes, uint8_t* outFlags) override; void requestRefreshConfiguration(uint32_t changes) override; @@ -237,8 +237,9 @@ private: typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code); int32_t getStateLocked(int32_t deviceId, uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc) REQUIRES(mLock); - bool markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask, size_t numCodes, - const int32_t* keyCodes, uint8_t* outFlags) REQUIRES(mLock); + bool markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask, + const std::vector& keyCodes, uint8_t* outFlags) + REQUIRES(mLock); // find an InputDevice from an InputDevice id InputDevice* findInputDeviceLocked(int32_t deviceId) const REQUIRES(mLock); diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp index 7b185e029c..75cebf3ddb 100644 --- a/services/inputflinger/reader/mapper/InputMapper.cpp +++ b/services/inputflinger/reader/mapper/InputMapper.cpp @@ -55,8 +55,8 @@ int32_t InputMapper::getKeyCodeForKeyLocation(int32_t locationKeyCode) const { return AKEYCODE_UNKNOWN; } -bool InputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, - const int32_t* keyCodes, uint8_t* outFlags) { +bool InputMapper::markSupportedKeyCodes(uint32_t sourceMask, const std::vector& keyCodes, + uint8_t* outFlags) { return false; } diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h index fce6409b3f..7858728b6e 100644 --- a/services/inputflinger/reader/mapper/InputMapper.h +++ b/services/inputflinger/reader/mapper/InputMapper.h @@ -64,8 +64,8 @@ public: virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode); virtual int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const; - virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, - const int32_t* keyCodes, uint8_t* outFlags); + virtual bool markSupportedKeyCodes(uint32_t sourceMask, const std::vector& keyCodes, + uint8_t* outFlags); virtual void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token); virtual void cancelVibrate(int32_t token); virtual bool isVibrating(); diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index 2ac81781fa..8eb870fc87 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -379,9 +379,10 @@ int32_t KeyboardInputMapper::getKeyCodeForKeyLocation(int32_t locationKeyCode) c return getDeviceContext().getKeyCodeForKeyLocation(locationKeyCode); } -bool KeyboardInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, - const int32_t* keyCodes, uint8_t* outFlags) { - return getDeviceContext().markSupportedKeyCodes(numCodes, keyCodes, outFlags); +bool KeyboardInputMapper::markSupportedKeyCodes(uint32_t sourceMask, + const std::vector& keyCodes, + uint8_t* outFlags) { + return getDeviceContext().markSupportedKeyCodes(keyCodes, outFlags); } int32_t KeyboardInputMapper::getMetaState() { @@ -433,13 +434,12 @@ void KeyboardInputMapper::updateLedState(bool reset) { mMetaState |= getContext()->getLedMetaState(); constexpr int32_t META_NUM = 3; - const std::array keyCodes = {AKEYCODE_CAPS_LOCK, AKEYCODE_NUM_LOCK, - AKEYCODE_SCROLL_LOCK}; + const std::vector keyCodes{AKEYCODE_CAPS_LOCK, AKEYCODE_NUM_LOCK, + AKEYCODE_SCROLL_LOCK}; const std::array metaCodes = {AMETA_CAPS_LOCK_ON, AMETA_NUM_LOCK_ON, AMETA_SCROLL_LOCK_ON}; std::array flags = {0, 0, 0}; - bool hasKeyLayout = - getDeviceContext().markSupportedKeyCodes(META_NUM, keyCodes.data(), flags.data()); + bool hasKeyLayout = getDeviceContext().markSupportedKeyCodes(keyCodes, flags.data()); // If the device doesn't have the physical meta key it shouldn't generate the corresponding // meta state. if (hasKeyLayout) { diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index 378769639e..0a55def33f 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -36,8 +36,8 @@ public: virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode) override; virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override; - virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, - const int32_t* keyCodes, uint8_t* outFlags) override; + virtual bool markSupportedKeyCodes(uint32_t sourceMask, const std::vector& keyCodes, + uint8_t* outFlags) override; virtual int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const override; virtual int32_t getMetaState() override; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 637b1cb263..d6b72ed261 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -4021,10 +4021,11 @@ int32_t TouchInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode return AKEY_STATE_UNKNOWN; } -bool TouchInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, - const int32_t* keyCodes, uint8_t* outFlags) { +bool TouchInputMapper::markSupportedKeyCodes(uint32_t sourceMask, + const std::vector& keyCodes, + uint8_t* outFlags) { for (const VirtualKey& virtualKey : mVirtualKeys) { - for (size_t i = 0; i < numCodes; i++) { + for (size_t i = 0; i < keyCodes.size(); i++) { if (virtualKey.keyCode == keyCodes[i]) { outFlags[i] = 1; } diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index c948f565d9..714ad3fff6 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -147,7 +147,7 @@ public: int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode) override; int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override; - bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, const int32_t* keyCodes, + bool markSupportedKeyCodes(uint32_t sourceMask, const std::vector& keyCodes, uint8_t* outFlags) override; void cancelTouch(nsecs_t when, nsecs_t readTime) override; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index a26a0bcf67..13801125d2 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -887,13 +887,13 @@ private: } // Return true if the device has non-empty key layout. - bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, + bool markSupportedKeyCodes(int32_t deviceId, const std::vector& keyCodes, uint8_t* outFlags) const override { bool result = false; Device* device = getDevice(deviceId); if (device) { result = device->keysByScanCode.size() > 0 || device->keysByUsageCode.size() > 0; - for (size_t i = 0; i < numCodes; i++) { + for (size_t i = 0; i < keyCodes.size(); i++) { for (size_t j = 0; j < device->keysByScanCode.size(); j++) { if (keyCodes[i] == device->keysByScanCode.valueAt(j).keyCode) { outFlags[i] = 1; @@ -1209,9 +1209,9 @@ private: } // Return true if the device has non-empty key layout. - bool markSupportedKeyCodes(uint32_t, size_t numCodes, const int32_t* keyCodes, + bool markSupportedKeyCodes(uint32_t, const std::vector& keyCodes, uint8_t* outFlags) override { - for (size_t i = 0; i < numCodes; i++) { + for (size_t i = 0; i < keyCodes.size(); i++) { for (size_t j = 0; j < mSupportedKeyCodes.size(); j++) { if (keyCodes[i] == mSupportedKeyCodes[j]) { outFlags[i] = 1; @@ -1855,34 +1855,37 @@ TEST_F(InputReaderTest, MarkSupportedKeyCodes_ForwardsRequestsToMappers) { mapper.addSupportedKeyCode(AKEYCODE_A); mapper.addSupportedKeyCode(AKEYCODE_B); - const int32_t keyCodes[4] = { AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2 }; + const std::vector keyCodes{AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2}; uint8_t flags[4] = { 0, 0, 0, 1 }; - ASSERT_FALSE(mReader->hasKeys(0, AINPUT_SOURCE_ANY, 4, keyCodes, flags)) + ASSERT_FALSE(mReader->hasKeys(0, AINPUT_SOURCE_ANY, keyCodes, flags)) << "Should return false when device id is >= 0 but unknown."; ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]); flags[3] = 1; - ASSERT_FALSE(mReader->hasKeys(deviceId, AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags)) + ASSERT_FALSE(mReader->hasKeys(deviceId, AINPUT_SOURCE_TRACKBALL, keyCodes, flags)) << "Should return false when device id is valid but the sources are not supported by " "the device."; ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]); flags[3] = 1; - ASSERT_TRUE(mReader->hasKeys(deviceId, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, 4, + ASSERT_TRUE(mReader->hasKeys(deviceId, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, keyCodes, flags)) << "Should return value provided by mapper when device id is valid and the device " "supports some of the sources."; ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]); flags[3] = 1; - ASSERT_FALSE(mReader->hasKeys(-1, AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags)) - << "Should return false when the device id is < 0 but the sources are not supported by any device."; + ASSERT_FALSE(mReader->hasKeys(-1, AINPUT_SOURCE_TRACKBALL, keyCodes, flags)) + << "Should return false when the device id is < 0 but the sources are not supported by " + "any device."; ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]); flags[3] = 1; - ASSERT_TRUE(mReader->hasKeys(-1, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags)) - << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources."; + ASSERT_TRUE( + mReader->hasKeys(-1, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, keyCodes, flags)) + << "Should return value provided by mapper when device id is < 0 and one of the " + "devices supports some of the sources."; ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]); } @@ -2718,9 +2721,9 @@ TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) { ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getSwitchState(AINPUT_SOURCE_KEYBOARD, 0)) << "Ignored device should return unknown switch state."; - const int32_t keyCodes[2] = { AKEYCODE_A, AKEYCODE_B }; + const std::vector keyCodes{AKEYCODE_A, AKEYCODE_B}; uint8_t flags[2] = { 0, 1 }; - ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, 2, keyCodes, flags)) + ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, keyCodes, flags)) << "Ignored device should never mark any key codes."; ASSERT_EQ(0, flags[0]) << "Flag for unsupported key should be unchanged."; ASSERT_EQ(1, flags[1]) << "Flag for unsupported key should be unchanged."; @@ -2795,16 +2798,16 @@ TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRe ASSERT_EQ(AKEY_STATE_DOWN, mDevice->getSwitchState(AINPUT_SOURCE_KEYBOARD, 4)) << "Should query mapper when source is supported."; - const int32_t keyCodes[4] = { AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2 }; + const std::vector keyCodes{AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2}; uint8_t flags[4] = { 0, 0, 0, 1 }; - ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags)) + ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_TRACKBALL, keyCodes, flags)) << "Should do nothing when source is unsupported."; ASSERT_EQ(0, flags[0]) << "Flag should be unchanged when source is unsupported."; ASSERT_EQ(0, flags[1]) << "Flag should be unchanged when source is unsupported."; ASSERT_EQ(0, flags[2]) << "Flag should be unchanged when source is unsupported."; ASSERT_EQ(1, flags[3]) << "Flag should be unchanged when source is unsupported."; - ASSERT_TRUE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, 4, keyCodes, flags)) + ASSERT_TRUE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, keyCodes, flags)) << "Should query mapper when source is supported."; ASSERT_EQ(1, flags[0]) << "Flag for supported key should be set."; ASSERT_EQ(1, flags[1]) << "Flag for supported key should be set."; @@ -3726,9 +3729,8 @@ TEST_F(KeyboardInputMapperTest, MarkSupportedKeyCodes) { mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0); - const int32_t keyCodes[2] = { AKEYCODE_A, AKEYCODE_B }; uint8_t flags[2] = { 0, 0 }; - ASSERT_TRUE(mapper.markSupportedKeyCodes(AINPUT_SOURCE_ANY, 1, keyCodes, flags)); + ASSERT_TRUE(mapper.markSupportedKeyCodes(AINPUT_SOURCE_ANY, {AKEYCODE_A, AKEYCODE_B}, flags)); ASSERT_TRUE(flags[0]); ASSERT_FALSE(flags[1]); } @@ -5363,9 +5365,9 @@ TEST_F(SingleTouchInputMapperTest, MarkSupportedKeyCodes) { prepareVirtualKeys(); SingleTouchInputMapper& mapper = addMapperAndConfigure(); - const int32_t keys[2] = { AKEYCODE_HOME, AKEYCODE_A }; uint8_t flags[2] = { 0, 0 }; - ASSERT_TRUE(mapper.markSupportedKeyCodes(AINPUT_SOURCE_ANY, 2, keys, flags)); + ASSERT_TRUE( + mapper.markSupportedKeyCodes(AINPUT_SOURCE_ANY, {AKEYCODE_HOME, AKEYCODE_A}, flags)); ASSERT_TRUE(flags[0]); ASSERT_FALSE(flags[1]); } -- GitLab From 9ed43398bd0766e759da21e89d9ea5c10bad2b78 Mon Sep 17 00:00:00 2001 From: Arthur Hung Date: Fri, 27 May 2022 06:31:57 +0000 Subject: [PATCH 0093/1310] Reduce setInputWindows call (1/2) The cursor surface and hotspot surface would cause the visible region dirty when moving on the screen. That would also cause SF update the input infos to InputFlinger. To reduce unecessary updating, add more checking for same value and ignore updating if just the cursor moving. Test: enable setInputWindow log, connect a mouse device and move. Bug: 133780957 Change-Id: I099c1175e291adccfafce49536acfdfb38c40371 --- services/surfaceflinger/Layer.cpp | 17 ++++++++++++++--- services/surfaceflinger/SurfaceFlinger.cpp | 14 +++++++++----- services/surfaceflinger/SurfaceFlinger.h | 2 +- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index b1f0ba1fe9..04c1bd96dc 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -724,7 +724,7 @@ uint32_t Layer::doTransaction(uint32_t flags) { if (s.sequence != mLastCommittedTxSequence) { // invalidate and recompute the visible regions if needed - mLastCommittedTxSequence = s.sequence; + mLastCommittedTxSequence = s.sequence; flags |= eVisibleRegion; this->contentDirty = true; @@ -732,6 +732,10 @@ uint32_t Layer::doTransaction(uint32_t flags) { mNeedsFiltering = getActiveTransform(s).needsBilinearFiltering(); } + if (!mPotentialCursor && (flags & Layer::eVisibleRegion)) { + mFlinger->mUpdateInputInfo = true; + } + commitTransaction(mDrawingState); return flags; @@ -882,7 +886,7 @@ bool Layer::setTrustedOverlay(bool isTrustedOverlay) { if (mDrawingState.isTrustedOverlay == isTrustedOverlay) return false; mDrawingState.isTrustedOverlay = isTrustedOverlay; mDrawingState.modified = true; - mFlinger->mInputInfoChanged = true; + mFlinger->mUpdateInputInfo = true; setTransactionFlags(eTransactionNeeded); return true; } @@ -979,6 +983,13 @@ bool Layer::setBackgroundBlurRadius(int backgroundBlurRadius) { return true; } bool Layer::setMatrix(const layer_state_t::matrix22_t& matrix) { + if (matrix.dsdx == mDrawingState.transform.dsdx() && + matrix.dtdy == mDrawingState.transform.dtdy() && + matrix.dtdx == mDrawingState.transform.dtdx() && + matrix.dsdy == mDrawingState.transform.dsdy()) { + return false; + } + ui::Transform t; t.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy); @@ -2051,7 +2062,7 @@ void Layer::setInputInfo(const WindowInfo& info) { mDrawingState.inputInfo = info; mDrawingState.touchableRegionCrop = fromHandle(info.touchableRegionCropHandle.promote()); mDrawingState.modified = true; - mFlinger->mInputInfoChanged = true; + mFlinger->mUpdateInputInfo = true; setTransactionFlags(eTransactionNeeded); } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index a7438f2f23..c2affbd95b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3115,6 +3115,7 @@ void SurfaceFlinger::processDisplayChangesLocked() { const KeyedVector, DisplayDeviceState>& draw(mDrawingState.displays); if (!curr.isIdenticalTo(draw)) { mVisibleRegionsDirty = true; + mUpdateInputInfo = true; // find the displays that were removed // (ie: in drawing state but not in current state) @@ -3159,6 +3160,7 @@ void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { if (mSomeChildrenChanged) { mVisibleRegionsDirty = true; mSomeChildrenChanged = false; + mUpdateInputInfo = true; } // Update transform hint. @@ -3222,6 +3224,7 @@ void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { mLayersAdded = false; // Layers have been added. mVisibleRegionsDirty = true; + mUpdateInputInfo = true; } // some layers might have been removed, so @@ -3229,6 +3232,7 @@ void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { if (mLayersRemoved) { mLayersRemoved = false; mVisibleRegionsDirty = true; + mUpdateInputInfo = true; mDrawingState.traverseInZOrder([&](Layer* layer) { if (mLayersPendingRemoval.indexOf(layer) >= 0) { // this layer is not visible anymore @@ -3253,14 +3257,14 @@ void SurfaceFlinger::updateInputFlinger() { std::vector windowInfos; std::vector displayInfos; bool updateWindowInfo = false; - if (mVisibleRegionsDirty || mInputInfoChanged) { - mInputInfoChanged = false; + if (mUpdateInputInfo) { + mUpdateInputInfo = false; updateWindowInfo = true; buildWindowInfos(windowInfos, displayInfos); - } - if (!updateWindowInfo && mInputWindowCommands.empty()) { + } else if (mInputWindowCommands.empty()) { return; } + BackgroundExecutor::getInstance().sendCallbacks({[updateWindowInfo, windowInfos = std::move(windowInfos), displayInfos = std::move(displayInfos), @@ -4609,7 +4613,7 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime if (what & layer_state_t::eDropInputModeChanged) { if (layer->setDropInputMode(s.dropInputMode)) { flags |= eTraversalNeeded; - mInputInfoChanged = true; + mUpdateInputInfo = true; } } // This has to happen after we reparent children because when we reparent to null we remove diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 6c7ed00480..f82fbdeae7 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1203,7 +1203,7 @@ private: // Set during transaction application stage to track if the input info or children // for a layer has changed. // TODO: Also move visibleRegions over to a boolean system. - bool mInputInfoChanged = false; + bool mUpdateInputInfo = false; bool mSomeChildrenChanged; bool mSomeDataspaceChanged = false; bool mForceTransactionDisplayChange = false; -- GitLab From 21fcc2b181c431210d0d976c7898058751c7d555 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 17 Jun 2022 21:29:02 +0000 Subject: [PATCH 0094/1310] Revert "Check whether pointer has stopped at liftoff" This reverts commit 456acaae9592985f6424cd244c55de9982537f42. Test: not tested, this is a revert Bug: 236335820 Reason for revert: breaks NumberPickerTest Change-Id: I9b1e2002c51e96c52b7bb13f55ae1331ffb4915b --- libs/input/VelocityTracker.cpp | 97 ++++++++++------------- libs/input/tests/VelocityTracker_test.cpp | 73 ++--------------- 2 files changed, 48 insertions(+), 122 deletions(-) diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp index 76aaf61da7..7f427f2364 100644 --- a/libs/input/VelocityTracker.cpp +++ b/libs/input/VelocityTracker.cpp @@ -27,8 +27,6 @@ #include #include -using std::literals::chrono_literals::operator""ms; - namespace android { /** @@ -59,14 +57,8 @@ static const nsecs_t NANOS_PER_MS = 1000000; // Some input devices do not send ACTION_MOVE events in the case where a pointer has // stopped. We need to detect this case so that we can accurately predict the // velocity after the pointer starts moving again. -static const std::chrono::duration ASSUME_POINTER_STOPPED_TIME = 40ms; +static const nsecs_t ASSUME_POINTER_STOPPED_TIME = 40 * NANOS_PER_MS; -static std::string toString(std::chrono::nanoseconds t) { - std::stringstream stream; - stream.precision(1); - stream << std::fixed << std::chrono::duration(t).count() << " ms"; - return stream.str(); -} static float vectorDot(const float* a, const float* b, uint32_t m) { float r = 0; @@ -154,14 +146,18 @@ std::unique_ptr VelocityTracker::createStrategy( VelocityTracker::Strategy strategy) { switch (strategy) { case VelocityTracker::Strategy::IMPULSE: - ALOGI_IF(DEBUG_STRATEGY, "Initializing impulse strategy"); + if (DEBUG_STRATEGY) { + ALOGI("Initializing impulse strategy"); + } return std::make_unique(); case VelocityTracker::Strategy::LSQ1: return std::make_unique(1); case VelocityTracker::Strategy::LSQ2: - ALOGI_IF(DEBUG_STRATEGY && !DEBUG_IMPULSE, "Initializing lsq2 strategy"); + if (DEBUG_STRATEGY && !DEBUG_IMPULSE) { + ALOGI("Initializing lsq2 strategy"); + } return std::make_unique(2); case VelocityTracker::Strategy::LSQ3: @@ -225,11 +221,12 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, idBits.clearLastMarkedBit(); } - if ((mCurrentPointerIdBits.value & idBits.value) && - std::chrono::nanoseconds(eventTime - mLastEventTime) > ASSUME_POINTER_STOPPED_TIME) { - ALOGD_IF(DEBUG_VELOCITY, "VelocityTracker: stopped for %s, clearing state.", - toString(std::chrono::nanoseconds(eventTime - mLastEventTime)).c_str()); - + if ((mCurrentPointerIdBits.value & idBits.value) + && eventTime >= mLastEventTime + ASSUME_POINTER_STOPPED_TIME) { + if (DEBUG_VELOCITY) { + ALOGD("VelocityTracker: stopped for %0.3f ms, clearing state.", + (eventTime - mLastEventTime) * 0.000001f); + } // We have not received any movements for too long. Assume that all pointers // have stopped. mStrategy->clear(); @@ -284,18 +281,8 @@ void VelocityTracker::addMovement(const MotionEvent* event) { case AMOTION_EVENT_ACTION_MOVE: case AMOTION_EVENT_ACTION_HOVER_MOVE: break; - case AMOTION_EVENT_ACTION_POINTER_UP: - case AMOTION_EVENT_ACTION_UP: { - std::chrono::nanoseconds delaySinceLastEvent(event->getEventTime() - mLastEventTime); - if (delaySinceLastEvent > ASSUME_POINTER_STOPPED_TIME) { - ALOGD_IF(DEBUG_VELOCITY, - "VelocityTracker: stopped for %s, clearing state upon pointer liftoff.", - toString(delaySinceLastEvent).c_str()); - // We have not received any movements for too long. Assume that all pointers - // have stopped. - mStrategy->clear(); - } - // These actions because they do not convey any new information about + default: + // Ignore all other actions because they do not convey any new information about // pointer movement. We also want to preserve the last known velocity of the pointers. // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position // of the pointers that went up. ACTION_POINTER_UP does include the new position of @@ -305,10 +292,6 @@ void VelocityTracker::addMovement(const MotionEvent* event) { // before adding the movement. return; } - default: - // Ignore all other actions. - return; - } size_t pointerCount = event->getPointerCount(); if (pointerCount > MAX_POINTERS) { @@ -455,10 +438,10 @@ void LeastSquaresVelocityTrackerStrategy::addMovement( static bool solveLeastSquares(const std::vector& x, const std::vector& y, const std::vector& w, uint32_t n, float* outB, float* outDet) { const size_t m = x.size(); - - ALOGD_IF(DEBUG_STRATEGY, "solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n), - vectorToString(x).c_str(), vectorToString(y).c_str(), vectorToString(w).c_str()); - + if (DEBUG_STRATEGY) { + ALOGD("solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n), + vectorToString(x).c_str(), vectorToString(y).c_str(), vectorToString(w).c_str()); + } LOG_ALWAYS_FATAL_IF(m != y.size() || m != w.size(), "Mismatched vector sizes"); // Expand the X vector to a matrix A, pre-multiplied by the weights. @@ -469,9 +452,9 @@ static bool solveLeastSquares(const std::vector& x, const std::vector& x, const std::vector& x, const std::vector& x, const std::vector 0.000001f ? 1.0f - (sserr / sstot) : 1; - - ALOGD_IF(DEBUG_STRATEGY, " - sserr=%f", sserr); - ALOGD_IF(DEBUG_STRATEGY, " - sstot=%f", sstot); - ALOGD_IF(DEBUG_STRATEGY, " - det=%f", *outDet); - + if (DEBUG_STRATEGY) { + ALOGD(" - sserr=%f", sserr); + ALOGD(" - sstot=%f", sstot); + ALOGD(" - det=%f", *outDet); + } return true; } @@ -687,11 +673,11 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id, outEstimator->time = newestMovement.eventTime; outEstimator->degree = degree; outEstimator->confidence = xdet * ydet; - - ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f", - int(outEstimator->degree), vectorToString(outEstimator->xCoeff, n).c_str(), - vectorToString(outEstimator->yCoeff, n).c_str(), outEstimator->confidence); - + if (DEBUG_STRATEGY) { + ALOGD("estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f", + int(outEstimator->degree), vectorToString(outEstimator->xCoeff, n).c_str(), + vectorToString(outEstimator->yCoeff, n).c_str(), outEstimator->confidence); + } return true; } } @@ -1199,10 +1185,9 @@ bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, outEstimator->time = newestMovement.eventTime; outEstimator->degree = 2; // similar results to 2nd degree fit outEstimator->confidence = 1; - - ALOGD_IF(DEBUG_STRATEGY, "velocity: (%.1f, %.1f)", outEstimator->xCoeff[1], - outEstimator->yCoeff[1]); - + if (DEBUG_STRATEGY) { + ALOGD("velocity: (%.1f, %.1f)", outEstimator->xCoeff[1], outEstimator->yCoeff[1]); + } if (DEBUG_IMPULSE) { // TODO(b/134179997): delete this block once the switch to 'impulse' is complete. // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp index 4a445de3ac..a87b1873f0 100644 --- a/libs/input/tests/VelocityTracker_test.cpp +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -26,9 +26,7 @@ #include #include -using std::literals::chrono_literals::operator""ms; -using std::literals::chrono_literals::operator""ns; -using std::literals::chrono_literals::operator""us; +using namespace std::chrono_literals; using android::base::StringPrintf; namespace android { @@ -151,7 +149,8 @@ static std::vector createMotionEventStream( if (i == 0) { action = AMOTION_EVENT_ACTION_DOWN; EXPECT_EQ(1U, pointerCount) << "First event should only have 1 pointer"; - } else if ((i == motions.size() - 1) && pointerCount == 1) { + } else if (i == motions.size() - 1) { + EXPECT_EQ(1U, pointerCount) << "Last event should only have 1 pointer"; action = AMOTION_EVENT_ACTION_UP; } else { const MotionEventEntry& previousEntry = motions[i-1]; @@ -196,7 +195,7 @@ static std::vector createMotionEventStream( static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, const std::vector& motions, int32_t axis, - float targetVelocity, uint32_t pointerId = DEFAULT_POINTER_ID) { + float targetVelocity) { VelocityTracker vt(strategy); float Vx, Vy; @@ -205,7 +204,7 @@ static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, vt.addMovement(&event); } - vt.getVelocity(pointerId, &Vx, &Vy); + vt.getVelocity(DEFAULT_POINTER_ID, &Vx, &Vy); switch (axis) { case AMOTION_EVENT_AXIS_X: @@ -847,70 +846,12 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_ThreeFi // Velocity should actually be zero, but we expect 0.016 here instead. // This is close enough to zero, and is likely caused by division by a very small number. - computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0); - computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y, 0); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, -0.016); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y, -0.016); computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0); computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_Y, 0); } -/** - * ================= Pointer liftoff =============================================================== - */ - -/** - * The last movement of a pointer is always ACTION_POINTER_UP or ACTION_UP. If there's a short delay - * between the last ACTION_MOVE and the next ACTION_POINTER_UP or ACTION_UP, velocity should not be - * affected by the liftoff. - */ -TEST_F(VelocityTrackerTest, ShortDelayBeforeActionUp) { - std::vector motions = { - {0ms, {{10, 0}}}, {10ms, {{20, 0}}}, {20ms, {{30, 0}}}, {30ms, {{30, 0}}}, // ACTION_UP - }; - computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, - 1000); - computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 1000); -} - -/** - * The last movement of a single pointer is ACTION_UP. If there's a long delay between the last - * ACTION_MOVE and the final ACTION_UP, velocity should be reported as zero because the pointer - * should be assumed to have stopped. - */ -TEST_F(VelocityTrackerTest, LongDelayBeforeActionUp) { - std::vector motions = { - {0ms, {{10, 0}}}, - {10ms, {{20, 0}}}, - {20ms, {{30, 0}}}, - {3000ms, {{30, 0}}}, // ACTION_UP - }; - computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0); - computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0); -} - -/** - * The last movement of a pointer is always ACTION_POINTER_UP or ACTION_UP. If there's a long delay - * before ACTION_POINTER_UP event, the movement should be assumed to have stopped. - * The final velocity should be reported as zero for all pointers. - */ -TEST_F(VelocityTrackerTest, LongDelayBeforeActionPointerUp) { - std::vector motions = { - {0ms, {{10, 0}}}, - {10ms, {{20, 0}, {100, 0}}}, - {20ms, {{30, 0}, {200, 0}}}, - {30ms, {{30, 0}, {300, 0}}}, - {40ms, {{30, 0}, {400, 0}}}, - {3000ms, {{30, 0}}}, // ACTION_POINTER_UP - }; - computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0, - /*pointerId*/ 0); - computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0, - /*pointerId*/ 0); - computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0, - /*pointerId*/ 1); - computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0, - /*pointerId*/ 1); -} - /** * ================== Tests for least squares fitting ============================================== * -- GitLab From 9f26fc3993b5683fd6e34c069172973bf26721ab Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 17 Jun 2022 22:13:57 +0000 Subject: [PATCH 0095/1310] Check whether pointer has stopped at liftoff - try 2 This reverts commit 21fcc2b181c431210d0d976c7898058751c7d555. Reason for revert: fixed faulty test logic that the original CL exposed Copying the commit message from the previous attempt: Android uses ACTION_UP and ACTION_POINTER_UP events to signal that a pointer has left the screen. These events don't carry new information, and should always have the same coordinates as the last ACTION_MOVE event. However, these events do have a new timestamp. Before this CL: these events are getting ignored completely by VelocityTracker. If there's a large delay before ACTION_UP, the velocity is still reported to be the same as the if there's no delay. In this CL: we will check these events for timestamps. If too much time has passed, we will clear the strategy. Example logs: VelocityTracker: VelocityTracker: stopped for 2970.0 ms, clearing state upon pointer liftoff. Bug: 200900433 Change-Id: I588b616cab3afcc57f2c380ec55596cfef70a40c Test: atest libinput_tests Test: atest android.widget.cts.NumberPickerTest --- libs/input/VelocityTracker.cpp | 97 +++++++++++++---------- libs/input/tests/VelocityTracker_test.cpp | 73 +++++++++++++++-- 2 files changed, 122 insertions(+), 48 deletions(-) diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp index 7f427f2364..76aaf61da7 100644 --- a/libs/input/VelocityTracker.cpp +++ b/libs/input/VelocityTracker.cpp @@ -27,6 +27,8 @@ #include #include +using std::literals::chrono_literals::operator""ms; + namespace android { /** @@ -57,8 +59,14 @@ static const nsecs_t NANOS_PER_MS = 1000000; // Some input devices do not send ACTION_MOVE events in the case where a pointer has // stopped. We need to detect this case so that we can accurately predict the // velocity after the pointer starts moving again. -static const nsecs_t ASSUME_POINTER_STOPPED_TIME = 40 * NANOS_PER_MS; +static const std::chrono::duration ASSUME_POINTER_STOPPED_TIME = 40ms; +static std::string toString(std::chrono::nanoseconds t) { + std::stringstream stream; + stream.precision(1); + stream << std::fixed << std::chrono::duration(t).count() << " ms"; + return stream.str(); +} static float vectorDot(const float* a, const float* b, uint32_t m) { float r = 0; @@ -146,18 +154,14 @@ std::unique_ptr VelocityTracker::createStrategy( VelocityTracker::Strategy strategy) { switch (strategy) { case VelocityTracker::Strategy::IMPULSE: - if (DEBUG_STRATEGY) { - ALOGI("Initializing impulse strategy"); - } + ALOGI_IF(DEBUG_STRATEGY, "Initializing impulse strategy"); return std::make_unique(); case VelocityTracker::Strategy::LSQ1: return std::make_unique(1); case VelocityTracker::Strategy::LSQ2: - if (DEBUG_STRATEGY && !DEBUG_IMPULSE) { - ALOGI("Initializing lsq2 strategy"); - } + ALOGI_IF(DEBUG_STRATEGY && !DEBUG_IMPULSE, "Initializing lsq2 strategy"); return std::make_unique(2); case VelocityTracker::Strategy::LSQ3: @@ -221,12 +225,11 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, idBits.clearLastMarkedBit(); } - if ((mCurrentPointerIdBits.value & idBits.value) - && eventTime >= mLastEventTime + ASSUME_POINTER_STOPPED_TIME) { - if (DEBUG_VELOCITY) { - ALOGD("VelocityTracker: stopped for %0.3f ms, clearing state.", - (eventTime - mLastEventTime) * 0.000001f); - } + if ((mCurrentPointerIdBits.value & idBits.value) && + std::chrono::nanoseconds(eventTime - mLastEventTime) > ASSUME_POINTER_STOPPED_TIME) { + ALOGD_IF(DEBUG_VELOCITY, "VelocityTracker: stopped for %s, clearing state.", + toString(std::chrono::nanoseconds(eventTime - mLastEventTime)).c_str()); + // We have not received any movements for too long. Assume that all pointers // have stopped. mStrategy->clear(); @@ -281,8 +284,18 @@ void VelocityTracker::addMovement(const MotionEvent* event) { case AMOTION_EVENT_ACTION_MOVE: case AMOTION_EVENT_ACTION_HOVER_MOVE: break; - default: - // Ignore all other actions because they do not convey any new information about + case AMOTION_EVENT_ACTION_POINTER_UP: + case AMOTION_EVENT_ACTION_UP: { + std::chrono::nanoseconds delaySinceLastEvent(event->getEventTime() - mLastEventTime); + if (delaySinceLastEvent > ASSUME_POINTER_STOPPED_TIME) { + ALOGD_IF(DEBUG_VELOCITY, + "VelocityTracker: stopped for %s, clearing state upon pointer liftoff.", + toString(delaySinceLastEvent).c_str()); + // We have not received any movements for too long. Assume that all pointers + // have stopped. + mStrategy->clear(); + } + // These actions because they do not convey any new information about // pointer movement. We also want to preserve the last known velocity of the pointers. // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position // of the pointers that went up. ACTION_POINTER_UP does include the new position of @@ -292,6 +305,10 @@ void VelocityTracker::addMovement(const MotionEvent* event) { // before adding the movement. return; } + default: + // Ignore all other actions. + return; + } size_t pointerCount = event->getPointerCount(); if (pointerCount > MAX_POINTERS) { @@ -438,10 +455,10 @@ void LeastSquaresVelocityTrackerStrategy::addMovement( static bool solveLeastSquares(const std::vector& x, const std::vector& y, const std::vector& w, uint32_t n, float* outB, float* outDet) { const size_t m = x.size(); - if (DEBUG_STRATEGY) { - ALOGD("solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n), - vectorToString(x).c_str(), vectorToString(y).c_str(), vectorToString(w).c_str()); - } + + ALOGD_IF(DEBUG_STRATEGY, "solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n), + vectorToString(x).c_str(), vectorToString(y).c_str(), vectorToString(w).c_str()); + LOG_ALWAYS_FATAL_IF(m != y.size() || m != w.size(), "Mismatched vector sizes"); // Expand the X vector to a matrix A, pre-multiplied by the weights. @@ -452,9 +469,9 @@ static bool solveLeastSquares(const std::vector& x, const std::vector& x, const std::vector& x, const std::vector& x, const std::vector 0.000001f ? 1.0f - (sserr / sstot) : 1; - if (DEBUG_STRATEGY) { - ALOGD(" - sserr=%f", sserr); - ALOGD(" - sstot=%f", sstot); - ALOGD(" - det=%f", *outDet); - } + + ALOGD_IF(DEBUG_STRATEGY, " - sserr=%f", sserr); + ALOGD_IF(DEBUG_STRATEGY, " - sstot=%f", sstot); + ALOGD_IF(DEBUG_STRATEGY, " - det=%f", *outDet); + return true; } @@ -673,11 +687,11 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id, outEstimator->time = newestMovement.eventTime; outEstimator->degree = degree; outEstimator->confidence = xdet * ydet; - if (DEBUG_STRATEGY) { - ALOGD("estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f", - int(outEstimator->degree), vectorToString(outEstimator->xCoeff, n).c_str(), - vectorToString(outEstimator->yCoeff, n).c_str(), outEstimator->confidence); - } + + ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f", + int(outEstimator->degree), vectorToString(outEstimator->xCoeff, n).c_str(), + vectorToString(outEstimator->yCoeff, n).c_str(), outEstimator->confidence); + return true; } } @@ -1185,9 +1199,10 @@ bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, outEstimator->time = newestMovement.eventTime; outEstimator->degree = 2; // similar results to 2nd degree fit outEstimator->confidence = 1; - if (DEBUG_STRATEGY) { - ALOGD("velocity: (%.1f, %.1f)", outEstimator->xCoeff[1], outEstimator->yCoeff[1]); - } + + ALOGD_IF(DEBUG_STRATEGY, "velocity: (%.1f, %.1f)", outEstimator->xCoeff[1], + outEstimator->yCoeff[1]); + if (DEBUG_IMPULSE) { // TODO(b/134179997): delete this block once the switch to 'impulse' is complete. // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp index a87b1873f0..4a445de3ac 100644 --- a/libs/input/tests/VelocityTracker_test.cpp +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -26,7 +26,9 @@ #include #include -using namespace std::chrono_literals; +using std::literals::chrono_literals::operator""ms; +using std::literals::chrono_literals::operator""ns; +using std::literals::chrono_literals::operator""us; using android::base::StringPrintf; namespace android { @@ -149,8 +151,7 @@ static std::vector createMotionEventStream( if (i == 0) { action = AMOTION_EVENT_ACTION_DOWN; EXPECT_EQ(1U, pointerCount) << "First event should only have 1 pointer"; - } else if (i == motions.size() - 1) { - EXPECT_EQ(1U, pointerCount) << "Last event should only have 1 pointer"; + } else if ((i == motions.size() - 1) && pointerCount == 1) { action = AMOTION_EVENT_ACTION_UP; } else { const MotionEventEntry& previousEntry = motions[i-1]; @@ -195,7 +196,7 @@ static std::vector createMotionEventStream( static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, const std::vector& motions, int32_t axis, - float targetVelocity) { + float targetVelocity, uint32_t pointerId = DEFAULT_POINTER_ID) { VelocityTracker vt(strategy); float Vx, Vy; @@ -204,7 +205,7 @@ static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, vt.addMovement(&event); } - vt.getVelocity(DEFAULT_POINTER_ID, &Vx, &Vy); + vt.getVelocity(pointerId, &Vx, &Vy); switch (axis) { case AMOTION_EVENT_AXIS_X: @@ -846,12 +847,70 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_ThreeFi // Velocity should actually be zero, but we expect 0.016 here instead. // This is close enough to zero, and is likely caused by division by a very small number. - computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, -0.016); - computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y, -0.016); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y, 0); computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0); computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_Y, 0); } +/** + * ================= Pointer liftoff =============================================================== + */ + +/** + * The last movement of a pointer is always ACTION_POINTER_UP or ACTION_UP. If there's a short delay + * between the last ACTION_MOVE and the next ACTION_POINTER_UP or ACTION_UP, velocity should not be + * affected by the liftoff. + */ +TEST_F(VelocityTrackerTest, ShortDelayBeforeActionUp) { + std::vector motions = { + {0ms, {{10, 0}}}, {10ms, {{20, 0}}}, {20ms, {{30, 0}}}, {30ms, {{30, 0}}}, // ACTION_UP + }; + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, + 1000); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 1000); +} + +/** + * The last movement of a single pointer is ACTION_UP. If there's a long delay between the last + * ACTION_MOVE and the final ACTION_UP, velocity should be reported as zero because the pointer + * should be assumed to have stopped. + */ +TEST_F(VelocityTrackerTest, LongDelayBeforeActionUp) { + std::vector motions = { + {0ms, {{10, 0}}}, + {10ms, {{20, 0}}}, + {20ms, {{30, 0}}}, + {3000ms, {{30, 0}}}, // ACTION_UP + }; + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0); +} + +/** + * The last movement of a pointer is always ACTION_POINTER_UP or ACTION_UP. If there's a long delay + * before ACTION_POINTER_UP event, the movement should be assumed to have stopped. + * The final velocity should be reported as zero for all pointers. + */ +TEST_F(VelocityTrackerTest, LongDelayBeforeActionPointerUp) { + std::vector motions = { + {0ms, {{10, 0}}}, + {10ms, {{20, 0}, {100, 0}}}, + {20ms, {{30, 0}, {200, 0}}}, + {30ms, {{30, 0}, {300, 0}}}, + {40ms, {{30, 0}, {400, 0}}}, + {3000ms, {{30, 0}}}, // ACTION_POINTER_UP + }; + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0, + /*pointerId*/ 0); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0, + /*pointerId*/ 0); + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0, + /*pointerId*/ 1); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0, + /*pointerId*/ 1); +} + /** * ================== Tests for least squares fitting ============================================== * -- GitLab From 6abcf8ffee398175a2c0f3bba06723881e387aad Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Mon, 6 Jun 2022 10:08:05 +0000 Subject: [PATCH 0096/1310] Call pilfer pointers when second pointer goes down during D&D We added new functionality to pilferPointers API in ag/18234604. Using it we can extend the functionality of drag and drop to allow user to interact with the UI while using 1 pointer/finger to drag. Pilfering pointers allows gesture monitors to restart gesture detection with new pointers that down. Avoid pilfering right after transferTouch on D&D to prevent security bug: Apps can misuse startDragAndDrop() API to keep pilfering pointers for gesture monitors and user can get stuck in an app. Instead we will pilfer when a second pointer goes down indicating user want to interact while using D&D. DD: go/global_drag_and_drop Bug: 220109830 Test: atest inputflinger_tests Change-Id: I956f040ff307021bb2f74a06228c6addb90e2168 --- .../dispatcher/InputDispatcher.cpp | 10 +++++ .../inputflinger/dispatcher/InputDispatcher.h | 2 + .../tests/InputDispatcher_test.cpp | 37 ++++++++++++++++++- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 0b3b77045a..5e92f0d8da 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -1657,6 +1657,13 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr< InputEventInjectionResult injectionResult; if (isPointerEvent) { // Pointer event. (eg. touchscreen) + + if (mDragState && + (entry->action & AMOTION_EVENT_ACTION_MASK) == AMOTION_EVENT_ACTION_POINTER_DOWN) { + // If drag and drop ongoing and pointer down occur: pilfer drag window pointers + pilferPointersLocked(mDragState->dragWindow->getToken()); + } + injectionResult = findTouchedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime, &conflictingPointerActions); @@ -5575,7 +5582,10 @@ void InputDispatcher::removeMonitorChannelLocked(const sp& connectionTo status_t InputDispatcher::pilferPointers(const sp& token) { std::scoped_lock _l(mLock); + return pilferPointersLocked(token); +} +status_t InputDispatcher::pilferPointersLocked(const sp& token) { const std::shared_ptr requestingChannel = getInputChannelLocked(token); if (!requestingChannel) { ALOGW("Attempted to pilfer pointers from an un-registered channel or invalid token"); diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 44a6dc3cd8..50124a6606 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -255,6 +255,8 @@ private: void removeConnectionLocked(const sp& connection) REQUIRES(mLock); + status_t pilferPointersLocked(const sp& token) REQUIRES(mLock); + template struct StrongPointerHash { std::size_t operator()(const sp& b) const { return std::hash{}(b.get()); } diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 0c0f9f8a56..8af7cc3a44 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -6094,6 +6094,7 @@ protected: sp mWindow; sp mSecondWindow; sp mDragWindow; + sp mSpyWindow; void SetUp() override { InputDispatcherTest::SetUp(); @@ -6104,8 +6105,13 @@ protected: mSecondWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT); mSecondWindow->setFrame(Rect(100, 0, 200, 100)); + mSpyWindow = new FakeWindowHandle(mApp, mDispatcher, "SpyWindow", ADISPLAY_ID_DEFAULT); + mSpyWindow->setSpy(true); + mSpyWindow->setTrustedOverlay(true); + mSpyWindow->setFrame(Rect(0, 0, 200, 100)); + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp); - mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mSecondWindow}}}); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mSpyWindow, mWindow, mSecondWindow}}}); } void injectDown() { @@ -6116,6 +6122,8 @@ protected: // Window should receive motion event. mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); + // Spy window should also receive motion event + mSpyWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); } // Start performing drag, we will create a drag window and transfer touch to it. @@ -6128,8 +6136,9 @@ protected: // The drag window covers the entire display mDragWindow = new FakeWindowHandle(mApp, mDispatcher, "DragWindow", ADISPLAY_ID_DEFAULT); + mDragWindow->setTouchableRegion(Region{{0, 0, 0, 0}}); mDispatcher->setInputWindows( - {{ADISPLAY_ID_DEFAULT, {mDragWindow, mWindow, mSecondWindow}}}); + {{ADISPLAY_ID_DEFAULT, {mDragWindow, mSpyWindow, mWindow, mSecondWindow}}}); // Transfer touch focus to the drag window bool transferred = @@ -6207,6 +6216,30 @@ TEST_F(InputDispatcherDragTests, DragEnterAndDragExit) { mSecondWindow->assertNoEvents(); } +TEST_F(InputDispatcherDragTests, DragEnterAndPointerDownPilfersPointers) { + performDrag(); + + // No cancel event after drag start + mSpyWindow->assertNoEvents(); + + const MotionEvent secondFingerDownEvent = + MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) + .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(60).y(60)) + .build(); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, + InputEventInjectionSync::WAIT_FOR_RESULT)) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + + // Receives cancel for first pointer after next pointer down + mSpyWindow->consumeMotionCancel(); + mSpyWindow->consumeMotionDown(); + + mSpyWindow->assertNoEvents(); +} + TEST_F(InputDispatcherDragTests, DragAndDrop) { performDrag(); -- GitLab From 825df7f64206678c4346d48e8f77aa9adecc5866 Mon Sep 17 00:00:00 2001 From: Pablo Gamito Date: Thu, 23 Jun 2022 14:45:44 +0000 Subject: [PATCH 0097/1310] Parcel transaction id between processes Transaction ids were no being parceled which meant we were getting different ids for the same transaction passed across process boundaries This was problematic for FaaS which dumped the transaction id in one process but the transaction was applied in another process with a different id meaning they couldn't be associated. Bug: 230462538 Test: make sure transaction id is still the same after calling shell.Transitions#onTransitionReady (WM Shell process) from wm.Transition#onTransactionReady (WM process). Change-Id: I66191310b87e784df62259de61b98ea0f9e33345 --- libs/gui/SurfaceComposerClient.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 9bc159d3b9..7191de8f7f 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -660,6 +660,7 @@ SurfaceComposerClient::Transaction::createFromParcel(const Parcel* parcel) { status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel) { + const uint64_t transactionId = parcel->readUint64(); const uint32_t forceSynchronous = parcel->readUint32(); const uint32_t transactionNestCount = parcel->readUint32(); const bool animation = parcel->readBool(); @@ -737,6 +738,7 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel inputWindowCommands.read(*parcel); // Parsing was successful. Update the object. + mId = transactionId; mForceSynchronous = forceSynchronous; mTransactionNestCount = transactionNestCount; mAnimation = animation; @@ -768,6 +770,7 @@ status_t SurfaceComposerClient::Transaction::writeToParcel(Parcel* parcel) const const_cast(this)->cacheBuffers(); + parcel->writeUint64(mId); parcel->writeUint32(mForceSynchronous); parcel->writeUint32(mTransactionNestCount); parcel->writeBool(mAnimation); -- GitLab From 722c8560815b53feada7fc0f553b53119c6e7992 Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Thu, 9 Jun 2022 21:54:06 -0700 Subject: [PATCH 0098/1310] Fix the issue that renderEngine bails out too early. - Remove the check about empty layer, because bailing out too early will cause dirty buffers to be re-used before they get cleared. - Test locally but hold off adding corresponding test until ANGLE side fixes the issue of clearing a new created buffer. Bug: 233052879 Test: librenderengine_test, and test locally with new test case Change-Id: Ie1a04b1ce6b691f99555f60d680846c8ab4f1f97 --- libs/renderengine/skia/SkiaGLRenderEngine.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index c9f9ec362a..5187d7b7f7 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -779,15 +779,9 @@ void SkiaGLRenderEngine::drawLayersInternal( const DisplaySettings& display, const std::vector& layers, const std::shared_ptr& buffer, const bool /*useFramebufferCache*/, base::unique_fd&& bufferFence) { - ATRACE_NAME("SkiaGL::drawLayers"); + ATRACE_NAME("SkiaGL::drawLayersInternal"); std::lock_guard lock(mRenderingMutex); - if (layers.empty()) { - ALOGV("Drawing empty layer stack"); - resultPromise->set_value({NO_ERROR, base::unique_fd()}); - return; - } - if (buffer == nullptr) { ALOGE("No output buffer provided. Aborting GPU composition."); resultPromise->set_value({BAD_VALUE, base::unique_fd()}); -- GitLab From 9dc9cfbab4b4f7351cdfbeb45eec99aaab975d3d Mon Sep 17 00:00:00 2001 From: Jooyung Han Date: Fri, 24 Jun 2022 14:56:24 +0900 Subject: [PATCH 0099/1310] Set min_sdk_version This was done via the hard-coded list in build/soong. Bug: 158059172 Test: m Change-Id: I994c20b87ccdcd9b653bd38e0c7e147ba8ebcc06 --- cmds/lshal/libprocpartition/Android.bp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmds/lshal/libprocpartition/Android.bp b/cmds/lshal/libprocpartition/Android.bp index cbfbdc9223..af85666276 100644 --- a/cmds/lshal/libprocpartition/Android.bp +++ b/cmds/lshal/libprocpartition/Android.bp @@ -35,5 +35,6 @@ cc_library_static { ], export_include_dirs: [ "include", - ] + ], + min_sdk_version: "30", } -- GitLab From 60656e32d1cf7ffb579cbc4257b78fdac61136c1 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 24 Jun 2022 18:19:55 +0000 Subject: [PATCH 0100/1310] Apply pointer capture changes only in mode POINTER_RELATIVE Pointer Capture should not effect devices in NAVIGATION mode like trackballs. This CL also migrates the Pointer Mode enum to an enum class. Bug: 233184154 Test: presubmit Change-Id: Ic7a74588d44bd54086bd544c77880bd6037e5985 --- .../reader/mapper/CursorInputMapper.cpp | 51 +++++++------------ .../reader/mapper/CursorInputMapper.h | 15 ++++-- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index a9a4c71c02..bea0ecab8f 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -76,7 +76,7 @@ uint32_t CursorInputMapper::getSources() const { void CursorInputMapper::populateDeviceInfo(InputDeviceInfo* info) { InputMapper::populateDeviceInfo(info); - if (mParameters.mode == Parameters::MODE_POINTER) { + if (mParameters.mode == Parameters::Mode::POINTER) { float minX, minY, maxX, maxY; if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) { info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, minX, maxX, 0.0f, 0.0f, 0.0f); @@ -131,12 +131,12 @@ void CursorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* // Configure device mode. switch (mParameters.mode) { - case Parameters::MODE_POINTER_RELATIVE: + case Parameters::Mode::POINTER_RELATIVE: // Should not happen during first time configuration. ALOGE("Cannot start a device in MODE_POINTER_RELATIVE, starting in MODE_POINTER"); - mParameters.mode = Parameters::MODE_POINTER; + mParameters.mode = Parameters::Mode::POINTER; [[fallthrough]]; - case Parameters::MODE_POINTER: + case Parameters::Mode::POINTER: mSource = AINPUT_SOURCE_MOUSE; mXPrecision = 1.0f; mYPrecision = 1.0f; @@ -144,7 +144,7 @@ void CursorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* mYScale = 1.0f; mPointerController = getContext()->getPointerController(getDeviceId()); break; - case Parameters::MODE_NAVIGATION: + case Parameters::Mode::NAVIGATION: mSource = AINPUT_SOURCE_TRACKBALL; mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD; mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD; @@ -157,12 +157,13 @@ void CursorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* mHWheelScale = 1.0f; } - const bool configurePointerCapture = (!changes && config->pointerCaptureRequest.enable) || - (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE); + const bool configurePointerCapture = mParameters.mode != Parameters::Mode::NAVIGATION && + ((!changes && config->pointerCaptureRequest.enable) || + (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE)); if (configurePointerCapture) { if (config->pointerCaptureRequest.enable) { - if (mParameters.mode == Parameters::MODE_POINTER) { - mParameters.mode = Parameters::MODE_POINTER_RELATIVE; + if (mParameters.mode == Parameters::Mode::POINTER) { + mParameters.mode = Parameters::Mode::POINTER_RELATIVE; mSource = AINPUT_SOURCE_MOUSE_RELATIVE; // Keep PointerController around in order to preserve the pointer position. mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); @@ -170,8 +171,8 @@ void CursorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* ALOGE("Cannot request pointer capture, device is not in MODE_POINTER"); } } else { - if (mParameters.mode == Parameters::MODE_POINTER_RELATIVE) { - mParameters.mode = Parameters::MODE_POINTER; + if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) { + mParameters.mode = Parameters::Mode::POINTER; mSource = AINPUT_SOURCE_MOUSE; } else { ALOGE("Cannot release pointer capture, device is not in MODE_POINTER_RELATIVE"); @@ -186,8 +187,8 @@ void CursorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED) || configurePointerCapture) { - if (config->pointerCaptureRequest.enable) { - // Disable any acceleration or scaling when Pointer Capture is enabled. + if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) { + // Disable any acceleration or scaling for the pointer when Pointer Capture is enabled. mPointerVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); mWheelXVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); mWheelYVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); @@ -221,12 +222,12 @@ void CursorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* } void CursorInputMapper::configureParameters() { - mParameters.mode = Parameters::MODE_POINTER; + mParameters.mode = Parameters::Mode::POINTER; String8 cursorModeString; if (getDeviceContext().getConfiguration().tryGetProperty(String8("cursor.mode"), cursorModeString)) { if (cursorModeString == "navigation") { - mParameters.mode = Parameters::MODE_NAVIGATION; + mParameters.mode = Parameters::Mode::NAVIGATION; } else if (cursorModeString != "pointer" && cursorModeString != "default") { ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString.string()); } @@ -237,7 +238,7 @@ void CursorInputMapper::configureParameters() { mParameters.orientationAware); mParameters.hasAssociatedDisplay = false; - if (mParameters.mode == Parameters::MODE_POINTER || mParameters.orientationAware) { + if (mParameters.mode == Parameters::Mode::POINTER || mParameters.orientationAware) { mParameters.hasAssociatedDisplay = true; } } @@ -246,21 +247,7 @@ void CursorInputMapper::dumpParameters(std::string& dump) { dump += INDENT3 "Parameters:\n"; dump += StringPrintf(INDENT4 "HasAssociatedDisplay: %s\n", toString(mParameters.hasAssociatedDisplay)); - - switch (mParameters.mode) { - case Parameters::MODE_POINTER: - dump += INDENT4 "Mode: pointer\n"; - break; - case Parameters::MODE_POINTER_RELATIVE: - dump += INDENT4 "Mode: relative pointer\n"; - break; - case Parameters::MODE_NAVIGATION: - dump += INDENT4 "Mode: navigation\n"; - break; - default: - ALOG_ASSERT(false); - } - + dump += StringPrintf(INDENT4 "Mode: %s\n", ftl::enum_string(mParameters.mode).c_str()); dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware)); } @@ -486,7 +473,7 @@ int32_t CursorInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCod std::optional CursorInputMapper::getAssociatedDisplayId() { if (mParameters.hasAssociatedDisplay) { - if (mParameters.mode == Parameters::MODE_POINTER) { + if (mParameters.mode == Parameters::Mode::POINTER) { return std::make_optional(mPointerController->getDisplayId()); } else { // If the device is orientationAware and not a mouse, diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index c84c6c4229..75aeffb846 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -74,10 +74,17 @@ private: // Immutable configuration parameters. struct Parameters { - enum Mode { - MODE_POINTER, - MODE_POINTER_RELATIVE, - MODE_NAVIGATION, + enum class Mode { + // In POINTER mode, the device is a mouse that controls the mouse cursor on the screen, + // reporting absolute screen locations using SOURCE_MOUSE. + POINTER, + // A mouse device in POINTER mode switches to the POINTER_RELATIVE mode when Pointer + // Capture is enabled, and reports relative values only using SOURCE_MOUSE_RELATIVE. + POINTER_RELATIVE, + // A device in NAVIGATION mode emits relative values using SOURCE_TRACKBALL. + NAVIGATION, + + ftl_last = NAVIGATION, }; Mode mode; -- GitLab From 208360bba3cae0953026dcf62809ddd3bc49ec9b Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 24 Jun 2022 18:37:04 +0000 Subject: [PATCH 0101/1310] Do not transform values from a SOURCE_MOUSE_RELATIVE device In Pointer Capture mode, we want the API to report events directly as they are coming from the kernel without any transformations. We already do this for SORUCE_TOUCHPAD. Here, we update the handling of SOURCE_MOUSE_RELATIVE so that when Pointer Capture is enabled, mouse events are not effected by the display or window state. Bug: 233184154 Test: Apply non-standard scaling to a device using "adb shell wm size" and run: atest MicrosoftSculpttouchTest Test: atest libinput_tests Test: atest inputflinger_tests Change-Id: Ia6ef5a21312da949ac2704e9bfe4638626d3a894 --- libs/input/Input.cpp | 5 ++- libs/input/tests/InputEvent_test.cpp | 10 ++--- .../reader/mapper/CursorInputMapper.cpp | 8 ++-- .../inputflinger/tests/InputReader_test.cpp | 42 +++++++++++++++++++ 4 files changed, 55 insertions(+), 10 deletions(-) diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index 13ca9ecd35..375b68433d 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -64,9 +64,10 @@ float transformAngle(const ui::Transform& transform, float angleRadians) { } bool shouldDisregardTransformation(uint32_t source) { - // Do not apply any transformations to axes from joysticks or touchpads. + // Do not apply any transformations to axes from joysticks, touchpads, or relative mice. return isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK) || - isFromSource(source, AINPUT_SOURCE_CLASS_POSITION); + isFromSource(source, AINPUT_SOURCE_CLASS_POSITION) || + isFromSource(source, AINPUT_SOURCE_MOUSE_RELATIVE); } bool shouldDisregardOffset(uint32_t source) { diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp index a92016ba3b..4b3124636b 100644 --- a/libs/input/tests/InputEvent_test.cpp +++ b/libs/input/tests/InputEvent_test.cpp @@ -715,10 +715,10 @@ TEST_F(MotionEventTest, ApplyTransform) { } TEST_F(MotionEventTest, JoystickAndTouchpadAreNotTransformed) { - constexpr static std::array kNonTransformedSources = {std::pair(AINPUT_SOURCE_TOUCHPAD, - AMOTION_EVENT_ACTION_DOWN), - std::pair(AINPUT_SOURCE_JOYSTICK, - AMOTION_EVENT_ACTION_MOVE)}; + constexpr static std::array kNonTransformedSources = + {std::pair(AINPUT_SOURCE_TOUCHPAD, AMOTION_EVENT_ACTION_DOWN), + std::pair(AINPUT_SOURCE_JOYSTICK, AMOTION_EVENT_ACTION_MOVE), + std::pair(AINPUT_SOURCE_MOUSE_RELATIVE, AMOTION_EVENT_ACTION_MOVE)}; // Create a rotate-90 transform with an offset (like a window which isn't fullscreen). ui::Transform transform(ui::Transform::ROT_90, 800, 400); transform.set(transform.tx() + 20, transform.ty() + 40); @@ -738,7 +738,7 @@ TEST_F(MotionEventTest, JoystickAndTouchpadAreNotTransformed) { TEST_F(MotionEventTest, NonPointerSourcesAreNotTranslated) { constexpr static std::array kNonPointerSources = {std::pair(AINPUT_SOURCE_TRACKBALL, AMOTION_EVENT_ACTION_DOWN), - std::pair(AINPUT_SOURCE_MOUSE_RELATIVE, + std::pair(AINPUT_SOURCE_TOUCH_NAVIGATION, AMOTION_EVENT_ACTION_MOVE)}; // Create a rotate-90 transform with an offset (like a window which isn't fullscreen). ui::Transform transform(ui::Transform::ROT_90, 800, 400); diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index bea0ecab8f..91dc61923b 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -199,7 +199,8 @@ void CursorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* } } - if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { + if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO) || + configurePointerCapture) { mOrientation = DISPLAY_ORIENTATION_0; const bool isOrientedDevice = (mParameters.orientationAware && mParameters.hasAssociatedDisplay); @@ -208,8 +209,9 @@ void CursorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* // anything if the device is already orientation-aware. If the device is not // orientation-aware, then we need to apply the inverse rotation of the display so that // when the display rotation is applied later as a part of the per-window transform, we - // get the expected screen coordinates. - if (!isOrientedDevice) { + // get the expected screen coordinates. When pointer capture is enabled, we do not apply any + // rotations and report values directly from the input device. + if (!isOrientedDevice && mParameters.mode != Parameters::Mode::POINTER_RELATIVE) { std::optional internalViewport = config->getDisplayViewportByType(ViewportType::INTERNAL); if (internalViewport) { diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 13801125d2..caf917ebb8 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -4971,6 +4971,48 @@ TEST_F(CursorInputMapperTest, PointerCaptureDisablesVelocityProcessing) { ASSERT_EQ(20, args.pointerCoords[0].getY()); } +TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = addMapperAndConfigure(); + + NotifyDeviceResetArgs resetArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime); + ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); + + // Ensure the display is rotated. + prepareDisplay(DISPLAY_ORIENTATION_90); + + NotifyMotionArgs args; + + // Verify that the coordinates are rotated. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); + ASSERT_EQ(-20, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X)); + ASSERT_EQ(10, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y)); + + // Enable Pointer Capture. + mFakePolicy->setPointerCapture(true); + configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); + NotifyPointerCaptureChangedArgs captureArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs)); + ASSERT_TRUE(captureArgs.request.enable); + + // Move and verify rotation is not applied. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + ASSERT_EQ(10, args.pointerCoords[0].getX()); + ASSERT_EQ(20, args.pointerCoords[0].getY()); +} + TEST_F(CursorInputMapperTest, Process_ShouldHandleDisplayId) { CursorInputMapper& mapper = addMapperAndConfigure(); -- GitLab From 952e65b2aa60ddfa9ee3e5640a8f9f32be125874 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 23 Jun 2022 17:49:55 +0000 Subject: [PATCH 0102/1310] EventHub: Clean up reading from inotify Refactor reading from inotify to be slightly more readable. Bug: None Test: manual: flash device, connect/disconnect BT input device, observe logs Change-Id: If4a15a500798d3646b41fdad90b2571e90c80c77 --- services/inputflinger/reader/EventHub.cpp | 91 ++++++++++--------- .../inputflinger/reader/include/EventHub.h | 5 +- 2 files changed, 53 insertions(+), 43 deletions(-) diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index 6a8ed49db8..fe4d443aac 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -687,6 +687,7 @@ EventHub::EventHub(void) LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno)); mINotifyFd = inotify_init1(IN_CLOEXEC); + LOG_ALWAYS_FATAL_IF(mINotifyFd < 0, "Could not create inotify instance: %s", strerror(errno)); std::error_code errorCode; bool isDeviceInotifyAdded = false; @@ -1723,7 +1724,10 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz // before closing the devices. if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) { mPendingINotify = false; - readNotifyLocked(); + const auto res = readNotifyLocked(); + if (!res.ok()) { + ALOGW("Failed to read from inotify: %s", res.error().message().c_str()); + } deviceChanged = true; } @@ -2413,53 +2417,56 @@ void EventHub::closeDeviceLocked(Device& device) { mDevices.erase(device.id); } -status_t EventHub::readNotifyLocked() { - int res; - char event_buf[512]; - int event_size; - int event_pos = 0; - struct inotify_event* event; +base::Result EventHub::readNotifyLocked() { + static constexpr auto EVENT_SIZE = static_cast(sizeof(inotify_event)); + uint8_t eventBuffer[512]; + ssize_t sizeRead; ALOGV("EventHub::readNotify nfd: %d\n", mINotifyFd); - res = read(mINotifyFd, event_buf, sizeof(event_buf)); - if (res < (int)sizeof(*event)) { - if (errno == EINTR) return 0; - ALOGW("could not get event, %s\n", strerror(errno)); - return -1; - } - - while (res >= (int)sizeof(*event)) { - event = (struct inotify_event*)(event_buf + event_pos); - if (event->len) { - if (event->wd == mDeviceInputWd) { - std::string filename = std::string(DEVICE_INPUT_PATH) + "/" + event->name; - if (event->mask & IN_CREATE) { - openDeviceLocked(filename); - } else { - ALOGI("Removing device '%s' due to inotify event\n", filename.c_str()); - closeDeviceByPathLocked(filename); - } - } else if (event->wd == mDeviceWd) { - if (isV4lTouchNode(event->name)) { - std::string filename = std::string(DEVICE_PATH) + "/" + event->name; - if (event->mask & IN_CREATE) { - openVideoDeviceLocked(filename); - } else { - ALOGI("Removing video device '%s' due to inotify event", filename.c_str()); - closeVideoDeviceByPathLocked(filename); - } - } else if (strcmp(event->name, "input") == 0 && event->mask & IN_CREATE) { - addDeviceInputInotify(); - } + do { + sizeRead = read(mINotifyFd, eventBuffer, sizeof(eventBuffer)); + } while (sizeRead < 0 && errno == EINTR); + + if (sizeRead < EVENT_SIZE) return Errorf("could not get event, %s", strerror(errno)); + + for (ssize_t eventPos = 0; sizeRead >= EVENT_SIZE;) { + const inotify_event* event; + event = (const inotify_event*)(eventBuffer + eventPos); + if (event->len == 0) continue; + + handleNotifyEventLocked(*event); + + const ssize_t eventSize = EVENT_SIZE + event->len; + sizeRead -= eventSize; + eventPos += eventSize; + } + return {}; +} + +void EventHub::handleNotifyEventLocked(const inotify_event& event) { + if (event.wd == mDeviceInputWd) { + std::string filename = std::string(DEVICE_INPUT_PATH) + "/" + event.name; + if (event.mask & IN_CREATE) { + openDeviceLocked(filename); + } else { + ALOGI("Removing device '%s' due to inotify event\n", filename.c_str()); + closeDeviceByPathLocked(filename); + } + } else if (event.wd == mDeviceWd) { + if (isV4lTouchNode(event.name)) { + std::string filename = std::string(DEVICE_PATH) + "/" + event.name; + if (event.mask & IN_CREATE) { + openVideoDeviceLocked(filename); } else { - LOG_ALWAYS_FATAL("Unexpected inotify event, wd = %i", event->wd); + ALOGI("Removing video device '%s' due to inotify event", filename.c_str()); + closeVideoDeviceByPathLocked(filename); } + } else if (strcmp(event.name, "input") == 0 && event.mask & IN_CREATE) { + addDeviceInputInotify(); } - event_size = sizeof(*event) + event->len; - res -= event_size; - event_pos += event_size; + } else { + LOG_ALWAYS_FATAL("Unexpected inotify event, wd = %i", event.wd); } - return 0; } status_t EventHub::scanDirLocked(const std::string& dirname) { diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index 5453ebbc9c..b2d50fc6fb 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -44,6 +44,8 @@ #include "TouchVideoDevice.h" #include "VibrationElement.h" +struct inotify_event; + namespace android { /* Number of colors : {red, green, blue} */ @@ -648,7 +650,8 @@ private: status_t scanDirLocked(const std::string& dirname) REQUIRES(mLock); status_t scanVideoDirLocked(const std::string& dirname) REQUIRES(mLock); void scanDevicesLocked() REQUIRES(mLock); - status_t readNotifyLocked() REQUIRES(mLock); + base::Result readNotifyLocked() REQUIRES(mLock); + void handleNotifyEventLocked(const inotify_event&) REQUIRES(mLock); Device* getDeviceByDescriptorLocked(const std::string& descriptor) const REQUIRES(mLock); Device* getDeviceLocked(int32_t deviceId) const REQUIRES(mLock); -- GitLab From 1fed86ea9ea16a2d88f1a6a285fbab91143bcc6d Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Thu, 23 Jun 2022 15:33:52 -0700 Subject: [PATCH 0103/1310] Add test to validate that renderengine doesn't bail out early. - Make sure that renderengine doesn't bail out early that causes dirty buffer to re-use by next draw. Bug: 233052879 Test: this Change-Id: Id606928df6d35c5e31b1982f6f95d68c7bba3027 --- libs/renderengine/tests/RenderEngineTest.cpp | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index 61af698d01..3340857472 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -1606,6 +1606,36 @@ TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) { drawEmptyLayers(); } +TEST_P(RenderEngineTest, drawLayers_fillRedBufferAndEmptyBuffer) { + const auto& renderEngineFactory = GetParam(); + if (renderEngineFactory->type() == renderengine::RenderEngine::RenderEngineType::GLES) { + // GLES-specific test + return; + } + + initializeRenderEngine(); + renderengine::DisplaySettings settings; + settings.physicalDisplay = fullscreenRect(); + settings.clip = fullscreenRect(); + settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR; + + // add a red layer + renderengine::LayerSettings layerOne{ + .geometry.boundaries = fullscreenRect().toFloatRect(), + .source.solidColor = half3(1.0f, 0.0f, 0.0f), + .alpha = 1.f, + }; + + std::vector layersFirst{layerOne}; + invokeDraw(settings, layersFirst); + expectBufferColor(fullscreenRect(), 255, 0, 0, 255); + + // re-draw with an empty layer above it, and we get a transparent black one + std::vector layersSecond; + invokeDraw(settings, layersSecond); + expectBufferColor(fullscreenRect(), 0, 0, 0, 0); +} + TEST_P(RenderEngineTest, drawLayers_withoutBuffers_withColorTransform) { initializeRenderEngine(); -- GitLab From f0bd62e7d34a63b21500112d98b7930f8c383896 Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Wed, 16 Jun 2021 15:27:05 -0700 Subject: [PATCH 0104/1310] surfaceflinger: repaint on DOZE_SUSPEND exit This will force SF push out one more frame when transitioning out of DOZE_SUSPEND. This will allow display to update with the latest frame generated by the AP if MCU has drawn something on screen. Bug: 191264924 Bug: 189731001 Change-Id: I491134973c00e98bf1c9f5af2ec37d4e0e5e547b (cherry picked from commit 45ebe726419d344d5cf4abc83d71f1847b60e86a) --- services/surfaceflinger/SurfaceFlinger.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index aba7998b3c..c4d6b0ca3a 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4920,6 +4920,9 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: // Update display while dozing getHwComposer().setPowerMode(displayId, mode); if (isDisplayActiveLocked(display) && currentMode == hal::PowerMode::DOZE_SUSPEND) { + ALOGI("Force repainting for DOZE_SUSPEND -> DOZE or ON."); + mVisibleRegionsDirty = true; + scheduleRepaint(); mScheduler->onScreenAcquired(mAppConnectionHandle); mScheduler->resyncToHardwareVsync(true, refreshRate); } -- GitLab From 728dba1f95927d8f7acb52d09c65c486f81ea50d Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 29 Jun 2022 22:13:49 +0000 Subject: [PATCH 0105/1310] BBQ: Remove BufferFreedListener If BBQ is destroyed without calling abandon, its BufferItemConsumer will call onBufferFreed. Since BBQ registers itself as the BufferFreedListener we end up trying to promote and destroy the BBQ again. The BufferFreedListener is not used so as a simple fix, we remove the listener. Test: presubmit Bug: 200246498 Change-Id: I7b2be265b3c09d691cc0c41b80c73067f9a8b84d --- libs/gui/BLASTBufferQueue.cpp | 2 -- libs/gui/include/gui/BLASTBufferQueue.h | 5 +---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index f34061492a..77022dc9b8 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -159,7 +159,6 @@ BLASTBufferQueue::BLASTBufferQueue(const std::string& name, bool updateDestinati id++; mBufferItemConsumer->setName(String8(consumerName.c_str())); mBufferItemConsumer->setFrameAvailableListener(this); - mBufferItemConsumer->setBufferFreedListener(this); ComposerServiceAIDL::getComposerService()->getMaxAcquiredBufferCount(&mMaxAcquiredBuffers); mBufferItemConsumer->setMaxAcquiredBufferCount(mMaxAcquiredBuffers); @@ -1114,7 +1113,6 @@ void BLASTBufferQueue::abandon() { if (mBufferItemConsumer != nullptr) { mBufferItemConsumer->abandon(); mBufferItemConsumer->setFrameAvailableListener(nullptr); - mBufferItemConsumer->setBufferFreedListener(nullptr); } mBufferItemConsumer = nullptr; mConsumer = nullptr; diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h index 9328a54184..f6bf861b06 100644 --- a/libs/gui/include/gui/BLASTBufferQueue.h +++ b/libs/gui/include/gui/BLASTBufferQueue.h @@ -69,9 +69,7 @@ private: bool mPreviouslyConnected GUARDED_BY(mMutex); }; -class BLASTBufferQueue - : public ConsumerBase::FrameAvailableListener, public BufferItemConsumer::BufferFreedListener -{ +class BLASTBufferQueue : public ConsumerBase::FrameAvailableListener { public: BLASTBufferQueue(const std::string& name, bool updateDestinationFrame = true); BLASTBufferQueue(const std::string& name, const sp& surface, int width, @@ -83,7 +81,6 @@ public: sp getSurface(bool includeSurfaceControlHandle); bool isSameSurfaceControl(const sp& surfaceControl) const; - void onBufferFreed(const wp&/* graphicBuffer*/) override { /* TODO */ } void onFrameReplaced(const BufferItem& item) override; void onFrameAvailable(const BufferItem& item) override; void onFrameDequeued(const uint64_t) override; -- GitLab From 641f7f22ab6608144c9abbb117cb383298419673 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Wed, 22 Jun 2022 19:25:35 +0000 Subject: [PATCH 0106/1310] Update syncInputWindows to use a callback. Adds callbacks to InputWindowCommands and updates SurfaceComposerClient::Transaction::syncInputWindows to use those callbacks. A subsequent CL will replace syncInputWindows with a method that adds user defined callbacks to InputWindowCommands. The condition variable added to SurfaceComposerClient::Transaction is used to retain syncInputWindows' existing behavior and will be removed once users can define callbacks. Bug: b/222421815 Test: manual, TransactionApplicationTest, SwitchImeWindowsFromGestureNavTest Change-Id: Ib582aded1e42f5e049ebe21d5f2ccedf4cf7d654 --- libs/gui/LayerState.cpp | 27 +++++-- libs/gui/SurfaceComposerClient.cpp | 44 ++++++++++- libs/gui/include/gui/LayerState.h | 5 +- libs/gui/include/gui/SurfaceComposerClient.h | 18 +++++ services/surfaceflinger/SurfaceFlinger.cpp | 32 ++++---- services/surfaceflinger/SurfaceFlinger.h | 1 - services/surfaceflinger/TransactionState.h | 1 - .../WindowInfosListenerInvoker.cpp | 71 ++++++++++++------ .../WindowInfosListenerInvoker.h | 10 +-- .../fuzzer/surfaceflinger_fuzzer.cpp | 1 - .../unittests/TransactionApplicationTest.cpp | 73 +++++++------------ 11 files changed, 180 insertions(+), 103 deletions(-) diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 7f0f6381e1..59c4c5db8a 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -664,29 +664,44 @@ bool InputWindowCommands::merge(const InputWindowCommands& other) { changes |= !other.focusRequests.empty(); focusRequests.insert(focusRequests.end(), std::make_move_iterator(other.focusRequests.begin()), std::make_move_iterator(other.focusRequests.end())); - changes |= other.syncInputWindows && !syncInputWindows; - syncInputWindows |= other.syncInputWindows; + changes |= !other.windowInfosReportedListeners.empty(); + windowInfosReportedListeners.insert(other.windowInfosReportedListeners.begin(), + other.windowInfosReportedListeners.end()); return changes; } bool InputWindowCommands::empty() const { - return focusRequests.empty() && !syncInputWindows; + return focusRequests.empty() && windowInfosReportedListeners.empty(); } void InputWindowCommands::clear() { focusRequests.clear(); - syncInputWindows = false; + windowInfosReportedListeners.clear(); } status_t InputWindowCommands::write(Parcel& output) const { SAFE_PARCEL(output.writeParcelableVector, focusRequests); - SAFE_PARCEL(output.writeBool, syncInputWindows); + + SAFE_PARCEL(output.writeInt32, windowInfosReportedListeners.size()); + for (const auto& listener : windowInfosReportedListeners) { + SAFE_PARCEL(output.writeStrongBinder, listener); + } + return NO_ERROR; } status_t InputWindowCommands::read(const Parcel& input) { SAFE_PARCEL(input.readParcelableVector, &focusRequests); - SAFE_PARCEL(input.readBool, &syncInputWindows); + + int listenerSize = 0; + SAFE_PARCEL_READ_SIZE(input.readInt32, &listenerSize, input.dataSize()); + windowInfosReportedListeners.reserve(listenerSize); + for (int i = 0; i < listenerSize; i++) { + sp listener; + SAFE_PARCEL(input.readStrongBinder, &listener); + windowInfosReportedListeners.insert(listener); + } + return NO_ERROR; } diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 7191de8f7f..647a22cbe9 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -635,7 +636,8 @@ SurfaceComposerClient::Transaction::Transaction(const Transaction& other) mDesiredPresentTime(other.mDesiredPresentTime), mIsAutoTimestamp(other.mIsAutoTimestamp), mFrameTimelineInfo(other.mFrameTimelineInfo), - mApplyToken(other.mApplyToken) { + mApplyToken(other.mApplyToken), + mWindowInfosReportedEvent(other.mWindowInfosReportedEvent) { mDisplayStates = other.mDisplayStates; mComposerStates = other.mComposerStates; mInputWindowCommands = other.mInputWindowCommands; @@ -879,6 +881,9 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::merge(Tr mEarlyWakeupStart = mEarlyWakeupStart || other.mEarlyWakeupStart; mEarlyWakeupEnd = mEarlyWakeupEnd || other.mEarlyWakeupEnd; mApplyToken = other.mApplyToken; + if (other.mWindowInfosReportedEvent) { + mWindowInfosReportedEvent = std::move(other.mWindowInfosReportedEvent); + } mergeFrameTimelineInfo(mFrameTimelineInfo, other.mFrameTimelineInfo); @@ -901,6 +906,7 @@ void SurfaceComposerClient::Transaction::clear() { mIsAutoTimestamp = true; clearFrameTimelineInfo(mFrameTimelineInfo); mApplyToken = nullptr; + mWindowInfosReportedEvent = nullptr; } uint64_t SurfaceComposerClient::Transaction::getId() { @@ -1047,6 +1053,10 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay hasListenerCallbacks, listenerCallbacks, mId); mId = generateId(); + if (mWindowInfosReportedEvent && !mWindowInfosReportedEvent->wait()) { + ALOGE("Timed out waiting for window infos to be reported."); + } + // Clear the current states and flags clear(); @@ -1733,8 +1743,25 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFocus return *this; } +class NotifyWindowInfosReported : public gui::BnWindowInfosReportedListener { +public: + NotifyWindowInfosReported( + std::shared_ptr windowInfosReportedEvent) + : mWindowInfosReportedEvent(windowInfosReportedEvent) {} + + binder::Status onWindowInfosReported() { + mWindowInfosReportedEvent->set(); + return binder::Status::ok(); + } + +private: + std::shared_ptr mWindowInfosReportedEvent; +}; + SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::syncInputWindows() { - mInputWindowCommands.syncInputWindows = true; + mWindowInfosReportedEvent = std::make_shared(); + mInputWindowCommands.windowInfosReportedListeners.insert( + sp::make(mWindowInfosReportedEvent)); return *this; } @@ -2809,4 +2836,17 @@ void ReleaseCallbackThread::threadMain() { } } +// --------------------------------------------------------------------------------- + +void SurfaceComposerClient::Event::set() { + std::lock_guard lock(mMutex); + mComplete = true; + mConditionVariable.notify_all(); +} + +bool SurfaceComposerClient::Event::wait() { + std::unique_lock lock(mMutex); + return mConditionVariable.wait_for(lock, sTimeout, [this] { return mComplete; }); +} + } // namespace android diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 37a159512f..423cd8409d 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -359,7 +360,9 @@ struct DisplayState { struct InputWindowCommands { std::vector focusRequests; - bool syncInputWindows{false}; + std::unordered_set, + SpHash> + windowInfosReportedListeners; // Merges the passed in commands and returns true if there were any changes. bool merge(const InputWindowCommands& other); diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 9f036a6fb1..0927363cfe 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -387,6 +387,22 @@ public: std::unordered_set, SCHash> surfaceControls; }; + // TODO(b/222421815) this class should be removed when + // SurfaceComposerClient::Transaction::syncInputWindows is removed and replaced with a method + // for adding callbacks to InputWindowCommands. + class Event { + private: + static constexpr std::chrono::seconds sTimeout{5}; + + bool mComplete = false; + std::condition_variable mConditionVariable; + std::mutex mMutex; + + public: + void set(); + bool wait(); + }; + class Transaction : public Parcelable { private: void releaseBufferIfOverwriting(const layer_state_t& state); @@ -436,6 +452,8 @@ public: InputWindowCommands mInputWindowCommands; int mStatus = NO_ERROR; + std::shared_ptr mWindowInfosReportedEvent = nullptr; + layer_state_t* getLayerState(const sp& sc); DisplayState& getDisplayState(const sp& token); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index aba7998b3c..d701221758 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -341,7 +341,7 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory, SkipInitializationTag) mInternalDisplayDensity(getDensityFromProperty("ro.sf.lcd_density", true)), mEmulatedDisplayDensity(getDensityFromProperty("qemu.sf.lcd_density", false)), mPowerAdvisor(std::make_unique(*this)), - mWindowInfosListenerInvoker(sp::make(*this)) { + mWindowInfosListenerInvoker(sp::make()) { ALOGI("Using HWComposer service: %s", mHwcServiceName.c_str()); } @@ -3273,12 +3273,17 @@ void SurfaceFlinger::updateInputFlinger() { inputFlinger = mInputFlinger, this]() { ATRACE_NAME("BackgroundExecutor::updateInputFlinger"); if (updateWindowInfo) { - mWindowInfosListenerInvoker->windowInfosChanged(windowInfos, displayInfos, - inputWindowCommands.syncInputWindows); - } else if (inputWindowCommands.syncInputWindows) { - // If the caller requested to sync input windows, but there are no - // changes to input windows, notify immediately. - windowInfosReported(); + mWindowInfosListenerInvoker + ->windowInfosChanged(windowInfos, displayInfos, + inputWindowCommands.windowInfosReportedListeners); + } else { + // If there are listeners but no changes to input windows, call the listeners + // immediately. + for (const auto& listener : inputWindowCommands.windowInfosReportedListeners) { + if (IInterface::asBinder(listener)->isBinderAlive()) { + listener->onWindowInfosReported(); + } + } } for (const auto& focusRequest : inputWindowCommands.focusRequests) { inputFlinger->setFocusedWindow(focusRequest); @@ -4101,11 +4106,9 @@ void SurfaceFlinger::queueTransaction(TransactionState& state) { Mutex::Autolock lock(mQueueLock); // Generate a CountDownLatch pending state if this is a synchronous transaction. - if ((state.flags & eSynchronous) || state.inputWindowCommands.syncInputWindows) { - state.transactionCommittedSignal = std::make_shared( - (state.inputWindowCommands.syncInputWindows - ? (CountDownLatch::eSyncInputWindows | CountDownLatch::eSyncTransaction) - : CountDownLatch::eSyncTransaction)); + if (state.flags & eSynchronous) { + state.transactionCommittedSignal = + std::make_shared(CountDownLatch::eSyncTransaction); } mTransactionQueue.emplace_back(state); @@ -6803,11 +6806,6 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( return future; } -void SurfaceFlinger::windowInfosReported() { - Mutex::Autolock _l(mStateLock); - signalSynchronousTransactions(CountDownLatch::eSyncInputWindows); -} - // --------------------------------------------------------------------------- void SurfaceFlinger::State::traverse(const LayerVector::Visitor& visitor) const { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index e1a4cd3e9a..ed6c8ce6d7 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -333,7 +333,6 @@ public: // If set, disables reusing client composition buffers. This can be set by // debug.sf.disable_client_composition_cache bool mDisableClientCompositionCache = false; - void windowInfosReported(); // Disables expensive rendering for all displays // This is scheduled on the main thread diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h index 900d566942..bbfeac1643 100644 --- a/services/surfaceflinger/TransactionState.h +++ b/services/surfaceflinger/TransactionState.h @@ -106,7 +106,6 @@ class CountDownLatch { public: enum { eSyncTransaction = 1 << 0, - eSyncInputWindows = 1 << 1, }; explicit CountDownLatch(uint32_t flags) : mFlags(flags) {} diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp index 30b9d8f1cb..cc33001a8b 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp +++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp @@ -26,21 +26,39 @@ using gui::DisplayInfo; using gui::IWindowInfosListener; using gui::WindowInfo; -struct WindowInfosListenerInvoker::WindowInfosReportedListener - : gui::BnWindowInfosReportedListener { - explicit WindowInfosReportedListener(WindowInfosListenerInvoker& invoker) : mInvoker(invoker) {} +struct WindowInfosListenerInvoker::WindowInfosReportedListener : gui::BnWindowInfosReportedListener, + DeathRecipient { + explicit WindowInfosReportedListener( + size_t callbackCount, + const std::unordered_set, + SpHash>& + windowInfosReportedListeners) + : mCallbacksPending(callbackCount), + mWindowInfosReportedListeners(windowInfosReportedListeners) {} binder::Status onWindowInfosReported() override { - mInvoker.windowInfosReported(); + // TODO(b/222421815) There could potentially be callbacks that we don't need to wait for + // before calling the WindowInfosReportedListeners coming from InputWindowCommands. Filter + // the list of callbacks down to those from system server. + if (--mCallbacksPending == 0) { + for (const auto& listener : mWindowInfosReportedListeners) { + sp asBinder = IInterface::asBinder(listener); + if (asBinder->isBinderAlive()) { + listener->onWindowInfosReported(); + } + } + } return binder::Status::ok(); } - WindowInfosListenerInvoker& mInvoker; -}; + void binderDied(const wp&) { onWindowInfosReported(); } -WindowInfosListenerInvoker::WindowInfosListenerInvoker(SurfaceFlinger& flinger) - : mFlinger(flinger), - mWindowInfosReportedListener(sp::make(*this)) {} +private: + std::atomic mCallbacksPending; + std::unordered_set, + SpHash> + mWindowInfosReportedListeners; +}; void WindowInfosListenerInvoker::addWindowInfosListener(sp listener) { sp asBinder = IInterface::asBinder(listener); @@ -64,9 +82,11 @@ void WindowInfosListenerInvoker::binderDied(const wp& who) { mWindowInfosListeners.erase(who); } -void WindowInfosListenerInvoker::windowInfosChanged(const std::vector& windowInfos, - const std::vector& displayInfos, - bool shouldSync) { +void WindowInfosListenerInvoker::windowInfosChanged( + const std::vector& windowInfos, const std::vector& displayInfos, + const std::unordered_set, + SpHash>& + windowInfosReportedListeners) { ftl::SmallVector, kStaticCapacity> windowInfosListeners; { std::scoped_lock lock(mListenersMutex); @@ -75,18 +95,25 @@ void WindowInfosListenerInvoker::windowInfosChanged(const std::vector::make(windowInfosListeners.size(), + windowInfosReportedListeners); for (const auto& listener : windowInfosListeners) { - listener->onWindowInfosChanged(windowInfos, displayInfos, - shouldSync ? mWindowInfosReportedListener : nullptr); - } -} + sp asBinder = IInterface::asBinder(listener); + + // linkToDeath is used here to ensure that the windowInfosReportedListeners + // are called even if one of the windowInfosListeners dies before + // calling onWindowInfosReported. + if (windowInfosReportedListener) { + asBinder->linkToDeath(windowInfosReportedListener); + } -void WindowInfosListenerInvoker::windowInfosReported() { - mCallbacksPending--; - if (mCallbacksPending == 0) { - mFlinger.windowInfosReported(); + auto status = listener->onWindowInfosChanged(windowInfos, displayInfos, + windowInfosReportedListener); + if (!status.isOk()) { + windowInfosReportedListener->onWindowInfosReported(); + } } } diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h index d8d8d0f570..a1d66a186e 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.h +++ b/services/surfaceflinger/WindowInfosListenerInvoker.h @@ -29,22 +29,21 @@ class SurfaceFlinger; class WindowInfosListenerInvoker : public IBinder::DeathRecipient { public: - explicit WindowInfosListenerInvoker(SurfaceFlinger&); - void addWindowInfosListener(sp); void removeWindowInfosListener(const sp& windowInfosListener); void windowInfosChanged(const std::vector&, - const std::vector&, bool shouldSync); + const std::vector&, + const std::unordered_set, + SpHash>& + windowInfosReportedListeners); protected: void binderDied(const wp& who) override; private: struct WindowInfosReportedListener; - void windowInfosReported(); - SurfaceFlinger& mFlinger; std::mutex mListenersMutex; static constexpr size_t kStaticCapacity = 3; @@ -52,7 +51,6 @@ private: mWindowInfosListeners GUARDED_BY(mListenersMutex); sp mWindowInfosReportedListener; - std::atomic mCallbacksPending{0}; }; } // namespace android diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp index f25043cd6d..8e60247c2d 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp @@ -151,7 +151,6 @@ void SurfaceFlingerFuzzer::invokeFlinger() { sp handle = defaultServiceManager()->checkService( String16(mFdp.ConsumeRandomLengthString().c_str())); mFlinger->fromHandle(handle); - mFlinger->windowInfosReported(); mFlinger->disableExpensiveRendering(); } diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index ded75318ed..84f11704d0 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -107,22 +107,20 @@ public: EXPECT_EQ(info.desiredPresentTime, state.desiredPresentTime); } - void setupSingle(TransactionInfo& transaction, uint32_t flags, bool syncInputWindows, - int64_t desiredPresentTime, bool isAutoTimestamp, - const FrameTimelineInfo& frameTimelineInfo) { + void setupSingle(TransactionInfo& transaction, uint32_t flags, int64_t desiredPresentTime, + bool isAutoTimestamp, const FrameTimelineInfo& frameTimelineInfo) { mTransactionNumber++; transaction.flags |= flags; // ISurfaceComposer::eSynchronous; - transaction.inputWindowCommands.syncInputWindows = syncInputWindows; transaction.desiredPresentTime = desiredPresentTime; transaction.isAutoTimestamp = isAutoTimestamp; transaction.frameTimelineInfo = frameTimelineInfo; } - void NotPlacedOnTransactionQueue(uint32_t flags, bool syncInputWindows) { + void NotPlacedOnTransactionQueue(uint32_t flags) { ASSERT_EQ(0u, mFlinger.getTransactionQueue().size()); EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1); TransactionInfo transaction; - setupSingle(transaction, flags, syncInputWindows, + setupSingle(transaction, flags, /*desiredPresentTime*/ systemTime(), /*isAutoTimestamp*/ true, FrameTimelineInfo{}); nsecs_t applicationTime = systemTime(); @@ -133,12 +131,10 @@ public: transaction.uncacheBuffer, mHasListenerCallbacks, mCallbacks, transaction.id); - // If transaction is synchronous or syncs input windows, SF - // applyTransactionState should time out (5s) wating for SF to commit - // the transaction or to receive a signal that syncInputWindows has - // completed. If this is animation, it should not time out waiting. + // If transaction is synchronous, SF applyTransactionState should time out (5s) wating for + // SF to commit the transaction. If this is animation, it should not time out waiting. nsecs_t returnedTime = systemTime(); - if (flags & ISurfaceComposer::eSynchronous || syncInputWindows) { + if (flags & ISurfaceComposer::eSynchronous) { EXPECT_GE(returnedTime, applicationTime + mFlinger.getAnimationTransactionTimeout()); } else { EXPECT_LE(returnedTime, applicationTime + mFlinger.getAnimationTransactionTimeout()); @@ -148,7 +144,7 @@ public: EXPECT_EQ(1u, transactionQueue.size()); } - void PlaceOnTransactionQueue(uint32_t flags, bool syncInputWindows) { + void PlaceOnTransactionQueue(uint32_t flags) { ASSERT_EQ(0u, mFlinger.getTransactionQueue().size()); EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1); @@ -156,8 +152,8 @@ public: // but afterwards it will look like the desired present time has passed nsecs_t time = systemTime(); TransactionInfo transaction; - setupSingle(transaction, flags, syncInputWindows, - /*desiredPresentTime*/ time + s2ns(1), false, FrameTimelineInfo{}); + setupSingle(transaction, flags, /*desiredPresentTime*/ time + s2ns(1), false, + FrameTimelineInfo{}); nsecs_t applicationSentTime = systemTime(); mFlinger.setTransactionState(transaction.frameTimelineInfo, transaction.states, transaction.displays, transaction.flags, @@ -167,7 +163,7 @@ public: transaction.id); nsecs_t returnedTime = systemTime(); - if ((flags & ISurfaceComposer::eSynchronous) || syncInputWindows) { + if (flags & ISurfaceComposer::eSynchronous) { EXPECT_GE(systemTime(), applicationSentTime + mFlinger.getAnimationTransactionTimeout()); } else { @@ -179,25 +175,21 @@ public: EXPECT_EQ(1u, transactionQueue.size()); } - void BlockedByPriorTransaction(uint32_t flags, bool syncInputWindows) { + void BlockedByPriorTransaction(uint32_t flags) { ASSERT_EQ(0u, mFlinger.getTransactionQueue().size()); nsecs_t time = systemTime(); - if (!syncInputWindows) { - EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(2); - } else { - EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1); - } + EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(2); + // transaction that should go on the pending thread TransactionInfo transactionA; - setupSingle(transactionA, /*flags*/ 0, /*syncInputWindows*/ false, - /*desiredPresentTime*/ time + s2ns(1), false, FrameTimelineInfo{}); + setupSingle(transactionA, /*flags*/ 0, /*desiredPresentTime*/ time + s2ns(1), false, + FrameTimelineInfo{}); // transaction that would not have gone on the pending thread if not // blocked TransactionInfo transactionB; - setupSingle(transactionB, flags, syncInputWindows, - /*desiredPresentTime*/ systemTime(), /*isAutoTimestamp*/ true, - FrameTimelineInfo{}); + setupSingle(transactionB, flags, /*desiredPresentTime*/ systemTime(), + /*isAutoTimestamp*/ true, FrameTimelineInfo{}); nsecs_t applicationSentTime = systemTime(); mFlinger.setTransactionState(transactionA.frameTimelineInfo, transactionA.states, @@ -226,8 +218,7 @@ public: // if this is an animation, this thread should be blocked for 5s // in setTransactionState waiting for transactionA to flush. Otherwise, // the transaction should be placed on the pending queue - if (flags & (ISurfaceComposer::eSynchronous) || - syncInputWindows) { + if (flags & ISurfaceComposer::eSynchronous) { EXPECT_GE(systemTime(), applicationSentTime + mFlinger.getAnimationTransactionTimeout()); } else { @@ -253,8 +244,8 @@ TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) { EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1); TransactionInfo transactionA; // transaction to go on pending queue - setupSingle(transactionA, /*flags*/ 0, /*syncInputWindows*/ false, - /*desiredPresentTime*/ s2ns(1), false, FrameTimelineInfo{}); + setupSingle(transactionA, /*flags*/ 0, /*desiredPresentTime*/ s2ns(1), false, + FrameTimelineInfo{}); mFlinger.setTransactionState(transactionA.frameTimelineInfo, transactionA.states, transactionA.displays, transactionA.flags, transactionA.applyToken, transactionA.inputWindowCommands, transactionA.desiredPresentTime, @@ -285,31 +276,23 @@ TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) { } TEST_F(TransactionApplicationTest, NotPlacedOnTransactionQueue_Synchronous) { - NotPlacedOnTransactionQueue(ISurfaceComposer::eSynchronous, /*syncInputWindows*/ false); + NotPlacedOnTransactionQueue(ISurfaceComposer::eSynchronous); } TEST_F(TransactionApplicationTest, NotPlacedOnTransactionQueue_SyncInputWindows) { - NotPlacedOnTransactionQueue(/*flags*/ 0, /*syncInputWindows*/ true); + NotPlacedOnTransactionQueue(/*flags*/ 0); } TEST_F(TransactionApplicationTest, PlaceOnTransactionQueue_Synchronous) { - PlaceOnTransactionQueue(ISurfaceComposer::eSynchronous, /*syncInputWindows*/ false); + PlaceOnTransactionQueue(ISurfaceComposer::eSynchronous); } TEST_F(TransactionApplicationTest, PlaceOnTransactionQueue_SyncInputWindows) { - PlaceOnTransactionQueue(/*flags*/ 0, /*syncInputWindows*/ true); + PlaceOnTransactionQueue(/*flags*/ 0); } TEST_F(TransactionApplicationTest, BlockWithPriorTransaction_Synchronous) { - BlockedByPriorTransaction(ISurfaceComposer::eSynchronous, /*syncInputWindows*/ false); -} - -TEST_F(TransactionApplicationTest, BlockWithPriorTransaction_Animation) { - BlockedByPriorTransaction(ISurfaceComposer::eSynchronous, /*syncInputWindows*/ false); -} - -TEST_F(TransactionApplicationTest, BlockWithPriorTransaction_SyncInputWindows) { - BlockedByPriorTransaction(/*flags*/ 0, /*syncInputWindows*/ true); + BlockedByPriorTransaction(ISurfaceComposer::eSynchronous); } TEST_F(TransactionApplicationTest, FromHandle) { @@ -359,13 +342,11 @@ public: const std::vector& states) { TransactionInfo transaction; const uint32_t kFlags = ISurfaceComposer::eSynchronous; - const bool kSyncInputWindows = false; const nsecs_t kDesiredPresentTime = systemTime(); const bool kIsAutoTimestamp = true; const auto kFrameTimelineInfo = FrameTimelineInfo{}; - setupSingle(transaction, kFlags, kSyncInputWindows, kDesiredPresentTime, kIsAutoTimestamp, - kFrameTimelineInfo); + setupSingle(transaction, kFlags, kDesiredPresentTime, kIsAutoTimestamp, kFrameTimelineInfo); transaction.applyToken = applyToken; for (const auto& state : states) { transaction.states.push_back(state); -- GitLab From ea2bb822282312b755d3d1be6edab64abfc9645a Mon Sep 17 00:00:00 2001 From: ramindani Date: Mon, 27 Jun 2022 19:52:10 +0000 Subject: [PATCH 0107/1310] Returns min of minTime or presentTime instead of minTime or endTime. Test: atest libsurfaceflinger_unittest BUG: 237113799 Change-Id: I83f270faf2f3872095723854d9bce7b50581cdee --- .../FrameTimeline/FrameTimeline.cpp | 2 +- .../tests/unittests/FrameTimelineTest.cpp | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp index 66beff2953..3e69b5c67a 100644 --- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp +++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp @@ -289,7 +289,7 @@ nsecs_t getMinTime(PredictionState predictionState, TimelineItem predictions, minTime = std::min(minTime, actuals.endTime); } if (actuals.presentTime != 0) { - minTime = std::min(minTime, actuals.endTime); + minTime = std::min(minTime, actuals.presentTime); } return minTime; } diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp index bc379f2fb5..756db8a1d1 100644 --- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp +++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp @@ -2342,4 +2342,38 @@ TEST_F(FrameTimelineTest, computeFps_averagesOverMultipleFrames) { EXPECT_EQ(mFrameTimeline->computeFps({sLayerIdOne}), 5.0f); } +TEST_F(FrameTimelineTest, getMinTime) { + // Use SurfaceFrame::getBaseTime to test the getMinTime. + FrameTimelineInfo ftInfo; + + // Valid prediction state test. + ftInfo.vsyncId = 0L; + mTokenManager->generateTokenForPredictions({10}); + auto surfaceFrame = + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); + ASSERT_EQ(surfaceFrame->getBaseTime(), 10); + + // Test prediction state which is not valid. + ftInfo.vsyncId = FrameTimelineInfo::INVALID_VSYNC_ID; + surfaceFrame = mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); + // Start time test. + surfaceFrame->setActualStartTime(200); + ASSERT_EQ(surfaceFrame->getBaseTime(), 200); + + // End time test. + surfaceFrame->setAcquireFenceTime(100); + ASSERT_EQ(surfaceFrame->getBaseTime(), 100); + + // Present time test. + auto presentFence = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); + surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented); + mFrameTimeline->addSurfaceFrame(surfaceFrame); + presentFence->signalForTest(std::chrono::nanoseconds(50ns).count()); + mFrameTimeline->setSfPresent(50, presentFence); + ASSERT_EQ(surfaceFrame->getBaseTime(), 50); +} } // namespace android::frametimeline -- GitLab From d7740d681c5b577dac7827fae66f9ec9847327dc Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 1 Jul 2022 17:54:18 +0000 Subject: [PATCH 0108/1310] Classifier: Ping the HAL from monitor() instead of dump() The input dump should not be affected by the state of the HAL. Previously, we were pinging the HAL duing a dump, which means the dumpsys would fail if the HAL is stuck. Instead, we ping the HAL duing the monitor() call so that a HAL that is stuck can be more easiliy identified. Bug: 237347585 Test: manual, flash device Change-Id: I050bacc5510f10b788232e156ea91d505d841b55 --- services/inputflinger/InputClassifier.cpp | 26 ++++++++++++----------- services/inputflinger/InputClassifier.h | 12 ++++++++--- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/services/inputflinger/InputClassifier.cpp b/services/inputflinger/InputClassifier.cpp index 8ce2f35d7b..21695c354d 100644 --- a/services/inputflinger/InputClassifier.cpp +++ b/services/inputflinger/InputClassifier.cpp @@ -331,20 +331,9 @@ void MotionClassifier::reset(const NotifyDeviceResetArgs& args) { enqueueEvent(std::make_unique(args)); } -const char* MotionClassifier::getServiceStatus() REQUIRES(mLock) { - if (!mService) { - return "null"; - } - - if (AIBinder_ping(mService->asBinder().get()) == STATUS_OK) { - return "running"; - } - return "not responding"; -} - void MotionClassifier::dump(std::string& dump) { std::scoped_lock lock(mLock); - dump += StringPrintf(INDENT2 "mService status: %s\n", getServiceStatus()); + dump += StringPrintf(INDENT2 "mService connected: %s\n", mService ? "true" : "false"); dump += StringPrintf(INDENT2 "mEvents: %zu element(s) (max=%zu)\n", mEvents.size(), MAX_EVENTS); dump += INDENT2 "mClassifications, mLastDownTimes:\n"; @@ -365,6 +354,18 @@ void MotionClassifier::dump(std::string& dump) { } } +void MotionClassifier::monitor() { + std::scoped_lock lock(mLock); + if (mService) { + // Ping the HAL service to ensure it is alive and not blocked. + const binder_status_t status = AIBinder_ping(mService->asBinder().get()); + if (status != STATUS_OK) { + ALOGW("IInputProcessor HAL is not responding; binder ping result: %s", + AStatus_getDescription(AStatus_fromStatus(status))); + } + } +} + // --- InputClassifier --- InputClassifier::InputClassifier(InputListenerInterface& listener) : mQueuedListener(listener) {} @@ -504,6 +505,7 @@ void InputClassifier::dump(std::string& dump) { void InputClassifier::monitor() { std::scoped_lock lock(mLock); + if (mMotionClassifier) mMotionClassifier->monitor(); } InputClassifier::~InputClassifier() { diff --git a/services/inputflinger/InputClassifier.h b/services/inputflinger/InputClassifier.h index 56cf760256..9b31a3c33d 100644 --- a/services/inputflinger/InputClassifier.h +++ b/services/inputflinger/InputClassifier.h @@ -78,9 +78,14 @@ public: virtual void reset(const NotifyDeviceResetArgs& args) = 0; /** - * Dump the state of the motion classifier + * Dump the state of the motion classifier. */ virtual void dump(std::string& dump) = 0; + + /** + * Called by the heartbeat to ensure the HAL is still processing normally. + */ + virtual void monitor() = 0; }; /** @@ -96,7 +101,7 @@ public: */ virtual void dump(std::string& dump) = 0; - /* Called by the heatbeat to ensures that the classifier has not deadlocked. */ + /** Called by the heartbeat to ensure that the classifier has not deadlocked. */ virtual void monitor() = 0; InputClassifierInterface() { } @@ -155,13 +160,14 @@ public: virtual void reset(const NotifyDeviceResetArgs& args) override; virtual void dump(std::string& dump) override; + virtual void monitor() override; private: friend class MotionClassifierTest; // to create MotionClassifier with a test HAL implementation explicit MotionClassifier( std::shared_ptr service); - // The events that need to be sent to the HAL. + /** The events that need to be sent to the HAL. */ BlockingQueue mEvents; /** * Add an event to the queue mEvents. -- GitLab From a32c371fa7caaa8744b98778f7a39524c49d33dd Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 1 Jul 2022 13:38:51 -0700 Subject: [PATCH 0109/1310] Dump IInputProcessor when dump is called We need to dump this process because it may become unresponsive. Currently, it's missing from the dumps. After this change, I'm finding the following data inside bugreport: ----- Waiting Channels: pid 1794 at 2022-07-01 21:08:01.751449995+0000 ----- Cmd line: /vendor/bin/hw/android.hardware.input.processor-service sysTid=1794 binder_wait_for_work sysTid=1804 futex_wait_queue_me ----- end 1794 ----- Bug: 237347585 Bug: 237322365 Test: adb bugreport Change-Id: Id5a1a2cba2131e58a4eadd4492f9d1c82de4d746 --- libs/dumputils/dump_utils.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/dumputils/dump_utils.cpp b/libs/dumputils/dump_utils.cpp index e5439174a9..a6585c5ad2 100644 --- a/libs/dumputils/dump_utils.cpp +++ b/libs/dumputils/dump_utils.cpp @@ -94,6 +94,7 @@ static const std::vector aidl_interfaces_to_dump { "android.hardware.graphics.allocator.IAllocator", "android.hardware.graphics.composer3.IComposer", "android.hardware.health.IHealth", + "android.hardware.input.processor.IInputProcessor", "android.hardware.neuralnetworks.IDevice", "android.hardware.power.IPower", "android.hardware.power.stats.IPowerStats", -- GitLab From c42eae5297150eaa3606eea472ceb568c48b0a43 Mon Sep 17 00:00:00 2001 From: Lingfeng Yang Date: Fri, 1 Jul 2022 18:18:29 -0700 Subject: [PATCH 0110/1310] Allow use of PASS_THROUGH colorspace with RGBA8 This CL allows us to specify the pass through color space, and as a consequence, HAL_DATASPACE_ARBITRARY, on swapchain creation. This helps us pass Media encode/decode test cases on ANGLE. Bug: 237512291 Change-Id: I7aebe80d77fdc454f04489fbc552bad40a922ae3 --- vulkan/libvulkan/swapchain.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp index f86600569b..eb669c0b8c 100644 --- a/vulkan/libvulkan/swapchain.cpp +++ b/vulkan/libvulkan/swapchain.cpp @@ -763,7 +763,11 @@ VkResult GetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice pdev, // We must support R8G8B8A8 std::vector all_formats = { {VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}, - {VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}}; + {VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}, + // Also allow to use PASS_THROUGH + HAL_DATASPACE_ARBITRARY + {VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_PASS_THROUGH_EXT}, + {VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_PASS_THROUGH_EXT}, + }; if (colorspace_ext) { all_formats.emplace_back(VkSurfaceFormatKHR{ -- GitLab From 882bd9bcf3567981022a810f08155be10c980d38 Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Thu, 23 Jun 2022 14:54:54 +0000 Subject: [PATCH 0111/1310] Send correct downtime for motion events in split pointers case Currently, for split pointer scenario, the down time is still the the down time for the first pointer on the display. This might not necessarily be the downTime for a window in split pointer case. Need to send the correct downTime when we split pointers. Test: atest inputflinger_tests Bug: 228331266 Bug: 220109830 Change-Id: I80cf889e3243c39619ba96bd2b74073642cabc36 --- .../dispatcher/InputDispatcher.cpp | 70 +++++++++++++----- .../inputflinger/dispatcher/InputDispatcher.h | 9 ++- .../inputflinger/dispatcher/InputTarget.h | 3 + .../inputflinger/dispatcher/TouchState.cpp | 9 ++- services/inputflinger/dispatcher/TouchState.h | 3 +- .../inputflinger/dispatcher/TouchedWindow.h | 3 + .../tests/InputDispatcher_test.cpp | 73 +++++++++++++++++++ 7 files changed, 145 insertions(+), 25 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 5e92f0d8da..625c367338 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -1872,6 +1872,17 @@ bool InputDispatcher::shouldWaitToSendKeyLocked(nsecs_t currentTime, return false; } +static std::optional getDownTime(const EventEntry& eventEntry) { + if (eventEntry.type == EventEntry::Type::KEY) { + const KeyEntry& keyEntry = static_cast(eventEntry); + return keyEntry.downTime; + } else if (eventEntry.type == EventEntry::Type::MOTION) { + const MotionEntry& motionEntry = static_cast(eventEntry); + return motionEntry.downTime; + } + return std::nullopt; +} + InputEventInjectionResult InputDispatcher::findFocusedWindowTargetsLocked( nsecs_t currentTime, const EventEntry& entry, std::vector& inputTargets, nsecs_t* nextWakeupTime) { @@ -1961,7 +1972,7 @@ InputEventInjectionResult InputDispatcher::findFocusedWindowTargetsLocked( // Success! Output targets. addWindowTargetLocked(focusedWindowHandle, InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, - BitSet32(0), inputTargets); + BitSet32(0), getDownTime(entry), inputTargets); // Done. return InputEventInjectionResult::SUCCEEDED; @@ -2200,7 +2211,8 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( pointerIds.markBit(pointerId); } - tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds); + tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds, + entry.eventTime); } // If any existing window is pilfering pointers from newly added window, remove it @@ -2287,7 +2299,8 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( if (isSplit) { pointerIds.markBit(entry.pointerProperties[0].id); } - tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds); + tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds, + entry.eventTime); } } } @@ -2404,7 +2417,7 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( InputTarget:: FLAG_WINDOW_IS_PARTIALLY_OBSCURED | InputTarget::FLAG_DISPATCH_AS_IS, - BitSet32(0)); + BitSet32(0), entry.eventTime); } } } @@ -2415,7 +2428,8 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( for (const TouchedWindow& touchedWindow : tempTouchState.windows) { addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, - touchedWindow.pointerIds, inputTargets); + touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget, + inputTargets); } // Drop the outside or hover touch windows since we will not care about them @@ -2600,6 +2614,7 @@ void InputDispatcher::addDragEventLocked(const MotionEntry& entry) { void InputDispatcher::addWindowTargetLocked(const sp& windowHandle, int32_t targetFlags, BitSet32 pointerIds, + std::optional firstDownTimeInTarget, std::vector& inputTargets) { std::vector::iterator it = std::find_if(inputTargets.begin(), inputTargets.end(), @@ -2621,6 +2636,7 @@ void InputDispatcher::addWindowTargetLocked(const sp& windowHa inputTarget.inputChannel = inputChannel; inputTarget.flags = targetFlags; inputTarget.globalScaleFactor = windowInfo->globalScaleFactor; + inputTarget.firstDownTimeInTarget = firstDownTimeInTarget; const auto& displayInfoIt = mDisplayInfos.find(windowInfo->displayId); if (displayInfoIt != mDisplayInfos.end()) { inputTarget.displayTransform = displayInfoIt->second.transform; @@ -2646,6 +2662,8 @@ void InputDispatcher::addGlobalMonitoringTargetsLocked(std::vector& InputTarget target; target.inputChannel = monitor.inputChannel; target.flags = InputTarget::FLAG_DISPATCH_AS_IS; + // target.firstDownTimeInTarget is not set for global monitors. It is only required in split + // touch and global monitoring works as intended even without setting firstDownTimeInTarget if (const auto& it = mDisplayInfos.find(displayId); it != mDisplayInfos.end()) { target.displayTransform = it->second.transform; } @@ -2930,8 +2948,12 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, const MotionEntry& originalMotionEntry = static_cast(*eventEntry); if (inputTarget.pointerIds.count() != originalMotionEntry.pointerCount) { + LOG_ALWAYS_FATAL_IF(!inputTarget.firstDownTimeInTarget.has_value(), + "Splitting motion events requires a down time to be set for the " + "target"); std::unique_ptr splitMotionEntry = - splitMotionEvent(originalMotionEntry, inputTarget.pointerIds); + splitMotionEvent(originalMotionEntry, inputTarget.pointerIds, + inputTarget.firstDownTimeInTarget.value()); if (!splitMotionEntry) { return; // split event was dropped } @@ -3687,15 +3709,13 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( } void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( - const sp& connection) { + const nsecs_t downTime, const sp& connection) { if (connection->status == Connection::Status::BROKEN) { return; } - nsecs_t currentTime = now(); - std::vector> downEvents = - connection->inputState.synthesizePointerDownEvents(currentTime); + connection->inputState.synthesizePointerDownEvents(downTime); if (downEvents.empty()) { return; @@ -3743,11 +3763,11 @@ void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( InputTarget::FLAG_DISPATCH_AS_IS); } - startDispatchCycleLocked(currentTime, connection); + startDispatchCycleLocked(downTime, connection); } std::unique_ptr InputDispatcher::splitMotionEvent( - const MotionEntry& originalMotionEntry, BitSet32 pointerIds) { + const MotionEntry& originalMotionEntry, BitSet32 pointerIds, nsecs_t splitDownTime) { ALOG_ASSERT(pointerIds.value != 0); uint32_t splitPointerIndexMap[MAX_POINTERS]; @@ -3815,6 +3835,13 @@ std::unique_ptr InputDispatcher::splitMotionEvent( } } + if (action == AMOTION_EVENT_ACTION_DOWN) { + LOG_ALWAYS_FATAL_IF(splitDownTime != originalMotionEntry.eventTime, + "Split motion event has mismatching downTime and eventTime for " + "ACTION_DOWN, motionEntry=%s, splitDownTime=%" PRId64 "ms", + originalMotionEntry.getDescription().c_str(), ns2ms(splitDownTime)); + } + int32_t newId = mIdGenerator.nextId(); if (ATRACE_ENABLED()) { std::string message = StringPrintf("Split MotionEvent(id=0x%" PRIx32 @@ -3835,9 +3862,9 @@ std::unique_ptr InputDispatcher::splitMotionEvent( originalMotionEntry.xPrecision, originalMotionEntry.yPrecision, originalMotionEntry.xCursorPosition, - originalMotionEntry.yCursorPosition, - originalMotionEntry.downTime, splitPointerCount, - splitPointerProperties, splitPointerCoords); + originalMotionEntry.yCursorPosition, splitDownTime, + splitPointerCount, splitPointerProperties, + splitPointerCoords); if (originalMotionEntry.injectionState) { splitMotionEntry->injectionState = originalMotionEntry.injectionState; @@ -5074,12 +5101,13 @@ bool InputDispatcher::transferTouchFocus(const sp& fromToken, const sp< state->removeWindowByToken(fromToken); // Add new window. + nsecs_t downTimeInTarget = now(); int32_t newTargetFlags = oldTargetFlags & (InputTarget::FLAG_SPLIT | InputTarget::FLAG_DISPATCH_AS_IS); if (canReceiveForegroundTouches(*toWindowHandle->getInfo())) { newTargetFlags |= InputTarget::FLAG_FOREGROUND; } - state->addOrUpdateWindow(toWindowHandle, newTargetFlags, pointerIds); + state->addOrUpdateWindow(toWindowHandle, newTargetFlags, pointerIds, downTimeInTarget); // Store the dragging window. if (isDragDrop) { @@ -5103,7 +5131,7 @@ bool InputDispatcher::transferTouchFocus(const sp& fromToken, const sp< options(CancelationOptions::CANCEL_POINTER_EVENTS, "transferring touch focus from this window to another window"); synthesizeCancelationEventsForConnectionLocked(fromConnection, options); - synthesizePointerDownEventsForConnectionLocked(toConnection); + synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection); } if (DEBUG_FOCUS) { @@ -5253,10 +5281,12 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) { dump += INDENT3 "Windows:\n"; for (size_t i = 0; i < state.windows.size(); i++) { const TouchedWindow& touchedWindow = state.windows[i]; - dump += StringPrintf(INDENT4 - "%zu: name='%s', pointerIds=0x%0x, targetFlags=0x%x\n", + dump += StringPrintf(INDENT4 "%zu: name='%s', pointerIds=0x%0x, " + "targetFlags=0x%x, firstDownTimeInTarget=%" PRId64 + "ms\n", i, touchedWindow.windowHandle->getName().c_str(), - touchedWindow.pointerIds.value, touchedWindow.targetFlags); + touchedWindow.pointerIds.value, touchedWindow.targetFlags, + ns2ms(touchedWindow.firstDownTimeInTarget.value_or(0))); } } else { dump += INDENT3 "Windows: \n"; diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 50124a6606..da4af485fd 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -550,6 +550,7 @@ private: void addWindowTargetLocked(const sp& windowHandle, int32_t targetFlags, BitSet32 pointerIds, + std::optional firstDownTimeInTarget, std::vector& inputTargets) REQUIRES(mLock); void addGlobalMonitoringTargetsLocked(std::vector& inputTargets, int32_t displayId) REQUIRES(mLock); @@ -621,12 +622,14 @@ private: const CancelationOptions& options) REQUIRES(mLock); - void synthesizePointerDownEventsForConnectionLocked(const sp& connection) + void synthesizePointerDownEventsForConnectionLocked(const nsecs_t downTime, + const sp& connection) REQUIRES(mLock); - // Splitting motion events across windows. + // Splitting motion events across windows. When splitting motion event for a target, + // splitDownTime refers to the time of first 'down' event on that particular target std::unique_ptr splitMotionEvent(const MotionEntry& originalMotionEntry, - BitSet32 pointerIds); + BitSet32 pointerIds, nsecs_t splitDownTime); // Reset and drop everything the dispatcher is doing. void resetAndDropEverythingLocked(const char* reason) REQUIRES(mLock); diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h index 0725389ed6..ac20dab205 100644 --- a/services/inputflinger/dispatcher/InputTarget.h +++ b/services/inputflinger/dispatcher/InputTarget.h @@ -106,6 +106,9 @@ struct InputTarget { // The subset of pointer ids to include in motion events dispatched to this input target // if FLAG_SPLIT is set. BitSet32 pointerIds; + // Event time for the first motion event (ACTION_DOWN) dispatched to this input target if + // FLAG_SPLIT is set. + std::optional firstDownTimeInTarget; // The data is stored by the pointerId. Use the bit position of pointerIds to look up // Transform per pointerId. ui::Transform pointerTransforms[MAX_POINTERS]; diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index 51c6826675..cf0c38af33 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -30,7 +30,7 @@ void TouchState::reset() { } void TouchState::addOrUpdateWindow(const sp& windowHandle, int32_t targetFlags, - BitSet32 pointerIds) { + BitSet32 pointerIds, std::optional eventTime) { if (targetFlags & InputTarget::FLAG_SPLIT) { split = true; } @@ -42,7 +42,13 @@ void TouchState::addOrUpdateWindow(const sp& windowHandle, int if (targetFlags & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) { touchedWindow.targetFlags &= ~InputTarget::FLAG_DISPATCH_AS_IS; } + // For cases like hover enter/exit or DISPATCH_AS_OUTSIDE a touch window might not have + // downTime set initially. Need to update existing window when an pointer is down for + // the window. touchedWindow.pointerIds.value |= pointerIds.value; + if (!touchedWindow.firstDownTimeInTarget.has_value()) { + touchedWindow.firstDownTimeInTarget = eventTime; + } return; } } @@ -51,6 +57,7 @@ void TouchState::addOrUpdateWindow(const sp& windowHandle, int touchedWindow.windowHandle = windowHandle; touchedWindow.targetFlags = targetFlags; touchedWindow.pointerIds = pointerIds; + touchedWindow.firstDownTimeInTarget = eventTime; windows.push_back(touchedWindow); } diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index 327863f44f..1fb51e161d 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -47,7 +47,8 @@ struct TouchState { void reset(); void addOrUpdateWindow(const sp& windowHandle, - int32_t targetFlags, BitSet32 pointerIds); + int32_t targetFlags, BitSet32 pointerIds, + std::optional eventTime = std::nullopt); void removeWindowByToken(const sp& token); void filterNonAsIsTouchWindows(); void filterWindowsExcept(const sp& token); diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h index 83b52a4167..0962d0cc2c 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -31,6 +31,9 @@ struct TouchedWindow { int32_t targetFlags; BitSet32 pointerIds; // zero unless target flag FLAG_SPLIT is set bool isPilferingPointers = false; + // Time at which the first action down occurred on this window. + // NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE scenario. + std::optional firstDownTimeInTarget; }; } // namespace inputdispatcher diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 8af7cc3a44..70fd25c1aa 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -58,6 +58,8 @@ static constexpr int32_t POINTER_1_DOWN = AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); static constexpr int32_t POINTER_2_DOWN = AMOTION_EVENT_ACTION_POINTER_DOWN | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); +static constexpr int32_t POINTER_3_DOWN = + AMOTION_EVENT_ACTION_POINTER_DOWN | (3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); static constexpr int32_t POINTER_1_UP = AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); @@ -1941,6 +1943,77 @@ TEST_F(InputDispatcherTest, SplitWorksWhenNonTouchableWindowIsTouched) { window2->consumeMotionDown(); } +/** + * When splitting touch events the downTime should be adjusted such that the downTime corresponds + * to the event time of the first ACTION_DOWN sent to the particular window. + */ +TEST_F(InputDispatcherTest, SplitTouchesSendCorrectActionDownTime) { + std::shared_ptr application = std::make_shared(); + sp window1 = + new FakeWindowHandle(application, mDispatcher, "Window1", DISPLAY_ID); + window1->setTouchableRegion(Region{{0, 0, 100, 100}}); + sp window2 = + new FakeWindowHandle(application, mDispatcher, "Window2", DISPLAY_ID); + window2->setTouchableRegion(Region{{100, 0, 200, 100}}); + + mDispatcher->setInputWindows({{DISPLAY_ID, {window1, window2}}}); + + NotifyMotionArgs args; + // Touch down on the first window + mDispatcher->notifyMotion(&(args = generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}}))); + + mDispatcher->waitForIdle(); + InputEvent* inputEvent1 = window1->consume(); + window2->assertNoEvents(); + MotionEvent& motionEvent1 = static_cast(*inputEvent1); + nsecs_t downTimeForWindow1 = motionEvent1.getDownTime(); + ASSERT_EQ(motionEvent1.getDownTime(), motionEvent1.getEventTime()); + + // Now touch down on the window with another pointer + mDispatcher->notifyMotion(&(args = generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}}))); + mDispatcher->waitForIdle(); + InputEvent* inputEvent2 = window2->consume(); + MotionEvent& motionEvent2 = static_cast(*inputEvent2); + nsecs_t downTimeForWindow2 = motionEvent2.getDownTime(); + ASSERT_NE(downTimeForWindow1, downTimeForWindow2); + ASSERT_EQ(motionEvent2.getDownTime(), motionEvent2.getEventTime()); + + // Now move the pointer on the second window + mDispatcher->notifyMotion( + &(args = generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{50, 50}, {151, 51}}))); + mDispatcher->waitForIdle(); + InputEvent* inputEvent3 = window2->consume(); + MotionEvent& motionEvent3 = static_cast(*inputEvent3); + ASSERT_EQ(motionEvent3.getDownTime(), downTimeForWindow2); + + // Now add new touch down on the second window + mDispatcher->notifyMotion( + &(args = generateTouchArgs(POINTER_2_DOWN, {{50, 50}, {151, 51}, {150, 50}}))); + mDispatcher->waitForIdle(); + InputEvent* inputEvent4 = window2->consume(); + MotionEvent& motionEvent4 = static_cast(*inputEvent4); + ASSERT_EQ(motionEvent4.getDownTime(), downTimeForWindow2); + + // TODO(b/232530217): do not send the unnecessary MOVE event and delete the next line + window1->consumeMotionMove(); + window1->assertNoEvents(); + + // Now move the pointer on the first window + mDispatcher->notifyMotion( + &(args = generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{51, 51}, {151, 51}}))); + mDispatcher->waitForIdle(); + InputEvent* inputEvent5 = window1->consume(); + MotionEvent& motionEvent5 = static_cast(*inputEvent5); + ASSERT_EQ(motionEvent5.getDownTime(), downTimeForWindow1); + + mDispatcher->notifyMotion(&( + args = generateTouchArgs(POINTER_3_DOWN, {{51, 51}, {151, 51}, {150, 50}, {50, 50}}))); + mDispatcher->waitForIdle(); + InputEvent* inputEvent6 = window1->consume(); + MotionEvent& motionEvent6 = static_cast(*inputEvent6); + ASSERT_EQ(motionEvent6.getDownTime(), downTimeForWindow1); +} + TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { std::shared_ptr application = std::make_shared(); sp windowLeft = -- GitLab From 096227e9a48c96ed309ec369d8cbeb40015618f2 Mon Sep 17 00:00:00 2001 From: Andy Labrada Date: Wed, 15 Jun 2022 16:58:11 +0000 Subject: [PATCH 0112/1310] SurfaceFlinger: Remove usage of window types to implement policy Native side for a new API, setFrameRateDefault, where policy implementation logic is made using setFrameRateDefault instead of checking window types. To test the implementation of this API, SurfaceFlinger property use_content_detection_for_refresh_rate should be set to 1. Bug: 192291754 Test: atest LayerHistoryTest Test: added logs and verified status bar window gets no vote, wallpaper gets min vote and other windows get heuristic vote when use_content_detection_for_refresh_rate is set to 1 Change-Id: I736ac1bd82644b1fd8164f3be33f086934d27487 --- libs/gui/LayerState.cpp | 7 +++ libs/gui/SurfaceComposerClient.cpp | 13 +++++ libs/gui/include/gui/LayerState.h | 5 +- libs/gui/include/gui/SurfaceComposerClient.h | 3 ++ libs/nativewindow/include/system/window.h | 5 ++ services/surfaceflinger/Layer.cpp | 16 +++++++ services/surfaceflinger/Layer.h | 7 +++ .../surfaceflinger/Scheduler/LayerHistory.cpp | 36 +++++++++++++- .../surfaceflinger/Scheduler/LayerHistory.h | 6 ++- services/surfaceflinger/Scheduler/LayerInfo.h | 2 + .../surfaceflinger/Scheduler/Scheduler.cpp | 21 +++----- services/surfaceflinger/Scheduler/Scheduler.h | 1 + services/surfaceflinger/SurfaceFlinger.cpp | 8 ++++ .../tests/unittests/LayerHistoryTest.cpp | 48 +++++++++++++++++++ .../tests/unittests/mock/MockLayer.h | 7 ++- 15 files changed, 166 insertions(+), 19 deletions(-) diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 7f0f6381e1..241c82f919 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -63,6 +63,7 @@ layer_state_t::layer_state_t() frameRate(0.0f), frameRateCompatibility(ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT), changeFrameRateStrategy(ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS), + defaultFrameRateCompatibility(ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT), fixedTransformHint(ui::Transform::ROT_INVALID), autoRefresh(false), isTrustedOverlay(false), @@ -137,6 +138,7 @@ status_t layer_state_t::write(Parcel& output) const SAFE_PARCEL(output.writeFloat, frameRate); SAFE_PARCEL(output.writeByte, frameRateCompatibility); SAFE_PARCEL(output.writeByte, changeFrameRateStrategy); + SAFE_PARCEL(output.writeByte, defaultFrameRateCompatibility); SAFE_PARCEL(output.writeUint32, fixedTransformHint); SAFE_PARCEL(output.writeBool, autoRefresh); SAFE_PARCEL(output.writeBool, dimmingEnabled); @@ -257,6 +259,7 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(input.readFloat, &frameRate); SAFE_PARCEL(input.readByte, &frameRateCompatibility); SAFE_PARCEL(input.readByte, &changeFrameRateStrategy); + SAFE_PARCEL(input.readByte, &defaultFrameRateCompatibility); SAFE_PARCEL(input.readUint32, &tmpUint32); fixedTransformHint = static_cast(tmpUint32); SAFE_PARCEL(input.readBool, &autoRefresh); @@ -573,6 +576,10 @@ void layer_state_t::merge(const layer_state_t& other) { borderWidth = other.borderWidth; borderColor = other.borderColor; } + if (other.what & eDefaultFrameRateCompatibilityChanged) { + what |= eDefaultFrameRateCompatibilityChanged; + defaultFrameRateCompatibility = other.defaultFrameRateCompatibility; + } if (other.what & eFrameRateSelectionPriority) { what |= eFrameRateSelectionPriority; frameRateSelectionPriority = other.frameRateSelectionPriority; diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 7191de8f7f..c459b3aa55 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -1841,6 +1841,19 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFrame return *this; } +SurfaceComposerClient::Transaction& +SurfaceComposerClient::Transaction::setDefaultFrameRateCompatibility(const sp& sc, + int8_t compatibility) { + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + s->what |= layer_state_t::eDefaultFrameRateCompatibilityChanged; + s->defaultFrameRateCompatibility = compatibility; + return *this; +} + SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFixedTransformHint( const sp& sc, int32_t fixedTransformHint) { layer_state_t* s = getLayerState(sc); diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 37a159512f..d9a4ae52d1 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -165,7 +165,7 @@ struct layer_state_t { eTransformToDisplayInverseChanged = 0x00080000, eCropChanged = 0x00100000, eBufferChanged = 0x00200000, - /* unused 0x00400000, */ + eDefaultFrameRateCompatibilityChanged = 0x00400000, eDataspaceChanged = 0x00800000, eHdrMetadataChanged = 0x01000000, eSurfaceDamageRegionChanged = 0x02000000, @@ -275,6 +275,9 @@ struct layer_state_t { int8_t frameRateCompatibility; int8_t changeFrameRateStrategy; + // Default frame rate compatibility used to set the layer refresh rate votetype. + int8_t defaultFrameRateCompatibility; + // Set by window manager indicating the layer and all its children are // in a different orientation than the display. The hint suggests that // the graphic producers should receive a transform hint as if the diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 9f036a6fb1..0dc521935c 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -583,6 +583,9 @@ public: Transaction& setFrameRate(const sp& sc, float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy); + Transaction& setDefaultFrameRateCompatibility(const sp& sc, + int8_t compatibility); + // Set by window manager indicating the layer and all its children are // in a different orientation than the display. The hint suggests that // the graphic producers should receive a transform hint as if the diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h index a54af1fa62..79f49e1786 100644 --- a/libs/nativewindow/include/system/window.h +++ b/libs/nativewindow/include/system/window.h @@ -1034,6 +1034,11 @@ enum { * This surface is ignored while choosing the refresh rate. */ ANATIVEWINDOW_FRAME_RATE_NO_VOTE, + + /** + * This surface will vote for the minimum refresh rate. + */ + ANATIVEWINDOW_FRAME_RATE_MIN }; static inline int native_window_set_frame_rate(struct ANativeWindow* window, float frameRate, diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 80df3996ce..6e9dad1131 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -148,6 +148,7 @@ Layer::Layer(const LayerCreationArgs& args) mDrawingState.isTrustedOverlay = false; mDrawingState.dropInputMode = gui::DropInputMode::NONE; mDrawingState.dimmingEnabled = true; + mDrawingState.defaultFrameRateCompatibility = FrameRateCompatibility::Default; if (args.flags & ISurfaceComposerClient::eNoColorFill) { // Set an invalid color so there is no color fill. @@ -1101,6 +1102,19 @@ int32_t Layer::getFrameRateSelectionPriority() const { return Layer::PRIORITY_UNSET; } +bool Layer::setDefaultFrameRateCompatibility(FrameRateCompatibility compatibility) { + if (mDrawingState.defaultFrameRateCompatibility == compatibility) return false; + mDrawingState.defaultFrameRateCompatibility = compatibility; + mDrawingState.modified = true; + mFlinger->mScheduler->setDefaultFrameRateCompatibility(this); + setTransactionFlags(eTransactionNeeded); + return true; +} + +scheduler::LayerInfo::FrameRateCompatibility Layer::getDefaultFrameRateCompatibility() const { + return mDrawingState.defaultFrameRateCompatibility; +} + bool Layer::isLayerFocusedBasedOnPriority(int32_t priority) { return priority == PRIORITY_FOCUSED_WITH_MODE || priority == PRIORITY_FOCUSED_WITHOUT_MODE; }; @@ -2643,6 +2657,8 @@ Layer::FrameRateCompatibility Layer::FrameRate::convertCompatibility(int8_t comp return FrameRateCompatibility::ExactOrMultiple; case ANATIVEWINDOW_FRAME_RATE_EXACT: return FrameRateCompatibility::Exact; + case ANATIVEWINDOW_FRAME_RATE_MIN: + return FrameRateCompatibility::Min; case ANATIVEWINDOW_FRAME_RATE_NO_VOTE: return FrameRateCompatibility::NoVote; default: diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 911ab78956..fb9b663d8a 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -235,6 +235,9 @@ public: // Priority of the layer assigned by Window Manager. int32_t frameRateSelectionPriority; + // Default frame rate compatibility used to set the layer refresh rate votetype. + FrameRateCompatibility defaultFrameRateCompatibility; + FrameRate frameRate; // The combined frame rate of parents / children of this layer @@ -438,6 +441,7 @@ public: virtual bool setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace); virtual bool setColorSpaceAgnostic(const bool agnostic); virtual bool setDimmingEnabled(const bool dimmingEnabled); + virtual bool setDefaultFrameRateCompatibility(FrameRateCompatibility compatibility); virtual bool setFrameRateSelectionPriority(int32_t priority); virtual bool setFixedTransformHint(ui::Transform::RotationFlags fixedTransformHint); virtual void setAutoRefresh(bool /* autoRefresh */) {} @@ -446,6 +450,9 @@ public: // If the variable is not set on the layer, it traverses up the tree to inherit the frame // rate priority from its parent. virtual int32_t getFrameRateSelectionPriority() const; + // + virtual FrameRateCompatibility getDefaultFrameRateCompatibility() const; + // virtual ui::Dataspace getDataSpace() const { return ui::Dataspace::UNKNOWN; } virtual sp getCompositionEngineLayerFE() const; diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp index 5f64efa3d1..ae111c3d45 100644 --- a/services/surfaceflinger/Scheduler/LayerHistory.cpp +++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp @@ -72,6 +72,20 @@ void trace(const LayerInfo& info, LayerHistory::LayerVoteType type, int fps) { ALOGD("%s: %s @ %d Hz", __FUNCTION__, info.getName().c_str(), fps); } + +LayerHistory::LayerVoteType getVoteType(LayerInfo::FrameRateCompatibility compatibility, + bool contentDetectionEnabled) { + LayerHistory::LayerVoteType voteType; + if (!contentDetectionEnabled || compatibility == LayerInfo::FrameRateCompatibility::NoVote) { + voteType = LayerHistory::LayerVoteType::NoVote; + } else if (compatibility == LayerInfo::FrameRateCompatibility::Min) { + voteType = LayerHistory::LayerVoteType::Min; + } else { + voteType = LayerHistory::LayerVoteType::Heuristic; + } + return voteType; +} + } // namespace LayerHistory::LayerHistory() @@ -81,10 +95,12 @@ LayerHistory::LayerHistory() LayerHistory::~LayerHistory() = default; -void LayerHistory::registerLayer(Layer* layer, LayerVoteType type) { +void LayerHistory::registerLayer(Layer* layer, bool contentDetectionEnabled) { std::lock_guard lock(mLock); LOG_ALWAYS_FATAL_IF(findLayer(layer->getSequence()).first != LayerStatus::NotFound, "%s already registered", layer->getName().c_str()); + LayerVoteType type = + getVoteType(layer->getDefaultFrameRateCompatibility(), contentDetectionEnabled); auto info = std::make_unique(layer->getName(), layer->getOwnerUid(), type); // The layer can be placed on either map, it is assumed that partitionLayers() will be called @@ -132,6 +148,22 @@ void LayerHistory::record(Layer* layer, nsecs_t presentTime, nsecs_t now, } } +void LayerHistory::setDefaultFrameRateCompatibility(Layer* layer, bool contentDetectionEnabled) { + std::lock_guard lock(mLock); + auto id = layer->getSequence(); + + auto [found, layerPair] = findLayer(id); + if (found == LayerStatus::NotFound) { + // Offscreen layer + ALOGV("%s: %s not registered", __func__, layer->getName().c_str()); + return; + } + + const auto& info = layerPair->second; + info->setDefaultLayerVote( + getVoteType(layer->getDefaultFrameRateCompatibility(), contentDetectionEnabled)); +} + auto LayerHistory::summarize(const RefreshRateConfigs& configs, nsecs_t now) -> Summary { Summary summary; @@ -203,6 +235,8 @@ void LayerHistory::partitionLayers(nsecs_t now) { switch (frameRate.type) { case Layer::FrameRateCompatibility::Default: return LayerVoteType::ExplicitDefault; + case Layer::FrameRateCompatibility::Min: + return LayerVoteType::Min; case Layer::FrameRateCompatibility::ExactOrMultiple: return LayerVoteType::ExplicitExactOrMultiple; case Layer::FrameRateCompatibility::NoVote: diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h index 7b6096f7f3..12bec8dfc1 100644 --- a/services/surfaceflinger/Scheduler/LayerHistory.h +++ b/services/surfaceflinger/Scheduler/LayerHistory.h @@ -45,7 +45,7 @@ public: ~LayerHistory(); // Layers are unregistered when the weak reference expires. - void registerLayer(Layer*, LayerVoteType type); + void registerLayer(Layer*, bool contentDetectionEnabled); // Sets the display size. Client is responsible for synchronization. void setDisplayArea(uint32_t displayArea) { mDisplayArea = displayArea; } @@ -63,6 +63,10 @@ public: // Marks the layer as active, and records the given state to its history. void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType); + // Updates the default frame rate compatibility which takes effect when the app + // does not set a preference for refresh rate. + void setDefaultFrameRateCompatibility(Layer*, bool contentDetectionEnabled); + using Summary = std::vector; // Rebuilds sets of active/inactive layers, and accumulates stats for active layers. diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h index 8a3b0b97e4..28cb24a64f 100644 --- a/services/surfaceflinger/Scheduler/LayerInfo.h +++ b/services/surfaceflinger/Scheduler/LayerInfo.h @@ -74,6 +74,8 @@ public: enum class FrameRateCompatibility { Default, // Layer didn't specify any specific handling strategy + Min, // Layer needs the minimum frame rate. + Exact, // Layer needs the exact frame rate. ExactOrMultiple, // Layer needs the exact frame rate (or a multiple of it) to present the diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index bbf46678d5..3181a7f9c7 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -497,24 +497,10 @@ void Scheduler::addPresentFence(std::shared_ptr fence) { } void Scheduler::registerLayer(Layer* layer) { - using WindowType = gui::WindowInfo::Type; - - scheduler::LayerHistory::LayerVoteType voteType; - - if (!mFeatures.test(Feature::kContentDetection) || - layer->getWindowType() == WindowType::STATUS_BAR) { - voteType = scheduler::LayerHistory::LayerVoteType::NoVote; - } else if (layer->getWindowType() == WindowType::WALLPAPER) { - // Running Wallpaper at Min is considered as part of content detection. - voteType = scheduler::LayerHistory::LayerVoteType::Min; - } else { - voteType = scheduler::LayerHistory::LayerVoteType::Heuristic; - } - // If the content detection feature is off, we still keep the layer history, // since we use it for other features (like Frame Rate API), so layers // still need to be registered. - mLayerHistory.registerLayer(layer, voteType); + mLayerHistory.registerLayer(layer, mFeatures.test(Feature::kContentDetection)); } void Scheduler::deregisterLayer(Layer* layer) { @@ -535,6 +521,11 @@ void Scheduler::setModeChangePending(bool pending) { mLayerHistory.setModeChangePending(pending); } +void Scheduler::setDefaultFrameRateCompatibility(Layer* layer) { + mLayerHistory.setDefaultFrameRateCompatibility(layer, + mFeatures.test(Feature::kContentDetection)); +} + void Scheduler::chooseRefreshRateForContent() { const auto configs = holdRefreshRateConfigs(); if (!configs->canSwitch()) return; diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 587a773433..7f76d1edc1 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -178,6 +178,7 @@ public: void recordLayerHistory(Layer*, nsecs_t presentTime, LayerHistory::LayerUpdateType updateType) EXCLUDES(mRefreshRateConfigsLock); void setModeChangePending(bool pending); + void setDefaultFrameRateCompatibility(Layer*); void deregisterLayer(Layer*); // Detects content using layer history, and selects a matching refresh rate. diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index aba7998b3c..c720c76f5f 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4563,6 +4563,14 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime if (what & layer_state_t::eShadowRadiusChanged) { if (layer->setShadowRadius(s.shadowRadius)) flags |= eTraversalNeeded; } + if (what & layer_state_t::eDefaultFrameRateCompatibilityChanged) { + const auto compatibility = + Layer::FrameRate::convertCompatibility(s.defaultFrameRateCompatibility); + + if (layer->setDefaultFrameRateCompatibility(compatibility)) { + flags |= eTraversalNeeded; + } + } if (what & layer_state_t::eFrameRateSelectionPriority) { if (layer->setFrameRateSelectionPriority(s.frameRateSelectionPriority)) { flags |= eTraversalNeeded; diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp index 17511cd615..972198cbdb 100644 --- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp @@ -141,6 +141,54 @@ protected: namespace { +TEST_F(LayerHistoryTest, singleLayerNoVoteDefaultCompatibility) { + const auto layer = createLayer(); + EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true)); + EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate())); + EXPECT_CALL(*layer, getDefaultFrameRateCompatibility()) + .WillOnce(Return(LayerInfo::FrameRateCompatibility::NoVote)); + + EXPECT_EQ(1, layerCount()); + EXPECT_EQ(0, activeLayerCount()); + + nsecs_t time = systemTime(); + + // No layers returned if no layers are active. + EXPECT_TRUE(summarizeLayerHistory(time).empty()); + EXPECT_EQ(0, activeLayerCount()); + + history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer); + history().setDefaultFrameRateCompatibility(layer.get(), true /* contentDetectionEnabled */); + + EXPECT_TRUE(summarizeLayerHistory(time).empty()); + EXPECT_EQ(1, activeLayerCount()); +} + +TEST_F(LayerHistoryTest, singleLayerMinVoteDefaultCompatibility) { + const auto layer = createLayer(); + EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true)); + EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate())); + EXPECT_CALL(*layer, getDefaultFrameRateCompatibility()) + .WillOnce(Return(LayerInfo::FrameRateCompatibility::Min)); + + EXPECT_EQ(1, layerCount()); + EXPECT_EQ(0, activeLayerCount()); + + nsecs_t time = systemTime(); + + EXPECT_TRUE(summarizeLayerHistory(time).empty()); + EXPECT_EQ(0, activeLayerCount()); + + history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer); + history().setDefaultFrameRateCompatibility(layer.get(), true /* contentDetectionEnabled */); + + auto summary = summarizeLayerHistory(time); + ASSERT_EQ(1, summarizeLayerHistory(time).size()); + + EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote); + EXPECT_EQ(1, activeLayerCount()); +} + TEST_F(LayerHistoryTest, oneLayer) { const auto layer = createLayer(); EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true)); diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h index 0840a2f77c..d086d79834 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockLayer.h +++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h @@ -25,7 +25,10 @@ namespace android::mock { class MockLayer : public Layer { public: MockLayer(SurfaceFlinger* flinger, std::string name) - : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) {} + : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) { + EXPECT_CALL(*this, getDefaultFrameRateCompatibility()) + .WillOnce(testing::Return(scheduler::LayerInfo::FrameRateCompatibility::Default)); + } explicit MockLayer(SurfaceFlinger* flinger) : MockLayer(flinger, "TestLayer") {} MOCK_CONST_METHOD0(getType, const char*()); @@ -33,6 +36,8 @@ public: MOCK_CONST_METHOD0(isVisible, bool()); MOCK_METHOD0(createClone, sp()); MOCK_CONST_METHOD0(getFrameRateForLayerTree, FrameRate()); + MOCK_CONST_METHOD0(getDefaultFrameRateCompatibility, + scheduler::LayerInfo::FrameRateCompatibility()); MOCK_CONST_METHOD0(getOwnerUid, uid_t()); MOCK_CONST_METHOD0(getDataSpace, ui::Dataspace()); }; -- GitLab From 78d7fb36a7f5408c6e7e7ad0e64de7ff4d557ec9 Mon Sep 17 00:00:00 2001 From: Jason Macnak Date: Mon, 13 Jun 2022 10:45:07 -0700 Subject: [PATCH 0113/1310] Add ANativeWindow_getBuffersDefaultDataSpace() This is useful for ANGLE (OpenGL ES implementation layered on top of Vulkan) which needs to the know the default dataspace to pass along to Vulkan when creating a surface/swapchain for an ANativeWindow. See http://aosp/q/topic:cuttlefish-angle-colorspace Bug: b/229286407 Test: lunch aosp_cf_x86_64_phone-userdebug && m && cts -m CtsMediaCodecTestCases Change-Id: Ie7a0c4b4f409f730f5c70bd88485644cfa03f10d --- libs/nativewindow/ANativeWindow.cpp | 7 +++++++ libs/nativewindow/include/android/native_window.h | 10 ++++++++++ libs/nativewindow/libnativewindow.map.txt | 1 + 3 files changed, 18 insertions(+) diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp index ec11b81fb7..731f989658 100644 --- a/libs/nativewindow/ANativeWindow.cpp +++ b/libs/nativewindow/ANativeWindow.cpp @@ -193,6 +193,13 @@ int32_t ANativeWindow_getBuffersDataSpace(ANativeWindow* window) { return query(window, NATIVE_WINDOW_DATASPACE); } +int32_t ANativeWindow_getBuffersDefaultDataSpace(ANativeWindow* window) { + if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) { + return -EINVAL; + } + return query(window, NATIVE_WINDOW_DEFAULT_DATASPACE); +} + int32_t ANativeWindow_setFrameRate(ANativeWindow* window, float frameRate, int8_t compatibility) { return ANativeWindow_setFrameRateWithChangeStrategy(window, frameRate, compatibility, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS); diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h index f0e1c4d749..20f4b52caa 100644 --- a/libs/nativewindow/include/android/native_window.h +++ b/libs/nativewindow/include/android/native_window.h @@ -227,6 +227,16 @@ int32_t ANativeWindow_setBuffersDataSpace(ANativeWindow* window, int32_t dataSpa */ int32_t ANativeWindow_getBuffersDataSpace(ANativeWindow* window) __INTRODUCED_IN(28); +/** + * Get the default dataspace of the buffers in window as set by the consumer. + * + * Available since API level 34. + * + * \return the dataspace of buffers in window, ADATASPACE_UNKNOWN is returned if + * dataspace is unknown, or -EINVAL if window is invalid. + */ +int32_t ANativeWindow_getBuffersDefaultDataSpace(ANativeWindow* window) __INTRODUCED_IN(34); + /** Compatibility value for ANativeWindow_setFrameRate. */ enum ANativeWindow_FrameRateCompatibility { /** diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt index 988132cd1c..f8c9e4a55f 100644 --- a/libs/nativewindow/libnativewindow.map.txt +++ b/libs/nativewindow/libnativewindow.map.txt @@ -21,6 +21,7 @@ LIBNATIVEWINDOW { ANativeWindow_cancelBuffer; # llndk ANativeWindow_dequeueBuffer; # llndk ANativeWindow_getBuffersDataSpace; # introduced=28 + ANativeWindow_getBuffersDefaultDataSpace; # introduced=34 ANativeWindow_getFormat; ANativeWindow_getHeight; ANativeWindow_getLastDequeueDuration; # apex # introduced=30 -- GitLab From 22ca2c4a9e74736d2dccf9145c975a63e2c04a83 Mon Sep 17 00:00:00 2001 From: Yiwei Zhang Date: Thu, 7 Jul 2022 07:00:16 +0000 Subject: [PATCH 0114/1310] Fix bufferNeedsFiltering to compare in the same coordination Bug: 238246763 Test: SurfaceViewTests#testMovingWhiteSurfaceView Change-Id: Idf826fe99791674ff1e2f6cf70c6f44f35190dc6 --- services/surfaceflinger/BufferStateLayer.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp index 3875f151cb..d88d7c99e9 100644 --- a/services/surfaceflinger/BufferStateLayer.cpp +++ b/services/surfaceflinger/BufferStateLayer.cpp @@ -853,7 +853,15 @@ bool BufferStateLayer::bufferNeedsFiltering() const { } const Rect layerSize{getBounds()}; - return layerSize.width() != bufferWidth || layerSize.height() != bufferHeight; + int32_t layerWidth = layerSize.getWidth(); + int32_t layerHeight = layerSize.getHeight(); + + // Align the layer orientation with the buffer before comparism + if (mTransformHint & ui::Transform::ROT_90) { + std::swap(layerWidth, layerHeight); + } + + return layerWidth != bufferWidth || layerHeight != bufferHeight; } void BufferStateLayer::decrementPendingBufferCount() { -- GitLab From 4f797ab12b9844d62f904c7b67ad8d8eb6121490 Mon Sep 17 00:00:00 2001 From: Rob Carr Date: Thu, 7 Jul 2022 18:29:22 +0000 Subject: [PATCH 0115/1310] BLASTBufferQueueTest: Fix leak of BBQ and SC A newly added test results in a dangling reference which causes a SurfaceControl to not be cleaned up. This occludes the screen and interferes with further tests. While BLASTBufferQueue is expectin a complete callback, a manually incremented reference is kept. The sync transaction in this test is not applied (since it's just used to verify some release on buffer overwrite semantics). This results in this manually incremented reference dangling. We can fix this easily by applying the transaction after verifying the test success. Bug: 237010140 Test: Existing tests pass Change-Id: I30b48111ab236acda5cce3f240149ccaa353b89b --- libs/gui/tests/BLASTBufferQueue_test.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp index a071eb994d..3e563b2aa2 100644 --- a/libs/gui/tests/BLASTBufferQueue_test.cpp +++ b/libs/gui/tests/BLASTBufferQueue_test.cpp @@ -1148,6 +1148,7 @@ TEST_F(BLASTBufferQueueTest, SyncNextTransactionDropBuffer) { ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults)); ASSERT_NO_FATAL_FAILURE( checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight})); + sync.apply(); } // This test will currently fail because the old surfacecontrol will steal the last presented buffer -- GitLab From fb3beed5c6d2561bf47421420246df33f111d661 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Thu, 7 Jul 2022 07:34:38 -0700 Subject: [PATCH 0116/1310] SF: Do not block the main thread on screenshots The destructor of the future returned by std::async blocks until the async operation completes, which defeats the purpose of spinning up a thread to wait on the RenderEngine future. Instead, defer the wait back to the Binder thread. Bug: 232436803 Test: Perfetto Change-Id: I7260002741a0174d8960894d490079757dc4bb5b --- services/surfaceflinger/SurfaceFlinger.cpp | 25 ++++++++++------------ 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 83e8d3f713..d313406044 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -6624,24 +6624,21 @@ ftl::SharedFuture SurfaceFlinger::captureScreenCommon( }); if (captureListener) { - // TODO: The future returned by std::async blocks the main thread. Return a chain of - // futures to the Binder thread instead. - std::async([=]() mutable { - ATRACE_NAME("captureListener is nonnull!"); - auto fenceResult = renderFuture.get(); - // TODO(b/232535621): Change ScreenCaptureResults to store a FenceResult. - captureResults.result = fenceStatus(fenceResult); - captureResults.fence = std::move(fenceResult).value_or(Fence::NO_FENCE); - captureListener->onScreenCaptureCompleted(captureResults); - }); + // Defer blocking on renderFuture back to the Binder thread. + return ftl::Future(std::move(renderFuture)) + .then([captureListener, captureResults = std::move(captureResults)]( + FenceResult fenceResult) mutable -> FenceResult { + // TODO(b/232535621): Change ScreenCaptureResults to store a FenceResult. + captureResults.result = fenceStatus(fenceResult); + captureResults.fence = std::move(fenceResult).value_or(Fence::NO_FENCE); + captureListener->onScreenCaptureCompleted(captureResults); + return base::unexpected(NO_ERROR); + }) + .share(); } return renderFuture; }); - if (captureListener) { - return ftl::yield(base::unexpected(NO_ERROR)).share(); - } - // Flatten nested futures. auto chain = ftl::Future(std::move(future)).then([](ftl::SharedFuture future) { return future; -- GitLab From d7f43139a3a283a8ca53293d8a7f4b9e8d30ddfc Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 8 Jul 2022 10:52:26 -0700 Subject: [PATCH 0117/1310] Add NumberPickerTest to input presubmit This is a useful integration test for input, because it relies on the VelocityTracker. Let's add it to input presubmit to avoid future regressions. Bug: 200900433 Test: cd frameworks/native/services/inputflinger && atest Change-Id: Idc9a855d9512b418df3f80a10f05873476f62805 --- services/inputflinger/TEST_MAPPING | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING index 318940b636..f673a287c4 100644 --- a/services/inputflinger/TEST_MAPPING +++ b/services/inputflinger/TEST_MAPPING @@ -44,6 +44,14 @@ } ] }, + { + "name": "CtsWidgetTestCases", + "options": [ + { + "include-filter": "android.widget.cts.NumberPickerTest" + } + ] + }, { "name": "FrameworksCoreTests", "options": [ @@ -123,6 +131,14 @@ } ] }, + { + "name": "CtsWidgetTestCases", + "options": [ + { + "include-filter": "android.widget.cts.NumberPickerTest" + } + ] + }, { "name": "FrameworksCoreTests", "options": [ -- GitLab From 6bb90f026ae8bdb2131abd952b479c014a726c00 Mon Sep 17 00:00:00 2001 From: Devendra Singhi Date: Thu, 2 Dec 2021 09:42:35 +0530 Subject: [PATCH 0118/1310] Add libgui_surfaceComposer_fuzzer Test: ./libgui_surfaceComposer_fuzzer Bug: 202910330 Change-Id: I2cb389295d7004614c9928a7cd752b095d94bff4 --- libs/gui/fuzzer/Android.bp | 78 +++++ libs/gui/fuzzer/README.md | 42 +++ libs/gui/fuzzer/libgui_fuzzer_utils.h | 311 ++++++++++++++++++ .../fuzzer/libgui_surfaceComposer_fuzzer.cpp | 41 +++ 4 files changed, 472 insertions(+) create mode 100644 libs/gui/fuzzer/Android.bp create mode 100644 libs/gui/fuzzer/README.md create mode 100644 libs/gui/fuzzer/libgui_fuzzer_utils.h create mode 100644 libs/gui/fuzzer/libgui_surfaceComposer_fuzzer.cpp diff --git a/libs/gui/fuzzer/Android.bp b/libs/gui/fuzzer/Android.bp new file mode 100644 index 0000000000..952b6a9306 --- /dev/null +++ b/libs/gui/fuzzer/Android.bp @@ -0,0 +1,78 @@ +/* + * Copyright 2021 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. + */ +cc_defaults { + name: "libgui_fuzzer_defaults", + static_libs: [ + "android.hidl.token@1.0-utils", + "libbinder_random_parcel", + "libgui_aidl_static", + "libgui_window_info_static", + "libpdx", + "libgmock", + "libgui_mocks", + "libgmock_ndk", + "libgmock_main", + "libgtest_ndk_c++", + "libgmock_main_ndk", + "librenderengine_mocks", + "perfetto_trace_protos", + "libcompositionengine_mocks", + "perfetto_trace_protos", + ], + shared_libs: [ + "android.hardware.configstore@1.0", + "android.hardware.configstore-utils", + "android.hardware.graphics.bufferqueue@1.0", + "android.hardware.graphics.bufferqueue@2.0", + "android.hardware.power-V2-cpp", + "android.hidl.token@1.0", + "libSurfaceFlingerProp", + "libgui", + "libbase", + "liblog", + "libEGL", + "libGLESv2", + "libbinder", + "libcutils", + "libhidlbase", + "libinput", + "libui", + "libutils", + "libnativewindow", + "libvndksupport", + "libbufferhubqueue", + ], + header_libs: [ + "libdvr_headers", + "libui_fuzzableDataspaces_headers", + ], + fuzz_config: { + cc: [ + "android-media-fuzzing-reports@google.com", + ], + componentid: 155276, + }, +} + +cc_fuzz { + name: "libgui_surfaceComposer_fuzzer", + srcs: [ + "libgui_surfaceComposer_fuzzer.cpp", + ], + defaults: [ + "libgui_fuzzer_defaults", + ], +} diff --git a/libs/gui/fuzzer/README.md b/libs/gui/fuzzer/README.md new file mode 100644 index 0000000000..c9e8b653ee --- /dev/null +++ b/libs/gui/fuzzer/README.md @@ -0,0 +1,42 @@ +# Fuzzers for Libgui + +## Table of contents ++ [libgui_surfaceComposer_fuzzer](#SurfaceComposer) + +# Fuzzer for SurfaceComposer + +SurfaceComposer supports the following parameters: +1. SurfaceWidth (parameter name:`width`) +2. SurfaceHeight (parameter name:`height`) +3. TransactionStateFlags (parameter name:`flags`) +4. TransformHint (parameter name:`outTransformHint`) +5. SurfacePixelFormat (parameter name:`format`) +6. LayerId (parameter name:`outLayerId`) +7. SurfaceComposerTags (parameter name:`surfaceTag`) +8. PowerBoostID (parameter name:`boostId`) +9. VsyncSource (parameter name:`vsyncSource`) +10. EventRegistrationFlags (parameter name:`eventRegistration`) +11. FrameRateCompatibility (parameter name:`frameRateCompatibility`) +12. ChangeFrameRateStrategy (parameter name:`changeFrameRateStrategy`) +13. HdrTypes (parameter name:`hdrTypes`) + +| Parameter| Valid Values| Configured Value| +|------------- |-------------| ----- | +|`surfaceTag` | 0.`BnSurfaceComposer::BOOT_FINISHED`, 1.`BnSurfaceComposer::CREATE_CONNECTION`, 2.`BnSurfaceComposer::GET_STATIC_DISPLAY_INFO`, 3.`BnSurfaceComposer::CREATE_DISPLAY_EVENT_CONNECTION`, 4.`BnSurfaceComposer::CREATE_DISPLAY`, 5.`BnSurfaceComposer::DESTROY_DISPLAY`, 6.`BnSurfaceComposer::GET_PHYSICAL_DISPLAY_TOKEN`, 7.`BnSurfaceComposer::SET_TRANSACTION_STATE`, 8.`BnSurfaceComposer::AUTHENTICATE_SURFACE`, 9.`BnSurfaceComposer::GET_SUPPORTED_FRAME_TIMESTAMPS`, 10.`BnSurfaceComposer::GET_DISPLAY_STATE`, 11.`BnSurfaceComposer::CAPTURE_DISPLAY`, 12.`BnSurfaceComposer::CAPTURE_LAYERS`, 13.`BnSurfaceComposer::CLEAR_ANIMATION_FRAME_STATS`, 14.`BnSurfaceComposer::GET_ANIMATION_FRAME_STATS`, 15.`BnSurfaceComposer::SET_POWER_MODE`, 16.`BnSurfaceComposer::GET_DISPLAY_STATS`, 17.`BnSurfaceComposer::SET_ACTIVE_COLOR_MODE`, 18.`BnSurfaceComposer::ENABLE_VSYNC_INJECTIONS`, 19.`BnSurfaceComposer::INJECT_VSYNC`, 20.`BnSurfaceComposer::GET_LAYER_DEBUG_INFO`, 21.`BnSurfaceComposer::GET_COMPOSITION_PREFERENCE`, 22.`BnSurfaceComposer::GET_COLOR_MANAGEMENT`, 23.`BnSurfaceComposer::GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES`, 24.`BnSurfaceComposer::SET_DISPLAY_CONTENT_SAMPLING_ENABLED`, 25.`BnSurfaceComposer::GET_DISPLAYED_CONTENT_SAMPLE`, 26.`BnSurfaceComposer::GET_PROTECTED_CONTENT_SUPPORT`, 27.`BnSurfaceComposer::IS_WIDE_COLOR_DISPLAY`, 28.`BnSurfaceComposer::GET_DISPLAY_NATIVE_PRIMARIES`, 29.`BnSurfaceComposer::GET_PHYSICAL_DISPLAY_IDS`, 30.`BnSurfaceComposer::ADD_REGION_SAMPLING_LISTENER`, 31.`BnSurfaceComposer::REMOVE_REGION_SAMPLING_LISTENER`, 32.`BnSurfaceComposer::SET_DESIRED_DISPLAY_MODE_SPECS`, 33.`BnSurfaceComposer::GET_DESIRED_DISPLAY_MODE_SPECS`, 34.`BnSurfaceComposer::GET_DISPLAY_BRIGHTNESS_SUPPORT`, 35.`BnSurfaceComposer::SET_DISPLAY_BRIGHTNESS`, 36.`BnSurfaceComposer::CAPTURE_DISPLAY_BY_ID`, 37.`BnSurfaceComposer::NOTIFY_POWER_BOOST`, 38.`BnSurfaceComposer::SET_GLOBAL_SHADOW_SETTINGS`, 39.`BnSurfaceComposer::SET_AUTO_LOW_LATENCY_MODE`, 40.`BnSurfaceComposer::SET_GAME_CONTENT_TYPE`, 41.`BnSurfaceComposer::SET_FRAME_RATE`, 42.`BnSurfaceComposer::ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN`, 43.`BnSurfaceComposer::SET_FRAME_TIMELINE_INFO`, 44.`BnSurfaceComposer::ADD_TRANSACTION_TRACE_LISTENER`, 45.`BnSurfaceComposer::GET_GPU_CONTEXT_PRIORITY`, 46.`BnSurfaceComposer::GET_MAX_ACQUIRED_BUFFER_COUNT`, 47.`BnSurfaceComposer::GET_DYNAMIC_DISPLAY_INFO`, 48.`BnSurfaceComposer::ADD_FPS_LISTENER`, 49.`BnSurfaceComposer::REMOVE_FPS_LISTENER`, 50.`BnSurfaceComposer::OVERRIDE_HDR_TYPES`, 51.`BnSurfaceComposer::ADD_HDR_LAYER_INFO_LISTENER`, 52.`BnSurfaceComposer::REMOVE_HDR_LAYER_INFO_LISTENER`, 53.`BnSurfaceComposer::ON_PULL_ATOM`, 54.`BnSurfaceComposer::ADD_TUNNEL_MODE_ENABLED_LISTENER`, 55.`BnSurfaceComposer::REMOVE_TUNNEL_MODE_ENABLED_LISTENER` | Value obtained from FuzzedDataProvider| +|`boostId`| 0.`hardware::power::Boost::INTERACTION`, 1.`hardware::power::Boost::DISPLAY_UPDATE_IMMINENT`, 2.`hardware::power::Boost::ML_ACC`, 3.`hardware::power::Boost::AUDIO_LAUNCH`, 4.`hardware::power::Boost::CAMERA_LAUNCH`, 5.`hardware::power::Boost::CAMERA_SHOT` |Value obtained from FuzzedDataProvider| +|`vsyncSource`| 0.`ISurfaceComposer::eVsyncSourceApp`, 1.`ISurfaceComposer::eVsyncSourceSurfaceFlinger`, |Value obtained from FuzzedDataProvider| +|`eventRegistration`| 0.`ISurfaceComposer::EventRegistration::modeChanged`, 1.`ISurfaceComposer::EventRegistration::frameRateOverride` |Value obtained from FuzzedDataProvider| +|`frameRateCompatibility`| 0.`ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT`, 1.`ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE` |Value obtained from FuzzedDataProvider| +|`changeFrameRateStrategy`| 0.`ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS`, 1.`ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS` |Value obtained from FuzzedDataProvider| +|`hdrTypes`| 0.`ui::Hdr::DOLBY_VISION`, 1.`ui::Hdr::HDR10`, 2.`ui::Hdr::HLG`, 3.`ui::Hdr::HDR10_PLUS` |Value obtained from FuzzedDataProvider| + +#### Steps to run +1. Build the fuzzer +``` + $ mm -j$(nproc) libgui_surfaceComposer_fuzzer +``` +2. Run on device +``` + $ adb sync data + $ adb shell /data/fuzz/arm64/libgui_surfaceComposer_fuzzer/libgui_surfaceComposer_fuzzer +``` diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h new file mode 100644 index 0000000000..98e2ea3c79 --- /dev/null +++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h @@ -0,0 +1,311 @@ +/* + * Copyright 2021 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace android { + +constexpr uint32_t kOrientation[] = { + ui::Transform::ROT_0, ui::Transform::FLIP_H, ui::Transform::FLIP_V, + ui::Transform::ROT_90, ui::Transform::ROT_180, ui::Transform::ROT_270, +}; + +Rect getRect(FuzzedDataProvider* fdp) { + const int32_t left = fdp->ConsumeIntegral(); + const int32_t top = fdp->ConsumeIntegral(); + const int32_t right = fdp->ConsumeIntegral(); + const int32_t bottom = fdp->ConsumeIntegral(); + return Rect(left, top, right, bottom); +} + +gui::DisplayBrightness getBrightness(FuzzedDataProvider* fdp) { + static constexpr float kMinBrightness = 0; + static constexpr float kMaxBrightness = 1; + gui::DisplayBrightness brightness; + brightness.sdrWhitePoint = + fdp->ConsumeFloatingPointInRange(kMinBrightness, kMaxBrightness); + brightness.sdrWhitePointNits = + fdp->ConsumeFloatingPointInRange(kMinBrightness, kMaxBrightness); + brightness.displayBrightness = + fdp->ConsumeFloatingPointInRange(kMinBrightness, kMaxBrightness); + brightness.displayBrightnessNits = + fdp->ConsumeFloatingPointInRange(kMinBrightness, kMaxBrightness); + return brightness; +} + +class FakeBnSurfaceComposer : public gui::BnSurfaceComposer { +public: + MOCK_METHOD(binder::Status, bootFinished, (), (override)); + MOCK_METHOD(binder::Status, createDisplayEventConnection, + (gui::ISurfaceComposer::VsyncSource, gui::ISurfaceComposer::EventRegistration, + sp*), + (override)); + MOCK_METHOD(binder::Status, createConnection, (sp*), (override)); + MOCK_METHOD(binder::Status, createDisplay, (const std::string&, bool, sp*), + (override)); + MOCK_METHOD(binder::Status, destroyDisplay, (const sp&), (override)); + MOCK_METHOD(binder::Status, getPhysicalDisplayIds, (std::vector*), (override)); + MOCK_METHOD(binder::Status, getPrimaryPhysicalDisplayId, (int64_t*), (override)); + MOCK_METHOD(binder::Status, getPhysicalDisplayToken, (int64_t, sp*), (override)); + MOCK_METHOD(binder::Status, setPowerMode, (const sp&, int), (override)); + MOCK_METHOD(binder::Status, getSupportedFrameTimestamps, (std::vector*), + (override)); + MOCK_METHOD(binder::Status, getDisplayStats, (const sp&, gui::DisplayStatInfo*), + (override)); + MOCK_METHOD(binder::Status, getDisplayState, (const sp&, gui::DisplayState*), + (override)); + MOCK_METHOD(binder::Status, getStaticDisplayInfo, (const sp&, gui::StaticDisplayInfo*), + (override)); + MOCK_METHOD(binder::Status, getDynamicDisplayInfo, + (const sp&, gui::DynamicDisplayInfo*), (override)); + MOCK_METHOD(binder::Status, getDisplayNativePrimaries, + (const sp&, gui::DisplayPrimaries*), (override)); + MOCK_METHOD(binder::Status, setActiveColorMode, (const sp&, int), (override)); + MOCK_METHOD(binder::Status, setBootDisplayMode, (const sp&, int), (override)); + MOCK_METHOD(binder::Status, clearBootDisplayMode, (const sp&), (override)); + MOCK_METHOD(binder::Status, getBootDisplayModeSupport, (bool*), (override)); + MOCK_METHOD(binder::Status, setAutoLowLatencyMode, (const sp&, bool), (override)); + MOCK_METHOD(binder::Status, setGameContentType, (const sp&, bool), (override)); + MOCK_METHOD(binder::Status, captureDisplay, + (const DisplayCaptureArgs&, const sp&), (override)); + MOCK_METHOD(binder::Status, captureDisplayById, (int64_t, const sp&), + (override)); + MOCK_METHOD(binder::Status, captureLayers, + (const LayerCaptureArgs&, const sp&), (override)); + MOCK_METHOD(binder::Status, clearAnimationFrameStats, (), (override)); + MOCK_METHOD(binder::Status, getAnimationFrameStats, (gui::FrameStats*), (override)); + MOCK_METHOD(binder::Status, overrideHdrTypes, (const sp&, const std::vector&), + (override)); + MOCK_METHOD(binder::Status, onPullAtom, (int32_t, gui::PullAtomData*), (override)); + MOCK_METHOD(binder::Status, enableVSyncInjections, (bool), (override)); + MOCK_METHOD(binder::Status, injectVSync, (int64_t), (override)); + MOCK_METHOD(binder::Status, getLayerDebugInfo, (std::vector*), (override)); + MOCK_METHOD(binder::Status, getColorManagement, (bool*), (override)); + MOCK_METHOD(binder::Status, getCompositionPreference, (gui::CompositionPreference*), + (override)); + MOCK_METHOD(binder::Status, getDisplayedContentSamplingAttributes, + (const sp&, gui::ContentSamplingAttributes*), (override)); + MOCK_METHOD(binder::Status, setDisplayContentSamplingEnabled, + (const sp&, bool, int8_t, int64_t), (override)); + MOCK_METHOD(binder::Status, getDisplayedContentSample, + (const sp&, int64_t, int64_t, gui::DisplayedFrameStats*), (override)); + MOCK_METHOD(binder::Status, getProtectedContentSupport, (bool*), (override)); + MOCK_METHOD(binder::Status, isWideColorDisplay, (const sp&, bool*), (override)); + MOCK_METHOD(binder::Status, addRegionSamplingListener, + (const gui::ARect&, const sp&, const sp&), + (override)); + MOCK_METHOD(binder::Status, removeRegionSamplingListener, + (const sp&), (override)); + MOCK_METHOD(binder::Status, addFpsListener, (int32_t, const sp&), + (override)); + MOCK_METHOD(binder::Status, removeFpsListener, (const sp&), (override)); + MOCK_METHOD(binder::Status, addTunnelModeEnabledListener, + (const sp&), (override)); + MOCK_METHOD(binder::Status, removeTunnelModeEnabledListener, + (const sp&), (override)); + MOCK_METHOD(binder::Status, setDesiredDisplayModeSpecs, + (const sp&, int32_t, bool, float, float, float, + float appRequestRefreshRateMax), + (override)); + MOCK_METHOD(binder::Status, getDesiredDisplayModeSpecs, + (const sp&, gui::DisplayModeSpecs*), (override)); + MOCK_METHOD(binder::Status, getDisplayBrightnessSupport, (const sp&, bool*), + (override)); + MOCK_METHOD(binder::Status, setDisplayBrightness, + (const sp&, const gui::DisplayBrightness&), (override)); + MOCK_METHOD(binder::Status, addHdrLayerInfoListener, + (const sp&, const sp&), (override)); + MOCK_METHOD(binder::Status, removeHdrLayerInfoListener, + (const sp&, const sp&), (override)); + MOCK_METHOD(binder::Status, notifyPowerBoost, (int), (override)); + MOCK_METHOD(binder::Status, setGlobalShadowSettings, + (const gui::Color&, const gui::Color&, float, float, float), (override)); + MOCK_METHOD(binder::Status, getDisplayDecorationSupport, + (const sp&, std::optional*), (override)); + MOCK_METHOD(binder::Status, setOverrideFrameRate, (int32_t, float), (override)); + MOCK_METHOD(binder::Status, addTransactionTraceListener, + (const sp&), (override)); + MOCK_METHOD(binder::Status, getGpuContextPriority, (int32_t*), (override)); + MOCK_METHOD(binder::Status, getMaxAcquiredBufferCount, (int32_t*), (override)); + MOCK_METHOD(binder::Status, addWindowInfosListener, (const sp&), + (override)); + MOCK_METHOD(binder::Status, removeWindowInfosListener, (const sp&), + (override)); +}; + +class FakeBnSurfaceComposerClient : public gui::BnSurfaceComposerClient { +public: + MOCK_METHOD(binder::Status, createSurface, + (const std::string& name, int32_t flags, const sp& parent, + const gui::LayerMetadata& metadata, gui::CreateSurfaceResult* outResult), + (override)); + + MOCK_METHOD(binder::Status, clearLayerFrameStats, (const sp& handle), (override)); + + MOCK_METHOD(binder::Status, getLayerFrameStats, + (const sp& handle, gui::FrameStats* outStats), (override)); + + MOCK_METHOD(binder::Status, mirrorSurface, + (const sp& mirrorFromHandle, gui::MirrorSurfaceResult* outResult), + (override)); +}; + +class FakeDisplayEventDispatcher : public DisplayEventDispatcher { +public: + FakeDisplayEventDispatcher(const sp& looper, + gui::ISurfaceComposer::VsyncSource vsyncSource, + gui::ISurfaceComposer::EventRegistration eventRegistration) + : DisplayEventDispatcher(looper, vsyncSource, eventRegistration){}; + + MOCK_METHOD4(dispatchVsync, void(nsecs_t, PhysicalDisplayId, uint32_t, VsyncEventData)); + MOCK_METHOD3(dispatchHotplug, void(nsecs_t, PhysicalDisplayId, bool)); + MOCK_METHOD4(dispatchModeChanged, void(nsecs_t, PhysicalDisplayId, int32_t, nsecs_t)); + MOCK_METHOD2(dispatchNullEvent, void(nsecs_t, PhysicalDisplayId)); + MOCK_METHOD3(dispatchFrameRateOverrides, + void(nsecs_t, PhysicalDisplayId, std::vector)); +}; + +} // namespace android + +namespace android::hardware { + +namespace graphics::bufferqueue::V1_0::utils { + +class FakeGraphicBufferProducerV1 : public HGraphicBufferProducer { +public: + FakeGraphicBufferProducerV1() { + ON_CALL(*this, setMaxDequeuedBufferCount).WillByDefault([]() { return 0; }); + ON_CALL(*this, setAsyncMode).WillByDefault([]() { return 0; }); + ON_CALL(*this, detachBuffer).WillByDefault([]() { return 0; }); + ON_CALL(*this, cancelBuffer).WillByDefault([]() { return 0; }); + ON_CALL(*this, disconnect).WillByDefault([]() { return 0; }); + ON_CALL(*this, setSidebandStream).WillByDefault([]() { return 0; }); + ON_CALL(*this, allowAllocation).WillByDefault([]() { return 0; }); + ON_CALL(*this, setGenerationNumber).WillByDefault([]() { return 0; }); + ON_CALL(*this, setSharedBufferMode).WillByDefault([]() { return 0; }); + ON_CALL(*this, setAutoRefresh).WillByDefault([]() { return 0; }); + ON_CALL(*this, setDequeueTimeout).WillByDefault([]() { return 0; }); + ON_CALL(*this, setLegacyBufferDrop).WillByDefault([]() { return 0; }); + }; + MOCK_METHOD2(requestBuffer, Return(int, requestBuffer_cb)); + MOCK_METHOD1(setMaxDequeuedBufferCount, Return(int32_t)); + MOCK_METHOD1(setAsyncMode, Return(bool)); + MOCK_METHOD6(dequeueBuffer, + Return(uint32_t, uint32_t, graphics::common::V1_0::PixelFormat, uint32_t, + bool, dequeueBuffer_cb)); + MOCK_METHOD1(detachBuffer, Return(int)); + MOCK_METHOD1(detachNextBuffer, Return(detachNextBuffer_cb)); + MOCK_METHOD2(attachBuffer, Return(const media::V1_0::AnwBuffer&, attachBuffer_cb)); + MOCK_METHOD3( + queueBuffer, + Return( + int, + const graphics::bufferqueue::V1_0::IGraphicBufferProducer::QueueBufferInput&, + queueBuffer_cb)); + MOCK_METHOD2(cancelBuffer, Return(int, const hidl_handle&)); + MOCK_METHOD2(query, Return(int32_t, query_cb)); + MOCK_METHOD4(connect, + Return(const sp&, int32_t, + bool, connect_cb)); + MOCK_METHOD2(disconnect, + Return( + int, graphics::bufferqueue::V1_0::IGraphicBufferProducer::DisconnectMode)); + MOCK_METHOD1(setSidebandStream, Return(const hidl_handle&)); + MOCK_METHOD4(allocateBuffers, + Return(uint32_t, uint32_t, graphics::common::V1_0::PixelFormat, uint32_t)); + MOCK_METHOD1(allowAllocation, Return(bool)); + MOCK_METHOD1(setGenerationNumber, Return(uint32_t)); + MOCK_METHOD1(getConsumerName, Return(getConsumerName_cb)); + MOCK_METHOD1(setSharedBufferMode, Return(bool)); + MOCK_METHOD1(setAutoRefresh, Return(bool)); + MOCK_METHOD1(setDequeueTimeout, Return(nsecs_t)); + MOCK_METHOD1(setLegacyBufferDrop, Return(bool)); + MOCK_METHOD1(getLastQueuedBuffer, Return(getLastQueuedBuffer_cb)); + MOCK_METHOD1(getFrameTimestamps, Return(getFrameTimestamps_cb)); + MOCK_METHOD1(getUniqueId, Return(getUniqueId_cb)); +}; + +}; // namespace graphics::bufferqueue::V1_0::utils + +namespace graphics::bufferqueue::V2_0::utils { + +class FakeGraphicBufferProducerV2 : public HGraphicBufferProducer { +public: + FakeGraphicBufferProducerV2() { + ON_CALL(*this, setMaxDequeuedBufferCount).WillByDefault([]() { return Status::OK; }); + ON_CALL(*this, setAsyncMode).WillByDefault([]() { return Status::OK; }); + ON_CALL(*this, detachBuffer).WillByDefault([]() { return Status::OK; }); + ON_CALL(*this, cancelBuffer).WillByDefault([]() { return Status::OK; }); + ON_CALL(*this, disconnect).WillByDefault([]() { return Status::OK; }); + ON_CALL(*this, allocateBuffers).WillByDefault([]() { return Status::OK; }); + ON_CALL(*this, allowAllocation).WillByDefault([]() { return Status::OK; }); + ON_CALL(*this, setGenerationNumber).WillByDefault([]() { return Status::OK; }); + ON_CALL(*this, setDequeueTimeout).WillByDefault([]() { return Status::OK; }); + ON_CALL(*this, getUniqueId).WillByDefault([]() { return 0; }); + }; + MOCK_METHOD2(requestBuffer, Return(int, requestBuffer_cb)); + MOCK_METHOD1(setMaxDequeuedBufferCount, Return(int)); + MOCK_METHOD1(setAsyncMode, Return(bool)); + MOCK_METHOD2( + dequeueBuffer, + Return( + const graphics::bufferqueue::V2_0::IGraphicBufferProducer::DequeueBufferInput&, + dequeueBuffer_cb)); + MOCK_METHOD1(detachBuffer, Return(int)); + MOCK_METHOD1(detachNextBuffer, Return(detachNextBuffer_cb)); + MOCK_METHOD3(attachBuffer, + Return(const graphics::common::V1_2::HardwareBuffer&, uint32_t, + attachBuffer_cb)); + MOCK_METHOD3( + queueBuffer, + Return( + int, + const graphics::bufferqueue::V2_0::IGraphicBufferProducer::QueueBufferInput&, + queueBuffer_cb)); + MOCK_METHOD2(cancelBuffer, + Return(int, const hidl_handle&)); + MOCK_METHOD2(query, Return(int32_t, query_cb)); + MOCK_METHOD4(connect, + Return(const sp&, + graphics::bufferqueue::V2_0::ConnectionType, bool, connect_cb)); + MOCK_METHOD1(disconnect, + Return( + graphics::bufferqueue::V2_0::ConnectionType)); + MOCK_METHOD4(allocateBuffers, + Return(uint32_t, uint32_t, uint32_t, + uint64_t)); + MOCK_METHOD1(allowAllocation, Return(bool)); + MOCK_METHOD1(setGenerationNumber, Return(uint32_t)); + MOCK_METHOD1(getConsumerName, Return(getConsumerName_cb)); + MOCK_METHOD1(setDequeueTimeout, Return(int64_t)); + MOCK_METHOD0(getUniqueId, Return()); +}; + +}; // namespace graphics::bufferqueue::V2_0::utils +}; // namespace android::hardware diff --git a/libs/gui/fuzzer/libgui_surfaceComposer_fuzzer.cpp b/libs/gui/fuzzer/libgui_surfaceComposer_fuzzer.cpp new file mode 100644 index 0000000000..6d5427bc9e --- /dev/null +++ b/libs/gui/fuzzer/libgui_surfaceComposer_fuzzer.cpp @@ -0,0 +1,41 @@ +/* + * Copyright 2021 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 +#include + +using namespace android; + +class SurfaceComposerFuzzer { +public: + SurfaceComposerFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; + void process(); + +private: + FuzzedDataProvider mFdp; +}; + +void SurfaceComposerFuzzer::process() { + sp composer(new FakeBnSurfaceComposer()); + fuzzService(composer.get(), std::move(mFdp)); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + SurfaceComposerFuzzer surfaceComposerFuzzer(data, size); + surfaceComposerFuzzer.process(); + return 0; +} -- GitLab From 5d164f2b95e5f867307b7105bbaaadd6a7fcc422 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Thu, 7 Jul 2022 07:56:07 -0700 Subject: [PATCH 0119/1310] SF: Move some Scheduler APIs to VsyncSchedule Scheduler::getDisplayStatInfo forces callers to query both the deadline and period, so split it into two VsyncSchedule APIs. Inline Scheduler:: getPreviousVsyncFrom. Introduce for type-safe, self-documenting wrappers around std::chrono, which will gradually supersede nsecs_t (including integration). Clean up SF helpers for previous present fences. Remove the unnecessary and inaccurate CompositorTiming initialization. Bug: 185535769 Test: Perfetto timeline is green. Change-Id: I22d8ad44ae37612e66f9d98fd4e7e1831951eb99 --- .../surfaceflinger/Scheduler/Scheduler.cpp | 17 +-- services/surfaceflinger/Scheduler/Scheduler.h | 9 +- .../Scheduler/VsyncSchedule.cpp | 8 ++ .../surfaceflinger/Scheduler/VsyncSchedule.h | 4 + .../Scheduler/include/scheduler/Time.h | 67 +++++++++ services/surfaceflinger/SurfaceFlinger.cpp | 132 +++++++++--------- services/surfaceflinger/SurfaceFlinger.h | 42 +++--- .../fuzzer/surfaceflinger_fuzzers_utils.h | 11 +- ...urfaceFlinger_OnInitializeDisplaysTest.cpp | 6 - 9 files changed, 169 insertions(+), 127 deletions(-) create mode 100644 services/surfaceflinger/Scheduler/include/scheduler/Time.h diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index bbf46678d5..00250be0c3 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include @@ -172,8 +171,7 @@ impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const { return [this](uid_t uid) { const Fps refreshRate = holdRefreshRateConfigs()->getActiveMode()->getFps(); - const auto currentPeriod = - mVsyncSchedule->getTracker().currentPeriod() ?: refreshRate.getPeriodNsecs(); + const nsecs_t currentPeriod = mVsyncSchedule->period().ns() ?: refreshRate.getPeriodNsecs(); const auto frameRate = getFrameRateOverride(uid); if (!frameRate.has_value()) { @@ -361,12 +359,6 @@ void Scheduler::setDuration(ConnectionHandle handle, std::chrono::nanoseconds wo thread->setDuration(workDuration, readyDuration); } -DisplayStatInfo Scheduler::getDisplayStatInfo(nsecs_t now) { - const auto vsyncTime = mVsyncSchedule->getTracker().nextAnticipatedVSyncTimeFrom(now); - const auto vsyncPeriod = mVsyncSchedule->getTracker().currentPeriod(); - return DisplayStatInfo{.vsyncTime = vsyncTime, .vsyncPeriod = vsyncPeriod}; -} - ConnectionHandle Scheduler::enableVSyncInjection(bool enable) { if (mInjectVSyncs == enable) { return {}; @@ -776,11 +768,4 @@ void Scheduler::setPreferredRefreshRateForUid(FrameRateOverride frameRateOverrid mFrameRateOverrideMappings.setPreferredRefreshRateForUid(frameRateOverride); } -std::chrono::steady_clock::time_point Scheduler::getPreviousVsyncFrom( - nsecs_t expectedPresentTime) const { - const auto presentTime = std::chrono::nanoseconds(expectedPresentTime); - const auto vsyncPeriod = std::chrono::nanoseconds(mVsyncSchedule->getTracker().currentPeriod()); - return std::chrono::steady_clock::time_point(presentTime - vsyncPeriod); -} - } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 587a773433..444ec2ad93 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -32,9 +32,8 @@ #include #pragma clang diagnostic pop // ignored "-Wconversion -Wextra" -#include - #include +#include #include "EventThread.h" #include "FrameRateOverrideMappings.h" @@ -150,8 +149,6 @@ public: void setDuration(ConnectionHandle, std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration); - DisplayStatInfo getDisplayStatInfo(nsecs_t now); - // Returns injector handle if injection has toggled, or an invalid handle otherwise. ConnectionHandle enableVSyncInjection(bool enable); // Returns false if injection is disabled. @@ -190,14 +187,12 @@ public: void setDisplayPowerMode(hal::PowerMode powerMode); - VSyncDispatch& getVsyncDispatch() { return mVsyncSchedule->getDispatch(); } + VsyncSchedule& getVsyncSchedule() { return *mVsyncSchedule; } // Returns true if a given vsync timestamp is considered valid vsync // for a given uid bool isVsyncValid(nsecs_t expectedVsyncTimestamp, uid_t uid) const; - std::chrono::steady_clock::time_point getPreviousVsyncFrom(nsecs_t expectedPresentTime) const; - void dump(std::string&) const; void dump(ConnectionHandle, std::string&) const; void dumpVsync(std::string&) const; diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp index 3a918a1660..95bc31f239 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp @@ -68,6 +68,14 @@ VsyncSchedule::VsyncSchedule(TrackerPtr tracker, DispatchPtr dispatch, Controlle VsyncSchedule::VsyncSchedule(VsyncSchedule&&) = default; VsyncSchedule::~VsyncSchedule() = default; +Period VsyncSchedule::period() const { + return Period::fromNs(mTracker->currentPeriod()); +} + +TimePoint VsyncSchedule::vsyncDeadlineAfter(TimePoint timePoint) const { + return TimePoint::fromNs(mTracker->nextAnticipatedVSyncTimeFrom(timePoint.ns())); +} + void VsyncSchedule::dump(std::string& out) const { out.append("VsyncController:\n"); mController->dump(out); diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h index 0d9b114875..8c17409b02 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.h +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h @@ -20,6 +20,7 @@ #include #include +#include namespace android::scheduler { @@ -38,6 +39,9 @@ public: VsyncSchedule(VsyncSchedule&&); ~VsyncSchedule(); + Period period() const; + TimePoint vsyncDeadlineAfter(TimePoint) const; + // TODO(b/185535769): Hide behind API. const VsyncTracker& getTracker() const { return *mTracker; } VsyncTracker& getTracker() { return *mTracker; } diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Time.h b/services/surfaceflinger/Scheduler/include/scheduler/Time.h new file mode 100644 index 0000000000..6fa548e5a7 --- /dev/null +++ b/services/surfaceflinger/Scheduler/include/scheduler/Time.h @@ -0,0 +1,67 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include + +namespace android { +namespace scheduler { + +// TODO(b/185535769): Pull Clock.h to libscheduler to reuse this. +using SchedulerClock = std::chrono::high_resolution_clock; +static_assert(SchedulerClock::is_steady); + +} // namespace scheduler + +struct Duration; + +struct TimePoint : scheduler::SchedulerClock::time_point { + explicit TimePoint(const Duration&); + + // Implicit conversion from std::chrono counterpart. + constexpr TimePoint(scheduler::SchedulerClock::time_point p) + : scheduler::SchedulerClock::time_point(p) {} + + static TimePoint fromNs(nsecs_t); + + nsecs_t ns() const; +}; + +struct Duration : TimePoint::duration { + // Implicit conversion from std::chrono counterpart. + constexpr Duration(TimePoint::duration d) : TimePoint::duration(d) {} + + static Duration fromNs(nsecs_t ns) { return {std::chrono::nanoseconds(ns)}; } + + nsecs_t ns() const { return std::chrono::nanoseconds(*this).count(); } +}; + +using Period = Duration; + +inline TimePoint::TimePoint(const Duration& d) : scheduler::SchedulerClock::time_point(d) {} + +inline TimePoint TimePoint::fromNs(nsecs_t ns) { + return TimePoint(Duration::fromNs(ns)); +} + +inline nsecs_t TimePoint::ns() const { + return Duration(time_since_epoch()).ns(); +} + +} // namespace android diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index d313406044..65e4b4e2c7 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1055,12 +1055,14 @@ status_t SurfaceFlinger::getDynamicDisplayInfo(const sp& displayToken, return NO_ERROR; } -status_t SurfaceFlinger::getDisplayStats(const sp&, DisplayStatInfo* stats) { - if (!stats) { +status_t SurfaceFlinger::getDisplayStats(const sp&, DisplayStatInfo* outStats) { + if (!outStats) { return BAD_VALUE; } - *stats = mScheduler->getDisplayStatInfo(systemTime()); + const auto& schedule = mScheduler->getVsyncSchedule(); + outStats->vsyncTime = schedule.vsyncDeadlineAfter(scheduler::SchedulerClock::now()).ns(); + outStats->vsyncPeriod = schedule.period().ns(); return NO_ERROR; } @@ -1569,12 +1571,10 @@ status_t SurfaceFlinger::enableVSyncInjections(bool enable) { status_t SurfaceFlinger::injectVSync(nsecs_t when) { Mutex::Autolock lock(mStateLock); - const DisplayStatInfo stats = mScheduler->getDisplayStatInfo(when); - const auto expectedPresent = calculateExpectedPresentTime(stats); - return mScheduler->injectVSync(when, /*expectedVSyncTime=*/expectedPresent, - /*deadlineTimestamp=*/expectedPresent) - ? NO_ERROR - : BAD_VALUE; + const nsecs_t expectedPresentTime = calculateExpectedPresentTime(when).ns(); + const nsecs_t deadlineTimestamp = expectedPresentTime; + return mScheduler->injectVSync(when, expectedPresentTime, deadlineTimestamp) ? NO_ERROR + : BAD_VALUE; } status_t SurfaceFlinger::getLayerDebugInfo(std::vector* outLayers) { @@ -1951,18 +1951,15 @@ void SurfaceFlinger::setVsyncEnabled(bool enabled) { })); } -SurfaceFlinger::FenceWithFenceTime SurfaceFlinger::previousFrameFence() { - const auto now = systemTime(); - const auto vsyncPeriod = mScheduler->getDisplayStatInfo(now).vsyncPeriod; - const bool expectedPresentTimeIsTheNextVsync = mExpectedPresentTime - now <= vsyncPeriod; - return expectedPresentTimeIsTheNextVsync ? mPreviousPresentFences[0] - : mPreviousPresentFences[1]; +auto SurfaceFlinger::getPreviousPresentFence(nsecs_t frameTime, Period vsyncPeriod) + -> const FenceTimePtr& { + const bool isTwoVsyncsAhead = mExpectedPresentTime - frameTime > vsyncPeriod.ns(); + const size_t i = static_cast(isTwoVsyncsAhead); + return mPreviousPresentFences[i].fenceTime; } -bool SurfaceFlinger::previousFramePending(int graceTimeMs) { +bool SurfaceFlinger::isFencePending(const FenceTimePtr& fence, int graceTimeMs) { ATRACE_CALL(); - const std::shared_ptr& fence = previousFrameFence().fenceTime; - if (fence == FenceTime::NO_FENCE) { return false; } @@ -1973,37 +1970,32 @@ bool SurfaceFlinger::previousFramePending(int graceTimeMs) { return status == -ETIME; } -nsecs_t SurfaceFlinger::previousFramePresentTime() { - const std::shared_ptr& fence = previousFrameFence().fenceTime; +TimePoint SurfaceFlinger::calculateExpectedPresentTime(nsecs_t frameTime) const { + const auto& schedule = mScheduler->getVsyncSchedule(); - if (fence == FenceTime::NO_FENCE) { - return Fence::SIGNAL_TIME_INVALID; + const TimePoint vsyncDeadline = schedule.vsyncDeadlineAfter(TimePoint::fromNs(frameTime)); + if (mVsyncModulator->getVsyncConfig().sfOffset > 0) { + return vsyncDeadline; } - return fence->getSignalTime(); -} - -nsecs_t SurfaceFlinger::calculateExpectedPresentTime(DisplayStatInfo stats) const { - // Inflate the expected present time if we're targetting the next vsync. - return mVsyncModulator->getVsyncConfig().sfOffset > 0 ? stats.vsyncTime - : stats.vsyncTime + stats.vsyncPeriod; + // Inflate the expected present time if we're targeting the next vsync. + return vsyncDeadline + schedule.period(); } bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expectedVsyncTime) FTL_FAKE_GUARD(kMainThreadContext) { - // calculate the expected present time once and use the cached - // value throughout this frame to make sure all layers are - // seeing this same value. - if (expectedVsyncTime >= frameTime) { - mExpectedPresentTime = expectedVsyncTime; - } else { - const DisplayStatInfo stats = mScheduler->getDisplayStatInfo(frameTime); - mExpectedPresentTime = calculateExpectedPresentTime(stats); - } - + // The expectedVsyncTime, which was predicted when this frame was scheduled, is normally in the + // future relative to frameTime, but may not be for delayed frames. Adjust mExpectedPresentTime + // accordingly, but not mScheduledPresentTime. const nsecs_t lastScheduledPresentTime = mScheduledPresentTime; mScheduledPresentTime = expectedVsyncTime; + // Calculate the expected present time once and use the cached value throughout this frame to + // make sure all layers are seeing this same value. + mExpectedPresentTime = expectedVsyncTime >= frameTime + ? expectedVsyncTime + : calculateExpectedPresentTime(frameTime).ns(); + const auto vsyncIn = [&] { if (!ATRACE_ENABLED()) return 0.f; return (mExpectedPresentTime - systemTime()) / 1e6f; @@ -2011,6 +2003,9 @@ bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expected ATRACE_FORMAT("%s %" PRId64 " vsyncIn %.2fms%s", __func__, vsyncId, vsyncIn, mExpectedPresentTime == expectedVsyncTime ? "" : " (adjusted)"); + const Period vsyncPeriod = mScheduler->getVsyncSchedule().period(); + const FenceTimePtr& previousPresentFence = getPreviousPresentFence(frameTime, vsyncPeriod); + // When Backpressure propagation is enabled we want to give a small grace period // for the present fence to fire instead of just giving up on this frame to handle cases // where present fence is just about to get signaled. @@ -2019,7 +2014,8 @@ bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expected // Pending frames may trigger backpressure propagation. const TracedOrdinal framePending = {"PrevFramePending", - previousFramePending(graceTimeForPresentFenceMs)}; + isFencePending(previousPresentFence, + graceTimeForPresentFenceMs)}; // Frame missed counts for metrics tracking. // A frame is missed if the prior frame is still pending. If no longer pending, @@ -2029,9 +2025,8 @@ bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expected // Add some slop to correct for drift. This should generally be // smaller than a typical frame duration, but should not be so small // that it reports reasonable drift as a missed frame. - const DisplayStatInfo stats = mScheduler->getDisplayStatInfo(systemTime()); - const nsecs_t frameMissedSlop = stats.vsyncPeriod / 2; - const nsecs_t previousPresentTime = previousFramePresentTime(); + const nsecs_t frameMissedSlop = vsyncPeriod.ns() / 2; + const nsecs_t previousPresentTime = previousPresentFence->getSignalTime(); const TracedOrdinal frameMissed = {"PrevFrameMissed", framePending || (previousPresentTime >= 0 && @@ -2118,7 +2113,7 @@ bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expected // Composite if transactions were committed, or if requested by HWC. bool mustComposite = mMustComposite.exchange(false); { - mFrameTimeline->setSfWakeUp(vsyncId, frameTime, Fps::fromPeriodNsecs(stats.vsyncPeriod)); + mFrameTimeline->setSfWakeUp(vsyncId, frameTime, Fps::fromPeriodNsecs(vsyncPeriod.ns())); bool needsTraversal = false; if (clearTransactionFlags(eTransactionFlushNeeded)) { @@ -2239,7 +2234,9 @@ void SurfaceFlinger::composite(nsecs_t frameTime, int64_t vsyncId) } const auto expectedPresentTime = mExpectedPresentTime.load(); - const auto prevVsyncTime = mScheduler->getPreviousVsyncFrom(expectedPresentTime); + const auto prevVsyncTime = + TimePoint::fromNs(expectedPresentTime) - mScheduler->getVsyncSchedule().period(); + const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration; refreshArgs.earliestPresentTime = prevVsyncTime - hwcMinWorkDuration; refreshArgs.previousPresentFence = mPreviousPresentFences[0].fenceTime; @@ -2326,11 +2323,11 @@ void SurfaceFlinger::updateLayerGeometry() { mLayersPendingRefresh.clear(); } -void SurfaceFlinger::updateCompositorTiming(const DisplayStatInfo& stats, nsecs_t compositeTime, - std::shared_ptr& presentFenceTime) { +nsecs_t SurfaceFlinger::trackPresentLatency(nsecs_t compositeTime, + std::shared_ptr presentFenceTime) { // Update queue of past composite+present times and determine the // most recently known composite to present latency. - getBE().mCompositePresentTimes.push({compositeTime, presentFenceTime}); + getBE().mCompositePresentTimes.push({compositeTime, std::move(presentFenceTime)}); nsecs_t compositeToPresentLatency = -1; while (!getBE().mCompositePresentTimes.empty()) { SurfaceFlingerBE::CompositePresentTime& cpt = getBE().mCompositePresentTimes.front(); @@ -2349,13 +2346,13 @@ void SurfaceFlinger::updateCompositorTiming(const DisplayStatInfo& stats, nsecs_ getBE().mCompositePresentTimes.pop(); } - setCompositorTimingSnapped(stats, compositeToPresentLatency); + return compositeToPresentLatency; } -void SurfaceFlinger::setCompositorTimingSnapped(const DisplayStatInfo& stats, +void SurfaceFlinger::setCompositorTimingSnapped(nsecs_t vsyncDeadline, nsecs_t vsyncPeriod, nsecs_t compositeToPresentLatency) { // Avoid division by 0 by defaulting to 60Hz - const auto vsyncPeriod = stats.vsyncPeriod ?: (60_Hz).getPeriodNsecs(); + vsyncPeriod = vsyncPeriod ?: (60_Hz).getPeriodNsecs(); // Integer division and modulo round toward 0 not -inf, so we need to // treat negative and positive offsets differently. @@ -2381,7 +2378,7 @@ void SurfaceFlinger::setCompositorTimingSnapped(const DisplayStatInfo& stats, (extraVsyncs > 0) ? idealLatency + (extraVsyncs * vsyncPeriod) : idealLatency; std::lock_guard lock(getBE().mCompositorTimingLock); - getBE().mCompositorTiming.deadline = stats.vsyncTime - idealLatency; + getBE().mCompositorTiming.deadline = vsyncDeadline - idealLatency; getBE().mCompositorTiming.interval = vsyncPeriod; getBE().mCompositorTiming.presentLatency = snappedCompositeToPresentLatency; } @@ -2470,13 +2467,18 @@ void SurfaceFlinger::postComposition() { mFrameTimeline->setSfPresent(/* sfPresentTime */ now, mPreviousPresentFences[0].fenceTime, glCompositionDoneFenceTime); - const DisplayStatInfo stats = mScheduler->getDisplayStatInfo(now); - // We use the CompositionEngine::getLastFrameRefreshTimestamp() which might // be sampled a little later than when we started doing work for this frame, - // but that should be okay since updateCompositorTiming has snapping logic. - updateCompositorTiming(stats, mCompositionEngine->getLastFrameRefreshTimestamp(), - mPreviousPresentFences[0].fenceTime); + // but that should be okay since setCompositorTimingSnapped has snapping logic. + const nsecs_t compositeTime = mCompositionEngine->getLastFrameRefreshTimestamp(); + const nsecs_t presentLatency = + trackPresentLatency(compositeTime, mPreviousPresentFences[0].fenceTime); + + const auto& schedule = mScheduler->getVsyncSchedule(); + const TimePoint vsyncDeadline = schedule.vsyncDeadlineAfter(TimePoint::fromNs(now)); + const Period vsyncPeriod = schedule.period(); + setCompositorTimingSnapped(vsyncDeadline.ns(), vsyncPeriod.ns(), presentLatency); + CompositorTiming compositorTiming; { std::lock_guard lock(getBE().mCompositorTimingLock); @@ -2593,7 +2595,7 @@ void SurfaceFlinger::postComposition() { mHasPoweredOff = false; } else { nsecs_t elapsedTime = currentTime - getBE().mLastSwapTime; - size_t numPeriods = static_cast(elapsedTime / stats.vsyncPeriod); + const size_t numPeriods = static_cast(elapsedTime / vsyncPeriod.ns()); if (numPeriods < SurfaceFlingerBE::NUM_BUCKETS - 1) { getBE().mFrameBuckets[numPeriods] += elapsedTime; } else { @@ -3470,8 +3472,8 @@ void SurfaceFlinger::initScheduler(const sp& display) { mInterceptor->saveVSyncEvent(timestamp); }); - mScheduler->initVsync(mScheduler->getVsyncDispatch(), *mFrameTimeline->getTokenManager(), - configs.late.sfWorkDuration); + mScheduler->initVsync(mScheduler->getVsyncSchedule().getDispatch(), + *mFrameTimeline->getTokenManager(), configs.late.sfWorkDuration); mRegionSamplingThread = new RegionSamplingThread(*this, RegionSamplingThread::EnvironmentTimingTunables()); @@ -3941,10 +3943,10 @@ bool SurfaceFlinger::transactionFlushNeeded() { bool SurfaceFlinger::frameIsEarly(nsecs_t expectedPresentTime, int64_t vsyncId) const { // The amount of time SF can delay a frame if it is considered early based // on the VsyncModulator::VsyncConfig::appWorkDuration - constexpr static std::chrono::nanoseconds kEarlyLatchMaxThreshold = 100ms; + constexpr std::chrono::nanoseconds kEarlyLatchMaxThreshold = 100ms; - const auto currentVsyncPeriod = mScheduler->getDisplayStatInfo(systemTime()).vsyncPeriod; - const auto earlyLatchVsyncThreshold = currentVsyncPeriod / 2; + const nsecs_t currentVsyncPeriod = mScheduler->getVsyncSchedule().period().ns(); + const nsecs_t earlyLatchVsyncThreshold = currentVsyncPeriod / 2; const auto prediction = mFrameTimeline->getTokenManager()->getPredictionsForToken(vsyncId); if (!prediction.has_value()) { @@ -4839,10 +4841,6 @@ void SurfaceFlinger::onInitializeDisplays() { const nsecs_t vsyncPeriod = display->refreshRateConfigs().getActiveMode()->getVsyncPeriod(); mAnimFrameTracker.setDisplayRefreshPeriod(vsyncPeriod); mActiveDisplayTransformHint = display->getTransformHint(); - // Use phase of 0 since phase is not known. - // Use latency of 0, which will snap to the ideal latency. - DisplayStatInfo stats{0 /* vsyncTime */, vsyncPeriod}; - setCompositorTimingSnapped(stats, 0); } void SurfaceFlinger::initializeDisplays() { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index ed6c8ce6d7..f6dbfbfd3d 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -958,10 +958,12 @@ private: * Compositing */ void postComposition(); + + // Returns the composite-to-present latency of the latest presented frame. + nsecs_t trackPresentLatency(nsecs_t compositeTime, std::shared_ptr presentFenceTime); + void getCompositorTiming(CompositorTiming* compositorTiming); - void updateCompositorTiming(const DisplayStatInfo& stats, nsecs_t compositeTime, - std::shared_ptr& presentFenceTime); - void setCompositorTimingSnapped(const DisplayStatInfo& stats, + void setCompositorTimingSnapped(nsecs_t vsyncDeadline, nsecs_t vsyncPeriod, nsecs_t compositeToPresentLatency); void postFrame() REQUIRES(kMainThreadContext); @@ -997,31 +999,17 @@ private: getHwComposer().setVsyncEnabled(id, enabled); } - struct FenceWithFenceTime { - sp fence = Fence::NO_FENCE; - std::shared_ptr fenceTime = FenceTime::NO_FENCE; - }; - - // Gets the fence for the previous frame. - // Must be called on the main thread. - FenceWithFenceTime previousFrameFence(); + using FenceTimePtr = std::shared_ptr; - // Whether the previous frame has not yet been presented to the display. - // If graceTimeMs is positive, this method waits for at most the provided - // grace period before reporting if the frame missed. - // Must be called on the main thread. - bool previousFramePending(int graceTimeMs = 0); + const FenceTimePtr& getPreviousPresentFence(nsecs_t frameTime, Period) + REQUIRES(kMainThreadContext); - // Returns the previous time that the frame was presented. If the frame has - // not been presented yet, then returns Fence::SIGNAL_TIME_PENDING. If there - // is no pending frame, then returns Fence::SIGNAL_TIME_INVALID. - // Must be called on the main thread. - nsecs_t previousFramePresentTime(); + // Blocks the thread waiting for up to graceTimeMs in case the fence is about to signal. + static bool isFencePending(const FenceTimePtr&, int graceTimeMs); // Calculates the expected present time for this frame. For negative offsets, performs a // correction using the predicted vsync for the next frame instead. - - nsecs_t calculateExpectedPresentTime(DisplayStatInfo) const; + TimePoint calculateExpectedPresentTime(nsecs_t frameTime) const; /* * Display identification @@ -1209,7 +1197,7 @@ private: std::unordered_set, SpHash> mLayersWithQueuedFrames; // Tracks layers that need to update a display's dirty region. std::vector> mLayersPendingRefresh; - std::array mPreviousPresentFences; + // True if in the previous frame at least one layer was composed via the GPU. bool mHadClientComposition = false; // True if in the previous frame at least one layer was composed via HW Composer. @@ -1346,6 +1334,12 @@ private: std::unique_ptr mRefreshRateStats; + struct FenceWithFenceTime { + sp fence = Fence::NO_FENCE; + FenceTimePtr fenceTime = FenceTime::NO_FENCE; + }; + std::array mPreviousPresentFences; + std::atomic mExpectedPresentTime = 0; nsecs_t mScheduledPresentTime = 0; hal::Vsync mHWCVsyncPendingState = hal::Vsync::DISABLE; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 456a4981c9..b811ea3124 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -535,11 +535,6 @@ public: mFlinger->setVsyncConfig(vsyncConfig, fdp->ConsumeIntegral()); } - void updateCompositorTiming(FuzzedDataProvider *fdp) { - std::shared_ptr presentFenceTime = FenceTime::NO_FENCE; - mFlinger->updateCompositorTiming({}, fdp->ConsumeIntegral(), presentFenceTime); - } - void getCompositorTiming() { CompositorTiming compositorTiming; mFlinger->getCompositorTiming(&compositorTiming); @@ -641,9 +636,11 @@ public: getCompositorTiming(); - updateCompositorTiming(&mFdp); + mFlinger->trackPresentLatency(mFdp.ConsumeIntegral(), FenceTime::NO_FENCE); + mFlinger->setCompositorTimingSnapped(mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral()); - mFlinger->setCompositorTimingSnapped({}, mFdp.ConsumeIntegral()); FTL_FAKE_GUARD(kMainThreadContext, mFlinger->postFrame()); mFlinger->calculateExpectedPresentTime({}); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp index 37cf05ef64..3d576f4450 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp @@ -85,12 +85,6 @@ TEST_F(OnInitializeDisplaysTest, onInitializeDisplaysSetsUpPrimaryDisplay) { // The display transaction needed flag should be set. EXPECT_TRUE(hasTransactionFlagSet(eDisplayTransactionNeeded)); - - // The compositor timing should be set to default values - const auto& compositorTiming = mFlinger.getCompositorTiming(); - EXPECT_EQ(-DEFAULT_VSYNC_PERIOD, compositorTiming.deadline); - EXPECT_EQ(DEFAULT_VSYNC_PERIOD, compositorTiming.interval); - EXPECT_EQ(DEFAULT_VSYNC_PERIOD, compositorTiming.presentLatency); } } // namespace -- GitLab From 4376bd84b4ee9d038daeba17dfae3e7174bbf5bd Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Thu, 7 Jul 2022 11:50:20 -0700 Subject: [PATCH 0120/1310] SF: Remove CompositorTiming state and lock Replace the CompositorTiming::interval access on layer creation with a RefreshRateConfigs lookup. Bug: 185535769 Test: Perfetto timeline is green. Change-Id: I0d2240f879b60c1d0ffe68be35c96a227e495ce3 --- services/surfaceflinger/Layer.cpp | 6 ++--- services/surfaceflinger/SurfaceFlinger.cpp | 26 ++++++------------- services/surfaceflinger/SurfaceFlinger.h | 9 ++----- .../fuzzer/surfaceflinger_fuzzers_utils.h | 13 +++------- .../tests/unittests/TestableSurfaceFlinger.h | 2 -- 5 files changed, 16 insertions(+), 40 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 80df3996ce..fa71e72809 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -155,9 +155,9 @@ Layer::Layer(const LayerCreationArgs& args) mDrawingState.color.g = -1.0_hf; mDrawingState.color.b = -1.0_hf; } - CompositorTiming compositorTiming; - args.flinger->getCompositorTiming(&compositorTiming); - mFrameTracker.setDisplayRefreshPeriod(compositorTiming.interval); + + mFrameTracker.setDisplayRefreshPeriod( + args.flinger->mScheduler->getVsyncPeriodFromRefreshRateConfigs()); mCallingPid = args.callingPid; mCallingUid = args.callingUid; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 65e4b4e2c7..9f1cf9c362 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1886,11 +1886,6 @@ void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t t } } -void SurfaceFlinger::getCompositorTiming(CompositorTiming* compositorTiming) { - std::lock_guard lock(getBE().mCompositorTimingLock); - *compositorTiming = getBE().mCompositorTiming; -} - void SurfaceFlinger::onComposerHalHotplug(hal::HWDisplayId hwcDisplayId, hal::Connection connection) { const bool connected = connection == hal::Connection::CONNECTED; @@ -2349,8 +2344,8 @@ nsecs_t SurfaceFlinger::trackPresentLatency(nsecs_t compositeTime, return compositeToPresentLatency; } -void SurfaceFlinger::setCompositorTimingSnapped(nsecs_t vsyncDeadline, nsecs_t vsyncPeriod, - nsecs_t compositeToPresentLatency) { +CompositorTiming SurfaceFlinger::makeCompositorTiming(nsecs_t vsyncDeadline, nsecs_t vsyncPeriod, + nsecs_t compositeToPresentLatency) { // Avoid division by 0 by defaulting to 60Hz vsyncPeriod = vsyncPeriod ?: (60_Hz).getPeriodNsecs(); @@ -2377,10 +2372,9 @@ void SurfaceFlinger::setCompositorTimingSnapped(nsecs_t vsyncDeadline, nsecs_t v const nsecs_t snappedCompositeToPresentLatency = (extraVsyncs > 0) ? idealLatency + (extraVsyncs * vsyncPeriod) : idealLatency; - std::lock_guard lock(getBE().mCompositorTimingLock); - getBE().mCompositorTiming.deadline = vsyncDeadline - idealLatency; - getBE().mCompositorTiming.interval = vsyncPeriod; - getBE().mCompositorTiming.presentLatency = snappedCompositeToPresentLatency; + return {.deadline = vsyncDeadline - idealLatency, + .interval = vsyncPeriod, + .presentLatency = snappedCompositeToPresentLatency}; } bool SurfaceFlinger::isHdrLayer(Layer* layer) const { @@ -2469,7 +2463,7 @@ void SurfaceFlinger::postComposition() { // We use the CompositionEngine::getLastFrameRefreshTimestamp() which might // be sampled a little later than when we started doing work for this frame, - // but that should be okay since setCompositorTimingSnapped has snapping logic. + // but that should be okay since makeCompositorTiming has snapping logic. const nsecs_t compositeTime = mCompositionEngine->getLastFrameRefreshTimestamp(); const nsecs_t presentLatency = trackPresentLatency(compositeTime, mPreviousPresentFences[0].fenceTime); @@ -2477,13 +2471,9 @@ void SurfaceFlinger::postComposition() { const auto& schedule = mScheduler->getVsyncSchedule(); const TimePoint vsyncDeadline = schedule.vsyncDeadlineAfter(TimePoint::fromNs(now)); const Period vsyncPeriod = schedule.period(); - setCompositorTimingSnapped(vsyncDeadline.ns(), vsyncPeriod.ns(), presentLatency); - CompositorTiming compositorTiming; - { - std::lock_guard lock(getBE().mCompositorTimingLock); - compositorTiming = getBE().mCompositorTiming; - } + const CompositorTiming compositorTiming = + makeCompositorTiming(vsyncDeadline.ns(), vsyncPeriod.ns(), presentLatency); for (const auto& layer: mLayersWithQueuedFrames) { layer->onPostComposition(display, glCompositionDoneFenceTime, diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index f6dbfbfd3d..0e5386fa81 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -168,10 +168,6 @@ enum class LatchUnsignaledConfig { using DisplayColorSetting = compositionengine::OutputColorSetting; struct SurfaceFlingerBE { - // protected by mCompositorTimingLock; - mutable std::mutex mCompositorTimingLock; - CompositorTiming mCompositorTiming; - // Only accessed from the main thread. struct CompositePresentTime { nsecs_t composite = -1; @@ -962,9 +958,8 @@ private: // Returns the composite-to-present latency of the latest presented frame. nsecs_t trackPresentLatency(nsecs_t compositeTime, std::shared_ptr presentFenceTime); - void getCompositorTiming(CompositorTiming* compositorTiming); - void setCompositorTimingSnapped(nsecs_t vsyncDeadline, nsecs_t vsyncPeriod, - nsecs_t compositeToPresentLatency); + CompositorTiming makeCompositorTiming(nsecs_t vsyncDeadline, nsecs_t vsyncPeriod, + nsecs_t compositeToPresentLatency); void postFrame() REQUIRES(kMainThreadContext); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index b811ea3124..69ef2bf18b 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -535,11 +535,6 @@ public: mFlinger->setVsyncConfig(vsyncConfig, fdp->ConsumeIntegral()); } - void getCompositorTiming() { - CompositorTiming compositorTiming; - mFlinger->getCompositorTiming(&compositorTiming); - } - sp fuzzBoot(FuzzedDataProvider *fdp) { mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(fdp->ConsumeBool()); const sp client = new Client(mFlinger); @@ -634,12 +629,10 @@ public: mFlinger->postComposition(); - getCompositorTiming(); - mFlinger->trackPresentLatency(mFdp.ConsumeIntegral(), FenceTime::NO_FENCE); - mFlinger->setCompositorTimingSnapped(mFdp.ConsumeIntegral(), - mFdp.ConsumeIntegral(), - mFdp.ConsumeIntegral()); + mFlinger->makeCompositorTiming(mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral(), + mFdp.ConsumeIntegral()); FTL_FAKE_GUARD(kMainThreadContext, mFlinger->postFrame()); mFlinger->calculateExpectedPresentTime({}); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 3a05e2fb34..e0d4f28ffe 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -501,8 +501,6 @@ public: } auto& getCompositionEngine() const { return mFlinger->getCompositionEngine(); } - const auto& getCompositorTiming() const { return mFlinger->getBE().mCompositorTiming; } - mock::FrameTracer* getFrameTracer() const { return static_cast(mFlinger->mFrameTracer.get()); } -- GitLab From 50c0afe2457ec631fc3c9a7731aa4b9c8e18fd76 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Mon, 11 Jul 2022 15:04:07 -0700 Subject: [PATCH 0121/1310] SF: Support xy scaling for rounded corners Currently only a single corner radius can be applied on a layer. If the layer's x and y scale do not match, the average is used to scale the corner radius. This can cause visual defects in the rendered rounded corners. This defect can be seen when moving the Camera app to recents. The camera buffer is submitted with a rotation and the layer's x and y scales are used to stretch the buffer to the desired size in portrait mode. Bug: 145094543 Test: atest librenderengine_test Test: adb shell wm size 1000x1900 & animate camera app to recents Change-Id: Ie76581eb200a57b847f619f4da0e61758f70dce7 --- libs/renderengine/gl/GLESRenderEngine.cpp | 14 +++-- .../include/renderengine/LayerSettings.h | 5 +- libs/renderengine/skia/Cache.cpp | 21 +++---- libs/renderengine/skia/SkiaGLRenderEngine.cpp | 24 ++++---- libs/renderengine/skia/SkiaGLRenderEngine.h | 3 +- libs/renderengine/tests/RenderEngineTest.cpp | 60 ++++++++++++++++--- services/surfaceflinger/Layer.cpp | 26 ++++---- services/surfaceflinger/Layer.h | 7 ++- .../tests/unittests/CompositionTest.cpp | 9 ++- 9 files changed, 110 insertions(+), 59 deletions(-) diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp index 22dd86698b..6dc01b916e 100644 --- a/libs/renderengine/gl/GLESRenderEngine.cpp +++ b/libs/renderengine/gl/GLESRenderEngine.cpp @@ -921,7 +921,8 @@ void GLESRenderEngine::handleRoundedCorners(const DisplaySettings& display, // Finally, we cut the layer into 3 parts, with top and bottom parts having rounded corners // and the middle part without rounded corners. - const int32_t radius = ceil(layer.geometry.roundedCornersRadius); + const int32_t radius = ceil( + (layer.geometry.roundedCornersRadius.x + layer.geometry.roundedCornersRadius.y) / 2.0); const Rect topRect(bounds.left, bounds.top, bounds.right, bounds.top + radius); setScissor(topRect); drawMesh(mesh); @@ -1266,23 +1267,24 @@ void GLESRenderEngine::drawLayersInternal( const half3 solidColor = layer.source.solidColor; const half4 color = half4(solidColor.r, solidColor.g, solidColor.b, layer.alpha); + const float radius = + (layer.geometry.roundedCornersRadius.x + layer.geometry.roundedCornersRadius.y) / + 2.0f; // Buffer sources will have a black solid color ignored in the shader, // so in that scenario the solid color passed here is arbitrary. - setupLayerBlending(usePremultipliedAlpha, isOpaque, disableTexture, color, - layer.geometry.roundedCornersRadius); + setupLayerBlending(usePremultipliedAlpha, isOpaque, disableTexture, color, radius); if (layer.disableBlending) { glDisable(GL_BLEND); } setSourceDataSpace(layer.sourceDataspace); if (layer.shadow.length > 0.0f) { - handleShadow(layer.geometry.boundaries, layer.geometry.roundedCornersRadius, - layer.shadow); + handleShadow(layer.geometry.boundaries, radius, layer.shadow); } // We only want to do a special handling for rounded corners when having rounded corners // is the only reason it needs to turn on blending, otherwise, we handle it like the // usual way since it needs to turn on blending anyway. - else if (layer.geometry.roundedCornersRadius > 0.0 && color.a >= 1.0f && isOpaque) { + else if (radius > 0.0 && color.a >= 1.0f && isOpaque) { handleRoundedCorners(display, layer, mesh); } else { drawMesh(mesh); diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h index 154e5269f0..b3a617c04b 100644 --- a/libs/renderengine/include/renderengine/LayerSettings.h +++ b/libs/renderengine/include/renderengine/LayerSettings.h @@ -87,7 +87,7 @@ struct Geometry { // rectangle to figure out how to apply the radius for this layer. The crop rectangle will be // in local layer coordinate space, so we have to take the layer transform into account when // walking up the tree. - float roundedCornersRadius = 0.0; + vec2 roundedCornersRadius = vec2(0.0f, 0.0f); // Rectangle within which corners will be rounded. FloatRect roundedCornersCrop = FloatRect(); @@ -258,7 +258,8 @@ static inline void PrintTo(const Geometry& settings, ::std::ostream* os) { PrintTo(settings.boundaries, os); *os << "\n .positionTransform = "; PrintMatrix(settings.positionTransform, os); - *os << "\n .roundedCornersRadius = " << settings.roundedCornersRadius; + *os << "\n .roundedCornersRadiusX = " << settings.roundedCornersRadius.x; + *os << "\n .roundedCornersRadiusY = " << settings.roundedCornersRadius.y; *os << "\n .roundedCornersCrop = "; PrintTo(settings.roundedCornersCrop, os); *os << "\n}"; diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp index f3064f3c69..c39f0a97fd 100644 --- a/libs/renderengine/skia/Cache.cpp +++ b/libs/renderengine/skia/Cache.cpp @@ -66,7 +66,7 @@ static void drawShadowLayers(SkiaRenderEngine* renderengine, const DisplaySettin Geometry{ .boundaries = rect, .roundedCornersCrop = rect, - .roundedCornersRadius = 50.f, + .roundedCornersRadius = {50.f, 50.f}, }, // drawShadow ignores alpha .shadow = @@ -87,7 +87,7 @@ static void drawShadowLayers(SkiaRenderEngine* renderengine, const DisplaySettin Geometry{ .boundaries = smallerRect, .roundedCornersCrop = rect, - .roundedCornersRadius = 50.f, + .roundedCornersRadius = {50.f, 50.f}, }, .source = PixelSource{ @@ -148,7 +148,7 @@ static void drawImageLayers(SkiaRenderEngine* renderengine, const DisplaySetting // In reduced shader mode, all non-zero round rect radii get the same code path. for (float roundedCornersRadius : {0.0f, 50.0f}) { // roundedCornersCrop is always set, but the radius triggers the behavior - layer.geometry.roundedCornersRadius = roundedCornersRadius; + layer.geometry.roundedCornersRadius = {roundedCornersRadius, roundedCornersRadius}; for (bool isOpaque : {true, false}) { layer.source.buffer.isOpaque = isOpaque; for (auto alpha : {half(.2f), half(1.0f)}) { @@ -181,7 +181,7 @@ static void drawSolidLayers(SkiaRenderEngine* renderengine, const DisplaySetting for (auto transform : {mat4(), kScaleAndTranslate}) { layer.geometry.positionTransform = transform; for (float roundedCornersRadius : {0.0f, 50.f}) { - layer.geometry.roundedCornersRadius = roundedCornersRadius; + layer.geometry.roundedCornersRadius = {roundedCornersRadius, roundedCornersRadius}; auto layers = std::vector{layer}; renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd()); @@ -238,7 +238,7 @@ static void drawClippedLayers(SkiaRenderEngine* renderengine, const DisplaySetti .geometry = Geometry{ .boundaries = rect, - .roundedCornersRadius = 27, // larger than the 20 above. + .roundedCornersRadius = {27.f, 27.f}, .roundedCornersCrop = FloatRect(0, 0, displayRect.width(), displayRect.height()), }, @@ -275,9 +275,9 @@ static void drawPIPImageLayer(SkiaRenderEngine* renderengine, const DisplaySetti // larger than the layer bounds. .positionTransform = kFlip, .boundaries = rect, - .roundedCornersRadius = 94.2551, - .roundedCornersCrop = FloatRect( - -93.75, 0, displayRect.width() + 93.75, displayRect.height()), + .roundedCornersRadius = {94.2551f, 94.2551f}, + .roundedCornersCrop = FloatRect(-93.75, 0, displayRect.width() + 93.75, + displayRect.height()), }, .source = PixelSource{.buffer = Buffer{ @@ -307,10 +307,11 @@ static void drawHolePunchLayer(SkiaRenderEngine* renderengine, const DisplaySett // the boundaries have to be smaller than the rounded crop so that // clipRRect is used instead of drawRRect .boundaries = small, - .roundedCornersRadius = 50.f, + .roundedCornersRadius = {50.f, 50.f}, .roundedCornersCrop = rect, }, - .source = PixelSource{ + .source = + PixelSource{ .solidColor = half3(0.f, 0.f, 0.f), }, .sourceDataspace = kDestDataSpace, diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index 5187d7b7f7..a0bba593e3 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -1307,7 +1307,7 @@ inline SkRect SkiaGLRenderEngine::getSkRect(const Rect& rect) { * produce the insected roundRect. If false, the returned state of the radii param is undefined. */ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, - const SkRect& insetCrop, float cornerRadius, + const SkRect& insetCrop, const vec2& cornerRadius, SkVector radii[4]) { const bool leftEqual = bounds.fLeft == crop.fLeft; const bool topEqual = bounds.fTop == crop.fTop; @@ -1319,8 +1319,8 @@ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, // In particular the round rect implementation will scale the value of all corner radii // if the sum of the radius along any edge is greater than the length of that edge. // See https://www.w3.org/TR/css-backgrounds-3/#corner-overlap - const bool requiredWidth = bounds.width() > (cornerRadius * 2); - const bool requiredHeight = bounds.height() > (cornerRadius * 2); + const bool requiredWidth = bounds.width() > (cornerRadius.x * 2); + const bool requiredHeight = bounds.height() > (cornerRadius.y * 2); if (!requiredWidth || !requiredHeight) { return false; } @@ -1329,7 +1329,7 @@ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, // contained within the cropped shape and does not need rounded. // compute the UpperLeft corner radius if (leftEqual && topEqual) { - radii[0].set(cornerRadius, cornerRadius); + radii[0].set(cornerRadius.x, cornerRadius.y); } else if ((leftEqual && bounds.fTop >= insetCrop.fTop) || (topEqual && bounds.fLeft >= insetCrop.fLeft)) { radii[0].set(0, 0); @@ -1338,7 +1338,7 @@ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, } // compute the UpperRight corner radius if (rightEqual && topEqual) { - radii[1].set(cornerRadius, cornerRadius); + radii[1].set(cornerRadius.x, cornerRadius.y); } else if ((rightEqual && bounds.fTop >= insetCrop.fTop) || (topEqual && bounds.fRight <= insetCrop.fRight)) { radii[1].set(0, 0); @@ -1347,7 +1347,7 @@ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, } // compute the BottomRight corner radius if (rightEqual && bottomEqual) { - radii[2].set(cornerRadius, cornerRadius); + radii[2].set(cornerRadius.x, cornerRadius.y); } else if ((rightEqual && bounds.fBottom <= insetCrop.fBottom) || (bottomEqual && bounds.fRight <= insetCrop.fRight)) { radii[2].set(0, 0); @@ -1356,7 +1356,7 @@ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, } // compute the BottomLeft corner radius if (leftEqual && bottomEqual) { - radii[3].set(cornerRadius, cornerRadius); + radii[3].set(cornerRadius.x, cornerRadius.y); } else if ((leftEqual && bounds.fBottom <= insetCrop.fBottom) || (bottomEqual && bounds.fLeft >= insetCrop.fLeft)) { radii[3].set(0, 0); @@ -1369,22 +1369,22 @@ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, inline std::pair SkiaGLRenderEngine::getBoundsAndClip(const FloatRect& boundsRect, const FloatRect& cropRect, - const float cornerRadius) { + const vec2& cornerRadius) { const SkRect bounds = getSkRect(boundsRect); const SkRect crop = getSkRect(cropRect); SkRRect clip; - if (cornerRadius > 0) { + if (cornerRadius.x > 0 && cornerRadius.y > 0) { // it the crop and the bounds are equivalent or there is no crop then we don't need a clip if (bounds == crop || crop.isEmpty()) { - return {SkRRect::MakeRectXY(bounds, cornerRadius, cornerRadius), clip}; + return {SkRRect::MakeRectXY(bounds, cornerRadius.x, cornerRadius.y), clip}; } // This makes an effort to speed up common, simple bounds + clip combinations by // converting them to a single RRect draw. It is possible there are other cases // that can be converted. if (crop.contains(bounds)) { - const auto insetCrop = crop.makeInset(cornerRadius, cornerRadius); + const auto insetCrop = crop.makeInset(cornerRadius.x, cornerRadius.y); if (insetCrop.contains(bounds)) { return {SkRRect::MakeRect(bounds), clip}; // clip is empty - no rounding required } @@ -1398,7 +1398,7 @@ inline std::pair SkiaGLRenderEngine::getBoundsAndClip(const Fl } // we didn't hit any of our fast paths so set the clip to the cropRect - clip.setRectXY(crop, cornerRadius, cornerRadius); + clip.setRectXY(crop, cornerRadius.x, cornerRadius.y); } // if we hit this point then we either don't have rounded corners or we are going to rely diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h index a8bc4f762e..34f18c1fde 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.h +++ b/libs/renderengine/skia/SkiaGLRenderEngine.h @@ -94,7 +94,8 @@ private: inline SkRect getSkRect(const FloatRect& layer); inline SkRect getSkRect(const Rect& layer); inline std::pair getBoundsAndClip(const FloatRect& bounds, - const FloatRect& crop, float cornerRadius); + const FloatRect& crop, + const vec2& cornerRadius); inline bool layerHasBlur(const LayerSettings& layer, bool colorTransformModifiesAlpha); inline SkColor getSkColor(const vec4& color); inline SkM44 getSkM44(const mat4& matrix); diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index 3340857472..f2897308af 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -444,7 +444,9 @@ public: const ubyte4& backgroundColor) { const Rect casterRect(castingLayer.geometry.boundaries); Region casterRegion = Region(casterRect); - const float casterCornerRadius = castingLayer.geometry.roundedCornersRadius; + const float casterCornerRadius = (castingLayer.geometry.roundedCornersRadius.x + + castingLayer.geometry.roundedCornersRadius.y) / + 2.0; if (casterCornerRadius > 0.0f) { // ignore the corners if a corner radius is set Rect cornerRect(casterCornerRadius, casterCornerRadius); @@ -1129,7 +1131,7 @@ void RenderEngineTest::fillRedBufferWithRoundedCorners() { renderengine::LayerSettings layer; layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; layer.geometry.boundaries = fullscreenRect().toFloatRect(); - layer.geometry.roundedCornersRadius = 5.0f; + layer.geometry.roundedCornersRadius = {5.0f, 5.0f}; layer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect(); SourceVariant::fillColor(layer, 1.0f, 0.0f, 0.0f, this); layer.alpha = 1.0f; @@ -2161,7 +2163,7 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) { casterBounds.offsetBy(shadowLength + 1, shadowLength + 1); renderengine::LayerSettings castingLayer; castingLayer.geometry.boundaries = casterBounds.toFloatRect(); - castingLayer.geometry.roundedCornersRadius = 3.0f; + castingLayer.geometry.roundedCornersRadius = {3.0f, 3.0f}; castingLayer.geometry.roundedCornersCrop = casterBounds.toFloatRect(); castingLayer.alpha = 1.0f; renderengine::ShadowSettings settings = @@ -2249,7 +2251,8 @@ TEST_P(RenderEngineTest, testRoundedCornersCrop) { renderengine::LayerSettings redLayer; redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; redLayer.geometry.boundaries = fullscreenRect().toFloatRect(); - redLayer.geometry.roundedCornersRadius = 5.0f; + redLayer.geometry.roundedCornersRadius = {5.0f, 5.0f}; + redLayer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect(); // Red background. redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f); @@ -2261,7 +2264,7 @@ TEST_P(RenderEngineTest, testRoundedCornersCrop) { renderengine::LayerSettings greenLayer; greenLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; greenLayer.geometry.boundaries = fullscreenRect().toFloatRect(); - greenLayer.geometry.roundedCornersRadius = 5.0f; + greenLayer.geometry.roundedCornersRadius = {5.0f, 5.0f}; // Bottom right corner is not going to be rounded. greenLayer.geometry.roundedCornersCrop = Rect(DEFAULT_DISPLAY_WIDTH / 3, DEFAULT_DISPLAY_HEIGHT / 3, DEFAULT_DISPLAY_HEIGHT, @@ -2298,7 +2301,7 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCrop) { renderengine::LayerSettings redLayer; redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; redLayer.geometry.boundaries = fullscreenRect().toFloatRect(); - redLayer.geometry.roundedCornersRadius = 5.0f; + redLayer.geometry.roundedCornersRadius = {5.0f, 5.0f}; redLayer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect(); // Red background. redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f); @@ -2343,7 +2346,7 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) { renderengine::LayerSettings redLayer; redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; redLayer.geometry.boundaries = FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, 32); - redLayer.geometry.roundedCornersRadius = 64; + redLayer.geometry.roundedCornersRadius = {64.0f, 64.0f}; redLayer.geometry.roundedCornersCrop = FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, 128); // Red background. redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f); @@ -2364,6 +2367,49 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) { expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH / 2, 31), 255, 0, 0, 255); } +TEST_P(RenderEngineTest, testRoundedCornersXY) { + if (GetParam()->type() != renderengine::RenderEngine::RenderEngineType::SKIA_GL) { + GTEST_SKIP(); + } + + initializeRenderEngine(); + + renderengine::DisplaySettings settings; + settings.physicalDisplay = fullscreenRect(); + settings.clip = fullscreenRect(); + settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR; + + std::vector layers; + + renderengine::LayerSettings redLayer; + redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; + redLayer.geometry.boundaries = fullscreenRect().toFloatRect(); + redLayer.geometry.roundedCornersRadius = {5.0f, 20.0f}; + redLayer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect(); + // Red background. + redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f); + redLayer.alpha = 1.0f; + + layers.push_back(redLayer); + + invokeDraw(settings, layers); + + // Due to roundedCornersRadius, the corners are untouched. + expectBufferColor(Point(0, 0), 0, 0, 0, 0); + expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 0), 0, 0, 0, 0); + expectBufferColor(Point(0, DEFAULT_DISPLAY_HEIGHT - 1), 0, 0, 0, 0); + expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, DEFAULT_DISPLAY_HEIGHT - 1), 0, 0, 0, 0); + + // Y-axis draws a larger radius, check that its untouched as well + expectBufferColor(Point(0, DEFAULT_DISPLAY_HEIGHT - 5), 0, 0, 0, 0); + expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, DEFAULT_DISPLAY_HEIGHT - 5), 0, 0, 0, 0); + expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 5), 0, 0, 0, 0); + expectBufferColor(Point(0, 5), 0, 0, 0, 0); + + // middle should be red + expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH / 2, DEFAULT_DISPLAY_HEIGHT / 2), 255, 0, 0, 255); +} + TEST_P(RenderEngineTest, testClear) { initializeRenderEngine(); diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 80df3996ce..cd77465f66 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -409,7 +409,7 @@ void Layer::prepareBasicGeometryCompositionState() { const auto& drawingState{getDrawingState()}; const auto alpha = static_cast(getAlpha()); const bool opaque = isOpaque(drawingState); - const bool usesRoundedCorners = getRoundedCornerState().radius != 0.f; + const bool usesRoundedCorners = hasRoundedCorners(); auto blendMode = Hwc2::IComposerClient::BlendMode::NONE; if (!opaque || alpha != 1.0f) { @@ -485,7 +485,7 @@ void Layer::preparePerFrameCompositionState() { compositionState->hasProtectedContent = isProtected(); compositionState->dimmingEnabled = isDimmingEnabled(); - const bool usesRoundedCorners = getRoundedCornerState().radius != 0.f; + const bool usesRoundedCorners = hasRoundedCorners(); compositionState->isOpaque = isOpaque(drawingState) && !usesRoundedCorners && getAlpha() == 1.0_hf; @@ -1954,27 +1954,22 @@ Layer::RoundedCornerState Layer::getRoundedCornerState() const { const auto& parent = mDrawingParent.promote(); if (parent != nullptr) { parentSettings = parent->getRoundedCornerState(); - if (parentSettings.radius > 0) { + if (parentSettings.hasRoundedCorners()) { ui::Transform t = getActiveTransform(getDrawingState()); t = t.inverse(); parentSettings.cropRect = t.transform(parentSettings.cropRect); - // The rounded corners shader only accepts 1 corner radius for performance reasons, - // but a transform matrix can define horizontal and vertical scales. - // Let's take the average between both of them and pass into the shader, practically we - // never do this type of transformation on windows anyway. - auto scaleX = sqrtf(t[0][0] * t[0][0] + t[0][1] * t[0][1]); - auto scaleY = sqrtf(t[1][0] * t[1][0] + t[1][1] * t[1][1]); - parentSettings.radius *= (scaleX + scaleY) / 2.0f; + parentSettings.radius.x *= t.getScaleX(); + parentSettings.radius.y *= t.getScaleY(); } } // Get layer settings Rect layerCropRect = getCroppedBufferSize(getDrawingState()); - const float radius = getDrawingState().cornerRadius; + const vec2 radius(getDrawingState().cornerRadius, getDrawingState().cornerRadius); RoundedCornerState layerSettings(layerCropRect.toFloatRect(), radius); - const bool layerSettingsValid = layerSettings.radius > 0 && layerCropRect.isValid(); + const bool layerSettingsValid = layerSettings.hasRoundedCorners() && layerCropRect.isValid(); - if (layerSettingsValid && parentSettings.radius > 0) { + if (layerSettingsValid && parentSettings.hasRoundedCorners()) { // If the parent and the layer have rounded corner settings, use the parent settings if the // parent crop is entirely inside the layer crop. // This has limitations and cause rendering artifacts. See b/200300845 for correct fix. @@ -1988,7 +1983,7 @@ Layer::RoundedCornerState Layer::getRoundedCornerState() const { } } else if (layerSettingsValid) { return layerSettings; - } else if (parentSettings.radius > 0) { + } else if (parentSettings.hasRoundedCorners()) { return parentSettings; } return {}; @@ -2109,7 +2104,8 @@ void Layer::writeToProtoDrawingState(LayerProto* layerInfo) { layerInfo->set_effective_scaling_mode(getEffectiveScalingMode()); layerInfo->set_requested_corner_radius(getDrawingState().cornerRadius); - layerInfo->set_corner_radius(getRoundedCornerState().radius); + layerInfo->set_corner_radius( + (getRoundedCornerState().radius.x + getRoundedCornerState().radius.y) / 2.0); layerInfo->set_background_blur_radius(getBackgroundBlurRadius()); layerInfo->set_is_trusted_overlay(isTrustedOverlay()); LayerProtoHelper::writeToProtoDeprecated(transform, layerInfo->mutable_transform()); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 911ab78956..f1921df866 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -137,13 +137,14 @@ public: struct RoundedCornerState { RoundedCornerState() = default; - RoundedCornerState(FloatRect cropRect, float radius) + RoundedCornerState(const FloatRect& cropRect, const vec2& radius) : cropRect(cropRect), radius(radius) {} // Rounded rectangle in local layer coordinate space. FloatRect cropRect = FloatRect(); // Radius of the rounded rectangle. - float radius = 0.0f; + vec2 radius; + bool hasRoundedCorners() const { return radius.x > 0.0f && radius.y > 0.0f; } }; using FrameRate = scheduler::LayerInfo::FrameRate; @@ -599,7 +600,7 @@ public: // corner crop does not intersect with its own rounded corner crop. virtual RoundedCornerState getRoundedCornerState() const; - bool hasRoundedCorners() const override { return getRoundedCornerState().radius > .0f; } + bool hasRoundedCorners() const override { return getRoundedCornerState().hasRoundedCorners(); } virtual PixelFormat getPixelFormat() const { return PIXEL_FORMAT_NONE; } /** diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 19eaa198db..f6ebfe5af1 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -629,7 +629,8 @@ struct BaseLayerProperties { EXPECT_EQ(false, layer.source.buffer.isY410BT2020); EXPECT_EQ(true, layer.source.buffer.usePremultipliedAlpha); EXPECT_EQ(false, layer.source.buffer.isOpaque); - EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius); + EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x); + EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y); EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace); EXPECT_EQ(LayerProperties::COLOR[3], layer.alpha); return resultFuture; @@ -679,7 +680,8 @@ struct BaseLayerProperties { EXPECT_EQ(half3(LayerProperties::COLOR[0], LayerProperties::COLOR[1], LayerProperties::COLOR[2]), layer.source.solidColor); - EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius); + EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x); + EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y); EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace); EXPECT_EQ(LayerProperties::COLOR[3], layer.alpha); return resultFuture; @@ -757,7 +759,8 @@ struct CommonSecureLayerProperties : public BaseLayerProperties const renderengine::LayerSettings layer = layerSettings.back(); EXPECT_THAT(layer.source.buffer.buffer, IsNull()); EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), layer.source.solidColor); - EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius); + EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x); + EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y); EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace); EXPECT_EQ(1.0f, layer.alpha); return resultFuture; -- GitLab From e6983c70def698d669b1d6ea39cb35158d6196e7 Mon Sep 17 00:00:00 2001 From: HQ Liu Date: Tue, 19 Apr 2022 22:14:56 +0000 Subject: [PATCH 0122/1310] Add a minimum physical threshold for freeform gesture on touchpad Currently, TouchInputMapper uses the product of the size of the touchpad and a ratio (currently 0.25) to determine a two finger gesture is a freeform gesture or a swipe, that when the distance of two pointers is greater than the calculated value, it is a freeform gesture, otherwise, it is a swipe gesture. However, on a really small touch pad, such as Sony Dualshock4's, due to the common size of human fingers, almost all two finger gestures are decided to be freeform. Adding a minimun physical value for the freeform gesture, 30 mm, can solve the problem. Bug: 197146318 Test: manual test Change-Id: Ia4ff521cb2f47b587fa55a5d753b90d023a648ef --- .../reader/mapper/TouchInputMapper.cpp | 19 +- .../reader/mapper/TouchInputMapper.h | 4 +- .../inputflinger/tests/InputReader_test.cpp | 256 ++++++++++++++++++ 3 files changed, 274 insertions(+), 5 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index f07a3e8dd3..1b0c61ca1c 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -42,6 +42,8 @@ static constexpr nsecs_t TOUCH_DATA_TIMEOUT = ms2ns(20); // data. static constexpr nsecs_t STYLUS_DATA_LATENCY = ms2ns(10); +// Minimum width between two pointers to determine a gesture as freeform gesture in mm +static const float MIN_FREEFORM_GESTURE_WIDTH_IN_MILLIMETER = 30; // --- Static Definitions --- template @@ -936,6 +938,11 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) // Raw width and height in the natural orientation. const int32_t rawWidth = mRawPointerAxes.getRawWidth(); const int32_t rawHeight = mRawPointerAxes.getRawHeight(); + const int32_t rawXResolution = mRawPointerAxes.x.resolution; + const int32_t rawYResolution = mRawPointerAxes.y.resolution; + // Calculate the mean resolution when both x and y resolution are set, otherwise set it to 0. + const float rawMeanResolution = + (rawXResolution > 0 && rawYResolution > 0) ? (rawXResolution + rawYResolution) / 2 : 0; const bool viewportChanged = mViewport != *newViewport; bool skipViewportUpdate = false; @@ -1094,10 +1101,14 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) mConfig.pointerGestureZoomSpeedRatio * displayDiagonal / rawDiagonal; mPointerYZoomScale = mPointerXZoomScale; - // Max width between pointers to detect a swipe gesture is more than some fraction - // of the diagonal axis of the touch pad. Touches that are wider than this are - // translated into freeform gestures. - mPointerGestureMaxSwipeWidth = mConfig.pointerGestureSwipeMaxWidthRatio * rawDiagonal; + // Calculate the min freeform gesture width. It will be 0 when the resolution of any + // axis is non positive value. + const float minFreeformGestureWidth = + rawMeanResolution * MIN_FREEFORM_GESTURE_WIDTH_IN_MILLIMETER; + + mPointerGestureMaxSwipeWidth = + std::max(mConfig.pointerGestureSwipeMaxWidthRatio * rawDiagonal, + minFreeformGestureWidth); // Abort current pointer usages because the state has changed. const nsecs_t readTime = when; // synthetic event diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 714ad3fff6..3042be68c1 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -527,7 +527,9 @@ private: float mPointerXZoomScale; float mPointerYZoomScale; - // The maximum swipe width. + // The maximum swipe width between pointers to detect a swipe gesture + // in the number of pixels.Touches that are wider than this are translated + // into freeform gestures. float mPointerGestureMaxSwipeWidth; struct PointerDistanceHeapElement { diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 901b4deac6..75a3c28e40 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -373,8 +373,12 @@ public: mConfig.defaultPointerDisplayId = pointerDisplayId; } + void setPointerGestureEnabled(bool enabled) { mConfig.pointerGesturesEnabled = enabled; } + float getPointerGestureMovementSpeedRatio() { return mConfig.pointerGestureMovementSpeedRatio; } + float getPointerGestureZoomSpeedRatio() { return mConfig.pointerGestureZoomSpeedRatio; } + void setVelocityControlParams(const VelocityControlParameters& params) { mConfig.pointerVelocityControlParameters = params; mConfig.wheelVelocityControlParameters = params; @@ -9464,6 +9468,258 @@ TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) { ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); } +class MultiTouchPointerModeTest : public MultiTouchInputMapperTest { +protected: + float mPointerMovementScale; + float mPointerXZoomScale; + void preparePointerMode(int xAxisResolution, int yAxisResolution) { + addConfigurationProperty("touch.deviceType", "pointer"); + std::shared_ptr fakePointerController = + std::make_shared(); + fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); + fakePointerController->setPosition(0, 0); + fakePointerController->setButtonState(0); + prepareDisplay(DISPLAY_ORIENTATION_0); + + prepareAxes(POSITION); + prepareAbsoluteAxisResolution(xAxisResolution, yAxisResolution); + // In order to enable swipe and freeform gesture in pointer mode, pointer capture + // needs to be disabled, and the pointer gesture needs to be enabled. + mFakePolicy->setPointerCapture(false); + mFakePolicy->setPointerGestureEnabled(true); + mFakePolicy->setPointerController(fakePointerController); + + float rawDiagonal = hypotf(RAW_X_MAX - RAW_X_MIN, RAW_Y_MAX - RAW_Y_MIN); + float displayDiagonal = hypotf(DISPLAY_WIDTH, DISPLAY_HEIGHT); + mPointerMovementScale = + mFakePolicy->getPointerGestureMovementSpeedRatio() * displayDiagonal / rawDiagonal; + mPointerXZoomScale = + mFakePolicy->getPointerGestureZoomSpeedRatio() * displayDiagonal / rawDiagonal; + } + + void prepareAbsoluteAxisResolution(int xAxisResolution, int yAxisResolution) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, + /*flat*/ 0, + /*fuzz*/ 0, /*resolution*/ xAxisResolution); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, RAW_Y_MIN, RAW_Y_MAX, + /*flat*/ 0, + /*fuzz*/ 0, /*resolution*/ yAxisResolution); + } +}; + +/** + * Two fingers down on a pointer mode touch pad. The width + * of the two finger is larger than 1/4 of the touch pack diagnal length. However, it + * is smaller than the fixed min physical length 30mm. Two fingers' distance must + * be greater than the both value to be freeform gesture, so that after two + * fingers start to move downwards, the gesture should be swipe. + */ +TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthSwipe) { + // The min freeform gesture width is 25units/mm x 30mm = 750 + // which is greater than fraction of the diagnal length of the touchpad (349). + // Thus, MaxSwipWidth is 750. + preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/); + MultiTouchInputMapper& mapper = addMapperAndConfigure(); + NotifyMotionArgs motionArgs; + + // Two fingers down at once. + // The two fingers are 450 units apart, expects the current gesture to be PRESS + // Pointer's initial position is used the [0,0] coordinate. + int32_t x1 = 100, y1 = 125, x2 = 550, y2 = 125; + + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, x1, y1); + processMTSync(mapper); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, x2, y2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(1U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE( + assertPointerCoords(motionArgs.pointerCoords[0], 0, 0, 1, 0, 0, 0, 0, 0, 0, 0)); + + // It should be recognized as a SWIPE gesture when two fingers start to move down, + // that there should be 1 pointer. + int32_t movingDistance = 200; + y1 += movingDistance; + y2 += movingDistance; + + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, x1, y1); + processMTSync(mapper); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, x2, y2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(1U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], 0, + movingDistance * mPointerMovementScale, 1, 0, 0, 0, + 0, 0, 0, 0)); +} + +/** + * Two fingers down on a pointer mode touch pad. The width of the two finger is larger + * than the minimum freeform gesture width, 30mm. However, it is smaller than 1/4 of + * the touch pack diagnal length. Two fingers' distance must be greater than the both + * value to be freeform gesture, so that after two fingers start to move downwards, + * the gesture should be swipe. + */ +TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthLowResolutionSwipe) { + // The min freeform gesture width is 5units/mm x 30mm = 150 + // which is greater than fraction of the diagnal length of the touchpad (349). + // Thus, MaxSwipWidth is the fraction of the diagnal length, 349. + preparePointerMode(5 /*xResolution*/, 5 /*yResolution*/); + MultiTouchInputMapper& mapper = addMapperAndConfigure(); + NotifyMotionArgs motionArgs; + + // Two fingers down at once. + // The two fingers are 250 units apart, expects the current gesture to be PRESS + // Pointer's initial position is used the [0,0] coordinate. + int32_t x1 = 100, y1 = 125, x2 = 350, y2 = 125; + + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, x1, y1); + processMTSync(mapper); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, x2, y2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(1U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE( + assertPointerCoords(motionArgs.pointerCoords[0], 0, 0, 1, 0, 0, 0, 0, 0, 0, 0)); + + // It should be recognized as a SWIPE gesture when two fingers start to move down, + // and there should be 1 pointer. + int32_t movingDistance = 200; + y1 += movingDistance; + y2 += movingDistance; + + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, x1, y1); + processMTSync(mapper); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, x2, y2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(1U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + // New coordinate is the scaled relative coordinate from the initial coordinate. + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], 0, + movingDistance * mPointerMovementScale, 1, 0, 0, 0, + 0, 0, 0, 0)); +} + +/** + * Touch the touch pad with two fingers with a distance wider than the minimum freeform + * gesture width and 1/4 of the diagnal length of the touchpad. Expect to receive + * freeform gestures after two fingers start to move downwards. + */ +TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthFreeform) { + preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/); + MultiTouchInputMapper& mapper = addMapperAndConfigure(); + + NotifyMotionArgs motionArgs; + + // Two fingers down at once. Wider than the max swipe width. + // The gesture is expected to be PRESS, then transformed to FREEFORM + int32_t x1 = 100, y1 = 125, x2 = 900, y2 = 125; + + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, x1, y1); + processMTSync(mapper); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, x2, y2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(1U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + // One pointer for PRESS, and its coordinate is used as the origin for pointer coordinates. + ASSERT_NO_FATAL_FAILURE( + assertPointerCoords(motionArgs.pointerCoords[0], 0, 0, 1, 0, 0, 0, 0, 0, 0, 0)); + + int32_t movingDistance = 200; + + // Move two fingers down, expect a cancel event because gesture is changing to freeform, + // then two down events for two pointers. + y1 += movingDistance; + y2 += movingDistance; + + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, x1, y1); + processMTSync(mapper); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, x2, y2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + // The previous PRESS gesture is cancelled, because it is transformed to freeform + ASSERT_EQ(1U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(1U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(2U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN, motionArgs.action & AMOTION_EVENT_ACTION_MASK); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + // Two pointers' scaled relative coordinates from their initial centroid. + // Initial y coordinates are 0 as y1 and y2 have the same value. + float cookedX1 = (x1 - x2) / 2 * mPointerXZoomScale; + float cookedX2 = (x2 - x1) / 2 * mPointerXZoomScale; + // When pointers move, the new coordinates equal to the initial coordinates plus + // scaled moving distance. + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], cookedX1, + movingDistance * mPointerMovementScale, 1, 0, 0, 0, + 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], cookedX2, + movingDistance * mPointerMovementScale, 1, 0, 0, 0, + 0, 0, 0, 0)); + + // Move two fingers down again, expect one MOVE motion event. + y1 += movingDistance; + y2 += movingDistance; + + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, x1, y1); + processMTSync(mapper); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, x2, y2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(2U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], cookedX1, + movingDistance * 2 * mPointerMovementScale, 1, 0, 0, + 0, 0, 0, 0, 0)); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], cookedX2, + movingDistance * 2 * mPointerMovementScale, 1, 0, 0, + 0, 0, 0, 0, 0)); +} + // --- JoystickInputMapperTest --- class JoystickInputMapperTest : public InputMapperTest { -- GitLab From 83a0b43b8e2029a701d418789e11e6f1d4a35eff Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 13 Jul 2022 02:42:44 +0000 Subject: [PATCH 0123/1310] SF: Remove more BQL dead code Test: builds and boots Bug: 238781169 Change-Id: I063f6bdc54dcb16e6c5cbcf18373dfff953e16d6 --- services/surfaceflinger/Android.bp | 3 - services/surfaceflinger/BufferLayer.cpp | 1 - services/surfaceflinger/BufferLayer.h | 2 - .../surfaceflinger/BufferLayerConsumer.cpp | 500 ------------------ services/surfaceflinger/BufferLayerConsumer.h | 354 ------------- services/surfaceflinger/Layer.cpp | 2 - services/surfaceflinger/Layer.h | 1 - services/surfaceflinger/LayerRejecter.cpp | 143 ----- services/surfaceflinger/LayerRejecter.h | 41 -- services/surfaceflinger/MonitoredProducer.cpp | 170 ------ services/surfaceflinger/MonitoredProducer.h | 89 ---- services/surfaceflinger/SurfaceFlinger.cpp | 1 - services/surfaceflinger/SurfaceFlinger.h | 1 - .../SurfaceFlingerDefaultFactory.cpp | 14 - .../SurfaceFlingerDefaultFactory.h | 6 - .../surfaceflinger/SurfaceFlingerFactory.h | 6 - .../Tracing/tools/LayerTraceGenerator.cpp | 13 - .../fuzzer/surfaceflinger_fuzzers_utils.h | 12 - .../fuzzer/surfaceflinger_layer_fuzzer.cpp | 2 - .../tests/unittests/TestableSurfaceFlinger.h | 12 - 20 files changed, 1373 deletions(-) delete mode 100644 services/surfaceflinger/BufferLayerConsumer.cpp delete mode 100644 services/surfaceflinger/BufferLayerConsumer.h delete mode 100644 services/surfaceflinger/LayerRejecter.cpp delete mode 100644 services/surfaceflinger/LayerRejecter.h delete mode 100644 services/surfaceflinger/MonitoredProducer.cpp delete mode 100644 services/surfaceflinger/MonitoredProducer.h diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index 20e86a03e0..eb179958ba 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -142,7 +142,6 @@ filegroup { srcs: [ "BackgroundExecutor.cpp", "BufferLayer.cpp", - "BufferLayerConsumer.cpp", "BufferStateLayer.cpp", "ClientCache.cpp", "Client.cpp", @@ -168,10 +167,8 @@ filegroup { "WindowInfosListenerInvoker.cpp", "Layer.cpp", "LayerProtoHelper.cpp", - "LayerRejecter.cpp", "LayerRenderArea.cpp", "LayerVector.cpp", - "MonitoredProducer.cpp", "NativeWindowSurface.cpp", "RefreshRateOverlay.cpp", "RegionSamplingThread.cpp", diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp index fb15f1d06c..5d7d293580 100644 --- a/services/surfaceflinger/BufferLayer.cpp +++ b/services/surfaceflinger/BufferLayer.cpp @@ -53,7 +53,6 @@ #include "Colorizer.h" #include "DisplayDevice.h" #include "FrameTracer/FrameTracer.h" -#include "LayerRejecter.h" #include "TimeStats/TimeStats.h" namespace android { diff --git a/services/surfaceflinger/BufferLayer.h b/services/surfaceflinger/BufferLayer.h index 3bb0fb3eb4..7cc67a2a9e 100644 --- a/services/surfaceflinger/BufferLayer.h +++ b/services/surfaceflinger/BufferLayer.h @@ -34,14 +34,12 @@ #include #include -#include "BufferLayerConsumer.h" #include "Client.h" #include "DisplayHardware/HWComposer.h" #include "FrameTimeline.h" #include "FrameTracker.h" #include "Layer.h" #include "LayerVector.h" -#include "MonitoredProducer.h" #include "SurfaceFlinger.h" namespace android { diff --git a/services/surfaceflinger/BufferLayerConsumer.cpp b/services/surfaceflinger/BufferLayerConsumer.cpp deleted file mode 100644 index 7361a4fdef..0000000000 --- a/services/surfaceflinger/BufferLayerConsumer.cpp +++ /dev/null @@ -1,500 +0,0 @@ -/* - * Copyright (C) 2010 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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - -#undef LOG_TAG -#define LOG_TAG "BufferLayerConsumer" -#define ATRACE_TAG ATRACE_TAG_GRAPHICS -//#define LOG_NDEBUG 0 - -#include "BufferLayerConsumer.h" -#include "Layer.h" -#include "Scheduler/VsyncController.h" - -#include - -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace android { - -// Macros for including the BufferLayerConsumer name in log messages -#define BLC_LOGV(x, ...) ALOGV("[%s] " x, mName.string(), ##__VA_ARGS__) -#define BLC_LOGD(x, ...) ALOGD("[%s] " x, mName.string(), ##__VA_ARGS__) -//#define BLC_LOGI(x, ...) ALOGI("[%s] " x, mName.string(), ##__VA_ARGS__) -#define BLC_LOGW(x, ...) ALOGW("[%s] " x, mName.string(), ##__VA_ARGS__) -#define BLC_LOGE(x, ...) ALOGE("[%s] " x, mName.string(), ##__VA_ARGS__) - -static const mat4 mtxIdentity; - -BufferLayerConsumer::BufferLayerConsumer(const sp& bq, - renderengine::RenderEngine& engine, uint32_t tex, - Layer* layer) - : ConsumerBase(bq, false), - mCurrentCrop(Rect::EMPTY_RECT), - mCurrentTransform(0), - mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE), - mCurrentFence(Fence::NO_FENCE), - mCurrentTimestamp(0), - mCurrentDataSpace(ui::Dataspace::UNKNOWN), - mCurrentFrameNumber(0), - mCurrentTransformToDisplayInverse(false), - mCurrentSurfaceDamage(), - mCurrentApi(0), - mDefaultWidth(1), - mDefaultHeight(1), - mFilteringEnabled(true), - mRE(engine), - mTexName(tex), - mLayer(layer), - mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT) { - BLC_LOGV("BufferLayerConsumer"); - - memcpy(mCurrentTransformMatrix, mtxIdentity.asArray(), sizeof(mCurrentTransformMatrix)); - - mConsumer->setConsumerUsageBits(DEFAULT_USAGE_FLAGS); -} - -status_t BufferLayerConsumer::setDefaultBufferSize(uint32_t w, uint32_t h) { - Mutex::Autolock lock(mMutex); - if (mAbandoned) { - BLC_LOGE("setDefaultBufferSize: BufferLayerConsumer is abandoned!"); - return NO_INIT; - } - mDefaultWidth = w; - mDefaultHeight = h; - return mConsumer->setDefaultBufferSize(w, h); -} - -void BufferLayerConsumer::setContentsChangedListener(const wp& listener) { - setFrameAvailableListener(listener); - Mutex::Autolock lock(mMutex); - mContentsChangedListener = listener; -} - -status_t BufferLayerConsumer::updateTexImage(BufferRejecter* rejecter, nsecs_t expectedPresentTime, - bool* autoRefresh, bool* queuedBuffer, - uint64_t maxFrameNumber) { - ATRACE_CALL(); - BLC_LOGV("updateTexImage"); - Mutex::Autolock lock(mMutex); - - if (mAbandoned) { - BLC_LOGE("updateTexImage: BufferLayerConsumer is abandoned!"); - return NO_INIT; - } - - BufferItem item; - - // Acquire the next buffer. - // In asynchronous mode the list is guaranteed to be one buffer - // deep, while in synchronous mode we use the oldest buffer. - status_t err = acquireBufferLocked(&item, expectedPresentTime, maxFrameNumber); - if (err != NO_ERROR) { - if (err == BufferQueue::NO_BUFFER_AVAILABLE) { - err = NO_ERROR; - } else if (err == BufferQueue::PRESENT_LATER) { - // return the error, without logging - } else { - BLC_LOGE("updateTexImage: acquire failed: %s (%d)", strerror(-err), err); - } - return err; - } - - if (autoRefresh) { - *autoRefresh = item.mAutoRefresh; - } - - if (queuedBuffer) { - *queuedBuffer = item.mQueuedBuffer; - } - - // We call the rejecter here, in case the caller has a reason to - // not accept this buffer. This is used by SurfaceFlinger to - // reject buffers which have the wrong size - int slot = item.mSlot; - if (rejecter && rejecter->reject(mSlots[slot].mGraphicBuffer, item)) { - releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer); - return BUFFER_REJECTED; - } - - // Release the previous buffer. - err = updateAndReleaseLocked(item, &mPendingRelease); - if (err != NO_ERROR) { - return err; - } - return err; -} - -void BufferLayerConsumer::setReleaseFence(const sp& fence) { - if (!fence->isValid()) { - return; - } - - auto slot = mPendingRelease.isPending ? mPendingRelease.currentTexture : mCurrentTexture; - if (slot == BufferQueue::INVALID_BUFFER_SLOT) { - return; - } - - auto buffer = mPendingRelease.isPending ? mPendingRelease.graphicBuffer - : mCurrentTextureBuffer->getBuffer(); - auto err = addReleaseFence(slot, buffer, fence); - if (err != OK) { - BLC_LOGE("setReleaseFence: failed to add the fence: %s (%d)", strerror(-err), err); - } -} - -bool BufferLayerConsumer::releasePendingBuffer() { - if (!mPendingRelease.isPending) { - BLC_LOGV("Pending buffer already released"); - return false; - } - BLC_LOGV("Releasing pending buffer"); - Mutex::Autolock lock(mMutex); - status_t result = - releaseBufferLocked(mPendingRelease.currentTexture, mPendingRelease.graphicBuffer); - if (result < NO_ERROR) { - BLC_LOGE("releasePendingBuffer failed: %s (%d)", strerror(-result), result); - } - mPendingRelease = PendingRelease(); - return true; -} - -sp BufferLayerConsumer::getPrevFinalReleaseFence() const { - Mutex::Autolock lock(mMutex); - return ConsumerBase::mPrevFinalReleaseFence; -} - -status_t BufferLayerConsumer::acquireBufferLocked(BufferItem* item, nsecs_t presentWhen, - uint64_t maxFrameNumber) { - status_t err = ConsumerBase::acquireBufferLocked(item, presentWhen, maxFrameNumber); - if (err != NO_ERROR) { - return err; - } - - // If item->mGraphicBuffer is not null, this buffer has not been acquired - // before, so we need to clean up old references. - if (item->mGraphicBuffer != nullptr) { - std::lock_guard lock(mImagesMutex); - if (mImages[item->mSlot] == nullptr || mImages[item->mSlot]->getBuffer() == nullptr || - mImages[item->mSlot]->getBuffer()->getId() != item->mGraphicBuffer->getId()) { - mImages[item->mSlot] = std::make_shared< - renderengine::impl::ExternalTexture>(item->mGraphicBuffer, mRE, - renderengine::impl::ExternalTexture:: - Usage::READABLE); - } - } - - return NO_ERROR; -} - -status_t BufferLayerConsumer::updateAndReleaseLocked(const BufferItem& item, - PendingRelease* pendingRelease) { - status_t err = NO_ERROR; - - int slot = item.mSlot; - - BLC_LOGV("updateAndRelease: (slot=%d buf=%p) -> (slot=%d buf=%p)", mCurrentTexture, - (mCurrentTextureBuffer != nullptr && mCurrentTextureBuffer->getBuffer() != nullptr) - ? mCurrentTextureBuffer->getBuffer()->handle - : 0, - slot, mSlots[slot].mGraphicBuffer->handle); - - // Hang onto the pointer so that it isn't freed in the call to - // releaseBufferLocked() if we're in shared buffer mode and both buffers are - // the same. - - std::shared_ptr nextTextureBuffer; - { - std::lock_guard lock(mImagesMutex); - nextTextureBuffer = mImages[slot]; - } - - // release old buffer - if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) { - if (pendingRelease == nullptr) { - status_t status = - releaseBufferLocked(mCurrentTexture, mCurrentTextureBuffer->getBuffer()); - if (status < NO_ERROR) { - BLC_LOGE("updateAndRelease: failed to release buffer: %s (%d)", strerror(-status), - status); - err = status; - // keep going, with error raised [?] - } - } else { - pendingRelease->currentTexture = mCurrentTexture; - pendingRelease->graphicBuffer = mCurrentTextureBuffer->getBuffer(); - pendingRelease->isPending = true; - } - } - - // Update the BufferLayerConsumer state. - mCurrentTexture = slot; - mCurrentTextureBuffer = nextTextureBuffer; - mCurrentCrop = item.mCrop; - mCurrentTransform = item.mTransform; - mCurrentScalingMode = item.mScalingMode; - mCurrentTimestamp = item.mTimestamp; - mCurrentDataSpace = static_cast(item.mDataSpace); - mCurrentHdrMetadata = item.mHdrMetadata; - mCurrentFence = item.mFence; - mCurrentFenceTime = item.mFenceTime; - mCurrentFrameNumber = item.mFrameNumber; - mCurrentTransformToDisplayInverse = item.mTransformToDisplayInverse; - mCurrentSurfaceDamage = item.mSurfaceDamage; - mCurrentApi = item.mApi; - - computeCurrentTransformMatrixLocked(); - - return err; -} - -void BufferLayerConsumer::getTransformMatrix(float mtx[16]) { - Mutex::Autolock lock(mMutex); - memcpy(mtx, mCurrentTransformMatrix, sizeof(mCurrentTransformMatrix)); -} - -void BufferLayerConsumer::setFilteringEnabled(bool enabled) { - Mutex::Autolock lock(mMutex); - if (mAbandoned) { - BLC_LOGE("setFilteringEnabled: BufferLayerConsumer is abandoned!"); - return; - } - bool needsRecompute = mFilteringEnabled != enabled; - mFilteringEnabled = enabled; - - if (needsRecompute && mCurrentTextureBuffer == nullptr) { - BLC_LOGD("setFilteringEnabled called with mCurrentTextureBuffer == nullptr"); - } - - if (needsRecompute && mCurrentTextureBuffer != nullptr) { - computeCurrentTransformMatrixLocked(); - } -} - -void BufferLayerConsumer::computeCurrentTransformMatrixLocked() { - BLC_LOGV("computeCurrentTransformMatrixLocked"); - if (mCurrentTextureBuffer == nullptr || mCurrentTextureBuffer->getBuffer() == nullptr) { - BLC_LOGD("computeCurrentTransformMatrixLocked: " - "mCurrentTextureBuffer is nullptr"); - } - GLConsumer::computeTransformMatrix(mCurrentTransformMatrix, - mCurrentTextureBuffer == nullptr - ? nullptr - : mCurrentTextureBuffer->getBuffer(), - getCurrentCropLocked(), mCurrentTransform, - mFilteringEnabled); -} - -nsecs_t BufferLayerConsumer::getTimestamp() { - BLC_LOGV("getTimestamp"); - Mutex::Autolock lock(mMutex); - return mCurrentTimestamp; -} - -ui::Dataspace BufferLayerConsumer::getCurrentDataSpace() { - BLC_LOGV("getCurrentDataSpace"); - Mutex::Autolock lock(mMutex); - return mCurrentDataSpace; -} - -const HdrMetadata& BufferLayerConsumer::getCurrentHdrMetadata() const { - BLC_LOGV("getCurrentHdrMetadata"); - Mutex::Autolock lock(mMutex); - return mCurrentHdrMetadata; -} - -uint64_t BufferLayerConsumer::getFrameNumber() { - BLC_LOGV("getFrameNumber"); - Mutex::Autolock lock(mMutex); - return mCurrentFrameNumber; -} - -bool BufferLayerConsumer::getTransformToDisplayInverse() const { - Mutex::Autolock lock(mMutex); - return mCurrentTransformToDisplayInverse; -} - -const Region& BufferLayerConsumer::getSurfaceDamage() const { - return mCurrentSurfaceDamage; -} - -void BufferLayerConsumer::mergeSurfaceDamage(const Region& damage) { - if (damage.bounds() == Rect::INVALID_RECT || - mCurrentSurfaceDamage.bounds() == Rect::INVALID_RECT) { - mCurrentSurfaceDamage = Region::INVALID_REGION; - } else { - mCurrentSurfaceDamage |= damage; - } -} - -int BufferLayerConsumer::getCurrentApi() const { - Mutex::Autolock lock(mMutex); - return mCurrentApi; -} - -std::shared_ptr BufferLayerConsumer::getCurrentBuffer( - int* outSlot, sp* outFence) const { - Mutex::Autolock lock(mMutex); - - if (outSlot != nullptr) { - *outSlot = mCurrentTexture; - } - - if (outFence != nullptr) { - *outFence = mCurrentFence; - } - - return mCurrentTextureBuffer == nullptr ? nullptr : mCurrentTextureBuffer; -} - -Rect BufferLayerConsumer::getCurrentCrop() const { - Mutex::Autolock lock(mMutex); - return getCurrentCropLocked(); -} - -Rect BufferLayerConsumer::getCurrentCropLocked() const { - uint32_t width = mDefaultWidth; - uint32_t height = mDefaultHeight; - // If the buffer comes with a rotated bit for 90 (or 270) degrees, switch width/height in order - // to scale and crop correctly. - if (mCurrentTransform & NATIVE_WINDOW_TRANSFORM_ROT_90) { - width = mDefaultHeight; - height = mDefaultWidth; - } - - return (mCurrentScalingMode == NATIVE_WINDOW_SCALING_MODE_SCALE_CROP) - ? GLConsumer::scaleDownCrop(mCurrentCrop, width, height) - : mCurrentCrop; -} - -uint32_t BufferLayerConsumer::getCurrentTransform() const { - Mutex::Autolock lock(mMutex); - return mCurrentTransform; -} - -uint32_t BufferLayerConsumer::getCurrentScalingMode() const { - Mutex::Autolock lock(mMutex); - return mCurrentScalingMode; -} - -sp BufferLayerConsumer::getCurrentFence() const { - Mutex::Autolock lock(mMutex); - return mCurrentFence; -} - -std::shared_ptr BufferLayerConsumer::getCurrentFenceTime() const { - Mutex::Autolock lock(mMutex); - return mCurrentFenceTime; -} - -void BufferLayerConsumer::freeBufferLocked(int slotIndex) { - BLC_LOGV("freeBufferLocked: slotIndex=%d", slotIndex); - std::lock_guard lock(mImagesMutex); - if (slotIndex == mCurrentTexture) { - mCurrentTexture = BufferQueue::INVALID_BUFFER_SLOT; - } - mImages[slotIndex] = nullptr; - ConsumerBase::freeBufferLocked(slotIndex); -} - -void BufferLayerConsumer::onDisconnect() { - Mutex::Autolock lock(mMutex); - - if (mAbandoned) { - // Nothing to do if we're already abandoned. - return; - } - - mLayer->onDisconnect(); -} - -void BufferLayerConsumer::onSidebandStreamChanged() { - [[maybe_unused]] FrameAvailableListener* unsafeFrameAvailableListener = nullptr; - { - Mutex::Autolock lock(mFrameAvailableMutex); - unsafeFrameAvailableListener = mFrameAvailableListener.unsafe_get(); - } - sp listener; - { // scope for the lock - Mutex::Autolock lock(mMutex); - ALOG_ASSERT(unsafeFrameAvailableListener == mContentsChangedListener.unsafe_get()); - listener = mContentsChangedListener.promote(); - } - - if (listener != nullptr) { - listener->onSidebandStreamChanged(); - } -} - -void BufferLayerConsumer::onBufferAvailable(const BufferItem& item) { - if (item.mGraphicBuffer != nullptr && item.mSlot != BufferQueue::INVALID_BUFFER_SLOT) { - std::lock_guard lock(mImagesMutex); - const std::shared_ptr& oldImage = mImages[item.mSlot]; - if (oldImage == nullptr || oldImage->getBuffer() == nullptr || - oldImage->getBuffer()->getId() != item.mGraphicBuffer->getId()) { - mImages[item.mSlot] = std::make_shared< - renderengine::impl::ExternalTexture>(item.mGraphicBuffer, mRE, - renderengine::impl::ExternalTexture:: - Usage::READABLE); - } - } -} - -void BufferLayerConsumer::abandonLocked() { - BLC_LOGV("abandonLocked"); - mCurrentTextureBuffer = nullptr; - for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) { - std::lock_guard lock(mImagesMutex); - mImages[i] = nullptr; - } - ConsumerBase::abandonLocked(); -} - -status_t BufferLayerConsumer::setConsumerUsageBits(uint64_t usage) { - return ConsumerBase::setConsumerUsageBits(usage | DEFAULT_USAGE_FLAGS); -} - -void BufferLayerConsumer::dumpLocked(String8& result, const char* prefix) const { - result.appendFormat("%smTexName=%d mCurrentTexture=%d\n" - "%smCurrentCrop=[%d,%d,%d,%d] mCurrentTransform=%#x\n", - prefix, mTexName, mCurrentTexture, prefix, mCurrentCrop.left, - mCurrentCrop.top, mCurrentCrop.right, mCurrentCrop.bottom, - mCurrentTransform); - - ConsumerBase::dumpLocked(result, prefix); -} -}; // namespace android - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/BufferLayerConsumer.h b/services/surfaceflinger/BufferLayerConsumer.h deleted file mode 100644 index 23ad2a3f91..0000000000 --- a/services/surfaceflinger/BufferLayerConsumer.h +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Copyright (C) 2010 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. - */ - -#ifndef ANDROID_BUFFERLAYERCONSUMER_H -#define ANDROID_BUFFERLAYERCONSUMER_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace android { -// ---------------------------------------------------------------------------- - -class Layer; -class String8; - -namespace renderengine { -class RenderEngine; -} // namespace renderengine - -/* - * BufferLayerConsumer consumes buffers of graphics data from a BufferQueue, - * and makes them available to RenderEngine as a texture. - * - * A typical usage pattern is to call updateTexImage() when a new frame is - * desired. If a new frame is available, the frame is latched. If not, the - * previous contents are retained. The texture is attached and updated after - * bindTextureImage() is called. - * - * All calls to updateTexImage must be made with RenderEngine being current. - * The texture is attached to the TEXTURE_EXTERNAL texture target. - */ -class BufferLayerConsumer : public ConsumerBase { -public: - static const status_t BUFFER_REJECTED = UNKNOWN_ERROR + 8; - - class BufferRejecter { - friend class BufferLayerConsumer; - virtual bool reject(const sp& buf, const BufferItem& item) = 0; - - protected: - virtual ~BufferRejecter() {} - }; - - struct ContentsChangedListener : public FrameAvailableListener { - virtual void onSidebandStreamChanged() = 0; - }; - - // BufferLayerConsumer constructs a new BufferLayerConsumer object. The - // tex parameter indicates the name of the RenderEngine texture to which - // images are to be streamed. - BufferLayerConsumer(const sp& bq, renderengine::RenderEngine& engine, - uint32_t tex, Layer* layer); - - // Sets the contents changed listener. This should be used instead of - // ConsumerBase::setFrameAvailableListener(). - void setContentsChangedListener(const wp& listener); - - // updateTexImage acquires the most recently queued buffer, and sets the - // image contents of the target texture to it. - // - // This call may only be made while RenderEngine is current. - // - // This calls doFenceWait to ensure proper synchronization unless native - // fence is supported. - // - // Unlike the GLConsumer version, this version takes a functor that may be - // used to reject the newly acquired buffer. It also does not bind the - // RenderEngine texture until bindTextureImage is called. - status_t updateTexImage(BufferRejecter* rejecter, nsecs_t expectedPresentTime, - bool* autoRefresh, bool* queuedBuffer, uint64_t maxFrameNumber); - - // setReleaseFence stores a fence that will signal when the current buffer - // is no longer being read. This fence will be returned to the producer - // when the current buffer is released by updateTexImage(). Multiple - // fences can be set for a given buffer; they will be merged into a single - // union fence. - void setReleaseFence(const sp& fence); - - bool releasePendingBuffer(); - - sp getPrevFinalReleaseFence() const; - - // See GLConsumer::getTransformMatrix. - void getTransformMatrix(float mtx[16]); - - // getTimestamp retrieves the timestamp associated with the texture image - // set by the most recent call to updateTexImage. - // - // The timestamp is in nanoseconds, and is monotonically increasing. Its - // other semantics (zero point, etc) are source-dependent and should be - // documented by the source. - int64_t getTimestamp(); - - // getDataSpace retrieves the DataSpace associated with the texture image - // set by the most recent call to updateTexImage. - ui::Dataspace getCurrentDataSpace(); - - // getCurrentHdrMetadata retrieves the HDR metadata associated with the - // texture image set by the most recent call to updateTexImage. - const HdrMetadata& getCurrentHdrMetadata() const; - - // getFrameNumber retrieves the frame number associated with the texture - // image set by the most recent call to updateTexImage. - // - // The frame number is an incrementing counter set to 0 at the creation of - // the BufferQueue associated with this consumer. - uint64_t getFrameNumber(); - - bool getTransformToDisplayInverse() const; - - // must be called from SF main thread - const Region& getSurfaceDamage() const; - - // Merge the given damage region into the current damage region value. - void mergeSurfaceDamage(const Region& damage); - - // getCurrentApi retrieves the API which queues the current buffer. - int getCurrentApi() const; - - // See GLConsumer::setDefaultBufferSize. - status_t setDefaultBufferSize(uint32_t width, uint32_t height); - - // setFilteringEnabled sets whether the transform matrix should be computed - // for use with bilinear filtering. - void setFilteringEnabled(bool enabled); - - // getCurrentBuffer returns the buffer associated with the current image. - // When outSlot is not nullptr, the current buffer slot index is also - // returned. Simiarly, when outFence is not nullptr, the current output - // fence is returned. - std::shared_ptr getCurrentBuffer( - int* outSlot = nullptr, sp* outFence = nullptr) const; - - // getCurrentCrop returns the cropping rectangle of the current buffer. - Rect getCurrentCrop() const; - - // getCurrentTransform returns the transform of the current buffer. - uint32_t getCurrentTransform() const; - - // getCurrentScalingMode returns the scaling mode of the current buffer. - uint32_t getCurrentScalingMode() const; - - // getCurrentFence returns the fence indicating when the current buffer is - // ready to be read from. - sp getCurrentFence() const; - - // getCurrentFence returns the FenceTime indicating when the current - // buffer is ready to be read from. - std::shared_ptr getCurrentFenceTime() const; - - // setConsumerUsageBits overrides the ConsumerBase method to OR - // DEFAULT_USAGE_FLAGS to usage. - status_t setConsumerUsageBits(uint64_t usage); - void onBufferAvailable(const BufferItem& item) EXCLUDES(mImagesMutex); - -protected: - // abandonLocked overrides the ConsumerBase method to clear - // mCurrentTextureImage in addition to the ConsumerBase behavior. - virtual void abandonLocked() EXCLUDES(mImagesMutex); - - // dumpLocked overrides the ConsumerBase method to dump BufferLayerConsumer- - // specific info in addition to the ConsumerBase behavior. - virtual void dumpLocked(String8& result, const char* prefix) const; - - // See ConsumerBase::acquireBufferLocked - virtual status_t acquireBufferLocked(BufferItem* item, nsecs_t presentWhen, - uint64_t maxFrameNumber = 0) override - EXCLUDES(mImagesMutex); - - bool canUseImageCrop(const Rect& crop) const; - - struct PendingRelease { - PendingRelease() : isPending(false), currentTexture(-1), graphicBuffer() {} - - bool isPending; - int currentTexture; - sp graphicBuffer; - }; - - // This releases the buffer in the slot referenced by mCurrentTexture, - // then updates state to refer to the BufferItem, which must be a - // newly-acquired buffer. If pendingRelease is not null, the parameters - // which would have been passed to releaseBufferLocked upon the successful - // completion of the method will instead be returned to the caller, so that - // it may call releaseBufferLocked itself later. - status_t updateAndReleaseLocked(const BufferItem& item, - PendingRelease* pendingRelease = nullptr) - EXCLUDES(mImagesMutex); - -private: - // Utility class for managing GraphicBuffer references into renderengine - class Image { - public: - Image(const sp& graphicBuffer, renderengine::RenderEngine& engine); - virtual ~Image(); - const sp& graphicBuffer() { return mGraphicBuffer; } - - private: - // mGraphicBuffer is the buffer that was used to create this image. - sp mGraphicBuffer; - // Back-reference into renderengine to initiate cleanup. - renderengine::RenderEngine& mRE; - DISALLOW_COPY_AND_ASSIGN(Image); - }; - - // freeBufferLocked frees up the given buffer slot. If the slot has been - // initialized this will release the reference to the GraphicBuffer in - // that slot. Otherwise it has no effect. - // - // This method must be called with mMutex locked. - virtual void freeBufferLocked(int slotIndex) EXCLUDES(mImagesMutex); - - // IConsumerListener interface - void onDisconnect() override; - void onSidebandStreamChanged() override; - void addAndGetFrameTimestamps(const NewFrameEventsEntry*, FrameEventHistoryDelta*) override {} - - // computeCurrentTransformMatrixLocked computes the transform matrix for the - // current texture. It uses mCurrentTransform and the current GraphicBuffer - // to compute this matrix and stores it in mCurrentTransformMatrix. - // mCurrentTextureImage must not be nullptr. - void computeCurrentTransformMatrixLocked(); - - // getCurrentCropLocked returns the cropping rectangle of the current buffer. - Rect getCurrentCropLocked() const; - - // The default consumer usage flags that BufferLayerConsumer always sets on its - // BufferQueue instance; these will be OR:d with any additional flags passed - // from the BufferLayerConsumer user. In particular, BufferLayerConsumer will always - // consume buffers as hardware textures. - static const uint64_t DEFAULT_USAGE_FLAGS = GraphicBuffer::USAGE_HW_TEXTURE; - - // mCurrentTextureBuffer is the buffer containing the current texture. It's - // possible that this buffer is not associated with any buffer slot, so we - // must track it separately in order to support the getCurrentBuffer method. - std::shared_ptr mCurrentTextureBuffer; - - // mCurrentCrop is the crop rectangle that applies to the current texture. - // It gets set each time updateTexImage is called. - Rect mCurrentCrop; - - // mCurrentTransform is the transform identifier for the current texture. It - // gets set each time updateTexImage is called. - uint32_t mCurrentTransform; - - // mCurrentScalingMode is the scaling mode for the current texture. It gets - // set each time updateTexImage is called. - uint32_t mCurrentScalingMode; - - // mCurrentFence is the fence received from BufferQueue in updateTexImage. - sp mCurrentFence; - - // The FenceTime wrapper around mCurrentFence. - std::shared_ptr mCurrentFenceTime{FenceTime::NO_FENCE}; - - // mCurrentTransformMatrix is the transform matrix for the current texture. - // It gets computed by computeTransformMatrix each time updateTexImage is - // called. - float mCurrentTransformMatrix[16]; - - // mCurrentTimestamp is the timestamp for the current texture. It - // gets set each time updateTexImage is called. - int64_t mCurrentTimestamp; - - // mCurrentDataSpace is the dataspace for the current texture. It - // gets set each time updateTexImage is called. - ui::Dataspace mCurrentDataSpace; - - // mCurrentHdrMetadata is the HDR metadata for the current texture. It - // gets set each time updateTexImage is called. - HdrMetadata mCurrentHdrMetadata; - - // mCurrentFrameNumber is the frame counter for the current texture. - // It gets set each time updateTexImage is called. - uint64_t mCurrentFrameNumber; - - // Indicates this buffer must be transformed by the inverse transform of the screen - // it is displayed onto. This is applied after BufferLayerConsumer::mCurrentTransform. - // This must be set/read from SurfaceFlinger's main thread. - bool mCurrentTransformToDisplayInverse; - - // The portion of this surface that has changed since the previous frame - Region mCurrentSurfaceDamage; - - int mCurrentApi; - - uint32_t mDefaultWidth, mDefaultHeight; - - // mFilteringEnabled indicates whether the transform matrix is computed for - // use with bilinear filtering. It defaults to true and is changed by - // setFilteringEnabled(). - bool mFilteringEnabled; - - renderengine::RenderEngine& mRE; - - // mTexName is the name of the RenderEngine texture to which streamed - // images will be bound when bindTexImage is called. It is set at - // construction time. - const uint32_t mTexName; - - // The layer for this BufferLayerConsumer. Always check mAbandoned before accessing. - Layer* mLayer GUARDED_BY(mMutex); - - wp mContentsChangedListener; - - // mCurrentTexture is the buffer slot index of the buffer that is currently - // bound to the RenderEngine texture. It is initialized to INVALID_BUFFER_SLOT, - // indicating that no buffer slot is currently bound to the texture. Note, - // however, that a value of INVALID_BUFFER_SLOT does not necessarily mean - // that no buffer is bound to the texture. A call to setBufferCount will - // reset mCurrentTexture to INVALID_BUFFER_SLOT. - int mCurrentTexture; - - // Shadow buffer cache for cleaning up renderengine references. - std::shared_ptr - mImages[BufferQueueDefs::NUM_BUFFER_SLOTS] GUARDED_BY(mImagesMutex); - - // Separate mutex guarding the shadow buffer cache. - // mImagesMutex can be manipulated with binder threads (e.g. onBuffersAllocated) - // which is contentious enough that we can't just use mMutex. - mutable std::mutex mImagesMutex; - - // A release that is pending on the receipt of a new release fence from - // presentDisplay - PendingRelease mPendingRelease; -}; - -// ---------------------------------------------------------------------------- -}; // namespace android - -#endif // ANDROID_BUFFERLAYERCONSUMER_H diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 6e9dad1131..3ff43f963b 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -70,8 +70,6 @@ #include "FrameTimeline.h" #include "FrameTracer/FrameTracer.h" #include "LayerProtoHelper.h" -#include "LayerRejecter.h" -#include "MonitoredProducer.h" #include "SurfaceFlinger.h" #include "TimeStats/TimeStats.h" #include "TunnelModeEnabledReporter.h" diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index fb9b663d8a..59040111c9 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -53,7 +53,6 @@ #include "DisplayHardware/HWComposer.h" #include "FrameTracker.h" #include "LayerVector.h" -#include "MonitoredProducer.h" #include "RenderArea.h" #include "Scheduler/LayerInfo.h" #include "SurfaceFlinger.h" diff --git a/services/surfaceflinger/LayerRejecter.cpp b/services/surfaceflinger/LayerRejecter.cpp deleted file mode 100644 index 1c0263ba0b..0000000000 --- a/services/surfaceflinger/LayerRejecter.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2011 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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - -#include "LayerRejecter.h" - -#include -#include - -#define DEBUG_RESIZE 0 - -namespace android { - -LayerRejecter::LayerRejecter(Layer::State& front, Layer::State& current, - bool& recomputeVisibleRegions, bool stickySet, const std::string& name, - bool transformToDisplayInverse) - : mFront(front), - mCurrent(current), - mRecomputeVisibleRegions(recomputeVisibleRegions), - mStickyTransformSet(stickySet), - mName(name), - mTransformToDisplayInverse(transformToDisplayInverse) {} - -bool LayerRejecter::reject(const sp& buf, const BufferItem& item) { - if (buf == nullptr) { - return false; - } - - uint32_t bufWidth = buf->getWidth(); - uint32_t bufHeight = buf->getHeight(); - - // check that we received a buffer of the right size - // (Take the buffer's orientation into account) - if (item.mTransform & ui::Transform::ROT_90) { - std::swap(bufWidth, bufHeight); - } - - if (mTransformToDisplayInverse) { - uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags(); - if (invTransform & ui::Transform::ROT_90) { - std::swap(bufWidth, bufHeight); - } - } - - int actualScalingMode = item.mScalingMode; - bool isFixedSize = actualScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE; - if (mFront.active_legacy != mFront.requested_legacy) { - if (isFixedSize || - (bufWidth == mFront.requested_legacy.w && bufHeight == mFront.requested_legacy.h)) { - // Here we pretend the transaction happened by updating the - // current and drawing states. Drawing state is only accessed - // in this thread, no need to have it locked - mFront.active_legacy = mFront.requested_legacy; - - // We also need to update the current state so that - // we don't end-up overwriting the drawing state with - // this stale current state during the next transaction - // - // NOTE: We don't need to hold the transaction lock here - // because State::active_legacy is only accessed from this thread. - mCurrent.active_legacy = mFront.active_legacy; - mCurrent.modified = true; - - // recompute visible region - mRecomputeVisibleRegions = true; - - if (mFront.crop != mFront.requestedCrop) { - mFront.crop = mFront.requestedCrop; - mCurrent.crop = mFront.requestedCrop; - mRecomputeVisibleRegions = true; - } - } - - ALOGD_IF(DEBUG_RESIZE, - "[%s] latchBuffer/reject: buffer (%ux%u, tr=%02x), scalingMode=%d\n" - " drawing={ active_legacy ={ wh={%4u,%4u} crop={%4d,%4d,%4d,%4d} " - "(%4d,%4d) " - "}\n" - " requested_legacy={ wh={%4u,%4u} }}\n", - mName.c_str(), bufWidth, bufHeight, item.mTransform, item.mScalingMode, - mFront.active_legacy.w, mFront.active_legacy.h, mFront.crop.left, mFront.crop.top, - mFront.crop.right, mFront.crop.bottom, mFront.crop.getWidth(), - mFront.crop.getHeight(), mFront.requested_legacy.w, mFront.requested_legacy.h); - } - - if (!isFixedSize && !mStickyTransformSet) { - if (mFront.active_legacy.w != bufWidth || mFront.active_legacy.h != bufHeight) { - // reject this buffer - ALOGE("[%s] rejecting buffer: " - "bufWidth=%d, bufHeight=%d, front.active_legacy.{w=%d, h=%d}", - mName.c_str(), bufWidth, bufHeight, mFront.active_legacy.w, - mFront.active_legacy.h); - return true; - } - } - - // if the transparent region has changed (this test is - // conservative, but that's fine, worst case we're doing - // a bit of extra work), we latch the new one and we - // trigger a visible-region recompute. - // - // We latch the transparent region here, instead of above where we latch - // the rest of the geometry because it is only content but not necessarily - // resize dependent. - if (!mFront.activeTransparentRegion_legacy.hasSameRects( - mFront.requestedTransparentRegion_legacy)) { - mFront.activeTransparentRegion_legacy = mFront.requestedTransparentRegion_legacy; - - // We also need to update the current state so that - // we don't end-up overwriting the drawing state with - // this stale current state during the next transaction - // - // NOTE: We don't need to hold the transaction lock here - // because State::active_legacy is only accessed from this thread. - mCurrent.activeTransparentRegion_legacy = mFront.activeTransparentRegion_legacy; - - // recompute visible region - mRecomputeVisibleRegions = true; - } - - return false; -} - -} // namespace android - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/LayerRejecter.h b/services/surfaceflinger/LayerRejecter.h deleted file mode 100644 index 4981f451d9..0000000000 --- a/services/surfaceflinger/LayerRejecter.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2007 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 "Layer.h" -#include "BufferLayerConsumer.h" - -namespace android { - -class LayerRejecter : public BufferLayerConsumer::BufferRejecter { -public: - LayerRejecter(Layer::State& front, Layer::State& current, bool& recomputeVisibleRegions, - bool stickySet, const std::string& name, - bool transformToDisplayInverse); - - virtual bool reject(const sp&, const BufferItem&); - -private: - Layer::State& mFront; - Layer::State& mCurrent; - bool& mRecomputeVisibleRegions; - const bool mStickyTransformSet; - const std::string& mName; - const bool mTransformToDisplayInverse; -}; - -} // namespace android diff --git a/services/surfaceflinger/MonitoredProducer.cpp b/services/surfaceflinger/MonitoredProducer.cpp deleted file mode 100644 index df76f50112..0000000000 --- a/services/surfaceflinger/MonitoredProducer.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2014 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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - -#include "MonitoredProducer.h" -#include "Layer.h" -#include "SurfaceFlinger.h" - -#include "Scheduler/MessageQueue.h" - -namespace android { - -MonitoredProducer::MonitoredProducer(const sp& producer, - const sp& flinger, - const wp& layer) : - mProducer(producer), - mFlinger(flinger), - mLayer(layer) {} - -MonitoredProducer::~MonitoredProducer() {} - -status_t MonitoredProducer::requestBuffer(int slot, sp* buf) { - return mProducer->requestBuffer(slot, buf); -} - -status_t MonitoredProducer::setMaxDequeuedBufferCount( - int maxDequeuedBuffers) { - return mProducer->setMaxDequeuedBufferCount(maxDequeuedBuffers); -} - -status_t MonitoredProducer::setAsyncMode(bool async) { - return mProducer->setAsyncMode(async); -} - -status_t MonitoredProducer::dequeueBuffer(int* slot, sp* fence, uint32_t w, uint32_t h, - PixelFormat format, uint64_t usage, - uint64_t* outBufferAge, - FrameEventHistoryDelta* outTimestamps) { - return mProducer->dequeueBuffer(slot, fence, w, h, format, usage, outBufferAge, outTimestamps); -} - -status_t MonitoredProducer::detachBuffer(int slot) { - return mProducer->detachBuffer(slot); -} - -status_t MonitoredProducer::detachNextBuffer(sp* outBuffer, - sp* outFence) { - return mProducer->detachNextBuffer(outBuffer, outFence); -} - -status_t MonitoredProducer::attachBuffer(int* outSlot, - const sp& buffer) { - return mProducer->attachBuffer(outSlot, buffer); -} - -status_t MonitoredProducer::queueBuffer(int slot, const QueueBufferInput& input, - QueueBufferOutput* output) { - return mProducer->queueBuffer(slot, input, output); -} - -status_t MonitoredProducer::cancelBuffer(int slot, const sp& fence) { - return mProducer->cancelBuffer(slot, fence); -} - -int MonitoredProducer::query(int what, int* value) { - return mProducer->query(what, value); -} - -status_t MonitoredProducer::connect(const sp& listener, - int api, bool producerControlledByApp, QueueBufferOutput* output) { - return mProducer->connect(listener, api, producerControlledByApp, output); -} - -status_t MonitoredProducer::disconnect(int api, DisconnectMode mode) { - return mProducer->disconnect(api, mode); -} - -status_t MonitoredProducer::setSidebandStream(const sp& stream) { - return mProducer->setSidebandStream(stream); -} - -void MonitoredProducer::allocateBuffers(uint32_t width, uint32_t height, - PixelFormat format, uint64_t usage) { - mProducer->allocateBuffers(width, height, format, usage); -} - -status_t MonitoredProducer::allowAllocation(bool allow) { - return mProducer->allowAllocation(allow); -} - -status_t MonitoredProducer::setGenerationNumber(uint32_t generationNumber) { - return mProducer->setGenerationNumber(generationNumber); -} - -String8 MonitoredProducer::getConsumerName() const { - return mProducer->getConsumerName(); -} - -status_t MonitoredProducer::setSharedBufferMode(bool sharedBufferMode) { - return mProducer->setSharedBufferMode(sharedBufferMode); -} - -status_t MonitoredProducer::setAutoRefresh(bool autoRefresh) { - return mProducer->setAutoRefresh(autoRefresh); -} - -status_t MonitoredProducer::setDequeueTimeout(nsecs_t timeout) { - return mProducer->setDequeueTimeout(timeout); -} - -status_t MonitoredProducer::setLegacyBufferDrop(bool drop) { - return mProducer->setLegacyBufferDrop(drop); -} - -status_t MonitoredProducer::getLastQueuedBuffer(sp* outBuffer, - sp* outFence, float outTransformMatrix[16]) { - return mProducer->getLastQueuedBuffer(outBuffer, outFence, - outTransformMatrix); -} - -status_t MonitoredProducer::getLastQueuedBuffer(sp* outBuffer, sp* outFence, - Rect* outRect, uint32_t* outTransform) { - return mProducer->getLastQueuedBuffer(outBuffer, outFence, outRect, outTransform); -} - -void MonitoredProducer::getFrameTimestamps(FrameEventHistoryDelta* outDelta) { - mProducer->getFrameTimestamps(outDelta); -} - -status_t MonitoredProducer::getUniqueId(uint64_t* outId) const { - return mProducer->getUniqueId(outId); -} - -status_t MonitoredProducer::getConsumerUsage(uint64_t* outUsage) const { - return mProducer->getConsumerUsage(outUsage); -} - -status_t MonitoredProducer::setAutoPrerotation(bool autoPrerotation) { - return mProducer->setAutoPrerotation(autoPrerotation); -} - -IBinder* MonitoredProducer::onAsBinder() { - return this; -} - -sp MonitoredProducer::getLayer() const { - return mLayer.promote(); -} - -// --------------------------------------------------------------------------- -}; // namespace android - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/MonitoredProducer.h b/services/surfaceflinger/MonitoredProducer.h deleted file mode 100644 index 3778277fd3..0000000000 --- a/services/surfaceflinger/MonitoredProducer.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2014 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. - */ - -#ifndef ANDROID_MONITORED_PRODUCER_H -#define ANDROID_MONITORED_PRODUCER_H - -#include - -namespace android { - -class IProducerListener; -class NativeHandle; -class SurfaceFlinger; -class Layer; - -// MonitoredProducer wraps an IGraphicBufferProducer so that SurfaceFlinger will -// be notified upon its destruction -class MonitoredProducer : public BnGraphicBufferProducer { -public: - MonitoredProducer(const sp& producer, - const sp& flinger, - const wp& layer); - virtual ~MonitoredProducer(); - - // From IGraphicBufferProducer - virtual status_t requestBuffer(int slot, sp* buf); - virtual status_t setMaxDequeuedBufferCount(int maxDequeuedBuffers); - virtual status_t setAsyncMode(bool async); - virtual status_t dequeueBuffer(int* slot, sp* fence, uint32_t w, uint32_t h, - PixelFormat format, uint64_t usage, uint64_t* outBufferAge, - FrameEventHistoryDelta* outTimestamps); - virtual status_t detachBuffer(int slot); - virtual status_t detachNextBuffer(sp* outBuffer, - sp* outFence); - virtual status_t attachBuffer(int* outSlot, - const sp& buffer); - virtual status_t queueBuffer(int slot, const QueueBufferInput& input, - QueueBufferOutput* output); - virtual status_t cancelBuffer(int slot, const sp& fence); - virtual int query(int what, int* value); - virtual status_t connect(const sp& token, int api, - bool producerControlledByApp, QueueBufferOutput* output); - virtual status_t disconnect(int api, DisconnectMode mode); - virtual status_t setSidebandStream(const sp& stream); - virtual void allocateBuffers(uint32_t width, uint32_t height, - PixelFormat format, uint64_t usage); - virtual status_t allowAllocation(bool allow); - virtual status_t setGenerationNumber(uint32_t generationNumber); - virtual String8 getConsumerName() const override; - virtual status_t setDequeueTimeout(nsecs_t timeout) override; - virtual status_t setLegacyBufferDrop(bool drop) override; - virtual status_t getLastQueuedBuffer(sp* outBuffer, - sp* outFence, float outTransformMatrix[16]) override; - virtual status_t getLastQueuedBuffer(sp* outBuffer, sp* outFence, - Rect* outRect, uint32_t* outTransform) override; - virtual IBinder* onAsBinder(); - virtual status_t setSharedBufferMode(bool sharedBufferMode) override; - virtual status_t setAutoRefresh(bool autoRefresh) override; - virtual void getFrameTimestamps(FrameEventHistoryDelta *outDelta) override; - virtual status_t getUniqueId(uint64_t* outId) const override; - virtual status_t getConsumerUsage(uint64_t* outUsage) const override; - virtual status_t setAutoPrerotation(bool autoPrerotation) override; - - // The Layer which created this producer, and on which queued Buffer's will be displayed. - sp getLayer() const; - -private: - sp mProducer; - sp mFlinger; - // The Layer which created this producer, and on which queued Buffer's will be displayed. - wp mLayer; -}; - -}; // namespace android - -#endif // ANDROID_MONITORED_PRODUCER_H diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index ab5bb77e42..a202dda1a3 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -129,7 +129,6 @@ #include "LayerProtoHelper.h" #include "LayerRenderArea.h" #include "LayerVector.h" -#include "MonitoredProducer.h" #include "MutexUtils.h" #include "NativeWindowSurface.h" #include "RefreshRateOverlay.h" diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index ed6c8ce6d7..bd4330f257 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -377,7 +377,6 @@ private: friend class FpsReporter; friend class TunnelModeEnabledReporter; friend class Layer; - friend class MonitoredProducer; friend class RefreshRateOverlay; friend class RegionSamplingThread; friend class LayerRenderArea; diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp index 39a5d0fc05..2399a39241 100644 --- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp +++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp @@ -22,14 +22,12 @@ #include #include -#include "BufferLayerConsumer.h" #include "BufferStateLayer.h" #include "ContainerLayer.h" #include "DisplayDevice.h" #include "EffectLayer.h" #include "FrameTracer/FrameTracer.h" #include "Layer.h" -#include "MonitoredProducer.h" #include "NativeWindowSurface.h" #include "StartPropertySetThread.h" #include "SurfaceFlingerDefaultFactory.h" @@ -83,18 +81,6 @@ void DefaultFactory::createBufferQueue(sp* outProducer, BufferQueue::createBufferQueue(outProducer, outConsumer, consumerIsSurfaceFlinger); } -sp DefaultFactory::createMonitoredProducer( - const sp& producer, const sp& flinger, - const wp& layer) { - return new MonitoredProducer(producer, flinger, layer); -} - -sp DefaultFactory::createBufferLayerConsumer( - const sp& consumer, renderengine::RenderEngine& renderEngine, - uint32_t textureName, Layer* layer) { - return new BufferLayerConsumer(consumer, renderEngine, textureName, layer); -} - std::unique_ptr DefaultFactory::createNativeWindowSurface( const sp& producer) { return surfaceflinger::impl::createNativeWindowSurface(producer); diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h index 173ca816dd..4000e09bbc 100644 --- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h +++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h @@ -38,12 +38,6 @@ public: void createBufferQueue(sp* outProducer, sp* outConsumer, bool consumerIsSurfaceFlinger) override; - sp createMonitoredProducer(const sp&, - const sp&, - const wp&) override; - sp createBufferLayerConsumer(const sp&, - renderengine::RenderEngine&, uint32_t tex, - Layer*) override; std::unique_ptr createNativeWindowSurface( const sp&) override; std::unique_ptr createCompositionEngine() override; diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h index e117e9694f..77a75c4e25 100644 --- a/services/surfaceflinger/SurfaceFlingerFactory.h +++ b/services/surfaceflinger/SurfaceFlingerFactory.h @@ -85,12 +85,6 @@ public: virtual void createBufferQueue(sp* outProducer, sp* outConsumer, bool consumerIsSurfaceFlinger) = 0; - virtual sp createMonitoredProducer(const sp&, - const sp&, - const wp&) = 0; - virtual sp createBufferLayerConsumer(const sp&, - renderengine::RenderEngine&, - uint32_t tex, Layer*) = 0; virtual std::unique_ptr createNativeWindowSurface( const sp&) = 0; diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index 5e20b74f9d..c4b1b0fb13 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -71,19 +71,6 @@ public: sp* /* outConsumer */, bool /* consumerIsSurfaceFlinger */) override {} - sp createMonitoredProducer( - const sp& /* producer */, - const sp& /* flinger */, const wp& /* layer */) override { - return nullptr; - } - - sp createBufferLayerConsumer( - const sp& /* consumer */, - renderengine::RenderEngine& /* renderEngine */, uint32_t /* textureName */, - Layer* /* layer */) override { - return nullptr; - } - std::unique_ptr createNativeWindowSurface( const sp& /* producer */) override { return nullptr; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 456a4981c9..60c46a9b08 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -333,18 +333,6 @@ public: mCreateBufferQueue(outProducer, outConsumer, consumerIsSurfaceFlinger); } - sp createMonitoredProducer(const sp &producer, - const sp &flinger, - const wp &layer) override { - return new MonitoredProducer(producer, flinger, layer); - } - - sp createBufferLayerConsumer(const sp &consumer, - renderengine::RenderEngine &renderEngine, - uint32_t textureName, Layer *layer) override { - return new BufferLayerConsumer(consumer, renderEngine, textureName, layer); - } - std::unique_ptr createNativeWindowSurface( const sp &producer) override { if (!mCreateNativeWindowSurface) return nullptr; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp index 4aef0173b6..62a7997aca 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp @@ -18,9 +18,7 @@ #include #include #include -#include #include -#include #include #include #include diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 3a05e2fb34..e8380eb20e 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -111,18 +111,6 @@ public: mCreateBufferQueue(outProducer, outConsumer, consumerIsSurfaceFlinger); } - sp createMonitoredProducer(const sp& producer, - const sp& flinger, - const wp& layer) override { - return new MonitoredProducer(producer, flinger, layer); - } - - sp createBufferLayerConsumer(const sp& consumer, - renderengine::RenderEngine& renderEngine, - uint32_t textureName, Layer* layer) override { - return new BufferLayerConsumer(consumer, renderEngine, textureName, layer); - } - std::unique_ptr createNativeWindowSurface( const sp& producer) override { if (!mCreateNativeWindowSurface) return nullptr; -- GitLab From 4f94c1ad3fe73105a34178af3a086aa571b8bf78 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 13 Jul 2022 07:29:51 -0700 Subject: [PATCH 0124/1310] Avoid KeyedVector and String8 in PropertyMap Update the external-facing APIs of PropertyMap to reduce the dependency on libutils. Here we remove String8 and KeyedVector from the header file. Eventually the Tokenizer can be moved to libinput as well, which would allow us to further reduce this dependency. Bug: 233177558 Test: atest libinput_tests inputflinger_tests Change-Id: I58965ccf7dbd5514c8526f15e713f0e26e498c83 --- include/input/PropertyMap.h | 27 ++++---- libs/input/Keyboard.cpp | 23 +++---- libs/input/PropertyMap.cpp | 47 ++++++------- libs/input/PropertyMap_fuzz.cpp | 32 +++------ services/inputflinger/host/InputDriver.cpp | 19 +++--- services/inputflinger/reader/EventHub.cpp | 11 ++-- .../inputflinger/reader/include/EventHub.h | 1 - .../reader/mapper/CursorInputMapper.cpp | 9 ++- .../reader/mapper/KeyboardInputMapper.cpp | 8 +-- .../mapper/RotaryEncoderInputMapper.cpp | 4 +- .../reader/mapper/SensorInputMapper.cpp | 2 +- .../reader/mapper/TouchInputMapper.cpp | 66 +++++++++---------- .../inputflinger/tests/InputReader_test.cpp | 12 ++-- 13 files changed, 118 insertions(+), 143 deletions(-) diff --git a/include/input/PropertyMap.h b/include/input/PropertyMap.h index 451918bb46..b1e3f85ed5 100644 --- a/include/input/PropertyMap.h +++ b/include/input/PropertyMap.h @@ -18,10 +18,8 @@ #define _UTILS_PROPERTY_MAP_H #include -#include -#include -#include #include +#include namespace android { @@ -58,30 +56,27 @@ public: /* Adds a property. * Replaces the property with the same key if it is already present. */ - void addProperty(const String8& key, const String8& value); - - /* Returns true if the property map contains the specified key. */ - bool hasProperty(const String8& key) const; + void addProperty(const std::string& key, const std::string& value); /* Gets the value of a property and parses it. * Returns true and sets outValue if the key was found and its value was parsed successfully. * Otherwise returns false and does not modify outValue. (Also logs a warning.) */ - bool tryGetProperty(const String8& key, String8& outValue) const; - bool tryGetProperty(const String8& key, bool& outValue) const; - bool tryGetProperty(const String8& key, int32_t& outValue) const; - bool tryGetProperty(const String8& key, float& outValue) const; + bool tryGetProperty(const std::string& key, std::string& outValue) const; + bool tryGetProperty(const std::string& key, bool& outValue) const; + bool tryGetProperty(const std::string& key, int32_t& outValue) const; + bool tryGetProperty(const std::string& key, float& outValue) const; /* Adds all values from the specified property map. */ void addAll(const PropertyMap* map); - /* Gets the underlying property map. */ - inline const KeyedVector& getProperties() const { return mProperties; } - /* Loads a property map from a file. */ static android::base::Result> load(const char* filename); private: + /* Returns true if the property map contains the specified key. */ + bool hasProperty(const std::string& key) const; + class Parser { PropertyMap* mMap; Tokenizer* mTokenizer; @@ -95,11 +90,11 @@ private: status_t parseType(); status_t parseKey(); status_t parseKeyProperty(); - status_t parseModifier(const String8& token, int32_t* outMetaState); + status_t parseModifier(const std::string& token, int32_t* outMetaState); status_t parseCharacterLiteral(char16_t* outCharacter); }; - KeyedVector mProperties; + std::unordered_map mProperties; }; } // namespace android diff --git a/libs/input/Keyboard.cpp b/libs/input/Keyboard.cpp index c3f5151fd1..3f8467d818 100644 --- a/libs/input/Keyboard.cpp +++ b/libs/input/Keyboard.cpp @@ -49,25 +49,23 @@ status_t KeyMap::load(const InputDeviceIdentifier& deviceIdentifier, const PropertyMap* deviceConfiguration) { // Use the configured key layout if available. if (deviceConfiguration) { - String8 keyLayoutName; - if (deviceConfiguration->tryGetProperty(String8("keyboard.layout"), - keyLayoutName)) { + std::string keyLayoutName; + if (deviceConfiguration->tryGetProperty("keyboard.layout", keyLayoutName)) { status_t status = loadKeyLayout(deviceIdentifier, keyLayoutName.c_str()); if (status == NAME_NOT_FOUND) { ALOGE("Configuration for keyboard device '%s' requested keyboard layout '%s' but " - "it was not found.", - deviceIdentifier.name.c_str(), keyLayoutName.string()); + "it was not found.", + deviceIdentifier.name.c_str(), keyLayoutName.c_str()); } } - String8 keyCharacterMapName; - if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"), - keyCharacterMapName)) { + std::string keyCharacterMapName; + if (deviceConfiguration->tryGetProperty("keyboard.characterMap", keyCharacterMapName)) { status_t status = loadKeyCharacterMap(deviceIdentifier, keyCharacterMapName.c_str()); if (status == NAME_NOT_FOUND) { ALOGE("Configuration for keyboard device '%s' requested keyboard character " - "map '%s' but it was not found.", - deviceIdentifier.name.c_str(), keyCharacterMapName.string()); + "map '%s' but it was not found.", + deviceIdentifier.name.c_str(), keyCharacterMapName.c_str()); } } @@ -165,7 +163,7 @@ bool isKeyboardSpecialFunction(const PropertyMap* config) { return false; } bool isSpecialFunction = false; - config->tryGetProperty(String8("keyboard.specialFunction"), isSpecialFunction); + config->tryGetProperty("keyboard.specialFunction", isSpecialFunction); return isSpecialFunction; } @@ -180,8 +178,7 @@ bool isEligibleBuiltInKeyboard(const InputDeviceIdentifier& deviceIdentifier, if (deviceConfiguration) { bool builtIn = false; - if (deviceConfiguration->tryGetProperty(String8("keyboard.builtIn"), builtIn) - && builtIn) { + if (deviceConfiguration->tryGetProperty("keyboard.builtIn", builtIn) && builtIn) { return true; } } diff --git a/libs/input/PropertyMap.cpp b/libs/input/PropertyMap.cpp index a842166761..662e568ac0 100644 --- a/libs/input/PropertyMap.cpp +++ b/libs/input/PropertyMap.cpp @@ -17,6 +17,7 @@ #define LOG_TAG "PropertyMap" #include where F is a function that maps T to U. + template + constexpr auto transform(F&& f) const& { + using U = std::remove_cv_t>; + if (has_value()) return Optional(std::invoke(std::forward(f), value())); + return Optional(); + } + + template + constexpr auto transform(F&& f) & { + using U = std::remove_cv_t>; + if (has_value()) return Optional(std::invoke(std::forward(f), value())); + return Optional(); + } + + template + constexpr auto transform(F&& f) const&& { + using U = std::invoke_result_t; + if (has_value()) return Optional(std::invoke(std::forward(f), std::move(value()))); + return Optional(); + } + + template + constexpr auto transform(F&& f) && { + using U = std::invoke_result_t; + if (has_value()) return Optional(std::invoke(std::forward(f), std::move(value()))); + return Optional(); + } +}; + +// Deduction guide. +template +Optional(T) -> Optional; + +} // namespace android::ftl diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp index c010a2e58a..8f89e7d151 100644 --- a/libs/ftl/Android.bp +++ b/libs/ftl/Android.bp @@ -20,6 +20,7 @@ cc_test { "fake_guard_test.cpp", "flags_test.cpp", "future_test.cpp", + "optional_test.cpp", "small_map_test.cpp", "small_vector_test.cpp", "static_vector_test.cpp", diff --git a/libs/ftl/optional_test.cpp b/libs/ftl/optional_test.cpp new file mode 100644 index 0000000000..6a8c8f9d3d --- /dev/null +++ b/libs/ftl/optional_test.cpp @@ -0,0 +1,77 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include +#include +#include + +using namespace std::placeholders; +using namespace std::string_literals; + +namespace android::test { + +using ftl::Optional; +using ftl::StaticVector; + +TEST(Optional, Transform) { + // Empty. + EXPECT_EQ(std::nullopt, Optional().transform([](int) { return 0; })); + + // By value. + EXPECT_EQ(0, Optional(0).transform([](int x) { return x; })); + EXPECT_EQ(100, Optional(99).transform([](int x) { return x + 1; })); + EXPECT_EQ("0b100"s, Optional(4).transform(std::bind(ftl::to_string, _1, ftl::Radix::kBin))); + + // By reference. + { + Optional opt = 'x'; + EXPECT_EQ('z', opt.transform([](char& c) { + c = 'y'; + return 'z'; + })); + + EXPECT_EQ('y', opt); + } + + // By rvalue reference. + { + std::string out; + EXPECT_EQ("xyz"s, Optional("abc"s).transform([&out](std::string&& str) { + out = std::move(str); + return "xyz"s; + })); + + EXPECT_EQ(out, "abc"s); + } + + // Chaining. + EXPECT_EQ(14u, Optional(StaticVector{"upside"s, "down"s}) + .transform([](StaticVector&& v) { + v.push_back("cake"s); + return v; + }) + .transform([](const StaticVector& v) { + return std::accumulate(v.begin(), v.end(), std::string()); + }) + .transform([](const std::string& s) { return s.length(); })); +} + +} // namespace android::test -- GitLab From 3fbc8ca62c1146470f190a2f55e99101269e4f3c Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Fri, 19 Aug 2022 15:35:24 +0000 Subject: [PATCH 0222/1310] Clear blur regions when region sampling Bug: b/243138863 Test: existing tests pass Change-Id: I0ef3114a15bf465ab8c1e326a157c37520f72f90 --- services/surfaceflinger/SurfaceFlinger.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 6b4cfa1249..29186dff87 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -6597,6 +6597,7 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( // blurs is already a pretty good approximation. if (regionSampling) { settings->backgroundBlurRadius = 0; + settings->blurRegions.clear(); } captureResults.capturedHdrLayers |= isHdrLayer(layer); -- GitLab From 54494bd0d003c49841334fc16c52b79c4be42fcb Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Tue, 2 Aug 2022 13:37:14 -0700 Subject: [PATCH 0223/1310] FTL: Generalize SmallMap lookup transformer Bug: 185536303 Test: ftl_test Change-Id: Idde9d842a4404095e839fbdbfca1ded416d95263 --- include/ftl/small_map.h | 52 +++++----------- include/ftl/unit.h | 61 +++++++++++++++++++ libs/ftl/optional_test.cpp | 8 +++ libs/ftl/small_map_test.cpp | 30 +++++---- services/surfaceflinger/SurfaceFlinger.cpp | 9 +-- .../surfaceflinger_scheduler_fuzzer.cpp | 3 +- 6 files changed, 107 insertions(+), 56 deletions(-) create mode 100644 include/ftl/unit.h diff --git a/include/ftl/small_map.h b/include/ftl/small_map.h index 5217e76064..49cde7fedc 100644 --- a/include/ftl/small_map.h +++ b/include/ftl/small_map.h @@ -17,11 +17,11 @@ #pragma once #include +#include #include #include #include -#include #include #include @@ -47,7 +47,7 @@ namespace android::ftl { // assert(!map.dynamic()); // // assert(map.contains(123)); -// assert(map.get(42, [](const std::string& s) { return s.size(); }) == 3u); +// assert(map.get(42).transform([](const std::string& s) { return s.size(); }) == 3u); // // const auto opt = map.get(-1); // assert(opt); @@ -59,7 +59,7 @@ namespace android::ftl { // map.emplace_or_replace(0, "vanilla", 2u, 3u); // assert(map.dynamic()); // -// assert(map == SmallMap(ftl::init::map(-1, "xyz")(0, "nil")(42, "???")(123, "abc"))); +// assert(map == SmallMap(ftl::init::map(-1, "xyz"sv)(0, "nil"sv)(42, "???"sv)(123, "abc"sv))); // template > class SmallMap final { @@ -123,9 +123,7 @@ class SmallMap final { const_iterator cend() const { return map_.cend(); } // Returns whether a mapping exists for the given key. - bool contains(const key_type& key) const { - return get(key, [](const mapped_type&) {}); - } + bool contains(const key_type& key) const { return get(key).has_value(); } // Returns a reference to the value for the given key, or std::nullopt if the key was not found. // @@ -139,46 +137,24 @@ class SmallMap final { // ref.get() = 'D'; // assert(d == 'D'); // - auto get(const key_type& key) const -> std::optional> { - return get(key, [](const mapped_type& v) { return std::cref(v); }); - } - - auto get(const key_type& key) -> std::optional> { - return get(key, [](mapped_type& v) { return std::ref(v); }); + auto get(const key_type& key) const -> Optional> { + for (const auto& [k, v] : *this) { + if (KeyEqual{}(k, key)) { + return std::cref(v); + } + } + return {}; } - // Returns the result R of a unary operation F on (a constant or mutable reference to) the value - // for the given key, or std::nullopt if the key was not found. If F has a return type of void, - // then the Boolean result indicates whether the key was found. - // - // ftl::SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z'); - // - // assert(map.get('c', [](char c) { return std::toupper(c); }) == 'Z'); - // assert(map.get('c', [](char& c) { c = std::toupper(c); })); - // - template > - auto get(const key_type& key, F f) const - -> std::conditional_t, bool, std::optional> { + auto get(const key_type& key) -> Optional> { for (auto& [k, v] : *this) { if (KeyEqual{}(k, key)) { - if constexpr (std::is_void_v) { - f(v); - return true; - } else { - return f(v); - } + return std::ref(v); } } - return {}; } - template - auto get(const key_type& key, F f) { - return std::as_const(*this).get( - key, [&f](const mapped_type& v) { return f(const_cast(v)); }); - } - // Returns an iterator to an existing mapping for the given key, or the end() iterator otherwise. const_iterator find(const key_type& key) const { return const_cast(*this).find(key); } iterator find(const key_type& key) { return find(key, begin()); } @@ -286,7 +262,7 @@ bool operator==(const SmallMap& lhs, const SmallMap& rhs for (const auto& [k, v] : lhs) { const auto& lv = v; - if (!rhs.get(k, [&lv](const auto& rv) { return lv == rv; }).value_or(false)) { + if (!rhs.get(k).transform([&lv](const W& rv) { return lv == rv; }).value_or(false)) { return false; } } diff --git a/include/ftl/unit.h b/include/ftl/unit.h new file mode 100644 index 0000000000..e38230b976 --- /dev/null +++ b/include/ftl/unit.h @@ -0,0 +1,61 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +namespace android::ftl { + +// The unit type, and its only value. +constexpr struct Unit { +} unit; + +constexpr bool operator==(Unit, Unit) { + return true; +} + +constexpr bool operator!=(Unit, Unit) { + return false; +} + +// Adapts a function object F to return Unit. The return value of F is ignored. +// +// As a practical use, the function passed to ftl::Optional::transform is not allowed to return +// void (cf. https://wg21.link/P0798R8#mapping-functions-returning-void), but may return Unit if +// only its side effects are meaningful: +// +// ftl::Optional opt = "food"s; +// opt.transform(ftl::unit_fn([](std::string& str) { str.pop_back(); })); +// assert(opt == "foo"s); +// +template +struct UnitFn { + F f; + + template + Unit operator()(Args&&... args) { + return f(std::forward(args)...), unit; + } +}; + +template +constexpr auto unit_fn(F&& f) -> UnitFn> { + return {std::forward(f)}; +} + +} // namespace android::ftl diff --git a/libs/ftl/optional_test.cpp b/libs/ftl/optional_test.cpp index 6a8c8f9d3d..ede159a955 100644 --- a/libs/ftl/optional_test.cpp +++ b/libs/ftl/optional_test.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -62,6 +63,13 @@ TEST(Optional, Transform) { EXPECT_EQ(out, "abc"s); } + // No return value. + { + Optional opt = "food"s; + EXPECT_EQ(ftl::unit, opt.transform(ftl::unit_fn([](std::string& str) { str.pop_back(); }))); + EXPECT_EQ(opt, "foo"s); + } + // Chaining. EXPECT_EQ(14u, Optional(StaticVector{"upside"s, "down"s}) .transform([](StaticVector&& v) { diff --git a/libs/ftl/small_map_test.cpp b/libs/ftl/small_map_test.cpp index 1740a2b54c..634877f672 100644 --- a/libs/ftl/small_map_test.cpp +++ b/libs/ftl/small_map_test.cpp @@ -15,12 +15,15 @@ */ #include +#include #include #include #include +#include using namespace std::string_literals; +using namespace std::string_view_literals; namespace android::test { @@ -38,7 +41,7 @@ TEST(SmallMap, Example) { EXPECT_TRUE(map.contains(123)); - EXPECT_EQ(map.get(42, [](const std::string& s) { return s.size(); }), 3u); + EXPECT_EQ(map.get(42).transform([](const std::string& s) { return s.size(); }), 3u); const auto opt = map.get(-1); ASSERT_TRUE(opt); @@ -50,7 +53,7 @@ TEST(SmallMap, Example) { map.emplace_or_replace(0, "vanilla", 2u, 3u); EXPECT_TRUE(map.dynamic()); - EXPECT_EQ(map, SmallMap(ftl::init::map(-1, "xyz")(0, "nil")(42, "???")(123, "abc"))); + EXPECT_EQ(map, SmallMap(ftl::init::map(-1, "xyz"sv)(0, "nil"sv)(42, "???"sv)(123, "abc"sv))); } TEST(SmallMap, Construct) { @@ -70,7 +73,7 @@ TEST(SmallMap, Construct) { EXPECT_EQ(map.max_size(), 5u); EXPECT_FALSE(map.dynamic()); - EXPECT_EQ(map, SmallMap(ftl::init::map(123, "abc")(456, "def")(789, "ghi"))); + EXPECT_EQ(map, SmallMap(ftl::init::map(123, "abc"sv)(456, "def"sv)(789, "ghi"sv))); } { // In-place constructor with different types. @@ -81,7 +84,7 @@ TEST(SmallMap, Construct) { EXPECT_EQ(map.max_size(), 5u); EXPECT_FALSE(map.dynamic()); - EXPECT_EQ(map, SmallMap(ftl::init::map(42, "???")(123, "abc")(-1, "\0\0\0"))); + EXPECT_EQ(map, SmallMap(ftl::init::map(42, "???"sv)(123, "abc"sv)(-1, ""sv))); } { // In-place constructor with implicit size. @@ -92,7 +95,7 @@ TEST(SmallMap, Construct) { EXPECT_EQ(map.max_size(), 3u); EXPECT_FALSE(map.dynamic()); - EXPECT_EQ(map, SmallMap(ftl::init::map(-1, "\0\0\0")(42, "???")(123, "abc"))); + EXPECT_EQ(map, SmallMap(ftl::init::map(-1, ""sv)(42, "???"sv)(123, "abc"sv))); } } @@ -108,7 +111,7 @@ TEST(SmallMap, Assign) { { // Convertible types; same capacity. SmallMap map1 = ftl::init::map('M', "mega")('G', "giga"); - const SmallMap map2 = ftl::init::map('T', "tera")('P', "peta"); + const SmallMap map2 = ftl::init::map('T', "tera"sv)('P', "peta"sv); map1 = map2; EXPECT_EQ(map1, map2); @@ -147,7 +150,7 @@ TEST(SmallMap, UniqueKeys) { } } -TEST(SmallMap, Find) { +TEST(SmallMap, Get) { { // Constant reference. const SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C'); @@ -172,14 +175,15 @@ TEST(SmallMap, Find) { EXPECT_EQ(d, 'D'); } { - // Constant unary operation. + // Immutable transform operation. const SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z'); - EXPECT_EQ(map.get('c', [](char c) { return std::toupper(c); }), 'Z'); + EXPECT_EQ(map.get('c').transform([](char c) { return std::toupper(c); }), 'Z'); } { - // Mutable unary operation. + // Mutable transform operation. SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z'); - EXPECT_TRUE(map.get('c', [](char& c) { c = std::toupper(c); })); + EXPECT_EQ(map.get('c').transform(ftl::unit_fn([](char& c) { c = std::toupper(c); })), + ftl::unit); EXPECT_EQ(map, SmallMap(ftl::init::map('c', 'Z')('b', 'y')('a', 'x'))); } @@ -247,7 +251,7 @@ TEST(SmallMap, TryReplace) { } { // Replacement arguments can refer to the replaced mapping. - const auto ref = map.get(2, [](const auto& s) { return s.str[0]; }); + const auto ref = map.get(2).transform([](const String& s) { return s.str[0]; }); ASSERT_TRUE(ref); // Construct std::string from one character. @@ -292,7 +296,7 @@ TEST(SmallMap, EmplaceOrReplace) { } { // Replacement arguments can refer to the replaced mapping. - const auto ref = map.get(2, [](const auto& s) { return s.str[0]; }); + const auto ref = map.get(2).transform([](const String& s) { return s.str[0]; }); ASSERT_TRUE(ref); // Construct std::string from one character. diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 96598cdfea..2d1220c771 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3247,10 +3247,11 @@ void SurfaceFlinger::buildWindowInfos(std::vector& outWindowInfos, mDrawingState.traverseInReverseZOrder([&](Layer* layer) { if (!layer->needsInputInfo()) return; - const auto opt = displayInputInfos.get(layer->getLayerStack(), - [](const auto& info) -> Layer::InputDisplayArgs { - return {&info.transform, info.isSecure}; - }); + const auto opt = displayInputInfos.get(layer->getLayerStack()) + .transform([](const DisplayDevice::InputInfo& info) { + return Layer::InputDisplayArgs{&info.transform, info.isSecure}; + }); + outWindowInfos.push_back(layer->fillInputInfo(opt.value_or(Layer::InputDisplayArgs{}))); }); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index f507ef0d1a..9584492001 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -379,7 +379,8 @@ void SchedulerFuzzer::fuzzRefreshRateConfigs() { RefreshRateStats refreshRateStats(timeStats, Fps::fromValue(mFdp.ConsumeFloatingPoint()), PowerMode::OFF); - const auto fpsOpt = displayModes.get(modeId, [](const auto& mode) { return mode->getFps(); }); + const auto fpsOpt = displayModes.get(modeId).transform( + [](const DisplayModePtr& mode) { return mode->getFps(); }); refreshRateStats.setRefreshRate(*fpsOpt); refreshRateStats.setPowerMode(mFdp.PickValueInArray(kPowerModes)); -- GitLab From 8fc145fa0c9f677db178e1ab7bb19aa926367816 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 17 Aug 2022 16:07:40 -0700 Subject: [PATCH 0224/1310] Do not read kernel configs on the host When the code runs on host (so that it can validate the key layout maps), it currently will attempt to read the kernel configs on the host. What we actually want is to either read the kernel configs on the device, or to always assume that the config exists. In this CL, we simply assume that the config exists. Since we are calling this tool for all key layouts, it should not affect the validation. Test: m out/target/common/obj/ETC/validate_framework_keymaps_intermediates/stamp Bug: 237835584 Change-Id: I79158a7591fa4d8ff0a33dcae9df2b138ae33485 --- libs/input/KeyLayoutMap.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libs/input/KeyLayoutMap.cpp b/libs/input/KeyLayoutMap.cpp index 59cc7d1dcd..d6b4579a94 100644 --- a/libs/input/KeyLayoutMap.cpp +++ b/libs/input/KeyLayoutMap.cpp @@ -25,8 +25,10 @@ #include #include #include +#if defined(__ANDROID__) #include #include +#endif #include #include @@ -79,6 +81,7 @@ static const std::unordered_map SENSOR_ sensorPair()}; bool kernelConfigsArePresent(const std::set& configs) { +#if defined(__ANDROID__) std::shared_ptr runtimeInfo = android::vintf::VintfObject::GetInstance()->getRuntimeInfo( vintf::RuntimeInfo::FetchFlag::CONFIG_GZ); @@ -99,6 +102,10 @@ bool kernelConfigsArePresent(const std::set& configs) { } } return true; +#else + (void)configs; // Suppress 'unused variable' warning + return true; +#endif } } // namespace -- GitLab From 2d347fdf19c201384947ebec5f1c5aa834e34425 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Mon, 22 Aug 2022 14:34:28 +0000 Subject: [PATCH 0225/1310] Delete TouchInputMapper DeviceType::TOUCH_PAD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This doesn't appear to do anything — it is set in a couple of cases (notably not ones that actually match with any known "touchpad" devices, which don't have REL_X or REL_Y), but no other code refers to it. Maybe it's related to "touch navigation" devices, but they already have a separate TOUCH_NAVIGATION device type. Bug: none Test: atest inputflinger_tests Change-Id: I786a3d29fa893cb982873aea7695d754b2c096c6 --- .../reader/mapper/TouchInputMapper.cpp | 7 ------- .../reader/mapper/TouchInputMapper.h | 1 - .../inputflinger/tests/InputReader_test.cpp | 19 ------------------- 3 files changed, 27 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 17ee54f528..924c4d0ba5 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -441,11 +441,6 @@ void TouchInputMapper::configureParameters() { } else if (getDeviceContext().hasInputProperty(INPUT_PROP_POINTER)) { // The device is a pointing device like a track pad. mParameters.deviceType = Parameters::DeviceType::POINTER; - } else if (getDeviceContext().hasRelativeAxis(REL_X) || - getDeviceContext().hasRelativeAxis(REL_Y)) { - // The device is a cursor device with a touch pad attached. - // By default don't use the touch pad to move the pointer. - mParameters.deviceType = Parameters::DeviceType::TOUCH_PAD; } else { // The device is a touch pad of unknown purpose. mParameters.deviceType = Parameters::DeviceType::POINTER; @@ -458,8 +453,6 @@ void TouchInputMapper::configureParameters() { deviceTypeString)) { if (deviceTypeString == "touchScreen") { mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN; - } else if (deviceTypeString == "touchPad") { - mParameters.deviceType = Parameters::DeviceType::TOUCH_PAD; } else if (deviceTypeString == "touchNavigation") { mParameters.deviceType = Parameters::DeviceType::TOUCH_NAVIGATION; } else if (deviceTypeString == "pointer") { diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 3cb11aa04e..cdf1c7d032 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -197,7 +197,6 @@ protected: struct Parameters { enum class DeviceType { TOUCH_SCREEN, - TOUCH_PAD, TOUCH_NAVIGATION, POINTER, diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index a0e61927d3..24721de75b 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -5325,25 +5325,6 @@ TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndNot ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); } -TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndIsACursor_ReturnsTouchPad) { - mFakeEventHub->addRelativeAxis(EVENTHUB_ID, REL_X); - mFakeEventHub->addRelativeAxis(EVENTHUB_ID, REL_Y); - prepareButtons(); - prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); - - ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); -} - -TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchPad_ReturnsTouchPad) { - prepareButtons(); - prepareAxes(POSITION); - addConfigurationProperty("touch.deviceType", "touchPad"); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); - - ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); -} - TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchScreen_ReturnsTouchScreen) { prepareButtons(); prepareAxes(POSITION); -- GitLab From ea04b6f71e8be837f288a17c48c3f83f19f95a1e Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Fri, 19 Aug 2022 21:28:17 +0000 Subject: [PATCH 0226/1310] SF: Clean up layer state Remove unused layer states. Bug: 238781169 Test: presubmit Change-Id: I84917bed157a93ec9bfd16d168312ce27ff1765e --- cmds/surfacereplayer/replayer/Replayer.cpp | 1 - libs/gui/LayerState.cpp | 11 --- libs/gui/SurfaceComposerClient.cpp | 15 ----- .../libgui_surfaceComposerClient_fuzzer.cpp | 2 - libs/gui/include/gui/LayerState.h | 4 +- libs/gui/include/gui/SurfaceComposerClient.h | 7 +- services/surfaceflinger/BufferStateLayer.cpp | 10 --- services/surfaceflinger/BufferStateLayer.h | 7 -- services/surfaceflinger/Layer.cpp | 33 ++------- services/surfaceflinger/Layer.h | 67 +------------------ services/surfaceflinger/SurfaceFlinger.cpp | 5 -- .../surfaceflinger/SurfaceInterceptor.cpp | 7 -- .../Tracing/TransactionProtoParser.cpp | 9 +-- .../layerproto/transactions.proto | 2 +- .../tests/LayerTransactionTest.h | 2 +- .../tests/SurfaceInterceptor_test.cpp | 16 ++--- .../tests/unittests/CompositionTest.cpp | 2 - 17 files changed, 18 insertions(+), 182 deletions(-) diff --git a/cmds/surfacereplayer/replayer/Replayer.cpp b/cmds/surfacereplayer/replayer/Replayer.cpp index 3f7c7d6a7b..44235ccdef 100644 --- a/cmds/surfacereplayer/replayer/Replayer.cpp +++ b/cmds/surfacereplayer/replayer/Replayer.cpp @@ -464,7 +464,6 @@ void Replayer::setPosition(SurfaceComposerClient::Transaction& t, void Replayer::setSize(SurfaceComposerClient::Transaction& t, layer_id id, const SizeChange& sc) { ALOGV("Layer %d: Setting Size -- w=%u, h=%u", id, sc.w(), sc.h()); - t.setSize(mLayers[id], sc.w(), sc.h()); } void Replayer::setLayer(SurfaceComposerClient::Transaction& t, diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index bb660854c8..4d5978ccf7 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -40,8 +40,6 @@ layer_state_t::layer_state_t() x(0), y(0), z(0), - w(0), - h(0), alpha(0), flags(0), mask(0), @@ -84,8 +82,6 @@ status_t layer_state_t::write(Parcel& output) const SAFE_PARCEL(output.writeFloat, x); SAFE_PARCEL(output.writeFloat, y); SAFE_PARCEL(output.writeInt32, z); - SAFE_PARCEL(output.writeUint32, w); - SAFE_PARCEL(output.writeUint32, h); SAFE_PARCEL(output.writeUint32, layerStack.id); SAFE_PARCEL(output.writeFloat, alpha); SAFE_PARCEL(output.writeUint32, flags); @@ -180,8 +176,6 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(input.readFloat, &x); SAFE_PARCEL(input.readFloat, &y); SAFE_PARCEL(input.readInt32, &z); - SAFE_PARCEL(input.readUint32, &w); - SAFE_PARCEL(input.readUint32, &h); SAFE_PARCEL(input.readUint32, &layerStack.id); SAFE_PARCEL(input.readFloat, &alpha); @@ -457,11 +451,6 @@ void layer_state_t::merge(const layer_state_t& other) { what &= ~eRelativeLayerChanged; z = other.z; } - if (other.what & eSizeChanged) { - what |= eSizeChanged; - w = other.w; - h = other.h; - } if (other.what & eAlphaChanged) { what |= eAlphaChanged; alpha = other.alpha; diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index e8aaf629b3..8e29c42f88 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -1165,21 +1165,6 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::hide( return setFlags(sc, layer_state_t::eLayerHidden, layer_state_t::eLayerHidden); } -SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setSize( - const sp& sc, uint32_t w, uint32_t h) { - layer_state_t* s = getLayerState(sc); - if (!s) { - mStatus = BAD_INDEX; - return *this; - } - s->what |= layer_state_t::eSizeChanged; - s->w = w; - s->h = h; - - registerSurfaceControlForCallback(sc); - return *this; -} - SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setLayer( const sp& sc, int32_t z) { layer_state_t* s = getLayerState(sc); diff --git a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp index 05564e093a..48c90c5e8f 100644 --- a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp +++ b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp @@ -182,8 +182,6 @@ void SurfaceComposerClientFuzzer::invokeSurfaceComposerTransaction() { sp surface = makeSurfaceControl(); SurfaceComposerClient::Transaction transaction; - transaction.setSize(surface, mFdp.ConsumeIntegral(), - mFdp.ConsumeIntegral()); int32_t layer = mFdp.ConsumeIntegral(); transaction.setLayer(surface, layer); diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 759fcc6c53..3c7b16266d 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -148,7 +148,7 @@ struct layer_state_t { enum { ePositionChanged = 0x00000001, eLayerChanged = 0x00000002, - eSizeChanged = 0x00000004, + // unused = 0x00000004, eAlphaChanged = 0x00000008, eMatrixChanged = 0x00000010, eTransparentRegionChanged = 0x00000020, @@ -217,8 +217,6 @@ struct layer_state_t { float x; float y; int32_t z; - uint32_t w; - uint32_t h; ui::LayerStack layerStack = ui::DEFAULT_LAYER_STACK; float alpha; uint32_t flags; diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 20c38d8012..533362e1fe 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -468,10 +468,9 @@ public: Transaction& merge(Transaction&& other); Transaction& show(const sp& sc); Transaction& hide(const sp& sc); - Transaction& setPosition(const sp& sc, - float x, float y); - Transaction& setSize(const sp& sc, - uint32_t w, uint32_t h); + Transaction& setPosition(const sp& sc, float x, float y); + // b/243180033 remove once functions are not called from vendor code + Transaction& setSize(const sp&, uint32_t, uint32_t) { return *this; } Transaction& setLayer(const sp& sc, int32_t z); diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp index cce6ad7fe0..0cedfc8138 100644 --- a/services/surfaceflinger/BufferStateLayer.cpp +++ b/services/surfaceflinger/BufferStateLayer.cpp @@ -531,8 +531,6 @@ bool BufferStateLayer::setBuffer(std::shared_ptr& FrameTracer::FrameEvent::QUEUE); } - mDrawingState.width = mDrawingState.buffer->getWidth(); - mDrawingState.height = mDrawingState.buffer->getHeight(); mDrawingState.releaseBufferEndpoint = bufferData.releaseBufferEndpoint; return true; } @@ -622,14 +620,6 @@ bool BufferStateLayer::setTransactionCompletedListeners( return willPresent; } -bool BufferStateLayer::setTransparentRegionHint(const Region& transparent) { - mDrawingState.sequence++; - mDrawingState.transparentRegionHint = transparent; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - Rect BufferStateLayer::getBufferSize(const State& /*s*/) const { // for buffer state layers we use the display frame size as the buffer size. diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h index a0f13e21d4..a0a52bfcd3 100644 --- a/services/surfaceflinger/BufferStateLayer.h +++ b/services/surfaceflinger/BufferStateLayer.h @@ -116,9 +116,6 @@ public: void releasePendingBuffer(nsecs_t dequeueReadyTime) override; - Region getActiveTransparentRegion(const Layer::State& s) const override { - return s.transparentRegionHint; - } Rect getCrop(const Layer::State& s) const; bool setTransform(uint32_t transform) override; @@ -137,10 +134,6 @@ public: bool setPosition(float /*x*/, float /*y*/) override; bool setMatrix(const layer_state_t::matrix22_t& /*matrix*/); - // Override to ignore legacy layer state properties that are not used by BufferStateLayer - bool setSize(uint32_t /*w*/, uint32_t /*h*/) override { return false; } - bool setTransparentRegionHint(const Region& transparent) override; - // BufferStateLayers can return Rect::INVALID_RECT if the layer does not have a display frame // and its parent layer is not bounded Rect getBufferSize(const State& s) const override; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index dfff8fe8ee..8a401eb597 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -111,16 +111,11 @@ Layer::Layer(const LayerCreationArgs& args) sSequence = *args.sequence + 1; } mDrawingState.flags = layerFlags; - mDrawingState.active_legacy.transform.set(0, 0); mDrawingState.crop.makeInvalid(); - mDrawingState.requestedCrop = mDrawingState.crop; mDrawingState.z = 0; mDrawingState.color.a = 1.0f; mDrawingState.layerStack = ui::DEFAULT_LAYER_STACK; mDrawingState.sequence = 0; - mDrawingState.requested_legacy = mDrawingState.active_legacy; - mDrawingState.width = UINT32_MAX; - mDrawingState.height = UINT32_MAX; mDrawingState.transform.set(0, 0); mDrawingState.frameNumber = 0; mDrawingState.bufferTransform = 0; @@ -875,20 +870,6 @@ bool Layer::isTrustedOverlay() const { return (p != nullptr) && p->isTrustedOverlay(); } -bool Layer::setSize(uint32_t w, uint32_t h) { - if (mDrawingState.requested_legacy.w == w && mDrawingState.requested_legacy.h == h) - return false; - mDrawingState.requested_legacy.w = w; - mDrawingState.requested_legacy.h = h; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - - // record the new size, from this point on, when the client request - // a buffer, it'll get the new size. - setDefaultBufferSize(mDrawingState.requested_legacy.w, mDrawingState.requested_legacy.h); - return true; -} - bool Layer::setAlpha(float alpha) { if (mDrawingState.color.a == alpha) return false; mDrawingState.sequence++; @@ -978,7 +959,8 @@ bool Layer::setMatrix(const layer_state_t::matrix22_t& matrix) { } bool Layer::setTransparentRegionHint(const Region& transparent) { - mDrawingState.requestedTransparentRegion_legacy = transparent; + mDrawingState.sequence++; + mDrawingState.transparentRegionHint = transparent; mDrawingState.modified = true; setTransactionFlags(eTransactionNeeded); return true; @@ -1007,9 +989,8 @@ bool Layer::setFlags(uint32_t flags, uint32_t mask) { } bool Layer::setCrop(const Rect& crop) { - if (mDrawingState.requestedCrop == crop) return false; + if (mDrawingState.crop == crop) return false; mDrawingState.sequence++; - mDrawingState.requestedCrop = crop; mDrawingState.crop = crop; mDrawingState.modified = true; @@ -1433,7 +1414,6 @@ gui::LayerDebugInfo Layer::getLayerDebugInfo(const DisplayDevice* display) const sp parent = mDrawingParent.promote(); info.mParentName = parent ? parent->getName() : "none"s; info.mType = getType(); - info.mTransparentRegion = ds.activeTransparentRegion_legacy; info.mVisibleRegion = getVisibleRegion(display); info.mSurfaceDamageRegion = surfaceDamageRegion; @@ -1441,8 +1421,6 @@ gui::LayerDebugInfo Layer::getLayerDebugInfo(const DisplayDevice* display) const info.mX = ds.transform.tx(); info.mY = ds.transform.ty(); info.mZ = ds.z; - info.mWidth = ds.width; - info.mHeight = ds.height; info.mCrop = ds.crop; info.mColor = ds.color; info.mFlags = ds.flags; @@ -2141,7 +2119,7 @@ void Layer::writeToProtoCommonState(LayerProto* layerInfo, LayerVector::StateSet } } - LayerProtoHelper::writeToProto(state.activeTransparentRegion_legacy, + LayerProtoHelper::writeToProto(state.transparentRegionHint, [&]() { return layerInfo->mutable_transparent_region(); }); layerInfo->set_layer_stack(getLayerStack().id); @@ -2151,9 +2129,6 @@ void Layer::writeToProtoCommonState(LayerProto* layerInfo, LayerVector::StateSet return layerInfo->mutable_requested_position(); }); - LayerProtoHelper::writeSizeToProto(state.width, state.height, - [&]() { return layerInfo->mutable_size(); }); - LayerProtoHelper::writeToProto(state.crop, [&]() { return layerInfo->mutable_crop(); }); layerInfo->set_is_opaque(isOpaque(state)); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 6e83b235d4..b05a4a04e5 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -150,40 +150,23 @@ public: using FrameRateCompatibility = scheduler::LayerInfo::FrameRateCompatibility; struct State { - Geometry active_legacy; - Geometry requested_legacy; int32_t z; - ui::LayerStack layerStack; - uint32_t flags; - uint8_t reserved[2]; int32_t sequence; // changes when visible regions can change bool modified; - // Crop is expressed in layer space coordinate. Rect crop; - Rect requestedCrop; - - // the transparentRegion hint is a bit special, it's latched only - // when we receive a buffer -- this is because it's "content" - // dependent. - Region activeTransparentRegion_legacy; - Region requestedTransparentRegion_legacy; - LayerMetadata metadata; - // If non-null, a Surface this Surface's Z-order is interpreted relative to. wp zOrderRelativeOf; bool isRelativeOf{false}; // A list of surfaces whose Z-order is interpreted relative to ours. SortedVector> zOrderRelatives; - half4 color; float cornerRadius; int backgroundBlurRadius; - gui::WindowInfo inputInfo; wp touchableRegionCrop; @@ -192,15 +175,10 @@ public: // The fields below this point are only used by BufferStateLayer uint64_t frameNumber; - uint32_t width; - uint32_t height; ui::Transform transform; - uint32_t bufferTransform; bool transformToDisplayInverse; - Region transparentRegionHint; - std::shared_ptr buffer; client_cache_t clientCacheId; sp acquireFence; @@ -208,11 +186,9 @@ public: HdrMetadata hdrMetadata; Region surfaceDamageRegion; int32_t api; - sp sidebandStream; mat4 colorTransform; bool hasColorTransform; - // pointer to background color layer that, if set, appears below the buffer state layer // and the buffer state layer's children. Z order will be set to // INT_MIN @@ -237,7 +213,6 @@ public: // Default frame rate compatibility used to set the layer refresh rate votetype. FrameRateCompatibility defaultFrameRateCompatibility; - FrameRate frameRate; // The combined frame rate of parents / children of this layer @@ -257,7 +232,6 @@ public: // When the transaction was posted nsecs_t postTime; - sp releaseBufferListener; // SurfaceFrame that tracks the timeline of Transactions that contain a Buffer. Only one // such SurfaceFrame exists because only one buffer can be presented on the layer per vsync. @@ -278,16 +252,11 @@ public: // Whether or not this layer is a trusted overlay for input bool isTrustedOverlay; - Rect bufferCrop; Rect destinationFrame; - sp releaseBufferEndpoint; - gui::DropInputMode dropInputMode; - bool autoRefresh = false; - bool dimmingEnabled = true; }; @@ -345,32 +314,6 @@ public: virtual sp createClone() = 0; - // Geometry setting functions. - // - // The following group of functions are used to specify the layers - // bounds, and the mapping of the texture on to those bounds. According - // to various settings changes to them may apply immediately, or be delayed until - // a pending resize is completed by the producer submitting a buffer. For example - // if we were to change the buffer size, and update the matrix ahead of the - // new buffer arriving, then we would be stretching the buffer to a different - // aspect before and after the buffer arriving, which probably isn't what we wanted. - // - // The first set of geometry functions are controlled by the scaling mode, described - // in window.h. The scaling mode may be set by the client, as it submits buffers. - // - // Put simply, if our scaling mode is SCALING_MODE_FREEZE, then - // matrix updates will not be applied while a resize is pending - // and the size and transform will remain in their previous state - // until a new buffer is submitted. If the scaling mode is another value - // then the old-buffer will immediately be scaled to the pending size - // and the new matrix will be immediately applied following this scaling - // transformation. - - // Set the default buffer size for the assosciated Producer, in pixels. This is - // also the rendered size of the layer prior to any transformations. Parent - // or local matrix transformations will not affect the size of the buffer, - // but may affect it's on-screen size or clipping. - virtual bool setSize(uint32_t w, uint32_t h); // Set a 2x2 transformation matrix on the layer. This transform // will be applied after parent transforms, but before any final // producer specified transform. @@ -407,7 +350,7 @@ public: // is specified in pixels. virtual bool setBackgroundBlurRadius(int backgroundBlurRadius); virtual bool setBlurRegions(const std::vector& effectRegions); - virtual bool setTransparentRegionHint(const Region& transparent); + bool setTransparentRegionHint(const Region& transparent); virtual bool setTrustedOverlay(bool); virtual bool setFlags(uint32_t flags, uint32_t mask); virtual bool setLayerStack(ui::LayerStack); @@ -500,11 +443,9 @@ public: // to avoid grabbing the lock again to avoid deadlock virtual bool isCreatedFromMainThread() const { return false; } - uint32_t getActiveWidth(const Layer::State& s) const { return s.width; } - uint32_t getActiveHeight(const Layer::State& s) const { return s.height; } ui::Transform getActiveTransform(const Layer::State& s) const { return s.transform; } - virtual Region getActiveTransparentRegion(const Layer::State& s) const { - return s.activeTransparentRegion_legacy; + Region getActiveTransparentRegion(const Layer::State& s) const { + return s.transparentRegionHint; } virtual Rect getCrop(const Layer::State& s) const { return s.crop; } virtual bool needsFiltering(const DisplayDevice*) const { return false; } @@ -524,8 +465,6 @@ public: virtual void updateCloneBufferInfo(){}; - virtual void setDefaultBufferSize(uint32_t /*w*/, uint32_t /*h*/) {} - virtual bool isHdrY410() const { return false; } /* diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 6b4cfa1249..e8d286295d 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4254,11 +4254,6 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime } } } - if (what & layer_state_t::eSizeChanged) { - if (layer->setSize(s.w, s.h)) { - flags |= eTraversalNeeded; - } - } if (what & layer_state_t::eAlphaChanged) { if (layer->setAlpha(s.alpha)) flags |= eTraversalNeeded; diff --git a/services/surfaceflinger/SurfaceInterceptor.cpp b/services/surfaceflinger/SurfaceInterceptor.cpp index 66691c2a31..6797aa697b 100644 --- a/services/surfaceflinger/SurfaceInterceptor.cpp +++ b/services/surfaceflinger/SurfaceInterceptor.cpp @@ -135,8 +135,6 @@ void SurfaceInterceptor::addInitialSurfaceStateLocked(Increment* increment, layer->mDrawingState.transform.ty()); addDepthLocked(transaction, layerId, layer->mDrawingState.z); addAlphaLocked(transaction, layerId, layer->mDrawingState.color.a); - addTransparentRegionLocked(transaction, layerId, - layer->mDrawingState.activeTransparentRegion_legacy); addLayerStackLocked(transaction, layerId, layer->mDrawingState.layerStack); addCropLocked(transaction, layerId, layer->mDrawingState.crop); addCornerRadiusLocked(transaction, layerId, layer->mDrawingState.cornerRadius); @@ -420,9 +418,6 @@ void SurfaceInterceptor::addSurfaceChangesLocked(Transaction* transaction, if (state.what & layer_state_t::eLayerChanged) { addDepthLocked(transaction, layerId, state.z); } - if (state.what & layer_state_t::eSizeChanged) { - addSizeLocked(transaction, layerId, state.w, state.h); - } if (state.what & layer_state_t::eAlphaChanged) { addAlphaLocked(transaction, layerId, state.alpha); } @@ -522,8 +517,6 @@ void SurfaceInterceptor::addSurfaceCreationLocked(Increment* increment, SurfaceCreation* creation(increment->mutable_surface_creation()); creation->set_id(getLayerId(layer)); creation->set_name(layer->getName()); - creation->set_w(layer->mDrawingState.active_legacy.w); - creation->set_h(layer->mDrawingState.active_legacy.h); } void SurfaceInterceptor::addSurfaceDeletionLocked(Increment* increment, diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp index 77dec6f7fb..dcc529ecc5 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp @@ -88,10 +88,7 @@ proto::LayerState TransactionProtoParser::toProto(const layer_state_t& layer) { if (layer.what & layer_state_t::eLayerChanged) { proto.set_z(layer.z); } - if (layer.what & layer_state_t::eSizeChanged) { - proto.set_w(layer.w); - proto.set_h(layer.h); - } + if (layer.what & layer_state_t::eLayerStackChanged) { proto.set_layer_stack(layer.layerStack.id); } @@ -376,10 +373,6 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta if (proto.what() & layer_state_t::eLayerChanged) { layer.z = proto.z(); } - if (proto.what() & layer_state_t::eSizeChanged) { - layer.w = proto.w(); - layer.h = proto.h(); - } if (proto.what() & layer_state_t::eLayerStackChanged) { layer.layerStack.id = proto.layer_stack(); } diff --git a/services/surfaceflinger/layerproto/transactions.proto b/services/surfaceflinger/layerproto/transactions.proto index 49487ee550..b687abc918 100644 --- a/services/surfaceflinger/layerproto/transactions.proto +++ b/services/surfaceflinger/layerproto/transactions.proto @@ -82,7 +82,7 @@ message LayerState { eChangesLsbNone = 0; ePositionChanged = 0x00000001; eLayerChanged = 0x00000002; - eSizeChanged = 0x00000004; + // unused = 0x00000004; eAlphaChanged = 0x00000008; eMatrixChanged = 0x00000010; diff --git a/services/surfaceflinger/tests/LayerTransactionTest.h b/services/surfaceflinger/tests/LayerTransactionTest.h index 4b9160580f..0e8f3dd1d4 100644 --- a/services/surfaceflinger/tests/LayerTransactionTest.h +++ b/services/surfaceflinger/tests/LayerTransactionTest.h @@ -233,7 +233,7 @@ protected: Rect(halfW, halfH, bufferWidth, bufferHeight), bottomRight); - Transaction().setBuffer(layer, buffer).setSize(layer, bufferWidth, bufferHeight).apply(); + Transaction().setBuffer(layer, buffer).apply(); } std::unique_ptr screenshot() { diff --git a/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp b/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp index 8dcd013985..d79e59211b 100644 --- a/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp +++ b/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp @@ -140,6 +140,7 @@ protected: mComposerClient = sp::make(); ASSERT_EQ(NO_ERROR, mComposerClient->initCheck()); + GTEST_SKIP(); } void TearDown() override { @@ -342,9 +343,7 @@ void SurfaceInterceptorTest::positionUpdate(Transaction& t) { t.setPosition(mBGSurfaceControl, POSITION_UPDATE, POSITION_UPDATE); } -void SurfaceInterceptorTest::sizeUpdate(Transaction& t) { - t.setSize(mBGSurfaceControl, SIZE_UPDATE, SIZE_UPDATE); -} +void SurfaceInterceptorTest::sizeUpdate(Transaction&) {} void SurfaceInterceptorTest::alphaUpdate(Transaction& t) { t.setAlpha(mBGSurfaceControl, ALPHA_UPDATE); @@ -472,15 +471,8 @@ bool SurfaceInterceptorTest::positionUpdateFound(const SurfaceChange& change, bo return foundPosition; } -bool SurfaceInterceptorTest::sizeUpdateFound(const SurfaceChange& change, bool foundSize) { - bool hasWidth(change.size().h() == SIZE_UPDATE); - bool hasHeight(change.size().w() == SIZE_UPDATE); - if (hasWidth && hasHeight && !foundSize) { - foundSize = true; - } else if (hasWidth && hasHeight && foundSize) { - [] () { FAIL(); }(); - } - return foundSize; +bool SurfaceInterceptorTest::sizeUpdateFound(const SurfaceChange&, bool) { + return true; } bool SurfaceInterceptorTest::alphaUpdateFound(const SurfaceChange& change, bool foundAlpha) { diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 2571e3a0ff..0666561642 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -828,8 +828,6 @@ struct BaseLayerVariant { static void initLayerDrawingStateAndComputeBounds(CompositionTest* test, sp layer) { auto& layerDrawingState = test->mFlinger.mutableLayerDrawingState(layer); layerDrawingState.layerStack = LAYER_STACK; - layerDrawingState.width = 100; - layerDrawingState.height = 100; layerDrawingState.color = half4(LayerProperties::COLOR[0], LayerProperties::COLOR[1], LayerProperties::COLOR[2], LayerProperties::COLOR[3]); layer->computeBounds(FloatRect(0, 0, 100, 100), ui::Transform(), 0.f /* shadowRadius */); -- GitLab From 869e28fbfcfa43bafa312e373b5e2df43237862f Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Fri, 12 Aug 2022 22:20:19 +0000 Subject: [PATCH 0227/1310] Plumb through Output::getSkipColorTransform() into CachedSet, to match the behavior in Output::composeSurfaces(). Bug: 240293363 Test: atest libcompositionengine_test. Fixed tests, added CachedSetTest::renderWhitePointNoColorTransform Change-Id: Ic0317b34978c2ae8d5c057c0a39ed889b86b3da0 --- .../impl/planner/CachedSet.h | 2 +- .../impl/planner/Flattener.h | 3 +- .../compositionengine/impl/planner/Planner.h | 3 +- .../CompositionEngine/src/Output.cpp | 3 +- .../src/planner/CachedSet.cpp | 5 +- .../src/planner/Flattener.cpp | 5 +- .../CompositionEngine/src/planner/Planner.cpp | 8 +- .../tests/planner/CachedSetTest.cpp | 65 +++++++++++-- .../tests/planner/FlattenerTest.cpp | 96 ++++++++++--------- 9 files changed, 125 insertions(+), 65 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h index 2bfd3cfb1f..24a7744542 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h @@ -115,7 +115,7 @@ public: // Renders the cached set with the supplied output composition state. void render(renderengine::RenderEngine& re, TexturePool& texturePool, - const OutputCompositionState& outputState); + const OutputCompositionState& outputState, bool deviceHandlesColorTransform); void dump(std::string& result) const; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h index 92cc484211..f934cb20a0 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h @@ -106,7 +106,8 @@ public: // Renders the newest cached sets with the supplied output composition state void renderCachedSets(const OutputCompositionState& outputState, - std::optional renderDeadline); + std::optional renderDeadline, + bool deviceHandlesColorTransform); void setTexturePoolEnabled(bool enabled) { mTexturePool.setEnabled(enabled); } diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h index b7ebca60fd..c968df708f 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h @@ -65,7 +65,8 @@ public: // Rendering a pending cached set is optional: if the renderDeadline is not far enough in the // future then the planner may opt to skip rendering the cached set. void renderCachedSets(const OutputCompositionState& outputState, - std::optional renderDeadline); + std::optional renderDeadline, + bool deviceHandlesColorTransform); void setTexturePoolEnabled(bool enabled) { mFlattener.setTexturePoolEnabled(enabled); } diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 6edc00f1e1..06ae9e9219 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -1536,7 +1536,8 @@ void Output::postFramebuffer() { void Output::renderCachedSets(const CompositionRefreshArgs& refreshArgs) { if (mPlanner) { - mPlanner->renderCachedSets(getState(), refreshArgs.scheduledFrameTime); + mPlanner->renderCachedSets(getState(), refreshArgs.scheduledFrameTime, + getState().usesDeviceComposition || getSkipColorTransform()); } } diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp index 9058f6709f..3315767380 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp @@ -159,7 +159,8 @@ void CachedSet::updateAge(std::chrono::steady_clock::time_point now) { } void CachedSet::render(renderengine::RenderEngine& renderEngine, TexturePool& texturePool, - const OutputCompositionState& outputState) { + const OutputCompositionState& outputState, + bool deviceHandlesColorTransform) { ATRACE_CALL(); const Rect& viewport = outputState.layerStackSpace.getContent(); const ui::Dataspace& outputDataspace = outputState.dataspace; @@ -170,6 +171,8 @@ void CachedSet::render(renderengine::RenderEngine& renderEngine, TexturePool& te .physicalDisplay = outputState.framebufferSpace.getContent(), .clip = viewport, .outputDataspace = outputDataspace, + .colorTransform = outputState.colorTransformMatrix, + .deviceHandlesColorTransform = deviceHandlesColorTransform, .orientation = orientation, .targetLuminanceNits = outputState.displayBrightnessNits, }; diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp index 1062b700dd..9175dd01a1 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp @@ -99,7 +99,8 @@ NonBufferHash Flattener::flattenLayers(const std::vector& lay void Flattener::renderCachedSets( const OutputCompositionState& outputState, - std::optional renderDeadline) { + std::optional renderDeadline, + bool deviceHandlesColorTransform) { ATRACE_CALL(); if (!mNewCachedSet) { @@ -136,7 +137,7 @@ void Flattener::renderCachedSets( } } - mNewCachedSet->render(mRenderEngine, mTexturePool, outputState); + mNewCachedSet->render(mRenderEngine, mTexturePool, outputState, deviceHandlesColorTransform); } void Flattener::dumpLayers(std::string& result) const { diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp index c8413eb8bc..54133d92b0 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp @@ -201,11 +201,11 @@ void Planner::reportFinalPlan( finalPlan); } -void Planner::renderCachedSets( - const OutputCompositionState& outputState, - std::optional renderDeadline) { +void Planner::renderCachedSets(const OutputCompositionState& outputState, + std::optional renderDeadline, + bool deviceHandlesColorTransform) { ATRACE_CALL(); - mFlattener.renderCachedSets(outputState, renderDeadline); + mFlattener.renderCachedSets(outputState, renderDeadline, deviceHandlesColorTransform); } void Planner::dump(const Vector& args, std::string& result) { diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp index cb4c4e23fe..2b938d0344 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp @@ -374,7 +374,7 @@ TEST_F(CachedSetTest, renderUnsecureOutput) { .WillOnce(Return(clientComp2)); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); mOutputState.isSecure = false; - cachedSet.render(mRenderEngine, mTexturePool, mOutputState); + cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true); expectReadyBuffer(cachedSet); EXPECT_EQ(mOutputState.framebufferSpace, cachedSet.getOutputSpace()); @@ -425,7 +425,7 @@ TEST_F(CachedSetTest, renderSecureOutput) { .WillOnce(Return(clientComp2)); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); mOutputState.isSecure = true; - cachedSet.render(mRenderEngine, mTexturePool, mOutputState); + cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true); expectReadyBuffer(cachedSet); EXPECT_EQ(mOutputState.framebufferSpace, cachedSet.getOutputSpace()); @@ -473,7 +473,58 @@ TEST_F(CachedSetTest, renderWhitePoint) { .WillOnce(Return(clientComp2)); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); mOutputState.isSecure = true; - cachedSet.render(mRenderEngine, mTexturePool, mOutputState); + cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true); + expectReadyBuffer(cachedSet); + + EXPECT_EQ(mOutputState.framebufferSpace, cachedSet.getOutputSpace()); + EXPECT_EQ(Rect(kOutputSize.width, kOutputSize.height), cachedSet.getTextureBounds()); + + // Now check that appending a new cached set properly cleans up RenderEngine resources. + CachedSet::Layer& layer3 = *mTestLayers[2]->cachedSetLayer.get(); + cachedSet.append(CachedSet(layer3)); +} + +TEST_F(CachedSetTest, renderWhitePointNoColorTransform) { + // Skip the 0th layer to ensure that the bounding box of the layers is offset from (0, 0) + // This is a duplicate of the "renderWhitePoint" test, but setting "deviceHandlesColorTransform" + // to false, in the render call. + + CachedSet::Layer& layer1 = *mTestLayers[1]->cachedSetLayer.get(); + sp layerFE1 = mTestLayers[1]->layerFE; + CachedSet::Layer& layer2 = *mTestLayers[2]->cachedSetLayer.get(); + sp layerFE2 = mTestLayers[2]->layerFE; + + CachedSet cachedSet(layer1); + cachedSet.append(CachedSet(layer2)); + + std::optional clientComp1; + clientComp1.emplace(); + + std::optional clientComp2; + clientComp2.emplace(); + + mOutputState.displayBrightnessNits = 400.f; + + const auto drawLayers = + [&](const renderengine::DisplaySettings& displaySettings, + const std::vector&, + const std::shared_ptr&, const bool, + base::unique_fd&&) -> std::future { + EXPECT_EQ(mOutputState.displayBrightnessNits, displaySettings.targetLuminanceNits); + return futureOf({NO_ERROR, base::unique_fd()}); + }; + + EXPECT_CALL(*layerFE1, + prepareClientComposition(ClientCompositionTargetSettingsWhitePointEq( + mOutputState.displayBrightnessNits))) + .WillOnce(Return(clientComp1)); + EXPECT_CALL(*layerFE2, + prepareClientComposition(ClientCompositionTargetSettingsWhitePointEq( + mOutputState.displayBrightnessNits))) + .WillOnce(Return(clientComp2)); + EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); + mOutputState.isSecure = true; + cachedSet.render(mRenderEngine, mTexturePool, mOutputState, false); expectReadyBuffer(cachedSet); EXPECT_EQ(mOutputState.framebufferSpace, cachedSet.getOutputSpace()); @@ -523,7 +574,7 @@ TEST_F(CachedSetTest, rendersWithOffsetFramebufferContent) { EXPECT_CALL(*layerFE1, prepareClientComposition(_)).WillOnce(Return(clientComp1)); EXPECT_CALL(*layerFE2, prepareClientComposition(_)).WillOnce(Return(clientComp2)); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); - cachedSet.render(mRenderEngine, mTexturePool, mOutputState); + cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true); expectReadyBuffer(cachedSet); EXPECT_EQ(mOutputState.framebufferSpace, cachedSet.getOutputSpace()); @@ -763,7 +814,7 @@ TEST_F(CachedSetTest, addHolePunch) { }; EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); - cachedSet.render(mRenderEngine, mTexturePool, mOutputState); + cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true); } TEST_F(CachedSetTest, addHolePunch_noBuffer) { @@ -825,7 +876,7 @@ TEST_F(CachedSetTest, addHolePunch_noBuffer) { }; EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); - cachedSet.render(mRenderEngine, mTexturePool, mOutputState); + cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true); } TEST_F(CachedSetTest, append_removesHolePunch) { @@ -965,7 +1016,7 @@ TEST_F(CachedSetTest, addBlur) { }; EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); - cachedSet.render(mRenderEngine, mTexturePool, mOutputState); + cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true); } } // namespace diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp index b624d1a2ea..430a66355c 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp @@ -159,13 +159,13 @@ void FlattenerTest::initializeFlattener(const std::vector& la initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // same geometry, update the internal layer stack initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); } void FlattenerTest::expectAllLayersFlattened(const std::vector& layers) { @@ -177,7 +177,7 @@ void FlattenerTest::expectAllLayersFlattened(const std::vectorflattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); for (const auto layer : layers) { EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer); @@ -187,7 +187,7 @@ void FlattenerTest::expectAllLayersFlattened(const std::vectorflattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); const auto buffer = layers[0]->getOutputLayer()->getState().overrideInfo.buffer; EXPECT_NE(nullptr, buffer); @@ -222,7 +222,7 @@ TEST_F(FlattenerTest, flattenLayers_ActiveLayersAreNotFlattened) { initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); } TEST_F(FlattenerTest, flattenLayers_ActiveLayersWithLowFpsAreFlattened) { @@ -284,7 +284,7 @@ TEST_F(FlattenerTest, flattenLayers_FlattenedLayersStayFlattenWhenNoUpdate) { initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_NE(nullptr, overrideBuffer1); EXPECT_EQ(overrideBuffer1, overrideBuffer2); @@ -389,7 +389,7 @@ TEST_F(FlattenerTest, flattenLayers_addLayerToFlattenedCauseReset) { initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_EQ(nullptr, overrideBuffer1); EXPECT_EQ(nullptr, overrideBuffer2); @@ -428,7 +428,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateToFlatten) { initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_EQ(nullptr, overrideBuffer1); EXPECT_EQ(nullptr, overrideBuffer2); @@ -437,7 +437,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateToFlatten) { initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_EQ(nullptr, overrideBuffer1); EXPECT_NE(nullptr, overrideBuffer2); @@ -452,7 +452,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateToFlatten) { initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_EQ(nullptr, overrideBuffer1); EXPECT_NE(nullptr, overrideBuffer2); @@ -461,7 +461,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateToFlatten) { initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_NE(nullptr, overrideBuffer1); EXPECT_EQ(overrideBuffer1, overrideBuffer2); @@ -505,7 +505,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateForMiddleLayer) { initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_EQ(nullptr, overrideBuffer1); EXPECT_EQ(nullptr, overrideBuffer2); @@ -521,7 +521,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateForMiddleLayer) { EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); mOutputState.framebufferSpace.setOrientation(ui::ROTATION_90); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_NE(nullptr, overrideBuffer1); EXPECT_EQ(overrideBuffer1, overrideBuffer2); @@ -534,7 +534,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateForMiddleLayer) { EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); mOutputState.framebufferSpace.setOrientation(ui::ROTATION_180); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_NE(nullptr, overrideBuffer1); EXPECT_EQ(overrideBuffer1, overrideBuffer2); @@ -550,7 +550,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateForMiddleLayer) { initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_NE(nullptr, overrideBuffer1); EXPECT_EQ(overrideBuffer1, overrideBuffer2); @@ -562,7 +562,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateForMiddleLayer) { EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); mOutputState.framebufferSpace.setOrientation(ui::ROTATION_270); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_NE(nullptr, overrideBuffer1); EXPECT_EQ(overrideBuffer1, overrideBuffer2); @@ -603,7 +603,7 @@ TEST_F(FlattenerTest, flattenLayers_pipRequiresRoundedCorners) { EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) .WillOnce(Return(ByMove( futureOf({NO_ERROR, base::unique_fd()})))); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. EXPECT_EQ(nullptr, overrideBuffer1); @@ -616,7 +616,7 @@ TEST_F(FlattenerTest, flattenLayers_pipRequiresRoundedCorners) { initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_NE(nullptr, overrideBuffer1); EXPECT_EQ(overrideBuffer1, overrideBuffer2); @@ -668,7 +668,7 @@ TEST_F(FlattenerTest, flattenLayers_pip) { EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) .WillOnce(Return(ByMove( futureOf({NO_ERROR, base::unique_fd()})))); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. EXPECT_EQ(nullptr, overrideBuffer1); @@ -681,7 +681,7 @@ TEST_F(FlattenerTest, flattenLayers_pip) { initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_NE(nullptr, overrideBuffer1); EXPECT_EQ(overrideBuffer1, overrideBuffer2); @@ -741,7 +741,7 @@ TEST_F(FlattenerTest, flattenLayers_holePunchSingleLayer) { EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) .WillOnce(Return(ByMove( futureOf({NO_ERROR, base::unique_fd()})))); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. EXPECT_EQ(nullptr, overrideBuffer0); @@ -751,7 +751,7 @@ TEST_F(FlattenerTest, flattenLayers_holePunchSingleLayer) { initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_NE(nullptr, overrideBuffer0); // got overridden EXPECT_EQ(nullptr, overrideBuffer1); // did not @@ -812,7 +812,7 @@ TEST_F(FlattenerTest, flattenLayers_holePunchSingleColorLayer) { EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) .WillOnce(Return(ByMove( futureOf({NO_ERROR, base::unique_fd()})))); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. EXPECT_EQ(nullptr, overrideBuffer0); @@ -822,7 +822,7 @@ TEST_F(FlattenerTest, flattenLayers_holePunchSingleColorLayer) { initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_NE(nullptr, overrideBuffer0); // got overridden EXPECT_EQ(nullptr, overrideBuffer1); // did not @@ -868,7 +868,7 @@ TEST_F(FlattenerTest, flattenLayers_flattensBlurBehindRunIfFirstRun) { initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); for (const auto layer : layers) { EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer); @@ -878,7 +878,7 @@ TEST_F(FlattenerTest, flattenLayers_flattensBlurBehindRunIfFirstRun) { initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_NE(nullptr, overrideBuffer1); EXPECT_EQ(overrideBuffer1, overrideBuffer2); EXPECT_EQ(nullptr, overrideBuffer3); @@ -914,7 +914,7 @@ TEST_F(FlattenerTest, flattenLayers_doesNotFlattenBlurBehindRun) { initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); for (const auto layer : layers) { EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer); @@ -925,7 +925,7 @@ TEST_F(FlattenerTest, flattenLayers_doesNotFlattenBlurBehindRun) { initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); for (const auto layer : layers) { EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer); } @@ -968,7 +968,7 @@ TEST_F(FlattenerTest, flattenLayers_flattenSkipsLayerWithBlurBehind) { initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); for (const auto layer : layers) { EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer); @@ -978,7 +978,7 @@ TEST_F(FlattenerTest, flattenLayers_flattenSkipsLayerWithBlurBehind) { initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_EQ(nullptr, overrideBuffer1); EXPECT_EQ(nullptr, blurOverrideBuffer); EXPECT_NE(nullptr, overrideBuffer3); @@ -1017,7 +1017,7 @@ TEST_F(FlattenerTest, flattenLayers_whenBlurLayerIsChanging_appliesBlurToInactiv initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); const auto& cachedSet = mFlattener->getNewCachedSetForTesting(); ASSERT_NE(std::nullopt, cachedSet); @@ -1031,7 +1031,7 @@ TEST_F(FlattenerTest, flattenLayers_whenBlurLayerIsChanging_appliesBlurToInactiv initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_NE(nullptr, overrideBuffer1); EXPECT_EQ(overrideBuffer2, overrideBuffer1); EXPECT_EQ(nullptr, blurOverrideBuffer); @@ -1060,7 +1060,7 @@ TEST_F(FlattenerTest, flattenLayers_renderCachedSets_doesNotRenderTwice) { initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_EQ(nullptr, overrideBuffer1); EXPECT_EQ(nullptr, overrideBuffer2); @@ -1068,12 +1068,12 @@ TEST_F(FlattenerTest, flattenLayers_renderCachedSets_doesNotRenderTwice) { // Simulate attempting to render prior to merging the new cached set with the layer stack. // Here we should not try to re-render. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We provide the override buffer now that it's rendered EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_NE(nullptr, overrideBuffer1); EXPECT_EQ(overrideBuffer2, overrideBuffer1); @@ -1117,7 +1117,8 @@ TEST_F(FlattenerRenderSchedulingTest, flattenLayers_renderCachedSets_defersUpToM EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0); mFlattener->renderCachedSets(mOutputState, std::chrono::steady_clock::now() - - (kCachedSetRenderDuration + 10ms)); + (kCachedSetRenderDuration + 10ms), + true); } EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) @@ -1125,7 +1126,8 @@ TEST_F(FlattenerRenderSchedulingTest, flattenLayers_renderCachedSets_defersUpToM futureOf({NO_ERROR, base::unique_fd()})))); mFlattener->renderCachedSets(mOutputState, std::chrono::steady_clock::now() - - (kCachedSetRenderDuration + 10ms)); + (kCachedSetRenderDuration + 10ms), + true); } TEST_F(FlattenerTest, flattenLayers_skipsBT601_625) { @@ -1159,7 +1161,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsBT601_625) { EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) .WillOnce(Return(ByMove( futureOf({NO_ERROR, base::unique_fd()})))); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. EXPECT_EQ(nullptr, overrideBuffer1); @@ -1172,7 +1174,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsBT601_625) { initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_NE(nullptr, overrideBuffer1); EXPECT_EQ(overrideBuffer1, overrideBuffer2); @@ -1210,7 +1212,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsHDR) { EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) .WillOnce(Return(ByMove( futureOf({NO_ERROR, base::unique_fd()})))); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. EXPECT_EQ(nullptr, overrideBuffer1); @@ -1223,7 +1225,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsHDR) { initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_NE(nullptr, overrideBuffer1); EXPECT_EQ(overrideBuffer1, overrideBuffer2); @@ -1261,7 +1263,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsHDR2) { EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) .WillOnce(Return(ByMove( futureOf({NO_ERROR, base::unique_fd()})))); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. EXPECT_EQ(nullptr, overrideBuffer1); @@ -1274,7 +1276,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsHDR2) { initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_NE(nullptr, overrideBuffer1); EXPECT_EQ(overrideBuffer1, overrideBuffer2); @@ -1315,7 +1317,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsColorLayers) { EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) .WillOnce(Return(ByMove( futureOf({NO_ERROR, base::unique_fd()})))); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. EXPECT_EQ(nullptr, overrideBuffer1); @@ -1329,7 +1331,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsColorLayers) { initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_EQ(nullptr, overrideBuffer1); EXPECT_EQ(nullptr, overrideBuffer2); @@ -1368,7 +1370,7 @@ TEST_F(FlattenerTest, flattenLayers_includes_DISPLAY_DECORATION) { EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) .WillOnce(Return(ByMove( futureOf({NO_ERROR, base::unique_fd()})))); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. EXPECT_EQ(nullptr, overrideBuffer1); @@ -1381,7 +1383,7 @@ TEST_F(FlattenerTest, flattenLayers_includes_DISPLAY_DECORATION) { initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); - mFlattener->renderCachedSets(mOutputState, std::nullopt); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); EXPECT_NE(nullptr, overrideBuffer1); EXPECT_EQ(overrideBuffer1, overrideBuffer2); -- GitLab From 5189478291e79cfe9bb5aba30cbb7de856b6bfcc Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 23 Aug 2022 16:29:10 +0000 Subject: [PATCH 0228/1310] EventHub: Associate AsociatedDevice using sysfs path, not descriptor EventHub previously attempted to link an EventHub device to an AssociatedDevice - which contains info about the EventHub device obtained through sysfs - through the descriptor. Since each EventHub device has a unique descriptor whose uniqueness is guaranteed by the use of the nonce value, using the descriptor to link the AssociatedDevice to device means that no two devices will share the same AssociatedDevice even if they have the same sysfs paths. This is clearly not working as intended. We now link the AssociatedDevice to the EventHub device by the sysfs path so that separate EventHub devices that share the same sysfs path also share the same AssociatedDevice. Unfortunately, we don't have good code coverage for reading from sysfs, and there are no existing test cases. Bug: 243005009 Test: Manual, check input dump Change-Id: Iea2bb7fd0493baa6e03df15f876c5d895c997b14 --- services/inputflinger/reader/EventHub.cpp | 34 +++++++++---------- .../inputflinger/reader/include/EventHub.h | 5 ++- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index bfa44acd17..db5032d51e 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -1060,7 +1060,7 @@ const std::vector EventHub::getRawBatteryIds(int32_t deviceId) { std::scoped_lock _l(mLock); std::vector batteryIds; - for (const auto [id, info] : getBatteryInfoLocked(deviceId)) { + for (const auto& [id, info] : getBatteryInfoLocked(deviceId)) { batteryIds.push_back(id); } @@ -1081,7 +1081,7 @@ std::optional EventHub::getRawBatteryInfo(int32_t deviceId, int3 } // Gets the light info map from light ID to RawLightInfo of the miscellaneous device associated -// with the deivice ID. Returns an empty map if no miscellaneous device found. +// with the device ID. Returns an empty map if no miscellaneous device found. const std::unordered_map& EventHub::getLightInfoLocked( int32_t deviceId) const { static const std::unordered_map EMPTY_LIGHT_INFO = {}; @@ -1096,7 +1096,7 @@ const std::vector EventHub::getRawLightIds(int32_t deviceId) { std::scoped_lock _l(mLock); std::vector lightIds; - for (const auto [id, info] : getLightInfoLocked(deviceId)) { + for (const auto& [id, info] : getLightInfoLocked(deviceId)) { lightIds.push_back(id); } @@ -2042,23 +2042,20 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { // Load the configuration file for the device. device->loadConfigurationLocked(); - bool hasBattery = false; - bool hasLights = false; // Check the sysfs root path - std::optional sysfsRootPath = getSysfsRootPath(devicePath.c_str()); + const std::optional sysfsRootPath = getSysfsRootPath(devicePath.c_str()); if (sysfsRootPath.has_value()) { std::shared_ptr associatedDevice; for (const auto& [id, dev] : mDevices) { - if (device->identifier.descriptor == dev->identifier.descriptor && - !dev->associatedDevice) { + if (dev->associatedDevice && dev->associatedDevice->sysfsRootPath == *sysfsRootPath) { associatedDevice = dev->associatedDevice; } } if (!associatedDevice) { associatedDevice = std::make_shared(sysfsRootPath.value()); + associatedDevice->configureBatteryLocked(); + associatedDevice->configureLightsLocked(); } - hasBattery = associatedDevice->configureBatteryLocked(); - hasLights = associatedDevice->configureLightsLocked(); device->associatedDevice = associatedDevice; } @@ -2212,12 +2209,12 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { } // Classify InputDeviceClass::BATTERY. - if (hasBattery) { + if (device->associatedDevice && !device->associatedDevice->batteryInfos.empty()) { device->classes |= InputDeviceClass::BATTERY; } // Classify InputDeviceClass::LIGHT. - if (hasLights) { + if (device->associatedDevice && !device->associatedDevice->lightInfos.empty()) { device->classes |= InputDeviceClass::LIGHT; } @@ -2544,12 +2541,13 @@ void EventHub::dump(std::string& dump) { device->keyMap.keyCharacterMapFile.c_str()); dump += StringPrintf(INDENT3 "ConfigurationFile: %s\n", device->configurationFile.c_str()); - dump += INDENT3 "VideoDevice: "; - if (device->videoDevice) { - dump += device->videoDevice->dump() + "\n"; - } else { - dump += "\n"; - } + dump += StringPrintf(INDENT3 "VideoDevice: %s\n", + device->videoDevice ? device->videoDevice->dump().c_str() + : ""); + dump += StringPrintf(INDENT3 "SysfsDevicePath: %s\n", + device->associatedDevice + ? device->associatedDevice->sysfsRootPath.c_str() + : ""); } dump += INDENT "Unattached video devices:\n"; diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index 4733ecb323..7d4196a05c 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -537,8 +538,6 @@ public: private: struct AssociatedDevice { - // The device descriptor from evdev device the misc device associated with. - std::string descriptor; // The sysfs root path of the misc device. std::filesystem::path sysfsRootPath; @@ -582,7 +581,7 @@ private: int16_t ffEffectId; // initially -1 // A shared_ptr of a device associated with the input device. - // The input devices with same descriptor has the same associated device. + // The input devices that have the same sysfs path have the same associated device. std::shared_ptr associatedDevice; int32_t controllerNumber; -- GitLab From cb42b4719fc24da4bc0b5a008e87ff1a350ff9b7 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 23 Aug 2022 16:01:19 +0000 Subject: [PATCH 0229/1310] Refactor AssociatedDevice initialization in EventHub Since AssociatedDevice contains information from the sysfs classes of a device that are loaded once when the device is connected, we change the AssociatedDevice initialization logic to be more functional and easier to read. There should be no behaivor change in this CL. Bug: 243005009 Test: atest inputflinger_tests Change-Id: I753fb07af9558c844e0c4eb7b8558b4f78b58004 --- services/inputflinger/reader/EventHub.cpp | 205 ++++++++++-------- .../inputflinger/reader/include/EventHub.h | 19 +- 2 files changed, 117 insertions(+), 107 deletions(-) diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index db5032d51e..5744b83d64 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -52,6 +52,7 @@ #include #include +#include #include "EventHub.h" @@ -193,8 +194,7 @@ static nsecs_t processEventTimestamp(const struct input_event& event) { } /** - * Returns the sysfs root path of the input device - * + * Returns the sysfs root path of the input device. */ static std::optional getSysfsRootPath(const char* devicePath) { std::error_code errorCode; @@ -301,6 +301,83 @@ static std::optional> getColorIndexArray( return colors; } +/** + * Read information about batteries exposed through the sysfs path. + */ +static std::unordered_map readBatteryConfiguration( + const std::filesystem::path& sysfsRootPath) { + std::unordered_map batteryInfos; + int32_t nextBatteryId = 0; + // Check if device has any battery. + const auto& paths = findSysfsNodes(sysfsRootPath, SysfsClass::POWER_SUPPLY); + for (const auto& nodePath : paths) { + RawBatteryInfo info; + info.id = ++nextBatteryId; + info.path = nodePath; + info.name = nodePath.filename(); + + // Scan the path for all the files + // Refer to https://www.kernel.org/doc/Documentation/leds/leds-class.txt + const auto& files = allFilesInPath(nodePath); + for (const auto& file : files) { + const auto it = BATTERY_CLASSES.find(file.filename().string()); + if (it != BATTERY_CLASSES.end()) { + info.flags |= it->second; + } + } + batteryInfos.insert_or_assign(info.id, info); + ALOGD("configureBatteryLocked rawBatteryId %d name %s", info.id, info.name.c_str()); + } + return batteryInfos; +} + +/** + * Read information about lights exposed through the sysfs path. + */ +static std::unordered_map readLightsConfiguration( + const std::filesystem::path& sysfsRootPath) { + std::unordered_map lightInfos; + int32_t nextLightId = 0; + // Check if device has any lights. + const auto& paths = findSysfsNodes(sysfsRootPath, SysfsClass::LEDS); + for (const auto& nodePath : paths) { + RawLightInfo info; + info.id = ++nextLightId; + info.path = nodePath; + info.name = nodePath.filename(); + info.maxBrightness = std::nullopt; + size_t nameStart = info.name.rfind(":"); + if (nameStart != std::string::npos) { + // Trim the name to color name + info.name = info.name.substr(nameStart + 1); + // Set InputLightClass flag for colors + const auto it = LIGHT_CLASSES.find(info.name); + if (it != LIGHT_CLASSES.end()) { + info.flags |= it->second; + } + } + // Scan the path for all the files + // Refer to https://www.kernel.org/doc/Documentation/leds/leds-class.txt + const auto& files = allFilesInPath(nodePath); + for (const auto& file : files) { + const auto it = LIGHT_CLASSES.find(file.filename().string()); + if (it != LIGHT_CLASSES.end()) { + info.flags |= it->second; + // If the node has maximum brightness, read it + if (it->second == InputLightClass::MAX_BRIGHTNESS) { + std::string str; + if (base::ReadFileToString(file, &str)) { + info.maxBrightness = std::stoi(str); + } + } + } + } + lightInfos.insert_or_assign(info.id, info); + ALOGD("configureLightsLocked rawLightId %d name %s", info.id, info.name.c_str()); + } + return lightInfos; +} + // --- Global Functions --- ftl::Flags getAbsAxisUsage(int32_t axis, @@ -357,18 +434,18 @@ ftl::Flags getAbsAxisUsage(int32_t axis, // --- EventHub::Device --- -EventHub::Device::Device(int fd, int32_t id, const std::string& path, - const InputDeviceIdentifier& identifier) +EventHub::Device::Device(int fd, int32_t id, std::string path, InputDeviceIdentifier identifier, + std::shared_ptr assocDev) : fd(fd), id(id), - path(path), - identifier(identifier), + path(std::move(path)), + identifier(std::move(identifier)), classes(0), configuration(nullptr), virtualKeyMap(nullptr), ffEffectPlaying(false), ffEffectId(-1), - associatedDevice(nullptr), + associatedDevice(std::move(assocDev)), controllerNumber(0), enabled(true), isVirtual(fd < 0) {} @@ -557,75 +634,6 @@ status_t EventHub::Device::mapLed(int32_t led, int32_t* outScanCode) const { return NAME_NOT_FOUND; } -// Check the sysfs path for any input device batteries, returns true if battery found. -bool EventHub::AssociatedDevice::configureBatteryLocked() { - nextBatteryId = 0; - // Check if device has any battery. - const auto& paths = findSysfsNodes(sysfsRootPath, SysfsClass::POWER_SUPPLY); - for (const auto& nodePath : paths) { - RawBatteryInfo info; - info.id = ++nextBatteryId; - info.path = nodePath; - info.name = nodePath.filename(); - - // Scan the path for all the files - // Refer to https://www.kernel.org/doc/Documentation/leds/leds-class.txt - const auto& files = allFilesInPath(nodePath); - for (const auto& file : files) { - const auto it = BATTERY_CLASSES.find(file.filename().string()); - if (it != BATTERY_CLASSES.end()) { - info.flags |= it->second; - } - } - batteryInfos.insert_or_assign(info.id, info); - ALOGD("configureBatteryLocked rawBatteryId %d name %s", info.id, info.name.c_str()); - } - return !batteryInfos.empty(); -} - -// Check the sysfs path for any input device lights, returns true if lights found. -bool EventHub::AssociatedDevice::configureLightsLocked() { - nextLightId = 0; - // Check if device has any lights. - const auto& paths = findSysfsNodes(sysfsRootPath, SysfsClass::LEDS); - for (const auto& nodePath : paths) { - RawLightInfo info; - info.id = ++nextLightId; - info.path = nodePath; - info.name = nodePath.filename(); - info.maxBrightness = std::nullopt; - size_t nameStart = info.name.rfind(":"); - if (nameStart != std::string::npos) { - // Trim the name to color name - info.name = info.name.substr(nameStart + 1); - // Set InputLightClass flag for colors - const auto it = LIGHT_CLASSES.find(info.name); - if (it != LIGHT_CLASSES.end()) { - info.flags |= it->second; - } - } - // Scan the path for all the files - // Refer to https://www.kernel.org/doc/Documentation/leds/leds-class.txt - const auto& files = allFilesInPath(nodePath); - for (const auto& file : files) { - const auto it = LIGHT_CLASSES.find(file.filename().string()); - if (it != LIGHT_CLASSES.end()) { - info.flags |= it->second; - // If the node has maximum brightness, read it - if (it->second == InputLightClass::MAX_BRIGHTNESS) { - std::string str; - if (base::ReadFileToString(file, &str)) { - info.maxBrightness = std::stoi(str); - } - } - } - } - lightInfos.insert_or_assign(info.id, info); - ALOGD("configureLightsLocked rawLightId %d name %s", info.id, info.name.c_str()); - } - return !lightInfos.empty(); -} - /** * Get the capabilities for the current process. * Crashes the system if unable to create / check / destroy the capabilities object. @@ -1358,6 +1366,27 @@ void EventHub::assignDescriptorLocked(InputDeviceIdentifier& identifier) { identifier.descriptor.c_str()); } +std::shared_ptr EventHub::obtainAssociatedDeviceLocked( + const std::filesystem::path& devicePath) const { + const std::optional sysfsRootPathOpt = + getSysfsRootPath(devicePath.c_str()); + if (!sysfsRootPathOpt) { + return nullptr; + } + + const auto& path = *sysfsRootPathOpt; + for (const auto& [id, dev] : mDevices) { + if (dev->associatedDevice && dev->associatedDevice->sysfsRootPath == path) { + return dev->associatedDevice; + } + } + + return std::make_shared( + AssociatedDevice{.sysfsRootPath = path, + .batteryInfos = readBatteryConfiguration(path), + .lightInfos = readLightsConfiguration(path)}); +} + void EventHub::vibrate(int32_t deviceId, const VibrationElement& element) { std::scoped_lock _l(mLock); Device* device = getDeviceLocked(deviceId); @@ -2024,7 +2053,9 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { // Allocate device. (The device object takes ownership of the fd at this point.) int32_t deviceId = mNextDeviceId++; - std::unique_ptr device = std::make_unique(fd, deviceId, devicePath, identifier); + std::unique_ptr device = + std::make_unique(fd, deviceId, devicePath, identifier, + obtainAssociatedDeviceLocked(devicePath)); ALOGV("add device %d: %s\n", deviceId, devicePath.c_str()); ALOGV(" bus: %04x\n" @@ -2042,24 +2073,6 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { // Load the configuration file for the device. device->loadConfigurationLocked(); - // Check the sysfs root path - const std::optional sysfsRootPath = getSysfsRootPath(devicePath.c_str()); - if (sysfsRootPath.has_value()) { - std::shared_ptr associatedDevice; - for (const auto& [id, dev] : mDevices) { - if (dev->associatedDevice && dev->associatedDevice->sysfsRootPath == *sysfsRootPath) { - associatedDevice = dev->associatedDevice; - } - } - if (!associatedDevice) { - associatedDevice = std::make_shared(sysfsRootPath.value()); - associatedDevice->configureBatteryLocked(); - associatedDevice->configureLightsLocked(); - } - - device->associatedDevice = associatedDevice; - } - // Figure out the kinds of events the device reports. device->readDeviceBitMask(EVIOCGBIT(EV_KEY, 0), device->keyBitmask); device->readDeviceBitMask(EVIOCGBIT(EV_ABS, 0), device->absBitmask); @@ -2337,7 +2350,7 @@ void EventHub::createVirtualKeyboardLocked() { std::unique_ptr device = std::make_unique(-1, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, "", - identifier); + identifier, nullptr /*associatedDevice*/); device->classes = InputDeviceClass::KEYBOARD | InputDeviceClass::ALPHAKEY | InputDeviceClass::DPAD | InputDeviceClass::VIRTUAL; device->loadKeyMapLocked(); diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index 7d4196a05c..60460b4105 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -537,18 +537,13 @@ public: ~EventHub() override; private: + // Holds information about the sysfs device associated with the Device. struct AssociatedDevice { // The sysfs root path of the misc device. std::filesystem::path sysfsRootPath; - int32_t nextBatteryId; - int32_t nextLightId; - std::unordered_map batteryInfos; - std::unordered_map lightInfos; - explicit AssociatedDevice(std::filesystem::path sysfsRootPath) - : sysfsRootPath(sysfsRootPath), nextBatteryId(0), nextLightId(0) {} - bool configureBatteryLocked(); - bool configureLightsLocked(); + std::unordered_map batteryInfos; + std::unordered_map lightInfos; }; struct Device { @@ -582,12 +577,12 @@ private: // A shared_ptr of a device associated with the input device. // The input devices that have the same sysfs path have the same associated device. - std::shared_ptr associatedDevice; + const std::shared_ptr associatedDevice; int32_t controllerNumber; - Device(int fd, int32_t id, const std::string& path, - const InputDeviceIdentifier& identifier); + Device(int fd, int32_t id, std::string path, InputDeviceIdentifier identifier, + std::shared_ptr assocDev); ~Device(); void close(); @@ -632,6 +627,8 @@ private: void createVirtualKeyboardLocked() REQUIRES(mLock); void addDeviceLocked(std::unique_ptr device) REQUIRES(mLock); void assignDescriptorLocked(InputDeviceIdentifier& identifier) REQUIRES(mLock); + std::shared_ptr obtainAssociatedDeviceLocked( + const std::filesystem::path& devicePath) const REQUIRES(mLock); void closeDeviceByPathLocked(const std::string& devicePath) REQUIRES(mLock); void closeVideoDeviceByPathLocked(const std::string& devicePath) REQUIRES(mLock); -- GitLab From ae4ff289d1ac2ac4f5a5b5bac9c4eb3b837a464b Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 23 Aug 2022 16:21:39 +0000 Subject: [PATCH 0230/1310] Improve EventHub const-correctness Ensure that all methods that "get" state from event hub are const-correct. While some "set" methods can also be const in event hub because they write to the system (side effect) instead of changing state, we leave these as non-const to make it easier for testing. Bug: None Test: atest inputflinger_tests Change-Id: I086b50458203a2395b1960e2bc1102610a3c0801 --- include/input/KeyLayoutMap.h | 2 +- libs/input/KeyLayoutMap.cpp | 3 +- services/inputflinger/reader/EventHub.cpp | 25 +++++----- .../inputflinger/reader/include/EventHub.h | 49 ++++++++++--------- .../inputflinger/tests/InputReader_test.cpp | 33 ++++++------- 5 files changed, 57 insertions(+), 55 deletions(-) diff --git a/include/input/KeyLayoutMap.h b/include/input/KeyLayoutMap.h index 1da78aa0c1..a6c696df26 100644 --- a/include/input/KeyLayoutMap.h +++ b/include/input/KeyLayoutMap.h @@ -78,7 +78,7 @@ public: std::optional mapAxis(int32_t scanCode) const; const std::string getLoadFileName() const; // Return pair of sensor type and sensor data index, for the input device abs code - base::Result> mapSensor(int32_t absCode); + base::Result> mapSensor(int32_t absCode) const; virtual ~KeyLayoutMap(); diff --git a/libs/input/KeyLayoutMap.cpp b/libs/input/KeyLayoutMap.cpp index 59cc7d1dcd..31aed517dd 100644 --- a/libs/input/KeyLayoutMap.cpp +++ b/libs/input/KeyLayoutMap.cpp @@ -185,7 +185,8 @@ status_t KeyLayoutMap::mapKey(int32_t scanCode, int32_t usageCode, } // Return pair of sensor type and sensor data index, for the input device abs code -base::Result> KeyLayoutMap::mapSensor(int32_t absCode) { +base::Result> KeyLayoutMap::mapSensor( + int32_t absCode) const { auto it = mSensorsByAbsCode.find(absCode); if (it == mSensorsByAbsCode.end()) { ALOGD_IF(DEBUG_MAPPING, "mapSensor: absCode=%d, ~ Failed.", absCode); diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index 5744b83d64..06a38c8f0f 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -1042,7 +1042,7 @@ status_t EventHub::mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxis } base::Result> EventHub::mapSensor(int32_t deviceId, - int32_t absCode) { + int32_t absCode) const { std::scoped_lock _l(mLock); Device* device = getDeviceLocked(deviceId); @@ -1064,7 +1064,7 @@ const std::unordered_map& EventHub::getBatteryInfoLocke return device->associatedDevice->batteryInfos; } -const std::vector EventHub::getRawBatteryIds(int32_t deviceId) { +std::vector EventHub::getRawBatteryIds(int32_t deviceId) const { std::scoped_lock _l(mLock); std::vector batteryIds; @@ -1075,7 +1075,8 @@ const std::vector EventHub::getRawBatteryIds(int32_t deviceId) { return batteryIds; } -std::optional EventHub::getRawBatteryInfo(int32_t deviceId, int32_t batteryId) { +std::optional EventHub::getRawBatteryInfo(int32_t deviceId, + int32_t batteryId) const { std::scoped_lock _l(mLock); const auto infos = getBatteryInfoLocked(deviceId); @@ -1100,7 +1101,7 @@ const std::unordered_map& EventHub::getLightInfoLocked( return device->associatedDevice->lightInfos; } -const std::vector EventHub::getRawLightIds(int32_t deviceId) { +std::vector EventHub::getRawLightIds(int32_t deviceId) const { std::scoped_lock _l(mLock); std::vector lightIds; @@ -1111,7 +1112,7 @@ const std::vector EventHub::getRawLightIds(int32_t deviceId) { return lightIds; } -std::optional EventHub::getRawLightInfo(int32_t deviceId, int32_t lightId) { +std::optional EventHub::getRawLightInfo(int32_t deviceId, int32_t lightId) const { std::scoped_lock _l(mLock); const auto infos = getLightInfoLocked(deviceId); @@ -1124,7 +1125,7 @@ std::optional EventHub::getRawLightInfo(int32_t deviceId, int32_t return std::nullopt; } -std::optional EventHub::getLightBrightness(int32_t deviceId, int32_t lightId) { +std::optional EventHub::getLightBrightness(int32_t deviceId, int32_t lightId) const { std::scoped_lock _l(mLock); const auto infos = getLightInfoLocked(deviceId); @@ -1141,7 +1142,7 @@ std::optional EventHub::getLightBrightness(int32_t deviceId, int32_t li } std::optional> EventHub::getLightIntensities( - int32_t deviceId, int32_t lightId) { + int32_t deviceId, int32_t lightId) const { std::scoped_lock _l(mLock); const auto infos = getLightInfoLocked(deviceId); @@ -1444,7 +1445,7 @@ void EventHub::cancelVibrate(int32_t deviceId) { } } -std::vector EventHub::getVibratorIds(int32_t deviceId) { +std::vector EventHub::getVibratorIds(int32_t deviceId) const { std::scoped_lock _l(mLock); std::vector vibrators; Device* device = getDeviceLocked(deviceId); @@ -2295,7 +2296,7 @@ bool EventHub::tryAddVideoDeviceLocked(EventHub::Device& device, return true; } -bool EventHub::isDeviceEnabled(int32_t deviceId) { +bool EventHub::isDeviceEnabled(int32_t deviceId) const { std::scoped_lock _l(mLock); Device* device = getDeviceLocked(deviceId); if (device == nullptr) { @@ -2519,7 +2520,7 @@ void EventHub::requestReopenDevices() { mNeedToReopenDevices = true; } -void EventHub::dump(std::string& dump) { +void EventHub::dump(std::string& dump) const { dump += "Event Hub State:\n"; { // acquire lock @@ -2573,9 +2574,9 @@ void EventHub::dump(std::string& dump) { } // release lock } -void EventHub::monitor() { +void EventHub::monitor() const { // Acquire and release the lock to ensure that the event hub has not deadlocked. std::unique_lock lock(mLock); } -}; // namespace android +} // namespace android diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index 60460b4105..8aec367795 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -282,22 +282,23 @@ public: */ virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) = 0; virtual std::vector getVideoFrames(int32_t deviceId) = 0; - virtual base::Result> mapSensor(int32_t deviceId, - int32_t absCode) = 0; + virtual base::Result> mapSensor( + int32_t deviceId, int32_t absCode) const = 0; // Raw batteries are sysfs power_supply nodes we found from the EventHub device sysfs node, // containing the raw info of the sysfs node structure. - virtual const std::vector getRawBatteryIds(int32_t deviceId) = 0; + virtual std::vector getRawBatteryIds(int32_t deviceId) const = 0; virtual std::optional getRawBatteryInfo(int32_t deviceId, - int32_t BatteryId) = 0; + int32_t BatteryId) const = 0; // Raw lights are sysfs led light nodes we found from the EventHub device sysfs node, // containing the raw info of the sysfs node structure. - virtual const std::vector getRawLightIds(int32_t deviceId) = 0; - virtual std::optional getRawLightInfo(int32_t deviceId, int32_t lightId) = 0; - virtual std::optional getLightBrightness(int32_t deviceId, int32_t lightId) = 0; + virtual std::vector getRawLightIds(int32_t deviceId) const = 0; + virtual std::optional getRawLightInfo(int32_t deviceId, + int32_t lightId) const = 0; + virtual std::optional getLightBrightness(int32_t deviceId, int32_t lightId) const = 0; virtual void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) = 0; virtual std::optional> getLightIntensities( - int32_t deviceId, int32_t lightId) = 0; + int32_t deviceId, int32_t lightId) const = 0; virtual void setLightIntensities(int32_t deviceId, int32_t lightId, std::unordered_map intensities) = 0; /* @@ -333,7 +334,7 @@ public: /* Control the vibrator. */ virtual void vibrate(int32_t deviceId, const VibrationElement& effect) = 0; virtual void cancelVibrate(int32_t deviceId) = 0; - virtual std::vector getVibratorIds(int32_t deviceId) = 0; + virtual std::vector getVibratorIds(int32_t deviceId) const = 0; /* Query battery level. */ virtual std::optional getBatteryCapacity(int32_t deviceId, @@ -349,13 +350,13 @@ public: virtual void wake() = 0; /* Dump EventHub state to a string. */ - virtual void dump(std::string& dump) = 0; + virtual void dump(std::string& dump) const = 0; /* Called by the heatbeat to ensures that the reader has not deadlocked. */ - virtual void monitor() = 0; + virtual void monitor() const = 0; /* Return true if the device is enabled. */ - virtual bool isDeviceEnabled(int32_t deviceId) = 0; + virtual bool isDeviceEnabled(int32_t deviceId) const = 0; /* Enable an input device */ virtual status_t enableDevice(int32_t deviceId) = 0; @@ -463,20 +464,22 @@ public: AxisInfo* outAxisInfo) const override final; base::Result> mapSensor( - int32_t deviceId, int32_t absCode) override final; + int32_t deviceId, int32_t absCode) const override final; - const std::vector getRawBatteryIds(int32_t deviceId) override final; + std::vector getRawBatteryIds(int32_t deviceId) const override final; std::optional getRawBatteryInfo(int32_t deviceId, - int32_t BatteryId) override final; + int32_t BatteryId) const override final; - const std::vector getRawLightIds(int32_t deviceId) override final; + std::vector getRawLightIds(int32_t deviceId) const override final; - std::optional getRawLightInfo(int32_t deviceId, int32_t lightId) override final; + std::optional getRawLightInfo(int32_t deviceId, + int32_t lightId) const override final; - std::optional getLightBrightness(int32_t deviceId, int32_t lightId) override final; + std::optional getLightBrightness(int32_t deviceId, + int32_t lightId) const override final; void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) override final; std::optional> getLightIntensities( - int32_t deviceId, int32_t lightId) override final; + int32_t deviceId, int32_t lightId) const override final; void setLightIntensities(int32_t deviceId, int32_t lightId, std::unordered_map intensities) override final; @@ -512,15 +515,15 @@ public: void vibrate(int32_t deviceId, const VibrationElement& effect) override final; void cancelVibrate(int32_t deviceId) override final; - std::vector getVibratorIds(int32_t deviceId) override final; + std::vector getVibratorIds(int32_t deviceId) const override final; void requestReopenDevices() override final; void wake() override final; - void dump(std::string& dump) override final; + void dump(std::string& dump) const override final; - void monitor() override final; + void monitor() const override final; std::optional getBatteryCapacity(int32_t deviceId, int32_t batteryId) const override final; @@ -528,7 +531,7 @@ public: std::optional getBatteryStatus(int32_t deviceId, int32_t batteryId) const override final; - bool isDeviceEnabled(int32_t deviceId) override final; + bool isDeviceEnabled(int32_t deviceId) const override final; status_t enableDevice(int32_t deviceId) override final; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index e80a95648b..f1c1ffcf66 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -509,7 +509,7 @@ public: enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0); } - bool isDeviceEnabled(int32_t deviceId) { + bool isDeviceEnabled(int32_t deviceId) const override { Device* device = getDevice(deviceId); if (device == nullptr) { ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); @@ -518,7 +518,7 @@ public: return device->enabled; } - status_t enableDevice(int32_t deviceId) { + status_t enableDevice(int32_t deviceId) override { status_t result; Device* device = getDevice(deviceId); if (device == nullptr) { @@ -533,7 +533,7 @@ public: return result; } - status_t disableDevice(int32_t deviceId) { + status_t disableDevice(int32_t deviceId) override { Device* device = getDevice(deviceId); if (device == nullptr) { ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); @@ -795,8 +795,8 @@ private: status_t mapAxis(int32_t, int32_t, AxisInfo*) const override { return NAME_NOT_FOUND; } - base::Result> mapSensor(int32_t deviceId, - int32_t absCode) { + base::Result> mapSensor( + int32_t deviceId, int32_t absCode) const override { Device* device = getDevice(deviceId); if (!device) { return Errorf("Sensor device not found."); @@ -981,7 +981,7 @@ private: void cancelVibrate(int32_t) override {} - std::vector getVibratorIds(int32_t deviceId) override { return mVibrators; }; + std::vector getVibratorIds(int32_t deviceId) const override { return mVibrators; }; std::optional getBatteryCapacity(int32_t, int32_t) const override { return BATTERY_CAPACITY; @@ -991,13 +991,14 @@ private: return BATTERY_STATUS; } - const std::vector getRawBatteryIds(int32_t deviceId) { return {}; } + std::vector getRawBatteryIds(int32_t deviceId) const override { return {}; } - std::optional getRawBatteryInfo(int32_t deviceId, int32_t batteryId) { + std::optional getRawBatteryInfo(int32_t deviceId, + int32_t batteryId) const override { return std::nullopt; } - const std::vector getRawLightIds(int32_t deviceId) override { + std::vector getRawLightIds(int32_t deviceId) const override { std::vector ids; for (const auto& [rawId, info] : mRawLightInfos) { ids.push_back(rawId); @@ -1005,7 +1006,7 @@ private: return ids; } - std::optional getRawLightInfo(int32_t deviceId, int32_t lightId) override { + std::optional getRawLightInfo(int32_t deviceId, int32_t lightId) const override { auto it = mRawLightInfos.find(lightId); if (it == mRawLightInfos.end()) { return std::nullopt; @@ -1022,7 +1023,7 @@ private: mLightIntensities.emplace(lightId, intensities); }; - std::optional getLightBrightness(int32_t deviceId, int32_t lightId) override { + std::optional getLightBrightness(int32_t deviceId, int32_t lightId) const override { auto lightIt = mLightBrightness.find(lightId); if (lightIt == mLightBrightness.end()) { return std::nullopt; @@ -1031,7 +1032,7 @@ private: } std::optional> getLightIntensities( - int32_t deviceId, int32_t lightId) override { + int32_t deviceId, int32_t lightId) const override { auto lightIt = mLightIntensities.find(lightId); if (lightIt == mLightIntensities.end()) { return std::nullopt; @@ -1039,13 +1040,9 @@ private: return lightIt->second; }; - virtual bool isExternal(int32_t) const { - return false; - } - - void dump(std::string&) override {} + void dump(std::string&) const override {} - void monitor() override {} + void monitor() const override {} void requestReopenDevices() override {} -- GitLab From 4548360368b3c9b47ddc0b478fa15715822ba6f0 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Wed, 24 Aug 2022 14:36:48 +0000 Subject: [PATCH 0231/1310] Use ALOGD_IF in TouchInputMapper This makes the code a little easier to read. Bug: none Test: enable the DEBUG_ parameters, check the logs appear Change-Id: I6f307a7572ff512e8b5d02cbf90889e22544a4e7 --- .../reader/mapper/TouchInputMapper.cpp | 284 +++++++----------- 1 file changed, 113 insertions(+), 171 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 924c4d0ba5..2ebbd3716b 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -1524,14 +1524,13 @@ void TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { assignPointerIds(last, next); } - if (DEBUG_RAW_EVENTS) { - ALOGD("syncTouch: pointerCount %d -> %d, touching ids 0x%08x -> 0x%08x, " - "hovering ids 0x%08x -> 0x%08x, canceled ids 0x%08x", - last.rawPointerData.pointerCount, next.rawPointerData.pointerCount, - last.rawPointerData.touchingIdBits.value, next.rawPointerData.touchingIdBits.value, - last.rawPointerData.hoveringIdBits.value, next.rawPointerData.hoveringIdBits.value, - next.rawPointerData.canceledIdBits.value); - } + ALOGD_IF(DEBUG_RAW_EVENTS, + "syncTouch: pointerCount %d -> %d, touching ids 0x%08x -> 0x%08x, " + "hovering ids 0x%08x -> 0x%08x, canceled ids 0x%08x", + last.rawPointerData.pointerCount, next.rawPointerData.pointerCount, + last.rawPointerData.touchingIdBits.value, next.rawPointerData.touchingIdBits.value, + last.rawPointerData.hoveringIdBits.value, next.rawPointerData.hoveringIdBits.value, + next.rawPointerData.canceledIdBits.value); if (!next.rawPointerData.touchingIdBits.isEmpty() && !next.rawPointerData.hoveringIdBits.isEmpty() && @@ -1585,9 +1584,8 @@ void TouchInputMapper::processRawTouches(bool timeout) { nsecs_t when = mExternalStylusFusionTimeout - STYLUS_DATA_LATENCY; clearStylusDataPendingFlags(); mCurrentRawState.copyFrom(mLastRawState); - if (DEBUG_STYLUS_FUSION) { - ALOGD("Timeout expired, synthesizing event with new stylus data"); - } + ALOGD_IF(DEBUG_STYLUS_FUSION, + "Timeout expired, synthesizing event with new stylus data"); const nsecs_t readTime = when; // consider this synthetic event to be zero latency cookAndDispatch(when, readTime); } else if (mExternalStylusFusionTimeout == LLONG_MAX) { @@ -1773,24 +1771,18 @@ bool TouchInputMapper::assignExternalStylusId(const RawState& state, bool timeou state.rawPointerData.pointerCount != 0; if (initialDown) { if (mExternalStylusState.pressure != 0.0f) { - if (DEBUG_STYLUS_FUSION) { - ALOGD("Have both stylus and touch data, beginning fusion"); - } + ALOGD_IF(DEBUG_STYLUS_FUSION, "Have both stylus and touch data, beginning fusion"); mExternalStylusId = state.rawPointerData.touchingIdBits.firstMarkedBit(); } else if (timeout) { - if (DEBUG_STYLUS_FUSION) { - ALOGD("Timeout expired, assuming touch is not a stylus."); - } + ALOGD_IF(DEBUG_STYLUS_FUSION, "Timeout expired, assuming touch is not a stylus."); resetExternalStylus(); } else { if (mExternalStylusFusionTimeout == LLONG_MAX) { mExternalStylusFusionTimeout = state.when + EXTERNAL_STYLUS_DATA_TIMEOUT; } - if (DEBUG_STYLUS_FUSION) { - ALOGD("No stylus data but stylus is connected, requesting timeout " - "(%" PRId64 "ms)", - mExternalStylusFusionTimeout); - } + ALOGD_IF(DEBUG_STYLUS_FUSION, + "No stylus data but stylus is connected, requesting timeout (%" PRId64 "ms)", + mExternalStylusFusionTimeout); getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout); return true; } @@ -1798,9 +1790,7 @@ bool TouchInputMapper::assignExternalStylusId(const RawState& state, bool timeou // Check if the stylus pointer has gone up. if (mExternalStylusId != -1 && !state.rawPointerData.touchingIdBits.hasBit(mExternalStylusId)) { - if (DEBUG_STYLUS_FUSION) { - ALOGD("Stylus pointer is going up"); - } + ALOGD_IF(DEBUG_STYLUS_FUSION, "Stylus pointer is going up"); mExternalStylusId = -1; } @@ -1841,10 +1831,9 @@ bool TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_ // Pointer went up while virtual key was down. mCurrentVirtualKey.down = false; if (!mCurrentVirtualKey.ignored) { - if (DEBUG_VIRTUAL_KEYS) { - ALOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d", - mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); - } + ALOGD_IF(DEBUG_VIRTUAL_KEYS, + "VirtualKeys: Generating key up: keyCode=%d, scanCode=%d", + mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY); } @@ -1868,10 +1857,8 @@ bool TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_ // into the main display surface. mCurrentVirtualKey.down = false; if (!mCurrentVirtualKey.ignored) { - if (DEBUG_VIRTUAL_KEYS) { - ALOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d", - mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); - } + ALOGD_IF(DEBUG_VIRTUAL_KEYS, "VirtualKeys: Canceling key: keyCode=%d, scanCode=%d", + mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY | AKEY_EVENT_FLAG_CANCELED); @@ -1901,10 +1888,9 @@ bool TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_ virtualKey->scanCode); if (!mCurrentVirtualKey.ignored) { - if (DEBUG_VIRTUAL_KEYS) { - ALOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d", - mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); - } + ALOGD_IF(DEBUG_VIRTUAL_KEYS, + "VirtualKeys: Generating key down: keyCode=%d, scanCode=%d", + mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_DOWN, AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY); @@ -2696,9 +2682,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi // Handle TAP timeout. if (isTimeout) { - if (DEBUG_GESTURES) { - ALOGD("Gestures: Processing timeout"); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: Processing timeout"); if (mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP) { if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) { @@ -2707,9 +2691,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mConfig.pointerGestureTapDragInterval); } else { // The tap is finished. - if (DEBUG_GESTURES) { - ALOGD("Gestures: TAP finished"); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: TAP finished"); *outFinishPreviousGesture = true; mPointerGesture.activeGestureId = -1; @@ -2805,11 +2787,9 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi // Switch states based on button and pointer state. if (isQuietTime) { // Case 1: Quiet time. (QUIET) - if (DEBUG_GESTURES) { - ALOGD("Gestures: QUIET for next %0.3fms", - (mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval - when) * - 0.000001f); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: QUIET for next %0.3fms", + (mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval - when) * + 0.000001f); if (mPointerGesture.lastGestureMode != PointerGesture::Mode::QUIET) { *outFinishPreviousGesture = true; } @@ -2833,11 +2813,9 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi // active. If the user first puts one finger down to click then adds another // finger to drag then the active pointer should switch to the finger that is // being dragged. - if (DEBUG_GESTURES) { - ALOGD("Gestures: BUTTON_CLICK_OR_DRAG activeTouchId=%d, " - "currentFingerCount=%d", - activeTouchId, currentFingerCount); - } + ALOGD_IF(DEBUG_GESTURES, + "Gestures: BUTTON_CLICK_OR_DRAG activeTouchId=%d, currentFingerCount=%d", + activeTouchId, currentFingerCount); // Reset state when just starting. if (mPointerGesture.lastGestureMode != PointerGesture::Mode::BUTTON_CLICK_OR_DRAG) { *outFinishPreviousGesture = true; @@ -2862,11 +2840,10 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi } if (bestId >= 0 && bestId != activeTouchId) { mPointerGesture.activeTouchId = activeTouchId = bestId; - if (DEBUG_GESTURES) { - ALOGD("Gestures: BUTTON_CLICK_OR_DRAG switched pointers, " - "bestId=%d, bestSpeed=%0.3f", - bestId, bestSpeed); - } + ALOGD_IF(DEBUG_GESTURES, + "Gestures: BUTTON_CLICK_OR_DRAG switched pointers, bestId=%d, " + "bestSpeed=%0.3f", + bestId, bestSpeed); } } @@ -2921,9 +2898,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerController->getPosition(&x, &y); if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop && fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) { - if (DEBUG_GESTURES) { - ALOGD("Gestures: TAP"); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: TAP"); mPointerGesture.tapUpTime = when; getContext()->requestTimeoutAtTime(when + @@ -2949,10 +2924,8 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi tapped = true; } else { - if (DEBUG_GESTURES) { - ALOGD("Gestures: Not a TAP, deltaX=%f, deltaY=%f", x - mPointerGesture.tapX, - y - mPointerGesture.tapY); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: Not a TAP, deltaX=%f, deltaY=%f", + x - mPointerGesture.tapX, y - mPointerGesture.tapY); } } else { if (DEBUG_GESTURES) { @@ -2969,9 +2942,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerVelocityControl.reset(); if (!tapped) { - if (DEBUG_GESTURES) { - ALOGD("Gestures: NEUTRAL"); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: NEUTRAL"); mPointerGesture.activeGestureId = -1; mPointerGesture.currentGestureMode = PointerGesture::Mode::NEUTRAL; mPointerGesture.currentGestureIdBits.clear(); @@ -2992,16 +2963,12 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) { mPointerGesture.currentGestureMode = PointerGesture::Mode::TAP_DRAG; } else { - if (DEBUG_GESTURES) { - ALOGD("Gestures: Not a TAP_DRAG, deltaX=%f, deltaY=%f", - x - mPointerGesture.tapX, y - mPointerGesture.tapY); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: Not a TAP_DRAG, deltaX=%f, deltaY=%f", + x - mPointerGesture.tapX, y - mPointerGesture.tapY); } } else { - if (DEBUG_GESTURES) { - ALOGD("Gestures: Not a TAP_DRAG, %0.3fms time since up", - (when - mPointerGesture.tapUpTime) * 0.000001f); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: Not a TAP_DRAG, %0.3fms time since up", + (when - mPointerGesture.tapUpTime) * 0.000001f); } } else if (mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP_DRAG) { mPointerGesture.currentGestureMode = PointerGesture::Mode::TAP_DRAG; @@ -3028,14 +2995,10 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi bool down; if (mPointerGesture.currentGestureMode == PointerGesture::Mode::TAP_DRAG) { - if (DEBUG_GESTURES) { - ALOGD("Gestures: TAP_DRAG"); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: TAP_DRAG"); down = true; } else { - if (DEBUG_GESTURES) { - ALOGD("Gestures: HOVER"); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: HOVER"); if (mPointerGesture.lastGestureMode != PointerGesture::Mode::HOVER) { *outFinishPreviousGesture = true; } @@ -3089,13 +3052,12 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi } else if (!settled && currentFingerCount > lastFingerCount) { // Additional pointers have gone down but not yet settled. // Reset the gesture. - if (DEBUG_GESTURES) { - ALOGD("Gestures: Resetting gesture since additional pointers went down for " - "MULTITOUCH, settle time remaining %0.3fms", - (mPointerGesture.firstTouchTime + - mConfig.pointerGestureMultitouchSettleInterval - when) * - 0.000001f); - } + ALOGD_IF(DEBUG_GESTURES, + "Gestures: Resetting gesture since additional pointers went down for " + "MULTITOUCH, settle time remaining %0.3fms", + (mPointerGesture.firstTouchTime + + mConfig.pointerGestureMultitouchSettleInterval - when) * + 0.000001f); *outCancelPreviousGesture = true; } else { // Continue previous gesture. @@ -3109,13 +3071,12 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerVelocityControl.reset(); // Use the centroid and pointer location as the reference points for the gesture. - if (DEBUG_GESTURES) { - ALOGD("Gestures: Using centroid as reference for MULTITOUCH, " - "settle time remaining %0.3fms", - (mPointerGesture.firstTouchTime + - mConfig.pointerGestureMultitouchSettleInterval - when) * - 0.000001f); - } + ALOGD_IF(DEBUG_GESTURES, + "Gestures: Using centroid as reference for MULTITOUCH, settle time remaining " + "%0.3fms", + (mPointerGesture.firstTouchTime + + mConfig.pointerGestureMultitouchSettleInterval - when) * + 0.000001f); mCurrentRawState.rawPointerData .getCentroidOfTouchingPointers(&mPointerGesture.referenceTouchX, &mPointerGesture.referenceTouchY); @@ -3173,10 +3134,9 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi if (distOverThreshold >= 2) { if (currentFingerCount > 2) { // There are more than two pointers, switch to FREEFORM. - if (DEBUG_GESTURES) { - ALOGD("Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2", - currentFingerCount); - } + ALOGD_IF(DEBUG_GESTURES, + "Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2", + currentFingerCount); *outCancelPreviousGesture = true; mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM; } else { @@ -3192,11 +3152,9 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi if (mutualDistance > mPointerGestureMaxSwipeWidth) { // There are two pointers but they are too far apart for a SWIPE, // switch to FREEFORM. - if (DEBUG_GESTURES) { - ALOGD("Gestures: PRESS transitioned to FREEFORM, distance %0.3f > " - "%0.3f", - mutualDistance, mPointerGestureMaxSwipeWidth); - } + ALOGD_IF(DEBUG_GESTURES, + "Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f", + mutualDistance, mPointerGestureMaxSwipeWidth); *outCancelPreviousGesture = true; mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM; } else { @@ -3221,25 +3179,23 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi float cosine = dot / (dist1 * dist2); // denominator always > 0 if (cosine >= mConfig.pointerGestureSwipeTransitionAngleCosine) { // Pointers are moving in the same direction. Switch to SWIPE. - if (DEBUG_GESTURES) { - ALOGD("Gestures: PRESS transitioned to SWIPE, " - "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, " - "cosine %0.3f >= %0.3f", - dist1, mConfig.pointerGestureMultitouchMinDistance, dist2, - mConfig.pointerGestureMultitouchMinDistance, cosine, - mConfig.pointerGestureSwipeTransitionAngleCosine); - } + ALOGD_IF(DEBUG_GESTURES, + "Gestures: PRESS transitioned to SWIPE, " + "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, " + "cosine %0.3f >= %0.3f", + dist1, mConfig.pointerGestureMultitouchMinDistance, dist2, + mConfig.pointerGestureMultitouchMinDistance, cosine, + mConfig.pointerGestureSwipeTransitionAngleCosine); mPointerGesture.currentGestureMode = PointerGesture::Mode::SWIPE; } else { // Pointers are moving in different directions. Switch to FREEFORM. - if (DEBUG_GESTURES) { - ALOGD("Gestures: PRESS transitioned to FREEFORM, " - "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, " - "cosine %0.3f < %0.3f", - dist1, mConfig.pointerGestureMultitouchMinDistance, dist2, - mConfig.pointerGestureMultitouchMinDistance, cosine, - mConfig.pointerGestureSwipeTransitionAngleCosine); - } + ALOGD_IF(DEBUG_GESTURES, + "Gestures: PRESS transitioned to FREEFORM, " + "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, " + "cosine %0.3f < %0.3f", + dist1, mConfig.pointerGestureMultitouchMinDistance, dist2, + mConfig.pointerGestureMultitouchMinDistance, cosine, + mConfig.pointerGestureSwipeTransitionAngleCosine); *outCancelPreviousGesture = true; mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM; } @@ -3251,10 +3207,9 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi // Switch from SWIPE to FREEFORM if additional pointers go down. // Cancel previous gesture. if (currentFingerCount > 2) { - if (DEBUG_GESTURES) { - ALOGD("Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2", - currentFingerCount); - } + ALOGD_IF(DEBUG_GESTURES, + "Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2", + currentFingerCount); *outCancelPreviousGesture = true; mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM; } @@ -3288,11 +3243,10 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS || mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) { // PRESS or SWIPE mode. - if (DEBUG_GESTURES) { - ALOGD("Gestures: PRESS or SWIPE activeTouchId=%d," - "activeGestureId=%d, currentTouchPointerCount=%d", - activeTouchId, mPointerGesture.activeGestureId, currentFingerCount); - } + ALOGD_IF(DEBUG_GESTURES, + "Gestures: PRESS or SWIPE activeTouchId=%d, activeGestureId=%d, " + "currentTouchPointerCount=%d", + activeTouchId, mPointerGesture.activeGestureId, currentFingerCount); ALOG_ASSERT(mPointerGesture.activeGestureId >= 0); mPointerGesture.currentGestureIdBits.clear(); @@ -3309,11 +3263,10 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) { // FREEFORM mode. - if (DEBUG_GESTURES) { - ALOGD("Gestures: FREEFORM activeTouchId=%d," - "activeGestureId=%d, currentTouchPointerCount=%d", - activeTouchId, mPointerGesture.activeGestureId, currentFingerCount); - } + ALOGD_IF(DEBUG_GESTURES, + "Gestures: FREEFORM activeTouchId=%d, activeGestureId=%d, " + "currentTouchPointerCount=%d", + activeTouchId, mPointerGesture.activeGestureId, currentFingerCount); ALOG_ASSERT(mPointerGesture.activeGestureId >= 0); mPointerGesture.currentGestureIdBits.clear(); @@ -3352,13 +3305,11 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi } } - if (DEBUG_GESTURES) { - ALOGD("Gestures: FREEFORM follow up " - "mappedTouchIdBits=0x%08x, usedGestureIdBits=0x%08x, " - "activeGestureId=%d", - mappedTouchIdBits.value, usedGestureIdBits.value, - mPointerGesture.activeGestureId); - } + ALOGD_IF(DEBUG_GESTURES, + "Gestures: FREEFORM follow up mappedTouchIdBits=0x%08x, " + "usedGestureIdBits=0x%08x, activeGestureId=%d", + mappedTouchIdBits.value, usedGestureIdBits.value, + mPointerGesture.activeGestureId); BitSet32 idBits(mCurrentCookedState.fingerIdBits); for (uint32_t i = 0; i < currentFingerCount; i++) { @@ -3367,18 +3318,14 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi if (!mappedTouchIdBits.hasBit(touchId)) { gestureId = usedGestureIdBits.markFirstUnmarkedBit(); mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId; - if (DEBUG_GESTURES) { - ALOGD("Gestures: FREEFORM " - "new mapping for touch id %d -> gesture id %d", - touchId, gestureId); - } + ALOGD_IF(DEBUG_GESTURES, + "Gestures: FREEFORM new mapping for touch id %d -> gesture id %d", + touchId, gestureId); } else { gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId]; - if (DEBUG_GESTURES) { - ALOGD("Gestures: FREEFORM " - "existing mapping for touch id %d -> gesture id %d", - touchId, gestureId); - } + ALOGD_IF(DEBUG_GESTURES, + "Gestures: FREEFORM existing mapping for touch id %d -> gesture id %d", + touchId, gestureId); } mPointerGesture.currentGestureIdBits.markBit(gestureId); mPointerGesture.currentGestureIdToIndex[gestureId] = i; @@ -3407,10 +3354,8 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi if (mPointerGesture.activeGestureId < 0) { mPointerGesture.activeGestureId = mPointerGesture.currentGestureIdBits.firstMarkedBit(); - if (DEBUG_GESTURES) { - ALOGD("Gestures: FREEFORM new activeGestureId=%d", - mPointerGesture.activeGestureId); - } + ALOGD_IF(DEBUG_GESTURES, "Gestures: FREEFORM new activeGestureId=%d", + mPointerGesture.activeGestureId); } } } @@ -3820,12 +3765,11 @@ bool TouchInputMapper::isPointInsidePhysicalFrame(int32_t x, int32_t y) const { const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHit(int32_t x, int32_t y) { for (const VirtualKey& virtualKey : mVirtualKeys) { - if (DEBUG_VIRTUAL_KEYS) { - ALOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, " - "left=%d, top=%d, right=%d, bottom=%d", - x, y, virtualKey.keyCode, virtualKey.scanCode, virtualKey.hitLeft, - virtualKey.hitTop, virtualKey.hitRight, virtualKey.hitBottom); - } + ALOGD_IF(DEBUG_VIRTUAL_KEYS, + "VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, " + "left=%d, top=%d, right=%d, bottom=%d", + x, y, virtualKey.keyCode, virtualKey.scanCode, virtualKey.hitLeft, + virtualKey.hitTop, virtualKey.hitRight, virtualKey.hitBottom); if (virtualKey.isHit(x, y)) { return &virtualKey; @@ -3996,11 +3940,10 @@ void TouchInputMapper::assignPointerIds(const RawState& last, RawState& current) currentPointerIndex)); usedIdBits.markBit(id); - if (DEBUG_POINTER_ASSIGNMENT) { - ALOGD("assignPointerIds - matched: cur=%" PRIu32 ", last=%" PRIu32 ", id=%" PRIu32 - ", distance=%" PRIu64, - lastPointerIndex, currentPointerIndex, id, heap[0].distance); - } + ALOGD_IF(DEBUG_POINTER_ASSIGNMENT, + "assignPointerIds - matched: cur=%" PRIu32 ", last=%" PRIu32 ", id=%" PRIu32 + ", distance=%" PRIu64, + lastPointerIndex, currentPointerIndex, id, heap[0].distance); break; } } @@ -4015,10 +3958,9 @@ void TouchInputMapper::assignPointerIds(const RawState& last, RawState& current) current.rawPointerData.markIdBit(id, current.rawPointerData.isHovering(currentPointerIndex)); - if (DEBUG_POINTER_ASSIGNMENT) { - ALOGD("assignPointerIds - assigned: cur=%" PRIu32 ", id=%" PRIu32, currentPointerIndex, - id); - } + ALOGD_IF(DEBUG_POINTER_ASSIGNMENT, + "assignPointerIds - assigned: cur=%" PRIu32 ", id=%" PRIu32, currentPointerIndex, + id); } } -- GitLab From 2e9748ff11588f10cdf011bcc4709e575c50508d Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Tue, 9 Aug 2022 22:48:18 +0000 Subject: [PATCH 0232/1310] Replace RenderEngineResult with FenceResult Bug: b/232535621 Test: atest librenderengine_test Test: atest SurfaceFlinger_test Change-Id: If036ab847024751b8da97d6ed856db447db19f6f --- libs/renderengine/RenderEngine.cpp | 13 +-- .../benchmark/RenderEngineBench.cpp | 15 ++-- libs/renderengine/gl/GLESRenderEngine.cpp | 21 +++-- libs/renderengine/gl/GLESRenderEngine.h | 3 +- .../include/renderengine/RenderEngine.h | 25 +++--- .../include/renderengine/mock/RenderEngine.h | 13 ++- libs/renderengine/skia/SkiaRenderEngine.cpp | 9 +- libs/renderengine/skia/SkiaRenderEngine.h | 2 +- libs/renderengine/tests/RenderEngineTest.cpp | 62 +++++++------ .../tests/RenderEngineThreadedTest.cpp | 15 ++-- .../threaded/RenderEngineThreaded.cpp | 10 +-- .../threaded/RenderEngineThreaded.h | 12 +-- .../ui/include/ui}/FenceResult.h | 16 ---- .../include/compositionengine/LayerFE.h | 3 +- .../CompositionEngine/src/Output.cpp | 9 +- .../src/planner/CachedSet.cpp | 9 +- .../CompositionEngine/tests/OutputTest.cpp | 62 +++++-------- .../CompositionEngine/tests/TestUtils.h | 31 ------- .../tests/planner/CachedSetTest.cpp | 90 +++++++++---------- .../tests/planner/FlattenerTest.cpp | 65 +++++--------- services/surfaceflinger/SurfaceFlinger.cpp | 10 +-- services/surfaceflinger/SurfaceFlinger.h | 2 +- .../TransactionCallbackInvoker.h | 2 +- .../tests/unittests/CompositionTest.cpp | 30 +++---- 24 files changed, 204 insertions(+), 325 deletions(-) rename {services/surfaceflinger/CompositionEngine/include/compositionengine => libs/ui/include/ui}/FenceResult.h (60%) delete mode 100644 services/surfaceflinger/CompositionEngine/tests/TestUtils.h diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp index c7ad058ab9..9d9cb6b2bc 100644 --- a/libs/renderengine/RenderEngine.cpp +++ b/libs/renderengine/RenderEngine.cpp @@ -63,12 +63,13 @@ void RenderEngine::validateOutputBufferUsage(const sp& buffer) { "output buffer not gpu writeable"); } -std::future RenderEngine::drawLayers( - const DisplaySettings& display, const std::vector& layers, - const std::shared_ptr& buffer, const bool useFramebufferCache, - base::unique_fd&& bufferFence) { - const auto resultPromise = std::make_shared>(); - std::future resultFuture = resultPromise->get_future(); +ftl::Future RenderEngine::drawLayers(const DisplaySettings& display, + const std::vector& layers, + const std::shared_ptr& buffer, + const bool useFramebufferCache, + base::unique_fd&& bufferFence) { + const auto resultPromise = std::make_shared>(); + std::future resultFuture = resultPromise->get_future(); drawLayersInternal(std::move(resultPromise), display, layers, buffer, useFramebufferCache, std::move(bufferFence)); return resultFuture; diff --git a/libs/renderengine/benchmark/RenderEngineBench.cpp b/libs/renderengine/benchmark/RenderEngineBench.cpp index 7bcfff5348..739f3fa327 100644 --- a/libs/renderengine/benchmark/RenderEngineBench.cpp +++ b/libs/renderengine/benchmark/RenderEngineBench.cpp @@ -159,9 +159,10 @@ static std::shared_ptr copyBuffer(RenderEngine& re, }; auto layers = std::vector{layer}; - auto [status, drawFence] = - re.drawLayers(display, layers, texture, kUseFrameBufferCache, base::unique_fd()).get(); - sp waitFence = sp::make(std::move(drawFence)); + sp waitFence = + re.drawLayers(display, layers, texture, kUseFrameBufferCache, base::unique_fd()) + .get() + .value(); waitFence->waitForever(LOG_TAG); return texture; } @@ -190,10 +191,10 @@ static void benchDrawLayers(RenderEngine& re, const std::vector& // This loop starts and stops the timer. for (auto _ : benchState) { - auto [status, drawFence] = re.drawLayers(display, layers, outputBuffer, - kUseFrameBufferCache, base::unique_fd()) - .get(); - sp waitFence = sp::make(std::move(drawFence)); + sp waitFence = re.drawLayers(display, layers, outputBuffer, kUseFrameBufferCache, + base::unique_fd()) + .get() + .value(); waitFence->waitForever(LOG_TAG); } diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp index 9a5ff54ac7..13f766c3e1 100644 --- a/libs/renderengine/gl/GLESRenderEngine.cpp +++ b/libs/renderengine/gl/GLESRenderEngine.cpp @@ -1081,14 +1081,14 @@ EGLImageKHR GLESRenderEngine::createFramebufferImageIfNeeded(ANativeWindowBuffer } void GLESRenderEngine::drawLayersInternal( - const std::shared_ptr>&& resultPromise, + const std::shared_ptr>&& resultPromise, const DisplaySettings& display, const std::vector& layers, const std::shared_ptr& buffer, const bool useFramebufferCache, base::unique_fd&& bufferFence) { ATRACE_CALL(); if (layers.empty()) { ALOGV("Drawing empty layer stack"); - resultPromise->set_value({NO_ERROR, base::unique_fd()}); + resultPromise->set_value(Fence::NO_FENCE); return; } @@ -1103,7 +1103,7 @@ void GLESRenderEngine::drawLayersInternal( if (buffer == nullptr) { ALOGE("No output buffer provided. Aborting GPU composition."); - resultPromise->set_value({BAD_VALUE, base::unique_fd()}); + resultPromise->set_value(base::unexpected(BAD_VALUE)); return; } @@ -1132,7 +1132,7 @@ void GLESRenderEngine::drawLayersInternal( ALOGE("Failed to bind framebuffer! Aborting GPU composition for buffer (%p).", buffer->getBuffer()->handle); checkErrors(); - resultPromise->set_value({fbo->getStatus(), base::unique_fd()}); + resultPromise->set_value(base::unexpected(fbo->getStatus())); return; } setViewportAndProjection(display.physicalDisplay, display.clip); @@ -1144,7 +1144,7 @@ void GLESRenderEngine::drawLayersInternal( ALOGE("Failed to prepare blur filter! Aborting GPU composition for buffer (%p).", buffer->getBuffer()->handle); checkErrors(); - resultPromise->set_value({status, base::unique_fd()}); + resultPromise->set_value(base::unexpected(status)); return; } } @@ -1178,7 +1178,7 @@ void GLESRenderEngine::drawLayersInternal( ALOGE("Failed to render blur effect! Aborting GPU composition for buffer (%p).", buffer->getBuffer()->handle); checkErrors("Can't render first blur pass"); - resultPromise->set_value({status, base::unique_fd()}); + resultPromise->set_value(base::unexpected(status)); return; } @@ -1201,7 +1201,7 @@ void GLESRenderEngine::drawLayersInternal( ALOGE("Failed to bind framebuffer! Aborting GPU composition for buffer (%p).", buffer->getBuffer()->handle); checkErrors("Can't bind native framebuffer"); - resultPromise->set_value({status, base::unique_fd()}); + resultPromise->set_value(base::unexpected(status)); return; } @@ -1210,7 +1210,7 @@ void GLESRenderEngine::drawLayersInternal( ALOGE("Failed to render blur effect! Aborting GPU composition for buffer (%p).", buffer->getBuffer()->handle); checkErrors("Can't render blur filter"); - resultPromise->set_value({status, base::unique_fd()}); + resultPromise->set_value(base::unexpected(status)); return; } } @@ -1310,7 +1310,7 @@ void GLESRenderEngine::drawLayersInternal( checkErrors(); // Chances are, something illegal happened (either the caller passed // us bad parameters, or we messed up our shader generation). - resultPromise->set_value({INVALID_OPERATION, std::move(drawFence)}); + resultPromise->set_value(base::unexpected(INVALID_OPERATION)); return; } mLastDrawFence = nullptr; @@ -1322,8 +1322,7 @@ void GLESRenderEngine::drawLayersInternal( mPriorResourcesCleaned = false; checkErrors(); - resultPromise->set_value({NO_ERROR, std::move(drawFence)}); - return; + resultPromise->set_value(sp::make(std::move(drawFence))); } void GLESRenderEngine::setViewportAndProjection(Rect viewport, Rect clip) { diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h index 1d7c2cafb5..1ee5cbaa3d 100644 --- a/libs/renderengine/gl/GLESRenderEngine.h +++ b/libs/renderengine/gl/GLESRenderEngine.h @@ -31,6 +31,7 @@ #include #include #include +#include #include "GLShadowTexture.h" #include "ImageManager.h" @@ -102,7 +103,7 @@ protected: EXCLUDES(mRenderingMutex); void unmapExternalTextureBuffer(const sp& buffer) EXCLUDES(mRenderingMutex); bool canSkipPostRenderCleanup() const override; - void drawLayersInternal(const std::shared_ptr>&& resultPromise, + void drawLayersInternal(const std::shared_ptr>&& resultPromise, const DisplaySettings& display, const std::vector& layers, const std::shared_ptr& buffer, diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h index 3e7f69ce02..199392c160 100644 --- a/libs/renderengine/include/renderengine/RenderEngine.h +++ b/libs/renderengine/include/renderengine/RenderEngine.h @@ -18,6 +18,7 @@ #define SF_RENDERENGINE_H_ #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include #include #include @@ -68,7 +70,6 @@ class Image; class Mesh; class Texture; struct RenderEngineCreationArgs; -struct RenderEngineResult; namespace threaded { class RenderEngineThreaded; @@ -158,12 +159,13 @@ public: // parameter does nothing. // @param bufferFence Fence signalling that the buffer is ready to be drawn // to. - // @return A future object of RenderEngineResult struct indicating whether - // drawing was successful in async mode. - virtual std::future drawLayers( - const DisplaySettings& display, const std::vector& layers, - const std::shared_ptr& buffer, const bool useFramebufferCache, - base::unique_fd&& bufferFence); + // @return A future object of FenceResult indicating whether drawing was + // successful in async mode. + virtual ftl::Future drawLayers(const DisplaySettings& display, + const std::vector& layers, + const std::shared_ptr& buffer, + const bool useFramebufferCache, + base::unique_fd&& bufferFence); // Clean-up method that should be called on the main thread after the // drawFence returned by drawLayers fires. This method will free up @@ -237,7 +239,7 @@ protected: const RenderEngineType mRenderEngineType; virtual void drawLayersInternal( - const std::shared_ptr>&& resultPromise, + const std::shared_ptr>&& resultPromise, const DisplaySettings& display, const std::vector& layers, const std::shared_ptr& buffer, const bool useFramebufferCache, base::unique_fd&& bufferFence) = 0; @@ -327,13 +329,6 @@ private: RenderEngine::RenderEngineType::SKIA_GL_THREADED; }; -struct RenderEngineResult { - // status indicates if drawing is successful - status_t status; - // drawFence will fire when the buffer has been drawn to and is ready to be examined. - base::unique_fd drawFence; -}; - } // namespace renderengine } // namespace android diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h index 248bd652c0..e3ce85dd07 100644 --- a/libs/renderengine/include/renderengine/mock/RenderEngine.h +++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h @@ -48,14 +48,13 @@ public: MOCK_METHOD0(cleanupPostRender, void()); MOCK_CONST_METHOD0(canSkipPostRenderCleanup, bool()); MOCK_METHOD5(drawLayers, - std::future(const DisplaySettings&, - const std::vector&, - const std::shared_ptr&, - const bool, base::unique_fd&&)); + ftl::Future(const DisplaySettings&, const std::vector&, + const std::shared_ptr&, const bool, + base::unique_fd&&)); MOCK_METHOD6(drawLayersInternal, - void(const std::shared_ptr>&&, - const DisplaySettings&, const std::vector&, - const std::shared_ptr&, const bool, base::unique_fd&&)); + void(const std::shared_ptr>&&, const DisplaySettings&, + const std::vector&, const std::shared_ptr&, + const bool, base::unique_fd&&)); MOCK_METHOD0(cleanFramebufferCache, void()); MOCK_METHOD0(getContextPriority, int()); MOCK_METHOD0(supportsBackgroundBlur, bool()); diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index db983a81d4..b9aa5acd30 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -632,7 +632,7 @@ private: }; void SkiaRenderEngine::drawLayersInternal( - const std::shared_ptr>&& resultPromise, + const std::shared_ptr>&& resultPromise, const DisplaySettings& display, const std::vector& layers, const std::shared_ptr& buffer, const bool /*useFramebufferCache*/, base::unique_fd&& bufferFence) { @@ -642,7 +642,7 @@ void SkiaRenderEngine::drawLayersInternal( if (buffer == nullptr) { ALOGE("No output buffer provided. Aborting GPU composition."); - resultPromise->set_value({BAD_VALUE, base::unique_fd()}); + resultPromise->set_value(base::unexpected(BAD_VALUE)); return; } @@ -675,7 +675,7 @@ void SkiaRenderEngine::drawLayersInternal( SkCanvas* dstCanvas = mCapture->tryCapture(dstSurface.get()); if (dstCanvas == nullptr) { ALOGE("Cannot acquire canvas from Skia."); - resultPromise->set_value({BAD_VALUE, base::unique_fd()}); + resultPromise->set_value(base::unexpected(BAD_VALUE)); return; } @@ -1126,8 +1126,7 @@ void SkiaRenderEngine::drawLayersInternal( } base::unique_fd drawFence = flushAndSubmit(grContext); - resultPromise->set_value({NO_ERROR, std::move(drawFence)}); - return; + resultPromise->set_value(sp::make(std::move(drawFence))); } size_t SkiaRenderEngine::getMaxTextureSize() const { diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h index a5cd278d4f..e7c5b8f0ab 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.h +++ b/libs/renderengine/skia/SkiaRenderEngine.h @@ -135,7 +135,7 @@ private: void initCanvas(SkCanvas* canvas, const DisplaySettings& display); void drawShadow(SkCanvas* canvas, const SkRRect& casterRRect, const ShadowSettings& shadowSettings); - void drawLayersInternal(const std::shared_ptr>&& resultPromise, + void drawLayersInternal(const std::shared_ptr>&& resultPromise, const DisplaySettings& display, const std::vector& layers, const std::shared_ptr& buffer, diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index d23063c84d..9d8b2df98a 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -528,16 +528,15 @@ public: void invokeDraw(const renderengine::DisplaySettings& settings, const std::vector& layers) { - std::future result = + ftl::Future future = mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd()); + ASSERT_TRUE(future.valid()); - ASSERT_TRUE(result.valid()); - auto [status, fence] = result.get(); + auto result = future.get(); + ASSERT_TRUE(result.ok()); - ASSERT_EQ(NO_ERROR, status); - if (fence.ok()) { - sync_wait(fence.get(), -1); - } + auto fence = result.value(); + fence->waitForever(LOG_TAG); if (layers.size() > 0 && mGLESRE != nullptr) { ASSERT_TRUE(mGLESRE->isFramebufferImageCachedForTesting(mBuffer->getBuffer()->getId())); @@ -1681,13 +1680,13 @@ TEST_P(RenderEngineTest, drawLayers_nullOutputBuffer) { layer.geometry.boundaries = fullscreenRect().toFloatRect(); BufferSourceVariant::fillColor(layer, 1.0f, 0.0f, 0.0f, this); layers.push_back(layer); - std::future result = + ftl::Future future = mRE->drawLayers(settings, layers, nullptr, true, base::unique_fd()); - ASSERT_TRUE(result.valid()); - auto [status, fence] = result.get(); - ASSERT_EQ(BAD_VALUE, status); - ASSERT_FALSE(fence.ok()); + ASSERT_TRUE(future.valid()); + auto result = future.get(); + ASSERT_FALSE(result.ok()); + ASSERT_EQ(BAD_VALUE, result.error()); } TEST_P(RenderEngineTest, drawLayers_doesNotCacheFramebuffer) { @@ -1712,15 +1711,14 @@ TEST_P(RenderEngineTest, drawLayers_doesNotCacheFramebuffer) { layer.alpha = 1.0; layers.push_back(layer); - std::future result = + ftl::Future future = mRE->drawLayers(settings, layers, mBuffer, false, base::unique_fd()); - ASSERT_TRUE(result.valid()); - auto [status, fence] = result.get(); + ASSERT_TRUE(future.valid()); + auto result = future.get(); - ASSERT_EQ(NO_ERROR, status); - if (fence.ok()) { - sync_wait(fence.get(), -1); - } + ASSERT_TRUE(result.ok()); + auto fence = result.value(); + fence->waitForever(LOG_TAG); ASSERT_FALSE(mGLESRE->isFramebufferImageCachedForTesting(mBuffer->getBuffer()->getId())); expectBufferColor(fullscreenRect(), 255, 0, 0, 255); @@ -2219,20 +2217,20 @@ TEST_P(RenderEngineTest, cleanupPostRender_cleansUpOnce) { layer.alpha = 1.0; layers.push_back(layer); - std::future resultOne = + ftl::Future futureOne = mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd()); - ASSERT_TRUE(resultOne.valid()); - auto [statusOne, fenceOne] = resultOne.get(); - ASSERT_EQ(NO_ERROR, statusOne); - - std::future resultTwo = - mRE->drawLayers(settings, layers, mBuffer, true, std::move(fenceOne)); - ASSERT_TRUE(resultTwo.valid()); - auto [statusTwo, fenceTwo] = resultTwo.get(); - ASSERT_EQ(NO_ERROR, statusTwo); - if (fenceTwo.ok()) { - sync_wait(fenceTwo.get(), -1); - } + ASSERT_TRUE(futureOne.valid()); + auto resultOne = futureOne.get(); + ASSERT_TRUE(resultOne.ok()); + auto fenceOne = resultOne.value(); + + ftl::Future futureTwo = + mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd(fenceOne->dup())); + ASSERT_TRUE(futureTwo.valid()); + auto resultTwo = futureTwo.get(); + ASSERT_TRUE(resultTwo.ok()); + auto fenceTwo = resultTwo.value(); + fenceTwo->waitForever(LOG_TAG); // Only cleanup the first time. EXPECT_FALSE(mRE->canSkipPostRenderCleanup()); diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp index 909ded3a78..1a96289bc0 100644 --- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp +++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp @@ -183,20 +183,17 @@ TEST_F(RenderEngineThreadedTest, drawLayers) { base::unique_fd bufferFence; EXPECT_CALL(*mRenderEngine, drawLayersInternal) - .WillOnce([&](const std::shared_ptr>&& - resultPromise, + .WillOnce([&](const std::shared_ptr>&& resultPromise, const renderengine::DisplaySettings&, const std::vector&, const std::shared_ptr&, const bool, - base::unique_fd&&) -> void { - resultPromise->set_value({NO_ERROR, base::unique_fd()}); - }); + base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); }); - std::future result = + ftl::Future future = mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence)); - ASSERT_TRUE(result.valid()); - auto [status, _] = result.get(); - ASSERT_EQ(NO_ERROR, status); + ASSERT_TRUE(future.valid()); + auto result = future.get(); + ASSERT_TRUE(result.ok()); } } // namespace android diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp index 203bb54701..b41e8432a9 100644 --- a/libs/renderengine/threaded/RenderEngineThreaded.cpp +++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp @@ -313,21 +313,21 @@ bool RenderEngineThreaded::canSkipPostRenderCleanup() const { } void RenderEngineThreaded::drawLayersInternal( - const std::shared_ptr>&& resultPromise, + const std::shared_ptr>&& resultPromise, const DisplaySettings& display, const std::vector& layers, const std::shared_ptr& buffer, const bool useFramebufferCache, base::unique_fd&& bufferFence) { - resultPromise->set_value({NO_ERROR, base::unique_fd()}); + resultPromise->set_value(Fence::NO_FENCE); return; } -std::future RenderEngineThreaded::drawLayers( +ftl::Future RenderEngineThreaded::drawLayers( const DisplaySettings& display, const std::vector& layers, const std::shared_ptr& buffer, const bool useFramebufferCache, base::unique_fd&& bufferFence) { ATRACE_CALL(); - const auto resultPromise = std::make_shared>(); - std::future resultFuture = resultPromise->get_future(); + const auto resultPromise = std::make_shared>(); + std::future resultFuture = resultPromise->get_future(); int fd = bufferFence.release(); { std::lock_guard lock(mThreadMutex); diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h index 1340902126..bf2ebea2a0 100644 --- a/libs/renderengine/threaded/RenderEngineThreaded.h +++ b/libs/renderengine/threaded/RenderEngineThreaded.h @@ -56,11 +56,11 @@ public: void useProtectedContext(bool useProtectedContext) override; void cleanupPostRender() override; - std::future drawLayers(const DisplaySettings& display, - const std::vector& layers, - const std::shared_ptr& buffer, - const bool useFramebufferCache, - base::unique_fd&& bufferFence) override; + ftl::Future drawLayers(const DisplaySettings& display, + const std::vector& layers, + const std::shared_ptr& buffer, + const bool useFramebufferCache, + base::unique_fd&& bufferFence) override; void cleanFramebufferCache() override; int getContextPriority() override; @@ -73,7 +73,7 @@ protected: void mapExternalTextureBuffer(const sp& buffer, bool isRenderable) override; void unmapExternalTextureBuffer(const sp& buffer) override; bool canSkipPostRenderCleanup() const override; - void drawLayersInternal(const std::shared_ptr>&& resultPromise, + void drawLayersInternal(const std::shared_ptr>&& resultPromise, const DisplaySettings& display, const std::vector& layers, const std::shared_ptr& buffer, diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/FenceResult.h b/libs/ui/include/ui/FenceResult.h similarity index 60% rename from services/surfaceflinger/CompositionEngine/include/compositionengine/FenceResult.h rename to libs/ui/include/ui/FenceResult.h index 0ce263b930..6d63fc9973 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/FenceResult.h +++ b/libs/ui/include/ui/FenceResult.h @@ -20,30 +20,14 @@ #include #include -// TODO(b/232535621): Pull this file to so that RenderEngine::drawLayers returns -// FenceResult rather than RenderEngineResult. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" -#include -#pragma clang diagnostic pop - namespace android { class Fence; using FenceResult = base::expected, status_t>; -// TODO(b/232535621): Prevent base::unexpected(NO_ERROR) from being a valid FenceResult. inline status_t fenceStatus(const FenceResult& fenceResult) { return fenceResult.ok() ? NO_ERROR : fenceResult.error(); } -inline FenceResult toFenceResult(renderengine::RenderEngineResult&& result) { - if (auto [status, fence] = std::move(result); fence.ok()) { - return sp::make(std::move(fence)); - } else { - return base::unexpected(status); - } -} - } // namespace android diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h index f93fd99f29..9753a6c83c 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h @@ -20,8 +20,6 @@ #include #include -#include - // TODO(b/129481165): remove the #pragma below and fix conversion issues #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wconversion" @@ -33,6 +31,7 @@ #pragma clang diagnostic pop // ignored "-Wconversion -Wextra" #include +#include #include #include diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 06ae9e9219..ea78517fac 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -1330,11 +1330,10 @@ std::optional Output::composeSurfaces( // over to RenderEngine, in which case this flag can be removed from the drawLayers interface. const bool useFramebufferCache = outputState.layerFilter.toInternalDisplay; - auto fenceResult = - toFenceResult(renderEngine - .drawLayers(clientCompositionDisplay, clientRenderEngineLayers, - tex, useFramebufferCache, std::move(fd)) - .get()); + auto fenceResult = renderEngine + .drawLayers(clientCompositionDisplay, clientRenderEngineLayers, tex, + useFramebufferCache, std::move(fd)) + .get(); if (mClientCompositionRequestCache && fenceStatus(fenceResult) != NO_ERROR) { // If rendering was not successful, remove the request from the cache. diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp index 3315767380..0731d4899c 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp @@ -273,11 +273,10 @@ void CachedSet::render(renderengine::RenderEngine& renderEngine, TexturePool& te constexpr bool kUseFramebufferCache = false; - auto fenceResult = - toFenceResult(renderEngine - .drawLayers(displaySettings, layerSettings, texture->get(), - kUseFramebufferCache, std::move(bufferFence)) - .get()); + auto fenceResult = renderEngine + .drawLayers(displaySettings, layerSettings, texture->get(), + kUseFramebufferCache, std::move(bufferFence)) + .get(); if (fenceStatus(fenceResult) == NO_ERROR) { mDrawFence = std::move(fenceResult).value_or(Fence::NO_FENCE); diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index 163a11ccd1..ace28648d7 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -39,7 +39,6 @@ #include "CallOrderStateMachineHelper.h" #include "MockHWC2.h" #include "RegionMatcher.h" -#include "TestUtils.h" namespace android::compositionengine { namespace { @@ -3527,9 +3526,8 @@ TEST_F(OutputComposeSurfacesTest, handlesZeroCompositionRequests) { .WillRepeatedly([&](const renderengine::DisplaySettings&, const std::vector&, const std::shared_ptr&, const bool, - base::unique_fd&&) - -> std::future { - return futureOf({NO_ERROR, base::unique_fd()}); + base::unique_fd&&) -> ftl::Future { + return ftl::yield(Fence::NO_FENCE); }); verify().execute().expectAFenceWasReturned(); } @@ -3559,9 +3557,8 @@ TEST_F(OutputComposeSurfacesTest, buildsAndRendersRequestList) { .WillRepeatedly([&](const renderengine::DisplaySettings&, const std::vector&, const std::shared_ptr&, const bool, - base::unique_fd&&) - -> std::future { - return futureOf({NO_ERROR, base::unique_fd()}); + base::unique_fd&&) -> ftl::Future { + return ftl::yield(Fence::NO_FENCE); }); verify().execute().expectAFenceWasReturned(); @@ -3594,9 +3591,8 @@ TEST_F(OutputComposeSurfacesTest, .WillRepeatedly([&](const renderengine::DisplaySettings&, const std::vector&, const std::shared_ptr&, const bool, - base::unique_fd&&) - -> std::future { - return futureOf({NO_ERROR, base::unique_fd()}); + base::unique_fd&&) -> ftl::Future { + return ftl::yield(Fence::NO_FENCE); }); verify().execute().expectAFenceWasReturned(); @@ -3622,10 +3618,8 @@ TEST_F(OutputComposeSurfacesTest, renderDuplicateClientCompositionRequestsWithou EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer)); EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _)) .Times(2) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))) + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); verify().execute().expectAFenceWasReturned(); EXPECT_FALSE(mOutput.mState.reusedClientComposition); @@ -3653,8 +3647,7 @@ TEST_F(OutputComposeSurfacesTest, skipDuplicateClientCompositionRequests) { EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer)); EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); EXPECT_CALL(mOutput, setExpensiveRenderingExpected(false)); verify().execute().expectAFenceWasReturned(); @@ -3693,9 +3686,8 @@ TEST_F(OutputComposeSurfacesTest, clientCompositionIfBufferChanges) { .WillRepeatedly([&](const renderengine::DisplaySettings&, const std::vector&, const std::shared_ptr&, const bool, - base::unique_fd&&) - -> std::future { - return futureOf({NO_ERROR, base::unique_fd()}); + base::unique_fd&&) -> ftl::Future { + return ftl::yield(Fence::NO_FENCE); }); verify().execute().expectAFenceWasReturned(); @@ -3726,11 +3718,9 @@ TEST_F(OutputComposeSurfacesTest, clientCompositionIfRequestChanges) { EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer)); EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r3), _, false, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); verify().execute().expectAFenceWasReturned(); EXPECT_FALSE(mOutput.mState.reusedClientComposition); @@ -3807,8 +3797,7 @@ struct OutputComposeSurfacesTest_UsesExpectedDisplaySettings : public OutputComp : public CallOrderStateMachineHelper { auto thenExpectDisplaySettingsUsed(renderengine::DisplaySettings settings) { EXPECT_CALL(getInstance()->mRenderEngine, drawLayers(settings, _, _, false, _)) - .WillOnce(Return(ByMove(futureOf( - {NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); return nextState(); } }; @@ -4061,14 +4050,12 @@ struct OutputComposeSurfacesTest_HandlesProtectedContent : public OutputComposeS .WillRepeatedly(Return()); EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer)); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _)) - .WillRepeatedly( - [&](const renderengine::DisplaySettings&, - const std::vector&, - const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { - return futureOf( - {NO_ERROR, base::unique_fd()}); - }); + .WillRepeatedly([&](const renderengine::DisplaySettings&, + const std::vector&, + const std::shared_ptr&, + const bool, base::unique_fd&&) -> ftl::Future { + return ftl::yield(Fence::NO_FENCE); + }); } Layer mLayer1; @@ -4133,8 +4120,7 @@ TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNotEnabled) { // Must happen after setting the protected content state. EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer)); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); base::unique_fd fd; std::shared_ptr tex; @@ -4226,8 +4212,7 @@ TEST_F(OutputComposeSurfacesTest_SetsExpensiveRendering, IfExepensiveOutputDatas EXPECT_CALL(mOutput, setExpensiveRenderingExpected(true)); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); base::unique_fd fd; std::shared_ptr tex; @@ -4250,8 +4235,7 @@ struct OutputComposeSurfacesTest_SetsExpensiveRendering_ForBlur EXPECT_CALL(mOutput, generateClientCompositionRequests(_, kDefaultOutputDataspace, _)) .WillOnce(Return(std::vector{})); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _)) - .WillOnce(Return(ByMove(futureOf( - {NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(1u)); EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0u)) .WillRepeatedly(Return(&mLayer.outputLayer)); diff --git a/services/surfaceflinger/CompositionEngine/tests/TestUtils.h b/services/surfaceflinger/CompositionEngine/tests/TestUtils.h deleted file mode 100644 index c80fde6ead..0000000000 --- a/services/surfaceflinger/CompositionEngine/tests/TestUtils.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2021 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 - -namespace android::compositionengine { -namespace { - -template -std::future futureOf(T obj) { - std::promise resultPromise; - std::future resultFuture = resultPromise.get_future(); - resultPromise.set_value(std::move(obj)); - return resultFuture; -} -} // namespace -} // namespace android::compositionengine diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp index 2b938d0344..d5d688e705 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp @@ -28,8 +28,6 @@ #include #include -#include "tests/TestUtils.h" - namespace android::compositionengine { using namespace std::chrono_literals; @@ -353,11 +351,10 @@ TEST_F(CachedSetTest, renderUnsecureOutput) { clientComp2.emplace(); clientComp2->alpha = 0.75f; - const auto drawLayers = - [&](const renderengine::DisplaySettings& displaySettings, - const std::vector& layers, - const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings, + const std::vector& layers, + const std::shared_ptr&, const bool, + base::unique_fd&&) -> ftl::Future { EXPECT_EQ(mOutputState.framebufferSpace.getContent(), displaySettings.physicalDisplay); EXPECT_EQ(mOutputState.layerStackSpace.getContent(), displaySettings.clip); EXPECT_EQ(ui::Transform::toRotationFlags(mOutputState.framebufferSpace.getOrientation()), @@ -365,7 +362,7 @@ TEST_F(CachedSetTest, renderUnsecureOutput) { EXPECT_EQ(0.5f, layers[0].alpha); EXPECT_EQ(0.75f, layers[1].alpha); EXPECT_EQ(ui::Dataspace::SRGB, displaySettings.outputDataspace); - return futureOf({NO_ERROR, base::unique_fd()}); + return ftl::yield(Fence::NO_FENCE); }; EXPECT_CALL(*layerFE1, prepareClientComposition(ClientCompositionTargetSettingsSecureEq(false))) @@ -403,11 +400,10 @@ TEST_F(CachedSetTest, renderSecureOutput) { clientComp2.emplace(); clientComp2->alpha = 0.75f; - const auto drawLayers = - [&](const renderengine::DisplaySettings& displaySettings, - const std::vector& layers, - const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings, + const std::vector& layers, + const std::shared_ptr&, const bool, + base::unique_fd&&) -> ftl::Future { EXPECT_EQ(mOutputState.framebufferSpace.getContent(), displaySettings.physicalDisplay); EXPECT_EQ(mOutputState.layerStackSpace.getContent(), displaySettings.clip); EXPECT_EQ(ui::Transform::toRotationFlags(mOutputState.framebufferSpace.getOrientation()), @@ -416,7 +412,7 @@ TEST_F(CachedSetTest, renderSecureOutput) { EXPECT_EQ(0.75f, layers[1].alpha); EXPECT_EQ(ui::Dataspace::SRGB, displaySettings.outputDataspace); - return futureOf({NO_ERROR, base::unique_fd()}); + return ftl::yield(Fence::NO_FENCE); }; EXPECT_CALL(*layerFE1, prepareClientComposition(ClientCompositionTargetSettingsSecureEq(true))) @@ -454,13 +450,12 @@ TEST_F(CachedSetTest, renderWhitePoint) { mOutputState.displayBrightnessNits = 400.f; - const auto drawLayers = - [&](const renderengine::DisplaySettings& displaySettings, - const std::vector&, - const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings, + const std::vector&, + const std::shared_ptr&, const bool, + base::unique_fd&&) -> ftl::Future { EXPECT_EQ(mOutputState.displayBrightnessNits, displaySettings.targetLuminanceNits); - return futureOf({NO_ERROR, base::unique_fd()}); + return ftl::yield(Fence::NO_FENCE); }; EXPECT_CALL(*layerFE1, @@ -505,13 +500,12 @@ TEST_F(CachedSetTest, renderWhitePointNoColorTransform) { mOutputState.displayBrightnessNits = 400.f; - const auto drawLayers = - [&](const renderengine::DisplaySettings& displaySettings, - const std::vector&, - const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings, + const std::vector&, + const std::shared_ptr&, const bool, + base::unique_fd&&) -> ftl::Future { EXPECT_EQ(mOutputState.displayBrightnessNits, displaySettings.targetLuminanceNits); - return futureOf({NO_ERROR, base::unique_fd()}); + return ftl::yield(Fence::NO_FENCE); }; EXPECT_CALL(*layerFE1, @@ -555,11 +549,10 @@ TEST_F(CachedSetTest, rendersWithOffsetFramebufferContent) { mOutputState.framebufferSpace = ProjectionSpace(ui::Size(10, 20), Rect(2, 3, 10, 5)); - const auto drawLayers = - [&](const renderengine::DisplaySettings& displaySettings, - const std::vector& layers, - const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings, + const std::vector& layers, + const std::shared_ptr&, const bool, + base::unique_fd&&) -> ftl::Future { EXPECT_EQ(mOutputState.framebufferSpace.getContent(), displaySettings.physicalDisplay); EXPECT_EQ(mOutputState.layerStackSpace.getContent(), displaySettings.clip); EXPECT_EQ(ui::Transform::toRotationFlags(mOutputState.framebufferSpace.getOrientation()), @@ -568,7 +561,7 @@ TEST_F(CachedSetTest, rendersWithOffsetFramebufferContent) { EXPECT_EQ(0.75f, layers[1].alpha); EXPECT_EQ(ui::Dataspace::SRGB, displaySettings.outputDataspace); - return futureOf({NO_ERROR, base::unique_fd()}); + return ftl::yield(Fence::NO_FENCE); }; EXPECT_CALL(*layerFE1, prepareClientComposition(_)).WillOnce(Return(clientComp1)); @@ -786,11 +779,10 @@ TEST_F(CachedSetTest, addHolePunch) { EXPECT_CALL(*layerFE2, prepareClientComposition(_)).WillOnce(Return(clientComp2)); EXPECT_CALL(*layerFE3, prepareClientComposition(_)).WillOnce(Return(clientComp3)); - const auto drawLayers = - [&](const renderengine::DisplaySettings&, - const std::vector& layers, - const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + const auto drawLayers = [&](const renderengine::DisplaySettings&, + const std::vector& layers, + const std::shared_ptr&, const bool, + base::unique_fd&&) -> ftl::Future { // If the highlight layer is enabled, it will increase the size by 1. // We're interested in the third layer either way. EXPECT_GE(layers.size(), 4u); @@ -810,7 +802,7 @@ TEST_F(CachedSetTest, addHolePunch) { EXPECT_EQ(1.0f, holePunchBackgroundSettings.alpha); } - return futureOf({NO_ERROR, base::unique_fd()}); + return ftl::yield(Fence::NO_FENCE); }; EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); @@ -847,11 +839,10 @@ TEST_F(CachedSetTest, addHolePunch_noBuffer) { EXPECT_CALL(*layerFE2, prepareClientComposition(_)).WillOnce(Return(clientComp2)); EXPECT_CALL(*layerFE3, prepareClientComposition(_)).WillOnce(Return(clientComp3)); - const auto drawLayers = - [&](const renderengine::DisplaySettings&, - const std::vector& layers, - const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + const auto drawLayers = [&](const renderengine::DisplaySettings&, + const std::vector& layers, + const std::shared_ptr&, const bool, + base::unique_fd&&) -> ftl::Future { // If the highlight layer is enabled, it will increase the size by 1. // We're interested in the third layer either way. EXPECT_GE(layers.size(), 4u); @@ -872,7 +863,7 @@ TEST_F(CachedSetTest, addHolePunch_noBuffer) { EXPECT_EQ(1.0f, holePunchBackgroundSettings.alpha); } - return futureOf({NO_ERROR, base::unique_fd()}); + return ftl::yield(Fence::NO_FENCE); }; EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); @@ -999,11 +990,10 @@ TEST_F(CachedSetTest, addBlur) { BackgroundBlurOnly))) .WillOnce(Return(clientComp3)); - const auto drawLayers = - [&](const renderengine::DisplaySettings&, - const std::vector& layers, - const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + const auto drawLayers = [&](const renderengine::DisplaySettings&, + const std::vector& layers, + const std::shared_ptr&, const bool, + base::unique_fd&&) -> ftl::Future { // If the highlight layer is enabled, it will increase the size by 1. // We're interested in the third layer either way. EXPECT_GE(layers.size(), 3u); @@ -1012,7 +1002,7 @@ TEST_F(CachedSetTest, addBlur) { EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), blurSettings.source.solidColor); EXPECT_EQ(0.0f, blurSettings.alpha); - return futureOf({NO_ERROR, base::unique_fd()}); + return ftl::yield(Fence::NO_FENCE); }; EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers)); diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp index 430a66355c..86cfee6f0a 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp @@ -27,8 +27,6 @@ #include #include -#include "tests/TestUtils.h" - namespace android::compositionengine { using namespace std::chrono_literals; using impl::planner::CachedSet; @@ -171,8 +169,7 @@ void FlattenerTest::initializeFlattener(const std::vector& la void FlattenerTest::expectAllLayersFlattened(const std::vector& layers) { // layers would be flattened but the buffer would not be overridden EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), @@ -423,8 +420,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateToFlatten) { layerState1->resetFramesSinceBufferUpdate(); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); @@ -447,8 +443,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateToFlatten) { mTime += 200ms; EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); @@ -500,8 +495,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateForMiddleLayer) { layerState3->resetFramesSinceBufferUpdate(); EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); @@ -515,8 +509,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateForMiddleLayer) { // Layers 1 and 2 will be flattened a new drawFrame would be called for Layer4 and Layer5 EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); @@ -545,8 +538,7 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateForMiddleLayer) { layerState3->incrementFramesSinceBufferUpdate(); mTime += 200ms; EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_NE(getNonBufferHash(layers), mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); @@ -601,8 +593,7 @@ TEST_F(FlattenerTest, flattenLayers_pipRequiresRoundedCorners) { // This will render a CachedSet. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. @@ -666,8 +657,7 @@ TEST_F(FlattenerTest, flattenLayers_pip) { // This will render a CachedSet. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. @@ -739,8 +729,7 @@ TEST_F(FlattenerTest, flattenLayers_holePunchSingleLayer) { // This will render a CachedSet of layer 0. Though it is just one layer, it satisfies the // exception that there would be a hole punch above it. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. @@ -810,8 +799,7 @@ TEST_F(FlattenerTest, flattenLayers_holePunchSingleColorLayer) { // This will render a CachedSet of layer 0. Though it is just one layer, it satisfies the // exception that there would be a hole punch above it. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. @@ -862,8 +850,7 @@ TEST_F(FlattenerTest, flattenLayers_flattensBlurBehindRunIfFirstRun) { // layers would be flattened but the buffer would not be overridden EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), @@ -908,8 +895,7 @@ TEST_F(FlattenerTest, flattenLayers_doesNotFlattenBlurBehindRun) { // layers would be flattened but the buffer would not be overridden EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillRepeatedly(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillRepeatedly(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), @@ -962,8 +948,7 @@ TEST_F(FlattenerTest, flattenLayers_flattenSkipsLayerWithBlurBehind) { // layers would be flattened but the buffer would not be overridden EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), @@ -1011,8 +996,7 @@ TEST_F(FlattenerTest, flattenLayers_whenBlurLayerIsChanging_appliesBlurToInactiv // layers would be flattened but the buffer would not be overridden EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), @@ -1054,8 +1038,7 @@ TEST_F(FlattenerTest, flattenLayers_renderCachedSets_doesNotRenderTwice) { mTime += 200ms; // layers would be flattened but the buffer would not be overridden EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); initializeOverrideBuffer(layers); EXPECT_EQ(getNonBufferHash(layers), @@ -1122,8 +1105,7 @@ TEST_F(FlattenerRenderSchedulingTest, flattenLayers_renderCachedSets_defersUpToM } EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::chrono::steady_clock::now() - (kCachedSetRenderDuration + 10ms), @@ -1159,8 +1141,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsBT601_625) { // This will render a CachedSet. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. @@ -1210,8 +1191,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsHDR) { // This will render a CachedSet. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. @@ -1261,8 +1241,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsHDR2) { // This will render a CachedSet. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. @@ -1315,8 +1294,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsColorLayers) { // This will render a CachedSet. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. @@ -1368,8 +1346,7 @@ TEST_F(FlattenerTest, flattenLayers_includes_DISPLAY_DECORATION) { // This will render a CachedSet. EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) - .WillOnce(Return(ByMove( - futureOf({NO_ERROR, base::unique_fd()})))); + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); mFlattener->renderCachedSets(mOutputState, std::nullopt, true); // We've rendered a CachedSet, but we haven't merged it in. diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index e1fcaa03c2..bdc8406575 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -6615,13 +6615,11 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( getRenderEngine().useProtectedContext(useProtected); constexpr bool kUseFramebufferCache = false; - auto chain = - ftl::Future(getRenderEngine().drawLayers(clientCompositionDisplay, - clientRenderEngineLayers, buffer, - kUseFramebufferCache, std::move(bufferFence))) - .then(&toFenceResult); + const auto future = getRenderEngine() + .drawLayers(clientCompositionDisplay, clientRenderEngineLayers, + buffer, kUseFramebufferCache, std::move(bufferFence)) + .share(); - const auto future = chain.share(); for (auto* layer : renderedLayers) { layer->onLayerDisplayed(future); } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 2b230b3003..310188512d 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -52,11 +52,11 @@ #include #include -#include #include #include #include #include +#include #include "ClientCache.h" #include "DisplayDevice.h" diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h index 59abe3347c..23ea7a551a 100644 --- a/services/surfaceflinger/TransactionCallbackInvoker.h +++ b/services/surfaceflinger/TransactionCallbackInvoker.h @@ -26,10 +26,10 @@ #include #include -#include #include #include #include +#include namespace android { diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 0666561642..77625b36ef 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -351,15 +351,13 @@ struct BaseDisplayVariant { .WillRepeatedly([&](const renderengine::DisplaySettings& displaySettings, const std::vector&, const std::shared_ptr&, - const bool, base::unique_fd&&) - -> std::future { + const bool, base::unique_fd&&) -> std::future { EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance); EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT), displaySettings.physicalDisplay); EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT), displaySettings.clip); - return futureOf( - {NO_ERROR, base::unique_fd()}); + return futureOf(Fence::NO_FENCE); }); } @@ -404,16 +402,14 @@ struct BaseDisplayVariant { .WillRepeatedly([&](const renderengine::DisplaySettings& displaySettings, const std::vector&, const std::shared_ptr&, - const bool, base::unique_fd&&) - -> std::future { + const bool, base::unique_fd&&) -> std::future { EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance); EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT), displaySettings.physicalDisplay); EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT), displaySettings.clip); EXPECT_EQ(ui::Dataspace::UNKNOWN, displaySettings.outputDataspace); - return futureOf( - {NO_ERROR, base::unique_fd()}); + return futureOf(Fence::NO_FENCE); }); } @@ -606,7 +602,7 @@ struct BaseLayerProperties { .WillOnce([&](const renderengine::DisplaySettings& displaySettings, const std::vector& layerSettings, const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + base::unique_fd&&) -> std::future { EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance); EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT), displaySettings.physicalDisplay); @@ -614,9 +610,7 @@ struct BaseLayerProperties { displaySettings.clip); // screen capture adds an additional color layer as an alpha // prefill, so gtet the back layer. - std::future resultFuture = - futureOf( - {NO_ERROR, base::unique_fd()}); + std::future resultFuture = futureOf(Fence::NO_FENCE); if (layerSettings.empty()) { ADD_FAILURE() << "layerSettings was not expected to be empty in " "setupREBufferCompositionCommonCallExpectations " @@ -659,7 +653,7 @@ struct BaseLayerProperties { .WillOnce([&](const renderengine::DisplaySettings& displaySettings, const std::vector& layerSettings, const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + base::unique_fd&&) -> std::future { EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance); EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT), displaySettings.physicalDisplay); @@ -667,9 +661,7 @@ struct BaseLayerProperties { displaySettings.clip); // screen capture adds an additional color layer as an alpha // prefill, so get the back layer. - std::future resultFuture = - futureOf( - {NO_ERROR, base::unique_fd()}); + std::future resultFuture = futureOf(Fence::NO_FENCE); if (layerSettings.empty()) { ADD_FAILURE() << "layerSettings was not expected to be empty in " @@ -740,7 +732,7 @@ struct CommonSecureLayerProperties : public BaseLayerProperties .WillOnce([&](const renderengine::DisplaySettings& displaySettings, const std::vector& layerSettings, const std::shared_ptr&, const bool, - base::unique_fd&&) -> std::future { + base::unique_fd&&) -> std::future { EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance); EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT), displaySettings.physicalDisplay); @@ -748,9 +740,7 @@ struct CommonSecureLayerProperties : public BaseLayerProperties displaySettings.clip); // screen capture adds an additional color layer as an alpha // prefill, so get the back layer. - std::future resultFuture = - futureOf( - {NO_ERROR, base::unique_fd()}); + std::future resultFuture = futureOf(Fence::NO_FENCE); if (layerSettings.empty()) { ADD_FAILURE() << "layerSettings was not expected to be empty in " "setupInsecureREBufferCompositionCommonCallExpectations " -- GitLab From 62f51d97bf888b5039a8adc22950b950cf6698ed Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Wed, 24 Aug 2022 22:20:22 +0000 Subject: [PATCH 0233/1310] SF: layers above the frameRateMultipleThreshold should always be counted An additional fix on top of ae2e3c741f0d15d0ed64afd2df93e8c4b2d76cfb which also include layers that have a desired refresh rate that was calculated hueristically and matches the max refresh rate. Change-Id: I85534a3e004f372349dbf923ee6013855a54e4fa Bug: 242283390 Test: newly added unit tests scenarios --- .../Scheduler/RefreshRateConfigs.cpp | 29 ++++++++++--------- .../unittests/RefreshRateConfigsTest.cpp | 22 +++++++++++++- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp index 803eb4f91f..6426478a06 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp @@ -42,9 +42,9 @@ struct RefreshRateScore { DisplayModeIterator modeIt; float overallScore; struct { - float belowThreshold; - float aboveThreshold; - } fixedRateLayersScore; + float modeBelowThreshold; + float modeAboveThreshold; + } fixedRateBelowThresholdLayersScore; }; template @@ -385,7 +385,7 @@ auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vectorgetGroup() == mActiveModeIt->second->getGroup(); @@ -451,21 +451,22 @@ auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vectorgetFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold) && + const bool layerBelowThreshold = mConfig.frameRateMultipleThreshold != 0 && layer.desiredRefreshRate < Fps::fromValue(mConfig.frameRateMultipleThreshold / 2); - if (fixedSourceLayer) { - if (layerAboveThreshold) { + const bool modeAboveThreshold = layerBelowThreshold && + mode->getFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold); + if (fixedSourceLayer && layerBelowThreshold) { + if (modeAboveThreshold) { ALOGV("%s gives %s fixed source (above threshold) score of %.4f", formatLayerInfo(layer, weight).c_str(), to_string(mode->getFps()).c_str(), layerScore); - fixedRateScore.aboveThreshold += weightedLayerScore; + fixedRateBelowThresholdLayersScore.modeAboveThreshold += weightedLayerScore; } else { ALOGV("%s gives %s fixed source (below threshold) score of %.4f", formatLayerInfo(layer, weight).c_str(), to_string(mode->getFps()).c_str(), layerScore); - fixedRateScore.belowThreshold += weightedLayerScore; + fixedRateBelowThresholdLayersScore.modeBelowThreshold += weightedLayerScore; } } else { ALOGV("%s gives %s score of %.4f", formatLayerInfo(layer, weight).c_str(), @@ -476,7 +477,7 @@ auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vectorsecond->getFps()).c_str(), overallScore); diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp index 1484bcd12b..188fd58dea 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp @@ -564,9 +564,10 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_mu TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60, {.frameRateMultipleThreshold = 120}); - std::vector layers = {{.weight = 1.f}, {.weight = 1.f}}; + std::vector layers = {{.weight = 1.f}, {.weight = 1.f}, {.weight = 1.f}}; auto& lr1 = layers[0]; auto& lr2 = layers[1]; + auto& lr3 = layers[2]; lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::ExplicitDefault; @@ -662,6 +663,25 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_mu lr2.vote = LayerVoteType::ExplicitExact; lr2.name = "120Hz ExplicitExact"; EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers)); + + lr1.desiredRefreshRate = 10_Hz; + lr1.vote = LayerVoteType::ExplicitExactOrMultiple; + lr1.name = "30Hz ExplicitExactOrMultiple"; + lr2.desiredRefreshRate = 120_Hz; + lr2.vote = LayerVoteType::Heuristic; + lr2.name = "120Hz ExplicitExact"; + EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers)); + + lr1.desiredRefreshRate = 30_Hz; + lr1.vote = LayerVoteType::ExplicitExactOrMultiple; + lr1.name = "30Hz ExplicitExactOrMultiple"; + lr2.desiredRefreshRate = 30_Hz; + lr2.vote = LayerVoteType::ExplicitExactOrMultiple; + lr2.name = "30Hz ExplicitExactOrMultiple"; + lr3.vote = LayerVoteType::Heuristic; + lr3.desiredRefreshRate = 120_Hz; + lr3.name = "120Hz Heuristic"; + EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers)); } TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60) { -- GitLab From d3f9584d49be0112f01547eab82990b65954d5e8 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Wed, 24 Aug 2022 15:49:36 +0000 Subject: [PATCH 0234/1310] TouchInputMapper: Fix pointer ID in dispatchPointerMouse I encountered this apparent error while refactoring. I tested cursor movement with both a mouse and a touchpad with this "fix" in place, and cannot see a difference in behaviour. I also can't imagine how it couldn't be an error, given that (if I understand correctly) the pointer index for a the same pointer ID can change between events. However, given that I can't work out the circumstances in which this code is executed, or locate the commit that originally introduced the error, I've separated this change into its own commit to call attention to it. Bug: none Test: check pointer movement; atest inputflinger_tests Change-Id: I89d8e10577f99fb8699abbea652a694ff3b7ca20 --- services/inputflinger/reader/mapper/TouchInputMapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 2ebbd3716b..eaf03f1031 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -3441,7 +3441,7 @@ void TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs_t readTime, uint uint32_t currentIndex = mCurrentRawState.rawPointerData.idToIndex[id]; float deltaX = 0, deltaY = 0; if (mLastCookedState.mouseIdBits.hasBit(id)) { - uint32_t lastIndex = mCurrentRawState.rawPointerData.idToIndex[id]; + uint32_t lastIndex = mLastRawState.rawPointerData.idToIndex[id]; deltaX = (mCurrentRawState.rawPointerData.pointers[currentIndex].x - mLastRawState.rawPointerData.pointers[lastIndex].x) * mPointerXMovementScale; -- GitLab From 714d1ad62c21b8f839dc0475ba8b2d4632c2ab22 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Wed, 24 Aug 2022 16:36:43 +0000 Subject: [PATCH 0235/1310] TouchInputMapper: Extract method for moving the cursor Bug: none Test: check cursor movement on Lenovo P12 Pro trackpad Test: atest inputflinger_tests Change-Id: I7b6accf91485608d1c8cfbe9887bf1cc4b733efc --- .../reader/mapper/TouchInputMapper.cpp | 58 ++++++------------- .../reader/mapper/TouchInputMapper.h | 4 ++ 2 files changed, 22 insertions(+), 40 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index eaf03f1031..539e24a098 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -2847,22 +2847,10 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi } } - float deltaX = 0, deltaY = 0; if (activeTouchId >= 0 && mLastCookedState.fingerIdBits.hasBit(activeTouchId)) { - const RawPointerData::Pointer& currentPointer = - mCurrentRawState.rawPointerData.pointerForId(activeTouchId); - const RawPointerData::Pointer& lastPointer = - mLastRawState.rawPointerData.pointerForId(activeTouchId); - deltaX = (currentPointer.x - lastPointer.x) * mPointerXMovementScale; - deltaY = (currentPointer.y - lastPointer.y) * mPointerYMovementScale; - - rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY); - mPointerVelocityControl.move(when, &deltaX, &deltaY); - - // Move the pointer using a relative motion. // When using spots, the click will occur at the position of the anchor // spot and all other spots will move there. - mPointerController->move(deltaX, deltaY); + moveMousePointerFromPointerDelta(when, activeTouchId); } else { mPointerVelocityControl.reset(); } @@ -2974,21 +2962,9 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerGesture.currentGestureMode = PointerGesture::Mode::TAP_DRAG; } - float deltaX = 0, deltaY = 0; if (mLastCookedState.fingerIdBits.hasBit(activeTouchId)) { - const RawPointerData::Pointer& currentPointer = - mCurrentRawState.rawPointerData.pointerForId(activeTouchId); - const RawPointerData::Pointer& lastPointer = - mLastRawState.rawPointerData.pointerForId(activeTouchId); - deltaX = (currentPointer.x - lastPointer.x) * mPointerXMovementScale; - deltaY = (currentPointer.y - lastPointer.y) * mPointerYMovementScale; - - rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY); - mPointerVelocityControl.move(when, &deltaX, &deltaY); - - // Move the pointer using a relative motion. // When using spots, the hover or drag will occur at the position of the anchor spot. - mPointerController->move(deltaX, deltaY); + moveMousePointerFromPointerDelta(when, activeTouchId); } else { mPointerVelocityControl.reset(); } @@ -3395,6 +3371,20 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi return true; } +void TouchInputMapper::moveMousePointerFromPointerDelta(nsecs_t when, uint32_t pointerId) { + const RawPointerData::Pointer& currentPointer = + mCurrentRawState.rawPointerData.pointerForId(pointerId); + const RawPointerData::Pointer& lastPointer = + mLastRawState.rawPointerData.pointerForId(pointerId); + float deltaX = (currentPointer.x - lastPointer.x) * mPointerXMovementScale; + float deltaY = (currentPointer.y - lastPointer.y) * mPointerYMovementScale; + + rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY); + mPointerVelocityControl.move(when, &deltaX, &deltaY); + + mPointerController->move(deltaX, deltaY); +} + void TouchInputMapper::dispatchPointerStylus(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { mPointerSimple.currentCoords.clear(); mPointerSimple.currentProperties.clear(); @@ -3438,21 +3428,8 @@ void TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs_t readTime, uint bool down, hovering; if (!mCurrentCookedState.mouseIdBits.isEmpty()) { uint32_t id = mCurrentCookedState.mouseIdBits.firstMarkedBit(); - uint32_t currentIndex = mCurrentRawState.rawPointerData.idToIndex[id]; - float deltaX = 0, deltaY = 0; if (mLastCookedState.mouseIdBits.hasBit(id)) { - uint32_t lastIndex = mLastRawState.rawPointerData.idToIndex[id]; - deltaX = (mCurrentRawState.rawPointerData.pointers[currentIndex].x - - mLastRawState.rawPointerData.pointers[lastIndex].x) * - mPointerXMovementScale; - deltaY = (mCurrentRawState.rawPointerData.pointers[currentIndex].y - - mLastRawState.rawPointerData.pointers[lastIndex].y) * - mPointerYMovementScale; - - rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY); - mPointerVelocityControl.move(when, &deltaX, &deltaY); - - mPointerController->move(deltaX, deltaY); + moveMousePointerFromPointerDelta(when, id); } else { mPointerVelocityControl.reset(); } @@ -3462,6 +3439,7 @@ void TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs_t readTime, uint float x, y; mPointerController->getPosition(&x, &y); + uint32_t currentIndex = mCurrentRawState.rawPointerData.idToIndex[id]; mPointerSimple.currentCoords.copyFrom( mCurrentCookedState.cookedPointerData.pointerCoords[currentIndex]); mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x); diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index cdf1c7d032..fe1700612e 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -754,6 +754,10 @@ private: bool preparePointerGestures(nsecs_t when, bool* outCancelPreviousGesture, bool* outFinishPreviousGesture, bool isTimeout); + // Moves the on-screen mouse pointer based on the movement of the pointer of the given ID + // between the last and current events. Uses a relative motion. + void moveMousePointerFromPointerDelta(nsecs_t when, uint32_t pointerId); + void dispatchPointerStylus(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); void abortPointerStylus(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); -- GitLab From dd82b8ebbd271b5f48f8bbc620a9f5b549b0905e Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Tue, 16 Aug 2022 15:34:01 +0000 Subject: [PATCH 0236/1310] Fetch country code information from Device sysfs path HID devices report the country code via sysfs. Need to read it and pass it and save it as a member variable to InputDevice In subsequent CLs will use it to auto-detect a layout for PK. More information in DD: go/pk_auto_layout_detection Test: atest inputflinger_tests:InputReaderTest Bug: 242715614 Change-Id: I73ca7518dbee3e563c41024bb3ed41261c8d7846 --- include/input/InputDevice.h | 11 +- libs/input/Android.bp | 1 + libs/input/InputDevice.cpp | 7 +- .../input/InputDeviceCountryCode.aidl | 212 ++++++++++++++++++ services/inputflinger/reader/EventHub.cpp | 32 +++ services/inputflinger/reader/InputDevice.cpp | 15 +- .../inputflinger/reader/include/EventHub.h | 11 +- .../inputflinger/reader/include/InputDevice.h | 4 + .../inputflinger/tests/InputReader_test.cpp | 28 +++ 9 files changed, 312 insertions(+), 9 deletions(-) create mode 100644 libs/input/android/hardware/input/InputDeviceCountryCode.aidl diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h index 3585392c2b..d51d6a722a 100644 --- a/include/input/InputDevice.h +++ b/include/input/InputDevice.h @@ -23,6 +23,8 @@ #include #include +#include "android/hardware/input/InputDeviceCountryCode.h" + namespace android { /* @@ -210,8 +212,10 @@ public: }; void initialize(int32_t id, int32_t generation, int32_t controllerNumber, - const InputDeviceIdentifier& identifier, const std::string& alias, bool isExternal, - bool hasMic); + const InputDeviceIdentifier& identifier, const std::string& alias, + bool isExternal, bool hasMic, + hardware::input::InputDeviceCountryCode countryCode = + hardware::input::InputDeviceCountryCode::INVALID); inline int32_t getId() const { return mId; } inline int32_t getControllerNumber() const { return mControllerNumber; } @@ -223,6 +227,7 @@ public: } inline bool isExternal() const { return mIsExternal; } inline bool hasMic() const { return mHasMic; } + inline hardware::input::InputDeviceCountryCode getCountryCode() const { return mCountryCode; } inline uint32_t getSources() const { return mSources; } const MotionRange* getMotionRange(int32_t axis, uint32_t source) const; @@ -274,9 +279,11 @@ private: std::string mAlias; bool mIsExternal; bool mHasMic; + hardware::input::InputDeviceCountryCode mCountryCode; uint32_t mSources; int32_t mKeyboardType; std::shared_ptr mKeyCharacterMap; + bool mHasVibrator; bool mHasBattery; bool mHasButtonUnderPad; diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 5030d60da2..29e02cf73a 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -26,6 +26,7 @@ package { filegroup { name: "inputconstants_aidl", srcs: [ + "android/hardware/input/InputDeviceCountryCode.aidl", "android/os/IInputConstants.aidl", "android/os/InputEventInjectionResult.aidl", "android/os/InputEventInjectionSync.aidl", diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp index a9089690b0..3fe03c7979 100644 --- a/libs/input/InputDevice.cpp +++ b/libs/input/InputDevice.cpp @@ -26,6 +26,7 @@ #include where F is a function that maps T to U. template constexpr auto transform(F&& f) const& { - using U = std::remove_cv_t>; - if (has_value()) return Optional(std::invoke(std::forward(f), value())); - return Optional(); + using R = details::transform_result_t; + if (has_value()) return R(std::invoke(std::forward(f), value())); + return R(); } template constexpr auto transform(F&& f) & { - using U = std::remove_cv_t>; - if (has_value()) return Optional(std::invoke(std::forward(f), value())); - return Optional(); + using R = details::transform_result_t; + if (has_value()) return R(std::invoke(std::forward(f), value())); + return R(); } template constexpr auto transform(F&& f) const&& { - using U = std::invoke_result_t; - if (has_value()) return Optional(std::invoke(std::forward(f), std::move(value()))); - return Optional(); + using R = details::transform_result_t; + if (has_value()) return R(std::invoke(std::forward(f), std::move(value()))); + return R(); } template constexpr auto transform(F&& f) && { - using U = std::invoke_result_t; - if (has_value()) return Optional(std::invoke(std::forward(f), std::move(value()))); - return Optional(); + using R = details::transform_result_t; + if (has_value()) return R(std::invoke(std::forward(f), std::move(value()))); + return R(); + } + + // Returns Optional where F is a function that maps T to Optional. + template + constexpr auto and_then(F&& f) const& { + using R = details::and_then_result_t; + if (has_value()) return std::invoke(std::forward(f), value()); + return R(); + } + + template + constexpr auto and_then(F&& f) & { + using R = details::and_then_result_t; + if (has_value()) return std::invoke(std::forward(f), value()); + return R(); + } + + template + constexpr auto and_then(F&& f) const&& { + using R = details::and_then_result_t; + if (has_value()) return std::invoke(std::forward(f), std::move(value())); + return R(); + } + + template + constexpr auto and_then(F&& f) && { + using R = details::and_then_result_t; + if (has_value()) return std::invoke(std::forward(f), std::move(value())); + return R(); } }; diff --git a/include/ftl/small_vector.h b/include/ftl/small_vector.h index 339726e4ea..11294c3ac8 100644 --- a/include/ftl/small_vector.h +++ b/include/ftl/small_vector.h @@ -21,11 +21,12 @@ #include #include -#include #include #include #include +#include + namespace android::ftl { template @@ -80,10 +81,6 @@ class SmallVector final : details::ArrayTraits, details::ArrayComparators; using Dynamic = SmallVector; - // TODO: Replace with std::remove_cvref_t in C++20. - template - using remove_cvref_t = std::remove_cv_t>; - public: FTL_ARRAY_TRAIT(T, value_type); FTL_ARRAY_TRAIT(T, size_type); @@ -104,7 +101,7 @@ class SmallVector final : details::ArrayTraits, details::ArrayComparators>{}>> + typename = std::enable_if_t>{}>> SmallVector(Arg&& arg, Args&&... args) : vector_(std::in_place_type, std::forward(arg), std::forward(args)...) {} diff --git a/libs/ftl/optional_test.cpp b/libs/ftl/optional_test.cpp index ede159a955..ad8d3cf8ef 100644 --- a/libs/ftl/optional_test.cpp +++ b/libs/ftl/optional_test.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -82,4 +83,62 @@ TEST(Optional, Transform) { .transform([](const std::string& s) { return s.length(); })); } +namespace { + +Optional parse_int(const std::string& str) { + if (const int i = std::atoi(str.c_str())) return i; + return std::nullopt; +} + +} // namespace + +TEST(Optional, AndThen) { + // Empty. + EXPECT_EQ(std::nullopt, Optional().and_then([](int) -> Optional { return 0; })); + EXPECT_EQ(std::nullopt, Optional().and_then([](int) { return Optional(); })); + + // By value. + EXPECT_EQ(0, Optional(0).and_then([](int x) { return Optional(x); })); + EXPECT_EQ(123, Optional("123").and_then(parse_int)); + EXPECT_EQ(std::nullopt, Optional("abc").and_then(parse_int)); + + // By reference. + { + Optional opt = 'x'; + EXPECT_EQ('z', opt.and_then([](char& c) { + c = 'y'; + return Optional('z'); + })); + + EXPECT_EQ('y', opt); + } + + // By rvalue reference. + { + std::string out; + EXPECT_EQ("xyz"s, Optional("abc"s).and_then([&out](std::string&& str) { + out = std::move(str); + return Optional("xyz"s); + })); + + EXPECT_EQ(out, "abc"s); + } + + // Chaining. + using StringVector = StaticVector; + EXPECT_EQ(14u, Optional(StaticVector{"-"s, "1"s}) + .and_then([](StringVector&& v) -> Optional { + if (v.push_back("4"s)) return v; + return {}; + }) + .and_then([](const StringVector& v) -> Optional { + if (v.full()) return std::accumulate(v.begin(), v.end(), std::string()); + return {}; + }) + .and_then(parse_int) + .and_then([](int i) { + return i > 0 ? std::nullopt : std::make_optional(static_cast(-i)); + })); +} + } // namespace android::test -- GitLab From d48d801b0f7f8b34ae3e342f0fbfdb464a5ee780 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Tue, 23 Aug 2022 08:36:32 -0700 Subject: [PATCH 0247/1310] FTL: Downcast to Optional implicitly The expression `ftl::Optional(std::optional(T()))` should not have type `ftl::Optional>`. Bug: 185536303 Test: ftl_test Change-Id: I931cc58b985e7c41037ed50bc68abdc8028c4bdd --- include/ftl/optional.h | 8 +++++++- libs/ftl/optional_test.cpp | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/include/ftl/optional.h b/include/ftl/optional.h index a0a95c4b9a..626507fd8f 100644 --- a/include/ftl/optional.h +++ b/include/ftl/optional.h @@ -32,6 +32,9 @@ template struct Optional final : std::optional { using std::optional::optional; + // Implicit downcast. + Optional(std::optional other) : std::optional(std::move(other)) {} + using std::optional::has_value; using std::optional::value; @@ -94,8 +97,11 @@ struct Optional final : std::optional { } }; -// Deduction guide. +// Deduction guides. template Optional(T) -> Optional; +template +Optional(std::optional) -> Optional; + } // namespace android::ftl diff --git a/libs/ftl/optional_test.cpp b/libs/ftl/optional_test.cpp index ad8d3cf8ef..f7410c2aa9 100644 --- a/libs/ftl/optional_test.cpp +++ b/libs/ftl/optional_test.cpp @@ -33,6 +33,29 @@ namespace android::test { using ftl::Optional; using ftl::StaticVector; +TEST(Optional, Construct) { + // Empty. + EXPECT_EQ(std::nullopt, Optional()); + EXPECT_EQ(std::nullopt, Optional(std::nullopt)); + + // Value. + EXPECT_EQ('?', Optional('?')); + EXPECT_EQ(""s, Optional(std::string())); + + // In place. + EXPECT_EQ("???"s, Optional(std::in_place, 3u, '?')); + EXPECT_EQ("abc"s, Optional(std::in_place, {'a', 'b', 'c'})); + + // Implicit downcast. + { + Optional opt = std::optional("test"s); + static_assert(std::is_same_v>); + + ASSERT_TRUE(opt); + EXPECT_EQ(opt.value(), "test"s); + } +} + TEST(Optional, Transform) { // Empty. EXPECT_EQ(std::nullopt, Optional().transform([](int) { return 0; })); -- GitLab From b8d9326f7586f47d7771c7d1e7bafd35d1b6c82e Mon Sep 17 00:00:00 2001 From: Michael Ensing Date: Tue, 12 May 2020 00:41:30 -0700 Subject: [PATCH 0248/1310] Add initial batch of inputFlinger fuzzers This batch of fuzzers is focused on the input Mappers. These fuzzers are not host_supported, and must be run on-device. Followup CLs containing additional inputflinger fuzzers will rely on the FuzzContainer.h and MapperHelpers.h header files. Fuzzers included: - CursorInputFuzzer - KeyboardInputFuzzer - MultiTouchInputFuzzer - SwitchInputFuzzer Test: Tested on a pixel 3a with HWASAN. Accurate % coverage information is not available due to the large number of shared libraries included in runs built with hwasan interfering with PC Count information (increasing the total PC count to ~682,000). Summary of updates: Coverage improvements: 75% to 82% Design changes: [1] Refactored - CursorInputFuzzer - KeyboardInputFuzzer - MultiTouchInputFuzzer - SwitchInputFuzzer in order to generate event as a combination of valid and invalid input. [2] Used FuzzedDataProvider to generate values for events. Signed-off-by: Michael Ensing Change-Id: Id39205c691f54c516f8a452293cb098382019335 --- libs/input/PropertyMap.cpp | 2 +- .../inputflinger/tests/fuzzers/Android.bp | 71 ++++ .../tests/fuzzers/CursorInputFuzzer.cpp | 96 +++++ .../tests/fuzzers/FuzzContainer.h | 82 ++++ .../tests/fuzzers/KeyboardInputFuzzer.cpp | 110 ++++++ .../tests/fuzzers/MapperHelpers.h | 372 ++++++++++++++++++ .../tests/fuzzers/MultiTouchInputFuzzer.cpp | 134 +++++++ .../tests/fuzzers/SwitchInputFuzzer.cpp | 61 +++ 8 files changed, 927 insertions(+), 1 deletion(-) create mode 100644 services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp create mode 100644 services/inputflinger/tests/fuzzers/FuzzContainer.h create mode 100644 services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp create mode 100644 services/inputflinger/tests/fuzzers/MapperHelpers.h create mode 100644 services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp create mode 100644 services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp diff --git a/libs/input/PropertyMap.cpp b/libs/input/PropertyMap.cpp index 662e568ac0..16ffa10223 100644 --- a/libs/input/PropertyMap.cpp +++ b/libs/input/PropertyMap.cpp @@ -75,7 +75,7 @@ bool PropertyMap::tryGetProperty(const std::string& key, int32_t& outValue) cons } char* end; - int value = strtol(stringValue.c_str(), &end, 10); + int32_t value = static_cast(strtol(stringValue.c_str(), &end, 10)); if (*end != '\0') { ALOGW("Property key '%s' has invalid value '%s'. Expected an integer.", key.c_str(), stringValue.c_str()); diff --git a/services/inputflinger/tests/fuzzers/Android.bp b/services/inputflinger/tests/fuzzers/Android.bp index 455a1e2133..a53db00282 100644 --- a/services/inputflinger/tests/fuzzers/Android.bp +++ b/services/inputflinger/tests/fuzzers/Android.bp @@ -46,3 +46,74 @@ cc_fuzz { cc: ["android-framework-input@google.com"], }, } + +cc_defaults { + name: "inputflinger_fuzz_defaults", + defaults: [ + "inputflinger_defaults", + ], + include_dirs: [ + "frameworks/native/services/inputflinger", + ], + shared_libs: [ + "android.hardware.input.classifier@1.0", + "libbase", + "libbinder", + "libcutils", + "liblog", + "libutils", + "libui", + "libinput", + "libinputflinger", + "libinputreader", + "libinputflinger_base", + "libstatslog", + ], + header_libs: [ + "libbatteryservice_headers", + "libinputreader_headers", + ], + fuzz_config: { + cc: ["android-framework-input@google.com"], + }, +} + +cc_fuzz { + name: "inputflinger_cursor_input_fuzzer", + defaults: [ + "inputflinger_fuzz_defaults", + ], + srcs: [ + "CursorInputFuzzer.cpp", + ], +} + +cc_fuzz { + name: "inputflinger_keyboard_input_fuzzer", + defaults: [ + "inputflinger_fuzz_defaults", + ], + srcs: [ + "KeyboardInputFuzzer.cpp", + ], +} + +cc_fuzz { + name: "inputflinger_multitouch_input_fuzzer", + defaults: [ + "inputflinger_fuzz_defaults", + ], + srcs: [ + "MultiTouchInputFuzzer.cpp", + ], +} + +cc_fuzz { + name: "inputflinger_switch_input_fuzzer", + defaults: [ + "inputflinger_fuzz_defaults", + ], + srcs: [ + "SwitchInputFuzzer.cpp", + ], +} diff --git a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp new file mode 100644 index 0000000000..4b542aab19 --- /dev/null +++ b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +namespace android { + +static void addProperty(FuzzContainer& fuzzer, std::shared_ptr fdp) { + // Pick a random property to set for the mapper to have set. + fdp->PickValueInArray>( + {[&]() -> void { fuzzer.addProperty("cursor.mode", "pointer"); }, + [&]() -> void { fuzzer.addProperty("cursor.mode", "navigation"); }, + [&]() -> void { + fuzzer.addProperty("cursor.mode", fdp->ConsumeRandomLengthString(100).data()); + }, + [&]() -> void { + fuzzer.addProperty("cursor.orientationAware", + fdp->ConsumeRandomLengthString(100).data()); + }})(); +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { + std::shared_ptr fdp = std::make_shared(data, size); + FuzzContainer fuzzer(fdp); + + CursorInputMapper& mapper = fuzzer.getMapper(); + auto policyConfig = fuzzer.getPolicyConfig(); + + // Loop through mapper operations until randomness is exhausted. + while (fdp->remaining_bytes() > 0) { + fdp->PickValueInArray>({ + [&]() -> void { addProperty(fuzzer, fdp); }, + [&]() -> void { + std::string dump; + mapper.dump(dump); + }, + [&]() -> void { mapper.getSources(); }, + [&]() -> void { + mapper.configure(fdp->ConsumeIntegral(), &policyConfig, + fdp->ConsumeIntegral()); + }, + [&]() -> void { + // Need to reconfigure with 0 or you risk a NPE. + mapper.configure(fdp->ConsumeIntegral(), &policyConfig, 0); + InputDeviceInfo info; + mapper.populateDeviceInfo(&info); + }, + [&]() -> void { + int32_t type, code; + type = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidTypes) + : fdp->ConsumeIntegral(); + code = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidCodes) + : fdp->ConsumeIntegral(); + + // Need to reconfigure with 0 or you risk a NPE. + mapper.configure(fdp->ConsumeIntegral(), &policyConfig, 0); + RawEvent rawEvent{fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + type, + code, + fdp->ConsumeIntegral()}; + mapper.process(&rawEvent); + }, + [&]() -> void { mapper.reset(fdp->ConsumeIntegral()); }, + [&]() -> void { + mapper.getScanCodeState(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + // Need to reconfigure with 0 or you risk a NPE. + mapper.configure(fdp->ConsumeIntegral(), &policyConfig, 0); + mapper.getAssociatedDisplayId(); + }, + })(); + } + + return 0; +} + +} // namespace android diff --git a/services/inputflinger/tests/fuzzers/FuzzContainer.h b/services/inputflinger/tests/fuzzers/FuzzContainer.h new file mode 100644 index 0000000000..62615d0116 --- /dev/null +++ b/services/inputflinger/tests/fuzzers/FuzzContainer.h @@ -0,0 +1,82 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace android { + +class FuzzContainer { + std::shared_ptr mFuzzEventHub; + sp mFuzzPolicy; + std::unique_ptr mFuzzListener; + std::unique_ptr mFuzzContext; + std::unique_ptr mFuzzDevice; + InputReaderConfiguration mPolicyConfig; + std::shared_ptr mFdp; + +public: + FuzzContainer(std::shared_ptr fdp) : mFdp(fdp) { + // Setup parameters. + std::string deviceName = mFdp->ConsumeRandomLengthString(16); + std::string deviceLocation = mFdp->ConsumeRandomLengthString(12); + int32_t deviceID = mFdp->ConsumeIntegralInRange(0, 5); + int32_t deviceGeneration = mFdp->ConsumeIntegralInRange(/*from*/ 0, /*to*/ 5); + + // Create mocked objects. + mFuzzEventHub = std::make_shared(mFdp); + mFuzzPolicy = sp::make(mFdp); + mFuzzListener = std::make_unique(); + mFuzzContext = std::make_unique(mFuzzEventHub, mFuzzPolicy, + *mFuzzListener, mFdp); + + InputDeviceIdentifier identifier; + identifier.name = deviceName; + identifier.location = deviceLocation; + mFuzzDevice = std::make_unique(mFuzzContext.get(), deviceID, deviceGeneration, + identifier); + mFuzzPolicy->getReaderConfiguration(&mPolicyConfig); + } + + ~FuzzContainer() {} + + void configureDevice() { + nsecs_t arbitraryTime = mFdp->ConsumeIntegral(); + mFuzzDevice->configure(arbitraryTime, &mPolicyConfig, 0); + mFuzzDevice->reset(arbitraryTime); + } + + void addProperty(std::string key, std::string value) { + mFuzzEventHub->addProperty(key, value); + configureDevice(); + } + + InputReaderConfiguration& getPolicyConfig() { return mPolicyConfig; } + + template + T& getMapper(Args... args) { + T& mapper = mFuzzDevice->addMapper(mFdp->ConsumeIntegral(), args...); + configureDevice(); + return mapper; + } +}; + +} // namespace android diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp new file mode 100644 index 0000000000..c48a099bca --- /dev/null +++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp @@ -0,0 +1,110 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +namespace android { + +const int32_t kMaxKeycodes = 100; + +static void addProperty(FuzzContainer& fuzzer, std::shared_ptr fdp) { + // Pick a random property to set for the mapper to have set. + fdp->PickValueInArray>( + {[&]() -> void { fuzzer.addProperty("keyboard.orientationAware", "1"); }, + [&]() -> void { + fuzzer.addProperty("keyboard.orientationAware", + fdp->ConsumeRandomLengthString(100).data()); + }, + [&]() -> void { + fuzzer.addProperty("keyboard.doNotWakeByDefault", + fdp->ConsumeRandomLengthString(100).data()); + }, + [&]() -> void { + fuzzer.addProperty("keyboard.handlesKeyRepeat", + fdp->ConsumeRandomLengthString(100).data()); + }})(); +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { + std::shared_ptr fdp = std::make_shared(data, size); + FuzzContainer fuzzer(fdp); + + KeyboardInputMapper& mapper = + fuzzer.getMapper(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + auto policyConfig = fuzzer.getPolicyConfig(); + + // Loop through mapper operations until randomness is exhausted. + while (fdp->remaining_bytes() > 0) { + fdp->PickValueInArray>({ + [&]() -> void { addProperty(fuzzer, fdp); }, + [&]() -> void { + std::string dump; + mapper.dump(dump); + }, + [&]() -> void { + InputDeviceInfo info; + mapper.populateDeviceInfo(&info); + }, + [&]() -> void { mapper.getSources(); }, + [&]() -> void { + mapper.configure(fdp->ConsumeIntegral(), &policyConfig, + fdp->ConsumeIntegral()); + }, + [&]() -> void { mapper.reset(fdp->ConsumeIntegral()); }, + [&]() -> void { + int32_t type, code; + type = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidTypes) + : fdp->ConsumeIntegral(); + code = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidCodes) + : fdp->ConsumeIntegral(); + RawEvent rawEvent{fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + type, + code, + fdp->ConsumeIntegral()}; + mapper.process(&rawEvent); + }, + [&]() -> void { + mapper.getKeyCodeState(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + mapper.getScanCodeState(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + std::vector keyCodes; + int32_t numBytes = fdp->ConsumeIntegralInRange(0, kMaxKeycodes); + for (int32_t i = 0; i < numBytes; ++i) { + keyCodes.push_back(fdp->ConsumeIntegral()); + } + mapper.markSupportedKeyCodes(fdp->ConsumeIntegral(), keyCodes, + nullptr); + }, + [&]() -> void { mapper.getMetaState(); }, + [&]() -> void { mapper.updateMetaState(fdp->ConsumeIntegral()); }, + [&]() -> void { mapper.getAssociatedDisplayId(); }, + })(); + } + + return 0; +} + +} // namespace android diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h new file mode 100644 index 0000000000..53a7b169e4 --- /dev/null +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -0,0 +1,372 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include "android/hardware/input/InputDeviceCountryCode.h" + +using android::hardware::input::InputDeviceCountryCode; + +constexpr size_t kValidTypes[] = {EV_SW, + EV_SYN, + SYN_REPORT, + EV_ABS, + EV_KEY, + EV_MSC, + EV_REL, + android::EventHubInterface::DEVICE_ADDED, + android::EventHubInterface::DEVICE_REMOVED, + android::EventHubInterface::FINISHED_DEVICE_SCAN}; + +constexpr size_t kValidCodes[] = { + SYN_REPORT, + ABS_MT_SLOT, + SYN_MT_REPORT, + ABS_MT_POSITION_X, + ABS_MT_POSITION_Y, + ABS_MT_TOUCH_MAJOR, + ABS_MT_TOUCH_MINOR, + ABS_MT_WIDTH_MAJOR, + ABS_MT_WIDTH_MINOR, + ABS_MT_ORIENTATION, + ABS_MT_TRACKING_ID, + ABS_MT_PRESSURE, + ABS_MT_DISTANCE, + ABS_MT_TOOL_TYPE, + SYN_MT_REPORT, + MSC_SCAN, + REL_X, + REL_Y, + REL_WHEEL, + REL_HWHEEL, + BTN_LEFT, + BTN_RIGHT, + BTN_MIDDLE, + BTN_BACK, + BTN_SIDE, + BTN_FORWARD, + BTN_EXTRA, + BTN_TASK, +}; + +constexpr InputDeviceCountryCode kCountryCodes[] = { + InputDeviceCountryCode::INVALID, + InputDeviceCountryCode::NOT_SUPPORTED, + InputDeviceCountryCode::ARABIC, + InputDeviceCountryCode::BELGIAN, + InputDeviceCountryCode::CANADIAN_BILINGUAL, + InputDeviceCountryCode::CANADIAN_FRENCH, + InputDeviceCountryCode::CZECH_REPUBLIC, + InputDeviceCountryCode::DANISH, + InputDeviceCountryCode::FINNISH, + InputDeviceCountryCode::FRENCH, + InputDeviceCountryCode::GERMAN, + InputDeviceCountryCode::GREEK, + InputDeviceCountryCode::HEBREW, + InputDeviceCountryCode::HUNGARY, + InputDeviceCountryCode::INTERNATIONAL, + InputDeviceCountryCode::ITALIAN, + InputDeviceCountryCode::JAPAN, + InputDeviceCountryCode::KOREAN, + InputDeviceCountryCode::LATIN_AMERICAN, + InputDeviceCountryCode::DUTCH, + InputDeviceCountryCode::NORWEGIAN, + InputDeviceCountryCode::PERSIAN, + InputDeviceCountryCode::POLAND, + InputDeviceCountryCode::PORTUGUESE, + InputDeviceCountryCode::RUSSIA, + InputDeviceCountryCode::SLOVAKIA, + InputDeviceCountryCode::SPANISH, + InputDeviceCountryCode::SWEDISH, + InputDeviceCountryCode::SWISS_FRENCH, + InputDeviceCountryCode::SWISS_GERMAN, + InputDeviceCountryCode::SWITZERLAND, + InputDeviceCountryCode::TAIWAN, + InputDeviceCountryCode::TURKISH_Q, + InputDeviceCountryCode::UK, + InputDeviceCountryCode::US, + InputDeviceCountryCode::YUGOSLAVIA, + InputDeviceCountryCode::TURKISH_F, +}; + +constexpr size_t kMaxSize = 256; + +namespace android { + +class FuzzEventHub : public EventHubInterface { + InputDeviceIdentifier mIdentifier; + std::vector mVideoFrames; + PropertyMap mFuzzConfig; + size_t mCount = 0; + std::array mBuf; + std::shared_ptr mFdp; + +public: + FuzzEventHub(std::shared_ptr fdp) : mFdp(std::move(fdp)) {} + ~FuzzEventHub() {} + void addProperty(std::string key, std::string value) { mFuzzConfig.addProperty(key, value); } + void addEvents(std::shared_ptr fdp) { + mCount = fdp->ConsumeIntegralInRange(0, kMaxSize); + + for (size_t i = 0; i < mCount; ++i) { + int32_t type = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidTypes) + : fdp->ConsumeIntegral(); + int32_t code = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidCodes) + : fdp->ConsumeIntegral(); + mBuf[i] = {fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + type, + code, + fdp->ConsumeIntegral()}; + } + } + + ftl::Flags getDeviceClasses(int32_t deviceId) const override { + return ftl::Flags(mFdp->ConsumeIntegral()); + } + InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override { + return mIdentifier; + } + int32_t getDeviceControllerNumber(int32_t deviceId) const override { + return mFdp->ConsumeIntegral(); + } + void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const override { + *outConfiguration = mFuzzConfig; + } + status_t getAbsoluteAxisInfo(int32_t deviceId, int axis, + RawAbsoluteAxisInfo* outAxisInfo) const override { + return mFdp->ConsumeIntegral(); + } + bool hasRelativeAxis(int32_t deviceId, int axis) const override { return mFdp->ConsumeBool(); } + bool hasInputProperty(int32_t deviceId, int property) const override { + return mFdp->ConsumeBool(); + } + bool hasMscEvent(int32_t deviceId, int mscEvent) const override { return mFdp->ConsumeBool(); } + status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState, + int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const override { + return mFdp->ConsumeIntegral(); + } + status_t mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxisInfo) const override { + return mFdp->ConsumeIntegral(); + } + void setExcludedDevices(const std::vector& devices) override {} + size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) override { + for (size_t i = 0; i < mCount; ++i) buffer[i] = mBuf[i]; + + return mCount; + } + std::vector getVideoFrames(int32_t deviceId) override { return mVideoFrames; } + + base::Result> mapSensor( + int32_t deviceId, int32_t absCode) const override { + return base::ResultError("Fuzzer", UNKNOWN_ERROR); + }; + // Raw batteries are sysfs power_supply nodes we found from the EventHub device sysfs node, + // containing the raw info of the sysfs node structure. + std::vector getRawBatteryIds(int32_t deviceId) const override { return {}; } + std::optional getRawBatteryInfo(int32_t deviceId, + int32_t BatteryId) const override { + return std::nullopt; + }; + + std::vector getRawLightIds(int32_t deviceId) const override { return {}; }; + std::optional getRawLightInfo(int32_t deviceId, int32_t lightId) const override { + return std::nullopt; + }; + std::optional getLightBrightness(int32_t deviceId, int32_t lightId) const override { + return std::nullopt; + }; + void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) override{}; + std::optional> getLightIntensities( + int32_t deviceId, int32_t lightId) const override { + return std::nullopt; + }; + void setLightIntensities(int32_t deviceId, int32_t lightId, + std::unordered_map intensities) override{}; + + InputDeviceCountryCode getCountryCode(int32_t deviceId) const override { + return mFdp->PickValueInArray(kCountryCodes); + }; + + int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override { + return mFdp->ConsumeIntegral(); + } + int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override { + return mFdp->ConsumeIntegral(); + } + int32_t getSwitchState(int32_t deviceId, int32_t sw) const override { + return mFdp->ConsumeIntegral(); + } + int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override { + return mFdp->ConsumeIntegral(); + } + status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, + int32_t* outValue) const override { + return mFdp->ConsumeIntegral(); + } + bool markSupportedKeyCodes(int32_t deviceId, const std::vector& keyCodes, + uint8_t* outFlags) const override { + return mFdp->ConsumeBool(); + } + bool hasScanCode(int32_t deviceId, int32_t scanCode) const override { + return mFdp->ConsumeBool(); + } + bool hasKeyCode(int32_t deviceId, int32_t keyCode) const override { + return mFdp->ConsumeBool(); + } + bool hasLed(int32_t deviceId, int32_t led) const override { return mFdp->ConsumeBool(); } + void setLedState(int32_t deviceId, int32_t led, bool on) override {} + void getVirtualKeyDefinitions( + int32_t deviceId, std::vector& outVirtualKeys) const override {} + const std::shared_ptr getKeyCharacterMap(int32_t deviceId) const override { + return nullptr; + } + bool setKeyboardLayoutOverlay(int32_t deviceId, std::shared_ptr map) override { + return mFdp->ConsumeBool(); + } + void vibrate(int32_t deviceId, const VibrationElement& effect) override {} + void cancelVibrate(int32_t deviceId) override {} + + std::vector getVibratorIds(int32_t deviceId) const override { return {}; }; + + /* Query battery level. */ + std::optional getBatteryCapacity(int32_t deviceId, int32_t batteryId) const override { + return std::nullopt; + }; + + /* Query battery status. */ + std::optional getBatteryStatus(int32_t deviceId, int32_t batteryId) const override { + return std::nullopt; + }; + + void requestReopenDevices() override {} + void wake() override {} + void dump(std::string& dump) const override {} + void monitor() const override {} + bool isDeviceEnabled(int32_t deviceId) const override { return mFdp->ConsumeBool(); } + status_t enableDevice(int32_t deviceId) override { return mFdp->ConsumeIntegral(); } + status_t disableDevice(int32_t deviceId) override { return mFdp->ConsumeIntegral(); } +}; + +class FuzzPointerController : public PointerControllerInterface { + std::shared_ptr mFdp; + +public: + FuzzPointerController(std::shared_ptr mFdp) : mFdp(mFdp) {} + ~FuzzPointerController() {} + bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override { + return mFdp->ConsumeBool(); + } + void move(float deltaX, float deltaY) override {} + void setButtonState(int32_t buttonState) override {} + int32_t getButtonState() const override { return mFdp->ConsumeIntegral(); } + void setPosition(float x, float y) override {} + void getPosition(float* outX, float* outY) const override {} + void fade(Transition transition) override {} + void unfade(Transition transition) override {} + void setPresentation(Presentation presentation) override {} + void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, + BitSet32 spotIdBits, int32_t displayId) override {} + void clearSpots() override {} + int32_t getDisplayId() const override { return mFdp->ConsumeIntegral(); } + void setDisplayViewport(const DisplayViewport& displayViewport) override {} +}; + +class FuzzInputReaderPolicy : public InputReaderPolicyInterface { + TouchAffineTransformation mTransform; + std::shared_ptr mPointerController; + std::shared_ptr mFdp; + +protected: + ~FuzzInputReaderPolicy() {} + +public: + FuzzInputReaderPolicy(std::shared_ptr mFdp) : mFdp(mFdp) { + mPointerController = std::make_shared(mFdp); + } + void getReaderConfiguration(InputReaderConfiguration* outConfig) override {} + std::shared_ptr obtainPointerController(int32_t deviceId) override { + return mPointerController; + } + void notifyInputDevicesChanged(const std::vector& inputDevices) override {} + std::shared_ptr getKeyboardLayoutOverlay( + const InputDeviceIdentifier& identifier) override { + return nullptr; + } + std::string getDeviceAlias(const InputDeviceIdentifier& identifier) { + return mFdp->ConsumeRandomLengthString(32); + } + TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor, + int32_t surfaceRotation) override { + return mTransform; + } + void setTouchAffineTransformation(const TouchAffineTransformation t) { mTransform = t; } +}; + +class FuzzInputListener : public virtual InputListenerInterface { +public: + void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override {} + void notifyKey(const NotifyKeyArgs* args) override {} + void notifyMotion(const NotifyMotionArgs* args) override {} + void notifySwitch(const NotifySwitchArgs* args) override {} + void notifySensor(const NotifySensorArgs* args) override{}; + void notifyVibratorState(const NotifyVibratorStateArgs* args) override{}; + void notifyDeviceReset(const NotifyDeviceResetArgs* args) override {} + void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override{}; +}; + +class FuzzInputReaderContext : public InputReaderContext { + std::shared_ptr mEventHub; + sp mPolicy; + InputListenerInterface& mListener; + std::shared_ptr mFdp; + +public: + FuzzInputReaderContext(std::shared_ptr eventHub, + const sp& policy, + InputListenerInterface& listener, + std::shared_ptr mFdp) + : mEventHub(eventHub), mPolicy(policy), mListener(listener), mFdp(mFdp) {} + ~FuzzInputReaderContext() {} + void updateGlobalMetaState() override {} + int32_t getGlobalMetaState() { return mFdp->ConsumeIntegral(); } + void disableVirtualKeysUntil(nsecs_t time) override {} + bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode, int32_t scanCode) override { + return mFdp->ConsumeBool(); + } + void fadePointer() override {} + std::shared_ptr getPointerController(int32_t deviceId) override { + return mPolicy->obtainPointerController(0); + } + void requestTimeoutAtTime(nsecs_t when) override {} + int32_t bumpGeneration() override { return mFdp->ConsumeIntegral(); } + void getExternalStylusDevices(std::vector& outDevices) override {} + void dispatchExternalStylusState(const StylusState& outState) override {} + InputReaderPolicyInterface* getPolicy() override { return mPolicy.get(); } + InputListenerInterface& getListener() override { return mListener; } + EventHubInterface* getEventHub() override { return mEventHub.get(); } + int32_t getNextId() override { return mFdp->ConsumeIntegral(); } + + void updateLedMetaState(int32_t metaState) override{}; + int32_t getLedMetaState() override { return mFdp->ConsumeIntegral(); }; +}; + +} // namespace android diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp new file mode 100644 index 0000000000..59b064249a --- /dev/null +++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp @@ -0,0 +1,134 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +namespace android { + +const int32_t kMaxKeycodes = 100; + +static void addProperty(FuzzContainer& fuzzer, std::shared_ptr fdp) { + // Pick a random property to set for the mapper to have set. + fdp->PickValueInArray>( + {[&]() -> void { fuzzer.addProperty("touch.deviceType", "touchScreen"); }, + [&]() -> void { + fuzzer.addProperty("touch.deviceType", fdp->ConsumeRandomLengthString(8).data()); + }, + [&]() -> void { + fuzzer.addProperty("touch.size.scale", fdp->ConsumeRandomLengthString(8).data()); + }, + [&]() -> void { + fuzzer.addProperty("touch.size.bias", fdp->ConsumeRandomLengthString(8).data()); + }, + [&]() -> void { + fuzzer.addProperty("touch.size.isSummed", + fdp->ConsumeRandomLengthString(8).data()); + }, + [&]() -> void { + fuzzer.addProperty("touch.size.calibration", + fdp->ConsumeRandomLengthString(8).data()); + }, + [&]() -> void { + fuzzer.addProperty("touch.pressure.scale", + fdp->ConsumeRandomLengthString(8).data()); + }, + [&]() -> void { + fuzzer.addProperty("touch.size.calibration", + fdp->ConsumeBool() ? "diameter" : "area"); + }, + [&]() -> void { + fuzzer.addProperty("touch.pressure.calibration", + fdp->ConsumeRandomLengthString(8).data()); + }})(); +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { + std::shared_ptr fdp = std::make_shared(data, size); + FuzzContainer fuzzer(fdp); + + MultiTouchInputMapper& mapper = fuzzer.getMapper(); + auto policyConfig = fuzzer.getPolicyConfig(); + + // Loop through mapper operations until randomness is exhausted. + while (fdp->remaining_bytes() > 0) { + fdp->PickValueInArray>({ + [&]() -> void { addProperty(fuzzer, fdp); }, + [&]() -> void { + std::string dump; + mapper.dump(dump); + }, + [&]() -> void { + InputDeviceInfo info; + mapper.populateDeviceInfo(&info); + }, + [&]() -> void { mapper.getSources(); }, + [&]() -> void { + mapper.configure(fdp->ConsumeIntegral(), &policyConfig, + fdp->ConsumeIntegral()); + }, + [&]() -> void { mapper.reset(fdp->ConsumeIntegral()); }, + [&]() -> void { + int32_t type = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidTypes) + : fdp->ConsumeIntegral(); + int32_t code = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidCodes) + : fdp->ConsumeIntegral(); + RawEvent rawEvent{fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + type, + code, + fdp->ConsumeIntegral()}; + mapper.process(&rawEvent); + }, + [&]() -> void { + mapper.getKeyCodeState(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + mapper.getScanCodeState(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + std::vector keyCodes; + int32_t numBytes = fdp->ConsumeIntegralInRange(0, kMaxKeycodes); + for (int32_t i = 0; i < numBytes; ++i) { + keyCodes.push_back(fdp->ConsumeIntegral()); + } + mapper.markSupportedKeyCodes(fdp->ConsumeIntegral(), keyCodes, + nullptr); + }, + [&]() -> void { + mapper.cancelTouch(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { mapper.timeoutExpired(fdp->ConsumeIntegral()); }, + [&]() -> void { + StylusState state{fdp->ConsumeIntegral(), + fdp->ConsumeFloatingPoint(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()}; + mapper.updateExternalStylusState(state); + }, + [&]() -> void { mapper.getAssociatedDisplayId(); }, + })(); + } + + return 0; +} + +} // namespace android diff --git a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp new file mode 100644 index 0000000000..e76bd728b8 --- /dev/null +++ b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +namespace android { + +extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { + std::shared_ptr fdp = std::make_shared(data, size); + FuzzContainer fuzzer(fdp); + + SwitchInputMapper& mapper = fuzzer.getMapper(); + auto policyConfig = fuzzer.getPolicyConfig(); + + // Loop through mapper operations until randomness is exhausted. + while (fdp->remaining_bytes() > 0) { + fdp->PickValueInArray>({ + [&]() -> void { + std::string dump; + mapper.dump(dump); + }, + [&]() -> void { mapper.getSources(); }, + [&]() -> void { + int32_t type = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidTypes) + : fdp->ConsumeIntegral(); + int32_t code = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidCodes) + : fdp->ConsumeIntegral(); + RawEvent rawEvent{fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + type, + code, + fdp->ConsumeIntegral()}; + mapper.process(&rawEvent); + }, + [&]() -> void { + mapper.getSwitchState(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + })(); + } + + return 0; +} + +} // namespace android -- GitLab From 910968de36b314ccd702aaf0e8f92f403cd4c352 Mon Sep 17 00:00:00 2001 From: Michael Ensing Date: Sun, 19 Jul 2020 17:19:31 -0700 Subject: [PATCH 0249/1310] Add inputflinger blockingQueue/classifier fuzzers This CL aims to add libfuzzer fuzzers to inputflinger, to test the BlockingQueue and InputClassifier objects. Test: Each fuzzer was tested on a Pixel 3a for a few million iterations to ensure stability. Executions/sec average about 500 for the BlockingQueue fuzzer, and ~2000-2500 for the InputClassifier fuzzers. Signed-off-by: Michael Ensing Change-Id: If48e5ddbe3066800da88216882ffbbf417dc0d2c --- .../inputflinger/tests/fuzzers/Android.bp | 21 +++ .../tests/fuzzers/BlockingQueueFuzzer.cpp | 69 ++++++++++ .../tests/fuzzers/InputClassifierFuzzer.cpp | 126 ++++++++++++++++++ 3 files changed, 216 insertions(+) create mode 100644 services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp create mode 100644 services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp diff --git a/services/inputflinger/tests/fuzzers/Android.bp b/services/inputflinger/tests/fuzzers/Android.bp index a53db00282..05755acf6c 100644 --- a/services/inputflinger/tests/fuzzers/Android.bp +++ b/services/inputflinger/tests/fuzzers/Android.bp @@ -57,6 +57,7 @@ cc_defaults { ], shared_libs: [ "android.hardware.input.classifier@1.0", + "android.hardware.input.processor-V1-ndk", "libbase", "libbinder", "libcutils", @@ -117,3 +118,23 @@ cc_fuzz { "SwitchInputFuzzer.cpp", ], } + +cc_fuzz { + name: "inputflinger_blocking_queue_fuzzer", + defaults: [ + "inputflinger_fuzz_defaults", + ], + srcs: [ + "BlockingQueueFuzzer.cpp", + ], +} + +cc_fuzz { + name: "inputflinger_input_classifier_fuzzer", + defaults: [ + "inputflinger_fuzz_defaults", + ], + srcs: [ + "InputClassifierFuzzer.cpp", + ], +} diff --git a/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp b/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp new file mode 100644 index 0000000000..d2595bfc9d --- /dev/null +++ b/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp @@ -0,0 +1,69 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "BlockingQueue.h" + +// Chosen to be a number large enough for variation in fuzzer runs, but not consume too much memory. +static constexpr size_t MAX_CAPACITY = 1024; + +namespace android { + +extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { + FuzzedDataProvider fdp(data, size); + size_t capacity = fdp.ConsumeIntegralInRange(1, MAX_CAPACITY); + size_t filled = 0; + BlockingQueue queue(capacity); + + while (fdp.remaining_bytes() > 0) { + fdp.PickValueInArray>({ + [&]() -> void { + size_t numPushes = fdp.ConsumeIntegralInRange(0, capacity + 1); + for (size_t i = 0; i < numPushes; i++) { + queue.push(fdp.ConsumeIntegral()); + } + filled = std::min(capacity, filled + numPushes); + }, + [&]() -> void { + // Pops blocks if it is empty, so only pop up to num elements inserted. + size_t numPops = fdp.ConsumeIntegralInRange(0, filled); + for (size_t i = 0; i < numPops; i++) { + queue.pop(); + } + filled > numPops ? filled -= numPops : filled = 0; + }, + [&]() -> void { + queue.clear(); + filled = 0; + }, + [&]() -> void { + int32_t eraseElement = fdp.ConsumeIntegral(); + queue.erase([&](int32_t element) { + if (element == eraseElement) { + filled--; + return true; + } + return false; + }); + }, + })(); + } + + return 0; +} + +} // namespace android diff --git a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp new file mode 100644 index 0000000000..c407cffdbf --- /dev/null +++ b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp @@ -0,0 +1,126 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "InputCommonConverter.h" +#include "InputProcessor.h" + +namespace android { + +static constexpr int32_t MAX_AXES = 64; + +// Used by two fuzz operations and a bit lengthy, so pulled out into a function. +NotifyMotionArgs generateFuzzedMotionArgs(FuzzedDataProvider &fdp) { + // Create a basic motion event for testing + PointerProperties properties; + properties.id = 0; + properties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + PointerCoords coords; + coords.clear(); + for (int32_t i = 0; i < fdp.ConsumeIntegralInRange(0, MAX_AXES); i++) { + coords.setAxisValue(fdp.ConsumeIntegral(), fdp.ConsumeFloatingPoint()); + } + + const nsecs_t downTime = 2; + const nsecs_t readTime = downTime + fdp.ConsumeIntegralInRange(0, 1E8); + NotifyMotionArgs motionArgs(fdp.ConsumeIntegral() /*sequenceNum*/, + downTime /*eventTime*/, readTime, + fdp.ConsumeIntegral() /*deviceId*/, AINPUT_SOURCE_ANY, + ADISPLAY_ID_DEFAULT, + fdp.ConsumeIntegral() /*policyFlags*/, + AMOTION_EVENT_ACTION_DOWN, + fdp.ConsumeIntegral() /*actionButton*/, + fdp.ConsumeIntegral() /*flags*/, AMETA_NONE, + fdp.ConsumeIntegral() /*buttonState*/, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, + 1 /*pointerCount*/, &properties, &coords, + fdp.ConsumeFloatingPoint() /*xPrecision*/, + fdp.ConsumeFloatingPoint() /*yPrecision*/, + AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, + {} /*videoFrames*/); + return motionArgs; +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { + FuzzedDataProvider fdp(data, size); + + std::unique_ptr mFuzzListener = std::make_unique(); + std::unique_ptr mClassifier = + std::make_unique(*mFuzzListener); + + while (fdp.remaining_bytes() > 0) { + fdp.PickValueInArray>({ + [&]() -> void { + // SendToNextStage_NotifyConfigurationChangedArgs + NotifyConfigurationChangedArgs + args(fdp.ConsumeIntegral() /*sequenceNum*/, + fdp.ConsumeIntegral() /*eventTime*/); + mClassifier->notifyConfigurationChanged(&args); + }, + [&]() -> void { + // SendToNextStage_NotifyKeyArgs + const nsecs_t eventTime = fdp.ConsumeIntegral(); + const nsecs_t readTime = + eventTime + fdp.ConsumeIntegralInRange(0, 1E8); + NotifyKeyArgs keyArgs(fdp.ConsumeIntegral() /*sequenceNum*/, + eventTime, readTime, + fdp.ConsumeIntegral() /*deviceId*/, + AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, + fdp.ConsumeIntegral() /*policyFlags*/, + AKEY_EVENT_ACTION_DOWN, + fdp.ConsumeIntegral() /*flags*/, AKEYCODE_HOME, + fdp.ConsumeIntegral() /*scanCode*/, AMETA_NONE, + fdp.ConsumeIntegral() /*downTime*/); + + mClassifier->notifyKey(&keyArgs); + }, + [&]() -> void { + // SendToNextStage_NotifyMotionArgs + NotifyMotionArgs motionArgs = generateFuzzedMotionArgs(fdp); + mClassifier->notifyMotion(&motionArgs); + }, + [&]() -> void { + // SendToNextStage_NotifySwitchArgs + NotifySwitchArgs switchArgs(fdp.ConsumeIntegral() /*sequenceNum*/, + fdp.ConsumeIntegral() /*eventTime*/, + fdp.ConsumeIntegral() /*policyFlags*/, + fdp.ConsumeIntegral() /*switchValues*/, + fdp.ConsumeIntegral() /*switchMask*/); + + mClassifier->notifySwitch(&switchArgs); + }, + [&]() -> void { + // SendToNextStage_NotifyDeviceResetArgs + NotifyDeviceResetArgs resetArgs(fdp.ConsumeIntegral() /*sequenceNum*/, + fdp.ConsumeIntegral() /*eventTime*/, + fdp.ConsumeIntegral() /*deviceId*/); + + mClassifier->notifyDeviceReset(&resetArgs); + }, + [&]() -> void { + // InputClassifierConverterTest + const NotifyMotionArgs motionArgs = generateFuzzedMotionArgs(fdp); + aidl::android::hardware::input::common::MotionEvent motionEvent = + notifyMotionArgsToHalMotionEvent(motionArgs); + }, + })(); + } + return 0; +} + +} // namespace android -- GitLab From 0f0ca6e181cdacc57d1ba7b14fb205e2604fc76b Mon Sep 17 00:00:00 2001 From: Michael Ensing Date: Tue, 12 Jan 2021 16:13:20 -0800 Subject: [PATCH 0250/1310] Add inputflinger InputReader fuzzer This CL aims to add a fuzzer to test inputflinger's InputReader functionality. Test: Fuzzers tested on a Pixel 3a, and run for approximately 1M iterations each to test stability. Executions/sec vary from ~400-900, depending on the fuzzer. Summary of updates: Coverage improvements: 36.75% to 82.29% Design changes: [1] Provided a mock Implementation for InputReaderInterface and added support for newer APIs. [2] Enabled support for few more Events that helped in discovering new code paths. [3] Added missing APIs and resolved OOB Access crashes. [4] Refactored InputReaderFuzzer to resolve build errors. Signed-off-by: Michael Ensing Change-Id: I8e9c5f78960dba6d84a46ad7ee46963b3be79fec --- .../inputflinger/tests/fuzzers/Android.bp | 10 + .../tests/fuzzers/InputReaderFuzzer.cpp | 279 ++++++++++++++++++ 2 files changed, 289 insertions(+) create mode 100644 services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp diff --git a/services/inputflinger/tests/fuzzers/Android.bp b/services/inputflinger/tests/fuzzers/Android.bp index 05755acf6c..f4ecba2a10 100644 --- a/services/inputflinger/tests/fuzzers/Android.bp +++ b/services/inputflinger/tests/fuzzers/Android.bp @@ -119,6 +119,16 @@ cc_fuzz { ], } +cc_fuzz { + name: "inputflinger_input_reader_fuzzer", + defaults: [ + "inputflinger_fuzz_defaults", + ], + srcs: [ + "InputReaderFuzzer.cpp", + ], +} + cc_fuzz { name: "inputflinger_blocking_queue_fuzzer", defaults: [ diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp new file mode 100644 index 0000000000..f15d871403 --- /dev/null +++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp @@ -0,0 +1,279 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +namespace android { + +constexpr InputDeviceSensorType kInputDeviceSensorType[] = { + InputDeviceSensorType::ACCELEROMETER, + InputDeviceSensorType::MAGNETIC_FIELD, + InputDeviceSensorType::ORIENTATION, + InputDeviceSensorType::GYROSCOPE, + InputDeviceSensorType::LIGHT, + InputDeviceSensorType::PRESSURE, + InputDeviceSensorType::TEMPERATURE, + InputDeviceSensorType::PROXIMITY, + InputDeviceSensorType::GRAVITY, + InputDeviceSensorType::LINEAR_ACCELERATION, + InputDeviceSensorType::ROTATION_VECTOR, + InputDeviceSensorType::RELATIVE_HUMIDITY, + InputDeviceSensorType::AMBIENT_TEMPERATURE, + InputDeviceSensorType::MAGNETIC_FIELD_UNCALIBRATED, + InputDeviceSensorType::GAME_ROTATION_VECTOR, + InputDeviceSensorType::GYROSCOPE_UNCALIBRATED, + InputDeviceSensorType::SIGNIFICANT_MOTION, +}; + +class FuzzInputReader : public InputReaderInterface { +public: + FuzzInputReader(std::shared_ptr fuzzEventHub, + const sp& fuzzPolicy, + InputListenerInterface& fuzzListener) { + reader = std::make_unique(fuzzEventHub, fuzzPolicy, fuzzListener); + } + + void dump(std::string& dump) { reader->dump(dump); } + + void monitor() { reader->monitor(); } + + bool isInputDeviceEnabled(int32_t deviceId) { return reader->isInputDeviceEnabled(deviceId); } + + status_t start() { return reader->start(); } + + status_t stop() { return reader->stop(); } + + std::vector getInputDevices() const { return reader->getInputDevices(); } + + int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, int32_t scanCode) { + return reader->getScanCodeState(deviceId, sourceMask, scanCode); + } + + int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, int32_t keyCode) { + return reader->getKeyCodeState(deviceId, sourceMask, keyCode); + } + + int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) { + return reader->getSwitchState(deviceId, sourceMask, sw); + } + + void toggleCapsLockState(int32_t deviceId) { reader->toggleCapsLockState(deviceId); } + + bool hasKeys(int32_t deviceId, uint32_t sourceMask, const std::vector& keyCodes, + uint8_t* outFlags) { + return reader->hasKeys(deviceId, sourceMask, keyCodes, outFlags); + } + + void requestRefreshConfiguration(uint32_t changes) { + reader->requestRefreshConfiguration(changes); + } + + void vibrate(int32_t deviceId, const VibrationSequence& sequence, ssize_t repeat, + int32_t token) { + reader->vibrate(deviceId, sequence, repeat, token); + } + + void cancelVibrate(int32_t deviceId, int32_t token) { reader->cancelVibrate(deviceId, token); } + + bool isVibrating(int32_t deviceId) { return reader->isVibrating(deviceId); } + + std::vector getVibratorIds(int32_t deviceId) { + return reader->getVibratorIds(deviceId); + } + + std::optional getBatteryCapacity(int32_t deviceId) { + return reader->getBatteryCapacity(deviceId); + } + + std::optional getBatteryStatus(int32_t deviceId) { + return reader->getBatteryStatus(deviceId); + } + + std::vector getLights(int32_t deviceId) { + return reader->getLights(deviceId); + } + + std::vector getSensors(int32_t deviceId) { + return reader->getSensors(deviceId); + } + + bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) { + return reader->canDispatchToDisplay(deviceId, displayId); + } + + bool enableSensor(int32_t deviceId, InputDeviceSensorType sensorType, + std::chrono::microseconds samplingPeriod, + std::chrono::microseconds maxBatchReportLatency) { + return reader->enableSensor(deviceId, sensorType, samplingPeriod, maxBatchReportLatency); + } + + void disableSensor(int32_t deviceId, InputDeviceSensorType sensorType) { + return reader->disableSensor(deviceId, sensorType); + } + + void flushSensor(int32_t deviceId, InputDeviceSensorType sensorType) { + return reader->flushSensor(deviceId, sensorType); + } + + bool setLightColor(int32_t deviceId, int32_t lightId, int32_t color) { + return reader->setLightColor(deviceId, lightId, color); + } + + bool setLightPlayerId(int32_t deviceId, int32_t lightId, int32_t playerId) { + return reader->setLightPlayerId(deviceId, lightId, playerId); + } + + std::optional getLightColor(int32_t deviceId, int32_t lightId) { + return reader->getLightColor(deviceId, lightId); + } + + std::optional getLightPlayerId(int32_t deviceId, int32_t lightId) { + return reader->getLightPlayerId(deviceId, lightId); + } + + int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const { + return reader->getKeyCodeForKeyLocation(deviceId, locationKeyCode); + } + +private: + std::unique_ptr reader; +}; + +extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { + std::shared_ptr fdp = std::make_shared(data, size); + + FuzzInputListener fuzzListener; + sp fuzzPolicy = sp::make(fdp); + std::shared_ptr fuzzEventHub = std::make_shared(fdp); + std::unique_ptr reader = + std::make_unique(fuzzEventHub, fuzzPolicy, fuzzListener); + fuzzEventHub->addEvents(fdp); + size_t patternCount = fdp->ConsumeIntegralInRange(1, 260); + VibrationSequence pattern(patternCount); + for (size_t i = 0; i < patternCount; ++i) { + VibrationElement element(i); + element.addChannel(fdp->ConsumeIntegral() /* vibratorId */, + fdp->ConsumeIntegral() /* amplitude */); + pattern.addElement(element); + } + reader->vibrate(fdp->ConsumeIntegral(), pattern, + fdp->ConsumeIntegral() /*repeat*/, + fdp->ConsumeIntegral() /*token*/); + reader->start(); + + // Loop through mapper operations until randomness is exhausted. + while (fdp->remaining_bytes() > 0) { + fdp->PickValueInArray>({ + [&]() -> void { + std::string dump; + reader->dump(dump); + }, + [&]() -> void { reader->monitor(); }, + [&]() -> void { reader->getInputDevices(); }, + [&]() -> void { reader->isInputDeviceEnabled(fdp->ConsumeIntegral()); }, + [&]() -> void { + reader->getScanCodeState(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + reader->getKeyCodeState(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + reader->getSwitchState(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { reader->toggleCapsLockState(fdp->ConsumeIntegral()); }, + [&]() -> void { + size_t count = fdp->ConsumeIntegralInRange(1, 1024); + std::vector outFlags(count); + std::vector keyCodes; + for (size_t i = 0; i < count; ++i) { + keyCodes.push_back(fdp->ConsumeIntegral()); + } + reader->hasKeys(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), keyCodes, outFlags.data()); + }, + [&]() -> void { + reader->requestRefreshConfiguration(fdp->ConsumeIntegral()); + }, + [&]() -> void { + reader->cancelVibrate(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + reader->canDispatchToDisplay(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + reader->getKeyCodeForKeyLocation(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { reader->getBatteryCapacity(fdp->ConsumeIntegral()); }, + [&]() -> void { reader->getBatteryStatus(fdp->ConsumeIntegral()); }, + [&]() -> void { reader->getLights(fdp->ConsumeIntegral()); }, + [&]() -> void { reader->getSensors(fdp->ConsumeIntegral()); }, + [&]() -> void { + reader->getLightPlayerId(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + reader->getLightColor(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + reader->setLightPlayerId(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + reader->setLightColor(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + reader->flushSensor(fdp->ConsumeIntegral(), + fdp->PickValueInArray( + kInputDeviceSensorType)); + }, + [&]() -> void { + reader->disableSensor(fdp->ConsumeIntegral(), + fdp->PickValueInArray( + kInputDeviceSensorType)); + }, + [&]() -> void { + reader->enableSensor(fdp->ConsumeIntegral(), + fdp->PickValueInArray( + kInputDeviceSensorType), + std::chrono::microseconds(fdp->ConsumeIntegral()), + std::chrono::microseconds(fdp->ConsumeIntegral())); + }, + })(); + } + + reader->stop(); + return 0; +} + +} // namespace android -- GitLab From 0f365abe4ffd82599b186e8ccd91e96fc943351c Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Wed, 3 Aug 2022 15:35:40 -0400 Subject: [PATCH 0251/1310] Remove undefined behavior in getDisplayDecorationSupport I3b46bae068ac3d482881dac96972a40e46581d34 introduced a bug when converting between two different versions of DisplayDecorationSupport. We attempt to set the member fields of an std::optional object before it has a value. Use emplace to insert a value into the object. Unfortunately, fixing this reveals b/241278870, which is a serious regression. On the other hand, the current checked in code results in undefined behavior, so prevent that. Once b/241278870 is fixed, it will be simple to remove the check for 'false'. Bug: 241277093 Test: manual Change-Id: I53a56b792d99bb72d49d32b5d8f071353dae1b41 --- libs/gui/SurfaceComposerClient.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index d3658519d7..52217df9d5 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2720,12 +2720,16 @@ std::optional SurfaceComposerClient::getDisplayDecorat ComposerServiceAIDL::getComposerService()->getDisplayDecorationSupport(displayToken, &gsupport); std::optional support; - if (status.isOk() && gsupport.has_value()) { - support->format = static_cast( - gsupport->format); - support->alphaInterpretation = + // TODO (b/241277093): Remove `false && ` once b/241278870 is fixed. + if (false && status.isOk() && gsupport.has_value()) { + support.emplace(DisplayDecorationSupport{ + .format = + static_cast( + gsupport->format), + .alphaInterpretation = static_cast( - gsupport->alphaInterpretation); + gsupport->alphaInterpretation) + }); } return support; } -- GitLab From 9a999f2c2edaa2079e33141e938dbb107f3dd172 Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Tue, 30 Aug 2022 15:45:14 -0700 Subject: [PATCH 0252/1310] Add android.hardware.graphics.allocator-ndk wrapper. Bug: 243429120 Test: builds Change-Id: I8e0c9529e069d8ea879d3009c45ccb98a4b78413 --- libs/ui/Android.bp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp index 58e42a0b76..d33dd34d4e 100644 --- a/libs/ui/Android.bp +++ b/libs/ui/Android.bp @@ -152,6 +152,7 @@ cc_library_shared { ], defaults: [ + "android.hardware.graphics.allocator-ndk_shared", "android.hardware.graphics.common-ndk_shared", "libui-defaults", // Uncomment the following line to enable VALIDATE_REGIONS traces @@ -162,7 +163,6 @@ cc_library_shared { "android.hardware.graphics.allocator@2.0", "android.hardware.graphics.allocator@3.0", "android.hardware.graphics.allocator@4.0", - "android.hardware.graphics.allocator-V1-ndk", "android.hardware.graphics.common@1.2", "android.hardware.graphics.mapper@2.0", "android.hardware.graphics.mapper@2.1", @@ -180,7 +180,6 @@ cc_library_shared { export_shared_lib_headers: [ "android.hardware.graphics.common@1.2", - "android.hardware.graphics.common-V3-ndk", "android.hardware.graphics.mapper@4.0", "libgralloctypes", ], -- GitLab From c73be48cb36d424d22124a40597aeac7ee9e6476 Mon Sep 17 00:00:00 2001 From: Chavi Weingarten Date: Wed, 31 Aug 2022 16:55:07 +0000 Subject: [PATCH 0253/1310] Use UIDFaker to ensure Credentials_test cleans up setting UID Credntials_test currently sets the UID back to ROOT in the teardown of the class. It also doesn't ensure the UIDs are set back after each call to seteuid. This seems to result in a race where the UID is not updated before calling the next test. This may lead to failures in later tests if the UID is not updated in time. This fix also exposes a bug in the CredentialsTest where it verifes that Shell UID doesn't have privilege access to SF, but that's actually incorrect since Shell does have ACCESS_SURFACE_FLINGER permission so it can call the privileged APIs. Test: CredentialsTest Bug: 243597796 Change-Id: I515f802aaedf1ea3dfc345af54e4d7383335bbb7 --- .../surfaceflinger/tests/Credentials_test.cpp | 142 +++++++++--------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp index 353b8139ca..775de4a8fe 100644 --- a/services/surfaceflinger/tests/Credentials_test.cpp +++ b/services/surfaceflinger/tests/Credentials_test.cpp @@ -55,19 +55,12 @@ const String8 SURFACE_NAME("Test Surface Name"); #pragma clang diagnostic ignored "-Wconversion" class CredentialsTest : public ::testing::Test { protected: - void SetUp() override { - // Start the tests as root. - seteuid(AID_ROOT); - - ASSERT_NO_FATAL_FAILURE(initClient()); - } + void SetUp() override { ASSERT_NO_FATAL_FAILURE(initClient()); } void TearDown() override { mComposerClient->dispose(); mBGSurfaceControl.clear(); mComposerClient.clear(); - // Finish the tests as root. - seteuid(AID_ROOT); } sp mDisplay; @@ -101,31 +94,6 @@ protected: t.setLayer(mBGSurfaceControl, INT_MAX - 3).show(mBGSurfaceControl).apply()); } - /** - * Sets UID to imitate Graphic's process. - */ - void setGraphicsUID() { - seteuid(AID_ROOT); - seteuid(AID_GRAPHICS); - } - - /** - * Sets UID to imitate System's process. - */ - void setSystemUID() { - seteuid(AID_ROOT); - seteuid(AID_SYSTEM); - } - - /** - * Sets UID to imitate a process that doesn't have any special privileges in - * our code. - */ - void setBinUID() { - seteuid(AID_ROOT); - seteuid(AID_BIN); - } - /** * Template function the check a condition for different types of users: root * graphics, system, and non-supported user. Root, graphics, and system should @@ -134,24 +102,34 @@ protected: template void checkWithPrivileges(std::function condition, T privilegedValue, T unprivilegedValue) { // Check with root. - seteuid(AID_ROOT); - ASSERT_EQ(privilegedValue, condition()); + { + UIDFaker f(AID_SYSTEM); + ASSERT_EQ(privilegedValue, condition()); + } // Check as a Graphics user. - setGraphicsUID(); - ASSERT_EQ(privilegedValue, condition()); + { + UIDFaker f(AID_GRAPHICS); + ASSERT_EQ(privilegedValue, condition()); + } // Check as a system user. - setSystemUID(); - ASSERT_EQ(privilegedValue, condition()); + { + UIDFaker f(AID_SYSTEM); + ASSERT_EQ(privilegedValue, condition()); + } // Check as a non-supported user. - setBinUID(); - ASSERT_EQ(unprivilegedValue, condition()); + { + UIDFaker f(AID_BIN); + ASSERT_EQ(unprivilegedValue, condition()); + } // Check as shell since shell has some additional permissions - seteuid(AID_SHELL); - ASSERT_EQ(unprivilegedValue, condition()); + { + UIDFaker f(AID_SHELL); + ASSERT_EQ(privilegedValue, condition()); + } } }; @@ -160,17 +138,23 @@ TEST_F(CredentialsTest, ClientInitTest) { ASSERT_NO_FATAL_FAILURE(initClient()); // Graphics can init the client. - setGraphicsUID(); - ASSERT_NO_FATAL_FAILURE(initClient()); + { + UIDFaker f(AID_GRAPHICS); + ASSERT_NO_FATAL_FAILURE(initClient()); + } // System can init the client. - setSystemUID(); - ASSERT_NO_FATAL_FAILURE(initClient()); + { + UIDFaker f(AID_SYSTEM); + ASSERT_NO_FATAL_FAILURE(initClient()); + } // Anyone else can init the client. - setBinUID(); - mComposerClient = sp::make(); - ASSERT_NO_FATAL_FAILURE(initClient()); + { + UIDFaker f(AID_BIN); + mComposerClient = sp::make(); + ASSERT_NO_FATAL_FAILURE(initClient()); + } } TEST_F(CredentialsTest, GetBuiltInDisplayAccessTest) { @@ -184,7 +168,7 @@ TEST_F(CredentialsTest, GetBuiltInDisplayAccessTest) { TEST_F(CredentialsTest, AllowedGetterMethodsTest) { // The following methods are tested with a UID that is not root, graphics, // or system, to show that anyone can access them. - setBinUID(); + UIDFaker f(AID_BIN); const auto display = SurfaceComposerClient::getInternalDisplayToken(); ASSERT_TRUE(display != nullptr); @@ -253,24 +237,34 @@ TEST_F(CredentialsTest, CreateDisplayTest) { }; // Check with root. - seteuid(AID_ROOT); - ASSERT_FALSE(condition()); + { + UIDFaker f(AID_ROOT); + ASSERT_FALSE(condition()); + } // Check as a Graphics user. - setGraphicsUID(); - ASSERT_TRUE(condition()); + { + UIDFaker f(AID_GRAPHICS); + ASSERT_TRUE(condition()); + } // Check as a system user. - setSystemUID(); - ASSERT_TRUE(condition()); + { + UIDFaker f(AID_SYSTEM); + ASSERT_TRUE(condition()); + } // Check as a non-supported user. - setBinUID(); - ASSERT_FALSE(condition()); + { + UIDFaker f(AID_BIN); + ASSERT_FALSE(condition()); + } // Check as shell since shell has some additional permissions - seteuid(AID_SHELL); - ASSERT_FALSE(condition()); + { + UIDFaker f(AID_SHELL); + ASSERT_FALSE(condition()); + } condition = [=]() { sp testDisplay = SurfaceComposerClient::createDisplay(DISPLAY_NAME, false); @@ -315,21 +309,27 @@ TEST_F(CredentialsTest, GetLayerDebugInfo) { // Historically, only root and shell can access the getLayerDebugInfo which // is called when we call dumpsys. I don't see a reason why we should change this. std::vector outLayers; + binder::Status status = binder::Status::ok(); // Check with root. - seteuid(AID_ROOT); - binder::Status status = sf->getLayerDebugInfo(&outLayers); - ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); + { + UIDFaker f(AID_ROOT); + status = sf->getLayerDebugInfo(&outLayers); + ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); + } // Check as a shell. - seteuid(AID_SHELL); - status = sf->getLayerDebugInfo(&outLayers); - ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); + { + UIDFaker f(AID_SHELL); + status = sf->getLayerDebugInfo(&outLayers); + ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status)); + } // Check as anyone else. - seteuid(AID_ROOT); - seteuid(AID_BIN); - status = sf->getLayerDebugInfo(&outLayers); - ASSERT_EQ(PERMISSION_DENIED, statusTFromBinderStatus(status)); + { + UIDFaker f(AID_BIN); + status = sf->getLayerDebugInfo(&outLayers); + ASSERT_EQ(PERMISSION_DENIED, statusTFromBinderStatus(status)); + } } TEST_F(CredentialsTest, IsWideColorDisplayBasicCorrectness) { -- GitLab From 44e612e8e7ced68ac457602a9c6988a9372c136f Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Tue, 30 Aug 2022 22:02:37 +0000 Subject: [PATCH 0254/1310] Remove rate limiter from adpf cpu hint session Remove old rate limiter code as it's not used and no longer needed Bug: b/244358432 Test: atest libsurfaceflinger_unittest:libsurfaceflinger_unittest.AidlPowerHalWrapperTest Change-Id: I531280bd8f2d80137839dd9a12bc88d21a213f95 --- .../DisplayHardware/PowerAdvisor.cpp | 43 ++---------- .../DisplayHardware/PowerAdvisor.h | 13 ---- .../unittests/AidlPowerHalWrapperTest.cpp | 67 ++++--------------- 3 files changed, 18 insertions(+), 105 deletions(-) diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp index 566537b576..d5309a986a 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp @@ -640,9 +640,6 @@ AidlPowerHalWrapper::AidlPowerHalWrapper(sp powerHal) : mPowerHal(std::m } mSupportsPowerHint = checkPowerHintSessionSupported(); - - // Currently set to 0 to disable rate limiter by default - mAllowedActualDeviation = base::GetIntProperty("debug.sf.allowed_actual_deviation", 0); } AidlPowerHalWrapper::~AidlPowerHalWrapper() { @@ -758,21 +755,6 @@ void AidlPowerHalWrapper::setTargetWorkDuration(int64_t targetDuration) { } } -bool AidlPowerHalWrapper::shouldReportActualDurations() { - // Report if we have never reported before or will go stale next frame - if (!mLastActualDurationSent.has_value() || - (mLastTargetDurationSent + systemTime() - mLastActualReportTimestamp) > - kStaleTimeout.count()) { - return true; - } - - if (!mActualDuration.has_value()) { - return false; - } - // Report if the change in actual duration exceeds the threshold - return abs(*mActualDuration - *mLastActualDurationSent) > mAllowedActualDeviation; -} - void AidlPowerHalWrapper::sendActualWorkDuration(int64_t actualDuration, nsecs_t timestamp) { ATRACE_CALL(); @@ -801,22 +783,13 @@ void AidlPowerHalWrapper::sendActualWorkDuration(int64_t actualDuration, nsecs_t " with error: %" PRId64, reportedDuration, mLastTargetDurationSent, reportedDuration - mLastTargetDurationSent); - // This rate limiter queues similar duration reports to the powerhal into - // batches to avoid excessive binder calls. The criteria to send a given batch - // are outlined in shouldReportActualDurationsNow() - if (shouldReportActualDurations()) { - ALOGV("Sending hint update batch"); - mLastActualReportTimestamp = systemTime(); - auto ret = mPowerHintSession->reportActualWorkDuration(mPowerHintQueue); - if (!ret.isOk()) { - ALOGW("Failed to report actual work durations with error: %s", - ret.exceptionMessage().c_str()); - mShouldReconnectHal = true; - } - mPowerHintQueue.clear(); - // We save the actual duration here for rate limiting - mLastActualDurationSent = actualDuration; + auto ret = mPowerHintSession->reportActualWorkDuration(mPowerHintQueue); + if (!ret.isOk()) { + ALOGW("Failed to report actual work durations with error: %s", + ret.exceptionMessage().c_str()); + mShouldReconnectHal = true; } + mPowerHintQueue.clear(); } bool AidlPowerHalWrapper::shouldReconnectHAL() { @@ -831,10 +804,6 @@ std::optional AidlPowerHalWrapper::getTargetWorkDuration() { return mTargetDuration; } -void AidlPowerHalWrapper::setAllowedActualDeviation(nsecs_t allowedDeviation) { - mAllowedActualDeviation = allowedDeviation; -} - const bool AidlPowerHalWrapper::sTraceHintSessionData = base::GetBoolProperty(std::string("debug.sf.trace_hint_sessions"), false); diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h index 53db612357..776d043083 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h @@ -305,10 +305,6 @@ private: bool checkPowerHintSessionSupported(); void closePowerHintSession(); - bool shouldReportActualDurations(); - - // Used for testing - void setAllowedActualDeviation(nsecs_t); const sp mPowerHal = nullptr; bool mHasExpensiveRendering = false; @@ -328,19 +324,10 @@ private: // The list of thread ids, stored so we can restart the session from this class if needed std::vector mPowerHintThreadIds; bool mSupportsPowerHint = false; - // Keep track of the last messages sent for rate limiter change detection - std::optional mLastActualDurationSent; - // Timestamp of the last report we sent, used to avoid stale sessions - nsecs_t mLastActualReportTimestamp = 0; nsecs_t mLastTargetDurationSent = kDefaultTarget.count(); - // Max amount the error term can vary without causing an actual value report - nsecs_t mAllowedActualDeviation = -1; // Whether we should emit ATRACE_INT data for hint sessions static const bool sTraceHintSessionData; static constexpr const std::chrono::nanoseconds kDefaultTarget = 16ms; - // Amount of time after the last message was sent before the session goes stale - // actually 100ms but we use 80 here to give some slack - static constexpr const std::chrono::nanoseconds kStaleTimeout = 80ms; }; } // namespace impl diff --git a/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp b/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp index 69d30c4c55..ab38b294c5 100644 --- a/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp +++ b/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp @@ -50,10 +50,8 @@ protected: sp> mMockHal = nullptr; sp> mMockSession = nullptr; void verifyAndClearExpectations(); - void sendActualWorkDurationGroup(std::vector durations, - std::chrono::nanoseconds sleepBeforeLastSend); - std::chrono::nanoseconds mAllowedDeviation; - std::chrono::nanoseconds mStaleTimeout; + void sendActualWorkDurationGroup(std::vector durations); + static constexpr std::chrono::duration kStaleTimeout = 100ms; }; void AidlPowerHalWrapperTest::SetUp() { @@ -61,9 +59,6 @@ void AidlPowerHalWrapperTest::SetUp() { mMockSession = sp>::make(); ON_CALL(*mMockHal.get(), getHintSessionPreferredRate(_)).WillByDefault(Return(Status::ok())); mWrapper = std::make_unique(mMockHal); - mWrapper->setAllowedActualDeviation(std::chrono::nanoseconds{10ms}.count()); - mAllowedDeviation = std::chrono::nanoseconds{mWrapper->mAllowedActualDeviation}; - mStaleTimeout = AidlPowerHalWrapper::kStaleTimeout; } void AidlPowerHalWrapperTest::verifyAndClearExpectations() { @@ -71,12 +66,8 @@ void AidlPowerHalWrapperTest::verifyAndClearExpectations() { Mock::VerifyAndClearExpectations(mMockSession.get()); } -void AidlPowerHalWrapperTest::sendActualWorkDurationGroup( - std::vector durations, std::chrono::nanoseconds sleepBeforeLastSend) { +void AidlPowerHalWrapperTest::sendActualWorkDurationGroup(std::vector durations) { for (size_t i = 0; i < durations.size(); i++) { - if (i == durations.size() - 1) { - std::this_thread::sleep_for(sleepBeforeLastSend); - } auto duration = durations[i]; mWrapper->sendActualWorkDuration(duration.durationNanos, duration.timeStampNanos); } @@ -206,58 +197,24 @@ TEST_F(AidlPowerHalWrapperTest, sendActualWorkDuration) { // 100ms const std::vector>, bool>> testCases = {{{{-1ms, 100}}, false}, - {{{100ms - (mAllowedDeviation / 2), 100}}, false}, - {{{100ms + (mAllowedDeviation / 2), 100}}, false}, - {{{100ms + (mAllowedDeviation + 1ms), 100}}, true}, - {{{100ms - (mAllowedDeviation + 1ms), 100}}, true}, + {{{50ms, 100}}, true}, {{{100ms, 100}, {200ms, 200}}, true}, {{{100ms, 500}, {100ms, 600}, {3ms, 600}}, true}}; for (const auto& test : testCases) { // reset actual duration - sendActualWorkDurationGroup({base}, mStaleTimeout); + sendActualWorkDurationGroup({base}); auto raw = test.first; std::vector durations(raw.size()); std::transform(raw.begin(), raw.end(), durations.begin(), [](auto d) { return toWorkDuration(d); }); - EXPECT_CALL(*mMockSession.get(), reportActualWorkDuration(durations)) - .Times(test.second ? 1 : 0); - sendActualWorkDurationGroup(durations, 0ms); - verifyAndClearExpectations(); - } -} - -TEST_F(AidlPowerHalWrapperTest, sendActualWorkDuration_exceedsStaleTime) { - ASSERT_TRUE(mWrapper->supportsPowerHintSession()); - - std::vector threadIds = {1, 2}; - mWrapper->setPowerHintSessionThreadIds(threadIds); - EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _)) - .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok()))); - ASSERT_TRUE(mWrapper->startPowerHintSession()); - verifyAndClearExpectations(); - - auto base = toWorkDuration(100ms, 0); - // test cases with actual work durations and whether it should update hint against baseline - // 100ms - const std::vector>, - std::chrono::nanoseconds, bool>> - testCases = {{{{100ms, 100}}, mStaleTimeout, true}, - {{{100ms + (mAllowedDeviation / 2), 100}}, mStaleTimeout, true}, - {{{100ms, 100}}, mStaleTimeout / 2, false}}; - - for (const auto& test : testCases) { - // reset actual duration - sendActualWorkDurationGroup({base}, mStaleTimeout); - - auto raw = std::get<0>(test); - std::vector durations(raw.size()); - std::transform(raw.begin(), raw.end(), durations.begin(), - [](auto d) { return toWorkDuration(d); }); - EXPECT_CALL(*mMockSession.get(), reportActualWorkDuration(durations)) - .Times(std::get<2>(test) ? 1 : 0); - sendActualWorkDurationGroup(durations, std::get<1>(test)); + for (auto& duration : durations) { + EXPECT_CALL(*mMockSession.get(), + reportActualWorkDuration(std::vector{duration})) + .Times(test.second ? 1 : 0); + } + sendActualWorkDurationGroup(durations); verifyAndClearExpectations(); } } @@ -275,7 +232,7 @@ TEST_F(AidlPowerHalWrapperTest, sendActualWorkDuration_shouldReconnectOnError) { duration.durationNanos = 1; EXPECT_CALL(*mMockSession.get(), reportActualWorkDuration(_)) .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_ILLEGAL_STATE))); - sendActualWorkDurationGroup({duration}, 0ms); + sendActualWorkDurationGroup({duration}); EXPECT_TRUE(mWrapper->shouldReconnectHAL()); } -- GitLab From 2fa850143f1632247d02229f671d6a9475eab906 Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Tue, 30 Aug 2022 22:38:45 +0000 Subject: [PATCH 0255/1310] Refactor adpf cpu hints to use TimePoint and Duration Replace nsecs_t in adpf cpu hint sessions in PowerAdvisor with new TimePoints and Durations Additionally, added TimePoint::now() to make getting current time cleaner Bug: b/244358432 Test: atest libsurfaceflinger_unittest Change-Id: I8b4d6f3de8204aa34cd9d8687febfde580deea92 --- .../CompositionEngine/src/Display.cpp | 8 +- .../tests/MockPowerAdvisor.h | 21 +-- .../DisplayHardware/PowerAdvisor.cpp | 176 +++++++++--------- .../DisplayHardware/PowerAdvisor.h | 139 +++++++------- .../Scheduler/include/scheduler/Time.h | 2 + services/surfaceflinger/SurfaceFlinger.cpp | 23 +-- .../unittests/AidlPowerHalWrapperTest.cpp | 13 +- .../tests/unittests/PowerAdvisorTest.cpp | 68 +++---- .../DisplayHardware/MockAidlPowerHalWrapper.h | 5 +- .../mock/DisplayHardware/MockPowerAdvisor.h | 21 +-- 10 files changed, 240 insertions(+), 236 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp index d359424801..0ebbcd0103 100644 --- a/services/surfaceflinger/CompositionEngine/src/Display.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp @@ -243,7 +243,7 @@ bool Display::chooseCompositionStrategy( return false; } - const nsecs_t startTime = systemTime(); + const TimePoint startTime = TimePoint::now(); // Get any composition changes requested by the HWC device, and apply them. std::optional changes; @@ -261,7 +261,7 @@ bool Display::chooseCompositionStrategy( } if (isPowerHintSessionEnabled()) { - mPowerAdvisor->setHwcValidateTiming(mId, startTime, systemTime()); + mPowerAdvisor->setHwcValidateTiming(mId, startTime, TimePoint::now()); mPowerAdvisor->setRequiresClientComposition(mId, requiresClientComposition); } @@ -365,7 +365,7 @@ compositionengine::Output::FrameFences Display::presentAndGetFrameFences() { auto& hwc = getCompositionEngine().getHwComposer(); - const nsecs_t startTime = systemTime(); + const TimePoint startTime = TimePoint::now(); if (isPowerHintSessionEnabled()) { if (!getCompositionEngine().getHwComposer().getComposer()->isSupported( @@ -379,7 +379,7 @@ compositionengine::Output::FrameFences Display::presentAndGetFrameFences() { getState().previousPresentFence); if (isPowerHintSessionEnabled()) { - mPowerAdvisor->setHwcPresentTiming(mId, startTime, systemTime()); + mPowerAdvisor->setHwcPresentTiming(mId, startTime, TimePoint::now()); } fences.presentFence = hwc.getPresentFence(*halDisplayIdOpt); diff --git a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h index c8bd5e436c..e220541461 100644 --- a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h +++ b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h @@ -38,7 +38,7 @@ public: MOCK_METHOD(bool, usePowerHintSession, (), (override)); MOCK_METHOD(bool, supportsPowerHintSession, (), (override)); MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override)); - MOCK_METHOD(void, setTargetWorkDuration, (int64_t targetDuration), (override)); + MOCK_METHOD(void, setTargetWorkDuration, (Duration targetDuration), (override)); MOCK_METHOD(void, sendActualWorkDuration, (), (override)); MOCK_METHOD(void, sendPredictedWorkDuration, (), (override)); MOCK_METHOD(void, enablePowerHint, (bool enabled), (override)); @@ -46,25 +46,24 @@ public: MOCK_METHOD(void, setGpuFenceTime, (DisplayId displayId, std::unique_ptr&& fenceTime), (override)); MOCK_METHOD(void, setHwcValidateTiming, - (DisplayId displayId, nsecs_t valiateStartTime, nsecs_t validateEndTime), + (DisplayId displayId, TimePoint validateStartTime, TimePoint validateEndTime), (override)); MOCK_METHOD(void, setHwcPresentTiming, - (DisplayId displayId, nsecs_t presentStartTime, nsecs_t presentEndTime), + (DisplayId displayId, TimePoint presentStartTime, TimePoint presentEndTime), (override)); MOCK_METHOD(void, setSkippedValidate, (DisplayId displayId, bool skipped), (override)); MOCK_METHOD(void, setRequiresClientComposition, (DisplayId displayId, bool requiresClientComposition), (override)); - MOCK_METHOD(void, setExpectedPresentTime, (nsecs_t expectedPresentTime), (override)); - MOCK_METHOD(void, setSfPresentTiming, (nsecs_t presentFenceTime, nsecs_t presentEndTime), + MOCK_METHOD(void, setExpectedPresentTime, (TimePoint expectedPresentTime), (override)); + MOCK_METHOD(void, setSfPresentTiming, (TimePoint presentFenceTime, TimePoint presentEndTime), (override)); MOCK_METHOD(void, setHwcPresentDelayedTime, - (DisplayId displayId, - std::chrono::steady_clock::time_point earliestFrameStartTime)); - MOCK_METHOD(void, setFrameDelay, (nsecs_t frameDelayDuration), (override)); - MOCK_METHOD(void, setCommitStart, (nsecs_t commitStartTime), (override)); - MOCK_METHOD(void, setCompositeEnd, (nsecs_t compositeEndtime), (override)); + (DisplayId displayId, TimePoint earliestFrameStartTime)); + MOCK_METHOD(void, setFrameDelay, (Duration frameDelayDuration), (override)); + MOCK_METHOD(void, setCommitStart, (TimePoint commitStartTime), (override)); + MOCK_METHOD(void, setCompositeEnd, (TimePoint compositeEndTime), (override)); MOCK_METHOD(void, setDisplays, (std::vector & displayIds), (override)); - MOCK_METHOD(void, setTotalFrameTargetWorkDuration, (int64_t targetDuration), (override)); + MOCK_METHOD(void, setTotalFrameTargetWorkDuration, (Duration targetDuration), (override)); }; } // namespace mock diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp index d5309a986a..cb2c8c53ef 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp @@ -59,8 +59,6 @@ using android::hardware::power::IPowerHintSession; using android::hardware::power::Mode; using android::hardware::power::WorkDuration; -using scheduler::OneShotTimer; - PowerAdvisor::~PowerAdvisor() = default; namespace { @@ -196,7 +194,7 @@ bool PowerAdvisor::isPowerHintSessionRunning() { return mPowerHintSessionRunning; } -void PowerAdvisor::setTargetWorkDuration(int64_t targetDuration) { +void PowerAdvisor::setTargetWorkDuration(Duration targetDuration) { if (!usePowerHintSession()) { ALOGV("Power hint session target duration cannot be set, skipping"); return; @@ -215,13 +213,13 @@ void PowerAdvisor::sendActualWorkDuration() { ALOGV("Actual work duration power hint cannot be sent, skipping"); return; } - const std::optional actualDuration = estimateWorkDuration(false); + const std::optional actualDuration = estimateWorkDuration(false); if (actualDuration.has_value()) { std::lock_guard lock(mPowerHalMutex); HalWrapper* const halWrapper = getPowerHal(); if (halWrapper != nullptr) { - halWrapper->sendActualWorkDuration(*actualDuration + kTargetSafetyMargin.count(), - systemTime()); + halWrapper->sendActualWorkDuration(*actualDuration + kTargetSafetyMargin, + TimePoint::now()); } } } @@ -232,14 +230,14 @@ void PowerAdvisor::sendPredictedWorkDuration() { return; } - const std::optional predictedDuration = estimateWorkDuration(true); + const std::optional predictedDuration = estimateWorkDuration(true); if (predictedDuration.has_value()) { std::lock_guard lock(mPowerHalMutex); HalWrapper* const halWrapper = getPowerHal(); if (halWrapper != nullptr) { - halWrapper->sendActualWorkDuration(*predictedDuration + kTargetSafetyMargin.count(), - systemTime()); + halWrapper->sendActualWorkDuration(*predictedDuration + kTargetSafetyMargin, + TimePoint::now()); } } } @@ -281,22 +279,22 @@ void PowerAdvisor::setGpuFenceTime(DisplayId displayId, std::unique_ptr& displayIds) { mDisplayIds = displayIds; } -void PowerAdvisor::setTotalFrameTargetWorkDuration(nsecs_t targetDuration) { +void PowerAdvisor::setTotalFrameTargetWorkDuration(Duration targetDuration) { mTotalFrameTargetDuration = targetDuration; } std::vector PowerAdvisor::getOrderedDisplayIds( - std::optional DisplayTimingData::*sortBy) { + std::optional DisplayTimingData::*sortBy) { std::vector sortedDisplays; std::copy_if(mDisplayIds.begin(), mDisplayIds.end(), std::back_inserter(sortedDisplays), [&](DisplayId id) { @@ -360,33 +356,34 @@ std::vector PowerAdvisor::getOrderedDisplayIds( return sortedDisplays; } -std::optional PowerAdvisor::estimateWorkDuration(bool earlyHint) { +std::optional PowerAdvisor::estimateWorkDuration(bool earlyHint) { if (earlyHint && (!mExpectedPresentTimes.isFull() || !mCommitStartTimes.isFull())) { return std::nullopt; } // Tracks when we finish presenting to hwc - nsecs_t estimatedEndTime = mCommitStartTimes[0]; + TimePoint estimatedEndTime = mCommitStartTimes[0]; // How long we spent this frame not doing anything, waiting for fences or vsync - nsecs_t idleDuration = 0; + Duration idleDuration = 0ns; // Most recent previous gpu end time in the current frame, probably from a prior display, used // as the start time for the next gpu operation if it ran over time since it probably blocked - std::optional previousValidGpuEndTime; + std::optional previousValidGpuEndTime; // The currently estimated gpu end time for the frame, // used to accumulate gpu time as we iterate over the active displays - std::optional estimatedGpuEndTime; + std::optional estimatedGpuEndTime; // If we're predicting at the start of the frame, we use last frame as our reference point // If we're predicting at the end of the frame, we use the current frame as a reference point - nsecs_t referenceFrameStartTime = (earlyHint ? mCommitStartTimes[-1] : mCommitStartTimes[0]); + TimePoint referenceFrameStartTime = (earlyHint ? mCommitStartTimes[-1] : mCommitStartTimes[0]); // When the prior frame should be presenting to the display // If we're predicting at the start of the frame, we use last frame's expected present time // If we're predicting at the end of the frame, the present fence time is already known - nsecs_t lastFramePresentTime = (earlyHint ? mExpectedPresentTimes[-1] : mLastPresentFenceTime); + TimePoint lastFramePresentTime = + (earlyHint ? mExpectedPresentTimes[-1] : mLastPresentFenceTime); // The timing info for the previously calculated display, if there was one std::optional previousDisplayReferenceTiming; @@ -427,10 +424,10 @@ std::optional PowerAdvisor::estimateWorkDuration(bool earlyHint) { // Track how long we spent waiting for the fence, can be excluded from the timing estimate idleDuration += estimatedTiming.probablyWaitsForPresentFence ? lastFramePresentTime - estimatedTiming.presentFenceWaitStartTime - : 0; + : 0ns; // Track how long we spent waiting to present, can be excluded from the timing estimate - idleDuration += earlyHint ? 0 : referenceTiming.hwcPresentDelayDuration; + idleDuration += earlyHint ? 0ns : referenceTiming.hwcPresentDelayDuration; // Estimate the reference frame's gpu timing auto gpuTiming = displayData.estimateGpuTiming(previousValidGpuEndTime); @@ -438,15 +435,15 @@ std::optional PowerAdvisor::estimateWorkDuration(bool earlyHint) { previousValidGpuEndTime = gpuTiming->startTime + gpuTiming->duration; // Estimate the prediction frame's gpu end time from the reference frame - estimatedGpuEndTime = - std::max(estimatedTiming.hwcPresentStartTime, estimatedGpuEndTime.value_or(0)) + + estimatedGpuEndTime = std::max(estimatedTiming.hwcPresentStartTime, + estimatedGpuEndTime.value_or(TimePoint{0ns})) + gpuTiming->duration; } previousDisplayReferenceTiming = referenceTiming; } - ATRACE_INT64("Idle duration", idleDuration); + ATRACE_INT64("Idle duration", idleDuration.ns()); - nsecs_t estimatedFlingerEndTime = earlyHint ? estimatedEndTime : mLastSfPresentEndTime; + TimePoint estimatedFlingerEndTime = earlyHint ? estimatedEndTime : mLastSfPresentEndTime; // Don't count time spent idly waiting in the estimate as we could do more work in that time estimatedEndTime -= idleDuration; @@ -454,21 +451,22 @@ std::optional PowerAdvisor::estimateWorkDuration(bool earlyHint) { // We finish the frame when both present and the gpu are done, so wait for the later of the two // Also add the frame delay duration since the target did not move while we were delayed - nsecs_t totalDuration = mFrameDelayDuration + - std::max(estimatedEndTime, estimatedGpuEndTime.value_or(0)) - mCommitStartTimes[0]; + Duration totalDuration = mFrameDelayDuration + + std::max(estimatedEndTime, estimatedGpuEndTime.value_or(TimePoint{0ns})) - + mCommitStartTimes[0]; // We finish SurfaceFlinger when post-composition finishes, so add that in here - nsecs_t flingerDuration = + Duration flingerDuration = estimatedFlingerEndTime + mLastPostcompDuration - mCommitStartTimes[0]; // Combine the two timings into a single normalized one - nsecs_t combinedDuration = combineTimingEstimates(totalDuration, flingerDuration); + Duration combinedDuration = combineTimingEstimates(totalDuration, flingerDuration); return std::make_optional(combinedDuration); } -nsecs_t PowerAdvisor::combineTimingEstimates(nsecs_t totalDuration, nsecs_t flingerDuration) { - nsecs_t targetDuration; +Duration PowerAdvisor::combineTimingEstimates(Duration totalDuration, Duration flingerDuration) { + Duration targetDuration{0ns}; { std::lock_guard lock(mPowerHalMutex); targetDuration = *getPowerHal()->getTargetWorkDuration(); @@ -477,17 +475,18 @@ nsecs_t PowerAdvisor::combineTimingEstimates(nsecs_t totalDuration, nsecs_t flin // Normalize total to the flinger target (vsync period) since that's how often we actually send // hints - nsecs_t normalizedTotalDuration = (targetDuration * totalDuration) / *mTotalFrameTargetDuration; + Duration normalizedTotalDuration = Duration::fromNs((targetDuration.ns() * totalDuration.ns()) / + mTotalFrameTargetDuration->ns()); return std::max(flingerDuration, normalizedTotalDuration); } PowerAdvisor::DisplayTimeline PowerAdvisor::DisplayTimeline::estimateTimelineFromReference( - nsecs_t fenceTime, nsecs_t displayStartTime) { + TimePoint fenceTime, TimePoint displayStartTime) { DisplayTimeline estimated; estimated.hwcPresentStartTime = displayStartTime; // We don't predict waiting for vsync alignment yet - estimated.hwcPresentDelayDuration = 0; + estimated.hwcPresentDelayDuration = 0ns; // How long we expect to run before we start waiting for the fence // For now just re-use last frame's post-present duration and assume it will not change much @@ -502,12 +501,11 @@ PowerAdvisor::DisplayTimeline PowerAdvisor::DisplayTimeline::estimateTimelineFro } PowerAdvisor::DisplayTimeline PowerAdvisor::DisplayTimingData::calculateDisplayTimeline( - nsecs_t fenceTime) { + TimePoint fenceTime) { DisplayTimeline timeline; // How long between calling hwc present and trying to wait on the fence - const nsecs_t fenceWaitStartDelay = - (skippedValidate ? kFenceWaitStartDelaySkippedValidate : kFenceWaitStartDelayValidated) - .count(); + const Duration fenceWaitStartDelay = + (skippedValidate ? kFenceWaitStartDelaySkippedValidate : kFenceWaitStartDelayValidated); // Did our reference frame wait for an appropriate vsync before calling into hwc const bool waitedOnHwcPresentTime = hwcPresentDelayedTime.has_value() && @@ -522,7 +520,7 @@ PowerAdvisor::DisplayTimeline PowerAdvisor::DisplayTimingData::calculateDisplayT // How long hwc present was delayed waiting for the next appropriate vsync timeline.hwcPresentDelayDuration = - (waitedOnHwcPresentTime ? *hwcPresentDelayedTime - *hwcPresentStartTime : 0); + (waitedOnHwcPresentTime ? *hwcPresentDelayedTime - *hwcPresentStartTime : 0ns); // When we started waiting for the present fence after calling into hwc present timeline.presentFenceWaitStartTime = timeline.hwcPresentStartTime + timeline.hwcPresentDelayDuration + fenceWaitStartDelay; @@ -537,23 +535,26 @@ PowerAdvisor::DisplayTimeline PowerAdvisor::DisplayTimingData::calculateDisplayT } std::optional PowerAdvisor::DisplayTimingData::estimateGpuTiming( - std::optional previousEnd) { + std::optional previousEndTime) { if (!(usedClientComposition && lastValidGpuStartTime.has_value() && gpuEndFenceTime)) { return std::nullopt; } - const nsecs_t latestGpuStartTime = std::max(previousEnd.value_or(0), *gpuStartTime); - const nsecs_t latestGpuEndTime = gpuEndFenceTime->getSignalTime(); - nsecs_t gpuDuration = 0; - if (latestGpuEndTime != Fence::SIGNAL_TIME_INVALID && - latestGpuEndTime != Fence::SIGNAL_TIME_PENDING) { + const TimePoint latestGpuStartTime = + std::max(previousEndTime.value_or(TimePoint{0ns}), *gpuStartTime); + const nsecs_t gpuEndFenceSignal = gpuEndFenceTime->getSignalTime(); + Duration gpuDuration{0ns}; + if (gpuEndFenceSignal != Fence::SIGNAL_TIME_INVALID && + gpuEndFenceSignal != Fence::SIGNAL_TIME_PENDING) { + const TimePoint latestGpuEndTime = TimePoint::fromNs(gpuEndFenceSignal); + // If we know how long the most recent gpu duration was, use that gpuDuration = latestGpuEndTime - latestGpuStartTime; } else if (lastValidGpuEndTime.has_value()) { // If we don't have the fence data, use the most recent information we do have gpuDuration = *lastValidGpuEndTime - *lastValidGpuStartTime; - if (latestGpuEndTime == Fence::SIGNAL_TIME_PENDING) { + if (gpuEndFenceSignal == Fence::SIGNAL_TIME_PENDING) { // If pending but went over the previous duration, use current time as the end - gpuDuration = std::max(gpuDuration, systemTime() - latestGpuStartTime); + gpuDuration = std::max(gpuDuration, Duration{TimePoint::now() - latestGpuStartTime}); } } return GpuTimeline{.duration = gpuDuration, .startTime = latestGpuStartTime}; @@ -614,15 +615,15 @@ public: bool startPowerHintSession() override { return false; } - void setTargetWorkDuration(int64_t) override {} + void setTargetWorkDuration(Duration) override {} - void sendActualWorkDuration(int64_t, nsecs_t) override {} + void sendActualWorkDuration(Duration, TimePoint) override {} bool shouldReconnectHAL() override { return false; } std::vector getPowerHintSessionThreadIds() override { return std::vector{}; } - std::optional getTargetWorkDuration() override { return std::nullopt; } + std::optional getTargetWorkDuration() override { return std::nullopt; } private: const sp mPowerHal = nullptr; @@ -727,9 +728,9 @@ bool AidlPowerHalWrapper::startPowerHintSession() { ALOGV("Cannot start power hint session, skipping"); return false; } - auto ret = - mPowerHal->createHintSession(getpid(), static_cast(getuid()), - mPowerHintThreadIds, mTargetDuration, &mPowerHintSession); + auto ret = mPowerHal->createHintSession(getpid(), static_cast(getuid()), + mPowerHintThreadIds, mTargetDuration.ns(), + &mPowerHintSession); if (!ret.isOk()) { ALOGW("Failed to start power hint session with error: %s", ret.exceptionToString(ret.exceptionCode()).c_str()); @@ -739,14 +740,14 @@ bool AidlPowerHalWrapper::startPowerHintSession() { return isPowerHintSessionRunning(); } -void AidlPowerHalWrapper::setTargetWorkDuration(int64_t targetDuration) { +void AidlPowerHalWrapper::setTargetWorkDuration(Duration targetDuration) { ATRACE_CALL(); mTargetDuration = targetDuration; - if (sTraceHintSessionData) ATRACE_INT64("Time target", targetDuration); + if (sTraceHintSessionData) ATRACE_INT64("Time target", targetDuration.ns()); if (isPowerHintSessionRunning() && (targetDuration != mLastTargetDurationSent)) { - ALOGV("Sending target time: %" PRId64 "ns", targetDuration); + ALOGV("Sending target time: %" PRId64 "ns", targetDuration.ns()); mLastTargetDurationSent = targetDuration; - auto ret = mPowerHintSession->updateTargetWorkDuration(targetDuration); + auto ret = mPowerHintSession->updateTargetWorkDuration(targetDuration.ns()); if (!ret.isOk()) { ALOGW("Failed to set power hint target work duration with error: %s", ret.exceptionMessage().c_str()); @@ -755,33 +756,32 @@ void AidlPowerHalWrapper::setTargetWorkDuration(int64_t targetDuration) { } } -void AidlPowerHalWrapper::sendActualWorkDuration(int64_t actualDuration, nsecs_t timestamp) { +void AidlPowerHalWrapper::sendActualWorkDuration(Duration actualDuration, TimePoint timestamp) { ATRACE_CALL(); - - if (actualDuration < 0 || !isPowerHintSessionRunning()) { + if (actualDuration < 0ns || !isPowerHintSessionRunning()) { ALOGV("Failed to send actual work duration, skipping"); return; } - const nsecs_t reportedDuration = actualDuration; - - mActualDuration = reportedDuration; + mActualDuration = actualDuration; WorkDuration duration; - duration.durationNanos = reportedDuration; - duration.timeStampNanos = timestamp; + duration.durationNanos = actualDuration.ns(); + duration.timeStampNanos = timestamp.ns(); mPowerHintQueue.push_back(duration); if (sTraceHintSessionData) { - ATRACE_INT64("Measured duration", actualDuration); - ATRACE_INT64("Target error term", actualDuration - mTargetDuration); + ATRACE_INT64("Measured duration", actualDuration.ns()); + ATRACE_INT64("Target error term", Duration{actualDuration - mTargetDuration}.ns()); - ATRACE_INT64("Reported duration", reportedDuration); - ATRACE_INT64("Reported target", mLastTargetDurationSent); - ATRACE_INT64("Reported target error term", reportedDuration - mLastTargetDurationSent); + ATRACE_INT64("Reported duration", actualDuration.ns()); + ATRACE_INT64("Reported target", mLastTargetDurationSent.ns()); + ATRACE_INT64("Reported target error term", + Duration{actualDuration - mLastTargetDurationSent}.ns()); } ALOGV("Sending actual work duration of: %" PRId64 " on reported target: %" PRId64 " with error: %" PRId64, - reportedDuration, mLastTargetDurationSent, reportedDuration - mLastTargetDurationSent); + actualDuration.ns(), mLastTargetDurationSent.ns(), + Duration{actualDuration - mLastTargetDurationSent}.ns()); auto ret = mPowerHintSession->reportActualWorkDuration(mPowerHintQueue); if (!ret.isOk()) { @@ -800,7 +800,7 @@ std::vector AidlPowerHalWrapper::getPowerHintSessionThreadIds() { return mPowerHintThreadIds; } -std::optional AidlPowerHalWrapper::getTargetWorkDuration() { +std::optional AidlPowerHalWrapper::getTargetWorkDuration() { return mTargetDuration; } @@ -814,7 +814,7 @@ PowerAdvisor::HalWrapper* PowerAdvisor::getPowerHal() { // Grab old hint session values before we destroy any existing wrapper std::vector oldPowerHintSessionThreadIds; - std::optional oldTargetWorkDuration; + std::optional oldTargetWorkDuration; if (mHalWrapper != nullptr) { oldPowerHintSessionThreadIds = mHalWrapper->getPowerHintSessionThreadIds(); diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h index 776d043083..1c9d123a1f 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h @@ -27,6 +27,7 @@ #include #include +#include #include #include "../Scheduler/OneShotTimer.h" @@ -53,7 +54,7 @@ public: virtual bool supportsPowerHintSession() = 0; virtual bool isPowerHintSessionRunning() = 0; // Sends a power hint that updates to the target work duration for the frame - virtual void setTargetWorkDuration(nsecs_t targetDuration) = 0; + virtual void setTargetWorkDuration(Duration targetDuration) = 0; // Sends a power hint for the actual known work duration at the end of the frame virtual void sendActualWorkDuration() = 0; // Sends a power hint for the upcoming frame predicted from previous frame timing @@ -65,33 +66,33 @@ public: // Provides PowerAdvisor with a copy of the gpu fence so it can determine the gpu end time virtual void setGpuFenceTime(DisplayId displayId, std::unique_ptr&& fenceTime) = 0; // Reports the start and end times of a hwc validate call this frame for a given display - virtual void setHwcValidateTiming(DisplayId displayId, nsecs_t validateStartTime, - nsecs_t validateEndTime) = 0; + virtual void setHwcValidateTiming(DisplayId displayId, TimePoint validateStartTime, + TimePoint validateEndTime) = 0; // Reports the start and end times of a hwc present call this frame for a given display - virtual void setHwcPresentTiming(DisplayId displayId, nsecs_t presentStartTime, - nsecs_t presentEndTime) = 0; + virtual void setHwcPresentTiming(DisplayId displayId, TimePoint presentStartTime, + TimePoint presentEndTime) = 0; // Reports the expected time that the current frame will present to the display - virtual void setExpectedPresentTime(nsecs_t expectedPresentTime) = 0; + virtual void setExpectedPresentTime(TimePoint expectedPresentTime) = 0; // Reports the most recent present fence time and end time once known - virtual void setSfPresentTiming(nsecs_t presentFenceTime, nsecs_t presentEndTime) = 0; + virtual void setSfPresentTiming(TimePoint presentFenceTime, TimePoint presentEndTime) = 0; // Reports whether a display used client composition this frame virtual void setRequiresClientComposition(DisplayId displayId, bool requiresClientComposition) = 0; // Reports whether a given display skipped validation this frame virtual void setSkippedValidate(DisplayId displayId, bool skipped) = 0; // Reports when a hwc present is delayed, and the time that it will resume - virtual void setHwcPresentDelayedTime( - DisplayId displayId, std::chrono::steady_clock::time_point earliestFrameStartTime) = 0; + virtual void setHwcPresentDelayedTime(DisplayId displayId, + TimePoint earliestFrameStartTime) = 0; // Reports the start delay for SurfaceFlinger this frame - virtual void setFrameDelay(nsecs_t frameDelayDuration) = 0; + virtual void setFrameDelay(Duration frameDelayDuration) = 0; // Reports the SurfaceFlinger commit start time this frame - virtual void setCommitStart(nsecs_t commitStartTime) = 0; + virtual void setCommitStart(TimePoint commitStartTime) = 0; // Reports the SurfaceFlinger composite end time this frame - virtual void setCompositeEnd(nsecs_t compositeEndTime) = 0; + virtual void setCompositeEnd(TimePoint compositeEndTime) = 0; // Reports the list of the currently active displays virtual void setDisplays(std::vector& displayIds) = 0; // Sets the target duration for the entire pipeline including the gpu - virtual void setTotalFrameTargetWorkDuration(nsecs_t targetDuration) = 0; + virtual void setTotalFrameTargetWorkDuration(Duration targetDuration) = 0; }; namespace impl { @@ -111,11 +112,11 @@ public: virtual void restartPowerHintSession() = 0; virtual void setPowerHintSessionThreadIds(const std::vector& threadIds) = 0; virtual bool startPowerHintSession() = 0; - virtual void setTargetWorkDuration(nsecs_t targetDuration) = 0; - virtual void sendActualWorkDuration(nsecs_t actualDuration, nsecs_t timestamp) = 0; + virtual void setTargetWorkDuration(Duration targetDuration) = 0; + virtual void sendActualWorkDuration(Duration actualDuration, TimePoint timestamp) = 0; virtual bool shouldReconnectHAL() = 0; virtual std::vector getPowerHintSessionThreadIds() = 0; - virtual std::optional getTargetWorkDuration() = 0; + virtual std::optional getTargetWorkDuration() = 0; }; PowerAdvisor(SurfaceFlinger& flinger); @@ -129,29 +130,27 @@ public: bool usePowerHintSession() override; bool supportsPowerHintSession() override; bool isPowerHintSessionRunning() override; - void setTargetWorkDuration(nsecs_t targetDuration) override; + void setTargetWorkDuration(Duration targetDuration) override; void sendActualWorkDuration() override; void sendPredictedWorkDuration() override; void enablePowerHint(bool enabled) override; bool startPowerHintSession(const std::vector& threadIds) override; void setGpuFenceTime(DisplayId displayId, std::unique_ptr&& fenceTime); - void setHwcValidateTiming(DisplayId displayId, nsecs_t valiateStartTime, - nsecs_t validateEndTime) override; - void setHwcPresentTiming(DisplayId displayId, nsecs_t presentStartTime, - nsecs_t presentEndTime) override; + void setHwcValidateTiming(DisplayId displayId, TimePoint validateStartTime, + TimePoint validateEndTime) override; + void setHwcPresentTiming(DisplayId displayId, TimePoint presentStartTime, + TimePoint presentEndTime) override; void setSkippedValidate(DisplayId displayId, bool skipped) override; void setRequiresClientComposition(DisplayId displayId, bool requiresClientComposition) override; - void setExpectedPresentTime(nsecs_t expectedPresentTime) override; - void setSfPresentTiming(nsecs_t presentFenceTime, nsecs_t presentEndTime) override; - void setHwcPresentDelayedTime( - DisplayId displayId, - std::chrono::steady_clock::time_point earliestFrameStartTime) override; - - void setFrameDelay(nsecs_t frameDelayDuration) override; - void setCommitStart(nsecs_t commitStartTime) override; - void setCompositeEnd(nsecs_t compositeEndTime) override; + void setExpectedPresentTime(TimePoint expectedPresentTime) override; + void setSfPresentTiming(TimePoint presentFenceTime, TimePoint presentEndTime) override; + void setHwcPresentDelayedTime(DisplayId displayId, TimePoint earliestFrameStartTime) override; + + void setFrameDelay(Duration frameDelayDuration) override; + void setCommitStart(TimePoint commitStartTime) override; + void setCompositeEnd(TimePoint compositeEndTime) override; void setDisplays(std::vector& displayIds) override; - void setTotalFrameTargetWorkDuration(nsecs_t targetDuration) override; + void setTotalFrameTargetWorkDuration(Duration targetDuration) override; private: friend class PowerAdvisorTest; @@ -178,44 +177,45 @@ private: // Higher-level timing data used for estimation struct DisplayTimeline { // The start of hwc present, or the start of validate if it happened there instead - nsecs_t hwcPresentStartTime = -1; + TimePoint hwcPresentStartTime; // The end of hwc present or validate, whichever one actually presented - nsecs_t hwcPresentEndTime = -1; + TimePoint hwcPresentEndTime; // How long the actual hwc present was delayed after hwcPresentStartTime - nsecs_t hwcPresentDelayDuration = 0; + Duration hwcPresentDelayDuration{0ns}; // When we think we started waiting for the present fence after calling into hwc present and // after potentially waiting for the earliest present time - nsecs_t presentFenceWaitStartTime = -1; + TimePoint presentFenceWaitStartTime; // How long we ran after we finished waiting for the fence but before hwc present finished - nsecs_t postPresentFenceHwcPresentDuration = 0; + Duration postPresentFenceHwcPresentDuration{0ns}; // Are we likely to have waited for the present fence during composition bool probablyWaitsForPresentFence = false; // Estimate one frame's timeline from that of a previous frame - DisplayTimeline estimateTimelineFromReference(nsecs_t fenceTime, nsecs_t displayStartTime); + DisplayTimeline estimateTimelineFromReference(TimePoint fenceTime, + TimePoint displayStartTime); }; struct GpuTimeline { - nsecs_t duration = 0; - nsecs_t startTime = -1; + Duration duration{0ns}; + TimePoint startTime; }; // Power hint session data recorded from the pipeline struct DisplayTimingData { std::unique_ptr gpuEndFenceTime; - std::optional gpuStartTime; - std::optional lastValidGpuEndTime; - std::optional lastValidGpuStartTime; - std::optional hwcPresentStartTime; - std::optional hwcPresentEndTime; - std::optional hwcValidateStartTime; - std::optional hwcValidateEndTime; - std::optional hwcPresentDelayedTime; + std::optional gpuStartTime; + std::optional lastValidGpuEndTime; + std::optional lastValidGpuStartTime; + std::optional hwcPresentStartTime; + std::optional hwcPresentEndTime; + std::optional hwcValidateStartTime; + std::optional hwcValidateEndTime; + std::optional hwcPresentDelayedTime; bool usedClientComposition = false; bool skippedValidate = false; // Calculate high-level timing milestones from more granular display timing data - DisplayTimeline calculateDisplayTimeline(nsecs_t fenceTime); + DisplayTimeline calculateDisplayTimeline(TimePoint fenceTime); // Estimate the gpu duration for a given display from previous gpu timing data - std::optional estimateGpuTiming(std::optional previousEnd); + std::optional estimateGpuTiming(std::optional previousEndTime); }; template @@ -240,30 +240,31 @@ private: }; // Filter and sort the display ids by a given property - std::vector getOrderedDisplayIds(std::optional DisplayTimingData::*sortBy); + std::vector getOrderedDisplayIds( + std::optional DisplayTimingData::*sortBy); // Estimates a frame's total work duration including gpu time. // Runs either at the beginning or end of a frame, using the most recent data available - std::optional estimateWorkDuration(bool earlyHint); + std::optional estimateWorkDuration(bool earlyHint); // There are two different targets and actual work durations we care about, // this normalizes them together and takes the max of the two - nsecs_t combineTimingEstimates(nsecs_t totalDuration, nsecs_t flingerDuration); + Duration combineTimingEstimates(Duration totalDuration, Duration flingerDuration); std::unordered_map mDisplayTimingData; // Current frame's delay - nsecs_t mFrameDelayDuration = 0; + Duration mFrameDelayDuration{0ns}; // Last frame's post-composition duration - nsecs_t mLastPostcompDuration = 0; + Duration mLastPostcompDuration{0ns}; // Buffer of recent commit start times - RingBuffer mCommitStartTimes; + RingBuffer mCommitStartTimes; // Buffer of recent expected present times - RingBuffer mExpectedPresentTimes; + RingBuffer mExpectedPresentTimes; // Most recent present fence time, provided by SF after composition engine finishes presenting - nsecs_t mLastPresentFenceTime = -1; + TimePoint mLastPresentFenceTime; // Most recent composition engine present end time, returned with the present fence from SF - nsecs_t mLastSfPresentEndTime = -1; + TimePoint mLastSfPresentEndTime; // Target duration for the entire pipeline including gpu - std::optional mTotalFrameTargetDuration; + std::optional mTotalFrameTargetDuration; // Updated list of display IDs std::vector mDisplayIds; @@ -273,11 +274,11 @@ private: // An adjustable safety margin which pads the "actual" value sent to PowerHAL, // encouraging more aggressive boosting to give SurfaceFlinger a larger margin for error - static constexpr const std::chrono::nanoseconds kTargetSafetyMargin = 1ms; + static constexpr const Duration kTargetSafetyMargin{1ms}; // How long we expect hwc to run after the present call until it waits for the fence - static constexpr const std::chrono::nanoseconds kFenceWaitStartDelayValidated = 150us; - static constexpr const std::chrono::nanoseconds kFenceWaitStartDelaySkippedValidate = 250us; + static constexpr const Duration kFenceWaitStartDelayValidated{150us}; + static constexpr const Duration kFenceWaitStartDelaySkippedValidate{250us}; }; class AidlPowerHalWrapper : public PowerAdvisor::HalWrapper { @@ -294,11 +295,11 @@ public: void restartPowerHintSession() override; void setPowerHintSessionThreadIds(const std::vector& threadIds) override; bool startPowerHintSession() override; - void setTargetWorkDuration(nsecs_t targetDuration) override; - void sendActualWorkDuration(nsecs_t actualDuration, nsecs_t timestamp) override; + void setTargetWorkDuration(Duration targetDuration) override; + void sendActualWorkDuration(Duration actualDuration, TimePoint timestamp) override; bool shouldReconnectHAL() override; std::vector getPowerHintSessionThreadIds() override; - std::optional getTargetWorkDuration() override; + std::optional getTargetWorkDuration() override; private: friend class AidlPowerHalWrapperTest; @@ -319,15 +320,15 @@ private: // Queue of actual durations saved to report std::vector mPowerHintQueue; // The latest values we have received for target and actual - nsecs_t mTargetDuration = kDefaultTarget.count(); - std::optional mActualDuration; + Duration mTargetDuration = kDefaultTargetDuration; + std::optional mActualDuration; // The list of thread ids, stored so we can restart the session from this class if needed std::vector mPowerHintThreadIds; bool mSupportsPowerHint = false; - nsecs_t mLastTargetDurationSent = kDefaultTarget.count(); + Duration mLastTargetDurationSent = kDefaultTargetDuration; // Whether we should emit ATRACE_INT data for hint sessions static const bool sTraceHintSessionData; - static constexpr const std::chrono::nanoseconds kDefaultTarget = 16ms; + static constexpr Duration kDefaultTargetDuration{16ms}; }; } // namespace impl diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Time.h b/services/surfaceflinger/Scheduler/include/scheduler/Time.h index f00d456c5e..2ca55d41ee 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/Time.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/Time.h @@ -41,6 +41,8 @@ struct TimePoint : scheduler::SchedulerClock::time_point { static constexpr TimePoint fromNs(nsecs_t); + static TimePoint now() { return scheduler::SchedulerClock::now(); }; + nsecs_t ns() const; }; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 3acf203b0a..31037926d4 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1056,7 +1056,7 @@ status_t SurfaceFlinger::getDisplayStats(const sp&, DisplayStatInfo* ou } const auto& schedule = mScheduler->getVsyncSchedule(); - outStats->vsyncTime = schedule.vsyncDeadlineAfter(scheduler::SchedulerClock::now()).ns(); + outStats->vsyncTime = schedule.vsyncDeadlineAfter(TimePoint::now()).ns(); outStats->vsyncPeriod = schedule.period().ns(); return NO_ERROR; } @@ -1972,7 +1972,7 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe : calculateExpectedPresentTime(frameTime); ATRACE_FORMAT("%s %" PRId64 " vsyncIn %.2fms%s", __func__, vsyncId.value, - ticks(mExpectedPresentTime - scheduler::SchedulerClock::now()), + ticks(mExpectedPresentTime - TimePoint::now()), mExpectedPresentTime == expectedVsyncTime ? "" : " (adjusted)"); const Period vsyncPeriod = mScheduler->getVsyncSchedule().period(); @@ -2059,16 +2059,16 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe activeDisplay->getPowerMode() == hal::PowerMode::ON; if (mPowerHintSessionEnabled) { const auto& display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get(); - const nsecs_t vsyncPeriod = display->getActiveMode()->getVsyncPeriod(); - mPowerAdvisor->setCommitStart(frameTime.ns()); - mPowerAdvisor->setExpectedPresentTime(mExpectedPresentTime.ns()); + const Period vsyncPeriod = Period::fromNs(display->getActiveMode()->getVsyncPeriod()); + mPowerAdvisor->setCommitStart(frameTime); + mPowerAdvisor->setExpectedPresentTime(mExpectedPresentTime); // Frame delay is how long we should have minus how long we actually have. const Duration idealSfWorkDuration = mVsyncModulator->getVsyncConfig().sfWorkDuration; const Duration frameDelay = idealSfWorkDuration - (mExpectedPresentTime - frameTime); - mPowerAdvisor->setFrameDelay(frameDelay.ns()); - mPowerAdvisor->setTotalFrameTargetWorkDuration(idealSfWorkDuration.ns()); + mPowerAdvisor->setFrameDelay(frameDelay); + mPowerAdvisor->setTotalFrameTargetWorkDuration(idealSfWorkDuration); mPowerAdvisor->setTargetWorkDuration(vsyncPeriod); // Send early hint here to make sure there's not another frame pending @@ -2228,8 +2228,9 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) // Send a power hint hint after presentation is finished if (mPowerHintSessionEnabled) { - mPowerAdvisor->setSfPresentTiming(mPreviousPresentFences[0].fenceTime->getSignalTime(), - systemTime()); + mPowerAdvisor->setSfPresentTiming(TimePoint::fromNs(mPreviousPresentFences[0] + .fenceTime->getSignalTime()), + TimePoint::now()); if (mPowerHintSessionMode.late) { mPowerAdvisor->sendActualWorkDuration(); } @@ -2279,7 +2280,7 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) } if (mPowerHintSessionEnabled) { - mPowerAdvisor->setCompositeEnd(systemTime()); + mPowerAdvisor->setCompositeEnd(TimePoint::now()); } } @@ -2376,7 +2377,7 @@ void SurfaceFlinger::postComposition() { auto presentFenceTime = std::make_shared(presentFence); mPreviousPresentFences[0] = {presentFence, presentFenceTime}; - const TimePoint presentTime = scheduler::SchedulerClock::now(); + const TimePoint presentTime = TimePoint::now(); // Set presentation information before calling Layer::releasePendingBuffer, such that jank // information from previous' frame classification is already available when sending jank info diff --git a/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp b/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp index ab38b294c5..513f77989b 100644 --- a/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp +++ b/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp @@ -69,7 +69,8 @@ void AidlPowerHalWrapperTest::verifyAndClearExpectations() { void AidlPowerHalWrapperTest::sendActualWorkDurationGroup(std::vector durations) { for (size_t i = 0; i < durations.size(); i++) { auto duration = durations[i]; - mWrapper->sendActualWorkDuration(duration.durationNanos, duration.timeStampNanos); + mWrapper->sendActualWorkDuration(Duration::fromNs(duration.durationNanos), + TimePoint::fromNs(duration.timeStampNanos)); } } @@ -155,13 +156,13 @@ TEST_F(AidlPowerHalWrapperTest, setTargetWorkDuration) { for (const auto& test : testCases) { // reset to 100ms baseline - mWrapper->setTargetWorkDuration(1); - mWrapper->setTargetWorkDuration(base.count()); + mWrapper->setTargetWorkDuration(1ns); + mWrapper->setTargetWorkDuration(base); - auto target = test.first; + std::chrono::nanoseconds target = test.first; EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(target.count())) .Times(test.second ? 1 : 0); - mWrapper->setTargetWorkDuration(target.count()); + mWrapper->setTargetWorkDuration(target); verifyAndClearExpectations(); } } @@ -178,7 +179,7 @@ TEST_F(AidlPowerHalWrapperTest, setTargetWorkDuration_shouldReconnectOnError) { EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(1)) .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_ILLEGAL_STATE))); - mWrapper->setTargetWorkDuration(1); + mWrapper->setTargetWorkDuration(1ns); EXPECT_TRUE(mWrapper->shouldReconnectHAL()); } diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp index 8711a42b2b..2d66d3cf92 100644 --- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp +++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp @@ -39,15 +39,15 @@ class PowerAdvisorTest : public testing::Test { public: void SetUp() override; void startPowerHintSession(); - void fakeBasicFrameTiming(nsecs_t startTime, nsecs_t vsyncPeriod); - void setExpectedTiming(nsecs_t startTime, nsecs_t vsyncPeriod); - nsecs_t getFenceWaitDelayDuration(bool skipValidate); + void fakeBasicFrameTiming(TimePoint startTime, Duration vsyncPeriod); + void setExpectedTiming(Duration totalFrameTargetDuration, TimePoint expectedPresentTime); + Duration getFenceWaitDelayDuration(bool skipValidate); protected: TestableSurfaceFlinger mFlinger; std::unique_ptr mPowerAdvisor; NiceMock* mMockAidlWrapper; - nsecs_t kErrorMargin = std::chrono::nanoseconds(1ms).count(); + Duration kErrorMargin = 1ms; }; void PowerAdvisorTest::SetUp() FTL_FAKE_GUARD(mPowerAdvisor->mPowerHalMutex) { @@ -67,21 +67,21 @@ void PowerAdvisorTest::startPowerHintSession() { mPowerAdvisor->startPowerHintSession(threadIds); } -void PowerAdvisorTest::setExpectedTiming(nsecs_t totalFrameTarget, nsecs_t expectedPresentTime) { - mPowerAdvisor->setTotalFrameTargetWorkDuration(totalFrameTarget); +void PowerAdvisorTest::setExpectedTiming(Duration totalFrameTargetDuration, + TimePoint expectedPresentTime) { + mPowerAdvisor->setTotalFrameTargetWorkDuration(totalFrameTargetDuration); mPowerAdvisor->setExpectedPresentTime(expectedPresentTime); } -void PowerAdvisorTest::fakeBasicFrameTiming(nsecs_t startTime, nsecs_t vsyncPeriod) { +void PowerAdvisorTest::fakeBasicFrameTiming(TimePoint startTime, Duration vsyncPeriod) { mPowerAdvisor->setCommitStart(startTime); - mPowerAdvisor->setFrameDelay(0); + mPowerAdvisor->setFrameDelay(0ns); mPowerAdvisor->setTargetWorkDuration(vsyncPeriod); } -nsecs_t PowerAdvisorTest::getFenceWaitDelayDuration(bool skipValidate) { +Duration PowerAdvisorTest::getFenceWaitDelayDuration(bool skipValidate) { return (skipValidate ? PowerAdvisor::kFenceWaitStartDelaySkippedValidate - : PowerAdvisor::kFenceWaitStartDelayValidated) - .count(); + : PowerAdvisor::kFenceWaitStartDelayValidated); } namespace { @@ -93,11 +93,11 @@ TEST_F(PowerAdvisorTest, hintSessionUseHwcDisplay) { std::vector displayIds{PhysicalDisplayId::fromPort(42u)}; // 60hz - const nsecs_t vsyncPeriod = std::chrono::nanoseconds(1s).count() / 60; - const nsecs_t presentDuration = std::chrono::nanoseconds(5ms).count(); - const nsecs_t postCompDuration = std::chrono::nanoseconds(1ms).count(); + const Duration vsyncPeriod{std::chrono::nanoseconds(1s) / 60}; + const Duration presentDuration = 5ms; + const Duration postCompDuration = 1ms; - nsecs_t startTime = 100; + TimePoint startTime{100ns}; // advisor only starts on frame 2 so do an initial no-op frame fakeBasicFrameTiming(startTime, vsyncPeriod); @@ -109,14 +109,14 @@ TEST_F(PowerAdvisorTest, hintSessionUseHwcDisplay) { // increment the frame startTime += vsyncPeriod; - const nsecs_t expectedDuration = kErrorMargin + presentDuration + postCompDuration; + const Duration expectedDuration = kErrorMargin + presentDuration + postCompDuration; EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1); fakeBasicFrameTiming(startTime, vsyncPeriod); setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod); mPowerAdvisor->setDisplays(displayIds); - mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1000000, startTime + 1500000); - mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2000000, startTime + 2500000); + mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1ms, startTime + 1500us); + mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2ms, startTime + 2500us); mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration); mPowerAdvisor->sendActualWorkDuration(); } @@ -128,12 +128,12 @@ TEST_F(PowerAdvisorTest, hintSessionSubtractsHwcFenceTime) { std::vector displayIds{PhysicalDisplayId::fromPort(42u)}; // 60hz - const nsecs_t vsyncPeriod = std::chrono::nanoseconds(1s).count() / 60; - const nsecs_t presentDuration = std::chrono::nanoseconds(5ms).count(); - const nsecs_t postCompDuration = std::chrono::nanoseconds(1ms).count(); - const nsecs_t hwcBlockedDuration = std::chrono::nanoseconds(500us).count(); + const Duration vsyncPeriod{std::chrono::nanoseconds(1s) / 60}; + const Duration presentDuration = 5ms; + const Duration postCompDuration = 1ms; + const Duration hwcBlockedDuration = 500us; - nsecs_t startTime = 100; + TimePoint startTime{100ns}; // advisor only starts on frame 2 so do an initial no-op frame fakeBasicFrameTiming(startTime, vsyncPeriod); @@ -145,17 +145,17 @@ TEST_F(PowerAdvisorTest, hintSessionSubtractsHwcFenceTime) { // increment the frame startTime += vsyncPeriod; - const nsecs_t expectedDuration = kErrorMargin + presentDuration + + const Duration expectedDuration = kErrorMargin + presentDuration + getFenceWaitDelayDuration(false) - hwcBlockedDuration + postCompDuration; EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1); fakeBasicFrameTiming(startTime, vsyncPeriod); setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod); mPowerAdvisor->setDisplays(displayIds); - mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1000000, startTime + 1500000); - mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2000000, startTime + 3000000); + mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1ms, startTime + 1500us); + mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2ms, startTime + 3ms); // now report the fence as having fired during the display HWC time - mPowerAdvisor->setSfPresentTiming(startTime + 2000000 + hwcBlockedDuration, + mPowerAdvisor->setSfPresentTiming(startTime + 2ms + hwcBlockedDuration, startTime + presentDuration); mPowerAdvisor->sendActualWorkDuration(); } @@ -168,12 +168,12 @@ TEST_F(PowerAdvisorTest, hintSessionUsingSecondaryVirtualDisplays) { GpuVirtualDisplayId(1)}; // 60hz - const nsecs_t vsyncPeriod = std::chrono::nanoseconds(1s).count() / 60; + const Duration vsyncPeriod{std::chrono::nanoseconds(1s) / 60}; // make present duration much later than the hwc display by itself will account for - const nsecs_t presentDuration = std::chrono::nanoseconds(10ms).count(); - const nsecs_t postCompDuration = std::chrono::nanoseconds(1ms).count(); + const Duration presentDuration{10ms}; + const Duration postCompDuration{1ms}; - nsecs_t startTime = 100; + TimePoint startTime{100ns}; // advisor only starts on frame 2 so do an initial no-op frame fakeBasicFrameTiming(startTime, vsyncPeriod); @@ -185,7 +185,7 @@ TEST_F(PowerAdvisorTest, hintSessionUsingSecondaryVirtualDisplays) { // increment the frame startTime += vsyncPeriod; - const nsecs_t expectedDuration = kErrorMargin + presentDuration + postCompDuration; + const Duration expectedDuration = kErrorMargin + presentDuration + postCompDuration; EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1); fakeBasicFrameTiming(startTime, vsyncPeriod); @@ -193,8 +193,8 @@ TEST_F(PowerAdvisorTest, hintSessionUsingSecondaryVirtualDisplays) { mPowerAdvisor->setDisplays(displayIds); // don't report timing for the gpu displays since they don't use hwc - mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1000000, startTime + 1500000); - mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2000000, startTime + 2500000); + mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1ms, startTime + 1500us); + mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2ms, startTime + 2500us); mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration); mPowerAdvisor->sendActualWorkDuration(); } diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h index 657ced3d08..c2c3d77364 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include "DisplayHardware/PowerAdvisor.h" @@ -42,8 +43,8 @@ public: MOCK_METHOD(void, setPowerHintSessionThreadIds, (const std::vector& threadIds), (override)); MOCK_METHOD(bool, startPowerHintSession, (), (override)); - MOCK_METHOD(void, setTargetWorkDuration, (nsecs_t targetDuration), (override)); - MOCK_METHOD(void, sendActualWorkDuration, (nsecs_t actualDuration, nsecs_t timestamp), + MOCK_METHOD(void, setTargetWorkDuration, (Duration targetDuration), (override)); + MOCK_METHOD(void, sendActualWorkDuration, (Duration actualDuration, TimePoint timestamp), (override)); MOCK_METHOD(bool, shouldReconnectHAL, (), (override)); }; diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h index aede250db5..fb1b3946a4 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h @@ -36,7 +36,7 @@ public: MOCK_METHOD(bool, usePowerHintSession, (), (override)); MOCK_METHOD(bool, supportsPowerHintSession, (), (override)); MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override)); - MOCK_METHOD(void, setTargetWorkDuration, (int64_t targetDuration), (override)); + MOCK_METHOD(void, setTargetWorkDuration, (Duration targetDuration), (override)); MOCK_METHOD(void, sendActualWorkDuration, (), (override)); MOCK_METHOD(void, sendPredictedWorkDuration, (), (override)); MOCK_METHOD(void, enablePowerHint, (bool enabled), (override)); @@ -44,25 +44,24 @@ public: MOCK_METHOD(void, setGpuFenceTime, (DisplayId displayId, std::unique_ptr&& fenceTime), (override)); MOCK_METHOD(void, setHwcValidateTiming, - (DisplayId displayId, nsecs_t valiateStartTime, nsecs_t validateEndTime), + (DisplayId displayId, TimePoint validateStartTime, TimePoint validateEndTime), (override)); MOCK_METHOD(void, setHwcPresentTiming, - (DisplayId displayId, nsecs_t presentStartTime, nsecs_t presentEndTime), + (DisplayId displayId, TimePoint presentStartTime, TimePoint presentEndTime), (override)); MOCK_METHOD(void, setSkippedValidate, (DisplayId displayId, bool skipped), (override)); MOCK_METHOD(void, setRequiresClientComposition, (DisplayId displayId, bool requiresClientComposition), (override)); - MOCK_METHOD(void, setExpectedPresentTime, (nsecs_t expectedPresentTime), (override)); - MOCK_METHOD(void, setSfPresentTiming, (nsecs_t presentFenceTime, nsecs_t presentEndTime), + MOCK_METHOD(void, setExpectedPresentTime, (TimePoint expectedPresentTime), (override)); + MOCK_METHOD(void, setSfPresentTiming, (TimePoint presentFenceTime, TimePoint presentEndTime), (override)); MOCK_METHOD(void, setHwcPresentDelayedTime, - (DisplayId displayId, - std::chrono::steady_clock::time_point earliestFrameStartTime)); - MOCK_METHOD(void, setFrameDelay, (nsecs_t frameDelayDuration), (override)); - MOCK_METHOD(void, setCommitStart, (nsecs_t commitStartTime), (override)); - MOCK_METHOD(void, setCompositeEnd, (nsecs_t compositeEndtime), (override)); + (DisplayId displayId, TimePoint earliestFrameStartTime)); + MOCK_METHOD(void, setFrameDelay, (Duration frameDelayDuration), (override)); + MOCK_METHOD(void, setCommitStart, (TimePoint commitStartTime), (override)); + MOCK_METHOD(void, setCompositeEnd, (TimePoint compositeEndTime), (override)); MOCK_METHOD(void, setDisplays, (std::vector & displayIds), (override)); - MOCK_METHOD(void, setTotalFrameTargetWorkDuration, (int64_t targetDuration), (override)); + MOCK_METHOD(void, setTotalFrameTargetWorkDuration, (Duration targetDuration), (override)); }; } // namespace android::Hwc2::mock -- GitLab From e6c5e6e6c7b387a487cf9088bd4f86cd6f5dc644 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Wed, 3 Aug 2022 12:58:28 -0700 Subject: [PATCH 0256/1310] FTL: Add find_if Bug: 185536303 Test: ftl_test Change-Id: I16b63d63da181c7f79d62af9d7a91639342d8c95 --- include/ftl/algorithm.h | 71 +++++++++++++++++++++++++++++++++++++ libs/ftl/Android.bp | 1 + libs/ftl/algorithm_test.cpp | 50 ++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 include/ftl/algorithm.h create mode 100644 libs/ftl/algorithm_test.cpp diff --git a/include/ftl/algorithm.h b/include/ftl/algorithm.h new file mode 100644 index 0000000000..c5ff03b80d --- /dev/null +++ b/include/ftl/algorithm.h @@ -0,0 +1,71 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include + +namespace android::ftl { + +// Adapter for std::find_if that converts the return value from iterator to optional. +// +// const ftl::StaticVector vector = {"upside"sv, "down"sv, "cake"sv}; +// assert(ftl::find_if(vector, [](const auto& str) { return str.front() == 'c'; }) == "cake"sv); +// +template +constexpr auto find_if(const Container& container, Predicate&& predicate) + -> Optional> { + const auto it = std::find_if(std::cbegin(container), std::cend(container), + std::forward(predicate)); + if (it == std::cend(container)) return {}; + return std::cref(*it); +} + +// Transformers for ftl::find_if on a map-like `Container` that contains key-value pairs. +// +// const ftl::SmallMap map = ftl::init::map>( +// 12, "snow"sv, "cone"sv)(13, "tiramisu"sv)(14, "upside"sv, "down"sv, "cake"sv); +// +// using Map = decltype(map); +// +// assert(14 == ftl::find_if(map, [](const auto& pair) { +// return pair.second.size() == 3; +// }).transform(ftl::to_key)); +// +// const auto opt = ftl::find_if(map, [](const auto& pair) { +// return pair.second.size() == 1; +// }).transform(ftl::to_mapped_ref); +// +// assert(opt); +// assert(opt->get() == ftl::StaticVector("tiramisu"sv)); +// +template +constexpr auto to_key(const Pair& pair) -> Key { + return pair.first; +} + +template +constexpr auto to_mapped_ref(const Pair& pair) -> std::reference_wrapper { + return std::cref(pair.second); +} + +} // namespace android::ftl diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp index 8f89e7d151..a25a49397e 100644 --- a/libs/ftl/Android.bp +++ b/libs/ftl/Android.bp @@ -14,6 +14,7 @@ cc_test { address: true, }, srcs: [ + "algorithm_test.cpp", "cast_test.cpp", "concat_test.cpp", "enum_test.cpp", diff --git a/libs/ftl/algorithm_test.cpp b/libs/ftl/algorithm_test.cpp new file mode 100644 index 0000000000..8052caf642 --- /dev/null +++ b/libs/ftl/algorithm_test.cpp @@ -0,0 +1,50 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include + +namespace android::test { + +// Keep in sync with example usage in header file. +TEST(Algorithm, FindIf) { + using namespace std::string_view_literals; + + const ftl::StaticVector vector = {"upside"sv, "down"sv, "cake"sv}; + EXPECT_EQ(ftl::find_if(vector, [](const auto& str) { return str.front() == 'c'; }), "cake"sv); + + const ftl::SmallMap map = ftl::init::map>( + 12, "snow"sv, "cone"sv)(13, "tiramisu"sv)(14, "upside"sv, "down"sv, "cake"sv); + + using Map = decltype(map); + + EXPECT_EQ(14, ftl::find_if(map, [](const auto& pair) { + return pair.second.size() == 3; + }).transform(ftl::to_key)); + + const auto opt = ftl::find_if(map, [](const auto& pair) { + return pair.second.size() == 1; + }).transform(ftl::to_mapped_ref); + + ASSERT_TRUE(opt); + EXPECT_EQ(opt->get(), ftl::StaticVector("tiramisu"sv)); +} + +} // namespace android::test -- GitLab From bc4ee5c5976b9a14db8a9b06033da07c33006b5b Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 16 Aug 2022 03:19:37 +0000 Subject: [PATCH 0257/1310] SF: Merge EffectLayer into BufferStateLayer This makes all the Layer instances a BufferStateLayer. The layer can draw effects or hold a buffer. If the caller tries to do both, drawing a buffer takes precedence. Test: go/wm-smoke Test: presubmit Bug: 238781169 Change-Id: Ied68cd1ed7399f6408bd24d9e0220707d6a3a2ab --- services/surfaceflinger/BufferStateLayer.cpp | 129 ++++++++++++++---- services/surfaceflinger/BufferStateLayer.h | 30 +++- services/surfaceflinger/EffectLayer.cpp | 117 +--------------- services/surfaceflinger/EffectLayer.h | 39 +----- services/surfaceflinger/Layer.cpp | 76 +---------- services/surfaceflinger/Layer.h | 117 +++++++--------- services/surfaceflinger/SurfaceFlinger.cpp | 12 +- .../fuzzer/surfaceflinger_scheduler_fuzzer.h | 6 +- .../tests/unittests/mock/MockLayer.h | 6 +- 9 files changed, 195 insertions(+), 337 deletions(-) diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp index 0cedfc8138..b939752be5 100644 --- a/services/surfaceflinger/BufferStateLayer.cpp +++ b/services/surfaceflinger/BufferStateLayer.cpp @@ -300,10 +300,6 @@ bool BufferStateLayer::willPresentCurrentTransaction() const { (mDrawingState.buffer != nullptr || mDrawingState.bgColorLayer != nullptr))); } -Rect BufferStateLayer::getCrop(const Layer::State& s) const { - return s.crop; -} - bool BufferStateLayer::setTransform(uint32_t transform) { if (mDrawingState.bufferTransform == transform) return false; mDrawingState.bufferTransform = transform; @@ -321,16 +317,6 @@ bool BufferStateLayer::setTransformToDisplayInverse(bool transformToDisplayInver return true; } -bool BufferStateLayer::setCrop(const Rect& crop) { - if (mDrawingState.crop == crop) return false; - mDrawingState.sequence++; - mDrawingState.crop = crop; - - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - bool BufferStateLayer::setBufferCrop(const Rect& bufferCrop) { if (mDrawingState.bufferCrop == bufferCrop) return false; @@ -408,9 +394,6 @@ bool BufferStateLayer::setMatrix(const layer_state_t::matrix22_t& matrix) { return false; } - ui::Transform t; - t.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy); - mRequestedTransform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy); mDrawingState.sequence++; @@ -536,6 +519,7 @@ bool BufferStateLayer::setBuffer(std::shared_ptr& } bool BufferStateLayer::setDataspace(ui::Dataspace dataspace) { + mDrawingState.dataspaceRequested = true; if (mDrawingState.dataspace == dataspace) return false; mDrawingState.dataspace = dataspace; mDrawingState.modified = true; @@ -858,6 +842,9 @@ void BufferStateLayer::tracePendingBufferCount(int32_t pendingBuffers) { * how to go from screen space back to window space. */ ui::Transform BufferStateLayer::getInputTransform() const { + if (!hasBufferOrSidebandStream()) { + return getTransform(); + } sp parent = mDrawingParent.promote(); if (parent == nullptr) { return ui::Transform(); @@ -872,6 +859,10 @@ ui::Transform BufferStateLayer::getInputTransform() const { * that's already included. */ Rect BufferStateLayer::getInputBounds() const { + if (!hasBufferOrSidebandStream()) { + return getCroppedBufferSize(getDrawingState()); + } + Rect bufferBounds = getCroppedBufferSize(getDrawingState()); if (mDrawingState.transform.getType() == ui::Transform::IDENTITY || !bufferBounds.isValid()) { return bufferBounds; @@ -1080,13 +1071,22 @@ void BufferStateLayer::useEmptyDamage() { bool BufferStateLayer::isOpaque(const Layer::State& s) const { // if we don't have a buffer or sidebandStream yet, we're translucent regardless of the // layer's opaque flag. - if ((mSidebandStream == nullptr) && (mBufferInfo.mBuffer == nullptr)) { + if (!hasSomethingToDraw()) { return false; } - // if the layer has the opaque flag, then we're always opaque, - // otherwise we use the current buffer's format. - return ((s.flags & layer_state_t::eLayerOpaque) != 0) || getOpacityForFormat(getPixelFormat()); + // if the layer has the opaque flag, then we're always opaque + if ((s.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque) { + return true; + } + + // If the buffer has no alpha channel, then we are opaque + if (hasBufferOrSidebandStream() && isOpaqueFormat(getPixelFormat())) { + return true; + } + + // Lastly consider the layer opaque if drawing a color with alpha == 1.0 + return fillsColor() && getAlpha() == 1.0_hf; } bool BufferStateLayer::canReceiveInput() const { @@ -1094,8 +1094,15 @@ bool BufferStateLayer::canReceiveInput() const { } bool BufferStateLayer::isVisible() const { - return !isHiddenByPolicy() && getAlpha() > 0.0f && - (mBufferInfo.mBuffer != nullptr || mSidebandStream != nullptr); + if (!hasSomethingToDraw()) { + return false; + } + + if (isHiddenByPolicy()) { + return false; + } + + return getAlpha() > 0.0f || hasBlur(); } std::optional BufferStateLayer::prepareClientComposition( @@ -1130,6 +1137,11 @@ BufferStateLayer::prepareClientCompositionInternal( return result; } + if (hasEffect()) { + prepareEffectsClientComposition(*result, targetSettings); + return result; + } + if (CC_UNLIKELY(mBufferInfo.mBuffer == 0) && mSidebandStream != nullptr) { // For surfaceview of tv sideband, there is no activeBuffer // in bufferqueue, we need return LayerSettings. @@ -1241,6 +1253,18 @@ BufferStateLayer::prepareClientCompositionInternal( return layer; } +void BufferStateLayer::prepareEffectsClientComposition( + compositionengine::LayerFE::LayerSettings& layerSettings, + compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { + // If fill bounds are occluded or the fill color is invalid skip the fill settings. + if (targetSettings.realContentIsVisible && fillsColor()) { + // Set color for color fill settings. + layerSettings.source.solidColor = getColor().rgb; + } else if (hasBlur() || drawShadows()) { + layerSettings.skipContentDraw = true; + } +} + bool BufferStateLayer::isHdrY410() const { // pixel format is HDR Y410 masquerading as RGBA_1010102 return (mBufferInfo.mDataspace == ui::Dataspace::BT2020_ITU_PQ && @@ -1249,7 +1273,12 @@ bool BufferStateLayer::isHdrY410() const { } sp BufferStateLayer::getCompositionEngineLayerFE() const { - return asLayerFE(); + // There's no need to get a CE Layer if the layer isn't going to draw anything. + if (hasSomethingToDraw()) { + return asLayerFE(); + } else { + return nullptr; + } } compositionengine::LayerFECompositionState* BufferStateLayer::editCompositionState() { @@ -1262,7 +1291,14 @@ const compositionengine::LayerFECompositionState* BufferStateLayer::getCompositi void BufferStateLayer::preparePerFrameCompositionState() { Layer::preparePerFrameCompositionState(); + if (hasBufferOrSidebandStream()) { + preparePerFrameBufferCompositionState(); + } else { + preparePerFrameEffectsCompositionState(); + } +} +void BufferStateLayer::preparePerFrameBufferCompositionState() { // Sideband layers auto* compositionState = editCompositionState(); if (compositionState->sidebandStream.get() && !compositionState->sidebandStreamHasFrame) { @@ -1289,6 +1325,13 @@ void BufferStateLayer::preparePerFrameCompositionState() { compositionState->sidebandStreamHasFrame = false; } +void BufferStateLayer::preparePerFrameEffectsCompositionState() { + auto* compositionState = editCompositionState(); + compositionState->color = getColor(); + compositionState->compositionType = + aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR; +} + void BufferStateLayer::onPostComposition(const DisplayDevice* display, const std::shared_ptr& glDoneFence, const std::shared_ptr& presentFence, @@ -1441,7 +1484,7 @@ bool BufferStateLayer::isProtected() const { // hardware.h, instead of using hard-coded values here. #define HARDWARE_IS_DEVICE_FORMAT(f) ((f) >= 0x100 && (f) <= 0x1FF) -bool BufferStateLayer::getOpacityForFormat(PixelFormat format) { +bool BufferStateLayer::isOpaqueFormat(PixelFormat format) { if (HARDWARE_IS_DEVICE_FORMAT(format)) { return true; } @@ -1458,6 +1501,9 @@ bool BufferStateLayer::getOpacityForFormat(PixelFormat format) { } bool BufferStateLayer::needsFiltering(const DisplayDevice* display) const { + if (!hasBufferOrSidebandStream()) { + return false; + } const auto outputLayer = findOutputLayerForDisplay(display); if (outputLayer == nullptr) { return false; @@ -1474,6 +1520,9 @@ bool BufferStateLayer::needsFiltering(const DisplayDevice* display) const { bool BufferStateLayer::needsFilteringForScreenshots( const DisplayDevice* display, const ui::Transform& inverseParentTransform) const { + if (!hasBufferOrSidebandStream()) { + return false; + } const auto outputLayer = findOutputLayerForDisplay(display); if (outputLayer == nullptr) { return false; @@ -1535,7 +1584,11 @@ uint32_t BufferStateLayer::getBufferTransform() const { } ui::Dataspace BufferStateLayer::getDataSpace() const { - return mBufferInfo.mDataspace; + return mDrawingState.dataspaceRequested ? getRequestedDataSpace() : ui::Dataspace::UNKNOWN; +} + +ui::Dataspace BufferStateLayer::getRequestedDataSpace() const { + return hasBufferOrSidebandStream() ? mBufferInfo.mDataspace : mDrawingState.dataspace; } ui::Dataspace BufferStateLayer::translateDataspace(ui::Dataspace dataspace) { @@ -1636,4 +1689,28 @@ const std::shared_ptr& BufferStateLayer::getExter return mBufferInfo.mBuffer; } +bool BufferStateLayer::setColor(const half3& color) { + if (mDrawingState.color.r == color.r && mDrawingState.color.g == color.g && + mDrawingState.color.b == color.b) { + return false; + } + + mDrawingState.sequence++; + mDrawingState.color.r = color.r; + mDrawingState.color.g = color.g; + mDrawingState.color.b = color.b; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool BufferStateLayer::fillsColor() const { + return !hasBufferOrSidebandStream() && mDrawingState.color.r >= 0.0_hf && + mDrawingState.color.g >= 0.0_hf && mDrawingState.color.b >= 0.0_hf; +} + +bool BufferStateLayer::hasBlur() const { + return getBackgroundBlurRadius() > 0 || getDrawingState().blurRegions.size() > 0; +} + } // namespace android diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h index a0a52bfcd3..6ef2b1932e 100644 --- a/services/surfaceflinger/BufferStateLayer.h +++ b/services/surfaceflinger/BufferStateLayer.h @@ -75,7 +75,7 @@ public: // GRALLOC_USAGE_PROTECTED sense. bool isProtected() const override; - bool usesSourceCrop() const override { return true; } + bool usesSourceCrop() const override { return hasBufferOrSidebandStream(); } bool isHdrY410() const override; @@ -103,7 +103,7 @@ public: uint32_t getBufferTransform() const override; ui::Dataspace getDataSpace() const override; - + ui::Dataspace getRequestedDataSpace() const; sp getBuffer() const override; const std::shared_ptr& getExternalTexture() const override; @@ -116,11 +116,12 @@ public: void releasePendingBuffer(nsecs_t dequeueReadyTime) override; - Rect getCrop(const Layer::State& s) const; + Region getActiveTransparentRegion(const Layer::State& s) const override { + return s.transparentRegionHint; + } bool setTransform(uint32_t transform) override; bool setTransformToDisplayInverse(bool transformToDisplayInverse) override; - bool setCrop(const Rect& crop) override; bool setBuffer(std::shared_ptr& /* buffer */, const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime, bool isAutoTimestamp, std::optional dequeueTime, @@ -154,6 +155,7 @@ public: std::optional prepareClientComposition( compositionengine::LayerFE::ClientCompositionTargetSettings&) const override; + bool setColor(const half3& color) override; protected: void gatherBufferInfo(); @@ -189,8 +191,10 @@ protected: */ const compositionengine::LayerFECompositionState* getCompositionState() const override; void preparePerFrameCompositionState() override; + void preparePerFrameBufferCompositionState(); + void preparePerFrameEffectsCompositionState(); - static bool getOpacityForFormat(PixelFormat format); + static bool isOpaqueFormat(PixelFormat format); // from graphics API const uint32_t mTextureName; @@ -218,7 +222,9 @@ private: // We generate InputWindowHandles for all non-cursor buffered layers regardless of whether they // have an InputChannel. This is to enable the InputDispatcher to do PID based occlusion // detection. - bool needsInputInfo() const override { return !mPotentialCursor; } + bool needsInputInfo() const override { + return (hasInputInfo() || hasBufferOrSidebandStream()) && !mPotentialCursor; + } // Returns true if this layer requires filtering bool needsFiltering(const DisplayDevice*) const override; @@ -265,6 +271,18 @@ private: std::optional prepareClientCompositionInternal( compositionengine::LayerFE::ClientCompositionTargetSettings&) const; + // Returns true if there is a valid color to fill. + bool fillsColor() const; + // Returns true if this layer has a blur value. + bool hasBlur() const; + bool hasEffect() const { return fillsColor() || drawShadows() || hasBlur(); } + bool hasBufferOrSidebandStream() const { + return ((mSidebandStream != nullptr) || (mBufferInfo.mBuffer != nullptr)); + } + bool hasSomethingToDraw() const { return hasEffect() || hasBufferOrSidebandStream(); } + void prepareEffectsClientComposition( + compositionengine::LayerFE::LayerSettings&, + compositionengine::LayerFE::ClientCompositionTargetSettings&) const; ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID; uint64_t mPreviousReleasedFrameNumber = 0; diff --git a/services/surfaceflinger/EffectLayer.cpp b/services/surfaceflinger/EffectLayer.cpp index d161c510fd..7180fa6a58 100644 --- a/services/surfaceflinger/EffectLayer.cpp +++ b/services/surfaceflinger/EffectLayer.cpp @@ -41,122 +41,7 @@ namespace android { // --------------------------------------------------------------------------- -EffectLayer::EffectLayer(const LayerCreationArgs& args) - : Layer(args), - mCompositionState{mFlinger->getCompositionEngine().createLayerFECompositionState()} {} - +EffectLayer::EffectLayer(const LayerCreationArgs& args) : BufferStateLayer(args) {} EffectLayer::~EffectLayer() = default; -std::optional EffectLayer::prepareClientComposition( - compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { - std::optional layerSettings = - Layer::prepareClientComposition(targetSettings); - // Nothing to render. - if (!layerSettings) { - return {}; - } - - // set the shadow for the layer if needed - prepareShadowClientComposition(*layerSettings, targetSettings.viewport); - - // If fill bounds are occluded or the fill color is invalid skip the fill settings. - if (targetSettings.realContentIsVisible && fillsColor()) { - // Set color for color fill settings. - layerSettings->source.solidColor = getColor().rgb; - return layerSettings; - } else if (hasBlur() || drawShadows()) { - layerSettings->skipContentDraw = true; - return layerSettings; - } - - return {}; -} - -bool EffectLayer::isVisible() const { - return hasSomethingToDraw() && !isHiddenByPolicy() && (getAlpha() > 0.0_hf || hasBlur()); -} - -bool EffectLayer::setColor(const half3& color) { - if (mDrawingState.color.r == color.r && mDrawingState.color.g == color.g && - mDrawingState.color.b == color.b) { - return false; - } - - mDrawingState.sequence++; - mDrawingState.color.r = color.r; - mDrawingState.color.g = color.g; - mDrawingState.color.b = color.b; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -bool EffectLayer::setDataspace(ui::Dataspace dataspace) { - if (mDrawingState.dataspace == dataspace) { - return false; - } - - mDrawingState.sequence++; - mDrawingState.dataspace = dataspace; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -void EffectLayer::preparePerFrameCompositionState() { - Layer::preparePerFrameCompositionState(); - - auto* compositionState = editCompositionState(); - compositionState->color = getColor(); - compositionState->compositionType = - aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR; -} - -sp EffectLayer::getCompositionEngineLayerFE() const { - // There's no need to get a CE Layer if the EffectLayer isn't going to draw anything. In that - // case, it acts more like a ContainerLayer so returning a null CE Layer makes more sense - if (hasSomethingToDraw()) { - return asLayerFE(); - } else { - return nullptr; - } -} - -compositionengine::LayerFECompositionState* EffectLayer::editCompositionState() { - return mCompositionState.get(); -} - -const compositionengine::LayerFECompositionState* EffectLayer::getCompositionState() const { - return mCompositionState.get(); -} - -bool EffectLayer::isOpaque(const Layer::State& s) const { - // Consider the layer to be opaque if its opaque flag is set or its effective - // alpha (considering the alpha of its parents as well) is 1.0; - return (s.flags & layer_state_t::eLayerOpaque) != 0 || (fillsColor() && getAlpha() == 1.0_hf); -} - -ui::Dataspace EffectLayer::getDataSpace() const { - return mDrawingState.dataspace; -} - -sp EffectLayer::createClone() { - sp layer = mFlinger->getFactory().createEffectLayer( - LayerCreationArgs(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata())); - layer->setInitialValuesForClone(sp::fromExisting(this)); - return layer; -} - -bool EffectLayer::fillsColor() const { - return mDrawingState.color.r >= 0.0_hf && mDrawingState.color.g >= 0.0_hf && - mDrawingState.color.b >= 0.0_hf; -} - -bool EffectLayer::hasBlur() const { - return getBackgroundBlurRadius() > 0 || getDrawingState().blurRegions.size() > 0; -} - } // namespace android - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/EffectLayer.h b/services/surfaceflinger/EffectLayer.h index 747b0bfe9a..311d4935fc 100644 --- a/services/surfaceflinger/EffectLayer.h +++ b/services/surfaceflinger/EffectLayer.h @@ -19,7 +19,7 @@ #include -#include "Layer.h" +#include "BufferStateLayer.h" namespace android { @@ -27,45 +27,10 @@ namespace android { // * fill the bounds of the layer with a color // * render a shadow cast by the bounds of the layer // If no effects are enabled, the layer is considered to be invisible. -class EffectLayer : public Layer { +class EffectLayer : public BufferStateLayer { public: explicit EffectLayer(const LayerCreationArgs&); ~EffectLayer() override; - - sp getCompositionEngineLayerFE() const override; - compositionengine::LayerFECompositionState* editCompositionState() override; - - const char* getType() const override { return "EffectLayer"; } - bool isVisible() const override; - - bool setColor(const half3& color) override; - - bool setDataspace(ui::Dataspace dataspace) override; - - ui::Dataspace getDataSpace() const override; - - bool isOpaque(const Layer::State& s) const override; - -protected: - /* - * compositionengine::LayerFE overrides - */ - const compositionengine::LayerFECompositionState* getCompositionState() const override; - void preparePerFrameCompositionState() override; - std::optional prepareClientComposition( - compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) - const override; - - std::unique_ptr mCompositionState; - - sp createClone() override; - -private: - // Returns true if there is a valid color to fill. - bool fillsColor() const; - // Returns true if this layer has a blur value. - bool hasBlur() const; - bool hasSomethingToDraw() const { return fillsColor() || drawShadows() || hasBlur(); } }; } // namespace android diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 8a401eb597..930ae72286 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -124,6 +124,7 @@ Layer::Layer(const LayerCreationArgs& args) mDrawingState.acquireFence = sp::make(-1); mDrawingState.acquireFenceTime = std::make_shared(mDrawingState.acquireFence); mDrawingState.dataspace = ui::Dataspace::UNKNOWN; + mDrawingState.dataspaceRequested = false; mDrawingState.hdrMetadata.validTypes = 0; mDrawingState.surfaceDamageRegion = Region::INVALID_REGION; mDrawingState.cornerRadius = 0.0f; @@ -204,13 +205,6 @@ LayerCreationArgs::LayerCreationArgs(SurfaceFlinger* flinger, sp client, // callbacks // --------------------------------------------------------------------------- -/* - * onLayerDisplayed is only meaningful for BufferLayer, but, is called through - * Layer. So, the implementation is done in BufferLayer. When called on a - * EffectLayer object, it's essentially a NOP. - */ -void Layer::onLayerDisplayed(ftl::SharedFuture) {} - void Layer::removeRelativeZ(const std::vector& layersInTree) { if (mDrawingState.zOrderRelativeOf == nullptr) { return; @@ -521,22 +515,6 @@ sp Layer::asLayerFE() const { return sp::fromExisting(layerFE); } -sp Layer::getCompositionEngineLayerFE() const { - return nullptr; -} - -compositionengine::LayerFECompositionState* Layer::editCompositionState() { - return nullptr; -} - -const compositionengine::LayerFECompositionState* Layer::getCompositionState() const { - return nullptr; -} - -bool Layer::onPreComposition(nsecs_t) { - return false; -} - void Layer::prepareCompositionState(compositionengine::LayerFE::StateSubset subset) { using StateSubset = compositionengine::LayerFE::StateSubset; @@ -736,16 +714,6 @@ void Layer::setTransactionFlags(uint32_t mask) { mTransactionFlags |= mask; } -bool Layer::setPosition(float x, float y) { - if (mDrawingState.transform.tx() == x && mDrawingState.transform.ty() == y) return false; - mDrawingState.sequence++; - mDrawingState.transform.set(x, y); - - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - bool Layer::setChildLayer(const sp& childLayer, int32_t z) { ssize_t idx = mCurrentChildren.indexOf(childLayer); if (idx < 0) { @@ -939,24 +907,6 @@ bool Layer::setBackgroundBlurRadius(int backgroundBlurRadius) { setTransactionFlags(eTransactionNeeded); return true; } -bool Layer::setMatrix(const layer_state_t::matrix22_t& matrix) { - if (matrix.dsdx == mDrawingState.transform.dsdx() && - matrix.dtdy == mDrawingState.transform.dtdy() && - matrix.dtdx == mDrawingState.transform.dtdx() && - matrix.dsdy == mDrawingState.transform.dsdy()) { - return false; - } - - ui::Transform t; - t.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy); - - mDrawingState.sequence++; - mDrawingState.transform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy); - mDrawingState.modified = true; - - setTransactionFlags(eTransactionNeeded); - return true; -} bool Layer::setTransparentRegionHint(const Region& transparent) { mDrawingState.sequence++; @@ -2188,14 +2138,6 @@ bool Layer::isRemovedFromCurrentState() const { return mRemovedFromDrawingState; } -ui::Transform Layer::getInputTransform() const { - return getTransform(); -} - -Rect Layer::getInputBounds() const { - return getCroppedBufferSize(getDrawingState()); -} - // Applies the given transform to the region, while protecting against overflows caused by any // offsets. If applying the offset in the transform to any of the Rects in the region would result // in an overflow, they are not added to the output Region. @@ -2459,10 +2401,6 @@ bool Layer::hasInputInfo() const { mDrawingState.inputInfo.inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL); } -bool Layer::canReceiveInput() const { - return !isHiddenByPolicy(); -} - compositionengine::OutputLayer* Layer::findOutputLayerForDisplay( const DisplayDevice* display) const { if (!display) return nullptr; @@ -2684,18 +2622,6 @@ void Layer::cloneDrawingState(const Layer* from) { mDrawingState.callbackHandles = {}; } -bool Layer::setTransactionCompletedListeners(const std::vector>& handles) { - if (handles.empty()) { - return false; - } - - for (const auto& handle : handles) { - mFlinger->getTransactionCallbackInvoker().registerUnpresentedCallbackHandle(handle); - } - - return true; -} - // --------------------------------------------------------------------------- std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate) { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index b05a4a04e5..946b7d0238 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -172,6 +172,7 @@ public: // dataspace is only used by BufferStateLayer and EffectLayer ui::Dataspace dataspace; + bool dataspaceRequested; // The fields below this point are only used by BufferStateLayer uint64_t frameNumber; @@ -317,7 +318,7 @@ public: // Set a 2x2 transformation matrix on the layer. This transform // will be applied after parent transforms, but before any final // producer specified transform. - virtual bool setMatrix(const layer_state_t::matrix22_t& matrix); + virtual bool setMatrix(const layer_state_t::matrix22_t& matrix) = 0; // This second set of geometry attributes are controlled by // setGeometryAppliesWithResize, and their default mode is to be @@ -327,9 +328,9 @@ public: // setPosition operates in parent buffer space (pre parent-transform) or display // space for top-level layers. - virtual bool setPosition(float x, float y); + virtual bool setPosition(float x, float y) = 0; // Buffer space - virtual bool setCrop(const Rect& crop); + bool setCrop(const Rect& crop); // TODO(b/38182121): Could we eliminate the various latching modes by // using the layer hierarchy? @@ -338,7 +339,7 @@ public: virtual bool setRelativeLayer(const sp& relativeToHandle, int32_t relativeZ); virtual bool setAlpha(float alpha); - virtual bool setColor(const half3& /*color*/) { return false; }; + virtual bool setColor(const half3& /*color*/) = 0; // Set rounded corner radius for this layer and its children. // @@ -365,29 +366,27 @@ public: virtual bool isDimmingEnabled() const { return getDrawingState().dimmingEnabled; }; // Used only to set BufferStateLayer state - virtual bool setTransform(uint32_t /*transform*/) { return false; }; - virtual bool setTransformToDisplayInverse(bool /*transformToDisplayInverse*/) { return false; }; + virtual bool setTransform(uint32_t /*transform*/) = 0; + virtual bool setTransformToDisplayInverse(bool /*transformToDisplayInverse*/) = 0; virtual bool setBuffer(std::shared_ptr& /* buffer */, const BufferData& /* bufferData */, nsecs_t /* postTime */, nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/, std::optional /* dequeueTime */, - const FrameTimelineInfo& /*info*/) { - return false; - }; - virtual bool setDataspace(ui::Dataspace /*dataspace*/) { return false; }; - virtual bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/) { return false; }; - virtual bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/) { return false; }; - virtual bool setApi(int32_t /*api*/) { return false; }; - virtual bool setSidebandStream(const sp& /*sidebandStream*/) { return false; }; + const FrameTimelineInfo& /*info*/) = 0; + virtual bool setDataspace(ui::Dataspace /*dataspace*/) = 0; + virtual bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/) = 0; + virtual bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/) = 0; + virtual bool setApi(int32_t /*api*/) = 0; + virtual bool setSidebandStream(const sp& /*sidebandStream*/) = 0; virtual bool setTransactionCompletedListeners( - const std::vector>& /*handles*/); + const std::vector>& /*handles*/) = 0; virtual bool setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace); virtual bool setColorSpaceAgnostic(const bool agnostic); virtual bool setDimmingEnabled(const bool dimmingEnabled); virtual bool setDefaultFrameRateCompatibility(FrameRateCompatibility compatibility); virtual bool setFrameRateSelectionPriority(int32_t priority); virtual bool setFixedTransformHint(ui::Transform::RotationFlags fixedTransformHint); - virtual void setAutoRefresh(bool /* autoRefresh */) {} + virtual void setAutoRefresh(bool /* autoRefresh */) = 0; bool setDropInputMode(gui::DropInputMode); // If the variable is not set on the layer, it traverses up the tree to inherit the frame @@ -396,10 +395,10 @@ public: // virtual FrameRateCompatibility getDefaultFrameRateCompatibility() const; // - virtual ui::Dataspace getDataSpace() const { return ui::Dataspace::UNKNOWN; } + virtual ui::Dataspace getDataSpace() const = 0; - virtual sp getCompositionEngineLayerFE() const; - virtual compositionengine::LayerFECompositionState* editCompositionState(); + virtual sp getCompositionEngineLayerFE() const = 0; + virtual compositionengine::LayerFECompositionState* editCompositionState() = 0; // If we have received a new buffer this frame, we will pass its surface // damage down to hardware composer. Otherwise, we must send a region with @@ -415,18 +414,18 @@ public: * pixel format includes an alpha channel) and the "opaque" flag set * on the layer. It does not examine the current plane alpha value. */ - virtual bool isOpaque(const Layer::State&) const { return false; } + virtual bool isOpaque(const Layer::State&) const = 0; /* * Returns whether this layer can receive input. */ - virtual bool canReceiveInput() const; + virtual bool canReceiveInput() const = 0; /* * isProtected - true if the layer may contain protected contents in the * GRALLOC_USAGE_PROTECTED sense. */ - virtual bool isProtected() const { return false; } + virtual bool isProtected() const = 0; /* * isFixedSize - true if content has a fixed size @@ -436,7 +435,7 @@ public: /* * usesSourceCrop - true if content should use a source crop */ - virtual bool usesSourceCrop() const { return false; } + virtual bool usesSourceCrop() const = 0; // Most layers aren't created from the main thread, and therefore need to // grab the SF state lock to access HWC, but ContainerLayer does, so we need @@ -444,11 +443,9 @@ public: virtual bool isCreatedFromMainThread() const { return false; } ui::Transform getActiveTransform(const Layer::State& s) const { return s.transform; } - Region getActiveTransparentRegion(const Layer::State& s) const { - return s.transparentRegionHint; - } - virtual Rect getCrop(const Layer::State& s) const { return s.crop; } - virtual bool needsFiltering(const DisplayDevice*) const { return false; } + virtual Region getActiveTransparentRegion(const Layer::State& s) const = 0; + Rect getCrop(const Layer::State& s) const { return s.crop; } + virtual bool needsFiltering(const DisplayDevice*) const = 0; // True if this layer requires filtering // This method is distinct from needsFiltering() in how the filter @@ -459,13 +456,11 @@ public: // different. // If the parent transform needs to be undone when capturing the layer, then // the inverse parent transform is also required. - virtual bool needsFilteringForScreenshots(const DisplayDevice*, const ui::Transform&) const { - return false; - } + virtual bool needsFilteringForScreenshots(const DisplayDevice*, const ui::Transform&) const = 0; virtual void updateCloneBufferInfo(){}; - virtual bool isHdrY410() const { return false; } + virtual bool isHdrY410() const = 0; /* * called after composition. @@ -474,10 +469,10 @@ public: virtual void onPostComposition(const DisplayDevice*, const std::shared_ptr& /*glDoneFence*/, const std::shared_ptr& /*presentFence*/, - const CompositorTiming&) {} + const CompositorTiming&) = 0; // If a buffer was replaced this frame, release the former buffer - virtual void releasePendingBuffer(nsecs_t /*dequeueReadyTime*/) { } + virtual void releasePendingBuffer(nsecs_t /*dequeueReadyTime*/) = 0; /* * latchBuffer - called each time the screen is redrawn and returns whether @@ -485,34 +480,30 @@ public: * operation, so this should be set only if needed). Typically this is used * to figure out if the content or size of a surface has changed. */ - virtual bool latchBuffer(bool& /*recomputeVisibleRegions*/, nsecs_t /*latchTime*/) { - return false; - } + virtual bool latchBuffer(bool& /*recomputeVisibleRegions*/, nsecs_t /*latchTime*/) = 0; - virtual void latchAndReleaseBuffer() {} + virtual void latchAndReleaseBuffer() = 0; /* * returns the rectangle that crops the content of the layer and scales it * to the layer's size. */ - virtual Rect getBufferCrop() const { return Rect(); } + virtual Rect getBufferCrop() const = 0; /* * Returns the transform applied to the buffer. */ - virtual uint32_t getBufferTransform() const { return 0; } + virtual uint32_t getBufferTransform() const = 0; - virtual sp getBuffer() const { return nullptr; } - virtual const std::shared_ptr& getExternalTexture() const { - return mDrawingState.buffer; - }; + virtual sp getBuffer() const = 0; + virtual const std::shared_ptr& getExternalTexture() const = 0; - virtual ui::Transform::RotationFlags getTransformHint() const { return ui::Transform::ROT_0; } + virtual ui::Transform::RotationFlags getTransformHint() const = 0; /* * Returns if a frame is ready */ - virtual bool hasReadyFrame() const { return false; } + virtual bool hasReadyFrame() const = 0; virtual int32_t getQueuedFrameCount() const { return 0; } @@ -520,19 +511,17 @@ public: * Returns active buffer size in the correct orientation. Buffer size is determined by undoing * any buffer transformations. If the layer has no buffer then return INVALID_RECT. */ - virtual Rect getBufferSize(const Layer::State&) const { return Rect::INVALID_RECT; } + virtual Rect getBufferSize(const Layer::State&) const = 0; /** * Returns the source bounds. If the bounds are not defined, it is inferred from the * buffer size. Failing that, the bounds are determined from the passed in parent bounds. * For the root layer, this is the display viewport size. */ - virtual FloatRect computeSourceBounds(const FloatRect& parentBounds) const { - return parentBounds; - } + virtual FloatRect computeSourceBounds(const FloatRect& parentBounds) const = 0; virtual FrameRate getFrameRateForLayerTree() const; - virtual bool getTransformToDisplayInverse() const { return false; } + virtual bool getTransformToDisplayInverse() const = 0; // Returns how rounded corners should be drawn for this layer. // A layer can override its parent's rounded corner settings if the parent's rounded @@ -548,19 +537,19 @@ public: * overrides this so we can generate input-info for Buffered layers that don't * have them (for input occlusion detection checks). */ - virtual bool needsInputInfo() const { return hasInputInfo(); } + virtual bool needsInputInfo() const = 0; // Implements RefBase. void onFirstRef() override; // implements compositionengine::LayerFE - const compositionengine::LayerFECompositionState* getCompositionState() const override; - bool onPreComposition(nsecs_t) override; + virtual const compositionengine::LayerFECompositionState* getCompositionState() const = 0; + virtual bool onPreComposition(nsecs_t) = 0; void prepareCompositionState(compositionengine::LayerFE::StateSubset subset) override; std::optional prepareClientComposition( compositionengine::LayerFE::ClientCompositionTargetSettings&) const override; - void onLayerDisplayed(ftl::SharedFuture) override; + virtual void onLayerDisplayed(ftl::SharedFuture) = 0; void setWasClientComposed(const sp& fence) override { mLastClientCompositionFence = fence; @@ -838,13 +827,13 @@ public: float getBorderWidth(); const half4& getBorderColor(); - virtual bool setBufferCrop(const Rect& /* bufferCrop */) { return false; } - virtual bool setDestinationFrame(const Rect& /* destinationFrame */) { return false; } - virtual std::atomic* getPendingBufferCounter() { return nullptr; } - virtual std::string getPendingBufferCounterName() { return ""; } - virtual bool updateGeometry() { return false; } + virtual bool setBufferCrop(const Rect& /* bufferCrop */) = 0; + virtual bool setDestinationFrame(const Rect& /* destinationFrame */) = 0; + virtual std::atomic* getPendingBufferCounter() = 0; + virtual std::string getPendingBufferCounterName() = 0; + virtual bool updateGeometry() = 0; - virtual bool simpleBufferUpdate(const layer_state_t&) const { return false; } + virtual bool simpleBufferUpdate(const layer_state_t&) const = 0; protected: friend class impl::SurfaceInterceptor; @@ -899,7 +888,7 @@ protected: compositionengine::OutputLayer* findOutputLayerForDisplay(const DisplayDevice*) const; bool usingRelativeZ(LayerVector::StateSet) const; - virtual ui::Transform getInputTransform() const; + virtual ui::Transform getInputTransform() const = 0; /** * Get the bounds in layer space within which this layer can receive input. * @@ -913,7 +902,7 @@ protected: * "replaceTouchableRegionWithCrop" is specified. In this case, the layer will receive input * in this layer's space, regardless of the specified crop layer. */ - virtual Rect getInputBounds() const; + virtual Rect getInputBounds() const = 0; // constant sp mFlinger; @@ -984,7 +973,7 @@ protected: sp mLastClientCompositionFence; bool mClearClientCompositionFenceOnLayerDisplayed = false; private: - virtual void setTransformHint(ui::Transform::RotationFlags) {} + virtual void setTransformHint(ui::Transform::RotationFlags) = 0; // Returns true if the layer can draw shadows on its border. virtual bool canDrawShadows() const { return true; } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 3acf203b0a..57ca859a24 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4575,7 +4575,11 @@ status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, sp* outHa switch (args.flags & ISurfaceComposerClient::eFXSurfaceMask) { case ISurfaceComposerClient::eFXSurfaceBufferQueue: - case ISurfaceComposerClient::eFXSurfaceBufferState: { + case ISurfaceComposerClient::eFXSurfaceContainer: + case ISurfaceComposerClient::eFXSurfaceBufferState: + args.flags |= ISurfaceComposerClient::eNoColorFill; + FMT_FALLTHROUGH; + case ISurfaceComposerClient::eFXSurfaceEffect: { result = createBufferStateLayer(args, outHandle, &layer); std::atomic* pendingBufferCounter = layer->getPendingBufferCounter(); if (pendingBufferCounter) { @@ -4584,12 +4588,6 @@ status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, sp* outHa pendingBufferCounter); } } break; - case ISurfaceComposerClient::eFXSurfaceContainer: - args.flags |= ISurfaceComposerClient::eNoColorFill; - FMT_FALLTHROUGH; - case ISurfaceComposerClient::eFXSurfaceEffect: - result = createEffectLayer(args, outHandle, &layer); - break; default: result = BAD_VALUE; break; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h index 1a49ead275..2c62b286ef 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h @@ -24,8 +24,8 @@ #include +#include "BufferStateLayer.h" #include "Clock.h" -#include "Layer.h" #include "Scheduler/EventThread.h" #include "Scheduler/RefreshRateConfigs.h" #include "Scheduler/Scheduler.h" @@ -66,10 +66,10 @@ private: std::chrono::steady_clock::time_point mNow; }; -class FuzzImplLayer : public Layer { +class FuzzImplLayer : public BufferStateLayer { public: FuzzImplLayer(SurfaceFlinger* flinger, std::string name) - : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) {} + : BufferStateLayer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) {} explicit FuzzImplLayer(SurfaceFlinger* flinger) : FuzzImplLayer(flinger, "FuzzLayer") {} const char* getType() const override { return ""; } diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h index d086d79834..48d05cbcba 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockLayer.h +++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h @@ -18,14 +18,14 @@ #include -#include "Layer.h" +#include "BufferStateLayer.h" namespace android::mock { -class MockLayer : public Layer { +class MockLayer : public BufferStateLayer { public: MockLayer(SurfaceFlinger* flinger, std::string name) - : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) { + : BufferStateLayer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) { EXPECT_CALL(*this, getDefaultFrameRateCompatibility()) .WillOnce(testing::Return(scheduler::LayerInfo::FrameRateCompatibility::Default)); } -- GitLab From bb25f8043da42c17f58d99d717240bbc90555eaa Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Tue, 30 Aug 2022 23:02:34 +0000 Subject: [PATCH 0258/1310] SF: Merge BufferStateLayer into Layer Test: go/wm-smoke Bug: 238781169 Change-Id: I1521ffa6995ce41a04a664ff65516431aead4dca --- services/surfaceflinger/Android.bp | 1 - services/surfaceflinger/BufferStateLayer.cpp | 1716 ----------------- services/surfaceflinger/BufferStateLayer.h | 295 +-- services/surfaceflinger/Layer.cpp | 1622 +++++++++++++++- services/surfaceflinger/Layer.h | 289 ++- .../SurfaceFlingerDefaultFactory.cpp | 1 + .../fuzzer/surfaceflinger_scheduler_fuzzer.h | 6 +- 7 files changed, 1836 insertions(+), 2094 deletions(-) delete mode 100644 services/surfaceflinger/BufferStateLayer.cpp diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index 662645100f..5e9fe65225 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -141,7 +141,6 @@ filegroup { name: "libsurfaceflinger_sources", srcs: [ "BackgroundExecutor.cpp", - "BufferStateLayer.cpp", "ClientCache.cpp", "Client.cpp", "EffectLayer.cpp", diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp deleted file mode 100644 index b939752be5..0000000000 --- a/services/surfaceflinger/BufferStateLayer.cpp +++ /dev/null @@ -1,1716 +0,0 @@ -/* - * Copyright (C) 2017 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_NDEBUG 0 -#undef LOG_TAG -#define LOG_TAG "BufferStateLayer" -#define ATRACE_TAG ATRACE_TAG_GRAPHICS - -#include "BufferStateLayer.h" - -#include - -#include -#include -#include -#include -#include -#include "TunnelModeEnabledReporter.h" - -#include -#include "EffectLayer.h" -#include "FrameTracer/FrameTracer.h" -#include "TimeStats/TimeStats.h" - -#define EARLY_RELEASE_ENABLED false - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "Colorizer.h" -#include "DisplayDevice.h" -#include "FrameTracer/FrameTracer.h" -#include "TimeStats/TimeStats.h" - -namespace android { - -using PresentState = frametimeline::SurfaceFrame::PresentState; -using gui::WindowInfo; -namespace { -static constexpr float defaultMaxLuminance = 1000.0; - -constexpr mat4 inverseOrientation(uint32_t transform) { - const mat4 flipH(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1); - const mat4 flipV(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1); - const mat4 rot90(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1); - mat4 tr; - - if (transform & NATIVE_WINDOW_TRANSFORM_ROT_90) { - tr = tr * rot90; - } - if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_H) { - tr = tr * flipH; - } - if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_V) { - tr = tr * flipV; - } - return inverse(tr); -} - -bool assignTransform(ui::Transform* dst, ui::Transform& from) { - if (*dst == from) { - return false; - } - *dst = from; - return true; -} - -TimeStats::SetFrameRateVote frameRateToSetFrameRateVotePayload(Layer::FrameRate frameRate) { - using FrameRateCompatibility = TimeStats::SetFrameRateVote::FrameRateCompatibility; - using Seamlessness = TimeStats::SetFrameRateVote::Seamlessness; - const auto frameRateCompatibility = [frameRate] { - switch (frameRate.type) { - case Layer::FrameRateCompatibility::Default: - return FrameRateCompatibility::Default; - case Layer::FrameRateCompatibility::ExactOrMultiple: - return FrameRateCompatibility::ExactOrMultiple; - default: - return FrameRateCompatibility::Undefined; - } - }(); - - const auto seamlessness = [frameRate] { - switch (frameRate.seamlessness) { - case scheduler::Seamlessness::OnlySeamless: - return Seamlessness::ShouldBeSeamless; - case scheduler::Seamlessness::SeamedAndSeamless: - return Seamlessness::NotRequired; - default: - return Seamlessness::Undefined; - } - }(); - - return TimeStats::SetFrameRateVote{.frameRate = frameRate.rate.getValue(), - .frameRateCompatibility = frameRateCompatibility, - .seamlessness = seamlessness}; -} -} // namespace - -BufferStateLayer::BufferStateLayer(const LayerCreationArgs& args) - : Layer(args), - mTextureName(args.textureName), - mCompositionState{mFlinger->getCompositionEngine().createLayerFECompositionState()}, - mHwcSlotGenerator(sp::make()) { - ALOGV("Creating Layer %s", getDebugName()); - - mPremultipliedAlpha = !(args.flags & ISurfaceComposerClient::eNonPremultiplied); - mPotentialCursor = args.flags & ISurfaceComposerClient::eCursorWindow; - mProtectedByApp = args.flags & ISurfaceComposerClient::eProtectedByApp; - mDrawingState.dataspace = ui::Dataspace::V0_SRGB; -} - -BufferStateLayer::~BufferStateLayer() { - // The original layer and the clone layer share the same texture and buffer. Therefore, only - // one of the layers, in this case the original layer, needs to handle the deletion. The - // original layer and the clone should be removed at the same time so there shouldn't be any - // issue with the clone layer trying to use the texture. - if (mBufferInfo.mBuffer != nullptr) { - callReleaseBufferCallback(mDrawingState.releaseBufferListener, - mBufferInfo.mBuffer->getBuffer(), mBufferInfo.mFrameNumber, - mBufferInfo.mFence, - mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate( - mOwnerUid)); - } - if (!isClone()) { - // The original layer and the clone layer share the same texture. Therefore, only one of - // the layers, in this case the original layer, needs to handle the deletion. The original - // layer and the clone should be removed at the same time so there shouldn't be any issue - // with the clone layer trying to use the deleted texture. - mFlinger->deleteTextureAsync(mTextureName); - } - const int32_t layerId = getSequence(); - mFlinger->mTimeStats->onDestroy(layerId); - mFlinger->mFrameTracer->onDestroy(layerId); -} - -void BufferStateLayer::callReleaseBufferCallback(const sp& listener, - const sp& buffer, - uint64_t framenumber, - const sp& releaseFence, - uint32_t currentMaxAcquiredBufferCount) { - if (!listener) { - return; - } - ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64, getDebugName(), framenumber); - listener->onReleaseBuffer({buffer->getId(), framenumber}, - releaseFence ? releaseFence : Fence::NO_FENCE, - currentMaxAcquiredBufferCount); -} - -// ----------------------------------------------------------------------- -// Interface implementation for Layer -// ----------------------------------------------------------------------- -void BufferStateLayer::onLayerDisplayed(ftl::SharedFuture futureFenceResult) { - // If we are displayed on multiple displays in a single composition cycle then we would - // need to do careful tracking to enable the use of the mLastClientCompositionFence. - // For example we can only use it if all the displays are client comp, and we need - // to merge all the client comp fences. We could do this, but for now we just - // disable the optimization when a layer is composed on multiple displays. - if (mClearClientCompositionFenceOnLayerDisplayed) { - mLastClientCompositionFence = nullptr; - } else { - mClearClientCompositionFenceOnLayerDisplayed = true; - } - - // The previous release fence notifies the client that SurfaceFlinger is done with the previous - // buffer that was presented on this layer. The first transaction that came in this frame that - // replaced the previous buffer on this layer needs this release fence, because the fence will - // let the client know when that previous buffer is removed from the screen. - // - // Every other transaction on this layer does not need a release fence because no other - // Transactions that were set on this layer this frame are going to have their preceeding buffer - // removed from the display this frame. - // - // For example, if we have 3 transactions this frame. The first transaction doesn't contain a - // buffer so it doesn't need a previous release fence because the layer still needs the previous - // buffer. The second transaction contains a buffer so it needs a previous release fence because - // the previous buffer will be released this frame. The third transaction also contains a - // buffer. It replaces the buffer in the second transaction. The buffer in the second - // transaction will now no longer be presented so it is released immediately and the third - // transaction doesn't need a previous release fence. - sp ch; - for (auto& handle : mDrawingState.callbackHandles) { - if (handle->releasePreviousBuffer && - mDrawingState.releaseBufferEndpoint == handle->listener) { - ch = handle; - break; - } - } - - // Prevent tracing the same release multiple times. - if (mPreviousFrameNumber != mPreviousReleasedFrameNumber) { - mPreviousReleasedFrameNumber = mPreviousFrameNumber; - } - - if (ch != nullptr) { - ch->previousReleaseCallbackId = mPreviousReleaseCallbackId; - ch->previousReleaseFences.emplace_back(std::move(futureFenceResult)); - ch->name = mName; - } -} - -void BufferStateLayer::onSurfaceFrameCreated( - const std::shared_ptr& surfaceFrame) { - while (mPendingJankClassifications.size() >= kPendingClassificationMaxSurfaceFrames) { - // Too many SurfaceFrames pending classification. The front of the deque is probably not - // tracked by FrameTimeline and will never be presented. This will only result in a memory - // leak. - ALOGW("Removing the front of pending jank deque from layer - %s to prevent memory leak", - mName.c_str()); - std::string miniDump = mPendingJankClassifications.front()->miniDump(); - ALOGD("Head SurfaceFrame mini dump\n%s", miniDump.c_str()); - mPendingJankClassifications.pop_front(); - } - mPendingJankClassifications.emplace_back(surfaceFrame); -} - -void BufferStateLayer::releasePendingBuffer(nsecs_t dequeueReadyTime) { - for (const auto& handle : mDrawingState.callbackHandles) { - handle->transformHint = mTransformHint; - handle->dequeueReadyTime = dequeueReadyTime; - handle->currentMaxAcquiredBufferCount = - mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mOwnerUid); - ATRACE_FORMAT_INSTANT("releasePendingBuffer %s - %" PRIu64, getDebugName(), - handle->previousReleaseCallbackId.framenumber); - } - - for (auto& handle : mDrawingState.callbackHandles) { - if (handle->releasePreviousBuffer && - mDrawingState.releaseBufferEndpoint == handle->listener) { - handle->previousReleaseCallbackId = mPreviousReleaseCallbackId; - break; - } - } - - std::vector jankData; - jankData.reserve(mPendingJankClassifications.size()); - while (!mPendingJankClassifications.empty() - && mPendingJankClassifications.front()->getJankType()) { - std::shared_ptr surfaceFrame = - mPendingJankClassifications.front(); - mPendingJankClassifications.pop_front(); - jankData.emplace_back( - JankData(surfaceFrame->getToken(), surfaceFrame->getJankType().value())); - } - - mFlinger->getTransactionCallbackInvoker().addCallbackHandles( - mDrawingState.callbackHandles, jankData); - - sp releaseFence = Fence::NO_FENCE; - for (auto& handle : mDrawingState.callbackHandles) { - if (handle->releasePreviousBuffer && - mDrawingState.releaseBufferEndpoint == handle->listener) { - releaseFence = - handle->previousReleaseFence ? handle->previousReleaseFence : Fence::NO_FENCE; - break; - } - } - - mDrawingState.callbackHandles = {}; -} - -bool BufferStateLayer::willPresentCurrentTransaction() const { - // Returns true if the most recent Transaction applied to CurrentState will be presented. - return (getSidebandStreamChanged() || getAutoRefresh() || - (mDrawingState.modified && - (mDrawingState.buffer != nullptr || mDrawingState.bgColorLayer != nullptr))); -} - -bool BufferStateLayer::setTransform(uint32_t transform) { - if (mDrawingState.bufferTransform == transform) return false; - mDrawingState.bufferTransform = transform; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -bool BufferStateLayer::setTransformToDisplayInverse(bool transformToDisplayInverse) { - if (mDrawingState.transformToDisplayInverse == transformToDisplayInverse) return false; - mDrawingState.sequence++; - mDrawingState.transformToDisplayInverse = transformToDisplayInverse; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -bool BufferStateLayer::setBufferCrop(const Rect& bufferCrop) { - if (mDrawingState.bufferCrop == bufferCrop) return false; - - mDrawingState.sequence++; - mDrawingState.bufferCrop = bufferCrop; - - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -bool BufferStateLayer::setDestinationFrame(const Rect& destinationFrame) { - if (mDrawingState.destinationFrame == destinationFrame) return false; - - mDrawingState.sequence++; - mDrawingState.destinationFrame = destinationFrame; - - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -// Translate destination frame into scale and position. If a destination frame is not set, use the -// provided scale and position -bool BufferStateLayer::updateGeometry() { - if ((mDrawingState.flags & layer_state_t::eIgnoreDestinationFrame) || - mDrawingState.destinationFrame.isEmpty()) { - // If destination frame is not set, use the requested transform set via - // BufferStateLayer::setPosition and BufferStateLayer::setMatrix. - return assignTransform(&mDrawingState.transform, mRequestedTransform); - } - - Rect destRect = mDrawingState.destinationFrame; - int32_t destW = destRect.width(); - int32_t destH = destRect.height(); - if (destRect.left < 0) { - destRect.left = 0; - destRect.right = destW; - } - if (destRect.top < 0) { - destRect.top = 0; - destRect.bottom = destH; - } - - if (!mDrawingState.buffer) { - ui::Transform t; - t.set(destRect.left, destRect.top); - return assignTransform(&mDrawingState.transform, t); - } - - uint32_t bufferWidth = mDrawingState.buffer->getWidth(); - uint32_t bufferHeight = mDrawingState.buffer->getHeight(); - // Undo any transformations on the buffer. - if (mDrawingState.bufferTransform & ui::Transform::ROT_90) { - std::swap(bufferWidth, bufferHeight); - } - uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags(); - if (mDrawingState.transformToDisplayInverse) { - if (invTransform & ui::Transform::ROT_90) { - std::swap(bufferWidth, bufferHeight); - } - } - - float sx = destW / static_cast(bufferWidth); - float sy = destH / static_cast(bufferHeight); - ui::Transform t; - t.set(sx, 0, 0, sy); - t.set(destRect.left, destRect.top); - return assignTransform(&mDrawingState.transform, t); -} - -bool BufferStateLayer::setMatrix(const layer_state_t::matrix22_t& matrix) { - if (mRequestedTransform.dsdx() == matrix.dsdx && mRequestedTransform.dtdy() == matrix.dtdy && - mRequestedTransform.dtdx() == matrix.dtdx && mRequestedTransform.dsdy() == matrix.dsdy) { - return false; - } - - mRequestedTransform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy); - - mDrawingState.sequence++; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - - return true; -} - -bool BufferStateLayer::setPosition(float x, float y) { - if (mRequestedTransform.tx() == x && mRequestedTransform.ty() == y) { - return false; - } - - mRequestedTransform.set(x, y); - - mDrawingState.sequence++; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - - return true; -} - -bool BufferStateLayer::setBuffer(std::shared_ptr& buffer, - const BufferData& bufferData, nsecs_t postTime, - nsecs_t desiredPresentTime, bool isAutoTimestamp, - std::optional dequeueTime, - const FrameTimelineInfo& info) { - ATRACE_CALL(); - - if (!buffer) { - return false; - } - - const bool frameNumberChanged = - bufferData.flags.test(BufferData::BufferDataChange::frameNumberChanged); - const uint64_t frameNumber = - frameNumberChanged ? bufferData.frameNumber : mDrawingState.frameNumber + 1; - - if (mDrawingState.buffer) { - mReleasePreviousBuffer = true; - if (!mBufferInfo.mBuffer || - (!mDrawingState.buffer->hasSameBuffer(*mBufferInfo.mBuffer) || - mDrawingState.frameNumber != mBufferInfo.mFrameNumber)) { - // If mDrawingState has a buffer, and we are about to update again - // before swapping to drawing state, then the first buffer will be - // dropped and we should decrement the pending buffer count and - // call any release buffer callbacks if set. - callReleaseBufferCallback(mDrawingState.releaseBufferListener, - mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber, - mDrawingState.acquireFence, - mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate( - mOwnerUid)); - decrementPendingBufferCount(); - if (mDrawingState.bufferSurfaceFrameTX != nullptr && - mDrawingState.bufferSurfaceFrameTX->getPresentState() != PresentState::Presented) { - addSurfaceFrameDroppedForBuffer(mDrawingState.bufferSurfaceFrameTX); - mDrawingState.bufferSurfaceFrameTX.reset(); - } - } else if (EARLY_RELEASE_ENABLED && mLastClientCompositionFence != nullptr) { - callReleaseBufferCallback(mDrawingState.releaseBufferListener, - mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber, - mLastClientCompositionFence, - mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate( - mOwnerUid)); - mLastClientCompositionFence = nullptr; - } - } - - mDrawingState.frameNumber = frameNumber; - mDrawingState.releaseBufferListener = bufferData.releaseBufferListener; - mDrawingState.buffer = std::move(buffer); - mDrawingState.clientCacheId = bufferData.cachedBuffer; - - mDrawingState.acquireFence = bufferData.flags.test(BufferData::BufferDataChange::fenceChanged) - ? bufferData.acquireFence - : Fence::NO_FENCE; - mDrawingState.acquireFenceTime = std::make_unique(mDrawingState.acquireFence); - if (mDrawingState.acquireFenceTime->getSignalTime() == Fence::SIGNAL_TIME_PENDING) { - // We latched this buffer unsiganled, so we need to pass the acquire fence - // on the callback instead of just the acquire time, since it's unknown at - // this point. - mCallbackHandleAcquireTimeOrFence = mDrawingState.acquireFence; - } else { - mCallbackHandleAcquireTimeOrFence = mDrawingState.acquireFenceTime->getSignalTime(); - } - - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - - const int32_t layerId = getSequence(); - mFlinger->mTimeStats->setPostTime(layerId, mDrawingState.frameNumber, getName().c_str(), - mOwnerUid, postTime, getGameMode()); - mDrawingState.desiredPresentTime = desiredPresentTime; - mDrawingState.isAutoTimestamp = isAutoTimestamp; - - const nsecs_t presentTime = [&] { - if (!isAutoTimestamp) return desiredPresentTime; - - const auto prediction = - mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken(info.vsyncId); - if (prediction.has_value()) return prediction->presentTime; - - return static_cast(0); - }(); - - using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType; - mFlinger->mScheduler->recordLayerHistory(this, presentTime, LayerUpdateType::Buffer); - - setFrameTimelineVsyncForBufferTransaction(info, postTime); - - if (dequeueTime && *dequeueTime != 0) { - const uint64_t bufferId = mDrawingState.buffer->getId(); - mFlinger->mFrameTracer->traceNewLayer(layerId, getName().c_str()); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, *dequeueTime, - FrameTracer::FrameEvent::DEQUEUE); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, postTime, - FrameTracer::FrameEvent::QUEUE); - } - - mDrawingState.releaseBufferEndpoint = bufferData.releaseBufferEndpoint; - return true; -} - -bool BufferStateLayer::setDataspace(ui::Dataspace dataspace) { - mDrawingState.dataspaceRequested = true; - if (mDrawingState.dataspace == dataspace) return false; - mDrawingState.dataspace = dataspace; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -bool BufferStateLayer::setHdrMetadata(const HdrMetadata& hdrMetadata) { - if (mDrawingState.hdrMetadata == hdrMetadata) return false; - mDrawingState.hdrMetadata = hdrMetadata; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -bool BufferStateLayer::setSurfaceDamageRegion(const Region& surfaceDamage) { - mDrawingState.surfaceDamageRegion = surfaceDamage; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -bool BufferStateLayer::setApi(int32_t api) { - if (mDrawingState.api == api) return false; - mDrawingState.api = api; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -bool BufferStateLayer::setSidebandStream(const sp& sidebandStream) { - if (mDrawingState.sidebandStream == sidebandStream) return false; - - if (mDrawingState.sidebandStream != nullptr && sidebandStream == nullptr) { - mFlinger->mTunnelModeEnabledReporter->decrementTunnelModeCount(); - } else if (sidebandStream != nullptr) { - mFlinger->mTunnelModeEnabledReporter->incrementTunnelModeCount(); - } - - mDrawingState.sidebandStream = sidebandStream; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - if (!mSidebandStreamChanged.exchange(true)) { - // mSidebandStreamChanged was false - mFlinger->onLayerUpdate(); - } - return true; -} - -bool BufferStateLayer::setTransactionCompletedListeners( - const std::vector>& handles) { - // If there is no handle, we will not send a callback so reset mReleasePreviousBuffer and return - if (handles.empty()) { - mReleasePreviousBuffer = false; - return false; - } - - const bool willPresent = willPresentCurrentTransaction(); - - for (const auto& handle : handles) { - // If this transaction set a buffer on this layer, release its previous buffer - handle->releasePreviousBuffer = mReleasePreviousBuffer; - - // If this layer will be presented in this frame - if (willPresent) { - // If this transaction set an acquire fence on this layer, set its acquire time - handle->acquireTimeOrFence = mCallbackHandleAcquireTimeOrFence; - handle->frameNumber = mDrawingState.frameNumber; - - // Store so latched time and release fence can be set - mDrawingState.callbackHandles.push_back(handle); - - } else { // If this layer will NOT need to be relatched and presented this frame - // Notify the transaction completed thread this handle is done - mFlinger->getTransactionCallbackInvoker().registerUnpresentedCallbackHandle(handle); - } - } - - mReleasePreviousBuffer = false; - mCallbackHandleAcquireTimeOrFence = -1; - - return willPresent; -} - -Rect BufferStateLayer::getBufferSize(const State& /*s*/) const { - // for buffer state layers we use the display frame size as the buffer size. - - if (mBufferInfo.mBuffer == nullptr) { - return Rect::INVALID_RECT; - } - - uint32_t bufWidth = mBufferInfo.mBuffer->getWidth(); - uint32_t bufHeight = mBufferInfo.mBuffer->getHeight(); - - // Undo any transformations on the buffer and return the result. - if (mBufferInfo.mTransform & ui::Transform::ROT_90) { - std::swap(bufWidth, bufHeight); - } - - if (getTransformToDisplayInverse()) { - uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags(); - if (invTransform & ui::Transform::ROT_90) { - std::swap(bufWidth, bufHeight); - } - } - - return Rect(0, 0, static_cast(bufWidth), static_cast(bufHeight)); -} - -FloatRect BufferStateLayer::computeSourceBounds(const FloatRect& parentBounds) const { - if (mBufferInfo.mBuffer == nullptr) { - return parentBounds; - } - - return getBufferSize(getDrawingState()).toFloatRect(); -} - -// ----------------------------------------------------------------------- -bool BufferStateLayer::fenceHasSignaled() const { - if (SurfaceFlinger::enableLatchUnsignaledConfig != LatchUnsignaledConfig::Disabled) { - return true; - } - - const bool fenceSignaled = - getDrawingState().acquireFence->getStatus() == Fence::Status::Signaled; - if (!fenceSignaled) { - mFlinger->mTimeStats->incrementLatchSkipped(getSequence(), - TimeStats::LatchSkipReason::LateAcquire); - } - - return fenceSignaled; -} - -bool BufferStateLayer::onPreComposition(nsecs_t refreshStartTime) { - for (const auto& handle : mDrawingState.callbackHandles) { - handle->refreshStartTime = refreshStartTime; - } - return hasReadyFrame(); -} - -void BufferStateLayer::setAutoRefresh(bool autoRefresh) { - mDrawingState.autoRefresh = autoRefresh; -} - -bool BufferStateLayer::latchSidebandStream(bool& recomputeVisibleRegions) { - // We need to update the sideband stream if the layer has both a buffer and a sideband stream. - editCompositionState()->sidebandStreamHasFrame = hasFrameUpdate() && mSidebandStream.get(); - - if (mSidebandStreamChanged.exchange(false)) { - const State& s(getDrawingState()); - // mSidebandStreamChanged was true - mSidebandStream = s.sidebandStream; - editCompositionState()->sidebandStream = mSidebandStream; - if (mSidebandStream != nullptr) { - setTransactionFlags(eTransactionNeeded); - mFlinger->setTransactionFlags(eTraversalNeeded); - } - recomputeVisibleRegions = true; - - return true; - } - return false; -} - -bool BufferStateLayer::hasFrameUpdate() const { - const State& c(getDrawingState()); - return (mDrawingStateModified || mDrawingState.modified) && (c.buffer != nullptr || c.bgColorLayer != nullptr); -} - -void BufferStateLayer::updateTexImage(nsecs_t latchTime) { - const State& s(getDrawingState()); - - if (!s.buffer) { - if (s.bgColorLayer) { - for (auto& handle : mDrawingState.callbackHandles) { - handle->latchTime = latchTime; - } - } - return; - } - - for (auto& handle : mDrawingState.callbackHandles) { - if (handle->frameNumber == mDrawingState.frameNumber) { - handle->latchTime = latchTime; - } - } - - const int32_t layerId = getSequence(); - const uint64_t bufferId = mDrawingState.buffer->getId(); - const uint64_t frameNumber = mDrawingState.frameNumber; - const auto acquireFence = std::make_shared(mDrawingState.acquireFence); - mFlinger->mTimeStats->setAcquireFence(layerId, frameNumber, acquireFence); - mFlinger->mTimeStats->setLatchTime(layerId, frameNumber, latchTime); - - mFlinger->mFrameTracer->traceFence(layerId, bufferId, frameNumber, acquireFence, - FrameTracer::FrameEvent::ACQUIRE_FENCE); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, latchTime, - FrameTracer::FrameEvent::LATCH); - - auto& bufferSurfaceFrame = mDrawingState.bufferSurfaceFrameTX; - if (bufferSurfaceFrame != nullptr && - bufferSurfaceFrame->getPresentState() != PresentState::Presented) { - // Update only if the bufferSurfaceFrame wasn't already presented. A Presented - // bufferSurfaceFrame could be seen here if a pending state was applied successfully and we - // are processing the next state. - addSurfaceFramePresentedForBuffer(bufferSurfaceFrame, - mDrawingState.acquireFenceTime->getSignalTime(), - latchTime); - mDrawingState.bufferSurfaceFrameTX.reset(); - } - - std::deque> remainingHandles; - mFlinger->getTransactionCallbackInvoker() - .addOnCommitCallbackHandles(mDrawingState.callbackHandles, remainingHandles); - mDrawingState.callbackHandles = remainingHandles; - - mDrawingStateModified = false; -} - -void BufferStateLayer::gatherBufferInfo() { - if (!mBufferInfo.mBuffer || !mDrawingState.buffer->hasSameBuffer(*mBufferInfo.mBuffer)) { - decrementPendingBufferCount(); - } - - mPreviousReleaseCallbackId = {getCurrentBufferId(), mBufferInfo.mFrameNumber}; - mBufferInfo.mBuffer = mDrawingState.buffer; - mBufferInfo.mFence = mDrawingState.acquireFence; - mBufferInfo.mFrameNumber = mDrawingState.frameNumber; - mBufferInfo.mPixelFormat = - !mBufferInfo.mBuffer ? PIXEL_FORMAT_NONE : mBufferInfo.mBuffer->getPixelFormat(); - mBufferInfo.mFrameLatencyNeeded = true; - mBufferInfo.mDesiredPresentTime = mDrawingState.desiredPresentTime; - mBufferInfo.mFenceTime = std::make_shared(mDrawingState.acquireFence); - mBufferInfo.mFence = mDrawingState.acquireFence; - mBufferInfo.mTransform = mDrawingState.bufferTransform; - auto lastDataspace = mBufferInfo.mDataspace; - mBufferInfo.mDataspace = translateDataspace(mDrawingState.dataspace); - if (lastDataspace != mBufferInfo.mDataspace) { - mFlinger->mSomeDataspaceChanged = true; - } - mBufferInfo.mCrop = computeBufferCrop(mDrawingState); - mBufferInfo.mScaleMode = NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW; - mBufferInfo.mSurfaceDamage = mDrawingState.surfaceDamageRegion; - mBufferInfo.mHdrMetadata = mDrawingState.hdrMetadata; - mBufferInfo.mApi = mDrawingState.api; - mBufferInfo.mTransformToDisplayInverse = mDrawingState.transformToDisplayInverse; - mBufferInfo.mBufferSlot = mHwcSlotGenerator->getHwcCacheSlot(mDrawingState.clientCacheId); -} - -Rect BufferStateLayer::computeBufferCrop(const State& s) { - if (s.buffer && !s.bufferCrop.isEmpty()) { - Rect bufferCrop; - s.buffer->getBounds().intersect(s.bufferCrop, &bufferCrop); - return bufferCrop; - } else if (s.buffer) { - return s.buffer->getBounds(); - } else { - return s.bufferCrop; - } -} - -sp BufferStateLayer::createClone() { - LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata()); - args.textureName = mTextureName; - sp layer = mFlinger->getFactory().createBufferStateLayer(args); - layer->mHwcSlotGenerator = mHwcSlotGenerator; - layer->setInitialValuesForClone(sp::fromExisting(this)); - return layer; -} - -bool BufferStateLayer::bufferNeedsFiltering() const { - const State& s(getDrawingState()); - if (!s.buffer) { - return false; - } - - int32_t bufferWidth = static_cast(s.buffer->getWidth()); - int32_t bufferHeight = static_cast(s.buffer->getHeight()); - - // Undo any transformations on the buffer and return the result. - if (s.bufferTransform & ui::Transform::ROT_90) { - std::swap(bufferWidth, bufferHeight); - } - - if (s.transformToDisplayInverse) { - uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags(); - if (invTransform & ui::Transform::ROT_90) { - std::swap(bufferWidth, bufferHeight); - } - } - - const Rect layerSize{getBounds()}; - int32_t layerWidth = layerSize.getWidth(); - int32_t layerHeight = layerSize.getHeight(); - - // Align the layer orientation with the buffer before comparism - if (mTransformHint & ui::Transform::ROT_90) { - std::swap(layerWidth, layerHeight); - } - - return layerWidth != bufferWidth || layerHeight != bufferHeight; -} - -void BufferStateLayer::decrementPendingBufferCount() { - int32_t pendingBuffers = --mPendingBufferTransactions; - tracePendingBufferCount(pendingBuffers); -} - -void BufferStateLayer::tracePendingBufferCount(int32_t pendingBuffers) { - ATRACE_INT(mBlastTransactionName.c_str(), pendingBuffers); -} - - -/* - * We don't want to send the layer's transform to input, but rather the - * parent's transform. This is because BufferStateLayer's transform is - * information about how the buffer is placed on screen. The parent's - * transform makes more sense to send since it's information about how the - * layer is placed on screen. This transform is used by input to determine - * how to go from screen space back to window space. - */ -ui::Transform BufferStateLayer::getInputTransform() const { - if (!hasBufferOrSidebandStream()) { - return getTransform(); - } - sp parent = mDrawingParent.promote(); - if (parent == nullptr) { - return ui::Transform(); - } - - return parent->getTransform(); -} - -/** - * Similar to getInputTransform, we need to update the bounds to include the transform. - * This is because bounds for BSL doesn't include buffer transform, where the input assumes - * that's already included. - */ -Rect BufferStateLayer::getInputBounds() const { - if (!hasBufferOrSidebandStream()) { - return getCroppedBufferSize(getDrawingState()); - } - - Rect bufferBounds = getCroppedBufferSize(getDrawingState()); - if (mDrawingState.transform.getType() == ui::Transform::IDENTITY || !bufferBounds.isValid()) { - return bufferBounds; - } - return mDrawingState.transform.transform(bufferBounds); -} - -bool BufferStateLayer::simpleBufferUpdate(const layer_state_t& s) const { - const uint64_t requiredFlags = layer_state_t::eBufferChanged; - - const uint64_t deniedFlags = layer_state_t::eProducerDisconnect | layer_state_t::eLayerChanged | - layer_state_t::eRelativeLayerChanged | layer_state_t::eTransparentRegionChanged | - layer_state_t::eFlagsChanged | layer_state_t::eBlurRegionsChanged | - layer_state_t::eLayerStackChanged | layer_state_t::eAutoRefreshChanged | - layer_state_t::eReparent; - - const uint64_t allowedFlags = layer_state_t::eHasListenerCallbacksChanged | - layer_state_t::eFrameRateSelectionPriority | layer_state_t::eFrameRateChanged | - layer_state_t::eSurfaceDamageRegionChanged | layer_state_t::eApiChanged | - layer_state_t::eMetadataChanged | layer_state_t::eDropInputModeChanged | - layer_state_t::eInputInfoChanged; - - if ((s.what & requiredFlags) != requiredFlags) { - ALOGV("%s: false [missing required flags 0x%" PRIx64 "]", __func__, - (s.what | requiredFlags) & ~s.what); - return false; - } - - if (s.what & deniedFlags) { - ALOGV("%s: false [has denied flags 0x%" PRIx64 "]", __func__, s.what & deniedFlags); - return false; - } - - if (s.what & allowedFlags) { - ALOGV("%s: [has allowed flags 0x%" PRIx64 "]", __func__, s.what & allowedFlags); - } - - if (s.what & layer_state_t::ePositionChanged) { - if (mRequestedTransform.tx() != s.x || mRequestedTransform.ty() != s.y) { - ALOGV("%s: false [ePositionChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eAlphaChanged) { - if (mDrawingState.color.a != s.alpha) { - ALOGV("%s: false [eAlphaChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eColorTransformChanged) { - if (mDrawingState.colorTransform != s.colorTransform) { - ALOGV("%s: false [eColorTransformChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eBackgroundColorChanged) { - if (mDrawingState.bgColorLayer || s.bgColorAlpha != 0) { - ALOGV("%s: false [eBackgroundColorChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eMatrixChanged) { - if (mRequestedTransform.dsdx() != s.matrix.dsdx || - mRequestedTransform.dtdy() != s.matrix.dtdy || - mRequestedTransform.dtdx() != s.matrix.dtdx || - mRequestedTransform.dsdy() != s.matrix.dsdy) { - ALOGV("%s: false [eMatrixChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eCornerRadiusChanged) { - if (mDrawingState.cornerRadius != s.cornerRadius) { - ALOGV("%s: false [eCornerRadiusChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eBackgroundBlurRadiusChanged) { - if (mDrawingState.backgroundBlurRadius != static_cast(s.backgroundBlurRadius)) { - ALOGV("%s: false [eBackgroundBlurRadiusChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eTransformChanged) { - if (mDrawingState.bufferTransform != s.transform) { - ALOGV("%s: false [eTransformChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eTransformToDisplayInverseChanged) { - if (mDrawingState.transformToDisplayInverse != s.transformToDisplayInverse) { - ALOGV("%s: false [eTransformToDisplayInverseChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eCropChanged) { - if (mDrawingState.crop != s.crop) { - ALOGV("%s: false [eCropChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eDataspaceChanged) { - if (mDrawingState.dataspace != s.dataspace) { - ALOGV("%s: false [eDataspaceChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eHdrMetadataChanged) { - if (mDrawingState.hdrMetadata != s.hdrMetadata) { - ALOGV("%s: false [eHdrMetadataChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eSidebandStreamChanged) { - if (mDrawingState.sidebandStream != s.sidebandStream) { - ALOGV("%s: false [eSidebandStreamChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eColorSpaceAgnosticChanged) { - if (mDrawingState.colorSpaceAgnostic != s.colorSpaceAgnostic) { - ALOGV("%s: false [eColorSpaceAgnosticChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eShadowRadiusChanged) { - if (mDrawingState.shadowRadius != s.shadowRadius) { - ALOGV("%s: false [eShadowRadiusChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eFixedTransformHintChanged) { - if (mDrawingState.fixedTransformHint != s.fixedTransformHint) { - ALOGV("%s: false [eFixedTransformHintChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eTrustedOverlayChanged) { - if (mDrawingState.isTrustedOverlay != s.isTrustedOverlay) { - ALOGV("%s: false [eTrustedOverlayChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eStretchChanged) { - StretchEffect temp = s.stretchEffect; - temp.sanitize(); - if (mDrawingState.stretchEffect != temp) { - ALOGV("%s: false [eStretchChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eBufferCropChanged) { - if (mDrawingState.bufferCrop != s.bufferCrop) { - ALOGV("%s: false [eBufferCropChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eDestinationFrameChanged) { - if (mDrawingState.destinationFrame != s.destinationFrame) { - ALOGV("%s: false [eDestinationFrameChanged changed]", __func__); - return false; - } - } - - if (s.what & layer_state_t::eDimmingEnabledChanged) { - if (mDrawingState.dimmingEnabled != s.dimmingEnabled) { - ALOGV("%s: false [eDimmingEnabledChanged changed]", __func__); - return false; - } - } - - ALOGV("%s: true", __func__); - return true; -} - -void BufferStateLayer::useSurfaceDamage() { - if (mFlinger->mForceFullDamage) { - surfaceDamageRegion = Region::INVALID_REGION; - } else { - surfaceDamageRegion = mBufferInfo.mSurfaceDamage; - } -} - -void BufferStateLayer::useEmptyDamage() { - surfaceDamageRegion.clear(); -} - -bool BufferStateLayer::isOpaque(const Layer::State& s) const { - // if we don't have a buffer or sidebandStream yet, we're translucent regardless of the - // layer's opaque flag. - if (!hasSomethingToDraw()) { - return false; - } - - // if the layer has the opaque flag, then we're always opaque - if ((s.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque) { - return true; - } - - // If the buffer has no alpha channel, then we are opaque - if (hasBufferOrSidebandStream() && isOpaqueFormat(getPixelFormat())) { - return true; - } - - // Lastly consider the layer opaque if drawing a color with alpha == 1.0 - return fillsColor() && getAlpha() == 1.0_hf; -} - -bool BufferStateLayer::canReceiveInput() const { - return !isHiddenByPolicy() && (mBufferInfo.mBuffer == nullptr || getAlpha() > 0.0f); -} - -bool BufferStateLayer::isVisible() const { - if (!hasSomethingToDraw()) { - return false; - } - - if (isHiddenByPolicy()) { - return false; - } - - return getAlpha() > 0.0f || hasBlur(); -} - -std::optional BufferStateLayer::prepareClientComposition( - compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { - std::optional layerSettings = - prepareClientCompositionInternal(targetSettings); - // Nothing to render. - if (!layerSettings) { - return {}; - } - - // HWC requests to clear this layer. - if (targetSettings.clearContent) { - prepareClearClientComposition(*layerSettings, false /* blackout */); - return *layerSettings; - } - - // set the shadow for the layer if needed - prepareShadowClientComposition(*layerSettings, targetSettings.viewport); - - return *layerSettings; -} - -std::optional -BufferStateLayer::prepareClientCompositionInternal( - compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { - ATRACE_CALL(); - - std::optional result = - Layer::prepareClientComposition(targetSettings); - if (!result) { - return result; - } - - if (hasEffect()) { - prepareEffectsClientComposition(*result, targetSettings); - return result; - } - - if (CC_UNLIKELY(mBufferInfo.mBuffer == 0) && mSidebandStream != nullptr) { - // For surfaceview of tv sideband, there is no activeBuffer - // in bufferqueue, we need return LayerSettings. - return result; - } - const bool blackOutLayer = (isProtected() && !targetSettings.supportsProtectedContent) || - ((isSecure() || isProtected()) && !targetSettings.isSecure); - const bool bufferCanBeUsedAsHwTexture = - mBufferInfo.mBuffer->getUsage() & GraphicBuffer::USAGE_HW_TEXTURE; - compositionengine::LayerFE::LayerSettings& layer = *result; - if (blackOutLayer || !bufferCanBeUsedAsHwTexture) { - ALOGE_IF(!bufferCanBeUsedAsHwTexture, "%s is blacked out as buffer is not gpu readable", - mName.c_str()); - prepareClearClientComposition(layer, true /* blackout */); - return layer; - } - - const State& s(getDrawingState()); - layer.source.buffer.buffer = mBufferInfo.mBuffer; - layer.source.buffer.isOpaque = isOpaque(s); - layer.source.buffer.fence = mBufferInfo.mFence; - layer.source.buffer.textureName = mTextureName; - layer.source.buffer.usePremultipliedAlpha = getPremultipledAlpha(); - layer.source.buffer.isY410BT2020 = isHdrY410(); - bool hasSmpte2086 = mBufferInfo.mHdrMetadata.validTypes & HdrMetadata::SMPTE2086; - bool hasCta861_3 = mBufferInfo.mHdrMetadata.validTypes & HdrMetadata::CTA861_3; - float maxLuminance = 0.f; - if (hasSmpte2086 && hasCta861_3) { - maxLuminance = std::min(mBufferInfo.mHdrMetadata.smpte2086.maxLuminance, - mBufferInfo.mHdrMetadata.cta8613.maxContentLightLevel); - } else if (hasSmpte2086) { - maxLuminance = mBufferInfo.mHdrMetadata.smpte2086.maxLuminance; - } else if (hasCta861_3) { - maxLuminance = mBufferInfo.mHdrMetadata.cta8613.maxContentLightLevel; - } else { - switch (layer.sourceDataspace & HAL_DATASPACE_TRANSFER_MASK) { - case HAL_DATASPACE_TRANSFER_ST2084: - case HAL_DATASPACE_TRANSFER_HLG: - // Behavior-match previous releases for HDR content - maxLuminance = defaultMaxLuminance; - break; - } - } - layer.source.buffer.maxLuminanceNits = maxLuminance; - layer.frameNumber = mCurrentFrameNumber; - layer.bufferId = mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getId() : 0; - - const bool useFiltering = - targetSettings.needsFiltering || mNeedsFiltering || bufferNeedsFiltering(); - - // Query the texture matrix given our current filtering mode. - float textureMatrix[16]; - getDrawingTransformMatrix(useFiltering, textureMatrix); - - if (getTransformToDisplayInverse()) { - /* - * the code below applies the primary display's inverse transform to - * the texture transform - */ - uint32_t transform = DisplayDevice::getPrimaryDisplayRotationFlags(); - mat4 tr = inverseOrientation(transform); - - /** - * TODO(b/36727915): This is basically a hack. - * - * Ensure that regardless of the parent transformation, - * this buffer is always transformed from native display - * orientation to display orientation. For example, in the case - * of a camera where the buffer remains in native orientation, - * we want the pixels to always be upright. - */ - sp p = mDrawingParent.promote(); - if (p != nullptr) { - const auto parentTransform = p->getTransform(); - tr = tr * inverseOrientation(parentTransform.getOrientation()); - } - - // and finally apply it to the original texture matrix - const mat4 texTransform(mat4(static_cast(textureMatrix)) * tr); - memcpy(textureMatrix, texTransform.asArray(), sizeof(textureMatrix)); - } - - const Rect win{getBounds()}; - float bufferWidth = getBufferSize(s).getWidth(); - float bufferHeight = getBufferSize(s).getHeight(); - - // BufferStateLayers can have a "buffer size" of [0, 0, -1, -1] when no display frame has - // been set and there is no parent layer bounds. In that case, the scale is meaningless so - // ignore them. - if (!getBufferSize(s).isValid()) { - bufferWidth = float(win.right) - float(win.left); - bufferHeight = float(win.bottom) - float(win.top); - } - - const float scaleHeight = (float(win.bottom) - float(win.top)) / bufferHeight; - const float scaleWidth = (float(win.right) - float(win.left)) / bufferWidth; - const float translateY = float(win.top) / bufferHeight; - const float translateX = float(win.left) / bufferWidth; - - // Flip y-coordinates because GLConsumer expects OpenGL convention. - mat4 tr = mat4::translate(vec4(.5f, .5f, 0.f, 1.f)) * mat4::scale(vec4(1.f, -1.f, 1.f, 1.f)) * - mat4::translate(vec4(-.5f, -.5f, 0.f, 1.f)) * - mat4::translate(vec4(translateX, translateY, 0.f, 1.f)) * - mat4::scale(vec4(scaleWidth, scaleHeight, 1.0f, 1.0f)); - - layer.source.buffer.useTextureFiltering = useFiltering; - layer.source.buffer.textureTransform = mat4(static_cast(textureMatrix)) * tr; - - return layer; -} - -void BufferStateLayer::prepareEffectsClientComposition( - compositionengine::LayerFE::LayerSettings& layerSettings, - compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { - // If fill bounds are occluded or the fill color is invalid skip the fill settings. - if (targetSettings.realContentIsVisible && fillsColor()) { - // Set color for color fill settings. - layerSettings.source.solidColor = getColor().rgb; - } else if (hasBlur() || drawShadows()) { - layerSettings.skipContentDraw = true; - } -} - -bool BufferStateLayer::isHdrY410() const { - // pixel format is HDR Y410 masquerading as RGBA_1010102 - return (mBufferInfo.mDataspace == ui::Dataspace::BT2020_ITU_PQ && - mBufferInfo.mApi == NATIVE_WINDOW_API_MEDIA && - mBufferInfo.mPixelFormat == HAL_PIXEL_FORMAT_RGBA_1010102); -} - -sp BufferStateLayer::getCompositionEngineLayerFE() const { - // There's no need to get a CE Layer if the layer isn't going to draw anything. - if (hasSomethingToDraw()) { - return asLayerFE(); - } else { - return nullptr; - } -} - -compositionengine::LayerFECompositionState* BufferStateLayer::editCompositionState() { - return mCompositionState.get(); -} - -const compositionengine::LayerFECompositionState* BufferStateLayer::getCompositionState() const { - return mCompositionState.get(); -} - -void BufferStateLayer::preparePerFrameCompositionState() { - Layer::preparePerFrameCompositionState(); - if (hasBufferOrSidebandStream()) { - preparePerFrameBufferCompositionState(); - } else { - preparePerFrameEffectsCompositionState(); - } -} - -void BufferStateLayer::preparePerFrameBufferCompositionState() { - // Sideband layers - auto* compositionState = editCompositionState(); - if (compositionState->sidebandStream.get() && !compositionState->sidebandStreamHasFrame) { - compositionState->compositionType = - aidl::android::hardware::graphics::composer3::Composition::SIDEBAND; - return; - } else if ((mDrawingState.flags & layer_state_t::eLayerIsDisplayDecoration) != 0) { - compositionState->compositionType = - aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION; - } else { - // Normal buffer layers - compositionState->hdrMetadata = mBufferInfo.mHdrMetadata; - compositionState->compositionType = mPotentialCursor - ? aidl::android::hardware::graphics::composer3::Composition::CURSOR - : aidl::android::hardware::graphics::composer3::Composition::DEVICE; - } - - compositionState->buffer = getBuffer(); - compositionState->bufferSlot = (mBufferInfo.mBufferSlot == BufferQueue::INVALID_BUFFER_SLOT) - ? 0 - : mBufferInfo.mBufferSlot; - compositionState->acquireFence = mBufferInfo.mFence; - compositionState->frameNumber = mBufferInfo.mFrameNumber; - compositionState->sidebandStreamHasFrame = false; -} - -void BufferStateLayer::preparePerFrameEffectsCompositionState() { - auto* compositionState = editCompositionState(); - compositionState->color = getColor(); - compositionState->compositionType = - aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR; -} - -void BufferStateLayer::onPostComposition(const DisplayDevice* display, - const std::shared_ptr& glDoneFence, - const std::shared_ptr& presentFence, - const CompositorTiming& compositorTiming) { - // mFrameLatencyNeeded is true when a new frame was latched for the - // composition. - if (!mBufferInfo.mFrameLatencyNeeded) return; - - for (const auto& handle : mDrawingState.callbackHandles) { - handle->gpuCompositionDoneFence = glDoneFence; - handle->compositorTiming = compositorTiming; - } - - // Update mFrameTracker. - nsecs_t desiredPresentTime = mBufferInfo.mDesiredPresentTime; - mFrameTracker.setDesiredPresentTime(desiredPresentTime); - - const int32_t layerId = getSequence(); - mFlinger->mTimeStats->setDesiredTime(layerId, mCurrentFrameNumber, desiredPresentTime); - - const auto outputLayer = findOutputLayerForDisplay(display); - if (outputLayer && outputLayer->requiresClientComposition()) { - nsecs_t clientCompositionTimestamp = outputLayer->getState().clientCompositionTimestamp; - mFlinger->mFrameTracer->traceTimestamp(layerId, getCurrentBufferId(), mCurrentFrameNumber, - clientCompositionTimestamp, - FrameTracer::FrameEvent::FALLBACK_COMPOSITION); - // Update the SurfaceFrames in the drawing state - if (mDrawingState.bufferSurfaceFrameTX) { - mDrawingState.bufferSurfaceFrameTX->setGpuComposition(); - } - for (auto& [token, surfaceFrame] : mDrawingState.bufferlessSurfaceFramesTX) { - surfaceFrame->setGpuComposition(); - } - } - - std::shared_ptr frameReadyFence = mBufferInfo.mFenceTime; - if (frameReadyFence->isValid()) { - mFrameTracker.setFrameReadyFence(std::move(frameReadyFence)); - } else { - // There was no fence for this frame, so assume that it was ready - // to be presented at the desired present time. - mFrameTracker.setFrameReadyTime(desiredPresentTime); - } - - if (display) { - const Fps refreshRate = display->refreshRateConfigs().getActiveMode()->getFps(); - const std::optional renderRate = - mFlinger->mScheduler->getFrameRateOverride(getOwnerUid()); - - const auto vote = frameRateToSetFrameRateVotePayload(mDrawingState.frameRate); - const auto gameMode = getGameMode(); - - if (presentFence->isValid()) { - mFlinger->mTimeStats->setPresentFence(layerId, mCurrentFrameNumber, presentFence, - refreshRate, renderRate, vote, gameMode); - mFlinger->mFrameTracer->traceFence(layerId, getCurrentBufferId(), mCurrentFrameNumber, - presentFence, - FrameTracer::FrameEvent::PRESENT_FENCE); - mFrameTracker.setActualPresentFence(std::shared_ptr(presentFence)); - } else if (const auto displayId = PhysicalDisplayId::tryCast(display->getId()); - displayId && mFlinger->getHwComposer().isConnected(*displayId)) { - // The HWC doesn't support present fences, so use the refresh - // timestamp instead. - const nsecs_t actualPresentTime = display->getRefreshTimestamp(); - mFlinger->mTimeStats->setPresentTime(layerId, mCurrentFrameNumber, actualPresentTime, - refreshRate, renderRate, vote, gameMode); - mFlinger->mFrameTracer->traceTimestamp(layerId, getCurrentBufferId(), - mCurrentFrameNumber, actualPresentTime, - FrameTracer::FrameEvent::PRESENT_FENCE); - mFrameTracker.setActualPresentTime(actualPresentTime); - } - } - - mFrameTracker.advanceFrame(); - mBufferInfo.mFrameLatencyNeeded = false; -} - -bool BufferStateLayer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime) { - ATRACE_FORMAT_INSTANT("latchBuffer %s - %" PRIu64, getDebugName(), - getDrawingState().frameNumber); - - bool refreshRequired = latchSidebandStream(recomputeVisibleRegions); - - if (refreshRequired) { - return refreshRequired; - } - - // If the head buffer's acquire fence hasn't signaled yet, return and - // try again later - if (!fenceHasSignaled()) { - ATRACE_NAME("!fenceHasSignaled()"); - mFlinger->onLayerUpdate(); - return false; - } - - updateTexImage(latchTime); - if (mDrawingState.buffer == nullptr) { - return false; - } - - // Capture the old state of the layer for comparisons later - BufferInfo oldBufferInfo = mBufferInfo; - const bool oldOpacity = isOpaque(mDrawingState); - mPreviousFrameNumber = mCurrentFrameNumber; - mCurrentFrameNumber = mDrawingState.frameNumber; - gatherBufferInfo(); - - if (oldBufferInfo.mBuffer == nullptr) { - // the first time we receive a buffer, we need to trigger a - // geometry invalidation. - recomputeVisibleRegions = true; - } - - if ((mBufferInfo.mCrop != oldBufferInfo.mCrop) || - (mBufferInfo.mTransform != oldBufferInfo.mTransform) || - (mBufferInfo.mScaleMode != oldBufferInfo.mScaleMode) || - (mBufferInfo.mTransformToDisplayInverse != oldBufferInfo.mTransformToDisplayInverse)) { - recomputeVisibleRegions = true; - } - - if (oldBufferInfo.mBuffer != nullptr) { - uint32_t bufWidth = mBufferInfo.mBuffer->getWidth(); - uint32_t bufHeight = mBufferInfo.mBuffer->getHeight(); - if (bufWidth != oldBufferInfo.mBuffer->getWidth() || - bufHeight != oldBufferInfo.mBuffer->getHeight()) { - recomputeVisibleRegions = true; - } - } - - if (oldOpacity != isOpaque(mDrawingState)) { - recomputeVisibleRegions = true; - } - - return true; -} - -bool BufferStateLayer::hasReadyFrame() const { - return hasFrameUpdate() || getSidebandStreamChanged() || getAutoRefresh(); -} - -bool BufferStateLayer::isProtected() const { - return (mBufferInfo.mBuffer != nullptr) && - (mBufferInfo.mBuffer->getUsage() & GRALLOC_USAGE_PROTECTED); -} - -// As documented in libhardware header, formats in the range -// 0x100 - 0x1FF are specific to the HAL implementation, and -// are known to have no alpha channel -// TODO: move definition for device-specific range into -// hardware.h, instead of using hard-coded values here. -#define HARDWARE_IS_DEVICE_FORMAT(f) ((f) >= 0x100 && (f) <= 0x1FF) - -bool BufferStateLayer::isOpaqueFormat(PixelFormat format) { - if (HARDWARE_IS_DEVICE_FORMAT(format)) { - return true; - } - switch (format) { - case PIXEL_FORMAT_RGBA_8888: - case PIXEL_FORMAT_BGRA_8888: - case PIXEL_FORMAT_RGBA_FP16: - case PIXEL_FORMAT_RGBA_1010102: - case PIXEL_FORMAT_R_8: - return false; - } - // in all other case, we have no blending (also for unknown formats) - return true; -} - -bool BufferStateLayer::needsFiltering(const DisplayDevice* display) const { - if (!hasBufferOrSidebandStream()) { - return false; - } - const auto outputLayer = findOutputLayerForDisplay(display); - if (outputLayer == nullptr) { - return false; - } - - // We need filtering if the sourceCrop rectangle size does not match the - // displayframe rectangle size (not a 1:1 render) - const auto& compositionState = outputLayer->getState(); - const auto displayFrame = compositionState.displayFrame; - const auto sourceCrop = compositionState.sourceCrop; - return sourceCrop.getHeight() != displayFrame.getHeight() || - sourceCrop.getWidth() != displayFrame.getWidth(); -} - -bool BufferStateLayer::needsFilteringForScreenshots( - const DisplayDevice* display, const ui::Transform& inverseParentTransform) const { - if (!hasBufferOrSidebandStream()) { - return false; - } - const auto outputLayer = findOutputLayerForDisplay(display); - if (outputLayer == nullptr) { - return false; - } - - // We need filtering if the sourceCrop rectangle size does not match the - // viewport rectangle size (not a 1:1 render) - const auto& compositionState = outputLayer->getState(); - const ui::Transform& displayTransform = display->getTransform(); - const ui::Transform inverseTransform = inverseParentTransform * displayTransform.inverse(); - // Undo the transformation of the displayFrame so that we're back into - // layer-stack space. - const Rect frame = inverseTransform.transform(compositionState.displayFrame); - const FloatRect sourceCrop = compositionState.sourceCrop; - - int32_t frameHeight = frame.getHeight(); - int32_t frameWidth = frame.getWidth(); - // If the display transform had a rotational component then undo the - // rotation so that the orientation matches the source crop. - if (displayTransform.getOrientation() & ui::Transform::ROT_90) { - std::swap(frameHeight, frameWidth); - } - return sourceCrop.getHeight() != frameHeight || sourceCrop.getWidth() != frameWidth; -} - -void BufferStateLayer::latchAndReleaseBuffer() { - if (hasReadyFrame()) { - bool ignored = false; - latchBuffer(ignored, systemTime()); - } - releasePendingBuffer(systemTime()); -} - -PixelFormat BufferStateLayer::getPixelFormat() const { - return mBufferInfo.mPixelFormat; -} - -bool BufferStateLayer::getTransformToDisplayInverse() const { - return mBufferInfo.mTransformToDisplayInverse; -} - -Rect BufferStateLayer::getBufferCrop() const { - // this is the crop rectangle that applies to the buffer - // itself (as opposed to the window) - if (!mBufferInfo.mCrop.isEmpty()) { - // if the buffer crop is defined, we use that - return mBufferInfo.mCrop; - } else if (mBufferInfo.mBuffer != nullptr) { - // otherwise we use the whole buffer - return mBufferInfo.mBuffer->getBounds(); - } else { - // if we don't have a buffer yet, we use an empty/invalid crop - return Rect(); - } -} - -uint32_t BufferStateLayer::getBufferTransform() const { - return mBufferInfo.mTransform; -} - -ui::Dataspace BufferStateLayer::getDataSpace() const { - return mDrawingState.dataspaceRequested ? getRequestedDataSpace() : ui::Dataspace::UNKNOWN; -} - -ui::Dataspace BufferStateLayer::getRequestedDataSpace() const { - return hasBufferOrSidebandStream() ? mBufferInfo.mDataspace : mDrawingState.dataspace; -} - -ui::Dataspace BufferStateLayer::translateDataspace(ui::Dataspace dataspace) { - ui::Dataspace updatedDataspace = dataspace; - // translate legacy dataspaces to modern dataspaces - switch (dataspace) { - case ui::Dataspace::SRGB: - updatedDataspace = ui::Dataspace::V0_SRGB; - break; - case ui::Dataspace::SRGB_LINEAR: - updatedDataspace = ui::Dataspace::V0_SRGB_LINEAR; - break; - case ui::Dataspace::JFIF: - updatedDataspace = ui::Dataspace::V0_JFIF; - break; - case ui::Dataspace::BT601_625: - updatedDataspace = ui::Dataspace::V0_BT601_625; - break; - case ui::Dataspace::BT601_525: - updatedDataspace = ui::Dataspace::V0_BT601_525; - break; - case ui::Dataspace::BT709: - updatedDataspace = ui::Dataspace::V0_BT709; - break; - default: - break; - } - - return updatedDataspace; -} - -sp BufferStateLayer::getBuffer() const { - return mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getBuffer() : nullptr; -} - -void BufferStateLayer::getDrawingTransformMatrix(bool filteringEnabled, float outMatrix[16]) const { - sp buffer = getBuffer(); - if (!buffer) { - ALOGE("Buffer should not be null!"); - return; - } - GLConsumer::computeTransformMatrix(outMatrix, buffer->getWidth(), buffer->getHeight(), - buffer->getPixelFormat(), mBufferInfo.mCrop, - mBufferInfo.mTransform, filteringEnabled); -} - -void BufferStateLayer::setInitialValuesForClone(const sp& clonedFrom) { - Layer::setInitialValuesForClone(clonedFrom); - - sp bufferClonedFrom = - sp::fromExisting(static_cast(clonedFrom.get())); - mPremultipliedAlpha = bufferClonedFrom->mPremultipliedAlpha; - mPotentialCursor = bufferClonedFrom->mPotentialCursor; - mProtectedByApp = bufferClonedFrom->mProtectedByApp; - - updateCloneBufferInfo(); -} - -void BufferStateLayer::updateCloneBufferInfo() { - if (!isClone() || !isClonedFromAlive()) { - return; - } - - sp clonedFrom = sp::fromExisting( - static_cast(getClonedFrom().get())); - mBufferInfo = clonedFrom->mBufferInfo; - mSidebandStream = clonedFrom->mSidebandStream; - surfaceDamageRegion = clonedFrom->surfaceDamageRegion; - mCurrentFrameNumber = clonedFrom->mCurrentFrameNumber.load(); - mPreviousFrameNumber = clonedFrom->mPreviousFrameNumber; - - // After buffer info is updated, the drawingState from the real layer needs to be copied into - // the cloned. This is because some properties of drawingState can change when latchBuffer is - // called. However, copying the drawingState would also overwrite the cloned layer's relatives - // and touchableRegionCrop. Therefore, temporarily store the relatives so they can be set in - // the cloned drawingState again. - wp tmpZOrderRelativeOf = mDrawingState.zOrderRelativeOf; - SortedVector> tmpZOrderRelatives = mDrawingState.zOrderRelatives; - wp tmpTouchableRegionCrop = mDrawingState.touchableRegionCrop; - WindowInfo tmpInputInfo = mDrawingState.inputInfo; - - cloneDrawingState(clonedFrom.get()); - - mDrawingState.touchableRegionCrop = tmpTouchableRegionCrop; - mDrawingState.zOrderRelativeOf = tmpZOrderRelativeOf; - mDrawingState.zOrderRelatives = tmpZOrderRelatives; - mDrawingState.inputInfo = tmpInputInfo; -} - -void BufferStateLayer::setTransformHint(ui::Transform::RotationFlags displayTransformHint) { - mTransformHint = getFixedTransformHint(); - if (mTransformHint == ui::Transform::ROT_INVALID) { - mTransformHint = displayTransformHint; - } -} - -const std::shared_ptr& BufferStateLayer::getExternalTexture() const { - return mBufferInfo.mBuffer; -} - -bool BufferStateLayer::setColor(const half3& color) { - if (mDrawingState.color.r == color.r && mDrawingState.color.g == color.g && - mDrawingState.color.b == color.b) { - return false; - } - - mDrawingState.sequence++; - mDrawingState.color.r = color.r; - mDrawingState.color.g = color.g; - mDrawingState.color.b = color.b; - mDrawingState.modified = true; - setTransactionFlags(eTransactionNeeded); - return true; -} - -bool BufferStateLayer::fillsColor() const { - return !hasBufferOrSidebandStream() && mDrawingState.color.r >= 0.0_hf && - mDrawingState.color.g >= 0.0_hf && mDrawingState.color.b >= 0.0_hf; -} - -bool BufferStateLayer::hasBlur() const { - return getBackgroundBlurRadius() > 0 || getDrawingState().blurRegions.size() > 0; -} - -} // namespace android diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h index 6ef2b1932e..e53e1c1d89 100644 --- a/services/surfaceflinger/BufferStateLayer.h +++ b/services/surfaceflinger/BufferStateLayer.h @@ -16,306 +16,13 @@ #pragma once -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include // For NATIVE_WINDOW_SCALING_MODE_FREEZE -#include -#include -#include -#include -#include -#include -#include - -#include "Client.h" -#include "DisplayHardware/HWComposer.h" -#include "FrameTimeline.h" -#include "FrameTracker.h" -#include "HwcSlotGenerator.h" #include "Layer.h" -#include "LayerVector.h" -#include "SurfaceFlinger.h" namespace android { -class SlotGenerationTest; - class BufferStateLayer : public Layer { public: - explicit BufferStateLayer(const LayerCreationArgs&); - - ~BufferStateLayer() override; - - // Implements Layer. - sp getCompositionEngineLayerFE() const override; - compositionengine::LayerFECompositionState* editCompositionState() override; - - // If we have received a new buffer this frame, we will pass its surface - // damage down to hardware composer. Otherwise, we must send a region with - // one empty rect. - void useSurfaceDamage() override; - void useEmptyDamage() override; - - bool isOpaque(const Layer::State& s) const override; - bool canReceiveInput() const override; - - // isVisible - true if this layer is visible, false otherwise - bool isVisible() const override; - - // isProtected - true if the layer may contain protected content in the - // GRALLOC_USAGE_PROTECTED sense. - bool isProtected() const override; - - bool usesSourceCrop() const override { return hasBufferOrSidebandStream(); } - - bool isHdrY410() const override; - - void onPostComposition(const DisplayDevice*, const std::shared_ptr& glDoneFence, - const std::shared_ptr& presentFence, - const CompositorTiming&) override; - - // latchBuffer - called each time the screen is redrawn and returns whether - // the visible regions need to be recomputed (this is a fairly heavy - // operation, so this should be set only if needed). Typically this is used - // to figure out if the content or size of a surface has changed. - bool latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime) override; - bool hasReadyFrame() const override; - - // Calls latchBuffer if the buffer has a frame queued and then releases the buffer. - // This is used if the buffer is just latched and releases to free up the buffer - // and will not be shown on screen. - // Should only be called on the main thread. - void latchAndReleaseBuffer() override; - - bool getTransformToDisplayInverse() const override; - - Rect getBufferCrop() const override; - - uint32_t getBufferTransform() const override; - - ui::Dataspace getDataSpace() const override; - ui::Dataspace getRequestedDataSpace() const; - sp getBuffer() const override; - const std::shared_ptr& getExternalTexture() const override; - - ui::Transform::RotationFlags getTransformHint() const override { return mTransformHint; } - - // Implements Layer. - const char* getType() const override { return "BufferStateLayer"; } - - void onLayerDisplayed(ftl::SharedFuture) override; - - void releasePendingBuffer(nsecs_t dequeueReadyTime) override; - - Region getActiveTransparentRegion(const Layer::State& s) const override { - return s.transparentRegionHint; - } - - bool setTransform(uint32_t transform) override; - bool setTransformToDisplayInverse(bool transformToDisplayInverse) override; - bool setBuffer(std::shared_ptr& /* buffer */, - const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime, - bool isAutoTimestamp, std::optional dequeueTime, - const FrameTimelineInfo& info) override; - bool setDataspace(ui::Dataspace dataspace) override; - bool setHdrMetadata(const HdrMetadata& hdrMetadata) override; - bool setSurfaceDamageRegion(const Region& surfaceDamage) override; - bool setApi(int32_t api) override; - bool setSidebandStream(const sp& sidebandStream) override; - bool setTransactionCompletedListeners(const std::vector>& handles) override; - bool setPosition(float /*x*/, float /*y*/) override; - bool setMatrix(const layer_state_t::matrix22_t& /*matrix*/); - - // BufferStateLayers can return Rect::INVALID_RECT if the layer does not have a display frame - // and its parent layer is not bounded - Rect getBufferSize(const State& s) const override; - FloatRect computeSourceBounds(const FloatRect& parentBounds) const override; - void setAutoRefresh(bool autoRefresh) override; - - bool setBufferCrop(const Rect& bufferCrop) override; - bool setDestinationFrame(const Rect& destinationFrame) override; - bool updateGeometry() override; - - bool fenceHasSignaled() const; - bool onPreComposition(nsecs_t) override; - - // See mPendingBufferTransactions - void decrementPendingBufferCount(); - std::atomic* getPendingBufferCounter() override { return &mPendingBufferTransactions; } - std::string getPendingBufferCounterName() override { return mBlastTransactionName; } - - std::optional prepareClientComposition( - compositionengine::LayerFE::ClientCompositionTargetSettings&) const override; - bool setColor(const half3& color) override; - -protected: - void gatherBufferInfo(); - void onSurfaceFrameCreated(const std::shared_ptr& surfaceFrame); - ui::Transform getInputTransform() const override; - Rect getInputBounds() const override; - - struct BufferInfo { - nsecs_t mDesiredPresentTime; - std::shared_ptr mFenceTime; - sp mFence; - uint32_t mTransform{0}; - ui::Dataspace mDataspace{ui::Dataspace::UNKNOWN}; - Rect mCrop; - uint32_t mScaleMode{NATIVE_WINDOW_SCALING_MODE_FREEZE}; - Region mSurfaceDamage; - HdrMetadata mHdrMetadata; - int mApi; - PixelFormat mPixelFormat{PIXEL_FORMAT_NONE}; - bool mTransformToDisplayInverse{false}; - - std::shared_ptr mBuffer; - uint64_t mFrameNumber; - int mBufferSlot{BufferQueue::INVALID_BUFFER_SLOT}; - - bool mFrameLatencyNeeded{false}; - }; - - BufferInfo mBufferInfo; - - /* - * compositionengine::LayerFE overrides - */ - const compositionengine::LayerFECompositionState* getCompositionState() const override; - void preparePerFrameCompositionState() override; - void preparePerFrameBufferCompositionState(); - void preparePerFrameEffectsCompositionState(); - - static bool isOpaqueFormat(PixelFormat format); - - // from graphics API - const uint32_t mTextureName; - ui::Dataspace translateDataspace(ui::Dataspace dataspace); - void setInitialValuesForClone(const sp& clonedFrom); - void updateCloneBufferInfo() override; - uint64_t mPreviousFrameNumber = 0; - - void setTransformHint(ui::Transform::RotationFlags displayTransformHint) override; - - // Transform hint provided to the producer. This must be accessed holding - // the mStateLock. - ui::Transform::RotationFlags mTransformHint = ui::Transform::ROT_0; - - bool getAutoRefresh() const { return mDrawingState.autoRefresh; } - bool getSidebandStreamChanged() const { return mSidebandStreamChanged; } - - std::atomic mSidebandStreamChanged{false}; - -private: - friend class SlotGenerationTest; - friend class TransactionFrameTracerTest; - friend class TransactionSurfaceFrameTest; - - // We generate InputWindowHandles for all non-cursor buffered layers regardless of whether they - // have an InputChannel. This is to enable the InputDispatcher to do PID based occlusion - // detection. - bool needsInputInfo() const override { - return (hasInputInfo() || hasBufferOrSidebandStream()) && !mPotentialCursor; - } - - // Returns true if this layer requires filtering - bool needsFiltering(const DisplayDevice*) const override; - bool needsFilteringForScreenshots(const DisplayDevice*, - const ui::Transform& inverseParentTransform) const override; - - PixelFormat getPixelFormat() const; - - // Computes the transform matrix using the setFilteringEnabled to determine whether the - // transform matrix should be computed for use with bilinear filtering. - void getDrawingTransformMatrix(bool filteringEnabled, float outMatrix[16]) const; - - std::unique_ptr mCompositionState; - - inline void tracePendingBufferCount(int32_t pendingBuffers); - - bool updateFrameEventHistory(const sp& acquireFence, nsecs_t postedTime, - nsecs_t requestedPresentTime); - - // Latch sideband stream and returns true if the dirty region should be updated. - bool latchSidebandStream(bool& recomputeVisibleRegions); - - bool hasFrameUpdate() const; - - void updateTexImage(nsecs_t latchTime); - - sp createClone() override; - - // Crop that applies to the buffer - Rect computeBufferCrop(const State& s); - - bool willPresentCurrentTransaction() const; - - // Returns true if the transformed buffer size does not match the layer size and we need - // to apply filtering. - bool bufferNeedsFiltering() const; - - bool simpleBufferUpdate(const layer_state_t& s) const override; - - void callReleaseBufferCallback(const sp& listener, - const sp& buffer, uint64_t framenumber, - const sp& releaseFence, - uint32_t currentMaxAcquiredBufferCount); - - std::optional prepareClientCompositionInternal( - compositionengine::LayerFE::ClientCompositionTargetSettings&) const; - // Returns true if there is a valid color to fill. - bool fillsColor() const; - // Returns true if this layer has a blur value. - bool hasBlur() const; - bool hasEffect() const { return fillsColor() || drawShadows() || hasBlur(); } - bool hasBufferOrSidebandStream() const { - return ((mSidebandStream != nullptr) || (mBufferInfo.mBuffer != nullptr)); - } - bool hasSomethingToDraw() const { return hasEffect() || hasBufferOrSidebandStream(); } - void prepareEffectsClientComposition( - compositionengine::LayerFE::LayerSettings&, - compositionengine::LayerFE::ClientCompositionTargetSettings&) const; - - ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID; - uint64_t mPreviousReleasedFrameNumber = 0; - - uint64_t mPreviousBarrierFrameNumber = 0; - - bool mReleasePreviousBuffer = false; - - // Stores the last set acquire fence signal time used to populate the callback handle's acquire - // time. - std::variant> mCallbackHandleAcquireTimeOrFence = -1; - - std::deque> mPendingJankClassifications; - // An upper bound on the number of SurfaceFrames in the pending classifications deque. - static constexpr int kPendingClassificationMaxSurfaceFrames = 25; - - const std::string mBlastTransactionName{"BufferTX - " + mName}; - // This integer is incremented everytime a buffer arrives at the server for this layer, - // and decremented when a buffer is dropped or latched. When changed the integer is exported - // to systrace with ATRACE_INT and mBlastTransactionName. This way when debugging perf it is - // possible to see when a buffer arrived at the server, and in which frame it latched. - // - // You can understand the trace this way: - // - If the integer increases, a buffer arrived at the server. - // - If the integer decreases in latchBuffer, that buffer was latched - // - If the integer decreases in setBuffer or doTransaction, a buffer was dropped - std::atomic mPendingBufferTransactions{0}; - - // Contains requested position and matrix updates. This will be applied if the client does - // not specify a destination frame. - ui::Transform mRequestedTransform; - - sp mHwcSlotGenerator; + explicit BufferStateLayer(const LayerCreationArgs& args) : Layer(args){}; }; } // namespace android diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 930ae72286..b6927202f2 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -39,8 +40,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -62,7 +65,6 @@ #include #include -#include "Colorizer.h" #include "DisplayDevice.h" #include "DisplayHardware/HWComposer.h" #include "EffectLayer.h" @@ -74,11 +76,72 @@ #include "TunnelModeEnabledReporter.h" #define DEBUG_RESIZE 0 +#define EARLY_RELEASE_ENABLED false namespace android { namespace { constexpr int kDumpTableRowLength = 159; + +static constexpr float defaultMaxLuminance = 1000.0; + const ui::Transform kIdentityTransform; + +constexpr mat4 inverseOrientation(uint32_t transform) { + const mat4 flipH(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1); + const mat4 flipV(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1); + const mat4 rot90(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1); + mat4 tr; + + if (transform & NATIVE_WINDOW_TRANSFORM_ROT_90) { + tr = tr * rot90; + } + if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_H) { + tr = tr * flipH; + } + if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_V) { + tr = tr * flipV; + } + return inverse(tr); +} + +bool assignTransform(ui::Transform* dst, ui::Transform& from) { + if (*dst == from) { + return false; + } + *dst = from; + return true; +} + +TimeStats::SetFrameRateVote frameRateToSetFrameRateVotePayload(Layer::FrameRate frameRate) { + using FrameRateCompatibility = TimeStats::SetFrameRateVote::FrameRateCompatibility; + using Seamlessness = TimeStats::SetFrameRateVote::Seamlessness; + const auto frameRateCompatibility = [frameRate] { + switch (frameRate.type) { + case Layer::FrameRateCompatibility::Default: + return FrameRateCompatibility::Default; + case Layer::FrameRateCompatibility::ExactOrMultiple: + return FrameRateCompatibility::ExactOrMultiple; + default: + return FrameRateCompatibility::Undefined; + } + }(); + + const auto seamlessness = [frameRate] { + switch (frameRate.seamlessness) { + case scheduler::Seamlessness::OnlySeamless: + return Seamlessness::ShouldBeSeamless; + case scheduler::Seamlessness::SeamedAndSeamless: + return Seamlessness::NotRequired; + default: + return Seamlessness::Undefined; + } + }(); + + return TimeStats::SetFrameRateVote{.frameRate = frameRate.rate.getValue(), + .frameRateCompatibility = frameRateCompatibility, + .seamlessness = seamlessness}; +} + } // namespace using namespace ftl::flag_operators; @@ -100,7 +163,12 @@ Layer::Layer(const LayerCreationArgs& args) mWindowType(static_cast( args.metadata.getInt32(gui::METADATA_WINDOW_TYPE, 0))), mLayerCreationFlags(args.flags), - mBorderEnabled(false) { + mBorderEnabled(false), + mTextureName(args.textureName), + mCompositionState{mFlinger->getCompositionEngine().createLayerFECompositionState()}, + mHwcSlotGenerator(sp::make()) { + ALOGV("Creating Layer %s", getDebugName()); + uint32_t layerFlags = 0; if (args.flags & ISurfaceComposerClient::eHidden) layerFlags |= layer_state_t::eLayerHidden; if (args.flags & ISurfaceComposerClient::eOpaque) layerFlags |= layer_state_t::eLayerOpaque; @@ -166,6 +234,11 @@ Layer::Layer(const LayerCreationArgs& args) mOwnerUid = mCallingUid; mOwnerPid = mCallingPid; } + + mPremultipliedAlpha = !(args.flags & ISurfaceComposerClient::eNonPremultiplied); + mPotentialCursor = args.flags & ISurfaceComposerClient::eCursorWindow; + mProtectedByApp = args.flags & ISurfaceComposerClient::eProtectedByApp; + mDrawingState.dataspace = ui::Dataspace::V0_SRGB; } void Layer::onFirstRef() { @@ -173,6 +246,28 @@ void Layer::onFirstRef() { } Layer::~Layer() { + // The original layer and the clone layer share the same texture and buffer. Therefore, only + // one of the layers, in this case the original layer, needs to handle the deletion. The + // original layer and the clone should be removed at the same time so there shouldn't be any + // issue with the clone layer trying to use the texture. + if (mBufferInfo.mBuffer != nullptr) { + callReleaseBufferCallback(mDrawingState.releaseBufferListener, + mBufferInfo.mBuffer->getBuffer(), mBufferInfo.mFrameNumber, + mBufferInfo.mFence, + mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate( + mOwnerUid)); + } + if (!isClone()) { + // The original layer and the clone layer share the same texture. Therefore, only one of + // the layers, in this case the original layer, needs to handle the deletion. The original + // layer and the clone should be removed at the same time so there shouldn't be any issue + // with the clone layer trying to use the deleted texture. + mFlinger->deleteTextureAsync(mTextureName); + } + const int32_t layerId = getSequence(); + mFlinger->mTimeStats->onDestroy(layerId); + mFlinger->mFrameTracer->onDestroy(layerId); + sp c(mClientRef.promote()); if (c != 0) { c->detachLayer(this); @@ -493,6 +588,46 @@ void Layer::preparePerFrameCompositionState() { // Retrieve it from the scheduler which maintains an instance of LayerHistory, and store it in // LayerFECompositionState where it would be visible to Flattener. compositionState->fps = mFlinger->getLayerFramerate(systemTime(), getSequence()); + + if (hasBufferOrSidebandStream()) { + preparePerFrameBufferCompositionState(); + } else { + preparePerFrameEffectsCompositionState(); + } +} + +void Layer::preparePerFrameBufferCompositionState() { + // Sideband layers + auto* compositionState = editCompositionState(); + if (compositionState->sidebandStream.get() && !compositionState->sidebandStreamHasFrame) { + compositionState->compositionType = + aidl::android::hardware::graphics::composer3::Composition::SIDEBAND; + return; + } else if ((mDrawingState.flags & layer_state_t::eLayerIsDisplayDecoration) != 0) { + compositionState->compositionType = + aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION; + } else { + // Normal buffer layers + compositionState->hdrMetadata = mBufferInfo.mHdrMetadata; + compositionState->compositionType = mPotentialCursor + ? aidl::android::hardware::graphics::composer3::Composition::CURSOR + : aidl::android::hardware::graphics::composer3::Composition::DEVICE; + } + + compositionState->buffer = getBuffer(); + compositionState->bufferSlot = (mBufferInfo.mBufferSlot == BufferQueue::INVALID_BUFFER_SLOT) + ? 0 + : mBufferInfo.mBufferSlot; + compositionState->acquireFence = mBufferInfo.mFence; + compositionState->frameNumber = mBufferInfo.mFrameNumber; + compositionState->sidebandStreamHasFrame = false; +} + +void Layer::preparePerFrameEffectsCompositionState() { + auto* compositionState = editCompositionState(); + compositionState->color = getColor(); + compositionState->compositionType = + aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR; } void Layer::prepareCursorCompositionState() { @@ -549,6 +684,29 @@ const char* Layer::getDebugName() const { std::optional Layer::prepareClientComposition( compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { + std::optional layerSettings = + prepareClientCompositionInternal(targetSettings); + // Nothing to render. + if (!layerSettings) { + return {}; + } + + // HWC requests to clear this layer. + if (targetSettings.clearContent) { + prepareClearClientComposition(*layerSettings, false /* blackout */); + return layerSettings; + } + + // set the shadow for the layer if needed + prepareShadowClientComposition(*layerSettings, targetSettings.viewport); + + return layerSettings; +} + +std::optional Layer::prepareClientCompositionInternal( + compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { + ATRACE_CALL(); + if (!getCompositionState()) { return {}; } @@ -610,6 +768,13 @@ std::optional Layer::prepareClientCom layerSettings.stretchEffect = getStretchEffect(); // Record the name of the layer for debugging further down the stack. layerSettings.name = getName(); + + if (hasEffect()) { + prepareEffectsClientComposition(layerSettings, targetSettings); + return layerSettings; + } + + prepareBufferStateClientComposition(layerSettings, targetSettings); return layerSettings; } @@ -626,6 +791,132 @@ void Layer::prepareClearClientComposition(LayerFE::LayerSettings& layerSettings, layerSettings.name = getName(); } +void Layer::prepareEffectsClientComposition( + compositionengine::LayerFE::LayerSettings& layerSettings, + compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { + // If fill bounds are occluded or the fill color is invalid skip the fill settings. + if (targetSettings.realContentIsVisible && fillsColor()) { + // Set color for color fill settings. + layerSettings.source.solidColor = getColor().rgb; + } else if (hasBlur() || drawShadows()) { + layerSettings.skipContentDraw = true; + } +} + +void Layer::prepareBufferStateClientComposition( + compositionengine::LayerFE::LayerSettings& layerSettings, + compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { + if (CC_UNLIKELY(mBufferInfo.mBuffer == 0) && mSidebandStream != nullptr) { + // For surfaceview of tv sideband, there is no activeBuffer + // in bufferqueue, we need return LayerSettings. + return; + } + const bool blackOutLayer = (isProtected() && !targetSettings.supportsProtectedContent) || + ((isSecure() || isProtected()) && !targetSettings.isSecure); + const bool bufferCanBeUsedAsHwTexture = + mBufferInfo.mBuffer->getUsage() & GraphicBuffer::USAGE_HW_TEXTURE; + if (blackOutLayer || !bufferCanBeUsedAsHwTexture) { + ALOGE_IF(!bufferCanBeUsedAsHwTexture, "%s is blacked out as buffer is not gpu readable", + mName.c_str()); + prepareClearClientComposition(layerSettings, true /* blackout */); + return; + } + + const State& s(getDrawingState()); + layerSettings.source.buffer.buffer = mBufferInfo.mBuffer; + layerSettings.source.buffer.isOpaque = isOpaque(s); + layerSettings.source.buffer.fence = mBufferInfo.mFence; + layerSettings.source.buffer.textureName = mTextureName; + layerSettings.source.buffer.usePremultipliedAlpha = getPremultipledAlpha(); + layerSettings.source.buffer.isY410BT2020 = isHdrY410(); + bool hasSmpte2086 = mBufferInfo.mHdrMetadata.validTypes & HdrMetadata::SMPTE2086; + bool hasCta861_3 = mBufferInfo.mHdrMetadata.validTypes & HdrMetadata::CTA861_3; + float maxLuminance = 0.f; + if (hasSmpte2086 && hasCta861_3) { + maxLuminance = std::min(mBufferInfo.mHdrMetadata.smpte2086.maxLuminance, + mBufferInfo.mHdrMetadata.cta8613.maxContentLightLevel); + } else if (hasSmpte2086) { + maxLuminance = mBufferInfo.mHdrMetadata.smpte2086.maxLuminance; + } else if (hasCta861_3) { + maxLuminance = mBufferInfo.mHdrMetadata.cta8613.maxContentLightLevel; + } else { + switch (layerSettings.sourceDataspace & HAL_DATASPACE_TRANSFER_MASK) { + case HAL_DATASPACE_TRANSFER_ST2084: + case HAL_DATASPACE_TRANSFER_HLG: + // Behavior-match previous releases for HDR content + maxLuminance = defaultMaxLuminance; + break; + } + } + layerSettings.source.buffer.maxLuminanceNits = maxLuminance; + layerSettings.frameNumber = mCurrentFrameNumber; + layerSettings.bufferId = mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getId() : 0; + + const bool useFiltering = + targetSettings.needsFiltering || mNeedsFiltering || bufferNeedsFiltering(); + + // Query the texture matrix given our current filtering mode. + float textureMatrix[16]; + getDrawingTransformMatrix(useFiltering, textureMatrix); + + if (getTransformToDisplayInverse()) { + /* + * the code below applies the primary display's inverse transform to + * the texture transform + */ + uint32_t transform = DisplayDevice::getPrimaryDisplayRotationFlags(); + mat4 tr = inverseOrientation(transform); + + /** + * TODO(b/36727915): This is basically a hack. + * + * Ensure that regardless of the parent transformation, + * this buffer is always transformed from native display + * orientation to display orientation. For example, in the case + * of a camera where the buffer remains in native orientation, + * we want the pixels to always be upright. + */ + sp p = mDrawingParent.promote(); + if (p != nullptr) { + const auto parentTransform = p->getTransform(); + tr = tr * inverseOrientation(parentTransform.getOrientation()); + } + + // and finally apply it to the original texture matrix + const mat4 texTransform(mat4(static_cast(textureMatrix)) * tr); + memcpy(textureMatrix, texTransform.asArray(), sizeof(textureMatrix)); + } + + const Rect win{getBounds()}; + float bufferWidth = getBufferSize(s).getWidth(); + float bufferHeight = getBufferSize(s).getHeight(); + + // BufferStateLayers can have a "buffer size" of [0, 0, -1, -1] when no display frame has + // been set and there is no parent layer bounds. In that case, the scale is meaningless so + // ignore them. + if (!getBufferSize(s).isValid()) { + bufferWidth = float(win.right) - float(win.left); + bufferHeight = float(win.bottom) - float(win.top); + } + + const float scaleHeight = (float(win.bottom) - float(win.top)) / bufferHeight; + const float scaleWidth = (float(win.right) - float(win.left)) / bufferWidth; + const float translateY = float(win.top) / bufferHeight; + const float translateX = float(win.left) / bufferWidth; + + // Flip y-coordinates because GLConsumer expects OpenGL convention. + mat4 tr = mat4::translate(vec4(.5f, .5f, 0.f, 1.f)) * mat4::scale(vec4(1.f, -1.f, 1.f, 1.f)) * + mat4::translate(vec4(-.5f, -.5f, 0.f, 1.f)) * + mat4::translate(vec4(translateX, translateY, 0.f, 1.f)) * + mat4::scale(vec4(scaleWidth, scaleHeight, 1.0f, 1.0f)); + + layerSettings.source.buffer.useTextureFiltering = useFiltering; + layerSettings.source.buffer.textureTransform = + mat4(static_cast(textureMatrix)) * tr; + + return; +} + aidl::android::hardware::graphics::composer3::Composition Layer::getCompositionType( const DisplayDevice& display) const { const auto outputLayer = findOutputLayerForDisplay(&display); @@ -2415,6 +2706,40 @@ Region Layer::getVisibleRegion(const DisplayDevice* display) const { void Layer::setInitialValuesForClone(const sp& clonedFrom) { cloneDrawingState(clonedFrom.get()); mClonedFrom = clonedFrom; + mPremultipliedAlpha = clonedFrom->mPremultipliedAlpha; + mPotentialCursor = clonedFrom->mPotentialCursor; + mProtectedByApp = clonedFrom->mProtectedByApp; + updateCloneBufferInfo(); +} + +void Layer::updateCloneBufferInfo() { + if (!isClone() || !isClonedFromAlive()) { + return; + } + + sp clonedFrom = getClonedFrom(); + mBufferInfo = clonedFrom->mBufferInfo; + mSidebandStream = clonedFrom->mSidebandStream; + surfaceDamageRegion = clonedFrom->surfaceDamageRegion; + mCurrentFrameNumber = clonedFrom->mCurrentFrameNumber.load(); + mPreviousFrameNumber = clonedFrom->mPreviousFrameNumber; + + // After buffer info is updated, the drawingState from the real layer needs to be copied into + // the cloned. This is because some properties of drawingState can change when latchBuffer is + // called. However, copying the drawingState would also overwrite the cloned layer's relatives + // and touchableRegionCrop. Therefore, temporarily store the relatives so they can be set in + // the cloned drawingState again. + wp tmpZOrderRelativeOf = mDrawingState.zOrderRelativeOf; + SortedVector> tmpZOrderRelatives = mDrawingState.zOrderRelatives; + wp tmpTouchableRegionCrop = mDrawingState.touchableRegionCrop; + WindowInfo tmpInputInfo = mDrawingState.inputInfo; + + cloneDrawingState(clonedFrom.get()); + + mDrawingState.touchableRegionCrop = tmpTouchableRegionCrop; + mDrawingState.zOrderRelativeOf = tmpZOrderRelativeOf; + mDrawingState.zOrderRelatives = tmpZOrderRelatives; + mDrawingState.inputInfo = tmpInputInfo; } void Layer::updateMirrorInfo() { @@ -2622,6 +2947,1299 @@ void Layer::cloneDrawingState(const Layer* from) { mDrawingState.callbackHandles = {}; } +void Layer::callReleaseBufferCallback(const sp& listener, + const sp& buffer, uint64_t framenumber, + const sp& releaseFence, + uint32_t currentMaxAcquiredBufferCount) { + if (!listener) { + return; + } + ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64, getDebugName(), framenumber); + listener->onReleaseBuffer({buffer->getId(), framenumber}, + releaseFence ? releaseFence : Fence::NO_FENCE, + currentMaxAcquiredBufferCount); +} + +void Layer::onLayerDisplayed(ftl::SharedFuture futureFenceResult) { + // If we are displayed on multiple displays in a single composition cycle then we would + // need to do careful tracking to enable the use of the mLastClientCompositionFence. + // For example we can only use it if all the displays are client comp, and we need + // to merge all the client comp fences. We could do this, but for now we just + // disable the optimization when a layer is composed on multiple displays. + if (mClearClientCompositionFenceOnLayerDisplayed) { + mLastClientCompositionFence = nullptr; + } else { + mClearClientCompositionFenceOnLayerDisplayed = true; + } + + // The previous release fence notifies the client that SurfaceFlinger is done with the previous + // buffer that was presented on this layer. The first transaction that came in this frame that + // replaced the previous buffer on this layer needs this release fence, because the fence will + // let the client know when that previous buffer is removed from the screen. + // + // Every other transaction on this layer does not need a release fence because no other + // Transactions that were set on this layer this frame are going to have their preceding buffer + // removed from the display this frame. + // + // For example, if we have 3 transactions this frame. The first transaction doesn't contain a + // buffer so it doesn't need a previous release fence because the layer still needs the previous + // buffer. The second transaction contains a buffer so it needs a previous release fence because + // the previous buffer will be released this frame. The third transaction also contains a + // buffer. It replaces the buffer in the second transaction. The buffer in the second + // transaction will now no longer be presented so it is released immediately and the third + // transaction doesn't need a previous release fence. + sp ch; + for (auto& handle : mDrawingState.callbackHandles) { + if (handle->releasePreviousBuffer && + mDrawingState.releaseBufferEndpoint == handle->listener) { + ch = handle; + break; + } + } + + // Prevent tracing the same release multiple times. + if (mPreviousFrameNumber != mPreviousReleasedFrameNumber) { + mPreviousReleasedFrameNumber = mPreviousFrameNumber; + } + + if (ch != nullptr) { + ch->previousReleaseCallbackId = mPreviousReleaseCallbackId; + ch->previousReleaseFences.emplace_back(std::move(futureFenceResult)); + ch->name = mName; + } +} + +void Layer::onSurfaceFrameCreated( + const std::shared_ptr& surfaceFrame) { + while (mPendingJankClassifications.size() >= kPendingClassificationMaxSurfaceFrames) { + // Too many SurfaceFrames pending classification. The front of the deque is probably not + // tracked by FrameTimeline and will never be presented. This will only result in a memory + // leak. + ALOGW("Removing the front of pending jank deque from layer - %s to prevent memory leak", + mName.c_str()); + std::string miniDump = mPendingJankClassifications.front()->miniDump(); + ALOGD("Head SurfaceFrame mini dump\n%s", miniDump.c_str()); + mPendingJankClassifications.pop_front(); + } + mPendingJankClassifications.emplace_back(surfaceFrame); +} + +void Layer::releasePendingBuffer(nsecs_t dequeueReadyTime) { + for (const auto& handle : mDrawingState.callbackHandles) { + handle->transformHint = mTransformHint; + handle->dequeueReadyTime = dequeueReadyTime; + handle->currentMaxAcquiredBufferCount = + mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mOwnerUid); + ATRACE_FORMAT_INSTANT("releasePendingBuffer %s - %" PRIu64, getDebugName(), + handle->previousReleaseCallbackId.framenumber); + } + + for (auto& handle : mDrawingState.callbackHandles) { + if (handle->releasePreviousBuffer && + mDrawingState.releaseBufferEndpoint == handle->listener) { + handle->previousReleaseCallbackId = mPreviousReleaseCallbackId; + break; + } + } + + std::vector jankData; + jankData.reserve(mPendingJankClassifications.size()); + while (!mPendingJankClassifications.empty() && + mPendingJankClassifications.front()->getJankType()) { + std::shared_ptr surfaceFrame = + mPendingJankClassifications.front(); + mPendingJankClassifications.pop_front(); + jankData.emplace_back( + JankData(surfaceFrame->getToken(), surfaceFrame->getJankType().value())); + } + + mFlinger->getTransactionCallbackInvoker().addCallbackHandles(mDrawingState.callbackHandles, + jankData); + + sp releaseFence = Fence::NO_FENCE; + for (auto& handle : mDrawingState.callbackHandles) { + if (handle->releasePreviousBuffer && + mDrawingState.releaseBufferEndpoint == handle->listener) { + releaseFence = + handle->previousReleaseFence ? handle->previousReleaseFence : Fence::NO_FENCE; + break; + } + } + + mDrawingState.callbackHandles = {}; +} + +bool Layer::willPresentCurrentTransaction() const { + // Returns true if the most recent Transaction applied to CurrentState will be presented. + return (getSidebandStreamChanged() || getAutoRefresh() || + (mDrawingState.modified && + (mDrawingState.buffer != nullptr || mDrawingState.bgColorLayer != nullptr))); +} + +bool Layer::setTransform(uint32_t transform) { + if (mDrawingState.bufferTransform == transform) return false; + mDrawingState.bufferTransform = transform; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool Layer::setTransformToDisplayInverse(bool transformToDisplayInverse) { + if (mDrawingState.transformToDisplayInverse == transformToDisplayInverse) return false; + mDrawingState.sequence++; + mDrawingState.transformToDisplayInverse = transformToDisplayInverse; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool Layer::setBufferCrop(const Rect& bufferCrop) { + if (mDrawingState.bufferCrop == bufferCrop) return false; + + mDrawingState.sequence++; + mDrawingState.bufferCrop = bufferCrop; + + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool Layer::setDestinationFrame(const Rect& destinationFrame) { + if (mDrawingState.destinationFrame == destinationFrame) return false; + + mDrawingState.sequence++; + mDrawingState.destinationFrame = destinationFrame; + + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +// Translate destination frame into scale and position. If a destination frame is not set, use the +// provided scale and position +bool Layer::updateGeometry() { + if ((mDrawingState.flags & layer_state_t::eIgnoreDestinationFrame) || + mDrawingState.destinationFrame.isEmpty()) { + // If destination frame is not set, use the requested transform set via + // Layer::setPosition and Layer::setMatrix. + return assignTransform(&mDrawingState.transform, mRequestedTransform); + } + + Rect destRect = mDrawingState.destinationFrame; + int32_t destW = destRect.width(); + int32_t destH = destRect.height(); + if (destRect.left < 0) { + destRect.left = 0; + destRect.right = destW; + } + if (destRect.top < 0) { + destRect.top = 0; + destRect.bottom = destH; + } + + if (!mDrawingState.buffer) { + ui::Transform t; + t.set(destRect.left, destRect.top); + return assignTransform(&mDrawingState.transform, t); + } + + uint32_t bufferWidth = mDrawingState.buffer->getWidth(); + uint32_t bufferHeight = mDrawingState.buffer->getHeight(); + // Undo any transformations on the buffer. + if (mDrawingState.bufferTransform & ui::Transform::ROT_90) { + std::swap(bufferWidth, bufferHeight); + } + uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags(); + if (mDrawingState.transformToDisplayInverse) { + if (invTransform & ui::Transform::ROT_90) { + std::swap(bufferWidth, bufferHeight); + } + } + + float sx = destW / static_cast(bufferWidth); + float sy = destH / static_cast(bufferHeight); + ui::Transform t; + t.set(sx, 0, 0, sy); + t.set(destRect.left, destRect.top); + return assignTransform(&mDrawingState.transform, t); +} + +bool Layer::setMatrix(const layer_state_t::matrix22_t& matrix) { + if (mRequestedTransform.dsdx() == matrix.dsdx && mRequestedTransform.dtdy() == matrix.dtdy && + mRequestedTransform.dtdx() == matrix.dtdx && mRequestedTransform.dsdy() == matrix.dsdy) { + return false; + } + + mRequestedTransform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy); + + mDrawingState.sequence++; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + + return true; +} + +bool Layer::setPosition(float x, float y) { + if (mRequestedTransform.tx() == x && mRequestedTransform.ty() == y) { + return false; + } + + mRequestedTransform.set(x, y); + + mDrawingState.sequence++; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + + return true; +} + +bool Layer::setBuffer(std::shared_ptr& buffer, + const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime, + bool isAutoTimestamp, std::optional dequeueTime, + const FrameTimelineInfo& info) { + ATRACE_CALL(); + + if (!buffer) { + return false; + } + + const bool frameNumberChanged = + bufferData.flags.test(BufferData::BufferDataChange::frameNumberChanged); + const uint64_t frameNumber = + frameNumberChanged ? bufferData.frameNumber : mDrawingState.frameNumber + 1; + + if (mDrawingState.buffer) { + mReleasePreviousBuffer = true; + if (!mBufferInfo.mBuffer || + (!mDrawingState.buffer->hasSameBuffer(*mBufferInfo.mBuffer) || + mDrawingState.frameNumber != mBufferInfo.mFrameNumber)) { + // If mDrawingState has a buffer, and we are about to update again + // before swapping to drawing state, then the first buffer will be + // dropped and we should decrement the pending buffer count and + // call any release buffer callbacks if set. + callReleaseBufferCallback(mDrawingState.releaseBufferListener, + mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber, + mDrawingState.acquireFence, + mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate( + mOwnerUid)); + decrementPendingBufferCount(); + if (mDrawingState.bufferSurfaceFrameTX != nullptr && + mDrawingState.bufferSurfaceFrameTX->getPresentState() != PresentState::Presented) { + addSurfaceFrameDroppedForBuffer(mDrawingState.bufferSurfaceFrameTX); + mDrawingState.bufferSurfaceFrameTX.reset(); + } + } else if (EARLY_RELEASE_ENABLED && mLastClientCompositionFence != nullptr) { + callReleaseBufferCallback(mDrawingState.releaseBufferListener, + mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber, + mLastClientCompositionFence, + mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate( + mOwnerUid)); + mLastClientCompositionFence = nullptr; + } + } + + mDrawingState.frameNumber = frameNumber; + mDrawingState.releaseBufferListener = bufferData.releaseBufferListener; + mDrawingState.buffer = std::move(buffer); + mDrawingState.clientCacheId = bufferData.cachedBuffer; + + mDrawingState.acquireFence = bufferData.flags.test(BufferData::BufferDataChange::fenceChanged) + ? bufferData.acquireFence + : Fence::NO_FENCE; + mDrawingState.acquireFenceTime = std::make_unique(mDrawingState.acquireFence); + if (mDrawingState.acquireFenceTime->getSignalTime() == Fence::SIGNAL_TIME_PENDING) { + // We latched this buffer unsiganled, so we need to pass the acquire fence + // on the callback instead of just the acquire time, since it's unknown at + // this point. + mCallbackHandleAcquireTimeOrFence = mDrawingState.acquireFence; + } else { + mCallbackHandleAcquireTimeOrFence = mDrawingState.acquireFenceTime->getSignalTime(); + } + + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + + const int32_t layerId = getSequence(); + mFlinger->mTimeStats->setPostTime(layerId, mDrawingState.frameNumber, getName().c_str(), + mOwnerUid, postTime, getGameMode()); + mDrawingState.desiredPresentTime = desiredPresentTime; + mDrawingState.isAutoTimestamp = isAutoTimestamp; + + const nsecs_t presentTime = [&] { + if (!isAutoTimestamp) return desiredPresentTime; + + const auto prediction = + mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken(info.vsyncId); + if (prediction.has_value()) return prediction->presentTime; + + return static_cast(0); + }(); + + using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType; + mFlinger->mScheduler->recordLayerHistory(this, presentTime, LayerUpdateType::Buffer); + + setFrameTimelineVsyncForBufferTransaction(info, postTime); + + if (dequeueTime && *dequeueTime != 0) { + const uint64_t bufferId = mDrawingState.buffer->getId(); + mFlinger->mFrameTracer->traceNewLayer(layerId, getName().c_str()); + mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, *dequeueTime, + FrameTracer::FrameEvent::DEQUEUE); + mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, postTime, + FrameTracer::FrameEvent::QUEUE); + } + + mDrawingState.releaseBufferEndpoint = bufferData.releaseBufferEndpoint; + return true; +} + +bool Layer::setDataspace(ui::Dataspace dataspace) { + mDrawingState.dataspaceRequested = true; + if (mDrawingState.dataspace == dataspace) return false; + mDrawingState.dataspace = dataspace; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool Layer::setHdrMetadata(const HdrMetadata& hdrMetadata) { + if (mDrawingState.hdrMetadata == hdrMetadata) return false; + mDrawingState.hdrMetadata = hdrMetadata; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool Layer::setSurfaceDamageRegion(const Region& surfaceDamage) { + mDrawingState.surfaceDamageRegion = surfaceDamage; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool Layer::setApi(int32_t api) { + if (mDrawingState.api == api) return false; + mDrawingState.api = api; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool Layer::setSidebandStream(const sp& sidebandStream) { + if (mDrawingState.sidebandStream == sidebandStream) return false; + + if (mDrawingState.sidebandStream != nullptr && sidebandStream == nullptr) { + mFlinger->mTunnelModeEnabledReporter->decrementTunnelModeCount(); + } else if (sidebandStream != nullptr) { + mFlinger->mTunnelModeEnabledReporter->incrementTunnelModeCount(); + } + + mDrawingState.sidebandStream = sidebandStream; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + if (!mSidebandStreamChanged.exchange(true)) { + // mSidebandStreamChanged was false + mFlinger->onLayerUpdate(); + } + return true; +} + +bool Layer::setTransactionCompletedListeners(const std::vector>& handles) { + // If there is no handle, we will not send a callback so reset mReleasePreviousBuffer and return + if (handles.empty()) { + mReleasePreviousBuffer = false; + return false; + } + + const bool willPresent = willPresentCurrentTransaction(); + + for (const auto& handle : handles) { + // If this transaction set a buffer on this layer, release its previous buffer + handle->releasePreviousBuffer = mReleasePreviousBuffer; + + // If this layer will be presented in this frame + if (willPresent) { + // If this transaction set an acquire fence on this layer, set its acquire time + handle->acquireTimeOrFence = mCallbackHandleAcquireTimeOrFence; + handle->frameNumber = mDrawingState.frameNumber; + + // Store so latched time and release fence can be set + mDrawingState.callbackHandles.push_back(handle); + + } else { // If this layer will NOT need to be relatched and presented this frame + // Notify the transaction completed thread this handle is done + mFlinger->getTransactionCallbackInvoker().registerUnpresentedCallbackHandle(handle); + } + } + + mReleasePreviousBuffer = false; + mCallbackHandleAcquireTimeOrFence = -1; + + return willPresent; +} + +Rect Layer::getBufferSize(const State& /*s*/) const { + // for buffer state layers we use the display frame size as the buffer size. + + if (mBufferInfo.mBuffer == nullptr) { + return Rect::INVALID_RECT; + } + + uint32_t bufWidth = mBufferInfo.mBuffer->getWidth(); + uint32_t bufHeight = mBufferInfo.mBuffer->getHeight(); + + // Undo any transformations on the buffer and return the result. + if (mBufferInfo.mTransform & ui::Transform::ROT_90) { + std::swap(bufWidth, bufHeight); + } + + if (getTransformToDisplayInverse()) { + uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags(); + if (invTransform & ui::Transform::ROT_90) { + std::swap(bufWidth, bufHeight); + } + } + + return Rect(0, 0, static_cast(bufWidth), static_cast(bufHeight)); +} + +FloatRect Layer::computeSourceBounds(const FloatRect& parentBounds) const { + if (mBufferInfo.mBuffer == nullptr) { + return parentBounds; + } + + return getBufferSize(getDrawingState()).toFloatRect(); +} + +bool Layer::fenceHasSignaled() const { + if (SurfaceFlinger::enableLatchUnsignaledConfig != LatchUnsignaledConfig::Disabled) { + return true; + } + + const bool fenceSignaled = + getDrawingState().acquireFence->getStatus() == Fence::Status::Signaled; + if (!fenceSignaled) { + mFlinger->mTimeStats->incrementLatchSkipped(getSequence(), + TimeStats::LatchSkipReason::LateAcquire); + } + + return fenceSignaled; +} + +bool Layer::onPreComposition(nsecs_t refreshStartTime) { + for (const auto& handle : mDrawingState.callbackHandles) { + handle->refreshStartTime = refreshStartTime; + } + return hasReadyFrame(); +} + +void Layer::setAutoRefresh(bool autoRefresh) { + mDrawingState.autoRefresh = autoRefresh; +} + +bool Layer::latchSidebandStream(bool& recomputeVisibleRegions) { + // We need to update the sideband stream if the layer has both a buffer and a sideband stream. + editCompositionState()->sidebandStreamHasFrame = hasFrameUpdate() && mSidebandStream.get(); + + if (mSidebandStreamChanged.exchange(false)) { + const State& s(getDrawingState()); + // mSidebandStreamChanged was true + mSidebandStream = s.sidebandStream; + editCompositionState()->sidebandStream = mSidebandStream; + if (mSidebandStream != nullptr) { + setTransactionFlags(eTransactionNeeded); + mFlinger->setTransactionFlags(eTraversalNeeded); + } + recomputeVisibleRegions = true; + + return true; + } + return false; +} + +bool Layer::hasFrameUpdate() const { + const State& c(getDrawingState()); + return (mDrawingStateModified || mDrawingState.modified) && + (c.buffer != nullptr || c.bgColorLayer != nullptr); +} + +void Layer::updateTexImage(nsecs_t latchTime) { + const State& s(getDrawingState()); + + if (!s.buffer) { + if (s.bgColorLayer) { + for (auto& handle : mDrawingState.callbackHandles) { + handle->latchTime = latchTime; + } + } + return; + } + + for (auto& handle : mDrawingState.callbackHandles) { + if (handle->frameNumber == mDrawingState.frameNumber) { + handle->latchTime = latchTime; + } + } + + const int32_t layerId = getSequence(); + const uint64_t bufferId = mDrawingState.buffer->getId(); + const uint64_t frameNumber = mDrawingState.frameNumber; + const auto acquireFence = std::make_shared(mDrawingState.acquireFence); + mFlinger->mTimeStats->setAcquireFence(layerId, frameNumber, acquireFence); + mFlinger->mTimeStats->setLatchTime(layerId, frameNumber, latchTime); + + mFlinger->mFrameTracer->traceFence(layerId, bufferId, frameNumber, acquireFence, + FrameTracer::FrameEvent::ACQUIRE_FENCE); + mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, latchTime, + FrameTracer::FrameEvent::LATCH); + + auto& bufferSurfaceFrame = mDrawingState.bufferSurfaceFrameTX; + if (bufferSurfaceFrame != nullptr && + bufferSurfaceFrame->getPresentState() != PresentState::Presented) { + // Update only if the bufferSurfaceFrame wasn't already presented. A Presented + // bufferSurfaceFrame could be seen here if a pending state was applied successfully and we + // are processing the next state. + addSurfaceFramePresentedForBuffer(bufferSurfaceFrame, + mDrawingState.acquireFenceTime->getSignalTime(), + latchTime); + mDrawingState.bufferSurfaceFrameTX.reset(); + } + + std::deque> remainingHandles; + mFlinger->getTransactionCallbackInvoker() + .addOnCommitCallbackHandles(mDrawingState.callbackHandles, remainingHandles); + mDrawingState.callbackHandles = remainingHandles; + + mDrawingStateModified = false; +} + +void Layer::gatherBufferInfo() { + if (!mBufferInfo.mBuffer || !mDrawingState.buffer->hasSameBuffer(*mBufferInfo.mBuffer)) { + decrementPendingBufferCount(); + } + + mPreviousReleaseCallbackId = {getCurrentBufferId(), mBufferInfo.mFrameNumber}; + mBufferInfo.mBuffer = mDrawingState.buffer; + mBufferInfo.mFence = mDrawingState.acquireFence; + mBufferInfo.mFrameNumber = mDrawingState.frameNumber; + mBufferInfo.mPixelFormat = + !mBufferInfo.mBuffer ? PIXEL_FORMAT_NONE : mBufferInfo.mBuffer->getPixelFormat(); + mBufferInfo.mFrameLatencyNeeded = true; + mBufferInfo.mDesiredPresentTime = mDrawingState.desiredPresentTime; + mBufferInfo.mFenceTime = std::make_shared(mDrawingState.acquireFence); + mBufferInfo.mFence = mDrawingState.acquireFence; + mBufferInfo.mTransform = mDrawingState.bufferTransform; + auto lastDataspace = mBufferInfo.mDataspace; + mBufferInfo.mDataspace = translateDataspace(mDrawingState.dataspace); + if (lastDataspace != mBufferInfo.mDataspace) { + mFlinger->mSomeDataspaceChanged = true; + } + mBufferInfo.mCrop = computeBufferCrop(mDrawingState); + mBufferInfo.mScaleMode = NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW; + mBufferInfo.mSurfaceDamage = mDrawingState.surfaceDamageRegion; + mBufferInfo.mHdrMetadata = mDrawingState.hdrMetadata; + mBufferInfo.mApi = mDrawingState.api; + mBufferInfo.mTransformToDisplayInverse = mDrawingState.transformToDisplayInverse; + mBufferInfo.mBufferSlot = mHwcSlotGenerator->getHwcCacheSlot(mDrawingState.clientCacheId); +} + +Rect Layer::computeBufferCrop(const State& s) { + if (s.buffer && !s.bufferCrop.isEmpty()) { + Rect bufferCrop; + s.buffer->getBounds().intersect(s.bufferCrop, &bufferCrop); + return bufferCrop; + } else if (s.buffer) { + return s.buffer->getBounds(); + } else { + return s.bufferCrop; + } +} + +sp Layer::createClone() { + LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata()); + args.textureName = mTextureName; + sp layer = mFlinger->getFactory().createBufferStateLayer(args); + layer->mHwcSlotGenerator = mHwcSlotGenerator; + layer->setInitialValuesForClone(sp::fromExisting(this)); + return layer; +} + +bool Layer::bufferNeedsFiltering() const { + const State& s(getDrawingState()); + if (!s.buffer) { + return false; + } + + int32_t bufferWidth = static_cast(s.buffer->getWidth()); + int32_t bufferHeight = static_cast(s.buffer->getHeight()); + + // Undo any transformations on the buffer and return the result. + if (s.bufferTransform & ui::Transform::ROT_90) { + std::swap(bufferWidth, bufferHeight); + } + + if (s.transformToDisplayInverse) { + uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags(); + if (invTransform & ui::Transform::ROT_90) { + std::swap(bufferWidth, bufferHeight); + } + } + + const Rect layerSize{getBounds()}; + int32_t layerWidth = layerSize.getWidth(); + int32_t layerHeight = layerSize.getHeight(); + + // Align the layer orientation with the buffer before comparism + if (mTransformHint & ui::Transform::ROT_90) { + std::swap(layerWidth, layerHeight); + } + + return layerWidth != bufferWidth || layerHeight != bufferHeight; +} + +void Layer::decrementPendingBufferCount() { + int32_t pendingBuffers = --mPendingBufferTransactions; + tracePendingBufferCount(pendingBuffers); +} + +void Layer::tracePendingBufferCount(int32_t pendingBuffers) { + ATRACE_INT(mBlastTransactionName.c_str(), pendingBuffers); +} + +/* + * We don't want to send the layer's transform to input, but rather the + * parent's transform. This is because Layer's transform is + * information about how the buffer is placed on screen. The parent's + * transform makes more sense to send since it's information about how the + * layer is placed on screen. This transform is used by input to determine + * how to go from screen space back to window space. + */ +ui::Transform Layer::getInputTransform() const { + if (!hasBufferOrSidebandStream()) { + return getTransform(); + } + sp parent = mDrawingParent.promote(); + if (parent == nullptr) { + return ui::Transform(); + } + + return parent->getTransform(); +} + +/** + * Similar to getInputTransform, we need to update the bounds to include the transform. + * This is because bounds don't include the buffer transform, where the input assumes + * that's already included. + */ +Rect Layer::getInputBounds() const { + if (!hasBufferOrSidebandStream()) { + return getCroppedBufferSize(getDrawingState()); + } + + Rect bufferBounds = getCroppedBufferSize(getDrawingState()); + if (mDrawingState.transform.getType() == ui::Transform::IDENTITY || !bufferBounds.isValid()) { + return bufferBounds; + } + return mDrawingState.transform.transform(bufferBounds); +} + +bool Layer::simpleBufferUpdate(const layer_state_t& s) const { + const uint64_t requiredFlags = layer_state_t::eBufferChanged; + + const uint64_t deniedFlags = layer_state_t::eProducerDisconnect | layer_state_t::eLayerChanged | + layer_state_t::eRelativeLayerChanged | layer_state_t::eTransparentRegionChanged | + layer_state_t::eFlagsChanged | layer_state_t::eBlurRegionsChanged | + layer_state_t::eLayerStackChanged | layer_state_t::eAutoRefreshChanged | + layer_state_t::eReparent; + + const uint64_t allowedFlags = layer_state_t::eHasListenerCallbacksChanged | + layer_state_t::eFrameRateSelectionPriority | layer_state_t::eFrameRateChanged | + layer_state_t::eSurfaceDamageRegionChanged | layer_state_t::eApiChanged | + layer_state_t::eMetadataChanged | layer_state_t::eDropInputModeChanged | + layer_state_t::eInputInfoChanged; + + if ((s.what & requiredFlags) != requiredFlags) { + ALOGV("%s: false [missing required flags 0x%" PRIx64 "]", __func__, + (s.what | requiredFlags) & ~s.what); + return false; + } + + if (s.what & deniedFlags) { + ALOGV("%s: false [has denied flags 0x%" PRIx64 "]", __func__, s.what & deniedFlags); + return false; + } + + if (s.what & allowedFlags) { + ALOGV("%s: [has allowed flags 0x%" PRIx64 "]", __func__, s.what & allowedFlags); + } + + if (s.what & layer_state_t::ePositionChanged) { + if (mRequestedTransform.tx() != s.x || mRequestedTransform.ty() != s.y) { + ALOGV("%s: false [ePositionChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eAlphaChanged) { + if (mDrawingState.color.a != s.alpha) { + ALOGV("%s: false [eAlphaChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eColorTransformChanged) { + if (mDrawingState.colorTransform != s.colorTransform) { + ALOGV("%s: false [eColorTransformChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eBackgroundColorChanged) { + if (mDrawingState.bgColorLayer || s.bgColorAlpha != 0) { + ALOGV("%s: false [eBackgroundColorChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eMatrixChanged) { + if (mRequestedTransform.dsdx() != s.matrix.dsdx || + mRequestedTransform.dtdy() != s.matrix.dtdy || + mRequestedTransform.dtdx() != s.matrix.dtdx || + mRequestedTransform.dsdy() != s.matrix.dsdy) { + ALOGV("%s: false [eMatrixChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eCornerRadiusChanged) { + if (mDrawingState.cornerRadius != s.cornerRadius) { + ALOGV("%s: false [eCornerRadiusChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eBackgroundBlurRadiusChanged) { + if (mDrawingState.backgroundBlurRadius != static_cast(s.backgroundBlurRadius)) { + ALOGV("%s: false [eBackgroundBlurRadiusChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eTransformChanged) { + if (mDrawingState.bufferTransform != s.transform) { + ALOGV("%s: false [eTransformChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eTransformToDisplayInverseChanged) { + if (mDrawingState.transformToDisplayInverse != s.transformToDisplayInverse) { + ALOGV("%s: false [eTransformToDisplayInverseChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eCropChanged) { + if (mDrawingState.crop != s.crop) { + ALOGV("%s: false [eCropChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eDataspaceChanged) { + if (mDrawingState.dataspace != s.dataspace) { + ALOGV("%s: false [eDataspaceChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eHdrMetadataChanged) { + if (mDrawingState.hdrMetadata != s.hdrMetadata) { + ALOGV("%s: false [eHdrMetadataChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eSidebandStreamChanged) { + if (mDrawingState.sidebandStream != s.sidebandStream) { + ALOGV("%s: false [eSidebandStreamChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eColorSpaceAgnosticChanged) { + if (mDrawingState.colorSpaceAgnostic != s.colorSpaceAgnostic) { + ALOGV("%s: false [eColorSpaceAgnosticChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eShadowRadiusChanged) { + if (mDrawingState.shadowRadius != s.shadowRadius) { + ALOGV("%s: false [eShadowRadiusChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eFixedTransformHintChanged) { + if (mDrawingState.fixedTransformHint != s.fixedTransformHint) { + ALOGV("%s: false [eFixedTransformHintChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eTrustedOverlayChanged) { + if (mDrawingState.isTrustedOverlay != s.isTrustedOverlay) { + ALOGV("%s: false [eTrustedOverlayChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eStretchChanged) { + StretchEffect temp = s.stretchEffect; + temp.sanitize(); + if (mDrawingState.stretchEffect != temp) { + ALOGV("%s: false [eStretchChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eBufferCropChanged) { + if (mDrawingState.bufferCrop != s.bufferCrop) { + ALOGV("%s: false [eBufferCropChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eDestinationFrameChanged) { + if (mDrawingState.destinationFrame != s.destinationFrame) { + ALOGV("%s: false [eDestinationFrameChanged changed]", __func__); + return false; + } + } + + if (s.what & layer_state_t::eDimmingEnabledChanged) { + if (mDrawingState.dimmingEnabled != s.dimmingEnabled) { + ALOGV("%s: false [eDimmingEnabledChanged changed]", __func__); + return false; + } + } + + ALOGV("%s: true", __func__); + return true; +} + +bool Layer::isHdrY410() const { + // pixel format is HDR Y410 masquerading as RGBA_1010102 + return (mBufferInfo.mDataspace == ui::Dataspace::BT2020_ITU_PQ && + mBufferInfo.mApi == NATIVE_WINDOW_API_MEDIA && + mBufferInfo.mPixelFormat == HAL_PIXEL_FORMAT_RGBA_1010102); +} + +sp Layer::getCompositionEngineLayerFE() const { + // There's no need to get a CE Layer if the layer isn't going to draw anything. + if (hasSomethingToDraw()) { + return asLayerFE(); + } else { + return nullptr; + } +} + +compositionengine::LayerFECompositionState* Layer::editCompositionState() { + return mCompositionState.get(); +} + +const compositionengine::LayerFECompositionState* Layer::getCompositionState() const { + return mCompositionState.get(); +} + +void Layer::useSurfaceDamage() { + if (mFlinger->mForceFullDamage) { + surfaceDamageRegion = Region::INVALID_REGION; + } else { + surfaceDamageRegion = mBufferInfo.mSurfaceDamage; + } +} + +void Layer::useEmptyDamage() { + surfaceDamageRegion.clear(); +} + +bool Layer::isOpaque(const Layer::State& s) const { + // if we don't have a buffer or sidebandStream yet, we're translucent regardless of the + // layer's opaque flag. + if (!hasSomethingToDraw()) { + return false; + } + + // if the layer has the opaque flag, then we're always opaque + if ((s.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque) { + return true; + } + + // If the buffer has no alpha channel, then we are opaque + if (hasBufferOrSidebandStream() && isOpaqueFormat(getPixelFormat())) { + return true; + } + + // Lastly consider the layer opaque if drawing a color with alpha == 1.0 + return fillsColor() && getAlpha() == 1.0_hf; +} + +bool Layer::canReceiveInput() const { + return !isHiddenByPolicy() && (mBufferInfo.mBuffer == nullptr || getAlpha() > 0.0f); +} + +bool Layer::isVisible() const { + if (!hasSomethingToDraw()) { + return false; + } + + if (isHiddenByPolicy()) { + return false; + } + + return getAlpha() > 0.0f || hasBlur(); +} + +void Layer::onPostComposition(const DisplayDevice* display, + const std::shared_ptr& glDoneFence, + const std::shared_ptr& presentFence, + const CompositorTiming& compositorTiming) { + // mFrameLatencyNeeded is true when a new frame was latched for the + // composition. + if (!mBufferInfo.mFrameLatencyNeeded) return; + + for (const auto& handle : mDrawingState.callbackHandles) { + handle->gpuCompositionDoneFence = glDoneFence; + handle->compositorTiming = compositorTiming; + } + + // Update mFrameTracker. + nsecs_t desiredPresentTime = mBufferInfo.mDesiredPresentTime; + mFrameTracker.setDesiredPresentTime(desiredPresentTime); + + const int32_t layerId = getSequence(); + mFlinger->mTimeStats->setDesiredTime(layerId, mCurrentFrameNumber, desiredPresentTime); + + const auto outputLayer = findOutputLayerForDisplay(display); + if (outputLayer && outputLayer->requiresClientComposition()) { + nsecs_t clientCompositionTimestamp = outputLayer->getState().clientCompositionTimestamp; + mFlinger->mFrameTracer->traceTimestamp(layerId, getCurrentBufferId(), mCurrentFrameNumber, + clientCompositionTimestamp, + FrameTracer::FrameEvent::FALLBACK_COMPOSITION); + // Update the SurfaceFrames in the drawing state + if (mDrawingState.bufferSurfaceFrameTX) { + mDrawingState.bufferSurfaceFrameTX->setGpuComposition(); + } + for (auto& [token, surfaceFrame] : mDrawingState.bufferlessSurfaceFramesTX) { + surfaceFrame->setGpuComposition(); + } + } + + std::shared_ptr frameReadyFence = mBufferInfo.mFenceTime; + if (frameReadyFence->isValid()) { + mFrameTracker.setFrameReadyFence(std::move(frameReadyFence)); + } else { + // There was no fence for this frame, so assume that it was ready + // to be presented at the desired present time. + mFrameTracker.setFrameReadyTime(desiredPresentTime); + } + + if (display) { + const Fps refreshRate = display->refreshRateConfigs().getActiveMode()->getFps(); + const std::optional renderRate = + mFlinger->mScheduler->getFrameRateOverride(getOwnerUid()); + + const auto vote = frameRateToSetFrameRateVotePayload(mDrawingState.frameRate); + const auto gameMode = getGameMode(); + + if (presentFence->isValid()) { + mFlinger->mTimeStats->setPresentFence(layerId, mCurrentFrameNumber, presentFence, + refreshRate, renderRate, vote, gameMode); + mFlinger->mFrameTracer->traceFence(layerId, getCurrentBufferId(), mCurrentFrameNumber, + presentFence, + FrameTracer::FrameEvent::PRESENT_FENCE); + mFrameTracker.setActualPresentFence(std::shared_ptr(presentFence)); + } else if (const auto displayId = PhysicalDisplayId::tryCast(display->getId()); + displayId && mFlinger->getHwComposer().isConnected(*displayId)) { + // The HWC doesn't support present fences, so use the refresh + // timestamp instead. + const nsecs_t actualPresentTime = display->getRefreshTimestamp(); + mFlinger->mTimeStats->setPresentTime(layerId, mCurrentFrameNumber, actualPresentTime, + refreshRate, renderRate, vote, gameMode); + mFlinger->mFrameTracer->traceTimestamp(layerId, getCurrentBufferId(), + mCurrentFrameNumber, actualPresentTime, + FrameTracer::FrameEvent::PRESENT_FENCE); + mFrameTracker.setActualPresentTime(actualPresentTime); + } + } + + mFrameTracker.advanceFrame(); + mBufferInfo.mFrameLatencyNeeded = false; +} + +bool Layer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime) { + ATRACE_FORMAT_INSTANT("latchBuffer %s - %" PRIu64, getDebugName(), + getDrawingState().frameNumber); + + bool refreshRequired = latchSidebandStream(recomputeVisibleRegions); + + if (refreshRequired) { + return refreshRequired; + } + + // If the head buffer's acquire fence hasn't signaled yet, return and + // try again later + if (!fenceHasSignaled()) { + ATRACE_NAME("!fenceHasSignaled()"); + mFlinger->onLayerUpdate(); + return false; + } + + updateTexImage(latchTime); + if (mDrawingState.buffer == nullptr) { + return false; + } + + // Capture the old state of the layer for comparisons later + BufferInfo oldBufferInfo = mBufferInfo; + const bool oldOpacity = isOpaque(mDrawingState); + mPreviousFrameNumber = mCurrentFrameNumber; + mCurrentFrameNumber = mDrawingState.frameNumber; + gatherBufferInfo(); + + if (oldBufferInfo.mBuffer == nullptr) { + // the first time we receive a buffer, we need to trigger a + // geometry invalidation. + recomputeVisibleRegions = true; + } + + if ((mBufferInfo.mCrop != oldBufferInfo.mCrop) || + (mBufferInfo.mTransform != oldBufferInfo.mTransform) || + (mBufferInfo.mScaleMode != oldBufferInfo.mScaleMode) || + (mBufferInfo.mTransformToDisplayInverse != oldBufferInfo.mTransformToDisplayInverse)) { + recomputeVisibleRegions = true; + } + + if (oldBufferInfo.mBuffer != nullptr) { + uint32_t bufWidth = mBufferInfo.mBuffer->getWidth(); + uint32_t bufHeight = mBufferInfo.mBuffer->getHeight(); + if (bufWidth != oldBufferInfo.mBuffer->getWidth() || + bufHeight != oldBufferInfo.mBuffer->getHeight()) { + recomputeVisibleRegions = true; + } + } + + if (oldOpacity != isOpaque(mDrawingState)) { + recomputeVisibleRegions = true; + } + + return true; +} + +bool Layer::hasReadyFrame() const { + return hasFrameUpdate() || getSidebandStreamChanged() || getAutoRefresh(); +} + +bool Layer::isProtected() const { + return (mBufferInfo.mBuffer != nullptr) && + (mBufferInfo.mBuffer->getUsage() & GRALLOC_USAGE_PROTECTED); +} + +// As documented in libhardware header, formats in the range +// 0x100 - 0x1FF are specific to the HAL implementation, and +// are known to have no alpha channel +// TODO: move definition for device-specific range into +// hardware.h, instead of using hard-coded values here. +#define HARDWARE_IS_DEVICE_FORMAT(f) ((f) >= 0x100 && (f) <= 0x1FF) + +bool Layer::isOpaqueFormat(PixelFormat format) { + if (HARDWARE_IS_DEVICE_FORMAT(format)) { + return true; + } + switch (format) { + case PIXEL_FORMAT_RGBA_8888: + case PIXEL_FORMAT_BGRA_8888: + case PIXEL_FORMAT_RGBA_FP16: + case PIXEL_FORMAT_RGBA_1010102: + case PIXEL_FORMAT_R_8: + return false; + } + // in all other case, we have no blending (also for unknown formats) + return true; +} + +bool Layer::needsFiltering(const DisplayDevice* display) const { + if (!hasBufferOrSidebandStream()) { + return false; + } + const auto outputLayer = findOutputLayerForDisplay(display); + if (outputLayer == nullptr) { + return false; + } + + // We need filtering if the sourceCrop rectangle size does not match the + // displayframe rectangle size (not a 1:1 render) + const auto& compositionState = outputLayer->getState(); + const auto displayFrame = compositionState.displayFrame; + const auto sourceCrop = compositionState.sourceCrop; + return sourceCrop.getHeight() != displayFrame.getHeight() || + sourceCrop.getWidth() != displayFrame.getWidth(); +} + +bool Layer::needsFilteringForScreenshots(const DisplayDevice* display, + const ui::Transform& inverseParentTransform) const { + if (!hasBufferOrSidebandStream()) { + return false; + } + const auto outputLayer = findOutputLayerForDisplay(display); + if (outputLayer == nullptr) { + return false; + } + + // We need filtering if the sourceCrop rectangle size does not match the + // viewport rectangle size (not a 1:1 render) + const auto& compositionState = outputLayer->getState(); + const ui::Transform& displayTransform = display->getTransform(); + const ui::Transform inverseTransform = inverseParentTransform * displayTransform.inverse(); + // Undo the transformation of the displayFrame so that we're back into + // layer-stack space. + const Rect frame = inverseTransform.transform(compositionState.displayFrame); + const FloatRect sourceCrop = compositionState.sourceCrop; + + int32_t frameHeight = frame.getHeight(); + int32_t frameWidth = frame.getWidth(); + // If the display transform had a rotational component then undo the + // rotation so that the orientation matches the source crop. + if (displayTransform.getOrientation() & ui::Transform::ROT_90) { + std::swap(frameHeight, frameWidth); + } + return sourceCrop.getHeight() != frameHeight || sourceCrop.getWidth() != frameWidth; +} + +void Layer::latchAndReleaseBuffer() { + if (hasReadyFrame()) { + bool ignored = false; + latchBuffer(ignored, systemTime()); + } + releasePendingBuffer(systemTime()); +} + +PixelFormat Layer::getPixelFormat() const { + return mBufferInfo.mPixelFormat; +} + +bool Layer::getTransformToDisplayInverse() const { + return mBufferInfo.mTransformToDisplayInverse; +} + +Rect Layer::getBufferCrop() const { + // this is the crop rectangle that applies to the buffer + // itself (as opposed to the window) + if (!mBufferInfo.mCrop.isEmpty()) { + // if the buffer crop is defined, we use that + return mBufferInfo.mCrop; + } else if (mBufferInfo.mBuffer != nullptr) { + // otherwise we use the whole buffer + return mBufferInfo.mBuffer->getBounds(); + } else { + // if we don't have a buffer yet, we use an empty/invalid crop + return Rect(); + } +} + +uint32_t Layer::getBufferTransform() const { + return mBufferInfo.mTransform; +} + +ui::Dataspace Layer::getDataSpace() const { + return mDrawingState.dataspaceRequested ? getRequestedDataSpace() : ui::Dataspace::UNKNOWN; +} + +ui::Dataspace Layer::getRequestedDataSpace() const { + return hasBufferOrSidebandStream() ? mBufferInfo.mDataspace : mDrawingState.dataspace; +} + +ui::Dataspace Layer::translateDataspace(ui::Dataspace dataspace) { + ui::Dataspace updatedDataspace = dataspace; + // translate legacy dataspaces to modern dataspaces + switch (dataspace) { + case ui::Dataspace::SRGB: + updatedDataspace = ui::Dataspace::V0_SRGB; + break; + case ui::Dataspace::SRGB_LINEAR: + updatedDataspace = ui::Dataspace::V0_SRGB_LINEAR; + break; + case ui::Dataspace::JFIF: + updatedDataspace = ui::Dataspace::V0_JFIF; + break; + case ui::Dataspace::BT601_625: + updatedDataspace = ui::Dataspace::V0_BT601_625; + break; + case ui::Dataspace::BT601_525: + updatedDataspace = ui::Dataspace::V0_BT601_525; + break; + case ui::Dataspace::BT709: + updatedDataspace = ui::Dataspace::V0_BT709; + break; + default: + break; + } + + return updatedDataspace; +} + +sp Layer::getBuffer() const { + return mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getBuffer() : nullptr; +} + +void Layer::getDrawingTransformMatrix(bool filteringEnabled, float outMatrix[16]) const { + sp buffer = getBuffer(); + if (!buffer) { + ALOGE("Buffer should not be null!"); + return; + } + GLConsumer::computeTransformMatrix(outMatrix, buffer->getWidth(), buffer->getHeight(), + buffer->getPixelFormat(), mBufferInfo.mCrop, + mBufferInfo.mTransform, filteringEnabled); +} + +void Layer::setTransformHint(ui::Transform::RotationFlags displayTransformHint) { + mTransformHint = getFixedTransformHint(); + if (mTransformHint == ui::Transform::ROT_INVALID) { + mTransformHint = displayTransformHint; + } +} + +const std::shared_ptr& Layer::getExternalTexture() const { + return mBufferInfo.mBuffer; +} + +bool Layer::setColor(const half3& color) { + if (mDrawingState.color.r == color.r && mDrawingState.color.g == color.g && + mDrawingState.color.b == color.b) { + return false; + } + + mDrawingState.sequence++; + mDrawingState.color.r = color.r; + mDrawingState.color.g = color.g; + mDrawingState.color.b = color.b; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + +bool Layer::fillsColor() const { + return !hasBufferOrSidebandStream() && mDrawingState.color.r >= 0.0_hf && + mDrawingState.color.g >= 0.0_hf && mDrawingState.color.b >= 0.0_hf; +} + +bool Layer::hasBlur() const { + return getBackgroundBlurRadius() > 0 || getDrawingState().blurRegions.size() > 0; +} + // --------------------------------------------------------------------------- std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate) { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 946b7d0238..f6b9b0fd07 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -48,12 +48,10 @@ #include #include "Client.h" -#include "ClientCache.h" -#include "DisplayHardware/ComposerHal.h" #include "DisplayHardware/HWComposer.h" #include "FrameTracker.h" +#include "HwcSlotGenerator.h" #include "LayerVector.h" -#include "RenderArea.h" #include "Scheduler/LayerInfo.h" #include "SurfaceFlinger.h" #include "Tracing/LayerTracing.h" @@ -170,11 +168,9 @@ public: gui::WindowInfo inputInfo; wp touchableRegionCrop; - // dataspace is only used by BufferStateLayer and EffectLayer ui::Dataspace dataspace; bool dataspaceRequested; - // The fields below this point are only used by BufferStateLayer uint64_t frameNumber; ui::Transform transform; uint32_t bufferTransform; @@ -308,17 +304,17 @@ public: static void miniDumpHeader(std::string& result); // Provide unique string for each class type in the Layer hierarchy - virtual const char* getType() const = 0; + virtual const char* getType() const { return "Layer"; } // true if this layer is visible, false otherwise - virtual bool isVisible() const = 0; + virtual bool isVisible() const; - virtual sp createClone() = 0; + virtual sp createClone(); // Set a 2x2 transformation matrix on the layer. This transform // will be applied after parent transforms, but before any final // producer specified transform. - virtual bool setMatrix(const layer_state_t::matrix22_t& matrix) = 0; + bool setMatrix(const layer_state_t::matrix22_t& matrix); // This second set of geometry attributes are controlled by // setGeometryAppliesWithResize, and their default mode is to be @@ -328,7 +324,7 @@ public: // setPosition operates in parent buffer space (pre parent-transform) or display // space for top-level layers. - virtual bool setPosition(float x, float y) = 0; + bool setPosition(float x, float y); // Buffer space bool setCrop(const Rect& crop); @@ -339,7 +335,7 @@ public: virtual bool setRelativeLayer(const sp& relativeToHandle, int32_t relativeZ); virtual bool setAlpha(float alpha); - virtual bool setColor(const half3& /*color*/) = 0; + bool setColor(const half3& /*color*/); // Set rounded corner radius for this layer and its children. // @@ -365,28 +361,25 @@ public: virtual bool isColorSpaceAgnostic() const { return mDrawingState.colorSpaceAgnostic; } virtual bool isDimmingEnabled() const { return getDrawingState().dimmingEnabled; }; - // Used only to set BufferStateLayer state - virtual bool setTransform(uint32_t /*transform*/) = 0; - virtual bool setTransformToDisplayInverse(bool /*transformToDisplayInverse*/) = 0; - virtual bool setBuffer(std::shared_ptr& /* buffer */, - const BufferData& /* bufferData */, nsecs_t /* postTime */, - nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/, - std::optional /* dequeueTime */, - const FrameTimelineInfo& /*info*/) = 0; - virtual bool setDataspace(ui::Dataspace /*dataspace*/) = 0; - virtual bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/) = 0; - virtual bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/) = 0; - virtual bool setApi(int32_t /*api*/) = 0; - virtual bool setSidebandStream(const sp& /*sidebandStream*/) = 0; - virtual bool setTransactionCompletedListeners( - const std::vector>& /*handles*/) = 0; + bool setTransform(uint32_t /*transform*/); + bool setTransformToDisplayInverse(bool /*transformToDisplayInverse*/); + bool setBuffer(std::shared_ptr& /* buffer */, + const BufferData& /* bufferData */, nsecs_t /* postTime */, + nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/, + std::optional /* dequeueTime */, const FrameTimelineInfo& /*info*/); + bool setDataspace(ui::Dataspace /*dataspace*/); + bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/); + bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/); + bool setApi(int32_t /*api*/); + bool setSidebandStream(const sp& /*sidebandStream*/); + bool setTransactionCompletedListeners(const std::vector>& /*handles*/); virtual bool setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace); virtual bool setColorSpaceAgnostic(const bool agnostic); virtual bool setDimmingEnabled(const bool dimmingEnabled); virtual bool setDefaultFrameRateCompatibility(FrameRateCompatibility compatibility); virtual bool setFrameRateSelectionPriority(int32_t priority); virtual bool setFixedTransformHint(ui::Transform::RotationFlags fixedTransformHint); - virtual void setAutoRefresh(bool /* autoRefresh */) = 0; + void setAutoRefresh(bool /* autoRefresh */); bool setDropInputMode(gui::DropInputMode); // If the variable is not set on the layer, it traverses up the tree to inherit the frame @@ -395,16 +388,17 @@ public: // virtual FrameRateCompatibility getDefaultFrameRateCompatibility() const; // - virtual ui::Dataspace getDataSpace() const = 0; + ui::Dataspace getDataSpace() const; + ui::Dataspace getRequestedDataSpace() const; - virtual sp getCompositionEngineLayerFE() const = 0; - virtual compositionengine::LayerFECompositionState* editCompositionState() = 0; + virtual sp getCompositionEngineLayerFE() const; + compositionengine::LayerFECompositionState* editCompositionState(); // If we have received a new buffer this frame, we will pass its surface // damage down to hardware composer. Otherwise, we must send a region with // one empty rect. - virtual void useSurfaceDamage() {} - virtual void useEmptyDamage() {} + void useSurfaceDamage(); + void useEmptyDamage(); Region getVisibleRegion(const DisplayDevice*) const; /* @@ -414,18 +408,18 @@ public: * pixel format includes an alpha channel) and the "opaque" flag set * on the layer. It does not examine the current plane alpha value. */ - virtual bool isOpaque(const Layer::State&) const = 0; + bool isOpaque(const Layer::State&) const; /* * Returns whether this layer can receive input. */ - virtual bool canReceiveInput() const = 0; + bool canReceiveInput() const; /* * isProtected - true if the layer may contain protected contents in the * GRALLOC_USAGE_PROTECTED sense. */ - virtual bool isProtected() const = 0; + bool isProtected() const; /* * isFixedSize - true if content has a fixed size @@ -435,7 +429,7 @@ public: /* * usesSourceCrop - true if content should use a source crop */ - virtual bool usesSourceCrop() const = 0; + bool usesSourceCrop() const { return hasBufferOrSidebandStream(); } // Most layers aren't created from the main thread, and therefore need to // grab the SF state lock to access HWC, but ContainerLayer does, so we need @@ -443,9 +437,11 @@ public: virtual bool isCreatedFromMainThread() const { return false; } ui::Transform getActiveTransform(const Layer::State& s) const { return s.transform; } - virtual Region getActiveTransparentRegion(const Layer::State& s) const = 0; + Region getActiveTransparentRegion(const Layer::State& s) const { + return s.transparentRegionHint; + } Rect getCrop(const Layer::State& s) const { return s.crop; } - virtual bool needsFiltering(const DisplayDevice*) const = 0; + bool needsFiltering(const DisplayDevice*) const; // True if this layer requires filtering // This method is distinct from needsFiltering() in how the filter @@ -456,23 +452,25 @@ public: // different. // If the parent transform needs to be undone when capturing the layer, then // the inverse parent transform is also required. - virtual bool needsFilteringForScreenshots(const DisplayDevice*, const ui::Transform&) const = 0; + bool needsFilteringForScreenshots(const DisplayDevice*, const ui::Transform&) const; - virtual void updateCloneBufferInfo(){}; + // from graphics API + ui::Dataspace translateDataspace(ui::Dataspace dataspace); + void updateCloneBufferInfo(); + uint64_t mPreviousFrameNumber = 0; - virtual bool isHdrY410() const = 0; + bool isHdrY410() const; /* * called after composition. * returns true if the layer latched a new buffer this frame. */ - virtual void onPostComposition(const DisplayDevice*, - const std::shared_ptr& /*glDoneFence*/, - const std::shared_ptr& /*presentFence*/, - const CompositorTiming&) = 0; + void onPostComposition(const DisplayDevice*, const std::shared_ptr& /*glDoneFence*/, + const std::shared_ptr& /*presentFence*/, + const CompositorTiming&); // If a buffer was replaced this frame, release the former buffer - virtual void releasePendingBuffer(nsecs_t /*dequeueReadyTime*/) = 0; + void releasePendingBuffer(nsecs_t /*dequeueReadyTime*/); /* * latchBuffer - called each time the screen is redrawn and returns whether @@ -480,48 +478,55 @@ public: * operation, so this should be set only if needed). Typically this is used * to figure out if the content or size of a surface has changed. */ - virtual bool latchBuffer(bool& /*recomputeVisibleRegions*/, nsecs_t /*latchTime*/) = 0; + bool latchBuffer(bool& /*recomputeVisibleRegions*/, nsecs_t /*latchTime*/); - virtual void latchAndReleaseBuffer() = 0; + /* + * Calls latchBuffer if the buffer has a frame queued and then releases the buffer. + * This is used if the buffer is just latched and releases to free up the buffer + * and will not be shown on screen. + * Should only be called on the main thread. + */ + void latchAndReleaseBuffer(); /* * returns the rectangle that crops the content of the layer and scales it * to the layer's size. */ - virtual Rect getBufferCrop() const = 0; + Rect getBufferCrop() const; /* * Returns the transform applied to the buffer. */ - virtual uint32_t getBufferTransform() const = 0; + uint32_t getBufferTransform() const; - virtual sp getBuffer() const = 0; - virtual const std::shared_ptr& getExternalTexture() const = 0; + sp getBuffer() const; + const std::shared_ptr& getExternalTexture() const; - virtual ui::Transform::RotationFlags getTransformHint() const = 0; + ui::Transform::RotationFlags getTransformHint() const { return mTransformHint; } /* * Returns if a frame is ready */ - virtual bool hasReadyFrame() const = 0; + bool hasReadyFrame() const; virtual int32_t getQueuedFrameCount() const { return 0; } /** * Returns active buffer size in the correct orientation. Buffer size is determined by undoing - * any buffer transformations. If the layer has no buffer then return INVALID_RECT. + * any buffer transformations. Returns Rect::INVALID_RECT if the layer has no buffer or the + * layer does not have a display frame and its parent is not bounded. */ - virtual Rect getBufferSize(const Layer::State&) const = 0; + Rect getBufferSize(const Layer::State&) const; /** * Returns the source bounds. If the bounds are not defined, it is inferred from the * buffer size. Failing that, the bounds are determined from the passed in parent bounds. * For the root layer, this is the display viewport size. */ - virtual FloatRect computeSourceBounds(const FloatRect& parentBounds) const = 0; + FloatRect computeSourceBounds(const FloatRect& parentBounds) const; virtual FrameRate getFrameRateForLayerTree() const; - virtual bool getTransformToDisplayInverse() const = 0; + bool getTransformToDisplayInverse() const; // Returns how rounded corners should be drawn for this layer. // A layer can override its parent's rounded corner settings if the parent's rounded @@ -530,26 +535,51 @@ public: bool hasRoundedCorners() const override { return getRoundedCornerState().hasRoundedCorners(); } - virtual PixelFormat getPixelFormat() const { return PIXEL_FORMAT_NONE; } + PixelFormat getPixelFormat() const; /** - * Return whether this layer needs an input info. For most layer types - * this is only true if they explicitly set an input-info but BufferLayer - * overrides this so we can generate input-info for Buffered layers that don't - * have them (for input occlusion detection checks). + * Return whether this layer needs an input info. We generate InputWindowHandles for all + * non-cursor buffered layers regardless of whether they have an InputChannel. This is to enable + * the InputDispatcher to do PID based occlusion detection. */ - virtual bool needsInputInfo() const = 0; + bool needsInputInfo() const { + return (hasInputInfo() || hasBufferOrSidebandStream()) && !mPotentialCursor; + } // Implements RefBase. void onFirstRef() override; + struct BufferInfo { + nsecs_t mDesiredPresentTime; + std::shared_ptr mFenceTime; + sp mFence; + uint32_t mTransform{0}; + ui::Dataspace mDataspace{ui::Dataspace::UNKNOWN}; + Rect mCrop; + uint32_t mScaleMode{NATIVE_WINDOW_SCALING_MODE_FREEZE}; + Region mSurfaceDamage; + HdrMetadata mHdrMetadata; + int mApi; + PixelFormat mPixelFormat{PIXEL_FORMAT_NONE}; + bool mTransformToDisplayInverse{false}; + + std::shared_ptr mBuffer; + uint64_t mFrameNumber; + int mBufferSlot{BufferQueue::INVALID_BUFFER_SLOT}; + + bool mFrameLatencyNeeded{false}; + }; + + BufferInfo mBufferInfo; + // implements compositionengine::LayerFE - virtual const compositionengine::LayerFECompositionState* getCompositionState() const = 0; - virtual bool onPreComposition(nsecs_t) = 0; + const compositionengine::LayerFECompositionState* getCompositionState() const; + bool fenceHasSignaled() const; + bool onPreComposition(nsecs_t); void prepareCompositionState(compositionengine::LayerFE::StateSubset subset) override; std::optional prepareClientComposition( compositionengine::LayerFE::ClientCompositionTargetSettings&) const override; - virtual void onLayerDisplayed(ftl::SharedFuture) = 0; + void onLayerDisplayed(ftl::SharedFuture); void setWasClientComposed(const sp& fence) override { mLastClientCompositionFence = fence; @@ -827,13 +857,17 @@ public: float getBorderWidth(); const half4& getBorderColor(); - virtual bool setBufferCrop(const Rect& /* bufferCrop */) = 0; - virtual bool setDestinationFrame(const Rect& /* destinationFrame */) = 0; - virtual std::atomic* getPendingBufferCounter() = 0; - virtual std::string getPendingBufferCounterName() = 0; - virtual bool updateGeometry() = 0; + bool setBufferCrop(const Rect& /* bufferCrop */); + bool setDestinationFrame(const Rect& /* destinationFrame */); + // See mPendingBufferTransactions + void decrementPendingBufferCount(); + std::atomic* getPendingBufferCounter() { return &mPendingBufferTransactions; } + std::string getPendingBufferCounterName() { return mBlastTransactionName; } + bool updateGeometry(); + + bool simpleBufferUpdate(const layer_state_t&) const; - virtual bool simpleBufferUpdate(const layer_state_t&) const = 0; + static bool isOpaqueFormat(PixelFormat format); protected: friend class impl::SurfaceInterceptor; @@ -847,9 +881,12 @@ protected: friend class TransactionSurfaceFrameTest; virtual void setInitialValuesForClone(const sp& clonedFrom); - virtual void preparePerFrameCompositionState(); + void preparePerFrameCompositionState(); + void preparePerFrameBufferCompositionState(); + void preparePerFrameEffectsCompositionState(); virtual void commitTransaction(State& stateToCommit); - virtual void onSurfaceFrameCreated(const std::shared_ptr&) {} + void gatherBufferInfo(); + void onSurfaceFrameCreated(const std::shared_ptr&); sp asLayerFE() const; sp getClonedFrom() { return mClonedFrom != nullptr ? mClonedFrom.promote() : nullptr; } @@ -888,7 +925,7 @@ protected: compositionengine::OutputLayer* findOutputLayerForDisplay(const DisplayDevice*) const; bool usingRelativeZ(LayerVector::StateSet) const; - virtual ui::Transform getInputTransform() const = 0; + virtual ui::Transform getInputTransform() const; /** * Get the bounds in layer space within which this layer can receive input. * @@ -902,7 +939,7 @@ protected: * "replaceTouchableRegionWithCrop" is specified. In this case, the layer will receive input * in this layer's space, regardless of the specified crop layer. */ - virtual Rect getInputBounds() const = 0; + Rect getInputBounds() const; // constant sp mFlinger; @@ -973,7 +1010,14 @@ protected: sp mLastClientCompositionFence; bool mClearClientCompositionFenceOnLayerDisplayed = false; private: - virtual void setTransformHint(ui::Transform::RotationFlags) = 0; + friend class SlotGenerationTest; + friend class TransactionFrameTracerTest; + friend class TransactionSurfaceFrameTest; + + bool getAutoRefresh() const { return mDrawingState.autoRefresh; } + bool getSidebandStreamChanged() const { return mSidebandStreamChanged; } + + std::atomic mSidebandStreamChanged{false}; // Returns true if the layer can draw shadows on its border. virtual bool canDrawShadows() const { return true; } @@ -1018,6 +1062,52 @@ private: // Fills in the frame and transform info for the gui::WindowInfo. void fillInputFrameInfo(gui::WindowInfo&, const ui::Transform& screenToDisplay); + // Computes the transform matrix using the setFilteringEnabled to determine whether the + // transform matrix should be computed for use with bilinear filtering. + void getDrawingTransformMatrix(bool filteringEnabled, float outMatrix[16]) const; + + inline void tracePendingBufferCount(int32_t pendingBuffers); + + // Latch sideband stream and returns true if the dirty region should be updated. + bool latchSidebandStream(bool& recomputeVisibleRegions); + + bool hasFrameUpdate() const; + + void updateTexImage(nsecs_t latchTime); + + // Crop that applies to the buffer + Rect computeBufferCrop(const State& s); + + bool willPresentCurrentTransaction() const; + + // Returns true if the transformed buffer size does not match the layer size and we need + // to apply filtering. + bool bufferNeedsFiltering() const; + + void callReleaseBufferCallback(const sp& listener, + const sp& buffer, uint64_t framenumber, + const sp& releaseFence, + uint32_t currentMaxAcquiredBufferCount); + + std::optional prepareClientCompositionInternal( + compositionengine::LayerFE::ClientCompositionTargetSettings&) const; + // Returns true if there is a valid color to fill. + bool fillsColor() const; + // Returns true if this layer has a blur value. + bool hasBlur() const; + bool hasEffect() const { return fillsColor() || drawShadows() || hasBlur(); } + bool hasBufferOrSidebandStream() const { + return ((mSidebandStream != nullptr) || (mBufferInfo.mBuffer != nullptr)); + } + + bool hasSomethingToDraw() const { return hasEffect() || hasBufferOrSidebandStream(); } + void prepareBufferStateClientComposition( + compositionengine::LayerFE::LayerSettings&, + compositionengine::LayerFE::ClientCompositionTargetSettings&) const; + void prepareEffectsClientComposition( + compositionengine::LayerFE::LayerSettings&, + compositionengine::LayerFE::ClientCompositionTargetSettings&) const; + // Cached properties computed from drawing state // Effective transform taking into account parent transforms and any parent scaling, which is // a transform from the current layer coordinate space to display(screen) coordinate space. @@ -1067,6 +1157,49 @@ private: bool mBorderEnabled = false; float mBorderWidth; half4 mBorderColor; + + void setTransformHint(ui::Transform::RotationFlags); + + const uint32_t mTextureName; + + // Transform hint provided to the producer. This must be accessed holding + // the mStateLock. + ui::Transform::RotationFlags mTransformHint = ui::Transform::ROT_0; + + std::unique_ptr mCompositionState; + + ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID; + uint64_t mPreviousReleasedFrameNumber = 0; + + uint64_t mPreviousBarrierFrameNumber = 0; + + bool mReleasePreviousBuffer = false; + + // Stores the last set acquire fence signal time used to populate the callback handle's acquire + // time. + std::variant> mCallbackHandleAcquireTimeOrFence = -1; + + std::deque> mPendingJankClassifications; + // An upper bound on the number of SurfaceFrames in the pending classifications deque. + static constexpr int kPendingClassificationMaxSurfaceFrames = 25; + + const std::string mBlastTransactionName{"BufferTX - " + mName}; + // This integer is incremented everytime a buffer arrives at the server for this layer, + // and decremented when a buffer is dropped or latched. When changed the integer is exported + // to systrace with ATRACE_INT and mBlastTransactionName. This way when debugging perf it is + // possible to see when a buffer arrived at the server, and in which frame it latched. + // + // You can understand the trace this way: + // - If the integer increases, a buffer arrived at the server. + // - If the integer decreases in latchBuffer, that buffer was latched + // - If the integer decreases in setBuffer or doTransaction, a buffer was dropped + std::atomic mPendingBufferTransactions{0}; + + // Contains requested position and matrix updates. This will be applied if the client does + // not specify a destination frame. + ui::Transform mRequestedTransform; + + sp mHwcSlotGenerator; }; std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate); diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp index 5c96f35ae7..15a791e4d6 100644 --- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp +++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp @@ -34,6 +34,7 @@ #include "SurfaceInterceptor.h" #include "DisplayHardware/ComposerHal.h" +#include "FrameTimeline/FrameTimeline.h" #include "Scheduler/Scheduler.h" #include "Scheduler/VsyncConfiguration.h" #include "Scheduler/VsyncController.h" diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h index 2c62b286ef..1a49ead275 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h @@ -24,8 +24,8 @@ #include -#include "BufferStateLayer.h" #include "Clock.h" +#include "Layer.h" #include "Scheduler/EventThread.h" #include "Scheduler/RefreshRateConfigs.h" #include "Scheduler/Scheduler.h" @@ -66,10 +66,10 @@ private: std::chrono::steady_clock::time_point mNow; }; -class FuzzImplLayer : public BufferStateLayer { +class FuzzImplLayer : public Layer { public: FuzzImplLayer(SurfaceFlinger* flinger, std::string name) - : BufferStateLayer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) {} + : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) {} explicit FuzzImplLayer(SurfaceFlinger* flinger) : FuzzImplLayer(flinger, "FuzzLayer") {} const char* getType() const override { return ""; } -- GitLab From 4113fc1776406b3e55e855ebccc029381b92ddf9 Mon Sep 17 00:00:00 2001 From: Kriti Dang Date: Fri, 26 Aug 2022 16:30:37 +0200 Subject: [PATCH 0259/1310] Add clearFrameRate API in ndk This API is same as using setFrameRate with 0 as frame-rate value. But this causes confusion, as the remaining two arguments are ignored, when frame-rate is 0. Bug: 241096917 Test: android.graphics.cts.SetFrameRateTest#testClearFrameRate Change-Id: I469e41cc1a15abf4c0b402caca12a9f6db4cdb71 --- include/android/surface_control.h | 27 +++++++++++++++ libs/nativewindow/ANativeWindow.cpp | 9 +++++ .../include/android/native_window.h | 33 +++++++++++++++++++ libs/nativewindow/libnativewindow.map.txt | 1 + 4 files changed, 70 insertions(+) diff --git a/include/android/surface_control.h b/include/android/surface_control.h index 9a36ecb537..9842e7e534 100644 --- a/include/android/surface_control.h +++ b/include/android/surface_control.h @@ -545,6 +545,8 @@ void ASurfaceTransaction_setFrameRate(ASurfaceTransaction* transaction, * You can register for changes in the refresh rate using * \a AChoreographer_registerRefreshRateCallback. * + * See ASurfaceTransaction_clearFrameRate(). + * * \param frameRate is the intended frame rate of this surface, in frames per second. 0 is a special * value that indicates the app will accept the system's choice for the display frame rate, which is * the default behavior if this function isn't called. The frameRate param does not need to @@ -567,6 +569,31 @@ void ASurfaceTransaction_setFrameRateWithChangeStrategy(ASurfaceTransaction* tra int8_t compatibility, int8_t changeFrameRateStrategy) __INTRODUCED_IN(31); +/** + * Clears the frame rate which is set for \a surface_control. + * + * This is equivalent to calling + * ASurfaceTransaction_setFrameRateWithChangeStrategy( + * transaction, 0, compatibility, changeFrameRateStrategy). + * + * Usage of this API won't directly affect the application's frame production pipeline. However, + * because the system may change the display refresh rate, calls to this function may result in + * changes to Choreographer callback timings, and changes to the time interval at which the system + * releases buffers back to the application. + * + * See ASurfaceTransaction_setFrameRateWithChangeStrategy() + * + * You can register for changes in the refresh rate using + * \a AChoreographer_registerRefreshRateCallback. + * + * See ASurfaceTransaction_setFrameRateWithChangeStrategy(). + * + * Available since API level 34. + */ +void ASurfaceTransaction_clearFrameRate(ASurfaceTransaction* transaction, + ASurfaceControl* surface_control) + __INTRODUCED_IN(__ANDROID_API_U__); + /** * Indicate whether to enable backpressure for buffer submission to a given SurfaceControl. * diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp index 731f989658..b0750809f8 100644 --- a/libs/nativewindow/ANativeWindow.cpp +++ b/libs/nativewindow/ANativeWindow.cpp @@ -220,6 +220,15 @@ int32_t ANativeWindow_setFrameRateWithChangeStrategy(ANativeWindow* window, floa return native_window_set_frame_rate(window, frameRate, compatibility, changeFrameRateStrategy); } +int32_t ANativeWindow_clearFrameRate(ANativeWindow* window) { + if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) { + return -EINVAL; + } + return native_window_set_frame_rate(window, 0, + ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, + ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS); +} + /************************************************************************************************** * vndk-stable **************************************************************************************************/ diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h index 20f4b52caa..281ec52528 100644 --- a/libs/nativewindow/include/android/native_window.h +++ b/libs/nativewindow/include/android/native_window.h @@ -313,6 +313,8 @@ enum ANativeWindow_ChangeFrameRateStrategy { * You can register for changes in the refresh rate using * \a AChoreographer_registerRefreshRateCallback. * + * See ANativeWindow_clearFrameRate(). + * * Available since API level 31. * * \param window pointer to an ANativeWindow object. @@ -342,6 +344,37 @@ int32_t ANativeWindow_setFrameRateWithChangeStrategy(ANativeWindow* window, floa int8_t compatibility, int8_t changeFrameRateStrategy) __INTRODUCED_IN(31); +/** + * Clears the frame rate which is set for this window. + * + * This is equivalent to calling + * ANativeWindow_setFrameRateWithChangeStrategy(window, 0, compatibility, changeFrameRateStrategy). + * + * Usage of this API won't introduce frame rate throttling, + * or affect other aspects of the application's frame production + * pipeline. However, because the system may change the display refresh rate, + * calls to this function may result in changes to Choreographer callback + * timings, and changes to the time interval at which the system releases + * buffers back to the application. + * + * Note that this only has an effect for windows presented on the display. If + * this ANativeWindow is consumed by something other than the system compositor, + * e.g. a media codec, this call has no effect. + * + * You can register for changes in the refresh rate using + * \a AChoreographer_registerRefreshRateCallback. + * + * See ANativeWindow_setFrameRateWithChangeStrategy(). + * + * Available since API level 34. + * + * \param window pointer to an ANativeWindow object. + * + * \return 0 for success, -EINVAL if the window value is invalid. + */ +int32_t ANativeWindow_clearFrameRate(ANativeWindow* window) + __INTRODUCED_IN(__ANDROID_API_U__); + #ifdef __cplusplus }; #endif diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt index 6296f9ac59..6c6e42b136 100644 --- a/libs/nativewindow/libnativewindow.map.txt +++ b/libs/nativewindow/libnativewindow.map.txt @@ -49,6 +49,7 @@ LIBNATIVEWINDOW { ANativeWindow_setDequeueTimeout; # systemapi # introduced=30 ANativeWindow_setFrameRate; # introduced=30 ANativeWindow_setFrameRateWithChangeStrategy; # introduced=31 + ANativeWindow_clearFrameRate; # introduced=UpsideDownCake ANativeWindow_setSharedBufferMode; # llndk ANativeWindow_setSwapInterval; # llndk ANativeWindow_setUsage; # llndk -- GitLab From 22e33d68fd9445f6f36c3c65b362e8285eec13b6 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Fri, 2 Sep 2022 19:18:16 +0000 Subject: [PATCH 0260/1310] SF: Remove unnecessary mSidebandStream check Bug: 238781169 Test: go/wm-smoke Change-Id: I7c4860af0a0c0e4d4d2cd028e0f5937aabc69db6 --- services/surfaceflinger/Layer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index b6927202f2..108047eb70 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -806,7 +806,7 @@ void Layer::prepareEffectsClientComposition( void Layer::prepareBufferStateClientComposition( compositionengine::LayerFE::LayerSettings& layerSettings, compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { - if (CC_UNLIKELY(mBufferInfo.mBuffer == 0) && mSidebandStream != nullptr) { + if (CC_UNLIKELY(!mBufferInfo.mBuffer)) { // For surfaceview of tv sideband, there is no activeBuffer // in bufferqueue, we need return LayerSettings. return; -- GitLab From 303990190e5324f9e4a172e0b71ae13fb8a6e604 Mon Sep 17 00:00:00 2001 From: Sanjana Sunil Date: Mon, 28 Mar 2022 19:15:54 +0000 Subject: [PATCH 0261/1310] Bind mount misc storage to mirror for different volumes A mirror for misc_ce and misc_de storage is created for each new volume UUID and a bind mount occurs from /mnt/expand//misc_{ce,de} to /data_mirror/misc_{ce,de}/. This is similar to how apps use data_mirror. In addition, when a private volume is unmounted, the mirror for that volume UUID is deleted. Bug: 214241165 Test: atest SdkSandboxStorageHostTest Ignore-AOSP-First: Will cherry pick once other CLs are merged in as well Change-Id: I3ea99145e38ed3781a701c1305720f2d4dbe54ce --- cmds/installd/InstalldNativeService.cpp | 64 ++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp index 2c8adc7126..a278a48135 100644 --- a/cmds/installd/InstalldNativeService.cpp +++ b/cmds/installd/InstalldNativeService.cpp @@ -99,6 +99,8 @@ static constexpr const char* kXattrDefault = "user.default"; static constexpr const char* kDataMirrorCePath = "/data_mirror/data_ce"; static constexpr const char* kDataMirrorDePath = "/data_mirror/data_de"; +static constexpr const char* kMiscMirrorCePath = "/data_mirror/misc_ce"; +static constexpr const char* kMiscMirrorDePath = "/data_mirror/misc_de"; static constexpr const int MIN_RESTRICTED_HOME_SDK_VERSION = 24; // > M @@ -3579,16 +3581,28 @@ binder::Status InstalldNativeService::tryMountDataMirror( std::string mirrorVolCePath(StringPrintf("%s/%s", kDataMirrorCePath, uuid_)); if (fs_prepare_dir(mirrorVolCePath.c_str(), 0711, AID_SYSTEM, AID_SYSTEM) != 0) { - return error("Failed to create CE mirror"); + return error("Failed to create CE data mirror"); } std::string mirrorVolDePath(StringPrintf("%s/%s", kDataMirrorDePath, uuid_)); if (fs_prepare_dir(mirrorVolDePath.c_str(), 0711, AID_SYSTEM, AID_SYSTEM) != 0) { - return error("Failed to create DE mirror"); + return error("Failed to create DE data mirror"); + } + + std::string mirrorVolMiscCePath(StringPrintf("%s/%s", kMiscMirrorCePath, uuid_)); + if (fs_prepare_dir(mirrorVolMiscCePath.c_str(), 0711, AID_SYSTEM, AID_SYSTEM) != 0) { + return error("Failed to create CE misc mirror"); + } + + std::string mirrorVolMiscDePath(StringPrintf("%s/%s", kMiscMirrorDePath, uuid_)); + if (fs_prepare_dir(mirrorVolMiscDePath.c_str(), 0711, AID_SYSTEM, AID_SYSTEM) != 0) { + return error("Failed to create DE misc mirror"); } auto cePath = StringPrintf("%s/user", create_data_path(uuid_).c_str()); auto dePath = StringPrintf("%s/user_de", create_data_path(uuid_).c_str()); + auto miscCePath = StringPrintf("%s/misc_ce", create_data_path(uuid_).c_str()); + auto miscDePath = StringPrintf("%s/misc_de", create_data_path(uuid_).c_str()); if (access(cePath.c_str(), F_OK) != 0) { return error("Cannot access CE path: " + cePath); @@ -3596,6 +3610,12 @@ binder::Status InstalldNativeService::tryMountDataMirror( if (access(dePath.c_str(), F_OK) != 0) { return error("Cannot access DE path: " + dePath); } + if (access(miscCePath.c_str(), F_OK) != 0) { + return error("Cannot access misc CE path: " + cePath); + } + if (access(miscDePath.c_str(), F_OK) != 0) { + return error("Cannot access misc DE path: " + dePath); + } struct stat ceStat, mirrorCeStat; if (stat(cePath.c_str(), &ceStat) != 0) { @@ -3623,6 +3643,21 @@ binder::Status InstalldNativeService::tryMountDataMirror( MS_NOSUID | MS_NODEV | MS_NOATIME | MS_BIND | MS_NOEXEC, nullptr)) == -1) { return error("Failed to mount " + mirrorVolDePath); } + + // Mount misc CE mirror + if (TEMP_FAILURE_RETRY(mount(miscCePath.c_str(), mirrorVolMiscCePath.c_str(), NULL, + MS_NOSUID | MS_NODEV | MS_NOATIME | MS_BIND | MS_NOEXEC, + nullptr)) == -1) { + return error("Failed to mount " + mirrorVolMiscCePath); + } + + // Mount misc DE mirror + if (TEMP_FAILURE_RETRY(mount(miscDePath.c_str(), mirrorVolMiscDePath.c_str(), NULL, + MS_NOSUID | MS_NODEV | MS_NOATIME | MS_BIND | MS_NOEXEC, + nullptr)) == -1) { + return error("Failed to mount " + mirrorVolMiscDePath); + } + return ok(); } @@ -3645,6 +3680,8 @@ binder::Status InstalldNativeService::onPrivateVolumeRemoved( std::string mirrorCeVolPath(StringPrintf("%s/%s", kDataMirrorCePath, uuid_)); std::string mirrorDeVolPath(StringPrintf("%s/%s", kDataMirrorDePath, uuid_)); + std::string mirrorMiscCeVolPath(StringPrintf("%s/%s", kMiscMirrorCePath, uuid_)); + std::string mirrorMiscDeVolPath(StringPrintf("%s/%s", kMiscMirrorDePath, uuid_)); std::lock_guard lock(mMountsLock); @@ -3669,6 +3706,29 @@ binder::Status InstalldNativeService::onPrivateVolumeRemoved( if (delete_dir_contents_and_dir(mirrorDeVolPath, true) != 0) { res = error("Failed to delete " + mirrorDeVolPath); } + + // Unmount misc CE storage + if (TEMP_FAILURE_RETRY(umount(mirrorMiscCeVolPath.c_str())) != 0) { + if (errno != ENOENT) { + res = error(StringPrintf("Failed to umount %s %s", mirrorMiscCeVolPath.c_str(), + strerror(errno))); + } + } + if (delete_dir_contents_and_dir(mirrorMiscCeVolPath, true) != 0) { + res = error("Failed to delete " + mirrorMiscCeVolPath); + } + + // Unmount misc DE storage + if (TEMP_FAILURE_RETRY(umount(mirrorMiscDeVolPath.c_str())) != 0) { + if (errno != ENOENT) { + res = error(StringPrintf("Failed to umount %s %s", mirrorMiscDeVolPath.c_str(), + strerror(errno))); + } + } + if (delete_dir_contents_and_dir(mirrorMiscDeVolPath, true) != 0) { + res = error("Failed to delete " + mirrorMiscDeVolPath); + } + return res; } -- GitLab From f9f1a0247a54d6ae55ac1dcdc0a4e96aad9ac9f2 Mon Sep 17 00:00:00 2001 From: Andy Chen Date: Mon, 29 Aug 2022 20:07:10 -0400 Subject: [PATCH 0262/1310] Avoid UI freezing when reading battery capacity/status Reading battery capacity and status could fail and end up with timeout after 5s for some input devices, for example: 8BitDo SN30 Pro+ gamepad. Before reading those items, it would be better to release `mLock` so that other threads could run. Bug: 244088945 Test: Manually verified no UI freezing when calling `inputDevice.batteryState.capacity` or `inputDevice.batteryState.status` Change-Id: Ibb6b8c999b7fd4fe7fbcc57264a1fe6ad74903dc --- services/inputflinger/reader/EventHub.cpp | 54 +++++++++++++------ services/inputflinger/reader/InputDevice.cpp | 12 ++--- services/inputflinger/reader/InputReader.cpp | 44 ++++++++++----- .../controller/PeripheralController.cpp | 4 ++ .../reader/controller/PeripheralController.h | 2 + .../PeripheralControllerInterface.h | 2 + .../inputflinger/reader/include/InputDevice.h | 5 +- .../inputflinger/tests/InputReader_test.cpp | 7 ++- 8 files changed, 89 insertions(+), 41 deletions(-) diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index 06a38c8f0f..2f594d649b 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -1516,25 +1516,35 @@ EventHub::Device* EventHub::getDeviceByFdLocked(int fd) const { } std::optional EventHub::getBatteryCapacity(int32_t deviceId, int32_t batteryId) const { - std::scoped_lock _l(mLock); + std::filesystem::path batteryPath; + { + // Do not read the sysfs node to get the battery state while holding + // the EventHub lock. For some peripheral devices, reading battery state + // can be broken and take 5+ seconds. Holding the lock in this case would + // block all other event processing during this time. For now, we assume this + // call never happens on the InputReader thread and read the sysfs node outside + // the lock to prevent event processing from being blocked by this call. + std::scoped_lock _l(mLock); + + const auto infos = getBatteryInfoLocked(deviceId); + auto it = infos.find(batteryId); + if (it == infos.end()) { + return std::nullopt; + } + batteryPath = it->second.path; + } // release lock - const auto infos = getBatteryInfoLocked(deviceId); - auto it = infos.find(batteryId); - if (it == infos.end()) { - return std::nullopt; - } std::string buffer; // Some devices report battery capacity as an integer through the "capacity" file - if (base::ReadFileToString(it->second.path / BATTERY_NODES.at(InputBatteryClass::CAPACITY), + if (base::ReadFileToString(batteryPath / BATTERY_NODES.at(InputBatteryClass::CAPACITY), &buffer)) { return std::stoi(base::Trim(buffer)); } // Other devices report capacity as an enum value POWER_SUPPLY_CAPACITY_LEVEL_XXX // These values are taken from kernel source code include/linux/power_supply.h - if (base::ReadFileToString(it->second.path / - BATTERY_NODES.at(InputBatteryClass::CAPACITY_LEVEL), + if (base::ReadFileToString(batteryPath / BATTERY_NODES.at(InputBatteryClass::CAPACITY_LEVEL), &buffer)) { // Remove any white space such as trailing new line const auto levelIt = BATTERY_LEVEL.find(base::Trim(buffer)); @@ -1547,15 +1557,27 @@ std::optional EventHub::getBatteryCapacity(int32_t deviceId, int32_t ba } std::optional EventHub::getBatteryStatus(int32_t deviceId, int32_t batteryId) const { - std::scoped_lock _l(mLock); - const auto infos = getBatteryInfoLocked(deviceId); - auto it = infos.find(batteryId); - if (it == infos.end()) { - return std::nullopt; - } + std::filesystem::path batteryPath; + { + // Do not read the sysfs node to get the battery state while holding + // the EventHub lock. For some peripheral devices, reading battery state + // can be broken and take 5+ seconds. Holding the lock in this case would + // block all other event processing during this time. For now, we assume this + // call never happens on the InputReader thread and read the sysfs node outside + // the lock to prevent event processing from being blocked by this call. + std::scoped_lock _l(mLock); + + const auto infos = getBatteryInfoLocked(deviceId); + auto it = infos.find(batteryId); + if (it == infos.end()) { + return std::nullopt; + } + batteryPath = it->second.path; + } // release lock + std::string buffer; - if (!base::ReadFileToString(it->second.path / BATTERY_NODES.at(InputBatteryClass::STATUS), + if (!base::ReadFileToString(batteryPath / BATTERY_NODES.at(InputBatteryClass::STATUS), &buffer)) { ALOGE("Failed to read sysfs battery info: %s", strerror(errno)); return std::nullopt; diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 5c9e63e80e..4ab55cba87 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -548,14 +548,6 @@ void InputDevice::cancelTouch(nsecs_t when, nsecs_t readTime) { for_each_mapper([when, readTime](InputMapper& mapper) { mapper.cancelTouch(when, readTime); }); } -std::optional InputDevice::getBatteryCapacity() { - return mController ? mController->getBatteryCapacity(DEFAULT_BATTERY_ID) : std::nullopt; -} - -std::optional InputDevice::getBatteryStatus() { - return mController ? mController->getBatteryStatus(DEFAULT_BATTERY_ID) : std::nullopt; -} - bool InputDevice::setLightColor(int32_t lightId, int32_t color) { return mController ? mController->setLightColor(lightId, color) : false; } @@ -623,6 +615,10 @@ void InputDevice::updateLedState(bool reset) { for_each_mapper([reset](InputMapper& mapper) { mapper.updateLedState(reset); }); } +std::optional InputDevice::getBatteryEventHubId() const { + return mController ? std::make_optional(mController->getEventHubId()) : std::nullopt; +} + InputDeviceContext::InputDeviceContext(InputDevice& device, int32_t eventHubId) : mDevice(device), mContext(device.getContext()), diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index dc410513e1..4750d90fd6 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -706,23 +706,43 @@ void InputReader::flushSensor(int32_t deviceId, InputDeviceSensorType sensorType } std::optional InputReader::getBatteryCapacity(int32_t deviceId) { - std::scoped_lock _l(mLock); + std::optional eventHubId; + { + // Do not query the battery state while holding the lock. For some peripheral devices, + // reading battery state can be broken and take 5+ seconds. Holding the lock in this case + // would block all other event processing during this time. For now, we assume this + // call never happens on the InputReader thread and get the battery state outside the + // lock to prevent event processing from being blocked by this call. + std::scoped_lock _l(mLock); + InputDevice* device = findInputDeviceLocked(deviceId); + if (!device) return {}; + eventHubId = device->getBatteryEventHubId(); + } // release lock - InputDevice* device = findInputDeviceLocked(deviceId); - if (device) { - return device->getBatteryCapacity(); - } - return std::nullopt; + if (!eventHubId) return {}; + const auto batteryIds = mEventHub->getRawBatteryIds(*eventHubId); + if (batteryIds.empty()) return {}; + return mEventHub->getBatteryCapacity(*eventHubId, batteryIds.front()); } std::optional InputReader::getBatteryStatus(int32_t deviceId) { - std::scoped_lock _l(mLock); + std::optional eventHubId; + { + // Do not query the battery state while holding the lock. For some peripheral devices, + // reading battery state can be broken and take 5+ seconds. Holding the lock in this case + // would block all other event processing during this time. For now, we assume this + // call never happens on the InputReader thread and get the battery state outside the + // lock to prevent event processing from being blocked by this call. + std::scoped_lock _l(mLock); + InputDevice* device = findInputDeviceLocked(deviceId); + if (!device) return {}; + eventHubId = device->getBatteryEventHubId(); + } // release lock - InputDevice* device = findInputDeviceLocked(deviceId); - if (device) { - return device->getBatteryStatus(); - } - return std::nullopt; + if (!eventHubId) return {}; + const auto batteryIds = mEventHub->getRawBatteryIds(*eventHubId); + if (batteryIds.empty()) return {}; + return mEventHub->getBatteryStatus(*eventHubId, batteryIds.front()); } std::vector InputReader::getLights(int32_t deviceId) { diff --git a/services/inputflinger/reader/controller/PeripheralController.cpp b/services/inputflinger/reader/controller/PeripheralController.cpp index 76731749f1..eaf5b51e9f 100644 --- a/services/inputflinger/reader/controller/PeripheralController.cpp +++ b/services/inputflinger/reader/controller/PeripheralController.cpp @@ -521,4 +521,8 @@ std::optional PeripheralController::getLightPlayerId(int32_t lightId) { return light->getLightPlayerId(); } +int32_t PeripheralController::getEventHubId() const { + return getDeviceContext().getEventHubId(); +} + } // namespace android diff --git a/services/inputflinger/reader/controller/PeripheralController.h b/services/inputflinger/reader/controller/PeripheralController.h index b1bc8c732c..ac951ebe2a 100644 --- a/services/inputflinger/reader/controller/PeripheralController.h +++ b/services/inputflinger/reader/controller/PeripheralController.h @@ -31,6 +31,7 @@ public: explicit PeripheralController(InputDeviceContext& deviceContext); ~PeripheralController() override; + int32_t getEventHubId() const override; void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; void dump(std::string& dump) override; bool setLightColor(int32_t lightId, int32_t color) override; @@ -43,6 +44,7 @@ public: private: inline int32_t getDeviceId() { return mDeviceContext.getId(); } inline InputDeviceContext& getDeviceContext() { return mDeviceContext; } + inline InputDeviceContext& getDeviceContext() const { return mDeviceContext; } InputDeviceContext& mDeviceContext; void configureLights(); diff --git a/services/inputflinger/reader/controller/PeripheralControllerInterface.h b/services/inputflinger/reader/controller/PeripheralControllerInterface.h index 7688a431d1..306e36119b 100644 --- a/services/inputflinger/reader/controller/PeripheralControllerInterface.h +++ b/services/inputflinger/reader/controller/PeripheralControllerInterface.h @@ -33,6 +33,8 @@ public: PeripheralControllerInterface() {} virtual ~PeripheralControllerInterface() {} + virtual int32_t getEventHubId() const = 0; + // Interface methods for Battery virtual std::optional getBatteryCapacity(int32_t batteryId) = 0; virtual std::optional getBatteryStatus(int32_t batteryId) = 0; diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 51872ac095..968f53ae38 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -32,8 +32,6 @@ #include "InputReaderContext.h" namespace android { -// TODO b/180733860 support multiple battery in API and remove this. -constexpr int32_t DEFAULT_BATTERY_ID = 1; class PeripheralController; class PeripheralControllerInterface; @@ -100,8 +98,7 @@ public: void disableSensor(InputDeviceSensorType sensorType); void flushSensor(InputDeviceSensorType sensorType); - std::optional getBatteryCapacity(); - std::optional getBatteryStatus(); + std::optional getBatteryEventHubId() const; bool setLightColor(int32_t lightId, int32_t color); bool setLightPlayerId(int32_t lightId, int32_t playerId); diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 9a0c565de3..dadaa67891 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -991,7 +991,9 @@ private: return BATTERY_STATUS; } - std::vector getRawBatteryIds(int32_t deviceId) const override { return {}; } + std::vector getRawBatteryIds(int32_t deviceId) const override { + return {DEFAULT_BATTERY}; + } std::optional getRawBatteryInfo(int32_t deviceId, int32_t batteryId) const override { @@ -2141,6 +2143,8 @@ public: ~FakePeripheralController() override {} + int32_t getEventHubId() const { return getDeviceContext().getEventHubId(); } + void populateDeviceInfo(InputDeviceInfo* deviceInfo) override {} void dump(std::string& dump) override {} @@ -2174,6 +2178,7 @@ private: InputDeviceContext& mDeviceContext; inline int32_t getDeviceId() { return mDeviceContext.getId(); } inline InputDeviceContext& getDeviceContext() { return mDeviceContext; } + inline InputDeviceContext& getDeviceContext() const { return mDeviceContext; } }; TEST_F(InputReaderTest, BatteryGetCapacity) { -- GitLab From 86e11906597167694ce1919c4c25d747f793112d Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 6 Sep 2022 16:03:27 +0000 Subject: [PATCH 0263/1310] SurfaceFlinger: Fix layer creation race While we create a new layer, the main thread may wake up and set the layer parent which means we will start traversing the layer parents in the binder thread. Fix this by updating the transform hint which checks the parents before pass the layer creation request to the main thread. Change-Id: Icb87fb4c33bb3a110169d885398f628df319de88 Test: presubmit Fixes: 243122612 --- services/surfaceflinger/SurfaceFlinger.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index c4165e779c..cc93db3187 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3572,15 +3572,16 @@ status_t SurfaceFlinger::addClientLayer(const sp& client, const spupdateTransformHint(mActiveDisplayTransformHint); + if (outTransformHint) { + *outTransformHint = mActiveDisplayTransformHint; + } + { std::scoped_lock lock(mCreatedLayersLock); mCreatedLayers.emplace_back(layer, parent, addToRoot); } - layer->updateTransformHint(mActiveDisplayTransformHint); - if (outTransformHint) { - *outTransformHint = mActiveDisplayTransformHint; - } // attach this layer to the client if (client != nullptr) { client->attachLayer(handle, layer); -- GitLab From f7204114bfc07b973295453bad2461f58a9f7ccb Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 6 Sep 2022 19:11:26 +0000 Subject: [PATCH 0264/1310] SurfaceComposerClient: Use the default apply token when uncaching buffers When allowing callers to set the default apply token, we missed changing over one caller to use the process default. Change-Id: I39a9ffb58f5a16b28f4f59137ca00adcec5681a6 Test: presubmit Bug: 242193885 --- libs/gui/SurfaceComposerClient.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 3671a155f8..751721e2a8 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -915,9 +915,8 @@ void SurfaceComposerClient::doUncacheBufferTransaction(uint64_t cacheId) { uncacheBuffer.token = BufferCache::getInstance().getToken(); uncacheBuffer.id = cacheId; - sp applyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); - sf->setTransactionState(FrameTimelineInfo{}, {}, {}, 0, applyToken, {}, systemTime(), true, - uncacheBuffer, false, {}, generateId()); + sf->setTransactionState(FrameTimelineInfo{}, {}, {}, 0, Transaction::getDefaultApplyToken(), {}, + systemTime(), true, uncacheBuffer, false, {}, generateId()); } void SurfaceComposerClient::Transaction::cacheBuffers() { -- GitLab From f115db8c252ba3e7ac257d9f7ba70e03abc058b2 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 6 Sep 2022 16:59:33 -0700 Subject: [PATCH 0265/1310] SurfaceFlinger: Don't handle effects if a buffer is available Currently we do not support drawing effects and buffers in the same layer. Provide priority to drawing the buffer and then try and handle both as a follow up. Test: can add widgets to launcher Test: go/wm-smoke Bug: 245052266 Change-Id: I4399e954b6116fd8e5ee618c20fb3c604b657e04 --- services/surfaceflinger/Layer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 108047eb70..47bd91e195 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -769,7 +769,7 @@ std::optional Layer::prepareClientCom // Record the name of the layer for debugging further down the stack. layerSettings.name = getName(); - if (hasEffect()) { + if (hasEffect() && !hasBufferOrSidebandStream()) { prepareEffectsClientComposition(layerSettings, targetSettings); return layerSettings; } -- GitLab From 011953c712b995da30358730598cb62d15127d5d Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 6 Sep 2022 16:59:33 -0700 Subject: [PATCH 0266/1310] SurfaceFlinger: Don't handle effects if a buffer is available Currently we do not support drawing effects and buffers in the same layer. Provide priority to drawing the buffer and then try and handle both as a follow up. Test: can add widgets to launcher Test: go/wm-smoke Bug: 245052266 Change-Id: I4399e954b6116fd8e5ee618c20fb3c604b657e04 (cherry picked from commit f115db8c252ba3e7ac257d9f7ba70e03abc058b2) Merged-In: I4399e954b6116fd8e5ee618c20fb3c604b657e04 --- services/surfaceflinger/Layer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 108047eb70..47bd91e195 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -769,7 +769,7 @@ std::optional Layer::prepareClientCom // Record the name of the layer for debugging further down the stack. layerSettings.name = getName(); - if (hasEffect()) { + if (hasEffect() && !hasBufferOrSidebandStream()) { prepareEffectsClientComposition(layerSettings, targetSettings); return layerSettings; } -- GitLab From 5e3befdabe2dd5cd242c41469825b3af06ea7c22 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 7 Sep 2022 18:23:52 +0000 Subject: [PATCH 0267/1310] SurfaceFlinger: Add a test to ensure buffer takes priority over blur Test: atest LayerTransactionTest#BufferTakesPriorityOverBlur Bug: 245052266 Change-Id: Ide73878fc55e0b75cc63076cba82f16e6e91446c --- .../tests/LayerTransaction_test.cpp | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/services/surfaceflinger/tests/LayerTransaction_test.cpp b/services/surfaceflinger/tests/LayerTransaction_test.cpp index 513fdc3793..c206e1ffd1 100644 --- a/services/surfaceflinger/tests/LayerTransaction_test.cpp +++ b/services/surfaceflinger/tests/LayerTransaction_test.cpp @@ -154,6 +154,36 @@ TEST_F(LayerTransactionTest, DISABLED_BufferQueueLayerMergeDamageRegionWhenDropp ASSERT_EQ(OK, producer->disconnect(NATIVE_WINDOW_API_CPU)); } + +// b/245052266 - we possible could support blur and a buffer at the same layer but +// might break existing assumptions at higher level. This test captures the current +// expectations. A layer drawing a buffer will not support blur. +TEST_F(LayerTransactionTest, BufferTakesPriorityOverBlur) { + sp layer; + ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + Transaction().setBackgroundBlurRadius(layer, 5).apply(); + { + SCOPED_TRACE("BufferTakesPriorityOverBlur"); + const Rect rect(0, 0, 32, 32); + auto shot = screenshot(); + shot->expectColor(rect, Color::RED); + } +} + +TEST_F(LayerTransactionTest, BufferTakesPriorityOverColor) { + sp layer; + ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + Transaction().setColor(layer, {Color::GREEN.r, Color::GREEN.g, Color::GREEN.b}).apply(); + { + SCOPED_TRACE("BufferTakesPriorityOverColor"); + const Rect rect(0, 0, 32, 32); + auto shot = screenshot(); + shot->expectColor(rect, Color::RED); + } +} + } // namespace android // TODO(b/129481165): remove the #pragma below and fix conversion issues -- GitLab From 37acf6e3b7ff4b30567eb3210560db3d73c20a58 Mon Sep 17 00:00:00 2001 From: Yeabkal Wubshit Date: Sat, 27 Aug 2022 05:48:51 +0000 Subject: [PATCH 0268/1310] Make VelocityTracker 1D Currently, VelocityTracker is strictly tied to X and Y axes. It's APIs act on both axes, and its component structs (e.g. Position, Estimator) are tied to both X and Y axes. As a step towards supporting more axes for velocity tracking, this change: - removes the Position struct: stores/processes data as pure floats, one axis at a time - makes Estimator and Strategy specific to a single axis, instead of dealing with both/only X and Y at the same time Furthermore, we have pulled into VelocityTracker the logic to compute all velocity. This helps making the immediate JNI layer light-weight in addition to allowing us to test the logic (which is non-trivial and benefits from tests). Bug: 32830165 Test: VelocityTracker_test unaffected (atest libinput_tests) Change-Id: I181af7a033eb647e9cb97db9b86a36ae972290a5 --- include/input/VelocityControl.h | 2 +- include/input/VelocityTracker.h | 119 ++++--- libs/input/VelocityControl.cpp | 26 +- libs/input/VelocityTracker.cpp | 313 +++++++++--------- libs/input/tests/VelocityTracker_test.cpp | 107 +++++- .../reader/mapper/TouchInputMapper.cpp | 20 +- 6 files changed, 353 insertions(+), 234 deletions(-) diff --git a/include/input/VelocityControl.h b/include/input/VelocityControl.h index 1acc2aef70..f4c7061ad1 100644 --- a/include/input/VelocityControl.h +++ b/include/input/VelocityControl.h @@ -98,7 +98,7 @@ private: VelocityControlParameters mParameters; nsecs_t mLastMovementTime; - VelocityTracker::Position mRawPosition; + float mRawPositionX, mRawPositionY; VelocityTracker mVelocityTracker; }; diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h index 886f1f7753..6f2fcf4ce4 100644 --- a/include/input/VelocityTracker.h +++ b/include/input/VelocityTracker.h @@ -20,6 +20,8 @@ #include #include #include +#include +#include namespace android { @@ -46,18 +48,14 @@ public: MAX = LEGACY, }; - struct Position { - float x, y; - }; - struct Estimator { static const size_t MAX_DEGREE = 4; // Estimator time base. nsecs_t time; - // Polynomial coefficients describing motion in X and Y. - float xCoeff[MAX_DEGREE + 1], yCoeff[MAX_DEGREE + 1]; + // Polynomial coefficients describing motion. + float coeff[MAX_DEGREE + 1]; // Polynomial degree (number of coefficients), or zero if no information is // available. @@ -71,14 +69,40 @@ public: degree = 0; confidence = 0; for (size_t i = 0; i <= MAX_DEGREE; i++) { - xCoeff[i] = 0; - yCoeff[i] = 0; + coeff[i] = 0; + } + } + }; + + /* + * Contains all available velocity data from a VelocityTracker. + */ + struct ComputedVelocity { + inline std::optional getVelocity(int32_t axis, uint32_t id) const { + const auto& axisVelocities = mVelocities.find(axis); + if (axisVelocities == mVelocities.end()) { + return {}; + } + + const auto& axisIdVelocity = axisVelocities->second.find(id); + if (axisIdVelocity == axisVelocities->second.end()) { + return {}; } + + return axisIdVelocity->second; + } + + inline void addVelocity(int32_t axis, uint32_t id, float velocity) { + mVelocities[axis][id] = velocity; } + + private: + std::map> mVelocities; }; - // Creates a velocity tracker using the specified strategy. + // Creates a velocity tracker using the specified strategy for each supported axis. // If strategy is not provided, uses the default strategy for the platform. + // TODO(b/32830165): support axis-specific strategies. VelocityTracker(const Strategy strategy = Strategy::DEFAULT); ~VelocityTracker(); @@ -92,45 +116,57 @@ public: void clearPointers(BitSet32 idBits); // Adds movement information for a set of pointers. - // The idBits bitfield specifies the pointer ids of the pointers whose positions + // The idBits bitfield specifies the pointer ids of the pointers whose data points // are included in the movement. - // The positions array contains position information for each pointer in order by - // increasing id. Its size should be equal to the number of one bits in idBits. - void addMovement(nsecs_t eventTime, BitSet32 idBits, const std::vector& positions); + // The positions map contains a mapping of an axis to positions array. + // The positions arrays contain information for each pointer in order by increasing id. + // Each array's size should be equal to the number of one bits in idBits. + void addMovement(nsecs_t eventTime, BitSet32 idBits, + const std::map>& positions); // Adds movement information for all pointers in a MotionEvent, including historical samples. void addMovement(const MotionEvent* event); - // Gets the velocity of the specified pointer id in position units per second. - // Returns false and sets the velocity components to zero if there is - // insufficient movement information for the pointer. - bool getVelocity(uint32_t id, float* outVx, float* outVy) const; + // Returns the velocity of the specified pointer id and axis in position units per second. + // Returns empty optional if there is insufficient movement information for the pointer, or if + // the given axis is not supported for velocity tracking. + std::optional getVelocity(int32_t axis, uint32_t id) const; - // Gets an estimator for the recent movements of the specified pointer id. + // Populates a ComputedVelocity instance with all available velocity data, using the given units + // (reference: units == 1 means "per millisecond"), and clamping each velocity between + // [-maxVelocity, maxVelocity], inclusive. + void populateComputedVelocity(ComputedVelocity& computedVelocity, int32_t units, + float maxVelocity); + + // Gets an estimator for the recent movements of the specified pointer id for the given axis. // Returns false and clears the estimator if there is no information available // about the pointer. - bool getEstimator(uint32_t id, Estimator* outEstimator) const; + bool getEstimator(int32_t axis, uint32_t id, Estimator* outEstimator) const; // Gets the active pointer id, or -1 if none. inline int32_t getActivePointerId() const { return mActivePointerId; } - // Gets a bitset containing all pointer ids from the most recent movement. - inline BitSet32 getCurrentPointerIdBits() const { return mCurrentPointerIdBits; } - private: // The default velocity tracker strategy. // Although other strategies are available for testing and comparison purposes, // this is the strategy that applications will actually use. Be very careful // when adjusting the default strategy because it can dramatically affect // (often in a bad way) the user experience. + // TODO(b/32830165): define default strategy per axis. static const Strategy DEFAULT_STRATEGY = Strategy::LSQ2; + // Set of all axes supported for velocity tracking. + static const std::set SUPPORTED_AXES; + + // Axes specifying location on a 2D plane (i.e. X and Y). + static const std::set PLANAR_AXES; + nsecs_t mLastEventTime; BitSet32 mCurrentPointerIdBits; int32_t mActivePointerId; - std::unique_ptr mStrategy; + std::map> mStrategies; - bool configureStrategy(const Strategy strategy); + void configureStrategy(int32_t axis, const Strategy strategy); static std::unique_ptr createStrategy(const Strategy strategy); }; @@ -149,7 +185,7 @@ public: virtual void clear() = 0; virtual void clearPointers(BitSet32 idBits) = 0; virtual void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) = 0; + const std::vector& positions) = 0; virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const = 0; }; @@ -181,7 +217,7 @@ public: virtual void clear(); virtual void clearPointers(BitSet32 idBits); void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) override; + const std::vector& positions) override; virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const; private: @@ -196,11 +232,9 @@ private: struct Movement { nsecs_t eventTime; BitSet32 idBits; - VelocityTracker::Position positions[MAX_POINTERS]; + float positions[MAX_POINTERS]; - inline const VelocityTracker::Position& getPosition(uint32_t id) const { - return positions[idBits.getIndexOfBit(id)]; - } + inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; } }; float chooseWeight(uint32_t index) const; @@ -224,7 +258,7 @@ public: virtual void clear(); virtual void clearPointers(BitSet32 idBits); void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) override; + const std::vector& positions) override; virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const; private: @@ -233,16 +267,15 @@ private: nsecs_t updateTime; uint32_t degree; - float xpos, xvel, xaccel; - float ypos, yvel, yaccel; + float pos, vel, accel; }; const uint32_t mDegree; BitSet32 mPointerIdBits; State mPointerState[MAX_POINTER_ID + 1]; - void initState(State& state, nsecs_t eventTime, float xpos, float ypos) const; - void updateState(State& state, nsecs_t eventTime, float xpos, float ypos) const; + void initState(State& state, nsecs_t eventTime, float pos) const; + void updateState(State& state, nsecs_t eventTime, float pos) const; void populateEstimator(const State& state, VelocityTracker::Estimator* outEstimator) const; }; @@ -258,7 +291,7 @@ public: virtual void clear(); virtual void clearPointers(BitSet32 idBits); void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) override; + const std::vector& positions) override; virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const; private: @@ -274,11 +307,9 @@ private: struct Movement { nsecs_t eventTime; BitSet32 idBits; - VelocityTracker::Position positions[MAX_POINTERS]; + float positions[MAX_POINTERS]; - inline const VelocityTracker::Position& getPosition(uint32_t id) const { - return positions[idBits.getIndexOfBit(id)]; - } + inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; } }; uint32_t mIndex; @@ -293,7 +324,7 @@ public: virtual void clear(); virtual void clearPointers(BitSet32 idBits); void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) override; + const std::vector& positions) override; virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const; private: @@ -308,11 +339,9 @@ private: struct Movement { nsecs_t eventTime; BitSet32 idBits; - VelocityTracker::Position positions[MAX_POINTERS]; + float positions[MAX_POINTERS]; - inline const VelocityTracker::Position& getPosition(uint32_t id) const { - return positions[idBits.getIndexOfBit(id)]; - } + inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; } }; size_t mIndex; diff --git a/libs/input/VelocityControl.cpp b/libs/input/VelocityControl.cpp index 6e991e98bb..e2bfb508e1 100644 --- a/libs/input/VelocityControl.cpp +++ b/libs/input/VelocityControl.cpp @@ -44,8 +44,8 @@ void VelocityControl::setParameters(const VelocityControlParameters& parameters) void VelocityControl::reset() { mLastMovementTime = LLONG_MIN; - mRawPosition.x = 0; - mRawPosition.y = 0; + mRawPositionX = 0; + mRawPositionY = 0; mVelocityTracker.clear(); } @@ -61,17 +61,20 @@ void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) { mLastMovementTime = eventTime; if (deltaX) { - mRawPosition.x += *deltaX; + mRawPositionX += *deltaX; } if (deltaY) { - mRawPosition.y += *deltaY; + mRawPositionY += *deltaY; } - mVelocityTracker.addMovement(eventTime, BitSet32(BitSet32::valueForBit(0)), {mRawPosition}); + mVelocityTracker.addMovement(eventTime, BitSet32(BitSet32::valueForBit(0)), + {{AMOTION_EVENT_AXIS_X, {mRawPositionX}}, + {AMOTION_EVENT_AXIS_Y, {mRawPositionY}}}); - float vx, vy; + std::optional vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0); + std::optional vy = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, 0); float scale = mParameters.scale; - if (mVelocityTracker.getVelocity(0, &vx, &vy)) { - float speed = hypotf(vx, vy) * scale; + if (vx && vy) { + float speed = hypotf(*vx, *vy) * scale; if (speed >= mParameters.highThreshold) { // Apply full acceleration above the high speed threshold. scale *= mParameters.acceleration; @@ -85,10 +88,9 @@ void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) { if (DEBUG_ACCELERATION) { ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): " - "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f", - mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold, - mParameters.acceleration, - vx, vy, speed, scale / mParameters.scale); + "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f", + mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold, + mParameters.acceleration, *vx, *vy, speed, scale / mParameters.scale); } } else { diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp index 76aaf61da7..4f91af14ea 100644 --- a/libs/input/VelocityTracker.cpp +++ b/libs/input/VelocityTracker.cpp @@ -125,29 +125,39 @@ static std::string matrixToString(const float* a, uint32_t m, uint32_t n, bool r // --- VelocityTracker --- +const std::set VelocityTracker::SUPPORTED_AXES = {AMOTION_EVENT_AXIS_X, + AMOTION_EVENT_AXIS_Y}; + +const std::set VelocityTracker::PLANAR_AXES = {AMOTION_EVENT_AXIS_X, AMOTION_EVENT_AXIS_Y}; + VelocityTracker::VelocityTracker(const Strategy strategy) : mLastEventTime(0), mCurrentPointerIdBits(0), mActivePointerId(-1) { - // Configure the strategy. - if (!configureStrategy(strategy)) { - ALOGE("Unrecognized velocity tracker strategy %" PRId32 ".", strategy); - if (!configureStrategy(VelocityTracker::DEFAULT_STRATEGY)) { - LOG_ALWAYS_FATAL("Could not create the default velocity tracker strategy '%" PRId32 - "'!", - strategy); - } + // Configure the strategy for each axis. + for (int32_t axis : SUPPORTED_AXES) { + configureStrategy(axis, strategy); } } VelocityTracker::~VelocityTracker() { } -bool VelocityTracker::configureStrategy(Strategy strategy) { +void VelocityTracker::configureStrategy(int32_t axis, const Strategy strategy) { + std::unique_ptr createdStrategy; + if (strategy == VelocityTracker::Strategy::DEFAULT) { - mStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY); + createdStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY); } else { - mStrategy = createStrategy(strategy); + createdStrategy = createStrategy(strategy); + } + + if (createdStrategy == nullptr) { + ALOGE("Unrecognized velocity tracker strategy %" PRId32 ".", strategy); + createdStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY); + LOG_ALWAYS_FATAL_IF(createdStrategy == nullptr, + "Could not create the default velocity tracker strategy '%" PRId32 "'!", + strategy); } - return mStrategy != nullptr; + mStrategies[axis] = std::move(createdStrategy); } std::unique_ptr VelocityTracker::createStrategy( @@ -201,8 +211,9 @@ std::unique_ptr VelocityTracker::createStrategy( void VelocityTracker::clear() { mCurrentPointerIdBits.clear(); mActivePointerId = -1; - - mStrategy->clear(); + for (int32_t axis : SUPPORTED_AXES) { + mStrategies[axis]->clear(); + } } void VelocityTracker::clearPointers(BitSet32 idBits) { @@ -213,14 +224,13 @@ void VelocityTracker::clearPointers(BitSet32 idBits) { mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1; } - mStrategy->clearPointers(idBits); + for (int32_t axis : SUPPORTED_AXES) { + mStrategies[axis]->clearPointers(idBits); + } } void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { - LOG_ALWAYS_FATAL_IF(idBits.count() != positions.size(), - "Mismatching number of pointers, idBits=%" PRIu32 ", positions=%zu", - idBits.count(), positions.size()); + const std::map>& positions) { while (idBits.count() > MAX_POINTERS) { idBits.clearLastMarkedBit(); } @@ -232,7 +242,9 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, // We have not received any movements for too long. Assume that all pointers // have stopped. - mStrategy->clear(); + for (const auto& [_, strategy] : mStrategies) { + strategy->clear(); + } } mLastEventTime = eventTime; @@ -241,29 +253,37 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, mActivePointerId = idBits.isEmpty() ? -1 : idBits.firstMarkedBit(); } - mStrategy->addMovement(eventTime, idBits, positions); + for (const auto& [axis, positionValues] : positions) { + LOG_ALWAYS_FATAL_IF(idBits.count() != positionValues.size(), + "Mismatching number of pointers, idBits=%" PRIu32 ", positions=%zu", + idBits.count(), positionValues.size()); + mStrategies[axis]->addMovement(eventTime, idBits, positionValues); + } if (DEBUG_VELOCITY) { ALOGD("VelocityTracker: addMovement eventTime=%" PRId64 ", idBits=0x%08x, activePointerId=%d", eventTime, idBits.value, mActivePointerId); - for (BitSet32 iterBits(idBits); !iterBits.isEmpty();) { - uint32_t id = iterBits.firstMarkedBit(); - uint32_t index = idBits.getIndexOfBit(id); - iterBits.clearBit(id); - Estimator estimator; - getEstimator(id, &estimator); - ALOGD(" %d: position (%0.3f, %0.3f), " - "estimator (degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f)", - id, positions[index].x, positions[index].y, int(estimator.degree), - vectorToString(estimator.xCoeff, estimator.degree + 1).c_str(), - vectorToString(estimator.yCoeff, estimator.degree + 1).c_str(), - estimator.confidence); + for (const auto& positionsEntry : positions) { + for (BitSet32 iterBits(idBits); !iterBits.isEmpty();) { + uint32_t id = iterBits.firstMarkedBit(); + uint32_t index = idBits.getIndexOfBit(id); + iterBits.clearBit(id); + Estimator estimator; + getEstimator(positionsEntry.first, id, &estimator); + ALOGD(" %d: axis=%d, position=%0.3f, " + "estimator (degree=%d, coeff=%s, confidence=%f)", + id, positionsEntry.first, positionsEntry.second[index], int(estimator.degree), + vectorToString(estimator.coeff, estimator.degree + 1).c_str(), + estimator.confidence); + } } } } void VelocityTracker::addMovement(const MotionEvent* event) { + // Stores data about which axes to process based on the incoming motion event. + std::set axesToProcess; int32_t actionMasked = event->getActionMasked(); switch (actionMasked) { @@ -271,6 +291,9 @@ void VelocityTracker::addMovement(const MotionEvent* event) { case AMOTION_EVENT_ACTION_HOVER_ENTER: // Clear all pointers on down before adding the new movement. clear(); + for (int32_t axis : PLANAR_AXES) { + axesToProcess.insert(axis); + } break; case AMOTION_EVENT_ACTION_POINTER_DOWN: { // Start a new movement trace for a pointer that just went down. @@ -279,10 +302,16 @@ void VelocityTracker::addMovement(const MotionEvent* event) { BitSet32 downIdBits; downIdBits.markBit(event->getPointerId(event->getActionIndex())); clearPointers(downIdBits); + for (int32_t axis : PLANAR_AXES) { + axesToProcess.insert(axis); + } break; } case AMOTION_EVENT_ACTION_MOVE: case AMOTION_EVENT_ACTION_HOVER_MOVE: + for (int32_t axis : PLANAR_AXES) { + axesToProcess.insert(axis); + } break; case AMOTION_EVENT_ACTION_POINTER_UP: case AMOTION_EVENT_ACTION_UP: { @@ -293,7 +322,9 @@ void VelocityTracker::addMovement(const MotionEvent* event) { toString(delaySinceLastEvent).c_str()); // We have not received any movements for too long. Assume that all pointers // have stopped. - mStrategy->clear(); + for (int32_t axis : PLANAR_AXES) { + mStrategies[axis]->clear(); + } } // These actions because they do not convey any new information about // pointer movement. We also want to preserve the last known velocity of the pointers. @@ -325,37 +356,54 @@ void VelocityTracker::addMovement(const MotionEvent* event) { pointerIndex[i] = idBits.getIndexOfBit(event->getPointerId(i)); } - std::vector positions; - positions.resize(pointerCount); + std::map> positions; + for (int32_t axis : axesToProcess) { + positions[axis].resize(pointerCount); + } size_t historySize = event->getHistorySize(); for (size_t h = 0; h <= historySize; h++) { nsecs_t eventTime = event->getHistoricalEventTime(h); - for (size_t i = 0; i < pointerCount; i++) { - uint32_t index = pointerIndex[i]; - positions[index].x = event->getHistoricalX(i, h); - positions[index].y = event->getHistoricalY(i, h); + for (int32_t axis : axesToProcess) { + for (size_t i = 0; i < pointerCount; i++) { + positions[axis][pointerIndex[i]] = event->getHistoricalAxisValue(axis, i, h); + } } addMovement(eventTime, idBits, positions); } } -bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const { +std::optional VelocityTracker::getVelocity(int32_t axis, uint32_t id) const { Estimator estimator; - if (getEstimator(id, &estimator) && estimator.degree >= 1) { - *outVx = estimator.xCoeff[1]; - *outVy = estimator.yCoeff[1]; - return true; + bool validVelocity = getEstimator(axis, id, &estimator) && estimator.degree >= 1; + if (validVelocity) { + return estimator.coeff[1]; } - *outVx = 0; - *outVy = 0; - return false; + return {}; } -bool VelocityTracker::getEstimator(uint32_t id, Estimator* outEstimator) const { - return mStrategy->getEstimator(id, outEstimator); +void VelocityTracker::populateComputedVelocity(ComputedVelocity& computedVelocity, int32_t units, + float maxVelocity) { + for (int32_t axis : SUPPORTED_AXES) { + BitSet32 copyIdBits = BitSet32(mCurrentPointerIdBits); + while (!copyIdBits.isEmpty()) { + uint32_t id = copyIdBits.clearFirstMarkedBit(); + std::optional velocity = getVelocity(axis, id); + if (velocity) { + float adjustedVelocity = + std::clamp(*velocity * units / 1000, -maxVelocity, maxVelocity); + computedVelocity.addVelocity(axis, id, adjustedVelocity); + } + } + } } +bool VelocityTracker::getEstimator(int32_t axis, uint32_t id, Estimator* outEstimator) const { + if (SUPPORTED_AXES.find(axis) == SUPPORTED_AXES.end()) { + return false; + } + return mStrategies.at(axis)->getEstimator(id, outEstimator); +} // --- LeastSquaresVelocityTrackerStrategy --- @@ -378,9 +426,8 @@ void LeastSquaresVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { mMovements[mIndex].idBits = remainingIdBits; } -void LeastSquaresVelocityTrackerStrategy::addMovement( - nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { +void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, + const std::vector& positions) { if (mMovements[mIndex].eventTime != eventTime) { // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include @@ -627,8 +674,7 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id, outEstimator->clear(); // Iterate over movement samples in reverse time order and collect samples. - std::vector x; - std::vector y; + std::vector positions; std::vector w; std::vector time; @@ -645,15 +691,13 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id, break; } - const VelocityTracker::Position& position = movement.getPosition(id); - x.push_back(position.x); - y.push_back(position.y); + positions.push_back(movement.getPosition(id)); w.push_back(chooseWeight(index)); time.push_back(-age * 0.000000001f); index = (index == 0 ? HISTORY_SIZE : index) - 1; - } while (x.size() < HISTORY_SIZE); + } while (positions.size() < HISTORY_SIZE); - const size_t m = x.size(); + const size_t m = positions.size(); if (m == 0) { return false; // no data } @@ -666,39 +710,36 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id, if (degree == 2 && mWeighting == WEIGHTING_NONE) { // Optimize unweighted, quadratic polynomial fit - std::optional> xCoeff = solveUnweightedLeastSquaresDeg2(time, x); - std::optional> yCoeff = solveUnweightedLeastSquaresDeg2(time, y); - if (xCoeff && yCoeff) { + std::optional> coeff = + solveUnweightedLeastSquaresDeg2(time, positions); + if (coeff) { outEstimator->time = newestMovement.eventTime; outEstimator->degree = 2; outEstimator->confidence = 1; for (size_t i = 0; i <= outEstimator->degree; i++) { - outEstimator->xCoeff[i] = (*xCoeff)[i]; - outEstimator->yCoeff[i] = (*yCoeff)[i]; + outEstimator->coeff[i] = (*coeff)[i]; } return true; } } else if (degree >= 1) { // General case for an Nth degree polynomial fit - float xdet, ydet; + float det; uint32_t n = degree + 1; - if (solveLeastSquares(time, x, w, n, outEstimator->xCoeff, &xdet) && - solveLeastSquares(time, y, w, n, outEstimator->yCoeff, &ydet)) { + if (solveLeastSquares(time, positions, w, n, outEstimator->coeff, &det)) { outEstimator->time = newestMovement.eventTime; outEstimator->degree = degree; - outEstimator->confidence = xdet * ydet; + outEstimator->confidence = det; - ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f", - int(outEstimator->degree), vectorToString(outEstimator->xCoeff, n).c_str(), - vectorToString(outEstimator->yCoeff, n).c_str(), outEstimator->confidence); + ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, coeff=%s, confidence=%f", + int(outEstimator->degree), vectorToString(outEstimator->coeff, n).c_str(), + outEstimator->confidence); return true; } } // No velocity data available for this pointer, but we do have its current position. - outEstimator->xCoeff[0] = x[0]; - outEstimator->yCoeff[0] = y[0]; + outEstimator->coeff[0] = positions[0]; outEstimator->time = newestMovement.eventTime; outEstimator->degree = 0; outEstimator->confidence = 1; @@ -790,18 +831,17 @@ void IntegratingVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { mPointerIdBits.value &= ~idBits.value; } -void IntegratingVelocityTrackerStrategy::addMovement( - nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { +void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, + const std::vector& positions) { uint32_t index = 0; for (BitSet32 iterIdBits(idBits); !iterIdBits.isEmpty();) { uint32_t id = iterIdBits.clearFirstMarkedBit(); State& state = mPointerState[id]; - const VelocityTracker::Position& position = positions[index++]; + const float position = positions[index++]; if (mPointerIdBits.hasBit(id)) { - updateState(state, eventTime, position.x, position.y); + updateState(state, eventTime, position); } else { - initState(state, eventTime, position.x, position.y); + initState(state, eventTime, position); } } @@ -821,21 +861,18 @@ bool IntegratingVelocityTrackerStrategy::getEstimator(uint32_t id, return false; } -void IntegratingVelocityTrackerStrategy::initState(State& state, - nsecs_t eventTime, float xpos, float ypos) const { +void IntegratingVelocityTrackerStrategy::initState(State& state, nsecs_t eventTime, + float pos) const { state.updateTime = eventTime; state.degree = 0; - state.xpos = xpos; - state.xvel = 0; - state.xaccel = 0; - state.ypos = ypos; - state.yvel = 0; - state.yaccel = 0; + state.pos = pos; + state.accel = 0; + state.vel = 0; } -void IntegratingVelocityTrackerStrategy::updateState(State& state, - nsecs_t eventTime, float xpos, float ypos) const { +void IntegratingVelocityTrackerStrategy::updateState(State& state, nsecs_t eventTime, + float pos) const { const nsecs_t MIN_TIME_DELTA = 2 * NANOS_PER_MS; const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds @@ -846,34 +883,26 @@ void IntegratingVelocityTrackerStrategy::updateState(State& state, float dt = (eventTime - state.updateTime) * 0.000000001f; state.updateTime = eventTime; - float xvel = (xpos - state.xpos) / dt; - float yvel = (ypos - state.ypos) / dt; + float vel = (pos - state.pos) / dt; if (state.degree == 0) { - state.xvel = xvel; - state.yvel = yvel; + state.vel = vel; state.degree = 1; } else { float alpha = dt / (FILTER_TIME_CONSTANT + dt); if (mDegree == 1) { - state.xvel += (xvel - state.xvel) * alpha; - state.yvel += (yvel - state.yvel) * alpha; + state.vel += (vel - state.vel) * alpha; } else { - float xaccel = (xvel - state.xvel) / dt; - float yaccel = (yvel - state.yvel) / dt; + float accel = (vel - state.vel) / dt; if (state.degree == 1) { - state.xaccel = xaccel; - state.yaccel = yaccel; + state.accel = accel; state.degree = 2; } else { - state.xaccel += (xaccel - state.xaccel) * alpha; - state.yaccel += (yaccel - state.yaccel) * alpha; + state.accel += (accel - state.accel) * alpha; } - state.xvel += (state.xaccel * dt) * alpha; - state.yvel += (state.yaccel * dt) * alpha; + state.vel += (state.accel * dt) * alpha; } } - state.xpos = xpos; - state.ypos = ypos; + state.pos = pos; } void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state, @@ -881,12 +910,9 @@ void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state, outEstimator->time = state.updateTime; outEstimator->confidence = 1.0f; outEstimator->degree = state.degree; - outEstimator->xCoeff[0] = state.xpos; - outEstimator->xCoeff[1] = state.xvel; - outEstimator->xCoeff[2] = state.xaccel / 2; - outEstimator->yCoeff[0] = state.ypos; - outEstimator->yCoeff[1] = state.yvel; - outEstimator->yCoeff[2] = state.yaccel / 2; + outEstimator->coeff[0] = state.pos; + outEstimator->coeff[1] = state.vel; + outEstimator->coeff[2] = state.accel / 2; } @@ -909,9 +935,8 @@ void LegacyVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { mMovements[mIndex].idBits = remainingIdBits; } -void LegacyVelocityTrackerStrategy::addMovement( - nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { +void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, + const std::vector& positions) { if (++mIndex == HISTORY_SIZE) { mIndex = 0; } @@ -959,12 +984,11 @@ bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id, // overestimate the velocity at that time point. Most samples might be measured // 16ms apart but some consecutive samples could be only 0.5sm apart because // the hardware or driver reports them irregularly or in bursts. - float accumVx = 0; - float accumVy = 0; + float accumV = 0; uint32_t index = oldestIndex; uint32_t samplesUsed = 0; const Movement& oldestMovement = mMovements[oldestIndex]; - const VelocityTracker::Position& oldestPosition = oldestMovement.getPosition(id); + float oldestPosition = oldestMovement.getPosition(id); nsecs_t lastDuration = 0; while (numTouches-- > 1) { @@ -978,26 +1002,22 @@ bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id, // the velocity. Consequently, we impose a minimum duration constraint on the // samples that we include in the calculation. if (duration >= MIN_DURATION) { - const VelocityTracker::Position& position = movement.getPosition(id); + float position = movement.getPosition(id); float scale = 1000000000.0f / duration; // one over time delta in seconds - float vx = (position.x - oldestPosition.x) * scale; - float vy = (position.y - oldestPosition.y) * scale; - accumVx = (accumVx * lastDuration + vx * duration) / (duration + lastDuration); - accumVy = (accumVy * lastDuration + vy * duration) / (duration + lastDuration); + float v = (position - oldestPosition) * scale; + accumV = (accumV * lastDuration + v * duration) / (duration + lastDuration); lastDuration = duration; samplesUsed += 1; } } // Report velocity. - const VelocityTracker::Position& newestPosition = newestMovement.getPosition(id); + float newestPosition = newestMovement.getPosition(id); outEstimator->time = newestMovement.eventTime; outEstimator->confidence = 1; - outEstimator->xCoeff[0] = newestPosition.x; - outEstimator->yCoeff[0] = newestPosition.y; + outEstimator->coeff[0] = newestPosition; if (samplesUsed) { - outEstimator->xCoeff[1] = accumVx; - outEstimator->yCoeff[1] = accumVy; + outEstimator->coeff[1] = accumV; outEstimator->degree = 1; } else { outEstimator->degree = 0; @@ -1024,9 +1044,8 @@ void ImpulseVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { mMovements[mIndex].idBits = remainingIdBits; } -void ImpulseVelocityTrackerStrategy::addMovement( - nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { +void ImpulseVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, + const std::vector& positions) { if (mMovements[mIndex].eventTime != eventTime) { // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include @@ -1163,8 +1182,7 @@ bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, outEstimator->clear(); // Iterate over movement samples in reverse time order and collect samples. - float x[HISTORY_SIZE]; - float y[HISTORY_SIZE]; + float positions[HISTORY_SIZE]; nsecs_t time[HISTORY_SIZE]; size_t m = 0; // number of points that will be used for fitting size_t index = mIndex; @@ -1180,9 +1198,7 @@ bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, break; } - const VelocityTracker::Position& position = movement.getPosition(id); - x[m] = position.x; - y[m] = position.y; + positions[m] = movement.getPosition(id); time[m] = movement.eventTime; index = (index == 0 ? HISTORY_SIZE : index) - 1; } while (++m < HISTORY_SIZE); @@ -1190,33 +1206,30 @@ bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, if (m == 0) { return false; // no data } - outEstimator->xCoeff[0] = 0; - outEstimator->yCoeff[0] = 0; - outEstimator->xCoeff[1] = calculateImpulseVelocity(time, x, m); - outEstimator->yCoeff[1] = calculateImpulseVelocity(time, y, m); - outEstimator->xCoeff[2] = 0; - outEstimator->yCoeff[2] = 0; + outEstimator->coeff[0] = 0; + outEstimator->coeff[1] = calculateImpulseVelocity(time, positions, m); + outEstimator->coeff[2] = 0; + outEstimator->time = newestMovement.eventTime; outEstimator->degree = 2; // similar results to 2nd degree fit outEstimator->confidence = 1; - ALOGD_IF(DEBUG_STRATEGY, "velocity: (%.1f, %.1f)", outEstimator->xCoeff[1], - outEstimator->yCoeff[1]); + ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", outEstimator->coeff[1]); if (DEBUG_IMPULSE) { // TODO(b/134179997): delete this block once the switch to 'impulse' is complete. - // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons + // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons. + // X axis chosen arbitrarily for velocity comparisons. VelocityTracker lsq2(VelocityTracker::Strategy::LSQ2); BitSet32 idBits; const uint32_t pointerId = 0; idBits.markBit(pointerId); for (ssize_t i = m - 1; i >= 0; i--) { - lsq2.addMovement(time[i], idBits, {{x[i], y[i]}}); + lsq2.addMovement(time[i], idBits, {{AMOTION_EVENT_AXIS_X, {positions[i]}}}); } - float outVx = 0, outVy = 0; - const bool computed = lsq2.getVelocity(pointerId, &outVx, &outVy); - if (computed) { - ALOGD("lsq2 velocity: (%.1f, %.1f)", outVx, outVy); + std::optional v = lsq2.getVelocity(AMOTION_EVENT_AXIS_X, pointerId); + if (v) { + ALOGD("lsq2 velocity: %.1f", *v); } else { ALOGD("lsq2 velocity: could not compute velocity"); } diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp index 4a445de3ac..0a37318d3d 100644 --- a/libs/input/tests/VelocityTracker_test.cpp +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -16,9 +16,10 @@ #define LOG_TAG "VelocityTracker_test" +#include #include #include -#include +#include #include #include @@ -198,25 +199,13 @@ static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, const std::vector& motions, int32_t axis, float targetVelocity, uint32_t pointerId = DEFAULT_POINTER_ID) { VelocityTracker vt(strategy); - float Vx, Vy; std::vector events = createMotionEventStream(motions); for (MotionEvent event : events) { vt.addMovement(&event); } - vt.getVelocity(pointerId, &Vx, &Vy); - - switch (axis) { - case AMOTION_EVENT_AXIS_X: - checkVelocity(Vx, targetVelocity); - break; - case AMOTION_EVENT_AXIS_Y: - checkVelocity(Vy, targetVelocity); - break; - default: - FAIL() << "Axis must be either AMOTION_EVENT_AXIS_X or AMOTION_EVENT_AXIS_Y"; - } + checkVelocity(vt.getVelocity(axis, pointerId).value_or(0), targetVelocity); } static void computeAndCheckQuadraticEstimate(const std::vector& motions, @@ -226,17 +215,99 @@ static void computeAndCheckQuadraticEstimate(const std::vector for (MotionEvent event : events) { vt.addMovement(&event); } - VelocityTracker::Estimator estimator; - EXPECT_TRUE(vt.getEstimator(0, &estimator)); + VelocityTracker::Estimator estimatorX; + VelocityTracker::Estimator estimatorY; + EXPECT_TRUE(vt.getEstimator(AMOTION_EVENT_AXIS_X, 0, &estimatorX)); + EXPECT_TRUE(vt.getEstimator(AMOTION_EVENT_AXIS_Y, 0, &estimatorY)); for (size_t i = 0; i< coefficients.size(); i++) { - checkCoefficient(estimator.xCoeff[i], coefficients[i]); - checkCoefficient(estimator.yCoeff[i], coefficients[i]); + checkCoefficient(estimatorX.coeff[i], coefficients[i]); + checkCoefficient(estimatorY.coeff[i], coefficients[i]); } } /* * ================== VelocityTracker tests generated manually ===================================== */ +TEST_F(VelocityTrackerTest, TestComputedVelocity) { + VelocityTracker::ComputedVelocity computedVelocity; + + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, 0 /*id*/, 200 /*velocity*/); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, 26U /*id*/, 400 /*velocity*/); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, 27U /*id*/, 650 /*velocity*/); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID, 750 /*velocity*/); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, 0 /*id*/, 1000 /*velocity*/); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, 26U /*id*/, 2000 /*velocity*/); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, 27U /*id*/, 3000 /*velocity*/); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, MAX_POINTER_ID, 4000 /*velocity*/); + + // Check the axes/indices with velocity. + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, 0U /*id*/)), 200); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, 26U /*id*/)), 400); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, 27U /*id*/)), 650); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID)), 750); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, 0U /*id*/)), 1000); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, 26U /*id*/)), 2000); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, 27U /*id*/)), 3000); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, MAX_POINTER_ID)), 4000); + for (uint32_t id = 0; id < 32; id++) { + // Since no data was added for AXIS_SCROLL, expect empty value for the axis for any id. + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_SCROLL, id)) + << "Empty scroll data expected at id=" << id; + if (id == 0 || id == 26U || id == 27U || id == MAX_POINTER_ID) { + // Already checked above; continue. + continue; + } + // No data was added to X/Y for this id, expect empty value. + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, id)) + << "Empty X data expected at id=" << id; + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, id)) + << "Empty Y data expected at id=" << id; + } + // Out-of-bounds ids should given empty values. + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, -1)); + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID + 1)); +} + +TEST_F(VelocityTrackerTest, TestPopulateComputedVelocity) { + std::vector motions = { + {235089067457000ns, {{528.00, 0}}}, {235089084684000ns, {{527.00, 0}}}, + {235089093349000ns, {{527.00, 0}}}, {235089095677625ns, {{527.00, 0}}}, + {235089101859000ns, {{527.00, 0}}}, {235089110378000ns, {{528.00, 0}}}, + {235089112497111ns, {{528.25, 0}}}, {235089118760000ns, {{531.00, 0}}}, + {235089126686000ns, {{535.00, 0}}}, {235089129316820ns, {{536.33, 0}}}, + {235089135199000ns, {{540.00, 0}}}, {235089144297000ns, {{546.00, 0}}}, + {235089146136443ns, {{547.21, 0}}}, {235089152923000ns, {{553.00, 0}}}, + {235089160784000ns, {{559.00, 0}}}, {235089162955851ns, {{560.66, 0}}}, + {235089162955851ns, {{560.66, 0}}}, // ACTION_UP + }; + VelocityTracker vt(VelocityTracker::Strategy::IMPULSE); + std::vector events = createMotionEventStream(motions); + for (const MotionEvent& event : events) { + vt.addMovement(&event); + } + + float maxFloat = std::numeric_limits::max(); + VelocityTracker::ComputedVelocity computedVelocity; + vt.populateComputedVelocity(computedVelocity, 1000 /* units */, maxFloat); + checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)), + 764.345703); + + // Expect X velocity to be scaled with respective to provided units. + vt.populateComputedVelocity(computedVelocity, 1000000 /* units */, maxFloat); + checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)), + 764345.703); + + // Expect X velocity to be clamped by provided max velocity. + vt.populateComputedVelocity(computedVelocity, 1000000 /* units */, 1000); + checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)), 1000); + + // All 0 data for Y; expect 0 velocity. + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, DEFAULT_POINTER_ID)), 0); + + // No data for scroll-axis; expect empty velocity. + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_SCROLL, DEFAULT_POINTER_ID)); +} + TEST_F(VelocityTrackerTest, ThreePointsPositiveVelocityTest) { // Same coordinate is reported 2 times in a row // It is difficult to determine the correct answer here, but at least the direction diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 539e24a098..8c241f2f09 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -2712,17 +2712,18 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi // Update the velocity tracker. { - std::vector positions; + std::vector positionsX; + std::vector positionsY; for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) { uint32_t id = idBits.clearFirstMarkedBit(); const RawPointerData::Pointer& pointer = mCurrentRawState.rawPointerData.pointerForId(id); - float x = pointer.x * mPointerXMovementScale; - float y = pointer.y * mPointerYMovementScale; - positions.push_back({x, y}); + positionsX.push_back(pointer.x * mPointerXMovementScale); + positionsY.push_back(pointer.y * mPointerYMovementScale); } mPointerGesture.velocityTracker.addMovement(when, mCurrentCookedState.fingerIdBits, - positions); + {{AMOTION_EVENT_AXIS_X, positionsX}, + {AMOTION_EVENT_AXIS_Y, positionsY}}); } // If the gesture ever enters a mode other than TAP, HOVER or TAP_DRAG, without first returning @@ -2829,9 +2830,12 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed; for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) { uint32_t id = idBits.clearFirstMarkedBit(); - float vx, vy; - if (mPointerGesture.velocityTracker.getVelocity(id, &vx, &vy)) { - float speed = hypotf(vx, vy); + std::optional vx = + mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, id); + std::optional vy = + mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, id); + if (vx && vy) { + float speed = hypotf(*vx, *vy); if (speed > bestSpeed) { bestId = id; bestSpeed = speed; -- GitLab From a4343e649c61e690634d7b56221b7323ab86561f Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Thu, 8 Sep 2022 00:22:44 +0000 Subject: [PATCH 0269/1310] Clean up BBQ#abandon dead code Change-Id: I92f4d6c73a26885e1bb21c9683e25d8eb0ce8aa0 Test: presubmit Fixes: 245626507 --- libs/gui/BLASTBufferQueue.cpp | 44 ------------------------- libs/gui/include/gui/BLASTBufferQueue.h | 1 - 2 files changed, 45 deletions(-) diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index 3bf2e195c5..3afa339849 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -1083,50 +1083,6 @@ uint64_t BLASTBufferQueue::getLastAcquiredFrameNum() { return mLastAcquiredFrameNumber; } -void BLASTBufferQueue::abandon() { - std::unique_lock _lock{mMutex}; - // flush out the shadow queue - while (mNumFrameAvailable > 0) { - acquireAndReleaseBuffer(); - } - - // Clear submitted buffer states - mNumAcquired = 0; - mSubmitted.clear(); - mPendingRelease.clear(); - - if (!mPendingTransactions.empty()) { - BQA_LOGD("Applying pending transactions on abandon %d", - static_cast(mPendingTransactions.size())); - SurfaceComposerClient::Transaction t; - mergePendingTransactions(&t, std::numeric_limits::max() /* frameNumber */); - // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction - t.setApplyToken(mApplyToken).apply(false, true); - } - - // Clear sync states - if (!mSyncedFrameNumbers.empty()) { - BQA_LOGD("mSyncedFrameNumbers cleared"); - mSyncedFrameNumbers.clear(); - } - - if (mSyncTransaction != nullptr) { - BQA_LOGD("mSyncTransaction cleared mAcquireSingleBuffer=%s", - mAcquireSingleBuffer ? "true" : "false"); - mSyncTransaction = nullptr; - mAcquireSingleBuffer = false; - } - - // abandon buffer queue - if (mBufferItemConsumer != nullptr) { - mBufferItemConsumer->abandon(); - mBufferItemConsumer->setFrameAvailableListener(nullptr); - } - mBufferItemConsumer = nullptr; - mConsumer = nullptr; - mProducer = nullptr; -} - bool BLASTBufferQueue::isSameSurfaceControl(const sp& surfaceControl) const { std::unique_lock _lock{mMutex}; return SurfaceControl::isSameSurface(mSurfaceControl, surfaceControl); diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h index 95df811927..4535c98b97 100644 --- a/libs/gui/include/gui/BLASTBufferQueue.h +++ b/libs/gui/include/gui/BLASTBufferQueue.h @@ -108,7 +108,6 @@ public: uint32_t getLastTransformHint() const; uint64_t getLastAcquiredFrameNum(); - void abandon(); /** * Set a callback to be invoked when we are hung. The boolean parameter -- GitLab From cf875ab292ffebdf6165c037a00e0fc344b39b74 Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Wed, 7 Sep 2022 11:35:49 +0000 Subject: [PATCH 0270/1310] Add new native keycodes for keyboard backlight up/down/toggle Also syncing all the keycodes in KeyEvents.java file to here for consistency. Test: None Bug: 245506418 Change-Id: I40c0b6f7d0e67ffc1ee01c6cb1dfbcfc96b37d62 --- include/android/keycodes.h | 43 ++++++++++++++++++++++++++++++++- libs/input/InputEventLabels.cpp | 21 +++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/include/android/keycodes.h b/include/android/keycodes.h index 214559d683..16a13d6cd3 100644 --- a/include/android/keycodes.h +++ b/include/android/keycodes.h @@ -776,7 +776,48 @@ enum { AKEYCODE_THUMBS_DOWN = 287, /** Used to switch current account that is consuming content. * May be consumed by system to switch current viewer profile. */ - AKEYCODE_PROFILE_SWITCH = 288 + AKEYCODE_PROFILE_SWITCH = 288, + /** Video Application key #1. */ + AKEYCODE_VIDEO_APP_1 = 289, + /** Video Application key #2. */ + AKEYCODE_VIDEO_APP_2 = 290, + /** Video Application key #3. */ + AKEYCODE_VIDEO_APP_3 = 291, + /** Video Application key #4. */ + AKEYCODE_VIDEO_APP_4 = 292, + /** Video Application key #5. */ + AKEYCODE_VIDEO_APP_5 = 293, + /** Video Application key #6. */ + AKEYCODE_VIDEO_APP_6 = 294, + /** Video Application key #7. */ + AKEYCODE_VIDEO_APP_7 = 295, + /** Video Application key #8. */ + AKEYCODE_VIDEO_APP_8 = 296, + /** Featured Application key #1. */ + AKEYCODE_FEATURED_APP_1 = 297, + /** Featured Application key #2. */ + AKEYCODE_FEATURED_APP_2 = 298, + /** Featured Application key #3. */ + AKEYCODE_FEATURED_APP_3 = 299, + /** Featured Application key #4. */ + AKEYCODE_FEATURED_APP_4 = 300, + /** Demo Application key #1. */ + AKEYCODE_DEMO_APP_1 = 301, + /** Demo Application key #2. */ + AKEYCODE_DEMO_APP_2 = 302, + /** Demo Application key #3. */ + AKEYCODE_DEMO_APP_3 = 303, + /** Demo Application key #4. */ + AKEYCODE_DEMO_APP_4 = 304, + /** Keyboard backlight Down key. + * Adjusts the keyboard backlight brightness down. */ + AKEYCODE_KEYBOARD_BACKLIGHT_DOWN = 305, + /** Keyboard backlight Up key. + * Adjusts the keyboard backlight brightness up. */ + AKEYCODE_KEYBOARD_BACKLIGHT_UP = 306, + /** Keyboard backlight Toggle key. + * Toggles the keyboard backlight on/off. */ + AKEYCODE_KEYBOARD_BACKLIGHT_TOGGLE = 307, // NOTE: If you add a new keycode here you must also add it to several other files. // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list. diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp index c0aa2e26a2..5990ee0d30 100644 --- a/libs/input/InputEventLabels.cpp +++ b/libs/input/InputEventLabels.cpp @@ -314,7 +314,26 @@ namespace android { DEFINE_KEYCODE(REFRESH), \ DEFINE_KEYCODE(THUMBS_UP), \ DEFINE_KEYCODE(THUMBS_DOWN), \ - DEFINE_KEYCODE(PROFILE_SWITCH) + DEFINE_KEYCODE(PROFILE_SWITCH), \ + DEFINE_KEYCODE(VIDEO_APP_1), \ + DEFINE_KEYCODE(VIDEO_APP_2), \ + DEFINE_KEYCODE(VIDEO_APP_3), \ + DEFINE_KEYCODE(VIDEO_APP_4), \ + DEFINE_KEYCODE(VIDEO_APP_5), \ + DEFINE_KEYCODE(VIDEO_APP_6), \ + DEFINE_KEYCODE(VIDEO_APP_7), \ + DEFINE_KEYCODE(VIDEO_APP_8), \ + DEFINE_KEYCODE(FEATURED_APP_1), \ + DEFINE_KEYCODE(FEATURED_APP_2), \ + DEFINE_KEYCODE(FEATURED_APP_3), \ + DEFINE_KEYCODE(FEATURED_APP_4), \ + DEFINE_KEYCODE(DEMO_APP_1), \ + DEFINE_KEYCODE(DEMO_APP_2), \ + DEFINE_KEYCODE(DEMO_APP_3), \ + DEFINE_KEYCODE(DEMO_APP_4), \ + DEFINE_KEYCODE(KEYBOARD_BACKLIGHT_DOWN), \ + DEFINE_KEYCODE(KEYBOARD_BACKLIGHT_UP), \ + DEFINE_KEYCODE(KEYBOARD_BACKLIGHT_TOGGLE) // 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. -- GitLab From b363c4c28db829bc297e794eac91b02359ff2242 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Tue, 2 Aug 2022 14:03:41 -0700 Subject: [PATCH 0271/1310] SF: Introduce DisplaySnapshot DisplaySnapshot stores the immutable state of a physical display, captured on hotplug. The SurfaceFlinger class stores a map from PhysicalDisplayId to PhysicalDisplay, a new class that augments this state with the sp. Later, DisplayDevice will be removed, and SF::mDisplays will map wp to compositionengine::Display. Bug: 241285876 Test: libsurfaceflinger_unittest Test: dumpsys SurfaceFlinger --displays Change-Id: I172df87c4a7a9cf64659ff9cfbd6df195ee4bc8d --- libs/ui/include/ui/DisplayId.h | 5 +- libs/ui/include/ui/StaticDisplayInfo.h | 2 +- services/surfaceflinger/Android.bp | 7 +- services/surfaceflinger/Display/DisplayMap.h | 31 ++ .../Display/DisplaySnapshot.cpp | 58 +++ .../surfaceflinger/Display/DisplaySnapshot.h | 57 +++ .../surfaceflinger/Display/PhysicalDisplay.h | 76 ++++ services/surfaceflinger/DisplayDevice.cpp | 65 +--- services/surfaceflinger/DisplayDevice.h | 43 +-- services/surfaceflinger/SurfaceFlinger.cpp | 347 +++++++++++------- services/surfaceflinger/SurfaceFlinger.h | 62 ++-- .../tests/unittests/CompositionTest.cpp | 10 +- ...ceFlinger_DisplayTransactionCommitTest.cpp | 27 +- ...urfaceFlinger_SetPowerModeInternalTest.cpp | 19 +- ...nger_SetupNewDisplayDeviceInternalTest.cpp | 11 +- .../tests/unittests/TestableSurfaceFlinger.h | 44 ++- 16 files changed, 577 insertions(+), 287 deletions(-) create mode 100644 services/surfaceflinger/Display/DisplayMap.h create mode 100644 services/surfaceflinger/Display/DisplaySnapshot.cpp create mode 100644 services/surfaceflinger/Display/DisplaySnapshot.h create mode 100644 services/surfaceflinger/Display/PhysicalDisplay.h diff --git a/libs/ui/include/ui/DisplayId.h b/libs/ui/include/ui/DisplayId.h index 9120972a42..d0c03fe39f 100644 --- a/libs/ui/include/ui/DisplayId.h +++ b/libs/ui/include/ui/DisplayId.h @@ -17,9 +17,10 @@ #pragma once #include -#include #include +#include + namespace android { // ID of a physical or a virtual display. This class acts as a type safe wrapper around uint64_t. @@ -68,7 +69,7 @@ inline std::string to_string(DisplayId displayId) { // DisplayId of a physical display, such as the internal display or externally connected display. struct PhysicalDisplayId : DisplayId { - static constexpr std::optional tryCast(DisplayId id) { + static constexpr ftl::Optional tryCast(DisplayId id) { if (id.value & FLAG_VIRTUAL) { return std::nullopt; } diff --git a/libs/ui/include/ui/StaticDisplayInfo.h b/libs/ui/include/ui/StaticDisplayInfo.h index 566e4172a1..83da821f37 100644 --- a/libs/ui/include/ui/StaticDisplayInfo.h +++ b/libs/ui/include/ui/StaticDisplayInfo.h @@ -23,7 +23,7 @@ namespace android::ui { -enum class DisplayConnectionType { Internal, External }; +enum class DisplayConnectionType { Internal, External, ftl_last = External }; // Immutable information about physical display. struct StaticDisplayInfo { diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index 5e9fe65225..3348cec211 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -141,19 +141,20 @@ filegroup { name: "libsurfaceflinger_sources", srcs: [ "BackgroundExecutor.cpp", - "ClientCache.cpp", "Client.cpp", - "EffectLayer.cpp", + "ClientCache.cpp", + "Display/DisplaySnapshot.cpp", "DisplayDevice.cpp", "DisplayHardware/AidlComposerHal.cpp", - "DisplayHardware/HidlComposerHal.cpp", "DisplayHardware/ComposerHal.cpp", "DisplayHardware/FramebufferSurface.cpp", "DisplayHardware/HWC2.cpp", "DisplayHardware/HWComposer.cpp", + "DisplayHardware/HidlComposerHal.cpp", "DisplayHardware/PowerAdvisor.cpp", "DisplayHardware/VirtualDisplaySurface.cpp", "DisplayRenderArea.cpp", + "EffectLayer.cpp", "Effects/Daltonizer.cpp", "EventLog/EventLog.cpp", "FlagManager.cpp", diff --git a/services/surfaceflinger/Display/DisplayMap.h b/services/surfaceflinger/Display/DisplayMap.h new file mode 100644 index 0000000000..baf0da9f1b --- /dev/null +++ b/services/surfaceflinger/Display/DisplayMap.h @@ -0,0 +1,31 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +namespace android::display { + +// The static capacities were chosen to exceed a typical number of physical and/or virtual displays. + +template +using DisplayMap = ftl::SmallMap; + +template +using PhysicalDisplayMap = ftl::SmallMap; + +} // namespace android::display diff --git a/services/surfaceflinger/Display/DisplaySnapshot.cpp b/services/surfaceflinger/Display/DisplaySnapshot.cpp new file mode 100644 index 0000000000..b4f104a74d --- /dev/null +++ b/services/surfaceflinger/Display/DisplaySnapshot.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include + +#include "DisplaySnapshot.h" + +namespace android::display { + +DisplaySnapshot::DisplaySnapshot(PhysicalDisplayId displayId, + ui::DisplayConnectionType connectionType, + DisplayModes&& displayModes, + std::optional&& deviceProductInfo) + : mDisplayId(displayId), + mConnectionType(connectionType), + mDisplayModes(std::move(displayModes)), + mDeviceProductInfo(std::move(deviceProductInfo)) {} + +std::optional DisplaySnapshot::translateModeId(hal::HWConfigId hwcId) const { + return ftl::find_if(mDisplayModes, + [hwcId](const DisplayModes::value_type& pair) { + return pair.second->getHwcId() == hwcId; + }) + .transform(&ftl::to_key); +} + +void DisplaySnapshot::dump(std::string& out) const { + using namespace std::string_literals; + + out += " connectionType="s; + out += ftl::enum_string(mConnectionType); + + out += "\n deviceProductInfo="s; + if (mDeviceProductInfo) { + mDeviceProductInfo->dump(out); + } else { + out += "{}"s; + } +} + +} // namespace android::display diff --git a/services/surfaceflinger/Display/DisplaySnapshot.h b/services/surfaceflinger/Display/DisplaySnapshot.h new file mode 100644 index 0000000000..0279220b1b --- /dev/null +++ b/services/surfaceflinger/Display/DisplaySnapshot.h @@ -0,0 +1,57 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#include + +#include "../DisplayHardware/DisplayMode.h" + +namespace android::display { + +// Immutable state of a physical display, captured on hotplug. +class DisplaySnapshot { +public: + DisplaySnapshot(PhysicalDisplayId, ui::DisplayConnectionType, DisplayModes&&, + std::optional&&); + + DisplaySnapshot(const DisplaySnapshot&) = delete; + DisplaySnapshot(DisplaySnapshot&&) = default; + + PhysicalDisplayId displayId() const { return mDisplayId; } + ui::DisplayConnectionType connectionType() const { return mConnectionType; } + + std::optional translateModeId(hal::HWConfigId) const; + + const auto& displayModes() const { return mDisplayModes; } + const auto& deviceProductInfo() const { return mDeviceProductInfo; } + + void dump(std::string&) const; + +private: + const PhysicalDisplayId mDisplayId; + const ui::DisplayConnectionType mConnectionType; + + // Effectively const except in move constructor. + DisplayModes mDisplayModes; + std::optional mDeviceProductInfo; +}; + +} // namespace android::display diff --git a/services/surfaceflinger/Display/PhysicalDisplay.h b/services/surfaceflinger/Display/PhysicalDisplay.h new file mode 100644 index 0000000000..cba10146b7 --- /dev/null +++ b/services/surfaceflinger/Display/PhysicalDisplay.h @@ -0,0 +1,76 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#include +#include + +#include "DisplayMap.h" +#include "DisplaySnapshot.h" + +namespace android::display { + +// TODO(b/229877597): Replace with AIDL type. +using DisplayToken = IBinder; + +class PhysicalDisplay { +public: + template + PhysicalDisplay(sp token, Args&&... args) + : mToken(std::move(token)), mSnapshot(std::forward(args)...) {} + + PhysicalDisplay(const PhysicalDisplay&) = delete; + PhysicalDisplay(PhysicalDisplay&&) = default; + + const sp& token() const { return mToken; } + const DisplaySnapshot& snapshot() const { return mSnapshot; } + + // Transformers for PhysicalDisplays::get. + + using SnapshotRef = std::reference_wrapper; + SnapshotRef snapshotRef() const { return std::cref(mSnapshot); } + + bool isInternal() const { + return mSnapshot.connectionType() == ui::DisplayConnectionType::Internal; + } + + // Predicate for ftl::find_if on PhysicalDisplays. + static constexpr auto hasToken(const sp& token) { + return [&token](const std::pair& pair) { + return pair.second.token() == token; + }; + } + +private: + const sp mToken; + + // Effectively const except in move constructor. + DisplaySnapshot mSnapshot; +}; + +using PhysicalDisplays = PhysicalDisplayMap; + +// Combinator for ftl::Optional::and_then. +constexpr auto getPhysicalDisplay(const PhysicalDisplays& displays) { + return [&](PhysicalDisplayId id) { return displays.get(id); }; +} + +} // namespace android::display diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 2866a34a57..ebaf35a8f2 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -39,6 +39,7 @@ #include #include +#include "Display/DisplaySnapshot.h" #include "DisplayDevice.h" #include "Layer.h" #include "RefreshRateOverlay.h" @@ -63,12 +64,10 @@ DisplayDevice::DisplayDevice(DisplayDeviceCreationArgs& args) mHwComposer(args.hwComposer), mDisplayToken(args.displayToken), mSequenceId(args.sequenceId), - mConnectionType(args.connectionType), mCompositionDisplay{args.compositionDisplay}, mActiveModeFPSTrace("ActiveModeFPS -" + to_string(getId())), mActiveModeFPSHwcTrace("ActiveModeFPS_HWC -" + to_string(getId())), mPhysicalOrientation(args.physicalOrientation), - mSupportedModes(std::move(args.supportedModes)), mIsPrimary(args.isPrimary), mRefreshRateConfigs(std::move(args.refreshRateConfigs)) { mCompositionDisplay->editState().isSecure = args.isSecure; @@ -132,10 +131,6 @@ void DisplayDevice::setDisplayName(const std::string& displayName) { } } -void DisplayDevice::setDeviceProductInfo(std::optional info) { - mDeviceProductInfo = std::move(info); -} - auto DisplayDevice::getInputInfo() const -> InputInfo { gui::DisplayInfo info; info.displayId = getLayerStack().id; @@ -187,16 +182,20 @@ bool DisplayDevice::isPoweredOn() const { return mPowerMode && *mPowerMode != hal::PowerMode::OFF; } -void DisplayDevice::setActiveMode(DisplayModeId id) { - const auto mode = getMode(id); - LOG_FATAL_IF(!mode, "Cannot set active mode which is not supported."); - ATRACE_INT(mActiveModeFPSTrace.c_str(), mode->getFps().getIntValue()); - mActiveMode = mode; +void DisplayDevice::setActiveMode(DisplayModeId modeId, const display::DisplaySnapshot& snapshot) { + const auto modeOpt = snapshot.displayModes().get(modeId); + LOG_ALWAYS_FATAL_IF(!modeOpt, "Unknown mode"); + + mActiveMode = modeOpt->get(); + const Fps fps = mActiveMode->getFps(); + + ATRACE_INT(mActiveModeFPSTrace.c_str(), fps.getIntValue()); + if (mRefreshRateConfigs) { - mRefreshRateConfigs->setActiveModeId(mActiveMode->getId()); + mRefreshRateConfigs->setActiveModeId(modeId); } if (mRefreshRateOverlay) { - mRefreshRateOverlay->changeRefreshRate(mActiveMode->getFps()); + mRefreshRateOverlay->changeRefreshRate(fps); } } @@ -220,25 +219,6 @@ const DisplayModePtr& DisplayDevice::getActiveMode() const { return mActiveMode; } -const DisplayModes& DisplayDevice::getSupportedModes() const { - return mSupportedModes; -} - -DisplayModePtr DisplayDevice::getMode(DisplayModeId modeId) const { - const DisplayModePtr nullMode; - return mSupportedModes.get(modeId).value_or(std::cref(nullMode)); -} - -std::optional DisplayDevice::translateModeId(hal::HWConfigId hwcId) const { - const auto it = - std::find_if(mSupportedModes.begin(), mSupportedModes.end(), - [hwcId](const auto& pair) { return pair.second->getHwcId() == hwcId; }); - if (it != mSupportedModes.end()) { - return it->second->getId(); - } - return {}; -} - nsecs_t DisplayDevice::getVsyncPeriodFromHWC() const { const auto physicalId = getPhysicalId(); if (!mHwComposer.isConnected(physicalId)) { @@ -268,10 +248,10 @@ ui::Dataspace DisplayDevice::getCompositionDataSpace() const { return mCompositionDisplay->getState().dataspace; } -void DisplayDevice::setLayerStack(ui::LayerStack stack) { - mCompositionDisplay->setLayerFilter({stack, isInternal()}); +void DisplayDevice::setLayerFilter(ui::LayerFilter filter) { + mCompositionDisplay->setLayerFilter(filter); if (mRefreshRateOverlay) { - mRefreshRateOverlay->setLayerStack(stack); + mRefreshRateOverlay->setLayerStack(filter.layerStack); } } @@ -343,11 +323,7 @@ std::string DisplayDevice::getDebugName() const { std::string name = "Display "s + to_string(getId()) + " ("s; - if (mConnectionType) { - name += isInternal() ? "internal"s : "external"s; - } else { - name += "virtual"s; - } + name += isVirtual() ? "virtual"s : "physical"s; if (isPrimary()) { name += ", primary"s; @@ -361,15 +337,6 @@ void DisplayDevice::dump(std::string& result) const { result += getDebugName(); - if (!isVirtual()) { - result += "\n deviceProductInfo="s; - if (mDeviceProductInfo) { - mDeviceProductInfo->dump(result); - } else { - result += "{}"s; - } - } - result += "\n powerMode="s; result += mPowerMode.has_value() ? to_string(mPowerMode.value()) : "OFF(reset)"; result += '\n'; diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index 3eead178d3..d79a6b5f3d 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -65,6 +65,10 @@ class Display; class DisplaySurface; } // namespace compositionengine +namespace display { +class DisplaySnapshot; +} // namespace display + class DisplayDevice : public RefBase { public: constexpr static float sDefaultMinLumiance = 0.0; @@ -80,11 +84,8 @@ public: return mCompositionDisplay; } - std::optional getConnectionType() const { return mConnectionType; } - - bool isVirtual() const { return !mConnectionType; } + bool isVirtual() const { return VirtualDisplayId::tryCast(getId()).has_value(); } bool isPrimary() const { return mIsPrimary; } - bool isInternal() const { return mConnectionType == ui::DisplayConnectionType::Internal; } // isSecure indicates whether this display can be trusted to display // secure surfaces. @@ -94,7 +95,7 @@ public: int getHeight() const; ui::Size getSize() const { return {getWidth(), getHeight()}; } - void setLayerStack(ui::LayerStack); + void setLayerFilter(ui::LayerFilter); void setDisplaySize(int width, int height); void setProjection(ui::Rotation orientation, Rect viewport, Rect frame); void stageBrightness(float brightness) REQUIRES(kMainThreadContext); @@ -164,11 +165,6 @@ public: void setDisplayName(const std::string& displayName); const std::string& getDisplayName() const { return mDisplayName; } - void setDeviceProductInfo(std::optional info); - const std::optional& getDeviceProductInfo() const { - return mDeviceProductInfo; - } - struct InputInfo { gui::DisplayInfo info; ui::Transform transform; @@ -211,24 +207,14 @@ public: return mUpcomingActiveMode; } - void setActiveMode(DisplayModeId) REQUIRES(kMainThreadContext); + // Precondition: DisplaySnapshot must contain a mode with DisplayModeId. + void setActiveMode(DisplayModeId, const display::DisplaySnapshot&) REQUIRES(kMainThreadContext); + status_t initiateModeChange(const ActiveModeInfo&, const hal::VsyncPeriodChangeConstraints& constraints, hal::VsyncPeriodChangeTimeline* outTimeline) REQUIRES(kMainThreadContext); - // Return the immutable list of supported display modes. The HWC may report different modes - // after a hotplug reconnect event, in which case the DisplayDevice object will be recreated. - // Hotplug reconnects are common for external displays. - const DisplayModes& getSupportedModes() const; - - // Returns nullptr if the given mode ID is not supported. A previously - // supported mode may be no longer supported for some devices like TVs and - // set-top boxes after a hotplug reconnect. - DisplayModePtr getMode(DisplayModeId) const; - - std::optional translateModeId(hal::HWConfigId) const; - // Returns the refresh rate configs for this display. scheduler::RefreshRateConfigs& refreshRateConfigs() const { return *mRefreshRateConfigs; } @@ -267,7 +253,6 @@ private: HWComposer& mHwComposer; const wp mDisplayToken; const int32_t mSequenceId; - const std::optional mConnectionType; const std::shared_ptr mCompositionDisplay; @@ -285,7 +270,6 @@ private: DisplayModePtr mActiveMode; std::optional mStagedBrightness = std::nullopt; float mBrightness = -1.f; - const DisplayModes mSupportedModes; std::atomic mLastHwVsync = 0; @@ -294,8 +278,6 @@ private: uint32_t mFlags = 0; - std::optional mDeviceProductInfo; - std::vector mOverrideHdrTypes; std::shared_ptr mRefreshRateConfigs; @@ -313,14 +295,11 @@ private: struct DisplayDeviceState { struct Physical { PhysicalDisplayId id; - ui::DisplayConnectionType type; hardware::graphics::composer::hal::HWDisplayId hwcDisplayId; - std::optional deviceProductInfo; - DisplayModes supportedModes; DisplayModePtr activeMode; bool operator==(const Physical& other) const { - return id == other.id && type == other.type && hwcDisplayId == other.hwcDisplayId; + return id == other.id && hwcDisplayId == other.hwcDisplayId; } }; @@ -356,7 +335,6 @@ struct DisplayDeviceCreationArgs { std::shared_ptr refreshRateConfigs; int32_t sequenceId{0}; - std::optional connectionType; bool isSecure{false}; sp nativeWindow; sp displaySurface; @@ -367,7 +345,6 @@ struct DisplayDeviceCreationArgs { std::unordered_map> hwcColorModes; std::optional initialPowerMode; bool isPrimary{false}; - DisplayModes supportedModes; DisplayModeId activeModeId; }; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index cc93db3187..1bb81c555b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -53,9 +53,10 @@ #include #include #include +#include #include #include -#include +#include #include #include #include @@ -108,6 +109,7 @@ #include "BufferStateLayer.h" #include "Client.h" #include "Colorizer.h" +#include "Display/DisplayMap.h" #include "DisplayDevice.h" #include "DisplayHardware/ComposerHal.h" #include "DisplayHardware/FramebufferSurface.h" @@ -172,6 +174,8 @@ using CompositionStrategyPredictionState = android::compositionengine::impl:: OutputCompositionState::CompositionStrategyPredictionState; using base::StringAppendF; +using display::PhysicalDisplay; +using display::PhysicalDisplays; using gui::DisplayInfo; using gui::GameMode; using gui::IDisplayEventConnection; @@ -594,12 +598,12 @@ void SurfaceFlinger::releaseVirtualDisplay(VirtualDisplayId displayId) { std::vector SurfaceFlinger::getPhysicalDisplayIdsLocked() const { std::vector displayIds; - displayIds.reserve(mPhysicalDisplayTokens.size()); + displayIds.reserve(mPhysicalDisplays.size()); const auto defaultDisplayId = getDefaultDisplayDeviceLocked()->getPhysicalId(); displayIds.push_back(defaultDisplayId); - for (const auto& [id, token] : mPhysicalDisplayTokens) { + for (const auto& [id, display] : mPhysicalDisplays) { if (id != defaultDisplayId) { displayIds.push_back(id); } @@ -608,6 +612,12 @@ std::vector SurfaceFlinger::getPhysicalDisplayIdsLocked() con return displayIds; } +std::optional SurfaceFlinger::getPhysicalDisplayIdLocked( + const sp& displayToken) const { + return ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken)) + .transform(&ftl::to_key); +} + sp SurfaceFlinger::getPhysicalDisplayToken(PhysicalDisplayId displayId) const { Mutex::Autolock lock(mStateLock); return getPhysicalDisplayTokenLocked(displayId); @@ -932,16 +942,19 @@ status_t SurfaceFlinger::getStaticDisplayInfo(const sp& displayToken, Mutex::Autolock lock(mStateLock); - const auto display = getDisplayDeviceLocked(displayToken); - if (!display) { + const auto displayOpt = ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken)) + .transform(&ftl::to_mapped_ref) + .and_then(getDisplayDeviceAndSnapshot()); + + if (!displayOpt) { return NAME_NOT_FOUND; } - if (const auto connectionType = display->getConnectionType()) - info->connectionType = *connectionType; - else { - return INVALID_OPERATION; - } + const auto& [display, snapshotRef] = *displayOpt; + const auto& snapshot = snapshotRef.get(); + + info->connectionType = snapshot.connectionType(); + info->deviceProductInfo = snapshot.deviceProductInfo(); if (mEmulatedDisplayDensity) { info->density = mEmulatedDisplayDensity; @@ -953,7 +966,6 @@ status_t SurfaceFlinger::getStaticDisplayInfo(const sp& displayToken, info->density /= ACONFIGURATION_DENSITY_MEDIUM; info->secure = display->isSecure(); - info->deviceProductInfo = display->getDeviceProductInfo(); info->installOrientation = display->getPhysicalOrientation(); return NO_ERROR; @@ -967,23 +979,22 @@ status_t SurfaceFlinger::getDynamicDisplayInfo(const sp& displayToken, Mutex::Autolock lock(mStateLock); - const auto display = getDisplayDeviceLocked(displayToken); - if (!display) { + const auto displayOpt = ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken)) + .transform(&ftl::to_mapped_ref) + .and_then(getDisplayDeviceAndSnapshot()); + if (!displayOpt) { return NAME_NOT_FOUND; } - const auto displayId = PhysicalDisplayId::tryCast(display->getId()); - if (!displayId) { - return INVALID_OPERATION; - } + const auto& [display, snapshotRef] = *displayOpt; + const auto& snapshot = snapshotRef.get(); - info->activeDisplayModeId = display->getActiveMode()->getId().value(); + const auto& displayModes = snapshot.displayModes(); - const auto& supportedModes = display->getSupportedModes(); info->supportedDisplayModes.clear(); - info->supportedDisplayModes.reserve(supportedModes.size()); + info->supportedDisplayModes.reserve(displayModes.size()); - for (const auto& [id, mode] : supportedModes) { + for (const auto& [id, mode] : displayModes) { ui::DisplayMode outMode; outMode.id = static_cast(id.value()); @@ -1027,21 +1038,24 @@ status_t SurfaceFlinger::getDynamicDisplayInfo(const sp& displayToken, info->supportedDisplayModes.push_back(outMode); } + const PhysicalDisplayId displayId = snapshot.displayId(); + + info->activeDisplayModeId = display->getActiveMode()->getId().value(); info->activeColorMode = display->getCompositionDisplay()->getState().colorMode; - info->supportedColorModes = getDisplayColorModes(*display); + info->supportedColorModes = getDisplayColorModes(displayId); info->hdrCapabilities = display->getHdrCapabilities(); info->autoLowLatencyModeSupported = - getHwComposer().hasDisplayCapability(*displayId, + getHwComposer().hasDisplayCapability(displayId, DisplayCapability::AUTO_LOW_LATENCY_MODE); info->gameContentTypeSupported = - getHwComposer().supportsContentType(*displayId, hal::ContentType::GAME); + getHwComposer().supportsContentType(displayId, hal::ContentType::GAME); info->preferredBootDisplayMode = static_cast(-1); if (getHwComposer().hasCapability(Capability::BOOT_DISPLAY_CONFIG)) { - if (const auto hwcId = getHwComposer().getPreferredBootDisplayMode(*displayId)) { - if (const auto modeId = display->translateModeId(*hwcId)) { + if (const auto hwcId = getHwComposer().getPreferredBootDisplayMode(displayId)) { + if (const auto modeId = snapshot.translateModeId(*hwcId)) { info->preferredBootDisplayMode = modeId->value(); } } @@ -1089,39 +1103,44 @@ void SurfaceFlinger::setDesiredActiveMode(const ActiveModeInfo& info) { } } -status_t SurfaceFlinger::setActiveModeFromBackdoor(const sp& displayToken, int modeId) { +status_t SurfaceFlinger::setActiveModeFromBackdoor(const sp& displayToken, + DisplayModeId modeId) { ATRACE_CALL(); if (!displayToken) { return BAD_VALUE; } + const char* const whence = __func__; auto future = mScheduler->schedule([=]() -> status_t { - const auto display = FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(displayToken)); - if (!display) { - ALOGE("Attempt to set allowed display modes for invalid display token %p", - displayToken.get()); + const auto displayOpt = + FTL_FAKE_GUARD(mStateLock, + ftl::find_if(mPhysicalDisplays, + PhysicalDisplay::hasToken(displayToken)) + .transform(&ftl::to_mapped_ref) + .and_then(getDisplayDeviceAndSnapshot())); + if (!displayOpt) { + ALOGE("%s: Invalid physical display token %p", whence, displayToken.get()); return NAME_NOT_FOUND; } - if (display->isVirtual()) { - ALOGW("Attempt to set allowed display modes for virtual display"); - return INVALID_OPERATION; - } + const auto& [display, snapshotRef] = *displayOpt; + const auto& snapshot = snapshotRef.get(); + + const auto fpsOpt = snapshot.displayModes().get(modeId).transform( + [](const DisplayModePtr& mode) { return mode->getFps(); }); - const auto mode = display->getMode(DisplayModeId{modeId}); - if (!mode) { - ALOGW("Attempt to switch to an unsupported mode %d.", modeId); + if (!fpsOpt) { + ALOGE("%s: Invalid mode %d for display %s", whence, modeId.value(), + to_string(snapshot.displayId()).c_str()); return BAD_VALUE; } - const auto fps = mode->getFps(); + const Fps fps = *fpsOpt; // Keep the old switching type. - const auto allowGroupSwitching = + const bool allowGroupSwitching = display->refreshRateConfigs().getCurrentPolicy().allowGroupSwitching; - const scheduler::RefreshRateConfigs::Policy policy{mode->getId(), - allowGroupSwitching, - {fps, fps}}; + const scheduler::RefreshRateConfigs::Policy policy{modeId, allowGroupSwitching, {fps, fps}}; constexpr bool kOverridePolicy = false; return setDesiredDisplayModeSpecsInternal(display, policy, kOverridePolicy); @@ -1159,9 +1178,12 @@ void SurfaceFlinger::updateInternalStateWithChangedMode() { return; } - // We just created this display so we can call even if we are not on the main thread. - ftl::FakeGuard guard(kMainThreadContext); - display->setActiveMode(upcomingModeInfo.mode->getId()); + mPhysicalDisplays.get(display->getPhysicalId()) + .transform(&PhysicalDisplay::snapshotRef) + .transform(ftl::unit_fn([&](const display::DisplaySnapshot& snapshot) { + FTL_FAKE_GUARD(kMainThreadContext, + display->setActiveMode(upcomingModeInfo.mode->getId(), snapshot)); + })); const Fps refreshRate = upcomingModeInfo.mode->getFps(); mRefreshRateStats->setRefreshRate(refreshRate); @@ -1191,12 +1213,16 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { std::optional displayToUpdateImmediately; - for (const auto& iter : mDisplays) { - const auto& display = iter.second; - if (!display || !display->isInternal()) { + for (const auto& [id, physical] : mPhysicalDisplays) { + const auto& snapshot = physical.snapshot(); + + if (snapshot.connectionType() != ui::DisplayConnectionType::Internal) { continue; } + const auto display = getDisplayDeviceLocked(id); + if (!display) continue; + // Store the local variable to release the lock. const auto desiredActiveMode = display->getDesiredActiveMode(); if (!desiredActiveMode) { @@ -1210,20 +1236,23 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { continue; } - const auto desiredMode = display->getMode(desiredActiveMode->mode->getId()); - if (!desiredMode) { + const auto desiredModeId = desiredActiveMode->mode->getId(); + const auto refreshRateOpt = + snapshot.displayModes() + .get(desiredModeId) + .transform([](const DisplayModePtr& mode) { return mode->getFps(); }); + + if (!refreshRateOpt) { ALOGW("Desired display mode is no longer supported. Mode ID = %d", - desiredActiveMode->mode->getId().value()); + desiredModeId.value()); clearDesiredActiveModeState(display); continue; } - const auto refreshRate = desiredMode->getFps(); - ALOGV("%s changing active mode to %d(%s) for display %s", __func__, - desiredMode->getId().value(), to_string(refreshRate).c_str(), - to_string(display->getId()).c_str()); + ALOGV("%s changing active mode to %d(%s) for display %s", __func__, desiredModeId.value(), + to_string(*refreshRateOpt).c_str(), to_string(display->getId()).c_str()); - if (display->getActiveMode()->getId() == desiredActiveMode->mode->getId()) { + if (display->getActiveMode()->getId() == desiredModeId) { // we are already in the requested mode, there is nothing left to do desiredActiveModeChangeDone(display); continue; @@ -1232,8 +1261,7 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { // Desired active mode was set, it is different than the mode currently in use, however // allowed modes might have changed by the time we process the refresh. // Make sure the desired mode is still allowed - const auto displayModeAllowed = - display->refreshRateConfigs().isModeAllowed(desiredActiveMode->mode->getId()); + const auto displayModeAllowed = display->refreshRateConfigs().isModeAllowed(desiredModeId); if (!displayModeAllowed) { clearDesiredActiveModeState(display); continue; @@ -1295,15 +1323,18 @@ void SurfaceFlinger::disableExpensiveRendering() { future.wait(); } -std::vector SurfaceFlinger::getDisplayColorModes(const DisplayDevice& display) { - auto modes = getHwComposer().getColorModes(display.getPhysicalId()); +std::vector SurfaceFlinger::getDisplayColorModes(PhysicalDisplayId displayId) { + auto modes = getHwComposer().getColorModes(displayId); + + const bool isInternalDisplay = mPhysicalDisplays.get(displayId) + .transform(&PhysicalDisplay::isInternal) + .value_or(false); // If the display is internal and the configuration claims it's not wide color capable, // filter out all wide color modes. The typical reason why this happens is that the // hardware is not good enough to support GPU composition of wide color, and thus the // OEMs choose to disable this capability. - if (display.getConnectionType() == ui::DisplayConnectionType::Internal && - !hasWideColorDisplay) { + if (isInternalDisplay && !hasWideColorDisplay) { const auto newEnd = std::remove_if(modes.begin(), modes.end(), isWideColorMode); modes.erase(newEnd, modes.end()); } @@ -1319,13 +1350,13 @@ status_t SurfaceFlinger::getDisplayNativePrimaries(const sp& displayTok Mutex::Autolock lock(mStateLock); - const auto display = getDisplayDeviceLocked(displayToken); + const auto display = ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken)) + .transform(&ftl::to_mapped_ref); if (!display) { return NAME_NOT_FOUND; } - const auto connectionType = display->getConnectionType(); - if (connectionType != ui::DisplayConnectionType::Internal) { + if (!display.transform(&PhysicalDisplay::isInternal).value()) { return INVALID_OPERATION; } @@ -1353,7 +1384,7 @@ status_t SurfaceFlinger::setActiveColorMode(const sp& displayToken, Col return INVALID_OPERATION; } - const auto modes = getDisplayColorModes(*display); + const auto modes = getDisplayColorModes(display->getPhysicalId()); const bool exists = std::find(modes.begin(), modes.end(), mode) != modes.end(); if (mode < ColorMode::NATIVE || !exists) { @@ -1381,30 +1412,31 @@ status_t SurfaceFlinger::getBootDisplayModeSupport(bool* outSupport) const { return NO_ERROR; } -status_t SurfaceFlinger::setBootDisplayMode(const sp& displayToken, - ui::DisplayModeId modeId) { +status_t SurfaceFlinger::setBootDisplayMode(const sp& displayToken, + DisplayModeId modeId) { const char* const whence = __func__; auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t { - const auto display = getDisplayDeviceLocked(displayToken); - if (!display) { - ALOGE("%s: Invalid display token %p", whence, displayToken.get()); + const auto snapshotOpt = + ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken)) + .transform(&ftl::to_mapped_ref) + .transform(&PhysicalDisplay::snapshotRef); + + if (!snapshotOpt) { + ALOGE("%s: Invalid physical display token %p", whence, displayToken.get()); return NAME_NOT_FOUND; } - if (display->isVirtual()) { - ALOGE("%s: Invalid operation on virtual display", whence); - return INVALID_OPERATION; - } + const auto& snapshot = snapshotOpt->get(); + const auto hwcIdOpt = snapshot.displayModes().get(modeId).transform( + [](const DisplayModePtr& mode) { return mode->getHwcId(); }); - const auto displayId = display->getPhysicalId(); - const auto mode = display->getMode(DisplayModeId{modeId}); - if (!mode) { - ALOGE("%s: Invalid mode %d for display %s", whence, modeId, - to_string(displayId).c_str()); + if (!hwcIdOpt) { + ALOGE("%s: Invalid mode %d for display %s", whence, modeId.value(), + to_string(snapshot.displayId()).c_str()); return BAD_VALUE; } - return getHwComposer().setBootDisplayMode(displayId, mode->getHwcId()); + return getHwComposer().setBootDisplayMode(snapshot.displayId(), *hwcIdOpt); }); return future.get(); } @@ -2467,7 +2499,13 @@ void SurfaceFlinger::postComposition() { mTimeStats->incrementTotalFrames(); mTimeStats->setPresentFenceGlobal(presentFenceTime); - if (display && display->isInternal() && display->getPowerMode() == hal::PowerMode::ON && + const bool isInternalDisplay = display && + FTL_FAKE_GUARD(mStateLock, mPhysicalDisplays) + .get(display->getPhysicalId()) + .transform(&PhysicalDisplay::isInternal) + .value_or(false); + + if (isInternalDisplay && display && display->getPowerMode() == hal::PowerMode::ON && presentFenceTime->isValid()) { mScheduler->addPresentFence(std::move(presentFenceTime)); } @@ -2601,10 +2639,11 @@ std::pair SurfaceFlinger::loadDisplayModes( return {}; } - DisplayModes oldModes; - if (const auto token = getPhysicalDisplayTokenLocked(displayId)) { - oldModes = getDisplayDeviceLocked(token)->getSupportedModes(); - } + const DisplayModes oldModes = mPhysicalDisplays.get(displayId) + .transform([](const PhysicalDisplay& display) { + return display.snapshot().displayModes(); + }) + .value_or(DisplayModes{}); ui::DisplayModeId nextModeId = 1 + std::accumulate(oldModes.begin(), oldModes.end(), static_cast(-1), @@ -2669,21 +2708,22 @@ bool SurfaceFlinger::configureLocked() { const char* SurfaceFlinger::processHotplug(PhysicalDisplayId displayId, hal::HWDisplayId hwcDisplayId, bool connected, DisplayIdentificationInfo&& info) { - const auto tokenOpt = mPhysicalDisplayTokens.get(displayId); + const auto displayOpt = mPhysicalDisplays.get(displayId); if (!connected) { - LOG_ALWAYS_FATAL_IF(!tokenOpt); + LOG_ALWAYS_FATAL_IF(!displayOpt); + const auto& display = displayOpt->get(); - if (const ssize_t index = mCurrentState.displays.indexOfKey(tokenOpt->get()); index >= 0) { + if (const ssize_t index = mCurrentState.displays.indexOfKey(display.token()); index >= 0) { const DisplayDeviceState& state = mCurrentState.displays.valueAt(index); mInterceptor->saveDisplayDeletion(state.sequenceId); mCurrentState.displays.removeItemsAt(index); } - mPhysicalDisplayTokens.erase(displayId); + mPhysicalDisplays.erase(displayId); return "Disconnecting"; } - auto [supportedModes, activeMode] = loadDisplayModes(displayId); + auto [displayModes, activeMode] = loadDisplayModes(displayId); if (!activeMode) { // TODO(b/241286153): Report hotplug failure to the framework. ALOGE("Failed to hotplug display %s", to_string(displayId).c_str()); @@ -2691,30 +2731,42 @@ const char* SurfaceFlinger::processHotplug(PhysicalDisplayId displayId, return nullptr; } - if (tokenOpt) { - auto& state = mCurrentState.displays.editValueFor(tokenOpt->get()); - state.sequenceId = DisplayDeviceState{}.sequenceId; // Generate new sequenceId. - state.physical->supportedModes = std::move(supportedModes); - state.physical->activeMode = std::move(activeMode); + if (displayOpt) { + const auto& display = displayOpt->get(); + const auto& snapshot = display.snapshot(); + + std::optional deviceProductInfo; if (getHwComposer().updatesDeviceProductInfoOnHotplugReconnect()) { - state.physical->deviceProductInfo = std::move(info.deviceProductInfo); + deviceProductInfo = std::move(info.deviceProductInfo); + } else { + deviceProductInfo = snapshot.deviceProductInfo(); } + + const auto it = + mPhysicalDisplays.try_replace(displayId, display.token(), displayId, + snapshot.connectionType(), std::move(displayModes), + std::move(deviceProductInfo)); + + auto& state = mCurrentState.displays.editValueFor(it->second.token()); + state.sequenceId = DisplayDeviceState{}.sequenceId; // Generate new sequenceId. + state.physical->activeMode = std::move(activeMode); return "Reconnecting"; } + const sp token = sp::make(); + + mPhysicalDisplays.try_emplace(displayId, token, displayId, + getHwComposer().getDisplayConnectionType(displayId), + std::move(displayModes), std::move(info.deviceProductInfo)); + DisplayDeviceState state; state.physical = {.id = displayId, - .type = getHwComposer().getDisplayConnectionType(displayId), .hwcDisplayId = hwcDisplayId, - .deviceProductInfo = std::move(info.deviceProductInfo), - .supportedModes = std::move(supportedModes), .activeMode = std::move(activeMode)}; state.isSecure = true; // All physical displays are currently considered secure. state.displayName = std::move(info.name); - sp token = sp::make(); mCurrentState.displays.add(token, state); - mPhysicalDisplayTokens.try_emplace(displayId, std::move(token)); mInterceptor->saveDisplayCreation(state); return "Connecting"; } @@ -2739,8 +2791,6 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( creationArgs.supportedPerFrameMetadata = 0; if (const auto& physical = state.physical) { - creationArgs.connectionType = physical->type; - creationArgs.supportedModes = physical->supportedModes; creationArgs.activeModeId = physical->activeMode->getId(); const auto [kernelIdleTimerController, idleTimerTimeoutMs] = getKernelIdleTimerProperties(compositionDisplay->getId()); @@ -2751,9 +2801,17 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( base::GetIntProperty("debug.sf.frame_rate_multiple_threshold", 0), .idleTimerTimeout = idleTimerTimeoutMs, .kernelIdleTimerController = kernelIdleTimerController}; + creationArgs.refreshRateConfigs = - std::make_shared(creationArgs.supportedModes, - creationArgs.activeModeId, config); + mPhysicalDisplays.get(physical->id) + .transform(&PhysicalDisplay::snapshotRef) + .transform([&](const display::DisplaySnapshot& snapshot) { + return std::make_shared< + scheduler::RefreshRateConfigs>(snapshot.displayModes(), + creationArgs.activeModeId, + config); + }) + .value_or(nullptr); } if (const auto id = PhysicalDisplayId::tryCast(compositionDisplay->getId())) { @@ -2811,13 +2869,17 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( compositionengine::Output::ColorProfile{defaultColorMode, defaultDataSpace, RenderIntent::COLORIMETRIC, Dataspace::UNKNOWN}); - if (!state.isVirtual()) { - FTL_FAKE_GUARD(kMainThreadContext, - display->setActiveMode(state.physical->activeMode->getId())); - display->setDeviceProductInfo(state.physical->deviceProductInfo); + + if (const auto& physical = state.physical) { + mPhysicalDisplays.get(physical->id) + .transform(&PhysicalDisplay::snapshotRef) + .transform(ftl::unit_fn([&](const display::DisplaySnapshot& snapshot) { + FTL_FAKE_GUARD(kMainThreadContext, + display->setActiveMode(physical->activeMode->getId(), snapshot)); + })); } - display->setLayerStack(state.layerStack); + display->setLayerFilter(makeLayerFilterForDisplay(display->getId(), state.layerStack)); display->setProjection(state.orientation, state.layerStackSpaceRect, state.orientedDisplaySpaceRect); display->setDisplayName(state.displayName); @@ -2973,7 +3035,8 @@ void SurfaceFlinger::processDisplayChanged(const wp& displayToken, if (const auto display = getDisplayDeviceLocked(displayToken)) { if (currentState.layerStack != drawingState.layerStack) { - display->setLayerStack(currentState.layerStack); + display->setLayerFilter( + makeLayerFilterForDisplay(display->getId(), currentState.layerStack)); } if (currentState.flags != drawingState.flags) { display->setFlags(currentState.flags); @@ -3219,7 +3282,7 @@ void SurfaceFlinger::persistDisplayBrightness(bool needsComposite) { void SurfaceFlinger::buildWindowInfos(std::vector& outWindowInfos, std::vector& outDisplayInfos) { - ftl::SmallMap displayInputInfos; + display::DisplayMap displayInputInfos; for (const auto& [_, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) { const auto layerStack = display->getLayerStack(); @@ -4718,8 +4781,12 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: return; } + const bool isInternalDisplay = mPhysicalDisplays.get(displayId) + .transform(&PhysicalDisplay::isInternal) + .value_or(false); + const auto activeDisplay = getDisplayDeviceLocked(mActiveDisplayToken); - if (activeDisplay != display && display->isInternal() && activeDisplay && + if (isInternalDisplay && activeDisplay != display && activeDisplay && activeDisplay->isPoweredOn()) { ALOGW("Trying to change power mode on non active display while the active display is ON"); } @@ -4732,7 +4799,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: const auto refreshRate = display->refreshRateConfigs().getActiveMode()->getFps(); if (*currentMode == hal::PowerMode::OFF) { // Turn on the display - if (display->isInternal() && (!activeDisplay || !activeDisplay->isPoweredOn())) { + if (isInternalDisplay && (!activeDisplay || !activeDisplay->isPoweredOn())) { onActiveDisplayChangedLocked(display); } // Keep uclamp in a separate syscall and set it before changing to RT due to b/190237315. @@ -4991,10 +5058,20 @@ void SurfaceFlinger::dumpCompositionDisplays(std::string& result) const { } void SurfaceFlinger::dumpDisplays(std::string& result) const { - for (const auto& [token, display] : mDisplays) { - display->dump(result); + for (const auto& [id, display] : mPhysicalDisplays) { + if (const auto device = getDisplayDeviceLocked(id)) { + device->dump(result); + } + display.snapshot().dump(result); result += '\n'; } + + for (const auto& [token, display] : mDisplays) { + if (display->isVirtual()) { + display->dump(result); + result += '\n'; + } + } } void SurfaceFlinger::dumpDisplayIdentificationData(std::string& result) const { @@ -5793,7 +5870,7 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r }(); mDebugDisplayModeSetByBackdoor = false; - const status_t result = setActiveModeFromBackdoor(display, modeId); + const status_t result = setActiveModeFromBackdoor(display, DisplayModeId{modeId}); mDebugDisplayModeSetByBackdoor = result == NO_ERROR; return result; } @@ -6700,15 +6777,28 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( mScheduler->onNonPrimaryDisplayModeChanged(mAppConnectionHandle, activeMode); } - const DisplayModePtr preferredDisplayMode = [&] { - const auto schedulerMode = mScheduler->getPreferredDisplayMode(); - if (schedulerMode && schedulerMode->getPhysicalDisplayId() == display->getPhysicalId()) { + const DisplayModePtr preferredDisplayMode = [&]() REQUIRES(mStateLock) -> DisplayModePtr { + const auto displayId = display->getPhysicalId(); + + if (const auto schedulerMode = mScheduler->getPreferredDisplayMode(); + schedulerMode && schedulerMode->getPhysicalDisplayId() == displayId) { return schedulerMode; } - return display->getMode(currentPolicy.defaultMode); + const DisplayModePtr nullMode; + return mPhysicalDisplays.get(displayId) + .transform(&PhysicalDisplay::snapshotRef) + .and_then([&](const display::DisplaySnapshot& snapshot) { + return snapshot.displayModes().get(currentPolicy.defaultMode); + }) + .value_or(std::cref(nullMode)); }(); + if (!preferredDisplayMode) { + ALOGE("%s: Preferred mode is unknown", __func__); + return NAME_NOT_FOUND; + } + ALOGV("trying to switch to Scheduler preferred mode %d (%s)", preferredDisplayMode->getId().value(), to_string(preferredDisplayMode->getFps()).c_str()); @@ -6879,9 +6969,11 @@ status_t SurfaceFlinger::setOverrideFrameRate(uid_t uid, float frameRate) { } void SurfaceFlinger::enableRefreshRateOverlay(bool enable) { - for (const auto& [ignored, display] : mDisplays) { - if (display->isInternal()) { - display->enableRefreshRateOverlay(enable, mRefreshRateOverlaySpinner); + for (const auto& [id, display] : mPhysicalDisplays) { + if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal) { + if (const auto device = getDisplayDeviceLocked(id)) { + device->enableRefreshRateOverlay(enable, mRefreshRateOverlaySpinner); + } } } } @@ -7367,8 +7459,7 @@ binder::Status SurfaceComposerAIDL::setBootDisplayMode(const sp& displa int displayModeId) { status_t status = checkAccessPermission(); if (status == OK) { - status = mFlinger->setBootDisplayMode(display, - static_cast(displayModeId)); + status = mFlinger->setBootDisplayMode(display, DisplayModeId{displayModeId}); } return binderStatusFromStatusT(status); } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 310188512d..e9e13ef686 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -59,6 +58,8 @@ #include #include "ClientCache.h" +#include "Display/DisplayMap.h" +#include "Display/PhysicalDisplay.h" #include "DisplayDevice.h" #include "DisplayHardware/HWC2.h" #include "DisplayHardware/PowerAdvisor.h" @@ -515,7 +516,7 @@ private: status_t getDisplayNativePrimaries(const sp& displayToken, ui::DisplayPrimaries&); status_t setActiveColorMode(const sp& displayToken, ui::ColorMode colorMode); status_t getBootDisplayModeSupport(bool* outSupport) const; - status_t setBootDisplayMode(const sp& displayToken, ui::DisplayModeId id); + status_t setBootDisplayMode(const sp&, DisplayModeId); status_t clearBootDisplayMode(const sp& displayToken); void setAutoLowLatencyMode(const sp& displayToken, bool on); void setGameContentType(const sp& displayToken, bool on); @@ -651,7 +652,7 @@ private: void onInitializeDisplays() REQUIRES(mStateLock); // Sets the desired active mode bit. It obtains the lock, and sets mDesiredActiveMode. void setDesiredActiveMode(const ActiveModeInfo& info) REQUIRES(mStateLock); - status_t setActiveModeFromBackdoor(const sp& displayToken, int id); + status_t setActiveModeFromBackdoor(const sp&, DisplayModeId); // Sets the active mode and a new refresh rate in SF. void updateInternalStateWithChangedMode() REQUIRES(mStateLock); // Calls to setActiveMode on the main thread if there is a pending mode change @@ -820,6 +821,10 @@ private: // called when starting, or restarting after system_server death void initializeDisplays(); + bool isDisplayActiveLocked(const sp& display) const REQUIRES(mStateLock) { + return display->getDisplayToken() == mActiveDisplayToken; + } + sp getDisplayDeviceLocked(const wp& displayToken) const REQUIRES(mStateLock) { return const_cast(this)->getDisplayDeviceLocked(displayToken); @@ -867,6 +872,21 @@ private: return getDefaultDisplayDeviceLocked(); } + using DisplayDeviceAndSnapshot = + std::pair, display::PhysicalDisplay::SnapshotRef>; + + // Combinator for ftl::Optional::and_then. + auto getDisplayDeviceAndSnapshot() REQUIRES(mStateLock) { + return [this](const display::PhysicalDisplay& display) REQUIRES( + mStateLock) -> ftl::Optional { + if (auto device = getDisplayDeviceLocked(display.snapshot().displayId())) { + return std::make_pair(std::move(device), display.snapshotRef()); + } + + return {}; + }; + } + // Returns the first display that matches a `bool(const DisplayDevice&)` predicate. template sp findDisplay(Predicate p) const REQUIRES(mStateLock) { @@ -882,8 +902,13 @@ private: // region of all screens presenting this layer stack. void invalidateLayerStack(const sp& layer, const Region& dirty); - bool isDisplayActiveLocked(const sp& display) const REQUIRES(mStateLock) { - return display->getDisplayToken() == mActiveDisplayToken; + ui::LayerFilter makeLayerFilterForDisplay(DisplayId displayId, ui::LayerStack layerStack) + REQUIRES(mStateLock) { + return {layerStack, + PhysicalDisplayId::tryCast(displayId) + .and_then(display::getPhysicalDisplay(mPhysicalDisplays)) + .transform(&display::PhysicalDisplay::isInternal) + .value_or(false)}; } /* @@ -959,21 +984,16 @@ private: /* * Display identification */ - sp getPhysicalDisplayTokenLocked(PhysicalDisplayId displayId) const + sp getPhysicalDisplayTokenLocked(PhysicalDisplayId displayId) const REQUIRES(mStateLock) { - const sp nullToken; - return mPhysicalDisplayTokens.get(displayId).value_or(std::cref(nullToken)); + const sp nullToken; + return mPhysicalDisplays.get(displayId) + .transform([](const display::PhysicalDisplay& display) { return display.token(); }) + .value_or(std::cref(nullToken)); } std::optional getPhysicalDisplayIdLocked( - const sp& displayToken) const REQUIRES(mStateLock) { - for (const auto& [id, token] : mPhysicalDisplayTokens) { - if (token == displayToken) { - return id; - } - } - return {}; - } + const sp&) const REQUIRES(mStateLock); // Returns the first display connected at boot. // @@ -1063,7 +1083,7 @@ private: /* * Misc */ - std::vector getDisplayColorModes(const DisplayDevice&) REQUIRES(mStateLock); + std::vector getDisplayColorModes(PhysicalDisplayId) REQUIRES(mStateLock); static int calculateMaxAcquiredBufferCount(Fps refreshRate, std::chrono::nanoseconds presentLatency); @@ -1164,12 +1184,10 @@ private: // Displays are composited in `mDisplays` order. Internal displays are inserted at boot and // never removed, so take precedence over external and virtual displays. // - // The static capacities were chosen to exceed a typical number of physical/virtual displays. - // // May be read from any thread, but must only be written from the main thread. - ftl::SmallMap, const sp, 5> mDisplays GUARDED_BY(mStateLock); - ftl::SmallMap, 3> mPhysicalDisplayTokens - GUARDED_BY(mStateLock); + display::DisplayMap, const sp> mDisplays GUARDED_BY(mStateLock); + + display::PhysicalDisplays mPhysicalDisplays GUARDED_BY(mStateLock); struct { DisplayIdGenerator gpu; diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 77625b36ef..e546c2f8c9 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -309,16 +309,20 @@ struct BaseDisplayVariant { compositionengine::impl::createDisplay(test->mFlinger.getCompositionEngine(), ceDisplayArgs); + constexpr auto kDisplayConnectionType = ui::DisplayConnectionType::Internal; + constexpr bool kIsPrimary = true; + test->mDisplay = FakeDisplayDeviceInjector(test->mFlinger, compositionDisplay, - ui::DisplayConnectionType::Internal, HWC_DISPLAY, - true /* isPrimary */) + kDisplayConnectionType, HWC_DISPLAY, kIsPrimary) .setDisplaySurface(test->mDisplaySurface) .setNativeWindow(test->mNativeWindow) .setSecure(Derived::IS_SECURE) .setPowerMode(Derived::INIT_POWER_MODE) .inject(); Mock::VerifyAndClear(test->mNativeWindow.get()); - test->mDisplay->setLayerStack(LAYER_STACK); + + constexpr bool kIsInternal = kDisplayConnectionType == ui::DisplayConnectionType::Internal; + test->mDisplay->setLayerFilter({LAYER_STACK, kIsInternal}); } template diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp index 71f1a2bb35..73f654ba87 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp @@ -118,9 +118,7 @@ void DisplayTransactionCommitTest::verifyDisplayIsConnected(const sp& d ASSERT_TRUE(displayId); const auto hwcDisplayId = Case::Display::HWC_DISPLAY_ID_OPT::value; ASSERT_TRUE(hwcDisplayId); - expectedPhysical = {.id = *displayId, - .type = *connectionType, - .hwcDisplayId = *hwcDisplayId}; + expectedPhysical = {.id = *displayId, .hwcDisplayId = *hwcDisplayId}; } // The display should have been set up in the current display state @@ -145,10 +143,13 @@ void DisplayTransactionCommitTest::verifyPhysicalDisplayIsConnected() { const auto displayId = Case::Display::DISPLAY_ID::get(); ASSERT_TRUE(PhysicalDisplayId::tryCast(displayId)); - const auto displayTokenOpt = mFlinger.mutablePhysicalDisplayTokens().get(displayId); - ASSERT_TRUE(displayTokenOpt); + const auto displayOpt = mFlinger.mutablePhysicalDisplays().get(displayId); + ASSERT_TRUE(displayOpt); - verifyDisplayIsConnected(displayTokenOpt->get()); + const auto& display = displayOpt->get(); + EXPECT_EQ(Case::Display::CONNECTION_TYPE::value, display.snapshot().connectionType()); + + verifyDisplayIsConnected(display.token()); } void DisplayTransactionCommitTest::verifyDisplayIsNotConnected(const sp& displayToken) { @@ -247,10 +248,10 @@ void DisplayTransactionCommitTest::processesHotplugDisconnectCommon() { // HWComposer should not have an entry for the display EXPECT_FALSE(hasPhysicalHwcDisplay(Case::Display::HWC_DISPLAY_ID)); - // SF should not have a display token. + // SF should not have a PhysicalDisplay. const auto displayId = Case::Display::DISPLAY_ID::get(); ASSERT_TRUE(PhysicalDisplayId::tryCast(displayId)); - ASSERT_FALSE(mFlinger.mutablePhysicalDisplayTokens().contains(displayId)); + ASSERT_FALSE(mFlinger.mutablePhysicalDisplays().contains(displayId)); // The existing token should have been removed. verifyDisplayIsNotConnected(existing.token()); @@ -329,10 +330,10 @@ TEST_F(DisplayTransactionCommitTest, processesHotplugConnectThenDisconnectPrimar // HWComposer should not have an entry for the display EXPECT_FALSE(hasPhysicalHwcDisplay(Case::Display::HWC_DISPLAY_ID)); - // SF should not have a display token. + // SF should not have a PhysicalDisplay. const auto displayId = Case::Display::DISPLAY_ID::get(); ASSERT_TRUE(PhysicalDisplayId::tryCast(displayId)); - ASSERT_FALSE(mFlinger.mutablePhysicalDisplayTokens().contains(displayId)); + ASSERT_FALSE(mFlinger.mutablePhysicalDisplays().contains(displayId)); }(), testing::KilledBySignal(SIGABRT), "Primary display cannot be disconnected."); } @@ -376,9 +377,9 @@ TEST_F(DisplayTransactionCommitTest, processesHotplugDisconnectThenConnectPrimar const auto displayId = Case::Display::DISPLAY_ID::get(); ASSERT_TRUE(PhysicalDisplayId::tryCast(displayId)); - const auto displayTokenOpt = mFlinger.mutablePhysicalDisplayTokens().get(displayId); - ASSERT_TRUE(displayTokenOpt); - EXPECT_NE(existing.token(), displayTokenOpt->get()); + const auto displayOpt = mFlinger.mutablePhysicalDisplays().get(displayId); + ASSERT_TRUE(displayOpt); + EXPECT_NE(existing.token(), displayOpt->get().token()); // A new display should be connected in its place. verifyPhysicalDisplayIsConnected(); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp index 1756368216..9e54083615 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp @@ -253,14 +253,16 @@ struct DisplayPowerCase { using DispSync = DispSyncVariant; using Transition = TransitionVariant; - static auto injectDisplayWithInitialPowerMode(DisplayTransactionTest* test, PowerMode mode) { + static sp injectDisplayWithInitialPowerMode(DisplayTransactionTest* test, + PowerMode mode) { Display::injectHwcDisplayWithNoDefaultCapabilities(test); - auto display = Display::makeFakeExistingDisplayInjector(test); - display.inject(); - display.mutableDisplayDevice()->setPowerMode(mode); - if (display.mutableDisplayDevice()->isInternal()) { - test->mFlinger.mutableActiveDisplayToken() = - display.mutableDisplayDevice()->getDisplayToken(); + auto injector = Display::makeFakeExistingDisplayInjector(test); + const auto display = injector.inject(); + display->setPowerMode(mode); + if (injector.physicalDisplay() + .transform(&display::PhysicalDisplay::isInternal) + .value_or(false)) { + test->mFlinger.mutableActiveDisplayToken() = display->getDisplayToken(); } return display; @@ -353,8 +355,7 @@ void SetPowerModeInternalTest::transitionDisplayCommon() { // -------------------------------------------------------------------- // Invocation - mFlinger.setPowerModeInternal(display.mutableDisplayDevice(), - Case::Transition::TARGET_POWER_MODE); + mFlinger.setPowerModeInternal(display, Case::Transition::TARGET_POWER_MODE); // -------------------------------------------------------------------- // Postconditions diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp index 6aeb3feb9a..ec2c2b4358 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp @@ -246,11 +246,14 @@ void SetupNewDisplayDeviceInternalTest::setupNewDisplayDeviceInternalTest() { .setDpiY(DEFAULT_DPI) .setGroup(0) .build(); + state.physical = {.id = *displayId, - .type = *connectionType, .hwcDisplayId = *hwcDisplayId, - .supportedModes = makeModes(activeMode), - .activeMode = std::move(activeMode)}; + .activeMode = activeMode}; + + mFlinger.mutablePhysicalDisplays().emplace_or_replace(*displayId, displayToken, *displayId, + *connectionType, + makeModes(activeMode), std::nullopt); } state.isSecure = static_cast(Case::Display::SECURE); @@ -264,7 +267,6 @@ void SetupNewDisplayDeviceInternalTest::setupNewDisplayDeviceInternalTest() { ASSERT_TRUE(device != nullptr); EXPECT_EQ(Case::Display::DISPLAY_ID::get(), device->getId()); - EXPECT_EQ(Case::Display::CONNECTION_TYPE::value, device->getConnectionType()); EXPECT_EQ(static_cast(Case::Display::VIRTUAL), device->isVirtual()); EXPECT_EQ(static_cast(Case::Display::SECURE), device->isSecure()); EXPECT_EQ(static_cast(Case::Display::PRIMARY), device->isPrimary()); @@ -280,7 +282,6 @@ void SetupNewDisplayDeviceInternalTest::setupNewDisplayDeviceInternalTest() { device->receivesInput()); if constexpr (Case::Display::CONNECTION_TYPE::value) { - EXPECT_EQ(1, device->getSupportedModes().size()); EXPECT_NE(nullptr, device->getActiveMode()); EXPECT_EQ(Case::Display::HWC_ACTIVE_CONFIG_ID, device->getActiveMode()->getHwcId()); } diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 6c6c9aa684..a6b3f7c5df 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -503,6 +503,7 @@ public: */ const auto& displays() const { return mFlinger->mDisplays; } + const auto& physicalDisplays() const { return mFlinger->mPhysicalDisplays; } const auto& currentState() const { return mFlinger->mCurrentState; } const auto& drawingState() const { return mFlinger->mDrawingState; } const auto& transactionFlags() const { return mFlinger->mTransactionFlags; } @@ -515,12 +516,12 @@ public: auto& mutableCurrentState() { return mFlinger->mCurrentState; } auto& mutableDisplayColorSetting() { return mFlinger->mDisplayColorSetting; } auto& mutableDisplays() { return mFlinger->mDisplays; } + auto& mutablePhysicalDisplays() { return mFlinger->mPhysicalDisplays; } auto& mutableDrawingState() { return mFlinger->mDrawingState; } auto& mutableGeometryDirty() { return mFlinger->mGeometryDirty; } auto& mutableInterceptor() { return mFlinger->mInterceptor; } auto& mutableMainThreadId() { return mFlinger->mMainThreadId; } auto& mutablePendingHotplugEvents() { return mFlinger->mPendingHotplugEvents; } - auto& mutablePhysicalDisplayTokens() { return mFlinger->mPhysicalDisplayTokens; } auto& mutableTexturePool() { return mFlinger->mTexturePool; } auto& mutableTransactionFlags() { return mFlinger->mTransactionFlags; } auto& mutableDebugDisableHWC() { return mFlinger->mDebugDisableHWC; } @@ -725,14 +726,20 @@ public: : mFlinger(flinger), mCreationArgs(flinger.mFlinger, flinger.mFlinger->getHwComposer(), mDisplayToken, display), + mConnectionType(connectionType), mHwcDisplayId(hwcDisplayId) { - mCreationArgs.connectionType = connectionType; mCreationArgs.isPrimary = isPrimary; mCreationArgs.initialPowerMode = hal::PowerMode::ON; } sp token() const { return mDisplayToken; } + auto physicalDisplay() const { + return ftl::Optional(mCreationArgs.compositionDisplay->getDisplayId()) + .and_then(&PhysicalDisplayId::tryCast) + .and_then(display::getPhysicalDisplay(mFlinger.physicalDisplays())); + } + DisplayDeviceState& mutableDrawingDisplayState() { return mFlinger.mutableDrawingState().displays.editValueFor(mDisplayToken); } @@ -760,7 +767,7 @@ public: // the `configs` parameter in favor of an alternative setRefreshRateConfigs API. auto& setDisplayModes(DisplayModes modes, DisplayModeId activeModeId, std::shared_ptr configs = nullptr) { - mCreationArgs.supportedModes = std::move(modes); + mDisplayModes = std::move(modes); mCreationArgs.activeModeId = activeModeId; mCreationArgs.refreshRateConfigs = std::move(configs); return *this; @@ -806,7 +813,7 @@ public: sp inject() NO_THREAD_SAFETY_ANALYSIS { const auto displayId = mCreationArgs.compositionDisplay->getDisplayId(); - auto& modes = mCreationArgs.supportedModes; + auto& modes = mDisplayModes; auto& activeModeId = mCreationArgs.activeModeId; if (displayId && !mCreationArgs.refreshRateConfigs) { @@ -834,8 +841,13 @@ public: } } + sp display = sp::make(mCreationArgs); + mFlinger.mutableDisplays().emplace_or_replace(mDisplayToken, display); + DisplayDeviceState state; - if (const auto type = mCreationArgs.connectionType) { + state.isSecure = mCreationArgs.isSecure; + + if (mConnectionType) { LOG_ALWAYS_FATAL_IF(!displayId); const auto physicalId = PhysicalDisplayId::tryCast(*displayId); LOG_ALWAYS_FATAL_IF(!physicalId); @@ -845,29 +857,21 @@ public: LOG_ALWAYS_FATAL_IF(!activeMode); state.physical = {.id = *physicalId, - .type = *type, .hwcDisplayId = *mHwcDisplayId, - .deviceProductInfo = {}, - .supportedModes = modes, .activeMode = activeMode->get()}; - } - state.isSecure = mCreationArgs.isSecure; + const auto it = mFlinger.mutablePhysicalDisplays() + .emplace_or_replace(*physicalId, mDisplayToken, *physicalId, + *mConnectionType, std::move(modes), + std::nullopt) + .first; - sp display = sp::make(mCreationArgs); - if (!display->isVirtual()) { - display->setActiveMode(activeModeId); + display->setActiveMode(activeModeId, it->second.snapshot()); } - mFlinger.mutableDisplays().emplace_or_replace(mDisplayToken, display); mFlinger.mutableCurrentState().displays.add(mDisplayToken, state); mFlinger.mutableDrawingState().displays.add(mDisplayToken, state); - if (const auto& physical = state.physical) { - mFlinger.mutablePhysicalDisplayTokens().emplace_or_replace(physical->id, - mDisplayToken); - } - return display; } @@ -875,6 +879,8 @@ public: TestableSurfaceFlinger& mFlinger; sp mDisplayToken = sp::make(); DisplayDeviceCreationArgs mCreationArgs; + DisplayModes mDisplayModes; + const std::optional mConnectionType; const std::optional mHwcDisplayId; }; -- GitLab From f2595446af5d9bf2ca4b2e5bf07dfbbd842e61ec Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Mon, 29 Aug 2022 13:51:57 -0700 Subject: [PATCH 0272/1310] SF: Extract getPreferredDisplayMode Instead of aborting, error out from setDesiredDisplayModeSpecs if the display or mode cannot be found (which could happen if the display is disconnected) or if the mode is disallowed. Bug: 241285876 Test: Refresh rate switching still works Change-Id: Ifd7fdce0da596474440eb5195ab3ef3340772a05 --- services/surfaceflinger/SurfaceFlinger.cpp | 55 +++++++++++----------- services/surfaceflinger/SurfaceFlinger.h | 6 +++ 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 1bb81c555b..ca578e140d 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -6744,6 +6744,20 @@ void SurfaceFlinger::traverseLayersInLayerStack(ui::LayerStack layerStack, const } } +std::optional SurfaceFlinger::getPreferredDisplayMode( + PhysicalDisplayId displayId, DisplayModeId defaultModeId) const { + if (const auto schedulerMode = mScheduler->getPreferredDisplayMode(); + schedulerMode && schedulerMode->getPhysicalDisplayId() == displayId) { + return schedulerMode; + } + + return mPhysicalDisplays.get(displayId) + .transform(&PhysicalDisplay::snapshotRef) + .and_then([&](const display::DisplaySnapshot& snapshot) { + return snapshot.displayModes().get(defaultModeId); + }); +} + status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( const sp& display, const std::optional& policy, bool overridePolicy) { @@ -6762,7 +6776,7 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( return NO_ERROR; } - scheduler::RefreshRateConfigs::Policy currentPolicy = + const scheduler::RefreshRateConfigs::Policy currentPolicy = display->refreshRateConfigs().getCurrentPolicy(); ALOGV("Setting desired display mode specs: %s", currentPolicy.toString().c_str()); @@ -6777,40 +6791,25 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( mScheduler->onNonPrimaryDisplayModeChanged(mAppConnectionHandle, activeMode); } - const DisplayModePtr preferredDisplayMode = [&]() REQUIRES(mStateLock) -> DisplayModePtr { - const auto displayId = display->getPhysicalId(); - - if (const auto schedulerMode = mScheduler->getPreferredDisplayMode(); - schedulerMode && schedulerMode->getPhysicalDisplayId() == displayId) { - return schedulerMode; - } - - const DisplayModePtr nullMode; - return mPhysicalDisplays.get(displayId) - .transform(&PhysicalDisplay::snapshotRef) - .and_then([&](const display::DisplaySnapshot& snapshot) { - return snapshot.displayModes().get(currentPolicy.defaultMode); - }) - .value_or(std::cref(nullMode)); - }(); - - if (!preferredDisplayMode) { + auto preferredModeOpt = + getPreferredDisplayMode(display->getPhysicalId(), currentPolicy.defaultMode); + if (!preferredModeOpt) { ALOGE("%s: Preferred mode is unknown", __func__); return NAME_NOT_FOUND; } - ALOGV("trying to switch to Scheduler preferred mode %d (%s)", - preferredDisplayMode->getId().value(), to_string(preferredDisplayMode->getFps()).c_str()); + auto preferredMode = std::move(*preferredModeOpt); + const auto preferredModeId = preferredMode->getId(); - if (display->refreshRateConfigs().isModeAllowed(preferredDisplayMode->getId())) { - ALOGV("switching to Scheduler preferred display mode %d", - preferredDisplayMode->getId().value()); - setDesiredActiveMode({preferredDisplayMode, DisplayModeEvent::Changed}); - } else { - LOG_ALWAYS_FATAL("Desired display mode not allowed: %d", - preferredDisplayMode->getId().value()); + ALOGV("Switching to Scheduler preferred mode %d (%s)", preferredModeId.value(), + to_string(preferredMode->getFps()).c_str()); + + if (!display->refreshRateConfigs().isModeAllowed(preferredModeId)) { + ALOGE("%s: Preferred mode %d is disallowed", __func__, preferredModeId.value()); + return INVALID_OPERATION; } + setDesiredActiveMode({std::move(preferredMode), DisplayModeEvent::Changed}); return NO_ERROR; } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index e9e13ef686..2af17e7c6a 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -668,6 +668,12 @@ private: // Returns true if the display has a visible HDR layer in its layer stack. bool hasVisibleHdrLayer(const sp& display) REQUIRES(mStateLock); + // Returns the preferred mode for PhysicalDisplayId if the Scheduler has selected one for that + // display. Falls back to the display's defaultModeId otherwise. + std::optional getPreferredDisplayMode(PhysicalDisplayId, + DisplayModeId defaultModeId) const + REQUIRES(mStateLock); + // Sets the desired display mode specs. status_t setDesiredDisplayModeSpecsInternal( const sp& display, -- GitLab From f8734e014d3a5596be2edc6ee85dbda7589bb889 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Fri, 26 Aug 2022 09:06:59 -0700 Subject: [PATCH 0273/1310] SF: Enforce thread safety of active mode access Provide two RefreshRateConfigs APIs for retrieving the active mode: getActiveMode for access from the main thread (which does not need to lock nor copy), and getActiveModePtr for other threads. Bug: 241285191 Test: Build (-Wthread-safety) Change-Id: If156d85861ec2d82a394ba181314a6ba3048974f --- services/surfaceflinger/Layer.cpp | 2 +- .../Scheduler/RefreshRateConfigs.cpp | 54 +++++++++++++------ .../Scheduler/RefreshRateConfigs.h | 23 +++++--- .../surfaceflinger/Scheduler/Scheduler.cpp | 8 +-- services/surfaceflinger/Scheduler/Scheduler.h | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 28 +++++----- services/surfaceflinger/SurfaceFlinger.h | 27 +++++----- .../fuzzer/surfaceflinger_fuzzer.cpp | 3 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 19 ++++--- .../surfaceflinger_scheduler_fuzzer.cpp | 2 +- .../unittests/RefreshRateConfigsTest.cpp | 37 ++++++++----- .../tests/unittests/TestableSurfaceFlinger.h | 4 +- 12 files changed, 128 insertions(+), 81 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 47bd91e195..701071b36e 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -3947,7 +3947,7 @@ void Layer::onPostComposition(const DisplayDevice* display, } if (display) { - const Fps refreshRate = display->refreshRateConfigs().getActiveMode()->getFps(); + const Fps refreshRate = display->refreshRateConfigs().getActiveModePtr()->getFps(); const std::optional renderRate = mFlinger->mScheduler->getFrameRateOverride(getOwnerUid()); diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp index a48c921378..d270655f4f 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "../SurfaceFlingerProperties.h" @@ -325,6 +326,8 @@ auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vectordefaultMode)->get(); + const auto& activeMode = *getActiveModeItLocked()->second; + // If the default mode group is different from the group of current mode, // this means a layer requesting a seamed mode switch just disappeared and // we should switch back to the default group. @@ -332,7 +335,7 @@ auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vector 0 ? mActiveModeIt->second->getGroup() : defaultMode->getGroup(); + seamedFocusedLayers > 0 ? activeMode.getGroup() : defaultMode->getGroup(); // Consider the touch event if there are no Explicit* layers. Otherwise wait until after we've // selected a refresh rate to see if we should apply touch boost. @@ -387,12 +390,12 @@ auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vectorgetGroup() == mActiveModeIt->second->getGroup(); + const bool isSeamlessSwitch = mode->getGroup() == activeMode.getGroup(); if (layer.seamlessness == Seamlessness::OnlySeamless && !isSeamlessSwitch) { ALOGV("%s ignores %s to avoid non-seamless switch. Current mode = %s", formatLayerInfo(layer, weight).c_str(), to_string(*mode).c_str(), - to_string(*mActiveModeIt->second).c_str()); + to_string(activeMode).c_str()); continue; } @@ -401,7 +404,7 @@ auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vectorsecond).c_str()); + to_string(activeMode).c_str()); continue; } @@ -413,7 +416,7 @@ auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vectorgetGroup() == anchorGroup; if (layer.seamlessness == Seamlessness::Default && !isInPolicyForDefault) { ALOGV("%s ignores %s. Current mode = %s", formatLayerInfo(layer, weight).c_str(), - to_string(*mode).c_str(), to_string(*mActiveModeIt->second).c_str()); + to_string(*mode).c_str(), to_string(activeMode).c_str()); continue; } @@ -676,7 +679,7 @@ std::optional RefreshRateConfigs::onKernelTimerChanged( const DisplayModePtr& current = desiredActiveModeId ? mDisplayModes.get(*desiredActiveModeId)->get() - : mActiveModeIt->second; + : getActiveModeItLocked()->second; const DisplayModePtr& min = mMinRefreshRateModeIt->second; if (current == min) { @@ -688,16 +691,17 @@ std::optional RefreshRateConfigs::onKernelTimerChanged( } const DisplayModePtr& RefreshRateConfigs::getMinRefreshRateByPolicyLocked() const { + const auto& activeMode = *getActiveModeItLocked()->second; + for (const DisplayModeIterator modeIt : mPrimaryRefreshRates) { const auto& mode = modeIt->second; - if (mActiveModeIt->second->getGroup() == mode->getGroup()) { + if (activeMode.getGroup() == mode->getGroup()) { return mode; } } - ALOGE("Can't find min refresh rate by policy with the same mode group" - " as the current mode %s", - to_string(*mActiveModeIt->second).c_str()); + ALOGE("Can't find min refresh rate by policy with the same mode group as the current mode %s", + to_string(activeMode).c_str()); // Default to the lowest refresh rate. return mPrimaryRefreshRates.front()->second; @@ -708,6 +712,11 @@ DisplayModePtr RefreshRateConfigs::getMaxRefreshRateByPolicy() const { return getMaxRefreshRateByPolicyLocked(); } +const DisplayModePtr& RefreshRateConfigs::getMaxRefreshRateByPolicyLocked() const { + const int anchorGroup = getActiveModeItLocked()->second->getGroup(); + return getMaxRefreshRateByPolicyLocked(anchorGroup); +} + const DisplayModePtr& RefreshRateConfigs::getMaxRefreshRateByPolicyLocked(int anchorGroup) const { for (auto it = mPrimaryRefreshRates.rbegin(); it != mPrimaryRefreshRates.rend(); ++it) { const auto& mode = (*it)->second; @@ -716,17 +725,28 @@ const DisplayModePtr& RefreshRateConfigs::getMaxRefreshRateByPolicyLocked(int an } } - ALOGE("Can't find max refresh rate by policy with the same mode group" - " as the current mode %s", - to_string(*mActiveModeIt->second).c_str()); + const auto& activeMode = *getActiveModeItLocked()->second; + ALOGE("Can't find max refresh rate by policy with the same mode group as the current mode %s", + to_string(activeMode).c_str()); // Default to the highest refresh rate. return mPrimaryRefreshRates.back()->second; } -DisplayModePtr RefreshRateConfigs::getActiveMode() const { +DisplayModePtr RefreshRateConfigs::getActiveModePtr() const { std::lock_guard lock(mLock); - return mActiveModeIt->second; + return getActiveModeItLocked()->second; +} + +const DisplayMode& RefreshRateConfigs::getActiveMode() const { + // Reads from kMainThreadContext do not require mLock. + ftl::FakeGuard guard(mLock); + return *mActiveModeIt->second; +} + +DisplayModeIterator RefreshRateConfigs::getActiveModeItLocked() const { + // Reads under mLock do not require kMainThreadContext. + return FTL_FAKE_GUARD(kMainThreadContext, mActiveModeIt); } void RefreshRateConfigs::setActiveModeId(DisplayModeId modeId) { @@ -744,7 +764,7 @@ RefreshRateConfigs::RefreshRateConfigs(DisplayModes modes, DisplayModeId activeM Config config) : mKnownFrameRates(constructKnownFrameRates(modes)), mConfig(config) { initializeIdleTimer(); - updateDisplayModes(std::move(modes), activeModeId); + FTL_FAKE_GUARD(kMainThreadContext, updateDisplayModes(std::move(modes), activeModeId)); } void RefreshRateConfigs::initializeIdleTimer() { @@ -976,7 +996,7 @@ void RefreshRateConfigs::dump(std::string& result) const { std::lock_guard lock(mLock); - const auto activeModeId = mActiveModeIt->first; + const auto activeModeId = getActiveModeItLocked()->first; result += " activeModeId="s; result += std::to_string(activeModeId.value()); diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h index a79002e959..b2cfb03e42 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h +++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h @@ -31,6 +31,7 @@ #include "DisplayHardware/HWComposer.h" #include "Scheduler/OneShotTimer.h" #include "Scheduler/StrongTyping.h" +#include "ThreadContext.h" namespace android::scheduler { @@ -207,8 +208,11 @@ public: // uses the primary range, not the app request range. DisplayModePtr getMaxRefreshRateByPolicy() const EXCLUDES(mLock); - void setActiveModeId(DisplayModeId) EXCLUDES(mLock); - DisplayModePtr getActiveMode() const EXCLUDES(mLock); + void setActiveModeId(DisplayModeId) EXCLUDES(mLock) REQUIRES(kMainThreadContext); + + // See mActiveModeIt for thread safety. + DisplayModePtr getActiveModePtr() const EXCLUDES(mLock); + const DisplayMode& getActiveMode() const REQUIRES(kMainThreadContext); // Returns a known frame rate that is the closest to frameRate Fps findClosestKnownFrameRate(Fps frameRate) const; @@ -332,6 +336,9 @@ private: void constructAvailableRefreshRates() REQUIRES(mLock); + // See mActiveModeIt for thread safety. + DisplayModeIterator getActiveModeItLocked() const REQUIRES(mLock); + std::pair getBestRefreshRateLocked( const std::vector&, GlobalSignals) const REQUIRES(mLock); @@ -345,10 +352,8 @@ private: // Returns the highest refresh rate according to the current policy. May change at runtime. Only // uses the primary range, not the app request range. + const DisplayModePtr& getMaxRefreshRateByPolicyLocked() const REQUIRES(mLock); const DisplayModePtr& getMaxRefreshRateByPolicyLocked(int anchorGroup) const REQUIRES(mLock); - const DisplayModePtr& getMaxRefreshRateByPolicyLocked() const REQUIRES(mLock) { - return getMaxRefreshRateByPolicyLocked(mActiveModeIt->second->getGroup()); - } const Policy* getCurrentPolicyLocked() const REQUIRES(mLock); bool isPolicyValidLocked(const Policy& policy) const REQUIRES(mLock); @@ -361,7 +366,8 @@ private: float calculateNonExactMatchingLayerScoreLocked(const LayerRequirement&, Fps refreshRate) const REQUIRES(mLock); - void updateDisplayModes(DisplayModes, DisplayModeId activeModeId) EXCLUDES(mLock); + void updateDisplayModes(DisplayModes, DisplayModeId activeModeId) EXCLUDES(mLock) + REQUIRES(kMainThreadContext); void initializeIdleTimer(); @@ -377,7 +383,10 @@ private: // is also dependent, so must be reset as well. DisplayModes mDisplayModes GUARDED_BY(mLock); - DisplayModeIterator mActiveModeIt GUARDED_BY(mLock); + // Written under mLock exclusively from kMainThreadContext, so reads from kMainThreadContext + // need not be under mLock. + DisplayModeIterator mActiveModeIt GUARDED_BY(mLock) GUARDED_BY(kMainThreadContext); + DisplayModeIterator mMinRefreshRateModeIt GUARDED_BY(mLock); DisplayModeIterator mMaxRefreshRateModeIt GUARDED_BY(mLock); diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 55ae013358..bec39a75d0 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -182,7 +182,7 @@ impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const { return [this](uid_t uid) { - const Fps refreshRate = holdRefreshRateConfigs()->getActiveMode()->getFps(); + const Fps refreshRate = holdRefreshRateConfigs()->getActiveModePtr()->getFps(); const nsecs_t currentPeriod = mVsyncSchedule->period().ns() ?: refreshRate.getPeriodNsecs(); const auto frameRate = getFrameRateOverride(uid); @@ -320,7 +320,7 @@ void Scheduler::dispatchCachedReportedMode() { // mode change is in progress. In that case we shouldn't dispatch an event // as it will be dispatched when the current mode changes. if (std::scoped_lock lock(mRefreshRateConfigsLock); - mRefreshRateConfigs->getActiveMode() != mPolicy.mode) { + mRefreshRateConfigs->getActiveModePtr() != mPolicy.mode) { return; } @@ -453,7 +453,7 @@ void Scheduler::resync() { if (now - last > kIgnoreDelay) { const auto refreshRate = [&] { std::scoped_lock lock(mRefreshRateConfigsLock); - return mRefreshRateConfigs->getActiveMode()->getFps(); + return mRefreshRateConfigs->getActiveModePtr()->getFps(); }(); resyncToHardwareVsync(false, refreshRate); } @@ -577,7 +577,7 @@ void Scheduler::kernelIdleTimerCallback(TimerState state) { // magic number const Fps refreshRate = [&] { std::scoped_lock lock(mRefreshRateConfigsLock); - return mRefreshRateConfigs->getActiveMode()->getFps(); + return mRefreshRateConfigs->getActiveModePtr()->getFps(); }(); constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER = 65_Hz; diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index eb3856d6fc..afb34590e3 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -229,7 +229,7 @@ public: nsecs_t getVsyncPeriodFromRefreshRateConfigs() const EXCLUDES(mRefreshRateConfigsLock) { std::scoped_lock lock(mRefreshRateConfigsLock); - return mRefreshRateConfigs->getActiveMode()->getFps().getPeriodNsecs(); + return mRefreshRateConfigs->getActiveModePtr()->getFps().getPeriodNsecs(); } // Returns the framerate of the layer with the given sequence ID diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index ca578e140d..78eaa14e5b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1157,9 +1157,7 @@ void SurfaceFlinger::updateInternalStateWithChangedMode() { return; } - const auto upcomingModeInfo = - FTL_FAKE_GUARD(kMainThreadContext, display->getUpcomingActiveMode()); - + const auto upcomingModeInfo = display->getUpcomingActiveMode(); if (!upcomingModeInfo.mode) { // There is no pending mode change. This can happen if the active // display changed and the mode change happened on a different display. @@ -1273,9 +1271,8 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { constraints.seamlessRequired = false; hal::VsyncPeriodChangeTimeline outTimeline; - const auto status = FTL_FAKE_GUARD(kMainThreadContext, - display->initiateModeChange(*desiredActiveMode, - constraints, &outTimeline)); + const auto status = + display->initiateModeChange(*desiredActiveMode, constraints, &outTimeline); if (status != NO_ERROR) { // initiateModeChange may fail if a hotplug event is just about @@ -2161,7 +2158,7 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe // Hold mStateLock as chooseRefreshRateForContent promotes wp to sp // and may eventually call to ~Layer() if it holds the last reference { - Mutex::Autolock _l(mStateLock); + Mutex::Autolock lock(mStateLock); mScheduler->chooseRefreshRateForContent(); setActiveModeInHwcIfNeeded(); } @@ -3060,9 +3057,10 @@ void SurfaceFlinger::processDisplayChanged(const wp& displayToken, } } } + void SurfaceFlinger::updateInternalDisplayVsyncLocked(const sp& activeDisplay) { mVsyncConfiguration->reset(); - const Fps refreshRate = activeDisplay->refreshRateConfigs().getActiveMode()->getFps(); + const Fps refreshRate = activeDisplay->refreshRateConfigs().getActiveMode().getFps(); updatePhaseConfiguration(refreshRate); mRefreshRateStats->setRefreshRate(refreshRate); } @@ -4724,8 +4722,6 @@ void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp& layer) { } } -// --------------------------------------------------------------------------- - void SurfaceFlinger::onInitializeDisplays() { const auto display = getDefaultDisplayDeviceLocked(); if (!display) return; @@ -4763,8 +4759,9 @@ void SurfaceFlinger::onInitializeDisplays() { void SurfaceFlinger::initializeDisplays() { // Async since we may be called from the main thread. - static_cast( - mScheduler->schedule([this]() FTL_FAKE_GUARD(mStateLock) { onInitializeDisplays(); })); + static_cast(mScheduler->schedule( + [this]() FTL_FAKE_GUARD(mStateLock) + FTL_FAKE_GUARD(kMainThreadContext) { onInitializeDisplays(); })); } void SurfaceFlinger::setPowerModeInternal(const sp& display, hal::PowerMode mode) { @@ -4796,7 +4793,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: if (mInterceptor->isEnabled()) { mInterceptor->savePowerModeUpdate(display->getSequenceId(), static_cast(mode)); } - const auto refreshRate = display->refreshRateConfigs().getActiveMode()->getFps(); + const auto refreshRate = display->refreshRateConfigs().getActiveMode().getFps(); if (*currentMode == hal::PowerMode::OFF) { // Turn on the display if (isInternalDisplay && (!activeDisplay || !activeDisplay->isPoweredOn())) { @@ -4870,7 +4867,8 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: } void SurfaceFlinger::setPowerMode(const sp& displayToken, int mode) { - auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) { + auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD( + kMainThreadContext) { const auto display = getDisplayDeviceLocked(displayToken); if (!display) { ALOGE("Attempt to set power mode %d for invalid display token %p", mode, @@ -7021,7 +7019,7 @@ uint32_t SurfaceFlinger::getMaxAcquiredBufferCountForCurrentRefreshRate(uid_t ui refreshRate = *frameRateOverride; } else if (!getHwComposer().isHeadless()) { if (const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked())) { - refreshRate = display->refreshRateConfigs().getActiveMode()->getFps(); + refreshRate = display->refreshRateConfigs().getActiveModePtr()->getFps(); } } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 2af17e7c6a..f7684a0ce1 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -648,22 +648,20 @@ private: // Show spinner with refresh rate overlay bool mRefreshRateOverlaySpinner = false; - // Called on the main thread in response to initializeDisplays() - void onInitializeDisplays() REQUIRES(mStateLock); // Sets the desired active mode bit. It obtains the lock, and sets mDesiredActiveMode. void setDesiredActiveMode(const ActiveModeInfo& info) REQUIRES(mStateLock); status_t setActiveModeFromBackdoor(const sp&, DisplayModeId); // Sets the active mode and a new refresh rate in SF. - void updateInternalStateWithChangedMode() REQUIRES(mStateLock); + void updateInternalStateWithChangedMode() REQUIRES(mStateLock, kMainThreadContext); // Calls to setActiveMode on the main thread if there is a pending mode change // that needs to be applied. - void setActiveModeInHwcIfNeeded() REQUIRES(mStateLock); + void setActiveModeInHwcIfNeeded() REQUIRES(mStateLock, kMainThreadContext); void clearDesiredActiveModeState(const sp&) REQUIRES(mStateLock); // Called when active mode is no longer is progress void desiredActiveModeChangeDone(const sp&) REQUIRES(mStateLock); // Called on the main thread in response to setPowerMode() void setPowerModeInternal(const sp& display, hal::PowerMode mode) - REQUIRES(mStateLock); + REQUIRES(mStateLock, kMainThreadContext); // Returns true if the display has a visible HDR layer in its layer stack. bool hasVisibleHdrLayer(const sp& display) REQUIRES(mStateLock); @@ -680,8 +678,9 @@ private: const std::optional& policy, bool overridePolicy) EXCLUDES(mStateLock); - void commitTransactions() EXCLUDES(mStateLock); - void commitTransactionsLocked(uint32_t transactionFlags) REQUIRES(mStateLock); + void commitTransactions() EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); + void commitTransactionsLocked(uint32_t transactionFlags) + REQUIRES(mStateLock, kMainThreadContext); void doCommitTransactions() REQUIRES(mStateLock); // Returns whether a new buffer has been latched. @@ -824,8 +823,10 @@ private: /* * Display and layer stack management */ - // called when starting, or restarting after system_server death + + // Called during boot, and restart after system_server death. void initializeDisplays(); + void onInitializeDisplays() REQUIRES(mStateLock, kMainThreadContext); bool isDisplayActiveLocked(const sp& display) const REQUIRES(mStateLock) { return display->getDisplayToken() == mActiveDisplayToken; @@ -957,11 +958,12 @@ private: const DisplayDeviceState& state, const sp& displaySurface, const sp& producer) REQUIRES(mStateLock); - void processDisplayChangesLocked() REQUIRES(mStateLock); + void processDisplayChangesLocked() REQUIRES(mStateLock, kMainThreadContext); void processDisplayRemoved(const wp& displayToken) REQUIRES(mStateLock); void processDisplayChanged(const wp& displayToken, const DisplayDeviceState& currentState, - const DisplayDeviceState& drawingState) REQUIRES(mStateLock); + const DisplayDeviceState& drawingState) + REQUIRES(mStateLock, kMainThreadContext); void dispatchDisplayHotplugEvent(PhysicalDisplayId displayId, bool connected); @@ -1022,7 +1024,8 @@ private: VirtualDisplayId acquireVirtualDisplay(ui::Size, ui::PixelFormat) REQUIRES(mStateLock); void releaseVirtualDisplay(VirtualDisplayId); - void onActiveDisplayChangedLocked(const sp& activeDisplay) REQUIRES(mStateLock); + void onActiveDisplayChangedLocked(const sp& activeDisplay) + REQUIRES(mStateLock, kMainThreadContext); void onActiveDisplaySizeChanged(const sp& activeDisplay); @@ -1096,7 +1099,7 @@ private: int getMaxAcquiredBufferCountForRefreshRate(Fps refreshRate) const; void updateInternalDisplayVsyncLocked(const sp& activeDisplay) - REQUIRES(mStateLock); + REQUIRES(mStateLock, kMainThreadContext); bool isHdrLayer(Layer* layer) const; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp index 28b875a309..79112bd998 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp @@ -237,7 +237,8 @@ void SurfaceFlingerFuzzer::process(const uint8_t *data, size_t size) { mTestableFlinger.enableHalVirtualDisplays(mFdp.ConsumeBool()); - mTestableFlinger.commitTransactionsLocked(mFdp.ConsumeIntegral()); + FTL_FAKE_GUARD(kMainThreadContext, + mTestableFlinger.commitTransactionsLocked(mFdp.ConsumeIntegral())); mTestableFlinger.notifyPowerBoost(mFdp.ConsumeIntegral()); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 3aa3633bd9..a8612638b2 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -600,14 +600,18 @@ public: mFlinger->binderDied(display); mFlinger->onFirstRef(); - mFlinger->commitTransactions(); mFlinger->updateInputFlinger(); mFlinger->updateCursorAsync(); setVsyncConfig(&mFdp); - FTL_FAKE_GUARD(kMainThreadContext, - mFlinger->flushTransactionQueues(getFuzzedVsyncId(mFdp))); + { + ftl::FakeGuard guard(kMainThreadContext); + + mFlinger->commitTransactions(); + mFlinger->flushTransactionQueues(getFuzzedVsyncId(mFdp)); + mFlinger->postComposition(); + } mFlinger->setTransactionFlags(mFdp.ConsumeIntegral()); mFlinger->clearTransactionFlags(mFdp.ConsumeIntegral()); @@ -624,8 +628,6 @@ public: mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mFdp.ConsumeIntegral()); - FTL_FAKE_GUARD(kMainThreadContext, mFlinger->postComposition()); - mFlinger->calculateExpectedPresentTime({}); mFlinger->enableHalVirtualDisplays(mFdp.ConsumeBool()); @@ -664,7 +666,8 @@ public: } mRefreshRateConfigs = std::make_shared(modes, kModeId60); - const auto fps = mRefreshRateConfigs->getActiveMode()->getFps(); + const auto fps = + FTL_FAKE_GUARD(kMainThreadContext, mRefreshRateConfigs->getActiveMode().getFps()); mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps); mFlinger->mVsyncModulator = sp::make( mFlinger->mVsyncConfiguration->getCurrentConfigs()); @@ -715,9 +718,9 @@ public: void enableHalVirtualDisplays(bool enable) { mFlinger->enableHalVirtualDisplays(enable); } - auto commitTransactionsLocked(uint32_t transactionFlags) { + void commitTransactionsLocked(uint32_t transactionFlags) FTL_FAKE_GUARD(kMainThreadContext) { Mutex::Autolock lock(mFlinger->mStateLock); - return mFlinger->commitTransactionsLocked(transactionFlags); + mFlinger->commitTransactionsLocked(transactionFlags); } auto setDisplayStateLocked(const DisplayState &s) { diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index 9584492001..3fc2b7e233 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -366,7 +366,7 @@ void SchedulerFuzzer::fuzzRefreshRateConfigs() { {modeId, {Fps::fromValue(mFdp.ConsumeFloatingPoint()), Fps::fromValue(mFdp.ConsumeFloatingPoint())}}); - refreshRateConfigs.setActiveModeId(modeId); + FTL_FAKE_GUARD(kMainThreadContext, refreshRateConfigs.setActiveModeId(modeId)); RefreshRateConfigs::isFractionalPairOrMultiple(Fps::fromValue( mFdp.ConsumeFloatingPoint()), diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp index 188fd58dea..4f20932f2f 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp @@ -18,6 +18,7 @@ #define LOG_TAG "SchedulerUnittests" #include +#include #include #include #include @@ -41,6 +42,16 @@ using mock::createDisplayMode; struct TestableRefreshRateConfigs : RefreshRateConfigs { using RefreshRateConfigs::RefreshRateConfigs; + void setActiveModeId(DisplayModeId modeId) { + ftl::FakeGuard guard(kMainThreadContext); + return RefreshRateConfigs::setActiveModeId(modeId); + } + + const DisplayMode& getActiveMode() const { + ftl::FakeGuard guard(kMainThreadContext); + return RefreshRateConfigs::getActiveMode(); + } + DisplayModePtr getMinSupportedRefreshRate() const { std::lock_guard lock(mLock); return mMinRefreshRateModeIt->second; @@ -243,20 +254,20 @@ TEST_F(RefreshRateConfigsTest, twoModes_policyChange) { TEST_F(RefreshRateConfigsTest, twoModes_getActiveMode) { TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); { - const auto mode = configs.getActiveMode(); - EXPECT_EQ(mode->getId(), kModeId60); + const auto& mode = configs.getActiveMode(); + EXPECT_EQ(mode.getId(), kModeId60); } configs.setActiveModeId(kModeId90); { - const auto mode = configs.getActiveMode(); - EXPECT_EQ(mode->getId(), kModeId90); + const auto& mode = configs.getActiveMode(); + EXPECT_EQ(mode.getId(), kModeId90); } EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}), 0); { - const auto mode = configs.getActiveMode(); - EXPECT_EQ(mode->getId(), kModeId90); + const auto& mode = configs.getActiveMode(); + EXPECT_EQ(mode.getId(), kModeId90); } } @@ -1898,30 +1909,30 @@ TEST_F(RefreshRateConfigsTest, testKernelIdleTimerActionFor120Hz) { } TEST_F(RefreshRateConfigsTest, getFrameRateDivisor) { - RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId30); + TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId30); const auto frameRate = 30_Hz; - Fps displayRefreshRate = configs.getActiveMode()->getFps(); + Fps displayRefreshRate = configs.getActiveMode().getFps(); EXPECT_EQ(1, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate)); configs.setActiveModeId(kModeId60); - displayRefreshRate = configs.getActiveMode()->getFps(); + displayRefreshRate = configs.getActiveMode().getFps(); EXPECT_EQ(2, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate)); configs.setActiveModeId(kModeId72); - displayRefreshRate = configs.getActiveMode()->getFps(); + displayRefreshRate = configs.getActiveMode().getFps(); EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate)); configs.setActiveModeId(kModeId90); - displayRefreshRate = configs.getActiveMode()->getFps(); + displayRefreshRate = configs.getActiveMode().getFps(); EXPECT_EQ(3, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate)); configs.setActiveModeId(kModeId120); - displayRefreshRate = configs.getActiveMode()->getFps(); + displayRefreshRate = configs.getActiveMode().getFps(); EXPECT_EQ(4, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate)); configs.setActiveModeId(kModeId90); - displayRefreshRate = configs.getActiveMode()->getFps(); + displayRefreshRate = configs.getActiveMode().getFps(); EXPECT_EQ(4, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, 22.5_Hz)); EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivisor(24_Hz, 25_Hz)); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index a6b3f7c5df..f8fdb65d31 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -223,7 +223,7 @@ public: configs = std::make_shared(modes, kModeId60); } - const auto fps = configs->getActiveMode()->getFps(); + const auto fps = FTL_FAKE_GUARD(kMainThreadContext, configs->getActiveMode().getFps()); mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps); mFlinger->mVsyncModulator = sp::make( mFlinger->mVsyncConfiguration->getCurrentConfigs()); @@ -371,6 +371,7 @@ public: void commitTransactionsLocked(uint32_t transactionFlags) { Mutex::Autolock lock(mFlinger->mStateLock); + ftl::FakeGuard guard(kMainThreadContext); mFlinger->commitTransactionsLocked(transactionFlags); } @@ -464,6 +465,7 @@ public: void onActiveDisplayChanged(const sp& activeDisplay) { Mutex::Autolock lock(mFlinger->mStateLock); + ftl::FakeGuard guard(kMainThreadContext); mFlinger->onActiveDisplayChangedLocked(activeDisplay); } -- GitLab From e4df875a3b8079bf7fee90ccf80e4cd3a49c1082 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Thu, 8 Sep 2022 09:17:55 -0700 Subject: [PATCH 0274/1310] Revert "InputDispatcher: Blame the window from the focus request for ANR" This reverts commit 2f5bc8b8cfda7762213268842c13e2280ac02466. Breaks android.server.wm.AnrTests#slowUiThreadWithKeyEventTriggersAnr Test: atest android.server.wm.AnrTests#slowUiThreadWithKeyEventTriggersAnr Bug: 239907039 Change-Id: I45fcaf7d92fdedfa965d563cdb62ad2da3c8f5d4 --- .../inputflinger/dispatcher/FocusResolver.cpp | 2 +- .../inputflinger/dispatcher/FocusResolver.h | 2 +- .../dispatcher/InputDispatcher.cpp | 26 ----------- .../inputflinger/dispatcher/InputDispatcher.h | 1 - .../tests/InputDispatcher_test.cpp | 43 +++---------------- 5 files changed, 9 insertions(+), 65 deletions(-) diff --git a/services/inputflinger/dispatcher/FocusResolver.cpp b/services/inputflinger/dispatcher/FocusResolver.cpp index 85dcf8f4cf..4da846bcf8 100644 --- a/services/inputflinger/dispatcher/FocusResolver.cpp +++ b/services/inputflinger/dispatcher/FocusResolver.cpp @@ -39,7 +39,7 @@ sp FocusResolver::getFocusedWindowToken(int32_t displayId) const { return it != mFocusedWindowTokenByDisplay.end() ? it->second.second : nullptr; } -std::optional FocusResolver::getFocusRequest(int32_t displayId) const { +std::optional FocusResolver::getFocusRequest(int32_t displayId) { auto it = mFocusRequestByDisplay.find(displayId); return it != mFocusRequestByDisplay.end() ? std::make_optional<>(it->second) : std::nullopt; } diff --git a/services/inputflinger/dispatcher/FocusResolver.h b/services/inputflinger/dispatcher/FocusResolver.h index 8a6dfa4ca9..6d11a77aad 100644 --- a/services/inputflinger/dispatcher/FocusResolver.h +++ b/services/inputflinger/dispatcher/FocusResolver.h @@ -62,7 +62,6 @@ public: std::optional setFocusedWindow( const android::gui::FocusRequest& request, const std::vector>& windows); - std::optional getFocusRequest(int32_t displayId) const; // Display has been removed from the system, clean up old references. void displayRemoved(int32_t displayId); @@ -113,6 +112,7 @@ private: std::optional updateFocusedWindow( int32_t displayId, const std::string& reason, const sp& token, const std::string& tokenName = ""); + std::optional getFocusRequest(int32_t displayId); }; } // namespace android::inputdispatcher diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index b52e3128db..ff63967c2f 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -659,13 +659,6 @@ void InputDispatcher::processNoFocusedWindowAnrLocked() { if (focusedWindowHandle != nullptr) { return; // We now have a focused window. No need for ANR. } - std::optional pendingRequest = - mFocusResolver.getFocusRequest(mAwaitedApplicationDisplayId); - if (pendingRequest.has_value() && onAnrLocked(*pendingRequest)) { - // We don't have a focusable window but we know which window should have - // been focused. Blame that process in case it doesn't belong to the focused app. - return; - } onAnrLocked(mAwaitedFocusedApplication); } @@ -5855,25 +5848,6 @@ void InputDispatcher::sendDropWindowCommandLocked(const sp& token, floa postCommandLocked(std::move(command)); } -bool InputDispatcher::onAnrLocked(const android::gui::FocusRequest& pendingFocusRequest) { - if (pendingFocusRequest.token == nullptr) { - return false; - } - - const std::string reason = android::base::StringPrintf("%s is not focusable.", - pendingFocusRequest.windowName.c_str()); - updateLastAnrStateLocked(pendingFocusRequest.windowName, reason); - sp connection = getConnectionLocked(pendingFocusRequest.token); - if (connection != nullptr) { - processConnectionUnresponsiveLocked(*connection, std::move(reason)); - // Stop waking up for events on this connection, it is already unresponsive - cancelEventsForAnrLocked(connection); - } else { - sendWindowUnresponsiveCommandLocked(pendingFocusRequest.token, std::nullopt, reason); - } - return true; -} - void InputDispatcher::onAnrLocked(const sp& connection) { if (connection == nullptr) { LOG_ALWAYS_FATAL("Caller must check for nullness"); diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index dc6dd5cdc0..be619ae4c2 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -658,7 +658,6 @@ private: void sendDropWindowCommandLocked(const sp& token, float x, float y) REQUIRES(mLock); void onAnrLocked(const sp& connection) REQUIRES(mLock); void onAnrLocked(std::shared_ptr application) REQUIRES(mLock); - bool onAnrLocked(const android::gui::FocusRequest& pendingFocusRequest) REQUIRES(mLock); void updateLastAnrStateLocked(const sp& window, const std::string& reason) REQUIRES(mLock); void updateLastAnrStateLocked(const InputApplicationHandle& application, diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index cd853a64e1..7ee6950b09 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -4718,36 +4718,6 @@ TEST_F(InputDispatcherSingleWindowAnr, OnKeyDown_BasicAnr) { // We have a focused application, but no focused window TEST_F(InputDispatcherSingleWindowAnr, FocusedApplication_NoFocusedWindow) { - FocusRequest request; - request.token = nullptr; - request.windowName = ""; - request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC); - request.displayId = mWindow->getInfo()->displayId; - mDispatcher->setFocusedWindow(request); - mWindow->consumeFocusEvent(false); - - // taps on the window work as normal - ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - WINDOW_LOCATION)); - ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionDown()); - mDispatcher->waitForIdle(); - mFakePolicy->assertNotifyAnrWasNotCalled(); - - // Once a focused event arrives, we get an ANR for this application - // We specify the injection timeout to be smaller than the application timeout, to ensure that - // injection times out (instead of failing). - const InputEventInjectionResult result = - injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, false /* allowKeyRepeat */); - ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result); - const std::chrono::duration timeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT); - mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(timeout, mApplication); - ASSERT_TRUE(mDispatcher->waitForIdle()); -} - -// We have a focused application, but we are waiting on the requested window to become focusable -TEST_F(InputDispatcherSingleWindowAnr, FocusedApplication_PendingFocusedRequest) { mWindow->setFocusable(false); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}}); mWindow->consumeFocusEvent(false); @@ -4768,7 +4738,7 @@ TEST_F(InputDispatcherSingleWindowAnr, FocusedApplication_PendingFocusedRequest) InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, false /* allowKeyRepeat */); ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result); const std::chrono::duration timeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT); - mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow); + mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(timeout, mApplication); ASSERT_TRUE(mDispatcher->waitForIdle()); } @@ -4818,10 +4788,11 @@ TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DoesNotSendDuplicateAnr) injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT, InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, false /* allowKeyRepeat */); ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result); - const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); - mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow); + const std::chrono::duration appTimeout = + mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT); + mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(appTimeout, mApplication); - std::this_thread::sleep_for(timeout); + std::this_thread::sleep_for(appTimeout); // ANR should not be raised again. It is up to policy to do that if it desires. mFakePolicy->assertNotifyAnrWasNotCalled(); @@ -4842,8 +4813,8 @@ TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DropsFocusedEvents) { InputEventInjectionSync::WAIT_FOR_RESULT, 10ms); ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result); - const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); - mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow); + const std::chrono::duration timeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT); + mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(timeout, mApplication); // Future focused events get dropped right away ASSERT_EQ(InputEventInjectionResult::FAILED, injectKeyDown(mDispatcher)); -- GitLab From 0679cf200ea30c2e791cd79b27a26676e8848932 Mon Sep 17 00:00:00 2001 From: Rachel Lee Date: Thu, 8 Sep 2022 19:51:32 -0700 Subject: [PATCH 0275/1310] Fix getLatestVsyncEventData deadline. Bug: 239775097 Test: atest libsurfaceflinger_unittest Change-Id: Ib92849291458f59c2ef2238d3586211b87174c7f --- services/surfaceflinger/Scheduler/DispSyncSource.cpp | 6 +++--- .../tests/unittests/DispSyncSourceTest.cpp | 11 +++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/services/surfaceflinger/Scheduler/DispSyncSource.cpp b/services/surfaceflinger/Scheduler/DispSyncSource.cpp index 747032be0d..4af1f5c65f 100644 --- a/services/surfaceflinger/Scheduler/DispSyncSource.cpp +++ b/services/surfaceflinger/Scheduler/DispSyncSource.cpp @@ -188,10 +188,10 @@ void DispSyncSource::onVsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime VSyncSource::VSyncData DispSyncSource::getLatestVSyncData() const { std::lock_guard lock(mVsyncMutex); - nsecs_t expectedPresentTime = mVSyncTracker.nextAnticipatedVSyncTimeFrom( + nsecs_t expectedPresentationTime = mVSyncTracker.nextAnticipatedVSyncTimeFrom( systemTime() + mWorkDuration.get().count() + mReadyDuration.count()); - nsecs_t deadline = expectedPresentTime - mWorkDuration.get().count() - mReadyDuration.count(); - return {expectedPresentTime, deadline}; + nsecs_t deadline = expectedPresentationTime - mReadyDuration.count(); + return {expectedPresentationTime, deadline}; } void DispSyncSource::dump(std::string& result) const { diff --git a/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp b/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp index ec27edac6e..67ace1af83 100644 --- a/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp +++ b/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp @@ -292,9 +292,10 @@ TEST_F(DispSyncSourceTest, waitForCallbacksWithDurationChange) { TEST_F(DispSyncSourceTest, getLatestVsyncData) { const nsecs_t now = systemTime(); - const nsecs_t vsyncInternalDuration = mWorkDuration.count() + mReadyDuration.count(); + const nsecs_t expectedPresentationTime = + now + mWorkDuration.count() + mReadyDuration.count() + 1; EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_)) - .WillOnce(Return(now + vsyncInternalDuration + 1)); + .WillOnce(Return(expectedPresentationTime)); { InSequence seq; EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).Times(1); @@ -306,10 +307,8 @@ TEST_F(DispSyncSourceTest, getLatestVsyncData) { EXPECT_TRUE(mDispSyncSource); const auto vsyncData = mDispSyncSource->getLatestVSyncData(); - ASSERT_GT(vsyncData.deadlineTimestamp, now); - ASSERT_GT(vsyncData.expectedPresentationTime, vsyncData.deadlineTimestamp); - EXPECT_EQ(vsyncData.deadlineTimestamp, - vsyncData.expectedPresentationTime - vsyncInternalDuration); + ASSERT_EQ(vsyncData.expectedPresentationTime, expectedPresentationTime); + EXPECT_EQ(vsyncData.deadlineTimestamp, expectedPresentationTime - mReadyDuration.count()); } } // namespace -- GitLab From 86f72a3882ab4994dfbb410ebebc9ab3a75b8b16 Mon Sep 17 00:00:00 2001 From: Sam Dubey Date: Fri, 9 Sep 2022 05:09:34 +0000 Subject: [PATCH 0276/1310] Revert "Make VelocityTracker 1D" Revert "Conform to 1D VelocityTracker" Revert submission 19762050-generic_vt Reason for revert: b/245842062 Reverted Changes: I181af7a03:Make VelocityTracker 1D Ib0be0fc38:Conform to 1D VelocityTracker Change-Id: Ife5675e4abdf154eb6466f687e52b6a427860d26 --- include/input/VelocityControl.h | 2 +- include/input/VelocityTracker.h | 119 +++---- libs/input/VelocityControl.cpp | 26 +- libs/input/VelocityTracker.cpp | 313 +++++++++--------- libs/input/tests/VelocityTracker_test.cpp | 107 +----- .../reader/mapper/TouchInputMapper.cpp | 20 +- 6 files changed, 234 insertions(+), 353 deletions(-) diff --git a/include/input/VelocityControl.h b/include/input/VelocityControl.h index f4c7061ad1..1acc2aef70 100644 --- a/include/input/VelocityControl.h +++ b/include/input/VelocityControl.h @@ -98,7 +98,7 @@ private: VelocityControlParameters mParameters; nsecs_t mLastMovementTime; - float mRawPositionX, mRawPositionY; + VelocityTracker::Position mRawPosition; VelocityTracker mVelocityTracker; }; diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h index 6f2fcf4ce4..886f1f7753 100644 --- a/include/input/VelocityTracker.h +++ b/include/input/VelocityTracker.h @@ -20,8 +20,6 @@ #include #include #include -#include -#include namespace android { @@ -48,14 +46,18 @@ public: MAX = LEGACY, }; + struct Position { + float x, y; + }; + struct Estimator { static const size_t MAX_DEGREE = 4; // Estimator time base. nsecs_t time; - // Polynomial coefficients describing motion. - float coeff[MAX_DEGREE + 1]; + // Polynomial coefficients describing motion in X and Y. + float xCoeff[MAX_DEGREE + 1], yCoeff[MAX_DEGREE + 1]; // Polynomial degree (number of coefficients), or zero if no information is // available. @@ -69,40 +71,14 @@ public: degree = 0; confidence = 0; for (size_t i = 0; i <= MAX_DEGREE; i++) { - coeff[i] = 0; - } - } - }; - - /* - * Contains all available velocity data from a VelocityTracker. - */ - struct ComputedVelocity { - inline std::optional getVelocity(int32_t axis, uint32_t id) const { - const auto& axisVelocities = mVelocities.find(axis); - if (axisVelocities == mVelocities.end()) { - return {}; - } - - const auto& axisIdVelocity = axisVelocities->second.find(id); - if (axisIdVelocity == axisVelocities->second.end()) { - return {}; + xCoeff[i] = 0; + yCoeff[i] = 0; } - - return axisIdVelocity->second; - } - - inline void addVelocity(int32_t axis, uint32_t id, float velocity) { - mVelocities[axis][id] = velocity; } - - private: - std::map> mVelocities; }; - // Creates a velocity tracker using the specified strategy for each supported axis. + // Creates a velocity tracker using the specified strategy. // If strategy is not provided, uses the default strategy for the platform. - // TODO(b/32830165): support axis-specific strategies. VelocityTracker(const Strategy strategy = Strategy::DEFAULT); ~VelocityTracker(); @@ -116,57 +92,45 @@ public: void clearPointers(BitSet32 idBits); // Adds movement information for a set of pointers. - // The idBits bitfield specifies the pointer ids of the pointers whose data points + // The idBits bitfield specifies the pointer ids of the pointers whose positions // are included in the movement. - // The positions map contains a mapping of an axis to positions array. - // The positions arrays contain information for each pointer in order by increasing id. - // Each array's size should be equal to the number of one bits in idBits. - void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::map>& positions); + // The positions array contains position information for each pointer in order by + // increasing id. Its size should be equal to the number of one bits in idBits. + void addMovement(nsecs_t eventTime, BitSet32 idBits, const std::vector& positions); // Adds movement information for all pointers in a MotionEvent, including historical samples. void addMovement(const MotionEvent* event); - // Returns the velocity of the specified pointer id and axis in position units per second. - // Returns empty optional if there is insufficient movement information for the pointer, or if - // the given axis is not supported for velocity tracking. - std::optional getVelocity(int32_t axis, uint32_t id) const; + // Gets the velocity of the specified pointer id in position units per second. + // Returns false and sets the velocity components to zero if there is + // insufficient movement information for the pointer. + bool getVelocity(uint32_t id, float* outVx, float* outVy) const; - // Populates a ComputedVelocity instance with all available velocity data, using the given units - // (reference: units == 1 means "per millisecond"), and clamping each velocity between - // [-maxVelocity, maxVelocity], inclusive. - void populateComputedVelocity(ComputedVelocity& computedVelocity, int32_t units, - float maxVelocity); - - // Gets an estimator for the recent movements of the specified pointer id for the given axis. + // Gets an estimator for the recent movements of the specified pointer id. // Returns false and clears the estimator if there is no information available // about the pointer. - bool getEstimator(int32_t axis, uint32_t id, Estimator* outEstimator) const; + bool getEstimator(uint32_t id, Estimator* outEstimator) const; // Gets the active pointer id, or -1 if none. inline int32_t getActivePointerId() const { return mActivePointerId; } + // Gets a bitset containing all pointer ids from the most recent movement. + inline BitSet32 getCurrentPointerIdBits() const { return mCurrentPointerIdBits; } + private: // The default velocity tracker strategy. // Although other strategies are available for testing and comparison purposes, // this is the strategy that applications will actually use. Be very careful // when adjusting the default strategy because it can dramatically affect // (often in a bad way) the user experience. - // TODO(b/32830165): define default strategy per axis. static const Strategy DEFAULT_STRATEGY = Strategy::LSQ2; - // Set of all axes supported for velocity tracking. - static const std::set SUPPORTED_AXES; - - // Axes specifying location on a 2D plane (i.e. X and Y). - static const std::set PLANAR_AXES; - nsecs_t mLastEventTime; BitSet32 mCurrentPointerIdBits; int32_t mActivePointerId; - std::map> mStrategies; + std::unique_ptr mStrategy; - void configureStrategy(int32_t axis, const Strategy strategy); + bool configureStrategy(const Strategy strategy); static std::unique_ptr createStrategy(const Strategy strategy); }; @@ -185,7 +149,7 @@ public: virtual void clear() = 0; virtual void clearPointers(BitSet32 idBits) = 0; virtual void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) = 0; + const std::vector& positions) = 0; virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const = 0; }; @@ -217,7 +181,7 @@ public: virtual void clear(); virtual void clearPointers(BitSet32 idBits); void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) override; + const std::vector& positions) override; virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const; private: @@ -232,9 +196,11 @@ private: struct Movement { nsecs_t eventTime; BitSet32 idBits; - float positions[MAX_POINTERS]; + VelocityTracker::Position positions[MAX_POINTERS]; - inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; } + inline const VelocityTracker::Position& getPosition(uint32_t id) const { + return positions[idBits.getIndexOfBit(id)]; + } }; float chooseWeight(uint32_t index) const; @@ -258,7 +224,7 @@ public: virtual void clear(); virtual void clearPointers(BitSet32 idBits); void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) override; + const std::vector& positions) override; virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const; private: @@ -267,15 +233,16 @@ private: nsecs_t updateTime; uint32_t degree; - float pos, vel, accel; + float xpos, xvel, xaccel; + float ypos, yvel, yaccel; }; const uint32_t mDegree; BitSet32 mPointerIdBits; State mPointerState[MAX_POINTER_ID + 1]; - void initState(State& state, nsecs_t eventTime, float pos) const; - void updateState(State& state, nsecs_t eventTime, float pos) const; + void initState(State& state, nsecs_t eventTime, float xpos, float ypos) const; + void updateState(State& state, nsecs_t eventTime, float xpos, float ypos) const; void populateEstimator(const State& state, VelocityTracker::Estimator* outEstimator) const; }; @@ -291,7 +258,7 @@ public: virtual void clear(); virtual void clearPointers(BitSet32 idBits); void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) override; + const std::vector& positions) override; virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const; private: @@ -307,9 +274,11 @@ private: struct Movement { nsecs_t eventTime; BitSet32 idBits; - float positions[MAX_POINTERS]; + VelocityTracker::Position positions[MAX_POINTERS]; - inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; } + inline const VelocityTracker::Position& getPosition(uint32_t id) const { + return positions[idBits.getIndexOfBit(id)]; + } }; uint32_t mIndex; @@ -324,7 +293,7 @@ public: virtual void clear(); virtual void clearPointers(BitSet32 idBits); void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) override; + const std::vector& positions) override; virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const; private: @@ -339,9 +308,11 @@ private: struct Movement { nsecs_t eventTime; BitSet32 idBits; - float positions[MAX_POINTERS]; + VelocityTracker::Position positions[MAX_POINTERS]; - inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; } + inline const VelocityTracker::Position& getPosition(uint32_t id) const { + return positions[idBits.getIndexOfBit(id)]; + } }; size_t mIndex; diff --git a/libs/input/VelocityControl.cpp b/libs/input/VelocityControl.cpp index e2bfb508e1..6e991e98bb 100644 --- a/libs/input/VelocityControl.cpp +++ b/libs/input/VelocityControl.cpp @@ -44,8 +44,8 @@ void VelocityControl::setParameters(const VelocityControlParameters& parameters) void VelocityControl::reset() { mLastMovementTime = LLONG_MIN; - mRawPositionX = 0; - mRawPositionY = 0; + mRawPosition.x = 0; + mRawPosition.y = 0; mVelocityTracker.clear(); } @@ -61,20 +61,17 @@ void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) { mLastMovementTime = eventTime; if (deltaX) { - mRawPositionX += *deltaX; + mRawPosition.x += *deltaX; } if (deltaY) { - mRawPositionY += *deltaY; + mRawPosition.y += *deltaY; } - mVelocityTracker.addMovement(eventTime, BitSet32(BitSet32::valueForBit(0)), - {{AMOTION_EVENT_AXIS_X, {mRawPositionX}}, - {AMOTION_EVENT_AXIS_Y, {mRawPositionY}}}); + mVelocityTracker.addMovement(eventTime, BitSet32(BitSet32::valueForBit(0)), {mRawPosition}); - std::optional vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0); - std::optional vy = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, 0); + float vx, vy; float scale = mParameters.scale; - if (vx && vy) { - float speed = hypotf(*vx, *vy) * scale; + if (mVelocityTracker.getVelocity(0, &vx, &vy)) { + float speed = hypotf(vx, vy) * scale; if (speed >= mParameters.highThreshold) { // Apply full acceleration above the high speed threshold. scale *= mParameters.acceleration; @@ -88,9 +85,10 @@ void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) { if (DEBUG_ACCELERATION) { ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): " - "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f", - mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold, - mParameters.acceleration, *vx, *vy, speed, scale / mParameters.scale); + "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f", + mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold, + mParameters.acceleration, + vx, vy, speed, scale / mParameters.scale); } } else { diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp index 4f91af14ea..76aaf61da7 100644 --- a/libs/input/VelocityTracker.cpp +++ b/libs/input/VelocityTracker.cpp @@ -125,39 +125,29 @@ static std::string matrixToString(const float* a, uint32_t m, uint32_t n, bool r // --- VelocityTracker --- -const std::set VelocityTracker::SUPPORTED_AXES = {AMOTION_EVENT_AXIS_X, - AMOTION_EVENT_AXIS_Y}; - -const std::set VelocityTracker::PLANAR_AXES = {AMOTION_EVENT_AXIS_X, AMOTION_EVENT_AXIS_Y}; - VelocityTracker::VelocityTracker(const Strategy strategy) : mLastEventTime(0), mCurrentPointerIdBits(0), mActivePointerId(-1) { - // Configure the strategy for each axis. - for (int32_t axis : SUPPORTED_AXES) { - configureStrategy(axis, strategy); + // Configure the strategy. + if (!configureStrategy(strategy)) { + ALOGE("Unrecognized velocity tracker strategy %" PRId32 ".", strategy); + if (!configureStrategy(VelocityTracker::DEFAULT_STRATEGY)) { + LOG_ALWAYS_FATAL("Could not create the default velocity tracker strategy '%" PRId32 + "'!", + strategy); + } } } VelocityTracker::~VelocityTracker() { } -void VelocityTracker::configureStrategy(int32_t axis, const Strategy strategy) { - std::unique_ptr createdStrategy; - +bool VelocityTracker::configureStrategy(Strategy strategy) { if (strategy == VelocityTracker::Strategy::DEFAULT) { - createdStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY); + mStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY); } else { - createdStrategy = createStrategy(strategy); - } - - if (createdStrategy == nullptr) { - ALOGE("Unrecognized velocity tracker strategy %" PRId32 ".", strategy); - createdStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY); - LOG_ALWAYS_FATAL_IF(createdStrategy == nullptr, - "Could not create the default velocity tracker strategy '%" PRId32 "'!", - strategy); + mStrategy = createStrategy(strategy); } - mStrategies[axis] = std::move(createdStrategy); + return mStrategy != nullptr; } std::unique_ptr VelocityTracker::createStrategy( @@ -211,9 +201,8 @@ std::unique_ptr VelocityTracker::createStrategy( void VelocityTracker::clear() { mCurrentPointerIdBits.clear(); mActivePointerId = -1; - for (int32_t axis : SUPPORTED_AXES) { - mStrategies[axis]->clear(); - } + + mStrategy->clear(); } void VelocityTracker::clearPointers(BitSet32 idBits) { @@ -224,13 +213,14 @@ void VelocityTracker::clearPointers(BitSet32 idBits) { mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1; } - for (int32_t axis : SUPPORTED_AXES) { - mStrategies[axis]->clearPointers(idBits); - } + mStrategy->clearPointers(idBits); } void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::map>& positions) { + const std::vector& positions) { + LOG_ALWAYS_FATAL_IF(idBits.count() != positions.size(), + "Mismatching number of pointers, idBits=%" PRIu32 ", positions=%zu", + idBits.count(), positions.size()); while (idBits.count() > MAX_POINTERS) { idBits.clearLastMarkedBit(); } @@ -242,9 +232,7 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, // We have not received any movements for too long. Assume that all pointers // have stopped. - for (const auto& [_, strategy] : mStrategies) { - strategy->clear(); - } + mStrategy->clear(); } mLastEventTime = eventTime; @@ -253,37 +241,29 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, mActivePointerId = idBits.isEmpty() ? -1 : idBits.firstMarkedBit(); } - for (const auto& [axis, positionValues] : positions) { - LOG_ALWAYS_FATAL_IF(idBits.count() != positionValues.size(), - "Mismatching number of pointers, idBits=%" PRIu32 ", positions=%zu", - idBits.count(), positionValues.size()); - mStrategies[axis]->addMovement(eventTime, idBits, positionValues); - } + mStrategy->addMovement(eventTime, idBits, positions); if (DEBUG_VELOCITY) { ALOGD("VelocityTracker: addMovement eventTime=%" PRId64 ", idBits=0x%08x, activePointerId=%d", eventTime, idBits.value, mActivePointerId); - for (const auto& positionsEntry : positions) { - for (BitSet32 iterBits(idBits); !iterBits.isEmpty();) { - uint32_t id = iterBits.firstMarkedBit(); - uint32_t index = idBits.getIndexOfBit(id); - iterBits.clearBit(id); - Estimator estimator; - getEstimator(positionsEntry.first, id, &estimator); - ALOGD(" %d: axis=%d, position=%0.3f, " - "estimator (degree=%d, coeff=%s, confidence=%f)", - id, positionsEntry.first, positionsEntry.second[index], int(estimator.degree), - vectorToString(estimator.coeff, estimator.degree + 1).c_str(), - estimator.confidence); - } + for (BitSet32 iterBits(idBits); !iterBits.isEmpty();) { + uint32_t id = iterBits.firstMarkedBit(); + uint32_t index = idBits.getIndexOfBit(id); + iterBits.clearBit(id); + Estimator estimator; + getEstimator(id, &estimator); + ALOGD(" %d: position (%0.3f, %0.3f), " + "estimator (degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f)", + id, positions[index].x, positions[index].y, int(estimator.degree), + vectorToString(estimator.xCoeff, estimator.degree + 1).c_str(), + vectorToString(estimator.yCoeff, estimator.degree + 1).c_str(), + estimator.confidence); } } } void VelocityTracker::addMovement(const MotionEvent* event) { - // Stores data about which axes to process based on the incoming motion event. - std::set axesToProcess; int32_t actionMasked = event->getActionMasked(); switch (actionMasked) { @@ -291,9 +271,6 @@ void VelocityTracker::addMovement(const MotionEvent* event) { case AMOTION_EVENT_ACTION_HOVER_ENTER: // Clear all pointers on down before adding the new movement. clear(); - for (int32_t axis : PLANAR_AXES) { - axesToProcess.insert(axis); - } break; case AMOTION_EVENT_ACTION_POINTER_DOWN: { // Start a new movement trace for a pointer that just went down. @@ -302,16 +279,10 @@ void VelocityTracker::addMovement(const MotionEvent* event) { BitSet32 downIdBits; downIdBits.markBit(event->getPointerId(event->getActionIndex())); clearPointers(downIdBits); - for (int32_t axis : PLANAR_AXES) { - axesToProcess.insert(axis); - } break; } case AMOTION_EVENT_ACTION_MOVE: case AMOTION_EVENT_ACTION_HOVER_MOVE: - for (int32_t axis : PLANAR_AXES) { - axesToProcess.insert(axis); - } break; case AMOTION_EVENT_ACTION_POINTER_UP: case AMOTION_EVENT_ACTION_UP: { @@ -322,9 +293,7 @@ void VelocityTracker::addMovement(const MotionEvent* event) { toString(delaySinceLastEvent).c_str()); // We have not received any movements for too long. Assume that all pointers // have stopped. - for (int32_t axis : PLANAR_AXES) { - mStrategies[axis]->clear(); - } + mStrategy->clear(); } // These actions because they do not convey any new information about // pointer movement. We also want to preserve the last known velocity of the pointers. @@ -356,54 +325,37 @@ void VelocityTracker::addMovement(const MotionEvent* event) { pointerIndex[i] = idBits.getIndexOfBit(event->getPointerId(i)); } - std::map> positions; - for (int32_t axis : axesToProcess) { - positions[axis].resize(pointerCount); - } + std::vector positions; + positions.resize(pointerCount); size_t historySize = event->getHistorySize(); for (size_t h = 0; h <= historySize; h++) { nsecs_t eventTime = event->getHistoricalEventTime(h); - for (int32_t axis : axesToProcess) { - for (size_t i = 0; i < pointerCount; i++) { - positions[axis][pointerIndex[i]] = event->getHistoricalAxisValue(axis, i, h); - } + for (size_t i = 0; i < pointerCount; i++) { + uint32_t index = pointerIndex[i]; + positions[index].x = event->getHistoricalX(i, h); + positions[index].y = event->getHistoricalY(i, h); } addMovement(eventTime, idBits, positions); } } -std::optional VelocityTracker::getVelocity(int32_t axis, uint32_t id) const { +bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const { Estimator estimator; - bool validVelocity = getEstimator(axis, id, &estimator) && estimator.degree >= 1; - if (validVelocity) { - return estimator.coeff[1]; + if (getEstimator(id, &estimator) && estimator.degree >= 1) { + *outVx = estimator.xCoeff[1]; + *outVy = estimator.yCoeff[1]; + return true; } - return {}; + *outVx = 0; + *outVy = 0; + return false; } -void VelocityTracker::populateComputedVelocity(ComputedVelocity& computedVelocity, int32_t units, - float maxVelocity) { - for (int32_t axis : SUPPORTED_AXES) { - BitSet32 copyIdBits = BitSet32(mCurrentPointerIdBits); - while (!copyIdBits.isEmpty()) { - uint32_t id = copyIdBits.clearFirstMarkedBit(); - std::optional velocity = getVelocity(axis, id); - if (velocity) { - float adjustedVelocity = - std::clamp(*velocity * units / 1000, -maxVelocity, maxVelocity); - computedVelocity.addVelocity(axis, id, adjustedVelocity); - } - } - } +bool VelocityTracker::getEstimator(uint32_t id, Estimator* outEstimator) const { + return mStrategy->getEstimator(id, outEstimator); } -bool VelocityTracker::getEstimator(int32_t axis, uint32_t id, Estimator* outEstimator) const { - if (SUPPORTED_AXES.find(axis) == SUPPORTED_AXES.end()) { - return false; - } - return mStrategies.at(axis)->getEstimator(id, outEstimator); -} // --- LeastSquaresVelocityTrackerStrategy --- @@ -426,8 +378,9 @@ void LeastSquaresVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { mMovements[mIndex].idBits = remainingIdBits; } -void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { +void LeastSquaresVelocityTrackerStrategy::addMovement( + nsecs_t eventTime, BitSet32 idBits, + const std::vector& positions) { if (mMovements[mIndex].eventTime != eventTime) { // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include @@ -674,7 +627,8 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id, outEstimator->clear(); // Iterate over movement samples in reverse time order and collect samples. - std::vector positions; + std::vector x; + std::vector y; std::vector w; std::vector time; @@ -691,13 +645,15 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id, break; } - positions.push_back(movement.getPosition(id)); + const VelocityTracker::Position& position = movement.getPosition(id); + x.push_back(position.x); + y.push_back(position.y); w.push_back(chooseWeight(index)); time.push_back(-age * 0.000000001f); index = (index == 0 ? HISTORY_SIZE : index) - 1; - } while (positions.size() < HISTORY_SIZE); + } while (x.size() < HISTORY_SIZE); - const size_t m = positions.size(); + const size_t m = x.size(); if (m == 0) { return false; // no data } @@ -710,36 +666,39 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id, if (degree == 2 && mWeighting == WEIGHTING_NONE) { // Optimize unweighted, quadratic polynomial fit - std::optional> coeff = - solveUnweightedLeastSquaresDeg2(time, positions); - if (coeff) { + std::optional> xCoeff = solveUnweightedLeastSquaresDeg2(time, x); + std::optional> yCoeff = solveUnweightedLeastSquaresDeg2(time, y); + if (xCoeff && yCoeff) { outEstimator->time = newestMovement.eventTime; outEstimator->degree = 2; outEstimator->confidence = 1; for (size_t i = 0; i <= outEstimator->degree; i++) { - outEstimator->coeff[i] = (*coeff)[i]; + outEstimator->xCoeff[i] = (*xCoeff)[i]; + outEstimator->yCoeff[i] = (*yCoeff)[i]; } return true; } } else if (degree >= 1) { // General case for an Nth degree polynomial fit - float det; + float xdet, ydet; uint32_t n = degree + 1; - if (solveLeastSquares(time, positions, w, n, outEstimator->coeff, &det)) { + if (solveLeastSquares(time, x, w, n, outEstimator->xCoeff, &xdet) && + solveLeastSquares(time, y, w, n, outEstimator->yCoeff, &ydet)) { outEstimator->time = newestMovement.eventTime; outEstimator->degree = degree; - outEstimator->confidence = det; + outEstimator->confidence = xdet * ydet; - ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, coeff=%s, confidence=%f", - int(outEstimator->degree), vectorToString(outEstimator->coeff, n).c_str(), - outEstimator->confidence); + ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f", + int(outEstimator->degree), vectorToString(outEstimator->xCoeff, n).c_str(), + vectorToString(outEstimator->yCoeff, n).c_str(), outEstimator->confidence); return true; } } // No velocity data available for this pointer, but we do have its current position. - outEstimator->coeff[0] = positions[0]; + outEstimator->xCoeff[0] = x[0]; + outEstimator->yCoeff[0] = y[0]; outEstimator->time = newestMovement.eventTime; outEstimator->degree = 0; outEstimator->confidence = 1; @@ -831,17 +790,18 @@ void IntegratingVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { mPointerIdBits.value &= ~idBits.value; } -void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { +void IntegratingVelocityTrackerStrategy::addMovement( + nsecs_t eventTime, BitSet32 idBits, + const std::vector& positions) { uint32_t index = 0; for (BitSet32 iterIdBits(idBits); !iterIdBits.isEmpty();) { uint32_t id = iterIdBits.clearFirstMarkedBit(); State& state = mPointerState[id]; - const float position = positions[index++]; + const VelocityTracker::Position& position = positions[index++]; if (mPointerIdBits.hasBit(id)) { - updateState(state, eventTime, position); + updateState(state, eventTime, position.x, position.y); } else { - initState(state, eventTime, position); + initState(state, eventTime, position.x, position.y); } } @@ -861,18 +821,21 @@ bool IntegratingVelocityTrackerStrategy::getEstimator(uint32_t id, return false; } -void IntegratingVelocityTrackerStrategy::initState(State& state, nsecs_t eventTime, - float pos) const { +void IntegratingVelocityTrackerStrategy::initState(State& state, + nsecs_t eventTime, float xpos, float ypos) const { state.updateTime = eventTime; state.degree = 0; - state.pos = pos; - state.accel = 0; - state.vel = 0; + state.xpos = xpos; + state.xvel = 0; + state.xaccel = 0; + state.ypos = ypos; + state.yvel = 0; + state.yaccel = 0; } -void IntegratingVelocityTrackerStrategy::updateState(State& state, nsecs_t eventTime, - float pos) const { +void IntegratingVelocityTrackerStrategy::updateState(State& state, + nsecs_t eventTime, float xpos, float ypos) const { const nsecs_t MIN_TIME_DELTA = 2 * NANOS_PER_MS; const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds @@ -883,26 +846,34 @@ void IntegratingVelocityTrackerStrategy::updateState(State& state, nsecs_t event float dt = (eventTime - state.updateTime) * 0.000000001f; state.updateTime = eventTime; - float vel = (pos - state.pos) / dt; + float xvel = (xpos - state.xpos) / dt; + float yvel = (ypos - state.ypos) / dt; if (state.degree == 0) { - state.vel = vel; + state.xvel = xvel; + state.yvel = yvel; state.degree = 1; } else { float alpha = dt / (FILTER_TIME_CONSTANT + dt); if (mDegree == 1) { - state.vel += (vel - state.vel) * alpha; + state.xvel += (xvel - state.xvel) * alpha; + state.yvel += (yvel - state.yvel) * alpha; } else { - float accel = (vel - state.vel) / dt; + float xaccel = (xvel - state.xvel) / dt; + float yaccel = (yvel - state.yvel) / dt; if (state.degree == 1) { - state.accel = accel; + state.xaccel = xaccel; + state.yaccel = yaccel; state.degree = 2; } else { - state.accel += (accel - state.accel) * alpha; + state.xaccel += (xaccel - state.xaccel) * alpha; + state.yaccel += (yaccel - state.yaccel) * alpha; } - state.vel += (state.accel * dt) * alpha; + state.xvel += (state.xaccel * dt) * alpha; + state.yvel += (state.yaccel * dt) * alpha; } } - state.pos = pos; + state.xpos = xpos; + state.ypos = ypos; } void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state, @@ -910,9 +881,12 @@ void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state, outEstimator->time = state.updateTime; outEstimator->confidence = 1.0f; outEstimator->degree = state.degree; - outEstimator->coeff[0] = state.pos; - outEstimator->coeff[1] = state.vel; - outEstimator->coeff[2] = state.accel / 2; + outEstimator->xCoeff[0] = state.xpos; + outEstimator->xCoeff[1] = state.xvel; + outEstimator->xCoeff[2] = state.xaccel / 2; + outEstimator->yCoeff[0] = state.ypos; + outEstimator->yCoeff[1] = state.yvel; + outEstimator->yCoeff[2] = state.yaccel / 2; } @@ -935,8 +909,9 @@ void LegacyVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { mMovements[mIndex].idBits = remainingIdBits; } -void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { +void LegacyVelocityTrackerStrategy::addMovement( + nsecs_t eventTime, BitSet32 idBits, + const std::vector& positions) { if (++mIndex == HISTORY_SIZE) { mIndex = 0; } @@ -984,11 +959,12 @@ bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id, // overestimate the velocity at that time point. Most samples might be measured // 16ms apart but some consecutive samples could be only 0.5sm apart because // the hardware or driver reports them irregularly or in bursts. - float accumV = 0; + float accumVx = 0; + float accumVy = 0; uint32_t index = oldestIndex; uint32_t samplesUsed = 0; const Movement& oldestMovement = mMovements[oldestIndex]; - float oldestPosition = oldestMovement.getPosition(id); + const VelocityTracker::Position& oldestPosition = oldestMovement.getPosition(id); nsecs_t lastDuration = 0; while (numTouches-- > 1) { @@ -1002,22 +978,26 @@ bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id, // the velocity. Consequently, we impose a minimum duration constraint on the // samples that we include in the calculation. if (duration >= MIN_DURATION) { - float position = movement.getPosition(id); + const VelocityTracker::Position& position = movement.getPosition(id); float scale = 1000000000.0f / duration; // one over time delta in seconds - float v = (position - oldestPosition) * scale; - accumV = (accumV * lastDuration + v * duration) / (duration + lastDuration); + float vx = (position.x - oldestPosition.x) * scale; + float vy = (position.y - oldestPosition.y) * scale; + accumVx = (accumVx * lastDuration + vx * duration) / (duration + lastDuration); + accumVy = (accumVy * lastDuration + vy * duration) / (duration + lastDuration); lastDuration = duration; samplesUsed += 1; } } // Report velocity. - float newestPosition = newestMovement.getPosition(id); + const VelocityTracker::Position& newestPosition = newestMovement.getPosition(id); outEstimator->time = newestMovement.eventTime; outEstimator->confidence = 1; - outEstimator->coeff[0] = newestPosition; + outEstimator->xCoeff[0] = newestPosition.x; + outEstimator->yCoeff[0] = newestPosition.y; if (samplesUsed) { - outEstimator->coeff[1] = accumV; + outEstimator->xCoeff[1] = accumVx; + outEstimator->yCoeff[1] = accumVy; outEstimator->degree = 1; } else { outEstimator->degree = 0; @@ -1044,8 +1024,9 @@ void ImpulseVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { mMovements[mIndex].idBits = remainingIdBits; } -void ImpulseVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { +void ImpulseVelocityTrackerStrategy::addMovement( + nsecs_t eventTime, BitSet32 idBits, + const std::vector& positions) { if (mMovements[mIndex].eventTime != eventTime) { // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include @@ -1182,7 +1163,8 @@ bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, outEstimator->clear(); // Iterate over movement samples in reverse time order and collect samples. - float positions[HISTORY_SIZE]; + float x[HISTORY_SIZE]; + float y[HISTORY_SIZE]; nsecs_t time[HISTORY_SIZE]; size_t m = 0; // number of points that will be used for fitting size_t index = mIndex; @@ -1198,7 +1180,9 @@ bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, break; } - positions[m] = movement.getPosition(id); + const VelocityTracker::Position& position = movement.getPosition(id); + x[m] = position.x; + y[m] = position.y; time[m] = movement.eventTime; index = (index == 0 ? HISTORY_SIZE : index) - 1; } while (++m < HISTORY_SIZE); @@ -1206,30 +1190,33 @@ bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, if (m == 0) { return false; // no data } - outEstimator->coeff[0] = 0; - outEstimator->coeff[1] = calculateImpulseVelocity(time, positions, m); - outEstimator->coeff[2] = 0; - + outEstimator->xCoeff[0] = 0; + outEstimator->yCoeff[0] = 0; + outEstimator->xCoeff[1] = calculateImpulseVelocity(time, x, m); + outEstimator->yCoeff[1] = calculateImpulseVelocity(time, y, m); + outEstimator->xCoeff[2] = 0; + outEstimator->yCoeff[2] = 0; outEstimator->time = newestMovement.eventTime; outEstimator->degree = 2; // similar results to 2nd degree fit outEstimator->confidence = 1; - ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", outEstimator->coeff[1]); + ALOGD_IF(DEBUG_STRATEGY, "velocity: (%.1f, %.1f)", outEstimator->xCoeff[1], + outEstimator->yCoeff[1]); if (DEBUG_IMPULSE) { // TODO(b/134179997): delete this block once the switch to 'impulse' is complete. - // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons. - // X axis chosen arbitrarily for velocity comparisons. + // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons VelocityTracker lsq2(VelocityTracker::Strategy::LSQ2); BitSet32 idBits; const uint32_t pointerId = 0; idBits.markBit(pointerId); for (ssize_t i = m - 1; i >= 0; i--) { - lsq2.addMovement(time[i], idBits, {{AMOTION_EVENT_AXIS_X, {positions[i]}}}); + lsq2.addMovement(time[i], idBits, {{x[i], y[i]}}); } - std::optional v = lsq2.getVelocity(AMOTION_EVENT_AXIS_X, pointerId); - if (v) { - ALOGD("lsq2 velocity: %.1f", *v); + float outVx = 0, outVy = 0; + const bool computed = lsq2.getVelocity(pointerId, &outVx, &outVy); + if (computed) { + ALOGD("lsq2 velocity: (%.1f, %.1f)", outVx, outVy); } else { ALOGD("lsq2 velocity: could not compute velocity"); } diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp index 0a37318d3d..4a445de3ac 100644 --- a/libs/input/tests/VelocityTracker_test.cpp +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -16,10 +16,9 @@ #define LOG_TAG "VelocityTracker_test" -#include #include #include -#include +#include #include #include @@ -199,13 +198,25 @@ static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, const std::vector& motions, int32_t axis, float targetVelocity, uint32_t pointerId = DEFAULT_POINTER_ID) { VelocityTracker vt(strategy); + float Vx, Vy; std::vector events = createMotionEventStream(motions); for (MotionEvent event : events) { vt.addMovement(&event); } - checkVelocity(vt.getVelocity(axis, pointerId).value_or(0), targetVelocity); + vt.getVelocity(pointerId, &Vx, &Vy); + + switch (axis) { + case AMOTION_EVENT_AXIS_X: + checkVelocity(Vx, targetVelocity); + break; + case AMOTION_EVENT_AXIS_Y: + checkVelocity(Vy, targetVelocity); + break; + default: + FAIL() << "Axis must be either AMOTION_EVENT_AXIS_X or AMOTION_EVENT_AXIS_Y"; + } } static void computeAndCheckQuadraticEstimate(const std::vector& motions, @@ -215,99 +226,17 @@ static void computeAndCheckQuadraticEstimate(const std::vector for (MotionEvent event : events) { vt.addMovement(&event); } - VelocityTracker::Estimator estimatorX; - VelocityTracker::Estimator estimatorY; - EXPECT_TRUE(vt.getEstimator(AMOTION_EVENT_AXIS_X, 0, &estimatorX)); - EXPECT_TRUE(vt.getEstimator(AMOTION_EVENT_AXIS_Y, 0, &estimatorY)); + VelocityTracker::Estimator estimator; + EXPECT_TRUE(vt.getEstimator(0, &estimator)); for (size_t i = 0; i< coefficients.size(); i++) { - checkCoefficient(estimatorX.coeff[i], coefficients[i]); - checkCoefficient(estimatorY.coeff[i], coefficients[i]); + checkCoefficient(estimator.xCoeff[i], coefficients[i]); + checkCoefficient(estimator.yCoeff[i], coefficients[i]); } } /* * ================== VelocityTracker tests generated manually ===================================== */ -TEST_F(VelocityTrackerTest, TestComputedVelocity) { - VelocityTracker::ComputedVelocity computedVelocity; - - computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, 0 /*id*/, 200 /*velocity*/); - computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, 26U /*id*/, 400 /*velocity*/); - computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, 27U /*id*/, 650 /*velocity*/); - computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID, 750 /*velocity*/); - computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, 0 /*id*/, 1000 /*velocity*/); - computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, 26U /*id*/, 2000 /*velocity*/); - computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, 27U /*id*/, 3000 /*velocity*/); - computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, MAX_POINTER_ID, 4000 /*velocity*/); - - // Check the axes/indices with velocity. - EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, 0U /*id*/)), 200); - EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, 26U /*id*/)), 400); - EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, 27U /*id*/)), 650); - EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID)), 750); - EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, 0U /*id*/)), 1000); - EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, 26U /*id*/)), 2000); - EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, 27U /*id*/)), 3000); - EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, MAX_POINTER_ID)), 4000); - for (uint32_t id = 0; id < 32; id++) { - // Since no data was added for AXIS_SCROLL, expect empty value for the axis for any id. - EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_SCROLL, id)) - << "Empty scroll data expected at id=" << id; - if (id == 0 || id == 26U || id == 27U || id == MAX_POINTER_ID) { - // Already checked above; continue. - continue; - } - // No data was added to X/Y for this id, expect empty value. - EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, id)) - << "Empty X data expected at id=" << id; - EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, id)) - << "Empty Y data expected at id=" << id; - } - // Out-of-bounds ids should given empty values. - EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, -1)); - EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID + 1)); -} - -TEST_F(VelocityTrackerTest, TestPopulateComputedVelocity) { - std::vector motions = { - {235089067457000ns, {{528.00, 0}}}, {235089084684000ns, {{527.00, 0}}}, - {235089093349000ns, {{527.00, 0}}}, {235089095677625ns, {{527.00, 0}}}, - {235089101859000ns, {{527.00, 0}}}, {235089110378000ns, {{528.00, 0}}}, - {235089112497111ns, {{528.25, 0}}}, {235089118760000ns, {{531.00, 0}}}, - {235089126686000ns, {{535.00, 0}}}, {235089129316820ns, {{536.33, 0}}}, - {235089135199000ns, {{540.00, 0}}}, {235089144297000ns, {{546.00, 0}}}, - {235089146136443ns, {{547.21, 0}}}, {235089152923000ns, {{553.00, 0}}}, - {235089160784000ns, {{559.00, 0}}}, {235089162955851ns, {{560.66, 0}}}, - {235089162955851ns, {{560.66, 0}}}, // ACTION_UP - }; - VelocityTracker vt(VelocityTracker::Strategy::IMPULSE); - std::vector events = createMotionEventStream(motions); - for (const MotionEvent& event : events) { - vt.addMovement(&event); - } - - float maxFloat = std::numeric_limits::max(); - VelocityTracker::ComputedVelocity computedVelocity; - vt.populateComputedVelocity(computedVelocity, 1000 /* units */, maxFloat); - checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)), - 764.345703); - - // Expect X velocity to be scaled with respective to provided units. - vt.populateComputedVelocity(computedVelocity, 1000000 /* units */, maxFloat); - checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)), - 764345.703); - - // Expect X velocity to be clamped by provided max velocity. - vt.populateComputedVelocity(computedVelocity, 1000000 /* units */, 1000); - checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)), 1000); - - // All 0 data for Y; expect 0 velocity. - EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, DEFAULT_POINTER_ID)), 0); - - // No data for scroll-axis; expect empty velocity. - EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_SCROLL, DEFAULT_POINTER_ID)); -} - TEST_F(VelocityTrackerTest, ThreePointsPositiveVelocityTest) { // Same coordinate is reported 2 times in a row // It is difficult to determine the correct answer here, but at least the direction diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 8c241f2f09..539e24a098 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -2712,18 +2712,17 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi // Update the velocity tracker. { - std::vector positionsX; - std::vector positionsY; + std::vector positions; for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) { uint32_t id = idBits.clearFirstMarkedBit(); const RawPointerData::Pointer& pointer = mCurrentRawState.rawPointerData.pointerForId(id); - positionsX.push_back(pointer.x * mPointerXMovementScale); - positionsY.push_back(pointer.y * mPointerYMovementScale); + float x = pointer.x * mPointerXMovementScale; + float y = pointer.y * mPointerYMovementScale; + positions.push_back({x, y}); } mPointerGesture.velocityTracker.addMovement(when, mCurrentCookedState.fingerIdBits, - {{AMOTION_EVENT_AXIS_X, positionsX}, - {AMOTION_EVENT_AXIS_Y, positionsY}}); + positions); } // If the gesture ever enters a mode other than TAP, HOVER or TAP_DRAG, without first returning @@ -2830,12 +2829,9 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed; for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) { uint32_t id = idBits.clearFirstMarkedBit(); - std::optional vx = - mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, id); - std::optional vy = - mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, id); - if (vx && vy) { - float speed = hypotf(*vx, *vy); + float vx, vy; + if (mPointerGesture.velocityTracker.getVelocity(id, &vx, &vy)) { + float speed = hypotf(vx, vy); if (speed > bestSpeed) { bestId = id; bestSpeed = speed; -- GitLab From 384ab0fa6432f8b61ae4cd990480fe6a16228dd0 Mon Sep 17 00:00:00 2001 From: Yeabkal Wubshit Date: Fri, 9 Sep 2022 16:39:18 +0000 Subject: [PATCH 0277/1310] Reland "Make VelocityTracker 1D" (--try 2) This fixes a bug where stale velocities were not cleaned up during calls to get updated velocities. 86f72a3882ab4994dfbb410ebebc9ab3a75b8b16 Topic for the revert which is being rolled-forward: https://googleplex-android-review.git.corp.google.com/q/topic:revert-19762050-generic_vt-XWGGUQUHYR Bug: 32830165 Test: VelocityTracker_test unaffected (atest libinput_tests) Change-Id: Ib706e76b9502e262eabfd221bd741e5faaee1ed5 --- include/input/VelocityControl.h | 2 +- include/input/VelocityTracker.h | 118 ++++--- libs/input/VelocityControl.cpp | 26 +- libs/input/VelocityTracker.cpp | 315 +++++++++--------- libs/input/tests/VelocityTracker_test.cpp | 107 +++++- .../reader/mapper/TouchInputMapper.cpp | 20 +- 6 files changed, 354 insertions(+), 234 deletions(-) diff --git a/include/input/VelocityControl.h b/include/input/VelocityControl.h index 1acc2aef70..f4c7061ad1 100644 --- a/include/input/VelocityControl.h +++ b/include/input/VelocityControl.h @@ -98,7 +98,7 @@ private: VelocityControlParameters mParameters; nsecs_t mLastMovementTime; - VelocityTracker::Position mRawPosition; + float mRawPositionX, mRawPositionY; VelocityTracker mVelocityTracker; }; diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h index 886f1f7753..53603c54cc 100644 --- a/include/input/VelocityTracker.h +++ b/include/input/VelocityTracker.h @@ -20,6 +20,8 @@ #include #include #include +#include +#include namespace android { @@ -46,18 +48,14 @@ public: MAX = LEGACY, }; - struct Position { - float x, y; - }; - struct Estimator { static const size_t MAX_DEGREE = 4; // Estimator time base. nsecs_t time; - // Polynomial coefficients describing motion in X and Y. - float xCoeff[MAX_DEGREE + 1], yCoeff[MAX_DEGREE + 1]; + // Polynomial coefficients describing motion. + float coeff[MAX_DEGREE + 1]; // Polynomial degree (number of coefficients), or zero if no information is // available. @@ -71,14 +69,40 @@ public: degree = 0; confidence = 0; for (size_t i = 0; i <= MAX_DEGREE; i++) { - xCoeff[i] = 0; - yCoeff[i] = 0; + coeff[i] = 0; + } + } + }; + + /* + * Contains all available velocity data from a VelocityTracker. + */ + struct ComputedVelocity { + inline std::optional getVelocity(int32_t axis, uint32_t id) const { + const auto& axisVelocities = mVelocities.find(axis); + if (axisVelocities == mVelocities.end()) { + return {}; + } + + const auto& axisIdVelocity = axisVelocities->second.find(id); + if (axisIdVelocity == axisVelocities->second.end()) { + return {}; } + + return axisIdVelocity->second; + } + + inline void addVelocity(int32_t axis, uint32_t id, float velocity) { + mVelocities[axis][id] = velocity; } + + private: + std::map> mVelocities; }; - // Creates a velocity tracker using the specified strategy. + // Creates a velocity tracker using the specified strategy for each supported axis. // If strategy is not provided, uses the default strategy for the platform. + // TODO(b/32830165): support axis-specific strategies. VelocityTracker(const Strategy strategy = Strategy::DEFAULT); ~VelocityTracker(); @@ -92,45 +116,56 @@ public: void clearPointers(BitSet32 idBits); // Adds movement information for a set of pointers. - // The idBits bitfield specifies the pointer ids of the pointers whose positions + // The idBits bitfield specifies the pointer ids of the pointers whose data points // are included in the movement. - // The positions array contains position information for each pointer in order by - // increasing id. Its size should be equal to the number of one bits in idBits. - void addMovement(nsecs_t eventTime, BitSet32 idBits, const std::vector& positions); + // The positions map contains a mapping of an axis to positions array. + // The positions arrays contain information for each pointer in order by increasing id. + // Each array's size should be equal to the number of one bits in idBits. + void addMovement(nsecs_t eventTime, BitSet32 idBits, + const std::map>& positions); // Adds movement information for all pointers in a MotionEvent, including historical samples. void addMovement(const MotionEvent* event); - // Gets the velocity of the specified pointer id in position units per second. - // Returns false and sets the velocity components to zero if there is - // insufficient movement information for the pointer. - bool getVelocity(uint32_t id, float* outVx, float* outVy) const; + // Returns the velocity of the specified pointer id and axis in position units per second. + // Returns empty optional if there is insufficient movement information for the pointer, or if + // the given axis is not supported for velocity tracking. + std::optional getVelocity(int32_t axis, uint32_t id) const; - // Gets an estimator for the recent movements of the specified pointer id. + // Returns a ComputedVelocity instance with all available velocity data, using the given units + // (reference: units == 1 means "per millisecond"), and clamping each velocity between + // [-maxVelocity, maxVelocity], inclusive. + ComputedVelocity getComputedVelocity(int32_t units, float maxVelocity); + + // Gets an estimator for the recent movements of the specified pointer id for the given axis. // Returns false and clears the estimator if there is no information available // about the pointer. - bool getEstimator(uint32_t id, Estimator* outEstimator) const; + bool getEstimator(int32_t axis, uint32_t id, Estimator* outEstimator) const; // Gets the active pointer id, or -1 if none. inline int32_t getActivePointerId() const { return mActivePointerId; } - // Gets a bitset containing all pointer ids from the most recent movement. - inline BitSet32 getCurrentPointerIdBits() const { return mCurrentPointerIdBits; } - private: // The default velocity tracker strategy. // Although other strategies are available for testing and comparison purposes, // this is the strategy that applications will actually use. Be very careful // when adjusting the default strategy because it can dramatically affect // (often in a bad way) the user experience. + // TODO(b/32830165): define default strategy per axis. static const Strategy DEFAULT_STRATEGY = Strategy::LSQ2; + // Set of all axes supported for velocity tracking. + static const std::set SUPPORTED_AXES; + + // Axes specifying location on a 2D plane (i.e. X and Y). + static const std::set PLANAR_AXES; + nsecs_t mLastEventTime; BitSet32 mCurrentPointerIdBits; int32_t mActivePointerId; - std::unique_ptr mStrategy; + std::map> mStrategies; - bool configureStrategy(const Strategy strategy); + void configureStrategy(int32_t axis, const Strategy strategy); static std::unique_ptr createStrategy(const Strategy strategy); }; @@ -149,7 +184,7 @@ public: virtual void clear() = 0; virtual void clearPointers(BitSet32 idBits) = 0; virtual void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) = 0; + const std::vector& positions) = 0; virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const = 0; }; @@ -181,7 +216,7 @@ public: virtual void clear(); virtual void clearPointers(BitSet32 idBits); void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) override; + const std::vector& positions) override; virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const; private: @@ -196,11 +231,9 @@ private: struct Movement { nsecs_t eventTime; BitSet32 idBits; - VelocityTracker::Position positions[MAX_POINTERS]; + float positions[MAX_POINTERS]; - inline const VelocityTracker::Position& getPosition(uint32_t id) const { - return positions[idBits.getIndexOfBit(id)]; - } + inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; } }; float chooseWeight(uint32_t index) const; @@ -224,7 +257,7 @@ public: virtual void clear(); virtual void clearPointers(BitSet32 idBits); void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) override; + const std::vector& positions) override; virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const; private: @@ -233,16 +266,15 @@ private: nsecs_t updateTime; uint32_t degree; - float xpos, xvel, xaccel; - float ypos, yvel, yaccel; + float pos, vel, accel; }; const uint32_t mDegree; BitSet32 mPointerIdBits; State mPointerState[MAX_POINTER_ID + 1]; - void initState(State& state, nsecs_t eventTime, float xpos, float ypos) const; - void updateState(State& state, nsecs_t eventTime, float xpos, float ypos) const; + void initState(State& state, nsecs_t eventTime, float pos) const; + void updateState(State& state, nsecs_t eventTime, float pos) const; void populateEstimator(const State& state, VelocityTracker::Estimator* outEstimator) const; }; @@ -258,7 +290,7 @@ public: virtual void clear(); virtual void clearPointers(BitSet32 idBits); void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) override; + const std::vector& positions) override; virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const; private: @@ -274,11 +306,9 @@ private: struct Movement { nsecs_t eventTime; BitSet32 idBits; - VelocityTracker::Position positions[MAX_POINTERS]; + float positions[MAX_POINTERS]; - inline const VelocityTracker::Position& getPosition(uint32_t id) const { - return positions[idBits.getIndexOfBit(id)]; - } + inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; } }; uint32_t mIndex; @@ -293,7 +323,7 @@ public: virtual void clear(); virtual void clearPointers(BitSet32 idBits); void addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) override; + const std::vector& positions) override; virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const; private: @@ -308,11 +338,9 @@ private: struct Movement { nsecs_t eventTime; BitSet32 idBits; - VelocityTracker::Position positions[MAX_POINTERS]; + float positions[MAX_POINTERS]; - inline const VelocityTracker::Position& getPosition(uint32_t id) const { - return positions[idBits.getIndexOfBit(id)]; - } + inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; } }; size_t mIndex; diff --git a/libs/input/VelocityControl.cpp b/libs/input/VelocityControl.cpp index 6e991e98bb..e2bfb508e1 100644 --- a/libs/input/VelocityControl.cpp +++ b/libs/input/VelocityControl.cpp @@ -44,8 +44,8 @@ void VelocityControl::setParameters(const VelocityControlParameters& parameters) void VelocityControl::reset() { mLastMovementTime = LLONG_MIN; - mRawPosition.x = 0; - mRawPosition.y = 0; + mRawPositionX = 0; + mRawPositionY = 0; mVelocityTracker.clear(); } @@ -61,17 +61,20 @@ void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) { mLastMovementTime = eventTime; if (deltaX) { - mRawPosition.x += *deltaX; + mRawPositionX += *deltaX; } if (deltaY) { - mRawPosition.y += *deltaY; + mRawPositionY += *deltaY; } - mVelocityTracker.addMovement(eventTime, BitSet32(BitSet32::valueForBit(0)), {mRawPosition}); + mVelocityTracker.addMovement(eventTime, BitSet32(BitSet32::valueForBit(0)), + {{AMOTION_EVENT_AXIS_X, {mRawPositionX}}, + {AMOTION_EVENT_AXIS_Y, {mRawPositionY}}}); - float vx, vy; + std::optional vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0); + std::optional vy = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, 0); float scale = mParameters.scale; - if (mVelocityTracker.getVelocity(0, &vx, &vy)) { - float speed = hypotf(vx, vy) * scale; + if (vx && vy) { + float speed = hypotf(*vx, *vy) * scale; if (speed >= mParameters.highThreshold) { // Apply full acceleration above the high speed threshold. scale *= mParameters.acceleration; @@ -85,10 +88,9 @@ void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) { if (DEBUG_ACCELERATION) { ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): " - "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f", - mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold, - mParameters.acceleration, - vx, vy, speed, scale / mParameters.scale); + "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f", + mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold, + mParameters.acceleration, *vx, *vy, speed, scale / mParameters.scale); } } else { diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp index 76aaf61da7..6f88a3860b 100644 --- a/libs/input/VelocityTracker.cpp +++ b/libs/input/VelocityTracker.cpp @@ -125,29 +125,39 @@ static std::string matrixToString(const float* a, uint32_t m, uint32_t n, bool r // --- VelocityTracker --- +const std::set VelocityTracker::SUPPORTED_AXES = {AMOTION_EVENT_AXIS_X, + AMOTION_EVENT_AXIS_Y}; + +const std::set VelocityTracker::PLANAR_AXES = {AMOTION_EVENT_AXIS_X, AMOTION_EVENT_AXIS_Y}; + VelocityTracker::VelocityTracker(const Strategy strategy) : mLastEventTime(0), mCurrentPointerIdBits(0), mActivePointerId(-1) { - // Configure the strategy. - if (!configureStrategy(strategy)) { - ALOGE("Unrecognized velocity tracker strategy %" PRId32 ".", strategy); - if (!configureStrategy(VelocityTracker::DEFAULT_STRATEGY)) { - LOG_ALWAYS_FATAL("Could not create the default velocity tracker strategy '%" PRId32 - "'!", - strategy); - } + // Configure the strategy for each axis. + for (int32_t axis : SUPPORTED_AXES) { + configureStrategy(axis, strategy); } } VelocityTracker::~VelocityTracker() { } -bool VelocityTracker::configureStrategy(Strategy strategy) { +void VelocityTracker::configureStrategy(int32_t axis, const Strategy strategy) { + std::unique_ptr createdStrategy; + if (strategy == VelocityTracker::Strategy::DEFAULT) { - mStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY); + createdStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY); } else { - mStrategy = createStrategy(strategy); + createdStrategy = createStrategy(strategy); + } + + if (createdStrategy == nullptr) { + ALOGE("Unrecognized velocity tracker strategy %" PRId32 ".", strategy); + createdStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY); + LOG_ALWAYS_FATAL_IF(createdStrategy == nullptr, + "Could not create the default velocity tracker strategy '%" PRId32 "'!", + strategy); } - return mStrategy != nullptr; + mStrategies[axis] = std::move(createdStrategy); } std::unique_ptr VelocityTracker::createStrategy( @@ -201,8 +211,9 @@ std::unique_ptr VelocityTracker::createStrategy( void VelocityTracker::clear() { mCurrentPointerIdBits.clear(); mActivePointerId = -1; - - mStrategy->clear(); + for (int32_t axis : SUPPORTED_AXES) { + mStrategies[axis]->clear(); + } } void VelocityTracker::clearPointers(BitSet32 idBits) { @@ -213,14 +224,13 @@ void VelocityTracker::clearPointers(BitSet32 idBits) { mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1; } - mStrategy->clearPointers(idBits); + for (int32_t axis : SUPPORTED_AXES) { + mStrategies[axis]->clearPointers(idBits); + } } void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { - LOG_ALWAYS_FATAL_IF(idBits.count() != positions.size(), - "Mismatching number of pointers, idBits=%" PRIu32 ", positions=%zu", - idBits.count(), positions.size()); + const std::map>& positions) { while (idBits.count() > MAX_POINTERS) { idBits.clearLastMarkedBit(); } @@ -232,7 +242,9 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, // We have not received any movements for too long. Assume that all pointers // have stopped. - mStrategy->clear(); + for (const auto& [_, strategy] : mStrategies) { + strategy->clear(); + } } mLastEventTime = eventTime; @@ -241,29 +253,37 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, mActivePointerId = idBits.isEmpty() ? -1 : idBits.firstMarkedBit(); } - mStrategy->addMovement(eventTime, idBits, positions); + for (const auto& [axis, positionValues] : positions) { + LOG_ALWAYS_FATAL_IF(idBits.count() != positionValues.size(), + "Mismatching number of pointers, idBits=%" PRIu32 ", positions=%zu", + idBits.count(), positionValues.size()); + mStrategies[axis]->addMovement(eventTime, idBits, positionValues); + } if (DEBUG_VELOCITY) { ALOGD("VelocityTracker: addMovement eventTime=%" PRId64 ", idBits=0x%08x, activePointerId=%d", eventTime, idBits.value, mActivePointerId); - for (BitSet32 iterBits(idBits); !iterBits.isEmpty();) { - uint32_t id = iterBits.firstMarkedBit(); - uint32_t index = idBits.getIndexOfBit(id); - iterBits.clearBit(id); - Estimator estimator; - getEstimator(id, &estimator); - ALOGD(" %d: position (%0.3f, %0.3f), " - "estimator (degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f)", - id, positions[index].x, positions[index].y, int(estimator.degree), - vectorToString(estimator.xCoeff, estimator.degree + 1).c_str(), - vectorToString(estimator.yCoeff, estimator.degree + 1).c_str(), - estimator.confidence); + for (const auto& positionsEntry : positions) { + for (BitSet32 iterBits(idBits); !iterBits.isEmpty();) { + uint32_t id = iterBits.firstMarkedBit(); + uint32_t index = idBits.getIndexOfBit(id); + iterBits.clearBit(id); + Estimator estimator; + getEstimator(positionsEntry.first, id, &estimator); + ALOGD(" %d: axis=%d, position=%0.3f, " + "estimator (degree=%d, coeff=%s, confidence=%f)", + id, positionsEntry.first, positionsEntry.second[index], int(estimator.degree), + vectorToString(estimator.coeff, estimator.degree + 1).c_str(), + estimator.confidence); + } } } } void VelocityTracker::addMovement(const MotionEvent* event) { + // Stores data about which axes to process based on the incoming motion event. + std::set axesToProcess; int32_t actionMasked = event->getActionMasked(); switch (actionMasked) { @@ -271,6 +291,9 @@ void VelocityTracker::addMovement(const MotionEvent* event) { case AMOTION_EVENT_ACTION_HOVER_ENTER: // Clear all pointers on down before adding the new movement. clear(); + for (int32_t axis : PLANAR_AXES) { + axesToProcess.insert(axis); + } break; case AMOTION_EVENT_ACTION_POINTER_DOWN: { // Start a new movement trace for a pointer that just went down. @@ -279,10 +302,16 @@ void VelocityTracker::addMovement(const MotionEvent* event) { BitSet32 downIdBits; downIdBits.markBit(event->getPointerId(event->getActionIndex())); clearPointers(downIdBits); + for (int32_t axis : PLANAR_AXES) { + axesToProcess.insert(axis); + } break; } case AMOTION_EVENT_ACTION_MOVE: case AMOTION_EVENT_ACTION_HOVER_MOVE: + for (int32_t axis : PLANAR_AXES) { + axesToProcess.insert(axis); + } break; case AMOTION_EVENT_ACTION_POINTER_UP: case AMOTION_EVENT_ACTION_UP: { @@ -293,7 +322,9 @@ void VelocityTracker::addMovement(const MotionEvent* event) { toString(delaySinceLastEvent).c_str()); // We have not received any movements for too long. Assume that all pointers // have stopped. - mStrategy->clear(); + for (int32_t axis : PLANAR_AXES) { + mStrategies[axis]->clear(); + } } // These actions because they do not convey any new information about // pointer movement. We also want to preserve the last known velocity of the pointers. @@ -325,37 +356,56 @@ void VelocityTracker::addMovement(const MotionEvent* event) { pointerIndex[i] = idBits.getIndexOfBit(event->getPointerId(i)); } - std::vector positions; - positions.resize(pointerCount); + std::map> positions; + for (int32_t axis : axesToProcess) { + positions[axis].resize(pointerCount); + } size_t historySize = event->getHistorySize(); for (size_t h = 0; h <= historySize; h++) { nsecs_t eventTime = event->getHistoricalEventTime(h); - for (size_t i = 0; i < pointerCount; i++) { - uint32_t index = pointerIndex[i]; - positions[index].x = event->getHistoricalX(i, h); - positions[index].y = event->getHistoricalY(i, h); + for (int32_t axis : axesToProcess) { + for (size_t i = 0; i < pointerCount; i++) { + positions[axis][pointerIndex[i]] = event->getHistoricalAxisValue(axis, i, h); + } } addMovement(eventTime, idBits, positions); } } -bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const { +std::optional VelocityTracker::getVelocity(int32_t axis, uint32_t id) const { Estimator estimator; - if (getEstimator(id, &estimator) && estimator.degree >= 1) { - *outVx = estimator.xCoeff[1]; - *outVy = estimator.yCoeff[1]; - return true; + bool validVelocity = getEstimator(axis, id, &estimator) && estimator.degree >= 1; + if (validVelocity) { + return estimator.coeff[1]; } - *outVx = 0; - *outVy = 0; - return false; + return {}; } -bool VelocityTracker::getEstimator(uint32_t id, Estimator* outEstimator) const { - return mStrategy->getEstimator(id, outEstimator); +VelocityTracker::ComputedVelocity VelocityTracker::getComputedVelocity(int32_t units, + float maxVelocity) { + ComputedVelocity computedVelocity; + for (int32_t axis : SUPPORTED_AXES) { + BitSet32 copyIdBits = BitSet32(mCurrentPointerIdBits); + while (!copyIdBits.isEmpty()) { + uint32_t id = copyIdBits.clearFirstMarkedBit(); + std::optional velocity = getVelocity(axis, id); + if (velocity) { + float adjustedVelocity = + std::clamp(*velocity * units / 1000, -maxVelocity, maxVelocity); + computedVelocity.addVelocity(axis, id, adjustedVelocity); + } + } + } + return computedVelocity; } +bool VelocityTracker::getEstimator(int32_t axis, uint32_t id, Estimator* outEstimator) const { + if (SUPPORTED_AXES.find(axis) == SUPPORTED_AXES.end()) { + return false; + } + return mStrategies.at(axis)->getEstimator(id, outEstimator); +} // --- LeastSquaresVelocityTrackerStrategy --- @@ -378,9 +428,8 @@ void LeastSquaresVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { mMovements[mIndex].idBits = remainingIdBits; } -void LeastSquaresVelocityTrackerStrategy::addMovement( - nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { +void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, + const std::vector& positions) { if (mMovements[mIndex].eventTime != eventTime) { // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include @@ -627,8 +676,7 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id, outEstimator->clear(); // Iterate over movement samples in reverse time order and collect samples. - std::vector x; - std::vector y; + std::vector positions; std::vector w; std::vector time; @@ -645,15 +693,13 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id, break; } - const VelocityTracker::Position& position = movement.getPosition(id); - x.push_back(position.x); - y.push_back(position.y); + positions.push_back(movement.getPosition(id)); w.push_back(chooseWeight(index)); time.push_back(-age * 0.000000001f); index = (index == 0 ? HISTORY_SIZE : index) - 1; - } while (x.size() < HISTORY_SIZE); + } while (positions.size() < HISTORY_SIZE); - const size_t m = x.size(); + const size_t m = positions.size(); if (m == 0) { return false; // no data } @@ -666,39 +712,36 @@ bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id, if (degree == 2 && mWeighting == WEIGHTING_NONE) { // Optimize unweighted, quadratic polynomial fit - std::optional> xCoeff = solveUnweightedLeastSquaresDeg2(time, x); - std::optional> yCoeff = solveUnweightedLeastSquaresDeg2(time, y); - if (xCoeff && yCoeff) { + std::optional> coeff = + solveUnweightedLeastSquaresDeg2(time, positions); + if (coeff) { outEstimator->time = newestMovement.eventTime; outEstimator->degree = 2; outEstimator->confidence = 1; for (size_t i = 0; i <= outEstimator->degree; i++) { - outEstimator->xCoeff[i] = (*xCoeff)[i]; - outEstimator->yCoeff[i] = (*yCoeff)[i]; + outEstimator->coeff[i] = (*coeff)[i]; } return true; } } else if (degree >= 1) { // General case for an Nth degree polynomial fit - float xdet, ydet; + float det; uint32_t n = degree + 1; - if (solveLeastSquares(time, x, w, n, outEstimator->xCoeff, &xdet) && - solveLeastSquares(time, y, w, n, outEstimator->yCoeff, &ydet)) { + if (solveLeastSquares(time, positions, w, n, outEstimator->coeff, &det)) { outEstimator->time = newestMovement.eventTime; outEstimator->degree = degree; - outEstimator->confidence = xdet * ydet; + outEstimator->confidence = det; - ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f", - int(outEstimator->degree), vectorToString(outEstimator->xCoeff, n).c_str(), - vectorToString(outEstimator->yCoeff, n).c_str(), outEstimator->confidence); + ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, coeff=%s, confidence=%f", + int(outEstimator->degree), vectorToString(outEstimator->coeff, n).c_str(), + outEstimator->confidence); return true; } } // No velocity data available for this pointer, but we do have its current position. - outEstimator->xCoeff[0] = x[0]; - outEstimator->yCoeff[0] = y[0]; + outEstimator->coeff[0] = positions[0]; outEstimator->time = newestMovement.eventTime; outEstimator->degree = 0; outEstimator->confidence = 1; @@ -790,18 +833,17 @@ void IntegratingVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { mPointerIdBits.value &= ~idBits.value; } -void IntegratingVelocityTrackerStrategy::addMovement( - nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { +void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, + const std::vector& positions) { uint32_t index = 0; for (BitSet32 iterIdBits(idBits); !iterIdBits.isEmpty();) { uint32_t id = iterIdBits.clearFirstMarkedBit(); State& state = mPointerState[id]; - const VelocityTracker::Position& position = positions[index++]; + const float position = positions[index++]; if (mPointerIdBits.hasBit(id)) { - updateState(state, eventTime, position.x, position.y); + updateState(state, eventTime, position); } else { - initState(state, eventTime, position.x, position.y); + initState(state, eventTime, position); } } @@ -821,21 +863,18 @@ bool IntegratingVelocityTrackerStrategy::getEstimator(uint32_t id, return false; } -void IntegratingVelocityTrackerStrategy::initState(State& state, - nsecs_t eventTime, float xpos, float ypos) const { +void IntegratingVelocityTrackerStrategy::initState(State& state, nsecs_t eventTime, + float pos) const { state.updateTime = eventTime; state.degree = 0; - state.xpos = xpos; - state.xvel = 0; - state.xaccel = 0; - state.ypos = ypos; - state.yvel = 0; - state.yaccel = 0; + state.pos = pos; + state.accel = 0; + state.vel = 0; } -void IntegratingVelocityTrackerStrategy::updateState(State& state, - nsecs_t eventTime, float xpos, float ypos) const { +void IntegratingVelocityTrackerStrategy::updateState(State& state, nsecs_t eventTime, + float pos) const { const nsecs_t MIN_TIME_DELTA = 2 * NANOS_PER_MS; const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds @@ -846,34 +885,26 @@ void IntegratingVelocityTrackerStrategy::updateState(State& state, float dt = (eventTime - state.updateTime) * 0.000000001f; state.updateTime = eventTime; - float xvel = (xpos - state.xpos) / dt; - float yvel = (ypos - state.ypos) / dt; + float vel = (pos - state.pos) / dt; if (state.degree == 0) { - state.xvel = xvel; - state.yvel = yvel; + state.vel = vel; state.degree = 1; } else { float alpha = dt / (FILTER_TIME_CONSTANT + dt); if (mDegree == 1) { - state.xvel += (xvel - state.xvel) * alpha; - state.yvel += (yvel - state.yvel) * alpha; + state.vel += (vel - state.vel) * alpha; } else { - float xaccel = (xvel - state.xvel) / dt; - float yaccel = (yvel - state.yvel) / dt; + float accel = (vel - state.vel) / dt; if (state.degree == 1) { - state.xaccel = xaccel; - state.yaccel = yaccel; + state.accel = accel; state.degree = 2; } else { - state.xaccel += (xaccel - state.xaccel) * alpha; - state.yaccel += (yaccel - state.yaccel) * alpha; + state.accel += (accel - state.accel) * alpha; } - state.xvel += (state.xaccel * dt) * alpha; - state.yvel += (state.yaccel * dt) * alpha; + state.vel += (state.accel * dt) * alpha; } } - state.xpos = xpos; - state.ypos = ypos; + state.pos = pos; } void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state, @@ -881,12 +912,9 @@ void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state, outEstimator->time = state.updateTime; outEstimator->confidence = 1.0f; outEstimator->degree = state.degree; - outEstimator->xCoeff[0] = state.xpos; - outEstimator->xCoeff[1] = state.xvel; - outEstimator->xCoeff[2] = state.xaccel / 2; - outEstimator->yCoeff[0] = state.ypos; - outEstimator->yCoeff[1] = state.yvel; - outEstimator->yCoeff[2] = state.yaccel / 2; + outEstimator->coeff[0] = state.pos; + outEstimator->coeff[1] = state.vel; + outEstimator->coeff[2] = state.accel / 2; } @@ -909,9 +937,8 @@ void LegacyVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { mMovements[mIndex].idBits = remainingIdBits; } -void LegacyVelocityTrackerStrategy::addMovement( - nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { +void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, + const std::vector& positions) { if (++mIndex == HISTORY_SIZE) { mIndex = 0; } @@ -959,12 +986,11 @@ bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id, // overestimate the velocity at that time point. Most samples might be measured // 16ms apart but some consecutive samples could be only 0.5sm apart because // the hardware or driver reports them irregularly or in bursts. - float accumVx = 0; - float accumVy = 0; + float accumV = 0; uint32_t index = oldestIndex; uint32_t samplesUsed = 0; const Movement& oldestMovement = mMovements[oldestIndex]; - const VelocityTracker::Position& oldestPosition = oldestMovement.getPosition(id); + float oldestPosition = oldestMovement.getPosition(id); nsecs_t lastDuration = 0; while (numTouches-- > 1) { @@ -978,26 +1004,22 @@ bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id, // the velocity. Consequently, we impose a minimum duration constraint on the // samples that we include in the calculation. if (duration >= MIN_DURATION) { - const VelocityTracker::Position& position = movement.getPosition(id); + float position = movement.getPosition(id); float scale = 1000000000.0f / duration; // one over time delta in seconds - float vx = (position.x - oldestPosition.x) * scale; - float vy = (position.y - oldestPosition.y) * scale; - accumVx = (accumVx * lastDuration + vx * duration) / (duration + lastDuration); - accumVy = (accumVy * lastDuration + vy * duration) / (duration + lastDuration); + float v = (position - oldestPosition) * scale; + accumV = (accumV * lastDuration + v * duration) / (duration + lastDuration); lastDuration = duration; samplesUsed += 1; } } // Report velocity. - const VelocityTracker::Position& newestPosition = newestMovement.getPosition(id); + float newestPosition = newestMovement.getPosition(id); outEstimator->time = newestMovement.eventTime; outEstimator->confidence = 1; - outEstimator->xCoeff[0] = newestPosition.x; - outEstimator->yCoeff[0] = newestPosition.y; + outEstimator->coeff[0] = newestPosition; if (samplesUsed) { - outEstimator->xCoeff[1] = accumVx; - outEstimator->yCoeff[1] = accumVy; + outEstimator->coeff[1] = accumV; outEstimator->degree = 1; } else { outEstimator->degree = 0; @@ -1024,9 +1046,8 @@ void ImpulseVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { mMovements[mIndex].idBits = remainingIdBits; } -void ImpulseVelocityTrackerStrategy::addMovement( - nsecs_t eventTime, BitSet32 idBits, - const std::vector& positions) { +void ImpulseVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, + const std::vector& positions) { if (mMovements[mIndex].eventTime != eventTime) { // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include @@ -1163,8 +1184,7 @@ bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, outEstimator->clear(); // Iterate over movement samples in reverse time order and collect samples. - float x[HISTORY_SIZE]; - float y[HISTORY_SIZE]; + float positions[HISTORY_SIZE]; nsecs_t time[HISTORY_SIZE]; size_t m = 0; // number of points that will be used for fitting size_t index = mIndex; @@ -1180,9 +1200,7 @@ bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, break; } - const VelocityTracker::Position& position = movement.getPosition(id); - x[m] = position.x; - y[m] = position.y; + positions[m] = movement.getPosition(id); time[m] = movement.eventTime; index = (index == 0 ? HISTORY_SIZE : index) - 1; } while (++m < HISTORY_SIZE); @@ -1190,33 +1208,30 @@ bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, if (m == 0) { return false; // no data } - outEstimator->xCoeff[0] = 0; - outEstimator->yCoeff[0] = 0; - outEstimator->xCoeff[1] = calculateImpulseVelocity(time, x, m); - outEstimator->yCoeff[1] = calculateImpulseVelocity(time, y, m); - outEstimator->xCoeff[2] = 0; - outEstimator->yCoeff[2] = 0; + outEstimator->coeff[0] = 0; + outEstimator->coeff[1] = calculateImpulseVelocity(time, positions, m); + outEstimator->coeff[2] = 0; + outEstimator->time = newestMovement.eventTime; outEstimator->degree = 2; // similar results to 2nd degree fit outEstimator->confidence = 1; - ALOGD_IF(DEBUG_STRATEGY, "velocity: (%.1f, %.1f)", outEstimator->xCoeff[1], - outEstimator->yCoeff[1]); + ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", outEstimator->coeff[1]); if (DEBUG_IMPULSE) { // TODO(b/134179997): delete this block once the switch to 'impulse' is complete. - // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons + // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons. + // X axis chosen arbitrarily for velocity comparisons. VelocityTracker lsq2(VelocityTracker::Strategy::LSQ2); BitSet32 idBits; const uint32_t pointerId = 0; idBits.markBit(pointerId); for (ssize_t i = m - 1; i >= 0; i--) { - lsq2.addMovement(time[i], idBits, {{x[i], y[i]}}); + lsq2.addMovement(time[i], idBits, {{AMOTION_EVENT_AXIS_X, {positions[i]}}}); } - float outVx = 0, outVy = 0; - const bool computed = lsq2.getVelocity(pointerId, &outVx, &outVy); - if (computed) { - ALOGD("lsq2 velocity: (%.1f, %.1f)", outVx, outVy); + std::optional v = lsq2.getVelocity(AMOTION_EVENT_AXIS_X, pointerId); + if (v) { + ALOGD("lsq2 velocity: %.1f", *v); } else { ALOGD("lsq2 velocity: could not compute velocity"); } diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp index 4a445de3ac..f8e505299c 100644 --- a/libs/input/tests/VelocityTracker_test.cpp +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -16,9 +16,10 @@ #define LOG_TAG "VelocityTracker_test" +#include #include #include -#include +#include #include #include @@ -198,25 +199,13 @@ static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, const std::vector& motions, int32_t axis, float targetVelocity, uint32_t pointerId = DEFAULT_POINTER_ID) { VelocityTracker vt(strategy); - float Vx, Vy; std::vector events = createMotionEventStream(motions); for (MotionEvent event : events) { vt.addMovement(&event); } - vt.getVelocity(pointerId, &Vx, &Vy); - - switch (axis) { - case AMOTION_EVENT_AXIS_X: - checkVelocity(Vx, targetVelocity); - break; - case AMOTION_EVENT_AXIS_Y: - checkVelocity(Vy, targetVelocity); - break; - default: - FAIL() << "Axis must be either AMOTION_EVENT_AXIS_X or AMOTION_EVENT_AXIS_Y"; - } + checkVelocity(vt.getVelocity(axis, pointerId).value_or(0), targetVelocity); } static void computeAndCheckQuadraticEstimate(const std::vector& motions, @@ -226,17 +215,99 @@ static void computeAndCheckQuadraticEstimate(const std::vector for (MotionEvent event : events) { vt.addMovement(&event); } - VelocityTracker::Estimator estimator; - EXPECT_TRUE(vt.getEstimator(0, &estimator)); + VelocityTracker::Estimator estimatorX; + VelocityTracker::Estimator estimatorY; + EXPECT_TRUE(vt.getEstimator(AMOTION_EVENT_AXIS_X, 0, &estimatorX)); + EXPECT_TRUE(vt.getEstimator(AMOTION_EVENT_AXIS_Y, 0, &estimatorY)); for (size_t i = 0; i< coefficients.size(); i++) { - checkCoefficient(estimator.xCoeff[i], coefficients[i]); - checkCoefficient(estimator.yCoeff[i], coefficients[i]); + checkCoefficient(estimatorX.coeff[i], coefficients[i]); + checkCoefficient(estimatorY.coeff[i], coefficients[i]); } } /* * ================== VelocityTracker tests generated manually ===================================== */ +TEST_F(VelocityTrackerTest, TestComputedVelocity) { + VelocityTracker::ComputedVelocity computedVelocity; + + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, 0 /*id*/, 200 /*velocity*/); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, 26U /*id*/, 400 /*velocity*/); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, 27U /*id*/, 650 /*velocity*/); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID, 750 /*velocity*/); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, 0 /*id*/, 1000 /*velocity*/); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, 26U /*id*/, 2000 /*velocity*/); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, 27U /*id*/, 3000 /*velocity*/); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, MAX_POINTER_ID, 4000 /*velocity*/); + + // Check the axes/indices with velocity. + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, 0U /*id*/)), 200); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, 26U /*id*/)), 400); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, 27U /*id*/)), 650); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID)), 750); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, 0U /*id*/)), 1000); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, 26U /*id*/)), 2000); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, 27U /*id*/)), 3000); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, MAX_POINTER_ID)), 4000); + for (uint32_t id = 0; id <= MAX_POINTER_ID; id++) { + // Since no data was added for AXIS_SCROLL, expect empty value for the axis for any id. + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_SCROLL, id)) + << "Empty scroll data expected at id=" << id; + if (id == 0 || id == 26U || id == 27U || id == MAX_POINTER_ID) { + // Already checked above; continue. + continue; + } + // No data was added to X/Y for this id, expect empty value. + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, id)) + << "Empty X data expected at id=" << id; + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, id)) + << "Empty Y data expected at id=" << id; + } + // Out-of-bounds ids should given empty values. + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, -1)); + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID + 1)); +} + +TEST_F(VelocityTrackerTest, TestGetComputedVelocity) { + std::vector motions = { + {235089067457000ns, {{528.00, 0}}}, {235089084684000ns, {{527.00, 0}}}, + {235089093349000ns, {{527.00, 0}}}, {235089095677625ns, {{527.00, 0}}}, + {235089101859000ns, {{527.00, 0}}}, {235089110378000ns, {{528.00, 0}}}, + {235089112497111ns, {{528.25, 0}}}, {235089118760000ns, {{531.00, 0}}}, + {235089126686000ns, {{535.00, 0}}}, {235089129316820ns, {{536.33, 0}}}, + {235089135199000ns, {{540.00, 0}}}, {235089144297000ns, {{546.00, 0}}}, + {235089146136443ns, {{547.21, 0}}}, {235089152923000ns, {{553.00, 0}}}, + {235089160784000ns, {{559.00, 0}}}, {235089162955851ns, {{560.66, 0}}}, + {235089162955851ns, {{560.66, 0}}}, // ACTION_UP + }; + VelocityTracker vt(VelocityTracker::Strategy::IMPULSE); + std::vector events = createMotionEventStream(motions); + for (const MotionEvent& event : events) { + vt.addMovement(&event); + } + + float maxFloat = std::numeric_limits::max(); + VelocityTracker::ComputedVelocity computedVelocity; + computedVelocity = vt.getComputedVelocity(1000 /* units */, maxFloat); + checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)), + 764.345703); + + // Expect X velocity to be scaled with respective to provided units. + computedVelocity = vt.getComputedVelocity(1000000 /* units */, maxFloat); + checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)), + 764345.703); + + // Expect X velocity to be clamped by provided max velocity. + computedVelocity = vt.getComputedVelocity(1000000 /* units */, 1000); + checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)), 1000); + + // All 0 data for Y; expect 0 velocity. + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, DEFAULT_POINTER_ID)), 0); + + // No data for scroll-axis; expect empty velocity. + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_SCROLL, DEFAULT_POINTER_ID)); +} + TEST_F(VelocityTrackerTest, ThreePointsPositiveVelocityTest) { // Same coordinate is reported 2 times in a row // It is difficult to determine the correct answer here, but at least the direction diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 539e24a098..8c241f2f09 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -2712,17 +2712,18 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi // Update the velocity tracker. { - std::vector positions; + std::vector positionsX; + std::vector positionsY; for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) { uint32_t id = idBits.clearFirstMarkedBit(); const RawPointerData::Pointer& pointer = mCurrentRawState.rawPointerData.pointerForId(id); - float x = pointer.x * mPointerXMovementScale; - float y = pointer.y * mPointerYMovementScale; - positions.push_back({x, y}); + positionsX.push_back(pointer.x * mPointerXMovementScale); + positionsY.push_back(pointer.y * mPointerYMovementScale); } mPointerGesture.velocityTracker.addMovement(when, mCurrentCookedState.fingerIdBits, - positions); + {{AMOTION_EVENT_AXIS_X, positionsX}, + {AMOTION_EVENT_AXIS_Y, positionsY}}); } // If the gesture ever enters a mode other than TAP, HOVER or TAP_DRAG, without first returning @@ -2829,9 +2830,12 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed; for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) { uint32_t id = idBits.clearFirstMarkedBit(); - float vx, vy; - if (mPointerGesture.velocityTracker.getVelocity(id, &vx, &vy)) { - float speed = hypotf(vx, vy); + std::optional vx = + mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, id); + std::optional vy = + mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, id); + if (vx && vy) { + float speed = hypotf(*vx, *vy); if (speed > bestSpeed) { bestId = id; bestSpeed = speed; -- GitLab From 0a4cebbcc9663f1d92bd0658726047359f4f65e8 Mon Sep 17 00:00:00 2001 From: Brian Duddie Date: Thu, 25 Aug 2022 19:20:18 +0000 Subject: [PATCH 0278/1310] Use fdsan in direct channel registration To help catch future bugs related to misuse of file descriptors. Fixes: 244214188 Test: run test-sensorservice with & without fix for bug 234456046, confirm reliable crash without the fix + no crash with it Test: atest CtsSensorTestCases Change-Id: I7aca5830e02e6bde988e89d54e7008500d0db26f --- libs/sensor/ISensorServer.cpp | 7 ++++--- services/sensorservice/SensorDevice.cpp | 1 - services/sensorservice/SensorDirectConnection.cpp | 4 ++-- services/sensorservice/SensorService.cpp | 5 +++-- services/sensorservice/tests/sensorservicetest.cpp | 11 +++++++++++ 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/libs/sensor/ISensorServer.cpp b/libs/sensor/ISensorServer.cpp index a6cacad374..78f692bb0c 100644 --- a/libs/sensor/ISensorServer.cpp +++ b/libs/sensor/ISensorServer.cpp @@ -22,12 +22,12 @@ #include #include #include -#include #include +#include -#include #include #include +#include #include #include @@ -205,9 +205,10 @@ status_t BnSensorServer::onTransact( if (resource == nullptr) { return BAD_VALUE; } + native_handle_set_fdsan_tag(resource); sp ch = createSensorDirectConnection(opPackageName, size, type, format, resource); - native_handle_close(resource); + native_handle_close_with_tag(resource); native_handle_delete(resource); reply->writeStrongBinder(IInterface::asBinder(ch)); return NO_ERROR; diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp index de050e02d0..10ca990f87 100644 --- a/services/sensorservice/SensorDevice.cpp +++ b/services/sensorservice/SensorDevice.cpp @@ -39,7 +39,6 @@ #include using namespace android::hardware::sensors; -using android::hardware::Return; using android::util::ProtoOutputStream; namespace android { diff --git a/services/sensorservice/SensorDirectConnection.cpp b/services/sensorservice/SensorDirectConnection.cpp index 2dd12e9446..291c770692 100644 --- a/services/sensorservice/SensorDirectConnection.cpp +++ b/services/sensorservice/SensorDirectConnection.cpp @@ -14,11 +14,11 @@ * limitations under the License. */ -#include "SensorDevice.h" #include "SensorDirectConnection.h" #include #include #include +#include "SensorDevice.h" #define UNUSED(x) (void)(x) @@ -51,7 +51,7 @@ void SensorService::SensorDirectConnection::destroy() { stopAll(); mService->cleanupConnection(this); if (mMem.handle != nullptr) { - native_handle_close(mMem.handle); + native_handle_close_with_tag(mMem.handle); native_handle_delete(const_cast(mMem.handle)); } mDestroyed = true; diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp index e0a4f034cb..21d6b6b16f 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -25,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -1475,6 +1475,7 @@ sp SensorService::createSensorDirectConnection( if (!clone) { return nullptr; } + native_handle_set_fdsan_tag(clone); sp conn; SensorDevice& dev(SensorDevice::getInstance()); @@ -1488,7 +1489,7 @@ sp SensorService::createSensorDirectConnection( } if (conn == nullptr) { - native_handle_close(clone); + native_handle_close_with_tag(clone); native_handle_delete(clone); } else { // add to list of direct connections diff --git a/services/sensorservice/tests/sensorservicetest.cpp b/services/sensorservice/tests/sensorservicetest.cpp index caf7f03361..b00d1a761b 100644 --- a/services/sensorservice/tests/sensorservicetest.cpp +++ b/services/sensorservice/tests/sensorservicetest.cpp @@ -89,6 +89,17 @@ void testInvalidSharedMem_NoCrash(SensorManager &mgr) { // Should print -22 (BAD_VALUE) and the device runtime shouldn't restart printf("createInvalidDirectChannel=%d\n", ret); + + // Secondary test: correct channel creation & destruction (should print 0) + ret = mgr.createDirectChannel(kMemSize, ASENSOR_DIRECT_CHANNEL_TYPE_HARDWARE_BUFFER, + resourceHandle); + printf("createValidDirectChannel=%d\n", ret); + + // Third test: double-destroy (should not crash) + mgr.destroyDirectChannel(ret); + AHardwareBuffer_release(hardwareBuffer); + printf("duplicate destroyDirectChannel...\n"); + mgr.destroyDirectChannel(ret); } int main() { -- GitLab From c13ff081db31e93ee3b0a96bb007704c61d10a02 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 8 Sep 2022 22:03:30 +0000 Subject: [PATCH 0279/1310] Resolve associated display and pointer display in CursorInputMapper An InputDevice can be associated with a display, in which case it should only generate events for that display. A mouse generally generates events for whichever display is currently showing the mouse cursor. In the case where a mouse InputDevice is associated with a display, we need to make sure that it only generates events when the mouse cursor is on the associated display. Additionally, any display-dependent configuration, such as orientation, should depend on whichever display the device is configured for. Bug: 236075874 Test: atest inputflinger_tests Change-Id: I1021c121c1eae768585124d312f5187be90da666 --- include/input/PrintTools.h | 10 ++ .../reader/mapper/CursorInputMapper.cpp | 60 +++++---- .../reader/mapper/CursorInputMapper.h | 4 + .../inputflinger/tests/InputReader_test.cpp | 115 ++++++++++++++---- 4 files changed, 142 insertions(+), 47 deletions(-) diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h index 0a75278494..55f730b287 100644 --- a/include/input/PrintTools.h +++ b/include/input/PrintTools.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include #include @@ -27,6 +28,15 @@ std::string constToString(const T& v) { return std::to_string(v); } +/** + * Convert an optional type to string. + */ +template +std::string toString(const std::optional& optional, + std::string (*toString)(const T&) = constToString) { + return optional ? toString(*optional) : ""; +} + /** * Convert a set of integral types to string. */ diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index 6058a1e5bd..d6d324b5b2 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -25,6 +25,8 @@ #include "PointerControllerInterface.h" #include "TouchCursorInputMapperCommon.h" +#include "input/PrintTools.h" + namespace android { // The default velocity control parameters that has no effect. @@ -117,6 +119,7 @@ void CursorInputMapper::dump(std::string& dump) { toString(mCursorScrollAccumulator.haveRelativeHWheel())); dump += StringPrintf(INDENT3 "VWheelScale: %0.3f\n", mVWheelScale); dump += StringPrintf(INDENT3 "HWheelScale: %0.3f\n", mHWheelScale); + dump += StringPrintf(INDENT3 "DisplayId: %s\n", toString(mDisplayId).c_str()); dump += StringPrintf(INDENT3 "Orientation: %d\n", mOrientation); dump += StringPrintf(INDENT3 "ButtonState: 0x%08x\n", mButtonState); dump += StringPrintf(INDENT3 "Down: %s\n", toString(isPointerDown(mButtonState))); @@ -205,21 +208,34 @@ void CursorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO) || configurePointerCapture) { + const bool isPointer = mParameters.mode == Parameters::Mode::POINTER; + + mDisplayId = ADISPLAY_ID_NONE; + if (auto viewport = mDeviceContext.getAssociatedViewport(); viewport) { + // This InputDevice is associated with a viewport. + // Only generate events for the associated display. + const bool mismatchedPointerDisplay = + isPointer && (viewport->displayId != mPointerController->getDisplayId()); + mDisplayId = mismatchedPointerDisplay ? std::nullopt + : std::make_optional(viewport->displayId); + } else if (isPointer) { + // The InputDevice is not associated with a viewport, but it controls the mouse pointer. + mDisplayId = mPointerController->getDisplayId(); + } + mOrientation = DISPLAY_ORIENTATION_0; const bool isOrientedDevice = (mParameters.orientationAware && mParameters.hasAssociatedDisplay); - // InputReader works in the un-rotated display coordinate space, so we don't need to do // anything if the device is already orientation-aware. If the device is not // orientation-aware, then we need to apply the inverse rotation of the display so that // when the display rotation is applied later as a part of the per-window transform, we // get the expected screen coordinates. When pointer capture is enabled, we do not apply any // rotations and report values directly from the input device. - if (!isOrientedDevice && mParameters.mode != Parameters::Mode::POINTER_RELATIVE) { - std::optional internalViewport = - config->getDisplayViewportByType(ViewportType::INTERNAL); - if (internalViewport) { - mOrientation = getInverseRotation(internalViewport->orientation); + if (!isOrientedDevice && mDisplayId && + mParameters.mode != Parameters::Mode::POINTER_RELATIVE) { + if (auto viewport = config->getDisplayViewportById(*mDisplayId); viewport) { + mOrientation = getInverseRotation(viewport->orientation); } } @@ -282,6 +298,11 @@ void CursorInputMapper::process(const RawEvent* rawEvent) { } void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { + if (!mDisplayId) { + // Ignore events when there is no target display configured. + return; + } + int32_t lastButtonState = mButtonState; int32_t currentButtonState = mCursorButtonAccumulator.getButtonState(); mButtonState = currentButtonState; @@ -327,7 +348,6 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { mPointerVelocityControl.move(when, &deltaX, &deltaY); - int32_t displayId = ADISPLAY_ID_NONE; float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; if (mSource == AINPUT_SOURCE_MOUSE) { @@ -351,7 +371,6 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY); - displayId = mPointerController->getDisplayId(); } else { // Pointer capture and navigation modes pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, deltaX); @@ -373,7 +392,7 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { // Synthesize key down from buttons if needed. synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, readTime, getDeviceId(), - mSource, displayId, policyFlags, lastButtonState, currentButtonState); + mSource, *mDisplayId, policyFlags, lastButtonState, currentButtonState); // Send motion event. if (downChanged || moved || scrolled || buttonsChanged) { @@ -394,7 +413,7 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { int32_t actionButton = BitSet32::valueForBit(released.clearFirstMarkedBit()); buttonState &= ~actionButton; NotifyMotionArgs releaseArgs(getContext()->getNextId(), when, readTime, - getDeviceId(), mSource, displayId, policyFlags, + getDeviceId(), mSource, *mDisplayId, policyFlags, AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0, metaState, buttonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, @@ -406,7 +425,7 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { } NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - displayId, policyFlags, motionEventAction, 0, 0, metaState, + *mDisplayId, policyFlags, motionEventAction, 0, 0, metaState, currentButtonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords, mXPrecision, mYPrecision, xCursorPosition, yCursorPosition, downTime, @@ -419,7 +438,7 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { int32_t actionButton = BitSet32::valueForBit(pressed.clearFirstMarkedBit()); buttonState |= actionButton; NotifyMotionArgs pressArgs(getContext()->getNextId(), when, readTime, getDeviceId(), - mSource, displayId, policyFlags, + mSource, *mDisplayId, policyFlags, AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0, metaState, buttonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, @@ -435,7 +454,7 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { // Send hover move after UP to tell the application that the mouse is hovering now. if (motionEventAction == AMOTION_EVENT_ACTION_UP && (mSource == AINPUT_SOURCE_MOUSE)) { NotifyMotionArgs hoverArgs(getContext()->getNextId(), when, readTime, getDeviceId(), - mSource, displayId, policyFlags, + mSource, *mDisplayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState, currentButtonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, @@ -450,7 +469,7 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll); NotifyMotionArgs scrollArgs(getContext()->getNextId(), when, readTime, getDeviceId(), - mSource, displayId, policyFlags, + mSource, *mDisplayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState, currentButtonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, @@ -462,7 +481,7 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { // Synthesize key up from buttons if needed. synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, readTime, getDeviceId(), mSource, - displayId, policyFlags, lastButtonState, currentButtonState); + *mDisplayId, policyFlags, lastButtonState, currentButtonState); mCursorMotionAccumulator.finishSync(); mCursorScrollAccumulator.finishSync(); @@ -477,16 +496,7 @@ int32_t CursorInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCod } std::optional CursorInputMapper::getAssociatedDisplayId() { - if (mParameters.hasAssociatedDisplay) { - if (mParameters.mode == Parameters::Mode::POINTER) { - return std::make_optional(mPointerController->getDisplayId()); - } else { - // If the device is orientationAware and not a mouse, - // it expects to dispatch events to any display - return std::make_optional(ADISPLAY_ID_NONE); - } - } - return std::nullopt; + return mDisplayId; } } // namespace android diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index 75aeffb846..60b3dd9ee0 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -111,6 +111,10 @@ private: VelocityControl mWheelXVelocityControl; VelocityControl mWheelYVelocityControl; + // The display that events generated by this mapper should target. This can be set to + // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e. + // std::nullopt), all events will be ignored. + std::optional mDisplayId; int32_t mOrientation; std::shared_ptr mPointerController; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 851043d890..1cfe1edae7 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -59,7 +59,9 @@ static constexpr nsecs_t READ_TIME = 4321; // Arbitrary display properties. static constexpr int32_t DISPLAY_ID = 0; +static const std::string DISPLAY_UNIQUE_ID = "local:1"; static constexpr int32_t SECONDARY_DISPLAY_ID = DISPLAY_ID + 1; +static const std::string SECONDARY_DISPLAY_UNIQUE_ID = "local:2"; static constexpr int32_t DISPLAY_WIDTH = 480; static constexpr int32_t DISPLAY_HEIGHT = 800; static constexpr int32_t VIRTUAL_DISPLAY_ID = 1; @@ -94,6 +96,24 @@ static constexpr int32_t ACTION_POINTER_1_UP = // Error tolerance for floating point assertions. static const float EPSILON = 0.001f; +using ::testing::AllOf; + +MATCHER_P(WithAction, action, "InputEvent with specified action") { + return arg.action == action; +} + +MATCHER_P(WithSource, source, "InputEvent with specified source") { + return arg.source == source; +} + +MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") { + return arg.displayId == displayId; +} + +MATCHER_P2(WithCoords, x, y, "MotionEvent with specified action") { + return arg.pointerCoords[0].getX() == x && arg.pointerCoords[0].getY(); +} + template static inline T min(T a, T b) { return a < b ? a : b; @@ -2909,7 +2929,6 @@ TEST_F(InputDeviceTest, Configure_AssignsDisplayUniqueId) { // Device should be disabled because it is associated with a specific display, but the // corresponding display is not found. - const std::string DISPLAY_UNIQUE_ID = "displayUniqueId"; mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), InputReaderConfiguration::CHANGE_DISPLAY_INFO); @@ -2940,7 +2959,6 @@ TEST_F(InputDeviceTest, Configure_UniqueId_CorrectlyMatches) { mDevice->addMapper(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD); mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); - const std::string DISPLAY_UNIQUE_ID = "displayUniqueId"; mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID, @@ -4255,10 +4273,14 @@ protected: int32_t rotatedX, int32_t rotatedY); void prepareDisplay(int32_t orientation) { - const std::string uniqueId = "local:0"; - const ViewportType viewportType = ViewportType::INTERNAL; - setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - orientation, uniqueId, NO_PORT, viewportType); + setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation, + DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); + } + + void prepareSecondaryDisplay() { + setDisplayInfoAndReconfigure(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, + DISPLAY_ORIENTATION_0, SECONDARY_DISPLAY_UNIQUE_ID, NO_PORT, + ViewportType::EXTERNAL); } static void assertCursorPointerCoords(const PointerCoords& coords, float x, float y, @@ -4535,6 +4557,7 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleCombinedXYAndButtonUpdates) { } TEST_F(CursorInputMapperTest, Process_WhenOrientationAware_ShouldNotRotateMotions) { + mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); addConfigurationProperty("cursor.mode", "navigation"); // InputReader works in the un-rotated coordinate space, so orientation-aware devices do not // need to be rotated. @@ -4553,11 +4576,13 @@ TEST_F(CursorInputMapperTest, Process_WhenOrientationAware_ShouldNotRotateMotion } TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotions) { + mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); addConfigurationProperty("cursor.mode", "navigation"); // Since InputReader works in the un-rotated coordinate space, only devices that are not // orientation-aware are affected by display rotation. CursorInputMapper& mapper = addMapperAndConfigure(); + clearViewports(); prepareDisplay(DISPLAY_ORIENTATION_0); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, 1)); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, 1)); @@ -4568,6 +4593,7 @@ TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotion ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, -1, 0)); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, 1)); + clearViewports(); prepareDisplay(DISPLAY_ORIENTATION_90); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, -1, 0)); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, -1, 1)); @@ -4578,6 +4604,7 @@ TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotion ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 0, -1)); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, -1)); + clearViewports(); prepareDisplay(DISPLAY_ORIENTATION_180); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, -1)); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, -1, -1)); @@ -4588,6 +4615,7 @@ TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotion ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 1, 0)); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, 1, -1)); + clearViewports(); prepareDisplay(DISPLAY_ORIENTATION_270); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 1, 0)); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, -1)); @@ -5084,33 +5112,76 @@ TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) { ASSERT_EQ(20, args.pointerCoords[0].getY()); } -TEST_F(CursorInputMapperTest, Process_ShouldHandleDisplayId) { +TEST_F(CursorInputMapperTest, ConfigureDisplayId_NoAssociatedViewport) { CursorInputMapper& mapper = addMapperAndConfigure(); - // Setup for second display. - constexpr int32_t SECOND_DISPLAY_ID = 1; - const std::string SECOND_DISPLAY_UNIQUE_ID = "local:1"; - mFakePolicy->addDisplayViewport(SECOND_DISPLAY_ID, 800, 480, DISPLAY_ORIENTATION_0, - true /*isActive*/, SECOND_DISPLAY_UNIQUE_ID, NO_PORT, - ViewportType::EXTERNAL); - mFakePolicy->setDefaultPointerDisplayId(SECOND_DISPLAY_ID); + // Set up the default display. + prepareDisplay(DISPLAY_ORIENTATION_90); + + // Set up the secondary display as the display on which the pointer should be shown. + // The InputDevice is not associated with any display. + prepareSecondaryDisplay(); + mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); - mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); + mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); mFakePointerController->setPosition(100, 200); mFakePointerController->setButtonState(0); - NotifyMotionArgs args; + // Ensure input events are generated for the secondary display. process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source); - ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); - ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], - 110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE), + WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(110.0f, 220.0f)))); ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f)); - ASSERT_EQ(SECOND_DISPLAY_ID, args.displayId); +} + +TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) { + CursorInputMapper& mapper = addMapperAndConfigure(); + + // Set up the default display. + prepareDisplay(DISPLAY_ORIENTATION_90); + + // Set up the secondary display as the display on which the pointer should be shown, + // and associate the InputDevice with the secondary display. + prepareSecondaryDisplay(); + mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); + mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID); + configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + + mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); + mFakePointerController->setPosition(100, 200); + mFakePointerController->setButtonState(0); + + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE), + WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(110.0f, 220.0f)))); + ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f)); +} + +TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPointerDisplay) { + CursorInputMapper& mapper = addMapperAndConfigure(); + + // Set up the default display as the display on which the pointer should be shown. + prepareDisplay(DISPLAY_ORIENTATION_90); + mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); + + // Associate the InputDevice with the secondary display. + prepareSecondaryDisplay(); + mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID); + configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + + // The mapper should not generate any events because it is associated with a display that is + // different from the pointer display. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } // --- TouchInputMapperTest --- -- GitLab From 739dca4e62cad456993c1f20ca29a6f05b2470ad Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 9 Sep 2022 20:12:01 +0000 Subject: [PATCH 0280/1310] Add header for Matchers used with TestInputListener Move all the matcher definitions to a common header. Bug: 245989146 Test: atest inputflinger_tests Change-Id: I1dfd4342b5e291760df5b2078b02fd374b6546ac --- .../inputflinger/tests/InputReader_test.cpp | 31 +++------- .../tests/TestInputListenerMatchers.h | 60 +++++++++++++++++++ .../tests/UnwantedInteractionBlocker_test.cpp | 40 ++++--------- 3 files changed, 81 insertions(+), 50 deletions(-) create mode 100644 services/inputflinger/tests/TestInputListenerMatchers.h diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 1cfe1edae7..19a7d86233 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -47,7 +48,7 @@ using android::hardware::input::InputDeviceCountryCode; namespace android { using namespace ftl::flag_operators; - +using testing::AllOf; using std::chrono_literals::operator""ms; // Timeout for waiting for an expected event @@ -96,24 +97,6 @@ static constexpr int32_t ACTION_POINTER_1_UP = // Error tolerance for floating point assertions. static const float EPSILON = 0.001f; -using ::testing::AllOf; - -MATCHER_P(WithAction, action, "InputEvent with specified action") { - return arg.action == action; -} - -MATCHER_P(WithSource, source, "InputEvent with specified source") { - return arg.source == source; -} - -MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") { - return arg.displayId == displayId; -} - -MATCHER_P2(WithCoords, x, y, "MotionEvent with specified action") { - return arg.pointerCoords[0].getX() == x && arg.pointerCoords[0].getY(); -} - template static inline T min(T a, T b) { return a < b ? a : b; @@ -5133,8 +5116,9 @@ TEST_F(CursorInputMapperTest, ConfigureDisplayId_NoAssociatedViewport) { process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE), - WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(110.0f, 220.0f)))); + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID), + WithCoords(110.0f, 220.0f)))); ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f)); } @@ -5159,8 +5143,9 @@ TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) { process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE), - WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(110.0f, 220.0f)))); + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID), + WithCoords(110.0f, 220.0f)))); ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f)); } diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h new file mode 100644 index 0000000000..6c783a7e63 --- /dev/null +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _UI_TEST_INPUT_LISTENER_MATCHERS_H +#define _UI_TEST_INPUT_LISTENER_MATCHERS_H + +#include +#include +#include + +namespace android { + +MATCHER_P(WithMotionAction, action, "InputEvent with specified action") { + if (action == AMOTION_EVENT_ACTION_CANCEL) { + *result_listener << "expected FLAG_CANCELED to be set with ACTION_CANCEL, but was not set"; + return (arg.flags & AMOTION_EVENT_FLAG_CANCELED) != 0; + } + *result_listener << "expected action " << MotionEvent::actionToString(action) << ", but got " + << MotionEvent::actionToString(arg.action); + return action == arg.action; +} + +MATCHER_P(WithSource, source, "InputEvent with specified source") { + *result_listener << "expected source " << source << ", but got " << arg.source; + return arg.source == source; +} + +MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") { + *result_listener << "expected displayId " << displayId << ", but got " << arg.displayId; + return arg.displayId == displayId; +} + +MATCHER_P2(WithCoords, x, y, "InputEvent with specified coords") { + const auto argX = arg.pointerCoords[0].getX(); + const auto argY = arg.pointerCoords[0].getY(); + *result_listener << "expected coords (" << x << ", " << y << "), but got (" << argX << ", " + << argY << ")"; + return argX == x && argY == y; +} + +MATCHER_P(WithFlags, flags, "InputEvent with specified flags") { + *result_listener << "expected flags " << flags << ", but got " << arg.flags; + return arg.flags == flags; +} + +} // namespace android +#endif diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp index 526e4eb7c1..4c84160a1d 100644 --- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp +++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp @@ -24,6 +24,7 @@ #include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h" #include "TestInputListener.h" +#include "TestInputListenerMatchers.h" using ::testing::AllOf; @@ -55,21 +56,6 @@ constexpr int CANCEL = AMOTION_EVENT_ACTION_CANCEL; constexpr int32_t FLAG_CANCELED = AMOTION_EVENT_FLAG_CANCELED; -MATCHER_P(WithAction, action, "MotionEvent with specified action") { - bool result = true; - if (action == CANCEL) { - result &= (arg.flags & FLAG_CANCELED) != 0; - } - result &= arg.action == action; - *result_listener << "expected to receive " << MotionEvent::actionToString(action) - << " but received " << MotionEvent::actionToString(arg.action) << " instead."; - return result; -} - -MATCHER_P(WithFlags, flags, "MotionEvent with specified flags") { - return arg.flags == flags; -} - static nsecs_t toNs(std::chrono::nanoseconds duration) { return duration.count(); } @@ -605,19 +591,19 @@ TEST_F(UnwantedInteractionBlockerTest, HeuristicFilterWorks) { // Small touch down NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}}); mBlocker->notifyMotion(&args1); - mTestListener.assertNotifyMotionWasCalled(WithAction(DOWN)); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN)); // Large touch oval on the next move NotifyMotionArgs args2 = generateMotionArgs(0 /*downTime*/, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}}); mBlocker->notifyMotion(&args2); - mTestListener.assertNotifyMotionWasCalled(WithAction(MOVE)); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE)); // Lift up the touch to force the model to decide on whether it's a palm NotifyMotionArgs args3 = generateMotionArgs(0 /*downTime*/, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}); mBlocker->notifyMotion(&args3); - mTestListener.assertNotifyMotionWasCalled(WithAction(CANCEL)); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(CANCEL)); } /** @@ -633,14 +619,14 @@ TEST_F(UnwantedInteractionBlockerTest, StylusIsNotBlocked) { NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}}); args1.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; mBlocker->notifyMotion(&args1); - mTestListener.assertNotifyMotionWasCalled(WithAction(DOWN)); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN)); // Move the stylus, setting large TOUCH_MAJOR/TOUCH_MINOR dimensions NotifyMotionArgs args2 = generateMotionArgs(0 /*downTime*/, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}}); args2.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; mBlocker->notifyMotion(&args2); - mTestListener.assertNotifyMotionWasCalled(WithAction(MOVE)); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE)); // Lift up the stylus. If it were a touch event, this would force the model to decide on whether // it's a palm. @@ -648,7 +634,7 @@ TEST_F(UnwantedInteractionBlockerTest, StylusIsNotBlocked) { generateMotionArgs(0 /*downTime*/, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}); args3.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; mBlocker->notifyMotion(&args3); - mTestListener.assertNotifyMotionWasCalled(WithAction(UP)); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(UP)); } /** @@ -664,21 +650,21 @@ TEST_F(UnwantedInteractionBlockerTest, TouchIsBlockedWhenMixedWithStylus) { // Touch down NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}}); mBlocker->notifyMotion(&args1); - mTestListener.assertNotifyMotionWasCalled(WithAction(DOWN)); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN)); // Stylus pointer down NotifyMotionArgs args2 = generateMotionArgs(0 /*downTime*/, RESAMPLE_PERIOD, POINTER_1_DOWN, {{1, 2, 3}, {10, 20, 30}}); args2.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; mBlocker->notifyMotion(&args2); - mTestListener.assertNotifyMotionWasCalled(WithAction(POINTER_1_DOWN)); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(POINTER_1_DOWN)); // Large touch oval on the next finger move NotifyMotionArgs args3 = generateMotionArgs(0 /*downTime*/, 2 * RESAMPLE_PERIOD, MOVE, {{1, 2, 300}, {11, 21, 30}}); args3.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; mBlocker->notifyMotion(&args3); - mTestListener.assertNotifyMotionWasCalled(WithAction(MOVE)); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE)); // Lift up the finger pointer. It should be canceled due to the heuristic filter. NotifyMotionArgs args4 = generateMotionArgs(0 /*downTime*/, 3 * RESAMPLE_PERIOD, POINTER_0_UP, @@ -686,14 +672,14 @@ TEST_F(UnwantedInteractionBlockerTest, TouchIsBlockedWhenMixedWithStylus) { args4.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; mBlocker->notifyMotion(&args4); mTestListener.assertNotifyMotionWasCalled( - AllOf(WithAction(POINTER_0_UP), WithFlags(FLAG_CANCELED))); + AllOf(WithMotionAction(POINTER_0_UP), WithFlags(FLAG_CANCELED))); NotifyMotionArgs args5 = generateMotionArgs(0 /*downTime*/, 4 * RESAMPLE_PERIOD, MOVE, {{12, 22, 30}}); args5.pointerProperties[0].id = args4.pointerProperties[1].id; args5.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; mBlocker->notifyMotion(&args5); - mTestListener.assertNotifyMotionWasCalled(WithAction(MOVE)); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE)); // Lift up the stylus pointer NotifyMotionArgs args6 = @@ -701,7 +687,7 @@ TEST_F(UnwantedInteractionBlockerTest, TouchIsBlockedWhenMixedWithStylus) { args6.pointerProperties[0].id = args4.pointerProperties[1].id; args6.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; mBlocker->notifyMotion(&args6); - mTestListener.assertNotifyMotionWasCalled(WithAction(UP)); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(UP)); } using UnwantedInteractionBlockerTestDeathTest = UnwantedInteractionBlockerTest; -- GitLab From 4810866ed0a355fe7997857a5bac93099e8565c6 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 9 Sep 2022 21:22:04 +0000 Subject: [PATCH 0281/1310] inputflinger: Use #pragma once instead of explicit define guards The #pragma once directive is commonly used by modern cpp code, and support for its use in Andorid is proposed in go/droidcppstyle. Bug: 245989146 Test: Build, Presubmit Change-Id: Id257f056972b753f505afe1d253d306bb1824098 --- services/inputflinger/BlockingQueue.h | 5 +---- services/inputflinger/InputManager.h | 5 +---- services/inputflinger/InputProcessor.h | 4 +--- services/inputflinger/dispatcher/AnrTracker.h | 5 +---- services/inputflinger/dispatcher/CancelationOptions.h | 5 +---- services/inputflinger/dispatcher/Connection.h | 5 +---- services/inputflinger/dispatcher/DebugConfig.h | 5 +---- services/inputflinger/dispatcher/DragState.h | 5 +---- services/inputflinger/dispatcher/Entry.h | 5 +---- services/inputflinger/dispatcher/InjectionState.h | 5 +---- services/inputflinger/dispatcher/InputDispatcher.h | 5 +---- services/inputflinger/dispatcher/InputEventTimeline.h | 5 +---- services/inputflinger/dispatcher/InputState.h | 5 +---- services/inputflinger/dispatcher/InputTarget.h | 5 +---- services/inputflinger/dispatcher/LatencyAggregator.h | 5 +---- services/inputflinger/dispatcher/LatencyTracker.h | 5 +---- services/inputflinger/dispatcher/Monitor.h | 5 +---- services/inputflinger/dispatcher/TouchState.h | 5 +---- services/inputflinger/dispatcher/TouchedWindow.h | 5 +---- .../dispatcher/include/InputDispatcherConfiguration.h | 5 +---- .../inputflinger/dispatcher/include/InputDispatcherFactory.h | 5 +---- .../dispatcher/include/InputDispatcherInterface.h | 5 +---- .../dispatcher/include/InputDispatcherPolicyInterface.h | 5 +---- services/inputflinger/host/InputDriver.h | 4 +--- services/inputflinger/host/InputFlinger.h | 5 +---- services/inputflinger/host/InputHost.h | 4 +--- services/inputflinger/include/InputListener.h | 5 +---- services/inputflinger/include/InputReaderBase.h | 5 +---- services/inputflinger/include/InputThread.h | 5 +---- services/inputflinger/include/PointerControllerInterface.h | 5 +---- services/inputflinger/include/VibrationElement.h | 5 +---- services/inputflinger/reader/Macros.h | 5 +---- .../inputflinger/reader/controller/PeripheralController.h | 5 +---- .../reader/controller/PeripheralControllerInterface.h | 5 +---- services/inputflinger/reader/include/EventHub.h | 5 +---- services/inputflinger/reader/include/InputDevice.h | 5 +---- services/inputflinger/reader/include/InputReader.h | 5 +---- services/inputflinger/reader/include/InputReaderContext.h | 5 +---- services/inputflinger/reader/include/StylusState.h | 5 +---- services/inputflinger/reader/include/TouchVideoDevice.h | 5 +---- services/inputflinger/reader/mapper/CursorInputMapper.h | 5 +---- .../inputflinger/reader/mapper/ExternalStylusInputMapper.h | 5 +---- services/inputflinger/reader/mapper/InputMapper.h | 5 +---- services/inputflinger/reader/mapper/JoystickInputMapper.h | 5 +---- services/inputflinger/reader/mapper/KeyboardInputMapper.h | 5 +---- services/inputflinger/reader/mapper/MultiTouchInputMapper.h | 5 +---- .../inputflinger/reader/mapper/RotaryEncoderInputMapper.h | 5 +---- services/inputflinger/reader/mapper/SensorInputMapper.h | 5 +---- services/inputflinger/reader/mapper/SingleTouchInputMapper.h | 5 +---- services/inputflinger/reader/mapper/SwitchInputMapper.h | 5 +---- .../reader/mapper/TouchCursorInputMapperCommon.h | 5 +---- services/inputflinger/reader/mapper/TouchInputMapper.h | 5 +---- services/inputflinger/reader/mapper/VibratorInputMapper.h | 5 +---- .../reader/mapper/accumulator/CursorButtonAccumulator.h | 5 +---- .../reader/mapper/accumulator/CursorScrollAccumulator.h | 5 +---- .../reader/mapper/accumulator/SingleTouchMotionAccumulator.h | 5 +---- .../reader/mapper/accumulator/TouchButtonAccumulator.h | 5 +---- services/inputflinger/reporter/InputReporterInterface.h | 5 +---- services/inputflinger/tests/TestInputListener.h | 4 +--- services/inputflinger/tests/TestInputListenerMatchers.h | 4 +--- services/inputflinger/tests/UinputDevice.h | 5 +---- 61 files changed, 61 insertions(+), 239 deletions(-) diff --git a/services/inputflinger/BlockingQueue.h b/services/inputflinger/BlockingQueue.h index 8300e8a3da..fe3728789d 100644 --- a/services/inputflinger/BlockingQueue.h +++ b/services/inputflinger/BlockingQueue.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_BLOCKING_QUEUE_H -#define _UI_INPUT_BLOCKING_QUEUE_H +#pragma once #include "android-base/thread_annotations.h" #include @@ -106,6 +105,4 @@ private: std::vector mQueue GUARDED_BY(mLock); }; - } // namespace android -#endif diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h index b40ab7fdde..11371934c2 100644 --- a/services/inputflinger/InputManager.h +++ b/services/inputflinger/InputManager.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_MANAGER_H -#define _UI_INPUT_MANAGER_H +#pragma once /** * Native input manager. @@ -130,5 +129,3 @@ private: }; } // namespace android - -#endif // _UI_INPUT_MANAGER_H diff --git a/services/inputflinger/InputProcessor.h b/services/inputflinger/InputProcessor.h index 261e0125bf..bbf391e069 100644 --- a/services/inputflinger/InputProcessor.h +++ b/services/inputflinger/InputProcessor.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_CLASSIFIER_H -#define _UI_INPUT_CLASSIFIER_H +#pragma once #include #include @@ -288,4 +287,3 @@ private: }; } // namespace android -#endif diff --git a/services/inputflinger/dispatcher/AnrTracker.h b/services/inputflinger/dispatcher/AnrTracker.h index 097dba5bea..5e5e0c4bae 100644 --- a/services/inputflinger/dispatcher/AnrTracker.h +++ b/services/inputflinger/dispatcher/AnrTracker.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_ANRTRACKER_H -#define _UI_INPUT_INPUTDISPATCHER_ANRTRACKER_H +#pragma once #include #include @@ -56,5 +55,3 @@ private: }; } // namespace android::inputdispatcher - -#endif // _UI_INPUT_INPUTDISPATCHER_ANRTRACKER_H diff --git a/services/inputflinger/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h index fec5f83638..d210e9e846 100644 --- a/services/inputflinger/dispatcher/CancelationOptions.h +++ b/services/inputflinger/dispatcher/CancelationOptions.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_CANCELLATIONOPTIONS_H -#define _UI_INPUT_INPUTDISPATCHER_CANCELLATIONOPTIONS_H +#pragma once #include #include @@ -55,5 +54,3 @@ struct CancelationOptions { } // namespace inputdispatcher } // namespace android - -#endif // _UI_INPUT_INPUTDISPATCHER_CANCELLATIONOPTIONS_H diff --git a/services/inputflinger/dispatcher/Connection.h b/services/inputflinger/dispatcher/Connection.h index dc6a081ff6..6040e9b078 100644 --- a/services/inputflinger/dispatcher/Connection.h +++ b/services/inputflinger/dispatcher/Connection.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_CONNECTION_H -#define _UI_INPUT_INPUTDISPATCHER_CONNECTION_H +#pragma once #include "InputState.h" @@ -74,5 +73,3 @@ public: }; } // namespace android::inputdispatcher - -#endif // _UI_INPUT_INPUTDISPATCHER_CONNECTION_H diff --git a/services/inputflinger/dispatcher/DebugConfig.h b/services/inputflinger/dispatcher/DebugConfig.h index 4f8995f43d..d34ae49072 100644 --- a/services/inputflinger/dispatcher/DebugConfig.h +++ b/services/inputflinger/dispatcher/DebugConfig.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_DISPATCHER_DEBUG_CONFIG_H -#define _UI_INPUT_DISPATCHER_DEBUG_CONFIG_H +#pragma once #define LOG_TAG "InputDispatcher" @@ -95,5 +94,3 @@ const bool DEBUG_APP_SWITCH = const bool DEBUG_HOVER = __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Hover", ANDROID_LOG_INFO); } // namespace android::inputdispatcher - -#endif // _UI_INPUT_DISPATCHER_DEBUG_CONFIG_H \ No newline at end of file diff --git a/services/inputflinger/dispatcher/DragState.h b/services/inputflinger/dispatcher/DragState.h index d1c8b8a10e..9809148853 100644 --- a/services/inputflinger/dispatcher/DragState.h +++ b/services/inputflinger/dispatcher/DragState.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_DRAGSTATE_H -#define _UI_INPUT_INPUTDISPATCHER_DRAGSTATE_H +#pragma once #include #include @@ -44,5 +43,3 @@ struct DragState { } // namespace inputdispatcher } // namespace android - -#endif // _UI_INPUT_INPUTDISPATCHER_DRAGSTATE_H \ No newline at end of file diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h index 0f792967eb..1f916f39e4 100644 --- a/services/inputflinger/dispatcher/Entry.h +++ b/services/inputflinger/dispatcher/Entry.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_ENTRY_H -#define _UI_INPUT_INPUTDISPATCHER_ENTRY_H +#pragma once #include "InjectionState.h" #include "InputTarget.h" @@ -257,5 +256,3 @@ VerifiedMotionEvent verifiedMotionEventFromMotionEntry(const MotionEntry& entry, const ui::Transform& rawTransform); } // namespace android::inputdispatcher - -#endif // _UI_INPUT_INPUTDISPATCHER_ENTRY_H diff --git a/services/inputflinger/dispatcher/InjectionState.h b/services/inputflinger/dispatcher/InjectionState.h index 90cf150ac3..d9e27ba50b 100644 --- a/services/inputflinger/dispatcher/InjectionState.h +++ b/services/inputflinger/dispatcher/InjectionState.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_INJECTIONSTATE_H -#define _UI_INPUT_INPUTDISPATCHER_INJECTIONSTATE_H +#pragma once #include #include "InputDispatcherInterface.h" @@ -41,5 +40,3 @@ private: } // namespace inputdispatcher } // namespace android - -#endif // _UI_INPUT_INPUTDISPATCHER_INJECTIONSTATE_H diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index dc6dd5cdc0..d078b352bc 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_DISPATCHER_H -#define _UI_INPUT_DISPATCHER_H +#pragma once #include "AnrTracker.h" #include "CancelationOptions.h" @@ -694,5 +693,3 @@ private: }; } // namespace android::inputdispatcher - -#endif // _UI_INPUT_DISPATCHER_H diff --git a/services/inputflinger/dispatcher/InputEventTimeline.h b/services/inputflinger/dispatcher/InputEventTimeline.h index 77b8472f89..daf375d918 100644 --- a/services/inputflinger/dispatcher/InputEventTimeline.h +++ b/services/inputflinger/dispatcher/InputEventTimeline.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_INPUTEVENTTIMELINE_H -#define _UI_INPUT_INPUTDISPATCHER_INPUTEVENTTIMELINE_H +#pragma once #include #include @@ -104,5 +103,3 @@ public: } // namespace inputdispatcher } // namespace android - -#endif // _UI_INPUT_INPUTDISPATCHER_INPUTEVENTTIMELINE_H diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h index 77a6db19bc..6ab48c9273 100644 --- a/services/inputflinger/dispatcher/InputState.h +++ b/services/inputflinger/dispatcher/InputState.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_INPUTSTATE_H -#define _UI_INPUT_INPUTDISPATCHER_INPUTSTATE_H +#pragma once #include "CancelationOptions.h" #include "Entry.h" @@ -134,5 +133,3 @@ private: } // namespace inputdispatcher } // namespace android - -#endif // _UI_INPUT_INPUTDISPATCHER_INPUTSTATE_H diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h index ac20dab205..b2966f680c 100644 --- a/services/inputflinger/dispatcher/InputTarget.h +++ b/services/inputflinger/dispatcher/InputTarget.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_INPUTTARGET_H -#define _UI_INPUT_INPUTDISPATCHER_INPUTTARGET_H +#pragma once #include #include @@ -136,5 +135,3 @@ struct InputTarget { std::string dispatchModeToString(int32_t dispatchMode); } // namespace android::inputdispatcher - -#endif // _UI_INPUT_INPUTDISPATCHER_INPUTTARGET_H diff --git a/services/inputflinger/dispatcher/LatencyAggregator.h b/services/inputflinger/dispatcher/LatencyAggregator.h index ed5731f8d9..accfc29d8e 100644 --- a/services/inputflinger/dispatcher/LatencyAggregator.h +++ b/services/inputflinger/dispatcher/LatencyAggregator.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_LATENCYAGGREGATOR_H -#define _UI_INPUT_INPUTDISPATCHER_LATENCYAGGREGATOR_H +#pragma once #include #include @@ -86,5 +85,3 @@ private: }; } // namespace android::inputdispatcher - -#endif // _UI_INPUT_INPUTDISPATCHER_LATENCYAGGREGATOR_H diff --git a/services/inputflinger/dispatcher/LatencyTracker.h b/services/inputflinger/dispatcher/LatencyTracker.h index 4b0c618590..64dfeef20d 100644 --- a/services/inputflinger/dispatcher/LatencyTracker.h +++ b/services/inputflinger/dispatcher/LatencyTracker.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_LATENCYTRACKER_H -#define _UI_INPUT_INPUTDISPATCHER_LATENCYTRACKER_H +#pragma once #include #include @@ -81,5 +80,3 @@ private: }; } // namespace android::inputdispatcher - -#endif // _UI_INPUT_INPUTDISPATCHER_LATENCYTRACKER_H diff --git a/services/inputflinger/dispatcher/Monitor.h b/services/inputflinger/dispatcher/Monitor.h index 365d5bee4c..7b511915d0 100644 --- a/services/inputflinger/dispatcher/Monitor.h +++ b/services/inputflinger/dispatcher/Monitor.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_MONITOR_H -#define _UI_INPUT_INPUTDISPATCHER_MONITOR_H +#pragma once #include @@ -30,5 +29,3 @@ struct Monitor { }; } // namespace android::inputdispatcher - -#endif // _UI_INPUT_INPUTDISPATCHER_MONITOR_H diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index 1fb51e161d..cf5f1e58c6 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_TOUCHSTATE_H -#define _UI_INPUT_INPUTDISPATCHER_TOUCHSTATE_H +#pragma once #include "Monitor.h" #include "TouchedWindow.h" @@ -67,5 +66,3 @@ struct TouchState { } // namespace inputdispatcher } // namespace android - -#endif // _UI_INPUT_INPUTDISPATCHER_TOUCHSTATE_H diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h index a7839dbb36..a6c505b854 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_TOUCHEDWINDOW_H -#define _UI_INPUT_INPUTDISPATCHER_TOUCHEDWINDOW_H +#pragma once namespace android { @@ -38,5 +37,3 @@ struct TouchedWindow { } // namespace inputdispatcher } // namespace android - -#endif // _UI_INPUT_INPUTDISPATCHER_TOUCHEDWINDOW_H diff --git a/services/inputflinger/dispatcher/include/InputDispatcherConfiguration.h b/services/inputflinger/dispatcher/include/InputDispatcherConfiguration.h index 00abf47cd2..5eb3a32ef9 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherConfiguration.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherConfiguration.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERCONFIGURATION_H -#define _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERCONFIGURATION_H +#pragma once #include @@ -40,5 +39,3 @@ struct InputDispatcherConfiguration { }; } // namespace android - -#endif // _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERCONFIGURATION_H diff --git a/services/inputflinger/dispatcher/include/InputDispatcherFactory.h b/services/inputflinger/dispatcher/include/InputDispatcherFactory.h index 38d0c32529..5247d8eabb 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherFactory.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherFactory.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERFACTORY_H -#define _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERFACTORY_H +#pragma once #include @@ -29,5 +28,3 @@ std::unique_ptr createInputDispatcher( const sp& policy); } // namespace android - -#endif // _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERFACTORY_H diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h index 32b3ddbdeb..484b0d3b7f 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERINTERFACE_H -#define _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERINTERFACE_H +#pragma once #include #include @@ -226,5 +225,3 @@ public: }; } // namespace android - -#endif // _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERINTERFACE_H diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h index 7c299b2608..44f3baf204 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERPOLICYINTERFACE_H -#define _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERPOLICYINTERFACE_H +#pragma once #include "InputDispatcherConfiguration.h" @@ -143,5 +142,3 @@ public: }; } // namespace android - -#endif // _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERPOLICYINTERFACE_H diff --git a/services/inputflinger/host/InputDriver.h b/services/inputflinger/host/InputDriver.h index e56673b4d3..b4c90943a8 100644 --- a/services/inputflinger/host/InputDriver.h +++ b/services/inputflinger/host/InputDriver.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef ANDROID_INPUT_DRIVER_H -#define ANDROID_INPUT_DRIVER_H +#pragma once #include #include @@ -192,4 +191,3 @@ void input_free_device_property_map(input_host_t* host, input_property_map_t* ma } } // namespace android -#endif // ANDROID_INPUT_DRIVER_H diff --git a/services/inputflinger/host/InputFlinger.h b/services/inputflinger/host/InputFlinger.h index 3cf1b2b797..388988bfce 100644 --- a/services/inputflinger/host/InputFlinger.h +++ b/services/inputflinger/host/InputFlinger.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef ANDROID_INPUT_FLINGER_H -#define ANDROID_INPUT_FLINGER_H +#pragma once #include #include @@ -58,5 +57,3 @@ private: }; } // namespace android - -#endif // ANDROID_INPUT_FLINGER_H diff --git a/services/inputflinger/host/InputHost.h b/services/inputflinger/host/InputHost.h index eda4a89c73..bdc4225738 100644 --- a/services/inputflinger/host/InputHost.h +++ b/services/inputflinger/host/InputHost.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef ANDROID_INPUT_HOST_H -#define ANDROID_INPUT_HOST_H +#pragma once #include @@ -55,4 +54,3 @@ private: }; } // namespace android -#endif // ANDRIOD_INPUT_HOST_H diff --git a/services/inputflinger/include/InputListener.h b/services/inputflinger/include/InputListener.h index d9822ce156..c8ab6c5011 100644 --- a/services/inputflinger/include/InputListener.h +++ b/services/inputflinger/include/InputListener.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_LISTENER_H -#define _UI_INPUT_LISTENER_H +#pragma once #include @@ -292,5 +291,3 @@ private: }; } // namespace android - -#endif // _UI_INPUT_LISTENER_H diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index 77c9142131..44424597f8 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_READER_BASE_H -#define _UI_INPUT_READER_BASE_H +#pragma once #include #include @@ -395,5 +394,3 @@ public: }; } // namespace android - -#endif // _UI_INPUT_READER_COMMON_H diff --git a/services/inputflinger/include/InputThread.h b/services/inputflinger/include/InputThread.h index 407365a269..5e75027056 100644 --- a/services/inputflinger/include/InputThread.h +++ b/services/inputflinger/include/InputThread.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_THREAD_H -#define _UI_INPUT_THREAD_H +#pragma once #include @@ -42,5 +41,3 @@ private: }; } // namespace android - -#endif // _UI_INPUT_THREAD_H \ No newline at end of file diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h index db4228d862..647e10c974 100644 --- a/services/inputflinger/include/PointerControllerInterface.h +++ b/services/inputflinger/include/PointerControllerInterface.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _INPUTFLINGER_POINTER_CONTROLLER_INTERFACE_H -#define _INPUTFLINGER_POINTER_CONTROLLER_INTERFACE_H +#pragma once #include #include @@ -108,5 +107,3 @@ public: }; } // namespace android - -#endif // _INPUTFLINGER_POINTER_CONTROLLER_INTERFACE_H diff --git a/services/inputflinger/include/VibrationElement.h b/services/inputflinger/include/VibrationElement.h index 736041e7fd..04c2e394c8 100644 --- a/services/inputflinger/include/VibrationElement.h +++ b/services/inputflinger/include/VibrationElement.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _VIBRATION_ELEMENT_H -#define _VIBRATION_ELEMENT_H +#pragma once #include #include @@ -71,5 +70,3 @@ struct VibrationSequence { }; } // namespace android - -#endif // _VIBRATION_ELEMENT_H diff --git a/services/inputflinger/reader/Macros.h b/services/inputflinger/reader/Macros.h index 1bbf38676a..e107d882b7 100644 --- a/services/inputflinger/reader/Macros.h +++ b/services/inputflinger/reader/Macros.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_MACROS_H -#define _UI_INPUTREADER_MACROS_H +#pragma once #define LOG_TAG "InputReader" @@ -115,5 +114,3 @@ static inline bool sourcesMatchMask(uint32_t sources, uint32_t sourceMask) { } } // namespace android - -#endif // _UI_INPUTREADER_MACROS_H \ No newline at end of file diff --git a/services/inputflinger/reader/controller/PeripheralController.h b/services/inputflinger/reader/controller/PeripheralController.h index ac951ebe2a..25cf435620 100644 --- a/services/inputflinger/reader/controller/PeripheralController.h +++ b/services/inputflinger/reader/controller/PeripheralController.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_LIGHT_CONTROLLER_H -#define _UI_INPUTREADER_LIGHT_CONTROLLER_H +#pragma once #include "PeripheralControllerInterface.h" @@ -150,5 +149,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_LIGHT_CONTROLLER_H diff --git a/services/inputflinger/reader/controller/PeripheralControllerInterface.h b/services/inputflinger/reader/controller/PeripheralControllerInterface.h index 306e36119b..76ed1ca038 100644 --- a/services/inputflinger/reader/controller/PeripheralControllerInterface.h +++ b/services/inputflinger/reader/controller/PeripheralControllerInterface.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_INPUT_CONTROLLER_H -#define _UI_INPUTREADER_INPUT_CONTROLLER_H +#pragma once #include "EventHub.h" #include "InputDevice.h" @@ -50,5 +49,3 @@ public: }; } // namespace android - -#endif // _UI_INPUTREADER_INPUT_CONTROLLER_H diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index dfb98f14c5..5cf22144a1 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _RUNTIME_EVENT_HUB_H -#define _RUNTIME_EVENT_HUB_H +#pragma once #include #include @@ -732,5 +731,3 @@ private: }; } // namespace android - -#endif // _RUNTIME_EVENT_HUB_H diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 3ef1840e43..4ae9ae9dd0 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_INPUT_DEVICE_H -#define _UI_INPUTREADER_INPUT_DEVICE_H +#pragma once #include #include @@ -409,5 +408,3 @@ private: }; } // namespace android - -#endif //_UI_INPUTREADER_INPUT_DEVICE_H diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h index ae41e01f7e..24194a73fe 100644 --- a/services/inputflinger/reader/include/InputReader.h +++ b/services/inputflinger/reader/include/InputReader.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_INPUT_READER_H -#define _UI_INPUTREADER_INPUT_READER_H +#pragma once #include #include @@ -246,5 +245,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_INPUT_READER_H diff --git a/services/inputflinger/reader/include/InputReaderContext.h b/services/inputflinger/reader/include/InputReaderContext.h index 823d160f86..f2f156c6e2 100644 --- a/services/inputflinger/reader/include/InputReaderContext.h +++ b/services/inputflinger/reader/include/InputReaderContext.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_INPUT_READER_CONTEXT_H -#define _UI_INPUTREADER_INPUT_READER_CONTEXT_H +#pragma once #include @@ -65,5 +64,3 @@ public: }; } // namespace android - -#endif // _UI_INPUTREADER_INPUT_READER_CONTEXT_H diff --git a/services/inputflinger/reader/include/StylusState.h b/services/inputflinger/reader/include/StylusState.h index 17f158c9e1..8d14d3c169 100644 --- a/services/inputflinger/reader/include/StylusState.h +++ b/services/inputflinger/reader/include/StylusState.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_STYLUS_STATE_H -#define _UI_INPUTREADER_STYLUS_STATE_H +#pragma once #include @@ -49,5 +48,3 @@ struct StylusState { }; } // namespace android - -#endif // _UI_INPUTREADER_STYLUS_STATE_H diff --git a/services/inputflinger/reader/include/TouchVideoDevice.h b/services/inputflinger/reader/include/TouchVideoDevice.h index 7de9b830b2..08eba31199 100644 --- a/services/inputflinger/reader/include/TouchVideoDevice.h +++ b/services/inputflinger/reader/include/TouchVideoDevice.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTFLINGER_TOUCH_VIDEO_DEVICE_H -#define _UI_INPUTFLINGER_TOUCH_VIDEO_DEVICE_H +#pragma once #include #include @@ -123,5 +122,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTFLINGER_TOUCH_VIDEO_DEVICE_H diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index 60b3dd9ee0..a0229a74c7 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_CURSOR_INPUT_MAPPER_H -#define _UI_INPUTREADER_CURSOR_INPUT_MAPPER_H +#pragma once #include "CursorButtonAccumulator.h" #include "CursorScrollAccumulator.h" @@ -129,5 +128,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_CURSOR_INPUT_MAPPER_H diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h index 516aa51a14..03d9909b51 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_EXTERNAL_STYLUS_INPUT_MAPPER_H -#define _UI_INPUTREADER_EXTERNAL_STYLUS_INPUT_MAPPER_H +#pragma once #include "InputMapper.h" @@ -49,5 +48,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_EXTERNAL_STYLUS_INPUT_MAPPER_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h index 7858728b6e..5567cac6a9 100644 --- a/services/inputflinger/reader/mapper/InputMapper.h +++ b/services/inputflinger/reader/mapper/InputMapper.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_INPUT_MAPPER_H -#define _UI_INPUTREADER_INPUT_MAPPER_H +#pragma once #include "EventHub.h" #include "InputDevice.h" @@ -109,5 +108,3 @@ protected: }; } // namespace android - -#endif // _UI_INPUTREADER_INPUT_MAPPER_H diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.h b/services/inputflinger/reader/mapper/JoystickInputMapper.h index 307bf5b38b..e0023970de 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.h +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_JOYSTICK_INPUT_MAPPER_H -#define _UI_INPUTREADER_JOYSTICK_INPUT_MAPPER_H +#pragma once #include "InputMapper.h" @@ -111,5 +110,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_JOYSTICK_INPUT_MAPPER_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index 31251f4433..2136d253c9 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_KEYBOARD_INPUT_MAPPER_H -#define _UI_INPUTREADER_KEYBOARD_INPUT_MAPPER_H +#pragma once #include "InputMapper.h" @@ -102,5 +101,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_KEYBOARD_INPUT_MAPPER_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h index fe8af5d818..212c9c9108 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_MULTI_TOUCH_INPUT_MAPPER_H -#define _UI_INPUTREADER_MULTI_TOUCH_INPUT_MAPPER_H +#pragma once #include "TouchInputMapper.h" @@ -122,5 +121,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_MULTI_TOUCH_INPUT_MAPPER_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h index 1859355a93..42e2421c37 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_ROTARY_ENCODER_INPUT_MAPPER_H -#define _UI_INPUTREADER_ROTARY_ENCODER_INPUT_MAPPER_H +#pragma once #include "CursorScrollAccumulator.h" #include "InputMapper.h" @@ -46,5 +45,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_ROTARY_ENCODER_INPUT_MAPPER_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h index 27a61771f2..38d4c3c935 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.h +++ b/services/inputflinger/reader/mapper/SensorInputMapper.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_SENSOR_INPUT_MAPPER_H -#define _UI_INPUTREADER_SENSOR_INPUT_MAPPER_H +#pragma once #include "InputMapper.h" @@ -133,5 +132,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_SENSOR_INPUT_MAPPER_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h index 9cb3f67f20..f54c19502e 100644 --- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_SINGLE_TOUCH_INPUT_MAPPER_H -#define _UI_INPUTREADER_SINGLE_TOUCH_INPUT_MAPPER_H +#pragma once #include "SingleTouchMotionAccumulator.h" #include "TouchInputMapper.h" @@ -40,5 +39,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_SINGLE_TOUCH_INPUT_MAPPER_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.h b/services/inputflinger/reader/mapper/SwitchInputMapper.h index 64b9aa27b4..e0c949f012 100644 --- a/services/inputflinger/reader/mapper/SwitchInputMapper.h +++ b/services/inputflinger/reader/mapper/SwitchInputMapper.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_SWITCH_INPUT_MAPPER_H -#define _UI_INPUTREADER_SWITCH_INPUT_MAPPER_H +#pragma once #include "InputMapper.h" @@ -41,5 +40,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_SWITCH_INPUT_MAPPER_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h index 31a3d2e172..42d819b362 100644 --- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h +++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_TOUCH_CURSOR_INPUT_MAPPER_COMMON_H -#define _UI_INPUTREADER_TOUCH_CURSOR_INPUT_MAPPER_COMMON_H +#pragma once #include #include @@ -99,5 +98,3 @@ static void synthesizeButtonKeys(InputReaderContext* context, int32_t action, ns } } // namespace android - -#endif // _UI_INPUTREADER_TOUCH_CURSOR_INPUT_MAPPER_COMMON_H diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index fe1700612e..bd4cff6d9a 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_TOUCH_INPUT_MAPPER_H -#define _UI_INPUTREADER_TOUCH_INPUT_MAPPER_H +#pragma once #include @@ -805,5 +804,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_TOUCH_INPUT_MAPPER_H diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.h b/services/inputflinger/reader/mapper/VibratorInputMapper.h index d3c22b60a8..894c5739e4 100644 --- a/services/inputflinger/reader/mapper/VibratorInputMapper.h +++ b/services/inputflinger/reader/mapper/VibratorInputMapper.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_VIBRATOR_INPUT_MAPPER_H -#define _UI_INPUTREADER_VIBRATOR_INPUT_MAPPER_H +#pragma once #include "InputMapper.h" @@ -50,5 +49,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_VIBRATOR_INPUT_MAPPER_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h index 9e159064fa..ed4c789cfd 100644 --- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_CURSOR_BUTTON_ACCUMULATOR_H -#define _UI_INPUTREADER_CURSOR_BUTTON_ACCUMULATOR_H +#pragma once #include @@ -48,5 +47,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_CURSOR_BUTTON_ACCUMULATOR_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h index 16495599d7..ae1b7a32f4 100644 --- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_CURSOR_SCROLL_ACCUMULATOR_H -#define _UI_INPUTREADER_CURSOR_SCROLL_ACCUMULATOR_H +#pragma once #include @@ -56,5 +55,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_CURSOR_SCROLL_ACCUMULATOR_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h index 4c011f16a7..93056f06e6 100644 --- a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_SINGLE_TOUCH_MOTION_ACCUMULATOR_H -#define _UI_INPUTREADER_SINGLE_TOUCH_MOTION_ACCUMULATOR_H +#pragma once #include @@ -53,5 +52,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_SINGLE_TOUCH_MOTION_ACCUMULATOR_H \ No newline at end of file diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h index 22ebb720d5..7b889be238 100644 --- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUTREADER_TOUCH_BUTTON_ACCUMULATOR_H -#define _UI_INPUTREADER_TOUCH_BUTTON_ACCUMULATOR_H +#pragma once #include @@ -62,5 +61,3 @@ private: }; } // namespace android - -#endif // _UI_INPUTREADER_TOUCH_BUTTON_ACCUMULATOR_H \ No newline at end of file diff --git a/services/inputflinger/reporter/InputReporterInterface.h b/services/inputflinger/reporter/InputReporterInterface.h index e5d360609f..72a4aeb13b 100644 --- a/services/inputflinger/reporter/InputReporterInterface.h +++ b/services/inputflinger/reporter/InputReporterInterface.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_INPUT_REPORTER_INTERFACE_H -#define _UI_INPUT_REPORTER_INTERFACE_H +#pragma once #include @@ -49,5 +48,3 @@ public: sp createInputReporter(); } // namespace android - -#endif // _UI_INPUT_REPORTER_INTERFACE_H diff --git a/services/inputflinger/tests/TestInputListener.h b/services/inputflinger/tests/TestInputListener.h index 0bdfc6b9fb..cad698fd36 100644 --- a/services/inputflinger/tests/TestInputListener.h +++ b/services/inputflinger/tests/TestInputListener.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_TEST_INPUT_LISTENER_H -#define _UI_TEST_INPUT_LISTENER_H +#pragma once #include #include @@ -103,4 +102,3 @@ private: }; } // namespace android -#endif diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h index 6c783a7e63..03736a33af 100644 --- a/services/inputflinger/tests/TestInputListenerMatchers.h +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_TEST_INPUT_LISTENER_MATCHERS_H -#define _UI_TEST_INPUT_LISTENER_MATCHERS_H +#pragma once #include #include @@ -57,4 +56,3 @@ MATCHER_P(WithFlags, flags, "InputEvent with specified flags") { } } // namespace android -#endif diff --git a/services/inputflinger/tests/UinputDevice.h b/services/inputflinger/tests/UinputDevice.h index a37fc2b790..e0ff8c3d4c 100644 --- a/services/inputflinger/tests/UinputDevice.h +++ b/services/inputflinger/tests/UinputDevice.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UI_TEST_INPUT_UINPUT_INJECTOR_H -#define _UI_TEST_INPUT_UINPUT_INJECTOR_H +#pragma once #include #include @@ -155,5 +154,3 @@ private: }; } // namespace android - -#endif // _UI_TEST_INPUT_UINPUT_INJECTOR_H -- GitLab From e287ecd5ff7aa2212e9ab28e3176bea5f4aab5b9 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 7 Sep 2022 21:18:05 +0000 Subject: [PATCH 0282/1310] Add pipeline to get the sysfs path for an InputDevice's battery This will be used to listen to UEvent notifications coming from the battery's device path that notify userspace about battery state changes. Bug: 243005009 Test: atest inputflinger_tests Test: manual with logs Change-Id: I464c6212cdb33242cb319176d502b6e8431125fb --- .../inputflinger/include/InputReaderBase.h | 2 ++ services/inputflinger/reader/EventHub.cpp | 4 +-- services/inputflinger/reader/InputReader.cpp | 32 +++++++++++++++++-- .../inputflinger/reader/include/InputReader.h | 2 ++ .../inputflinger/tests/InputReader_test.cpp | 23 ++++++++++++- .../tests/fuzzers/InputReaderFuzzer.cpp | 5 +++ 6 files changed, 63 insertions(+), 5 deletions(-) diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index 77c9142131..88aaa9de0c 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -114,6 +114,8 @@ public: virtual std::optional getBatteryCapacity(int32_t deviceId) = 0; /* Get battery status of a particular input device. */ virtual std::optional getBatteryStatus(int32_t deviceId) = 0; + /* Get the device path for the battery of an input device. */ + virtual std::optional getBatteryDevicePath(int32_t deviceId) = 0; virtual std::vector getLights(int32_t deviceId) = 0; diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index ff0b9a5f7e..c9d21dc573 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -1555,7 +1555,7 @@ std::optional EventHub::getBatteryCapacity(int32_t deviceId, int32_t ba // the lock to prevent event processing from being blocked by this call. std::scoped_lock _l(mLock); - const auto infos = getBatteryInfoLocked(deviceId); + const auto& infos = getBatteryInfoLocked(deviceId); auto it = infos.find(batteryId); if (it == infos.end()) { return std::nullopt; @@ -1596,7 +1596,7 @@ std::optional EventHub::getBatteryStatus(int32_t deviceId, int32_t batt // the lock to prevent event processing from being blocked by this call. std::scoped_lock _l(mLock); - const auto infos = getBatteryInfoLocked(deviceId); + const auto& infos = getBatteryInfoLocked(deviceId); auto it = infos.find(batteryId); if (it == infos.end()) { return std::nullopt; diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 4750d90fd6..4c38ce81b6 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -721,7 +721,10 @@ std::optional InputReader::getBatteryCapacity(int32_t deviceId) { if (!eventHubId) return {}; const auto batteryIds = mEventHub->getRawBatteryIds(*eventHubId); - if (batteryIds.empty()) return {}; + if (batteryIds.empty()) { + ALOGW("%s: There are no battery ids for EventHub device %d", __func__, *eventHubId); + return {}; + } return mEventHub->getBatteryCapacity(*eventHubId, batteryIds.front()); } @@ -741,10 +744,35 @@ std::optional InputReader::getBatteryStatus(int32_t deviceId) { if (!eventHubId) return {}; const auto batteryIds = mEventHub->getRawBatteryIds(*eventHubId); - if (batteryIds.empty()) return {}; + if (batteryIds.empty()) { + ALOGW("%s: There are no battery ids for EventHub device %d", __func__, *eventHubId); + return {}; + } return mEventHub->getBatteryStatus(*eventHubId, batteryIds.front()); } +std::optional InputReader::getBatteryDevicePath(int32_t deviceId) { + std::scoped_lock _l(mLock); + + InputDevice* device = findInputDeviceLocked(deviceId); + if (!device) return {}; + + std::optional eventHubId = device->getBatteryEventHubId(); + if (!eventHubId) return {}; + const auto batteryIds = mEventHub->getRawBatteryIds(*eventHubId); + if (batteryIds.empty()) { + ALOGW("%s: There are no battery ids for EventHub device %d", __func__, *eventHubId); + return {}; + } + const auto batteryInfo = mEventHub->getRawBatteryInfo(*eventHubId, batteryIds.front()); + if (!batteryInfo) { + ALOGW("%s: Failed to get RawBatteryInfo for battery %d of EventHub device %d", __func__, + batteryIds.front(), *eventHubId); + return {}; + } + return batteryInfo->path; +} + std::vector InputReader::getLights(int32_t deviceId) { std::scoped_lock _l(mLock); diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h index ae41e01f7e..cb55c456c6 100644 --- a/services/inputflinger/reader/include/InputReader.h +++ b/services/inputflinger/reader/include/InputReader.h @@ -100,6 +100,8 @@ public: std::optional getBatteryStatus(int32_t deviceId) override; + std::optional getBatteryDevicePath(int32_t deviceId) override; + std::vector getLights(int32_t deviceId) override; std::vector getSensors(int32_t deviceId) override; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 851043d890..594c1cb8ef 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -78,6 +78,7 @@ static constexpr int32_t THIRD_TRACKING_ID = 2; static constexpr int32_t DEFAULT_BATTERY = 1; static constexpr int32_t BATTERY_STATUS = 4; static constexpr int32_t BATTERY_CAPACITY = 66; +static const std::string BATTERY_DEVPATH = "/sys/devices/mydevice/power_supply/mybattery"; static constexpr int32_t LIGHT_BRIGHTNESS = 0x55000000; static constexpr int32_t LIGHT_COLOR = 0x7F448866; static constexpr int32_t LIGHT_PLAYER_ID = 2; @@ -1014,7 +1015,12 @@ private: std::optional getRawBatteryInfo(int32_t deviceId, int32_t batteryId) const override { - return std::nullopt; + if (batteryId != DEFAULT_BATTERY) return {}; + static const auto BATTERY_INFO = RawBatteryInfo{.id = DEFAULT_BATTERY, + .name = "default battery", + .flags = InputBatteryClass::CAPACITY, + .path = BATTERY_DEVPATH}; + return BATTERY_INFO; } std::vector getRawLightIds(int32_t deviceId) const override { @@ -2232,6 +2238,21 @@ TEST_F(InputReaderTest, BatteryGetStatus) { ASSERT_EQ(mReader->getBatteryStatus(deviceId), BATTERY_STATUS); } +TEST_F(InputReaderTest, BatteryGetDevicePath) { + constexpr int32_t deviceId = END_RESERVED_ID + 1000; + ftl::Flags deviceClass = + InputDeviceClass::KEYBOARD | InputDeviceClass::BATTERY; + constexpr int32_t eventHubId = 1; + const char* DEVICE_LOCATION = "BLUETOOTH"; + std::shared_ptr device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION); + device->addController(eventHubId); + mReader->pushNextDevice(device); + + ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); + + ASSERT_EQ(mReader->getBatteryDevicePath(deviceId), BATTERY_DEVPATH); +} + TEST_F(InputReaderTest, LightGetColor) { constexpr int32_t deviceId = END_RESERVED_ID + 1000; ftl::Flags deviceClass = InputDeviceClass::KEYBOARD | InputDeviceClass::LIGHT; diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp index f15d871403..f5bd29763e 100644 --- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp @@ -107,6 +107,10 @@ public: return reader->getBatteryStatus(deviceId); } + std::optional getBatteryDevicePath(int32_t deviceId) { + return reader->getBatteryDevicePath(deviceId); + } + std::vector getLights(int32_t deviceId) { return reader->getLights(deviceId); } @@ -232,6 +236,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { }, [&]() -> void { reader->getBatteryCapacity(fdp->ConsumeIntegral()); }, [&]() -> void { reader->getBatteryStatus(fdp->ConsumeIntegral()); }, + [&]() -> void { reader->getBatteryDevicePath(fdp->ConsumeIntegral()); }, [&]() -> void { reader->getLights(fdp->ConsumeIntegral()); }, [&]() -> void { reader->getSensors(fdp->ConsumeIntegral()); }, [&]() -> void { -- GitLab From c08b0dbd8b4b3090734144208b192c2d374f14b0 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Sat, 10 Sep 2022 00:57:15 +0000 Subject: [PATCH 0283/1310] libinput: Use #pragma once instead of explicit define guards The #pragma once directive is commonly used by modern cpp code, and support for its use in Andorid is proposed in go/droidcppstyle. Bug: 245989146 Test: Build, Presubmit Change-Id: Ib7852f1a343864dff46e7bc616d707bc7d5bf512 --- include/input/DisplayViewport.h | 5 +---- include/input/Input.h | 5 +---- include/input/InputDevice.h | 5 +---- include/input/InputEventLabels.h | 4 +--- include/input/InputTransport.h | 5 +---- include/input/KeyCharacterMap.h | 5 +---- include/input/KeyLayoutMap.h | 5 +---- include/input/Keyboard.h | 5 +---- include/input/PropertyMap.h | 5 +---- include/input/TouchVideoFrame.h | 5 +---- include/input/VelocityControl.h | 5 +---- include/input/VelocityTracker.h | 5 +---- include/input/VirtualKeyMap.h | 5 +---- 13 files changed, 13 insertions(+), 51 deletions(-) diff --git a/include/input/DisplayViewport.h b/include/input/DisplayViewport.h index 9148fee532..98a18c9560 100644 --- a/include/input/DisplayViewport.h +++ b/include/input/DisplayViewport.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _LIBINPUT_DISPLAY_VIEWPORT_H -#define _LIBINPUT_DISPLAY_VIEWPORT_H +#pragma once #include #include @@ -144,5 +143,3 @@ struct DisplayViewport { }; } // namespace android - -#endif // _LIBINPUT_DISPLAY_VIEWPORT_H diff --git a/include/input/Input.h b/include/input/Input.h index a3c9f33f33..2dd651e9d7 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _LIBINPUT_INPUT_H -#define _LIBINPUT_INPUT_H +#pragma once #pragma GCC system_header @@ -1104,5 +1103,3 @@ enum class PointerIconStyle : int32_t { }; } // namespace android - -#endif // _LIBINPUT_INPUT_H diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h index d51d6a722a..415080d15d 100644 --- a/include/input/InputDevice.h +++ b/include/input/InputDevice.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _LIBINPUT_INPUT_DEVICE_H -#define _LIBINPUT_INPUT_DEVICE_H +#pragma once #include #include @@ -341,5 +340,3 @@ enum ReservedInputDeviceId : int32_t { }; } // namespace android - -#endif // _LIBINPUT_INPUT_DEVICE_H diff --git a/include/input/InputEventLabels.h b/include/input/InputEventLabels.h index 2a742f9cf4..b4374acdcc 100644 --- a/include/input/InputEventLabels.h +++ b/include/input/InputEventLabels.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _LIBINPUT_INPUT_EVENT_LABELS_H -#define _LIBINPUT_INPUT_EVENT_LABELS_H +#pragma once #include #include @@ -68,4 +67,3 @@ private: }; } // namespace android -#endif // _LIBINPUT_INPUT_EVENT_LABELS_H diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h index dbc7bfa388..1c52792cf6 100644 --- a/include/input/InputTransport.h +++ b/include/input/InputTransport.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _LIBINPUT_INPUT_TRANSPORT_H -#define _LIBINPUT_INPUT_TRANSPORT_H +#pragma once #pragma GCC system_header @@ -674,5 +673,3 @@ private: }; } // namespace android - -#endif // _LIBINPUT_INPUT_TRANSPORT_H diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h index 1c9a5eaac1..dc928b806f 100644 --- a/include/input/KeyCharacterMap.h +++ b/include/input/KeyCharacterMap.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _LIBINPUT_KEY_CHARACTER_MAP_H -#define _LIBINPUT_KEY_CHARACTER_MAP_H +#pragma once #include #include @@ -270,5 +269,3 @@ private: }; } // namespace android - -#endif // _LIBINPUT_KEY_CHARACTER_MAP_H diff --git a/include/input/KeyLayoutMap.h b/include/input/KeyLayoutMap.h index a6c696df26..e203d190a6 100644 --- a/include/input/KeyLayoutMap.h +++ b/include/input/KeyLayoutMap.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _LIBINPUT_KEY_LAYOUT_MAP_H -#define _LIBINPUT_KEY_LAYOUT_MAP_H +#pragma once #include #include @@ -131,5 +130,3 @@ private: }; } // namespace android - -#endif // _LIBINPUT_KEY_LAYOUT_MAP_H diff --git a/include/input/Keyboard.h b/include/input/Keyboard.h index 9a3e15f1cd..f7f960f8e6 100644 --- a/include/input/Keyboard.h +++ b/include/input/Keyboard.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _LIBINPUT_KEYBOARD_H -#define _LIBINPUT_KEYBOARD_H +#pragma once #include #include @@ -88,5 +87,3 @@ extern int32_t normalizeMetaState(int32_t oldMetaState); extern bool isMetaKey(int32_t keyCode); } // namespace android - -#endif // _LIBINPUT_KEYBOARD_H diff --git a/include/input/PropertyMap.h b/include/input/PropertyMap.h index b1e3f85ed5..28e4816afe 100644 --- a/include/input/PropertyMap.h +++ b/include/input/PropertyMap.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _UTILS_PROPERTY_MAP_H -#define _UTILS_PROPERTY_MAP_H +#pragma once #include #include @@ -98,5 +97,3 @@ private: }; } // namespace android - -#endif // _UTILS_PROPERTY_MAP_H diff --git a/include/input/TouchVideoFrame.h b/include/input/TouchVideoFrame.h index eda628e233..a616a95ab1 100644 --- a/include/input/TouchVideoFrame.h +++ b/include/input/TouchVideoFrame.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _LIBINPUT_TOUCHVIDEOFRAME_H -#define _LIBINPUT_TOUCHVIDEOFRAME_H +#pragma once #include #include @@ -75,5 +74,3 @@ private: }; } // namespace android - -#endif // _LIBINPUT_TOUCHVIDEOFRAME_H diff --git a/include/input/VelocityControl.h b/include/input/VelocityControl.h index f4c7061ad1..f72a1bdded 100644 --- a/include/input/VelocityControl.h +++ b/include/input/VelocityControl.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _LIBINPUT_VELOCITY_CONTROL_H -#define _LIBINPUT_VELOCITY_CONTROL_H +#pragma once #include #include @@ -103,5 +102,3 @@ private: }; } // namespace android - -#endif // _LIBINPUT_VELOCITY_CONTROL_H diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h index 6f2fcf4ce4..15e987999b 100644 --- a/include/input/VelocityTracker.h +++ b/include/input/VelocityTracker.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _LIBINPUT_VELOCITY_TRACKER_H -#define _LIBINPUT_VELOCITY_TRACKER_H +#pragma once #include #include @@ -349,5 +348,3 @@ private: }; } // namespace android - -#endif // _LIBINPUT_VELOCITY_TRACKER_H diff --git a/include/input/VirtualKeyMap.h b/include/input/VirtualKeyMap.h index 6e8e2c9cf4..a4381eaab9 100644 --- a/include/input/VirtualKeyMap.h +++ b/include/input/VirtualKeyMap.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _LIBINPUT_VIRTUAL_KEY_MAP_H -#define _LIBINPUT_VIRTUAL_KEY_MAP_H +#pragma once #include @@ -77,5 +76,3 @@ private: }; } // namespace android - -#endif // _LIBINPUT_KEY_CHARACTER_MAP_H -- GitLab From 46b61b9b2afa34a53f5dd49530fd69d125475175 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Thu, 1 Sep 2022 17:25:49 +0000 Subject: [PATCH 0284/1310] SF: Remove EffectLayer class Bug: 238781169 Test: refactor, existing tests pass Change-Id: I05724f6af3a207d566589420c2e3346b378b70e0 --- services/surfaceflinger/Android.bp | 1 - services/surfaceflinger/EffectLayer.cpp | 47 ------------------- services/surfaceflinger/EffectLayer.h | 36 -------------- services/surfaceflinger/Layer.cpp | 6 +-- services/surfaceflinger/LayerRenderArea.cpp | 3 +- services/surfaceflinger/SurfaceFlinger.cpp | 1 - .../SurfaceFlingerDefaultFactory.cpp | 5 +- .../SurfaceFlingerDefaultFactory.h | 2 +- .../surfaceflinger/SurfaceFlingerFactory.h | 3 +- .../Tracing/tools/LayerTraceGenerator.cpp | 4 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 5 +- .../fuzzer/surfaceflinger_layer_fuzzer.cpp | 5 +- .../tests/unittests/CompositionTest.cpp | 15 +++--- .../tests/unittests/FpsReporterTest.cpp | 1 - .../tests/unittests/LayerTest.cpp | 1 - .../tests/unittests/LayerTestUtils.cpp | 2 +- .../tests/unittests/LayerTestUtils.h | 2 - .../unittests/RefreshRateSelectionTest.cpp | 7 ++- .../tests/unittests/SetFrameRateTest.cpp | 6 --- .../tests/unittests/TestableSurfaceFlinger.h | 3 +- 20 files changed, 25 insertions(+), 130 deletions(-) delete mode 100644 services/surfaceflinger/EffectLayer.cpp delete mode 100644 services/surfaceflinger/EffectLayer.h diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index 3348cec211..b911ae75d4 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -154,7 +154,6 @@ filegroup { "DisplayHardware/PowerAdvisor.cpp", "DisplayHardware/VirtualDisplaySurface.cpp", "DisplayRenderArea.cpp", - "EffectLayer.cpp", "Effects/Daltonizer.cpp", "EventLog/EventLog.cpp", "FlagManager.cpp", diff --git a/services/surfaceflinger/EffectLayer.cpp b/services/surfaceflinger/EffectLayer.cpp deleted file mode 100644 index 7180fa6a58..0000000000 --- a/services/surfaceflinger/EffectLayer.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - -// #define LOG_NDEBUG 0 -#undef LOG_TAG -#define LOG_TAG "EffectLayer" - -#include "EffectLayer.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "DisplayDevice.h" -#include "SurfaceFlinger.h" - -namespace android { -// --------------------------------------------------------------------------- - -EffectLayer::EffectLayer(const LayerCreationArgs& args) : BufferStateLayer(args) {} -EffectLayer::~EffectLayer() = default; - -} // namespace android diff --git a/services/surfaceflinger/EffectLayer.h b/services/surfaceflinger/EffectLayer.h deleted file mode 100644 index 311d4935fc..0000000000 --- a/services/surfaceflinger/EffectLayer.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2007 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 - -#include - -#include "BufferStateLayer.h" - -namespace android { - -// A layer that can render a combination of the following effects. -// * fill the bounds of the layer with a color -// * render a shadow cast by the bounds of the layer -// If no effects are enabled, the layer is considered to be invisible. -class EffectLayer : public BufferStateLayer { -public: - explicit EffectLayer(const LayerCreationArgs&); - ~EffectLayer() override; -}; - -} // namespace android diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 701071b36e..1c720cc4dd 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -65,9 +65,9 @@ #include #include +#include "BufferStateLayer.h" #include "DisplayDevice.h" #include "DisplayHardware/HWComposer.h" -#include "EffectLayer.h" #include "FrameTimeline.h" #include "FrameTracer/FrameTracer.h" #include "LayerProtoHelper.h" @@ -2205,8 +2205,8 @@ void Layer::prepareShadowClientComposition(LayerFE::LayerSettings& caster, renderengine::ShadowSettings state = mFlinger->mDrawingState.globalShadowSettings; // Note: this preserves existing behavior of shadowing the entire layer and not cropping it if - // transparent regions are present. This may not be necessary since shadows are only cast by - // SurfaceFlinger's EffectLayers, which do not typically use transparent regions. + // transparent regions are present. This may not be necessary since shadows are typically cast + // by layers without transparent regions. state.boundaries = mBounds; // Shift the spot light x-position to the middle of the display and then diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp index e17b01f520..6bc7dc1dd7 100644 --- a/services/surfaceflinger/LayerRenderArea.cpp +++ b/services/surfaceflinger/LayerRenderArea.cpp @@ -18,7 +18,6 @@ #include #include "DisplayDevice.h" -#include "EffectLayer.h" #include "Layer.h" #include "LayerRenderArea.h" #include "SurfaceFlinger.h" @@ -110,7 +109,7 @@ void LayerRenderArea::render(std::function drawLayers) { // layer which has no properties set and which does not draw. // We hold the statelock as the reparent-for-drawing operation modifies the // hierarchy and there could be readers on Binder threads, like dump. - sp screenshotParentLayer = mFlinger.getFactory().createEffectLayer( + auto screenshotParentLayer = mFlinger.getFactory().createEffectLayer( {&mFlinger, nullptr, "Screenshot Parent"s, ISurfaceComposerClient::eNoColorFill, LayerMetadata()}); { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 78eaa14e5b..9dd3713998 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -118,7 +118,6 @@ #include "DisplayHardware/PowerAdvisor.h" #include "DisplayHardware/VirtualDisplaySurface.h" #include "DisplayRenderArea.h" -#include "EffectLayer.h" #include "Effects/Daltonizer.h" #include "FlagManager.h" #include "FpsReporter.h" diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp index 15a791e4d6..319d014237 100644 --- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp +++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp @@ -24,7 +24,6 @@ #include "BufferStateLayer.h" #include "DisplayDevice.h" -#include "EffectLayer.h" #include "FrameTracer/FrameTracer.h" #include "Layer.h" #include "NativeWindowSurface.h" @@ -94,8 +93,8 @@ sp DefaultFactory::createBufferStateLayer(const LayerCreationA return sp::make(args); } -sp DefaultFactory::createEffectLayer(const LayerCreationArgs& args) { - return sp::make(args); +sp DefaultFactory::createEffectLayer(const LayerCreationArgs& args) { + return sp::make(args); } std::unique_ptr DefaultFactory::createFrameTracer() { diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h index 8d00379ba2..66022401b7 100644 --- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h +++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h @@ -42,7 +42,7 @@ public: const sp&) override; std::unique_ptr createCompositionEngine() override; sp createBufferStateLayer(const LayerCreationArgs& args) override; - sp createEffectLayer(const LayerCreationArgs& args) override; + sp createEffectLayer(const LayerCreationArgs& args) override; std::unique_ptr createFrameTracer() override; std::unique_ptr createFrameTimeline( std::shared_ptr timeStats, pid_t surfaceFlingerPid) override; diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h index 291838f81a..dc2afd3790 100644 --- a/services/surfaceflinger/SurfaceFlingerFactory.h +++ b/services/surfaceflinger/SurfaceFlingerFactory.h @@ -33,7 +33,6 @@ typedef int32_t PixelFormat; class BufferLayerConsumer; class BufferStateLayer; class DisplayDevice; -class EffectLayer; class FrameTracer; class GraphicBuffer; class HWComposer; @@ -91,7 +90,7 @@ public: virtual std::unique_ptr createCompositionEngine() = 0; virtual sp createBufferStateLayer(const LayerCreationArgs& args) = 0; - virtual sp createEffectLayer(const LayerCreationArgs& args) = 0; + virtual sp createEffectLayer(const LayerCreationArgs& args) = 0; virtual std::unique_ptr createFrameTracer() = 0; virtual std::unique_ptr createFrameTimeline( std::shared_ptr timeStats, pid_t surfaceFlingerPid) = 0; diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index 312d4ab45a..6501e2092b 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -84,9 +84,7 @@ public: return sp::make(args); } - sp createEffectLayer(const LayerCreationArgs& args) { - return sp::make(args); - } + sp createEffectLayer(const LayerCreationArgs& args) { return sp::make(args); } std::unique_ptr createFrameTracer() override { return std::make_unique>(); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index a8612638b2..22976186f2 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -33,7 +33,6 @@ #include "BufferStateLayer.h" #include "DisplayDevice.h" #include "DisplayHardware/ComposerHal.h" -#include "EffectLayer.h" #include "FrameTimeline/FrameTimeline.h" #include "FrameTracer/FrameTracer.h" #include "Layer.h" @@ -360,8 +359,8 @@ public: return nullptr; } - sp createEffectLayer(const LayerCreationArgs &args) override { - return sp::make(args); + sp createEffectLayer(const LayerCreationArgs &args) override { + return sp::make(args); } std::unique_ptr createFrameTracer() override { diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp index aeccc522f3..5b2ec961c6 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -79,13 +78,13 @@ void LayerFuzzer::invokeEffectLayer() { TestableSurfaceFlinger flinger; sp client = sp::make(sp::fromExisting(flinger.flinger())); const LayerCreationArgs layerCreationArgs = createLayerCreationArgs(&flinger, client); - sp effectLayer = sp::make(layerCreationArgs); + sp effectLayer = sp::make(layerCreationArgs); effectLayer->setColor({(mFdp.ConsumeFloatingPointInRange(0, 255) /*x*/, mFdp.ConsumeFloatingPointInRange(0, 255) /*y*/, mFdp.ConsumeFloatingPointInRange(0, 255) /*z*/)}); effectLayer->setDataspace(mFdp.PickValueInArray(kDataspaces)); - sp parent = sp::make(layerCreationArgs); + sp parent = sp::make(layerCreationArgs); effectLayer->setChildrenDrawingParent(parent); const FrameTimelineInfo frameInfo = getFuzzedFrameTimelineInfo(); diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index e546c2f8c9..9485f48eea 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -38,7 +38,6 @@ #include #include "DisplayRenderArea.h" -#include "EffectLayer.h" #include "Layer.h" #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockComposer.h" @@ -857,13 +856,13 @@ struct BaseLayerVariant { template struct EffectLayerVariant : public BaseLayerVariant { using Base = BaseLayerVariant; - using FlingerLayerType = sp; + using FlingerLayerType = sp; static FlingerLayerType createLayer(CompositionTest* test) { - FlingerLayerType layer = Base::template createLayerWithFactory(test, [test]() { - return sp::make( - LayerCreationArgs(test->mFlinger.flinger(), sp(), "test-layer", - LayerProperties::LAYER_FLAGS, LayerMetadata())); + FlingerLayerType layer = Base::template createLayerWithFactory(test, [test]() { + return sp::make(LayerCreationArgs(test->mFlinger.flinger(), sp(), + "test-layer", LayerProperties::LAYER_FLAGS, + LayerMetadata())); }); auto& layerDrawingState = test->mFlinger.mutableLayerDrawingState(layer); @@ -945,12 +944,12 @@ struct BufferLayerVariant : public BaseLayerVariant { template struct ContainerLayerVariant : public BaseLayerVariant { using Base = BaseLayerVariant; - using FlingerLayerType = sp; + using FlingerLayerType = sp; static FlingerLayerType createLayer(CompositionTest* test) { LayerCreationArgs args(test->mFlinger.flinger(), sp(), "test-container-layer", LayerProperties::LAYER_FLAGS, LayerMetadata()); - FlingerLayerType layer = sp::make(args); + FlingerLayerType layer = sp::make(args); Base::template initLayerDrawingStateAndComputeBounds(test, layer); return layer; } diff --git a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp index 0b4e196d43..9789df5c3b 100644 --- a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp +++ b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp @@ -25,7 +25,6 @@ #include #include "BufferStateLayer.h" -#include "EffectLayer.h" #include "FpsReporter.h" #include "Layer.h" #include "TestableSurfaceFlinger.h" diff --git a/services/surfaceflinger/tests/unittests/LayerTest.cpp b/services/surfaceflinger/tests/unittests/LayerTest.cpp index 4974f90383..95e54f655b 100644 --- a/services/surfaceflinger/tests/unittests/LayerTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerTest.cpp @@ -17,7 +17,6 @@ #undef LOG_TAG #define LOG_TAG "LibSurfaceFlingerUnittests" -#include #include #include #include diff --git a/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp b/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp index b7a8a93f5c..14304d1199 100644 --- a/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp +++ b/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp @@ -35,7 +35,7 @@ sp BufferStateLayerFactory::createLayer(TestableSurfaceFlinger& flinger) sp EffectLayerFactory::createLayer(TestableSurfaceFlinger& flinger) { sp client; LayerCreationArgs args(flinger.flinger(), client, "color-layer", LAYER_FLAGS, LayerMetadata()); - return sp::make(args); + return sp::make(args); } std::string PrintToStringParamName( diff --git a/services/surfaceflinger/tests/unittests/LayerTestUtils.h b/services/surfaceflinger/tests/unittests/LayerTestUtils.h index fc9b6a27aa..ab446fafeb 100644 --- a/services/surfaceflinger/tests/unittests/LayerTestUtils.h +++ b/services/surfaceflinger/tests/unittests/LayerTestUtils.h @@ -23,8 +23,6 @@ // TODO(b/129481165): remove the #pragma below and fix conversion issues #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wconversion" -#include "BufferStateLayer.h" -#include "EffectLayer.h" #include "Layer.h" // TODO(b/129481165): remove the #pragma below and fix conversion issues #pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp index 6752a39384..abf1786c0d 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp @@ -22,7 +22,6 @@ #include #include "BufferStateLayer.h" -#include "EffectLayer.h" #include "Layer.h" #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockComposer.h" @@ -60,7 +59,7 @@ protected: void setupScheduler(); sp createBufferStateLayer(); - sp createEffectLayer(); + sp createEffectLayer(); void setParent(Layer* child, Layer* parent); void commitTransaction(Layer* layer); @@ -96,10 +95,10 @@ sp RefreshRateSelectionTest::createBufferStateLayer() { return sp::make(args); } -sp RefreshRateSelectionTest::createEffectLayer() { +sp RefreshRateSelectionTest::createEffectLayer() { sp client; LayerCreationArgs args(mFlinger.flinger(), client, "color-layer", LAYER_FLAGS, LayerMetadata()); - return sp::make(args); + return sp::make(args); } void RefreshRateSelectionTest::setParent(Layer* child, Layer* parent) { diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp index 6ee8174caf..51c6beabae 100644 --- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp +++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp @@ -25,7 +25,6 @@ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wconversion" #include "BufferStateLayer.h" -#include "EffectLayer.h" #include "Layer.h" // TODO(b/129481165): remove the #pragma below and fix conversion issues #pragma clang diagnostic pop // ignored "-Wconversion" @@ -374,11 +373,6 @@ TEST_P(SetFrameRateTest, SetOnParentActivatesTree) { const auto& layerFactory = GetParam(); auto parent = mLayers.emplace_back(layerFactory->createLayer(mFlinger)); - if (!parent->isVisible()) { - // This is a hack as all the test layers except EffectLayer are not visible, - // but since the logic is unified in Layer, it should be fine. - return; - } auto child = mLayers.emplace_back(layerFactory->createLayer(mFlinger)); addChild(parent, child); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index f8fdb65d31..1ce6e18d69 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -32,7 +32,6 @@ #include "BufferStateLayer.h" #include "DisplayDevice.h" -#include "EffectLayer.h" #include "FakeVsyncConfiguration.h" #include "FrameTracer/FrameTracer.h" #include "Layer.h" @@ -125,7 +124,7 @@ public: return nullptr; } - sp createEffectLayer(const LayerCreationArgs&) override { return nullptr; } + sp createEffectLayer(const LayerCreationArgs&) override { return nullptr; } std::unique_ptr createFrameTracer() override { return std::make_unique(); -- GitLab From 741111bd79f8ac9daa3d9fca35bc491fda08685b Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 12 Sep 2022 20:45:30 +0000 Subject: [PATCH 0285/1310] Add Android keycodes for stylus buttons Ensure the new stylus buttons are not yet sent to apps. DD: go/android-stylus-buttons Bug: 246394583 Test: Build, Presubmit Change-Id: I3f429c3435c90429c908fb77f8dbf964290d47aa --- include/android/keycodes.h | 11 +++++++++++ libs/input/InputEventLabels.cpp | 11 +++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/include/android/keycodes.h b/include/android/keycodes.h index 16a13d6cd3..e5b5db2964 100644 --- a/include/android/keycodes.h +++ b/include/android/keycodes.h @@ -818,6 +818,17 @@ enum { /** Keyboard backlight Toggle key. * Toggles the keyboard backlight on/off. */ AKEYCODE_KEYBOARD_BACKLIGHT_TOGGLE = 307, + /** The primary button on the barrel of a stylus. + * This is usually the button closest to the tip of the stylus. */ + AKEYCODE_STYLUS_BUTTON_PRIMARY = 308, + /** The secondary button on the barrel of a stylus. + * This is usually the second button from the tip of the stylus. */ + AKEYCODE_STYLUS_BUTTON_SECONDARY = 309, + /** The tertiary button on the barrel of a stylus. + * This is usually the third button from the tip of the stylus. */ + AKEYCODE_STYLUS_BUTTON_TERTIARY = 310, + /** A button on the tail end of a stylus. */ + AKEYCODE_STYLUS_BUTTON_TAIL = 311, // NOTE: If you add a new keycode here you must also add it to several other files. // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list. diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp index 5990ee0d30..163a2fe924 100644 --- a/libs/input/InputEventLabels.cpp +++ b/libs/input/InputEventLabels.cpp @@ -23,6 +23,8 @@ namespace android { +// clang-format off + // NOTE: If you add a new keycode here you must also add it to several other files. // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list. #define KEYCODES_SEQUENCE \ @@ -333,7 +335,11 @@ namespace android { DEFINE_KEYCODE(DEMO_APP_4), \ DEFINE_KEYCODE(KEYBOARD_BACKLIGHT_DOWN), \ DEFINE_KEYCODE(KEYBOARD_BACKLIGHT_UP), \ - DEFINE_KEYCODE(KEYBOARD_BACKLIGHT_TOGGLE) + DEFINE_KEYCODE(KEYBOARD_BACKLIGHT_TOGGLE), \ + DEFINE_KEYCODE(STYLUS_BUTTON_PRIMARY), \ + DEFINE_KEYCODE(STYLUS_BUTTON_SECONDARY), \ + DEFINE_KEYCODE(STYLUS_BUTTON_TERTIARY), \ + DEFINE_KEYCODE(STYLUS_BUTTON_TAIL) // 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. @@ -387,7 +393,6 @@ namespace android { DEFINE_AXIS(GENERIC_15), \ DEFINE_AXIS(GENERIC_16) - // NOTE: If you add new LEDs here, you must also add them to Input.h #define LEDS_SEQUENCE \ DEFINE_LED(NUM_LOCK), \ @@ -412,6 +417,8 @@ namespace android { DEFINE_FLAG(GESTURE), \ DEFINE_FLAG(WAKE) +// clang-format on + // --- InputEventLookup --- const std::unordered_map InputEventLookup::KEYCODES = {KEYCODES_SEQUENCE}; -- GitLab From ba354103c9d09159ad2f8088fa7e0285566d385d Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Mon, 1 Aug 2022 00:14:18 +0000 Subject: [PATCH 0286/1310] CE: Update layer composition state outside of CE First step in breaking CE dependencies with frontend. FE should provide a snapshot of the state for CE to consume. This cl updates the composition state based on whether visible regions are dirty before calling into composition engine. Test: presubmit Test: go/wm-smoke Bug: 238781169 Change-Id: I6efd71cf66d9e1cae163c98ad4ce3097e09848af --- .../include/compositionengine/LayerFE.h | 27 +--------- .../include/compositionengine/Output.h | 3 -- .../impl/CompositionEngine.h | 2 - .../include/compositionengine/impl/Output.h | 1 - .../include/compositionengine/mock/LayerFE.h | 3 +- .../include/compositionengine/mock/Output.h | 1 - .../src/CompositionEngine.cpp | 13 +---- .../CompositionEngine/src/Output.cpp | 9 ---- .../tests/CompositionEngineTest.cpp | 30 +++++------ .../CompositionEngine/tests/OutputTest.cpp | 53 ------------------- services/surfaceflinger/Layer.cpp | 42 ++++++--------- services/surfaceflinger/Layer.h | 6 +-- services/surfaceflinger/SurfaceFlinger.cpp | 10 ++-- services/surfaceflinger/fuzzer/README.md | 5 +- .../fuzzer/surfaceflinger_layer_fuzzer.cpp | 3 +- 15 files changed, 46 insertions(+), 162 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h index 9753a6c83c..a738da045a 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h @@ -53,31 +53,8 @@ public: // Called before composition starts. Should return true if this layer has // pending updates which would require an extra display refresh cycle to // process. - virtual bool onPreComposition(nsecs_t refreshStartTime) = 0; - - // Used with latchCompositionState() - enum class StateSubset { - // Gets the basic geometry (bounds, transparent region, visibility, - // transforms, alpha) for the layer, for computing visibility and - // coverage. - BasicGeometry, - - // Gets the full geometry (crops, buffer transforms, metadata) and - // content (buffer or color) state for the layer. - GeometryAndContent, - - // Gets the per frame content (buffer or color) state for the layer. - Content, - - // Gets the cursor state for the layer. - Cursor, - }; - - // Prepares the output-independent composition state for the layer. The - // StateSubset argument selects what portion of the state is actually needed - // by the CompositionEngine code, since computing everything may be - // expensive. - virtual void prepareCompositionState(StateSubset) = 0; + virtual bool onPreComposition(nsecs_t refreshStartTime, + bool updatingOutputGeometryThisFrame) = 0; struct ClientCompositionTargetSettings { enum class BlurSetting { diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h index 2203639b1a..874b330c1c 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h @@ -262,9 +262,6 @@ public: // Presents the output, finalizing all composition details virtual void present(const CompositionRefreshArgs&) = 0; - // Latches the front-end layer state for each output layer - virtual void updateLayerStateFromFE(const CompositionRefreshArgs&) const = 0; - // Enables predicting composition strategy to run client composition earlier virtual void setPredictCompositionStrategy(bool) = 0; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h index 386808d714..09079264da 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h @@ -51,8 +51,6 @@ public: // Debugging void dump(std::string&) const override; - void updateLayerStateFromFE(CompositionRefreshArgs& args); - // Testing void setNeedsAnotherUpdateForTest(bool); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h index 38a391b114..23d5570096 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h @@ -89,7 +89,6 @@ public: compositionengine::Output::CoverageState&) override; void setReleasedLayers(const compositionengine::CompositionRefreshArgs&) override; - void updateLayerStateFromFE(const CompositionRefreshArgs&) const override; void updateCompositionState(const compositionengine::CompositionRefreshArgs&) override; void planComposition() override; void writeCompositionState(const compositionengine::CompositionRefreshArgs&) override; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h index 2b704e697f..be0dbceffe 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h @@ -42,9 +42,8 @@ public: MOCK_CONST_METHOD0(getCompositionState, const LayerFECompositionState*()); - MOCK_METHOD1(onPreComposition, bool(nsecs_t)); + MOCK_METHOD2(onPreComposition, bool(nsecs_t, bool)); - MOCK_METHOD1(prepareCompositionState, void(compositionengine::LayerFE::StateSubset)); MOCK_CONST_METHOD1(prepareClientComposition, std::optional( compositionengine::LayerFE::ClientCompositionTargetSettings&)); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h index 2a04949cff..7592cac582 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h @@ -91,7 +91,6 @@ public: void(sp&, compositionengine::Output::CoverageState&)); MOCK_METHOD1(setReleasedLayers, void(const compositionengine::CompositionRefreshArgs&)); - MOCK_CONST_METHOD1(updateLayerStateFromFE, void(const CompositionRefreshArgs&)); MOCK_METHOD1(updateCompositionState, void(const CompositionRefreshArgs&)); MOCK_METHOD0(planComposition, void()); MOCK_METHOD1(writeCompositionState, void(const CompositionRefreshArgs&)); diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp index 6203dc6737..855507eec4 100644 --- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp +++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp @@ -105,8 +105,6 @@ void CompositionEngine::present(CompositionRefreshArgs& args) { } } - updateLayerStateFromFE(args); - for (const auto& output : args.outputs) { output->present(args); } @@ -119,8 +117,6 @@ void CompositionEngine::updateCursorAsync(CompositionRefreshArgs& args) { for (const auto& output : args.outputs) { for (auto* layer : output->getOutputLayersOrderedByZ()) { if (layer->isHardwareCursor()) { - // Latch the cursor composition state from each front-end layer. - layer->getLayerFE().prepareCompositionState(LayerFE::StateSubset::Cursor); layer->writeCursorPositionToHWC(); } } @@ -136,7 +132,7 @@ void CompositionEngine::preComposition(CompositionRefreshArgs& args) { mRefreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC); for (auto& layer : args.layers) { - if (layer->onPreComposition(mRefreshStartTime)) { + if (layer->onPreComposition(mRefreshStartTime, args.updatingOutputGeometryThisFrame)) { needsAnotherUpdate = true; } } @@ -152,12 +148,5 @@ void CompositionEngine::setNeedsAnotherUpdateForTest(bool value) { mNeedsAnotherUpdate = value; } -void CompositionEngine::updateLayerStateFromFE(CompositionRefreshArgs& args) { - // Update the composition state from each front-end layer - for (const auto& output : args.outputs) { - output->updateLayerStateFromFE(args); - } -} - } // namespace impl } // namespace android::compositionengine diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index b5894cf0be..e3f3680c1c 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -501,7 +501,6 @@ void Output::ensureOutputLayerIfVisible(sp& layerFE, // appear on multiple outputs. if (!coverage.latchedLayers.count(layerFE)) { coverage.latchedLayers.insert(layerFE); - layerFE->prepareCompositionState(compositionengine::LayerFE::StateSubset::BasicGeometry); } // Only consider the layers on this output @@ -725,14 +724,6 @@ void Output::setReleasedLayers(const compositionengine::CompositionRefreshArgs&) // The base class does nothing with this call. } -void Output::updateLayerStateFromFE(const CompositionRefreshArgs& args) const { - for (auto* layer : getOutputLayersOrderedByZ()) { - layer->getLayerFE().prepareCompositionState( - args.updatingGeometryThisFrame ? LayerFE::StateSubset::GeometryAndContent - : LayerFE::StateSubset::Content); - } -} - void Output::updateCompositionState(const compositionengine::CompositionRefreshArgs& refreshArgs) { ATRACE_CALL(); ALOGV(__FUNCTION__); diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp index de9de0150d..b570979f1f 100644 --- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp @@ -108,12 +108,6 @@ TEST_F(CompositionEnginePresentTest, worksAsExpected) { EXPECT_CALL(*mOutput2, prepare(Ref(mRefreshArgs), _)); EXPECT_CALL(*mOutput3, prepare(Ref(mRefreshArgs), _)); - // The next step in presenting is to make sure all outputs have the latest - // state from the front-end (SurfaceFlinger). - EXPECT_CALL(*mOutput1, updateLayerStateFromFE(Ref(mRefreshArgs))); - EXPECT_CALL(*mOutput2, updateLayerStateFromFE(Ref(mRefreshArgs))); - EXPECT_CALL(*mOutput3, updateLayerStateFromFE(Ref(mRefreshArgs))); - // The last step is to actually present each output. EXPECT_CALL(*mOutput1, present(Ref(mRefreshArgs))); EXPECT_CALL(*mOutput2, present(Ref(mRefreshArgs))); @@ -175,21 +169,18 @@ TEST_F(CompositionEngineUpdateCursorAsyncTest, handlesMultipleLayersBeingCursorL { InSequence seq; EXPECT_CALL(mOutput2Layer1.outputLayer, isHardwareCursor()).WillRepeatedly(Return(true)); - EXPECT_CALL(*mOutput2Layer1.layerFE, prepareCompositionState(LayerFE::StateSubset::Cursor)); EXPECT_CALL(mOutput2Layer1.outputLayer, writeCursorPositionToHWC()); } { InSequence seq; EXPECT_CALL(mOutput3Layer1.outputLayer, isHardwareCursor()).WillRepeatedly(Return(true)); - EXPECT_CALL(*mOutput3Layer1.layerFE, prepareCompositionState(LayerFE::StateSubset::Cursor)); EXPECT_CALL(mOutput3Layer1.outputLayer, writeCursorPositionToHWC()); } { InSequence seq; EXPECT_CALL(mOutput3Layer2.outputLayer, isHardwareCursor()).WillRepeatedly(Return(true)); - EXPECT_CALL(*mOutput3Layer2.layerFE, prepareCompositionState(LayerFE::StateSubset::Cursor)); EXPECT_CALL(mOutput3Layer2.outputLayer, writeCursorPositionToHWC()); } @@ -222,9 +213,12 @@ TEST_F(CompositionTestPreComposition, preCompositionInvokesLayerPreCompositionWi nsecs_t ts1 = 0; nsecs_t ts2 = 0; nsecs_t ts3 = 0; - EXPECT_CALL(*mLayer1FE, onPreComposition(_)).WillOnce(DoAll(SaveArg<0>(&ts1), Return(false))); - EXPECT_CALL(*mLayer2FE, onPreComposition(_)).WillOnce(DoAll(SaveArg<0>(&ts2), Return(false))); - EXPECT_CALL(*mLayer3FE, onPreComposition(_)).WillOnce(DoAll(SaveArg<0>(&ts3), Return(false))); + EXPECT_CALL(*mLayer1FE, onPreComposition(_, _)) + .WillOnce(DoAll(SaveArg<0>(&ts1), Return(false))); + EXPECT_CALL(*mLayer2FE, onPreComposition(_, _)) + .WillOnce(DoAll(SaveArg<0>(&ts2), Return(false))); + EXPECT_CALL(*mLayer3FE, onPreComposition(_, _)) + .WillOnce(DoAll(SaveArg<0>(&ts3), Return(false))); mRefreshArgs.outputs = {mOutput1}; mRefreshArgs.layers = {mLayer1FE, mLayer2FE, mLayer3FE}; @@ -238,9 +232,9 @@ TEST_F(CompositionTestPreComposition, preCompositionInvokesLayerPreCompositionWi } TEST_F(CompositionTestPreComposition, preCompositionDefaultsToNoUpdateNeeded) { - EXPECT_CALL(*mLayer1FE, onPreComposition(_)).WillOnce(Return(false)); - EXPECT_CALL(*mLayer2FE, onPreComposition(_)).WillOnce(Return(false)); - EXPECT_CALL(*mLayer3FE, onPreComposition(_)).WillOnce(Return(false)); + EXPECT_CALL(*mLayer1FE, onPreComposition(_, _)).WillOnce(Return(false)); + EXPECT_CALL(*mLayer2FE, onPreComposition(_, _)).WillOnce(Return(false)); + EXPECT_CALL(*mLayer3FE, onPreComposition(_, _)).WillOnce(Return(false)); mEngine.setNeedsAnotherUpdateForTest(true); @@ -255,9 +249,9 @@ TEST_F(CompositionTestPreComposition, preCompositionDefaultsToNoUpdateNeeded) { TEST_F(CompositionTestPreComposition, preCompositionSetsNeedsAnotherUpdateIfAtLeastOneLayerRequestsIt) { - EXPECT_CALL(*mLayer1FE, onPreComposition(_)).WillOnce(Return(true)); - EXPECT_CALL(*mLayer2FE, onPreComposition(_)).WillOnce(Return(false)); - EXPECT_CALL(*mLayer3FE, onPreComposition(_)).WillOnce(Return(false)); + EXPECT_CALL(*mLayer1FE, onPreComposition(_, _)).WillOnce(Return(true)); + EXPECT_CALL(*mLayer2FE, onPreComposition(_, _)).WillOnce(Return(false)); + EXPECT_CALL(*mLayer3FE, onPreComposition(_, _)).WillOnce(Return(false)); mRefreshArgs.outputs = {mOutput1}; mRefreshArgs.layers = {mLayer1FE, mLayer2FE, mLayer3FE}; diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index ace28648d7..eb209e9b72 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -757,56 +757,6 @@ TEST_F(OutputSetReleasedLayersTest, setReleasedLayersTakesGivenLayers) { ASSERT_EQ(layer3FE.get(), setLayers[2].promote().get()); } -/* - * Output::updateLayerStateFromFE() - */ - -using OutputUpdateLayerStateFromFETest = OutputTest; - -TEST_F(OutputUpdateLayerStateFromFETest, handlesNoOutputLayerCase) { - CompositionRefreshArgs refreshArgs; - - mOutput->updateLayerStateFromFE(refreshArgs); -} - -TEST_F(OutputUpdateLayerStateFromFETest, preparesContentStateForAllContainedLayers) { - InjectedLayer layer1; - InjectedLayer layer2; - InjectedLayer layer3; - - EXPECT_CALL(*layer1.layerFE.get(), prepareCompositionState(LayerFE::StateSubset::Content)); - EXPECT_CALL(*layer2.layerFE.get(), prepareCompositionState(LayerFE::StateSubset::Content)); - EXPECT_CALL(*layer3.layerFE.get(), prepareCompositionState(LayerFE::StateSubset::Content)); - - injectOutputLayer(layer1); - injectOutputLayer(layer2); - injectOutputLayer(layer3); - - CompositionRefreshArgs refreshArgs; - refreshArgs.updatingGeometryThisFrame = false; - - mOutput->updateLayerStateFromFE(refreshArgs); -} - -TEST_F(OutputUpdateLayerStateFromFETest, preparesGeometryAndContentStateForAllContainedLayers) { - InjectedLayer layer1; - InjectedLayer layer2; - InjectedLayer layer3; - - EXPECT_CALL(*layer1.layerFE, prepareCompositionState(LayerFE::StateSubset::GeometryAndContent)); - EXPECT_CALL(*layer2.layerFE, prepareCompositionState(LayerFE::StateSubset::GeometryAndContent)); - EXPECT_CALL(*layer3.layerFE, prepareCompositionState(LayerFE::StateSubset::GeometryAndContent)); - - injectOutputLayer(layer1); - injectOutputLayer(layer2); - injectOutputLayer(layer3); - - CompositionRefreshArgs refreshArgs; - refreshArgs.updatingGeometryThisFrame = true; - - mOutput->updateLayerStateFromFE(refreshArgs); -} - /* * Output::updateAndWriteCompositionState() */ @@ -1536,9 +1486,6 @@ const Region OutputEnsureOutputLayerIfVisibleTest::kTransparentRegionHintNegativ TEST_F(OutputEnsureOutputLayerIfVisibleTest, performsGeomLatchBeforeCheckingIfLayerIncluded) { EXPECT_CALL(mOutput, includesLayer(sp(mLayer.layerFE))).WillOnce(Return(false)); - EXPECT_CALL(*mLayer.layerFE, - prepareCompositionState(compositionengine::LayerFE::StateSubset::BasicGeometry)); - mGeomSnapshots.clear(); ensureOutputLayerIfVisible(); diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 1c720cc4dd..08b71c2754 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -465,6 +465,10 @@ void Layer::computeBounds(FloatRect parentBounds, ui::Transform parentTransform, for (const sp& child : mDrawingChildren) { child->computeBounds(mBounds, mEffectiveTransform, childShadowRadius); } + + if (mPotentialCursor) { + prepareCursorCompositionState(); + } } Rect Layer::getCroppedBufferSize(const State& s) const { @@ -650,30 +654,6 @@ sp Layer::asLayerFE() const { return sp::fromExisting(layerFE); } -void Layer::prepareCompositionState(compositionengine::LayerFE::StateSubset subset) { - using StateSubset = compositionengine::LayerFE::StateSubset; - - switch (subset) { - case StateSubset::BasicGeometry: - prepareBasicGeometryCompositionState(); - break; - - case StateSubset::GeometryAndContent: - prepareBasicGeometryCompositionState(); - prepareGeometryCompositionState(); - preparePerFrameCompositionState(); - break; - - case StateSubset::Content: - preparePerFrameCompositionState(); - break; - - case StateSubset::Cursor: - prepareCursorCompositionState(); - break; - } -} - const char* Layer::getDebugName() const { return mName.c_str(); } @@ -3426,7 +3406,7 @@ bool Layer::fenceHasSignaled() const { return fenceSignaled; } -bool Layer::onPreComposition(nsecs_t refreshStartTime) { +bool Layer::onPreComposition(nsecs_t refreshStartTime, bool /* updatingOutputGeometryThisFrame */) { for (const auto& handle : mDrawingState.callbackHandles) { handle->refreshStartTime = refreshStartTime; } @@ -4240,6 +4220,18 @@ bool Layer::hasBlur() const { return getBackgroundBlurRadius() > 0 || getDrawingState().blurRegions.size() > 0; } +void Layer::updateSnapshot(bool updateGeometry) { + if (!getCompositionEngineLayerFE()) { + return; + } + + if (updateGeometry) { + prepareBasicGeometryCompositionState(); + prepareGeometryCompositionState(); + } + preparePerFrameCompositionState(); +} + // --------------------------------------------------------------------------- std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate) { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index f6b9b0fd07..5030fd826e 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -574,9 +574,8 @@ public: // implements compositionengine::LayerFE const compositionengine::LayerFECompositionState* getCompositionState() const; bool fenceHasSignaled() const; - bool onPreComposition(nsecs_t); - void prepareCompositionState(compositionengine::LayerFE::StateSubset subset) override; - + // Called before composition. updatingOutputGeometryThisFrame is used by ARC++'s Layer subclass. + bool onPreComposition(nsecs_t refreshStartTime, bool updatingOutputGeometryThisFrame); std::optional prepareClientComposition( compositionengine::LayerFE::ClientCompositionTargetSettings&) const override; void onLayerDisplayed(ftl::SharedFuture); @@ -868,6 +867,7 @@ public: bool simpleBufferUpdate(const layer_state_t&) const; static bool isOpaqueFormat(PixelFormat format); + void updateSnapshot(bool updateGeometry); protected: friend class impl::SurfaceInterceptor; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 9dd3713998..e31490c66d 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2189,10 +2189,6 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) displayIds.push_back(display->getId()); } mPowerAdvisor->setDisplays(displayIds); - mDrawingState.traverseInZOrder([&refreshArgs](Layer* layer) { - if (auto layerFE = layer->getCompositionEngineLayerFE()) - refreshArgs.layers.push_back(layerFE); - }); if (DOES_CONTAIN_BORDER) { refreshArgs.borderInfoList.clear(); @@ -2223,6 +2219,12 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty; refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty; + mDrawingState.traverseInZOrder([&refreshArgs](Layer* layer) { + layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame); + if (auto layerFE = layer->getCompositionEngineLayerFE()) { + refreshArgs.layers.push_back(layerFE); + } + }); refreshArgs.blursAreExpensive = mBlursAreExpensive; refreshArgs.internalDisplayRotationFlags = DisplayDevice::getPrimaryDisplayRotationFlags(); diff --git a/services/surfaceflinger/fuzzer/README.md b/services/surfaceflinger/fuzzer/README.md index 7a5f229ae9..a06c41b139 100644 --- a/services/surfaceflinger/fuzzer/README.md +++ b/services/surfaceflinger/fuzzer/README.md @@ -78,9 +78,8 @@ You can find the possible values in the fuzzer's source code. Layer supports the following parameters: 1. Display Connection Types (parameter name: `fakeDisplay`) 2. State Sets (parameter name: `traverseInZOrder`) -3. State Subsets (parameter name: `prepareCompositionState`) -4. Disconnect modes (parameter name: `disconnect`) -5. Data Spaces (parameter name: `setDataspace`) +3. Disconnect modes (parameter name: `disconnect`) +4. Data Spaces (parameter name: `setDataspace`) You can find the possible values in the fuzzer's source code. diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp index 5b2ec961c6..9ece260ba5 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp @@ -148,7 +148,8 @@ void LayerFuzzer::invokeBufferStateLayer() { layer->computeSourceBounds(getFuzzedFloatRect(&mFdp)); layer->fenceHasSignaled(); - layer->onPreComposition(mFdp.ConsumeIntegral()); + layer->onPreComposition(mFdp.ConsumeIntegral(), + false /*updatingOutputGeometryThisFrame*/); const std::vector> callbacks; layer->setTransactionCompletedListeners(callbacks); -- GitLab From 47ff7080aef55395f038e4bdae59c95505dde51a Mon Sep 17 00:00:00 2001 From: Yeabkal Wubshit Date: Sat, 10 Sep 2022 23:09:15 -0700 Subject: [PATCH 0287/1310] Improve VelocityTracker Strategy Handling The native VelocityTracker class has been made 1-dimensional to allow support for axes beyond the historically-supported X/Y axes. This means that a given VelocityTracker instance does not necessarily handle data for all supported axes. As such, this CL sets up tracking strategy for an axis only on the first occassion a data arrives for the axis. Furthermore, to support use cases where different strategies may suit different axes better, we have introduced per-axis default strategies. Bug: 32830165 Test: atest libinput_tests; manual on-device fling tests Change-Id: I3f7115fdcec78d1577e90e9a55175e8868bf2dfb --- include/input/VelocityTracker.h | 27 +++---- libs/input/VelocityTracker.cpp | 96 ++++++++--------------- libs/input/tests/VelocityTracker_test.cpp | 21 +++++ 3 files changed, 66 insertions(+), 78 deletions(-) diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h index 53603c54cc..da4d877d0f 100644 --- a/include/input/VelocityTracker.h +++ b/include/input/VelocityTracker.h @@ -146,16 +146,12 @@ public: inline int32_t getActivePointerId() const { return mActivePointerId; } private: - // The default velocity tracker strategy. + // All axes supported for velocity tracking, mapped to their default strategies. // Although other strategies are available for testing and comparison purposes, - // this is the strategy that applications will actually use. Be very careful + // the default strategy is the one that applications will actually use. Be very careful // when adjusting the default strategy because it can dramatically affect // (often in a bad way) the user experience. - // TODO(b/32830165): define default strategy per axis. - static const Strategy DEFAULT_STRATEGY = Strategy::LSQ2; - - // Set of all axes supported for velocity tracking. - static const std::set SUPPORTED_AXES; + static const std::map DEFAULT_STRATEGY_BY_AXIS; // Axes specifying location on a 2D plane (i.e. X and Y). static const std::set PLANAR_AXES; @@ -163,9 +159,17 @@ private: nsecs_t mLastEventTime; BitSet32 mCurrentPointerIdBits; int32_t mActivePointerId; - std::map> mStrategies; - void configureStrategy(int32_t axis, const Strategy strategy); + // An override strategy passed in the constructor to be used for all axes. + // This strategy will apply to all axes, unless the default strategy is specified here. + // When default strategy is specified, then each axis will use a potentially different strategy + // based on a hardcoded mapping. + const Strategy mOverrideStrategy; + // Maps axes to their respective VelocityTrackerStrategy instances. + // Note that, only axes that have had MotionEvents (and not all supported axes) will be here. + std::map> mConfiguredStrategies; + + void configureStrategy(int32_t axis); static std::unique_ptr createStrategy(const Strategy strategy); }; @@ -181,7 +185,6 @@ protected: public: virtual ~VelocityTrackerStrategy() { } - virtual void clear() = 0; virtual void clearPointers(BitSet32 idBits) = 0; virtual void addMovement(nsecs_t eventTime, BitSet32 idBits, const std::vector& positions) = 0; @@ -213,7 +216,6 @@ public: LeastSquaresVelocityTrackerStrategy(uint32_t degree, Weighting weighting = WEIGHTING_NONE); virtual ~LeastSquaresVelocityTrackerStrategy(); - virtual void clear(); virtual void clearPointers(BitSet32 idBits); void addMovement(nsecs_t eventTime, BitSet32 idBits, const std::vector& positions) override; @@ -254,7 +256,6 @@ public: IntegratingVelocityTrackerStrategy(uint32_t degree); ~IntegratingVelocityTrackerStrategy(); - virtual void clear(); virtual void clearPointers(BitSet32 idBits); void addMovement(nsecs_t eventTime, BitSet32 idBits, const std::vector& positions) override; @@ -287,7 +288,6 @@ public: LegacyVelocityTrackerStrategy(); virtual ~LegacyVelocityTrackerStrategy(); - virtual void clear(); virtual void clearPointers(BitSet32 idBits); void addMovement(nsecs_t eventTime, BitSet32 idBits, const std::vector& positions) override; @@ -320,7 +320,6 @@ public: ImpulseVelocityTrackerStrategy(); virtual ~ImpulseVelocityTrackerStrategy(); - virtual void clear(); virtual void clearPointers(BitSet32 idBits); void addMovement(nsecs_t eventTime, BitSet32 idBits, const std::vector& positions) override; diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp index 6f88a3860b..527a75b2cf 100644 --- a/libs/input/VelocityTracker.cpp +++ b/libs/input/VelocityTracker.cpp @@ -125,39 +125,32 @@ static std::string matrixToString(const float* a, uint32_t m, uint32_t n, bool r // --- VelocityTracker --- -const std::set VelocityTracker::SUPPORTED_AXES = {AMOTION_EVENT_AXIS_X, - AMOTION_EVENT_AXIS_Y}; +const std::map VelocityTracker::DEFAULT_STRATEGY_BY_AXIS = + {{AMOTION_EVENT_AXIS_X, VelocityTracker::Strategy::LSQ2}, + {AMOTION_EVENT_AXIS_Y, VelocityTracker::Strategy::LSQ2}}; const std::set VelocityTracker::PLANAR_AXES = {AMOTION_EVENT_AXIS_X, AMOTION_EVENT_AXIS_Y}; VelocityTracker::VelocityTracker(const Strategy strategy) - : mLastEventTime(0), mCurrentPointerIdBits(0), mActivePointerId(-1) { - // Configure the strategy for each axis. - for (int32_t axis : SUPPORTED_AXES) { - configureStrategy(axis, strategy); - } -} + : mLastEventTime(0), + mCurrentPointerIdBits(0), + mActivePointerId(-1), + mOverrideStrategy(strategy) {} VelocityTracker::~VelocityTracker() { } -void VelocityTracker::configureStrategy(int32_t axis, const Strategy strategy) { +void VelocityTracker::configureStrategy(int32_t axis) { std::unique_ptr createdStrategy; - - if (strategy == VelocityTracker::Strategy::DEFAULT) { - createdStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY); + if (mOverrideStrategy != VelocityTracker::Strategy::DEFAULT) { + createdStrategy = createStrategy(mOverrideStrategy); } else { - createdStrategy = createStrategy(strategy); + createdStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY_BY_AXIS.at(axis)); } - if (createdStrategy == nullptr) { - ALOGE("Unrecognized velocity tracker strategy %" PRId32 ".", strategy); - createdStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY); - LOG_ALWAYS_FATAL_IF(createdStrategy == nullptr, - "Could not create the default velocity tracker strategy '%" PRId32 "'!", - strategy); - } - mStrategies[axis] = std::move(createdStrategy); + LOG_ALWAYS_FATAL_IF(createdStrategy == nullptr, + "Could not create velocity tracker strategy for axis '%" PRId32 "'!", axis); + mConfiguredStrategies[axis] = std::move(createdStrategy); } std::unique_ptr VelocityTracker::createStrategy( @@ -211,9 +204,7 @@ std::unique_ptr VelocityTracker::createStrategy( void VelocityTracker::clear() { mCurrentPointerIdBits.clear(); mActivePointerId = -1; - for (int32_t axis : SUPPORTED_AXES) { - mStrategies[axis]->clear(); - } + mConfiguredStrategies.clear(); } void VelocityTracker::clearPointers(BitSet32 idBits) { @@ -224,8 +215,8 @@ void VelocityTracker::clearPointers(BitSet32 idBits) { mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1; } - for (int32_t axis : SUPPORTED_AXES) { - mStrategies[axis]->clearPointers(idBits); + for (const auto& [_, strategy] : mConfiguredStrategies) { + strategy->clearPointers(idBits); } } @@ -242,9 +233,7 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, // We have not received any movements for too long. Assume that all pointers // have stopped. - for (const auto& [_, strategy] : mStrategies) { - strategy->clear(); - } + mConfiguredStrategies.clear(); } mLastEventTime = eventTime; @@ -257,7 +246,10 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, LOG_ALWAYS_FATAL_IF(idBits.count() != positionValues.size(), "Mismatching number of pointers, idBits=%" PRIu32 ", positions=%zu", idBits.count(), positionValues.size()); - mStrategies[axis]->addMovement(eventTime, idBits, positionValues); + if (mConfiguredStrategies.find(axis) == mConfiguredStrategies.end()) { + configureStrategy(axis); + } + mConfiguredStrategies[axis]->addMovement(eventTime, idBits, positionValues); } if (DEBUG_VELOCITY) { @@ -323,7 +315,7 @@ void VelocityTracker::addMovement(const MotionEvent* event) { // We have not received any movements for too long. Assume that all pointers // have stopped. for (int32_t axis : PLANAR_AXES) { - mStrategies[axis]->clear(); + mConfiguredStrategies.erase(axis); } } // These actions because they do not convey any new information about @@ -385,7 +377,7 @@ std::optional VelocityTracker::getVelocity(int32_t axis, uint32_t id) con VelocityTracker::ComputedVelocity VelocityTracker::getComputedVelocity(int32_t units, float maxVelocity) { ComputedVelocity computedVelocity; - for (int32_t axis : SUPPORTED_AXES) { + for (const auto& [axis, _] : mConfiguredStrategies) { BitSet32 copyIdBits = BitSet32(mCurrentPointerIdBits); while (!copyIdBits.isEmpty()) { uint32_t id = copyIdBits.clearFirstMarkedBit(); @@ -401,28 +393,22 @@ VelocityTracker::ComputedVelocity VelocityTracker::getComputedVelocity(int32_t u } bool VelocityTracker::getEstimator(int32_t axis, uint32_t id, Estimator* outEstimator) const { - if (SUPPORTED_AXES.find(axis) == SUPPORTED_AXES.end()) { + const auto& it = mConfiguredStrategies.find(axis); + if (it == mConfiguredStrategies.end()) { return false; } - return mStrategies.at(axis)->getEstimator(id, outEstimator); + return it->second->getEstimator(id, outEstimator); } // --- LeastSquaresVelocityTrackerStrategy --- -LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy( - uint32_t degree, Weighting weighting) : - mDegree(degree), mWeighting(weighting) { - clear(); -} +LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(uint32_t degree, + Weighting weighting) + : mDegree(degree), mWeighting(weighting), mIndex(0) {} LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() { } -void LeastSquaresVelocityTrackerStrategy::clear() { - mIndex = 0; - mMovements[0].idBits.clear(); -} - void LeastSquaresVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value); mMovements[mIndex].idBits = remainingIdBits; @@ -825,10 +811,6 @@ IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(uint32_t IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() { } -void IntegratingVelocityTrackerStrategy::clear() { - mPointerIdBits.clear(); -} - void IntegratingVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { mPointerIdBits.value &= ~idBits.value; } @@ -920,18 +902,11 @@ void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state, // --- LegacyVelocityTrackerStrategy --- -LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() { - clear(); -} +LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() : mIndex(0) {} LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() { } -void LegacyVelocityTrackerStrategy::clear() { - mIndex = 0; - mMovements[0].idBits.clear(); -} - void LegacyVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value); mMovements[mIndex].idBits = remainingIdBits; @@ -1029,18 +1004,11 @@ bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id, // --- ImpulseVelocityTrackerStrategy --- -ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy() { - clear(); -} +ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy() : mIndex(0) {} ImpulseVelocityTrackerStrategy::~ImpulseVelocityTrackerStrategy() { } -void ImpulseVelocityTrackerStrategy::clear() { - mIndex = 0; - mMovements[0].idBits.clear(); -} - void ImpulseVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value); mMovements[mIndex].idBits = remainingIdBits; diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp index f8e505299c..0b7def372b 100644 --- a/libs/input/tests/VelocityTracker_test.cpp +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -308,6 +308,27 @@ TEST_F(VelocityTrackerTest, TestGetComputedVelocity) { EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_SCROLL, DEFAULT_POINTER_ID)); } +TEST_F(VelocityTrackerTest, TestApiInteractionsWithNoMotionEvents) { + VelocityTracker vt(VelocityTracker::Strategy::DEFAULT); + + EXPECT_FALSE(vt.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)); + + VelocityTracker::Estimator estimator; + EXPECT_FALSE(vt.getEstimator(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID, &estimator)); + + VelocityTracker::ComputedVelocity computedVelocity = vt.getComputedVelocity(1000, 1000); + for (uint32_t id = 0; id <= MAX_POINTER_ID; id++) { + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, id)); + EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, id)); + } + + EXPECT_EQ(-1, vt.getActivePointerId()); + + // Make sure that the clearing functions execute without an issue. + vt.clearPointers(BitSet32(7U)); + vt.clear(); +} + TEST_F(VelocityTrackerTest, ThreePointsPositiveVelocityTest) { // Same coordinate is reported 2 times in a row // It is difficult to determine the correct answer here, but at least the direction -- GitLab From 38c849884c74c1c7845046da656734d80669617a Mon Sep 17 00:00:00 2001 From: ramindani Date: Mon, 29 Aug 2022 18:02:57 +0000 Subject: [PATCH 0288/1310] SF Adds powerOnImminent to the GlobalSignals This helps with moving the refresh rate selection into RefreshRateConfigs instead of calling from Scheduler. BUG: 240743471 Test: atest libsurfaceflinger_unittest Change-Id: I4dba607e82ddb139be3daebe7f5c764fd5ae418c --- .../Scheduler/RefreshRateConfigs.cpp | 10 ++++++- .../Scheduler/RefreshRateConfigs.h | 6 ++++- .../surfaceflinger/Scheduler/Scheduler.cpp | 14 ++++------ .../unittests/RefreshRateConfigsTest.cpp | 26 +++++++++++++++++++ 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp index d270655f4f..00886f030e 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp @@ -280,6 +280,15 @@ auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vectorsecond; + + // Keep the display at max refresh rate for the duration of powering on the display. + if (signals.powerOnImminent) { + ALOGV("Power On Imminent"); + const auto& max = getMaxRefreshRateByPolicyLocked(activeMode.getGroup()); + return {max, GlobalSignals{.powerOnImminent = true}}; + } + int noVoteLayers = 0; int minVoteLayers = 0; int maxVoteLayers = 0; @@ -326,7 +335,6 @@ auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vectordefaultMode)->get(); - const auto& activeMode = *getActiveModeItLocked()->second; // If the default mode group is different from the group of current mode, // this means a layer requesting a seamed mode switch just disappeared and diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h index b2cfb03e42..19bcb94bf2 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h +++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h @@ -185,9 +185,13 @@ public: bool touch = false; // True if the system hasn't seen any buffers posted to layers recently. bool idle = false; + // Whether the display is about to be powered on, or has been in PowerMode::ON + // within the timeout of DisplayPowerTimer. + bool powerOnImminent = false; bool operator==(GlobalSignals other) const { - return touch == other.touch && idle == other.idle; + return touch == other.touch && idle == other.idle && + powerOnImminent == other.powerOnImminent; } }; diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index bec39a75d0..ff6b461449 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -704,17 +704,13 @@ auto Scheduler::chooseDisplayMode() -> std::pair const auto configs = holdRefreshRateConfigs(); - // If Display Power is not in normal operation we want to be in performance mode. When coming - // back to normal mode, a grace period is given with DisplayPowerTimer. - if (mDisplayPowerTimer && - (mPolicy.displayPowerMode != hal::PowerMode::ON || - mPolicy.displayPowerTimer == TimerState::Reset)) { - constexpr GlobalSignals kNoSignals; - return {configs->getMaxRefreshRateByPolicy(), kNoSignals}; - } + const bool powerOnImminent = mDisplayPowerTimer && + (mPolicy.displayPowerMode != hal::PowerMode::ON || + mPolicy.displayPowerTimer == TimerState::Reset); const GlobalSignals signals{.touch = mTouchTimer && mPolicy.touch == TouchState::Active, - .idle = mPolicy.idleTimer == TimerState::Expired}; + .idle = mPolicy.idleTimer == TimerState::Expired, + .powerOnImminent = powerOnImminent}; return configs->getBestRefreshRate(mPolicy.contentRequirements, signals); } diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp index 4f20932f2f..5d9b2a837d 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp @@ -977,6 +977,32 @@ TEST_F(RefreshRateConfigsTest, scrollWhileWatching60fps_60_90) { EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); } +TEST_F(RefreshRateConfigsTest, powerOnImminentConsidered) { + RefreshRateConfigs configs(kModes_60_90, kModeId60); + + auto [refreshRate, signals] = configs.getBestRefreshRate({}, {}); + EXPECT_FALSE(signals.powerOnImminent); + EXPECT_EQ(kMode90, refreshRate); + + std::tie(refreshRate, signals) = configs.getBestRefreshRate({}, {.powerOnImminent = true}); + EXPECT_TRUE(signals.powerOnImminent); + EXPECT_EQ(kMode90, refreshRate); + + std::vector layers = {{.weight = 1.f}}; + auto& lr1 = layers[0]; + lr1.vote = LayerVoteType::ExplicitExactOrMultiple; + lr1.desiredRefreshRate = 60_Hz; + lr1.name = "60Hz ExplicitExactOrMultiple"; + + std::tie(refreshRate, signals) = configs.getBestRefreshRate(layers, {.powerOnImminent = false}); + EXPECT_FALSE(signals.powerOnImminent); + EXPECT_EQ(kMode60, refreshRate); + + std::tie(refreshRate, signals) = configs.getBestRefreshRate(layers, {.powerOnImminent = true}); + EXPECT_TRUE(signals.powerOnImminent); + EXPECT_EQ(kMode90, refreshRate); +} + TEST_F(RefreshRateConfigsTest, touchConsidered) { RefreshRateConfigs configs(kModes_60_90, kModeId60); -- GitLab From 95785e28b472603a527a37705c393d3c97aea21d Mon Sep 17 00:00:00 2001 From: hongzuo liu Date: Tue, 6 Sep 2022 02:51:35 +0000 Subject: [PATCH 0289/1310] [Optimization] It's no need to start the dispatch cycle going if the outbound queue was not previously empty. If the outbound queue was not previpusly empty, it indicates that the connection's pipe is full.We will try start the next dispatch cycle for this connection in "doDispatchCycleFinishedCommand". This follows "enqueueDispatchEntriesLocked". Test: none Change-Id: I7736c2bfdb13c35a51b74c46ada7b0f562fecfd9 --- .../inputflinger/dispatcher/InputDispatcher.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index b52e3128db..f73e90753b 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2998,7 +2998,7 @@ void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime, ATRACE_NAME(message.c_str()); } - bool wasEmpty = connection->outboundQueue.empty(); + const bool wasEmpty = connection->outboundQueue.empty(); // Enqueue dispatch entries for the requested modes. enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, @@ -3681,6 +3681,8 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( target.inputChannel = connection->inputChannel; target.flags = InputTarget::FLAG_DISPATCH_AS_IS; + const bool wasEmpty = connection->outboundQueue.empty(); + for (size_t i = 0; i < cancelationEvents.size(); i++) { std::unique_ptr cancelationEventEntry = std::move(cancelationEvents[i]); switch (cancelationEventEntry->type) { @@ -3715,7 +3717,10 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( InputTarget::FLAG_DISPATCH_AS_IS); } - startDispatchCycleLocked(currentTime, connection); + // If the outbound queue was previously empty, start the dispatch cycle going. + if (wasEmpty && !connection->outboundQueue.empty()) { + startDispatchCycleLocked(currentTime, connection); + } } void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( @@ -3747,6 +3752,7 @@ void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( target.inputChannel = connection->inputChannel; target.flags = InputTarget::FLAG_DISPATCH_AS_IS; + const bool wasEmpty = connection->outboundQueue.empty(); for (std::unique_ptr& downEventEntry : downEvents) { switch (downEventEntry->type) { case EventEntry::Type::MOTION: { @@ -3773,7 +3779,10 @@ void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( InputTarget::FLAG_DISPATCH_AS_IS); } - startDispatchCycleLocked(downTime, connection); + // If the outbound queue was previously empty, start the dispatch cycle going. + if (wasEmpty && !connection->outboundQueue.empty()) { + startDispatchCycleLocked(downTime, connection); + } } std::unique_ptr InputDispatcher::splitMotionEvent( -- GitLab From c16974e32d4628dab9e888612cec0e8620451e2e Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Tue, 13 Sep 2022 17:35:48 +0000 Subject: [PATCH 0290/1310] Turn down GLESRenderEngine tests Some tests are flaky which is causing delays in continuous integration. GLESRenderEngine has not been the default backend for RenderEngine since Android 12, and no known OEM currently opts out of Skia's RenderEngine, which is the replacement. So, now's not a bad time to turn down these tests. Also, as part of removing the tests, turn on and fix some color management tests which had been accidentally disabled. Bug: 245359125 Bug: 199918329 Test: librenderengine_test Change-Id: I1ec93eaad63daa2268f92403bf71bcb65861072b --- libs/renderengine/tests/RenderEngineTest.cpp | 226 ++----------------- 1 file changed, 17 insertions(+), 209 deletions(-) diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index 9d8b2df98a..777d02f415 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -37,7 +37,6 @@ #include #include -#include "../gl/GLESRenderEngine.h" #include "../skia/SkiaGLRenderEngine.h" #include "../threaded/RenderEngineThreaded.h" @@ -108,73 +107,9 @@ public: virtual std::string name() = 0; virtual renderengine::RenderEngine::RenderEngineType type() = 0; virtual std::unique_ptr createRenderEngine() = 0; - virtual std::unique_ptr createGLESRenderEngine() { - return nullptr; - } virtual bool useColorManagement() const = 0; }; -class GLESRenderEngineFactory : public RenderEngineFactory { -public: - std::string name() override { return "GLESRenderEngineFactory"; } - - renderengine::RenderEngine::RenderEngineType type() { - return renderengine::RenderEngine::RenderEngineType::GLES; - } - - std::unique_ptr createRenderEngine() override { - return createGLESRenderEngine(); - } - - std::unique_ptr createGLESRenderEngine() { - renderengine::RenderEngineCreationArgs reCreationArgs = - renderengine::RenderEngineCreationArgs::Builder() - .setPixelFormat(static_cast(ui::PixelFormat::RGBA_8888)) - .setImageCacheSize(1) - .setUseColorManagerment(false) - .setEnableProtectedContext(false) - .setPrecacheToneMapperShaderOnly(false) - .setSupportsBackgroundBlur(true) - .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM) - .setRenderEngineType(type()) - .setUseColorManagerment(useColorManagement()) - .build(); - return renderengine::gl::GLESRenderEngine::create(reCreationArgs); - } - - bool useColorManagement() const override { return false; } -}; - -class GLESCMRenderEngineFactory : public RenderEngineFactory { -public: - std::string name() override { return "GLESCMRenderEngineFactory"; } - - renderengine::RenderEngine::RenderEngineType type() { - return renderengine::RenderEngine::RenderEngineType::GLES; - } - - std::unique_ptr createRenderEngine() override { - return createGLESRenderEngine(); - } - - std::unique_ptr createGLESRenderEngine() override { - renderengine::RenderEngineCreationArgs reCreationArgs = - renderengine::RenderEngineCreationArgs::Builder() - .setPixelFormat(static_cast(ui::PixelFormat::RGBA_8888)) - .setImageCacheSize(1) - .setEnableProtectedContext(false) - .setPrecacheToneMapperShaderOnly(false) - .setSupportsBackgroundBlur(true) - .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM) - .setRenderEngineType(type()) - .setUseColorManagerment(useColorManagement()) - .build(); - return renderengine::gl::GLESRenderEngine::create(reCreationArgs); - } - - bool useColorManagement() const override { return true; } -}; - class SkiaGLESRenderEngineFactory : public RenderEngineFactory { public: std::string name() override { return "SkiaGLRenderEngineFactory"; } @@ -313,9 +248,6 @@ public: } for (uint32_t texName : mTexNames) { mRE->deleteTextures(1, &texName); - if (mGLESRE != nullptr) { - EXPECT_FALSE(mGLESRE->isTextureNameKnownForTesting(texName)); - } } const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); @@ -537,10 +469,6 @@ public: auto fence = result.value(); fence->waitForever(LOG_TAG); - - if (layers.size() > 0 && mGLESRE != nullptr) { - ASSERT_TRUE(mGLESRE->isFramebufferImageCachedForTesting(mBuffer->getBuffer()->getId())); - } } void drawEmptyLayers() { @@ -663,26 +591,13 @@ public: std::unique_ptr mRE; std::shared_ptr mBuffer; - // GLESRenderEngine for testing GLES-specific behavior. - // Owened by mRE, but this is downcasted. - renderengine::gl::GLESRenderEngine* mGLESRE = nullptr; std::vector mTexNames; }; void RenderEngineTest::initializeRenderEngine() { const auto& renderEngineFactory = GetParam(); - if (renderEngineFactory->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - // Only GLESRenderEngine exposes test-only methods. Provide a pointer to the - // GLESRenderEngine if we're using it so that we don't need to dynamic_cast - // every time. - std::unique_ptr renderEngine = - renderEngineFactory->createGLESRenderEngine(); - mGLESRE = renderEngine.get(); - mRE = std::move(renderEngine); - } else { - mRE = renderEngineFactory->createRenderEngine(); - } + mRE = renderEngineFactory->createRenderEngine(); mBuffer = allocateDefaultBuffer(); } @@ -1003,9 +918,9 @@ void RenderEngineTest::fillBufferWithColorTransformAndSourceDataspace( std::vector layers; renderengine::LayerSettings layer; - layer.sourceDataspace = sourceDataspace; layer.geometry.boundaries = Rect(1, 1).toFloatRect(); SourceVariant::fillColor(layer, 0.5f, 0.25f, 0.125f, this); + layer.sourceDataspace = sourceDataspace; layer.alpha = 1.0f; // construct a fake color matrix @@ -1031,13 +946,13 @@ void RenderEngineTest::fillBufferColorTransform() { template void RenderEngineTest::fillBufferColorTransformAndSourceDataspace() { unordered_map dataspaceToColorMap; - dataspaceToColorMap[ui::Dataspace::V0_BT709] = {172, 0, 0, 255}; - dataspaceToColorMap[ui::Dataspace::BT2020] = {172, 0, 0, 255}; - dataspaceToColorMap[ui::Dataspace::ADOBE_RGB] = {172, 0, 0, 255}; + dataspaceToColorMap[ui::Dataspace::V0_BT709] = {77, 0, 0, 255}; + dataspaceToColorMap[ui::Dataspace::BT2020] = {101, 0, 0, 255}; + dataspaceToColorMap[ui::Dataspace::ADOBE_RGB] = {75, 0, 0, 255}; ui::Dataspace customizedDataspace = static_cast( ui::Dataspace::STANDARD_BT709 | ui::Dataspace::TRANSFER_GAMMA2_2 | ui::Dataspace::RANGE_FULL); - dataspaceToColorMap[customizedDataspace] = {172, 0, 0, 255}; + dataspaceToColorMap[customizedDataspace] = {61, 0, 0, 255}; for (const auto& [sourceDataspace, color] : dataspaceToColorMap) { fillBufferWithColorTransformAndSourceDataspace(sourceDataspace); expectBufferColor(fullscreenRect(), color.r, color.g, color.b, color.a, 1); @@ -1077,13 +992,13 @@ void RenderEngineTest::fillBufferWithColorTransformAndOutputDataspace( template void RenderEngineTest::fillBufferColorTransformAndOutputDataspace() { unordered_map dataspaceToColorMap; - dataspaceToColorMap[ui::Dataspace::V0_BT709] = {202, 0, 0, 255}; - dataspaceToColorMap[ui::Dataspace::BT2020] = {192, 0, 0, 255}; - dataspaceToColorMap[ui::Dataspace::ADOBE_RGB] = {202, 0, 0, 255}; + dataspaceToColorMap[ui::Dataspace::V0_BT709] = {198, 0, 0, 255}; + dataspaceToColorMap[ui::Dataspace::BT2020] = {187, 0, 0, 255}; + dataspaceToColorMap[ui::Dataspace::ADOBE_RGB] = {192, 0, 0, 255}; ui::Dataspace customizedDataspace = static_cast( ui::Dataspace::STANDARD_BT709 | ui::Dataspace::TRANSFER_GAMMA2_6 | ui::Dataspace::RANGE_FULL); - dataspaceToColorMap[customizedDataspace] = {202, 0, 0, 255}; + dataspaceToColorMap[customizedDataspace] = {205, 0, 0, 255}; for (const auto& [outputDataspace, color] : dataspaceToColorMap) { fillBufferWithColorTransformAndOutputDataspace(outputDataspace); expectBufferColor(fullscreenRect(), color.r, color.g, color.b, color.a, 1); @@ -1599,9 +1514,7 @@ void RenderEngineTest::tonemap(ui::Dataspace sourceDataspace, std::function(), - std::make_shared(), - std::make_shared(), + testing::Values(std::make_shared(), std::make_shared())); TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) { @@ -1610,12 +1523,6 @@ TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) { } TEST_P(RenderEngineTest, drawLayers_fillRedBufferAndEmptyBuffer) { - const auto& renderEngineFactory = GetParam(); - if (renderEngineFactory->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - // GLES-specific test - return; - } - initializeRenderEngine(); renderengine::DisplaySettings settings; settings.physicalDisplay = fullscreenRect(); @@ -1689,41 +1596,6 @@ TEST_P(RenderEngineTest, drawLayers_nullOutputBuffer) { ASSERT_EQ(BAD_VALUE, result.error()); } -TEST_P(RenderEngineTest, drawLayers_doesNotCacheFramebuffer) { - const auto& renderEngineFactory = GetParam(); - - if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { - // GLES-specific test - return; - } - - initializeRenderEngine(); - - renderengine::DisplaySettings settings; - settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR; - settings.physicalDisplay = fullscreenRect(); - settings.clip = fullscreenRect(); - - std::vector layers; - renderengine::LayerSettings layer; - layer.geometry.boundaries = fullscreenRect().toFloatRect(); - BufferSourceVariant::fillColor(layer, 1.0f, 0.0f, 0.0f, this); - layer.alpha = 1.0; - layers.push_back(layer); - - ftl::Future future = - mRE->drawLayers(settings, layers, mBuffer, false, base::unique_fd()); - ASSERT_TRUE(future.valid()); - auto result = future.get(); - - ASSERT_TRUE(result.ok()); - auto fence = result.value(); - fence->waitForever(LOG_TAG); - - ASSERT_FALSE(mGLESRE->isFramebufferImageCachedForTesting(mBuffer->getBuffer()->getId())); - expectBufferColor(fullscreenRect(), 255, 0, 0, 255); -} - TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_colorSource) { initializeRenderEngine(); fillRedBuffer(); @@ -1783,11 +1655,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_sourceDataspace) { const auto& renderEngineFactory = GetParam(); // skip for non color management if (!renderEngineFactory->useColorManagement()) { - return; - } - // skip for GLESRenderEngine - if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { - return; + GTEST_SKIP(); } initializeRenderEngine(); @@ -1798,11 +1666,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_outputDataspace) { const auto& renderEngineFactory = GetParam(); // skip for non color management if (!renderEngineFactory->useColorManagement()) { - return; - } - // skip for GLESRenderEngine - if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { - return; + GTEST_SKIP(); } initializeRenderEngine(); @@ -1893,11 +1757,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_o const auto& renderEngineFactory = GetParam(); // skip for non color management if (!renderEngineFactory->useColorManagement()) { - return; - } - // skip for GLESRenderEngine - if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { - return; + GTEST_SKIP(); } initializeRenderEngine(); @@ -1908,11 +1768,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_o const auto& renderEngineFactory = GetParam(); // skip for non color management if (!renderEngineFactory->useColorManagement()) { - return; - } - // skip for GLESRenderEngine - if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { - return; + GTEST_SKIP(); } initializeRenderEngine(); @@ -2003,11 +1859,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_b const auto& renderEngineFactory = GetParam(); // skip for non color management if (!renderEngineFactory->useColorManagement()) { - return; - } - // skip for GLESRenderEngine - if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { - return; + GTEST_SKIP(); } initializeRenderEngine(); @@ -2018,11 +1870,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_b const auto& renderEngineFactory = GetParam(); // skip for non color management if (!renderEngineFactory->useColorManagement()) { - return; - } - // skip for GLESRenderEngine - if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) { - return; + GTEST_SKIP(); } initializeRenderEngine(); @@ -2537,10 +2385,6 @@ TEST_P(RenderEngineTest, testBorder) { } TEST_P(RenderEngineTest, testDimming) { - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - GTEST_SKIP(); - } - initializeRenderEngine(); const ui::Dataspace dataspace = ui::Dataspace::V0_SRGB_LINEAR; @@ -2613,9 +2457,6 @@ TEST_P(RenderEngineTest, testDimming) { } TEST_P(RenderEngineTest, testDimming_inGammaSpace) { - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - GTEST_SKIP(); - } initializeRenderEngine(); const ui::Dataspace dataspace = static_cast(ui::Dataspace::STANDARD_BT709 | @@ -2691,9 +2532,6 @@ TEST_P(RenderEngineTest, testDimming_inGammaSpace) { } TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform) { - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - GTEST_SKIP(); - } initializeRenderEngine(); const ui::Dataspace dataspace = static_cast(ui::Dataspace::STANDARD_BT709 | @@ -2754,9 +2592,6 @@ TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform) { } TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform_deviceHandles) { - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - GTEST_SKIP(); - } initializeRenderEngine(); const ui::Dataspace dataspace = static_cast(ui::Dataspace::STANDARD_BT709 | @@ -2819,9 +2654,6 @@ TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform_devi TEST_P(RenderEngineTest, testDimming_withoutTargetLuminance) { initializeRenderEngine(); - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - return; - } const auto displayRect = Rect(2, 1); const renderengine::DisplaySettings display{ @@ -2927,10 +2759,6 @@ TEST_P(RenderEngineTest, test_tonemapPQMatches) { GTEST_SKIP(); } - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - GTEST_SKIP(); - } - initializeRenderEngine(); tonemap( @@ -2948,10 +2776,6 @@ TEST_P(RenderEngineTest, test_tonemapHLGMatches) { GTEST_SKIP(); } - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - GTEST_SKIP(); - } - initializeRenderEngine(); tonemap( @@ -2965,10 +2789,6 @@ TEST_P(RenderEngineTest, test_tonemapHLGMatches) { } TEST_P(RenderEngineTest, r8_behaves_as_mask) { - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - return; - } - initializeRenderEngine(); const auto r8Buffer = allocateR8Buffer(2, 1); @@ -3026,10 +2846,6 @@ TEST_P(RenderEngineTest, r8_behaves_as_mask) { } TEST_P(RenderEngineTest, r8_respects_color_transform) { - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - return; - } - initializeRenderEngine(); const auto r8Buffer = allocateR8Buffer(2, 1); @@ -3092,10 +2908,6 @@ TEST_P(RenderEngineTest, r8_respects_color_transform) { } TEST_P(RenderEngineTest, r8_respects_color_transform_when_device_handles) { - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - return; - } - initializeRenderEngine(); const auto r8Buffer = allocateR8Buffer(2, 1); @@ -3161,10 +2973,6 @@ TEST_P(RenderEngineTest, r8_respects_color_transform_when_device_handles) { } TEST_P(RenderEngineTest, primeShaderCache) { - if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) { - GTEST_SKIP(); - } - initializeRenderEngine(); auto fut = mRE->primeCache(); -- GitLab From c58ebaa059986e549794313e826023f10a5becb4 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 14 Sep 2022 18:23:11 +0000 Subject: [PATCH 0291/1310] Include all input tests from CtsViewTestCases in input presubmit Bug: 246392485 Change-Id: I117f48c6401c30f1f4bd256b17f97df08eeb419e Test: Presubmit --- services/inputflinger/TEST_MAPPING | 1 + 1 file changed, 1 insertion(+) diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING index f673a287c4..4a0f2ec304 100644 --- a/services/inputflinger/TEST_MAPPING +++ b/services/inputflinger/TEST_MAPPING @@ -38,6 +38,7 @@ "name": "CtsViewTestCases", "options": [ { + "include-filter": "android.view.cts.input", "include-filter": "android.view.cts.MotionEventTest", "include-filter": "android.view.cts.PointerCaptureTest", "include-filter": "android.view.cts.VerifyInputEventTest" -- GitLab From 009619f98ce51e113dbbefb5722d4b585e719dbd Mon Sep 17 00:00:00 2001 From: Chavi Weingarten Date: Thu, 15 Sep 2022 20:27:25 +0000 Subject: [PATCH 0292/1310] Add more tracing info when setting buffer Test: Traces Bug: 244218818 Change-Id: I150d703986f24fd6ad3a3bc3c6ea65c5f717cba7 --- services/surfaceflinger/Layer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 08b71c2754..91e32f3bfe 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -3177,8 +3177,7 @@ bool Layer::setBuffer(std::shared_ptr& buffer, const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime, bool isAutoTimestamp, std::optional dequeueTime, const FrameTimelineInfo& info) { - ATRACE_CALL(); - + ATRACE_FORMAT("setBuffer %s - hasBuffer=%s", getDebugName(), (buffer ? "true" : "false")); if (!buffer) { return false; } @@ -3187,6 +3186,7 @@ bool Layer::setBuffer(std::shared_ptr& buffer, bufferData.flags.test(BufferData::BufferDataChange::frameNumberChanged); const uint64_t frameNumber = frameNumberChanged ? bufferData.frameNumber : mDrawingState.frameNumber + 1; + ATRACE_FORMAT_INSTANT("setBuffer %s - %" PRIu64, getDebugName(), frameNumber); if (mDrawingState.buffer) { mReleasePreviousBuffer = true; -- GitLab From 67b3ab097b8128255f56a9e5d6db2462e7f1e9d1 Mon Sep 17 00:00:00 2001 From: Yeabkal Wubshit Date: Fri, 16 Sep 2022 00:18:17 -0700 Subject: [PATCH 0293/1310] VelocityTracker Cleanups This is a minor clean-up CL on changes made in recent CLs to make VelocityTracker 1D (see ag/19911102 and ag/19921486). Bug: 32830165 Test: build and tests unaffected Change-Id: I36eead3e8191855a8c87fae5c1ef1fc9424ee6b9 --- include/input/VelocityTracker.h | 10 ---------- libs/input/VelocityTracker.cpp | 32 ++++++++++++++++---------------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h index 4251f0417d..6fac19f3dd 100644 --- a/include/input/VelocityTracker.h +++ b/include/input/VelocityTracker.h @@ -145,16 +145,6 @@ public: inline int32_t getActivePointerId() const { return mActivePointerId; } private: - // All axes supported for velocity tracking, mapped to their default strategies. - // Although other strategies are available for testing and comparison purposes, - // the default strategy is the one that applications will actually use. Be very careful - // when adjusting the default strategy because it can dramatically affect - // (often in a bad way) the user experience. - static const std::map DEFAULT_STRATEGY_BY_AXIS; - - // Axes specifying location on a 2D plane (i.e. X and Y). - static const std::set PLANAR_AXES; - nsecs_t mLastEventTime; BitSet32 mCurrentPointerIdBits; int32_t mActivePointerId; diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp index 527a75b2cf..587e014cc9 100644 --- a/libs/input/VelocityTracker.cpp +++ b/libs/input/VelocityTracker.cpp @@ -55,6 +55,18 @@ const bool DEBUG_IMPULSE = // Nanoseconds per milliseconds. static const nsecs_t NANOS_PER_MS = 1000000; +// All axes supported for velocity tracking, mapped to their default strategies. +// Although other strategies are available for testing and comparison purposes, +// the default strategy is the one that applications will actually use. Be very careful +// when adjusting the default strategy because it can dramatically affect +// (often in a bad way) the user experience. +static const std::map DEFAULT_STRATEGY_BY_AXIS = + {{AMOTION_EVENT_AXIS_X, VelocityTracker::Strategy::LSQ2}, + {AMOTION_EVENT_AXIS_Y, VelocityTracker::Strategy::LSQ2}}; + +// Axes specifying location on a 2D plane (i.e. X and Y). +static const std::set PLANAR_AXES = {AMOTION_EVENT_AXIS_X, AMOTION_EVENT_AXIS_Y}; + // Threshold for determining that a pointer has stopped moving. // Some input devices do not send ACTION_MOVE events in the case where a pointer has // stopped. We need to detect this case so that we can accurately predict the @@ -125,12 +137,6 @@ static std::string matrixToString(const float* a, uint32_t m, uint32_t n, bool r // --- VelocityTracker --- -const std::map VelocityTracker::DEFAULT_STRATEGY_BY_AXIS = - {{AMOTION_EVENT_AXIS_X, VelocityTracker::Strategy::LSQ2}, - {AMOTION_EVENT_AXIS_Y, VelocityTracker::Strategy::LSQ2}}; - -const std::set VelocityTracker::PLANAR_AXES = {AMOTION_EVENT_AXIS_X, AMOTION_EVENT_AXIS_Y}; - VelocityTracker::VelocityTracker(const Strategy strategy) : mLastEventTime(0), mCurrentPointerIdBits(0), @@ -145,7 +151,7 @@ void VelocityTracker::configureStrategy(int32_t axis) { if (mOverrideStrategy != VelocityTracker::Strategy::DEFAULT) { createdStrategy = createStrategy(mOverrideStrategy); } else { - createdStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY_BY_AXIS.at(axis)); + createdStrategy = createStrategy(DEFAULT_STRATEGY_BY_AXIS.at(axis)); } LOG_ALWAYS_FATAL_IF(createdStrategy == nullptr, @@ -283,9 +289,7 @@ void VelocityTracker::addMovement(const MotionEvent* event) { case AMOTION_EVENT_ACTION_HOVER_ENTER: // Clear all pointers on down before adding the new movement. clear(); - for (int32_t axis : PLANAR_AXES) { - axesToProcess.insert(axis); - } + axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end()); break; case AMOTION_EVENT_ACTION_POINTER_DOWN: { // Start a new movement trace for a pointer that just went down. @@ -294,16 +298,12 @@ void VelocityTracker::addMovement(const MotionEvent* event) { BitSet32 downIdBits; downIdBits.markBit(event->getPointerId(event->getActionIndex())); clearPointers(downIdBits); - for (int32_t axis : PLANAR_AXES) { - axesToProcess.insert(axis); - } + axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end()); break; } case AMOTION_EVENT_ACTION_MOVE: case AMOTION_EVENT_ACTION_HOVER_MOVE: - for (int32_t axis : PLANAR_AXES) { - axesToProcess.insert(axis); - } + axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end()); break; case AMOTION_EVENT_ACTION_POINTER_UP: case AMOTION_EVENT_ACTION_UP: { -- GitLab From 1ac2a9297dc31a9d54cac2d5723f9d021985e7dd Mon Sep 17 00:00:00 2001 From: Chavi Weingarten Date: Thu, 15 Sep 2022 20:34:48 +0000 Subject: [PATCH 0294/1310] Fixed bug with caching buffers SCC::cacheBuffers checks if the Transaction contains a buffer before looking through the states. However, the flag mContainsBuffer could be cleared if a single layer clears the buffer. This isn't correct since there could be other SC in the Transaction that have a buffer and we would end up not caching them. Rename mContainsBuffer to mMayContainBuffer since it's more of a hint that the Transaction may have a buffer. It's not really worth storing a count when we're going to check each layer anyway. mMayContainBuffer just helps optimize in the scenario when we know for sure that no buffer has even been set before apply was called. Additionally, remove the code that clears mMayContainBuffer when the buffer has been removed since there may be a different buffer set for a different SC. Because mMayContainBuffer is only used to determine whether to cache, there's no need to parcel it. When writeToParcel is called, the data is cached so the other process doesn't need to know about it. Test: Builds Bug: 246986005 Change-Id: Id642cba7f13112084d842b1222b2bc7256d2e577 --- libs/gui/SurfaceComposerClient.cpp | 15 +++++---------- libs/gui/include/gui/SurfaceComposerClient.h | 6 ++++-- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 751721e2a8..a9d6b0ec4b 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -632,7 +632,7 @@ SurfaceComposerClient::Transaction::Transaction(const Transaction& other) mAnimation(other.mAnimation), mEarlyWakeupStart(other.mEarlyWakeupStart), mEarlyWakeupEnd(other.mEarlyWakeupEnd), - mContainsBuffer(other.mContainsBuffer), + mMayContainBuffer(other.mMayContainBuffer), mDesiredPresentTime(other.mDesiredPresentTime), mIsAutoTimestamp(other.mIsAutoTimestamp), mFrameTimelineInfo(other.mFrameTimelineInfo), @@ -667,7 +667,6 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel const bool animation = parcel->readBool(); const bool earlyWakeupStart = parcel->readBool(); const bool earlyWakeupEnd = parcel->readBool(); - const bool containsBuffer = parcel->readBool(); const int64_t desiredPresentTime = parcel->readInt64(); const bool isAutoTimestamp = parcel->readBool(); FrameTimelineInfo frameTimelineInfo; @@ -745,7 +744,6 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel mAnimation = animation; mEarlyWakeupStart = earlyWakeupStart; mEarlyWakeupEnd = earlyWakeupEnd; - mContainsBuffer = containsBuffer; mDesiredPresentTime = desiredPresentTime; mIsAutoTimestamp = isAutoTimestamp; mFrameTimelineInfo = frameTimelineInfo; @@ -777,7 +775,6 @@ status_t SurfaceComposerClient::Transaction::writeToParcel(Parcel* parcel) const parcel->writeBool(mAnimation); parcel->writeBool(mEarlyWakeupStart); parcel->writeBool(mEarlyWakeupEnd); - parcel->writeBool(mContainsBuffer); parcel->writeInt64(mDesiredPresentTime); parcel->writeBool(mIsAutoTimestamp); mFrameTimelineInfo.writeToParcel(parcel); @@ -876,7 +873,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::merge(Tr mInputWindowCommands.merge(other.mInputWindowCommands); - mContainsBuffer |= other.mContainsBuffer; + mMayContainBuffer |= other.mMayContainBuffer; mEarlyWakeupStart = mEarlyWakeupStart || other.mEarlyWakeupStart; mEarlyWakeupEnd = mEarlyWakeupEnd || other.mEarlyWakeupEnd; mApplyToken = other.mApplyToken; @@ -892,7 +889,7 @@ void SurfaceComposerClient::Transaction::clear() { mDisplayStates.clear(); mListenerCallbacks.clear(); mInputWindowCommands.clear(); - mContainsBuffer = false; + mMayContainBuffer = false; mForceSynchronous = 0; mTransactionNestCount = 0; mAnimation = false; @@ -920,7 +917,7 @@ void SurfaceComposerClient::doUncacheBufferTransaction(uint64_t cacheId) { } void SurfaceComposerClient::Transaction::cacheBuffers() { - if (!mContainsBuffer) { + if (!mMayContainBuffer) { return; } @@ -1464,7 +1461,6 @@ std::shared_ptr SurfaceComposerClient::Transaction::getAndClearBuffe s->what &= ~layer_state_t::eBufferChanged; s->bufferData = nullptr; - mContainsBuffer = false; return bufferData; } @@ -1495,7 +1491,6 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffe if (buffer == nullptr) { s->what &= ~layer_state_t::eBufferChanged; s->bufferData = nullptr; - mContainsBuffer = false; return *this; } @@ -1530,7 +1525,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffe const std::vector&) {}, nullptr); - mContainsBuffer = true; + mMayContainBuffer = true; return *this; } diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 8c47ebc3b8..963cc09ca9 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -409,8 +409,10 @@ public: bool mEarlyWakeupStart = false; bool mEarlyWakeupEnd = false; - // Indicates that the Transaction contains a buffer that should be cached - bool mContainsBuffer = false; + // 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 + // hint that at some point a buffer was added to this transaction before apply was called. + bool mMayContainBuffer = false; // mDesiredPresentTime is the time in nanoseconds that the client would like the transaction // to be presented. When it is not possible to present at exactly that time, it will be -- GitLab From 15beb5171f87731367fb972630f7f3c5cbc2207a Mon Sep 17 00:00:00 2001 From: Antonio Kantek Date: Mon, 13 Jun 2022 22:35:41 +0000 Subject: [PATCH 0295/1310] (touch-mode-md 2/n) Add md touch mode support in InputDispatcher Major changes in this CL: * Drop kPerDisplayTouchModeEnabled (keep the native layer simple); * Include display id in TouchModeEntry; * Cache touch mode state per display id. Bug: 198499018 Test: atest inputflinger_tests Change-Id: Idc800e08bfca1c19342ad34865bd6c9e5dc6a7d3 --- .../benchmarks/InputDispatcher_benchmarks.cpp | 2 - services/inputflinger/dispatcher/Entry.cpp | 5 +- services/inputflinger/dispatcher/Entry.h | 3 +- .../dispatcher/InputDispatcher.cpp | 50 ++++++++------- .../inputflinger/dispatcher/InputDispatcher.h | 11 ++-- .../include/InputDispatcherPolicyInterface.h | 3 - .../tests/InputDispatcher_test.cpp | 61 ++++++++++++++----- 7 files changed, 86 insertions(+), 49 deletions(-) diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp index 12eeea60cb..3d4dd7eabb 100644 --- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp +++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp @@ -112,8 +112,6 @@ private: void notifyDropWindow(const sp&, float x, float y) override {} - bool isPerDisplayTouchModeEnabled() override { return false; } - InputDispatcherConfiguration mConfig; }; diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp index 8046bbe25c..33e7e17ec0 100644 --- a/services/inputflinger/dispatcher/Entry.cpp +++ b/services/inputflinger/dispatcher/Entry.cpp @@ -195,9 +195,10 @@ void KeyEntry::recycle() { // --- TouchModeEntry --- -TouchModeEntry::TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode) +TouchModeEntry::TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode, int displayId) : EventEntry(id, Type::TOUCH_MODE_CHANGED, eventTime, POLICY_FLAG_PASS_TO_USER), - inTouchMode(inTouchMode) {} + inTouchMode(inTouchMode), + displayId(displayId) {} TouchModeEntry::~TouchModeEntry() {} diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h index 1f916f39e4..60f319a056 100644 --- a/services/inputflinger/dispatcher/Entry.h +++ b/services/inputflinger/dispatcher/Entry.h @@ -210,8 +210,9 @@ struct SensorEntry : EventEntry { struct TouchModeEntry : EventEntry { bool inTouchMode; + int32_t displayId; - TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode); + TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode, int32_t displayId); std::string getDescription() const override; ~TouchModeEntry() override; diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index ff63a6f05f..399153c0d7 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -544,17 +544,12 @@ InputDispatcher::InputDispatcher(const sp& polic mDispatchEnabled(false), mDispatchFrozen(false), mInputFilterEnabled(false), - // mInTouchMode will be initialized by the WindowManager to the default device config. - // To avoid leaking stack in case that call never comes, and for tests, - // initialize it here anyways. - mInTouchMode(kDefaultInTouchMode), mMaximumObscuringOpacityForTouch(1.0f), mFocusedDisplayId(ADISPLAY_ID_DEFAULT), mWindowTokenWithPointerCapture(nullptr), mStaleEventTimeout(staleEventTimeout), mLatencyAggregator(), - mLatencyTracker(&mLatencyAggregator), - kPerDisplayTouchModeEnabled(mPolicy->isPerDisplayTouchModeEnabled()) { + mLatencyTracker(&mLatencyAggregator) { mLooper = sp::make(false); mReporter = createInputReporter(); @@ -562,7 +557,6 @@ InputDispatcher::InputDispatcher(const sp& polic SurfaceComposerClient::getDefault()->addWindowInfosListener(mWindowInfoListener); mKeyRepeatState.lastKeyEntry = nullptr; - policy->getDispatcherConfiguration(&mConfig); } @@ -1435,7 +1429,7 @@ void InputDispatcher::dispatchPointerCaptureChangedLocked( void InputDispatcher::dispatchTouchModeChangeLocked(nsecs_t currentTime, const std::shared_ptr& entry) { const std::vector>& windowHandles = - getWindowHandlesLocked(mFocusedDisplayId); + getWindowHandlesLocked(entry->displayId); if (windowHandles.empty()) { return; } @@ -1452,7 +1446,6 @@ std::vector InputDispatcher::getInputTargetsFromWindowHandlesLocked const std::vector>& windowHandles) const { std::vector inputTargets; for (const sp& handle : windowHandles) { - // TODO(b/193718270): Due to performance concerns, consider notifying visible windows only. const sp& token = handle->getToken(); if (token == nullptr) { continue; @@ -5012,15 +5005,20 @@ bool InputDispatcher::setInTouchMode(bool inTouchMode, int32_t pid, int32_t uid, bool needWake = false; { std::scoped_lock lock(mLock); - if (mInTouchMode == inTouchMode) { + ALOGD_IF(DEBUG_TOUCH_MODE, + "Request to change touch mode to %s (calling pid=%d, uid=%d, " + "hasPermission=%s, target displayId=%d, mTouchModePerDisplay[displayId]=%s)", + toString(inTouchMode), pid, uid, toString(hasPermission), displayId, + mTouchModePerDisplay.count(displayId) == 0 + ? "not set" + : std::to_string(mTouchModePerDisplay[displayId]).c_str()); + + // TODO(b/198499018): Ensure that WM can guarantee that touch mode is properly set when + // display is created. + auto touchModeIt = mTouchModePerDisplay.find(displayId); + if (touchModeIt != mTouchModePerDisplay.end() && touchModeIt->second == inTouchMode) { return false; } - if (DEBUG_TOUCH_MODE) { - ALOGD("Request to change touch mode from %s to %s (calling pid=%d, uid=%d, " - "hasPermission=%s, target displayId=%d, perDisplayTouchModeEnabled=%s)", - toString(mInTouchMode), toString(inTouchMode), pid, uid, toString(hasPermission), - displayId, toString(kPerDisplayTouchModeEnabled)); - } if (!hasPermission) { if (!focusedWindowIsOwnedByLocked(pid, uid) && !recentWindowsAreOwnedByLocked(pid, uid)) { @@ -5030,11 +5028,9 @@ bool InputDispatcher::setInTouchMode(bool inTouchMode, int32_t pid, int32_t uid, return false; } } - - // TODO(b/198499018): Store touch mode per display (kPerDisplayTouchModeEnabled) - mInTouchMode = inTouchMode; - - auto entry = std::make_unique(mIdGenerator.nextId(), now(), inTouchMode); + mTouchModePerDisplay[displayId] = inTouchMode; + auto entry = std::make_unique(mIdGenerator.nextId(), now(), inTouchMode, + displayId); needWake = enqueueInboundEventLocked(std::move(entry)); } // release lock @@ -5474,6 +5470,16 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) { dump += INDENT "AppSwitch: not pending\n"; } + if (!mTouchModePerDisplay.empty()) { + dump += INDENT "TouchModePerDisplay:\n"; + for (const auto& [displayId, touchMode] : mTouchModePerDisplay) { + dump += StringPrintf(INDENT2 "Display: %" PRId32 " TouchMode: %s\n", displayId, + std::to_string(touchMode).c_str()); + } + } else { + dump += INDENT "TouchModePerDisplay: \n"; + } + dump += INDENT "Configuration:\n"; dump += StringPrintf(INDENT2 "KeyRepeatDelay: %" PRId64 "ms\n", ns2ms(mConfig.keyRepeatDelay)); dump += StringPrintf(INDENT2 "KeyRepeatTimeout: %" PRId64 "ms\n", @@ -6366,6 +6372,8 @@ void InputDispatcher::displayRemoved(int32_t displayId) { mFocusResolver.displayRemoved(displayId); // Reset pointer capture eligibility, regardless of previous state. std::erase(mIneligibleDisplaysForPointerCapture, displayId); + // Remove the associated touch mode state. + mTouchModePerDisplay.erase(displayId); } // release lock // Wake up poll loop since it may need to make new input dispatching choices. diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index b5bbce8e8d..f6872d17e2 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -343,9 +343,13 @@ private: bool mDispatchEnabled GUARDED_BY(mLock); bool mDispatchFrozen GUARDED_BY(mLock); bool mInputFilterEnabled GUARDED_BY(mLock); - bool mInTouchMode GUARDED_BY(mLock); float mMaximumObscuringOpacityForTouch GUARDED_BY(mLock); + // This map is not really needed, but it helps a lot with debugging (dumpsys input). + // In the java layer, touch mode states are spread across multiple DisplayContent objects, + // making harder to snapshot and retrieve them. + std::map mTouchModePerDisplay GUARDED_BY(mLock); + class DispatcherWindowListener : public gui::WindowInfosListener { public: explicit DispatcherWindowListener(InputDispatcher& dispatcher) : mDispatcher(dispatcher){}; @@ -384,7 +388,7 @@ private: bool hasResponsiveConnectionLocked(android::gui::WindowInfoHandle& windowHandle) const REQUIRES(mLock); - // Gets all the input targets (with their respective input channels) from the window handles + // Returns all the input targets (with their respective input channels) from the window handles // passed as argument. std::vector getInputTargetsFromWindowHandlesLocked( const std::vector>& windowHandles) const @@ -685,9 +689,6 @@ private: bool focusedWindowIsOwnedByLocked(int32_t pid, int32_t uid) REQUIRES(mLock); bool recentWindowsAreOwnedByLocked(int32_t pid, int32_t uid) REQUIRES(mLock); - // Per display touch mode enabled - const bool kPerDisplayTouchModeEnabled; - sp mReporter; }; diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h index 44f3baf204..7843923a1f 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h @@ -136,9 +136,6 @@ public: /* Notifies the policy that the drag window has moved over to another window */ virtual void notifyDropWindow(const sp& token, float x, float y) = 0; - - /* If touch mode is enabled per display or global */ - virtual bool isPerDisplayTouchModeEnabled() = 0; }; } // namespace android diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 7ee6950b09..985c9c570c 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -63,10 +63,14 @@ static constexpr int32_t POINTER_3_DOWN = static constexpr int32_t POINTER_1_UP = AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); -// The default pid and uid for windows created by the test. +// The default pid and uid for windows created on the primary display by the test. static constexpr int32_t WINDOW_PID = 999; static constexpr int32_t WINDOW_UID = 1001; +// The default pid and uid for the windows created on the secondary display by the test. +static constexpr int32_t SECONDARY_WINDOW_PID = 1010; +static constexpr int32_t SECONDARY_WINDOW_UID = 1012; + // The default policy flags to use for event injection by tests. static constexpr uint32_t DEFAULT_POLICY_FLAGS = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER; @@ -500,11 +504,6 @@ private: verify(*mFilteredEvent); mFilteredEvent = nullptr; } - - bool isPerDisplayTouchModeEnabled() { - // TODO(b/198499018): Make this a regular property once per display touch mode is enabled - return false; - } }; // --- InputDispatcherTest --- @@ -5461,7 +5460,6 @@ class InputDispatcherMultiWindowOcclusionTests : public InputDispatcherTest { sp::make(mApplication, mDispatcher, "Window without input channel", ADISPLAY_ID_DEFAULT, std::make_optional>(nullptr) /*token*/); - mNoInputWindow->setNoInputChannel(true); mNoInputWindow->setFrame(Rect(0, 0, 100, 100)); // It's perfectly valid for this window to not have an associated input channel @@ -6806,44 +6804,67 @@ TEST_F(InputDispatcherDropInputFeatureTest, UnobscuredWindowGetsInput) { class InputDispatcherTouchModeChangedTests : public InputDispatcherTest { protected: std::shared_ptr mApp; + std::shared_ptr mSecondaryApp; sp mWindow; sp mSecondWindow; + sp mThirdWindow; void SetUp() override { InputDispatcherTest::SetUp(); mApp = std::make_shared(); + mSecondaryApp = std::make_shared(); mWindow = sp::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); mWindow->setFocusable(true); setFocusedWindow(mWindow); mSecondWindow = sp::make(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT); mSecondWindow->setFocusable(true); + mThirdWindow = + sp::make(mSecondaryApp, mDispatcher, + "TestWindow3_SecondaryDisplay", SECOND_DISPLAY_ID); + mThirdWindow->setFocusable(true); mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp); - mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mSecondWindow}}}); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mSecondWindow}}, + {SECOND_DISPLAY_ID, {mThirdWindow}}}); + mThirdWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID); mWindow->consumeFocusEvent(true); - // Set initial touch mode to InputDispatcher::kDefaultInTouchMode. + // Set main display initial touch mode to InputDispatcher::kDefaultInTouchMode. if (mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, WINDOW_PID, - WINDOW_UID, true /*hasPermission*/, ADISPLAY_ID_DEFAULT)) { + WINDOW_UID, true /* hasPermission */, + ADISPLAY_ID_DEFAULT)) { mWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode); mSecondWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode); + mThirdWindow->assertNoEvents(); + } + + // Set secondary display initial touch mode to InputDispatcher::kDefaultInTouchMode. + if (mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, SECONDARY_WINDOW_PID, + SECONDARY_WINDOW_UID, true /* hasPermission */, + SECOND_DISPLAY_ID)) { + mWindow->assertNoEvents(); + mSecondWindow->assertNoEvents(); + mThirdWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode); } } - void changeAndVerifyTouchMode(bool inTouchMode, int32_t pid, int32_t uid, bool hasPermission) { + void changeAndVerifyTouchModeInMainDisplayOnly(bool inTouchMode, int32_t pid, int32_t uid, + bool hasPermission) { ASSERT_TRUE(mDispatcher->setInTouchMode(inTouchMode, pid, uid, hasPermission, ADISPLAY_ID_DEFAULT)); mWindow->consumeTouchModeEvent(inTouchMode); mSecondWindow->consumeTouchModeEvent(inTouchMode); + mThirdWindow->assertNoEvents(); } }; TEST_F(InputDispatcherTouchModeChangedTests, FocusedWindowCanChangeTouchMode) { const WindowInfo& windowInfo = *mWindow->getInfo(); - changeAndVerifyTouchMode(!InputDispatcher::kDefaultInTouchMode, windowInfo.ownerPid, - windowInfo.ownerUid, false /*hasPermission*/); + changeAndVerifyTouchModeInMainDisplayOnly(!InputDispatcher::kDefaultInTouchMode, + windowInfo.ownerPid, windowInfo.ownerUid, + false /* hasPermission */); } TEST_F(InputDispatcherTouchModeChangedTests, NonFocusedWindowOwnerCannotChangeTouchMode) { @@ -6863,8 +6884,8 @@ TEST_F(InputDispatcherTouchModeChangedTests, NonWindowOwnerMayChangeTouchModeOnP int32_t ownerPid = windowInfo.ownerPid; int32_t ownerUid = windowInfo.ownerUid; mWindow->setOwnerInfo(/* pid */ -1, /* uid */ -1); - changeAndVerifyTouchMode(!InputDispatcher::kDefaultInTouchMode, ownerPid, ownerUid, - true /*hasPermission*/); + changeAndVerifyTouchModeInMainDisplayOnly(!InputDispatcher::kDefaultInTouchMode, ownerPid, + ownerUid, true /*hasPermission*/); } TEST_F(InputDispatcherTouchModeChangedTests, EventIsNotGeneratedIfNotChangingTouchMode) { @@ -6876,6 +6897,16 @@ TEST_F(InputDispatcherTouchModeChangedTests, EventIsNotGeneratedIfNotChangingTou mSecondWindow->assertNoEvents(); } +TEST_F(InputDispatcherTouchModeChangedTests, ChangeTouchOnSecondaryDisplayOnly) { + const WindowInfo& windowInfo = *mThirdWindow->getInfo(); + ASSERT_TRUE(mDispatcher->setInTouchMode(!InputDispatcher::kDefaultInTouchMode, + windowInfo.ownerPid, windowInfo.ownerUid, + true /*hasPermission*/, SECOND_DISPLAY_ID)); + mWindow->assertNoEvents(); + mSecondWindow->assertNoEvents(); + mThirdWindow->consumeTouchModeEvent(!InputDispatcher::kDefaultInTouchMode); +} + TEST_F(InputDispatcherTouchModeChangedTests, CanChangeTouchModeWhenOwningLastInteractedWindow) { // Interact with the window first. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher, ADISPLAY_ID_DEFAULT)) -- GitLab From d72ba16b34c204f2cf89a9b87c57b297206ec616 Mon Sep 17 00:00:00 2001 From: ramindani Date: Fri, 9 Sep 2022 21:33:40 +0000 Subject: [PATCH 0296/1310] [MD] Return list of scored refresh rates based on overallscore. The sorted list returns the refresh rates in the descending order of priority and their overallscore. This overall score will be used and called for multiple displays and will be used to make the final selection in the DisplayModeController for all the displays. BUG: 240743471 Test: atest libsurfaceflinger_unittest Change-Id: I8355425e89452f5ce73858a6173c53be9f3753ef --- .../Scheduler/RefreshRateConfigs.cpp | 222 +++++++----- .../Scheduler/RefreshRateConfigs.h | 40 ++- .../surfaceflinger/Scheduler/Scheduler.cpp | 11 +- services/surfaceflinger/Scheduler/Scheduler.h | 6 +- .../surfaceflinger_scheduler_fuzzer.cpp | 2 +- .../unittests/RefreshRateConfigsTest.cpp | 334 ++++++++++++++++-- 6 files changed, 468 insertions(+), 147 deletions(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp index 00886f030e..3cb052c4e3 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp @@ -48,24 +48,6 @@ struct RefreshRateScore { } fixedRateBelowThresholdLayersScore; }; -template -const DisplayModePtr& getMaxScoreRefreshRate(Iterator begin, Iterator end) { - const auto it = - std::max_element(begin, end, [](RefreshRateScore max, RefreshRateScore current) { - const auto& [modeIt, overallScore, _] = current; - - std::string name = to_string(modeIt->second->getFps()); - ALOGV("%s scores %.2f", name.c_str(), overallScore); - - ATRACE_INT(name.c_str(), static_cast(std::round(overallScore * 100))); - - constexpr float kEpsilon = 0.0001f; - return overallScore > max.overallScore * (1 + kEpsilon); - }); - - return it->modeIt->second; -} - constexpr RefreshRateConfigs::GlobalSignals kNoSignals; std::string formatLayerInfo(const RefreshRateConfigs::LayerRequirement& layer, float weight) { @@ -137,6 +119,34 @@ bool canModesSupportFrameRateOverride(const std::vector& so } // namespace +struct RefreshRateConfigs::RefreshRateScoreComparator { + bool operator()(const RefreshRateScore& lhs, const RefreshRateScore& rhs) const { + const auto& [modeIt, overallScore, _] = lhs; + + std::string name = to_string(modeIt->second->getFps()); + ALOGV("%s sorting scores %.2f", name.c_str(), overallScore); + + ATRACE_INT(name.c_str(), static_cast(std::round(overallScore * 100))); + + constexpr float kEpsilon = 0.0001f; + if (std::abs(overallScore - rhs.overallScore) > kEpsilon) { + return overallScore > rhs.overallScore; + } + + // If overallScore tie we will pick the higher refresh rate if + // high refresh rate is the priority else the lower refresh rate. + if (refreshRateOrder == RefreshRateOrder::Descending) { + using fps_approx_ops::operator>; + return modeIt->second->getFps() > rhs.modeIt->second->getFps(); + } else { + using fps_approx_ops::operator<; + return modeIt->second->getFps() < rhs.modeIt->second->getFps(); + } + } + + const RefreshRateOrder refreshRateOrder; +}; + std::string RefreshRateConfigs::Policy::toString() const { return base::StringPrintf("{defaultModeId=%d, allowGroupSwitching=%s" ", primaryRange=%s, appRequestRange=%s}", @@ -218,6 +228,13 @@ float RefreshRateConfigs::calculateNonExactMatchingLayerScoreLocked(const LayerR return 0; } +float RefreshRateConfigs::calculateRefreshRateScoreForFps(Fps refreshRate) const { + const float ratio = + refreshRate.getValue() / mAppRequestRefreshRates.back()->second->getFps().getValue(); + // Use ratio^2 to get a lower score the more we get further from peak + return ratio * ratio; +} + float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& layer, Fps refreshRate, bool isSeamlessSwitch) const { // Slightly prefer seamless switches. @@ -226,10 +243,7 @@ float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& laye // If the layer wants Max, give higher score to the higher refresh rate if (layer.vote == LayerVoteType::Max) { - const auto& maxRefreshRate = mAppRequestRefreshRates.back()->second; - const auto ratio = refreshRate.getValue() / maxRefreshRate->getFps().getValue(); - // use ratio^2 to get a lower score the more we get further from peak - return ratio * ratio; + return calculateRefreshRateScoreForFps(refreshRate); } if (layer.vote == LayerVoteType::ExplicitExact) { @@ -258,24 +272,24 @@ float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& laye kNonExactMatchingPenalty; } -auto RefreshRateConfigs::getBestRefreshRate(const std::vector& layers, - GlobalSignals signals) const - -> std::pair { +auto RefreshRateConfigs::getRankedRefreshRates(const std::vector& layers, + GlobalSignals signals) const + -> std::pair, GlobalSignals> { std::lock_guard lock(mLock); - if (mGetBestRefreshRateCache && - mGetBestRefreshRateCache->arguments == std::make_pair(layers, signals)) { - return mGetBestRefreshRateCache->result; + if (mGetRankedRefreshRatesCache && + mGetRankedRefreshRatesCache->arguments == std::make_pair(layers, signals)) { + return mGetRankedRefreshRatesCache->result; } - const auto result = getBestRefreshRateLocked(layers, signals); - mGetBestRefreshRateCache = GetBestRefreshRateCache{{layers, signals}, result}; + const auto result = getRankedRefreshRatesLocked(layers, signals); + mGetRankedRefreshRatesCache = GetRankedRefreshRatesCache{{layers, signals}, result}; return result; } -auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vector& layers, - GlobalSignals signals) const - -> std::pair { +auto RefreshRateConfigs::getRankedRefreshRatesLocked(const std::vector& layers, + GlobalSignals signals) const + -> std::pair, GlobalSignals> { using namespace fps_approx_ops; ATRACE_CALL(); ALOGV("%s: %zu layers", __func__, layers.size()); @@ -285,8 +299,8 @@ auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vectorgetFps()).c_str()); - return {max, GlobalSignals{.touch = true}}; + ALOGV("Touch Boost"); + return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending), + GlobalSignals{.touch = true}}; } // If the primary range consists of a single refresh rate then we can only @@ -360,22 +370,22 @@ auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vectorprimaryRange.min, policy->primaryRange.max); if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) { - const DisplayModePtr& min = getMinRefreshRateByPolicyLocked(); - ALOGV("Idle - choose %s", to_string(min->getFps()).c_str()); - return {min, GlobalSignals{.idle = true}}; + ALOGV("Idle"); + return {getRefreshRatesByPolicyLocked(activeMode.getGroup(), RefreshRateOrder::Ascending), + GlobalSignals{.idle = true}}; } if (layers.empty() || noVoteLayers == layers.size()) { - const DisplayModePtr& max = getMaxRefreshRateByPolicyLocked(anchorGroup); - ALOGV("no layers with votes - choose %s", to_string(max->getFps()).c_str()); - return {max, kNoSignals}; + ALOGV("No layers with votes"); + return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending), + kNoSignals}; } // Only if all layers want Min we should return Min if (noVoteLayers + minVoteLayers == layers.size()) { - const DisplayModePtr& min = getMinRefreshRateByPolicyLocked(); - ALOGV("all layers Min - choose %s", to_string(min->getFps()).c_str()); - return {min, kNoSignals}; + ALOGV("All layers Min"); + return {getRefreshRatesByPolicyLocked(activeMode.getGroup(), RefreshRateOrder::Ascending), + kNoSignals}; } // Find the best refresh rate based on score @@ -522,22 +532,29 @@ auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vector 0 - ? getMaxScoreRefreshRate(scores.rbegin(), scores.rend()) - : getMaxScoreRefreshRate(scores.begin(), scores.end()); + // overallScore. Sort the scores based on their overallScore in descending order of priority. + const RefreshRateOrder refreshRateOrder = + maxVoteLayers > 0 ? RefreshRateOrder::Descending : RefreshRateOrder::Ascending; + std::sort(scores.begin(), scores.end(), + RefreshRateScoreComparator{.refreshRateOrder = refreshRateOrder}); + std::vector rankedRefreshRates; + rankedRefreshRates.reserve(scores.size()); + + std::transform(scores.begin(), scores.end(), back_inserter(rankedRefreshRates), + [](const RefreshRateScore& score) { + return RefreshRateRanking{score.modeIt->second, score.overallScore}; + }); if (primaryRangeIsSingleRate) { // If we never scored any layers, then choose the rate from the primary // range instead of picking a random score from the app range. if (std::all_of(scores.begin(), scores.end(), [](RefreshRateScore score) { return score.overallScore == 0; })) { - const DisplayModePtr& max = getMaxRefreshRateByPolicyLocked(anchorGroup); - ALOGV("layers not scored - choose %s", to_string(max->getFps()).c_str()); - return {max, kNoSignals}; + ALOGV("Layers not scored"); + return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending), + kNoSignals}; } else { - return {bestRefreshRate, kNoSignals}; + return {rankedRefreshRates, kNoSignals}; } } @@ -545,8 +562,6 @@ auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vectorgetFps() < touchRefreshRate->getFps()) { - ALOGV("TouchBoost - choose %s", to_string(touchRefreshRate->getFps()).c_str()); - return {touchRefreshRate, GlobalSignals{.touch = true}}; + scores.front().modeIt->second->getFps() < + touchRefreshRates.front().displayModePtr->getFps()) { + ALOGV("Touch Boost"); + return {touchRefreshRates, GlobalSignals{.touch = true}}; } - return {bestRefreshRate, kNoSignals}; + return {rankedRefreshRates, kNoSignals}; } std::unordered_map> @@ -670,11 +688,13 @@ RefreshRateConfigs::UidToFrameRateOverride RefreshRateConfigs::getFrameRateOverr continue; } - // Now that we scored all the refresh rates we need to pick the one that got the highest - // score. + // Now that we scored all the refresh rates we need to pick the lowest refresh rate + // that got the highest score. const DisplayModePtr& bestRefreshRate = - getMaxScoreRefreshRate(scores.begin(), scores.end()); - + std::min_element(scores.begin(), scores.end(), + RefreshRateScoreComparator{.refreshRateOrder = + RefreshRateOrder::Ascending}) + ->modeIt->second; frameRateOverrides.emplace(uid, bestRefreshRate->getFps()); } @@ -715,16 +735,6 @@ const DisplayModePtr& RefreshRateConfigs::getMinRefreshRateByPolicyLocked() cons return mPrimaryRefreshRates.front()->second; } -DisplayModePtr RefreshRateConfigs::getMaxRefreshRateByPolicy() const { - std::lock_guard lock(mLock); - return getMaxRefreshRateByPolicyLocked(); -} - -const DisplayModePtr& RefreshRateConfigs::getMaxRefreshRateByPolicyLocked() const { - const int anchorGroup = getActiveModeItLocked()->second->getGroup(); - return getMaxRefreshRateByPolicyLocked(anchorGroup); -} - const DisplayModePtr& RefreshRateConfigs::getMaxRefreshRateByPolicyLocked(int anchorGroup) const { for (auto it = mPrimaryRefreshRates.rbegin(); it != mPrimaryRefreshRates.rend(); ++it) { const auto& mode = (*it)->second; @@ -733,14 +743,41 @@ const DisplayModePtr& RefreshRateConfigs::getMaxRefreshRateByPolicyLocked(int an } } - const auto& activeMode = *getActiveModeItLocked()->second; - ALOGE("Can't find max refresh rate by policy with the same mode group as the current mode %s", - to_string(activeMode).c_str()); + ALOGE("Can't find max refresh rate by policy with the same group %d", anchorGroup); // Default to the highest refresh rate. return mPrimaryRefreshRates.back()->second; } +std::vector RefreshRateConfigs::getRefreshRatesByPolicyLocked( + std::optional anchorGroupOpt, RefreshRateOrder refreshRateOrder) const { + std::vector rankings; + const auto makeRanking = [&](const DisplayModeIterator it) REQUIRES(mLock) { + const auto& mode = it->second; + const bool inverseScore = (refreshRateOrder == RefreshRateOrder::Ascending); + const float score = calculateRefreshRateScoreForFps(mode->getFps()); + if (!anchorGroupOpt || mode->getGroup() == anchorGroupOpt) { + rankings.push_back(RefreshRateRanking{mode, inverseScore ? 1.0f / score : score}); + } + }; + + if (refreshRateOrder == RefreshRateOrder::Ascending) { + std::for_each(mPrimaryRefreshRates.begin(), mPrimaryRefreshRates.end(), makeRanking); + } else { + std::for_each(mPrimaryRefreshRates.rbegin(), mPrimaryRefreshRates.rend(), makeRanking); + } + + if (!rankings.empty() || !anchorGroupOpt) { + return rankings; + } + + ALOGW("Can't find %s refresh rate by policy with the same mode group" + " as the mode group %d", + refreshRateOrder == RefreshRateOrder::Ascending ? "min" : "max", anchorGroupOpt.value()); + + return getRefreshRatesByPolicyLocked(/*anchorGroupOpt*/ std::nullopt, refreshRateOrder); +} + DisplayModePtr RefreshRateConfigs::getActiveModePtr() const { std::lock_guard lock(mLock); return getActiveModeItLocked()->second; @@ -760,9 +797,9 @@ DisplayModeIterator RefreshRateConfigs::getActiveModeItLocked() const { void RefreshRateConfigs::setActiveModeId(DisplayModeId modeId) { std::lock_guard lock(mLock); - // Invalidate the cached invocation to getBestRefreshRate. This forces - // the refresh rate to be recomputed on the next call to getBestRefreshRate. - mGetBestRefreshRateCache.reset(); + // Invalidate the cached invocation to getRankedRefreshRates. This forces + // the refresh rate to be recomputed on the next call to getRankedRefreshRates. + mGetRankedRefreshRatesCache.reset(); mActiveModeIt = mDisplayModes.find(modeId); LOG_ALWAYS_FATAL_IF(mActiveModeIt == mDisplayModes.end()); @@ -797,9 +834,9 @@ void RefreshRateConfigs::initializeIdleTimer() { void RefreshRateConfigs::updateDisplayModes(DisplayModes modes, DisplayModeId activeModeId) { std::lock_guard lock(mLock); - // Invalidate the cached invocation to getBestRefreshRate. This forces - // the refresh rate to be recomputed on the next call to getBestRefreshRate. - mGetBestRefreshRateCache.reset(); + // Invalidate the cached invocation to getRankedRefreshRates. This forces + // the refresh rate to be recomputed on the next call to getRankedRefreshRates. + mGetRankedRefreshRatesCache.reset(); mDisplayModes = std::move(modes); mActiveModeIt = mDisplayModes.find(activeModeId); @@ -843,7 +880,7 @@ status_t RefreshRateConfigs::setDisplayManagerPolicy(const Policy& policy) { ALOGE("Invalid refresh rate policy: %s", policy.toString().c_str()); return BAD_VALUE; } - mGetBestRefreshRateCache.reset(); + mGetRankedRefreshRatesCache.reset(); Policy previousPolicy = *getCurrentPolicyLocked(); mDisplayManagerPolicy = policy; if (*getCurrentPolicyLocked() == previousPolicy) { @@ -858,7 +895,7 @@ status_t RefreshRateConfigs::setOverridePolicy(const std::optional& poli if (policy && !isPolicyValidLocked(*policy)) { return BAD_VALUE; } - mGetBestRefreshRateCache.reset(); + mGetRankedRefreshRatesCache.reset(); Policy previousPolicy = *getCurrentPolicyLocked(); mOverridePolicy = policy; if (*getCurrentPolicyLocked() == previousPolicy) { @@ -958,7 +995,8 @@ RefreshRateConfigs::KernelIdleTimerAction RefreshRateConfigs::getIdleTimerAction return KernelIdleTimerAction::TurnOff; } - const DisplayModePtr& maxByPolicy = getMaxRefreshRateByPolicyLocked(); + const DisplayModePtr& maxByPolicy = + getMaxRefreshRateByPolicyLocked(getActiveModeItLocked()->second->getGroup()); if (minByPolicy == maxByPolicy) { // Turn on the timer when the min of the primary range is below the device min. if (const Policy* currentPolicy = getCurrentPolicyLocked(); diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h index 19bcb94bf2..0642fcbd14 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h +++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h @@ -44,6 +44,15 @@ inline DisplayModeEvent operator|(DisplayModeEvent lhs, DisplayModeEvent rhs) { return static_cast(static_cast(lhs) | static_cast(rhs)); } +struct RefreshRateRanking { + DisplayModePtr displayModePtr; + float score = 0.0f; + + bool operator==(const RefreshRateRanking& ranking) const { + return displayModePtr == ranking.displayModePtr && score == ranking.score; + } +}; + using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride; /** @@ -195,9 +204,9 @@ public: } }; - // Returns the refresh rate that best fits the given layers, and whether the refresh rate was - // chosen based on touch boost and/or idle timer. - std::pair getBestRefreshRate( + // Returns the list in the descending order of refresh rates desired + // based on their overall score, and the GlobalSignals that were considered. + std::pair, GlobalSignals> getRankedRefreshRates( const std::vector&, GlobalSignals) const EXCLUDES(mLock); FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) { @@ -208,10 +217,6 @@ public: std::optional onKernelTimerChanged(std::optional desiredActiveModeId, bool timerExpired) const EXCLUDES(mLock); - // Returns the highest refresh rate according to the current policy. May change at runtime. Only - // uses the primary range, not the app request range. - DisplayModePtr getMaxRefreshRateByPolicy() const EXCLUDES(mLock); - void setActiveModeId(DisplayModeId) EXCLUDES(mLock) REQUIRES(kMainThreadContext); // See mActiveModeIt for thread safety. @@ -343,7 +348,7 @@ private: // See mActiveModeIt for thread safety. DisplayModeIterator getActiveModeItLocked() const REQUIRES(mLock); - std::pair getBestRefreshRateLocked( + std::pair, GlobalSignals> getRankedRefreshRatesLocked( const std::vector&, GlobalSignals) const REQUIRES(mLock); // Returns number of display frames and remainder when dividing the layer refresh period by @@ -356,12 +361,23 @@ private: // Returns the highest refresh rate according to the current policy. May change at runtime. Only // uses the primary range, not the app request range. - const DisplayModePtr& getMaxRefreshRateByPolicyLocked() const REQUIRES(mLock); const DisplayModePtr& getMaxRefreshRateByPolicyLocked(int anchorGroup) const REQUIRES(mLock); + struct RefreshRateScoreComparator; + + enum class RefreshRateOrder { Ascending, Descending }; + + // Returns the rankings in RefreshRateOrder. May change at runtime. + // Only uses the primary range, not the app request range. + std::vector getRefreshRatesByPolicyLocked(std::optional anchorGroupOpt, + RefreshRateOrder) const + REQUIRES(mLock); + const Policy* getCurrentPolicyLocked() const REQUIRES(mLock); bool isPolicyValidLocked(const Policy& policy) const REQUIRES(mLock); + // Returns the refresh rate score as a ratio to max refresh rate, which has a score of 1. + float calculateRefreshRateScoreForFps(Fps refreshRate) const REQUIRES(mLock); // calculates a score for a layer. Used to determine the display refresh rate // and the frame rate override for certains applications. float calculateLayerScoreLocked(const LayerRequirement&, Fps refreshRate, @@ -410,11 +426,11 @@ private: const Config mConfig; bool mSupportsFrameRateOverrideByContent; - struct GetBestRefreshRateCache { + struct GetRankedRefreshRatesCache { std::pair, GlobalSignals> arguments; - std::pair result; + std::pair, GlobalSignals> result; }; - mutable std::optional mGetBestRefreshRateCache GUARDED_BY(mLock); + mutable std::optional mGetRankedRefreshRatesCache GUARDED_BY(mLock); // Declare mIdleTimer last to ensure its thread joins before the mutex/callbacks are destroyed. std::mutex mIdleTimerCallbacksMutex; diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index ff6b461449..6d68bac8e2 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -674,7 +674,9 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals if (currentState == newState) return {}; currentState = std::forward(newState); - std::tie(newMode, consideredSignals) = chooseDisplayMode(); + const auto [rankings, signals] = getRankedDisplayModes(); + newMode = rankings.front().displayModePtr; + consideredSignals = signals; frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, newMode->getFps()); if (mPolicy.mode == newMode) { @@ -699,7 +701,8 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals return consideredSignals; } -auto Scheduler::chooseDisplayMode() -> std::pair { +auto Scheduler::getRankedDisplayModes() + -> std::pair, GlobalSignals> { ATRACE_CALL(); const auto configs = holdRefreshRateConfigs(); @@ -712,14 +715,14 @@ auto Scheduler::chooseDisplayMode() -> std::pair .idle = mPolicy.idleTimer == TimerState::Expired, .powerOnImminent = powerOnImminent}; - return configs->getBestRefreshRate(mPolicy.contentRequirements, signals); + return configs->getRankedRefreshRates(mPolicy.contentRequirements, signals); } DisplayModePtr Scheduler::getPreferredDisplayMode() { std::lock_guard lock(mPolicyLock); // Make sure the stored mode is up to date. if (mPolicy.mode) { - mPolicy.mode = chooseDisplayMode().first; + mPolicy.mode = getRankedDisplayModes().first.front().displayModePtr; } return mPolicy.mode; } diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index afb34590e3..9f3f4d9337 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -269,8 +269,10 @@ private: template GlobalSignals applyPolicy(S Policy::*, T&&) EXCLUDES(mPolicyLock); - // Returns the display mode that fulfills the policy, and the signals that were considered. - std::pair chooseDisplayMode() REQUIRES(mPolicyLock); + // Returns the list of display modes in descending order of their priority that fulfills the + // policy, and the signals that were considered. + std::pair, GlobalSignals> getRankedDisplayModes() + REQUIRES(mPolicyLock); bool updateFrameRateOverrides(GlobalSignals, Fps displayRefreshRate) REQUIRES(mPolicyLock); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index 3fc2b7e233..21405883e2 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -350,7 +350,7 @@ void SchedulerFuzzer::fuzzRefreshRateConfigs() { const RefreshRateConfigs::GlobalSignals globalSignals = {.touch = false, .idle = false}; std::vector layers = {{.weight = mFdp.ConsumeFloatingPoint()}}; - refreshRateConfigs.getBestRefreshRate(layers, globalSignals); + refreshRateConfigs.getRankedRefreshRates(layers, globalSignals); layers[0].name = mFdp.ConsumeRandomLengthString(kRandomStringLength); layers[0].ownerUid = mFdp.ConsumeIntegral(); diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp index 5d9b2a837d..a706c4b33b 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp @@ -41,6 +41,7 @@ using mock::createDisplayMode; struct TestableRefreshRateConfigs : RefreshRateConfigs { using RefreshRateConfigs::RefreshRateConfigs; + using RefreshRateConfigs::RefreshRateOrder; void setActiveModeId(DisplayModeId modeId) { ftl::FakeGuard guard(kMainThreadContext); @@ -67,19 +68,30 @@ struct TestableRefreshRateConfigs : RefreshRateConfigs { return getMinRefreshRateByPolicyLocked(); } + DisplayModePtr getMaxRefreshRateByPolicy() const { + std::lock_guard lock(mLock); + return getMaxRefreshRateByPolicyLocked(getActiveModeItLocked()->second->getGroup()); + } + + std::vector getRefreshRatesByPolicy( + std::optional anchorGroupOpt, RefreshRateOrder refreshRateOrder) const { + std::lock_guard lock(mLock); + return RefreshRateConfigs::getRefreshRatesByPolicyLocked(anchorGroupOpt, refreshRateOrder); + } + const std::vector& knownFrameRates() const { return mKnownFrameRates; } - using RefreshRateConfigs::GetBestRefreshRateCache; - auto& mutableGetBestRefreshRateCache() { return mGetBestRefreshRateCache; } + using RefreshRateConfigs::GetRankedRefreshRatesCache; + auto& mutableGetRankedRefreshRatesCache() { return mGetRankedRefreshRatesCache; } - auto getBestRefreshRateAndSignals(const std::vector& layers, - GlobalSignals signals) const { - return RefreshRateConfigs::getBestRefreshRate(layers, signals); + auto getRankedRefreshRatesAndSignals(const std::vector& layers, + GlobalSignals signals) const { + return RefreshRateConfigs::getRankedRefreshRates(layers, signals); } DisplayModePtr getBestRefreshRate(const std::vector& layers = {}, GlobalSignals signals = {}) const { - return getBestRefreshRateAndSignals(layers, signals).first; + return getRankedRefreshRatesAndSignals(layers, signals).first.front().displayModePtr; } }; @@ -977,16 +989,116 @@ TEST_F(RefreshRateConfigsTest, scrollWhileWatching60fps_60_90) { EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); } +TEST_F(RefreshRateConfigsTest, getMaxRefreshRatesByPolicy) { + // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the + // different group. + TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60); + const std::vector& expectedRefreshRates = {RefreshRateRanking{kMode90}, + RefreshRateRanking{kMode60}, + RefreshRateRanking{kMode30}}; + + const std::vector& refreshRates = + configs.getRefreshRatesByPolicy(configs.getActiveMode().getGroup(), + TestableRefreshRateConfigs::RefreshRateOrder:: + Descending); + + ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { + EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr) + << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue() + << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue(); + } +} + +TEST_F(RefreshRateConfigsTest, getMinRefreshRatesByPolicy) { + // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the + // different group. + TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60); + const std::vector& expectedRefreshRates = {RefreshRateRanking{kMode30}, + RefreshRateRanking{kMode60}, + RefreshRateRanking{kMode90}}; + + const std::vector& refreshRates = + configs.getRefreshRatesByPolicy(configs.getActiveMode().getGroup(), + TestableRefreshRateConfigs::RefreshRateOrder:: + Ascending); + + ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { + EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr) + << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue() + << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue(); + } +} + +TEST_F(RefreshRateConfigsTest, getMinRefreshRatesByPolicyOutsideTheGroup) { + // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the + // different group. + TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId72); + const std::vector& expectedRefreshRates = {RefreshRateRanking{kMode30}, + RefreshRateRanking{kMode60}, + RefreshRateRanking{kMode90}}; + + EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}}), 0); + + const std::vector& refreshRates = + configs.getRefreshRatesByPolicy(/*anchorGroupOpt*/ std::nullopt, + TestableRefreshRateConfigs::RefreshRateOrder:: + Ascending); + + ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { + EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr) + << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue() + << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue(); + } +} + +TEST_F(RefreshRateConfigsTest, getMaxRefreshRatesByPolicyOutsideTheGroup) { + // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the + // different group. + TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId72); + const std::vector& expectedRefreshRates = {RefreshRateRanking{kMode90}, + RefreshRateRanking{kMode60}, + RefreshRateRanking{kMode30}}; + + EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}}), 0); + + const std::vector& refreshRates = + configs.getRefreshRatesByPolicy(/*anchorGroupOpt*/ std::nullopt, + TestableRefreshRateConfigs::RefreshRateOrder:: + Descending); + + ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { + EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr) + << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue() + << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue(); + } +} + TEST_F(RefreshRateConfigsTest, powerOnImminentConsidered) { RefreshRateConfigs configs(kModes_60_90, kModeId60); + std::vector expectedRefreshRates = {RefreshRateRanking{kMode90}, + RefreshRateRanking{kMode60}}; - auto [refreshRate, signals] = configs.getBestRefreshRate({}, {}); + auto [refreshRates, signals] = configs.getRankedRefreshRates({}, {}); EXPECT_FALSE(signals.powerOnImminent); - EXPECT_EQ(kMode90, refreshRate); + ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { + EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr) + << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue() + << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue(); + } - std::tie(refreshRate, signals) = configs.getBestRefreshRate({}, {.powerOnImminent = true}); + std::tie(refreshRates, signals) = configs.getRankedRefreshRates({}, {.powerOnImminent = true}); EXPECT_TRUE(signals.powerOnImminent); - EXPECT_EQ(kMode90, refreshRate); + ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { + EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr) + << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue() + << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue(); + } std::vector layers = {{.weight = 1.f}}; auto& lr1 = layers[0]; @@ -994,22 +1106,35 @@ TEST_F(RefreshRateConfigsTest, powerOnImminentConsidered) { lr1.desiredRefreshRate = 60_Hz; lr1.name = "60Hz ExplicitExactOrMultiple"; - std::tie(refreshRate, signals) = configs.getBestRefreshRate(layers, {.powerOnImminent = false}); - EXPECT_FALSE(signals.powerOnImminent); - EXPECT_EQ(kMode60, refreshRate); - - std::tie(refreshRate, signals) = configs.getBestRefreshRate(layers, {.powerOnImminent = true}); + std::tie(refreshRates, signals) = + configs.getRankedRefreshRates(layers, {.powerOnImminent = true}); EXPECT_TRUE(signals.powerOnImminent); - EXPECT_EQ(kMode90, refreshRate); + ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { + EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr) + << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue() + << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue(); + } + + expectedRefreshRates = {RefreshRateRanking{kMode60}, RefreshRateRanking{kMode90}}; + std::tie(refreshRates, signals) = + configs.getRankedRefreshRates(layers, {.powerOnImminent = false}); + EXPECT_FALSE(signals.powerOnImminent); + ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { + EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr) + << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue() + << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue(); + } } TEST_F(RefreshRateConfigsTest, touchConsidered) { RefreshRateConfigs configs(kModes_60_90, kModeId60); - auto [_, signals] = configs.getBestRefreshRate({}, {}); + auto [_, signals] = configs.getRankedRefreshRates({}, {}); EXPECT_FALSE(signals.touch); - std::tie(std::ignore, signals) = configs.getBestRefreshRate({}, {.touch = true}); + std::tie(std::ignore, signals) = configs.getRankedRefreshRates({}, {.touch = true}); EXPECT_TRUE(signals.touch); std::vector layers = {{.weight = 1.f}, {.weight = 1.f}}; @@ -1022,16 +1147,16 @@ TEST_F(RefreshRateConfigsTest, touchConsidered) { lr2.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 60_Hz; lr2.name = "60Hz Heuristic"; - std::tie(std::ignore, signals) = configs.getBestRefreshRate(layers, {.touch = true}); + std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true}); EXPECT_TRUE(signals.touch); lr1.vote = LayerVoteType::ExplicitDefault; lr1.desiredRefreshRate = 60_Hz; - lr1.name = "60Hz ExplicitExactOrMultiple"; + lr1.name = "60Hz ExplicitDefault"; lr2.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 60_Hz; lr2.name = "60Hz Heuristic"; - std::tie(std::ignore, signals) = configs.getBestRefreshRate(layers, {.touch = true}); + std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true}); EXPECT_FALSE(signals.touch); lr1.vote = LayerVoteType::ExplicitExactOrMultiple; @@ -1040,16 +1165,16 @@ TEST_F(RefreshRateConfigsTest, touchConsidered) { lr2.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 60_Hz; lr2.name = "60Hz Heuristic"; - std::tie(std::ignore, signals) = configs.getBestRefreshRate(layers, {.touch = true}); + std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true}); EXPECT_TRUE(signals.touch); lr1.vote = LayerVoteType::ExplicitDefault; lr1.desiredRefreshRate = 60_Hz; - lr1.name = "60Hz ExplicitExactOrMultiple"; + lr1.name = "60Hz ExplicitDefault"; lr2.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 60_Hz; lr2.name = "60Hz Heuristic"; - std::tie(std::ignore, signals) = configs.getBestRefreshRate(layers, {.touch = true}); + std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true}); EXPECT_FALSE(signals.touch); } @@ -1187,9 +1312,10 @@ TEST_F(RefreshRateConfigsTest, lr.name = "60Hz ExplicitDefault"; lr.focused = true; - const auto [mode, signals] = configs.getBestRefreshRate(layers, {.touch = true, .idle = true}); + const auto [mode, signals] = + configs.getRankedRefreshRates(layers, {.touch = true, .idle = true}); - EXPECT_EQ(mode, kMode60); + EXPECT_EQ(mode.begin()->displayModePtr, kMode60); EXPECT_FALSE(signals.touch); } @@ -1209,14 +1335,147 @@ TEST_F(RefreshRateConfigsTest, EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.idle = true})); } +TEST_F(RefreshRateConfigsTest, testDisplayModeOrdering) { + TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60); + + std::vector layers = {{.weight = 1.f}, + {.weight = 1.f}, + {.weight = 1.f}, + {.weight = 1.f}, + {.weight = 1.f}}; + auto& lr1 = layers[0]; + auto& lr2 = layers[1]; + auto& lr3 = layers[2]; + auto& lr4 = layers[3]; + auto& lr5 = layers[4]; + + lr1.desiredRefreshRate = 90_Hz; + lr1.name = "90Hz"; + lr1.focused = true; + + lr2.desiredRefreshRate = 60_Hz; + lr2.name = "60Hz"; + lr2.focused = true; + + lr3.desiredRefreshRate = 72_Hz; + lr3.name = "72Hz"; + lr3.focused = true; + + lr4.desiredRefreshRate = 120_Hz; + lr4.name = "120Hz"; + lr4.focused = true; + + lr5.desiredRefreshRate = 30_Hz; + lr5.name = "30Hz"; + lr5.focused = true; + + std::vector expectedRankings = { + RefreshRateRanking{kMode120}, RefreshRateRanking{kMode90}, RefreshRateRanking{kMode72}, + RefreshRateRanking{kMode60}, RefreshRateRanking{kMode30}, + }; + + std::vector actualOrder = + configs.getRankedRefreshRatesAndSignals(layers, {}).first; + ASSERT_EQ(expectedRankings.size(), actualOrder.size()); + for (size_t i = 0; i < expectedRankings.size(); ++i) { + EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr) + << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue() + << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue(); + } + + lr1.vote = LayerVoteType::Max; + lr1.name = "Max"; + + lr2.desiredRefreshRate = 60_Hz; + lr2.name = "60Hz"; + + lr3.desiredRefreshRate = 72_Hz; + lr3.name = "72Hz"; + + lr4.desiredRefreshRate = 90_Hz; + lr4.name = "90Hz"; + + lr5.desiredRefreshRate = 120_Hz; + lr5.name = "120Hz"; + + expectedRankings = { + RefreshRateRanking{kMode120}, RefreshRateRanking{kMode90}, RefreshRateRanking{kMode72}, + RefreshRateRanking{kMode60}, RefreshRateRanking{kMode30}, + }; + + actualOrder = configs.getRankedRefreshRatesAndSignals(layers, {}).first; + + ASSERT_EQ(expectedRankings.size(), actualOrder.size()); + for (size_t i = 0; i < expectedRankings.size(); ++i) { + EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr) + << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue() + << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue(); + } + + lr1.vote = LayerVoteType::Heuristic; + lr1.desiredRefreshRate = 30_Hz; + lr1.name = "30Hz"; + + lr2.desiredRefreshRate = 120_Hz; + lr2.name = "120Hz"; + + lr3.desiredRefreshRate = 60_Hz; + lr3.name = "60Hz"; + + lr5.desiredRefreshRate = 72_Hz; + lr5.name = "72Hz"; + + expectedRankings = { + RefreshRateRanking{kMode30}, RefreshRateRanking{kMode60}, RefreshRateRanking{kMode90}, + RefreshRateRanking{kMode120}, RefreshRateRanking{kMode72}, + }; + + actualOrder = configs.getRankedRefreshRatesAndSignals(layers, {}).first; + ASSERT_EQ(expectedRankings.size(), actualOrder.size()); + for (size_t i = 0; i < expectedRankings.size(); ++i) { + EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr) + << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue() + << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue(); + } + + lr1.desiredRefreshRate = 120_Hz; + lr1.name = "120Hz"; + lr1.weight = 0.0f; + + lr2.desiredRefreshRate = 60_Hz; + lr2.name = "60Hz"; + lr2.vote = LayerVoteType::NoVote; + + lr3.name = "60Hz-2"; + lr3.vote = LayerVoteType::Heuristic; + + lr4.vote = LayerVoteType::ExplicitExact; + + lr5.desiredRefreshRate = 120_Hz; + lr5.name = "120Hz-2"; + + expectedRankings = { + RefreshRateRanking{kMode90}, RefreshRateRanking{kMode60}, RefreshRateRanking{kMode120}, + RefreshRateRanking{kMode72}, RefreshRateRanking{kMode30}, + }; + + actualOrder = configs.getRankedRefreshRatesAndSignals(layers, {}).first; + ASSERT_EQ(expectedRankings.size(), actualOrder.size()); + for (size_t i = 0; i < expectedRankings.size(); ++i) { + EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr) + << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue() + << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue(); + } +} + TEST_F(RefreshRateConfigsTest, getBestRefreshRate_withDisplayManagerRequestingSingleRate_onlySwitchesRatesForExplicitFocusedLayers) { TestableRefreshRateConfigs configs(kModes_60_90, kModeId90); EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}}), 0); - const auto [mode, signals] = configs.getBestRefreshRateAndSignals({}, {}); - EXPECT_EQ(mode, kMode90); + const auto [mode, signals] = configs.getRankedRefreshRatesAndSignals({}, {}); + EXPECT_EQ(mode.front().displayModePtr, kMode90); EXPECT_FALSE(signals.touch); std::vector layers = {{.weight = 1.f}}; @@ -1593,11 +1852,12 @@ TEST_F(RefreshRateConfigsTest, idle) { layers[0].desiredRefreshRate = 90_Hz; const auto [refreshRate, signals] = - configs.getBestRefreshRateAndSignals(layers, {.touch = touchActive, .idle = true}); + configs.getRankedRefreshRatesAndSignals(layers, + {.touch = touchActive, .idle = true}); // Refresh rate will be chosen by either touch state or idle state. EXPECT_EQ(!touchActive, signals.idle); - return refreshRate->getId(); + return refreshRate.front().displayModePtr->getId(); }; EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}, {60_Hz, 90_Hz}}), 0); @@ -1756,24 +2016,26 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ReadsCache) { using GlobalSignals = RefreshRateConfigs::GlobalSignals; const auto args = std::make_pair(std::vector{}, GlobalSignals{.touch = true, .idle = true}); - const auto result = std::make_pair(kMode90, GlobalSignals{.touch = true}); - configs.mutableGetBestRefreshRateCache() = {args, result}; + const auto result = std::make_pair(std::vector{RefreshRateRanking{kMode90}}, + GlobalSignals{.touch = true}); + + configs.mutableGetRankedRefreshRatesCache() = {args, result}; - EXPECT_EQ(result, configs.getBestRefreshRateAndSignals(args.first, args.second)); + EXPECT_EQ(result, configs.getRankedRefreshRatesAndSignals(args.first, args.second)); } TEST_F(RefreshRateConfigsTest, getBestRefreshRate_WritesCache) { TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60); - EXPECT_FALSE(configs.mutableGetBestRefreshRateCache()); + EXPECT_FALSE(configs.mutableGetRankedRefreshRatesCache()); std::vector layers = {{.weight = 1.f}, {.weight = 0.5f}}; RefreshRateConfigs::GlobalSignals globalSignals{.touch = true, .idle = true}; - const auto result = configs.getBestRefreshRateAndSignals(layers, globalSignals); + const auto result = configs.getRankedRefreshRatesAndSignals(layers, globalSignals); - const auto& cache = configs.mutableGetBestRefreshRateCache(); + const auto& cache = configs.mutableGetRankedRefreshRatesCache(); ASSERT_TRUE(cache); EXPECT_EQ(cache->arguments, std::make_pair(layers, globalSignals)); -- GitLab From 6839927f9fac02a048c73e63a4a988cd7be118b9 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Thu, 15 Sep 2022 07:54:46 -0700 Subject: [PATCH 0297/1310] SF: Fix a sporadic crash during multi-display boot The Scheduler is created when the first commit adds the primary display, and accessed whenever a commit adds a physical display. Transactions are ordered by display token, which is an arbitrary address. In a multi-display boot, the addition of the primary display could be reordered after other displays, causing a crash when the Scheduler is accessed before creation. As a short-term workaround until Scheduler initialization is decoupled from commit of display transactions, commit the primary display first. Bug: 244442572 Test: Boot Change-Id: I62c4b5299de329a02e60d5f2529ed49907d74415 --- services/surfaceflinger/SurfaceFlinger.cpp | 52 +++++++++++++++------- services/surfaceflinger/SurfaceFlinger.h | 19 ++++++-- 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 78eaa14e5b..13331ecbc7 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -815,16 +815,37 @@ void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { enableHalVirtualDisplays(true); } - // Process any initial hotplug and resulting display changes. + // Process hotplug for displays connected at boot. LOG_ALWAYS_FATAL_IF(!configureLocked(), "Initial display configuration failed: HWC did not hotplug"); - processDisplayChangesLocked(); - const auto display = getDefaultDisplayDeviceLocked(); + // Commit primary display. + sp display; + if (const auto indexOpt = mCurrentState.getDisplayIndex(getPrimaryDisplayIdLocked())) { + const auto& displays = mCurrentState.displays; + + const auto& token = displays.keyAt(*indexOpt); + const auto& state = displays.valueAt(*indexOpt); + + processDisplayAdded(token, state); + mDrawingState.displays.add(token, state); + + display = getDefaultDisplayDeviceLocked(); + } + LOG_ALWAYS_FATAL_IF(!display, "Failed to configure the primary display"); LOG_ALWAYS_FATAL_IF(!getHwComposer().isConnected(display->getPhysicalId()), "Primary display is disconnected"); + // TODO(b/241285876): The Scheduler needlessly depends on creating the CompositionEngine part of + // the DisplayDevice, hence the above commit of the primary display. Remove that special case by + // initializing the Scheduler after configureLocked, once decoupled from DisplayDevice. + initScheduler(display); + dispatchDisplayHotplugEvent(display->getPhysicalId(), true); + + // Commit secondary display(s). + processDisplayChangesLocked(); + // initialize our drawing state mDrawingState = mCurrentState; @@ -2952,10 +2973,13 @@ void SurfaceFlinger::processDisplayAdded(const wp& displayToken, LOG_FATAL_IF(!displaySurface); auto display = setupNewDisplayDeviceInternal(displayToken, std::move(compositionDisplay), state, displaySurface, producer); - if (display->isPrimary()) { - initScheduler(display); - } - if (!state.isVirtual()) { + + if (mScheduler && !display->isVirtual()) { + // Display modes are reloaded on hotplug reconnect. + if (display->isPrimary()) { + mScheduler->setRefreshRateConfigs(display->holdRefreshRateConfigs()); + } + dispatchDisplayHotplugEvent(display->getPhysicalId(), true); } @@ -3367,15 +3391,9 @@ void SurfaceFlinger::triggerOnFrameRateOverridesChanged() { mScheduler->onFrameRateOverridesChanged(mAppConnectionHandle, displayId); } -void SurfaceFlinger::initScheduler(const sp& display) { - if (mScheduler) { - // If the scheduler is already initialized, this means that we received - // a hotplug(connected) on the primary display. In that case we should - // update the scheduler with the most recent display information. - ALOGW("Scheduler already initialized, updating instead"); - mScheduler->setRefreshRateConfigs(display->holdRefreshRateConfigs()); - return; - } +void SurfaceFlinger::initScheduler(const sp& display) { + LOG_ALWAYS_FATAL_IF(mScheduler); + const auto currRefreshRate = display->getActiveMode()->getFps(); mRefreshRateStats = std::make_unique(*mTimeStats, currRefreshRate, hal::PowerMode::OFF); @@ -7076,7 +7094,7 @@ void SurfaceFlinger::sample() { mRegionSamplingThread->onCompositionComplete(mScheduler->getScheduledFrameTime()); } -void SurfaceFlinger::onActiveDisplaySizeChanged(const sp& activeDisplay) { +void SurfaceFlinger::onActiveDisplaySizeChanged(const sp& activeDisplay) { mScheduler->onActiveDisplayAreaChanged(activeDisplay->getWidth() * activeDisplay->getHeight()); getRenderEngine().onActiveDisplaySizeChanged(activeDisplay->getSize()); } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index f7684a0ce1..9b4e7369f6 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -374,7 +374,20 @@ private: const LayerVector::StateSet stateSet = LayerVector::StateSet::Invalid; LayerVector layersSortedByZ; - DefaultKeyedVector< wp, DisplayDeviceState> displays; + + // TODO(b/241285876): Replace deprecated DefaultKeyedVector with ftl::SmallMap. + DefaultKeyedVector, DisplayDeviceState> displays; + + std::optional getDisplayIndex(PhysicalDisplayId displayId) const { + for (size_t i = 0; i < displays.size(); i++) { + const auto& state = displays.valueAt(i); + if (state.physical && state.physical->id == displayId) { + return i; + } + } + + return {}; + } bool colorMatrixChanged = true; mat4 colorMatrix; @@ -695,7 +708,7 @@ private: void commitInputWindowCommands() REQUIRES(mStateLock); void updateCursorAsync(); - void initScheduler(const sp& display) REQUIRES(mStateLock); + void initScheduler(const sp&) REQUIRES(mStateLock); void updatePhaseConfiguration(const Fps&) REQUIRES(mStateLock); void setVsyncConfig(const VsyncModulator::VsyncConfig&, nsecs_t vsyncPeriod); @@ -1027,7 +1040,7 @@ private: void onActiveDisplayChangedLocked(const sp& activeDisplay) REQUIRES(mStateLock, kMainThreadContext); - void onActiveDisplaySizeChanged(const sp& activeDisplay); + void onActiveDisplaySizeChanged(const sp&); /* * Debugging & dumpsys -- GitLab From 0bb5e59dd59b48b2878fff92c1220707420c8d68 Mon Sep 17 00:00:00 2001 From: Yeabkal Wubshit Date: Wed, 14 Sep 2022 01:22:28 -0700 Subject: [PATCH 0298/1310] Add VelocityTracker Support for AXIS_SCROLL AXIS_SCROLL motion events report diffs in movement between successive events, so we have introduced a concept of cumulative-axes in this CL to support AXIS_SCROLL. Demo: go/rsb-fling-demo-1 Bug: 32830165 Test: atest libinput_tests; manual fling with rotating side button Change-Id: Ic124b5c6d4d4bc30d1a45c49be4e5e8856d6c974 --- include/input/VelocityTracker.h | 10 +- libs/input/VelocityTracker.cpp | 47 ++++- libs/input/tests/VelocityTracker_test.cpp | 238 ++++++++++++++++++---- 3 files changed, 238 insertions(+), 57 deletions(-) diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h index 6fac19f3dd..f6556d6712 100644 --- a/include/input/VelocityTracker.h +++ b/include/input/VelocityTracker.h @@ -160,7 +160,8 @@ private: void configureStrategy(int32_t axis); - static std::unique_ptr createStrategy(const Strategy strategy); + static std::unique_ptr createStrategy(const Strategy strategy, + bool isCumulative); }; @@ -306,7 +307,7 @@ private: class ImpulseVelocityTrackerStrategy : public VelocityTrackerStrategy { public: - ImpulseVelocityTrackerStrategy(); + ImpulseVelocityTrackerStrategy(bool deltaValues); virtual ~ImpulseVelocityTrackerStrategy(); virtual void clearPointers(BitSet32 idBits); @@ -331,6 +332,11 @@ private: inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; } }; + // Whether or not the input movement values for the strategy come in the form of delta values. + // If the input values are not deltas, the strategy needs to calculate deltas as part of its + // velocity calculation. + const bool mDeltaValues; + size_t mIndex; Movement mMovements[HISTORY_SIZE]; }; diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp index 587e014cc9..4a4f734a86 100644 --- a/libs/input/VelocityTracker.cpp +++ b/libs/input/VelocityTracker.cpp @@ -62,11 +62,15 @@ static const nsecs_t NANOS_PER_MS = 1000000; // (often in a bad way) the user experience. static const std::map DEFAULT_STRATEGY_BY_AXIS = {{AMOTION_EVENT_AXIS_X, VelocityTracker::Strategy::LSQ2}, - {AMOTION_EVENT_AXIS_Y, VelocityTracker::Strategy::LSQ2}}; + {AMOTION_EVENT_AXIS_Y, VelocityTracker::Strategy::LSQ2}, + {AMOTION_EVENT_AXIS_SCROLL, VelocityTracker::Strategy::IMPULSE}}; // Axes specifying location on a 2D plane (i.e. X and Y). static const std::set PLANAR_AXES = {AMOTION_EVENT_AXIS_X, AMOTION_EVENT_AXIS_Y}; +// Axes whose motion values are differential values (i.e. deltas). +static const std::set DIFFERENTIAL_AXES = {AMOTION_EVENT_AXIS_SCROLL}; + // Threshold for determining that a pointer has stopped moving. // Some input devices do not send ACTION_MOVE events in the case where a pointer has // stopped. We need to detect this case so that we can accurately predict the @@ -147,11 +151,14 @@ VelocityTracker::~VelocityTracker() { } void VelocityTracker::configureStrategy(int32_t axis) { + const bool isDifferentialAxis = DIFFERENTIAL_AXES.find(axis) != DIFFERENTIAL_AXES.end(); + std::unique_ptr createdStrategy; if (mOverrideStrategy != VelocityTracker::Strategy::DEFAULT) { - createdStrategy = createStrategy(mOverrideStrategy); + createdStrategy = createStrategy(mOverrideStrategy, isDifferentialAxis /* deltaValues */); } else { - createdStrategy = createStrategy(DEFAULT_STRATEGY_BY_AXIS.at(axis)); + createdStrategy = createStrategy(DEFAULT_STRATEGY_BY_AXIS.at(axis), + isDifferentialAxis /* deltaValues */); } LOG_ALWAYS_FATAL_IF(createdStrategy == nullptr, @@ -160,11 +167,11 @@ void VelocityTracker::configureStrategy(int32_t axis) { } std::unique_ptr VelocityTracker::createStrategy( - VelocityTracker::Strategy strategy) { + VelocityTracker::Strategy strategy, bool deltaValues) { switch (strategy) { case VelocityTracker::Strategy::IMPULSE: ALOGI_IF(DEBUG_STRATEGY, "Initializing impulse strategy"); - return std::make_unique(); + return std::make_unique(deltaValues); case VelocityTracker::Strategy::LSQ1: return std::make_unique(1); @@ -328,6 +335,9 @@ void VelocityTracker::addMovement(const MotionEvent* event) { // before adding the movement. return; } + case AMOTION_EVENT_ACTION_SCROLL: + axesToProcess.insert(AMOTION_EVENT_AXIS_SCROLL); + break; default: // Ignore all other actions. return; @@ -1004,7 +1014,8 @@ bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id, // --- ImpulseVelocityTrackerStrategy --- -ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy() : mIndex(0) {} +ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy(bool deltaValues) + : mDeltaValues(deltaValues), mIndex(0) {} ImpulseVelocityTrackerStrategy::~ImpulseVelocityTrackerStrategy() { } @@ -1112,7 +1123,8 @@ static float kineticEnergyToVelocity(float work) { return (work < 0 ? -1.0 : 1.0) * sqrtf(fabsf(work)) * sqrt2; } -static float calculateImpulseVelocity(const nsecs_t* t, const float* x, size_t count) { +static float calculateImpulseVelocity(const nsecs_t* t, const float* x, size_t count, + bool deltaValues) { // The input should be in reversed time order (most recent sample at index i=0) // t[i] is in nanoseconds, but due to FP arithmetic, convert to seconds inside this function static constexpr float SECONDS_PER_NANO = 1E-9; @@ -1123,12 +1135,26 @@ static float calculateImpulseVelocity(const nsecs_t* t, const float* x, size_t c if (t[1] > t[0]) { // Algorithm will still work, but not perfectly ALOGE("Samples provided to calculateImpulseVelocity in the wrong order"); } + + // If the data values are delta values, we do not have to calculate deltas here. + // We can use the delta values directly, along with the calculated time deltas. + // Since the data value input is in reversed time order: + // [a] for non-delta inputs, instantenous velocity = (x[i] - x[i-1])/(t[i] - t[i-1]) + // [b] for delta inputs, instantenous velocity = -x[i-1]/(t[i] - t[i - 1]) + // e.g., let the non-delta values are: V = [2, 3, 7], the equivalent deltas are D = [2, 1, 4]. + // Since the input is in reversed time order, the input values for this function would be + // V'=[7, 3, 2] and D'=[4, 1, 2] for the non-delta and delta values, respectively. + // + // The equivalent of {(V'[2] - V'[1]) = 2 - 3 = -1} would be {-D'[1] = -1} + // Similarly, the equivalent of {(V'[1] - V'[0]) = 3 - 7 = -4} would be {-D'[0] = -4} + if (count == 2) { // if 2 points, basic linear calculation if (t[1] == t[0]) { ALOGE("Events have identical time stamps t=%" PRId64 ", setting velocity = 0", t[0]); return 0; } - return (x[1] - x[0]) / (SECONDS_PER_NANO * (t[1] - t[0])); + const float deltaX = deltaValues ? -x[0] : x[1] - x[0]; + return deltaX / (SECONDS_PER_NANO * (t[1] - t[0])); } // Guaranteed to have at least 3 points here float work = 0; @@ -1138,7 +1164,8 @@ static float calculateImpulseVelocity(const nsecs_t* t, const float* x, size_t c continue; } float vprev = kineticEnergyToVelocity(work); // v[i-1] - float vcurr = (x[i] - x[i-1]) / (SECONDS_PER_NANO * (t[i] - t[i-1])); // v[i] + const float deltaX = deltaValues ? -x[i-1] : x[i] - x[i-1]; + float vcurr = deltaX / (SECONDS_PER_NANO * (t[i] - t[i-1])); // v[i] work += (vcurr - vprev) * fabsf(vcurr); if (i == count - 1) { work *= 0.5; // initial condition, case 2) above @@ -1177,7 +1204,7 @@ bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, return false; // no data } outEstimator->coeff[0] = 0; - outEstimator->coeff[1] = calculateImpulseVelocity(time, positions, m); + outEstimator->coeff[1] = calculateImpulseVelocity(time, positions, m, mDeltaValues); outEstimator->coeff[2] = 0; outEstimator->time = newestMovement.eventTime; diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp index 0b7def372b..8b00b5c44d 100644 --- a/libs/input/tests/VelocityTracker_test.cpp +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -87,7 +87,7 @@ struct Position { } }; -struct MotionEventEntry { +struct PlanarMotionEventEntry { std::chrono::nanoseconds eventTime; std::vector positions; }; @@ -136,15 +136,43 @@ static int32_t resolveAction(const std::vector& lastPositions, return AMOTION_EVENT_ACTION_MOVE; } -static std::vector createMotionEventStream( - const std::vector& motions) { +static std::vector createAxisScrollMotionEventStream( + const std::vector>& motions) { + std::vector events; + for (const auto& [timeStamp, value] : motions) { + EXPECT_TRUE(!isnan(value)) << "The entry at pointerId must be valid"; + + PointerCoords coords[1]; + coords[0].setAxisValue(AMOTION_EVENT_AXIS_SCROLL, value); + + PointerProperties properties[1]; + properties[0].id = DEFAULT_POINTER_ID; + + MotionEvent event; + ui::Transform identityTransform; + event.initialize(InputEvent::nextId(), 5 /*deviceId*/, AINPUT_SOURCE_ROTARY_ENCODER, + ADISPLAY_ID_NONE, INVALID_HMAC, AMOTION_EVENT_ACTION_SCROLL, + 0 /*actionButton*/, 0 /*flags*/, AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, + 0 /*buttonState*/, MotionClassification::NONE, identityTransform, + 0 /*xPrecision*/, 0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0 /*downTime*/, + timeStamp.count(), 1 /*pointerCount*/, properties, coords); + + events.emplace_back(event); + } + + return events; +} + +static std::vector createTouchMotionEventStream( + const std::vector& motions) { if (motions.empty()) { ADD_FAILURE() << "Need at least 1 sample to create a MotionEvent. Received empty vector."; } std::vector events; for (size_t i = 0; i < motions.size(); i++) { - const MotionEventEntry& entry = motions[i]; + const PlanarMotionEventEntry& entry = motions[i]; BitSet32 pointers = getValidPointers(entry.positions); const uint32_t pointerCount = pointers.count(); @@ -155,8 +183,8 @@ static std::vector createMotionEventStream( } else if ((i == motions.size() - 1) && pointerCount == 1) { action = AMOTION_EVENT_ACTION_UP; } else { - const MotionEventEntry& previousEntry = motions[i-1]; - const MotionEventEntry& nextEntry = motions[i+1]; + const PlanarMotionEventEntry& previousEntry = motions[i-1]; + const PlanarMotionEventEntry& nextEntry = motions[i+1]; action = resolveAction(previousEntry.positions, entry.positions, nextEntry.positions); } @@ -196,11 +224,12 @@ static std::vector createMotionEventStream( } static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, - const std::vector& motions, int32_t axis, - float targetVelocity, uint32_t pointerId = DEFAULT_POINTER_ID) { + const std::vector& motions, + int32_t axis, float targetVelocity, + uint32_t pointerId = DEFAULT_POINTER_ID) { VelocityTracker vt(strategy); - std::vector events = createMotionEventStream(motions); + std::vector events = createTouchMotionEventStream(motions); for (MotionEvent event : events) { vt.addMovement(&event); } @@ -208,10 +237,33 @@ static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, checkVelocity(vt.getVelocity(axis, pointerId).value_or(0), targetVelocity); } -static void computeAndCheckQuadraticEstimate(const std::vector& motions, - const std::array& coefficients) { +static void computeAndCheckAxisScrollVelocity( + const VelocityTracker::Strategy strategy, + const std::vector>& motions, + std::optional targetVelocity) { + VelocityTracker vt(strategy); + + std::vector events = createAxisScrollMotionEventStream(motions); + for (const MotionEvent& event : events) { + vt.addMovement(&event); + } + + std::optional velocity = vt.getVelocity(AMOTION_EVENT_AXIS_SCROLL, DEFAULT_POINTER_ID); + if (velocity && !targetVelocity) { + FAIL() << "Expected no velocity, but found " << *velocity; + } + if (!velocity && targetVelocity) { + FAIL() << "Expected velocity, but found no velocity"; + } + if (velocity) { + checkVelocity(*velocity, *targetVelocity); + } +} + +static void computeAndCheckQuadraticEstimate(const std::vector& motions, + const std::array& coefficients) { VelocityTracker vt(VelocityTracker::Strategy::LSQ2); - std::vector events = createMotionEventStream(motions); + std::vector events = createTouchMotionEventStream(motions); for (MotionEvent event : events) { vt.addMovement(&event); } @@ -269,7 +321,7 @@ TEST_F(VelocityTrackerTest, TestComputedVelocity) { } TEST_F(VelocityTrackerTest, TestGetComputedVelocity) { - std::vector motions = { + std::vector motions = { {235089067457000ns, {{528.00, 0}}}, {235089084684000ns, {{527.00, 0}}}, {235089093349000ns, {{527.00, 0}}}, {235089095677625ns, {{527.00, 0}}}, {235089101859000ns, {{527.00, 0}}}, {235089110378000ns, {{528.00, 0}}}, @@ -281,7 +333,7 @@ TEST_F(VelocityTrackerTest, TestGetComputedVelocity) { {235089162955851ns, {{560.66, 0}}}, // ACTION_UP }; VelocityTracker vt(VelocityTracker::Strategy::IMPULSE); - std::vector events = createMotionEventStream(motions); + std::vector events = createTouchMotionEventStream(motions); for (const MotionEvent& event : events) { vt.addMovement(&event); } @@ -333,7 +385,7 @@ TEST_F(VelocityTrackerTest, ThreePointsPositiveVelocityTest) { // Same coordinate is reported 2 times in a row // It is difficult to determine the correct answer here, but at least the direction // of the reported velocity should be positive. - std::vector motions = { + std::vector motions = { {0ms, {{273, 0}}}, {12585us, {{293, 0}}}, {14730us, {{293, 0}}}, @@ -345,7 +397,7 @@ TEST_F(VelocityTrackerTest, ThreePointsPositiveVelocityTest) { TEST_F(VelocityTrackerTest, ThreePointsZeroVelocityTest) { // Same coordinate is reported 3 times in a row - std::vector motions = { + std::vector motions = { {0ms, {{293, 0}}}, {6132us, {{293, 0}}}, {11283us, {{293, 0}}}, @@ -357,7 +409,7 @@ TEST_F(VelocityTrackerTest, ThreePointsZeroVelocityTest) { TEST_F(VelocityTrackerTest, ThreePointsLinearVelocityTest) { // Fixed velocity at 5 points per 10 milliseconds - std::vector motions = { + std::vector motions = { {0ms, {{0, 0}}}, {10ms, {{5, 0}}}, {20ms, {{10, 0}}}, {20ms, {{10, 0}}}, // ACTION_UP }; computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 500); @@ -381,7 +433,7 @@ TEST_F(VelocityTrackerTest, ThreePointsLinearVelocityTest) { // --------------- Recorded by hand on swordfish --------------------------------------------------- TEST_F(VelocityTrackerTest, SwordfishFlingDown) { // Recording of a fling on Swordfish that could cause a fling in the wrong direction - std::vector motions = { + std::vector motions = { { 0ms, {{271, 96}} }, { 16071042ns, {{269.786346, 106.922775}} }, { 35648403ns, {{267.983063, 156.660034}} }, @@ -416,7 +468,7 @@ TEST_F(VelocityTrackerTest, SwordfishFlingDown) { TEST_F(VelocityTrackerTest, SailfishFlingUpSlow1) { // Sailfish - fling up - slow - 1 - std::vector motions = { + std::vector motions = { { 235089067457000ns, {{528.00, 983.00}} }, { 235089084684000ns, {{527.00, 981.00}} }, { 235089093349000ns, {{527.00, 977.00}} }, @@ -448,7 +500,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpSlow1) { TEST_F(VelocityTrackerTest, SailfishFlingUpSlow2) { // Sailfish - fling up - slow - 2 - std::vector motions = { + std::vector motions = { { 235110560704000ns, {{522.00, 1107.00}} }, { 235110575764000ns, {{522.00, 1107.00}} }, { 235110584385000ns, {{522.00, 1107.00}} }, @@ -477,7 +529,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpSlow2) { TEST_F(VelocityTrackerTest, SailfishFlingUpSlow3) { // Sailfish - fling up - slow - 3 - std::vector motions = { + std::vector motions = { { 792536237000ns, {{580.00, 1317.00}} }, { 792541538987ns, {{580.63, 1311.94}} }, { 792544613000ns, {{581.00, 1309.00}} }, @@ -511,7 +563,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpSlow3) { TEST_F(VelocityTrackerTest, SailfishFlingUpFaster1) { // Sailfish - fling up - faster - 1 - std::vector motions = { + std::vector motions = { { 235160420675000ns, {{610.00, 1042.00}} }, { 235160428220000ns, {{609.00, 1026.00}} }, { 235160436544000ns, {{609.00, 1024.00}} }, @@ -545,7 +597,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpFaster1) { TEST_F(VelocityTrackerTest, SailfishFlingUpFaster2) { // Sailfish - fling up - faster - 2 - std::vector motions = { + std::vector motions = { { 847153808000ns, {{576.00, 1264.00}} }, { 847171174000ns, {{576.00, 1262.00}} }, { 847179640000ns, {{576.00, 1257.00}} }, @@ -571,7 +623,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpFaster2) { TEST_F(VelocityTrackerTest, SailfishFlingUpFaster3) { // Sailfish - fling up - faster - 3 - std::vector motions = { + std::vector motions = { { 235200532789000ns, {{507.00, 1084.00}} }, { 235200549221000ns, {{507.00, 1083.00}} }, { 235200557841000ns, {{507.00, 1081.00}} }, @@ -597,7 +649,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpFaster3) { TEST_F(VelocityTrackerTest, SailfishFlingUpFast1) { // Sailfish - fling up - fast - 1 - std::vector motions = { + std::vector motions = { { 920922149000ns, {{561.00, 1412.00}} }, { 920930185000ns, {{559.00, 1377.00}} }, { 920930262463ns, {{558.98, 1376.66}} }, @@ -626,7 +678,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpFast1) { TEST_F(VelocityTrackerTest, SailfishFlingUpFast2) { // Sailfish - fling up - fast - 2 - std::vector motions = { + std::vector motions = { { 235247153233000ns, {{518.00, 1168.00}} }, { 235247170452000ns, {{517.00, 1167.00}} }, { 235247178908000ns, {{515.00, 1159.00}} }, @@ -649,7 +701,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpFast2) { TEST_F(VelocityTrackerTest, SailfishFlingUpFast3) { // Sailfish - fling up - fast - 3 - std::vector motions = { + std::vector motions = { { 235302568736000ns, {{529.00, 1167.00}} }, { 235302576644000ns, {{523.00, 1140.00}} }, { 235302579395063ns, {{520.91, 1130.61}} }, @@ -670,7 +722,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpFast3) { TEST_F(VelocityTrackerTest, SailfishFlingDownSlow1) { // Sailfish - fling down - slow - 1 - std::vector motions = { + std::vector motions = { { 235655749552755ns, {{582.00, 432.49}} }, { 235655750638000ns, {{582.00, 433.00}} }, { 235655758865000ns, {{582.00, 440.00}} }, @@ -704,7 +756,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownSlow1) { TEST_F(VelocityTrackerTest, SailfishFlingDownSlow2) { // Sailfish - fling down - slow - 2 - std::vector motions = { + std::vector motions = { { 235671152083370ns, {{485.24, 558.28}} }, { 235671154126000ns, {{485.00, 559.00}} }, { 235671162497000ns, {{484.00, 566.00}} }, @@ -738,7 +790,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownSlow2) { TEST_F(VelocityTrackerTest, SailfishFlingDownSlow3) { // Sailfish - fling down - slow - 3 - std::vector motions = { + std::vector motions = { { 170983201000ns, {{557.00, 533.00}} }, { 171000668000ns, {{556.00, 534.00}} }, { 171007359750ns, {{554.73, 535.27}} }, @@ -765,7 +817,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownSlow3) { TEST_F(VelocityTrackerTest, SailfishFlingDownFaster1) { // Sailfish - fling down - faster - 1 - std::vector motions = { + std::vector motions = { { 235695280333000ns, {{558.00, 451.00}} }, { 235695283971237ns, {{558.43, 454.45}} }, { 235695289038000ns, {{559.00, 462.00}} }, @@ -795,7 +847,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFaster1) { TEST_F(VelocityTrackerTest, SailfishFlingDownFaster2) { // Sailfish - fling down - faster - 2 - std::vector motions = { + std::vector motions = { { 235709624766000ns, {{535.00, 579.00}} }, { 235709642256000ns, {{534.00, 580.00}} }, { 235709643350278ns, {{533.94, 580.06}} }, @@ -826,7 +878,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFaster2) { TEST_F(VelocityTrackerTest, SailfishFlingDownFaster3) { // Sailfish - fling down - faster - 3 - std::vector motions = { + std::vector motions = { { 235727628927000ns, {{540.00, 440.00}} }, { 235727636810000ns, {{537.00, 454.00}} }, { 235727646176000ns, {{536.00, 454.00}} }, @@ -855,7 +907,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFaster3) { TEST_F(VelocityTrackerTest, SailfishFlingDownFast1) { // Sailfish - fling down - fast - 1 - std::vector motions = { + std::vector motions = { { 235762352849000ns, {{467.00, 286.00}} }, { 235762360250000ns, {{443.00, 344.00}} }, { 235762362787412ns, {{434.77, 363.89}} }, @@ -876,7 +928,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFast1) { TEST_F(VelocityTrackerTest, SailfishFlingDownFast2) { // Sailfish - fling down - fast - 2 - std::vector motions = { + std::vector motions = { { 235772487188000ns, {{576.00, 204.00}} }, { 235772495159000ns, {{553.00, 236.00}} }, { 235772503568000ns, {{551.00, 240.00}} }, @@ -897,7 +949,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFast2) { TEST_F(VelocityTrackerTest, SailfishFlingDownFast3) { // Sailfish - fling down - fast - 3 - std::vector motions = { + std::vector motions = { { 507650295000ns, {{628.00, 233.00}} }, { 507658234000ns, {{605.00, 269.00}} }, { 507666784000ns, {{601.00, 274.00}} }, @@ -928,7 +980,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFast3) { * part of the fitted data), this can cause large velocity values to be reported instead. */ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_ThreeFingerTap) { - std::vector motions = { + std::vector motions = { { 0us, {{1063, 1128}, {NAN, NAN}, {NAN, NAN}} }, { 10800us, {{1063, 1128}, {682, 1318}, {NAN, NAN}} }, // POINTER_DOWN { 10800us, {{1063, 1128}, {682, 1318}, {397, 1747}} }, // POINTER_DOWN @@ -955,7 +1007,7 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_ThreeFi * affected by the liftoff. */ TEST_F(VelocityTrackerTest, ShortDelayBeforeActionUp) { - std::vector motions = { + std::vector motions = { {0ms, {{10, 0}}}, {10ms, {{20, 0}}}, {20ms, {{30, 0}}}, {30ms, {{30, 0}}}, // ACTION_UP }; computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, @@ -969,7 +1021,7 @@ TEST_F(VelocityTrackerTest, ShortDelayBeforeActionUp) { * should be assumed to have stopped. */ TEST_F(VelocityTrackerTest, LongDelayBeforeActionUp) { - std::vector motions = { + std::vector motions = { {0ms, {{10, 0}}}, {10ms, {{20, 0}}}, {20ms, {{30, 0}}}, @@ -985,7 +1037,7 @@ TEST_F(VelocityTrackerTest, LongDelayBeforeActionUp) { * The final velocity should be reported as zero for all pointers. */ TEST_F(VelocityTrackerTest, LongDelayBeforeActionPointerUp) { - std::vector motions = { + std::vector motions = { {0ms, {{10, 0}}}, {10ms, {{20, 0}, {100, 0}}}, {20ms, {{30, 0}, {200, 0}}}, @@ -1029,7 +1081,7 @@ TEST_F(VelocityTrackerTest, LongDelayBeforeActionPointerUp) { * In the test, we would convert these coefficients to (0*(1E3)^0, 0*(1E3)^1, 1*(1E3)^2). */ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Constant) { - std::vector motions = { + std::vector motions = { { 0ms, {{1, 1}} }, // 0 s { 1ms, {{1, 1}} }, // 0.001 s { 2ms, {{1, 1}} }, // 0.002 s @@ -1047,7 +1099,7 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Constan * Straight line y = x :: the constant and quadratic coefficients are zero. */ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Linear) { - std::vector motions = { + std::vector motions = { { 0ms, {{-2, -2}} }, { 1ms, {{-1, -1}} }, { 2ms, {{-0, -0}} }, @@ -1065,7 +1117,7 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Linear) * Parabola */ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic) { - std::vector motions = { + std::vector motions = { { 0ms, {{1, 1}} }, { 1ms, {{4, 4}} }, { 2ms, {{8, 8}} }, @@ -1083,7 +1135,7 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabol * Parabola */ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic2) { - std::vector motions = { + std::vector motions = { { 0ms, {{1, 1}} }, { 1ms, {{4, 4}} }, { 2ms, {{9, 9}} }, @@ -1101,7 +1153,7 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabol * Parabola :: y = x^2 :: the constant and linear coefficients are zero. */ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic3) { - std::vector motions = { + std::vector motions = { { 0ms, {{4, 4}} }, { 1ms, {{1, 1}} }, { 2ms, {{0, 0}} }, @@ -1115,4 +1167,100 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabol computeAndCheckQuadraticEstimate(motions, std::array({0, 0E3, 1E6})); } +// Recorded by hand on sailfish, but only the diffs are taken to test cumulative axis velocity. +TEST_F(VelocityTrackerTest, AxisScrollVelocity) { + std::vector> motions = { + {235089067457000ns, 0.00}, {235089084684000ns, -1.00}, {235089093349000ns, 0.00}, + {235089095677625ns, 0.00}, {235089101859000ns, 0.00}, {235089110378000ns, 0.00}, + {235089112497111ns, 0.25}, {235089118760000ns, 1.75}, {235089126686000ns, 4.00}, + {235089129316820ns, 1.33}, {235089135199000ns, 3.67}, {235089144297000ns, 6.00}, + {235089146136443ns, 1.21}, {235089152923000ns, 5.79}, {235089160784000ns, 6.00}, + {235089162955851ns, 1.66}, + }; + + computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, {764.345703}); +} + +// --------------- Recorded by hand on a Wear OS device using a rotating side button --------------- +TEST_F(VelocityTrackerTest, AxisScrollVelocity_ScrollDown) { + std::vector> motions = { + {224598065152ns, -0.050100}, {224621871104ns, -0.133600}, {224645464064ns, -0.551100}, + {224669171712ns, -0.801600}, {224687063040ns, -1.035400}, {224706691072ns, -0.484300}, + {224738213888ns, -0.334000}, {224754401280ns, -0.083500}, + }; + + computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, {-27.86}); +} + +TEST_F(VelocityTrackerTest, AxisScrollVelocity_ScrollUp) { + std::vector> motions = { + {269606010880ns, 0.050100}, {269626064896ns, 0.217100}, {269641973760ns, 0.267200}, + {269658079232ns, 0.267200}, {269674217472ns, 0.267200}, {269690683392ns, 0.367400}, + {269706133504ns, 0.551100}, {269722173440ns, 0.501000}, + }; + + computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, {31.92}); +} + +TEST_F(VelocityTrackerTest, AxisScrollVelocity_ScrollDown_ThenUp_ThenDown) { + std::vector> motions = { + {2580534001664ns, -0.033400}, {2580549992448ns, -0.133600}, + {2580566769664ns, -0.250500}, {2580581974016ns, -0.183700}, + {2580597964800ns, -0.267200}, {2580613955584ns, -0.551100}, + {2580635189248ns, -0.601200}, {2580661927936ns, -0.450900}, + {2580683161600ns, -0.417500}, {2580705705984ns, -0.150300}, + {2580722745344ns, -0.016700}, {2580786446336ns, 0.050100}, + {2580801912832ns, 0.150300}, {2580822360064ns, 0.300600}, + {2580838088704ns, 0.300600}, {2580854341632ns, 0.400800}, + {2580869808128ns, 0.517700}, {2580886061056ns, 0.501000}, + {2580905984000ns, 0.350700}, {2580921974784ns, 0.350700}, + {2580937965568ns, 0.066800}, {2580974665728ns, 0.016700}, + {2581034434560ns, -0.066800}, {2581049901056ns, -0.116900}, + {2581070610432ns, -0.317300}, {2581086076928ns, -0.200400}, + {2581101805568ns, -0.233800}, {2581118058496ns, -0.417500}, + {2581134049280ns, -0.417500}, {2581150040064ns, -0.367400}, + {2581166030848ns, -0.267200}, {2581181759488ns, -0.150300}, + {2581199847424ns, -0.066800}, + }; + + computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, {-9.73}); +} + +// ------------------------------- Hand generated test cases --------------------------------------- +TEST_F(VelocityTrackerTest, AxisScrollVelocity_SimilarDifferentialValues) { + std::vector> motions = {{1ns, 2.12}, {3ns, 2.12}, + {7ns, 2.12}, {8ns, 2.12}, + {15ns, 2.12}, {18ns, 2.12}}; + + computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, {1690236059.86}); +} + +TEST_F(VelocityTrackerTest, AxisScrollVelocity_OnlyTwoValues) { + std::vector> motions = {{1ms, 5}, {2ms, 10}}; + + computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, {10000}); +} + +TEST_F(VelocityTrackerTest, AxisScrollVelocity_ConstantVelocity) { + std::vector> motions = {{1ms, 20}, {2ms, 20}, + {3ms, 20}, {4ms, 20}, + {5ms, 20}, {6ms, 20}}; + + computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, {20000}); +} + +TEST_F(VelocityTrackerTest, AxisScrollVelocity_NoMotion) { + std::vector> motions = {{1ns, 0}, {2ns, 0}, + {3ns, 0}, {4ns, 0}, + {5ns, 0}, {6ns, 0}}; + + computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, {0}); +} + +TEST_F(VelocityTrackerTest, AxisScrollVelocity_NoData) { + std::vector> motions = {}; + + computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, std::nullopt); +} + } // namespace android -- GitLab From 83f36b254e1cf0df5802f618cdac003fa3784e0d Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Wed, 14 Sep 2022 17:57:35 +0000 Subject: [PATCH 0299/1310] SF: Remove BufferStateLayer class This CL removes the BufferStateLayer class and some of the references to "BufferStateLayer" in factory and utility method names. In order to keep the diff smaller, I'll send out a separate CL to remove the remaining references. Bug: 238781169 Test: refactor, existing tests pass Change-Id: I62fe00caa87e109435da62329e817c19cfb96ead --- libs/gui/include/gui/LayerState.h | 2 +- libs/ui/include/ui/GraphicBuffer.h | 2 +- services/surfaceflinger/BufferStateLayer.h | 28 -------- services/surfaceflinger/Layer.cpp | 5 +- services/surfaceflinger/Scheduler/Scheduler.h | 10 +-- services/surfaceflinger/SurfaceFlinger.cpp | 1 - services/surfaceflinger/SurfaceFlinger.h | 1 - .../SurfaceFlingerDefaultFactory.cpp | 5 +- .../SurfaceFlingerDefaultFactory.h | 2 +- .../surfaceflinger/SurfaceFlingerFactory.h | 3 +- .../Tracing/tools/LayerTraceGenerator.cpp | 4 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 5 +- .../fuzzer/surfaceflinger_layer_fuzzer.cpp | 4 +- .../surfaceflinger_scheduler_fuzzer.cpp | 2 +- .../surfaceflinger/tests/LayerBorder_test.cpp | 10 +-- .../tests/LayerCallback_test.cpp | 64 +++++++++---------- .../tests/LayerRenderTypeTransaction_test.cpp | 46 ++++++------- .../tests/LayerTransactionTest.h | 6 +- .../tests/LayerTransaction_test.cpp | 6 +- .../surfaceflinger/tests/MirrorLayer_test.cpp | 20 +++--- .../tests/ScreenCapture_test.cpp | 24 +++---- .../tests/unittests/CachingTest.cpp | 3 +- .../tests/unittests/CompositionTest.cpp | 16 ++--- .../tests/unittests/FpsReporterTest.cpp | 7 +- .../tests/unittests/GameModeTest.cpp | 27 ++++---- .../tests/unittests/LayerTestUtils.cpp | 2 +- .../unittests/RefreshRateSelectionTest.cpp | 8 +-- .../tests/unittests/SetFrameRateTest.cpp | 1 - .../tests/unittests/TestableSurfaceFlinger.h | 5 +- .../unittests/TransactionApplicationTest.cpp | 3 +- .../unittests/TransactionFrameTracerTest.cpp | 6 +- .../unittests/TransactionSurfaceFrameTest.cpp | 25 ++++---- .../TunnelModeEnabledReporterTest.cpp | 8 +-- .../tests/unittests/mock/MockLayer.h | 6 +- 34 files changed, 158 insertions(+), 209 deletions(-) delete mode 100644 services/surfaceflinger/BufferStateLayer.h diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 3c7b16266d..c8927ad55a 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -133,7 +133,7 @@ struct layer_state_t { eLayerOpaque = 0x02, // SURFACE_OPAQUE eLayerSkipScreenshot = 0x40, // SKIP_SCREENSHOT eLayerSecure = 0x80, // SECURE - // Queue up BufferStateLayer buffers instead of dropping the oldest buffer when this flag is + // Queue up layer buffers instead of dropping the oldest buffer when this flag is // set. This blocks the client until all the buffers have been presented. If the buffers // have presentation timestamps, then we may drop buffers. eEnableBackpressure = 0x100, // ENABLE_BACKPRESSURE diff --git a/libs/ui/include/ui/GraphicBuffer.h b/libs/ui/include/ui/GraphicBuffer.h index 57be686592..dbe475b805 100644 --- a/libs/ui/include/ui/GraphicBuffer.h +++ b/libs/ui/include/ui/GraphicBuffer.h @@ -270,7 +270,7 @@ private: // Send a callback when a GraphicBuffer dies. // - // This is used for BufferStateLayer caching. GraphicBuffers are refcounted per process. When + // This is used for layer caching. GraphicBuffers are refcounted per process. When // A GraphicBuffer doesn't have any more sp<> in a process, it is destroyed. This causes // problems when trying to implicitcly cache across process boundaries. Ideally, both sides // of the cache would hold onto wp<> references. When an app dropped its sp<>, the GraphicBuffer diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h deleted file mode 100644 index e53e1c1d89..0000000000 --- a/services/surfaceflinger/BufferStateLayer.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2018 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 "Layer.h" - -namespace android { - -class BufferStateLayer : public Layer { -public: - explicit BufferStateLayer(const LayerCreationArgs& args) : Layer(args){}; -}; - -} // namespace android diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 91e32f3bfe..e690aaf691 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -65,7 +65,6 @@ #include #include -#include "BufferStateLayer.h" #include "DisplayDevice.h" #include "DisplayHardware/HWComposer.h" #include "FrameTimeline.h" @@ -871,7 +870,7 @@ void Layer::prepareBufferStateClientComposition( float bufferWidth = getBufferSize(s).getWidth(); float bufferHeight = getBufferSize(s).getHeight(); - // BufferStateLayers can have a "buffer size" of [0, 0, -1, -1] when no display frame has + // Layers can have a "buffer size" of [0, 0, -1, -1] when no display frame has // been set and there is no parent layer bounds. In that case, the scale is meaningless so // ignore them. if (!getBufferSize(s).isValid()) { @@ -3538,7 +3537,7 @@ Rect Layer::computeBufferCrop(const State& s) { sp Layer::createClone() { LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata()); args.textureName = mTextureName; - sp layer = mFlinger->getFactory().createBufferStateLayer(args); + sp layer = mFlinger->getFactory().createBufferStateLayer(args); layer->mHwcSlotGenerator = mHwcSlotGenerator; layer->setInitialValuesForClone(sp::fromExisting(this)); return layer; diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index afb34590e3..1df60a51f9 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -95,8 +95,8 @@ protected: ~ISchedulerCallback() = default; }; -class Scheduler : impl::MessageQueue { - using Impl = impl::MessageQueue; +class Scheduler : android::impl::MessageQueue { + using Impl = android::impl::MessageQueue; public: Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags); @@ -130,7 +130,7 @@ public: ConnectionHandle createConnection(const char* connectionName, frametimeline::TokenManager*, std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration, - impl::EventThread::InterceptVSyncsCallback); + android::impl::EventThread::InterceptVSyncsCallback); sp createDisplayEventConnection( ConnectionHandle, EventRegistrationFlags eventRegistration = {}); @@ -276,9 +276,9 @@ private: void dispatchCachedReportedMode() REQUIRES(mPolicyLock) EXCLUDES(mRefreshRateConfigsLock); - impl::EventThread::ThrottleVsyncCallback makeThrottleVsyncCallback() const + android::impl::EventThread::ThrottleVsyncCallback makeThrottleVsyncCallback() const EXCLUDES(mRefreshRateConfigsLock); - impl::EventThread::GetVsyncPeriodFunction makeGetVsyncPeriodFunction() const; + android::impl::EventThread::GetVsyncPeriodFunction makeGetVsyncPeriodFunction() const; std::shared_ptr holdRefreshRateConfigs() const EXCLUDES(mRefreshRateConfigsLock) { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index e31490c66d..a7a08bfdaa 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -106,7 +106,6 @@ #include #include "BackgroundExecutor.h" -#include "BufferStateLayer.h" #include "Client.h" #include "Colorizer.h" #include "Display/DisplayMap.h" diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index f7684a0ce1..0daf71d438 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -333,7 +333,6 @@ protected: private: friend class BufferLayer; - friend class BufferStateLayer; friend class Client; friend class FpsReporter; friend class TunnelModeEnabledReporter; diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp index 319d014237..3e30dcb817 100644 --- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp +++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp @@ -22,7 +22,6 @@ #include #include -#include "BufferStateLayer.h" #include "DisplayDevice.h" #include "FrameTracer/FrameTracer.h" #include "Layer.h" @@ -89,8 +88,8 @@ std::unique_ptr DefaultFactory::createComp return compositionengine::impl::createCompositionEngine(); } -sp DefaultFactory::createBufferStateLayer(const LayerCreationArgs& args) { - return sp::make(args); +sp DefaultFactory::createBufferStateLayer(const LayerCreationArgs& args) { + return sp::make(args); } sp DefaultFactory::createEffectLayer(const LayerCreationArgs& args) { diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h index 66022401b7..6fca402641 100644 --- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h +++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h @@ -41,7 +41,7 @@ public: std::unique_ptr createNativeWindowSurface( const sp&) override; std::unique_ptr createCompositionEngine() override; - sp createBufferStateLayer(const LayerCreationArgs& args) override; + sp createBufferStateLayer(const LayerCreationArgs& args) override; sp createEffectLayer(const LayerCreationArgs& args) override; std::unique_ptr createFrameTracer() override; std::unique_ptr createFrameTimeline( diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h index dc2afd3790..6d18adea39 100644 --- a/services/surfaceflinger/SurfaceFlingerFactory.h +++ b/services/surfaceflinger/SurfaceFlingerFactory.h @@ -31,7 +31,6 @@ namespace android { typedef int32_t PixelFormat; class BufferLayerConsumer; -class BufferStateLayer; class DisplayDevice; class FrameTracer; class GraphicBuffer; @@ -89,7 +88,7 @@ public: virtual std::unique_ptr createCompositionEngine() = 0; - virtual sp createBufferStateLayer(const LayerCreationArgs& args) = 0; + virtual sp createBufferStateLayer(const LayerCreationArgs& args) = 0; virtual sp createEffectLayer(const LayerCreationArgs& args) = 0; virtual std::unique_ptr createFrameTracer() = 0; virtual std::unique_ptr createFrameTimeline( diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index 6501e2092b..88171785c7 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -80,8 +80,8 @@ public: return compositionengine::impl::createCompositionEngine(); } - sp createBufferStateLayer(const LayerCreationArgs& args) { - return sp::make(args); + sp createBufferStateLayer(const LayerCreationArgs& args) { + return sp::make(args); } sp createEffectLayer(const LayerCreationArgs& args) { return sp::make(args); } diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 22976186f2..69dbfe0280 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -30,7 +30,6 @@ #include #include -#include "BufferStateLayer.h" #include "DisplayDevice.h" #include "DisplayHardware/ComposerHal.h" #include "FrameTimeline/FrameTimeline.h" @@ -355,9 +354,7 @@ public: return compositionengine::impl::createCompositionEngine(); } - sp createBufferStateLayer(const LayerCreationArgs &) override { - return nullptr; - } + sp createBufferStateLayer(const LayerCreationArgs &) override { return nullptr; } sp createEffectLayer(const LayerCreationArgs &args) override { return sp::make(args); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp index 9ece260ba5..0a142c3ce6 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp @@ -14,7 +14,6 @@ * limitations under the License. * */ -#include #include #include #include @@ -109,8 +108,7 @@ void LayerFuzzer::invokeEffectLayer() { void LayerFuzzer::invokeBufferStateLayer() { TestableSurfaceFlinger flinger; sp client = sp::make(sp::fromExisting(flinger.flinger())); - sp layer = - sp::make(createLayerCreationArgs(&flinger, client)); + sp layer = sp::make(createLayerCreationArgs(&flinger, client)); sp fence = sp::make(); const std::shared_ptr fenceTime = std::make_shared(fence); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index 3fc2b7e233..cb940b3b57 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -322,7 +322,7 @@ void SchedulerFuzzer::fuzzRefreshRateSelection() { LayerCreationArgs args(flinger.flinger(), client, mFdp.ConsumeRandomLengthString(kRandomStringLength) /*name*/, mFdp.ConsumeIntegral() /*layerFlags*/, LayerMetadata()); - sp layer = sp::make(args); + sp layer = sp::make(args); layer->setFrameRateSelectionPriority(mFdp.ConsumeIntegral()); } diff --git a/services/surfaceflinger/tests/LayerBorder_test.cpp b/services/surfaceflinger/tests/LayerBorder_test.cpp index 0d55ec1dcf..85108ad8e6 100644 --- a/services/surfaceflinger/tests/LayerBorder_test.cpp +++ b/services/surfaceflinger/tests/LayerBorder_test.cpp @@ -215,13 +215,13 @@ TEST_F(LayerBorderTest, InvisibleLayers) { }); } -TEST_F(LayerBorderTest, BufferStateLayer) { +TEST_F(LayerBorderTest, LayerWithBuffer) { asTransaction([&](Transaction& t) { t.hide(mEffectLayer1); t.hide(mEffectLayer2); t.show(mContainerLayer); - sp bufferStateLayer = + sp layer = mClient->createSurface(String8("BufferState"), 0 /* width */, 0 /* height */, PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eFXSurfaceBufferState, @@ -236,9 +236,9 @@ TEST_F(LayerBorderTest, BufferStateLayer) { TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 200, 200), Color::GREEN); TransactionUtils::fillGraphicBufferColor(buffer, Rect(200, 200, 400, 400), Color::BLUE); - t.setBuffer(bufferStateLayer, buffer); - t.setPosition(bufferStateLayer, 100, 100); - t.show(bufferStateLayer); + t.setBuffer(layer, buffer); + t.setPosition(layer, 100, 100); + t.show(layer); t.enableBorder(mContainerLayer, true, 20, mColorOrange); }); } diff --git a/services/surfaceflinger/tests/LayerCallback_test.cpp b/services/surfaceflinger/tests/LayerCallback_test.cpp index 1460fe1ce9..26dbc76c3c 100644 --- a/services/surfaceflinger/tests/LayerCallback_test.cpp +++ b/services/surfaceflinger/tests/LayerCallback_test.cpp @@ -51,7 +51,7 @@ public: LayerTransactionTest::TearDown(); } - virtual sp createBufferStateLayer() { + virtual sp createLayerWithBuffer() { return createLayer(mClient, "test", 0, 0, ISurfaceComposerClient::eFXSurfaceBufferState); } @@ -164,7 +164,7 @@ public: TEST_F(LayerCallbackTest, BufferColor) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -183,7 +183,7 @@ TEST_F(LayerCallbackTest, BufferColor) { TEST_F(LayerCallbackTest, NoBufferNoColor) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -206,7 +206,7 @@ TEST_F(LayerCallbackTest, NoBufferNoColor) { TEST_F(LayerCallbackTest, BufferNoColor) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -228,7 +228,7 @@ TEST_F(LayerCallbackTest, BufferNoColor) { TEST_F(LayerCallbackTest, NoBufferColor) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -266,7 +266,7 @@ TEST_F(LayerCallbackTest, NoStateChange) { TEST_F(LayerCallbackTest, OffScreen) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -288,8 +288,8 @@ TEST_F(LayerCallbackTest, OffScreen) { TEST_F(LayerCallbackTest, MergeBufferNoColor) { sp layer1, layer2; - ASSERT_NO_FATAL_FAILURE(layer1 = createBufferStateLayer()); - ASSERT_NO_FATAL_FAILURE(layer2 = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer1 = createLayerWithBuffer()); + ASSERT_NO_FATAL_FAILURE(layer2 = createLayerWithBuffer()); Transaction transaction1, transaction2; CallbackHelper callback1, callback2; @@ -322,8 +322,8 @@ TEST_F(LayerCallbackTest, MergeBufferNoColor) { TEST_F(LayerCallbackTest, MergeNoBufferColor) { sp layer1, layer2; - ASSERT_NO_FATAL_FAILURE(layer1 = createBufferStateLayer()); - ASSERT_NO_FATAL_FAILURE(layer2 = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer1 = createLayerWithBuffer()); + ASSERT_NO_FATAL_FAILURE(layer2 = createLayerWithBuffer()); Transaction transaction1, transaction2; CallbackHelper callback1, callback2; @@ -357,8 +357,8 @@ TEST_F(LayerCallbackTest, MergeNoBufferColor) { TEST_F(LayerCallbackTest, MergeOneBufferOneColor) { sp layer1, layer2; - ASSERT_NO_FATAL_FAILURE(layer1 = createBufferStateLayer()); - ASSERT_NO_FATAL_FAILURE(layer2 = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer1 = createLayerWithBuffer()); + ASSERT_NO_FATAL_FAILURE(layer2 = createLayerWithBuffer()); Transaction transaction1, transaction2; CallbackHelper callback1, callback2; @@ -392,8 +392,8 @@ TEST_F(LayerCallbackTest, MergeOneBufferOneColor) { } TEST_F(LayerCallbackTest, Merge_SameCallback) { sp layer1, layer2; - ASSERT_NO_FATAL_FAILURE(layer1 = createBufferStateLayer()); - ASSERT_NO_FATAL_FAILURE(layer2 = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer1 = createLayerWithBuffer()); + ASSERT_NO_FATAL_FAILURE(layer2 = createLayerWithBuffer()); Transaction transaction1, transaction2; CallbackHelper callback; @@ -418,7 +418,7 @@ TEST_F(LayerCallbackTest, Merge_SameCallback) { TEST_F(LayerCallbackTest, Merge_SameLayer) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction1, transaction2; CallbackHelper callback1, callback2; @@ -485,7 +485,7 @@ TEST_F(LayerCallbackTest, Merge_DifferentClients) { TEST_F(LayerCallbackTest, MultipleTransactions) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -510,7 +510,7 @@ TEST_F(LayerCallbackTest, MultipleTransactions) { TEST_F(LayerCallbackTest, MultipleTransactions_NoStateChange) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -541,7 +541,7 @@ TEST_F(LayerCallbackTest, MultipleTransactions_NoStateChange) { TEST_F(LayerCallbackTest, MultipleTransactions_SameStateChange) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -579,8 +579,8 @@ TEST_F(LayerCallbackTest, MultipleTransactions_SameStateChange) { TEST_F(LayerCallbackTest, MultipleTransactions_Merge) { sp layer1, layer2; - ASSERT_NO_FATAL_FAILURE(layer1 = createBufferStateLayer()); - ASSERT_NO_FATAL_FAILURE(layer2 = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer1 = createLayerWithBuffer()); + ASSERT_NO_FATAL_FAILURE(layer2 = createLayerWithBuffer()); Transaction transaction1, transaction2; CallbackHelper callback1, callback2; @@ -799,7 +799,7 @@ TEST_F(LayerCallbackTest, MultipleTransactions_Merge_DifferentClients_SameStateC // TODO (b/183181768): Fix & re-enable TEST_F(LayerCallbackTest, DISABLED_MultipleTransactions_SingleFrame) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -823,7 +823,7 @@ TEST_F(LayerCallbackTest, DISABLED_MultipleTransactions_SingleFrame) { TEST_F(LayerCallbackTest, MultipleTransactions_SingleFrame_NoStateChange) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); // Normal call to set up test Transaction transaction; @@ -858,7 +858,7 @@ TEST_F(LayerCallbackTest, MultipleTransactions_SingleFrame_NoStateChange) { TEST_F(LayerCallbackTest, MultipleTransactions_SingleFrame_SameStateChange) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); // Normal call to set up test Transaction transaction; @@ -901,7 +901,7 @@ TEST_F(LayerCallbackTest, MultipleTransactions_SingleFrame_SameStateChange) { TEST_F(LayerCallbackTest, DesiredPresentTime) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -925,7 +925,7 @@ TEST_F(LayerCallbackTest, DesiredPresentTime) { TEST_F(LayerCallbackTest, DesiredPresentTime_Multiple) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback1; @@ -971,7 +971,7 @@ TEST_F(LayerCallbackTest, DesiredPresentTime_Multiple) { // TODO (b/183181768): Fix & re-enable TEST_F(LayerCallbackTest, DISABLED_DesiredPresentTime_OutOfOrder) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback1; @@ -1015,7 +1015,7 @@ TEST_F(LayerCallbackTest, DISABLED_DesiredPresentTime_OutOfOrder) { TEST_F(LayerCallbackTest, DesiredPresentTime_Past) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -1039,7 +1039,7 @@ TEST_F(LayerCallbackTest, DesiredPresentTime_Past) { TEST_F(LayerCallbackTest, ExpectedPresentTime) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -1065,8 +1065,8 @@ TEST_F(LayerCallbackTest, ExpectedPresentTime) { // b202394221 TEST_F(LayerCallbackTest, EmptyBufferStateChanges) { sp bufferLayer, emptyBufferLayer; - ASSERT_NO_FATAL_FAILURE(bufferLayer = createBufferStateLayer()); - ASSERT_NO_FATAL_FAILURE(emptyBufferLayer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(bufferLayer = createLayerWithBuffer()); + ASSERT_NO_FATAL_FAILURE(emptyBufferLayer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; @@ -1120,7 +1120,7 @@ TEST_F(LayerCallbackTest, DISABLED_NonBufferLayerStateChanges) { TEST_F(LayerCallbackTest, CommitCallbackOffscreenLayer) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); sp offscreenLayer = createSurface(mClient, "Offscreen Layer", 0, 0, PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eFXSurfaceBufferState, layer.get()); @@ -1151,7 +1151,7 @@ TEST_F(LayerCallbackTest, CommitCallbackOffscreenLayer) { TEST_F(LayerCallbackTest, TransactionCommittedCallback_BSL) { sp layer; - ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer()); + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); Transaction transaction; CallbackHelper callback; diff --git a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp index bbe7ae80ce..bf7cae9091 100644 --- a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp +++ b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp @@ -399,7 +399,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetTransparentRegionHintOutOfBounds_Buffe .apply(); ASSERT_NO_FATAL_FAILURE( fillBufferQueueLayerColor(layerTransparent, Color::TRANSPARENT, 32, 32)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layerR, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layerR, Color::RED, 32, 32)); getScreenCapture()->expectColor(Rect(16, 16, 48, 48), Color::RED); } @@ -482,7 +482,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetColorBasic) { } } -// RED: Color layer base color and BufferQueueLayer/BufferStateLayer fill +// RED: Color layer base color and Layer buffer fill // BLUE: prior background color // GREEN: final background color // BLACK: no color or fill @@ -516,7 +516,7 @@ void LayerRenderTypeTransactionTest::setBackgroundColorHelper(uint32_t layerType case ISurfaceComposerClient::eFXSurfaceBufferState: ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", width, height, layerType)); if (bufferFill) { - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, fillColor, width, height)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, fillColor, width, height)); expectedColor = fillColor; } Transaction().setCrop(layer, Rect(0, 0, width, height)).apply(); @@ -832,7 +832,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetCropBasic_BufferState) { sp layer; ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); const Rect crop(8, 8, 24, 24); Transaction().setCrop(layer, crop).apply(); @@ -863,7 +863,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetCropEmpty_BufferState) { sp layer; ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); { SCOPED_TRACE("empty rect"); @@ -944,7 +944,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetCropWithTranslation_BufferState) { sp layer; ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); const Rect crop(8, 8, 24, 24); Transaction().setPosition(layer, 32, 32).setCrop(layer, crop).apply(); @@ -972,7 +972,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetFrameBasic_BufferState) { sp layer; ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); const Rect frame(8, 8, 24, 24); Transaction t; @@ -988,7 +988,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetFrameEmpty_BufferState) { sp layer; ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); Transaction t; { @@ -1014,7 +1014,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetFrameDefaultParentless_BufferState) { sp layer; ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 10, 10)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 10, 10)); // A layer with a buffer will have a computed size that matches the buffer size. auto shot = getScreenCapture(); @@ -1026,11 +1026,11 @@ TEST_P(LayerRenderTypeTransactionTest, SetFrameDefaultBSParent_BufferState) { sp parent, child; ASSERT_NO_FATAL_FAILURE( parent = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(parent, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(parent, Color::RED, 32, 32)); ASSERT_NO_FATAL_FAILURE( child = createLayer("test", 10, 10, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(child, Color::BLUE, 10, 10)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(child, Color::BLUE, 10, 10)); Transaction().reparent(child, parent).apply(); @@ -1047,7 +1047,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetFrameDefaultBQParent_BufferState) { ASSERT_NO_FATAL_FAILURE( child = createLayer("test", 10, 10, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(child, Color::BLUE, 10, 10)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(child, Color::BLUE, 10, 10)); Transaction().reparent(child, parent).apply(); @@ -1061,7 +1061,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetFrameUpdate_BufferState) { sp layer; ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); std::this_thread::sleep_for(500ms); @@ -1080,9 +1080,9 @@ TEST_P(LayerRenderTypeTransactionTest, SetFrameOutsideBounds_BufferState) { child = createLayer("test", 10, 10, ISurfaceComposerClient::eFXSurfaceBufferState)); Transaction().reparent(child, parent).apply(); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(parent, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(parent, Color::RED, 32, 32)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(child, Color::BLUE, 10, 10)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(child, Color::BLUE, 10, 10)); Rect childDst(0, 16, 32, 32); Transaction t; TransactionUtils::setFrame(t, child, Rect(0, 0, 10, 10), childDst); @@ -1099,7 +1099,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferBasic_BufferState) { ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); auto shot = getScreenCapture(); shot->expectColor(Rect(0, 0, 32, 32), Color::RED); @@ -1111,7 +1111,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferMultipleBuffers_BufferState) { ASSERT_NO_FATAL_FAILURE( layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); { SCOPED_TRACE("set buffer 1"); @@ -1120,7 +1120,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferMultipleBuffers_BufferState) { shot->expectBorder(Rect(0, 0, 32, 32), Color::BLACK); } - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::BLUE, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::BLUE, 32, 32)); { SCOPED_TRACE("set buffer 2"); @@ -1129,7 +1129,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferMultipleBuffers_BufferState) { shot->expectBorder(Rect(0, 0, 32, 32), Color::BLACK); } - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); { SCOPED_TRACE("set buffer 3"); @@ -1148,7 +1148,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferMultipleLayers_BufferState) { ASSERT_NO_FATAL_FAILURE( layer2 = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer1, Color::RED, 64, 64)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer1, Color::RED, 64, 64)); { SCOPED_TRACE("set layer 1 buffer red"); @@ -1156,7 +1156,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferMultipleLayers_BufferState) { shot->expectColor(Rect(0, 0, 64, 64), Color::RED); } - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer2, Color::BLUE, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer2, Color::BLUE, 32, 32)); { SCOPED_TRACE("set layer 2 buffer blue"); @@ -1166,7 +1166,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferMultipleLayers_BufferState) { shot->expectColor(Rect(0, 32, 32, 64), Color::RED); } - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer1, Color::GREEN, 64, 64)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer1, Color::GREEN, 64, 64)); { SCOPED_TRACE("set layer 1 buffer green"); auto shot = getScreenCapture(); @@ -1175,7 +1175,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetBufferMultipleLayers_BufferState) { shot->expectColor(Rect(0, 32, 32, 64), Color::GREEN); } - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer2, Color::WHITE, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer2, Color::WHITE, 32, 32)); { SCOPED_TRACE("set layer 2 buffer white"); diff --git a/services/surfaceflinger/tests/LayerTransactionTest.h b/services/surfaceflinger/tests/LayerTransactionTest.h index 0e8f3dd1d4..774c1d7b98 100644 --- a/services/surfaceflinger/tests/LayerTransactionTest.h +++ b/services/surfaceflinger/tests/LayerTransactionTest.h @@ -137,8 +137,8 @@ protected: postBufferQueueLayerBuffer(layer); } - virtual void fillBufferStateLayerColor(const sp& layer, const Color& color, - int32_t bufferWidth, int32_t bufferHeight) { + virtual void fillBufferLayerColor(const sp& layer, const Color& color, + int32_t bufferWidth, int32_t bufferHeight) { sp buffer = sp::make(static_cast(bufferWidth), static_cast(bufferHeight), PIXEL_FORMAT_RGBA_8888, @@ -159,7 +159,7 @@ protected: fillBufferQueueLayerColor(layer, color, bufferWidth, bufferHeight); break; case ISurfaceComposerClient::eFXSurfaceBufferState: - fillBufferStateLayerColor(layer, color, bufferWidth, bufferHeight); + fillBufferLayerColor(layer, color, bufferWidth, bufferHeight); break; default: ASSERT_TRUE(false) << "unsupported layer type: " << mLayerType; diff --git a/services/surfaceflinger/tests/LayerTransaction_test.cpp b/services/surfaceflinger/tests/LayerTransaction_test.cpp index c206e1ffd1..cbd54e7aa9 100644 --- a/services/surfaceflinger/tests/LayerTransaction_test.cpp +++ b/services/surfaceflinger/tests/LayerTransaction_test.cpp @@ -32,7 +32,7 @@ TEST_F(LayerTransactionTest, SetTransformToDisplayInverse_BufferState) { Transaction().setTransformToDisplayInverse(layer, false).apply(); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::GREEN, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::GREEN, 32, 32)); Transaction().setTransformToDisplayInverse(layer, true).apply(); } @@ -161,7 +161,7 @@ TEST_F(LayerTransactionTest, DISABLED_BufferQueueLayerMergeDamageRegionWhenDropp TEST_F(LayerTransactionTest, BufferTakesPriorityOverBlur) { sp layer; ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", 32, 32)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); Transaction().setBackgroundBlurRadius(layer, 5).apply(); { SCOPED_TRACE("BufferTakesPriorityOverBlur"); @@ -174,7 +174,7 @@ TEST_F(LayerTransactionTest, BufferTakesPriorityOverBlur) { TEST_F(LayerTransactionTest, BufferTakesPriorityOverColor) { sp layer; ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", 32, 32)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); Transaction().setColor(layer, {Color::GREEN.r, Color::GREEN.g, Color::GREEN.b}).apply(); { SCOPED_TRACE("BufferTakesPriorityOverColor"); diff --git a/services/surfaceflinger/tests/MirrorLayer_test.cpp b/services/surfaceflinger/tests/MirrorLayer_test.cpp index a921aa810e..faaef5d5ec 100644 --- a/services/surfaceflinger/tests/MirrorLayer_test.cpp +++ b/services/surfaceflinger/tests/MirrorLayer_test.cpp @@ -193,14 +193,14 @@ TEST_F(MirrorLayerTest, MirrorBufferLayer) { shot->expectColor(Rect(750, 750, 950, 950), Color::GREEN); } - sp bufferStateLayer = - createLayer("BufferStateLayer", 200, 200, ISurfaceComposerClient::eFXSurfaceBufferState, + sp layer = + createLayer("Layer", 200, 200, ISurfaceComposerClient::eFXSurfaceBufferState, mChildLayer.get()); - fillBufferStateLayerColor(bufferStateLayer, Color::BLUE, 200, 200); - Transaction().show(bufferStateLayer).apply(); + fillBufferLayerColor(layer, Color::BLUE, 200, 200); + Transaction().show(layer).apply(); { - SCOPED_TRACE("Initial Mirror BufferStateLayer"); + SCOPED_TRACE("Initial Mirror Layer"); auto shot = screenshot(); // Buffer mirror shot->expectColor(Rect(550, 550, 750, 750), Color::BLUE); @@ -208,9 +208,9 @@ TEST_F(MirrorLayerTest, MirrorBufferLayer) { shot->expectColor(Rect(750, 750, 950, 950), Color::GREEN); } - fillBufferStateLayerColor(bufferStateLayer, Color::WHITE, 200, 200); + fillBufferLayerColor(layer, Color::WHITE, 200, 200); { - SCOPED_TRACE("Update BufferStateLayer"); + SCOPED_TRACE("Update Layer"); auto shot = screenshot(); // Buffer mirror shot->expectColor(Rect(550, 550, 750, 750), Color::WHITE); @@ -218,9 +218,9 @@ TEST_F(MirrorLayerTest, MirrorBufferLayer) { shot->expectColor(Rect(750, 750, 950, 950), Color::GREEN); } - Transaction().reparent(bufferStateLayer, nullptr).apply(); + Transaction().reparent(layer, nullptr).apply(); { - SCOPED_TRACE("Removed BufferStateLayer"); + SCOPED_TRACE("Removed Layer"); auto shot = screenshot(); // Buffer mirror shot->expectColor(Rect(550, 550, 750, 750), Color::GREEN); @@ -283,7 +283,7 @@ TEST_F(MirrorLayerTest, OffscreenMirrorScreenshot) { sp grandchild = createLayer("Grandchild layer", 50, 50, ISurfaceComposerClient::eFXSurfaceBufferState, mChildLayer.get()); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(grandchild, Color::BLUE, 50, 50)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(grandchild, Color::BLUE, 50, 50)); Rect childBounds = Rect(50, 50, 450, 450); asTransaction([&](Transaction& t) { diff --git a/services/surfaceflinger/tests/ScreenCapture_test.cpp b/services/surfaceflinger/tests/ScreenCapture_test.cpp index d78c8a9ee0..3a5e5328e2 100644 --- a/services/surfaceflinger/tests/ScreenCapture_test.cpp +++ b/services/surfaceflinger/tests/ScreenCapture_test.cpp @@ -371,7 +371,7 @@ TEST_F(ScreenCaptureTest, CaptureBufferLayerWithoutBufferFails) { ScreenCaptureResults captureResults; ASSERT_EQ(BAD_VALUE, ScreenCapture::captureLayers(args, captureResults)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(child, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(child, Color::RED, 32, 32)); SurfaceComposerClient::Transaction().apply(true); ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(args, captureResults)); ScreenCapture sc(captureResults.buffer, captureResults.capturedHdrLayers); @@ -449,8 +449,8 @@ TEST_F(ScreenCaptureTest, CaptureCrop) { ISurfaceComposerClient::eFXSurfaceBufferState, redLayer.get()); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(redLayer, Color::RED, 60, 60)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(blueLayer, Color::BLUE, 30, 30)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(redLayer, Color::RED, 60, 60)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(blueLayer, Color::BLUE, 30, 30)); SurfaceComposerClient::Transaction() .setLayer(redLayer, INT32_MAX - 1) @@ -484,8 +484,8 @@ TEST_F(ScreenCaptureTest, CaptureSize) { ISurfaceComposerClient::eFXSurfaceBufferState, redLayer.get()); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(redLayer, Color::RED, 60, 60)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(blueLayer, Color::BLUE, 30, 30)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(redLayer, Color::RED, 60, 60)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(blueLayer, Color::BLUE, 30, 30)); SurfaceComposerClient::Transaction() .setLayer(redLayer, INT32_MAX - 1) @@ -549,8 +549,8 @@ TEST_F(ScreenCaptureTest, CaptureSecureLayer) { ISurfaceComposerClient::eSecure | ISurfaceComposerClient::eFXSurfaceBufferState, redLayer.get()); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(redLayer, Color::RED, 60, 60)); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(secureLayer, Color::BLUE, 30, 30)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(redLayer, Color::RED, 60, 60)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(secureLayer, Color::BLUE, 30, 30)); auto redLayerHandle = redLayer->getHandle(); Transaction() @@ -803,7 +803,7 @@ TEST_F(ScreenCaptureTest, CaptureWithGrayscale) { ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState, mBGSurfaceControl.get())); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); Transaction().show(layer).setLayer(layer, INT32_MAX).apply(); LayerCaptureArgs captureArgs; @@ -825,7 +825,7 @@ TEST_F(ScreenCaptureTest, CaptureWithGrayscale) { mCapture->expectColor(Rect(0, 0, 32, 32), Color{expectedColor, expectedColor, expectedColor, 255}, tolerance); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::BLUE, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::BLUE, 32, 32)); ScreenCapture::captureLayers(&mCapture, captureArgs); expectedColor = luminance.b * 255; @@ -838,7 +838,7 @@ TEST_F(ScreenCaptureTest, CaptureOffscreen) { ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState, mBGSurfaceControl.get())); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::RED, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::RED, 32, 32)); Transaction().show(layer).hide(mFGSurfaceControl).reparent(layer, nullptr).apply(); @@ -865,7 +865,7 @@ TEST_F(ScreenCaptureTest, CaptureNonHdrLayer) { ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState, mBGSurfaceControl.get())); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::BLACK, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::BLACK, 32, 32)); Transaction() .show(layer) .setLayer(layer, INT32_MAX) @@ -885,7 +885,7 @@ TEST_F(ScreenCaptureTest, CaptureHdrLayer) { ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState, mBGSurfaceControl.get())); - ASSERT_NO_FATAL_FAILURE(fillBufferStateLayerColor(layer, Color::BLACK, 32, 32)); + ASSERT_NO_FATAL_FAILURE(fillBufferLayerColor(layer, Color::BLACK, 32, 32)); Transaction() .show(layer) .setLayer(layer, INT32_MAX) diff --git a/services/surfaceflinger/tests/unittests/CachingTest.cpp b/services/surfaceflinger/tests/unittests/CachingTest.cpp index 9082a222fd..c1cbbfb4ef 100644 --- a/services/surfaceflinger/tests/unittests/CachingTest.cpp +++ b/services/surfaceflinger/tests/unittests/CachingTest.cpp @@ -20,7 +20,8 @@ #include #include #include -#include "BufferStateLayer.h" + +#include "HwcSlotGenerator.h" namespace android { diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 9485f48eea..7148c117c5 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -491,7 +491,7 @@ struct BaseLayerProperties { static constexpr IComposerClient::BlendMode BLENDMODE = IComposerClient::BlendMode::PREMULTIPLIED; - static void setupLatchedBuffer(CompositionTest* test, sp layer) { + static void setupLatchedBuffer(CompositionTest* test, sp layer) { Mock::VerifyAndClear(test->mRenderEngine); const auto buffer = std::make_shared< @@ -515,7 +515,7 @@ struct BaseLayerProperties { Mock::VerifyAndClear(test->mRenderEngine); } - static void setupLayerState(CompositionTest* test, sp layer) { + static void setupLayerState(CompositionTest* test, sp layer) { setupLatchedBuffer(test, layer); } @@ -699,7 +699,7 @@ struct SidebandLayerProperties : public BaseLayerProperties; static constexpr IComposerClient::BlendMode BLENDMODE = IComposerClient::BlendMode::NONE; - static void setupLayerState(CompositionTest* test, sp layer) { + static void setupLayerState(CompositionTest* test, sp layer) { sp stream = NativeHandle::create(reinterpret_cast(DEFAULT_SIDEBAND_STREAM), false); @@ -780,14 +780,14 @@ struct SecureLayerProperties : public CommonSecureLayerProperties { using Base = BaseLayerProperties; - static void setupLayerState(CompositionTest* test, sp layer) { + static void setupLayerState(CompositionTest* test, sp layer) { Base::setupLayerState(test, layer); test->mFlinger.setLayerPotentialCursor(layer, true); } }; struct NoLayerVariant { - using FlingerLayerType = sp; + using FlingerLayerType = sp; static FlingerLayerType createLayer(CompositionTest*) { return FlingerLayerType(); } static void injectLayer(CompositionTest*, FlingerLayerType) {} @@ -892,17 +892,17 @@ struct EffectLayerVariant : public BaseLayerVariant { template struct BufferLayerVariant : public BaseLayerVariant { using Base = BaseLayerVariant; - using FlingerLayerType = sp; + using FlingerLayerType = sp; static FlingerLayerType createLayer(CompositionTest* test) { test->mFlinger.mutableTexturePool().push_back(DEFAULT_TEXTURE_ID); FlingerLayerType layer = - Base::template createLayerWithFactory(test, [test]() { + Base::template createLayerWithFactory(test, [test]() { LayerCreationArgs args(test->mFlinger.flinger(), sp(), "test-layer", LayerProperties::LAYER_FLAGS, LayerMetadata()); args.textureName = test->mFlinger.mutableTexturePool().back(); - return sp::make(args); + return sp::make(args); }); LayerProperties::setupLayerState(test, layer); diff --git a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp index 9789df5c3b..1cd9e49051 100644 --- a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp +++ b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp @@ -24,7 +24,6 @@ #include #include -#include "BufferStateLayer.h" #include "FpsReporter.h" #include "Layer.h" #include "TestableSurfaceFlinger.h" @@ -79,7 +78,7 @@ protected: static constexpr int32_t PRIORITY_UNSET = -1; void setupScheduler(); - sp createBufferStateLayer(LayerMetadata metadata); + sp createBufferStateLayer(LayerMetadata metadata); TestableSurfaceFlinger mFlinger; mock::FrameTimeline mFrameTimeline = @@ -115,10 +114,10 @@ FpsReporterTest::~FpsReporterTest() { ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); } -sp FpsReporterTest::createBufferStateLayer(LayerMetadata metadata = {}) { +sp FpsReporterTest::createBufferStateLayer(LayerMetadata metadata = {}) { sp client; LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", LAYER_FLAGS, metadata); - return sp::make(args); + return sp::make(args); } void FpsReporterTest::setupScheduler() { diff --git a/services/surfaceflinger/tests/unittests/GameModeTest.cpp b/services/surfaceflinger/tests/unittests/GameModeTest.cpp index cd857c3d34..29aa7171ba 100644 --- a/services/surfaceflinger/tests/unittests/GameModeTest.cpp +++ b/services/surfaceflinger/tests/unittests/GameModeTest.cpp @@ -53,11 +53,10 @@ public: ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); } - sp createBufferStateLayer() { + sp createLayer() { sp client; - LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", 0, - LayerMetadata()); - return sp::make(args); + LayerCreationArgs args(mFlinger.flinger(), client, "layer", 0, LayerMetadata()); + return sp::make(args); } void setupScheduler() { @@ -108,9 +107,9 @@ public: }; TEST_F(GameModeTest, SetGameModeSetsForAllCurrentChildren) { - sp rootLayer = createBufferStateLayer(); - sp childLayer1 = createBufferStateLayer(); - sp childLayer2 = createBufferStateLayer(); + sp rootLayer = createLayer(); + sp childLayer1 = createLayer(); + sp childLayer2 = createLayer(); rootLayer->addChild(childLayer1); rootLayer->addChild(childLayer2); rootLayer->setGameModeForTree(GameMode::Performance); @@ -121,8 +120,8 @@ TEST_F(GameModeTest, SetGameModeSetsForAllCurrentChildren) { } TEST_F(GameModeTest, AddChildAppliesGameModeFromParent) { - sp rootLayer = createBufferStateLayer(); - sp childLayer = createBufferStateLayer(); + sp rootLayer = createLayer(); + sp childLayer = createLayer(); rootLayer->setGameModeForTree(GameMode::Performance); rootLayer->addChild(childLayer); @@ -131,8 +130,8 @@ TEST_F(GameModeTest, AddChildAppliesGameModeFromParent) { } TEST_F(GameModeTest, RemoveChildResetsGameMode) { - sp rootLayer = createBufferStateLayer(); - sp childLayer = createBufferStateLayer(); + sp rootLayer = createLayer(); + sp childLayer = createLayer(); rootLayer->setGameModeForTree(GameMode::Performance); rootLayer->addChild(childLayer); @@ -144,9 +143,9 @@ TEST_F(GameModeTest, RemoveChildResetsGameMode) { } TEST_F(GameModeTest, ReparentingDoesNotOverrideMetadata) { - sp rootLayer = createBufferStateLayer(); - sp childLayer1 = createBufferStateLayer(); - sp childLayer2 = createBufferStateLayer(); + sp rootLayer = createLayer(); + sp childLayer1 = createLayer(); + sp childLayer2 = createLayer(); rootLayer->setGameModeForTree(GameMode::Standard); rootLayer->addChild(childLayer1); diff --git a/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp b/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp index 14304d1199..ee42e19c34 100644 --- a/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp +++ b/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp @@ -29,7 +29,7 @@ sp BufferStateLayerFactory::createLayer(TestableSurfaceFlinger& flinger) sp client; LayerCreationArgs args(flinger.flinger(), client, "buffer-state-layer", LAYER_FLAGS, LayerMetadata()); - return sp::make(args); + return sp::make(args); } sp EffectLayerFactory::createLayer(TestableSurfaceFlinger& flinger) { diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp index abf1786c0d..ac63a0edbd 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp @@ -21,7 +21,6 @@ #include #include -#include "BufferStateLayer.h" #include "Layer.h" #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockComposer.h" @@ -58,7 +57,7 @@ protected: static constexpr int32_t PRIORITY_UNSET = -1; void setupScheduler(); - sp createBufferStateLayer(); + sp createBufferStateLayer(); sp createEffectLayer(); void setParent(Layer* child, Layer* parent); @@ -87,12 +86,11 @@ RefreshRateSelectionTest::~RefreshRateSelectionTest() { ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); } - -sp RefreshRateSelectionTest::createBufferStateLayer() { +sp RefreshRateSelectionTest::createBufferStateLayer() { sp client; LayerCreationArgs args(mFlinger.flinger(), client, "buffer-queue-layer", LAYER_FLAGS, LayerMetadata()); - return sp::make(args); + return sp::make(args); } sp RefreshRateSelectionTest::createEffectLayer() { diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp index 51c6beabae..dfcfd912f9 100644 --- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp +++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp @@ -24,7 +24,6 @@ // TODO(b/129481165): remove the #pragma below and fix conversion issues #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wconversion" -#include "BufferStateLayer.h" #include "Layer.h" // TODO(b/129481165): remove the #pragma below and fix conversion issues #pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 1ce6e18d69..983fac231c 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -30,7 +30,6 @@ #include #include -#include "BufferStateLayer.h" #include "DisplayDevice.h" #include "FakeVsyncConfiguration.h" #include "FrameTracer/FrameTracer.h" @@ -120,9 +119,7 @@ public: return compositionengine::impl::createCompositionEngine(); } - sp createBufferStateLayer(const LayerCreationArgs&) override { - return nullptr; - } + sp createBufferStateLayer(const LayerCreationArgs&) override { return nullptr; } sp createEffectLayer(const LayerCreationArgs&) override { return nullptr; } diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index efb9e0c5ba..b493d113b7 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -347,8 +347,7 @@ public: state.state.bufferData->acquireFence = std::move(fence); state.state.layerId = layerId; state.state.surface = - sp::make( - LayerCreationArgs(mFlinger.flinger(), nullptr, "TestLayer", 0, {})) + sp::make(LayerCreationArgs(mFlinger.flinger(), nullptr, "TestLayer", 0, {})) ->getHandle(); state.state.bufferData->flags = BufferData::BufferDataChange::fenceChanged; diff --git a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp index cd64325961..1173d1c876 100644 --- a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp @@ -56,11 +56,11 @@ public: ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); } - sp createBufferStateLayer() { + sp createLayer() { sp client; LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", 0, LayerMetadata()); - return sp::make(args); + return sp::make(args); } void commitTransaction(Layer* layer) { @@ -101,7 +101,7 @@ public: FenceToFenceTimeMap fenceFactory; void BLASTTransactionSendsFrameTracerEvents() { - sp layer = createBufferStateLayer(); + sp layer = createLayer(); sp fence(sp::make()); int32_t layerId = layer->getSequence(); diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp index d5823c3089..ae03db43a7 100644 --- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp @@ -56,11 +56,10 @@ public: ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); } - sp createBufferStateLayer() { + sp createLayer() { sp client; - LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", 0, - LayerMetadata()); - return sp::make(args); + LayerCreationArgs args(mFlinger.flinger(), client, "layer", 0, LayerMetadata()); + return sp::make(args); } void commitTransaction(Layer* layer) { @@ -101,7 +100,7 @@ public: FenceToFenceTimeMap fenceFactory; void PresentedSurfaceFrameForBufferlessTransaction() { - sp layer = createBufferStateLayer(); + sp layer = createLayer(); FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; @@ -116,7 +115,7 @@ public: } void PresentedSurfaceFrameForBufferTransaction() { - sp layer = createBufferStateLayer(); + sp layer = createLayer(); sp fence(sp::make()); auto acquireFence = fenceFactory.createFenceTimeForTest(fence); BufferData bufferData; @@ -150,7 +149,7 @@ public: } void DroppedSurfaceFrameForBufferTransaction() { - sp layer = createBufferStateLayer(); + sp layer = createLayer(); sp fence1(sp::make()); auto acquireFence1 = fenceFactory.createFenceTimeForTest(fence1); @@ -208,7 +207,7 @@ public: } void BufferlessSurfaceFramePromotedToBufferSurfaceFrame() { - sp layer = createBufferStateLayer(); + sp layer = createLayer(); FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; @@ -249,7 +248,7 @@ public: } void BufferlessSurfaceFrameNotCreatedIfBufferSufaceFrameExists() { - sp layer = createBufferStateLayer(); + sp layer = createLayer(); sp fence(sp::make()); auto acquireFence = fenceFactory.createFenceTimeForTest(fence); BufferData bufferData; @@ -275,7 +274,7 @@ public: } void MultipleSurfaceFramesPresentedTogether() { - sp layer = createBufferStateLayer(); + sp layer = createLayer(); FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; @@ -336,7 +335,7 @@ public: } void PendingSurfaceFramesRemovedAfterClassification() { - sp layer = createBufferStateLayer(); + sp layer = createLayer(); sp fence1(sp::make()); auto acquireFence1 = fenceFactory.createFenceTimeForTest(fence1); @@ -388,7 +387,7 @@ public: } void BufferSurfaceFrame_ReplaceValidTokenBufferWithInvalidTokenBuffer() { - sp layer = createBufferStateLayer(); + sp layer = createLayer(); sp fence1(sp::make()); auto acquireFence1 = fenceFactory.createFenceTimeForTest(fence1); @@ -477,7 +476,7 @@ public: } void MultipleCommitsBeforeLatch() { - sp layer = createBufferStateLayer(); + sp layer = createLayer(); uint32_t surfaceFramesPendingClassification = 0; std::vector> bufferlessSurfaceFrames; for (int i = 0; i < 10; i += 2) { diff --git a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp index 45ebb8532c..da87f1db17 100644 --- a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp +++ b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp @@ -22,7 +22,6 @@ #include #include -#include "BufferStateLayer.h" #include "TestableSurfaceFlinger.h" #include "TunnelModeEnabledReporter.h" #include "mock/DisplayHardware/MockComposer.h" @@ -64,7 +63,7 @@ protected: void setupScheduler(); void setupComposer(uint32_t virtualDisplayCount); - sp createBufferStateLayer(LayerMetadata metadata); + sp createBufferStateLayer(LayerMetadata metadata); TestableSurfaceFlinger mFlinger; Hwc2::mock::Composer* mComposer = nullptr; @@ -95,11 +94,10 @@ TunnelModeEnabledReporterTest::~TunnelModeEnabledReporterTest() { mTunnelModeEnabledReporter->removeListener(mTunnelModeEnabledListener); } -sp TunnelModeEnabledReporterTest::createBufferStateLayer( - LayerMetadata metadata = {}) { +sp TunnelModeEnabledReporterTest::createBufferStateLayer(LayerMetadata metadata = {}) { sp client; LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", LAYER_FLAGS, metadata); - return sp::make(args); + return sp::make(args); } void TunnelModeEnabledReporterTest::setupScheduler() { diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h index 48d05cbcba..0d94f4c644 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockLayer.h +++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h @@ -18,14 +18,12 @@ #include -#include "BufferStateLayer.h" - namespace android::mock { -class MockLayer : public BufferStateLayer { +class MockLayer : public Layer { public: MockLayer(SurfaceFlinger* flinger, std::string name) - : BufferStateLayer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) { + : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) { EXPECT_CALL(*this, getDefaultFrameRateCompatibility()) .WillOnce(testing::Return(scheduler::LayerInfo::FrameRateCompatibility::Default)); } -- GitLab From bedb44bb15cd11689b96770a45eccf475778e9eb Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 2 Aug 2022 21:47:40 +0000 Subject: [PATCH 0300/1310] SF: Populate RE layer settings from layer snapshot and CE layer state Make it easier to break FE dependencies to CE by populating RE layer settings from a layer snapshot and CE layer composition state. Test: presubmit Test: go/wm-smoke Bug: 238781169 Change-Id: I92bdc0a0f605c13e2dc1465c8d1ddfd17e554633 --- services/surfaceflinger/Layer.cpp | 311 ++++++++++-------- services/surfaceflinger/Layer.h | 39 ++- services/surfaceflinger/SurfaceFlinger.cpp | 5 + .../tests/unittests/TestableSurfaceFlinger.h | 2 +- 4 files changed, 208 insertions(+), 149 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index e690aaf691..0771e1055b 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -164,7 +164,6 @@ Layer::Layer(const LayerCreationArgs& args) mLayerCreationFlags(args.flags), mBorderEnabled(false), mTextureName(args.textureName), - mCompositionState{mFlinger->getCompositionEngine().createLayerFECompositionState()}, mHwcSlotGenerator(sp::make()) { ALOGV("Creating Layer %s", getDebugName()); @@ -238,6 +237,12 @@ Layer::Layer(const LayerCreationArgs& args) mPotentialCursor = args.flags & ISurfaceComposerClient::eCursorWindow; mProtectedByApp = args.flags & ISurfaceComposerClient::eProtectedByApp; mDrawingState.dataspace = ui::Dataspace::V0_SRGB; + + mSnapshot->sequence = sequence; + mSnapshot->name = getDebugName(); + mSnapshot->textureName = mTextureName; + mSnapshot->premultipliedAlpha = mPremultipliedAlpha; + mSnapshot->transform = {}; } void Layer::onFirstRef() { @@ -503,40 +508,40 @@ void Layer::prepareBasicGeometryCompositionState() { : Hwc2::IComposerClient::BlendMode::COVERAGE; } - auto* compositionState = editCompositionState(); - compositionState->outputFilter = getOutputFilter(); - compositionState->isVisible = isVisible(); - compositionState->isOpaque = opaque && !usesRoundedCorners && alpha == 1.f; - compositionState->shadowRadius = mEffectiveShadowRadius; + auto* snapshot = editLayerSnapshot(); + snapshot->outputFilter = getOutputFilter(); + snapshot->isVisible = isVisible(); + snapshot->isOpaque = opaque && !usesRoundedCorners && alpha == 1.f; + snapshot->shadowRadius = mEffectiveShadowRadius; - compositionState->contentDirty = contentDirty; + snapshot->contentDirty = contentDirty; contentDirty = false; - compositionState->geomLayerBounds = mBounds; - compositionState->geomLayerTransform = getTransform(); - compositionState->geomInverseLayerTransform = compositionState->geomLayerTransform.inverse(); - compositionState->transparentRegionHint = getActiveTransparentRegion(drawingState); + snapshot->geomLayerBounds = mBounds; + snapshot->geomLayerTransform = getTransform(); + snapshot->geomInverseLayerTransform = snapshot->geomLayerTransform.inverse(); + snapshot->transparentRegionHint = getActiveTransparentRegion(drawingState); - compositionState->blendMode = static_cast(blendMode); - compositionState->alpha = alpha; - compositionState->backgroundBlurRadius = drawingState.backgroundBlurRadius; - compositionState->blurRegions = drawingState.blurRegions; - compositionState->stretchEffect = getStretchEffect(); + snapshot->blendMode = static_cast(blendMode); + snapshot->alpha = alpha; + snapshot->backgroundBlurRadius = drawingState.backgroundBlurRadius; + snapshot->blurRegions = drawingState.blurRegions; + snapshot->stretchEffect = getStretchEffect(); } void Layer::prepareGeometryCompositionState() { const auto& drawingState{getDrawingState()}; - auto* compositionState = editCompositionState(); + auto* snapshot = editLayerSnapshot(); - compositionState->geomBufferSize = getBufferSize(drawingState); - compositionState->geomContentCrop = getBufferCrop(); - compositionState->geomCrop = getCrop(drawingState); - compositionState->geomBufferTransform = getBufferTransform(); - compositionState->geomBufferUsesDisplayInverseTransform = getTransformToDisplayInverse(); - compositionState->geomUsesSourceCrop = usesSourceCrop(); - compositionState->isSecure = isSecure(); + snapshot->geomBufferSize = getBufferSize(drawingState); + snapshot->geomContentCrop = getBufferCrop(); + snapshot->geomCrop = getCrop(drawingState); + snapshot->geomBufferTransform = getBufferTransform(); + snapshot->geomBufferUsesDisplayInverseTransform = getTransformToDisplayInverse(); + snapshot->geomUsesSourceCrop = usesSourceCrop(); + snapshot->isSecure = isSecure(); - compositionState->metadata.clear(); + snapshot->metadata.clear(); const auto& supportedMetadata = mFlinger->getHwComposer().getSupportedLayerGenericMetadata(); for (const auto& [key, mandatory] : supportedMetadata) { const auto& genericLayerMetadataCompatibilityMap = @@ -552,45 +557,45 @@ void Layer::prepareGeometryCompositionState() { continue; } - compositionState->metadata - .emplace(key, compositionengine::GenericLayerMetadataEntry{mandatory, it->second}); + snapshot->metadata.emplace(key, + compositionengine::GenericLayerMetadataEntry{mandatory, + it->second}); } } void Layer::preparePerFrameCompositionState() { const auto& drawingState{getDrawingState()}; - auto* compositionState = editCompositionState(); + auto* snapshot = editLayerSnapshot(); - compositionState->forceClientComposition = false; + snapshot->forceClientComposition = false; - compositionState->isColorspaceAgnostic = isColorSpaceAgnostic(); - compositionState->dataspace = getDataSpace(); - compositionState->colorTransform = getColorTransform(); - compositionState->colorTransformIsIdentity = !hasColorTransform(); - compositionState->surfaceDamage = surfaceDamageRegion; - compositionState->hasProtectedContent = isProtected(); - compositionState->dimmingEnabled = isDimmingEnabled(); + snapshot->isColorspaceAgnostic = isColorSpaceAgnostic(); + snapshot->dataspace = getDataSpace(); + snapshot->colorTransform = getColorTransform(); + snapshot->colorTransformIsIdentity = !hasColorTransform(); + snapshot->surfaceDamage = surfaceDamageRegion; + snapshot->hasProtectedContent = isProtected(); + snapshot->dimmingEnabled = isDimmingEnabled(); const bool usesRoundedCorners = hasRoundedCorners(); - compositionState->isOpaque = - isOpaque(drawingState) && !usesRoundedCorners && getAlpha() == 1.0_hf; + snapshot->isOpaque = isOpaque(drawingState) && !usesRoundedCorners && getAlpha() == 1.0_hf; // Force client composition for special cases known only to the front-end. // Rounded corners no longer force client composition, since we may use a // hole punch so that the layer will appear to have rounded corners. if (isHdrY410() || drawShadows() || drawingState.blurRegions.size() > 0 || - compositionState->stretchEffect.hasEffect()) { - compositionState->forceClientComposition = true; + snapshot->stretchEffect.hasEffect()) { + snapshot->forceClientComposition = true; } // If there are no visible region changes, we still need to update blur parameters. - compositionState->blurRegions = drawingState.blurRegions; - compositionState->backgroundBlurRadius = drawingState.backgroundBlurRadius; + snapshot->blurRegions = drawingState.blurRegions; + snapshot->backgroundBlurRadius = drawingState.backgroundBlurRadius; // Layer framerate is used in caching decisions. // Retrieve it from the scheduler which maintains an instance of LayerHistory, and store it in // LayerFECompositionState where it would be visible to Flattener. - compositionState->fps = mFlinger->getLayerFramerate(systemTime(), getSequence()); + snapshot->fps = mFlinger->getLayerFramerate(systemTime(), getSequence()); if (hasBufferOrSidebandStream()) { preparePerFrameBufferCompositionState(); @@ -601,41 +606,41 @@ void Layer::preparePerFrameCompositionState() { void Layer::preparePerFrameBufferCompositionState() { // Sideband layers - auto* compositionState = editCompositionState(); - if (compositionState->sidebandStream.get() && !compositionState->sidebandStreamHasFrame) { - compositionState->compositionType = + auto* snapshot = editLayerSnapshot(); + if (snapshot->sidebandStream.get() && !snapshot->sidebandStreamHasFrame) { + snapshot->compositionType = aidl::android::hardware::graphics::composer3::Composition::SIDEBAND; return; } else if ((mDrawingState.flags & layer_state_t::eLayerIsDisplayDecoration) != 0) { - compositionState->compositionType = + snapshot->compositionType = aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION; } else { // Normal buffer layers - compositionState->hdrMetadata = mBufferInfo.mHdrMetadata; - compositionState->compositionType = mPotentialCursor + snapshot->hdrMetadata = mBufferInfo.mHdrMetadata; + snapshot->compositionType = mPotentialCursor ? aidl::android::hardware::graphics::composer3::Composition::CURSOR : aidl::android::hardware::graphics::composer3::Composition::DEVICE; } - compositionState->buffer = getBuffer(); - compositionState->bufferSlot = (mBufferInfo.mBufferSlot == BufferQueue::INVALID_BUFFER_SLOT) + snapshot->buffer = getBuffer(); + snapshot->bufferSlot = (mBufferInfo.mBufferSlot == BufferQueue::INVALID_BUFFER_SLOT) ? 0 : mBufferInfo.mBufferSlot; - compositionState->acquireFence = mBufferInfo.mFence; - compositionState->frameNumber = mBufferInfo.mFrameNumber; - compositionState->sidebandStreamHasFrame = false; + snapshot->acquireFence = mBufferInfo.mFence; + snapshot->frameNumber = mBufferInfo.mFrameNumber; + snapshot->sidebandStreamHasFrame = false; } void Layer::preparePerFrameEffectsCompositionState() { - auto* compositionState = editCompositionState(); - compositionState->color = getColor(); - compositionState->compositionType = + auto* snapshot = editLayerSnapshot(); + snapshot->color = getColor(); + snapshot->compositionType = aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR; } void Layer::prepareCursorCompositionState() { const State& drawingState{getDrawingState()}; - auto* compositionState = editCompositionState(); + auto* snapshot = editLayerSnapshot(); // Apply the layer's transform, followed by the display's global transform // Here we're guaranteed that the layer's transform preserves rects @@ -644,7 +649,7 @@ void Layer::prepareCursorCompositionState() { Rect bounds = reduce(win, getActiveTransparentRegion(drawingState)); Rect frame(getTransform().transform(bounds)); - compositionState->cursorFrame = frame; + snapshot->cursorFrame = frame; } sp Layer::asLayerFE() const { @@ -686,31 +691,30 @@ std::optional Layer::prepareClientCom compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { ATRACE_CALL(); - if (!getCompositionState()) { + const auto* snapshot = getLayerSnapshot(); + if (!snapshot) { return {}; } - FloatRect bounds = getBounds(); - half alpha = getAlpha(); - compositionengine::LayerFE::LayerSettings layerSettings; - layerSettings.geometry.boundaries = bounds; - layerSettings.geometry.positionTransform = getTransform().asMatrix4(); + layerSettings.geometry.boundaries = + reduce(snapshot->geomLayerBounds, snapshot->transparentRegionHint); + layerSettings.geometry.positionTransform = snapshot->geomLayerTransform.asMatrix4(); // skip drawing content if the targetSettings indicate the content will be occluded const bool drawContent = targetSettings.realContentIsVisible || targetSettings.clearContent; layerSettings.skipContentDraw = !drawContent; if (hasColorTransform()) { - layerSettings.colorTransform = getColorTransform(); + layerSettings.colorTransform = snapshot->colorTransform; } - const auto roundedCornerState = getRoundedCornerState(); + const auto& roundedCornerState = snapshot->roundedCorner; layerSettings.geometry.roundedCornersRadius = roundedCornerState.radius; layerSettings.geometry.roundedCornersCrop = roundedCornerState.cropRect; - layerSettings.alpha = alpha; - layerSettings.sourceDataspace = getDataSpace(); + layerSettings.alpha = snapshot->alpha; + layerSettings.sourceDataspace = snapshot->dataspace; // Override the dataspace transfer from 170M to sRGB if the device configuration requests this. // We do this here instead of in buffer info so that dumpsys can still report layers that are @@ -727,26 +731,24 @@ std::optional Layer::prepareClientCom layerSettings.whitePointNits = targetSettings.whitePointNits; switch (targetSettings.blurSetting) { case LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled: - layerSettings.backgroundBlurRadius = getBackgroundBlurRadius(); - layerSettings.blurRegions = getBlurRegions(); - layerSettings.blurRegionTransform = - getActiveTransform(getDrawingState()).inverse().asMatrix4(); + layerSettings.backgroundBlurRadius = snapshot->backgroundBlurRadius; + layerSettings.blurRegions = snapshot->blurRegions; + layerSettings.blurRegionTransform = snapshot->geomInverseLayerTransform.asMatrix4(); break; case LayerFE::ClientCompositionTargetSettings::BlurSetting::BackgroundBlurOnly: - layerSettings.backgroundBlurRadius = getBackgroundBlurRadius(); + layerSettings.backgroundBlurRadius = snapshot->backgroundBlurRadius; break; case LayerFE::ClientCompositionTargetSettings::BlurSetting::BlurRegionsOnly: - layerSettings.blurRegions = getBlurRegions(); - layerSettings.blurRegionTransform = - getActiveTransform(getDrawingState()).inverse().asMatrix4(); + layerSettings.blurRegions = snapshot->blurRegions; + layerSettings.blurRegionTransform = snapshot->geomInverseLayerTransform.asMatrix4(); break; case LayerFE::ClientCompositionTargetSettings::BlurSetting::Disabled: default: break; } - layerSettings.stretchEffect = getStretchEffect(); + layerSettings.stretchEffect = snapshot->stretchEffect; // Record the name of the layer for debugging further down the stack. - layerSettings.name = getName(); + layerSettings.name = snapshot->name; if (hasEffect() && !hasBufferOrSidebandStream()) { prepareEffectsClientComposition(layerSettings, targetSettings); @@ -767,7 +769,7 @@ void Layer::prepareClearClientComposition(LayerFE::LayerSettings& layerSettings, // If layer is blacked out, force alpha to 1 so that we draw a black color layer. layerSettings.alpha = blackout ? 1.0f : 0.0f; - layerSettings.name = getName(); + layerSettings.name = getLayerSnapshot()->name; } void Layer::prepareEffectsClientComposition( @@ -785,39 +787,41 @@ void Layer::prepareEffectsClientComposition( void Layer::prepareBufferStateClientComposition( compositionengine::LayerFE::LayerSettings& layerSettings, compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { - if (CC_UNLIKELY(!mBufferInfo.mBuffer)) { - // For surfaceview of tv sideband, there is no activeBuffer - // in bufferqueue, we need return LayerSettings. + ATRACE_CALL(); + const auto* snapshot = getLayerSnapshot(); + if (CC_UNLIKELY(!snapshot->externalTexture)) { + // If there is no buffer for the layer or we have sidebandstream where there is no + // activeBuffer, then we need to return LayerSettings. return; } - const bool blackOutLayer = (isProtected() && !targetSettings.supportsProtectedContent) || - ((isSecure() || isProtected()) && !targetSettings.isSecure); + const bool blackOutLayer = + (snapshot->hasProtectedContent && !targetSettings.supportsProtectedContent) || + ((snapshot->isSecure || snapshot->hasProtectedContent) && !targetSettings.isSecure); const bool bufferCanBeUsedAsHwTexture = - mBufferInfo.mBuffer->getUsage() & GraphicBuffer::USAGE_HW_TEXTURE; + snapshot->externalTexture->getUsage() & GraphicBuffer::USAGE_HW_TEXTURE; if (blackOutLayer || !bufferCanBeUsedAsHwTexture) { ALOGE_IF(!bufferCanBeUsedAsHwTexture, "%s is blacked out as buffer is not gpu readable", - mName.c_str()); + snapshot->name.c_str()); prepareClearClientComposition(layerSettings, true /* blackout */); return; } - const State& s(getDrawingState()); - layerSettings.source.buffer.buffer = mBufferInfo.mBuffer; - layerSettings.source.buffer.isOpaque = isOpaque(s); - layerSettings.source.buffer.fence = mBufferInfo.mFence; - layerSettings.source.buffer.textureName = mTextureName; - layerSettings.source.buffer.usePremultipliedAlpha = getPremultipledAlpha(); - layerSettings.source.buffer.isY410BT2020 = isHdrY410(); - bool hasSmpte2086 = mBufferInfo.mHdrMetadata.validTypes & HdrMetadata::SMPTE2086; - bool hasCta861_3 = mBufferInfo.mHdrMetadata.validTypes & HdrMetadata::CTA861_3; + layerSettings.source.buffer.buffer = snapshot->externalTexture; + layerSettings.source.buffer.isOpaque = snapshot->contentOpaque; + layerSettings.source.buffer.fence = snapshot->acquireFence; + layerSettings.source.buffer.textureName = snapshot->textureName; + layerSettings.source.buffer.usePremultipliedAlpha = snapshot->premultipliedAlpha; + layerSettings.source.buffer.isY410BT2020 = snapshot->isHdrY410; + bool hasSmpte2086 = snapshot->hdrMetadata.validTypes & HdrMetadata::SMPTE2086; + bool hasCta861_3 = snapshot->hdrMetadata.validTypes & HdrMetadata::CTA861_3; float maxLuminance = 0.f; if (hasSmpte2086 && hasCta861_3) { - maxLuminance = std::min(mBufferInfo.mHdrMetadata.smpte2086.maxLuminance, - mBufferInfo.mHdrMetadata.cta8613.maxContentLightLevel); + maxLuminance = std::min(snapshot->hdrMetadata.smpte2086.maxLuminance, + snapshot->hdrMetadata.cta8613.maxContentLightLevel); } else if (hasSmpte2086) { - maxLuminance = mBufferInfo.mHdrMetadata.smpte2086.maxLuminance; + maxLuminance = snapshot->hdrMetadata.smpte2086.maxLuminance; } else if (hasCta861_3) { - maxLuminance = mBufferInfo.mHdrMetadata.cta8613.maxContentLightLevel; + maxLuminance = snapshot->hdrMetadata.cta8613.maxContentLightLevel; } else { switch (layerSettings.sourceDataspace & HAL_DATASPACE_TRANSFER_MASK) { case HAL_DATASPACE_TRANSFER_ST2084: @@ -828,17 +832,17 @@ void Layer::prepareBufferStateClientComposition( } } layerSettings.source.buffer.maxLuminanceNits = maxLuminance; - layerSettings.frameNumber = mCurrentFrameNumber; - layerSettings.bufferId = mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getId() : 0; + layerSettings.frameNumber = snapshot->frameNumber; + layerSettings.bufferId = snapshot->externalTexture->getId(); - const bool useFiltering = - targetSettings.needsFiltering || mNeedsFiltering || bufferNeedsFiltering(); + const bool useFiltering = targetSettings.needsFiltering || + snapshot->geomLayerTransform.needsBilinearFiltering() || snapshot->bufferNeedsFiltering; // Query the texture matrix given our current filtering mode. float textureMatrix[16]; getDrawingTransformMatrix(useFiltering, textureMatrix); - if (getTransformToDisplayInverse()) { + if (snapshot->geomBufferUsesDisplayInverseTransform) { /* * the code below applies the primary display's inverse transform to * the texture transform @@ -855,25 +859,22 @@ void Layer::prepareBufferStateClientComposition( * of a camera where the buffer remains in native orientation, * we want the pixels to always be upright. */ - sp p = mDrawingParent.promote(); - if (p != nullptr) { - const auto parentTransform = p->getTransform(); - tr = tr * inverseOrientation(parentTransform.getOrientation()); - } + const auto parentTransform = snapshot->transform; + tr = tr * inverseOrientation(parentTransform.getOrientation()); // and finally apply it to the original texture matrix const mat4 texTransform(mat4(static_cast(textureMatrix)) * tr); memcpy(textureMatrix, texTransform.asArray(), sizeof(textureMatrix)); } - const Rect win{getBounds()}; - float bufferWidth = getBufferSize(s).getWidth(); - float bufferHeight = getBufferSize(s).getHeight(); + const Rect win{layerSettings.geometry.boundaries}; + float bufferWidth = snapshot->bufferSize.getWidth(); + float bufferHeight = snapshot->bufferSize.getHeight(); // Layers can have a "buffer size" of [0, 0, -1, -1] when no display frame has // been set and there is no parent layer bounds. In that case, the scale is meaningless so // ignore them. - if (!getBufferSize(s).isValid()) { + if (!snapshot->bufferSize.isValid()) { bufferWidth = float(win.right) - float(win.left); bufferHeight = float(win.bottom) - float(win.top); } @@ -2181,35 +2182,17 @@ Layer::RoundedCornerState Layer::getRoundedCornerState() const { void Layer::prepareShadowClientComposition(LayerFE::LayerSettings& caster, const Rect& layerStackRect) const { - renderengine::ShadowSettings state = mFlinger->mDrawingState.globalShadowSettings; - - // Note: this preserves existing behavior of shadowing the entire layer and not cropping it if - // transparent regions are present. This may not be necessary since shadows are typically cast - // by layers without transparent regions. - state.boundaries = mBounds; + const auto* snapshot = getLayerSnapshot(); + renderengine::ShadowSettings state = snapshot->shadowSettings; + if (state.length <= 0.f || (state.ambientColor.a <= 0.f && state.spotColor.a <= 0.f)) { + return; + } // Shift the spot light x-position to the middle of the display and then // offset it by casting layer's screen pos. - state.lightPos.x = (layerStackRect.width() / 2.f) - mScreenBounds.left; - state.lightPos.y -= mScreenBounds.top; - - state.length = mEffectiveShadowRadius; - - if (state.length > 0.f) { - const float casterAlpha = caster.alpha; - const bool casterIsOpaque = - ((caster.source.buffer.buffer != nullptr) && caster.source.buffer.isOpaque); - - // If the casting layer is translucent, we need to fill in the shadow underneath the layer. - // Otherwise the generated shadow will only be shown around the casting layer. - state.casterIsTranslucent = !casterIsOpaque || (casterAlpha < 1.0f); - state.ambientColor *= casterAlpha; - state.spotColor *= casterAlpha; - - if (state.ambientColor.a > 0.f && state.spotColor.a > 0.f) { - caster.shadow = state; - } - } + state.lightPos.x = (layerStackRect.width() / 2.f) - snapshot->transformedBounds.left; + state.lightPos.y -= snapshot->transformedBounds.top; + caster.shadow = state; } bool Layer::findInHierarchy(const sp& l) { @@ -3418,13 +3401,14 @@ void Layer::setAutoRefresh(bool autoRefresh) { bool Layer::latchSidebandStream(bool& recomputeVisibleRegions) { // We need to update the sideband stream if the layer has both a buffer and a sideband stream. - editCompositionState()->sidebandStreamHasFrame = hasFrameUpdate() && mSidebandStream.get(); + auto* snapshot = editLayerSnapshot(); + snapshot->sidebandStreamHasFrame = hasFrameUpdate() && mSidebandStream.get(); if (mSidebandStreamChanged.exchange(false)) { const State& s(getDrawingState()); // mSidebandStreamChanged was true mSidebandStream = s.sidebandStream; - editCompositionState()->sidebandStream = mSidebandStream; + snapshot->sidebandStream = mSidebandStream; if (mSidebandStream != nullptr) { setTransactionFlags(eTransactionNeeded); mFlinger->setTransactionFlags(eTraversalNeeded); @@ -3824,12 +3808,15 @@ sp Layer::getCompositionEngineLayerFE() const { } } -compositionengine::LayerFECompositionState* Layer::editCompositionState() { - return mCompositionState.get(); +const Layer::LayerSnapshot* Layer::getLayerSnapshot() const { + return mSnapshot.get(); } +Layer::LayerSnapshot* Layer::editLayerSnapshot() { + return mSnapshot.get(); +} const compositionengine::LayerFECompositionState* Layer::getCompositionState() const { - return mCompositionState.get(); + return mSnapshot.get(); } void Layer::useSurfaceDamage() { @@ -4224,10 +4211,44 @@ void Layer::updateSnapshot(bool updateGeometry) { return; } + auto* snapshot = editLayerSnapshot(); if (updateGeometry) { prepareBasicGeometryCompositionState(); prepareGeometryCompositionState(); + snapshot->roundedCorner = getRoundedCornerState(); + snapshot->stretchEffect = getStretchEffect(); + snapshot->transformedBounds = mScreenBounds; + if (mEffectiveShadowRadius > 0.f) { + snapshot->shadowSettings = mFlinger->mDrawingState.globalShadowSettings; + + // Note: this preserves existing behavior of shadowing the entire layer and not cropping + // it if transparent regions are present. This may not be necessary since shadows are + // typically cast by layers without transparent regions. + snapshot->shadowSettings.boundaries = mBounds; + + const float casterAlpha = snapshot->alpha; + const bool casterIsOpaque = + ((mBufferInfo.mBuffer != nullptr) && isOpaque(mDrawingState)); + + // If the casting layer is translucent, we need to fill in the shadow underneath the + // layer. Otherwise the generated shadow will only be shown around the casting layer. + snapshot->shadowSettings.casterIsTranslucent = !casterIsOpaque || (casterAlpha < 1.0f); + snapshot->shadowSettings.ambientColor *= casterAlpha; + snapshot->shadowSettings.spotColor *= casterAlpha; + } + snapshot->shadowSettings.length = mEffectiveShadowRadius; + } + snapshot->contentOpaque = isOpaque(mDrawingState); + snapshot->isHdrY410 = isHdrY410(); + snapshot->bufferNeedsFiltering = bufferNeedsFiltering(); + sp p = mDrawingParent.promote(); + if (p != nullptr) { + snapshot->transform = p->getTransform(); + } else { + snapshot->transform.reset(); } + snapshot->bufferSize = getBufferSize(mDrawingState); + snapshot->externalTexture = mBufferInfo.mBuffer; preparePerFrameCompositionState(); } diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 5030fd826e..4079d16438 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -38,6 +38,7 @@ #include #include +#include #include #include @@ -144,6 +145,29 @@ public: bool hasRoundedCorners() const { return radius.x > 0.0f && radius.y > 0.0f; } }; + // LayerSnapshot stores Layer state used by Composition Engine and Render Engine. Composition + // Engine uses a pointer to LayerSnapshot (as LayerFECompositionState*) and the LayerSettings + // passed to Render Engine are created using properties stored on this struct. + // + // TODO(b/238781169) Implement LayerFE as a separate subclass. Migrate LayerSnapshot to that + // LayerFE subclass. + struct LayerSnapshot : public compositionengine::LayerFECompositionState { + int32_t sequence; + std::string name; + uint32_t textureName; + bool contentOpaque; + RoundedCornerState roundedCorner; + StretchEffect stretchEffect; + FloatRect transformedBounds; + renderengine::ShadowSettings shadowSettings; + bool premultipliedAlpha; + bool isHdrY410; + bool bufferNeedsFiltering; + ui::Transform transform; + Rect bufferSize; + std::shared_ptr externalTexture; + }; + using FrameRate = scheduler::LayerInfo::FrameRate; using FrameRateCompatibility = scheduler::LayerInfo::FrameRateCompatibility; @@ -392,7 +416,9 @@ public: ui::Dataspace getRequestedDataSpace() const; virtual sp getCompositionEngineLayerFE() const; - compositionengine::LayerFECompositionState* editCompositionState(); + + const LayerSnapshot* getLayerSnapshot() const; + LayerSnapshot* editLayerSnapshot(); // If we have received a new buffer this frame, we will pass its surface // damage down to hardware composer. Otherwise, we must send a region with @@ -867,6 +893,13 @@ public: bool simpleBufferUpdate(const layer_state_t&) const; static bool isOpaqueFormat(PixelFormat format); + + // Updates the LayerSnapshot. This must be called prior to sending layer data to + // CompositionEngine or RenderEngine (i.e. before calling CompositionEngine::present or + // Layer::prepareClientComposition). + // + // TODO(b/238781169) Remove direct calls to RenderEngine::drawLayers that don't go through + // CompositionEngine to create a single path for composing layers. void updateSnapshot(bool updateGeometry); protected: @@ -1166,8 +1199,6 @@ private: // the mStateLock. ui::Transform::RotationFlags mTransformHint = ui::Transform::ROT_0; - std::unique_ptr mCompositionState; - ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID; uint64_t mPreviousReleasedFrameNumber = 0; @@ -1200,6 +1231,8 @@ private: ui::Transform mRequestedTransform; sp mHwcSlotGenerator; + + std::unique_ptr mSnapshot = std::make_unique(); }; std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index bcd2b43a62..9a19f0a6c0 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -6653,6 +6653,11 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( std::vector renderedLayers; bool disableBlurs = false; traverseLayers([&](Layer* layer) { + // Layer::prepareClientComposition uses the layer's snapshot to populate the resulting + // LayerSettings. Calling Layer::updateSnapshot ensures that LayerSettings are + // generated with the layer's current buffer and geometry. + layer->updateSnapshot(true /* updateGeometry */); + disableBlurs |= layer->getDrawingState().sidebandStream != nullptr; Region clip(renderArea.getBounds()); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 983fac231c..2c9c451f29 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -285,7 +285,7 @@ public: const sp& sidebandStream) { layer->mDrawingState.sidebandStream = sidebandStream; layer->mSidebandStream = sidebandStream; - layer->editCompositionState()->sidebandStream = sidebandStream; + layer->editLayerSnapshot()->sidebandStream = sidebandStream; } void setLayerCompositionType(const sp& layer, -- GitLab From 02bb207aa8952182a461ed6a3ac5fcdd9e9f261b Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Fri, 26 Aug 2022 15:02:52 -0700 Subject: [PATCH 0301/1310] SF: Deduplicate state for active display mode RefreshRateConfigs tracks the active mode, so remove the duplicate (and not thread-safe) state in DisplayDevice. Bug: 241285191 Test: Build (-Wthread-safety) Change-Id: I6b551cc68da3916a797a28085be984667fa1901e --- services/surfaceflinger/DisplayDevice.cpp | 23 +++---- services/surfaceflinger/DisplayDevice.h | 14 ++-- services/surfaceflinger/SurfaceFlinger.cpp | 69 ++++++++++--------- services/surfaceflinger/SurfaceFlinger.h | 2 +- .../SurfaceFlinger_DisplayModeSwitching.cpp | 31 ++++++--- ...nger_SetupNewDisplayDeviceInternalTest.cpp | 8 ++- 6 files changed, 78 insertions(+), 69 deletions(-) diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index b823e06e0e..a25296c86d 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -192,17 +192,16 @@ bool DisplayDevice::isPoweredOn() const { } void DisplayDevice::setActiveMode(DisplayModeId modeId, const display::DisplaySnapshot& snapshot) { - const auto modeOpt = snapshot.displayModes().get(modeId); - LOG_ALWAYS_FATAL_IF(!modeOpt, "Unknown mode"); + const auto fpsOpt = snapshot.displayModes().get(modeId).transform( + [](const DisplayModePtr& mode) { return mode->getFps(); }); - mActiveMode = modeOpt->get(); - const Fps fps = mActiveMode->getFps(); + LOG_ALWAYS_FATAL_IF(!fpsOpt, "Unknown mode"); + const Fps fps = *fpsOpt; ATRACE_INT(mActiveModeFPSTrace.c_str(), fps.getIntValue()); - if (mRefreshRateConfigs) { - mRefreshRateConfigs->setActiveModeId(modeId); - } + mRefreshRateConfigs->setActiveModeId(modeId); + if (mRefreshRateOverlay) { mRefreshRateOverlay->changeRefreshRate(fps); } @@ -224,10 +223,6 @@ status_t DisplayDevice::initiateModeChange(const ActiveModeInfo& info, constraints, outTimeline); } -const DisplayModePtr& DisplayDevice::getActiveMode() const { - return mActiveMode; -} - nsecs_t DisplayDevice::getVsyncPeriodFromHWC() const { const auto physicalId = getPhysicalId(); if (!mHwComposer.isConnected(physicalId)) { @@ -240,7 +235,7 @@ nsecs_t DisplayDevice::getVsyncPeriodFromHWC() const { return vsyncPeriod; } - return getActiveMode()->getFps().getPeriodNsecs(); + return refreshRateConfigs().getActiveModePtr()->getVsyncPeriod(); } nsecs_t DisplayDevice::getRefreshTimestamp() const { @@ -451,7 +446,7 @@ void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinnner) { mRefreshRateOverlay = std::make_unique(fpsRange, showSpinnner); mRefreshRateOverlay->setLayerStack(getLayerStack()); mRefreshRateOverlay->setViewport(getSize()); - mRefreshRateOverlay->changeRefreshRate(getActiveMode()->getFps()); + mRefreshRateOverlay->changeRefreshRate(getActiveMode().getFps()); } bool DisplayDevice::onKernelTimerChanged(std::optional desiredModeId, @@ -492,7 +487,7 @@ bool DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info) { } // Check if we are already at the desired mode - if (getActiveMode()->getId() == info.mode->getId()) { + if (refreshRateConfigs().getActiveModePtr()->getId() == info.mode->getId()) { return false; } diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index d79a6b5f3d..0f52aff4f3 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -189,8 +189,6 @@ public: /* ------------------------------------------------------------------------ * Display mode management. */ - const DisplayModePtr& getActiveMode() const; - struct ActiveModeInfo { DisplayModePtr mode; scheduler::DisplayModeEvent event = scheduler::DisplayModeEvent::None; @@ -207,6 +205,10 @@ public: return mUpcomingActiveMode; } + const DisplayMode& getActiveMode() const REQUIRES(kMainThreadContext) { + return mRefreshRateConfigs->getActiveMode(); + } + // Precondition: DisplaySnapshot must contain a mode with DisplayModeId. void setActiveMode(DisplayModeId, const display::DisplaySnapshot&) REQUIRES(kMainThreadContext); @@ -226,7 +228,7 @@ public: } // Enables an overlay to be displayed with the current refresh rate - void enableRefreshRateOverlay(bool enable, bool showSpinner); + void enableRefreshRateOverlay(bool enable, bool showSpinner) REQUIRES(kMainThreadContext); bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; } bool onKernelTimerChanged(std::optional, bool timerExpired); void animateRefreshRateOverlay(); @@ -265,10 +267,10 @@ private: static ui::Transform::RotationFlags sPrimaryDisplayRotationFlags; - // allow initial power mode as null. + // Allow nullopt as initial power mode. std::optional mPowerMode; - DisplayModePtr mActiveMode; - std::optional mStagedBrightness = std::nullopt; + + std::optional mStagedBrightness; float mBrightness = -1.f; std::atomic mLastHwVsync = 0; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index bcd2b43a62..55a54fe6a9 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -680,7 +680,7 @@ void SurfaceFlinger::bootFinished() { sp input(defaultServiceManager()->getService(String16("inputflinger"))); - static_cast(mScheduler->schedule([=] { + static_cast(mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) { if (input == nullptr) { ALOGE("Failed to link to input service"); } else { @@ -708,8 +708,9 @@ void SurfaceFlinger::bootFinished() { mBootStage = BootStage::FINISHED; - if (property_get_bool("sf.debug.show_refresh_rate_overlay", false)) { - FTL_FAKE_GUARD(mStateLock, enableRefreshRateOverlay(true)); + if (base::GetBoolProperty("sf.debug.show_refresh_rate_overlay"s, false)) { + ftl::FakeGuard guard(mStateLock); + enableRefreshRateOverlay(true); } })); } @@ -1059,7 +1060,7 @@ status_t SurfaceFlinger::getDynamicDisplayInfo(const sp& displayToken, const PhysicalDisplayId displayId = snapshot.displayId(); - info->activeDisplayModeId = display->getActiveMode()->getId().value(); + info->activeDisplayModeId = display->refreshRateConfigs().getActiveModePtr()->getId().value(); info->activeColorMode = display->getCompositionDisplay()->getState().colorMode; info->supportedColorModes = getDisplayColorModes(displayId); info->hdrCapabilities = display->getHdrCapabilities(); @@ -1183,7 +1184,7 @@ void SurfaceFlinger::updateInternalStateWithChangedMode() { return; } - if (display->getActiveMode()->getResolution() != upcomingModeInfo.mode->getResolution()) { + if (display->getActiveMode().getResolution() != upcomingModeInfo.mode->getResolution()) { auto& state = mCurrentState.displays.editValueFor(display->getDisplayToken()); // We need to generate new sequenceId in order to recreate the display (and this // way the framebuffer). @@ -1269,7 +1270,7 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { ALOGV("%s changing active mode to %d(%s) for display %s", __func__, desiredModeId.value(), to_string(*refreshRateOpt).c_str(), to_string(display->getId()).c_str()); - if (display->getActiveMode()->getId() == desiredModeId) { + if (display->getActiveMode().getId() == desiredModeId) { // we are already in the requested mode, there is nothing left to do desiredActiveModeChangeDone(display); continue; @@ -1318,7 +1319,7 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { const auto display = getDisplayDeviceLocked(*displayToUpdateImmediately); const auto desiredActiveMode = display->getDesiredActiveMode(); if (desiredActiveMode && - display->getActiveMode()->getId() == desiredActiveMode->mode->getId()) { + display->getActiveMode().getId() == desiredActiveMode->mode->getId()) { desiredActiveModeChangeDone(display); } } @@ -2107,7 +2108,7 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe activeDisplay->getPowerMode() == hal::PowerMode::ON; if (mPowerHintSessionEnabled) { const auto& display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get(); - const Period vsyncPeriod = Period::fromNs(display->getActiveMode()->getVsyncPeriod()); + const Period vsyncPeriod = Period::fromNs(display->getActiveMode().getVsyncPeriod()); mPowerAdvisor->setCommitStart(frameTime); mPowerAdvisor->setExpectedPresentTime(mExpectedPresentTime); @@ -3084,7 +3085,7 @@ void SurfaceFlinger::processDisplayChanged(const wp& displayToken, void SurfaceFlinger::updateInternalDisplayVsyncLocked(const sp& activeDisplay) { mVsyncConfiguration->reset(); - const Fps refreshRate = activeDisplay->refreshRateConfigs().getActiveMode().getFps(); + const Fps refreshRate = activeDisplay->getActiveMode().getFps(); updatePhaseConfiguration(refreshRate); mRefreshRateStats->setRefreshRate(refreshRate); } @@ -3394,11 +3395,13 @@ void SurfaceFlinger::triggerOnFrameRateOverridesChanged() { void SurfaceFlinger::initScheduler(const sp& display) { LOG_ALWAYS_FATAL_IF(mScheduler); - const auto currRefreshRate = display->getActiveMode()->getFps(); - mRefreshRateStats = std::make_unique(*mTimeStats, currRefreshRate, - hal::PowerMode::OFF); + const auto activeModePtr = display->refreshRateConfigs().getActiveModePtr(); + const Fps activeRefreshRate = activeModePtr->getFps(); + mRefreshRateStats = + std::make_unique(*mTimeStats, activeRefreshRate, + hal::PowerMode::OFF); - mVsyncConfiguration = getFactory().createVsyncConfiguration(currRefreshRate); + mVsyncConfiguration = getFactory().createVsyncConfiguration(activeRefreshRate); mVsyncModulator = sp::make(mVsyncConfiguration->getCurrentConfigs()); using Feature = scheduler::Feature; @@ -3431,7 +3434,7 @@ void SurfaceFlinger::initScheduler(const sp& display) { mScheduler->startTimers(); const auto configs = mVsyncConfiguration->getCurrentConfigs(); - const nsecs_t vsyncPeriod = currRefreshRate.getPeriodNsecs(); + const nsecs_t vsyncPeriod = activeRefreshRate.getPeriodNsecs(); mAppConnectionHandle = mScheduler->createConnection("app", mFrameTimeline->getTokenManager(), /*workDuration=*/configs.late.appWorkDuration, @@ -3459,7 +3462,7 @@ void SurfaceFlinger::initScheduler(const sp& display) { // This is a bit hacky, but this avoids a back-pointer into the main SF // classes from EventThread, and there should be no run-time binder cost // anyway since there are no connected apps at this point. - mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, display->getActiveMode()); + mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, activeModePtr); } void SurfaceFlinger::updatePhaseConfiguration(const Fps& refreshRate) { @@ -5348,10 +5351,10 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, std::string& result) co if (const auto display = getDefaultDisplayDeviceLocked()) { std::string fps, xDpi, yDpi; - if (const auto activeMode = display->getActiveMode()) { - fps = to_string(activeMode->getFps()); + if (const auto activeModePtr = display->refreshRateConfigs().getActiveModePtr()) { + fps = to_string(activeModePtr->getFps()); - const auto dpi = activeMode->getDpi(); + const auto dpi = activeModePtr->getDpi(); xDpi = base::StringPrintf("%.2f", dpi.x); yDpi = base::StringPrintf("%.2f", dpi.y); } else { @@ -5851,19 +5854,17 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r return NO_ERROR; } case 1034: { - auto future = mScheduler->schedule([&] { - switch (n = data.readInt32()) { - case 0: - case 1: - FTL_FAKE_GUARD(mStateLock, - enableRefreshRateOverlay(static_cast(n))); - break; - default: { - reply->writeBool( - FTL_FAKE_GUARD(mStateLock, isRefreshRateOverlayEnabled())); - } - } - }); + auto future = mScheduler->schedule( + [&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) { + switch (n = data.readInt32()) { + case 0: + case 1: + enableRefreshRateOverlay(static_cast(n)); + break; + default: + reply->writeBool(isRefreshRateOverlayEnabled()); + } + }); future.wait(); return NO_ERROR; @@ -6799,12 +6800,12 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( // TODO(b/140204874): Leave the event in until we do proper testing with all apps that might // be depending in this callback. - const auto activeMode = display->getActiveMode(); + const auto activeModePtr = display->refreshRateConfigs().getActiveModePtr(); if (isDisplayActiveLocked(display)) { - mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, activeMode); + mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, activeModePtr); toggleKernelIdleTimer(); } else { - mScheduler->onNonPrimaryDisplayModeChanged(mAppConnectionHandle, activeMode); + mScheduler->onNonPrimaryDisplayModeChanged(mAppConnectionHandle, activeModePtr); } auto preferredModeOpt = diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 1f8874df5f..fdaacf074c 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1340,7 +1340,7 @@ private: std::unique_ptr mPowerAdvisor; - void enableRefreshRateOverlay(bool enable) REQUIRES(mStateLock); + void enableRefreshRateOverlay(bool enable) REQUIRES(mStateLock, kMainThreadContext); // Flag used to set override desired display mode from backdoor bool mDebugDisplayModeSetByBackdoor = false; diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp index 57937dcaea..6b7e3533a5 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp @@ -20,6 +20,7 @@ #include "DisplayTransactionTestHelpers.h" +#include #include namespace android { @@ -111,8 +112,10 @@ void DisplayModeSwitchingTest::setupScheduler( } TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRequired) { + ftl::FakeGuard guard(kMainThreadContext); + ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60); + ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60); mFlinger.onActiveDisplayChanged(mDisplay); @@ -121,7 +124,7 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRe ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90); - ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60); + ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60); // Verify that next commit will call setActiveConfigWithConstraints in HWC const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; @@ -134,7 +137,7 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRe Mock::VerifyAndClearExpectations(mComposer); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60); + ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60); // Verify that the next commit will complete the mode change and send // a onModeChanged event to the framework. @@ -144,10 +147,12 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRe Mock::VerifyAndClearExpectations(mAppEventThread); ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId90); + ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId90); } TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithoutRefreshRequired) { + ftl::FakeGuard guard(kMainThreadContext); + ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); mFlinger.onActiveDisplayChanged(mDisplay); @@ -157,7 +162,7 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithoutRefres ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90); - ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60); + ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60); // Verify that next commit will call setActiveConfigWithConstraints in HWC // and complete the mode change. @@ -172,15 +177,17 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithoutRefres mFlinger.commit(); ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId90); + ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId90); } TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) { + ftl::FakeGuard guard(kMainThreadContext); + // Test that if we call setDesiredDisplayModeSpecs while a previous mode change // is still being processed the later call will be respected. ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60); + ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60); mFlinger.onActiveDisplayChanged(mDisplay); @@ -214,12 +221,14 @@ TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) { mFlinger.commit(); ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId120); + ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId120); } TEST_F(DisplayModeSwitchingTest, changeResolution_OnActiveDisplay_WithoutRefreshRequired) { + ftl::FakeGuard guard(kMainThreadContext); + ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60); + ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60); mFlinger.onActiveDisplayChanged(mDisplay); @@ -228,7 +237,7 @@ TEST_F(DisplayModeSwitchingTest, changeResolution_OnActiveDisplay_WithoutRefresh ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90_4K); - ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60); + ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60); // Verify that next commit will call setActiveConfigWithConstraints in HWC // and complete the mode change. @@ -263,7 +272,7 @@ TEST_F(DisplayModeSwitchingTest, changeResolution_OnActiveDisplay_WithoutRefresh mDisplay = mFlinger.getDisplay(displayToken); ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId90_4K); + ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId90_4K); } } // namespace diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp index ec2c2b4358..073c459ea3 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp @@ -17,6 +17,8 @@ #undef LOG_TAG #define LOG_TAG "LibSurfaceFlingerUnittests" +#include + #include "DisplayHardware/DisplayMode.h" #include "DisplayTransactionTestHelpers.h" @@ -265,7 +267,7 @@ void SetupNewDisplayDeviceInternalTest::setupNewDisplayDeviceInternalTest() { // -------------------------------------------------------------------- // Postconditions - ASSERT_TRUE(device != nullptr); + ASSERT_NE(nullptr, device); EXPECT_EQ(Case::Display::DISPLAY_ID::get(), device->getId()); EXPECT_EQ(static_cast(Case::Display::VIRTUAL), device->isVirtual()); EXPECT_EQ(static_cast(Case::Display::SECURE), device->isSecure()); @@ -282,8 +284,8 @@ void SetupNewDisplayDeviceInternalTest::setupNewDisplayDeviceInternalTest() { device->receivesInput()); if constexpr (Case::Display::CONNECTION_TYPE::value) { - EXPECT_NE(nullptr, device->getActiveMode()); - EXPECT_EQ(Case::Display::HWC_ACTIVE_CONFIG_ID, device->getActiveMode()->getHwcId()); + ftl::FakeGuard guard(kMainThreadContext); + EXPECT_EQ(Case::Display::HWC_ACTIVE_CONFIG_ID, device->getActiveMode().getHwcId()); } } -- GitLab From 10205866c4d345f2ec9d638ca2d909ac5a1661a3 Mon Sep 17 00:00:00 2001 From: Bo Liu Date: Thu, 15 Sep 2022 16:24:36 -0400 Subject: [PATCH 0302/1310] NDK methods to get SurfaceControl/Transction from java Test: ASurfaceControlTest#testSurfaceControl_fromSurfaceControl and ASurfaceControlTest#testSurfaceTransaction_fromTransaction Change-Id: I1fe4459ba96c8b051c69d8206c704f1739272ac3 --- include/android/surface_control_jni.h | 65 +++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 include/android/surface_control_jni.h diff --git a/include/android/surface_control_jni.h b/include/android/surface_control_jni.h new file mode 100644 index 0000000000..67e3a9ff42 --- /dev/null +++ b/include/android/surface_control_jni.h @@ -0,0 +1,65 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup NativeActivity Native Activity + * @{ + */ + +/** + * @file surface_control_jni.h + */ + +#ifndef ANDROID_SURFACE_CONTROL_JNI_H +#define ANDROID_SURFACE_CONTROL_JNI_H + +#include +#include + +#include + +__BEGIN_DECLS + +/** + * Return the ASurfaceControl wrapped by a Java SurfaceControl object. + * + * This method does not acquire any additional reference to the ASurfaceControl + * that is returned. To keep the ASurfaceControl alive after the Java + * SurfaceControl object is closed, explicitly or by the garbage collector, be + * sure to use ASurfaceControl_acquire() to acquire an additional reference. + * + * Available since API level 34. + */ +ASurfaceControl* _Nullable ASurfaceControl_fromSurfaceControl(JNIEnv* _Nonnull env, + jobject _Nonnull surfaceControlObj) __INTRODUCED_IN(__ANDROID_API_U__); + +/** + * Return the ASurfaceTransaction wrapped by a Java Transaction object. + * + * The returned ASurfaceTransaction is still owned by the Java Transaction object is only + * valid while the Java Transaction object is alive. In particular, the returned transaction + * must NOT be deleted with ASurfaceTransaction_delete. + * May return nullptr on error. + * + * Available since API level 34. + */ +ASurfaceTransaction* _Nullable ASurfaceTransaction_fromTransaction(JNIEnv* _Nonnull env, + jobject _Nonnull transactionObj) __INTRODUCED_IN(__ANDROID_API_U__); + +__END_DECLS + +#endif // ANDROID_SURFACE_CONTROL_JNI_H +/** @} */ -- GitLab From e8dd962b15b6264ff75efb061b8ac9aa68c84acf Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Wed, 21 Sep 2022 17:18:51 +0000 Subject: [PATCH 0303/1310] Remove unused code in Layer::releasePendingBuffer This loop was originally added to find the most recently presented fence to pass over to FrameEventHistory. But FrameEventHistory no longer exists, so this loop just finds a fence to do nothing with it. Bug: 247513510 Test: builds Change-Id: Ifb887c3e7542395da39c5634b68f3782d10bd704 --- services/surfaceflinger/Layer.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 0771e1055b..13437a4497 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -3017,17 +3017,6 @@ void Layer::releasePendingBuffer(nsecs_t dequeueReadyTime) { mFlinger->getTransactionCallbackInvoker().addCallbackHandles(mDrawingState.callbackHandles, jankData); - - sp releaseFence = Fence::NO_FENCE; - for (auto& handle : mDrawingState.callbackHandles) { - if (handle->releasePreviousBuffer && - mDrawingState.releaseBufferEndpoint == handle->listener) { - releaseFence = - handle->previousReleaseFence ? handle->previousReleaseFence : Fence::NO_FENCE; - break; - } - } - mDrawingState.callbackHandles = {}; } -- GitLab From 439b60968f1fe544b79a5c5ba4657fca9e82b54a Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Wed, 14 Sep 2022 22:24:17 +0000 Subject: [PATCH 0304/1310] Remove coordinate swapping in region sampling Captured screens for region sampling were originally rendered upside-down, which required swapping coordinates to sample from the correct area in the output buffer. Since then, several fixes to the screenshot path have landed, which treat screen orientation consistently, so the captured images are no longer upside-down. But, the coordinate swapping was never removed. Remove it now, as sampling during landscape is now broken. Bug: 133849373 Bug: 241967077 Test: Split screen in landscape with settings app in dark mode, calculator in light mode, and trigger back gestures Change-Id: I52c65032d33d01a4407dc1b30215e7edac6eb1ea --- .../surfaceflinger/RegionSamplingThread.cpp | 19 +++-------- .../tests/unittests/RegionSamplingTest.cpp | 34 ------------------- 2 files changed, 4 insertions(+), 49 deletions(-) diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp index 570525598e..8dd3b0f59d 100644 --- a/services/surfaceflinger/RegionSamplingThread.cpp +++ b/services/surfaceflinger/RegionSamplingThread.cpp @@ -203,25 +203,14 @@ float sampleArea(const uint32_t* data, int32_t width, int32_t height, int32_t st return 0.0f; } - // (b/133849373) ROT_90 screencap images produced upside down - auto area = sample_area; - if (orientation & ui::Transform::ROT_90) { - area.top = height - area.top; - area.bottom = height - area.bottom; - std::swap(area.top, area.bottom); - - area.left = width - area.left; - area.right = width - area.right; - std::swap(area.left, area.right); - } - - const uint32_t pixelCount = (area.bottom - area.top) * (area.right - area.left); + const uint32_t pixelCount = + (sample_area.bottom - sample_area.top) * (sample_area.right - sample_area.left); uint32_t accumulatedLuma = 0; // Calculates luma with approximation of Rec. 709 primaries - for (int32_t row = area.top; row < area.bottom; ++row) { + for (int32_t row = sample_area.top; row < sample_area.bottom; ++row) { const uint32_t* rowBase = data + row * stride; - for (int32_t column = area.left; column < area.right; ++column) { + for (int32_t column = sample_area.left; column < sample_area.right; ++column) { uint32_t pixel = rowBase[column]; const uint32_t r = pixel & 0xFF; const uint32_t g = (pixel >> 8) & 0xFF; diff --git a/services/surfaceflinger/tests/unittests/RegionSamplingTest.cpp b/services/surfaceflinger/tests/unittests/RegionSamplingTest.cpp index f19e55409c..409e1ef5d7 100644 --- a/services/surfaceflinger/tests/unittests/RegionSamplingTest.cpp +++ b/services/surfaceflinger/tests/unittests/RegionSamplingTest.cpp @@ -106,40 +106,6 @@ TEST_F(RegionSamplingTest, bounds_checking) { testing::Eq(0.0)); } -// workaround for b/133849373 -TEST_F(RegionSamplingTest, orientation_90) { - std::generate(buffer.begin(), buffer.end(), - [n = 0]() mutable { return (n++ > (kStride * kHeight >> 1)) ? kBlack : kWhite; }); - - Rect tl_region{0, 0, 4, 4}; - EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, ui::Transform::ROT_0, - tl_region), - testing::Eq(1.0)); - EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, ui::Transform::ROT_180, - tl_region), - testing::Eq(1.0)); - EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, ui::Transform::ROT_90, - tl_region), - testing::Eq(0.0)); - EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, ui::Transform::ROT_270, - tl_region), - testing::Eq(0.0)); - - Rect br_region{kWidth - 4, kHeight - 4, kWidth, kHeight}; - EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, ui::Transform::ROT_0, - br_region), - testing::Eq(0.0)); - EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, ui::Transform::ROT_180, - br_region), - testing::Eq(0.0)); - EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, ui::Transform::ROT_90, - br_region), - testing::Eq(1.0)); - EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, ui::Transform::ROT_270, - br_region), - testing::Eq(1.0)); -} - } // namespace android // TODO(b/129481165): remove the #pragma below and fix conversion issues -- GitLab From 73c9508bef2005b2314870dbfd1db19fb0a943eb Mon Sep 17 00:00:00 2001 From: Yeabkal Wubshit Date: Thu, 22 Sep 2022 10:12:33 +0000 Subject: [PATCH 0305/1310] VelocityTracker Cleanups: Differential Axes Support Follow Up This CL follows ag/19950271. It: - fixes a param name, to make it consistent with the latest terminology we use (and the one we use in the .cpp file) - add comment to the VelocityTrackr#createStrategy function about this param Bug: 32830165 Change-Id: Idf90547c6b9629e98ee9014549085a1db9024700 Test: tests unaffected (trivial naming/comment change) --- include/input/VelocityTracker.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h index f6556d6712..294879ed21 100644 --- a/include/input/VelocityTracker.h +++ b/include/input/VelocityTracker.h @@ -160,8 +160,12 @@ private: void configureStrategy(int32_t axis); + // Generates a VelocityTrackerStrategy instance for the given Strategy type. + // The `deltaValues` parameter indicates whether or not the created strategy should treat motion + // values as deltas (and not as absolute values). This the parameter is applicable only for + // strategies that support differential axes. static std::unique_ptr createStrategy(const Strategy strategy, - bool isCumulative); + bool deltaValues); }; -- GitLab From 007713dd5cd5f005f3e332053431366f8fea9caa Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 16 Sep 2022 14:40:11 -0700 Subject: [PATCH 0306/1310] Add fuzzers to checkinput Now that we have more code that breaks during refactorings, let's make sure checkinput catches these breakages. Bug: 211379801 Test: m checkinput Change-Id: I1731b91d9dac770625a9e85d98c4b580b1e300b4 --- services/inputflinger/Android.bp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp index 5b286dc908..88a9acb80e 100644 --- a/services/inputflinger/Android.bp +++ b/services/inputflinger/Android.bp @@ -191,7 +191,16 @@ phony { "libinputservice_test", "Bug-115739809", "StructLayout_test", + + // native fuzzers "inputflinger_latencytracker_fuzzer", + "inputflinger_cursor_input_fuzzer", + "inputflinger_keyboard_input_fuzzer", + "inputflinger_multitouch_input_fuzzer", + "inputflinger_switch_input_fuzzer", + "inputflinger_input_reader_fuzzer", + "inputflinger_blocking_queue_fuzzer", + "inputflinger_input_classifier_fuzzer", // Java/Kotlin targets "CtsWindowManagerDeviceTestCases", -- GitLab From 82b37d6e0bc12bd7218d2986df061320069f962a Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Mon, 12 Sep 2022 13:36:48 +0000 Subject: [PATCH 0307/1310] Add support for detecting Keyboard backlight using sysfs node Add new light type for Keyboard backlight and corresponding detection logic. Also, refactor PeripheralController logic to support Light type and light capabilities similar to how it is handled on Java side of code. Test: atest inputflinger_tests Bug: 245506418 Change-Id: Ic69e42555bf57a66683fcf359169869ee2a00749 --- include/input/InputDevice.h | 20 ++- services/inputflinger/reader/EventHub.cpp | 30 ++-- .../controller/PeripheralController.cpp | 39 ++++- .../reader/controller/PeripheralController.h | 24 +-- .../inputflinger/reader/include/EventHub.h | 2 + .../inputflinger/tests/InputReader_test.cpp | 139 +++++++++++++++++- 6 files changed, 220 insertions(+), 34 deletions(-) diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h index 415080d15d..0026e82caa 100644 --- a/include/input/InputDevice.h +++ b/include/input/InputDevice.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include #include #include @@ -105,12 +106,18 @@ enum class InputDeviceSensorReportingMode : int32_t { }; enum class InputDeviceLightType : int32_t { - MONO = 0, + INPUT = 0, PLAYER_ID = 1, - RGB = 2, - MULTI_COLOR = 3, + KEYBOARD_BACKLIGHT = 2, - ftl_last = MULTI_COLOR + ftl_last = KEYBOARD_BACKLIGHT +}; + +enum class InputDeviceLightCapability : uint32_t { + /** Capability to change brightness of the light */ + BRIGHTNESS = 0x00000001, + /** Capability to change color of the light */ + RGB = 0x00000002, }; struct InputDeviceSensorInfo { @@ -171,14 +178,17 @@ struct InputDeviceSensorInfo { struct InputDeviceLightInfo { explicit InputDeviceLightInfo(std::string name, int32_t id, InputDeviceLightType type, + ftl::Flags capabilityFlags, int32_t ordinal) - : name(name), id(id), type(type), ordinal(ordinal) {} + : name(name), id(id), type(type), capabilityFlags(capabilityFlags), ordinal(ordinal) {} // Name string of the light. std::string name; // Light id int32_t id; // Type of the light. InputDeviceLightType type; + // Light capabilities. + ftl::Flags capabilityFlags; // Ordinal of the light int32_t ordinal; }; diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index c9d21dc573..5351a51016 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -117,7 +117,8 @@ static const std::unordered_map LIGHT_CLASSES = {"brightness", InputLightClass::BRIGHTNESS}, {"multi_index", InputLightClass::MULTI_INDEX}, {"multi_intensity", InputLightClass::MULTI_INTENSITY}, - {"max_brightness", InputLightClass::MAX_BRIGHTNESS}}; + {"max_brightness", InputLightClass::MAX_BRIGHTNESS}, + {"kbd_backlight", InputLightClass::KEYBOARD_BACKLIGHT}}; // Mapping for input multicolor led class node names. // https://www.kernel.org/doc/html/latest/leds/leds-class-multicolor.html @@ -365,15 +366,26 @@ static std::unordered_map readLightsConfigura info.path = nodePath; info.name = nodePath.filename(); info.maxBrightness = std::nullopt; - size_t nameStart = info.name.rfind(":"); - if (nameStart != std::string::npos) { - // Trim the name to color name - info.name = info.name.substr(nameStart + 1); - // Set InputLightClass flag for colors - const auto it = LIGHT_CLASSES.find(info.name); - if (it != LIGHT_CLASSES.end()) { - info.flags |= it->second; + + // Light name should follow the naming pattern :: + // Refer kernel docs /leds/leds-class.html for valid supported LED names. + std::regex indexPattern("([a-zA-Z0-9_.:]*:)?([a-zA-Z0-9_.]*):([a-zA-Z0-9_.]*)"); + std::smatch results; + + if (std::regex_match(info.name, results, indexPattern)) { + // regex_match will return full match at index 0 and at index 1. For RawLightInfo + // we only care about sections and which will be at index 2 and 3. + for (int i = 2; i <= 3; i++) { + const auto it = LIGHT_CLASSES.find(results.str(i)); + if (it != LIGHT_CLASSES.end()) { + info.flags |= it->second; + } } + + // Set name of the raw light to which represents playerIDs for LEDs that + // turn on/off based on the current player ID (Refer to PeripheralController.cpp for + // player ID logic) + info.name = results.str(3); } // Scan the path for all the files // Refer to https://www.kernel.org/doc/Documentation/leds/leds-class.txt diff --git a/services/inputflinger/reader/controller/PeripheralController.cpp b/services/inputflinger/reader/controller/PeripheralController.cpp index eaf5b51e9f..cedbacb046 100644 --- a/services/inputflinger/reader/controller/PeripheralController.cpp +++ b/services/inputflinger/reader/controller/PeripheralController.cpp @@ -16,6 +16,7 @@ #include #include +#include #include @@ -70,7 +71,7 @@ std::optional PeripheralController::Light::getRawLightBrightness(i // If the light node doesn't have max brightness, use the default max brightness. int rawMaxBrightness = rawInfoOpt->maxBrightness.value_or(MAX_BRIGHTNESS); - float ratio = MAX_BRIGHTNESS / rawMaxBrightness; + float ratio = static_cast(MAX_BRIGHTNESS) / rawMaxBrightness; // Scale the returned brightness in [0, rawMaxBrightness] to [0, 255] if (rawMaxBrightness != MAX_BRIGHTNESS) { brightness = brightness * ratio; @@ -89,7 +90,7 @@ void PeripheralController::Light::setRawLightBrightness(int32_t rawLightId, int3 } // If the light node doesn't have max brightness, use the default max brightness. int rawMaxBrightness = rawInfo->maxBrightness.value_or(MAX_BRIGHTNESS); - float ratio = MAX_BRIGHTNESS / rawMaxBrightness; + float ratio = static_cast(MAX_BRIGHTNESS) / rawMaxBrightness; // Scale the requested brightness in [0, 255] to [0, rawMaxBrightness] if (rawMaxBrightness != MAX_BRIGHTNESS) { brightness = ceil(brightness / ratio); @@ -271,7 +272,8 @@ void PeripheralController::populateDeviceInfo(InputDeviceInfo* deviceInfo) { for (const auto& [lightId, light] : mLights) { // Input device light doesn't support ordinal, always pass 1. - InputDeviceLightInfo lightInfo(light->name, light->id, light->type, 1 /* ordinal */); + InputDeviceLightInfo lightInfo(light->name, light->id, light->type, light->capabilityFlags, + 1 /* ordinal */); deviceInfo->addLightInfo(lightInfo); } } @@ -284,6 +286,8 @@ void PeripheralController::dump(std::string& dump) { dump += StringPrintf(INDENT4 "Id: %d", lightId); dump += StringPrintf(INDENT4 "Name: %s", light->name.c_str()); dump += StringPrintf(INDENT4 "Type: %s", ftl::enum_string(light->type).c_str()); + dump += StringPrintf(INDENT4 "Capability flags: %s", + light->capabilityFlags.string().c_str()); light->dump(dump); } } @@ -363,6 +367,8 @@ void PeripheralController::configureLights() { std::unordered_map rawRgbIds; // Map from player Id to raw light Id std::unordered_map playerIdLightIds; + // Set of Keyboard backlights + std::set keyboardBacklightIds; // Check raw lights const std::vector rawLightIds = getDeviceContext().getRawLightIds(); @@ -391,6 +397,10 @@ void PeripheralController::configureLights() { } } } + // Check if this is a Keyboard backlight + if (rawInfo->flags.test(InputLightClass::KEYBOARD_BACKLIGHT)) { + keyboardBacklightIds.insert(rawId); + } // Check if this is an LED of RGB light if (rawInfo->flags.test(InputLightClass::RED)) { hasRedLed = true; @@ -431,8 +441,21 @@ void PeripheralController::configureLights() { ALOGD("Rgb light ids [%d, %d, %d] \n", rawRgbIds.at(LightColor::RED), rawRgbIds.at(LightColor::GREEN), rawRgbIds.at(LightColor::BLUE)); } + bool isKeyboardBacklight = keyboardBacklightIds.find(rawRgbIds.at(LightColor::RED)) != + keyboardBacklightIds.end() && + keyboardBacklightIds.find(rawRgbIds.at(LightColor::GREEN)) != + keyboardBacklightIds.end() && + keyboardBacklightIds.find(rawRgbIds.at(LightColor::BLUE)) != + keyboardBacklightIds.end() && + (!rawGlobalId.has_value() || + keyboardBacklightIds.find(rawGlobalId.value()) != keyboardBacklightIds.end()); + std::unique_ptr light = - std::make_unique(getDeviceContext(), ++mNextId, rawRgbIds, rawGlobalId); + std::make_unique(getDeviceContext(), ++mNextId, + isKeyboardBacklight + ? InputDeviceLightType::KEYBOARD_BACKLIGHT + : InputDeviceLightType::INPUT, + rawRgbIds, rawGlobalId); mLights.insert_or_assign(light->id, std::move(light)); // Remove from raw light info as they've been composed a RBG light. rawInfos.erase(rawRgbIds.at(LightColor::RED)); @@ -445,6 +468,10 @@ void PeripheralController::configureLights() { // Check the rest of raw light infos for (const auto& [rawId, rawInfo] : rawInfos) { + InputDeviceLightType type = keyboardBacklightIds.find(rawId) != keyboardBacklightIds.end() + ? InputDeviceLightType::KEYBOARD_BACKLIGHT + : InputDeviceLightType::INPUT; + // If the node is multi-color led, construct a MULTI_COLOR light if (rawInfo.flags.test(InputLightClass::MULTI_INDEX) && rawInfo.flags.test(InputLightClass::MULTI_INTENSITY)) { @@ -453,7 +480,7 @@ void PeripheralController::configureLights() { } std::unique_ptr light = std::make_unique(getDeviceContext(), rawInfo.name, ++mNextId, - rawInfo.id); + type, rawInfo.id); mLights.insert_or_assign(light->id, std::move(light)); continue; } @@ -462,7 +489,7 @@ void PeripheralController::configureLights() { ALOGD("Mono light Id %d name %s \n", rawInfo.id, rawInfo.name.c_str()); } std::unique_ptr light = std::make_unique(getDeviceContext(), rawInfo.name, - ++mNextId, rawInfo.id); + ++mNextId, type, rawInfo.id); mLights.insert_or_assign(light->id, std::move(light)); } diff --git a/services/inputflinger/reader/controller/PeripheralController.h b/services/inputflinger/reader/controller/PeripheralController.h index 25cf435620..8ac42c3792 100644 --- a/services/inputflinger/reader/controller/PeripheralController.h +++ b/services/inputflinger/reader/controller/PeripheralController.h @@ -67,6 +67,7 @@ private: std::string name; int32_t id; InputDeviceLightType type; + ftl::Flags capabilityFlags; virtual bool setLightColor(int32_t color) { return false; } virtual std::optional getLightColor() { return std::nullopt; } @@ -81,8 +82,10 @@ private: struct MonoLight : public Light { explicit MonoLight(InputDeviceContext& context, const std::string& name, int32_t id, - int32_t rawId) - : Light(context, name, id, InputDeviceLightType::MONO), rawId(rawId) {} + InputDeviceLightType type, int32_t rawId) + : Light(context, name, id, type), rawId(rawId) { + capabilityFlags |= InputDeviceLightCapability::BRIGHTNESS; + } int32_t rawId; bool setLightColor(int32_t color) override; @@ -91,15 +94,15 @@ private: }; struct RgbLight : public Light { - explicit RgbLight(InputDeviceContext& context, int32_t id, + explicit RgbLight(InputDeviceContext& context, int32_t id, InputDeviceLightType type, const std::unordered_map& rawRgbIds, std::optional rawGlobalId) - : Light(context, "RGB", id, InputDeviceLightType::RGB), - rawRgbIds(rawRgbIds), - rawGlobalId(rawGlobalId) { + : Light(context, "RGB", id, type), rawRgbIds(rawRgbIds), rawGlobalId(rawGlobalId) { brightness = rawGlobalId.has_value() ? getRawLightBrightness(rawGlobalId.value()).value_or(MAX_BRIGHTNESS) : MAX_BRIGHTNESS; + capabilityFlags |= InputDeviceLightCapability::BRIGHTNESS; + capabilityFlags |= InputDeviceLightCapability::RGB; } // Map from color to raw light id. std::unordered_map rawRgbIds; @@ -114,8 +117,11 @@ private: struct MultiColorLight : public Light { explicit MultiColorLight(InputDeviceContext& context, const std::string& name, int32_t id, - int32_t rawId) - : Light(context, name, id, InputDeviceLightType::MULTI_COLOR), rawId(rawId) {} + InputDeviceLightType type, int32_t rawId) + : Light(context, name, id, type), rawId(rawId) { + capabilityFlags |= InputDeviceLightCapability::BRIGHTNESS; + capabilityFlags |= InputDeviceLightCapability::RGB; + } int32_t rawId; bool setLightColor(int32_t color) override; @@ -131,7 +137,7 @@ private: // Map from player Id to raw light Id std::unordered_map rawLightIds; - bool setLightPlayerId(int32_t palyerId) override; + bool setLightPlayerId(int32_t playerId) override; std::optional getLightPlayerId() override; void dump(std::string& dump) override; }; diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index 5cf22144a1..6b8cc257e9 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -174,6 +174,8 @@ enum class InputLightClass : uint32_t { MULTI_INTENSITY = 0x00000040, /* The input light has max brightness node. */ MAX_BRIGHTNESS = 0x00000080, + /* The input light has kbd_backlight name */ + KEYBOARD_BACKLIGHT = 0x00000100, }; enum class InputBatteryClass : uint32_t { diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index ee6993ced0..5577bd4131 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -10099,7 +10099,7 @@ protected: TEST_F(LightControllerTest, MonoLight) { RawLightInfo infoMono = {.id = 1, - .name = "Mono", + .name = "mono_light", .maxBrightness = 255, .flags = InputLightClass::BRIGHTNESS, .path = ""}; @@ -10110,7 +10110,29 @@ TEST_F(LightControllerTest, MonoLight) { controller.populateDeviceInfo(&info); std::vector lights = info.getLights(); ASSERT_EQ(1U, lights.size()); - ASSERT_EQ(InputDeviceLightType::MONO, lights[0].type); + ASSERT_EQ(InputDeviceLightType::INPUT, lights[0].type); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)); + + ASSERT_TRUE(controller.setLightColor(lights[0].id, LIGHT_BRIGHTNESS)); + ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_BRIGHTNESS); +} + +TEST_F(LightControllerTest, MonoKeyboardBacklight) { + RawLightInfo infoMono = {.id = 1, + .name = "mono_keyboard_backlight", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS | + InputLightClass::KEYBOARD_BACKLIGHT, + .path = ""}; + mFakeEventHub->addRawLightInfo(infoMono.id, std::move(infoMono)); + + PeripheralController& controller = addControllerAndConfigure(); + InputDeviceInfo info; + controller.populateDeviceInfo(&info); + std::vector lights = info.getLights(); + ASSERT_EQ(1U, lights.size()); + ASSERT_EQ(InputDeviceLightType::KEYBOARD_BACKLIGHT, lights[0].type); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)); ASSERT_TRUE(controller.setLightColor(lights[0].id, LIGHT_BRIGHTNESS)); ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_BRIGHTNESS); @@ -10141,7 +10163,85 @@ TEST_F(LightControllerTest, RGBLight) { controller.populateDeviceInfo(&info); std::vector lights = info.getLights(); ASSERT_EQ(1U, lights.size()); - ASSERT_EQ(InputDeviceLightType::RGB, lights[0].type); + ASSERT_EQ(InputDeviceLightType::INPUT, lights[0].type); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::RGB)); + + ASSERT_TRUE(controller.setLightColor(lights[0].id, LIGHT_COLOR)); + ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_COLOR); +} + +TEST_F(LightControllerTest, CorrectRGBKeyboardBacklight) { + RawLightInfo infoRed = {.id = 1, + .name = "red_keyboard_backlight", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS | InputLightClass::RED | + InputLightClass::KEYBOARD_BACKLIGHT, + .path = ""}; + RawLightInfo infoGreen = {.id = 2, + .name = "green_keyboard_backlight", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS | InputLightClass::GREEN | + InputLightClass::KEYBOARD_BACKLIGHT, + .path = ""}; + RawLightInfo infoBlue = {.id = 3, + .name = "blue_keyboard_backlight", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS | InputLightClass::BLUE | + InputLightClass::KEYBOARD_BACKLIGHT, + .path = ""}; + mFakeEventHub->addRawLightInfo(infoRed.id, std::move(infoRed)); + mFakeEventHub->addRawLightInfo(infoGreen.id, std::move(infoGreen)); + mFakeEventHub->addRawLightInfo(infoBlue.id, std::move(infoBlue)); + + PeripheralController& controller = addControllerAndConfigure(); + InputDeviceInfo info; + controller.populateDeviceInfo(&info); + std::vector lights = info.getLights(); + ASSERT_EQ(1U, lights.size()); + ASSERT_EQ(InputDeviceLightType::KEYBOARD_BACKLIGHT, lights[0].type); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::RGB)); + + ASSERT_TRUE(controller.setLightColor(lights[0].id, LIGHT_COLOR)); + ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_COLOR); +} + +TEST_F(LightControllerTest, IncorrectRGBKeyboardBacklight) { + RawLightInfo infoRed = {.id = 1, + .name = "red", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS | InputLightClass::RED, + .path = ""}; + RawLightInfo infoGreen = {.id = 2, + .name = "green", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS | InputLightClass::GREEN, + .path = ""}; + RawLightInfo infoBlue = {.id = 3, + .name = "blue", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS | InputLightClass::BLUE, + .path = ""}; + RawLightInfo infoGlobal = {.id = 3, + .name = "global_keyboard_backlight", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS | InputLightClass::GLOBAL | + InputLightClass::KEYBOARD_BACKLIGHT, + .path = ""}; + mFakeEventHub->addRawLightInfo(infoRed.id, std::move(infoRed)); + mFakeEventHub->addRawLightInfo(infoGreen.id, std::move(infoGreen)); + mFakeEventHub->addRawLightInfo(infoBlue.id, std::move(infoBlue)); + mFakeEventHub->addRawLightInfo(infoBlue.id, std::move(infoGlobal)); + + PeripheralController& controller = addControllerAndConfigure(); + InputDeviceInfo info; + controller.populateDeviceInfo(&info); + std::vector lights = info.getLights(); + ASSERT_EQ(1U, lights.size()); + ASSERT_EQ(InputDeviceLightType::INPUT, lights[0].type); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::RGB)); ASSERT_TRUE(controller.setLightColor(lights[0].id, LIGHT_COLOR)); ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_COLOR); @@ -10149,7 +10249,7 @@ TEST_F(LightControllerTest, RGBLight) { TEST_F(LightControllerTest, MultiColorRGBLight) { RawLightInfo infoColor = {.id = 1, - .name = "red", + .name = "multi_color", .maxBrightness = 255, .flags = InputLightClass::BRIGHTNESS | InputLightClass::MULTI_INTENSITY | @@ -10163,7 +10263,34 @@ TEST_F(LightControllerTest, MultiColorRGBLight) { controller.populateDeviceInfo(&info); std::vector lights = info.getLights(); ASSERT_EQ(1U, lights.size()); - ASSERT_EQ(InputDeviceLightType::MULTI_COLOR, lights[0].type); + ASSERT_EQ(InputDeviceLightType::INPUT, lights[0].type); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::RGB)); + + ASSERT_TRUE(controller.setLightColor(lights[0].id, LIGHT_COLOR)); + ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_COLOR); +} + +TEST_F(LightControllerTest, MultiColorRGBKeyboardBacklight) { + RawLightInfo infoColor = {.id = 1, + .name = "multi_color_keyboard_backlight", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS | + InputLightClass::MULTI_INTENSITY | + InputLightClass::MULTI_INDEX | + InputLightClass::KEYBOARD_BACKLIGHT, + .path = ""}; + + mFakeEventHub->addRawLightInfo(infoColor.id, std::move(infoColor)); + + PeripheralController& controller = addControllerAndConfigure(); + InputDeviceInfo info; + controller.populateDeviceInfo(&info); + std::vector lights = info.getLights(); + ASSERT_EQ(1U, lights.size()); + ASSERT_EQ(InputDeviceLightType::KEYBOARD_BACKLIGHT, lights[0].type); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)); + ASSERT_TRUE(lights[0].capabilityFlags.test(InputDeviceLightCapability::RGB)); ASSERT_TRUE(controller.setLightColor(lights[0].id, LIGHT_COLOR)); ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_COLOR); @@ -10201,6 +10328,8 @@ TEST_F(LightControllerTest, PlayerIdLight) { std::vector lights = info.getLights(); ASSERT_EQ(1U, lights.size()); ASSERT_EQ(InputDeviceLightType::PLAYER_ID, lights[0].type); + ASSERT_FALSE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)); + ASSERT_FALSE(lights[0].capabilityFlags.test(InputDeviceLightCapability::RGB)); ASSERT_FALSE(controller.setLightColor(lights[0].id, LIGHT_COLOR)); ASSERT_TRUE(controller.setLightPlayerId(lights[0].id, LIGHT_PLAYER_ID)); -- GitLab From aa721c202d675e8fca91002c9a56faba33cbb544 Mon Sep 17 00:00:00 2001 From: Reema Bajwa Date: Thu, 22 Sep 2022 23:25:06 +0000 Subject: [PATCH 0308/1310] Add feature to guard starting the credential manager system service on certain devices Test: Built and deployed locally Bug: 247851514 Change-Id: I514459a5a652dd5c8d545db502f0e5fdbe23374c --- data/etc/android.software.credentials.xml | 19 +++++++++++++++++++ data/etc/handheld_core_hardware.xml | 1 + data/etc/tablet_core_hardware.xml | 1 + 3 files changed, 21 insertions(+) create mode 100644 data/etc/android.software.credentials.xml diff --git a/data/etc/android.software.credentials.xml b/data/etc/android.software.credentials.xml new file mode 100644 index 0000000000..234d14411c --- /dev/null +++ b/data/etc/android.software.credentials.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/data/etc/handheld_core_hardware.xml b/data/etc/handheld_core_hardware.xml index 8fdd8d06d5..08330126c7 100644 --- a/data/etc/handheld_core_hardware.xml +++ b/data/etc/handheld_core_hardware.xml @@ -52,6 +52,7 @@ + diff --git a/data/etc/tablet_core_hardware.xml b/data/etc/tablet_core_hardware.xml index 59d5b10947..6af4d91418 100644 --- a/data/etc/tablet_core_hardware.xml +++ b/data/etc/tablet_core_hardware.xml @@ -52,6 +52,7 @@ + -- GitLab From 2800fb0c6b56711c6daf70491270dbea89fbf660 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Thu, 15 Sep 2022 13:49:23 +0000 Subject: [PATCH 0309/1310] Use TWO_FINGER_SWIPE classification for touchpad scroll events This allows apps to distinguish the fake finger created for touchpad scrolling from an actual finger. Bug: 246758376 Test: add classification to InputDispatcher's outbound event logs and check the new value is used when scrolling Change-Id: Ia90f9984e75ad6fde2d0e42628ab42eab371b7a5 --- include/android/input.h | 6 ++ include/input/Input.h | 4 ++ libs/input/Input.cpp | 2 + services/inputflinger/InputProcessor.cpp | 9 ++- .../reader/mapper/TouchInputMapper.cpp | 59 ++++++++++++------- .../reader/mapper/TouchInputMapper.h | 3 +- .../inputflinger/tests/InputReader_test.cpp | 8 +++ 7 files changed, 69 insertions(+), 22 deletions(-) diff --git a/include/android/input.h b/include/android/input.h index 8cd9e9551a..7080386df7 100644 --- a/include/android/input.h +++ b/include/android/input.h @@ -840,6 +840,12 @@ enum AMotionClassification : uint32_t { * This classification type should be used to accelerate the long press behaviour. */ AMOTION_EVENT_CLASSIFICATION_DEEP_PRESS = 2, + /** + * Classification constant: touchpad two-finger swipe. + * + * The current event stream represents the user swiping with two fingers on a touchpad. + */ + AMOTION_EVENT_CLASSIFICATION_TWO_FINGER_SWIPE = 3, }; /** diff --git a/include/input/Input.h b/include/input/Input.h index 2dd651e9d7..172e5b46d8 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -289,6 +289,10 @@ enum class MotionClassification : uint8_t { * The current gesture likely represents a user intentionally exerting force on the touchscreen. */ DEEP_PRESS = AMOTION_EVENT_CLASSIFICATION_DEEP_PRESS, + /** + * The current gesture represents the user swiping with two fingers on a touchpad. + */ + TWO_FINGER_SWIPE = AMOTION_EVENT_CLASSIFICATION_TWO_FINGER_SWIPE, }; /** diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index 2b7483d27d..579b28e588 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -87,6 +87,8 @@ const char* motionClassificationToString(MotionClassification classification) { return "AMBIGUOUS_GESTURE"; case MotionClassification::DEEP_PRESS: return "DEEP_PRESS"; + case MotionClassification::TWO_FINGER_SWIPE: + return "TWO_FINGER_SWIPE"; } } diff --git a/services/inputflinger/InputProcessor.cpp b/services/inputflinger/InputProcessor.cpp index 0749441694..126cb33b8d 100644 --- a/services/inputflinger/InputProcessor.cpp +++ b/services/inputflinger/InputProcessor.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -436,7 +437,13 @@ void InputProcessor::notifyMotion(const NotifyMotionArgs* args) { mQueuedListener.notifyMotion(args); } else { NotifyMotionArgs newArgs(*args); - newArgs.classification = mMotionClassifier->classify(newArgs); + const MotionClassification newClassification = mMotionClassifier->classify(newArgs); + LOG_ALWAYS_FATAL_IF(args->classification != MotionClassification::NONE && + newClassification != MotionClassification::NONE, + "Conflicting classifications %s (new) and %s (old)!", + motionClassificationToString(newClassification), + motionClassificationToString(args->classification)); + newArgs.classification = newClassification; mQueuedListener.notifyMotion(&newArgs); } } // release lock diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 8c241f2f09..4cd2cce9f9 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -1950,7 +1950,8 @@ void TouchInputMapper::abortTouches(nsecs_t when, nsecs_t readTime, uint32_t pol mCurrentCookedState.cookedPointerData.pointerProperties, mCurrentCookedState.cookedPointerData.pointerCoords, mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime); + mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE); mCurrentMotionAborted = true; } } @@ -1970,7 +1971,8 @@ void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t mCurrentCookedState.cookedPointerData.pointerProperties, mCurrentCookedState.cookedPointerData.pointerCoords, mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime); + mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE); } } else { // There may be pointers going up and pointers going down and pointers moving @@ -2005,7 +2007,8 @@ void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t mLastCookedState.cookedPointerData.pointerProperties, mLastCookedState.cookedPointerData.pointerCoords, mLastCookedState.cookedPointerData.idToIndex, dispatchedIdBits, upId, - mOrientedXPrecision, mOrientedYPrecision, mDownTime); + mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE); dispatchedIdBits.clearBit(upId); mCurrentCookedState.cookedPointerData.canceledIdBits.clearBit(upId); } @@ -2020,7 +2023,8 @@ void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t mCurrentCookedState.cookedPointerData.pointerProperties, mCurrentCookedState.cookedPointerData.pointerCoords, mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime); + mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE); } // Dispatch pointer down events using the new pointer locations. @@ -2038,7 +2042,8 @@ void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t mCurrentCookedState.cookedPointerData.pointerProperties, mCurrentCookedState.cookedPointerData.pointerCoords, mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits, - downId, mOrientedXPrecision, mOrientedYPrecision, mDownTime); + downId, mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE); } } } @@ -2054,7 +2059,7 @@ void TouchInputMapper::dispatchHoverExit(nsecs_t when, nsecs_t readTime, uint32_ mLastCookedState.cookedPointerData.pointerCoords, mLastCookedState.cookedPointerData.idToIndex, mLastCookedState.cookedPointerData.hoveringIdBits, -1, mOrientedXPrecision, - mOrientedYPrecision, mDownTime); + mOrientedYPrecision, mDownTime, MotionClassification::NONE); mSentHoverEnter = false; } } @@ -2071,7 +2076,8 @@ void TouchInputMapper::dispatchHoverEnterAndMove(nsecs_t when, nsecs_t readTime, mCurrentCookedState.cookedPointerData.pointerCoords, mCurrentCookedState.cookedPointerData.idToIndex, mCurrentCookedState.cookedPointerData.hoveringIdBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime); + mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE); mSentHoverEnter = true; } @@ -2081,7 +2087,8 @@ void TouchInputMapper::dispatchHoverEnterAndMove(nsecs_t when, nsecs_t readTime, mCurrentCookedState.cookedPointerData.pointerCoords, mCurrentCookedState.cookedPointerData.idToIndex, mCurrentCookedState.cookedPointerData.hoveringIdBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime); + mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE); } } @@ -2098,7 +2105,8 @@ void TouchInputMapper::dispatchButtonRelease(nsecs_t when, nsecs_t readTime, uin mCurrentCookedState.cookedPointerData.pointerProperties, mCurrentCookedState.cookedPointerData.pointerCoords, mCurrentCookedState.cookedPointerData.idToIndex, idBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime); + mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE); } } @@ -2115,7 +2123,8 @@ void TouchInputMapper::dispatchButtonPress(nsecs_t when, nsecs_t readTime, uint3 mCurrentCookedState.cookedPointerData.pointerProperties, mCurrentCookedState.cookedPointerData.pointerCoords, mCurrentCookedState.cookedPointerData.idToIndex, idBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime); + mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE); } } @@ -2502,6 +2511,10 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u // Send events! int32_t metaState = getContext()->getGlobalMetaState(); int32_t buttonState = mCurrentCookedState.buttonState; + const MotionClassification classification = + mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE + ? MotionClassification::TWO_FINGER_SWIPE + : MotionClassification::NONE; uint32_t flags = 0; @@ -2542,7 +2555,7 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u flags, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, dispatchedGestureIdBits, -1, 0, 0, - mPointerGesture.downTime); + mPointerGesture.downTime, classification); dispatchedGestureIdBits.clear(); } else { @@ -2561,7 +2574,7 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u AMOTION_EVENT_EDGE_FLAG_NONE, mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, dispatchedGestureIdBits, id, 0, - 0, mPointerGesture.downTime); + 0, mPointerGesture.downTime, classification); dispatchedGestureIdBits.clearBit(id); } @@ -2575,7 +2588,7 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u mPointerGesture.currentGestureProperties, mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, dispatchedGestureIdBits, -1, 0, 0, - mPointerGesture.downTime); + mPointerGesture.downTime, classification); } // Send motion events for all pointers that went down. @@ -2595,7 +2608,7 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u mPointerGesture.currentGestureProperties, mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, dispatchedGestureIdBits, id, 0, - 0, mPointerGesture.downTime); + 0, mPointerGesture.downTime, classification); } } @@ -2606,7 +2619,8 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u mPointerGesture.currentGestureProperties, mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, - mPointerGesture.currentGestureIdBits, -1, 0, 0, mPointerGesture.downTime); + mPointerGesture.currentGestureIdBits, -1, 0, 0, mPointerGesture.downTime, + MotionClassification::NONE); } else if (dispatchedGestureIdBits.isEmpty() && !mPointerGesture.lastGestureIdBits.isEmpty()) { // Synthesize a hover move event after all pointers go up to indicate that // the pointer is hovering again even if the user is not currently touching @@ -2653,6 +2667,10 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u } void TouchInputMapper::abortPointerGestures(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { + const MotionClassification classification = + mPointerGesture.lastGestureMode == PointerGesture::Mode::SWIPE + ? MotionClassification::TWO_FINGER_SWIPE + : MotionClassification::NONE; // Cancel previously dispatches pointers. if (!mPointerGesture.lastGestureIdBits.isEmpty()) { int32_t metaState = getContext()->getGlobalMetaState(); @@ -2661,7 +2679,7 @@ void TouchInputMapper::abortPointerGestures(nsecs_t when, nsecs_t readTime, uint metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, mPointerGesture.lastGestureIdBits, -1, - 0, 0, mPointerGesture.downTime); + 0, 0, mPointerGesture.downTime, classification); } // Reset the current pointer gesture. @@ -3611,7 +3629,8 @@ void TouchInputMapper::dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t p int32_t edgeFlags, const PointerProperties* properties, const PointerCoords* coords, const uint32_t* idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision, - float yPrecision, nsecs_t downTime) { + float yPrecision, nsecs_t downTime, + MotionClassification classification) { PointerCoords pointerCoords[MAX_POINTERS]; PointerProperties pointerProperties[MAX_POINTERS]; uint32_t pointerCount = 0; @@ -3659,9 +3678,9 @@ void TouchInputMapper::dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t p [this](TouchVideoFrame& frame) { frame.rotate(this->mInputDeviceOrientation); }); NotifyMotionArgs args(getContext()->getNextId(), when, readTime, deviceId, source, displayId, policyFlags, action, actionButton, flags, metaState, buttonState, - MotionClassification::NONE, edgeFlags, pointerCount, pointerProperties, - pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition, - downTime, std::move(frames)); + classification, edgeFlags, pointerCount, pointerProperties, pointerCoords, + xPrecision, yPrecision, xCursorPosition, yCursorPosition, downTime, + std::move(frames)); getListener().notifyMotion(&args); } diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index bd4cff6d9a..31806e12e3 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -779,7 +779,8 @@ private: int32_t action, int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState, int32_t edgeFlags, const PointerProperties* properties, const PointerCoords* coords, const uint32_t* idToIndex, BitSet32 idBits, - int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime); + int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime, + MotionClassification classification); // Updates pointer coords and properties for pointers with specified ids that have moved. // Returns true if any of them changed. diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index ee6993ced0..097659b631 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -9751,6 +9751,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthSwipe) { ASSERT_EQ(1U, motionArgs.pointerCount); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); ASSERT_NO_FATAL_FAILURE( assertPointerCoords(motionArgs.pointerCoords[0], 0, 0, 1, 0, 0, 0, 0, 0, 0, 0)); @@ -9772,6 +9773,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthSwipe) { ASSERT_EQ(1U, motionArgs.pointerCount); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(MotionClassification::TWO_FINGER_SWIPE, motionArgs.classification); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], 0, movingDistance * mPointerMovementScale, 1, 0, 0, 0, 0, 0, 0, 0)); @@ -9809,6 +9811,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthLowResolutionSwipe) ASSERT_EQ(1U, motionArgs.pointerCount); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); ASSERT_NO_FATAL_FAILURE( assertPointerCoords(motionArgs.pointerCoords[0], 0, 0, 1, 0, 0, 0, 0, 0, 0, 0)); @@ -9830,6 +9833,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthLowResolutionSwipe) ASSERT_EQ(1U, motionArgs.pointerCount); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(MotionClassification::TWO_FINGER_SWIPE, motionArgs.classification); // New coordinate is the scaled relative coordinate from the initial coordinate. ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], 0, movingDistance * mPointerMovementScale, 1, 0, 0, 0, @@ -9863,6 +9867,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthFreeform) { ASSERT_EQ(1U, motionArgs.pointerCount); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); // One pointer for PRESS, and its coordinate is used as the origin for pointer coordinates. ASSERT_NO_FATAL_FAILURE( assertPointerCoords(motionArgs.pointerCoords[0], 0, 0, 1, 0, 0, 0, 0, 0, 0, 0)); @@ -9892,9 +9897,11 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthFreeform) { ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); ASSERT_EQ(2U, motionArgs.pointerCount); ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN, motionArgs.action & AMOTION_EVENT_ACTION_MASK); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); // Two pointers' scaled relative coordinates from their initial centroid. // Initial y coordinates are 0 as y1 and y2 have the same value. float cookedX1 = (x1 - x2) / 2 * mPointerXZoomScale; @@ -9924,6 +9931,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthFreeform) { ASSERT_EQ(2U, motionArgs.pointerCount); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], cookedX1, movingDistance * 2 * mPointerMovementScale, 1, 0, 0, 0, 0, 0, 0, 0)); -- GitLab From 5ae265bf4a01ec55d70983e6054fd0a0ac380ab2 Mon Sep 17 00:00:00 2001 From: Nataniel Borges Date: Sat, 16 Jul 2022 16:17:20 +0000 Subject: [PATCH 0310/1310] Migrate flicker to proto lite instead of nano Proto nano is deprecated and no longer developed nor supported. Test: atest FlickerLibTest Change-Id: I8ed40b09617dfb12fc04957224c1ee81442918d6 --- services/surfaceflinger/layerproto/Android.bp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/services/surfaceflinger/layerproto/Android.bp b/services/surfaceflinger/layerproto/Android.bp index 973a4392a2..7287dd0103 100644 --- a/services/surfaceflinger/layerproto/Android.bp +++ b/services/surfaceflinger/layerproto/Android.bp @@ -44,10 +44,11 @@ cc_library { } java_library_static { - name: "layersprotosnano", + name: "layersprotoslite", host_supported: true, proto: { - type: "nano", + type: "lite", + include_dirs: ["external/protobuf/src"], }, srcs: ["*.proto"], sdk_version: "core_platform", @@ -56,7 +57,7 @@ java_library_static { jarjar_rules: "jarjar-rules.txt", }, host: { - static_libs: ["libprotobuf-java-nano"], + static_libs: ["libprotobuf-java-lite"], }, }, } -- GitLab From d4f479322dca227138e3bccb7713c2d3ce8338f0 Mon Sep 17 00:00:00 2001 From: tsaichristine Date: Fri, 9 Sep 2022 16:26:31 -0700 Subject: [PATCH 0311/1310] StatsAidl implementation for new repeated and nested field types Bug: 237423972 Test: build, flash, and run aidl_stats_client on device Test: atest VtsAidlHalStatsTargetTest Change-Id: Idb17804e8ed1d6dd30d1421d2f8bdb65a0906f6d --- services/stats/StatsAidl.cpp | 55 ++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/services/stats/StatsAidl.cpp b/services/stats/StatsAidl.cpp index 8d6a9bdb5a..3de51a437d 100644 --- a/services/stats/StatsAidl.cpp +++ b/services/stats/StatsAidl.cpp @@ -66,6 +66,61 @@ ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) { AStatsEvent_writeBool(event, atomValue.get()); break; + case VendorAtomValue::repeatedIntValue: { + const std::optional>& repeatedIntValue = + atomValue.get(); + AStatsEvent_writeInt32Array(event, repeatedIntValue->data(), + repeatedIntValue->size()); + break; + } + case VendorAtomValue::repeatedLongValue: { + const std::optional>& repeatedLongValue = + atomValue.get(); + AStatsEvent_writeInt64Array(event, repeatedLongValue->data(), + repeatedLongValue->size()); + break; + } + case VendorAtomValue::repeatedFloatValue: { + const std::optional>& repeatedFloatValue = + atomValue.get(); + AStatsEvent_writeFloatArray(event, repeatedFloatValue->data(), + repeatedFloatValue->size()); + break; + } + case VendorAtomValue::repeatedStringValue: { + const std::optional>>& repeatedStringValue = + atomValue.get(); + const std::vector>& repeatedStringVector = + *repeatedStringValue; + const char* cStringArray[repeatedStringVector.size()]; + + for (int i = 0; i < repeatedStringVector.size(); ++i) { + cStringArray[i] = repeatedStringVector[i]->c_str(); + } + + AStatsEvent_writeStringArray(event, cStringArray, repeatedStringVector.size()); + break; + } + case VendorAtomValue::repeatedBoolValue: { + const std::optional>& repeatedBoolValue = + atomValue.get(); + const std::vector& repeatedBoolVector = *repeatedBoolValue; + bool boolArray[repeatedBoolValue->size()]; + + for (int i = 0; i < repeatedBoolVector.size(); ++i) { + boolArray[i] = repeatedBoolVector[i]; + } + + AStatsEvent_writeBoolArray(event, boolArray, repeatedBoolVector.size()); + break; + } + case VendorAtomValue::byteArrayValue: { + const std::optional>& byteArrayValue = + atomValue.get(); + + AStatsEvent_writeByteArray(event, byteArrayValue->data(), byteArrayValue->size()); + break; + } } } AStatsEvent_build(event); -- GitLab From 9476468faca9d185b4ced3f91628139fda175017 Mon Sep 17 00:00:00 2001 From: Trevor David Black Date: Mon, 26 Sep 2022 20:31:28 +0000 Subject: [PATCH 0312/1310] Increased the Android blob cache size from 2mb to 32mb Documentation available at: go/android-platform-blobcache-improvements go/angle-shader-caching Data & Results available at: go/angle-shader-caching-perf Test: Build & run w/ any applications Bug: b/246966894, b/240607708, b/228054341 Change-Id: I1d7979d1b6a0b6b02c74fc30d674947ed113f3f4 --- opengl/libs/EGL/egl_cache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp index efa67dbc1c..8348d6cb0a 100644 --- a/opengl/libs/EGL/egl_cache.cpp +++ b/opengl/libs/EGL/egl_cache.cpp @@ -28,7 +28,7 @@ // Cache size limits. static const size_t maxKeySize = 12 * 1024; static const size_t maxValueSize = 64 * 1024; -static const size_t maxTotalSize = 2 * 1024 * 1024; +static const size_t maxTotalSize = 32 * 1024 * 1024; // The time in seconds to wait before saving newly inserted cache entries. static const unsigned int deferredSaveDelay = 4; -- GitLab From 31b5ac21e84a0fde6483dc8487af664582dcc5ee Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Mon, 15 Aug 2022 20:38:10 -0700 Subject: [PATCH 0313/1310] Remove internal display related methods SurfaceFlinger and SurfaceComposerClient simply return the first connected display as internal display, which is not really correct. In particular,in the case of dual display for foldable devices, both displays are marked as Internal determined by calling into HWC2 IComposerClient::getDisplayConnectionType(). Therefore, the concept of internal/external/primary displays is removed from SurfaceFlinger, and the display manager is the better place to handle the logics. flatland is modified to take an extra argument to specify display id, and error occurs if no display is specified in case of multi-display. Bug: 241285477 Bug: 242763577 Bug: 74619554 Test: atest libgui_test libsurfaceflinger_unittest SurfaceFlinger_test Change-Id: Ib6c7e502ef3269c2c60a4e5388e5ac75275f87ed --- cmds/flatland/GLHelper.cpp | 13 ++-- cmds/flatland/GLHelper.h | 4 +- cmds/flatland/Main.cpp | 64 +++++++++++++++---- libs/gui/Surface.cpp | 37 +++++------ libs/gui/SurfaceComposerClient.cpp | 10 --- libs/gui/include/gui/Surface.h | 4 +- libs/gui/include/gui/SurfaceComposerClient.h | 2 - .../include/private/gui/ComposerServiceAIDL.h | 22 ------- libs/gui/tests/BLASTBufferQueue_test.cpp | 5 +- .../tests/DisplayedContentSampling_test.cpp | 5 +- libs/gui/tests/EndToEndNativeInputTest.cpp | 6 +- libs/gui/tests/Surface_test.cpp | 5 +- libs/nativedisplay/ADisplay.cpp | 9 +-- libs/nativewindow/include/system/window.h | 35 +++++++--- .../benchmark/RenderEngineBench.cpp | 28 +++++--- opengl/tests/lib/WindowSurface.cpp | 9 ++- .../surfaceflinger_displayhardware_fuzzer.cpp | 2 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 12 +++- .../tests/BootDisplayMode_test.cpp | 9 ++- .../surfaceflinger/tests/Credentials_test.cpp | 33 ++++++---- .../tests/DisplayConfigs_test.cpp | 6 +- .../surfaceflinger/tests/EffectLayer_test.cpp | 8 ++- services/surfaceflinger/tests/IPC_test.cpp | 4 +- .../surfaceflinger/tests/LayerBorder_test.cpp | 4 +- .../tests/LayerTransactionTest.h | 4 +- .../surfaceflinger/tests/LayerUpdate_test.cpp | 4 +- .../surfaceflinger/tests/MirrorLayer_test.cpp | 13 +++- .../tests/MultiDisplayLayerBounds_test.cpp | 4 +- .../surfaceflinger/tests/RelativeZ_test.cpp | 4 +- .../tests/ScreenCapture_test.cpp | 4 +- .../tests/SurfaceInterceptor_test.cpp | 4 +- .../tests/TransactionTestHarnesses.h | 5 +- .../tests/utils/ScreenshotUtils.h | 6 +- 33 files changed, 244 insertions(+), 140 deletions(-) diff --git a/cmds/flatland/GLHelper.cpp b/cmds/flatland/GLHelper.cpp index 01f7d30a42..c163095c50 100644 --- a/cmds/flatland/GLHelper.cpp +++ b/cmds/flatland/GLHelper.cpp @@ -35,9 +35,12 @@ GLHelper::GLHelper() : GLHelper::~GLHelper() { } -bool GLHelper::setUp(const ShaderDesc* shaderDescs, size_t numShaders) { +bool GLHelper::setUp(const sp& displayToken, const ShaderDesc* shaderDescs, + size_t numShaders) { bool result; + mDisplayToken = displayToken; + mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (mDisplay == EGL_NO_DISPLAY) { fprintf(stderr, "eglGetDisplay error: %#x\n", eglGetError()); @@ -221,14 +224,8 @@ bool GLHelper::createNamedSurfaceTexture(GLuint name, uint32_t w, uint32_t h, } bool GLHelper::computeWindowScale(uint32_t w, uint32_t h, float* scale) { - const sp dpy = mSurfaceComposerClient->getInternalDisplayToken(); - if (dpy == nullptr) { - fprintf(stderr, "SurfaceComposer::getInternalDisplayToken failed.\n"); - return false; - } - ui::DisplayMode mode; - status_t err = mSurfaceComposerClient->getActiveDisplayMode(dpy, &mode); + status_t err = mSurfaceComposerClient->getActiveDisplayMode(mDisplayToken, &mode); if (err != NO_ERROR) { fprintf(stderr, "SurfaceComposer::getActiveDisplayMode failed: %#x\n", err); return false; diff --git a/cmds/flatland/GLHelper.h b/cmds/flatland/GLHelper.h index d09463a9b8..5194f5084f 100644 --- a/cmds/flatland/GLHelper.h +++ b/cmds/flatland/GLHelper.h @@ -44,7 +44,7 @@ public: ~GLHelper(); - bool setUp(const ShaderDesc* shaderDescs, size_t numShaders); + bool setUp(const sp& displayToken, const ShaderDesc* shaderDescs, size_t numShaders); void tearDown(); @@ -87,6 +87,8 @@ private: size_t mNumShaders; GLuint mDitherTexture; + + sp mDisplayToken; }; } // namespace android diff --git a/cmds/flatland/Main.cpp b/cmds/flatland/Main.cpp index 7ceb397032..6d14d568a4 100644 --- a/cmds/flatland/Main.cpp +++ b/cmds/flatland/Main.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -34,9 +35,10 @@ using namespace ::android; -static uint32_t g_SleepBetweenSamplesMs = 0; -static bool g_PresentToWindow = false; -static size_t g_BenchmarkNameLen = 0; +static uint32_t g_SleepBetweenSamplesMs = 0; +static bool g_PresentToWindow = false; +static size_t g_BenchmarkNameLen = 0; +static sp g_DisplayToken = nullptr; struct BenchmarkDesc { // The name of the test. @@ -393,7 +395,7 @@ public: uint32_t h = mDesc.runHeights[mInstance]; mGLHelper = new GLHelper(); - result = mGLHelper->setUp(shaders, NELEMS(shaders)); + result = mGLHelper->setUp(g_DisplayToken, shaders, NELEMS(shaders)); if (!result) { return false; } @@ -718,13 +720,17 @@ static size_t maxBenchmarkNameLen() { } // Print the command usage help to stderr. -static void showHelp(const char *cmd) { - fprintf(stderr, "usage: %s [options]\n", cmd); - fprintf(stderr, "options include:\n" - " -s N sleep for N ms between samples\n" - " -d display the test frame to a window\n" - " --help print this helpful message and exit\n" - ); +static void showHelp(const char* cmd) { + fprintf(stderr, "usage: %s [options]\n", cmd); + fprintf( + stderr, + "options include:\n" + " -s N sleep for N ms between samples\n" + " -d display the test frame to a window\n" + " -i display-id specify a display ID to use for multi-display device\n" + " see \"dumpsys SurfaceFlinger --display-id\" for valid " + "display IDs\n" + " --help print this helpful message and exit\n"); } int main(int argc, char** argv) { @@ -733,6 +739,14 @@ int main(int argc, char** argv) { exit(0); } + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + if (ids.empty()) { + fprintf(stderr, "Failed to get ID for any displays.\n"); + exit(3); + } + + std::optional displayId; + for (;;) { int ret; int option_index = 0; @@ -741,7 +755,7 @@ int main(int argc, char** argv) { { 0, 0, 0, 0 } }; - ret = getopt_long(argc, argv, "ds:", + ret = getopt_long(argc, argv, "ds:i:", long_options, &option_index); if (ret < 0) { @@ -757,6 +771,14 @@ int main(int argc, char** argv) { g_SleepBetweenSamplesMs = atoi(optarg); break; + case 'i': + displayId = DisplayId::fromValue(atoll(optarg)); + if (!displayId) { + fprintf(stderr, "Invalid display ID: %s.\n", optarg); + exit(4); + } + break; + case 0: if (strcmp(long_options[option_index].name, "help")) { showHelp(argv[0]); @@ -770,6 +792,22 @@ int main(int argc, char** argv) { } } + if (!displayId) { // no display id is specified + if (ids.size() == 1) { + displayId = ids.front(); + } else { + fprintf(stderr, "Please specify a display ID for multi-display device.\n"); + showHelp(argv[0]); + exit(5); + } + } + + g_DisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(*displayId); + if (g_DisplayToken == nullptr) { + fprintf(stderr, "SurfaceComposer::getPhysicalDisplayToken failed.\n"); + exit(6); + } + g_BenchmarkNameLen = maxBenchmarkNameLen(); printf(" cmdline:"); @@ -782,4 +820,6 @@ int main(int argc, char** argv) { fprintf(stderr, "exiting due to error.\n"); return 1; } + + return 0; } diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index 4b4d46a92e..c4fb1cf408 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -56,6 +56,12 @@ using ui::Dataspace; namespace { +enum { + // moved from nativewindow/include/system/window.h, to be removed + NATIVE_WINDOW_GET_WIDE_COLOR_SUPPORT = 28, + NATIVE_WINDOW_GET_HDR_SUPPORT = 29, +}; + bool isInterceptorRegistrationOp(int op) { return op == NATIVE_WINDOW_SET_CANCEL_INTERCEPTOR || op == NATIVE_WINDOW_SET_DEQUEUE_INTERCEPTOR || @@ -348,34 +354,25 @@ status_t Surface::getFrameTimestamps(uint64_t frameNumber, return NO_ERROR; } +// Deprecated(b/242763577): to be removed, this method should not be used +// The reason this method still exists here is to support compiled vndk +// Surface support should not be tied to the display +// Return true since most displays should have this support status_t Surface::getWideColorSupport(bool* supported) { ATRACE_CALL(); - const sp display = ComposerServiceAIDL::getInstance().getInternalDisplayToken(); - if (display == nullptr) { - return NAME_NOT_FOUND; - } - - *supported = false; - binder::Status status = composerServiceAIDL()->isWideColorDisplay(display, supported); - return statusTFromBinderStatus(status); + *supported = true; + return NO_ERROR; } +// Deprecated(b/242763577): to be removed, this method should not be used +// The reason this method still exists here is to support compiled vndk +// Surface support should not be tied to the display +// Return true since most displays should have this support status_t Surface::getHdrSupport(bool* supported) { ATRACE_CALL(); - const sp display = ComposerServiceAIDL::getInstance().getInternalDisplayToken(); - if (display == nullptr) { - return NAME_NOT_FOUND; - } - - gui::DynamicDisplayInfo info; - if (binder::Status status = composerServiceAIDL()->getDynamicDisplayInfo(display, &info); - !status.isOk()) { - return statusTFromBinderStatus(status); - } - - *supported = !info.hdrCapabilities.supportedHdrTypes.empty(); + *supported = true; return NO_ERROR; } diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 751721e2a8..18366ab083 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -1091,11 +1091,6 @@ std::vector SurfaceComposerClient::getPhysicalDisplayIds() { return physicalDisplayIds; } -std::optional SurfaceComposerClient::getInternalDisplayId() { - ComposerServiceAIDL& instance = ComposerServiceAIDL::getInstance(); - return instance.getInternalDisplayId(); -} - sp SurfaceComposerClient::getPhysicalDisplayToken(PhysicalDisplayId displayId) { sp display = nullptr; binder::Status status = @@ -1104,11 +1099,6 @@ sp SurfaceComposerClient::getPhysicalDisplayToken(PhysicalDisplayId dis return status.isOk() ? display : nullptr; } -sp SurfaceComposerClient::getInternalDisplayToken() { - ComposerServiceAIDL& instance = ComposerServiceAIDL::getInstance(); - return instance.getInternalDisplayToken(); -} - void SurfaceComposerClient::Transaction::setAnimationTransaction() { mAnimation = true; } diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h index 634acb68b9..1f19f4e1da 100644 --- a/libs/gui/include/gui/Surface.h +++ b/libs/gui/include/gui/Surface.h @@ -187,8 +187,8 @@ public: nsecs_t* outDisplayPresentTime, nsecs_t* outDequeueReadyTime, nsecs_t* outReleaseTime); - status_t getWideColorSupport(bool* supported); - status_t getHdrSupport(bool* supported); + status_t getWideColorSupport(bool* supported) __attribute__((__deprecated__)); + status_t getHdrSupport(bool* supported) __attribute__((__deprecated__)); status_t getUniqueId(uint64_t* outId) const; status_t getConsumerUsage(uint64_t* outUsage) const; diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 8c47ebc3b8..a688a52495 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -351,11 +351,9 @@ public: //! Get stable IDs for connected physical displays static std::vector getPhysicalDisplayIds(); - static std::optional getInternalDisplayId(); //! Get token for a physical display given its stable ID static sp getPhysicalDisplayToken(PhysicalDisplayId displayId); - static sp getInternalDisplayToken(); static status_t enableVSyncInjections(bool enable); diff --git a/libs/gui/include/private/gui/ComposerServiceAIDL.h b/libs/gui/include/private/gui/ComposerServiceAIDL.h index 296358329b..6352a5851c 100644 --- a/libs/gui/include/private/gui/ComposerServiceAIDL.h +++ b/libs/gui/include/private/gui/ComposerServiceAIDL.h @@ -51,28 +51,6 @@ public: // Get a connection to the Composer Service. This will block until // a connection is established. Returns null if permission is denied. static sp getComposerService(); - - // the following two methods are moved from ISurfaceComposer.h - // TODO(b/74619554): Remove this stopgap once the framework is display-agnostic. - std::optional getInternalDisplayId() const { - std::vector displayIds; - binder::Status status = mComposerService->getPhysicalDisplayIds(&displayIds); - return (!status.isOk() || displayIds.empty()) - ? std::nullopt - : DisplayId::fromValue( - static_cast(displayIds.front())); - } - - // TODO(b/74619554): Remove this stopgap once the framework is display-agnostic. - sp getInternalDisplayToken() const { - const auto displayId = getInternalDisplayId(); - if (!displayId) return nullptr; - sp display; - binder::Status status = - mComposerService->getPhysicalDisplayToken(static_cast(displayId->value), - &display); - return status.isOk() ? display : nullptr; - } }; // --------------------------------------------------------------------------- diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp index c4c2fa5b85..cf2593dc81 100644 --- a/libs/gui/tests/BLASTBufferQueue_test.cpp +++ b/libs/gui/tests/BLASTBufferQueue_test.cpp @@ -188,7 +188,10 @@ protected: void SetUp() { mComposer = ComposerService::getComposerService(); mClient = new SurfaceComposerClient(); - mDisplayToken = mClient->getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + // display 0 is picked as this test is not much display depedent + mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_NE(nullptr, mDisplayToken.get()); Transaction t; t.setDisplayLayerStack(mDisplayToken, ui::DEFAULT_LAYER_STACK); diff --git a/libs/gui/tests/DisplayedContentSampling_test.cpp b/libs/gui/tests/DisplayedContentSampling_test.cpp index b647aaba8f..0a2750a4dd 100644 --- a/libs/gui/tests/DisplayedContentSampling_test.cpp +++ b/libs/gui/tests/DisplayedContentSampling_test.cpp @@ -32,7 +32,10 @@ protected: void SetUp() { mComposerClient = new SurfaceComposerClient; ASSERT_EQ(OK, mComposerClient->initCheck()); - mDisplayToken = mComposerClient->getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + // display 0 is picked for now, can extend to support all displays if needed + mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_TRUE(mDisplayToken); } diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp index 2637f59b5e..3344e0b690 100644 --- a/libs/gui/tests/EndToEndNativeInputTest.cpp +++ b/libs/gui/tests/EndToEndNativeInputTest.cpp @@ -360,8 +360,10 @@ public: void SetUp() { mComposerClient = new SurfaceComposerClient; ASSERT_EQ(NO_ERROR, mComposerClient->initCheck()); - - const auto display = mComposerClient->getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + // display 0 is picked for now, can extend to support all displays if needed + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_NE(display, nullptr); ui::DisplayMode mode; diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index b9358e7717..c078378118 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -263,7 +263,10 @@ TEST_F(SurfaceTest, ScreenshotsOfProtectedBuffersDontSucceed) { sp anw(mSurface); // Verify the screenshot works with no protected buffers. - const sp display = ComposerServiceAIDL::getInstance().getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + // display 0 is picked for now, can extend to support all displays if needed + const sp display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_FALSE(display == nullptr); DisplayCaptureArgs captureArgs; diff --git a/libs/nativedisplay/ADisplay.cpp b/libs/nativedisplay/ADisplay.cpp index 76b85d6002..60328e48a6 100644 --- a/libs/nativedisplay/ADisplay.cpp +++ b/libs/nativedisplay/ADisplay.cpp @@ -136,6 +136,7 @@ int ADisplay_acquirePhysicalDisplays(ADisplay*** outDisplays) { } std::vector modesPerDisplay[size]; + ui::DisplayConnectionType displayConnectionTypes[size]; int numModes = 0; for (int i = 0; i < size; ++i) { const sp token = SurfaceComposerClient::getPhysicalDisplayToken(ids[i]); @@ -145,6 +146,7 @@ int ADisplay_acquirePhysicalDisplays(ADisplay*** outDisplays) { status != OK) { return status; } + displayConnectionTypes[i] = staticInfo.connectionType; ui::DynamicDisplayInfo dynamicInfo; if (const status_t status = @@ -168,8 +170,6 @@ int ADisplay_acquirePhysicalDisplays(ADisplay*** outDisplays) { } } - const std::optional internalId = - SurfaceComposerClient::getInternalDisplayId(); ui::Dataspace defaultDataspace; ui::PixelFormat defaultPixelFormat; ui::Dataspace wcgDataspace; @@ -201,8 +201,9 @@ int ADisplay_acquirePhysicalDisplays(ADisplay*** outDisplays) { for (size_t i = 0; i < size; ++i) { const PhysicalDisplayId id = ids[i]; - const ADisplayType type = (internalId == id) ? ADisplayType::DISPLAY_TYPE_INTERNAL - : ADisplayType::DISPLAY_TYPE_EXTERNAL; + const ADisplayType type = (displayConnectionTypes[i] == ui::DisplayConnectionType::Internal) + ? ADisplayType::DISPLAY_TYPE_INTERNAL + : ADisplayType::DISPLAY_TYPE_EXTERNAL; const std::vector& configs = modesPerDisplay[i]; memcpy(configData, configs.data(), sizeof(DisplayConfigImpl) * configs.size()); diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h index 79f49e1786..c7745e6672 100644 --- a/libs/nativewindow/include/system/window.h +++ b/libs/nativewindow/include/system/window.h @@ -235,8 +235,8 @@ enum { NATIVE_WINDOW_ENABLE_FRAME_TIMESTAMPS = 25, NATIVE_WINDOW_GET_COMPOSITOR_TIMING = 26, NATIVE_WINDOW_GET_FRAME_TIMESTAMPS = 27, - NATIVE_WINDOW_GET_WIDE_COLOR_SUPPORT = 28, - NATIVE_WINDOW_GET_HDR_SUPPORT = 29, + /* 28, removed: NATIVE_WINDOW_GET_WIDE_COLOR_SUPPORT */ + /* 29, removed: NATIVE_WINDOW_GET_HDR_SUPPORT */ NATIVE_WINDOW_SET_USAGE64 = ANATIVEWINDOW_PERFORM_SET_USAGE64, NATIVE_WINDOW_GET_CONSUMER_USAGE64 = 31, NATIVE_WINDOW_SET_BUFFERS_SMPTE2086_METADATA = 32, @@ -988,15 +988,34 @@ static inline int native_window_get_frame_timestamps( outDequeueReadyTime, outReleaseTime); } -static inline int native_window_get_wide_color_support( - struct ANativeWindow* window, bool* outSupport) { - return window->perform(window, NATIVE_WINDOW_GET_WIDE_COLOR_SUPPORT, - outSupport); +/* deprecated. Always returns 0 and outSupport holds true. Don't call. */ +static inline int native_window_get_wide_color_support ( + struct ANativeWindow* window __UNUSED, bool* outSupport) __deprecated; + +/* + Deprecated(b/242763577): to be removed, this method should not be used + Surface support should not be tied to the display + Return true since most displays should have this support +*/ +static inline int native_window_get_wide_color_support ( + struct ANativeWindow* window __UNUSED, bool* outSupport) { + *outSupport = true; + return 0; } -static inline int native_window_get_hdr_support(struct ANativeWindow* window, +/* deprecated. Always returns 0 and outSupport holds true. Don't call. */ +static inline int native_window_get_hdr_support(struct ANativeWindow* window __UNUSED, + bool* outSupport) __deprecated; + +/* + Deprecated(b/242763577): to be removed, this method should not be used + Surface support should not be tied to the display + Return true since most displays should have this support +*/ +static inline int native_window_get_hdr_support(struct ANativeWindow* window __UNUSED, bool* outSupport) { - return window->perform(window, NATIVE_WINDOW_GET_HDR_SUPPORT, outSupport); + *outSupport = true; + return 0; } static inline int native_window_get_consumer_usage(struct ANativeWindow* window, diff --git a/libs/renderengine/benchmark/RenderEngineBench.cpp b/libs/renderengine/benchmark/RenderEngineBench.cpp index 739f3fa327..d44eb463f7 100644 --- a/libs/renderengine/benchmark/RenderEngineBench.cpp +++ b/libs/renderengine/benchmark/RenderEngineBench.cpp @@ -80,16 +80,26 @@ std::pair getDisplaySize() { std::once_flag once; std::call_once(once, []() { auto surfaceComposerClient = SurfaceComposerClient::getDefault(); - auto displayToken = surfaceComposerClient->getInternalDisplayToken(); - ui::DisplayMode displayMode; - if (surfaceComposerClient->getActiveDisplayMode(displayToken, &displayMode) < 0) { - LOG_ALWAYS_FATAL("Failed to get active display mode!"); + auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + LOG_ALWAYS_FATAL_IF(ids.empty(), "Failed to get any display!"); + ui::Size resolution = ui::kEmptySize; + // find the largest display resolution + for (auto id : ids) { + auto displayToken = surfaceComposerClient->getPhysicalDisplayToken(id); + ui::DisplayMode displayMode; + if (surfaceComposerClient->getActiveDisplayMode(displayToken, &displayMode) < 0) { + LOG_ALWAYS_FATAL("Failed to get active display mode!"); + } + auto tw = displayMode.resolution.width; + auto th = displayMode.resolution.height; + LOG_ALWAYS_FATAL_IF(tw <= 0 || th <= 0, "Invalid display size!"); + if (resolution.width * resolution.height < + displayMode.resolution.width * displayMode.resolution.height) { + resolution = displayMode.resolution; + } } - auto w = displayMode.resolution.width; - auto h = displayMode.resolution.height; - LOG_ALWAYS_FATAL_IF(w <= 0 || h <= 0, "Invalid display size!"); - width = static_cast(w); - height = static_cast(h); + width = static_cast(resolution.width); + height = static_cast(resolution.height); }); return std::pair(width, height); } diff --git a/opengl/tests/lib/WindowSurface.cpp b/opengl/tests/lib/WindowSurface.cpp index fd4522e757..e94b565f11 100644 --- a/opengl/tests/lib/WindowSurface.cpp +++ b/opengl/tests/lib/WindowSurface.cpp @@ -36,7 +36,14 @@ WindowSurface::WindowSurface() { return; } - const auto displayToken = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + if (ids.empty()) { + fprintf(stderr, "Failed to get ID for any displays.\n"); + return; + } + + // display 0 is picked for now, can extend to support all displays if needed + const auto displayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); if (displayToken == nullptr) { fprintf(stderr, "ERROR: no display\n"); return; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp index fae916542a..f8fc6f5a40 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp @@ -116,7 +116,7 @@ static constexpr hal::HWConfigId kActiveConfig = 0; class DisplayHardwareFuzzer { public: DisplayHardwareFuzzer(const uint8_t* data, size_t size) : mFdp(data, size) { - mPhysicalDisplayId = SurfaceComposerClient::getInternalDisplayId().value_or( + mPhysicalDisplayId = TestableSurfaceFlinger::getFirstDisplayId().value_or( PhysicalDisplayId::fromPort(mFdp.ConsumeIntegral())); }; void process(); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 22976186f2..72148f4341 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -529,6 +529,13 @@ public: mFlinger->setVsyncConfig(vsyncConfig, fdp->ConsumeIntegral()); } + // TODO(b/248317436): extend to cover all displays for multi-display devices + static std::optional getFirstDisplayId() { + std::vector ids = SurfaceComposerClient::getPhysicalDisplayIds(); + if (ids.empty()) return {}; + return ids.front(); + } + sp fuzzBoot(FuzzedDataProvider *fdp) { mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(fdp->ConsumeBool()); const sp client = sp::make(mFlinger); @@ -540,9 +547,8 @@ public: ui::PixelFormat pixelFormat{}; mFlinger->getHwComposer().allocateVirtualDisplay(halVirtualDisplayId, uiSize, &pixelFormat); - PhysicalDisplayId physicalDisplayId = - SurfaceComposerClient::getInternalDisplayId().value_or( - PhysicalDisplayId::fromPort(fdp->ConsumeIntegral())); + PhysicalDisplayId physicalDisplayId = getFirstDisplayId().value_or( + PhysicalDisplayId::fromPort(fdp->ConsumeIntegral())); mFlinger->getHwComposer().allocatePhysicalDisplay(kHwDisplayId, physicalDisplayId); sp display = diff --git a/services/surfaceflinger/tests/BootDisplayMode_test.cpp b/services/surfaceflinger/tests/BootDisplayMode_test.cpp index 432e227cae..f2874ae0e1 100644 --- a/services/surfaceflinger/tests/BootDisplayMode_test.cpp +++ b/services/surfaceflinger/tests/BootDisplayMode_test.cpp @@ -30,7 +30,10 @@ using gui::aidl_utils::statusTFromBinderStatus; TEST(BootDisplayModeTest, setBootDisplayMode) { sp sf(ComposerServiceAIDL::getComposerService()); - auto displayToken = SurfaceComposerClient::getInternalDisplayToken(); + + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + auto displayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); bool bootModeSupport = false; binder::Status status = sf->getBootDisplayModeSupport(&bootModeSupport); ASSERT_NO_FATAL_FAILURE(statusTFromBinderStatus(status)); @@ -42,7 +45,9 @@ TEST(BootDisplayModeTest, setBootDisplayMode) { TEST(BootDisplayModeTest, clearBootDisplayMode) { sp sf(ComposerServiceAIDL::getComposerService()); - auto displayToken = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + auto displayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); bool bootModeSupport = false; binder::Status status = sf->getBootDisplayModeSupport(&bootModeSupport); ASSERT_NO_FATAL_FAILURE(statusTFromBinderStatus(status)); diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp index 775de4a8fe..4f04934d34 100644 --- a/services/surfaceflinger/tests/Credentials_test.cpp +++ b/services/surfaceflinger/tests/Credentials_test.cpp @@ -74,8 +74,17 @@ protected: ASSERT_EQ(NO_ERROR, mComposerClient->initCheck()); } + static sp getFirstDisplayToken() { + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + if (ids.empty()) { + return nullptr; + } + + return SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); + } + void setupBackgroundSurface() { - mDisplay = SurfaceComposerClient::getInternalDisplayToken(); + mDisplay = getFirstDisplayToken(); ASSERT_FALSE(mDisplay == nullptr); ui::DisplayMode mode; @@ -158,9 +167,7 @@ TEST_F(CredentialsTest, ClientInitTest) { } TEST_F(CredentialsTest, GetBuiltInDisplayAccessTest) { - std::function condition = [] { - return SurfaceComposerClient::getInternalDisplayToken() != nullptr; - }; + std::function condition = [] { return getFirstDisplayToken() != nullptr; }; // Anyone can access display information. ASSERT_NO_FATAL_FAILURE(checkWithPrivileges(condition, true, true)); } @@ -169,7 +176,7 @@ TEST_F(CredentialsTest, AllowedGetterMethodsTest) { // The following methods are tested with a UID that is not root, graphics, // or system, to show that anyone can access them. UIDFaker f(AID_BIN); - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto display = getFirstDisplayToken(); ASSERT_TRUE(display != nullptr); ui::DisplayMode mode; @@ -181,7 +188,7 @@ TEST_F(CredentialsTest, AllowedGetterMethodsTest) { } TEST_F(CredentialsTest, GetDynamicDisplayInfoTest) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto display = getFirstDisplayToken(); std::function condition = [=]() { ui::DynamicDisplayInfo info; return SurfaceComposerClient::getDynamicDisplayInfo(display, &info); @@ -190,7 +197,7 @@ TEST_F(CredentialsTest, GetDynamicDisplayInfoTest) { } TEST_F(CredentialsTest, GetDisplayNativePrimariesTest) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto display = getFirstDisplayToken(); std::function condition = [=]() { ui::DisplayPrimaries primaries; return SurfaceComposerClient::getDisplayNativePrimaries(display, primaries); @@ -199,7 +206,7 @@ TEST_F(CredentialsTest, GetDisplayNativePrimariesTest) { } TEST_F(CredentialsTest, SetDesiredDisplayConfigsTest) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto display = getFirstDisplayToken(); ui::DisplayModeId defaultMode; bool allowGroupSwitching; float primaryFpsMin; @@ -222,7 +229,7 @@ TEST_F(CredentialsTest, SetDesiredDisplayConfigsTest) { } TEST_F(CredentialsTest, SetActiveColorModeTest) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto display = getFirstDisplayToken(); std::function condition = [=]() { return SurfaceComposerClient::setActiveColorMode(display, ui::ColorMode::NATIVE); }; @@ -274,7 +281,7 @@ TEST_F(CredentialsTest, CreateDisplayTest) { } TEST_F(CredentialsTest, CaptureTest) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto display = getFirstDisplayToken(); std::function condition = [=]() { sp outBuffer; DisplayCaptureArgs captureArgs; @@ -333,7 +340,7 @@ TEST_F(CredentialsTest, GetLayerDebugInfo) { } TEST_F(CredentialsTest, IsWideColorDisplayBasicCorrectness) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto display = getFirstDisplayToken(); ASSERT_FALSE(display == nullptr); bool result = false; status_t error = SurfaceComposerClient::isWideColorDisplay(display, &result); @@ -357,7 +364,7 @@ TEST_F(CredentialsTest, IsWideColorDisplayBasicCorrectness) { } TEST_F(CredentialsTest, IsWideColorDisplayWithPrivileges) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto display = getFirstDisplayToken(); ASSERT_FALSE(display == nullptr); std::function condition = [=]() { bool result = false; @@ -367,7 +374,7 @@ TEST_F(CredentialsTest, IsWideColorDisplayWithPrivileges) { } TEST_F(CredentialsTest, GetActiveColorModeBasicCorrectness) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto display = getFirstDisplayToken(); ASSERT_FALSE(display == nullptr); ui::DynamicDisplayInfo info; SurfaceComposerClient::getDynamicDisplayInfo(display, &info); diff --git a/services/surfaceflinger/tests/DisplayConfigs_test.cpp b/services/surfaceflinger/tests/DisplayConfigs_test.cpp index 2dc96b8511..02c934e576 100644 --- a/services/surfaceflinger/tests/DisplayConfigs_test.cpp +++ b/services/surfaceflinger/tests/DisplayConfigs_test.cpp @@ -48,7 +48,9 @@ private: protected: void SetUp() override { - mDisplayToken = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); status_t res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &initialDefaultMode, @@ -149,4 +151,4 @@ TEST_F(RefreshRateRangeTest, setAllowGroupSwitching) { } // 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/services/surfaceflinger/tests/EffectLayer_test.cpp b/services/surfaceflinger/tests/EffectLayer_test.cpp index 9fa0452915..52aa502743 100644 --- a/services/surfaceflinger/tests/EffectLayer_test.cpp +++ b/services/surfaceflinger/tests/EffectLayer_test.cpp @@ -28,7 +28,9 @@ protected: LayerTransactionTest::SetUp(); ASSERT_EQ(NO_ERROR, mClient->initCheck()); - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_FALSE(display == nullptr); mParentLayer = createColorLayer("Parent layer", Color::RED); @@ -177,7 +179,9 @@ TEST_F(EffectLayerTest, BlurEffectLayerIsVisible) { } TEST_F(EffectLayerTest, EffectLayerWithColorNoCrop) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ui::DisplayMode mode; ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); const ui::Size& resolution = mode.resolution; diff --git a/services/surfaceflinger/tests/IPC_test.cpp b/services/surfaceflinger/tests/IPC_test.cpp index c63d251c31..40a5d5757d 100644 --- a/services/surfaceflinger/tests/IPC_test.cpp +++ b/services/surfaceflinger/tests/IPC_test.cpp @@ -224,7 +224,9 @@ public: mClient = sp::make(); ASSERT_EQ(NO_ERROR, mClient->initCheck()); - mPrimaryDisplay = mClient->getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + mPrimaryDisplay = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ui::DisplayMode mode; mClient->getActiveDisplayMode(mPrimaryDisplay, &mode); mDisplayWidth = mode.resolution.getWidth(); diff --git a/services/surfaceflinger/tests/LayerBorder_test.cpp b/services/surfaceflinger/tests/LayerBorder_test.cpp index 0d55ec1dcf..a907d9d3b4 100644 --- a/services/surfaceflinger/tests/LayerBorder_test.cpp +++ b/services/surfaceflinger/tests/LayerBorder_test.cpp @@ -34,7 +34,9 @@ protected: toHalf3 = ColorTransformHelper::toHalf3; toHalf4 = ColorTransformHelper::toHalf4; - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_FALSE(display == nullptr); mColorOrange = toHalf4({255, 140, 0, 255}); mParentLayer = createColorLayer("Parent layer", Color::RED); diff --git a/services/surfaceflinger/tests/LayerTransactionTest.h b/services/surfaceflinger/tests/LayerTransactionTest.h index 0e8f3dd1d4..9ed951b6d1 100644 --- a/services/surfaceflinger/tests/LayerTransactionTest.h +++ b/services/surfaceflinger/tests/LayerTransactionTest.h @@ -289,7 +289,9 @@ protected: private: void SetUpDisplay() { - mDisplay = mClient->getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + mDisplay = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_FALSE(mDisplay == nullptr) << "failed to get display"; ui::DisplayMode mode; diff --git a/services/surfaceflinger/tests/LayerUpdate_test.cpp b/services/surfaceflinger/tests/LayerUpdate_test.cpp index e1a7ecc03b..867eddbc44 100644 --- a/services/surfaceflinger/tests/LayerUpdate_test.cpp +++ b/services/surfaceflinger/tests/LayerUpdate_test.cpp @@ -33,7 +33,9 @@ protected: LayerTransactionTest::SetUp(); ASSERT_EQ(NO_ERROR, mClient->initCheck()); - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_FALSE(display == nullptr); ui::DisplayMode mode; diff --git a/services/surfaceflinger/tests/MirrorLayer_test.cpp b/services/surfaceflinger/tests/MirrorLayer_test.cpp index a921aa810e..e3f2f02b0a 100644 --- a/services/surfaceflinger/tests/MirrorLayer_test.cpp +++ b/services/surfaceflinger/tests/MirrorLayer_test.cpp @@ -29,8 +29,10 @@ protected: virtual void SetUp() { LayerTransactionTest::SetUp(); ASSERT_EQ(NO_ERROR, mClient->initCheck()); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_FALSE(display == nullptr); mParentLayer = createColorLayer("Parent layer", Color::RED); @@ -231,7 +233,10 @@ TEST_F(MirrorLayerTest, MirrorBufferLayer) { // Test that the mirror layer is initially offscreen. TEST_F(MirrorLayerTest, InitialMirrorState) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ui::DisplayMode mode; SurfaceComposerClient::getActiveDisplayMode(display, &mode); const ui::Size& size = mode.resolution; @@ -275,7 +280,9 @@ TEST_F(MirrorLayerTest, InitialMirrorState) { // Test that a mirror layer can be screenshot when offscreen TEST_F(MirrorLayerTest, OffscreenMirrorScreenshot) { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ui::DisplayMode mode; SurfaceComposerClient::getActiveDisplayMode(display, &mode); const ui::Size& size = mode.resolution; diff --git a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp b/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp index 1ed6c65afb..15ff696412 100644 --- a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp +++ b/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp @@ -35,7 +35,9 @@ protected: LayerTransactionTest::SetUp(); ASSERT_EQ(NO_ERROR, mClient->initCheck()); - mMainDisplay = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + mMainDisplay = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); SurfaceComposerClient::getDisplayState(mMainDisplay, &mMainDisplayState); SurfaceComposerClient::getActiveDisplayMode(mMainDisplay, &mMainDisplayMode); diff --git a/services/surfaceflinger/tests/RelativeZ_test.cpp b/services/surfaceflinger/tests/RelativeZ_test.cpp index 50a4092ddb..9cebf11b9c 100644 --- a/services/surfaceflinger/tests/RelativeZ_test.cpp +++ b/services/surfaceflinger/tests/RelativeZ_test.cpp @@ -33,7 +33,9 @@ protected: LayerTransactionTest::SetUp(); ASSERT_EQ(NO_ERROR, mClient->initCheck()); - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_FALSE(display == nullptr); // Back layer diff --git a/services/surfaceflinger/tests/ScreenCapture_test.cpp b/services/surfaceflinger/tests/ScreenCapture_test.cpp index d78c8a9ee0..34503755e3 100644 --- a/services/surfaceflinger/tests/ScreenCapture_test.cpp +++ b/services/surfaceflinger/tests/ScreenCapture_test.cpp @@ -30,7 +30,9 @@ protected: LayerTransactionTest::SetUp(); ASSERT_EQ(NO_ERROR, mClient->initCheck()); - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_FALSE(display == nullptr); ui::DisplayMode mode; diff --git a/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp b/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp index d79e59211b..7166d413f1 100644 --- a/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp +++ b/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp @@ -264,7 +264,9 @@ void SurfaceInterceptorTest::capture(TestAction action, Trace* outTrace) { } void SurfaceInterceptorTest::setupBackgroundSurface() { - const auto display = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + ASSERT_FALSE(ids.empty()); + const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ASSERT_FALSE(display == nullptr); ui::DisplayMode mode; diff --git a/services/surfaceflinger/tests/TransactionTestHarnesses.h b/services/surfaceflinger/tests/TransactionTestHarnesses.h index ad03ed3073..797a64c7d7 100644 --- a/services/surfaceflinger/tests/TransactionTestHarnesses.h +++ b/services/surfaceflinger/tests/TransactionTestHarnesses.h @@ -35,7 +35,10 @@ public: return mDelegate->screenshot(); case RenderPath::VIRTUAL_DISPLAY: - const auto displayToken = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + const auto displayToken = ids.empty() + ? nullptr + : SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); ui::DisplayState displayState; SurfaceComposerClient::getDisplayState(displayToken, &displayState); diff --git a/services/surfaceflinger/tests/utils/ScreenshotUtils.h b/services/surfaceflinger/tests/utils/ScreenshotUtils.h index 224868c014..f297da5511 100644 --- a/services/surfaceflinger/tests/utils/ScreenshotUtils.h +++ b/services/surfaceflinger/tests/utils/ScreenshotUtils.h @@ -51,7 +51,11 @@ public: } static void captureScreen(std::unique_ptr* sc) { - captureScreen(sc, SurfaceComposerClient::getInternalDisplayToken()); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + // TODO(b/248317436): extend to cover all displays for multi-display devices + const auto display = + ids.empty() ? nullptr : SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); + captureScreen(sc, display); } static void captureScreen(std::unique_ptr* sc, sp displayToken) { -- GitLab From 6761733ad1dd775f011588c59d5a6d210175c546 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Mon, 26 Sep 2022 21:37:01 +0000 Subject: [PATCH 0314/1310] Fix use-after-free in SurfaceFlinger::doDump SurfaceFlinger::doDump previously contained two layer traversals, one on the main thread and one off the main thread. During concurrent dumpsys commands, the layer traversals may race with each other, which causes shared ownership of the underlying storage of a SortedVector containing the z-ordered list of layers. Because the implementation of SortedVector's STL iterators assumes that the underlying storage may be edited, this can cause the storage to be copied whenever SortedVector::begin and SortedVector::end are called, which means that SortedVector::begin() + SortedVector::size() == SortedVector::end() is not always true, which causes invalid iteration. In general, this use-after-free can happen as long as the off-main thread traversal exists in doDump(), because the traversal can run in parallel with any workload on the main thread that executes a layer traversal. So, this patch moves the traversal for dumping out the list of composition layers into the main thread. A future patch could explore either fixing SortedVector to fix judicious iterator invalidation, or building LayerVector on top of std::set, but either option is an invasive data structure change. Bug: 237291506 Test: Test script that calls dumpsys SurfaceFlinger on many threads Change-Id: I0748396519c924dc1b84113d44259f22d0d7ebd6 --- services/surfaceflinger/SurfaceFlinger.cpp | 37 ++++++++++++++-------- services/surfaceflinger/SurfaceFlinger.h | 3 +- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index f3551ae080..e27c713806 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4935,6 +4935,25 @@ status_t SurfaceFlinger::doDump(int fd, const DumpArgs& args, bool asProto) { const auto flag = args.empty() ? ""s : std::string(String8(args[0])); + // Traversal of drawing state must happen on the main thread. + // Otherwise, SortedVector may have shared ownership during concurrent + // traversals, which can result in use-after-frees. + std::string compositionLayers; + mScheduler + ->schedule([&] { + StringAppendF(&compositionLayers, "Composition layers\n"); + mDrawingState.traverseInZOrder([&](Layer* layer) { + auto* compositionState = layer->getCompositionState(); + if (!compositionState || !compositionState->isVisible) return; + + android::base::StringAppendF(&compositionLayers, "* Layer %p (%s)\n", layer, + layer->getDebugName() ? layer->getDebugName() + : ""); + compositionState->dump(compositionLayers); + }); + }) + .get(); + bool dumpLayers = true; { TimedLock lock(mStateLock, s2ns(1), __func__); @@ -4947,7 +4966,7 @@ status_t SurfaceFlinger::doDump(int fd, const DumpArgs& args, bool asProto) { (it->second)(args, asProto, result); dumpLayers = false; } else if (!asProto) { - dumpAllLocked(args, result); + dumpAllLocked(args, compositionLayers, result); } } @@ -5254,7 +5273,8 @@ void SurfaceFlinger::dumpHwcLayersMinidumpLocked(std::string& result) const { } } -void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, std::string& result) const { +void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& compositionLayers, + std::string& result) const { const bool colorize = !args.empty() && args[0] == String16("--color"); Colorizer colorizer(colorize); @@ -5302,18 +5322,7 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, std::string& result) co StringAppendF(&result, "Visible layers (count = %zu)\n", mNumLayers.load()); colorizer.reset(result); - { - StringAppendF(&result, "Composition layers\n"); - mDrawingState.traverseInZOrder([&](Layer* layer) { - auto* compositionState = layer->getCompositionState(); - if (!compositionState || !compositionState->isVisible) return; - - android::base::StringAppendF(&result, "* Layer %p (%s)\n", layer, - layer->getDebugName() ? layer->getDebugName() - : ""); - compositionState->dump(result); - }); - } + result.append(compositionLayers); colorizer.bold(result); StringAppendF(&result, "Displays (%zu entries)\n", mDisplays.size()); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index fdaacf074c..3c92d565ed 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1044,7 +1044,8 @@ private: /* * Debugging & dumpsys */ - void dumpAllLocked(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock); + void dumpAllLocked(const DumpArgs& args, const std::string& compositionLayers, + std::string& result) const REQUIRES(mStateLock); void dumpHwcLayersMinidumpLocked(std::string& result) const REQUIRES(mStateLock); void appendSfConfigString(std::string& result) const; -- GitLab From 18c38bb2fd7779acd762baf47894b266c3dcce73 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Tue, 27 Sep 2022 00:21:40 +0000 Subject: [PATCH 0315/1310] Make uncache buffer transactions oneway. Buffers that were previously sent to SurfaceFlinger have a death callback attached which sends a transaction to SurfaceFlinger to remove the buffer from SurfaceFlinger's caches for prompt memory cleanup. Originally, this transaction was applied synchronously, which contributes to performance issues when the producer side of a BlastBufferQueue disconnects and destroys all buffers in the queue. This is particularly problematic when: 1. the producer is a media decoder which tend to allocate 20+ buffers, and: 2. the disconnect happens on the UI thread, which is typical when using SurfaceView with RecyclerView for a scrolling list of videos which is a common UX for contemporary social media applications. On some devices, the synchronous binder calls can be more expensive for CPU walltime than freeing the backing memory for the buffers. Marking these transactions as one-way will reduce the risk of UI jank. Bug: 248533454 Test: Test application with SurfaceView scrolling + media playback Change-Id: Ie5c50f2eb6c65f3c8652df84a91c2116327b8c0e --- libs/gui/SurfaceComposerClient.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index a9d6b0ec4b..fc0b1ee7e2 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -912,8 +912,9 @@ void SurfaceComposerClient::doUncacheBufferTransaction(uint64_t cacheId) { uncacheBuffer.token = BufferCache::getInstance().getToken(); uncacheBuffer.id = cacheId; - sf->setTransactionState(FrameTimelineInfo{}, {}, {}, 0, Transaction::getDefaultApplyToken(), {}, - systemTime(), true, uncacheBuffer, false, {}, generateId()); + sf->setTransactionState(FrameTimelineInfo{}, {}, {}, ISurfaceComposer::eOneWay, + Transaction::getDefaultApplyToken(), {}, systemTime(), true, + uncacheBuffer, false, {}, generateId()); } void SurfaceComposerClient::Transaction::cacheBuffers() { -- GitLab From a1fcd3dd5391ad67bbc735dc12430fb0590e0561 Mon Sep 17 00:00:00 2001 From: Chris Forbes Date: Tue, 27 Sep 2022 17:11:35 +1300 Subject: [PATCH 0316/1310] egl: plumb through counter events to atrace egl_angle_platform handled some but not all trace events that ANGLE can emit. In particular, counters were dropped on the floor. From a change proposed in b/216025815. Bug: b/216025815 Change-Id: I7abb78d5feed90de16099e97e4a816f56443cf27 --- opengl/libs/EGL/egl_angle_platform.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/opengl/libs/EGL/egl_angle_platform.cpp b/opengl/libs/EGL/egl_angle_platform.cpp index d38f2eff01..f1122fd098 100644 --- a/opengl/libs/EGL/egl_angle_platform.cpp +++ b/opengl/libs/EGL/egl_angle_platform.cpp @@ -68,9 +68,9 @@ static void logInfo(PlatformMethods* /*platform*/, const char* infoMessage) { static TraceEventHandle addTraceEvent( PlatformMethods* /**platform*/, char phase, const unsigned char* /*category_group_enabled*/, - const char* name, unsigned long long /*id*/, double /*timestamp*/, int /*num_args*/, - const char** /*arg_names*/, const unsigned char* /*arg_types*/, - const unsigned long long* /*arg_values*/, unsigned char /*flags*/) { + const char* name, unsigned long long /*id*/, double /*timestamp*/, int num_args, + const char** arg_names, const unsigned char* /*arg_types*/, + const unsigned long long* arg_values, unsigned char /*flags*/) { switch (phase) { case 'B': { ATRACE_BEGIN(name); @@ -84,6 +84,13 @@ static TraceEventHandle addTraceEvent( ATRACE_NAME(name); break; } + case 'C': { + for(int i=0; i Date: Thu, 15 Sep 2022 17:02:20 -0700 Subject: [PATCH 0317/1310] Use std::variant for NotifyArgs Previously, NotifyArgs was a base class and we relied on inheritance in order to refer to these args in a generic manner. Unfortunately, this prevents us from being able to cast into a descendant. If you got NotifyArgs, there was no way to figure out its type. In this CL, we switch to std::variant. Doing this allows us to remove the inheritance, and several other functions. The classes are now mostly about data. This allows us to receive a generic NotifyArgs object and cast it into NotifyMotionArgs (for example). This also allows us to separate NotifyArgs from the InputListener interface (not done in this CL). Furthermore, we don't need to store the args as unique_ptr anymore. Bug: 211379801 Test: m checkinput Change-Id: I5b10d485a9eb27b4ffb6a3a6e21227f52ac3dede --- services/inputflinger/InputListener.cpp | 175 +++++------------- services/inputflinger/InputProcessor.cpp | 41 ++-- services/inputflinger/InputProcessor.h | 10 +- services/inputflinger/include/InputListener.h | 120 +++++------- 4 files changed, 115 insertions(+), 231 deletions(-) diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp index dce327ed18..110f08bbe3 100644 --- a/services/inputflinger/InputListener.cpp +++ b/services/inputflinger/InputListener.cpp @@ -34,19 +34,7 @@ namespace android { // --- NotifyConfigurationChangedArgs --- NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs(int32_t id, nsecs_t eventTime) - : NotifyArgs(id, eventTime) {} - -NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs( - const NotifyConfigurationChangedArgs& other) - : NotifyArgs(other.id, other.eventTime) {} - -bool NotifyConfigurationChangedArgs::operator==(const NotifyConfigurationChangedArgs& rhs) const { - return id == rhs.id && eventTime == rhs.eventTime; -} - -void NotifyConfigurationChangedArgs::notify(InputListenerInterface& listener) const { - listener.notifyConfigurationChanged(this); -} + : id(id), eventTime(eventTime) {} // --- NotifyKeyArgs --- @@ -54,7 +42,8 @@ NotifyKeyArgs::NotifyKeyArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, in uint32_t source, int32_t displayId, uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) - : NotifyArgs(id, eventTime), + : id(id), + eventTime(eventTime), deviceId(deviceId), source(source), displayId(displayId), @@ -67,32 +56,6 @@ NotifyKeyArgs::NotifyKeyArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, in downTime(downTime), readTime(readTime) {} -NotifyKeyArgs::NotifyKeyArgs(const NotifyKeyArgs& other) - : NotifyArgs(other.id, other.eventTime), - deviceId(other.deviceId), - source(other.source), - displayId(other.displayId), - policyFlags(other.policyFlags), - action(other.action), - flags(other.flags), - keyCode(other.keyCode), - scanCode(other.scanCode), - metaState(other.metaState), - downTime(other.downTime), - readTime(other.readTime) {} - -bool NotifyKeyArgs::operator==(const NotifyKeyArgs& rhs) const { - return id == rhs.id && eventTime == rhs.eventTime && readTime == rhs.readTime && - deviceId == rhs.deviceId && source == rhs.source && displayId == rhs.displayId && - policyFlags == rhs.policyFlags && action == rhs.action && flags == rhs.flags && - keyCode == rhs.keyCode && scanCode == rhs.scanCode && metaState == rhs.metaState && - downTime == rhs.downTime; -} - -void NotifyKeyArgs::notify(InputListenerInterface& listener) const { - listener.notifyKey(this); -} - // --- NotifyMotionArgs --- NotifyMotionArgs::NotifyMotionArgs( @@ -103,7 +66,8 @@ NotifyMotionArgs::NotifyMotionArgs( const PointerCoords* pointerCoords, float xPrecision, float yPrecision, float xCursorPosition, float yCursorPosition, nsecs_t downTime, const std::vector& videoFrames) - : NotifyArgs(id, eventTime), + : id(id), + eventTime(eventTime), deviceId(deviceId), source(source), displayId(displayId), @@ -130,7 +94,8 @@ NotifyMotionArgs::NotifyMotionArgs( } NotifyMotionArgs::NotifyMotionArgs(const NotifyMotionArgs& other) - : NotifyArgs(other.id, other.eventTime), + : id(other.id), + eventTime(other.eventTime), deviceId(other.deviceId), source(other.source), displayId(other.displayId), @@ -220,41 +185,24 @@ std::string NotifyMotionArgs::dump() const { flags); } -void NotifyMotionArgs::notify(InputListenerInterface& listener) const { - listener.notifyMotion(this); -} - // --- NotifySwitchArgs --- NotifySwitchArgs::NotifySwitchArgs(int32_t id, nsecs_t eventTime, uint32_t policyFlags, uint32_t switchValues, uint32_t switchMask) - : NotifyArgs(id, eventTime), + : id(id), + eventTime(eventTime), policyFlags(policyFlags), switchValues(switchValues), switchMask(switchMask) {} -NotifySwitchArgs::NotifySwitchArgs(const NotifySwitchArgs& other) - : NotifyArgs(other.id, other.eventTime), - policyFlags(other.policyFlags), - switchValues(other.switchValues), - switchMask(other.switchMask) {} - -bool NotifySwitchArgs::operator==(const NotifySwitchArgs rhs) const { - return id == rhs.id && eventTime == rhs.eventTime && policyFlags == rhs.policyFlags && - switchValues == rhs.switchValues && switchMask == rhs.switchMask; -} - -void NotifySwitchArgs::notify(InputListenerInterface& listener) const { - listener.notifySwitch(this); -} - // --- NotifySensorArgs --- NotifySensorArgs::NotifySensorArgs(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source, InputDeviceSensorType sensorType, InputDeviceSensorAccuracy accuracy, bool accuracyChanged, nsecs_t hwTimestamp, std::vector values) - : NotifyArgs(id, eventTime), + : id(id), + eventTime(eventTime), deviceId(deviceId), source(source), sensorType(sensorType), @@ -263,77 +211,25 @@ NotifySensorArgs::NotifySensorArgs(int32_t id, nsecs_t eventTime, int32_t device hwTimestamp(hwTimestamp), values(std::move(values)) {} -NotifySensorArgs::NotifySensorArgs(const NotifySensorArgs& other) - : NotifyArgs(other.id, other.eventTime), - deviceId(other.deviceId), - source(other.source), - sensorType(other.sensorType), - accuracy(other.accuracy), - accuracyChanged(other.accuracyChanged), - hwTimestamp(other.hwTimestamp), - values(other.values) {} - -bool NotifySensorArgs::operator==(const NotifySensorArgs rhs) const { - return id == rhs.id && eventTime == rhs.eventTime && sensorType == rhs.sensorType && - accuracy == rhs.accuracy && accuracyChanged == rhs.accuracyChanged && - hwTimestamp == rhs.hwTimestamp && values == rhs.values; -} - -void NotifySensorArgs::notify(InputListenerInterface& listener) const { - listener.notifySensor(this); -} - // --- NotifyVibratorStateArgs --- NotifyVibratorStateArgs::NotifyVibratorStateArgs(int32_t id, nsecs_t eventTime, int32_t deviceId, bool isOn) - : NotifyArgs(id, eventTime), deviceId(deviceId), isOn(isOn) {} + : id(id), eventTime(eventTime), deviceId(deviceId), isOn(isOn) {} NotifyVibratorStateArgs::NotifyVibratorStateArgs(const NotifyVibratorStateArgs& other) - : NotifyArgs(other.id, other.eventTime), deviceId(other.deviceId), isOn(other.isOn) {} - -bool NotifyVibratorStateArgs::operator==(const NotifyVibratorStateArgs rhs) const { - return id == rhs.id && eventTime == rhs.eventTime && deviceId == rhs.deviceId && - isOn == rhs.isOn; -} - -void NotifyVibratorStateArgs::notify(InputListenerInterface& listener) const { - listener.notifyVibratorState(this); -} + : id(other.id), eventTime(other.eventTime), deviceId(other.deviceId), isOn(other.isOn) {} // --- NotifyDeviceResetArgs --- NotifyDeviceResetArgs::NotifyDeviceResetArgs(int32_t id, nsecs_t eventTime, int32_t deviceId) - : NotifyArgs(id, eventTime), deviceId(deviceId) {} - -NotifyDeviceResetArgs::NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other) - : NotifyArgs(other.id, other.eventTime), deviceId(other.deviceId) {} - -bool NotifyDeviceResetArgs::operator==(const NotifyDeviceResetArgs& rhs) const { - return id == rhs.id && eventTime == rhs.eventTime && deviceId == rhs.deviceId; -} - -void NotifyDeviceResetArgs::notify(InputListenerInterface& listener) const { - listener.notifyDeviceReset(this); -} + : id(id), eventTime(eventTime), deviceId(deviceId) {} // --- NotifyPointerCaptureChangedArgs --- NotifyPointerCaptureChangedArgs::NotifyPointerCaptureChangedArgs( int32_t id, nsecs_t eventTime, const PointerCaptureRequest& request) - : NotifyArgs(id, eventTime), request(request) {} - -NotifyPointerCaptureChangedArgs::NotifyPointerCaptureChangedArgs( - const NotifyPointerCaptureChangedArgs& other) - : NotifyArgs(other.id, other.eventTime), request(other.request) {} - -bool NotifyPointerCaptureChangedArgs::operator==(const NotifyPointerCaptureChangedArgs& rhs) const { - return id == rhs.id && eventTime == rhs.eventTime && request == rhs.request; -} - -void NotifyPointerCaptureChangedArgs::notify(InputListenerInterface& listener) const { - listener.notifyPointerCaptureChanged(this); -} + : id(id), eventTime(eventTime), request(request) {} // --- QueuedInputListener --- @@ -350,47 +246,68 @@ QueuedInputListener::QueuedInputListener(InputListenerInterface& innerListener) void QueuedInputListener::notifyConfigurationChanged( const NotifyConfigurationChangedArgs* args) { traceEvent(__func__, args->id); - mArgsQueue.emplace_back(std::make_unique(*args)); + mArgsQueue.emplace_back(*args); } void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) { traceEvent(__func__, args->id); - mArgsQueue.emplace_back(std::make_unique(*args)); + mArgsQueue.emplace_back(*args); } void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) { traceEvent(__func__, args->id); - mArgsQueue.emplace_back(std::make_unique(*args)); + mArgsQueue.emplace_back(*args); } void QueuedInputListener::notifySwitch(const NotifySwitchArgs* args) { traceEvent(__func__, args->id); - mArgsQueue.emplace_back(std::make_unique(*args)); + mArgsQueue.emplace_back(*args); } void QueuedInputListener::notifySensor(const NotifySensorArgs* args) { traceEvent(__func__, args->id); - mArgsQueue.emplace_back(std::make_unique(*args)); + mArgsQueue.emplace_back(*args); } void QueuedInputListener::notifyVibratorState(const NotifyVibratorStateArgs* args) { traceEvent(__func__, args->id); - mArgsQueue.emplace_back(std::make_unique(*args)); + mArgsQueue.emplace_back(*args); } void QueuedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) { traceEvent(__func__, args->id); - mArgsQueue.emplace_back(std::make_unique(*args)); + mArgsQueue.emplace_back(*args); } void QueuedInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) { traceEvent(__func__, args->id); - mArgsQueue.emplace_back(std::make_unique(*args)); + mArgsQueue.emplace_back(*args); } +// Helper to std::visit with lambdas. +template +struct Visitor : V... {}; +// explicit deduction guide (not needed as of C++20) +template +Visitor(V...) -> Visitor; + void QueuedInputListener::flush() { - for (const std::unique_ptr& args : mArgsQueue) { - args->notify(mInnerListener); + Visitor v{ + [&](const NotifyConfigurationChangedArgs& args) { + mInnerListener.notifyConfigurationChanged(&args); + }, + [&](const NotifyKeyArgs& args) { mInnerListener.notifyKey(&args); }, + [&](const NotifyMotionArgs& args) { mInnerListener.notifyMotion(&args); }, + [&](const NotifySwitchArgs& args) { mInnerListener.notifySwitch(&args); }, + [&](const NotifySensorArgs& args) { mInnerListener.notifySensor(&args); }, + [&](const NotifyVibratorStateArgs& args) { mInnerListener.notifyVibratorState(&args); }, + [&](const NotifyDeviceResetArgs& args) { mInnerListener.notifyDeviceReset(&args); }, + [&](const NotifyPointerCaptureChangedArgs& args) { + mInnerListener.notifyPointerCaptureChanged(&args); + }, + }; + for (const NotifyArgs& args : mArgsQueue) { + std::visit(v, args); } mArgsQueue.clear(); } diff --git a/services/inputflinger/InputProcessor.cpp b/services/inputflinger/InputProcessor.cpp index 0749441694..d27b804979 100644 --- a/services/inputflinger/InputProcessor.cpp +++ b/services/inputflinger/InputProcessor.cpp @@ -123,40 +123,38 @@ ScopedDeathRecipient::~ScopedDeathRecipient() { // --- ClassifierEvent --- -ClassifierEvent::ClassifierEvent(std::unique_ptr args) - : type(ClassifierEventType::MOTION), args(std::move(args)){}; -ClassifierEvent::ClassifierEvent(std::unique_ptr args) - : type(ClassifierEventType::DEVICE_RESET), args(std::move(args)){}; -ClassifierEvent::ClassifierEvent(ClassifierEventType type, std::unique_ptr args) - : type(type), args(std::move(args)){}; +ClassifierEvent::ClassifierEvent(const NotifyMotionArgs& args) + : type(ClassifierEventType::MOTION), args(args){}; -ClassifierEvent::ClassifierEvent(ClassifierEvent&& other) - : type(other.type), args(std::move(other.args)){}; +ClassifierEvent::ClassifierEvent(const NotifyDeviceResetArgs& args) + : type(ClassifierEventType::DEVICE_RESET), args(args){}; + +ClassifierEvent::ClassifierEvent(ClassifierEventType type, std::optional args) + : type(type), args(args){}; ClassifierEvent& ClassifierEvent::operator=(ClassifierEvent&& other) { type = other.type; - args = std::move(other.args); + args = other.args; return *this; } ClassifierEvent ClassifierEvent::createHalResetEvent() { - return ClassifierEvent(ClassifierEventType::HAL_RESET, nullptr); + return ClassifierEvent(ClassifierEventType::HAL_RESET, std::nullopt); } ClassifierEvent ClassifierEvent::createExitEvent() { - return ClassifierEvent(ClassifierEventType::EXIT, nullptr); + return ClassifierEvent(ClassifierEventType::EXIT, std::nullopt); } std::optional ClassifierEvent::getDeviceId() const { switch (type) { case ClassifierEventType::MOTION: { - NotifyMotionArgs* motionArgs = static_cast(args.get()); - return motionArgs->deviceId; + const NotifyMotionArgs& motionArgs = std::get(*args); + return motionArgs.deviceId; } case ClassifierEventType::DEVICE_RESET: { - NotifyDeviceResetArgs* deviceResetArgs = - static_cast(args.get()); - return deviceResetArgs->deviceId; + const NotifyDeviceResetArgs& deviceResetArgs = std::get(*args); + return deviceResetArgs.deviceId; } case ClassifierEventType::HAL_RESET: { return std::nullopt; @@ -212,12 +210,12 @@ void MotionClassifier::processEvents() { bool halResponseOk = true; switch (event.type) { case ClassifierEventType::MOTION: { - NotifyMotionArgs* motionArgs = static_cast(event.args.get()); - common::MotionEvent motionEvent = notifyMotionArgsToHalMotionEvent(*motionArgs); + NotifyMotionArgs& motionArgs = std::get(*event.args); + common::MotionEvent motionEvent = notifyMotionArgsToHalMotionEvent(motionArgs); common::Classification classification; ndk::ScopedAStatus response = mService->classify(motionEvent, &classification); if (response.isOk()) { - updateClassification(motionArgs->deviceId, motionArgs->eventTime, + updateClassification(motionArgs.deviceId, motionArgs.eventTime, getMotionClassification(classification)); } break; @@ -307,8 +305,7 @@ MotionClassification MotionClassifier::classify(const NotifyMotionArgs& args) { updateLastDownTime(args.deviceId, args.downTime); } - ClassifierEvent event(std::make_unique(args)); - enqueueEvent(std::move(event)); + enqueueEvent(args); return getClassification(args.deviceId); } @@ -328,7 +325,7 @@ void MotionClassifier::reset(const NotifyDeviceResetArgs& args) { std::optional eventDeviceId = event.getDeviceId(); return eventDeviceId && (*eventDeviceId == deviceId); }); - enqueueEvent(std::make_unique(args)); + enqueueEvent(args); } void MotionClassifier::dump(std::string& dump) { diff --git a/services/inputflinger/InputProcessor.h b/services/inputflinger/InputProcessor.h index bbf391e069..f4d02b6f30 100644 --- a/services/inputflinger/InputProcessor.h +++ b/services/inputflinger/InputProcessor.h @@ -35,12 +35,12 @@ enum class ClassifierEventType : uint8_t { struct ClassifierEvent { ClassifierEventType type; - std::unique_ptr args; + std::optional args; - ClassifierEvent(ClassifierEventType type, std::unique_ptr args); - ClassifierEvent(std::unique_ptr args); - ClassifierEvent(std::unique_ptr args); - ClassifierEvent(ClassifierEvent&& other); + ClassifierEvent(ClassifierEventType type, std::optional args); + ClassifierEvent(const NotifyMotionArgs& args); + ClassifierEvent(const NotifyDeviceResetArgs& args); + ClassifierEvent(ClassifierEvent&& other) = default; ClassifierEvent& operator=(ClassifierEvent&& other); // Convenience function to create a HAL_RESET event diff --git a/services/inputflinger/include/InputListener.h b/services/inputflinger/include/InputListener.h index c8ab6c5011..508d3410e6 100644 --- a/services/inputflinger/include/InputListener.h +++ b/services/inputflinger/include/InputListener.h @@ -26,41 +26,25 @@ namespace android { class InputListenerInterface; - -/* Superclass of all input event argument objects */ -struct NotifyArgs { +/* Describes a configuration change event. */ +struct NotifyConfigurationChangedArgs { int32_t id; nsecs_t eventTime; - inline NotifyArgs() : id(0), eventTime(0) {} - - inline explicit NotifyArgs(int32_t id, nsecs_t eventTime) : id(id), eventTime(eventTime) {} - - virtual ~NotifyArgs() { } - - virtual void notify(InputListenerInterface& listener) const = 0; -}; - - -/* Describes a configuration change event. */ -struct NotifyConfigurationChangedArgs : public NotifyArgs { - inline NotifyConfigurationChangedArgs() { } - bool operator==(const NotifyConfigurationChangedArgs& rhs) const; - NotifyConfigurationChangedArgs(int32_t id, nsecs_t eventTime); - NotifyConfigurationChangedArgs(const NotifyConfigurationChangedArgs& other); + bool operator==(const NotifyConfigurationChangedArgs& rhs) const = default; - virtual ~NotifyConfigurationChangedArgs() { } - - void notify(InputListenerInterface& listener) const override; + NotifyConfigurationChangedArgs(const NotifyConfigurationChangedArgs& other) = default; }; - /* Describes a key event. */ -struct NotifyKeyArgs : public NotifyArgs { +struct NotifyKeyArgs { + int32_t id; + nsecs_t eventTime; + int32_t deviceId; uint32_t source; int32_t displayId; @@ -80,18 +64,16 @@ struct NotifyKeyArgs : public NotifyArgs { int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime); - bool operator==(const NotifyKeyArgs& rhs) const; - - NotifyKeyArgs(const NotifyKeyArgs& other); - - virtual ~NotifyKeyArgs() { } + bool operator==(const NotifyKeyArgs& rhs) const = default; - void notify(InputListenerInterface& listener) const override; + NotifyKeyArgs(const NotifyKeyArgs& other) = default; }; - /* Describes a motion event. */ -struct NotifyMotionArgs : public NotifyArgs { +struct NotifyMotionArgs { + int32_t id; + nsecs_t eventTime; + int32_t deviceId; uint32_t source; int32_t displayId; @@ -136,17 +118,16 @@ struct NotifyMotionArgs : public NotifyArgs { NotifyMotionArgs(const NotifyMotionArgs& other); - virtual ~NotifyMotionArgs() { } - bool operator==(const NotifyMotionArgs& rhs) const; - void notify(InputListenerInterface& listener) const override; - std::string dump() const; }; /* Describes a sensor event. */ -struct NotifySensorArgs : public NotifyArgs { +struct NotifySensorArgs { + int32_t id; + nsecs_t eventTime; + int32_t deviceId; uint32_t source; InputDeviceSensorType sensorType; @@ -161,17 +142,14 @@ struct NotifySensorArgs : public NotifyArgs { InputDeviceSensorType sensorType, InputDeviceSensorAccuracy accuracy, bool accuracyChanged, nsecs_t hwTimestamp, std::vector values); - NotifySensorArgs(const NotifySensorArgs& other); - - bool operator==(const NotifySensorArgs rhs) const; - - ~NotifySensorArgs() override {} - - void notify(InputListenerInterface& listener) const override; + NotifySensorArgs(const NotifySensorArgs& other) = default; }; /* Describes a switch event. */ -struct NotifySwitchArgs : public NotifyArgs { +struct NotifySwitchArgs { + int32_t id; + nsecs_t eventTime; + uint32_t policyFlags; uint32_t switchValues; uint32_t switchMask; @@ -181,54 +159,48 @@ struct NotifySwitchArgs : public NotifyArgs { NotifySwitchArgs(int32_t id, nsecs_t eventTime, uint32_t policyFlags, uint32_t switchValues, uint32_t switchMask); - NotifySwitchArgs(const NotifySwitchArgs& other); - - bool operator==(const NotifySwitchArgs rhs) const; + NotifySwitchArgs(const NotifySwitchArgs& other) = default; - virtual ~NotifySwitchArgs() { } - - void notify(InputListenerInterface& listener) const override; + bool operator==(const NotifySwitchArgs& rhs) const = default; }; - /* Describes a device reset event, such as when a device is added, * reconfigured, or removed. */ -struct NotifyDeviceResetArgs : public NotifyArgs { +struct NotifyDeviceResetArgs { + int32_t id; + nsecs_t eventTime; + int32_t deviceId; inline NotifyDeviceResetArgs() { } NotifyDeviceResetArgs(int32_t id, nsecs_t eventTime, int32_t deviceId); - NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other); - - bool operator==(const NotifyDeviceResetArgs& rhs) const; - - virtual ~NotifyDeviceResetArgs() { } + NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other) = default; - void notify(InputListenerInterface& listener) const override; + bool operator==(const NotifyDeviceResetArgs& rhs) const = default; }; /* Describes a change in the state of Pointer Capture. */ -struct NotifyPointerCaptureChangedArgs : public NotifyArgs { +struct NotifyPointerCaptureChangedArgs { // The sequence number of the Pointer Capture request, if enabled. + int32_t id; + nsecs_t eventTime; + PointerCaptureRequest request; inline NotifyPointerCaptureChangedArgs() {} NotifyPointerCaptureChangedArgs(int32_t id, nsecs_t eventTime, const PointerCaptureRequest&); - NotifyPointerCaptureChangedArgs(const NotifyPointerCaptureChangedArgs& other); - - bool operator==(const NotifyPointerCaptureChangedArgs& rhs) const; - - virtual ~NotifyPointerCaptureChangedArgs() {} - - void notify(InputListenerInterface& listener) const override; + NotifyPointerCaptureChangedArgs(const NotifyPointerCaptureChangedArgs& other) = default; }; /* Describes a vibrator state event. */ -struct NotifyVibratorStateArgs : public NotifyArgs { +struct NotifyVibratorStateArgs { + int32_t id; + nsecs_t eventTime; + int32_t deviceId; bool isOn; @@ -237,14 +209,12 @@ struct NotifyVibratorStateArgs : public NotifyArgs { NotifyVibratorStateArgs(int32_t id, nsecs_t eventTIme, int32_t deviceId, bool isOn); NotifyVibratorStateArgs(const NotifyVibratorStateArgs& other); - - bool operator==(const NotifyVibratorStateArgs rhs) const; - - virtual ~NotifyVibratorStateArgs() {} - - void notify(InputListenerInterface& listener) const override; }; +using NotifyArgs = std::variant; + /* * The interface used by the InputReader to notify the InputListener about input events. */ @@ -287,7 +257,7 @@ public: private: InputListenerInterface& mInnerListener; - std::vector> mArgsQueue; + std::vector mArgsQueue; }; } // namespace android -- GitLab From 51b46e2abaab95a07cc66116ffd4479ac1036a7a Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 15 Sep 2022 18:44:37 -0700 Subject: [PATCH 0318/1310] Add a generic 'notify' function This new function will know how to route NotifyArgs. The caller doesn't have to figure out the right invocation based on the args type. Bug: 211379801 Test: m checkinput Change-Id: I24cfc4aa0315eb20d008806b08eaf158ae09e719 --- services/inputflinger/InputListener.cpp | 48 ++++++++++--------- services/inputflinger/include/InputListener.h | 2 + 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp index 110f08bbe3..54d0e02e1e 100644 --- a/services/inputflinger/InputListener.cpp +++ b/services/inputflinger/InputListener.cpp @@ -231,6 +231,31 @@ NotifyPointerCaptureChangedArgs::NotifyPointerCaptureChangedArgs( int32_t id, nsecs_t eventTime, const PointerCaptureRequest& request) : id(id), eventTime(eventTime), request(request) {} +// --- InputListenerInterface --- + +// Helper to std::visit with lambdas. +template +struct Visitor : V... {}; +// explicit deduction guide (not needed as of C++20) +template +Visitor(V...) -> Visitor; + +void InputListenerInterface::notify(const NotifyArgs& generalArgs) { + Visitor v{ + [&](const NotifyConfigurationChangedArgs& args) { notifyConfigurationChanged(&args); }, + [&](const NotifyKeyArgs& args) { notifyKey(&args); }, + [&](const NotifyMotionArgs& args) { notifyMotion(&args); }, + [&](const NotifySwitchArgs& args) { notifySwitch(&args); }, + [&](const NotifySensorArgs& args) { notifySensor(&args); }, + [&](const NotifyVibratorStateArgs& args) { notifyVibratorState(&args); }, + [&](const NotifyDeviceResetArgs& args) { notifyDeviceReset(&args); }, + [&](const NotifyPointerCaptureChangedArgs& args) { + notifyPointerCaptureChanged(&args); + }, + }; + std::visit(v, generalArgs); +} + // --- QueuedInputListener --- static inline void traceEvent(const char* functionName, int32_t id) { @@ -284,30 +309,9 @@ void QueuedInputListener::notifyPointerCaptureChanged(const NotifyPointerCapture mArgsQueue.emplace_back(*args); } -// Helper to std::visit with lambdas. -template -struct Visitor : V... {}; -// explicit deduction guide (not needed as of C++20) -template -Visitor(V...) -> Visitor; - void QueuedInputListener::flush() { - Visitor v{ - [&](const NotifyConfigurationChangedArgs& args) { - mInnerListener.notifyConfigurationChanged(&args); - }, - [&](const NotifyKeyArgs& args) { mInnerListener.notifyKey(&args); }, - [&](const NotifyMotionArgs& args) { mInnerListener.notifyMotion(&args); }, - [&](const NotifySwitchArgs& args) { mInnerListener.notifySwitch(&args); }, - [&](const NotifySensorArgs& args) { mInnerListener.notifySensor(&args); }, - [&](const NotifyVibratorStateArgs& args) { mInnerListener.notifyVibratorState(&args); }, - [&](const NotifyDeviceResetArgs& args) { mInnerListener.notifyDeviceReset(&args); }, - [&](const NotifyPointerCaptureChangedArgs& args) { - mInnerListener.notifyPointerCaptureChanged(&args); - }, - }; for (const NotifyArgs& args : mArgsQueue) { - std::visit(v, args); + mInnerListener.notify(args); } mArgsQueue.clear(); } diff --git a/services/inputflinger/include/InputListener.h b/services/inputflinger/include/InputListener.h index 508d3410e6..036d57e1bb 100644 --- a/services/inputflinger/include/InputListener.h +++ b/services/inputflinger/include/InputListener.h @@ -233,6 +233,8 @@ public: virtual void notifyVibratorState(const NotifyVibratorStateArgs* args) = 0; virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) = 0; virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) = 0; + + void notify(const NotifyArgs& args); }; /* -- GitLab From 7b3ea0be365356d3c9554960f85cd0c68df03ee3 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 16 Sep 2022 14:23:20 -0700 Subject: [PATCH 0319/1310] Return events from EventHub Rather than filling some buffer, EventHub will now return events explicitly. This will simplify the interface and avoid storing the buffer when no events are being actively produced. Bug: 211379801 Test: atest libinput_tests inputflinger_tests Change-Id: Ic4ba7789f899b2de9fbe5a0abe54475b74eb5f82 --- services/inputflinger/reader/EventHub.cpp | 68 ++++++++++--------- services/inputflinger/reader/InputReader.cpp | 6 +- .../inputflinger/reader/include/EventHub.h | 4 +- .../inputflinger/reader/include/InputReader.h | 4 -- services/inputflinger/tests/EventHub_test.cpp | 10 ++- .../inputflinger/tests/InputReader_test.cpp | 9 ++- .../tests/fuzzers/InputReaderFuzzer.cpp | 1 - .../tests/fuzzers/MapperHelpers.h | 40 +++++------ 8 files changed, 68 insertions(+), 74 deletions(-) diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index 5351a51016..5c24244dfd 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -76,6 +76,8 @@ static constexpr size_t OBFUSCATED_LENGTH = 8; static constexpr int32_t FF_STRONG_MAGNITUDE_CHANNEL_IDX = 0; static constexpr int32_t FF_WEAK_MAGNITUDE_CHANNEL_IDX = 1; +static constexpr size_t EVENT_BUFFER_SIZE = 256; + // Mapping for input battery class node IDs lookup. // https://www.kernel.org/doc/Documentation/power/power_supply_class.txt static const std::unordered_map BATTERY_CLASSES = @@ -1633,14 +1635,13 @@ std::optional EventHub::getBatteryStatus(int32_t deviceId, int32_t batt return std::nullopt; } -size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) { - ALOG_ASSERT(bufferSize >= 1); - +std::vector EventHub::getEvents(int timeoutMillis) { std::scoped_lock _l(mLock); + constexpr size_t bufferSize = EVENT_BUFFER_SIZE; struct input_event readBuffer[bufferSize]; - RawEvent* event = buffer; + std::vector events; size_t capacity = bufferSize; bool awoken = false; for (;;) { @@ -1661,15 +1662,17 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz for (auto it = mClosingDevices.begin(); it != mClosingDevices.end();) { std::unique_ptr device = std::move(*it); ALOGV("Reporting device closed: id=%d, name=%s\n", device->id, device->path.c_str()); - event->when = now; - event->deviceId = (device->id == mBuiltInKeyboardId) + const int32_t deviceId = (device->id == mBuiltInKeyboardId) ? ReservedInputDeviceId::BUILT_IN_KEYBOARD_ID : device->id; - event->type = DEVICE_REMOVED; - event += 1; + events.push_back({ + .when = now, + .deviceId = deviceId, + .type = DEVICE_REMOVED, + }); it = mClosingDevices.erase(it); mNeedToSendFinishedDeviceScan = true; - if (--capacity == 0) { + if (events.size() == capacity) { break; } } @@ -1684,10 +1687,12 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz std::unique_ptr device = std::move(*mOpeningDevices.rbegin()); mOpeningDevices.pop_back(); ALOGV("Reporting device opened: id=%d, name=%s\n", device->id, device->path.c_str()); - event->when = now; - event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id; - event->type = DEVICE_ADDED; - event += 1; + const int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id; + events.push_back({ + .when = now, + .deviceId = deviceId, + .type = DEVICE_ADDED, + }); // Try to find a matching video device by comparing device names for (auto it = mUnattachedVideoDevices.begin(); it != mUnattachedVideoDevices.end(); @@ -1705,17 +1710,18 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz ALOGW("Device id %d exists, replaced.", device->id); } mNeedToSendFinishedDeviceScan = true; - if (--capacity == 0) { + if (events.size() == capacity) { break; } } if (mNeedToSendFinishedDeviceScan) { mNeedToSendFinishedDeviceScan = false; - event->when = now; - event->type = FINISHED_DEVICE_SCAN; - event += 1; - if (--capacity == 0) { + events.push_back({ + .when = now, + .type = FINISHED_DEVICE_SCAN, + }); + if (events.size() == capacity) { break; } } @@ -1794,21 +1800,21 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz } else if ((readSize % sizeof(struct input_event)) != 0) { ALOGE("could not get event (wrong size: %d)", readSize); } else { - int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id; + const int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id; - size_t count = size_t(readSize) / sizeof(struct input_event); + const size_t count = size_t(readSize) / sizeof(struct input_event); for (size_t i = 0; i < count; i++) { struct input_event& iev = readBuffer[i]; - event->when = processEventTimestamp(iev); - event->readTime = systemTime(SYSTEM_TIME_MONOTONIC); - event->deviceId = deviceId; - event->type = iev.type; - event->code = iev.code; - event->value = iev.value; - event += 1; - capacity -= 1; + events.push_back({ + .when = processEventTimestamp(iev), + .readTime = systemTime(SYSTEM_TIME_MONOTONIC), + .deviceId = deviceId, + .type = iev.type, + .code = iev.code, + .value = iev.value, + }); } - if (capacity == 0) { + if (events.size() >= capacity) { // The result buffer is full. Reset the pending event index // so we will try to read the device again on the next iteration. mPendingEventIndex -= 1; @@ -1844,7 +1850,7 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz } // Return now if we have collected any events or if we were explicitly awoken. - if (event != buffer || awoken) { + if (!events.empty() || awoken) { break; } @@ -1890,7 +1896,7 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz } // All done, return the number of events we read. - return event - buffer; + return events; } std::vector EventHub::getVideoFrames(int32_t deviceId) { diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 4c38ce81b6..86508766f5 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -120,14 +120,14 @@ void InputReader::loopOnce() { } } // release lock - size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); + std::vector events = mEventHub->getEvents(timeoutMillis); { // acquire lock std::scoped_lock _l(mLock); mReaderIsAliveCondition.notify_all(); - if (count) { - processEventsLocked(mEventBuffer, count); + if (!events.empty()) { + processEventsLocked(events.data(), events.size()); } if (mNextTimeout != LLONG_MAX) { diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index 6b8cc257e9..2eeb3f46e5 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -282,7 +282,7 @@ public: * * Returns the number of events obtained, or 0 if the timeout expired. */ - virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) = 0; + virtual std::vector getEvents(int timeoutMillis) = 0; virtual std::vector getVideoFrames(int32_t deviceId) = 0; virtual base::Result> mapSensor( int32_t deviceId, int32_t absCode) const = 0; @@ -500,7 +500,7 @@ public: bool markSupportedKeyCodes(int32_t deviceId, const std::vector& keyCodes, uint8_t* outFlags) const override final; - size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) override final; + std::vector getEvents(int timeoutMillis) override final; std::vector getVideoFrames(int32_t deviceId) override final; bool hasScanCode(int32_t deviceId, int32_t scanCode) const override final; diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h index fbce87f9a7..012d43fc3a 100644 --- a/services/inputflinger/reader/include/InputReader.h +++ b/services/inputflinger/reader/include/InputReader.h @@ -170,10 +170,6 @@ private: InputReaderConfiguration mConfig GUARDED_BY(mLock); - // The event queue. - static const int EVENT_BUFFER_SIZE = 256; - RawEvent mEventBuffer[EVENT_BUFFER_SIZE] GUARDED_BY(mLock); - // An input device can represent a collection of EventHub devices. This map provides a way // to lookup the input device instance from the EventHub device id. std::unordered_map> mDevices diff --git a/services/inputflinger/tests/EventHub_test.cpp b/services/inputflinger/tests/EventHub_test.cpp index 6ef6e4498c..9380c7142f 100644 --- a/services/inputflinger/tests/EventHub_test.cpp +++ b/services/inputflinger/tests/EventHub_test.cpp @@ -99,8 +99,6 @@ protected: }; std::vector EventHubTest::getEvents(std::optional expectedEvents) { - static constexpr size_t EVENT_BUFFER_SIZE = 256; - std::array eventBuffer; std::vector events; while (true) { @@ -108,12 +106,12 @@ std::vector EventHubTest::getEvents(std::optional expectedEven if (expectedEvents) { timeout = 2s; } - const size_t count = - mEventHub->getEvents(timeout.count(), eventBuffer.data(), eventBuffer.size()); - if (count == 0) { + + std::vector newEvents = mEventHub->getEvents(timeout.count()); + if (newEvents.empty()) { break; } - events.insert(events.end(), eventBuffer.begin(), eventBuffer.begin() + count); + events.insert(events.end(), newEvents.begin(), newEvents.end()); if (expectedEvents && events.size() >= *expectedEvents) { break; } diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index f1f1abd8b0..b36b49812d 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -826,15 +826,14 @@ private: mExcludedDevices = devices; } - size_t getEvents(int, RawEvent* buffer, size_t bufferSize) override { + std::vector getEvents(int) override { std::scoped_lock lock(mLock); - const size_t filledSize = std::min(mEvents.size(), bufferSize); - std::copy(mEvents.begin(), mEvents.begin() + filledSize, buffer); + std::vector buffer; + std::swap(buffer, mEvents); - mEvents.erase(mEvents.begin(), mEvents.begin() + filledSize); mEventsCondition.notify_all(); - return filledSize; + return buffer; } std::vector getVideoFrames(int32_t deviceId) override { diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp index f5bd29763e..a9f5a3ac86 100644 --- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp @@ -169,7 +169,6 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { std::shared_ptr fuzzEventHub = std::make_shared(fdp); std::unique_ptr reader = std::make_unique(fuzzEventHub, fuzzPolicy, fuzzListener); - fuzzEventHub->addEvents(fdp); size_t patternCount = fdp->ConsumeIntegralInRange(1, 260); VibrationSequence pattern(patternCount); for (size_t i = 0; i < patternCount; ++i) { diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index 53a7b169e4..03c226600e 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -114,30 +114,12 @@ class FuzzEventHub : public EventHubInterface { InputDeviceIdentifier mIdentifier; std::vector mVideoFrames; PropertyMap mFuzzConfig; - size_t mCount = 0; - std::array mBuf; std::shared_ptr mFdp; public: FuzzEventHub(std::shared_ptr fdp) : mFdp(std::move(fdp)) {} ~FuzzEventHub() {} void addProperty(std::string key, std::string value) { mFuzzConfig.addProperty(key, value); } - void addEvents(std::shared_ptr fdp) { - mCount = fdp->ConsumeIntegralInRange(0, kMaxSize); - - for (size_t i = 0; i < mCount; ++i) { - int32_t type = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidTypes) - : fdp->ConsumeIntegral(); - int32_t code = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidCodes) - : fdp->ConsumeIntegral(); - mBuf[i] = {fdp->ConsumeIntegral(), - fdp->ConsumeIntegral(), - fdp->ConsumeIntegral(), - type, - code, - fdp->ConsumeIntegral()}; - } - } ftl::Flags getDeviceClasses(int32_t deviceId) const override { return ftl::Flags(mFdp->ConsumeIntegral()); @@ -168,10 +150,24 @@ public: return mFdp->ConsumeIntegral(); } void setExcludedDevices(const std::vector& devices) override {} - size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) override { - for (size_t i = 0; i < mCount; ++i) buffer[i] = mBuf[i]; - - return mCount; + std::vector getEvents(int timeoutMillis) override { + std::vector events; + const size_t count = mFdp->ConsumeIntegralInRange(0, kMaxSize); + for (size_t i = 0; i < count; ++i) { + int32_t type = mFdp->ConsumeBool() ? mFdp->PickValueInArray(kValidTypes) + : mFdp->ConsumeIntegral(); + int32_t code = mFdp->ConsumeBool() ? mFdp->PickValueInArray(kValidCodes) + : mFdp->ConsumeIntegral(); + events.push_back({ + .when = mFdp->ConsumeIntegral(), + .readTime = mFdp->ConsumeIntegral(), + .deviceId = mFdp->ConsumeIntegral(), + .type = type, + .code = code, + .value = mFdp->ConsumeIntegral(), + }); + } + return events; } std::vector getVideoFrames(int32_t deviceId) override { return mVideoFrames; } -- GitLab From 2fb3dd6f5b3e9aa2d3544fd1e4818a75b65b0481 Mon Sep 17 00:00:00 2001 From: Kevin Jeon Date: Wed, 28 Sep 2022 18:48:55 +0000 Subject: [PATCH 0320/1310] Reduce timeout for DUMP NETSTATS PROTO This change reduces the timeout on 'dumpsys netstats --proto' from 120s to 5s. This is because go/bugreport-durations shows that the DUMP NETSTATS PROTO is always under 1s unless it stalls, in which case it will always hit the timeout. Ignore-AOSP-First: The netstats section only exists on internal builds. Test: Collect a bug report with 'adb bugreportz' to verify that the 'DUMP NETSTATS PROTO' section still exists. Bug: 249522178 Change-Id: I377bdb0ea61b53e76b6329fc52d634d1c2c0b0ad --- cmds/dumpstate/dumpstate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp index 0b698296b7..73046f7a67 100644 --- a/cmds/dumpstate/dumpstate.cpp +++ b/cmds/dumpstate/dumpstate.cpp @@ -1049,7 +1049,7 @@ static void DumpNetstatsProto() { return; } RunCommandToFd(fd, "", {"dumpsys", "netstats", "--proto"}, - CommandOptions::WithTimeout(120).Build()); + CommandOptions::WithTimeout(5).Build()); bool empty = 0 == lseek(fd, 0, SEEK_END); if (!empty) { ds.EnqueueAddZipEntryAndCleanupIfNeeded(kProtoPath + "netstats" + kProtoExt, -- GitLab From edeec3ba65694b4c4bc1d83b2af3a4301424296f Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 26 Aug 2022 22:33:55 +0000 Subject: [PATCH 0321/1310] Revert behavior of loading the AssociatedDevice ag/19714571 changed the behavior of loading AssociatedDevices so that it was only read once for each sysfsPath, even if multiple event hub devices had the same sysfs path. However, that caused CTS hardware tests that read battery info to be flaky. To mitigate this, we revert to the previous behavior of re-loading the AssociatedDevice for each event hub device for now, until we can debug the source of this issue. Bug: 243979881 Test: atest SonyDualshock4BluetoothTest#testBattery --iterations 100 Change-Id: If7ab1076f0528945cf75073986ad0397471533ca --- services/inputflinger/reader/EventHub.cpp | 28 +++++++++++++++---- .../inputflinger/reader/include/EventHub.h | 12 +++++++- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index 5351a51016..5f715666d9 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -1416,17 +1416,28 @@ std::shared_ptr EventHub::obtainAssociatedDevi } const auto& path = *sysfsRootPathOpt; - for (const auto& [id, dev] : mDevices) { - if (dev->associatedDevice && dev->associatedDevice->sysfsRootPath == path) { - return dev->associatedDevice; - } - } - return std::make_shared( + std::shared_ptr associatedDevice = std::make_shared( AssociatedDevice{.sysfsRootPath = path, .countryCode = readCountryCodeLocked(path), .batteryInfos = readBatteryConfiguration(path), .lightInfos = readLightsConfiguration(path)}); + + bool associatedDeviceChanged = false; + for (const auto& [id, dev] : mDevices) { + if (dev->associatedDevice && dev->associatedDevice->sysfsRootPath == path) { + if (*associatedDevice != *dev->associatedDevice) { + associatedDeviceChanged = true; + dev->associatedDevice = associatedDevice; + } + associatedDevice = dev->associatedDevice; + } + } + ALOGI_IF(associatedDeviceChanged, + "The AssociatedDevice changed for path '%s'. Using new AssociatedDevice: %s", + path.c_str(), associatedDevice->dump().c_str()); + + return associatedDevice; } void EventHub::vibrate(int32_t deviceId, const VibrationElement& element) { @@ -2645,4 +2656,9 @@ void EventHub::monitor() const { std::unique_lock lock(mLock); } +std::string EventHub::AssociatedDevice::dump() const { + return StringPrintf("path=%s, numBatteries=%zu, numLight=%zu", sysfsRootPath.c_str(), + batteryInfos.size(), lightInfos.size()); +} + } // namespace android diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index 6b8cc257e9..ceae10db0c 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -195,6 +195,9 @@ struct RawLightInfo { ftl::Flags flags; std::array rgbIndex; std::filesystem::path path; + + bool operator==(const RawLightInfo&) const = default; + bool operator!=(const RawLightInfo&) const = default; }; /* Describes a raw battery. */ @@ -203,6 +206,9 @@ struct RawBatteryInfo { std::string name; ftl::Flags flags; std::filesystem::path path; + + bool operator==(const RawBatteryInfo&) const = default; + bool operator!=(const RawBatteryInfo&) const = default; }; /* @@ -551,6 +557,10 @@ private: hardware::input::InputDeviceCountryCode countryCode; std::unordered_map batteryInfos; std::unordered_map lightInfos; + + bool operator==(const AssociatedDevice&) const = default; + bool operator!=(const AssociatedDevice&) const = default; + std::string dump() const; }; struct Device { @@ -584,7 +594,7 @@ private: // A shared_ptr of a device associated with the input device. // The input devices that have the same sysfs path have the same associated device. - const std::shared_ptr associatedDevice; + std::shared_ptr associatedDevice; int32_t controllerNumber; -- GitLab From 45fd904efb5af52fbdd130ef97ef3948046d8f54 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 27 Sep 2022 13:26:20 -0700 Subject: [PATCH 0322/1310] Remove redundant EventHub variables Some of the variables are just copies of the same constant. Consolidate to simply use 'capacity'. Bug: 211379801 Test: atest inputflinger_tests Change-Id: I6e51ba5671a31c6dc89c4511a37f0448934a1a3e --- services/inputflinger/reader/EventHub.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index 5c24244dfd..443c89a4d2 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -1638,11 +1638,9 @@ std::optional EventHub::getBatteryStatus(int32_t deviceId, int32_t batt std::vector EventHub::getEvents(int timeoutMillis) { std::scoped_lock _l(mLock); - constexpr size_t bufferSize = EVENT_BUFFER_SIZE; - struct input_event readBuffer[bufferSize]; + std::array readBuffer; std::vector events; - size_t capacity = bufferSize; bool awoken = false; for (;;) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); @@ -1672,7 +1670,7 @@ std::vector EventHub::getEvents(int timeoutMillis) { }); it = mClosingDevices.erase(it); mNeedToSendFinishedDeviceScan = true; - if (events.size() == capacity) { + if (events.size() == EVENT_BUFFER_SIZE) { break; } } @@ -1710,7 +1708,7 @@ std::vector EventHub::getEvents(int timeoutMillis) { ALOGW("Device id %d exists, replaced.", device->id); } mNeedToSendFinishedDeviceScan = true; - if (events.size() == capacity) { + if (events.size() == EVENT_BUFFER_SIZE) { break; } } @@ -1721,7 +1719,7 @@ std::vector EventHub::getEvents(int timeoutMillis) { .when = now, .type = FINISHED_DEVICE_SCAN, }); - if (events.size() == capacity) { + if (events.size() == EVENT_BUFFER_SIZE) { break; } } @@ -1785,12 +1783,13 @@ std::vector EventHub::getEvents(int timeoutMillis) { // This must be an input event if (eventItem.events & EPOLLIN) { int32_t readSize = - read(device->fd, readBuffer, sizeof(struct input_event) * capacity); + read(device->fd, readBuffer.data(), + sizeof(decltype(readBuffer)::value_type) * readBuffer.size()); if (readSize == 0 || (readSize < 0 && errno == ENODEV)) { // Device was removed before INotify noticed. ALOGW("could not get event, removed? (fd: %d size: %" PRId32 - " bufferSize: %zu capacity: %zu errno: %d)\n", - device->fd, readSize, bufferSize, capacity, errno); + " capacity: %zu errno: %d)\n", + device->fd, readSize, readBuffer.size(), errno); deviceChanged = true; closeDeviceLocked(*device); } else if (readSize < 0) { @@ -1814,7 +1813,7 @@ std::vector EventHub::getEvents(int timeoutMillis) { .value = iev.value, }); } - if (events.size() >= capacity) { + if (events.size() >= EVENT_BUFFER_SIZE) { // The result buffer is full. Reset the pending event index // so we will try to read the device again on the next iteration. mPendingEventIndex -= 1; -- GitLab From 30feb8c162aa2e5348bba20e99e8db2a61bac6e7 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 28 Sep 2022 10:48:29 -0700 Subject: [PATCH 0323/1310] Delete mController when eventHub device is going away When an event hub device is going away, the controller associated with it should be deleted. Before this CL, mController would remain, and its use would result in an illegal memory access, because it was storing a reference to a deleted context. Bug: 245770596 Test: atest inputflinger_tests:InputDeviceTest#DumpDoesNotCrash Change-Id: I298f43172472f74fa4d5d8e0b7f52bd5c13d14f7 --- services/inputflinger/reader/InputDevice.cpp | 4 ++++ services/inputflinger/tests/InputReader_test.cpp | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 44a005e5a9..6b9b9f1255 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -234,6 +234,10 @@ void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) { } void InputDevice::removeEventHubDevice(int32_t eventHubId) { + if (mController != nullptr && mController->getEventHubId() == eventHubId) { + // Delete mController, since the corresponding eventhub device is going away + mController = nullptr; + } mDevices.erase(eventHubId); } diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index b36b49812d..78ea692085 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -2971,6 +2971,21 @@ TEST_F(InputDeviceTest, Configure_UniqueId_CorrectlyMatches) { ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueId()); } +/** + * This test reproduces a crash caused by a dangling reference that remains after device is added + * and removed. The reference is accessed in InputDevice::dump(..); + */ +TEST_F(InputDeviceTest, DumpDoesNotCrash) { + constexpr int32_t TEST_EVENTHUB_ID = 10; + mFakeEventHub->addDevice(TEST_EVENTHUB_ID, "Test EventHub device", InputDeviceClass::BATTERY); + + InputDevice device(mReader->getContext(), 1 /*id*/, 2 /*generation*/, {} /*identifier*/); + device.addEventHubDevice(TEST_EVENTHUB_ID, true /*populateMappers*/); + device.removeEventHubDevice(TEST_EVENTHUB_ID); + std::string dumpStr, eventHubDevStr; + device.dump(dumpStr, eventHubDevStr); +} + // --- InputMapperTest --- class InputMapperTest : public testing::Test { -- GitLab From 7851303579fe07d1fde766b4511b871840aefe61 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 15 Sep 2022 18:42:05 -0700 Subject: [PATCH 0324/1310] Split NotifyArgs into a separate file This way, we can pass NotifyArgs without having to learn about InputListener. InputListener, however, knows how to route the args. Bug: 211379801 Test: m libinputreader_arc Change-Id: Idf653facd26348fb1ebdf48fb53cf004603587a1 --- services/inputflinger/Android.bp | 1 + services/inputflinger/InputListener.cpp | 200 --------------- services/inputflinger/NotifyArgs.cpp | 228 ++++++++++++++++++ services/inputflinger/include/InputListener.h | 192 +-------------- services/inputflinger/include/NotifyArgs.h | 216 +++++++++++++++++ 5 files changed, 446 insertions(+), 391 deletions(-) create mode 100644 services/inputflinger/NotifyArgs.cpp create mode 100644 services/inputflinger/include/NotifyArgs.h diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp index 88a9acb80e..011cdf8925 100644 --- a/services/inputflinger/Android.bp +++ b/services/inputflinger/Android.bp @@ -139,6 +139,7 @@ filegroup { "InputListener.cpp", "InputReaderBase.cpp", "InputThread.cpp", + "NotifyArgs.cpp", "VibrationElement.cpp", ], } diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp index 54d0e02e1e..0972e224e9 100644 --- a/services/inputflinger/InputListener.cpp +++ b/services/inputflinger/InputListener.cpp @@ -31,206 +31,6 @@ using android::base::StringPrintf; namespace android { -// --- NotifyConfigurationChangedArgs --- - -NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs(int32_t id, nsecs_t eventTime) - : id(id), eventTime(eventTime) {} - -// --- NotifyKeyArgs --- - -NotifyKeyArgs::NotifyKeyArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId, - uint32_t source, int32_t displayId, uint32_t policyFlags, - int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, - int32_t metaState, nsecs_t downTime) - : id(id), - eventTime(eventTime), - deviceId(deviceId), - source(source), - displayId(displayId), - policyFlags(policyFlags), - action(action), - flags(flags), - keyCode(keyCode), - scanCode(scanCode), - metaState(metaState), - downTime(downTime), - readTime(readTime) {} - -// --- NotifyMotionArgs --- - -NotifyMotionArgs::NotifyMotionArgs( - int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId, uint32_t source, - int32_t displayId, uint32_t policyFlags, int32_t action, int32_t actionButton, - int32_t flags, int32_t metaState, int32_t buttonState, MotionClassification classification, - int32_t edgeFlags, uint32_t pointerCount, const PointerProperties* pointerProperties, - const PointerCoords* pointerCoords, float xPrecision, float yPrecision, - float xCursorPosition, float yCursorPosition, nsecs_t downTime, - const std::vector& videoFrames) - : id(id), - eventTime(eventTime), - deviceId(deviceId), - source(source), - displayId(displayId), - policyFlags(policyFlags), - action(action), - actionButton(actionButton), - flags(flags), - metaState(metaState), - buttonState(buttonState), - classification(classification), - edgeFlags(edgeFlags), - pointerCount(pointerCount), - xPrecision(xPrecision), - yPrecision(yPrecision), - xCursorPosition(xCursorPosition), - yCursorPosition(yCursorPosition), - downTime(downTime), - readTime(readTime), - videoFrames(videoFrames) { - for (uint32_t i = 0; i < pointerCount; i++) { - this->pointerProperties[i].copyFrom(pointerProperties[i]); - this->pointerCoords[i].copyFrom(pointerCoords[i]); - } -} - -NotifyMotionArgs::NotifyMotionArgs(const NotifyMotionArgs& other) - : id(other.id), - eventTime(other.eventTime), - deviceId(other.deviceId), - source(other.source), - displayId(other.displayId), - policyFlags(other.policyFlags), - action(other.action), - actionButton(other.actionButton), - flags(other.flags), - metaState(other.metaState), - buttonState(other.buttonState), - classification(other.classification), - edgeFlags(other.edgeFlags), - pointerCount(other.pointerCount), - xPrecision(other.xPrecision), - yPrecision(other.yPrecision), - xCursorPosition(other.xCursorPosition), - yCursorPosition(other.yCursorPosition), - downTime(other.downTime), - readTime(other.readTime), - videoFrames(other.videoFrames) { - for (uint32_t i = 0; i < pointerCount; i++) { - pointerProperties[i].copyFrom(other.pointerProperties[i]); - pointerCoords[i].copyFrom(other.pointerCoords[i]); - } -} - -static inline bool isCursorPositionEqual(float lhs, float rhs) { - return (isnan(lhs) && isnan(rhs)) || lhs == rhs; -} - -bool NotifyMotionArgs::operator==(const NotifyMotionArgs& rhs) const { - bool equal = id == rhs.id && eventTime == rhs.eventTime && readTime == rhs.readTime && - deviceId == rhs.deviceId && source == rhs.source && displayId == rhs.displayId && - policyFlags == rhs.policyFlags && action == rhs.action && - actionButton == rhs.actionButton && flags == rhs.flags && metaState == rhs.metaState && - buttonState == rhs.buttonState && classification == rhs.classification && - edgeFlags == rhs.edgeFlags && - pointerCount == rhs.pointerCount - // PointerProperties and PointerCoords are compared separately below - && xPrecision == rhs.xPrecision && yPrecision == rhs.yPrecision && - isCursorPositionEqual(xCursorPosition, rhs.xCursorPosition) && - isCursorPositionEqual(yCursorPosition, rhs.yCursorPosition) && - downTime == rhs.downTime && videoFrames == rhs.videoFrames; - if (!equal) { - return false; - } - - for (size_t i = 0; i < pointerCount; i++) { - equal = - pointerProperties[i] == rhs.pointerProperties[i] - && pointerCoords[i] == rhs.pointerCoords[i]; - if (!equal) { - return false; - } - } - return true; -} - -std::string NotifyMotionArgs::dump() const { - std::string coords; - for (uint32_t i = 0; i < pointerCount; i++) { - if (!coords.empty()) { - coords += ", "; - } - coords += StringPrintf("{%" PRIu32 ": ", i); - coords += - StringPrintf("id=%" PRIu32 " x=%.1f y=%.1f pressure=%.1f", pointerProperties[i].id, - pointerCoords[i].getX(), pointerCoords[i].getY(), - pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); - const int32_t toolType = pointerProperties[i].toolType; - if (toolType != AMOTION_EVENT_TOOL_TYPE_FINGER) { - coords += StringPrintf(" toolType=%s", motionToolTypeToString(toolType)); - } - const float major = pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR); - const float minor = pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR); - const float orientation = pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION); - if (major != 0 || minor != 0) { - coords += StringPrintf(" major=%.1f minor=%.1f orientation=%.1f", major, minor, - orientation); - } - coords += "}"; - } - return StringPrintf("NotifyMotionArgs(id=%" PRId32 ", eventTime=%" PRId64 ", deviceId=%" PRId32 - ", source=%s, action=%s, pointerCount=%" PRIu32 - " pointers=%s, flags=0x%08x)", - id, eventTime, deviceId, inputEventSourceToString(source).c_str(), - MotionEvent::actionToString(action).c_str(), pointerCount, coords.c_str(), - flags); -} - -// --- NotifySwitchArgs --- - -NotifySwitchArgs::NotifySwitchArgs(int32_t id, nsecs_t eventTime, uint32_t policyFlags, - uint32_t switchValues, uint32_t switchMask) - : id(id), - eventTime(eventTime), - policyFlags(policyFlags), - switchValues(switchValues), - switchMask(switchMask) {} - -// --- NotifySensorArgs --- - -NotifySensorArgs::NotifySensorArgs(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source, - InputDeviceSensorType sensorType, - InputDeviceSensorAccuracy accuracy, bool accuracyChanged, - nsecs_t hwTimestamp, std::vector values) - : id(id), - eventTime(eventTime), - deviceId(deviceId), - source(source), - sensorType(sensorType), - accuracy(accuracy), - accuracyChanged(accuracyChanged), - hwTimestamp(hwTimestamp), - values(std::move(values)) {} - -// --- NotifyVibratorStateArgs --- - -NotifyVibratorStateArgs::NotifyVibratorStateArgs(int32_t id, nsecs_t eventTime, int32_t deviceId, - bool isOn) - : id(id), eventTime(eventTime), deviceId(deviceId), isOn(isOn) {} - -NotifyVibratorStateArgs::NotifyVibratorStateArgs(const NotifyVibratorStateArgs& other) - : id(other.id), eventTime(other.eventTime), deviceId(other.deviceId), isOn(other.isOn) {} - -// --- NotifyDeviceResetArgs --- - -NotifyDeviceResetArgs::NotifyDeviceResetArgs(int32_t id, nsecs_t eventTime, int32_t deviceId) - : id(id), eventTime(eventTime), deviceId(deviceId) {} - -// --- NotifyPointerCaptureChangedArgs --- - -NotifyPointerCaptureChangedArgs::NotifyPointerCaptureChangedArgs( - int32_t id, nsecs_t eventTime, const PointerCaptureRequest& request) - : id(id), eventTime(eventTime), request(request) {} - // --- InputListenerInterface --- // Helper to std::visit with lambdas. diff --git a/services/inputflinger/NotifyArgs.cpp b/services/inputflinger/NotifyArgs.cpp new file mode 100644 index 0000000000..1f37774218 --- /dev/null +++ b/services/inputflinger/NotifyArgs.cpp @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "NotifyArgs" + +#define ATRACE_TAG ATRACE_TAG_INPUT + +#include "NotifyArgs.h" + +#include +#include +#include +#include + +using android::base::StringPrintf; + +namespace android { + +// --- NotifyConfigurationChangedArgs --- + +NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs(int32_t id, nsecs_t eventTime) + : id(id), eventTime(eventTime) {} + +// --- NotifyKeyArgs --- + +NotifyKeyArgs::NotifyKeyArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId, + uint32_t source, int32_t displayId, uint32_t policyFlags, + int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, + int32_t metaState, nsecs_t downTime) + : id(id), + eventTime(eventTime), + deviceId(deviceId), + source(source), + displayId(displayId), + policyFlags(policyFlags), + action(action), + flags(flags), + keyCode(keyCode), + scanCode(scanCode), + metaState(metaState), + downTime(downTime), + readTime(readTime) {} + +// --- NotifyMotionArgs --- + +NotifyMotionArgs::NotifyMotionArgs( + int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId, uint32_t source, + int32_t displayId, uint32_t policyFlags, int32_t action, int32_t actionButton, + int32_t flags, int32_t metaState, int32_t buttonState, MotionClassification classification, + int32_t edgeFlags, uint32_t pointerCount, const PointerProperties* pointerProperties, + const PointerCoords* pointerCoords, float xPrecision, float yPrecision, + float xCursorPosition, float yCursorPosition, nsecs_t downTime, + const std::vector& videoFrames) + : id(id), + eventTime(eventTime), + deviceId(deviceId), + source(source), + displayId(displayId), + policyFlags(policyFlags), + action(action), + actionButton(actionButton), + flags(flags), + metaState(metaState), + buttonState(buttonState), + classification(classification), + edgeFlags(edgeFlags), + pointerCount(pointerCount), + xPrecision(xPrecision), + yPrecision(yPrecision), + xCursorPosition(xCursorPosition), + yCursorPosition(yCursorPosition), + downTime(downTime), + readTime(readTime), + videoFrames(videoFrames) { + for (uint32_t i = 0; i < pointerCount; i++) { + this->pointerProperties[i].copyFrom(pointerProperties[i]); + this->pointerCoords[i].copyFrom(pointerCoords[i]); + } +} + +NotifyMotionArgs::NotifyMotionArgs(const NotifyMotionArgs& other) + : id(other.id), + eventTime(other.eventTime), + deviceId(other.deviceId), + source(other.source), + displayId(other.displayId), + policyFlags(other.policyFlags), + action(other.action), + actionButton(other.actionButton), + flags(other.flags), + metaState(other.metaState), + buttonState(other.buttonState), + classification(other.classification), + edgeFlags(other.edgeFlags), + pointerCount(other.pointerCount), + xPrecision(other.xPrecision), + yPrecision(other.yPrecision), + xCursorPosition(other.xCursorPosition), + yCursorPosition(other.yCursorPosition), + downTime(other.downTime), + readTime(other.readTime), + videoFrames(other.videoFrames) { + for (uint32_t i = 0; i < pointerCount; i++) { + pointerProperties[i].copyFrom(other.pointerProperties[i]); + pointerCoords[i].copyFrom(other.pointerCoords[i]); + } +} + +static inline bool isCursorPositionEqual(float lhs, float rhs) { + return (isnan(lhs) && isnan(rhs)) || lhs == rhs; +} + +bool NotifyMotionArgs::operator==(const NotifyMotionArgs& rhs) const { + bool equal = id == rhs.id && eventTime == rhs.eventTime && readTime == rhs.readTime && + deviceId == rhs.deviceId && source == rhs.source && displayId == rhs.displayId && + policyFlags == rhs.policyFlags && action == rhs.action && + actionButton == rhs.actionButton && flags == rhs.flags && metaState == rhs.metaState && + buttonState == rhs.buttonState && classification == rhs.classification && + edgeFlags == rhs.edgeFlags && + pointerCount == rhs.pointerCount + // PointerProperties and PointerCoords are compared separately below + && xPrecision == rhs.xPrecision && yPrecision == rhs.yPrecision && + isCursorPositionEqual(xCursorPosition, rhs.xCursorPosition) && + isCursorPositionEqual(yCursorPosition, rhs.yCursorPosition) && + downTime == rhs.downTime && videoFrames == rhs.videoFrames; + if (!equal) { + return false; + } + + for (size_t i = 0; i < pointerCount; i++) { + equal = pointerProperties[i] == rhs.pointerProperties[i] && + pointerCoords[i] == rhs.pointerCoords[i]; + if (!equal) { + return false; + } + } + return true; +} + +std::string NotifyMotionArgs::dump() const { + std::string coords; + for (uint32_t i = 0; i < pointerCount; i++) { + if (!coords.empty()) { + coords += ", "; + } + coords += StringPrintf("{%" PRIu32 ": ", i); + coords += + StringPrintf("id=%" PRIu32 " x=%.1f y=%.1f pressure=%.1f", pointerProperties[i].id, + pointerCoords[i].getX(), pointerCoords[i].getY(), + pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); + const int32_t toolType = pointerProperties[i].toolType; + if (toolType != AMOTION_EVENT_TOOL_TYPE_FINGER) { + coords += StringPrintf(" toolType=%s", motionToolTypeToString(toolType)); + } + const float major = pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR); + const float minor = pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR); + const float orientation = pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION); + if (major != 0 || minor != 0) { + coords += StringPrintf(" major=%.1f minor=%.1f orientation=%.1f", major, minor, + orientation); + } + coords += "}"; + } + return StringPrintf("NotifyMotionArgs(id=%" PRId32 ", eventTime=%" PRId64 ", deviceId=%" PRId32 + ", source=%s, action=%s, pointerCount=%" PRIu32 + " pointers=%s, flags=0x%08x)", + id, eventTime, deviceId, inputEventSourceToString(source).c_str(), + MotionEvent::actionToString(action).c_str(), pointerCount, coords.c_str(), + flags); +} + +// --- NotifySwitchArgs --- + +NotifySwitchArgs::NotifySwitchArgs(int32_t id, nsecs_t eventTime, uint32_t policyFlags, + uint32_t switchValues, uint32_t switchMask) + : id(id), + eventTime(eventTime), + policyFlags(policyFlags), + switchValues(switchValues), + switchMask(switchMask) {} + +// --- NotifySensorArgs --- + +NotifySensorArgs::NotifySensorArgs(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source, + InputDeviceSensorType sensorType, + InputDeviceSensorAccuracy accuracy, bool accuracyChanged, + nsecs_t hwTimestamp, std::vector values) + : id(id), + eventTime(eventTime), + deviceId(deviceId), + source(source), + sensorType(sensorType), + accuracy(accuracy), + accuracyChanged(accuracyChanged), + hwTimestamp(hwTimestamp), + values(std::move(values)) {} + +// --- NotifyVibratorStateArgs --- + +NotifyVibratorStateArgs::NotifyVibratorStateArgs(int32_t id, nsecs_t eventTime, int32_t deviceId, + bool isOn) + : id(id), eventTime(eventTime), deviceId(deviceId), isOn(isOn) {} + +// --- NotifyDeviceResetArgs --- + +NotifyDeviceResetArgs::NotifyDeviceResetArgs(int32_t id, nsecs_t eventTime, int32_t deviceId) + : id(id), eventTime(eventTime), deviceId(deviceId) {} + +// --- NotifyPointerCaptureChangedArgs --- + +NotifyPointerCaptureChangedArgs::NotifyPointerCaptureChangedArgs( + int32_t id, nsecs_t eventTime, const PointerCaptureRequest& request) + : id(id), eventTime(eventTime), request(request) {} + +} // namespace android diff --git a/services/inputflinger/include/InputListener.h b/services/inputflinger/include/InputListener.h index 036d57e1bb..8647bcb737 100644 --- a/services/inputflinger/include/InputListener.h +++ b/services/inputflinger/include/InputListener.h @@ -21,200 +21,10 @@ #include #include #include +#include "NotifyArgs.h" namespace android { -class InputListenerInterface; - -/* Describes a configuration change event. */ -struct NotifyConfigurationChangedArgs { - int32_t id; - nsecs_t eventTime; - - inline NotifyConfigurationChangedArgs() { } - - NotifyConfigurationChangedArgs(int32_t id, nsecs_t eventTime); - - bool operator==(const NotifyConfigurationChangedArgs& rhs) const = default; - - NotifyConfigurationChangedArgs(const NotifyConfigurationChangedArgs& other) = default; -}; - -/* Describes a key event. */ -struct NotifyKeyArgs { - int32_t id; - nsecs_t eventTime; - - int32_t deviceId; - uint32_t source; - int32_t displayId; - uint32_t policyFlags; - int32_t action; - int32_t flags; - int32_t keyCode; - int32_t scanCode; - int32_t metaState; - nsecs_t downTime; - nsecs_t readTime; - - inline NotifyKeyArgs() { } - - NotifyKeyArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId, - uint32_t source, int32_t displayId, uint32_t policyFlags, int32_t action, - int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, - nsecs_t downTime); - - bool operator==(const NotifyKeyArgs& rhs) const = default; - - NotifyKeyArgs(const NotifyKeyArgs& other) = default; -}; - -/* Describes a motion event. */ -struct NotifyMotionArgs { - int32_t id; - nsecs_t eventTime; - - int32_t deviceId; - uint32_t source; - int32_t displayId; - uint32_t policyFlags; - int32_t action; - int32_t actionButton; - int32_t flags; - int32_t metaState; - int32_t buttonState; - /** - * Classification of the current touch gesture - */ - MotionClassification classification; - int32_t edgeFlags; - - uint32_t pointerCount; - PointerProperties pointerProperties[MAX_POINTERS]; - PointerCoords pointerCoords[MAX_POINTERS]; - float xPrecision; - float yPrecision; - /** - * Mouse cursor position when this event is reported relative to the origin of the specified - * display. Only valid if this is a mouse event (originates from a mouse or from a trackpad in - * gestures enabled mode. - */ - float xCursorPosition; - float yCursorPosition; - nsecs_t downTime; - nsecs_t readTime; - std::vector videoFrames; - - inline NotifyMotionArgs() { } - - NotifyMotionArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId, - uint32_t source, int32_t displayId, uint32_t policyFlags, int32_t action, - int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState, - MotionClassification classification, int32_t edgeFlags, uint32_t pointerCount, - const PointerProperties* pointerProperties, const PointerCoords* pointerCoords, - float xPrecision, float yPrecision, float xCursorPosition, - float yCursorPosition, nsecs_t downTime, - const std::vector& videoFrames); - - NotifyMotionArgs(const NotifyMotionArgs& other); - - bool operator==(const NotifyMotionArgs& rhs) const; - - std::string dump() const; -}; - -/* Describes a sensor event. */ -struct NotifySensorArgs { - int32_t id; - nsecs_t eventTime; - - int32_t deviceId; - uint32_t source; - InputDeviceSensorType sensorType; - InputDeviceSensorAccuracy accuracy; - bool accuracyChanged; - nsecs_t hwTimestamp; - std::vector values; - - inline NotifySensorArgs() {} - - NotifySensorArgs(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source, - InputDeviceSensorType sensorType, InputDeviceSensorAccuracy accuracy, - bool accuracyChanged, nsecs_t hwTimestamp, std::vector values); - - NotifySensorArgs(const NotifySensorArgs& other) = default; -}; - -/* Describes a switch event. */ -struct NotifySwitchArgs { - int32_t id; - nsecs_t eventTime; - - uint32_t policyFlags; - uint32_t switchValues; - uint32_t switchMask; - - inline NotifySwitchArgs() { } - - NotifySwitchArgs(int32_t id, nsecs_t eventTime, uint32_t policyFlags, uint32_t switchValues, - uint32_t switchMask); - - NotifySwitchArgs(const NotifySwitchArgs& other) = default; - - bool operator==(const NotifySwitchArgs& rhs) const = default; -}; - -/* Describes a device reset event, such as when a device is added, - * reconfigured, or removed. */ -struct NotifyDeviceResetArgs { - int32_t id; - nsecs_t eventTime; - - int32_t deviceId; - - inline NotifyDeviceResetArgs() { } - - NotifyDeviceResetArgs(int32_t id, nsecs_t eventTime, int32_t deviceId); - - NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other) = default; - - bool operator==(const NotifyDeviceResetArgs& rhs) const = default; -}; - -/* Describes a change in the state of Pointer Capture. */ -struct NotifyPointerCaptureChangedArgs { - // The sequence number of the Pointer Capture request, if enabled. - int32_t id; - nsecs_t eventTime; - - PointerCaptureRequest request; - - inline NotifyPointerCaptureChangedArgs() {} - - NotifyPointerCaptureChangedArgs(int32_t id, nsecs_t eventTime, const PointerCaptureRequest&); - - NotifyPointerCaptureChangedArgs(const NotifyPointerCaptureChangedArgs& other) = default; -}; - -/* Describes a vibrator state event. */ -struct NotifyVibratorStateArgs { - int32_t id; - nsecs_t eventTime; - - int32_t deviceId; - bool isOn; - - inline NotifyVibratorStateArgs() {} - - NotifyVibratorStateArgs(int32_t id, nsecs_t eventTIme, int32_t deviceId, bool isOn); - - NotifyVibratorStateArgs(const NotifyVibratorStateArgs& other); -}; - -using NotifyArgs = std::variant; - /* * The interface used by the InputReader to notify the InputListener about input events. */ diff --git a/services/inputflinger/include/NotifyArgs.h b/services/inputflinger/include/NotifyArgs.h new file mode 100644 index 0000000000..611b1aac81 --- /dev/null +++ b/services/inputflinger/include/NotifyArgs.h @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include + +namespace android { + +/* Describes a configuration change event. */ +struct NotifyConfigurationChangedArgs { + int32_t id; + nsecs_t eventTime; + + inline NotifyConfigurationChangedArgs() {} + + NotifyConfigurationChangedArgs(int32_t id, nsecs_t eventTime); + + bool operator==(const NotifyConfigurationChangedArgs& rhs) const = default; + + NotifyConfigurationChangedArgs(const NotifyConfigurationChangedArgs& other) = default; +}; + +/* Describes a key event. */ +struct NotifyKeyArgs { + int32_t id; + nsecs_t eventTime; + + int32_t deviceId; + uint32_t source; + int32_t displayId; + uint32_t policyFlags; + int32_t action; + int32_t flags; + int32_t keyCode; + int32_t scanCode; + int32_t metaState; + nsecs_t downTime; + nsecs_t readTime; + + inline NotifyKeyArgs() {} + + NotifyKeyArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId, + uint32_t source, int32_t displayId, uint32_t policyFlags, int32_t action, + int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, + nsecs_t downTime); + + bool operator==(const NotifyKeyArgs& rhs) const = default; + + NotifyKeyArgs(const NotifyKeyArgs& other) = default; +}; + +/* Describes a motion event. */ +struct NotifyMotionArgs { + int32_t id; + nsecs_t eventTime; + + int32_t deviceId; + uint32_t source; + int32_t displayId; + uint32_t policyFlags; + int32_t action; + int32_t actionButton; + int32_t flags; + int32_t metaState; + int32_t buttonState; + /** + * Classification of the current touch gesture + */ + MotionClassification classification; + int32_t edgeFlags; + + uint32_t pointerCount; + PointerProperties pointerProperties[MAX_POINTERS]; + PointerCoords pointerCoords[MAX_POINTERS]; + float xPrecision; + float yPrecision; + /** + * Mouse cursor position when this event is reported relative to the origin of the specified + * display. Only valid if this is a mouse event (originates from a mouse or from a trackpad in + * gestures enabled mode. + */ + float xCursorPosition; + float yCursorPosition; + nsecs_t downTime; + nsecs_t readTime; + std::vector videoFrames; + + inline NotifyMotionArgs() {} + + NotifyMotionArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId, + uint32_t source, int32_t displayId, uint32_t policyFlags, int32_t action, + int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState, + MotionClassification classification, int32_t edgeFlags, uint32_t pointerCount, + const PointerProperties* pointerProperties, const PointerCoords* pointerCoords, + float xPrecision, float yPrecision, float xCursorPosition, + float yCursorPosition, nsecs_t downTime, + const std::vector& videoFrames); + + NotifyMotionArgs(const NotifyMotionArgs& other); + + bool operator==(const NotifyMotionArgs& rhs) const; + + std::string dump() const; +}; + +/* Describes a sensor event. */ +struct NotifySensorArgs { + int32_t id; + nsecs_t eventTime; + + int32_t deviceId; + uint32_t source; + InputDeviceSensorType sensorType; + InputDeviceSensorAccuracy accuracy; + bool accuracyChanged; + nsecs_t hwTimestamp; + std::vector values; + + inline NotifySensorArgs() {} + + NotifySensorArgs(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source, + InputDeviceSensorType sensorType, InputDeviceSensorAccuracy accuracy, + bool accuracyChanged, nsecs_t hwTimestamp, std::vector values); + + NotifySensorArgs(const NotifySensorArgs& other) = default; +}; + +/* Describes a switch event. */ +struct NotifySwitchArgs { + int32_t id; + nsecs_t eventTime; + + uint32_t policyFlags; + uint32_t switchValues; + uint32_t switchMask; + + inline NotifySwitchArgs() {} + + NotifySwitchArgs(int32_t id, nsecs_t eventTime, uint32_t policyFlags, uint32_t switchValues, + uint32_t switchMask); + + NotifySwitchArgs(const NotifySwitchArgs& other) = default; + + bool operator==(const NotifySwitchArgs& rhs) const = default; +}; + +/* Describes a device reset event, such as when a device is added, + * reconfigured, or removed. */ +struct NotifyDeviceResetArgs { + int32_t id; + nsecs_t eventTime; + + int32_t deviceId; + + inline NotifyDeviceResetArgs() {} + + NotifyDeviceResetArgs(int32_t id, nsecs_t eventTime, int32_t deviceId); + + NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other) = default; + + bool operator==(const NotifyDeviceResetArgs& rhs) const = default; +}; + +/* Describes a change in the state of Pointer Capture. */ +struct NotifyPointerCaptureChangedArgs { + // The sequence number of the Pointer Capture request, if enabled. + int32_t id; + nsecs_t eventTime; + + PointerCaptureRequest request; + + inline NotifyPointerCaptureChangedArgs() {} + + NotifyPointerCaptureChangedArgs(int32_t id, nsecs_t eventTime, const PointerCaptureRequest&); + + NotifyPointerCaptureChangedArgs(const NotifyPointerCaptureChangedArgs& other) = default; +}; + +/* Describes a vibrator state event. */ +struct NotifyVibratorStateArgs { + int32_t id; + nsecs_t eventTime; + + int32_t deviceId; + bool isOn; + + inline NotifyVibratorStateArgs() {} + + NotifyVibratorStateArgs(int32_t id, nsecs_t eventTIme, int32_t deviceId, bool isOn); + + NotifyVibratorStateArgs(const NotifyVibratorStateArgs& other) = default; +}; + +using NotifyArgs = std::variant; + +} // namespace android -- GitLab From 862ae2109cfa6f91922ced5d88882a4e756bdb21 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 28 Sep 2022 15:17:28 -0700 Subject: [PATCH 0325/1310] Remove libui dependencies from input code We would like to eventually compile some or all of the input code for host. Currently, libui-types is a small library that provides types like Transform, Region, Rect. That build target is host-supported. Switch input dependencies to use libui-types only. Bug: 249591924 Test: m checkinput Change-Id: I406a4c3b5ffe190271486213240d67a2e7d4bd25 --- services/inputflinger/Android.bp | 3 +-- services/inputflinger/benchmarks/Android.bp | 1 - services/inputflinger/dispatcher/Android.bp | 1 - services/inputflinger/dispatcher/InputDispatcher.h | 1 - services/inputflinger/reader/Android.bp | 2 +- services/inputflinger/tests/InputFlingerService_test.cpp | 2 -- services/inputflinger/tests/fuzzers/Android.bp | 7 ++----- 7 files changed, 4 insertions(+), 13 deletions(-) diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp index 88a9acb80e..378900a2bb 100644 --- a/services/inputflinger/Android.bp +++ b/services/inputflinger/Android.bp @@ -85,12 +85,12 @@ cc_defaults { "libstatspull", "libstatssocket", "libutils", - "libui", "server_configurable_flags", ], static_libs: [ "libattestation", "libpalmrejection", + "libui-types", ], } @@ -152,7 +152,6 @@ cc_defaults { "libcutils", "libinput", "liblog", - "libui", "libutils", ], header_libs: [ diff --git a/services/inputflinger/benchmarks/Android.bp b/services/inputflinger/benchmarks/Android.bp index 75071d55ac..e5c19afead 100644 --- a/services/inputflinger/benchmarks/Android.bp +++ b/services/inputflinger/benchmarks/Android.bp @@ -26,7 +26,6 @@ cc_benchmark { "libinputreporter", "liblog", "libstatslog", - "libui", "libutils", ], static_libs: [ diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp index cdad9c93fd..eb79b76b28 100644 --- a/services/inputflinger/dispatcher/Android.bp +++ b/services/inputflinger/dispatcher/Android.bp @@ -63,7 +63,6 @@ cc_defaults { "libstatslog", "libstatspull", "libstatssocket", - "libui", "libgui", "libutils", "server_configurable_flags", diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index b5bbce8e8d..0cf2a1a86f 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -41,7 +41,6 @@ #include #include #include -#include #include #include #include diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index 01146a3c8b..0f87201caf 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -69,12 +69,12 @@ cc_defaults { "libinput", "liblog", "libstatslog", - "libui", "libutils", "libPlatformProperties", ], static_libs: [ "libc++fs", + "libui-types", ], header_libs: [ "libbatteryservice_headers", diff --git a/services/inputflinger/tests/InputFlingerService_test.cpp b/services/inputflinger/tests/InputFlingerService_test.cpp index 22ae8946ab..ca548bec3e 100644 --- a/services/inputflinger/tests/InputFlingerService_test.cpp +++ b/services/inputflinger/tests/InputFlingerService_test.cpp @@ -33,8 +33,6 @@ #include #include #include -#include -#include #include #include #include diff --git a/services/inputflinger/tests/fuzzers/Android.bp b/services/inputflinger/tests/fuzzers/Android.bp index f4ecba2a10..55c2db6c91 100644 --- a/services/inputflinger/tests/fuzzers/Android.bp +++ b/services/inputflinger/tests/fuzzers/Android.bp @@ -21,7 +21,6 @@ package { default_applicable_licenses: ["frameworks_native_license"], } - cc_fuzz { name: "inputflinger_latencytracker_fuzzer", defaults: [ @@ -34,7 +33,6 @@ cc_fuzz { "libbase", "libbinder", "liblog", - "libui", "libutils", "libinput", "libinputflinger", @@ -43,7 +41,7 @@ cc_fuzz { "LatencyTrackerFuzzer.cpp", ], fuzz_config: { - cc: ["android-framework-input@google.com"], + cc: ["android-framework-input@google.com"], }, } @@ -63,7 +61,6 @@ cc_defaults { "libcutils", "liblog", "libutils", - "libui", "libinput", "libinputflinger", "libinputreader", @@ -75,7 +72,7 @@ cc_defaults { "libinputreader_headers", ], fuzz_config: { - cc: ["android-framework-input@google.com"], + cc: ["android-framework-input@google.com"], }, } -- GitLab From 0a4fb0014f0c90c7c42f9dc9551b949b0665c7c7 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Mon, 8 Aug 2022 02:40:42 +0000 Subject: [PATCH 0326/1310] CE: Snapshot layer metadata Adds LayerMetadata to LayerSnapshots. LayerMetadata is only populated when CE enables the new feature flag and the feature flag will only be enabled in ARC++'s CE. This change is necessary to split LayerFE from the Layer class as ARC++'s LayerFE subclass accesses LayerMetadata. Bug: 238781169 Test: go/wm-smoke Test: presubmit Change-Id: Iae405707897eedf5af9025fe628c1cd60b596e10 --- .../compositionengine/CompositionEngine.h | 5 +- .../include/compositionengine/Feature.h | 30 +++ .../include/compositionengine/LayerFE.h | 6 + .../impl/CompositionEngine.h | 2 + .../mock/CompositionEngine.h | 2 + .../include/compositionengine/mock/LayerFE.h | 2 + .../src/CompositionEngine.cpp | 4 + services/surfaceflinger/Layer.cpp | 32 +++ services/surfaceflinger/Layer.h | 11 + services/surfaceflinger/SurfaceFlinger.cpp | 37 ++- services/surfaceflinger/SurfaceFlinger.h | 4 + .../surfaceflinger/tests/unittests/Android.bp | 1 + ...linger_UpdateLayerMetadataSnapshotTest.cpp | 212 ++++++++++++++++++ .../tests/unittests/TestableSurfaceFlinger.h | 2 + 14 files changed, 348 insertions(+), 2 deletions(-) create mode 100644 services/surfaceflinger/CompositionEngine/include/compositionengine/Feature.h create mode 100644 services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h index 3faa068c03..6832ae12df 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h @@ -18,9 +18,10 @@ #include #include - #include +#include "Feature.h" + namespace android { class HWComposer; @@ -72,6 +73,8 @@ public: // TODO(b/121291683): These will become private/internal virtual void preComposition(CompositionRefreshArgs&) = 0; + virtual FeatureFlags getFeatureFlags() const = 0; + // Debugging virtual void dump(std::string&) const = 0; }; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Feature.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Feature.h new file mode 100644 index 0000000000..ee8000ae18 --- /dev/null +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Feature.h @@ -0,0 +1,30 @@ +/* + * Copyright 2019 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 +#include + +namespace android::compositionengine { + +enum class Feature : int32_t { + kSnapshotLayerMetadata = 1 << 0, +}; + +using FeatureFlags = ftl::Flags; + +} // namespace android::compositionengine \ No newline at end of file diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h index a738da045a..fe8cad502b 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h @@ -39,6 +39,10 @@ namespace android { class Fence; +namespace gui { +struct LayerMetadata; +} + namespace compositionengine { struct LayerFECompositionState; @@ -144,6 +148,8 @@ public: // Whether the layer should be rendered with rounded corners. virtual bool hasRoundedCorners() const = 0; virtual void setWasClientComposed(const sp&) {} + virtual const gui::LayerMetadata* getMetadata() const = 0; + virtual const gui::LayerMetadata* getRelativeMetadata() const = 0; }; // TODO(b/121291683): Specialize std::hash<> for sp so these and others can diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h index 09079264da..dd4dbe9318 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h @@ -48,6 +48,8 @@ public: void preComposition(CompositionRefreshArgs&) override; + FeatureFlags getFeatureFlags() const override; + // Debugging void dump(std::string&) const override; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h index f953d0b7d0..a48cc6f975 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h @@ -53,6 +53,8 @@ public: MOCK_METHOD1(preComposition, void(CompositionRefreshArgs&)); + MOCK_CONST_METHOD0(getFeatureFlags, FeatureFlags()); + MOCK_CONST_METHOD1(dump, void(std::string&)); }; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h index be0dbceffe..14922a4e0d 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h @@ -53,6 +53,8 @@ public: MOCK_CONST_METHOD0(getDebugName, const char*()); MOCK_CONST_METHOD0(getSequence, int32_t()); MOCK_CONST_METHOD0(hasRoundedCorners, bool()); + MOCK_CONST_METHOD0(getMetadata, gui::LayerMetadata*()); + MOCK_CONST_METHOD0(getRelativeMetadata, gui::LayerMetadata*()); }; } // namespace android::compositionengine::mock diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp index 855507eec4..a4e1fff2ad 100644 --- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp +++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp @@ -140,6 +140,10 @@ void CompositionEngine::preComposition(CompositionRefreshArgs& args) { mNeedsAnotherUpdate = needsAnotherUpdate; } +FeatureFlags CompositionEngine::getFeatureFlags() const { + return {}; +} + void CompositionEngine::dump(std::string&) const { // The base class has no state to dump, but derived classes might. } diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 13437a4497..55a32ab5de 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -4241,6 +4241,38 @@ void Layer::updateSnapshot(bool updateGeometry) { preparePerFrameCompositionState(); } +void Layer::updateMetadataSnapshot(const LayerMetadata& parentMetadata) { + mSnapshot->layerMetadata = parentMetadata; + mSnapshot->layerMetadata.merge(mDrawingState.metadata); + for (const sp& child : mDrawingChildren) { + child->updateMetadataSnapshot(mSnapshot->layerMetadata); + } +} + +void Layer::updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata, + std::unordered_set& visited) { + if (visited.find(this) != visited.end()) { + ALOGW("Cycle containing layer %s detected in z-order relatives", getDebugName()); + return; + } + visited.insert(this); + + mSnapshot->relativeLayerMetadata = relativeLayerMetadata; + + if (mDrawingState.zOrderRelatives.empty()) { + return; + } + LayerMetadata childRelativeLayerMetadata = mSnapshot->relativeLayerMetadata; + childRelativeLayerMetadata.merge(mSnapshot->layerMetadata); + for (wp weakRelative : mDrawingState.zOrderRelatives) { + sp relative = weakRelative.promote(); + if (!relative) { + continue; + } + relative->updateRelativeMetadataSnapshot(childRelativeLayerMetadata, visited); + } +} + // --------------------------------------------------------------------------- std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate) { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 4079d16438..4ff86e5dd6 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -166,6 +166,8 @@ public: ui::Transform transform; Rect bufferSize; std::shared_ptr externalTexture; + LayerMetadata layerMetadata; + LayerMetadata relativeLayerMetadata; }; using FrameRate = scheduler::LayerInfo::FrameRate; @@ -611,6 +613,12 @@ public: mClearClientCompositionFenceOnLayerDisplayed = false; } + const LayerMetadata* getMetadata() const override { return &mSnapshot->layerMetadata; } + + const LayerMetadata* getRelativeMetadata() const override { + return &mSnapshot->relativeLayerMetadata; + } + const char* getDebugName() const override; bool setShadowRadius(float shadowRadius); @@ -901,6 +909,9 @@ public: // TODO(b/238781169) Remove direct calls to RenderEngine::drawLayers that don't go through // CompositionEngine to create a single path for composing layers. void updateSnapshot(bool updateGeometry); + void updateMetadataSnapshot(const LayerMetadata& parentMetadata); + void updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata, + std::unordered_set& visited); protected: friend class impl::SurfaceInterceptor; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index e27c713806..07e7e3a1e9 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2211,6 +2211,13 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) } mPowerAdvisor->setDisplays(displayIds); + const bool updateTaskMetadata = mCompositionEngine->getFeatureFlags().test( + compositionengine::Feature::kSnapshotLayerMetadata); + if (updateTaskMetadata && (mVisibleRegionsDirty || mLayerMetadataSnapshotNeeded)) { + updateLayerMetadataSnapshot(); + mLayerMetadataSnapshotNeeded = false; + } + if (DOES_CONTAIN_BORDER) { refreshArgs.borderInfoList.clear(); mDrawingState.traverse([&refreshArgs](Layer* layer) { @@ -4445,7 +4452,10 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime layer->setGameModeForTree(static_cast(gameMode)); } - if (layer->setMetadata(s.metadata)) flags |= eTraversalNeeded; + if (layer->setMetadata(s.metadata)) { + flags |= eTraversalNeeded; + mLayerMetadataSnapshotNeeded = true; + } } if (what & layer_state_t::eColorSpaceAgnosticChanged) { if (layer->setColorSpaceAgnostic(s.colorSpaceAgnostic)) { @@ -7240,6 +7250,31 @@ bool SurfaceFlinger::commitCreatedLayers(VsyncId vsyncId) { return true; } +void SurfaceFlinger::updateLayerMetadataSnapshot() { + LayerMetadata parentMetadata; + for (const auto& layer : mDrawingState.layersSortedByZ) { + layer->updateMetadataSnapshot(parentMetadata); + } + + std::unordered_set visited; + mDrawingState.traverse([&visited](Layer* layer) { + if (visited.find(layer) != visited.end()) { + return; + } + + // If the layer isRelativeOf, then either it's relative metadata will be set + // recursively when updateRelativeMetadataSnapshot is called on its relative parent or + // it's relative parent has been deleted. Clear the layer's relativeLayerMetadata to ensure + // that layers with deleted relative parents don't hold stale relativeLayerMetadata. + if (layer->getDrawingState().isRelativeOf) { + layer->editLayerSnapshot()->relativeLayerMetadata = {}; + return; + } + + layer->updateRelativeMetadataSnapshot({}, visited); + }); +} + // gui::ISurfaceComposer binder::Status SurfaceComposerAIDL::bootFinished() { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 3c92d565ed..0c60137ffd 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -699,6 +699,7 @@ private: bool latchBuffers(); void updateLayerGeometry(); + void updateLayerMetadataSnapshot(); void updateInputFlinger(); void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext); @@ -1176,6 +1177,9 @@ private: bool mSomeDataspaceChanged = false; bool mForceTransactionDisplayChange = false; + // Set if LayerMetadata has changed since the last LayerMetadata snapshot. + bool mLayerMetadataSnapshotNeeded = false; + // Tracks layers that have pending frames which are candidates for being // latched. std::unordered_set, SpHash> mLayersWithQueuedFrames; diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index dec14d02a6..4469df007a 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -109,6 +109,7 @@ cc_test { "SurfaceFlinger_SetDisplayStateTest.cpp", "SurfaceFlinger_SetPowerModeInternalTest.cpp", "SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp", + "SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp", "SchedulerTest.cpp", "SetFrameRateTest.cpp", "RefreshRateConfigsTest.cpp", diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp new file mode 100644 index 0000000000..0cf3bdf0d5 --- /dev/null +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp @@ -0,0 +1,212 @@ +#include +#include +#include + +#include "TestableSurfaceFlinger.h" +#include "mock/MockEventThread.h" +#include "mock/MockVsyncController.h" + +namespace android { + +using testing::_; +using testing::Return; +using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; + +class SurfaceFlingerUpdateLayerMetadataSnapshotTest : public testing::Test { +public: + SurfaceFlingerUpdateLayerMetadataSnapshotTest() { setupScheduler(); } + +protected: + void setupScheduler() { + auto eventThread = std::make_unique(); + auto sfEventThread = std::make_unique(); + + EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); + EXPECT_CALL(*eventThread, createEventConnection(_, _)) + .WillOnce(Return(sp::make(eventThread.get(), + mock::EventThread::kCallingUid, + ResyncCallback()))); + + EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); + EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) + .WillOnce(Return(sp::make(sfEventThread.get(), + mock::EventThread::kCallingUid, + ResyncCallback()))); + + auto vsyncController = std::make_unique(); + auto vsyncTracker = std::make_unique(); + + EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); + EXPECT_CALL(*vsyncTracker, currentPeriod()) + .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); + EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); + mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), + std::move(eventThread), std::move(sfEventThread)); + } + + sp createLayer(const char* name, LayerMetadata layerMetadata) { + return sp::make( + LayerCreationArgs{mFlinger.flinger(), nullptr, name, 0, layerMetadata}); + } + + TestableSurfaceFlinger mFlinger; +}; + +class LayerMetadataBuilder { +public: + LayerMetadataBuilder(LayerMetadata layerMetadata = {}) : mLayerMetadata(layerMetadata) {} + + LayerMetadataBuilder& setInt32(uint32_t key, int32_t value) { + mLayerMetadata.setInt32(key, value); + return *this; + } + + LayerMetadata build() { return mLayerMetadata; } + +private: + LayerMetadata mLayerMetadata; +}; + +bool operator==(const LayerMetadata& lhs, const LayerMetadata& rhs) { + return lhs.mMap == rhs.mMap; +} + +std::ostream& operator<<(std::ostream& stream, const LayerMetadata& layerMetadata) { + stream << "LayerMetadata{"; + for (auto it = layerMetadata.mMap.cbegin(); it != layerMetadata.mMap.cend(); it++) { + if (it != layerMetadata.mMap.cbegin()) { + stream << ", "; + } + stream << layerMetadata.itemToString(it->first, ":"); + } + return stream << "}"; +} + +// Test that the snapshot's layer metadata is set. +TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, updatesSnapshotMetadata) { + auto layerMetadata = LayerMetadataBuilder().setInt32(METADATA_TASK_ID, 1).build(); + auto layer = createLayer("layer", layerMetadata); + mFlinger.mutableDrawingState().layersSortedByZ.add(layer); + + mFlinger.updateLayerMetadataSnapshot(); + + ASSERT_EQ(layer->getLayerSnapshot()->layerMetadata, layerMetadata); +} + +// Test that snapshot layer metadata is set by merging the child's metadata on top of its +// parent's metadata. +TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, mergesSnapshotMetadata) { + auto layerAMetadata = LayerMetadataBuilder() + .setInt32(METADATA_OWNER_UID, 1) + .setInt32(METADATA_TASK_ID, 2) + .build(); + auto layerA = createLayer("parent", layerAMetadata); + auto layerBMetadata = LayerMetadataBuilder().setInt32(METADATA_TASK_ID, 3).build(); + auto layerB = createLayer("child", layerBMetadata); + layerA->addChild(layerB); + layerA->commitChildList(); + mFlinger.mutableDrawingState().layersSortedByZ.add(layerA); + + mFlinger.updateLayerMetadataSnapshot(); + + ASSERT_EQ(layerA->getLayerSnapshot()->layerMetadata, layerAMetadata); + auto expectedChildMetadata = + LayerMetadataBuilder(layerAMetadata).setInt32(METADATA_TASK_ID, 3).build(); + ASSERT_EQ(layerB->getLayerSnapshot()->layerMetadata, expectedChildMetadata); +} + +// Test that snapshot relative layer metadata is set to the parent's layer metadata merged on top of +// that parent's relative layer metadata. +TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, updatesRelativeMetadata) { + auto layerAMetadata = LayerMetadataBuilder().setInt32(METADATA_TASK_ID, 1).build(); + auto layerA = createLayer("relative-parent", layerAMetadata); + auto layerAHandle = layerA->getHandle(); + auto layerBMetadata = LayerMetadataBuilder().setInt32(METADATA_TASK_ID, 2).build(); + auto layerB = createLayer("relative-child", layerBMetadata); + layerB->setRelativeLayer(layerAHandle, 1); + mFlinger.mutableDrawingState().layersSortedByZ.add(layerA); + mFlinger.mutableDrawingState().layersSortedByZ.add(layerB); + + mFlinger.updateLayerMetadataSnapshot(); + + ASSERT_EQ(layerA->getLayerSnapshot()->relativeLayerMetadata, LayerMetadata{}); + ASSERT_EQ(layerB->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata); +} + +// Test that snapshot relative layer metadata is set correctly when a layer is interleaved within +// two other layers. +// +// Layer +// A +// / \ +// B D +// / +// C +// +// Z-order Relatives +// B <- D <- C +TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, updatesRelativeMetadataInterleaved) { + auto layerAMetadata = LayerMetadataBuilder().setInt32(METADATA_OWNER_UID, 1).build(); + auto layerA = createLayer("layer-a", layerAMetadata); + auto layerBMetadata = LayerMetadataBuilder() + .setInt32(METADATA_TASK_ID, 2) + .setInt32(METADATA_OWNER_PID, 3) + .build(); + auto layerB = createLayer("layer-b", layerBMetadata); + auto layerBHandle = layerB->getHandle(); + auto layerC = createLayer("layer-c", {}); + auto layerDMetadata = LayerMetadataBuilder().setInt32(METADATA_TASK_ID, 4).build(); + auto layerD = createLayer("layer-d", layerDMetadata); + auto layerDHandle = layerD->getHandle(); + layerB->addChild(layerC); + layerA->addChild(layerB); + layerA->addChild(layerD); + layerC->setRelativeLayer(layerDHandle, 1); + layerD->setRelativeLayer(layerBHandle, 1); + layerA->commitChildList(); + mFlinger.mutableDrawingState().layersSortedByZ.add(layerA); + + mFlinger.updateLayerMetadataSnapshot(); + + auto expectedLayerDRelativeMetadata = LayerMetadataBuilder() + // From layer A, parent of relative parent + .setInt32(METADATA_OWNER_UID, 1) + // From layer B, relative parent + .setInt32(METADATA_TASK_ID, 2) + .setInt32(METADATA_OWNER_PID, 3) + .build(); + ASSERT_EQ(layerD->getLayerSnapshot()->relativeLayerMetadata, expectedLayerDRelativeMetadata); + auto expectedLayerCRelativeMetadata = + LayerMetadataBuilder() + // From layer A, parent of relative parent + .setInt32(METADATA_OWNER_UID, 1) + // From layer B, relative parent of relative parent + .setInt32(METADATA_OWNER_PID, 3) + // From layer D, relative parent + .setInt32(METADATA_TASK_ID, 4) + .build(); + ASSERT_EQ(layerC->getLayerSnapshot()->relativeLayerMetadata, expectedLayerCRelativeMetadata); +} + +TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, + updatesRelativeMetadataMultipleRelativeChildren) { + auto layerAMetadata = LayerMetadataBuilder().setInt32(METADATA_OWNER_UID, 1).build(); + auto layerA = createLayer("layer-a", layerAMetadata); + auto layerAHandle = layerA->getHandle(); + auto layerB = createLayer("layer-b", {}); + auto layerC = createLayer("layer-c", {}); + layerB->setRelativeLayer(layerAHandle, 1); + layerC->setRelativeLayer(layerAHandle, 2); + layerA->commitChildList(); + mFlinger.mutableDrawingState().layersSortedByZ.add(layerA); + mFlinger.mutableDrawingState().layersSortedByZ.add(layerB); + mFlinger.mutableDrawingState().layersSortedByZ.add(layerC); + + mFlinger.updateLayerMetadataSnapshot(); + + ASSERT_EQ(layerA->getLayerSnapshot()->relativeLayerMetadata, LayerMetadata{}); + ASSERT_EQ(layerB->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata); + ASSERT_EQ(layerC->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata); +} + +} // namespace android diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 2c9c451f29..13389a1ad3 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -477,6 +477,8 @@ public: return mFlinger->mirrorLayer(args, mirrorFromHandle, outHandle, outLayerId); } + void updateLayerMetadataSnapshot() { mFlinger->updateLayerMetadataSnapshot(); } + /* ------------------------------------------------------------------------ * Read-only access to private data to assert post-conditions. */ -- GitLab From dc2bb807093861f6f30c1bf1c1ae8e969c7a177c Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Wed, 28 Sep 2022 16:02:59 -0400 Subject: [PATCH 0327/1310] SF: Remove DisplayDevice::onVsync The DisplayDevice does not exist until the display transaction created by the `configure` stage is committed. HWComposer::DisplayData already stores the present timestamp, so query that and remove DisplayDevice's duplicate. Bug: 248345991 Bug: 241285876 Test: Hotplug Change-Id: I6b0cbe2e36d2d4ec0e2a1a1197b60e0c9ae60bfe --- .../CompositionEngine/tests/MockHWComposer.h | 1 + services/surfaceflinger/DisplayDevice.cpp | 10 ---------- services/surfaceflinger/DisplayDevice.h | 4 ---- services/surfaceflinger/DisplayHardware/HWC2.h | 2 +- .../surfaceflinger/DisplayHardware/HWComposer.cpp | 11 ++++++++--- .../surfaceflinger/DisplayHardware/HWComposer.h | 15 +++++++++------ services/surfaceflinger/Layer.cpp | 11 ++++++++--- services/surfaceflinger/SurfaceFlinger.cpp | 7 +------ services/surfaceflinger/SurfaceFlinger.h | 2 +- 9 files changed, 29 insertions(+), 34 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h index d7704a893d..9102139634 100644 --- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h +++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h @@ -70,6 +70,7 @@ public: MOCK_METHOD1(disconnectDisplay, void(HalDisplayId)); MOCK_CONST_METHOD1(hasDeviceComposition, bool(const std::optional&)); MOCK_CONST_METHOD1(getPresentFence, sp(HalDisplayId)); + MOCK_METHOD(nsecs_t, getPresentTimestamp, (PhysicalDisplayId), (const, override)); MOCK_CONST_METHOD2(getLayerReleaseFence, sp(HalDisplayId, HWC2::Layer*)); MOCK_METHOD3(setOutputBuffer, status_t(HalVirtualDisplayId, const sp&, const sp&)); diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index a25296c86d..f8115ebcd9 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -238,16 +238,6 @@ nsecs_t DisplayDevice::getVsyncPeriodFromHWC() const { return refreshRateConfigs().getActiveModePtr()->getVsyncPeriod(); } -nsecs_t DisplayDevice::getRefreshTimestamp() const { - const nsecs_t now = systemTime(CLOCK_MONOTONIC); - const auto vsyncPeriodNanos = getVsyncPeriodFromHWC(); - return now - ((now - mLastHwVsync) % vsyncPeriodNanos); -} - -void DisplayDevice::onVsync(nsecs_t timestamp) { - mLastHwVsync = timestamp; -} - ui::Dataspace DisplayDevice::getCompositionDataSpace() const { return mCompositionDisplay->getState().dataspace; } diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index 0f52aff4f3..510df81da1 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -233,9 +233,7 @@ public: bool onKernelTimerChanged(std::optional, bool timerExpired); void animateRefreshRateOverlay(); - void onVsync(nsecs_t timestamp); nsecs_t getVsyncPeriodFromHWC() const; - nsecs_t getRefreshTimestamp() const; status_t setRefreshRatePolicy( const std::optional& policy, @@ -273,8 +271,6 @@ private: std::optional mStagedBrightness; float mBrightness = -1.f; - std::atomic mLastHwVsync = 0; - // TODO(b/182939859): Remove special cases for primary display. const bool mIsPrimary; diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h index 486eaf8f03..96399e263e 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.h +++ b/services/surfaceflinger/DisplayHardware/HWC2.h @@ -70,7 +70,7 @@ namespace hal = android::hardware::graphics::composer::hal; struct ComposerCallback { virtual void onComposerHalHotplug(hal::HWDisplayId, hal::Connection) = 0; virtual void onComposerHalRefresh(hal::HWDisplayId) = 0; - virtual void onComposerHalVsync(hal::HWDisplayId, int64_t timestamp, + virtual void onComposerHalVsync(hal::HWDisplayId, nsecs_t timestamp, std::optional) = 0; virtual void onComposerHalVsyncPeriodTimingChanged(hal::HWDisplayId, const hal::VsyncPeriodChangeTimeline&) = 0; diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp index 15d5041a1e..0a4ad9796b 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -144,7 +144,7 @@ bool HWComposer::updatesDeviceProductInfoOnHotplugReconnect() const { return mUpdateDeviceProductInfoOnHotplugReconnect; } -bool HWComposer::onVsync(hal::HWDisplayId hwcDisplayId, int64_t timestamp) { +bool HWComposer::onVsync(hal::HWDisplayId hwcDisplayId, nsecs_t timestamp) { const auto displayId = toPhysicalDisplayId(hwcDisplayId); if (!displayId) { LOG_HWC_DISPLAY_ERROR(hwcDisplayId, "Invalid HWC display"); @@ -160,13 +160,13 @@ bool HWComposer::onVsync(hal::HWDisplayId hwcDisplayId, int64_t timestamp) { // with the same timestamp when turning the display off and on. This // is a bug in the HWC implementation, but filter the extra events // out here so they don't cause havoc downstream. - if (timestamp == displayData.lastHwVsync) { + if (timestamp == displayData.lastPresentTimestamp) { ALOGW("Ignoring duplicate VSYNC event from HWC for display %s (t=%" PRId64 ")", to_string(*displayId).c_str(), timestamp); return false; } - displayData.lastHwVsync = timestamp; + displayData.lastPresentTimestamp = timestamp; } const auto tag = "HW_VSYNC_" + to_string(*displayId); @@ -485,6 +485,11 @@ sp HWComposer::getPresentFence(HalDisplayId displayId) const { return mDisplayData.at(displayId).lastPresentFence; } +nsecs_t HWComposer::getPresentTimestamp(PhysicalDisplayId displayId) const { + RETURN_IF_INVALID_DISPLAY(displayId, 0); + return mDisplayData.at(displayId).lastPresentTimestamp; +} + sp HWComposer::getLayerReleaseFence(HalDisplayId displayId, HWC2::Layer* layer) const { RETURN_IF_INVALID_DISPLAY(displayId, Fence::NO_FENCE); const auto& displayFences = mDisplayData.at(displayId).releaseFences; diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h index 92a8f30f1b..6c43d8b812 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.h +++ b/services/surfaceflinger/DisplayHardware/HWComposer.h @@ -160,8 +160,9 @@ public: // reset state when a display is disconnected virtual void disconnectDisplay(HalDisplayId) = 0; - // get the present fence received from the last call to present. + // Get the present fence/timestamp received from the last call to present. virtual sp getPresentFence(HalDisplayId) const = 0; + virtual nsecs_t getPresentTimestamp(PhysicalDisplayId) const = 0; // Get last release fence for the given layer virtual sp getLayerReleaseFence(HalDisplayId, HWC2::Layer*) const = 0; @@ -214,7 +215,7 @@ public: // TODO(b/157555476): Remove when the framework has proper support for headless mode virtual bool updatesDeviceProductInfoOnHotplugReconnect() const = 0; - virtual bool onVsync(hal::HWDisplayId, int64_t timestamp) = 0; + virtual bool onVsync(hal::HWDisplayId, nsecs_t timestamp) = 0; virtual void setVsyncEnabled(PhysicalDisplayId, hal::Vsync enabled) = 0; virtual bool isConnected(PhysicalDisplayId) const = 0; @@ -343,8 +344,9 @@ public: // reset state when a display is disconnected void disconnectDisplay(HalDisplayId) override; - // get the present fence received from the last call to present. + // Get the present fence/timestamp received from the last call to present. sp getPresentFence(HalDisplayId) const override; + nsecs_t getPresentTimestamp(PhysicalDisplayId) const override; // Get last release fence for the given layer sp getLayerReleaseFence(HalDisplayId, HWC2::Layer*) const override; @@ -387,7 +389,7 @@ public: bool updatesDeviceProductInfoOnHotplugReconnect() const override; - bool onVsync(hal::HWDisplayId, int64_t timestamp) override; + bool onVsync(hal::HWDisplayId, nsecs_t timestamp) override; void setVsyncEnabled(PhysicalDisplayId, hal::Vsync enabled) override; bool isConnected(PhysicalDisplayId) const override; @@ -456,7 +458,10 @@ private: struct DisplayData { std::unique_ptr hwcDisplay; + sp lastPresentFence = Fence::NO_FENCE; // signals when the last set op retires + nsecs_t lastPresentTimestamp = 0; + std::unordered_map> releaseFences; bool validateWasSkipped; @@ -466,8 +471,6 @@ private: std::mutex vsyncEnabledLock; hal::Vsync vsyncEnabled GUARDED_BY(vsyncEnabledLock) = hal::Vsync::DISABLE; - - nsecs_t lastHwVsync = 0; }; std::optional onHotplugConnect(hal::HWDisplayId); diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 13437a4497..96ee28cc9b 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -3918,9 +3918,14 @@ void Layer::onPostComposition(const DisplayDevice* display, mFrameTracker.setActualPresentFence(std::shared_ptr(presentFence)); } else if (const auto displayId = PhysicalDisplayId::tryCast(display->getId()); displayId && mFlinger->getHwComposer().isConnected(*displayId)) { - // The HWC doesn't support present fences, so use the refresh - // timestamp instead. - const nsecs_t actualPresentTime = display->getRefreshTimestamp(); + // The HWC doesn't support present fences, so use the present timestamp instead. + const nsecs_t presentTimestamp = + mFlinger->getHwComposer().getPresentTimestamp(*displayId); + + const nsecs_t now = systemTime(CLOCK_MONOTONIC); + const nsecs_t vsyncPeriod = display->getVsyncPeriodFromHWC(); + const nsecs_t actualPresentTime = now - ((now - presentTimestamp) % vsyncPeriod); + mFlinger->mTimeStats->setPresentTime(layerId, mCurrentFrameNumber, actualPresentTime, refreshRate, renderRate, vote, gameMode); mFlinger->mFrameTracer->traceTimestamp(layerId, getCurrentBufferId(), diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index f3551ae080..4024e1fe42 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1893,17 +1893,12 @@ void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t t ATRACE_FORMAT("onComposerHalVsync%s", tracePeriod.c_str()); Mutex::Autolock lock(mStateLock); - const auto displayId = getHwComposer().toPhysicalDisplayId(hwcDisplayId); - if (displayId) { - const auto token = getPhysicalDisplayTokenLocked(*displayId); - const auto display = getDisplayDeviceLocked(token); - display->onVsync(timestamp); - } if (!getHwComposer().onVsync(hwcDisplayId, timestamp)) { return; } + const auto displayId = getHwComposer().toPhysicalDisplayId(hwcDisplayId); const bool isActiveDisplay = displayId && getPhysicalDisplayTokenLocked(*displayId) == mActiveDisplayToken; if (!isActiveDisplay) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index fdaacf074c..00b0c1e047 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -608,7 +608,7 @@ private: void binderDied(const wp& who) override; // HWC2::ComposerCallback overrides: - void onComposerHalVsync(hal::HWDisplayId, int64_t timestamp, + void onComposerHalVsync(hal::HWDisplayId, nsecs_t timestamp, std::optional) override; void onComposerHalHotplug(hal::HWDisplayId, hal::Connection) override; void onComposerHalRefresh(hal::HWDisplayId) override; -- GitLab From 2709efe8a9891180839f067d6aa89b54f0e8edeb Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 28 Sep 2022 17:17:08 -0700 Subject: [PATCH 0328/1310] SurfaceComposerClient: Remove mForceSynchronous flag Legacy code forced the transaction to be sync when setting display projection. This shouldn't be needed as we should be synchronizing at a higher level. This display projection is set as part of the WM global transaction so avoid an unnecessary sync is advantageous here. Test: go/wm-smoke Bug: 238781169 Change-Id: I83a213a04b3c664b0bffef4c7597d99f901a492e --- libs/gui/SurfaceComposerClient.cpp | 22 +++++++------------- libs/gui/include/gui/SurfaceComposerClient.h | 1 - 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index a9d6b0ec4b..90076f122f 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -627,7 +627,6 @@ SurfaceComposerClient::Transaction::Transaction() { SurfaceComposerClient::Transaction::Transaction(const Transaction& other) : mId(other.mId), - mForceSynchronous(other.mForceSynchronous), mTransactionNestCount(other.mTransactionNestCount), mAnimation(other.mAnimation), mEarlyWakeupStart(other.mEarlyWakeupStart), @@ -662,7 +661,6 @@ SurfaceComposerClient::Transaction::createFromParcel(const Parcel* parcel) { status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel) { const uint64_t transactionId = parcel->readUint64(); - const uint32_t forceSynchronous = parcel->readUint32(); const uint32_t transactionNestCount = parcel->readUint32(); const bool animation = parcel->readBool(); const bool earlyWakeupStart = parcel->readBool(); @@ -739,7 +737,6 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel // Parsing was successful. Update the object. mId = transactionId; - mForceSynchronous = forceSynchronous; mTransactionNestCount = transactionNestCount; mAnimation = animation; mEarlyWakeupStart = earlyWakeupStart; @@ -770,7 +767,6 @@ status_t SurfaceComposerClient::Transaction::writeToParcel(Parcel* parcel) const const_cast(this)->cacheBuffers(); parcel->writeUint64(mId); - parcel->writeUint32(mForceSynchronous); parcel->writeUint32(mTransactionNestCount); parcel->writeBool(mAnimation); parcel->writeBool(mEarlyWakeupStart); @@ -890,7 +886,6 @@ void SurfaceComposerClient::Transaction::clear() { mListenerCallbacks.clear(); mInputWindowCommands.clear(); mMayContainBuffer = false; - mForceSynchronous = 0; mTransactionNestCount = 0; mAnimation = false; mEarlyWakeupStart = false; @@ -1002,27 +997,25 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay Vector displayStates; uint32_t flags = 0; - mForceSynchronous |= synchronous; - for (auto const& kv : mComposerStates){ composerStates.add(kv.second); } displayStates = std::move(mDisplayStates); - if (mForceSynchronous) { + if (synchronous) { flags |= ISurfaceComposer::eSynchronous; } if (mAnimation) { flags |= ISurfaceComposer::eAnimation; } if (oneWay) { - if (mForceSynchronous) { - 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; - } + 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; + } } // If both mEarlyWakeupStart and mEarlyWakeupEnd are set @@ -2020,7 +2013,6 @@ void SurfaceComposerClient::Transaction::setDisplayProjection(const sp& s.layerStackSpaceRect = layerStackRect; s.orientedDisplaySpaceRect = displayRect; s.what |= DisplayState::eDisplayProjectionChanged; - mForceSynchronous = true; // TODO: do we actually still need this? } void SurfaceComposerClient::Transaction::setDisplaySize(const sp& token, uint32_t width, uint32_t height) { diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 963cc09ca9..61d47145e9 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -403,7 +403,6 @@ public: uint64_t mId; - uint32_t mForceSynchronous = 0; uint32_t mTransactionNestCount = 0; bool mAnimation = false; bool mEarlyWakeupStart = false; -- GitLab From f2fdb56c787297cb92029ac107d407dc8d3f94d3 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Fri, 2 Sep 2022 09:50:39 -0700 Subject: [PATCH 0329/1310] FTL: Add std::variant matcher Bug: 185536303 Test: ftl_test Change-Id: Id6357fa04ffe0c17b17c99b49b5a5262d68925bf --- include/ftl/details/match.h | 59 +++++++++++++++++++++++++++++++++++ include/ftl/match.h | 62 +++++++++++++++++++++++++++++++++++++ libs/ftl/Android.bp | 1 + libs/ftl/match_test.cpp | 48 ++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+) create mode 100644 include/ftl/details/match.h create mode 100644 include/ftl/match.h create mode 100644 libs/ftl/match_test.cpp diff --git a/include/ftl/details/match.h b/include/ftl/details/match.h new file mode 100644 index 0000000000..51b99d2f13 --- /dev/null +++ b/include/ftl/details/match.h @@ -0,0 +1,59 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +namespace android::ftl::details { + +template +struct Matcher : Ms... { + using Ms::operator()...; +}; + +// Deduction guide. +template +Matcher(Ms...) -> Matcher; + +template +constexpr bool is_exhaustive_match_v = (std::is_invocable_v && ...); + +template +struct Match; + +template +struct Match { + template + static decltype(auto) match(Variant& variant, const Matcher& matcher) { + if (auto* const ptr = std::get_if(&variant)) { + return matcher(*ptr); + } else { + return Match::match(variant, matcher); + } + } +}; + +template +struct Match { + template + static decltype(auto) match(Variant& variant, const Matcher& matcher) { + return matcher(std::get(variant)); + } +}; + +} // namespace android::ftl::details diff --git a/include/ftl/match.h b/include/ftl/match.h new file mode 100644 index 0000000000..7318c45b7b --- /dev/null +++ b/include/ftl/match.h @@ -0,0 +1,62 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include + +namespace android::ftl { + +// Concise alternative to std::visit that compiles to branches rather than a dispatch table. For +// std::variant where N is small, this is slightly faster since the branches can be +// inlined unlike the function pointers. +// +// using namespace std::chrono; +// std::variant duration = 119min; +// +// // Mutable match. +// ftl::match(duration, [](auto& d) { ++d; }); +// +// // Immutable match. Exhaustive due to minutes being convertible to seconds. +// assert("2 hours"s == +// ftl::match(duration, +// [](const seconds& s) { +// const auto h = duration_cast(s); +// return std::to_string(h.count()) + " hours"s; +// }, +// [](const hours& h) { return std::to_string(h.count() / 24) + " days"s; })); +// +template +decltype(auto) match(std::variant& variant, Ms&&... matchers) { + const auto matcher = details::Matcher{std::forward(matchers)...}; + static_assert(details::is_exhaustive_match_v, "Non-exhaustive match"); + + return details::Match::match(variant, matcher); +} + +template +decltype(auto) match(const std::variant& variant, Ms&&... matchers) { + const auto matcher = details::Matcher{std::forward(matchers)...}; + static_assert(details::is_exhaustive_match_v, + "Non-exhaustive match"); + + return details::Match::match(variant, matcher); +} + +} // namespace android::ftl diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp index a25a49397e..c1945fddb3 100644 --- a/libs/ftl/Android.bp +++ b/libs/ftl/Android.bp @@ -21,6 +21,7 @@ cc_test { "fake_guard_test.cpp", "flags_test.cpp", "future_test.cpp", + "match_test.cpp", "optional_test.cpp", "small_map_test.cpp", "small_vector_test.cpp", diff --git a/libs/ftl/match_test.cpp b/libs/ftl/match_test.cpp new file mode 100644 index 0000000000..a6cff2eed6 --- /dev/null +++ b/libs/ftl/match_test.cpp @@ -0,0 +1,48 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include + +namespace android::test { + +// Keep in sync with example usage in header file. +TEST(Match, Example) { + using namespace std::chrono; + using namespace std::chrono_literals; + using namespace std::string_literals; + + std::variant duration = 119min; + + // Mutable match. + ftl::match(duration, [](auto& d) { ++d; }); + + // Immutable match. Exhaustive due to minutes being convertible to seconds. + EXPECT_EQ("2 hours"s, + ftl::match( + duration, + [](const seconds& s) { + const auto h = duration_cast(s); + return std::to_string(h.count()) + " hours"s; + }, + [](const hours& h) { return std::to_string(h.count() / 24) + " days"s; })); +} + +} // namespace android::test -- GitLab From 871cc8ad9b579131a3ad2a23eda4e9ba361aad23 Mon Sep 17 00:00:00 2001 From: Yeabkal Wubshit Date: Mon, 26 Sep 2022 17:46:28 -0700 Subject: [PATCH 0330/1310] Add Unit Tests to Check Default Strategy Setup VelocityTracker uses different default strategies for different axes, in cases where it's not supplied with an explicit strategy. This CL tests that behavior for all the axes currently supported (X,Y, and SCROLL). Bug: 249171348 Test: unit tests Change-Id: Iba9dcc0a7dee39d30d10b60f1058c3657be09ad7 --- libs/input/tests/VelocityTracker_test.cpp | 74 +++++++++++++++++++---- 1 file changed, 63 insertions(+), 11 deletions(-) diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp index 8b00b5c44d..e48012bda6 100644 --- a/libs/input/tests/VelocityTracker_test.cpp +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -223,10 +223,10 @@ static std::vector createTouchMotionEventStream( return events; } -static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, - const std::vector& motions, - int32_t axis, float targetVelocity, - uint32_t pointerId = DEFAULT_POINTER_ID) { +static std::optional computePlanarVelocity( + const VelocityTracker::Strategy strategy, + const std::vector& motions, int32_t axis, + uint32_t pointerId = DEFAULT_POINTER_ID) { VelocityTracker vt(strategy); std::vector events = createTouchMotionEventStream(motions); @@ -234,21 +234,45 @@ static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, vt.addMovement(&event); } - checkVelocity(vt.getVelocity(axis, pointerId).value_or(0), targetVelocity); + return vt.getVelocity(axis, pointerId); } -static void computeAndCheckAxisScrollVelocity( +static std::vector createMotionEventStream( + int32_t axis, const std::vector>& motion) { + switch (axis) { + case AMOTION_EVENT_AXIS_SCROLL: + return createAxisScrollMotionEventStream(motion); + default: + ADD_FAILURE() << "Axis " << axis << " is not supported"; + return {}; + } +} + +static std::optional computeVelocity( const VelocityTracker::Strategy strategy, - const std::vector>& motions, - std::optional targetVelocity) { + const std::vector>& motions, int32_t axis) { VelocityTracker vt(strategy); - std::vector events = createAxisScrollMotionEventStream(motions); - for (const MotionEvent& event : events) { + for (const MotionEvent& event : createMotionEventStream(axis, motions)) { vt.addMovement(&event); } - std::optional velocity = vt.getVelocity(AMOTION_EVENT_AXIS_SCROLL, DEFAULT_POINTER_ID); + return vt.getVelocity(axis, DEFAULT_POINTER_ID); +} + +static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, + const std::vector& motions, + int32_t axis, float targetVelocity, + uint32_t pointerId = DEFAULT_POINTER_ID) { + checkVelocity(computePlanarVelocity(strategy, motions, axis, pointerId).value_or(0), + targetVelocity); +} + +static void computeAndCheckAxisScrollVelocity( + const VelocityTracker::Strategy strategy, + const std::vector>& motions, + std::optional targetVelocity) { + std::optional velocity = computeVelocity(strategy, motions, AMOTION_EVENT_AXIS_SCROLL); if (velocity && !targetVelocity) { FAIL() << "Expected no velocity, but found " << *velocity; } @@ -280,6 +304,20 @@ static void computeAndCheckQuadraticEstimate(const std::vector motions = {{10ms, {{2, 4}}}, + {20ms, {{4, 12}}}, + {30ms, {{6, 20}}}, + {40ms, {{10, 30}}}}; + + EXPECT_EQ(computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X), + computePlanarVelocity(VelocityTracker::Strategy::DEFAULT, motions, + AMOTION_EVENT_AXIS_X)); + EXPECT_EQ(computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y), + computePlanarVelocity(VelocityTracker::Strategy::DEFAULT, motions, + AMOTION_EVENT_AXIS_Y)); +} + TEST_F(VelocityTrackerTest, TestComputedVelocity) { VelocityTracker::ComputedVelocity computedVelocity; @@ -1227,6 +1265,20 @@ TEST_F(VelocityTrackerTest, AxisScrollVelocity_ScrollDown_ThenUp_ThenDown) { } // ------------------------------- Hand generated test cases --------------------------------------- +TEST_F(VelocityTrackerTest, TestDefaultStrategyForAxisScroll) { + std::vector> motions = { + {10ms, 20}, + {20ms, 25}, + {30ms, 50}, + {40ms, 100}, + }; + + EXPECT_EQ(computeVelocity(VelocityTracker::Strategy::IMPULSE, motions, + AMOTION_EVENT_AXIS_SCROLL), + computeVelocity(VelocityTracker::Strategy::DEFAULT, motions, + AMOTION_EVENT_AXIS_SCROLL)); +} + TEST_F(VelocityTrackerTest, AxisScrollVelocity_SimilarDifferentialValues) { std::vector> motions = {{1ns, 2.12}, {3ns, 2.12}, {7ns, 2.12}, {8ns, 2.12}, -- GitLab From e70461ad2658371bc06adef2307fbdef6e39cca8 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Tue, 30 Aug 2022 14:42:01 -0700 Subject: [PATCH 0331/1310] SF: Add utility class for dumpsys formatting The Dumper class automates stringifying (a minimal set of types for now) and indenting. Bug: 241285876 Test: dumpsys SurfaceFlinger --displays Change-Id: I5581cd9cb4235e2c710e303b6ab634847554cc30 --- libs/ui/DeviceProductInfo.cpp | 49 +++++++------ libs/ui/include/ui/DeviceProductInfo.h | 4 +- .../Display/DisplaySnapshot.cpp | 17 ++--- .../surfaceflinger/Display/DisplaySnapshot.h | 6 +- services/surfaceflinger/DisplayDevice.cpp | 13 ++-- services/surfaceflinger/DisplayDevice.h | 3 +- services/surfaceflinger/DisplayHardware/Hal.h | 5 ++ .../Scheduler/RefreshRateConfigs.cpp | 40 +++++------ .../Scheduler/RefreshRateConfigs.h | 3 +- services/surfaceflinger/SurfaceFlinger.cpp | 15 ++-- services/surfaceflinger/Utils/Dumper.h | 71 +++++++++++++++++++ 11 files changed, 149 insertions(+), 77 deletions(-) create mode 100644 services/surfaceflinger/Utils/Dumper.h diff --git a/libs/ui/DeviceProductInfo.cpp b/libs/ui/DeviceProductInfo.cpp index 33060126ff..6ae27dee1d 100644 --- a/libs/ui/DeviceProductInfo.cpp +++ b/libs/ui/DeviceProductInfo.cpp @@ -14,44 +14,43 @@ * limitations under the License. */ +#include #include #include -#include - -#define RETURN_IF_ERROR(op) \ - if (const status_t status = (op); status != OK) return status; namespace android { -using base::StringAppendF; - -void DeviceProductInfo::dump(std::string& result) const { - StringAppendF(&result, "{name=\"%s\", ", name.c_str()); - StringAppendF(&result, "manufacturerPnpId=%s, ", manufacturerPnpId.data()); - StringAppendF(&result, "productId=%s, ", productId.c_str()); - - if (const auto* model = std::get_if(&manufactureOrModelDate)) { - StringAppendF(&result, "modelYear=%u, ", model->year); - } else if (const auto* manufactureWeekAndYear = - std::get_if(&manufactureOrModelDate)) { - StringAppendF(&result, "manufactureWeek=%u, ", manufactureWeekAndYear->week); - StringAppendF(&result, "manufactureYear=%d, ", manufactureWeekAndYear->year); - } else if (const auto* manufactureYear = - std::get_if(&manufactureOrModelDate)) { - StringAppendF(&result, "manufactureYear=%d, ", manufactureYear->year); - } else { - ALOGE("Unknown alternative for variant DeviceProductInfo::ManufactureOrModelDate"); - } +std::string to_string(const DeviceProductInfo& info) { + using base::StringAppendF; + + std::string result; + StringAppendF(&result, "{name=\"%s\", ", info.name.c_str()); + StringAppendF(&result, "manufacturerPnpId=%s, ", info.manufacturerPnpId.data()); + StringAppendF(&result, "productId=%s, ", info.productId.c_str()); + + ftl::match( + info.manufactureOrModelDate, + [&](DeviceProductInfo::ModelYear model) { + StringAppendF(&result, "modelYear=%u, ", model.year); + }, + [&](DeviceProductInfo::ManufactureWeekAndYear manufacture) { + StringAppendF(&result, "manufactureWeek=%u, ", manufacture.week); + StringAppendF(&result, "manufactureYear=%d, ", manufacture.year); + }, + [&](DeviceProductInfo::ManufactureYear manufacture) { + StringAppendF(&result, "manufactureYear=%d, ", manufacture.year); + }); result.append("relativeAddress=["); - for (size_t i = 0; i < relativeAddress.size(); i++) { + for (size_t i = 0; i < info.relativeAddress.size(); i++) { if (i != 0) { result.append(", "); } - StringAppendF(&result, "%u", relativeAddress[i]); + StringAppendF(&result, "%u", info.relativeAddress[i]); } result.append("]}"); + return result; } } // namespace android diff --git a/libs/ui/include/ui/DeviceProductInfo.h b/libs/ui/include/ui/DeviceProductInfo.h index 879e46fbdc..4229cf1507 100644 --- a/libs/ui/include/ui/DeviceProductInfo.h +++ b/libs/ui/include/ui/DeviceProductInfo.h @@ -61,8 +61,8 @@ struct DeviceProductInfo { // address is unavailable. // For example, for HDMI connected device this will be the physical address. std::vector relativeAddress; - - void dump(std::string& result) const; }; +std::string to_string(const DeviceProductInfo&); + } // namespace android diff --git a/services/surfaceflinger/Display/DisplaySnapshot.cpp b/services/surfaceflinger/Display/DisplaySnapshot.cpp index b4f104a74d..fca3a3de08 100644 --- a/services/surfaceflinger/Display/DisplaySnapshot.cpp +++ b/services/surfaceflinger/Display/DisplaySnapshot.cpp @@ -41,18 +41,11 @@ std::optional DisplaySnapshot::translateModeId(hal::HWConfigId hw .transform(&ftl::to_key); } -void DisplaySnapshot::dump(std::string& out) const { - using namespace std::string_literals; - - out += " connectionType="s; - out += ftl::enum_string(mConnectionType); - - out += "\n deviceProductInfo="s; - if (mDeviceProductInfo) { - mDeviceProductInfo->dump(out); - } else { - out += "{}"s; - } +void DisplaySnapshot::dump(utils::Dumper& dumper) const { + using namespace std::string_view_literals; + + dumper.dump("connectionType"sv, ftl::enum_string(mConnectionType)); + dumper.dump("deviceProductInfo"sv, mDeviceProductInfo); } } // namespace android::display diff --git a/services/surfaceflinger/Display/DisplaySnapshot.h b/services/surfaceflinger/Display/DisplaySnapshot.h index 0279220b1b..3f34e39d92 100644 --- a/services/surfaceflinger/Display/DisplaySnapshot.h +++ b/services/surfaceflinger/Display/DisplaySnapshot.h @@ -17,12 +17,12 @@ #pragma once #include -#include #include #include -#include "../DisplayHardware/DisplayMode.h" +#include "DisplayHardware/DisplayMode.h" +#include "Utils/Dumper.h" namespace android::display { @@ -43,7 +43,7 @@ public: const auto& displayModes() const { return mDisplayModes; } const auto& deviceProductInfo() const { return mDeviceProductInfo; } - void dump(std::string&) const; + void dump(utils::Dumper&) const; private: const PhysicalDisplayId mDisplayId; diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index a25296c86d..eeab0cd78f 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -338,17 +338,16 @@ std::string DisplayDevice::getDebugName() const { return name + ", \""s + mDisplayName + "\")"s; } -void DisplayDevice::dump(std::string& result) const { - using namespace std::string_literals; +void DisplayDevice::dump(utils::Dumper& dumper) const { + using namespace std::string_view_literals; - result += getDebugName(); + dumper.dump({}, getDebugName()); - result += "\n powerMode="s; - result += mPowerMode.has_value() ? to_string(mPowerMode.value()) : "OFF(reset)"; - result += '\n'; + utils::Dumper::Indent indent(dumper); + dumper.dump("powerMode"sv, mPowerMode); if (mRefreshRateConfigs) { - mRefreshRateConfigs->dump(result); + mRefreshRateConfigs->dump(dumper); } } diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index 0f52aff4f3..1de8147547 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -47,6 +47,7 @@ #include "Scheduler/RefreshRateConfigs.h" #include "ThreadContext.h" #include "TracedOrdinal.h" +#include "Utils/Dumper.h" namespace android { @@ -248,7 +249,7 @@ public: * Debugging */ std::string getDebugName() const; - void dump(std::string& result) const; + void dump(utils::Dumper&) const; private: const sp mFlinger; diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h index 473703423e..33a7bca038 100644 --- a/services/surfaceflinger/DisplayHardware/Hal.h +++ b/services/surfaceflinger/DisplayHardware/Hal.h @@ -177,6 +177,9 @@ inline std::string to_string(hardware::graphics::composer::hal::Error error) { return to_string(static_cast(error)); } +// For utils::Dumper ADL. +namespace hardware::graphics::composer::V2_2 { + inline std::string to_string(hardware::graphics::composer::hal::PowerMode mode) { switch (mode) { case hardware::graphics::composer::hal::PowerMode::OFF: @@ -194,6 +197,8 @@ inline std::string to_string(hardware::graphics::composer::hal::PowerMode mode) } } +} // namespace hardware::graphics::composer::V2_2 + inline std::string to_string(hardware::graphics::composer::hal::Vsync vsync) { switch (vsync) { case hardware::graphics::composer::hal::Vsync::ENABLE: diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp index 3cb052c4e3..fb505881fc 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp @@ -1037,47 +1037,45 @@ bool RefreshRateConfigs::isFractionalPairOrMultiple(Fps smaller, Fps bigger) { isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier * kCoef)); } -void RefreshRateConfigs::dump(std::string& result) const { - using namespace std::string_literals; +void RefreshRateConfigs::dump(utils::Dumper& dumper) const { + using namespace std::string_view_literals; std::lock_guard lock(mLock); const auto activeModeId = getActiveModeItLocked()->first; - result += " activeModeId="s; - result += std::to_string(activeModeId.value()); - - result += "\n displayModes=\n"s; - for (const auto& [id, mode] : mDisplayModes) { - result += " "s; - result += to_string(*mode); - result += '\n'; + dumper.dump("activeModeId"sv, std::to_string(activeModeId.value())); + + dumper.dump("displayModes"sv); + { + utils::Dumper::Indent indent(dumper); + for (const auto& [id, mode] : mDisplayModes) { + dumper.dump({}, to_string(*mode)); + } } - base::StringAppendF(&result, " displayManagerPolicy=%s\n", - mDisplayManagerPolicy.toString().c_str()); + dumper.dump("displayManagerPolicy"sv, mDisplayManagerPolicy.toString()); if (const Policy& currentPolicy = *getCurrentPolicyLocked(); mOverridePolicy && currentPolicy != mDisplayManagerPolicy) { - base::StringAppendF(&result, " overridePolicy=%s\n", currentPolicy.toString().c_str()); + dumper.dump("overridePolicy"sv, currentPolicy.toString()); } - base::StringAppendF(&result, " supportsFrameRateOverrideByContent=%s\n", - mSupportsFrameRateOverrideByContent ? "true" : "false"); + dumper.dump("supportsFrameRateOverrideByContent"sv, mSupportsFrameRateOverrideByContent); - result += " idleTimer="s; + std::string idleTimer; if (mIdleTimer) { - result += mIdleTimer->dump(); + idleTimer = mIdleTimer->dump(); } else { - result += "off"s; + idleTimer = "off"sv; } if (const auto controller = mConfig.kernelIdleTimerController) { - base::StringAppendF(&result, " (kernel via %s)", ftl::enum_string(*controller).c_str()); + base::StringAppendF(&idleTimer, " (kernel via %s)", ftl::enum_string(*controller).c_str()); } else { - result += " (platform)"s; + idleTimer += " (platform)"sv; } - result += '\n'; + dumper.dump("idleTimer"sv, idleTimer); } std::chrono::milliseconds RefreshRateConfigs::getIdleTimerTimeout() { diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h index 0642fcbd14..8b89104c5c 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h +++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h @@ -32,6 +32,7 @@ #include "Scheduler/OneShotTimer.h" #include "Scheduler/StrongTyping.h" #include "ThreadContext.h" +#include "Utils/Dumper.h" namespace android::scheduler { @@ -336,7 +337,7 @@ public: mIdleTimer->reset(); } - void dump(std::string& result) const EXCLUDES(mLock); + void dump(utils::Dumper&) const EXCLUDES(mLock); std::chrono::milliseconds getIdleTimerTimeout(); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index f3551ae080..896eebfce2 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -142,6 +142,7 @@ #include "SurfaceInterceptor.h" #include "TimeStats/TimeStats.h" #include "TunnelModeEnabledReporter.h" +#include "Utils/Dumper.h" #include "WindowInfosListenerInvoker.h" #include @@ -5077,18 +5078,22 @@ void SurfaceFlinger::dumpCompositionDisplays(std::string& result) const { } void SurfaceFlinger::dumpDisplays(std::string& result) const { + utils::Dumper dumper{result}; + for (const auto& [id, display] : mPhysicalDisplays) { if (const auto device = getDisplayDeviceLocked(id)) { - device->dump(result); + device->dump(dumper); } - display.snapshot().dump(result); - result += '\n'; + + utils::Dumper::Indent indent(dumper); + display.snapshot().dump(dumper); + dumper.eol(); } for (const auto& [token, display] : mDisplays) { if (display->isVirtual()) { - display->dump(result); - result += '\n'; + display->dump(dumper); + dumper.eol(); } } } diff --git a/services/surfaceflinger/Utils/Dumper.h b/services/surfaceflinger/Utils/Dumper.h new file mode 100644 index 0000000000..3761f9e806 --- /dev/null +++ b/services/surfaceflinger/Utils/Dumper.h @@ -0,0 +1,71 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +namespace android::utils { + +// Dumps variables by appending their name and value to the output string. A variable is formatted +// as "name=value". If the name or value is empty, the format is "value" or "name=", respectively. +// A value of user-defined type T is stringified via `std::string to_string(const T&)`, which must +// be defined in the same namespace as T per the rules of ADL (argument-dependent lookup). +// +// TODO(b/249828573): Consolidate with +class Dumper { +public: + explicit Dumper(std::string& out) : mOut(out) {} + + void eol() { mOut += '\n'; } + + void dump(std::string_view name, std::string_view value = {}) { + using namespace std::string_view_literals; + + for (int i = mIndent; i-- > 0;) mOut += " "sv; + mOut += name; + if (!name.empty()) mOut += '='; + mOut += value; + eol(); + } + + void dump(std::string_view name, bool value) { + using namespace std::string_view_literals; + dump(name, value ? "true"sv : "false"sv); + } + + template + void dump(std::string_view name, const std::optional& value) { + using namespace std::string_view_literals; + using std::to_string; + dump(name, value ? to_string(*value) : "nullopt"sv); + } + + struct Indent { + explicit Indent(Dumper& dumper) : dumper(dumper) { dumper.mIndent++; } + ~Indent() { dumper.mIndent--; } + + Dumper& dumper; + }; + +private: + std::string& mOut; + int mIndent = 0; +}; + +} // namespace android::utils -- GitLab From 9402bb06deb0399086908c801010291a36d541ca Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 28 Sep 2022 16:45:06 -0700 Subject: [PATCH 0332/1310] SurfaceComposerClient: Replace sync transactions with commit callbacks Test: presubmit Bug: 238781169 Change-Id: Icc1eda4284eaf48f1f759e92fab852763f5641ab --- libs/gui/SurfaceComposerClient.cpp | 56 +++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 72bd1fb675..a05c1cccb0 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -16,6 +16,7 @@ #define LOG_TAG "SurfaceComposerClient" +#include #include #include @@ -958,12 +959,55 @@ void SurfaceComposerClient::Transaction::cacheBuffers() { } } +class SyncCallback { +public: + static void function(void* callbackContext, nsecs_t /* latchTime */, + const sp& /* presentFence */, + const std::vector& /* stats */) { + if (!callbackContext) { + ALOGE("failed to get callback context for SyncCallback"); + } + SyncCallback* helper = static_cast(callbackContext); + LOG_ALWAYS_FATAL_IF(sem_post(&helper->mSemaphore), "sem_post failed"); + } + ~SyncCallback() { + if (mInitialized) { + LOG_ALWAYS_FATAL_IF(sem_destroy(&mSemaphore), "sem_destroy failed"); + } + } + void init() { + LOG_ALWAYS_FATAL_IF(clock_gettime(CLOCK_MONOTONIC, &mTimeoutTimespec) == -1, + "clock_gettime() fail! in SyncCallback::init"); + mTimeoutTimespec.tv_sec += 4; + LOG_ALWAYS_FATAL_IF(sem_init(&mSemaphore, 0, 0), "sem_init failed"); + mInitialized = true; + } + void wait() { + int result = sem_clockwait(&mSemaphore, CLOCK_MONOTONIC, &mTimeoutTimespec); + if (result && errno != ETIMEDOUT && errno != EINTR) { + LOG_ALWAYS_FATAL("sem_clockwait failed(%d)", errno); + } else if (errno == ETIMEDOUT) { + ALOGW("Sync transaction timed out waiting for commit callback."); + } + } + void* getContext() { return static_cast(this); } + +private: + sem_t mSemaphore; + bool mInitialized = false; + timespec mTimeoutTimespec; +}; + status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay) { if (mStatus != NO_ERROR) { return mStatus; } - sp sf(ComposerService::getComposerService()); + SyncCallback syncCallback; + if (synchronous) { + syncCallback.init(); + addTransactionCommittedCallback(syncCallback.function, syncCallback.getContext()); + } bool hasListenerCallbacks = !mListenerCallbacks.empty(); std::vector listenerCallbacks; @@ -998,15 +1042,12 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay Vector displayStates; uint32_t flags = 0; - for (auto const& kv : mComposerStates){ + for (auto const& kv : mComposerStates) { composerStates.add(kv.second); } displayStates = std::move(mDisplayStates); - if (synchronous) { - flags |= ISurfaceComposer::eSynchronous; - } if (mAnimation) { flags |= ISurfaceComposer::eAnimation; } @@ -1030,6 +1071,7 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay sp applyToken = mApplyToken ? mApplyToken : sApplyToken; + sp sf(ComposerService::getComposerService()); sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken, mInputWindowCommands, mDesiredPresentTime, mIsAutoTimestamp, {} /*uncacheBuffer - only set in doUncacheBufferTransaction*/, @@ -1039,6 +1081,10 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay // Clear the current states and flags clear(); + if (synchronous) { + syncCallback.wait(); + } + mStatus = NO_ERROR; return NO_ERROR; } -- GitLab From a15e599c77f37fb314fc45a7a3039b3ae24d6756 Mon Sep 17 00:00:00 2001 From: Yeabkal Wubshit Date: Thu, 29 Sep 2022 19:00:19 -0700 Subject: [PATCH 0333/1310] Update VelocityTracker_test to Reflect Empty-Velocities VelocityTracker can now return empty optional for velocities, in cases where a given axis is not supported, or there is not enough data to calculate velocity. This CL updates the test to reflect that capability (came up during a code review for b/249171348). Bug: 249171348 Test: unit tests pass Change-Id: I6054450548d42d96af9957e55ea2c92790d1bb86 --- libs/input/tests/VelocityTracker_test.cpp | 65 ++++++++++++----------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp index e48012bda6..bd126636d7 100644 --- a/libs/input/tests/VelocityTracker_test.cpp +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -65,8 +65,15 @@ static void EXPECT_NEAR_BY_FRACTION(float actual, float target, float fraction) EXPECT_NEAR(actual, target, tolerance); } -static void checkVelocity(float Vactual, float Vtarget) { - EXPECT_NEAR_BY_FRACTION(Vactual, Vtarget, VELOCITY_TOLERANCE); +static void checkVelocity(std::optional Vactual, std::optional Vtarget) { + if (Vactual != std::nullopt) { + if (Vtarget == std::nullopt) { + FAIL() << "Expected no velocity, but found " << *Vactual; + } + EXPECT_NEAR_BY_FRACTION(*Vactual, *Vtarget, VELOCITY_TOLERANCE); + } else if (Vtarget != std::nullopt) { + FAIL() << "Expected velocity, but found no velocity"; + } } static void checkCoefficient(float actual, float target) { @@ -262,26 +269,16 @@ static std::optional computeVelocity( static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy, const std::vector& motions, - int32_t axis, float targetVelocity, + int32_t axis, std::optional targetVelocity, uint32_t pointerId = DEFAULT_POINTER_ID) { - checkVelocity(computePlanarVelocity(strategy, motions, axis, pointerId).value_or(0), - targetVelocity); + checkVelocity(computePlanarVelocity(strategy, motions, axis, pointerId), targetVelocity); } static void computeAndCheckAxisScrollVelocity( const VelocityTracker::Strategy strategy, const std::vector>& motions, std::optional targetVelocity) { - std::optional velocity = computeVelocity(strategy, motions, AMOTION_EVENT_AXIS_SCROLL); - if (velocity && !targetVelocity) { - FAIL() << "Expected no velocity, but found " << *velocity; - } - if (!velocity && targetVelocity) { - FAIL() << "Expected velocity, but found no velocity"; - } - if (velocity) { - checkVelocity(*velocity, *targetVelocity); - } + checkVelocity(computeVelocity(strategy, motions, AMOTION_EVENT_AXIS_SCROLL), targetVelocity); } static void computeAndCheckQuadraticEstimate(const std::vector& motions, @@ -1013,7 +1010,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFast3) { /** * ================== Multiple pointers ============================================================ * - * Three fingers quickly tap the screen. Since this is a tap, the velocities should be zero. + * Three fingers quickly tap the screen. Since this is a tap, the velocities should be empty. * If the events with POINTER_UP or POINTER_DOWN are not handled correctly (these should not be * part of the fitted data), this can cause large velocity values to be reported instead. */ @@ -1027,12 +1024,14 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_ThreeFi { 272700us, {{1063, 1128}, {NAN, NAN}, {NAN, NAN}} }, }; - // Velocity should actually be zero, but we expect 0.016 here instead. - // This is close enough to zero, and is likely caused by division by a very small number. - computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0); - computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y, 0); - computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0); - computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_Y, 0); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, + std::nullopt); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y, + std::nullopt); + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, + std::nullopt); + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_Y, + std::nullopt); } /** @@ -1055,7 +1054,7 @@ TEST_F(VelocityTrackerTest, ShortDelayBeforeActionUp) { /** * The last movement of a single pointer is ACTION_UP. If there's a long delay between the last - * ACTION_MOVE and the final ACTION_UP, velocity should be reported as zero because the pointer + * ACTION_MOVE and the final ACTION_UP, velocity should be reported as empty because the pointer * should be assumed to have stopped. */ TEST_F(VelocityTrackerTest, LongDelayBeforeActionUp) { @@ -1065,14 +1064,16 @@ TEST_F(VelocityTrackerTest, LongDelayBeforeActionUp) { {20ms, {{30, 0}}}, {3000ms, {{30, 0}}}, // ACTION_UP }; - computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0); - computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0); + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, + std::nullopt); + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, + std::nullopt); } /** * The last movement of a pointer is always ACTION_POINTER_UP or ACTION_UP. If there's a long delay * before ACTION_POINTER_UP event, the movement should be assumed to have stopped. - * The final velocity should be reported as zero for all pointers. + * The final velocity should be reported as empty for all pointers. */ TEST_F(VelocityTrackerTest, LongDelayBeforeActionPointerUp) { std::vector motions = { @@ -1083,13 +1084,17 @@ TEST_F(VelocityTrackerTest, LongDelayBeforeActionPointerUp) { {40ms, {{30, 0}, {400, 0}}}, {3000ms, {{30, 0}}}, // ACTION_POINTER_UP }; - computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0, + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, + std::nullopt, /*pointerId*/ 0); - computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0, + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, + std::nullopt, /*pointerId*/ 0); - computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, 0, + computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X, + std::nullopt, /*pointerId*/ 1); - computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 0, + computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, + std::nullopt, /*pointerId*/ 1); } -- GitLab From 788945b09717718b0db50dc8f16ac779b99a9490 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Tue, 30 Aug 2022 12:10:56 -0700 Subject: [PATCH 0334/1310] SF: Move color modes to DisplaySnapshot Bug: 241285876 Test: dumpsys SurfaceFlinger --displays Test: dumpsys SurfaceFlinger --wide-color Change-Id: Ib546cd66950155102dbe021a2a764ad2c47d27fa --- libs/ui/include/ui/ColorMode.h | 60 ++++++ .../Display/DisplaySnapshot.cpp | 29 ++- .../surfaceflinger/Display/DisplaySnapshot.h | 7 +- services/surfaceflinger/SurfaceFlinger.cpp | 191 ++++++------------ services/surfaceflinger/SurfaceFlinger.h | 13 +- .../fuzzer/surfaceflinger_fuzzer.cpp | 2 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 9 +- .../unittests/DisplayTransactionTest.cpp | 3 +- .../unittests/DisplayTransactionTestHelpers.h | 7 +- ...nger_SetupNewDisplayDeviceInternalTest.cpp | 13 +- .../tests/unittests/TestableSurfaceFlinger.h | 4 +- 11 files changed, 178 insertions(+), 160 deletions(-) create mode 100644 libs/ui/include/ui/ColorMode.h diff --git a/libs/ui/include/ui/ColorMode.h b/libs/ui/include/ui/ColorMode.h new file mode 100644 index 0000000000..a47eaed171 --- /dev/null +++ b/libs/ui/include/ui/ColorMode.h @@ -0,0 +1,60 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include + +namespace android::ui { + +using ColorModes = std::vector; + +inline bool isWideColorMode(ColorMode colorMode) { + switch (colorMode) { + case ColorMode::DISPLAY_P3: + case ColorMode::ADOBE_RGB: + case ColorMode::DCI_P3: + case ColorMode::BT2020: + case ColorMode::DISPLAY_BT2020: + case ColorMode::BT2100_PQ: + case ColorMode::BT2100_HLG: + return true; + case ColorMode::NATIVE: + case ColorMode::STANDARD_BT601_625: + case ColorMode::STANDARD_BT601_625_UNADJUSTED: + case ColorMode::STANDARD_BT601_525: + case ColorMode::STANDARD_BT601_525_UNADJUSTED: + case ColorMode::STANDARD_BT709: + case ColorMode::SRGB: + return false; + } +} + +inline Dataspace pickDataspaceFor(ColorMode colorMode) { + switch (colorMode) { + case ColorMode::DISPLAY_P3: + case ColorMode::BT2100_PQ: + case ColorMode::BT2100_HLG: + case ColorMode::DISPLAY_BT2020: + return Dataspace::DISPLAY_P3; + default: + return Dataspace::V0_SRGB; + } +} + +} // namespace android::ui diff --git a/services/surfaceflinger/Display/DisplaySnapshot.cpp b/services/surfaceflinger/Display/DisplaySnapshot.cpp index fca3a3de08..0c7a58ee71 100644 --- a/services/surfaceflinger/Display/DisplaySnapshot.cpp +++ b/services/surfaceflinger/Display/DisplaySnapshot.cpp @@ -14,11 +14,13 @@ * limitations under the License. */ +#include #include #include #include #include +#include #include "DisplaySnapshot.h" @@ -26,11 +28,12 @@ namespace android::display { DisplaySnapshot::DisplaySnapshot(PhysicalDisplayId displayId, ui::DisplayConnectionType connectionType, - DisplayModes&& displayModes, + DisplayModes&& displayModes, ui::ColorModes&& colorModes, std::optional&& deviceProductInfo) : mDisplayId(displayId), mConnectionType(connectionType), mDisplayModes(std::move(displayModes)), + mColorModes(std::move(colorModes)), mDeviceProductInfo(std::move(deviceProductInfo)) {} std::optional DisplaySnapshot::translateModeId(hal::HWConfigId hwcId) const { @@ -41,10 +44,34 @@ std::optional DisplaySnapshot::translateModeId(hal::HWConfigId hw .transform(&ftl::to_key); } +ui::ColorModes DisplaySnapshot::filterColorModes(bool supportsWideColor) const { + ui::ColorModes modes = mColorModes; + + // If the display is internal and the configuration claims it's not wide color capable, filter + // out all wide color modes. The typical reason why this happens is that the hardware is not + // good enough to support GPU composition of wide color, and thus the OEMs choose to disable + // this capability. + if (mConnectionType == ui::DisplayConnectionType::Internal && !supportsWideColor) { + const auto it = std::remove_if(modes.begin(), modes.end(), ui::isWideColorMode); + modes.erase(it, modes.end()); + } + + return modes; +} + void DisplaySnapshot::dump(utils::Dumper& dumper) const { using namespace std::string_view_literals; dumper.dump("connectionType"sv, ftl::enum_string(mConnectionType)); + + dumper.dump("colorModes"sv); + { + utils::Dumper::Indent indent(dumper); + for (const auto mode : mColorModes) { + dumper.dump({}, decodeColorMode(mode)); + } + } + dumper.dump("deviceProductInfo"sv, mDeviceProductInfo); } diff --git a/services/surfaceflinger/Display/DisplaySnapshot.h b/services/surfaceflinger/Display/DisplaySnapshot.h index 3f34e39d92..23471f5d8e 100644 --- a/services/surfaceflinger/Display/DisplaySnapshot.h +++ b/services/surfaceflinger/Display/DisplaySnapshot.h @@ -18,6 +18,7 @@ #include +#include #include #include @@ -29,7 +30,7 @@ namespace android::display { // Immutable state of a physical display, captured on hotplug. class DisplaySnapshot { public: - DisplaySnapshot(PhysicalDisplayId, ui::DisplayConnectionType, DisplayModes&&, + DisplaySnapshot(PhysicalDisplayId, ui::DisplayConnectionType, DisplayModes&&, ui::ColorModes&&, std::optional&&); DisplaySnapshot(const DisplaySnapshot&) = delete; @@ -41,8 +42,11 @@ public: std::optional translateModeId(hal::HWConfigId) const; const auto& displayModes() const { return mDisplayModes; } + const auto& colorModes() const { return mColorModes; } const auto& deviceProductInfo() const { return mDeviceProductInfo; } + ui::ColorModes filterColorModes(bool supportsWideColor) const; + void dump(utils::Dumper&) const; private: @@ -51,6 +55,7 @@ private: // Effectively const except in move constructor. DisplayModes mDisplayModes; + ui::ColorModes mColorModes; std::optional mDeviceProductInfo; }; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 950d42afa6..ddd0f4c1d8 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -182,7 +182,6 @@ using gui::IWindowInfosListener; using gui::LayerMetadata; using gui::WindowInfo; using gui::aidl_utils::binderStatusFromStatusT; -using ui::ColorMode; using ui::Dataspace; using ui::DisplayPrimaries; using ui::RenderIntent; @@ -193,33 +192,6 @@ namespace hal = android::hardware::graphics::composer::hal; namespace { -#pragma clang diagnostic push -#pragma clang diagnostic error "-Wswitch-enum" - -bool isWideColorMode(const ColorMode colorMode) { - switch (colorMode) { - case ColorMode::DISPLAY_P3: - case ColorMode::ADOBE_RGB: - case ColorMode::DCI_P3: - case ColorMode::BT2020: - case ColorMode::DISPLAY_BT2020: - case ColorMode::BT2100_PQ: - case ColorMode::BT2100_HLG: - return true; - case ColorMode::NATIVE: - case ColorMode::STANDARD_BT601_625: - case ColorMode::STANDARD_BT601_625_UNADJUSTED: - case ColorMode::STANDARD_BT601_525: - case ColorMode::STANDARD_BT601_525_UNADJUSTED: - case ColorMode::STANDARD_BT709: - case ColorMode::SRGB: - return false; - } - return false; -} - -#pragma clang diagnostic pop - // TODO(b/141333600): Consolidate with DisplayMode::Builder::getDefaultDensity. constexpr float FALLBACK_DENSITY = ACONFIGURATION_DENSITY_TV; @@ -291,7 +263,6 @@ bool SurfaceFlinger::hasSyncFramework; int64_t SurfaceFlinger::maxFrameBufferAcquiredBuffers; uint32_t SurfaceFlinger::maxGraphicsWidth; uint32_t SurfaceFlinger::maxGraphicsHeight; -bool SurfaceFlinger::hasWideColorDisplay; bool SurfaceFlinger::useContextPriority; Dataspace SurfaceFlinger::defaultCompositionDataspace = Dataspace::V0_SRGB; ui::PixelFormat SurfaceFlinger::defaultCompositionPixelFormat = ui::PixelFormat::RGBA_8888; @@ -360,11 +331,11 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI maxGraphicsWidth = std::max(max_graphics_width(0), 0); maxGraphicsHeight = std::max(max_graphics_height(0), 0); - hasWideColorDisplay = has_wide_color_display(false); + mSupportsWideColor = has_wide_color_display(false); mDefaultCompositionDataspace = static_cast(default_composition_dataspace(Dataspace::V0_SRGB)); mWideColorGamutCompositionDataspace = static_cast(wcg_composition_dataspace( - hasWideColorDisplay ? Dataspace::DISPLAY_P3 : Dataspace::V0_SRGB)); + mSupportsWideColor ? Dataspace::DISPLAY_P3 : Dataspace::V0_SRGB)); defaultCompositionDataspace = mDefaultCompositionDataspace; wideColorGamutCompositionDataspace = mWideColorGamutCompositionDataspace; defaultCompositionPixelFormat = static_cast( @@ -896,8 +867,8 @@ void SurfaceFlinger::readPersistentProperties() { property_get("persist.sys.sf.native_mode", value, "0"); mDisplayColorSetting = static_cast(atoi(value)); - property_get("persist.sys.sf.color_mode", value, "0"); - mForceColorMode = static_cast(atoi(value)); + mForceColorMode = + static_cast(base::GetIntProperty("persist.sys.sf.color_mode"s, 0)); } void SurfaceFlinger::startBootAnim() { @@ -1059,11 +1030,12 @@ status_t SurfaceFlinger::getDynamicDisplayInfo(const sp& displayToken, info->supportedDisplayModes.push_back(outMode); } + info->supportedColorModes = snapshot.filterColorModes(mSupportsWideColor); + const PhysicalDisplayId displayId = snapshot.displayId(); info->activeDisplayModeId = display->refreshRateConfigs().getActiveModePtr()->getId().value(); info->activeColorMode = display->getCompositionDisplay()->getState().colorMode; - info->supportedColorModes = getDisplayColorModes(displayId); info->hdrCapabilities = display->getHdrCapabilities(); info->autoLowLatencyModeSupported = @@ -1341,25 +1313,6 @@ void SurfaceFlinger::disableExpensiveRendering() { future.wait(); } -std::vector SurfaceFlinger::getDisplayColorModes(PhysicalDisplayId displayId) { - auto modes = getHwComposer().getColorModes(displayId); - - const bool isInternalDisplay = mPhysicalDisplays.get(displayId) - .transform(&PhysicalDisplay::isInternal) - .value_or(false); - - // If the display is internal and the configuration claims it's not wide color capable, - // filter out all wide color modes. The typical reason why this happens is that the - // hardware is not good enough to support GPU composition of wide color, and thus the - // OEMs choose to disable this capability. - if (isInternalDisplay && !hasWideColorDisplay) { - const auto newEnd = std::remove_if(modes.begin(), modes.end(), isWideColorMode); - modes.erase(newEnd, modes.end()); - } - - return modes; -} - status_t SurfaceFlinger::getDisplayNativePrimaries(const sp& displayToken, ui::DisplayPrimaries& primaries) { if (!displayToken) { @@ -1383,31 +1336,32 @@ status_t SurfaceFlinger::getDisplayNativePrimaries(const sp& displayTok return NO_ERROR; } -status_t SurfaceFlinger::setActiveColorMode(const sp& displayToken, ColorMode mode) { +status_t SurfaceFlinger::setActiveColorMode(const sp& displayToken, ui::ColorMode mode) { if (!displayToken) { return BAD_VALUE; } + const char* const whence = __func__; auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t { - const auto display = getDisplayDeviceLocked(displayToken); - if (!display) { - ALOGE("Attempt to set active color mode %s (%d) for invalid display token %p", - decodeColorMode(mode).c_str(), mode, displayToken.get()); + const auto displayOpt = + ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken)) + .transform(&ftl::to_mapped_ref) + .and_then(getDisplayDeviceAndSnapshot()); + + if (!displayOpt) { + ALOGE("%s: Invalid physical display token %p", whence, displayToken.get()); return NAME_NOT_FOUND; } - if (display->isVirtual()) { - ALOGW("Attempt to set active color mode %s (%d) for virtual display", - decodeColorMode(mode).c_str(), mode); - return INVALID_OPERATION; - } + const auto& [display, snapshotRef] = *displayOpt; + const auto& snapshot = snapshotRef.get(); - const auto modes = getDisplayColorModes(display->getPhysicalId()); + const auto modes = snapshot.filterColorModes(mSupportsWideColor); const bool exists = std::find(modes.begin(), modes.end(), mode) != modes.end(); - if (mode < ColorMode::NATIVE || !exists) { - ALOGE("Attempt to set invalid active color mode %s (%d) for display token %p", - decodeColorMode(mode).c_str(), mode, displayToken.get()); + if (mode < ui::ColorMode::NATIVE || !exists) { + ALOGE("%s: Invalid color mode %s (%d) for display %s", whence, + decodeColorMode(mode).c_str(), mode, to_string(snapshot.displayId()).c_str()); return BAD_VALUE; } @@ -1585,7 +1539,7 @@ status_t SurfaceFlinger::isWideColorDisplay(const sp& displayToken, } *outIsWideColorDisplay = - display->isPrimary() ? hasWideColorDisplay : display->hasWideColorGamut(); + display->isPrimary() ? mSupportsWideColor : display->hasWideColorGamut(); return NO_ERROR; } @@ -2753,6 +2707,8 @@ const char* SurfaceFlinger::processHotplug(PhysicalDisplayId displayId, return nullptr; } + ui::ColorModes colorModes = getHwComposer().getColorModes(displayId); + if (displayOpt) { const auto& display = displayOpt->get(); const auto& snapshot = display.snapshot(); @@ -2767,7 +2723,7 @@ const char* SurfaceFlinger::processHotplug(PhysicalDisplayId displayId, const auto it = mPhysicalDisplays.try_replace(displayId, display.token(), displayId, snapshot.connectionType(), std::move(displayModes), - std::move(deviceProductInfo)); + std::move(colorModes), std::move(deviceProductInfo)); auto& state = mCurrentState.displays.editValueFor(it->second.token()); state.sequenceId = DisplayDeviceState{}.sequenceId; // Generate new sequenceId. @@ -2779,7 +2735,8 @@ const char* SurfaceFlinger::processHotplug(PhysicalDisplayId displayId, mPhysicalDisplays.try_emplace(displayId, token, displayId, getHwComposer().getDisplayConnectionType(displayId), - std::move(displayModes), std::move(info.deviceProductInfo)); + std::move(displayModes), std::move(colorModes), + std::move(info.deviceProductInfo)); DisplayDeviceState state; state.physical = {.id = displayId, @@ -2834,22 +2791,20 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( config); }) .value_or(nullptr); - } - if (const auto id = PhysicalDisplayId::tryCast(compositionDisplay->getId())) { - creationArgs.isPrimary = id == getPrimaryDisplayIdLocked(); + creationArgs.isPrimary = physical->id == getPrimaryDisplayIdLocked(); if (useColorManagement) { - std::vector modes = getHwComposer().getColorModes(*id); - for (ColorMode colorMode : modes) { - if (isWideColorMode(colorMode)) { - creationArgs.hasWideColorGamut = true; - } - - std::vector renderIntents = - getHwComposer().getRenderIntents(*id, colorMode); - creationArgs.hwcColorModes.emplace(colorMode, renderIntents); - } + mPhysicalDisplays.get(physical->id) + .transform(&PhysicalDisplay::snapshotRef) + .transform(ftl::unit_fn([&](const display::DisplaySnapshot& snapshot) { + for (const auto mode : snapshot.colorModes()) { + creationArgs.hasWideColorGamut |= ui::isWideColorMode(mode); + creationArgs.hwcColorModes + .emplace(mode, + getHwComposer().getRenderIntents(physical->id, mode)); + } + })); } } @@ -2881,10 +2836,10 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( nativeWindowSurface->preallocateBuffers(); - ColorMode defaultColorMode = ColorMode::NATIVE; + ui::ColorMode defaultColorMode = ui::ColorMode::NATIVE; Dataspace defaultDataSpace = Dataspace::UNKNOWN; if (display->hasWideColorGamut()) { - defaultColorMode = ColorMode::SRGB; + defaultColorMode = ui::ColorMode::SRGB; defaultDataSpace = Dataspace::V0_SRGB; } display->getCompositionDisplay()->setColorProfile( @@ -5173,28 +5128,24 @@ void SurfaceFlinger::dumpRawDisplayIdentificationData(const DumpArgs& args, } void SurfaceFlinger::dumpWideColorInfo(std::string& result) const { - StringAppendF(&result, "Device has wide color built-in display: %d\n", hasWideColorDisplay); + StringAppendF(&result, "Device supports wide color: %d\n", mSupportsWideColor); StringAppendF(&result, "Device uses color management: %d\n", useColorManagement); StringAppendF(&result, "DisplayColorSetting: %s\n", decodeDisplayColorSetting(mDisplayColorSetting).c_str()); // TODO: print out if wide-color mode is active or not - for (const auto& [token, display] : mDisplays) { - const auto displayId = PhysicalDisplayId::tryCast(display->getId()); - if (!displayId) { - continue; - } - - StringAppendF(&result, "Display %s color modes:\n", to_string(*displayId).c_str()); - std::vector modes = getHwComposer().getColorModes(*displayId); - for (auto&& mode : modes) { + for (const auto& [id, display] : mPhysicalDisplays) { + StringAppendF(&result, "Display %s color modes:\n", to_string(id).c_str()); + for (const auto mode : display.snapshot().colorModes()) { StringAppendF(&result, " %s (%d)\n", decodeColorMode(mode).c_str(), mode); } - ColorMode currentMode = display->getCompositionDisplay()->getState().colorMode; - StringAppendF(&result, " Current color mode: %s (%d)\n", - decodeColorMode(currentMode).c_str(), currentMode); + if (const auto display = getDisplayDeviceLocked(id)) { + ui::ColorMode currentMode = display->getCompositionDisplay()->getState().colorMode; + StringAppendF(&result, " Current color mode: %s (%d)\n", + decodeColorMode(currentMode).c_str(), currentMode); + } } result.append("\n"); } @@ -5738,12 +5689,11 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r updateColorMatrixLocked(); return NO_ERROR; } - case 1023: { // Set native mode - int32_t colorMode; - + case 1023: { // Set color mode. mDisplayColorSetting = static_cast(data.readInt32()); - if (data.readInt32(&colorMode) == NO_ERROR) { - mForceColorMode = static_cast(colorMode); + + if (int32_t colorMode; data.readInt32(&colorMode) == NO_ERROR) { + mForceColorMode = static_cast(colorMode); } scheduleRepaint(); return NO_ERROR; @@ -6172,18 +6122,6 @@ private: const int mApi; }; -static Dataspace pickDataspaceFromColorMode(const ColorMode colorMode) { - switch (colorMode) { - case ColorMode::DISPLAY_P3: - case ColorMode::BT2100_PQ: - case ColorMode::BT2100_HLG: - case ColorMode::DISPLAY_BT2020: - return Dataspace::DISPLAY_P3; - default: - return Dataspace::V0_SRGB; - } -} - static bool hasCaptureBlackoutContentPermission() { IPCThreadState* ipc = IPCThreadState::self(); const int pid = ipc->getCallingPid(); @@ -6295,15 +6233,11 @@ status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, reqSize = display->getLayerStackSpaceRect().getSize(); } - // The dataspace is depended on the color mode of display, that could use non-native mode - // (ex. displayP3) to enhance the content, but some cases are checking native RGB in bytes, - // and failed if display is not in native mode. This provide a way to force using native - // colors when capture. - dataspace = args.dataspace; - if (dataspace == ui::Dataspace::UNKNOWN) { - const ui::ColorMode colorMode = display->getCompositionDisplay()->getState().colorMode; - dataspace = pickDataspaceFromColorMode(colorMode); - } + // Allow the caller to specify a dataspace regardless of the display's color mode, e.g. if + // it wants sRGB regardless of the display's wide color mode. + dataspace = args.dataspace == ui::Dataspace::UNKNOWN + ? ui::pickDataspaceFor(display->getCompositionDisplay()->getState().colorMode) + : args.dataspace; } RenderAreaFuture renderAreaFuture = ftl::defer([=] { @@ -6338,9 +6272,7 @@ status_t SurfaceFlinger::captureDisplay(DisplayId displayId, displayWeak = display; layerStack = display->getLayerStack(); size = display->getLayerStackSpaceRect().getSize(); - - dataspace = - pickDataspaceFromColorMode(display->getCompositionDisplay()->getState().colorMode); + dataspace = ui::pickDataspaceFor(display->getCompositionDisplay()->getState().colorMode); } RenderAreaFuture renderAreaFuture = ftl::defer([=] { @@ -6616,7 +6548,7 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( auto sdrWhitePointNits = DisplayDevice::sDefaultMaxLumiance; auto displayBrightnessNits = DisplayDevice::sDefaultMaxLumiance; - if ((dataspace == ui::Dataspace::UNKNOWN) && (parent != nullptr)) { + if (dataspace == ui::Dataspace::UNKNOWN && parent) { Mutex::Autolock lock(mStateLock); auto display = findDisplay([layerStack = parent->getLayerStack()](const auto& display) { return display.getLayerStack() == layerStack; @@ -6626,8 +6558,7 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( display = getDefaultDisplayDeviceLocked(); } - const ui::ColorMode colorMode = display->getCompositionDisplay()->getState().colorMode; - dataspace = pickDataspaceFromColorMode(colorMode); + dataspace = ui::pickDataspaceFor(display->getCompositionDisplay()->getState().colorMode); renderIntent = display->getCompositionDisplay()->getState().renderIntent; sdrWhitePointNits = display->getCompositionDisplay()->getState().sdrWhitePointNits; displayBrightnessNits = display->getCompositionDisplay()->getState().displayBrightnessNits; diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 85d11bad04..378c9f027b 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -214,10 +214,6 @@ public: static uint32_t maxGraphicsWidth; static uint32_t maxGraphicsHeight; - // Indicate if a device has wide color gamut display. This is typically - // found on devices with wide color gamut (e.g. Display-P3) display. - static bool hasWideColorDisplay; - // Indicate if device wants color management on its display. static const constexpr bool useColorManagement = true; @@ -1103,11 +1099,6 @@ private: */ const std::unordered_map& getGenericLayerMetadataKeyMap() const; - /* - * Misc - */ - std::vector getDisplayColorModes(PhysicalDisplayId) REQUIRES(mStateLock); - static int calculateMaxAcquiredBufferCount(Fps refreshRate, std::chrono::nanoseconds presentLatency); int getMaxAcquiredBufferCountForRefreshRate(Fps refreshRate) const; @@ -1285,6 +1276,10 @@ private: // This property can be used to force SurfaceFlinger to always pick a certain color mode. ui::ColorMode mForceColorMode = ui::ColorMode::NATIVE; + // Whether to enable wide color gamut (e.g. Display P3) for internal displays that support it. + // If false, wide color modes are filtered out for all internal displays. + bool mSupportsWideColor = false; + ui::Dataspace mDefaultCompositionDataspace; ui::Dataspace mWideColorGamutCompositionDataspace; ui::Dataspace mColorSpaceAgnosticDataspace; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp index 79112bd998..22d80ca3db 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp @@ -130,7 +130,7 @@ void SurfaceFlingerFuzzer::invokeFlinger() { mFlinger->maxFrameBufferAcquiredBuffers = mFdp.ConsumeIntegral(); mFlinger->maxGraphicsWidth = mFdp.ConsumeIntegral(); mFlinger->maxGraphicsHeight = mFdp.ConsumeIntegral(); - mFlinger->hasWideColorDisplay = mFdp.ConsumeBool(); + mTestableFlinger.mutableSupportsWideColor() = mFdp.ConsumeBool(); mFlinger->useContextPriority = mFdp.ConsumeBool(); mFlinger->defaultCompositionDataspace = mFdp.PickValueInArray(kDataspaces); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 69dbfe0280..82158d8d7e 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -762,10 +762,11 @@ public: * post-conditions. */ - auto &mutableCurrentState() { return mFlinger->mCurrentState; } - auto &mutableDisplays() { return mFlinger->mDisplays; } - auto &mutableDrawingState() { return mFlinger->mDrawingState; } - auto &mutableInterceptor() { return mFlinger->mInterceptor; } + auto& mutableSupportsWideColor() { return mFlinger->mSupportsWideColor; } + auto& mutableCurrentState() { return mFlinger->mCurrentState; } + auto& mutableDisplays() { return mFlinger->mDisplays; } + auto& mutableDrawingState() { return mFlinger->mDrawingState; } + auto& mutableInterceptor() { return mFlinger->mInterceptor; } auto fromHandle(const sp &handle) { return mFlinger->fromHandle(handle); } diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp index 31262b3623..abe32c9794 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp @@ -36,8 +36,7 @@ DisplayTransactionTest::DisplayTransactionTest() { ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - // Default to no wide color display support configured - mFlinger.mutableHasWideColorDisplay() = false; + mFlinger.mutableSupportsWideColor() = false; mFlinger.mutableDisplayColorSetting() = DisplayColorSetting::kUnmanaged; mFlinger.setCreateBufferQueueFunction([](auto, auto, auto) { diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h index 885d6cdd15..20e776f1f2 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h @@ -679,12 +679,10 @@ struct WideColorNotSupportedVariant { static constexpr bool WIDE_COLOR_SUPPORTED = false; static void injectConfigChange(DisplayTransactionTest* test) { - test->mFlinger.mutableHasWideColorDisplay() = true; + test->mFlinger.mutableSupportsWideColor() = true; } static void setupComposerCallExpectations(DisplayTransactionTest* test) { - EXPECT_CALL(*test->mComposer, getColorModes(Display::HWC_DISPLAY_ID, _)) - .WillOnce(DoAll(SetArgPointee<1>(std::vector()), Return(Error::NONE))); EXPECT_CALL(*test->mComposer, setColorMode(_, _, _)).Times(0); } }; @@ -696,12 +694,11 @@ struct WideColorSupportNotConfiguredVariant { static constexpr bool WIDE_COLOR_SUPPORTED = false; static void injectConfigChange(DisplayTransactionTest* test) { - test->mFlinger.mutableHasWideColorDisplay() = false; + test->mFlinger.mutableSupportsWideColor() = false; test->mFlinger.mutableDisplayColorSetting() = DisplayColorSetting::kUnmanaged; } static void setupComposerCallExpectations(DisplayTransactionTest* test) { - EXPECT_CALL(*test->mComposer, getColorModes(_, _)).Times(0); EXPECT_CALL(*test->mComposer, getRenderIntents(_, _, _)).Times(0); EXPECT_CALL(*test->mComposer, setColorMode(_, _, _)).Times(0); } diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp index 073c459ea3..0384568707 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp @@ -36,16 +36,13 @@ struct WideColorP3ColorimetricSupportedVariant { static constexpr bool WIDE_COLOR_SUPPORTED = true; static void injectConfigChange(DisplayTransactionTest* test) { - test->mFlinger.mutableHasWideColorDisplay() = true; + test->mFlinger.mutableSupportsWideColor() = true; test->mFlinger.mutableDisplayColorSetting() = DisplayColorSetting::kUnmanaged; } static void setupComposerCallExpectations(DisplayTransactionTest* test) { EXPECT_CALL(*test->mNativeWindow, perform(NATIVE_WINDOW_SET_BUFFERS_DATASPACE)).Times(1); - EXPECT_CALL(*test->mComposer, getColorModes(Display::HWC_DISPLAY_ID, _)) - .WillOnce(DoAll(SetArgPointee<1>(std::vector({ColorMode::DISPLAY_P3})), - Return(Error::NONE))); EXPECT_CALL(*test->mComposer, getRenderIntents(Display::HWC_DISPLAY_ID, ColorMode::DISPLAY_P3, _)) .WillOnce(DoAll(SetArgPointee<2>( @@ -253,9 +250,15 @@ void SetupNewDisplayDeviceInternalTest::setupNewDisplayDeviceInternalTest() { .hwcDisplayId = *hwcDisplayId, .activeMode = activeMode}; + ui::ColorModes colorModes; + if constexpr (Case::WideColorSupport::WIDE_COLOR_SUPPORTED) { + colorModes.push_back(ColorMode::DISPLAY_P3); + } + mFlinger.mutablePhysicalDisplays().emplace_or_replace(*displayId, displayToken, *displayId, *connectionType, - makeModes(activeMode), std::nullopt); + makeModes(activeMode), + std::move(colorModes), std::nullopt); } state.isSecure = static_cast(Case::Display::SECURE); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 13389a1ad3..e624ab9ca5 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -511,7 +511,7 @@ public: const auto& hwcPhysicalDisplayIdMap() const { return getHwComposer().mPhysicalDisplayIdMap; } const auto& hwcDisplayData() const { return getHwComposer().mDisplayData; } - auto& mutableHasWideColorDisplay() { return SurfaceFlinger::hasWideColorDisplay; } + auto& mutableSupportsWideColor() { return mFlinger->mSupportsWideColor; } auto& mutableCurrentState() { return mFlinger->mCurrentState; } auto& mutableDisplayColorSetting() { return mFlinger->mDisplayColorSetting; } @@ -863,7 +863,7 @@ public: const auto it = mFlinger.mutablePhysicalDisplays() .emplace_or_replace(*physicalId, mDisplayToken, *physicalId, *mConnectionType, std::move(modes), - std::nullopt) + ui::ColorModes(), std::nullopt) .first; display->setActiveMode(activeModeId, it->second.snapshot()); -- GitLab From 1523dad8dc7c2cd9a110e0839ada541f49920a75 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Thu, 29 Sep 2022 16:05:18 -0700 Subject: [PATCH 0335/1310] SurfaceFlinger: Remove eSynchronous flag Sync transactions are now implemented with the commit callback so this flag is no longer being set. Test: presubmit Bug: 238781169 Change-Id: Ibbfa7202dd2abb38c6a0e2df0dca0b286a798f3b --- libs/gui/include/gui/ISurfaceComposer.h | 1 - services/surfaceflinger/SurfaceFlinger.cpp | 41 +---- services/surfaceflinger/SurfaceFlinger.h | 5 - .../surfaceflinger/SurfaceInterceptor.cpp | 2 - services/surfaceflinger/TransactionState.h | 44 ----- .../fuzzer/surfaceflinger_fuzzers_utils.h | 4 - .../tests/unittests/TestableSurfaceFlinger.h | 6 - .../unittests/TransactionApplicationTest.cpp | 151 +++++------------- 8 files changed, 37 insertions(+), 217 deletions(-) diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index 1e85131386..d46c2e7e24 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -95,7 +95,6 @@ public: // flags for setTransactionState() enum { - eSynchronous = 0x01, eAnimation = 0x02, // Explicit indication that this transaction and others to follow will likely result in a diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 7d3fc98578..3419721865 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3228,7 +3228,6 @@ void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { } doCommitTransactions(); - signalSynchronousTransactions(CountDownLatch::eSyncTransaction); } void SurfaceFlinger::updateInputFlinger() { @@ -3825,10 +3824,6 @@ bool SurfaceFlinger::applyTransactions(std::vector& transactio transaction.permissions, transaction.hasListenerCallbacks, transaction.listenerCallbacks, transaction.originPid, transaction.originUid, transaction.id); - if (transaction.transactionCommittedSignal) { - mTransactionCommittedSignals.emplace_back( - std::move(transaction.transactionCommittedSignal)); - } } if (mTransactionTracing) { @@ -4004,12 +3999,6 @@ auto SurfaceFlinger::transactionIsReadyToBeApplied( } void SurfaceFlinger::queueTransaction(TransactionState& state) { - // Generate a CountDownLatch pending state if this is a synchronous transaction. - if (state.flags & eSynchronous) { - state.transactionCommittedSignal = - std::make_shared(CountDownLatch::eSyncTransaction); - } - mLocklessTransactionQueue.push(state); mPendingTransactionCount++; ATRACE_INT("TransactionQueue", mPendingTransactionCount.load()); @@ -4025,28 +4014,6 @@ void SurfaceFlinger::queueTransaction(TransactionState& state) { setTransactionFlags(eTransactionFlushNeeded, schedule, state.applyToken, frameHint); } -void SurfaceFlinger::waitForSynchronousTransaction( - const CountDownLatch& transactionCommittedSignal) { - // applyTransactionState is called on the main SF thread. While a given process may wish - // to wait on synchronous transactions, the main SF thread should apply the transaction and - // set the value to notify this after committed. - if (!transactionCommittedSignal.wait_until( - std::chrono::nanoseconds(mAnimationTransactionTimeout))) { - ALOGE("setTransactionState timed out!"); - } -} - -void SurfaceFlinger::signalSynchronousTransactions(const uint32_t flag) { - for (auto it = mTransactionCommittedSignals.begin(); - it != mTransactionCommittedSignals.end();) { - if ((*it)->countDown(flag)) { - it = mTransactionCommittedSignals.erase(it); - } else { - it++; - } - } -} - status_t SurfaceFlinger::setTransactionState( const FrameTimelineInfo& frameTimelineInfo, const Vector& states, const Vector& displays, uint32_t flags, const sp& applyToken, @@ -4099,11 +4066,6 @@ status_t SurfaceFlinger::setTransactionState( } queueTransaction(state); - // Check the pending state to make sure the transaction is synchronous. - if (state.transactionCommittedSignal) { - waitForSynchronousTransaction(*state.transactionCommittedSignal); - } - return NO_ERROR; } @@ -4160,8 +4122,7 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin // anyway. This can be used as a flush mechanism for previous async transactions. // Empty animation transaction can be used to simulate back-pressure, so also force a // transaction for empty animation transactions. - if (transactionFlags == 0 && - ((flags & eSynchronous) || (flags & eAnimation))) { + if (transactionFlags == 0 && (flags & eAnimation)) { transactionFlags = eTransactionNeeded; } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 85d11bad04..baaf41ec02 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1095,8 +1095,6 @@ private: // Add transaction to the Transaction Queue void queueTransaction(TransactionState& state); - void waitForSynchronousTransaction(const CountDownLatch& transactionCommittedSignal); - void signalSynchronousTransactions(const uint32_t flag); /* * Generic Layer Metadata @@ -1129,7 +1127,6 @@ private: mutable Mutex mStateLock; State mCurrentState{LayerVector::StateSet::Current}; std::atomic mTransactionFlags = 0; - std::vector> mTransactionCommittedSignals; std::atomic mUniqueTransactionId = 1; SortedVector> mLayersPendingRemoval; @@ -1418,8 +1415,6 @@ private: bool early = false; } mPowerHintSessionMode; - nsecs_t mAnimationTransactionTimeout = s2ns(5); - friend class SurfaceComposerAIDL; }; diff --git a/services/surfaceflinger/SurfaceInterceptor.cpp b/services/surfaceflinger/SurfaceInterceptor.cpp index 6797aa697b..c90ae4cd83 100644 --- a/services/surfaceflinger/SurfaceInterceptor.cpp +++ b/services/surfaceflinger/SurfaceInterceptor.cpp @@ -127,7 +127,6 @@ void SurfaceInterceptor::addInitialSurfaceStateLocked(Increment* increment, { Transaction* transaction(increment->mutable_transaction()); const uint32_t layerFlags = layer->getTransactionFlags(); - transaction->set_synchronous(layerFlags & BnSurfaceComposer::eSynchronous); transaction->set_animation(layerFlags & BnSurfaceComposer::eAnimation); const int32_t layerId(getLayerId(layer)); @@ -495,7 +494,6 @@ void SurfaceInterceptor::addTransactionLocked( const Vector& changedDisplays, uint32_t transactionFlags, int originPid, int originUid, uint64_t transactionId) { Transaction* transaction(increment->mutable_transaction()); - transaction->set_synchronous(transactionFlags & BnSurfaceComposer::eSynchronous); transaction->set_animation(transactionFlags & BnSurfaceComposer::eAnimation); setTransactionOriginLocked(transaction, originPid, originUid); transaction->set_id(transactionId); diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h index 61f0fa6b64..95eb503327 100644 --- a/services/surfaceflinger/TransactionState.h +++ b/services/surfaceflinger/TransactionState.h @@ -26,8 +26,6 @@ namespace android { -class CountDownLatch; - struct TransactionState { TransactionState() = default; @@ -97,49 +95,7 @@ struct TransactionState { int originPid; int originUid; uint64_t id; - std::shared_ptr transactionCommittedSignal; bool sentFenceTimeoutWarning = false; }; -class CountDownLatch { -public: - enum { - eSyncTransaction = 1 << 0, - }; - explicit CountDownLatch(uint32_t flags) : mFlags(flags) {} - - // True if there is no waiting condition after count down. - bool countDown(uint32_t flag) { - std::unique_lock lock(mMutex); - if (mFlags == 0) { - return true; - } - mFlags &= ~flag; - if (mFlags == 0) { - mCountDownComplete.notify_all(); - return true; - } - return false; - } - - // Return true if triggered. - bool wait_until(const std::chrono::nanoseconds& timeout) const { - std::unique_lock lock(mMutex); - const auto untilTime = std::chrono::system_clock::now() + timeout; - while (mFlags != 0) { - // Conditional variables can be woken up sporadically, so we check count - // to verify the wakeup was triggered by |countDown|. - if (std::cv_status::timeout == mCountDownComplete.wait_until(lock, untilTime)) { - return false; - } - } - return true; - } - -private: - uint32_t mFlags; - mutable std::condition_variable mCountDownComplete; - mutable std::mutex mMutex; -}; - } // namespace android diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 69dbfe0280..370b23c7ce 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -455,10 +455,6 @@ public: mFlinger->calculateColorMatrix(fdp->ConsumeFloatingPoint()); mFlinger->updateColorMatrixLocked(); mFlinger->CheckTransactCodeCredentials(fdp->ConsumeIntegral()); - - const CountDownLatch transactionCommittedSignal(fdp->ConsumeIntegral()); - mFlinger->waitForSynchronousTransaction(transactionCommittedSignal); - mFlinger->signalSynchronousTransactions(fdp->ConsumeIntegral()); } void getCompositionPreference() { diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 13389a1ad3..e1a0eeb7fe 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -158,7 +158,6 @@ public: if (!mFlinger) { mFlinger = sp::make(mFactory, SurfaceFlinger::SkipInitialization); } - mFlinger->mAnimationTransactionTimeout = ms2ns(10); } SurfaceFlinger* flinger() { return mFlinger.get(); } @@ -420,7 +419,6 @@ public: auto& getTransactionQueue() { return mFlinger->mLocklessTransactionQueue; } auto& getPendingTransactionQueue() { return mFlinger->mPendingTransactionQueues; } - auto& getTransactionCommittedSignals() { return mFlinger->mTransactionCommittedSignals; } auto setTransactionState( const FrameTimelineInfo& frameTimelineInfo, const Vector& states, @@ -493,10 +491,6 @@ public: return static_cast(mFlinger->mFrameTracer.get()); } - nsecs_t getAnimationTransactionTimeout() const { - return mFlinger->mAnimationTransactionTimeout; - } - /* ------------------------------------------------------------------------ * Read-write access to private data to set up preconditions and assert * post-conditions. diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index b493d113b7..b4030b3617 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -37,7 +37,7 @@ using testing::_; using testing::Return; using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; - +constexpr nsecs_t TRANSACTION_TIMEOUT = s2ns(5); class TransactionApplicationTest : public testing::Test { public: TransactionApplicationTest() { @@ -112,7 +112,7 @@ public: void setupSingle(TransactionInfo& transaction, uint32_t flags, int64_t desiredPresentTime, bool isAutoTimestamp, const FrameTimelineInfo& frameTimelineInfo) { mTransactionNumber++; - transaction.flags |= flags; // ISurfaceComposer::eSynchronous; + transaction.flags |= flags; transaction.desiredPresentTime = desiredPresentTime; transaction.isAutoTimestamp = isAutoTimestamp; transaction.frameTimelineInfo = frameTimelineInfo; @@ -136,11 +136,7 @@ public: // If transaction is synchronous, SF applyTransactionState should time out (5s) wating for // SF to commit the transaction. If this is animation, it should not time out waiting. nsecs_t returnedTime = systemTime(); - if (flags & ISurfaceComposer::eSynchronous) { - EXPECT_GE(returnedTime, applicationTime + mFlinger.getAnimationTransactionTimeout()); - } else { - EXPECT_LE(returnedTime, applicationTime + mFlinger.getAnimationTransactionTimeout()); - } + EXPECT_LE(returnedTime, applicationTime + TRANSACTION_TIMEOUT); // Each transaction should have been placed on the transaction queue auto& transactionQueue = mFlinger.getTransactionQueue(); EXPECT_FALSE(transactionQueue.isEmpty()); @@ -165,13 +161,7 @@ public: transaction.id); nsecs_t returnedTime = systemTime(); - if (flags & ISurfaceComposer::eSynchronous) { - EXPECT_GE(systemTime(), - applicationSentTime + mFlinger.getAnimationTransactionTimeout()); - } else { - EXPECT_LE(returnedTime, - applicationSentTime + mFlinger.getAnimationTransactionTimeout()); - } + EXPECT_LE(returnedTime, applicationSentTime + TRANSACTION_TIMEOUT); // This transaction should have been placed on the transaction queue auto& transactionQueue = mFlinger.getTransactionQueue(); EXPECT_FALSE(transactionQueue.isEmpty()); @@ -204,7 +194,7 @@ public: // This thread should not have been blocked by the above transaction // (5s is the timeout period that applyTransactionState waits for SF to // commit the transaction) - EXPECT_LE(systemTime(), applicationSentTime + mFlinger.getAnimationTransactionTimeout()); + EXPECT_LE(systemTime(), applicationSentTime + TRANSACTION_TIMEOUT); // transaction that would goes to pending transaciton queue. mFlinger.flushTransactionQueues(); @@ -220,13 +210,7 @@ public: // if this is an animation, this thread should be blocked for 5s // in setTransactionState waiting for transactionA to flush. Otherwise, // the transaction should be placed on the pending queue - if (flags & ISurfaceComposer::eSynchronous) { - EXPECT_GE(systemTime(), - applicationSentTime + mFlinger.getAnimationTransactionTimeout()); - } else { - EXPECT_LE(systemTime(), - applicationSentTime + mFlinger.getAnimationTransactionTimeout()); - } + EXPECT_LE(systemTime(), applicationSentTime + TRANSACTION_TIMEOUT); // transaction that would goes to pending transaciton queue. mFlinger.flushTransactionQueues(); @@ -294,26 +278,14 @@ TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) { EXPECT_TRUE(mFlinger.getTransactionQueue().isEmpty()); } -TEST_F(TransactionApplicationTest, NotPlacedOnTransactionQueue_Synchronous) { - NotPlacedOnTransactionQueue(ISurfaceComposer::eSynchronous); -} - TEST_F(TransactionApplicationTest, NotPlacedOnTransactionQueue_SyncInputWindows) { NotPlacedOnTransactionQueue(/*flags*/ 0); } -TEST_F(TransactionApplicationTest, PlaceOnTransactionQueue_Synchronous) { - PlaceOnTransactionQueue(ISurfaceComposer::eSynchronous); -} - TEST_F(TransactionApplicationTest, PlaceOnTransactionQueue_SyncInputWindows) { PlaceOnTransactionQueue(/*flags*/ 0); } -TEST_F(TransactionApplicationTest, BlockWithPriorTransaction_Synchronous) { - BlockedByPriorTransaction(ISurfaceComposer::eSynchronous); -} - TEST_F(TransactionApplicationTest, FromHandle) { sp badHandle; auto ret = mFlinger.fromHandle(badHandle); @@ -329,7 +301,6 @@ public: mFlinger.getTransactionQueue().pop(); } mFlinger.getPendingTransactionQueue().clear(); - mFlinger.getTransactionCommittedSignals().clear(); mFlinger.commitTransactionsLocked(eTransactionMask); mFlinger.mutableCurrentState().layersSortedByZ.clear(); mFlinger.mutableDrawingState().layersSortedByZ.clear(); @@ -361,7 +332,7 @@ public: TransactionInfo createTransactionInfo(const sp& applyToken, const std::vector& states) { TransactionInfo transaction; - const uint32_t kFlags = ISurfaceComposer::eSynchronous; + const uint32_t kFlags = 0; const nsecs_t kDesiredPresentTime = systemTime(); const bool kIsAutoTimestamp = true; const auto kFrameTimelineInfo = FrameTimelineInfo{}; @@ -376,7 +347,6 @@ public: } void setTransactionStates(const std::vector& transactions, - size_t expectedTransactionsApplied, size_t expectedTransactionsPending) { EXPECT_TRUE(mFlinger.getTransactionQueue().isEmpty()); EXPECT_EQ(0u, mFlinger.getPendingTransactionQueue().size()); @@ -392,7 +362,6 @@ public: mFlinger.flushTransactionQueues(); EXPECT_TRUE(mFlinger.getTransactionQueue().isEmpty()); EXPECT_EQ(expectedTransactionsPending, mFlinger.getPendingTransactionQueue().size()); - EXPECT_EQ(expectedTransactionsApplied, mFlinger.getTransactionCommittedSignals().size()); } }; @@ -408,22 +377,19 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesSingleSignaledFromTheQue const sp kApplyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId = 1; - const auto kExpectedTransactionsApplied = 1u; const auto kExpectedTransactionsPending = 0u; const auto signaledTransaction = createTransactionInfo(kApplyToken, {createComposerState(kLayerId, fence(Fence::Status::Signaled), layer_state_t::eBufferChanged)}); - setTransactionStates({signaledTransaction}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({signaledTransaction}, kExpectedTransactionsPending); } TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesSingleUnSignaledFromTheQueue) { const sp kApplyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId = 1; - const auto kExpectedTransactionsApplied = 1u; const auto kExpectedTransactionsPending = 0u; const auto unsignaledTransaction = @@ -433,15 +399,13 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesSingleUnSignaledFromTheQ fence(Fence::Status::Unsignaled), layer_state_t::eBufferChanged), }); - setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); } TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBufferCropChange) { const sp kApplyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId = 1; - const auto kExpectedTransactionsApplied = 0u; const auto kExpectedTransactionsPending = 1u; const auto unsignaledTransaction = @@ -451,15 +415,13 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBu fence(Fence::Status::Unsignaled), layer_state_t::eCropChanged), }); - setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); } TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBufferChangeClubed) { const sp kApplyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId = 1; - const auto kExpectedTransactionsApplied = 0u; const auto kExpectedTransactionsPending = 1u; const auto unsignaledTransaction = @@ -471,15 +433,13 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBu layer_state_t:: eBufferChanged), }); - setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); } TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsInTheQueueSameApplyTokenMultiState) { const sp kApplyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId = 1; - const auto kExpectedTransactionsApplied = 0u; const auto kExpectedTransactionsPending = 1u; const auto mixedTransaction = @@ -492,8 +452,7 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsInTheQueueSameApplyTokenMu fence(Fence::Status::Signaled), layer_state_t::eBufferChanged), }); - setTransactionStates({mixedTransaction}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({mixedTransaction}, kExpectedTransactionsPending); } TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsInTheQueue_MultipleStateTransaction) { @@ -501,7 +460,6 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsInTheQueue_MultipleStateTr IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId1 = 1; const auto kLayerId2 = 2; - const auto kExpectedTransactionsApplied = 0u; const auto kExpectedTransactionsPending = 1u; const auto mixedTransaction = @@ -514,8 +472,7 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsInTheQueue_MultipleStateTr fence(Fence::Status::Signaled), layer_state_t::eBufferChanged), }); - setTransactionStates({mixedTransaction}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({mixedTransaction}, kExpectedTransactionsPending); } TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesSignaledFromTheQueue) { @@ -523,7 +480,6 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesSignaledFromTheQueue) { IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId1 = 1; const auto kLayerId2 = 2; - const auto kExpectedTransactionsApplied = 2u; const auto kExpectedTransactionsPending = 0u; const auto signaledTransaction = @@ -540,8 +496,7 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesSignaledFromTheQueue) { fence(Fence::Status::Signaled), layer_state_t::eBufferChanged), }); - setTransactionStates({signaledTransaction, signaledTransaction2}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({signaledTransaction, signaledTransaction2}, kExpectedTransactionsPending); } TEST_F(LatchUnsignaledAutoSingleLayerTest, @@ -552,7 +507,6 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, const sp kApplyToken3 = sp::make(); const auto kLayerId1 = 1; const auto kLayerId2 = 2; - const auto kExpectedTransactionsApplied = 2u; const auto kExpectedTransactionsPending = 1u; const auto unsignaledTransaction = @@ -579,7 +533,7 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, }); setTransactionStates({unsignaledTransaction, signaledTransaction, signaledTransaction2}, - kExpectedTransactionsApplied, kExpectedTransactionsPending); + kExpectedTransactionsPending); } TEST_F(LatchUnsignaledAutoSingleLayerTest, UnsignaledNotAppliedWhenThereAreSignaled_SignaledFirst) { @@ -589,7 +543,6 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, UnsignaledNotAppliedWhenThereAreSigna const sp kApplyToken3 = sp::make(); const auto kLayerId1 = 1; const auto kLayerId2 = 2; - const auto kExpectedTransactionsApplied = 2u; const auto kExpectedTransactionsPending = 1u; const auto signaledTransaction = @@ -615,7 +568,7 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, UnsignaledNotAppliedWhenThereAreSigna }); setTransactionStates({signaledTransaction, signaledTransaction2, unsignaledTransaction}, - kExpectedTransactionsApplied, kExpectedTransactionsPending); + kExpectedTransactionsPending); } TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsTransactionInTheQueueSameApplyToken) { @@ -623,7 +576,6 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsTransactionInTheQueueSameA IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId1 = 1; const auto kLayerId2 = 2; - const auto kExpectedTransactionsApplied = 1u; const auto kExpectedTransactionsPending = 1u; const auto unsignaledTransaction = @@ -640,7 +592,7 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsTransactionInTheQueueSameA fence(Fence::Status::Signaled), layer_state_t::eBufferChanged), }); - setTransactionStates({unsignaledTransaction, signaledTransaction}, kExpectedTransactionsApplied, + setTransactionStates({unsignaledTransaction, signaledTransaction}, kExpectedTransactionsPending); } @@ -650,7 +602,6 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsTransactionInTheQueue) { const sp kApplyToken2 = sp::make(); const auto kLayerId1 = 1; const auto kLayerId2 = 2; - const auto kExpectedTransactionsApplied = 1u; const auto kExpectedTransactionsPending = 1u; const auto unsignaledTransaction = @@ -668,14 +619,13 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsTransactionInTheQueue) { layer_state_t::eBufferChanged), }); setTransactionStates({unsignaledTransaction, unsignaledTransaction2}, - kExpectedTransactionsApplied, kExpectedTransactionsPending); + kExpectedTransactionsPending); } TEST_F(LatchUnsignaledAutoSingleLayerTest, DontLatchUnsignaledWhenEarlyOffset) { const sp kApplyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId = 1; - const auto kExpectedTransactionsApplied = 0u; const auto kExpectedTransactionsPending = 1u; const auto unsignaledTransaction = @@ -689,8 +639,7 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, DontLatchUnsignaledWhenEarlyOffset) { // Get VsyncModulator out of the default config static_cast(mFlinger.mutableVsyncModulator()->onRefreshRateChangeInitiated()); - setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); } class LatchUnsignaledDisabledTest : public LatchUnsignaledTest { @@ -705,22 +654,19 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_RemovesSignaledFromTheQueue) { const sp kApplyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId = 1; - const auto kExpectedTransactionsApplied = 1u; const auto kExpectedTransactionsPending = 0u; const auto signaledTransaction = createTransactionInfo(kApplyToken, {createComposerState(kLayerId, fence(Fence::Status::Signaled), layer_state_t::eBufferChanged)}); - setTransactionStates({signaledTransaction}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({signaledTransaction}, kExpectedTransactionsPending); } TEST_F(LatchUnsignaledDisabledTest, Flush_KeepsInTheQueue) { const sp kApplyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId = 1; - const auto kExpectedTransactionsApplied = 0u; const auto kExpectedTransactionsPending = 1u; const auto unsignaledTransaction = @@ -730,15 +676,13 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepsInTheQueue) { fence(Fence::Status::Unsignaled), layer_state_t::eBufferChanged), }); - setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); } TEST_F(LatchUnsignaledDisabledTest, Flush_KeepsInTheQueueSameLayerId) { const sp kApplyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId = 1; - const auto kExpectedTransactionsApplied = 0u; const auto kExpectedTransactionsPending = 1u; const auto unsignaledTransaction = @@ -751,8 +695,7 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepsInTheQueueSameLayerId) { fence(Fence::Status::Unsignaled), layer_state_t::eBufferChanged), }); - setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); } TEST_F(LatchUnsignaledDisabledTest, Flush_KeepsInTheQueueDifferentLayerId) { @@ -760,7 +703,6 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepsInTheQueueDifferentLayerId) { IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId1 = 1; const auto kLayerId2 = 2; - const auto kExpectedTransactionsApplied = 0u; const auto kExpectedTransactionsPending = 1u; const auto unsignaledTransaction = @@ -773,8 +715,7 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepsInTheQueueDifferentLayerId) { fence(Fence::Status::Unsignaled), layer_state_t::eBufferChanged), }); - setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); } TEST_F(LatchUnsignaledDisabledTest, Flush_RemovesSignaledFromTheQueue_MultipleLayers) { @@ -782,7 +723,6 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_RemovesSignaledFromTheQueue_MultipleLa IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId1 = 1; const auto kLayerId2 = 2; - const auto kExpectedTransactionsApplied = 2u; const auto kExpectedTransactionsPending = 0u; const auto signaledTransaction = @@ -799,8 +739,7 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_RemovesSignaledFromTheQueue_MultipleLa fence(Fence::Status::Signaled), layer_state_t::eBufferChanged), }); - setTransactionStates({signaledTransaction, signaledTransaction2}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({signaledTransaction, signaledTransaction2}, kExpectedTransactionsPending); } TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheQueueDifferentApplyToken) { @@ -809,7 +748,6 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheQueueDifferentApplyToken) { const sp kApplyToken2 = sp::make(); const auto kLayerId1 = 1; const auto kLayerId2 = 2; - const auto kExpectedTransactionsApplied = 1u; const auto kExpectedTransactionsPending = 1u; const auto unsignaledTransaction = @@ -826,7 +764,7 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheQueueDifferentApplyToken) { fence(Fence::Status::Signaled), layer_state_t::eBufferChanged), }); - setTransactionStates({unsignaledTransaction, signaledTransaction}, kExpectedTransactionsApplied, + setTransactionStates({unsignaledTransaction, signaledTransaction}, kExpectedTransactionsPending); } @@ -835,7 +773,6 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheQueueSameApplyToken) { IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId1 = 1; const auto kLayerId2 = 2; - const auto kExpectedTransactionsApplied = 1u; const auto kExpectedTransactionsPending = 1u; const auto signaledTransaction = @@ -852,7 +789,7 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheQueueSameApplyToken) { fence(Fence::Status::Unsignaled), layer_state_t::eBufferChanged), }); - setTransactionStates({signaledTransaction, unsignaledTransaction}, kExpectedTransactionsApplied, + setTransactionStates({signaledTransaction, unsignaledTransaction}, kExpectedTransactionsPending); } @@ -861,7 +798,6 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheUnsignaledTheQueue) { IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId1 = 1; const auto kLayerId2 = 2; - const auto kExpectedTransactionsApplied = 0u; const auto kExpectedTransactionsPending = 1u; const auto unsignaledTransaction = @@ -879,7 +815,7 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheUnsignaledTheQueue) { layer_state_t::eBufferChanged), }); setTransactionStates({unsignaledTransaction, unsignaledTransaction2}, - kExpectedTransactionsApplied, kExpectedTransactionsPending); + kExpectedTransactionsPending); } class LatchUnsignaledAlwaysTest : public LatchUnsignaledTest { @@ -894,37 +830,32 @@ TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesSignaledFromTheQueue) { const sp kApplyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId = 1; - const auto kExpectedTransactionsApplied = 1u; const auto kExpectedTransactionsPending = 0u; const auto signaledTransaction = createTransactionInfo(kApplyToken, {createComposerState(kLayerId, fence(Fence::Status::Signaled), layer_state_t::eBufferChanged)}); - setTransactionStates({signaledTransaction}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({signaledTransaction}, kExpectedTransactionsPending); } TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueue) { const sp kApplyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId = 1; - const auto kExpectedTransactionsApplied = 1u; const auto kExpectedTransactionsPending = 0u; const auto unsignaledTransaction = createTransactionInfo(kApplyToken, {createComposerState(kLayerId, fence(Fence::Status::Unsignaled), layer_state_t::eBufferChanged)}); - setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); } TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueueSameLayerId) { const sp kApplyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId = 1; - const auto kExpectedTransactionsApplied = 1u; const auto kExpectedTransactionsPending = 0u; const auto mixedTransaction = @@ -933,8 +864,7 @@ TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueueSameLayerId) { layer_state_t::eBufferChanged), createComposerState(kLayerId, fence(Fence::Status::Signaled), layer_state_t::eBufferChanged)}); - setTransactionStates({mixedTransaction}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({mixedTransaction}, kExpectedTransactionsPending); } TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueueDifferentLayerId) { @@ -942,7 +872,6 @@ TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueueDifferentLayerId) { IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId1 = 1; const auto kLayerId2 = 2; - const auto kExpectedTransactionsApplied = 1u; const auto kExpectedTransactionsPending = 0u; const auto mixedTransaction = @@ -951,8 +880,7 @@ TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueueDifferentLayerId) { layer_state_t::eBufferChanged), createComposerState(kLayerId2, fence(Fence::Status::Signaled), layer_state_t::eBufferChanged)}); - setTransactionStates({mixedTransaction}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({mixedTransaction}, kExpectedTransactionsPending); } TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesSignaledFromTheQueue_MultipleLayers) { @@ -960,7 +888,6 @@ TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesSignaledFromTheQueue_MultipleLaye IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId1 = 1; const auto kLayerId2 = 2; - const auto kExpectedTransactionsApplied = 2u; const auto kExpectedTransactionsPending = 0u; const auto signaledTransaction = @@ -977,8 +904,7 @@ TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesSignaledFromTheQueue_MultipleLaye fence(Fence::Status::Signaled), layer_state_t::eBufferChanged), }); - setTransactionStates({signaledTransaction, signaledTransaction2}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({signaledTransaction, signaledTransaction2}, kExpectedTransactionsPending); } TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueueDifferentApplyToken) { @@ -987,7 +913,6 @@ TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueueDifferentApplyToken) const sp kApplyToken2 = sp::make(); const auto kLayerId1 = 1; const auto kLayerId2 = 2; - const auto kExpectedTransactionsApplied = 2u; const auto kExpectedTransactionsPending = 0u; const auto signaledTransaction = @@ -1004,7 +929,7 @@ TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueueDifferentApplyToken) fence(Fence::Status::Unsignaled), layer_state_t::eBufferChanged), }); - setTransactionStates({signaledTransaction, unsignaledTransaction}, kExpectedTransactionsApplied, + setTransactionStates({signaledTransaction, unsignaledTransaction}, kExpectedTransactionsPending); } @@ -1013,7 +938,6 @@ TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesUnsignaledFromTheQueueSameApplyTo IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId1 = 1; const auto kLayerId2 = 2; - const auto kExpectedTransactionsApplied = 2u; const auto kExpectedTransactionsPending = 0u; const auto unsignaledTransaction = @@ -1030,7 +954,7 @@ TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesUnsignaledFromTheQueueSameApplyTo fence(Fence::Status::Signaled), layer_state_t::eBufferChanged), }); - setTransactionStates({unsignaledTransaction, signaledTransaction}, kExpectedTransactionsApplied, + setTransactionStates({unsignaledTransaction, signaledTransaction}, kExpectedTransactionsPending); } @@ -1040,7 +964,6 @@ TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesUnsignaledFromTheQueue) { const sp kApplyToken2 = sp::make(); const auto kLayerId1 = 1; const auto kLayerId2 = 2; - const auto kExpectedTransactionsApplied = 2u; const auto kExpectedTransactionsPending = 0u; const auto unsignaledTransaction = @@ -1058,14 +981,13 @@ TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesUnsignaledFromTheQueue) { layer_state_t::eBufferChanged), }); setTransactionStates({unsignaledTransaction, unsignaledTransaction2}, - kExpectedTransactionsApplied, kExpectedTransactionsPending); + kExpectedTransactionsPending); } TEST_F(LatchUnsignaledAlwaysTest, LatchUnsignaledWhenEarlyOffset) { const sp kApplyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId = 1; - const auto kExpectedTransactionsApplied = 1u; const auto kExpectedTransactionsPending = 0u; const auto unsignaledTransaction = @@ -1079,8 +1001,7 @@ TEST_F(LatchUnsignaledAlwaysTest, LatchUnsignaledWhenEarlyOffset) { // Get VsyncModulator out of the default config static_cast(mFlinger.mutableVsyncModulator()->onRefreshRateChangeInitiated()); - setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied, - kExpectedTransactionsPending); + setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); } } // namespace android -- GitLab From d4e3f3af9d74ab4de578143325e676dd30776ac3 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 27 Sep 2022 14:31:05 -0700 Subject: [PATCH 0336/1310] Improve logs for windows with NO_INPUT_CHANNEL When touched, these windows today generate a misleading log. The log states that the window is not responsive. In reality, these windows simply don't have an input channel. Touches should continue to be dropped for such windows, but the logs should be improved. In this CL, the logic is flattened. We explicitly check for whether the window has NO_INPUT_CHANNEL flag and generate the appropriate log. Bug: 249163063 Test: atest inputflinger_tests Change-Id: I1858c5f10d9a9c6a6916691a72d9e73486480311 --- .../dispatcher/InputDispatcher.cpp | 119 +++++++++--------- .../inputflinger/dispatcher/InputDispatcher.h | 4 +- 2 files changed, 62 insertions(+), 61 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 399153c0d7..4228a1da86 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -524,6 +524,21 @@ std::optional verifyTargetedInjection(const sp& w return {}; } +Point resolveTouchedPosition(const MotionEntry& entry) { + const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE); + // Always dispatch mouse events to cursor position. + if (isFromMouse) { + return Point(static_cast(entry.xCursorPosition), + static_cast(entry.yCursorPosition)); + } + + const int32_t pointerIndex = getMotionEventActionPointerIndex(entry.action); + return Point(static_cast( + entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_X)), + static_cast( + entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y))); +} + } // namespace // --- InputDispatcher --- @@ -2067,18 +2082,8 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) { /* Case 1: New splittable pointer going down, or need target for hover or scroll. */ - - int32_t x; - int32_t y; + const auto [x, y] = resolveTouchedPosition(entry); const int32_t pointerIndex = getMotionEventActionPointerIndex(action); - // Always dispatch mouse events to cursor position. - if (isFromMouse) { - x = int32_t(entry.xCursorPosition); - y = int32_t(entry.yCursorPosition); - } else { - x = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_X)); - y = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y)); - } const bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN; const bool isStylus = isPointerFromStylus(entry, pointerIndex); newTouchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, @@ -2141,43 +2146,7 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( } for (const sp& windowHandle : newTouchedWindows) { - const WindowInfo& info = *windowHandle->getInfo(); - - // Skip spy window targets that are not valid for targeted injection. - if (const auto err = verifyTargetedInjection(windowHandle, entry); err) { - continue; - } - - if (info.inputConfig.test(WindowInfo::InputConfig::PAUSE_DISPATCHING)) { - ALOGI("Not sending touch event to %s because it is paused", - windowHandle->getName().c_str()); - continue; - } - - // Ensure the window has a connection and the connection is responsive - const bool isResponsive = hasResponsiveConnectionLocked(*windowHandle); - if (!isResponsive) { - ALOGW("Not sending touch gesture to %s because it is not responsive", - windowHandle->getName().c_str()); - continue; - } - - // Drop events that can't be trusted due to occlusion - TouchOcclusionInfo occlusionInfo = computeTouchOcclusionInfoLocked(windowHandle, x, y); - if (!isTouchTrustedLocked(occlusionInfo)) { - if (DEBUG_TOUCH_OCCLUSION) { - ALOGD("Stack of obscuring windows during untrusted touch (%d, %d):", x, y); - for (const auto& log : occlusionInfo.debugInfo) { - ALOGD("%s", log.c_str()); - } - } - ALOGW("Dropping untrusted touch event due to %s/%d", - occlusionInfo.obscuringPackage.c_str(), occlusionInfo.obscuringUid); - continue; - } - - // Drop touch events if requested by input feature - if (shouldDropInput(entry, windowHandle)) { + if (!canWindowReceiveMotionLocked(windowHandle, entry)) { continue; } @@ -4607,26 +4576,58 @@ sp InputDispatcher::getFocusedWindowHandleLocked(int displayId return getWindowHandleLocked(focusedToken, displayId); } -bool InputDispatcher::hasResponsiveConnectionLocked(WindowInfoHandle& windowHandle) const { - sp connection = getConnectionLocked(windowHandle.getToken()); - const bool noInputChannel = - windowHandle.getInfo()->inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL); - if (connection != nullptr && noInputChannel) { - ALOGW("%s has feature NO_INPUT_CHANNEL, but it matched to connection %s", - windowHandle.getName().c_str(), connection->inputChannel->getName().c_str()); +bool InputDispatcher::canWindowReceiveMotionLocked(const sp& window, + const MotionEntry& motionEntry) const { + const WindowInfo& info = *window->getInfo(); + + // Skip spy window targets that are not valid for targeted injection. + if (const auto err = verifyTargetedInjection(window, motionEntry); err) { + return false; + } + + if (info.inputConfig.test(WindowInfo::InputConfig::PAUSE_DISPATCHING)) { + ALOGI("Not sending touch event to %s because it is paused", window->getName().c_str()); return false; } + if (info.inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL)) { + ALOGW("Not sending touch gesture to %s because it has config NO_INPUT_CHANNEL", + window->getName().c_str()); + return false; + } + + sp connection = getConnectionLocked(window->getToken()); if (connection == nullptr) { - if (!noInputChannel) { - ALOGI("Could not find connection for %s", windowHandle.getName().c_str()); - } + ALOGW("Not sending touch to %s because there's no corresponding connection", + window->getName().c_str()); return false; } + if (!connection->responsive) { - ALOGW("Window %s is not responsive", windowHandle.getName().c_str()); + ALOGW("Not sending touch to %s because it is not responsive", window->getName().c_str()); return false; } + + // Drop events that can't be trusted due to occlusion + const auto [x, y] = resolveTouchedPosition(motionEntry); + TouchOcclusionInfo occlusionInfo = computeTouchOcclusionInfoLocked(window, x, y); + if (!isTouchTrustedLocked(occlusionInfo)) { + if (DEBUG_TOUCH_OCCLUSION) { + ALOGD("Stack of obscuring windows during untrusted touch (%d, %d):", x, y); + for (const auto& log : occlusionInfo.debugInfo) { + ALOGD("%s", log.c_str()); + } + } + ALOGW("Dropping untrusted touch event due to %s/%d", occlusionInfo.obscuringPackage.c_str(), + occlusionInfo.obscuringUid); + return false; + } + + // Drop touch events if requested by input feature + if (shouldDropInput(motionEntry, window)) { + return false; + } + return true; } diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 630d21a95d..14b046af24 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -384,8 +384,8 @@ private: REQUIRES(mLock); sp getFocusedWindowHandleLocked(int displayId) const REQUIRES(mLock); - bool hasResponsiveConnectionLocked(android::gui::WindowInfoHandle& windowHandle) const - REQUIRES(mLock); + bool canWindowReceiveMotionLocked(const sp& window, + const MotionEntry& motionEntry) const REQUIRES(mLock); // Returns all the input targets (with their respective input channels) from the window handles // passed as argument. -- GitLab From 4af3a561b0832849f9cbb6b73203328e6b21693c Mon Sep 17 00:00:00 2001 From: Rob Carr Date: Fri, 30 Sep 2022 20:46:23 +0000 Subject: [PATCH 0337/1310] libgui fuzzers: Fix build An update to SurfaceControlStats merged through QPR somehow failed to trigger presubmit failure and broke master build. Bug: 250037892 Test: Existing tests pass Change-Id: I3323319e41c3fe1ca2a04d4a0aaf2bd095ae8c59 --- libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp b/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp index c97770bf0e..7829e94e85 100644 --- a/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp +++ b/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp @@ -145,7 +145,8 @@ void BufferQueueFuzzer::invokeBlastBufferQueue() { sp presentFence = new Fence(memfd_create("fd", MFD_ALLOW_SEALING)); SurfaceControlStats controlStats(surface, mFdp.ConsumeIntegral(), mFdp.ConsumeIntegral(), presentFence, previousFence, - mFdp.ConsumeIntegral(), frameStats); + mFdp.ConsumeIntegral(), frameStats, + mFdp.ConsumeIntegral()); stats.push_back(controlStats); } -- GitLab From 9306c38759b6f41a7562b6c5cb9492830088fe75 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 30 Sep 2022 15:30:31 -0700 Subject: [PATCH 0338/1310] Use consistent coordinate extraction code in dispatcher Consolidate the code for extraction of the coordinates from MotionEntry. This allows us to more easily change the behaviour in the future. In this CL, there will be a behaviour change that we will now use mouse cursor position for some of the location resolution instead of pointer. This should be OK to do. Bug: 250029068 Test: atest inputflinger_tests Change-Id: I137c26f881ad993f7a7463c2c05674e48cf864f4 --- .../inputflinger/dispatcher/InputDispatcher.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 4228a1da86..a793f57ade 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -936,13 +936,10 @@ bool InputDispatcher::shouldPruneInboundQueueLocked(const MotionEntry& motionEnt // If the application takes too long to catch up then we drop all events preceding // the touch into the other window. if (isPointerDownEvent && mAwaitedFocusedApplication != nullptr) { - int32_t displayId = motionEntry.displayId; - int32_t x = static_cast( - motionEntry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X)); - int32_t y = static_cast( - motionEntry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y)); - + const int32_t displayId = motionEntry.displayId; + const auto [x, y] = resolveTouchedPosition(motionEntry); const bool isStylus = isPointerFromStylus(motionEntry, 0 /*pointerIndex*/); + sp touchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, nullptr, isStylus); if (touchedWindowHandle != nullptr && @@ -2202,9 +2199,7 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( // Check whether touches should slip outside of the current foreground window. if (maskedAction == AMOTION_EVENT_ACTION_MOVE && entry.pointerCount == 1 && tempTouchState.isSlippery()) { - const int32_t x = int32_t(entry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X)); - const int32_t y = int32_t(entry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y)); - + const auto [x, y] = resolveTouchedPosition(entry); const bool isStylus = isPointerFromStylus(entry, 0 /*pointerIndex*/); sp oldTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle(); -- GitLab From afabcde5a9ac2b77586d61ad4a226718130663c3 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 27 Sep 2022 19:32:43 +0000 Subject: [PATCH 0339/1310] Preserve multi-touch slot state when resetting input mappers The existing pattern in InputReader is that when a mapper is reset, all of its state is cleared and state of the buttons/axes is queried through EventHub to rebuild the current state of the mapper. This approach does not work for multi-touch devices, since there is no way to query the current state of the contacts through the evdev multi-touch protocol. Since we cannot fully rebuild the current state of a multi-touch device, we work around this limitation by never clearing the multi-touch state. Bug: 248506674 Test: atest inputflinger_tests Change-Id: Ic59d9fcb4e13fe262c422825c2485185004b719b --- .../reader/mapper/MultiTouchInputMapper.cpp | 34 +++--- .../reader/mapper/MultiTouchInputMapper.h | 3 +- .../reader/mapper/TouchInputMapper.cpp | 4 + .../reader/mapper/TouchInputMapper.h | 2 + .../inputflinger/tests/InputReader_test.cpp | 110 +++++++++++++++++- .../tests/TestInputListenerMatchers.h | 6 + 6 files changed, 133 insertions(+), 26 deletions(-) diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp index 8f5dc9bfd5..047f068751 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp @@ -38,13 +38,9 @@ void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, s bool usingSlotsProtocol) { mUsingSlotsProtocol = usingSlotsProtocol; mHaveStylus = deviceContext.hasAbsoluteAxis(ABS_MT_TOOL_TYPE); - mSlots = std::vector(slotCount); -} -void MultiTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) { - // Unfortunately there is no way to read the initial contents of the slots. - // So when we reset the accumulator, we must assume they are all zeroes. + mCurrentSlot = -1; if (mUsingSlotsProtocol) { // Query the driver for the current slot index and use it as the initial slot // before we start reading events from the device. It is possible that the @@ -56,22 +52,20 @@ void MultiTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) { // This can cause the touch point to "jump", but at least there will be // no stuck touches. int32_t initialSlot; - status_t status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); - if (status) { - ALOGD("Could not retrieve current multitouch slot index. status=%d", status); - initialSlot = -1; + if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); + status == OK) { + mCurrentSlot = initialSlot; + } else { + ALOGD("Could not retrieve current multi-touch slot index. status=%d", status); } - clearSlots(initialSlot); - } else { - clearSlots(-1); } } -void MultiTouchMotionAccumulator::clearSlots(int32_t initialSlot) { +void MultiTouchMotionAccumulator::resetSlots() { for (Slot& slot : mSlots) { slot.clear(); } - mCurrentSlot = initialSlot; + mCurrentSlot = -1; } void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { @@ -159,7 +153,7 @@ void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { void MultiTouchMotionAccumulator::finishSync() { if (!mUsingSlotsProtocol) { - clearSlots(-1); + resetSlots(); } } @@ -198,10 +192,12 @@ MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext) MultiTouchInputMapper::~MultiTouchInputMapper() {} void MultiTouchInputMapper::reset(nsecs_t when) { - mMultiTouchMotionAccumulator.reset(getDeviceContext()); - - mPointerIdBits.clear(); - + // The evdev multi-touch protocol does not allow userspace applications to query the initial or + // current state of the pointers at any time. This means if we clear our accumulated state when + // resetting the input mapper, there's no way to rebuild the full initial state of the pointers. + // We can only wait for updates to all the pointers and axes. Rather than clearing the state and + // rebuilding the state from scratch, we work around this kernel API limitation by never + // fully clearing any state specific to the multi-touch protocol. TouchInputMapper::reset(when); } diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h index 212c9c9108..75cde8e6db 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h @@ -68,7 +68,6 @@ public: MultiTouchMotionAccumulator(); void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol); - void reset(InputDeviceContext& deviceContext); void process(const RawEvent* rawEvent); void finishSync(); bool hasStylus() const; @@ -85,7 +84,7 @@ private: bool mUsingSlotsProtocol; bool mHaveStylus; - void clearSlots(int32_t initialSlot); + void resetSlots(); void warnIfNotInUse(const RawEvent& event, const Slot& slot); }; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 4cd2cce9f9..5d0f1888ef 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -1495,6 +1495,10 @@ void TouchInputMapper::process(const RawEvent* rawEvent) { } void TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { + if (mDeviceMode == DeviceMode::DISABLED) { + // Only save the last pending state when the device is disabled. + mRawStatesPending.clear(); + } // Push a new state. mRawStatesPending.emplace_back(); diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 31806e12e3..76fdf5dcee 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -320,6 +320,8 @@ protected: int32_t rawVScroll; int32_t rawHScroll; + explicit inline RawState() { clear(); } + void copyFrom(const RawState& other) { when = other.when; readTime = other.readTime; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index f1f1abd8b0..857c705f93 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -6821,6 +6821,32 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenAbsPressureIsPresent_HoversIfItsV toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); } +TEST_F(SingleTouchInputMapperTest, Reset_RecreatesTouchState) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + prepareAxes(POSITION | PRESSURE); + SingleTouchInputMapper& mapper = addMapperAndConfigure(); + + // Set the initial state for the touch pointer. + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 100); + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_Y, 200); + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_PRESSURE, RAW_PRESSURE_MAX); + mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1); + + // Reset the mapper. When the mapper is reset, we expect it to attempt to recreate the touch + // state by reading the current axis values. + mapper.reset(ARBITRARY_TIME); + + // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use + // the recreated touch state to generate a down event. + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPressure(1.f)))); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + TEST_F(SingleTouchInputMapperTest, Process_WhenViewportDisplayIdChanged_TouchIsCanceledAndDeviceIsReset) { addConfigurationProperty("touch.deviceType", "touchScreen"); @@ -6880,10 +6906,17 @@ TEST_F(SingleTouchInputMapperTest, // No events are generated while the viewport is inactive. processMove(mapper, 101, 201); processSync(mapper); - processDown(mapper, 102, 202); + processUp(mapper); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + // Start a new gesture while the viewport is still inactive. + processDown(mapper, 300, 400); + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 300); + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_Y, 400); + mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1); + processSync(mapper); + // Make the viewport active again. The device should resume processing events. viewport->isActive = true; mFakePolicy->updateViewport(*viewport); @@ -6893,11 +6926,10 @@ TEST_F(SingleTouchInputMapperTest, ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); - // Start a new gesture. - processDown(mapper, 100, 200); + // In the next sync, the touch state that was recreated when the device was reset is reported. processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_DOWN))); // No more events. ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); @@ -9429,6 +9461,74 @@ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) { ASSERT_EQ(uint32_t(1), motionArgs.pointerCount); } +TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | ID | SLOT | PRESSURE); + MultiTouchInputMapper& mapper = addMapperAndConfigure(); + + // First finger down. + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, 100, 200); + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_DOWN))); + + // Second finger down. + processSlot(mapper, SECOND_SLOT); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, 300, 400); + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE( + mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(ACTION_POINTER_1_DOWN))); + + // Reset the mapper. When the mapper is reset, we expect the current multi-touch state to be + // preserved. Resetting should not generate any events. + mapper.reset(ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + + // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use + // the existing touch state to generate a down event. + processPosition(mapper, 301, 302); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPressure(1.f)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(ACTION_POINTER_1_DOWN), WithPressure(1.f)))); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + +TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState_NoPointersDown) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | ID | SLOT | PRESSURE); + MultiTouchInputMapper& mapper = addMapperAndConfigure(); + + // First finger touches down and releases. + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, 100, 200); + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_DOWN))); + processId(mapper, INVALID_TRACKING_ID); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE( + mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP))); + + // Reset the mapper. When the mapper is reset, we expect it to restore the latest + // raw state where no pointers are down. + mapper.reset(ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + + // Send an empty sync frame. Since there are no pointers, no events are generated. + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + // --- MultiTouchInputMapperTest_ExternalDevice --- class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest { diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h index 03736a33af..2580405c02 100644 --- a/services/inputflinger/tests/TestInputListenerMatchers.h +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -50,6 +50,12 @@ MATCHER_P2(WithCoords, x, y, "InputEvent with specified coords") { return argX == x && argY == y; } +MATCHER_P(WithPressure, pressure, "InputEvent with specified pressure") { + const auto argPressure = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE); + *result_listener << "expected pressure " << pressure << ", but got " << pressure; + return argPressure; +} + MATCHER_P(WithFlags, flags, "InputEvent with specified flags") { *result_listener << "expected flags " << flags << ", but got " << arg.flags; return arg.flags == flags; -- GitLab From 306633e5896bcb3170c1a84471f34d1aa9c035e9 Mon Sep 17 00:00:00 2001 From: Kean Mariotti Date: Mon, 5 Sep 2022 16:30:47 +0000 Subject: [PATCH 0340/1310] Add bugreport pre-dump functionality Allow apps to trigger the dump of certain critical data, e.g. data stored in short ring buffers that might get lost by the time a bugreport is requested. Then, a bugreport request can specify whether the pre-dumped data should be used. Fixes: 205138504 Test: atest com.android.os.bugreports.tests.BugreportManagerTest Ignore-AOSP-First: depends on changes (surfaceflinger) that cannot go into AOSP Change-Id: I976f2ed3189e83f5bd71dc81e20306527c411d10 --- cmds/dumpstate/Android.bp | 5 +- cmds/dumpstate/DumpstateService.cpp | 30 +++++-- cmds/dumpstate/DumpstateService.h | 7 +- .../binder/android/os/IDumpstate.aidl | 22 +++++- cmds/dumpstate/dumpstate.cpp | 78 +++++++++++++++---- cmds/dumpstate/dumpstate.h | 22 +++++- cmds/dumpstate/main.cpp | 2 +- cmds/dumpstate/tests/dumpstate_smoke_test.cpp | 27 ++++--- cmds/dumpstate/tests/dumpstate_test.cpp | 31 +++++--- services/surfaceflinger/SurfaceFlinger.cpp | 7 -- 10 files changed, 175 insertions(+), 56 deletions(-) diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp index a60972b722..8620a2eb95 100644 --- a/cmds/dumpstate/Android.bp +++ b/cmds/dumpstate/Android.bp @@ -154,7 +154,10 @@ cc_test { "dumpstate.cpp", "tests/dumpstate_test.cpp", ], - static_libs: ["libgmock"], + static_libs: [ + "libc++fs", + "libgmock", + ], test_config: "dumpstate_test.xml", data: [ ":dumpstate_test_fixture", diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp index e42ee0540b..42e9e0f1a7 100644 --- a/cmds/dumpstate/DumpstateService.cpp +++ b/cmds/dumpstate/DumpstateService.cpp @@ -51,7 +51,7 @@ static binder::Status exception(uint32_t code, const std::string& msg, // Creates a bugreport and exits, thus preserving the oneshot nature of the service. // Note: takes ownership of data. -[[noreturn]] static void* dumpstate_thread_main(void* data) { +[[noreturn]] static void* dumpstate_thread_bugreport(void* data) { std::unique_ptr ds_info(static_cast(data)); ds_info->ds->Run(ds_info->calling_uid, ds_info->calling_package); MYLOGD("Finished taking a bugreport. Exiting.\n"); @@ -84,11 +84,28 @@ status_t DumpstateService::Start() { return android::OK; } +binder::Status DumpstateService::preDumpUiData(const std::string&) { + std::lock_guard lock(lock_); + MYLOGI("preDumpUiData()"); + + if (ds_ != nullptr) { + MYLOGE("Error! DumpstateService is currently already being used. Returning."); + return exception(binder::Status::EX_SERVICE_SPECIFIC, + "DumpstateService is already being used"); + } + + ds_ = &(Dumpstate::GetInstance()); + ds_->PreDumpUiData(); + + return binder::Status::ok(); +} + binder::Status DumpstateService::startBugreport(int32_t calling_uid, const std::string& calling_package, android::base::unique_fd bugreport_fd, android::base::unique_fd screenshot_fd, int bugreport_mode, + int bugreport_flags, const sp& listener, bool is_screenshot_requested) { MYLOGI("startBugreport() with mode: %d\n", bugreport_mode); @@ -96,12 +113,12 @@ binder::Status DumpstateService::startBugreport(int32_t calling_uid, // Ensure there is only one bugreport in progress at a time. std::lock_guard lock(lock_); if (ds_ != nullptr) { - MYLOGE("Error! There is already a bugreport in progress. Returning."); + MYLOGE("Error! DumpstateService is currently already being used. Returning."); if (listener != nullptr) { listener->onError(IDumpstateListener::BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS); } return exception(binder::Status::EX_SERVICE_SPECIFIC, - "There is already a bugreport in progress"); + "DumpstateService is already being used"); } // From here on, all conditions that indicate we are done with this incoming request should @@ -123,8 +140,8 @@ binder::Status DumpstateService::startBugreport(int32_t calling_uid, } std::unique_ptr options = std::make_unique(); - options->Initialize(static_cast(bugreport_mode), bugreport_fd, - screenshot_fd, is_screenshot_requested); + options->Initialize(static_cast(bugreport_mode), bugreport_flags, + bugreport_fd, screenshot_fd, is_screenshot_requested); if (bugreport_fd.get() == -1 || (options->do_screenshot && screenshot_fd.get() == -1)) { MYLOGE("Invalid filedescriptor"); @@ -148,10 +165,9 @@ binder::Status DumpstateService::startBugreport(int32_t calling_uid, pthread_t thread; // Initialize dumpstate ds_->Initialize(); - status_t err = pthread_create(&thread, nullptr, dumpstate_thread_main, ds_info); + status_t err = pthread_create(&thread, nullptr, dumpstate_thread_bugreport, ds_info); if (err != 0) { delete ds_info; - ds_info = nullptr; MYLOGE("Could not create a thread"); signalErrorAndExit(listener, IDumpstateListener::BUGREPORT_ERROR_RUNTIME_ERROR); } diff --git a/cmds/dumpstate/DumpstateService.h b/cmds/dumpstate/DumpstateService.h index 3ec8471c04..997999c805 100644 --- a/cmds/dumpstate/DumpstateService.h +++ b/cmds/dumpstate/DumpstateService.h @@ -38,13 +38,16 @@ class DumpstateService : public BinderService, public BnDumpst status_t dump(int fd, const Vector& args) override; + binder::Status preDumpUiData(const std::string& callingPackage) override; + binder::Status startBugreport(int32_t calling_uid, const std::string& calling_package, android::base::unique_fd bugreport_fd, android::base::unique_fd screenshot_fd, int bugreport_mode, - const sp& listener, + int bugreport_flags, const sp& listener, bool is_screenshot_requested) override; - binder::Status cancelBugreport(int32_t calling_uid, const std::string& calling_package); + binder::Status cancelBugreport(int32_t calling_uid, + const std::string& calling_package) override; private: // Dumpstate object which contains all the bugreporting logic. diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl index 0793f0b95f..d4323af3cf 100644 --- a/cmds/dumpstate/binder/android/os/IDumpstate.aidl +++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl @@ -49,6 +49,23 @@ interface IDumpstate { // Default mode. const int BUGREPORT_MODE_DEFAULT = 6; + // Use pre-dumped data. + const int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 1; + + /** + * Speculatively pre-dumps UI data for a bugreport request that might come later. + * + *

Triggers the dump of certain critical UI data, e.g. traces stored in short + * ring buffers that might get lost by the time the actual bugreport is requested. + * + *

{@code startBugreport} will then pick the pre-dumped data if: + * - {@link BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA} is specified. + * - {@code preDumpUiData} and {@code startBugreport} were called by the same UID. + * + * @param callingPackage package of the original application that requested the report. + */ + void preDumpUiData(@utf8InCpp String callingPackage); + /** * Starts a bugreport in the background. * @@ -63,13 +80,14 @@ interface IDumpstate { * @param bugreportFd the file to which the zipped bugreport should be written * @param screenshotFd the file to which screenshot should be written * @param bugreportMode the mode that specifies other run time options; must be one of above + * @param bugreportFlags flags to customize the bugreport generation * @param listener callback for updates; optional * @param isScreenshotRequested indicates screenshot is requested or not */ void startBugreport(int callingUid, @utf8InCpp String callingPackage, FileDescriptor bugreportFd, FileDescriptor screenshotFd, - int bugreportMode, IDumpstateListener listener, - boolean isScreenshotRequested); + int bugreportMode, int bugreportFlags, + IDumpstateListener listener, boolean isScreenshotRequested); /** * Cancels the bugreport currently in progress. diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp index 0b698296b7..7d9bdcb0dd 100644 --- a/cmds/dumpstate/dumpstate.cpp +++ b/cmds/dumpstate/dumpstate.cpp @@ -237,6 +237,7 @@ static const std::string DUMP_NETSTATS_PROTO_TASK = "DUMP NETSTATS PROTO"; static const std::string DUMP_HALS_TASK = "DUMP HALS"; static const std::string DUMP_BOARD_TASK = "dumpstate_board()"; static const std::string DUMP_CHECKINS_TASK = "DUMP CHECKINS"; +static const std::string POST_PROCESS_UI_TRACES_TASK = "POST-PROCESS UI TRACES"; namespace android { namespace os { @@ -1588,16 +1589,16 @@ static void DumpAppInfos(int out_fd = STDOUT_FILENO) { // via the consent they are shown. Ignores other errors that occur while running various // commands. The consent checking is currently done around long running tasks, which happen to // be distributed fairly evenly throughout the function. -static Dumpstate::RunStatus dumpstate() { +Dumpstate::RunStatus Dumpstate::dumpstate() { DurationReporter duration_reporter("DUMPSTATE"); // Enqueue slow functions into the thread pool, if the parallel run is enabled. std::future dump_hals, dump_incident_report, dump_board, dump_checkins, - dump_netstats_report; + dump_netstats_report, post_process_ui_traces; if (ds.dump_pool_) { // Pool was shutdown in DumpstateDefaultAfterCritical method in order to - // drop root user. Restarts it with two threads for the parallel run. - ds.dump_pool_->start(/* thread_counts = */2); + // drop root user. Restarts it. + ds.dump_pool_->start(/* thread_counts = */3); dump_hals = ds.dump_pool_->enqueueTaskWithFd(DUMP_HALS_TASK, &DumpHals, _1); dump_incident_report = ds.dump_pool_->enqueueTask( @@ -1607,6 +1608,8 @@ static Dumpstate::RunStatus dumpstate() { dump_board = ds.dump_pool_->enqueueTaskWithFd( DUMP_BOARD_TASK, &Dumpstate::DumpstateBoard, &ds, _1); dump_checkins = ds.dump_pool_->enqueueTaskWithFd(DUMP_CHECKINS_TASK, &DumpCheckins, _1); + post_process_ui_traces = ds.dump_pool_->enqueueTask( + POST_PROCESS_UI_TRACES_TASK, &Dumpstate::MaybePostProcessUiTraces, &ds); } // Dump various things. Note that anything that takes "long" (i.e. several seconds) should @@ -1731,11 +1734,6 @@ static Dumpstate::RunStatus dumpstate() { DumpFile("BINDER STATS", binder_logs_dir + "/stats"); DumpFile("BINDER STATE", binder_logs_dir + "/state"); - /* Add window and surface trace files. */ - if (!PropertiesHelper::IsUserBuild()) { - ds.AddDir(WMTRACE_DATA_DIR, false); - } - ds.AddDir(SNAPSHOTCTL_LOG_DIR, false); if (ds.dump_pool_) { @@ -1815,6 +1813,14 @@ static Dumpstate::RunStatus dumpstate() { DumpIncidentReport); } + if (ds.dump_pool_) { + WaitForTask(std::move(post_process_ui_traces)); + } else { + RUN_SLOW_FUNCTION_AND_LOG(POST_PROCESS_UI_TRACES_TASK, MaybePostProcessUiTraces); + } + + MaybeAddUiTracesToZip(); + return Dumpstate::RunStatus::OK; } @@ -2783,9 +2789,11 @@ static void LogDumpOptions(const Dumpstate::DumpOptions& options) { } void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode, + int bugreport_flags, const android::base::unique_fd& bugreport_fd_in, const android::base::unique_fd& screenshot_fd_in, bool is_screenshot_requested) { + this->use_predumped_ui_data = bugreport_flags & BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA; // Duplicate the fds because the passed in fds don't outlive the binder transaction. bugreport_fd.reset(fcntl(bugreport_fd_in.get(), F_DUPFD_CLOEXEC, 0)); screenshot_fd.reset(fcntl(screenshot_fd_in.get(), F_DUPFD_CLOEXEC, 0)); @@ -2912,6 +2920,10 @@ void Dumpstate::Cancel() { } } +void Dumpstate::PreDumpUiData() { + MaybeSnapshotUiTraces(); +} + /* * Dumps relevant information to a bugreport based on the given options. * @@ -3103,9 +3115,9 @@ Dumpstate::RunStatus Dumpstate::RunInternal(int32_t calling_uid, // The trace file is added to the zip by MaybeAddSystemTraceToZip(). MaybeSnapshotSystemTrace(); - // If a winscope trace is running, snapshot it now. It will be pulled into bugreport later - // from WMTRACE_DATA_DIR. - MaybeSnapshotWinTrace(); + // Snapshot the UI traces now (if running). + // The trace files will be added to bugreport later. + MaybeSnapshotUiTraces(); } onUiIntensiveBugreportDumpsFinished(calling_uid); MaybeCheckUserConsent(calling_uid, calling_package); @@ -3219,15 +3231,53 @@ void Dumpstate::MaybeSnapshotSystemTrace() { // file in the later stages. } -void Dumpstate::MaybeSnapshotWinTrace() { +void Dumpstate::MaybeSnapshotUiTraces() { + if (PropertiesHelper::IsUserBuild() || options_->use_predumped_ui_data) { + return; + } + // Currently WindowManagerService and InputMethodManagerSerivice support WinScope protocol. - for (const auto& service : {"window", "input_method"}) { + for (const auto& service : {"input_method", "window"}) { RunCommand( // Empty name because it's not intended to be classified as a bugreport section. // Actual tracing files can be found in "/data/misc/wmtrace/" in the bugreport. "", {"cmd", service, "tracing", "save-for-bugreport"}, CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build()); } + + static const auto SURFACEFLINGER_COMMAND_SAVE_ALL_TRACES = std::vector { + "service", "call", "SurfaceFlinger", "1042" + }; + // Empty name because it's not intended to be classified as a bugreport section. + // Actual tracing files can be found in "/data/misc/wmtrace/" in the bugreport. + RunCommand( + "", SURFACEFLINGER_COMMAND_SAVE_ALL_TRACES, + CommandOptions::WithTimeout(10).Always().AsRoot().RedirectStderr().Build()); +} + +void Dumpstate::MaybePostProcessUiTraces() { + if (PropertiesHelper::IsUserBuild()) { + return; + } + + RunCommand( + // Empty name because it's not intended to be classified as a bugreport section. + // Actual tracing files can be found in "/data/misc/wmtrace/" in the bugreport. + "", { + "/system/xbin/su", "system", + "/system/bin/layertracegenerator", + "/data/misc/wmtrace/transactions_trace.winscope", + "/data/misc/wmtrace/layers_trace_from_transactions.winscope" + }, + CommandOptions::WithTimeout(120).Always().RedirectStderr().Build()); +} + +void Dumpstate::MaybeAddUiTracesToZip() { + if (PropertiesHelper::IsUserBuild()) { + return; + } + + ds.AddDir(WMTRACE_DATA_DIR, false); } void Dumpstate::onUiIntensiveBugreportDumpsFinished(int32_t calling_uid) { diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h index ee6b1aee18..1ab46c87af 100644 --- a/cmds/dumpstate/dumpstate.h +++ b/cmds/dumpstate/dumpstate.h @@ -204,6 +204,12 @@ class Dumpstate { BUGREPORT_DEFAULT = android::os::IDumpstate::BUGREPORT_MODE_DEFAULT }; + // The flags used to customize bugreport requests. + enum BugreportFlag { + BUGREPORT_USE_PREDUMPED_UI_DATA = + android::os::IDumpstate::BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA + }; + static android::os::dumpstate::CommandOptions DEFAULT_DUMPSYS; static Dumpstate& GetInstance(); @@ -333,6 +339,12 @@ class Dumpstate { struct DumpOptions; + /** + * Pre-dump critical UI data, e.g. data stored in short ring buffers that might get lost + * by the time the actual bugreport is requested. + */ + void PreDumpUiData(); + /* * Main entry point for running a complete bugreport. * @@ -396,6 +408,8 @@ class Dumpstate { // TODO(b/148168577) get rid of the AIDL values, replace them with the HAL values instead. // The HAL is actually an API surface that can be validated, while the AIDL is not (@hide). BugreportMode bugreport_mode = Dumpstate::BugreportMode::BUGREPORT_DEFAULT; + // Will use data collected through a previous call to PreDumpUiData(). + bool use_predumped_ui_data; // File descriptor to output zip file. Takes precedence over out_dir. android::base::unique_fd bugreport_fd; // File descriptor to screenshot file. @@ -414,7 +428,8 @@ class Dumpstate { RunStatus Initialize(int argc, char* argv[]); /* Initializes options from the requested mode. */ - void Initialize(BugreportMode bugreport_mode, const android::base::unique_fd& bugreport_fd, + void Initialize(BugreportMode bugreport_mode, int bugreport_flags, + const android::base::unique_fd& bugreport_fd, const android::base::unique_fd& screenshot_fd, bool is_screenshot_requested); @@ -532,10 +547,13 @@ class Dumpstate { RunStatus RunInternal(int32_t calling_uid, const std::string& calling_package); RunStatus DumpstateDefaultAfterCritical(); + RunStatus dumpstate(); void MaybeTakeEarlyScreenshot(); void MaybeSnapshotSystemTrace(); - void MaybeSnapshotWinTrace(); + void MaybeSnapshotUiTraces(); + void MaybePostProcessUiTraces(); + void MaybeAddUiTracesToZip(); void onUiIntensiveBugreportDumpsFinished(int32_t calling_uid); diff --git a/cmds/dumpstate/main.cpp b/cmds/dumpstate/main.cpp index ec89c0dd6e..a634f9371f 100644 --- a/cmds/dumpstate/main.cpp +++ b/cmds/dumpstate/main.cpp @@ -56,7 +56,7 @@ int main(int argc, char* argv[]) { MYLOGE("Unable to start 'dumpstate' service: %d", ret); exit(1); } - MYLOGI("'dumpstate' service started and will wait for a call to startBugreport()"); + MYLOGI("'dumpstate' service started and will wait for a call"); // Waits forever for an incoming connection. // TODO(b/111441001): should this time out? diff --git a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp index 28e5ee2ca9..b091c8e297 100644 --- a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp +++ b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp @@ -497,14 +497,17 @@ TEST_F(DumpstateBinderTest, Baseline) { // Prepare arguments unique_fd bugreport_fd(OpenForWrite("/bugreports/tmp.zip")); unique_fd screenshot_fd(OpenForWrite("/bugreports/tmp.png")); + int flags = 0; EXPECT_NE(bugreport_fd.get(), -1); EXPECT_NE(screenshot_fd.get(), -1); sp listener(new DumpstateListener(dup(fileno(stdout)))); android::binder::Status status = - ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd), std::move(screenshot_fd), - Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, listener, true); + ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd), + std::move(screenshot_fd), + Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags, listener, + true); // startBugreport is an async call. Verify binder call succeeded first, then wait till listener // gets expected callbacks. EXPECT_TRUE(status.isOk()); @@ -532,6 +535,7 @@ TEST_F(DumpstateBinderTest, ServiceDies_OnInvalidInput) { // Prepare arguments unique_fd bugreport_fd(OpenForWrite("/data/local/tmp/tmp.zip")); unique_fd screenshot_fd(OpenForWrite("/data/local/tmp/tmp.png")); + int flags = 0; EXPECT_NE(bugreport_fd.get(), -1); EXPECT_NE(screenshot_fd.get(), -1); @@ -539,9 +543,9 @@ TEST_F(DumpstateBinderTest, ServiceDies_OnInvalidInput) { // Call startBugreport with bad arguments. sp listener(new DumpstateListener(dup(fileno(stdout)))); android::binder::Status status = - ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd), std::move(screenshot_fd), - 2000, // invalid bugreport mode - listener, false); + ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd), + std::move(screenshot_fd), 2000, // invalid bugreport mode + flags, listener, false); EXPECT_EQ(listener->getErrorCode(), IDumpstateListener::BUGREPORT_ERROR_INVALID_INPUT); // The service should have died, freeing itself up for a new invocation. @@ -563,6 +567,7 @@ TEST_F(DumpstateBinderTest, SimultaneousBugreportsNotAllowed) { unique_fd bugreport_fd2(dup(bugreport_fd.get())); unique_fd screenshot_fd(OpenForWrite("/data/local/tmp/tmp.png")); unique_fd screenshot_fd2(dup(screenshot_fd.get())); + int flags = 0; EXPECT_NE(bugreport_fd.get(), -1); EXPECT_NE(bugreport_fd2.get(), -1); @@ -571,14 +576,18 @@ TEST_F(DumpstateBinderTest, SimultaneousBugreportsNotAllowed) { sp listener1(new DumpstateListener(dup(fileno(stdout)))); android::binder::Status status = - ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd), std::move(screenshot_fd), - Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, listener1, true); + ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd), + std::move(screenshot_fd), + Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags, listener1, + true); EXPECT_TRUE(status.isOk()); // try to make another call to startBugreport. This should fail. sp listener2(new DumpstateListener(dup(fileno(stdout)))); - status = ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd2), std::move(screenshot_fd2), - Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, listener2, true); + status = ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd2), + std::move(screenshot_fd2), + Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags, + listener2, true); EXPECT_FALSE(status.isOk()); WaitTillExecutionComplete(listener2.get()); EXPECT_EQ(listener2->getErrorCode(), diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp index 70b4e5c0d8..1ffcafa544 100644 --- a/cmds/dumpstate/tests/dumpstate_test.cpp +++ b/cmds/dumpstate/tests/dumpstate_test.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -237,7 +238,7 @@ TEST_F(DumpOptionsTest, InitializeAdbShellBugreport) { } TEST_F(DumpOptionsTest, InitializeFullBugReport) { - options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_FULL, fd, fd, true); + options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true); EXPECT_TRUE(options_.do_screenshot); // Other options retain default values @@ -251,7 +252,7 @@ TEST_F(DumpOptionsTest, InitializeFullBugReport) { } TEST_F(DumpOptionsTest, InitializeInteractiveBugReport) { - options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, fd, fd, true); + options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, 0, fd, fd, true); EXPECT_TRUE(options_.do_progress_updates); EXPECT_TRUE(options_.do_screenshot); @@ -265,7 +266,7 @@ TEST_F(DumpOptionsTest, InitializeInteractiveBugReport) { } TEST_F(DumpOptionsTest, InitializeRemoteBugReport) { - options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_REMOTE, fd, fd, false); + options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_REMOTE, 0, fd, fd, false); EXPECT_TRUE(options_.is_remote_mode); EXPECT_FALSE(options_.do_vibrate); EXPECT_FALSE(options_.do_screenshot); @@ -279,7 +280,7 @@ TEST_F(DumpOptionsTest, InitializeRemoteBugReport) { } TEST_F(DumpOptionsTest, InitializeWearBugReport) { - options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WEAR, fd, fd, true); + options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WEAR, 0, fd, fd, true); EXPECT_TRUE(options_.do_screenshot); EXPECT_TRUE(options_.do_progress_updates); @@ -294,7 +295,7 @@ TEST_F(DumpOptionsTest, InitializeWearBugReport) { } TEST_F(DumpOptionsTest, InitializeTelephonyBugReport) { - options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_TELEPHONY, fd, fd, false); + options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_TELEPHONY, 0, fd, fd, false); EXPECT_FALSE(options_.do_screenshot); EXPECT_TRUE(options_.telephony_only); EXPECT_TRUE(options_.do_progress_updates); @@ -309,7 +310,7 @@ TEST_F(DumpOptionsTest, InitializeTelephonyBugReport) { } TEST_F(DumpOptionsTest, InitializeWifiBugReport) { - options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WIFI, fd, fd, false); + options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WIFI, 0, fd, fd, false); EXPECT_FALSE(options_.do_screenshot); EXPECT_TRUE(options_.wifi_only); @@ -982,6 +983,19 @@ TEST_F(DumpstateTest, DumpPool_withParallelRunDisabled_isNull) { EXPECT_FALSE(ds.dump_pool_); } +TEST_F(DumpstateBaseTest, PreDumpUiData) { + // SurfaceFlinger's transactions trace is always enabled, i.e. it is always pre-dumped + static const auto kTransactionsTrace = + std::filesystem::path {"/data/misc/wmtrace/transactions_trace.winscope"}; + + std::system(("rm " + kTransactionsTrace.string()).c_str()); + EXPECT_FALSE(std::filesystem::exists(kTransactionsTrace)); + + Dumpstate& ds_ = Dumpstate::GetInstance(); + ds_.PreDumpUiData(); + EXPECT_TRUE(std::filesystem::exists(kTransactionsTrace)); +} + class ZippedBugReportStreamTest : public DumpstateBaseTest { public: void SetUp() { @@ -1045,11 +1059,6 @@ TEST_F(ZippedBugReportStreamTest, StreamLimitedOnlyReport) { VerifyEntry(handle_, bugreport_txt_name, &entry); } -class DumpstateServiceTest : public DumpstateBaseTest { - public: - DumpstateService dss; -}; - class ProgressTest : public DumpstateBaseTest { public: Progress GetInstance(int32_t max, double growth_factor, const std::string& path = "") { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index f3551ae080..b20f54dbdb 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4974,13 +4974,6 @@ status_t SurfaceFlinger::doDump(int fd, const DumpArgs& args, bool asProto) { } status_t SurfaceFlinger::dumpCritical(int fd, const DumpArgs&, bool asProto) { - if (asProto) { - mLayerTracing.writeToFile(); - if (mTransactionTracing) { - mTransactionTracing->writeToFile(); - } - } - return doDump(fd, DumpArgs(), asProto); } -- GitLab From 9a11a2ea839d5a9dd692211693692f65bb1da2eb Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Tue, 4 Oct 2022 12:46:21 -0400 Subject: [PATCH 0341/1310] Restore display decoration if device supports it This was previously disabled due to a bug (somewhere), but the bug seems to have been fixed. Restore the intended behavior. Bug: 241278870 Test: manual Change-Id: I0ed7c5f4cd35b290d0cc283d3a6d7844ba00f4ba --- libs/gui/SurfaceComposerClient.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 751721e2a8..6ddadbaf5b 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2714,8 +2714,7 @@ std::optional SurfaceComposerClient::getDisplayDecorat ComposerServiceAIDL::getComposerService()->getDisplayDecorationSupport(displayToken, &gsupport); std::optional support; - // TODO (b/241277093): Remove `false && ` once b/241278870 is fixed. - if (false && status.isOk() && gsupport.has_value()) { + if (status.isOk() && gsupport.has_value()) { support.emplace(DisplayDecorationSupport{ .format = static_cast( -- GitLab From 36dced862c21c7cfdb73ff9aa85e5c2b45739add Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Fri, 2 Sep 2022 09:24:00 -0700 Subject: [PATCH 0342/1310] SF: Refactor setter for DM and override policy Move DisplayDevice::setRefreshRatePolicy to RefreshRateConfigs, and merge the repetitive set{DisplayManager,Override}Policy helpers. Improve type correctness of the parameters and return value. Bug: 241285191 Test: libsurfaceflinger_unittest Change-Id: Iee87316e5702282b828bc3f28cd7d30041030ed5 --- services/surfaceflinger/DisplayDevice.cpp | 22 --- services/surfaceflinger/DisplayDevice.h | 6 - .../Scheduler/RefreshRateConfigs.cpp | 94 +++++++--- .../Scheduler/RefreshRateConfigs.h | 33 ++-- services/surfaceflinger/SurfaceFlinger.cpp | 62 +++--- services/surfaceflinger/SurfaceFlinger.h | 8 +- .../surfaceflinger_scheduler_fuzzer.cpp | 23 ++- .../unittests/RefreshRateConfigsTest.cpp | 176 ++++++++++++------ 8 files changed, 257 insertions(+), 167 deletions(-) diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index a57af092ea..c63d57f6ee 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -216,7 +216,6 @@ status_t DisplayDevice::initiateModeChange(const ActiveModeInfo& info, to_string(getId()).c_str()); return BAD_VALUE; } - mNumModeSwitchesInPolicy++; mUpcomingActiveMode = info; ATRACE_INT(mActiveModeFPSHwcTrace.c_str(), info.mode->getFps().getIntValue()); return mHwComposer.setActiveModeWithConstraints(getPhysicalId(), info.mode->getHwcId(), @@ -498,27 +497,6 @@ void DisplayDevice::clearDesiredActiveModeState() { mDesiredActiveModeChanged = false; } -status_t DisplayDevice::setRefreshRatePolicy( - const std::optional& policy, bool overridePolicy) { - const auto oldPolicy = mRefreshRateConfigs->getCurrentPolicy(); - const status_t setPolicyResult = overridePolicy - ? mRefreshRateConfigs->setOverridePolicy(policy) - : mRefreshRateConfigs->setDisplayManagerPolicy(*policy); - - if (setPolicyResult == OK) { - const int numModeChanges = mNumModeSwitchesInPolicy.exchange(0); - - ALOGI("Display %s policy changed\n" - "Previous: {%s}\n" - "Current: {%s}\n" - "%d mode changes were performed under the previous policy", - to_string(getId()).c_str(), oldPolicy.toString().c_str(), - policy ? policy->toString().c_str() : "null", numModeChanges); - } - - return setPolicyResult; -} - std::atomic DisplayDeviceState::sNextSequenceId(1); } // namespace android diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index e155ca1a12..06a812b6f4 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -236,10 +236,6 @@ public: nsecs_t getVsyncPeriodFromHWC() const; - status_t setRefreshRatePolicy( - const std::optional& policy, - bool overridePolicy); - // release HWC resources (if any) for removable displays void disconnect(); @@ -287,8 +283,6 @@ private: TracedOrdinal mDesiredActiveModeChanged GUARDED_BY(mActiveModeLock) = {"DesiredActiveModeChanged", false}; ActiveModeInfo mUpcomingActiveMode GUARDED_BY(kMainThreadContext); - - std::atomic_int mNumModeSwitchesInPolicy = 0; }; struct DisplayDeviceState { diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp index fb505881fc..c10b817467 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include "../SurfaceFlingerProperties.h" @@ -117,6 +118,20 @@ bool canModesSupportFrameRateOverride(const std::vector& so return false; } +std::string toString(const RefreshRateConfigs::PolicyVariant& policy) { + using namespace std::string_literals; + + return ftl::match( + policy, + [](const RefreshRateConfigs::DisplayManagerPolicy& policy) { + return "DisplayManagerPolicy"s + policy.toString(); + }, + [](const RefreshRateConfigs::OverridePolicy& policy) { + return "OverridePolicy"s + policy.toString(); + }, + [](RefreshRateConfigs::NoOverridePolicy) { return "NoOverridePolicy"s; }); +} + } // namespace struct RefreshRateConfigs::RefreshRateScoreComparator { @@ -874,35 +889,60 @@ bool RefreshRateConfigs::isPolicyValidLocked(const Policy& policy) const { policy.appRequestRange.max >= policy.primaryRange.max; } -status_t RefreshRateConfigs::setDisplayManagerPolicy(const Policy& policy) { - std::lock_guard lock(mLock); - if (!isPolicyValidLocked(policy)) { - ALOGE("Invalid refresh rate policy: %s", policy.toString().c_str()); - return BAD_VALUE; - } - mGetRankedRefreshRatesCache.reset(); - Policy previousPolicy = *getCurrentPolicyLocked(); - mDisplayManagerPolicy = policy; - if (*getCurrentPolicyLocked() == previousPolicy) { - return CURRENT_POLICY_UNCHANGED; - } - constructAvailableRefreshRates(); - return NO_ERROR; -} +auto RefreshRateConfigs::setPolicy(const PolicyVariant& policy) -> SetPolicyResult { + Policy oldPolicy; + { + std::lock_guard lock(mLock); + oldPolicy = *getCurrentPolicyLocked(); + + const bool valid = ftl::match( + policy, + [this](const auto& policy) { + ftl::FakeGuard guard(mLock); + if (!isPolicyValidLocked(policy)) { + ALOGE("Invalid policy: %s", policy.toString().c_str()); + return false; + } -status_t RefreshRateConfigs::setOverridePolicy(const std::optional& policy) { - std::lock_guard lock(mLock); - if (policy && !isPolicyValidLocked(*policy)) { - return BAD_VALUE; - } - mGetRankedRefreshRatesCache.reset(); - Policy previousPolicy = *getCurrentPolicyLocked(); - mOverridePolicy = policy; - if (*getCurrentPolicyLocked() == previousPolicy) { - return CURRENT_POLICY_UNCHANGED; + using T = std::decay_t; + + if constexpr (std::is_same_v) { + mDisplayManagerPolicy = policy; + } else { + static_assert(std::is_same_v); + mOverridePolicy = policy; + } + return true; + }, + [this](NoOverridePolicy) { + ftl::FakeGuard guard(mLock); + mOverridePolicy.reset(); + return true; + }); + + if (!valid) { + return SetPolicyResult::Invalid; + } + + mGetRankedRefreshRatesCache.reset(); + + if (*getCurrentPolicyLocked() == oldPolicy) { + return SetPolicyResult::Unchanged; + } + constructAvailableRefreshRates(); } - constructAvailableRefreshRates(); - return NO_ERROR; + + const auto displayId = getActiveMode().getPhysicalDisplayId(); + const unsigned numModeChanges = std::exchange(mNumModeSwitchesInPolicy, 0u); + + ALOGI("Display %s policy changed\n" + "Previous: %s\n" + "Current: %s\n" + "%u mode changes were performed under the previous policy", + to_string(displayId).c_str(), oldPolicy.toString().c_str(), toString(policy).c_str(), + numModeChanges); + + return SetPolicyResult::Changed; } const RefreshRateConfigs::Policy* RefreshRateConfigs::getCurrentPolicyLocked() const { diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h index 8b89104c5c..2c2e34ac13 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h +++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h @@ -21,6 +21,7 @@ #include #include #include +#include #include @@ -67,8 +68,7 @@ public: static constexpr nsecs_t MARGIN_FOR_PERIOD_CALCULATION = std::chrono::nanoseconds(800us).count(); - struct Policy { - private: + class Policy { static constexpr int kAllowGroupSwitchingDefault = false; public: @@ -118,23 +118,28 @@ public: std::string toString() const; }; - // Return code set*Policy() to indicate the current policy is unchanged. - static constexpr int CURRENT_POLICY_UNCHANGED = 1; + enum class SetPolicyResult { Invalid, Unchanged, Changed }; // We maintain the display manager policy and the override policy separately. The override // policy is used by CTS tests to get a consistent device state for testing. While the override // policy is set, it takes precedence over the display manager policy. Once the override policy // is cleared, we revert to using the display manager policy. + struct DisplayManagerPolicy : Policy { + using Policy::Policy; + }; + + struct OverridePolicy : Policy { + using Policy::Policy; + }; + + struct NoOverridePolicy {}; + + using PolicyVariant = std::variant; + + SetPolicyResult setPolicy(const PolicyVariant&) EXCLUDES(mLock) REQUIRES(kMainThreadContext); + + void onModeChangeInitiated() REQUIRES(kMainThreadContext) { mNumModeSwitchesInPolicy++; } - // Sets the display manager policy to choose refresh rates. The return value will be: - // - A negative value if the policy is invalid or another error occurred. - // - NO_ERROR if the policy was successfully updated, and the current policy is different from - // what it was before the call. - // - CURRENT_POLICY_UNCHANGED if the policy was successfully updated, but the current policy - // is the same as it was before the call. - status_t setDisplayManagerPolicy(const Policy& policy) EXCLUDES(mLock); - // Sets the override policy. See setDisplayManagerPolicy() for the meaning of the return value. - status_t setOverridePolicy(const std::optional& policy) EXCLUDES(mLock); // Gets the current policy, which will be the override policy if active, and the display manager // policy otherwise. Policy getCurrentPolicy() const EXCLUDES(mLock); @@ -418,6 +423,8 @@ private: Policy mDisplayManagerPolicy GUARDED_BY(mLock); std::optional mOverridePolicy GUARDED_BY(mLock); + unsigned mNumModeSwitchesInPolicy GUARDED_BY(kMainThreadContext) = 0; + mutable std::mutex mLock; // A sorted list of known frame rates that a Heuristic layer will choose diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index ddd0f4c1d8..1e39f629ce 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1105,7 +1105,7 @@ status_t SurfaceFlinger::setActiveModeFromBackdoor(const spschedule([=]() -> status_t { + auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t { const auto displayOpt = FTL_FAKE_GUARD(mStateLock, ftl::find_if(mPhysicalDisplays, @@ -1130,13 +1130,16 @@ status_t SurfaceFlinger::setActiveModeFromBackdoor(const sprefreshRateConfigs().getCurrentPolicy().allowGroupSwitching; - const scheduler::RefreshRateConfigs::Policy policy{modeId, allowGroupSwitching, {fps, fps}}; - constexpr bool kOverridePolicy = false; - return setDesiredDisplayModeSpecsInternal(display, policy, kOverridePolicy); + const scheduler::RefreshRateConfigs::DisplayManagerPolicy policy{modeId, + allowGroupSwitching, + {fps, fps}}; + + return setDesiredDisplayModeSpecsInternal(display, policy); }); return future.get(); @@ -1273,6 +1276,8 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { ALOGW("initiateModeChange failed: %d", status); continue; } + + display->refreshRateConfigs().onModeChangeInitiated(); mScheduler->onNewVsyncPeriodChangeTimeline(outTimeline); if (outTimeline.refreshRequired) { @@ -5866,7 +5871,7 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r case 1036: { if (data.readInt32() > 0) { // turn on return mScheduler - ->schedule([this] { + ->schedule([this]() FTL_FAKE_GUARD(kMainThreadContext) { const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()); @@ -5876,24 +5881,21 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r // defaultMode. The defaultMode doesn't matter for the override // policy though, since we set allowGroupSwitching to true, so it's // not a problem. - scheduler::RefreshRateConfigs::Policy overridePolicy; + scheduler::RefreshRateConfigs::OverridePolicy overridePolicy; overridePolicy.defaultMode = display->refreshRateConfigs() .getDisplayManagerPolicy() .defaultMode; overridePolicy.allowGroupSwitching = true; - constexpr bool kOverridePolicy = true; - return setDesiredDisplayModeSpecsInternal(display, overridePolicy, - kOverridePolicy); + return setDesiredDisplayModeSpecsInternal(display, overridePolicy); }) .get(); } else { // turn off return mScheduler - ->schedule([this] { + ->schedule([this]() FTL_FAKE_GUARD(kMainThreadContext) { const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()); - constexpr bool kOverridePolicy = true; - return setDesiredDisplayModeSpecsInternal(display, {}, - kOverridePolicy); + return setDesiredDisplayModeSpecsInternal( + display, scheduler::RefreshRateConfigs::NoOverridePolicy{}); }) .get(); } @@ -6732,7 +6734,7 @@ std::optional SurfaceFlinger::getPreferredDisplayMode( status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( const sp& display, - const std::optional& policy, bool overridePolicy) { + const scheduler::RefreshRateConfigs::PolicyVariant& policy) { Mutex::Autolock lock(mStateLock); if (mDebugDisplayModeSetByBackdoor) { @@ -6740,23 +6742,24 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( return NO_ERROR; } - const status_t setPolicyResult = display->setRefreshRatePolicy(policy, overridePolicy); - if (setPolicyResult < 0) { - return BAD_VALUE; - } - if (setPolicyResult == scheduler::RefreshRateConfigs::CURRENT_POLICY_UNCHANGED) { - return NO_ERROR; - } + auto& configs = display->refreshRateConfigs(); + using SetPolicyResult = scheduler::RefreshRateConfigs::SetPolicyResult; - const scheduler::RefreshRateConfigs::Policy currentPolicy = - display->refreshRateConfigs().getCurrentPolicy(); + switch (configs.setPolicy(policy)) { + case SetPolicyResult::Invalid: + return BAD_VALUE; + case SetPolicyResult::Unchanged: + return NO_ERROR; + case SetPolicyResult::Changed: + break; + } + const scheduler::RefreshRateConfigs::Policy currentPolicy = configs.getCurrentPolicy(); ALOGV("Setting desired display mode specs: %s", currentPolicy.toString().c_str()); // TODO(b/140204874): Leave the event in until we do proper testing with all apps that might // be depending in this callback. - const auto activeModePtr = display->refreshRateConfigs().getActiveModePtr(); - if (isDisplayActiveLocked(display)) { + if (const auto activeModePtr = configs.getActiveModePtr(); isDisplayActiveLocked(display)) { mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, activeModePtr); toggleKernelIdleTimer(); } else { @@ -6776,7 +6779,7 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( ALOGV("Switching to Scheduler preferred mode %d (%s)", preferredModeId.value(), to_string(preferredMode->getFps()).c_str()); - if (!display->refreshRateConfigs().isModeAllowed(preferredModeId)) { + if (!configs.isModeAllowed(preferredModeId)) { ALOGE("%s: Preferred mode %d is disallowed", __func__, preferredModeId.value()); return INVALID_OPERATION; } @@ -6795,7 +6798,7 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecs( return BAD_VALUE; } - auto future = mScheduler->schedule([=]() -> status_t { + auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t { const auto display = FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(displayToken)); if (!display) { ALOGE("Attempt to set desired display modes for invalid display token %p", @@ -6805,16 +6808,15 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecs( ALOGW("Attempt to set desired display modes for virtual display"); return INVALID_OPERATION; } else { - using Policy = scheduler::RefreshRateConfigs::Policy; + using Policy = scheduler::RefreshRateConfigs::DisplayManagerPolicy; const Policy policy{DisplayModeId(defaultMode), allowGroupSwitching, {Fps::fromValue(primaryRefreshRateMin), Fps::fromValue(primaryRefreshRateMax)}, {Fps::fromValue(appRequestRefreshRateMin), Fps::fromValue(appRequestRefreshRateMax)}}; - constexpr bool kOverridePolicy = false; - return setDesiredDisplayModeSpecsInternal(display, policy, kOverridePolicy); + return setDesiredDisplayModeSpecsInternal(display, policy); } }); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 378c9f027b..adde907d2d 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -680,11 +680,9 @@ private: DisplayModeId defaultModeId) const REQUIRES(mStateLock); - // Sets the desired display mode specs. - status_t setDesiredDisplayModeSpecsInternal( - const sp& display, - const std::optional& policy, bool overridePolicy) - EXCLUDES(mStateLock); + status_t setDesiredDisplayModeSpecsInternal(const sp&, + const scheduler::RefreshRateConfigs::PolicyVariant&) + EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); void commitTransactions() EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); void commitTransactionsLocked(uint32_t transactionFlags) diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index 66bac44a3b..a949440f41 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -362,11 +362,24 @@ void SchedulerFuzzer::fuzzRefreshRateConfigs() { mFdp.ConsumeFloatingPoint()), globalSignals); - refreshRateConfigs.setDisplayManagerPolicy( - {modeId, - {Fps::fromValue(mFdp.ConsumeFloatingPoint()), - Fps::fromValue(mFdp.ConsumeFloatingPoint())}}); - FTL_FAKE_GUARD(kMainThreadContext, refreshRateConfigs.setActiveModeId(modeId)); + { + ftl::FakeGuard guard(kMainThreadContext); + + refreshRateConfigs.setPolicy( + RefreshRateConfigs:: + DisplayManagerPolicy{modeId, + {Fps::fromValue(mFdp.ConsumeFloatingPoint()), + Fps::fromValue(mFdp.ConsumeFloatingPoint())}}); + refreshRateConfigs.setPolicy( + RefreshRateConfigs::OverridePolicy{modeId, + {Fps::fromValue( + mFdp.ConsumeFloatingPoint()), + Fps::fromValue( + mFdp.ConsumeFloatingPoint())}}); + refreshRateConfigs.setPolicy(RefreshRateConfigs::NoOverridePolicy{}); + + refreshRateConfigs.setActiveModeId(modeId); + } RefreshRateConfigs::isFractionalPairOrMultiple(Fps::fromValue( mFdp.ConsumeFloatingPoint()), diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp index a706c4b33b..00f1b089e5 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp @@ -34,6 +34,7 @@ namespace android::scheduler { namespace hal = android::hardware::graphics::composer::hal; +using SetPolicyResult = RefreshRateConfigs::SetPolicyResult; using LayerVoteType = RefreshRateConfigs::LayerVoteType; using LayerRequirement = RefreshRateConfigs::LayerRequirement; @@ -93,6 +94,15 @@ struct TestableRefreshRateConfigs : RefreshRateConfigs { GlobalSignals signals = {}) const { return getRankedRefreshRatesAndSignals(layers, signals).first.front().displayModePtr; } + + SetPolicyResult setPolicy(const PolicyVariant& policy) { + ftl::FakeGuard guard(kMainThreadContext); + return RefreshRateConfigs::setPolicy(policy); + } + + SetPolicyResult setDisplayManagerPolicy(const DisplayManagerPolicy& policy) { + return setPolicy(policy); + } }; class RefreshRateConfigsTest : public testing::Test { @@ -178,9 +188,33 @@ TEST_F(RefreshRateConfigsTest, oneMode_canSwitch) { } TEST_F(RefreshRateConfigsTest, invalidPolicy) { - RefreshRateConfigs configs(kModes_60, kModeId60); - EXPECT_LT(configs.setDisplayManagerPolicy({DisplayModeId(10), {60_Hz, 60_Hz}}), 0); - EXPECT_LT(configs.setDisplayManagerPolicy({kModeId60, {20_Hz, 40_Hz}}), 0); + TestableRefreshRateConfigs configs(kModes_60, kModeId60); + + EXPECT_EQ(SetPolicyResult::Invalid, + configs.setDisplayManagerPolicy({DisplayModeId(10), {60_Hz, 60_Hz}})); + EXPECT_EQ(SetPolicyResult::Invalid, + configs.setDisplayManagerPolicy({kModeId60, {20_Hz, 40_Hz}})); +} + +TEST_F(RefreshRateConfigsTest, unchangedPolicy) { + TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); + + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}})); + + EXPECT_EQ(SetPolicyResult::Unchanged, + configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}})); + + // Override to the same policy. + EXPECT_EQ(SetPolicyResult::Unchanged, + configs.setPolicy(RefreshRateConfigs::OverridePolicy{kModeId90, {60_Hz, 90_Hz}})); + + // Clear override to restore DisplayManagerPolicy. + EXPECT_EQ(SetPolicyResult::Unchanged, + configs.setPolicy(RefreshRateConfigs::NoOverridePolicy{})); + + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId90, {30_Hz, 90_Hz}})); } TEST_F(RefreshRateConfigsTest, twoModes_storesFullRefreshRateMap) { @@ -211,7 +245,8 @@ TEST_F(RefreshRateConfigsTest, twoModes_storesFullRefreshRateMap_differentGroups EXPECT_EQ(kMode60, minRate60); EXPECT_EQ(kMode60, performanceRate60); - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}), 0); + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}})); configs.setActiveModeId(kModeId90); const auto minRate90 = configs.getMinRefreshRateByPolicy(); @@ -234,7 +269,8 @@ TEST_F(RefreshRateConfigsTest, twoModes_storesFullRefreshRateMap_differentResolu EXPECT_EQ(kMode60, minRate60); EXPECT_EQ(kMode60, performanceRate60); - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}), 0); + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}})); configs.setActiveModeId(kModeId90); const auto minRate90 = configs.getMinRefreshRateByPolicy(); @@ -254,7 +290,8 @@ TEST_F(RefreshRateConfigsTest, twoModes_policyChange) { EXPECT_EQ(kMode60, minRate); EXPECT_EQ(kMode90, performanceRate); - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), 0); + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}})); const auto minRate60 = configs.getMinRefreshRateByPolicy(); const auto performanceRate60 = configs.getMaxRefreshRateByPolicy(); @@ -276,7 +313,8 @@ TEST_F(RefreshRateConfigsTest, twoModes_getActiveMode) { EXPECT_EQ(mode.getId(), kModeId90); } - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}), 0); + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}})); { const auto& mode = configs.getActiveMode(); EXPECT_EQ(mode.getId(), kModeId90); @@ -291,15 +329,17 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_noLayers) { // range. EXPECT_EQ(kMode90, configs.getBestRefreshRate()); - EXPECT_EQ(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), NO_ERROR); + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}})); EXPECT_EQ(kMode60, configs.getBestRefreshRate()); } { // We select max even when this will cause a non-seamless switch. TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60); constexpr bool kAllowGroupSwitching = true; - EXPECT_EQ(configs.setDisplayManagerPolicy({kModeId90, kAllowGroupSwitching, {0_Hz, 90_Hz}}), - NO_ERROR); + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy( + {kModeId90, kAllowGroupSwitching, {0_Hz, 90_Hz}})); EXPECT_EQ(kMode90_G1, configs.getBestRefreshRate()); } } @@ -340,7 +380,8 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_60_90) { EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); lr.name = ""; - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), 0); + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}})); lr.vote = LayerVoteType::Min; EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); @@ -364,7 +405,8 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_60_90) { lr.desiredRefreshRate = 24_Hz; EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}), 0); + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}})); lr.vote = LayerVoteType::Min; EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); @@ -388,7 +430,8 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_60_90) { lr.desiredRefreshRate = 24_Hz; EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {0_Hz, 120_Hz}}), 0); + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId60, {0_Hz, 120_Hz}})); lr.vote = LayerVoteType::Min; EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); @@ -1039,7 +1082,8 @@ TEST_F(RefreshRateConfigsTest, getMinRefreshRatesByPolicyOutsideTheGroup) { RefreshRateRanking{kMode60}, RefreshRateRanking{kMode90}}; - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}}), 0); + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}})); const std::vector& refreshRates = configs.getRefreshRatesByPolicy(/*anchorGroupOpt*/ std::nullopt, @@ -1062,7 +1106,8 @@ TEST_F(RefreshRateConfigsTest, getMaxRefreshRatesByPolicyOutsideTheGroup) { RefreshRateRanking{kMode60}, RefreshRateRanking{kMode30}}; - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}}), 0); + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}})); const std::vector& refreshRates = configs.getRefreshRatesByPolicy(/*anchorGroupOpt*/ std::nullopt, @@ -1300,9 +1345,10 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExact_WithFractionalRe TEST_F(RefreshRateConfigsTest, getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresTouchFlag) { - RefreshRateConfigs configs(kModes_60_90, kModeId90); + TestableRefreshRateConfigs configs(kModes_60_90, kModeId90); - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}}), 0); + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}})); std::vector layers = {{.weight = 1.f}}; auto& lr = layers[0]; @@ -1323,7 +1369,8 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresIdleFlag) { TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 90_Hz}}), 0); + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 90_Hz}})); std::vector layers = {{.weight = 1.f}}; auto& lr = layers[0]; @@ -1472,7 +1519,8 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_withDisplayManagerRequestingSingleRate_onlySwitchesRatesForExplicitFocusedLayers) { TestableRefreshRateConfigs configs(kModes_60_90, kModeId90); - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}}), 0); + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}})); const auto [mode, signals] = configs.getRankedRefreshRatesAndSignals({}, {}); EXPECT_EQ(mode.front().displayModePtr, kMode90); @@ -1546,10 +1594,10 @@ TEST_F(RefreshRateConfigsTest, groupSwitchingNotAllowed) { TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayer) { TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60); - RefreshRateConfigs::Policy policy; + RefreshRateConfigs::DisplayManagerPolicy policy; policy.defaultMode = configs.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0); + EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); std::vector layers = {{.weight = 1.f}}; auto& layer = layers[0]; @@ -1564,10 +1612,10 @@ TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayer) { TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerOnlySeamless) { TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60); - RefreshRateConfigs::Policy policy; + RefreshRateConfigs::DisplayManagerPolicy policy; policy.defaultMode = configs.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0); + EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); // Verify that we won't change the group if seamless switch is required. std::vector layers = {{.weight = 1.f}}; @@ -1583,10 +1631,10 @@ TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerOnlySeamless) { TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps) { TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60); - RefreshRateConfigs::Policy policy; + RefreshRateConfigs::DisplayManagerPolicy policy; policy.defaultMode = configs.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0); + EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); configs.setActiveModeId(kModeId90); @@ -1604,10 +1652,10 @@ TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps) TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerDefaultSeamlessness) { TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60); - RefreshRateConfigs::Policy policy; + RefreshRateConfigs::DisplayManagerPolicy policy; policy.defaultMode = configs.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0); + EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); configs.setActiveModeId(kModeId90); @@ -1628,10 +1676,10 @@ TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerDefaultSeamlessness) { TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed) { TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60); - RefreshRateConfigs::Policy policy; + RefreshRateConfigs::DisplayManagerPolicy policy; policy.defaultMode = configs.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0); + EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); configs.setActiveModeId(kModeId90); @@ -1657,10 +1705,10 @@ TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed) TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeamed) { TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60); - RefreshRateConfigs::Policy policy; + RefreshRateConfigs::DisplayManagerPolicy policy; policy.defaultMode = configs.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0); + EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); configs.setActiveModeId(kModeId90); @@ -1690,10 +1738,10 @@ TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeame TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersDefaultNotFocusedAndSeamed) { TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60); - RefreshRateConfigs::Policy policy; + RefreshRateConfigs::DisplayManagerPolicy policy; policy.defaultMode = configs.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0); + EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); configs.setActiveModeId(kModeId90); @@ -1721,10 +1769,10 @@ TEST_F(RefreshRateConfigsTest, nonSeamlessVotePrefersSeamlessSwitches) { TestableRefreshRateConfigs configs(kModes_30_60, kModeId60); // Allow group switching. - RefreshRateConfigs::Policy policy; + RefreshRateConfigs::DisplayManagerPolicy policy; policy.defaultMode = configs.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0); + EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); std::vector layers = {{.weight = 1.f}}; auto& layer = layers[0]; @@ -1744,10 +1792,10 @@ TEST_F(RefreshRateConfigsTest, nonSeamlessExactAndSeamlessMultipleLayers) { TestableRefreshRateConfigs configs(kModes_25_30_50_60, kModeId60); // Allow group switching. - RefreshRateConfigs::Policy policy; + RefreshRateConfigs::DisplayManagerPolicy policy; policy.defaultMode = configs.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0); + EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); std::vector layers = {{.name = "60Hz ExplicitDefault", .vote = LayerVoteType::ExplicitDefault, @@ -1776,10 +1824,10 @@ TEST_F(RefreshRateConfigsTest, minLayersDontTrigerSeamedSwitch) { TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId90); // Allow group switching. - RefreshRateConfigs::Policy policy; + RefreshRateConfigs::DisplayManagerPolicy policy; policy.defaultMode = configs.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0); + EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); std::vector layers = { {.name = "Min", .vote = LayerVoteType::Min, .weight = 1.f, .focused = true}}; @@ -1807,7 +1855,8 @@ TEST_F(RefreshRateConfigsTest, primaryVsAppRequestPolicy) { return configs.getBestRefreshRate(layers, {.touch = args.touch})->getId(); }; - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 60_Hz}, {30_Hz, 90_Hz}}), 0); + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 60_Hz}, {30_Hz, 90_Hz}})); EXPECT_EQ(kModeId60, configs.getBestRefreshRate()->getId()); EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz)); @@ -1831,7 +1880,8 @@ TEST_F(RefreshRateConfigsTest, primaryVsAppRequestPolicy) { EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz, {.touch = true})); - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 60_Hz}}), 0); + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 60_Hz}})); EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz)); EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Min, 90_Hz)); @@ -1860,7 +1910,8 @@ TEST_F(RefreshRateConfigsTest, idle) { return refreshRate.front().displayModePtr->getId(); }; - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}, {60_Hz, 90_Hz}}), 0); + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}, {60_Hz, 90_Hz}})); // Idle should be lower priority than touch boost. { @@ -2156,43 +2207,50 @@ TEST_F(RefreshRateConfigsTest, modeComparison) { TEST_F(RefreshRateConfigsTest, testKernelIdleTimerAction) { using KernelIdleTimerAction = RefreshRateConfigs::KernelIdleTimerAction; - RefreshRateConfigs configs(kModes_60_90, kModeId90); + TestableRefreshRateConfigs configs(kModes_60_90, kModeId90); - // SetPolicy(60, 90), current 90Hz => TurnOn. + // setPolicy(60, 90), current 90Hz => TurnOn. EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction()); - // SetPolicy(60, 90), current 60Hz => TurnOn. - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}}), 0); + // setPolicy(60, 90), current 60Hz => TurnOn. + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}})); EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction()); - // SetPolicy(60, 60), current 60Hz => TurnOff - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), 0); + // setPolicy(60, 60), current 60Hz => TurnOff + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}})); EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction()); - // SetPolicy(90, 90), current 90Hz => TurnOff. - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}), 0); + // setPolicy(90, 90), current 90Hz => TurnOff. + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}})); EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction()); } TEST_F(RefreshRateConfigsTest, testKernelIdleTimerActionFor120Hz) { using KernelIdleTimerAction = RefreshRateConfigs::KernelIdleTimerAction; - RefreshRateConfigs configs(kModes_60_120, kModeId120); + TestableRefreshRateConfigs configs(kModes_60_120, kModeId120); - // SetPolicy(0, 60), current 60Hz => TurnOn. - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {0_Hz, 60_Hz}}), 0); + // setPolicy(0, 60), current 60Hz => TurnOn. + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId60, {0_Hz, 60_Hz}})); EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction()); - // SetPolicy(60, 60), current 60Hz => TurnOff. - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), 0); + // setPolicy(60, 60), current 60Hz => TurnOff. + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}})); EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction()); - // SetPolicy(60, 120), current 60Hz => TurnOn. - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 120_Hz}}), 0); + // setPolicy(60, 120), current 60Hz => TurnOn. + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 120_Hz}})); EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction()); - // SetPolicy(120, 120), current 120Hz => TurnOff. - EXPECT_GE(configs.setDisplayManagerPolicy({kModeId120, {120_Hz, 120_Hz}}), 0); + // setPolicy(120, 120), current 120Hz => TurnOff. + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId120, {120_Hz, 120_Hz}})); EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction()); } -- GitLab From e591b55b7028d3f092e0377f96ea68c990d62903 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Fri, 9 Sep 2022 10:44:56 -0700 Subject: [PATCH 0343/1310] SF: Identify the active display with its ID The active display (and the superseding concept of leader display) is necessarily physical, so constrain its type to PhysicalDisplayId. Bug: 241285876 Test: Fold/unfold Change-Id: If83c908446e5e5267dfcb15189a26b779d75b216 --- services/surfaceflinger/SurfaceFlinger.cpp | 47 +++++++++---------- services/surfaceflinger/SurfaceFlinger.h | 15 +++--- .../SurfaceFlinger_PowerHintTest.cpp | 2 +- ...urfaceFlinger_SetPowerModeInternalTest.cpp | 2 +- .../tests/unittests/TestableSurfaceFlinger.h | 2 +- 5 files changed, 32 insertions(+), 36 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 1e39f629ce..cae49b7044 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1190,7 +1190,7 @@ void SurfaceFlinger::updateInternalStateWithChangedMode() { void SurfaceFlinger::clearDesiredActiveModeState(const sp& display) { display->clearDesiredActiveModeState(); - if (isDisplayActiveLocked(display)) { + if (display->getPhysicalId() == mActiveDisplayId) { mScheduler->setModeChangePending(false); } } @@ -1220,12 +1220,12 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { // Store the local variable to release the lock. const auto desiredActiveMode = display->getDesiredActiveMode(); if (!desiredActiveMode) { - // No desired active mode pending to be applied + // No desired active mode pending to be applied. continue; } - if (!isDisplayActiveLocked(display)) { - // display is no longer the active display, so abort the mode change + if (id != mActiveDisplayId) { + // Display is no longer the active display, so abort the mode change. clearDesiredActiveModeState(display); continue; } @@ -1858,10 +1858,8 @@ void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t t return; } - const auto displayId = getHwComposer().toPhysicalDisplayId(hwcDisplayId); - const bool isActiveDisplay = - displayId && getPhysicalDisplayTokenLocked(*displayId) == mActiveDisplayToken; - if (!isActiveDisplay) { + if (const auto displayId = getHwComposer().toPhysicalDisplayId(hwcDisplayId); + displayId != mActiveDisplayId) { // For now, we don't do anything with non active display vsyncs. return; } @@ -2057,8 +2055,7 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe // Save this once per commit + composite to ensure consistency // TODO (b/240619471): consider removing active display check once AOD is fixed - const auto activeDisplay = - FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(mActiveDisplayToken)); + const auto activeDisplay = FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(mActiveDisplayId)); mPowerHintSessionEnabled = mPowerAdvisor->usePowerHintSession() && activeDisplay && activeDisplay->getPowerMode() == hal::PowerMode::ON; if (mPowerHintSessionEnabled) { @@ -3031,7 +3028,7 @@ void SurfaceFlinger::processDisplayChanged(const wp& displayToken, (currentState.orientedDisplaySpaceRect != drawingState.orientedDisplaySpaceRect)) { display->setProjection(currentState.orientation, currentState.layerStackSpaceRect, currentState.orientedDisplaySpaceRect); - if (isDisplayActiveLocked(display)) { + if (display->getId() == mActiveDisplayId) { mActiveDisplayTransformHint = display->getTransformHint(); } } @@ -3039,7 +3036,7 @@ void SurfaceFlinger::processDisplayChanged(const wp& displayToken, currentState.height != drawingState.height) { display->setDisplaySize(currentState.width, currentState.height); - if (isDisplayActiveLocked(display)) { + if (display->getId() == mActiveDisplayId) { onActiveDisplaySizeChanged(display); } } @@ -4765,11 +4762,12 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: return; } + const bool isActiveDisplay = displayId == mActiveDisplayId; const bool isInternalDisplay = mPhysicalDisplays.get(displayId) .transform(&PhysicalDisplay::isInternal) .value_or(false); - const auto activeDisplay = getDisplayDeviceLocked(mActiveDisplayToken); + const auto activeDisplay = getDisplayDeviceLocked(mActiveDisplayId); if (isInternalDisplay && activeDisplay != display && activeDisplay && activeDisplay->isPoweredOn()) { ALOGW("Trying to change power mode on non active display while the active display is ON"); @@ -4795,7 +4793,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: ALOGW("Couldn't set SCHED_FIFO on display on: %s\n", strerror(errno)); } getHwComposer().setPowerMode(displayId, mode); - if (isDisplayActiveLocked(display) && mode != hal::PowerMode::DOZE_SUSPEND) { + if (isActiveDisplay && mode != hal::PowerMode::DOZE_SUSPEND) { setHWCVsyncEnabled(displayId, mHWCVsyncPendingState); mScheduler->onScreenAcquired(mAppConnectionHandle); mScheduler->resyncToHardwareVsync(true, refreshRate); @@ -4811,7 +4809,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: if (SurfaceFlinger::setSchedAttr(false) != NO_ERROR) { ALOGW("Couldn't set uclamp.min on display off: %s\n", strerror(errno)); } - if (isDisplayActiveLocked(display) && *currentMode != hal::PowerMode::DOZE_SUSPEND) { + if (isActiveDisplay && *currentMode != hal::PowerMode::DOZE_SUSPEND) { mScheduler->disableHardwareVsync(true); mScheduler->onScreenReleased(mAppConnectionHandle); } @@ -4825,7 +4823,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: } else if (mode == hal::PowerMode::DOZE || mode == hal::PowerMode::ON) { // Update display while dozing getHwComposer().setPowerMode(displayId, mode); - if (isDisplayActiveLocked(display) && *currentMode == hal::PowerMode::DOZE_SUSPEND) { + if (isActiveDisplay && *currentMode == hal::PowerMode::DOZE_SUSPEND) { ALOGI("Force repainting for DOZE_SUSPEND -> DOZE or ON."); mVisibleRegionsDirty = true; scheduleRepaint(); @@ -4834,7 +4832,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: } } else if (mode == hal::PowerMode::DOZE_SUSPEND) { // Leave display going to doze - if (isDisplayActiveLocked(display)) { + if (isActiveDisplay) { mScheduler->disableHardwareVsync(true); mScheduler->onScreenReleased(mAppConnectionHandle); } @@ -4844,7 +4842,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: getHwComposer().setPowerMode(displayId, mode); } - if (isDisplayActiveLocked(display)) { + if (isActiveDisplay) { mTimeStats->setPowerMode(mode); mRefreshRateStats->setPowerMode(mode); mScheduler->setDisplayPowerMode(mode); @@ -5230,7 +5228,7 @@ void SurfaceFlinger::dumpHwcLayersMinidumpLocked(std::string& result) const { } StringAppendF(&result, "Display %s (%s) HWC layers:\n", to_string(*displayId).c_str(), - (isDisplayActiveLocked(display) ? "active" : "inactive")); + displayId == mActiveDisplayId ? "active" : "inactive"); Layer::miniDumpHeader(result); const DisplayDevice& ref = *display; @@ -6735,6 +6733,8 @@ std::optional SurfaceFlinger::getPreferredDisplayMode( status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( const sp& display, const scheduler::RefreshRateConfigs::PolicyVariant& policy) { + const auto displayId = display->getPhysicalId(); + Mutex::Autolock lock(mStateLock); if (mDebugDisplayModeSetByBackdoor) { @@ -6759,15 +6759,14 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( // TODO(b/140204874): Leave the event in until we do proper testing with all apps that might // be depending in this callback. - if (const auto activeModePtr = configs.getActiveModePtr(); isDisplayActiveLocked(display)) { + if (const auto activeModePtr = configs.getActiveModePtr(); displayId == mActiveDisplayId) { mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, activeModePtr); toggleKernelIdleTimer(); } else { mScheduler->onNonPrimaryDisplayModeChanged(mAppConnectionHandle, activeModePtr); } - auto preferredModeOpt = - getPreferredDisplayMode(display->getPhysicalId(), currentPolicy.defaultMode); + auto preferredModeOpt = getPreferredDisplayMode(displayId, currentPolicy.defaultMode); if (!preferredModeOpt) { ALOGE("%s: Preferred mode is unknown", __func__); return NAME_NOT_FOUND; @@ -7060,7 +7059,7 @@ void SurfaceFlinger::onActiveDisplaySizeChanged(const sp& a void SurfaceFlinger::onActiveDisplayChangedLocked(const sp& activeDisplay) { ATRACE_CALL(); - if (const auto display = getDisplayDeviceLocked(mActiveDisplayToken)) { + if (const auto display = getDisplayDeviceLocked(mActiveDisplayId)) { display->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false); } @@ -7068,7 +7067,7 @@ void SurfaceFlinger::onActiveDisplayChangedLocked(const sp& activ ALOGE("%s: activeDisplay is null", __func__); return; } - mActiveDisplayToken = activeDisplay->getDisplayToken(); + mActiveDisplayId = activeDisplay->getPhysicalId(); activeDisplay->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true); updateInternalDisplayVsyncLocked(activeDisplay); mScheduler->setModeChangePending(false); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index adde907d2d..cf07f301b1 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -835,10 +835,6 @@ private: void initializeDisplays(); void onInitializeDisplays() REQUIRES(mStateLock, kMainThreadContext); - bool isDisplayActiveLocked(const sp& display) const REQUIRES(mStateLock) { - return display->getDisplayToken() == mActiveDisplayToken; - } - sp getDisplayDeviceLocked(const wp& displayToken) const REQUIRES(mStateLock) { return const_cast(this)->getDisplayDeviceLocked(displayToken); @@ -873,12 +869,12 @@ private: } sp getDefaultDisplayDeviceLocked() REQUIRES(mStateLock) { - if (const auto display = getDisplayDeviceLocked(mActiveDisplayToken)) { + if (const auto display = getDisplayDeviceLocked(mActiveDisplayId)) { return display; } // The active display is outdated, so fall back to the primary display. - mActiveDisplayToken.clear(); - return getDisplayDeviceLocked(getPrimaryDisplayTokenLocked()); + mActiveDisplayId = getPrimaryDisplayIdLocked(); + return getDisplayDeviceLocked(mActiveDisplayId); } sp getDefaultDisplayDevice() const EXCLUDES(mStateLock) { @@ -1204,6 +1200,9 @@ private: display::PhysicalDisplays mPhysicalDisplays GUARDED_BY(mStateLock); + // The inner or outer display for foldables, assuming they have mutually exclusive power states. + PhysicalDisplayId mActiveDisplayId GUARDED_BY(mStateLock); + struct { DisplayIdGenerator gpu; std::optional> hal; @@ -1393,8 +1392,6 @@ private: [](const auto& display) { return display.isRefreshRateOverlayEnabled(); }); } - wp mActiveDisplayToken GUARDED_BY(mStateLock); - const sp mWindowInfosListenerInvoker; FlagManager mFlagManager; diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp index e256d2c9a1..bc66961f44 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp @@ -97,7 +97,7 @@ void SurfaceFlingerPowerHintTest::SetUp() { .setNativeWindow(mNativeWindow) .setPowerMode(hal::PowerMode::ON) .inject(); - mFlinger.mutableActiveDisplayToken() = mDisplay->getDisplayToken(); + mFlinger.mutableActiveDisplayId() = mDisplay->getPhysicalId(); } void SurfaceFlingerPowerHintTest::setupScheduler() { diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp index 9e54083615..6f84437372 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp @@ -262,7 +262,7 @@ struct DisplayPowerCase { if (injector.physicalDisplay() .transform(&display::PhysicalDisplay::isInternal) .value_or(false)) { - test->mFlinger.mutableActiveDisplayToken() = display->getDisplayToken(); + test->mFlinger.mutableActiveDisplayId() = display->getPhysicalId(); } return display; diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index e624ab9ca5..fdd55a5328 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -530,7 +530,7 @@ public: auto& mutableHwcDisplayData() { return getHwComposer().mDisplayData; } auto& mutableHwcPhysicalDisplayIdMap() { return getHwComposer().mPhysicalDisplayIdMap; } auto& mutablePrimaryHwcDisplayId() { return getHwComposer().mPrimaryHwcDisplayId; } - auto& mutableActiveDisplayToken() { return mFlinger->mActiveDisplayToken; } + auto& mutableActiveDisplayId() { return mFlinger->mActiveDisplayId; } auto fromHandle(const sp& handle) { return mFlinger->fromHandle(handle); -- GitLab From 0cbd08bfe4bda34fabd60cefd503efaf6c4eb2b3 Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Wed, 17 Aug 2022 12:12:28 -0700 Subject: [PATCH 0344/1310] SurfaceFlinger HWC overlay API Bug: 242588489 Test: pending Change-Id: Ic5c7395ab530a6153f760bd39b65a05b4c78b8a4 --- libs/gui/SurfaceComposerClient.cpp | 6 +++++ .../aidl/android/gui/ISurfaceComposer.aidl | 3 +++ .../aidl/android/gui/OverlayProperties.aidl | 24 +++++++++++++++++++ .../gui/SupportedBufferCombinations.aidl | 23 ++++++++++++++++++ libs/gui/fuzzer/libgui_fuzzer_utils.h | 1 + libs/gui/include/gui/SurfaceComposerClient.h | 4 ++++ libs/gui/tests/Surface_test.cpp | 4 ++++ .../CompositionEngine/tests/MockHWComposer.h | 2 ++ .../DisplayHardware/AidlComposerHal.cpp | 6 +++++ .../DisplayHardware/AidlComposerHal.h | 2 ++ .../DisplayHardware/ComposerHal.h | 2 ++ .../surfaceflinger/DisplayHardware/HWC2.cpp | 6 +++++ .../surfaceflinger/DisplayHardware/HWC2.h | 6 +++++ .../DisplayHardware/HWComposer.cpp | 5 ++++ .../DisplayHardware/HWComposer.h | 7 ++++++ .../DisplayHardware/HidlComposerHal.cpp | 5 ++++ .../DisplayHardware/HidlComposerHal.h | 2 ++ services/surfaceflinger/SurfaceFlinger.cpp | 12 ++++++++++ services/surfaceflinger/SurfaceFlinger.h | 2 ++ .../mock/DisplayHardware/MockComposer.h | 2 ++ .../unittests/mock/DisplayHardware/MockHWC2.h | 3 +++ 21 files changed, 127 insertions(+) create mode 100644 libs/gui/aidl/android/gui/OverlayProperties.aidl create mode 100644 libs/gui/aidl/android/gui/SupportedBufferCombinations.aidl diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index fc0b1ee7e2..73d8a4af46 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2422,6 +2422,12 @@ status_t SurfaceComposerClient::getBootDisplayModeSupport(bool* support) { return statusTFromBinderStatus(status); } +status_t SurfaceComposerClient::getOverlaySupport(gui::OverlayProperties* outProperties) { + binder::Status status = + ComposerServiceAIDL::getComposerService()->getOverlaySupport(outProperties); + return statusTFromBinderStatus(status); +} + status_t SurfaceComposerClient::setBootDisplayMode(const sp& display, ui::DisplayModeId displayModeId) { binder::Status status = ComposerServiceAIDL::getComposerService() diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 3c220fcc66..53d0a0489d 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -41,6 +41,7 @@ import android.gui.ITunnelModeEnabledListener; import android.gui.IWindowInfosListener; import android.gui.LayerCaptureArgs; import android.gui.LayerDebugInfo; +import android.gui.OverlayProperties; import android.gui.PullAtomData; import android.gui.ARect; import android.gui.StaticDisplayInfo; @@ -482,4 +483,6 @@ interface ISurfaceComposer { void addWindowInfosListener(IWindowInfosListener windowInfosListener); void removeWindowInfosListener(IWindowInfosListener windowInfosListener); + + OverlayProperties getOverlaySupport(); } diff --git a/libs/gui/aidl/android/gui/OverlayProperties.aidl b/libs/gui/aidl/android/gui/OverlayProperties.aidl new file mode 100644 index 0000000000..80d5ced0a4 --- /dev/null +++ b/libs/gui/aidl/android/gui/OverlayProperties.aidl @@ -0,0 +1,24 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +import android.gui.SupportedBufferCombinations; + +/** @hide */ +parcelable OverlayProperties { + SupportedBufferCombinations[] combinations; +} diff --git a/libs/gui/aidl/android/gui/SupportedBufferCombinations.aidl b/libs/gui/aidl/android/gui/SupportedBufferCombinations.aidl new file mode 100644 index 0000000000..a8bc994274 --- /dev/null +++ b/libs/gui/aidl/android/gui/SupportedBufferCombinations.aidl @@ -0,0 +1,23 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +/** @hide */ +parcelable SupportedBufferCombinations { + int[] pixelFormats; + int[] dataspaces; +} diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h index d51f6850c0..1dda97e383 100644 --- a/libs/gui/fuzzer/libgui_fuzzer_utils.h +++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h @@ -156,6 +156,7 @@ public: (override)); MOCK_METHOD(binder::Status, removeWindowInfosListener, (const sp&), (override)); + MOCK_METHOD(binder::Status, getOverlaySupport, (gui::OverlayProperties*), (override)); }; class FakeBnSurfaceComposerClient : public gui::BnSurfaceComposerClient { diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 963cc09ca9..25042cfd4c 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -179,6 +179,10 @@ public: // Gets if boot display mode operations are supported on a device static status_t getBootDisplayModeSupport(bool* support); + + // Gets the overlay properties of the device + static status_t getOverlaySupport(gui::OverlayProperties* outProperties); + // Sets the user-preferred display mode that a device should boot in static status_t setBootDisplayMode(const sp& display, ui::DisplayModeId); // Clears the user-preferred display mode diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index b9358e7717..0c99cd2f0f 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -997,6 +997,10 @@ public: return binder::Status::ok(); } + binder::Status getOverlaySupport(gui::OverlayProperties* /*properties*/) override { + return binder::Status::ok(); + } + protected: IBinder* onAsBinder() override { return nullptr; } diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h index d7704a893d..59e34ed180 100644 --- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h +++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h @@ -138,6 +138,8 @@ public: MOCK_METHOD(Hwc2::AidlTransform, getPhysicalDisplayOrientation, (PhysicalDisplayId), (const, override)); MOCK_METHOD(bool, getValidateSkipped, (HalDisplayId), (const, override)); + MOCK_METHOD(status_t, getOverlaySupport, + (aidl::android::hardware::graphics::composer3::OverlayProperties*)); }; } // namespace mock diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp index 79dcd159d3..a4a89ea319 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp @@ -55,6 +55,7 @@ using AidlDisplayContentSample = aidl::android::hardware::graphics::composer3::D using AidlDisplayAttribute = aidl::android::hardware::graphics::composer3::DisplayAttribute; using AidlDisplayCapability = aidl::android::hardware::graphics::composer3::DisplayCapability; using AidlHdrCapabilities = aidl::android::hardware::graphics::composer3::HdrCapabilities; +using AidlOverlayProperties = aidl::android::hardware::graphics::composer3::OverlayProperties; using AidlPerFrameMetadata = aidl::android::hardware::graphics::composer3::PerFrameMetadata; using AidlPerFrameMetadataKey = aidl::android::hardware::graphics::composer3::PerFrameMetadataKey; using AidlPerFrameMetadataBlob = aidl::android::hardware::graphics::composer3::PerFrameMetadataBlob; @@ -499,6 +500,11 @@ Error AidlComposer::getHdrCapabilities(Display display, std::vector* outTyp return Error::NONE; } +Error AidlComposer::getOverlaySupport(AidlOverlayProperties* /*outProperties*/) { + // TODO(b/242588489): implement details + return Error::NONE; +} + Error AidlComposer::getReleaseFences(Display display, std::vector* outLayers, std::vector* outReleaseFences) { auto fences = mReader.takeReleaseFences(translate(display)); diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h index 18d2242c7e..f2a59a5b95 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h @@ -48,6 +48,7 @@ namespace android::Hwc2 { using aidl::android::hardware::graphics::common::DisplayDecorationSupport; using aidl::android::hardware::graphics::composer3::ComposerClientReader; using aidl::android::hardware::graphics::composer3::ComposerClientWriter; +using aidl::android::hardware::graphics::composer3::OverlayProperties; class AidlIComposerCallbackWrapper; @@ -103,6 +104,7 @@ public: Error hasDisplayIdleTimerCapability(Display display, bool* outSupport) override; Error getHdrCapabilities(Display display, std::vector* outTypes, float* outMaxLuminance, float* outMaxAverageLuminance, float* outMinLuminance) override; + Error getOverlaySupport(OverlayProperties* outProperties) override; Error getReleaseFences(Display display, std::vector* outLayers, std::vector* outReleaseFences) override; diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h index d266d942fb..b02f867b85 100644 --- a/services/surfaceflinger/DisplayHardware/ComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -281,6 +282,7 @@ public: virtual Error setIdleTimerEnabled(Display displayId, std::chrono::milliseconds timeout) = 0; virtual Error getPhysicalDisplayOrientation(Display displayId, AidlTransform* outDisplayOrientation) = 0; + virtual Error getOverlaySupport(V3_0::OverlayProperties* outProperties) = 0; }; } // namespace Hwc2 diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp index 16d398474d..a9337d8d88 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.cpp +++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp @@ -40,6 +40,7 @@ using aidl::android::hardware::graphics::composer3::Color; using aidl::android::hardware::graphics::composer3::Composition; using AidlCapability = aidl::android::hardware::graphics::composer3::Capability; using aidl::android::hardware::graphics::composer3::DisplayCapability; +using aidl::android::hardware::graphics::composer3::OverlayProperties; namespace android { @@ -333,6 +334,11 @@ Error Display::getHdrCapabilities(HdrCapabilities* outCapabilities) const return Error::NONE; } +Error Display::getOverlaySupport(OverlayProperties* /*outProperties*/) const { + // TODO(b/242588489): implement details + return Error::NONE; +} + Error Display::getDisplayedContentSamplingAttributes(hal::PixelFormat* outFormat, Dataspace* outDataspace, uint8_t* outComponentMask) const { diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h index 486eaf8f03..91ded86bbe 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.h +++ b/services/surfaceflinger/DisplayHardware/HWC2.h @@ -43,6 +43,7 @@ #include #include #include +#include namespace android { @@ -117,6 +118,9 @@ public: [[nodiscard]] virtual hal::Error supportsDoze(bool* outSupport) const = 0; [[nodiscard]] virtual hal::Error getHdrCapabilities( android::HdrCapabilities* outCapabilities) const = 0; + [[nodiscard]] virtual hal::Error getOverlaySupport( + aidl::android::hardware::graphics::composer3::OverlayProperties* outProperties) + const = 0; [[nodiscard]] virtual hal::Error getDisplayedContentSamplingAttributes( hal::PixelFormat* outFormat, hal::Dataspace* outDataspace, uint8_t* outComponentMask) const = 0; @@ -204,6 +208,8 @@ public: hal::Error getConnectionType(ui::DisplayConnectionType*) const override; hal::Error supportsDoze(bool* outSupport) const override EXCLUDES(mDisplayCapabilitiesMutex); hal::Error getHdrCapabilities(android::HdrCapabilities* outCapabilities) const override; + hal::Error getOverlaySupport(aidl::android::hardware::graphics::composer3::OverlayProperties* + outProperties) const override; hal::Error getDisplayedContentSamplingAttributes(hal::PixelFormat* outFormat, hal::Dataspace* outDataspace, uint8_t* outComponentMask) const override; diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp index 15d5041a1e..bf1d9ea53e 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -647,6 +647,11 @@ status_t HWComposer::getHdrCapabilities(HalDisplayId displayId, HdrCapabilities* return NO_ERROR; } +status_t HWComposer::getOverlaySupport( + aidl::android::hardware::graphics::composer3::OverlayProperties* /*outProperties*/) { + return NO_ERROR; +} + int32_t HWComposer::getSupportedPerFrameMetadata(HalDisplayId displayId) const { RETURN_IF_INVALID_DISPLAY(displayId, 0); return mDisplayData.at(displayId).hwcDisplay->getSupportedPerFrameMetadata(); diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h index 92a8f30f1b..0b141baaab 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.h +++ b/services/surfaceflinger/DisplayHardware/HWComposer.h @@ -48,6 +48,7 @@ #include #include #include +#include namespace android { @@ -177,6 +178,9 @@ public: // Fetches the HDR capabilities of the given display virtual status_t getHdrCapabilities(HalDisplayId, HdrCapabilities* outCapabilities) = 0; + virtual status_t getOverlaySupport( + aidl::android::hardware::graphics::composer3::OverlayProperties* outProperties) = 0; + virtual int32_t getSupportedPerFrameMetadata(HalDisplayId) const = 0; // Returns the available RenderIntent of the given display. @@ -360,6 +364,9 @@ public: // Fetches the HDR capabilities of the given display status_t getHdrCapabilities(HalDisplayId, HdrCapabilities* outCapabilities) override; + status_t getOverlaySupport(aidl::android::hardware::graphics::composer3::OverlayProperties* + outProperties) override; + int32_t getSupportedPerFrameMetadata(HalDisplayId) const override; // Returns the available RenderIntent of the given display. diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp index 2597ae6091..a664d2cf38 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp @@ -40,6 +40,7 @@ using aidl::android::hardware::graphics::composer3::Capability; using aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness; using aidl::android::hardware::graphics::composer3::DimmingStage; using aidl::android::hardware::graphics::composer3::DisplayCapability; +using aidl::android::hardware::graphics::composer3::OverlayProperties; namespace android { @@ -540,6 +541,10 @@ Error HidlComposer::getHdrCapabilities(Display display, std::vector* outTyp return error; } +Error HidlComposer::getOverlaySupport(OverlayProperties* /*outProperties*/) { + return Error::NONE; +} + Error HidlComposer::getReleaseFences(Display display, std::vector* outLayers, std::vector* outReleaseFences) { mReader.takeReleaseFences(display, outLayers, outReleaseFences); diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h index d0d3c2e6d7..b436408fb1 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h @@ -211,6 +211,8 @@ public: Error hasDisplayIdleTimerCapability(Display display, bool* outSupport) override; Error getHdrCapabilities(Display display, std::vector* outTypes, float* outMaxLuminance, float* outMaxAverageLuminance, float* outMinLuminance) override; + Error getOverlaySupport(aidl::android::hardware::graphics::composer3::OverlayProperties* + outProperties) override; Error getReleaseFences(Display display, std::vector* outLayers, std::vector* outReleaseFences) override; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index e27c713806..8b19dccda4 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1429,6 +1429,10 @@ status_t SurfaceFlinger::getBootDisplayModeSupport(bool* outSupport) const { return NO_ERROR; } +status_t SurfaceFlinger::getOverlaySupport(gui::OverlayProperties* /*outProperties*/) const { + return NO_ERROR; +} + status_t SurfaceFlinger::setBootDisplayMode(const sp& displayToken, DisplayModeId modeId) { const char* const whence = __func__; @@ -7502,6 +7506,14 @@ binder::Status SurfaceComposerAIDL::clearBootDisplayMode(const sp& disp return binderStatusFromStatusT(status); } +binder::Status SurfaceComposerAIDL::getOverlaySupport(gui::OverlayProperties* outProperties) { + status_t status = checkAccessPermission(); + if (status == OK) { + status = mFlinger->getOverlaySupport(outProperties); + } + return binderStatusFromStatusT(status); +} + binder::Status SurfaceComposerAIDL::getBootDisplayModeSupport(bool* outMode) { status_t status = checkAccessPermission(); if (status == OK) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 3c92d565ed..163c1fd607 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -529,6 +529,7 @@ private: status_t setActiveColorMode(const sp& displayToken, ui::ColorMode colorMode); status_t getBootDisplayModeSupport(bool* outSupport) const; status_t setBootDisplayMode(const sp&, DisplayModeId); + status_t getOverlaySupport(gui::OverlayProperties* outProperties) const; status_t clearBootDisplayMode(const sp& displayToken); void setAutoLowLatencyMode(const sp& displayToken, bool on); void setGameContentType(const sp& displayToken, bool on); @@ -1449,6 +1450,7 @@ public: binder::Status setBootDisplayMode(const sp& display, int displayModeId) override; binder::Status clearBootDisplayMode(const sp& display) override; binder::Status getBootDisplayModeSupport(bool* outMode) override; + binder::Status getOverlaySupport(gui::OverlayProperties* outProperties) override; binder::Status setAutoLowLatencyMode(const sp& display, bool on) override; binder::Status setGameContentType(const sp& display, bool on) override; binder::Status captureDisplay(const DisplayCaptureArgs&, diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h index aa8b5211e9..3808487d0f 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h @@ -164,6 +164,8 @@ public: MOCK_METHOD2(setIdleTimerEnabled, Error(Display, std::chrono::milliseconds)); MOCK_METHOD2(hasDisplayIdleTimerCapability, Error(Display, bool*)); MOCK_METHOD2(getPhysicalDisplayOrientation, Error(Display, AidlTransform*)); + MOCK_METHOD1(getOverlaySupport, + Error(aidl::android::hardware::graphics::composer3::OverlayProperties*)); }; } // namespace Hwc2::mock diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h index 07cd15da93..40f59b8cb9 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h @@ -105,6 +105,9 @@ public: MOCK_METHOD(bool, hasDisplayIdleTimerCapability, (), (const override)); MOCK_METHOD(hal::Error, getPhysicalDisplayOrientation, (Hwc2::AidlTransform *), (const override)); + MOCK_METHOD(hal::Error, getOverlaySupport, + (aidl::android::hardware::graphics::composer3::OverlayProperties *), + (const override)); }; class Layer : public HWC2::Layer { -- GitLab From 2935db7177bf6ddee879883da5b0a07846f44391 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 22 Sep 2022 13:35:22 -0700 Subject: [PATCH 0345/1310] Return events from mappers and InputDevice We are changing the way android input events are reported from the InputReader. Previously, the process was opaque - anywhere in the code you were allowed to grab the listener and send events to it. Now, the flow changes - you will have to explicitly return the events back to the caller. With the new approach, InputReader will ultimately be the one dispatching the events to the listener. Bug: 211379801 Test: atest inputflinger_tests Change-Id: I2318ad1220fa66b197ca2a49b8625afcfb45103f --- services/inputflinger/InputListener.cpp | 5 + services/inputflinger/NotifyArgs.cpp | 23 + services/inputflinger/include/InputListener.h | 2 + services/inputflinger/include/NotifyArgs.h | 2 + services/inputflinger/reader/InputDevice.cpp | 83 ++- services/inputflinger/reader/InputReader.cpp | 65 +- .../inputflinger/reader/include/InputDevice.h | 28 +- .../inputflinger/reader/include/InputReader.h | 18 +- .../reader/include/InputReaderContext.h | 5 +- .../reader/mapper/CursorInputMapper.cpp | 117 ++-- .../reader/mapper/CursorInputMapper.h | 11 +- .../mapper/ExternalStylusInputMapper.cpp | 20 +- .../reader/mapper/ExternalStylusInputMapper.h | 17 +- .../reader/mapper/InputMapper.cpp | 31 +- .../inputflinger/reader/mapper/InputMapper.h | 21 +- .../reader/mapper/JoystickInputMapper.cpp | 38 +- .../reader/mapper/JoystickInputMapper.h | 11 +- .../reader/mapper/KeyboardInputMapper.cpp | 62 +- .../reader/mapper/KeyboardInputMapper.h | 42 +- .../reader/mapper/MultiTouchInputMapper.cpp | 9 +- .../reader/mapper/MultiTouchInputMapper.h | 4 +- .../mapper/RotaryEncoderInputMapper.cpp | 36 +- .../reader/mapper/RotaryEncoderInputMapper.h | 11 +- .../reader/mapper/SensorInputMapper.cpp | 35 +- .../reader/mapper/SensorInputMapper.h | 10 +- .../reader/mapper/SingleTouchInputMapper.cpp | 9 +- .../reader/mapper/SingleTouchInputMapper.h | 4 +- .../reader/mapper/SwitchInputMapper.cpp | 15 +- .../reader/mapper/SwitchInputMapper.h | 4 +- .../mapper/TouchCursorInputMapperCommon.h | 38 +- .../reader/mapper/TouchInputMapper.cpp | 648 ++++++++++-------- .../reader/mapper/TouchInputMapper.h | 103 +-- .../reader/mapper/VibratorInputMapper.cpp | 40 +- .../reader/mapper/VibratorInputMapper.h | 13 +- .../inputflinger/tests/InputReader_test.cpp | 185 ++--- .../inputflinger/tests/TestInputListener.cpp | 18 +- .../inputflinger/tests/TestInputListener.h | 2 +- .../tests/fuzzers/CursorInputFuzzer.cpp | 20 +- .../tests/fuzzers/FuzzContainer.h | 13 +- .../tests/fuzzers/KeyboardInputFuzzer.cpp | 11 +- .../tests/fuzzers/MapperHelpers.h | 8 +- .../tests/fuzzers/MultiTouchInputFuzzer.cpp | 23 +- .../tests/fuzzers/SwitchInputFuzzer.cpp | 2 +- 43 files changed, 1087 insertions(+), 775 deletions(-) diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp index 0972e224e9..d33b29888f 100644 --- a/services/inputflinger/InputListener.cpp +++ b/services/inputflinger/InputListener.cpp @@ -31,6 +31,11 @@ using android::base::StringPrintf; namespace android { +std::list& operator+=(std::list& keep, std::list&& consume) { + keep.splice(keep.end(), consume); + return keep; +} + // --- InputListenerInterface --- // Helper to std::visit with lambdas. diff --git a/services/inputflinger/NotifyArgs.cpp b/services/inputflinger/NotifyArgs.cpp index 1f37774218..b192ad73c3 100644 --- a/services/inputflinger/NotifyArgs.cpp +++ b/services/inputflinger/NotifyArgs.cpp @@ -225,4 +225,27 @@ NotifyPointerCaptureChangedArgs::NotifyPointerCaptureChangedArgs( int32_t id, nsecs_t eventTime, const PointerCaptureRequest& request) : id(id), eventTime(eventTime), request(request) {} +// Helper to std::visit with lambdas. +template +struct Visitor : V... {}; +// explicit deduction guide (not needed as of C++20) +template +Visitor(V...) -> Visitor; + +const char* toString(const NotifyArgs& args) { + Visitor toStringVisitor{ + [&](const NotifyConfigurationChangedArgs&) { return "NotifyConfigurationChangedArgs"; }, + [&](const NotifyKeyArgs&) { return "NotifyKeyArgs"; }, + [&](const NotifyMotionArgs&) { return "NotifyMotionArgs"; }, + [&](const NotifySensorArgs&) { return "NotifySensorArgs"; }, + [&](const NotifySwitchArgs&) { return "NotifySwitchArgs"; }, + [&](const NotifyDeviceResetArgs&) { return "NotifyDeviceResetArgs"; }, + [&](const NotifyPointerCaptureChangedArgs&) { + return "NotifyPointerCaptureChangedArgs"; + }, + [&](const NotifyVibratorStateArgs&) { return "NotifyVibratorStateArgs"; }, + }; + return std::visit(toStringVisitor, args); +} + } // namespace android diff --git a/services/inputflinger/include/InputListener.h b/services/inputflinger/include/InputListener.h index 8647bcb737..1bb19686e7 100644 --- a/services/inputflinger/include/InputListener.h +++ b/services/inputflinger/include/InputListener.h @@ -25,6 +25,8 @@ namespace android { +std::list& operator+=(std::list& keep, std::list&& consume); + /* * The interface used by the InputReader to notify the InputListener about input events. */ diff --git a/services/inputflinger/include/NotifyArgs.h b/services/inputflinger/include/NotifyArgs.h index 611b1aac81..f28dbf3039 100644 --- a/services/inputflinger/include/NotifyArgs.h +++ b/services/inputflinger/include/NotifyArgs.h @@ -213,4 +213,6 @@ using NotifyArgs = std::variant; +const char* toString(const NotifyArgs& args); + } // namespace android diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 6b9b9f1255..5291776638 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -65,7 +65,8 @@ bool InputDevice::isEnabled() { return enabled; } -void InputDevice::setEnabled(bool enabled, nsecs_t when) { +std::list InputDevice::setEnabled(bool enabled, nsecs_t when) { + std::list out; if (enabled && mAssociatedDisplayPort && !mAssociatedViewport) { ALOGW("Cannot enable input device %s because it is associated with port %" PRIu8 ", " "but the corresponding viewport is not found", @@ -74,7 +75,7 @@ void InputDevice::setEnabled(bool enabled, nsecs_t when) { } if (isEnabled() == enabled) { - return; + return out; } // When resetting some devices, the driver needs to be queried to ensure that a proper reset is @@ -82,13 +83,14 @@ void InputDevice::setEnabled(bool enabled, nsecs_t when) { // but before disabling the device. See MultiTouchMotionAccumulator::reset for more information. if (enabled) { for_each_subdevice([](auto& context) { context.enableDevice(); }); - reset(when); + out += reset(when); } else { - reset(when); + out += reset(when); for_each_subdevice([](auto& context) { context.disableDevice(); }); } // Must change generation to flag this device as changed bumpGeneration(); + return out; } void InputDevice::dump(std::string& dump, const std::string& eventHubDevStr) { @@ -241,8 +243,9 @@ void InputDevice::removeEventHubDevice(int32_t eventHubId) { mDevices.erase(eventHubId); } -void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) { +std::list InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config, + uint32_t changes) { + std::list out; mSources = 0; mClasses = ftl::Flags(0); mControllerNumber = 0; @@ -313,7 +316,7 @@ void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config if (!changes || (changes & InputReaderConfiguration::CHANGE_ENABLED_STATE)) { auto it = config->disabledDevices.find(mId); bool enabled = it == config->disabledDevices.end(); - setEnabled(enabled, when); + out += setEnabled(enabled, when); } if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { @@ -366,37 +369,42 @@ void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config // For first-time configuration, only allow device to be disabled after mappers have // finished configuring. This is because we need to read some of the properties from // the device's open fd. - setEnabled(enabled, when); + out += setEnabled(enabled, when); } } - for_each_mapper([this, when, config, changes](InputMapper& mapper) { - mapper.configure(when, config, changes); + for_each_mapper([this, when, &config, changes, &out](InputMapper& mapper) { + out += mapper.configure(when, config, changes); mSources |= mapper.getSources(); }); // If a device is just plugged but it might be disabled, we need to update some info like // axis range of touch from each InputMapper first, then disable it. if (!changes) { - setEnabled(config->disabledDevices.find(mId) == config->disabledDevices.end(), when); + out += setEnabled(config->disabledDevices.find(mId) == config->disabledDevices.end(), + when); } } + return out; } -void InputDevice::reset(nsecs_t when) { - for_each_mapper([when](InputMapper& mapper) { mapper.reset(when); }); +std::list InputDevice::reset(nsecs_t when) { + std::list out; + for_each_mapper([&](InputMapper& mapper) { out += mapper.reset(when); }); mContext->updateGlobalMetaState(); - notifyReset(when); + out.push_back(notifyReset(when)); + return out; } -void InputDevice::process(const RawEvent* rawEvents, size_t count) { +std::list InputDevice::process(const RawEvent* rawEvents, size_t count) { // Process all of the events in order for each mapper. // We cannot simply ask each mapper to process them in bulk because mappers may // have side-effects that must be interleaved. For example, joystick movement events and // gamepad button presses are handled by different mappers but they should be dispatched // in the order received. + std::list out; for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) { if (DEBUG_RAW_EVENTS) { ALOGD("Input event: device=%d type=0x%04x code=0x%04x value=0x%08x when=%" PRId64, @@ -418,22 +426,27 @@ void InputDevice::process(const RawEvent* rawEvents, size_t count) { } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) { ALOGI("Detected input event buffer overrun for device %s.", getName().c_str()); mDropUntilNextSync = true; - reset(rawEvent->when); + out += reset(rawEvent->when); } else { - for_each_mapper_in_subdevice(rawEvent->deviceId, [rawEvent](InputMapper& mapper) { - mapper.process(rawEvent); + for_each_mapper_in_subdevice(rawEvent->deviceId, [&](InputMapper& mapper) { + out += mapper.process(rawEvent); }); } --count; } + return out; } -void InputDevice::timeoutExpired(nsecs_t when) { - for_each_mapper([when](InputMapper& mapper) { mapper.timeoutExpired(when); }); +std::list InputDevice::timeoutExpired(nsecs_t when) { + std::list out; + for_each_mapper([&](InputMapper& mapper) { out += mapper.timeoutExpired(when); }); + return out; } -void InputDevice::updateExternalStylusState(const StylusState& state) { - for_each_mapper([state](InputMapper& mapper) { mapper.updateExternalStylusState(state); }); +std::list InputDevice::updateExternalStylusState(const StylusState& state) { + std::list out; + for_each_mapper([&](InputMapper& mapper) { out += mapper.updateExternalStylusState(state); }); + return out; } InputDeviceInfo InputDevice::getDeviceInfo() { @@ -511,14 +524,17 @@ int32_t InputDevice::getKeyCodeForKeyLocation(int32_t locationKeyCode) const { return *result; } -void InputDevice::vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token) { - for_each_mapper([sequence, repeat, token](InputMapper& mapper) { - mapper.vibrate(sequence, repeat, token); - }); +std::list InputDevice::vibrate(const VibrationSequence& sequence, ssize_t repeat, + int32_t token) { + std::list out; + for_each_mapper([&](InputMapper& mapper) { out += mapper.vibrate(sequence, repeat, token); }); + return out; } -void InputDevice::cancelVibrate(int32_t token) { - for_each_mapper([token](InputMapper& mapper) { mapper.cancelVibrate(token); }); +std::list InputDevice::cancelVibrate(int32_t token) { + std::list out; + for_each_mapper([&](InputMapper& mapper) { out += mapper.cancelVibrate(token); }); + return out; } bool InputDevice::isVibrating() { @@ -561,8 +577,10 @@ void InputDevice::flushSensor(InputDeviceSensorType sensorType) { for_each_mapper([sensorType](InputMapper& mapper) { mapper.flushSensor(sensorType); }); } -void InputDevice::cancelTouch(nsecs_t when, nsecs_t readTime) { - for_each_mapper([when, readTime](InputMapper& mapper) { mapper.cancelTouch(when, readTime); }); +std::list InputDevice::cancelTouch(nsecs_t when, nsecs_t readTime) { + std::list out; + for_each_mapper([&](InputMapper& mapper) { out += mapper.cancelTouch(when, readTime); }); + return out; } bool InputDevice::setLightColor(int32_t lightId, int32_t color) { @@ -601,9 +619,8 @@ void InputDevice::bumpGeneration() { mGeneration = mContext->bumpGeneration(); } -void InputDevice::notifyReset(nsecs_t when) { - NotifyDeviceResetArgs args(mContext->getNextId(), when, mId); - mContext->getListener().notifyDeviceReset(&args); +NotifyDeviceResetArgs InputDevice::notifyReset(nsecs_t when) { + return NotifyDeviceResetArgs(mContext->getNextId(), when, mId); } std::optional InputDevice::getAssociatedDisplayId() { diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 86508766f5..428e999c1b 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -127,7 +127,7 @@ void InputReader::loopOnce() { mReaderIsAliveCondition.notify_all(); if (!events.empty()) { - processEventsLocked(events.data(), events.size()); + notifyAll(processEventsLocked(events.data(), events.size())); } if (mNextTimeout != LLONG_MAX) { @@ -137,7 +137,7 @@ void InputReader::loopOnce() { ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f); } mNextTimeout = LLONG_MAX; - timeoutExpiredLocked(now); + notifyAll(timeoutExpiredLocked(now)); } } @@ -162,7 +162,8 @@ void InputReader::loopOnce() { mQueuedListener.flush(); } -void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) { +std::list InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) { + std::list out; for (const RawEvent* rawEvent = rawEvents; count;) { int32_t type = rawEvent->type; size_t batchSize = 1; @@ -178,7 +179,7 @@ void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) { if (DEBUG_RAW_EVENTS) { ALOGD("BatchSize: %zu Count: %zu", batchSize, count); } - processEventsForDeviceLocked(deviceId, rawEvent, batchSize); + out += processEventsForDeviceLocked(deviceId, rawEvent, batchSize); } else { switch (rawEvent->type) { case EventHubInterface::DEVICE_ADDED: @@ -198,6 +199,7 @@ void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) { count -= batchSize; rawEvent += batchSize; } + return out; } void InputReader::addDeviceLocked(nsecs_t when, int32_t eventHubId) { @@ -208,8 +210,9 @@ void InputReader::addDeviceLocked(nsecs_t when, int32_t eventHubId) { InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(eventHubId); std::shared_ptr device = createDeviceLocked(eventHubId, identifier); - device->configure(when, &mConfig, 0); - device->reset(when); + + notifyAll(device->configure(when, &mConfig, 0)); + notifyAll(device->reset(when)); if (device->isIgnored()) { ALOGI("Device added: id=%d, eventHubId=%d, name='%s', descriptor='%s' " @@ -282,10 +285,12 @@ void InputReader::removeDeviceLocked(nsecs_t when, int32_t eventHubId) { notifyExternalStylusPresenceChangedLocked(); } + std::list resetEvents; if (device->hasEventHubDevices()) { - device->configure(when, &mConfig, 0); + resetEvents += device->configure(when, &mConfig, 0); } - device->reset(when); + resetEvents += device->reset(when); + notifyAll(std::move(resetEvents)); } std::shared_ptr InputReader::createDeviceLocked( @@ -308,21 +313,22 @@ std::shared_ptr InputReader::createDeviceLocked( return device; } -void InputReader::processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents, - size_t count) { +std::list InputReader::processEventsForDeviceLocked(int32_t eventHubId, + const RawEvent* rawEvents, + size_t count) { auto deviceIt = mDevices.find(eventHubId); if (deviceIt == mDevices.end()) { ALOGW("Discarding event for unknown eventHubId %d.", eventHubId); - return; + return {}; } std::shared_ptr& device = deviceIt->second; if (device->isIgnored()) { // ALOGD("Discarding event for ignored deviceId %d.", deviceId); - return; + return {}; } - device->process(rawEvents, count); + return device->process(rawEvents, count); } InputDevice* InputReader::findInputDeviceLocked(int32_t deviceId) const { @@ -336,13 +342,15 @@ InputDevice* InputReader::findInputDeviceLocked(int32_t deviceId) const { return nullptr; } -void InputReader::timeoutExpiredLocked(nsecs_t when) { +std::list InputReader::timeoutExpiredLocked(nsecs_t when) { + std::list out; for (auto& devicePair : mDevices) { std::shared_ptr& device = devicePair.second; if (!device->isIgnored()) { - device->timeoutExpired(when); + out += device->timeoutExpired(when); } } + return out; } int32_t InputReader::nextInputDeviceIdLocked() { @@ -377,7 +385,7 @@ void InputReader::refreshConfigurationLocked(uint32_t changes) { } else { for (auto& devicePair : mDevices) { std::shared_ptr& device = devicePair.second; - device->configure(now, &mConfig, changes); + notifyAll(device->configure(now, &mConfig, changes)); } } @@ -394,6 +402,12 @@ void InputReader::refreshConfigurationLocked(uint32_t changes) { } } +void InputReader::notifyAll(std::list&& argsList) { + for (const NotifyArgs& args : argsList) { + mQueuedListener.notify(args); + } +} + void InputReader::updateGlobalMetaStateLocked() { mGlobalMetaState = 0; @@ -432,11 +446,13 @@ void InputReader::getExternalStylusDevicesLocked(std::vector& o } } -void InputReader::dispatchExternalStylusStateLocked(const StylusState& state) { +std::list InputReader::dispatchExternalStylusStateLocked(const StylusState& state) { + std::list out; for (auto& devicePair : mDevices) { std::shared_ptr& device = devicePair.second; - device->updateExternalStylusState(state); + out += device->updateExternalStylusState(state); } + return out; } void InputReader::disableVirtualKeysUntilLocked(nsecs_t time) { @@ -642,7 +658,7 @@ void InputReader::vibrate(int32_t deviceId, const VibrationSequence& sequence, s InputDevice* device = findInputDeviceLocked(deviceId); if (device) { - device->vibrate(sequence, repeat, token); + notifyAll(device->vibrate(sequence, repeat, token)); } } @@ -651,7 +667,7 @@ void InputReader::cancelVibrate(int32_t deviceId, int32_t token) { InputDevice* device = findInputDeviceLocked(deviceId); if (device) { - device->cancelVibrate(token); + notifyAll(device->cancelVibrate(token)); } } @@ -1015,18 +1031,15 @@ void InputReader::ContextImpl::getExternalStylusDevices(std::vectorgetExternalStylusDevicesLocked(outDevices); } -void InputReader::ContextImpl::dispatchExternalStylusState(const StylusState& state) { - mReader->dispatchExternalStylusStateLocked(state); +std::list InputReader::ContextImpl::dispatchExternalStylusState( + const StylusState& state) { + return mReader->dispatchExternalStylusStateLocked(state); } InputReaderPolicyInterface* InputReader::ContextImpl::getPolicy() { return mReader->mPolicy.get(); } -InputListenerInterface& InputReader::ContextImpl::getListener() { - return mReader->mQueuedListener; -} - EventHubInterface* InputReader::ContextImpl::getEventHub() { return mReader->mEventHub.get(); } diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 4ae9ae9dd0..afb1bed1f2 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -29,6 +29,7 @@ #include "EventHub.h" #include "InputReaderBase.h" #include "InputReaderContext.h" +#include "NotifyArgs.h" namespace android { @@ -69,16 +70,18 @@ public: inline bool isIgnored() { return !getMapperCount(); } bool isEnabled(); - void setEnabled(bool enabled, nsecs_t when); + [[nodiscard]] std::list setEnabled(bool enabled, nsecs_t when); void dump(std::string& dump, const std::string& eventHubDevStr); void addEventHubDevice(int32_t eventHubId, bool populateMappers = true); void removeEventHubDevice(int32_t eventHubId); - void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes); - void reset(nsecs_t when); - void process(const RawEvent* rawEvents, size_t count); - void timeoutExpired(nsecs_t when); - void updateExternalStylusState(const StylusState& state); + [[nodiscard]] std::list configure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes); + [[nodiscard]] std::list reset(nsecs_t when); + [[nodiscard]] std::list process(const RawEvent* rawEvents, size_t count); + [[nodiscard]] std::list timeoutExpired(nsecs_t when); + [[nodiscard]] std::list updateExternalStylusState(const StylusState& state); InputDeviceInfo getDeviceInfo(); int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); @@ -87,11 +90,12 @@ public: int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const; bool markSupportedKeyCodes(uint32_t sourceMask, const std::vector& keyCodes, uint8_t* outFlags); - void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token); - void cancelVibrate(int32_t token); + [[nodiscard]] std::list vibrate(const VibrationSequence& sequence, ssize_t repeat, + int32_t token); + [[nodiscard]] std::list cancelVibrate(int32_t token); bool isVibrating(); std::vector getVibratorIds(); - void cancelTouch(nsecs_t when, nsecs_t readTime); + [[nodiscard]] std::list cancelTouch(nsecs_t when, nsecs_t readTime); bool enableSensor(InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod, std::chrono::microseconds maxBatchReportLatency); void disableSensor(InputDeviceSensorType sensorType); @@ -109,7 +113,7 @@ public: void bumpGeneration(); - void notifyReset(nsecs_t when); + [[nodiscard]] NotifyDeviceResetArgs notifyReset(nsecs_t when); inline const PropertyMap& getConfiguration() { return mConfiguration; } inline EventHubInterface* getEventHub() { return mContext->getEventHub(); } @@ -395,7 +399,9 @@ public: inline std::optional getAssociatedViewport() const { return mDevice.getAssociatedViewport(); } - inline void cancelTouch(nsecs_t when, nsecs_t readTime) { mDevice.cancelTouch(when, readTime); } + [[nodiscard]] inline std::list cancelTouch(nsecs_t when, nsecs_t readTime) { + return mDevice.cancelTouch(when, readTime); + } inline void bumpGeneration() { mDevice.bumpGeneration(); } inline const PropertyMap& getConfiguration() { return mDevice.getConfiguration(); } diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h index 012d43fc3a..de268cf66c 100644 --- a/services/inputflinger/reader/include/InputReader.h +++ b/services/inputflinger/reader/include/InputReader.h @@ -142,10 +142,9 @@ protected: int32_t bumpGeneration() NO_THREAD_SAFETY_ANALYSIS override; void getExternalStylusDevices(std::vector& outDevices) REQUIRES(mReader->mLock) override; - void dispatchExternalStylusState(const StylusState& outState) + [[nodiscard]] std::list dispatchExternalStylusState(const StylusState& outState) REQUIRES(mReader->mLock) override; InputReaderPolicyInterface* getPolicy() REQUIRES(mReader->mLock) override; - InputListenerInterface& getListener() REQUIRES(mReader->mLock) override; EventHubInterface* getEventHub() REQUIRES(mReader->mLock) override; int32_t getNextId() NO_THREAD_SAFETY_ANALYSIS override; void updateLedMetaState(int32_t metaState) REQUIRES(mReader->mLock) override; @@ -181,13 +180,15 @@ private: mDeviceToEventHubIdsMap GUARDED_BY(mLock); // low-level input event decoding and device management - void processEventsLocked(const RawEvent* rawEvents, size_t count) REQUIRES(mLock); + [[nodiscard]] std::list processEventsLocked(const RawEvent* rawEvents, size_t count) + REQUIRES(mLock); void addDeviceLocked(nsecs_t when, int32_t eventHubId) REQUIRES(mLock); void removeDeviceLocked(nsecs_t when, int32_t eventHubId) REQUIRES(mLock); - void processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents, size_t count) - REQUIRES(mLock); - void timeoutExpiredLocked(nsecs_t when) REQUIRES(mLock); + [[nodiscard]] std::list processEventsForDeviceLocked(int32_t eventHubId, + const RawEvent* rawEvents, + size_t count) REQUIRES(mLock); + [[nodiscard]] std::list timeoutExpiredLocked(nsecs_t when) REQUIRES(mLock); void handleConfigurationChangedLocked(nsecs_t when) REQUIRES(mLock); @@ -201,7 +202,8 @@ private: void notifyExternalStylusPresenceChangedLocked() REQUIRES(mLock); void getExternalStylusDevicesLocked(std::vector& outDevices) REQUIRES(mLock); - void dispatchExternalStylusStateLocked(const StylusState& state) REQUIRES(mLock); + [[nodiscard]] std::list dispatchExternalStylusStateLocked(const StylusState& state) + REQUIRES(mLock); // The PointerController that is shared among all the input devices that need it. std::weak_ptr mPointerController; @@ -228,6 +230,8 @@ private: uint32_t mConfigurationChangesToRefresh GUARDED_BY(mLock); void refreshConfigurationLocked(uint32_t changes) REQUIRES(mLock); + void notifyAll(std::list&& argsList); + PointerCaptureRequest mCurrentPointerCaptureRequest GUARDED_BY(mLock); // state queries diff --git a/services/inputflinger/reader/include/InputReaderContext.h b/services/inputflinger/reader/include/InputReaderContext.h index f2f156c6e2..0beace19ab 100644 --- a/services/inputflinger/reader/include/InputReaderContext.h +++ b/services/inputflinger/reader/include/InputReaderContext.h @@ -17,6 +17,7 @@ #pragma once #include +#include "NotifyArgs.h" #include @@ -51,10 +52,10 @@ public: virtual int32_t bumpGeneration() = 0; virtual void getExternalStylusDevices(std::vector& outDevices) = 0; - virtual void dispatchExternalStylusState(const StylusState& outState) = 0; + [[nodiscard]] virtual std::list dispatchExternalStylusState( + const StylusState& outState) = 0; virtual InputReaderPolicyInterface* getPolicy() = 0; - virtual InputListenerInterface& getListener() = 0; virtual EventHubInterface* getEventHub() = 0; virtual int32_t getNextId() = 0; diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index d6d324b5b2..c691ca943f 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -126,9 +126,10 @@ void CursorInputMapper::dump(std::string& dump) { dump += StringPrintf(INDENT3 "DownTime: %" PRId64 "\n", mDownTime); } -void CursorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) { - InputMapper::configure(when, config, changes); +std::list CursorInputMapper::configure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) { + std::list out = InputMapper::configure(when, config, changes); if (!changes) { // first time only mCursorScrollAccumulator.configure(getDeviceContext()); @@ -187,8 +188,7 @@ void CursorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* } bumpGeneration(); if (changes) { - NotifyDeviceResetArgs args(getContext()->getNextId(), when, getDeviceId()); - getListener().notifyDeviceReset(&args); + out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId())); } } @@ -241,6 +241,7 @@ void CursorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* bumpGeneration(); } + return out; } void CursorInputMapper::configureParameters() { @@ -272,7 +273,7 @@ void CursorInputMapper::dumpParameters(std::string& dump) { dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware)); } -void CursorInputMapper::reset(nsecs_t when) { +std::list CursorInputMapper::reset(nsecs_t when) { mButtonState = 0; mDownTime = 0; @@ -284,23 +285,26 @@ void CursorInputMapper::reset(nsecs_t when) { mCursorMotionAccumulator.reset(getDeviceContext()); mCursorScrollAccumulator.reset(getDeviceContext()); - InputMapper::reset(when); + return InputMapper::reset(when); } -void CursorInputMapper::process(const RawEvent* rawEvent) { +std::list CursorInputMapper::process(const RawEvent* rawEvent) { + std::list out; mCursorButtonAccumulator.process(rawEvent); mCursorMotionAccumulator.process(rawEvent); mCursorScrollAccumulator.process(rawEvent); if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { - sync(rawEvent->when, rawEvent->readTime); + out += sync(rawEvent->when, rawEvent->readTime); } + return out; } -void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { +std::list CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { + std::list out; if (!mDisplayId) { // Ignore events when there is no target display configured. - return; + return out; } int32_t lastButtonState = mButtonState; @@ -391,8 +395,9 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { } // Synthesize key down from buttons if needed. - synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, readTime, getDeviceId(), - mSource, *mDisplayId, policyFlags, lastButtonState, currentButtonState); + out += synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, readTime, getDeviceId(), + mSource, *mDisplayId, policyFlags, lastButtonState, + currentButtonState); // Send motion event. if (downChanged || moved || scrolled || buttonsChanged) { @@ -412,40 +417,38 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { while (!released.isEmpty()) { int32_t actionButton = BitSet32::valueForBit(released.clearFirstMarkedBit()); buttonState &= ~actionButton; - NotifyMotionArgs releaseArgs(getContext()->getNextId(), when, readTime, - getDeviceId(), mSource, *mDisplayId, policyFlags, - AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0, - metaState, buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, - &pointerCoords, mXPrecision, mYPrecision, - xCursorPosition, yCursorPosition, downTime, - /* videoFrames */ {}); - getListener().notifyMotion(&releaseArgs); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, + getDeviceId(), mSource, *mDisplayId, policyFlags, + AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0, + metaState, buttonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, + &pointerCoords, mXPrecision, mYPrecision, + xCursorPosition, yCursorPosition, downTime, + /* videoFrames */ {})); } } - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - *mDisplayId, policyFlags, motionEventAction, 0, 0, metaState, - currentButtonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords, - mXPrecision, mYPrecision, xCursorPosition, yCursorPosition, downTime, - /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, *mDisplayId, policyFlags, motionEventAction, 0, 0, + metaState, currentButtonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, + &pointerCoords, mXPrecision, mYPrecision, xCursorPosition, + yCursorPosition, downTime, + /* videoFrames */ {})); if (buttonsPressed) { BitSet32 pressed(buttonsPressed); while (!pressed.isEmpty()) { int32_t actionButton = BitSet32::valueForBit(pressed.clearFirstMarkedBit()); buttonState |= actionButton; - NotifyMotionArgs pressArgs(getContext()->getNextId(), when, readTime, getDeviceId(), - mSource, *mDisplayId, policyFlags, - AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0, - metaState, buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, - &pointerCoords, mXPrecision, mYPrecision, - xCursorPosition, yCursorPosition, downTime, - /* videoFrames */ {}); - getListener().notifyMotion(&pressArgs); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, + getDeviceId(), mSource, *mDisplayId, policyFlags, + AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0, + metaState, buttonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, + &pointerCoords, mXPrecision, mYPrecision, + xCursorPosition, yCursorPosition, downTime, + /* videoFrames */ {})); } } @@ -453,14 +456,14 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { // Send hover move after UP to tell the application that the mouse is hovering now. if (motionEventAction == AMOTION_EVENT_ACTION_UP && (mSource == AINPUT_SOURCE_MOUSE)) { - NotifyMotionArgs hoverArgs(getContext()->getNextId(), when, readTime, getDeviceId(), - mSource, *mDisplayId, policyFlags, - AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState, - currentButtonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, - &pointerCoords, mXPrecision, mYPrecision, xCursorPosition, - yCursorPosition, downTime, /* videoFrames */ {}); - getListener().notifyMotion(&hoverArgs); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, *mDisplayId, policyFlags, + AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState, + currentButtonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, + &pointerCoords, mXPrecision, mYPrecision, + xCursorPosition, yCursorPosition, downTime, + /* videoFrames */ {})); } // Send scroll events. @@ -468,23 +471,25 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll); - NotifyMotionArgs scrollArgs(getContext()->getNextId(), when, readTime, getDeviceId(), - mSource, *mDisplayId, policyFlags, - AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState, - currentButtonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, - &pointerCoords, mXPrecision, mYPrecision, xCursorPosition, - yCursorPosition, downTime, /* videoFrames */ {}); - getListener().notifyMotion(&scrollArgs); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, *mDisplayId, policyFlags, + AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState, + currentButtonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, + &pointerCoords, mXPrecision, mYPrecision, + xCursorPosition, yCursorPosition, downTime, + /* videoFrames */ {})); } } // Synthesize key up from buttons if needed. - synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, readTime, getDeviceId(), mSource, - *mDisplayId, policyFlags, lastButtonState, currentButtonState); + out += synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, readTime, getDeviceId(), + mSource, *mDisplayId, policyFlags, lastButtonState, + currentButtonState); mCursorMotionAccumulator.finishSync(); mCursorScrollAccumulator.finishSync(); + return out; } int32_t CursorInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) { diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index a0229a74c7..6a4275ed54 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -58,10 +58,11 @@ public: virtual uint32_t getSources() const override; virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; virtual void dump(std::string& dump) override; - virtual void configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) override; - virtual void reset(nsecs_t when) override; - virtual void process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list configure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) override; + [[nodiscard]] std::list reset(nsecs_t when) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override; @@ -124,7 +125,7 @@ private: void configureParameters(); void dumpParameters(std::string& dump); - void sync(nsecs_t when, nsecs_t readTime); + [[nodiscard]] std::list sync(nsecs_t when, nsecs_t readTime); }; } // namespace android diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp index 6b5d37f8d5..0404c9acc1 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp @@ -44,28 +44,32 @@ void ExternalStylusInputMapper::dump(std::string& dump) { dumpStylusState(dump, mStylusState); } -void ExternalStylusInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) { +std::list ExternalStylusInputMapper::configure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) { getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPressureAxis); mTouchButtonAccumulator.configure(getDeviceContext()); + return {}; } -void ExternalStylusInputMapper::reset(nsecs_t when) { +std::list ExternalStylusInputMapper::reset(nsecs_t when) { mSingleTouchMotionAccumulator.reset(getDeviceContext()); mTouchButtonAccumulator.reset(getDeviceContext()); - InputMapper::reset(when); + return InputMapper::reset(when); } -void ExternalStylusInputMapper::process(const RawEvent* rawEvent) { +std::list ExternalStylusInputMapper::process(const RawEvent* rawEvent) { + std::list out; mSingleTouchMotionAccumulator.process(rawEvent); mTouchButtonAccumulator.process(rawEvent); if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { - sync(rawEvent->when); + out += sync(rawEvent->when); } + return out; } -void ExternalStylusInputMapper::sync(nsecs_t when) { +std::list ExternalStylusInputMapper::sync(nsecs_t when) { mStylusState.clear(); mStylusState.when = when; @@ -86,7 +90,7 @@ void ExternalStylusInputMapper::sync(nsecs_t when) { mStylusState.buttons = mTouchButtonAccumulator.getButtonState(); - getContext()->dispatchExternalStylusState(mStylusState); + return getContext()->dispatchExternalStylusState(mStylusState); } } // namespace android diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h index 03d9909b51..b6c9055cb7 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h @@ -29,13 +29,14 @@ public: explicit ExternalStylusInputMapper(InputDeviceContext& deviceContext); virtual ~ExternalStylusInputMapper() = default; - virtual uint32_t getSources() const override; - virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; - virtual void dump(std::string& dump) override; - virtual void configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) override; - virtual void reset(nsecs_t when) override; - virtual void process(const RawEvent* rawEvent) override; + uint32_t getSources() const override; + void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; + void dump(std::string& dump) override; + [[nodiscard]] std::list configure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) override; + [[nodiscard]] std::list reset(nsecs_t when) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; private: SingleTouchMotionAccumulator mSingleTouchMotionAccumulator; @@ -44,7 +45,7 @@ private: StylusState mStylusState; - void sync(nsecs_t when); + [[nodiscard]] std::list sync(nsecs_t when); }; } // namespace android diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp index 75cebf3ddb..844afe0522 100644 --- a/services/inputflinger/reader/mapper/InputMapper.cpp +++ b/services/inputflinger/reader/mapper/InputMapper.cpp @@ -32,12 +32,18 @@ void InputMapper::populateDeviceInfo(InputDeviceInfo* info) { void InputMapper::dump(std::string& dump) {} -void InputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) {} +std::list InputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, + uint32_t changes) { + return {}; +} -void InputMapper::reset(nsecs_t when) {} +std::list InputMapper::reset(nsecs_t when) { + return {}; +} -void InputMapper::timeoutExpired(nsecs_t when) {} +std::list InputMapper::timeoutExpired(nsecs_t when) { + return {}; +} int32_t InputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { return AKEY_STATE_UNKNOWN; @@ -60,9 +66,14 @@ bool InputMapper::markSupportedKeyCodes(uint32_t sourceMask, const std::vector InputMapper::vibrate(const VibrationSequence& sequence, ssize_t repeat, + int32_t token) { + return {}; +} -void InputMapper::cancelVibrate(int32_t token) {} +std::list InputMapper::cancelVibrate(int32_t token) { + return {}; +} bool InputMapper::isVibrating() { return false; @@ -72,7 +83,9 @@ std::vector InputMapper::getVibratorIds() { return {}; } -void InputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) {} +std::list InputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) { + return {}; +} bool InputMapper::enableSensor(InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod, @@ -92,7 +105,9 @@ bool InputMapper::updateMetaState(int32_t keyCode) { return false; } -void InputMapper::updateExternalStylusState(const StylusState& state) {} +std::list InputMapper::updateExternalStylusState(const StylusState& state) { + return {}; +} status_t InputMapper::getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo) { return getDeviceContext().getAbsoluteAxisInfo(axis, axisInfo); diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h index 5567cac6a9..104305b730 100644 --- a/services/inputflinger/reader/mapper/InputMapper.h +++ b/services/inputflinger/reader/mapper/InputMapper.h @@ -20,6 +20,7 @@ #include "InputDevice.h" #include "InputListener.h" #include "InputReaderContext.h" +#include "NotifyArgs.h" #include "StylusState.h" #include "VibrationElement.h" @@ -48,15 +49,16 @@ public: inline const std::string getDeviceName() const { return mDeviceContext.getName(); } inline InputReaderContext* getContext() { return mDeviceContext.getContext(); } inline InputReaderPolicyInterface* getPolicy() { return getContext()->getPolicy(); } - inline InputListenerInterface& getListener() { return getContext()->getListener(); } virtual uint32_t getSources() const = 0; virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo); virtual void dump(std::string& dump); - virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes); - virtual void reset(nsecs_t when); - virtual void process(const RawEvent* rawEvent) = 0; - virtual void timeoutExpired(nsecs_t when); + [[nodiscard]] virtual std::list configure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes); + [[nodiscard]] virtual std::list reset(nsecs_t when); + [[nodiscard]] virtual std::list process(const RawEvent* rawEvent) = 0; + [[nodiscard]] virtual std::list timeoutExpired(nsecs_t when); virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); @@ -65,11 +67,12 @@ public: virtual bool markSupportedKeyCodes(uint32_t sourceMask, const std::vector& keyCodes, uint8_t* outFlags); - virtual void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token); - virtual void cancelVibrate(int32_t token); + [[nodiscard]] virtual std::list vibrate(const VibrationSequence& sequence, + ssize_t repeat, int32_t token); + [[nodiscard]] virtual std::list cancelVibrate(int32_t token); virtual bool isVibrating(); virtual std::vector getVibratorIds(); - virtual void cancelTouch(nsecs_t when, nsecs_t readTime); + [[nodiscard]] virtual std::list cancelTouch(nsecs_t when, nsecs_t readTime); virtual bool enableSensor(InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod, std::chrono::microseconds maxBatchReportLatency); @@ -91,7 +94,7 @@ public: */ virtual bool updateMetaState(int32_t keyCode); - virtual void updateExternalStylusState(const StylusState& state); + [[nodiscard]] virtual std::list updateExternalStylusState(const StylusState& state); virtual std::optional getAssociatedDisplayId() { return std::nullopt; } virtual void updateLedState(bool reset) {} diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp index 7d30d0c1eb..42b80129a1 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp @@ -103,9 +103,10 @@ void JoystickInputMapper::dump(std::string& dump) { } } -void JoystickInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) { - InputMapper::configure(when, config, changes); +std::list JoystickInputMapper::configure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) { + std::list out = InputMapper::configure(when, config, changes); if (!changes) { // first time only // Collect all axes. @@ -164,6 +165,7 @@ void JoystickInputMapper::configure(nsecs_t when, const InputReaderConfiguration it++; } } + return out; } JoystickInputMapper::Axis JoystickInputMapper::createAxis(const AxisInfo& axisInfo, @@ -246,17 +248,18 @@ bool JoystickInputMapper::isCenteredAxis(int32_t axis) { } } -void JoystickInputMapper::reset(nsecs_t when) { +std::list JoystickInputMapper::reset(nsecs_t when) { // Recenter all axes. for (std::pair& pair : mAxes) { Axis& axis = pair.second; axis.resetValue(); } - InputMapper::reset(when); + return InputMapper::reset(when); } -void JoystickInputMapper::process(const RawEvent* rawEvent) { +std::list JoystickInputMapper::process(const RawEvent* rawEvent) { + std::list out; switch (rawEvent->type) { case EV_ABS: { auto it = mAxes.find(rawEvent->code); @@ -298,16 +301,18 @@ void JoystickInputMapper::process(const RawEvent* rawEvent) { case EV_SYN: switch (rawEvent->code) { case SYN_REPORT: - sync(rawEvent->when, rawEvent->readTime, false /*force*/); + out += sync(rawEvent->when, rawEvent->readTime, false /*force*/); break; } break; } + return out; } -void JoystickInputMapper::sync(nsecs_t when, nsecs_t readTime, bool force) { +std::list JoystickInputMapper::sync(nsecs_t when, nsecs_t readTime, bool force) { + std::list out; if (!filterAxes(force)) { - return; + return out; } int32_t metaState = getContext()->getGlobalMetaState(); @@ -340,13 +345,14 @@ void JoystickInputMapper::sync(nsecs_t when, nsecs_t readTime, bool force) { displayId = getDeviceContext().getAssociatedViewport()->displayId; } - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), - AINPUT_SOURCE_JOYSTICK, displayId, policyFlags, AMOTION_EVENT_ACTION_MOVE, - 0, 0, metaState, buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords, 0, 0, - AMOTION_EVENT_INVALID_CURSOR_POSITION, - AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + AINPUT_SOURCE_JOYSTICK, displayId, policyFlags, + AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState, buttonState, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, + &pointerProperties, &pointerCoords, 0, 0, + AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {})); + return out; } void JoystickInputMapper::setPointerCoordsAxisValue(PointerCoords* pointerCoords, int32_t axis, diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.h b/services/inputflinger/reader/mapper/JoystickInputMapper.h index e0023970de..72b8a528b5 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.h +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h @@ -28,10 +28,11 @@ public: virtual uint32_t getSources() const override; virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; virtual void dump(std::string& dump) override; - virtual void configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) override; - virtual void reset(nsecs_t when) override; - virtual void process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list configure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) override; + [[nodiscard]] std::list reset(nsecs_t when) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; private: struct Axis { @@ -91,7 +92,7 @@ private: // Axes indexed by raw ABS_* axis index. std::unordered_map mAxes; - void sync(nsecs_t when, nsecs_t readTime, bool force); + [[nodiscard]] std::list sync(nsecs_t when, nsecs_t readTime, bool force); bool haveAxis(int32_t axisId); void pruneAxes(bool ignoreExplicitlyMappedAxes); diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index 9bb6273d71..8704d1b211 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -143,9 +143,10 @@ std::optional KeyboardInputMapper::findViewport( return std::nullopt; } -void KeyboardInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) { - InputMapper::configure(when, config, changes); +std::list KeyboardInputMapper::configure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) { + std::list out = InputMapper::configure(when, config, changes); if (!changes) { // first time only // Configure basic parameters. @@ -155,6 +156,7 @@ void KeyboardInputMapper::configure(nsecs_t when, const InputReaderConfiguration if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { mViewport = findViewport(when, config); } + return out; } static void mapStemKey(int32_t keyCode, const PropertyMap& config, char const* property) { @@ -194,16 +196,18 @@ void KeyboardInputMapper::dumpParameters(std::string& dump) { dump += StringPrintf(INDENT4 "HandlesKeyRepeat: %s\n", toString(mParameters.handlesKeyRepeat)); } -void KeyboardInputMapper::reset(nsecs_t when) { - cancelAllDownKeys(when); +std::list KeyboardInputMapper::reset(nsecs_t when) { + std::list out = cancelAllDownKeys(when); mCurrentHidUsage = 0; resetLedState(); - InputMapper::reset(when); + out += InputMapper::reset(when); + return out; } -void KeyboardInputMapper::process(const RawEvent* rawEvent) { +std::list KeyboardInputMapper::process(const RawEvent* rawEvent) { + std::list out; switch (rawEvent->type) { case EV_KEY: { int32_t scanCode = rawEvent->code; @@ -211,8 +215,8 @@ void KeyboardInputMapper::process(const RawEvent* rawEvent) { mCurrentHidUsage = 0; if (isKeyboardOrGamepadKey(scanCode)) { - processKey(rawEvent->when, rawEvent->readTime, rawEvent->value != 0, scanCode, - usageCode); + out += processKey(rawEvent->when, rawEvent->readTime, rawEvent->value != 0, + scanCode, usageCode); } break; } @@ -228,6 +232,7 @@ void KeyboardInputMapper::process(const RawEvent* rawEvent) { } } } + return out; } bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) { @@ -265,8 +270,9 @@ bool KeyboardInputMapper::isMediaKey(int32_t keyCode) { return false; } -void KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, int32_t scanCode, - int32_t usageCode) { +std::list KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, + int32_t scanCode, int32_t usageCode) { + std::list out; int32_t keyCode; int32_t keyMetaState; uint32_t policyFlags; @@ -295,10 +301,10 @@ void KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, // key down if ((policyFlags & POLICY_FLAG_VIRTUAL) && getContext()->shouldDropVirtualKey(when, keyCode, scanCode)) { - return; + return out; } if (policyFlags & POLICY_FLAG_GESTURE) { - getDeviceContext().cancelTouch(when, readTime); + out += getDeviceContext().cancelTouch(when, readTime); } KeyDown keyDown; @@ -320,7 +326,7 @@ void KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, ALOGI("Dropping key up from device %s because the key was not down. " "keyCode=%d, scanCode=%d", getDeviceName().c_str(), keyCode, scanCode); - return; + return out; } } @@ -347,11 +353,12 @@ void KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT; } - NotifyKeyArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - getDisplayId(), policyFlags, - down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, - AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime); - getListener().notifyKey(&args); + out.push_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, + getDisplayId(), policyFlags, + down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, + AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, + downTime)); + return out; } ssize_t KeyboardInputMapper::findKeyDown(int32_t scanCode) { @@ -470,19 +477,20 @@ std::optional KeyboardInputMapper::getAssociatedDisplayId() { return std::nullopt; } -void KeyboardInputMapper::cancelAllDownKeys(nsecs_t when) { +std::list KeyboardInputMapper::cancelAllDownKeys(nsecs_t when) { + std::list out; size_t n = mKeyDowns.size(); for (size_t i = 0; i < n; i++) { - NotifyKeyArgs args(getContext()->getNextId(), when, systemTime(SYSTEM_TIME_MONOTONIC), - getDeviceId(), mSource, getDisplayId(), 0 /*policyFlags*/, - AKEY_EVENT_ACTION_UP, - AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED, - mKeyDowns[i].keyCode, mKeyDowns[i].scanCode, AMETA_NONE, - mKeyDowns[i].downTime); - getListener().notifyKey(&args); + out.push_back(NotifyKeyArgs(getContext()->getNextId(), when, + systemTime(SYSTEM_TIME_MONOTONIC), getDeviceId(), mSource, + getDisplayId(), 0 /*policyFlags*/, AKEY_EVENT_ACTION_UP, + AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED, + mKeyDowns[i].keyCode, mKeyDowns[i].scanCode, AMETA_NONE, + mKeyDowns[i].downTime)); } mKeyDowns.clear(); mMetaState = AMETA_NONE; + return out; } } // namespace android diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index 2136d253c9..8d72ee9629 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -25,24 +25,25 @@ public: KeyboardInputMapper(InputDeviceContext& deviceContext, uint32_t source, int32_t keyboardType); virtual ~KeyboardInputMapper(); - virtual uint32_t getSources() const override; - virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; - virtual void dump(std::string& dump) override; - virtual void configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) override; - virtual void reset(nsecs_t when) override; - virtual void process(const RawEvent* rawEvent) override; - - virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode) override; - virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override; - virtual bool markSupportedKeyCodes(uint32_t sourceMask, const std::vector& keyCodes, - uint8_t* outFlags) override; - virtual int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const override; - - virtual int32_t getMetaState() override; - virtual bool updateMetaState(int32_t keyCode) override; - virtual std::optional getAssociatedDisplayId() override; - virtual void updateLedState(bool reset); + uint32_t getSources() const override; + void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; + void dump(std::string& dump) override; + [[nodiscard]] std::list configure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) override; + [[nodiscard]] std::list reset(nsecs_t when) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; + + int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode) override; + int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override; + bool markSupportedKeyCodes(uint32_t sourceMask, const std::vector& keyCodes, + uint8_t* outFlags) override; + int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const override; + + int32_t getMetaState() override; + bool updateMetaState(int32_t keyCode) override; + std::optional getAssociatedDisplayId() override; + void updateLedState(bool reset) override; private: // The current viewport. @@ -86,7 +87,8 @@ private: bool isKeyboardOrGamepadKey(int32_t scanCode); bool isMediaKey(int32_t keyCode); - void processKey(nsecs_t when, nsecs_t readTime, bool down, int32_t scanCode, int32_t usageCode); + [[nodiscard]] std::list processKey(nsecs_t when, nsecs_t readTime, bool down, + int32_t scanCode, int32_t usageCode); bool updateMetaStateIfNeeded(int32_t keyCode, bool down); @@ -97,7 +99,7 @@ private: void updateLedStateForModifier(LedState& ledState, int32_t led, int32_t modifier, bool reset); std::optional findViewport(nsecs_t when, const InputReaderConfiguration* config); - void cancelAllDownKeys(nsecs_t when); + [[nodiscard]] std::list cancelAllDownKeys(nsecs_t when); }; } // namespace android diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp index 047f068751..1d53eaba6e 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp @@ -191,20 +191,21 @@ MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext) MultiTouchInputMapper::~MultiTouchInputMapper() {} -void MultiTouchInputMapper::reset(nsecs_t when) { +std::list MultiTouchInputMapper::reset(nsecs_t when) { // The evdev multi-touch protocol does not allow userspace applications to query the initial or // current state of the pointers at any time. This means if we clear our accumulated state when // resetting the input mapper, there's no way to rebuild the full initial state of the pointers. // We can only wait for updates to all the pointers and axes. Rather than clearing the state and // rebuilding the state from scratch, we work around this kernel API limitation by never // fully clearing any state specific to the multi-touch protocol. - TouchInputMapper::reset(when); + return TouchInputMapper::reset(when); } -void MultiTouchInputMapper::process(const RawEvent* rawEvent) { - TouchInputMapper::process(rawEvent); +std::list MultiTouchInputMapper::process(const RawEvent* rawEvent) { + std::list out = TouchInputMapper::process(rawEvent); mMultiTouchMotionAccumulator.process(rawEvent); + return out; } std::optional MultiTouchInputMapper::getActiveBitId( diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h index 75cde8e6db..047e62de62 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h @@ -93,8 +93,8 @@ public: explicit MultiTouchInputMapper(InputDeviceContext& deviceContext); ~MultiTouchInputMapper() override; - void reset(nsecs_t when) override; - void process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list reset(nsecs_t when) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; protected: void syncTouch(nsecs_t when, RawState* outState) override; diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp index 05973f783c..29a1bda3ee 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp @@ -60,9 +60,10 @@ void RotaryEncoderInputMapper::dump(std::string& dump) { toString(mRotaryEncoderScrollAccumulator.haveRelativeVWheel())); } -void RotaryEncoderInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) { - InputMapper::configure(when, config, changes); +std::list RotaryEncoderInputMapper::configure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) { + std::list out = InputMapper::configure(when, config, changes); if (!changes) { mRotaryEncoderScrollAccumulator.configure(getDeviceContext()); } @@ -75,23 +76,27 @@ void RotaryEncoderInputMapper::configure(nsecs_t when, const InputReaderConfigur mOrientation = DISPLAY_ORIENTATION_0; } } + return out; } -void RotaryEncoderInputMapper::reset(nsecs_t when) { +std::list RotaryEncoderInputMapper::reset(nsecs_t when) { mRotaryEncoderScrollAccumulator.reset(getDeviceContext()); - InputMapper::reset(when); + return InputMapper::reset(when); } -void RotaryEncoderInputMapper::process(const RawEvent* rawEvent) { +std::list RotaryEncoderInputMapper::process(const RawEvent* rawEvent) { + std::list out; mRotaryEncoderScrollAccumulator.process(rawEvent); if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { - sync(rawEvent->when, rawEvent->readTime); + out += sync(rawEvent->when, rawEvent->readTime); } + return out; } -void RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readTime) { +std::list RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readTime) { + std::list out; PointerCoords pointerCoords; pointerCoords.clear(); @@ -121,16 +126,17 @@ void RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readTime) { int32_t metaState = getContext()->getGlobalMetaState(); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_SCROLL, scroll * mScalingFactor); - NotifyMotionArgs scrollArgs(getContext()->getNextId(), when, readTime, getDeviceId(), - mSource, displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, - 0, metaState, /* buttonState */ 0, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, - &pointerCoords, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, - AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {}); - getListener().notifyMotion(&scrollArgs); + out.push_back( + NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, + displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0, + metaState, /* buttonState */ 0, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, + &pointerCoords, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {})); } mRotaryEncoderScrollAccumulator.finishSync(); + return out; } } // namespace android diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h index 42e2421c37..f4352e76a0 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h @@ -29,10 +29,11 @@ public: virtual uint32_t getSources() const override; virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; virtual void dump(std::string& dump) override; - virtual void configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) override; - virtual void reset(nsecs_t when) override; - virtual void process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list configure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) override; + [[nodiscard]] std::list reset(nsecs_t when) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; private: CursorScrollAccumulator mRotaryEncoderScrollAccumulator; @@ -41,7 +42,7 @@ private: float mScalingFactor; int32_t mOrientation; - void sync(nsecs_t when, nsecs_t readTime); + [[nodiscard]] std::list sync(nsecs_t when, nsecs_t readTime); }; } // namespace android diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp index 573f99cf87..d81022f6e5 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp @@ -122,9 +122,10 @@ void SensorInputMapper::dump(std::string& dump) { } } -void SensorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) { - InputMapper::configure(when, config, changes); +std::list SensorInputMapper::configure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) { + std::list out = InputMapper::configure(when, config, changes); if (!changes) { // first time only mDeviceEnabled = true; @@ -158,6 +159,7 @@ void SensorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* } } } + return out; } SensorInputMapper::Axis SensorInputMapper::createAxis(const AxisInfo& axisInfo, @@ -185,7 +187,7 @@ SensorInputMapper::Axis SensorInputMapper::createAxis(const AxisInfo& axisInfo, return Axis(rawAxisInfo, axisInfo, scale, offset, min, max, flat, fuzz, resolution, filter); } -void SensorInputMapper::reset(nsecs_t when) { +std::list SensorInputMapper::reset(nsecs_t when) { // Recenter all axes. for (std::pair& pair : mAxes) { Axis& axis = pair.second; @@ -193,7 +195,7 @@ void SensorInputMapper::reset(nsecs_t when) { } mHardwareTimestamp = 0; mPrevMscTime = 0; - InputMapper::reset(when); + return InputMapper::reset(when); } SensorInputMapper::Sensor SensorInputMapper::createSensor(InputDeviceSensorType sensorType, @@ -256,7 +258,8 @@ void SensorInputMapper::processHardWareTimestamp(nsecs_t evTime, int32_t mscTime mPrevMscTime = static_cast(mscTime); } -void SensorInputMapper::process(const RawEvent* rawEvent) { +std::list SensorInputMapper::process(const RawEvent* rawEvent) { + std::list out; switch (rawEvent->type) { case EV_ABS: { auto it = mAxes.find(rawEvent->code); @@ -274,7 +277,7 @@ void SensorInputMapper::process(const RawEvent* rawEvent) { Axis& axis = pair.second; axis.currentValue = axis.newValue; } - sync(rawEvent->when, false /*force*/); + out += sync(rawEvent->when, false /*force*/); break; } break; @@ -287,6 +290,7 @@ void SensorInputMapper::process(const RawEvent* rawEvent) { break; } } + return out; } bool SensorInputMapper::setSensorEnabled(InputDeviceSensorType sensorType, bool enabled) { @@ -375,7 +379,8 @@ void SensorInputMapper::disableSensor(InputDeviceSensorType sensorType) { } } -void SensorInputMapper::sync(nsecs_t when, bool force) { +std::list SensorInputMapper::sync(nsecs_t when, bool force) { + std::list out; for (auto& [sensorType, sensor] : mSensors) { // Skip if sensor not enabled if (!sensor.enabled) { @@ -405,17 +410,17 @@ void SensorInputMapper::sync(nsecs_t when, bool force) { // Convert to Android unit convertFromLinuxToAndroid(values, sensorType); // Notify dispatcher for sensor event - NotifySensorArgs args(getContext()->getNextId(), when, getDeviceId(), - AINPUT_SOURCE_SENSOR, sensorType, sensor.sensorInfo.accuracy, - sensor.accuracy != - sensor.sensorInfo.accuracy /* accuracyChanged */, - timestamp /* hwTimestamp */, values); - - getListener().notifySensor(&args); + out.push_back(NotifySensorArgs(getContext()->getNextId(), when, getDeviceId(), + AINPUT_SOURCE_SENSOR, sensorType, + sensor.sensorInfo.accuracy, + sensor.accuracy != + sensor.sensorInfo.accuracy /* accuracyChanged */, + timestamp /* hwTimestamp */, values)); sensor.lastSampleTimeNs = timestamp; sensor.accuracy = sensor.sensorInfo.accuracy; } } + return out; } } // namespace android diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h index 38d4c3c935..457567ba7b 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.h +++ b/services/inputflinger/reader/mapper/SensorInputMapper.h @@ -30,9 +30,11 @@ public: uint32_t getSources() const override; void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; void dump(std::string& dump) override; - void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) override; - void reset(nsecs_t when) override; - void process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list configure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) override; + [[nodiscard]] std::list reset(nsecs_t when) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; bool enableSensor(InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod, std::chrono::microseconds maxBatchReportLatency) override; void disableSensor(InputDeviceSensorType sensorType) override; @@ -116,7 +118,7 @@ private: // Sensor list std::unordered_map mSensors; - void sync(nsecs_t when, bool force); + [[nodiscard]] std::list sync(nsecs_t when, bool force); template bool tryGetProperty(std::string keyName, T& outValue); diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp index 4fff9bebb5..13ad224111 100644 --- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp @@ -23,16 +23,17 @@ SingleTouchInputMapper::SingleTouchInputMapper(InputDeviceContext& deviceContext SingleTouchInputMapper::~SingleTouchInputMapper() {} -void SingleTouchInputMapper::reset(nsecs_t when) { +std::list SingleTouchInputMapper::reset(nsecs_t when) { mSingleTouchMotionAccumulator.reset(getDeviceContext()); - TouchInputMapper::reset(when); + return TouchInputMapper::reset(when); } -void SingleTouchInputMapper::process(const RawEvent* rawEvent) { - TouchInputMapper::process(rawEvent); +std::list SingleTouchInputMapper::process(const RawEvent* rawEvent) { + std::list out = TouchInputMapper::process(rawEvent); mSingleTouchMotionAccumulator.process(rawEvent); + return out; } void SingleTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h index f54c19502e..662e6bca25 100644 --- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h @@ -26,8 +26,8 @@ public: explicit SingleTouchInputMapper(InputDeviceContext& deviceContext); ~SingleTouchInputMapper() override; - void reset(nsecs_t when) override; - void process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list reset(nsecs_t when) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; protected: void syncTouch(nsecs_t when, RawState* outState) override; diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp index ebb5de66ed..c9101cadc6 100644 --- a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp @@ -29,7 +29,8 @@ uint32_t SwitchInputMapper::getSources() const { return AINPUT_SOURCE_SWITCH; } -void SwitchInputMapper::process(const RawEvent* rawEvent) { +std::list SwitchInputMapper::process(const RawEvent* rawEvent) { + std::list out; switch (rawEvent->type) { case EV_SW: processSwitch(rawEvent->code, rawEvent->value); @@ -37,9 +38,10 @@ void SwitchInputMapper::process(const RawEvent* rawEvent) { case EV_SYN: if (rawEvent->code == SYN_REPORT) { - sync(rawEvent->when); + out += sync(rawEvent->when); } } + return out; } void SwitchInputMapper::processSwitch(int32_t switchCode, int32_t switchValue) { @@ -53,15 +55,16 @@ void SwitchInputMapper::processSwitch(int32_t switchCode, int32_t switchValue) { } } -void SwitchInputMapper::sync(nsecs_t when) { +std::list SwitchInputMapper::sync(nsecs_t when) { + std::list out; if (mUpdatedSwitchMask) { uint32_t updatedSwitchValues = mSwitchValues & mUpdatedSwitchMask; - NotifySwitchArgs args(getContext()->getNextId(), when, 0 /*policyFlags*/, - updatedSwitchValues, mUpdatedSwitchMask); - getListener().notifySwitch(&args); + out.push_back(NotifySwitchArgs(getContext()->getNextId(), when, 0 /*policyFlags*/, + updatedSwitchValues, mUpdatedSwitchMask)); mUpdatedSwitchMask = 0; } + return out; } int32_t SwitchInputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) { diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.h b/services/inputflinger/reader/mapper/SwitchInputMapper.h index e0c949f012..06d6504684 100644 --- a/services/inputflinger/reader/mapper/SwitchInputMapper.h +++ b/services/inputflinger/reader/mapper/SwitchInputMapper.h @@ -26,7 +26,7 @@ public: virtual ~SwitchInputMapper(); virtual uint32_t getSources() const override; - virtual void process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode) override; virtual void dump(std::string& dump) override; @@ -36,7 +36,7 @@ private: uint32_t mUpdatedSwitchMask; void processSwitch(int32_t switchCode, int32_t switchValue); - void sync(nsecs_t when); + [[nodiscard]] std::list sync(nsecs_t when); }; } // namespace android diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h index 42d819b362..5a7ba9a6ed 100644 --- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h +++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h @@ -71,30 +71,34 @@ static bool isPointerDown(int32_t buttonState) { AMOTION_EVENT_BUTTON_TERTIARY); } -static void synthesizeButtonKey(InputReaderContext* context, int32_t action, nsecs_t when, - nsecs_t readTime, int32_t deviceId, uint32_t source, - int32_t displayId, uint32_t policyFlags, int32_t lastButtonState, - int32_t currentButtonState, int32_t buttonState, int32_t keyCode) { +[[nodiscard]] static std::list synthesizeButtonKey( + InputReaderContext* context, int32_t action, nsecs_t when, nsecs_t readTime, + int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags, + int32_t lastButtonState, int32_t currentButtonState, int32_t buttonState, int32_t keyCode) { + std::list out; if ((action == AKEY_EVENT_ACTION_DOWN && !(lastButtonState & buttonState) && (currentButtonState & buttonState)) || (action == AKEY_EVENT_ACTION_UP && (lastButtonState & buttonState) && !(currentButtonState & buttonState))) { - NotifyKeyArgs args(context->getNextId(), when, readTime, deviceId, source, displayId, - policyFlags, action, 0, keyCode, 0, context->getGlobalMetaState(), when); - context->getListener().notifyKey(&args); + out.push_back(NotifyKeyArgs(context->getNextId(), when, readTime, deviceId, source, + displayId, policyFlags, action, 0, keyCode, 0, + context->getGlobalMetaState(), when)); } + return out; } -static void synthesizeButtonKeys(InputReaderContext* context, int32_t action, nsecs_t when, - nsecs_t readTime, int32_t deviceId, uint32_t source, - int32_t displayId, uint32_t policyFlags, int32_t lastButtonState, - int32_t currentButtonState) { - synthesizeButtonKey(context, action, when, readTime, deviceId, source, displayId, policyFlags, - lastButtonState, currentButtonState, AMOTION_EVENT_BUTTON_BACK, - AKEYCODE_BACK); - synthesizeButtonKey(context, action, when, readTime, deviceId, source, displayId, policyFlags, - lastButtonState, currentButtonState, AMOTION_EVENT_BUTTON_FORWARD, - AKEYCODE_FORWARD); +[[nodiscard]] static std::list synthesizeButtonKeys( + InputReaderContext* context, int32_t action, nsecs_t when, nsecs_t readTime, + int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags, + int32_t lastButtonState, int32_t currentButtonState) { + std::list out; + out += synthesizeButtonKey(context, action, when, readTime, deviceId, source, displayId, + policyFlags, lastButtonState, currentButtonState, + AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK); + out += synthesizeButtonKey(context, action, when, readTime, deviceId, source, displayId, + policyFlags, lastButtonState, currentButtonState, + AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD); + return out; } } // namespace android diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 5d0f1888ef..da58efde31 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -346,9 +346,10 @@ void TouchInputMapper::dump(std::string& dump) { } } -void TouchInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) { - InputMapper::configure(when, config, changes); +std::list TouchInputMapper::configure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) { + std::list out = InputMapper::configure(when, config, changes); mConfig = *config; @@ -395,14 +396,14 @@ void TouchInputMapper::configure(nsecs_t when, const InputReaderConfiguration* c if (changes && resetNeeded) { // If the device needs to be reset, cancel any ongoing gestures and reset the state. - cancelTouch(when, when); - reset(when); + out += cancelTouch(when, when); + out += reset(when); // Send reset, unless this is the first time the device has been configured, // in which case the reader will call reset itself after all mappers are ready. - NotifyDeviceResetArgs args(getContext()->getNextId(), when, getDeviceId()); - getListener().notifyDeviceReset(&args); + out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId())); } + return out; } void TouchInputMapper::resolveExternalStylusPresence() { @@ -1438,7 +1439,7 @@ void TouchInputMapper::updateAffineTransformation() { mInputDeviceOrientation); } -void TouchInputMapper::reset(nsecs_t when) { +std::list TouchInputMapper::reset(nsecs_t when) { mCursorButtonAccumulator.reset(getDeviceContext()); mCursorScrollAccumulator.reset(getDeviceContext()); mTouchButtonAccumulator.reset(getDeviceContext()); @@ -1469,7 +1470,7 @@ void TouchInputMapper::reset(nsecs_t when) { mPointerController->clearSpots(); } - InputMapper::reset(when); + return InputMapper::reset(when); } void TouchInputMapper::resetExternalStylus() { @@ -1484,17 +1485,20 @@ void TouchInputMapper::clearStylusDataPendingFlags() { mExternalStylusFusionTimeout = LLONG_MAX; } -void TouchInputMapper::process(const RawEvent* rawEvent) { +std::list TouchInputMapper::process(const RawEvent* rawEvent) { mCursorButtonAccumulator.process(rawEvent); mCursorScrollAccumulator.process(rawEvent); mTouchButtonAccumulator.process(rawEvent); + std::list out; if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { - sync(rawEvent->when, rawEvent->readTime); + out += sync(rawEvent->when, rawEvent->readTime); } + return out; } -void TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { +std::list TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { + std::list out; if (mDeviceMode == DeviceMode::DISABLED) { // Only save the last pending state when the device is disabled. mRawStatesPending.clear(); @@ -1543,16 +1547,18 @@ void TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { next.rawPointerData.hoveringIdBits.value); } - processRawTouches(false /*timeout*/); + out += processRawTouches(false /*timeout*/); + return out; } -void TouchInputMapper::processRawTouches(bool timeout) { +std::list TouchInputMapper::processRawTouches(bool timeout) { + std::list out; if (mDeviceMode == DeviceMode::DISABLED) { // Drop all input if the device is disabled. - cancelTouch(mCurrentRawState.when, mCurrentRawState.readTime); + out += cancelTouch(mCurrentRawState.when, mCurrentRawState.readTime); mCurrentCookedState.clear(); updateTouchSpots(); - return; + return out; } // Drain any pending touch states. The invariant here is that the mCurrentRawState is always @@ -1577,7 +1583,7 @@ void TouchInputMapper::processRawTouches(bool timeout) { mCurrentRawState.when = mLastRawState.when; mCurrentRawState.readTime = mLastRawState.readTime; } - cookAndDispatch(mCurrentRawState.when, mCurrentRawState.readTime); + out += cookAndDispatch(mCurrentRawState.when, mCurrentRawState.readTime); } if (count != 0) { mRawStatesPending.erase(mRawStatesPending.begin(), mRawStatesPending.begin() + count); @@ -1591,15 +1597,17 @@ void TouchInputMapper::processRawTouches(bool timeout) { ALOGD_IF(DEBUG_STYLUS_FUSION, "Timeout expired, synthesizing event with new stylus data"); const nsecs_t readTime = when; // consider this synthetic event to be zero latency - cookAndDispatch(when, readTime); + out += cookAndDispatch(when, readTime); } else if (mExternalStylusFusionTimeout == LLONG_MAX) { mExternalStylusFusionTimeout = mExternalStylusState.when + TOUCH_DATA_TIMEOUT; getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout); } } + return out; } -void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) { +std::list TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) { + std::list out; // Always start with a clean state. mCurrentCookedState.clear(); @@ -1625,7 +1633,9 @@ void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) { // Consume raw off-screen touches before cooking pointer data. // If touches are consumed, subsequent code will not receive any pointer data. - if (consumeRawTouches(when, readTime, policyFlags)) { + bool consumed; + out += consumeRawTouches(when, readTime, policyFlags, consumed /*byref*/); + if (consumed) { mCurrentRawState.rawPointerData.clear(); } @@ -1638,9 +1648,9 @@ void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) { applyExternalStylusTouchState(when); // Synthesize key down from raw buttons if needed. - synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, readTime, getDeviceId(), - mSource, mViewport.displayId, policyFlags, mLastCookedState.buttonState, - mCurrentCookedState.buttonState); + out += synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, readTime, getDeviceId(), + mSource, mViewport.displayId, policyFlags, + mLastCookedState.buttonState, mCurrentCookedState.buttonState); // Dispatch the touches either directly or by translation through a pointer on screen. if (mDeviceMode == DeviceMode::POINTER) { @@ -1682,15 +1692,15 @@ void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) { pointerUsage = PointerUsage::GESTURES; } - dispatchPointerUsage(when, readTime, policyFlags, pointerUsage); + out += dispatchPointerUsage(when, readTime, policyFlags, pointerUsage); } else { if (!mCurrentMotionAborted) { updateTouchSpots(); - dispatchButtonRelease(when, readTime, policyFlags); - dispatchHoverExit(when, readTime, policyFlags); - dispatchTouches(when, readTime, policyFlags); - dispatchHoverEnterAndMove(when, readTime, policyFlags); - dispatchButtonPress(when, readTime, policyFlags); + out += dispatchButtonRelease(when, readTime, policyFlags); + out += dispatchHoverExit(when, readTime, policyFlags); + out += dispatchTouches(when, readTime, policyFlags); + out += dispatchHoverEnterAndMove(when, readTime, policyFlags); + out += dispatchButtonPress(when, readTime, policyFlags); } if (mCurrentCookedState.cookedPointerData.pointerCount == 0) { @@ -1699,9 +1709,9 @@ void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) { } // Synthesize key up from raw buttons if needed. - synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, readTime, getDeviceId(), mSource, - mViewport.displayId, policyFlags, mLastCookedState.buttonState, - mCurrentCookedState.buttonState); + out += synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, readTime, getDeviceId(), + mSource, mViewport.displayId, policyFlags, + mLastCookedState.buttonState, mCurrentCookedState.buttonState); // Clear some transient state. mCurrentRawState.rawVScroll = 0; @@ -1710,6 +1720,7 @@ void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) { // Copy current touch to last touch in preparation for the next cycle. mLastRawState.copyFrom(mCurrentRawState); mLastCookedState.copyFrom(mCurrentCookedState); + return out; } void TouchInputMapper::updateTouchSpots() { @@ -1801,34 +1812,41 @@ bool TouchInputMapper::assignExternalStylusId(const RawState& state, bool timeou return false; } -void TouchInputMapper::timeoutExpired(nsecs_t when) { +std::list TouchInputMapper::timeoutExpired(nsecs_t when) { + std::list out; if (mDeviceMode == DeviceMode::POINTER) { if (mPointerUsage == PointerUsage::GESTURES) { // Since this is a synthetic event, we can consider its latency to be zero const nsecs_t readTime = when; - dispatchPointerGestures(when, readTime, 0 /*policyFlags*/, true /*isTimeout*/); + out += dispatchPointerGestures(when, readTime, 0 /*policyFlags*/, true /*isTimeout*/); } } else if (mDeviceMode == DeviceMode::DIRECT) { if (mExternalStylusFusionTimeout < when) { - processRawTouches(true /*timeout*/); + out += processRawTouches(true /*timeout*/); } else if (mExternalStylusFusionTimeout != LLONG_MAX) { getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout); } } + return out; } -void TouchInputMapper::updateExternalStylusState(const StylusState& state) { +std::list TouchInputMapper::updateExternalStylusState(const StylusState& state) { + std::list out; mExternalStylusState.copyFrom(state); if (mExternalStylusId != -1 || mExternalStylusFusionTimeout != LLONG_MAX) { // We're either in the middle of a fused stream of data or we're waiting on data before // dispatching the initial down, so go ahead and dispatch now that we have fresh stylus // data. mExternalStylusDataPending = true; - processRawTouches(false /*timeout*/); + out += processRawTouches(false /*timeout*/); } + return out; } -bool TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, bool& outConsumed) { + outConsumed = false; + std::list out; // Check for release of a virtual key. if (mCurrentVirtualKey.down) { if (mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) { @@ -1838,10 +1856,12 @@ bool TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_ ALOGD_IF(DEBUG_VIRTUAL_KEYS, "VirtualKeys: Generating key up: keyCode=%d, scanCode=%d", mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); - dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_UP, - AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY); + out.push_back(dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_UP, + AKEY_EVENT_FLAG_FROM_SYSTEM | + AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)); } - return true; + outConsumed = true; + return out; } if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) { @@ -1851,7 +1871,8 @@ bool TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_ const VirtualKey* virtualKey = findVirtualKeyHit(pointer.x, pointer.y); if (virtualKey && virtualKey->keyCode == mCurrentVirtualKey.keyCode) { // Pointer is still within the space of the virtual key. - return true; + outConsumed = true; + return out; } } @@ -1863,9 +1884,10 @@ bool TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_ if (!mCurrentVirtualKey.ignored) { ALOGD_IF(DEBUG_VIRTUAL_KEYS, "VirtualKeys: Canceling key: keyCode=%d, scanCode=%d", mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); - dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_UP, - AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY | - AKEY_EVENT_FLAG_CANCELED); + out.push_back(dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_UP, + AKEY_EVENT_FLAG_FROM_SYSTEM | + AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY | + AKEY_EVENT_FLAG_CANCELED)); } } @@ -1895,13 +1917,15 @@ bool TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_ ALOGD_IF(DEBUG_VIRTUAL_KEYS, "VirtualKeys: Generating key down: keyCode=%d, scanCode=%d", mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); - dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_DOWN, - AKEY_EVENT_FLAG_FROM_SYSTEM | - AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY); + out.push_back(dispatchVirtualKey(when, readTime, policyFlags, + AKEY_EVENT_ACTION_DOWN, + AKEY_EVENT_FLAG_FROM_SYSTEM | + AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)); } } } - return true; + outConsumed = true; + return out; } } @@ -1923,44 +1947,50 @@ bool TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_ !mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) { getContext()->disableVirtualKeysUntil(when + mConfig.virtualKeyQuietTime); } - return false; + return out; } -void TouchInputMapper::dispatchVirtualKey(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, - int32_t keyEventAction, int32_t keyEventFlags) { +NotifyKeyArgs TouchInputMapper::dispatchVirtualKey(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, int32_t keyEventAction, + int32_t keyEventFlags) { int32_t keyCode = mCurrentVirtualKey.keyCode; int32_t scanCode = mCurrentVirtualKey.scanCode; nsecs_t downTime = mCurrentVirtualKey.downTime; int32_t metaState = getContext()->getGlobalMetaState(); policyFlags |= POLICY_FLAG_VIRTUAL; - NotifyKeyArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), - AINPUT_SOURCE_KEYBOARD, mViewport.displayId, policyFlags, keyEventAction, - keyEventFlags, keyCode, scanCode, metaState, downTime); - getListener().notifyKey(&args); + return NotifyKeyArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + AINPUT_SOURCE_KEYBOARD, mViewport.displayId, policyFlags, keyEventAction, + keyEventFlags, keyCode, scanCode, metaState, downTime); } -void TouchInputMapper::abortTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::abortTouches(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + std::list out; if (mCurrentMotionAborted) { // Current motion event was already aborted. - return; + return out; } BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits; if (!currentIdBits.isEmpty()) { int32_t metaState = getContext()->getGlobalMetaState(); int32_t buttonState = mCurrentCookedState.buttonState; - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, 0, - metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, - mCurrentCookedState.cookedPointerData.pointerProperties, - mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime, - MotionClassification::NONE); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_CANCEL, 0, 0, metaState, buttonState, + AMOTION_EVENT_EDGE_FLAG_NONE, + mCurrentCookedState.cookedPointerData.pointerProperties, + mCurrentCookedState.cookedPointerData.pointerCoords, + mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits, + -1, mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); mCurrentMotionAborted = true; } + return out; } -void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + std::list out; BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits; BitSet32 lastIdBits = mLastCookedState.cookedPointerData.touchingIdBits; int32_t metaState = getContext()->getGlobalMetaState(); @@ -1970,13 +2000,14 @@ void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t if (!currentIdBits.isEmpty()) { // No pointer id changes so this is a move event. // The listener takes care of batching moves so we don't have to deal with that here. - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0, - metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, - mCurrentCookedState.cookedPointerData.pointerProperties, - mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime, - MotionClassification::NONE); + out.push_back( + dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, + 0, 0, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mCurrentCookedState.cookedPointerData.pointerProperties, + mCurrentCookedState.cookedPointerData.pointerCoords, + mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits, + -1, mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); } } else { // There may be pointers going up and pointers going down and pointers moving @@ -2006,13 +2037,16 @@ void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t if (isCanceled) { ALOGI("Canceling pointer %d for the palm event was detected.", upId); } - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_UP, 0, - isCanceled ? AMOTION_EVENT_FLAG_CANCELED : 0, metaState, buttonState, 0, - mLastCookedState.cookedPointerData.pointerProperties, - mLastCookedState.cookedPointerData.pointerCoords, - mLastCookedState.cookedPointerData.idToIndex, dispatchedIdBits, upId, - mOrientedXPrecision, mOrientedYPrecision, mDownTime, - MotionClassification::NONE); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_POINTER_UP, 0, + isCanceled ? AMOTION_EVENT_FLAG_CANCELED : 0, metaState, + buttonState, 0, + mLastCookedState.cookedPointerData.pointerProperties, + mLastCookedState.cookedPointerData.pointerCoords, + mLastCookedState.cookedPointerData.idToIndex, + dispatchedIdBits, upId, mOrientedXPrecision, + mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); dispatchedIdBits.clearBit(upId); mCurrentCookedState.cookedPointerData.canceledIdBits.clearBit(upId); } @@ -2022,13 +2056,14 @@ void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t // events, they do not generally handle them except when presented in a move event. if (moveNeeded && !moveIdBits.isEmpty()) { ALOG_ASSERT(moveIdBits.value == dispatchedIdBits.value); - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0, - metaState, buttonState, 0, - mCurrentCookedState.cookedPointerData.pointerProperties, - mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime, - MotionClassification::NONE); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState, buttonState, 0, + mCurrentCookedState.cookedPointerData.pointerProperties, + mCurrentCookedState.cookedPointerData.pointerCoords, + mCurrentCookedState.cookedPointerData.idToIndex, + dispatchedIdBits, -1, mOrientedXPrecision, + mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); } // Dispatch pointer down events using the new pointer locations. @@ -2041,62 +2076,75 @@ void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t mDownTime = when; } - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_DOWN, - 0, 0, metaState, buttonState, 0, - mCurrentCookedState.cookedPointerData.pointerProperties, - mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits, - downId, mOrientedXPrecision, mOrientedYPrecision, mDownTime, - MotionClassification::NONE); + out.push_back( + dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_POINTER_DOWN, 0, 0, metaState, buttonState, + 0, mCurrentCookedState.cookedPointerData.pointerProperties, + mCurrentCookedState.cookedPointerData.pointerCoords, + mCurrentCookedState.cookedPointerData.idToIndex, + dispatchedIdBits, downId, mOrientedXPrecision, + mOrientedYPrecision, mDownTime, MotionClassification::NONE)); } } + return out; } -void TouchInputMapper::dispatchHoverExit(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::dispatchHoverExit(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + std::list out; if (mSentHoverEnter && (mCurrentCookedState.cookedPointerData.hoveringIdBits.isEmpty() || !mCurrentCookedState.cookedPointerData.touchingIdBits.isEmpty())) { int32_t metaState = getContext()->getGlobalMetaState(); - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0, - metaState, mLastCookedState.buttonState, 0, - mLastCookedState.cookedPointerData.pointerProperties, - mLastCookedState.cookedPointerData.pointerCoords, - mLastCookedState.cookedPointerData.idToIndex, - mLastCookedState.cookedPointerData.hoveringIdBits, -1, mOrientedXPrecision, - mOrientedYPrecision, mDownTime, MotionClassification::NONE); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0, metaState, + mLastCookedState.buttonState, 0, + mLastCookedState.cookedPointerData.pointerProperties, + mLastCookedState.cookedPointerData.pointerCoords, + mLastCookedState.cookedPointerData.idToIndex, + mLastCookedState.cookedPointerData.hoveringIdBits, -1, + mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); mSentHoverEnter = false; } + return out; } -void TouchInputMapper::dispatchHoverEnterAndMove(nsecs_t when, nsecs_t readTime, - uint32_t policyFlags) { +std::list TouchInputMapper::dispatchHoverEnterAndMove(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + std::list out; if (mCurrentCookedState.cookedPointerData.touchingIdBits.isEmpty() && !mCurrentCookedState.cookedPointerData.hoveringIdBits.isEmpty()) { int32_t metaState = getContext()->getGlobalMetaState(); if (!mSentHoverEnter) { - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_ENTER, - 0, 0, metaState, mCurrentRawState.buttonState, 0, - mCurrentCookedState.cookedPointerData.pointerProperties, - mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, - mCurrentCookedState.cookedPointerData.hoveringIdBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime, - MotionClassification::NONE); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_HOVER_ENTER, 0, 0, metaState, + mCurrentRawState.buttonState, 0, + mCurrentCookedState.cookedPointerData.pointerProperties, + mCurrentCookedState.cookedPointerData.pointerCoords, + mCurrentCookedState.cookedPointerData.idToIndex, + mCurrentCookedState.cookedPointerData.hoveringIdBits, -1, + mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); mSentHoverEnter = true; } - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, - metaState, mCurrentRawState.buttonState, 0, - mCurrentCookedState.cookedPointerData.pointerProperties, - mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, - mCurrentCookedState.cookedPointerData.hoveringIdBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime, - MotionClassification::NONE); - } + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState, + mCurrentRawState.buttonState, 0, + mCurrentCookedState.cookedPointerData.pointerProperties, + mCurrentCookedState.cookedPointerData.pointerCoords, + mCurrentCookedState.cookedPointerData.idToIndex, + mCurrentCookedState.cookedPointerData.hoveringIdBits, -1, + mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); + } + return out; } -void TouchInputMapper::dispatchButtonRelease(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::dispatchButtonRelease(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + std::list out; BitSet32 releasedButtons(mLastCookedState.buttonState & ~mCurrentCookedState.buttonState); const BitSet32& idBits = findActiveIdBits(mLastCookedState.cookedPointerData); const int32_t metaState = getContext()->getGlobalMetaState(); @@ -2104,17 +2152,21 @@ void TouchInputMapper::dispatchButtonRelease(nsecs_t when, nsecs_t readTime, uin while (!releasedButtons.isEmpty()) { int32_t actionButton = BitSet32::valueForBit(releasedButtons.clearFirstMarkedBit()); buttonState &= ~actionButton; - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_BUTTON_RELEASE, - actionButton, 0, metaState, buttonState, 0, - mCurrentCookedState.cookedPointerData.pointerProperties, - mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, idBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime, - MotionClassification::NONE); - } + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0, + metaState, buttonState, 0, + mCurrentCookedState.cookedPointerData.pointerProperties, + mCurrentCookedState.cookedPointerData.pointerCoords, + mCurrentCookedState.cookedPointerData.idToIndex, idBits, -1, + mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); + } + return out; } -void TouchInputMapper::dispatchButtonPress(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::dispatchButtonPress(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + std::list out; BitSet32 pressedButtons(mCurrentCookedState.buttonState & ~mLastCookedState.buttonState); const BitSet32& idBits = findActiveIdBits(mCurrentCookedState.cookedPointerData); const int32_t metaState = getContext()->getGlobalMetaState(); @@ -2122,14 +2174,16 @@ void TouchInputMapper::dispatchButtonPress(nsecs_t when, nsecs_t readTime, uint3 while (!pressedButtons.isEmpty()) { int32_t actionButton = BitSet32::valueForBit(pressedButtons.clearFirstMarkedBit()); buttonState |= actionButton; - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_BUTTON_PRESS, - actionButton, 0, metaState, buttonState, 0, - mCurrentCookedState.cookedPointerData.pointerProperties, - mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, idBits, -1, - mOrientedXPrecision, mOrientedYPrecision, mDownTime, - MotionClassification::NONE); - } + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0, metaState, + buttonState, 0, + mCurrentCookedState.cookedPointerData.pointerProperties, + mCurrentCookedState.cookedPointerData.pointerCoords, + mCurrentCookedState.cookedPointerData.idToIndex, idBits, -1, + mOrientedXPrecision, mOrientedYPrecision, mDownTime, + MotionClassification::NONE)); + } + return out; } const BitSet32& TouchInputMapper::findActiveIdBits(const CookedPointerData& cookedPointerData) { @@ -2411,54 +2465,62 @@ void TouchInputMapper::cookPointerData() { } } -void TouchInputMapper::dispatchPointerUsage(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, - PointerUsage pointerUsage) { +std::list TouchInputMapper::dispatchPointerUsage(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, + PointerUsage pointerUsage) { + std::list out; if (pointerUsage != mPointerUsage) { - abortPointerUsage(when, readTime, policyFlags); + out += abortPointerUsage(when, readTime, policyFlags); mPointerUsage = pointerUsage; } switch (mPointerUsage) { case PointerUsage::GESTURES: - dispatchPointerGestures(when, readTime, policyFlags, false /*isTimeout*/); + out += dispatchPointerGestures(when, readTime, policyFlags, false /*isTimeout*/); break; case PointerUsage::STYLUS: - dispatchPointerStylus(when, readTime, policyFlags); + out += dispatchPointerStylus(when, readTime, policyFlags); break; case PointerUsage::MOUSE: - dispatchPointerMouse(when, readTime, policyFlags); + out += dispatchPointerMouse(when, readTime, policyFlags); break; case PointerUsage::NONE: break; } + return out; } -void TouchInputMapper::abortPointerUsage(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::abortPointerUsage(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + std::list out; switch (mPointerUsage) { case PointerUsage::GESTURES: - abortPointerGestures(when, readTime, policyFlags); + out += abortPointerGestures(when, readTime, policyFlags); break; case PointerUsage::STYLUS: - abortPointerStylus(when, readTime, policyFlags); + out += abortPointerStylus(when, readTime, policyFlags); break; case PointerUsage::MOUSE: - abortPointerMouse(when, readTime, policyFlags); + out += abortPointerMouse(when, readTime, policyFlags); break; case PointerUsage::NONE: break; } mPointerUsage = PointerUsage::NONE; + return out; } -void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, - bool isTimeout) { +std::list TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, + bool isTimeout) { + std::list out; // Update current gesture coordinates. bool cancelPreviousGesture, finishPreviousGesture; bool sendEvents = preparePointerGestures(when, &cancelPreviousGesture, &finishPreviousGesture, isTimeout); if (!sendEvents) { - return; + return {}; } if (finishPreviousGesture) { cancelPreviousGesture = false; @@ -2555,11 +2617,14 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u BitSet32 dispatchedGestureIdBits(mPointerGesture.lastGestureIdBits); if (!dispatchedGestureIdBits.isEmpty()) { if (cancelPreviousGesture) { - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, - flags, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, - mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords, - mPointerGesture.lastGestureIdToIndex, dispatchedGestureIdBits, -1, 0, 0, - mPointerGesture.downTime, classification); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_CANCEL, 0, flags, metaState, + buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.lastGestureProperties, + mPointerGesture.lastGestureCoords, + mPointerGesture.lastGestureIdToIndex, + dispatchedGestureIdBits, -1, 0, 0, + mPointerGesture.downTime, classification)); dispatchedGestureIdBits.clear(); } else { @@ -2573,12 +2638,14 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u while (!upGestureIdBits.isEmpty()) { uint32_t id = upGestureIdBits.clearFirstMarkedBit(); - dispatchMotion(when, readTime, policyFlags, mSource, - AMOTION_EVENT_ACTION_POINTER_UP, 0, flags, metaState, buttonState, - AMOTION_EVENT_EDGE_FLAG_NONE, mPointerGesture.lastGestureProperties, - mPointerGesture.lastGestureCoords, - mPointerGesture.lastGestureIdToIndex, dispatchedGestureIdBits, id, 0, - 0, mPointerGesture.downTime, classification); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_POINTER_UP, 0, flags, metaState, + buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.lastGestureProperties, + mPointerGesture.lastGestureCoords, + mPointerGesture.lastGestureIdToIndex, + dispatchedGestureIdBits, id, 0, 0, + mPointerGesture.downTime, classification)); dispatchedGestureIdBits.clearBit(id); } @@ -2587,12 +2654,13 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u // Send motion events for all pointers that moved. if (moveNeeded) { - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, flags, - metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, - mPointerGesture.currentGestureProperties, - mPointerGesture.currentGestureCoords, - mPointerGesture.currentGestureIdToIndex, dispatchedGestureIdBits, -1, 0, 0, - mPointerGesture.downTime, classification); + out.push_back( + dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, + flags, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.currentGestureProperties, + mPointerGesture.currentGestureCoords, + mPointerGesture.currentGestureIdToIndex, dispatchedGestureIdBits, -1, + 0, 0, mPointerGesture.downTime, classification)); } // Send motion events for all pointers that went down. @@ -2607,24 +2675,26 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u mPointerGesture.downTime = when; } - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_DOWN, - 0, flags, metaState, buttonState, 0, - mPointerGesture.currentGestureProperties, - mPointerGesture.currentGestureCoords, - mPointerGesture.currentGestureIdToIndex, dispatchedGestureIdBits, id, 0, - 0, mPointerGesture.downTime, classification); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_POINTER_DOWN, 0, flags, metaState, + buttonState, 0, mPointerGesture.currentGestureProperties, + mPointerGesture.currentGestureCoords, + mPointerGesture.currentGestureIdToIndex, + dispatchedGestureIdBits, id, 0, 0, + mPointerGesture.downTime, classification)); } } // Send motion events for hover. if (mPointerGesture.currentGestureMode == PointerGesture::Mode::HOVER) { - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, - flags, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, - mPointerGesture.currentGestureProperties, - mPointerGesture.currentGestureCoords, - mPointerGesture.currentGestureIdToIndex, - mPointerGesture.currentGestureIdBits, -1, 0, 0, mPointerGesture.downTime, - MotionClassification::NONE); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_HOVER_MOVE, 0, flags, metaState, + buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.currentGestureProperties, + mPointerGesture.currentGestureCoords, + mPointerGesture.currentGestureIdToIndex, + mPointerGesture.currentGestureIdBits, -1, 0, 0, + mPointerGesture.downTime, MotionClassification::NONE)); } else if (dispatchedGestureIdBits.isEmpty() && !mPointerGesture.lastGestureIdBits.isEmpty()) { // Synthesize a hover move event after all pointers go up to indicate that // the pointer is hovering again even if the user is not currently touching @@ -2644,12 +2714,13 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y); const int32_t displayId = mPointerController->getDisplayId(); - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, flags, - metaState, buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords, - 0, 0, x, y, mPointerGesture.downTime, /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, displayId, policyFlags, + AMOTION_EVENT_ACTION_HOVER_MOVE, 0, flags, metaState, + buttonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, + &pointerCoords, 0, 0, x, y, mPointerGesture.downTime, + /* videoFrames */ {})); } // Update state. @@ -2668,22 +2739,28 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u mPointerGesture.lastGestureIdToIndex[id] = index; } } + return out; } -void TouchInputMapper::abortPointerGestures(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::abortPointerGestures(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { const MotionClassification classification = mPointerGesture.lastGestureMode == PointerGesture::Mode::SWIPE ? MotionClassification::TWO_FINGER_SWIPE : MotionClassification::NONE; + std::list out; // Cancel previously dispatches pointers. if (!mPointerGesture.lastGestureIdBits.isEmpty()) { int32_t metaState = getContext()->getGlobalMetaState(); int32_t buttonState = mCurrentRawState.buttonState; - dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, 0, - metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, - mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords, - mPointerGesture.lastGestureIdToIndex, mPointerGesture.lastGestureIdBits, -1, - 0, 0, mPointerGesture.downTime, classification); + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_CANCEL, 0, 0, metaState, buttonState, + AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.lastGestureProperties, + mPointerGesture.lastGestureCoords, + mPointerGesture.lastGestureIdToIndex, + mPointerGesture.lastGestureIdBits, -1, 0, 0, + mPointerGesture.downTime, classification)); } // Reset the current pointer gesture. @@ -2695,6 +2772,7 @@ void TouchInputMapper::abortPointerGestures(nsecs_t when, nsecs_t readTime, uint mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); mPointerController->clearSpots(); } + return out; } bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPreviousGesture, @@ -3411,7 +3489,8 @@ void TouchInputMapper::moveMousePointerFromPointerDelta(nsecs_t when, uint32_t p mPointerController->move(deltaX, deltaY); } -void TouchInputMapper::dispatchPointerStylus(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::dispatchPointerStylus(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { mPointerSimple.currentCoords.clear(); mPointerSimple.currentProperties.clear(); @@ -3440,14 +3519,16 @@ void TouchInputMapper::dispatchPointerStylus(nsecs_t when, nsecs_t readTime, uin hovering = false; } - dispatchPointerSimple(when, readTime, policyFlags, down, hovering); + return dispatchPointerSimple(when, readTime, policyFlags, down, hovering); } -void TouchInputMapper::abortPointerStylus(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { - abortPointerSimple(when, readTime, policyFlags); +std::list TouchInputMapper::abortPointerStylus(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + return abortPointerSimple(when, readTime, policyFlags); } -void TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { mPointerSimple.currentCoords.clear(); mPointerSimple.currentProperties.clear(); @@ -3482,17 +3563,22 @@ void TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs_t readTime, uint hovering = false; } - dispatchPointerSimple(when, readTime, policyFlags, down, hovering); + return dispatchPointerSimple(when, readTime, policyFlags, down, hovering); } -void TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { - abortPointerSimple(when, readTime, policyFlags); +std::list TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { + std::list out = abortPointerSimple(when, readTime, policyFlags); mPointerVelocityControl.reset(); + + return out; } -void TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, - bool down, bool hovering) { +std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, bool down, + bool hovering) { + std::list out; int32_t metaState = getContext()->getGlobalMetaState(); if (down || hovering) { @@ -3512,28 +3598,29 @@ void TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uin mPointerSimple.down = false; // Send up. - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - displayId, policyFlags, AMOTION_EVENT_ACTION_UP, 0, 0, metaState, - mLastRawState.buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties, - &mPointerSimple.lastCoords, mOrientedXPrecision, mOrientedYPrecision, - xCursorPosition, yCursorPosition, mPointerSimple.downTime, - /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, displayId, policyFlags, AMOTION_EVENT_ACTION_UP, 0, + 0, metaState, mLastRawState.buttonState, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, + &mPointerSimple.lastProperties, &mPointerSimple.lastCoords, + mOrientedXPrecision, mOrientedYPrecision, xCursorPosition, + yCursorPosition, mPointerSimple.downTime, + /* videoFrames */ {})); } if (mPointerSimple.hovering && !hovering) { mPointerSimple.hovering = false; // Send hover exit. - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0, - metaState, mLastRawState.buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties, - &mPointerSimple.lastCoords, mOrientedXPrecision, mOrientedYPrecision, - xCursorPosition, yCursorPosition, mPointerSimple.downTime, - /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, displayId, policyFlags, + AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0, metaState, + mLastRawState.buttonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, + &mPointerSimple.lastProperties, &mPointerSimple.lastCoords, + mOrientedXPrecision, mOrientedYPrecision, xCursorPosition, + yCursorPosition, mPointerSimple.downTime, + /* videoFrames */ {})); } if (down) { @@ -3542,25 +3629,26 @@ void TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uin mPointerSimple.downTime = when; // Send down. - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - displayId, policyFlags, AMOTION_EVENT_ACTION_DOWN, 0, 0, - metaState, mCurrentRawState.buttonState, - MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, - &mPointerSimple.currentProperties, &mPointerSimple.currentCoords, - mOrientedXPrecision, mOrientedYPrecision, xCursorPosition, - yCursorPosition, mPointerSimple.downTime, /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, displayId, policyFlags, + AMOTION_EVENT_ACTION_DOWN, 0, 0, metaState, + mCurrentRawState.buttonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, + &mPointerSimple.currentProperties, + &mPointerSimple.currentCoords, mOrientedXPrecision, + mOrientedYPrecision, xCursorPosition, yCursorPosition, + mPointerSimple.downTime, /* videoFrames */ {})); } // Send move. - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - displayId, policyFlags, AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState, - mCurrentRawState.buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties, - &mPointerSimple.currentCoords, mOrientedXPrecision, - mOrientedYPrecision, xCursorPosition, yCursorPosition, - mPointerSimple.downTime, /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, displayId, policyFlags, AMOTION_EVENT_ACTION_MOVE, + 0, 0, metaState, mCurrentRawState.buttonState, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, + &mPointerSimple.currentProperties, + &mPointerSimple.currentCoords, mOrientedXPrecision, + mOrientedYPrecision, xCursorPosition, yCursorPosition, + mPointerSimple.downTime, /* videoFrames */ {})); } if (hovering) { @@ -3568,25 +3656,26 @@ void TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uin mPointerSimple.hovering = true; // Send hover enter. - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_ENTER, 0, 0, - metaState, mCurrentRawState.buttonState, - MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, - &mPointerSimple.currentProperties, &mPointerSimple.currentCoords, - mOrientedXPrecision, mOrientedYPrecision, xCursorPosition, - yCursorPosition, mPointerSimple.downTime, /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, displayId, policyFlags, + AMOTION_EVENT_ACTION_HOVER_ENTER, 0, 0, metaState, + mCurrentRawState.buttonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, + &mPointerSimple.currentProperties, + &mPointerSimple.currentCoords, mOrientedXPrecision, + mOrientedYPrecision, xCursorPosition, yCursorPosition, + mPointerSimple.downTime, /* videoFrames */ {})); } // Send hover move. - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, - metaState, mCurrentRawState.buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties, - &mPointerSimple.currentCoords, mOrientedXPrecision, - mOrientedYPrecision, xCursorPosition, yCursorPosition, - mPointerSimple.downTime, /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back( + NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, + displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, + metaState, mCurrentRawState.buttonState, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, + &mPointerSimple.currentProperties, &mPointerSimple.currentCoords, + mOrientedXPrecision, mOrientedYPrecision, xCursorPosition, + yCursorPosition, mPointerSimple.downTime, /* videoFrames */ {})); } if (mCurrentRawState.rawVScroll || mCurrentRawState.rawHScroll) { @@ -3601,14 +3690,14 @@ void TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uin pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll); - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState, - mCurrentRawState.buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties, - &pointerCoords, mOrientedXPrecision, mOrientedYPrecision, - xCursorPosition, yCursorPosition, mPointerSimple.downTime, - /* videoFrames */ {}); - getListener().notifyMotion(&args); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, + 0, 0, metaState, mCurrentRawState.buttonState, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, + &mPointerSimple.currentProperties, &pointerCoords, + mOrientedXPrecision, mOrientedYPrecision, xCursorPosition, + yCursorPosition, mPointerSimple.downTime, + /* videoFrames */ {})); } // Save state. @@ -3618,23 +3707,23 @@ void TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uin } else { mPointerSimple.reset(); } + return out; } -void TouchInputMapper::abortPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { +std::list TouchInputMapper::abortPointerSimple(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags) { mPointerSimple.currentCoords.clear(); mPointerSimple.currentProperties.clear(); - dispatchPointerSimple(when, readTime, policyFlags, false, false); + return dispatchPointerSimple(when, readTime, policyFlags, false, false); } -void TouchInputMapper::dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, - uint32_t source, int32_t action, int32_t actionButton, - int32_t flags, int32_t metaState, int32_t buttonState, - int32_t edgeFlags, const PointerProperties* properties, - const PointerCoords* coords, const uint32_t* idToIndex, - BitSet32 idBits, int32_t changedId, float xPrecision, - float yPrecision, nsecs_t downTime, - MotionClassification classification) { +NotifyMotionArgs TouchInputMapper::dispatchMotion( + nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action, + int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState, + int32_t edgeFlags, const PointerProperties* properties, const PointerCoords* coords, + const uint32_t* idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision, + float yPrecision, nsecs_t downTime, MotionClassification classification) { PointerCoords pointerCoords[MAX_POINTERS]; PointerProperties pointerProperties[MAX_POINTERS]; uint32_t pointerCount = 0; @@ -3680,12 +3769,11 @@ void TouchInputMapper::dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t p std::vector frames = getDeviceContext().getVideoFrames(); std::for_each(frames.begin(), frames.end(), [this](TouchVideoFrame& frame) { frame.rotate(this->mInputDeviceOrientation); }); - NotifyMotionArgs args(getContext()->getNextId(), when, readTime, deviceId, source, displayId, - policyFlags, action, actionButton, flags, metaState, buttonState, - classification, edgeFlags, pointerCount, pointerProperties, pointerCoords, - xPrecision, yPrecision, xCursorPosition, yCursorPosition, downTime, - std::move(frames)); - getListener().notifyMotion(&args); + return NotifyMotionArgs(getContext()->getNextId(), when, readTime, deviceId, source, displayId, + policyFlags, action, actionButton, flags, metaState, buttonState, + classification, edgeFlags, pointerCount, pointerProperties, + pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition, + downTime, std::move(frames)); } bool TouchInputMapper::updateMovedPointers(const PointerProperties* inProperties, @@ -3718,9 +3806,11 @@ bool TouchInputMapper::updateMovedPointers(const PointerProperties* inProperties return changed; } -void TouchInputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) { - abortPointerUsage(when, readTime, 0 /*policyFlags*/); - abortTouches(when, readTime, 0 /* policyFlags*/); +std::list TouchInputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) { + std::list out; + out += abortPointerUsage(when, readTime, 0 /*policyFlags*/); + out += abortTouches(when, readTime, 0 /* policyFlags*/); + return out; } // Transform input device coordinates to display panel coordinates. diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 76fdf5dcee..50f30c824e 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -140,18 +140,21 @@ public: uint32_t getSources() const override; void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; void dump(std::string& dump) override; - void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) override; - void reset(nsecs_t when) override; - void process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list configure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) override; + [[nodiscard]] std::list reset(nsecs_t when) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode) override; int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override; bool markSupportedKeyCodes(uint32_t sourceMask, const std::vector& keyCodes, uint8_t* outFlags) override; - void cancelTouch(nsecs_t when, nsecs_t readTime) override; - void timeoutExpired(nsecs_t when) override; - void updateExternalStylusState(const StylusState& state) override; + [[nodiscard]] std::list cancelTouch(nsecs_t when, nsecs_t readTime) override; + [[nodiscard]] std::list timeoutExpired(nsecs_t when) override; + [[nodiscard]] std::list updateExternalStylusState( + const StylusState& state) override; std::optional getAssociatedDisplayId() override; protected: @@ -728,30 +731,42 @@ private: void initializeOrientedRanges(); void initializeSizeRanges(); - void sync(nsecs_t when, nsecs_t readTime); - - bool consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); - void processRawTouches(bool timeout); - void cookAndDispatch(nsecs_t when, nsecs_t readTime); - void dispatchVirtualKey(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, - int32_t keyEventAction, int32_t keyEventFlags); - - void dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); - void dispatchHoverExit(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); - void dispatchHoverEnterAndMove(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); - void dispatchButtonRelease(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); - void dispatchButtonPress(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); + [[nodiscard]] std::list sync(nsecs_t when, nsecs_t readTime); + + [[nodiscard]] std::list consumeRawTouches(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, bool& outConsumed); + [[nodiscard]] std::list processRawTouches(bool timeout); + [[nodiscard]] std::list cookAndDispatch(nsecs_t when, nsecs_t readTime); + [[nodiscard]] NotifyKeyArgs dispatchVirtualKey(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, int32_t keyEventAction, + int32_t keyEventFlags); + + [[nodiscard]] std::list dispatchTouches(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + [[nodiscard]] std::list dispatchHoverExit(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + [[nodiscard]] std::list dispatchHoverEnterAndMove(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + [[nodiscard]] std::list dispatchButtonRelease(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + [[nodiscard]] std::list dispatchButtonPress(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); const BitSet32& findActiveIdBits(const CookedPointerData& cookedPointerData); void cookPointerData(); - void abortTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); - - void dispatchPointerUsage(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, - PointerUsage pointerUsage); - void abortPointerUsage(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); - - void dispatchPointerGestures(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, - bool isTimeout); - void abortPointerGestures(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); + [[nodiscard]] std::list abortTouches(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + + [[nodiscard]] std::list dispatchPointerUsage(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, + PointerUsage pointerUsage); + [[nodiscard]] std::list abortPointerUsage(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + + [[nodiscard]] std::list dispatchPointerGestures(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, + bool isTimeout); + [[nodiscard]] std::list abortPointerGestures(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); bool preparePointerGestures(nsecs_t when, bool* outCancelPreviousGesture, bool* outFinishPreviousGesture, bool isTimeout); @@ -759,15 +774,21 @@ private: // between the last and current events. Uses a relative motion. void moveMousePointerFromPointerDelta(nsecs_t when, uint32_t pointerId); - void dispatchPointerStylus(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); - void abortPointerStylus(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); + [[nodiscard]] std::list dispatchPointerStylus(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + [[nodiscard]] std::list abortPointerStylus(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); - void dispatchPointerMouse(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); - void abortPointerMouse(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); + [[nodiscard]] std::list dispatchPointerMouse(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); + [[nodiscard]] std::list abortPointerMouse(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); - void dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, bool down, - bool hovering); - void abortPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); + [[nodiscard]] std::list dispatchPointerSimple(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags, bool down, + bool hovering); + [[nodiscard]] std::list abortPointerSimple(nsecs_t when, nsecs_t readTime, + uint32_t policyFlags); bool assignExternalStylusId(const RawState& state, bool timeout); void applyExternalStylusButtonState(nsecs_t when); @@ -777,12 +798,12 @@ private: // If the changedId is >= 0 and the action is POINTER_DOWN or POINTER_UP, the // method will take care of setting the index and transmuting the action to DOWN or UP // it is the first / last pointer to go down / up. - void dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, - int32_t action, int32_t actionButton, int32_t flags, int32_t metaState, - int32_t buttonState, int32_t edgeFlags, const PointerProperties* properties, - const PointerCoords* coords, const uint32_t* idToIndex, BitSet32 idBits, - int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime, - MotionClassification classification); + [[nodiscard]] NotifyMotionArgs dispatchMotion( + nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action, + int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState, + int32_t edgeFlags, const PointerProperties* properties, const PointerCoords* coords, + const uint32_t* idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision, + float yPrecision, nsecs_t downTime, MotionClassification classification); // Updates pointer coords and properties for pointers with specified ids that have moved. // Returns true if any of them changed. diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp index 33db527db1..7645b12f1b 100644 --- a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp @@ -35,16 +35,18 @@ void VibratorInputMapper::populateDeviceInfo(InputDeviceInfo* info) { info->setVibrator(true); } -void VibratorInputMapper::process(const RawEvent* rawEvent) { +std::list VibratorInputMapper::process(const RawEvent* rawEvent) { // TODO: Handle FF_STATUS, although it does not seem to be widely supported. + return {}; } -void VibratorInputMapper::vibrate(const VibrationSequence& sequence, ssize_t repeat, - int32_t token) { +std::list VibratorInputMapper::vibrate(const VibrationSequence& sequence, + ssize_t repeat, int32_t token) { if (DEBUG_VIBRATOR) { ALOGD("vibrate: deviceId=%d, pattern=[%s], repeat=%zd, token=%d", getDeviceId(), sequence.toString().c_str(), repeat, token); } + std::list out; mVibrating = true; mSequence = sequence; @@ -53,19 +55,22 @@ void VibratorInputMapper::vibrate(const VibrationSequence& sequence, ssize_t rep mIndex = -1; // Request InputReader to notify InputManagerService for vibration started. - NotifyVibratorStateArgs args(getContext()->getNextId(), systemTime(), getDeviceId(), true); - getListener().notifyVibratorState(&args); - nextStep(); + out.push_back( + NotifyVibratorStateArgs(getContext()->getNextId(), systemTime(), getDeviceId(), true)); + out += nextStep(); + return out; } -void VibratorInputMapper::cancelVibrate(int32_t token) { +std::list VibratorInputMapper::cancelVibrate(int32_t token) { if (DEBUG_VIBRATOR) { ALOGD("cancelVibrate: deviceId=%d, token=%d", getDeviceId(), token); } + std::list out; if (mVibrating && mToken == token) { - stopVibrating(); + out.push_back(stopVibrating()); } + return out; } bool VibratorInputMapper::isVibrating() { @@ -76,26 +81,29 @@ std::vector VibratorInputMapper::getVibratorIds() { return getDeviceContext().getVibratorIds(); } -void VibratorInputMapper::timeoutExpired(nsecs_t when) { +std::list VibratorInputMapper::timeoutExpired(nsecs_t when) { + std::list out; if (mVibrating) { if (when >= mNextStepTime) { - nextStep(); + out += nextStep(); } else { getContext()->requestTimeoutAtTime(mNextStepTime); } } + return out; } -void VibratorInputMapper::nextStep() { +std::list VibratorInputMapper::nextStep() { if (DEBUG_VIBRATOR) { ALOGD("nextStep: index=%d, vibrate deviceId=%d", (int)mIndex, getDeviceId()); } + std::list out; mIndex += 1; if (size_t(mIndex) >= mSequence.pattern.size()) { if (mRepeat < 0) { // We are done. - stopVibrating(); - return; + out.push_back(stopVibrating()); + return out; } mIndex = mRepeat; } @@ -122,9 +130,10 @@ void VibratorInputMapper::nextStep() { if (DEBUG_VIBRATOR) { ALOGD("nextStep: scheduled timeout in %lldms", element.duration.count()); } + return out; } -void VibratorInputMapper::stopVibrating() { +NotifyVibratorStateArgs VibratorInputMapper::stopVibrating() { mVibrating = false; if (DEBUG_VIBRATOR) { ALOGD("stopVibrating: sending cancel vibrate deviceId=%d", getDeviceId()); @@ -132,8 +141,7 @@ void VibratorInputMapper::stopVibrating() { getDeviceContext().cancelVibrate(); // Request InputReader to notify InputManagerService for vibration complete. - NotifyVibratorStateArgs args(getContext()->getNextId(), systemTime(), getDeviceId(), false); - getListener().notifyVibratorState(&args); + return NotifyVibratorStateArgs(getContext()->getNextId(), systemTime(), getDeviceId(), false); } void VibratorInputMapper::dump(std::string& dump) { diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.h b/services/inputflinger/reader/mapper/VibratorInputMapper.h index 894c5739e4..e98f63a8b4 100644 --- a/services/inputflinger/reader/mapper/VibratorInputMapper.h +++ b/services/inputflinger/reader/mapper/VibratorInputMapper.h @@ -27,13 +27,14 @@ public: virtual uint32_t getSources() const override; virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; - virtual void process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; - virtual void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token) override; - virtual void cancelVibrate(int32_t token) override; + [[nodiscard]] std::list vibrate(const VibrationSequence& sequence, ssize_t repeat, + int32_t token) override; + [[nodiscard]] std::list cancelVibrate(int32_t token) override; virtual bool isVibrating() override; virtual std::vector getVibratorIds() override; - virtual void timeoutExpired(nsecs_t when) override; + [[nodiscard]] std::list timeoutExpired(nsecs_t when) override; virtual void dump(std::string& dump) override; private: @@ -44,8 +45,8 @@ private: ssize_t mIndex; nsecs_t mNextStepTime; - void nextStep(); - void stopVibrating(); + [[nodiscard]] std::list nextStep(); + [[nodiscard]] NotifyVibratorStateArgs stopVibrating(); }; } // namespace android diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 8972e9fcdf..dded6a13e0 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -1190,7 +1190,8 @@ private: } } - void configure(nsecs_t, const InputReaderConfiguration* config, uint32_t changes) override { + std::list configure(nsecs_t, const InputReaderConfiguration* config, + uint32_t changes) override { std::scoped_lock lock(mLock); mConfigureWasCalled = true; @@ -1201,19 +1202,22 @@ private: } mStateChangedCondition.notify_all(); + return {}; } - void reset(nsecs_t) override { + std::list reset(nsecs_t) override { std::scoped_lock lock(mLock); mResetWasCalled = true; mStateChangedCondition.notify_all(); + return {}; } - void process(const RawEvent* rawEvent) override { + std::list process(const RawEvent* rawEvent) override { std::scoped_lock lock(mLock); mLastEvent = *rawEvent; mProcessWasCalled = true; mStateChangedCondition.notify_all(); + return {}; } int32_t getKeyCodeState(uint32_t, int32_t keyCode) override { @@ -2736,7 +2740,7 @@ TEST_F(InputDeviceTest, CountryCodeCorrectlyMapped) { // Configuration mDevice->addMapper(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD); InputReaderConfiguration config; - mDevice->configure(ARBITRARY_TIME, &config, 0); + std::list unused = mDevice->configure(ARBITRARY_TIME, &config, 0); ASSERT_EQ(InputDeviceCountryCode::INTERNATIONAL, mDevice->getDeviceInfo().getCountryCode()); } @@ -2748,10 +2752,10 @@ TEST_F(InputDeviceTest, WhenDeviceCreated_EnabledIsFalse) { TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) { // Configuration. InputReaderConfiguration config; - mDevice->configure(ARBITRARY_TIME, &config, 0); + std::list unused = mDevice->configure(ARBITRARY_TIME, &config, 0); // Reset. - mDevice->reset(ARBITRARY_TIME); + unused += mDevice->reset(ARBITRARY_TIME); NotifyDeviceResetArgs resetArgs; ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); @@ -2807,7 +2811,7 @@ TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRe mapper2.setMetaState(AMETA_SHIFT_ON); InputReaderConfiguration config; - mDevice->configure(ARBITRARY_TIME, &config, 0); + std::list unused = mDevice->configure(ARBITRARY_TIME, &config, 0); std::string propertyValue; ASSERT_TRUE(mDevice->getConfiguration().tryGetProperty("key", propertyValue)) @@ -2818,7 +2822,7 @@ TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRe ASSERT_NO_FATAL_FAILURE(mapper2.assertConfigureWasCalled()); // Reset - mDevice->reset(ARBITRARY_TIME); + unused += mDevice->reset(ARBITRARY_TIME); ASSERT_NO_FATAL_FAILURE(mapper1.assertResetWasCalled()); ASSERT_NO_FATAL_FAILURE(mapper2.assertResetWasCalled()); @@ -2874,7 +2878,7 @@ TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRe // Event handling. RawEvent event; event.deviceId = EVENTHUB_ID; - mDevice->process(&event, 1); + unused += mDevice->process(&event, 1); ASSERT_NO_FATAL_FAILURE(mapper1.assertProcessWasCalled()); ASSERT_NO_FATAL_FAILURE(mapper2.assertProcessWasCalled()); @@ -2887,7 +2891,8 @@ TEST_F(InputDeviceTest, Configure_AssignsDisplayPort) { mDevice->addMapper(EVENTHUB_ID, AINPUT_SOURCE_TOUCHSCREEN); // First Configuration. - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); + std::list unused = + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); // Device should be enabled by default. ASSERT_TRUE(mDevice->isEnabled()); @@ -2897,8 +2902,8 @@ TEST_F(InputDeviceTest, Configure_AssignsDisplayPort) { const std::string UNIQUE_ID = "local:1"; mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi); - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::CHANGE_DISPLAY_INFO); // Device should be disabled because it is associated with a specific display via // input port <-> display port association, but the corresponding display is not found ASSERT_FALSE(mDevice->isEnabled()); @@ -2907,19 +2912,19 @@ TEST_F(InputDeviceTest, Configure_AssignsDisplayPort) { mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0, true /*isActive*/, UNIQUE_ID, hdmi, ViewportType::INTERNAL); - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::CHANGE_DISPLAY_INFO); ASSERT_TRUE(mDevice->isEnabled()); // Device should be disabled after set disable. mFakePolicy->addDisabledDevice(mDevice->getId()); - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_ENABLED_STATE); + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::CHANGE_ENABLED_STATE); ASSERT_FALSE(mDevice->isEnabled()); // Device should still be disabled even found the associated display. - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::CHANGE_DISPLAY_INFO); ASSERT_FALSE(mDevice->isEnabled()); } @@ -2927,47 +2932,49 @@ TEST_F(InputDeviceTest, Configure_AssignsDisplayUniqueId) { // Device should be enabled by default. mFakePolicy->clearViewports(); mDevice->addMapper(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD); - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); + std::list unused = + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); ASSERT_TRUE(mDevice->isEnabled()); // Device should be disabled because it is associated with a specific display, but the // corresponding display is not found. mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::CHANGE_DISPLAY_INFO); ASSERT_FALSE(mDevice->isEnabled()); // Device should be enabled when a display is found. mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::CHANGE_DISPLAY_INFO); ASSERT_TRUE(mDevice->isEnabled()); // Device should be disabled after set disable. mFakePolicy->addDisabledDevice(mDevice->getId()); - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_ENABLED_STATE); + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::CHANGE_ENABLED_STATE); ASSERT_FALSE(mDevice->isEnabled()); // Device should still be disabled even found the associated display. - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::CHANGE_DISPLAY_INFO); ASSERT_FALSE(mDevice->isEnabled()); } TEST_F(InputDeviceTest, Configure_UniqueId_CorrectlyMatches) { mFakePolicy->clearViewports(); mDevice->addMapper(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD); - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); + std::list unused = + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::CHANGE_DISPLAY_INFO); ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueId()); } @@ -3028,7 +3035,7 @@ protected: mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, key, value); } - void configureDevice(uint32_t changes) { + std::list configureDevice(uint32_t changes) { if (!changes || (changes & (InputReaderConfiguration::CHANGE_DISPLAY_INFO | @@ -3036,9 +3043,14 @@ protected: mReader->requestRefreshConfiguration(changes); mReader->loopOnce(); } - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), changes); + std::list out = + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), changes); // Loop the reader to flush the input listener queue. + for (const NotifyArgs& args : out) { + mFakeListener->notify(args); + } mReader->loopOnce(); + return out; } std::shared_ptr newDevice(int32_t deviceId, const std::string& name, @@ -3060,9 +3072,12 @@ protected: T& addMapperAndConfigure(Args... args) { T& mapper = mDevice->addMapper(EVENTHUB_ID, args...); configureDevice(0); - mDevice->reset(ARBITRARY_TIME); - mapper.reset(ARBITRARY_TIME); + std::list resetArgList = mDevice->reset(ARBITRARY_TIME); + resetArgList += mapper.reset(ARBITRARY_TIME); // Loop the reader to flush the input listener queue. + for (const NotifyArgs& loopArgs : resetArgList) { + mFakeListener->notify(loopArgs); + } mReader->loopOnce(); return mapper; } @@ -3079,8 +3094,8 @@ protected: mFakePolicy->clearViewports(); } - void process(InputMapper& mapper, nsecs_t when, nsecs_t readTime, int32_t type, int32_t code, - int32_t value) { + std::list process(InputMapper& mapper, nsecs_t when, nsecs_t readTime, int32_t type, + int32_t code, int32_t value) { RawEvent event; event.when = when; event.readTime = readTime; @@ -3088,9 +3103,13 @@ protected: event.type = type; event.code = code; event.value = value; - mapper.process(&event); + std::list processArgList = mapper.process(&event); + for (const NotifyArgs& args : processArgList) { + mFakeListener->notify(args); + } // Loop the reader to flush the input listener queue. mReader->loopOnce(); + return processArgList; } static void assertMotionRange(const InputDeviceInfo& info, @@ -3166,14 +3185,17 @@ TEST_F(SwitchInputMapperTest, GetSwitchState) { TEST_F(SwitchInputMapperTest, Process) { SwitchInputMapper& mapper = addMapperAndConfigure(); - - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_LID, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_JACK_PHYSICAL_INSERT, 1); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_HEADPHONE_INSERT, 0); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - - NotifySwitchArgs args; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifySwitchWasCalled(&args)); + std::list out; + out = process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_LID, 1); + ASSERT_TRUE(out.empty()); + out = process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_JACK_PHYSICAL_INSERT, 1); + ASSERT_TRUE(out.empty()); + out = process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_HEADPHONE_INSERT, 0); + ASSERT_TRUE(out.empty()); + out = process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + + ASSERT_EQ(1u, out.size()); + const NotifySwitchArgs& args = std::get(*out.begin()); ASSERT_EQ(ARBITRARY_TIME, args.eventTime); ASSERT_EQ((1U << SW_LID) | (1U << SW_JACK_PHYSICAL_INSERT), args.switchValues); ASSERT_EQ((1U << SW_LID) | (1U << SW_JACK_PHYSICAL_INSERT) | (1 << SW_HEADPHONE_INSERT), @@ -3220,22 +3242,23 @@ TEST_F(VibratorInputMapperTest, Vibrate) { ASSERT_FALSE(mapper.isVibrating()); // Start vibrating - mapper.vibrate(sequence, -1 /* repeat */, VIBRATION_TOKEN); + std::list out = mapper.vibrate(sequence, -1 /* repeat */, VIBRATION_TOKEN); ASSERT_TRUE(mapper.isVibrating()); // Verify vibrator state listener was notified. mReader->loopOnce(); - NotifyVibratorStateArgs args; - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyVibratorStateWasCalled(&args)); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_TRUE(args.isOn); + ASSERT_EQ(1u, out.size()); + const NotifyVibratorStateArgs& vibrateArgs = std::get(*out.begin()); + ASSERT_EQ(DEVICE_ID, vibrateArgs.deviceId); + ASSERT_TRUE(vibrateArgs.isOn); // Stop vibrating - mapper.cancelVibrate(VIBRATION_TOKEN); + out = mapper.cancelVibrate(VIBRATION_TOKEN); ASSERT_FALSE(mapper.isVibrating()); // Verify vibrator state listener was notified. mReader->loopOnce(); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyVibratorStateWasCalled(&args)); - ASSERT_EQ(DEVICE_ID, args.deviceId); - ASSERT_FALSE(args.isOn); + ASSERT_EQ(1u, out.size()); + const NotifyVibratorStateArgs& cancelArgs = std::get(*out.begin()); + ASSERT_EQ(DEVICE_ID, cancelArgs.deviceId); + ASSERT_FALSE(cancelArgs.isOn); } // --- SensorInputMapperTest --- @@ -3891,7 +3914,7 @@ TEST_F(KeyboardInputMapperTest, NoMetaStateWhenMetaKeysNotPresent) { AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC); // Meta state should be AMETA_NONE after reset - mapper.reset(ARBITRARY_TIME); + std::list unused = mapper.reset(ARBITRARY_TIME); ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); // Meta state should be AMETA_NONE with update, as device doesn't have the keys. mapper.updateMetaState(AKEYCODE_NUM_LOCK); @@ -3943,8 +3966,10 @@ TEST_F(KeyboardInputMapperTest, Configure_AssignsDisplayPort) { KeyboardInputMapper& mapper2 = device2->addMapper(SECOND_EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); - device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0 /*changes*/); - device2->reset(ARBITRARY_TIME); + std::list unused = + device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + 0 /*changes*/); + unused += device2->reset(ARBITRARY_TIME); // Prepared displays and associated info. constexpr uint8_t hdmi1 = 0; @@ -3955,8 +3980,8 @@ TEST_F(KeyboardInputMapperTest, Configure_AssignsDisplayPort) { mFakePolicy->addInputPortAssociation(USB2, hdmi2); // No associated display viewport found, should disable the device. - device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::CHANGE_DISPLAY_INFO); ASSERT_FALSE(device2->isEnabled()); // Prepare second display. @@ -3966,8 +3991,8 @@ TEST_F(KeyboardInputMapperTest, Configure_AssignsDisplayPort) { setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0, SECONDARY_UNIQUE_ID, hdmi2, ViewportType::EXTERNAL); // Default device will reconfigure above, need additional reconfiguration for another device. - device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::CHANGE_DISPLAY_INFO); // Device should be enabled after the associated display is found. ASSERT_TRUE(mDevice->isEnabled()); @@ -4051,8 +4076,10 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleAfterReattach) { KeyboardInputMapper& mapper2 = device2->addMapper(SECOND_EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); - device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0 /*changes*/); - device2->reset(ARBITRARY_TIME); + std::list unused = + device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + 0 /*changes*/); + unused += device2->reset(ARBITRARY_TIME); ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_CAPSL)); ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_NUML)); @@ -4110,8 +4137,10 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleInMultiDevices) { KeyboardInputMapper& mapper2 = device2->addMapper(SECOND_EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); - device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0 /*changes*/); - device2->reset(ARBITRARY_TIME); + std::list unused = + device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + 0 /*changes*/); + unused += device2->reset(ARBITRARY_TIME); // Initial metastate is AMETA_NONE. ASSERT_EQ(AMETA_NONE, mapper1.getMetaState()); @@ -6850,7 +6879,7 @@ TEST_F(SingleTouchInputMapperTest, Reset_RecreatesTouchState) { // Reset the mapper. When the mapper is reset, we expect it to attempt to recreate the touch // state by reading the current axis values. - mapper.reset(ARBITRARY_TIME); + std::list unused = mapper.reset(ARBITRARY_TIME); // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use // the recreated touch state to generate a down event. @@ -8859,8 +8888,10 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { // Setup the second touch screen device. MultiTouchInputMapper& mapper2 = device2->addMapper(SECOND_EVENTHUB_ID); - device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0 /*changes*/); - device2->reset(ARBITRARY_TIME); + std::list unused = + device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + 0 /*changes*/); + unused += device2->reset(ARBITRARY_TIME); // Setup PointerController. std::shared_ptr fakePointerController = @@ -8879,9 +8910,9 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { prepareSecondaryDisplay(ViewportType::EXTERNAL, hdmi2); // Default device will reconfigure above, need additional reconfiguration for another device. - device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO | - InputReaderConfiguration::CHANGE_SHOW_TOUCHES); + unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::CHANGE_DISPLAY_INFO | + InputReaderConfiguration::CHANGE_SHOW_TOUCHES); // Two fingers down at default display. int32_t x1 = 100, y1 = 125, x2 = 300, y2 = 500; @@ -8911,8 +8942,8 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { // Disable the show touches configuration and ensure the spots are cleared. mFakePolicy->setShowTouches(false); - device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_SHOW_TOUCHES); + unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::CHANGE_SHOW_TOUCHES); ASSERT_TRUE(fakePointerController->getSpots().empty()); } @@ -9500,7 +9531,7 @@ TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) { // Reset the mapper. When the mapper is reset, we expect the current multi-touch state to be // preserved. Resetting should not generate any events. - mapper.reset(ARBITRARY_TIME); + std::list unused = mapper.reset(ARBITRARY_TIME); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use @@ -9535,7 +9566,7 @@ TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState_NoPointersDown) // Reset the mapper. When the mapper is reset, we expect it to restore the latest // raw state where no pointers are down. - mapper.reset(ARBITRARY_TIME); + std::list unused = mapper.reset(ARBITRARY_TIME); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); // Send an empty sync frame. Since there are no pointers, no events are generated. @@ -10149,12 +10180,12 @@ protected: mFakePolicy.clear(); } - void configureDevice(uint32_t changes) { + std::list configureDevice(uint32_t changes) { if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { mReader->requestRefreshConfiguration(changes); mReader->loopOnce(); } - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), changes); + return mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), changes); } std::shared_ptr newDevice(int32_t deviceId, const std::string& name, diff --git a/services/inputflinger/tests/TestInputListener.cpp b/services/inputflinger/tests/TestInputListener.cpp index 57b382c718..29093efc67 100644 --- a/services/inputflinger/tests/TestInputListener.cpp +++ b/services/inputflinger/tests/TestInputListener.cpp @@ -147,7 +147,7 @@ void TestInputListener::assertNotCalled(std::string message) { } template -void TestInputListener::notify(const NotifyArgsType* args) { +void TestInputListener::addToQueue(const NotifyArgsType* args) { std::scoped_lock lock(mLock); std::vector& queue = std::get>(mQueues); @@ -156,35 +156,35 @@ void TestInputListener::notify(const NotifyArgsType* args) { } void TestInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) { - notify(args); + addToQueue(args); } void TestInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) { - notify(args); + addToQueue(args); } void TestInputListener::notifyKey(const NotifyKeyArgs* args) { - notify(args); + addToQueue(args); } void TestInputListener::notifyMotion(const NotifyMotionArgs* args) { - notify(args); + addToQueue(args); } void TestInputListener::notifySwitch(const NotifySwitchArgs* args) { - notify(args); + addToQueue(args); } void TestInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) { - notify(args); + addToQueue(args); } void TestInputListener::notifySensor(const NotifySensorArgs* args) { - notify(args); + addToQueue(args); } void TestInputListener::notifyVibratorState(const NotifyVibratorStateArgs* args) { - notify(args); + addToQueue(args); } } // namespace android diff --git a/services/inputflinger/tests/TestInputListener.h b/services/inputflinger/tests/TestInputListener.h index cad698fd36..4ad1c4264d 100644 --- a/services/inputflinger/tests/TestInputListener.h +++ b/services/inputflinger/tests/TestInputListener.h @@ -67,7 +67,7 @@ private: void assertNotCalled(std::string message); template - void notify(const NotifyArgsType* args); + void addToQueue(const NotifyArgsType* args); virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override; diff --git a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp index 4b542aab19..cc523e12cd 100644 --- a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp @@ -51,12 +51,14 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { }, [&]() -> void { mapper.getSources(); }, [&]() -> void { - mapper.configure(fdp->ConsumeIntegral(), &policyConfig, - fdp->ConsumeIntegral()); + std::list unused = + mapper.configure(fdp->ConsumeIntegral(), &policyConfig, + fdp->ConsumeIntegral()); }, [&]() -> void { // Need to reconfigure with 0 or you risk a NPE. - mapper.configure(fdp->ConsumeIntegral(), &policyConfig, 0); + std::list unused = + mapper.configure(fdp->ConsumeIntegral(), &policyConfig, 0); InputDeviceInfo info; mapper.populateDeviceInfo(&info); }, @@ -68,23 +70,27 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { : fdp->ConsumeIntegral(); // Need to reconfigure with 0 or you risk a NPE. - mapper.configure(fdp->ConsumeIntegral(), &policyConfig, 0); + std::list unused = + mapper.configure(fdp->ConsumeIntegral(), &policyConfig, 0); RawEvent rawEvent{fdp->ConsumeIntegral(), fdp->ConsumeIntegral(), fdp->ConsumeIntegral(), type, code, fdp->ConsumeIntegral()}; - mapper.process(&rawEvent); + unused += mapper.process(&rawEvent); + }, + [&]() -> void { + std::list unused = mapper.reset(fdp->ConsumeIntegral()); }, - [&]() -> void { mapper.reset(fdp->ConsumeIntegral()); }, [&]() -> void { mapper.getScanCodeState(fdp->ConsumeIntegral(), fdp->ConsumeIntegral()); }, [&]() -> void { // Need to reconfigure with 0 or you risk a NPE. - mapper.configure(fdp->ConsumeIntegral(), &policyConfig, 0); + std::list unused = + mapper.configure(fdp->ConsumeIntegral(), &policyConfig, 0); mapper.getAssociatedDisplayId(); }, })(); diff --git a/services/inputflinger/tests/fuzzers/FuzzContainer.h b/services/inputflinger/tests/fuzzers/FuzzContainer.h index 62615d0116..1e0764f10f 100644 --- a/services/inputflinger/tests/fuzzers/FuzzContainer.h +++ b/services/inputflinger/tests/fuzzers/FuzzContainer.h @@ -27,7 +27,7 @@ namespace android { class FuzzContainer { std::shared_ptr mFuzzEventHub; sp mFuzzPolicy; - std::unique_ptr mFuzzListener; + FuzzInputListener mFuzzListener; std::unique_ptr mFuzzContext; std::unique_ptr mFuzzDevice; InputReaderConfiguration mPolicyConfig; @@ -44,9 +44,8 @@ public: // Create mocked objects. mFuzzEventHub = std::make_shared(mFdp); mFuzzPolicy = sp::make(mFdp); - mFuzzListener = std::make_unique(); mFuzzContext = std::make_unique(mFuzzEventHub, mFuzzPolicy, - *mFuzzListener, mFdp); + mFuzzListener, mFdp); InputDeviceIdentifier identifier; identifier.name = deviceName; @@ -60,8 +59,12 @@ public: void configureDevice() { nsecs_t arbitraryTime = mFdp->ConsumeIntegral(); - mFuzzDevice->configure(arbitraryTime, &mPolicyConfig, 0); - mFuzzDevice->reset(arbitraryTime); + std::list out; + out += mFuzzDevice->configure(arbitraryTime, &mPolicyConfig, 0); + out += mFuzzDevice->reset(arbitraryTime); + for (const NotifyArgs& args : out) { + mFuzzListener.notify(args); + } } void addProperty(std::string key, std::string value) { diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp index c48a099bca..e880f55891 100644 --- a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp @@ -63,10 +63,13 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { }, [&]() -> void { mapper.getSources(); }, [&]() -> void { - mapper.configure(fdp->ConsumeIntegral(), &policyConfig, - fdp->ConsumeIntegral()); + std::list unused = + mapper.configure(fdp->ConsumeIntegral(), &policyConfig, + fdp->ConsumeIntegral()); + }, + [&]() -> void { + std::list unused = mapper.reset(fdp->ConsumeIntegral()); }, - [&]() -> void { mapper.reset(fdp->ConsumeIntegral()); }, [&]() -> void { int32_t type, code; type = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidTypes) @@ -79,7 +82,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { type, code, fdp->ConsumeIntegral()}; - mapper.process(&rawEvent); + std::list unused = mapper.process(&rawEvent); }, [&]() -> void { mapper.getKeyCodeState(fdp->ConsumeIntegral(), diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index 03c226600e..bd81761981 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -332,7 +332,6 @@ public: class FuzzInputReaderContext : public InputReaderContext { std::shared_ptr mEventHub; sp mPolicy; - InputListenerInterface& mListener; std::shared_ptr mFdp; public: @@ -340,7 +339,7 @@ public: const sp& policy, InputListenerInterface& listener, std::shared_ptr mFdp) - : mEventHub(eventHub), mPolicy(policy), mListener(listener), mFdp(mFdp) {} + : mEventHub(eventHub), mPolicy(policy), mFdp(mFdp) {} ~FuzzInputReaderContext() {} void updateGlobalMetaState() override {} int32_t getGlobalMetaState() { return mFdp->ConsumeIntegral(); } @@ -355,9 +354,10 @@ public: void requestTimeoutAtTime(nsecs_t when) override {} int32_t bumpGeneration() override { return mFdp->ConsumeIntegral(); } void getExternalStylusDevices(std::vector& outDevices) override {} - void dispatchExternalStylusState(const StylusState& outState) override {} + std::list dispatchExternalStylusState(const StylusState& outState) override { + return {}; + } InputReaderPolicyInterface* getPolicy() override { return mPolicy.get(); } - InputListenerInterface& getListener() override { return mListener; } EventHubInterface* getEventHub() override { return mEventHub.get(); } int32_t getNextId() override { return mFdp->ConsumeIntegral(); } diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp index 59b064249a..99fd0831a9 100644 --- a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp @@ -78,10 +78,13 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { }, [&]() -> void { mapper.getSources(); }, [&]() -> void { - mapper.configure(fdp->ConsumeIntegral(), &policyConfig, - fdp->ConsumeIntegral()); + std::list unused = + mapper.configure(fdp->ConsumeIntegral(), &policyConfig, + fdp->ConsumeIntegral()); + }, + [&]() -> void { + std::list unused = mapper.reset(fdp->ConsumeIntegral()); }, - [&]() -> void { mapper.reset(fdp->ConsumeIntegral()); }, [&]() -> void { int32_t type = fdp->ConsumeBool() ? fdp->PickValueInArray(kValidTypes) : fdp->ConsumeIntegral(); @@ -93,7 +96,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { type, code, fdp->ConsumeIntegral()}; - mapper.process(&rawEvent); + std::list unused = mapper.process(&rawEvent); }, [&]() -> void { mapper.getKeyCodeState(fdp->ConsumeIntegral(), @@ -113,16 +116,20 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { nullptr); }, [&]() -> void { - mapper.cancelTouch(fdp->ConsumeIntegral(), - fdp->ConsumeIntegral()); + std::list unused = + mapper.cancelTouch(fdp->ConsumeIntegral(), + fdp->ConsumeIntegral()); + }, + [&]() -> void { + std::list unused = + mapper.timeoutExpired(fdp->ConsumeIntegral()); }, - [&]() -> void { mapper.timeoutExpired(fdp->ConsumeIntegral()); }, [&]() -> void { StylusState state{fdp->ConsumeIntegral(), fdp->ConsumeFloatingPoint(), fdp->ConsumeIntegral(), fdp->ConsumeIntegral()}; - mapper.updateExternalStylusState(state); + std::list unused = mapper.updateExternalStylusState(state); }, [&]() -> void { mapper.getAssociatedDisplayId(); }, })(); diff --git a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp index e76bd728b8..7416ce9d47 100644 --- a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp @@ -46,7 +46,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { type, code, fdp->ConsumeIntegral()}; - mapper.process(&rawEvent); + std::list unused = mapper.process(&rawEvent); }, [&]() -> void { mapper.getSwitchState(fdp->ConsumeIntegral(), -- GitLab From 37d4692067e7da5b8fede2ddeab23780b60819e0 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Wed, 5 Oct 2022 13:08:51 -0700 Subject: [PATCH 0346/1310] SF: avoid changing refresh rate for ExplicitExact When a layer with ExplicitExact vote is present and the corresponding refresh rate is not available, the refresh rate should remain the same. Bug: 246230302 Test: SF unit tests Change-Id: I3e1712d6494fc9fc46b8d26fdae310231f57e7b1 --- .../Scheduler/RefreshRateConfigs.cpp | 66 ++++++++++++++----- .../Scheduler/RefreshRateConfigs.h | 6 +- .../unittests/RefreshRateConfigsTest.cpp | 39 +++++------ 3 files changed, 68 insertions(+), 43 deletions(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp index c10b817467..30483a2aff 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp @@ -314,7 +314,8 @@ auto RefreshRateConfigs::getRankedRefreshRatesLocked(const std::vectorsecond, score.overallScore}; }); + const bool noLayerScore = std::all_of(scores.begin(), scores.end(), [](RefreshRateScore score) { + return score.overallScore == 0; + }); + if (primaryRangeIsSingleRate) { // If we never scored any layers, then choose the rate from the primary // range instead of picking a random score from the app range. - if (std::all_of(scores.begin(), scores.end(), - [](RefreshRateScore score) { return score.overallScore == 0; })) { + if (noLayerScore) { ALOGV("Layers not scored"); - return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending), + return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending, + /*preferredDisplayModeOpt*/ std::nullopt), kNoSignals}; } else { return {rankedRefreshRates, kNoSignals}; @@ -588,7 +597,8 @@ auto RefreshRateConfigs::getRankedRefreshRatesLocked(const std::vector RefreshRateConfigs::getRefreshRatesByPolicyLocked( - std::optional anchorGroupOpt, RefreshRateOrder refreshRateOrder) const { - std::vector rankings; + std::optional anchorGroupOpt, RefreshRateOrder refreshRateOrder, + std::optional preferredDisplayModeOpt) const { + std::deque rankings; const auto makeRanking = [&](const DisplayModeIterator it) REQUIRES(mLock) { const auto& mode = it->second; + if (anchorGroupOpt && mode->getGroup() != anchorGroupOpt) { + return; + } + + float score = calculateRefreshRateScoreForFps(mode->getFps()); const bool inverseScore = (refreshRateOrder == RefreshRateOrder::Ascending); - const float score = calculateRefreshRateScoreForFps(mode->getFps()); - if (!anchorGroupOpt || mode->getGroup() == anchorGroupOpt) { - rankings.push_back(RefreshRateRanking{mode, inverseScore ? 1.0f / score : score}); + if (inverseScore) { + score = 1.0f / score; + } + if (preferredDisplayModeOpt) { + if (*preferredDisplayModeOpt == mode->getId()) { + rankings.push_front(RefreshRateRanking{mode, /*score*/ 1.0f}); + return; + } + constexpr float kNonPreferredModePenalty = 0.95f; + score *= kNonPreferredModePenalty; } + rankings.push_back(RefreshRateRanking{mode, score}); }; if (refreshRateOrder == RefreshRateOrder::Ascending) { @@ -783,14 +816,15 @@ std::vector RefreshRateConfigs::getRefreshRatesByPolicyLocke } if (!rankings.empty() || !anchorGroupOpt) { - return rankings; + return {rankings.begin(), rankings.end()}; } ALOGW("Can't find %s refresh rate by policy with the same mode group" " as the mode group %d", refreshRateOrder == RefreshRateOrder::Ascending ? "min" : "max", anchorGroupOpt.value()); - return getRefreshRatesByPolicyLocked(/*anchorGroupOpt*/ std::nullopt, refreshRateOrder); + return getRefreshRatesByPolicyLocked(/*anchorGroupOpt*/ std::nullopt, refreshRateOrder, + preferredDisplayModeOpt); } DisplayModePtr RefreshRateConfigs::getActiveModePtr() const { diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h index 2c2e34ac13..721958480b 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h +++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h @@ -375,9 +375,9 @@ private: // Returns the rankings in RefreshRateOrder. May change at runtime. // Only uses the primary range, not the app request range. - std::vector getRefreshRatesByPolicyLocked(std::optional anchorGroupOpt, - RefreshRateOrder) const - REQUIRES(mLock); + std::vector getRefreshRatesByPolicyLocked( + std::optional anchorGroupOpt, RefreshRateOrder, + std::optional preferredDisplayModeOpt) const REQUIRES(mLock); const Policy* getCurrentPolicyLocked() const REQUIRES(mLock); bool isPolicyValidLocked(const Policy& policy) const REQUIRES(mLock); diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp index 00f1b089e5..620825f066 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp @@ -77,7 +77,9 @@ struct TestableRefreshRateConfigs : RefreshRateConfigs { std::vector getRefreshRatesByPolicy( std::optional anchorGroupOpt, RefreshRateOrder refreshRateOrder) const { std::lock_guard lock(mLock); - return RefreshRateConfigs::getRefreshRatesByPolicyLocked(anchorGroupOpt, refreshRateOrder); + return RefreshRateConfigs:: + getRefreshRatesByPolicyLocked(anchorGroupOpt, refreshRateOrder, + /*preferredDisplayModeOpt*/ std::nullopt); } const std::vector& knownFrameRates() const { return mKnownFrameRates; } @@ -344,6 +346,18 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_noLayers) { } } +TEST_F(RefreshRateConfigsTest, getBestRefreshRate_exactDontChangeRefreshRateWhenNotInPolicy) { + TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId72); + + std::vector layers = {{.weight = 1.f}}; + layers[0].vote = LayerVoteType::ExplicitExact; + layers[0].desiredRefreshRate = 120_Hz; + + EXPECT_EQ(SetPolicyResult::Changed, + configs.setDisplayManagerPolicy({kModeId72, {0_Hz, 90_Hz}})); + EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers)); +} + TEST_F(RefreshRateConfigsTest, getBestRefreshRate_60_90) { TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); @@ -1318,29 +1332,6 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExact_WithFractionalRe EXPECT_EQ(lr.desiredRefreshRate, configs.getBestRefreshRate(layers)->getFps()); } } - - // Test that 23.976 will choose 24 if 23.976 is not supported - { - TestableRefreshRateConfigs configs(makeModes(kMode24, kMode25, kMode30, kMode30Frac, - kMode60, kMode60Frac), - kModeId60); - - lr.vote = LayerVoteType::ExplicitExact; - lr.desiredRefreshRate = 23.976_Hz; - lr.name = "ExplicitExact 23.976 Hz"; - EXPECT_EQ(kModeId24, configs.getBestRefreshRate(layers)->getId()); - } - - // Test that 24 will choose 23.976 if 24 is not supported - { - TestableRefreshRateConfigs configs(makeModes(kMode24Frac, kMode25, kMode30, kMode30Frac, - kMode60, kMode60Frac), - kModeId60); - - lr.desiredRefreshRate = 24_Hz; - lr.name = "ExplicitExact 24 Hz"; - EXPECT_EQ(kModeId24Frac, configs.getBestRefreshRate(layers)->getId()); - } } TEST_F(RefreshRateConfigsTest, -- GitLab From 59f6d2df6cbc895651eb2628c9f98505ed9de8bf Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 5 Oct 2022 16:59:56 -0700 Subject: [PATCH 0347/1310] SF: Add TransactionHandler - migrate transaction queueing and flushing into a new class and remove dependencies from other components. - Add a filter interface for other components to participate in transactionready logic. Test: presubmit Bug: 238781169 Change-Id: Ia4da386cd72058126f6f765adafb9cb4d15b1d2b --- libs/gui/LayerState.cpp | 2 +- libs/gui/include/gui/fake/BufferData.h | 51 +++ services/surfaceflinger/Android.bp | 1 + services/surfaceflinger/Layer.h | 4 +- services/surfaceflinger/SurfaceFlinger.cpp | 332 ++++++------------ services/surfaceflinger/SurfaceFlinger.h | 41 +-- .../Tracing/TransactionProtoParser.h | 32 +- .../surfaceflinger/TransactionHandler.cpp | 188 ++++++++++ services/surfaceflinger/TransactionHandler.h | 74 ++++ services/surfaceflinger/TransactionState.h | 9 + .../fuzzer/surfaceflinger_fuzzers_utils.h | 6 +- .../tests/unittests/TestableSurfaceFlinger.h | 9 +- .../unittests/TransactionApplicationTest.cpp | 67 ++-- 13 files changed, 490 insertions(+), 326 deletions(-) create mode 100644 libs/gui/include/gui/fake/BufferData.h create mode 100644 services/surfaceflinger/TransactionHandler.cpp create mode 100644 services/surfaceflinger/TransactionHandler.h diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 4d5978ccf7..2759c58fb1 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -634,7 +634,7 @@ bool layer_state_t::hasBufferChanges() const { } bool layer_state_t::hasValidBuffer() const { - return bufferData && (bufferData->buffer || bufferData->cachedBuffer.isValid()); + return bufferData && (bufferData->hasBuffer() || bufferData->cachedBuffer.isValid()); } status_t layer_state_t::matrix22_t::write(Parcel& output) const { diff --git a/libs/gui/include/gui/fake/BufferData.h b/libs/gui/include/gui/fake/BufferData.h new file mode 100644 index 0000000000..725d11c313 --- /dev/null +++ b/libs/gui/include/gui/fake/BufferData.h @@ -0,0 +1,51 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +namespace android::fake { + +// Class which exposes buffer properties from BufferData without holding on to an actual buffer +class BufferData : public android::BufferData { +public: + BufferData(uint64_t bufferId, uint32_t width, uint32_t height, int32_t pixelFormat, + uint64_t outUsage) + : mBufferId(bufferId), + mWidth(width), + mHeight(height), + mPixelFormat(pixelFormat), + mOutUsage(outUsage) {} + bool hasBuffer() const override { return mBufferId != 0; } + bool hasSameBuffer(const android::BufferData& other) const override { + return getId() == other.getId() && frameNumber == other.frameNumber; + } + uint32_t getWidth() const override { return mWidth; } + uint32_t getHeight() const override { return mHeight; } + uint64_t getId() const override { return mBufferId; } + PixelFormat getPixelFormat() const override { return mPixelFormat; } + uint64_t getUsage() const override { return mOutUsage; } + +private: + uint64_t mBufferId; + uint32_t mWidth; + uint32_t mHeight; + int32_t mPixelFormat; + uint64_t mOutUsage; +}; + +} // namespace android::fake diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index b911ae75d4..809c80be0d 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -194,6 +194,7 @@ filegroup { "Tracing/TransactionTracing.cpp", "Tracing/TransactionProtoParser.cpp", "TransactionCallbackInvoker.cpp", + "TransactionHandler.cpp", "TunnelModeEnabledReporter.cpp", ], } diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 4ff86e5dd6..418056c22c 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -881,7 +881,9 @@ public: bool mPendingHWCDestroy{false}; - bool backpressureEnabled() { return mDrawingState.flags & layer_state_t::eEnableBackpressure; } + bool backpressureEnabled() const { + return mDrawingState.flags & layer_state_t::eEnableBackpressure; + } bool setStretchEffect(const StretchEffect& effect); StretchEffect getStretchEffect() const; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 3419721865..002a637899 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -129,14 +129,11 @@ #include "LayerVector.h" #include "MutexUtils.h" #include "NativeWindowSurface.h" -#include "RefreshRateOverlay.h" #include "RegionSamplingThread.h" -#include "Scheduler/DispSyncSource.h" #include "Scheduler/EventThread.h" #include "Scheduler/LayerHistory.h" #include "Scheduler/Scheduler.h" #include "Scheduler/VsyncConfiguration.h" -#include "Scheduler/VsyncController.h" #include "StartPropertySetThread.h" #include "SurfaceFlingerProperties.h" #include "SurfaceInterceptor.h" @@ -775,6 +772,7 @@ chooseRenderEngineTypeViaSysProp() { void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { ALOGI( "SurfaceFlinger's main thread ready to run. " "Initializing graphics H/W..."); + addTransactionReadyFilters(); Mutex::Autolock lock(mStateLock); // Get a RenderEngine for the given display / config (can't fail) @@ -3691,122 +3689,117 @@ void SurfaceFlinger::setTransactionFlags(uint32_t mask, TransactionSchedule sche } } -int SurfaceFlinger::flushPendingTransactionQueues( - std::vector& transactions, - std::unordered_map, uint64_t, SpHash>& bufferLayersReadyToPresent, - bool tryApplyUnsignaled) { - std::unordered_set, SpHash> applyTokensWithUnsignaledTransactions; - int transactionsPendingBarrier = 0; - auto it = mPendingTransactionQueues.begin(); - while (it != mPendingTransactionQueues.end()) { - auto& [applyToken, transactionQueue] = *it; - while (!transactionQueue.empty()) { - // if we are in LatchUnsignaledConfig::AutoSingleLayer - // then we should have only one applyToken for processing. - // so we can stop further transactions on this applyToken. - if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer && - !applyTokensWithUnsignaledTransactions.empty()) { - ATRACE_NAME("stopTransactionProcessing"); - break; - } +TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyTimelineCheck( + const TransactionHandler::TransactionFlushState& flushState) { + using TransactionReadiness = TransactionHandler::TransactionReadiness; + const auto& transaction = *flushState.transaction; + ATRACE_FORMAT("transactionIsReadyToBeApplied vsyncId: %" PRId64, + transaction.frameTimelineInfo.vsyncId); + TimePoint desiredPresentTime = TimePoint::fromNs(transaction.desiredPresentTime); + // Do not present if the desiredPresentTime has not passed unless it is more than + // one second in the future. We ignore timestamps more than 1 second in the future + // for stability reasons. + if (!transaction.isAutoTimestamp && desiredPresentTime >= mExpectedPresentTime && + desiredPresentTime < mExpectedPresentTime + 1s) { + ATRACE_NAME("not current"); + return TransactionReadiness::NotReady; + } - auto& transaction = transactionQueue.front(); - const auto ready = - transactionIsReadyToBeApplied(transaction, transaction.frameTimelineInfo, - transaction.isAutoTimestamp, - TimePoint::fromNs(transaction.desiredPresentTime), - transaction.originUid, transaction.states, - bufferLayersReadyToPresent, transactions.size(), - tryApplyUnsignaled); - ATRACE_INT("TransactionReadiness", static_cast(ready)); - if (ready == TransactionReadiness::NotReady) { - setTransactionFlags(eTransactionFlushNeeded); - break; - } - if (ready == TransactionReadiness::NotReadyBarrier) { - transactionsPendingBarrier++; - setTransactionFlags(eTransactionFlushNeeded); - break; + if (!mScheduler->isVsyncValid(mExpectedPresentTime, transaction.originUid)) { + ATRACE_NAME("!isVsyncValid"); + return TransactionReadiness::NotReady; + } + + // If the client didn't specify desiredPresentTime, use the vsyncId to determine the + // expected present time of this transaction. + if (transaction.isAutoTimestamp && + frameIsEarly(mExpectedPresentTime, VsyncId{transaction.frameTimelineInfo.vsyncId})) { + ATRACE_NAME("frameIsEarly"); + return TransactionReadiness::NotReady; + } + return TransactionReadiness::Ready; +} + +TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferCheck( + const TransactionHandler::TransactionFlushState& flushState) { + using TransactionReadiness = TransactionHandler::TransactionReadiness; + auto ready = TransactionReadiness::Ready; + flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const layer_state_t& s) -> bool { + sp layer = Layer::fromHandle(s.surface).promote(); + const auto& transaction = *flushState.transaction; + // check for barrier frames + if (s.bufferData->hasBarrier && + ((layer->getDrawingState().frameNumber) < s.bufferData->barrierFrameNumber)) { + const bool willApplyBarrierFrame = + flushState.bufferLayersReadyToPresent.contains(s.surface.get()) && + (flushState.bufferLayersReadyToPresent.get(s.surface.get()) >= + s.bufferData->barrierFrameNumber); + if (!willApplyBarrierFrame) { + ATRACE_NAME("NotReadyBarrier"); + ready = TransactionReadiness::NotReadyBarrier; + return false; } - transaction.traverseStatesWithBuffers([&](const layer_state_t& state) { - const bool frameNumberChanged = state.bufferData->flags.test( - BufferData::BufferDataChange::frameNumberChanged); - if (frameNumberChanged) { - bufferLayersReadyToPresent[state.surface] = state.bufferData->frameNumber; - } else { - // Barrier function only used for BBQ which always includes a frame number - bufferLayersReadyToPresent[state.surface] = - std::numeric_limits::max(); + } + + // If backpressure is enabled and we already have a buffer to commit, keep + // the transaction in the queue. + const bool hasPendingBuffer = + flushState.bufferLayersReadyToPresent.contains(s.surface.get()); + if (layer->backpressureEnabled() && hasPendingBuffer && transaction.isAutoTimestamp) { + ATRACE_NAME("hasPendingBuffer"); + ready = TransactionReadiness::NotReady; + return false; + } + + // check fence status + const bool allowLatchUnsignaled = shouldLatchUnsignaled(layer, s, transaction.states.size(), + flushState.firstTransaction); + ATRACE_FORMAT("%s allowLatchUnsignaled=%s", layer->getName().c_str(), + allowLatchUnsignaled ? "true" : "false"); + + const bool acquireFenceChanged = s.bufferData && + s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) && + s.bufferData->acquireFence; + const bool fenceSignaled = + (!acquireFenceChanged || + s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled); + if (!fenceSignaled) { + if (!allowLatchUnsignaled) { + ready = TransactionReadiness::NotReady; + auto& listener = s.bufferData->releaseBufferListener; + if (listener && + (flushState.queueProcessTime - transaction.postTime) > + std::chrono::nanoseconds(4s).count()) { + mTransactionHandler.onTransactionQueueStalled(transaction, listener); } - }); - const bool appliedUnsignaled = (ready == TransactionReadiness::ReadyUnsignaled); - if (appliedUnsignaled) { - applyTokensWithUnsignaledTransactions.insert(transaction.applyToken); + return false; } - transactions.emplace_back(std::move(transaction)); - transactionQueue.pop(); - mPendingTransactionCount--; - ATRACE_INT("TransactionQueue", mPendingTransactionCount.load()); + ready = enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer + ? TransactionReadiness::ReadyUnsignaledSingle + : TransactionReadiness::ReadyUnsignaled; } + return true; + }); + ATRACE_INT("TransactionReadiness", static_cast(ready)); + return ready; +} - if (transactionQueue.empty()) { - it = mPendingTransactionQueues.erase(it); - } else { - it = std::next(it, 1); - } - } - return transactionsPendingBarrier; +void SurfaceFlinger::addTransactionReadyFilters() { + mTransactionHandler.addTransactionReadyFilter( + std::bind(&SurfaceFlinger::transactionReadyTimelineCheck, this, std::placeholders::_1)); + mTransactionHandler.addTransactionReadyFilter( + std::bind(&SurfaceFlinger::transactionReadyBufferCheck, this, std::placeholders::_1)); } bool SurfaceFlinger::flushTransactionQueues(VsyncId vsyncId) { // to prevent onHandleDestroyed from being called while the lock is held, // we must keep a copy of the transactions (specifically the composer // states) around outside the scope of the lock - std::vector transactions; - // Layer handles that have transactions with buffers that are ready to be applied. - std::unordered_map, uint64_t, SpHash> bufferLayersReadyToPresent; + std::vector transactions = mTransactionHandler.flushTransactions(); { Mutex::Autolock _l(mStateLock); - { - while (!mLocklessTransactionQueue.isEmpty()) { - auto maybeTransaction = mLocklessTransactionQueue.pop(); - if (!maybeTransaction.has_value()) { - break; - } - auto transaction = maybeTransaction.value(); - mPendingTransactionQueues[transaction.applyToken].push(std::move(transaction)); - } - - // Transactions with a buffer pending on a barrier may be on a different applyToken - // than the transaction which satisfies our barrier. In fact this is the exact use case - // that the primitive is designed for. This means we may first process - // the barrier dependent transaction, determine it ineligible to complete - // and then satisfy in a later inner iteration of flushPendingTransactionQueues. - // The barrier dependent transaction was eligible to be presented in this frame - // but we would have prevented it without case. To fix this we continually - // loop through flushPendingTransactionQueues until we perform an iteration - // where the number of transactionsPendingBarrier doesn't change. This way - // we can continue to resolve dependency chains of barriers as far as possible. - int lastTransactionsPendingBarrier = 0; - int transactionsPendingBarrier = 0; - do { - lastTransactionsPendingBarrier = transactionsPendingBarrier; - transactionsPendingBarrier = - flushPendingTransactionQueues(transactions, bufferLayersReadyToPresent, - /*tryApplyUnsignaled*/ false); - } while (lastTransactionsPendingBarrier != transactionsPendingBarrier); - - // We collected all transactions that could apply without latching unsignaled buffers. - // If we are allowing latch unsignaled of some form, now it's the time to go over the - // transactions that were not applied and try to apply them unsignaled. - if (enableLatchUnsignaledConfig != LatchUnsignaledConfig::Disabled) { - flushPendingTransactionQueues(transactions, bufferLayersReadyToPresent, - /*tryApplyUnsignaled*/ true); - } - - return applyTransactions(transactions, vsyncId); - } + return applyTransactions(transactions, vsyncId); } } @@ -3833,7 +3826,7 @@ bool SurfaceFlinger::applyTransactions(std::vector& transactio } bool SurfaceFlinger::transactionFlushNeeded() { - return !mPendingTransactionQueues.empty() || !mLocklessTransactionQueue.isEmpty(); + return mTransactionHandler.hasPendingTransactions(); } bool SurfaceFlinger::frameIsEarly(TimePoint expectedPresentTime, VsyncId vsyncId) const { @@ -3859,7 +3852,7 @@ bool SurfaceFlinger::frameIsEarly(TimePoint expectedPresentTime, VsyncId vsyncId } bool SurfaceFlinger::shouldLatchUnsignaled(const sp& layer, const layer_state_t& state, - size_t numStates, size_t totalTXapplied) const { + size_t numStates, bool firstTransaction) const { if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::Disabled) { ALOGV("%s: false (LatchUnsignaledConfig::Disabled)", __func__); return false; @@ -3878,9 +3871,9 @@ bool SurfaceFlinger::shouldLatchUnsignaled(const sp& layer, const layer_s } if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer) { - if (totalTXapplied > 0) { - ALOGV("%s: false (LatchUnsignaledConfig::AutoSingleLayer; totalTXapplied=%zu)", - __func__, totalTXapplied); + if (!firstTransaction) { + ALOGV("%s: false (LatchUnsignaledConfig::AutoSingleLayer; not first transaction)", + __func__); return false; } @@ -3904,116 +3897,6 @@ bool SurfaceFlinger::shouldLatchUnsignaled(const sp& layer, const layer_s return true; } -auto SurfaceFlinger::transactionIsReadyToBeApplied( - TransactionState& transaction, const FrameTimelineInfo& info, bool isAutoTimestamp, - TimePoint desiredPresentTime, uid_t originUid, const Vector& states, - const std::unordered_map, uint64_t, SpHash>& - bufferLayersReadyToPresent, - size_t totalTXapplied, bool tryApplyUnsignaled) const -> TransactionReadiness { - ATRACE_FORMAT("transactionIsReadyToBeApplied vsyncId: %" PRId64, info.vsyncId); - // Do not present if the desiredPresentTime has not passed unless it is more than one second - // in the future. We ignore timestamps more than 1 second in the future for stability reasons. - if (!isAutoTimestamp && desiredPresentTime >= mExpectedPresentTime && - desiredPresentTime < mExpectedPresentTime + 1s) { - ATRACE_NAME("not current"); - return TransactionReadiness::NotReady; - } - - if (!mScheduler->isVsyncValid(mExpectedPresentTime, originUid)) { - ATRACE_NAME("!isVsyncValid"); - return TransactionReadiness::NotReady; - } - - // If the client didn't specify desiredPresentTime, use the vsyncId to determine the expected - // present time of this transaction. - if (isAutoTimestamp && frameIsEarly(mExpectedPresentTime, VsyncId{info.vsyncId})) { - ATRACE_NAME("frameIsEarly"); - return TransactionReadiness::NotReady; - } - - bool fenceUnsignaled = false; - auto queueProcessTime = systemTime(); - for (const ComposerState& state : states) { - const layer_state_t& s = state.state; - - sp layer = nullptr; - if (s.surface) { - layer = fromHandle(s.surface).promote(); - } else if (s.hasBufferChanges()) { - ALOGW("Transaction with buffer, but no Layer?"); - continue; - } - if (!layer) { - continue; - } - - if (s.hasBufferChanges() && s.bufferData->hasBarrier && - ((layer->getDrawingState().frameNumber) < s.bufferData->barrierFrameNumber)) { - const bool willApplyBarrierFrame = - (bufferLayersReadyToPresent.find(s.surface) != bufferLayersReadyToPresent.end()) && - (bufferLayersReadyToPresent.at(s.surface) >= s.bufferData->barrierFrameNumber); - if (!willApplyBarrierFrame) { - ATRACE_NAME("NotReadyBarrier"); - return TransactionReadiness::NotReadyBarrier; - } - } - - const bool allowLatchUnsignaled = tryApplyUnsignaled && - shouldLatchUnsignaled(layer, s, states.size(), totalTXapplied); - ATRACE_FORMAT("%s allowLatchUnsignaled=%s", layer->getName().c_str(), - allowLatchUnsignaled ? "true" : "false"); - - const bool acquireFenceChanged = s.bufferData && - s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) && - s.bufferData->acquireFence; - fenceUnsignaled = fenceUnsignaled || - (acquireFenceChanged && - s.bufferData->acquireFence->getStatus() == Fence::Status::Unsignaled); - - if (fenceUnsignaled && !allowLatchUnsignaled) { - if (!transaction.sentFenceTimeoutWarning && - queueProcessTime - transaction.postTime > std::chrono::nanoseconds(4s).count()) { - transaction.sentFenceTimeoutWarning = true; - auto listener = s.bufferData->releaseBufferListener; - if (listener) { - listener->onTransactionQueueStalled(); - } - } - - ATRACE_NAME("fence unsignaled"); - return TransactionReadiness::NotReady; - } - - if (s.hasBufferChanges()) { - // If backpressure is enabled and we already have a buffer to commit, keep the - // transaction in the queue. - const bool hasPendingBuffer = bufferLayersReadyToPresent.find(s.surface) != - bufferLayersReadyToPresent.end(); - if (layer->backpressureEnabled() && hasPendingBuffer && isAutoTimestamp) { - ATRACE_NAME("hasPendingBuffer"); - return TransactionReadiness::NotReady; - } - } - } - return fenceUnsignaled ? TransactionReadiness::ReadyUnsignaled : TransactionReadiness::Ready; -} - -void SurfaceFlinger::queueTransaction(TransactionState& state) { - mLocklessTransactionQueue.push(state); - mPendingTransactionCount++; - ATRACE_INT("TransactionQueue", mPendingTransactionCount.load()); - - const auto schedule = [](uint32_t flags) { - if (flags & eEarlyWakeupEnd) return TransactionSchedule::EarlyEnd; - if (flags & eEarlyWakeupStart) return TransactionSchedule::EarlyStart; - return TransactionSchedule::Late; - }(state.flags); - - const auto frameHint = state.isFrameActive() ? FrameHint::kActive : FrameHint::kNone; - - setTransactionFlags(eTransactionFlushNeeded, schedule, state.applyToken, frameHint); -} - status_t SurfaceFlinger::setTransactionState( const FrameTimelineInfo& frameTimelineInfo, const Vector& states, const Vector& displays, uint32_t flags, const sp& applyToken, @@ -4064,7 +3947,16 @@ status_t SurfaceFlinger::setTransactionState( if (mTransactionTracing) { mTransactionTracing->addQueuedTransaction(state); } - queueTransaction(state); + + const auto schedule = [](uint32_t flags) { + if (flags & eEarlyWakeupEnd) return TransactionSchedule::EarlyEnd; + if (flags & eEarlyWakeupStart) return TransactionSchedule::EarlyStart; + return TransactionSchedule::Late; + }(state.flags); + + const auto frameHint = state.isFrameActive() ? FrameHint::kActive : FrameHint::kNone; + setTransactionFlags(eTransactionFlushNeeded, schedule, state.applyToken, frameHint); + mTransactionHandler.queueTransaction(std::move(state)); return NO_ERROR; } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index baaf41ec02..f4fb8de010 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -57,7 +57,6 @@ #include #include -#include "ClientCache.h" #include "Display/DisplayMap.h" #include "Display/PhysicalDisplay.h" #include "DisplayDevice.h" @@ -66,19 +65,17 @@ #include "DisplayIdGenerator.h" #include "Effects/Daltonizer.h" #include "FlagManager.h" -#include "FrameTracker.h" #include "LayerVector.h" -#include "LocklessQueue.h" #include "Scheduler/RefreshRateConfigs.h" #include "Scheduler/RefreshRateStats.h" #include "Scheduler/Scheduler.h" #include "Scheduler/VsyncModulator.h" #include "SurfaceFlingerFactory.h" #include "ThreadContext.h" -#include "TracedOrdinal.h" #include "Tracing/LayerTracing.h" #include "Tracing/TransactionTracing.h" #include "TransactionCallbackInvoker.h" +#include "TransactionHandler.h" #include "TransactionState.h" #include @@ -341,6 +338,7 @@ private: friend class RegionSamplingThread; friend class LayerRenderArea; friend class LayerTracing; + friend class SurfaceComposerAIDL; // For unit tests friend class TestableSurfaceFlinger; @@ -729,11 +727,13 @@ private: bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext); // Returns true if there is at least one transaction that needs to be flushed bool transactionFlushNeeded(); - - int flushPendingTransactionQueues( - std::vector& transactions, - std::unordered_map, uint64_t, SpHash>& bufferLayersReadyToPresent, - bool tryApplyUnsignaled) REQUIRES(mStateLock) REQUIRES(kMainThreadContext); + void addTransactionReadyFilters(); + TransactionHandler::TransactionReadiness transactionReadyTimelineCheck( + const TransactionHandler::TransactionFlushState& flushState) + REQUIRES(kMainThreadContext); + TransactionHandler::TransactionReadiness transactionReadyBufferCheck( + const TransactionHandler::TransactionFlushState& flushState) + REQUIRES(kMainThreadContext); uint32_t setClientStateLocked(const FrameTimelineInfo&, ComposerState&, int64_t desiredPresentTime, bool isAutoTimestamp, @@ -751,23 +751,9 @@ private: void commitOffscreenLayers(); - enum class TransactionReadiness { - NotReady, - NotReadyBarrier, - Ready, - ReadyUnsignaled, - }; - TransactionReadiness transactionIsReadyToBeApplied( - TransactionState&, const FrameTimelineInfo&, bool isAutoTimestamp, - TimePoint desiredPresentTime, uid_t originUid, const Vector&, - const std::unordered_map, uint64_t, SpHash>& - bufferLayersReadyToPresent, - size_t totalTXapplied, bool tryApplyUnsignaled) const REQUIRES(mStateLock) - REQUIRES(kMainThreadContext); - static LatchUnsignaledConfig getLatchUnsignaledConfig(); bool shouldLatchUnsignaled(const sp& layer, const layer_state_t&, size_t numStates, - size_t totalTXapplied) const; + bool firstTransaction) const; bool applyTransactions(std::vector& transactions, VsyncId) REQUIRES(mStateLock); uint32_t setDisplayStateLocked(const DisplayState& s) REQUIRES(mStateLock); @@ -1094,7 +1080,6 @@ private: status_t CheckTransactCodeCredentials(uint32_t code); // Add transaction to the Transaction Queue - void queueTransaction(TransactionState& state); /* * Generic Layer Metadata @@ -1256,10 +1241,6 @@ private: uint32_t mTexturePoolSize = 0; std::vector mTexturePool; - std::unordered_map, std::queue, IListenerHash> - mPendingTransactionQueues; - LocklessQueue mLocklessTransactionQueue; - std::atomic mPendingTransactionCount = 0; std::atomic mNumLayers = 0; // to linkToDeath @@ -1415,7 +1396,7 @@ private: bool early = false; } mPowerHintSessionMode; - friend class SurfaceComposerAIDL; + TransactionHandler mTransactionHandler; }; class SurfaceComposerAIDL : public gui::BnSurfaceComposer { diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.h b/services/surfaceflinger/Tracing/TransactionProtoParser.h index 872a901b21..2232bb9cb1 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.h +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.h @@ -15,6 +15,7 @@ */ #pragma once +#include #include #include @@ -43,35 +44,6 @@ struct TracingLayerState : layer_state_t { TracingLayerCreationArgs args; }; -// Class which exposes buffer properties from BufferData without holding on to the actual buffer -// handle. -class BufferDataStub : public BufferData { -public: - BufferDataStub(uint64_t bufferId, uint32_t width, uint32_t height, int32_t pixelFormat, - uint64_t outUsage) - : mBufferId(bufferId), - mWidth(width), - mHeight(height), - mPixelFormat(pixelFormat), - mOutUsage(outUsage) {} - bool hasBuffer() const override { return mBufferId != 0; } - bool hasSameBuffer(const BufferData& other) const override { - return getId() == other.getId() && frameNumber == other.frameNumber; - } - uint32_t getWidth() const override { return mWidth; } - uint32_t getHeight() const override { return mHeight; } - uint64_t getId() const override { return mBufferId; } - PixelFormat getPixelFormat() const override { return mPixelFormat; } - uint64_t getUsage() const override { return mOutUsage; } - -private: - uint64_t mBufferId; - uint32_t mWidth; - uint32_t mHeight; - int32_t mPixelFormat; - uint64_t mOutUsage; -}; - class TransactionProtoParser { public: // Utility class to map handles to ids and buffers to buffer properties without pulling @@ -87,7 +59,7 @@ public: virtual std::shared_ptr getGraphicData(uint64_t bufferId, uint32_t width, uint32_t height, int32_t pixelFormat, uint64_t usage) const { - return std::make_shared(bufferId, width, height, pixelFormat, usage); + return std::make_shared(bufferId, width, height, pixelFormat, usage); } virtual void getGraphicBufferPropertiesFromCache(client_cache_t /* cachedBuffer */, uint64_t* /* outBufferId */, diff --git a/services/surfaceflinger/TransactionHandler.cpp b/services/surfaceflinger/TransactionHandler.cpp new file mode 100644 index 0000000000..6c6a487b67 --- /dev/null +++ b/services/surfaceflinger/TransactionHandler.cpp @@ -0,0 +1,188 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// #define LOG_NDEBUG 0 +#undef LOG_TAG +#define LOG_TAG "TransactionHandler" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + +#include +#include + +#include "TransactionHandler.h" + +namespace android { + +void TransactionHandler::queueTransaction(TransactionState&& state) { + mLocklessTransactionQueue.push(std::move(state)); + mPendingTransactionCount.fetch_add(1); + ATRACE_INT("TransactionQueue", static_cast(mPendingTransactionCount.load())); +} + +std::vector TransactionHandler::flushTransactions() { + while (!mLocklessTransactionQueue.isEmpty()) { + auto maybeTransaction = mLocklessTransactionQueue.pop(); + if (!maybeTransaction.has_value()) { + break; + } + auto transaction = maybeTransaction.value(); + mPendingTransactionQueues[transaction.applyToken].emplace(std::move(transaction)); + } + + // Collect transaction that are ready to be applied. + std::vector transactions; + TransactionFlushState flushState; + flushState.queueProcessTime = systemTime(); + // Transactions with a buffer pending on a barrier may be on a different applyToken + // than the transaction which satisfies our barrier. In fact this is the exact use case + // that the primitive is designed for. This means we may first process + // the barrier dependent transaction, determine it ineligible to complete + // and then satisfy in a later inner iteration of flushPendingTransactionQueues. + // The barrier dependent transaction was eligible to be presented in this frame + // but we would have prevented it without case. To fix this we continually + // loop through flushPendingTransactionQueues until we perform an iteration + // where the number of transactionsPendingBarrier doesn't change. This way + // we can continue to resolve dependency chains of barriers as far as possible. + int lastTransactionsPendingBarrier = 0; + int transactionsPendingBarrier = 0; + do { + lastTransactionsPendingBarrier = transactionsPendingBarrier; + // Collect transactions that are ready to be applied. + transactionsPendingBarrier = flushPendingTransactionQueues(transactions, flushState); + } while (lastTransactionsPendingBarrier != transactionsPendingBarrier); + + mPendingTransactionCount.fetch_sub(transactions.size()); + ATRACE_INT("TransactionQueue", static_cast(mPendingTransactionCount.load())); + return transactions; +} + +TransactionHandler::TransactionReadiness TransactionHandler::applyFilters( + TransactionFlushState& flushState) { + auto ready = TransactionReadiness::Ready; + for (auto& filter : mTransactionReadyFilters) { + auto perFilterReady = filter(flushState); + switch (perFilterReady) { + case TransactionReadiness::NotReady: + case TransactionReadiness::NotReadyBarrier: + return perFilterReady; + + case TransactionReadiness::ReadyUnsignaled: + case TransactionReadiness::ReadyUnsignaledSingle: + // If one of the filters allows latching an unsignaled buffer, latch this ready + // state. + ready = perFilterReady; + break; + case TransactionReadiness::Ready: + continue; + } + } + return ready; +} + +int TransactionHandler::flushPendingTransactionQueues(std::vector& transactions, + TransactionFlushState& flushState) { + int transactionsPendingBarrier = 0; + auto it = mPendingTransactionQueues.begin(); + while (it != mPendingTransactionQueues.end()) { + auto& queue = it->second; + IBinder* queueToken = it->first.get(); + + // if we have already flushed a transaction with an unsignaled buffer then stop queue + // processing + if (std::find(flushState.queuesWithUnsignaledBuffers.begin(), + flushState.queuesWithUnsignaledBuffers.end(), + queueToken) != flushState.queuesWithUnsignaledBuffers.end()) { + continue; + } + + while (!queue.empty()) { + auto& transaction = queue.front(); + flushState.transaction = &transaction; + auto ready = applyFilters(flushState); + if (ready == TransactionReadiness::NotReadyBarrier) { + transactionsPendingBarrier++; + break; + } else if (ready == TransactionReadiness::NotReady) { + break; + } + + // Transaction is ready move it from the pending queue. + flushState.firstTransaction = false; + removeFromStalledTransactions(transaction.id); + transactions.emplace_back(std::move(transaction)); + queue.pop(); + + // If the buffer is unsignaled, then we don't want to signal other transactions using + // the buffer as a barrier. + auto& readyToApplyTransaction = transactions.back(); + if (ready == TransactionReadiness::Ready) { + readyToApplyTransaction.traverseStatesWithBuffers([&](const layer_state_t& state) { + const bool frameNumberChanged = state.bufferData->flags.test( + BufferData::BufferDataChange::frameNumberChanged); + if (frameNumberChanged) { + flushState.bufferLayersReadyToPresent + .emplace_or_replace(state.surface.get(), + state.bufferData->frameNumber); + } else { + // Barrier function only used for BBQ which always includes a frame number. + // This value only used for barrier logic. + flushState.bufferLayersReadyToPresent + .emplace_or_replace(state.surface.get(), + std::numeric_limits::max()); + } + }); + } else if (ready == TransactionReadiness::ReadyUnsignaledSingle) { + // Track queues with a flushed unsingaled buffer. + flushState.queuesWithUnsignaledBuffers.emplace_back(queueToken); + break; + } + } + + if (queue.empty()) { + it = mPendingTransactionQueues.erase(it); + } else { + it = std::next(it, 1); + } + } + return transactionsPendingBarrier; +} + +void TransactionHandler::addTransactionReadyFilter(TransactionFilter&& filter) { + mTransactionReadyFilters.emplace_back(std::move(filter)); +} + +bool TransactionHandler::hasPendingTransactions() { + return !mPendingTransactionQueues.empty() || !mLocklessTransactionQueue.isEmpty(); +} + +void TransactionHandler::onTransactionQueueStalled(const TransactionState& transaction, + sp& listener) { + if (std::find(mStalledTransactions.begin(), mStalledTransactions.end(), transaction.id) != + mStalledTransactions.end()) { + return; + } + + mStalledTransactions.push_back(transaction.id); + listener->onTransactionQueueStalled(); +} + +void TransactionHandler::removeFromStalledTransactions(uint64_t id) { + auto it = std::find(mStalledTransactions.begin(), mStalledTransactions.end(), id); + if (it != mStalledTransactions.end()) { + mStalledTransactions.erase(it); + } +} +} // namespace android \ No newline at end of file diff --git a/services/surfaceflinger/TransactionHandler.h b/services/surfaceflinger/TransactionHandler.h new file mode 100644 index 0000000000..237b48d55f --- /dev/null +++ b/services/surfaceflinger/TransactionHandler.h @@ -0,0 +1,74 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace android { +class TransactionHandler { +public: + struct TransactionFlushState { + const TransactionState* transaction; + bool firstTransaction = true; + nsecs_t queueProcessTime = 0; + // Layer handles that have transactions with buffers that are ready to be applied. + ftl::SmallMap + bufferLayersReadyToPresent = {}; + ftl::SmallVector queuesWithUnsignaledBuffers; + }; + enum class TransactionReadiness { + NotReady, + NotReadyBarrier, + Ready, + ReadyUnsignaled, + ReadyUnsignaledSingle, + }; + using TransactionFilter = std::function; + + bool hasPendingTransactions(); + std::vector flushTransactions(); + void addTransactionReadyFilter(TransactionFilter&&); + void queueTransaction(TransactionState&&); + void onTransactionQueueStalled(const TransactionState&, sp&); + void removeFromStalledTransactions(uint64_t transactionId); + +private: + // For unit tests + friend class TestableSurfaceFlinger; + + int flushPendingTransactionQueues(std::vector&, TransactionFlushState&); + TransactionReadiness applyFilters(TransactionFlushState&); + std::unordered_map, std::queue, IListenerHash> + mPendingTransactionQueues; + LocklessQueue mLocklessTransactionQueue; + std::atomic mPendingTransactionCount = 0; + ftl::SmallVector mTransactionReadyFilters; + std::vector mStalledTransactions; +}; + +} // namespace android diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h index 95eb503327..3cbfe811ea 100644 --- a/services/surfaceflinger/TransactionState.h +++ b/services/surfaceflinger/TransactionState.h @@ -64,6 +64,15 @@ struct TransactionState { } } + template + void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) const { + for (const auto& [state] : states) { + if (state.hasBufferChanges() && state.hasValidBuffer() && state.surface) { + if (!visitor(state)) return; + } + } + } + // TODO(b/185535769): Remove FrameHint. Instead, reset the idle timer (of the relevant physical // display) on the main thread if commit leads to composite. Then, RefreshRateOverlay should be // able to setFrameRate once, rather than for each transaction. diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 370b23c7ce..49dd80ede7 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -728,8 +728,10 @@ public: return mFlinger->setPowerModeInternal(display, mode); } - auto &getTransactionQueue() { return mFlinger->mLocklessTransactionQueue; } - auto &getPendingTransactionQueue() { return mFlinger->mPendingTransactionQueues; } + auto &getTransactionQueue() { return mFlinger->mTransactionHandler.mLocklessTransactionQueue; } + auto &getPendingTransactionQueue() { + return mFlinger->mTransactionHandler.mPendingTransactionQueues; + } auto setTransactionState( const FrameTimelineInfo &frameTimelineInfo, const Vector &states, diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index e1a0eeb7fe..7b319f52c5 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -417,8 +417,13 @@ public: return mFlinger->SurfaceFlinger::getDisplayNativePrimaries(displayToken, primaries); } - auto& getTransactionQueue() { return mFlinger->mLocklessTransactionQueue; } - auto& getPendingTransactionQueue() { return mFlinger->mPendingTransactionQueues; } + auto& getTransactionQueue() { return mFlinger->mTransactionHandler.mLocklessTransactionQueue; } + auto& getPendingTransactionQueue() { + return mFlinger->mTransactionHandler.mPendingTransactionQueues; + } + size_t getPendingTransactionCount() { + return mFlinger->mTransactionHandler.mPendingTransactionCount.load(); + } auto setTransactionState( const FrameTimelineInfo& frameTimelineInfo, const Vector& states, diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index b4030b3617..db438b70ab 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -14,7 +14,6 @@ * limitations under the License. */ - #undef LOG_TAG #define LOG_TAG "CompositionTest" @@ -22,12 +21,17 @@ #include #include #include +#include #include +#include #include #include #include +#include +#include #include "TestableSurfaceFlinger.h" +#include "TransactionHandler.h" #include "mock/MockEventThread.h" #include "mock/MockVsyncController.h" @@ -78,6 +82,7 @@ public: mFlinger.setupScheduler(std::unique_ptr(mVsyncController), std::unique_ptr(mVSyncTracker), std::move(eventThread), std::move(sfEventThread)); + mFlinger.flinger()->addTransactionReadyFilters(); } TestableSurfaceFlinger mFlinger; @@ -314,7 +319,10 @@ public: ComposerState createComposerState(int layerId, sp fence, uint64_t what) { ComposerState state; - state.state.bufferData = std::make_shared(); + state.state.bufferData = + std::make_shared(/* bufferId */ 123L, /* width */ 1, + /* height */ 2, /* pixelFormat */ 0, + /* outUsage */ 0); state.state.bufferData->acquireFence = std::move(fence); state.state.layerId = layerId; state.state.surface = @@ -361,7 +369,7 @@ public: } mFlinger.flushTransactionQueues(); EXPECT_TRUE(mFlinger.getTransactionQueue().isEmpty()); - EXPECT_EQ(expectedTransactionsPending, mFlinger.getPendingTransactionQueue().size()); + EXPECT_EQ(expectedTransactionsPending, mFlinger.getPendingTransactionCount()); } }; @@ -413,7 +421,9 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBu { createComposerState(kLayerId, fence(Fence::Status::Unsignaled), - layer_state_t::eCropChanged), + layer_state_t::eCropChanged | + layer_state_t:: + eBufferChanged), }); setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); } @@ -536,41 +546,6 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, kExpectedTransactionsPending); } -TEST_F(LatchUnsignaledAutoSingleLayerTest, UnsignaledNotAppliedWhenThereAreSignaled_SignaledFirst) { - const sp kApplyToken1 = - IInterface::asBinder(TransactionCompletedListener::getIInstance()); - const sp kApplyToken2 = sp::make(); - const sp kApplyToken3 = sp::make(); - const auto kLayerId1 = 1; - const auto kLayerId2 = 2; - const auto kExpectedTransactionsPending = 1u; - - const auto signaledTransaction = - createTransactionInfo(kApplyToken1, - { - createComposerState(kLayerId1, - fence(Fence::Status::Signaled), - layer_state_t::eBufferChanged), - }); - const auto signaledTransaction2 = - createTransactionInfo(kApplyToken2, - { - createComposerState(kLayerId1, - fence(Fence::Status::Signaled), - layer_state_t::eBufferChanged), - }); - const auto unsignaledTransaction = - createTransactionInfo(kApplyToken3, - { - createComposerState(kLayerId2, - fence(Fence::Status::Unsignaled), - layer_state_t::eBufferChanged), - }); - - setTransactionStates({signaledTransaction, signaledTransaction2, unsignaledTransaction}, - kExpectedTransactionsPending); -} - TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsTransactionInTheQueueSameApplyToken) { const sp kApplyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); @@ -798,7 +773,7 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheUnsignaledTheQueue) { IInterface::asBinder(TransactionCompletedListener::getIInstance()); const auto kLayerId1 = 1; const auto kLayerId2 = 2; - const auto kExpectedTransactionsPending = 1u; + const auto kExpectedTransactionsPending = 2u; const auto unsignaledTransaction = createTransactionInfo(kApplyToken, @@ -1004,4 +979,16 @@ TEST_F(LatchUnsignaledAlwaysTest, LatchUnsignaledWhenEarlyOffset) { setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); } +TEST(TransactionHandlerTest, QueueTransaction) { + TransactionHandler handler; + TransactionState transaction; + transaction.applyToken = sp::make(); + transaction.id = 42; + handler.queueTransaction(std::move(transaction)); + std::vector transactionsReadyToBeApplied = handler.flushTransactions(); + + EXPECT_EQ(transactionsReadyToBeApplied.size(), 1u); + EXPECT_EQ(transactionsReadyToBeApplied.front().id, 42u); +} + } // namespace android -- GitLab From 5a1e9348471743066a54053b1a7c927ee83af5fc Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 5 Oct 2022 15:10:05 -0700 Subject: [PATCH 0348/1310] Delete InputFlingerService_test This test currently has 2 test cases: 1) sends an inputchannel via binder and then asks for it back. This is just implementation of the service, so it's just testing the implementation. 2) checks that an fd that's sent over binder remains non-blocking. A similar test was already committed into binderLibTest as aosp/2239719. This is general binder behaviour, and it's better if it lives next to the binder stuff. We don't need this test in input. Therefore, delete this test. It's causing some crashes on hwasan devices when libinput is linked statically, and the setup is too complicated to get correctly (and the setup is already done for us in binderLibTest). Bug: 217165277 Test: atest --host --no-bazel-mode inputflinger_tests libinput_tests Change-Id: Iff9970465018e2a9ba73377c3332e014c870e160 --- services/inputflinger/tests/Android.bp | 2 - .../tests/IInputFlingerQuery.aidl | 27 -- .../tests/InputFlingerService_test.cpp | 243 ------------------ 3 files changed, 272 deletions(-) delete mode 100644 services/inputflinger/tests/IInputFlingerQuery.aidl delete mode 100644 services/inputflinger/tests/InputFlingerService_test.cpp diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 76500c577d..fcbb98fce8 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -40,12 +40,10 @@ cc_test { "BlockingQueue_test.cpp", "EventHub_test.cpp", "FocusResolver_test.cpp", - "IInputFlingerQuery.aidl", "InputProcessor_test.cpp", "InputProcessorConverter_test.cpp", "InputDispatcher_test.cpp", "InputReader_test.cpp", - "InputFlingerService_test.cpp", "LatencyTracker_test.cpp", "PreferStylusOverTouch_test.cpp", "TestInputListener.cpp", diff --git a/services/inputflinger/tests/IInputFlingerQuery.aidl b/services/inputflinger/tests/IInputFlingerQuery.aidl deleted file mode 100644 index 5aeb21f6b4..0000000000 --- a/services/inputflinger/tests/IInputFlingerQuery.aidl +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2020, 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. - */ - -import android.InputChannel; -import android.gui.FocusRequest; -import android.gui.WindowInfo; - -/** @hide */ -interface IInputFlingerQuery -{ - /* Test interfaces */ - void getInputChannels(out InputChannel[] channels); - void resetInputManager(); -} diff --git a/services/inputflinger/tests/InputFlingerService_test.cpp b/services/inputflinger/tests/InputFlingerService_test.cpp deleted file mode 100644 index ca548bec3e..0000000000 --- a/services/inputflinger/tests/InputFlingerService_test.cpp +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright (C) 2020 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 - -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#define TAG "InputFlingerServiceTest" - -using android::gui::FocusRequest; -using android::os::BnInputFlinger; -using android::os::IInputFlinger; - -using std::chrono_literals::operator""ms; -using std::chrono_literals::operator""s; - -namespace android { - -static const String16 kTestServiceName = String16("InputFlingerService"); -static const String16 kQueryServiceName = String16("InputFlingerQueryService"); - -// --- InputFlingerServiceTest --- -class InputFlingerServiceTest : public testing::Test { -public: - void SetUp() override; - void TearDown() override; - -protected: - void InitializeInputFlinger(); - - sp mService; - sp mQuery; - -private: - std::unique_ptr mServerChannel, mClientChannel; - std::mutex mLock; -}; - - -class TestInputManager : public BnInputFlinger { -protected: - virtual ~TestInputManager(){}; - -public: - TestInputManager(){}; - - binder::Status getInputChannels(std::vector<::android::InputChannel>* channels); - - status_t dump(int fd, const Vector& args) override; - - binder::Status createInputChannel(const std::string& name, InputChannel* outChannel) override; - binder::Status removeInputChannel(const sp& connectionToken) override; - binder::Status setFocusedWindow(const FocusRequest&) override; - - void reset(); - -private: - mutable Mutex mLock; - std::vector> mInputChannels; -}; - -class TestInputQuery : public BnInputFlingerQuery { -public: - TestInputQuery(sp manager) : mManager(manager){}; - binder::Status getInputChannels(std::vector<::android::InputChannel>* channels) override; - binder::Status resetInputManager() override; - -private: - sp mManager; -}; - -binder::Status TestInputQuery::getInputChannels(std::vector<::android::InputChannel>* channels) { - return mManager->getInputChannels(channels); -} - -binder::Status TestInputQuery::resetInputManager() { - mManager->reset(); - return binder::Status::ok(); -} - -binder::Status TestInputManager::createInputChannel(const std::string& name, - InputChannel* outChannel) { - AutoMutex _l(mLock); - std::unique_ptr serverChannel; - std::unique_ptr clientChannel; - InputChannel::openInputChannelPair(name, serverChannel, clientChannel); - - clientChannel->copyTo(*outChannel); - - mInputChannels.emplace_back(std::move(serverChannel)); - - return binder::Status::ok(); -} - -binder::Status TestInputManager::removeInputChannel(const sp& connectionToken) { - AutoMutex _l(mLock); - - auto it = std::find_if(mInputChannels.begin(), mInputChannels.end(), - [&](std::shared_ptr& c) { - return c->getConnectionToken() == connectionToken; - }); - if (it != mInputChannels.end()) { - mInputChannels.erase(it); - } - - return binder::Status::ok(); -} - -status_t TestInputManager::dump(int fd, const Vector& args) { - std::string dump; - - dump += " InputFlinger dump\n"; - - ::write(fd, dump.c_str(), dump.size()); - return NO_ERROR; -} - -binder::Status TestInputManager::getInputChannels(std::vector<::android::InputChannel>* channels) { - channels->clear(); - for (std::shared_ptr& channel : mInputChannels) { - channels->push_back(*channel); - } - return binder::Status::ok(); -} - -binder::Status TestInputManager::setFocusedWindow(const FocusRequest& request) { - return binder::Status::ok(); -} - -void TestInputManager::reset() { - mInputChannels.clear(); -} - -void InputFlingerServiceTest::SetUp() { - InputChannel::openInputChannelPair("testchannels", mServerChannel, mClientChannel); - InitializeInputFlinger(); -} - -void InputFlingerServiceTest::TearDown() { - mQuery->resetInputManager(); -} - -void InputFlingerServiceTest::InitializeInputFlinger() { - sp input(defaultServiceManager()->waitForService(kTestServiceName)); - ASSERT_TRUE(input != nullptr); - mService = interface_cast(input); - - input = defaultServiceManager()->waitForService(kQueryServiceName); - ASSERT_TRUE(input != nullptr); - mQuery = interface_cast(input); -} - -/** - * Test InputFlinger service interface createInputChannel - */ -TEST_F(InputFlingerServiceTest, CreateInputChannelReturnsUnblockedFd) { - // Test that the unblocked file descriptor flag is kept across processes over binder - // transactions. - - InputChannel channel; - ASSERT_TRUE(mService->createInputChannel("testchannels", &channel).isOk()); - - const base::unique_fd& fd = channel.getFd(); - ASSERT_TRUE(fd.ok()); - - const int result = fcntl(fd, F_GETFL); - EXPECT_NE(result, -1); - EXPECT_EQ(result & O_NONBLOCK, O_NONBLOCK); -} - -TEST_F(InputFlingerServiceTest, CreateInputChannel) { - InputChannel channel; - ASSERT_TRUE(mService->createInputChannel("testchannels", &channel).isOk()); - - std::vector<::android::InputChannel> channels; - mQuery->getInputChannels(&channels); - ASSERT_EQ(channels.size(), 1UL); - EXPECT_EQ(channels[0].getConnectionToken(), channel.getConnectionToken()); - - mService->removeInputChannel(channel.getConnectionToken()); - mQuery->getInputChannels(&channels); - EXPECT_EQ(channels.size(), 0UL); -} - -} // namespace android - -int main(int argc, char** argv) { - pid_t forkPid = fork(); - - if (forkPid == 0) { - // Server process - android::sp manager = - android::sp::make(); - android::sp query = - android::sp::make(manager); - - android::defaultServiceManager()->addService(android::kTestServiceName, manager, - false /*allowIsolated*/); - android::defaultServiceManager()->addService(android::kQueryServiceName, query, - false /*allowIsolated*/); - android::ProcessState::self()->startThreadPool(); - android::IPCThreadState::self()->joinThreadPool(); - } else { - android::ProcessState::self()->startThreadPool(); - ::testing::InitGoogleTest(&argc, argv); - int result = RUN_ALL_TESTS(); - kill(forkPid, SIGKILL); - return result; - } - return 0; -} -- GitLab From 69b58e83f320fc39955d2e1a64bb6ad98059ae3d Mon Sep 17 00:00:00 2001 From: ramindani Date: Mon, 26 Sep 2022 16:48:36 -0700 Subject: [PATCH 0349/1310] [MD] Single refresh rate selection Selects the single refresh rate for all the displays. BUG: 240743471 Test: atest libsurfaceflinger_unittest Change-Id: Ifa1bf23bc991fe60e67dba1cb31077e42fd5396e --- .../surfaceflinger/Scheduler/Scheduler.cpp | 139 ++++++++++++++++-- services/surfaceflinger/Scheduler/Scheduler.h | 55 ++++++- .../Scheduler/include/scheduler/Fps.h | 4 + services/surfaceflinger/SurfaceFlinger.cpp | 41 ++++-- services/surfaceflinger/SurfaceFlinger.h | 4 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 2 +- .../tests/unittests/FakeDisplayInjector.h | 90 ++++++++++++ .../tests/unittests/SchedulerTest.cpp | 131 +++++++++++++++-- .../SurfaceFlinger_NotifyPowerBoostTest.cpp | 3 + .../tests/unittests/TestableScheduler.h | 18 +++ .../tests/unittests/TestableSurfaceFlinger.h | 3 + .../unittests/mock/MockSchedulerCallback.h | 4 +- 12 files changed, 451 insertions(+), 43 deletions(-) create mode 100644 services/surfaceflinger/tests/unittests/FakeDisplayInjector.h diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 6d68bac8e2..7f04a4de85 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -57,6 +57,39 @@ } \ } while (false) +namespace { + +using android::Fps; +using android::FpsApproxEqual; +using android::FpsHash; +using android::scheduler::AggregatedFpsScore; +using android::scheduler::RefreshRateRankingsAndSignals; + +// Returns the aggregated score per Fps for the RefreshRateRankingsAndSignals sourced. +auto getAggregatedScoresPerFps( + const std::vector& refreshRateRankingsAndSignalsPerDisplay) + -> std::unordered_map { + std::unordered_map aggregatedScoresPerFps; + + for (const auto& refreshRateRankingsAndSignal : refreshRateRankingsAndSignalsPerDisplay) { + const auto& refreshRateRankings = refreshRateRankingsAndSignal.refreshRateRankings; + + std::for_each(refreshRateRankings.begin(), refreshRateRankings.end(), [&](const auto& it) { + const auto [score, result] = + aggregatedScoresPerFps.try_emplace(it.displayModePtr->getFps(), + AggregatedFpsScore{it.score, + /* numDisplays */ 1}); + if (!result) { // update + score->second.totalScore += it.score; + score->second.numDisplays++; + } + }); + } + return aggregatedScoresPerFps; +} + +} // namespace + namespace android::scheduler { Scheduler::Scheduler(ICompositor& compositor, ISchedulerCallback& callback, FeatureFlags features) @@ -662,6 +695,7 @@ template auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals { DisplayModePtr newMode; GlobalSignals consideredSignals; + std::vector displayModeConfigs; bool refreshRateChanged = false; bool frameRateOverridesChanged; @@ -674,9 +708,27 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals if (currentState == newState) return {}; currentState = std::forward(newState); - const auto [rankings, signals] = getRankedDisplayModes(); - newMode = rankings.front().displayModePtr; - consideredSignals = signals; + displayModeConfigs = getBestDisplayModeConfigs(); + + // mPolicy holds the current mode, using the current mode we find out + // what display is currently being tracked through the policy and + // then find the DisplayModeConfig for that display. So that + // later we check if the policy mode has changed for the same display in policy. + // If mPolicy mode isn't available then we take the first display from the best display + // modes as the candidate for policy changes and frame rate overrides. + // TODO(b/240743786) Update the single display based assumptions and make mode changes + // and mPolicy per display. + const DisplayModeConfig& displayModeConfigForCurrentPolicy = mPolicy.mode + ? *std::find_if(displayModeConfigs.begin(), displayModeConfigs.end(), + [&](const auto& displayModeConfig) REQUIRES(mPolicyLock) { + return displayModeConfig.displayModePtr + ->getPhysicalDisplayId() == + mPolicy.mode->getPhysicalDisplayId(); + }) + : displayModeConfigs.front(); + + newMode = displayModeConfigForCurrentPolicy.displayModePtr; + consideredSignals = displayModeConfigForCurrentPolicy.signals; frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, newMode->getFps()); if (mPolicy.mode == newMode) { @@ -691,9 +743,7 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals } } if (refreshRateChanged) { - mSchedulerCallback.requestDisplayMode(std::move(newMode), - consideredSignals.idle ? DisplayModeEvent::None - : DisplayModeEvent::Changed); + mSchedulerCallback.requestDisplayModes(std::move(displayModeConfigs)); } if (frameRateOverridesChanged) { mSchedulerCallback.triggerOnFrameRateOverridesChanged(); @@ -701,12 +751,68 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals return consideredSignals; } -auto Scheduler::getRankedDisplayModes() - -> std::pair, GlobalSignals> { - ATRACE_CALL(); +void Scheduler::registerDisplay(const sp& display) { + const bool ok = mDisplays.try_emplace(display->getPhysicalId(), display).second; + ALOGE_IF(!ok, "Duplicate display registered"); +} - const auto configs = holdRefreshRateConfigs(); +void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { + mDisplays.erase(displayId); +} +std::vector Scheduler::getBestDisplayModeConfigs() const { + ATRACE_CALL(); + + std::vector refreshRateRankingsAndSignalsPerDisplay; + refreshRateRankingsAndSignalsPerDisplay.reserve(mDisplays.size()); + + const auto displayModeSelectionParams = getDisplayModeSelectionParams(); + + std::for_each(mDisplays.begin(), mDisplays.end(), [&](const auto& display) { + const auto& [refreshRateRankings, globalSignals] = + display.second->holdRefreshRateConfigs() + ->getRankedRefreshRates(displayModeSelectionParams.layerRequirements, + displayModeSelectionParams.globalSignals); + refreshRateRankingsAndSignalsPerDisplay.emplace_back( + RefreshRateRankingsAndSignals{refreshRateRankings, globalSignals}); + }); + + // FPS and their Aggregated score. + std::unordered_map aggregatedScoresPerFps = + getAggregatedScoresPerFps(refreshRateRankingsAndSignalsPerDisplay); + + Fps chosenFps = std::max_element(aggregatedScoresPerFps.begin(), aggregatedScoresPerFps.end(), + [](const auto& max, const auto& current) { + return max.second.totalScore <= current.second.totalScore; + }) + ->first; + + return getDisplayModeConfigsForTheChosenFps(chosenFps, refreshRateRankingsAndSignalsPerDisplay); +} + +std::vector Scheduler::getDisplayModeConfigsForTheChosenFps( + Fps chosenFps, + const std::vector& refreshRateRankingsAndSignalsPerDisplay) + const { + std::vector displayModeConfigs; + displayModeConfigs.reserve(mDisplays.size()); + using fps_approx_ops::operator==; + std::for_each(refreshRateRankingsAndSignalsPerDisplay.begin(), + refreshRateRankingsAndSignalsPerDisplay.end(), + [&](const auto& refreshRateRankingsAndSignal) { + for (const auto& ranking : refreshRateRankingsAndSignal.refreshRateRankings) { + if (ranking.displayModePtr->getFps() == chosenFps) { + displayModeConfigs.emplace_back( + DisplayModeConfig{refreshRateRankingsAndSignal.globalSignals, + ranking.displayModePtr}); + break; + } + } + }); + return displayModeConfigs; +} + +DisplayModeSelectionParams Scheduler::getDisplayModeSelectionParams() const { const bool powerOnImminent = mDisplayPowerTimer && (mPolicy.displayPowerMode != hal::PowerMode::ON || mPolicy.displayPowerTimer == TimerState::Reset); @@ -715,7 +821,18 @@ auto Scheduler::getRankedDisplayModes() .idle = mPolicy.idleTimer == TimerState::Expired, .powerOnImminent = powerOnImminent}; - return configs->getRankedRefreshRates(mPolicy.contentRequirements, signals); + return {mPolicy.contentRequirements, signals}; +} + +auto Scheduler::getRankedDisplayModes() + -> std::pair, GlobalSignals> { + ATRACE_CALL(); + + const auto configs = holdRefreshRateConfigs(); + + const auto displayModeSelectionParams = getDisplayModeSelectionParams(); + return configs->getRankedRefreshRates(displayModeSelectionParams.layerRequirements, + displayModeSelectionParams.globalSignals); } DisplayModePtr Scheduler::getPreferredDisplayMode() { diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index f567205631..d6f62ca898 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -24,6 +24,7 @@ #include #include #include +#include // TODO(b/129481165): remove the #pragma below and fix conversion issues #pragma clang diagnostic push @@ -32,9 +33,11 @@ #include #pragma clang diagnostic pop // ignored "-Wconversion -Wextra" +#include #include #include +#include "Display/DisplayMap.h" #include "EventThread.h" #include "FrameRateOverrideMappings.h" #include "LayerHistory.h" @@ -83,11 +86,22 @@ class TokenManager; namespace scheduler { +using GlobalSignals = RefreshRateConfigs::GlobalSignals; + +// Config representing the DisplayMode and considered signals for the Display. +struct DisplayModeConfig { + const GlobalSignals signals; + const DisplayModePtr displayModePtr; + + DisplayModeConfig(GlobalSignals signals, DisplayModePtr displayModePtr) + : signals(signals), displayModePtr(std::move(displayModePtr)) {} +}; + struct ISchedulerCallback { using DisplayModeEvent = scheduler::DisplayModeEvent; virtual void setVsyncEnabled(bool) = 0; - virtual void requestDisplayMode(DisplayModePtr, DisplayModeEvent) = 0; + virtual void requestDisplayModes(std::vector) = 0; virtual void kernelTimerChanged(bool expired) = 0; virtual void triggerOnFrameRateOverridesChanged() = 0; @@ -95,6 +109,25 @@ protected: ~ISchedulerCallback() = default; }; +// Holds the total score of the FPS and +// number of displays the FPS is found in. +struct AggregatedFpsScore { + float totalScore; + size_t numDisplays; +}; + +// Represents LayerRequirements and GlobalSignals to be considered for the display mode selection. +struct DisplayModeSelectionParams { + std::vector layerRequirements; + GlobalSignals globalSignals; +}; + +// Represents the RefreshRateRankings and GlobalSignals for the selected RefreshRateRankings. +struct RefreshRateRankingsAndSignals { + std::vector refreshRateRankings; + GlobalSignals globalSignals; +}; + class Scheduler : android::impl::MessageQueue { using Impl = android::impl::MessageQueue; @@ -237,6 +270,9 @@ public: return mLayerHistory.getLayerFramerate(now, id); } + void registerDisplay(const sp&); + void unregisterDisplay(PhysicalDisplayId); + private: friend class TestableScheduler; @@ -260,8 +296,6 @@ private: void setVsyncPeriod(nsecs_t period); - using GlobalSignals = RefreshRateConfigs::GlobalSignals; - struct Policy; // Sets the S state of the policy to the T value under mPolicyLock, and chooses a display mode @@ -274,6 +308,17 @@ private: std::pair, GlobalSignals> getRankedDisplayModes() REQUIRES(mPolicyLock); + // Returns the best display mode per display. + std::vector getBestDisplayModeConfigs() const REQUIRES(mPolicyLock); + + // Returns the list of DisplayModeConfigs per display for the chosenFps. + std::vector getDisplayModeConfigsForTheChosenFps( + Fps chosenFps, const std::vector&) const; + + // Returns the DisplayModeSelectionParams to be considered for the + // DisplayMode selection based on the current Policy and GlobalSignals. + DisplayModeSelectionParams getDisplayModeSelectionParams() const REQUIRES(mPolicyLock); + bool updateFrameRateOverrides(GlobalSignals, Fps displayRefreshRate) REQUIRES(mPolicyLock); void dispatchCachedReportedMode() REQUIRES(mPolicyLock) EXCLUDES(mRefreshRateConfigsLock); @@ -323,6 +368,10 @@ private: mutable std::mutex mPolicyLock; + // Holds the Physical displays registered through the SurfaceFlinger, used for making + // the refresh rate selections. + display::PhysicalDisplayMap> mDisplays; + struct Policy { // Policy for choosing the display mode. LayerHistory::Summary contentRequirements; diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h index bd4f40989d..2c77142aa8 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h @@ -138,6 +138,10 @@ struct FpsApproxEqual { bool operator()(Fps lhs, Fps rhs) const { return isApproxEqual(lhs, rhs); } }; +struct FpsHash { + size_t operator()(Fps fps) const { return std::hash()(fps.getPeriodNsecs()); } +}; + inline std::string to_string(Fps fps) { return base::StringPrintf("%.2f Hz", fps.getValue()); } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 450594d0bb..2d29e4cf2b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2938,7 +2938,7 @@ void SurfaceFlinger::processDisplayAdded(const wp& displayToken, if (display->isPrimary()) { mScheduler->setRefreshRateConfigs(display->holdRefreshRateConfigs()); } - + mScheduler->registerDisplay(display); dispatchDisplayHotplugEvent(display->getPhysicalId(), true); } @@ -2954,6 +2954,7 @@ void SurfaceFlinger::processDisplayRemoved(const wp& displayToken) { releaseVirtualDisplay(display->getVirtualId()); } else { dispatchDisplayHotplugEvent(display->getPhysicalId(), false); + mScheduler->unregisterDisplay(display->getPhysicalId()); } } @@ -2990,6 +2991,8 @@ void SurfaceFlinger::processDisplayChanged(const wp& displayToken, display->disconnect(); if (display->isVirtual()) { releaseVirtualDisplay(display->getVirtualId()); + } else { + mScheduler->unregisterDisplay(display->getPhysicalId()); } } @@ -3319,25 +3322,34 @@ void SurfaceFlinger::updateCursorAsync() { mCompositionEngine->updateCursorAsync(refreshArgs); } -void SurfaceFlinger::requestDisplayMode(DisplayModePtr mode, DisplayModeEvent event) { +void SurfaceFlinger::requestDisplayModes( + std::vector displayModeConfigs) { + if (mBootStage != BootStage::FINISHED) { + ALOGV("Currently in the boot stage, skipping display mode changes"); + return; + } + + ATRACE_CALL(); // If this is called from the main thread mStateLock must be locked before // Currently the only way to call this function from the main thread is from // Scheduler::chooseRefreshRateForContent ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId); - const auto display = getDefaultDisplayDeviceLocked(); - if (!display || mBootStage != BootStage::FINISHED) { - return; - } - ATRACE_CALL(); - - if (!display->refreshRateConfigs().isModeAllowed(mode->getId())) { - ALOGV("Skipping disallowed mode %d", mode->getId().value()); - return; - } - - setDesiredActiveMode({std::move(mode), event}); + std::for_each(displayModeConfigs.begin(), displayModeConfigs.end(), + [&](const auto& config) REQUIRES(mStateLock) { + const auto& displayModePtr = config.displayModePtr; + if (const auto display = + getDisplayDeviceLocked(displayModePtr->getPhysicalDisplayId()); + display->refreshRateConfigs().isModeAllowed(displayModePtr->getId())) { + const auto event = config.signals.idle ? DisplayModeEvent::None + : DisplayModeEvent::Changed; + setDesiredActiveMode({displayModePtr, event}); + } else { + ALOGV("Skipping disallowed mode %d for display %" PRId64, + displayModePtr->getId().value(), display->getPhysicalId().value); + } + }); } void SurfaceFlinger::triggerOnFrameRateOverridesChanged() { @@ -3386,6 +3398,7 @@ void SurfaceFlinger::initScheduler(const sp& display) { mScheduler->createVsyncSchedule(features); mScheduler->setRefreshRateConfigs(std::move(configs)); + mScheduler->registerDisplay(display); } setVsyncEnabled(false); mScheduler->startTimers(); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 99d3736031..426e7886ec 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -631,8 +631,8 @@ private: // Toggles hardware VSYNC by calling into HWC. void setVsyncEnabled(bool) override; - // Sets the desired display mode if allowed by policy. - void requestDisplayMode(DisplayModePtr, DisplayModeEvent) override; + // Sets the desired display mode per display if allowed by policy . + void requestDisplayModes(std::vector) override; // Called when kernel idle timer has expired. Used to update the refresh rate overlay. void kernelTimerChanged(bool expired) override; // Called when the frame rate override list changed to trigger an event. diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 72b6cc9d88..0b21272a04 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -781,7 +781,7 @@ public: private: void setVsyncEnabled(bool) override {} - void requestDisplayMode(DisplayModePtr, DisplayModeEvent) override {} + void requestDisplayModes(std::vector) override {} void kernelTimerChanged(bool) override {} void triggerOnFrameRateOverridesChanged() override {} diff --git a/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h new file mode 100644 index 0000000000..81b420cbc4 --- /dev/null +++ b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h @@ -0,0 +1,90 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "TestableSurfaceFlinger.h" +#include "mock/DisplayHardware/MockPowerAdvisor.h" +#include "mock/system/window/MockNativeWindow.h" + +namespace android { + +using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector; +using android::hardware::graphics::composer::hal::HWDisplayId; +using android::Hwc2::mock::PowerAdvisor; +using testing::_; +using testing::AnyNumber; +using testing::DoAll; +using testing::Mock; +using testing::ResultOf; +using testing::Return; +using testing::SetArgPointee; + +class FakeDisplayInjector { +public: + sp injectDefaultInternalDisplay( + const std::function& injectExtra, + TestableSurfaceFlinger& flinger, uint8_t port = 255u) const { + constexpr int DEFAULT_DISPLAY_WIDTH = 1080; + constexpr int DEFAULT_DISPLAY_HEIGHT = 1920; + constexpr HWDisplayId DEFAULT_DISPLAY_HWC_DISPLAY_ID = 0; + + const PhysicalDisplayId physicalDisplayId = PhysicalDisplayId::fromPort(port); + + // The DisplayDevice is required to have a framebuffer (behind the + // ANativeWindow interface) which uses the actual hardware display + // size. + EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_WIDTH, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_WIDTH), Return(0))); + EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_HEIGHT, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_HEIGHT), Return(0))); + EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_BUFFERS_FORMAT)); + EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_CONNECT)); + EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_USAGE64)); + EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_DISCONNECT)).Times(AnyNumber()); + + auto compositionDisplay = compositionengine::impl:: + createDisplay(flinger.getCompositionEngine(), + compositionengine::DisplayCreationArgsBuilder() + .setId(physicalDisplayId) + .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT}) + .setPowerAdvisor(mPowerAdvisor) + .build()); + + constexpr bool kIsPrimary = true; + auto injector = FakeDisplayDeviceInjector(flinger, compositionDisplay, + ui::DisplayConnectionType::Internal, + DEFAULT_DISPLAY_HWC_DISPLAY_ID, kIsPrimary); + + injector.setNativeWindow(mNativeWindow); + if (injectExtra) { + injectExtra(injector); + } + + auto displayDevice = injector.inject(); + + Mock::VerifyAndClear(mNativeWindow.get()); + + return displayDevice; + } + + sp mNativeWindow = sp::make(); + PowerAdvisor* mPowerAdvisor = new PowerAdvisor(); +}; + +} // namespace android \ No newline at end of file diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 53e49eb0d4..8d2130fa0a 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -20,6 +20,7 @@ #include +#include "FakeDisplayInjector.h" #include "Scheduler/EventThread.h" #include "Scheduler/RefreshRateConfigs.h" #include "TestableScheduler.h" @@ -40,6 +41,7 @@ namespace { using MockEventThread = android::mock::EventThread; using MockLayer = android::mock::MockLayer; +using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector; constexpr PhysicalDisplayId PHYSICAL_DISPLAY_ID = PhysicalDisplayId::fromPort(255u); @@ -59,11 +61,13 @@ protected: SchedulerTest(); - static inline const DisplayModePtr kMode60 = createDisplayMode(DisplayModeId(0), 60_Hz); - static inline const DisplayModePtr kMode120 = createDisplayMode(DisplayModeId(1), 120_Hz); + static inline const DisplayModePtr kMode60_1 = createDisplayMode(DisplayModeId(0), 60_Hz); + static inline const DisplayModePtr kMode120_1 = createDisplayMode(DisplayModeId(1), 120_Hz); + static inline const DisplayModePtr kMode60_2 = createDisplayMode(DisplayModeId(2), 60_Hz); + static inline const DisplayModePtr kMode120_2 = createDisplayMode(DisplayModeId(3), 120_Hz); std::shared_ptr mConfigs = - std::make_shared(makeModes(kMode60), kMode60->getId()); + std::make_shared(makeModes(kMode60_1), kMode60_1->getId()); mock::SchedulerCallback mSchedulerCallback; TestableScheduler* mScheduler = new TestableScheduler{mConfigs, mSchedulerCallback}; @@ -71,6 +75,7 @@ protected: ConnectionHandle mConnectionHandle; MockEventThread* mEventThread; sp mEventThreadConnection; + FakeDisplayInjector mFakeDisplayInjector; TestableSurfaceFlinger mFlinger; }; @@ -166,7 +171,7 @@ TEST_F(SchedulerTest, chooseRefreshRateForContentIsNoopWhenModeSwitchingIsNotSup constexpr uint32_t kDisplayArea = 999'999; mScheduler->onActiveDisplayAreaChanged(kDisplayArea); - EXPECT_CALL(mSchedulerCallback, requestDisplayMode(_, _)).Times(0); + EXPECT_CALL(mSchedulerCallback, requestDisplayModes(_)).Times(0); mScheduler->chooseRefreshRateForContent(); } @@ -176,7 +181,8 @@ TEST_F(SchedulerTest, updateDisplayModes) { ASSERT_EQ(1u, mScheduler->layerHistorySize()); mScheduler->setRefreshRateConfigs( - std::make_shared(makeModes(kMode60, kMode120), kMode60->getId())); + std::make_shared(makeModes(kMode60_1, kMode120_1), + kMode60_1->getId())); ASSERT_EQ(0u, mScheduler->getNumActiveLayers()); mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer); @@ -215,12 +221,18 @@ TEST_F(SchedulerTest, calculateMaxAcquiredBufferCount) { } MATCHER(Is120Hz, "") { - return isApproxEqual(arg->getFps(), 120_Hz); + return isApproxEqual(arg.front().displayModePtr->getFps(), 120_Hz); } TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) { - mScheduler->setRefreshRateConfigs( - std::make_shared(makeModes(kMode60, kMode120), kMode60->getId())); + auto display = mFakeDisplayInjector.injectDefaultInternalDisplay( + [&](FakeDisplayDeviceInjector& injector) { + injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId()); + }, + mFlinger); + + mScheduler->registerDisplay(display); + mScheduler->setRefreshRateConfigs(display->holdRefreshRateConfigs()); const sp layer = sp::make(mFlinger.flinger()); EXPECT_CALL(*layer, isVisible()).WillOnce(Return(true)); @@ -233,12 +245,111 @@ TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) { constexpr uint32_t kDisplayArea = 999'999; mScheduler->onActiveDisplayAreaChanged(kDisplayArea); - EXPECT_CALL(mSchedulerCallback, requestDisplayMode(Is120Hz(), _)).Times(1); + EXPECT_CALL(mSchedulerCallback, requestDisplayModes(Is120Hz())).Times(1); mScheduler->chooseRefreshRateForContent(); // No-op if layer requirements have not changed. - EXPECT_CALL(mSchedulerCallback, requestDisplayMode(_, _)).Times(0); + EXPECT_CALL(mSchedulerCallback, requestDisplayModes(_)).Times(0); mScheduler->chooseRefreshRateForContent(); } +TEST_F(SchedulerTest, getBestDisplayMode_singleDisplay) { + auto display = mFakeDisplayInjector.injectDefaultInternalDisplay( + [&](FakeDisplayDeviceInjector& injector) { + injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId()); + }, + mFlinger); + mScheduler->registerDisplay(display); + + std::vector layers = + std::vector({{.weight = 1.f}, {.weight = 1.f}}); + mScheduler->setContentRequirements(layers); + GlobalSignals globalSignals = {.idle = true}; + mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); + + std::vector displayModeConfigs = mScheduler->getBestDisplayModeConfigs(); + ASSERT_EQ(1ul, displayModeConfigs.size()); + EXPECT_EQ(displayModeConfigs.front().displayModePtr, kMode60_1); + EXPECT_EQ(displayModeConfigs.front().signals, globalSignals); + + globalSignals = {.idle = false}; + mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); + displayModeConfigs = mScheduler->getBestDisplayModeConfigs(); + ASSERT_EQ(1ul, displayModeConfigs.size()); + EXPECT_EQ(displayModeConfigs.front().displayModePtr, kMode120_1); + EXPECT_EQ(displayModeConfigs.front().signals, globalSignals); + + globalSignals = {.touch = true}; + mScheduler->replaceTouchTimer(10); + mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); + displayModeConfigs = mScheduler->getBestDisplayModeConfigs(); + ASSERT_EQ(1ul, displayModeConfigs.size()); + EXPECT_EQ(displayModeConfigs.front().displayModePtr, kMode120_1); + EXPECT_EQ(displayModeConfigs.front().signals, globalSignals); + + mScheduler->unregisterDisplay(display->getPhysicalId()); + EXPECT_TRUE(mScheduler->mutableDisplays().empty()); +} + +TEST_F(SchedulerTest, getBestDisplayModes_multipleDisplays) { + auto display1 = mFakeDisplayInjector.injectDefaultInternalDisplay( + [&](FakeDisplayDeviceInjector& injector) { + injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId()); + }, + mFlinger); + auto display2 = mFakeDisplayInjector.injectDefaultInternalDisplay( + [&](FakeDisplayDeviceInjector& injector) { + injector.setDisplayModes(makeModes(kMode60_2, kMode120_2), kMode60_2->getId()); + }, + mFlinger, /* port */ 253u); + mScheduler->registerDisplay(display1); + mScheduler->registerDisplay(display2); + + const std::vector>& expectedDisplays = {display1, display2}; + std::vector layers = {{.weight = 1.f}, {.weight = 1.f}}; + GlobalSignals globalSignals = {.idle = true}; + std::vector expectedConfigs = {DisplayModeConfig{globalSignals, kMode60_1}, + DisplayModeConfig{globalSignals, kMode60_2}}; + + mScheduler->setContentRequirements(layers); + mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); + std::vector displayModeConfigs = mScheduler->getBestDisplayModeConfigs(); + ASSERT_EQ(displayModeConfigs.size(), expectedConfigs.size()); + for (size_t i = 0; i < expectedConfigs.size(); ++i) { + EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr) + << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue() + << " Actual fps " + << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue(); + EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals); + } + + expectedConfigs = std::vector{DisplayModeConfig{globalSignals, kMode120_1}, + DisplayModeConfig{globalSignals, kMode120_2}}; + + globalSignals = {.idle = false}; + mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); + displayModeConfigs = mScheduler->getBestDisplayModeConfigs(); + ASSERT_EQ(expectedConfigs.size(), displayModeConfigs.size()); + for (size_t i = 0; i < expectedConfigs.size(); ++i) { + EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr) + << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue() + << " Actual fps " + << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue(); + EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals); + } + + globalSignals = {.touch = true}; + mScheduler->replaceTouchTimer(10); + mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); + displayModeConfigs = mScheduler->getBestDisplayModeConfigs(); + ASSERT_EQ(expectedConfigs.size(), displayModeConfigs.size()); + for (size_t i = 0; i < expectedConfigs.size(); ++i) { + EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr) + << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue() + << " Actual fps " + << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue(); + EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals); + } +} + } // namespace android::scheduler diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp index ec7e8a7f82..4e9f293dc3 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp @@ -21,6 +21,7 @@ #include #include "DisplayTransactionTestHelpers.h" +#include "FakeDisplayInjector.h" #include @@ -32,6 +33,8 @@ using android::hardware::power::Boost; TEST_F(DisplayTransactionTest, notifyPowerBoostNotifiesTouchEvent) { using namespace std::chrono_literals; + injectDefaultInternalDisplay([](FakeDisplayDeviceInjector&) {}); + mFlinger.scheduler()->replaceTouchTimer(100); std::this_thread::sleep_for(10ms); // wait for callback to be triggered EXPECT_TRUE(mFlinger.scheduler()->isTouchActive()); // Starting timer activates touch diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index 93e305960c..68df987689 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -68,6 +68,8 @@ public: auto& mutableLayerHistory() { return mLayerHistory; } + auto& mutableDisplays() { return mDisplays; } + size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS { return mLayerHistory.mActiveLayerInfos.size() + mLayerHistory.mInactiveLayerInfos.size(); } @@ -94,6 +96,22 @@ public: return mPolicy.touch == Scheduler::TouchState::Active; } + void setTouchStateAndIdleTimerPolicy(GlobalSignals globalSignals) { + std::lock_guard lock(mPolicyLock); + mPolicy.touch = globalSignals.touch ? TouchState::Active : TouchState::Inactive; + mPolicy.idleTimer = globalSignals.idle ? TimerState::Expired : TimerState::Reset; + } + + void setContentRequirements(std::vector layers) { + std::lock_guard lock(mPolicyLock); + mPolicy.contentRequirements = std::move(layers); + } + + std::vector getBestDisplayModeConfigs() { + std::lock_guard lock(mPolicyLock); + return Scheduler::getBestDisplayModeConfigs(); + } + void dispatchCachedReportedMode() { std::lock_guard lock(mPolicyLock); return Scheduler::dispatchCachedReportedMode(); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 5338f4acf6..f5042d301d 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -842,6 +842,9 @@ public: sp display = sp::make(mCreationArgs); mFlinger.mutableDisplays().emplace_or_replace(mDisplayToken, display); + if (mFlinger.scheduler()) { + mFlinger.scheduler()->registerDisplay(display); + } DisplayDeviceState state; state.isSecure = mCreationArgs.isSecure; diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h index 52675869fa..8af2dfa9cf 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h +++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h @@ -24,14 +24,14 @@ namespace android::scheduler::mock { struct SchedulerCallback final : ISchedulerCallback { MOCK_METHOD(void, setVsyncEnabled, (bool), (override)); - MOCK_METHOD(void, requestDisplayMode, (DisplayModePtr, DisplayModeEvent), (override)); + MOCK_METHOD(void, requestDisplayModes, (std::vector), (override)); MOCK_METHOD(void, kernelTimerChanged, (bool), (override)); MOCK_METHOD(void, triggerOnFrameRateOverridesChanged, (), (override)); }; struct NoOpSchedulerCallback final : ISchedulerCallback { void setVsyncEnabled(bool) override {} - void requestDisplayMode(DisplayModePtr, DisplayModeEvent) override {} + void requestDisplayModes(std::vector) override {} void kernelTimerChanged(bool) override {} void triggerOnFrameRateOverridesChanged() override {} }; -- GitLab From eee671eb373e06c9ddd9920921fa0c4b60cfff0a Mon Sep 17 00:00:00 2001 From: Yeabkal Wubshit Date: Thu, 6 Oct 2022 15:13:34 -0700 Subject: [PATCH 0350/1310] Add MAXIMUM_VALID_AMOTION_EVENT_AXIS_VALUE This helps to standardize checks for the max possible motion event axis value. Bug: 32830165 Test: static assert added Change-Id: I664ee86b065745720a1af4d07c577eb088525d3d --- include/android/input.h | 9 +++++++++ services/inputflinger/InputCommonConverter.cpp | 2 ++ .../inputflinger/reader/mapper/JoystickInputMapper.cpp | 4 ++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/include/android/input.h b/include/android/input.h index 7080386df7..d906af6e0c 100644 --- a/include/android/input.h +++ b/include/android/input.h @@ -772,8 +772,17 @@ enum { */ AMOTION_EVENT_AXIS_GENERIC_16 = 47, + /** + * Note: This is not an "Axis constant". It does not represent any axis, nor should it be used + * to represent any axis. It is a constant holding the value of the largest defined axis value, + * to make some computations (like iterating through all possible axes) cleaner. + * Please update the value accordingly if you add a new axis. + */ + AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE = AMOTION_EVENT_AXIS_GENERIC_16, + // 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. + // Update AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE accordingly as well. }; /** diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp index 8aee39fd0b..23b6f57b23 100644 --- a/services/inputflinger/InputCommonConverter.cpp +++ b/services/inputflinger/InputCommonConverter.cpp @@ -263,6 +263,8 @@ static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_13) == common static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_14) == common::Axis::GENERIC_14); static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_15) == common::Axis::GENERIC_15); static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_16) == common::Axis::GENERIC_16); +static_assert(static_cast(AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE) == + static_cast(AMOTION_EVENT_AXIS_GENERIC_16)); static common::VideoFrame getHalVideoFrame(const TouchVideoFrame& frame) { common::VideoFrame out; diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp index 7d30d0c1eb..17f139fb4d 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp @@ -145,12 +145,12 @@ void JoystickInputMapper::configure(nsecs_t when, const InputReaderConfiguration for (auto it = mAxes.begin(); it != mAxes.end(); /*increment it inside loop*/) { Axis& axis = it->second; if (axis.axisInfo.axis < 0) { - while (nextGenericAxisId <= AMOTION_EVENT_AXIS_GENERIC_16 && + while (nextGenericAxisId <= AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE && haveAxis(nextGenericAxisId)) { nextGenericAxisId += 1; } - if (nextGenericAxisId <= AMOTION_EVENT_AXIS_GENERIC_16) { + if (nextGenericAxisId <= AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE) { axis.axisInfo.axis = nextGenericAxisId; nextGenericAxisId += 1; } else { -- GitLab From ab8ffef9fe8845e72d19bd728da5266398cba002 Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Thu, 18 Aug 2022 13:02:26 -0700 Subject: [PATCH 0351/1310] Remove SurfaceInterceptor and surfacereplayer Both are no longer used and thus obsolete. Ignore-AOSP-First: depends on other changes on master Bug: 241285477 Test: atest libgui_test libsurfaceflinger_unittest SurfaceFlinger_test Change-Id: I1858826b9eca27b355edb4236510e3aad1ddb399 --- cmds/surfacereplayer/Android.bp | 13 - cmds/surfacereplayer/OWNERS | 1 - cmds/surfacereplayer/proto/Android.bp | 23 - cmds/surfacereplayer/proto/src/trace.proto | 225 ---- cmds/surfacereplayer/replayer/Android.bp | 71 -- .../replayer/BufferQueueScheduler.cpp | 109 -- .../replayer/BufferQueueScheduler.h | 82 -- cmds/surfacereplayer/replayer/Color.h | 124 --- cmds/surfacereplayer/replayer/Event.cpp | 51 - cmds/surfacereplayer/replayer/Event.h | 57 -- cmds/surfacereplayer/replayer/Main.cpp | 116 --- cmds/surfacereplayer/replayer/README.md | 262 ----- cmds/surfacereplayer/replayer/Replayer.h | 170 ---- .../replayer/trace_creator/trace_creator.py | 285 ------ libs/gui/Android.bp | 1 - libs/gui/ISurfaceComposer.cpp | 1 - libs/gui/TransactionTracing.cpp | 53 - .../aidl/android/gui/ISurfaceComposer.aidl | 6 - .../gui/ITransactionTraceListener.aidl | 6 - libs/gui/fuzzer/libgui_fuzzer_utils.h | 2 - libs/gui/include/gui/ISurfaceComposer.h | 1 - libs/gui/include/gui/TransactionTracing.h | 41 - libs/gui/tests/Surface_test.cpp | 5 - services/surfaceflinger/Android.bp | 3 - .../CompositionEngine/Android.bp | 1 - services/surfaceflinger/Layer.h | 6 - .../surfaceflinger/Scheduler/EventThread.cpp | 25 +- .../surfaceflinger/Scheduler/EventThread.h | 6 +- .../surfaceflinger/Scheduler/Scheduler.cpp | 10 +- services/surfaceflinger/Scheduler/Scheduler.h | 3 +- services/surfaceflinger/SurfaceFlinger.cpp | 61 +- services/surfaceflinger/SurfaceFlinger.h | 5 - .../SurfaceFlingerDefaultFactory.cpp | 5 - .../SurfaceFlingerDefaultFactory.h | 1 - .../surfaceflinger/SurfaceFlingerFactory.h | 2 - .../surfaceflinger/SurfaceInterceptor.cpp | 711 ------------- services/surfaceflinger/SurfaceInterceptor.h | 211 ---- .../Tracing/tools/LayerTraceGenerator.cpp | 4 - .../fuzzer/surfaceflinger_fuzzers_utils.h | 9 - .../surfaceflinger_scheduler_fuzzer.cpp | 2 +- services/surfaceflinger/tests/Android.bp | 2 - .../tests/SurfaceInterceptor_test.cpp | 961 ------------------ .../surfaceflinger/tests/fakehwc/Android.bp | 1 - .../surfaceflinger/tests/unittests/Android.bp | 2 - .../unittests/DisplayTransactionTest.cpp | 1 - .../unittests/DisplayTransactionTestHelpers.h | 2 - .../tests/unittests/EventThreadTest.cpp | 69 +- .../SurfaceFlinger_CreateDisplayTest.cpp | 6 - .../SurfaceFlinger_DestroyDisplayTest.cpp | 3 - ...ceFlinger_DisplayTransactionCommitTest.cpp | 3 - ...urfaceFlinger_OnInitializeDisplaysTest.cpp | 5 - ...urfaceFlinger_SetPowerModeInternalTest.cpp | 8 - .../tests/unittests/TestableSurfaceFlinger.h | 7 - .../unittests/mock/MockSurfaceInterceptor.cpp | 32 - .../unittests/mock/MockSurfaceInterceptor.h | 50 - 55 files changed, 37 insertions(+), 3885 deletions(-) delete mode 100644 cmds/surfacereplayer/Android.bp delete mode 100644 cmds/surfacereplayer/OWNERS delete mode 100644 cmds/surfacereplayer/proto/Android.bp delete mode 100644 cmds/surfacereplayer/proto/src/trace.proto delete mode 100644 cmds/surfacereplayer/replayer/Android.bp delete mode 100644 cmds/surfacereplayer/replayer/BufferQueueScheduler.cpp delete mode 100644 cmds/surfacereplayer/replayer/BufferQueueScheduler.h delete mode 100644 cmds/surfacereplayer/replayer/Color.h delete mode 100644 cmds/surfacereplayer/replayer/Event.cpp delete mode 100644 cmds/surfacereplayer/replayer/Event.h delete mode 100644 cmds/surfacereplayer/replayer/Main.cpp delete mode 100644 cmds/surfacereplayer/replayer/README.md delete mode 100644 cmds/surfacereplayer/replayer/Replayer.h delete mode 100644 cmds/surfacereplayer/replayer/trace_creator/trace_creator.py delete mode 100644 libs/gui/TransactionTracing.cpp delete mode 100644 libs/gui/aidl/android/gui/ITransactionTraceListener.aidl delete mode 100644 libs/gui/include/gui/TransactionTracing.h delete mode 100644 services/surfaceflinger/SurfaceInterceptor.cpp delete mode 100644 services/surfaceflinger/SurfaceInterceptor.h delete mode 100644 services/surfaceflinger/tests/SurfaceInterceptor_test.cpp delete mode 100644 services/surfaceflinger/tests/unittests/mock/MockSurfaceInterceptor.cpp delete mode 100644 services/surfaceflinger/tests/unittests/mock/MockSurfaceInterceptor.h diff --git a/cmds/surfacereplayer/Android.bp b/cmds/surfacereplayer/Android.bp deleted file mode 100644 index 34fc8b10ea..0000000000 --- a/cmds/surfacereplayer/Android.bp +++ /dev/null @@ -1,13 +0,0 @@ -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"], -} - -subdirs = [ - "proto", - "replayer", -] diff --git a/cmds/surfacereplayer/OWNERS b/cmds/surfacereplayer/OWNERS deleted file mode 100644 index 32bcc83468..0000000000 --- a/cmds/surfacereplayer/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include platform/frameworks/native:/services/surfaceflinger/OWNERS diff --git a/cmds/surfacereplayer/proto/Android.bp b/cmds/surfacereplayer/proto/Android.bp deleted file mode 100644 index 23b54ee5b0..0000000000 --- a/cmds/surfacereplayer/proto/Android.bp +++ /dev/null @@ -1,23 +0,0 @@ -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_library_static { - name: "libtrace_proto", - srcs: [ - "src/trace.proto", - ], - cflags: [ - "-Wall", - "-Werror", - ], - proto: { - type: "lite", - export_proto_headers: true, - }, -} diff --git a/cmds/surfacereplayer/proto/src/trace.proto b/cmds/surfacereplayer/proto/src/trace.proto deleted file mode 100644 index a177027e5c..0000000000 --- a/cmds/surfacereplayer/proto/src/trace.proto +++ /dev/null @@ -1,225 +0,0 @@ -syntax = "proto2"; -option optimize_for = LITE_RUNTIME; -package android.surfaceflinger; - -message Trace { - repeated Increment increment = 1; -} - -message Increment { - required int64 time_stamp = 1; - - oneof increment { - Transaction transaction = 2; - SurfaceCreation surface_creation = 3; - SurfaceDeletion surface_deletion = 4; - BufferUpdate buffer_update = 5; - VSyncEvent vsync_event = 6; - DisplayCreation display_creation = 7; - DisplayDeletion display_deletion = 8; - PowerModeUpdate power_mode_update = 9; - } -} - -message Transaction { - repeated SurfaceChange surface_change = 1; - repeated DisplayChange display_change = 2; - - required bool synchronous = 3; - required bool animation = 4; - optional Origin origin = 5; - optional uint64 id = 6; -} - -message SurfaceChange { - required int32 id = 1; - reserved 7; - oneof SurfaceChange { - PositionChange position = 2; - SizeChange size = 3; - AlphaChange alpha = 4; - LayerChange layer = 5; - CropChange crop = 6; - MatrixChange matrix = 8; - TransparentRegionHintChange transparent_region_hint = 10; - LayerStackChange layer_stack = 11; - HiddenFlagChange hidden_flag = 12; - OpaqueFlagChange opaque_flag = 13; - SecureFlagChange secure_flag = 14; - CornerRadiusChange corner_radius = 16; - ReparentChange reparent = 17; - RelativeParentChange relative_parent = 18; - BackgroundBlurRadiusChange background_blur_radius = 20; - ShadowRadiusChange shadow_radius = 21; - BlurRegionsChange blur_regions = 22; - TrustedOverlayChange trusted_overlay = 23; - } -} - -message PositionChange { - required float x = 1; - required float y = 2; -} - -message SizeChange { - required uint32 w = 1; - required uint32 h = 2; -} - -message AlphaChange { - required float alpha = 1; -} - -message CornerRadiusChange { - required float corner_radius = 1; -} - -message BackgroundBlurRadiusChange { - required float background_blur_radius = 1; -} - -message LayerChange { - required uint32 layer = 1; -} - -message CropChange { - required Rectangle rectangle = 1; -} - -message MatrixChange { - required float dsdx = 1; - required float dtdx = 2; - required float dsdy = 3; - required float dtdy = 4; -} - -message TransparentRegionHintChange { - repeated Rectangle region = 1; -} - -message LayerStackChange { - required uint32 layer_stack = 1; -} - -message DisplayFlagsChange { - required uint32 flags = 1; -} - -message HiddenFlagChange { - required bool hidden_flag = 1; -} - -message OpaqueFlagChange { - required bool opaque_flag = 1; -} - -message SecureFlagChange { - required bool secure_flag = 1; -} - -message DisplayChange { - required int32 id = 1; - - oneof DisplayChange { - DispSurfaceChange surface = 2; - LayerStackChange layer_stack = 3; - SizeChange size = 4; - ProjectionChange projection = 5; - DisplayFlagsChange flags = 6; - } -} - -message DispSurfaceChange { - required uint64 buffer_queue_id = 1; - required string buffer_queue_name = 2; -} - -message ProjectionChange { - required int32 orientation = 1; - required Rectangle viewport = 2; - required Rectangle frame = 3; -} - -message Rectangle { - required int32 left = 1; - required int32 top = 2; - required int32 right = 3; - required int32 bottom = 4; -} - -message SurfaceCreation { - required int32 id = 1; - required string name = 2; - required uint32 w = 3; - required uint32 h = 4; -} - -message SurfaceDeletion { - required int32 id = 1; -} - -message BufferUpdate { - required int32 id = 1; - required uint32 w = 2; - required uint32 h = 3; - required uint64 frame_number = 4; -} - -message VSyncEvent { - required int64 when = 1; -} - -message DisplayCreation { - required int32 id = 1; - required string name = 2; - optional uint64 display_id = 3; - required bool is_secure = 4; -} - -message DisplayDeletion { - required int32 id = 1; -} - -message PowerModeUpdate { - required int32 id = 1; - required int32 mode = 2; -} - -message ReparentChange { - required int32 parent_id = 1; -} - -message RelativeParentChange { - required int32 relative_parent_id = 1; - required int32 z = 2; -} - -message ShadowRadiusChange { - required float radius = 1; -} - -message TrustedOverlayChange { - required float is_trusted_overlay = 1; -} - -message BlurRegionsChange { - repeated BlurRegionChange blur_regions = 1; -} - -message BlurRegionChange { - required uint32 blur_radius = 1; - required float corner_radius_tl = 2; - required float corner_radius_tr = 3; - required float corner_radius_bl = 4; - required float corner_radius_br = 5; - required float alpha = 6; - required int32 left = 7; - required int32 top = 8; - required int32 right = 9; - required int32 bottom = 10; -} - -message Origin { - required int32 pid = 1; - required int32 uid = 2; -} diff --git a/cmds/surfacereplayer/replayer/Android.bp b/cmds/surfacereplayer/replayer/Android.bp deleted file mode 100644 index 3985230f08..0000000000 --- a/cmds/surfacereplayer/replayer/Android.bp +++ /dev/null @@ -1,71 +0,0 @@ -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_library_shared { - name: "libsurfacereplayer", - srcs: [ - "BufferQueueScheduler.cpp", - "Event.cpp", - "Replayer.cpp", - ], - cppflags: [ - "-Werror", - "-Wno-unused-parameter", - "-Wno-format", - "-Wno-c++98-compat-pedantic", - "-Wno-float-conversion", - "-Wno-disabled-macro-expansion", - "-Wno-float-equal", - "-Wno-sign-conversion", - "-Wno-padded", - ], - static_libs: [ - "libtrace_proto", - ], - shared_libs: [ - "libEGL", - "libGLESv2", - "libbinder", - "liblog", - "libcutils", - "libgui", - "libui", - "libutils", - "libprotobuf-cpp-lite", - "libbase", - "libnativewindow", - ], - export_include_dirs: [ - ".", - ], -} - -cc_binary { - name: "surfacereplayer", - srcs: [ - "Main.cpp", - ], - shared_libs: [ - "libprotobuf-cpp-lite", - "libsurfacereplayer", - "libutils", - "libgui", - ], - static_libs: [ - "libtrace_proto", - ], - cppflags: [ - "-Werror", - "-Wno-unused-parameter", - "-Wno-c++98-compat-pedantic", - "-Wno-float-conversion", - "-Wno-disabled-macro-expansion", - "-Wno-float-equal", - ], -} diff --git a/cmds/surfacereplayer/replayer/BufferQueueScheduler.cpp b/cmds/surfacereplayer/replayer/BufferQueueScheduler.cpp deleted file mode 100644 index 77de8dc44c..0000000000 --- a/cmds/surfacereplayer/replayer/BufferQueueScheduler.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2016 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 "BufferQueueScheduler" - -#include "BufferQueueScheduler.h" - -#include -#include - -using namespace android; - -BufferQueueScheduler::BufferQueueScheduler( - const sp& surfaceControl, const HSV& color, int id) - : mSurfaceControl(surfaceControl), mColor(color), mSurfaceId(id), mContinueScheduling(true) {} - -void BufferQueueScheduler::startScheduling() { - ALOGV("Starting Scheduler for %d Layer", mSurfaceId); - std::unique_lock lock(mMutex); - if (mSurfaceControl == nullptr) { - mCondition.wait(lock, [&] { return (mSurfaceControl != nullptr); }); - } - - while (mContinueScheduling) { - while (true) { - if (mBufferEvents.empty()) { - break; - } - - BufferEvent event = mBufferEvents.front(); - lock.unlock(); - - bufferUpdate(event.dimensions); - fillSurface(event.event); - mColor.modulate(); - lock.lock(); - mBufferEvents.pop(); - } - mCondition.wait(lock); - } -} - -void BufferQueueScheduler::addEvent(const BufferEvent& event) { - std::lock_guard lock(mMutex); - mBufferEvents.push(event); - mCondition.notify_one(); -} - -void BufferQueueScheduler::stopScheduling() { - std::lock_guard lock(mMutex); - mContinueScheduling = false; - mCondition.notify_one(); -} - -void BufferQueueScheduler::setSurfaceControl( - const sp& surfaceControl, const HSV& color) { - std::lock_guard lock(mMutex); - mSurfaceControl = surfaceControl; - mColor = color; - mCondition.notify_one(); -} - -void BufferQueueScheduler::bufferUpdate(const Dimensions& dimensions) { - sp s = mSurfaceControl->getSurface(); - s->setBuffersDimensions(dimensions.width, dimensions.height); -} - -void BufferQueueScheduler::fillSurface(const std::shared_ptr& event) { - ANativeWindow_Buffer outBuffer; - sp s = mSurfaceControl->getSurface(); - - status_t status = s->lock(&outBuffer, nullptr); - - if (status != NO_ERROR) { - ALOGE("fillSurface: failed to lock buffer, (%d)", status); - return; - } - - auto color = mColor.getRGB(); - - auto img = reinterpret_cast(outBuffer.bits); - for (int y = 0; y < outBuffer.height; y++) { - for (int x = 0; x < outBuffer.width; x++) { - uint8_t* pixel = img + (4 * (y * outBuffer.stride + x)); - pixel[0] = color.r; - pixel[1] = color.g; - pixel[2] = color.b; - pixel[3] = LAYER_ALPHA; - } - } - - event->readyToExecute(); - - status = s->unlockAndPost(); - - ALOGE_IF(status != NO_ERROR, "fillSurface: failed to unlock and post buffer, (%d)", status); -} diff --git a/cmds/surfacereplayer/replayer/BufferQueueScheduler.h b/cmds/surfacereplayer/replayer/BufferQueueScheduler.h deleted file mode 100644 index cb20fcc798..0000000000 --- a/cmds/surfacereplayer/replayer/BufferQueueScheduler.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2016 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. - */ - -#ifndef ANDROID_SURFACEREPLAYER_BUFFERQUEUESCHEDULER_H -#define ANDROID_SURFACEREPLAYER_BUFFERQUEUESCHEDULER_H - -#include "Color.h" -#include "Event.h" - -#include - -#include - -#include -#include -#include -#include -#include - -namespace android { - -auto constexpr LAYER_ALPHA = 190; - -struct Dimensions { - Dimensions() = default; - Dimensions(int w, int h) : width(w), height(h) {} - - int width = 0; - int height = 0; -}; - -struct BufferEvent { - BufferEvent() = default; - BufferEvent(std::shared_ptr e, Dimensions d) : event(e), dimensions(d) {} - - std::shared_ptr event; - Dimensions dimensions; -}; - -class BufferQueueScheduler { - public: - BufferQueueScheduler(const sp& surfaceControl, const HSV& color, int id); - - void startScheduling(); - void addEvent(const BufferEvent&); - void stopScheduling(); - - void setSurfaceControl(const sp& surfaceControl, const HSV& color); - - private: - void bufferUpdate(const Dimensions& dimensions); - - // Lock and fill the surface, block until the event is signaled by the main loop, - // then unlock and post the buffer. - void fillSurface(const std::shared_ptr& event); - - sp mSurfaceControl; - HSV mColor; - const int mSurfaceId; - - bool mContinueScheduling; - - std::queue mBufferEvents; - std::mutex mMutex; - std::condition_variable mCondition; -}; - -} // namespace android -#endif diff --git a/cmds/surfacereplayer/replayer/Color.h b/cmds/surfacereplayer/replayer/Color.h deleted file mode 100644 index ce644be7be..0000000000 --- a/cmds/surfacereplayer/replayer/Color.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2016 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. - */ - -#ifndef ANDROID_SURFACEREPLAYER_COLOR_H -#define ANDROID_SURFACEREPLAYER_COLOR_H - -#include -#include - -namespace android { - -constexpr double modulateFactor = .0001; -constexpr double modulateLimit = .80; - -struct RGB { - RGB(uint8_t rIn, uint8_t gIn, uint8_t bIn) : r(rIn), g(gIn), b(bIn) {} - - uint8_t r = 0; - uint8_t g = 0; - uint8_t b = 0; -}; - -struct HSV { - HSV() = default; - HSV(double hIn, double sIn, double vIn) : h(hIn), s(sIn), v(vIn) {} - - double h = 0; - double s = 0; - double v = 0; - - RGB getRGB() const; - - bool modulateUp = false; - - void modulate(); -}; - -void inline HSV::modulate() { - if(modulateUp) { - v += modulateFactor; - } else { - v -= modulateFactor; - } - - if(v <= modulateLimit || v >= 1) { - modulateUp = !modulateUp; - } -} - -inline RGB HSV::getRGB() const { - using namespace std; - double r = 0, g = 0, b = 0; - - if (s == 0) { - r = v; - g = v; - b = v; - } else { - auto tempHue = static_cast(h) % 360; - tempHue = tempHue / 60; - - int i = static_cast(trunc(tempHue)); - double f = h - i; - - double x = v * (1.0 - s); - double y = v * (1.0 - (s * f)); - double z = v * (1.0 - (s * (1.0 - f))); - - switch (i) { - case 0: - r = v; - g = z; - b = x; - break; - - case 1: - r = y; - g = v; - b = x; - break; - - case 2: - r = x; - g = v; - b = z; - break; - - case 3: - r = x; - g = y; - b = v; - break; - - case 4: - r = z; - g = x; - b = v; - break; - - default: - r = v; - g = x; - b = y; - break; - } - } - - return RGB(round(r * 255), round(g * 255), round(b * 255)); -} -} -#endif diff --git a/cmds/surfacereplayer/replayer/Event.cpp b/cmds/surfacereplayer/replayer/Event.cpp deleted file mode 100644 index 64db5f07b1..0000000000 --- a/cmds/surfacereplayer/replayer/Event.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2016 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 "Event.h" - -using namespace android; -using Increment = surfaceflinger::Increment; - -Event::Event(Increment::IncrementCase type) : mIncrementType(type) {} - -void Event::readyToExecute() { - changeState(Event::EventState::Waiting); - waitUntil(Event::EventState::Signaled); - changeState(Event::EventState::Running); -} - -void Event::complete() { - waitUntil(Event::EventState::Waiting); - changeState(Event::EventState::Signaled); - waitUntil(Event::EventState::Running); -} - -void Event::waitUntil(Event::EventState state) { - std::unique_lock lock(mLock); - mCond.wait(lock, [this, state] { return (mState == state); }); -} - -void Event::changeState(Event::EventState state) { - std::unique_lock lock(mLock); - mState = state; - lock.unlock(); - - mCond.notify_one(); -} - -Increment::IncrementCase Event::getIncrementType() { - return mIncrementType; -} diff --git a/cmds/surfacereplayer/replayer/Event.h b/cmds/surfacereplayer/replayer/Event.h deleted file mode 100644 index 09a7c248d5..0000000000 --- a/cmds/surfacereplayer/replayer/Event.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2016 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. - */ - -#ifndef ANDROID_SURFACEREPLAYER_EVENT_H -#define ANDROID_SURFACEREPLAYER_EVENT_H - -#include - -#include -#include - -namespace android { - -using Increment = surfaceflinger::Increment; - -class Event { - public: - Event(Increment::IncrementCase); - - enum class EventState { - SettingUp, // Completing as much time-independent work as possible - Waiting, // Waiting for signal from main thread to finish execution - Signaled, // Signaled by main thread, about to immediately switch to Running - Running // Finishing execution of rest of work - }; - - void readyToExecute(); - void complete(); - - Increment::IncrementCase getIncrementType(); - - private: - void waitUntil(EventState state); - void changeState(EventState state); - - std::mutex mLock; - std::condition_variable mCond; - - EventState mState = EventState::SettingUp; - - Increment::IncrementCase mIncrementType; -}; -} -#endif diff --git a/cmds/surfacereplayer/replayer/Main.cpp b/cmds/surfacereplayer/replayer/Main.cpp deleted file mode 100644 index fbfcacf1aa..0000000000 --- a/cmds/surfacereplayer/replayer/Main.cpp +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2016 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. - */ - -/* - * Replayer - Main.cpp - * - * 1. Get flags from command line - * 2. Commit actions or settings based on the flags - * 3. Initalize a replayer object with the filename passed in - * 4. Replay - * 5. Exit successfully or print error statement - */ - -#include - -#include -#include -#include -#include - -using namespace android; - -void printHelpMenu() { - std::cout << "SurfaceReplayer options:\n"; - std::cout << "Usage: surfacereplayer [OPTIONS...] \n"; - std::cout << " File path must be absolute" << std::endl << std::endl; - - std::cout << " -m Stops the replayer at the start of the trace and switches "; - "to manual replay\n"; - - std::cout << "\n -t [Number of Threads] Specifies the number of threads to be used while " - "replaying (default is " << android::DEFAULT_THREADS << ")\n"; - - std::cout << "\n -s [Timestamp] Specify at what timestamp should the replayer switch " - "to manual replay\n"; - - std::cout << " -n Ignore timestamps and run through trace as fast as possible\n"; - - std::cout << " -l Indefinitely loop the replayer\n"; - - std::cout << " -h Display help menu\n"; - - std::cout << std::endl; -} - -int main(int argc, char** argv) { - std::string filename; - bool loop = false; - bool wait = true; - bool pauseBeginning = false; - int numThreads = DEFAULT_THREADS; - long stopHere = -1; - - int opt = 0; - while ((opt = getopt(argc, argv, "mt:s:nlh?")) != -1) { - switch (opt) { - case 'm': - pauseBeginning = true; - break; - case 't': - numThreads = atoi(optarg); - break; - case 's': - stopHere = atol(optarg); - break; - case 'n': - wait = false; - break; - case 'l': - loop = true; - break; - case 'h': - case '?': - printHelpMenu(); - exit(0); - default: - std::cerr << "Invalid argument...exiting" << std::endl; - printHelpMenu(); - exit(0); - } - } - - char** input = argv + optind; - if (input[0] == nullptr) { - std::cerr << "No trace file provided...exiting" << std::endl; - abort(); - } - filename.assign(input[0]); - - status_t status = NO_ERROR; - do { - android::Replayer r(filename, pauseBeginning, numThreads, wait, stopHere); - status = r.replay(); - } while(loop); - - if (status == NO_ERROR) { - std::cout << "Successfully finished replaying trace" << std::endl; - } else { - std::cerr << "Trace replayer returned error: " << status << std::endl; - } - - return 0; -} diff --git a/cmds/surfacereplayer/replayer/README.md b/cmds/surfacereplayer/replayer/README.md deleted file mode 100644 index 893f0dc0f6..0000000000 --- a/cmds/surfacereplayer/replayer/README.md +++ /dev/null @@ -1,262 +0,0 @@ -SurfaceReplayer Documentation -=================== - -[go/SurfaceReplayer](go/SurfaceReplayer) - -SurfaceReplayer is a playback mechanism that allows the replaying of traces recorded by -[SurfaceInterceptor](go/SurfaceInterceptor) from SurfaceFlinger. It specifically replays - -* Creation and deletion of surfaces/displays -* Alterations to the surfaces/displays called Transactions -* Buffer Updates to surfaces -* VSync events - -At their specified times to be as close to the original trace. - -Usage --------- - -###Creating a trace - -SurfaceInterceptor is the mechanism used to create traces. The device needs to be rooted in order to -utilize it. To allow it to write to the device, run - -`setenforce 0` - -To start recording a trace, run - -`service call SurfaceFlinger 1020 i32 1` - -To stop recording, run - -`service call SurfaceFlinger 1020 i32 0` - -The default location for the trace is `/data/SurfaceTrace.dat` - -###Executable - -To replay a specific trace, execute - -`/data/local/tmp/surfacereplayer /absolute/path/to/trace` - -inside the android shell. This will replay the full trace and then exit. Running this command -outside of the shell by prepending `adb shell` will not allow for manual control and will not turn -off VSync injections if it interrupted in any way other than fully replaying the trace - -The replay will not fill surfaces with their contents during the capture. Rather they are given a -random color which will be the same every time the trace is replayed. Surfaces modulate their color -at buffer updates. - -**Options:** - -- -m pause the replayer at the start of the trace for manual replay -- -t [Number of Threads] uses specified number of threads to queue up actions (default is 3) -- -s [Timestamp] switches to manual replay at specified timestamp -- -n Ignore timestamps and run through trace as fast as possible -- -l Indefinitely loop the replayer -- -h displays help menu - -**Manual Replay:** -When replaying, if the user presses CTRL-C, the replay will stop and can be manually controlled -by the user. Pressing CTRL-C again will exit the replayer. - -Manual replaying is similar to debugging in gdb. A prompt is presented and the user is able to -input commands to choose how to proceed by hitting enter after inputting a command. Pressing enter -without inputting a command repeats the previous command. - -- n - steps the replayer to the next VSync event -- ni - steps the replayer to the next increment -- c - continues normal replaying -- c [milliseconds] - continue until specified number of milliseconds have passed -- s [timestamp] - continue and stop at specified timestamp -- l - list out timestamp of current increment -- h - displays help menu - -###Shared Library - -To use the shared library include these shared libraries - -`libsurfacereplayer` -`libprotobuf-cpp-full` -`libutils` - -And the static library - -`libtrace_proto` - -Include the replayer header at the top of your file - -`#include ` - -There are two constructors for the replayer - -`Replayer(std::string& filename, bool replayManually, int numThreads, bool wait, nsecs_t stopHere)` -`Replayer(Trace& trace, ... ditto ...)` - -The first constructor takes in the filepath where the trace is located and loads in the trace -object internally. -- replayManually - **True**: if the replayer will immediately switch to manual replay at the start -- numThreads - Number of worker threads the replayer will use. -- wait - **False**: Replayer ignores waits in between increments -- stopHere - Time stamp of where the replayer should run to then switch to manual replay - -The second constructor includes all of the same parameters but takes in a preloaded trace object. -To use add - -`#include ` - -To your file - -After initializing the Replayer call - - replayer.replay(); - -And the trace will start replaying. Once the trace is finished replaying, the function will return. -The layers that are visible at the end of the trace will remain on screen until the program -terminates. - - -**If VSyncs are broken after running the replayer** that means `enableVSyncInjections(false)` was -never executed. This can be fixed by executing - -`service call SurfaceFlinger 23 i32 0` - -in the android shell - -Code Breakdown -------------- - -The Replayer is composed of 5 components. - -- The data format of the trace (Trace.proto) -- The Replayer object (Replayer.cpp) -- The synchronization mechanism to signal threads within the Replayer (Event.cpp) -- The scheduler for buffer updates per surface (BufferQueueScheduler.cpp) -- The Main executable (Main.cpp) - -### Traces - -Traces are represented as a protobuf message located in surfacereplayer/proto/src. - -**Traces** contain *repeated* **Increments** (events that have occurred in SurfaceFlinger). -**Increments** contain the time stamp of when it occurred and a *oneof* which can be a - - - Transaction - - SurfaceCreation - - SurfaceDeletion - - DisplayCreation - - DisplayDeleteion - - BufferUpdate - - VSyncEvent - - PowerModeUpdate - -**Transactions** contain whether the transaction was synchronous or animated and *repeated* -**SurfaceChanges** and **DisplayChanges** - -- **SurfaceChanges** contain an id of the surface being manipulated and can be changes such as -position, alpha, hidden, size, etc. -- **DisplayChanges** contain the id of the display being manipulated and can be changes such as -size, layer stack, projection, etc. - -**Surface/Display Creation** contain the id of the surface/display and the name of the -surface/display - -**Surface/Display Deletion** contain the id of the surface/display to be deleted - -**Buffer Updates** contain the id of the surface who's buffer is being updated, the size of the -buffer, and the frame number. - -**VSyncEvents** contain when the VSync event has occurred. - -**PowerModeUpdates** contain the id of the display being updated and what mode it is being -changed to. - -To output the contents of a trace in a readable format, execute - -`**aprotoc** --decode=Trace \ --I=$ANDROID_BUILD_TOP/frameworks/native/cmds/surfacereplayer/proto/src \ -$ANDROID_BUILD_TOP/frameworks/native/cmds/surfacereplayer/proto/src/trace.proto \ - < **YourTraceFile.dat** > **YourOutputName.txt**` - - -###Replayer - -Fundamentally the replayer loads a trace and iterates through each increment, waiting the required -amount of time until the increment should be executed, then executing the increment. The first -increment in a trace does not start at 0, rather the replayer treats its time stamp as time 0 and -goes from there. - -Increments from the trace are played asynchronously rather than one by one, being dispatched by -the main thread, queued up in a thread pool and completed when the main thread deems they are -ready to finish execution. - -When an increment is dispatched, it completes as much work as it can before it has to be -synchronized (e.g. prebaking a buffer for a BufferUpdate). When it gets to a critical action -(e.g. locking and pushing a buffer), it waits for the main thread to complete it using an Event -object. The main thread holds a queue of these Event objects and completes the -corresponding Event base on its time stamp. After completing an increment, the main thread will -dispatch another increment and continue. - -The main thread's execution flow is outlined below - - initReplay() //queue up the initial increments - while(!pendingIncrements.empty()) { //while increments remaining - event = pendingIncrement.pop(); - wait(event.time_stamp(); //waitUntil it is time to complete this increment - - event.complete() //signal to let event finish - if(increments remaing()) { - dispatchEvent() //queue up another increment - } - } - -A worker thread's flow looks like so - - //dispatched! - Execute non-time sensitive work here - ... - event.readyToExecute() //time sensitive point...waiting for Main Thread - ... - Finish execution - - -### Event - -An Event is a simple synchronization mechanism used to facilitate communication between the main -and worker threads. Every time an increment is dispatched, an Event object is also created. - -An Event can be in 4 different states: - -- **SettingUp** - The worker is in the process of completing all non-time sensitive work -- **Waiting** - The worker is waiting on the main thread to signal it. -- **Signaled** - The worker has just been signaled by the main thread -- **Running** - The worker is running again and finishing the rest of its work. - -When the main thread wants to finish the execution of a worker, the worker can either still be -**SettingUp**, in which the main thread will wait, or the worker will be **Waiting**, in which the -main thread will **Signal** it to complete. The worker thread changes itself to the **Running** -state once **Signaled**. This last step exists in order to communicate back to the main thread that -the worker thread has actually started completing its execution, rather than being preempted right -after signalling. Once this happens, the main thread schedules the next worker. This makes sure -there is a constant amount of workers running at one time. - -This activity is encapsulated in the `readyToExecute()` and `complete()` functions called by the -worker and main thread respectively. - -### BufferQueueScheduler - -During a **BuferUpdate**, the worker thread will wait until **Signaled** to unlock and post a -buffer that has been prefilled during the **SettingUp** phase. However if there are two sequential -**BufferUpdates** that act on the same surface, both threads will try to lock a buffer and fill it, -which isn't possible and will cause a deadlock. The BufferQueueScheduler solves this problem by -handling when **BufferUpdates** should be scheduled, making sure that they don't overlap. - -When a surface is created, a BufferQueueScheduler is also created along side it. Whenever a -**BufferUpdate** is read, it schedules the event onto its own internal queue and then schedules one -every time an Event is completed. - -### Main - -The main exectuable reads in the command line arguments. Creates the Replayer using those -arguments. Executes `replay()` on the Replayer. If there are no errors while replaying it will exit -gracefully, if there are then it will report the error and then exit. diff --git a/cmds/surfacereplayer/replayer/Replayer.h b/cmds/surfacereplayer/replayer/Replayer.h deleted file mode 100644 index d62522a497..0000000000 --- a/cmds/surfacereplayer/replayer/Replayer.h +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2016 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. - */ - -#ifndef ANDROID_SURFACEREPLAYER_H -#define ANDROID_SURFACEREPLAYER_H - -#include "BufferQueueScheduler.h" -#include "Color.h" -#include "Event.h" - -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace android::surfaceflinger; - -namespace android { - -const auto DEFAULT_PATH = "/data/local/tmp/SurfaceTrace.dat"; -const auto RAND_COLOR_SEED = 700; -const auto DEFAULT_THREADS = 3; - -typedef int32_t layer_id; -typedef int32_t display_id; - -typedef google::protobuf::RepeatedPtrField SurfaceChanges; -typedef google::protobuf::RepeatedPtrField DisplayChanges; - -class Replayer { - public: - Replayer(const std::string& filename, bool replayManually = false, - int numThreads = DEFAULT_THREADS, bool wait = true, nsecs_t stopHere = -1); - Replayer(const Trace& trace, bool replayManually = false, int numThreads = DEFAULT_THREADS, - bool wait = true, nsecs_t stopHere = -1); - - status_t replay(); - - private: - status_t initReplay(); - - void waitForConsoleCommmand(); - static void stopAutoReplayHandler(int signal); - - status_t dispatchEvent(int index); - - status_t doTransaction(const Transaction& transaction, const std::shared_ptr& event); - status_t createSurfaceControl(const SurfaceCreation& create, - const std::shared_ptr& event); - status_t injectVSyncEvent(const VSyncEvent& vsyncEvent, const std::shared_ptr& event); - void createDisplay(const DisplayCreation& create, const std::shared_ptr& event); - void deleteDisplay(const DisplayDeletion& delete_, const std::shared_ptr& event); - void updatePowerMode(const PowerModeUpdate& update, const std::shared_ptr& event); - - status_t doSurfaceTransaction(SurfaceComposerClient::Transaction& transaction, - const SurfaceChanges& surfaceChange); - void doDisplayTransaction(SurfaceComposerClient::Transaction& transaction, - const DisplayChanges& displayChange); - - void setPosition(SurfaceComposerClient::Transaction& t, - layer_id id, const PositionChange& pc); - void setSize(SurfaceComposerClient::Transaction& t, - layer_id id, const SizeChange& sc); - void setAlpha(SurfaceComposerClient::Transaction& t, - layer_id id, const AlphaChange& ac); - void setLayer(SurfaceComposerClient::Transaction& t, - layer_id id, const LayerChange& lc); - void setCrop(SurfaceComposerClient::Transaction& t, - layer_id id, const CropChange& cc); - void setCornerRadius(SurfaceComposerClient::Transaction& t, - layer_id id, const CornerRadiusChange& cc); - void setBackgroundBlurRadius(SurfaceComposerClient::Transaction& t, - layer_id id, const BackgroundBlurRadiusChange& cc); - void setBlurRegions(SurfaceComposerClient::Transaction& t, - layer_id id, const BlurRegionsChange& cc); - void setMatrix(SurfaceComposerClient::Transaction& t, - layer_id id, const MatrixChange& mc); - void setTransparentRegionHint(SurfaceComposerClient::Transaction& t, - layer_id id, const TransparentRegionHintChange& trgc); - void setLayerStack(SurfaceComposerClient::Transaction& t, - layer_id id, const LayerStackChange& lsc); - void setHiddenFlag(SurfaceComposerClient::Transaction& t, - layer_id id, const HiddenFlagChange& hfc); - void setOpaqueFlag(SurfaceComposerClient::Transaction& t, - layer_id id, const OpaqueFlagChange& ofc); - void setSecureFlag(SurfaceComposerClient::Transaction& t, - layer_id id, const SecureFlagChange& sfc); - void setReparentChange(SurfaceComposerClient::Transaction& t, - layer_id id, const ReparentChange& c); - void setRelativeParentChange(SurfaceComposerClient::Transaction& t, - layer_id id, const RelativeParentChange& c); - void setShadowRadiusChange(SurfaceComposerClient::Transaction& t, - layer_id id, const ShadowRadiusChange& c); - void setBlurRegionsChange(SurfaceComposerClient::Transaction& t, - layer_id id, const BlurRegionsChange& c); - - void setDisplaySurface(SurfaceComposerClient::Transaction& t, - display_id id, const DispSurfaceChange& dsc); - void setDisplayLayerStack(SurfaceComposerClient::Transaction& t, - display_id id, const LayerStackChange& lsc); - void setDisplaySize(SurfaceComposerClient::Transaction& t, - display_id id, const SizeChange& sc); - void setDisplayProjection(SurfaceComposerClient::Transaction& t, - display_id id, const ProjectionChange& pc); - - void waitUntilTimestamp(int64_t timestamp); - status_t loadSurfaceComposerClient(); - - Trace mTrace; - bool mLoaded = false; - int32_t mIncrementIndex = 0; - int64_t mCurrentTime = 0; - int32_t mNumThreads = DEFAULT_THREADS; - - Increment mCurrentIncrement; - - std::string mLastInput; - - static atomic_bool sReplayingManually; - bool mWaitingForNextVSync; - bool mWaitForTimeStamps; - nsecs_t mStopTimeStamp; - bool mHasStopped; - - std::mutex mLayerLock; - std::condition_variable mLayerCond; - std::unordered_map> mLayers; - std::unordered_map mColors; - - std::mutex mPendingLayersLock; - std::vector mLayersPendingRemoval; - - std::mutex mBufferQueueSchedulerLock; - std::unordered_map> mBufferQueueSchedulers; - - std::mutex mDisplayLock; - std::condition_variable mDisplayCond; - std::unordered_map> mDisplays; - - sp mComposerClient; - std::queue> mPendingIncrements; -}; - -} // namespace android -#endif diff --git a/cmds/surfacereplayer/replayer/trace_creator/trace_creator.py b/cmds/surfacereplayer/replayer/trace_creator/trace_creator.py deleted file mode 100644 index 58bfbf3c43..0000000000 --- a/cmds/surfacereplayer/replayer/trace_creator/trace_creator.py +++ /dev/null @@ -1,285 +0,0 @@ -#!/usr/bin/python -from subprocess import call -import os -proto_path = os.environ['ANDROID_BUILD_TOP'] + "/frameworks/native/cmds/surfacereplayer/proto/src/" -call(["aprotoc", "-I=" + proto_path, "--python_out=.", proto_path + "trace.proto"]) - -from trace_pb2 import * - -trace = Trace() - -def main(): - global trace - while(1): - option = main_menu() - - if option == 0: - break - - increment = trace.increment.add() - increment.time_stamp = int(input("Time stamp of action: ")) - - if option == 1: - transaction(increment) - elif option == 2: - surface_create(increment) - elif option == 3: - surface_delete(increment) - elif option == 4: - display_create(increment) - elif option == 5: - display_delete(increment) - elif option == 6: - buffer_update(increment) - elif option == 7: - vsync_event(increment) - elif option == 8: - power_mode_update(increment) - - seralizeTrace() - -def seralizeTrace(): - with open("trace.dat", 'wb') as f: - f.write(trace.SerializeToString()) - - -def main_menu(): - print ("") - print ("What would you like to do?") - print ("1. Add transaction") - print ("2. Add surface creation") - print ("3. Add surface deletion") - print ("4. Add display creation") - print ("5. Add display deletion") - print ("6. Add buffer update") - print ("7. Add VSync event") - print ("8. Add power mode update") - print ("0. Finish and serialize") - print ("") - - return int(input("> ")) - -def transaction_menu(): - print ("") - print ("What kind of transaction?") - print ("1. Position Change") - print ("2. Size Change") - print ("3. Alpha Change") - print ("4. Layer Change") - print ("5. Crop Change") - print ("6. Final Crop Change") - print ("7. Matrix Change") - print ("9. Transparent Region Hint Change") - print ("10. Layer Stack Change") - print ("11. Hidden Flag Change") - print ("12. Opaque Flag Change") - print ("13. Secure Flag Change") - print ("14. Deferred Transaction Change") - print ("15. Display - Surface Change") - print ("16. Display - Layer Stack Change") - print ("17. Display - Size Change") - print ("18. Display - Projection Change") - print ("0. Finished adding Changes to this transaction") - print ("") - - return int(input("> ")) - -def transaction(increment): - global trace - - increment.transaction.synchronous \ - = bool(input("Is transaction synchronous (True/False): ")) - increment.transaction.animation \ - = bool(input("Is transaction animated (True/False): ")) - - while(1): - option = transaction_menu() - - if option == 0: - break - - change = None - if option <= 14: - change = increment.transaction.surface_change.add() - elif option >= 15 and option <= 18: - change = increment.transaction.display_change.add() - - change.id = int(input("ID of layer/display to undergo a change: ")) - - if option == 1: - change.position.x, change.position.y = position() - elif option == 2: - change.size.w, change.size.h = size() - elif option == 3: - change.alpha.alpha = alpha() - elif option == 4: - change.layer.layer = layer() - elif option == 5: - change.crop.rectangle.left, change.crop.rectangle.top, \ - change.crop.rectangle.right, change.crop.rectangle.bottom = crop() - elif option == 6: - change.final_crop.rectangle.left, \ - change.final_crop.rectangle.top, \ - change.final_crop.rectangle.right,\ - change.final_crop.rectangle.bottom = final_crop() - elif option == 7: - change.matrix.dsdx,\ - change.matrix.dtdx,\ - change.matrix.dsdy,\ - change.matrix.dtdy = layer() - elif option == 9: - for rect in transparent_region_hint(): - new = increment.transparent_region_hint.region.add() - new.left = rect[0] - new.top = rect[1] - new.right = rect[2] - new.bottom = rect[3] - elif option == 10: - change.layer_stack.layer_stack = layer_stack() - elif option == 11: - change.hidden_flag.hidden_flag = hidden_flag() - elif option == 12: - change.opaque_flag.opaque_flag = opaque_flag() - elif option == 13: - change.secure_flag.secure_flag = secure_flag() - elif option == 14: - change.deferred_transaction.layer_id, \ - change.deferred_transaction.frame_number = deferred_transaction() - elif option == 15: - change.surface.buffer_queue_id, \ - change.surface.buffer_queue_name = surface() - elif option == 16: - change.layer_stack.layer_stack = layer_stack() - elif option == 17: - change.size.w, change.size.h = size() - elif option == 18: - projection(change) - -def surface_create(increment): - increment.surface_creation.id = int(input("Enter id: ")) - n = str(raw_input("Enter name: ")) - increment.surface_creation.name = n - increment.surface_creation.w = input("Enter w: ") - increment.surface_creation.h = input("Enter h: ") - -def surface_delete(increment): - increment.surface_deletion.id = int(input("Enter id: ")) - -def display_create(increment): - increment.display_creation.id = int(input("Enter id: ")) - increment.display_creation.name = str(raw_input("Enter name: ")) - increment.display_creation.display_id = int(input("Enter display ID: ")) - increment.display_creation.is_secure = bool(input("Enter if secure: ")) - -def display_delete(increment): - increment.surface_deletion.id = int(input("Enter id: ")) - -def buffer_update(increment): - increment.buffer_update.id = int(input("Enter id: ")) - increment.buffer_update.w = int(input("Enter w: ")) - increment.buffer_update.h = int(input("Enter h: ")) - increment.buffer_update.frame_number = int(input("Enter frame_number: ")) - -def vsync_event(increment): - increment.vsync_event.when = int(input("Enter when: ")) - -def power_mode_update(increment): - increment.power_mode_update.id = int(input("Enter id: ")) - increment.power_mode_update.mode = int(input("Enter mode: ")) - -def position(): - x = input("Enter x: ") - y = input("Enter y: ") - - return float(x), float(y) - -def size(): - w = input("Enter w: ") - h = input("Enter h: ") - - return int(w), int(h) - -def alpha(): - alpha = input("Enter alpha: ") - - return float(alpha) - -def layer(): - layer = input("Enter layer: ") - - return int(layer) - -def crop(): - return rectangle() - -def final_crop(): - return rectangle() - -def matrix(): - dsdx = input("Enter dsdx: ") - dtdx = input("Enter dtdx: ") - dsdy = input("Enter dsdy: ") - dtdy = input("Enter dtdy: ") - - return float(dsdx) - -def transparent_region_hint(): - num = input("Enter number of rectangles in region: ") - - return [rectangle() in range(x)] - -def layer_stack(): - layer_stack = input("Enter layer stack: ") - - return int(layer_stack) - -def hidden_flag(): - flag = input("Enter hidden flag state (True/False): ") - - return bool(flag) - -def opaque_flag(): - flag = input("Enter opaque flag state (True/False): ") - - return bool(flag) - -def secure_flag(): - flag = input("Enter secure flag state (True/False): ") - - return bool(flag) - -def deferred_transaction(): - layer_id = input("Enter layer_id: ") - frame_number = input("Enter frame_number: ") - - return int(layer_id), int(frame_number) - -def surface(): - id = input("Enter id: ") - name = raw_input("Enter name: ") - - return int(id), str(name) - -def projection(change): - change.projection.orientation = input("Enter orientation: ") - print("Enter rectangle for viewport") - change.projection.viewport.left, \ - change.projection.viewport.top, \ - change.projection.viewport.right,\ - change.projection.viewport.bottom = rectangle() - print("Enter rectangle for frame") - change.projection.frame.left, \ - change.projection.frame.top, \ - change.projection.frame.right,\ - change.projection.frame.bottom = rectangle() - -def rectangle(): - left = input("Enter left: ") - top = input("Enter top: ") - right = input("Enter right: ") - bottom = input("Enter bottom: ") - - return int(left), int(top), int(right), int(bottom) - -if __name__ == "__main__": - main() diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index d3144bb6c1..23a2181e59 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -217,7 +217,6 @@ cc_library_shared { "SurfaceControl.cpp", "SurfaceComposerClient.cpp", "SyncFeatures.cpp", - "TransactionTracing.cpp", "VsyncEventData.cpp", "view/Surface.cpp", "WindowInfosListenerReporter.cpp", diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index af64b3bd32..4c887ec96d 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -19,7 +19,6 @@ #include #include -#include #include #include #include diff --git a/libs/gui/TransactionTracing.cpp b/libs/gui/TransactionTracing.cpp deleted file mode 100644 index 59450fb411..0000000000 --- a/libs/gui/TransactionTracing.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2020 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/TransactionTracing.h" -#include "android/gui/ISurfaceComposer.h" - -#include - -namespace android { - -sp TransactionTraceListener::sInstance = nullptr; -std::mutex TransactionTraceListener::sMutex; - -TransactionTraceListener::TransactionTraceListener() {} - -sp TransactionTraceListener::getInstance() { - const std::lock_guard lock(sMutex); - - if (sInstance == nullptr) { - sInstance = new TransactionTraceListener; - - sp sf(ComposerServiceAIDL::getComposerService()); - sf->addTransactionTraceListener(sInstance); - } - - return sInstance; -} - -binder::Status TransactionTraceListener::onToggled(bool enabled) { - ALOGD("TransactionTraceListener: onToggled listener called"); - mTracingEnabled = enabled; - - return binder::Status::ok(); -} - -bool TransactionTraceListener::isTracingEnabled() { - return mTracingEnabled; -} - -} // namespace android diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 3c220fcc66..ac1442b73e 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -36,7 +36,6 @@ import android.gui.IHdrLayerInfoListener; import android.gui.IRegionSamplingListener; import android.gui.IScreenCaptureListener; import android.gui.ISurfaceComposerClient; -import android.gui.ITransactionTraceListener; import android.gui.ITunnelModeEnabledListener; import android.gui.IWindowInfosListener; import android.gui.LayerCaptureArgs; @@ -452,11 +451,6 @@ interface ISurfaceComposer { */ void setOverrideFrameRate(int uid, float frameRate); - /** - * Adds a TransactionTraceListener to listen for transaction tracing state updates. - */ - void addTransactionTraceListener(ITransactionTraceListener listener); - /** * Gets priority of the RenderEngine in SurfaceFlinger. */ diff --git a/libs/gui/aidl/android/gui/ITransactionTraceListener.aidl b/libs/gui/aidl/android/gui/ITransactionTraceListener.aidl deleted file mode 100644 index 5cd12fdc2b..0000000000 --- a/libs/gui/aidl/android/gui/ITransactionTraceListener.aidl +++ /dev/null @@ -1,6 +0,0 @@ -package android.gui; - -/** @hide */ -interface ITransactionTraceListener { - void onToggled(boolean enabled); -} \ No newline at end of file diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h index d51f6850c0..a5527484c9 100644 --- a/libs/gui/fuzzer/libgui_fuzzer_utils.h +++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h @@ -148,8 +148,6 @@ public: MOCK_METHOD(binder::Status, getDisplayDecorationSupport, (const sp&, std::optional*), (override)); MOCK_METHOD(binder::Status, setOverrideFrameRate, (int32_t, float), (override)); - MOCK_METHOD(binder::Status, addTransactionTraceListener, - (const sp&), (override)); MOCK_METHOD(binder::Status, getGpuContextPriority, (int32_t*), (override)); MOCK_METHOD(binder::Status, getMaxAcquiredBufferCount, (int32_t*), (override)); MOCK_METHOD(binder::Status, addWindowInfosListener, (const sp&), diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index d46c2e7e24..e91d75467d 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include diff --git a/libs/gui/include/gui/TransactionTracing.h b/libs/gui/include/gui/TransactionTracing.h deleted file mode 100644 index 9efba47a18..0000000000 --- a/libs/gui/include/gui/TransactionTracing.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2020 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 -#include - -namespace android { - -class TransactionTraceListener : public gui::BnTransactionTraceListener { - static std::mutex sMutex; - static sp sInstance; - - TransactionTraceListener(); - -public: - static sp getInstance(); - - binder::Status onToggled(bool enabled) override; - - bool isTracingEnabled(); - -private: - bool mTracingEnabled = false; -}; - -} // namespace android diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index c078378118..d5cc55f7fd 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -977,11 +977,6 @@ public: return binder::Status::ok(); } - binder::Status addTransactionTraceListener( - const sp& /*listener*/) override { - return binder::Status::ok(); - } - binder::Status getGpuContextPriority(int32_t* /*outPriority*/) override { return binder::Status::ok(); } diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index 809c80be0d..8a76d7c3f8 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -84,7 +84,6 @@ cc_defaults { "libserviceutils", "libshaders", "libtonemap", - "libtrace_proto", ], header_libs: [ "android.hardware.graphics.composer@2.1-command-buffer", @@ -189,7 +188,6 @@ filegroup { "StartPropertySetThread.cpp", "SurfaceFlinger.cpp", "SurfaceFlingerDefaultFactory.cpp", - "SurfaceInterceptor.cpp", "Tracing/LayerTracing.cpp", "Tracing/TransactionTracing.cpp", "Tracing/TransactionProtoParser.cpp", @@ -225,7 +223,6 @@ cc_defaults { ], static_libs: [ "libserviceutils", - "libtrace_proto", ], } diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp index b5d2ad0f74..0ae8bf98bf 100644 --- a/services/surfaceflinger/CompositionEngine/Android.bp +++ b/services/surfaceflinger/CompositionEngine/Android.bp @@ -42,7 +42,6 @@ cc_defaults { "libmath", "librenderengine", "libtonemap", - "libtrace_proto", "libaidlcommonsupport", "libprocessgroup", "libcgrouprc", diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 418056c22c..8ace8123f9 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -77,10 +77,6 @@ namespace gui { class LayerDebugInfo; } -namespace impl { -class SurfaceInterceptor; -} - namespace frametimeline { class SurfaceFrame; } // namespace frametimeline @@ -916,8 +912,6 @@ public: std::unordered_set& visited); protected: - friend class impl::SurfaceInterceptor; - // For unit tests friend class TestableSurfaceFlinger; friend class FpsReporterTest; diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index d3f53c11e4..a6cd47b253 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -237,16 +237,13 @@ namespace impl { EventThread::EventThread(std::unique_ptr vsyncSource, android::frametimeline::TokenManager* tokenManager, - InterceptVSyncsCallback interceptVSyncsCallback, ThrottleVsyncCallback throttleVsyncCallback, GetVsyncPeriodFunction getVsyncPeriodFunction) : mVSyncSource(std::move(vsyncSource)), mTokenManager(tokenManager), - mInterceptVSyncsCallback(std::move(interceptVSyncsCallback)), mThrottleVsyncCallback(std::move(throttleVsyncCallback)), mGetVsyncPeriodFunction(std::move(getVsyncPeriodFunction)), mThreadName(mVSyncSource->getName()) { - LOG_ALWAYS_FATAL_IF(getVsyncPeriodFunction == nullptr, "getVsyncPeriodFunction must not be null"); @@ -443,21 +440,13 @@ void EventThread::threadMain(std::unique_lock& lock) { event = mPendingEvents.front(); mPendingEvents.pop_front(); - switch (event->header.type) { - case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG: - if (event->hotplug.connected && !mVSyncState) { - mVSyncState.emplace(event->header.displayId); - } else if (!event->hotplug.connected && mVSyncState && - mVSyncState->displayId == event->header.displayId) { - mVSyncState.reset(); - } - break; - - case DisplayEventReceiver::DISPLAY_EVENT_VSYNC: - if (mInterceptVSyncsCallback) { - mInterceptVSyncsCallback(event->header.timestamp); - } - break; + if (event->header.type == DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG) { + if (event->hotplug.connected && !mVSyncState) { + mVSyncState.emplace(event->header.displayId); + } else if (!event->hotplug.connected && mVSyncState && + mVSyncState->displayId == event->header.displayId) { + mVSyncState.reset(); + } } } diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h index d85d140fc0..7a5a348db9 100644 --- a/services/surfaceflinger/Scheduler/EventThread.h +++ b/services/surfaceflinger/Scheduler/EventThread.h @@ -161,12 +161,11 @@ namespace impl { class EventThread : public android::EventThread, private VSyncSource::Callback { public: - using InterceptVSyncsCallback = std::function; using ThrottleVsyncCallback = std::function; using GetVsyncPeriodFunction = std::function; - EventThread(std::unique_ptr, frametimeline::TokenManager*, InterceptVSyncsCallback, - ThrottleVsyncCallback, GetVsyncPeriodFunction); + EventThread(std::unique_ptr, frametimeline::TokenManager*, ThrottleVsyncCallback, + GetVsyncPeriodFunction); ~EventThread(); sp createEventConnection( @@ -225,7 +224,6 @@ private: const std::unique_ptr mVSyncSource GUARDED_BY(mMutex); frametimeline::TokenManager* const mTokenManager; - const InterceptVSyncsCallback mInterceptVSyncsCallback; const ThrottleVsyncCallback mThrottleVsyncCallback; const GetVsyncPeriodFunction mGetVsyncPeriodFunction; const char* const mThreadName; diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 7f04a4de85..72b65455b0 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -231,15 +231,14 @@ impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction( }; } -ConnectionHandle Scheduler::createConnection( - const char* connectionName, frametimeline::TokenManager* tokenManager, - std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration, - impl::EventThread::InterceptVSyncsCallback interceptCallback) { +ConnectionHandle Scheduler::createConnection(const char* connectionName, + frametimeline::TokenManager* tokenManager, + std::chrono::nanoseconds workDuration, + std::chrono::nanoseconds readyDuration) { auto vsyncSource = makePrimaryDispSyncSource(connectionName, workDuration, readyDuration); auto throttleVsync = makeThrottleVsyncCallback(); auto getVsyncPeriod = makeGetVsyncPeriodFunction(); auto eventThread = std::make_unique(std::move(vsyncSource), tokenManager, - std::move(interceptCallback), std::move(throttleVsync), std::move(getVsyncPeriod)); return createConnection(std::move(eventThread)); @@ -418,7 +417,6 @@ ConnectionHandle Scheduler::enableVSyncInjection(bool enable) { auto eventThread = std::make_unique(std::move(vsyncSource), /*tokenManager=*/nullptr, - impl::EventThread::InterceptVSyncsCallback(), impl::EventThread::ThrottleVsyncCallback(), impl::EventThread::GetVsyncPeriodFunction()); diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index d6f62ca898..4c4956291d 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -162,8 +162,7 @@ public: ConnectionHandle createConnection(const char* connectionName, frametimeline::TokenManager*, std::chrono::nanoseconds workDuration, - std::chrono::nanoseconds readyDuration, - android::impl::EventThread::InterceptVSyncsCallback); + std::chrono::nanoseconds readyDuration); sp createDisplayEventConnection( ConnectionHandle, EventRegistrationFlags eventRegistration = {}); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 2d29e4cf2b..d21b0fafda 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -136,7 +136,6 @@ #include "Scheduler/VsyncConfiguration.h" #include "StartPropertySetThread.h" #include "SurfaceFlingerProperties.h" -#include "SurfaceInterceptor.h" #include "TimeStats/TimeStats.h" #include "TunnelModeEnabledReporter.h" #include "Utils/Dumper.h" @@ -300,7 +299,6 @@ bool callingThreadHasInternalSystemWindowAccess() { SurfaceFlinger::SurfaceFlinger(Factory& factory, SkipInitializationTag) : mFactory(factory), mPid(getpid()), - mInterceptor(mFactory.createSurfaceInterceptor()), mTimeStats(std::make_shared()), mFrameTracer(mFactory.createFrameTracer()), mFrameTimeline(mFactory.createFrameTimeline(mTimeStats, mPid)), @@ -495,7 +493,6 @@ sp SurfaceFlinger::createDisplay(const String8& displayName, bool secur state.isSecure = secure; state.displayName = displayName; mCurrentState.displays.add(token, state); - mInterceptor->saveDisplayCreation(state); return token; } @@ -513,7 +510,6 @@ void SurfaceFlinger::destroyDisplay(const sp& displayToken) { ALOGE("%s: Invalid operation on physical display", __func__); return; } - mInterceptor->saveDisplayDeletion(state.sequenceId); mCurrentState.displays.removeItemsAt(index); setTransactionFlags(eDisplayTransactionNeeded); } @@ -2690,8 +2686,6 @@ const char* SurfaceFlinger::processHotplug(PhysicalDisplayId displayId, const auto& display = displayOpt->get(); if (const ssize_t index = mCurrentState.displays.indexOfKey(display.token()); index >= 0) { - const DisplayDeviceState& state = mCurrentState.displays.valueAt(index); - mInterceptor->saveDisplayDeletion(state.sequenceId); mCurrentState.displays.removeItemsAt(index); } @@ -2746,7 +2740,6 @@ const char* SurfaceFlinger::processHotplug(PhysicalDisplayId displayId, state.displayName = std::move(info.name); mCurrentState.displays.add(token, state); - mInterceptor->saveDisplayCreation(state); return "Connecting"; } @@ -3408,15 +3401,11 @@ void SurfaceFlinger::initScheduler(const sp& display) { mAppConnectionHandle = mScheduler->createConnection("app", mFrameTimeline->getTokenManager(), /*workDuration=*/configs.late.appWorkDuration, - /*readyDuration=*/configs.late.sfWorkDuration, - impl::EventThread::InterceptVSyncsCallback()); + /*readyDuration=*/configs.late.sfWorkDuration); mSfConnectionHandle = mScheduler->createConnection("appSf", mFrameTimeline->getTokenManager(), /*workDuration=*/std::chrono::nanoseconds(vsyncPeriod), - /*readyDuration=*/configs.late.sfWorkDuration, - [this](nsecs_t timestamp) { - mInterceptor->saveVSyncEvent(timestamp); - }); + /*readyDuration=*/configs.late.sfWorkDuration); mScheduler->initVsync(mScheduler->getVsyncSchedule().getDispatch(), *mFrameTimeline->getTokenManager(), configs.late.sfWorkDuration); @@ -3991,11 +3980,6 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin bool needsTraversal = false; if (transactionFlags) { - if (mInterceptor->isEnabled()) { - mInterceptor->saveTransaction(states, mCurrentState.displays, displays, flags, - originPid, originUid, transactionId); - } - // We are on the main thread, we are about to preform a traversal. Clear the traversal bit // so we don't have to wake up again next frame to preform an unnecessary traversal. if (transactionFlags & eTraversalNeeded) { @@ -4641,9 +4625,6 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: display->setPowerMode(mode); - if (mInterceptor->isEnabled()) { - mInterceptor->savePowerModeUpdate(display->getSequenceId(), static_cast(mode)); - } const auto refreshRate = display->refreshRateConfigs().getActiveMode().getFps(); if (*currentMode == hal::PowerMode::OFF) { // Turn on the display @@ -5533,17 +5514,8 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r mScheduler->setDuration(mSfConnectionHandle, std::chrono::nanoseconds(n), 0ns); return NO_ERROR; } - case 1020: { // Layer updates interceptor - n = data.readInt32(); - if (n) { - ALOGV("Interceptor enabled"); - mInterceptor->enable(mDrawingState.layersSortedByZ, mDrawingState.displays); - } - else{ - ALOGV("Interceptor disabled"); - mInterceptor->disable(); - } - return NO_ERROR; + case 1020: { // Unused + return NAME_NOT_FOUND; } case 1021: { // Disable HWC virtual displays const bool enable = data.readInt32() != 0; @@ -6816,17 +6788,6 @@ void SurfaceFlinger::enableRefreshRateOverlay(bool enable) { } } -status_t SurfaceFlinger::addTransactionTraceListener( - const sp& listener) { - if (!listener) { - return BAD_VALUE; - } - - mInterceptor->addTransactionTraceListener(listener); - - return NO_ERROR; -} - int SurfaceFlinger::getGpuContextPriority() { return getRenderEngine().getContextPriority(); } @@ -6906,7 +6867,6 @@ void SurfaceFlinger::handleLayerCreatedLocked(const LayerCreatedState& state, Vs if (mTransactionTracing) { mTransactionTracing->onLayerAddedToDrawingState(layer->getSequence(), vsyncId.value); } - mInterceptor->saveSurfaceCreation(layer); } void SurfaceFlinger::sample() { @@ -7758,19 +7718,6 @@ binder::Status SurfaceComposerAIDL::setOverrideFrameRate(int32_t uid, float fram return binderStatusFromStatusT(status); } -binder::Status SurfaceComposerAIDL::addTransactionTraceListener( - const sp& listener) { - status_t status; - IPCThreadState* ipc = IPCThreadState::self(); - const int uid = ipc->getCallingUid(); - if (uid == AID_ROOT || uid == AID_GRAPHICS || uid == AID_SYSTEM || uid == AID_SHELL) { - status = mFlinger->addTransactionTraceListener(listener); - } else { - status = PERMISSION_DENIED; - } - return binderStatusFromStatusT(status); -} - binder::Status SurfaceComposerAIDL::getGpuContextPriority(int32_t* outPriority) { *outPriority = mFlinger->getGpuContextPriority(); return binder::Status::ok(); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 426e7886ec..ac23c12c85 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -588,8 +588,6 @@ private: status_t setOverrideFrameRate(uid_t uid, float frameRate); - status_t addTransactionTraceListener(const sp& listener); - int getGpuContextPriority(); status_t getMaxAcquiredBufferCount(int* buffers) const; @@ -1198,7 +1196,6 @@ private: bool mLayerCachingEnabled = false; bool mPropagateBackpressureClientComposition = false; - sp mInterceptor; LayerTracing mLayerTracing{*this}; bool mLayerTracingEnabled = false; @@ -1489,8 +1486,6 @@ public: const sp& displayToken, std::optional* outSupport) override; binder::Status setOverrideFrameRate(int32_t uid, float frameRate) override; - binder::Status addTransactionTraceListener( - const sp& listener) override; binder::Status getGpuContextPriority(int32_t* outPriority) override; binder::Status getMaxAcquiredBufferCount(int32_t* buffers) override; binder::Status addWindowInfosListener( diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp index 3e30dcb817..2f1f263afa 100644 --- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp +++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp @@ -29,7 +29,6 @@ #include "StartPropertySetThread.h" #include "SurfaceFlingerDefaultFactory.h" #include "SurfaceFlingerProperties.h" -#include "SurfaceInterceptor.h" #include "DisplayHardware/ComposerHal.h" #include "FrameTimeline/FrameTimeline.h" @@ -54,10 +53,6 @@ std::unique_ptr DefaultFactory::createVsyncConfig } } -sp DefaultFactory::createSurfaceInterceptor() { - return sp::make(); -} - sp DefaultFactory::createStartPropertySetThread( bool timestampPropertyValue) { return sp::make(timestampPropertyValue); diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h index 6fca402641..447a02fc0c 100644 --- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h +++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h @@ -29,7 +29,6 @@ public: std::unique_ptr createHWComposer(const std::string& serviceName) override; std::unique_ptr createVsyncConfiguration( Fps currentRefreshRate) override; - sp createSurfaceInterceptor() override; sp createStartPropertySetThread(bool timestampPropertyValue) override; sp createDisplayDevice(DisplayDeviceCreationArgs&) override; sp createGraphicBuffer(uint32_t width, uint32_t height, PixelFormat format, diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h index 6d18adea39..9c4d5c82ea 100644 --- a/services/surfaceflinger/SurfaceFlingerFactory.h +++ b/services/surfaceflinger/SurfaceFlingerFactory.h @@ -40,7 +40,6 @@ class IGraphicBufferProducer; class Layer; class StartPropertySetThread; class SurfaceFlinger; -class SurfaceInterceptor; class TimeStats; struct DisplayDeviceCreationArgs; @@ -71,7 +70,6 @@ public: virtual std::unique_ptr createHWComposer(const std::string& serviceName) = 0; virtual std::unique_ptr createVsyncConfiguration( Fps currentRefreshRate) = 0; - virtual sp createSurfaceInterceptor() = 0; virtual sp createStartPropertySetThread( bool timestampPropertyValue) = 0; diff --git a/services/surfaceflinger/SurfaceInterceptor.cpp b/services/surfaceflinger/SurfaceInterceptor.cpp deleted file mode 100644 index c90ae4cd83..0000000000 --- a/services/surfaceflinger/SurfaceInterceptor.cpp +++ /dev/null @@ -1,711 +0,0 @@ -/* - * Copyright 2016 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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" -#undef LOG_TAG -#define LOG_TAG "SurfaceInterceptor" -#define ATRACE_TAG ATRACE_TAG_GRAPHICS - -#include "Layer.h" -#include "SurfaceFlinger.h" -#include "SurfaceInterceptor.h" - -#include - -#include -#include -#include - -namespace android { - -// ---------------------------------------------------------------------------- -// TODO(marissaw): add new layer state values to SurfaceInterceptor - -SurfaceInterceptor::~SurfaceInterceptor() = default; - -namespace impl { - -void SurfaceInterceptor::addTransactionTraceListener( - const sp& listener) { - sp asBinder = IInterface::asBinder(listener); - - std::scoped_lock lock(mListenersMutex); - - asBinder->linkToDeath(sp::fromExisting(this)); - - listener->onToggled(mEnabled); // notifies of current state - - mTraceToggledListeners.emplace(asBinder, listener); -} - -void SurfaceInterceptor::binderDied(const wp& who) { - std::scoped_lock lock(mListenersMutex); - mTraceToggledListeners.erase(who); -} - -void SurfaceInterceptor::enable(const SortedVector>& layers, - const DefaultKeyedVector< wp, DisplayDeviceState>& displays) -{ - if (mEnabled) { - return; - } - ATRACE_CALL(); - { - std::scoped_lock lock(mListenersMutex); - for (const auto& [_, listener] : mTraceToggledListeners) { - listener->onToggled(true); - } - } - mEnabled = true; - std::scoped_lock protoGuard(mTraceMutex); - saveExistingDisplaysLocked(displays); - saveExistingSurfacesLocked(layers); -} - -void SurfaceInterceptor::disable() { - if (!mEnabled) { - return; - } - ATRACE_CALL(); - { - std::scoped_lock lock(mListenersMutex); - for (const auto& [_, listener] : mTraceToggledListeners) { - listener->onToggled(false); - } - } - mEnabled = false; - std::scoped_lock protoGuard(mTraceMutex); - status_t err(writeProtoFileLocked()); - ALOGE_IF(err == PERMISSION_DENIED, "Could not save the proto file! Permission denied"); - ALOGE_IF(err == NOT_ENOUGH_DATA, "Could not save the proto file! There are missing fields"); - mTrace.Clear(); -} - -bool SurfaceInterceptor::isEnabled() { - return mEnabled; -} - -void SurfaceInterceptor::saveExistingDisplaysLocked( - const DefaultKeyedVector< wp, DisplayDeviceState>& displays) -{ - // Caveat: The initial snapshot does not capture the power mode of the existing displays - ATRACE_CALL(); - for (size_t i = 0 ; i < displays.size() ; i++) { - addDisplayCreationLocked(createTraceIncrementLocked(), displays[i]); - addInitialDisplayStateLocked(createTraceIncrementLocked(), displays[i]); - } -} - -void SurfaceInterceptor::saveExistingSurfacesLocked(const SortedVector>& layers) { - ATRACE_CALL(); - for (const auto& l : layers) { - l->traverseInZOrder(LayerVector::StateSet::Drawing, [this](Layer* layer) { - addSurfaceCreationLocked(createTraceIncrementLocked(), sp::fromExisting(layer)); - addInitialSurfaceStateLocked(createTraceIncrementLocked(), - sp::fromExisting(layer)); - }); - } -} - -void SurfaceInterceptor::addInitialSurfaceStateLocked(Increment* increment, - const sp& layer) -{ - Transaction* transaction(increment->mutable_transaction()); - const uint32_t layerFlags = layer->getTransactionFlags(); - transaction->set_animation(layerFlags & BnSurfaceComposer::eAnimation); - - const int32_t layerId(getLayerId(layer)); - addPositionLocked(transaction, layerId, layer->mDrawingState.transform.tx(), - layer->mDrawingState.transform.ty()); - addDepthLocked(transaction, layerId, layer->mDrawingState.z); - addAlphaLocked(transaction, layerId, layer->mDrawingState.color.a); - addLayerStackLocked(transaction, layerId, layer->mDrawingState.layerStack); - addCropLocked(transaction, layerId, layer->mDrawingState.crop); - addCornerRadiusLocked(transaction, layerId, layer->mDrawingState.cornerRadius); - addBackgroundBlurRadiusLocked(transaction, layerId, layer->mDrawingState.backgroundBlurRadius); - addBlurRegionsLocked(transaction, layerId, layer->mDrawingState.blurRegions); - addFlagsLocked(transaction, layerId, layer->mDrawingState.flags, - layer_state_t::eLayerHidden | layer_state_t::eLayerOpaque | - layer_state_t::eLayerSecure); - addReparentLocked(transaction, layerId, getLayerIdFromWeakRef(layer->mDrawingParent)); - addRelativeParentLocked(transaction, layerId, - getLayerIdFromWeakRef(layer->mDrawingState.zOrderRelativeOf), - layer->mDrawingState.z); - addShadowRadiusLocked(transaction, layerId, layer->mDrawingState.shadowRadius); - addTrustedOverlayLocked(transaction, layerId, layer->mDrawingState.isTrustedOverlay); -} - -void SurfaceInterceptor::addInitialDisplayStateLocked(Increment* increment, - const DisplayDeviceState& display) -{ - Transaction* transaction(increment->mutable_transaction()); - transaction->set_synchronous(false); - transaction->set_animation(false); - - addDisplaySurfaceLocked(transaction, display.sequenceId, display.surface); - addDisplayLayerStackLocked(transaction, display.sequenceId, display.layerStack); - addDisplayFlagsLocked(transaction, display.sequenceId, display.flags); - addDisplaySizeLocked(transaction, display.sequenceId, display.width, display.height); - addDisplayProjectionLocked(transaction, display.sequenceId, toRotationInt(display.orientation), - display.layerStackSpaceRect, display.orientedDisplaySpaceRect); -} - -status_t SurfaceInterceptor::writeProtoFileLocked() { - ATRACE_CALL(); - std::string output; - - if (!mTrace.IsInitialized()) { - return NOT_ENOUGH_DATA; - } - if (!mTrace.SerializeToString(&output)) { - return PERMISSION_DENIED; - } - if (!android::base::WriteStringToFile(output, mOutputFileName, true)) { - return PERMISSION_DENIED; - } - - return NO_ERROR; -} - -const sp SurfaceInterceptor::getLayer(const wp& weakHandle) const { - sp handle = weakHandle.promote(); - return Layer::fromHandle(handle).promote(); -} - -int32_t SurfaceInterceptor::getLayerId(const sp& layer) const { - return layer->sequence; -} - -int32_t SurfaceInterceptor::getLayerIdFromWeakRef(const wp& layer) const { - if (layer == nullptr) { - return -1; - } - auto strongLayer = layer.promote(); - return strongLayer == nullptr ? -1 : getLayerId(strongLayer); -} - -int32_t SurfaceInterceptor::getLayerIdFromHandle(const sp& handle) const { - if (handle == nullptr) { - return -1; - } - const sp layer = Layer::fromHandle(handle).promote(); - return layer == nullptr ? -1 : getLayerId(layer); -} - -Increment* SurfaceInterceptor::createTraceIncrementLocked() { - Increment* increment(mTrace.add_increment()); - increment->set_time_stamp(elapsedRealtimeNano()); - return increment; -} - -SurfaceChange* SurfaceInterceptor::createSurfaceChangeLocked(Transaction* transaction, - int32_t layerId) -{ - SurfaceChange* change(transaction->add_surface_change()); - change->set_id(layerId); - return change; -} - -DisplayChange* SurfaceInterceptor::createDisplayChangeLocked(Transaction* transaction, - int32_t sequenceId) -{ - DisplayChange* dispChange(transaction->add_display_change()); - dispChange->set_id(sequenceId); - return dispChange; -} - -void SurfaceInterceptor::setProtoRectLocked(Rectangle* protoRect, const Rect& rect) { - protoRect->set_left(rect.left); - protoRect->set_top(rect.top); - protoRect->set_right(rect.right); - protoRect->set_bottom(rect.bottom); -} - -void SurfaceInterceptor::setTransactionOriginLocked(Transaction* transaction, int32_t pid, - int32_t uid) { - Origin* origin(transaction->mutable_origin()); - origin->set_pid(pid); - origin->set_uid(uid); -} - -void SurfaceInterceptor::addPositionLocked(Transaction* transaction, int32_t layerId, - float x, float y) -{ - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - PositionChange* posChange(change->mutable_position()); - posChange->set_x(x); - posChange->set_y(y); -} - -void SurfaceInterceptor::addDepthLocked(Transaction* transaction, int32_t layerId, - uint32_t z) -{ - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - LayerChange* depthChange(change->mutable_layer()); - depthChange->set_layer(z); -} - -void SurfaceInterceptor::addSizeLocked(Transaction* transaction, int32_t layerId, uint32_t w, - uint32_t h) -{ - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - SizeChange* sizeChange(change->mutable_size()); - sizeChange->set_w(w); - sizeChange->set_h(h); -} - -void SurfaceInterceptor::addAlphaLocked(Transaction* transaction, int32_t layerId, - float alpha) -{ - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - AlphaChange* alphaChange(change->mutable_alpha()); - alphaChange->set_alpha(alpha); -} - -void SurfaceInterceptor::addMatrixLocked(Transaction* transaction, int32_t layerId, - const layer_state_t::matrix22_t& matrix) -{ - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - MatrixChange* matrixChange(change->mutable_matrix()); - matrixChange->set_dsdx(matrix.dsdx); - matrixChange->set_dtdx(matrix.dtdx); - matrixChange->set_dsdy(matrix.dsdy); - matrixChange->set_dtdy(matrix.dtdy); -} - -void SurfaceInterceptor::addTransparentRegionLocked(Transaction* transaction, - int32_t layerId, const Region& transRegion) -{ - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - TransparentRegionHintChange* transparentChange(change->mutable_transparent_region_hint()); - - for (const auto& rect : transRegion) { - Rectangle* protoRect(transparentChange->add_region()); - setProtoRectLocked(protoRect, rect); - } -} - -void SurfaceInterceptor::addFlagsLocked(Transaction* transaction, int32_t layerId, uint8_t flags, - uint8_t mask) { - // There can be multiple flags changed - if (mask & layer_state_t::eLayerHidden) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - HiddenFlagChange* flagChange(change->mutable_hidden_flag()); - flagChange->set_hidden_flag(flags & layer_state_t::eLayerHidden); - } - if (mask & layer_state_t::eLayerOpaque) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - OpaqueFlagChange* flagChange(change->mutable_opaque_flag()); - flagChange->set_opaque_flag(flags & layer_state_t::eLayerOpaque); - } - if (mask & layer_state_t::eLayerSecure) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - SecureFlagChange* flagChange(change->mutable_secure_flag()); - flagChange->set_secure_flag(flags & layer_state_t::eLayerSecure); - } -} - -void SurfaceInterceptor::addLayerStackLocked(Transaction* transaction, int32_t layerId, - ui::LayerStack layerStack) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - LayerStackChange* layerStackChange(change->mutable_layer_stack()); - layerStackChange->set_layer_stack(layerStack.id); -} - -void SurfaceInterceptor::addCropLocked(Transaction* transaction, int32_t layerId, - const Rect& rect) -{ - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - CropChange* cropChange(change->mutable_crop()); - Rectangle* protoRect(cropChange->mutable_rectangle()); - setProtoRectLocked(protoRect, rect); -} - -void SurfaceInterceptor::addCornerRadiusLocked(Transaction* transaction, int32_t layerId, - float cornerRadius) -{ - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - CornerRadiusChange* cornerRadiusChange(change->mutable_corner_radius()); - cornerRadiusChange->set_corner_radius(cornerRadius); -} - -void SurfaceInterceptor::addBackgroundBlurRadiusLocked(Transaction* transaction, int32_t layerId, - int32_t backgroundBlurRadius) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - BackgroundBlurRadiusChange* blurRadiusChange(change->mutable_background_blur_radius()); - blurRadiusChange->set_background_blur_radius(backgroundBlurRadius); -} - -void SurfaceInterceptor::addBlurRegionsLocked(Transaction* transaction, int32_t layerId, - const std::vector& blurRegions) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - BlurRegionsChange* blurRegionsChange(change->mutable_blur_regions()); - for (const auto blurRegion : blurRegions) { - const auto blurRegionChange = blurRegionsChange->add_blur_regions(); - blurRegionChange->set_blur_radius(blurRegion.blurRadius); - blurRegionChange->set_corner_radius_tl(blurRegion.cornerRadiusTL); - blurRegionChange->set_corner_radius_tr(blurRegion.cornerRadiusTR); - blurRegionChange->set_corner_radius_bl(blurRegion.cornerRadiusBL); - blurRegionChange->set_corner_radius_br(blurRegion.cornerRadiusBR); - blurRegionChange->set_alpha(blurRegion.alpha); - blurRegionChange->set_left(blurRegion.left); - blurRegionChange->set_top(blurRegion.top); - blurRegionChange->set_right(blurRegion.right); - blurRegionChange->set_bottom(blurRegion.bottom); - } -} - -void SurfaceInterceptor::addReparentLocked(Transaction* transaction, int32_t layerId, - int32_t parentId) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - ReparentChange* overrideChange(change->mutable_reparent()); - overrideChange->set_parent_id(parentId); -} - -void SurfaceInterceptor::addRelativeParentLocked(Transaction* transaction, int32_t layerId, - int32_t parentId, int z) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - RelativeParentChange* overrideChange(change->mutable_relative_parent()); - overrideChange->set_relative_parent_id(parentId); - overrideChange->set_z(z); -} - -void SurfaceInterceptor::addShadowRadiusLocked(Transaction* transaction, int32_t layerId, - float shadowRadius) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - ShadowRadiusChange* overrideChange(change->mutable_shadow_radius()); - overrideChange->set_radius(shadowRadius); -} - -void SurfaceInterceptor::addTrustedOverlayLocked(Transaction* transaction, int32_t layerId, - bool isTrustedOverlay) { - SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId)); - TrustedOverlayChange* overrideChange(change->mutable_trusted_overlay()); - overrideChange->set_is_trusted_overlay(isTrustedOverlay); -} - -void SurfaceInterceptor::addSurfaceChangesLocked(Transaction* transaction, - const layer_state_t& state) -{ - const sp layer(getLayer(state.surface)); - if (layer == nullptr) { - ALOGE("An existing layer could not be retrieved with the surface " - "from the layer_state_t surface in the update transaction"); - return; - } - - const int32_t layerId(getLayerId(layer)); - - if (state.what & layer_state_t::ePositionChanged) { - addPositionLocked(transaction, layerId, state.x, state.y); - } - if (state.what & layer_state_t::eLayerChanged) { - addDepthLocked(transaction, layerId, state.z); - } - if (state.what & layer_state_t::eAlphaChanged) { - addAlphaLocked(transaction, layerId, state.alpha); - } - if (state.what & layer_state_t::eMatrixChanged) { - addMatrixLocked(transaction, layerId, state.matrix); - } - if (state.what & layer_state_t::eTransparentRegionChanged) { - addTransparentRegionLocked(transaction, layerId, state.transparentRegion); - } - if (state.what & layer_state_t::eFlagsChanged) { - addFlagsLocked(transaction, layerId, state.flags, state.mask); - } - if (state.what & layer_state_t::eLayerStackChanged) { - addLayerStackLocked(transaction, layerId, state.layerStack); - } - if (state.what & layer_state_t::eCropChanged) { - addCropLocked(transaction, layerId, state.crop); - } - if (state.what & layer_state_t::eCornerRadiusChanged) { - addCornerRadiusLocked(transaction, layerId, state.cornerRadius); - } - if (state.what & layer_state_t::eBackgroundBlurRadiusChanged) { - addBackgroundBlurRadiusLocked(transaction, layerId, state.backgroundBlurRadius); - } - if (state.what & layer_state_t::eBlurRegionsChanged) { - addBlurRegionsLocked(transaction, layerId, state.blurRegions); - } - if (state.what & layer_state_t::eReparent) { - auto parentHandle = (state.parentSurfaceControlForChild) - ? state.parentSurfaceControlForChild->getHandle() - : nullptr; - addReparentLocked(transaction, layerId, getLayerIdFromHandle(parentHandle)); - } - if (state.what & layer_state_t::eRelativeLayerChanged) { - addRelativeParentLocked(transaction, layerId, - getLayerIdFromHandle( - state.relativeLayerSurfaceControl->getHandle()), - state.z); - } - if (state.what & layer_state_t::eShadowRadiusChanged) { - addShadowRadiusLocked(transaction, layerId, state.shadowRadius); - } - if (state.what & layer_state_t::eTrustedOverlayChanged) { - addTrustedOverlayLocked(transaction, layerId, state.isTrustedOverlay); - } - if (state.what & layer_state_t::eStretchChanged) { - ALOGW("SurfaceInterceptor not implemented for eStretchChanged"); - } -} - -void SurfaceInterceptor::addDisplayChangesLocked(Transaction* transaction, - const DisplayState& state, int32_t sequenceId) -{ - if (state.what & DisplayState::eSurfaceChanged) { - addDisplaySurfaceLocked(transaction, sequenceId, state.surface); - } - if (state.what & DisplayState::eLayerStackChanged) { - addDisplayLayerStackLocked(transaction, sequenceId, state.layerStack); - } - if (state.what & DisplayState::eFlagsChanged) { - addDisplayFlagsLocked(transaction, sequenceId, state.flags); - } - if (state.what & DisplayState::eDisplaySizeChanged) { - addDisplaySizeLocked(transaction, sequenceId, state.width, state.height); - } - if (state.what & DisplayState::eDisplayProjectionChanged) { - addDisplayProjectionLocked(transaction, sequenceId, toRotationInt(state.orientation), - state.layerStackSpaceRect, state.orientedDisplaySpaceRect); - } -} - -void SurfaceInterceptor::addTransactionLocked( - Increment* increment, const Vector& stateUpdates, - const DefaultKeyedVector, DisplayDeviceState>& displays, - const Vector& changedDisplays, uint32_t transactionFlags, int originPid, - int originUid, uint64_t transactionId) { - Transaction* transaction(increment->mutable_transaction()); - transaction->set_animation(transactionFlags & BnSurfaceComposer::eAnimation); - setTransactionOriginLocked(transaction, originPid, originUid); - transaction->set_id(transactionId); - for (const auto& compState: stateUpdates) { - addSurfaceChangesLocked(transaction, compState.state); - } - for (const auto& disp: changedDisplays) { - ssize_t dpyIdx = displays.indexOfKey(disp.token); - if (dpyIdx >= 0) { - const DisplayDeviceState& dispState(displays.valueAt(dpyIdx)); - addDisplayChangesLocked(transaction, disp, dispState.sequenceId); - } - } -} - -void SurfaceInterceptor::addSurfaceCreationLocked(Increment* increment, - const sp& layer) -{ - SurfaceCreation* creation(increment->mutable_surface_creation()); - creation->set_id(getLayerId(layer)); - creation->set_name(layer->getName()); -} - -void SurfaceInterceptor::addSurfaceDeletionLocked(Increment* increment, - const sp& layer) -{ - SurfaceDeletion* deletion(increment->mutable_surface_deletion()); - deletion->set_id(getLayerId(layer)); -} - -void SurfaceInterceptor::addBufferUpdateLocked(Increment* increment, int32_t layerId, - uint32_t width, uint32_t height, uint64_t frameNumber) -{ - BufferUpdate* update(increment->mutable_buffer_update()); - update->set_id(layerId); - update->set_w(width); - update->set_h(height); - update->set_frame_number(frameNumber); -} - -void SurfaceInterceptor::addVSyncUpdateLocked(Increment* increment, nsecs_t timestamp) { - VSyncEvent* event(increment->mutable_vsync_event()); - event->set_when(timestamp); -} - -void SurfaceInterceptor::addDisplaySurfaceLocked(Transaction* transaction, int32_t sequenceId, - const sp& surface) -{ - if (surface == nullptr) { - return; - } - uint64_t bufferQueueId = 0; - status_t err(surface->getUniqueId(&bufferQueueId)); - if (err == NO_ERROR) { - DisplayChange* dispChange(createDisplayChangeLocked(transaction, sequenceId)); - DispSurfaceChange* surfaceChange(dispChange->mutable_surface()); - surfaceChange->set_buffer_queue_id(bufferQueueId); - surfaceChange->set_buffer_queue_name(surface->getConsumerName().string()); - } - else { - ALOGE("invalid graphic buffer producer received while tracing a display change (%s)", - strerror(-err)); - } -} - -void SurfaceInterceptor::addDisplayLayerStackLocked(Transaction* transaction, int32_t sequenceId, - ui::LayerStack layerStack) { - DisplayChange* dispChange(createDisplayChangeLocked(transaction, sequenceId)); - LayerStackChange* layerStackChange(dispChange->mutable_layer_stack()); - layerStackChange->set_layer_stack(layerStack.id); -} - -void SurfaceInterceptor::addDisplayFlagsLocked(Transaction* transaction, int32_t sequenceId, - uint32_t flags) { - DisplayChange* dispChange(createDisplayChangeLocked(transaction, sequenceId)); - DisplayFlagsChange* flagsChange(dispChange->mutable_flags()); - flagsChange->set_flags(flags); -} - -void SurfaceInterceptor::addDisplaySizeLocked(Transaction* transaction, int32_t sequenceId, - uint32_t w, uint32_t h) -{ - DisplayChange* dispChange(createDisplayChangeLocked(transaction, sequenceId)); - SizeChange* sizeChange(dispChange->mutable_size()); - sizeChange->set_w(w); - sizeChange->set_h(h); -} - -void SurfaceInterceptor::addDisplayProjectionLocked(Transaction* transaction, - int32_t sequenceId, int32_t orientation, const Rect& viewport, const Rect& frame) -{ - DisplayChange* dispChange(createDisplayChangeLocked(transaction, sequenceId)); - ProjectionChange* projectionChange(dispChange->mutable_projection()); - projectionChange->set_orientation(orientation); - Rectangle* viewportRect(projectionChange->mutable_viewport()); - setProtoRectLocked(viewportRect, viewport); - Rectangle* frameRect(projectionChange->mutable_frame()); - setProtoRectLocked(frameRect, frame); -} - -void SurfaceInterceptor::addDisplayCreationLocked(Increment* increment, - const DisplayDeviceState& info) -{ - DisplayCreation* creation(increment->mutable_display_creation()); - creation->set_id(info.sequenceId); - creation->set_name(info.displayName); - creation->set_is_secure(info.isSecure); - if (info.physical) { - creation->set_display_id(info.physical->id.value); - } -} - -void SurfaceInterceptor::addDisplayDeletionLocked(Increment* increment, int32_t sequenceId) { - DisplayDeletion* deletion(increment->mutable_display_deletion()); - deletion->set_id(sequenceId); -} - -void SurfaceInterceptor::addPowerModeUpdateLocked(Increment* increment, int32_t sequenceId, - int32_t mode) -{ - PowerModeUpdate* powerModeUpdate(increment->mutable_power_mode_update()); - powerModeUpdate->set_id(sequenceId); - powerModeUpdate->set_mode(mode); -} - -void SurfaceInterceptor::saveTransaction( - const Vector& stateUpdates, - const DefaultKeyedVector, DisplayDeviceState>& displays, - const Vector& changedDisplays, uint32_t flags, int originPid, int originUid, - uint64_t transactionId) { - if (!mEnabled || (stateUpdates.size() <= 0 && changedDisplays.size() <= 0)) { - return; - } - ATRACE_CALL(); - std::lock_guard protoGuard(mTraceMutex); - addTransactionLocked(createTraceIncrementLocked(), stateUpdates, displays, changedDisplays, - flags, originPid, originUid, transactionId); -} - -void SurfaceInterceptor::saveSurfaceCreation(const sp& layer) { - if (!mEnabled || layer == nullptr) { - return; - } - ATRACE_CALL(); - std::lock_guard protoGuard(mTraceMutex); - addSurfaceCreationLocked(createTraceIncrementLocked(), layer); -} - -void SurfaceInterceptor::saveSurfaceDeletion(const sp& layer) { - if (!mEnabled || layer == nullptr) { - return; - } - ATRACE_CALL(); - std::lock_guard protoGuard(mTraceMutex); - addSurfaceDeletionLocked(createTraceIncrementLocked(), layer); -} - -/** - * Here we pass the layer by ID instead of by sp<> since this is called without - * holding the state-lock from a Binder thread. If we required the caller - * to pass 'this' by sp<> the temporary sp<> constructed could end up - * being the last reference and we might accidentally destroy the Layer - * from this binder thread. - */ -void SurfaceInterceptor::saveBufferUpdate(int32_t layerId, uint32_t width, - uint32_t height, uint64_t frameNumber) -{ - if (!mEnabled) { - return; - } - ATRACE_CALL(); - std::lock_guard protoGuard(mTraceMutex); - addBufferUpdateLocked(createTraceIncrementLocked(), layerId, width, height, frameNumber); -} - -void SurfaceInterceptor::saveVSyncEvent(nsecs_t timestamp) { - if (!mEnabled) { - return; - } - std::lock_guard protoGuard(mTraceMutex); - addVSyncUpdateLocked(createTraceIncrementLocked(), timestamp); -} - -void SurfaceInterceptor::saveDisplayCreation(const DisplayDeviceState& info) { - if (!mEnabled) { - return; - } - ATRACE_CALL(); - std::lock_guard protoGuard(mTraceMutex); - addDisplayCreationLocked(createTraceIncrementLocked(), info); -} - -void SurfaceInterceptor::saveDisplayDeletion(int32_t sequenceId) { - if (!mEnabled) { - return; - } - ATRACE_CALL(); - std::lock_guard protoGuard(mTraceMutex); - addDisplayDeletionLocked(createTraceIncrementLocked(), sequenceId); -} - -void SurfaceInterceptor::savePowerModeUpdate(int32_t sequenceId, int32_t mode) { - if (!mEnabled) { - return; - } - ATRACE_CALL(); - std::lock_guard protoGuard(mTraceMutex); - addPowerModeUpdateLocked(createTraceIncrementLocked(), sequenceId, mode); -} - -} // namespace impl -} // namespace android - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/SurfaceInterceptor.h b/services/surfaceflinger/SurfaceInterceptor.h deleted file mode 100644 index 970c3e5c27..0000000000 --- a/services/surfaceflinger/SurfaceInterceptor.h +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright 2016 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. - */ - -#ifndef ANDROID_SURFACEINTERCEPTOR_H -#define ANDROID_SURFACEINTERCEPTOR_H - -#include - -#include - -#include - -#include - -#include -#include -#include -#include - -#include "DisplayDevice.h" - -namespace android { - -class BufferItem; -class Layer; -class SurfaceFlinger; -struct ComposerState; -struct DisplayDeviceState; -struct DisplayState; -struct layer_state_t; -using Transaction = surfaceflinger::Transaction; -using Trace = surfaceflinger::Trace; -using Rectangle = surfaceflinger::Rectangle; -using SurfaceChange = surfaceflinger::SurfaceChange; -using Increment = surfaceflinger::Increment; -using DisplayChange = surfaceflinger::DisplayChange; - -constexpr auto DEFAULT_FILENAME = "/data/misc/wmtrace/transaction_trace.winscope"; - -class SurfaceInterceptor : public IBinder::DeathRecipient { -public: - virtual ~SurfaceInterceptor(); - - // Both vectors are used to capture the current state of SF as the initial snapshot in the trace - virtual void enable(const SortedVector>& layers, - const DefaultKeyedVector, DisplayDeviceState>& displays) = 0; - virtual void disable() = 0; - virtual bool isEnabled() = 0; - - virtual void addTransactionTraceListener( - const sp& listener) = 0; - virtual void binderDied(const wp& who) = 0; - - // Intercept display and surface transactions - virtual void saveTransaction( - const Vector& stateUpdates, - const DefaultKeyedVector, DisplayDeviceState>& displays, - const Vector& changedDisplays, uint32_t flags, int originPid, - int originUid, uint64_t transactionId) = 0; - - // Intercept surface data - virtual void saveSurfaceCreation(const sp& layer) = 0; - virtual void saveSurfaceDeletion(const sp& layer) = 0; - virtual void saveBufferUpdate(int32_t layerId, uint32_t width, uint32_t height, - uint64_t frameNumber) = 0; - - // Intercept display data - virtual void saveDisplayCreation(const DisplayDeviceState& info) = 0; - virtual void saveDisplayDeletion(int32_t sequenceId) = 0; - virtual void savePowerModeUpdate(int32_t sequenceId, int32_t mode) = 0; - virtual void saveVSyncEvent(nsecs_t timestamp) = 0; -}; - -namespace impl { - -/* - * SurfaceInterceptor intercepts and stores incoming streams of window - * properties on SurfaceFlinger. - */ -class SurfaceInterceptor final : public android::SurfaceInterceptor { -public: - SurfaceInterceptor() = default; - ~SurfaceInterceptor() override = default; - - // Both vectors are used to capture the current state of SF as the initial snapshot in the trace - void enable(const SortedVector>& layers, - const DefaultKeyedVector, DisplayDeviceState>& displays) override; - void disable() override; - bool isEnabled() override; - - void addTransactionTraceListener(const sp& listener) override; - void binderDied(const wp& who) override; - - // Intercept display and surface transactions - void saveTransaction(const Vector& stateUpdates, - const DefaultKeyedVector, DisplayDeviceState>& displays, - const Vector& changedDisplays, uint32_t flags, int originPid, - int originUid, uint64_t transactionId) override; - - // Intercept surface data - void saveSurfaceCreation(const sp& layer) override; - void saveSurfaceDeletion(const sp& layer) override; - void saveBufferUpdate(int32_t layerId, uint32_t width, uint32_t height, - uint64_t frameNumber) override; - - // Intercept display data - void saveDisplayCreation(const DisplayDeviceState& info) override; - void saveDisplayDeletion(int32_t sequenceId) override; - void savePowerModeUpdate(int32_t sequenceId, int32_t mode) override; - void saveVSyncEvent(nsecs_t timestamp) override; - -private: - // The creation increments of Surfaces and Displays do not contain enough information to capture - // the initial state of each object, so a transaction with all of the missing properties is - // performed at the initial snapshot for each display and surface. - void saveExistingDisplaysLocked( - const DefaultKeyedVector< wp, DisplayDeviceState>& displays); - void saveExistingSurfacesLocked(const SortedVector>& layers); - void addInitialSurfaceStateLocked(Increment* increment, const sp& layer); - void addInitialDisplayStateLocked(Increment* increment, const DisplayDeviceState& display); - - status_t writeProtoFileLocked(); - const sp getLayer(const wp& weakHandle) const; - int32_t getLayerId(const sp& layer) const; - int32_t getLayerIdFromWeakRef(const wp& layer) const; - int32_t getLayerIdFromHandle(const sp& weakHandle) const; - - Increment* createTraceIncrementLocked(); - void addSurfaceCreationLocked(Increment* increment, const sp& layer); - void addSurfaceDeletionLocked(Increment* increment, const sp& layer); - void addBufferUpdateLocked(Increment* increment, int32_t layerId, uint32_t width, - uint32_t height, uint64_t frameNumber); - void addVSyncUpdateLocked(Increment* increment, nsecs_t timestamp); - void addDisplayCreationLocked(Increment* increment, const DisplayDeviceState& info); - void addDisplayDeletionLocked(Increment* increment, int32_t sequenceId); - void addPowerModeUpdateLocked(Increment* increment, int32_t sequenceId, int32_t mode); - - // Add surface transactions to the trace - SurfaceChange* createSurfaceChangeLocked(Transaction* transaction, int32_t layerId); - void setProtoRectLocked(Rectangle* protoRect, const Rect& rect); - void addPositionLocked(Transaction* transaction, int32_t layerId, float x, float y); - void addDepthLocked(Transaction* transaction, int32_t layerId, uint32_t z); - void addSizeLocked(Transaction* transaction, int32_t layerId, uint32_t w, uint32_t h); - void addAlphaLocked(Transaction* transaction, int32_t layerId, float alpha); - void addMatrixLocked(Transaction* transaction, int32_t layerId, - const layer_state_t::matrix22_t& matrix); - void addTransparentRegionLocked(Transaction* transaction, int32_t layerId, - const Region& transRegion); - void addFlagsLocked(Transaction* transaction, int32_t layerId, uint8_t flags, uint8_t mask); - void addLayerStackLocked(Transaction* transaction, int32_t layerId, ui::LayerStack); - void addCropLocked(Transaction* transaction, int32_t layerId, const Rect& rect); - void addCornerRadiusLocked(Transaction* transaction, int32_t layerId, float cornerRadius); - void addBackgroundBlurRadiusLocked(Transaction* transaction, int32_t layerId, - int32_t backgroundBlurRadius); - void addBlurRegionsLocked(Transaction* transaction, int32_t layerId, - const std::vector& effectRegions); - void addSurfaceChangesLocked(Transaction* transaction, const layer_state_t& state); - void addTransactionLocked(Increment* increment, const Vector& stateUpdates, - const DefaultKeyedVector, DisplayDeviceState>& displays, - const Vector& changedDisplays, - uint32_t transactionFlags, int originPid, int originUid, - uint64_t transactionId); - void addReparentLocked(Transaction* transaction, int32_t layerId, int32_t parentId); - void addRelativeParentLocked(Transaction* transaction, int32_t layerId, int32_t parentId, - int z); - void addShadowRadiusLocked(Transaction* transaction, int32_t layerId, float shadowRadius); - void addTrustedOverlayLocked(Transaction* transaction, int32_t layerId, bool isTrustedOverlay); - - // Add display transactions to the trace - DisplayChange* createDisplayChangeLocked(Transaction* transaction, int32_t sequenceId); - void addDisplaySurfaceLocked(Transaction* transaction, int32_t sequenceId, - const sp& surface); - void addDisplayLayerStackLocked(Transaction* transaction, int32_t sequenceId, ui::LayerStack); - void addDisplayFlagsLocked(Transaction* transaction, int32_t sequenceId, uint32_t flags); - void addDisplaySizeLocked(Transaction* transaction, int32_t sequenceId, uint32_t w, - uint32_t h); - void addDisplayProjectionLocked(Transaction* transaction, int32_t sequenceId, - int32_t orientation, const Rect& viewport, const Rect& frame); - void addDisplayChangesLocked(Transaction* transaction, - const DisplayState& state, int32_t sequenceId); - - // Add transaction origin to trace - void setTransactionOriginLocked(Transaction* transaction, int32_t pid, int32_t uid); - - bool mEnabled {false}; - std::string mOutputFileName {DEFAULT_FILENAME}; - std::mutex mTraceMutex {}; - Trace mTrace {}; - std::mutex mListenersMutex; - std::map, sp> mTraceToggledListeners - GUARDED_BY(mListenersMutex); -}; - -} // namespace impl - -} // namespace android - -#endif // ANDROID_SURFACEINTERCEPTOR_H diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index 88171785c7..963f7b6aff 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -47,10 +47,6 @@ public: return std::make_unique(); } - sp createSurfaceInterceptor() override { - return sp::make(); - } - sp createStartPropertySetThread( bool /* timestampPropertyValue */) override { return sp(); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 2f8062ec2f..03630c9db5 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -46,7 +46,6 @@ #include "StartPropertySetThread.h" #include "SurfaceFlinger.h" #include "SurfaceFlingerDefaultFactory.h" -#include "SurfaceInterceptor.h" #include "ThreadContext.h" #include "TimeStats/TimeStats.h" @@ -60,7 +59,6 @@ #include "tests/unittests/mock/MockFrameTimeline.h" #include "tests/unittests/mock/MockFrameTracer.h" #include "tests/unittests/mock/MockNativeWindowSurface.h" -#include "tests/unittests/mock/MockSurfaceInterceptor.h" #include "tests/unittests/mock/MockTimeStats.h" #include "tests/unittests/mock/MockVSyncTracker.h" #include "tests/unittests/mock/MockVsyncController.h" @@ -316,10 +314,6 @@ public: return nullptr; } - sp createSurfaceInterceptor() override { - return sp::make(); - } - sp createStartPropertySetThread(bool timestampPropertyValue) override { return sp::make(timestampPropertyValue); } @@ -765,12 +759,10 @@ public: /* Read-write access to private data to set up preconditions and assert * post-conditions. */ - auto& mutableSupportsWideColor() { return mFlinger->mSupportsWideColor; } auto& mutableCurrentState() { return mFlinger->mCurrentState; } auto& mutableDisplays() { return mFlinger->mDisplays; } auto& mutableDrawingState() { return mFlinger->mDrawingState; } - auto& mutableInterceptor() { return mFlinger->mInterceptor; } auto fromHandle(const sp &handle) { return mFlinger->fromHandle(handle); } @@ -778,7 +770,6 @@ public: mutableDisplays().clear(); mutableCurrentState().displays.clear(); mutableDrawingState().displays.clear(); - mutableInterceptor().clear(); mFlinger->mScheduler.reset(); mFlinger->mCompositionEngine->setHwComposer(std::unique_ptr()); mFlinger->mCompositionEngine->setRenderEngine( diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index a949440f41..2614288016 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -92,7 +92,7 @@ void SchedulerFuzzer::fuzzEventThread() { const auto getVsyncPeriod = [](uid_t /* uid */) { return kSyncPeriod.count(); }; std::unique_ptr thread = std::make_unique< android::impl::EventThread>(std::move(std::make_unique()), nullptr, - nullptr, nullptr, getVsyncPeriod); + nullptr, getVsyncPeriod); thread->onHotplugReceived(getPhysicalDisplayId(), mFdp.ConsumeBool()); sp connection = diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp index 13ce65d4af..71b1c0eeb1 100644 --- a/services/surfaceflinger/tests/Android.bp +++ b/services/surfaceflinger/tests/Android.bp @@ -56,13 +56,11 @@ cc_test { "SetFrameRateOverride_test.cpp", "SetGeometry_test.cpp", "Stress_test.cpp", - "SurfaceInterceptor_test.cpp", "VirtualDisplay_test.cpp", "WindowInfosListener_test.cpp", ], data: ["SurfaceFlinger_test.filter"], static_libs: [ - "libtrace_proto", "liblayers_proto", "android.hardware.graphics.composer@2.1", ], diff --git a/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp b/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp deleted file mode 100644 index 7166d413f1..0000000000 --- a/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp +++ /dev/null @@ -1,961 +0,0 @@ -/* - * Copyright (C) 2016 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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" -#pragma clang diagnostic ignored "-Wextra" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace android { - -using Transaction = SurfaceComposerClient::Transaction; -using SurfaceChange = surfaceflinger::SurfaceChange; -using Trace = surfaceflinger::Trace; -using Increment = surfaceflinger::Increment; - -constexpr uint32_t BUFFER_UPDATES = 18; -constexpr uint32_t LAYER_UPDATE = INT_MAX - 2; -constexpr uint32_t SIZE_UPDATE = 134; -constexpr uint32_t STACK_UPDATE = 1; -constexpr int32_t RELATIVE_Z = 42; -constexpr float ALPHA_UPDATE = 0.29f; -constexpr float CORNER_RADIUS_UPDATE = 0.2f; -constexpr int BACKGROUND_BLUR_RADIUS_UPDATE = 24; -constexpr float POSITION_UPDATE = 121; -const Rect CROP_UPDATE(16, 16, 32, 32); -const float SHADOW_RADIUS_UPDATE = 35.0f; -std::vector BLUR_REGIONS_UPDATE; - -const String8 DISPLAY_NAME("SurfaceInterceptor Display Test"); -constexpr auto TEST_BG_SURFACE_NAME = "BG Interceptor Test Surface"; -constexpr auto TEST_FG_SURFACE_NAME = "FG Interceptor Test Surface"; -constexpr auto LAYER_NAME = "Layer Create and Delete Test"; - -constexpr auto DEFAULT_FILENAME = "/data/misc/wmtrace/transaction_trace.winscope"; - -// Fill an RGBA_8888 formatted surface with a single color. -static void fillSurfaceRGBA8(const sp& sc, uint8_t r, uint8_t g, uint8_t b) { - ANativeWindow_Buffer outBuffer; - sp s = sc->getSurface(); - ASSERT_TRUE(s != nullptr); - ASSERT_EQ(NO_ERROR, s->lock(&outBuffer, nullptr)); - uint8_t* img = reinterpret_cast(outBuffer.bits); - for (int y = 0; y < outBuffer.height; y++) { - for (int x = 0; x < outBuffer.width; x++) { - uint8_t* pixel = img + (4 * (y*outBuffer.stride + x)); - pixel[0] = r; - pixel[1] = g; - pixel[2] = b; - pixel[3] = 255; - } - } - ASSERT_EQ(NO_ERROR, s->unlockAndPost()); -} - -static status_t readProtoFile(Trace* trace) { - status_t err = NO_ERROR; - - int fd = open(DEFAULT_FILENAME, O_RDONLY); - { - google::protobuf::io::FileInputStream f(fd); - if (fd && !trace->ParseFromZeroCopyStream(&f)) { - err = PERMISSION_DENIED; - } - } - close(fd); - - return err; -} - -static void enableInterceptor() { - system("service call SurfaceFlinger 1020 i32 1 > /dev/null"); -} - -static void disableInterceptor() { - system("service call SurfaceFlinger 1020 i32 0 > /dev/null"); -} - -std::string getUniqueName(const std::string& name, const Increment& increment) { - return base::StringPrintf("%s#%d", name.c_str(), increment.surface_creation().id()); -} - -int32_t getSurfaceId(const Trace& capturedTrace, const std::string& surfaceName) { - int32_t layerId = 0; - for (const auto& increment : capturedTrace.increment()) { - if (increment.increment_case() == increment.kSurfaceCreation) { - if (increment.surface_creation().name() == getUniqueName(surfaceName, increment)) { - layerId = increment.surface_creation().id(); - } - } - } - return layerId; -} - -int32_t getDisplayId(const Trace& capturedTrace, const std::string& displayName) { - int32_t displayId = 0; - for (const auto& increment : capturedTrace.increment()) { - if (increment.increment_case() == increment.kDisplayCreation) { - if (increment.display_creation().name() == displayName) { - displayId = increment.display_creation().id(); - break; - } - } - } - return displayId; -} - -class SurfaceInterceptorTest : public ::testing::Test { -protected: - void SetUp() override { - // Allow SurfaceInterceptor write to /data - system("setenforce 0"); - - mComposerClient = sp::make(); - ASSERT_EQ(NO_ERROR, mComposerClient->initCheck()); - GTEST_SKIP(); - } - - void TearDown() override { - mComposerClient->dispose(); - mBGSurfaceControl.clear(); - mFGSurfaceControl.clear(); - mComposerClient.clear(); - system("setenforce 1"); - } - - sp mComposerClient; - sp mBGSurfaceControl; - sp mFGSurfaceControl; - int32_t mBGLayerId; - int32_t mFGLayerId; - -public: - using TestTransactionAction = void (SurfaceInterceptorTest::*)(Transaction&); - using TestAction = void (SurfaceInterceptorTest::*)(); - using TestBooleanVerification = bool (SurfaceInterceptorTest::*)(const Trace&); - using TestVerification = void (SurfaceInterceptorTest::*)(const Trace&); - - void setupBackgroundSurface(); - void preProcessTrace(const Trace& trace); - - // captureTest will enable SurfaceInterceptor, setup background surface, - // disable SurfaceInterceptor, collect the trace and process the trace for - // id of background surface before further verification. - void captureTest(TestTransactionAction action, TestBooleanVerification verification); - void captureTest(TestTransactionAction action, SurfaceChange::SurfaceChangeCase changeCase); - void captureTest(TestTransactionAction action, Increment::IncrementCase incrementCase); - void captureTest(TestAction action, TestBooleanVerification verification); - void captureTest(TestAction action, TestVerification verification); - void runInTransaction(TestTransactionAction action); - - // Verification of changes to a surface - bool positionUpdateFound(const SurfaceChange& change, bool foundPosition); - bool sizeUpdateFound(const SurfaceChange& change, bool foundSize); - bool alphaUpdateFound(const SurfaceChange& change, bool foundAlpha); - bool layerUpdateFound(const SurfaceChange& change, bool foundLayer); - bool cropUpdateFound(const SurfaceChange& change, bool foundCrop); - bool cornerRadiusUpdateFound(const SurfaceChange& change, bool foundCornerRadius); - bool backgroundBlurRadiusUpdateFound(const SurfaceChange& change, - bool foundBackgroundBlurRadius); - bool blurRegionsUpdateFound(const SurfaceChange& change, bool foundBlurRegions); - bool matrixUpdateFound(const SurfaceChange& change, bool foundMatrix); - bool scalingModeUpdateFound(const SurfaceChange& change, bool foundScalingMode); - bool transparentRegionHintUpdateFound(const SurfaceChange& change, bool foundTransparentRegion); - bool layerStackUpdateFound(const SurfaceChange& change, bool foundLayerStack); - bool hiddenFlagUpdateFound(const SurfaceChange& change, bool foundHiddenFlag); - bool opaqueFlagUpdateFound(const SurfaceChange& change, bool foundOpaqueFlag); - bool secureFlagUpdateFound(const SurfaceChange& change, bool foundSecureFlag); - bool reparentUpdateFound(const SurfaceChange& change, bool found); - bool relativeParentUpdateFound(const SurfaceChange& change, bool found); - bool shadowRadiusUpdateFound(const SurfaceChange& change, bool found); - bool trustedOverlayUpdateFound(const SurfaceChange& change, bool found); - bool surfaceUpdateFound(const Trace& trace, SurfaceChange::SurfaceChangeCase changeCase); - - // Find all of the updates in the single trace - void assertAllUpdatesFound(const Trace& trace); - - // Verification of creation and deletion of a surface - bool surfaceCreationFound(const Increment& increment, bool foundSurface); - bool surfaceDeletionFound(const Increment& increment, const int32_t targetId, - bool foundSurface); - bool displayCreationFound(const Increment& increment, bool foundDisplay); - bool displayDeletionFound(const Increment& increment, const int32_t targetId, - bool foundDisplay); - bool singleIncrementFound(const Trace& trace, Increment::IncrementCase incrementCase); - - // Verification of buffer updates - bool bufferUpdatesFound(const Trace& trace); - - // Perform each of the possible changes to a surface - void positionUpdate(Transaction&); - void sizeUpdate(Transaction&); - void alphaUpdate(Transaction&); - void layerUpdate(Transaction&); - void cropUpdate(Transaction&); - void cornerRadiusUpdate(Transaction&); - void backgroundBlurRadiusUpdate(Transaction&); - void blurRegionsUpdate(Transaction&); - void matrixUpdate(Transaction&); - void transparentRegionHintUpdate(Transaction&); - void layerStackUpdate(Transaction&); - void hiddenFlagUpdate(Transaction&); - void opaqueFlagUpdate(Transaction&); - void secureFlagUpdate(Transaction&); - void reparentUpdate(Transaction&); - void relativeParentUpdate(Transaction&); - void shadowRadiusUpdate(Transaction&); - void trustedOverlayUpdate(Transaction&); - void surfaceCreation(Transaction&); - void displayCreation(Transaction&); - void displayDeletion(Transaction&); - - void nBufferUpdates(); - void runAllUpdates(); - -private: - void captureInTransaction(TestTransactionAction action, Trace*); - void capture(TestAction action, Trace*); -}; - -void SurfaceInterceptorTest::captureInTransaction(TestTransactionAction action, Trace* outTrace) { - enableInterceptor(); - setupBackgroundSurface(); - runInTransaction(action); - disableInterceptor(); - ASSERT_EQ(NO_ERROR, readProtoFile(outTrace)); - preProcessTrace(*outTrace); -} - -void SurfaceInterceptorTest::capture(TestAction action, Trace* outTrace) { - enableInterceptor(); - setupBackgroundSurface(); - (this->*action)(); - disableInterceptor(); - ASSERT_EQ(NO_ERROR, readProtoFile(outTrace)); - preProcessTrace(*outTrace); -} - -void SurfaceInterceptorTest::setupBackgroundSurface() { - const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); - ASSERT_FALSE(ids.empty()); - const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); - ASSERT_FALSE(display == nullptr); - - ui::DisplayMode mode; - ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); - const ui::Size& resolution = mode.resolution; - - // Background surface - mBGSurfaceControl = - mComposerClient->createSurface(String8(TEST_BG_SURFACE_NAME), resolution.getWidth(), - resolution.getHeight(), PIXEL_FORMAT_RGBA_8888, 0); - ASSERT_TRUE(mBGSurfaceControl != nullptr); - ASSERT_TRUE(mBGSurfaceControl->isValid()); - - // Foreground surface - mFGSurfaceControl = - mComposerClient->createSurface(String8(TEST_FG_SURFACE_NAME), resolution.getWidth(), - resolution.getHeight(), PIXEL_FORMAT_RGBA_8888, 0); - ASSERT_TRUE(mFGSurfaceControl != nullptr); - ASSERT_TRUE(mFGSurfaceControl->isValid()); - - Transaction t; - t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK); - ASSERT_EQ(NO_ERROR, - t.setLayer(mBGSurfaceControl, INT_MAX - 3) - .show(mBGSurfaceControl) - .setLayer(mFGSurfaceControl, INT_MAX - 3) - .show(mFGSurfaceControl) - .apply()); -} - -void SurfaceInterceptorTest::preProcessTrace(const Trace& trace) { - mBGLayerId = getSurfaceId(trace, TEST_BG_SURFACE_NAME); - mFGLayerId = getSurfaceId(trace, TEST_FG_SURFACE_NAME); -} - -void SurfaceInterceptorTest::captureTest(TestTransactionAction action, - TestBooleanVerification verification) { - Trace capturedTrace; - captureInTransaction(action, &capturedTrace); - ASSERT_TRUE((this->*verification)(capturedTrace)); -} - -void SurfaceInterceptorTest::captureTest(TestTransactionAction action, - Increment::IncrementCase incrementCase) { - Trace capturedTrace; - captureInTransaction(action, &capturedTrace); - ASSERT_TRUE(singleIncrementFound(capturedTrace, incrementCase)); -} - -void SurfaceInterceptorTest::captureTest(TestTransactionAction action, - SurfaceChange::SurfaceChangeCase changeCase) { - Trace capturedTrace; - captureInTransaction(action, &capturedTrace); - ASSERT_TRUE(surfaceUpdateFound(capturedTrace, changeCase)); -} - -void SurfaceInterceptorTest::captureTest(TestAction action, TestBooleanVerification verification) { - Trace capturedTrace; - capture(action, &capturedTrace); - ASSERT_TRUE((this->*verification)(capturedTrace)); -} - -void SurfaceInterceptorTest::captureTest(TestAction action, TestVerification verification) { - Trace capturedTrace; - capture(action, &capturedTrace); - (this->*verification)(capturedTrace); -} - -void SurfaceInterceptorTest::runInTransaction(TestTransactionAction action) { - Transaction t; - (this->*action)(t); - t.apply(true); -} - -void SurfaceInterceptorTest::positionUpdate(Transaction& t) { - t.setPosition(mBGSurfaceControl, POSITION_UPDATE, POSITION_UPDATE); -} - -void SurfaceInterceptorTest::sizeUpdate(Transaction&) {} - -void SurfaceInterceptorTest::alphaUpdate(Transaction& t) { - t.setAlpha(mBGSurfaceControl, ALPHA_UPDATE); -} - -void SurfaceInterceptorTest::cornerRadiusUpdate(Transaction& t) { - t.setCornerRadius(mBGSurfaceControl, CORNER_RADIUS_UPDATE); -} - -void SurfaceInterceptorTest::backgroundBlurRadiusUpdate(Transaction& t) { - t.setBackgroundBlurRadius(mBGSurfaceControl, BACKGROUND_BLUR_RADIUS_UPDATE); -} - -void SurfaceInterceptorTest::blurRegionsUpdate(Transaction& t) { - BLUR_REGIONS_UPDATE.empty(); - BLUR_REGIONS_UPDATE.push_back(BlurRegion()); - t.setBlurRegions(mBGSurfaceControl, BLUR_REGIONS_UPDATE); -} - -void SurfaceInterceptorTest::layerUpdate(Transaction& t) { - t.setLayer(mBGSurfaceControl, LAYER_UPDATE); -} - -void SurfaceInterceptorTest::cropUpdate(Transaction& t) { - t.setCrop(mBGSurfaceControl, CROP_UPDATE); -} - -void SurfaceInterceptorTest::matrixUpdate(Transaction& t) { - t.setMatrix(mBGSurfaceControl, M_SQRT1_2, M_SQRT1_2, -M_SQRT1_2, M_SQRT1_2); -} - -void SurfaceInterceptorTest::transparentRegionHintUpdate(Transaction& t) { - Region region(CROP_UPDATE); - t.setTransparentRegionHint(mBGSurfaceControl, region); -} - -void SurfaceInterceptorTest::layerStackUpdate(Transaction& t) { - t.setLayerStack(mBGSurfaceControl, ui::LayerStack::fromValue(STACK_UPDATE)); -} - -void SurfaceInterceptorTest::hiddenFlagUpdate(Transaction& t) { - t.setFlags(mBGSurfaceControl, layer_state_t::eLayerHidden, layer_state_t::eLayerHidden); -} - -void SurfaceInterceptorTest::opaqueFlagUpdate(Transaction& t) { - t.setFlags(mBGSurfaceControl, layer_state_t::eLayerOpaque, layer_state_t::eLayerOpaque); -} - -void SurfaceInterceptorTest::secureFlagUpdate(Transaction& t) { - t.setFlags(mBGSurfaceControl, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure); -} - -void SurfaceInterceptorTest::reparentUpdate(Transaction& t) { - t.reparent(mBGSurfaceControl, mFGSurfaceControl); -} - -void SurfaceInterceptorTest::relativeParentUpdate(Transaction& t) { - t.setRelativeLayer(mBGSurfaceControl, mFGSurfaceControl, RELATIVE_Z); -} - -void SurfaceInterceptorTest::shadowRadiusUpdate(Transaction& t) { - t.setShadowRadius(mBGSurfaceControl, SHADOW_RADIUS_UPDATE); -} - -void SurfaceInterceptorTest::trustedOverlayUpdate(Transaction& t) { - t.setTrustedOverlay(mBGSurfaceControl, true); -} - -void SurfaceInterceptorTest::displayCreation(Transaction&) { - sp testDisplay = SurfaceComposerClient::createDisplay(DISPLAY_NAME, false); - SurfaceComposerClient::destroyDisplay(testDisplay); -} - -void SurfaceInterceptorTest::displayDeletion(Transaction&) { - sp testDisplay = SurfaceComposerClient::createDisplay(DISPLAY_NAME, false); - SurfaceComposerClient::destroyDisplay(testDisplay); -} - -void SurfaceInterceptorTest::runAllUpdates() { - runInTransaction(&SurfaceInterceptorTest::positionUpdate); - runInTransaction(&SurfaceInterceptorTest::sizeUpdate); - runInTransaction(&SurfaceInterceptorTest::alphaUpdate); - runInTransaction(&SurfaceInterceptorTest::cornerRadiusUpdate); - runInTransaction(&SurfaceInterceptorTest::backgroundBlurRadiusUpdate); - runInTransaction(&SurfaceInterceptorTest::blurRegionsUpdate); - runInTransaction(&SurfaceInterceptorTest::layerUpdate); - runInTransaction(&SurfaceInterceptorTest::cropUpdate); - runInTransaction(&SurfaceInterceptorTest::matrixUpdate); - runInTransaction(&SurfaceInterceptorTest::transparentRegionHintUpdate); - runInTransaction(&SurfaceInterceptorTest::layerStackUpdate); - runInTransaction(&SurfaceInterceptorTest::hiddenFlagUpdate); - runInTransaction(&SurfaceInterceptorTest::opaqueFlagUpdate); - runInTransaction(&SurfaceInterceptorTest::secureFlagUpdate); - runInTransaction(&SurfaceInterceptorTest::reparentUpdate); - runInTransaction(&SurfaceInterceptorTest::relativeParentUpdate); - runInTransaction(&SurfaceInterceptorTest::shadowRadiusUpdate); - runInTransaction(&SurfaceInterceptorTest::trustedOverlayUpdate); -} - -void SurfaceInterceptorTest::surfaceCreation(Transaction&) { - mComposerClient->createSurface(String8(LAYER_NAME), SIZE_UPDATE, SIZE_UPDATE, - PIXEL_FORMAT_RGBA_8888, 0); -} - -void SurfaceInterceptorTest::nBufferUpdates() { - std::random_device rd; - std::mt19937_64 gen(rd()); - // This makes testing fun - std::uniform_int_distribution dis; - for (uint32_t i = 0; i < BUFFER_UPDATES; ++i) { - fillSurfaceRGBA8(mBGSurfaceControl, dis(gen), dis(gen), dis(gen)); - } -} - -bool SurfaceInterceptorTest::positionUpdateFound(const SurfaceChange& change, bool foundPosition) { - // There should only be one position transaction with x and y = POSITION_UPDATE - bool hasX(change.position().x() == POSITION_UPDATE); - bool hasY(change.position().y() == POSITION_UPDATE); - if (hasX && hasY && !foundPosition) { - foundPosition = true; - } else if (hasX && hasY && foundPosition) { - // Failed because the position update was found a second time - [] () { FAIL(); }(); - } - return foundPosition; -} - -bool SurfaceInterceptorTest::sizeUpdateFound(const SurfaceChange&, bool) { - return true; -} - -bool SurfaceInterceptorTest::alphaUpdateFound(const SurfaceChange& change, bool foundAlpha) { - bool hasAlpha(change.alpha().alpha() == ALPHA_UPDATE); - if (hasAlpha && !foundAlpha) { - foundAlpha = true; - } else if (hasAlpha && foundAlpha) { - [] () { FAIL(); }(); - } - return foundAlpha; -} - -bool SurfaceInterceptorTest::cornerRadiusUpdateFound(const SurfaceChange &change, - bool foundCornerRadius) { - bool hasCornerRadius(change.corner_radius().corner_radius() == CORNER_RADIUS_UPDATE); - if (hasCornerRadius && !foundCornerRadius) { - foundCornerRadius = true; - } else if (hasCornerRadius && foundCornerRadius) { - [] () { FAIL(); }(); - } - return foundCornerRadius; -} - -bool SurfaceInterceptorTest::backgroundBlurRadiusUpdateFound(const SurfaceChange& change, - bool foundBackgroundBlur) { - bool hasBackgroundBlur(change.background_blur_radius().background_blur_radius() == - BACKGROUND_BLUR_RADIUS_UPDATE); - if (hasBackgroundBlur && !foundBackgroundBlur) { - foundBackgroundBlur = true; - } else if (hasBackgroundBlur && foundBackgroundBlur) { - []() { FAIL(); }(); - } - return foundBackgroundBlur; -} - -bool SurfaceInterceptorTest::blurRegionsUpdateFound(const SurfaceChange& change, - bool foundBlurRegions) { - bool hasBlurRegions(change.blur_regions().blur_regions_size() == BLUR_REGIONS_UPDATE.size()); - if (hasBlurRegions && !foundBlurRegions) { - foundBlurRegions = true; - } else if (hasBlurRegions && foundBlurRegions) { - []() { FAIL(); }(); - } - return foundBlurRegions; -} - -bool SurfaceInterceptorTest::layerUpdateFound(const SurfaceChange& change, bool foundLayer) { - bool hasLayer(change.layer().layer() == LAYER_UPDATE); - if (hasLayer && !foundLayer) { - foundLayer = true; - } else if (hasLayer && foundLayer) { - [] () { FAIL(); }(); - } - return foundLayer; -} - -bool SurfaceInterceptorTest::cropUpdateFound(const SurfaceChange& change, bool foundCrop) { - bool hasLeft(change.crop().rectangle().left() == CROP_UPDATE.left); - bool hasTop(change.crop().rectangle().top() == CROP_UPDATE.top); - bool hasRight(change.crop().rectangle().right() == CROP_UPDATE.right); - bool hasBottom(change.crop().rectangle().bottom() == CROP_UPDATE.bottom); - if (hasLeft && hasRight && hasTop && hasBottom && !foundCrop) { - foundCrop = true; - } else if (hasLeft && hasRight && hasTop && hasBottom && foundCrop) { - [] () { FAIL(); }(); - } - return foundCrop; -} - -bool SurfaceInterceptorTest::matrixUpdateFound(const SurfaceChange& change, bool foundMatrix) { - bool hasSx((float)change.matrix().dsdx() == (float)M_SQRT1_2); - bool hasTx((float)change.matrix().dtdx() == (float)M_SQRT1_2); - bool hasSy((float)change.matrix().dsdy() == (float)M_SQRT1_2); - bool hasTy((float)change.matrix().dtdy() == (float)-M_SQRT1_2); - if (hasSx && hasTx && hasSy && hasTy && !foundMatrix) { - foundMatrix = true; - } else if (hasSx && hasTx && hasSy && hasTy && foundMatrix) { - [] () { FAIL(); }(); - } - return foundMatrix; -} - -bool SurfaceInterceptorTest::transparentRegionHintUpdateFound(const SurfaceChange& change, - bool foundTransparentRegion) { - auto traceRegion = change.transparent_region_hint().region(0); - bool hasLeft(traceRegion.left() == CROP_UPDATE.left); - bool hasTop(traceRegion.top() == CROP_UPDATE.top); - bool hasRight(traceRegion.right() == CROP_UPDATE.right); - bool hasBottom(traceRegion.bottom() == CROP_UPDATE.bottom); - if (hasLeft && hasRight && hasTop && hasBottom && !foundTransparentRegion) { - foundTransparentRegion = true; - } else if (hasLeft && hasRight && hasTop && hasBottom && foundTransparentRegion) { - [] () { FAIL(); }(); - } - return foundTransparentRegion; -} - -bool SurfaceInterceptorTest::layerStackUpdateFound(const SurfaceChange& change, - bool foundLayerStack) { - bool hasLayerStackUpdate(change.layer_stack().layer_stack() == STACK_UPDATE); - if (hasLayerStackUpdate && !foundLayerStack) { - foundLayerStack = true; - } else if (hasLayerStackUpdate && foundLayerStack) { - [] () { FAIL(); }(); - } - return foundLayerStack; -} - -bool SurfaceInterceptorTest::hiddenFlagUpdateFound(const SurfaceChange& change, - bool foundHiddenFlag) { - bool hasHiddenFlag(change.hidden_flag().hidden_flag()); - if (hasHiddenFlag && !foundHiddenFlag) { - foundHiddenFlag = true; - } else if (hasHiddenFlag && foundHiddenFlag) { - [] () { FAIL(); }(); - } - return foundHiddenFlag; -} - -bool SurfaceInterceptorTest::opaqueFlagUpdateFound(const SurfaceChange& change, - bool foundOpaqueFlag) { - bool hasOpaqueFlag(change.opaque_flag().opaque_flag()); - if (hasOpaqueFlag && !foundOpaqueFlag) { - foundOpaqueFlag = true; - } else if (hasOpaqueFlag && foundOpaqueFlag) { - [] () { FAIL(); }(); - } - return foundOpaqueFlag; -} - -bool SurfaceInterceptorTest::secureFlagUpdateFound(const SurfaceChange& change, - bool foundSecureFlag) { - bool hasSecureFlag(change.secure_flag().secure_flag()); - if (hasSecureFlag && !foundSecureFlag) { - foundSecureFlag = true; - } else if (hasSecureFlag && foundSecureFlag) { - [] () { FAIL(); }(); - } - return foundSecureFlag; -} - -bool SurfaceInterceptorTest::reparentUpdateFound(const SurfaceChange& change, bool found) { - bool hasId(change.reparent().parent_id() == mFGLayerId); - if (hasId && !found) { - found = true; - } else if (hasId && found) { - []() { FAIL(); }(); - } - return found; -} - -bool SurfaceInterceptorTest::relativeParentUpdateFound(const SurfaceChange& change, bool found) { - bool hasId(change.relative_parent().relative_parent_id() == mFGLayerId); - if (hasId && !found) { - found = true; - } else if (hasId && found) { - []() { FAIL(); }(); - } - return found; -} - -bool SurfaceInterceptorTest::shadowRadiusUpdateFound(const SurfaceChange& change, - bool foundShadowRadius) { - bool hasShadowRadius(change.shadow_radius().radius() == SHADOW_RADIUS_UPDATE); - if (hasShadowRadius && !foundShadowRadius) { - foundShadowRadius = true; - } else if (hasShadowRadius && foundShadowRadius) { - []() { FAIL(); }(); - } - return foundShadowRadius; -} - -bool SurfaceInterceptorTest::trustedOverlayUpdateFound(const SurfaceChange& change, - bool foundTrustedOverlay) { - bool hasTrustedOverlay(change.trusted_overlay().is_trusted_overlay()); - if (hasTrustedOverlay && !foundTrustedOverlay) { - foundTrustedOverlay = true; - } else if (hasTrustedOverlay && foundTrustedOverlay) { - []() { FAIL(); }(); - } - return foundTrustedOverlay; -} - -bool SurfaceInterceptorTest::surfaceUpdateFound(const Trace& trace, - SurfaceChange::SurfaceChangeCase changeCase) { - bool foundUpdate = false; - for (const auto& increment : trace.increment()) { - if (increment.increment_case() == increment.kTransaction) { - for (const auto& change : increment.transaction().surface_change()) { - if (change.id() == mBGLayerId && change.SurfaceChange_case() == changeCase) { - switch (changeCase) { - case SurfaceChange::SurfaceChangeCase::kPosition: - // foundUpdate is sent for the tests to fail on duplicated increments - foundUpdate = positionUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kSize: - foundUpdate = sizeUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kAlpha: - foundUpdate = alphaUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kLayer: - foundUpdate = layerUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kCrop: - foundUpdate = cropUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kCornerRadius: - foundUpdate = cornerRadiusUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kBackgroundBlurRadius: - foundUpdate = backgroundBlurRadiusUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kBlurRegions: - foundUpdate = blurRegionsUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kMatrix: - foundUpdate = matrixUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kTransparentRegionHint: - foundUpdate = transparentRegionHintUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kLayerStack: - foundUpdate = layerStackUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kHiddenFlag: - foundUpdate = hiddenFlagUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kOpaqueFlag: - foundUpdate = opaqueFlagUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kSecureFlag: - foundUpdate = secureFlagUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kReparent: - foundUpdate = reparentUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kRelativeParent: - foundUpdate = relativeParentUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kShadowRadius: - foundUpdate = shadowRadiusUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::kTrustedOverlay: - foundUpdate = trustedOverlayUpdateFound(change, foundUpdate); - break; - case SurfaceChange::SurfaceChangeCase::SURFACECHANGE_NOT_SET: - break; - } - } - } - } - } - return foundUpdate; -} - -void SurfaceInterceptorTest::assertAllUpdatesFound(const Trace& trace) { - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kPosition)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kSize)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kAlpha)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kLayer)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kCrop)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kMatrix)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kTransparentRegionHint)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kLayerStack)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kHiddenFlag)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kOpaqueFlag)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kSecureFlag)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kReparent)); - ASSERT_TRUE(surfaceUpdateFound(trace, SurfaceChange::SurfaceChangeCase::kRelativeParent)); -} - -bool SurfaceInterceptorTest::surfaceCreationFound(const Increment& increment, bool foundSurface) { - bool isMatch(increment.surface_creation().name() == getUniqueName(LAYER_NAME, increment)); - if (isMatch && !foundSurface) { - foundSurface = true; - } else if (isMatch && foundSurface) { - [] () { FAIL(); }(); - } - return foundSurface; -} - -bool SurfaceInterceptorTest::surfaceDeletionFound(const Increment& increment, - const int32_t targetId, bool foundSurface) { - bool isMatch(increment.surface_deletion().id() == targetId); - if (isMatch && !foundSurface) { - foundSurface = true; - } else if (isMatch && foundSurface) { - [] () { FAIL(); }(); - } - return foundSurface; -} - -bool SurfaceInterceptorTest::displayCreationFound(const Increment& increment, bool foundDisplay) { - bool isMatch(increment.display_creation().name() == DISPLAY_NAME.string() && - !increment.display_creation().is_secure()); - if (isMatch && !foundDisplay) { - foundDisplay = true; - } else if (isMatch && foundDisplay) { - [] () { FAIL(); }(); - } - return foundDisplay; -} - -bool SurfaceInterceptorTest::displayDeletionFound(const Increment& increment, - const int32_t targetId, bool foundDisplay) { - bool isMatch(increment.display_deletion().id() == targetId); - if (isMatch && !foundDisplay) { - foundDisplay = true; - } else if (isMatch && foundDisplay) { - [] () { FAIL(); }(); - } - return foundDisplay; -} - -bool SurfaceInterceptorTest::singleIncrementFound(const Trace& trace, - Increment::IncrementCase incrementCase) { - bool foundIncrement = false; - for (const auto& increment : trace.increment()) { - if (increment.increment_case() == incrementCase) { - int32_t targetId = 0; - switch (incrementCase) { - case Increment::IncrementCase::kSurfaceCreation: - foundIncrement = surfaceCreationFound(increment, foundIncrement); - break; - case Increment::IncrementCase::kSurfaceDeletion: - // Find the id of created surface. - targetId = getSurfaceId(trace, LAYER_NAME); - foundIncrement = surfaceDeletionFound(increment, targetId, foundIncrement); - break; - case Increment::IncrementCase::kDisplayCreation: - foundIncrement = displayCreationFound(increment, foundIncrement); - break; - case Increment::IncrementCase::kDisplayDeletion: - // Find the id of created display. - targetId = getDisplayId(trace, DISPLAY_NAME.string()); - foundIncrement = displayDeletionFound(increment, targetId, foundIncrement); - break; - default: - /* code */ - break; - } - } - } - return foundIncrement; -} - -bool SurfaceInterceptorTest::bufferUpdatesFound(const Trace& trace) { - uint32_t updates = 0; - for (const auto& inc : trace.increment()) { - if (inc.increment_case() == inc.kBufferUpdate && inc.buffer_update().id() == mBGLayerId) { - updates++; - } - } - return updates == BUFFER_UPDATES; -} - -TEST_F(SurfaceInterceptorTest, InterceptPositionUpdateWorks) { - captureTest(&SurfaceInterceptorTest::positionUpdate, - SurfaceChange::SurfaceChangeCase::kPosition); -} - -TEST_F(SurfaceInterceptorTest, InterceptSizeUpdateWorks) { - captureTest(&SurfaceInterceptorTest::sizeUpdate, SurfaceChange::SurfaceChangeCase::kSize); -} - -TEST_F(SurfaceInterceptorTest, InterceptAlphaUpdateWorks) { - captureTest(&SurfaceInterceptorTest::alphaUpdate, SurfaceChange::SurfaceChangeCase::kAlpha); -} - -TEST_F(SurfaceInterceptorTest, InterceptLayerUpdateWorks) { - captureTest(&SurfaceInterceptorTest::layerUpdate, SurfaceChange::SurfaceChangeCase::kLayer); -} - -TEST_F(SurfaceInterceptorTest, InterceptCropUpdateWorks) { - captureTest(&SurfaceInterceptorTest::cropUpdate, SurfaceChange::SurfaceChangeCase::kCrop); -} - -TEST_F(SurfaceInterceptorTest, InterceptCornerRadiusUpdateWorks) { - captureTest(&SurfaceInterceptorTest::cornerRadiusUpdate, - SurfaceChange::SurfaceChangeCase::kCornerRadius); -} - -TEST_F(SurfaceInterceptorTest, InterceptBackgroundBlurRadiusUpdateWorks) { - captureTest(&SurfaceInterceptorTest::backgroundBlurRadiusUpdate, - SurfaceChange::SurfaceChangeCase::kBackgroundBlurRadius); -} - -TEST_F(SurfaceInterceptorTest, InterceptBlurRegionsUpdateWorks) { - captureTest(&SurfaceInterceptorTest::blurRegionsUpdate, - SurfaceChange::SurfaceChangeCase::kBlurRegions); -} - -TEST_F(SurfaceInterceptorTest, InterceptMatrixUpdateWorks) { - captureTest(&SurfaceInterceptorTest::matrixUpdate, SurfaceChange::SurfaceChangeCase::kMatrix); -} - -TEST_F(SurfaceInterceptorTest, InterceptTransparentRegionHintUpdateWorks) { - captureTest(&SurfaceInterceptorTest::transparentRegionHintUpdate, - SurfaceChange::SurfaceChangeCase::kTransparentRegionHint); -} - -TEST_F(SurfaceInterceptorTest, InterceptLayerStackUpdateWorks) { - captureTest(&SurfaceInterceptorTest::layerStackUpdate, - SurfaceChange::SurfaceChangeCase::kLayerStack); -} - -TEST_F(SurfaceInterceptorTest, InterceptHiddenFlagUpdateWorks) { - captureTest(&SurfaceInterceptorTest::hiddenFlagUpdate, - SurfaceChange::SurfaceChangeCase::kHiddenFlag); -} - -TEST_F(SurfaceInterceptorTest, InterceptOpaqueFlagUpdateWorks) { - captureTest(&SurfaceInterceptorTest::opaqueFlagUpdate, - SurfaceChange::SurfaceChangeCase::kOpaqueFlag); -} - -TEST_F(SurfaceInterceptorTest, InterceptSecureFlagUpdateWorks) { - captureTest(&SurfaceInterceptorTest::secureFlagUpdate, - SurfaceChange::SurfaceChangeCase::kSecureFlag); -} - -TEST_F(SurfaceInterceptorTest, InterceptReparentUpdateWorks) { - captureTest(&SurfaceInterceptorTest::reparentUpdate, - SurfaceChange::SurfaceChangeCase::kReparent); -} - -TEST_F(SurfaceInterceptorTest, InterceptRelativeParentUpdateWorks) { - captureTest(&SurfaceInterceptorTest::relativeParentUpdate, - SurfaceChange::SurfaceChangeCase::kRelativeParent); -} - -TEST_F(SurfaceInterceptorTest, InterceptShadowRadiusUpdateWorks) { - captureTest(&SurfaceInterceptorTest::shadowRadiusUpdate, - SurfaceChange::SurfaceChangeCase::kShadowRadius); -} - -TEST_F(SurfaceInterceptorTest, InterceptTrustedOverlayUpdateWorks) { - captureTest(&SurfaceInterceptorTest::trustedOverlayUpdate, - SurfaceChange::SurfaceChangeCase::kTrustedOverlay); -} - -TEST_F(SurfaceInterceptorTest, InterceptAllUpdatesWorks) { - captureTest(&SurfaceInterceptorTest::runAllUpdates, - &SurfaceInterceptorTest::assertAllUpdatesFound); -} - -TEST_F(SurfaceInterceptorTest, InterceptSurfaceCreationWorks) { - captureTest(&SurfaceInterceptorTest::surfaceCreation, - Increment::IncrementCase::kSurfaceCreation); -} - -TEST_F(SurfaceInterceptorTest, InterceptDisplayCreationWorks) { - captureTest(&SurfaceInterceptorTest::displayCreation, - Increment::IncrementCase::kDisplayCreation); -} - -TEST_F(SurfaceInterceptorTest, InterceptDisplayDeletionWorks) { - enableInterceptor(); - runInTransaction(&SurfaceInterceptorTest::displayDeletion); - disableInterceptor(); - Trace capturedTrace; - ASSERT_EQ(NO_ERROR, readProtoFile(&capturedTrace)); - ASSERT_TRUE(singleIncrementFound(capturedTrace, Increment::IncrementCase::kDisplayDeletion)); -} - -// If the interceptor is enabled while buffer updates are being pushed, the interceptor should -// first create a snapshot of the existing displays and surfaces and then start capturing -// the buffer updates -TEST_F(SurfaceInterceptorTest, InterceptWhileBufferUpdatesWorks) { - setupBackgroundSurface(); - std::thread bufferUpdates(&SurfaceInterceptorTest::nBufferUpdates, this); - enableInterceptor(); - disableInterceptor(); - bufferUpdates.join(); - - Trace capturedTrace; - ASSERT_EQ(NO_ERROR, readProtoFile(&capturedTrace)); - const auto& firstIncrement = capturedTrace.mutable_increment(0); - ASSERT_EQ(firstIncrement->increment_case(), Increment::IncrementCase::kDisplayCreation); -} -} -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion -Wextra" diff --git a/services/surfaceflinger/tests/fakehwc/Android.bp b/services/surfaceflinger/tests/fakehwc/Android.bp index 06afdb10cc..0d40e7085a 100644 --- a/services/surfaceflinger/tests/fakehwc/Android.bp +++ b/services/surfaceflinger/tests/fakehwc/Android.bp @@ -53,7 +53,6 @@ cc_test { "libgmock", "libperfetto_client_experimental", "librenderengine", - "libtrace_proto", "libaidlcommonsupport", ], header_libs: [ diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index 4469df007a..d2b58137f0 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -34,7 +34,6 @@ filegroup { "mock/MockFrameTimeline.cpp", "mock/MockFrameTracer.cpp", "mock/MockNativeWindowSurface.cpp", - "mock/MockSurfaceInterceptor.cpp", "mock/MockTimeStats.cpp", "mock/MockVsyncController.cpp", "mock/MockVSyncTracker.cpp", @@ -168,7 +167,6 @@ cc_defaults { "libtimestats_atoms_proto", "libtimestats_proto", "libtonemap", - "libtrace_proto", "perfetto_trace_protos", ], shared_libs: [ diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp index abe32c9794..8b91c67982 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp @@ -50,7 +50,6 @@ DisplayTransactionTest::DisplayTransactionTest() { injectMockScheduler(); mFlinger.setupRenderEngine(std::unique_ptr(mRenderEngine)); - mFlinger.mutableInterceptor() = mSurfaceInterceptor; injectMockComposer(0); } diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h index 20e776f1f2..9cceb5e4df 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h @@ -49,7 +49,6 @@ #include "mock/DisplayHardware/MockPowerAdvisor.h" #include "mock/MockEventThread.h" #include "mock/MockNativeWindowSurface.h" -#include "mock/MockSurfaceInterceptor.h" #include "mock/MockVsyncController.h" #include "mock/system/window/MockNativeWindow.h" @@ -120,7 +119,6 @@ public: // to keep a reference to them for use in setting up call expectations. renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine(); Hwc2::mock::Composer* mComposer = nullptr; - sp mSurfaceInterceptor = sp::make(); mock::VsyncController* mVsyncController = new mock::VsyncController; mock::VSyncTracker* mVSyncTracker = new mock::VSyncTracker; diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp index 978afc510a..a5beaba286 100644 --- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp +++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp @@ -93,7 +93,6 @@ protected: void expectVSyncSetDurationCallReceived(std::chrono::nanoseconds expectedDuration, std::chrono::nanoseconds expectedReadyDuration); VSyncSource::Callback* expectVSyncSetCallbackCallReceived(); - void expectInterceptCallReceived(nsecs_t expectedTimestamp); void expectVsyncEventReceivedByConnection(const char* name, ConnectionEventRecorder& connectionEventRecorder, nsecs_t expectedTimestamp, unsigned expectedCount); @@ -114,7 +113,6 @@ protected: AsyncCallRecorder mVSyncSetDurationCallRecorder; AsyncCallRecorder mResyncCallRecorder; - AsyncCallRecorder mInterceptVSyncCallRecorder; AsyncCallRecorder mThrottleVsyncCallRecorder; ConnectionEventRecorder mConnectionEventCallRecorder{0}; ConnectionEventRecorder mThrottledConnectionEventCallRecorder{0}; @@ -181,7 +179,6 @@ void EventThreadTest::createThread(std::unique_ptr source) { mTokenManager = std::make_unique(); mThread = std::make_unique(std::move(source), mTokenManager.get(), - mInterceptVSyncCallRecorder.getInvocable(), throttleVsync, getVsyncPeriod); // EventThread should register itself as VSyncSource callback. @@ -219,12 +216,6 @@ VSyncSource::Callback* EventThreadTest::expectVSyncSetCallbackCallReceived() { return callbackSet.has_value() ? std::get<0>(callbackSet.value()) : nullptr; } -void EventThreadTest::expectInterceptCallReceived(nsecs_t expectedTimestamp) { - auto args = mInterceptVSyncCallRecorder.waitForCall(); - ASSERT_TRUE(args.has_value()); - EXPECT_EQ(expectedTimestamp, std::get<0>(args.value())); -} - void EventThreadTest::expectThrottleVsyncReceived(nsecs_t expectedTimestamp, uid_t uid) { auto args = mThrottleVsyncCallRecorder.waitForCall(); ASSERT_TRUE(args.has_value()); @@ -348,7 +339,6 @@ TEST_F(EventThreadTest, canCreateAndDestroyThreadWithNoEventsSent) { EXPECT_FALSE(mVSyncSetCallbackCallRecorder.waitForCall(0us).has_value()); EXPECT_FALSE(mVSyncSetDurationCallRecorder.waitForCall(0us).has_value()); EXPECT_FALSE(mResyncCallRecorder.waitForCall(0us).has_value()); - EXPECT_FALSE(mInterceptVSyncCallRecorder.waitForCall(0us).has_value()); EXPECT_FALSE(mConnectionEventCallRecorder.waitForCall(0us).has_value()); } @@ -374,17 +364,15 @@ TEST_F(EventThreadTest, requestNextVsyncPostsASingleVSyncEventToTheConnection) { expectVSyncSetEnabledCallReceived(true); // Use the received callback to signal a first vsync event. - // The interceptor should receive the event, as well as the connection. + // The throttler should receive the event, as well as the connection. mCallback->onVSyncEvent(123, {456, 789}); - expectInterceptCallReceived(123); expectThrottleVsyncReceived(456, mConnectionUid); expectVsyncEventReceivedByConnection(123, 1u); // Use the received callback to signal a second vsync event. - // The interceptor should receive the event, but the connection should + // The throttler should receive the event, but the connection should // not as it was only interested in the first. mCallback->onVSyncEvent(456, {123, 0}); - expectInterceptCallReceived(456); EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value()); EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value()); @@ -400,10 +388,9 @@ TEST_F(EventThreadTest, requestNextVsyncEventFrameTimelinesCorrect) { expectVSyncSetEnabledCallReceived(true); // Use the received callback to signal a vsync event. - // The interceptor should receive the event, as well as the connection. + // The throttler should receive the event, as well as the connection. VSyncSource::VSyncData vsyncData = {456, 789}; mCallback->onVSyncEvent(123, vsyncData); - expectInterceptCallReceived(123); expectVsyncEventFrameTimelinesCorrect(123, vsyncData); } @@ -477,10 +464,9 @@ TEST_F(EventThreadTest, setVsyncRateZeroPostsNoVSyncEventsToThatConnection) { expectVSyncSetEnabledCallReceived(true); // Send a vsync event. EventThread should then make a call to the - // interceptor, and the second connection. The first connection should not + // the second connection. The first connection should not // get the event. mCallback->onVSyncEvent(123, {456, 0}); - expectInterceptCallReceived(123); EXPECT_FALSE(firstConnectionEventRecorder.waitForUnexpectedCall().has_value()); expectVsyncEventReceivedByConnection("secondConnection", secondConnectionEventRecorder, 123, 1u); @@ -493,21 +479,18 @@ TEST_F(EventThreadTest, setVsyncRateOnePostsAllEventsToThatConnection) { expectVSyncSetEnabledCallReceived(true); // Send a vsync event. EventThread should then make a call to the - // interceptor, and the connection. + // throttler, and the connection. mCallback->onVSyncEvent(123, {456, 789}); - expectInterceptCallReceived(123); expectThrottleVsyncReceived(456, mConnectionUid); expectVsyncEventReceivedByConnection(123, 1u); // A second event should go to the same places. mCallback->onVSyncEvent(456, {123, 0}); - expectInterceptCallReceived(456); expectThrottleVsyncReceived(123, mConnectionUid); expectVsyncEventReceivedByConnection(456, 2u); // A third event should go to the same places. mCallback->onVSyncEvent(789, {777, 111}); - expectInterceptCallReceived(789); expectThrottleVsyncReceived(777, mConnectionUid); expectVsyncEventReceivedByConnection(789, 3u); } @@ -518,27 +501,23 @@ TEST_F(EventThreadTest, setVsyncRateTwoPostsEveryOtherEventToThatConnection) { // EventThread should enable vsync callbacks. expectVSyncSetEnabledCallReceived(true); - // The first event will be seen by the interceptor, and not the connection. + // The first event will not be seen by the connection. mCallback->onVSyncEvent(123, {456, 789}); - expectInterceptCallReceived(123); EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value()); EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value()); - // The second event will be seen by the interceptor and the connection. + // The second event will be seen by the connection. mCallback->onVSyncEvent(456, {123, 0}); - expectInterceptCallReceived(456); expectVsyncEventReceivedByConnection(456, 2u); EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value()); - // The third event will be seen by the interceptor, and not the connection. + // The third event will not be seen by the connection. mCallback->onVSyncEvent(789, {777, 744}); - expectInterceptCallReceived(789); EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value()); EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value()); - // The fourth event will be seen by the interceptor and the connection. + // The fourth event will be seen by the connection. mCallback->onVSyncEvent(101112, {7847, 86}); - expectInterceptCallReceived(101112); expectVsyncEventReceivedByConnection(101112, 4u); } @@ -551,9 +530,8 @@ TEST_F(EventThreadTest, connectionsRemovedIfInstanceDestroyed) { // Destroy the only (strong) reference to the connection. mConnection = nullptr; - // The first event will be seen by the interceptor, and not the connection. + // The first event will not be seen by the connection. mCallback->onVSyncEvent(123, {456, 789}); - expectInterceptCallReceived(123); EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value()); // EventThread should disable vsync callbacks @@ -568,16 +546,12 @@ TEST_F(EventThreadTest, connectionsRemovedIfEventDeliveryError) { // EventThread should enable vsync callbacks. expectVSyncSetEnabledCallReceived(true); - // The first event will be seen by the interceptor, and by the connection, - // which then returns an error. + // The first event will be seen by the connection, which then returns an error. mCallback->onVSyncEvent(123, {456, 789}); - expectInterceptCallReceived(123); expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 123, 1u); - // A subsequent event will be seen by the interceptor and not by the - // connection. + // A subsequent event will not be seen by the connection. mCallback->onVSyncEvent(456, {123, 0}); - expectInterceptCallReceived(456); EXPECT_FALSE(errorConnectionEventRecorder.waitForUnexpectedCall().has_value()); // EventThread should disable vsync callbacks with the second event @@ -599,10 +573,8 @@ TEST_F(EventThreadTest, tracksEventConnections) { // EventThread should enable vsync callbacks. expectVSyncSetEnabledCallReceived(true); - // The first event will be seen by the interceptor, and by the connection, - // which then returns an error. + // The first event will be seen by the connection, which then returns an error. mCallback->onVSyncEvent(123, {456, 789}); - expectInterceptCallReceived(123); expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 123, 1u); expectVsyncEventReceivedByConnection("successConnection", secondConnectionEventRecorder, 123, 1u); @@ -617,16 +589,13 @@ TEST_F(EventThreadTest, eventsDroppedIfNonfatalEventDeliveryError) { // EventThread should enable vsync callbacks. expectVSyncSetEnabledCallReceived(true); - // The first event will be seen by the interceptor, and by the connection, - // which then returns an non-fatal error. + // The first event will be seen by the connection, which then returns a non-fatal error. mCallback->onVSyncEvent(123, {456, 789}); - expectInterceptCallReceived(123); expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 123, 1u); - // A subsequent event will be seen by the interceptor, and by the connection, - // which still then returns an non-fatal error. + // A subsequent event will be seen by the connection, which still then returns a non-fatal + // error. mCallback->onVSyncEvent(456, {123, 0}); - expectInterceptCallReceived(456); expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 456, 2u); // EventThread will not disable vsync callbacks as the errors are non-fatal. @@ -748,17 +717,15 @@ TEST_F(EventThreadTest, requestNextVsyncWithThrottleVsyncDoesntPostVSync) { expectVSyncSetEnabledCallReceived(true); // Use the received callback to signal a first vsync event. - // The interceptor should receive the event, but not the connection. + // The throttler should receive the event, but not the connection. mCallback->onVSyncEvent(123, {456, 789}); - expectInterceptCallReceived(123); expectThrottleVsyncReceived(456, mThrottledConnectionUid); mThrottledConnectionEventCallRecorder.waitForUnexpectedCall(); // Use the received callback to signal a second vsync event. - // The interceptor should receive the event, but the connection should + // The throttler should receive the event, but the connection should // not as it was only interested in the first. mCallback->onVSyncEvent(456, {123, 0}); - expectInterceptCallReceived(456); expectThrottleVsyncReceived(123, mThrottledConnectionUid); EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value()); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp index f7d34ac5da..6a9c970bc3 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp @@ -30,9 +30,6 @@ TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForNonsecureDisplay) { // -------------------------------------------------------------------- // Call Expectations - // The call should notify the interceptor that a display was created. - EXPECT_CALL(*mSurfaceInterceptor, saveDisplayCreation(_)).Times(1); - // -------------------------------------------------------------------- // Invocation @@ -61,9 +58,6 @@ TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForSecureDisplay) { // -------------------------------------------------------------------- // Call Expectations - // The call should notify the interceptor that a display was created. - EXPECT_CALL(*mSurfaceInterceptor, saveDisplayCreation(_)).Times(1); - // -------------------------------------------------------------------- // Invocation int64_t oldId = IPCThreadState::self()->clearCallingIdentity(); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp index 40ef949468..93a3811172 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp @@ -37,9 +37,6 @@ TEST_F(DestroyDisplayTest, destroyDisplayClearsCurrentStateForDisplay) { // -------------------------------------------------------------------- // Call Expectations - // The call should notify the interceptor that a display was created. - EXPECT_CALL(*mSurfaceInterceptor, saveDisplayDeletion(_)).Times(1); - // Destroying the display commits a display transaction. EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp index 73f654ba87..94d517a3c3 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp @@ -90,15 +90,12 @@ void DisplayTransactionCommitTest::setupCommonCallExpectationsForConnectProcessi Case::HdrSupport::setupComposerCallExpectations(this); Case::PerFrameMetadataSupport::setupComposerCallExpectations(this); - EXPECT_CALL(*mSurfaceInterceptor, saveDisplayCreation(_)).Times(1); expectHotplugReceived(mEventThread); expectHotplugReceived(mSFEventThread); } template void DisplayTransactionCommitTest::setupCommonCallExpectationsForDisconnectProcessing() { - EXPECT_CALL(*mSurfaceInterceptor, saveDisplayDeletion(_)).Times(1); - expectHotplugReceived(mEventThread); expectHotplugReceived(mSFEventThread); } diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp index 98249bf104..f553a23f3c 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp @@ -38,11 +38,6 @@ TEST_F(OnInitializeDisplaysTest, onInitializeDisplaysSetsUpPrimaryDisplay) { // -------------------------------------------------------------------- // Call Expectations - // We expect the surface interceptor to possibly be used, but we treat it as - // disabled since it is called as a side effect rather than directly by this - // function. - EXPECT_CALL(*mSurfaceInterceptor, isEnabled()).WillOnce(Return(false)); - // We expect a call to get the active display config. Case::Display::setupHwcGetActiveConfigCallExpectations(this); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp index 6f84437372..25857ecb4e 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp @@ -276,13 +276,6 @@ struct DisplayPowerCase { EXPECT_CALL(*test->mFlinger.scheduler(), scheduleFrame()).Times(1); } - static void setupSurfaceInterceptorCallExpectations(DisplayTransactionTest* test, - PowerMode mode) { - EXPECT_CALL(*test->mSurfaceInterceptor, isEnabled()).WillOnce(Return(true)); - EXPECT_CALL(*test->mSurfaceInterceptor, savePowerModeUpdate(_, static_cast(mode))) - .Times(1); - } - static void setupComposerCallExpectations(DisplayTransactionTest* test, PowerMode mode) { // Any calls to get the active config will return a default value. EXPECT_CALL(*test->mComposer, getActiveConfig(Display::HWC_DISPLAY_ID, _)) @@ -349,7 +342,6 @@ void SetPowerModeInternalTest::transitionDisplayCommon() { // -------------------------------------------------------------------- // Call Expectations - Case::setupSurfaceInterceptorCallExpectations(this, Case::Transition::TARGET_POWER_MODE); Case::Transition::template setupCallExpectations(this); // -------------------------------------------------------------------- diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index f5042d301d..e0784eec06 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -40,7 +40,6 @@ #include "StartPropertySetThread.h" #include "SurfaceFlinger.h" #include "SurfaceFlingerDefaultFactory.h" -#include "SurfaceInterceptor.h" #include "TestableScheduler.h" #include "mock/DisplayHardware/MockComposer.h" #include "mock/DisplayHardware/MockDisplayMode.h" @@ -81,10 +80,6 @@ public: return std::make_unique(); } - sp createSurfaceInterceptor() override { - return sp::make(); - } - sp createStartPropertySetThread(bool timestampPropertyValue) override { return sp::make(timestampPropertyValue); } @@ -518,7 +513,6 @@ public: auto& mutablePhysicalDisplays() { return mFlinger->mPhysicalDisplays; } auto& mutableDrawingState() { return mFlinger->mDrawingState; } auto& mutableGeometryDirty() { return mFlinger->mGeometryDirty; } - auto& mutableInterceptor() { return mFlinger->mInterceptor; } auto& mutableMainThreadId() { return mFlinger->mMainThreadId; } auto& mutablePendingHotplugEvents() { return mFlinger->mPendingHotplugEvents; } auto& mutableTexturePool() { return mFlinger->mTexturePool; } @@ -543,7 +537,6 @@ public: mutableDisplays().clear(); mutableCurrentState().displays.clear(); mutableDrawingState().displays.clear(); - mutableInterceptor().clear(); mFlinger->mScheduler.reset(); mFlinger->mCompositionEngine->setHwComposer(std::unique_ptr()); mFlinger->mCompositionEngine->setRenderEngine( diff --git a/services/surfaceflinger/tests/unittests/mock/MockSurfaceInterceptor.cpp b/services/surfaceflinger/tests/unittests/mock/MockSurfaceInterceptor.cpp deleted file mode 100644 index 0a0e7b5861..0000000000 --- a/services/surfaceflinger/tests/unittests/mock/MockSurfaceInterceptor.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2018 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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - -#include "mock/MockSurfaceInterceptor.h" - -namespace android::mock { - -// Explicit default instantiation is recommended. -SurfaceInterceptor::SurfaceInterceptor() = default; -SurfaceInterceptor::~SurfaceInterceptor() = default; - -} // namespace android::mock - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/tests/unittests/mock/MockSurfaceInterceptor.h b/services/surfaceflinger/tests/unittests/mock/MockSurfaceInterceptor.h deleted file mode 100644 index b085027397..0000000000 --- a/services/surfaceflinger/tests/unittests/mock/MockSurfaceInterceptor.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2018 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 - -#include "SurfaceInterceptor.h" - -namespace android::mock { - -class SurfaceInterceptor : public android::SurfaceInterceptor { -public: - SurfaceInterceptor(); - ~SurfaceInterceptor() override; - - MOCK_METHOD2(enable, - void(const SortedVector>&, - const DefaultKeyedVector, DisplayDeviceState>&)); - MOCK_METHOD0(disable, void()); - MOCK_METHOD0(isEnabled, bool()); - MOCK_METHOD1(addTransactionTraceListener, void(const sp&)); - MOCK_METHOD1(binderDied, void(const wp&)); - MOCK_METHOD7(saveTransaction, - void(const Vector&, - const DefaultKeyedVector, DisplayDeviceState>&, - const Vector&, uint32_t, int, int, uint64_t)); - MOCK_METHOD1(saveSurfaceCreation, void(const sp&)); - MOCK_METHOD1(saveSurfaceDeletion, void(const sp&)); - MOCK_METHOD4(saveBufferUpdate, void(int32_t, uint32_t, uint32_t, uint64_t)); - MOCK_METHOD1(saveDisplayCreation, void(const DisplayDeviceState&)); - MOCK_METHOD1(saveDisplayDeletion, void(int32_t)); - MOCK_METHOD2(savePowerModeUpdate, void(int32_t, int32_t)); - MOCK_METHOD1(saveVSyncEvent, void(nsecs_t)); -}; - -} // namespace android::mock -- GitLab From a361de6e3b7bac5a47f6098d49c5a1acb5baf476 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Thu, 6 Oct 2022 20:34:10 +0000 Subject: [PATCH 0352/1310] Add layer name to layer_state_t and SurfaceControl This information will be used in subsequent CLs to trace layer cache events. Bug: 244218818 Test: presubmits Change-Id: I98bcd97310e3a2f061994481911073e8a1545cab --- libs/gui/SurfaceComposerClient.cpp | 15 +++-- libs/gui/SurfaceControl.cpp | 21 ++++-- .../aidl/android/gui/CreateSurfaceResult.aidl | 1 + .../android/gui/ISurfaceComposerClient.aidl | 5 +- .../aidl/android/gui/MirrorSurfaceResult.aidl | 23 ------- libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp | 7 +- libs/gui/fuzzer/libgui_fuzzer_utils.h | 4 +- .../libgui_surfaceComposerClient_fuzzer.cpp | 5 +- libs/gui/include/gui/SurfaceControl.h | 7 +- services/surfaceflinger/Client.cpp | 28 ++------ services/surfaceflinger/Client.h | 4 +- services/surfaceflinger/SurfaceFlinger.cpp | 64 +++++++++---------- services/surfaceflinger/SurfaceFlinger.h | 10 ++- .../Tracing/TransactionProtoParser.cpp | 4 +- .../Tracing/tools/LayerTraceGenerator.cpp | 14 ++-- .../tests/InvalidHandles_test.cpp | 2 +- .../tests/unittests/TestableSurfaceFlinger.h | 12 ++-- .../unittests/TransactionProtoParserTest.cpp | 3 +- 18 files changed, 103 insertions(+), 126 deletions(-) delete mode 100644 libs/gui/aidl/android/gui/MirrorSurfaceResult.aidl diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index d780173fef..a2390089d9 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2156,6 +2156,10 @@ sp SurfaceComposerClient::createSurface(const String8& name, uin return s; } +static std::string toString(const String16& string) { + return std::string(String8(string).c_str()); +} + status_t SurfaceComposerClient::createSurfaceChecked(const String8& name, uint32_t w, uint32_t h, PixelFormat format, sp* outSurface, int32_t flags, @@ -2175,7 +2179,8 @@ status_t SurfaceComposerClient::createSurfaceChecked(const String8& name, uint32 } ALOGE_IF(err, "SurfaceComposerClient::createSurface error %s", strerror(-err)); if (err == NO_ERROR) { - *outSurface = new SurfaceControl(this, result.handle, result.layerId, w, h, format, + *outSurface = new SurfaceControl(this, result.handle, result.layerId, + toString(result.layerName), w, h, format, result.transformHint, flags); } } @@ -2188,21 +2193,21 @@ sp SurfaceComposerClient::mirrorSurface(SurfaceControl* mirrorFr } sp mirrorFromHandle = mirrorFromSurface->getHandle(); - gui::MirrorSurfaceResult result; + gui::CreateSurfaceResult result; const binder::Status status = mClient->mirrorSurface(mirrorFromHandle, &result); const status_t err = statusTFromBinderStatus(status); if (err == NO_ERROR) { - return new SurfaceControl(this, result.handle, result.layerId); + return new SurfaceControl(this, result.handle, result.layerId, toString(result.layerName)); } return nullptr; } sp SurfaceComposerClient::mirrorDisplay(DisplayId displayId) { - gui::MirrorSurfaceResult result; + gui::CreateSurfaceResult result; const binder::Status status = mClient->mirrorDisplay(displayId.value, &result); const status_t err = statusTFromBinderStatus(status); if (err == NO_ERROR) { - return new SurfaceControl(this, result.handle, result.layerId); + return new SurfaceControl(this, result.handle, result.layerId, toString(result.layerName)); } return nullptr; } diff --git a/libs/gui/SurfaceControl.cpp b/libs/gui/SurfaceControl.cpp index 84257dee9b..7aee882422 100644 --- a/libs/gui/SurfaceControl.cpp +++ b/libs/gui/SurfaceControl.cpp @@ -49,11 +49,12 @@ namespace android { // ============================================================================ SurfaceControl::SurfaceControl(const sp& client, const sp& handle, - int32_t layerId, uint32_t w, uint32_t h, PixelFormat format, - uint32_t transform, uint32_t flags) + int32_t layerId, const std::string& name, uint32_t w, uint32_t h, + PixelFormat format, uint32_t transform, uint32_t flags) : mClient(client), mHandle(handle), mLayerId(layerId), + mName(name), mTransformHint(transform), mWidth(w), mHeight(h), @@ -65,6 +66,7 @@ SurfaceControl::SurfaceControl(const sp& other) { mHandle = other->mHandle; mTransformHint = other->mTransformHint; mLayerId = other->mLayerId; + mName = other->mName; mWidth = other->mWidth; mHeight = other->mHeight; mFormat = other->mFormat; @@ -185,6 +187,10 @@ int32_t SurfaceControl::getLayerId() const { return mLayerId; } +const std::string& SurfaceControl::getName() const { + return mName; +} + sp SurfaceControl::getIGraphicBufferProducer() { getSurface(); @@ -212,6 +218,7 @@ status_t SurfaceControl::writeToParcel(Parcel& parcel) { SAFE_PARCEL(parcel.writeStrongBinder, ISurfaceComposerClient::asBinder(mClient->getClient())); SAFE_PARCEL(parcel.writeStrongBinder, mHandle); SAFE_PARCEL(parcel.writeInt32, mLayerId); + SAFE_PARCEL(parcel.writeUtf8AsUtf16, mName); SAFE_PARCEL(parcel.writeUint32, mTransformHint); SAFE_PARCEL(parcel.writeUint32, mWidth); SAFE_PARCEL(parcel.writeUint32, mHeight); @@ -225,6 +232,7 @@ status_t SurfaceControl::readFromParcel(const Parcel& parcel, sp client; sp handle; int32_t layerId; + std::string layerName; uint32_t transformHint; uint32_t width; uint32_t height; @@ -233,16 +241,17 @@ status_t SurfaceControl::readFromParcel(const Parcel& parcel, SAFE_PARCEL(parcel.readStrongBinder, &client); SAFE_PARCEL(parcel.readStrongBinder, &handle); SAFE_PARCEL(parcel.readInt32, &layerId); + SAFE_PARCEL(parcel.readUtf8FromUtf16, &layerName); SAFE_PARCEL(parcel.readUint32, &transformHint); SAFE_PARCEL(parcel.readUint32, &width); SAFE_PARCEL(parcel.readUint32, &height); SAFE_PARCEL(parcel.readUint32, &format); // We aren't the original owner of the surface. - *outSurfaceControl = - new SurfaceControl(new SurfaceComposerClient( - interface_cast(client)), - handle.get(), layerId, width, height, format, transformHint); + *outSurfaceControl = new SurfaceControl(new SurfaceComposerClient( + interface_cast(client)), + handle.get(), layerId, layerName, width, height, format, + transformHint); return NO_ERROR; } diff --git a/libs/gui/aidl/android/gui/CreateSurfaceResult.aidl b/libs/gui/aidl/android/gui/CreateSurfaceResult.aidl index 39e49167aa..eea12dc75d 100644 --- a/libs/gui/aidl/android/gui/CreateSurfaceResult.aidl +++ b/libs/gui/aidl/android/gui/CreateSurfaceResult.aidl @@ -20,5 +20,6 @@ package android.gui; parcelable CreateSurfaceResult { IBinder handle; int layerId; + String layerName; int transformHint; } diff --git a/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl b/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl index b8ee4d72d7..68781ce953 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl @@ -19,7 +19,6 @@ package android.gui; import android.gui.CreateSurfaceResult; import android.gui.FrameStats; import android.gui.LayerMetadata; -import android.gui.MirrorSurfaceResult; /** @hide */ interface ISurfaceComposerClient { @@ -58,7 +57,7 @@ interface ISurfaceComposerClient { */ FrameStats getLayerFrameStats(IBinder handle); - MirrorSurfaceResult mirrorSurface(IBinder mirrorFromHandle); + CreateSurfaceResult mirrorSurface(IBinder mirrorFromHandle); - MirrorSurfaceResult mirrorDisplay(long displayId); + CreateSurfaceResult mirrorDisplay(long displayId); } diff --git a/libs/gui/aidl/android/gui/MirrorSurfaceResult.aidl b/libs/gui/aidl/android/gui/MirrorSurfaceResult.aidl deleted file mode 100644 index 9fac3e8644..0000000000 --- a/libs/gui/aidl/android/gui/MirrorSurfaceResult.aidl +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.gui; - -/** @hide */ -parcelable MirrorSurfaceResult { - IBinder handle; - int layerId; -} diff --git a/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp b/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp index 7829e94e85..761f08feb6 100644 --- a/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp +++ b/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include #include #include #include @@ -91,8 +92,10 @@ sp BufferQueueFuzzer::makeSurfaceControl() { const sp testClient(new FakeBnSurfaceComposerClient()); sp client = new SurfaceComposerClient(testClient); sp producer; - return sp::make(client, handle, mFdp.ConsumeIntegral(), - mFdp.ConsumeIntegral(), + uint32_t layerId = mFdp.ConsumeIntegral(); + std::string layerName = base::StringPrintf("#%d", layerId); + return sp::make(client, handle, layerId, layerName, + mFdp.ConsumeIntegral(), mFdp.ConsumeIntegral(), mFdp.ConsumeIntegral(), mFdp.ConsumeIntegral(), diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h index d51f6850c0..d43c197d9c 100644 --- a/libs/gui/fuzzer/libgui_fuzzer_utils.h +++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h @@ -171,11 +171,11 @@ public: (const sp& handle, gui::FrameStats* outStats), (override)); MOCK_METHOD(binder::Status, mirrorSurface, - (const sp& mirrorFromHandle, gui::MirrorSurfaceResult* outResult), + (const sp& mirrorFromHandle, gui::CreateSurfaceResult* outResult), (override)); MOCK_METHOD(binder::Status, mirrorDisplay, - (int64_t displayId, gui::MirrorSurfaceResult* outResult), (override)); + (int64_t displayId, gui::CreateSurfaceResult* outResult), (override)); }; class FakeDisplayEventDispatcher : public DisplayEventDispatcher { diff --git a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp index 48c90c5e8f..5bd65316dd 100644 --- a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp +++ b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp @@ -18,6 +18,7 @@ #include #include #include +#include "android-base/stringprintf.h" using namespace android; @@ -175,7 +176,9 @@ sp SurfaceComposerClientFuzzer::makeSurfaceControl() { uint32_t flags = mFdp.ConsumeIntegral(); int32_t format = mFdp.ConsumeIntegral(); int32_t layerId = mFdp.ConsumeIntegral(); - return new SurfaceControl(client, handle, layerId, width, height, format, transformHint, flags); + std::string layerName = base::StringPrintf("#%d", layerId); + return new SurfaceControl(client, handle, layerId, layerName, width, height, format, + transformHint, flags); } void SurfaceComposerClientFuzzer::invokeSurfaceComposerTransaction() { diff --git a/libs/gui/include/gui/SurfaceControl.h b/libs/gui/include/gui/SurfaceControl.h index e4a1350643..1d4fc7f06d 100644 --- a/libs/gui/include/gui/SurfaceControl.h +++ b/libs/gui/include/gui/SurfaceControl.h @@ -78,6 +78,7 @@ public: sp getHandle() const; sp getLayerStateHandle() const; int32_t getLayerId() const; + const std::string& getName() const; sp getIGraphicBufferProducer(); @@ -94,8 +95,9 @@ public: explicit SurfaceControl(const sp& other); SurfaceControl(const sp& client, const sp& handle, - int32_t layerId, uint32_t width = 0, uint32_t height = 0, PixelFormat format = 0, - uint32_t transformHint = 0, uint32_t flags = 0); + int32_t layerId, const std::string& layerName, uint32_t width = 0, + uint32_t height = 0, PixelFormat format = 0, uint32_t transformHint = 0, + uint32_t flags = 0); sp getParentingLayer(); @@ -121,6 +123,7 @@ private: mutable sp mBbq; mutable sp mBbqChild; int32_t mLayerId = 0; + std::string mName; uint32_t mTransformHint = 0; uint32_t mWidth = 0; uint32_t mHeight = 0; diff --git a/services/surfaceflinger/Client.cpp b/services/surfaceflinger/Client.cpp index 3685bb4ecd..30b875967c 100644 --- a/services/surfaceflinger/Client.cpp +++ b/services/surfaceflinger/Client.cpp @@ -81,17 +81,9 @@ binder::Status Client::createSurface(const std::string& name, int32_t flags, gui::CreateSurfaceResult* outResult) { // We rely on createLayer to check permissions. sp handle; - int32_t layerId; - uint32_t transformHint; LayerCreationArgs args(mFlinger.get(), sp::fromExisting(this), name.c_str(), static_cast(flags), std::move(metadata)); - const status_t status = - mFlinger->createLayer(args, &handle, parent, &layerId, nullptr, &transformHint); - if (status == NO_ERROR) { - outResult->handle = handle; - outResult->layerId = layerId; - outResult->transformHint = static_cast(transformHint); - } + const status_t status = mFlinger->createLayer(args, parent, *outResult); return binderStatusFromStatusT(status); } @@ -134,31 +126,21 @@ binder::Status Client::getLayerFrameStats(const sp& handle, gui::FrameS } binder::Status Client::mirrorSurface(const sp& mirrorFromHandle, - gui::MirrorSurfaceResult* outResult) { + gui::CreateSurfaceResult* outResult) { sp handle; - int32_t layerId; LayerCreationArgs args(mFlinger.get(), sp::fromExisting(this), "MirrorRoot", 0 /* flags */, gui::LayerMetadata()); - status_t status = mFlinger->mirrorLayer(args, mirrorFromHandle, &handle, &layerId); - if (status == NO_ERROR) { - outResult->handle = handle; - outResult->layerId = layerId; - } + status_t status = mFlinger->mirrorLayer(args, mirrorFromHandle, *outResult); return binderStatusFromStatusT(status); } -binder::Status Client::mirrorDisplay(int64_t displayId, gui::MirrorSurfaceResult* outResult) { +binder::Status Client::mirrorDisplay(int64_t displayId, gui::CreateSurfaceResult* outResult) { sp handle; - int32_t layerId; LayerCreationArgs args(mFlinger.get(), sp::fromExisting(this), "MirrorRoot-" + std::to_string(displayId), 0 /* flags */, gui::LayerMetadata()); std::optional id = DisplayId::fromValue(static_cast(displayId)); - status_t status = mFlinger->mirrorDisplay(*id, args, &handle, &layerId); - if (status == NO_ERROR) { - outResult->handle = handle; - outResult->layerId = layerId; - } + status_t status = mFlinger->mirrorDisplay(*id, args, *outResult); return binderStatusFromStatusT(status); } diff --git a/services/surfaceflinger/Client.h b/services/surfaceflinger/Client.h index 4e59dfdf3a..02079a3b3f 100644 --- a/services/surfaceflinger/Client.h +++ b/services/surfaceflinger/Client.h @@ -57,9 +57,9 @@ private: gui::FrameStats* outStats) override; binder::Status mirrorSurface(const sp& mirrorFromHandle, - gui::MirrorSurfaceResult* outResult) override; + gui::CreateSurfaceResult* outResult) override; - binder::Status mirrorDisplay(int64_t displayId, gui::MirrorSurfaceResult* outResult) override; + binder::Status mirrorDisplay(int64_t displayId, gui::CreateSurfaceResult* outResult) override; // constant sp mFlinger; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 4055769649..0b628b0afa 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4486,8 +4486,8 @@ uint32_t SurfaceFlinger::addInputWindowCommands(const InputWindowCommands& input } status_t SurfaceFlinger::mirrorLayer(const LayerCreationArgs& args, - const sp& mirrorFromHandle, sp* outHandle, - int32_t* outLayerId) { + const sp& mirrorFromHandle, + gui::CreateSurfaceResult& outResult) { if (!mirrorFromHandle) { return NAME_NOT_FOUND; } @@ -4502,7 +4502,7 @@ status_t SurfaceFlinger::mirrorLayer(const LayerCreationArgs& args, } LayerCreationArgs mirrorArgs = args; mirrorArgs.flags |= ISurfaceComposerClient::eNoColorFill; - status_t result = createEffectLayer(mirrorArgs, outHandle, &mirrorLayer); + status_t result = createEffectLayer(mirrorArgs, &outResult.handle, &mirrorLayer); if (result != NO_ERROR) { return result; } @@ -4510,17 +4510,20 @@ status_t SurfaceFlinger::mirrorLayer(const LayerCreationArgs& args, mirrorLayer->setClonedChild(mirrorFrom->createClone()); } - *outLayerId = mirrorLayer->sequence; + outResult.layerId = mirrorLayer->sequence; + outResult.layerName = String16(mirrorLayer->getDebugName()); if (mTransactionTracing) { - mTransactionTracing->onMirrorLayerAdded((*outHandle)->localBinder(), mirrorLayer->sequence, - args.name, mirrorFrom->sequence); + mTransactionTracing->onMirrorLayerAdded(outResult.handle->localBinder(), + mirrorLayer->sequence, args.name, + mirrorFrom->sequence); } - return addClientLayer(args.client, *outHandle, mirrorLayer /* layer */, nullptr /* parent */, - false /* addToRoot */, nullptr /* outTransformHint */); + return addClientLayer(args.client, outResult.handle, mirrorLayer /* layer */, + nullptr /* parent */, false /* addToRoot */, + nullptr /* outTransformHint */); } status_t SurfaceFlinger::mirrorDisplay(DisplayId displayId, const LayerCreationArgs& args, - sp* outHandle, int32_t* outLayerId) { + gui::CreateSurfaceResult& outResult) { IPCThreadState* ipc = IPCThreadState::self(); const int uid = ipc->getCallingUid(); if (uid != AID_ROOT && uid != AID_GRAPHICS && uid != AID_SYSTEM && uid != AID_SHELL) { @@ -4543,9 +4546,10 @@ status_t SurfaceFlinger::mirrorDisplay(DisplayId displayId, const LayerCreationA layerStack = display->getLayerStack(); LayerCreationArgs mirrorArgs = args; mirrorArgs.flags |= ISurfaceComposerClient::eNoColorFill; - result = createEffectLayer(mirrorArgs, outHandle, &rootMirrorLayer); - *outLayerId = rootMirrorLayer->sequence; - result |= addClientLayer(args.client, *outHandle, rootMirrorLayer /* layer */, + result = createEffectLayer(mirrorArgs, &outResult.handle, &rootMirrorLayer); + outResult.layerId = rootMirrorLayer->sequence; + outResult.layerName = String16(rootMirrorLayer->getDebugName()); + result |= addClientLayer(args.client, outResult.handle, rootMirrorLayer /* layer */, nullptr /* parent */, true /* addToRoot */, nullptr /* outTransformHint */); } @@ -4555,26 +4559,21 @@ status_t SurfaceFlinger::mirrorDisplay(DisplayId displayId, const LayerCreationA } if (mTransactionTracing) { - mTransactionTracing->onLayerAdded((*outHandle)->localBinder(), *outLayerId, args.name, - args.flags, -1 /* parentId */); + mTransactionTracing->onLayerAdded(outResult.handle->localBinder(), outResult.layerId, + args.name, args.flags, -1 /* parentId */); } { std::scoped_lock lock(mMirrorDisplayLock); - mMirrorDisplays.emplace_back(layerStack, *outHandle, args.client); + mMirrorDisplays.emplace_back(layerStack, outResult.handle, args.client); } setTransactionFlags(eTransactionFlushNeeded); return NO_ERROR; } -status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, sp* outHandle, - const sp& parentHandle, int32_t* outLayerId, - const sp& parentLayer, uint32_t* outTransformHint) { - ALOG_ASSERT(parentLayer == nullptr || parentHandle == nullptr, - "Expected only one of parentLayer or parentHandle to be non-null. " - "Programmer error?"); - +status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, const sp& parentHandle, + gui::CreateSurfaceResult& outResult) { status_t result = NO_ERROR; sp layer; @@ -4586,11 +4585,11 @@ status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, sp* outHa args.flags |= ISurfaceComposerClient::eNoColorFill; FMT_FALLTHROUGH; case ISurfaceComposerClient::eFXSurfaceEffect: { - result = createBufferStateLayer(args, outHandle, &layer); + result = createBufferStateLayer(args, &outResult.handle, &layer); std::atomic* pendingBufferCounter = layer->getPendingBufferCounter(); if (pendingBufferCounter) { std::string counterName = layer->getPendingBufferCounterName(); - mBufferCountTracker.add((*outHandle)->localBinder(), counterName, + mBufferCountTracker.add(outResult.handle->localBinder(), counterName, pendingBufferCounter); } } break; @@ -4604,14 +4603,11 @@ status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, sp* outHa } bool addToRoot = args.addToRoot && callingThreadHasUnscopedSurfaceFlingerAccess(); - wp parent(parentHandle != nullptr ? fromHandle(parentHandle) : parentLayer); + wp parent = fromHandle(parentHandle); if (parentHandle != nullptr && parent == nullptr) { ALOGE("Invalid parent handle %p.", parentHandle.get()); addToRoot = false; } - if (parentLayer != nullptr) { - addToRoot = false; - } int parentId = -1; // We can safely promote the layer in binder thread because we have a strong reference @@ -4621,16 +4617,20 @@ status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, sp* outHa parentId = parentSp->getSequence(); } if (mTransactionTracing) { - mTransactionTracing->onLayerAdded((*outHandle)->localBinder(), layer->sequence, args.name, - args.flags, parentId); + mTransactionTracing->onLayerAdded(outResult.handle->localBinder(), layer->sequence, + args.name, args.flags, parentId); } - result = addClientLayer(args.client, *outHandle, layer, parent, addToRoot, outTransformHint); + uint32_t outTransformHint; + result = addClientLayer(args.client, outResult.handle, layer, parent, addToRoot, + &outTransformHint); if (result != NO_ERROR) { return result; } - *outLayerId = layer->sequence; + outResult.transformHint = static_cast(outTransformHint); + outResult.layerId = layer->sequence; + outResult.layerName = String16(layer->getDebugName()); return result; } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 5f4ab14b60..cbb209adb9 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -774,10 +774,8 @@ private: /* * Layer management */ - status_t createLayer(LayerCreationArgs& args, sp* outHandle, - const sp& parentHandle, int32_t* outLayerId, - const sp& parentLayer = nullptr, - uint32_t* outTransformHint = nullptr); + status_t createLayer(LayerCreationArgs& args, const sp& parentHandle, + gui::CreateSurfaceResult& outResult); status_t createBufferStateLayer(LayerCreationArgs& args, sp* outHandle, sp* outLayer); @@ -786,10 +784,10 @@ private: sp* outLayer); status_t mirrorLayer(const LayerCreationArgs& args, const sp& mirrorFromHandle, - sp* outHandle, int32_t* outLayerId); + gui::CreateSurfaceResult& outResult); status_t mirrorDisplay(DisplayId displayId, const LayerCreationArgs& args, - sp* outHandle, int32_t* outLayerId); + gui::CreateSurfaceResult& outResult); // called when all clients have released all their references to // this layer meaning it is entirely safe to destroy all diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp index dcc529ecc5..267f3d0219 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp @@ -452,7 +452,7 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta layer.parentSurfaceControlForChild = sp::make(SurfaceComposerClient::getDefault(), mMapper->getLayerHandle(static_cast(layerId)), - static_cast(layerId)); + static_cast(layerId), ""); } } if (proto.what() & layer_state_t::eRelativeLayerChanged) { @@ -463,7 +463,7 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta layer.relativeLayerSurfaceControl = sp::make(SurfaceComposerClient::getDefault(), mMapper->getLayerHandle(static_cast(layerId)), - static_cast(layerId)); + static_cast(layerId), ""); } layer.z = proto.z(); } diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index 88171785c7..b1431605b5 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -213,8 +213,7 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, TracingLayerCreationArgs tracingArgs; parser.fromProto(entry.added_layers(j), tracingArgs); - sp outHandle; - int32_t outLayerId; + gui::CreateSurfaceResult outResult; LayerCreationArgs args(mFlinger.flinger(), nullptr /* client */, tracingArgs.name, tracingArgs.flags, LayerMetadata()); args.sequence = std::make_optional(tracingArgs.layerId); @@ -228,16 +227,15 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, } else if (tracingArgs.parentId != -1) { parentHandle = dataMapper->getLayerHandle(tracingArgs.parentId); } - mFlinger.createLayer(args, &outHandle, parentHandle, &outLayerId, - nullptr /* parentLayer */, nullptr /* outTransformHint */); + mFlinger.createLayer(args, parentHandle, outResult); } else { sp mirrorFromHandle = dataMapper->getLayerHandle(tracingArgs.mirrorFromId); - mFlinger.mirrorLayer(args, mirrorFromHandle, &outHandle, &outLayerId); + mFlinger.mirrorLayer(args, mirrorFromHandle, outResult); } - LOG_ALWAYS_FATAL_IF(outLayerId != tracingArgs.layerId, + LOG_ALWAYS_FATAL_IF(outResult.layerId != tracingArgs.layerId, "Could not create layer expected:%d actual:%d", tracingArgs.layerId, - outLayerId); - dataMapper->mLayerHandles[tracingArgs.layerId] = outHandle; + outResult.layerId); + dataMapper->mLayerHandles[tracingArgs.layerId] = outResult.handle; } for (int j = 0; j < entry.transactions_size(); j++) { diff --git a/services/surfaceflinger/tests/InvalidHandles_test.cpp b/services/surfaceflinger/tests/InvalidHandles_test.cpp index 741b6f73e5..666ce76c6b 100644 --- a/services/surfaceflinger/tests/InvalidHandles_test.cpp +++ b/services/surfaceflinger/tests/InvalidHandles_test.cpp @@ -48,7 +48,7 @@ protected: } sp makeNotSurfaceControl() { - return sp::make(mScc, sp::make(), 1); + return sp::make(mScc, sp::make(), 1, "#1"); } }; diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index cff529c98a..2e90c7ec32 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -463,16 +463,14 @@ public: mFlinger->onActiveDisplayChangedLocked(activeDisplay); } - auto createLayer(LayerCreationArgs& args, sp* outHandle, - const sp& parentHandle, int32_t* outLayerId, - const sp& parentLayer, uint32_t* outTransformHint) { - return mFlinger->createLayer(args, outHandle, parentHandle, outLayerId, parentLayer, - outTransformHint); + auto createLayer(LayerCreationArgs& args, const sp& parentHandle, + gui::CreateSurfaceResult& outResult) { + return mFlinger->createLayer(args, parentHandle, outResult); } auto mirrorLayer(const LayerCreationArgs& args, const sp& mirrorFromHandle, - sp* outHandle, int32_t* outLayerId) { - return mFlinger->mirrorLayer(args, mirrorFromHandle, outHandle, outLayerId); + gui::CreateSurfaceResult& outResult) { + return mFlinger->mirrorLayer(args, mirrorFromHandle, outResult); } void updateLayerMetadataSnapshot() { mFlinger->updateLayerMetadataSnapshot(); } diff --git a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp index 1f011bee0d..14e1aac793 100644 --- a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp @@ -49,7 +49,8 @@ TEST(TransactionProtoParserTest, parse) { ComposerState s; if (i == 1) { layer.parentSurfaceControlForChild = - sp::make(SurfaceComposerClient::getDefault(), layerHandle, 42); + sp::make(SurfaceComposerClient::getDefault(), layerHandle, 42, + "#42"); } s.state = layer; t1.states.add(s); -- GitLab From b8fd3ef57dd02e0f9c171c3ba9775ca781cd5527 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 10 Oct 2022 08:03:54 -0700 Subject: [PATCH 0353/1310] Delete struct PointerData to fix ODR violation This data structure is defined with the same name in PreferStylusOverTouch_test and in UnwantedInteractionBlocker_test. This creates a "one-definition-rule" violation. This violation is not getting caught by the compiler today. As a result, this causes the read to go out of bounds when the wrong data structure is used. The issue can be reproduced by running the tests on host. To fix it, simply remove this struct and fallback to the Point struct from libui. Bug: 217165277 249591924 251318977 Test: atest --host --no-bazel-mode inputflinger_tests Change-Id: I79e0647df63bbf69dd3074f1bd2931b298f4cd48 --- .../inputflinger/tests/PreferStylusOverTouch_test.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp index 8e2ab88e80..bd053603df 100644 --- a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp +++ b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp @@ -33,14 +33,8 @@ static constexpr int32_t POINTER_1_DOWN = constexpr int32_t TOUCHSCREEN = AINPUT_SOURCE_TOUCHSCREEN; constexpr int32_t STYLUS = AINPUT_SOURCE_STYLUS; -struct PointerData { - float x; - float y; -}; - static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, int32_t action, - const std::vector& points, - uint32_t source) { + const std::vector& points, uint32_t source) { size_t pointerCount = points.size(); if (action == DOWN || action == UP) { EXPECT_EQ(1U, pointerCount) << "Actions DOWN and UP can only contain a single pointer"; -- GitLab From edb0ba739a01d5f9c07e5a6bb88cd0b985f9986f Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 4 Oct 2022 15:44:11 +0000 Subject: [PATCH 0354/1310] TouchInputMapper: Use early return in populateDeviceInfo Using the early return reduces the indentation level and is more readable. Bug: 245989146 Test: atest inputflinger_tests Change-Id: I5ebaac48e8ec390824f2c8889c301f291c5d17ab --- .../reader/mapper/TouchInputMapper.cpp | 118 +++++++++--------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index da58efde31..6980144428 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -189,73 +189,73 @@ uint32_t TouchInputMapper::getSources() const { void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) { InputMapper::populateDeviceInfo(info); - if (mDeviceMode != DeviceMode::DISABLED) { - info->addMotionRange(mOrientedRanges.x); - info->addMotionRange(mOrientedRanges.y); - info->addMotionRange(mOrientedRanges.pressure); - - if (mDeviceMode == DeviceMode::UNSCALED && mSource == AINPUT_SOURCE_TOUCHPAD) { - // Populate RELATIVE_X and RELATIVE_Y motion ranges for touchpad capture mode. - // - // RELATIVE_X and RELATIVE_Y motion ranges should be the largest possible relative - // motion, i.e. the hardware dimensions, as the finger could move completely across the - // touchpad in one sample cycle. - const InputDeviceInfo::MotionRange& x = mOrientedRanges.x; - const InputDeviceInfo::MotionRange& y = mOrientedRanges.y; - info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -x.max, x.max, x.flat, - x.fuzz, x.resolution); - info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -y.max, y.max, y.flat, - y.fuzz, y.resolution); - } + if (mDeviceMode == DeviceMode::DISABLED) { + return; + } - if (mOrientedRanges.size) { - info->addMotionRange(*mOrientedRanges.size); - } + info->addMotionRange(mOrientedRanges.x); + info->addMotionRange(mOrientedRanges.y); + info->addMotionRange(mOrientedRanges.pressure); - if (mOrientedRanges.touchMajor) { - info->addMotionRange(*mOrientedRanges.touchMajor); - info->addMotionRange(*mOrientedRanges.touchMinor); - } + if (mDeviceMode == DeviceMode::UNSCALED && mSource == AINPUT_SOURCE_TOUCHPAD) { + // Populate RELATIVE_X and RELATIVE_Y motion ranges for touchpad capture mode. + // + // RELATIVE_X and RELATIVE_Y motion ranges should be the largest possible relative + // motion, i.e. the hardware dimensions, as the finger could move completely across the + // touchpad in one sample cycle. + const InputDeviceInfo::MotionRange& x = mOrientedRanges.x; + const InputDeviceInfo::MotionRange& y = mOrientedRanges.y; + info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -x.max, x.max, x.flat, x.fuzz, + x.resolution); + info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -y.max, y.max, y.flat, y.fuzz, + y.resolution); + } - if (mOrientedRanges.toolMajor) { - info->addMotionRange(*mOrientedRanges.toolMajor); - info->addMotionRange(*mOrientedRanges.toolMinor); - } + if (mOrientedRanges.size) { + info->addMotionRange(*mOrientedRanges.size); + } - if (mOrientedRanges.orientation) { - info->addMotionRange(*mOrientedRanges.orientation); - } + if (mOrientedRanges.touchMajor) { + info->addMotionRange(*mOrientedRanges.touchMajor); + info->addMotionRange(*mOrientedRanges.touchMinor); + } - if (mOrientedRanges.distance) { - info->addMotionRange(*mOrientedRanges.distance); - } + if (mOrientedRanges.toolMajor) { + info->addMotionRange(*mOrientedRanges.toolMajor); + info->addMotionRange(*mOrientedRanges.toolMinor); + } - if (mOrientedRanges.tilt) { - info->addMotionRange(*mOrientedRanges.tilt); - } + if (mOrientedRanges.orientation) { + info->addMotionRange(*mOrientedRanges.orientation); + } - if (mCursorScrollAccumulator.haveRelativeVWheel()) { - info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, - 0.0f); - } - if (mCursorScrollAccumulator.haveRelativeHWheel()) { - info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, - 0.0f); - } - if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::BOX) { - const InputDeviceInfo::MotionRange& x = mOrientedRanges.x; - const InputDeviceInfo::MotionRange& y = mOrientedRanges.y; - info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_1, mSource, x.min, x.max, x.flat, - x.fuzz, x.resolution); - info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_2, mSource, y.min, y.max, y.flat, - y.fuzz, y.resolution); - info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_3, mSource, x.min, x.max, x.flat, - x.fuzz, x.resolution); - info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_4, mSource, y.min, y.max, y.flat, - y.fuzz, y.resolution); - } - info->setButtonUnderPad(mParameters.hasButtonUnderPad); + if (mOrientedRanges.distance) { + info->addMotionRange(*mOrientedRanges.distance); + } + + if (mOrientedRanges.tilt) { + info->addMotionRange(*mOrientedRanges.tilt); + } + + if (mCursorScrollAccumulator.haveRelativeVWheel()) { + info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); + } + if (mCursorScrollAccumulator.haveRelativeHWheel()) { + info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); + } + if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::BOX) { + const InputDeviceInfo::MotionRange& x = mOrientedRanges.x; + const InputDeviceInfo::MotionRange& y = mOrientedRanges.y; + info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_1, mSource, x.min, x.max, x.flat, x.fuzz, + x.resolution); + info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_2, mSource, y.min, y.max, y.flat, y.fuzz, + y.resolution); + info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_3, mSource, x.min, x.max, x.flat, x.fuzz, + x.resolution); + info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_4, mSource, y.min, y.max, y.flat, y.fuzz, + y.resolution); } + info->setButtonUnderPad(mParameters.hasButtonUnderPad); } void TouchInputMapper::dump(std::string& dump) { -- GitLab From f5b4d7a828fe02044a92da0f2f512ee2763d6539 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 3 Oct 2022 15:45:50 +0000 Subject: [PATCH 0355/1310] TouchInputMapper: Cancel ongoing gesture when resetting Since reset is called when a device is being enabled or disabled, if it doesn't cancel ongoing gestures, there is a possiblity of seeing an inconsistent gesture stream. Also, make sure we send FLAG_CANCELED when sending ACTION_CANCEL from TouchInputMapper. Bug: 245989146 Test: atest inputflinger_tests Change-Id: I9921eee9acf365b28d97f3fbe9b4d6cd15fe7087 --- .../reader/mapper/TouchInputMapper.cpp | 25 +++++------ .../inputflinger/tests/InputReader_test.cpp | 45 ++++++++++++++++--- .../tests/TestInputListenerMatchers.h | 14 ++++-- 3 files changed, 61 insertions(+), 23 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 6980144428..c2454ac6d4 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -395,13 +395,11 @@ std::list TouchInputMapper::configure(nsecs_t when, } if (changes && resetNeeded) { - // If the device needs to be reset, cancel any ongoing gestures and reset the state. - out += cancelTouch(when, when); out += reset(when); // Send reset, unless this is the first time the device has been configured, // in which case the reader will call reset itself after all mappers are ready. - out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId())); + out.emplace_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId())); } return out; } @@ -1440,6 +1438,9 @@ void TouchInputMapper::updateAffineTransformation() { } std::list TouchInputMapper::reset(nsecs_t when) { + std::list out = cancelTouch(when, when); + updateTouchSpots(); + mCursorButtonAccumulator.reset(getDeviceContext()); mCursorScrollAccumulator.reset(getDeviceContext()); mTouchButtonAccumulator.reset(getDeviceContext()); @@ -1470,7 +1471,7 @@ std::list TouchInputMapper::reset(nsecs_t when) { mPointerController->clearSpots(); } - return InputMapper::reset(when); + return out += InputMapper::reset(when); } void TouchInputMapper::resetExternalStylus() { @@ -1554,10 +1555,7 @@ std::list TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { std::list TouchInputMapper::processRawTouches(bool timeout) { std::list out; if (mDeviceMode == DeviceMode::DISABLED) { - // Drop all input if the device is disabled. - out += cancelTouch(mCurrentRawState.when, mCurrentRawState.readTime); - mCurrentCookedState.clear(); - updateTouchSpots(); + // Do not process raw event while the device is disabled. return out; } @@ -1976,8 +1974,8 @@ std::list TouchInputMapper::abortTouches(nsecs_t when, nsecs_t readT int32_t metaState = getContext()->getGlobalMetaState(); int32_t buttonState = mCurrentCookedState.buttonState; out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, - AMOTION_EVENT_ACTION_CANCEL, 0, 0, metaState, buttonState, - AMOTION_EVENT_EDGE_FLAG_NONE, + AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED, + metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, mCurrentCookedState.cookedPointerData.pointerProperties, mCurrentCookedState.cookedPointerData.pointerCoords, mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits, @@ -2617,8 +2615,9 @@ std::list TouchInputMapper::dispatchPointerGestures(nsecs_t when, ns BitSet32 dispatchedGestureIdBits(mPointerGesture.lastGestureIdBits); if (!dispatchedGestureIdBits.isEmpty()) { if (cancelPreviousGesture) { + const uint32_t cancelFlags = flags | AMOTION_EVENT_FLAG_CANCELED; out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, - AMOTION_EVENT_ACTION_CANCEL, 0, flags, metaState, + AMOTION_EVENT_ACTION_CANCEL, 0, cancelFlags, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords, @@ -2754,8 +2753,8 @@ std::list TouchInputMapper::abortPointerGestures(nsecs_t when, nsecs int32_t metaState = getContext()->getGlobalMetaState(); int32_t buttonState = mCurrentRawState.buttonState; out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, - AMOTION_EVENT_ACTION_CANCEL, 0, 0, metaState, buttonState, - AMOTION_EVENT_EDGE_FLAG_NONE, + AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED, + metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index dded6a13e0..8ac8dfc3ac 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -3112,6 +3112,15 @@ protected: return processArgList; } + void resetMapper(InputMapper& mapper, nsecs_t when) { + const auto resetArgs = mapper.reset(when); + for (const auto args : resetArgs) { + mFakeListener->notify(args); + } + // Loop the reader to flush the input listener queue. + mReader->loopOnce(); + } + static void assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source, float min, float max, float flat, float fuzz) { const InputDeviceInfo::MotionRange* range = info.getMotionRange(axis, source); @@ -6864,6 +6873,28 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenAbsPressureIsPresent_HoversIfItsV toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); } +TEST_F(SingleTouchInputMapperTest, Reset_CancelsOngoingGesture) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + prepareAxes(POSITION | PRESSURE); + SingleTouchInputMapper& mapper = addMapperAndConfigure(); + + // Touch down. + processDown(mapper, 100, 200); + processPressure(mapper, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_DOWN))); + + // Reset the mapper. This should cancel the ongoing gesture. + resetMapper(mapper, ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_CANCEL))); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + TEST_F(SingleTouchInputMapperTest, Reset_RecreatesTouchState) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(DISPLAY_ORIENTATION_0); @@ -6878,8 +6909,9 @@ TEST_F(SingleTouchInputMapperTest, Reset_RecreatesTouchState) { mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1); // Reset the mapper. When the mapper is reset, we expect it to attempt to recreate the touch - // state by reading the current axis values. - std::list unused = mapper.reset(ARBITRARY_TIME); + // state by reading the current axis values. Since there was no ongoing gesture, calling reset + // does not generate any events. + resetMapper(mapper, ARBITRARY_TIME); // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use // the recreated touch state to generate a down event. @@ -9530,9 +9562,10 @@ TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) { mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(ACTION_POINTER_1_DOWN))); // Reset the mapper. When the mapper is reset, we expect the current multi-touch state to be - // preserved. Resetting should not generate any events. - std::list unused = mapper.reset(ARBITRARY_TIME); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + // preserved. Resetting should cancel the ongoing gesture. + resetMapper(mapper, ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_CANCEL))); // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use // the existing touch state to generate a down event. @@ -9566,7 +9599,7 @@ TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState_NoPointersDown) // Reset the mapper. When the mapper is reset, we expect it to restore the latest // raw state where no pointers are down. - std::list unused = mapper.reset(ARBITRARY_TIME); + resetMapper(mapper, ARBITRARY_TIME); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); // Send an empty sync frame. Since there are no pointers, no events are generated. diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h index 2580405c02..ff7455b452 100644 --- a/services/inputflinger/tests/TestInputListenerMatchers.h +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -23,13 +23,19 @@ namespace android { MATCHER_P(WithMotionAction, action, "InputEvent with specified action") { + bool matches = action == arg.action; + if (!matches) { + *result_listener << "expected action " << MotionEvent::actionToString(action) + << ", but got " << MotionEvent::actionToString(arg.action); + } if (action == AMOTION_EVENT_ACTION_CANCEL) { + if (!matches) { + *result_listener << "; "; + } *result_listener << "expected FLAG_CANCELED to be set with ACTION_CANCEL, but was not set"; - return (arg.flags & AMOTION_EVENT_FLAG_CANCELED) != 0; + matches &= (arg.flags & AMOTION_EVENT_FLAG_CANCELED) != 0; } - *result_listener << "expected action " << MotionEvent::actionToString(action) << ", but got " - << MotionEvent::actionToString(arg.action); - return action == arg.action; + return matches; } MATCHER_P(WithSource, source, "InputEvent with specified source") { -- GitLab From 167c270ea82781d0a003c05292ff623fcb5f8243 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 14 Sep 2022 00:37:24 +0000 Subject: [PATCH 0356/1310] Determine whether an input device supports USI using IDC files It is necessary to identify a USI device so that additional logic can be applied to the battery information that is exposed via USI. Bug: 243005009 Test: manual, check dumpsys output Change-Id: I697008e6600ef16b7ecde85c99732759747b8a9b --- include/input/InputDevice.h | 5 +++++ libs/input/InputDevice.cpp | 2 ++ services/inputflinger/reader/mapper/TouchInputMapper.cpp | 6 ++++++ services/inputflinger/reader/mapper/TouchInputMapper.h | 3 +++ 4 files changed, 16 insertions(+) diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h index 0026e82caa..f4a15238cf 100644 --- a/include/input/InputDevice.h +++ b/include/input/InputDevice.h @@ -280,6 +280,9 @@ public: std::vector getLights(); + inline void setSupportsUsi(bool supportsUsi) { mSupportsUsi = supportsUsi; } + inline bool supportsUsi() const { return mSupportsUsi; } + private: int32_t mId; int32_t mGeneration; @@ -292,6 +295,8 @@ private: uint32_t mSources; int32_t mKeyboardType; std::shared_ptr mKeyCharacterMap; + // Whether this device supports the Universal Stylus Initiative (USI) protocol for styluses. + bool mSupportsUsi; bool mHasVibrator; bool mHasBattery; diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp index 3fe03c7979..4751a7de8b 100644 --- a/libs/input/InputDevice.cpp +++ b/libs/input/InputDevice.cpp @@ -182,6 +182,7 @@ InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) mSources(other.mSources), mKeyboardType(other.mKeyboardType), mKeyCharacterMap(other.mKeyCharacterMap), + mSupportsUsi(other.mSupportsUsi), mHasVibrator(other.mHasVibrator), mHasBattery(other.mHasBattery), mHasButtonUnderPad(other.mHasButtonUnderPad), @@ -210,6 +211,7 @@ void InputDeviceInfo::initialize(int32_t id, int32_t generation, int32_t control mHasBattery = false; mHasButtonUnderPad = false; mHasSensor = false; + mSupportsUsi = false; mMotionRanges.clear(); mSensors.clear(); mLights.clear(); diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index c2454ac6d4..18a73eadfb 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -256,6 +256,7 @@ void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) { y.resolution); } info->setButtonUnderPad(mParameters.hasButtonUnderPad); + info->setSupportsUsi(mParameters.supportsUsi); } void TouchInputMapper::dump(std::string& dump) { @@ -505,6 +506,10 @@ void TouchInputMapper::configureParameters() { // up in your pocket but you can enable it using the input device configuration. mParameters.wake = getDeviceContext().isExternal(); getDeviceContext().getConfiguration().tryGetProperty("touch.wake", mParameters.wake); + + mParameters.supportsUsi = false; + getDeviceContext().getConfiguration().tryGetProperty("touch.supportsUsi", + mParameters.supportsUsi); } void TouchInputMapper::dumpParameters(std::string& dump) { @@ -521,6 +526,7 @@ void TouchInputMapper::dumpParameters(std::string& dump) { mParameters.uniqueDisplayId.c_str()); dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware)); dump += INDENT4 "Orientation: " + ftl::enum_string(mParameters.orientation) + "\n"; + dump += StringPrintf(INDENT4 "SupportsUsi: %s\n", toString(mParameters.supportsUsi)); } void TouchInputMapper::configureRawPointerAxes() { diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 50f30c824e..7b0327e913 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -232,6 +232,9 @@ protected: GestureMode gestureMode; bool wake; + + // Whether the device supports the Universal Stylus Initiative (USI) protocol for styluses. + bool supportsUsi; } mParameters; // Immutable calibration parameters in parsed form. -- GitLab From bbceb46b722db9d1ececb909c845e45302721f89 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Mon, 10 Oct 2022 04:52:13 +0000 Subject: [PATCH 0357/1310] SurfaceComposerClient: Clean up layer state - align layer alpha/color with SF layer drawing state - remove unused flags and fields Bug: 238781169 Test: presubmit Change-Id: I4be9c56b3006b7d7a0ca19160511ebb9e3551a8c --- libs/gui/LayerState.cpp | 31 +++++++++---------- libs/gui/SurfaceComposerClient.cpp | 10 +++--- libs/gui/include/gui/LayerState.h | 15 +++++---- services/surfaceflinger/Layer.cpp | 15 ++++----- services/surfaceflinger/SurfaceFlinger.cpp | 12 +++---- .../Tracing/TransactionProtoParser.cpp | 12 +++---- .../layerproto/transactions.proto | 3 +- 7 files changed, 44 insertions(+), 54 deletions(-) diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 2759c58fb1..924e65ec4a 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -40,13 +40,13 @@ layer_state_t::layer_state_t() x(0), y(0), z(0), - alpha(0), flags(0), mask(0), reserved(0), cornerRadius(0.0f), backgroundBlurRadius(0), - transform(0), + color(0), + bufferTransform(0), transformToDisplayInverse(false), crop(Rect::INVALID_RECT), dataspace(ui::Dataspace::UNKNOWN), @@ -83,20 +83,19 @@ status_t layer_state_t::write(Parcel& output) const SAFE_PARCEL(output.writeFloat, y); SAFE_PARCEL(output.writeInt32, z); SAFE_PARCEL(output.writeUint32, layerStack.id); - SAFE_PARCEL(output.writeFloat, alpha); SAFE_PARCEL(output.writeUint32, flags); SAFE_PARCEL(output.writeUint32, mask); SAFE_PARCEL(matrix.write, output); SAFE_PARCEL(output.write, crop); - SAFE_PARCEL(SurfaceControl::writeNullableToParcel, output, reparentSurfaceControl); SAFE_PARCEL(SurfaceControl::writeNullableToParcel, output, relativeLayerSurfaceControl); SAFE_PARCEL(SurfaceControl::writeNullableToParcel, output, parentSurfaceControlForChild); SAFE_PARCEL(output.writeFloat, color.r); SAFE_PARCEL(output.writeFloat, color.g); SAFE_PARCEL(output.writeFloat, color.b); + SAFE_PARCEL(output.writeFloat, color.a); SAFE_PARCEL(windowInfoHandle->writeToParcel, &output); SAFE_PARCEL(output.write, transparentRegion); - SAFE_PARCEL(output.writeUint32, transform); + SAFE_PARCEL(output.writeUint32, bufferTransform); SAFE_PARCEL(output.writeBool, transformToDisplayInverse); SAFE_PARCEL(output.writeBool, borderEnabled); SAFE_PARCEL(output.writeFloat, borderWidth); @@ -177,7 +176,6 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(input.readFloat, &y); SAFE_PARCEL(input.readInt32, &z); SAFE_PARCEL(input.readUint32, &layerStack.id); - SAFE_PARCEL(input.readFloat, &alpha); SAFE_PARCEL(input.readUint32, &flags); @@ -185,7 +183,6 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(matrix.read, input); SAFE_PARCEL(input.read, crop); - SAFE_PARCEL(SurfaceControl::readNullableFromParcel, input, &reparentSurfaceControl); SAFE_PARCEL(SurfaceControl::readNullableFromParcel, input, &relativeLayerSurfaceControl); SAFE_PARCEL(SurfaceControl::readNullableFromParcel, input, &parentSurfaceControlForChild); @@ -197,10 +194,13 @@ status_t layer_state_t::read(const Parcel& input) color.g = tmpFloat; SAFE_PARCEL(input.readFloat, &tmpFloat); color.b = tmpFloat; + SAFE_PARCEL(input.readFloat, &tmpFloat); + color.a = tmpFloat; + SAFE_PARCEL(windowInfoHandle->readFromParcel, &input); SAFE_PARCEL(input.read, transparentRegion); - SAFE_PARCEL(input.readUint32, &transform); + SAFE_PARCEL(input.readUint32, &bufferTransform); SAFE_PARCEL(input.readBool, &transformToDisplayInverse); SAFE_PARCEL(input.readBool, &borderEnabled); SAFE_PARCEL(input.readFloat, &tmpFloat); @@ -453,7 +453,7 @@ void layer_state_t::merge(const layer_state_t& other) { } if (other.what & eAlphaChanged) { what |= eAlphaChanged; - alpha = other.alpha; + color.a = other.color.a; } if (other.what & eMatrixChanged) { what |= eMatrixChanged; @@ -495,12 +495,9 @@ void layer_state_t::merge(const layer_state_t& other) { what |= eReparent; parentSurfaceControlForChild = other.parentSurfaceControlForChild; } - if (other.what & eDestroySurface) { - what |= eDestroySurface; - } - if (other.what & eTransformChanged) { - what |= eTransformChanged; - transform = other.transform; + if (other.what & eBufferTransformChanged) { + what |= eBufferTransformChanged; + bufferTransform = other.bufferTransform; } if (other.what & eTransformToDisplayInverseChanged) { what |= eTransformToDisplayInverseChanged; @@ -547,7 +544,7 @@ void layer_state_t::merge(const layer_state_t& other) { } if (other.what & eBackgroundColorChanged) { what |= eBackgroundColorChanged; - color = other.color; + color.rgb = other.color.rgb; bgColorAlpha = other.bgColorAlpha; bgColorDataspace = other.bgColorDataspace; } @@ -612,7 +609,7 @@ void layer_state_t::merge(const layer_state_t& other) { } if (other.what & eColorChanged) { what |= eColorChanged; - color = other.color; + color.rgb = other.color.rgb; } if (other.what & eColorSpaceAgnosticChanged) { what |= eColorSpaceAgnosticChanged; diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 694bc5af5b..fee45ea693 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -1293,7 +1293,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setAlpha ALOGE("SurfaceComposerClient::Transaction::setAlpha: invalid alpha %f, clamping", alpha); } s->what |= layer_state_t::eAlphaChanged; - s->alpha = std::clamp(alpha, 0.f, 1.f); + s->color.a = std::clamp(alpha, 0.f, 1.f); registerSurfaceControlForCallback(sc); return *this; @@ -1424,7 +1424,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setColor return *this; } s->what |= layer_state_t::eColorChanged; - s->color = color; + s->color.rgb = color; registerSurfaceControlForCallback(sc); return *this; @@ -1439,7 +1439,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBackg } s->what |= layer_state_t::eBackgroundColorChanged; - s->color = color; + s->color.rgb = color; s->bgColorAlpha = alpha; s->bgColorDataspace = dataspace; @@ -1454,8 +1454,8 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setTrans mStatus = BAD_INDEX; return *this; } - s->what |= layer_state_t::eTransformChanged; - s->transform = transform; + s->what |= layer_state_t::eBufferTransformChanged; + s->bufferTransform = transform; registerSurfaceControlForCallback(sc); return *this; diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index c8927ad55a..45272e7431 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -148,12 +148,14 @@ struct layer_state_t { enum { ePositionChanged = 0x00000001, eLayerChanged = 0x00000002, - // unused = 0x00000004, + /* unused = 0x00000004, */ eAlphaChanged = 0x00000008, eMatrixChanged = 0x00000010, eTransparentRegionChanged = 0x00000020, eFlagsChanged = 0x00000040, eLayerStackChanged = 0x00000080, + /* unused = 0x00000100, */ + /* unused = 0x00000200, */ eDimmingEnabledChanged = 0x00000400, eShadowRadiusChanged = 0x00000800, eRenderBorderChanged = 0x00001000, @@ -161,8 +163,8 @@ struct layer_state_t { eRelativeLayerChanged = 0x00004000, eReparent = 0x00008000, eColorChanged = 0x00010000, - eDestroySurface = 0x00020000, - eTransformChanged = 0x00040000, + /* unused = 0x00020000, */ + eBufferTransformChanged = 0x00040000, eTransformToDisplayInverseChanged = 0x00080000, eCropChanged = 0x00100000, eBufferChanged = 0x00200000, @@ -218,25 +220,22 @@ struct layer_state_t { float y; int32_t z; ui::LayerStack layerStack = ui::DEFAULT_LAYER_STACK; - float alpha; uint32_t flags; uint32_t mask; uint8_t reserved; matrix22_t matrix; float cornerRadius; uint32_t backgroundBlurRadius; - sp reparentSurfaceControl; sp relativeLayerSurfaceControl; sp parentSurfaceControlForChild; - half3 color; + half4 color; // non POD must be last. see write/read Region transparentRegion; - - uint32_t transform; + uint32_t bufferTransform; bool transformToDisplayInverse; Rect crop; std::shared_ptr bufferData = nullptr; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 410e43846d..9bb93050dc 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -3633,7 +3633,7 @@ bool Layer::simpleBufferUpdate(const layer_state_t& s) const { } if (s.what & layer_state_t::eAlphaChanged) { - if (mDrawingState.color.a != s.alpha) { + if (mDrawingState.color.a != s.color.a) { ALOGV("%s: false [eAlphaChanged changed]", __func__); return false; } @@ -3677,9 +3677,9 @@ bool Layer::simpleBufferUpdate(const layer_state_t& s) const { } } - if (s.what & layer_state_t::eTransformChanged) { - if (mDrawingState.bufferTransform != s.transform) { - ALOGV("%s: false [eTransformChanged changed]", __func__); + if (s.what & layer_state_t::eBufferTransformChanged) { + if (mDrawingState.bufferTransform != s.bufferTransform) { + ALOGV("%s: false [eBufferTransformChanged changed]", __func__); return false; } } @@ -4177,15 +4177,12 @@ const std::shared_ptr& Layer::getExternalTexture( } bool Layer::setColor(const half3& color) { - if (mDrawingState.color.r == color.r && mDrawingState.color.g == color.g && - mDrawingState.color.b == color.b) { + if (mDrawingState.color.rgb == color) { return false; } mDrawingState.sequence++; - mDrawingState.color.r = color.r; - mDrawingState.color.g = color.g; - mDrawingState.color.b = color.b; + mDrawingState.color.rgb = color; mDrawingState.modified = true; setTransactionFlags(eTransactionNeeded); return true; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 5466497008..3b00ca803e 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4152,12 +4152,10 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime } } if (what & layer_state_t::eAlphaChanged) { - if (layer->setAlpha(s.alpha)) - flags |= eTraversalNeeded; + if (layer->setAlpha(s.color.a)) flags |= eTraversalNeeded; } if (what & layer_state_t::eColorChanged) { - if (layer->setColor(s.color)) - flags |= eTraversalNeeded; + if (layer->setColor(s.color.rgb)) flags |= eTraversalNeeded; } if (what & layer_state_t::eColorTransformChanged) { if (layer->setColorTransform(s.colorTransform)) { @@ -4165,7 +4163,7 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime } } if (what & layer_state_t::eBackgroundColorChanged) { - if (layer->setBackgroundColor(s.color, s.bgColorAlpha, s.bgColorDataspace)) { + if (layer->setBackgroundColor(s.color.rgb, s.bgColorAlpha, s.bgColorDataspace)) { flags |= eTraversalNeeded; } } @@ -4214,8 +4212,8 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime flags |= eTransactionNeeded | eTraversalNeeded | eTransformHintUpdateNeeded; } } - if (what & layer_state_t::eTransformChanged) { - if (layer->setTransform(s.transform)) flags |= eTraversalNeeded; + if (what & layer_state_t::eBufferTransformChanged) { + if (layer->setTransform(s.bufferTransform)) flags |= eTraversalNeeded; } if (what & layer_state_t::eTransformToDisplayInverseChanged) { if (layer->setTransformToDisplayInverse(s.transformToDisplayInverse)) diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp index 267f3d0219..3418c82f9e 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp @@ -111,7 +111,7 @@ proto::LayerState TransactionProtoParser::toProto(const layer_state_t& layer) { } if (layer.what & layer_state_t::eAlphaChanged) { - proto.set_alpha(layer.alpha); + proto.set_alpha(layer.color.a); } if (layer.what & layer_state_t::eColorChanged) { @@ -123,8 +123,8 @@ proto::LayerState TransactionProtoParser::toProto(const layer_state_t& layer) { if (layer.what & layer_state_t::eTransparentRegionChanged) { LayerProtoHelper::writeToProto(layer.transparentRegion, proto.mutable_transparent_region()); } - if (layer.what & layer_state_t::eTransformChanged) { - proto.set_transform(layer.transform); + if (layer.what & layer_state_t::eBufferTransformChanged) { + proto.set_transform(layer.bufferTransform); } if (layer.what & layer_state_t::eTransformToDisplayInverseChanged) { proto.set_transform_to_display_inverse(layer.transformToDisplayInverse); @@ -395,7 +395,7 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta } if (proto.what() & layer_state_t::eAlphaChanged) { - layer.alpha = proto.alpha(); + layer.color.a = proto.alpha(); } if (proto.what() & layer_state_t::eColorChanged) { @@ -407,8 +407,8 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta if (proto.what() & layer_state_t::eTransparentRegionChanged) { LayerProtoHelper::readFromProto(proto.transparent_region(), layer.transparentRegion); } - if (proto.what() & layer_state_t::eTransformChanged) { - layer.transform = proto.transform(); + if (proto.what() & layer_state_t::eBufferTransformChanged) { + layer.bufferTransform = proto.transform(); } if (proto.what() & layer_state_t::eTransformToDisplayInverseChanged) { layer.transformToDisplayInverse = proto.transform_to_display_inverse(); diff --git a/services/surfaceflinger/layerproto/transactions.proto b/services/surfaceflinger/layerproto/transactions.proto index b687abc918..4c6a9cf85b 100644 --- a/services/surfaceflinger/layerproto/transactions.proto +++ b/services/surfaceflinger/layerproto/transactions.proto @@ -98,8 +98,7 @@ message LayerState { eReparent = 0x00008000; eColorChanged = 0x00010000; - eDestroySurface = 0x00020000; - eTransformChanged = 0x00040000; + eBufferTransformChanged = 0x00040000; eTransformToDisplayInverseChanged = 0x00080000; eCropChanged = 0x00100000; -- GitLab From d71299ebf092a0ace157915d865ca2e2ef025357 Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Fri, 19 Aug 2022 17:37:01 -0700 Subject: [PATCH 0358/1310] Remove vsync injection methods These methods are no longer used. Bug: 169865816 Bug: 241285477 Test: atest libgui_test libsurfaceflinger_unittest SurfaceFlinger_test Change-Id: I773570802134e68289db483cf3c22f3125ace472 --- libs/gui/SurfaceComposerClient.cpp | 12 - .../aidl/android/gui/ISurfaceComposer.aidl | 4 - libs/gui/fuzzer/libgui_fuzzer_utils.h | 2 - .../libgui_surfaceComposerClient_fuzzer.cpp | 4 - libs/gui/include/gui/SurfaceComposerClient.h | 4 - libs/gui/tests/Surface_test.cpp | 4 - .../Scheduler/InjectVSyncSource.h | 57 - .../surfaceflinger/Scheduler/MessageQueue.cpp | 56 - .../surfaceflinger/Scheduler/MessageQueue.h | 11 - .../surfaceflinger/Scheduler/Scheduler.cpp | 38 - services/surfaceflinger/Scheduler/Scheduler.h | 10 - services/surfaceflinger/SurfaceFlinger.cpp | 45 - services/surfaceflinger/SurfaceFlinger.h | 6 - .../fuzzer/surfaceflinger_fuzzers_utils.h | 2 - services/surfaceflinger/tests/Android.bp | 1 - .../surfaceflinger/tests/fakehwc/Android.bp | 64 - .../tests/fakehwc/FakeComposerClient.cpp | 929 --------- .../tests/fakehwc/FakeComposerClient.h | 301 --- .../tests/fakehwc/FakeComposerService.cpp | 179 -- .../tests/fakehwc/FakeComposerService.h | 92 - .../tests/fakehwc/FakeComposerUtils.cpp | 193 -- .../tests/fakehwc/FakeComposerUtils.h | 115 -- .../tests/fakehwc/MockComposerHal.h | 47 - .../tests/fakehwc/RenderState.h | 42 - .../tests/fakehwc/SFFakeHwc_test.cpp | 1794 ----------------- 25 files changed, 4012 deletions(-) delete mode 100644 services/surfaceflinger/Scheduler/InjectVSyncSource.h delete mode 100644 services/surfaceflinger/tests/fakehwc/Android.bp delete mode 100644 services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp delete mode 100644 services/surfaceflinger/tests/fakehwc/FakeComposerClient.h delete mode 100644 services/surfaceflinger/tests/fakehwc/FakeComposerService.cpp delete mode 100644 services/surfaceflinger/tests/fakehwc/FakeComposerService.h delete mode 100644 services/surfaceflinger/tests/fakehwc/FakeComposerUtils.cpp delete mode 100644 services/surfaceflinger/tests/fakehwc/FakeComposerUtils.h delete mode 100644 services/surfaceflinger/tests/fakehwc/MockComposerHal.h delete mode 100644 services/surfaceflinger/tests/fakehwc/RenderState.h delete mode 100644 services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 694bc5af5b..81b4d3d85f 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2237,18 +2237,6 @@ status_t SurfaceComposerClient::getLayerFrameStats(const sp& token, // ---------------------------------------------------------------------------- -status_t SurfaceComposerClient::enableVSyncInjections(bool enable) { - sp sf(ComposerServiceAIDL::getComposerService()); - binder::Status status = sf->enableVSyncInjections(enable); - return statusTFromBinderStatus(status); -} - -status_t SurfaceComposerClient::injectVSync(nsecs_t when) { - sp sf(ComposerServiceAIDL::getComposerService()); - binder::Status status = sf->injectVSync(when); - return statusTFromBinderStatus(status); -} - status_t SurfaceComposerClient::getDisplayState(const sp& display, ui::DisplayState* state) { gui::DisplayState ds; diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index ac1442b73e..ca0b97f8db 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -224,10 +224,6 @@ interface ISurfaceComposer { */ PullAtomData onPullAtom(int atomId); - oneway void enableVSyncInjections(boolean enable); - - oneway void injectVSync(long when); - /** * Gets the list of active layers in Z order for debugging purposes * diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h index 433b04469b..315b92503f 100644 --- a/libs/gui/fuzzer/libgui_fuzzer_utils.h +++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h @@ -102,8 +102,6 @@ public: MOCK_METHOD(binder::Status, overrideHdrTypes, (const sp&, const std::vector&), (override)); MOCK_METHOD(binder::Status, onPullAtom, (int32_t, gui::PullAtomData*), (override)); - MOCK_METHOD(binder::Status, enableVSyncInjections, (bool), (override)); - MOCK_METHOD(binder::Status, injectVSync, (int64_t), (override)); MOCK_METHOD(binder::Status, getLayerDebugInfo, (std::vector*), (override)); MOCK_METHOD(binder::Status, getColorManagement, (bool*), (override)); MOCK_METHOD(binder::Status, getCompositionPreference, (gui::CompositionPreference*), diff --git a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp index 5bd65316dd..eecbe0fe21 100644 --- a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp +++ b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp @@ -271,10 +271,6 @@ void SurfaceComposerClientFuzzer::invokeSurfaceComposerClient() { sp surfaceParent( new Surface(producer, mFdp.ConsumeBool() /*controlledByApp*/, handle)); - SurfaceComposerClient::enableVSyncInjections(mFdp.ConsumeBool() /*secure*/); - nsecs_t when = mFdp.ConsumeIntegral(); - SurfaceComposerClient::injectVSync(when); - fuzzOnPullAtom(); SurfaceComposerClient::setDisplayContentSamplingEnabled(displayToken, mFdp.ConsumeBool() /*enable*/, diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 04d3ab252d..d138d6832b 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -358,10 +358,6 @@ public: //! Get token for a physical display given its stable ID static sp getPhysicalDisplayToken(PhysicalDisplayId displayId); - static status_t enableVSyncInjections(bool enable); - - static status_t injectVSync(nsecs_t when); - struct SCHash { std::size_t operator()(const sp& sc) const { return std::hash{}(sc.get()); diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index d5cc55f7fd..72d76ee99c 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -851,10 +851,6 @@ public: return binder::Status::ok(); } - binder::Status enableVSyncInjections(bool /*enable*/) override { return binder::Status::ok(); } - - binder::Status injectVSync(int64_t /*when*/) override { return binder::Status::ok(); } - binder::Status getLayerDebugInfo(std::vector* /*outLayers*/) override { return binder::Status::ok(); } diff --git a/services/surfaceflinger/Scheduler/InjectVSyncSource.h b/services/surfaceflinger/Scheduler/InjectVSyncSource.h deleted file mode 100644 index 760a4ee886..0000000000 --- a/services/surfaceflinger/Scheduler/InjectVSyncSource.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2018 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 - -#include "EventThread.h" - -namespace android { - -/** - * VSync signals used during SurfaceFlinger trace playback (traces we captured - * with SurfaceInterceptor). - */ -class InjectVSyncSource final : public VSyncSource { -public: - ~InjectVSyncSource() override = default; - - void setCallback(VSyncSource::Callback* callback) override { - std::lock_guard lock(mCallbackMutex); - mCallback = callback; - } - - void onInjectSyncEvent(nsecs_t when, nsecs_t expectedVSyncTimestamp, - nsecs_t deadlineTimestamp) { - std::lock_guard lock(mCallbackMutex); - if (mCallback) { - mCallback->onVSyncEvent(when, {expectedVSyncTimestamp, deadlineTimestamp}); - } - } - - const char* getName() const override { return "inject"; } - void setVSyncEnabled(bool) override {} - void setDuration(std::chrono::nanoseconds, std::chrono::nanoseconds) override {} - VSyncData getLatestVSyncData() const override { return {}; } - void dump(std::string&) const override {} - -private: - std::mutex mCallbackMutex; - VSyncSource::Callback* mCallback GUARDED_BY(mCallbackMutex) = nullptr; -}; - -} // namespace android diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp index e4e65b4828..ae10ff4989 100644 --- a/services/surfaceflinger/Scheduler/MessageQueue.cpp +++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp @@ -57,37 +57,6 @@ MessageQueue::MessageQueue(ICompositor& compositor, sp handler) mLooper(sp::make(kAllowNonCallbacks)), mHandler(std::move(handler)) {} -// TODO(b/169865816): refactor VSyncInjections to use MessageQueue directly -// and remove the EventThread from MessageQueue -void MessageQueue::setInjector(sp connection) { - auto& tube = mInjector.tube; - - if (const int fd = tube.getFd(); fd >= 0) { - mLooper->removeFd(fd); - } - - if (connection) { - // The EventThreadConnection is retained when disabling injection, so avoid subsequently - // stealing invalid FDs. Note that the stolen FDs are kept open. - if (tube.getFd() < 0) { - connection->stealReceiveChannel(&tube); - } else { - ALOGW("Recycling channel for VSYNC injection."); - } - - mLooper->addFd( - tube.getFd(), 0, Looper::EVENT_INPUT, - [](int, int, void* data) { - reinterpret_cast(data)->injectorCallback(); - return 1; // Keep registration. - }, - this); - } - - std::lock_guard lock(mInjector.mutex); - mInjector.connection = std::move(connection); -} - void MessageQueue::vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, nsecs_t readyTime) { ATRACE_CALL(); // Trace VSYNC-sf @@ -174,15 +143,6 @@ void MessageQueue::scheduleConfigure() { void MessageQueue::scheduleFrame() { ATRACE_CALL(); - { - std::lock_guard lock(mInjector.mutex); - if (CC_UNLIKELY(mInjector.connection)) { - ALOGD("%s while injecting VSYNC", __func__); - mInjector.connection->requestNextVsync(); - return; - } - } - std::lock_guard lock(mVsync.mutex); mVsync.scheduledFrameTime = mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(), @@ -190,22 +150,6 @@ void MessageQueue::scheduleFrame() { .earliestVsync = mVsync.lastCallbackTime.ns()}); } -void MessageQueue::injectorCallback() { - ssize_t n; - DisplayEventReceiver::Event buffer[8]; - while ((n = DisplayEventReceiver::getEvents(&mInjector.tube, buffer, 8)) > 0) { - for (int i = 0; i < n; i++) { - if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) { - auto& vsync = buffer[i].vsync.vsyncData; - mHandler->dispatchFrame(VsyncId{vsync.preferredVsyncId()}, - TimePoint::fromNs( - vsync.preferredExpectedPresentationTime())); - break; - } - } - } -} - auto MessageQueue::getScheduledFrameTime() const -> std::optional { if (mHandler->isFramePending()) { return Clock::now(); diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h index 899233a151..04de492479 100644 --- a/services/surfaceflinger/Scheduler/MessageQueue.h +++ b/services/surfaceflinger/Scheduler/MessageQueue.h @@ -76,7 +76,6 @@ public: virtual void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&, std::chrono::nanoseconds workDuration) = 0; virtual void setDuration(std::chrono::nanoseconds workDuration) = 0; - virtual void setInjector(sp) = 0; virtual void waitMessage() = 0; virtual void postMessage(sp&&) = 0; virtual void scheduleConfigure() = 0; @@ -132,16 +131,7 @@ private: TracedOrdinal value = {"VSYNC-sf", 0}; }; - struct Injector { - gui::BitTube tube; - std::mutex mutex; - sp connection GUARDED_BY(mutex); - }; - Vsync mVsync; - Injector mInjector; - - void injectorCallback(); public: explicit MessageQueue(ICompositor&); @@ -149,7 +139,6 @@ public: void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&, std::chrono::nanoseconds workDuration) override; void setDuration(std::chrono::nanoseconds workDuration) override; - void setInjector(sp) override; void waitMessage() override; void postMessage(sp&&) override; diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 72b65455b0..037ed8f689 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -43,7 +43,6 @@ #include "DispSyncSource.h" #include "EventThread.h" #include "FrameRateOverrideMappings.h" -#include "InjectVSyncSource.h" #include "OneShotTimer.h" #include "SurfaceFlingerProperties.h" #include "VSyncPredictor.h" @@ -403,43 +402,6 @@ void Scheduler::setDuration(ConnectionHandle handle, std::chrono::nanoseconds wo thread->setDuration(workDuration, readyDuration); } -ConnectionHandle Scheduler::enableVSyncInjection(bool enable) { - if (mInjectVSyncs == enable) { - return {}; - } - - ALOGV("%s VSYNC injection", enable ? "Enabling" : "Disabling"); - - if (!mInjectorConnectionHandle) { - auto vsyncSource = std::make_unique(); - mVSyncInjector = vsyncSource.get(); - - auto eventThread = - std::make_unique(std::move(vsyncSource), - /*tokenManager=*/nullptr, - impl::EventThread::ThrottleVsyncCallback(), - impl::EventThread::GetVsyncPeriodFunction()); - - // EventThread does not dispatch VSYNC unless the display is connected and powered on. - eventThread->onHotplugReceived(PhysicalDisplayId::fromPort(0), true); - eventThread->onScreenAcquired(); - - mInjectorConnectionHandle = createConnection(std::move(eventThread)); - } - - mInjectVSyncs = enable; - return mInjectorConnectionHandle; -} - -bool Scheduler::injectVSync(nsecs_t when, nsecs_t expectedVSyncTime, nsecs_t deadlineTimestamp) { - if (!mInjectVSyncs || !mVSyncInjector) { - return false; - } - - mVSyncInjector->onInjectSyncEvent(when, expectedVSyncTime, deadlineTimestamp); - return true; -} - void Scheduler::enableHardwareVsync() { std::lock_guard lock(mHWVsyncLock); if (!mPrimaryHWVsyncEnabled && mHWVsyncAvailable) { diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 4c4956291d..609cebce08 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -78,7 +78,6 @@ struct hash { namespace android { class FenceTime; -class InjectVSyncSource; namespace frametimeline { class TokenManager; @@ -144,7 +143,6 @@ public: void createVsyncSchedule(FeatureFlags); using Impl::initVsync; - using Impl::setInjector; using Impl::getScheduledFrameTime; using Impl::setDuration; @@ -182,10 +180,6 @@ public: void setDuration(ConnectionHandle, std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration); - // Returns injector handle if injection has toggled, or an invalid handle otherwise. - ConnectionHandle enableVSyncInjection(bool enable); - // Returns false if injection is disabled. - bool injectVSync(nsecs_t when, nsecs_t expectedVSyncTime, nsecs_t deadlineTimestamp); void enableHardwareVsync(); void disableHardwareVsync(bool makeUnavailable); @@ -342,10 +336,6 @@ private: mutable std::mutex mConnectionsLock; std::unordered_map mConnections GUARDED_BY(mConnectionsLock); - bool mInjectVSyncs = false; - InjectVSyncSource* mVSyncInjector = nullptr; - ConnectionHandle mInjectorConnectionHandle; - mutable std::mutex mHWVsyncLock; bool mPrimaryHWVsyncEnabled GUARDED_BY(mHWVsyncLock) = false; bool mHWVsyncAvailable GUARDED_BY(mHWVsyncLock) = false; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 5466497008..6167378614 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1542,27 +1542,6 @@ status_t SurfaceFlinger::isWideColorDisplay(const sp& displayToken, return NO_ERROR; } -status_t SurfaceFlinger::enableVSyncInjections(bool enable) { - auto future = mScheduler->schedule([=] { - Mutex::Autolock lock(mStateLock); - - if (const auto handle = mScheduler->enableVSyncInjection(enable)) { - mScheduler->setInjector(enable ? mScheduler->getEventConnection(handle) : nullptr); - } - }); - - future.wait(); - return NO_ERROR; -} - -status_t SurfaceFlinger::injectVSync(nsecs_t when) { - Mutex::Autolock lock(mStateLock); - const nsecs_t expectedPresentTime = calculateExpectedPresentTime(TimePoint::fromNs(when)).ns(); - const nsecs_t deadlineTimestamp = expectedPresentTime; - return mScheduler->injectVSync(when, expectedPresentTime, deadlineTimestamp) ? NO_ERROR - : BAD_VALUE; -} - status_t SurfaceFlinger::getLayerDebugInfo(std::vector* outLayers) { outLayers->clear(); auto future = mScheduler->schedule([=] { @@ -7375,30 +7354,6 @@ binder::Status SurfaceComposerAIDL::onPullAtom(int32_t atomId, gui::PullAtomData return binderStatusFromStatusT(status); } -binder::Status SurfaceComposerAIDL::enableVSyncInjections(bool enable) { - if (!mFlinger->hasMockHwc()) { - return binderStatusFromStatusT(PERMISSION_DENIED); - } - - status_t status = checkAccessPermission(); - if (status == OK) { - status = mFlinger->enableVSyncInjections(enable); - } - return binderStatusFromStatusT(status); -} - -binder::Status SurfaceComposerAIDL::injectVSync(int64_t when) { - if (!mFlinger->hasMockHwc()) { - return binderStatusFromStatusT(PERMISSION_DENIED); - } - - status_t status = checkAccessPermission(); - if (status == OK) { - status = mFlinger->injectVSync(when); - } - return binderStatusFromStatusT(status); -} - binder::Status SurfaceComposerAIDL::getLayerDebugInfo(std::vector* outLayers) { if (!outLayers) { return binderStatusFromStatusT(UNEXPECTED_NULL); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 1bc45d9b76..c4c5b561db 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -530,8 +530,6 @@ private: status_t overrideHdrTypes(const sp& displayToken, const std::vector& hdrTypes); status_t onPullAtom(const int32_t atomId, std::string* pulledData, bool* success); - status_t enableVSyncInjections(bool enable); - status_t injectVSync(nsecs_t when); status_t getLayerDebugInfo(std::vector* outLayers); status_t getColorManagement(bool* outGetColorManagement) const; status_t getCompositionPreference(ui::Dataspace* outDataspace, ui::PixelFormat* outPixelFormat, @@ -1262,8 +1260,6 @@ private: const std::string mHwcServiceName; - bool hasMockHwc() const { return mHwcServiceName == "mock"; } - /* * Scheduler */ @@ -1433,8 +1429,6 @@ public: binder::Status overrideHdrTypes(const sp& display, const std::vector& hdrTypes) override; binder::Status onPullAtom(int32_t atomId, gui::PullAtomData* outPullData) override; - binder::Status enableVSyncInjections(bool enable) override; - binder::Status injectVSync(int64_t when) override; binder::Status getLayerDebugInfo(std::vector* outLayers) override; binder::Status getColorManagement(bool* outGetColorManagement) override; binder::Status getCompositionPreference(gui::CompositionPreference* outPref) override; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 03630c9db5..ed2fe23669 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -576,8 +576,6 @@ public: onPullAtom(&mFdp); - mFlinger->injectVSync(mFdp.ConsumeIntegral()); - getCompositionPreference(); getDisplayedContentSample(display, &mFdp); getDesiredDisplayModeSpecs(display); diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp index 71b1c0eeb1..bd6367d672 100644 --- a/services/surfaceflinger/tests/Android.bp +++ b/services/surfaceflinger/tests/Android.bp @@ -126,7 +126,6 @@ cc_test { } subdirs = [ - "fakehwc", "hwc2", "unittests", "utils", diff --git a/services/surfaceflinger/tests/fakehwc/Android.bp b/services/surfaceflinger/tests/fakehwc/Android.bp deleted file mode 100644 index 0d40e7085a..0000000000 --- a/services/surfaceflinger/tests/fakehwc/Android.bp +++ /dev/null @@ -1,64 +0,0 @@ -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_test { - name: "sffakehwc_test", - defaults: [ - "android.hardware.graphics.composer3-ndk_shared", - "surfaceflinger_defaults", - ], - test_suites: ["device-tests"], - srcs: [ - "FakeComposerClient.cpp", - "FakeComposerService.cpp", - "FakeComposerUtils.cpp", - "SFFakeHwc_test.cpp", - ], - require_root: true, - shared_libs: [ - "android.hardware.graphics.composer@2.1", - "android.hardware.graphics.composer@2.2", - "android.hardware.graphics.composer@2.3", - "android.hardware.graphics.composer@2.4", - "android.hardware.graphics.mapper@2.0", - "android.hardware.graphics.mapper@3.0", - "android.hardware.graphics.mapper@4.0", - "android.hardware.power@1.3", - "android.hardware.power-V2-cpp", - "libbase", - "libbinder", - "libbinder_ndk", - "libcutils", - "libfmq", - "libgui", - "libhidlbase", - "liblayers_proto", - "liblog", - "libnativewindow", - "libsync", - "libtimestats", - "libui", - "libutils", - ], - static_libs: [ - "android.hardware.graphics.composer@2.1-resources", - "libaidlcommonsupport", - "libcompositionengine", - "libgmock", - "libperfetto_client_experimental", - "librenderengine", - "libaidlcommonsupport", - ], - header_libs: [ - "android.hardware.graphics.composer@2.4-command-buffer", - "android.hardware.graphics.composer@2.4-hal", - "android.hardware.graphics.composer3-command-buffer", - "libsurfaceflinger_headers", - ], -} diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp b/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp deleted file mode 100644 index a5cca3597c..0000000000 --- a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp +++ /dev/null @@ -1,929 +0,0 @@ -/* - * Copyright (C) 2017 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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - -//#define LOG_NDEBUG 0 -#undef LOG_TAG -#define LOG_TAG "FakeComposer" - -#include "FakeComposerClient.h" - -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -constexpr Config NULL_DISPLAY_CONFIG = static_cast(0); - -using namespace sftest; - -using android::Condition; -using android::Mutex; - -using Clock = std::chrono::steady_clock; -using TimePoint = std::chrono::time_point; - -namespace { - -// Internal state of a layer in the HWC API. -class LayerImpl { -public: - LayerImpl() = default; - - bool mValid = true; - RenderState mRenderState; - uint32_t mZ = 0; -}; - -// Struct for storing per frame rectangle state. Contains the render -// state shared to the test case. Basically a snapshot and a subset of -// LayerImpl sufficient to re-create the pixels of a layer for the -// frame. -struct FrameRect { -public: - FrameRect(Layer layer_, const RenderState& state, uint32_t z_) - : layer(layer_), renderState(state), z(z_) {} - - const Layer layer; - const RenderState renderState; - const uint32_t z; -}; - -// Collection of FrameRects forming one rendered frame. Could store -// related fences and other data in the future. -class Frame { -public: - Frame() = default; - std::vector> rectangles; -}; - -class DelayedEventGenerator { -public: - explicit DelayedEventGenerator(std::function onTimerExpired) - : mOnTimerExpired(onTimerExpired) { - mThread = std::thread([this]() { loop(); }); - } - - ~DelayedEventGenerator() { - ALOGI("DelayedEventGenerator exiting."); - { - std::unique_lock lock(mMutex); - mRunning = false; - mWakeups.clear(); - mCondition.notify_one(); - } - mThread.join(); - ALOGI("DelayedEventGenerator exited."); - } - - void wakeAfter(std::chrono::nanoseconds waitTime) { - std::unique_lock lock(mMutex); - mWakeups.insert(Clock::now() + waitTime); - mCondition.notify_one(); - } - -private: - void loop() { - while (true) { - // Lock scope - { - std::unique_lock lock(mMutex); - mCondition.wait(lock, [this]() { return !mRunning || !mWakeups.empty(); }); - if (!mRunning && mWakeups.empty()) { - // This thread should only exit once the destructor has been called and all - // wakeups have been processed - return; - } - - // At this point, mWakeups will not be empty - - TimePoint target = *(mWakeups.begin()); - auto status = mCondition.wait_until(lock, target); - while (status == std::cv_status::no_timeout) { - // This was either a spurious wakeup or another wakeup was added, so grab the - // oldest point and wait again - target = *(mWakeups.begin()); - status = mCondition.wait_until(lock, target); - } - - // status must have been timeout, so we can finally clear this point - mWakeups.erase(target); - } - // Callback *without* locks! - mOnTimerExpired(); - } - } - - std::function mOnTimerExpired; - std::thread mThread; - std::mutex mMutex; - std::condition_variable mCondition; - bool mRunning = true; - std::set mWakeups; -}; - -} // namespace - -FakeComposerClient::FakeComposerClient() - : mEventCallback(nullptr), - mEventCallback_2_4(nullptr), - mCurrentConfig(NULL_DISPLAY_CONFIG), - mVsyncEnabled(false), - mLayers(), - mDelayedEventGenerator( - std::make_unique([this]() { this->requestVSync(); })), - mSurfaceComposer(nullptr) {} - -FakeComposerClient::~FakeComposerClient() {} - -bool FakeComposerClient::hasCapability(hwc2_capability_t /*capability*/) { - return false; -} - -std::string FakeComposerClient::dumpDebugInfo() { - return {}; -} - -void FakeComposerClient::registerEventCallback(EventCallback* callback) { - ALOGV("registerEventCallback"); - LOG_FATAL_IF(mEventCallback_2_4 != nullptr, - "already registered using registerEventCallback_2_4"); - - mEventCallback = callback; - if (mEventCallback) { - mEventCallback->onHotplug(PRIMARY_DISPLAY, IComposerCallback::Connection::CONNECTED); - } -} - -void FakeComposerClient::unregisterEventCallback() { - ALOGV("unregisterEventCallback"); - mEventCallback = nullptr; -} - -void FakeComposerClient::hotplugDisplay(Display display, IComposerCallback::Connection state) { - if (mEventCallback) { - mEventCallback->onHotplug(display, state); - } else if (mEventCallback_2_4) { - mEventCallback_2_4->onHotplug(display, state); - } -} - -void FakeComposerClient::refreshDisplay(Display display) { - if (mEventCallback) { - mEventCallback->onRefresh(display); - } else if (mEventCallback_2_4) { - mEventCallback_2_4->onRefresh(display); - } -} - -uint32_t FakeComposerClient::getMaxVirtualDisplayCount() { - ALOGV("getMaxVirtualDisplayCount"); - return 1; -} - -V2_1::Error FakeComposerClient::createVirtualDisplay(uint32_t /*width*/, uint32_t /*height*/, - V1_0::PixelFormat* /*format*/, - Display* /*outDisplay*/) { - ALOGV("createVirtualDisplay"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::destroyVirtualDisplay(Display /*display*/) { - ALOGV("destroyVirtualDisplay"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::createLayer(Display /*display*/, Layer* outLayer) { - ALOGV("createLayer"); - *outLayer = mLayers.size(); - auto newLayer = std::make_unique(); - mLayers.push_back(std::move(newLayer)); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::destroyLayer(Display /*display*/, Layer layer) { - ALOGV("destroyLayer"); - mLayers[layer]->mValid = false; - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::getActiveConfig(Display display, Config* outConfig) { - ALOGV("getActiveConfig"); - if (mMockHal) { - return mMockHal->getActiveConfig(display, outConfig); - } - - // TODO Assert outConfig != nullptr - - // TODO This is my reading of the - // IComposerClient::getActiveConfig, but returning BAD_CONFIG - // seems to not fit SurfaceFlinger plans. See version 2 below. - // if (mCurrentConfig == NULL_DISPLAY_CONFIG) { - // return V2_1::Error::BAD_CONFIG; - // } - //*outConfig = mCurrentConfig; - *outConfig = 1; // Very special config for you my friend - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::getClientTargetSupport(Display /*display*/, uint32_t /*width*/, - uint32_t /*height*/, - V1_0::PixelFormat /*format*/, - V1_0::Dataspace /*dataspace*/) { - ALOGV("getClientTargetSupport"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::getColorModes(Display /*display*/, - hidl_vec* /*outModes*/) { - ALOGV("getColorModes"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::getDisplayAttribute(Display display, Config config, - V2_1::IComposerClient::Attribute attribute, - int32_t* outValue) { - auto tmpError = - getDisplayAttribute_2_4(display, config, - static_cast(attribute), outValue); - return static_cast(tmpError); -} - -V2_1::Error FakeComposerClient::getDisplayConfigs(Display display, hidl_vec* outConfigs) { - ALOGV("getDisplayConfigs"); - if (mMockHal) { - return mMockHal->getDisplayConfigs(display, outConfigs); - } - - // TODO assert display == 1, outConfigs != nullptr - - outConfigs->resize(1); - (*outConfigs)[0] = 1; - - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::getDisplayName(Display /*display*/, hidl_string* /*outName*/) { - ALOGV("getDisplayName"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::getDisplayType(Display /*display*/, - IComposerClient::DisplayType* outType) { - ALOGV("getDisplayType"); - // TODO: This setting nothing on the output had no effect on initial trials. Is first display - // assumed to be physical? - *outType = static_cast(HWC2_DISPLAY_TYPE_PHYSICAL); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::getDozeSupport(Display /*display*/, bool* /*outSupport*/) { - ALOGV("getDozeSupport"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::getHdrCapabilities(Display /*display*/, - hidl_vec* /*outTypes*/, - float* /*outMaxLuminance*/, - float* /*outMaxAverageLuminance*/, - float* /*outMinLuminance*/) { - ALOGV("getHdrCapabilities"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setActiveConfig(Display display, Config config) { - ALOGV("setActiveConfig"); - if (mMockHal) { - return mMockHal->setActiveConfig(display, config); - } - mCurrentConfig = config; - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setColorMode(Display /*display*/, V1_0::ColorMode /*mode*/) { - ALOGV("setColorMode"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setPowerMode(Display /*display*/, - V2_1::IComposerClient::PowerMode /*mode*/) { - ALOGV("setPowerMode"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setVsyncEnabled(Display /*display*/, - IComposerClient::Vsync enabled) { - mVsyncEnabled = (enabled == IComposerClient::Vsync::ENABLE); - ALOGV("setVsyncEnabled(%s)", mVsyncEnabled ? "ENABLE" : "DISABLE"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setColorTransform(Display /*display*/, const float* /*matrix*/, - int32_t /*hint*/) { - ALOGV("setColorTransform"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setClientTarget(Display /*display*/, buffer_handle_t /*target*/, - int32_t /*acquireFence*/, int32_t /*dataspace*/, - const std::vector& /*damage*/) { - ALOGV("setClientTarget"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setOutputBuffer(Display /*display*/, buffer_handle_t /*buffer*/, - int32_t /*releaseFence*/) { - ALOGV("setOutputBuffer"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::validateDisplay( - Display /*display*/, std::vector* /*outChangedLayers*/, - std::vector* /*outCompositionTypes*/, - uint32_t* /*outDisplayRequestMask*/, std::vector* /*outRequestedLayers*/, - std::vector* /*outRequestMasks*/) { - ALOGV("validateDisplay"); - // TODO: Assume touching nothing means All Korrekt! - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::acceptDisplayChanges(Display /*display*/) { - ALOGV("acceptDisplayChanges"); - // Didn't ask for changes because software is omnipotent. - return V2_1::Error::NONE; -} - -bool layerZOrdering(const std::unique_ptr& a, const std::unique_ptr& b) { - return a->z <= b->z; -} - -V2_1::Error FakeComposerClient::presentDisplay(Display /*display*/, int32_t* /*outPresentFence*/, - std::vector* /*outLayers*/, - std::vector* /*outReleaseFences*/) { - ALOGV("presentDisplay"); - // TODO Leaving layers and their fences out for now. Doing so - // means that we've already processed everything. Important to - // test that the fences are respected, though. (How?) - - std::unique_ptr newFrame(new Frame); - for (uint64_t layer = 0; layer < mLayers.size(); layer++) { - const LayerImpl& layerImpl = *mLayers[layer]; - - if (!layerImpl.mValid) continue; - - auto rect = std::make_unique(layer, layerImpl.mRenderState, layerImpl.mZ); - newFrame->rectangles.push_back(std::move(rect)); - } - std::sort(newFrame->rectangles.begin(), newFrame->rectangles.end(), layerZOrdering); - { - Mutex::Autolock _l(mStateMutex); - mFrames.push_back(std::move(newFrame)); - mFramesAvailable.broadcast(); - } - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setLayerCursorPosition(Display /*display*/, Layer /*layer*/, - int32_t /*x*/, int32_t /*y*/) { - ALOGV("setLayerCursorPosition"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setLayerBuffer(Display /*display*/, Layer layer, - buffer_handle_t buffer, int32_t acquireFence) { - ALOGV("setLayerBuffer"); - LayerImpl& l = getLayerImpl(layer); - if (buffer != l.mRenderState.mBuffer) { - l.mRenderState.mSwapCount++; // TODO: Is setting to same value a swap or not? - } - l.mRenderState.mBuffer = buffer; - l.mRenderState.mAcquireFence = acquireFence; - - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setLayerSurfaceDamage(Display /*display*/, Layer /*layer*/, - const std::vector& /*damage*/) { - ALOGV("setLayerSurfaceDamage"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setLayerBlendMode(Display /*display*/, Layer layer, int32_t mode) { - ALOGV("setLayerBlendMode"); - getLayerImpl(layer).mRenderState.mBlendMode = static_cast(mode); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setLayerColor(Display /*display*/, Layer layer, - IComposerClient::Color color) { - ALOGV("setLayerColor"); - getLayerImpl(layer).mRenderState.mLayerColor.r = color.r; - getLayerImpl(layer).mRenderState.mLayerColor.g = color.g; - getLayerImpl(layer).mRenderState.mLayerColor.b = color.b; - getLayerImpl(layer).mRenderState.mLayerColor.a = color.a; - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setLayerCompositionType(Display /*display*/, Layer /*layer*/, - int32_t /*type*/) { - ALOGV("setLayerCompositionType"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setLayerDataspace(Display /*display*/, Layer /*layer*/, - int32_t /*dataspace*/) { - ALOGV("setLayerDataspace"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setLayerDisplayFrame(Display /*display*/, Layer layer, - const hwc_rect_t& frame) { - ALOGV("setLayerDisplayFrame (%d, %d, %d, %d)", frame.left, frame.top, frame.right, - frame.bottom); - getLayerImpl(layer).mRenderState.mDisplayFrame = frame; - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setLayerPlaneAlpha(Display /*display*/, Layer layer, float alpha) { - ALOGV("setLayerPlaneAlpha"); - getLayerImpl(layer).mRenderState.mPlaneAlpha = alpha; - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setLayerSidebandStream(Display /*display*/, Layer /*layer*/, - buffer_handle_t /*stream*/) { - ALOGV("setLayerSidebandStream"); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setLayerSourceCrop(Display /*display*/, Layer layer, - const hwc_frect_t& crop) { - ALOGV("setLayerSourceCrop"); - getLayerImpl(layer).mRenderState.mSourceCrop = crop; - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setLayerTransform(Display /*display*/, Layer layer, - int32_t transform) { - ALOGV("setLayerTransform"); - getLayerImpl(layer).mRenderState.mTransform = static_cast(transform); - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setLayerVisibleRegion(Display /*display*/, Layer layer, - const std::vector& visible) { - ALOGV("setLayerVisibleRegion"); - getLayerImpl(layer).mRenderState.mVisibleRegion = visible; - return V2_1::Error::NONE; -} - -V2_1::Error FakeComposerClient::setLayerZOrder(Display /*display*/, Layer layer, uint32_t z) { - ALOGV("setLayerZOrder"); - getLayerImpl(layer).mZ = z; - return V2_1::Error::NONE; -} - -// Composer 2.2 -V2_1::Error FakeComposerClient::getPerFrameMetadataKeys( - Display /*display*/, std::vector* /*outKeys*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::setLayerPerFrameMetadata( - Display /*display*/, Layer /*layer*/, - const std::vector& /*metadata*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::getReadbackBufferAttributes( - Display /*display*/, graphics::common::V1_1::PixelFormat* /*outFormat*/, - graphics::common::V1_1::Dataspace* /*outDataspace*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::setReadbackBuffer(Display /*display*/, - const native_handle_t* /*bufferHandle*/, - android::base::unique_fd /*fenceFd*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::getReadbackBufferFence(Display /*display*/, - android::base::unique_fd* /*outFenceFd*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::createVirtualDisplay_2_2( - uint32_t /*width*/, uint32_t /*height*/, graphics::common::V1_1::PixelFormat* /*format*/, - Display* /*outDisplay*/) { - return V2_1::Error::UNSUPPORTED; -} -V2_1::Error FakeComposerClient::getClientTargetSupport_2_2( - Display /*display*/, uint32_t /*width*/, uint32_t /*height*/, - graphics::common::V1_1::PixelFormat /*format*/, - graphics::common::V1_1::Dataspace /*dataspace*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::setPowerMode_2_2(Display /*display*/, - V2_2::IComposerClient::PowerMode /*mode*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::setLayerFloatColor(Display /*display*/, Layer /*layer*/, - V2_2::IComposerClient::FloatColor /*color*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::getColorModes_2_2( - Display /*display*/, hidl_vec* /*outModes*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::getRenderIntents( - Display /*display*/, graphics::common::V1_1::ColorMode /*mode*/, - std::vector* /*outIntents*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::setColorMode_2_2(Display /*display*/, - graphics::common::V1_1::ColorMode /*mode*/, - graphics::common::V1_1::RenderIntent /*intent*/) { - return V2_1::Error::UNSUPPORTED; -} - -std::array FakeComposerClient::getDataspaceSaturationMatrix( - graphics::common::V1_1::Dataspace /*dataspace*/) { - return {}; -} - -// Composer 2.3 -V2_1::Error FakeComposerClient::getPerFrameMetadataKeys_2_3( - Display /*display*/, std::vector* /*outKeys*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::setColorMode_2_3(Display /*display*/, - graphics::common::V1_2::ColorMode /*mode*/, - graphics::common::V1_1::RenderIntent /*intent*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::getRenderIntents_2_3( - Display /*display*/, graphics::common::V1_2::ColorMode /*mode*/, - std::vector* /*outIntents*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::getColorModes_2_3( - Display /*display*/, hidl_vec* /*outModes*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::getClientTargetSupport_2_3( - Display /*display*/, uint32_t /*width*/, uint32_t /*height*/, - graphics::common::V1_2::PixelFormat /*format*/, - graphics::common::V1_2::Dataspace /*dataspace*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::getReadbackBufferAttributes_2_3( - Display /*display*/, graphics::common::V1_2::PixelFormat* /*outFormat*/, - graphics::common::V1_2::Dataspace* /*outDataspace*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::getHdrCapabilities_2_3( - Display /*display*/, hidl_vec* /*outTypes*/, - float* /*outMaxLuminance*/, float* /*outMaxAverageLuminance*/, float* /*outMinLuminance*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::setLayerPerFrameMetadata_2_3( - Display /*display*/, Layer /*layer*/, - const std::vector& /*metadata*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::getDisplayIdentificationData(Display /*display*/, - uint8_t* /*outPort*/, - std::vector* /*outData*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::setLayerColorTransform(Display /*display*/, Layer /*layer*/, - const float* /*matrix*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::getDisplayedContentSamplingAttributes( - uint64_t /*display*/, graphics::common::V1_2::PixelFormat& /*format*/, - graphics::common::V1_2::Dataspace& /*dataspace*/, - hidl_bitfield& /*componentMask*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::setDisplayedContentSamplingEnabled( - uint64_t /*display*/, V2_3::IComposerClient::DisplayedContentSampling /*enable*/, - hidl_bitfield /*componentMask*/, - uint64_t /*maxFrames*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::getDisplayedContentSample( - uint64_t /*display*/, uint64_t /*maxFrames*/, uint64_t /*timestamp*/, - uint64_t& /*frameCount*/, hidl_vec& /*sampleComponent0*/, - hidl_vec& /*sampleComponent1*/, hidl_vec& /*sampleComponent2*/, - hidl_vec& /*sampleComponent3*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::getDisplayCapabilities( - Display /*display*/, - std::vector* /*outCapabilities*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::setLayerPerFrameMetadataBlobs( - Display /*display*/, Layer /*layer*/, - std::vector& /*blobs*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::getDisplayBrightnessSupport(Display /*display*/, - bool* /*outSupport*/) { - return V2_1::Error::UNSUPPORTED; -} - -V2_1::Error FakeComposerClient::setDisplayBrightness(Display /*display*/, float /*brightness*/) { - return V2_1::Error::UNSUPPORTED; -} - -// Composer 2.4 -void FakeComposerClient::registerEventCallback_2_4(EventCallback_2_4* callback) { - ALOGV("registerEventCallback_2_4"); - LOG_FATAL_IF(mEventCallback != nullptr, "already registered using registerEventCallback"); - - mEventCallback_2_4 = callback; - if (mEventCallback_2_4) { - mEventCallback_2_4->onHotplug(PRIMARY_DISPLAY, IComposerCallback::Connection::CONNECTED); - } -} - -void FakeComposerClient::unregisterEventCallback_2_4() { - ALOGV("unregisterEventCallback_2_4"); - mEventCallback_2_4 = nullptr; -} - -V2_4::Error FakeComposerClient::getDisplayCapabilities_2_4( - Display /*display*/, - std::vector* /*outCapabilities*/) { - return V2_4::Error::UNSUPPORTED; -} - -V2_4::Error FakeComposerClient::getDisplayConnectionType( - Display /*display*/, V2_4::IComposerClient::DisplayConnectionType* /*outType*/) { - return V2_4::Error::UNSUPPORTED; -} - -V2_4::Error FakeComposerClient::getDisplayAttribute_2_4(Display display, Config config, - IComposerClient::Attribute attribute, - int32_t* outValue) { - ALOGV("getDisplayAttribute (%d, %d, %d, %p)", static_cast(display), - static_cast(config), static_cast(attribute), outValue); - if (mMockHal) { - return mMockHal->getDisplayAttribute_2_4(display, config, attribute, outValue); - } - - // TODO: SOOO much fun to be had with these alone - switch (attribute) { - case IComposerClient::Attribute::WIDTH: - *outValue = 1920; - break; - case IComposerClient::Attribute::HEIGHT: - *outValue = 1080; - break; - case IComposerClient::Attribute::VSYNC_PERIOD: - *outValue = 1666666666; - break; // TOOD: Tests break down if lowered to 16ms? - case IComposerClient::Attribute::DPI_X: - *outValue = 240; - break; - case IComposerClient::Attribute::DPI_Y: - *outValue = 240; - break; - default: - LOG_ALWAYS_FATAL("Say what!?! New attribute"); - } - - return Error::NONE; -} - -V2_4::Error FakeComposerClient::getDisplayVsyncPeriod(Display display, - V2_4::VsyncPeriodNanos* outVsyncPeriod) { - ALOGV("getDisplayVsyncPeriod"); - if (mMockHal) { - return mMockHal->getDisplayVsyncPeriod(display, outVsyncPeriod); - } - - return V2_4::Error::UNSUPPORTED; -} - -V2_4::Error FakeComposerClient::setActiveConfigWithConstraints( - Display display, Config config, - const V2_4::IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints, - VsyncPeriodChangeTimeline* timeline) { - ALOGV("setActiveConfigWithConstraints"); - if (mMockHal) { - return mMockHal->setActiveConfigWithConstraints(display, config, - vsyncPeriodChangeConstraints, timeline); - } - return V2_4::Error::UNSUPPORTED; -} - -V2_4::Error FakeComposerClient::setAutoLowLatencyMode(Display, bool) { - ALOGV("setAutoLowLatencyMode"); - return V2_4::Error::UNSUPPORTED; -} - -V2_4::Error FakeComposerClient::getSupportedContentTypes( - Display, std::vector*) { - ALOGV("getSupportedContentTypes"); - return V2_4::Error::UNSUPPORTED; -} - -V2_4::Error FakeComposerClient::setContentType(Display, IComposerClient::ContentType) { - ALOGV("setContentType"); - return V2_4::Error::UNSUPPORTED; -} - -V2_4::Error FakeComposerClient::validateDisplay_2_4( - Display /*display*/, std::vector* /*outChangedLayers*/, - std::vector* /*outCompositionTypes*/, - uint32_t* /*outDisplayRequestMask*/, std::vector* /*outRequestedLayers*/, - std::vector* /*outRequestMasks*/, - IComposerClient::ClientTargetProperty* /*outClientTargetProperty*/) { - return V2_4::Error::NONE; -} - -V2_4::Error FakeComposerClient::setLayerGenericMetadata(Display, Layer, const std::string&, bool, - const std::vector&) { - ALOGV("setLayerGenericMetadata"); - return V2_4::Error::UNSUPPORTED; -} - -V2_4::Error FakeComposerClient::getLayerGenericMetadataKeys( - std::vector*) { - ALOGV("getLayerGenericMetadataKeys"); - return V2_4::Error::UNSUPPORTED; -} - -////////////////////////////////////////////////////////////////// - -void FakeComposerClient::requestVSync(uint64_t vsyncTime) { - if (mEventCallback || mEventCallback_2_4) { - uint64_t timestamp = vsyncTime; - ALOGV("Vsync"); - if (timestamp == 0) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - timestamp = ts.tv_sec * 1000 * 1000 * 1000 + ts.tv_nsec; - } - if (mSurfaceComposer != nullptr) { - mSurfaceComposer->injectVSync(timestamp); - } else if (mEventCallback) { - mEventCallback->onVsync(PRIMARY_DISPLAY, timestamp); - } else { - mEventCallback_2_4->onVsync_2_4(PRIMARY_DISPLAY, timestamp, 16'666'666); - } - } -} - -void FakeComposerClient::runVSyncAfter(std::chrono::nanoseconds wait) { - mDelayedEventGenerator->wakeAfter(wait); -} - -LayerImpl& FakeComposerClient::getLayerImpl(Layer handle) { - // TODO Change these to an internal state check that can be - // invoked from the gtest? GTest macros do not seem all that safe - // when used outside the test class - EXPECT_GE(handle, static_cast(0)); - EXPECT_LT(handle, mLayers.size()); - return *(mLayers[handle]); -} - -int FakeComposerClient::getFrameCount() const { - return mFrames.size(); -} - -static std::vector extractRenderState( - const std::vector>& internalRects) { - std::vector result; - result.reserve(internalRects.size()); - for (const std::unique_ptr& rect : internalRects) { - result.push_back(rect->renderState); - } - return result; -} - -std::vector FakeComposerClient::getFrameRects(int frame) const { - Mutex::Autolock _l(mStateMutex); - return extractRenderState(mFrames[frame]->rectangles); -} - -std::vector FakeComposerClient::getLatestFrame() const { - Mutex::Autolock _l(mStateMutex); - return extractRenderState(mFrames[mFrames.size() - 1]->rectangles); -} - -void FakeComposerClient::runVSyncAndWait(std::chrono::nanoseconds maxWait) { - int currentFrame = 0; - { - Mutex::Autolock _l(mStateMutex); // I hope this is ok... - currentFrame = static_cast(mFrames.size()); - requestVSync(); - } - waitUntilFrame(currentFrame + 1, maxWait); -} - -void FakeComposerClient::waitUntilFrame(int targetFrame, std::chrono::nanoseconds maxWait) const { - Mutex::Autolock _l(mStateMutex); - while (mFrames.size() < static_cast(targetFrame)) { - android::status_t result = mFramesAvailable.waitRelative(mStateMutex, maxWait.count()); - if (result == android::TIMED_OUT) { - ALOGE("Waiting for frame %d (at frame %zu now) timed out after %lld ns", targetFrame, - mFrames.size(), maxWait.count()); - return; - } - } -} - -void FakeComposerClient::clearFrames() { - Mutex::Autolock _l(mStateMutex); - mFrames.clear(); - for (const std::unique_ptr& layer : mLayers) { - if (layer->mValid) { - layer->mRenderState.mSwapCount = 0; - } - } -} - -void FakeComposerClient::onSurfaceFlingerStart() { - mSurfaceComposer = nullptr; - do { - mSurfaceComposer = android::sp::make(); - android::status_t initResult = mSurfaceComposer->initCheck(); - if (initResult != android::NO_ERROR) { - ALOGD("Init result: %d", initResult); - mSurfaceComposer = nullptr; - std::this_thread::sleep_for(10ms); - } - } while (mSurfaceComposer == nullptr); - ALOGD("SurfaceComposerClient created"); - mSurfaceComposer->enableVSyncInjections(true); -} - -void FakeComposerClient::onSurfaceFlingerStop() { - mSurfaceComposer->enableVSyncInjections(false); - mSurfaceComposer->dispose(); - mSurfaceComposer.clear(); -} - -// Includes destroyed layers, stored in order of creation. -int FakeComposerClient::getLayerCount() const { - return mLayers.size(); -} - -Layer FakeComposerClient::getLayer(size_t index) const { - // NOTE: If/when passing calls through to actual implementation, - // this might get more involving. - return static_cast(index); -} - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.h b/services/surfaceflinger/tests/fakehwc/FakeComposerClient.h deleted file mode 100644 index 600e765e9b..0000000000 --- a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.h +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright 2017 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 - -#include -#include -#include -#include -#include - -#include "MockComposerHal.h" -#include "RenderState.h" - -using namespace android::hardware::graphics::common; -using namespace android::hardware::graphics::composer; -using namespace android::hardware::graphics::composer::V2_4; -using namespace android::hardware::graphics::composer::V2_4::hal; -using namespace android::hardware; -using namespace std::chrono_literals; - -namespace { -class LayerImpl; -class Frame; -class DelayedEventGenerator; -} // namespace - -namespace android { -class SurfaceComposerClient; -} // namespace android - -namespace sftest { -// NOTE: The ID's need to be exactly these. VR composer and parts of -// the SurfaceFlinger assume the display IDs to have these values -// despite the enum being documented as a display type. -// TODO: Reference to actual documentation -constexpr Display PRIMARY_DISPLAY = static_cast(HWC_DISPLAY_PRIMARY); -constexpr Display EXTERNAL_DISPLAY = static_cast(HWC_DISPLAY_EXTERNAL); - -class FakeComposerClient : public ComposerHal { -public: - FakeComposerClient(); - virtual ~FakeComposerClient(); - - void setMockHal(MockComposerHal* mockHal) { mMockHal = mockHal; } - - bool hasCapability(hwc2_capability_t capability) override; - - std::string dumpDebugInfo() override; - void registerEventCallback(EventCallback* callback) override; - void unregisterEventCallback() override; - - uint32_t getMaxVirtualDisplayCount() override; - V2_1::Error createVirtualDisplay(uint32_t width, uint32_t height, V1_0::PixelFormat* format, - Display* outDisplay) override; - V2_1::Error destroyVirtualDisplay(Display display) override; - V2_1::Error createLayer(Display display, Layer* outLayer) override; - V2_1::Error destroyLayer(Display display, Layer layer) override; - - V2_1::Error getActiveConfig(Display display, Config* outConfig) override; - V2_1::Error getClientTargetSupport(Display display, uint32_t width, uint32_t height, - V1_0::PixelFormat format, - V1_0::Dataspace dataspace) override; - V2_1::Error getColorModes(Display display, hidl_vec* outModes) override; - V2_1::Error getDisplayAttribute(Display display, Config config, - V2_1::IComposerClient::Attribute attribute, - int32_t* outValue) override; - V2_1::Error getDisplayConfigs(Display display, hidl_vec* outConfigs) override; - V2_1::Error getDisplayName(Display display, hidl_string* outName) override; - V2_1::Error getDisplayType(Display display, IComposerClient::DisplayType* outType) override; - V2_1::Error getDozeSupport(Display display, bool* outSupport) override; - V2_1::Error getHdrCapabilities(Display display, hidl_vec* outTypes, - float* outMaxLuminance, float* outMaxAverageLuminance, - float* outMinLuminance) override; - - V2_1::Error setActiveConfig(Display display, Config config) override; - V2_1::Error setColorMode(Display display, V1_0::ColorMode mode) override; - V2_1::Error setPowerMode(Display display, V2_1::IComposerClient::PowerMode mode) override; - V2_1::Error setVsyncEnabled(Display display, IComposerClient::Vsync enabled) override; - - V2_1::Error setColorTransform(Display display, const float* matrix, int32_t hint) override; - V2_1::Error setClientTarget(Display display, buffer_handle_t target, int32_t acquireFence, - int32_t dataspace, const std::vector& damage) override; - V2_1::Error setOutputBuffer(Display display, buffer_handle_t buffer, - int32_t releaseFence) override; - V2_1::Error validateDisplay(Display display, std::vector* outChangedLayers, - std::vector* outCompositionTypes, - uint32_t* outDisplayRequestMask, - std::vector* outRequestedLayers, - std::vector* outRequestMasks) override; - V2_1::Error acceptDisplayChanges(Display display) override; - V2_1::Error presentDisplay(Display display, int32_t* outPresentFence, - std::vector* outLayers, - std::vector* outReleaseFences) override; - - V2_1::Error setLayerCursorPosition(Display display, Layer layer, int32_t x, int32_t y) override; - V2_1::Error setLayerBuffer(Display display, Layer layer, buffer_handle_t buffer, - int32_t acquireFence) override; - V2_1::Error setLayerSurfaceDamage(Display display, Layer layer, - const std::vector& damage) override; - V2_1::Error setLayerBlendMode(Display display, Layer layer, int32_t mode) override; - V2_1::Error setLayerColor(Display display, Layer layer, IComposerClient::Color color) override; - V2_1::Error setLayerCompositionType(Display display, Layer layer, int32_t type) override; - V2_1::Error setLayerDataspace(Display display, Layer layer, int32_t dataspace) override; - V2_1::Error setLayerDisplayFrame(Display display, Layer layer, - const hwc_rect_t& frame) override; - V2_1::Error setLayerPlaneAlpha(Display display, Layer layer, float alpha) override; - V2_1::Error setLayerSidebandStream(Display display, Layer layer, - buffer_handle_t stream) override; - V2_1::Error setLayerSourceCrop(Display display, Layer layer, const hwc_frect_t& crop) override; - V2_1::Error setLayerTransform(Display display, Layer layer, int32_t transform) override; - V2_1::Error setLayerVisibleRegion(Display display, Layer layer, - const std::vector& visible) override; - V2_1::Error setLayerZOrder(Display display, Layer layer, uint32_t z) override; - - // Composer 2.2 - V2_1::Error getPerFrameMetadataKeys( - Display display, - std::vector* outKeys) override; - V2_1::Error setLayerPerFrameMetadata( - Display display, Layer layer, - const std::vector& metadata) override; - - V2_1::Error getReadbackBufferAttributes( - Display display, graphics::common::V1_1::PixelFormat* outFormat, - graphics::common::V1_1::Dataspace* outDataspace) override; - V2_1::Error setReadbackBuffer(Display display, const native_handle_t* bufferHandle, - android::base::unique_fd fenceFd) override; - V2_1::Error getReadbackBufferFence(Display display, - android::base::unique_fd* outFenceFd) override; - V2_1::Error createVirtualDisplay_2_2(uint32_t width, uint32_t height, - graphics::common::V1_1::PixelFormat* format, - Display* outDisplay) override; - V2_1::Error getClientTargetSupport_2_2(Display display, uint32_t width, uint32_t height, - graphics::common::V1_1::PixelFormat format, - graphics::common::V1_1::Dataspace dataspace) override; - V2_1::Error setPowerMode_2_2(Display display, V2_2::IComposerClient::PowerMode mode) override; - - V2_1::Error setLayerFloatColor(Display display, Layer layer, - V2_2::IComposerClient::FloatColor color) override; - - V2_1::Error getColorModes_2_2(Display display, - hidl_vec* outModes) override; - V2_1::Error getRenderIntents( - Display display, graphics::common::V1_1::ColorMode mode, - std::vector* outIntents) override; - V2_1::Error setColorMode_2_2(Display display, graphics::common::V1_1::ColorMode mode, - graphics::common::V1_1::RenderIntent intent) override; - - std::array getDataspaceSaturationMatrix( - graphics::common::V1_1::Dataspace dataspace) override; - - // Composer 2.3 - V2_1::Error getPerFrameMetadataKeys_2_3( - Display display, - std::vector* outKeys) override; - - V2_1::Error setColorMode_2_3(Display display, graphics::common::V1_2::ColorMode mode, - graphics::common::V1_1::RenderIntent intent) override; - - V2_1::Error getRenderIntents_2_3( - Display display, graphics::common::V1_2::ColorMode mode, - std::vector* outIntents) override; - - V2_1::Error getColorModes_2_3(Display display, - hidl_vec* outModes) override; - - V2_1::Error getClientTargetSupport_2_3(Display display, uint32_t width, uint32_t height, - graphics::common::V1_2::PixelFormat format, - graphics::common::V1_2::Dataspace dataspace) override; - V2_1::Error getReadbackBufferAttributes_2_3( - Display display, graphics::common::V1_2::PixelFormat* outFormat, - graphics::common::V1_2::Dataspace* outDataspace) override; - V2_1::Error getHdrCapabilities_2_3(Display display, - hidl_vec* outTypes, - float* outMaxLuminance, float* outMaxAverageLuminance, - float* outMinLuminance) override; - V2_1::Error setLayerPerFrameMetadata_2_3( - Display display, Layer layer, - const std::vector& metadata) override; - V2_1::Error getDisplayIdentificationData(Display display, uint8_t* outPort, - std::vector* outData) override; - V2_1::Error setLayerColorTransform(Display display, Layer layer, const float* matrix) override; - V2_1::Error getDisplayedContentSamplingAttributes( - uint64_t display, graphics::common::V1_2::PixelFormat& format, - graphics::common::V1_2::Dataspace& dataspace, - hidl_bitfield& componentMask) override; - V2_1::Error setDisplayedContentSamplingEnabled( - uint64_t display, V2_3::IComposerClient::DisplayedContentSampling enable, - hidl_bitfield componentMask, - uint64_t maxFrames) override; - V2_1::Error getDisplayedContentSample(uint64_t display, uint64_t maxFrames, uint64_t timestamp, - uint64_t& frameCount, - hidl_vec& sampleComponent0, - hidl_vec& sampleComponent1, - hidl_vec& sampleComponent2, - hidl_vec& sampleComponent3) override; - V2_1::Error getDisplayCapabilities( - Display display, - std::vector* outCapabilities) override; - V2_1::Error setLayerPerFrameMetadataBlobs( - Display display, Layer layer, - std::vector& blobs) override; - V2_1::Error getDisplayBrightnessSupport(Display display, bool* outSupport) override; - V2_1::Error setDisplayBrightness(Display display, float brightness) override; - - // Composer 2.4 - void registerEventCallback_2_4(EventCallback_2_4* callback) override; - - void unregisterEventCallback_2_4() override; - - V2_4::Error getDisplayCapabilities_2_4( - Display display, - std::vector* outCapabilities) override; - V2_4::Error getDisplayConnectionType( - Display display, V2_4::IComposerClient::DisplayConnectionType* outType) override; - V2_4::Error getDisplayAttribute_2_4(Display display, Config config, - IComposerClient::Attribute attribute, - int32_t* outValue) override; - V2_4::Error getDisplayVsyncPeriod(Display display, - V2_4::VsyncPeriodNanos* outVsyncPeriod) override; - V2_4::Error setActiveConfigWithConstraints( - Display display, Config config, - const V2_4::IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints, - VsyncPeriodChangeTimeline* outTimeline) override; - V2_4::Error setAutoLowLatencyMode(Display display, bool on) override; - V2_4::Error getSupportedContentTypes( - Display display, - std::vector* outSupportedContentTypes) override; - V2_4::Error setContentType(Display display, IComposerClient::ContentType type) override; - V2_4::Error validateDisplay_2_4( - Display display, std::vector* outChangedLayers, - std::vector* outCompositionTypes, - uint32_t* outDisplayRequestMask, std::vector* outRequestedLayers, - std::vector* outRequestMasks, - IComposerClient::ClientTargetProperty* outClientTargetProperty) override; - V2_4::Error setLayerGenericMetadata(Display display, Layer layer, const std::string& key, - bool mandatory, const std::vector& value) override; - V2_4::Error getLayerGenericMetadataKeys( - std::vector* outKeys) override; - - void setClient(ComposerClient* client); - - void requestVSync(uint64_t vsyncTime = 0); - // We don't want tests hanging, so always use a timeout. Remember - // to always check the number of frames with test ASSERT_! - // Wait until next frame is rendered after requesting vsync. - void runVSyncAndWait(std::chrono::nanoseconds maxWait = 100ms); - void runVSyncAfter(std::chrono::nanoseconds wait); - - int getFrameCount() const; - // We don't want tests hanging, so always use a timeout. Remember - // to always check the number of frames with test ASSERT_! - void waitUntilFrame(int targetFrame, std::chrono::nanoseconds maxWait = 100ms) const; - std::vector getFrameRects(int frame) const; - std::vector getLatestFrame() const; - void clearFrames(); - - void onSurfaceFlingerStart(); - void onSurfaceFlingerStop(); - - int getLayerCount() const; - Layer getLayer(size_t index) const; - - void hotplugDisplay(Display display, IComposerCallback::Connection state); - void refreshDisplay(Display display); - -private: - LayerImpl& getLayerImpl(Layer handle); - - EventCallback* mEventCallback; - EventCallback_2_4* mEventCallback_2_4; - Config mCurrentConfig; - bool mVsyncEnabled; - std::vector> mLayers; - std::vector> mFrames; - // Using a pointer to hide the implementation into the CPP file. - std::unique_ptr mDelayedEventGenerator; - android::sp mSurfaceComposer; // For VSync injections - mutable android::Mutex mStateMutex; - mutable android::Condition mFramesAvailable; - - MockComposerHal* mMockHal = nullptr; -}; - -} // namespace sftest diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerService.cpp b/services/surfaceflinger/tests/fakehwc/FakeComposerService.cpp deleted file mode 100644 index c656eed0fb..0000000000 --- a/services/surfaceflinger/tests/fakehwc/FakeComposerService.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2017 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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - -#define LOG_NDEBUG 0 -#undef LOG_TAG -#define LOG_TAG "FakeHwcService" -#include - -#include "FakeComposerService.h" - -using namespace android::hardware; -using namespace android::hardware::graphics::composer; - -namespace sftest { - -FakeComposerService_2_1::FakeComposerService_2_1(android::sp& client) - : mClient(client) {} - -FakeComposerService_2_1::~FakeComposerService_2_1() { - ALOGI("Maybe killing client %p", mClient.get()); - // Rely on sp to kill the client. -} - -Return FakeComposerService_2_1::getCapabilities(getCapabilities_cb hidl_cb) { - ALOGI("FakeComposerService::getCapabilities"); - hidl_cb(hidl_vec()); - return Void(); -} - -Return FakeComposerService_2_1::dumpDebugInfo(dumpDebugInfo_cb hidl_cb) { - ALOGI("FakeComposerService::dumpDebugInfo"); - hidl_cb(hidl_string()); - return Void(); -} - -Return FakeComposerService_2_1::createClient(createClient_cb hidl_cb) { - ALOGI("FakeComposerService::createClient %p", mClient.get()); - if (!mClient->init()) { - LOG_ALWAYS_FATAL("failed to initialize ComposerClient"); - } - hidl_cb(V2_1::Error::NONE, mClient); - return Void(); -} - -FakeComposerService_2_2::FakeComposerService_2_2(android::sp& client) - : mClient(client) {} - -FakeComposerService_2_2::~FakeComposerService_2_2() { - ALOGI("Maybe killing client %p", mClient.get()); - // Rely on sp to kill the client. -} - -Return FakeComposerService_2_2::getCapabilities(getCapabilities_cb hidl_cb) { - ALOGI("FakeComposerService::getCapabilities"); - hidl_cb(hidl_vec()); - return Void(); -} - -Return FakeComposerService_2_2::dumpDebugInfo(dumpDebugInfo_cb hidl_cb) { - ALOGI("FakeComposerService::dumpDebugInfo"); - hidl_cb(hidl_string()); - return Void(); -} - -Return FakeComposerService_2_2::createClient(createClient_cb hidl_cb) { - ALOGI("FakeComposerService::createClient %p", mClient.get()); - if (!mClient->init()) { - LOG_ALWAYS_FATAL("failed to initialize ComposerClient"); - } - hidl_cb(V2_1::Error::NONE, mClient); - return Void(); -} - -FakeComposerService_2_3::FakeComposerService_2_3(android::sp& client) - : mClient(client) {} - -FakeComposerService_2_3::~FakeComposerService_2_3() { - ALOGI("Maybe killing client %p", mClient.get()); - // Rely on sp to kill the client. -} - -Return FakeComposerService_2_3::getCapabilities(getCapabilities_cb hidl_cb) { - ALOGI("FakeComposerService::getCapabilities"); - hidl_cb(hidl_vec()); - return Void(); -} - -Return FakeComposerService_2_3::dumpDebugInfo(dumpDebugInfo_cb hidl_cb) { - ALOGI("FakeComposerService::dumpDebugInfo"); - hidl_cb(hidl_string()); - return Void(); -} - -Return FakeComposerService_2_3::createClient(createClient_cb hidl_cb) { - LOG_ALWAYS_FATAL("createClient called on FakeComposerService_2_3"); - if (!mClient->init()) { - LOG_ALWAYS_FATAL("failed to initialize ComposerClient"); - } - hidl_cb(V2_1::Error::UNSUPPORTED, nullptr); - return Void(); -} - -Return FakeComposerService_2_3::createClient_2_3(createClient_2_3_cb hidl_cb) { - ALOGI("FakeComposerService_2_3::createClient_2_3 %p", mClient.get()); - if (!mClient->init()) { - LOG_ALWAYS_FATAL("failed to initialize ComposerClient"); - } - hidl_cb(V2_1::Error::NONE, mClient); - return Void(); -} - -FakeComposerService_2_4::FakeComposerService_2_4(android::sp& client) - : mClient(client) {} - -FakeComposerService_2_4::~FakeComposerService_2_4() { - ALOGI("Maybe killing client %p", mClient.get()); - // Rely on sp to kill the client. -} - -Return FakeComposerService_2_4::getCapabilities(getCapabilities_cb hidl_cb) { - ALOGI("FakeComposerService::getCapabilities"); - hidl_cb(hidl_vec()); - return Void(); -} - -Return FakeComposerService_2_4::dumpDebugInfo(dumpDebugInfo_cb hidl_cb) { - ALOGI("FakeComposerService::dumpDebugInfo"); - hidl_cb(hidl_string()); - return Void(); -} - -Return FakeComposerService_2_4::createClient(createClient_cb hidl_cb) { - LOG_ALWAYS_FATAL("createClient called on FakeComposerService_2_4"); - if (!mClient->init()) { - LOG_ALWAYS_FATAL("failed to initialize ComposerClient"); - } - hidl_cb(V2_1::Error::UNSUPPORTED, nullptr); - return Void(); -} - -Return FakeComposerService_2_4::createClient_2_3(createClient_2_3_cb hidl_cb) { - LOG_ALWAYS_FATAL("createClient_2_3 called on FakeComposerService_2_4"); - if (!mClient->init()) { - LOG_ALWAYS_FATAL("failed to initialize ComposerClient"); - } - hidl_cb(V2_1::Error::UNSUPPORTED, nullptr); - return Void(); -} - -Return FakeComposerService_2_4::createClient_2_4(createClient_2_4_cb hidl_cb) { - ALOGI("FakeComposerService_2_4::createClient_2_4 %p", mClient.get()); - if (!mClient->init()) { - LOG_ALWAYS_FATAL("failed to initialize ComposerClient"); - } - hidl_cb(V2_4::Error::NONE, mClient); - return Void(); -} - -} // namespace sftest - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerService.h b/services/surfaceflinger/tests/fakehwc/FakeComposerService.h deleted file mode 100644 index 47f970f5b2..0000000000 --- a/services/surfaceflinger/tests/fakehwc/FakeComposerService.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2017 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 -#include -#include -#include -#include - -using android::hardware::Return; - -using ComposerClient = android::hardware::graphics::composer::V2_4::hal::ComposerClient; - -namespace sftest { - -using IComposer_2_1 = android::hardware::graphics::composer::V2_1::IComposer; - -class FakeComposerService_2_1 : public IComposer_2_1 { -public: - explicit FakeComposerService_2_1(android::sp& client); - virtual ~FakeComposerService_2_1(); - - Return getCapabilities(getCapabilities_cb hidl_cb) override; - Return dumpDebugInfo(dumpDebugInfo_cb hidl_cb) override; - Return createClient(createClient_cb hidl_cb) override; - -private: - android::sp mClient; -}; - -using IComposer_2_2 = android::hardware::graphics::composer::V2_2::IComposer; -class FakeComposerService_2_2 : public IComposer_2_2 { -public: - explicit FakeComposerService_2_2(android::sp& client); - virtual ~FakeComposerService_2_2(); - - Return getCapabilities(getCapabilities_cb hidl_cb) override; - Return dumpDebugInfo(dumpDebugInfo_cb hidl_cb) override; - Return createClient(createClient_cb hidl_cb) override; - -private: - android::sp mClient; -}; - -using IComposer_2_3 = android::hardware::graphics::composer::V2_3::IComposer; -class FakeComposerService_2_3 : public IComposer_2_3 { -public: - explicit FakeComposerService_2_3(android::sp& client); - virtual ~FakeComposerService_2_3(); - - Return getCapabilities(getCapabilities_cb hidl_cb) override; - Return dumpDebugInfo(dumpDebugInfo_cb hidl_cb) override; - Return createClient(createClient_cb hidl_cb) override; - Return createClient_2_3(createClient_2_3_cb hidl_cb) override; - -private: - android::sp mClient; -}; - -using IComposer_2_4 = android::hardware::graphics::composer::V2_4::IComposer; - -class FakeComposerService_2_4 : public IComposer_2_4 { -public: - explicit FakeComposerService_2_4(android::sp& client); - virtual ~FakeComposerService_2_4(); - - Return getCapabilities(getCapabilities_cb hidl_cb) override; - Return dumpDebugInfo(dumpDebugInfo_cb hidl_cb) override; - Return createClient(createClient_cb hidl_cb) override; - Return createClient_2_3(createClient_2_3_cb hidl_cb) override; - Return createClient_2_4(createClient_2_4_cb hidl_cb) override; - -private: - android::sp mClient; -}; - -} // namespace sftest diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.cpp b/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.cpp deleted file mode 100644 index 1cea25a80f..0000000000 --- a/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.cpp +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright 2017 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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - -#define LOG_NDEBUG 0 -#undef LOG_TAG -#define LOG_TAG "FakeHwcUtil" -#include - -#include "FakeComposerUtils.h" -#include "RenderState.h" - -#include "SurfaceFlinger.h" // Get the name of the service... - -#include -#include -#include - -#include -#include - -using android::String16; -using android::sp; -using namespace std::chrono_literals; -using namespace sftest; -using std::setw; - -namespace sftest { - -// clang-format off -inline void printSourceRectAligned(::std::ostream& os, const hwc_frect_t& sourceRect, int align) { - os << std::fixed << std::setprecision(1) << "(" - << setw(align) << sourceRect.left << setw(0) << "," - << setw(align) << sourceRect.top << setw(0) << "," - << setw(align) << sourceRect.right << setw(0) << "," - << setw(align) << sourceRect.bottom << setw(0) << ")"; -} - -inline void printDisplayRectAligned(::std::ostream& os, const hwc_rect_t& displayRect, int align) { - os << "(" - << setw(align) << displayRect.left << setw(0) << "," - << setw(align) << displayRect.top << setw(0) << "," - << setw(align) << displayRect.right << setw(0) << "," - << setw(align) << displayRect.bottom << setw(0) << ")"; -} -// clang-format on - -inline ::std::ostream& operator<<(::std::ostream& os, const sftest::RenderState& state) { - printSourceRectAligned(os, state.mSourceCrop, 7); - os << "->"; - printDisplayRectAligned(os, state.mDisplayFrame, 5); - return os << " Swaps:" << state.mSwapCount << " Alpha:" << std::setprecision(3) - << state.mPlaneAlpha << " Xform:" << state.mTransform; -} - -// Helper for verifying the parts of the RenderState -template -bool valuesMatch(::testing::AssertionResult& message, const T& ref, const T& val, - const char* name) { - if (ref != val) { - message = message << "Expected " << name << ":" << ref << ", got:" << val << "."; - return false; - } - return true; -} - -::testing::AssertionResult rectsAreSame(const RenderState& ref, const RenderState& val) { - // TODO: Message could start as success and be assigned as failure. - // Only problem is that utility assumes it to be failure and just adds stuff. Would - // need still special case the initial failure in the utility? - // TODO: ... or would it be possible to break this back to gtest primitives? - ::testing::AssertionResult message = ::testing::AssertionFailure(); - bool passes = true; - - // The work here is mostly about providing good log strings for differences - passes &= valuesMatch(message, ref.mDisplayFrame, val.mDisplayFrame, "display frame"); - passes &= valuesMatch(message, ref.mPlaneAlpha, val.mPlaneAlpha, "alpha"); - passes &= valuesMatch(message, ref.mSwapCount, val.mSwapCount, "swap count"); - passes &= valuesMatch(message, ref.mSourceCrop, val.mSourceCrop, "source crop"); - // ... add more - if (passes) { - return ::testing::AssertionSuccess(); - } - return message; -} - -::testing::AssertionResult framesAreSame(const std::vector& ref, - const std::vector& val) { - ::testing::AssertionResult message = ::testing::AssertionFailure(); - bool passed = true; - if (ref.size() != val.size()) { - message << "Expected " << ref.size() << " rects, got " << val.size() << "."; - passed = false; - } - for (size_t rectIndex = 0; rectIndex < std::min(ref.size(), val.size()); rectIndex++) { - ::testing::AssertionResult rectResult = rectsAreSame(ref[rectIndex], val[rectIndex]); - if (rectResult == false) { - message << "First different rect at " << rectIndex << ": " << rectResult.message(); - passed = false; - break; - } - } - - if (passed) { - return ::testing::AssertionSuccess(); - } else { - message << "\nReference:"; - for (auto state = ref.begin(); state != ref.end(); ++state) { - message << "\n" << *state; - } - message << "\nActual:"; - for (auto state = val.begin(); state != val.end(); ++state) { - message << "\n" << *state; - } - } - return message; -} - -void startSurfaceFlinger() { - ALOGI("Start SurfaceFlinger"); - system("start surfaceflinger"); - - sp sm(android::defaultServiceManager()); - sp sf; - while (sf == nullptr) { - std::this_thread::sleep_for(10ms); - sf = sm->checkService(String16(android::SurfaceFlinger::getServiceName())); - } - ALOGV("SurfaceFlinger running"); -} - -void stopSurfaceFlinger() { - ALOGI("Stop SurfaceFlinger"); - system("stop surfaceflinger"); - sp sm(android::defaultServiceManager()); - sp sf; - while (sf != nullptr) { - std::this_thread::sleep_for(10ms); - sf = sm->checkService(String16(android::SurfaceFlinger::getServiceName())); - } - ALOGV("SurfaceFlinger stopped"); -} - -//////////////////////////////////////////////// - -void FakeHwcEnvironment::SetUp() { - ALOGI("Test env setup"); - system("setenforce 0"); - system("stop"); - property_set("debug.sf.nobootanimation", "1"); - { - char value[PROPERTY_VALUE_MAX]; - property_get("debug.sf.nobootanimation", value, "0"); - LOG_FATAL_IF(atoi(value) != 1, "boot skip not set"); - } - // TODO: Try registering the mock as the default service instead. - property_set("debug.sf.hwc_service_name", "mock"); - - // This allows tests/SF to register/load a HIDL service not listed in manifest files. - android::hardware::details::setTrebleTestingOverride(true); - property_set("debug.sf.treble_testing_override", "true"); -} - -void FakeHwcEnvironment::TearDown() { - ALOGI("Test env tear down"); - system("stop"); - // Wait for mock call signaling teardown? - property_set("debug.sf.nobootanimation", "0"); - property_set("debug.sf.hwc_service_name", "default"); - system("setenforce 1"); - ALOGI("Test env tear down - done"); -} - -} // namespace sftest - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.h b/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.h deleted file mode 100644 index 383a111859..0000000000 --- a/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.h +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2017 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 "FakeComposerClient.h" - -#include -#include -#include - -// clang-format off -// Note: This needs to reside in the global namespace for the GTest to use it -inline ::std::ostream& operator<<(::std::ostream& os, const hwc_rect_t& rect) { - return os << "(" << rect.left << "," - << rect.top << "," - << rect.right << "," - << rect.bottom << ")"; -} - -inline ::std::ostream& operator<<(::std::ostream& os, const hwc_frect_t& rect) { - return os << "(" << rect.left << "," - << rect.top << "," - << rect.right << "," - << rect.bottom << ")"; -} -// clang-format on - -namespace sftest { - -class RenderState; - -// clang-format off -inline bool operator==(const hwc_rect_t& a, const hwc_rect_t& b) { - return a.top == b.top && - a.left == b.left && - a.bottom == b.bottom && - a.right == b.right; -} - -inline bool operator==(const hwc_frect_t& a, const hwc_frect_t& b) { - return a.top == b.top && - a.left == b.left && - a.bottom == b.bottom && - a.right == b.right; -} -// clang-format on - -inline bool operator!=(const hwc_rect_t& a, const hwc_rect_t& b) { - return !(a == b); -} - -inline bool operator!=(const hwc_frect_t& a, const hwc_frect_t& b) { - return !(a == b); -} - -::testing::AssertionResult rectsAreSame(const RenderState& ref, const RenderState& val); -::testing::AssertionResult framesAreSame(const std::vector& ref, - const std::vector& val); - -void startSurfaceFlinger(); -void stopSurfaceFlinger(); - -class FakeHwcEnvironment : public ::testing::Environment { -public: - virtual ~FakeHwcEnvironment() {} - void SetUp() override; - void TearDown() override; -}; - -/* - * All surface state changes are supposed to happen inside a global - * transaction. TransactionScope object at the beginning of - * scope automates the process. The resulting scope gives a visual cue - * on the span of the transaction as well. - * - * Closing the transaction is synchronous, i.e., it waits for - * SurfaceFlinger to composite one frame. Now, the FakeComposerClient - * is built to explicitly request vsyncs one at the time. A delayed - * request must be made before closing the transaction or the test - * thread stalls until SurfaceFlinger does an emergency vsync by - * itself. TransactionScope encapsulates this vsync magic. - */ -class TransactionScope : public android::SurfaceComposerClient::Transaction { -public: - explicit TransactionScope(FakeComposerClient& composer) : Transaction(), mComposer(composer) {} - - ~TransactionScope() { - int frameCount = mComposer.getFrameCount(); - mComposer.runVSyncAfter(1ms); - LOG_ALWAYS_FATAL_IF(android::NO_ERROR != apply()); - // Make sure that exactly one frame has been rendered. - mComposer.waitUntilFrame(frameCount + 1); - // LOG_ALWAYS_FATAL_IF(frameCount + 1 != mComposer.getFrameCount(), - // "Unexpected frame advance. Delta: %d", - // mComposer.getFrameCount() - frameCount); - } - - FakeComposerClient& mComposer; -}; - -} // namespace sftest diff --git a/services/surfaceflinger/tests/fakehwc/MockComposerHal.h b/services/surfaceflinger/tests/fakehwc/MockComposerHal.h deleted file mode 100644 index 5dc3778153..0000000000 --- a/services/surfaceflinger/tests/fakehwc/MockComposerHal.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2019 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 - -#include - -using namespace android::hardware::graphics::common; -using namespace android::hardware::graphics::composer; -using namespace android::hardware::graphics::composer::V2_4; -using namespace android::hardware::graphics::composer::V2_4::hal; -using namespace android::hardware; -using namespace std::chrono_literals; - -namespace sftest { - -// Mock class for ComposerHal. Implements only the functions used in the test. -class MockComposerHal { -public: - MOCK_METHOD2(getActiveConfig, V2_1::Error(Display, Config*)); - MOCK_METHOD4(getDisplayAttribute_2_4, - V2_4::Error(Display, Config, V2_4::IComposerClient::Attribute, int32_t*)); - MOCK_METHOD2(getDisplayConfigs, V2_1::Error(Display, hidl_vec*)); - MOCK_METHOD2(setActiveConfig, V2_1::Error(Display, Config)); - MOCK_METHOD2(getDisplayVsyncPeriod, V2_4::Error(Display, V2_4::VsyncPeriodNanos*)); - MOCK_METHOD4(setActiveConfigWithConstraints, - V2_4::Error(Display, Config, - const V2_4::IComposerClient::VsyncPeriodChangeConstraints&, - VsyncPeriodChangeTimeline*)); -}; - -} // namespace sftest \ No newline at end of file diff --git a/services/surfaceflinger/tests/fakehwc/RenderState.h b/services/surfaceflinger/tests/fakehwc/RenderState.h deleted file mode 100644 index 40193f237e..0000000000 --- a/services/surfaceflinger/tests/fakehwc/RenderState.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2017 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 - -namespace sftest { -// Description of a rendered rectangle. Should only contain -// instructions necessary to rasterize the rectangle. The full scene -// is given as a sorted list of rectangles, bottom layer at index 0. -class RenderState { -public: - RenderState() = default; - // Default copy-ctor - - hwc_rect_t mDisplayFrame = {0, 0, 0, 0}; - hwc_frect_t mSourceCrop = {0.f, 0.f, 0.f, 0.f}; - std::vector mVisibleRegion; - hwc2_blend_mode_t mBlendMode = HWC2_BLEND_MODE_NONE; - buffer_handle_t mBuffer = 0; - uint32_t mSwapCount = 0; // How many set buffer calls to the layer. - int32_t mAcquireFence = 0; // Probably should not be here. - float mPlaneAlpha = 0.f; - hwc_color_t mLayerColor = {0, 0, 0, 0}; - hwc_transform_t mTransform = static_cast(0); -}; - -} // namespace sftest diff --git a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp b/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp deleted file mode 100644 index 1d3401aa0f..0000000000 --- a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp +++ /dev/null @@ -1,1794 +0,0 @@ -/* - * Copyright (C) 2017 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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" -#pragma clang diagnostic ignored "-Wextra" - -// #define LOG_NDEBUG 0 -#undef LOG_TAG -#define LOG_TAG "FakeHwcTest" - -#include "FakeComposerClient.h" -#include "FakeComposerService.h" -#include "FakeComposerUtils.h" -#include "MockComposerHal.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -using namespace std::chrono_literals; - -using namespace android; -using namespace android::hardware; - -using namespace sftest; - -namespace { - -// Mock test helpers -using ::testing::_; -using ::testing::DoAll; -using ::testing::Return; -using ::testing::SetArgPointee; - -using Transaction = SurfaceComposerClient::Transaction; -using Attribute = V2_4::IComposerClient::Attribute; -using Display = V2_1::Display; - -/////////////////////////////////////////////// -constexpr PhysicalDisplayId physicalIdFromHwcDisplayId(Display hwcId) { - return PhysicalDisplayId::fromPort(hwcId); -} -constexpr PhysicalDisplayId kPrimaryDisplayId = physicalIdFromHwcDisplayId(PRIMARY_DISPLAY); -constexpr PhysicalDisplayId kExternalDisplayId = physicalIdFromHwcDisplayId(EXTERNAL_DISPLAY); - -struct TestColor { -public: - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; -}; - -constexpr static TestColor RED = {195, 63, 63, 255}; -constexpr static TestColor LIGHT_RED = {255, 177, 177, 255}; -constexpr static TestColor GREEN = {63, 195, 63, 255}; -constexpr static TestColor BLUE = {63, 63, 195, 255}; -constexpr static TestColor LIGHT_GRAY = {200, 200, 200, 255}; - -// Fill an RGBA_8888 formatted surface with a single color. -static void fillSurfaceRGBA8(const sp& sc, const TestColor& color, - bool unlock = true) { - ANativeWindow_Buffer outBuffer; - sp s = sc->getSurface(); - ASSERT_TRUE(s != nullptr); - ASSERT_EQ(NO_ERROR, s->lock(&outBuffer, nullptr)); - uint8_t* img = reinterpret_cast(outBuffer.bits); - for (int y = 0; y < outBuffer.height; y++) { - for (int x = 0; x < outBuffer.width; x++) { - uint8_t* pixel = img + (4 * (y * outBuffer.stride + x)); - pixel[0] = color.r; - pixel[1] = color.g; - pixel[2] = color.b; - pixel[3] = color.a; - } - } - if (unlock) { - ASSERT_EQ(NO_ERROR, s->unlockAndPost()); - } -} - -inline RenderState makeSimpleRect(int left, int top, int right, int bottom) { - RenderState res; - res.mDisplayFrame = hwc_rect_t{left, top, right, bottom}; - res.mPlaneAlpha = 1.0f; - res.mSwapCount = 0; - res.mSourceCrop = hwc_frect_t{0.f, 0.f, static_cast(right - left), - static_cast(bottom - top)}; - return res; -} - -inline RenderState makeSimpleRect(unsigned int left, unsigned int top, unsigned int right, - unsigned int bottom) { - EXPECT_LE(left, static_cast(INT_MAX)); - EXPECT_LE(top, static_cast(INT_MAX)); - EXPECT_LE(right, static_cast(INT_MAX)); - EXPECT_LE(bottom, static_cast(INT_MAX)); - return makeSimpleRect(static_cast(left), static_cast(top), static_cast(right), - static_cast(bottom)); -} - -/////////////////////////////////////////////// -template -class DisplayTest : public ::testing::Test { -protected: - struct TestConfig { - int32_t id; - int32_t w; - int32_t h; - int32_t vsyncPeriod; - int32_t group; - }; - - static int processDisplayEvents(int /*fd*/, int /*events*/, void* data) { - auto self = static_cast(data); - - ssize_t n; - DisplayEventReceiver::Event buffer[1]; - - while ((n = self->mReceiver->getEvents(buffer, 1)) > 0) { - for (int i = 0; i < n; i++) { - self->mReceivedDisplayEvents.push_back(buffer[i]); - } - } - ALOGD_IF(n < 0, "Error reading events (%s)", strerror(-n)); - return 1; - } - - Error getDisplayAttributeNoMock(Display display, Config config, - V2_4::IComposerClient::Attribute attribute, int32_t* outValue) { - mFakeComposerClient->setMockHal(nullptr); - auto ret = - mFakeComposerClient->getDisplayAttribute_2_4(display, config, attribute, outValue); - mFakeComposerClient->setMockHal(mMockComposer.get()); - return ret; - } - - void setExpectationsForConfigs(Display display, std::vector testConfigs, - Config activeConfig, V2_4::VsyncPeriodNanos defaultVsyncPeriod) { - std::vector configIds; - for (size_t i = 0; i < testConfigs.size(); i++) { - configIds.push_back(testConfigs[i].id); - - EXPECT_CALL(*mMockComposer, - getDisplayAttribute_2_4(display, testConfigs[i].id, Attribute::WIDTH, _)) - .WillRepeatedly(DoAll(SetArgPointee<3>(testConfigs[i].w), Return(Error::NONE))); - EXPECT_CALL(*mMockComposer, - getDisplayAttribute_2_4(display, testConfigs[i].id, Attribute::HEIGHT, _)) - .WillRepeatedly(DoAll(SetArgPointee<3>(testConfigs[i].h), Return(Error::NONE))); - EXPECT_CALL(*mMockComposer, - getDisplayAttribute_2_4(display, testConfigs[i].id, Attribute::VSYNC_PERIOD, - _)) - .WillRepeatedly(DoAll(SetArgPointee<3>(testConfigs[i].vsyncPeriod), - Return(Error::NONE))); - EXPECT_CALL(*mMockComposer, - getDisplayAttribute_2_4(display, testConfigs[i].id, Attribute::CONFIG_GROUP, - _)) - .WillRepeatedly( - DoAll(SetArgPointee<3>(testConfigs[i].group), Return(Error::NONE))); - EXPECT_CALL(*mMockComposer, - getDisplayAttribute_2_4(display, testConfigs[i].id, Attribute::DPI_X, _)) - .WillRepeatedly(Return(Error::UNSUPPORTED)); - EXPECT_CALL(*mMockComposer, - getDisplayAttribute_2_4(display, testConfigs[i].id, Attribute::DPI_Y, _)) - .WillRepeatedly(Return(Error::UNSUPPORTED)); - } - - EXPECT_CALL(*mMockComposer, getDisplayConfigs(display, _)) - .WillRepeatedly(DoAll(SetArgPointee<1>(hidl_vec(configIds)), - Return(V2_1::Error::NONE))); - - EXPECT_CALL(*mMockComposer, getActiveConfig(display, _)) - .WillRepeatedly(DoAll(SetArgPointee<1>(activeConfig), Return(V2_1::Error::NONE))); - - EXPECT_CALL(*mMockComposer, getDisplayVsyncPeriod(display, _)) - .WillRepeatedly( - DoAll(SetArgPointee<1>(defaultVsyncPeriod), Return(V2_4::Error::NONE))); - } - - void SetUp() override { - mMockComposer = std::make_unique(); - mFakeComposerClient = new FakeComposerClient(); - mFakeComposerClient->setMockHal(mMockComposer.get()); - - auto client = sp::make(mFakeComposerClient); - mFakeService = sp::make(client); - ASSERT_EQ(android::OK, mFakeService->registerAsService("mock")); - - android::hardware::ProcessState::self()->startThreadPool(); - android::ProcessState::self()->startThreadPool(); - - setExpectationsForConfigs(PRIMARY_DISPLAY, - {{ - .id = 1, - .w = 1920, - .h = 1024, - .vsyncPeriod = 16'666'666, - .group = 0, - }}, - 1, 16'666'666); - - startSurfaceFlinger(); - - // Fake composer wants to enable VSync injection - mFakeComposerClient->onSurfaceFlingerStart(); - - mComposerClient = sp::make(); - ASSERT_EQ(NO_ERROR, mComposerClient->initCheck()); - - mReceiver.reset( - new DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp, - gui::ISurfaceComposer::EventRegistration::modeChanged)); - mLooper = sp::make(false); - mLooper->addFd(mReceiver->getFd(), 0, ALOOPER_EVENT_INPUT, processDisplayEvents, this); - } - - void TearDown() override { - mLooper = nullptr; - mReceiver = nullptr; - - mComposerClient->dispose(); - mComposerClient = nullptr; - - // Fake composer needs to release SurfaceComposerClient before the stop. - mFakeComposerClient->onSurfaceFlingerStop(); - stopSurfaceFlinger(); - - mFakeComposerClient->setMockHal(nullptr); - - mFakeService = nullptr; - // TODO: Currently deleted in FakeComposerClient::removeClient(). Devise better lifetime - // management. - mMockComposer = nullptr; - } - - void waitForDisplayTransaction(Display display) { - // Both a refresh and a vsync event are needed to apply pending display - // transactions. - mFakeComposerClient->refreshDisplay(display); - mFakeComposerClient->runVSyncAndWait(); - - // Extra vsync and wait to avoid a 10% flake due to a race. - mFakeComposerClient->runVSyncAndWait(); - } - - bool waitForHotplugEvent(Display displayId, bool connected) { - return waitForHotplugEvent(physicalIdFromHwcDisplayId(displayId), connected); - } - - bool waitForHotplugEvent(PhysicalDisplayId displayId, bool connected) { - int waitCount = 20; - while (waitCount--) { - while (!mReceivedDisplayEvents.empty()) { - auto event = mReceivedDisplayEvents.front(); - mReceivedDisplayEvents.pop_front(); - - ALOGV_IF(event.header.type == DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG, - "event hotplug: displayId %s, connected %d", - to_string(event.header.displayId).c_str(), event.hotplug.connected); - - if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG && - event.header.displayId == displayId && event.hotplug.connected == connected) { - return true; - } - } - - mLooper->pollOnce(1); - } - return false; - } - - bool waitForModeChangedEvent(Display display, int32_t modeId) { - PhysicalDisplayId displayId = physicalIdFromHwcDisplayId(display); - int waitCount = 20; - while (waitCount--) { - while (!mReceivedDisplayEvents.empty()) { - auto event = mReceivedDisplayEvents.front(); - mReceivedDisplayEvents.pop_front(); - - ALOGV_IF(event.header.type == DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE, - "event mode: displayId %s, modeId %d", - to_string(event.header.displayId).c_str(), event.modeChange.modeId); - - if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE && - event.header.displayId == displayId && event.modeChange.modeId == modeId) { - return true; - } - } - - mLooper->pollOnce(1); - } - return false; - } - - void Test_HotplugOneConfig() { - ALOGD("DisplayTest::Test_Hotplug_oneConfig"); - - setExpectationsForConfigs(EXTERNAL_DISPLAY, - {{.id = 1, - .w = 200, - .h = 400, - .vsyncPeriod = 16'666'666, - .group = 0}}, - 1, 16'666'666); - - mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY, - V2_1::IComposerCallback::Connection::CONNECTED); - waitForDisplayTransaction(EXTERNAL_DISPLAY); - EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, true)); - - { - const auto display = SurfaceComposerClient::getPhysicalDisplayToken(kExternalDisplayId); - EXPECT_FALSE(display == nullptr); - - ui::DisplayMode mode; - EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); - const ui::Size& resolution = mode.resolution; - EXPECT_EQ(ui::Size(200, 400), resolution); - EXPECT_EQ(1e9f / 16'666'666, mode.refreshRate); - - auto surfaceControl = - mComposerClient->createSurface(String8("Display Test Surface Foo"), - resolution.getWidth(), resolution.getHeight(), - PIXEL_FORMAT_RGBA_8888, 0); - EXPECT_TRUE(surfaceControl != nullptr); - EXPECT_TRUE(surfaceControl->isValid()); - fillSurfaceRGBA8(surfaceControl, BLUE); - - { - TransactionScope ts(*mFakeComposerClient); - ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK); - - ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl); - } - } - - mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY, - V2_1::IComposerCallback::Connection::DISCONNECTED); - waitForDisplayTransaction(EXTERNAL_DISPLAY); - mFakeComposerClient->clearFrames(); - EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, false)); - - { - const auto display = SurfaceComposerClient::getPhysicalDisplayToken(kExternalDisplayId); - EXPECT_TRUE(display == nullptr); - - ui::DisplayMode mode; - EXPECT_NE(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); - } - } - - void Test_HotplugTwoSeparateConfigs() { - ALOGD("DisplayTest::Test_HotplugTwoSeparateConfigs"); - - setExpectationsForConfigs(EXTERNAL_DISPLAY, - {{.id = 1, - .w = 200, - .h = 400, - .vsyncPeriod = 16'666'666, - .group = 0}, - {.id = 2, - .w = 800, - .h = 1600, - .vsyncPeriod = 11'111'111, - .group = 1}}, - 1, 16'666'666); - - mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY, - V2_1::IComposerCallback::Connection::CONNECTED); - waitForDisplayTransaction(EXTERNAL_DISPLAY); - EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, true)); - - const auto display = SurfaceComposerClient::getPhysicalDisplayToken(kExternalDisplayId); - EXPECT_FALSE(display == nullptr); - - ui::DisplayMode mode; - EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); - EXPECT_EQ(ui::Size(200, 400), mode.resolution); - EXPECT_EQ(1e9f / 16'666'666, mode.refreshRate); - - mFakeComposerClient->clearFrames(); - { - const ui::Size& resolution = mode.resolution; - auto surfaceControl = - mComposerClient->createSurface(String8("Display Test Surface Foo"), - resolution.getWidth(), resolution.getHeight(), - PIXEL_FORMAT_RGBA_8888, 0); - EXPECT_TRUE(surfaceControl != nullptr); - EXPECT_TRUE(surfaceControl->isValid()); - fillSurfaceRGBA8(surfaceControl, BLUE); - - { - TransactionScope ts(*mFakeComposerClient); - ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK); - - ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl); - } - } - - ui::DynamicDisplayInfo info; - EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info)); - const auto& modes = info.supportedDisplayModes; - EXPECT_EQ(modes.size(), 2); - - // change active mode - - if (mIs2_4Client) { - EXPECT_CALL(*mMockComposer, setActiveConfigWithConstraints(EXTERNAL_DISPLAY, 2, _, _)) - .WillOnce(Return(V2_4::Error::NONE)); - } else { - EXPECT_CALL(*mMockComposer, setActiveConfig(EXTERNAL_DISPLAY, 2)) - .WillOnce(Return(V2_1::Error::NONE)); - } - - for (int i = 0; i < modes.size(); i++) { - const auto& mode = modes[i]; - if (mode.resolution.getWidth() == 800) { - EXPECT_EQ(NO_ERROR, - SurfaceComposerClient::setDesiredDisplayModeSpecs(display, i, false, - mode.refreshRate, - mode.refreshRate, - mode.refreshRate, - mode.refreshRate)); - waitForDisplayTransaction(EXTERNAL_DISPLAY); - EXPECT_TRUE(waitForModeChangedEvent(EXTERNAL_DISPLAY, i)); - break; - } - } - - EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); - EXPECT_EQ(ui::Size(800, 1600), mode.resolution); - EXPECT_EQ(1e9f / 11'111'111, mode.refreshRate); - - mFakeComposerClient->clearFrames(); - { - const ui::Size& resolution = mode.resolution; - auto surfaceControl = - mComposerClient->createSurface(String8("Display Test Surface Foo"), - resolution.getWidth(), resolution.getHeight(), - PIXEL_FORMAT_RGBA_8888, 0); - EXPECT_TRUE(surfaceControl != nullptr); - EXPECT_TRUE(surfaceControl->isValid()); - fillSurfaceRGBA8(surfaceControl, BLUE); - - { - TransactionScope ts(*mFakeComposerClient); - ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK); - - ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl); - } - } - - mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY, - V2_1::IComposerCallback::Connection::DISCONNECTED); - waitForDisplayTransaction(EXTERNAL_DISPLAY); - mFakeComposerClient->clearFrames(); - EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, false)); - } - - void Test_HotplugTwoConfigsSameGroup() { - ALOGD("DisplayTest::Test_HotplugTwoConfigsSameGroup"); - - setExpectationsForConfigs(EXTERNAL_DISPLAY, - {{.id = 2, - .w = 800, - .h = 1600, - .vsyncPeriod = 16'666'666, - .group = 31}, - {.id = 3, - .w = 800, - .h = 1600, - .vsyncPeriod = 11'111'111, - .group = 31}}, - 2, 16'666'666); - - mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY, - V2_1::IComposerCallback::Connection::CONNECTED); - waitForDisplayTransaction(EXTERNAL_DISPLAY); - EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, true)); - - const auto display = SurfaceComposerClient::getPhysicalDisplayToken(kExternalDisplayId); - EXPECT_FALSE(display == nullptr); - - ui::DisplayMode mode; - EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); - EXPECT_EQ(ui::Size(800, 1600), mode.resolution); - EXPECT_EQ(1e9f / 16'666'666, mode.refreshRate); - - mFakeComposerClient->clearFrames(); - { - const ui::Size& resolution = mode.resolution; - auto surfaceControl = - mComposerClient->createSurface(String8("Display Test Surface Foo"), - resolution.getWidth(), resolution.getHeight(), - PIXEL_FORMAT_RGBA_8888, 0); - EXPECT_TRUE(surfaceControl != nullptr); - EXPECT_TRUE(surfaceControl->isValid()); - fillSurfaceRGBA8(surfaceControl, BLUE); - - { - TransactionScope ts(*mFakeComposerClient); - ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK); - - ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl); - } - } - - ui::DynamicDisplayInfo info; - EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info)); - const auto& modes = info.supportedDisplayModes; - EXPECT_EQ(modes.size(), 2); - - // change active mode - if (mIs2_4Client) { - EXPECT_CALL(*mMockComposer, setActiveConfigWithConstraints(EXTERNAL_DISPLAY, 3, _, _)) - .WillOnce(Return(V2_4::Error::NONE)); - } else { - EXPECT_CALL(*mMockComposer, setActiveConfig(EXTERNAL_DISPLAY, 3)) - .WillOnce(Return(V2_1::Error::NONE)); - } - - for (int i = 0; i < modes.size(); i++) { - const auto& mode = modes[i]; - if (mode.refreshRate == 1e9f / 11'111'111) { - EXPECT_EQ(NO_ERROR, - SurfaceComposerClient::setDesiredDisplayModeSpecs(display, i, false, - mode.refreshRate, - mode.refreshRate, - mode.refreshRate, - mode.refreshRate)); - waitForDisplayTransaction(EXTERNAL_DISPLAY); - EXPECT_TRUE(waitForModeChangedEvent(EXTERNAL_DISPLAY, i)); - break; - } - } - - EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); - EXPECT_EQ(ui::Size(800, 1600), mode.resolution); - EXPECT_EQ(1e9f / 11'111'111, mode.refreshRate); - - mFakeComposerClient->clearFrames(); - { - const ui::Size& resolution = mode.resolution; - auto surfaceControl = - mComposerClient->createSurface(String8("Display Test Surface Foo"), - resolution.getWidth(), resolution.getHeight(), - PIXEL_FORMAT_RGBA_8888, 0); - EXPECT_TRUE(surfaceControl != nullptr); - EXPECT_TRUE(surfaceControl->isValid()); - fillSurfaceRGBA8(surfaceControl, BLUE); - - { - TransactionScope ts(*mFakeComposerClient); - ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK); - - ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl); - } - } - - mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY, - V2_1::IComposerCallback::Connection::DISCONNECTED); - waitForDisplayTransaction(EXTERNAL_DISPLAY); - mFakeComposerClient->clearFrames(); - EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, false)); - } - - void Test_HotplugThreeConfigsMixedGroups() { - ALOGD("DisplayTest::Test_HotplugThreeConfigsMixedGroups"); - - setExpectationsForConfigs(EXTERNAL_DISPLAY, - {{.id = 2, - .w = 800, - .h = 1600, - .vsyncPeriod = 16'666'666, - .group = 0}, - {.id = 3, - .w = 800, - .h = 1600, - .vsyncPeriod = 11'111'111, - .group = 0}, - {.id = 4, - .w = 1600, - .h = 3200, - .vsyncPeriod = 8'333'333, - .group = 1}, - {.id = 5, - .w = 1600, - .h = 3200, - .vsyncPeriod = 11'111'111, - .group = 1}}, - 2, 16'666'666); - - mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY, - V2_1::IComposerCallback::Connection::CONNECTED); - waitForDisplayTransaction(EXTERNAL_DISPLAY); - EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, true)); - - const auto display = SurfaceComposerClient::getPhysicalDisplayToken(kExternalDisplayId); - EXPECT_FALSE(display == nullptr); - - ui::DisplayMode mode; - EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); - EXPECT_EQ(ui::Size(800, 1600), mode.resolution); - EXPECT_EQ(1e9f / 16'666'666, mode.refreshRate); - - mFakeComposerClient->clearFrames(); - { - const ui::Size& resolution = mode.resolution; - auto surfaceControl = - mComposerClient->createSurface(String8("Display Test Surface Foo"), - resolution.getWidth(), resolution.getHeight(), - PIXEL_FORMAT_RGBA_8888, 0); - EXPECT_TRUE(surfaceControl != nullptr); - EXPECT_TRUE(surfaceControl->isValid()); - fillSurfaceRGBA8(surfaceControl, BLUE); - - { - TransactionScope ts(*mFakeComposerClient); - ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK); - - ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl); - } - } - - ui::DynamicDisplayInfo info; - EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info)); - const auto& modes = info.supportedDisplayModes; - EXPECT_EQ(modes.size(), 4); - - // change active mode to 800x1600@90Hz - if (mIs2_4Client) { - EXPECT_CALL(*mMockComposer, setActiveConfigWithConstraints(EXTERNAL_DISPLAY, 3, _, _)) - .WillOnce(Return(V2_4::Error::NONE)); - } else { - EXPECT_CALL(*mMockComposer, setActiveConfig(EXTERNAL_DISPLAY, 3)) - .WillOnce(Return(V2_1::Error::NONE)); - } - - for (size_t i = 0; i < modes.size(); i++) { - const auto& mode = modes[i]; - if (mode.resolution.getWidth() == 800 && mode.refreshRate == 1e9f / 11'111'111) { - EXPECT_EQ(NO_ERROR, - SurfaceComposerClient::setDesiredDisplayModeSpecs(display, i, false, - modes[i].refreshRate, - modes[i].refreshRate, - modes[i].refreshRate, - modes[i].refreshRate)); - waitForDisplayTransaction(EXTERNAL_DISPLAY); - EXPECT_TRUE(waitForModeChangedEvent(EXTERNAL_DISPLAY, i)); - break; - } - } - - EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); - EXPECT_EQ(ui::Size(800, 1600), mode.resolution); - EXPECT_EQ(1e9f / 11'111'111, mode.refreshRate); - - mFakeComposerClient->clearFrames(); - { - const ui::Size& resolution = mode.resolution; - auto surfaceControl = - mComposerClient->createSurface(String8("Display Test Surface Foo"), - resolution.getWidth(), resolution.getHeight(), - PIXEL_FORMAT_RGBA_8888, 0); - EXPECT_TRUE(surfaceControl != nullptr); - EXPECT_TRUE(surfaceControl->isValid()); - fillSurfaceRGBA8(surfaceControl, BLUE); - - { - TransactionScope ts(*mFakeComposerClient); - ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK); - - ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl); - } - } - - // change active mode to 1600x3200@120Hz - if (mIs2_4Client) { - EXPECT_CALL(*mMockComposer, setActiveConfigWithConstraints(EXTERNAL_DISPLAY, 4, _, _)) - .WillOnce(Return(V2_4::Error::NONE)); - } else { - EXPECT_CALL(*mMockComposer, setActiveConfig(EXTERNAL_DISPLAY, 4)) - .WillOnce(Return(V2_1::Error::NONE)); - } - - for (int i = 0; i < modes.size(); i++) { - const auto& mode = modes[i]; - if (mode.refreshRate == 1e9f / 8'333'333) { - EXPECT_EQ(NO_ERROR, - SurfaceComposerClient::setDesiredDisplayModeSpecs(display, i, false, - mode.refreshRate, - mode.refreshRate, - mode.refreshRate, - mode.refreshRate)); - waitForDisplayTransaction(EXTERNAL_DISPLAY); - EXPECT_TRUE(waitForModeChangedEvent(EXTERNAL_DISPLAY, i)); - break; - } - } - - EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); - EXPECT_EQ(ui::Size(1600, 3200), mode.resolution); - EXPECT_EQ(1e9f / 8'333'333, mode.refreshRate); - - mFakeComposerClient->clearFrames(); - { - const ui::Size& resolution = mode.resolution; - auto surfaceControl = - mComposerClient->createSurface(String8("Display Test Surface Foo"), - resolution.getWidth(), resolution.getHeight(), - PIXEL_FORMAT_RGBA_8888, 0); - EXPECT_TRUE(surfaceControl != nullptr); - EXPECT_TRUE(surfaceControl->isValid()); - fillSurfaceRGBA8(surfaceControl, BLUE); - - { - TransactionScope ts(*mFakeComposerClient); - ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK); - - ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl); - } - } - - // change active mode to 1600x3200@90Hz - if (mIs2_4Client) { - EXPECT_CALL(*mMockComposer, setActiveConfigWithConstraints(EXTERNAL_DISPLAY, 5, _, _)) - .WillOnce(Return(V2_4::Error::NONE)); - } else { - EXPECT_CALL(*mMockComposer, setActiveConfig(EXTERNAL_DISPLAY, 5)) - .WillOnce(Return(V2_1::Error::NONE)); - } - - for (int i = 0; i < modes.size(); i++) { - const auto& mode = modes[i]; - if (mode.resolution.getWidth() == 1600 && mode.refreshRate == 1e9f / 11'111'111) { - EXPECT_EQ(NO_ERROR, - SurfaceComposerClient::setDesiredDisplayModeSpecs(display, i, false, - mode.refreshRate, - mode.refreshRate, - mode.refreshRate, - mode.refreshRate)); - waitForDisplayTransaction(EXTERNAL_DISPLAY); - EXPECT_TRUE(waitForModeChangedEvent(EXTERNAL_DISPLAY, i)); - break; - } - } - - EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); - EXPECT_EQ(ui::Size(1600, 3200), mode.resolution); - EXPECT_EQ(1e9f / 11'111'111, mode.refreshRate); - - mFakeComposerClient->clearFrames(); - { - const ui::Size& resolution = mode.resolution; - auto surfaceControl = - mComposerClient->createSurface(String8("Display Test Surface Foo"), - resolution.getWidth(), resolution.getHeight(), - PIXEL_FORMAT_RGBA_8888, 0); - EXPECT_TRUE(surfaceControl != nullptr); - EXPECT_TRUE(surfaceControl->isValid()); - fillSurfaceRGBA8(surfaceControl, BLUE); - - { - TransactionScope ts(*mFakeComposerClient); - ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK); - - ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl); - } - } - - mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY, - V2_1::IComposerCallback::Connection::DISCONNECTED); - waitForDisplayTransaction(EXTERNAL_DISPLAY); - mFakeComposerClient->clearFrames(); - EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, false)); - } - - void Test_HotplugPrimaryDisplay() { - ALOGD("DisplayTest::HotplugPrimaryDisplay"); - - mFakeComposerClient->hotplugDisplay(PRIMARY_DISPLAY, - V2_1::IComposerCallback::Connection::DISCONNECTED); - - waitForDisplayTransaction(PRIMARY_DISPLAY); - - EXPECT_TRUE(waitForHotplugEvent(PRIMARY_DISPLAY, false)); - { - const auto display = SurfaceComposerClient::getPhysicalDisplayToken(kPrimaryDisplayId); - EXPECT_TRUE(display == nullptr); - - ui::DisplayMode mode; - auto result = SurfaceComposerClient::getActiveDisplayMode(display, &mode); - EXPECT_NE(NO_ERROR, result); - } - - mFakeComposerClient->clearFrames(); - - setExpectationsForConfigs(PRIMARY_DISPLAY, - {{.id = 1, - .w = 400, - .h = 200, - .vsyncPeriod = 16'666'666, - .group = 0}}, - 1, 16'666'666); - - mFakeComposerClient->hotplugDisplay(PRIMARY_DISPLAY, - V2_1::IComposerCallback::Connection::CONNECTED); - - waitForDisplayTransaction(PRIMARY_DISPLAY); - - EXPECT_TRUE(waitForHotplugEvent(PRIMARY_DISPLAY, true)); - - { - const auto display = SurfaceComposerClient::getPhysicalDisplayToken(kPrimaryDisplayId); - EXPECT_FALSE(display == nullptr); - - ui::DisplayMode mode; - auto result = SurfaceComposerClient::getActiveDisplayMode(display, &mode); - EXPECT_EQ(NO_ERROR, result); - ASSERT_EQ(ui::Size(400, 200), mode.resolution); - EXPECT_EQ(1e9f / 16'666'666, mode.refreshRate); - } - } - - void Test_SubsequentHotplugConnectUpdatesDisplay(Display hwcDisplayId) { - ALOGD("DisplayTest::Test_SubsequentHotplugConnectUpdatesDisplay"); - - // Send a hotplug connected event to set up the initial display modes. - // The primary display is already connected so this will update it. - // If we're running the test of an external display this will create it. - setExpectationsForConfigs(hwcDisplayId, - {{.id = 1, - .w = 800, - .h = 1600, - .vsyncPeriod = 11'111'111, - .group = 1}}, - /* activeConfig */ 1, 11'111'111); - - mFakeComposerClient->hotplugDisplay(hwcDisplayId, - V2_1::IComposerCallback::Connection::CONNECTED); - waitForDisplayTransaction(hwcDisplayId); - EXPECT_TRUE(waitForHotplugEvent(hwcDisplayId, true)); - - const auto displayId = physicalIdFromHwcDisplayId(hwcDisplayId); - const auto display = SurfaceComposerClient::getPhysicalDisplayToken(displayId); - EXPECT_FALSE(display == nullptr); - - // Verify that the active mode and the supported moded are updated - { - ui::DisplayMode mode; - EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); - EXPECT_EQ(ui::Size(800, 1600), mode.resolution); - EXPECT_EQ(1e9f / 11'111'111, mode.refreshRate); - - ui::DynamicDisplayInfo info; - EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info)); - const auto& modes = info.supportedDisplayModes; - EXPECT_EQ(modes.size(), 1); - } - - // Send another hotplug connected event - setExpectationsForConfigs(hwcDisplayId, - { - {.id = 1, - .w = 800, - .h = 1600, - .vsyncPeriod = 16'666'666, - .group = 1}, - {.id = 2, - .w = 800, - .h = 1600, - .vsyncPeriod = 11'111'111, - .group = 1}, - {.id = 3, - .w = 800, - .h = 1600, - .vsyncPeriod = 8'333'333, - .group = 1}, - }, - /* activeConfig */ 1, 16'666'666); - - mFakeComposerClient->hotplugDisplay(hwcDisplayId, - V2_1::IComposerCallback::Connection::CONNECTED); - waitForDisplayTransaction(hwcDisplayId); - EXPECT_TRUE(waitForHotplugEvent(hwcDisplayId, true)); - - // Verify that the active mode and the supported moded are updated - { - ui::DisplayMode mode; - EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); - EXPECT_EQ(ui::Size(800, 1600), mode.resolution); - EXPECT_EQ(1e9f / 16'666'666, mode.refreshRate); - } - - ui::DynamicDisplayInfo info; - EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info)); - const auto& modes = info.supportedDisplayModes; - EXPECT_EQ(modes.size(), 3); - - EXPECT_EQ(ui::Size(800, 1600), modes[0].resolution); - EXPECT_EQ(1e9f / 16'666'666, modes[0].refreshRate); - - EXPECT_EQ(ui::Size(800, 1600), modes[1].resolution); - EXPECT_EQ(1e9f / 11'111'111, modes[1].refreshRate); - - EXPECT_EQ(ui::Size(800, 1600), modes[2].resolution); - EXPECT_EQ(1e9f / 8'333'333, modes[2].refreshRate); - - // Verify that we are able to switch to any of the modes - for (int i = modes.size() - 1; i >= 0; i--) { - const auto hwcId = i + 1; - // Set up HWC expectations for the mode change - if (mIs2_4Client) { - EXPECT_CALL(*mMockComposer, - setActiveConfigWithConstraints(hwcDisplayId, hwcId, _, _)) - .WillOnce(Return(V2_4::Error::NONE)); - } else { - EXPECT_CALL(*mMockComposer, setActiveConfig(hwcDisplayId, hwcId)) - .WillOnce(Return(V2_1::Error::NONE)); - } - - EXPECT_EQ(NO_ERROR, - SurfaceComposerClient::setDesiredDisplayModeSpecs(display, i, false, - modes[i].refreshRate, - modes[i].refreshRate, - modes[i].refreshRate, - modes[i].refreshRate)); - // We need to refresh twice - once to apply the pending mode change request, - // and once to process the change. - waitForDisplayTransaction(hwcDisplayId); - waitForDisplayTransaction(hwcDisplayId); - EXPECT_TRUE(waitForModeChangedEvent(hwcDisplayId, i)) - << "Failure while switching to mode " << i; - - ui::DisplayMode mode; - EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); - EXPECT_EQ(ui::Size(800, 1600), mode.resolution); - EXPECT_EQ(modes[i].refreshRate, mode.refreshRate); - } - } - - sp mFakeService; - sp mComposerClient; - - std::unique_ptr mMockComposer; - FakeComposerClient* mFakeComposerClient; - - std::unique_ptr mReceiver; - sp mLooper; - std::deque mReceivedDisplayEvents; - - static constexpr bool mIs2_4Client = - std::is_same::value; -}; - -using DisplayTest_2_1 = DisplayTest; - -// Tests that VSYNC injection can be safely toggled while invalidating. -TEST_F(DisplayTest_2_1, VsyncInjection) { - const auto flinger = ComposerServiceAIDL::getComposerService(); - bool enable = true; - - for (int i = 0; i < 100; i++) { - flinger->enableVSyncInjections(enable); - enable = !enable; - - constexpr uint32_t kForceInvalidate = 1004; - android::Parcel data, reply; - data.writeInterfaceToken(String16("android.ui.ISurfaceComposer")); - EXPECT_EQ(NO_ERROR, - android::IInterface::asBinder(flinger)->transact(kForceInvalidate, data, &reply)); - - std::this_thread::sleep_for(5ms); - } -} - -TEST_F(DisplayTest_2_1, HotplugOneConfig) { - Test_HotplugOneConfig(); -} - -TEST_F(DisplayTest_2_1, HotplugTwoSeparateConfigs) { - Test_HotplugTwoSeparateConfigs(); -} - -TEST_F(DisplayTest_2_1, HotplugTwoConfigsSameGroup) { - Test_HotplugTwoConfigsSameGroup(); -} - -TEST_F(DisplayTest_2_1, HotplugThreeConfigsMixedGroups) { - Test_HotplugThreeConfigsMixedGroups(); -} - -TEST_F(DisplayTest_2_1, HotplugPrimaryOneConfig) { - Test_HotplugPrimaryDisplay(); -} - -TEST_F(DisplayTest_2_1, SubsequentHotplugConnectUpdatesPrimaryDisplay) { - Test_SubsequentHotplugConnectUpdatesDisplay(PRIMARY_DISPLAY); -} - -TEST_F(DisplayTest_2_1, SubsequentHotplugConnectUpdatesExternalDisplay) { - Test_SubsequentHotplugConnectUpdatesDisplay(EXTERNAL_DISPLAY); -} - -using DisplayTest_2_2 = DisplayTest; - -TEST_F(DisplayTest_2_2, HotplugOneConfig) { - Test_HotplugOneConfig(); -} - -TEST_F(DisplayTest_2_2, HotplugTwoSeparateConfigs) { - Test_HotplugTwoSeparateConfigs(); -} - -TEST_F(DisplayTest_2_2, HotplugTwoConfigsSameGroup) { - Test_HotplugTwoConfigsSameGroup(); -} - -TEST_F(DisplayTest_2_2, HotplugThreeConfigsMixedGroups) { - Test_HotplugThreeConfigsMixedGroups(); -} - -TEST_F(DisplayTest_2_2, HotplugPrimaryOneConfig) { - Test_HotplugPrimaryDisplay(); -} - -TEST_F(DisplayTest_2_2, SubsequentHotplugConnectUpdatesPrimaryDisplay) { - Test_SubsequentHotplugConnectUpdatesDisplay(PRIMARY_DISPLAY); -} - -TEST_F(DisplayTest_2_2, SubsequentHotplugConnectUpdatesExternalDisplay) { - Test_SubsequentHotplugConnectUpdatesDisplay(EXTERNAL_DISPLAY); -} - -using DisplayTest_2_3 = DisplayTest; - -TEST_F(DisplayTest_2_3, HotplugOneConfig) { - Test_HotplugOneConfig(); -} - -TEST_F(DisplayTest_2_3, HotplugTwoSeparateConfigs) { - Test_HotplugTwoSeparateConfigs(); -} - -TEST_F(DisplayTest_2_3, HotplugTwoConfigsSameGroup) { - Test_HotplugTwoConfigsSameGroup(); -} - -TEST_F(DisplayTest_2_3, HotplugThreeConfigsMixedGroups) { - Test_HotplugThreeConfigsMixedGroups(); -} - -TEST_F(DisplayTest_2_3, HotplugPrimaryOneConfig) { - Test_HotplugPrimaryDisplay(); -} - -TEST_F(DisplayTest_2_3, SubsequentHotplugConnectUpdatesPrimaryDisplay) { - Test_SubsequentHotplugConnectUpdatesDisplay(PRIMARY_DISPLAY); -} - -TEST_F(DisplayTest_2_3, SubsequentHotplugConnectUpdatesExternalDisplay) { - Test_SubsequentHotplugConnectUpdatesDisplay(EXTERNAL_DISPLAY); -} - -using DisplayTest_2_4 = DisplayTest; - -TEST_F(DisplayTest_2_4, HotplugOneConfig) { - Test_HotplugOneConfig(); -} - -TEST_F(DisplayTest_2_4, HotplugTwoSeparateConfigs) { - Test_HotplugTwoSeparateConfigs(); -} - -TEST_F(DisplayTest_2_4, HotplugTwoConfigsSameGroup) { - Test_HotplugTwoConfigsSameGroup(); -} - -TEST_F(DisplayTest_2_4, HotplugThreeConfigsMixedGroups) { - Test_HotplugThreeConfigsMixedGroups(); -} - -TEST_F(DisplayTest_2_4, HotplugPrimaryOneConfig) { - Test_HotplugPrimaryDisplay(); -} - -TEST_F(DisplayTest_2_4, SubsequentHotplugConnectUpdatesPrimaryDisplay) { - Test_SubsequentHotplugConnectUpdatesDisplay(PRIMARY_DISPLAY); -} - -TEST_F(DisplayTest_2_4, SubsequentHotplugConnectUpdatesExternalDisplay) { - Test_SubsequentHotplugConnectUpdatesDisplay(EXTERNAL_DISPLAY); -} - -//////////////////////////////////////////////// - -template -class TransactionTest : public ::testing::Test { -protected: - // Layer array indexing constants. - constexpr static int BG_LAYER = 0; - constexpr static int FG_LAYER = 1; - - static void SetUpTestCase() { - // TODO: See TODO comment at DisplayTest::SetUp for background on - // the lifetime of the FakeComposerClient. - sFakeComposer = new FakeComposerClient; - auto client = sp::make(sFakeComposer); - sp fakeService = sp::make(client); - (void)fakeService->registerAsService("mock"); - - android::hardware::ProcessState::self()->startThreadPool(); - android::ProcessState::self()->startThreadPool(); - - startSurfaceFlinger(); - - // Fake composer wants to enable VSync injection - sFakeComposer->onSurfaceFlingerStart(); - } - - static void TearDownTestCase() { - // Fake composer needs to release SurfaceComposerClient before the stop. - sFakeComposer->onSurfaceFlingerStop(); - stopSurfaceFlinger(); - // TODO: This is deleted when the ComposerClient calls - // removeClient. Devise better lifetime control. - sFakeComposer = nullptr; - } - - void SetUp() override { - ALOGI("TransactionTest::SetUp"); - mComposerClient = sp::make(); - ASSERT_EQ(NO_ERROR, mComposerClient->initCheck()); - - ALOGI("TransactionTest::SetUp - display"); - const auto display = SurfaceComposerClient::getPhysicalDisplayToken(kPrimaryDisplayId); - ASSERT_FALSE(display == nullptr); - - ui::DisplayMode mode; - ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); - - const ui::Size& resolution = mode.resolution; - mDisplayWidth = resolution.getWidth(); - mDisplayHeight = resolution.getHeight(); - - // Background surface - mBGSurfaceControl = - mComposerClient->createSurface(String8("BG Test Surface"), mDisplayWidth, - mDisplayHeight, PIXEL_FORMAT_RGBA_8888, 0); - ASSERT_TRUE(mBGSurfaceControl != nullptr); - ASSERT_TRUE(mBGSurfaceControl->isValid()); - fillSurfaceRGBA8(mBGSurfaceControl, BLUE); - - // Foreground surface - mFGSurfaceControl = mComposerClient->createSurface(String8("FG Test Surface"), 64, 64, - PIXEL_FORMAT_RGBA_8888, 0); - ASSERT_TRUE(mFGSurfaceControl != nullptr); - ASSERT_TRUE(mFGSurfaceControl->isValid()); - - fillSurfaceRGBA8(mFGSurfaceControl, RED); - - Transaction t; - t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK); - - t.setLayer(mBGSurfaceControl, INT32_MAX - 2); - t.show(mBGSurfaceControl); - - t.setLayer(mFGSurfaceControl, INT32_MAX - 1); - t.setPosition(mFGSurfaceControl, 64, 64); - t.show(mFGSurfaceControl); - - // Synchronous transaction will stop this thread, so we set up a - // delayed, off-thread vsync request before closing the - // transaction. In the test code this is usually done with - // TransactionScope. Leaving here in the 'vanilla' form for - // reference. - ASSERT_EQ(0, sFakeComposer->getFrameCount()); - sFakeComposer->runVSyncAfter(1ms); - t.apply(); - sFakeComposer->waitUntilFrame(1); - - // Reference data. This is what the HWC should see. - static_assert(BG_LAYER == 0 && FG_LAYER == 1, "Unexpected enum values for array indexing"); - mBaseFrame.push_back(makeSimpleRect(0u, 0u, mDisplayWidth, mDisplayHeight)); - mBaseFrame[BG_LAYER].mSwapCount = 1; - mBaseFrame.push_back(makeSimpleRect(64, 64, 64 + 64, 64 + 64)); - mBaseFrame[FG_LAYER].mSwapCount = 1; - - auto frame = sFakeComposer->getFrameRects(0); - ASSERT_TRUE(framesAreSame(mBaseFrame, frame)); - } - - void TearDown() override { - ALOGD("TransactionTest::TearDown"); - - mComposerClient->dispose(); - mBGSurfaceControl = 0; - mFGSurfaceControl = 0; - mComposerClient = 0; - - sFakeComposer->runVSyncAndWait(); - mBaseFrame.clear(); - sFakeComposer->clearFrames(); - ASSERT_EQ(0, sFakeComposer->getFrameCount()); - - sp sf(ComposerServiceAIDL::getComposerService()); - std::vector layers; - binder::Status status = sf->getLayerDebugInfo(&layers); - status_t result = gui::aidl_utils::statusTFromBinderStatus(status); - if (result != NO_ERROR) { - ALOGE("Failed to get layers %s %d", strerror(-result), result); - } else { - // If this fails, the test being torn down leaked layers. - EXPECT_EQ(0u, layers.size()); - if (layers.size() > 0) { - for (auto layer = layers.begin(); layer != layers.end(); ++layer) { - std::cout << to_string(*layer).c_str(); - } - // To ensure the next test has clean slate, will run the class - // tear down and setup here. - TearDownTestCase(); - SetUpTestCase(); - } - } - ALOGD("TransactionTest::TearDown - complete"); - } - - void Test_LayerMove() { - ALOGD("TransactionTest::LayerMove"); - - // The scope opens and closes a global transaction and, at the - // same time, makes sure the SurfaceFlinger progresses one frame - // after the transaction closes. The results of the transaction - // should be available in the latest frame stored by the fake - // composer. - { - TransactionScope ts(*sFakeComposer); - ts.setPosition(mFGSurfaceControl, 128, 128); - // NOTE: No changes yet, so vsync will do nothing, HWC does not get any calls. - // (How to verify that? Throw in vsync and wait a 2x frame time? Separate test?) - // - // sFakeComposer->runVSyncAndWait(); - } - - fillSurfaceRGBA8(mFGSurfaceControl, GREEN); - sFakeComposer->runVSyncAndWait(); - - ASSERT_EQ(3, sFakeComposer->getFrameCount()); // Make sure the waits didn't time out and - // there's no extra frames. - - // NOTE: Frame 0 is produced in the SetUp. - auto frame1Ref = mBaseFrame; - frame1Ref[FG_LAYER].mDisplayFrame = - hwc_rect_t{128, 128, 128 + 64, 128 + 64}; // Top-most layer moves. - EXPECT_TRUE(framesAreSame(frame1Ref, sFakeComposer->getFrameRects(1))); - - auto frame2Ref = frame1Ref; - frame2Ref[FG_LAYER].mSwapCount++; - EXPECT_TRUE(framesAreSame(frame2Ref, sFakeComposer->getFrameRects(2))); - } - - void Test_LayerCrop() { - // TODO: Add scaling to confirm that crop happens in buffer space? - { - TransactionScope ts(*sFakeComposer); - Rect cropRect(16, 16, 32, 32); - ts.setCrop(mFGSurfaceControl, cropRect); - } - ASSERT_EQ(2, sFakeComposer->getFrameCount()); - - auto referenceFrame = mBaseFrame; - referenceFrame[FG_LAYER].mSourceCrop = hwc_frect_t{16.f, 16.f, 32.f, 32.f}; - referenceFrame[FG_LAYER].mDisplayFrame = hwc_rect_t{64 + 16, 64 + 16, 64 + 32, 64 + 32}; - EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame())); - } - - void Test_LayerSetLayer() { - { - TransactionScope ts(*sFakeComposer); - ts.setLayer(mFGSurfaceControl, INT_MAX - 3); - } - ASSERT_EQ(2, sFakeComposer->getFrameCount()); - - // The layers will switch order, but both are rendered because the background layer is - // transparent (RGBA8888). - std::vector referenceFrame(2); - referenceFrame[0] = mBaseFrame[FG_LAYER]; - referenceFrame[1] = mBaseFrame[BG_LAYER]; - EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame())); - } - - void Test_LayerSetLayerOpaque() { - { - TransactionScope ts(*sFakeComposer); - ts.setLayer(mFGSurfaceControl, INT_MAX - 3); - ts.setFlags(mBGSurfaceControl, layer_state_t::eLayerOpaque, - layer_state_t::eLayerOpaque); - } - ASSERT_EQ(2, sFakeComposer->getFrameCount()); - - // The former foreground layer is now covered with opaque layer - it should have disappeared - std::vector referenceFrame(1); - referenceFrame[BG_LAYER] = mBaseFrame[BG_LAYER]; - EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame())); - } - - void Test_SetLayerStack() { - ALOGD("TransactionTest::SetLayerStack"); - { - TransactionScope ts(*sFakeComposer); - ts.setLayerStack(mFGSurfaceControl, ui::LayerStack{1}); - } - - // Foreground layer should have disappeared. - ASSERT_EQ(2, sFakeComposer->getFrameCount()); - std::vector refFrame(1); - refFrame[BG_LAYER] = mBaseFrame[BG_LAYER]; - EXPECT_TRUE(framesAreSame(refFrame, sFakeComposer->getLatestFrame())); - } - - void Test_LayerShowHide() { - ALOGD("TransactionTest::LayerShowHide"); - { - TransactionScope ts(*sFakeComposer); - ts.hide(mFGSurfaceControl); - } - - // Foreground layer should have disappeared. - ASSERT_EQ(2, sFakeComposer->getFrameCount()); - std::vector refFrame(1); - refFrame[BG_LAYER] = mBaseFrame[BG_LAYER]; - EXPECT_TRUE(framesAreSame(refFrame, sFakeComposer->getLatestFrame())); - - { - TransactionScope ts(*sFakeComposer); - ts.show(mFGSurfaceControl); - } - - // Foreground layer should be back - ASSERT_EQ(3, sFakeComposer->getFrameCount()); - EXPECT_TRUE(framesAreSame(mBaseFrame, sFakeComposer->getLatestFrame())); - } - - void Test_LayerSetAlpha() { - { - TransactionScope ts(*sFakeComposer); - ts.setAlpha(mFGSurfaceControl, 0.75f); - } - - ASSERT_EQ(2, sFakeComposer->getFrameCount()); - auto referenceFrame = mBaseFrame; - referenceFrame[FG_LAYER].mPlaneAlpha = 0.75f; - EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame())); - } - - void Test_LayerSetFlags() { - { - TransactionScope ts(*sFakeComposer); - ts.setFlags(mFGSurfaceControl, layer_state_t::eLayerHidden, - layer_state_t::eLayerHidden); - } - - // Foreground layer should have disappeared. - ASSERT_EQ(2, sFakeComposer->getFrameCount()); - std::vector refFrame(1); - refFrame[BG_LAYER] = mBaseFrame[BG_LAYER]; - EXPECT_TRUE(framesAreSame(refFrame, sFakeComposer->getLatestFrame())); - } - - void Test_LayerSetMatrix() { - struct matrixTestData { - float matrix[4]; - hwc_transform_t expectedTransform; - hwc_rect_t expectedDisplayFrame; - }; - - // The matrix operates on the display frame and is applied before - // the position is added. So, the foreground layer rect is (0, 0, - // 64, 64) is first transformed, potentially yielding negative - // coordinates and then the position (64, 64) is added yielding - // the final on-screen rectangles given. - - const matrixTestData MATRIX_TESTS[7] = // clang-format off - {{{-1.f, 0.f, 0.f, 1.f}, HWC_TRANSFORM_FLIP_H, {0, 64, 64, 128}}, - {{1.f, 0.f, 0.f, -1.f}, HWC_TRANSFORM_FLIP_V, {64, 0, 128, 64}}, - {{0.f, 1.f, -1.f, 0.f}, HWC_TRANSFORM_ROT_90, {0, 64, 64, 128}}, - {{-1.f, 0.f, 0.f, -1.f}, HWC_TRANSFORM_ROT_180, {0, 0, 64, 64}}, - {{0.f, -1.f, 1.f, 0.f}, HWC_TRANSFORM_ROT_270, {64, 0, 128, 64}}, - {{0.f, 1.f, 1.f, 0.f}, HWC_TRANSFORM_FLIP_H_ROT_90, {64, 64, 128, 128}}, - {{0.f, 1.f, 1.f, 0.f}, HWC_TRANSFORM_FLIP_V_ROT_90, {64, 64, 128, 128}}}; - // clang-format on - constexpr int TEST_COUNT = sizeof(MATRIX_TESTS) / sizeof(matrixTestData); - - for (int i = 0; i < TEST_COUNT; i++) { - // TODO: How to leverage the HWC2 stringifiers? - const matrixTestData& xform = MATRIX_TESTS[i]; - SCOPED_TRACE(i); - { - TransactionScope ts(*sFakeComposer); - ts.setMatrix(mFGSurfaceControl, xform.matrix[0], xform.matrix[1], xform.matrix[2], - xform.matrix[3]); - } - - auto referenceFrame = mBaseFrame; - referenceFrame[FG_LAYER].mTransform = xform.expectedTransform; - referenceFrame[FG_LAYER].mDisplayFrame = xform.expectedDisplayFrame; - - EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame())); - } - } - - void Test_SetRelativeLayer() { - constexpr int RELATIVE_LAYER = 2; - auto relativeSurfaceControl = mComposerClient->createSurface(String8("Test Surface"), 64, - 64, PIXEL_FORMAT_RGBA_8888, 0); - fillSurfaceRGBA8(relativeSurfaceControl, LIGHT_RED); - - // Now we stack the surface above the foreground surface and make sure it is visible. - { - TransactionScope ts(*sFakeComposer); - ts.setPosition(relativeSurfaceControl, 64, 64); - ts.show(relativeSurfaceControl); - ts.setRelativeLayer(relativeSurfaceControl, mFGSurfaceControl, 1); - } - auto referenceFrame = mBaseFrame; - // NOTE: All three layers will be visible as the surfaces are - // transparent because of the RGBA format. - referenceFrame.push_back(makeSimpleRect(64, 64, 64 + 64, 64 + 64)); - referenceFrame[RELATIVE_LAYER].mSwapCount = 1; - EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame())); - - // A call to setLayer will override a call to setRelativeLayer - { - TransactionScope ts(*sFakeComposer); - ts.setLayer(relativeSurfaceControl, 0); - } - - // Previous top layer will now appear at the bottom. - auto referenceFrame2 = mBaseFrame; - referenceFrame2.insert(referenceFrame2.begin(), referenceFrame[RELATIVE_LAYER]); - EXPECT_EQ(3, sFakeComposer->getFrameCount()); - EXPECT_TRUE(framesAreSame(referenceFrame2, sFakeComposer->getLatestFrame())); - } - - sp mComposerClient; - sp mBGSurfaceControl; - sp mFGSurfaceControl; - std::vector mBaseFrame; - uint32_t mDisplayWidth; - uint32_t mDisplayHeight; - - static inline FakeComposerClient* sFakeComposer; -}; - -using TransactionTest_2_1 = TransactionTest; - -TEST_F(TransactionTest_2_1, DISABLED_LayerMove) { - Test_LayerMove(); -} - -TEST_F(TransactionTest_2_1, DISABLED_LayerCrop) { - Test_LayerCrop(); -} - -TEST_F(TransactionTest_2_1, DISABLED_LayerSetLayer) { - Test_LayerSetLayer(); -} - -TEST_F(TransactionTest_2_1, DISABLED_LayerSetLayerOpaque) { - Test_LayerSetLayerOpaque(); -} - -TEST_F(TransactionTest_2_1, DISABLED_SetLayerStack) { - Test_SetLayerStack(); -} - -TEST_F(TransactionTest_2_1, DISABLED_LayerShowHide) { - Test_LayerShowHide(); -} - -TEST_F(TransactionTest_2_1, DISABLED_LayerSetAlpha) { - Test_LayerSetAlpha(); -} - -TEST_F(TransactionTest_2_1, DISABLED_LayerSetFlags) { - Test_LayerSetFlags(); -} - -TEST_F(TransactionTest_2_1, DISABLED_LayerSetMatrix) { - Test_LayerSetMatrix(); -} - -TEST_F(TransactionTest_2_1, DISABLED_SetRelativeLayer) { - Test_SetRelativeLayer(); -} - -template -class ChildLayerTest : public TransactionTest { - using Base = TransactionTest; - -protected: - constexpr static int CHILD_LAYER = 2; - - void SetUp() override { - Base::SetUp(); - mChild = Base::mComposerClient->createSurface(String8("Child surface"), 10, 10, - PIXEL_FORMAT_RGBA_8888, 0, - Base::mFGSurfaceControl->getHandle()); - fillSurfaceRGBA8(mChild, LIGHT_GRAY); - - Base::sFakeComposer->runVSyncAndWait(); - Base::mBaseFrame.push_back(makeSimpleRect(64, 64, 64 + 10, 64 + 10)); - Base::mBaseFrame[CHILD_LAYER].mSwapCount = 1; - ASSERT_EQ(2, Base::sFakeComposer->getFrameCount()); - ASSERT_TRUE(framesAreSame(Base::mBaseFrame, Base::sFakeComposer->getLatestFrame())); - } - - void TearDown() override { - mChild = 0; - Base::TearDown(); - } - - void Test_Positioning() { - { - TransactionScope ts(*Base::sFakeComposer); - ts.show(mChild); - ts.setPosition(mChild, 10, 10); - // Move to the same position as in the original setup. - ts.setPosition(Base::mFGSurfaceControl, 64, 64); - } - - auto referenceFrame = Base::mBaseFrame; - referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{64, 64, 64 + 64, 64 + 64}; - referenceFrame[CHILD_LAYER].mDisplayFrame = - hwc_rect_t{64 + 10, 64 + 10, 64 + 10 + 10, 64 + 10 + 10}; - EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame())); - - { - TransactionScope ts(*Base::sFakeComposer); - ts.setPosition(Base::mFGSurfaceControl, 0, 0); - } - - auto referenceFrame2 = Base::mBaseFrame; - referenceFrame2[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 0 + 64, 0 + 64}; - referenceFrame2[CHILD_LAYER].mDisplayFrame = - hwc_rect_t{0 + 10, 0 + 10, 0 + 10 + 10, 0 + 10 + 10}; - EXPECT_TRUE(framesAreSame(referenceFrame2, Base::sFakeComposer->getLatestFrame())); - } - - void Test_Cropping() { - { - TransactionScope ts(*Base::sFakeComposer); - ts.show(mChild); - ts.setPosition(mChild, 0, 0); - ts.setPosition(Base::mFGSurfaceControl, 0, 0); - ts.setCrop(Base::mFGSurfaceControl, Rect(0, 0, 5, 5)); - } - // NOTE: The foreground surface would be occluded by the child - // now, but is included in the stack because the child is - // transparent. - auto referenceFrame = Base::mBaseFrame; - referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 0 + 5, 0 + 5}; - referenceFrame[Base::FG_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 5.f, 5.f}; - referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 0 + 5, 0 + 5}; - referenceFrame[CHILD_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 5.f, 5.f}; - EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame())); - } - - void Test_Constraints() { - { - TransactionScope ts(*Base::sFakeComposer); - ts.show(mChild); - ts.setPosition(Base::mFGSurfaceControl, 0, 0); - ts.setPosition(mChild, 63, 63); - } - auto referenceFrame = Base::mBaseFrame; - referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64}; - referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{63, 63, 64, 64}; - referenceFrame[CHILD_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 1.f, 1.f}; - EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame())); - } - - void Test_Scaling() { - { - TransactionScope ts(*Base::sFakeComposer); - ts.setPosition(Base::mFGSurfaceControl, 0, 0); - } - auto referenceFrame = Base::mBaseFrame; - referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64}; - referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 10, 10}; - EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame())); - - { - TransactionScope ts(*Base::sFakeComposer); - ts.setMatrix(Base::mFGSurfaceControl, 2.0, 0, 0, 2.0); - } - - auto referenceFrame2 = Base::mBaseFrame; - referenceFrame2[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 128, 128}; - referenceFrame2[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 20, 20}; - EXPECT_TRUE(framesAreSame(referenceFrame2, Base::sFakeComposer->getLatestFrame())); - } - - void Test_LayerAlpha() { - { - TransactionScope ts(*Base::sFakeComposer); - ts.show(mChild); - ts.setPosition(mChild, 0, 0); - ts.setPosition(Base::mFGSurfaceControl, 0, 0); - ts.setAlpha(mChild, 0.5); - } - - auto referenceFrame = Base::mBaseFrame; - referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64}; - referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 10, 10}; - referenceFrame[CHILD_LAYER].mPlaneAlpha = 0.5f; - EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame())); - - { - TransactionScope ts(*Base::sFakeComposer); - ts.setAlpha(Base::mFGSurfaceControl, 0.5); - } - - auto referenceFrame2 = referenceFrame; - referenceFrame2[Base::FG_LAYER].mPlaneAlpha = 0.5f; - referenceFrame2[CHILD_LAYER].mPlaneAlpha = 0.25f; - EXPECT_TRUE(framesAreSame(referenceFrame2, Base::sFakeComposer->getLatestFrame())); - } - - sp mChild; -}; - -using ChildLayerTest_2_1 = ChildLayerTest; - -TEST_F(ChildLayerTest_2_1, DISABLED_Positioning) { - Test_Positioning(); -} - -TEST_F(ChildLayerTest_2_1, DISABLED_Cropping) { - Test_Cropping(); -} - -TEST_F(ChildLayerTest_2_1, DISABLED_Constraints) { - Test_Constraints(); -} - -TEST_F(ChildLayerTest_2_1, DISABLED_Scaling) { - Test_Scaling(); -} - -TEST_F(ChildLayerTest_2_1, DISABLED_LayerAlpha) { - Test_LayerAlpha(); -} - -template -class ChildColorLayerTest : public ChildLayerTest { - using Base = ChildLayerTest; - -protected: - void SetUp() override { - Base::SetUp(); - Base::mChild = - Base::mComposerClient->createSurface(String8("Child surface"), 0, 0, - PIXEL_FORMAT_RGBA_8888, - ISurfaceComposerClient::eFXSurfaceEffect, - Base::mFGSurfaceControl->getHandle()); - { - TransactionScope ts(*Base::sFakeComposer); - ts.setColor(Base::mChild, - {LIGHT_GRAY.r / 255.0f, LIGHT_GRAY.g / 255.0f, LIGHT_GRAY.b / 255.0f}); - ts.setCrop(Base::mChild, Rect(0, 0, 10, 10)); - } - - Base::sFakeComposer->runVSyncAndWait(); - Base::mBaseFrame.push_back(makeSimpleRect(64, 64, 64 + 10, 64 + 10)); - Base::mBaseFrame[Base::CHILD_LAYER].mSourceCrop = hwc_frect_t{0.0f, 0.0f, 0.0f, 0.0f}; - Base::mBaseFrame[Base::CHILD_LAYER].mSwapCount = 0; - ASSERT_EQ(2, Base::sFakeComposer->getFrameCount()); - ASSERT_TRUE(framesAreSame(Base::mBaseFrame, Base::sFakeComposer->getLatestFrame())); - } - - void Test_LayerAlpha() { - { - TransactionScope ts(*Base::sFakeComposer); - ts.show(Base::mChild); - ts.setPosition(Base::mChild, 0, 0); - ts.setPosition(Base::mFGSurfaceControl, 0, 0); - ts.setAlpha(Base::mChild, 0.5); - } - - auto referenceFrame = Base::mBaseFrame; - referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64}; - referenceFrame[Base::CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 10, 10}; - referenceFrame[Base::CHILD_LAYER].mPlaneAlpha = 0.5f; - EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame())); - - { - TransactionScope ts(*Base::sFakeComposer); - ts.setAlpha(Base::mFGSurfaceControl, 0.5); - } - - auto referenceFrame2 = referenceFrame; - referenceFrame2[Base::FG_LAYER].mPlaneAlpha = 0.5f; - referenceFrame2[Base::CHILD_LAYER].mPlaneAlpha = 0.25f; - EXPECT_TRUE(framesAreSame(referenceFrame2, Base::sFakeComposer->getLatestFrame())); - } - - void Test_LayerZeroAlpha() { - { - TransactionScope ts(*Base::sFakeComposer); - ts.show(Base::mChild); - ts.setPosition(Base::mChild, 0, 0); - ts.setPosition(Base::mFGSurfaceControl, 0, 0); - ts.setAlpha(Base::mChild, 0.5); - } - - auto referenceFrame = Base::mBaseFrame; - referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64}; - referenceFrame[Base::CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 10, 10}; - referenceFrame[Base::CHILD_LAYER].mPlaneAlpha = 0.5f; - EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame())); - - { - TransactionScope ts(*Base::sFakeComposer); - ts.setAlpha(Base::mFGSurfaceControl, 0.0f); - } - - std::vector refFrame(1); - refFrame[Base::BG_LAYER] = Base::mBaseFrame[Base::BG_LAYER]; - - EXPECT_TRUE(framesAreSame(refFrame, Base::sFakeComposer->getLatestFrame())); - } -}; - -using ChildColorLayerTest_2_1 = ChildColorLayerTest; - -TEST_F(ChildColorLayerTest_2_1, DISABLED_LayerAlpha) { - Test_LayerAlpha(); -} - -TEST_F(ChildColorLayerTest_2_1, DISABLED_LayerZeroAlpha) { - Test_LayerZeroAlpha(); -} -} // namespace - -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - - auto* fakeEnvironment = new sftest::FakeHwcEnvironment; - ::testing::AddGlobalTestEnvironment(fakeEnvironment); - ::testing::InitGoogleMock(&argc, argv); - return RUN_ALL_TESTS(); -} - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion -Wextra" -- GitLab From fcb1686a3f396ebb45789b8231e1ed8669af681b Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Mon, 10 Oct 2022 14:35:21 -0700 Subject: [PATCH 0359/1310] SF: make FrameTimeline more robust for fence errors - Emit a valid timestamp to Perfetto when fence signal time is invalid - Mark pending fences as invalid if a newer fence has signaled Test: SF unit tests Bug: 243939707 Change-Id: Ieac7eb53fe3e36178d860cc0683bfd8fad7560cd --- .../FrameTimeline/FrameTimeline.cpp | 34 +++++++++++++- .../FrameTimeline/FrameTimeline.h | 1 + .../tests/unittests/FrameTimelineTest.cpp | 46 ++++++++++++++++++- 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp index c73a75c756..cd1ba70d84 100644 --- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp +++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp @@ -892,6 +892,10 @@ void FrameTimeline::DisplayFrame::classifyJank(nsecs_t& deadlineDelta, nsecs_t& mJankType = JankType::Unknown; deadlineDelta = 0; deltaToVsync = 0; + if (mSurfaceFlingerActuals.presentTime == Fence::SIGNAL_TIME_INVALID) { + mSurfaceFlingerActuals.presentTime = mSurfaceFlingerActuals.endTime; + } + return; } @@ -1168,22 +1172,50 @@ float FrameTimeline::computeFps(const std::unordered_set& layerIds) { static_cast(totalPresentToPresentWalls); } +std::optional FrameTimeline::getFirstSignalFenceIndex() const { + for (size_t i = 0; i < mPendingPresentFences.size(); i++) { + const auto& [fence, _] = mPendingPresentFences[i]; + if (fence && fence->isValid() && fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) { + return i; + } + } + + return {}; +} + void FrameTimeline::flushPendingPresentFences() { + const auto firstSignaledFence = getFirstSignalFenceIndex(); + if (!firstSignaledFence.has_value()) { + return; + } + // Perfetto is using boottime clock to void drifts when the device goes // to suspend. const auto monoBootOffset = mUseBootTimeClock ? (systemTime(SYSTEM_TIME_BOOTTIME) - systemTime(SYSTEM_TIME_MONOTONIC)) : 0; + // Present fences are expected to be signaled in order. Mark all the previous + // pending fences as errors. + for (size_t i = 0; i < firstSignaledFence.value(); i++) { + const auto& pendingPresentFence = *mPendingPresentFences.begin(); + const nsecs_t signalTime = Fence::SIGNAL_TIME_INVALID; + auto& displayFrame = pendingPresentFence.second; + displayFrame->onPresent(signalTime, mPreviousPresentTime); + displayFrame->trace(mSurfaceFlingerPid, monoBootOffset); + mPendingPresentFences.erase(mPendingPresentFences.begin()); + } + for (size_t i = 0; i < mPendingPresentFences.size(); i++) { const auto& pendingPresentFence = mPendingPresentFences[i]; nsecs_t signalTime = Fence::SIGNAL_TIME_INVALID; if (pendingPresentFence.first && pendingPresentFence.first->isValid()) { signalTime = pendingPresentFence.first->getSignalTime(); if (signalTime == Fence::SIGNAL_TIME_PENDING) { - continue; + break; } } + auto& displayFrame = pendingPresentFence.second; displayFrame->onPresent(signalTime, mPreviousPresentTime); displayFrame->trace(mSurfaceFlingerPid, monoBootOffset); diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h index a2305af554..31074b1959 100644 --- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h +++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h @@ -474,6 +474,7 @@ private: friend class android::frametimeline::FrameTimelineTest; void flushPendingPresentFences() REQUIRES(mMutex); + std::optional getFirstSignalFenceIndex() const REQUIRES(mMutex); void finalizeCurrentDisplayFrame() REQUIRES(mMutex); void dumpAll(std::string& result); void dumpJank(std::string& result); diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp index 874fa7c766..f47ac6dc43 100644 --- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp +++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp @@ -480,7 +480,7 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_invalidSignalTime) { addEmptyDisplayFrame(); auto displayFrame0 = getDisplayFrame(0); - EXPECT_EQ(displayFrame0->getActuals().presentTime, -1); + EXPECT_EQ(displayFrame0->getActuals().presentTime, 59); EXPECT_EQ(displayFrame0->getJankType(), JankType::Unknown); EXPECT_EQ(surfaceFrame1->getActuals().presentTime, -1); EXPECT_EQ(surfaceFrame1->getJankType(), JankType::Unknown); @@ -2228,6 +2228,50 @@ TEST_F(FrameTimelineTest, jankClassification_displayFrameLateFinishLatePresent_G EXPECT_EQ(displayFrame2->getJankType(), JankType::SurfaceFlingerCpuDeadlineMissed); } +TEST_F(FrameTimelineTest, jankClassification_presentFenceError) { + auto erroneousPresentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); + auto erroneousPresentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); + auto validPresentFence = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); + int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 40}); + int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 60}); + int64_t sfToken3 = mTokenManager->generateTokenForPredictions({72, 80, 80}); + + mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11)); + mFrameTimeline->setSfPresent(26, erroneousPresentFence1); + + mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(11)); + mFrameTimeline->setSfPresent(60, erroneousPresentFence2); + + mFrameTimeline->setSfWakeUp(sfToken3, 72, Fps::fromPeriodNsecs(11)); + mFrameTimeline->setSfPresent(80, validPresentFence); + + validPresentFence->signalForTest(80); + + addEmptyDisplayFrame(); + + { + auto displayFrame = getDisplayFrame(0); + EXPECT_EQ(displayFrame->getActuals().presentTime, 26); + EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::UnknownPresent); + EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::UnknownFinish); + EXPECT_EQ(displayFrame->getJankType(), JankType::Unknown); + } + { + auto displayFrame = getDisplayFrame(1); + EXPECT_EQ(displayFrame->getActuals().presentTime, 60); + EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::UnknownPresent); + EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::UnknownFinish); + EXPECT_EQ(displayFrame->getJankType(), JankType::Unknown); + } + { + auto displayFrame = getDisplayFrame(2); + EXPECT_EQ(displayFrame->getActuals().presentTime, 80); + EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent); + EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish); + EXPECT_EQ(displayFrame->getJankType(), JankType::None); + } +} + TEST_F(FrameTimelineTest, computeFps_noLayerIds_returnsZero) { EXPECT_EQ(mFrameTimeline->computeFps({}), 0.0f); } -- GitLab From 95df6a1ceb72675c6cbf03a1a4e86aeda4a4b59c Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Fri, 7 Oct 2022 18:11:07 -0400 Subject: [PATCH 0360/1310] SF: Remove DisplayModeSelectionParams Extract makeGlobalSignals instead. Inline getRankedDisplayModes. Bug: 241285191 Test: libsurfaceflinger_unittest Change-Id: Ic48ac3ad1bd9df6820c9c9e5f6384b8d15b38809 --- .../surfaceflinger/Scheduler/Scheduler.cpp | 42 +++++++------------ services/surfaceflinger/Scheduler/Scheduler.h | 15 +------ 2 files changed, 17 insertions(+), 40 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 7f04a4de85..8e710569c8 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -766,16 +766,14 @@ std::vector Scheduler::getBestDisplayModeConfigs() const { std::vector refreshRateRankingsAndSignalsPerDisplay; refreshRateRankingsAndSignalsPerDisplay.reserve(mDisplays.size()); - const auto displayModeSelectionParams = getDisplayModeSelectionParams(); + for (const auto& [id, display] : mDisplays) { + const auto [rankings, signals] = + display->holdRefreshRateConfigs() + ->getRankedRefreshRates(mPolicy.contentRequirements, makeGlobalSignals()); - std::for_each(mDisplays.begin(), mDisplays.end(), [&](const auto& display) { - const auto& [refreshRateRankings, globalSignals] = - display.second->holdRefreshRateConfigs() - ->getRankedRefreshRates(displayModeSelectionParams.layerRequirements, - displayModeSelectionParams.globalSignals); refreshRateRankingsAndSignalsPerDisplay.emplace_back( - RefreshRateRankingsAndSignals{refreshRateRankings, globalSignals}); - }); + RefreshRateRankingsAndSignals{rankings, signals}); + } // FPS and their Aggregated score. std::unordered_map aggregatedScoresPerFps = @@ -812,34 +810,26 @@ std::vector Scheduler::getDisplayModeConfigsForTheChosenFps( return displayModeConfigs; } -DisplayModeSelectionParams Scheduler::getDisplayModeSelectionParams() const { +GlobalSignals Scheduler::makeGlobalSignals() const { const bool powerOnImminent = mDisplayPowerTimer && (mPolicy.displayPowerMode != hal::PowerMode::ON || mPolicy.displayPowerTimer == TimerState::Reset); - const GlobalSignals signals{.touch = mTouchTimer && mPolicy.touch == TouchState::Active, - .idle = mPolicy.idleTimer == TimerState::Expired, - .powerOnImminent = powerOnImminent}; - - return {mPolicy.contentRequirements, signals}; -} - -auto Scheduler::getRankedDisplayModes() - -> std::pair, GlobalSignals> { - ATRACE_CALL(); - - const auto configs = holdRefreshRateConfigs(); - - const auto displayModeSelectionParams = getDisplayModeSelectionParams(); - return configs->getRankedRefreshRates(displayModeSelectionParams.layerRequirements, - displayModeSelectionParams.globalSignals); + return {.touch = mTouchTimer && mPolicy.touch == TouchState::Active, + .idle = mPolicy.idleTimer == TimerState::Expired, + .powerOnImminent = powerOnImminent}; } DisplayModePtr Scheduler::getPreferredDisplayMode() { std::lock_guard lock(mPolicyLock); // Make sure the stored mode is up to date. if (mPolicy.mode) { - mPolicy.mode = getRankedDisplayModes().first.front().displayModePtr; + const auto configs = holdRefreshRateConfigs(); + const auto rankings = + configs->getRankedRefreshRates(mPolicy.contentRequirements, makeGlobalSignals()) + .first; + + mPolicy.mode = rankings.front().displayModePtr; } return mPolicy.mode; } diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index d6f62ca898..f104e4581d 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -116,12 +116,6 @@ struct AggregatedFpsScore { size_t numDisplays; }; -// Represents LayerRequirements and GlobalSignals to be considered for the display mode selection. -struct DisplayModeSelectionParams { - std::vector layerRequirements; - GlobalSignals globalSignals; -}; - // Represents the RefreshRateRankings and GlobalSignals for the selected RefreshRateRankings. struct RefreshRateRankingsAndSignals { std::vector refreshRateRankings; @@ -303,11 +297,6 @@ private: template GlobalSignals applyPolicy(S Policy::*, T&&) EXCLUDES(mPolicyLock); - // Returns the list of display modes in descending order of their priority that fulfills the - // policy, and the signals that were considered. - std::pair, GlobalSignals> getRankedDisplayModes() - REQUIRES(mPolicyLock); - // Returns the best display mode per display. std::vector getBestDisplayModeConfigs() const REQUIRES(mPolicyLock); @@ -315,9 +304,7 @@ private: std::vector getDisplayModeConfigsForTheChosenFps( Fps chosenFps, const std::vector&) const; - // Returns the DisplayModeSelectionParams to be considered for the - // DisplayMode selection based on the current Policy and GlobalSignals. - DisplayModeSelectionParams getDisplayModeSelectionParams() const REQUIRES(mPolicyLock); + GlobalSignals makeGlobalSignals() const REQUIRES(mPolicyLock); bool updateFrameRateOverrides(GlobalSignals, Fps displayRefreshRate) REQUIRES(mPolicyLock); -- GitLab From 640b7290a5704a8d26875968c0050e8978b63638 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Tue, 11 Oct 2022 19:25:06 +0000 Subject: [PATCH 0361/1310] SF: Trace buffer cache errors Bug: 244218818 Test: presubmits Change-Id: I68e46d1952fe064f6f21958108cfc75e96976b3a --- libs/gui/SurfaceComposerClient.cpp | 12 +++++++++--- libs/gui/include/gui/TraceUtils.h | 6 ++++++ services/surfaceflinger/ClientCache.cpp | 17 +++++++++-------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 694bc5af5b..e5ec30a6ce 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -910,9 +911,14 @@ void SurfaceComposerClient::doUncacheBufferTransaction(uint64_t cacheId) { uncacheBuffer.token = BufferCache::getInstance().getToken(); uncacheBuffer.id = cacheId; - sf->setTransactionState(FrameTimelineInfo{}, {}, {}, ISurfaceComposer::eOneWay, - Transaction::getDefaultApplyToken(), {}, systemTime(), true, - uncacheBuffer, false, {}, generateId()); + status_t status = + sf->setTransactionState(FrameTimelineInfo{}, {}, {}, ISurfaceComposer::eOneWay, + Transaction::getDefaultApplyToken(), {}, systemTime(), true, + uncacheBuffer, false, {}, generateId()); + if (status != NO_ERROR) { + ALOGE_AND_TRACE("SurfaceComposerClient::doUncacheBufferTransaction - %s", + strerror(-status)); + } } void SurfaceComposerClient::Transaction::cacheBuffers() { diff --git a/libs/gui/include/gui/TraceUtils.h b/libs/gui/include/gui/TraceUtils.h index 4c01683a86..441b833b5d 100644 --- a/libs/gui/include/gui/TraceUtils.h +++ b/libs/gui/include/gui/TraceUtils.h @@ -30,6 +30,12 @@ #define ATRACE_FORMAT_INSTANT(fmt, ...) \ (CC_UNLIKELY(ATRACE_ENABLED()) && (TraceUtils::instantFormat(fmt, ##__VA_ARGS__), true)) +#define ALOGE_AND_TRACE(fmt, ...) \ + do { \ + ALOGE(fmt, ##__VA_ARGS__); \ + ATRACE_FORMAT_INSTANT(fmt, ##__VA_ARGS__); \ + } while (false) + namespace android { class TraceUtils { diff --git a/services/surfaceflinger/ClientCache.cpp b/services/surfaceflinger/ClientCache.cpp index cf932a86c2..b01932e413 100644 --- a/services/surfaceflinger/ClientCache.cpp +++ b/services/surfaceflinger/ClientCache.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include "ClientCache.h" @@ -36,12 +37,12 @@ bool ClientCache::getBuffer(const client_cache_t& cacheId, ClientCacheBuffer** outClientCacheBuffer) { auto& [processToken, id] = cacheId; if (processToken == nullptr) { - ALOGE("failed to get buffer, invalid (nullptr) process token"); + ALOGE_AND_TRACE("ClientCache::getBuffer - invalid (nullptr) process token"); return false; } auto it = mBuffers.find(processToken); if (it == mBuffers.end()) { - ALOGE("failed to get buffer, invalid process token"); + ALOGE_AND_TRACE("ClientCache::getBuffer - invalid process token"); return false; } @@ -49,7 +50,7 @@ bool ClientCache::getBuffer(const client_cache_t& cacheId, auto bufItr = processBuffers.find(id); if (bufItr == processBuffers.end()) { - ALOGV("failed to get buffer, invalid buffer id"); + ALOGE_AND_TRACE("ClientCache::getBuffer - invalid buffer id"); return false; } @@ -61,12 +62,12 @@ bool ClientCache::getBuffer(const client_cache_t& cacheId, bool ClientCache::add(const client_cache_t& cacheId, const sp& buffer) { auto& [processToken, id] = cacheId; if (processToken == nullptr) { - ALOGE("failed to cache buffer: invalid process token"); + ALOGE_AND_TRACE("ClientCache::add - invalid (nullptr) process token"); return false; } if (!buffer) { - ALOGE("failed to cache buffer: invalid buffer"); + ALOGE_AND_TRACE("ClientCache::add - invalid (nullptr) buffer"); return false; } @@ -79,7 +80,7 @@ bool ClientCache::add(const client_cache_t& cacheId, const sp& bu if (it == mBuffers.end()) { token = processToken.promote(); if (!token) { - ALOGE("failed to cache buffer: invalid token"); + ALOGE_AND_TRACE("ClientCache::add - invalid token"); return false; } @@ -87,7 +88,7 @@ bool ClientCache::add(const client_cache_t& cacheId, const sp& bu if (token->localBinder() == nullptr) { status_t err = token->linkToDeath(mDeathRecipient); if (err != NO_ERROR) { - ALOGE("failed to cache buffer: could not link to death"); + ALOGE_AND_TRACE("ClientCache::add - could not link to death"); return false; } } @@ -102,7 +103,7 @@ bool ClientCache::add(const client_cache_t& cacheId, const sp& bu auto& processBuffers = it->second.second; if (processBuffers.size() > BUFFER_CACHE_MAX_SIZE) { - ALOGE("failed to cache buffer: cache is full"); + ALOGE_AND_TRACE("ClientCache::add - cache is full"); return false; } -- GitLab From 7c487288d388141ecefd14ae2e41e0ffe8e683b5 Mon Sep 17 00:00:00 2001 From: ramindani Date: Mon, 10 Oct 2022 16:17:51 -0700 Subject: [PATCH 0362/1310] Add the filter to select the refresh rate This select the refresh rate with max score and is present on all the displays. Test: unit test BUG: 240743471 Change-Id: I66d5b7b258d418daf9734386cd42b3e91482212c --- .../surfaceflinger/Scheduler/Scheduler.cpp | 18 +++++++----- .../tests/unittests/SchedulerTest.cpp | 29 ++++++++++++++++++- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 7f04a4de85..0a26b91c00 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -781,13 +781,17 @@ std::vector Scheduler::getBestDisplayModeConfigs() const { std::unordered_map aggregatedScoresPerFps = getAggregatedScoresPerFps(refreshRateRankingsAndSignalsPerDisplay); - Fps chosenFps = std::max_element(aggregatedScoresPerFps.begin(), aggregatedScoresPerFps.end(), - [](const auto& max, const auto& current) { - return max.second.totalScore <= current.second.totalScore; - }) - ->first; - - return getDisplayModeConfigsForTheChosenFps(chosenFps, refreshRateRankingsAndSignalsPerDisplay); + auto maxScoreIt = aggregatedScoresPerFps.cbegin(); + // Selects the max Fps that is present on all the displays. + for (auto it = aggregatedScoresPerFps.cbegin(); it != aggregatedScoresPerFps.cend(); ++it) { + const auto [fps, aggregatedScore] = *it; + if (aggregatedScore.numDisplays == mDisplays.size() && + aggregatedScore.totalScore >= maxScoreIt->second.totalScore) { + maxScoreIt = it; + } + } + return getDisplayModeConfigsForTheChosenFps(maxScoreIt->first, + refreshRateRankingsAndSignalsPerDisplay); } std::vector Scheduler::getDisplayModeConfigsForTheChosenFps( diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 8d2130fa0a..406d2bc550 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -65,6 +65,7 @@ protected: static inline const DisplayModePtr kMode120_1 = createDisplayMode(DisplayModeId(1), 120_Hz); static inline const DisplayModePtr kMode60_2 = createDisplayMode(DisplayModeId(2), 60_Hz); static inline const DisplayModePtr kMode120_2 = createDisplayMode(DisplayModeId(3), 120_Hz); + static inline const DisplayModePtr kMode60_3 = createDisplayMode(DisplayModeId(4), 60_Hz); std::shared_ptr mConfigs = std::make_shared(makeModes(kMode60_1), kMode60_1->getId()); @@ -305,7 +306,7 @@ TEST_F(SchedulerTest, getBestDisplayModes_multipleDisplays) { mScheduler->registerDisplay(display1); mScheduler->registerDisplay(display2); - const std::vector>& expectedDisplays = {display1, display2}; + std::vector> expectedDisplays = {display1, display2}; std::vector layers = {{.weight = 1.f}, {.weight = 1.f}}; GlobalSignals globalSignals = {.idle = true}; std::vector expectedConfigs = {DisplayModeConfig{globalSignals, kMode60_1}, @@ -350,6 +351,32 @@ TEST_F(SchedulerTest, getBestDisplayModes_multipleDisplays) { << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue(); EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals); } + + // Filters out the 120Hz as it's not present on the display3, even with touch active + // we select 60Hz here. + auto display3 = mFakeDisplayInjector.injectDefaultInternalDisplay( + [&](FakeDisplayDeviceInjector& injector) { + injector.setDisplayModes(makeModes(kMode60_3), kMode60_3->getId()); + }, + mFlinger, /* port */ 252u); + mScheduler->registerDisplay(display3); + + expectedDisplays = {display1, display2, display3}; + globalSignals = {.touch = true}; + mScheduler->replaceTouchTimer(10); + expectedConfigs = std::vector{DisplayModeConfig{globalSignals, kMode60_1}, + DisplayModeConfig{globalSignals, kMode60_2}, + DisplayModeConfig{globalSignals, kMode60_3}}; + mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); + displayModeConfigs = mScheduler->getBestDisplayModeConfigs(); + ASSERT_EQ(expectedConfigs.size(), displayModeConfigs.size()); + for (size_t i = 0; i < expectedConfigs.size(); ++i) { + EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr) + << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue() + << " Actual fps " + << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue(); + EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals); + } } } // namespace android::scheduler -- GitLab From 85b37566ccf58d14c1ac1f5a1e635cd4f8bade2d Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Tue, 11 Oct 2022 11:08:28 -0700 Subject: [PATCH 0363/1310] Add JPEG Recovery Map Library Bug: b/252835416 Test: make Change-Id: I23198a1e12310912f756163907a1fbc2850d073d --- libs/jpegrecoverymap/Android.bp | 34 ++++++++ libs/jpegrecoverymap/OWNERS | 3 + .../include/jpegrecoverymap/recoverymap.h | 76 ++++++++++++++++++ libs/jpegrecoverymap/recoverymap.cpp | 77 +++++++++++++++++++ libs/jpegrecoverymap/tests/Android.bp | 33 ++++++++ .../tests/recoverymap_test.cpp | 22 ++++++ 6 files changed, 245 insertions(+) create mode 100644 libs/jpegrecoverymap/Android.bp create mode 100644 libs/jpegrecoverymap/OWNERS create mode 100644 libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h create mode 100644 libs/jpegrecoverymap/recoverymap.cpp create mode 100644 libs/jpegrecoverymap/tests/Android.bp create mode 100644 libs/jpegrecoverymap/tests/recoverymap_test.cpp diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp new file mode 100644 index 0000000000..285f8d56dc --- /dev/null +++ b/libs/jpegrecoverymap/Android.bp @@ -0,0 +1,34 @@ +// Copyright 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +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_library_static { + name: "libjpegrecoverymap", + vendor_available: true, + + export_include_dirs: ["include"], + local_include_dirs: ["include"], + + srcs: [ + "recoverymap.cpp", + ], +} \ No newline at end of file diff --git a/libs/jpegrecoverymap/OWNERS b/libs/jpegrecoverymap/OWNERS new file mode 100644 index 0000000000..6ace354d0b --- /dev/null +++ b/libs/jpegrecoverymap/OWNERS @@ -0,0 +1,3 @@ +arifdikici@google.com +dichenzhang@google.com +kyslov@google.com \ No newline at end of file diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h new file mode 100644 index 0000000000..c5f8e9acd3 --- /dev/null +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -0,0 +1,76 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace android::recoverymap { + +class RecoveryMap { +public: + /* + * This method is called in the decoding pipeline. It will decode the recovery map. + * + * input: compressed recovery map + * output: uncompressed recovery map + */ + void* decodeRecoveryMap(void* compressed_recovery_map); + + /* + * This method is called in the encoding pipeline. It will encode the recovery map. + * + * input: uncompressed recovery map + * output: compressed recovery map + */ + void* encodeRecoveryMap(void* uncompressed_recovery_map); + + /* + * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and + * 10-bit yuv images as input, and calculate the uncompressed recovery map. + * + * input: uncompressed yuv_420 image, uncompressed p010 image + * output: uncompressed recovery map + */ + void* generateRecoveryMap(void* uncompressed_yuv_420_image, void* uncompressed_p010_image); + + /* + * This method is called in the decoding pipeline. It will take the uncompressed (decoded) + * 8-bit yuv image and the uncompressed(decoded) recovery map as input, and calculate the + * 10-bit recovered image (in p010 color format). + * + * input: uncompressed yuv_420 image, uncompressed recovery map + * output: uncompress p010 image + */ + void* applyRecoveryMap(void* uncompressed_yuv_420_image, void* uncompressed_recovery_map); + + /* + * This method is called in the decoding pipeline. It will read XMP metadata to find the start + * position of the compressed recovery map, and will extract the compressed recovery map. + * + * input: compressed JPEG-G image (8-bit JPEG + compressed recovery map) + * output: compressed recovery map + */ + void* extractRecoveryMap(void* compressed_jpeg_g_image); + + /* + * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image + * and the compressed recovery map as input, and update the XMP metadata with the end of JPEG + * marker, and append the compressed gian map after the JPEG. + * + * input: compressed 8-bit JPEG image (standard JPEG), compressed recovery map + * output: compressed JPEG-G image (8-bit JPEG + compressed recovery map) + */ + void* appendRecoveryMap(void* compressed_jpeg_image, void* compressed_recovery_map); +}; + +} // namespace android::recoverymap \ No newline at end of file diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp new file mode 100644 index 0000000000..3e95a31871 --- /dev/null +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -0,0 +1,77 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +namespace android::recoverymap { + +void* RecoveryMap::decodeRecoveryMap(void* compressed_recovery_map) { + if (compressed_recovery_map == nullptr) { + return nullptr; + } + + // TBD + return nullptr; +} + +void* RecoveryMap::encodeRecoveryMap(void* uncompressed_recovery_map) { + if (uncompressed_recovery_map == nullptr) { + return nullptr; + } + + // TBD + return nullptr; +} + +void* RecoveryMap::generateRecoveryMap( + void* uncompressed_yuv_420_image, void* uncompressed_p010_image) { + if (uncompressed_yuv_420_image == nullptr || uncompressed_p010_image == nullptr) { + return nullptr; + } + + // TBD + return nullptr; +} + +void* RecoveryMap::applyRecoveryMap( + void* uncompressed_yuv_420_image, void* uncompressed_recovery_map) { + if (uncompressed_yuv_420_image == nullptr || uncompressed_recovery_map == nullptr) { + return nullptr; + } + + // TBD + return nullptr; +} + +void* RecoveryMap::extractRecoveryMap(void* compressed_jpeg_g_image) { + if (compressed_jpeg_g_image == nullptr) { + return nullptr; + } + + // TBD + return nullptr; +} + +void* RecoveryMap::appendRecoveryMap(void* compressed_jpeg_image, void* compressed_recovery_map) { + if (compressed_jpeg_image == nullptr || compressed_recovery_map == nullptr) { + return nullptr; + } + + // TBD + return nullptr; +} + +} // namespace android::recoverymap diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp new file mode 100644 index 0000000000..79bf723ea8 --- /dev/null +++ b/libs/jpegrecoverymap/tests/Android.bp @@ -0,0 +1,33 @@ +// Copyright 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +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_test { + name: "libjpegrecoverymap_test", + test_suites: ["device-tests"], + srcs: [ + "recoverymap_test.cpp", + ], + static_libs: [ + "libjpegrecoverymap", + ], +} \ No newline at end of file diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp new file mode 100644 index 0000000000..c436138cdc --- /dev/null +++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp @@ -0,0 +1,22 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +namespace android { + +// Add new tests here. +} // namespace android -- GitLab From 80d42dbc668d3aefb2fa3e5460983a3e4349e221 Mon Sep 17 00:00:00 2001 From: Bo Liu Date: Tue, 11 Oct 2022 20:18:59 -0400 Subject: [PATCH 0364/1310] SurfaceControl ndk-jni API review ASurfaceControl_fromSurfaceControl should acquire a reference before returning. Abort if the arguments are invalid, make the return code nonnull. Test: ASurfaceControlTest#testSurfaceControl_fromSurfaceControl and ASurfaceControlTest#testSurfaceTransaction_fromTransaction Bug: 253053203 Change-Id: Ia9b910985e34c7a97b7ff508ee63638105f17c3b --- include/android/surface_control_jni.h | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/include/android/surface_control_jni.h b/include/android/surface_control_jni.h index 67e3a9ff42..a0a1fdb372 100644 --- a/include/android/surface_control_jni.h +++ b/include/android/surface_control_jni.h @@ -36,14 +36,15 @@ __BEGIN_DECLS /** * Return the ASurfaceControl wrapped by a Java SurfaceControl object. * - * This method does not acquire any additional reference to the ASurfaceControl - * that is returned. To keep the ASurfaceControl alive after the Java - * SurfaceControl object is closed, explicitly or by the garbage collector, be - * sure to use ASurfaceControl_acquire() to acquire an additional reference. + * The caller takes ownership of the returned ASurfaceControl returned and must + * release it * using ASurfaceControl_release. + * + * surfaceControlObj must be a non-null instance of android.view.SurfaceControl + * and isValid() must be true. * * Available since API level 34. */ -ASurfaceControl* _Nullable ASurfaceControl_fromSurfaceControl(JNIEnv* _Nonnull env, +ASurfaceControl* _Nonnull ASurfaceControl_fromSurfaceControl(JNIEnv* _Nonnull env, jobject _Nonnull surfaceControlObj) __INTRODUCED_IN(__ANDROID_API_U__); /** @@ -52,11 +53,13 @@ ASurfaceControl* _Nullable ASurfaceControl_fromSurfaceControl(JNIEnv* _Nonnull e * The returned ASurfaceTransaction is still owned by the Java Transaction object is only * valid while the Java Transaction object is alive. In particular, the returned transaction * must NOT be deleted with ASurfaceTransaction_delete. - * May return nullptr on error. + * + * transactionObj must be a non-null instance of + * android.view.SurfaceControl.Transaction and close() must not already be called. * * Available since API level 34. */ -ASurfaceTransaction* _Nullable ASurfaceTransaction_fromTransaction(JNIEnv* _Nonnull env, +ASurfaceTransaction* _Nonnull ASurfaceTransaction_fromTransaction(JNIEnv* _Nonnull env, jobject _Nonnull transactionObj) __INTRODUCED_IN(__ANDROID_API_U__); __END_DECLS -- GitLab From 327d609d643eed24c8d0c527f121099ed44b8b1f Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Tue, 11 Oct 2022 18:05:08 -0400 Subject: [PATCH 0365/1310] SF: Deduplicate FakeDisplayInjector Also, parametrize to inject secondary internal display. Bug: 241285876 Test: libsurfaceflinger_unittest Change-Id: I63d21c2216a3ab864b040bdb2c9e8fba0768b66a --- .../unittests/DisplayTransactionTest.cpp | 45 --------------- .../unittests/DisplayTransactionTestHelpers.h | 8 ++- .../tests/unittests/FakeDisplayInjector.h | 56 ++++++++++--------- .../tests/unittests/SchedulerTest.cpp | 37 ++++++------ 4 files changed, 58 insertions(+), 88 deletions(-) diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp index 8b91c67982..e0b508aa73 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp @@ -120,51 +120,6 @@ void DisplayTransactionTest::injectFakeNativeWindowSurfaceFactory() { }); } -sp DisplayTransactionTest::injectDefaultInternalDisplay( - std::function injectExtra) { - constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(255u); - constexpr int DEFAULT_DISPLAY_WIDTH = 1080; - constexpr int DEFAULT_DISPLAY_HEIGHT = 1920; - constexpr HWDisplayId DEFAULT_DISPLAY_HWC_DISPLAY_ID = 0; - - // The DisplayDevice is required to have a framebuffer (behind the - // ANativeWindow interface) which uses the actual hardware display - // size. - EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_WIDTH, _)) - .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_WIDTH), Return(0))); - EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_HEIGHT, _)) - .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_HEIGHT), Return(0))); - EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_BUFFERS_FORMAT)); - EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_CONNECT)); - EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_USAGE64)); - EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_DISCONNECT)).Times(AnyNumber()); - - auto compositionDisplay = - compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(), - compositionengine::DisplayCreationArgsBuilder() - .setId(DEFAULT_DISPLAY_ID) - .setPixels({DEFAULT_DISPLAY_WIDTH, - DEFAULT_DISPLAY_HEIGHT}) - .setPowerAdvisor(&mPowerAdvisor) - .build()); - - constexpr bool kIsPrimary = true; - auto injector = FakeDisplayDeviceInjector(mFlinger, compositionDisplay, - ui::DisplayConnectionType::Internal, - DEFAULT_DISPLAY_HWC_DISPLAY_ID, kIsPrimary); - - injector.setNativeWindow(mNativeWindow); - if (injectExtra) { - injectExtra(injector); - } - - auto displayDevice = injector.inject(); - - Mock::VerifyAndClear(mNativeWindow.get()); - - return displayDevice; -} - bool DisplayTransactionTest::hasPhysicalHwcDisplay(HWDisplayId hwcDisplayId) const { const auto& map = mFlinger.hwcPhysicalDisplayIdMap(); diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h index 9cceb5e4df..19c7d5cd11 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h @@ -42,6 +42,7 @@ #include #include +#include "FakeDisplayInjector.h" #include "TestableScheduler.h" #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockComposer.h" @@ -88,8 +89,11 @@ public: void injectMockComposer(int virtualDisplayCount); void injectFakeBufferQueueFactory(); void injectFakeNativeWindowSurfaceFactory(); + sp injectDefaultInternalDisplay( - std::function); + std::function injectExtra) { + return mFakeDisplayInjector.injectInternalDisplay(injectExtra); + } // -------------------------------------------------------------------- // Postcondition helpers @@ -114,6 +118,8 @@ public: sp mBuffer = sp::make(); Hwc2::mock::PowerAdvisor mPowerAdvisor; + FakeDisplayInjector mFakeDisplayInjector{mFlinger, mPowerAdvisor, mNativeWindow}; + // These mocks are created by the test, but are destroyed by SurfaceFlinger // by virtue of being stored into a std::unique_ptr. However we still need // to keep a reference to them for use in setting up call expectations. diff --git a/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h index 81b420cbc4..6ee4b9bf66 100644 --- a/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h +++ b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h @@ -27,49 +27,54 @@ namespace android { using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector; using android::hardware::graphics::composer::hal::HWDisplayId; using android::Hwc2::mock::PowerAdvisor; -using testing::_; -using testing::AnyNumber; -using testing::DoAll; -using testing::Mock; -using testing::ResultOf; -using testing::Return; -using testing::SetArgPointee; + +struct FakeDisplayInjectorArgs { + uint8_t port = 255u; + HWDisplayId hwcDisplayId = 0; + bool isPrimary = true; +}; class FakeDisplayInjector { public: - sp injectDefaultInternalDisplay( + FakeDisplayInjector(TestableSurfaceFlinger& flinger, Hwc2::mock::PowerAdvisor& powerAdvisor, + sp nativeWindow) + : mFlinger(flinger), mPowerAdvisor(powerAdvisor), mNativeWindow(nativeWindow) {} + + sp injectInternalDisplay( const std::function& injectExtra, - TestableSurfaceFlinger& flinger, uint8_t port = 255u) const { - constexpr int DEFAULT_DISPLAY_WIDTH = 1080; - constexpr int DEFAULT_DISPLAY_HEIGHT = 1920; - constexpr HWDisplayId DEFAULT_DISPLAY_HWC_DISPLAY_ID = 0; + FakeDisplayInjectorArgs args = {}) { + using testing::_; + using testing::AnyNumber; + using testing::DoAll; + using testing::Mock; + using testing::Return; + using testing::SetArgPointee; - const PhysicalDisplayId physicalDisplayId = PhysicalDisplayId::fromPort(port); + constexpr ui::Size kResolution = {1080, 1920}; // The DisplayDevice is required to have a framebuffer (behind the // ANativeWindow interface) which uses the actual hardware display // size. EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_WIDTH, _)) - .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_WIDTH), Return(0))); + .WillRepeatedly(DoAll(SetArgPointee<1>(kResolution.getWidth()), Return(0))); EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_HEIGHT, _)) - .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_HEIGHT), Return(0))); + .WillRepeatedly(DoAll(SetArgPointee<1>(kResolution.getHeight()), Return(0))); EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_BUFFERS_FORMAT)); EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_CONNECT)); EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_USAGE64)); EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_DISCONNECT)).Times(AnyNumber()); auto compositionDisplay = compositionengine::impl:: - createDisplay(flinger.getCompositionEngine(), + createDisplay(mFlinger.getCompositionEngine(), compositionengine::DisplayCreationArgsBuilder() - .setId(physicalDisplayId) - .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT}) - .setPowerAdvisor(mPowerAdvisor) + .setId(PhysicalDisplayId::fromPort(args.port)) + .setPixels(kResolution) + .setPowerAdvisor(&mPowerAdvisor) .build()); - constexpr bool kIsPrimary = true; - auto injector = FakeDisplayDeviceInjector(flinger, compositionDisplay, + auto injector = FakeDisplayDeviceInjector(mFlinger, compositionDisplay, ui::DisplayConnectionType::Internal, - DEFAULT_DISPLAY_HWC_DISPLAY_ID, kIsPrimary); + args.hwcDisplayId, args.isPrimary); injector.setNativeWindow(mNativeWindow); if (injectExtra) { @@ -83,8 +88,9 @@ public: return displayDevice; } - sp mNativeWindow = sp::make(); - PowerAdvisor* mPowerAdvisor = new PowerAdvisor(); + TestableSurfaceFlinger& mFlinger; + Hwc2::mock::PowerAdvisor& mPowerAdvisor; + sp mNativeWindow; }; -} // namespace android \ No newline at end of file +} // namespace android diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 406d2bc550..392398dbe3 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -76,9 +76,12 @@ protected: ConnectionHandle mConnectionHandle; MockEventThread* mEventThread; sp mEventThreadConnection; - FakeDisplayInjector mFakeDisplayInjector; TestableSurfaceFlinger mFlinger; + Hwc2::mock::PowerAdvisor mPowerAdvisor; + sp mNativeWindow = sp::make(); + + FakeDisplayInjector mFakeDisplayInjector{mFlinger, mPowerAdvisor, mNativeWindow}; }; SchedulerTest::SchedulerTest() { @@ -226,11 +229,10 @@ MATCHER(Is120Hz, "") { } TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) { - auto display = mFakeDisplayInjector.injectDefaultInternalDisplay( - [&](FakeDisplayDeviceInjector& injector) { + auto display = + mFakeDisplayInjector.injectInternalDisplay([&](FakeDisplayDeviceInjector& injector) { injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId()); - }, - mFlinger); + }); mScheduler->registerDisplay(display); mScheduler->setRefreshRateConfigs(display->holdRefreshRateConfigs()); @@ -255,11 +257,11 @@ TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) { } TEST_F(SchedulerTest, getBestDisplayMode_singleDisplay) { - auto display = mFakeDisplayInjector.injectDefaultInternalDisplay( - [&](FakeDisplayDeviceInjector& injector) { + auto display = + mFakeDisplayInjector.injectInternalDisplay([&](FakeDisplayDeviceInjector& injector) { injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId()); - }, - mFlinger); + }); + mScheduler->registerDisplay(display); std::vector layers = @@ -293,16 +295,16 @@ TEST_F(SchedulerTest, getBestDisplayMode_singleDisplay) { } TEST_F(SchedulerTest, getBestDisplayModes_multipleDisplays) { - auto display1 = mFakeDisplayInjector.injectDefaultInternalDisplay( - [&](FakeDisplayDeviceInjector& injector) { + auto display1 = + mFakeDisplayInjector.injectInternalDisplay([&](FakeDisplayDeviceInjector& injector) { injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId()); - }, - mFlinger); - auto display2 = mFakeDisplayInjector.injectDefaultInternalDisplay( + }); + auto display2 = mFakeDisplayInjector.injectInternalDisplay( [&](FakeDisplayDeviceInjector& injector) { injector.setDisplayModes(makeModes(kMode60_2, kMode120_2), kMode60_2->getId()); }, - mFlinger, /* port */ 253u); + {.port = 253u, .hwcDisplayId = 42, .isPrimary = false}); + mScheduler->registerDisplay(display1); mScheduler->registerDisplay(display2); @@ -354,11 +356,12 @@ TEST_F(SchedulerTest, getBestDisplayModes_multipleDisplays) { // Filters out the 120Hz as it's not present on the display3, even with touch active // we select 60Hz here. - auto display3 = mFakeDisplayInjector.injectDefaultInternalDisplay( + auto display3 = mFakeDisplayInjector.injectInternalDisplay( [&](FakeDisplayDeviceInjector& injector) { injector.setDisplayModes(makeModes(kMode60_3), kMode60_3->getId()); }, - mFlinger, /* port */ 252u); + {.port = 252u, .hwcDisplayId = 41, .isPrimary = false}); + mScheduler->registerDisplay(display3); expectedDisplays = {display1, display2, display3}; -- GitLab From 5a65d6eddbd0d834dcefa86e290a596e0d4984fb Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Wed, 12 Oct 2022 20:59:11 +0000 Subject: [PATCH 0366/1310] Add Nick Deakin (deakin@google.com) as owner. Change-Id: I832621f56a0ffea9c7f12cd3bb6e61220b303aac --- libs/jpegrecoverymap/OWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/jpegrecoverymap/OWNERS b/libs/jpegrecoverymap/OWNERS index 6ace354d0b..133af5bcd4 100644 --- a/libs/jpegrecoverymap/OWNERS +++ b/libs/jpegrecoverymap/OWNERS @@ -1,3 +1,4 @@ arifdikici@google.com +deakin@google.com dichenzhang@google.com kyslov@google.com \ No newline at end of file -- GitLab From 5e83dfec4aad9a3ce47eb4820f1ebde50ddcfd3c Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 28 Sep 2022 17:04:42 -0700 Subject: [PATCH 0367/1310] Run libinput_tests on host These tests are testing the libinput library, so they should run OK on host. Compile and run these on host in this CL. Some tests have been skipped because the corresponding code hardcoded Android-specific paths. Those can be improved in the future. Due to the complexity of getting Android device's kernel configs on host, those tests have been skipped, as well. Bug: 249591924 Bug: 237835584 Test: atest --no-bazel-mode --host libinput_tests Change-Id: I110f3ad80823b40b0ec7980c3f1d59e653b4fd8b --- libs/input/Android.bp | 1 - libs/input/tests/Android.bp | 7 +++++-- libs/input/tests/InputDevice_test.cpp | 9 +++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 29e02cf73a..34ef7b4946 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -89,7 +89,6 @@ cc_library { shared_libs: [ "libutils", "libbinder", - "libui", ], static_libs: [ diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index c53811a92d..5aae37dae8 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -10,6 +10,7 @@ package { cc_test { name: "libinput_tests", + host_supported: true, srcs: [ "IdGenerator_test.cpp", "InputChannel_test.cpp", @@ -24,6 +25,7 @@ cc_test { static_libs: [ "libgui_window_info_static", "libinput", + "libui-types", ], cflags: [ "-Wall", @@ -35,11 +37,13 @@ cc_test { "libbinder", "libcutils", "liblog", - "libui", "libutils", "libvintf", ], data: ["data/*"], + test_options: { + unit_test: true, + }, test_suites: ["device-tests"], } @@ -60,7 +64,6 @@ cc_library_static { "libcutils", "libutils", "libbinder", - "libui", "libbase", ], } diff --git a/libs/input/tests/InputDevice_test.cpp b/libs/input/tests/InputDevice_test.cpp index e872fa442b..2344463241 100644 --- a/libs/input/tests/InputDevice_test.cpp +++ b/libs/input/tests/InputDevice_test.cpp @@ -65,6 +65,9 @@ protected: } void SetUp() override { +#if !defined(__ANDROID__) + GTEST_SKIP() << "b/253299089 Generic files are currently read directly from device."; +#endif loadKeyLayout("Generic"); loadKeyCharacterMap("Generic"); } @@ -131,6 +134,9 @@ TEST_F(InputDeviceKeyMapTest, keyCharacteMapApplyMultipleOverlaysTest) { } TEST(InputDeviceKeyLayoutTest, DoesNotLoadWhenRequiredKernelConfigIsMissing) { +#if !defined(__ANDROID__) + GTEST_SKIP() << "Can't check kernel configs on host"; +#endif std::string klPath = base::GetExecutableDirectory() + "/data/kl_with_required_fake_config.kl"; base::Result> ret = KeyLayoutMap::load(klPath); ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath; @@ -139,6 +145,9 @@ TEST(InputDeviceKeyLayoutTest, DoesNotLoadWhenRequiredKernelConfigIsMissing) { } TEST(InputDeviceKeyLayoutTest, LoadsWhenRequiredKernelConfigIsPresent) { +#if !defined(__ANDROID__) + GTEST_SKIP() << "Can't check kernel configs on host"; +#endif std::string klPath = base::GetExecutableDirectory() + "/data/kl_with_required_real_config.kl"; base::Result> ret = KeyLayoutMap::load(klPath); ASSERT_TRUE(ret.ok()) << "Cannot load KeyLayout at " << klPath; -- GitLab From c3894a4df8d8362fe5b1ccb9ee0ddb6ead046003 Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Thu, 1 Sep 2022 21:17:15 +0000 Subject: [PATCH 0368/1310] Extend PowerHalWrapper to support HIDL 1.2 and 1.3 * Added support for 1.2 and 1.3 HIDL calls to the PowerHalWrapper * Expanded the wrapper and loader tests to cover these new versions * Lightly tweaked the existing tests to make them more comprehensive Bug: b/244631171 Test: atest libpowermanager_test Change-Id: I5890106817b7cf243cdd21b3acf22ff7fcd8174e --- include/powermanager/PowerHalLoader.h | 6 + include/powermanager/PowerHalWrapper.h | 45 +++- services/powermanager/Android.bp | 2 + services/powermanager/PowerHalController.cpp | 20 +- services/powermanager/PowerHalLoader.cpp | 20 ++ services/powermanager/PowerHalWrapper.cpp | 73 +++++- services/powermanager/benchmarks/Android.bp | 2 + services/powermanager/tests/Android.bp | 4 + .../powermanager/tests/PowerHalLoaderTest.cpp | 14 +- .../tests/PowerHalWrapperHidlV1_0Test.cpp | 28 ++- .../tests/PowerHalWrapperHidlV1_1Test.cpp | 56 ++--- .../tests/PowerHalWrapperHidlV1_2Test.cpp | 200 +++++++++++++++++ .../tests/PowerHalWrapperHidlV1_3Test.cpp | 210 ++++++++++++++++++ 13 files changed, 620 insertions(+), 60 deletions(-) create mode 100644 services/powermanager/tests/PowerHalWrapperHidlV1_2Test.cpp create mode 100644 services/powermanager/tests/PowerHalWrapperHidlV1_3Test.cpp diff --git a/include/powermanager/PowerHalLoader.h b/include/powermanager/PowerHalLoader.h index ed6f6f35f5..e0384f31db 100644 --- a/include/powermanager/PowerHalLoader.h +++ b/include/powermanager/PowerHalLoader.h @@ -19,6 +19,8 @@ #include #include +#include +#include #include namespace android { @@ -32,12 +34,16 @@ public: static sp loadAidl(); static sp loadHidlV1_0(); static sp loadHidlV1_1(); + static sp loadHidlV1_2(); + static sp loadHidlV1_3(); private: static std::mutex gHalMutex; static sp gHalAidl GUARDED_BY(gHalMutex); static sp gHalHidlV1_0 GUARDED_BY(gHalMutex); static sp gHalHidlV1_1 GUARDED_BY(gHalMutex); + static sp gHalHidlV1_2 GUARDED_BY(gHalMutex); + static sp gHalHidlV1_3 GUARDED_BY(gHalMutex); static sp loadHidlV1_0Locked() EXCLUSIVE_LOCKS_REQUIRED(gHalMutex); diff --git a/include/powermanager/PowerHalWrapper.h b/include/powermanager/PowerHalWrapper.h index dfb0ff59a0..8028aa86e1 100644 --- a/include/powermanager/PowerHalWrapper.h +++ b/include/powermanager/PowerHalWrapper.h @@ -19,6 +19,8 @@ #include #include +#include +#include #include #include #include @@ -142,8 +144,8 @@ public: // Wrapper for the HIDL Power HAL v1.0. class HidlHalWrapperV1_0 : public HalWrapper { public: - explicit HidlHalWrapperV1_0(sp Hal) - : mHandleV1_0(std::move(Hal)) {} + explicit HidlHalWrapperV1_0(sp handleV1_0) + : mHandleV1_0(std::move(handleV1_0)) {} virtual ~HidlHalWrapperV1_0() = default; virtual HalResult setBoost(hardware::power::Boost boost, int32_t durationMs) override; @@ -154,10 +156,10 @@ public: virtual HalResult getHintSessionPreferredRate() override; protected: - virtual HalResult sendPowerHint(hardware::power::V1_0::PowerHint hintId, uint32_t data); + const sp mHandleV1_0; + virtual HalResult sendPowerHint(hardware::power::V1_3::PowerHint hintId, uint32_t data); private: - sp mHandleV1_0; HalResult setInteractive(bool enabled); HalResult setFeature(hardware::power::V1_0::Feature feature, bool enabled); }; @@ -165,17 +167,40 @@ private: // Wrapper for the HIDL Power HAL v1.1. class HidlHalWrapperV1_1 : public HidlHalWrapperV1_0 { public: - HidlHalWrapperV1_1(sp handleV1_0, - sp handleV1_1) - : HidlHalWrapperV1_0(std::move(handleV1_0)), mHandleV1_1(std::move(handleV1_1)) {} + HidlHalWrapperV1_1(sp handleV1_1) + : HidlHalWrapperV1_0(std::move(handleV1_1)) {} virtual ~HidlHalWrapperV1_1() = default; protected: - virtual HalResult sendPowerHint(hardware::power::V1_0::PowerHint hintId, + virtual HalResult sendPowerHint(hardware::power::V1_3::PowerHint hintId, uint32_t data) override; +}; -private: - sp mHandleV1_1; +// Wrapper for the HIDL Power HAL v1.2. +class HidlHalWrapperV1_2 : public HidlHalWrapperV1_1 { +public: + virtual HalResult setBoost(hardware::power::Boost boost, int32_t durationMs) override; + virtual HalResult setMode(hardware::power::Mode mode, bool enabled) override; + HidlHalWrapperV1_2(sp handleV1_2) + : HidlHalWrapperV1_1(std::move(handleV1_2)) {} + virtual ~HidlHalWrapperV1_2() = default; + +protected: + virtual HalResult sendPowerHint(hardware::power::V1_3::PowerHint hintId, + uint32_t data) override; +}; + +// Wrapper for the HIDL Power HAL v1.3. +class HidlHalWrapperV1_3 : public HidlHalWrapperV1_2 { +public: + virtual HalResult setMode(hardware::power::Mode mode, bool enabled) override; + HidlHalWrapperV1_3(sp handleV1_3) + : HidlHalWrapperV1_2(std::move(handleV1_3)) {} + virtual ~HidlHalWrapperV1_3() = default; + +protected: + virtual HalResult sendPowerHint(hardware::power::V1_3::PowerHint hintId, + uint32_t data) override; }; // Wrapper for the AIDL Power HAL. diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp index 6fbba3f568..b7de61982e 100644 --- a/services/powermanager/Android.bp +++ b/services/powermanager/Android.bp @@ -38,6 +38,8 @@ cc_library_shared { "libutils", "android.hardware.power@1.0", "android.hardware.power@1.1", + "android.hardware.power@1.2", + "android.hardware.power@1.3", "android.hardware.power-V3-cpp", ], diff --git a/services/powermanager/PowerHalController.cpp b/services/powermanager/PowerHalController.cpp index 8c225d5d02..f89035fd1c 100644 --- a/services/powermanager/PowerHalController.cpp +++ b/services/powermanager/PowerHalController.cpp @@ -33,16 +33,20 @@ namespace power { // ------------------------------------------------------------------------------------------------- std::unique_ptr HalConnector::connect() { - sp halAidl = PowerHalLoader::loadAidl(); - if (halAidl) { + if (sp halAidl = PowerHalLoader::loadAidl()) { return std::make_unique(halAidl); } - sp halHidlV1_0 = PowerHalLoader::loadHidlV1_0(); - sp halHidlV1_1 = PowerHalLoader::loadHidlV1_1(); - if (halHidlV1_1) { - return std::make_unique(halHidlV1_0, halHidlV1_1); - } - if (halHidlV1_0) { + // If V1_0 isn't defined, none of them are + if (sp halHidlV1_0 = PowerHalLoader::loadHidlV1_0()) { + if (sp halHidlV1_3 = PowerHalLoader::loadHidlV1_3()) { + return std::make_unique(halHidlV1_3); + } + if (sp halHidlV1_2 = PowerHalLoader::loadHidlV1_2()) { + return std::make_unique(halHidlV1_2); + } + if (sp halHidlV1_1 = PowerHalLoader::loadHidlV1_1()) { + return std::make_unique(halHidlV1_1); + } return std::make_unique(halHidlV1_0); } return nullptr; diff --git a/services/powermanager/PowerHalLoader.cpp b/services/powermanager/PowerHalLoader.cpp index 1f1b43a607..6bd40f8ff2 100644 --- a/services/powermanager/PowerHalLoader.cpp +++ b/services/powermanager/PowerHalLoader.cpp @@ -17,6 +17,8 @@ #define LOG_TAG "PowerHalLoader" #include +#include +#include #include #include #include @@ -55,12 +57,16 @@ std::mutex PowerHalLoader::gHalMutex; sp PowerHalLoader::gHalAidl = nullptr; sp PowerHalLoader::gHalHidlV1_0 = nullptr; sp PowerHalLoader::gHalHidlV1_1 = nullptr; +sp PowerHalLoader::gHalHidlV1_2 = nullptr; +sp PowerHalLoader::gHalHidlV1_3 = nullptr; void PowerHalLoader::unloadAll() { std::lock_guard lock(gHalMutex); gHalAidl = nullptr; gHalHidlV1_0 = nullptr; gHalHidlV1_1 = nullptr; + gHalHidlV1_2 = nullptr; + gHalHidlV1_3 = nullptr; } sp PowerHalLoader::loadAidl() { @@ -82,6 +88,20 @@ sp PowerHalLoader::loadHidlV1_1() { return loadHal(gHalExists, gHalHidlV1_1, loadFn, "HIDL v1.1"); } +sp PowerHalLoader::loadHidlV1_2() { + std::lock_guard lock(gHalMutex); + static bool gHalExists = true; + static auto loadFn = []() { return V1_2::IPower::castFrom(loadHidlV1_0Locked()); }; + return loadHal(gHalExists, gHalHidlV1_2, loadFn, "HIDL v1.2"); +} + +sp PowerHalLoader::loadHidlV1_3() { + std::lock_guard lock(gHalMutex); + static bool gHalExists = true; + static auto loadFn = []() { return V1_3::IPower::castFrom(loadHidlV1_0Locked()); }; + return loadHal(gHalExists, gHalHidlV1_3, loadFn, "HIDL v1.3"); +} + sp PowerHalLoader::loadHidlV1_0Locked() { static bool gHalExists = true; static auto loadFn = []() { return V1_0::IPower::getService(); }; diff --git a/services/powermanager/PowerHalWrapper.cpp b/services/powermanager/PowerHalWrapper.cpp index d74bd23a8d..9e7adf8e5c 100644 --- a/services/powermanager/PowerHalWrapper.cpp +++ b/services/powermanager/PowerHalWrapper.cpp @@ -24,8 +24,6 @@ #include using namespace android::hardware::power; -namespace V1_0 = android::hardware::power::V1_0; -namespace V1_1 = android::hardware::power::V1_1; namespace Aidl = android::hardware::power; namespace android { @@ -108,7 +106,7 @@ HalResult EmptyHalWrapper::getHintSessionPreferredRate() { HalResult HidlHalWrapperV1_0::setBoost(Boost boost, int32_t durationMs) { if (boost == Boost::INTERACTION) { - return sendPowerHint(V1_0::PowerHint::INTERACTION, durationMs); + return sendPowerHint(V1_3::PowerHint::INTERACTION, durationMs); } else { ALOGV("Skipped setBoost %s because Power HAL AIDL not available", toString(boost).c_str()); return HalResult::unsupported(); @@ -119,13 +117,13 @@ HalResult HidlHalWrapperV1_0::setMode(Mode mode, bool enabled) { uint32_t data = enabled ? 1 : 0; switch (mode) { case Mode::LAUNCH: - return sendPowerHint(V1_0::PowerHint::LAUNCH, data); + return sendPowerHint(V1_3::PowerHint::LAUNCH, data); case Mode::LOW_POWER: - return sendPowerHint(V1_0::PowerHint::LOW_POWER, data); + return sendPowerHint(V1_3::PowerHint::LOW_POWER, data); case Mode::SUSTAINED_PERFORMANCE: - return sendPowerHint(V1_0::PowerHint::SUSTAINED_PERFORMANCE, data); + return sendPowerHint(V1_3::PowerHint::SUSTAINED_PERFORMANCE, data); case Mode::VR: - return sendPowerHint(V1_0::PowerHint::VR_MODE, data); + return sendPowerHint(V1_3::PowerHint::VR_MODE, data); case Mode::INTERACTIVE: return setInteractive(enabled); case Mode::DOUBLE_TAP_TO_WAKE: @@ -137,8 +135,8 @@ HalResult HidlHalWrapperV1_0::setMode(Mode mode, bool enabled) { } } -HalResult HidlHalWrapperV1_0::sendPowerHint(V1_0::PowerHint hintId, uint32_t data) { - auto ret = mHandleV1_0->powerHint(hintId, data); +HalResult HidlHalWrapperV1_0::sendPowerHint(V1_3::PowerHint hintId, uint32_t data) { + auto ret = mHandleV1_0->powerHint(static_cast(hintId), data); return HalResult::fromReturn(ret); } @@ -152,7 +150,7 @@ HalResult HidlHalWrapperV1_0::setFeature(V1_0::Feature feature, bool enabl return HalResult::fromReturn(ret); } -HalResult> HidlHalWrapperV1_0::createHintSession( +HalResult> HidlHalWrapperV1_0::createHintSession( int32_t, int32_t, const std::vector& threadIds, int64_t) { ALOGV("Skipped createHintSession(task num=%zu) because Power HAL not available", threadIds.size()); @@ -166,8 +164,59 @@ HalResult HidlHalWrapperV1_0::getHintSessionPreferredRate() { // ------------------------------------------------------------------------------------------------- -HalResult HidlHalWrapperV1_1::sendPowerHint(V1_0::PowerHint hintId, uint32_t data) { - auto ret = mHandleV1_1->powerHintAsync(hintId, data); +HalResult HidlHalWrapperV1_1::sendPowerHint(V1_3::PowerHint hintId, uint32_t data) { + auto handle = static_cast(mHandleV1_0.get()); + auto ret = handle->powerHintAsync(static_cast(hintId), data); + return HalResult::fromReturn(ret); +} + +// ------------------------------------------------------------------------------------------------- + +HalResult HidlHalWrapperV1_2::sendPowerHint(V1_3::PowerHint hintId, uint32_t data) { + auto handle = static_cast(mHandleV1_0.get()); + auto ret = handle->powerHintAsync_1_2(static_cast(hintId), data); + return HalResult::fromReturn(ret); +} + +HalResult HidlHalWrapperV1_2::setBoost(Boost boost, int32_t durationMs) { + switch (boost) { + case Boost::CAMERA_SHOT: + return sendPowerHint(V1_3::PowerHint::CAMERA_SHOT, durationMs); + case Boost::CAMERA_LAUNCH: + return sendPowerHint(V1_3::PowerHint::CAMERA_LAUNCH, durationMs); + default: + return HidlHalWrapperV1_1::setBoost(boost, durationMs); + } +} + +HalResult HidlHalWrapperV1_2::setMode(Mode mode, bool enabled) { + uint32_t data = enabled ? 1 : 0; + switch (mode) { + case Mode::CAMERA_STREAMING_SECURE: + case Mode::CAMERA_STREAMING_LOW: + case Mode::CAMERA_STREAMING_MID: + case Mode::CAMERA_STREAMING_HIGH: + return sendPowerHint(V1_3::PowerHint::CAMERA_STREAMING, data); + case Mode::AUDIO_STREAMING_LOW_LATENCY: + return sendPowerHint(V1_3::PowerHint::AUDIO_LOW_LATENCY, data); + default: + return HidlHalWrapperV1_1::setMode(mode, enabled); + } +} + +// ------------------------------------------------------------------------------------------------- + +HalResult HidlHalWrapperV1_3::setMode(Mode mode, bool enabled) { + uint32_t data = enabled ? 1 : 0; + if (mode == Mode::EXPENSIVE_RENDERING) { + return sendPowerHint(V1_3::PowerHint::EXPENSIVE_RENDERING, data); + } + return HidlHalWrapperV1_2::setMode(mode, enabled); +} + +HalResult HidlHalWrapperV1_3::sendPowerHint(V1_3::PowerHint hintId, uint32_t data) { + auto handle = static_cast(mHandleV1_0.get()); + auto ret = handle->powerHintAsync_1_3(hintId, data); return HalResult::fromReturn(ret); } diff --git a/services/powermanager/benchmarks/Android.bp b/services/powermanager/benchmarks/Android.bp index fcb012fc75..0286a8160c 100644 --- a/services/powermanager/benchmarks/Android.bp +++ b/services/powermanager/benchmarks/Android.bp @@ -38,6 +38,8 @@ cc_benchmark { "libutils", "android.hardware.power@1.0", "android.hardware.power@1.1", + "android.hardware.power@1.2", + "android.hardware.power@1.3", "android.hardware.power-V3-cpp", ], static_libs: [ diff --git a/services/powermanager/tests/Android.bp b/services/powermanager/tests/Android.bp index 962784cbae..eec6801b1f 100644 --- a/services/powermanager/tests/Android.bp +++ b/services/powermanager/tests/Android.bp @@ -31,6 +31,8 @@ cc_test { "PowerHalWrapperAidlTest.cpp", "PowerHalWrapperHidlV1_0Test.cpp", "PowerHalWrapperHidlV1_1Test.cpp", + "PowerHalWrapperHidlV1_2Test.cpp", + "PowerHalWrapperHidlV1_3Test.cpp", "WorkSourceTest.cpp", ], cflags: [ @@ -47,6 +49,8 @@ cc_test { "libutils", "android.hardware.power@1.0", "android.hardware.power@1.1", + "android.hardware.power@1.2", + "android.hardware.power@1.3", "android.hardware.power-V3-cpp", ], static_libs: [ diff --git a/services/powermanager/tests/PowerHalLoaderTest.cpp b/services/powermanager/tests/PowerHalLoaderTest.cpp index 058e1b5ca8..e36deed042 100644 --- a/services/powermanager/tests/PowerHalLoaderTest.cpp +++ b/services/powermanager/tests/PowerHalLoaderTest.cpp @@ -26,6 +26,8 @@ using IPowerV1_0 = android::hardware::power::V1_0::IPower; using IPowerV1_1 = android::hardware::power::V1_1::IPower; +using IPowerV1_2 = android::hardware::power::V1_2::IPower; +using IPowerV1_3 = android::hardware::power::V1_3::IPower; using IPowerAidl = android::hardware::power::IPower; using namespace android; @@ -52,6 +54,16 @@ sp loadHal() { return PowerHalLoader::loadHidlV1_1(); } +template <> +sp loadHal() { + return PowerHalLoader::loadHidlV1_2(); +} + +template <> +sp loadHal() { + return PowerHalLoader::loadHidlV1_3(); +} + // ------------------------------------------------------------------------------------------------- template @@ -63,7 +75,7 @@ public: // ------------------------------------------------------------------------------------------------- -typedef ::testing::Types PowerHalTypes; +typedef ::testing::Types PowerHalTypes; TYPED_TEST_SUITE(PowerHalLoaderTest, PowerHalTypes); TYPED_TEST(PowerHalLoaderTest, TestLoadsOnlyOnce) { diff --git a/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp b/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp index b54762cf6d..0cd2e22e7e 100644 --- a/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp +++ b/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp @@ -87,18 +87,28 @@ TEST_F(PowerHalWrapperHidlV1_0Test, TestSetBoostFailed) { } TEST_F(PowerHalWrapperHidlV1_0Test, TestSetBoostUnsupported) { + EXPECT_CALL(*mMockHal.get(), powerHint(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0); + EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0); + auto result = mWrapper->setBoost(Boost::CAMERA_LAUNCH, 10); ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setBoost(Boost::ML_ACC, 10); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 10); + ASSERT_TRUE(result.isUnsupported()); } TEST_F(PowerHalWrapperHidlV1_0Test, TestSetModeSuccessful) { { InSequence seq; - EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::LAUNCH), Eq(1))).Times(Exactly(1)); - EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::LOW_POWER), Eq(0))).Times(Exactly(1)); - EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::SUSTAINED_PERFORMANCE), Eq(1))) + EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::LAUNCH), Eq(true))).Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::LOW_POWER), Eq(false))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::SUSTAINED_PERFORMANCE), Eq(true))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::VR_MODE), Eq(false))) .Times(Exactly(1)); - EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::VR_MODE), Eq(0))).Times(Exactly(1)); EXPECT_CALL(*mMockHal.get(), setInteractive(Eq(true))).Times(Exactly(1)); EXPECT_CALL(*mMockHal.get(), setFeature(Eq(Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE), Eq(false))) @@ -131,6 +141,16 @@ TEST_F(PowerHalWrapperHidlV1_0Test, TestSetModeFailed) { } TEST_F(PowerHalWrapperHidlV1_0Test, TestSetModeIgnored) { + EXPECT_CALL(*mMockHal.get(), powerHint(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0); + EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0); + auto result = mWrapper->setMode(Mode::CAMERA_STREAMING_HIGH, true); ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::EXPENSIVE_RENDERING, false); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::FIXED_PERFORMANCE, true); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::GAME_LOADING, false); + ASSERT_TRUE(result.isUnsupported()); } diff --git a/services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp b/services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp index d30e8d2c49..32f84e20b6 100644 --- a/services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp +++ b/services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp @@ -31,7 +31,6 @@ using android::hardware::power::Mode; using android::hardware::power::V1_0::Feature; using android::hardware::power::V1_0::PowerHint; using IPowerV1_1 = android::hardware::power::V1_1::IPower; -using IPowerV1_0 = android::hardware::power::V1_0::IPower; using namespace android; using namespace android::power; @@ -40,15 +39,6 @@ using namespace testing; // ------------------------------------------------------------------------------------------------- -class MockIPowerV1_0 : public IPowerV1_0 { -public: - MOCK_METHOD(hardware::Return, setInteractive, (bool interactive), (override)); - MOCK_METHOD(hardware::Return, powerHint, (PowerHint hint, int32_t data), (override)); - MOCK_METHOD(hardware::Return, setFeature, (Feature feature, bool activate), (override)); - MOCK_METHOD(hardware::Return, getPlatformLowPowerStats, - (getPlatformLowPowerStats_cb _hidl_cb), (override)); -}; - class MockIPowerV1_1 : public IPowerV1_1 { public: MOCK_METHOD(hardware::Return, setInteractive, (bool interactive), (override)); @@ -69,23 +59,22 @@ public: protected: std::unique_ptr mWrapper = nullptr; - sp> mMockHalV1_0 = nullptr; - sp> mMockHalV1_1 = nullptr; + sp> mMockHal = nullptr; }; // ------------------------------------------------------------------------------------------------- void PowerHalWrapperHidlV1_1Test::SetUp() { - mMockHalV1_0 = new StrictMock(); - mMockHalV1_1 = new StrictMock(); - mWrapper = std::make_unique(mMockHalV1_0, mMockHalV1_1); + mMockHal = new StrictMock(); + mWrapper = std::make_unique(mMockHal); ASSERT_NE(mWrapper, nullptr); + EXPECT_CALL(*mMockHal.get(), powerHint(_, _)).Times(0); } // ------------------------------------------------------------------------------------------------- TEST_F(PowerHalWrapperHidlV1_1Test, TestSetBoostSuccessful) { - EXPECT_CALL(*mMockHalV1_1.get(), powerHintAsync(Eq(PowerHint::INTERACTION), Eq(1000))) + EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::INTERACTION), Eq(1000))) .Times(Exactly(1)); auto result = mWrapper->setBoost(Boost::INTERACTION, 1000); @@ -93,7 +82,7 @@ TEST_F(PowerHalWrapperHidlV1_1Test, TestSetBoostSuccessful) { } TEST_F(PowerHalWrapperHidlV1_1Test, TestSetBoostFailed) { - EXPECT_CALL(*mMockHalV1_1.get(), powerHintAsync(Eq(PowerHint::INTERACTION), Eq(1000))) + EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::INTERACTION), Eq(1000))) .Times(Exactly(1)) .WillRepeatedly([](PowerHint, int32_t) { return hardware::Return(hardware::Status::fromExceptionCode(-1)); @@ -104,24 +93,31 @@ TEST_F(PowerHalWrapperHidlV1_1Test, TestSetBoostFailed) { } TEST_F(PowerHalWrapperHidlV1_1Test, TestSetBoostUnsupported) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0); + EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0); + auto result = mWrapper->setBoost(Boost::CAMERA_LAUNCH, 10); ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setBoost(Boost::ML_ACC, 10); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 10); + ASSERT_TRUE(result.isUnsupported()); } TEST_F(PowerHalWrapperHidlV1_1Test, TestSetMode) { { InSequence seq; - EXPECT_CALL(*mMockHalV1_1.get(), powerHintAsync(Eq(PowerHint::LAUNCH), Eq(1))) + EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::LAUNCH), Eq(true))) .Times(Exactly(1)); - EXPECT_CALL(*mMockHalV1_1.get(), powerHintAsync(Eq(PowerHint::LOW_POWER), Eq(0))) + EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::LOW_POWER), Eq(false))) .Times(Exactly(1)); - EXPECT_CALL(*mMockHalV1_1.get(), - powerHintAsync(Eq(PowerHint::SUSTAINED_PERFORMANCE), Eq(1))) + EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::SUSTAINED_PERFORMANCE), Eq(true))) .Times(Exactly(1)); - EXPECT_CALL(*mMockHalV1_1.get(), powerHintAsync(Eq(PowerHint::VR_MODE), Eq(0))) + EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::VR_MODE), Eq(false))) .Times(Exactly(1)); - EXPECT_CALL(*mMockHalV1_0.get(), setInteractive(Eq(true))).Times(Exactly(1)); - EXPECT_CALL(*mMockHalV1_0.get(), + EXPECT_CALL(*mMockHal.get(), setInteractive(Eq(true))).Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), setFeature(Eq(Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE), Eq(false))) .Times(Exactly(1)); } @@ -141,7 +137,7 @@ TEST_F(PowerHalWrapperHidlV1_1Test, TestSetMode) { } TEST_F(PowerHalWrapperHidlV1_1Test, TestSetModeFailed) { - EXPECT_CALL(*mMockHalV1_1.get(), powerHintAsync(Eq(PowerHint::LAUNCH), Eq(1))) + EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::LAUNCH), Eq(true))) .Times(Exactly(1)) .WillRepeatedly([](PowerHint, int32_t) { return hardware::Return(hardware::Status::fromExceptionCode(-1)); @@ -152,6 +148,16 @@ TEST_F(PowerHalWrapperHidlV1_1Test, TestSetModeFailed) { } TEST_F(PowerHalWrapperHidlV1_1Test, TestSetModeIgnored) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0); + EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0); + auto result = mWrapper->setMode(Mode::CAMERA_STREAMING_HIGH, true); ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::EXPENSIVE_RENDERING, false); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::FIXED_PERFORMANCE, true); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::GAME_LOADING, false); + ASSERT_TRUE(result.isUnsupported()); } diff --git a/services/powermanager/tests/PowerHalWrapperHidlV1_2Test.cpp b/services/powermanager/tests/PowerHalWrapperHidlV1_2Test.cpp new file mode 100644 index 0000000000..cf48409f5f --- /dev/null +++ b/services/powermanager/tests/PowerHalWrapperHidlV1_2Test.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "PowerHalWrapperHidlV1_2Test" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using android::hardware::power::Boost; +using android::hardware::power::Mode; +using android::hardware::power::V1_0::Feature; +using PowerHintV1_0 = android::hardware::power::V1_0::PowerHint; +using PowerHintV1_2 = android::hardware::power::V1_2::PowerHint; + +using IPowerV1_2 = android::hardware::power::V1_2::IPower; + +using namespace android; +using namespace android::power; +using namespace std::chrono_literals; +using namespace testing; + +// ------------------------------------------------------------------------------------------------- + +class MockIPowerV1_2 : public IPowerV1_2 { +public: + MOCK_METHOD(hardware::Return, setInteractive, (bool interactive), (override)); + MOCK_METHOD(hardware::Return, powerHint, (PowerHintV1_0 hint, int32_t data), (override)); + MOCK_METHOD(hardware::Return, setFeature, (Feature feature, bool activate), (override)); + MOCK_METHOD(hardware::Return, getPlatformLowPowerStats, + (getPlatformLowPowerStats_cb _hidl_cb), (override)); + MOCK_METHOD(hardware::Return, powerHintAsync, (PowerHintV1_0 hint, int32_t data), + (override)); + MOCK_METHOD(hardware::Return, powerHintAsync_1_2, (PowerHintV1_2 hint, int32_t data), + (override)); + MOCK_METHOD(hardware::Return, getSubsystemLowPowerStats, + (getSubsystemLowPowerStats_cb _hidl_cb), (override)); +}; + +// ------------------------------------------------------------------------------------------------- + +class PowerHalWrapperHidlV1_2Test : public Test { +public: + void SetUp() override; + +protected: + std::unique_ptr mWrapper = nullptr; + sp> mMockHal = nullptr; +}; + +// ------------------------------------------------------------------------------------------------- + +void PowerHalWrapperHidlV1_2Test::SetUp() { + mMockHal = new StrictMock(); + mWrapper = std::make_unique(mMockHal); + ASSERT_NE(mWrapper, nullptr); + EXPECT_CALL(*mMockHal.get(), powerHint(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), powerHintAsync(_, _)).Times(0); +} + +// ------------------------------------------------------------------------------------------------- + +TEST_F(PowerHalWrapperHidlV1_2Test, TestSetBoostSuccessful) { + { + InSequence seq; + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::INTERACTION), Eq(1000))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::CAMERA_SHOT), Eq(500))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::CAMERA_LAUNCH), Eq(300))) + .Times(Exactly(1)); + } + + auto result = mWrapper->setBoost(Boost::INTERACTION, 1000); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setBoost(Boost::CAMERA_SHOT, 500); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setBoost(Boost::CAMERA_LAUNCH, 300); + ASSERT_TRUE(result.isOk()); +} + +TEST_F(PowerHalWrapperHidlV1_2Test, TestSetBoostFailed) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::INTERACTION), Eq(1000))) + .Times(Exactly(1)) + .WillRepeatedly([](PowerHintV1_2, int32_t) { + return hardware::Return(hardware::Status::fromExceptionCode(-1)); + }); + + auto result = mWrapper->setBoost(Boost::INTERACTION, 1000); + ASSERT_TRUE(result.isFailed()); +} + +TEST_F(PowerHalWrapperHidlV1_2Test, TestSetBoostUnsupported) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0); + EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0); + + auto result = mWrapper->setBoost(Boost::ML_ACC, 10); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 10); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setBoost(Boost::AUDIO_LAUNCH, 10); + ASSERT_TRUE(result.isUnsupported()); +} + +TEST_F(PowerHalWrapperHidlV1_2Test, TestSetMode) { + { + InSequence seq; + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::LAUNCH), Eq(true))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::LOW_POWER), Eq(false))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), + powerHintAsync_1_2(Eq(PowerHintV1_2::SUSTAINED_PERFORMANCE), Eq(true))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::VR_MODE), Eq(false))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), setInteractive(Eq(true))).Times(Exactly(true)); + EXPECT_CALL(*mMockHal.get(), + setFeature(Eq(Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE), Eq(false))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), + powerHintAsync_1_2(Eq(PowerHintV1_2::CAMERA_STREAMING), Eq(true))) + .Times(Exactly(2)); + EXPECT_CALL(*mMockHal.get(), + powerHintAsync_1_2(Eq(PowerHintV1_2::CAMERA_STREAMING), Eq(false))) + .Times(Exactly(2)); + EXPECT_CALL(*mMockHal.get(), + powerHintAsync_1_2(Eq(PowerHintV1_2::AUDIO_LOW_LATENCY), Eq(true))) + .Times(Exactly(1)); + } + + auto result = mWrapper->setMode(Mode::LAUNCH, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::LOW_POWER, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::SUSTAINED_PERFORMANCE, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::VR, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::INTERACTIVE, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::DOUBLE_TAP_TO_WAKE, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::CAMERA_STREAMING_SECURE, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::CAMERA_STREAMING_LOW, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::CAMERA_STREAMING_MID, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::CAMERA_STREAMING_HIGH, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::AUDIO_STREAMING_LOW_LATENCY, true); + ASSERT_TRUE(result.isOk()); +} + +TEST_F(PowerHalWrapperHidlV1_2Test, TestSetModeFailed) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::LAUNCH), Eq(1))) + .Times(Exactly(1)) + .WillRepeatedly([](PowerHintV1_2, int32_t) { + return hardware::Return(hardware::Status::fromExceptionCode(-1)); + }); + + auto result = mWrapper->setMode(Mode::LAUNCH, 1); + ASSERT_TRUE(result.isFailed()); +} + +TEST_F(PowerHalWrapperHidlV1_2Test, TestSetModeIgnored) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0); + EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0); + + auto result = mWrapper->setMode(Mode::EXPENSIVE_RENDERING, true); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::DISPLAY_INACTIVE, true); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::FIXED_PERFORMANCE, true); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::GAME_LOADING, false); + ASSERT_TRUE(result.isUnsupported()); +} diff --git a/services/powermanager/tests/PowerHalWrapperHidlV1_3Test.cpp b/services/powermanager/tests/PowerHalWrapperHidlV1_3Test.cpp new file mode 100644 index 0000000000..2c48537edc --- /dev/null +++ b/services/powermanager/tests/PowerHalWrapperHidlV1_3Test.cpp @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "PowerHalWrapperHidlV1_3Test" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using android::hardware::power::Boost; +using android::hardware::power::Mode; +using android::hardware::power::V1_0::Feature; +using PowerHintV1_0 = android::hardware::power::V1_0::PowerHint; +using PowerHintV1_2 = android::hardware::power::V1_2::PowerHint; +using PowerHintV1_3 = android::hardware::power::V1_3::PowerHint; + +using IPowerV1_3 = android::hardware::power::V1_3::IPower; + +using namespace android; +using namespace android::power; +using namespace std::chrono_literals; +using namespace testing; + +// ------------------------------------------------------------------------------------------------- + +class MockIPowerV1_3 : public IPowerV1_3 { +public: + MOCK_METHOD(hardware::Return, setInteractive, (bool interactive), (override)); + MOCK_METHOD(hardware::Return, powerHint, (PowerHintV1_0 hint, int32_t data), (override)); + MOCK_METHOD(hardware::Return, setFeature, (Feature feature, bool activate), (override)); + MOCK_METHOD(hardware::Return, getPlatformLowPowerStats, + (getPlatformLowPowerStats_cb _hidl_cb), (override)); + MOCK_METHOD(hardware::Return, powerHintAsync, (PowerHintV1_0 hint, int32_t data), + (override)); + MOCK_METHOD(hardware::Return, powerHintAsync_1_2, (PowerHintV1_2 hint, int32_t data), + (override)); + MOCK_METHOD(hardware::Return, powerHintAsync_1_3, (PowerHintV1_3 hint, int32_t data), + (override)); + + MOCK_METHOD(hardware::Return, getSubsystemLowPowerStats, + (getSubsystemLowPowerStats_cb _hidl_cb), (override)); +}; + +// ------------------------------------------------------------------------------------------------- + +class PowerHalWrapperHidlV1_3Test : public Test { +public: + void SetUp() override; + +protected: + std::unique_ptr mWrapper = nullptr; + sp> mMockHal = nullptr; +}; + +// ------------------------------------------------------------------------------------------------- + +void PowerHalWrapperHidlV1_3Test::SetUp() { + mMockHal = new StrictMock(); + mWrapper = std::make_unique(mMockHal); + ASSERT_NE(mWrapper, nullptr); + EXPECT_CALL(*mMockHal.get(), powerHint(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), powerHintAsync(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(_, _)).Times(0); +} + +// ------------------------------------------------------------------------------------------------- + +TEST_F(PowerHalWrapperHidlV1_3Test, TestSetBoostSuccessful) { + { + InSequence seq; + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::INTERACTION), Eq(1000))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::CAMERA_SHOT), Eq(500))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::CAMERA_LAUNCH), Eq(300))) + .Times(Exactly(1)); + } + + auto result = mWrapper->setBoost(Boost::INTERACTION, 1000); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setBoost(Boost::CAMERA_SHOT, 500); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setBoost(Boost::CAMERA_LAUNCH, 300); + ASSERT_TRUE(result.isOk()); +} + +TEST_F(PowerHalWrapperHidlV1_3Test, TestSetBoostFailed) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::INTERACTION), Eq(1000))) + .Times(Exactly(1)) + .WillRepeatedly([](PowerHintV1_3, int32_t) { + return hardware::Return(hardware::Status::fromExceptionCode(-1)); + }); + + auto result = mWrapper->setBoost(Boost::INTERACTION, 1000); + ASSERT_TRUE(result.isFailed()); +} + +TEST_F(PowerHalWrapperHidlV1_3Test, TestSetBoostUnsupported) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0); + EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0); + + auto result = mWrapper->setBoost(Boost::ML_ACC, 10); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 10); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setBoost(Boost::AUDIO_LAUNCH, 10); + ASSERT_TRUE(result.isUnsupported()); +} + +TEST_F(PowerHalWrapperHidlV1_3Test, TestSetMode) { + { + InSequence seq; + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::LAUNCH), Eq(true))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::LOW_POWER), Eq(false))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), + powerHintAsync_1_3(Eq(PowerHintV1_3::SUSTAINED_PERFORMANCE), Eq(true))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::VR_MODE), Eq(false))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), setInteractive(Eq(true))).Times(Exactly(true)); + EXPECT_CALL(*mMockHal.get(), + setFeature(Eq(Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE), Eq(false))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), + powerHintAsync_1_3(Eq(PowerHintV1_3::CAMERA_STREAMING), Eq(true))) + .Times(Exactly(2)); + EXPECT_CALL(*mMockHal.get(), + powerHintAsync_1_3(Eq(PowerHintV1_3::CAMERA_STREAMING), Eq(false))) + .Times(Exactly(2)); + EXPECT_CALL(*mMockHal.get(), + powerHintAsync_1_3(Eq(PowerHintV1_3::AUDIO_LOW_LATENCY), Eq(true))) + .Times(Exactly(1)); + EXPECT_CALL(*mMockHal.get(), + powerHintAsync_1_3(Eq(PowerHintV1_3::EXPENSIVE_RENDERING), Eq(false))) + .Times(Exactly(1)); + } + + auto result = mWrapper->setMode(Mode::LAUNCH, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::LOW_POWER, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::SUSTAINED_PERFORMANCE, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::VR, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::INTERACTIVE, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::DOUBLE_TAP_TO_WAKE, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::CAMERA_STREAMING_SECURE, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::CAMERA_STREAMING_LOW, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::CAMERA_STREAMING_MID, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::CAMERA_STREAMING_HIGH, false); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::AUDIO_STREAMING_LOW_LATENCY, true); + ASSERT_TRUE(result.isOk()); + result = mWrapper->setMode(Mode::EXPENSIVE_RENDERING, false); + ASSERT_TRUE(result.isOk()); +} + +TEST_F(PowerHalWrapperHidlV1_3Test, TestSetModeFailed) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::LAUNCH), Eq(1))) + .Times(Exactly(1)) + .WillRepeatedly([](PowerHintV1_3, int32_t) { + return hardware::Return(hardware::Status::fromExceptionCode(-1)); + }); + + auto result = mWrapper->setMode(Mode::LAUNCH, 1); + ASSERT_TRUE(result.isFailed()); +} + +TEST_F(PowerHalWrapperHidlV1_3Test, TestSetModeIgnored) { + EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(_, _)).Times(0); + EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0); + EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0); + + auto result = mWrapper->setMode(Mode::GAME, true); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::DISPLAY_INACTIVE, true); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::FIXED_PERFORMANCE, true); + ASSERT_TRUE(result.isUnsupported()); + result = mWrapper->setMode(Mode::GAME_LOADING, false); + ASSERT_TRUE(result.isUnsupported()); +} -- GitLab From 596a7564d738a34553ff81f19e139f6235d82008 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Wed, 12 Oct 2022 14:57:05 -0700 Subject: [PATCH 0369/1310] Minor changes Changed data input/output types (add compressed/uncompressed struct). Fixed typos in some places (jpeg_g to jpeg_r). Bug: b/252835416 Change-Id: Ibc9d4c24108417a528052bda64c718e365421e28 --- .../include/jpegrecoverymap/recoverymap.h | 79 ++++++++++++++----- libs/jpegrecoverymap/recoverymap.cpp | 51 +++++++----- 2 files changed, 89 insertions(+), 41 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h index c5f8e9acd3..6949f85873 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -16,61 +16,100 @@ namespace android::recoverymap { +/* + * Holds information for uncompressed image or recovery map. + */ +struct jpeg_r_uncompressed_struct { + // Pointer to the data location. + void* data; + // Width of the recovery map or image in pixels. + int width; + // Height of the recovery map or image in pixels. + int height; +}; + +/* + * Holds information for compressed image or recovery map. + */ +struct jpeg_r_compressed_struct { + // Pointer to the data location. + void* data; + // Data length; + int length; +}; + +typedef struct jpeg_r_uncompressed_struct* j_r_uncompressed_ptr; +typedef struct jpeg_r_compressed_struct* j_r_compressed_ptr; + class RecoveryMap { public: /* * This method is called in the decoding pipeline. It will decode the recovery map. * - * input: compressed recovery map - * output: uncompressed recovery map + * @param compressed_recovery_map compressed recovery map + * @param dest decoded recover map + * @return true if decoding succeeds */ - void* decodeRecoveryMap(void* compressed_recovery_map); + bool decodeRecoveryMap(j_r_compressed_ptr compressed_recovery_map, + j_r_uncompressed_ptr dest); /* * This method is called in the encoding pipeline. It will encode the recovery map. * - * input: uncompressed recovery map - * output: compressed recovery map + * @param uncompressed_recovery_map uncompressed recovery map + * @param dest encoded recover map + * @return true if encoding succeeds */ - void* encodeRecoveryMap(void* uncompressed_recovery_map); + bool encodeRecoveryMap(j_r_uncompressed_ptr uncompressed_recovery_map, + j_r_compressed_ptr dest); /* * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and * 10-bit yuv images as input, and calculate the uncompressed recovery map. * - * input: uncompressed yuv_420 image, uncompressed p010 image - * output: uncompressed recovery map + * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format + * @param uncompressed_p010_image uncompressed HDR image in P010 color format + * @param dest recover map + * @return true if calculation succeeds */ - void* generateRecoveryMap(void* uncompressed_yuv_420_image, void* uncompressed_p010_image); + bool generateRecoveryMap(j_r_uncompressed_ptr uncompressed_yuv_420_image, + j_r_uncompressed_ptr uncompressed_p010_image, + j_r_uncompressed_ptr dest); /* * This method is called in the decoding pipeline. It will take the uncompressed (decoded) - * 8-bit yuv image and the uncompressed(decoded) recovery map as input, and calculate the + * 8-bit yuv image and the uncompressed (decoded) recovery map as input, and calculate the * 10-bit recovered image (in p010 color format). * - * input: uncompressed yuv_420 image, uncompressed recovery map - * output: uncompress p010 image + * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format + * @param uncompressed_recovery_map uncompressed recovery map + * @param dest reconstructed HDR image + * @return true if calculation succeeds */ - void* applyRecoveryMap(void* uncompressed_yuv_420_image, void* uncompressed_recovery_map); + bool applyRecoveryMap(j_r_uncompressed_ptr uncompressed_yuv_420_image, + j_r_uncompressed_ptr uncompressed_recovery_map, + j_r_uncompressed_ptr dest); /* * This method is called in the decoding pipeline. It will read XMP metadata to find the start * position of the compressed recovery map, and will extract the compressed recovery map. * - * input: compressed JPEG-G image (8-bit JPEG + compressed recovery map) - * output: compressed recovery map + * @param compressed_jpeg_r_image compressed JPEG_R image + * @return compressed recovery map */ - void* extractRecoveryMap(void* compressed_jpeg_g_image); + j_r_compressed_ptr extractRecoveryMap(void* compressed_jpeg_r_image); /* * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image * and the compressed recovery map as input, and update the XMP metadata with the end of JPEG * marker, and append the compressed gian map after the JPEG. * - * input: compressed 8-bit JPEG image (standard JPEG), compressed recovery map - * output: compressed JPEG-G image (8-bit JPEG + compressed recovery map) + * @param compressed_jpeg_image compressed 8-bit JPEG image + * @param compress_recovery_map compressed recover map + * @return compressed JPEG_R image */ - void* appendRecoveryMap(void* compressed_jpeg_image, void* compressed_recovery_map); + void* appendRecoveryMap(void* compressed_jpeg_image, + j_r_compressed_ptr compressed_recovery_map); }; -} // namespace android::recoverymap \ No newline at end of file +} // namespace android::recoverymap diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index 3e95a31871..bd92652368 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -18,46 +18,54 @@ namespace android::recoverymap { -void* RecoveryMap::decodeRecoveryMap(void* compressed_recovery_map) { - if (compressed_recovery_map == nullptr) { - return nullptr; +bool RecoveryMap::decodeRecoveryMap(j_r_compressed_ptr compressed_recovery_map, + j_r_uncompressed_ptr dest) { + if (compressed_recovery_map == nullptr || dest == nullptr) { + return false; } // TBD - return nullptr; + return true; } -void* RecoveryMap::encodeRecoveryMap(void* uncompressed_recovery_map) { - if (uncompressed_recovery_map == nullptr) { - return nullptr; +bool RecoveryMap::encodeRecoveryMap(j_r_uncompressed_ptr uncompressed_recovery_map, + j_r_compressed_ptr dest) { + if (uncompressed_recovery_map == nullptr || dest == nullptr) { + return false; } // TBD - return nullptr; + return true; } -void* RecoveryMap::generateRecoveryMap( - void* uncompressed_yuv_420_image, void* uncompressed_p010_image) { - if (uncompressed_yuv_420_image == nullptr || uncompressed_p010_image == nullptr) { - return nullptr; +bool RecoveryMap::generateRecoveryMap(j_r_uncompressed_ptr uncompressed_yuv_420_image, + j_r_uncompressed_ptr uncompressed_p010_image, + j_r_uncompressed_ptr dest) { + if (uncompressed_yuv_420_image == nullptr + || uncompressed_p010_image == nullptr + || dest == nullptr) { + return false; } // TBD - return nullptr; + return true; } -void* RecoveryMap::applyRecoveryMap( - void* uncompressed_yuv_420_image, void* uncompressed_recovery_map) { - if (uncompressed_yuv_420_image == nullptr || uncompressed_recovery_map == nullptr) { - return nullptr; +bool RecoveryMap::applyRecoveryMap(j_r_uncompressed_ptr uncompressed_yuv_420_image, + j_r_uncompressed_ptr uncompressed_recovery_map, + j_r_uncompressed_ptr dest) { + if (uncompressed_yuv_420_image == nullptr + || uncompressed_recovery_map == nullptr + || dest == nullptr) { + return false; } // TBD - return nullptr; + return true; } -void* RecoveryMap::extractRecoveryMap(void* compressed_jpeg_g_image) { - if (compressed_jpeg_g_image == nullptr) { +j_r_compressed_ptr RecoveryMap::extractRecoveryMap(void* compressed_jpeg_r_image) { + if (compressed_jpeg_r_image == nullptr) { return nullptr; } @@ -65,7 +73,8 @@ void* RecoveryMap::extractRecoveryMap(void* compressed_jpeg_g_image) { return nullptr; } -void* RecoveryMap::appendRecoveryMap(void* compressed_jpeg_image, void* compressed_recovery_map) { +void* RecoveryMap::appendRecoveryMap(void* compressed_jpeg_image, + j_r_compressed_ptr compressed_recovery_map) { if (compressed_jpeg_image == nullptr || compressed_recovery_map == nullptr) { return nullptr; } -- GitLab From 39b7ca2933ffde9094aed908a507158d20be3d7a Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Wed, 5 Oct 2022 15:55:48 +0000 Subject: [PATCH 0370/1310] Report motion offsets for touchpad swipes Adds two new axes, AXIS_GESTURE_X_OFFSET and AXIS_GESTURE_Y_OFFSET, which report the movement of swipe gestures on the touchpad as a fraction of the touchpad's size. Bug: 246758376 Test: check axis values come through in a test app Change-Id: I313410053a8db13273bd05a33d3a6a1f75081dae --- include/android/input.h | 17 ++++++- libs/input/InputEventLabels.cpp | 4 +- .../inputflinger/InputCommonConverter.cpp | 5 +- .../reader/mapper/TouchInputMapper.cpp | 30 ++++++++---- .../inputflinger/tests/InputReader_test.cpp | 46 +++++++++++++++++++ 5 files changed, 89 insertions(+), 13 deletions(-) diff --git a/include/android/input.h b/include/android/input.h index d906af6e0c..5d19c5cb13 100644 --- a/include/android/input.h +++ b/include/android/input.h @@ -771,6 +771,21 @@ enum { * The interpretation of a generic axis is device-specific. */ AMOTION_EVENT_AXIS_GENERIC_16 = 47, + /** + * Axis constant: X gesture offset axis of a motion event. + * + * - For a touch pad, reports the distance that a swipe gesture has moved in the X axis, as a + * proportion of the touch pad's size. For example, if a touch pad is 1000 units wide, and a + * swipe gesture starts at X = 500 then moves to X = 400, this axis would have a value of + * -0.1. + */ + AMOTION_EVENT_AXIS_GESTURE_X_OFFSET = 48, + /** + * Axis constant: Y gesture offset axis of a motion event. + * + * The same as {@link AMOTION_EVENT_AXIS_GESTURE_X_OFFSET}, but for the Y axis. + */ + AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET = 49, /** * Note: This is not an "Axis constant". It does not represent any axis, nor should it be used @@ -778,7 +793,7 @@ enum { * to make some computations (like iterating through all possible axes) cleaner. * Please update the value accordingly if you add a new axis. */ - AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE = AMOTION_EVENT_AXIS_GENERIC_16, + AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE = AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, // 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/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp index 163a2fe924..b78fae3027 100644 --- a/libs/input/InputEventLabels.cpp +++ b/libs/input/InputEventLabels.cpp @@ -391,7 +391,9 @@ namespace android { DEFINE_AXIS(GENERIC_13), \ DEFINE_AXIS(GENERIC_14), \ DEFINE_AXIS(GENERIC_15), \ - DEFINE_AXIS(GENERIC_16) + DEFINE_AXIS(GENERIC_16), \ + DEFINE_AXIS(GESTURE_X_OFFSET), \ + DEFINE_AXIS(GESTURE_Y_OFFSET) // NOTE: If you add new LEDs here, you must also add them to Input.h #define LEDS_SEQUENCE \ diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp index 23b6f57b23..6db89d4759 100644 --- a/services/inputflinger/InputCommonConverter.cpp +++ b/services/inputflinger/InputCommonConverter.cpp @@ -263,8 +263,11 @@ static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_13) == common static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_14) == common::Axis::GENERIC_14); static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_15) == common::Axis::GENERIC_15); static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_16) == common::Axis::GENERIC_16); +// TODO(hcutts): add GESTURE_X_OFFSET and GESTURE_Y_OFFSET. +// If you added a new axis, consider whether this should also be exposed as a HAL axis. Update the +// static_assert below and add the new axis here, or leave a comment summarizing your decision. static_assert(static_cast(AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE) == - static_cast(AMOTION_EVENT_AXIS_GENERIC_16)); + static_cast(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET)); static common::VideoFrame getHalVideoFrame(const TouchVideoFrame& frame) { common::VideoFrame out; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index da58efde31..7f6785eaa6 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -3175,7 +3175,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerGesture.referenceIdBits = mCurrentCookedState.fingerIdBits; // Add delta for all fingers and calculate a common movement delta. - float commonDeltaX = 0, commonDeltaY = 0; + int32_t commonDeltaRawX = 0, commonDeltaRawY = 0; BitSet32 commonIdBits(mLastCookedState.fingerIdBits.value & mCurrentCookedState.fingerIdBits.value); for (BitSet32 idBits(commonIdBits); !idBits.isEmpty();) { @@ -3188,11 +3188,11 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi delta.dy += cpd.y - lpd.y; if (first) { - commonDeltaX = delta.dx; - commonDeltaY = delta.dy; + commonDeltaRawX = delta.dx; + commonDeltaRawY = delta.dy; } else { - commonDeltaX = calculateCommonVector(commonDeltaX, delta.dx); - commonDeltaY = calculateCommonVector(commonDeltaY, delta.dy); + commonDeltaRawX = calculateCommonVector(commonDeltaRawX, delta.dx); + commonDeltaRawY = calculateCommonVector(commonDeltaRawY, delta.dy); } } @@ -3298,7 +3298,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi // Move the reference points based on the overall group motion of the fingers // except in PRESS mode while waiting for a transition to occur. if (mPointerGesture.currentGestureMode != PointerGesture::Mode::PRESS && - (commonDeltaX || commonDeltaY)) { + (commonDeltaRawX || commonDeltaRawY)) { for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) { uint32_t id = idBits.clearFirstMarkedBit(); PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id]; @@ -3306,11 +3306,11 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi delta.dy = 0; } - mPointerGesture.referenceTouchX += commonDeltaX; - mPointerGesture.referenceTouchY += commonDeltaY; + mPointerGesture.referenceTouchX += commonDeltaRawX; + mPointerGesture.referenceTouchY += commonDeltaRawY; - commonDeltaX *= mPointerXMovementScale; - commonDeltaY *= mPointerYMovementScale; + float commonDeltaX = commonDeltaRawX * mPointerXMovementScale; + float commonDeltaY = commonDeltaRawY * mPointerYMovementScale; rotateDelta(mInputDeviceOrientation, &commonDeltaX, &commonDeltaY); mPointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY); @@ -3341,6 +3341,16 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, mPointerGesture.referenceGestureY); mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) { + float xOffset = static_cast(commonDeltaRawX) / + (mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue); + float yOffset = static_cast(commonDeltaRawY) / + (mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue); + mPointerGesture.currentGestureCoords[0] + .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, xOffset); + mPointerGesture.currentGestureCoords[0] + .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset); + } } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) { // FREEFORM mode. ALOGD_IF(DEBUG_GESTURES, diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index dded6a13e0..1e26265b46 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -10085,6 +10085,52 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthFreeform) { 0, 0, 0, 0, 0)); } +TEST_F(MultiTouchPointerModeTest, TwoFingerSwipeOffsets) { + preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/); + MultiTouchInputMapper& mapper = addMapperAndConfigure(); + NotifyMotionArgs motionArgs; + + // Place two fingers down. + int32_t x1 = 100, y1 = 125, x2 = 550, y2 = 125; + + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, x1, y1); + processMTSync(mapper); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, x2, y2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(1U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); + ASSERT_EQ(0, motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET)); + ASSERT_EQ(0, motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET)); + + // Move the two fingers down and to the left. + int32_t movingDistance = 200; + x1 -= movingDistance; + y1 += movingDistance; + x2 -= movingDistance; + y2 += movingDistance; + + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, x1, y1); + processMTSync(mapper); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, x2, y2); + processMTSync(mapper); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(1U, motionArgs.pointerCount); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(MotionClassification::TWO_FINGER_SWIPE, motionArgs.classification); + ASSERT_LT(motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET), 0); + ASSERT_GT(motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET), 0); +} + // --- JoystickInputMapperTest --- class JoystickInputMapperTest : public InputMapperTest { -- GitLab From 6a778e81cd549ed9dfc11b7b59a86f796b33c300 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Thu, 13 Oct 2022 13:47:51 +0000 Subject: [PATCH 0371/1310] Add asserts for new HAL axes in InputCommonConverter Bug: 246758376 Test: check that build succeeds Change-Id: I13d341fa05819f84b476c412783e4492ab7032b8 --- services/inputflinger/Android.bp | 2 +- services/inputflinger/InputCommonConverter.cpp | 5 ++++- services/inputflinger/tests/fuzzers/Android.bp | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp index ddcd51f357..8714b0397f 100644 --- a/services/inputflinger/Android.bp +++ b/services/inputflinger/Android.bp @@ -69,7 +69,7 @@ cc_defaults { name: "libinputflinger_defaults", srcs: [":libinputflinger_sources"], shared_libs: [ - "android.hardware.input.processor-V1-ndk", + "android.hardware.input.processor-V2-ndk", "libbase", "libbinder", "libbinder_ndk", diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp index 6db89d4759..09f1c0f6f3 100644 --- a/services/inputflinger/InputCommonConverter.cpp +++ b/services/inputflinger/InputCommonConverter.cpp @@ -263,7 +263,10 @@ static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_13) == common static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_14) == common::Axis::GENERIC_14); static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_15) == common::Axis::GENERIC_15); static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_16) == common::Axis::GENERIC_16); -// TODO(hcutts): add GESTURE_X_OFFSET and GESTURE_Y_OFFSET. +static_assert(static_cast(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET) == + common::Axis::GESTURE_X_OFFSET); +static_assert(static_cast(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET) == + common::Axis::GESTURE_Y_OFFSET); // If you added a new axis, consider whether this should also be exposed as a HAL axis. Update the // static_assert below and add the new axis here, or leave a comment summarizing your decision. static_assert(static_cast(AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE) == diff --git a/services/inputflinger/tests/fuzzers/Android.bp b/services/inputflinger/tests/fuzzers/Android.bp index 55c2db6c91..4359a4b7fb 100644 --- a/services/inputflinger/tests/fuzzers/Android.bp +++ b/services/inputflinger/tests/fuzzers/Android.bp @@ -55,7 +55,7 @@ cc_defaults { ], shared_libs: [ "android.hardware.input.classifier@1.0", - "android.hardware.input.processor-V1-ndk", + "android.hardware.input.processor-V2-ndk", "libbase", "libbinder", "libcutils", -- GitLab From 016025220cccd0ee963829340346b2f989722cab Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Fri, 7 Oct 2022 19:02:28 -0400 Subject: [PATCH 0372/1310] SF: Simplify per-display refresh rate selection Remove verbose, single-use helper functions/types to centralize the selection logic and merge two passes. Avoid allocation and hashing. Fix the algorithm to not choose a refresh rate based on total score unless it is common to all displays, and not be thrown off by equal scores. Bug: 241285191 Test: libsurfaceflinger_unittest Change-Id: I355dea767c6b564a04a51476f0cc235a1fceb879 --- services/surfaceflinger/Display/DisplayMap.h | 4 + .../surfaceflinger/Scheduler/Scheduler.cpp | 152 +++++++++--------- services/surfaceflinger/Scheduler/Scheduler.h | 30 +--- .../Scheduler/include/scheduler/Fps.h | 4 - 4 files changed, 85 insertions(+), 105 deletions(-) diff --git a/services/surfaceflinger/Display/DisplayMap.h b/services/surfaceflinger/Display/DisplayMap.h index baf0da9f1b..0d5970676e 100644 --- a/services/surfaceflinger/Display/DisplayMap.h +++ b/services/surfaceflinger/Display/DisplayMap.h @@ -17,6 +17,7 @@ #pragma once #include +#include namespace android::display { @@ -28,4 +29,7 @@ using DisplayMap = ftl::SmallMap; template using PhysicalDisplayMap = ftl::SmallMap; +template +using PhysicalDisplayVector = ftl::SmallVector; + } // namespace android::display diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 12949d64f7..30f2c27b1e 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,7 @@ #include "../Layer.h" #include "DispSyncSource.h" +#include "Display/DisplayMap.h" #include "EventThread.h" #include "FrameRateOverrideMappings.h" #include "OneShotTimer.h" @@ -56,39 +58,6 @@ } \ } while (false) -namespace { - -using android::Fps; -using android::FpsApproxEqual; -using android::FpsHash; -using android::scheduler::AggregatedFpsScore; -using android::scheduler::RefreshRateRankingsAndSignals; - -// Returns the aggregated score per Fps for the RefreshRateRankingsAndSignals sourced. -auto getAggregatedScoresPerFps( - const std::vector& refreshRateRankingsAndSignalsPerDisplay) - -> std::unordered_map { - std::unordered_map aggregatedScoresPerFps; - - for (const auto& refreshRateRankingsAndSignal : refreshRateRankingsAndSignalsPerDisplay) { - const auto& refreshRateRankings = refreshRateRankingsAndSignal.refreshRateRankings; - - std::for_each(refreshRateRankings.begin(), refreshRateRankings.end(), [&](const auto& it) { - const auto [score, result] = - aggregatedScoresPerFps.try_emplace(it.displayModePtr->getFps(), - AggregatedFpsScore{it.score, - /* numDisplays */ 1}); - if (!result) { // update - score->second.totalScore += it.score; - score->second.numDisplays++; - } - }); - } - return aggregatedScoresPerFps; -} - -} // namespace - namespace android::scheduler { Scheduler::Scheduler(ICompositor& compositor, ISchedulerCallback& callback, FeatureFlags features) @@ -157,6 +126,15 @@ void Scheduler::setRefreshRateConfigs(std::shared_ptr config mRefreshRateConfigs->startIdleTimer(); } +void Scheduler::registerDisplay(sp display) { + const bool ok = mDisplays.try_emplace(display->getPhysicalId(), std::move(display)).second; + ALOGE_IF(!ok, "%s: Duplicate display", __func__); +} + +void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { + mDisplays.erase(displayId); +} + void Scheduler::run() { while (true) { waitMessage(); @@ -711,66 +689,86 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals return consideredSignals; } -void Scheduler::registerDisplay(const sp& display) { - const bool ok = mDisplays.try_emplace(display->getPhysicalId(), display).second; - ALOGE_IF(!ok, "Duplicate display registered"); -} - -void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { - mDisplays.erase(displayId); -} - std::vector Scheduler::getBestDisplayModeConfigs() const { ATRACE_CALL(); - std::vector refreshRateRankingsAndSignalsPerDisplay; - refreshRateRankingsAndSignalsPerDisplay.reserve(mDisplays.size()); + using Rankings = std::pair, GlobalSignals>; + display::PhysicalDisplayVector perDisplayRankings; + + // Tallies the score of a refresh rate across `displayCount` displays. + struct RefreshRateTally { + explicit RefreshRateTally(float score) : score(score) {} + + float score; + size_t displayCount = 1; + }; + + // Chosen to exceed a typical number of refresh rates across displays. + constexpr size_t kStaticCapacity = 8; + ftl::SmallMap refreshRateTallies; + + const auto globalSignals = makeGlobalSignals(); for (const auto& [id, display] : mDisplays) { - const auto [rankings, signals] = + auto [rankings, signals] = display->holdRefreshRateConfigs() - ->getRankedRefreshRates(mPolicy.contentRequirements, makeGlobalSignals()); + ->getRankedRefreshRates(mPolicy.contentRequirements, globalSignals); + + for (const auto& [modePtr, score] : rankings) { + const auto [it, inserted] = refreshRateTallies.try_emplace(modePtr->getFps(), score); + + if (!inserted) { + auto& tally = it->second; + tally.score += score; + tally.displayCount++; + } + } - refreshRateRankingsAndSignalsPerDisplay.emplace_back( - RefreshRateRankingsAndSignals{rankings, signals}); + perDisplayRankings.emplace_back(std::move(rankings), signals); } - // FPS and their Aggregated score. - std::unordered_map aggregatedScoresPerFps = - getAggregatedScoresPerFps(refreshRateRankingsAndSignalsPerDisplay); + auto maxScoreIt = refreshRateTallies.cbegin(); + + // Find the first refresh rate common to all displays. + while (maxScoreIt != refreshRateTallies.cend() && + maxScoreIt->second.displayCount != mDisplays.size()) { + ++maxScoreIt; + } - auto maxScoreIt = aggregatedScoresPerFps.cbegin(); - // Selects the max Fps that is present on all the displays. - for (auto it = aggregatedScoresPerFps.cbegin(); it != aggregatedScoresPerFps.cend(); ++it) { - const auto [fps, aggregatedScore] = *it; - if (aggregatedScore.numDisplays == mDisplays.size() && - aggregatedScore.totalScore >= maxScoreIt->second.totalScore) { - maxScoreIt = it; + if (maxScoreIt != refreshRateTallies.cend()) { + // Choose the highest refresh rate common to all displays, if any. + for (auto it = maxScoreIt + 1; it != refreshRateTallies.cend(); ++it) { + const auto [fps, tally] = *it; + + if (tally.displayCount == mDisplays.size() && tally.score > maxScoreIt->second.score) { + maxScoreIt = it; + } } } - return getDisplayModeConfigsForTheChosenFps(maxScoreIt->first, - refreshRateRankingsAndSignalsPerDisplay); -} -std::vector Scheduler::getDisplayModeConfigsForTheChosenFps( - Fps chosenFps, - const std::vector& refreshRateRankingsAndSignalsPerDisplay) - const { + const std::optional chosenFps = maxScoreIt != refreshRateTallies.cend() + ? std::make_optional(maxScoreIt->first) + : std::nullopt; + std::vector displayModeConfigs; displayModeConfigs.reserve(mDisplays.size()); + using fps_approx_ops::operator==; - std::for_each(refreshRateRankingsAndSignalsPerDisplay.begin(), - refreshRateRankingsAndSignalsPerDisplay.end(), - [&](const auto& refreshRateRankingsAndSignal) { - for (const auto& ranking : refreshRateRankingsAndSignal.refreshRateRankings) { - if (ranking.displayModePtr->getFps() == chosenFps) { - displayModeConfigs.emplace_back( - DisplayModeConfig{refreshRateRankingsAndSignal.globalSignals, - ranking.displayModePtr}); - break; - } - } - }); + + for (const auto& [rankings, signals] : perDisplayRankings) { + if (!chosenFps) { + displayModeConfigs.emplace_back(signals, rankings.front().displayModePtr); + continue; + } + + for (const auto& ranking : rankings) { + const auto& modePtr = ranking.displayModePtr; + if (modePtr->getFps() == *chosenFps) { + displayModeConfigs.emplace_back(signals, modePtr); + break; + } + } + } return displayModeConfigs; } diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 25fa714fcb..6633b0500a 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -33,11 +33,12 @@ #include #pragma clang diagnostic pop // ignored "-Wconversion -Wextra" -#include #include #include +#include #include "Display/DisplayMap.h" +#include "DisplayDevice.h" #include "EventThread.h" #include "FrameRateOverrideMappings.h" #include "LayerHistory.h" @@ -108,19 +109,6 @@ protected: ~ISchedulerCallback() = default; }; -// Holds the total score of the FPS and -// number of displays the FPS is found in. -struct AggregatedFpsScore { - float totalScore; - size_t numDisplays; -}; - -// Represents the RefreshRateRankings and GlobalSignals for the selected RefreshRateRankings. -struct RefreshRateRankingsAndSignals { - std::vector refreshRateRankings; - GlobalSignals globalSignals; -}; - class Scheduler : android::impl::MessageQueue { using Impl = android::impl::MessageQueue; @@ -132,6 +120,9 @@ public: void setRefreshRateConfigs(std::shared_ptr) EXCLUDES(mRefreshRateConfigsLock); + void registerDisplay(sp); + void unregisterDisplay(PhysicalDisplayId); + void run(); void createVsyncSchedule(FeatureFlags); @@ -257,9 +248,6 @@ public: return mLayerHistory.getLayerFramerate(now, id); } - void registerDisplay(const sp&); - void unregisterDisplay(PhysicalDisplayId); - private: friend class TestableScheduler; @@ -293,10 +281,6 @@ private: // Returns the best display mode per display. std::vector getBestDisplayModeConfigs() const REQUIRES(mPolicyLock); - // Returns the list of DisplayModeConfigs per display for the chosenFps. - std::vector getDisplayModeConfigsForTheChosenFps( - Fps chosenFps, const std::vector&) const; - GlobalSignals makeGlobalSignals() const REQUIRES(mPolicyLock); bool updateFrameRateOverrides(GlobalSignals, Fps displayRefreshRate) REQUIRES(mPolicyLock); @@ -344,9 +328,7 @@ private: mutable std::mutex mPolicyLock; - // Holds the Physical displays registered through the SurfaceFlinger, used for making - // the refresh rate selections. - display::PhysicalDisplayMap> mDisplays; + display::PhysicalDisplayMap> mDisplays; struct Policy { // Policy for choosing the display mode. diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h index 2c77142aa8..bd4f40989d 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h @@ -138,10 +138,6 @@ struct FpsApproxEqual { bool operator()(Fps lhs, Fps rhs) const { return isApproxEqual(lhs, rhs); } }; -struct FpsHash { - size_t operator()(Fps fps) const { return std::hash()(fps.getPeriodNsecs()); } -}; - inline std::string to_string(Fps fps) { return base::StringPrintf("%.2f Hz", fps.getValue()); } -- GitLab From e14c6b35f3af8817381760875139ce0548d9b5d6 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Sat, 6 Aug 2022 04:20:15 +0000 Subject: [PATCH 0373/1310] SF: Split FE and CE Make LayerFE a standalone class with no links back to Layer. Pass LayerSnapshot from Layer into LayerFE before CompositionEngine::present and back after. Bug: 238781169 Test: go/wm-smoke Test: presubmit Change-Id: I5395fb717a931f88e2bf26395acd21e8b308961e --- services/surfaceflinger/Android.bp | 1 + .../include/compositionengine/LayerFE.h | 3 + .../impl/planner/LayerState.h | 15 +- .../CompositionEngine/src/Output.cpp | 10 +- .../src/planner/CachedSet.cpp | 8 +- .../CompositionEngine/tests/OutputTest.cpp | 22 + services/surfaceflinger/Layer.cpp | 322 ++------------- services/surfaceflinger/Layer.h | 94 +---- services/surfaceflinger/LayerFE.cpp | 386 ++++++++++++++++++ services/surfaceflinger/LayerFE.h | 118 ++++++ services/surfaceflinger/LayerRenderArea.cpp | 1 + services/surfaceflinger/SurfaceFlinger.cpp | 43 +- .../SurfaceFlingerDefaultFactory.cpp | 4 + .../SurfaceFlingerDefaultFactory.h | 1 + .../surfaceflinger/SurfaceFlingerFactory.h | 2 + .../Tracing/tools/LayerTraceGenerator.cpp | 2 + .../fuzzer/surfaceflinger_fuzzers_utils.h | 4 + .../fuzzer/surfaceflinger_layer_fuzzer.cpp | 3 +- .../tests/unittests/TestableSurfaceFlinger.h | 4 + 19 files changed, 661 insertions(+), 382 deletions(-) create mode 100644 services/surfaceflinger/LayerFE.cpp create mode 100644 services/surfaceflinger/LayerFE.h diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index 8a76d7c3f8..67a226e24c 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -163,6 +163,7 @@ filegroup { "HwcSlotGenerator.cpp", "WindowInfosListenerInvoker.cpp", "Layer.cpp", + "LayerFE.cpp", "LayerProtoHelper.cpp", "LayerRenderArea.cpp", "LayerVector.cpp", diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h index fe8cad502b..608c53a21a 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h @@ -118,6 +118,9 @@ public: // Requested white point of the layer in nits const float whitePointNits; + + // True if layers with 170M dataspace should be overridden to sRGB. + const bool treat170mAsSrgb; }; // A superset of LayerSettings required by RenderEngine to compose a layer diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h index 5aec7c2e88..e309442220 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h @@ -72,6 +72,7 @@ enum class LayerStateField : uint32_t { SolidColor = 1u << 16, BackgroundBlurRadius = 1u << 17, BlurRegions = 1u << 18, + HasProtectedContent = 1u << 19, }; // clang-format on @@ -245,9 +246,9 @@ public: ui::Dataspace getDataspace() const { return mOutputDataspace.get(); } - bool isProtected() const { - return getOutputLayer()->getLayerFE().getCompositionState()->hasProtectedContent; - } + wp getBuffer() const { return mBuffer.get(); } + + bool isProtected() const { return mIsProtected.get(); } bool hasSolidColorCompositionType() const { return getOutputLayer()->getLayerFE().getCompositionState()->compositionType == @@ -482,7 +483,11 @@ private: return hash; }}; - static const constexpr size_t kNumNonUniqueFields = 17; + OutputLayerState mIsProtected{[](auto layer) { + return layer->getLayerFE().getCompositionState()->hasProtectedContent; + }}; + + static const constexpr size_t kNumNonUniqueFields = 18; std::array getNonUniqueFields() { std::array constFields = @@ -501,7 +506,7 @@ private: &mAlpha, &mLayerMetadata, &mVisibleRegion, &mOutputDataspace, &mPixelFormat, &mColorTransform, &mCompositionType, &mSidebandStream, &mBuffer, &mSolidColor, &mBackgroundBlurRadius, &mBlurRegions, - &mFrameNumber, + &mFrameNumber, &mIsProtected, }; } }; diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index e3f3680c1c..0622534491 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -900,6 +900,13 @@ ui::Dataspace Output::getBestDataspace(ui::Dataspace* outHdrDataSpace, ui::Dataspace bestDataSpace = ui::Dataspace::V0_SRGB; *outHdrDataSpace = ui::Dataspace::UNKNOWN; + // An Output's layers may be stale when it is disabled. As a consequence, the layers returned by + // getOutputLayersOrderedByZ may not be in a valid state and it is not safe to access their + // properties. Return a default dataspace value in this case. + if (!getState().isEnabled) { + return ui::Dataspace::V0_SRGB; + } + for (const auto* layer : getOutputLayersOrderedByZ()) { switch (layer->getLayerFE().getCompositionState()->dataspace) { case ui::Dataspace::V0_SCRGB: @@ -1420,7 +1427,8 @@ std::vector Output::generateClientCompositionRequests( .realContentIsVisible = realContentIsVisible, .clearContent = !clientComposition, .blurSetting = blurSetting, - .whitePointNits = layerState.whitePointNits}; + .whitePointNits = layerState.whitePointNits, + .treat170mAsSrgb = outputState.treat170mAsSrgb}; if (auto clientCompositionSettings = layerFE.prepareClientComposition(targetSettings)) { clientCompositionLayers.push_back(std::move(*clientCompositionSettings)); diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp index 0731d4899c..ed9a88d3c4 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp @@ -411,8 +411,8 @@ void CachedSet::dump(std::string& result) const { if (mLayers.size() == 1) { base::StringAppendF(&result, " Layer [%s]\n", mLayers[0].getName().c_str()); - if (auto* buffer = mLayers[0].getBuffer().get()) { - base::StringAppendF(&result, " Buffer %p", buffer); + if (const sp buffer = mLayers[0].getState()->getBuffer().promote()) { + base::StringAppendF(&result, " Buffer %p", buffer.get()); base::StringAppendF(&result, " Format %s", decodePixelFormat(buffer->getPixelFormat()).c_str()); } @@ -422,8 +422,8 @@ void CachedSet::dump(std::string& result) const { result.append(" Cached set of:\n"); for (const Layer& layer : mLayers) { base::StringAppendF(&result, " Layer [%s]\n", layer.getName().c_str()); - if (auto* buffer = layer.getBuffer().get()) { - base::StringAppendF(&result, " Buffer %p", buffer); + if (const sp buffer = layer.getState()->getBuffer().promote()) { + base::StringAppendF(&result, " Buffer %p", buffer.get()); base::StringAppendF(&result, " Format[%s]", decodePixelFormat(buffer->getPixelFormat()).c_str()); } diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index eb209e9b72..514a8ff8fc 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -2081,6 +2081,7 @@ struct OutputUpdateColorProfileTest : public testing::Test { mOutput.setDisplayColorProfileForTest( std::unique_ptr(mDisplayColorProfile)); mOutput.setRenderSurfaceForTest(std::unique_ptr(mRenderSurface)); + mOutput.editState().isEnabled = true; EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0)) .WillRepeatedly(Return(&mLayer1.mOutputLayer)); @@ -4464,6 +4465,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, clearsHWCLayersIfOpaqu true /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{ Region(kDisplayFrame), @@ -4476,6 +4478,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, clearsHWCLayersIfOpaqu false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; LayerFE::LayerSettings mBlackoutSettings = mLayers[1].mLayerSettings; @@ -4516,6 +4519,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{ Region(Rect(0, 0, 30, 30)), @@ -4528,6 +4532,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{ Region(Rect(0, 0, 40, 201)), @@ -4540,6 +4545,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings)))) @@ -4570,6 +4576,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{ Region(kDisplayFrame), @@ -4582,6 +4589,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{ Region(kDisplayFrame), @@ -4594,6 +4602,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings)))) @@ -4624,6 +4633,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{ Region(kDisplayFrame), @@ -4636,6 +4646,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{ Region(kDisplayFrame), @@ -4648,6 +4659,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings)))) @@ -4677,6 +4689,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{ Region(kDisplayFrame), @@ -4689,6 +4702,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{ Region(kDisplayFrame), @@ -4701,6 +4715,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings)))) @@ -4728,6 +4743,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{ Region(kDisplayFrame), @@ -4740,6 +4756,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{ Region(kDisplayFrame), @@ -4752,6 +4769,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings)))) @@ -4936,6 +4954,7 @@ TEST_F(GenerateClientCompositionRequestsTest, handlesLandscapeModeSplitScreenReq false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; EXPECT_CALL(leftLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true)); @@ -4954,6 +4973,7 @@ TEST_F(GenerateClientCompositionRequestsTest, handlesLandscapeModeSplitScreenReq false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; EXPECT_CALL(rightLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true)); @@ -4987,6 +5007,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; LayerFE::LayerSettings mShadowSettings; @@ -5029,6 +5050,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, false /* clearContent */, compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, kLayerWhitePointNits, + false /* treat170mAsSrgb */, }; EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false)); diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 9bb93050dc..c2a0e3082d 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -40,7 +40,6 @@ #include #include #include -#include #include #include #include @@ -81,28 +80,8 @@ namespace android { namespace { constexpr int kDumpTableRowLength = 159; -static constexpr float defaultMaxLuminance = 1000.0; - const ui::Transform kIdentityTransform; -constexpr mat4 inverseOrientation(uint32_t transform) { - const mat4 flipH(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1); - const mat4 flipV(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1); - const mat4 rot90(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1); - mat4 tr; - - if (transform & NATIVE_WINDOW_TRANSFORM_ROT_90) { - tr = tr * rot90; - } - if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_H) { - tr = tr * flipH; - } - if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_V) { - tr = tr * flipV; - } - return inverse(tr); -} - bool assignTransform(ui::Transform* dst, ui::Transform& from) { if (*dst == from) { return false; @@ -164,7 +143,8 @@ Layer::Layer(const LayerCreationArgs& args) mLayerCreationFlags(args.flags), mBorderEnabled(false), mTextureName(args.textureName), - mHwcSlotGenerator(sp::make()) { + mHwcSlotGenerator(sp::make()), + mLayerFE(args.flinger->getFactory().createLayerFE(mName)) { ALOGV("Creating Layer %s", getDebugName()); uint32_t layerFlags = 0; @@ -652,12 +632,6 @@ void Layer::prepareCursorCompositionState() { snapshot->cursorFrame = frame; } -sp Layer::asLayerFE() const { - compositionengine::LayerFE* layerFE = const_cast( - static_cast(this)); - return sp::fromExisting(layerFE); -} - const char* Layer::getDebugName() const { return mName.c_str(); } @@ -666,237 +640,6 @@ const char* Layer::getDebugName() const { // drawing... // --------------------------------------------------------------------------- -std::optional Layer::prepareClientComposition( - compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { - std::optional layerSettings = - prepareClientCompositionInternal(targetSettings); - // Nothing to render. - if (!layerSettings) { - return {}; - } - - // HWC requests to clear this layer. - if (targetSettings.clearContent) { - prepareClearClientComposition(*layerSettings, false /* blackout */); - return layerSettings; - } - - // set the shadow for the layer if needed - prepareShadowClientComposition(*layerSettings, targetSettings.viewport); - - return layerSettings; -} - -std::optional Layer::prepareClientCompositionInternal( - compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { - ATRACE_CALL(); - - const auto* snapshot = getLayerSnapshot(); - if (!snapshot) { - return {}; - } - - compositionengine::LayerFE::LayerSettings layerSettings; - layerSettings.geometry.boundaries = - reduce(snapshot->geomLayerBounds, snapshot->transparentRegionHint); - layerSettings.geometry.positionTransform = snapshot->geomLayerTransform.asMatrix4(); - - // skip drawing content if the targetSettings indicate the content will be occluded - const bool drawContent = targetSettings.realContentIsVisible || targetSettings.clearContent; - layerSettings.skipContentDraw = !drawContent; - - if (hasColorTransform()) { - layerSettings.colorTransform = snapshot->colorTransform; - } - - const auto& roundedCornerState = snapshot->roundedCorner; - layerSettings.geometry.roundedCornersRadius = roundedCornerState.radius; - layerSettings.geometry.roundedCornersCrop = roundedCornerState.cropRect; - - layerSettings.alpha = snapshot->alpha; - layerSettings.sourceDataspace = snapshot->dataspace; - - // Override the dataspace transfer from 170M to sRGB if the device configuration requests this. - // We do this here instead of in buffer info so that dumpsys can still report layers that are - // using the 170M transfer. - if (mFlinger->mTreat170mAsSrgb && - (layerSettings.sourceDataspace & HAL_DATASPACE_TRANSFER_MASK) == - HAL_DATASPACE_TRANSFER_SMPTE_170M) { - layerSettings.sourceDataspace = static_cast( - (layerSettings.sourceDataspace & HAL_DATASPACE_STANDARD_MASK) | - (layerSettings.sourceDataspace & HAL_DATASPACE_RANGE_MASK) | - HAL_DATASPACE_TRANSFER_SRGB); - } - - layerSettings.whitePointNits = targetSettings.whitePointNits; - switch (targetSettings.blurSetting) { - case LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled: - layerSettings.backgroundBlurRadius = snapshot->backgroundBlurRadius; - layerSettings.blurRegions = snapshot->blurRegions; - layerSettings.blurRegionTransform = snapshot->geomInverseLayerTransform.asMatrix4(); - break; - case LayerFE::ClientCompositionTargetSettings::BlurSetting::BackgroundBlurOnly: - layerSettings.backgroundBlurRadius = snapshot->backgroundBlurRadius; - break; - case LayerFE::ClientCompositionTargetSettings::BlurSetting::BlurRegionsOnly: - layerSettings.blurRegions = snapshot->blurRegions; - layerSettings.blurRegionTransform = snapshot->geomInverseLayerTransform.asMatrix4(); - break; - case LayerFE::ClientCompositionTargetSettings::BlurSetting::Disabled: - default: - break; - } - layerSettings.stretchEffect = snapshot->stretchEffect; - // Record the name of the layer for debugging further down the stack. - layerSettings.name = snapshot->name; - - if (hasEffect() && !hasBufferOrSidebandStream()) { - prepareEffectsClientComposition(layerSettings, targetSettings); - return layerSettings; - } - - prepareBufferStateClientComposition(layerSettings, targetSettings); - return layerSettings; -} - -void Layer::prepareClearClientComposition(LayerFE::LayerSettings& layerSettings, - bool blackout) const { - layerSettings.source.buffer.buffer = nullptr; - layerSettings.source.solidColor = half3(0.0, 0.0, 0.0); - layerSettings.disableBlending = true; - layerSettings.bufferId = 0; - layerSettings.frameNumber = 0; - - // If layer is blacked out, force alpha to 1 so that we draw a black color layer. - layerSettings.alpha = blackout ? 1.0f : 0.0f; - layerSettings.name = getLayerSnapshot()->name; -} - -void Layer::prepareEffectsClientComposition( - compositionengine::LayerFE::LayerSettings& layerSettings, - compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { - // If fill bounds are occluded or the fill color is invalid skip the fill settings. - if (targetSettings.realContentIsVisible && fillsColor()) { - // Set color for color fill settings. - layerSettings.source.solidColor = getColor().rgb; - } else if (hasBlur() || drawShadows()) { - layerSettings.skipContentDraw = true; - } -} - -void Layer::prepareBufferStateClientComposition( - compositionengine::LayerFE::LayerSettings& layerSettings, - compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { - ATRACE_CALL(); - const auto* snapshot = getLayerSnapshot(); - if (CC_UNLIKELY(!snapshot->externalTexture)) { - // If there is no buffer for the layer or we have sidebandstream where there is no - // activeBuffer, then we need to return LayerSettings. - return; - } - const bool blackOutLayer = - (snapshot->hasProtectedContent && !targetSettings.supportsProtectedContent) || - ((snapshot->isSecure || snapshot->hasProtectedContent) && !targetSettings.isSecure); - const bool bufferCanBeUsedAsHwTexture = - snapshot->externalTexture->getUsage() & GraphicBuffer::USAGE_HW_TEXTURE; - if (blackOutLayer || !bufferCanBeUsedAsHwTexture) { - ALOGE_IF(!bufferCanBeUsedAsHwTexture, "%s is blacked out as buffer is not gpu readable", - snapshot->name.c_str()); - prepareClearClientComposition(layerSettings, true /* blackout */); - return; - } - - layerSettings.source.buffer.buffer = snapshot->externalTexture; - layerSettings.source.buffer.isOpaque = snapshot->contentOpaque; - layerSettings.source.buffer.fence = snapshot->acquireFence; - layerSettings.source.buffer.textureName = snapshot->textureName; - layerSettings.source.buffer.usePremultipliedAlpha = snapshot->premultipliedAlpha; - layerSettings.source.buffer.isY410BT2020 = snapshot->isHdrY410; - bool hasSmpte2086 = snapshot->hdrMetadata.validTypes & HdrMetadata::SMPTE2086; - bool hasCta861_3 = snapshot->hdrMetadata.validTypes & HdrMetadata::CTA861_3; - float maxLuminance = 0.f; - if (hasSmpte2086 && hasCta861_3) { - maxLuminance = std::min(snapshot->hdrMetadata.smpte2086.maxLuminance, - snapshot->hdrMetadata.cta8613.maxContentLightLevel); - } else if (hasSmpte2086) { - maxLuminance = snapshot->hdrMetadata.smpte2086.maxLuminance; - } else if (hasCta861_3) { - maxLuminance = snapshot->hdrMetadata.cta8613.maxContentLightLevel; - } else { - switch (layerSettings.sourceDataspace & HAL_DATASPACE_TRANSFER_MASK) { - case HAL_DATASPACE_TRANSFER_ST2084: - case HAL_DATASPACE_TRANSFER_HLG: - // Behavior-match previous releases for HDR content - maxLuminance = defaultMaxLuminance; - break; - } - } - layerSettings.source.buffer.maxLuminanceNits = maxLuminance; - layerSettings.frameNumber = snapshot->frameNumber; - layerSettings.bufferId = snapshot->externalTexture->getId(); - - const bool useFiltering = targetSettings.needsFiltering || - snapshot->geomLayerTransform.needsBilinearFiltering() || snapshot->bufferNeedsFiltering; - - // Query the texture matrix given our current filtering mode. - float textureMatrix[16]; - getDrawingTransformMatrix(useFiltering, textureMatrix); - - if (snapshot->geomBufferUsesDisplayInverseTransform) { - /* - * the code below applies the primary display's inverse transform to - * the texture transform - */ - uint32_t transform = DisplayDevice::getPrimaryDisplayRotationFlags(); - mat4 tr = inverseOrientation(transform); - - /** - * TODO(b/36727915): This is basically a hack. - * - * Ensure that regardless of the parent transformation, - * this buffer is always transformed from native display - * orientation to display orientation. For example, in the case - * of a camera where the buffer remains in native orientation, - * we want the pixels to always be upright. - */ - const auto parentTransform = snapshot->transform; - tr = tr * inverseOrientation(parentTransform.getOrientation()); - - // and finally apply it to the original texture matrix - const mat4 texTransform(mat4(static_cast(textureMatrix)) * tr); - memcpy(textureMatrix, texTransform.asArray(), sizeof(textureMatrix)); - } - - const Rect win{layerSettings.geometry.boundaries}; - float bufferWidth = snapshot->bufferSize.getWidth(); - float bufferHeight = snapshot->bufferSize.getHeight(); - - // Layers can have a "buffer size" of [0, 0, -1, -1] when no display frame has - // been set and there is no parent layer bounds. In that case, the scale is meaningless so - // ignore them. - if (!snapshot->bufferSize.isValid()) { - bufferWidth = float(win.right) - float(win.left); - bufferHeight = float(win.bottom) - float(win.top); - } - - const float scaleHeight = (float(win.bottom) - float(win.top)) / bufferHeight; - const float scaleWidth = (float(win.right) - float(win.left)) / bufferWidth; - const float translateY = float(win.top) / bufferHeight; - const float translateX = float(win.left) / bufferWidth; - - // Flip y-coordinates because GLConsumer expects OpenGL convention. - mat4 tr = mat4::translate(vec4(.5f, .5f, 0.f, 1.f)) * mat4::scale(vec4(1.f, -1.f, 1.f, 1.f)) * - mat4::translate(vec4(-.5f, -.5f, 0.f, 1.f)) * - mat4::translate(vec4(translateX, translateY, 0.f, 1.f)) * - mat4::scale(vec4(scaleWidth, scaleHeight, 1.0f, 1.0f)); - - layerSettings.source.buffer.useTextureFiltering = useFiltering; - layerSettings.source.buffer.textureTransform = - mat4(static_cast(textureMatrix)) * tr; - - return; -} - aidl::android::hardware::graphics::composer3::Composition Layer::getCompositionType( const DisplayDevice& display) const { const auto outputLayer = findOutputLayerForDisplay(&display); @@ -1816,6 +1559,8 @@ void Layer::setChildrenDrawingParent(const sp& newParent) { newParent->canDrawShadows() ? 0.f : newParent->mEffectiveShadowRadius; child->computeBounds(newParent->mBounds, newParent->mEffectiveTransform, parentShadowRadius); + child->updateSnapshot(true /* updateGeometry */); + child->updateChildrenSnapshots(true /* updateGeometry */); } } @@ -2139,7 +1884,7 @@ const std::vector Layer::getBlurRegions() const { return regionsCopy; } -Layer::RoundedCornerState Layer::getRoundedCornerState() const { +RoundedCornerState Layer::getRoundedCornerState() const { // Get parent settings RoundedCornerState parentSettings; const auto& parent = mDrawingParent.promote(); @@ -2180,21 +1925,6 @@ Layer::RoundedCornerState Layer::getRoundedCornerState() const { return {}; } -void Layer::prepareShadowClientComposition(LayerFE::LayerSettings& caster, - const Rect& layerStackRect) const { - const auto* snapshot = getLayerSnapshot(); - renderengine::ShadowSettings state = snapshot->shadowSettings; - if (state.length <= 0.f || (state.ambientColor.a <= 0.f && state.spotColor.a <= 0.f)) { - return; - } - - // Shift the spot light x-position to the middle of the display and then - // offset it by casting layer's screen pos. - state.lightPos.x = (layerStackRect.width() / 2.f) - snapshot->transformedBounds.left; - state.lightPos.y -= snapshot->transformedBounds.top; - caster.shadow = state; -} - bool Layer::findInHierarchy(const sp& l) { if (l == this) { return true; @@ -3377,7 +3107,7 @@ bool Layer::fenceHasSignaled() const { return fenceSignaled; } -bool Layer::onPreComposition(nsecs_t refreshStartTime, bool /* updatingOutputGeometryThisFrame */) { +bool Layer::onPreComposition(nsecs_t refreshStartTime) { for (const auto& handle : mDrawingState.callbackHandles) { handle->refreshStartTime = refreshStartTime; } @@ -3788,26 +3518,31 @@ bool Layer::isHdrY410() const { mBufferInfo.mPixelFormat == HAL_PIXEL_FORMAT_RGBA_1010102); } -sp Layer::getCompositionEngineLayerFE() const { +sp Layer::getCompositionEngineLayerFE() const { // There's no need to get a CE Layer if the layer isn't going to draw anything. - if (hasSomethingToDraw()) { - return asLayerFE(); - } else { - return nullptr; - } + return hasSomethingToDraw() ? mLayerFE : nullptr; } -const Layer::LayerSnapshot* Layer::getLayerSnapshot() const { +const LayerSnapshot* Layer::getLayerSnapshot() const { return mSnapshot.get(); } -Layer::LayerSnapshot* Layer::editLayerSnapshot() { +LayerSnapshot* Layer::editLayerSnapshot() { return mSnapshot.get(); } + const compositionengine::LayerFECompositionState* Layer::getCompositionState() const { return mSnapshot.get(); } +void Layer::moveSnapshotToLayerFE() { + mLayerFE->mSnapshot = std::move(mSnapshot); +} + +void Layer::moveSnapshotToLayer() { + mSnapshot = std::move(mLayerFE->mSnapshot); +} + void Layer::useSurfaceDamage() { if (mFlinger->mForceFullDamage) { surfaceDamageRegion = Region::INVALID_REGION; @@ -4154,17 +3889,6 @@ sp Layer::getBuffer() const { return mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getBuffer() : nullptr; } -void Layer::getDrawingTransformMatrix(bool filteringEnabled, float outMatrix[16]) const { - sp buffer = getBuffer(); - if (!buffer) { - ALOGE("Buffer should not be null!"); - return; - } - GLConsumer::computeTransformMatrix(outMatrix, buffer->getWidth(), buffer->getHeight(), - buffer->getPixelFormat(), mBufferInfo.mCrop, - mBufferInfo.mTransform, filteringEnabled); -} - void Layer::setTransformHint(ui::Transform::RotationFlags displayTransformHint) { mTransformHint = getFixedTransformHint(); if (mTransformHint == ui::Transform::ROT_INVALID) { @@ -4240,9 +3964,17 @@ void Layer::updateSnapshot(bool updateGeometry) { } snapshot->bufferSize = getBufferSize(mDrawingState); snapshot->externalTexture = mBufferInfo.mBuffer; + snapshot->hasReadyFrame = hasReadyFrame(); preparePerFrameCompositionState(); } +void Layer::updateChildrenSnapshots(bool updateGeometry) { + for (const sp& child : mDrawingChildren) { + child->updateSnapshot(updateGeometry); + child->updateChildrenSnapshots(updateGeometry); + } +} + void Layer::updateMetadataSnapshot(const LayerMetadata& parentMetadata) { mSnapshot->layerMetadata = parentMetadata; mSnapshot->layerMetadata.merge(mDrawingState.metadata); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 8ace8123f9..da8be6b8f0 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -52,6 +52,7 @@ #include "DisplayHardware/HWComposer.h" #include "FrameTracker.h" #include "HwcSlotGenerator.h" +#include "LayerFE.h" #include "LayerVector.h" #include "Scheduler/LayerInfo.h" #include "SurfaceFlinger.h" @@ -97,7 +98,7 @@ struct LayerCreationArgs { bool addToRoot = true; }; -class Layer : public virtual RefBase, compositionengine::LayerFE { +class Layer : public virtual RefBase { static std::atomic sSequence; // The following constants represent priority of the window. SF uses this information when // deciding which window has a priority when deciding about the refresh rate of the screen. @@ -129,43 +130,6 @@ public: inline bool operator!=(const Geometry& rhs) const { return !operator==(rhs); } }; - struct RoundedCornerState { - RoundedCornerState() = default; - RoundedCornerState(const FloatRect& cropRect, const vec2& radius) - : cropRect(cropRect), radius(radius) {} - - // Rounded rectangle in local layer coordinate space. - FloatRect cropRect = FloatRect(); - // Radius of the rounded rectangle. - vec2 radius; - bool hasRoundedCorners() const { return radius.x > 0.0f && radius.y > 0.0f; } - }; - - // LayerSnapshot stores Layer state used by Composition Engine and Render Engine. Composition - // Engine uses a pointer to LayerSnapshot (as LayerFECompositionState*) and the LayerSettings - // passed to Render Engine are created using properties stored on this struct. - // - // TODO(b/238781169) Implement LayerFE as a separate subclass. Migrate LayerSnapshot to that - // LayerFE subclass. - struct LayerSnapshot : public compositionengine::LayerFECompositionState { - int32_t sequence; - std::string name; - uint32_t textureName; - bool contentOpaque; - RoundedCornerState roundedCorner; - StretchEffect stretchEffect; - FloatRect transformedBounds; - renderengine::ShadowSettings shadowSettings; - bool premultipliedAlpha; - bool isHdrY410; - bool bufferNeedsFiltering; - ui::Transform transform; - Rect bufferSize; - std::shared_ptr externalTexture; - LayerMetadata layerMetadata; - LayerMetadata relativeLayerMetadata; - }; - using FrameRate = scheduler::LayerInfo::FrameRate; using FrameRateCompatibility = scheduler::LayerInfo::FrameRateCompatibility; @@ -413,7 +377,16 @@ public: ui::Dataspace getDataSpace() const; ui::Dataspace getRequestedDataSpace() const; - virtual sp getCompositionEngineLayerFE() const; + virtual sp getCompositionEngineLayerFE() const; + + // Move LayerSnapshot from this layer into its LayerFE. This must be called before passing the + // LayerFE to CompositionEngine. Moving the snapshot instead of sharing common state + // prevents use of LayerFE outside the main thread by making errors obvious (i.e. use outside + // the main thread results in SEGFAULTs due to nullptr dereference). + void moveSnapshotToLayerFE(); + // Move LayerSnapshot into this layer from its LayerFE. This must be called after + // CompositionEngine has presented the layer. + void moveSnapshotToLayer(); const LayerSnapshot* getLayerSnapshot() const; LayerSnapshot* editLayerSnapshot(); @@ -557,7 +530,7 @@ public: // corner crop does not intersect with its own rounded corner crop. virtual RoundedCornerState getRoundedCornerState() const; - bool hasRoundedCorners() const override { return getRoundedCornerState().hasRoundedCorners(); } + bool hasRoundedCorners() const { return getRoundedCornerState().hasRoundedCorners(); } PixelFormat getPixelFormat() const; /** @@ -598,24 +571,15 @@ public: // implements compositionengine::LayerFE const compositionengine::LayerFECompositionState* getCompositionState() const; bool fenceHasSignaled() const; - // Called before composition. updatingOutputGeometryThisFrame is used by ARC++'s Layer subclass. - bool onPreComposition(nsecs_t refreshStartTime, bool updatingOutputGeometryThisFrame); - std::optional prepareClientComposition( - compositionengine::LayerFE::ClientCompositionTargetSettings&) const override; + bool onPreComposition(nsecs_t refreshStartTime); void onLayerDisplayed(ftl::SharedFuture); - void setWasClientComposed(const sp& fence) override { + void setWasClientComposed(const sp& fence) { mLastClientCompositionFence = fence; mClearClientCompositionFenceOnLayerDisplayed = false; } - const LayerMetadata* getMetadata() const override { return &mSnapshot->layerMetadata; } - - const LayerMetadata* getRelativeMetadata() const override { - return &mSnapshot->relativeLayerMetadata; - } - - const char* getDebugName() const override; + const char* getDebugName() const; bool setShadowRadius(float shadowRadius); @@ -640,7 +604,7 @@ public: // Compute bounds for the layer and cache the results. void computeBounds(FloatRect parentBounds, ui::Transform parentTransform, float shadowRadius); - int32_t getSequence() const override { return sequence; } + int32_t getSequence() const { return sequence; } // For tracing. // TODO: Replace with raw buffer id from buffer metadata when that becomes available. @@ -902,7 +866,7 @@ public: // Updates the LayerSnapshot. This must be called prior to sending layer data to // CompositionEngine or RenderEngine (i.e. before calling CompositionEngine::present or - // Layer::prepareClientComposition). + // LayerFE::prepareClientComposition). // // TODO(b/238781169) Remove direct calls to RenderEngine::drawLayers that don't go through // CompositionEngine to create a single path for composing layers. @@ -928,7 +892,6 @@ protected: void gatherBufferInfo(); void onSurfaceFrameCreated(const std::shared_ptr&); - sp asLayerFE() const; sp getClonedFrom() { return mClonedFrom != nullptr ? mClonedFrom.promote() : nullptr; } bool isClone() { return mClonedFrom != nullptr; } bool isClonedFromAlive() { return getClonedFrom() != nullptr; } @@ -941,12 +904,6 @@ protected: void addChildToDrawing(const sp&); void updateClonedInputInfo(const std::map, sp>& clonedLayersMap); - // Modifies the passed in layer settings to clear the contents. If the blackout flag is set, - // the settings clears the content with a solid black fill. - void prepareClearClientComposition(LayerFE::LayerSettings&, bool blackout) const; - void prepareShadowClientComposition(LayerFE::LayerSettings& caster, - const Rect& layerStackRect) const; - void prepareBasicGeometryCompositionState(); void prepareGeometryCompositionState(); void prepareCursorCompositionState(); @@ -1102,10 +1059,6 @@ private: // Fills in the frame and transform info for the gui::WindowInfo. void fillInputFrameInfo(gui::WindowInfo&, const ui::Transform& screenToDisplay); - // Computes the transform matrix using the setFilteringEnabled to determine whether the - // transform matrix should be computed for use with bilinear filtering. - void getDrawingTransformMatrix(bool filteringEnabled, float outMatrix[16]) const; - inline void tracePendingBufferCount(int32_t pendingBuffers); // Latch sideband stream and returns true if the dirty region should be updated. @@ -1129,8 +1082,6 @@ private: const sp& releaseFence, uint32_t currentMaxAcquiredBufferCount); - std::optional prepareClientCompositionInternal( - compositionengine::LayerFE::ClientCompositionTargetSettings&) const; // Returns true if there is a valid color to fill. bool fillsColor() const; // Returns true if this layer has a blur value. @@ -1141,12 +1092,8 @@ private: } bool hasSomethingToDraw() const { return hasEffect() || hasBufferOrSidebandStream(); } - void prepareBufferStateClientComposition( - compositionengine::LayerFE::LayerSettings&, - compositionengine::LayerFE::ClientCompositionTargetSettings&) const; - void prepareEffectsClientComposition( - compositionengine::LayerFE::LayerSettings&, - compositionengine::LayerFE::ClientCompositionTargetSettings&) const; + + void updateChildrenSnapshots(bool updateGeometry); // Cached properties computed from drawing state // Effective transform taking into account parent transforms and any parent scaling, which is @@ -1239,6 +1186,7 @@ private: sp mHwcSlotGenerator; + sp mLayerFE; std::unique_ptr mSnapshot = std::make_unique(); }; diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp new file mode 100644 index 0000000000..3bdb521d0e --- /dev/null +++ b/services/surfaceflinger/LayerFE.cpp @@ -0,0 +1,386 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// #define LOG_NDEBUG 0 +#undef LOG_TAG +#define LOG_TAG "LayerFE" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + +#include +#include +#include +#include +#include + +#include "DisplayDevice.h" +#include "LayerFE.h" + +namespace android { + +namespace { +constexpr float defaultMaxLuminance = 1000.0; + +constexpr mat4 inverseOrientation(uint32_t transform) { + const mat4 flipH(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1); + const mat4 flipV(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1); + const mat4 rot90(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1); + mat4 tr; + + if (transform & NATIVE_WINDOW_TRANSFORM_ROT_90) { + tr = tr * rot90; + } + if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_H) { + tr = tr * flipH; + } + if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_V) { + tr = tr * flipV; + } + return inverse(tr); +} + +FloatRect reduce(const FloatRect& win, const Region& exclude) { + if (CC_LIKELY(exclude.isEmpty())) { + return win; + } + // Convert through Rect (by rounding) for lack of FloatRegion + return Region(Rect{win}).subtract(exclude).getBounds().toFloatRect(); +} + +// Computes the transform matrix using the setFilteringEnabled to determine whether the +// transform matrix should be computed for use with bilinear filtering. +void getDrawingTransformMatrix(const std::shared_ptr& buffer, + Rect bufferCrop, uint32_t bufferTransform, bool filteringEnabled, + float outMatrix[16]) { + if (!buffer) { + ALOGE("Buffer should not be null!"); + return; + } + GLConsumer::computeTransformMatrix(outMatrix, static_cast(buffer->getWidth()), + static_cast(buffer->getHeight()), + buffer->getPixelFormat(), bufferCrop, bufferTransform, + filteringEnabled); +} + +} // namespace + +LayerFE::LayerFE(const std::string& name) : mName(name) {} + +const compositionengine::LayerFECompositionState* LayerFE::getCompositionState() const { + return mSnapshot.get(); +} + +bool LayerFE::onPreComposition(nsecs_t refreshStartTime, bool) { + mCompositionResult.refreshStartTime = refreshStartTime; + return mSnapshot->hasReadyFrame; +} + +std::optional LayerFE::prepareClientComposition( + compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { + std::optional layerSettings = + prepareClientCompositionInternal(targetSettings); + // Nothing to render. + if (!layerSettings) { + return {}; + } + + // HWC requests to clear this layer. + if (targetSettings.clearContent) { + prepareClearClientComposition(*layerSettings, false /* blackout */); + return layerSettings; + } + + // set the shadow for the layer if needed + prepareShadowClientComposition(*layerSettings, targetSettings.viewport); + + return layerSettings; +} + +std::optional LayerFE::prepareClientCompositionInternal( + compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { + ATRACE_CALL(); + compositionengine::LayerFE::LayerSettings layerSettings; + layerSettings.geometry.boundaries = + reduce(mSnapshot->geomLayerBounds, mSnapshot->transparentRegionHint); + layerSettings.geometry.positionTransform = mSnapshot->geomLayerTransform.asMatrix4(); + + // skip drawing content if the targetSettings indicate the content will be occluded + const bool drawContent = targetSettings.realContentIsVisible || targetSettings.clearContent; + layerSettings.skipContentDraw = !drawContent; + + if (!mSnapshot->colorTransformIsIdentity) { + layerSettings.colorTransform = mSnapshot->colorTransform; + } + + const auto& roundedCornerState = mSnapshot->roundedCorner; + layerSettings.geometry.roundedCornersRadius = roundedCornerState.radius; + layerSettings.geometry.roundedCornersCrop = roundedCornerState.cropRect; + + layerSettings.alpha = mSnapshot->alpha; + layerSettings.sourceDataspace = mSnapshot->dataspace; + + // Override the dataspace transfer from 170M to sRGB if the device configuration requests this. + // We do this here instead of in buffer info so that dumpsys can still report layers that are + // using the 170M transfer. + if (targetSettings.treat170mAsSrgb && + (layerSettings.sourceDataspace & HAL_DATASPACE_TRANSFER_MASK) == + HAL_DATASPACE_TRANSFER_SMPTE_170M) { + layerSettings.sourceDataspace = static_cast( + (layerSettings.sourceDataspace & HAL_DATASPACE_STANDARD_MASK) | + (layerSettings.sourceDataspace & HAL_DATASPACE_RANGE_MASK) | + HAL_DATASPACE_TRANSFER_SRGB); + } + + layerSettings.whitePointNits = targetSettings.whitePointNits; + switch (targetSettings.blurSetting) { + case LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled: + layerSettings.backgroundBlurRadius = mSnapshot->backgroundBlurRadius; + layerSettings.blurRegions = mSnapshot->blurRegions; + layerSettings.blurRegionTransform = mSnapshot->geomInverseLayerTransform.asMatrix4(); + break; + case LayerFE::ClientCompositionTargetSettings::BlurSetting::BackgroundBlurOnly: + layerSettings.backgroundBlurRadius = mSnapshot->backgroundBlurRadius; + break; + case LayerFE::ClientCompositionTargetSettings::BlurSetting::BlurRegionsOnly: + layerSettings.blurRegions = mSnapshot->blurRegions; + layerSettings.blurRegionTransform = mSnapshot->geomInverseLayerTransform.asMatrix4(); + break; + case LayerFE::ClientCompositionTargetSettings::BlurSetting::Disabled: + default: + break; + } + layerSettings.stretchEffect = mSnapshot->stretchEffect; + // Record the name of the layer for debugging further down the stack. + layerSettings.name = mSnapshot->name; + + if (hasEffect() && !hasBufferOrSidebandStream()) { + prepareEffectsClientComposition(layerSettings, targetSettings); + return layerSettings; + } + + prepareBufferStateClientComposition(layerSettings, targetSettings); + return layerSettings; +} + +void LayerFE::prepareClearClientComposition(LayerFE::LayerSettings& layerSettings, + bool blackout) const { + layerSettings.source.buffer.buffer = nullptr; + layerSettings.source.solidColor = half3(0.0f, 0.0f, 0.0f); + layerSettings.disableBlending = true; + layerSettings.bufferId = 0; + layerSettings.frameNumber = 0; + + // If layer is blacked out, force alpha to 1 so that we draw a black color layer. + layerSettings.alpha = blackout ? 1.0f : 0.0f; + layerSettings.name = mSnapshot->name; +} + +void LayerFE::prepareEffectsClientComposition( + compositionengine::LayerFE::LayerSettings& layerSettings, + compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { + // If fill bounds are occluded or the fill color is invalid skip the fill settings. + if (targetSettings.realContentIsVisible && fillsColor()) { + // Set color for color fill settings. + layerSettings.source.solidColor = mSnapshot->color.rgb; + } else if (hasBlur() || drawShadows()) { + layerSettings.skipContentDraw = true; + } +} + +void LayerFE::prepareBufferStateClientComposition( + compositionengine::LayerFE::LayerSettings& layerSettings, + compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { + ATRACE_CALL(); + if (CC_UNLIKELY(!mSnapshot->externalTexture)) { + // If there is no buffer for the layer or we have sidebandstream where there is no + // activeBuffer, then we need to return LayerSettings. + return; + } + const bool blackOutLayer = + (mSnapshot->hasProtectedContent && !targetSettings.supportsProtectedContent) || + ((mSnapshot->isSecure || mSnapshot->hasProtectedContent) && !targetSettings.isSecure); + const bool bufferCanBeUsedAsHwTexture = + mSnapshot->externalTexture->getUsage() & GraphicBuffer::USAGE_HW_TEXTURE; + if (blackOutLayer || !bufferCanBeUsedAsHwTexture) { + ALOGE_IF(!bufferCanBeUsedAsHwTexture, "%s is blacked out as buffer is not gpu readable", + mSnapshot->name.c_str()); + prepareClearClientComposition(layerSettings, true /* blackout */); + return; + } + + layerSettings.source.buffer.buffer = mSnapshot->externalTexture; + layerSettings.source.buffer.isOpaque = mSnapshot->contentOpaque; + layerSettings.source.buffer.fence = mSnapshot->acquireFence; + layerSettings.source.buffer.textureName = mSnapshot->textureName; + layerSettings.source.buffer.usePremultipliedAlpha = mSnapshot->premultipliedAlpha; + layerSettings.source.buffer.isY410BT2020 = mSnapshot->isHdrY410; + bool hasSmpte2086 = mSnapshot->hdrMetadata.validTypes & HdrMetadata::SMPTE2086; + bool hasCta861_3 = mSnapshot->hdrMetadata.validTypes & HdrMetadata::CTA861_3; + float maxLuminance = 0.f; + if (hasSmpte2086 && hasCta861_3) { + maxLuminance = std::min(mSnapshot->hdrMetadata.smpte2086.maxLuminance, + mSnapshot->hdrMetadata.cta8613.maxContentLightLevel); + } else if (hasSmpte2086) { + maxLuminance = mSnapshot->hdrMetadata.smpte2086.maxLuminance; + } else if (hasCta861_3) { + maxLuminance = mSnapshot->hdrMetadata.cta8613.maxContentLightLevel; + } else { + switch (layerSettings.sourceDataspace & HAL_DATASPACE_TRANSFER_MASK) { + case HAL_DATASPACE_TRANSFER_ST2084: + case HAL_DATASPACE_TRANSFER_HLG: + // Behavior-match previous releases for HDR content + maxLuminance = defaultMaxLuminance; + break; + } + } + layerSettings.source.buffer.maxLuminanceNits = maxLuminance; + layerSettings.frameNumber = mSnapshot->frameNumber; + layerSettings.bufferId = mSnapshot->externalTexture->getId(); + + const bool useFiltering = targetSettings.needsFiltering || + mSnapshot->geomLayerTransform.needsBilinearFiltering() || + mSnapshot->bufferNeedsFiltering; + + // Query the texture matrix given our current filtering mode. + float textureMatrix[16]; + getDrawingTransformMatrix(layerSettings.source.buffer.buffer, mSnapshot->geomContentCrop, + mSnapshot->geomBufferTransform, useFiltering, textureMatrix); + + if (mSnapshot->geomBufferUsesDisplayInverseTransform) { + /* + * the code below applies the primary display's inverse transform to + * the texture transform + */ + uint32_t transform = DisplayDevice::getPrimaryDisplayRotationFlags(); + mat4 tr = inverseOrientation(transform); + + /** + * TODO(b/36727915): This is basically a hack. + * + * Ensure that regardless of the parent transformation, + * this buffer is always transformed from native display + * orientation to display orientation. For example, in the case + * of a camera where the buffer remains in native orientation, + * we want the pixels to always be upright. + */ + const auto parentTransform = mSnapshot->transform; + tr = tr * inverseOrientation(parentTransform.getOrientation()); + + // and finally apply it to the original texture matrix + const mat4 texTransform(mat4(static_cast(textureMatrix)) * tr); + memcpy(textureMatrix, texTransform.asArray(), sizeof(textureMatrix)); + } + + const Rect win{layerSettings.geometry.boundaries}; + float bufferWidth = static_cast(mSnapshot->bufferSize.getWidth()); + float bufferHeight = static_cast(mSnapshot->bufferSize.getHeight()); + + // Layers can have a "buffer size" of [0, 0, -1, -1] when no display frame has + // been set and there is no parent layer bounds. In that case, the scale is meaningless so + // ignore them. + if (!mSnapshot->bufferSize.isValid()) { + bufferWidth = float(win.right) - float(win.left); + bufferHeight = float(win.bottom) - float(win.top); + } + + const float scaleHeight = (float(win.bottom) - float(win.top)) / bufferHeight; + const float scaleWidth = (float(win.right) - float(win.left)) / bufferWidth; + const float translateY = float(win.top) / bufferHeight; + const float translateX = float(win.left) / bufferWidth; + + // Flip y-coordinates because GLConsumer expects OpenGL convention. + mat4 tr = mat4::translate(vec4(.5f, .5f, 0.f, 1.f)) * mat4::scale(vec4(1.f, -1.f, 1.f, 1.f)) * + mat4::translate(vec4(-.5f, -.5f, 0.f, 1.f)) * + mat4::translate(vec4(translateX, translateY, 0.f, 1.f)) * + mat4::scale(vec4(scaleWidth, scaleHeight, 1.0f, 1.0f)); + + layerSettings.source.buffer.useTextureFiltering = useFiltering; + layerSettings.source.buffer.textureTransform = + mat4(static_cast(textureMatrix)) * tr; + + return; +} + +void LayerFE::prepareShadowClientComposition(LayerFE::LayerSettings& caster, + const Rect& layerStackRect) const { + renderengine::ShadowSettings state = mSnapshot->shadowSettings; + if (state.length <= 0.f || (state.ambientColor.a <= 0.f && state.spotColor.a <= 0.f)) { + return; + } + + // Shift the spot light x-position to the middle of the display and then + // offset it by casting layer's screen pos. + state.lightPos.x = + (static_cast(layerStackRect.width()) / 2.f) - mSnapshot->transformedBounds.left; + state.lightPos.y -= mSnapshot->transformedBounds.top; + caster.shadow = state; +} + +void LayerFE::onLayerDisplayed(ftl::SharedFuture futureFenceResult) { + mCompositionResult.releaseFences.emplace_back(std::move(futureFenceResult)); +} + +CompositionResult&& LayerFE::stealCompositionResult() { + return std::move(mCompositionResult); +} + +const char* LayerFE::getDebugName() const { + return mName.c_str(); +} + +const LayerMetadata* LayerFE::getMetadata() const { + return &mSnapshot->layerMetadata; +} + +const LayerMetadata* LayerFE::getRelativeMetadata() const { + return &mSnapshot->relativeLayerMetadata; +} + +int32_t LayerFE::getSequence() const { + return mSnapshot->sequence; +} + +bool LayerFE::hasRoundedCorners() const { + return mSnapshot->roundedCorner.hasRoundedCorners(); +} + +void LayerFE::setWasClientComposed(const sp& fence) { + mCompositionResult.lastClientCompositionFence = fence; +} + +bool LayerFE::hasBufferOrSidebandStream() const { + return mSnapshot->externalTexture || mSnapshot->sidebandStream; +} + +bool LayerFE::fillsColor() const { + return mSnapshot->color.r >= 0.0_hf && mSnapshot->color.g >= 0.0_hf && + mSnapshot->color.b >= 0.0_hf; +} + +bool LayerFE::hasBlur() const { + return mSnapshot->backgroundBlurRadius > 0 || mSnapshot->blurRegions.size() > 0; +} + +bool LayerFE::drawShadows() const { + return mSnapshot->shadowSettings.length > 0.f && + (mSnapshot->shadowSettings.ambientColor.a > 0 || + mSnapshot->shadowSettings.spotColor.a > 0); +}; + +const sp LayerFE::getBuffer() const { + return mSnapshot->externalTexture ? mSnapshot->externalTexture->getBuffer() : nullptr; +} + +} // namespace android diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h new file mode 100644 index 0000000000..e4f6889762 --- /dev/null +++ b/services/surfaceflinger/LayerFE.h @@ -0,0 +1,118 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "compositionengine/LayerFE.h" +#include "compositionengine/LayerFECompositionState.h" +#include "renderengine/LayerSettings.h" + +namespace android { +struct RoundedCornerState { + RoundedCornerState() = default; + RoundedCornerState(const FloatRect& cropRect, const vec2& radius) + : cropRect(cropRect), radius(radius) {} + + // Rounded rectangle in local layer coordinate space. + FloatRect cropRect = FloatRect(); + // Radius of the rounded rectangle. + vec2 radius; + bool hasRoundedCorners() const { return radius.x > 0.0f && radius.y > 0.0f; } +}; + +// LayerSnapshot stores Layer state used by CompositionEngine and RenderEngine. Composition +// Engine uses a pointer to LayerSnapshot (as LayerFECompositionState*) and the LayerSettings +// passed to Render Engine are created using properties stored on this struct. +struct LayerSnapshot : public compositionengine::LayerFECompositionState { + int32_t sequence; + std::string name; + uint32_t textureName; + bool contentOpaque; + RoundedCornerState roundedCorner; + StretchEffect stretchEffect; + FloatRect transformedBounds; + renderengine::ShadowSettings shadowSettings; + bool premultipliedAlpha; + bool isHdrY410; + bool bufferNeedsFiltering; + ui::Transform transform; + Rect bufferSize; + std::shared_ptr externalTexture; + gui::LayerMetadata layerMetadata; + gui::LayerMetadata relativeLayerMetadata; + bool contentDirty; + bool hasReadyFrame; +}; + +struct CompositionResult { + // TODO(b/238781169) update CE to no longer pass refreshStartTime to LayerFE::onPreComposition + // and remove this field. + nsecs_t refreshStartTime = 0; + std::vector> releaseFences; + sp lastClientCompositionFence = nullptr; +}; + +class LayerFE : public virtual RefBase, public virtual compositionengine::LayerFE { +public: + LayerFE(const std::string& name); + + // compositionengine::LayerFE overrides + const compositionengine::LayerFECompositionState* getCompositionState() const override; + bool onPreComposition(nsecs_t refreshStartTime, bool updatingOutputGeometryThisFrame) override; + void onLayerDisplayed(ftl::SharedFuture) override; + const char* getDebugName() const override; + int32_t getSequence() const override; + bool hasRoundedCorners() const override; + void setWasClientComposed(const sp&) override; + const gui::LayerMetadata* getMetadata() const override; + const gui::LayerMetadata* getRelativeMetadata() const override; + std::optional prepareClientComposition( + compositionengine::LayerFE::ClientCompositionTargetSettings&) const; + CompositionResult&& stealCompositionResult(); + + std::unique_ptr mSnapshot; + +private: + std::optional prepareClientCompositionInternal( + compositionengine::LayerFE::ClientCompositionTargetSettings&) const; + // Modifies the passed in layer settings to clear the contents. If the blackout flag is set, + // the settings clears the content with a solid black fill. + void prepareClearClientComposition(LayerFE::LayerSettings&, bool blackout) const; + void prepareShadowClientComposition(LayerFE::LayerSettings& caster, + const Rect& layerStackRect) const; + void prepareBufferStateClientComposition( + compositionengine::LayerFE::LayerSettings&, + compositionengine::LayerFE::ClientCompositionTargetSettings&) const; + void prepareEffectsClientComposition( + compositionengine::LayerFE::LayerSettings&, + compositionengine::LayerFE::ClientCompositionTargetSettings&) const; + + bool hasEffect() const { return fillsColor() || drawShadows() || hasBlur(); } + bool hasBufferOrSidebandStream() const; + + bool fillsColor() const; + bool hasBlur() const; + bool drawShadows() const; + + const sp getBuffer() const; + + CompositionResult mCompositionResult; + std::string mName; +}; + +} // namespace android diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp index 6bc7dc1dd7..3e6ed416fc 100644 --- a/services/surfaceflinger/LayerRenderArea.cpp +++ b/services/surfaceflinger/LayerRenderArea.cpp @@ -30,6 +30,7 @@ void reparentForDrawing(const sp& oldParent, const sp& newParent, // Compute and cache the bounds for the new parent layer. newParent->computeBounds(drawingBounds.toFloatRect(), ui::Transform(), 0.f /* shadowRadius */); + newParent->updateSnapshot(true /* updateGeometry */); oldParent->setChildrenDrawingParent(newParent); }; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index e4fee1cdcc..6144a2f04b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2172,10 +2172,14 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty; refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty; - mDrawingState.traverseInZOrder([&refreshArgs](Layer* layer) { + std::vector layers; + + mDrawingState.traverseInZOrder([&refreshArgs, &layers](Layer* layer) { layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame); if (auto layerFE = layer->getCompositionEngineLayerFE()) { + layer->moveSnapshotToLayerFE(); refreshArgs.layers.push_back(layerFE); + layers.push_back(layer); } }); refreshArgs.blursAreExpensive = mBlursAreExpensive; @@ -2207,6 +2211,19 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) mCompositionEngine->present(refreshArgs); + for (auto& layer : layers) { + layer->moveSnapshotToLayer(); + CompositionResult compositionResult{ + layer->getCompositionEngineLayerFE()->stealCompositionResult()}; + layer->onPreComposition(compositionResult.refreshStartTime); + for (auto releaseFence : compositionResult.releaseFences) { + layer->onLayerDisplayed(releaseFence); + } + if (compositionResult.lastClientCompositionFence) { + layer->setWasClientComposed(compositionResult.lastClientCompositionFence); + } + } + mTimeStats->recordFrameDuration(frameTime.ns(), systemTime()); // Send a power hint hint after presentation is finished @@ -3291,7 +3308,21 @@ void SurfaceFlinger::updateCursorAsync() { } } + std::vector cursorLayers; + mDrawingState.traverse([&cursorLayers](Layer* layer) { + if (layer->getLayerSnapshot()->compositionType == + aidl::android::hardware::graphics::composer3::Composition::CURSOR) { + layer->updateSnapshot(false /* updateGeometry */); + layer->moveSnapshotToLayerFE(); + cursorLayers.push_back(layer); + } + }); + mCompositionEngine->updateCursorAsync(refreshArgs); + + for (Layer* layer : cursorLayers) { + layer->moveSnapshotToLayer(); + } } void SurfaceFlinger::requestDisplayModes( @@ -6444,8 +6475,16 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( isHdrLayer(layer) ? displayBrightnessNits : sdrWhitePointNits, }; + auto layerFE = layer->getCompositionEngineLayerFE(); + if (!layerFE) { + return; + } + + layer->moveSnapshotToLayerFE(); std::optional settings = - layer->prepareClientComposition(targetSettings); + layerFE->prepareClientComposition(targetSettings); + layer->moveSnapshotToLayer(); + if (!settings) { return; } diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp index 2f1f263afa..7e6894d3f7 100644 --- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp +++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp @@ -91,6 +91,10 @@ sp DefaultFactory::createEffectLayer(const LayerCreationArgs& args) { return sp::make(args); } +sp DefaultFactory::createLayerFE(const std::string& layerName) { + return sp::make(layerName); +} + std::unique_ptr DefaultFactory::createFrameTracer() { return std::make_unique(); } diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h index 447a02fc0c..2c6de0e113 100644 --- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h +++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h @@ -42,6 +42,7 @@ public: std::unique_ptr createCompositionEngine() override; sp createBufferStateLayer(const LayerCreationArgs& args) override; sp createEffectLayer(const LayerCreationArgs& args) override; + sp createLayerFE(const std::string& layerName) override; std::unique_ptr createFrameTracer() override; std::unique_ptr createFrameTimeline( std::shared_ptr timeStats, pid_t surfaceFlingerPid) override; diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h index 9c4d5c82ea..d1f21bfb43 100644 --- a/services/surfaceflinger/SurfaceFlingerFactory.h +++ b/services/surfaceflinger/SurfaceFlingerFactory.h @@ -38,6 +38,7 @@ class HWComposer; class IGraphicBufferConsumer; class IGraphicBufferProducer; class Layer; +class LayerFE; class StartPropertySetThread; class SurfaceFlinger; class TimeStats; @@ -88,6 +89,7 @@ public: virtual sp createBufferStateLayer(const LayerCreationArgs& args) = 0; virtual sp createEffectLayer(const LayerCreationArgs& args) = 0; + virtual sp createLayerFE(const std::string& layerName) = 0; virtual std::unique_ptr createFrameTracer() = 0; virtual std::unique_ptr createFrameTimeline( std::shared_ptr timeStats, pid_t surfaceFlingerPid) = 0; diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index d0364f23d9..276b3dad18 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -82,6 +82,8 @@ public: sp createEffectLayer(const LayerCreationArgs& args) { return sp::make(args); } + sp createLayerFE(const std::string& layerName) { return sp::make(layerName); } + std::unique_ptr createFrameTracer() override { return std::make_unique>(); } diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index ed2fe23669..e2ae4f4d7f 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -354,6 +354,10 @@ public: return sp::make(args); } + sp createLayerFE(const std::string &layerName) override { + return sp::make(layerName); + } + std::unique_ptr createFrameTracer() override { return std::make_unique(); } diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp index 0a142c3ce6..acfc1d4dc8 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp @@ -146,8 +146,7 @@ void LayerFuzzer::invokeBufferStateLayer() { layer->computeSourceBounds(getFuzzedFloatRect(&mFdp)); layer->fenceHasSignaled(); - layer->onPreComposition(mFdp.ConsumeIntegral(), - false /*updatingOutputGeometryThisFrame*/); + layer->onPreComposition(mFdp.ConsumeIntegral()); const std::vector> callbacks; layer->setTransactionCompletedListeners(callbacks); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 29ff5ee183..83d852a1fa 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -118,6 +118,10 @@ public: sp createEffectLayer(const LayerCreationArgs&) override { return nullptr; } + sp createLayerFE(const std::string& layerName) override { + return sp::make(layerName); + } + std::unique_ptr createFrameTracer() override { return std::make_unique(); } -- GitLab From 9fc61df2c564be0f827d2c0ce807123b18d3e61f Mon Sep 17 00:00:00 2001 From: tsaichristine Date: Mon, 26 Sep 2022 18:33:05 -0700 Subject: [PATCH 0374/1310] Handle null repeated and nested fields in VendorAtom Bug: 237423972 Bug: 237424483 Test: build, flash, and run aidl_stats_client on device Test: atest VtsAidlHalStatsTargetTest Test: atest VtsVendorAtomJavaTest Change-Id: Id233024078d7daa658650765b26ff11ba9ca6f34 --- services/stats/StatsAidl.cpp | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/services/stats/StatsAidl.cpp b/services/stats/StatsAidl.cpp index 3de51a437d..9e13849485 100644 --- a/services/stats/StatsAidl.cpp +++ b/services/stats/StatsAidl.cpp @@ -69,6 +69,10 @@ ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) { case VendorAtomValue::repeatedIntValue: { const std::optional>& repeatedIntValue = atomValue.get(); + if (!repeatedIntValue) { + AStatsEvent_writeInt32Array(event, {}, 0); + break; + } AStatsEvent_writeInt32Array(event, repeatedIntValue->data(), repeatedIntValue->size()); break; @@ -76,6 +80,10 @@ ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) { case VendorAtomValue::repeatedLongValue: { const std::optional>& repeatedLongValue = atomValue.get(); + if (!repeatedLongValue) { + AStatsEvent_writeInt64Array(event, {}, 0); + break; + } AStatsEvent_writeInt64Array(event, repeatedLongValue->data(), repeatedLongValue->size()); break; @@ -83,6 +91,10 @@ ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) { case VendorAtomValue::repeatedFloatValue: { const std::optional>& repeatedFloatValue = atomValue.get(); + if (!repeatedFloatValue) { + AStatsEvent_writeFloatArray(event, {}, 0); + break; + } AStatsEvent_writeFloatArray(event, repeatedFloatValue->data(), repeatedFloatValue->size()); break; @@ -90,12 +102,18 @@ ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) { case VendorAtomValue::repeatedStringValue: { const std::optional>>& repeatedStringValue = atomValue.get(); + if (!repeatedStringValue) { + AStatsEvent_writeStringArray(event, {}, 0); + break; + } const std::vector>& repeatedStringVector = *repeatedStringValue; const char* cStringArray[repeatedStringVector.size()]; for (int i = 0; i < repeatedStringVector.size(); ++i) { - cStringArray[i] = repeatedStringVector[i]->c_str(); + cStringArray[i] = repeatedStringVector[i].has_value() + ? repeatedStringVector[i]->c_str() + : ""; } AStatsEvent_writeStringArray(event, cStringArray, repeatedStringVector.size()); @@ -104,6 +122,10 @@ ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) { case VendorAtomValue::repeatedBoolValue: { const std::optional>& repeatedBoolValue = atomValue.get(); + if (!repeatedBoolValue) { + AStatsEvent_writeBoolArray(event, {}, 0); + break; + } const std::vector& repeatedBoolVector = *repeatedBoolValue; bool boolArray[repeatedBoolValue->size()]; @@ -117,7 +139,10 @@ ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) { case VendorAtomValue::byteArrayValue: { const std::optional>& byteArrayValue = atomValue.get(); - + if (!byteArrayValue) { + AStatsEvent_writeByteArray(event, {}, 0); + break; + } AStatsEvent_writeByteArray(event, byteArrayValue->data(), byteArrayValue->size()); break; } -- GitLab From 6b649a7fd81b0a760794a9f01486834eb1cbc698 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Fri, 14 Oct 2022 21:21:29 +0000 Subject: [PATCH 0375/1310] Fix ITransactionCompletedListener Tag::lAST Bug: 244218818 Test: presubmits Change-Id: I433d2d08a788c6bd7680a2704c98f7daef2f6c91 --- libs/gui/ITransactionCompletedListener.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp index e4b8bad8f8..ca91afa35d 100644 --- a/libs/gui/ITransactionCompletedListener.cpp +++ b/libs/gui/ITransactionCompletedListener.cpp @@ -30,7 +30,7 @@ enum class Tag : uint32_t { ON_TRANSACTION_COMPLETED = IBinder::FIRST_CALL_TRANSACTION, ON_RELEASE_BUFFER, ON_TRANSACTION_QUEUE_STALLED, - LAST = ON_RELEASE_BUFFER, + LAST = ON_TRANSACTION_QUEUE_STALLED, }; } // Anonymous namespace -- GitLab From 31977184520e99110e1deadceb6197636f76450a Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 30 Sep 2022 08:51:23 -0700 Subject: [PATCH 0376/1310] Run some inputflinger_tests on host Sometimes, it's convenient to execute the input tests without having a connected device. This is especially useful when doing cherry-picks of patch on a cloud device. Allow input code to build for host, and enable the tests for running on host. Bug: 249591924 Test: atest --host --no-bazel-mode -c -m inputflinger_tests Change-Id: Ib9be6a5fb6c35ffc450e41cb2a5688bfb2c8d01a --- libs/attestation/Android.bp | 1 + libs/input/Input.cpp | 36 ++++++++------- services/batteryservice/Android.bp | 1 + services/inputflinger/Android.bp | 46 +++++++++++++++---- services/inputflinger/InputThread.cpp | 6 +++ services/inputflinger/benchmarks/Android.bp | 1 - services/inputflinger/dispatcher/Android.bp | 27 ++++++++--- .../dispatcher/InputDispatcher.cpp | 5 +- services/inputflinger/reader/Android.bp | 37 +++++++++++---- services/inputflinger/reader/EventHub.cpp | 1 - .../reader/mapper/MultiTouchInputMapper.cpp | 8 +++- services/inputflinger/reporter/Android.bp | 3 +- services/inputflinger/tests/Android.bp | 27 +++++++++++ services/inputflinger/tests/EventHub_test.cpp | 6 +++ .../tests/InputDispatcher_test.cpp | 2 +- .../inputflinger/tests/InputReader_test.cpp | 9 ++++ services/inputflinger/tests/UinputDevice.cpp | 1 + 17 files changed, 172 insertions(+), 45 deletions(-) diff --git a/libs/attestation/Android.bp b/libs/attestation/Android.bp index 2bf15d45eb..fddecc0ceb 100644 --- a/libs/attestation/Android.bp +++ b/libs/attestation/Android.bp @@ -22,6 +22,7 @@ package { cc_library_static { name: "libattestation", + host_supported: true, cflags: [ "-Wall", "-Wextra", diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index 579b28e588..a6f6b14bae 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -34,9 +35,6 @@ #ifdef __linux__ #include #endif -#ifdef __ANDROID__ -#include -#endif using android::base::StringPrintf; @@ -112,28 +110,34 @@ const char* motionToolTypeToString(int32_t toolType) { } // --- IdGenerator --- + +static status_t getRandomBytes(uint8_t* data, size_t size) { + int ret = TEMP_FAILURE_RETRY(open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOFOLLOW)); + if (ret == -1) { + return -errno; + } + + base::unique_fd fd(ret); + if (!base::ReadFully(fd, data, size)) { + return -errno; + } + return OK; +} + IdGenerator::IdGenerator(Source source) : mSource(source) {} int32_t IdGenerator::nextId() const { constexpr uint32_t SEQUENCE_NUMBER_MASK = ~SOURCE_MASK; int32_t id = 0; -// Avoid building against syscall getrandom(2) on host, which will fail build on Mac. Host doesn't -// use sequence number so just always return mSource. -#ifdef __ANDROID__ - constexpr size_t BUF_LEN = sizeof(id); - size_t totalBytes = 0; - while (totalBytes < BUF_LEN) { - ssize_t bytes = TEMP_FAILURE_RETRY(getrandom(&id, BUF_LEN, GRND_NONBLOCK)); - if (CC_UNLIKELY(bytes < 0)) { - ALOGW("Failed to fill in random number for sequence number: %s.", strerror(errno)); - id = 0; +#if defined(__linux__) + while (true) { + status_t result = getRandomBytes(reinterpret_cast(&id), sizeof(id)); + if (result == OK) { break; } - totalBytes += bytes; } -#endif // __ANDROID__ - +#endif // __linux__ return (id & SEQUENCE_NUMBER_MASK) | static_cast(mSource); } diff --git a/services/batteryservice/Android.bp b/services/batteryservice/Android.bp index 1e3799185e..9b783916d3 100644 --- a/services/batteryservice/Android.bp +++ b/services/batteryservice/Android.bp @@ -9,6 +9,7 @@ package { cc_library_headers { name: "libbatteryservice_headers", + host_supported: true, vendor_available: true, recovery_available: true, export_include_dirs: ["include"], diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp index ddcd51f357..4dcbba2c43 100644 --- a/services/inputflinger/Android.bp +++ b/services/inputflinger/Android.bp @@ -41,7 +41,9 @@ cc_defaults { "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION", ], sanitize: { - misc_undefined: ["bounds"], + misc_undefined: [ + "bounds", + ], }, tidy: true, tidy_checks: [ @@ -57,11 +59,11 @@ cc_defaults { filegroup { name: "libinputflinger_sources", srcs: [ - "InputProcessor.cpp", "InputCommonConverter.cpp", + "InputManager.cpp", + "InputProcessor.cpp", "PreferStylusOverTouchBlocker.cpp", "UnwantedInteractionBlocker.cpp", - "InputManager.cpp", ], } @@ -77,13 +79,10 @@ cc_defaults { "libcrypto", "libcutils", "libhidlbase", - "libinput", "libkll", "liblog", "libprotobuf-cpp-lite", "libstatslog", - "libstatspull", - "libstatssocket", "libutils", "server_configurable_flags", ], @@ -92,6 +91,23 @@ cc_defaults { "libpalmrejection", "libui-types", ], + target: { + android: { + shared_libs: [ + "libgui", + "libinput", + "libstatspull", + "libstatssocket", + ], + }, + host: { + static_libs: [ + "libinput", + "libstatspull", + "libstatssocket", + ], + }, + }, } cc_library_shared { @@ -108,9 +124,8 @@ cc_library_shared { // This should consist only of dependencies from inputflinger. Other dependencies should be // in cc_defaults so that they are included in the tests. "libinputflinger_base", - "libinputreporter", "libinputreader", - "libgui", + "libinputreporter", ], static_libs: [ "libinputdispatcher", @@ -130,6 +145,7 @@ cc_library_shared { cc_library_headers { name: "libinputflinger_headers", + host_supported: true, export_include_dirs: ["include"], } @@ -151,17 +167,29 @@ cc_defaults { "libbase", "libbinder", "libcutils", - "libinput", "liblog", "libutils", ], header_libs: [ "libinputflinger_headers", ], + target: { + android: { + shared_libs: [ + "libinput", + ], + }, + host: { + static_libs: [ + "libinput", + ], + }, + }, } cc_library_shared { name: "libinputflinger_base", + host_supported: true, defaults: [ "inputflinger_defaults", "libinputflinger_base_defaults", diff --git a/services/inputflinger/InputThread.cpp b/services/inputflinger/InputThread.cpp index e2e64f992f..e74f258168 100644 --- a/services/inputflinger/InputThread.cpp +++ b/services/inputflinger/InputThread.cpp @@ -54,7 +54,13 @@ InputThread::~InputThread() { } bool InputThread::isCallingThread() { +#if defined(__ANDROID__) return gettid() == mThread->getTid(); +#else + // Assume that the caller is doing everything correctly, + // since thread information is not available on host + return false; +#endif } } // namespace android \ No newline at end of file diff --git a/services/inputflinger/benchmarks/Android.bp b/services/inputflinger/benchmarks/Android.bp index e5c19afead..4e2a6fbf87 100644 --- a/services/inputflinger/benchmarks/Android.bp +++ b/services/inputflinger/benchmarks/Android.bp @@ -21,7 +21,6 @@ cc_benchmark { "libbinder", "libcrypto", "libcutils", - "libinput", "libinputflinger_base", "libinputreporter", "liblog", diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp index eb79b76b28..99c4936f32 100644 --- a/services/inputflinger/dispatcher/Android.bp +++ b/services/inputflinger/dispatcher/Android.bp @@ -23,6 +23,7 @@ package { cc_library_headers { name: "libinputdispatcher_headers", + host_supported: true, export_include_dirs: [ "include", ], @@ -33,6 +34,7 @@ filegroup { srcs: [ "AnrTracker.cpp", "Connection.cpp", + "DragState.cpp", "Entry.cpp", "FocusResolver.cpp", "InjectionState.cpp", @@ -45,7 +47,6 @@ filegroup { "LatencyTracker.cpp", "Monitor.cpp", "TouchState.cpp", - "DragState.cpp", ], } @@ -56,20 +57,34 @@ cc_defaults { "libbase", "libcrypto", "libcutils", - "libinput", "libkll", "liblog", "libprotobuf-cpp-lite", "libstatslog", - "libstatspull", - "libstatssocket", - "libgui", "libutils", "server_configurable_flags", ], static_libs: [ "libattestation", + "libgui_window_info_static", ], + target: { + android: { + shared_libs: [ + "libgui", + "libinput", + "libstatspull", + "libstatssocket", + ], + }, + host: { + static_libs: [ + "libinput", + "libstatspull", + "libstatssocket", + ], + }, + }, header_libs: [ "libinputdispatcher_headers", ], @@ -84,8 +99,8 @@ cc_library_static { shared_libs: [ // This should consist only of dependencies from inputflinger. Other dependencies should be // in cc_defaults so that they are included in the tests. - "libinputreporter", "libinputflinger_base", + "libinputreporter", ], export_header_lib_headers: [ "libinputdispatcher_headers", diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index a793f57ade..5587a8f5d0 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -25,7 +25,9 @@ #include #include #include +#if defined(__ANDROID__) #include +#endif #include

(pointer)) { + if (!pointer_) std::abort(); + } + + private: + template + friend constexpr auto as_non_null(P&&) -> NonNull>; + + Pointer pointer_; +}; + +template +constexpr auto as_non_null(P&& pointer) -> NonNull> { + using Passkey = typename NonNull>::Passkey; + return {Passkey{}, std::forward

(pointer)}; +} + +template +constexpr bool operator==(const NonNull

& lhs, const NonNull& rhs) { + return lhs.get() == rhs.get(); +} + +template +constexpr bool operator!=(const NonNull

& lhs, const NonNull& rhs) { + return !operator==(lhs, rhs); +} + +} // namespace android::ftl diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp index c1945fddb3..81113bc211 100644 --- a/libs/ftl/Android.bp +++ b/libs/ftl/Android.bp @@ -22,6 +22,7 @@ cc_test { "flags_test.cpp", "future_test.cpp", "match_test.cpp", + "non_null_test.cpp", "optional_test.cpp", "small_map_test.cpp", "small_vector_test.cpp", diff --git a/libs/ftl/non_null_test.cpp b/libs/ftl/non_null_test.cpp new file mode 100644 index 0000000000..bd0462b3b6 --- /dev/null +++ b/libs/ftl/non_null_test.cpp @@ -0,0 +1,75 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include + +namespace android::test { +namespace { + +void get_length(const ftl::NonNull>& string_ptr, + ftl::NonNull length_ptr) { + // No need for `nullptr` checks. + *length_ptr = string_ptr->length(); +} + +using Pair = std::pair>, std::shared_ptr>; + +Pair dupe_if(ftl::NonNull> non_null_ptr, bool condition) { + // Move the underlying pointer out, so `non_null_ptr` must not be accessed after this point. + auto unique_ptr = std::move(non_null_ptr).take(); + + auto non_null_shared_ptr = ftl::as_non_null(std::shared_ptr(std::move(unique_ptr))); + auto nullable_shared_ptr = condition ? non_null_shared_ptr.get() : nullptr; + + return {std::move(non_null_shared_ptr), std::move(nullable_shared_ptr)}; +} + +} // namespace + +// Keep in sync with example usage in header file. +TEST(NonNull, Example) { + const auto string_ptr = ftl::as_non_null(std::make_shared("android")); + std::size_t size; + get_length(string_ptr, ftl::as_non_null(&size)); + EXPECT_EQ(size, 7u); + + auto ptr = ftl::as_non_null(std::make_unique(42)); + const auto [ptr1, ptr2] = dupe_if(std::move(ptr), true); + EXPECT_EQ(ptr1.get(), ptr2); +} + +namespace { + +constexpr std::string_view kApple = "apple"; +constexpr std::string_view kOrange = "orange"; + +using StringViewPtr = ftl::NonNull; +constexpr StringViewPtr kApplePtr = ftl::as_non_null(&kApple); +constexpr StringViewPtr kOrangePtr = ftl::as_non_null(&kOrange); + +constexpr StringViewPtr longest(StringViewPtr ptr1, StringViewPtr ptr2) { + return ptr1->length() > ptr2->length() ? ptr1 : ptr2; +} + +static_assert(longest(kApplePtr, kOrangePtr) == kOrangePtr); + +} // namespace +} // namespace android::test -- GitLab From 5b79930750cdfd7c9aa09f95fdfe3fa9ecc34768 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Tue, 18 Oct 2022 21:52:41 -0700 Subject: [PATCH 0382/1310] Make inputflinger_tests compile for musl inputflinger_tests compile for the host now, so they need to be compatible with musl libc. Use LLONG_MIN and LLONG_MAX instead of the nonstandard LONG_LONG_MIN and LONG_LONG_MAX. Use input_event_sec and input_event_usec to intialize time fields in struct input_event, they are not always stored in a struct timespec. Include linux/ioctl.h to define _IOC_SIZE. Cast tv_sec and tv_usec to long long, as the type can be any integer type. Cast EPOLLIN to compare with epoll_event.events, it is not unsigned in musl. Include transitive dependencies of static libraries that are not needed by the linker when CFI is enabled, but are detected as errors when CFI is not enabled. Bug: 190084016 Test: m USE_HOST_MUSL=true inputflinger_tests Change-Id: I625bb612d5a8816ec5d1ade6d3a887981a549569 --- services/inputflinger/Android.bp | 1 + .../inputflinger/dispatcher/AnrTracker.cpp | 2 +- services/inputflinger/dispatcher/AnrTracker.h | 2 +- .../dispatcher/InputDispatcher.cpp | 22 +++++++++---------- services/inputflinger/reader/Android.bp | 1 + services/inputflinger/reader/EventHub.cpp | 17 +++++++------- .../inputflinger/reader/TouchVideoDevice.cpp | 5 +++-- .../inputflinger/tests/AnrTracker_test.cpp | 2 +- .../tests/InputDispatcher_test.cpp | 2 +- services/inputflinger/tests/UinputDevice.cpp | 2 +- 10 files changed, 30 insertions(+), 26 deletions(-) diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp index 4dcbba2c43..d1de551799 100644 --- a/services/inputflinger/Android.bp +++ b/services/inputflinger/Android.bp @@ -182,6 +182,7 @@ cc_defaults { host: { static_libs: [ "libinput", + "libui-types", ], }, }, diff --git a/services/inputflinger/dispatcher/AnrTracker.cpp b/services/inputflinger/dispatcher/AnrTracker.cpp index c3f611e7db..a18063f299 100644 --- a/services/inputflinger/dispatcher/AnrTracker.cpp +++ b/services/inputflinger/dispatcher/AnrTracker.cpp @@ -54,7 +54,7 @@ bool AnrTracker::empty() const { } // If empty() is false, return the time at which the next connection should cause an ANR -// If empty() is true, return LONG_LONG_MAX +// If empty() is true, return LLONG_MAX nsecs_t AnrTracker::firstTimeout() const { if (mAnrTimeouts.empty()) { return std::numeric_limits::max(); diff --git a/services/inputflinger/dispatcher/AnrTracker.h b/services/inputflinger/dispatcher/AnrTracker.h index 5e5e0c4bae..cff5d00e4b 100644 --- a/services/inputflinger/dispatcher/AnrTracker.h +++ b/services/inputflinger/dispatcher/AnrTracker.h @@ -35,7 +35,7 @@ public: bool empty() const; // If empty() is false, return the time at which the next connection should cause an ANR - // If empty() is true, return LONG_LONG_MAX + // If empty() is true, return LLONG_MAX nsecs_t firstTimeout() const; // Return the token of the next connection that should cause an ANR. // Do not call this unless empty() is false, you will encounter undefined behaviour. diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 5587a8f5d0..84c18fd74b 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -555,7 +555,7 @@ InputDispatcher::InputDispatcher(const sp& polic mLastDropReason(DropReason::NOT_DROPPED), mIdGenerator(IdGenerator::Source::INPUT_DISPATCHER), mAppSwitchSawKeyDown(false), - mAppSwitchDueTime(LONG_LONG_MAX), + mAppSwitchDueTime(LLONG_MAX), mNextUnblockedEvent(nullptr), mMonitorDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT), mDispatchEnabled(false), @@ -612,7 +612,7 @@ status_t InputDispatcher::stop() { } void InputDispatcher::dispatchOnce() { - nsecs_t nextWakeupTime = LONG_LONG_MAX; + nsecs_t nextWakeupTime = LLONG_MAX; { // acquire lock std::scoped_lock _l(mLock); mDispatcherIsAlive.notify_all(); @@ -626,7 +626,7 @@ void InputDispatcher::dispatchOnce() { // Run all pending commands if there are any. // If any commands were run then force the next poll to wake up immediately. if (runCommandsLockedInterruptable()) { - nextWakeupTime = LONG_LONG_MIN; + nextWakeupTime = LLONG_MIN; } // If we are still waiting for ack on some events, @@ -636,7 +636,7 @@ void InputDispatcher::dispatchOnce() { // We are about to enter an infinitely long sleep, because we have no commands or // pending or queued events - if (nextWakeupTime == LONG_LONG_MAX) { + if (nextWakeupTime == LLONG_MAX) { mDispatcherEnteredIdle.notify_all(); } } // release lock @@ -681,14 +681,14 @@ void InputDispatcher::processNoFocusedWindowAnrLocked() { */ nsecs_t InputDispatcher::processAnrsLocked() { const nsecs_t currentTime = now(); - nsecs_t nextAnrCheck = LONG_LONG_MAX; + nsecs_t nextAnrCheck = LLONG_MAX; // Check if we are waiting for a focused window to appear. Raise ANR if waited too long if (mNoFocusedWindowTimeoutTime.has_value() && mAwaitedFocusedApplication != nullptr) { if (currentTime >= *mNoFocusedWindowTimeoutTime) { processNoFocusedWindowAnrLocked(); mAwaitedFocusedApplication.reset(); mNoFocusedWindowTimeoutTime = std::nullopt; - return LONG_LONG_MIN; + return LLONG_MIN; } else { // Keep waiting. We will drop the event when mNoFocusedWindowTimeoutTime comes. nextAnrCheck = *mNoFocusedWindowTimeoutTime; @@ -711,7 +711,7 @@ nsecs_t InputDispatcher::processAnrsLocked() { // Stop waking up for this unresponsive connection mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken()); onAnrLocked(connection); - return LONG_LONG_MIN; + return LLONG_MIN; } std::chrono::nanoseconds InputDispatcher::getDispatchingTimeoutLocked( @@ -918,7 +918,7 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { mLastDropReason = dropReason; releasePendingEventLocked(); - *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately + *nextWakeupTime = LLONG_MIN; // force next poll to wake up immediately } } @@ -1201,11 +1201,11 @@ bool InputDispatcher::isAppSwitchKeyEvent(const KeyEntry& keyEntry) { } bool InputDispatcher::isAppSwitchPendingLocked() { - return mAppSwitchDueTime != LONG_LONG_MAX; + return mAppSwitchDueTime != LLONG_MAX; } void InputDispatcher::resetPendingAppSwitchLocked(bool handled) { - mAppSwitchDueTime = LONG_LONG_MAX; + mAppSwitchDueTime = LLONG_MAX; if (DEBUG_APP_SWITCH) { if (handled) { @@ -1498,7 +1498,7 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptrrepeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1; resetKeyRepeatLocked(); - mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves + mKeyRepeatState.nextRepeatTime = LLONG_MAX; // don't generate repeats ourselves } else { // Not a repeat. Save key down state in case we do see a repeat later. resetKeyRepeatLocked(); diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index 43259c0c5f..c5e1f0ca94 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -89,6 +89,7 @@ cc_defaults { host: { static_libs: [ "libinput", + "libbinder", ], }, }, diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index 956746d7d2..b97c46613b 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -191,8 +192,8 @@ static nsecs_t processEventTimestamp(const struct input_event& event) { // calls clock_gettime(CLOCK_MONOTONIC) which is implemented as a // system call that also queries ktime_get_ts(). - const nsecs_t inputEventTime = seconds_to_nanoseconds(event.time.tv_sec) + - microseconds_to_nanoseconds(event.time.tv_usec); + const nsecs_t inputEventTime = seconds_to_nanoseconds(event.input_event_sec) + + microseconds_to_nanoseconds(event.input_event_usec); return inputEventTime; } @@ -632,8 +633,8 @@ void EventHub::Device::setLedStateLocked(int32_t led, bool on) { int32_t sc; if (hasValidFd() && mapLed(led, &sc) != NAME_NOT_FOUND) { struct input_event ev; - ev.time.tv_sec = 0; - ev.time.tv_usec = 0; + ev.input_event_sec = 0; + ev.input_event_usec = 0; ev.type = EV_LED; ev.code = sc; ev.value = on ? 1 : 0; @@ -1462,8 +1463,8 @@ void EventHub::vibrate(int32_t deviceId, const VibrationElement& element) { device->ffEffectId = effect.id; struct input_event ev; - ev.time.tv_sec = 0; - ev.time.tv_usec = 0; + ev.input_event_sec = 0; + ev.input_event_usec = 0; ev.type = EV_FF; ev.code = device->ffEffectId; ev.value = 1; @@ -1484,8 +1485,8 @@ void EventHub::cancelVibrate(int32_t deviceId) { device->ffEffectPlaying = false; struct input_event ev; - ev.time.tv_sec = 0; - ev.time.tv_usec = 0; + ev.input_event_sec = 0; + ev.input_event_usec = 0; ev.type = EV_FF; ev.code = device->ffEffectId; ev.value = 0; diff --git a/services/inputflinger/reader/TouchVideoDevice.cpp b/services/inputflinger/reader/TouchVideoDevice.cpp index 2f8138b832..627dcba9cf 100644 --- a/services/inputflinger/reader/TouchVideoDevice.cpp +++ b/services/inputflinger/reader/TouchVideoDevice.cpp @@ -198,8 +198,9 @@ std::optional TouchVideoDevice::readFrame() { if ((buf.flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) != V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC) { // We use CLOCK_MONOTONIC for input events, so if the clocks don't match, // we can't compare timestamps. Just log a warning, since this is a driver issue - ALOGW("The timestamp %ld.%ld was not acquired using CLOCK_MONOTONIC", buf.timestamp.tv_sec, - buf.timestamp.tv_usec); + ALOGW("The timestamp %lld.%lld was not acquired using CLOCK_MONOTONIC", + static_cast(buf.timestamp.tv_sec), + static_cast(buf.timestamp.tv_usec)); } std::vector data(mHeight * mWidth); const int16_t* readFrom = mReadLocations[buf.index]; diff --git a/services/inputflinger/tests/AnrTracker_test.cpp b/services/inputflinger/tests/AnrTracker_test.cpp index f8be48af25..25adeea48f 100644 --- a/services/inputflinger/tests/AnrTracker_test.cpp +++ b/services/inputflinger/tests/AnrTracker_test.cpp @@ -137,7 +137,7 @@ TEST(AnrTrackerTest, Empty_DoesntCrash) { ASSERT_TRUE(tracker.empty()); - ASSERT_EQ(LONG_LONG_MAX, tracker.firstTimeout()); + ASSERT_EQ(LLONG_MAX, tracker.firstTimeout()); // Can't call firstToken() if tracker.empty() } diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index a1ccfc7d16..aaf50ce5ed 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -7027,7 +7027,7 @@ TEST_F(InputDispatcherSpyWindowTest, ReceivesInputInOrder) { break; // epoll_wait timed out } for (int i = 0; i < nFds; i++) { - ASSERT_EQ(EPOLLIN, events[i].events); + ASSERT_EQ(static_cast(EPOLLIN), events[i].events); eventOrder.push_back(static_cast(events[i].data.u64)); channels[i]->consumeMotionDown(); } diff --git a/services/inputflinger/tests/UinputDevice.cpp b/services/inputflinger/tests/UinputDevice.cpp index 7862b5839c..a23c873656 100644 --- a/services/inputflinger/tests/UinputDevice.cpp +++ b/services/inputflinger/tests/UinputDevice.cpp @@ -59,11 +59,11 @@ void UinputDevice::init() { } void UinputDevice::injectEvent(uint16_t type, uint16_t code, int32_t value) { + // uinput ignores the timestamp struct input_event event = {}; event.type = type; event.code = code; event.value = value; - event.time = {}; // uinput ignores the timestamp if (write(mDeviceFd, &event, sizeof(input_event)) < 0) { std::string msg = base::StringPrintf("Could not write event %" PRIu16 " %" PRIu16 -- GitLab From d3d4060123e774d38f69c85d7ae4cb3d66f0e2d8 Mon Sep 17 00:00:00 2001 From: Sandro Meier Date: Wed, 19 Oct 2022 16:18:26 +0000 Subject: [PATCH 0383/1310] Add INVALID_INPUT_DEVICE_ID to IInputConstants The ID is used to reference invalid input devices before they are initialized. For consistency the INVALID_INPUT_DEVICE_ID is also added to the ReservcedInputDeviceId enum. Test: Tested as part of ag/20002215 Bug: 244516415 Change-Id: I2055e8668780ecbcfdc201422fc1fd5ab3549ab6 --- include/input/InputDevice.h | 3 +++ libs/input/android/os/IInputConstants.aidl | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h index f4a15238cf..ac9c5a5a6f 100644 --- a/include/input/InputDevice.h +++ b/include/input/InputDevice.h @@ -23,6 +23,7 @@ #include #include +#include #include "android/hardware/input/InputDeviceCountryCode.h" namespace android { @@ -346,6 +347,8 @@ extern std::string getInputDeviceConfigurationFilePathByName( const std::string& name, InputDeviceConfigurationFileType type); enum ReservedInputDeviceId : int32_t { + // Device id representing an invalid device + INVALID_INPUT_DEVICE_ID = android::os::IInputConstants::INVALID_INPUT_DEVICE_ID, // Device id of a special "virtual" keyboard that is always present. VIRTUAL_KEYBOARD_ID = -1, // Device id of the "built-in" keyboard if there is one. diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl index 5ce10a4a50..dab843b48f 100644 --- a/libs/input/android/os/IInputConstants.aidl +++ b/libs/input/android/os/IInputConstants.aidl @@ -34,6 +34,14 @@ interface IInputConstants */ const int INVALID_INPUT_EVENT_ID = 0; + /** + * Every input device has an id. This constant value is used when a valid input device id is not + * available. + * The virtual keyboard uses -1 as the input device id. Therefore, we use -2 as the value for + * an invalid input device. + */ + const int INVALID_INPUT_DEVICE_ID = -2; + /** * The input event was injected from accessibility. Used in policyFlags for input event * injection. -- GitLab From 82bafb324d829829f58cb55447b33c8e27fbceb4 Mon Sep 17 00:00:00 2001 From: Chia-I Wu Date: Wed, 19 Oct 2022 14:41:59 -0700 Subject: [PATCH 0384/1310] SF: set highp on outTexCoords On implementations that use half floats for outTexCoords, the precision is not enough when the layer is > 1024 in either dimension. Bug: 253540001 Test: screencap, CTS Change-Id: Ic712454b04e58cb93334a90377cacf013734a95a --- libs/renderengine/gl/ProgramCache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/renderengine/gl/ProgramCache.cpp b/libs/renderengine/gl/ProgramCache.cpp index 5ff92402dc..f7f2d54515 100644 --- a/libs/renderengine/gl/ProgramCache.cpp +++ b/libs/renderengine/gl/ProgramCache.cpp @@ -601,7 +601,7 @@ String8 ProgramCache::generateFragmentShader(const Key& needs) { } if (needs.hasTextureCoords()) { - fs << "varying vec2 outTexCoords;"; + fs << "varying highp vec2 outTexCoords;"; } if (needs.hasRoundedCorners()) { -- GitLab From 5d60bbec116473860102c4f6602f254ddc7d147d Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 18 Oct 2022 16:59:10 -0700 Subject: [PATCH 0385/1310] LayerTraceGenerator: fix log spam from stubbed sf Test: sh ${ANDROID_BUILD_TOP}/frameworks/native/services/surfaceflinger/Tracing/tools/run.sh and check logcat Bug: 253555650 Change-Id: I5ca75c1762a5b6b4946b0bd6dac9bfe1c042a18d --- services/surfaceflinger/Tracing/tools/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/surfaceflinger/Tracing/tools/main.cpp b/services/surfaceflinger/Tracing/tools/main.cpp index f3cf42d7ab..9f9ae4815b 100644 --- a/services/surfaceflinger/Tracing/tools/main.cpp +++ b/services/surfaceflinger/Tracing/tools/main.cpp @@ -52,6 +52,10 @@ int main(int argc, char** argv) { ; ALOGD("Generating %s...", outputLayersTracePath); std::cout << "Generating " << outputLayersTracePath << "\n"; + + // sink any log spam from the stubbed surfaceflinger + __android_log_set_logger([](const struct __android_log_message* /* log_message */) {}); + if (!LayerTraceGenerator().generate(transactionTraceFile, outputLayersTracePath)) { std::cout << "Error: Failed to generate layers trace " << outputLayersTracePath; return -1; -- GitLab From 22491b8da91f46db499208f80b5eea5423db330c Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 18 Oct 2022 14:59:14 -0700 Subject: [PATCH 0386/1310] SF: Fix onSurfaceFrameCreated for layers without buffers Previously we did not create surfaceframes for layers without buffers. This behavior regressed with flattening of the layer types and causes some jank classifications to be eviceted from mPendingJankClassifications list which has a max value of 25. Fix this by checking if the layer has a buffer and also while we are here, increase the max to 50 in anticipation of multiple displays and multiple layers. Test: presubmit, logcat for no logspam Bug: 253555650 Change-Id: I0be3d7706305e059074b9efc14428316bdb41e6d --- services/surfaceflinger/Layer.cpp | 5 ++++- services/surfaceflinger/Layer.h | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 410e43846d..420fd5651f 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -1551,7 +1551,6 @@ std::shared_ptr Layer::createSurfaceFrameForBuffer( if (fps) { surfaceFrame->setRenderRate(*fps); } - // TODO(b/178542907): Implement onSurfaceFrameCreated for BQLayer as well. onSurfaceFrameCreated(surfaceFrame); return surfaceFrame; } @@ -2973,6 +2972,10 @@ void Layer::onLayerDisplayed(ftl::SharedFuture futureFenceResult) { void Layer::onSurfaceFrameCreated( const std::shared_ptr& surfaceFrame) { + if (!hasBufferOrSidebandStreamInDrawing()) { + return; + } + while (mPendingJankClassifications.size() >= kPendingClassificationMaxSurfaceFrames) { // Too many SurfaceFrames pending classification. The front of the deque is probably not // tracked by FrameTimeline and will never be presented. This will only result in a memory diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 8ace8123f9..d94295d341 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -1140,6 +1140,10 @@ private: return ((mSidebandStream != nullptr) || (mBufferInfo.mBuffer != nullptr)); } + bool hasBufferOrSidebandStreamInDrawing() const { + return ((mDrawingState.sidebandStream != nullptr) || (mDrawingState.buffer != nullptr)); + } + bool hasSomethingToDraw() const { return hasEffect() || hasBufferOrSidebandStream(); } void prepareBufferStateClientComposition( compositionengine::LayerFE::LayerSettings&, @@ -1219,7 +1223,7 @@ private: std::deque> mPendingJankClassifications; // An upper bound on the number of SurfaceFrames in the pending classifications deque. - static constexpr int kPendingClassificationMaxSurfaceFrames = 25; + static constexpr int kPendingClassificationMaxSurfaceFrames = 50; const std::string mBlastTransactionName{"BufferTX - " + mName}; // This integer is incremented everytime a buffer arrives at the server for this layer, -- GitLab From 60fb5c10058d9a4cb0d9a8733522af38be03f90a Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 19 Oct 2022 16:20:47 -0700 Subject: [PATCH 0387/1310] SF: Only update layer snapshots if there is something to draw Avoid doing unnecessary work when preparing composition state. Fixing a regression from the recent frontend refactors. Bug: 246680021, 245680157, 238781169 Test: simpleperf with bouncy ball Change-Id: I3f0bf29bf10ae3ec2d84b851f409ced023410e45 --- services/surfaceflinger/SurfaceFlinger.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 6cd57c4e53..5a0f87f9d0 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2179,8 +2179,8 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) std::vector layers; mDrawingState.traverseInZOrder([&refreshArgs, &layers](Layer* layer) { - layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame); if (auto layerFE = layer->getCompositionEngineLayerFE()) { + layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame); refreshArgs.layers.push_back(layerFE); layers.push_back(layer); } @@ -6447,6 +6447,10 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( std::vector renderedLayers; bool disableBlurs = false; traverseLayers([&](Layer* layer) FTL_FAKE_GUARD(kMainThreadContext) { + auto layerFE = layer->getCompositionEngineLayerFE(); + if (!layerFE) { + return; + } // Layer::prepareClientComposition uses the layer's snapshot to populate the resulting // LayerSettings. Calling Layer::updateSnapshot ensures that LayerSettings are // generated with the layer's current buffer and geometry. @@ -6472,11 +6476,6 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( isHdrLayer(layer) ? displayBrightnessNits : sdrWhitePointNits, }; - auto layerFE = layer->getCompositionEngineLayerFE(); - if (!layerFE) { - return; - } - std::optional settings; { LayerSnapshotGuard layerSnapshotGuard(layer); -- GitLab From 71fcf918ac5b8b3f870451e547aca25982d7cfe8 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 18 Oct 2022 09:14:20 -0700 Subject: [PATCH 0388/1310] SF: Avoid updating clients with stale or incorrect transform hints When the layer is removed from a display or the display the layer is on is turned off, the client will continue to receive transform hint updates via the transaction complete callback using the default/active displays install orientation. Once the layer is back on the display and it does not submit a new frame, a buffer with a suboptimal transform may remain on display. Fix this by not reporting stale/incorrect values via the callback. Once the layer is reparent back to the display and the display state is not OFF, it will continue to get hints via the callback. For special cases where we want the app to draw its first frame before the display is available, we rely on WMS and DMS to provide the right information so the client can calculate the hint. Bug: 251360251 Test: move app between displays, rotate, check final buffer transforms Change-Id: I0a9abac7e9cf4ade1c49ec400e73b634c8269b4b --- libs/gui/BLASTBufferQueue.cpp | 8 ++++--- libs/gui/ITransactionCompletedListener.cpp | 21 +++++++++++++++++-- libs/gui/SurfaceComposerClient.cpp | 5 +++-- .../gui/ITransactionCompletedListener.h | 4 ++-- libs/gui/include/gui/SurfaceComposerClient.h | 4 ++-- services/surfaceflinger/Layer.cpp | 10 ++++++++- services/surfaceflinger/Layer.h | 3 ++- services/surfaceflinger/SurfaceFlinger.cpp | 17 ++++++++------- .../TransactionCallbackInvoker.h | 3 ++- 9 files changed, 53 insertions(+), 22 deletions(-) diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index 8b9a878598..7fcb8e8b50 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -334,9 +334,11 @@ void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp statsOptional = findMatchingStat(stats, pendingSC); if (statsOptional) { SurfaceControlStats stat = *statsOptional; - mTransformHint = stat.transformHint; - mBufferItemConsumer->setTransformHint(mTransformHint); - BQA_LOGV("updated mTransformHint=%d", mTransformHint); + if (stat.transformHint) { + mTransformHint = *stat.transformHint; + mBufferItemConsumer->setTransformHint(mTransformHint); + BQA_LOGV("updated mTransformHint=%d", mTransformHint); + } // Update frametime stamps if the frame was latched and presented, indicated by a // valid latch time. if (stat.latchTime > 0) { diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp index e4b8bad8f8..712aefde35 100644 --- a/libs/gui/ITransactionCompletedListener.cpp +++ b/libs/gui/ITransactionCompletedListener.cpp @@ -17,6 +17,9 @@ #define LOG_TAG "ITransactionCompletedListener" //#define LOG_NDEBUG 0 +#include +#include + #include #include #include @@ -126,7 +129,12 @@ status_t SurfaceStats::writeToParcel(Parcel* output) const { } else { SAFE_PARCEL(output->writeBool, false); } - SAFE_PARCEL(output->writeUint32, transformHint); + + SAFE_PARCEL(output->writeBool, transformHint.has_value()); + if (transformHint.has_value()) { + output->writeUint32(transformHint.value()); + } + SAFE_PARCEL(output->writeUint32, currentMaxAcquiredBufferCount); SAFE_PARCEL(output->writeParcelable, eventStats); SAFE_PARCEL(output->writeInt32, static_cast(jankData.size())); @@ -156,7 +164,16 @@ status_t SurfaceStats::readFromParcel(const Parcel* input) { previousReleaseFence = new Fence(); SAFE_PARCEL(input->read, *previousReleaseFence); } - SAFE_PARCEL(input->readUint32, &transformHint); + bool hasTransformHint = false; + SAFE_PARCEL(input->readBool, &hasTransformHint); + if (hasTransformHint) { + uint32_t tempTransformHint; + SAFE_PARCEL(input->readUint32, &tempTransformHint); + transformHint = std::make_optional(tempTransformHint); + } else { + transformHint = std::nullopt; + } + SAFE_PARCEL(input->readUint32, ¤tMaxAcquiredBufferCount); SAFE_PARCEL(input->readParcelable, &eventStats); diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 81b4d3d85f..a957059220 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -385,10 +385,11 @@ void TransactionCompletedListener::onTransactionCompleted(ListenerStats listener surfaceStats.previousReleaseFence, surfaceStats.transformHint, surfaceStats.eventStats, surfaceStats.currentMaxAcquiredBufferCount); - if (callbacksMap[callbackId].surfaceControls[surfaceStats.surfaceControl]) { + if (callbacksMap[callbackId].surfaceControls[surfaceStats.surfaceControl] && + surfaceStats.transformHint.has_value()) { callbacksMap[callbackId] .surfaceControls[surfaceStats.surfaceControl] - ->setTransformHint(surfaceStats.transformHint); + ->setTransformHint(*surfaceStats.transformHint); } // If there is buffer id set, we look up any pending client release buffer callbacks // and call them. This is a performance optimization when we have a transaction diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h index cc136bb40a..162d3d36ab 100644 --- a/libs/gui/include/gui/ITransactionCompletedListener.h +++ b/libs/gui/include/gui/ITransactionCompletedListener.h @@ -132,7 +132,7 @@ public: SurfaceStats() = default; SurfaceStats(const sp& sc, std::variant> acquireTimeOrFence, - const sp& prevReleaseFence, uint32_t hint, + const sp& prevReleaseFence, std::optional hint, uint32_t currentMaxAcquiredBuffersCount, FrameEventHistoryStats frameEventStats, std::vector jankData, ReleaseCallbackId previousReleaseCallbackId) : surfaceControl(sc), @@ -147,7 +147,7 @@ public: sp surfaceControl; std::variant> acquireTimeOrFence = -1; sp previousReleaseFence; - uint32_t transformHint = 0; + std::optional transformHint = 0; uint32_t currentMaxAcquiredBufferCount = 0; FrameEventHistoryStats eventStats; std::vector jankData; diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index d138d6832b..36dc3b773b 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -69,7 +69,7 @@ struct SurfaceControlStats { SurfaceControlStats(const sp& sc, nsecs_t latchTime, std::variant> acquireTimeOrFence, const sp& presentFence, const sp& prevReleaseFence, - uint32_t hint, FrameEventHistoryStats eventStats, + std::optional hint, FrameEventHistoryStats eventStats, uint32_t currentMaxAcquiredBufferCount) : surfaceControl(sc), latchTime(latchTime), @@ -85,7 +85,7 @@ struct SurfaceControlStats { std::variant> acquireTimeOrFence = -1; sp presentFence; sp previousReleaseFence; - uint32_t transformHint = 0; + std::optional transformHint = 0; FrameEventHistoryStats frameEventStats; uint32_t currentMaxAcquiredBufferCount = 0; }; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 410e43846d..ca330e46c7 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -63,6 +63,7 @@ #include #include +#include #include #include "DisplayDevice.h" @@ -1613,6 +1614,10 @@ uint32_t Layer::getEffectiveUsage(uint32_t usage) const { return usage; } +void Layer::skipReportingTransformHint() { + mSkipReportingTransformHint = true; +} + void Layer::updateTransformHint(ui::Transform::RotationFlags transformHint) { if (mFlinger->mDebugDisableTransformHint || transformHint & ui::Transform::ROT_INVALID) { transformHint = ui::Transform::ROT_0; @@ -2988,7 +2993,9 @@ void Layer::onSurfaceFrameCreated( void Layer::releasePendingBuffer(nsecs_t dequeueReadyTime) { for (const auto& handle : mDrawingState.callbackHandles) { - handle->transformHint = mTransformHint; + handle->transformHint = mSkipReportingTransformHint + ? std::nullopt + : std::make_optional(mTransformHint); handle->dequeueReadyTime = dequeueReadyTime; handle->currentMaxAcquiredBufferCount = mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mOwnerUid); @@ -4170,6 +4177,7 @@ void Layer::setTransformHint(ui::Transform::RotationFlags displayTransformHint) if (mTransformHint == ui::Transform::ROT_INVALID) { mTransformHint = displayTransformHint; } + mSkipReportingTransformHint = false; } const std::shared_ptr& Layer::getExternalTexture() const { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 8ace8123f9..e5fa83156f 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -723,7 +723,7 @@ public: * Sets display transform hint on BufferLayerConsumer. */ void updateTransformHint(ui::Transform::RotationFlags); - + void skipReportingTransformHint(); inline const State& getDrawingState() const { return mDrawingState; } inline State& getDrawingState() { return mDrawingState; } @@ -1205,6 +1205,7 @@ private: // Transform hint provided to the producer. This must be accessed holding // the mStateLock. ui::Transform::RotationFlags mTransformHint = ui::Transform::ROT_0; + bool mSkipReportingTransformHint = true; ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID; uint64_t mPreviousReleasedFrameNumber = 0; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 6167378614..62eca56a64 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3117,20 +3117,21 @@ void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { } } - if (!hintDisplay && mDisplays.size() > 0) { + if (!hintDisplay) { // NOTE: TEMPORARY FIX ONLY. Real fix should cause layers to // redraw after transform hint changes. See bug 8508397. - // could be null when this layer is using a layerStack // that is not visible on any display. Also can occur at // screen off/on times. - hintDisplay = getDefaultDisplayDeviceLocked(); - } - - if (hintDisplay) { - layer->updateTransformHint(hintDisplay->getTransformHint()); + // U Update: Don't provide stale hints to the clients. For + // special cases where we want the app to draw its + // first frame before the display is available, we rely + // on WMS and DMS to provide the right information + // so the client can calculate the hint. + ALOGV("Skipping reporting transform hint update for %s", layer->getDebugName()); + layer->skipReportingTransformHint(); } else { - ALOGW("Ignoring transform hint update for %s", layer->getDebugName()); + layer->updateTransformHint(hintDisplay->getTransformHint()); } }); } diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h index 23ea7a551a..61ff9bce98 100644 --- a/services/surfaceflinger/TransactionCallbackInvoker.h +++ b/services/surfaceflinger/TransactionCallbackInvoker.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -48,7 +49,7 @@ public: std::vector> previousReleaseFences; std::variant> acquireTimeOrFence = -1; nsecs_t latchTime = -1; - uint32_t transformHint = 0; + std::optional transformHint = std::nullopt; uint32_t currentMaxAcquiredBufferCount = 0; std::shared_ptr gpuCompositionDoneFence{FenceTime::NO_FENCE}; CompositorTiming compositorTiming; -- GitLab From cb8be5077baca59c45b3a0975ace5944b251937a Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 12 Oct 2022 19:03:23 +0000 Subject: [PATCH 0389/1310] SF: Carve out LayerCreationArgs Move most layer creation logic outside of Layer. Specify layer sequence id via creation args and move ownerUid/ownerPid into the layer creation args so we can share logic between the existing layer class and the new server layer state. Add layer parent and mirror from handles to be used with the new LayerLifecycleManager. Bug: 238781169 Test: presubmit Change-Id: I7cf344181b29f405c070cda2ad45f06233fd1e8c --- libs/gui/include/gui/LayerMetadata.h | 3 +- services/surfaceflinger/Android.bp | 1 + services/surfaceflinger/Client.cpp | 4 +- .../FrontEnd/LayerCreationArgs.cpp | 62 +++++++++++++++++++ .../FrontEnd/LayerCreationArgs.h | 56 +++++++++++++++++ services/surfaceflinger/Layer.cpp | 38 ++---------- services/surfaceflinger/Layer.h | 22 ------- services/surfaceflinger/LayerRenderArea.cpp | 1 + services/surfaceflinger/SurfaceFlinger.cpp | 42 ++++++------- services/surfaceflinger/SurfaceFlinger.h | 8 +-- .../surfaceflinger/SurfaceFlingerFactory.h | 2 +- .../Tracing/tools/LayerTraceGenerator.cpp | 4 +- ...linger_UpdateLayerMetadataSnapshotTest.cpp | 58 ++++++++++------- .../tests/unittests/TestableSurfaceFlinger.h | 4 +- 14 files changed, 197 insertions(+), 108 deletions(-) create mode 100644 services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp create mode 100644 services/surfaceflinger/FrontEnd/LayerCreationArgs.h diff --git a/libs/gui/include/gui/LayerMetadata.h b/libs/gui/include/gui/LayerMetadata.h index 5af598956b..e16f89c6a5 100644 --- a/libs/gui/include/gui/LayerMetadata.h +++ b/libs/gui/include/gui/LayerMetadata.h @@ -30,7 +30,8 @@ enum { METADATA_ACCESSIBILITY_ID = 5, METADATA_OWNER_PID = 6, METADATA_DEQUEUE_TIME = 7, - METADATA_GAME_MODE = 8 + METADATA_GAME_MODE = 8, + METADATA_CALLING_UID = 9, }; struct LayerMetadata : public Parcelable { diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index c9c0143c7d..b65f1b48e3 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -155,6 +155,7 @@ filegroup { "DisplayRenderArea.cpp", "Effects/Daltonizer.cpp", "EventLog/EventLog.cpp", + "FrontEnd/LayerCreationArgs.cpp", "FrontEnd/TransactionHandler.cpp", "FlagManager.cpp", "FpsReporter.cpp", diff --git a/services/surfaceflinger/Client.cpp b/services/surfaceflinger/Client.cpp index 30b875967c..7202bef606 100644 --- a/services/surfaceflinger/Client.cpp +++ b/services/surfaceflinger/Client.cpp @@ -24,6 +24,7 @@ #include #include "Client.h" +#include "FrontEnd/LayerCreationArgs.h" #include "Layer.h" #include "SurfaceFlinger.h" @@ -83,7 +84,8 @@ binder::Status Client::createSurface(const std::string& name, int32_t flags, sp handle; LayerCreationArgs args(mFlinger.get(), sp::fromExisting(this), name.c_str(), static_cast(flags), std::move(metadata)); - const status_t status = mFlinger->createLayer(args, parent, *outResult); + args.parentHandle = parent; + const status_t status = mFlinger->createLayer(args, *outResult); return binderStatusFromStatusT(status); } diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp new file mode 100644 index 0000000000..6d492c0f4a --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp @@ -0,0 +1,62 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "LayerCreationArgs.h" +#include +#include +#include "Client.h" +#include "gui/LayerMetadata.h" + +namespace android::surfaceflinger { + +std::atomic LayerCreationArgs::sSequence{1}; + +LayerCreationArgs::LayerCreationArgs(SurfaceFlinger* flinger, sp client, std::string name, + uint32_t flags, gui::LayerMetadata metadataArg, + std::optional id) + : flinger(flinger), + client(std::move(client)), + name(std::move(name)), + flags(flags), + metadata(std::move(metadataArg)) { + IPCThreadState* ipc = IPCThreadState::self(); + ownerPid = ipc->getCallingPid(); + uid_t callingUid = ipc->getCallingUid(); + metadata.setInt32(gui::METADATA_CALLING_UID, static_cast(callingUid)); + ownerUid = callingUid; + if (ownerUid == AID_GRAPHICS || ownerUid == AID_SYSTEM) { + // System can override the calling UID/PID since it can create layers on behalf of apps. + ownerPid = metadata.getInt32(gui::METADATA_OWNER_PID, ownerPid); + ownerUid = static_cast( + metadata.getInt32(gui::METADATA_OWNER_UID, static_cast(ownerUid))); + } + + if (id) { + sequence = *id; + sSequence = *id + 1; + } else { + sequence = sSequence++; + if (sequence == UNASSIGNED_LAYER_ID) { + ALOGW("Layer sequence id rolled over."); + sequence = sSequence++; + } + } +} + +LayerCreationArgs::LayerCreationArgs(const LayerCreationArgs& args) + : LayerCreationArgs(args.flinger, args.client, args.name, args.flags, args.metadata) {} + +} // namespace android::surfaceflinger diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h new file mode 100644 index 0000000000..7b5a157871 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h @@ -0,0 +1,56 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +constexpr uint32_t UNASSIGNED_LAYER_ID = std::numeric_limits::max(); + +namespace android { +class SurfaceFlinger; +class Client; +} // namespace android + +namespace android::surfaceflinger { + +struct LayerCreationArgs { + static std::atomic sSequence; + + LayerCreationArgs(android::SurfaceFlinger*, sp, std::string name, + uint32_t flags, gui::LayerMetadata, + std::optional id = std::nullopt); + LayerCreationArgs(const LayerCreationArgs&); + + android::SurfaceFlinger* flinger; + sp client; + std::string name; + uint32_t flags; // ISurfaceComposerClient flags + gui::LayerMetadata metadata; + pid_t ownerPid; + uid_t ownerUid; + uint32_t textureName; + uint32_t sequence; + bool addToRoot = true; + wp parentHandle = nullptr; + wp mirrorLayerHandle = nullptr; +}; + +} // namespace android::surfaceflinger diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index c2a0e3082d..c79b7f5a08 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -68,6 +68,7 @@ #include "DisplayHardware/HWComposer.h" #include "FrameTimeline.h" #include "FrameTracer/FrameTracer.h" +#include "FrontEnd/LayerCreationArgs.h" #include "LayerProtoHelper.h" #include "SurfaceFlinger.h" #include "TimeStats/TimeStats.h" @@ -131,10 +132,8 @@ using gui::WindowInfo; using PresentState = frametimeline::SurfaceFrame::PresentState; -std::atomic Layer::sSequence{1}; - Layer::Layer(const LayerCreationArgs& args) - : sequence(args.sequence.value_or(sSequence++)), + : sequence(args.sequence), mFlinger(sp::fromExisting(args.flinger)), mName(base::StringPrintf("%s#%d", args.name.c_str(), sequence)), mClientRef(args.client), @@ -153,9 +152,6 @@ Layer::Layer(const LayerCreationArgs& args) if (args.flags & ISurfaceComposerClient::eSecure) layerFlags |= layer_state_t::eLayerSecure; if (args.flags & ISurfaceComposerClient::eSkipScreenshot) layerFlags |= layer_state_t::eLayerSkipScreenshot; - if (args.sequence) { - sSequence = *args.sequence + 1; - } mDrawingState.flags = layerFlags; mDrawingState.crop.makeInvalid(); mDrawingState.z = 0; @@ -200,18 +196,8 @@ Layer::Layer(const LayerCreationArgs& args) mFrameTracker.setDisplayRefreshPeriod( args.flinger->mScheduler->getVsyncPeriodFromRefreshRateConfigs()); - mCallingPid = args.callingPid; - mCallingUid = args.callingUid; - - if (mCallingUid == AID_GRAPHICS || mCallingUid == AID_SYSTEM) { - // If the system didn't send an ownerUid, use the callingUid for the ownerUid. - mOwnerUid = args.metadata.getInt32(gui::METADATA_OWNER_UID, mCallingUid); - mOwnerPid = args.metadata.getInt32(gui::METADATA_OWNER_PID, mCallingPid); - } else { - // A create layer request from a non system request cannot specify the owner uid - mOwnerUid = mCallingUid; - mOwnerPid = mCallingPid; - } + mOwnerUid = args.ownerUid; + mOwnerPid = args.ownerPid; mPremultipliedAlpha = !(args.flags & ISurfaceComposerClient::eNonPremultiplied); mPotentialCursor = args.flags & ISurfaceComposerClient::eCursorWindow; @@ -268,18 +254,6 @@ Layer::~Layer() { } } -LayerCreationArgs::LayerCreationArgs(SurfaceFlinger* flinger, sp client, std::string name, - uint32_t flags, LayerMetadata metadata) - : flinger(flinger), - client(std::move(client)), - name(std::move(name)), - flags(flags), - metadata(std::move(metadata)) { - IPCThreadState* ipc = IPCThreadState::self(); - callingPid = ipc->getCallingPid(); - callingUid = ipc->getCallingUid(); -} - // --------------------------------------------------------------------------- // callbacks // --------------------------------------------------------------------------- @@ -1498,8 +1472,8 @@ void Layer::getFrameStats(FrameStats* outStats) const { } void Layer::dumpCallingUidPid(std::string& result) const { - StringAppendF(&result, "Layer %s (%s) callingPid:%d callingUid:%d ownerUid:%d\n", - getName().c_str(), getType(), mCallingPid, mCallingUid, mOwnerUid); + StringAppendF(&result, "Layer %s (%s) ownerPid:%d ownerUid:%d\n", getName().c_str(), getType(), + mOwnerPid, mOwnerUid); } void Layer::onDisconnect() { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index da8be6b8f0..ba13444532 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -82,24 +82,7 @@ namespace frametimeline { class SurfaceFrame; } // namespace frametimeline -struct LayerCreationArgs { - LayerCreationArgs(SurfaceFlinger*, sp, std::string name, uint32_t flags, LayerMetadata); - - SurfaceFlinger* flinger; - const sp client; - std::string name; - uint32_t flags; - LayerMetadata metadata; - - pid_t callingPid; - uid_t callingUid; - uint32_t textureName; - std::optional sequence = std::nullopt; - bool addToRoot = true; -}; - class Layer : public virtual RefBase { - static std::atomic sSequence; // The following constants represent priority of the window. SF uses this information when // deciding which window has a priority when deciding about the refresh rate of the screen. // Priority 0 is considered the highest priority. -1 means that the priority is unset. @@ -1113,11 +1096,6 @@ private: bool mGetHandleCalled = false; - // Tracks the process and user id of the caller when creating this layer - // to help debugging. - pid_t mCallingPid; - uid_t mCallingUid; - // The current layer is a clone of mClonedFrom. This means that this layer will update it's // properties based on mClonedFrom. When mClonedFrom latches a new buffer for BufferLayers, // this layer will update it's buffer. When mClonedFrom updates it's drawing state, children, diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp index 3e6ed416fc..554fae401e 100644 --- a/services/surfaceflinger/LayerRenderArea.cpp +++ b/services/surfaceflinger/LayerRenderArea.cpp @@ -18,6 +18,7 @@ #include #include "DisplayDevice.h" +#include "FrontEnd/LayerCreationArgs.h" #include "Layer.h" #include "LayerRenderArea.h" #include "SurfaceFlinger.h" diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index a04ceefaf4..a558a99461 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -122,6 +122,7 @@ #include "FpsReporter.h" #include "FrameTimeline/FrameTimeline.h" #include "FrameTracer/FrameTracer.h" +#include "FrontEnd/LayerCreationArgs.h" #include "HdrLayerInfoReporter.h" #include "Layer.h" #include "LayerProtoHelper.h" @@ -3601,9 +3602,9 @@ bool SurfaceFlinger::latchBuffers() { return !mLayersWithQueuedFrames.empty() && newDataLatched; } -status_t SurfaceFlinger::addClientLayer(const sp& client, const sp& handle, +status_t SurfaceFlinger::addClientLayer(const LayerCreationArgs& args, const sp& handle, const sp& layer, const wp& parent, - bool addToRoot, uint32_t* outTransformHint) { + uint32_t* outTransformHint) { if (mNumLayers >= MAX_LAYERS) { ALOGE("AddClientLayer failed, mNumLayers (%zu) >= MAX_LAYERS (%zu)", mNumLayers.load(), MAX_LAYERS); @@ -3634,12 +3635,12 @@ status_t SurfaceFlinger::addClientLayer(const sp& client, const sp lock(mCreatedLayersLock); - mCreatedLayers.emplace_back(layer, parent, addToRoot); + mCreatedLayers.emplace_back(layer, parent, args.addToRoot); } // attach this layer to the client - if (client != nullptr) { - client->attachLayer(handle, layer); + if (args.client != nullptr) { + args.client->attachLayer(handle, layer); } setTransactionFlags(eTransactionNeeded); @@ -4403,8 +4404,10 @@ status_t SurfaceFlinger::mirrorLayer(const LayerCreationArgs& args, if (!mirrorFrom) { return NAME_NOT_FOUND; } - LayerCreationArgs mirrorArgs = args; + LayerCreationArgs mirrorArgs(args); mirrorArgs.flags |= ISurfaceComposerClient::eNoColorFill; + mirrorArgs.mirrorLayerHandle = mirrorFromHandle; + mirrorArgs.addToRoot = false; status_t result = createEffectLayer(mirrorArgs, &outResult.handle, &mirrorLayer); if (result != NO_ERROR) { return result; @@ -4420,8 +4423,7 @@ status_t SurfaceFlinger::mirrorLayer(const LayerCreationArgs& args, mirrorLayer->sequence, args.name, mirrorFrom->sequence); } - return addClientLayer(args.client, outResult.handle, mirrorLayer /* layer */, - nullptr /* parent */, false /* addToRoot */, + return addClientLayer(args, outResult.handle, mirrorLayer /* layer */, nullptr /* parent */, nullptr /* outTransformHint */); } @@ -4447,14 +4449,14 @@ status_t SurfaceFlinger::mirrorDisplay(DisplayId displayId, const LayerCreationA } layerStack = display->getLayerStack(); - LayerCreationArgs mirrorArgs = args; + LayerCreationArgs mirrorArgs(args); mirrorArgs.flags |= ISurfaceComposerClient::eNoColorFill; + mirrorArgs.addToRoot = true; result = createEffectLayer(mirrorArgs, &outResult.handle, &rootMirrorLayer); outResult.layerId = rootMirrorLayer->sequence; outResult.layerName = String16(rootMirrorLayer->getDebugName()); - result |= addClientLayer(args.client, outResult.handle, rootMirrorLayer /* layer */, - nullptr /* parent */, true /* addToRoot */, - nullptr /* outTransformHint */); + result |= addClientLayer(args, outResult.handle, rootMirrorLayer /* layer */, + nullptr /* parent */, nullptr /* outTransformHint */); } if (result != NO_ERROR) { @@ -4475,8 +4477,7 @@ status_t SurfaceFlinger::mirrorDisplay(DisplayId displayId, const LayerCreationA return NO_ERROR; } -status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, const sp& parentHandle, - gui::CreateSurfaceResult& outResult) { +status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, gui::CreateSurfaceResult& outResult) { status_t result = NO_ERROR; sp layer; @@ -4505,11 +4506,11 @@ status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, const sp& return result; } - bool addToRoot = args.addToRoot && callingThreadHasUnscopedSurfaceFlingerAccess(); - wp parent = fromHandle(parentHandle); - if (parentHandle != nullptr && parent == nullptr) { - ALOGE("Invalid parent handle %p.", parentHandle.get()); - addToRoot = false; + args.addToRoot = args.addToRoot && callingThreadHasUnscopedSurfaceFlingerAccess(); + wp parent = fromHandle(args.parentHandle.promote()); + if (args.parentHandle != nullptr && parent == nullptr) { + ALOGE("Invalid parent handle %p.", args.parentHandle.promote().get()); + args.addToRoot = false; } int parentId = -1; @@ -4525,8 +4526,7 @@ status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, const sp& } uint32_t outTransformHint; - result = addClientLayer(args.client, outResult.handle, layer, parent, addToRoot, - &outTransformHint); + result = addClientLayer(args, outResult.handle, layer, parent, &outTransformHint); if (result != NO_ERROR) { return result; } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index d512841e9f..1d50830ce2 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -65,6 +65,7 @@ #include "DisplayIdGenerator.h" #include "Effects/Daltonizer.h" #include "FlagManager.h" +#include "FrontEnd/LayerCreationArgs.h" #include "FrontEnd/TransactionHandler.h" #include "LayerVector.h" #include "Scheduler/RefreshRateConfigs.h" @@ -755,8 +756,7 @@ private: /* * Layer management */ - status_t createLayer(LayerCreationArgs& args, const sp& parentHandle, - gui::CreateSurfaceResult& outResult); + status_t createLayer(LayerCreationArgs& args, gui::CreateSurfaceResult& outResult); status_t createBufferStateLayer(LayerCreationArgs& args, sp* outHandle, sp* outLayer); @@ -777,8 +777,8 @@ private: void markLayerPendingRemovalLocked(const sp& layer); // add a layer to SurfaceFlinger - status_t addClientLayer(const sp& client, const sp& handle, - const sp& lbc, const wp& parentLayer, bool addToRoot, + status_t addClientLayer(const LayerCreationArgs& args, const sp& handle, + const sp& layer, const wp& parentLayer, uint32_t* outTransformHint); // Traverse through all the layers and compute and cache its bounds. diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h index d1f21bfb43..41edd22102 100644 --- a/services/surfaceflinger/SurfaceFlingerFactory.h +++ b/services/surfaceflinger/SurfaceFlingerFactory.h @@ -44,7 +44,6 @@ class SurfaceFlinger; class TimeStats; struct DisplayDeviceCreationArgs; -struct LayerCreationArgs; namespace compositionengine { class CompositionEngine; @@ -62,6 +61,7 @@ class FrameTimeline; namespace surfaceflinger { +struct LayerCreationArgs; class NativeWindowSurface; // The interface that SurfaceFlinger uses to create all of the implementations diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index 276b3dad18..033f5b45d9 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -213,8 +213,8 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, gui::CreateSurfaceResult outResult; LayerCreationArgs args(mFlinger.flinger(), nullptr /* client */, tracingArgs.name, - tracingArgs.flags, LayerMetadata()); - args.sequence = std::make_optional(tracingArgs.layerId); + tracingArgs.flags, LayerMetadata(), + std::make_optional(tracingArgs.layerId)); if (tracingArgs.mirrorFromId == -1) { sp parentHandle = nullptr; diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp index 0cf3bdf0d5..fed6a1ae56 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp @@ -44,9 +44,11 @@ protected: std::move(eventThread), std::move(sfEventThread)); } - sp createLayer(const char* name, LayerMetadata layerMetadata) { - return sp::make( - LayerCreationArgs{mFlinger.flinger(), nullptr, name, 0, layerMetadata}); + sp createLayer(const char* name, LayerMetadata& inOutlayerMetadata) { + LayerCreationArgs args = + LayerCreationArgs{mFlinger.flinger(), nullptr, name, 0, inOutlayerMetadata}; + inOutlayerMetadata = args.metadata; + return sp::make(args); } TestableSurfaceFlinger mFlinger; @@ -90,7 +92,7 @@ TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, updatesSnapshotMetadata) { mFlinger.updateLayerMetadataSnapshot(); - ASSERT_EQ(layer->getLayerSnapshot()->layerMetadata, layerMetadata); + EXPECT_EQ(layer->getLayerSnapshot()->layerMetadata, layerMetadata); } // Test that snapshot layer metadata is set by merging the child's metadata on top of its @@ -109,10 +111,10 @@ TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, mergesSnapshotMetadata) { mFlinger.updateLayerMetadataSnapshot(); - ASSERT_EQ(layerA->getLayerSnapshot()->layerMetadata, layerAMetadata); + EXPECT_EQ(layerA->getLayerSnapshot()->layerMetadata, layerAMetadata); auto expectedChildMetadata = LayerMetadataBuilder(layerAMetadata).setInt32(METADATA_TASK_ID, 3).build(); - ASSERT_EQ(layerB->getLayerSnapshot()->layerMetadata, expectedChildMetadata); + EXPECT_EQ(layerB->getLayerSnapshot()->layerMetadata, expectedChildMetadata); } // Test that snapshot relative layer metadata is set to the parent's layer metadata merged on top of @@ -129,8 +131,8 @@ TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, updatesRelativeMetadata) { mFlinger.updateLayerMetadataSnapshot(); - ASSERT_EQ(layerA->getLayerSnapshot()->relativeLayerMetadata, LayerMetadata{}); - ASSERT_EQ(layerB->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata); + EXPECT_EQ(layerA->getLayerSnapshot()->relativeLayerMetadata, LayerMetadata{}); + EXPECT_EQ(layerB->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata); } // Test that snapshot relative layer metadata is set correctly when a layer is interleaved within @@ -154,7 +156,8 @@ TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, updatesRelativeMetadataInt .build(); auto layerB = createLayer("layer-b", layerBMetadata); auto layerBHandle = layerB->getHandle(); - auto layerC = createLayer("layer-c", {}); + LayerMetadata layerCMetadata; + auto layerC = createLayer("layer-c", layerCMetadata); auto layerDMetadata = LayerMetadataBuilder().setInt32(METADATA_TASK_ID, 4).build(); auto layerD = createLayer("layer-d", layerDMetadata); auto layerDHandle = layerD->getHandle(); @@ -168,14 +171,18 @@ TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, updatesRelativeMetadataInt mFlinger.updateLayerMetadataSnapshot(); - auto expectedLayerDRelativeMetadata = LayerMetadataBuilder() - // From layer A, parent of relative parent - .setInt32(METADATA_OWNER_UID, 1) - // From layer B, relative parent - .setInt32(METADATA_TASK_ID, 2) - .setInt32(METADATA_OWNER_PID, 3) - .build(); - ASSERT_EQ(layerD->getLayerSnapshot()->relativeLayerMetadata, expectedLayerDRelativeMetadata); + auto expectedLayerDRelativeMetadata = + LayerMetadataBuilder() + // From layer A, parent of relative parent + .setInt32(METADATA_OWNER_UID, 1) + // From layer B, relative parent + .setInt32(METADATA_TASK_ID, 2) + .setInt32(METADATA_OWNER_PID, 3) + // added by layer creation args + .setInt32(gui::METADATA_CALLING_UID, + layerDMetadata.getInt32(gui::METADATA_CALLING_UID, 0)) + .build(); + EXPECT_EQ(layerD->getLayerSnapshot()->relativeLayerMetadata, expectedLayerDRelativeMetadata); auto expectedLayerCRelativeMetadata = LayerMetadataBuilder() // From layer A, parent of relative parent @@ -184,8 +191,11 @@ TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, updatesRelativeMetadataInt .setInt32(METADATA_OWNER_PID, 3) // From layer D, relative parent .setInt32(METADATA_TASK_ID, 4) + // added by layer creation args + .setInt32(gui::METADATA_CALLING_UID, + layerDMetadata.getInt32(gui::METADATA_CALLING_UID, 0)) .build(); - ASSERT_EQ(layerC->getLayerSnapshot()->relativeLayerMetadata, expectedLayerCRelativeMetadata); + EXPECT_EQ(layerC->getLayerSnapshot()->relativeLayerMetadata, expectedLayerCRelativeMetadata); } TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, @@ -193,8 +203,10 @@ TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, auto layerAMetadata = LayerMetadataBuilder().setInt32(METADATA_OWNER_UID, 1).build(); auto layerA = createLayer("layer-a", layerAMetadata); auto layerAHandle = layerA->getHandle(); - auto layerB = createLayer("layer-b", {}); - auto layerC = createLayer("layer-c", {}); + LayerMetadata layerBMetadata; + auto layerB = createLayer("layer-b", layerBMetadata); + LayerMetadata layerCMetadata; + auto layerC = createLayer("layer-c", layerCMetadata); layerB->setRelativeLayer(layerAHandle, 1); layerC->setRelativeLayer(layerAHandle, 2); layerA->commitChildList(); @@ -204,9 +216,9 @@ TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, mFlinger.updateLayerMetadataSnapshot(); - ASSERT_EQ(layerA->getLayerSnapshot()->relativeLayerMetadata, LayerMetadata{}); - ASSERT_EQ(layerB->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata); - ASSERT_EQ(layerC->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata); + EXPECT_EQ(layerA->getLayerSnapshot()->relativeLayerMetadata, LayerMetadata{}); + EXPECT_EQ(layerB->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata); + EXPECT_EQ(layerC->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata); } } // namespace android diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 83d852a1fa..51607b2331 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -33,6 +33,7 @@ #include "DisplayDevice.h" #include "FakeVsyncConfiguration.h" #include "FrameTracer/FrameTracer.h" +#include "FrontEnd/LayerCreationArgs.h" #include "Layer.h" #include "NativeWindowSurface.h" #include "Scheduler/MessageQueue.h" @@ -469,7 +470,8 @@ public: auto createLayer(LayerCreationArgs& args, const sp& parentHandle, gui::CreateSurfaceResult& outResult) { - return mFlinger->createLayer(args, parentHandle, outResult); + args.parentHandle = parentHandle; + return mFlinger->createLayer(args, outResult); } auto mirrorLayer(const LayerCreationArgs& args, const sp& mirrorFromHandle, -- GitLab From f1e5df1d266f70a508c7b520fd52feced8fbcf61 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Mon, 17 Oct 2022 21:37:42 +0000 Subject: [PATCH 0390/1310] SF: Trigger ANR when buffer cache is full * Updates the transaction queue stall listener to take a string that contains the reason for hanging. * Updates ClientCache::add to indicate whether or not a failure is due to the cache being full * Calls the transaction queue stall listener when the ClientCache is full Bug: 244218818 Test: presubmits Change-Id: I5fdc9aef0f0a1601ace1c42cfac5024c3de8d299 --- libs/gui/BLASTBufferQueue.cpp | 20 +++-- libs/gui/ITransactionCompletedListener.cpp | 16 ++-- libs/gui/SurfaceComposerClient.cpp | 29 +++---- libs/gui/include/gui/BLASTBufferQueue.h | 10 +-- .../gui/ITransactionCompletedListener.h | 3 +- libs/gui/include/gui/SurfaceComposerClient.h | 6 +- services/surfaceflinger/ClientCache.cpp | 23 +++--- services/surfaceflinger/ClientCache.h | 5 +- .../FrontEnd/TransactionHandler.cpp | 13 +-- .../FrontEnd/TransactionHandler.h | 3 +- services/surfaceflinger/SurfaceFlinger.cpp | 82 +++++++++++-------- services/surfaceflinger/SurfaceFlinger.h | 5 +- .../Tracing/tools/LayerTraceGenerator.cpp | 3 +- 13 files changed, 123 insertions(+), 95 deletions(-) diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index 8b9a878598..fc8aff87af 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -167,14 +167,15 @@ BLASTBufferQueue::BLASTBufferQueue(const std::string& name, bool updateDestinati mNumFrameAvailable = 0; TransactionCompletedListener::getInstance()->addQueueStallListener( - [&]() { - std::function callbackCopy; - { - std::unique_lock _lock{mMutex}; - callbackCopy = mTransactionHangCallback; - } - if (callbackCopy) callbackCopy(true); - }, this); + [&](const std::string& reason) { + std::function callbackCopy; + { + std::unique_lock _lock{mMutex}; + callbackCopy = mTransactionHangCallback; + } + if (callbackCopy) callbackCopy(reason); + }, + this); BQA_LOGV("BLASTBufferQueue created"); } @@ -1112,7 +1113,8 @@ bool BLASTBufferQueue::isSameSurfaceControl(const sp& surfaceCon return SurfaceControl::isSameSurface(mSurfaceControl, surfaceControl); } -void BLASTBufferQueue::setTransactionHangCallback(std::function callback) { +void BLASTBufferQueue::setTransactionHangCallback( + std::function callback) { std::unique_lock _lock{mMutex}; mTransactionHangCallback = callback; } diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp index ca91afa35d..2a114c82ee 100644 --- a/libs/gui/ITransactionCompletedListener.cpp +++ b/libs/gui/ITransactionCompletedListener.cpp @@ -273,15 +273,17 @@ public: void onReleaseBuffer(ReleaseCallbackId callbackId, sp releaseFence, uint32_t currentMaxAcquiredBufferCount) override { - callRemoteAsync(Tag::ON_RELEASE_BUFFER, - callbackId, releaseFence, - currentMaxAcquiredBufferCount); + callRemoteAsync(Tag::ON_RELEASE_BUFFER, callbackId, + releaseFence, + currentMaxAcquiredBufferCount); } - void onTransactionQueueStalled() override { - callRemoteAsync( - Tag::ON_TRANSACTION_QUEUE_STALLED); + void onTransactionQueueStalled(const String8& reason) override { + callRemoteAsync< + decltype(&ITransactionCompletedListener:: + onTransactionQueueStalled)>(Tag::ON_TRANSACTION_QUEUE_STALLED, + reason); } }; diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index a5879a77e1..9efdd35a52 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -455,23 +455,24 @@ void TransactionCompletedListener::onTransactionCompleted(ListenerStats listener } } -void TransactionCompletedListener::onTransactionQueueStalled() { - std::unordered_map> callbackCopy; - { - std::scoped_lock lock(mMutex); - callbackCopy = mQueueStallListeners; - } - for (auto const& it : callbackCopy) { - it.second(); - } -} - -void TransactionCompletedListener::addQueueStallListener(std::function stallListener, - void* id) { +void TransactionCompletedListener::onTransactionQueueStalled(const String8& reason) { + std::unordered_map> callbackCopy; + { + std::scoped_lock lock(mMutex); + callbackCopy = mQueueStallListeners; + } + for (auto const& it : callbackCopy) { + it.second(reason.c_str()); + } +} + +void TransactionCompletedListener::addQueueStallListener( + std::function stallListener, void* id) { std::scoped_lock lock(mMutex); mQueueStallListeners[id] = stallListener; } -void TransactionCompletedListener::removeQueueStallListener(void *id) { + +void TransactionCompletedListener::removeQueueStallListener(void* id) { std::scoped_lock lock(mMutex); mQueueStallListeners.erase(id); } diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h index 827a6cc5a9..957652e1f1 100644 --- a/libs/gui/include/gui/BLASTBufferQueue.h +++ b/libs/gui/include/gui/BLASTBufferQueue.h @@ -113,12 +113,10 @@ public: uint64_t getLastAcquiredFrameNum(); /** - * Set a callback to be invoked when we are hung. The boolean parameter - * indicates whether the hang is due to an unfired fence. - * TODO: The boolean is always true atm, unfired fence is - * the only case we detect. + * Set a callback to be invoked when we are hung. The string parameter + * indicates the reason for the hang. */ - void setTransactionHangCallback(std::function callback); + void setTransactionHangCallback(std::function callback); virtual ~BLASTBufferQueue(); @@ -282,7 +280,7 @@ private: bool mAppliedLastTransaction = false; uint64_t mLastAppliedFrameNumber = 0; - std::function mTransactionHangCallback; + std::function mTransactionHangCallback; std::unordered_set mSyncedFrameNumbers GUARDED_BY(mMutex); }; diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h index cc136bb40a..d2a6f40cea 100644 --- a/libs/gui/include/gui/ITransactionCompletedListener.h +++ b/libs/gui/include/gui/ITransactionCompletedListener.h @@ -194,7 +194,8 @@ public: virtual void onReleaseBuffer(ReleaseCallbackId callbackId, sp releaseFence, uint32_t currentMaxAcquiredBufferCount) = 0; - virtual void onTransactionQueueStalled() = 0; + + virtual void onTransactionQueueStalled(const String8& name) = 0; }; class BnTransactionCompletedListener : public SafeBnInterface { diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 36969db576..9efde6cb35 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -786,7 +786,7 @@ protected: // This is protected by mSurfaceStatsListenerMutex, but GUARDED_BY isn't supported for // std::recursive_mutex std::multimap mSurfaceStatsListeners; - std::unordered_map> mQueueStallListeners; + std::unordered_map> mQueueStallListeners; public: static sp getInstance(); @@ -804,7 +804,7 @@ public: const sp& surfaceControl, const std::unordered_set& callbackIds); - void addQueueStallListener(std::function stallListener, void* id); + void addQueueStallListener(std::function stallListener, void* id); void removeQueueStallListener(void *id); /* @@ -835,7 +835,7 @@ public: // For Testing Only static void setInstance(const sp&); - void onTransactionQueueStalled() override; + void onTransactionQueueStalled(const String8& reason) override; private: ReleaseBufferCallback popReleaseBufferCallbackLocked(const ReleaseCallbackId&); diff --git a/services/surfaceflinger/ClientCache.cpp b/services/surfaceflinger/ClientCache.cpp index b01932e413..2bd8f324e1 100644 --- a/services/surfaceflinger/ClientCache.cpp +++ b/services/surfaceflinger/ClientCache.cpp @@ -59,16 +59,17 @@ bool ClientCache::getBuffer(const client_cache_t& cacheId, return true; } -bool ClientCache::add(const client_cache_t& cacheId, const sp& buffer) { +base::expected, ClientCache::AddError> +ClientCache::add(const client_cache_t& cacheId, const sp& buffer) { auto& [processToken, id] = cacheId; if (processToken == nullptr) { ALOGE_AND_TRACE("ClientCache::add - invalid (nullptr) process token"); - return false; + return base::unexpected(AddError::Unspecified); } if (!buffer) { ALOGE_AND_TRACE("ClientCache::add - invalid (nullptr) buffer"); - return false; + return base::unexpected(AddError::Unspecified); } std::lock_guard lock(mMutex); @@ -81,7 +82,7 @@ bool ClientCache::add(const client_cache_t& cacheId, const sp& bu token = processToken.promote(); if (!token) { ALOGE_AND_TRACE("ClientCache::add - invalid token"); - return false; + return base::unexpected(AddError::Unspecified); } // Only call linkToDeath if not a local binder @@ -89,7 +90,7 @@ bool ClientCache::add(const client_cache_t& cacheId, const sp& bu status_t err = token->linkToDeath(mDeathRecipient); if (err != NO_ERROR) { ALOGE_AND_TRACE("ClientCache::add - could not link to death"); - return false; + return base::unexpected(AddError::Unspecified); } } auto [itr, success] = @@ -104,17 +105,17 @@ bool ClientCache::add(const client_cache_t& cacheId, const sp& bu if (processBuffers.size() > BUFFER_CACHE_MAX_SIZE) { ALOGE_AND_TRACE("ClientCache::add - cache is full"); - return false; + return base::unexpected(AddError::CacheFull); } LOG_ALWAYS_FATAL_IF(mRenderEngine == nullptr, "Attempted to build the ClientCache before a RenderEngine instance was " "ready!"); - processBuffers[id].buffer = std::make_shared< - renderengine::impl::ExternalTexture>(buffer, *mRenderEngine, - renderengine::impl::ExternalTexture::Usage:: - READABLE); - return true; + + return (processBuffers[id].buffer = std::make_shared< + renderengine::impl::ExternalTexture>(buffer, *mRenderEngine, + renderengine::impl::ExternalTexture:: + Usage::READABLE)); } void ClientCache::erase(const client_cache_t& cacheId) { diff --git a/services/surfaceflinger/ClientCache.h b/services/surfaceflinger/ClientCache.h index a9b8177d70..cdeac2bbc1 100644 --- a/services/surfaceflinger/ClientCache.h +++ b/services/surfaceflinger/ClientCache.h @@ -37,7 +37,10 @@ class ClientCache : public Singleton { public: ClientCache(); - bool add(const client_cache_t& cacheId, const sp& buffer); + enum class AddError { CacheFull, Unspecified }; + + base::expected, AddError> add( + const client_cache_t& cacheId, const sp& buffer); void erase(const client_cache_t& cacheId); std::shared_ptr get(const client_cache_t& cacheId); diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp index 6c6a487b67..95961fe6a9 100644 --- a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp +++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp @@ -168,15 +168,16 @@ bool TransactionHandler::hasPendingTransactions() { return !mPendingTransactionQueues.empty() || !mLocklessTransactionQueue.isEmpty(); } -void TransactionHandler::onTransactionQueueStalled(const TransactionState& transaction, - sp& listener) { - if (std::find(mStalledTransactions.begin(), mStalledTransactions.end(), transaction.id) != +void TransactionHandler::onTransactionQueueStalled(uint64_t transactionId, + sp& listener, + const std::string& reason) { + if (std::find(mStalledTransactions.begin(), mStalledTransactions.end(), transactionId) != mStalledTransactions.end()) { return; } - mStalledTransactions.push_back(transaction.id); - listener->onTransactionQueueStalled(); + mStalledTransactions.push_back(transactionId); + listener->onTransactionQueueStalled(String8(reason.c_str())); } void TransactionHandler::removeFromStalledTransactions(uint64_t id) { @@ -185,4 +186,4 @@ void TransactionHandler::removeFromStalledTransactions(uint64_t id) { mStalledTransactions.erase(it); } } -} // namespace android \ No newline at end of file +} // namespace android diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h index 237b48d55f..2b6f07d49a 100644 --- a/services/surfaceflinger/FrontEnd/TransactionHandler.h +++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h @@ -54,7 +54,8 @@ public: std::vector flushTransactions(); void addTransactionReadyFilter(TransactionFilter&&); void queueTransaction(TransactionState&&); - void onTransactionQueueStalled(const TransactionState&, sp&); + void onTransactionQueueStalled(uint64_t transactionId, sp&, + const std::string& reason); void removeFromStalledTransactions(uint64_t transactionId); private: diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 100ad43f32..d8491592d0 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3744,7 +3744,10 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC if (listener && (flushState.queueProcessTime - transaction.postTime) > std::chrono::nanoseconds(4s).count()) { - mTransactionHandler.onTransactionQueueStalled(transaction, listener); + mTransactionHandler + .onTransactionQueueStalled(transaction.id, listener, + "Buffer processing hung up due to stuck " + "fence. Indicates GPU hang"); } return false; } @@ -3960,8 +3963,9 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin uint32_t clientStateFlags = 0; for (int i = 0; i < states.size(); i++) { ComposerState& state = states.editItemAt(i); - clientStateFlags |= setClientStateLocked(frameTimelineInfo, state, desiredPresentTime, - isAutoTimestamp, postTime, permissions); + clientStateFlags |= + setClientStateLocked(frameTimelineInfo, state, desiredPresentTime, isAutoTimestamp, + postTime, permissions, transactionId); if ((flags & eAnimation) && state.state.surface) { if (const auto layer = fromHandle(state.state.surface).promote()) { using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType; @@ -4077,7 +4081,8 @@ bool SurfaceFlinger::callingThreadHasUnscopedSurfaceFlingerAccess(bool usePermis uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTimelineInfo, ComposerState& composerState, int64_t desiredPresentTime, bool isAutoTimestamp, - int64_t postTime, uint32_t permissions) { + int64_t postTime, uint32_t permissions, + uint64_t transactionId) { layer_state_t& s = composerState.state; s.sanitize(permissions); @@ -4370,7 +4375,8 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime if (what & layer_state_t::eBufferChanged) { std::shared_ptr buffer = - getExternalTextureFromBufferData(*s.bufferData, layer->getDebugName()); + getExternalTextureFromBufferData(*s.bufferData, layer->getDebugName(), + transactionId); if (layer->setBuffer(buffer, *s.bufferData, postTime, desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp, frameTimelineInfo)) { flags |= eTraversalNeeded; @@ -6955,34 +6961,44 @@ status_t SurfaceFlinger::removeWindowInfosListener( } std::shared_ptr SurfaceFlinger::getExternalTextureFromBufferData( - const BufferData& bufferData, const char* layerName) const { - bool cacheIdChanged = bufferData.flags.test(BufferData::BufferDataChange::cachedBufferChanged); - bool bufferSizeExceedsLimit = false; - std::shared_ptr buffer = nullptr; - if (cacheIdChanged && bufferData.buffer != nullptr) { - bufferSizeExceedsLimit = exceedsMaxRenderTargetSize(bufferData.buffer->getWidth(), - bufferData.buffer->getHeight()); - if (!bufferSizeExceedsLimit) { - ClientCache::getInstance().add(bufferData.cachedBuffer, bufferData.buffer); - buffer = ClientCache::getInstance().get(bufferData.cachedBuffer); - } - } else if (cacheIdChanged) { - buffer = ClientCache::getInstance().get(bufferData.cachedBuffer); - } else if (bufferData.buffer != nullptr) { - bufferSizeExceedsLimit = exceedsMaxRenderTargetSize(bufferData.buffer->getWidth(), - bufferData.buffer->getHeight()); - if (!bufferSizeExceedsLimit) { - buffer = std::make_shared< - renderengine::impl::ExternalTexture>(bufferData.buffer, getRenderEngine(), - renderengine::impl::ExternalTexture:: - Usage::READABLE); - } - } - ALOGE_IF(bufferSizeExceedsLimit, - "Attempted to create an ExternalTexture for layer %s that exceeds render target size " - "limit.", - layerName); - return buffer; + BufferData& bufferData, const char* layerName, uint64_t transactionId) { + if (bufferData.buffer && + exceedsMaxRenderTargetSize(bufferData.buffer->getWidth(), bufferData.buffer->getHeight())) { + ALOGE("Attempted to create an ExternalTexture for layer %s that exceeds render target " + "size limit.", + layerName); + return nullptr; + } + + bool cachedBufferChanged = + bufferData.flags.test(BufferData::BufferDataChange::cachedBufferChanged); + if (cachedBufferChanged && bufferData.buffer) { + auto result = ClientCache::getInstance().add(bufferData.cachedBuffer, bufferData.buffer); + if (result.ok()) { + return result.value(); + } + + if (result.error() == ClientCache::AddError::CacheFull) { + mTransactionHandler + .onTransactionQueueStalled(transactionId, bufferData.releaseBufferListener, + "Buffer processing hung due to full buffer cache"); + } + + return nullptr; + } + + if (cachedBufferChanged) { + return ClientCache::getInstance().get(bufferData.cachedBuffer); + } + + if (bufferData.buffer) { + return std::make_shared< + renderengine::impl::ExternalTexture>(bufferData.buffer, getRenderEngine(), + renderengine::impl::ExternalTexture::Usage:: + READABLE); + } + + return nullptr; } bool SurfaceFlinger::commitMirrorDisplays(VsyncId vsyncId) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 674f21f77e..3a14ba0493 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -312,7 +312,7 @@ protected: REQUIRES(mStateLock); virtual std::shared_ptr getExternalTextureFromBufferData( - const BufferData& bufferData, const char* layerName) const; + BufferData& bufferData, const char* layerName, uint64_t transactionId); // Returns true if any display matches a `bool(const DisplayDevice&)` predicate. template @@ -728,7 +728,8 @@ private: uint32_t setClientStateLocked(const FrameTimelineInfo&, ComposerState&, int64_t desiredPresentTime, bool isAutoTimestamp, - int64_t postTime, uint32_t permissions) REQUIRES(mStateLock); + int64_t postTime, uint32_t permissions, uint64_t transactionId) + REQUIRES(mStateLock); uint32_t getTransactionFlags() const; diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index 276b3dad18..3b11f2383e 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -100,7 +100,8 @@ public: MockSurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SurfaceFlinger::SkipInitialization) {} std::shared_ptr getExternalTextureFromBufferData( - const BufferData& bufferData, const char* /* layerName */) const override { + BufferData& bufferData, const char* /* layerName */, + uint64_t /* transactionId */) override { return std::make_shared(bufferData.getWidth(), bufferData.getHeight(), bufferData.getId(), -- GitLab From 258446dd988ecc074bf2c748551fe67f21c1eaac Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Thu, 20 Oct 2022 22:33:32 +0000 Subject: [PATCH 0391/1310] SF: Remove unnecessary updateSnapshot call Bug: 238781169 Test: presubmits Change-Id: Ib72ca5f4d77283f70f644a30d9c419b4bae56079 --- services/surfaceflinger/Layer.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index d4660ff132..6119d4cd99 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -1558,8 +1558,6 @@ void Layer::setChildrenDrawingParent(const sp& newParent) { newParent->canDrawShadows() ? 0.f : newParent->mEffectiveShadowRadius; child->computeBounds(newParent->mBounds, newParent->mEffectiveTransform, parentShadowRadius); - child->updateSnapshot(true /* updateGeometry */); - child->updateChildrenSnapshots(true /* updateGeometry */); } } -- GitLab From 1ab71435fec5f76eb1c1d73f2361b87ec21810f3 Mon Sep 17 00:00:00 2001 From: Antonio Kantek Date: Thu, 20 Oct 2022 22:42:09 +0000 Subject: [PATCH 0392/1310] Delete obsolete TODO in setInTouchMode Bug: 198499018 Test: m (built) Change-Id: If024ce661ee2a3d12d71261ad68406328322ec92 --- services/inputflinger/dispatcher/InputDispatcher.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 84c18fd74b..2ecd7bc826 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -5012,8 +5012,6 @@ bool InputDispatcher::setInTouchMode(bool inTouchMode, int32_t pid, int32_t uid, ? "not set" : std::to_string(mTouchModePerDisplay[displayId]).c_str()); - // TODO(b/198499018): Ensure that WM can guarantee that touch mode is properly set when - // display is created. auto touchModeIt = mTouchModePerDisplay.find(displayId); if (touchModeIt != mTouchModePerDisplay.end() && touchModeIt->second == inTouchMode) { return false; -- GitLab From 6ebd069ce62654062b17e996e887d55c2828d12f Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 20 Oct 2022 15:05:45 -0700 Subject: [PATCH 0393/1310] Mark findTouchedWindowAtLocked const This function doesn't change the dispatcher, so we can mark it const. Let's also print out the value of action when the debug config for inbound events is enabled. Bug: 211379801 Test: adb logcat Change-Id: I80b5c48308c49e22e093e1427b2be693200f799c --- services/inputflinger/dispatcher/InputDispatcher.cpp | 11 ++++++----- services/inputflinger/dispatcher/InputDispatcher.h | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 84c18fd74b..0ab4049d9a 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -1071,7 +1071,7 @@ sp InputDispatcher::findTouchedWindowAtLocked(int32_t displayI int32_t y, TouchState* touchState, bool isStylus, bool addOutsideTargets, - bool ignoreDragWindow) { + bool ignoreDragWindow) const { if (addOutsideTargets && touchState == nullptr) { LOG_ALWAYS_FATAL("Must provide a valid touch state if adding outside targets"); } @@ -3984,13 +3984,14 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) { if (DEBUG_INBOUND_EVENT_DETAILS) { ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=0x%x, " "displayId=%" PRId32 ", policyFlags=0x%x, " - "action=0x%x, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, " + "action=%s, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, " "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, xCursorPosition=%f, " "yCursorPosition=%f, downTime=%" PRId64, args->id, args->eventTime, args->deviceId, args->source, args->displayId, - args->policyFlags, args->action, args->actionButton, args->flags, args->metaState, - args->buttonState, args->edgeFlags, args->xPrecision, args->yPrecision, - args->xCursorPosition, args->yCursorPosition, args->downTime); + args->policyFlags, MotionEvent::actionToString(args->action).c_str(), + args->actionButton, args->flags, args->metaState, args->buttonState, args->edgeFlags, + args->xPrecision, args->yPrecision, args->xCursorPosition, args->yCursorPosition, + args->downTime); for (uint32_t i = 0; i < args->pointerCount; i++) { ALOGD(" Pointer %d: id=%d, toolType=%d, " "x=%f, y=%f, pressure=%f, size=%f, " diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 14b046af24..b20a548eb5 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -239,7 +239,7 @@ private: sp findTouchedWindowAtLocked( int32_t displayId, int32_t x, int32_t y, TouchState* touchState, bool isStylus = false, - bool addOutsideTargets = false, bool ignoreDragWindow = false) REQUIRES(mLock); + bool addOutsideTargets = false, bool ignoreDragWindow = false) const REQUIRES(mLock); std::vector> findTouchedSpyWindowsAtLocked( int32_t displayId, int32_t x, int32_t y, bool isStylus) const REQUIRES(mLock); -- GitLab From 71c7e4f62c444d4babf1c0b778aa117656832571 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Wed, 19 Oct 2022 16:07:16 -0400 Subject: [PATCH 0394/1310] FTL: Allow Concat of bool and char Also, allow references to integral types. Bug: 185536303 Test: ftl_test Change-Id: Ic9be008ed7f72ecdb7a369b01bc5a8235b35ac2c --- include/ftl/concat.h | 4 +++- include/ftl/details/concat.h | 36 +++++++++++++++++++++++++++---- include/ftl/details/type_traits.h | 6 ++++++ libs/ftl/concat_test.cpp | 17 +++++++++++++++ 4 files changed, 58 insertions(+), 5 deletions(-) diff --git a/include/ftl/concat.h b/include/ftl/concat.h index ded48f7c8c..e0774d39f3 100644 --- a/include/ftl/concat.h +++ b/include/ftl/concat.h @@ -20,7 +20,9 @@ namespace android::ftl { -// Lightweight (not allocating nor sprintf-based) concatenation. +// Lightweight (not allocating nor sprintf-based) concatenation. The variadic arguments can be +// values of integral type (including bool and char), string literals, or strings whose length +// is constrained: // // std::string_view name = "Volume"; // ftl::Concat string(ftl::truncated<3>(name), ": ", -3, " dB"); diff --git a/include/ftl/details/concat.h b/include/ftl/details/concat.h index 8ce949ef05..726ba0297e 100644 --- a/include/ftl/details/concat.h +++ b/include/ftl/details/concat.h @@ -19,6 +19,7 @@ #include #include +#include #include namespace android::ftl::details { @@ -26,16 +27,42 @@ namespace android::ftl::details { template struct StaticString; +// Booleans. template -struct StaticString>> { - static constexpr std::size_t N = to_chars_length_v; +struct StaticString>> { + static constexpr std::size_t N = 5; // Length of "false". - explicit StaticString(T v) : view(to_chars(buffer, v)) {} + explicit constexpr StaticString(bool b) : view(b ? "true" : "false") {} - to_chars_buffer_t buffer; const std::string_view view; }; +// Characters. +template +struct StaticString>> { + static constexpr std::size_t N = 1; + + explicit constexpr StaticString(char c) : character(c) {} + + const char character; + const std::string_view view{&character, 1u}; +}; + +// Integers, including the integer value of other character types like char32_t. +template +struct StaticString< + T, std::enable_if_t> && !is_bool_v && !is_char_v>> { + using U = remove_cvref_t; + static constexpr std::size_t N = to_chars_length_v; + + // TODO: Mark this and to_chars as `constexpr` in C++23. + explicit StaticString(U v) : view(to_chars(buffer, v)) {} + + to_chars_buffer_t buffer; + const std::string_view view; +}; + +// Character arrays. template struct StaticString { static constexpr std::size_t N = M - 1; @@ -50,6 +77,7 @@ struct Truncated { std::string_view view; }; +// Strings with constrained length. template struct StaticString, void> { static constexpr std::size_t N = M; diff --git a/include/ftl/details/type_traits.h b/include/ftl/details/type_traits.h index 7092ec50fb..47bebc5114 100644 --- a/include/ftl/details/type_traits.h +++ b/include/ftl/details/type_traits.h @@ -24,4 +24,10 @@ namespace android::ftl::details { template using remove_cvref_t = std::remove_cv_t>; +template +constexpr bool is_bool_v = std::is_same_v, bool>; + +template +constexpr bool is_char_v = std::is_same_v, char>; + } // namespace android::ftl::details diff --git a/libs/ftl/concat_test.cpp b/libs/ftl/concat_test.cpp index 8ecb1b252d..771f05478a 100644 --- a/libs/ftl/concat_test.cpp +++ b/libs/ftl/concat_test.cpp @@ -28,8 +28,25 @@ TEST(Concat, Example) { EXPECT_EQ(string.c_str()[string.size()], '\0'); } +TEST(Concat, Characters) { + EXPECT_EQ(ftl::Concat(u'a', ' ', U'b').str(), "97 98"); +} + +TEST(Concat, References) { + int i[] = {-1, 2}; + unsigned u = 3; + EXPECT_EQ(ftl::Concat(i[0], std::as_const(i[1]), u).str(), "-123"); + + const bool b = false; + const char c = 'o'; + EXPECT_EQ(ftl::Concat(b, "tt", c).str(), "falsetto"); +} + namespace { +static_assert(ftl::Concat{true, false, true}.str() == "truefalsetrue"); +static_assert(ftl::Concat{':', '-', ')'}.str() == ":-)"); + static_assert(ftl::Concat{"foo"}.str() == "foo"); static_assert(ftl::Concat{ftl::truncated<3>("foobar")}.str() == "foo"); -- GitLab From bea6ce5a7fceb854f80e40b113c2a7559860639e Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Fri, 14 Oct 2022 15:17:30 +0000 Subject: [PATCH 0395/1310] Split up TouchInputMapper::preparePointerGestures This monster 700-line method was quite difficult to understand. It still is, but hopefully splitting it up into a few more methods helps. Also, activeTouchId was basically just being used as a shorter name for mPointerGesture.activeTouchId (with both being assigned to at once), leaving space for a bug to come in if an assignment was made to one but not the other. Making it a const reference removes that possibility, and makes it a bit clearer what's going on. This CL should not result in any behaviour changes. Bug: none Test: atest inputflinger_tests Test: compare touchpad gesture behaviour before and after Change-Id: Ia85bf5dd80d87bd8fb83332f50dc25ad8769c32e --- .../reader/mapper/TouchInputMapper.cpp | 794 +++++++++--------- .../reader/mapper/TouchInputMapper.h | 8 + 2 files changed, 408 insertions(+), 394 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 98669f32b4..615889ebe3 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -2844,54 +2844,20 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi // Otherwise choose an arbitrary remaining pointer. // This guarantees we always have an active touch id when there is at least one pointer. // We keep the same active touch id for as long as possible. - int32_t lastActiveTouchId = mPointerGesture.activeTouchId; - int32_t activeTouchId = lastActiveTouchId; - if (activeTouchId < 0) { + if (mPointerGesture.activeTouchId < 0) { if (!mCurrentCookedState.fingerIdBits.isEmpty()) { - activeTouchId = mPointerGesture.activeTouchId = - mCurrentCookedState.fingerIdBits.firstMarkedBit(); + mPointerGesture.activeTouchId = mCurrentCookedState.fingerIdBits.firstMarkedBit(); mPointerGesture.firstTouchTime = when; } - } else if (!mCurrentCookedState.fingerIdBits.hasBit(activeTouchId)) { - if (!mCurrentCookedState.fingerIdBits.isEmpty()) { - activeTouchId = mPointerGesture.activeTouchId = - mCurrentCookedState.fingerIdBits.firstMarkedBit(); - } else { - activeTouchId = mPointerGesture.activeTouchId = -1; - } - } - - // Determine whether we are in quiet time. - bool isQuietTime = false; - if (activeTouchId < 0) { - mPointerGesture.resetQuietTime(); - } else { - isQuietTime = when < mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval; - if (!isQuietTime) { - if ((mPointerGesture.lastGestureMode == PointerGesture::Mode::PRESS || - mPointerGesture.lastGestureMode == PointerGesture::Mode::SWIPE || - mPointerGesture.lastGestureMode == PointerGesture::Mode::FREEFORM) && - currentFingerCount < 2) { - // Enter quiet time when exiting swipe or freeform state. - // This is to prevent accidentally entering the hover state and flinging the - // pointer when finishing a swipe and there is still one pointer left onscreen. - isQuietTime = true; - } else if (mPointerGesture.lastGestureMode == - PointerGesture::Mode::BUTTON_CLICK_OR_DRAG && - currentFingerCount >= 2 && !isPointerDown(mCurrentRawState.buttonState)) { - // Enter quiet time when releasing the button and there are still two or more - // fingers down. This may indicate that one finger was used to press the button - // but it has not gone up yet. - isQuietTime = true; - } - if (isQuietTime) { - mPointerGesture.quietTime = when; - } - } + } else if (!mCurrentCookedState.fingerIdBits.hasBit(mPointerGesture.activeTouchId)) { + mPointerGesture.activeTouchId = !mCurrentCookedState.fingerIdBits.isEmpty() + ? mCurrentCookedState.fingerIdBits.firstMarkedBit() + : -1; } + const int32_t& activeTouchId = mPointerGesture.activeTouchId; // Switch states based on button and pointer state. - if (isQuietTime) { + if (checkForTouchpadQuietTime(when)) { // Case 1: Quiet time. (QUIET) ALOGD_IF(DEBUG_GESTURES, "Gestures: QUIET for next %0.3fms", (mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval - when) * @@ -2931,24 +2897,9 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi // Switch pointers if needed. // Find the fastest pointer and follow it. if (activeTouchId >= 0 && currentFingerCount > 1) { - int32_t bestId = -1; - float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed; - for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) { - uint32_t id = idBits.clearFirstMarkedBit(); - std::optional vx = - mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, id); - std::optional vy = - mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, id); - if (vx && vy) { - float speed = hypotf(*vx, *vy); - if (speed > bestSpeed) { - bestId = id; - bestSpeed = speed; - } - } - } + const auto [bestId, bestSpeed] = getFastestFinger(); if (bestId >= 0 && bestId != activeTouchId) { - mPointerGesture.activeTouchId = activeTouchId = bestId; + mPointerGesture.activeTouchId = bestId; ALOGD_IF(DEBUG_GESTURES, "Gestures: BUTTON_CLICK_OR_DRAG switched pointers, bestId=%d, " "bestSpeed=%0.3f", @@ -3114,380 +3065,435 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi } } else { // Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM) - // We need to provide feedback for each finger that goes down so we cannot wait - // for the fingers to move before deciding what to do. - // - // The ambiguous case is deciding what to do when there are two fingers down but they - // have not moved enough to determine whether they are part of a drag or part of a - // freeform gesture, or just a press or long-press at the pointer location. - // - // When there are two fingers we start with the PRESS hypothesis and we generate a - // down at the pointer location. - // - // When the two fingers move enough or when additional fingers are added, we make - // a decision to transition into SWIPE or FREEFORM mode accordingly. - ALOG_ASSERT(activeTouchId >= 0); + prepareMultiFingerPointerGestures(when, outCancelPreviousGesture, outFinishPreviousGesture); + } - bool settled = when >= - mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval; - if (mPointerGesture.lastGestureMode != PointerGesture::Mode::PRESS && - mPointerGesture.lastGestureMode != PointerGesture::Mode::SWIPE && - mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) { - *outFinishPreviousGesture = true; - } else if (!settled && currentFingerCount > lastFingerCount) { - // Additional pointers have gone down but not yet settled. - // Reset the gesture. - ALOGD_IF(DEBUG_GESTURES, - "Gestures: Resetting gesture since additional pointers went down for " - "MULTITOUCH, settle time remaining %0.3fms", - (mPointerGesture.firstTouchTime + - mConfig.pointerGestureMultitouchSettleInterval - when) * - 0.000001f); - *outCancelPreviousGesture = true; - } else { - // Continue previous gesture. - mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode; + mPointerController->setButtonState(mCurrentRawState.buttonState); + + if (DEBUG_GESTURES) { + ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, " + "currentGestureMode=%d, currentGestureIdBits=0x%08x, " + "lastGestureMode=%d, lastGestureIdBits=0x%08x", + toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture), + mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value, + mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value); + for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty();) { + uint32_t id = idBits.clearFirstMarkedBit(); + uint32_t index = mPointerGesture.currentGestureIdToIndex[id]; + const PointerProperties& properties = mPointerGesture.currentGestureProperties[index]; + const PointerCoords& coords = mPointerGesture.currentGestureCoords[index]; + ALOGD(" currentGesture[%d]: index=%d, toolType=%d, " + "x=%0.3f, y=%0.3f, pressure=%0.3f", + id, index, properties.toolType, coords.getAxisValue(AMOTION_EVENT_AXIS_X), + coords.getAxisValue(AMOTION_EVENT_AXIS_Y), + coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); } + for (BitSet32 idBits = mPointerGesture.lastGestureIdBits; !idBits.isEmpty();) { + uint32_t id = idBits.clearFirstMarkedBit(); + uint32_t index = mPointerGesture.lastGestureIdToIndex[id]; + const PointerProperties& properties = mPointerGesture.lastGestureProperties[index]; + const PointerCoords& coords = mPointerGesture.lastGestureCoords[index]; + ALOGD(" lastGesture[%d]: index=%d, toolType=%d, " + "x=%0.3f, y=%0.3f, pressure=%0.3f", + id, index, properties.toolType, coords.getAxisValue(AMOTION_EVENT_AXIS_X), + coords.getAxisValue(AMOTION_EVENT_AXIS_Y), + coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); + } + } + return true; +} - if (*outFinishPreviousGesture || *outCancelPreviousGesture) { - mPointerGesture.currentGestureMode = PointerGesture::Mode::PRESS; - mPointerGesture.activeGestureId = 0; - mPointerGesture.referenceIdBits.clear(); - mPointerVelocityControl.reset(); +bool TouchInputMapper::checkForTouchpadQuietTime(nsecs_t when) { + if (mPointerGesture.activeTouchId < 0) { + mPointerGesture.resetQuietTime(); + return false; + } - // Use the centroid and pointer location as the reference points for the gesture. - ALOGD_IF(DEBUG_GESTURES, - "Gestures: Using centroid as reference for MULTITOUCH, settle time remaining " - "%0.3fms", - (mPointerGesture.firstTouchTime + - mConfig.pointerGestureMultitouchSettleInterval - when) * - 0.000001f); - mCurrentRawState.rawPointerData - .getCentroidOfTouchingPointers(&mPointerGesture.referenceTouchX, - &mPointerGesture.referenceTouchY); - mPointerController->getPosition(&mPointerGesture.referenceGestureX, - &mPointerGesture.referenceGestureY); + if (when < mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval) { + return true; + } + + const uint32_t currentFingerCount = mCurrentCookedState.fingerIdBits.count(); + bool isQuietTime = false; + if ((mPointerGesture.lastGestureMode == PointerGesture::Mode::PRESS || + mPointerGesture.lastGestureMode == PointerGesture::Mode::SWIPE || + mPointerGesture.lastGestureMode == PointerGesture::Mode::FREEFORM) && + currentFingerCount < 2) { + // Enter quiet time when exiting swipe or freeform state. + // This is to prevent accidentally entering the hover state and flinging the + // pointer when finishing a swipe and there is still one pointer left onscreen. + isQuietTime = true; + } else if (mPointerGesture.lastGestureMode == PointerGesture::Mode::BUTTON_CLICK_OR_DRAG && + currentFingerCount >= 2 && !isPointerDown(mCurrentRawState.buttonState)) { + // Enter quiet time when releasing the button and there are still two or more + // fingers down. This may indicate that one finger was used to press the button + // but it has not gone up yet. + isQuietTime = true; + } + if (isQuietTime) { + mPointerGesture.quietTime = when; + } + return isQuietTime; +} + +std::pair TouchInputMapper::getFastestFinger() { + int32_t bestId = -1; + float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed; + for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) { + uint32_t id = idBits.clearFirstMarkedBit(); + std::optional vx = + mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, id); + std::optional vy = + mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, id); + if (vx && vy) { + float speed = hypotf(*vx, *vy); + if (speed > bestSpeed) { + bestId = id; + bestSpeed = speed; + } } + } + return std::make_pair(bestId, bestSpeed); +} - // Clear the reference deltas for fingers not yet included in the reference calculation. - for (BitSet32 idBits(mCurrentCookedState.fingerIdBits.value & - ~mPointerGesture.referenceIdBits.value); - !idBits.isEmpty();) { - uint32_t id = idBits.clearFirstMarkedBit(); - mPointerGesture.referenceDeltas[id].dx = 0; - mPointerGesture.referenceDeltas[id].dy = 0; +void TouchInputMapper::prepareMultiFingerPointerGestures(nsecs_t when, bool* cancelPreviousGesture, + bool* finishPreviousGesture) { + // We need to provide feedback for each finger that goes down so we cannot wait for the fingers + // to move before deciding what to do. + // + // The ambiguous case is deciding what to do when there are two fingers down but they have not + // moved enough to determine whether they are part of a drag or part of a freeform gesture, or + // just a press or long-press at the pointer location. + // + // When there are two fingers we start with the PRESS hypothesis and we generate a down at the + // pointer location. + // + // When the two fingers move enough or when additional fingers are added, we make a decision to + // transition into SWIPE or FREEFORM mode accordingly. + const int32_t activeTouchId = mPointerGesture.activeTouchId; + ALOG_ASSERT(activeTouchId >= 0); + + const uint32_t currentFingerCount = mCurrentCookedState.fingerIdBits.count(); + const uint32_t lastFingerCount = mLastCookedState.fingerIdBits.count(); + bool settled = + when >= mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval; + if (mPointerGesture.lastGestureMode != PointerGesture::Mode::PRESS && + mPointerGesture.lastGestureMode != PointerGesture::Mode::SWIPE && + mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) { + *finishPreviousGesture = true; + } else if (!settled && currentFingerCount > lastFingerCount) { + // Additional pointers have gone down but not yet settled. + // Reset the gesture. + ALOGD_IF(DEBUG_GESTURES, + "Gestures: Resetting gesture since additional pointers went down for " + "MULTITOUCH, settle time remaining %0.3fms", + (mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval - + when) * 0.000001f); + *cancelPreviousGesture = true; + } else { + // Continue previous gesture. + mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode; + } + + if (*finishPreviousGesture || *cancelPreviousGesture) { + mPointerGesture.currentGestureMode = PointerGesture::Mode::PRESS; + mPointerGesture.activeGestureId = 0; + mPointerGesture.referenceIdBits.clear(); + mPointerVelocityControl.reset(); + + // Use the centroid and pointer location as the reference points for the gesture. + ALOGD_IF(DEBUG_GESTURES, + "Gestures: Using centroid as reference for MULTITOUCH, settle time remaining " + "%0.3fms", + (mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval - + when) * 0.000001f); + mCurrentRawState.rawPointerData + .getCentroidOfTouchingPointers(&mPointerGesture.referenceTouchX, + &mPointerGesture.referenceTouchY); + mPointerController->getPosition(&mPointerGesture.referenceGestureX, + &mPointerGesture.referenceGestureY); + } + + // Clear the reference deltas for fingers not yet included in the reference calculation. + for (BitSet32 idBits(mCurrentCookedState.fingerIdBits.value & + ~mPointerGesture.referenceIdBits.value); + !idBits.isEmpty();) { + uint32_t id = idBits.clearFirstMarkedBit(); + mPointerGesture.referenceDeltas[id].dx = 0; + mPointerGesture.referenceDeltas[id].dy = 0; + } + mPointerGesture.referenceIdBits = mCurrentCookedState.fingerIdBits; + + // Add delta for all fingers and calculate a common movement delta. + int32_t commonDeltaRawX = 0, commonDeltaRawY = 0; + BitSet32 commonIdBits(mLastCookedState.fingerIdBits.value & + mCurrentCookedState.fingerIdBits.value); + for (BitSet32 idBits(commonIdBits); !idBits.isEmpty();) { + bool first = (idBits == commonIdBits); + uint32_t id = idBits.clearFirstMarkedBit(); + const RawPointerData::Pointer& cpd = mCurrentRawState.rawPointerData.pointerForId(id); + const RawPointerData::Pointer& lpd = mLastRawState.rawPointerData.pointerForId(id); + PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id]; + delta.dx += cpd.x - lpd.x; + delta.dy += cpd.y - lpd.y; + + if (first) { + commonDeltaRawX = delta.dx; + commonDeltaRawY = delta.dy; + } else { + commonDeltaRawX = calculateCommonVector(commonDeltaRawX, delta.dx); + commonDeltaRawY = calculateCommonVector(commonDeltaRawY, delta.dy); } - mPointerGesture.referenceIdBits = mCurrentCookedState.fingerIdBits; - - // Add delta for all fingers and calculate a common movement delta. - int32_t commonDeltaRawX = 0, commonDeltaRawY = 0; - BitSet32 commonIdBits(mLastCookedState.fingerIdBits.value & - mCurrentCookedState.fingerIdBits.value); - for (BitSet32 idBits(commonIdBits); !idBits.isEmpty();) { - bool first = (idBits == commonIdBits); + } + + // Consider transitions from PRESS to SWIPE or MULTITOUCH. + if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS) { + float dist[MAX_POINTER_ID + 1]; + int32_t distOverThreshold = 0; + for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) { uint32_t id = idBits.clearFirstMarkedBit(); - const RawPointerData::Pointer& cpd = mCurrentRawState.rawPointerData.pointerForId(id); - const RawPointerData::Pointer& lpd = mLastRawState.rawPointerData.pointerForId(id); PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id]; - delta.dx += cpd.x - lpd.x; - delta.dy += cpd.y - lpd.y; - - if (first) { - commonDeltaRawX = delta.dx; - commonDeltaRawY = delta.dy; - } else { - commonDeltaRawX = calculateCommonVector(commonDeltaRawX, delta.dx); - commonDeltaRawY = calculateCommonVector(commonDeltaRawY, delta.dy); + dist[id] = hypotf(delta.dx * mPointerXZoomScale, delta.dy * mPointerYZoomScale); + if (dist[id] > mConfig.pointerGestureMultitouchMinDistance) { + distOverThreshold += 1; } } - // Consider transitions from PRESS to SWIPE or MULTITOUCH. - if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS) { - float dist[MAX_POINTER_ID + 1]; - int32_t distOverThreshold = 0; - for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) { - uint32_t id = idBits.clearFirstMarkedBit(); - PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id]; - dist[id] = hypotf(delta.dx * mPointerXZoomScale, delta.dy * mPointerYZoomScale); - if (dist[id] > mConfig.pointerGestureMultitouchMinDistance) { - distOverThreshold += 1; - } - } - - // Only transition when at least two pointers have moved further than - // the minimum distance threshold. - if (distOverThreshold >= 2) { - if (currentFingerCount > 2) { - // There are more than two pointers, switch to FREEFORM. + // Only transition when at least two pointers have moved further than + // the minimum distance threshold. + if (distOverThreshold >= 2) { + if (currentFingerCount > 2) { + // There are more than two pointers, switch to FREEFORM. + ALOGD_IF(DEBUG_GESTURES, + "Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2", + currentFingerCount); + *cancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM; + } else { + // There are exactly two pointers. + BitSet32 idBits(mCurrentCookedState.fingerIdBits); + uint32_t id1 = idBits.clearFirstMarkedBit(); + uint32_t id2 = idBits.firstMarkedBit(); + const RawPointerData::Pointer& p1 = + mCurrentRawState.rawPointerData.pointerForId(id1); + const RawPointerData::Pointer& p2 = + mCurrentRawState.rawPointerData.pointerForId(id2); + float mutualDistance = distance(p1.x, p1.y, p2.x, p2.y); + if (mutualDistance > mPointerGestureMaxSwipeWidth) { + // There are two pointers but they are too far apart for a SWIPE, + // switch to FREEFORM. ALOGD_IF(DEBUG_GESTURES, - "Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2", - currentFingerCount); - *outCancelPreviousGesture = true; + "Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f", + mutualDistance, mPointerGestureMaxSwipeWidth); + *cancelPreviousGesture = true; mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM; } else { - // There are exactly two pointers. - BitSet32 idBits(mCurrentCookedState.fingerIdBits); - uint32_t id1 = idBits.clearFirstMarkedBit(); - uint32_t id2 = idBits.firstMarkedBit(); - const RawPointerData::Pointer& p1 = - mCurrentRawState.rawPointerData.pointerForId(id1); - const RawPointerData::Pointer& p2 = - mCurrentRawState.rawPointerData.pointerForId(id2); - float mutualDistance = distance(p1.x, p1.y, p2.x, p2.y); - if (mutualDistance > mPointerGestureMaxSwipeWidth) { - // There are two pointers but they are too far apart for a SWIPE, - // switch to FREEFORM. - ALOGD_IF(DEBUG_GESTURES, - "Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f", - mutualDistance, mPointerGestureMaxSwipeWidth); - *outCancelPreviousGesture = true; - mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM; - } else { - // There are two pointers. Wait for both pointers to start moving - // before deciding whether this is a SWIPE or FREEFORM gesture. - float dist1 = dist[id1]; - float dist2 = dist[id2]; - if (dist1 >= mConfig.pointerGestureMultitouchMinDistance && - dist2 >= mConfig.pointerGestureMultitouchMinDistance) { - // Calculate the dot product of the displacement vectors. - // When the vectors are oriented in approximately the same direction, - // the angle betweeen them is near zero and the cosine of the angle - // approches 1.0. Recall that dot(v1, v2) = cos(angle) * mag(v1) * - // mag(v2). - PointerGesture::Delta& delta1 = mPointerGesture.referenceDeltas[id1]; - PointerGesture::Delta& delta2 = mPointerGesture.referenceDeltas[id2]; - float dx1 = delta1.dx * mPointerXZoomScale; - float dy1 = delta1.dy * mPointerYZoomScale; - float dx2 = delta2.dx * mPointerXZoomScale; - float dy2 = delta2.dy * mPointerYZoomScale; - float dot = dx1 * dx2 + dy1 * dy2; - float cosine = dot / (dist1 * dist2); // denominator always > 0 - if (cosine >= mConfig.pointerGestureSwipeTransitionAngleCosine) { - // Pointers are moving in the same direction. Switch to SWIPE. - ALOGD_IF(DEBUG_GESTURES, - "Gestures: PRESS transitioned to SWIPE, " - "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, " - "cosine %0.3f >= %0.3f", - dist1, mConfig.pointerGestureMultitouchMinDistance, dist2, - mConfig.pointerGestureMultitouchMinDistance, cosine, - mConfig.pointerGestureSwipeTransitionAngleCosine); - mPointerGesture.currentGestureMode = PointerGesture::Mode::SWIPE; - } else { - // Pointers are moving in different directions. Switch to FREEFORM. - ALOGD_IF(DEBUG_GESTURES, - "Gestures: PRESS transitioned to FREEFORM, " - "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, " - "cosine %0.3f < %0.3f", - dist1, mConfig.pointerGestureMultitouchMinDistance, dist2, - mConfig.pointerGestureMultitouchMinDistance, cosine, - mConfig.pointerGestureSwipeTransitionAngleCosine); - *outCancelPreviousGesture = true; - mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM; - } + // There are two pointers. Wait for both pointers to start moving + // before deciding whether this is a SWIPE or FREEFORM gesture. + float dist1 = dist[id1]; + float dist2 = dist[id2]; + if (dist1 >= mConfig.pointerGestureMultitouchMinDistance && + dist2 >= mConfig.pointerGestureMultitouchMinDistance) { + // Calculate the dot product of the displacement vectors. + // When the vectors are oriented in approximately the same direction, + // the angle betweeen them is near zero and the cosine of the angle + // approaches 1.0. Recall that dot(v1, v2) = cos(angle) * mag(v1) * + // mag(v2). + PointerGesture::Delta& delta1 = mPointerGesture.referenceDeltas[id1]; + PointerGesture::Delta& delta2 = mPointerGesture.referenceDeltas[id2]; + float dx1 = delta1.dx * mPointerXZoomScale; + float dy1 = delta1.dy * mPointerYZoomScale; + float dx2 = delta2.dx * mPointerXZoomScale; + float dy2 = delta2.dy * mPointerYZoomScale; + float dot = dx1 * dx2 + dy1 * dy2; + float cosine = dot / (dist1 * dist2); // denominator always > 0 + if (cosine >= mConfig.pointerGestureSwipeTransitionAngleCosine) { + // Pointers are moving in the same direction. Switch to SWIPE. + ALOGD_IF(DEBUG_GESTURES, + "Gestures: PRESS transitioned to SWIPE, " + "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, " + "cosine %0.3f >= %0.3f", + dist1, mConfig.pointerGestureMultitouchMinDistance, dist2, + mConfig.pointerGestureMultitouchMinDistance, cosine, + mConfig.pointerGestureSwipeTransitionAngleCosine); + mPointerGesture.currentGestureMode = PointerGesture::Mode::SWIPE; + } else { + // Pointers are moving in different directions. Switch to FREEFORM. + ALOGD_IF(DEBUG_GESTURES, + "Gestures: PRESS transitioned to FREEFORM, " + "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, " + "cosine %0.3f < %0.3f", + dist1, mConfig.pointerGestureMultitouchMinDistance, dist2, + mConfig.pointerGestureMultitouchMinDistance, cosine, + mConfig.pointerGestureSwipeTransitionAngleCosine); + *cancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM; } } } } - } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) { - // Switch from SWIPE to FREEFORM if additional pointers go down. - // Cancel previous gesture. - if (currentFingerCount > 2) { - ALOGD_IF(DEBUG_GESTURES, - "Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2", - currentFingerCount); - *outCancelPreviousGesture = true; - mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM; - } } + } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) { + // Switch from SWIPE to FREEFORM if additional pointers go down. + // Cancel previous gesture. + if (currentFingerCount > 2) { + ALOGD_IF(DEBUG_GESTURES, + "Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2", + currentFingerCount); + *cancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM; + } + } - // Move the reference points based on the overall group motion of the fingers - // except in PRESS mode while waiting for a transition to occur. - if (mPointerGesture.currentGestureMode != PointerGesture::Mode::PRESS && - (commonDeltaRawX || commonDeltaRawY)) { - for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) { - uint32_t id = idBits.clearFirstMarkedBit(); - PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id]; - delta.dx = 0; - delta.dy = 0; - } + // Move the reference points based on the overall group motion of the fingers + // except in PRESS mode while waiting for a transition to occur. + if (mPointerGesture.currentGestureMode != PointerGesture::Mode::PRESS && + (commonDeltaRawX || commonDeltaRawY)) { + for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) { + uint32_t id = idBits.clearFirstMarkedBit(); + PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id]; + delta.dx = 0; + delta.dy = 0; + } - mPointerGesture.referenceTouchX += commonDeltaRawX; - mPointerGesture.referenceTouchY += commonDeltaRawY; + mPointerGesture.referenceTouchX += commonDeltaRawX; + mPointerGesture.referenceTouchY += commonDeltaRawY; - float commonDeltaX = commonDeltaRawX * mPointerXMovementScale; - float commonDeltaY = commonDeltaRawY * mPointerYMovementScale; + float commonDeltaX = commonDeltaRawX * mPointerXMovementScale; + float commonDeltaY = commonDeltaRawY * mPointerYMovementScale; - rotateDelta(mInputDeviceOrientation, &commonDeltaX, &commonDeltaY); - mPointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY); + rotateDelta(mInputDeviceOrientation, &commonDeltaX, &commonDeltaY); + mPointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY); - mPointerGesture.referenceGestureX += commonDeltaX; - mPointerGesture.referenceGestureY += commonDeltaY; - } + mPointerGesture.referenceGestureX += commonDeltaX; + mPointerGesture.referenceGestureY += commonDeltaY; + } - // Report gestures. - if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS || - mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) { - // PRESS or SWIPE mode. - ALOGD_IF(DEBUG_GESTURES, - "Gestures: PRESS or SWIPE activeTouchId=%d, activeGestureId=%d, " - "currentTouchPointerCount=%d", - activeTouchId, mPointerGesture.activeGestureId, currentFingerCount); - ALOG_ASSERT(mPointerGesture.activeGestureId >= 0); + // Report gestures. + if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS || + mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) { + // PRESS or SWIPE mode. + ALOGD_IF(DEBUG_GESTURES, + "Gestures: PRESS or SWIPE activeTouchId=%d, activeGestureId=%d, " + "currentTouchPointerCount=%d", + activeTouchId, mPointerGesture.activeGestureId, currentFingerCount); + ALOG_ASSERT(mPointerGesture.activeGestureId >= 0); - mPointerGesture.currentGestureIdBits.clear(); - mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); - mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; - mPointerGesture.currentGestureProperties[0].clear(); - mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; - mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; - mPointerGesture.currentGestureCoords[0].clear(); - mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, - mPointerGesture.referenceGestureX); - mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, - mPointerGesture.referenceGestureY); - mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); - if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) { - float xOffset = static_cast(commonDeltaRawX) / - (mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue); - float yOffset = static_cast(commonDeltaRawY) / - (mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue); - mPointerGesture.currentGestureCoords[0] - .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, xOffset); - mPointerGesture.currentGestureCoords[0] - .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset); - } - } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) { - // FREEFORM mode. - ALOGD_IF(DEBUG_GESTURES, - "Gestures: FREEFORM activeTouchId=%d, activeGestureId=%d, " - "currentTouchPointerCount=%d", - activeTouchId, mPointerGesture.activeGestureId, currentFingerCount); - ALOG_ASSERT(mPointerGesture.activeGestureId >= 0); + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureProperties[0].clear(); + mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; + mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, + mPointerGesture.referenceGestureX); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, + mPointerGesture.referenceGestureY); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) { + float xOffset = static_cast(commonDeltaRawX) / + (mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue); + float yOffset = static_cast(commonDeltaRawY) / + (mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue); + mPointerGesture.currentGestureCoords[0] + .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, xOffset); + mPointerGesture.currentGestureCoords[0] + .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset); + } + } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) { + // FREEFORM mode. + ALOGD_IF(DEBUG_GESTURES, + "Gestures: FREEFORM activeTouchId=%d, activeGestureId=%d, " + "currentTouchPointerCount=%d", + activeTouchId, mPointerGesture.activeGestureId, currentFingerCount); + ALOG_ASSERT(mPointerGesture.activeGestureId >= 0); - mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.clear(); - BitSet32 mappedTouchIdBits; - BitSet32 usedGestureIdBits; - if (mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) { - // Initially, assign the active gesture id to the active touch point - // if there is one. No other touch id bits are mapped yet. - if (!*outCancelPreviousGesture) { - mappedTouchIdBits.markBit(activeTouchId); - usedGestureIdBits.markBit(mPointerGesture.activeGestureId); - mPointerGesture.freeformTouchToGestureIdMap[activeTouchId] = - mPointerGesture.activeGestureId; - } else { - mPointerGesture.activeGestureId = -1; - } + BitSet32 mappedTouchIdBits; + BitSet32 usedGestureIdBits; + if (mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) { + // Initially, assign the active gesture id to the active touch point + // if there is one. No other touch id bits are mapped yet. + if (!*cancelPreviousGesture) { + mappedTouchIdBits.markBit(activeTouchId); + usedGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.freeformTouchToGestureIdMap[activeTouchId] = + mPointerGesture.activeGestureId; } else { - // Otherwise, assume we mapped all touches from the previous frame. - // Reuse all mappings that are still applicable. - mappedTouchIdBits.value = mLastCookedState.fingerIdBits.value & - mCurrentCookedState.fingerIdBits.value; - usedGestureIdBits = mPointerGesture.lastGestureIdBits; - - // Check whether we need to choose a new active gesture id because the - // current went went up. - for (BitSet32 upTouchIdBits(mLastCookedState.fingerIdBits.value & - ~mCurrentCookedState.fingerIdBits.value); - !upTouchIdBits.isEmpty();) { - uint32_t upTouchId = upTouchIdBits.clearFirstMarkedBit(); - uint32_t upGestureId = mPointerGesture.freeformTouchToGestureIdMap[upTouchId]; - if (upGestureId == uint32_t(mPointerGesture.activeGestureId)) { - mPointerGesture.activeGestureId = -1; - break; - } - } + mPointerGesture.activeGestureId = -1; } - - ALOGD_IF(DEBUG_GESTURES, - "Gestures: FREEFORM follow up mappedTouchIdBits=0x%08x, " - "usedGestureIdBits=0x%08x, activeGestureId=%d", - mappedTouchIdBits.value, usedGestureIdBits.value, - mPointerGesture.activeGestureId); - - BitSet32 idBits(mCurrentCookedState.fingerIdBits); - for (uint32_t i = 0; i < currentFingerCount; i++) { - uint32_t touchId = idBits.clearFirstMarkedBit(); - uint32_t gestureId; - if (!mappedTouchIdBits.hasBit(touchId)) { - gestureId = usedGestureIdBits.markFirstUnmarkedBit(); - mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId; - ALOGD_IF(DEBUG_GESTURES, - "Gestures: FREEFORM new mapping for touch id %d -> gesture id %d", - touchId, gestureId); - } else { - gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId]; - ALOGD_IF(DEBUG_GESTURES, - "Gestures: FREEFORM existing mapping for touch id %d -> gesture id %d", - touchId, gestureId); + } else { + // Otherwise, assume we mapped all touches from the previous frame. + // Reuse all mappings that are still applicable. + mappedTouchIdBits.value = + mLastCookedState.fingerIdBits.value & mCurrentCookedState.fingerIdBits.value; + usedGestureIdBits = mPointerGesture.lastGestureIdBits; + + // Check whether we need to choose a new active gesture id because the + // current went went up. + for (BitSet32 upTouchIdBits(mLastCookedState.fingerIdBits.value & + ~mCurrentCookedState.fingerIdBits.value); + !upTouchIdBits.isEmpty();) { + uint32_t upTouchId = upTouchIdBits.clearFirstMarkedBit(); + uint32_t upGestureId = mPointerGesture.freeformTouchToGestureIdMap[upTouchId]; + if (upGestureId == uint32_t(mPointerGesture.activeGestureId)) { + mPointerGesture.activeGestureId = -1; + break; } - mPointerGesture.currentGestureIdBits.markBit(gestureId); - mPointerGesture.currentGestureIdToIndex[gestureId] = i; - - const RawPointerData::Pointer& pointer = - mCurrentRawState.rawPointerData.pointerForId(touchId); - float deltaX = (pointer.x - mPointerGesture.referenceTouchX) * mPointerXZoomScale; - float deltaY = (pointer.y - mPointerGesture.referenceTouchY) * mPointerYZoomScale; - rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY); - - mPointerGesture.currentGestureProperties[i].clear(); - mPointerGesture.currentGestureProperties[i].id = gestureId; - mPointerGesture.currentGestureProperties[i].toolType = - AMOTION_EVENT_TOOL_TYPE_FINGER; - mPointerGesture.currentGestureCoords[i].clear(); - mPointerGesture.currentGestureCoords[i] - .setAxisValue(AMOTION_EVENT_AXIS_X, - mPointerGesture.referenceGestureX + deltaX); - mPointerGesture.currentGestureCoords[i] - .setAxisValue(AMOTION_EVENT_AXIS_Y, - mPointerGesture.referenceGestureY + deltaY); - mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, - 1.0f); - } - - if (mPointerGesture.activeGestureId < 0) { - mPointerGesture.activeGestureId = - mPointerGesture.currentGestureIdBits.firstMarkedBit(); - ALOGD_IF(DEBUG_GESTURES, "Gestures: FREEFORM new activeGestureId=%d", - mPointerGesture.activeGestureId); } } - } - mPointerController->setButtonState(mCurrentRawState.buttonState); + ALOGD_IF(DEBUG_GESTURES, + "Gestures: FREEFORM follow up mappedTouchIdBits=0x%08x, usedGestureIdBits=0x%08x, " + "activeGestureId=%d", + mappedTouchIdBits.value, usedGestureIdBits.value, mPointerGesture.activeGestureId); + + BitSet32 idBits(mCurrentCookedState.fingerIdBits); + for (uint32_t i = 0; i < currentFingerCount; i++) { + uint32_t touchId = idBits.clearFirstMarkedBit(); + uint32_t gestureId; + if (!mappedTouchIdBits.hasBit(touchId)) { + gestureId = usedGestureIdBits.markFirstUnmarkedBit(); + mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId; + ALOGD_IF(DEBUG_GESTURES, + "Gestures: FREEFORM new mapping for touch id %d -> gesture id %d", touchId, + gestureId); + } else { + gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId]; + ALOGD_IF(DEBUG_GESTURES, + "Gestures: FREEFORM existing mapping for touch id %d -> gesture id %d", + touchId, gestureId); + } + mPointerGesture.currentGestureIdBits.markBit(gestureId); + mPointerGesture.currentGestureIdToIndex[gestureId] = i; - if (DEBUG_GESTURES) { - ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, " - "currentGestureMode=%d, currentGestureIdBits=0x%08x, " - "lastGestureMode=%d, lastGestureIdBits=0x%08x", - toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture), - mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value, - mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value); - for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty();) { - uint32_t id = idBits.clearFirstMarkedBit(); - uint32_t index = mPointerGesture.currentGestureIdToIndex[id]; - const PointerProperties& properties = mPointerGesture.currentGestureProperties[index]; - const PointerCoords& coords = mPointerGesture.currentGestureCoords[index]; - ALOGD(" currentGesture[%d]: index=%d, toolType=%d, " - "x=%0.3f, y=%0.3f, pressure=%0.3f", - id, index, properties.toolType, coords.getAxisValue(AMOTION_EVENT_AXIS_X), - coords.getAxisValue(AMOTION_EVENT_AXIS_Y), - coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); + const RawPointerData::Pointer& pointer = + mCurrentRawState.rawPointerData.pointerForId(touchId); + float deltaX = (pointer.x - mPointerGesture.referenceTouchX) * mPointerXZoomScale; + float deltaY = (pointer.y - mPointerGesture.referenceTouchY) * mPointerYZoomScale; + rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY); + + mPointerGesture.currentGestureProperties[i].clear(); + mPointerGesture.currentGestureProperties[i].id = gestureId; + mPointerGesture.currentGestureProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + mPointerGesture.currentGestureCoords[i].clear(); + mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, + mPointerGesture.referenceGestureX + + deltaX); + mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, + mPointerGesture.referenceGestureY + + deltaY); + mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); } - for (BitSet32 idBits = mPointerGesture.lastGestureIdBits; !idBits.isEmpty();) { - uint32_t id = idBits.clearFirstMarkedBit(); - uint32_t index = mPointerGesture.lastGestureIdToIndex[id]; - const PointerProperties& properties = mPointerGesture.lastGestureProperties[index]; - const PointerCoords& coords = mPointerGesture.lastGestureCoords[index]; - ALOGD(" lastGesture[%d]: index=%d, toolType=%d, " - "x=%0.3f, y=%0.3f, pressure=%0.3f", - id, index, properties.toolType, coords.getAxisValue(AMOTION_EVENT_AXIS_X), - coords.getAxisValue(AMOTION_EVENT_AXIS_Y), - coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); + + if (mPointerGesture.activeGestureId < 0) { + mPointerGesture.activeGestureId = mPointerGesture.currentGestureIdBits.firstMarkedBit(); + ALOGD_IF(DEBUG_GESTURES, "Gestures: FREEFORM new activeGestureId=%d", + mPointerGesture.activeGestureId); } } - return true; } void TouchInputMapper::moveMousePointerFromPointerDelta(nsecs_t when, uint32_t pointerId) { diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 7b0327e913..7680090188 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -773,6 +773,14 @@ private: bool preparePointerGestures(nsecs_t when, bool* outCancelPreviousGesture, bool* outFinishPreviousGesture, bool isTimeout); + // Returns true if we're in a period of "quiet time" when touchpad gestures should be ignored. + bool checkForTouchpadQuietTime(nsecs_t when); + + std::pair getFastestFinger(); + + void prepareMultiFingerPointerGestures(nsecs_t when, bool* outCancelPreviousGesture, + bool* outFinishPreviousGesture); + // Moves the on-screen mouse pointer based on the movement of the pointer of the given ID // between the last and current events. Uses a relative motion. void moveMousePointerFromPointerDelta(nsecs_t when, uint32_t pointerId); -- GitLab From 530d6bdde7c6dd6c18a7f8fd8b8406dc2adf1c86 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Mon, 10 Oct 2022 16:55:54 -0400 Subject: [PATCH 0396/1310] SF: Clean up API for refresh rate selection Define types for each step: ScoredRefreshRate, RefreshRateRanking, RankedRefreshRates, DisplayModeChoice, and DisplayModeRequest. The last will replace DisplayDevice::ActiveModeInfo in a follow-up CL. Add Scheduler::mLeaderDisplayId (always the primary display for now) and provisionally use its DisplayModeChoice until Scheduler::Policy is tracked per display. Rewrite multi-display tests, which relied on each DisplayMode having the same PhysicalDisplayId, and did not actually verify mode/display association (`expectedDisplays` was unused). Test RefreshRateRanking ordering by descending score. Bug: 241285191 Test: libsurfaceflinger_unittest Change-Id: I1d24d6a1fa9285aa7fc4bf2dd6654fa660d27b08 --- libs/ui/include/ui/DisplayId.h | 6 + .../Display/DisplayModeRequest.h | 36 +++ services/surfaceflinger/DisplayDevice.h | 14 +- .../Scheduler/RefreshRateConfigs.cpp | 80 +++--- .../Scheduler/RefreshRateConfigs.h | 65 +++-- .../surfaceflinger/Scheduler/Scheduler.cpp | 101 +++---- services/surfaceflinger/Scheduler/Scheduler.h | 37 ++- services/surfaceflinger/SurfaceFlinger.cpp | 58 ++-- services/surfaceflinger/SurfaceFlinger.h | 23 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 2 +- .../tests/unittests/FakeDisplayInjector.h | 4 +- .../unittests/RefreshRateConfigsTest.cpp | 260 +++++++++--------- .../tests/unittests/SchedulerTest.cpp | 245 +++++++++-------- .../tests/unittests/TestableScheduler.h | 7 +- .../mock/DisplayHardware/MockDisplayMode.h | 5 + .../unittests/mock/MockSchedulerCallback.h | 4 +- 16 files changed, 530 insertions(+), 417 deletions(-) create mode 100644 services/surfaceflinger/Display/DisplayModeRequest.h diff --git a/libs/ui/include/ui/DisplayId.h b/libs/ui/include/ui/DisplayId.h index d0c03fe39f..3a31fa0848 100644 --- a/libs/ui/include/ui/DisplayId.h +++ b/libs/ui/include/ui/DisplayId.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include #include @@ -67,6 +68,11 @@ inline std::string to_string(DisplayId displayId) { return std::to_string(displayId.value); } +// For tests. +inline std::ostream& operator<<(std::ostream& stream, DisplayId displayId) { + return stream << "DisplayId{" << displayId.value << '}'; +} + // DisplayId of a physical display, such as the internal display or externally connected display. struct PhysicalDisplayId : DisplayId { static constexpr ftl::Optional tryCast(DisplayId id) { diff --git a/services/surfaceflinger/Display/DisplayModeRequest.h b/services/surfaceflinger/Display/DisplayModeRequest.h new file mode 100644 index 0000000000..ac25fe08a5 --- /dev/null +++ b/services/surfaceflinger/Display/DisplayModeRequest.h @@ -0,0 +1,36 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "DisplayHardware/DisplayMode.h" + +namespace android::display { + +struct DisplayModeRequest { + ftl::NonNull modePtr; + + // Whether to emit DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE. + bool emitEvent = false; +}; + +inline bool operator==(const DisplayModeRequest& lhs, const DisplayModeRequest& rhs) { + return lhs.modePtr == rhs.modePtr && lhs.emitEvent == rhs.emitEvent; +} + +} // namespace android::display diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index 06a812b6f4..7abb94b84f 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -41,6 +41,7 @@ #include #include +#include "Display/DisplayModeRequest.h" #include "DisplayHardware/DisplayMode.h" #include "DisplayHardware/Hal.h" #include "DisplayHardware/PowerAdvisor.h" @@ -190,9 +191,20 @@ public: /* ------------------------------------------------------------------------ * Display mode management. */ + + // TODO(b/241285876): Replace ActiveModeInfo and DisplayModeEvent with DisplayModeRequest. struct ActiveModeInfo { + using Event = scheduler::DisplayModeEvent; + + ActiveModeInfo() = default; + ActiveModeInfo(DisplayModePtr mode, Event event) : mode(std::move(mode)), event(event) {} + + explicit ActiveModeInfo(display::DisplayModeRequest&& request) + : ActiveModeInfo(std::move(request.modePtr).take(), + request.emitEvent ? Event::Changed : Event::None) {} + DisplayModePtr mode; - scheduler::DisplayModeEvent event = scheduler::DisplayModeEvent::None; + Event event = Event::None; bool operator!=(const ActiveModeInfo& other) const { return mode != other.mode || event != other.event; diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp index 30483a2aff..39850c7e1e 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -143,8 +144,7 @@ struct RefreshRateConfigs::RefreshRateScoreComparator { ATRACE_INT(name.c_str(), static_cast(std::round(overallScore * 100))); - constexpr float kEpsilon = 0.0001f; - if (std::abs(overallScore - rhs.overallScore) > kEpsilon) { + if (!ScoredRefreshRate::scoresEqual(overallScore, rhs.overallScore)) { return overallScore > rhs.overallScore; } @@ -288,8 +288,7 @@ float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& laye } auto RefreshRateConfigs::getRankedRefreshRates(const std::vector& layers, - GlobalSignals signals) const - -> std::pair, GlobalSignals> { + GlobalSignals signals) const -> RankedRefreshRates { std::lock_guard lock(mLock); if (mGetRankedRefreshRatesCache && @@ -304,7 +303,7 @@ auto RefreshRateConfigs::getRankedRefreshRates(const std::vector& layers, GlobalSignals signals) const - -> std::pair, GlobalSignals> { + -> RankedRefreshRates { using namespace fps_approx_ops; ATRACE_CALL(); ALOGV("%s: %zu layers", __func__, layers.size()); @@ -314,8 +313,7 @@ auto RefreshRateConfigs::getRankedRefreshRatesLocked(const std::vector 0 ? RefreshRateOrder::Descending : RefreshRateOrder::Ascending; std::sort(scores.begin(), scores.end(), RefreshRateScoreComparator{.refreshRateOrder = refreshRateOrder}); - std::vector rankedRefreshRates; - rankedRefreshRates.reserve(scores.size()); - std::transform(scores.begin(), scores.end(), back_inserter(rankedRefreshRates), + RefreshRateRanking ranking; + ranking.reserve(scores.size()); + + std::transform(scores.begin(), scores.end(), back_inserter(ranking), [](const RefreshRateScore& score) { - return RefreshRateRanking{score.modeIt->second, score.overallScore}; + return ScoredRefreshRate{score.modeIt->second, score.overallScore}; }); const bool noLayerScore = std::all_of(scores.begin(), scores.end(), [](RefreshRateScore score) { @@ -574,11 +567,9 @@ auto RefreshRateConfigs::getRankedRefreshRatesLocked(const std::vectorsecond->getFps() < - touchRefreshRates.front().displayModePtr->getFps()) { + scores.front().modeIt->second->getFps() < touchRefreshRates.front().modePtr->getFps()) { ALOGV("Touch Boost"); return {touchRefreshRates, GlobalSignals{.touch = true}}; } @@ -612,12 +601,11 @@ auto RefreshRateConfigs::getRankedRefreshRatesLocked(const std::vector> @@ -783,11 +771,12 @@ const DisplayModePtr& RefreshRateConfigs::getMaxRefreshRateByPolicyLocked(int an return mPrimaryRefreshRates.back()->second; } -std::vector RefreshRateConfigs::getRefreshRatesByPolicyLocked( +auto RefreshRateConfigs::rankRefreshRates( std::optional anchorGroupOpt, RefreshRateOrder refreshRateOrder, - std::optional preferredDisplayModeOpt) const { - std::deque rankings; - const auto makeRanking = [&](const DisplayModeIterator it) REQUIRES(mLock) { + std::optional preferredDisplayModeOpt) const -> RefreshRateRanking { + std::deque ranking; + + const auto rankRefreshRate = [&](DisplayModeIterator it) REQUIRES(mLock) { const auto& mode = it->second; if (anchorGroupOpt && mode->getGroup() != anchorGroupOpt) { return; @@ -800,31 +789,32 @@ std::vector RefreshRateConfigs::getRefreshRatesByPolicyLocke } if (preferredDisplayModeOpt) { if (*preferredDisplayModeOpt == mode->getId()) { - rankings.push_front(RefreshRateRanking{mode, /*score*/ 1.0f}); + constexpr float kScore = std::numeric_limits::max(); + ranking.push_front(ScoredRefreshRate{mode, kScore}); return; } constexpr float kNonPreferredModePenalty = 0.95f; score *= kNonPreferredModePenalty; } - rankings.push_back(RefreshRateRanking{mode, score}); + ranking.push_back(ScoredRefreshRate{mode, score}); }; if (refreshRateOrder == RefreshRateOrder::Ascending) { - std::for_each(mPrimaryRefreshRates.begin(), mPrimaryRefreshRates.end(), makeRanking); + std::for_each(mPrimaryRefreshRates.begin(), mPrimaryRefreshRates.end(), rankRefreshRate); } else { - std::for_each(mPrimaryRefreshRates.rbegin(), mPrimaryRefreshRates.rend(), makeRanking); + std::for_each(mPrimaryRefreshRates.rbegin(), mPrimaryRefreshRates.rend(), rankRefreshRate); } - if (!rankings.empty() || !anchorGroupOpt) { - return {rankings.begin(), rankings.end()}; + if (!ranking.empty() || !anchorGroupOpt) { + return {ranking.begin(), ranking.end()}; } ALOGW("Can't find %s refresh rate by policy with the same mode group" " as the mode group %d", refreshRateOrder == RefreshRateOrder::Ascending ? "min" : "max", anchorGroupOpt.value()); - return getRefreshRatesByPolicyLocked(/*anchorGroupOpt*/ std::nullopt, refreshRateOrder, - preferredDisplayModeOpt); + constexpr std::optional kNoAnchorGroup = std::nullopt; + return rankRefreshRates(kNoAnchorGroup, refreshRateOrder, preferredDisplayModeOpt); } DisplayModePtr RefreshRateConfigs::getActiveModePtr() const { diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h index 721958480b..99f81aa75e 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h +++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -46,15 +47,6 @@ inline DisplayModeEvent operator|(DisplayModeEvent lhs, DisplayModeEvent rhs) { return static_cast(static_cast(lhs) | static_cast(rhs)); } -struct RefreshRateRanking { - DisplayModePtr displayModePtr; - float score = 0.0f; - - bool operator==(const RefreshRateRanking& ranking) const { - return displayModePtr == ranking.displayModePtr && score == ranking.score; - } -}; - using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride; /** @@ -208,12 +200,46 @@ public: return touch == other.touch && idle == other.idle && powerOnImminent == other.powerOnImminent; } + + auto toString() const { + return ftl::Concat("{touch=", touch, ", idle=", idle, + ", powerOnImminent=", powerOnImminent, '}'); + } + }; + + struct ScoredRefreshRate { + DisplayModePtr modePtr; + float score = 0.0f; + + bool operator==(const ScoredRefreshRate& other) const { + return modePtr == other.modePtr && score == other.score; + } + + static bool scoresEqual(float lhs, float rhs) { + constexpr float kEpsilon = 0.0001f; + return std::abs(lhs - rhs) <= kEpsilon; + } + + struct DescendingScore { + bool operator()(const ScoredRefreshRate& lhs, const ScoredRefreshRate& rhs) const { + return lhs.score > rhs.score && !scoresEqual(lhs.score, rhs.score); + } + }; + }; + + using RefreshRateRanking = std::vector; + + struct RankedRefreshRates { + RefreshRateRanking ranking; // Ordered by descending score. + GlobalSignals consideredSignals; + + bool operator==(const RankedRefreshRates& other) const { + return ranking == other.ranking && consideredSignals == other.consideredSignals; + } }; - // Returns the list in the descending order of refresh rates desired - // based on their overall score, and the GlobalSignals that were considered. - std::pair, GlobalSignals> getRankedRefreshRates( - const std::vector&, GlobalSignals) const EXCLUDES(mLock); + RankedRefreshRates getRankedRefreshRates(const std::vector&, + GlobalSignals) const EXCLUDES(mLock); FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) { std::lock_guard lock(mLock); @@ -354,8 +380,8 @@ private: // See mActiveModeIt for thread safety. DisplayModeIterator getActiveModeItLocked() const REQUIRES(mLock); - std::pair, GlobalSignals> getRankedRefreshRatesLocked( - const std::vector&, GlobalSignals) const REQUIRES(mLock); + RankedRefreshRates getRankedRefreshRatesLocked(const std::vector&, + GlobalSignals) const REQUIRES(mLock); // Returns number of display frames and remainder when dividing the layer refresh period by // display refresh period. @@ -373,11 +399,10 @@ private: enum class RefreshRateOrder { Ascending, Descending }; - // Returns the rankings in RefreshRateOrder. May change at runtime. // Only uses the primary range, not the app request range. - std::vector getRefreshRatesByPolicyLocked( - std::optional anchorGroupOpt, RefreshRateOrder, - std::optional preferredDisplayModeOpt) const REQUIRES(mLock); + RefreshRateRanking rankRefreshRates(std::optional anchorGroupOpt, RefreshRateOrder, + std::optional preferredDisplayModeOpt = + std::nullopt) const REQUIRES(mLock); const Policy* getCurrentPolicyLocked() const REQUIRES(mLock); bool isPolicyValidLocked(const Policy& policy) const REQUIRES(mLock); @@ -436,7 +461,7 @@ private: struct GetRankedRefreshRatesCache { std::pair, GlobalSignals> arguments; - std::pair, GlobalSignals> result; + RankedRefreshRates result; }; mutable std::optional mGetRankedRefreshRatesCache GUARDED_BY(mLock); diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 30f2c27b1e..be3ebb7947 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -127,11 +127,19 @@ void Scheduler::setRefreshRateConfigs(std::shared_ptr config } void Scheduler::registerDisplay(sp display) { + if (display->isPrimary()) { + mLeaderDisplayId = display->getPhysicalId(); + } + const bool ok = mDisplays.try_emplace(display->getPhysicalId(), std::move(display)).second; ALOGE_IF(!ok, "%s: Duplicate display", __func__); } void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { + if (mLeaderDisplayId == displayId) { + mLeaderDisplayId.reset(); + } + mDisplays.erase(displayId); } @@ -631,9 +639,8 @@ bool Scheduler::updateFrameRateOverrides(GlobalSignals consideredSignals, Fps di template auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals { - DisplayModePtr newMode; + std::vector modeRequests; GlobalSignals consideredSignals; - std::vector displayModeConfigs; bool refreshRateChanged = false; bool frameRateOverridesChanged; @@ -646,42 +653,41 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals if (currentState == newState) return {}; currentState = std::forward(newState); - displayModeConfigs = getBestDisplayModeConfigs(); - - // mPolicy holds the current mode, using the current mode we find out - // what display is currently being tracked through the policy and - // then find the DisplayModeConfig for that display. So that - // later we check if the policy mode has changed for the same display in policy. - // If mPolicy mode isn't available then we take the first display from the best display - // modes as the candidate for policy changes and frame rate overrides. - // TODO(b/240743786) Update the single display based assumptions and make mode changes - // and mPolicy per display. - const DisplayModeConfig& displayModeConfigForCurrentPolicy = mPolicy.mode - ? *std::find_if(displayModeConfigs.begin(), displayModeConfigs.end(), - [&](const auto& displayModeConfig) REQUIRES(mPolicyLock) { - return displayModeConfig.displayModePtr - ->getPhysicalDisplayId() == - mPolicy.mode->getPhysicalDisplayId(); - }) - : displayModeConfigs.front(); - - newMode = displayModeConfigForCurrentPolicy.displayModePtr; - consideredSignals = displayModeConfigForCurrentPolicy.signals; - frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, newMode->getFps()); - - if (mPolicy.mode == newMode) { + auto modeChoices = chooseDisplayModes(); + + // TODO(b/240743786): The leader display's mode must change for any DisplayModeRequest to go + // through. Fix this by tracking per-display Scheduler::Policy and timers. + DisplayModePtr modePtr; + std::tie(modePtr, consideredSignals) = + modeChoices.get(*mLeaderDisplayId) + .transform([](const DisplayModeChoice& choice) { + return std::make_pair(choice.modePtr, choice.consideredSignals); + }) + .value(); + + modeRequests.reserve(modeChoices.size()); + for (auto& [id, choice] : modeChoices) { + modeRequests.emplace_back( + display::DisplayModeRequest{.modePtr = + ftl::as_non_null(std::move(choice.modePtr)), + .emitEvent = !choice.consideredSignals.idle}); + } + + frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, modePtr->getFps()); + + if (mPolicy.mode != modePtr) { + mPolicy.mode = modePtr; + refreshRateChanged = true; + } else { // We don't need to change the display mode, but we might need to send an event // about a mode change, since it was suppressed if previously considered idle. if (!consideredSignals.idle) { dispatchCachedReportedMode(); } - } else { - mPolicy.mode = newMode; - refreshRateChanged = true; } } if (refreshRateChanged) { - mSchedulerCallback.requestDisplayModes(std::move(displayModeConfigs)); + mSchedulerCallback.requestDisplayModes(std::move(modeRequests)); } if (frameRateOverridesChanged) { mSchedulerCallback.triggerOnFrameRateOverridesChanged(); @@ -689,11 +695,11 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals return consideredSignals; } -std::vector Scheduler::getBestDisplayModeConfigs() const { +auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap { ATRACE_CALL(); - using Rankings = std::pair, GlobalSignals>; - display::PhysicalDisplayVector perDisplayRankings; + using RankedRefreshRates = RefreshRateConfigs::RankedRefreshRates; + display::PhysicalDisplayVector perDisplayRanking; // Tallies the score of a refresh rate across `displayCount` displays. struct RefreshRateTally { @@ -710,11 +716,11 @@ std::vector Scheduler::getBestDisplayModeConfigs() const { const auto globalSignals = makeGlobalSignals(); for (const auto& [id, display] : mDisplays) { - auto [rankings, signals] = + auto rankedRefreshRates = display->holdRefreshRateConfigs() ->getRankedRefreshRates(mPolicy.contentRequirements, globalSignals); - for (const auto& [modePtr, score] : rankings) { + for (const auto& [modePtr, score] : rankedRefreshRates.ranking) { const auto [it, inserted] = refreshRateTallies.try_emplace(modePtr->getFps(), score); if (!inserted) { @@ -724,7 +730,7 @@ std::vector Scheduler::getBestDisplayModeConfigs() const { } } - perDisplayRankings.emplace_back(std::move(rankings), signals); + perDisplayRanking.push_back(std::move(rankedRefreshRates)); } auto maxScoreIt = refreshRateTallies.cbegin(); @@ -750,26 +756,27 @@ std::vector Scheduler::getBestDisplayModeConfigs() const { ? std::make_optional(maxScoreIt->first) : std::nullopt; - std::vector displayModeConfigs; - displayModeConfigs.reserve(mDisplays.size()); + DisplayModeChoiceMap modeChoices; using fps_approx_ops::operator==; - for (const auto& [rankings, signals] : perDisplayRankings) { + for (auto& [ranking, signals] : perDisplayRanking) { if (!chosenFps) { - displayModeConfigs.emplace_back(signals, rankings.front().displayModePtr); + auto& [modePtr, _] = ranking.front(); + modeChoices.try_emplace(modePtr->getPhysicalDisplayId(), + DisplayModeChoice{std::move(modePtr), signals}); continue; } - for (const auto& ranking : rankings) { - const auto& modePtr = ranking.displayModePtr; + for (auto& [modePtr, _] : ranking) { if (modePtr->getFps() == *chosenFps) { - displayModeConfigs.emplace_back(signals, modePtr); + modeChoices.try_emplace(modePtr->getPhysicalDisplayId(), + DisplayModeChoice{std::move(modePtr), signals}); break; } } } - return displayModeConfigs; + return modeChoices; } GlobalSignals Scheduler::makeGlobalSignals() const { @@ -787,11 +794,11 @@ DisplayModePtr Scheduler::getPreferredDisplayMode() { // Make sure the stored mode is up to date. if (mPolicy.mode) { const auto configs = holdRefreshRateConfigs(); - const auto rankings = + const auto ranking = configs->getRankedRefreshRates(mPolicy.contentRequirements, makeGlobalSignals()) - .first; + .ranking; - mPolicy.mode = rankings.front().displayModePtr; + mPolicy.mode = ranking.front().modePtr; } return mPolicy.mode; } diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 6633b0500a..33f612632b 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -38,6 +38,7 @@ #include #include "Display/DisplayMap.h" +#include "Display/DisplayModeRequest.h" #include "DisplayDevice.h" #include "EventThread.h" #include "FrameRateOverrideMappings.h" @@ -88,20 +89,9 @@ namespace scheduler { using GlobalSignals = RefreshRateConfigs::GlobalSignals; -// Config representing the DisplayMode and considered signals for the Display. -struct DisplayModeConfig { - const GlobalSignals signals; - const DisplayModePtr displayModePtr; - - DisplayModeConfig(GlobalSignals signals, DisplayModePtr displayModePtr) - : signals(signals), displayModePtr(std::move(displayModePtr)) {} -}; - struct ISchedulerCallback { - using DisplayModeEvent = scheduler::DisplayModeEvent; - virtual void setVsyncEnabled(bool) = 0; - virtual void requestDisplayModes(std::vector) = 0; + virtual void requestDisplayModes(std::vector) = 0; virtual void kernelTimerChanged(bool expired) = 0; virtual void triggerOnFrameRateOverridesChanged() = 0; @@ -278,8 +268,26 @@ private: template GlobalSignals applyPolicy(S Policy::*, T&&) EXCLUDES(mPolicyLock); - // Returns the best display mode per display. - std::vector getBestDisplayModeConfigs() const REQUIRES(mPolicyLock); + struct DisplayModeChoice { + DisplayModeChoice(DisplayModePtr modePtr, GlobalSignals consideredSignals) + : modePtr(std::move(modePtr)), consideredSignals(consideredSignals) {} + + DisplayModePtr modePtr; + GlobalSignals consideredSignals; + + bool operator==(const DisplayModeChoice& other) const { + return modePtr == other.modePtr && consideredSignals == other.consideredSignals; + } + + // For tests. + friend std::ostream& operator<<(std::ostream& stream, const DisplayModeChoice& choice) { + return stream << '{' << to_string(*choice.modePtr) << " considering " + << choice.consideredSignals.toString().c_str() << '}'; + } + }; + + using DisplayModeChoiceMap = display::PhysicalDisplayMap; + DisplayModeChoiceMap chooseDisplayModes() const REQUIRES(mPolicyLock); GlobalSignals makeGlobalSignals() const REQUIRES(mPolicyLock); @@ -329,6 +337,7 @@ private: mutable std::mutex mPolicyLock; display::PhysicalDisplayMap> mDisplays; + std::optional mLeaderDisplayId; struct Policy { // Policy for choosing the display mode. diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 029dd9fcdf..8e297156c2 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1064,30 +1064,28 @@ status_t SurfaceFlinger::getDisplayStats(const sp&, DisplayStatInfo* ou return NO_ERROR; } -void SurfaceFlinger::setDesiredActiveMode(const ActiveModeInfo& info) { +void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request) { ATRACE_CALL(); - if (!info.mode) { - ALOGW("requested display mode is null"); - return; - } - auto display = getDisplayDeviceLocked(info.mode->getPhysicalDisplayId()); + auto display = getDisplayDeviceLocked(request.modePtr->getPhysicalDisplayId()); if (!display) { ALOGW("%s: display is no longer valid", __func__); return; } - if (display->setDesiredActiveMode(info)) { + const Fps refreshRate = request.modePtr->getFps(); + + if (display->setDesiredActiveMode(DisplayDevice::ActiveModeInfo(std::move(request)))) { scheduleComposite(FrameHint::kNone); // Start receiving vsync samples now, so that we can detect a period // switch. - mScheduler->resyncToHardwareVsync(true, info.mode->getFps()); + mScheduler->resyncToHardwareVsync(true, refreshRate); // As we called to set period, we will call to onRefreshRateChangeCompleted once // VsyncController model is locked. modulateVsync(&VsyncModulator::onRefreshRateChangeInitiated); - updatePhaseConfiguration(info.mode->getFps()); + updatePhaseConfiguration(refreshRate); mScheduler->setModeChangePending(true); } } @@ -1179,7 +1177,7 @@ void SurfaceFlinger::updateInternalStateWithChangedMode() { mRefreshRateStats->setRefreshRate(refreshRate); updatePhaseConfiguration(refreshRate); - if (upcomingModeInfo.event != DisplayModeEvent::None) { + if (upcomingModeInfo.event != scheduler::DisplayModeEvent::None) { mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, upcomingModeInfo.mode); } } @@ -3331,34 +3329,33 @@ void SurfaceFlinger::updateCursorAsync() { mCompositionEngine->updateCursorAsync(refreshArgs); } -void SurfaceFlinger::requestDisplayModes( - std::vector displayModeConfigs) { +void SurfaceFlinger::requestDisplayModes(std::vector modeRequests) { if (mBootStage != BootStage::FINISHED) { ALOGV("Currently in the boot stage, skipping display mode changes"); return; } ATRACE_CALL(); + // If this is called from the main thread mStateLock must be locked before // Currently the only way to call this function from the main thread is from // Scheduler::chooseRefreshRateForContent ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId); - std::for_each(displayModeConfigs.begin(), displayModeConfigs.end(), - [&](const auto& config) REQUIRES(mStateLock) { - const auto& displayModePtr = config.displayModePtr; - if (const auto display = - getDisplayDeviceLocked(displayModePtr->getPhysicalDisplayId()); - display->refreshRateConfigs().isModeAllowed(displayModePtr->getId())) { - const auto event = config.signals.idle ? DisplayModeEvent::None - : DisplayModeEvent::Changed; - setDesiredActiveMode({displayModePtr, event}); - } else { - ALOGV("Skipping disallowed mode %d for display %" PRId64, - displayModePtr->getId().value(), display->getPhysicalId().value); - } - }); + for (auto& request : modeRequests) { + const auto& modePtr = request.modePtr; + const auto display = getDisplayDeviceLocked(modePtr->getPhysicalDisplayId()); + + if (!display) continue; + + if (display->refreshRateConfigs().isModeAllowed(modePtr->getId())) { + setDesiredActiveMode(std::move(request)); + } else { + ALOGV("%s: Mode %d is disallowed for display %s", __func__, modePtr->getId().value(), + to_string(display->getId()).c_str()); + } + } } void SurfaceFlinger::triggerOnFrameRateOverridesChanged() { @@ -6583,18 +6580,19 @@ void SurfaceFlinger::traverseLayersInLayerStack(ui::LayerStack layerStack, const } } -std::optional SurfaceFlinger::getPreferredDisplayMode( +std::optional> SurfaceFlinger::getPreferredDisplayMode( PhysicalDisplayId displayId, DisplayModeId defaultModeId) const { if (const auto schedulerMode = mScheduler->getPreferredDisplayMode(); schedulerMode && schedulerMode->getPhysicalDisplayId() == displayId) { - return schedulerMode; + return ftl::as_non_null(schedulerMode); } return mPhysicalDisplays.get(displayId) .transform(&PhysicalDisplay::snapshotRef) .and_then([&](const display::DisplaySnapshot& snapshot) { return snapshot.displayModes().get(defaultModeId); - }); + }) + .transform(&ftl::as_non_null); } status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( @@ -6650,7 +6648,7 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( return INVALID_OPERATION; } - setDesiredActiveMode({std::move(preferredMode), DisplayModeEvent::Changed}); + setDesiredActiveMode({std::move(preferredMode), .emitEvent = true}); return NO_ERROR; } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index b65dec4dfe..9ffe6abbe6 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -430,10 +431,6 @@ private: mCounterByLayerHandle GUARDED_BY(mLock); }; - using ActiveModeInfo = DisplayDevice::ActiveModeInfo; - using KernelIdleTimerController = - ::android::scheduler::RefreshRateConfigs::KernelIdleTimerController; - enum class BootStage { BOOTLOADER, BOOTANIMATION, @@ -628,16 +625,17 @@ private: // ISchedulerCallback overrides: // Toggles hardware VSYNC by calling into HWC. + // TODO(b/241286146): Rename for self-explanatory API. void setVsyncEnabled(bool) override; - // Sets the desired display mode per display if allowed by policy . - void requestDisplayModes(std::vector) override; - // Called when kernel idle timer has expired. Used to update the refresh rate overlay. + void requestDisplayModes(std::vector) override; void kernelTimerChanged(bool expired) override; - // Called when the frame rate override list changed to trigger an event. void triggerOnFrameRateOverridesChanged() override; // Toggles the kernel idle timer on or off depending the policy decisions around refresh rates. void toggleKernelIdleTimer() REQUIRES(mStateLock); + + using KernelIdleTimerController = scheduler::RefreshRateConfigs::KernelIdleTimerController; + // Get the controller and timeout that will help decide how the kernel idle timer will be // configured and what value to use as the timeout. std::pair, std::chrono::milliseconds> @@ -652,8 +650,8 @@ private: // Show spinner with refresh rate overlay bool mRefreshRateOverlaySpinner = false; - // Sets the desired active mode bit. It obtains the lock, and sets mDesiredActiveMode. - void setDesiredActiveMode(const ActiveModeInfo& info) REQUIRES(mStateLock); + void setDesiredActiveMode(display::DisplayModeRequest&&) REQUIRES(mStateLock); + status_t setActiveModeFromBackdoor(const sp&, DisplayModeId); // Sets the active mode and a new refresh rate in SF. void updateInternalStateWithChangedMode() REQUIRES(mStateLock, kMainThreadContext); @@ -672,9 +670,8 @@ private: // Returns the preferred mode for PhysicalDisplayId if the Scheduler has selected one for that // display. Falls back to the display's defaultModeId otherwise. - std::optional getPreferredDisplayMode(PhysicalDisplayId, - DisplayModeId defaultModeId) const - REQUIRES(mStateLock); + std::optional> getPreferredDisplayMode( + PhysicalDisplayId, DisplayModeId defaultModeId) const REQUIRES(mStateLock); status_t setDesiredDisplayModeSpecsInternal(const sp&, const scheduler::RefreshRateConfigs::PolicyVariant&) diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index e2ae4f4d7f..99279dce8b 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -780,7 +780,7 @@ public: private: void setVsyncEnabled(bool) override {} - void requestDisplayModes(std::vector) override {} + void requestDisplayModes(std::vector) override {} void kernelTimerChanged(bool) override {} void triggerOnFrameRateOverridesChanged() override {} diff --git a/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h index 6ee4b9bf66..6e4bf2b06e 100644 --- a/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h +++ b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h @@ -29,7 +29,7 @@ using android::hardware::graphics::composer::hal::HWDisplayId; using android::Hwc2::mock::PowerAdvisor; struct FakeDisplayInjectorArgs { - uint8_t port = 255u; + PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(255u); HWDisplayId hwcDisplayId = 0; bool isPrimary = true; }; @@ -67,7 +67,7 @@ public: auto compositionDisplay = compositionengine::impl:: createDisplay(mFlinger.getCompositionEngine(), compositionengine::DisplayCreationArgsBuilder() - .setId(PhysicalDisplayId::fromPort(args.port)) + .setId(args.displayId) .setPixels(kResolution) .setPowerAdvisor(&mPowerAdvisor) .build()); diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp index 620825f066..924c5befde 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp @@ -17,6 +17,9 @@ #undef LOG_TAG #define LOG_TAG "SchedulerUnittests" +#include +#include + #include #include #include @@ -34,15 +37,17 @@ namespace android::scheduler { namespace hal = android::hardware::graphics::composer::hal; -using SetPolicyResult = RefreshRateConfigs::SetPolicyResult; -using LayerVoteType = RefreshRateConfigs::LayerVoteType; using LayerRequirement = RefreshRateConfigs::LayerRequirement; +using LayerVoteType = RefreshRateConfigs::LayerVoteType; +using SetPolicyResult = RefreshRateConfigs::SetPolicyResult; using mock::createDisplayMode; struct TestableRefreshRateConfigs : RefreshRateConfigs { - using RefreshRateConfigs::RefreshRateConfigs; using RefreshRateConfigs::RefreshRateOrder; + using RefreshRateConfigs::RefreshRateRanking; + + using RefreshRateConfigs::RefreshRateConfigs; void setActiveModeId(DisplayModeId modeId) { ftl::FakeGuard guard(kMainThreadContext); @@ -74,12 +79,10 @@ struct TestableRefreshRateConfigs : RefreshRateConfigs { return getMaxRefreshRateByPolicyLocked(getActiveModeItLocked()->second->getGroup()); } - std::vector getRefreshRatesByPolicy( - std::optional anchorGroupOpt, RefreshRateOrder refreshRateOrder) const { + RefreshRateRanking rankRefreshRates(std::optional anchorGroupOpt, + RefreshRateOrder refreshRateOrder) const { std::lock_guard lock(mLock); - return RefreshRateConfigs:: - getRefreshRatesByPolicyLocked(anchorGroupOpt, refreshRateOrder, - /*preferredDisplayModeOpt*/ std::nullopt); + return RefreshRateConfigs::rankRefreshRates(anchorGroupOpt, refreshRateOrder); } const std::vector& knownFrameRates() const { return mKnownFrameRates; } @@ -87,14 +90,25 @@ struct TestableRefreshRateConfigs : RefreshRateConfigs { using RefreshRateConfigs::GetRankedRefreshRatesCache; auto& mutableGetRankedRefreshRatesCache() { return mGetRankedRefreshRatesCache; } - auto getRankedRefreshRatesAndSignals(const std::vector& layers, - GlobalSignals signals) const { - return RefreshRateConfigs::getRankedRefreshRates(layers, signals); + auto getRankedRefreshRates(const std::vector& layers, + GlobalSignals signals) const { + const auto result = RefreshRateConfigs::getRankedRefreshRates(layers, signals); + + EXPECT_TRUE(std::is_sorted(result.ranking.begin(), result.ranking.end(), + ScoredRefreshRate::DescendingScore{})); + + return result; + } + + auto getRankedRefreshRatesAsPair(const std::vector& layers, + GlobalSignals signals) const { + const auto [ranking, consideredSignals] = getRankedRefreshRates(layers, signals); + return std::make_pair(ranking, consideredSignals); } DisplayModePtr getBestRefreshRate(const std::vector& layers = {}, GlobalSignals signals = {}) const { - return getRankedRefreshRatesAndSignals(layers, signals).first.front().displayModePtr; + return getRankedRefreshRates(layers, signals).ranking.front().modePtr; } SetPolicyResult setPolicy(const PolicyVariant& policy) { @@ -109,6 +123,8 @@ struct TestableRefreshRateConfigs : RefreshRateConfigs { class RefreshRateConfigsTest : public testing::Test { protected: + using RefreshRateOrder = TestableRefreshRateConfigs::RefreshRateOrder; + RefreshRateConfigsTest(); ~RefreshRateConfigsTest(); @@ -1050,20 +1066,17 @@ TEST_F(RefreshRateConfigsTest, getMaxRefreshRatesByPolicy) { // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the // different group. TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60); - const std::vector& expectedRefreshRates = {RefreshRateRanking{kMode90}, - RefreshRateRanking{kMode60}, - RefreshRateRanking{kMode30}}; - const std::vector& refreshRates = - configs.getRefreshRatesByPolicy(configs.getActiveMode().getGroup(), - TestableRefreshRateConfigs::RefreshRateOrder:: - Descending); + const auto refreshRates = configs.rankRefreshRates(configs.getActiveMode().getGroup(), + RefreshRateOrder::Descending); + const std::array expectedRefreshRates = {kMode90, kMode60, kMode30}; ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { - EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr) - << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue() - << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue(); + EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr) + << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue() + << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue(); } } @@ -1071,20 +1084,17 @@ TEST_F(RefreshRateConfigsTest, getMinRefreshRatesByPolicy) { // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the // different group. TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60); - const std::vector& expectedRefreshRates = {RefreshRateRanking{kMode30}, - RefreshRateRanking{kMode60}, - RefreshRateRanking{kMode90}}; - const std::vector& refreshRates = - configs.getRefreshRatesByPolicy(configs.getActiveMode().getGroup(), - TestableRefreshRateConfigs::RefreshRateOrder:: - Ascending); + const auto refreshRates = configs.rankRefreshRates(configs.getActiveMode().getGroup(), + RefreshRateOrder::Ascending); + const std::array expectedRefreshRates = {kMode30, kMode60, kMode90}; ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { - EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr) - << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue() - << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue(); + EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr) + << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue() + << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue(); } } @@ -1092,23 +1102,20 @@ TEST_F(RefreshRateConfigsTest, getMinRefreshRatesByPolicyOutsideTheGroup) { // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the // different group. TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId72); - const std::vector& expectedRefreshRates = {RefreshRateRanking{kMode30}, - RefreshRateRanking{kMode60}, - RefreshRateRanking{kMode90}}; EXPECT_EQ(SetPolicyResult::Changed, configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}})); - const std::vector& refreshRates = - configs.getRefreshRatesByPolicy(/*anchorGroupOpt*/ std::nullopt, - TestableRefreshRateConfigs::RefreshRateOrder:: - Ascending); + const auto refreshRates = + configs.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Ascending); + const std::array expectedRefreshRates = {kMode30, kMode60, kMode90}; ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { - EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr) - << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue() - << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue(); + EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr) + << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue() + << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue(); } } @@ -1116,47 +1123,48 @@ TEST_F(RefreshRateConfigsTest, getMaxRefreshRatesByPolicyOutsideTheGroup) { // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the // different group. TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId72); - const std::vector& expectedRefreshRates = {RefreshRateRanking{kMode90}, - RefreshRateRanking{kMode60}, - RefreshRateRanking{kMode30}}; EXPECT_EQ(SetPolicyResult::Changed, configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}})); - const std::vector& refreshRates = - configs.getRefreshRatesByPolicy(/*anchorGroupOpt*/ std::nullopt, - TestableRefreshRateConfigs::RefreshRateOrder:: - Descending); + const auto refreshRates = + configs.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Descending); + const std::array expectedRefreshRates = {kMode90, kMode60, kMode30}; ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { - EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr) - << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue() - << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue(); + EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr) + << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue() + << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue(); } } TEST_F(RefreshRateConfigsTest, powerOnImminentConsidered) { - RefreshRateConfigs configs(kModes_60_90, kModeId60); - std::vector expectedRefreshRates = {RefreshRateRanking{kMode90}, - RefreshRateRanking{kMode60}}; + TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); auto [refreshRates, signals] = configs.getRankedRefreshRates({}, {}); EXPECT_FALSE(signals.powerOnImminent); + + std::array expectedRefreshRates = {kMode90, kMode60}; ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { - EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr) - << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue() - << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue(); + EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr) + << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue() + << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue(); } - std::tie(refreshRates, signals) = configs.getRankedRefreshRates({}, {.powerOnImminent = true}); + std::tie(refreshRates, signals) = + configs.getRankedRefreshRatesAsPair({}, {.powerOnImminent = true}); EXPECT_TRUE(signals.powerOnImminent); + ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { - EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr) - << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue() - << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue(); + EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr) + << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue() + << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue(); } std::vector layers = {{.weight = 1.f}}; @@ -1166,34 +1174,38 @@ TEST_F(RefreshRateConfigsTest, powerOnImminentConsidered) { lr1.name = "60Hz ExplicitExactOrMultiple"; std::tie(refreshRates, signals) = - configs.getRankedRefreshRates(layers, {.powerOnImminent = true}); + configs.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = true}); EXPECT_TRUE(signals.powerOnImminent); + ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { - EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr) - << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue() - << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue(); + EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr) + << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue() + << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue(); } - expectedRefreshRates = {RefreshRateRanking{kMode60}, RefreshRateRanking{kMode90}}; std::tie(refreshRates, signals) = - configs.getRankedRefreshRates(layers, {.powerOnImminent = false}); + configs.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = false}); EXPECT_FALSE(signals.powerOnImminent); + + expectedRefreshRates = {kMode60, kMode90}; ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { - EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr) - << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue() - << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue(); + EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr) + << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue() + << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue(); } } TEST_F(RefreshRateConfigsTest, touchConsidered) { - RefreshRateConfigs configs(kModes_60_90, kModeId60); + TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); auto [_, signals] = configs.getRankedRefreshRates({}, {}); EXPECT_FALSE(signals.touch); - std::tie(std::ignore, signals) = configs.getRankedRefreshRates({}, {.touch = true}); + std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair({}, {.touch = true}); EXPECT_TRUE(signals.touch); std::vector layers = {{.weight = 1.f}, {.weight = 1.f}}; @@ -1206,7 +1218,7 @@ TEST_F(RefreshRateConfigsTest, touchConsidered) { lr2.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 60_Hz; lr2.name = "60Hz Heuristic"; - std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true}); + std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true}); EXPECT_TRUE(signals.touch); lr1.vote = LayerVoteType::ExplicitDefault; @@ -1215,7 +1227,7 @@ TEST_F(RefreshRateConfigsTest, touchConsidered) { lr2.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 60_Hz; lr2.name = "60Hz Heuristic"; - std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true}); + std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true}); EXPECT_FALSE(signals.touch); lr1.vote = LayerVoteType::ExplicitExactOrMultiple; @@ -1224,7 +1236,7 @@ TEST_F(RefreshRateConfigsTest, touchConsidered) { lr2.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 60_Hz; lr2.name = "60Hz Heuristic"; - std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true}); + std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true}); EXPECT_TRUE(signals.touch); lr1.vote = LayerVoteType::ExplicitDefault; @@ -1233,7 +1245,7 @@ TEST_F(RefreshRateConfigsTest, touchConsidered) { lr2.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 60_Hz; lr2.name = "60Hz Heuristic"; - std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true}); + std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true}); EXPECT_FALSE(signals.touch); } @@ -1352,7 +1364,7 @@ TEST_F(RefreshRateConfigsTest, const auto [mode, signals] = configs.getRankedRefreshRates(layers, {.touch = true, .idle = true}); - EXPECT_EQ(mode.begin()->displayModePtr, kMode60); + EXPECT_EQ(mode.begin()->modePtr, kMode60); EXPECT_FALSE(signals.touch); } @@ -1407,18 +1419,15 @@ TEST_F(RefreshRateConfigsTest, testDisplayModeOrdering) { lr5.name = "30Hz"; lr5.focused = true; - std::vector expectedRankings = { - RefreshRateRanking{kMode120}, RefreshRateRanking{kMode90}, RefreshRateRanking{kMode72}, - RefreshRateRanking{kMode60}, RefreshRateRanking{kMode30}, - }; + std::array expectedRanking = {kMode120, kMode90, kMode72, kMode60, kMode30}; + auto actualRanking = configs.getRankedRefreshRates(layers, {}).ranking; - std::vector actualOrder = - configs.getRankedRefreshRatesAndSignals(layers, {}).first; - ASSERT_EQ(expectedRankings.size(), actualOrder.size()); - for (size_t i = 0; i < expectedRankings.size(); ++i) { - EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr) - << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue() - << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue(); + ASSERT_EQ(expectedRanking.size(), actualRanking.size()); + + for (size_t i = 0; i < expectedRanking.size(); ++i) { + EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr) + << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps " + << actualRanking[i].modePtr->getFps().getIntValue(); } lr1.vote = LayerVoteType::Max; @@ -1436,18 +1445,15 @@ TEST_F(RefreshRateConfigsTest, testDisplayModeOrdering) { lr5.desiredRefreshRate = 120_Hz; lr5.name = "120Hz"; - expectedRankings = { - RefreshRateRanking{kMode120}, RefreshRateRanking{kMode90}, RefreshRateRanking{kMode72}, - RefreshRateRanking{kMode60}, RefreshRateRanking{kMode30}, - }; + expectedRanking = {kMode120, kMode90, kMode72, kMode60, kMode30}; + actualRanking = configs.getRankedRefreshRates(layers, {}).ranking; - actualOrder = configs.getRankedRefreshRatesAndSignals(layers, {}).first; + ASSERT_EQ(expectedRanking.size(), actualRanking.size()); - ASSERT_EQ(expectedRankings.size(), actualOrder.size()); - for (size_t i = 0; i < expectedRankings.size(); ++i) { - EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr) - << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue() - << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue(); + for (size_t i = 0; i < expectedRanking.size(); ++i) { + EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr) + << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps " + << actualRanking[i].modePtr->getFps().getIntValue(); } lr1.vote = LayerVoteType::Heuristic; @@ -1463,17 +1469,15 @@ TEST_F(RefreshRateConfigsTest, testDisplayModeOrdering) { lr5.desiredRefreshRate = 72_Hz; lr5.name = "72Hz"; - expectedRankings = { - RefreshRateRanking{kMode30}, RefreshRateRanking{kMode60}, RefreshRateRanking{kMode90}, - RefreshRateRanking{kMode120}, RefreshRateRanking{kMode72}, - }; + expectedRanking = {kMode30, kMode60, kMode90, kMode120, kMode72}; + actualRanking = configs.getRankedRefreshRates(layers, {}).ranking; + + ASSERT_EQ(expectedRanking.size(), actualRanking.size()); - actualOrder = configs.getRankedRefreshRatesAndSignals(layers, {}).first; - ASSERT_EQ(expectedRankings.size(), actualOrder.size()); - for (size_t i = 0; i < expectedRankings.size(); ++i) { - EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr) - << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue() - << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue(); + for (size_t i = 0; i < expectedRanking.size(); ++i) { + EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr) + << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps " + << actualRanking[i].modePtr->getFps().getIntValue(); } lr1.desiredRefreshRate = 120_Hz; @@ -1492,17 +1496,15 @@ TEST_F(RefreshRateConfigsTest, testDisplayModeOrdering) { lr5.desiredRefreshRate = 120_Hz; lr5.name = "120Hz-2"; - expectedRankings = { - RefreshRateRanking{kMode90}, RefreshRateRanking{kMode60}, RefreshRateRanking{kMode120}, - RefreshRateRanking{kMode72}, RefreshRateRanking{kMode30}, - }; + expectedRanking = {kMode90, kMode60, kMode120, kMode72, kMode30}; + actualRanking = configs.getRankedRefreshRates(layers, {}).ranking; + + ASSERT_EQ(expectedRanking.size(), actualRanking.size()); - actualOrder = configs.getRankedRefreshRatesAndSignals(layers, {}).first; - ASSERT_EQ(expectedRankings.size(), actualOrder.size()); - for (size_t i = 0; i < expectedRankings.size(); ++i) { - EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr) - << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue() - << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue(); + for (size_t i = 0; i < expectedRanking.size(); ++i) { + EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr) + << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps " + << actualRanking[i].modePtr->getFps().getIntValue(); } } @@ -1513,8 +1515,8 @@ TEST_F(RefreshRateConfigsTest, EXPECT_EQ(SetPolicyResult::Changed, configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}})); - const auto [mode, signals] = configs.getRankedRefreshRatesAndSignals({}, {}); - EXPECT_EQ(mode.front().displayModePtr, kMode90); + const auto [ranking, signals] = configs.getRankedRefreshRates({}, {}); + EXPECT_EQ(ranking.front().modePtr, kMode90); EXPECT_FALSE(signals.touch); std::vector layers = {{.weight = 1.f}}; @@ -1892,13 +1894,12 @@ TEST_F(RefreshRateConfigsTest, idle) { layers[0].vote = voteType; layers[0].desiredRefreshRate = 90_Hz; - const auto [refreshRate, signals] = - configs.getRankedRefreshRatesAndSignals(layers, - {.touch = touchActive, .idle = true}); + const auto [ranking, signals] = + configs.getRankedRefreshRates(layers, {.touch = touchActive, .idle = true}); // Refresh rate will be chosen by either touch state or idle state. EXPECT_EQ(!touchActive, signals.idle); - return refreshRate.front().displayModePtr->getId(); + return ranking.front().modePtr->getId(); }; EXPECT_EQ(SetPolicyResult::Changed, @@ -2059,12 +2060,13 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ReadsCache) { const auto args = std::make_pair(std::vector{}, GlobalSignals{.touch = true, .idle = true}); - const auto result = std::make_pair(std::vector{RefreshRateRanking{kMode90}}, - GlobalSignals{.touch = true}); + const RefreshRateConfigs::RankedRefreshRates result = {{RefreshRateConfigs::ScoredRefreshRate{ + kMode90}}, + {.touch = true}}; configs.mutableGetRankedRefreshRatesCache() = {args, result}; - EXPECT_EQ(result, configs.getRankedRefreshRatesAndSignals(args.first, args.second)); + EXPECT_EQ(result, configs.getRankedRefreshRates(args.first, args.second)); } TEST_F(RefreshRateConfigsTest, getBestRefreshRate_WritesCache) { @@ -2075,7 +2077,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_WritesCache) { std::vector layers = {{.weight = 1.f}, {.weight = 0.5f}}; RefreshRateConfigs::GlobalSignals globalSignals{.touch = true, .idle = true}; - const auto result = configs.getRankedRefreshRatesAndSignals(layers, globalSignals); + const auto result = configs.getRankedRefreshRates(layers, globalSignals); const auto& cache = configs.mutableGetRankedRefreshRatesCache(); ASSERT_TRUE(cache); diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 392398dbe3..147433b422 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -43,8 +43,6 @@ using MockEventThread = android::mock::EventThread; using MockLayer = android::mock::MockLayer; using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector; -constexpr PhysicalDisplayId PHYSICAL_DISPLAY_ID = PhysicalDisplayId::fromPort(255u); - class SchedulerTest : public testing::Test { protected: class MockEventThreadConnection : public android::EventThreadConnection { @@ -61,14 +59,28 @@ protected: SchedulerTest(); - static inline const DisplayModePtr kMode60_1 = createDisplayMode(DisplayModeId(0), 60_Hz); - static inline const DisplayModePtr kMode120_1 = createDisplayMode(DisplayModeId(1), 120_Hz); - static inline const DisplayModePtr kMode60_2 = createDisplayMode(DisplayModeId(2), 60_Hz); - static inline const DisplayModePtr kMode120_2 = createDisplayMode(DisplayModeId(3), 120_Hz); - static inline const DisplayModePtr kMode60_3 = createDisplayMode(DisplayModeId(4), 60_Hz); + static constexpr PhysicalDisplayId kDisplayId1 = PhysicalDisplayId::fromPort(255u); + static inline const DisplayModePtr kDisplay1Mode60 = + createDisplayMode(kDisplayId1, DisplayModeId(0), 60_Hz); + static inline const DisplayModePtr kDisplay1Mode120 = + createDisplayMode(kDisplayId1, DisplayModeId(1), 120_Hz); + static inline const DisplayModes kDisplay1Modes = makeModes(kDisplay1Mode60, kDisplay1Mode120); + + static constexpr PhysicalDisplayId kDisplayId2 = PhysicalDisplayId::fromPort(254u); + static inline const DisplayModePtr kDisplay2Mode60 = + createDisplayMode(kDisplayId2, DisplayModeId(0), 60_Hz); + static inline const DisplayModePtr kDisplay2Mode120 = + createDisplayMode(kDisplayId2, DisplayModeId(1), 120_Hz); + static inline const DisplayModes kDisplay2Modes = makeModes(kDisplay2Mode60, kDisplay2Mode120); + + static constexpr PhysicalDisplayId kDisplayId3 = PhysicalDisplayId::fromPort(253u); + static inline const DisplayModePtr kDisplay3Mode60 = + createDisplayMode(kDisplayId3, DisplayModeId(0), 60_Hz); + static inline const DisplayModes kDisplay3Modes = makeModes(kDisplay3Mode60); std::shared_ptr mConfigs = - std::make_shared(makeModes(kMode60_1), kMode60_1->getId()); + std::make_shared(makeModes(kDisplay1Mode60), + kDisplay1Mode60->getId()); mock::SchedulerCallback mSchedulerCallback; TestableScheduler* mScheduler = new TestableScheduler{mConfigs, mSchedulerCallback}; @@ -114,7 +126,7 @@ TEST_F(SchedulerTest, invalidConnectionHandle) { // The EXPECT_CALLS make sure we don't call the functions on the subsequent event threads. EXPECT_CALL(*mEventThread, onHotplugReceived(_, _)).Times(0); - mScheduler->onHotplugReceived(handle, PHYSICAL_DISPLAY_ID, false); + mScheduler->onHotplugReceived(handle, kDisplayId1, false); EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(0); mScheduler->onScreenAcquired(handle); @@ -138,8 +150,8 @@ TEST_F(SchedulerTest, validConnectionHandle) { ASSERT_EQ(mEventThreadConnection, connection); EXPECT_TRUE(mScheduler->getEventConnection(mConnectionHandle)); - EXPECT_CALL(*mEventThread, onHotplugReceived(PHYSICAL_DISPLAY_ID, false)).Times(1); - mScheduler->onHotplugReceived(mConnectionHandle, PHYSICAL_DISPLAY_ID, false); + EXPECT_CALL(*mEventThread, onHotplugReceived(kDisplayId1, false)).Times(1); + mScheduler->onHotplugReceived(mConnectionHandle, kDisplayId1, false); EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(1); mScheduler->onScreenAcquired(mConnectionHandle); @@ -185,8 +197,7 @@ TEST_F(SchedulerTest, updateDisplayModes) { ASSERT_EQ(1u, mScheduler->layerHistorySize()); mScheduler->setRefreshRateConfigs( - std::make_shared(makeModes(kMode60_1, kMode120_1), - kMode60_1->getId())); + std::make_shared(kDisplay1Modes, kDisplay1Mode60->getId())); ASSERT_EQ(0u, mScheduler->getNumActiveLayers()); mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer); @@ -203,7 +214,7 @@ TEST_F(SchedulerTest, dispatchCachedReportedMode) { TEST_F(SchedulerTest, onNonPrimaryDisplayModeChanged_invalidParameters) { const auto mode = DisplayMode::Builder(hal::HWConfigId(0)) .setId(DisplayModeId(111)) - .setPhysicalDisplayId(PHYSICAL_DISPLAY_ID) + .setPhysicalDisplayId(kDisplayId1) .setVsyncPeriod(111111) .build(); @@ -225,14 +236,15 @@ TEST_F(SchedulerTest, calculateMaxAcquiredBufferCount) { } MATCHER(Is120Hz, "") { - return isApproxEqual(arg.front().displayModePtr->getFps(), 120_Hz); + return isApproxEqual(arg.front().modePtr->getFps(), 120_Hz); } TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) { - auto display = - mFakeDisplayInjector.injectInternalDisplay([&](FakeDisplayDeviceInjector& injector) { - injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId()); - }); + const auto display = mFakeDisplayInjector.injectInternalDisplay( + [&](FakeDisplayDeviceInjector& injector) { + injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId()); + }, + {.displayId = kDisplayId1}); mScheduler->registerDisplay(display); mScheduler->setRefreshRateConfigs(display->holdRefreshRateConfigs()); @@ -256,11 +268,12 @@ TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) { mScheduler->chooseRefreshRateForContent(); } -TEST_F(SchedulerTest, getBestDisplayMode_singleDisplay) { - auto display = - mFakeDisplayInjector.injectInternalDisplay([&](FakeDisplayDeviceInjector& injector) { - injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId()); - }); +TEST_F(SchedulerTest, chooseDisplayModesSingleDisplay) { + const auto display = mFakeDisplayInjector.injectInternalDisplay( + [&](FakeDisplayDeviceInjector& injector) { + injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId()); + }, + {.displayId = kDisplayId1}); mScheduler->registerDisplay(display); @@ -270,115 +283,125 @@ TEST_F(SchedulerTest, getBestDisplayMode_singleDisplay) { GlobalSignals globalSignals = {.idle = true}; mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); - std::vector displayModeConfigs = mScheduler->getBestDisplayModeConfigs(); - ASSERT_EQ(1ul, displayModeConfigs.size()); - EXPECT_EQ(displayModeConfigs.front().displayModePtr, kMode60_1); - EXPECT_EQ(displayModeConfigs.front().signals, globalSignals); + using DisplayModeChoice = TestableScheduler::DisplayModeChoice; + + auto modeChoices = mScheduler->chooseDisplayModes(); + ASSERT_EQ(1u, modeChoices.size()); + + auto choice = modeChoices.get(kDisplayId1); + ASSERT_TRUE(choice); + EXPECT_EQ(choice->get(), DisplayModeChoice(kDisplay1Mode60, globalSignals)); globalSignals = {.idle = false}; mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); - displayModeConfigs = mScheduler->getBestDisplayModeConfigs(); - ASSERT_EQ(1ul, displayModeConfigs.size()); - EXPECT_EQ(displayModeConfigs.front().displayModePtr, kMode120_1); - EXPECT_EQ(displayModeConfigs.front().signals, globalSignals); + + modeChoices = mScheduler->chooseDisplayModes(); + ASSERT_EQ(1u, modeChoices.size()); + + choice = modeChoices.get(kDisplayId1); + ASSERT_TRUE(choice); + EXPECT_EQ(choice->get(), DisplayModeChoice(kDisplay1Mode120, globalSignals)); globalSignals = {.touch = true}; mScheduler->replaceTouchTimer(10); mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); - displayModeConfigs = mScheduler->getBestDisplayModeConfigs(); - ASSERT_EQ(1ul, displayModeConfigs.size()); - EXPECT_EQ(displayModeConfigs.front().displayModePtr, kMode120_1); - EXPECT_EQ(displayModeConfigs.front().signals, globalSignals); - mScheduler->unregisterDisplay(display->getPhysicalId()); + modeChoices = mScheduler->chooseDisplayModes(); + ASSERT_EQ(1u, modeChoices.size()); + + choice = modeChoices.get(kDisplayId1); + ASSERT_TRUE(choice); + EXPECT_EQ(choice->get(), DisplayModeChoice(kDisplay1Mode120, globalSignals)); + + mScheduler->unregisterDisplay(kDisplayId1); EXPECT_TRUE(mScheduler->mutableDisplays().empty()); } -TEST_F(SchedulerTest, getBestDisplayModes_multipleDisplays) { - auto display1 = - mFakeDisplayInjector.injectInternalDisplay([&](FakeDisplayDeviceInjector& injector) { - injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId()); - }); - auto display2 = mFakeDisplayInjector.injectInternalDisplay( +TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { + const auto display1 = mFakeDisplayInjector.injectInternalDisplay( + [&](FakeDisplayDeviceInjector& injector) { + injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId()); + }, + {.displayId = kDisplayId1, .hwcDisplayId = 42, .isPrimary = true}); + const auto display2 = mFakeDisplayInjector.injectInternalDisplay( [&](FakeDisplayDeviceInjector& injector) { - injector.setDisplayModes(makeModes(kMode60_2, kMode120_2), kMode60_2->getId()); + injector.setDisplayModes(kDisplay2Modes, kDisplay2Mode60->getId()); }, - {.port = 253u, .hwcDisplayId = 42, .isPrimary = false}); + {.displayId = kDisplayId2, .hwcDisplayId = 41, .isPrimary = false}); mScheduler->registerDisplay(display1); mScheduler->registerDisplay(display2); - std::vector> expectedDisplays = {display1, display2}; - std::vector layers = {{.weight = 1.f}, {.weight = 1.f}}; - GlobalSignals globalSignals = {.idle = true}; - std::vector expectedConfigs = {DisplayModeConfig{globalSignals, kMode60_1}, - DisplayModeConfig{globalSignals, kMode60_2}}; + using DisplayModeChoice = TestableScheduler::DisplayModeChoice; + TestableScheduler::DisplayModeChoiceMap expectedChoices; - mScheduler->setContentRequirements(layers); - mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); - std::vector displayModeConfigs = mScheduler->getBestDisplayModeConfigs(); - ASSERT_EQ(displayModeConfigs.size(), expectedConfigs.size()); - for (size_t i = 0; i < expectedConfigs.size(); ++i) { - EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr) - << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue() - << " Actual fps " - << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue(); - EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals); - } + { + const GlobalSignals globalSignals = {.idle = true}; + expectedChoices = + ftl::init::map(kDisplayId1, kDisplay1Mode60, + globalSignals)(kDisplayId2, kDisplay2Mode60, + globalSignals); - expectedConfigs = std::vector{DisplayModeConfig{globalSignals, kMode120_1}, - DisplayModeConfig{globalSignals, kMode120_2}}; + std::vector layers = {{.weight = 1.f}, + {.weight = 1.f}}; + mScheduler->setContentRequirements(layers); + mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); - globalSignals = {.idle = false}; - mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); - displayModeConfigs = mScheduler->getBestDisplayModeConfigs(); - ASSERT_EQ(expectedConfigs.size(), displayModeConfigs.size()); - for (size_t i = 0; i < expectedConfigs.size(); ++i) { - EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr) - << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue() - << " Actual fps " - << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue(); - EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals); + const auto actualChoices = mScheduler->chooseDisplayModes(); + EXPECT_EQ(expectedChoices, actualChoices); } - - globalSignals = {.touch = true}; - mScheduler->replaceTouchTimer(10); - mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); - displayModeConfigs = mScheduler->getBestDisplayModeConfigs(); - ASSERT_EQ(expectedConfigs.size(), displayModeConfigs.size()); - for (size_t i = 0; i < expectedConfigs.size(); ++i) { - EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr) - << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue() - << " Actual fps " - << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue(); - EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals); + { + const GlobalSignals globalSignals = {.idle = false}; + expectedChoices = + ftl::init::map(kDisplayId1, kDisplay1Mode120, + globalSignals)(kDisplayId2, kDisplay2Mode120, + globalSignals); + + mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); + + const auto actualChoices = mScheduler->chooseDisplayModes(); + EXPECT_EQ(expectedChoices, actualChoices); } - - // Filters out the 120Hz as it's not present on the display3, even with touch active - // we select 60Hz here. - auto display3 = mFakeDisplayInjector.injectInternalDisplay( - [&](FakeDisplayDeviceInjector& injector) { - injector.setDisplayModes(makeModes(kMode60_3), kMode60_3->getId()); - }, - {.port = 252u, .hwcDisplayId = 41, .isPrimary = false}); - - mScheduler->registerDisplay(display3); - - expectedDisplays = {display1, display2, display3}; - globalSignals = {.touch = true}; - mScheduler->replaceTouchTimer(10); - expectedConfigs = std::vector{DisplayModeConfig{globalSignals, kMode60_1}, - DisplayModeConfig{globalSignals, kMode60_2}, - DisplayModeConfig{globalSignals, kMode60_3}}; - mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); - displayModeConfigs = mScheduler->getBestDisplayModeConfigs(); - ASSERT_EQ(expectedConfigs.size(), displayModeConfigs.size()); - for (size_t i = 0; i < expectedConfigs.size(); ++i) { - EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr) - << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue() - << " Actual fps " - << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue(); - EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals); + { + const GlobalSignals globalSignals = {.touch = true}; + mScheduler->replaceTouchTimer(10); + mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); + + expectedChoices = + ftl::init::map(kDisplayId1, kDisplay1Mode120, + globalSignals)(kDisplayId2, kDisplay2Mode120, + globalSignals); + + const auto actualChoices = mScheduler->chooseDisplayModes(); + EXPECT_EQ(expectedChoices, actualChoices); + } + { + // This display does not support 120 Hz, so we should choose 60 Hz despite the touch signal. + const auto display3 = mFakeDisplayInjector.injectInternalDisplay( + [&](FakeDisplayDeviceInjector& injector) { + injector.setDisplayModes(kDisplay3Modes, kDisplay3Mode60->getId()); + }, + {.displayId = kDisplayId3, .hwcDisplayId = 40, .isPrimary = false}); + + mScheduler->registerDisplay(display3); + + const GlobalSignals globalSignals = {.touch = true}; + mScheduler->replaceTouchTimer(10); + mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); + + expectedChoices = + ftl::init::map(kDisplayId1, kDisplay1Mode60, + globalSignals)(kDisplayId2, kDisplay2Mode60, + globalSignals)(kDisplayId3, + kDisplay3Mode60, + globalSignals); + + const auto actualChoices = mScheduler->chooseDisplayModes(); + EXPECT_EQ(expectedChoices, actualChoices); } } diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index 68df987689..26b2b673a2 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -107,9 +107,12 @@ public: mPolicy.contentRequirements = std::move(layers); } - std::vector getBestDisplayModeConfigs() { + using Scheduler::DisplayModeChoice; + using Scheduler::DisplayModeChoiceMap; + + DisplayModeChoiceMap chooseDisplayModes() { std::lock_guard lock(mPolicyLock); - return Scheduler::getBestDisplayModeConfigs(); + return Scheduler::chooseDisplayModes(); } void dispatchCachedReportedMode() { diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h index a83ecbca26..c78b6bdc18 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h @@ -33,4 +33,9 @@ inline DisplayModePtr createDisplayMode( .build(); } +inline DisplayModePtr createDisplayMode(PhysicalDisplayId displayId, DisplayModeId modeId, + Fps refreshRate) { + return createDisplayMode(modeId, refreshRate, {}, {}, displayId); +} + } // namespace android::mock diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h index 8af2dfa9cf..7d4b159e5e 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h +++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h @@ -24,14 +24,14 @@ namespace android::scheduler::mock { struct SchedulerCallback final : ISchedulerCallback { MOCK_METHOD(void, setVsyncEnabled, (bool), (override)); - MOCK_METHOD(void, requestDisplayModes, (std::vector), (override)); + MOCK_METHOD(void, requestDisplayModes, (std::vector), (override)); MOCK_METHOD(void, kernelTimerChanged, (bool), (override)); MOCK_METHOD(void, triggerOnFrameRateOverridesChanged, (), (override)); }; struct NoOpSchedulerCallback final : ISchedulerCallback { void setVsyncEnabled(bool) override {} - void requestDisplayModes(std::vector) override {} + void requestDisplayModes(std::vector) override {} void kernelTimerChanged(bool) override {} void triggerOnFrameRateOverridesChanged() override {} }; -- GitLab From 8319e365848ca49f6c1026c0aca1d35e8c005610 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Mon, 24 Oct 2022 18:59:20 +0000 Subject: [PATCH 0397/1310] SF: Pass the correct layer creation args when mirroring Test: atest MirrorLayerTest#InitialMirrorState Bug: 238781169 Fixes: 255343651 Change-Id: Ib560eb2c4b1be450f6b32c15ea6a0b83e32d264d --- services/surfaceflinger/SurfaceFlinger.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 029dd9fcdf..46cd031b07 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4413,13 +4413,13 @@ status_t SurfaceFlinger::mirrorLayer(const LayerCreationArgs& args, sp mirrorLayer; sp mirrorFrom; + LayerCreationArgs mirrorArgs(args); { Mutex::Autolock _l(mStateLock); mirrorFrom = fromHandle(mirrorFromHandle).promote(); if (!mirrorFrom) { return NAME_NOT_FOUND; } - LayerCreationArgs mirrorArgs(args); mirrorArgs.flags |= ISurfaceComposerClient::eNoColorFill; mirrorArgs.mirrorLayerHandle = mirrorFromHandle; mirrorArgs.addToRoot = false; @@ -4438,8 +4438,8 @@ status_t SurfaceFlinger::mirrorLayer(const LayerCreationArgs& args, mirrorLayer->sequence, args.name, mirrorFrom->sequence); } - return addClientLayer(args, outResult.handle, mirrorLayer /* layer */, nullptr /* parent */, - nullptr /* outTransformHint */); + return addClientLayer(mirrorArgs, outResult.handle, mirrorLayer /* layer */, + nullptr /* parent */, nullptr /* outTransformHint */); } status_t SurfaceFlinger::mirrorDisplay(DisplayId displayId, const LayerCreationArgs& args, @@ -4470,7 +4470,7 @@ status_t SurfaceFlinger::mirrorDisplay(DisplayId displayId, const LayerCreationA result = createEffectLayer(mirrorArgs, &outResult.handle, &rootMirrorLayer); outResult.layerId = rootMirrorLayer->sequence; outResult.layerName = String16(rootMirrorLayer->getDebugName()); - result |= addClientLayer(args, outResult.handle, rootMirrorLayer /* layer */, + result |= addClientLayer(mirrorArgs, outResult.handle, rootMirrorLayer /* layer */, nullptr /* parent */, nullptr /* outTransformHint */); } -- GitLab From 6947d5359b2fb223066e613e7bee39d74d5fbd32 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Sat, 22 Oct 2022 02:16:21 +0000 Subject: [PATCH 0398/1310] libjpegrecoverymap: refactor Add public functions for encode/decode APIs, and make the the rest functions private. Rename jpeg_r to jpegr, and j_r to jr Test: build Bug: b/252835416 Change-Id: Ia43fa75447ad6aa7154ef2e2207a3d7e8035f412 --- libs/jpegrecoverymap/Android.bp | 4 + .../include/jpegrecoverymap/recoverymap.h | 106 +++++++++++++---- libs/jpegrecoverymap/recoverymap.cpp | 108 +++++++++++++----- 3 files changed, 167 insertions(+), 51 deletions(-) diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp index 285f8d56dc..b52c4c7030 100644 --- a/libs/jpegrecoverymap/Android.bp +++ b/libs/jpegrecoverymap/Android.bp @@ -31,4 +31,8 @@ cc_library_static { srcs: [ "recoverymap.cpp", ], + + shared_libs: [ + "libutils", + ], } \ No newline at end of file diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h index 6949f85873..15eca1e705 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -14,12 +14,14 @@ * limitations under the License. */ + #include + namespace android::recoverymap { /* * Holds information for uncompressed image or recovery map. */ -struct jpeg_r_uncompressed_struct { +struct jpegr_uncompressed_struct { // Pointer to the data location. void* data; // Width of the recovery map or image in pixels. @@ -31,37 +33,90 @@ struct jpeg_r_uncompressed_struct { /* * Holds information for compressed image or recovery map. */ -struct jpeg_r_compressed_struct { +struct jpegr_compressed_struct { // Pointer to the data location. void* data; // Data length; int length; }; -typedef struct jpeg_r_uncompressed_struct* j_r_uncompressed_ptr; -typedef struct jpeg_r_compressed_struct* j_r_compressed_ptr; +typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr; +typedef struct jpegr_compressed_struct* jr_compressed_ptr; class RecoveryMap { public: + /* + * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. + * + * Generate recovery map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append + * the recovery map to the end of the compressed JPEG. + * @param uncompressed_p010_image uncompressed HDR image in P010 color format + * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format + * @param dest destination of the compressed JPEGR image + * @return NO_ERROR if encoding succeeds, error code if error occurs. + */ + status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, + jr_uncompressed_ptr uncompressed_yuv_420_image, + void* dest); + + /* + * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. + * + * Generate recovery map from the HDR and SDR inputs, append the recovery map to the end of the + * compressed JPEG. + * @param uncompressed_p010_image uncompressed HDR image in P010 color format + * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format + * @param compressed_jpeg_image compressed 8-bit JPEG image + * @param dest destination of the compressed JPEGR image + * @return NO_ERROR if encoding succeeds, error code if error occurs. + */ + status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, + jr_uncompressed_ptr uncompressed_yuv_420_image, + void* compressed_jpeg_image, + void* dest); + + /* + * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. + * + * Decode the compressed 8-bit JPEG image to YUV SDR, generate recovery map from the HDR input + * and the decoded SDR result, append the recovery map to the end of the compressed JPEG. + * @param uncompressed_p010_image uncompressed HDR image in P010 color format + * @param compressed_jpeg_image compressed 8-bit JPEG image + * @param dest destination of the compressed JPEGR image + * @return NO_ERROR if encoding succeeds, error code if error occurs. + */ + status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, + void* compressed_jpeg_image, + void* dest); + + /* + * Decompress JPEGR image. + * + * @param compressed_jpegr_image compressed JPEGR image + * @param dest destination of the uncompressed JPEGR image + * @return NO_ERROR if decoding succeeds, error code if error occurs. + */ + status_t decodeJPEGR(void* compressed_jpegr_image, jr_uncompressed_ptr dest); +private: /* * This method is called in the decoding pipeline. It will decode the recovery map. * * @param compressed_recovery_map compressed recovery map * @param dest decoded recover map - * @return true if decoding succeeds + * @return NO_ERROR if decoding succeeds, error code if error occurs. */ - bool decodeRecoveryMap(j_r_compressed_ptr compressed_recovery_map, - j_r_uncompressed_ptr dest); + status_t decodeRecoveryMap(jr_compressed_ptr compressed_recovery_map, + jr_uncompressed_ptr dest); /* * This method is called in the encoding pipeline. It will encode the recovery map. * * @param uncompressed_recovery_map uncompressed recovery map * @param dest encoded recover map - * @return true if encoding succeeds + * @return NO_ERROR if encoding succeeds, error code if error occurs. */ - bool encodeRecoveryMap(j_r_uncompressed_ptr uncompressed_recovery_map, - j_r_compressed_ptr dest); + status_t encodeRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, + jr_compressed_ptr dest); /* * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and @@ -70,11 +125,11 @@ public: * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param dest recover map - * @return true if calculation succeeds + * @return NO_ERROR if calculation succeeds, error code if error occurs. */ - bool generateRecoveryMap(j_r_uncompressed_ptr uncompressed_yuv_420_image, - j_r_uncompressed_ptr uncompressed_p010_image, - j_r_uncompressed_ptr dest); + status_t generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, + jr_uncompressed_ptr uncompressed_p010_image, + jr_uncompressed_ptr dest); /* * This method is called in the decoding pipeline. It will take the uncompressed (decoded) @@ -84,20 +139,21 @@ public: * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format * @param uncompressed_recovery_map uncompressed recovery map * @param dest reconstructed HDR image - * @return true if calculation succeeds + * @return NO_ERROR if calculation succeeds, error code if error occurs. */ - bool applyRecoveryMap(j_r_uncompressed_ptr uncompressed_yuv_420_image, - j_r_uncompressed_ptr uncompressed_recovery_map, - j_r_uncompressed_ptr dest); + status_t applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, + jr_uncompressed_ptr uncompressed_recovery_map, + jr_uncompressed_ptr dest); /* * This method is called in the decoding pipeline. It will read XMP metadata to find the start * position of the compressed recovery map, and will extract the compressed recovery map. * - * @param compressed_jpeg_r_image compressed JPEG_R image - * @return compressed recovery map + * @param compressed_jpegr_image compressed JPEGR image + * @param dest destination of compressed recovery map + * @return NO_ERROR if calculation succeeds, error code if error occurs. */ - j_r_compressed_ptr extractRecoveryMap(void* compressed_jpeg_r_image); + status_t extractRecoveryMap(void* compressed_jpegr_image, jr_compressed_ptr dest); /* * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image @@ -106,10 +162,12 @@ public: * * @param compressed_jpeg_image compressed 8-bit JPEG image * @param compress_recovery_map compressed recover map - * @return compressed JPEG_R image + * @param dest compressed JPEGR image + * @return NO_ERROR if calculation succeeds, error code if error occurs. */ - void* appendRecoveryMap(void* compressed_jpeg_image, - j_r_compressed_ptr compressed_recovery_map); + status_t appendRecoveryMap(void* compressed_jpeg_image, + jr_compressed_ptr compressed_recovery_map, + void* dest); }; } // namespace android::recoverymap diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index bd92652368..5d2572213b 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -18,69 +18,123 @@ namespace android::recoverymap { -bool RecoveryMap::decodeRecoveryMap(j_r_compressed_ptr compressed_recovery_map, - j_r_uncompressed_ptr dest) { +status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, + jr_uncompressed_ptr uncompressed_yuv_420_image, + void* dest) { + if (uncompressed_p010_image == nullptr + || uncompressed_yuv_420_image == nullptr + || dest == nullptr) { + return BAD_VALUE; + } + + // TBD + return NO_ERROR; +} + +status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, + jr_uncompressed_ptr uncompressed_yuv_420_image, + void* compressed_jpeg_image, + void* dest) { + + if (uncompressed_p010_image == nullptr + || uncompressed_yuv_420_image == nullptr + || compressed_jpeg_image == nullptr + || dest == nullptr) { + return BAD_VALUE; + } + + // TBD + return NO_ERROR; +} + +status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, + void* compressed_jpeg_image, + void* dest) { + if (uncompressed_p010_image == nullptr + || compressed_jpeg_image == nullptr + || dest == nullptr) { + return BAD_VALUE; + } + + // TBD + return NO_ERROR; +} + +status_t RecoveryMap::decodeJPEGR(void* compressed_jpegr_image, jr_uncompressed_ptr dest) { + if (compressed_jpegr_image == nullptr || dest == nullptr) { + return BAD_VALUE; + } + + // TBD + return NO_ERROR; +} + +status_t RecoveryMap::decodeRecoveryMap(jr_compressed_ptr compressed_recovery_map, + jr_uncompressed_ptr dest) { if (compressed_recovery_map == nullptr || dest == nullptr) { - return false; + return BAD_VALUE; } // TBD - return true; + return NO_ERROR; } -bool RecoveryMap::encodeRecoveryMap(j_r_uncompressed_ptr uncompressed_recovery_map, - j_r_compressed_ptr dest) { +status_t RecoveryMap::encodeRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, + jr_compressed_ptr dest) { if (uncompressed_recovery_map == nullptr || dest == nullptr) { - return false; + return BAD_VALUE; } // TBD - return true; + return NO_ERROR; } -bool RecoveryMap::generateRecoveryMap(j_r_uncompressed_ptr uncompressed_yuv_420_image, - j_r_uncompressed_ptr uncompressed_p010_image, - j_r_uncompressed_ptr dest) { +status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, + jr_uncompressed_ptr uncompressed_p010_image, + jr_uncompressed_ptr dest) { if (uncompressed_yuv_420_image == nullptr || uncompressed_p010_image == nullptr || dest == nullptr) { - return false; + return BAD_VALUE; } // TBD - return true; + return NO_ERROR; } -bool RecoveryMap::applyRecoveryMap(j_r_uncompressed_ptr uncompressed_yuv_420_image, - j_r_uncompressed_ptr uncompressed_recovery_map, - j_r_uncompressed_ptr dest) { +status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, + jr_uncompressed_ptr uncompressed_recovery_map, + jr_uncompressed_ptr dest) { if (uncompressed_yuv_420_image == nullptr || uncompressed_recovery_map == nullptr || dest == nullptr) { - return false; + return BAD_VALUE; } // TBD - return true; + return NO_ERROR; } -j_r_compressed_ptr RecoveryMap::extractRecoveryMap(void* compressed_jpeg_r_image) { - if (compressed_jpeg_r_image == nullptr) { - return nullptr; +status_t RecoveryMap::extractRecoveryMap(void* compressed_jpegr_image, jr_compressed_ptr dest) { + if (compressed_jpegr_image == nullptr || dest == nullptr) { + return BAD_VALUE; } // TBD - return nullptr; + return NO_ERROR; } -void* RecoveryMap::appendRecoveryMap(void* compressed_jpeg_image, - j_r_compressed_ptr compressed_recovery_map) { - if (compressed_jpeg_image == nullptr || compressed_recovery_map == nullptr) { - return nullptr; +status_t RecoveryMap::appendRecoveryMap(void* compressed_jpeg_image, + jr_compressed_ptr compressed_recovery_map, + void* dest) { + if (compressed_jpeg_image == nullptr + || compressed_recovery_map == nullptr + || dest == nullptr) { + return BAD_VALUE; } // TBD - return nullptr; + return NO_ERROR; } } // namespace android::recoverymap -- GitLab From eca273cd14f1402052b372663995de3024703d54 Mon Sep 17 00:00:00 2001 From: Yeabkal Wubshit Date: Wed, 5 Oct 2022 19:06:40 -0700 Subject: [PATCH 0399/1310] Create Native VelocityTracker#isAxisSupported Function This will eventually be used by the Java layer, by the #isAxisSupported API. Bug: 32830165 Test: unit test Change-Id: Iebd040403797f14b3cd99b2d6fabb50b1ef7998e --- include/input/VelocityTracker.h | 3 +++ libs/input/VelocityTracker.cpp | 4 ++++ libs/input/tests/VelocityTracker_test.cpp | 20 ++++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h index 294879ed21..62c3ae15ce 100644 --- a/include/input/VelocityTracker.h +++ b/include/input/VelocityTracker.h @@ -106,6 +106,9 @@ public: ~VelocityTracker(); + /** Return true if the axis is supported for velocity tracking, false otherwise. */ + static bool isAxisSupported(int32_t axis); + // Resets the velocity tracker state. void clear(); diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp index 4a4f734a86..19b4684e4a 100644 --- a/libs/input/VelocityTracker.cpp +++ b/libs/input/VelocityTracker.cpp @@ -150,6 +150,10 @@ VelocityTracker::VelocityTracker(const Strategy strategy) VelocityTracker::~VelocityTracker() { } +bool VelocityTracker::isAxisSupported(int32_t axis) { + return DEFAULT_STRATEGY_BY_AXIS.find(axis) != DEFAULT_STRATEGY_BY_AXIS.end(); +} + void VelocityTracker::configureStrategy(int32_t axis) { const bool isDifferentialAxis = DIFFERENTIAL_AXES.find(axis) != DIFFERENTIAL_AXES.end(); diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp index bd126636d7..54feea2644 100644 --- a/libs/input/tests/VelocityTracker_test.cpp +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -298,6 +298,26 @@ static void computeAndCheckQuadraticEstimate(const std::vector Date: Mon, 17 Oct 2022 14:49:58 +0000 Subject: [PATCH 0400/1310] Decouple HapticScale and ExternalVibratorService scale. HapticScale operates independently of IExternalVibratorService, so there's no need to have identical constants. Bug: 248993206 Test: presubmit Change-Id: I990fcfa4a3c6fe6b468c49d4e3a023913d102a95 --- libs/vibrator/ExternalVibration.cpp | 29 +++++++++++++------ .../include/vibrator/ExternalVibration.h | 7 ++++- .../include/vibrator/ExternalVibrationUtils.h | 2 -- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/libs/vibrator/ExternalVibration.cpp b/libs/vibrator/ExternalVibration.cpp index ec906458a3..80e911c65d 100644 --- a/libs/vibrator/ExternalVibration.cpp +++ b/libs/vibrator/ExternalVibration.cpp @@ -22,15 +22,6 @@ #include #include - -// To guarantee if HapticScale enum has the same value as IExternalVibratorService -static_assert(static_cast(android::os::HapticScale::MUTE) == static_cast(android::os::IExternalVibratorService::SCALE_MUTE)); -static_assert(static_cast(android::os::HapticScale::VERY_LOW) == static_cast(android::os::IExternalVibratorService::SCALE_VERY_LOW)); -static_assert(static_cast(android::os::HapticScale::LOW) == static_cast(android::os::IExternalVibratorService::SCALE_LOW)); -static_assert(static_cast(android::os::HapticScale::NONE) == static_cast(android::os::IExternalVibratorService::SCALE_NONE)); -static_assert(static_cast(android::os::HapticScale::HIGH) == static_cast(android::os::IExternalVibratorService::SCALE_HIGH)); -static_assert(static_cast(android::os::HapticScale::VERY_HIGH) == static_cast(android::os::IExternalVibratorService::SCALE_VERY_HIGH)); - void writeAudioAttributes(const audio_attributes_t& attrs, android::Parcel* out) { out->writeInt32(attrs.usage); out->writeInt32(attrs.content_type); @@ -74,5 +65,25 @@ inline bool ExternalVibration::operator==(const ExternalVibration& rhs) const { return mToken == rhs.mToken; } +os::HapticScale ExternalVibration::externalVibrationScaleToHapticScale(int externalVibrationScale) { + switch (externalVibrationScale) { + case IExternalVibratorService::SCALE_MUTE: + return os::HapticScale::MUTE; + case IExternalVibratorService::SCALE_VERY_LOW: + return os::HapticScale::VERY_LOW; + case IExternalVibratorService::SCALE_LOW: + return os::HapticScale::LOW; + case IExternalVibratorService::SCALE_NONE: + return os::HapticScale::NONE; + case IExternalVibratorService::SCALE_HIGH: + return os::HapticScale::HIGH; + case IExternalVibratorService::SCALE_VERY_HIGH: + return os::HapticScale::VERY_HIGH; + default: + ALOGE("Unknown ExternalVibrationScale %d, not applying scaling", externalVibrationScale); + return os::HapticScale::NONE; + } +} + } // namespace os } // namespace android diff --git a/libs/vibrator/include/vibrator/ExternalVibration.h b/libs/vibrator/include/vibrator/ExternalVibration.h index 760dbce149..00cd3cd256 100644 --- a/libs/vibrator/include/vibrator/ExternalVibration.h +++ b/libs/vibrator/include/vibrator/ExternalVibration.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace android { namespace os { @@ -44,6 +45,10 @@ public : audio_attributes_t getAudioAttributes() const { return mAttrs; } sp getController() { return mController; } + /* Converts the scale from non-public ExternalVibrationService into the HapticScale + * used by the utils. + */ + static os::HapticScale externalVibrationScaleToHapticScale(int externalVibrationScale); private: int32_t mUid; @@ -53,7 +58,7 @@ private: sp mToken = new BBinder(); }; -} // namespace android } // namespace os +} // namespace android #endif // ANDROID_EXTERNAL_VIBRATION_H diff --git a/libs/vibrator/include/vibrator/ExternalVibrationUtils.h b/libs/vibrator/include/vibrator/ExternalVibrationUtils.h index c588bfdedd..ca219d3cbf 100644 --- a/libs/vibrator/include/vibrator/ExternalVibrationUtils.h +++ b/libs/vibrator/include/vibrator/ExternalVibrationUtils.h @@ -19,8 +19,6 @@ namespace android::os { -// Copied from frameworks/base/core/java/android/os/IExternalVibratorService.aidl -// The values are checked in ExternalVibration.cpp enum class HapticScale { MUTE = -100, VERY_LOW = -2, -- GitLab From 73908d154d3b35f1f990dd3eca73be1b106cd194 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Mon, 24 Oct 2022 21:46:42 -0700 Subject: [PATCH 0401/1310] SF: Update transform hint from current state Transform hint is updated before current state is committed to drawing state. Fix this by looking at current state when getting the layer's layerStack. Test: open app on non active display Fixes: 255246561 Change-Id: Ib002e4ed4cae1b5258bea761fda605b25138b77d --- services/surfaceflinger/Layer.cpp | 6 ++++-- services/surfaceflinger/Layer.h | 4 +++- services/surfaceflinger/SurfaceFlinger.cpp | 7 ++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index def0dfaa49..b47188fea4 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -1014,8 +1014,10 @@ bool Layer::isLayerFocusedBasedOnPriority(int32_t priority) { return priority == PRIORITY_FOCUSED_WITH_MODE || priority == PRIORITY_FOCUSED_WITHOUT_MODE; }; -ui::LayerStack Layer::getLayerStack() const { - if (const auto parent = mDrawingParent.promote()) { +ui::LayerStack Layer::getLayerStack(LayerVector::StateSet state) const { + bool useDrawing = state == LayerVector::StateSet::Drawing; + const auto parent = useDrawing ? mDrawingParent.promote() : mCurrentParent.promote(); + if (parent) { return parent->getLayerStack(); } return getDrawingState().layerStack; diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 1773c03a0a..336b4ff410 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -320,7 +320,9 @@ public: virtual bool setTrustedOverlay(bool); virtual bool setFlags(uint32_t flags, uint32_t mask); virtual bool setLayerStack(ui::LayerStack); - virtual ui::LayerStack getLayerStack() const; + virtual ui::LayerStack getLayerStack( + LayerVector::StateSet state = LayerVector::StateSet::Drawing) const; + virtual bool setMetadata(const LayerMetadata& data); virtual void setChildrenDrawingParent(const sp&); virtual bool reparent(const sp& newParentHandle); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 46cd031b07..52e85421b6 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4123,7 +4123,7 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime return 0; } - ui::LayerStack oldLayerStack = layer->getLayerStack(); + ui::LayerStack oldLayerStack = layer->getLayerStack(LayerVector::StateSet::Current); // Only set by BLAST adapter layers if (what & layer_state_t::eProducerDisconnect) { @@ -4392,7 +4392,8 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime // setTransactionCompletedListener // if the layer has been parented on to a new display, update its transform hint. - if (((flags & eTransformHintUpdateNeeded) == 0) && oldLayerStack != layer->getLayerStack()) { + if (((flags & eTransformHintUpdateNeeded) == 0) && + oldLayerStack != layer->getLayerStack(LayerVector::StateSet::Current)) { flags |= eTransformHintUpdateNeeded; } @@ -6892,7 +6893,7 @@ void SurfaceFlinger::handleLayerCreatedLocked(const LayerCreatedState& state, Vs parent->addChild(layer); } - ui::LayerStack layerStack = layer->getLayerStack(); + ui::LayerStack layerStack = layer->getLayerStack(LayerVector::StateSet::Current); sp hintDisplay; // Find the display that includes the layer. for (const auto& [token, display] : mDisplays) { -- GitLab From 4a66b3cdf1a66827767ebf495cdebdf2f81d974a Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Wed, 19 Oct 2022 18:04:47 +0000 Subject: [PATCH 0402/1310] libjpegrecoverymap: add jpeg encoder with YUV input bug: b/252835416 test: make, jpegencoder_test Change-Id: I36f9d56374f072d9a5bb356e8b584a9b9e03bc1e --- libs/jpegrecoverymap/Android.bp | 14 + .../include/jpegrecoverymap/jpegencoder.h | 86 + libs/jpegrecoverymap/jpegencoder.cpp | 197 ++ libs/jpegrecoverymap/tests/Android.bp | 16 + .../tests/data/minnie-318x240.yu12 | 1930 +++++++++++++++++ .../tests/data/minnie-320x240.yu12 | 1930 +++++++++++++++++ .../tests/jpegencoder_test.cpp | 110 + 7 files changed, 4283 insertions(+) create mode 100644 libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h create mode 100644 libs/jpegrecoverymap/jpegencoder.cpp create mode 100644 libs/jpegrecoverymap/tests/data/minnie-318x240.yu12 create mode 100644 libs/jpegrecoverymap/tests/data/minnie-320x240.yu12 create mode 100644 libs/jpegrecoverymap/tests/jpegencoder_test.cpp diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp index b52c4c7030..9b0ee2e2a7 100644 --- a/libs/jpegrecoverymap/Android.bp +++ b/libs/jpegrecoverymap/Android.bp @@ -35,4 +35,18 @@ cc_library_static { shared_libs: [ "libutils", ], +} + +cc_library_static { + name: "libjpegencoder", + + shared_libs: [ + "libjpeg", + ], + + export_include_dirs: ["include"], + + srcs: [ + "jpegencoder.cpp", + ], } \ No newline at end of file diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h new file mode 100644 index 0000000000..ec1291892d --- /dev/null +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h @@ -0,0 +1,86 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// We must include cstdio before jpeglib.h. It is a requirement of libjpeg. +#include + +extern "C" { +#include +#include +} + +#include +#include + +namespace android::recoverymap { + +/* + * Encapsulates a converter from YUV420Planer to JPEG format. This class is not thread-safe. + */ +class JpegEncoder { +public: + JpegEncoder(); + ~JpegEncoder(); + + /* + * Compresses YUV420Planer image to JPEG format. After calling this method, call + * getCompressedImage() to get the image. |quality| is the jpeg image quality parameter to use. + * It ranges from 1 (poorest quality) to 100 (highest quality). |iccBuffer| is the buffer of + * ICC segment which will be added to the compressed image. + * Returns false if errors occur during compression. + */ + bool compressImage(const void* image, int width, int height, int quality, + const void* iccBuffer, unsigned int iccSize); + + /* + * Returns the compressed JPEG buffer pointer. This method must be called only after calling + * compressImage(). + */ + const void* getCompressedImagePtr(); + + /* + * Returns the compressed JPEG buffer size. This method must be called only after calling + * compressImage(). + */ + size_t getCompressedImageSize(); + +private: + // initDestination(), emptyOutputBuffer() and emptyOutputBuffer() are callback functions to be + // passed into jpeg library. + static void initDestination(j_compress_ptr cinfo); + static boolean emptyOutputBuffer(j_compress_ptr cinfo); + static void terminateDestination(j_compress_ptr cinfo); + static void outputErrorMessage(j_common_ptr cinfo); + + // Returns false if errors occur. + bool encode(const void* inYuv, int width, int height, int jpegQuality, + const void* iccBuffer, unsigned int iccSize); + void setJpegDestination(jpeg_compress_struct* cinfo); + void setJpegCompressStruct(int width, int height, int quality, jpeg_compress_struct* cinfo); + // Returns false if errors occur. + bool compress(jpeg_compress_struct* cinfo, const uint8_t* yuv); + + // The block size for encoded jpeg image buffer. + static const int kBlockSize = 16384; + // Process 16 lines of Y and 16 lines of U/V each time. + // We must pass at least 16 scanlines according to libjpeg documentation. + static const int kCompressBatchSize = 16; + + // The buffer that holds the compressed result. + std::vector mResultBuffer; +}; + +} /* namespace android */ \ No newline at end of file diff --git a/libs/jpegrecoverymap/jpegencoder.cpp b/libs/jpegrecoverymap/jpegencoder.cpp new file mode 100644 index 0000000000..b5d3a063ca --- /dev/null +++ b/libs/jpegrecoverymap/jpegencoder.cpp @@ -0,0 +1,197 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include + +namespace android::recoverymap { + +// The destination manager that can access |mResultBuffer| in JpegEncoder. +struct destination_mgr { +public: + struct jpeg_destination_mgr mgr; + JpegEncoder* encoder; +}; + +JpegEncoder::JpegEncoder() { +} + +JpegEncoder::~JpegEncoder() { +} + +bool JpegEncoder::compressImage(const void* image, int width, int height, int quality, + const void* iccBuffer, unsigned int iccSize) { + if (width % 8 != 0 || height % 2 != 0) { + ALOGE("Image size can not be handled: %dx%d", width, height); + return false; + } + + mResultBuffer.clear(); + if (!encode(image, width, height, quality, iccBuffer, iccSize)) { + return false; + } + ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes", + (width * height * 12) / 8, width, height, mResultBuffer.size()); + return true; +} + +const void* JpegEncoder::getCompressedImagePtr() { + return mResultBuffer.data(); +} + +size_t JpegEncoder::getCompressedImageSize() { + return mResultBuffer.size(); +} + +void JpegEncoder::initDestination(j_compress_ptr cinfo) { + destination_mgr* dest = reinterpret_cast(cinfo->dest); + std::vector& buffer = dest->encoder->mResultBuffer; + buffer.resize(kBlockSize); + dest->mgr.next_output_byte = &buffer[0]; + dest->mgr.free_in_buffer = buffer.size(); +} + +boolean JpegEncoder::emptyOutputBuffer(j_compress_ptr cinfo) { + destination_mgr* dest = reinterpret_cast(cinfo->dest); + std::vector& buffer = dest->encoder->mResultBuffer; + size_t oldsize = buffer.size(); + buffer.resize(oldsize + kBlockSize); + dest->mgr.next_output_byte = &buffer[oldsize]; + dest->mgr.free_in_buffer = kBlockSize; + return true; +} + +void JpegEncoder::terminateDestination(j_compress_ptr cinfo) { + destination_mgr* dest = reinterpret_cast(cinfo->dest); + std::vector& buffer = dest->encoder->mResultBuffer; + buffer.resize(buffer.size() - dest->mgr.free_in_buffer); +} + +void JpegEncoder::outputErrorMessage(j_common_ptr cinfo) { + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + ALOGE("%s\n", buffer); +} + +bool JpegEncoder::encode(const void* inYuv, int width, int height, int jpegQuality, + const void* iccBuffer, unsigned int iccSize) { + jpeg_compress_struct cinfo; + jpeg_error_mgr jerr; + + cinfo.err = jpeg_std_error(&jerr); + // Override output_message() to print error log with ALOGE(). + cinfo.err->output_message = &outputErrorMessage; + jpeg_create_compress(&cinfo); + setJpegDestination(&cinfo); + + setJpegCompressStruct(width, height, jpegQuality, &cinfo); + jpeg_start_compress(&cinfo, TRUE); + + if (iccBuffer != nullptr && iccSize > 0) { + jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast(iccBuffer), iccSize); + } + + if (!compress(&cinfo, static_cast(inYuv))) { + return false; + } + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + return true; +} + +void JpegEncoder::setJpegDestination(jpeg_compress_struct* cinfo) { + destination_mgr* dest = static_cast((*cinfo->mem->alloc_small) ( + (j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(destination_mgr))); + dest->encoder = this; + dest->mgr.init_destination = &initDestination; + dest->mgr.empty_output_buffer = &emptyOutputBuffer; + dest->mgr.term_destination = &terminateDestination; + cinfo->dest = reinterpret_cast(dest); +} + +void JpegEncoder::setJpegCompressStruct(int width, int height, int quality, + jpeg_compress_struct* cinfo) { + cinfo->image_width = width; + cinfo->image_height = height; + cinfo->input_components = 3; + cinfo->in_color_space = JCS_YCbCr; + jpeg_set_defaults(cinfo); + + jpeg_set_quality(cinfo, quality, TRUE); + jpeg_set_colorspace(cinfo, JCS_YCbCr); + cinfo->raw_data_in = TRUE; + cinfo->dct_method = JDCT_IFAST; + + // Configure sampling factors. The sampling factor is JPEG subsampling 420 because the + // source format is YUV420. + cinfo->comp_info[0].h_samp_factor = 2; + cinfo->comp_info[0].v_samp_factor = 2; + cinfo->comp_info[1].h_samp_factor = 1; + cinfo->comp_info[1].v_samp_factor = 1; + cinfo->comp_info[2].h_samp_factor = 1; + cinfo->comp_info[2].v_samp_factor = 1; +} + +bool JpegEncoder::compress(jpeg_compress_struct* cinfo, const uint8_t* yuv) { + JSAMPROW y[kCompressBatchSize]; + JSAMPROW cb[kCompressBatchSize / 2]; + JSAMPROW cr[kCompressBatchSize / 2]; + JSAMPARRAY planes[3] {y, cb, cr}; + + size_t y_plane_size = cinfo->image_width * cinfo->image_height; + size_t uv_plane_size = y_plane_size / 4; + uint8_t* y_plane = const_cast(yuv); + uint8_t* u_plane = const_cast(yuv + y_plane_size); + uint8_t* v_plane = const_cast(yuv + y_plane_size + uv_plane_size); + std::unique_ptr empty(new uint8_t[cinfo->image_width]); + memset(empty.get(), 0, cinfo->image_width); + + while (cinfo->next_scanline < cinfo->image_height) { + for (int i = 0; i < kCompressBatchSize; ++i) { + size_t scanline = cinfo->next_scanline + i; + if (scanline < cinfo->image_height) { + y[i] = y_plane + scanline * cinfo->image_width; + } else { + y[i] = empty.get(); + } + } + // cb, cr only have half scanlines + for (int i = 0; i < kCompressBatchSize / 2; ++i) { + size_t scanline = cinfo->next_scanline / 2 + i; + if (scanline < cinfo->image_height / 2) { + int offset = scanline * (cinfo->image_width / 2); + cb[i] = u_plane + offset; + cr[i] = v_plane + offset; + } else { + cb[i] = cr[i] = empty.get(); + } + } + + int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize); + if (processed != kCompressBatchSize) { + ALOGE("Number of processed lines does not equal input lines."); + return false; + } + } + return true; +} + +} // namespace android \ No newline at end of file diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp index 79bf723ea8..6bc4bef806 100644 --- a/libs/jpegrecoverymap/tests/Android.bp +++ b/libs/jpegrecoverymap/tests/Android.bp @@ -30,4 +30,20 @@ cc_test { static_libs: [ "libjpegrecoverymap", ], +} + +cc_test { + name: "libjpegencoder_test", + test_suites: ["device-tests"], + srcs: [ + "jpegencoder_test.cpp", + ], + shared_libs: [ + "libjpeg", + "liblog", + ], + static_libs: [ + "libjpegencoder", + "libgtest", + ], } \ No newline at end of file diff --git a/libs/jpegrecoverymap/tests/data/minnie-318x240.yu12 b/libs/jpegrecoverymap/tests/data/minnie-318x240.yu12 new file mode 100644 index 0000000000..7b2fc71bc0 --- /dev/null +++ b/libs/jpegrecoverymap/tests/data/minnie-318x240.yu12 @@ -0,0 +1,1930 @@ +ØÖÖÓÑÑÏËÈÈÈÅÅÃÃÂÅÂÁÀÁÀ½¹¶³°«¬±´³²±´¯–‘ž°¯¯±´³³´µ¹»½½½»¸¸·¹»¼½¿ÁÃÆÇÉÌÎÏÐÎÍÌËÊÊÉÊÍÌËÊÆÇÈËÊÌÌÉÈÇÆÅÄÄÅÆÇÆÆÇÇÆÅÃÄÂÃÁÁÁÁÁ¿¾¼¼¼¼½»»¸·¹¸·¶µ³²²²²²±°¯¯®®®ª©¦£¤¥¤“}vrswqnpsqqklspmjmnlknw{’™›œ›š›››™›ššœœšœžŸ¡¢¤§«¯³µ¸ºº¼¼»º¹µ²­¥ž—”’”¦®´µ·¸¹º»»¼»¼¼½¾¾¾½¼º¹¸¹º»¼»½ÀÂÃÆÊËÊÊÍÍÌÌÉžºµ±­©©­´¶¶¶³²²³°¬©­²³¯©¢¡¨±´·µµµ³­›…„•¬¸º¹··³¨ ¦¯±£ÙÙ×ÕÒÑÏÎÉÇÇÈÅ¿¿ÁÄÂÀÀļ·´±­¬«®±²°­°§Ž›­®¯¯°²²²µ¹»½¼¼¼º¸¸¹»½¾¾ÁÅÇÆÉÍÏÐÏÎÎÌËÌÌËËËËÌÊÇÇÉÊËÌÌÌÊÉÈÈÆÇÇÇÈÇÇÆÆÆÆÆÆÄÃÃÃÂÂÁ¿½¼¼½½¾»¹¹¹¹¹·¶¶µ´µ´²±²±¯¯­­®­¨¤¢¥¤f96999:7464336753/01,..2344N“žœœššœœœœžž¡£¦«®°³µ¸»¼½½¼¼¹´®¨¡›•”–𤫳¶¶·¸º¼½½¾¼½½¾¾¾¿¿½»¹¹ººº»¼¾ÀÂÅÈËÌËÎÏÐÎÊÆÄ½¸µ±¬¨§¯·¶µ´²±²²°­©®²³®§¥¥«°³¶¶µ´¯¨“‚ˆ¡³¶¸···³¨¡§°´²ÚÚØÖÓÒÐÐÍÊÇÇŽ¾½¿ÁÁÁÃÄÁ»µ²¯®ª«°°¬¥£¬“¨­®¯°±°±µº½¼¼¼¼»¹¸¹º¼½¿ÁÅÈÉÌÎÐÐÎÍÍÊÊÌÍÍÌÌÌËÉÈÈÈÊËÌËËËÊÊÉÈÈÈÇÆÅÆÆÆÆÆÇÆÆÄÄÃÂÂÁ¿½¼½½½¾¼¹ºº¹¸·¶µµ´´µ´²±±¯¯­­­¬¬§¤ªr'Rouwwvzy{zxyzyxtsvurkgce_F U¢žœ›››œœ››Ÿ ¡£¨«°±³µ¸»½¿¾¾½º·°«£›–••˜ ¨®´¶¶¹»¼½½¾¾¾¾¾¾¾¾¿¿¼»º¹¹º»¼½¿ÁÄÇÊÍÍÍÑÒÏËÉÄÁ»·´±­§¨²·¶´³³³²²¯«©®²³­¨¤¦°³··¶µ³®£‰–®µµ¶¸¸¸²¤ §±¶·Ù×ÙØÔÑÐÍÎËÈÅÄÃÀ¿¼º¼½ÀÁÂĺµ²¯®­­®©™”ž£–£ª­¯°°±°²¹½½¼»¼º¹¸¹¹»¼¿ÁÄÈÊÌÎÑÑÏÍÌÊÊËÌÌÌËÌÊÉÉÈÈÊËËÊÊËÊÊÈÉÉÈÇÆÆÆÅÆÅÄÆÃÄÄÁÁÂÂÁ¿½»¼½¾½»¼»º¹·¶¶··´´µµ´²±°°®­¯®«ª¥¤@: ¬ª¬©ª¨§§©ª§©¨§©¦£¦¦£•¥£¤•;:—Ÿžœ™š››œ›œœŸ£¥§«®±´·»½¾¿À¿½»¸³¬§Ÿ™•”–ž¦­°³¶¹»½¾½¾½¾¾¾¿¿¿¿¿½¼»º»º»¼¼½ÁÂÇÊÊÌÏÑÑÐÏÌȾ¹´¯¯¬§«´¶¶´µ¶³²±¯ª¨­³²«¨¢©¶¹ºº·¶²¬™~‹¢°±²µ·¹¶®£ ¦±··Û×Ö×ÕÒÐÌÍÊÇÇÅÁ¿¾º¸·¹¼¿Á¿¹µ²¯®®°«—‰‘œš¡©ª­®²²°³º¼¼¼»»º¹¹¸¸¹¼¿ÀÄÇÈÌÎÐÑÏÎÍËÉÊËËÍÍËËÊÉÈÈËËÊÊËËÊÉÊËËÉÇÆÈÇÇÆÄÄÅÄÄÄÃÃÂÂÁ¿½½½¾¾½»º»»¹¹¸¸¹º·µ¸·¶¶³²³°°°°®­ª 5M¦¡¢¡¢¤¤¦¥£££¤¤¢  ¡ £}>Ž¡ž¥I1‘Ÿœš™™›ššš›œ›œ £¦«¯²´·º»¾¿¿¿¾½»µ®©£›˜–•›£©¯³¶·¹½¾¿¿¿¿½½¾¾¿¿À¿¾½»º¼»¼¼½¿ÁÃÇÊÌÎÏÑÑÐÎÊÆÁ¼¶±«ª¨¨­³µµ´´´´³²¯©§­±®«¦£­´¸º¹¶´°¤‰€’§«­°³¶·²­£ §±¸¹ÛÙ×ÓÑÓÏËÊÈÄÄÃÀÁ¿¼º¶´µ¸½ÀÃÿ¹´²°­¬«£ˆˆ”›Ÿ§ª«¬°±¯°º½½½½»»º¹·¸º¼ÀÀÃÆÈËÎÐÐÐÏÍÌÊËÌÌÍÌËËÊÉÇÈÊËÊÊÉÊÊËËÉÈÈÈÉÈÇÆÇÇÆÄÃÄÃÅÄÃÂÂÀ¾¾ÀÀÀ¿½»»¼¼»ºº¹¹¹¸¸¸¸¸µµµ³³³²°¯¬5T§¡£¢¤¥¦¦¦¦¥¥¥¦¤¢¢¡ ¢T1s¥Ÿ¢F)sš›—–————˜™››ž £¦ª¯³¶¸º½¿¿¿¾¾½»¹±­¦Ÿ™•–›£¨®³µ·¹»½¾À¿¾¾¾½½¾¿ÀÀ¾¾¼ºº»»½¾¿ÀÂÄÈÊÍÏÏÐÑÏÌÈÄ¿º´­©§¦§°µ´´³´³³³²¯¨©±±­¨¢¥°¶¸ºº·²¬ƒ‰˜¤¨¬¯²µ¶²­¤¤§²»ºÚØØÕÐÏÎÌËÉÃÁÀ¿¿À¾º·²°²¶»¿Â¼¸´²®¨©ª„Œ”ž©©««®°±°¸»¼½½»»»¹¹¸»½¿ÂÄÆÆÊÏÑÑÑÐÍËÍÎÎÎÌËÊÈÊÊÇÆÉÊÌÌËËÊÊÉÈÇÈÈÇÇÇÇÈÉÈÅÄÄÄÆÄÃÄÂÁÀ¿¿¿ÀÀ¾¾½½½¼»¼»¹¸¹º¹··¶µ´´µ´²²°­™2Z©¤¦¥¦¥¦¥¦¥¥¤¥¤¤¤£¢£‡HeP¡ ¡@&-Hh‰—–’”—ššœ ¤¦©­°µº»½¿¿ÀÀ¿¿¾»¶®©¡œ˜•™¢ª®²¶·ºº¼¾¾¾½½½½»½¿¿À¿¿¾»¼»º¼½¿¿ÂÄÆÊËÎÏÐÒÒÎËÇ»·°«¨¦¥¦­²³´´´´µ´²­¦©¯¯­¥ ¥·¹»»º·°§Š™¥©¬°²µµ´«¡¤©´½»ØÕÖÕÑÌÊÊÉÇÄÁ½»¾¿¿»¸³¯­¯´¹½Á¿»¶²­©§­£…ˆŽ—¥§©«¬®­®·¼¾»¹»¼»ººº»½¿ÂÄÆÇÍÑÑÓÒÏÍÌÌËÌÍÌËËËËÊÈÈÉÉËÌÍÌÊÊÉÈÈÇÆÄÅÅÆÉÉÇÅÄÄÆÅÂÂÃÃÁÀ¿¾¾ÀÀ¿¾½¾¾½»»º¹¸ºº¹¸¸·¶¶¶µµ´³±°–0b­¥¦¦§¥¥¦¦¥¥¤¥¤¤£££¨_gA‘¡š8$A,&6W†—›š›ž ¤§ª®²µ¹»½¿¿ÀÀÀ¾½¼·±©£ž™—™ž¦«¯²¶¸¼½¿¿½»»¼½»»½¿¿¿¿½½»»ººº»½ÀÃÆÉÌÍÐÑÑÒÒÏËÆÀ¹³­©¨¥¤¨¬°²³³´··¶±«¤¨­­©¢ž«¸½»¹·³® „ƒŸ¨«®¯°µ·²§Ÿ£«´»½ÐÌÎÐÒÎÊÇÆÆÅÁ¾¼¼¾¾½¹´°«¬®²·½¿½¶³®ª§¬°‰Œ” ¤¦¨ª©©ª¶¼¼º¹¹¹º»º»½¾ÁÂÅÈÊÍÐÐÒÑÎÌËËÌÌÍÍÌËËÊÈÇÆÆÈËËÊËÊÊÊÉÉÈÆÄÄÅÅÅÆÆÅÆÄÅÅÄÄÄÿ½½½¾¿ÀÁÀ¿½½½»»º¹·¹¹¹»º¹¹¸¶µ´³´³³•/l²§¦¦§§¨¨¨¨§¦¦¥¥¥¤¤—?S_8z¤”06‘}V5$.YšŸž¡¦ª¬®²µ¸¼¼¿ÁÁÂÁÀ¿½·³­§¢™™Ÿ¥¬°±´¸»½ÀÁÀ¾½¾¿¿¼½¿À¿¾¾¿½º¹¹¹¹»¾ÂÄÈÌÍÑÓÓÓÓÐÍÇž·±¬¨¦¤¥ª°²²²³µ··´¯¨£§««¤Ÿ¡±¹½»·µ±ª“‰•¢ª¬¬­²¶¶¯§ ¦­´»¼ÂÀÃÈÇËÊÇÅÄÁÁ¿½»»¼¼¹¶²­¬«¬±´º½º´°ª¦§±¨‡‹’¡£§©¨©«´»¼º»¼ºº¹º¼½¾ÁÂÆÆÊÎÑÑÑÐÎÊÊÍÏÍÌÎÍËÊÉÉÈÄÆÉËÌËËËÊÊÊÊÉÉÇÅÅÅÄÄÄÄÅÅÄÄÅÅÅÃÀ¾¼¼¼¾¿¿¾¿¿½½»¹¹¸·¸¹¹»ºº»¸·µ´³´³´”0xµª§¥¥§¨¨¨¦§§¥¤¦¦¤¥vXzvT^ ,C¢¤¡ˆ_;.2Mu•¥¥ª®²µ·»¼½ÁÂÂÃÂÀ¿»¶¯©£¡œš›£¨®²´¸»½¾ÀÀÀ¾½¾¾½¾¿ÁÂÁ¿À¿¾»º¹º»¾ÀÃÈÊÍÐÓÕÕÔÒÎÊÅÀ¹³®¨¦¥£§¬¯±±²²µ¶¶³®¨£§«¦Ÿ›¦µ»¼º·²­¢†ƒœ§­­­¯´µ´¯¤¡§­µ¼½¹µ³¼ÁÄÆÈÆÃÂÀ¾¾»ºººº¶³±¬©¨©®´¹¼¶±¬¨¥§±—‰™ ¢¦©¨©«³º¼½¼»»»ºº½½¾ÀÀÄÇÉÎÑÑÐÑÎÌËÌËÌÍÍËÊÉÉÈÉÆÆÈËÌÍÍËÊËÌËËÊÉÈÉÆÅÅÃÄÅÆÅÃÄÄÄÃÁ¿¾½¿¿À¿¿¿À½½¼ºº¸¸¹ºººº»»¸¶¶´´µµº“2‚¸®«¦¤¥¦¦¦¤¤£¡£§¥¤¤qЧ¦ŒW™ƒ'LŸ›¡¡ŽjI93Cjž­°µ¸»¼¾ÀÃÃÂÁÀ¿½¸³­¦¡ž››ž¦«°´¶º½¾¿À¿À¿½½¾½¾ÀÂÂÁ¿¾½¼º¹»¼¿ÂÄÇÌÎÐÓÕÔÔÓÏËǼµ°ª¥¢£¤©®²³´³µ¸¸¶³®¦¢¨ª¢™š«¸¼»¸´°ª˜€…“¢ª®®¯³²µµ¬¢¡¥¬¶¼¿¶´²±·¼¿ÅÆÄÂÁ¾»½¼º¹¸µ²²¯«§¦¨¬¯·¹´®¬§£«®Š˜ ¢¤¨ªªª³º¼¿¾¼»»»»¼½À¿ÀÁÅÈÍÏÐÐÐÍÌËÌÌËËËËÉÊÊÉÈÇÇÇÉËÍÍËÊÌËÊÌËÉÉÉÉÅÅÅÅÆÆÅÄÄÂÃÁÀÀ¿¿ÀÀÀÀ¿¼¾¿¿¿½¼ºº»ºººººº¸¶¶µµ´´¼ˆ3ˆº°­ª¦¦¦¥¦¤¢¡Ÿž£¤¤£ £Ÿ››ƒ$W šš› ¢žŠmR.T­¶¶»½¾ÁÃÄÃÂÁÀÀ»¶±«£Ÿœ›ž¤«¯³¶¸»½¿ÁÀÁÀÀ¾½¾¿ÀÂÂÃÂÀ¾¼¼¼»¾¿ÁÅÇÊÍÐÒÓÓÔÔÐÍÈÿ¹±«§¤¢¢¤¨¬°³¶¶¹¹·¶³¬¤£ª§¡™¡²·¹¹¸´°¦†ƒ‡˜¤«®¯³´µ¸´«¢£§­¸¾¿º²°¯±µº½ÂÃÃÁ¿»»»ºº¹¸³±¯¬§¥¤¥§¯³¶³¯«¦¨´ª‡”¢¥¦©«©ª±·»¾¾¾¼ºº¼»¾¿ÁÃÃÆÈÍÐÏÐÎÌËËÌÌËËÊÊÉÊÉÉÈÇÆÇÊÊÌÌÊÈÊÉÊËÌÊËÉÈÇÇÇÅÄÅÅÄÅÄÄÂÀÀ¿ÀÀÀÀÀ¿½½¿¿¾¿½º»»º¹¹¹¸¸¸··¸¶´²¼5Œ¹°®­«ª©§¥£¦x𣢤£¤£¡žœœž€ ^£œœ››i‡­¬¥X/˜º¹¼¿ÁÁÂÄÃÂÁÁ¾¹³®¨¢œ›¤ª¯²µ·º¼¾¿ÁÀÁÀÀ¼»¼¾¾ÀÁÀ¿¿½½½¼¼¾ÁÄÆÊËÎÐÒÔÔÔÑÎÊÆÂ¾·¯©£¢¤¥¦ª®°´·¸¸¹¸µ°«¥£¨¦œ™©´·¹¹·³ªš€‡œ¨¬­°´´¶·²¨ ¤©¯ºÀÀÁ´¯°°±µ»¾¿ÀÀ½»¹¸¹»º·¶³°«¨¤ ž ¤«²·µ­¨¦¬¸™Œž¢¥©«ª¨®¸»¼¾¾¼¹»»º»½ÀÃÅÇÊÍÎÍÎÏÍÊÉËÍÊÌÌÉÉÊÊÊÇÇÇÈÊËËËÊÈÉÉËËÌÍËÊÉÉÇÅÄÃÅÄÃÄÄÄ¿¾¿¿À¾¾¿¿¿¾¾¾¾½»¹ºº¸¹¸¸¹¸¶¶¶¶¶µ±¹s2’·°¯­¬«©¦¤¦—G#L—¥£¤¤¤¡Ÿžœt g¤›œœŸi1t¯¨³o0”¼º½¿ÀÁÁÁÁÁÀ¿º´°ª¦ œ¢©®±´·¹»¿¿ÀÁÀÁÁ¿»º»¼¼½¾¿¾½½¾¾¼»½ÁÄÆÊÍÐÓÓÕÖÒÎËÇþº²ª¥¢£¦¦¨¬°²¶¸¹¹¹¶´®ª¢ ¤ —°µ¹¹·¶°§Ž‡”¢«­­°³¶·¶°¤Ÿ¤¨°»ÁÁɺ±°±°²µº¼¾¿¿¼¸··¹¸¸¶µ²¯©¥žšš¡§±¸µ¬¨§¯°‘›¢¥§ª«¦¬¸¼¿¿¾½»»¹º»¼¾ÁÄÇÊÌÍÍÏÏËÉÈÈÊËËËÊÉËÊÉÈÆÈÉÉÊÌËÉÉÊÉÊËËËÊÊÊÊÈÆÆÄÄÄÄÄľ¾À¿¿¿¿ÀÀ¿¾¼¾¾¼ººº¹¹¹¶¶¹¹·µµ¶µ³±¸j.—´°¯®­«¨¥¥›B!)#JŸ¤¥¥£ žŸž n"r¤£zOXw±¬²Z;¬½»½¿ÀÂÁÁÂÁ¿»·²¬§¢Ÿœž¤¬¯³··º½ÀÀÀÀÁÁÁ¼»¼¼»½½½½½½½½½¼¼¼ÀÄÇÊÍÑÔÔÕÓÏÊÉÄÀ¼µ®¥¡¡£¦§©«¯²µ¹º»¸¶³¬¥ Ÿ œ™¨¶¹¹¶´±¬‚‚Œ™¥©¬®²¶·¹´­£ £«´¾À¿Ïµ²±°±´·¹º¾¿½¹¸·µ´¶¶µ³²®¨¡™—˜š£¯¹¹¯¬­¶¢•££¦ªª¨¬¸¼½¾½¼½º¹»¼½¾ÁÄÇËÌÎÐÑÏÌÉÊÉÊËÊËËËÉÈÉÈÇÈÉÊËÍÌÉÊÊÉÊÉÉÉÈÈÉÉÊÉÇÅÄÃÄÄÃÁ¿¾¾¾¿ÀÁÀ¿À¿¿¾¾¼»»»ºº¹¹·¸»»·¶¶µ³²±¶b/𴝱°°­©§¥M'()%dª¤¤£ ¡¡Ÿ¢j"}¥ž¤‹EŒlwµ²¥?`½½¿ÀÂÃÂÁÀ¿¼¸´­©¦ žž¢¨¬²µ¸¹»¾ÁÂÁÀÀÀ¾»»½¾¼½½½½¼¼¼»»º½½ÀÃÈÌÎÒÓÔÓÐÍÈÅÿ¹±¬¥¡ £¥¦©¬°´¶¹º¹¹¶´¯¨Ÿœœ˜°¹»¹·´¯¥Œ‚ˆ‘ž¤©¬¯´¹¸¸´©¢Ÿ¤®·¾Á¾ÏȺµ´²´µµµ¸¹»»º¸¶µ´³³µ²³°«¦ ™–““›¡¨µ»·³¶¶› ¢¥¨©¦ªµº¼¾½»»º¹º½¿ÂÃÅÉÍÎÎÐÐÎÌÊÊÊÉÉËÌÍÍËÉÊÈÈÈÇÉËÌËËËËÌËÊÉÈÈÉÊÈÈÉÇÆÃÁÁÂÁÁ¾½¾¾¿ÀÁÁÁÁÀÀ¾½¼»¹¸¸¹¹¹º»ºº·µ¶µ³³³·^4³®°°°¯ª¬u%&&'&/•¥£¤££ Ÿ¥e'…¥¦•IEzXy´·|5•ļ¿ÀÂÃÃÃÂÀ¾º´®©¤¡ŸŸ¢§¬²¶¸»¼½ÀÃÄÂÀÀ¿½º»½¾¾¿¾½½¼¼¼¼»»½¿¿ÃÊÎÐÓÓÒÐÍÈÅÃÀº³®¨¥¡¡£¤¥©¬±¸º»»¹¹¶³­¥›š™£³º»¹·³«œ‚‚‹–¢¦¨­±¶·¹¸±¤¢Ÿ¥°¹¿Á¿ÐÍÀ·¶¶¶´µ¶¶·¸¹··µ´µ³²²³´³°«¥Ž’–›¥³½¿¾¹¦› £¤§§©´¹»¼¾¼»¼¼»¼¿ÃÄÅÊÍÎÏÐÐÏÍËËÊÉÉËÍÎÍËÊÊÈÈÈÈÇÈÉÊÌÌËÌËËËÊÈÉÉÈÇÇÇÅÄÂÂÁÁÀ¾¾¾ÀÁÀÀ¿¿ÀÀÁÀÀ¿¼¹¹ººººº»º¸¶´¶µ´´´¸[9¡±®¯¯°®«¥B%)'&'++y¬¤¥¤£¢Ÿ¥^-§¦dm…g@}¸µPJ¹ÁÀÁÂÃÃÃÄÂÁ½µ°ª¦¢¡Ÿ¡¥ª®´·º¼¾ÀÃÄÄÃÁÁ¿¼»¼¾¿¿ÀÀ¾½½¾½½½»»½ÁÅËÏÑÔÔÑÏÊÆÃ¿»´°«¥¢¡¢¤§§¨«²·ºººº¸µ²«£›™—™©¶¹º¸³¬¦“…™¤§ª¯³¶¸º·¯¦¡¢©³º¿ÀÀÑÏĸ··¸·¶µ´´¶¶µ³³³²³³²²²´³¯¦¢”‰‰Š‘•ž­½Æ¿«˜ž¡£¥¤§²·º»½¾¼½½¼½¿ÂÅÇÉÌÍÏÐÑÎËËÊÉÊÊËÌÌÊÊÊÊÊËÊÊÉÈÉËÍÍËËÌËÌËÉÈÉÊÈÈÇÅÄÂÂÁÁÁÀ¿¿ÂÃÂÂÁÁÁ¿ÁÁÁ¿»¹º¼»»º¹¹·µ´¶·¶´´´¶X>£¯®®®¯¯­Ÿ9,+)))0.v¬¦§§¥£¢§Y2–¨¢—®±µq‚½Ÿ8}ÇÂÁÂÃÄÄÄÃÁ½¸±­©¦¢ Ÿ¥©¬°³·¹»¿ÃÃÅÃÂÀ¾½»»½¾¿ÁÀ¿¾¼¾¿¿½½¼¾¿ÄÉÍÏÒÓÒÐÎÈÄÁ¼¶±¬§£ ¡¤¦§§©ª²·¸º¼¹·´¯ª¢™–“ž±ºº¹µ±«œ„‚‡’ž¦§¬²¶¶¸¸µ¬¤œ£­µ¼ÀÀ¾ÒÒʺ¹¸»¹·´³³³²²¯¯±³³´³±°±³²¬§ –Іˆ‰“•Ÿ­´«—™ ££¢¥¯¶¹»½½½½¾¿ÀÀÁÅÈÉÊÌÎÎÏÌËÊËÊËËËÊÊÈÉÈÉÊËÌÊÈÇÉËÍÎÍËÌËËÊÉÇÇÉÉÈÇÅÅÅÃÁÁÂÀ¾¿ÂÄÃÃÂÁÀÁÁÁÁ¼»ºº»½»¹¸¸µµ···¶´µ¶µQB©¯­«¬­­¬ªZ04*+57F—§¦§¦¥¥¤§T6­ª®­­³”›½lA®ÃÀÀÁÂÃÃÂÁ¾¹´­¨¤¢ Ÿ¢¦«¯²´¶¹¼¿ÂÄÄÃÀ¿¼¼»¼½½¾¾½½¼»¼½¿½¼½ÀÄÇÌÏÑÑÐÏÎËÈþ¹³®©¥¡ ¢¤¦¦§ª­³·º»º¸¶²®¨¡™“•§¶»»¹³­¨’€†‹š¢§«°µ··¹¸²§š¢°¹¾¿À½ÏÒξ¸º»º¶µ´³²³±­¬®±±³²°¯®®±¯¬©Ÿ‘†„‡ŠŒ•›—–ž££¢£­¶º»¾¼¼¼¼¿ÀÀÂÄÈÊËËÍÎÎÌÉÇÉÊÉÊÉÊÊÉÉÊÊÉÊÌÌÈÈÉÊÌËÍÍËÊËÊÈÈÈÇÉÈÆÅÅÄÂÂÁÂÁ¿ÀÀÃÃÃÂÁÁÀ¿¾¿¿¿¾½½½¼ºº¹··¸¹¹¸¶¶µ²LI®®¬¬­­­ª®œme=1bm¥£¦©¨¦¤¤¦O<¤¯¯­¯±²³·ªAjÆ¿ÀÀÁÂÂÂÁ¿¼·±«§¢¡ŸŸ¡§­°³¶¸º½ÀÁÂÂÀ½½»ºº¼¾¾½»º¹ºº»»¼½½¾ÂÅÉÌÏÑÒÏÍÍÊǽ¶¯ª¦¢ ¡¢¤¨¨¨«°µ¹º»º¸µ±¬¦ ˜“¯¸¸¸µ±©Ÿ…‚Š–Ÿ¦©­±¶··¸µ©Ÿ˜ž¦°·¾Á¾ÏÐÏź¼»ºº·¶³²²°«©ª­®®¯®­«¬®°®¬§‹‚ƒ…„‡”–‘œž¡ ¡¨³¸»¼»¼½½¿¿¿ÁÅÇËÍÎÎÍÍÊÊÈÉÉÊËÊÉÉÊËÊÊÊÊÌÌÊÉÉÊËËÌÍÌÊÊÊÉÈÈÈÉÈÆÃÂÁÁÁÁÁÀÀÁÀÂÁÁÁÀÁÁ¿¾¿ÀÁ¿½½½½»»»º¹·¹¹¹·µ²¯FO°®­®°¯¯­¬°³¤H7–­£ ¥¥§§¥£¦ªLB«±±°±²³µ½‡:›Ä¾ÂÃÂÃÄþºµ°ª¦¢¡ ¢¦«°³¶·º¼¿ÀÀÁ¿½¼¼¹·¹»»º»¸·¶¸¹º»»¼½¿ÄÇÊÌÎÐÑÐÍËÈÄÀ¸±­¨£ŸŸ¡¢¥¨§¨¬´¸ºº¼»¸µ±¬¦ž””¦µ¹·µ²¬¤“‚…–¡¨¬¯µ···µ®¤œš ¨®¹¾Â¼ÎÒÐȼ¼»ºº¹·¶µ³®©¤¢¤©«««ªªª­¯¯­ª£—‡€‚ƒƒ‡Œ‘Œ”›œž¨²·»½¼»»½¿¿ÀÂÅÇËÎÐÐÏÍËËËËÊÊÊÉÉÉÉËÊÉÈÊÍÍËÉÊÊÉÊÌÍÌÌÉÉÉÈÈÇÈÉÅÃÂÁÁÁÁÀ¿¿¿À¿¿¿¿ÀÁÁÀ¿¿¿¾½¼½½½¼¼¼»¼·µµ´³²±§;Q²¬­®¯­¬¬­¬®ˆ>8}§ £¤¦¨¥¥§ª®IC¯²²±²´¶¸¸XV½À¿ÂÂÃÃÅÂÀ¼µ°­¨¥¢¢¡¤ª­±´·¹¼¾¿¿ÀÀ¿¾¾»ººº»ºº¹¸¹··¹ºº¹»»¾ÂÆÊÍÏÑÐÐÍÉÄÁ»µ±«§¡žž ¡£¦§¨¬³¶¹¹»¹¸¶²«¤ž–¬¶¸¶³°¨ž‡…†š ¦¬´¹¹·¶³¬ ššŸ©±º¿À¿¼ÏÏÏÊ¿»¼»¹¸¹·µ²­© Ÿ £§©¨¨¨«¬®°±¬§ ’‚}|€„‰ˆ™™™›§²·»½½½¼½¿ÀÂÃÄÇÈÊÍÑÏÌÌÌÌÌÍÍÌÊÉÉÊÉÉÉÆÈÊËËÈÊÊÊËËÍÌÌËÊÉÈÈÈÉÈÆÄÃÂÂÁÂÁÀ¿¿¾½¾¾¿¿ÁÁÀ¿¿¿¾½½½¿¿¿À¾½¼¸·¶´²±±¥7W¶®¯°±¯­­®®¯—|z¦¢£¥¦§¥¨«®¯GJ³²³³µ¶¶¼›;†ÈÀÂÃÅÄÃÿ»²¬¨¥¤£¢¤©¬¯´·¹º½¾¾¿À¿¾¿¿¼¼»¼¼¼º¹¸¹¹¹º»¼ºº½¿ÃÇÊÍÐÑÑÎËÅÁ½µ°­¨¥ Ÿ ¢¢¢¤¦©®´¶¹ººº¸µ°©£›¡´¸¹µ±­¥‘„Д𠦱·¸¹¸¶±¦ž››¡«³¼¿¿¾¼ÎÍÍ˺¼¼¼¸¶µµ²¬¨¢  Ÿ ¡¡ ¤¨«®°®¬© {|~€†Œˆ‰–™–—¢°¶¹¼¼¼¼¼½¿ÁÃÄÆÈÊËÍÏÍËÌÌÌÍËËËÈÈÈÈÇÈÇÉÊÉÉÉÊÉÉÊÌÍËÊËÊÈÉÊÊÊÉÈÄÄÃÃÃÃÂÀÀÀÁÀÁÁ¿¾ÀÀÁÀ¾ÀÁÀ¿¿¾¾¿À¾¾¼¹¹¸µ´´µ¤5c¹®°±²¯­®°°°²µ²ª¥¥¦§©¨§©¬±°EP¶²²³¶¶·¿oE³ÅÂÃÄÆÆÅľ·¯ª¥£¡£¥¦«°²µ·¹¼¾¿ÀÁ¿¾¿¾¾»»»»»»¹¸¹º¹º½¾½¾ÀÂÅÇÈËÎÑÒÏÌÉÄ¿º±¬©¦¤ ŸŸ¡¢¢£¤©¯³·¸¸¸·µ³­§¡™˜«´¶´²®¦œƒŒ—¤«·¸¹·¸´ª¢™šž¥®¹¾¿¾¾¹ÑÐÒ̹¼¼¼¹·µ´°«©¥¡¡—•——›ž£¦ª­®°±¬¢‹~zz|}€ˆ‡…“”““Ÿ®µ¹º»½½¼»¼¿ÁÂÅÈÊÌÍÍÌËÌÌÌÌËÌËÈÇÈÇÆÈÇÉÊÊÊÊÉÈÉÈÉÊÊÉÉÉÉÉÉÊÊÉÈÇÄÄÄÄÃÂÀ¿ÀÁÂÀ¿¿¾ÀÀ¿¾¿¿ÀÀÁ¿½¾¿¾½¾»¹¹¸¶´µ¶ 6p¼ªª³²¯¬­°°°²¯­©§¨©©©¨¦§«²¯AWº³²´µµº²DlÇÁÂÃÅÇÆÅÃÀ»´¯ª¦¥¤¤§©­±´¶¸¹¼¾¿ÀÀ¿¾¿¾»¸¹º»»ººº»¼¼¼¿¿¿ÂÆÇÈÉÉÌÐÔÒÏÌÇÁ»µ°«§£¡Ÿž   ¢¢¦«°³µ·¸¸µ´²­§ šž®²²±±©¢{‚„Œ”¦²·¸¸¸¶±§žšœŸ¨µ½¿¿½»¸×ÔÓÏȼ¼»··¶´²°¬©¥¢¡œ”Ž”™œ¡£¦©¯°¯® ‹yyy{|~„’ž¬±µ¹º½½¼¼¾¿ÀÂÅÈÊÏÏÎÍËËËËÌÌÍËÉÈÇÆÆÆÈÉÉÉÉÉÉÊÉÈÉËÌËÊÉÉÉÉÉÊÉÇÇÆÇÆÅÅÃÁÀ¿ÁÀÀ¿¾¿ÁÁÀÀÀÀÀ¿À¿¿ÀÀ¿¿¿º¹¸¸·¶¶¹š2yÁ™m¹³²}£±¯°°°®«¬«¬ª©¨§ª¯³­<^¸²²µ¶¶Â8¢ÈÄÄÆÇÇÅÃÁ½¶°¬©§§§§«¯²¶¹ºº¼¿ÀÀÀ¾¾½¾¼»¹º»»»»¼¼½¾¾¿ÂÁÂÅÇÈÊÌÍÐÑÒÐËǽ·±®«¨¤¡ŸŸ Ÿ ¢¤§¬°³´··¶´²°¬§¡œ¥®°°°¬¥™…€ƒ…Ž˜¢®¶¹¸¸ºµ¬¤œœ¡ª¸¿¿À¿»¶ÚÖÓÐʽº¼ºµ´²°®­©¦£¢œ•Љ‹‹“•–ž £§©¬ª§œ†{yz{z~|‡Žª¯´¹¹¼¼½¾¾ÀÁÁÅÊÍÐÒÑÎÌËËÊÊÊÉÉÇÇÆÅÆÇÇÉÈÉÉÈÉÊÉÉÊËÌËÊÉÉÈÈÉÉÈÇÆÆÆÆÆÅÂÁÁÀÀ¿¿À¿ÀÁÀÀÀ¿ÁÀÀÀ¿¿¿ÀÂÁ¿»º¹º¹¸¸¼”.|Á²Ro‚wc²±±²²°®¯¯®®«¬©§®³¶ª;c¶±³·¹½Á]ZÈÅÆÇÈÇÆÂÀ¾º³­©©¨§§«®²¶·¼»¼¿ÁÀÀ¿¾¾½¼»»»»¼¼¼¼¼½¾½¾ÂÄÃÃÅÇÉËÌÎÎÏÏÌÇÁ½·±®«©§¢ž ¥¥¨­±´´´¶µ³²¯«¨¡¦­®®¬¦¢€„†Œ•Ÿ©²¸¹¹¹·¯¥Ÿœ›œ¤®¹¾ÀÀÀ»´ÜÙÓÎÈÀ¹º¹·´±¯­«©¦¤¤ ˜‹ˆ‡ŠŽ“——›Ÿ¢¡¤¢•}yyyxywŠŒŽ™©¯´·¸¹º½¿ÀÂÂÂÆÉÍÐÑÑÏÎÌËÌÊÉÉÈÇÆÆÇÇÈÇÉÈÉÉÊÊÊÉÉÊÌÌËÊÊÊÉÉÉÉÈÇÅÅÅÆÆÅÂÁÂÂÁÁÁÁÂÂÂÁÀÀÂÂÁ¿¿¿ÀÁÁÁÁ¿»»»»ºº¸½“.ƒ¿¼iPsBй²²³²±°±±¯­««©«±³µ¦7f¸²¶¹¼Ä¨A“ÌÄÆÆÆÅÄÁ¾º¶°©§§¨©ª¯±´µ·»»¼¾¾¿Á¿¾¾¼»ºº¹»¼¼¼»º»¼½¾ÁÃÃÄÆÇÈÊÊÌÍÎÎËÆÀ»µ°¬©§£ œ›œž¢¥¨¬¯²³³³´³³±­«¦¡Ÿ§«¬­©£™…€ƒŠ‘›¦¯µ¹¹¸·°¨¡œš›ž§³»ÀÀ¿¾»´àÝÖÍÆÀ¹¶µµ³¯­«ª§¥¤¥¢˜††…ˆ‰ˆŽ’˜—Œ|yyxvsz†ˆ–§®±µ¶¸¼½¾ÁÃÄÅÇÊÏÒÐÏÏÎÍÎÎÌËÌÌËÊÇÇÉÉÈÈÇÇÉÉÉÉÈÇÉÊËÊÊÊÉÉÉÉÉÈÅÅÆÅÄÄÄÁÁÁÁÁÁÁÂÂÃÃÂÂÃÃÃÂÁ¿¿¿À¿¿½¼»¼¼¼»»¸¾Ž/ˆ»¸dž_ª³³²±¯°±²³²¯­««®²³µ¡2h¸²·½¾ÄpRÀÈÅÃÅÆÃÁ¾¾º³®©¦§¨ª­±²µ¶·º½¾¾½¼½¼¼¼ºº¹¹¹º»º¹º»º½¿ÀÂÃÄÅÆÉÊÊËËÍÍÊÇý·±¯¬¨¥¢ž›››œŸ£¦ª®°±±²²²±²±®«¦Ÿ §©ª«¥Ÿ‚…Œ”¡ª²·¹¸¸³¬§Ÿœ›œ¡ª·½À¿½¼¹±ÚÙÔËĽ¸¶¶´²°®¬«¦¢ ¤¤ š“‰„ƒ…ˆˆ‡‡‡‚€~‡‹Ž„{yxrlxƒ„Š”¦¬­°´º¼¼¾ÀÂÃÆÉÌÏÒÒÐÏÏÏÎÎÍÍÍÌÌÊÈÇÆÇÈÈÇÆÇÉÉÈÇÇÈÈÈÉÊÊÉÈÉÈÉÈÇÆÆÆÄÄÃÀÀÁÁÁÁÁÂÂÃÄÃÄÄÅÄÄÂÀ¿¿ÀÀ¼»»»»»»¹¹·»Š.ޏµªQa~·±²²°®¯°²³³²°°¯²´´±˜0l¸´º¾Â°EˆÍÄÅÃÅÆÃÀ¾»·²­ª©©ª¬¯²µ·¹»¼½½¾¾¼»»º»»º»¹º¼¼½»»½¼¾ÁÃÄÅÆÇÈÊËÌËÌÌÊÇÿ¸³­©§¦¤ šš›œ¡¥¦ª®¯°²³²±¯°¯­©§Ÿ §¨¨¨¡˜…ƒ‰’œ¤®¶¸·¸¶°«¥ž››ž¦°¸¾ÁÁ½¼·¯ÉÉž»¸¶·´±¯­­ª¦¡¡¢¢¡œ–Œƒ€ƒ…††„€{yxwx~€|vvtlv„‚†”¤««°µº¼½¾¿ÀÂÆËÏÐÒÓÑÏÎÏÎÎÎÍÌÊÉÉÈÈÆÆÇÇÇÈÉÉÉÈÇÈÉÊÉÉÊÈÈÈÇÈÇÆÆÆÆÆÄÅÁÀÀÁÁÁÁÁÃÃÃÃÃÃÅÄÅÄÁ¿¿½¾¾»»»»»»º¹º·¼ˆ-¹¯µ_@¨µ³²²°¯±±²³³³³´µµµ²²+r¸¸¾¾ÇzL¸ÅÃÄÅÅÄÿ¼¸µ²®ª«¬¬¯²µ¹º¼½½¿¿¿¿¿¾¾¼º»½½¼¾¿¿¿½½½¾¿ÂÄÆÇÈÉÊËÌËËËËÉÅÁ¼µ¯©¨¤¢Ÿ›˜™ž¡£¥¨¬¯±±±²°¯­­«©¤ž ¤§§¤Œ‡Œ˜¡«µ¸···´°© š™› ©²º¾¿¿¿¼´ª¼»¹¹¹¼»µµ²°®¬¬ª¦¢¢ žš•†€€ƒ…ƒ€{wtstwvwvtrojq‚ƒ‚¡ªª®´·»¾ÀÀ¿ÂÅÊÎÐÑÑÎÏÎÍÍÍÌÌÌËÉÈÇÆÇÅÆÈÈÉËÊÊÉÇÈÊËÌÊËËÊÉÇÆÅÅÅÅÅÅÅÄÁ¿¾ÀÁÁÁÂÂÁÁÁÁÂÀÁÂÁ¿¾¾½¼½½¼»¹¹»º¹¸¶¹‚-й«­†t³®­®°¯¯°¯±²³³²´¶´³²´Š,~À»¿ÀµItÇÁÅÆÇÆÃÀ¼º¹´°¬ªª«­±´¸¹¼¿¿¿ÁÂÁÀ¿ÁÀ¿½½¿¾¿ÁÀÀ¿¾¾¾ÀÂÅÅÇÉÊËËÌÌËÉÈÇÆÄ¾¹±«©¥¢Ÿœš˜šŸ¡¤¦¦©¬®®¯±±°°®¬¬«£ž¢¦¤ –†€ƒ‰“œ¦°µ¸µ·¶³ª¢œšš¥­·¼¿¿¿½º±¥³±³³µ·º·°°¯­««©¦¡ Ÿœš˜—”Œ€‚}ytrrrrqoqpmifm‚‚‹Ÿ¬©«±·¼¾¿ÀÁÄÅÈËÎÐÏÏÎÌËÌËÊÊËÉÆÅÆÇÆÆÇÈÈÈÊÊÉÈÈÈÉÊËÌËËÊÊÈÆÇÇÆÅÅÅÇÅÂÀ½¾ÁÂÁÂÂÁÀÂÁÁÀÁÁ¿¾¼½½¼½½»º¹¹»¹¸¶³´’.W§«©¬±°®­­°°®¯±²³´²±±±³²²¼‚2–Á»½Å–:£ÈÅÆÅÅÅþ»¹¸´°­ª©ª­±µ¹¼¾ÁÁÂÃÂÃÄÄÄÄÃÃÃÁÀÁÂÁÀ¾¿ÀÁÄÅÇÆÇÈÊÌËËËÊÇÆÄ¿¹²¬¨§¤¡š˜™›œŸ¢£§ª­®®®¯²²°°®««§£››Ÿ¢¡™‚‡™¡¬´¶¶¸¶³­¤žœ›ž¨²¹½ÀÀ¿¼·®¤®®¯°²µ¸·±­­«ª©©¥Ÿœžœ˜““„}€}vtrppnkkmkihch{€ƒŠ›«««¯´¹¼¿ÀÃÅÇÊËÍÏÐÏÌÍÌÊÉÉÈÉÈÅÅÅÄÅÇÈÈÇÈÊËÉÇÇÈÉÉÊËÌËÊÉÇÉÈÈÈÇÅÆÆÅÂÁ¿¿ÁÂÂÁÁÁÁÁÂÃÁÁ¾½½¼½½¾¼»ºº¸º»¹¸¶´²¯T'BWcltz‡”› ¥§©©®¯°¯¬¯®® RI³À½½ÆmLÀÅÇÆÅÄÿ¼º·µ²®«ª­¯²¶»¾ÁÄÄÅÇÅÅÇÇÆÇÇÆÅÃÁÂÅÃÁÁÂÂÄÅÅÆÆÈÇÉÊËÊÉÈÅÅÁ¾º´®¨¥¦¢Ÿš™˜šœ £¤¨¬®¯°¯®°±°°®¬©§¢™œ ¡›ƒ…‡’¦²·¶·¶³®¦¡ž››¢¬´»¾ÀÀ¾º´©¢®®®®¯²µ¸´­«ªª¨¥ ™š››˜“‡€}~~|zxuromkjihggeaev~‚Š›¥§ª­±µº¿ÀÃÅÉÌÎÐÑÑÎÌÎÍËÊÉÉÈÇÅÄÄÄÄÆÇÉÈÆÉÈÆÆÇÈËËËÊËÌËÊÈÊÉÉÉÈÇÇÇÇÃÂÁÁÂÂÂÂÁÀÀÂÂÂÀ¿¼½½½¾¿¿¾ººº·ººº»¹µ²µ¦eD;7630*0348;X‚Ÿ®¶¸··¸º¾ÃÇÂÂÃÂÅdZÇÇÆÆÅÿ¼º¸³¯¯¬ª«°´¸ÀÆÈÉÊÊÊÊÊÉÈÇÉÉÊÊÈÆÅÅÅÅÄÄÅÇÇÇÉÉÊÊÊÊÊÉÇÅÃÀ¾º·³¯«¦¤£¡œšššž ¡¢¤¨­®­¬«®±²±®­«©§¥Ÿ•˜˜—‡}€„Œ•£®´µµ·µ­¦£ œ™› «³¹¾¿¾½¼¹²ª§¢¹·³¯­¬®®±·®¢Ÿš˜––”’‘’“‘†|{{zurplkifde`XQVlw}„‘ ¦§§©¬²¸¿ÆÊÍÐÐÐÎÌËËÌÊÈÆÄÃÂÀÁÁÀÀÂÃÄÅÇÇÆÇÆÆÇÉÊÉÉÊÊÉÉÈÉÊÊÌÌËÊÊÉÈÅÃÄÅÅÆÅÆÅÄÂÁ¿¼½¼ºº»»¼¿¿½ººººº¹¹¹··¶·µ³´³²³³³´¶·®•nO::GdЧ¶¸¹¹º»½ÀÂÂÆ¯EŠÏÆÆÆÄÁ¿¿¼¹·²¯­««®±¶»ÂÆÈÉÉÉÉÉÊËÉÊËÊËÉÇÅÇÅÆÅÄÆÇÈÇÇÇÈÉÊÊÉÉÇÆÃÁ¿¼¹¶²®«¦¡Ÿ›™˜˜šœŸ£¤£¤«¯¯¬«¬¯°²±­¬ª©¨£›“˜—’}…ˆ‘œ¨³¶³·¶²¬¨£žš˜ž¦°¶»¿À¾¼»·±¬§¥»º·²¯°®¬«°¶§ ›š—”•”‘Œ‘’“Žz{zxuromlgdaa]XRPgv{ŽŸ¤¥¤¤¦¬°¸ÀÇÍÏÍÍËÉÈÈÇÆÆÃÀÀÀÁÀÁ¿ÀÁÁÃÄÅÅÆÇÆÈÈÊÈÇÇÈÊÉÊÉÈÉÉËÌÊÊÌËÉÅÄÆÆÆÆÅÅÄÂÂÁ¾¼¼»¼»º»»½½¼»¼º¹»¹¹¸¶µ¶µµµµ´´´´´µ¶¶¸»¼¯—wP;2;X{˜¯¼ÃÂÂÁ¾É{H¶ËÈÇÆÄÃÀ½º¶²²±¯®®³µºÀÄÇÈÉÊÉÈÈÉÉÉÊÊÊÊÈÆÅÆÇÇÇÈÇÉÈÇÆÆÈÉÉÉÈÇÆÄÂÀ½¸¶µ±®ª¥¡˜–——šœž¡¥¤§«­¬©«®°±±¯«««ª¨¢˜“•”…|‚†˜¤­´³µ¸¶°¨¤š™›¤¬³¹¾ÀÀ½»ºµ¯«¥£»¹ººµ°¬©§§¯²¢›–““’’ŒŒ‹Ž‘“‘Œƒ|xwuroojfca`\XQK^sx}ŠŸ¡¤£¢¤¤¥¬²¹ÁÅÅÄÃÀÀÀÁÁÁÀ¿½½¿¿¿¾¾¿ÀÁÂÂÀÃÄÅÈÈÈÇÇÆÇÈÈÉÉÉÈÉËÊÊÊËÌËÈÆÆÈÈÆÄÅÄÃÂÁ¿¾¾½½½»º»½¾¼¼½»¹¹¹·µ´³µµµµµ¶µ´µ¶¶··¸··º¿À· bJ:?:–ÖÌÍÍÍÌÍÊÊÒºl>VŸ¢†IFHLMKMLOT]ds‹‹‘—š›Ÿ¢¥§ª¬¯±´¶¸¹··¸´¯¬¨¥£¡ žœšššš›››œœžž¡£¤¦¦¦¨©©¨§§¦¦¥¥¥¤¢Ÿ›’|{¡ª©ª««¬¬«ª««­­­­¯¯¯°²·¹¸·³­¨¥¡¡¢¦©©©«™Š†„}z{}~€ztqppqrrsvuxxz~€‚‚ˆ•˜›œž  žŸ™‚s_RQaq~Œ”˜›—’“•––˜™ž£¤¤¥¥££¥¥¦¥§«­°²µ¸¹»»»¼¿¿ÁÃÄÅÆÆÇÇÇÈÌÏÒÒÑÐÏÎÏÏÐÐÏц;C>>>¦ÐËËËËÊÊÎÊ•E-:<=9QžÎÈÁÆi'..4>IVfs”˜››’‹‰¹ÄÃÉ—/*'&(&'$"!!6›š™¡a!9q•›œž £¥§«­®²µ·¹»º¹¸µ°«¨¦£¡ŸŸ›››œœœ›žŸ¡£¤¥¥¥¦¥¦©§¥¥¦¥¤£¤ Ÿœ˜ƒ™©¨©ª¬«ª«ªªª©©«««ª¬­­°³³²¯«©¦£ŸŸ¢¥¦¨©©•‹‡ƒ|xxz{}~~ytpopppqrsttvwwx{}}~‚ˆ††‡ˆ‰€vfVO[n‡’–—™–‘ŽŽ””••”–šžžŸŸž ¡¢¢¢£¦¨ª­°²¶¸¸¹º¼¾ÁÂÄÅÅÆÇÆÆÈËÏÑÑÐÑÏÏÏÏÏÎÍÐw:DA?@­ÎÊÊÊÉÌÒ¯c.3<=?:h¸ÑÉÄÀÄc243310.--/1/1552215¨ÇÂɈ140.,*(''&#""!!  9˜™–›Z " EŽª£§©«®¯±²³·¸¸¹¸·µ°­¨¥£¡¡Ÿš™šš›œš›œœŸ¡¤¤¥§¨¨§§¦¥¥£££¤¦¥¥¥¡Ÿš•€’¦¥§©ª«ª¨¨§¨¦¤¦§§¦¦¥¨ª­­¬ª¨©¨£žœœŸ£¦¨©ªš—†€~|ywxyy{}{uqmlmmmnooppopqsrsvvrutqqqrrnnj_UKTl‡””•––’ŽŽ‘’’’”——™™›  ¡£¥¥¦¨©¬­¯³µ·º¼¿ÁÅÅÄÅÆÈÉÊÍÐÑÑÏÐÎÎÏÎÎÌËËk;B@=C´ÌÈÊËÑË„80:=>9B…ÈÑÇÆÃÀÆ^43225689:9877778:<@®ÄÂÊ~.1/+*)'%$#!"! "! @™˜™N!#$%-†¯ª­°±³µ´µ¶¹¹¹¸¶²­©¦¢¡¡žœšš›š››™—˜™œ ¢¥§¨©¨§§§§¦¦¤¢£¤¥¤¤¤ ™˜¡¤¦§©©¦¡ ž™––™ ¡¢£¢£¤¦¨§§§§¨¦¡œ™šž¡£§¨¨‘“‘ˆ€|zzwuuvxy{xsmjiiiiikjllijjjijljhggfeeecb]WMIMe{ˆ˜•““‘‘ŽŽ‘Ž‹’••–™šœž ¢¢¤¢¢¥¨ª­°²´·º¼¿ÁÂÃÅÆÈÉÌÐÐÑÑÏÎÌÍÎÍÌÊËÇ[DC@9I»ÊÅÅγc.4=@<9V£ÐÍÆÇÇÃÁÃU.123242024467:9797=¯ÀÁÉs.1-*%*71/.,)(#" HŸ™”’C(%#"!%$&($.”³°³´µ·¶¸¹º»»¹µ°«§¥£¢¡ž›š››œœœ›˜˜™ ¢¤§¨©©§§¦¤¤¥£¢££¢¢¢¢Ÿ›š—’™¡¤¥¦¥¢œ—‘‰€~€‡˜žŸ¢£¡¡¦©©¨¥ ›˜˜›¢¥¤¤¦_ˆ‡~zyxurstwwxtlifeecccedbcc`b``ac``_^^\ZYXTPF@I_x„˜—”’‘”’Ž‹ŽŒ’’’”•™›ž ¢¡   ¡¤¦ª¬®²µ¶¶¹¾ÀÃÄÄÆÇÊÏÐÑÐÎÍÌÌÌÌÉÈʽL<@@8SÀÃÃÈ”D3::;:;t½ÎÇÆÅÅÄÁÀ½K034.B€yl\TJ:788669Z·½¾Åi10++&v “•“Œ…{tmffdx•“ŽŽ@(x†‚~vY3"$(*-&A«·¶·¸¸¹»»ºº¹¶±­©¨¥¤£¡ žœœœžžžœ›šœŸ¢£¦¨ª«ª¨¦¥¤£¢¡¡¤£¢¡ Ÿœš—”“𡣦¤Ÿ™‡ƒyuuu‡’™›œ ¤££¨«¬«¨£Ÿš–™Ÿ¡¡ £¤4^†……‚}{zwtqpqsutmifa`^^][XY[WVTWWWXYXYWUSQOMID;34]w‹“——”’’‘ŽŒŽŒŠŒŒŒ‘’’•–—™Ÿ¡¡Ÿ ž ¡¡£§ª®±³µ¸»¼¿ÃÅÅÄÇÌÎÏÎÍÌËËÊÈÇÆÈ³@48:6U½Â¾s24<881DËÇÀÀÂÁÁÀÀÀ·B256/_ËÇÉÄÀ·¦—”˜©¸¾½¿Äb)-+++‘²©§§¥¢¡Ÿ¡¡žžœ˜“‹47 ¡¤§«®›Q&*,,.'{¾¹»º¸º¼»»¹·²°«¥¦¤¥£¡¡žžž ŸŸŸœœž ¤¥§©ª«ª¨§¦§¦£¡¡£  ¡ œœš–”•› ¥¦£Ÿ•Šytrtv}†˜œ¡¦©ª¬±²±®ª¦£Ÿ¢¥¤£¢¢¢@5`†ƒ|{vwurooqrtqifc`^\XVURQPMNOOPRRPPOLKID@7,!T}‚ˆ•–”“‘’’‘‰‡‡Š‘“”–˜—šœœžžžž ¤¨«°µ·º¼¾ÂÄÆÆÈÊÍÎÌËÊÈÇÅÃÂÂÄ©93540O¾°V-68760S¤ÆÁ¾½½¾¾¾¿Àó?3451jÈ¿ÀÂÃÃÄÅÌÎÉÆÃÁ¿À¿Â_,.,()’«¤¡Ÿž›š›˜˜˜—•“,= ¡£¥§³«P(..0,O¶»»»»½¼ºº¸µ²¯«¥¤££¡ ¡œžŸ  ŸœœœŸ¤¨¦¦§©ª©§¨¦¦¤¡  ¡ Ÿžœ›™˜–“”›¡¥£š“‡{sqnnpuz‡’›¡¥¨ª®°±¯®ª§¤Ÿ¡¢£¥§§¥¥D@<^ƒ~~{xwutppprrqoida][YVSPPMHFECDIHHGEB@:0!P|„‡Ž“•–•“”––”‘ŽŒŠ‡†ˆŽ‘“–––˜™™™˜™š››œž ¤¨«®´·¹¿ÃÅÅÆÈÌÌËËÆÃÀ½ºº¹¸»™-./.(LŽA&53350d²Ãº¼¼»»¼¼¼¾¾Á¬>4352nÆ¿ÀÀ¿ÀÀÃÄÃÂÀ¿À¿¿½½X++)&*‘¦¡Ÿœ›————˜š™”’†'F¥Ÿ£¥¨©«¸œ6,/12<¤À¼½¼½¾¾¼¹¶³®©¥£¢ ŸŸžžžžŸŸŸŸŸ›œ¢¨¨¨¨©ª¨¨§¦¦¥¢ Ÿ ¡Ÿœš—•–”’”šš‘ƒ{qnnlmmptx{ƒŽ˜ ¡£§ª««ª¥£¢ žœžŸ¢¥¤¤£DCA8f||{zutsqnnnmnnjf`][XVRQMKHD>;99<=:3+' Hy‚†Š“”””•””“‘ŽŽ‹‰ˆ‡ˆ‘’‘’”“’”––——˜™š›œž¤§ª®°¶¼¿ÀÂÆÈÌÌÉÆ¿¸²®®®¯°µŽ%)'()+."-.-,1l·º´¶¸¸¹»¼½½½½À§84493qĽ¿ÁÀ¿¾¿ÁÁÂÀ¿¿¾½»ºQ%'%#'ŠŸ›™šœ›–•—™˜˜•‘Ž€#M§¢§©«¬®²·Q*//34“Á¼¾¾¿¿À¾¹¶°ª¦¤£¢ žžŸ   Ÿž ¥¨§©ªªªª¨¥¥¤¢¢ŸŸ Ÿœš™˜•“‘”—“…}tllmjmmptsux‰“™™œ £¢¢›‘‘—œ›™š› ¢¢¢BCA;=jxwyvtrromllljjhb]ZXUSPMJGB;7542( D|ƒ„ˆ“•–—•’‘ŽŒŠ‹Œ‹ŠŠ‹ŽŽŒŽŽŽ’““““•—˜™˜œž¢¤§ª±¶¸»ÀÃÅÉÊÅ¿µ­¥¡¡¡£¦¯€!&%%%$%))++.y¹¸±²³¶¸¸¸»½»¹ºÀŸ53242tȾ»¼ºº»¾½¼¾¾¼»»º¸ºM%&#"%‡˜–•—–•––“’’”Œ‹Žw\ª¤©­®®²´»_(1263–ýÀÀ¿¼¹·´±¬§¤£¢¡¡ŸžŸ ¢¢¡ Ÿœœž ¤§¨§©«©©¨¦¤¤£¡žž Ÿœ›™˜—•“‘ƒurkijkloqqtstx}†Ž“˜™š˜ƒ||ˆ•˜––˜šž¡¡¢CCC@7?kuvxurqnlkkjjlhc^ZWTRPMIE@<851( =|‚……ˆŽ”•–•”‘Œ‹Š‡‰Ž‹‰‰ŠŠ‰‰ˆ‰Œ’’‘””“’–™œ £¨®±¶¹¾ÁÄý¶ª¡œ˜˜™›Ÿªp!!!!"$%&&%T¶¯­¯°²³µ···¸¶¶¶½‘/100.R˜¤®·º¼ÀÀ¿½¼»¼¹¸··¶H"$ #‰¢›œ™–’‘”•’‘‘ŠŠŒm e¯§ª¯°±¶¹¿Y/0588¤ÅÁÀ¾½·´²°«¦¡¡¡¢¡Ÿ  ¡¡¢£¢¡ žŸ¢¦§¨§©©§¦¤£££¢Ÿœž ž››š˜–”’މ„tolkiiklnqrsusx~ƒ‰ŒŽ’‘‹€tpqŽ““•–šœžžœCBCA=8>outtrqmmlijhiheaZURRQMID>975(  8x}ƒ„†ŠŠ”•“‘ŽŠŠŠŠ‰ŒŒ‹‰‡†…„„ƒ†‰‹‹ŽŽŽŽ’–›¡¦©­±³¶¹´±© ›—•••˜›¤h#  ""$$3›®©­®¯¯¯¯¯°³´´²»„*--,,'(1>Lau…˜Ÿ¡ §¼¸´±E#!FTSYYZ`dagigkpŠ–c " p±«°³¶¶·¼¬A6346F·ÄÁ¾¼º¶´³®¨¢¡ŸŸŸžžŸ¡¡¢¢¡¢ Ÿ  ¤¤¦¦¥¦§§¦¥¢¢£¢¢ š››˜•”“‹€{vqnkihhkmnqrqtv{‚„‡‰Œ…wpor|‹’’•–™š›š–AA@@>=5Bqrrtqnmkifffffc]WSROMHB=74* 0r|‚ƒ…ˆˆ’”ŽŒ‹‰‰……‰‹Š‹Š†ƒ€€€ƒ„…†ˆ‰‡ŠŠ‰ˆ‰ˆŠ–˜œŸ¢¦¨««©¥™–”‘”––—ž`5 !";ž§¤¦§¦¥§¨©©­®¬µt$,+)*+(('&$'/028:;5„Áµ±«? @“’“›_ "&"z´¯´·¸¸»Ãl59573tÄ¿¿½»¹¶µ²­¦¡Ÿ ž›œŸ   ¡¢¡ ¡¢¤¤¥¥¥¥¥¥¤¤¢¢¡  žš˜˜š˜“‘Ž‹|trqmlighmlnpqruwz}€ƒ‡ˆytomrx~ˆ‘”˜š›™—‘@@@?<=;1Jpprqnmkifcacca`[UUPMFB<9+ 3m|…„†ˆ‰Š‹ŠŒ‹Š…‚„„ƒ‚€€}}}||~~€€‚ƒ€ƒ‚€ƒ‰Ž’–˜™ £¤¤¡œ—•““–––”šQeTM£››Ÿ¡¡¢¥¦¦§§§­e))(*+,+,./25446872‰À³­§>Dš˜š¡^##&$µ°µ¶¸½Å~/9875=®Á½½¼»¹·´°©¤ŸžžœœœœœŸŸŸ ¢¢¡¡£¤£¢¢¤¤¤£¢ žžœœœ™˜˜–—”‘ˆzupljifehkkmooqtwz}~€}yslknv|€†‹”˜››š“Š???>=:75,Nnqppmjgedaa_]^[XQNHB@8$ +  )jx€‚„†ˆŒŒ‹ŒŒŒŒŠˆ„~~|xwz||}|{z{{{z|}~{z{z{|~€ƒˆ‘“–™Ÿ Ÿ™—“‘“–˜•–Io˜2j¡–šœ™ ¢¡£¢£­U%%'$)'%'+,.0214785޼°¨£8 GŸž¨\#&%(&‡Àº¿À¿ªj/7;:82ŠÇ½¾½»¹¶³®©¤Ÿœ›››››œ›œŸžž ¡¢£££¡ ¡¢¤¤£¡ Ÿœ›žŸš••••“‘ŽŽŽ…ytpmhfeeeilmnpswz~}}|zvvnikrx~~‚Š•›ž›–Ž…?>>=<:85/)Kpllkihfcb_][ZXUOIE@. + %ct{ƒ…†‰‹‹Š‹‹‡…‚€|{zxuwxzywyxvwxwwxxuttuwx||{}€ƒ‡Œ“—›œ—–’Ž‹Œ”–‘Cq™~ x™’”–š››œžŸ¦F!"#)~ŠveTJB:965767’·©£›0K£ ¤¨V$&&)#^ˆƒzs[:+49=<1xƽ¼½»·³°¬¦¢Ÿœšš™š›š›œžŸžŸ¡ŸŸ¡¢ žž¡¢¡    ŸœŸžœš—‘‘“‘Ž‹ŒŠ‚vqmlifdcehkmnsy}yxvvtpghmrx||€Š•šœ•Š‚?==;;8641-*Gjkgffdcc_]YVRLLE, + + %_ryƒ„ˆˆŠ‹‰Šˆ‰ˆ„ƒ‚}{zyxvwxwvuvvttttttqorsstwwwww{~…‰•—›š—”ŒŒ’–‘:m‹‘g&„Œ’”•–˜˜™›œ7!! 2¦¸¹»º¸²«¦¢œ™––¨¬¡š+*UTTQONKIHIIKJrª¤¤§N$''))&))%(*/34:6:}Á¿¾¼¼¸³¯«¦¢ž›šš™™š™››œžžž  žžŸŸ ¢¢  Ÿžžœ›œ™–”’’‘ŒŒŠ‹‰spmkidcdfiilqz‚…ƒ}wuusrqkchoqv|€†”›ž—’†><;;:54411/):_gfb`ab_ZUMKI:!  + Wrw}‚……‚†‡„‡ˆˆˆ†„€|yzzvwvuutvtssrsrqompqptutwwvwy€‡ŒŽ–›™•“Ž‘Œ6n‡…‰J6Œ’“”—˜—‘.7Ÿ¦«²µºº¼¾½¿¿¾º±¥š“‚%C”‘‘”••—œ£¨§¥¤ªL$((*,-020234213PŸÊÀ¿¾½º³°¬¨¤ œš˜™˜™™™™šš›š›ššššœ›œž ¢¡¡ Ÿž››››š™–•“‘ŽŠ‹‹Š‰‡~vtpnhcbegklqw„ƒxvtqonlhdhnow‡’ ž™ˆƒ{><::85434311,1Oa^\\ZVPNL7 + + Lqx|‚ƒ‚‚‚„…ˆŠˆ‡‡‚{{ywwvtuutttrqsrrpooqprrqssttx|†‡–—–“‘‡‡†ˆ‰‰6o~|}‚4G‹ˆŠŽ‘’’”‡%:˜š¢§¬°²³³³´²°¬¥šŒw J‹ˆ‰Œ‘’–—™œ¡§¨ª©¬M&++-/-+.,-1/?b‘ÁÌÂÁ¿»¸±®¬¨¤¡žš˜—˜—–“’“””’‘••”’’–—–™¢¡Ÿš˜—˜˜–“““’‘‹ˆˆˆ‰ˆˆ„}|ysnidbdhmu{ƒˆ„€yuuspolhdefimw…“›¡¡Ÿ™”ƒw<;9877655331/.)?ORRQLIA"   + LVas°ÂÉÅÁÁ¿¹·´¯¬©¥ œš•“‘‘ŒŠˆˆŠ‰ŒŽŽ‘Ž‘•–˜˜••–“’‘”“Ž‘’Œˆ‡‡††‡ƒmtxslfcaensy†ˆ†{tpqonkecfghio‚“¡Ÿ›•ˆ‚|x;::877442320.--3?A@EG5 + *p{~ƒ„„„……†††††ˆˆ…~€€}{wvtqrrqrnqqqpnmnoppponpqruxz~†Ž’“’‹ˆ„‚„„/"uƒ‚~{€fp‡ƒ……ƒ‡t>‹ˆ‰‰‹ŽŽ‘’Šˆ„€€…a]‡†‹ŒŽ•–™Ÿ¥«­¯±³¶Q-16;<ŸÁÄÉÌÍÏËÅÃÿ¼¶²¯«¨¢™–’Ž‹Šˆ‡ˆ†…„ƒ„„ƒ‚‚…†„ƒƒ„†ŠŒ‹Š‹Œ‹‹‹‹‰†„„ƒƒ„‚aanmjeabhmu|‚„…‚{tqoomlhb`cgiiju„”š—”Žˆ„‚zw97788842310/,0;A@;8=4   + $c{€‚„„„…‡††‰‰†…‡…€€~}{xwurqsspnoooonnmnoooppqruuwz}‡Ž’’’Œ„})!wƒƒ€{Y(ƒ~€€~‚jDƒ~€ƒ‚ƒ‚‚€~}~‚Ueƒ†ŒŒ“–—ž¢¨­±³µ´µQ2369H½ÍÉÊÉÇÇÇÆÄÿº·²®ª¦¡œ—‘Š…„€€€}}|}}}|zzzz{z|}|ƒ‚ƒ…†ˆŠ‰ŒŠ‡…†Š‰‡ƒ‚‚€~€‚€bYdhecabgkxƒ„|upnnmmjd__cfhhjmr|‡‹‹ˆ‚€}ww5676665332/,1BGDA:90  + _}‚„……„‡ˆˆ‹ŒŒ‹‡†„‚€~}|{zwutttpopnoopqpprrsuvxwxy{{}‚‰’’‘‡v%&w€~~{y‚G=…~||z~_H…|}€}{||yxxz||}„Ks‹‹‘”˜›ž¥§«°´¸¹¸´J1344J¹ÆÇÈÉÈÈÈÆÃ¿»¶¯ª§¢œ˜”ˆ…ƒ~|zzzxwvxywvwvuuvtvyz{y{~~„…ˆ‰†ƒ‚ƒ‚~}}~~}`[`fccbcglw€ƒ‚|wsoonlke_^`ceggjkmsz‚…ƒzxz}578645542219EIGB>?:   + + Ly‚ƒƒ„„†…†‡‹‹‡……ƒ€~|{xvuusurqqqpqrqrtuy{~ƒƒƒ‚‚ƒ€„‹ŽŽŽŽ‰‚}{m )z}{{|}}~~4Mƒzzx}S.RV[bdhopsvv{~|€~„E"ƒ›—–›ž¢¤¨«­±´¹¸¹¹»´I5693M¿ÆÈÊÉÈÇÆÃ¿º´¯§ š–Šˆ…€}|zxwvwvttutvtrrsrsqppqsuvxyyz|~€‚‚‚~€~~}zzyz}{`[`baabehox€~|xsnmlkhb^]beggeffimx€€}yw|ƒ7865533414BJIGE@<@  + :x‚~}€€€ƒ„„……ƒƒ€}{zwxwuvuurrrqrru{€…‰Ž”“’“Ž‘‹‰‹‹„}yui"({zzy{|||v&^ƒxv{M#(+06:R~„=`twz†‰–˜š›žŸ±»¼À·H7776XÆÊËÉÈÆÃÀ½¸²­¦ —’Œ…‚|{yvuttrrrqsttrqqpqooooooqrsuvuvxyx||||||~~|{zvvvvz|x`[___`bcis|€ztnmjigc^\`dfeggbglp{}}{vyˆ77534322>JPJIFD>2   + + .wƒ~zyz|}}€ƒ…„ƒ„†„ƒ}}}||yxwwvrrtvz†‘—𠢤¤¥¥¢žš—”Œˆ†ˆŠ…|wro_PJE>Gwxzy{yzxx|dl{rwF/€€5!$&)+.1147›ÂÀõF7887hÌÈÈÇÅý¹´±¬¥ž—”Žˆ€|ywwwtrsrsrqprrqponllmmopnopprsrrstuuwyxxxyyzxwvtuvuwzv]Z[]_`delu}‚‚|tojkhie]Z\bdgffhfms{~|{{vv|‡7553229JVTPJHE:/!  + *f€|xxwz||~}~‚‚„†„…††…ƒ‚ƒ€{zyzyvy|~†Ž– §ª¬¯³±²µ´±¬©¢™’‰„ƒ…†…€ytoqsx{{wvwx{{|}|{z€V#&pvyB5€0"""%'(*+,//15ŸÆÁÇ®B:;<7rÍÈÆÃ¿½·²¬¦Ÿ™”Œ†€}zwvutssrrqrrqqpomlkjiklmmnmmooppppprrsttvusstuvuuututuwoZYZ^aceinv}€|vqniigfa[[aeejhhimu|€|}|zvz†”œ52123ASWTSKE<70,! + + + "[y~zwtrswwyz|€€„††‡†ˆˆ……ƒ€{{{yz‚‰‘™¤­±³¸¹»»»º¹¶´±¨œ„€‚„ƒ‚|wtrqprtsstuyz|}~}}|sjbXOI;[ww@A‚~ƒƒ. ""#&')*-/2359©ÇÂÇŸ76530…ÌÅľ»¶±¬¥ž™Ž†‚|yvutttrrrqqqppoomkjihhhghijijlmmmnopoqrqppqssrsrttstrsssukVW]`efimsy€yrnlgghe\Y\aefjkjnt|„…~{zvx‚‘œ 31/-3AA?A=75320/#  + + + + U{{wrrqrrstyz}€ƒ„…ˆ‰ˆŠŠ‰‡‰‡…‚~~‚…‹“𥭳º»¼¼½¾½º¸µ±ª£˜Š~{|~€€}zyyvuttuuuvz|||~}|}}}|x{}|{xxoaZSKD<62,)$#I‚€†) "!#%')*+-33/<­ÃÂÆ›S\bv‹µÇþº³®§¤ž•†~zvuttsrqppqppoommljhhghhfdghfggijllmmnnmnpqppqqrrpqrpqqrpqtqbadfhjntyƒ}wojigge^XY]cfjjlpsyƒ‰†}yut{‡“œ 1.,./.,.43334422' + + + + + Lyxwtqpqrssty|„‡‡‡ŠŠ‰††††‡‰†„‚ˆ‹’›£«²·¹»»»¼½¼¹¶²«¢—Œ~{zz}ƒ‚‚~{zzzzy{zz|{{€‚|zzz{}|z€{zyzywxxwspnlhcYTQl…‰ƒ?2.2300.1656;@FGNSZv¾ÂÅÆÃÂÊÏÒÐÉþ¹´®ª —ކ‚|xvsrrqopooopoollklljjijmlifiigeffiklllmmnlnpomnopomoqponnnprqighjiovƒƒxqlihge`YX]cfijjmsw~…†€zvqs‹”›¡.,+-.-/043566753   + + + + + + + + Euttsrsssstux{‚†‡‡ˆˆ†‡ˆˆ…†ˆ‡………‹Ž— ©±¶¹»¼¼¾¾¼¹´°­¨š‚zxyz}…………‚€~€~ƒ‚„„„‡…‡†‚‚‚€ƒƒ„„€|{xyz||~}~€‚‚ƒˆŽŽ‹ŠŒŽ‘““•™ž¡¤§¦©¬±¹¼¾ÁÆÍÊÈÉÉËËÈÉÇž·²ª£›‘‰„~yvtsrponmmmnonoomklnnnoorvurpmjihhfegjjilloonoononnmmlopnmlmmqpkijkoxˆ†{tnieeb`XV[_dghkmqw€‡ˆ€zwurx‚Œ”™ ,+,./00224677742  + + + + + + + :wwvvuvttuvwxy~ƒ„ˆŠ†…‡‡ˆ‡‡‰Šˆ‰Š‹•œ¢¬²µ·º¹º»º¹³­¤˜‘yutux{}ƒƒ†‡……‡…ƒ„„„„„ˆ‹ŠŠ‹Š‹Œ‰‹‹‰‰‰‹‹ˆƒ~ƒ‡†‡‰ŠŒŒŠ‘“‘“—˜œŸ£¤§¬²¹º¹½ÁÁÃÆÅÇÊËÉÊÊÊÉÈÉÇž·°«¥šˆ‚~{xtqrrponnlmnnnmoomnopprsux{yxvrqonkigfgigiklnnonnolllkkklllkmnpoljkqz‚„‡…zslhfccaZTW^dfhikqv{„‰…zvssu}‰‘˜œ +++-/00344455762   + + + + + + + 1s{}}}|zxvvwwvzƒ†‰‹ŠŠˆˆˆ‡ˆˆ‰‹‹‘•™ ¨¬±³¶¸¸¸¸µ°¨ž˜ˆ~usqqqty|€‚†ˆ‰‹ŒŒ‹ŒŒŒŽ’““”“‘‘’“•“’“’Љˆ‡‹ŽŽ’”•““•˜——™˜˜¡¤¦¨©­°°·»¼¾ÁÅÅÇÇÈÊËËÊÊÉÈÇÈÇžº´ª¢›’‰~zwutrqpqpnnnnonooopppqqsstwxz|{{zywvutppjhhhjklmmmlmnljkkjjjjkijnrplov{„‚|rjgedb_]USX^cegimsx€‡Šƒxurrx‚Œ“šž )**,-./11214112*  + + + + + + + + +n€„‡‡…„€|zzzzy~…‰‹ŠŠŒŒ‰Š‹‹ŠŒŽ’—𣫭®°²µ··´«¡œ–‹ƒ}vurqqsuw|€ƒ‡‘””••••–˜š›œœž¡Ÿ››žš™™˜•’Ž‘”““•—–™šœŸŸœœŸ§«­¯±´¹¼½¿¿¿ÁÃÆÇÇÈÊËÊÊÊÊÈÈÈȼ¸´¬¡™‹‚~yywuspnonnnmnnnoprrrrsttuuxyz{}}}}}}{|{zxunkjkkmnkllmmlljlkjihhhikmqnov…‡…~yrleddb`\VTV\bdfilot|„Š…}wsot|‡•—œ&''(())(''''%$#   + + + + + + + + "k‹‘Œ†ƒ|~}}„‡Š‰‡ŒŽŒŽ‘•™¤ª¬­®¯±´²­£šˆ€|yustsqsx}…‹’—™¡£¤¤¢¡žžŸ¡¤¥¦¨©ª©¨§§§¦¥£¡¡Ÿš–•—ššœ››œœŸ ¡¢¥¨¦¥¥§­°³µ¸¹¼¿ÀÃÄÄÅÆÇÈÉÊÊÊËÊÉÈÈÇÇÇý¸°¦•Œƒ}zxvvspnmmmmlmnpqqstrrstvwwyz{||}}€€}||}}|{xqmlkmmklkllmlkjjjiighfghiojju€ƒ{qjd^``^_ZRVZadefkkox~„…}xvqqy€‹‘“–™œ!! # ! !!"!   + + + + + + + + ]˜›š˜•‘Œ‡„„‚~}‚…ˆ‰ˆ‹Ž‘’‘’”–™ ¦ªª¬¬­°±¬¢™…|{xusutsyˆ‘œ¦¬±¯°®°´µ·³¯«©ª¬¬­®±²¯¯®­¬¬ª¨¦¡œŸ¤¦¦¤¡ŸŸ¢¦¦§§©¬®°²³±´·¸»¾¾ÁÄÅÆÇÉÊËÌÌÍÌÌÌËÉÉÈÉÇÆÀ¹´®¦š’ˆ‚{yxvtrpnllkjlnppqrsuvuwvwxz{{{|}~}~~}~}}}||ytqnnljkljljkjjjjhigfggfgjjYU_ijhfg`a^\Z]]ZTRX\`ccdgmpw}€|xtrorz…Ž“•˜™"""" !!##"#"!    + + + + + + + + Nˆ—Ÿ¡Ÿžš—‘Љ†ƒ†ˆŠ‹ŒŽ‘’’“”–™œ¢§ªª©ª«­­§›’‰‚€~xwvyz~‡˜¥«£‡mWMJJP_œµÂ½³³³³³´µ³´µ´´±¯­«¥¤¦«®¯«¨¦¦§«­®°°²²µ¹¼¾¾¼½½¾ÁÂÅÅÇÈÊËÌÍÍÎÎÌÌÌÊÊÉÈÇþµ¯¦ š‘‹|xvutrqonlloqqssrqstvwz{{{|}~~}~~€~~}~}|||xurppnlllkkjjjihihffdeeeicMJKSSTWYXY[[[[ZUQTX]bdeehlqx~}xurnpv€‰–˜˜š› !!"""!"   + + + + + <€‘Ÿ¦¥¥£ž™”І„ƒ…Š‹ŽŽŒ’““–™š¢¥§¦¦§¨ª©¥™‹‡†‡†ƒ…ˆŠ‹‘š¨±–c<0034453048Gw¯Ä»¹ºººº¸¸¸¹º·µ³±°²¸º»¸³±¯¯²´³´·¸¸¹¼¼¼½¾ÀÁÀÁÂÄÆÆÇÉËÌÌÍÍÌÍÍÌËÊÇÇÅÁ¼³­§ ™Š{xursqpponokWZ]aitvuxxxz|~~}~~~€€‚€~~}|||zyxusronnlklljifihfbcbbcf[FIJJMPQRVY[ZZZVQRV[^_cfeimqy}ytrpou~…Š“™™—š› !!" + + + + + + + + + + )q”Ÿ¥©©©¥¡›–’‹Š†ƒˆ‹‘Ž’••—››ž¡¡¢¡ £¦§¨¥¡œšœ›š›Ÿ£¤§«·§f0(/7;889:9<::5.@{¼Ã»¼¼¼¼»¼½¼º·¶¶¸¼ÃÆÊÆÅþ¸¹ºº»½¼¼¾¿ÀÀÀÂÄÄÄÄÆÅÆÉÌËÌÍÌÌËÉÊÉÉÈÆÄÀ¼¹°©¢”‡ƒzwutqrqpppoy>j{xxz|{|~~~~‚ƒ„…„…„ƒ‚~}}}||||zywvtrollkkjhghgeaababgM=BFIKNOQVYXYXWQOQVZ^`bdgkotyyuopqot…Š’–•˜š + + + + + + + + + + + + `Š—¢©«ªª©¤žš–‘Ž‹ˆŠ’“’“‘“–—™šœŸžŸŸžŸ¢§¨ª®²·¶²²²´·¹¼ÄšB'1524:95457;96784.Q©ÉÁÂÀÁÂÁ¾»ºº»¾ÄÇÈÑ—q‚Ž™¶½»»¼¾¾¿ÁÂÃÃÃÄÆÆÆËÐÐÏÎÉÊÉËËÊÈÉÊÇÅÄÁ¾¹´­£§c&*),+Mxstrqppont[f|ywxz{|}~€€„†‰‹‹‹‹‰ˆ…„‚}{{||}~~{zzxusrqnllkjhgfccaaac=3œÉÂÃÃÃÃÂÄÇËÌÌÌÆV16697P»¿¾¼¾ÀÂÃÂÃÄÇÇÇÇÏo0240KÈËÉÉÈÆÄÃÁ¾¹¶°¨ š“‰//qrqqqqpoyHi€|||}~€‚‡Œ”••”“‹ˆ†„~€€€}~}zxxwvurrrpopmlhdd>.379>DFJLNQSSPLOSUZ^^]_dghnsspnoprv|‚‡ˆ‹ŒŽ‘   + + + + + + + + + + + + +  `‘Ÿ¦¬°¯¯®«¨¥¤¡›˜™˜˜›žžœ››™—•’‘‘‘“˜¢°ºÁÅÆÈËÍÍÏz-58::0N•ÁËÊÈÈÉÊÌǯf59;;:.PÀÆÃÂÁÂÅÉÊÌËÄÇ{+633576œÅ¾¿¿ÂÃÄÃÆÅÇÇÉÊË\49:4eÍÇÇÇÅ¿¼¹¹¶­§ ™‹t!-oqqrrsqsck€€‚„†‹‘”˜™˜—•“‘Œ‰……„‚ƒ‚€€€€~|{{zyxuxxvwuturp]/+18;;ADGJMPRQMKQTZ]]^]`dginropootv{|€‚ƒ…„†ŠŒ   + + + + + + + + + + + + + + + R€Žœ¤¬²µµ´±­««©£Ÿœœœœœž   žžœ›•’ŽŽ•¨´»ÀÃÇÊÉÑŠ-4696/sÁÑÉÅÅÅÅÅÆÄÁÃÃ…79<1MœÃÄÃÁÂÅÊÌÎËÂÆ¢//10114.kÅ»½ÀÂÄÄÅÇÇÇÈÉÍÁH6885~ÌÄÅÄÀ½º·²­§Ÿ™”‡ƒ`,npqqrsrw1n‚‚ƒ„†ˆ‰Œ“–™™™˜—•’ŽŒ‰‡ˆˆ†††††‚‚‚‚€|~|}|{{{|}|{zyws3%+-3:;?BEHMQRPJJPUY]^_`acdinmlmoswz„…„‡†‰Š‹Œ   + + + + + + + + + =yˆ˜¡ª±´¶·¶³®­¬«¦¢¡¢  ¡¢¡ Ÿž™“ŽŒŒ‰‹Œ”žª³¸¼¾Àͱ707<71ƒÎÎÇÈÈÈÆÆÅÆÆÅ¾ȉ32a¸ËÃÄÅÆÉËÌÌÉÀ¼¹H&0/0001/?³½¾ÁÂÄÆÆÇÇÇÇÇË«:9874‘ËÁÁ¾º¸³¯ª£œ˜‘Š‚B#kpprspyRtˆ†ˆˆ‰ŒŽ‘•˜™œ›™—•‘ŽŒ‰Š‹ŠŠ‹Š‰‡…„‚~~~~€€}||U#*,7;?ADFKPOKIJOUY\]_``ceiljklov~ƒˆŒŽ‹ŒŽŒŽ  + + + + + + + + + + + + 'j‚Ž™¡¨­¯²´³°¯®®­ª¥£¥¥¥¥¤¢¡ žœ—“‹ˆ‡…†‡ŠŒ’©¯²´¶Äs+:<=2ƒÒÇÅÆÅÅÈÈÇÇÇÆÅÅÃÃÉ„ÆÌÅÆÇÇËÍÌÊÆ¾´¼n!--../1241†ÆÀÁÂÄÅÆÅÆÇÇÇÏ”4:889ÆÀ¾½¹´®©¢”Šƒ|zy.eqrrrshv‰‹‘‘’”˜›ŸŸžœš–’ŽŽ‘ŽŒŒ‹ˆ…„‚€€‚„‚€~|r# + !(,7AGJJJIHJMPUXYZ[\_abeknt~‡Œ’”“”—™˜———˜™    + + + + + +  + + + + + + + + + + + 0u›¢¦ª­°°²±¯­­©§¥¦§§¦¥¥¥£¢¡›“І„ƒƒ„ƒ„†Š“Ÿ¨°¹¡-+052ÌÄÆÈÈÈÉÈÇÆÆÅÄÅÅÅÅÅÇÆÆÇÇÊËËÌËÅÁº¸³E(0./+::279:9¦ÅÀÁÁÁ¿ÁÄÆÅÂÆa3979E¯¼¸²­§£œ’‹„ƒ~zxu|ADtsrxZ)…‘‘•––––˜™š››¢£¢ žœš™™š™™š™—•’‘ŽŠˆ‡„…ƒƒ„„†ˆ‡Šˆ‰ƒ|j "(+/9?CHIHFGHLNRUXY[\]^_ajr~…‹Ž‘”•———•”—›œ  + + + + + + + + + + + + + + + + e€Œ™¢¨«¬¯°³²°®­¬ª¨§§¦¥¤¤¥¥¤£¡˜ŽŠˆ……‚‚‚ƒ…Š“ž¨´‘%(),2¦ÄÁÄÆÆÇÇÈÇÆÅÄÃÃÄÅÆÆÈÇÇÉÉËËËÉÆÂ½¸¾s$/0/01˜x-;8;6oÇÀÂÁÂÂÂÅÆÄÃÃS4765F°¶±ª¤¡™•‚€|yxutn"$%prwo"*.“—šœœ››œ›œ ¢£¢¡ŸŸŸž›™—”‘‹‰‡ˆˆ‰Š‹Ž‘’‘‘Š: &).4BBCDEFFGGIMRRSVUVWX\`_cfhlnoonnsyxy{ + + + + + +  + + + + + + + + + + + + + + + + + + +  fŠ˜ §«­®°±²°²´³²¯°°°°¯°¯¯­¬©§¢œ–‘Œ†~~~~‰\­¨­°¶º¾ÁÂÂÃÄÅÆÆÆÅÃÄÅÄÄÃÅÃÂÈ£00113+I½À½½½Ãm08552lÇÀÃÄÃÂÀ¾Å‡+,(%!A—Іƒ~|wwxvuutsv1f|}/yš–M\ž›žŸ ¢¤¦¦¨§¥¤£¢¢£¤£¢££¡¡   žœœœž¢¥¨®±³·¸¹ºº¹¶²¬_ + +  (07>AAEGDGHFGJMPPQSTUVXY[_acedgihkmquw~„ + + + + +   + + + + + + + + + + + + + + + + + + + Nz„“ž¥¨¬¬¬¯±²²³µ´±²²±±°²²²¯®«¨¤ž˜“‡ƒ€€~~~!F¥¡§­²¶¹½ÁÃÃÄÅÄÄÃÅÂÂÄÃÃÄÅÄÈ¿N,300/7¤Å»»¼¹¾œ13223C¶ÄÃÅÃÁ¿¾Ãx%&" Eƒ|yxvuuvutttr)p‡hO£š™Hd¦ž¡¢£¤¥¦¦¦§¦¤¤¢¡¢¢¡¡¢¢  ŸžžžŸ¡¦ª¬°´¸º½½½¼»ºµ´‰#  + + &/8?BBFJFEJHGILOOOPQTVXXYZ\^^acdhjnr}ˆ“œ  +   + + + + + + +  + + + + + + It€Ž›¡¦©ª©¬¯±³µ¶´²±±°°±±°±²²¯­§¡œ•Іƒ~}~†Z]¥¡§¬¬¯´»¾¿À¿¾ÂŪÂÅÂÂÂÄÄÃÊl-630.,€Î¾ºº¹¸¹½M-3251ŽÊÂÅÃÀ¾¹½l!% NŽ~|xutuuuuttui"%z‡‰A&‘£ £F ! k§¢£¤¤¥¦¨¨§©¨¦¥¤¢£¢¡¡  ŸŸŸŸž ¥ª®²µº½¿ÁÂÁÀ¿¼¸µ§J  + + + + $-8?EGIKJFGIIIHLMLLPRSVXWWY]^`bfltŠ™¢£   + +    + + + + + + + + + + + + Tk|‰”£§ªª¬¯±´·¹¸¶µ³²²²°¯°°°¯®¬§¢Ÿ—‹†ƒ~}}€€„6b¤¨«¯´¸¸»»ÂÂ~?vËÁÁÂÃÀÌ19632/.[…Ÿ²¼¿ÁÂÌ€,5460^È¿»¶ºa !V…}{xvuuuuvvuwc*„’œ}"d®££¨M!!"lª£¤¤¥¦¨ªªª«¬¨¦¦¤¤¢¡¢¡Ÿ  Ÿ ¡£¨­²µ¹¾ÀÂÂÃÂÂÁ¿»³®k + + + + + +,7HGFHJKIGIIIGIIIJLOOTUUVY^`cnt„—›œš™•“  +  +    + + + + + + + + + + + + + + agm€Œ—¢¨ª«®±´µµ¹¹¶¶´³³³²³³±±±°®¬§£ž•ŽŠ…‚}~€y$G•¦§­®®®³ºÁ®_/83ɾÁÁű?56651--&$,>Qit{…m364459¬ÄÀ¾º´®²S_}{yyzyvwvvxzy|[/›žŸ§V  1¢®ª©«U!$o®¤¥¥¦¦¨ª«ª¬«ª¨¦¦¤¡¡¢¢ ŸŸ ¡¤ª±µ¹½ÀÂÃÅÄÃÃÂÁ¾º°‹* + + + +/HKFHJJKKKIKIGHJKNPSSVZ_cglsŠ‘‰‰Š‰  + +    + + + + + + + + + + + + $hfg{‹”Ÿ§«¬®±µ¶···¶³²³²²²²³´´´³±°¬¨¤–‹†ƒƒk.g‘§­®°²l9'5997’ÈÁÄÄb287670+*+)*-*+-.0076446,tº¸¶°§§Bk{wwyyyyzzz{}†W0˜¤¥¥«ž2#$s¶­®«¯] #$k®¨©¨§§¨ª¬¬ª©©¨¦¥¢Ÿ ¡¡  ¡¢¦«±¶»¿ÀÂÄÅÆÄÃÂÁÁ»²£F + + + + + + 3HFIJJKNQQNPRPRTWX^`adjnruz|~|~‚ƒ…     + + + + + + + + + + + + + + 'kda{•ž¦ª®°²³µ·¸¸·¶µ³³´´³³µ´³³³¯¬ª¦¢ž˜‘‹ˆ…€€}{ƒc'E\a[C+ )12482O»½Ä‡3989:3.%#&'+.232367754351D·ºµ²§ š7$xyxxzzz|~~€‚†Š“` 1¨«­«¸v'$=¬²±°¬¯`"%$b±ªªªª©©««¬«ª©§¥£¡Ÿ  ¢¢¤¦©­±µº¿ÂÂÃÄÅÅÃÃÁÀ½¶¯m  + + + +6@DCCGMQWWWYYXX\_adceikmoqrtttvx|}€ + +    + + + + + + + + + + + + + + + ,nf_o‡’›¦®±³´³³¶¸¹¸·¶µµµµµ´µ³³³²¯­«©¦¤Ÿš“Ž‹†‚€{z€`!&(,03.<¿À­=79:=6Kš‹`='$(./1466242256*ˆ½±ª¡™’,3yvxy{~ƒ†‡ŠŽ™k"#%3«­°²´³J%$u¹²²±®³l$(%\±¬««ªª©ªª©«ª§¥¤ Ÿ  ££¢¦«¯²·»¿ÂÃÅÆÅÄÃÃÂÁ¿¹´’( + + + +  +4;;:>EGMSTWTUTVXZ]`cegfijklqswwy|  + + +  + + + + +  + + + + + 4rj]cy‹—£ª¯±²³´´·¹¸¸···¶µ´³³´³³´³±¯¬©§¢ž˜•‘‡…‚}||‚s0 #%)+&J¤ÄºÁ_.;;<:7ż»®”vVGC?<>BD91541)I¯¦Ÿ˜‘"E}wz{|€„…ˆŠ‘–œo#%%(/–¯¯°²²¹•-<«²²³±¯µz(*)#P±®®­««©©©¨§¨¦¤¢žŸ¡¢¤¦§«®³·½ÀÂÅÆÇÅÄÄÄÄÃÀ¼·¦L  + + + + + + + + +#5435;=AEJNPQSQSVWZ^_cdddinsuw{†‡  + + + + + + + + + + + + + + + + + 9qj^`mƒ“ ¦«®¯±³µ¸¹º¹¸¸¸¸¸µ´³´³µ¶´´±¯­«§¢žš—”Žˆ…~}‚}L"#&f²¿·À-68<<5}ô³³´¹¾»º·´²³¸À–151-,$‡¤—Œy%5/-/...--..(o ¦{!''((±°°³´³»n{º³´³°¯²*(("A¯±±±®­«ªª¨¨©§¥  ¢¢¤¤§¬®²·¼ÀÄÅÆÇÆÅÃÄÄÄÂÀ»²u + + + + + + + + +%0147:;=BHLOSSQRUW[\_cfhlmtx|€„‡  + +  + + + + + + + + + + + + Csk^]f|‘𣍫®°³¶¸¸ºº···¹¹¸¶µµµµ¶·µ³²°®­©£Ÿ™”ŽŠ‡‚~ƒuF<е²±¸«>388;4ZÁ¹²²²²³²¶¾ÃÇÉÊÇÆÂN*-*(LŸŠˆp hª¨ˆ*++,*†µ²²´µ´µµµµ¶µ²±°³˜0))$:©´³²±¯¬¬ª©©©¦¢¡£¤¤¥¦©¯´·¼¿ÃÅÆÆÆÆÆÄÄÄÂÀ¼¹@  + + + + + + + + + + + + ,2269:<@DIMPSTRVW[^bfimqvyƒ…ŠŽ’•  + +   + + + + + + + + + +  + + + + + + + Gvm_]aoˆ•ž£§¬®±´··¸¸¶µµ¶·¶µ³´¶´µµ´´´³±¯­ª¦£ž›—“މ†…‚‚…‡‰xP/ ,Gz¡«ª«®´x4;864<¨À³±®¬­­°¶¿ÅÅÆÅÃÀÄ‚#)($ %‡……b #j¬¬‘1.-.+~º³µ¶¶¶µ¸º¸¸µ²±°´¢6)*)5Ÿ´²³²±¯­¬¬«¨¤£¤¤¦¦¦©®µº¾ÀÂÅÇÆÆÆÆÅÄÄÄÁ¾¶©w* + + + + + + + + +/48;>BBFLPRUWWY[`dhmty~‡ŒŒ‘  + + +   + + + + + + + + + Rwp`\af{™ ¤ª­®³¶··¶·¸µµ¶¶µ´´´´³²³³³²°®­¬©§¥¡ž›—”‘Ї‡ˆ‡ŒŠ‚ztt}£££§ª®²§¤¦ š–ž¾¸µ¬©©©«³¾ÅÈÇÆÄ¿¼¹§/$$ X…‡T $d­®™5,-,-s¸³µ¶¶··¸¹¹·´²±±³¨<(++.“µ²²°°°¯¯®¬§¥¤¤¥¦§©¬´º¾ÂÅÇÈÉÉÇÅÆÅÅÄþº¶‰c' + + + + + + + + + + + + 6:ƒ‚{l_cccedgz‰”Ÿ¨¯²´¶¸º»¼½½¼¼¼¼¼»¹¹·µ¶µ³±±±²²³²²²°¯®®¬­­­«©§¦£¡Ÿš—‘Їˆ†ˆˆ‰Œ•¢§¬³·»¾¿ÂÆÆÄ½»¸±°­ª­ª¨©ª­¯°²¶¸¸º»º¹º»¼¹¸··»½¼¼¼»»º·¶µµµ¶³´´µ´²±¯®®®­­­­°±°°²²´³³µ¸»ÁÃÆÉËÌÎÌËÌÊËËËËÊÈÆÃÀ»·³°­©£¢˜nadgioo` + + + + + + +     $&&$&(&)’’‘‘’ŒG  + + + + + + + + + + + + + + + + A‰†~o`bcgihgo‚™¤ª²´µ·¹¹»½¾½½½¾½¼¼»¹···µ³²²±°¯¯¯±°°°±±±²±°¬ª©¦£¢ŸŸ˜•’Ž‹‡†…‡‡ŠŽ“˜¦¯¶¹½ÀÄÄÆÈÈÅÄÁÀ¼º¹¹·¹ºººº»¼¾ÀÃÁÀÀ¾¿¿¾¼ºº»½¾½¼½¼¹¹¶µ´´µµ³´´´´²±°°°±°±±±²³³µ¶¸¸¸¹¼¾ÃÇÉËÌÌÌÌËÊËËËÊÊÈÇÄÁ»¸µ²®¬ª¨¡ž€cbehlrmb" + + + + + +  +  +  +  #&$$'&&(‘Ž‘ŽŽŽŒ‰J   + + + + + + + + + + + + + + + + KŒ†ƒ~saachkkkkv…’𤭱´µ·¸»¾¾½¼¾¾¾½¼¹····¶µ³²²±±¯­®°²²±²³²²³±¯¬ª©¨¦¥¡ž™”Œ‰ˆˆ‰‰Š“œ£«±¸»ÀÇÄÄÅÆÆÆÇÅÃÂÃÄÅÆÅÄÄÅÆÅÆÆÅÄÃÃÃÃÁ¿¾½¾¾¾¼¼¾¼»º¸··¶¶µµ¶´µ¶³°°±²²²µµ¶¶·¸¹º½ÀÂÃÅÇÊËËÌÌËÊÊÊÊËÊÉÉÆÅÿ¼¸´±­ª©¨¤™Šjcfikmund# + + + + + +  + + + + + + + +  "#%$%'(''ŽŒŒ‹ŠŠ‹J   + + + + + + + + + + + + + T‹‡†‚va^chjlmklx‡•Ÿ«°°²µ·¹½¾¼½¾¾½½»¸¶¶¶¶¶´³³³³³²±²²²²±°±±²´³³²°¯®­¬ª¦¢™•Љ‹Œ‘–ž£­¤OHuÆÁÂÃÄÅÅÆÎÑÐÑÊÊÉÈÈËÒÑÏÈÅÉÎÌÍÎËÉÊËÆÀ¿¾¾¿¾¾½»¹¹¸·¶¶·¶·µ³±±³³´¶¹º¹º¼½¿ÃÅÈÊËÍÍÍÌÊËËÊÉÊÊÊÉÉÇÄ¿º¶³¯¬ª¨§¥¢œŽthfiikntmf$ + + + + + + + + + !!"$$$'&&(ŽŽ‹Š‹Šˆ‡‡‰D  + + + + + + + + + + + + ^Љ„|b`cfklllgo“§®±´¶¸¹»¾¿¾¼½¼¼»¹¸·µµ¶µ¶´´µ´´µ³´µ³´´´µµ´µµµµ¶´±°¯¯¬ª¤ œ˜—–‘ŽŽ”šŸ¯e ,,žÅ¾ÂÄÄÄÄ~hl˜ÑÊÉÇɽr’ÊÉ­jhfdbgrŒ«ÇÉÂÁÁÀ¿¿½»¼»º¹¹¹¸·¶µ´´¶·¸¹¼¿ÁÂÄÅÇÉÊÌÍÍÌËËÊÊÊËÊÉÉÈÇÅ¿½¹·´¯­ª§¦§§¥Ÿ–‚qiikjkqtlh+ + + + + + + +  +    "#$%%$#'‹Ž‹ŒŠ‡†ˆ†…ˆE  + + + + + + + d•Ž‹‡jbeghllmjjx‹™¢©°µ¶·º»½¾¾»»½¾¼¹¹¸·¸¶µ¶·¸¸·µ···¹¹¹¹¹¸¹¸·¸¸¸···¶µ³³±¯ª§¢žš˜•‘‘‘’”œ–*%,)\Ä¿ÀÄÃÃÇ\)//}ÐÈÅɼE.MÃÌ«312:>;5.6^¡ÍÇÄÅÄÄÄÃÃÁÀ¾¼¼»ºº¹»»¼¾½ÀÄÅÇËÌËËÍÍÌÍËÊÉÉÈÈÉÉÉÊÈÆÂ¿»¸µ³±®«ª©¨¨§¥ ›xmjkkknuvml/ + + + + + + + + + + + +  + + +  !"#$$$%&&ˆ‰ŠŠ‰‡…‡‡„ƒC  + + + + + + + + + + + + k•Žˆodfgikklkjp} §¯´¶¸º¼½¾½»¼½½»»º¹¸¸·µµ¸¹¹¹¸¹º»¼½½¼½¼½¼»»»»º»¼»¹·¶µ´°®«¦¡ž™••–•‘œa%$**•Áº¾¾¿Çy076,nÉÈÃÇ]6?±È»H6G«º·³–b-3vÊÉÆÇÈÇÇÇÆÆÆÅÃÁÂÂÁÁÂÄÅÅÈÊÊËÍÍËÊÌËÊËÊÉÈÈÇÇÆÆÇÆÄÀ»·³°­ª©©§¨§¨¨¤¡œ“‚qlilklqwxnk1 + + + + + + + + + + + +  +  +"#$#$%&&‰‰‰Šˆ†„…„ƒ„? + + + + + + + + o–‘މ‚qcceggijjiiuˆ˜£­²µ¸¼¾À¿À¿¿¿¾¼»»º¸¶´³µ¸ºº¹»½¿¿¾¿ÁÁÁÀ¿¾¿¿¿ÀÀ¿¿½»º¸¸¸¶³²¯ª¦£ž›š˜–‘/!&!L´²¶¸»Ä2622,kÌÄÉn36›ÆÅ[1A¹ËÈÉÍÎ’8+eÉÉÇÉÈÉÉÈÉÉÉÈÇÈÉÈÇÈÉÊÊÊÊÌÌÌÌÌÊÊÊÊÊÉÈÉÇÆÅÃÂÁ¿»·±®ª¥¥¥¦¢¢¤¥¨¨¥Ÿš‘{mllnlnt{|pp5 + + + + + + + + + + + + + + +  #$##$%$'ˆˆ‡‡‡„ƒ„ƒ‚B  + + + + + u—“‘Š„vbccddfggigk~’Ÿ¨¯´º½¾¿¿ÀÁÂÁÀ¾¿½½º·´´¶¹»¼ºº¼¾¿¿ÀÁÁÀÀ¿¿¿¿ÁÂÁÀ¿¿¼½¼»¼¼º¹¶²¬¨£ ž˜œj.r!†¯¬²³¾-1RR10lÈˇ23ˆÇÈo25¢ËÅÅÄÅÐ6.ƒÏÆÇÉÉÉÉÉÊËÊÉÉÊÈÇÉÊËÊÉÊËËËÊËÉÉÊÊÉÉÇÆÂÀ½»»ºµ±­©¦¥¢Ÿ   ¢¤¦¨§¦Ÿ•ˆsonnnlou}|ot= + + + + + + + + + + + + +  +  !!"#&%%‡††…„‚ƒ€}}: + + + + + + + !™”‘‹†{ecc`adffeeiu‰™£«±¸»¼¿ÀÀÂÄÄÃÁ¿¾¼¸··¹¼¼¼½¼ºº½½¾¿¿ÀÁÁÁÁÁÀÁÂÁÀÀÁ¿¿¾½½½½»¸µ³®¨¤ ™—7XžEA§£§¬´)+^¬<4-xÏ–32qÉɆ03‡ÎÅÇÆÄÃÅY1E¼ÉÇÈÉÈÈÈÉÈÈÇÈÈÈÇÇÈÈÉÈÈÊÊÊÉÉÉÇÇÆÆÅ¿»¸·´²³­¦£ ŸŸŸœ››ž¡¤§§¦¥›“}npoonmpy€{qx? + + + + + + + + + + + + + + + +   "#$%%%……†ƒ~~|}4  + + + + + + + + %…™–‹…{hcc_`ccdeegk|ž¦¬²¶º¾¿ÁÃÃÃÃÁÀ½¹¶¶¸»¼½¿¾»»¼¼»¾¿ÀÂÂÂÂÁ¿ÀÁÂÂÂÁÁÂÁÀ¾½½¼»¶´³¯ª¥št!‚w}¢¤¬Š((RÑ/4,ª72_ÇÆœ56iÍÄÅÃÂÀÈs44™ÌÆÇÈÈÇÇÇÇÇÆÆÇÇÆÆÆÇÇÇÇÆÈÈÆÇÇÄÂÁÀ½¸µµ²²°­«¥Ÿœœ™š™™šœŸ£¦¦¥ ˜soqrrpnr…}szE + + + + + + + + + +   "$%$$$…„„‚ƒ€~}z|8   + + + &†–”†|hcba`abcdfgem|–¢§¯³·¹¼¾Á¿¾¾¾½»¹¶¶¸¹·ºº¹¹ºººº»½¾¾¾ÀÁÁÁÀÀÂÁÀ¿¿¿¿¿½»½»º¶³±­¬¥EO”ŠŽE>˜•š ƒ%#I±¶r'-1|;2N¼À¨?7PÀÁÀ¿ÁÃÇb21”ÉÄÅÆÆÅÅÅÆÆÆÅÆÆÅÄÅÅÅÄÄÄÃÃþ¾º·´²®®«©¦§¤œ™–•”’’‘”—šŸ£¦¥¢š“€lpssrppv‚ˆ|v|P + + + + + + + + + + + + + + + + +!#$$$%‚„ƒ€~~|}|{yz= + + + + + + + 'ŠŸ›–މ‚kcbb`_``cefedn†›¥¬²µ¹»½¾¼º¸¸¶¶··¶´³³´µ²´µ´µ¶·ºº¹ºº»¾¾½½¼½»¹¸·µµ²°®­¬«©§¦¤žž‡azvtYlЇv; Ÿ¦B %%*,<¯»±A2=´¾½½ÀÊŒ67>¬Á¾¾¾¾À¾¾ÀÀÀÀÁÀ¿¿¿À¿¾¾¾½½¼½¼¸µ²¯¬¨¥¡ Ÿœš•‹Šˆˆ‰Š‘•›Ÿ¢¤ œ“…roqsrqpr|†Š}z€Z + + + + + + + + + + + + + + + + +  !!"%%&‚„€~|{zz|{xz? + + + + + + +  +ŽŸš–’‹ofdb``abbdefghwŒŸ§­±¶»»»¼¹¶µ³³µ·¶³±­®­­­­­­¯±²³³¶¶¶·¸¸·³°°°¯«©¨¤¢¡žœ››˜–Žˆ‘D*|y~m-“’Ÿˆ# ##%1¥´±E-.¢¾¸»²0-&c³¯³±±³³³´³²±²³³²²²³³³²²°°¯­©¤¡Ÿžœœ˜—•’Š…ƒ~€ƒ†‰”™Ÿ¤¦¦£™Švmortrqqu€‹‹}~…d + + + + + + + +  + +  "%%%€}}|z{{zwv< + + + + + + + + 2‘𗓆thfdba`abdefhej“¢¨¯³¸¹¹¹¸¸¸¶¶¸¸¸´±®¨§¥¤£¥§©§©««««©«««§¢¢¡Ÿžœš™•’Š‹Šˆ……~pKyte*„ƒ„’c#Š¡ =^sfT80„–‘“—™››œœž £¢¡£¢£¥¤¤£¡ Ÿžš——”‹ˆ…„{{}€‚„‹•šŸ§««¨¢”‚qnprsrqsx„ŽŠ{€†g + + + + +  + +   #$$&}}~€|{z{xywuC + + + + + +  2’ž›˜’ˆ{jjhda__abfehhfr‰¦°´·¸¹¹º»½¼¼½½¾»¶²ª¤¡žœžœ›ž ¢¡ Ÿ  ž—ŒŠ‹†‡„ƒ{|{vxzussrxB$T[]\]^^L!Zja nsrsx?]rx1">gonsz{ƒ††‹‹Ž‘‘“”—™›žœœœš—”‘Ž‹ˆ…~}{|z{€ƒ‡”—ž¢¦­®¬¦šŠtmorssrru|‡”‹}ƒ…d + + + + +  + + !###'}}~}|zxwuusuE + + + + 2”Ÿš“‰{mllgcb`abachiho|‘¡¬²¶¸»¼½¿¿¾ÀÁÀÀ¿º´°©¢›š˜™š››››˜–“‹{yyywvtttrrqoolkje<59[kiiffffj]EC@\e@9:_iihhkI+*)Vfg?,1449AIS_hegjmqru{|y{z|€€„‰“—šœš˜–‘ŽŠ†„~{yz|}~„Š˜›Ÿ¤©¬¯®©¡“|mnpttssrv€Ž•‡€‡Œh + + + + + + +  + + + !##$'{|}|{yxturquP + + + + + + + 7— ž›–Šqqnleb_abcceikio‡œ§®´¶º½¿ÀÀÁÃľºµ±«¦¥¡ž›˜˜™š˜™˜”‘Œ„zvtutsrqrqnmmkhgea^]``^]^[^]^``aaa`^^_`cadeddcfcbb```_^`baaaababdfiknoqtutuuwzzz„Š‘•šœœœš—”‘ŽŠ‡„€~|zz|~‡Ž’™ž¡¦ª«°°­¦›smpqtuussy‡””„‚Šo + + + + + + + +  +  #$$&|zy{ywttssorR +  + + + + + + + + + + 9›¡ ˜’‹ƒvtumhea`bdcehjhiyœ§¯³·½¿ÀÀÃÄÃÂÿ½¹µ¯­¬¨¡ž›š›œ›™—“Œ„}wrrsqoponjijffggecdddefecddefgeddcbdcddcdedccabb````^^]^`^^_aaddeilmoopqqruwy{}€ƒ…ˆ–ššœš—–‘ŽŠ‰ˆ†‚~{|}}„ˆ‘”™ž£¦«­¯²¯­£”~lnpsstuuv–”„…Ž’u  + +   +!#%%{{z}vvtssrpmI  + + + + + + >¢¡˜“Œ†zuwrnhdabdcehiihmƒ”œ¦®µ»¾ÁÂÄÅÄÂÂÀÀ¿¾¹³¯¬§£¡Ÿž›™˜–’І€{wvrppommlnmmmoononnmmopmlmnmjjjhfghhhhghifefeddedbab```a`bccdfdejnpomoprsw{}~€ƒ‡Š”›žœ˜•“‹ˆ†…ƒ~}|~‚‰‘—œ ¥ª«®¯³²°¬žŒomprtutuvyƒ’™•‡’–x + + + + + + + + + +   +0!$%&yyzyuvssqpnnP  + + + + + BŸ¡¢ž™”‰}u{yrmgcbcdegjjlku…–¢©±·»ÀÂÄÂÂÁÀÀÁÂÁ»·´°¬¨¥££¡šš–’Œˆƒ|zwttrtvwxwwvvwvvuvtrqsrqpqqpponmmmnlmkjjikihghhgfddcccdcdeefghjmoponqsw{~‚ƒ†‹ŽŽ•š¡¡žœ˜”‰‡…‚€|}}„„Š”™ž£§ª¯±°±´²®¥–zkoqstuuuv~Š—œ”‰•˜y + + + + + + + +  * "#&&wxwvttrrollkW + + + +  J¡¢£ž›•މ€uz{uqleabcdfijkkm{™£¬³¸¼¿¿ÀÀÁÀÀÃý¹·³°«©¥¢ Ÿž›—”“ŒˆŠ‡‡„€€€„‡ˆ……‚‚‚€~|{yzxwxwuuttttrrrssrqnmmonmlllkihhhhiiikikmnpqsusrvz‚ˆ‰‘““•˜š›Ÿ¢¢ ™–‘ŽŠˆ†ƒ~|€‚†‰Ž–œŸ£¨«®±´´´´²«‰qmosutuvvz‚‘›Š“—šz + + + + + & "#&()ywttrrronlkhb + + +  N¢¢£Ÿ›•‹„vw|yuqkcbccegikkkr‘§®´¹»¾ÀÀÁÂÂÃÄÄ¿½º¶²®¬§¤¡ œ˜–”“‘“Ž“šŸœœ™˜™—•‘‹‹Š…„‚~}}|zzz{|zwvuvuvvuqsqqppnpqosttwy{ƒƒ„†ˆ–———˜›œžžŸ¢£¢Ÿœ˜“Žˆ…„ƒ€}{~‚†Š•›¡¦©«¯²µ¶··³­£‘tnoptvvwxw|ˆ˜œ‹Œ–˜{ + + + + + + + + + +  !' #&*)*ttrsrpmllkjfd + + +   P¦£¥¡›•ˆxx~|ysngabbdghjlmox‹— ª¯µ¹»½ÀÂÃÃÄÅÅÁÀ¼¹¶±®ª¦ œ™–””“‘”••˜™£ª±´´²±­®ªª§¥¥¦¤¦¥¤¡ž›œ™˜•””’‘“‘Žˆ†…ƒ‚€€€~~€‚ƒ‚‚„††‡ŠŽ‘™Ÿ¡¡ ¢£¤¤¥¦£¢£¥¤ œ—“Žˆ„~~|}ƒ‡Œ‘—œ ¦©¬¯³¶¹¸¹¸±§œ„lnqswvwyzzƒ›Ÿš‡™›› + + + + + + +  #'#&*,-.trsqonljjihec + + + +   N§¦¨¥ž˜“‰}v|~zwqkdaccdgjkkmr}™ ª²·º½ÁÄÃÄÆÇÅÃÃÀ¾»·²®¨¢™–“’“‘’“’—ž¤©±·»»»»¸³±±´µµ¶··¸·´²±°°®¬¬ª§¥¦§¦¥¢ž›š™—•”””•–“’’–—™šš™™š›œ›› ¢¢¥©««ªª«­­¬­«©¨¦¦¡™‘‰„||}~ƒ†‹’˜£§ª­±µ¸¹¹··³«ŸŠtmoquwuvyz‰•ž¡–„‘™›  + + + +$&!'*-.0sqrpmlkhhgddd + + + + + +  M¨¦¨¥ œ–‘‹€x|~~{umhbcbefijjlpvƒ˜¢¬´º½ÀÃÄÅÇÈÅÄÄÄ¿º¸µ¯¨¢˜”“‘“•žª±·¼¿À¼»¸¶º¼¾ÀÁ¿ÁÃÄÃÃÃÃÀ¾¾¼½¼º¶¶¸·¶¶²±®­«ª©§¦§©¨¦¦§¨©­¯±³¯°°¯¬¬­¯±²³³²±²²´´³²¯¬¨¨¥ –Š„€~~|}~ƒ†‹“™¤©¬®²¶¹ººº¶²­£–ynopsvwwwy|‚œ¢¢‘†•œœž + + + + + + + + +  &&"$+/02pnqmmkffffcac( + + + + + + + +   M§¦§¤ ž™•…y|~€{tneabdfgjjkou{‡”ž¦®¶»½ÂÆÆÇÇÄÄÇÆÅÃÀ½»µ°¬¦¢›™•’“™¡¨±º¿À½»»¾±±ª§§¥¥ž—˜—–”—¥·ÂÄÀ½¾¿¿¿¿¼»¼¼»º¸··¸º¸·¶¹¹½¸ª˜‡„Œ•£´¿½»»»»½½ºº»»»¹¸·³°®¬¥œ‘Š…€~}„…‰”˜Ÿ¤¨­´¶·»¼¼»¹´°§œ†nmpsvywvy|‰– §£‹†–Ÿ ¡~ + + + + + + + + +  +)(#!#(/45mmlhkjgeddcb`3  + + + + + +    S¤¨©¦¡™–’Šzy€ƒ€}xslb`bdfgjlnqv~Š“¥®¶»¿ÅÆÇÆÅÆÈÈÈÇÅÁÂÀ¼¸³¯«¨¦£¥ª²¹¼ÁÀ¿½»»¾¼RA=;;<;98<7476=K\¯ÆÄÀÀÀÀ¾¼½¾¾¾¾¾½½½¼»ÀÁ¨~X?320,.39KpžÂÇÀÀÂÃÄÃÂÁ¿½¼º¸¶´¯¥™„~}€†‹”™ ¦ª®³¸º¼¾¾½»¹²¬ tmmpuxywv{„›¤§¡ˆ‹™¡¡£z + + + + + +  +((&!#*468jliihhfccc`\[9 + + +   W¨ªªª¥Ÿ›™•‹~w€ƒ€{tpgba`cgijnoru}ˆ“𤝵¼ÁÃÆÆÅÆÈÉÉÈÇÇÈÇÄ¿»¹¶µ´µ¶ºÀ¿¾¼º¹¹¹»±?9:99::8;:6779;;5.@z»Æ¿½¾½¼½¾½¾¿ÀÀ¾½¿Á¢f:+01/430111,*7VŒ½ÆÀÁÁÀÀ¾½¼»¸·µ²ªœˆ€ƒŠ‘—›£§­±¶º½¿ÀÀÀ¾º¶­¡–}lnptwzxxyƒ‰–¡©©›ƒœ¡¡¢w + + + + + + +  +%)'"!!%17:jhffgdd`a_[WU= + + + + + [§©¬«§¡š–Žƒw~ƒ‚}wtjdb_`eginnprwŽ– ª³»¾ÂÄÄÃÄÅÈÉÈÇÈÉÉÈÆÅ¾¿¼¼½¾¿½»ºº¹¸¸¸»§63446776546::978874/JœÇ¼¼¼¼¼¼½½¾¿½ºÃµo6)021../210013793/M›Ãº¸¸¶´²°¯­¬ª¤›ˆ„„ƒ‚…’•𠦬³µ¸»¾ÀÁÀÀ¾º´®¥•„topsvzxwz}€ˆœ¦«©•’Ÿ£££x + + + + + + + + + + + + + ((% "%.6;gdcda``\XXVRQD + + + + + ]¨©¬«§¤¡›•’†y~ƒ„ƒ€|wrhb_^`eilnnqtz„™£ª´¸¾ÂÂÂÄÆÉÊÉÊÊËËÊÉÇÅÄÅÄÂÁÀ½¼»º»º··¸º0...0.0012/41,/3345731‰Æº»¼ºº¼½»»¸Á¥L///00010//./0/04421+1y»·²±­«¨¤¢žœ–†ƒƒƒ‚‚…‹‘”™Ÿ¥¬¯´¹½¾¿Á¿½¼¹µ¯©›‰wonrsy|yxz…‹–¡ª¬©‘ƒ–¤¦¤§p + +  %R6%' !$%*17aa`_]^]WVVSRPJ + + + +  cª©¬¬ª§¢Ÿš“ˆ}}„………zunfa^^aejlloru{„–Ÿª·¼ÀÁÂÆÉÊËËÌÎÍÌËÊÊÊÉÉÇÅÃÀ¿¾½½¼¹¶¶µµ•+)))$PŒŒ‘‘„pJ.(10/311’ø¹º»»¼»¹ÂŸ?0111/00),044.++2530...(k¸±®ª§£ š•‹ˆƒ€€‚…‰”šž£©¯´¹½¾¿¿¿¾½º¶±ª‹{opqtw{{{z}„‰œ¦®­¨‰‡š¤¦¥¥j  + + + 3M3$$"%%%,3___\[XXSSUQPLE + + +  c¬©­®¬¨£Ÿ›–Œ~ƒ‡‡†‚}xslea_``ehjloqv{…‹•Ÿ®·½ÀÄÈÊÊËËÍÎÍÌÌÌËÌËÈÇÈÆÃÀ¿¾¾¹·µ±¯®'&$%q³®±²µ²²´²“K&-,..1;§»µ¹¹¹»ºÂ¥=23/11,);]™ ¡šŒmG--0,,.,"q¯£¢ œ˜”ˆ„‚€‚…ˆŽ”›Ÿ¤ª°µº¿¿ÀÁÁÀ¾¼¹³«¢‘soqswz|{{|€‡Ž• ª¯®¤‰ž¦¨¦¨f + + + + + + + + + + + + +#(1G2$%!$%'-1]\[XWVTRQPMLHB + + + + +  e­«®°®ª¥ ˜‚~ƒŠŒ‡†‚|uphebb`bfhjlnsu{„Š‘ž«¹½ÃÈÊÊËËËÊÌÌÌÌËÊÊÈÈÊÈÆÃ¿¼¹µ³²°®¯Ž#$#$ p²­¯°±°®­²ºµc'-,--'Y¸´µ¶¶¸º¹L010.0++k§½¿ºº½¿¿Ã´:&,)&##$…¢™™•‘Їƒ€‚„†‰Ž•›¡¥©®´¸¾¿ÀÂÃÃÁ¾»·­¦›‡vopquy||||„Š‘™¥­±®˜Ž¢©¨¥¨^ + + + + + + + + + + + + &)*-@/!#"$%+1ZXVRRSROMLHDEB! + + +  j««°°¯¬§¢žœ“‡€‰Œ‹ˆ†€xsmhfdcbahikmqruy€†Ž™¬·¼ÁÄÆÇÉÊÊÊÊÉÊÊÉÊÊÈÊÉǾ»¹¶µ´±®²#$#%#t´­°³µ³°°´´·»]'.+,+-“º³³²³¼q,/-,.(5‹»¶´´¶¸ºººº»Ä«P"&!"?˜““ŒŠˆ†…ƒ„…†‰Ž•¡¦«¯³¸¼ÀÁÁÃÄ¿»·²ª|ooruxz|{|~ƒ‡Œ•Ÿ©°±«“£©¨§§W + + + + + + + + + + &())/A,"$!%%)0VSQONOLJIIFEC? + + + + +  fª¬±²°®¨£ ž–ˆ€ˆŒŒ‰‚~wpmifcbaaeimoostzƒˆš©´¼ÀÃÅÇÈÊÊÉÊÉÈÈÉÉÈÈÈÅÂÀ½º·¶¶²±²Œ$&#$#p³­±³´³´´µ¶´·­>'++,&]º±²±´Ÿ4**)+(5𷬮°±²´´³´´´²¶±Lz•Œ‹‰†…„…‡†ˆŠ”œ¡¥«¯³¹½ÁÂÃÄÄľº±ªžrmoswy}}||€…‹’›¤®±±¨†–¤¨¨§¥S  + + + +  + + + ((()(+7*$ $%'/SSOMLKLIFEDC?=& +  k«¬²³±®«§£Ÿ™Š€†ŒŽŽˆ„|vrkeddcaafklnqru}ƒŠ¬¶º½ÀÃÆÈÉÉÊÊÉÈÈÈÈÈÇÆÆÃÀ½¸¶µ´±³Ž$%$%$q²®²³´³´´³µµ³¹„'-+,*7§´²±·j#)(')&†¶«®¯¯°±±°°°°±®©ª›1P‘‰‡‡…„ƒ†‰Š”𡦩­²·»¿ÂÅÅÅÅÃÀ»µ«ž“ƒspoqvy{~~|~‚‰— ©¯±°¡}„›¥ªª©§N  + + + + +  ))()))+1*"!"'/QOMJJFGECAAA><. + +  p«­²²°¯­ª¦ œ€€ˆ‘‘Ž‹ˆ‚|wqiggfbbbeimpqsuzˆŽ—£­²¹¾ÂÄÆÈÈÉÉÈÇÇÈÈÇÈÉÉļ¹¶µ±³Œ$%$%#t³®°²³´´µ´³³²³®;++*+*‰¶®±­@"&&&!X°««¬¬­°±°¯¯®¬«§¤Ÿ£g4‡…„†…„…ˆŠ‹‘–š ¦ª¯¶¹¼¿ÂÅÇÅÆÂ¿»µ¬Ÿ•voprux{}~~|…Œ”œ¤­±´¯—|Œž§ª«§£E  + + + + + + *)(()((*3*#!#-MIGGHED@?@?=>:/ + + + +  r¨¬²³±°­«§£Ÿ˜‡‡Œ‘’Š…€{vojggeda`fhlnorsy~~‡‘¡­³¸¼ÀÃÅÅÇÈÈÇÈÉÈÇÇÈÉÇÅ¿½ºµµŒ$&&(%w¶±²µ¶¶·¹¸¶µµ´»Z$))+&m¹¯´—+&$$$+”°ª¬®¯±²±°¯®¬ª¨¤£Ÿš‰''€………„†ˆŠŽ‘”£¦¬±µ»ÀÃÅÆÇÇÅÄ¿»¶­¥•„uonqtwy}~~}~ƒŠ– ©¯³²«Ž‘¢«®¬¦=  + + + + + #)()(()))*/-" ".GFFDBAA?>>=<;73 + + + + +    sª«²´²¯««©¥ šŒ‚‚‹’“Œˆ†ztnihhfcbbegjmqsuv|€…‘¢«³¸½ÂÄÆÆÆÈÈÈÉÉÈÈÈÆÈÇÆÄ¿¹ºŽ%()*'~¹²²¶··¸»»¶¶µ³¹n())+'^¹²·~$'#%$Gªª«®®¯±°®­¬¬ª¨¥¢ ™‘’;'|‡ˆ‡ˆ‰‹Ž‘•œ¡§¬´¹½ÁÆÈÊÊÉÇÇÀ¼º²§™†wnmptx{}~}}‡Œ“›¤­²µ²¦…–¨¯¯¬§›; + + + + + + + #')(((*((''2- 'GCAA?;<<=;<;876 + + + +     s­¬²´´±­«ª¦£œ…‚Š“”’‘Œ‡„yrmjihgebcejmprruz|€„‘ «²·½ÀÂÅÆÅÅÆÆÇÇÈÉÉÉÇÅÄ¿¼¿(-+,+‚¸³µ···¸¸·¶´³²·z&)')'U±«±j $#%!_«¥©©©ªªªªª§¦¤¢¡™•‘“G%}Š‹‰ŒŽ”˜œ£§¬³º½ÂÇÈÉÉÈÆÃ¿»·°§›‹|tkortx{~~~~„‹— ©±¶¶±Ÿ~‡›©®®¬¨›4  + + + + + + $(('(')('''(0-$DB>=<988987875/ + + + + + + +   oª«²µ´³°­©§¦ –‡„Š’••”’‰†‚~wrnkjihfdegimppruxz{Š›¨­´¹½ÀÂÄÄÅÅÆÆÈÉÉÈÆÄÄÃÁ¾Å‰-/+-.Џµ¶··¸·µµ¶¶´²¹%)&(%M®¨°W!"% o©¤¨¨©©ª©ª©¦¤¢¡Ÿš–”Ž“I+…Ž‘•—› ¥«¯·½¿ÂÆÉÈÈÇÄÄÁ½¸±¨Œ}ummptw{~€~~‡Ž”›£®³·´¯˜|Œ©­®­©˜, + + +  + + + + + &('''%&&&'''(/+A?<<;999844543- + + + + + {©¬±´´³±®«¨©¦™‰„Š’˜˜˜—”‰†‚{upnllkjhgfgkmpqqtvxz„•¡«±·¼ÂÄÄÄÅÅÅÆÇÇÆÅÄÄÄÃÂÈ|-210/–¾¸¹¸¸¹¹¹¸¶¶µ²ºs%&$&#P¬¤ªP #"«§©«©««ª©¨¥£¢¡ž›–“’E3’“••˜œŸ£¦¬±¶¾ÁÃÅÇÈÈÇÅÂÁ¿»´«¢’ulmrtv{}~€€€„Š’™ §°¶¸³©‡{’ ª¯¯¯©' + + + + + + + + +(''&&'&&'&$%&%/0<>>=9866543420/ + + +    0ާ®²³´³°¯­ª©§„ˆ•˜šš—’Œ‰„ztollllljgghknqqrtuwyzœ¦±¼¾ÁÁÂÃÃÃÄÄÅÆÄÆÆÆÄÃÉo59:7:¦Áººººº»º¹·¶´±·Z!$#% Z¬¤ªQ!xª¦§§¨©©©§¥¤¤¢Ÿ™–“‘“=?˜•—™ž¡¥¨­³¹¿ÄÅÇÉÉÉÈÆÅÁ½º´¬¦—‡yrmpuwx|~€€‡Ž—ž¤«²¶·³¢‚‚–¥­°²°©Ž"  + + + + + (''''&&&&&%%%$&+-::::754420//.-) + + + + + + + +   T›¡­³µµ´±°®«©¨¢‘……‹’•˜˜—•’Œ…‚~ytommmnmkhiilpqrsttvxy~‰—¢¯¶»¾¿ÁÁÃÃÃÃÃÅÇÆÆÃÁÅ_89>=H²¾»º¹¹¸¸·¸¹µ°¯®?$$"$i«£«Zd©¤¦¦¦¤¥¨§¦¥£Ÿœ›˜–”—Š(Wœ—œŸ£¦«¯²·¿ÆÈÉÊÊÊÈÈÅÃÀ¼³«£—†zrnpuxx|€€„‹•œ¢©¯¶¸¶²—|†š¨¯²²¯ª…  + + + + + + + + !('&'''&%%%%%%$$$-0776753210.-,++* + +  {¡ ¬³µ´³²°¯®«©¢•Š…“•˜™š™•‘‹†ƒ}xsppnmnolkkknprsstuusuz‰™¦´º½¾¾ÀÁÂÀÀÅÅÄÃüO:9TgtthR4-42.00.‡®‘‡:"fmi'axw~…‹•–œ ¤§©ª«ª««¬°µ¼ÀÂÁ¿¼»»¼¶©”ˆ†‰’Ÿ©®°²´´µ¸¶°£“¨±´´¯¥¦¥“y- + + + + + + + + +  33-,,,++*+*+*)*)))++**)(()&(( + + +  + + +  =³ÈÊÍÉÅÉÈĽ®—™¡©°´·¸¸¸·¶··¸¹¹¶­ž”Œ‰‡†‚|wsonpruvx+,F=†ƒ‚) %*/3À»¼ÀÁÃÂÃÆÆÆÆĄ̈F4/..-.129ŸÌÈÊÊÊËÊÉÊÊÊÉÉÉÊÉÎÇ‚F523/*3./3A­º¸··¶³³²°®¬«©§¦¦¥¢ž›—“‘‹Œl H˜”w!!ƒ¡ ¢£¤¢¢  Ÿ  Ÿ¢¦®´¹½ÁÃÀ¼»½½¸¬šŒŠ•ž¨¯³²²µ·¹º·³ª–©°³µ¶·¶¯©²¯£F  + ##$##%,,,/0.164.-,+********++*)))**(((+.0/4956:758;89=@9:;7 B±ÇÎÏÍÉÊÊÆÂ½»¹§—“˜£«±¶¸¸¹¸·¸¹»½¼ºµ®£›’‘˜š˜”‹†„‚€{x(/rrkW}{y%/ššœ ££¤¨œ3$&%'%c·±³µ¹¢1,.,14ƾÁÁÁÁÀ¿Å21,+0T·´µ´²±°°±¯®¬°®®®¬¨¤žš“‘‡†‹‘z$<”“%d¥¢¤¤¤£¡  ŸžŸ” ®°·»½Á¾¼¾½»³¤”ŽŒ‹Ž–£¬±±±²µ¹º»¹²¦‘’ ª²µ¶¶··µ«®¯¥‘…f + + + +$##$$&-/.11-/172-*-+*****,*)()*))***(),1-.11011345578<<==<5 V½ÇÎÎËÈÊÊÆÁ¾¼¸©š“™¢©®²¶¸º»º¸¸¹¼½¼¶°©Ÿ•Ž”›š—•’‹†…ƒ~}/'suyM#r}|}**„”•“”˜™›¤b  1›¯¬­®®³S&*(+*_¿¹ºº¼¾¾ºÁ}**)(*]µ¯°®®­¬¬«ª¨©s`b_[USKC<81`‘’‚)0Œ•Œ,:¢§¥¤¢ œœœœ JV­¸¶¼¿ÂÀ¼½¾½º¬™Œ‹Œ“ž¨°±±±³¶¹ºº¶¯ •£­´µµ¶¸¹¸­­°©™…z, + + %$$%%)-0/00-,-26.+,,**+**)**(**))))),+-../00//3355565799:95 + vÃÇÎÎËÉÌÉÄÀ¾½º¬”–¡©®²³·º¼¼ººº»¼¼¸°§¢›“‘–››™–’†„„…3"mzvu)E€…‰0*…Ž‘’‹,X¢¢¦§¨§®s$#'&5¡¸±±³¶¸¸¼r%%%#$T®¨¨§¦¦¤¤¢¢¢¡C!Q’‹/(„““@ f«¡¡¡ž››››˜¤w'$R«¿ºÁÀ½»¾¾¼³¤“ŽŽ˜£«±²²±³¶¸»¹³­œ‘—£­´µ¶¸¹¹¸°«¯­¡ˆ€Q + + + &$$%&*///0/-,,-45,++++++++**)))()*)*3/000.10038766777596987! &˜ÃÉÏÏËËËÇÃÁ¾¼º¯ –•ž¦¬°´¶¸»¼º¹º¼¼¼º´«¢–’’˜š›™—•ކ‡‡2!o~{}\f‚ŠŽ3+‡ŽŒ‹ŒŒ‘\$|”—›žŸžž’* #j±§ªª«ª«±m""C¥ žžœšš˜™œ›> T‹Œ’4!}‘•_ '~ª Ÿ››š™— ”1"'$D¾¿¾º¼À¾¹¬˜ŽŒ“§¯±±±²´·¸¹·±¨—˜¥­³·¶¸¸¸¸°¨¯±©’€r + + "&$$&(,//0/.--,-.72++++)*(*))))))))**6324301128;667:88:=8:9:( + =¯ÄËÐÐÌËÊÆÃÀ¾½»²£˜”œ£ª¯´¶¸¹º¹¸¸»½¼¼º°¤›—’“•˜šœš˜•‹‹8"sƒ€~32„‡Œ‘1*„‹ˆ†…ˆ…25‚”˜œŸ¡šžQ2™¢¢¢ ¡¡¤o/Ž“’‘Ž‘–˜“7_ŒŒ’: y’•„'#.~§žšš™– œ='&(&~û¸¹¿Á½¶¥“Ž˜¢«±²±±³µ·¹¹µ¯¥–‘𥮳¶·¶¹¹¸´«°³¬œ€„5 %''&&),...-----,,091,,.,*)**())))()++<8798549<9:6::<<=AB?@A@7 +  f¹ÄÍÏÎÌÍËÄÂÀ¾¾¼´¥™•›¡§®²¶¸¹¹¹¹¸»¾¾¾»³©—“‘‘’•˜š›š—•’’:!x‹‡ƒŠa^‘“–‘0(€Ž‰‡ˆ‡Žd(6@JOT[`cIh¡—–—™˜›z]‡‚„…‡ˆ‘”–’1_ŽŒ7 v–•žK! )qœ¡›ž¡‹>&%&%9®¼µ¶»Á¾¸­š”𦮲²²²´¶¹º¹¶­£’‘œ§®²µ¶·¸ºº¶­¯³°£‹ˆ[ ((*))+,///.-,-.-,,27.,,-,++*)))))()**<<=>AB@CEEFEHHGO + 4™ºÄÎÏÍÌÍÊÆÂÀ¾¿¾·¦›“𢍮²³¶¹¹¸¸¸º¼¾¾¼¸®£™–““–˜˜™—–’4&}Š‹‡>‡™——˜‰&#yŒ‡†‡Š‹6-‹‘І}.,{‚ƒ‰”••H.--/-$fŒ‰†3"{“—Ÿ+ FrxZ&#"$$²²¸ÀÁ½°ŽŽ‘— ª°³³²²´¸»½»·¬ Ž’žª°³´´·¸º¹·®®²±©•ˆy  ()*+,......--,-..-.22,,,+-+****)*)()(;<DFEEFKLLJMNNOKNLMILR]C  .œ³½ÎÐÎÌÍÌÈÄÂÀÀÀÀ»®¡’“Ÿ§­°µ¸ºº¹¸¹º»¼¾½ºµ­¢™•“‘‘’“““”•••tH'-†“˜›šš™™~k‚‚ƒ‡?#xƒ€{{zkJ‚‚‡ŠŠ‰Š’•™˜”–tnЇ+&ƒ—› Ÿ¡b ""!!"#T¸¸®±»Á¿¸«›Œ—Ÿ¨±µ´³³µ¸»½¾¸°¤•Ž—¡«¯±´´¶¹¹ºº³ªª°±¤‹‹k + *()((&).0/.-././12:82270,++++++++*))*+BBFIJJKKNOQLNOOPNOMLOQPSD \¹°ÁÍÏÌËÎÌÆÃÁÁÁÁÁ½±¤•’𥬲¶¹»»¹ºº»¼¼¼»ºµ°§ž˜”‘“––—•’“••œˆ}—“‘”™œœ›˜–‘{"`„€€„r!'M‚}zx|OG‡ˆŠ‹Œ“•–•”\!u‰†t#+…–œŸšŸV!""$#f½¹¯®µ¿Â¼²¤”ŽŽ”œ¥°³¶´³³µ¹¼¾¾¸­¡”™£ª¯±±³µ¸º»¼·¬ª¯²¬”†ƒ. #*((('(*.0//..////0638Kd…™“‹‹‰ŠŒ‘”šŸ  ›–••–˜› ¢¥¬µ»¿Åľ»¾Â½±£—‘’‘“”›¥¯µ¸·µ³³´·¼ÀÀ¾¶¬Ÿ‘Š™¡ª±´·¸¹»¼½¾¾¾¾¾µ°±³µµ³¤Š‰Y2/+-.++.00.//00/-./1420/...,+,/43.+****+SQRSRQPWWWRQQTVUSRTUWVSUTTW9 + PÁÈÈ¿²¿ËÄÁËÒÌÉËÏÒ×ÒÏÍÎÑÔʸ¡—”“”˜¥­²·»¼¼»»¼¿¿ÀÀ¿¾º´®§¢›˜—šœžŸž›™™˜–’’–˜œœŸ    ŸŸž››ž £§ª°³¯¯­©¦¢—““••““””—šœŸ£¥©­¯°±²³´µµµ¶¶µ´³®®­«¨¢œœ ¡•ŽŒ‹Ž‘‘“•˜˜™šš˜’’“”—𣍬³»ÁÄÄ¿¼½Âý·§™“’‘’‘”™¡¬´¸·¶¶µ´¶º¿ÂÀ¼²¦–Ž‹‘œ¤­°µ·¹º¼½¾¾¾¾½¼·±±³µ·¶«‰q4.,--*).///0/.//../07600.-..,+-/44,)**++[Y]ZT[[^eaZ]YX[YW\XXXW[XYVSG”ÎÈȺ²ÃÈÂÂÌÒËËÏÓØØÒËÌÍÑÔË»¤—”•”•¡«±¶¹¼½½»½¾¿ÀÁÀ¾»¹³¬§¡ž›š™šœŸžžœš™™”Ž“—œ £¥§¦¥¦£¢¢££§§¤¤¦¨«­¬­¬§¢™‘‘““•—–™œž¢¦ª®²¶¸¸¸¹º¹º¼»¼¿¼¸·µ´°­©§¤£ œ˜“Œ‹Œ‹Š‹‹Ž‘“””’ŽŽ’•˜œ¢¨®³ºÀÆÇÄÁ¾¿Âľµ«”’”’‘“–œ¥°·¸¶µµ´µ¹¾ÁÁ¾¸¯ Š‹•Ÿ§®´·¸¹º¼¾½½¼»»»¶°±³µ·¶²™Š‚E20,)++.0/./../../.0C¾ÍÍÌÀ¬¸ÉÆÃÄÌÔÖ×ÕÔÐËÈÇÉÌÖÚÒ“‘‘”ž¨°µº¼½¾¾¾¾¿ÀÁÁ¿¼¸µ¯¨¤¡  Ÿ ¡¡¡¡ ›š™™—••—˜™™šœŸ£§¨¥££¢¥¥¥¥¢Ÿ›—”ŽŒŽ•—¢¦¬°¸½¿ÂÄÅÆÅÇÇÆÅÄÂÂÂÀ¾½¼º»¼½¾»·´¯¨£œ’ˆ…„‚‚‚„„„††…‡ˆˆ‹‘—¢§«°¶¼ÁÅÇÄÀÁÄÆÂ¹±¥™“’•“‘’˜£®´·¶µ³²´¸¾ÂÅÿ¸­Ÿ‘‡…‘§¯²´¶¸º¼½½º¹¹¹¼¼µ±³´·¸¹¸°š‹†M00.,,00//.-,-./-+,-/0372..--,*,-051-)*PRRWPPRSSTX[RPSRSQROLNPRQPPPNƒÐÌÌʹ¯¼ÉÆÂÃÌÔÖÖÕÒÍÇÅÆÇÍ×ÚÓÆ²š”’˜¢ª±¶»½¿¾¾¾¾ÁÁÁ¿½¹·³­©¦£  ¡ ¡¡¡¢ žœœœœœžžž¡¢¢   ŸŸœ˜–“”••”’‘‘’•˜œ¡¦©®²·º¾ÁÂÃÄÄÄÄÃÂÀ¾½¾½»¹·¶µ´´¶·µ³¯ª¢™ˆ„ƒ€€ƒ„ƒ…†††ˆ‰“™¤¨«®´»ÁÅÇÆÃÁÅÇý²¦›””””‘“Ÿª´¶¶¶µ´µ¸½ÁÄÄÀ»µ¨™Š„‡” ¨¯³µ·º½¾½»º¹¹º¼¼´±³µ¸¹ºº²žŠg/31,,11//0/-,...-,,..132/.--+,,-,/3.+*TXSTQQSTWVVXPNRTTQSSPNOPNOQNT0 C»ÌÌËŵ±ÀÉÇÂÂËÓÔÕÕÐËÆÃÄÇÌ×ÛÕȶ–’”¥­´¸º½½¾¿¾¿ÁÁÀ¿¾¼»·³®«§¤¤¢¢££¡ ¡  ŸŸ  žž ¡¢£¢¢¡¡¢¢¢¡ ›˜–••––—˜™š ¡£¥ª¯³¶¸»¾ÀÂÃÃÃÃÁÁ¿¾¼º·¸¶³±¯®®®¬«¨¨¦¡œ–‡€€ƒ…„‚‚…„†‹‘•𤍫¯´ºÀÆÉÇÃÃÄÅÄ¿´§““””’‘—¥°µ¶¶µ´³·½ÁÃÿ¹°¡‘…ƒ‹™¤ª®´µ¹»¼½½»¹¸¸»½¼±°´µ¹º»ºµ¥‹Œ~3*1+,/.////.-,-/-----./01-,,--,,+.030*UXSRSRWXZVVXPOQSTRPQRQQOOQPPY@ }ËÉÌËŲ²ÆËÈÄÀÉÓÕÕÔÐÉÅÃÅÈËÖÜÕʺ¢—“Ž‹˜¡ª¯µ·»½¿¿À¿ÀÀÁÀÀ¾½º¶³­¬«§¥¥¤£   žŸŸ Ÿ  Ÿ¢£¤¦¦¥¤¥¤¤£¢¡Ÿ›šœœ››œ ¢£¦ªª¬°µº½ÁÁÂÂÂÃÂÂÁ¿¾½º·¶³²³°­ªª«ª§¢ ›˜“‡„ƒ‚‚€~ƒ…ˆ’˜›ž¢¦ª¯´»ÁÅÉÈÄÂÆÇž¶©–’“”’• ­´¶µ´´³µ»ÀÁÃÄÁ¿¸«›Š‚„‘Ÿ¨ª®³·º»¼¾¼º¸¸¸»½º±²¶¶¸ºº¹¶ªˆŽF"//-.00.-..,-./.-,+-/02/,+,*+,,./47.TSQQSVTRWSUUPPPQPONPQSSSTUSTYM.«ÊÊÌËÀ¯µÆÊÈÅÁÇÓÕÖÔÏÉÄÂÄÇÍÖÚÕ˼¤–”‘Ž‹Œ’¦¬´¸»¼¾¿¿¿ÁÀÀÁÁÀÁ½¹¶±¯±­©¨§¦¥¢¡žžŸŸ ¢¡¢££¤¥¤¤¤¤£¤£¡¡ŸŸ¡  Ÿ¡¢¤§ª®±´·¼¿ÃÅÅÄÄÂÂÀÀ¿½º¸¶´±¬ª¨¥¤£¢£ œš˜Šˆ…ƒ‚ƒ€}}~‚†ˆ‹’–› £¥¨ª¯¶»ÂÇÉÉÅÂÅÇÄÀµ¨›—”“““Ž“žª²¶µ´³´¶º¿ÂÄÄÃÀ¼µ¥‘…„—£©«®³¸¼¾¾¾»º¹¶¸½½º¯²¶·¸¹¹¹¸®”†Ž_.,,-/.----,--.-,++,.00,*++*,,,,/42RPMRUSPQSTSRSPPONRSOPQRQRTQQQS)eÉÉËÌʼ­·ÆËÈÄÁÅÑÕÖÔÍÇÃÃÄÇÍ×ÛÖÍ¿¨™•’ŒŒ˜¡¨°¶º½¾½¾¾ÀÀÁÁÁÁÁÁ¾º¶µ´±­¬«ª©§¥£ ŸŸŸ ¢¡¡¢¢¢  ¡¢£¥¤¤¢ Ÿ ŸŸ ¢¢¥¥¨ª®±µ¹»ÀÃÄÅÆÆÅÄÃÄÂÁ¿½»·µ³±°®©¦¡žš“’‹„}|}„ƒ}~ƒ‡‘–𢤧©¬°µ»ÁÇÊÊÅÂÄÅü²¨œ”“””“’œ§°´¶´³²µ¹¾ÁÂÄÿ¼²žŒƒ‚Žœ¥«¬­³¹½¿À¿»¹¸·¸½½¹¯µ··º¼¹º¹±žŠŠv$,-,../...,,,-..-*+-/.++**,-,+++.4SPQUTRVVSSTSTQQRQSQOQNQOQROOOQ=(œÇÇÊËÆ¹«ºÆÊÈÄÀÃÎÒÔÒÌÇÄÃÅÇËÔÚ×Ï«œ–•’Œ“›¤«²¶¹¼¾½¾ÀÀÁÁÀÂÂÁ¿»¹¸¶´´²¯­¬¬ª¦¤¡ ¡¡¢¡    Ÿ¡¢££££¢¡ ¢£¡¢¡£¥©¬®²µ¸»¿ÂÄÅÇÇÈÆÅÅÄÃÂÀ½º·´²°®®­«§¢›–‘Žˆ†~~……ƒ€„†‹‘•–™œ £¦¨©¬°³º¿ÆÈÉÅÂÃÅû±ªŸ–““”“ŽŽ—£­³µµ²²´¹¼ÁÂÂÂÁÀ½¹®š‰ƒ‡•¢¨¬¬­²¹½ÀÀ¾¼¹·¶º¾¿¸°³¶·º¼»»ºµ¤Œ…€=).////0..----+++,-.,*,,*,,*,,*,4ƒ‚…ƒ€‚ƒ€€€‚„…‚~|~€€€€€€€€‚ƒ„ƒ€€‚ƒ‚‚‚‚€€€€„†{|}}}~~||}~„ƒ€€~ƒ‚€ƒ‚‚€‚‚€ƒ€€€€‚~€€€€~‚‚}~‚†‚~|~‚~ƒƒ‚‚ƒ‚‚‚€€‚‚€‚ƒ€€‚€€€€€€€‚€€€€€€€€‚‚€€‚‚‚‚ƒƒ‚€€‚ƒ…‚zwy{{zz{zzyx‚ˆ€€€€€€‚‚€€€€€€€‚ƒ‚€€‚ƒ€~€€€€€€€ƒ‚‚„„„}~…~ƒƒ„‚‚ƒ„‚€€€€€ƒƒ„€‚ƒƒ‚€€€€‚‚‚‚‚‚ƒ‚€‚‚€ƒ„ƒ‚‚‚‚‚‚‚‚ƒ‚‚€€‚‚……‚‚€ƒ‡€‚‹ˆ…€€€€€€€€€€€€€€ƒƒ‚€€€€€€€€€ƒ€~…ƒƒ€~ƒ„…………ƒƒ€€ƒ€ƒƒ‚€‚€€€€‚„ƒƒ„„„ƒ€€‚„ƒ‚ƒ„ƒ€‚ƒ€€‚‚€€€‚†…€ƒ„ƒƒ‚€…†ƒ€ƒ‚…†„„ƒ‚€~€€€€€€€€€‚€€‚‚€€€€€€€‚‚€„‚ƒ~€‚€„ƒ‚‚„ƒ‚€„ƒ‚‚‚€‚€€€‚‚‚€‚ƒ„ƒƒ‚€€ƒ‚‚‚€ƒƒ‚‚‚ƒ‚‚€‚ƒ‚€‚ƒƒ‚„‚€ƒ„„ƒƒƒ€€€‚‚wy€…††ƒ‚€€€€€~€‚€€€€€€‚€‚‚‚€€€€~€€€€ƒƒ~€‚ƒ†„‚€€‚ƒƒƒ‚„„ƒ„‚‚‚‚ƒ‚‚‚‚‚‚‚‚‚‚ƒƒ‚‚‚ƒ……ƒ€€‚‚‚ƒ‚‚ƒƒƒƒ‚‚‚‡ƒ‚‚ƒƒ„„‚€~€…†€~z|‰†‚€€‚‚ƒ€‚€€ƒƒ€€€„ƒ€€‚€~~‚€€€‚‚€€€‚„ƒƒƒ€~€€‚ƒƒ„ƒ‚‚‚ƒ„„„ƒƒƒ„ƒƒ††ƒƒ„ƒ‚‚‚ƒƒƒƒ‚‚„‚‚‚ƒ„ƒ‚ƒƒƒ€‚‚‚€€‚‚ƒ‚ƒ‚‚‰„‚ƒ…‰‰……„ƒ†…€„‡ƒ}†…€‚‚€‚‚€ƒƒ‚‚€‚‚‚„„€€€€‚‚‚„‚€€„†„…„€€ƒ‚ƒ…ƒƒƒ‚‚ƒƒ„€„„ƒ„„‚„ƒ€ƒ‚‚ƒƒ‚€€ƒ‚ƒƒƒƒƒ‚ƒ„ƒ‚‚„„‚‚ƒƒ‚‚І„ˆ…ˆ‡…„ƒ€…‚††„€ˆƒ‚„ƒƒƒ‚€€€€€ƒƒƒ‚ƒƒ€€‚‚‚‚€€‚‚‚€‚‚‚ƒ‚‚‚„‚„„€€‚€€€ƒ†„‚ƒƒ†…ƒ‚‚ƒƒƒ‚‚ƒ„ƒ‚‚ƒ‚‚ƒƒƒƒ‚„„ƒ‚ƒƒ„„‚‚ƒ„…„ƒƒ‚ƒ‚‚‰„€€„‰‚‚ƒ€†‚†„‹‚„ƒ‚‚€€€€€€‚‚€ƒ‚‚‚‚‚‚ƒ‚€€€‚‚ƒ~ƒƒ€€‚…„‚€€…„„ƒ„„ƒ‚‚‚‚ƒƒ‚‚………„…‡†‚ƒ‚‚€€‚ƒ‚‚ƒ‚„…ƒƒƒƒ€‚ƒ‚ƒ…„ƒƒƒƒ‚ƒƒ„‚‚ƒƒ‚ƒƒ‚ƒƒ‚‚‰ƒ„‚‚€}‚‚‚‰ƒ€††ƒ„ƒƒ‚€‚‚ƒƒ€€‚‚ƒƒ‚‚‚‚ƒ„ƒƒ‚‚‚‚‚~€†„‚„‡†„ƒ‚€‚ƒƒ‚ƒ‚ƒƒƒ‚€††…†…ƒ‚„‚‚‚‚‚‚‚„ƒƒ„ƒƒ‚€‚„„ƒƒ‚‚ƒ„…„ƒƒƒƒ„ƒƒ„„ƒƒ„ƒƒ‚‚„…Š„ƒ„„€€ƒ}‚‚ƒ‚‰„ƒ„‚„‰‚ƒƒ‚‚‚€€€€‚ƒƒ€€‚ƒƒƒƒƒƒ„ƒ‚€€€€‚ƒ~ƒ‚‚ƒ……‚‚‚ƒƒ„„‚€€‚ƒ‚‚‚€€„‡‡‡†…„„‚€‚ƒƒ‚‚€‚‚ƒƒ„„ƒ„ƒ€‚‚ƒƒ‚ƒ‚ƒ„ƒ‚‚ƒ‚ƒ„„ƒƒƒƒ‚„ƒ‚…‹„‚ƒ„„ƒ‚ƒƒ‚„„Š…„„‚ˆ‡‚ƒ‚ƒ‚‚€‚ƒ‚‚ƒ‚‚‚‚€€‚‚‚ƒƒƒ‚ƒƒ‚‚‚‚€€‚€€€€„…††ƒ€€ƒ„ƒ†…„€€‚ƒ‚‚„…ƒ€€ƒ†‡…†„ƒ‚ƒ‚„ƒ‚ƒ‚‚‚ƒ„ƒ‚‚ƒ„ƒƒƒ‚ƒƒƒ‚‚‚‚ƒƒƒƒƒƒ‚ƒ„ƒ‚ƒŠƒƒ…†ƒƒƒƒ„„…†Œ„ƒƒƒŠƒ„„‚‚‚‚‚ƒƒ‚‚„„‚ƒƒƒ‚‚ƒ‚‚ƒ„ƒƒƒ‚‚‚‚€€€€€€€€‚††…„‚„…‚†„ƒ‚‚€€ƒ„„‚‚‚„……†ƒ„ƒƒƒ‚‚……‚‚ƒƒ‚‚ƒƒ‚‚‚‚„„„„……„‚ƒƒ‚„…†……„„„„ƒ„Š‚ˆ‡‡‚„ƒ‚‚ƒƒ„„‚†‰…††„„ƒ‚‚‚€€‚‚‚ƒ‚‚‚ƒƒƒ„ƒƒ‚‚ƒ‚‚‚‚€€€€€€€€€‚††…ƒ‚‚ƒƒ„ƒ„…„‚€‚‚€‚ƒƒƒƒ………†‡‡…ƒƒƒ‚‚ƒƒ„‚‚…„‚‚„…„„ƒ‚‚‚‚‚‚ƒ„ƒ„……„ƒ‚‚„„„„ƒƒ„……„„‚„Š€…‡ƒƒƒƒƒƒƒƒ‡Ž…ƒ‚ˆ„‚…†…ƒ‚ƒ‚‚‚€‚‚‚‚‚‚‚ƒ„‚€€‚‚‚€€€‚‚‚‚‚€‚…‡†„„‚‚†ƒ‚‚€„„…ƒ€€€‚‚‚ƒƒƒ……†…ƒ„„‚ƒƒ„ƒ„ƒƒ„„‚‚‚„„ƒƒ‚‚‚ƒƒ‚ƒƒ‚ƒƒ‚ƒ„„„……ƒ„„ƒƒ„„„„„…„ƒ†Š‚…†‚ƒ„„……„„ƒ†Œ„‚…‰ƒ„ƒƒ‚ƒ‚ƒƒ‚€‚‚‚ƒ„„ƒƒ‚‚‚‚‚€‚‚ƒ…„ƒ€„ƒ„ƒ‚‚‚€‚ƒ‚ƒ‚ƒ‚€€€€€ƒ‚‚‚‚……†„ƒ…†…‚ƒ„‚ƒ‚ƒ„ƒ‚ƒƒ‚„„ƒƒƒ„„„ƒƒ„†…„„„„„…„„„…„ƒ„„„„„……ƒƒŠ‡‡†‡‡†…†‡‡†„‡Œ„‰ˆƒ…ƒ‚‚‚‚ƒ‚„„‚€‚‚‚„…„ƒƒƒƒ‚‚‚‚‚€€€€€€‚…ƒƒƒ‚‚€€ƒƒƒ‚ƒ„ƒ€ƒ„ƒ„„ƒ‚ƒ……€‚ƒ€€€€‚‚‚ƒ„ƒ„……ƒƒƒƒƒƒƒ‚‚„…„„„ƒ„„‚‚ƒƒ„„„ƒ…†„„„ƒ‚€€€„‡ˆ†„………„„‚‚†„„………ƒ‚‚€‚€€‚ƒ‚‚‚„ƒ‚‚ƒ‚ƒ„ƒƒ‚€€€‚€€‚„„‚ƒ‚‚€‚‚ƒ‚€€‚‚‚€€‚ƒ‚€€€ƒ‚€€€ƒ„‚€€ƒƒ„…„ƒ„„ƒ„„ƒ‚ƒƒƒ„ƒƒ„„„„‚‚ƒƒƒ„„ƒ‚ƒƒƒƒƒƒƒ„~{zz||}€~}|~€…Š‚ƒƒ„…„‚‚‚€‚‚‚‚‚ƒƒ‚ƒƒƒ‚‚‚‚‚ƒ‚‚€~€‚‚ƒ€€€‚„ƒ‚ƒ‚‚‚€€‚‚‚€€‚ƒ€~€‚ƒ‚ƒ…„„ƒ‚‚„…„ƒ„…„„…ƒƒ……„ƒ‚‚ƒƒ„„…†…„„ƒƒ„ƒ„ƒƒƒ„„ƒƒ„……„„……„ƒ‚zz~€…‡††…†‹„„„„ƒ‚‚ƒƒ‚‚ƒ„„…ƒ‚ƒƒ‚ƒƒ„„„ƒ‚‚‚ƒ‚€€€€€€€€€ƒ††€ƒ‚€€€€€‚ƒ„ƒ„‚€‚€€€€‚‚‚‚ƒ„‚ƒ…‚ƒƒƒƒ„††………„ƒƒ„„„„ƒƒƒƒ‚ƒ„…„ƒƒƒ„††……‚ƒ„„…†………„……„ƒƒ„…„„…„ƒ{{{}‚†…‚„„„ƒƒƒ‚‚‚‚„„……ƒ‚ƒ„ƒ‚‚ƒƒ„ƒƒ‚‚ƒ‚€€€€€€~€‚„‡†ƒ‚€€€‚‚‚€‚ƒ‚€€€~ƒ…„ƒ‚ƒ„„„„„„„ƒ„…„„…„ƒ„ƒ‚‚ƒ‚ƒ„…„‚‚ƒ………††ƒƒ„„„„„††…„‚ƒƒƒ„„ƒ„………„…ƒ‚}{~ƒ………„ƒƒ„ƒƒ‚‚„……„„„„‚‚ƒƒ„„„„ƒ‚€€€€‚‚‚€€ƒ…†ƒ€€€‚€€ƒ€‚‚‚€~€€~€~€}ƒ‚|‚„„„„„„ƒ„ƒƒ„„ƒ‚ƒƒƒƒƒƒƒƒƒƒ„„‚‚„†…„…„„„…†„‚ƒ„……„ƒ„„„„‚ƒ…„ƒ‚‚„…„ƒ„†‡†…„ƒ„ƒƒƒ„„„ƒ„…„…„„„ƒ„ƒƒƒƒƒƒƒƒ‚€€€€€€‚‚€‚ƒ‚ƒ~„……ƒ€€€€‚ƒƒ‚ƒ€…€‚„…‚‚€€€€ƒ€€‚}|‚„……„ƒƒƒƒ„„‚‚ƒƒƒƒ‚ƒ‚„„ƒ‚ƒ‚‚ƒ„„ƒƒ„…„………„…„„„„ƒƒƒƒƒ‚‚‚ƒ……ƒ‚‚„„ƒ„„„…„ƒ‚‚‚‚‚‚‚ƒ„ƒƒ„„ƒ„ƒƒƒƒƒ‚‚‚‚‚‚€€€€€€~€€€‚„ƒ‚€€„ƒ„‚€‚‚ƒƒƒ‚ƒ‚€€ƒ‚‚‚ƒƒ€€‚€‚€€€}}‚ƒƒ„„„ƒ‚‚ƒ„„ƒƒ„ƒƒ‚„ƒƒƒ„„ƒ‚‚„ƒ„ƒ„„„ƒ…††……………ƒ‚ƒƒƒ„„„ƒ„„„„„…„„„„„ƒ„……„„„„„ƒƒ‚‚ƒ„„…„……ƒ„ƒƒ„ƒƒ‚ƒ„ƒ€€€€€€€€‚‚‚‚‚‚}ƒ…‚‚‚‚‚‚ƒ„ƒ‚€€‚ƒƒ€~‚‚€€€€€~~{|}ƒƒ…†…„ƒƒƒƒ„ƒƒ„„„ƒƒ„„…ƒƒ‚‚ƒ„„ƒ„…„„…„‡‹†ƒ„ƒ„„„††……………„„„ƒƒ„……„„……††…†………„„„…ƒ„…„………†…„„…†…„ƒƒƒ‚‚‚ƒƒ‚‚€€‚‚€€€‚‚}„„ƒƒ‚ƒƒƒ…ƒ‚€‚ƒ€‚„ƒ€€€€€€~‚ƒƒ‚ƒ„†‡†…„„…„„„„„ƒƒ……„ƒƒƒ„„ƒƒƒƒ„„…†„‡Š„ƒ‚ƒ„…†Š‹Šˆ„„‰‹Šˆ‡†††‡†„„‡Š‰‰ŠŠ‰‰ŠŒ‰…„ˆ‹Šˆˆ‰ˆ‡„‚ƒ………„ƒ‚‚‚‚‚€€€€€€€…‚€‚‚‚ƒ„ƒ‚ƒ‚‚€‚‚‚€ƒ€€ƒ}}€€‚†‚‚‚„„ƒ‚„†…„„„„ƒ…„„„„ƒ„†…„ƒƒƒƒ‚‚ƒ…„……„…†ƒ„ˆ‰‚}ˆ‰†…‡‰Š‰‰‡‚ˆ‡‚€‚ƒ‡ˆ‚€ƒ„†…ƒƒ‡‹‡ƒ„„„„ƒ‚‚‚‚ƒ‚‚€€€‚€‚~‚€‚ƒ€ƒƒƒ‚€€„…~€|{€€€ƒ€‚„†‚€€€€„…„‚‚ƒƒ„ƒ„…††……„„„„„…„„‚„…ƒ„„…†‡ƒ‚‚…‹Š…|~ƒƒ„‡…}€€€€‰…€…ƒ~z{|}|„€|€~|}ƒŽ‰ƒƒƒ„ƒ‚‚‚ƒƒ€€€€€€€€„‚€‚ƒ……ƒ‚‚‚‚‚€€€{ƒ€~}€€€€€€€€‚ƒƒƒ„€€ƒƒ…†„ƒ‚ƒ……„„…„„„„„……„……„„ƒ„„„„„„…‰„ƒƒ…Œˆ‚|€„………Š…{|}{z{€‚…‚„|~~}{|€€~~„|x|ƒŽ‡„ƒ‚ƒƒƒƒ„‚€€‚‚€€€€€‚€€‚ƒ„…‚ƒƒ‚‚‚‚€sz}}|€~€€€‚‚ƒƒ€ƒƒ{~„ƒ‚ƒ„ƒ‚„„„„„‚ƒƒ„…†…ƒƒ„ƒƒ…„ƒ„†„„„‰†‚†Š‡}|…ƒƒƒƒ‡…‚ƒƒ‚‚ƒ‚„„‰‡‚ƒ‚‚‚ƒ‚~ƒŠ‚ƒ‚{|Љƒƒƒƒƒ€€€€‚‚€€€€€€ƒ…‚‚„ƒ‚‚‚ƒ‚rsx|}}€~€€€~€ƒ‚€~yw‚ƒƒ„ƒƒ‚ƒ…†ƒ‚„„„„††…„„ƒ‚ƒ„………†…„ˆ†„„‚}|…„ƒ‚‚‚‡‚ˆ†ƒƒ…„„„…„…ƒ††ƒƒ‚ƒ„„……€€…†‚……~~ˆ‰ƒ„„ƒ‚€€‚ƒ€€€€€ƒ‚€€‚ƒ…ƒ„„‚€ƒ„‚sqvy|‚‚€~€€~€}}||{z‚ƒ„„…‡‡…„…††…†…„…†††„„„„ƒ„†…††…„†…€~€…†„ƒ„„„„ƒ…ŠƒˆŒˆ‰‰‰‰‡…ƒ†ƒƒ†‰‰ˆˆ‰‰‡ƒ€€…†ƒ„…ƒ„~‰†……‚‚€€€‚‚€€€€~€‚€~€‚„ƒ„„…ƒ‚ƒsqsux€‚‚€€~~~~‚}{}~}~|}ƒ…………†††††‡‡†††‡‡‡†……††…„…„…„ƒƒ‡…€ƒƒŒˆ‚…†……„‰‹…†ˆ††‡ˆƒ‚‡ƒ€‚„„……†‰…€€‡‡ƒ„„†„ƒˆ‚‚„„‚‚‚‚‚‚€‚‚‚€€€€~|~€€€„ƒ€ƒ…ƒ€psuvv{€ƒ|{|}~€||‚‚ƒ„„„„…‡†„„……†‡‡‡……„…„ƒƒ…„ƒ…„„‰„€„ƒƒˆƒ…‡†…ƒ‰‰}~~}}‚‚ˆƒ~}~}~~…ƒ€…‡ˆŠ‹‰…†‚ƒ‚ƒƒ€€€‚‚€€€€€€€€~{{€€€‚€€ƒƒƒ€|osuuuv{€~‚€~{z|}|{~|z€‚„ƒƒ„…†…†‡…„……………†‡‡††„‚‚†‡†…„…‡„€€‚ƒ„„††…„„‚ˆˆ„…ƒ~}€€ƒ…~€€‚€~„‡„‚…‚€‚‚‚‚‚€€€~|~‚ƒ€€ƒƒ‚ƒƒ€}rqrsxwxz€‚~~}|{{}~~~}|z}‚ƒ…„…††‡†††…„„†…„†‡††……††„…†††……†€€‚„„‚‰……††‰‡„†††‡†‡‡††„ƒ…„„…„„„ƒ‚ƒ„€€€‚ƒƒƒ‚‚‚‚‚ƒƒƒ‚‚‚€€€€€€€~~€‚„ƒ‚}}€„…„ƒstsuwttv|ƒ}}~}~}}|{}~|y|ƒ„„…†„ƒ…‡†…‡†…„ƒƒ………………†‡†„ƒƒ…†‡…~€‚†……ƒˆ‰………†ƒ‚„…†‡ˆ‡†‡‡„‚…„„„„…„„ƒƒƒˆ…€ƒ„…ƒ‚‚ƒ‚‚ƒ€€‚ƒ‚€€‚‚ƒ€€~|}}‚‚€~~~~€…„~€uvvtsux{z|~|}}}|~~||||}|y{„†…ƒƒ‚‚…††‡‡†…†‡†………†…„…†…„„ƒƒ…ˆ…ƒ„†ˆ††ƒŠ††„„‡ˆˆˆ‡†…ƒ‚‚‡†…„„…„„ƒ‚„Ї‚„ƒƒ‚ƒƒ‚ƒ‚‚‚‚‚‚‚ƒ€€€€ƒ€||~~{|}~ƒ‚€€€~~€€€ƒ…€|}€vsstw{{}€~}~~~{||}|}~}}~{y‚…ƒ‚„…………†……„………†„‚‚‚‚ƒ„„„„ƒƒ†…‚€‚…†ˆ‡†„ƒ††…‚~ƒ„„…†ˆ†‚€€„†ˆ‰ŠŠ‹‰„‚…‹…‚„ƒ‚‚ƒ‚ƒƒ€‚€€€€€€~~}{{}}{}~€€€‚~~}~{~utuy}€~~}€~||~~~~~}}z‚†„…††††„„…††‡†„††ƒ‚ƒ‚ƒ„…†„„…„……ƒ„††‡†ƒ„„…‚~~~€ƒ†€„‚€ƒ…„‡‹ƒ‚…‰„‚„„„…‚‚ƒ‚€‚€€€€€€€€€€~~|}ƒƒ‚~~}}€~~su{~‚€~~~}~~~}}€~}|}~„†ƒ„‡†…………†‡‡‡‡††„‚‚„…„„‡ˆ‡‡††‡‡†††………ƒ„ƒƒ„……„‚€€†…„…€€€€ƒ‡„‚€„…„ƒ„ƒ€‚‚‚ƒ‚€€€€€€€€€€€}|€„„€€€~„„‚€„ƒvzz|}|}‚}{|}~|~|{}~}~~{‚‡ƒƒ†‡„ƒ„„………†……„‚ƒƒ„„…†††‡†…†‡‡††‡‡‡†…„ƒƒ„„†…„††„„†„‚„…„„ƒ‚€‚€‚„…ƒƒƒ„„……„ƒ€€€€€‚€€€€€€€‚}€€€~}~€‚€€€€ƒ„ƒ‚€€‚†…}}{|€€||y}~}}~}||}}~~z€…ƒƒ„…„„„…‡†„„ƒƒ„ƒƒ„…†††‡ˆ†„„„…„„„ƒ„„„……ƒƒƒ„„„…††ƒƒ„„ƒƒ„……„ƒ‚ƒ„„…„„„„……„ƒƒ„……„‚‚~‚ƒ‚ƒ‚€€‚‚€€}~€}~€ƒ‚‚€€~€‚„~€€‚„„€~‚~|z{~~}}~~~~|}{~ƒ„†††††‡‡†……„„……„…‡††‡‡‡…ƒƒ„……„ƒ‚‚„„…„ƒ„„„…„„„„„…„ƒƒƒ„„„„„„…„„……††…„„……††„‚‚‚‚‚‚‚ƒ„……‚€€€€€€€~|ƒ‚€~~€ƒ‚€€‚ƒƒ~~~~}}~|~}~}}€~}~|}|~{z€‚…†††‡†……†…ƒ„………†……„††…††„†‰Š‡‡‡‡‡…ƒƒ„„„………„„„ƒƒ„„……„„„ƒ…††…„„…†‡†…„„„……„ƒ‚ƒ…‡†‚‚„„ƒƒ„ƒ„ƒ€€~€€€~{{~€€€~€€€€‚ƒ‚‚}}|{|}~|~}€}|{|}}{}}}~}~}|x~‚ƒ‚„„…†……„ƒƒ„„„„„ƒ‚‚ƒƒ†Œ‰‚€ƒ„ƒ…‰‰†„„„…„„ƒ„‰ˆ†„„…„…†…„…†††……†††…ƒƒˆ‡‡…€€€€ƒ„†…„„……„ƒƒ‚‚€€‚€€€~~~}z|~}}~€€‚€€‚ƒƒ‚~|z|~€~||||}|||{|}|||~~||}‚ƒ„„„„„„ƒƒƒ„………„ƒƒ„„„‰‹„€~€€ƒ‚„‹†„„„„…„‡‹Œ†„…„„……ƒ‰Œ††‡…„„„ƒ‚†‚‚…€„€‚€„†„„…„ƒ„ƒ‚€€€‚€€€~{{~}~~|€€€‚€~{|€€}|~}~}|~||~~}}}~}~}~€‚‚ƒ„„ƒ„„ƒƒƒƒ„……„…†…††ŠŒƒƒƒ‚‚††ˆ†„……„ƒ‡Šƒ‚ˆ‡„†††…†„Šˆ‡„…††…†„ƒƒƒ~ƒ†‚ƒƒ‚€‡‡„„…„ƒƒƒ‚€€€€€€‚‚~z~|€~~~€€€‚‚ƒ…ƒƒ}}||z~}||{z~}}||}||~~~}~|}‚‚ƒ‚ƒ„„„ƒ‚‚ƒ„…………††…†‡‡Œƒ}€…‡………ƒƒ…‚„„……„„І„‰‚………†„ƒ‡ƒ…ƒ„…………ƒƒ†‚ƒ€€~††‚‚ƒƒ„ƒƒ‚€€€€€€€|}ƒ€{}}~€‚€€ƒ‚€€„…}~{{}z}€~}€}|~|}~{}~~|}~}|~ƒ…‚ƒ……ƒƒ„ƒƒ„…†††††…„…ˆ‰…†††………ƒ€‚………‡…††€€‰‡…„„…„ƒ…‚‡„†‡†……„‚‚€……‚‚„€€††„ƒ‚„„ƒ‚€€€€€‚‚{{‚€}€€€‚ƒ‚€€ƒ‚z}}}~|||}~~|}}|||{{}€~~~}}‚ƒ‚„„„ƒ„„……„……………†††…‰†€€ƒ…††‡……††……………††Š„ƒ‚ƒ‡‰…„„…„„…†‰ƒ†„„„„„„€€†ˆ‚„ƒ}ˆ†„„„ƒ„„„„‚€€€€‚ƒƒƒ„ƒy{€}€€~~}~€‚‚„……„‚~~~|}}{|~}~~~~}|{}}}€~||~€‚„„„„ƒ…„ƒƒ„„…†‡……††ˆ‡€‚„………………†‡†…†††‰‡…†€ƒ…ƒˆ„„†…ƒ„„†‰ƒ„††…„ƒ‚ƒƒ€„ˆ„…‡‚~ƒˆ…„ƒƒƒƒƒ„„‚‚‚‚ƒƒ…ƒ|z~}}}}~~~~~~~~„‡‡†„€€~~{|}~}||}~€~|}~}~€€€}}~ƒƒƒƒ„„„…„ƒ„„…†………†‡‡†ˆ„„‚‚„…††…†………………Ї€ƒ‚ƒ‚…‚‡ˆ†…ƒ††„‰…†……„„ƒƒƒƒ~€ƒ…Š€ƒŠ†ƒ‚‚ƒ„„ƒ€‚ƒƒ‚ƒ‚|€~}€~~~~~‚‚‚‚ƒ„~~~~}}{~}|}~~}{~}|}~~~~~~z|„ƒ‚‚ƒƒ………„ƒƒƒ„††‡ˆˆ‡„††…ˆ„…†††………‚„…„ˆ‹ƒ‚‚ƒƒ„…‡Š„„ƒ‚†ƒ‚…ƒ…†…ƒƒ‚„„‡‚€~†‡~€‚‹…‚‚ƒ‚‚‚€‚‚‚ƒƒƒ‚€}}~~€€~}~~~€€‚„~||}~~~~€~|}~}~€~}}~~}x|ƒ‚‚„„ƒ……„„ƒ‚ƒƒ„…†††‡†„‰†ˆˆ„‚ƒ„„‡‰„…†‰ƒ€ƒ††‡‡†ƒ‚†„ƒ„„‡…†…„„„ƒ‚ƒƒ„‰†€‚„Œƒ‚‹ƒ‚„„„„‚‚‚€‚ƒ„„ƒ„…„~|}~~}~~€‚ƒ~~€‚ƒƒ‚ƒ|{|~}}~~€€}~~|}~~~}}{|{~y|ƒ‚ƒ„„„„„„ƒ‚ƒ„„„„„†‡†…†ˆ„„ˆˆˆˆˆ‡†Š†„‹†ƒ„††‡‡†‚„ˆƒƒ‚…†…ƒ…†„ƒƒ‚†ˆ‹‚‚ƒ‰‡‚€‚„ƒ„„ƒ„ƒ‚‚‚ƒƒ„ƒƒ„…|}~}~}~‚„~€‚‚‚€‚†…z{|}}|{}~|~}}~}}}~}||~~~~~z~‚ƒƒ„ƒ„………„„„„……„…†‡‡†…„„ƒ†ˆ‡…„…††Š‚ƒƒ‚……„„ƒ€ƒŠ„„‚†„ƒ‚ƒ…ƒ‚„ƒ€†‡Œ‚ƒ€„Šƒ€Œ…ƒ‚ƒƒ„ƒƒ‚‚ƒƒƒ‚‚ƒ‚ƒ„ƒ|~„€~€€~~|~€ƒ~€€€€€€€‚‚ƒ‚|z|~~}}~~}~|~€€~|||}}~}|~€€y~ƒ€ƒƒƒ‚ƒ„…„„………………†††…‡†ƒ„„„‚ƒ„…„‚…Œ‡‚‚‚ƒ‚‚‚…†‡‡„‚†‚ƒ†‡‡…„Šˆ€…„‹†„ƒƒƒ€ƒŠˆ‚ƒƒ„„ƒƒƒƒ„ƒ‚‚‚ƒƒ„„y€€~€€~}|~}€ƒ~~€€‚‚}|{|}}|||}|||~}}~~||}~~~~~zƒ…ƒ„ƒ‚ƒ„„„„„„†……„…………„……†„ƒ„ƒ‚ƒƒ‚„…ˆ„‚‚ƒ……„ƒ„…ƒ‡‡„…ƒ‚ƒƒ‚ƒƒ‚ˆŠ€…ˆ‹…„„ƒƒƒ„~„‹ƒƒ‚„…ƒƒƒƒƒ„ƒƒƒƒ„…ƒ~y}~~~}||}€}|}~}€‚ƒ€|{{}~}||{{|}}}~~}~€~~}~}~~~zzƒ…ƒ‚‚ƒƒƒ„„„„„……„„„„……„……††…††„„……†…ƒ‚‚ƒ„…„„ƒ„…„…„ƒ†„„„„‚ƒ„„ƒƒˆˆ‚†ˆ‡ƒƒ„…„ƒ„„‚…ˆ…‚ƒƒ„ƒ„„„…„…„„„„„ƒ}x~}~~}~€~}~}}~}z{€~|zx€€|{~|~~~}~~}}~~}~~~}}zx‚…‚ƒƒ‚‚‚ƒ„„ƒ„„…………„„„„………†‡‡††‡‡†…†…„ƒƒ„„ƒ„…„„†„ƒ‚ƒƒƒ‚‚‚‚ƒ„„ƒ‚ƒ„„„……ƒƒƒƒ‚‚ƒƒ„„„ƒƒ…„ƒ„…ƒƒ‚}~x|~}}~~}}€~}~€€~|z}~}|{{xuyy||€‚‚||}}}~}||~~~}€~{y‚†‚ƒ‚‚ƒƒƒ„„„„…………„„„…„ƒƒ…††††††…………ƒ„…„ƒ„„……„……„„……„ƒ„„ƒ‚‚‚„„„…„„‡†…ƒƒƒ„ƒƒ„„ƒƒ„………†…ƒƒ„„„„ƒ‚‚x}~~€}~}}~}~~€}zzyyzyyyvuuuvwux|ƒ~{}}~~~}}}~~~}}{y‚†‚‚‚ƒƒƒ‚‚ƒ„……„…††…………………„„……†‡‡†……„„ƒƒƒƒ……ƒ„…„……„ƒ„„„„„„„……………„……„„…ƒ„………††…†‡‡†‡†…………„ƒ‚ƒx}~}~~}}}~~}~}||{{zyyyxwwvuwxzyy{€~}|}~|{||}||||}}~||€~yx‚…‚€€€ƒ‚ƒ„„……ƒ„…„„…†…………………†…………††‡†††„ƒƒƒ‚‚ƒ„„…†††„ƒƒ„……………††††……„„………†„„……………„†‡†…„…†……„„„ƒ‚~ƒ…w~€€~}}}~}}||{yyzywwwvuuxxz{x{‚~}|{}{|}~~}}}}~~€}~€{x……€€‚„………„……„…†……………„……„††……„„…††…ƒ„„‚ƒ‚‚ƒ„……ƒƒƒ„ƒ‚„„„ƒƒ…†…„„………††…„„„…†………„„…………………††„ƒƒ}~ƒƒv}€~~}}}~~~|z|yxyywxxuuvxxyxx{€}|}|}€}~~}}zw‚††‚‚€ƒ„„ƒ„……„††…………†…†…„……†…„„…††††……‚ŠŒƒƒ…ƒˆˆƒ‚ƒ‡†‚‡Š‹Š†‚„„…………„„„„„„†‡††…„„…†‡‡†„„„„„‚‚}}u{~€~~~~}}~}}{{{zxwvxvussux{xuy€~~}~}~}~}}}~~~~~}}}ywƒ…„‚‚€„„„ƒƒ………………………†„„…………††…ƒ„……„…………Љ‡‚„Š€ŒŒŒ„…††……………„„………††……†…„„„…„„„„ƒ~~€‚w|~~€€~€~~}~€~|{xxwvwwusrwwxvvz€€~}|}~|}}}~}~}}|{{}wxƒ……ƒ‚‚„‚€€‚„„„„„„„„„„…†††††…‡†††„„…†„ƒ„„„‡‰„‰ƒ‚‡‰Œ‡‹‰‹‚ŽŽƒ„…ƒƒ„………„…†…††……„„ƒ‚‚ƒƒƒ„„ƒ‚€ƒ„‚}z~~~~~€~~~}~€~€|||yxwwwwuspyxvtuz€€~~{}€~}}{|}|~}||}wzƒƒ†„‚ƒ„ƒ„…„„„ƒ……………††…††††‡†…„…………„„„„…ІƒŠ†€‡‰…Žˆƒ‹†‹‚‚€‡„…„………………†‡††…„„ƒƒƒƒ„……ƒ‚ƒ„‚€„ƒy|~}~~€}}€~~~~z{|zxxwwvsozyxvvz‚|z||}€~~€~||~~~w{ƒ…‡ƒƒƒ‚…†‚„„„……†…„………†…„„„„…‡………††…„ƒƒ‚†‰„ƒ‡ˆ„†ˆ†‹†ˆ‚„Š‚€…އ„„„„„„„„…†…„„„„„ƒ‚ƒ„†††„ƒ‚€€€‚‚‚{{~}}}€€~}}€{|~zyzzvsrryxxyyz€}||{}~}|}~{|~~}}x{‚…‡„ƒ‚‚ƒ„ƒƒ‚‚ƒ„…†………†††…„„„„„††…„……††††…Šˆ††‡‡‡‚†‰„‚ˆ‡ˆ„„Œˆ†‹Ž‰„„†…„ƒƒ„„„„…„…†…††…ƒ…†‡††„„~…ƒ‚‚ƒ{{~~~}}~~€~~~}zzz{ywvtutxxwwvz~z|}~|}}{|}}|}‚€‚€y}‚ƒ…„„‚‚ƒ‚‚ƒ€ƒ„„„„„……†††…„„„„†‡‡…†…‡††‡ˆˆ……ƒƒ†…„‡‡„ƒ„†…„‰‰‹Žˆ‚„ˆˆ††……††…„„„…„ƒ„…„„…‡†„ƒ„ƒ€€‚„…„‡}|~~}€~}}~~}~}|zxxxvvusyxwwwz}€||}~~~|}}|{|~‚}w}ƒ‚…‡†…„„ƒƒ‚€‚„„ƒƒ„„†‡††††………†††…………………†……††……„„„…„††„ƒƒƒ‚ƒ……„„†‡†‡‡‡‡††…„„ƒƒ„……………†……„ƒ€€ƒ…ƒ„{|~}€€}~}~~~~}zzyzzxvvtxxxwwy|~~~~~~}~}|~}}||}~~~w|ƒ„†‡……††……ƒ„ƒƒ…„„„…††…††…„„„……††††‡‡†……†‡ˆˆ‡‡‡‡††††ˆ‡††…„…†…†‡ˆ‡‡ˆ‡‡‡‡††‡†…ƒƒ„…†‡‡†…………ƒ€‚ƒƒƒ„‚€{|€~~~€€~~}~€{y{yyxwvvvwxxuuv~€€~}}~||}|}}€~~€€}w}„†……ƒ„†„„ƒ„„‚„…„„„……„…………„„…†††‡ˆˆ‡‡‡†‡‡ˆˆ‡ˆˆ‡‡†ˆˆŠ‰‡†††‡‡†‡‡ˆˆ‡†††††……†…ƒƒƒƒ……†‡††……„‚€€…ƒ†y}€~~}€€~~~€~|zxxxxxwvxvuussz~}}~~~~~|}~€€}}}|u}„„„„ƒƒ„†…„ƒƒƒ‚ƒ„……„„……„††††…„…†…†‡‡†„„„ƒƒ‚ƒ„„„……†††‡‡‡††‡………„…†‡†……††††…„„„ƒ„…‡†††……††…‚ƒ…ƒƒ……„€ƒƒxz~~}}|~~€{~}zyxxxvsuttwus{}~~}~~}|~~|}}|~€|w„…„…ƒ„„„ƒƒ…„ƒƒ‚‚ƒ„…††††‡†‡‡†‡‡†……†„ƒƒ„„ƒƒ‚ƒ‚ƒ…†…„…‡…„ƒƒƒ‚‚‚‚„……†…„„…†„……†††††††††…ƒ€ƒ†„„†…‚ƒ…zx}}€€}~~}~~~}€‚}{|{zywvurvutvwxy€~}|~~~€~}~}~|~}w……ƒ‚„„……ƒƒ„…ƒ‚‚ƒ‚ƒ„……†††‡‡††††…„„………ŠˆŠŠ‰‰‰ŠŠ†‚„†…„„†…ƒ‰‹Šˆ†ˆŠ†‚ƒ„„„ƒƒ‚…‡††††…†…††…ƒ€…„ƒ‚„…ƒ‚€…ƒzz|~~~~€}}~~€ƒ}x{|zyywttvuvrswz~~}||||~€€}~~~~~w~„ƒƒƒ„„†…„†…„ƒ‚ƒ…†…ƒ„………††††††††††………‡…„‡‡‰‰‡†‡‹„‚„ƒ…„ƒ‰Œˆ†…††…†‰‹…‚„„„…„„††‡†…„……………„‚ƒ„„„„………‚‡‚z{}}€~}}}~}}€~‚}{z}|zyyvurqtvttww|}~~~|}~}~~}~~€~x„„„……ƒ„…„…„„‚‚…‡†…„ƒ„„……„…†‡‡††††…††ˆ„ƒƒ„„…ˆŒ……ˆ‚ƒƒƒ‚ˆŠ†‡‰‰Š‹Œ‰…‡Œ†ƒƒ„††††…†…„…††„„„‚ƒ€‚ƒ„†…„…€y{€}€~|~}}~~€‚€}{zxvxuuuwvvvv||~~~~~~~€€~~~}w……„…‡…‚„††…„ƒƒ„…†…†„‚„………………†……††††‡ˆ…ƒ€‚ƒ‚†ˆ„‡ˆƒ…ƒ…†‰Šƒƒ„ƒ†‰„‡‹‡†……„…‡†…„…†††„…ƒ€‚ƒƒ‚ƒ…†„€„†€y{~~~~€~|€€€~||zyywtwyyxwxz~~~€~~€€€€}}~~~}v€„ƒƒƒ……ƒƒ„…††††„‚‚ƒ…„ƒ„„…†……†…„††††‡‡„†€ƒ…†…„‡Š†Š„„…‡ˆ‰‚ƒ…†……ƒƒ…„ˆ‰††„ƒ…‡†„„…††……„‚‚ƒ„ƒƒ„„ƒ‚……y|}~~~~~€€}}~€~}}~~{zxzzvqvxyxxwuz~}~~~~}~~~~~z€‚‚ƒ„„„„ƒ„…††…„ƒ‚ƒƒ„„„„„………‡‡……†………ˆŠ†ˆƒ†‡††…„‰…‡ƒˆ†ƒŠ„ƒ†……„„…ƒ„ƒ…ˆ………………†„„…………„‚€‚‚„„ƒ‚ˆƒy{|}}}~}}~~}~}}}{|zyyxvyyxyzywz|}~~~~~~~~~}~~€z~ƒ„ƒ„…ƒ‚ƒ„†„„‚‚ƒ„„…†††„ƒ„„…†……††††…‡‹†ˆƒ…†………ƒ‡ƒ††€‰„†Š…………„„„ƒƒ„„…Š„…‡†††‡†…†…„„‚€ƒ„ƒ‚‚„„„„…„†‚€x{~~}~€€~~}}}}{y|yx{zyyyz{{}~}~~~~€}}}~€€z€‚‚ƒƒ„†„„„„……†………†……„„††„‚„…†††††„‡‹†ˆ‚……„„„ƒ…€…ˆ‚‡„ˆ‡…‡‡……†…ƒƒƒ†‹„………‡†‡†‡…ƒƒ‚€ƒ……„„…†…‡…ƒ††ƒ~v}~|}}~}~~~€}{}}~}|zx|{xyyz|zxxy}~}~}}~}~}{~~~~}{€‚‚‚ƒƒ††„„„„…†‡†………„ƒ………ƒ„„……††††„‡‡†‡‚„„„ƒƒƒ‡‚†ˆƒ‡„‡…‚‡†……†…„„€„ˆ‰…††‡‡‡‡††…„ƒƒ„„‚ƒ‚„……„„„‚‡†„{~~~~|~~~}}~~€|}}}}|z|zywwwyzxxz~}}}~~~~~}€z|€ƒ„„ƒ†„‚‚ƒ…††„„†……„††ƒ„†…ƒ„„„……††…‰ˆ…ˆ‚„„„„ƒƒ†‡…†‡†ˆ‚…††………†‚ƒ†‹ˆ…††‡‡‡†…„„„‚‚„„‚„ƒ„…„…ƒ€…„„y€€€€~}}~~}‚|}}~}~€~{}uuwyxxx{}|}}}|}~}|€~~~~~~~{ƒƒ…„„„……„„ƒƒ„„„…†‡†††…††‡†……ƒƒ„ƒ„…†…‹‰†ˆ‚„…„„„‡…„ˆ‚…‰†Šƒ‚…………„‚††Ž…„†††…†……„ƒƒ‚‚„…„ƒƒ„„„…‡…‚€„††ƒz€€~~~~}}~}~€‚~}}}}}}}~}}xvxyyzyx{|~|€€~~~}€~|~}{ƒ„ƒƒƒƒ„…†…„ƒ„„„„„…………†……‡‡‡‡‡…ƒ„„………ƒ‹††‚€ƒƒƒƒ‡‡ˆ‰ƒƒ„‰††‡‚ƒ„„„€€…‡‹Šƒ…†……………„‚ƒ‚‚ƒ………ƒ„…„„…†„ƒ‚‡‡†ƒ{}€~}~~~||~€€~~~|zy|~{yxxz{zz|}|}}~~~~}~€€}|}z}…ƒ‚‚‚ƒƒ„…†„‚ƒƒƒƒ…………†‡†…‡‡‡‡……†……………ƒŠ„ƒƒ„‚ƒ†‡„…Š……†…ˆŠ‚‡‡„„ƒƒƒ††‹ƒ„†‡‡‡†…„ƒ‚ƒƒƒ„††……„††††…ƒ…ˆ†…ƒ€~~}€€‚€}|~€€€~~}{yyz{xy}€|{zxyyy}~|~€~~}}}||}}~z~…ƒ‚‚‚ƒƒ„††…ƒ‚„…………††…†††„ƒ‚…‡‡…………ƒ‰…„…‡†…†††‹‹„…†††…ˆˆ„…‡†………†Š‡„†‡‡‡†‡„‚‚ƒ…„„……††……………„ƒ†„„‚€}~~€€€~||}€€~||{zy{}zz{yzzxvyxx{}|~|}~}~}}}}zz…„ƒƒ‚‚‚ƒ…††…ƒ‚ƒƒ……„„„……†…„……†‡†„„††„Љˆ‡‡‡‡‡ˆŠˆ…„†‡‡†…ƒ‡Š‡„ƒƒ„…Žˆ†‡‡‡††……„‚ƒ‚„„ƒƒ‚ƒ†…††……†ƒ‚…„„‚€‚€€~}€}~~~}~}~~~}||||}|{}|{yx}}yz||}}~~~~|}{{ƒ‡‡ƒ‚‚ƒ„„††„ƒƒƒƒ„……„ƒ„………†ˆ‰‡ˆ‡…„†‡††‡‡†‡ˆˆ‡‡†……‡‡‡‡‡††………ˆŠŠ‹Œ‹Š‡†‡‡†††…„ƒƒ‚„„‚ƒ„……„………†‡†…„†…„„„‚ƒ}}|~~~~‚„‚€~||~{z€{|{{yz|}zy~}~€~~€~~€y|„…†ƒƒƒƒ„„†……„‚‚ƒƒ‚‚‚„…†‰‰‰‰‡‡††‡†††††‡‡…ƒ„„…†‡†………††††…„„………ƒ„†††††††„ƒƒ‚€‚…†††„„…………„€„ˆ…„ƒ‚„„}~~€‚}}€‚ƒ€€~|}||||{{{~|{{zyyzzzz~}~~}|~|~~||}y~‚„…‚‚„ƒƒ„„„…††…‚‚‚‚…‡‚„………‡‡ˆˆ‡ˆˆˆ‡††‡‰Šˆ‡‡††‡‡†…„…††……†††…††††‡†††…„„ƒ‚~}€ƒ…ƒƒ……„………‚†‡†„ƒ‚„ƒ~‚~~~}~€€~~€~||}}zy{||{{|yzxxz{yz|~~}}~}~~}~~}}€|z€€…†‚‚ƒ„„„„…††………ƒƒ‚‚ƒ‰††…„„„ƒ‚ƒ‡‡ˆˆˆ‡‡‡ˆˆ‡‡†††………†…††‡†††‡†††ˆ‡††††„‚‚‚~~‚‚ƒ…„„„ƒ…†…„„„……†……ƒ…„ƒ€}~~~}~~}~€~}}~||}|{z|||}}|}~{}}~{}~}€}~€~z|„ƒ„†‚‚ƒ„……„†‡‡†‡…ƒ‚„ƒ„ˆ€€‚ƒ„‚„‰††„‚…†‡†††‡‡††…„……………†††‡‡‡††‡††…„ƒ„……„‚‚ƒ|…………„Œ‰……‡‡…„„„„‚†††…„„„‚€}€~}~~~~~~€~~~}|z|}}z||~~~}{|}}€~~~}~‚}}x}‚„„†‚‚ƒƒ„„ƒ„‡‡†‡„‚‚ƒ†‡~}‚ƒ„‹‡†ˆ…‚…†‡‡†…ƒƒ‚‚††…†‡††‡‡††‡‡…ƒ€~€‚‚„„€ƒˆ‰ƒ~†ˆˆ‡‰‹Ž…ƒ…††…„„ƒ…‡…„…„‚ƒ‚€|€~}}~~~}}~~~~}}}}~~~|{}}}~€}}}€~~~~~}}{€…ƒ‚„‚„„„„„…†…†‡…‚ƒ‚ƒˆ…~~|ƒƒŠˆƒ…„„…†††‡„‚‡‡‡„„ƒ„††††††…ƒ€‚„„ˆ‡‡‰‹‰„‚€…††‰……††‰ˆ„„ƒ……„„…‚~‚††…ƒ‚‚‚……ƒƒ{€}~€‚€}|€~~€||}}~}}€}~|}|~~}~}~}{z„ƒ††‚‚ƒ„ƒƒƒ„†††††………„…ƒ‚‚~‡Šƒƒ†‚„…††††ƒŠ††ƒ‚…†‡‡††‡„€…†‘‘ŒŠ„Š‹€…{‰†…†…„„ƒ„„…††…„„‚‚„††…„ƒ‚…„‚€€}~€~}~}}}~~~}}}}|||}}|||~~~}~}||~~}€}|~xz‚ƒ„††ƒƒƒƒ„„„„†‡†††‡†„„†ƒ‡‚€„‰„‚…ƒ………„„ŠŠƒ‚„†‚‡‡‡†‡†‚ˆ‹‹ˆ†……Љ€‚†|†‰‡‡††…„ƒƒ„†…„……„‚ƒ…††…„„‚ƒ„‚€€‚~|}}~~€€€€||~{|}|{|}€~~~{}~}|~}€~€~}~|‚ƒƒ„…ƒ‚ƒƒ„„ƒƒ„…†††‡…„ƒ……}†„}ƒ†„…€ƒƒ…†…‚ˆ‰‡ŒŠ„ˆ…†††‡„‚ŠŒŒ„‚„„„„„ŠŠ‚‚†‡€‰†…†……„ƒƒ„………††…ƒƒ…‡…„ƒ„„ƒ††„‚ƒ~€~~€€€€~~}{}~~~~€~|~~}€€}}~~~}||}‚„„„„†ƒ‚ƒƒ„ƒƒ„„…†††‡††‡‡††‚ƒ„…„ƒƒ„†ƒ†‹…І‹†††„††‡†ƒ‹‰‰ƒ„„…†…„ƒƒƒƒˆ‡€Š‡†…ƒƒƒƒƒ„„……………ƒ…‡†„ƒƒ„ƒ„…„„„ƒ‚~~€~€€~{~~€€~~|€€~~~€‚€€~~|}~~~{|z}„„ƒƒ…ƒ‚ƒƒ„„…„…†„„„„…†‡‡ˆ€|‡…€„„‰ƒ€„„„„ƒ‰…ˆ…€ˆˆ‚‰ƒ„††„†‡†Š‚„……†„ƒ„‚€‚‚„‰„|‰‰……‚ƒ…„ƒ†‡†……„ƒ‚„††„„……‚€„…ƒ‚ƒƒ‚}‚€~~}€|€€€~}~~~~€‚€}}ƒ~€~€~}~}zz€„…„„††„„ƒ„„„ƒ„…„„ƒƒ„‡‡ˆ‹„~…‡„…ˆ‹ƒ„„ƒ‚ƒˆ†ƒŠ…ˆˆ…ƒ„‚‡…„ˆ‚„…„ƒƒ€€ƒ‚ƒ‡…~ƒˆ‡‚„‚ƒ…‚‚ƒƒ„„…‚„‡†„„„„‚€„„‚ƒƒ„ƒ~‚ƒ~‚€€€€€€€||}}|~‚€~~}}}~~~~~}}€‚ƒƒƒ„…ƒƒ„„„„ƒƒ…††„„…††‡‰„…‡‡†ƒ…‡‚ƒ„†„…‡ƒƒƒ€‚†„…†‚ƒ†ƒ…†ƒƒ„„ƒ…ˆ…„‚ƒˆ‡Š€„…ˆ€€ƒ„‚|‰Œ…„…„€ƒ„…†„…†„€„ƒ„…ƒƒ‚‚~~€€€€~~~~}|{|}‚ƒ€€€€€„…€}~~}}~|}‚ƒƒƒ„„…ƒ‚ƒ„„…†…††……†……„„ˆ‡‚ƒ‡Œ‰‚…ˆ‚„†„†…€€|~€€…‡‚ƒ‚‰„ƒ†‚ƒ††ƒ…‰†ƒ€ˆ€€†‰‚†ˆ†‰€ƒ’Œ„„ƒ‚„………„†‡„€„†„„„ƒ‚€~~‚€}}|{|}~||{{€ƒ‚€ƒ‚†|~~~}|~€ƒƒƒ„„„†„‚„„„………………†††…ƒƒŠ‹ƒ„‡‰ˆ†‡‰‚€ƒ„ƒ…„€€‚ƒ„‚…„ˆ‡€…ƒ††„†‹††„Š€‡Š„‚‰ˆ††“ƒ……‚„…††……‡†ƒƒ†‡††…„„ƒ€~‚ƒ~}|||~€€~~~~~‚‚€‚‚…~~~€~|€‚€„ƒƒ„„ƒƒ……ƒ‚‚ƒ…………†††……„ƒ‡Š„…‡………‡‰„ƒ…†ˆ†‚ˆ‡‡ˆ‡†……ƒ„‚„…†‰‚ƒƒ…††‡‡„‡‡Š…‚ˆ‰‚‚ƒ‹…‚€‚”…ƒ…ƒ‚ƒ†††………†„‚ƒ††„„„‚ƒ„ƒ€€€~€~~}~€€€}|}~}€ƒ‚€€~…ƒ}}|}}‚€„„ƒƒƒ„ƒ„…‚‚‚ƒ„„………††„„„„…„ƒ…………ƒ†‰„‚ƒ„†ƒ†ˆ‡Š‹Š‰ŠŠˆƒ‚‚††‰‹ƒƒƒ‚€€€ƒƒ†‰…„ˆˆƒ…ƒŒ‹ˆ‰Ž†…‡…‚„†……………††‚€‚‡†„…‡…‚‚ƒ‚€€‚€~~~~€~}}~{|~‚€€€€„ƒ{|~‚€‚ƒƒ„‚ƒ„„ƒ†‡ƒ‚ƒƒƒƒ…††††…„……††††…†‡…†Œ‰‡…‰ŠƒŠ†††‡‡†……‡†„‡‡Œ‹ƒƒƒ}~€ƒ‰„„†‹…„ƒ‚„ˆŠ‹‰„„†…‚„……‡†…………‚ƒ„…„………ƒ‚‚…ƒ€~~|~€€}~~{{~~€~}€€ƒ‚€€‚€…}{}ƒ……ƒ‚„ƒ„„…‡„‚‚ƒ„„……†‡…„………†‡††…………†ˆ‰ˆ†ŠŽŽŒˆ††‡‡‡‡‡†Š‹ŠŠ„†‡‡‰Œ…ƒ€‚ƒ‹€„ƒƒ……ƒ‚„††…†…‚ƒ…‡‡††……‚……†††…„„ƒ‚‚€ƒ…ƒƒ‚~€ƒ€€€€€}}€~}|~‚‚€€‚~}~}€…„‚‚‚ƒ……‡‡„‚‚ƒ„……„„…‡††……„„†„„…„„…†††……„…†‡‡‡‡‡†…†††‡ŠŒ‡†††…†Œ‹††‡‰‹ˆƒ‚ƒ†‹‡ƒ„„…„…‡ˆ†…‡„‚…„…………†††‚††…„„„ƒƒƒƒ„ƒ€‚‚‚~€€~‚ƒ~}}~‚€€ƒ€~‚„ƒ‚……‚‚„„…‡†„‚ƒ„ƒ„ƒƒ„…†‡‡……†‡…„…„„…†††††††………†…„„†‡‡‡††††††††‡††ˆ‹‹Œ‹‰„‚…†…ƒ‚ƒ„………†……††…€……†……„„…†„‚ƒ†‡†…„ƒƒƒƒ„†ƒ‚‚‚‚€~||}~~}~|„ƒ‚€€~ƒ‚‚~|€„…ƒƒƒƒ„…„„‡‡†ƒ‚ƒƒ‚‚‚ƒ…††……………„„„„…††……†‡†…„„ƒ„„…†‡††‡‡‡ˆˆ‡‡†††††‡†…ƒƒƒƒ„…„„ƒ……„„…†‡………„€„…†‡†„‚ƒ…†ƒ‚„††……„„ƒ‚‚ƒ„ƒƒ‚‚ƒ|{|‚~zz}{|}€€ƒ‚€€€|~€„€~~…„ƒƒ„„„ƒƒˆˆ†ƒ€ƒ…ƒ‚‚„…†…„ƒ„……„„…†…„„„………„„ƒ„†‡‡‡‡††ˆ‡†††……„ƒƒ„„…†…ƒƒ„„…†…„ƒ…††…†‡†††…‚€ƒ„…†…„………„ƒ‚…‡†„„„„ƒ‚‚ƒ…„‚„€{{|~€€~||€|{z€„€}€‚‚€€€„€€„†„„‚‚ƒ„„ƒƒ†‡ˆ‡„ƒ‚ƒ……ƒ„„…………„„„„ƒƒ„…„……†††„„……„ƒ…‡‡††††‡†„„„…„ƒ‚ƒƒƒ‚‚‚„………„„„„†‡†††††††…ƒƒ„ƒ…‡†…†‡‡…„ƒ‚…†„‚ƒƒƒ„„…„…†„ƒ€ƒ„€€}{~~}|~~}}~{€€‚€‚€~€€€…ƒ€‚…‡…ƒ„„ƒ‚…†‡††…‚„…„„„…………††††„ƒ„„„„„„„……„„…………††††††‡†……„„…………„ƒƒƒ…††……„„†‡‡††††††‡†€„„„†‡†…†††…„€……ƒ„…ƒ„††…………„ƒ…€~€}~~{z~}{{~~ƒ€~€€€~~~†ƒ€ƒ„„‚‚ƒ„…„ƒ‚ƒ…‡†‡†‚ƒ„ƒ‚ƒƒ„„…‡‡‡‡‡†‡†…………†…„„„…‡†‡‡‡‡‡†………†„‚„„‚‚ƒƒƒƒ…‡†……†……‡††…‡‡‡††„‚‚ƒ„…‡‡…„††…‡†ƒƒ‰‡„„…„ƒ„…„„†‡…ƒ‚€„€€€~~~~|~‚€€ŒŒŒ‹‹‰‰‰ˆ‰‰‡‡‡ˆ‰ˆ‡ˆˆˆ‡‡ˆ‰‰Š‰‡‡ˆ‡ˆ‰ŠŠ‰‰‰‰ˆ‡ˆˆˆˆ‡‡‡‡‡ˆˆˆ‡‡‡‡†††††††††††…†…„ƒƒ‡‰†††ˆ‰‰ˆ‡‡†‚‚„††……………†………†††‡‡……‡‡‡†‡‡‡‡‡‡‡ˆˆˆˆ‡ˆˆ‡‡ˆ‰Š‹Šˆˆˆˆ‹Šˆˆˆ‡Š‰‡‡ˆ‰‹ˆ‡‰Š‰‹‰ŽŒŒŒŒŒŠ‰‰ˆ‰Š‰ˆˆ‡‡‡‡‡‰‰ˆ‡‰‰‰ˆˆ‰‰‰‰ˆ‰ŠˆˆŠŠ‰‰‰‰‡‡‡ˆ‡‡‡‡‡‡‡‡‡‡‡††………†……†††…………ƒ‡Š‰‰ŠŠ‰‰‰Š‰‡Š…€„………„„„…†††…††‡‡†…††††††‡‡‡‡‡‡‡‡‡‡‡‡‡‰‰ˆ‰‰ˆˆ‡‡ˆˆ‰ˆ‡ˆ‡ˆ‰‰‡ˆ‰ŠŠ‡‡ˆˆ‰‹ˆ‰ŽŽŒŒ‹‹‹‹ŠˆˆŠ‹ŠŠŠˆˆˆ‡ˆ‡‡‡‰‰Šˆ‡‰ŠŠ‰‰‰‰ˆˆ‰‰‰ˆˆˆˆ‡‡ˆ‡‡‡‡‡‡‡‡‡‡‡‡…………†††………††…„‚…†………„„„„„„‚„„€ƒƒ„„„„„……†††††……†††††‡‡‡‡‡‡‡‡‡‡‡‡ˆ‡‡ˆ‰ˆ‰‰‰‰ˆ‡ˆ‰‰ˆ‡ˆ‡‰‰‡†ˆŠ‹Š‡‡‡‰Š‰ˆ‰Œ‹Œ‹ŠŠŠ‰ˆ‡ˆŠ‹Šˆ‡ˆ‹Šˆ‡‡‡ˆˆˆˆˆˆ‰‰‰‰Š‰ˆ‰‰‰ˆ‡‡ˆ‡‡‡‡‡‡‡‡‡‡‡‡‡††††………††††…††…„„ƒ„†††…„„„…„†…ƒƒƒ‡‚‚„…„…††……………„…†‡††‡†††‡‡†††‡‡‡‡‡†‡‡ˆ‰ŠŠŠ‰ˆ‡ˆ‰ˆˆ‰‰ˆ‰ˆ‡ˆ‰ŠŠ‰ˆˆ‡ˆŠ‰ˆˆŠŠŠ‹‹ŠŠ‰ˆ‰‰ˆˆ‰ˆ‡‡‡‰‰‡†‡‡ˆˆ‡‡ˆˆˆ‰‰ˆ‰‰ˆˆ‡‡‡‡ˆ‡‡†‡‡‡‡‡†††‡††††††††……†††…†…„„……„………„„„„„†ˆ‡†ƒ„‹‡‚‚‚‚„…†††††………††…††…†‡‡‡‡‡‡†‡‡†‡‡ˆˆ‰Šˆ‡‡ˆˆˆˆˆˆ‰‰Šˆ‰ˆ‡ˆ‰‰Šˆ‡ˆ‡ˆŠ‰ˆ‰ˆ‰‰‰Š‹ŠŠ‰‰‰ˆ‡‡‡‡‡‡ˆ‰ˆ‡‡‡ˆ‡†‡ˆˆˆ‰Š‰ˆˆˆˆ‡‡ˆ‰ˆˆ‡†‡‡‡‡‡†…†††…††…††‡†„„„…………„„ƒ„…„……„ƒ‚…†††„‚„†‡Š‰‚„………†††…†††‡……†††‡‡‡‡††‡‡‡‡ˆˆ‡‰ŠŠˆ‡ˆˆˆˆ‡‡‡ˆ‰Š‰ˆˆˆ‡ˆ‹‰ˆˆˆˆ‰ŠŠ‰‹‹‹Š‰‹Œ‰‰Š‰Š‰‡‡‡‡ˆˆ‡‡ˆˆ‡‡‡‡‡‡ˆˆˆ‰‰‡‡‡ˆˆ‡‡ˆˆˆˆˆˆ‡†††‡‡‡‡‡†‡††……………„„„………„„„‚‚………„ƒ€}~€‚ƒƒ…ƒ‚„ƒƒ†„ƒ„„„„………„„……………†††‡†††††††††‡ˆ‡ˆˆ‡‡‡‡ˆˆ‡‡‡‡‡ˆˆ‰‡†ˆ‰Šˆ‡‡‡ˆŠ‰ˆŠŠŒ‹‹ŠŠŠˆˆ‰‰‰‰ˆ‰ˆ‡‡ˆ‰‡‡‡‡‡ˆ‡‡‡ˆˆˆˆˆ‡‡‡‡‡ˆˆˆ‡‡‡ˆˆ‡††‡‡††††…†………„„„„„„„„„„„„„‚†…„„~~‚ƒƒ„†„ƒƒƒƒ‚‚……†………„„„„„……………………††††‡†…††‡ˆˆˆ‡‡†ˆˆ‡ˆ†‡‡‡‡ˆ‡‡‡†ˆ‰Š‹ˆ‡†‡‡Š‰‰‰ŠŒ‹Š‰‰‰ˆ‰Š‰‰‰ˆˆˆˆ‡‡‰ˆ††††‡ˆˆ‡‡‡‡ˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡†††………„…………„„„„„„…„„„ƒƒƒƒ„‚€†„ƒ„„€€ƒ‚‚ƒ„ƒ„ˆˆ„ƒƒ„„………………„„„„††„„…………„…†‡‡††††‡‡‡‡‡††‡ˆ‡‡ˆ‡‡†‡ˆ†‡ˆˆ‰Š‰ˆ‡‡‡ˆˆˆ‰‰‰ŽŒ‹Š‰‰‰‰‰‰‰ˆ‡‡‡‡‡††‡…………†‡‡‡‡‡‡ˆ‰‡†‡†‡‡‡‡‡‡‡‡‡†††††………………„„„„„„„ƒƒƒ„„„„„„‚‚ƒƒƒ††‚……ƒ‚‚€ƒ…‡…ƒƒƒ„„ƒƒ„„„ƒƒ„„……„„…………………†††…†‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡ˆŠŠ‡†‡ˆˆˆ‡‡‡ˆ‰Š‰‹ŠŠ‰ˆˆˆˆ‡ˆ‰‡††‡‡†…†……†‡‡‡‡‡‡‡‡‡††‡‡‡‡‡‡‡†‡‡‡‡‡‡‡†…„„„ƒ„……„„„„ƒƒƒ„„„ƒƒ„ƒ‚ƒƒƒ„†„†…‚‚ƒƒ€€ƒƒƒƒ‚„„„„„ƒ„ƒƒƒ„„„„„„„…………„„„………†‡‡‡‡‡‡‡†‡‡‡‡‡†ˆˆˆ‡‡‡‡‡‡Šˆ††ˆˆˆˆˆˆˆˆŠŒŠ‹‹ŠŠŠˆ‰Šˆˆˆˆ‡‡‡ˆ‡‡‡‡ˆ‡†‡††‡‡‡‡‡ˆ‡‡‡‡‡‡‡‡‡††‡††…„………„ƒƒƒƒ„…„ƒƒƒ„„„„„ƒ‚ƒƒƒƒƒƒƒƒ…ƒ‚‚‚„‚€‚‚ƒƒƒ„ƒƒƒƒƒƒƒƒ„„ƒƒ„„„„……„„…………††‡†‡‡‡‡†††‡‡‡‡‡†‡‡‡‡‡ˆ‰ˆˆ‰‡††‡ˆ‰‰ˆˆˆ‰‰‹Œ‹Š‰‰‰Š‰ˆ‡‡‡‡ˆ‰ˆ†‡‡‡†‡‡‡‡‡‡‡‡‡‡ˆ††‡††††††‡‡†………………„„ƒƒƒƒ„„„ƒƒƒ„„ƒƒƒƒƒ‚‚‚„‚‚‚‚‚ƒƒ‚‚‚…ƒ€‚‚‚ƒ„ƒ‚ƒƒƒƒƒ„„ƒƒƒƒ„„„„„„„„………†‡‡‡‡‡‡†…†‡††‡‡‡‡‡‡‡‡‡…‡‡ˆ‹Š‡…†‡‡‰‰‰‰ˆˆˆ‹Œ‹Šˆˆ‡ˆ‡††‡‡‡‡ˆ‡††‡†…†‡‡‡‡‡‡‡‡‡‡†††‡‡‡†††††††††……†…„„ƒ„„„„ƒ‚ƒƒ‚‚‚ƒƒƒƒƒƒƒ‚‚‚„ƒƒ„ƒ„ƒ‚ƒƒƒƒ„„„ƒƒƒƒƒ„„„ƒƒƒ„………††††††‡†……†‡†‡‡††‡‡‡‡‡†††‰‰‡‡‡‡‡‡‡‡‡ˆ‡ˆ‰ŠŠ‹Šˆˆ‡‡‡ˆˆˆˆ‡‡‡††…‡‡†‡‡‡‡‡‡††‡‡‡‡††‡‡‡†††††††…††……„ƒƒ„„…„ƒ‚‚ƒƒ„ƒ‚ƒƒƒƒƒ‚€ƒ„…„‚‚‚‚‚€‚ƒƒ‚‚ƒ„ƒ‚„ƒƒ‚‚ƒƒƒ„„„ƒƒƒƒƒ„…††††…†‡‡‡‡‡‡‡‡†††‡‡‡‡‡†††ˆˆ……†‡‡ˆ‰ˆ‡‡ˆ‰Š‹ŠŠŒ‹ŠŠŠ‰‰‰ˆˆ‡ˆˆ‡‡‡‡‡‡‡‡†††……†‡‡‡‡†‡‡‡‡‡‡†††………††††…„ƒƒ„ƒ„ƒƒ‚ƒƒ‚‚‚‚‚‚ƒƒ‚‚ƒ…„‚‚‚€‚‚‚‚ƒ‚‚‚‚‚ƒƒƒƒƒ‚ƒ„ƒƒ„„„„„„„„„…†‡†††††††††………………††‡‡‡†‡ˆ‡††…†‡‡ˆˆˆ‡ˆ‰ŠŠ‰Š‹Š‹‹ŠŠ‰‰Šˆˆˆ‰ˆ‡‡‡‡†…†…†‡†…†‡‡‡‡‡†‡‡‡‡†††……………††…„„„„„„ƒ‚‚‚‚‚‚‚‚ƒƒ‚‚‚‚~€€~~~~~€‚‚‚‚‚‚‚‚‚ƒƒ„„„„„„„ƒ„„„………††††‡……††…„„…†††……†‡‡‡††ˆ†…„…‡‡ˆˆ‰ˆ‡ˆ‰Š‹ŠŠŠŠ‹‰‰‰ŠŠŠŠ‰‰ˆˆ‡‡‡††‡‡††‡‡†…†‡‡‡‡‡‡†‡††…„„„„……†…„„„„…„ƒƒƒ‚‚‚‚‚‚‚€‚…††…‚~~€‚ƒ‚ƒ…„€‚‚‚‚ƒ„ƒ„„„„„……„…………„„……„„…††„„…††††††‡ˆ‡††ˆ†„„†‡‡‡ˆˆ‡‡ˆ‰‰‰‹Š‰Š‹ŠŠ‰Š‹Š‹Š‰‰‰ˆ‡‡†…†‡†‰ˆ‡†…†‡‡‡‡‡††††……„„„„„„ƒ„„„„„„ƒƒƒ‚‚‚‚‚‚‚‚‚€€„…†ˆˆ‡†ƒ„„…†…ƒ€€‚‚‚‚‚ƒ‚‚ƒƒƒ„„„„…………„„„…†††………†††††††‡‡‡‡††‡‡……††‡‡‡††‡‰‰‰‰ŒŒ‹Š‰‰Š‰‰Š‹‹Šˆˆˆˆ†††……†…†‡‡††‡†††………„„„„„„„„„„„„„ƒƒƒƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€‚„‡‡„€~}}~‚‚‚‚‚‚‚‚‚ƒƒ‚‚ƒ„ƒƒƒƒƒƒ„„„„„„………„…………†††…‡‡‡‡ˆ‡†††…†‡‡‡‡†‡‡ˆˆ‡ˆ‰‹‰ˆ‡ˆ‰‰Š‰ˆŠ‹‹Š‰‰ˆ‡†………†‡‡‡‡†††……„ƒ„„„„„„ƒ„„„„„„„…„ƒ‚‚ƒƒƒ‚‚‚‚€€€€€€€€€€€€€€‚ƒ†‰ˆ…ƒ„…‚€€‚‚‚ƒƒƒƒ„„ƒƒƒ„„„„„„„ƒ„†…†…„…††‡…†‡‡‡‡ˆˆ‡……†††‡ˆ‡‡‡ˆˆˆˆ‰ˆ‰‡‡ˆˆˆ‰‰‰‰‰‰ˆ‰ˆ‡‡‡†„ƒ‚…††‡‡…„„„……ƒƒƒƒƒ„„„……„„ƒƒ„„ƒ‚‚‚ƒ‚‚‚‚‚‚€‚‚€€€€€€€‚„†‡‚€€€€€‚‚‚ƒ‚‚ƒƒƒƒ„„„„„„„…††………†††††††ˆŠˆ†…†‡‡‡‡‡‡‡ˆˆˆˆ‰Šˆ‡†‡‰‰‰ŠŠŠ‰‰ŠŠŠ‰‡††……„‚‚…‡ˆ‡…„„„„…„„ƒƒ‚ƒƒƒ„„„ƒƒ„…„ƒ‚‚‚‚‚‚‚€€‚‚€€€€€€€€€€€€€€€€€€‚‚‚‚ƒƒƒƒ„„„……„ƒ„…………††††‡‡†‡ˆˆ†„…†‡‡‡‡‡‡‡‡ˆˆˆˆˆ‡ˆˆ‡‰ŒŠ‰Š‹ŠŠŠŠ‰‡‡ˆ‡†…„‚ƒ†‡‡†…………„„ƒƒ„ƒ‚‚ƒ‚ƒƒ„ƒƒƒƒ„ƒ‚‚‚‚‚‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒƒƒ‚‚ƒƒƒƒƒ„„ƒƒ„„…„„„„…††‡‡…‡ˆ†„„‡‡††‡‡‡ˆˆ‡‡‡ˆ‡†‡ˆˆˆˆ‹‹Š‹Œ‹‰‰‰ˆ‡‡‡ˆ†„ƒ‚‚†ˆ‡†„…………ƒƒ„„ƒƒƒƒ‚‚‚ƒƒ‚‚‚ƒ‚‚‚ƒ‚‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~€€€€‚‚‚€‚ƒƒƒƒ„„ƒƒ„…………„„„†……††‡‡…ƒ†‡‡††‡‡‡‡‡‡‡ˆˆ‡‡‡ˆ‰‰‰‰Š‹‹Œ‹‰‰ˆ‡‡‡‡‡‡†„‚ƒ‡‡†„„„„ƒ‚‚ƒ„„ƒƒƒ‚‚‚‚‚‚ƒƒ‚‚‚ƒƒ‚|}€}}}€€€€€€€€€€€~~~€€€€€€€€‚ƒƒƒƒƒ„„„„…††…„…†††††‡ˆ„…††††††‡‡‡‡‡‡‡‡‡‡‡ˆˆˆˆ‰‹‹‹‹ŠŠ‰‰ˆ‡‡†‡‡‡‡†††ˆ‡…„ƒƒƒƒƒ‚ƒ„„ƒƒƒƒ‚‚‚‚‚‚‚‚‚‚‚‚~€ƒ‚€€€~|{|€€|{|{{|}|}}~~||}||{{z{|€||}|{||~€€‚‚ƒƒ„„„…„„„…„„„………††‡‡„……………†††††‡ˆˆˆˆ‡‡‡‰ˆˆˆ‰Š‹‰ˆ‰ˆ‡ˆˆ‡‡††††………†‡†……„ƒƒƒƒ‚‚‚‚‚‚‚‚‚‚€€€€€ƒ€~}††}~~}}}|{||}€€€~€€~~€‚}~€}{}€€‚‚‚ƒƒƒƒƒ„ƒƒƒ„„„……………††…………†……†††††††‡‡ˆ‡‡ˆŠ‰‹Šˆ‰‹‰ˆ‰Š‡‡‡‡‡‡††‡†ƒ„……†…„„„„ƒƒ„„ƒ‚‚‚‚‚€€€‚}{~€ƒ‡…}€ƒ‚‚€€~‚„„„„„‚~€„€€€‚……‚€|}€€€€‚‚‚‚‚ƒ„„ƒƒ„„„…„………………†††††††…„„††…††‡ˆˆ‡‡‡‘ŒŠ‰‰‹Š‰Š‰‡‡‡†…†‡††…‚ƒˆ†„„……„„„„ƒƒƒƒƒ€€‚€€}}|~€„…‚€~………‡†‡„€€€}}‚ƒ‚ƒ„ƒ€ƒ€~}‚„‡ˆ‚€{~€€€€€‚‚‚‚ƒƒƒ„„ƒ„„……†††………„………†‡‡†…†††‡‡ˆ‰ˆ‡‡†•‘‹‰‰‰‰‰‰‡‡‡‡†„…‡††„€€€„Ї………„ƒƒƒƒƒ„ƒƒƒ€€€€€€€|~|{€…„€€€€€€€€€€€€€~}~€€€|z~€€‡…€}|€€€€‚‚‚‚‚ƒ„„ƒƒƒƒ„…†††††…†‡‡‡‡‡‡‡†…††††‡ˆ‰ˆˆ‡”’‘ŽŠ‰Š‰‰‰ˆˆ‡‡†„ƒƒ‚€€€€€…Ї…„„„„„„„„„ƒ‚‚‚€€€€€€€€€|}~~~„ƒ€€€€~~~}~~€€€~}|~~~€‚€|y~€€‚„~}€€€‚‚‚‚ƒ„ƒƒ‚ƒƒƒ„…†………†††‡‡‡††ˆ‡‡†††‡ˆ‡‡ˆˆ‡‡“ŽŠˆ‹‰ˆ‰‰‡††††„€€€€€…ˆ†††„‚ƒ„„„ƒƒ‚‚‚‚€€€€€€€€€{|~€~~~€~|}~}|{{{||~~||{{zy{|{~‚€|{~€€€~€€€€€€€‚‚ƒƒ„„„ƒƒ‚ƒƒ„…††††‡‡‡ˆˆ†††††‡‡‡‡‰Šˆ‡ˆˆˆˆ‘Œ‰‹ˆ‡ˆ‡†…†…„~€…ˆ†††…ƒ‚ƒ‚‚‚ƒ‚‚ƒƒ‚€€€€€€€€€€€~}~ƒ}~~~~~~€€€~}}|{~€~~}}}}~z~‚‚||€€€€€€‚ƒ„ƒ‚ƒ‚ƒ„„„„………†‡‡‡‡‡‡†††……‡‡‡‡ˆ‰ˆ‡‡ˆ‰‰‘’’‘ŽŠŠ‰ˆ‰ˆ‡†…‚‚€€€€€ƒˆ‡…„ƒƒƒƒ‚‚‚‚‚ƒƒ‚‚€€€€€€€€~~~ƒ€~}~~~}…†……„ƒ}}€‚‚‚‚‚€€}}||}€‚€€€€€ƒƒ‚„ƒƒƒ„„„………†‡‡ˆˆ‡‡†……††‡‡ˆˆˆ‡‡‡‡ˆ‡Š‘‘’‘ŽŒŒ‰ˆ‡‡†…ƒ€€€€€€ƒˆˆ†……ƒƒ„ƒ‚€€‚‚€€‚€€€€€€€€~}~€‚}~~~~~}€‚‚‚~~ƒ„„„„€€€€‚‚‚‚‚ƒƒƒƒƒ„„…‡†ˆˆˆ‡†„„„…‡‡‰‰ˆˆ‡‡‡‡‡ˆ‹‘‘‰‡…ƒ€€€€€€€‚ƒˆˆ†††„„ƒ‚€€€€€€€€€€€€€€~}}€~~}~~}}~~}|}~~~€‚~‚„„‚€€€€€‚‚‚‚‚‚ƒƒƒƒ…„…†‡‡ˆˆˆ‡†„……†‡‡ˆˆ‡‡‡††ˆ‰ŠŒ’‘‰„€€€€€€€€€€€†ˆ†……„„ƒ‚€‚ƒ‚‚‚‚€‚‚€€€€€€€€}~€€€~~~~||||~~€€~}}~~~~€€€‚~|‚€€€€€€€€€‚‚ƒƒ‚‚‚ƒƒƒƒ…„„…†‡‡‡ˆ‡ˆ†‡‡†…†‡‡ˆ‰‹Š‰ˆ‡††ˆŠŒŒŠ‡ƒ‚€€€€€€€€€„ˆ…ƒ„„ƒƒƒƒ‚‚‚‚ƒƒƒ‚€€€€€~~€~€~~}€~~~}{{{{||}~€~~||}~~~~~ƒ{|€€€€€€€€€‚ƒ„„…ƒ‚ƒ„……„„…„„…††‡‡†‡Šˆ†……„…‡‡‡ˆŠŠ‰ˆˆˆ‡ˆŠŒ‘‘Šˆ†‚€€€€€€€€€€€€„‰„ƒƒƒ‚ƒ‚‚‚€€‚ƒ‚‚€€~€€‚‚~~€€}~~~~~~}}}}|{z}€€~}{{||{zz}‚}|}€€€€€€€‚ƒ„„„„ƒ‚ƒ„„„…„„……††‡‡‡†ˆ‡…„ƒ„†‡‡‡ˆ‰ˆˆˆ‡‡ˆŠ‹‹‰Žˆ†…„€€~€€€€€€€€€„ˆ…„ƒƒ‚‚‚‚‚‚‚€€€~€€€€€‚€€€€€€~~€€~}z}‚~€€€€€€€‚ƒ…„‚‚‚ƒ„„………†‡‡‡‡‡†‡ˆ†…………†‡‡‡ˆˆ‡‡‡ˆˆŠŒŒŠˆ‹‰‡…„………€€€€€€€€€€€€‚‡†„ƒƒƒ‚ƒ‚€€€~}€€€~€€€€€€€‚ƒƒ‚‚‚‚€€€‚‚‚‚~}€ƒ‚€€€€€‚ƒ„„„ƒƒƒƒ„„„………†‡‡‡‡‡‡‡†„„†‡‡‡‡‡‡††††‡‡ˆŠ‰‡‡ˆ……†…„„„ƒ€€€€€€€€€€€€‚ˆ…‚‚‚‚‚‚€‚€€€€~~~~~€€€€€€€€€€€€€€€~~}}~€€ƒ‚‚‚ƒ„………ƒƒ„……„„†‡‡‡‡‡†……„…‡‡ˆˆ††……†‡‡‰Š‹ˆ†‡ƒ…†††…„ƒ€€€€€€€€€€€€‡‡ƒ‚ƒ€€€€€€~~€€€~€€~~~~~~~~~~~~~~~~~}}}}~~~~~~~}~~€€€‚ƒ€€‚ƒƒ„…„……‡†……††‡‡‡‡…†††‡ˆˆˆ†††…†‡ˆ‰‰‰ˆˆˆƒ„……ƒ‚‚€€€€€~€€€€€€…ˆ…‚‚‚‚‚€€€~€€€€€€~}|}~€~~}}~~~~}~~}}~~~~}}}~~~~~~~~€€€€€‚‚‚ƒ‚ƒ…„„…‡‡††††††‡†…††††‡ˆˆ‡……††‡ˆ‹Š‰ˆˆ‡ƒƒ‚‚ƒƒ‚€€€‚€€€€€€€€„ˆ†ƒ‚‚€€€€€€€€€€€€€€€€~|}}||}|}~~}~~~~~~~~}}}~~~~~~}}~~~~}~~€€€€}~‚€€€€€€‚ƒƒƒ…………………†††††ˆ†………„…‡ˆˆ‡††††ˆ‰‹‰ˆ‡†‡…ƒ‚€‚„ƒ€€€€€€€€€€€ƒ‡…‚‚‚€€€€€€€€€€€€€€€€€€€|{}}~~~}{}~~~~~}~}{{||}}}}}|||||}}}}}}}}~}|{{€€€|{z€€€€€€€€€‚ƒƒƒƒ„……„„……†‡††‡ˆ†„…‡„…‡††‡‡††‡‰‹‹ˆˆˆ‡‡ƒƒƒ‚‚‚‚€€€€€€€€€€€€~€†‡‚‚‚€€€€€€€€€€€~|~€€ƒ„‚}}|}~~~~~}|||{|}~}}|}}|{{}}}}}}}}}|z€€‚€|{€€€€€‚ƒ„‚‚„„„„…††‡†‡‡‰…„††††††††………‡ˆ‰‰ˆˆˆˆ‡„ƒƒ€€€€€€€€€€€€„‡ƒ‚‚€€€€€€~{}‚„ƒ€€€~~~~~}|}}{{}}}}}|}}{{}}|}}}|}€}}{€€||€€€‚ƒƒƒ‚ƒƒ„„„…„…†‡‰†‚ƒ…†††‡‡†……‡‡‡‡†‡‡‡ˆˆˆ€€€€€€€€€€€~€€€€€…„„€€€€€€€€€€€€}|~†~~~~}~~~}}}||}~~}|}}}}|{|}}|}}}~~}|}€~~|€€€{}~~~€ƒ‚‚„ƒ„„„„„ƒ„†ˆ‡€ƒƒ‚„…†††……†‡‡ˆˆ†††‡ˆˆˆ€€€€€€€€€€€€€€€€€€€€††…ƒ€€€€€€€€€€€€€€€€€||ƒ‚~}}~~~~~~~}}}|}{{}€~}~}}}}}}~||}~~~}}€~}{~€~~{}€€€€€‚ƒ„„„„„„…ˆ‹‚~€‚‚ƒ†‡‡††…†‡‡‡ˆ†‡‡‡‡‡‡€€€€€€€€€€€€€€€€€€€„‡„‚€€€€€€€€€||‚€~~}~~~~}}}}||||}}~€~~}|}}}~~}~~{||}~~}}~~}}~~‚|}€€€€€€€€€€€€‚„ƒƒƒ‚‚„‰‡‚ƒ‡†……………‡‡‡‡‡ˆˆ‡‡‡‡€€‚€€€€€€€€€€€€€€€€€€‚†ƒ€€€€€€€€€~€€~~~€~~~~~~}}}}}}|{|}€~}}~}}}}|}|}|{|~~€~~~~~~ƒ€||€€€€€€€€€€€€€‚‚€‚ƒ…Š‚€€‚……ƒƒ„„†††‡‡‡‡‡‡ˆˆˆˆ€€€€€€€€€€€€€€€€€€€€„„ƒ‚€€€€€€€€}€~~~~~~~}}}~}|||€~~}}}|}|||}{||~{{{}}~}€€~~~}{}z|€€€€€€€€€€€€ƒ‰†€€ƒƒ‚ƒ„††††‡‡ˆ‰ˆ‡‡‡‡‰€€€€€‚€€€€€€€€€€€€„†„‚€€€€€€€~~~~~~|~~~}}~}}}}}}||~€}||||||}}}|~~}{|||}~~~}{}‚~{|€€|y}~€€€€€€€€€€€€€ƒƒƒ‡‰€€€ƒƒ„…†‡……„…‡‡‡‡‡†‡‡‡€€€€€€€€€€€€€€~€€€€„„ƒ‚€€€€€~~~}~~~}}~}~~||}|{}}}|}~~~}{||}~|z{{|}|}}|||||}~€}|||€‚~|z~‚~z}~~€€€€€€€€€€€‚‚…Šƒ€€€€€€€ƒ„…††…„„„‡‡‡‡‡ˆˆ‰ˆ€€€€€€€€€€€€€€€€€„ƒƒƒ€€€€€~~~~~~}}~~}{z{|||}z{}~}}~~}|{z|{||||~~{{|~~}{}~‚z{‚{|~€€€€€€€€€€€‚‰‡€€€€~~ƒ„…†††…†‡‡‡‡‡‡‡‡ˆ€€‚€€€€€€€€€€€€€€€€†„„„‚€€€€€€€~~}~}}}~~~€||}~~~~}|}}||~}~~}|||||}y|}{|~€ƒ{|€€€€€€€€€€ƒ†‰‚€€€€€€€€ƒ…ƒ„…†‡††††‡ˆ‡‡ˆ‰€€€€‚€€€€€€€€€€€€€€€€„ƒ‚€€€€€€€€~~~~~}~€€€}|}~}||~~}}}}}|||~~~~|||}{{€~{x{}}~~~‚~{|€€€€€€€€€€€ƒ‰„~€€€€€€„„ƒ„…†…†‡……‡ˆ‡‡ˆ€€€€€€€€‚€€€€€€€€…€‚„€€€€~~~~~€€€}}~~~~}||{|||{|~}{}}|}}|}}}}{z~{y{|}}}~}~~zz~€€~~€€€€€‚…‰€€€€€€€‚ƒ„„…†‡‡…†ˆ‰Š‰Š€€€€€€€€€€~~€€€€€€€†€ƒ„‚€€€€€€~~~~~~~}}}}}}}~~~~||}}}}|{{}||||{|~~}~~}}}}}}}}zz~}||||}}}}}€||~€€€€‚…‡‚€€€€€€‚„„…†‡ˆ‡ˆ‰‰ŠŠ‹|~€€€~€€€€€€€€€€€‚ˆ~„„‚€€€€€~~~}~~}}}}}}}}}}}}}}|{|{z|{z|}~~}}}~~~}}~~||}}}}||}}~~~~~€€€€‚„„‡‚€€€€€€€€~ƒƒ„…††‡‡ˆŠŽŽqrruy{~€€€€€€€€€€‚‰ƒ€ƒ„ƒ‚€€€€€~~~~~~~}}}}||}~}}}~}}}}{{z{{{{||||||||}}|||||||||||}}}~~~~~~~~~~~~€€€€„…ƒ‡€€€€€€€€€€€~‚„„†‡‡‡‰ŒŒŽŽŽmkkmnnv€€€€€€€€€€€€ƒˆ…‚‚‚€€€€€€~~~~~~~~~~~~}}}}}}}||||}}}||{{||||}|||||{{{{{{|}}}}||}}}}}}}}~~~~~~~}~€€€€€€€€‚…‚‚ˆ€€€€€€€€ƒ†‡‡‰‰‰Œ‘kjjkkjr€€€€~€€€€€€€€€€€€‚†ƒ‚‚‚‚€€€€€~~~~~~~~}}|}}|}}}|}{{|}}||||{{{{|||||}}|||}|||}}|||}}~~~~~}}~~}~€€€€€€€„‚€ƒˆ‚€€€€€€€€€€€€€„†ˆˆˆŠŒ‘’kjhhigr€€€€€€€€€€€€€ƒ†ƒ‚‚‚‚ƒ€€€€€~~~~~~~~~}~}}}|}}}|{||{|}}||||{{{{{|||||}}||}||||||||{|}~}}}}}~~~€€€€€€€„„‚‰‚€€€€€€€€€€€€€€€€€ƒ…‡‡ˆ‹‘hhhhhdp~€€€€€€€†‡€€‚€€€€€~~}~~~}}}|}|||||||}||}}{z|||||{||}|{||{{{||{||}}}|{|||}}}~~~~~€€€€‚„‚ƒ„‰‚€€€€€€€€€€€€€€ƒ„†ˆ‰ŒŒfffefbp€€€€€€€€€€€€€†ˆ€ƒ€€€€~~~~~~~}}}}}}||||||||||{|~}}{z~}}{z~}{{{{z|}}~}}|}}}}}}~~~~~~~~€€€€€€‚ƒƒ‚„‰‚€€€€€€€‚„†ˆ‰‹ŒŒŽffedd`p~€~€€€€€€€‡‰€€€‚‚‚€€€€€~~~~}~~~~}}}~}}|||{{||{{|||}}~~~}~}z}{z|}~}z|}|||||}}}}|}}}~~~€€€€ƒ„„ƒƒ‡ƒ€€€€€€€€€€~€€‚†‡‡ˆ‰‹ŒŽeeeecbr€€€~~€€€€€€€€€€ˆˆ€€€‚€€€€€€~~~}~~}||}}}}|||}}z|~}~~|}}~}~}{||y|||~xz|{||}}}}~~}~~~~~€€€€€€€€€‚ƒ‚‚‚‚‚†„€€€€€€€€€€€€ƒ††‡ˆ‰‹ŽŽccccb`q~~€€€€€~€€€ˆ‡€€€€€€€€€€€~~~~||}}}}|||~|{}}}}}|{{~|{{{{|y{|}~x|}}||}}}}~~~~€€€€~€‚‚‚ƒ‡„€€€€€€€€€€‚„„‡‡ˆŠŽŽŽedbaa_p€€€€€€€€€€€€€€€ˆ‡€€€€€€€€‚€€~~~}}~~~~~~}}}}}|{{{{||}}|z{}}|}}}}}}{{|{|}|zz~~}|{}~}}}~~~~~~}}~€‚‚€ƒ‡†€€€€€€€€€~……†‡‡ŠŒŽŽdddcb_q€€€€€€€€€€€€€€~€€Šˆ€€€€€ƒ‚€€€~||}}~~}}}}||{|}}||{|~}{{}}|||{|}|{{}|{{z{~~{{|||||||}}}~~~~€~~~ƒ‚€€‚ƒ‰‡€€€~~~~€€€…†…‡‡Š‹ŒŽcccca`p€€€€€€€€€€€‰‡€€€€€€€€€€€€€€€}}}}}|}}|}}|{|}}||}}}}|||}|}}{{{|~}}}~~~~}{|}}}}||||}}~~~~~‚„ƒ€‚‚ˆ‡~~~ƒ†‡‡‡ˆŠ‹Œcbbccan€€€~~€€€€Ї€€€€€€€€€€€€€€€€~}}}||||}}|{{{{||{{}~}|||{{}}}}}}||}~~}}|z{}}|||{{|}}~~~~€ƒ‚‚€€€‡‡€€~~‚†‡‡‡‰‹Ždddcbal€€~€€€‚€€€‚Š…€€€€€€€€€€€~~}}~~}}}||||{{{{||{{{||{{{zz||||{{{|||{{|{{{{{|}}}}}}~~€~~~~€‚‚€€€€‡‡€€€€€€€€~ƒ†‡‡ˆ‰‹ŒŽcccbbaj~€€€€€€€€€~‹„€€€€€€€~~~}~~~}|}}||||}}|||||||{{{{{|{{zzzz{{|{{{{{{{{|}}}~~~~~~~~~~~€€€€€€€€‡‡€€€€€€€€€€€~€„†‡†ˆ‰‹Œdcccdcj~€€€€€€€€€€€Š…€€€€€€€€€€€~~~}}||{|}}}}}|}~~~~~}}}}|||{z{{{{}}}~}}}}||||}}}~~~~~}}~~~€‚€€€€€‚†‡€€€€€€ƒ‡‡†‰ˆˆŒ“ddeeecg|€~€€€€€€€€€€€ˆ„€€€€€€€€€€€€€€~~~~}}||||}}}}{{||{{|z{}~}|}|{|}~~}zz||z{|~~}}}}}~~}~~~~}~~ƒ„€€€€€ƒ€€‡†€€€€€€€€€€†††‰‰ŠŒ’eeeffdf{€€€€€€€€€€€€€€‰„€€€€€€€€€€€€~~~~}}|||}}|}|zzzzz{{}~}}|{|~}z}}|||{{z{|~~~|}~~~~~~|||}~€€‚„‚€€€ƒ€‡‡€€€€€€€€€€ƒƒ‚…†‡‡ˆ‹Ž‘fghfhfgz€€€€€€€€€€€€€€Š…€€€€€€€€€€€€€€€€~~~~~}}}||||||}}|{zzz{{{|~}|{~}z{}{zz{{{|}z|~}}}}}}~~}|}~~€ƒƒ€€€~‚‡‡~€€€€€€€ƒ„‚ƒ„…†ˆ‹efffgegx€€€€€€€€€€~‚‹…‚‚€€€€€€€€€€€€€~~~~}~}|}}}{{||~~}}}{|~|}}||}{||z{}~~}}|~}z|}}}~~~~}}}}~€€€€€€€€ƒˆ†€€€€€€€€€€€€„…„†‡‡ŠŽeffhjjiv~€€€€€€€€€€‚‹„‚€€‚€€€€€€€€€€€~~~}}}}|}}}{{||~}|||||}|||||||{{{|}}||}~}}{|~~~~~~~~~}}~€€‚ƒ€€€€~€ƒ€€ƒŠ„~€€€€€€€~€€‚„……†ˆˆŠijkjkkks~€€€€~€€€‰…‚€€€€€€€€€€€€€€€€~~~~}}}}{{}}~|{{||}|}{{|}~z||{{|}}}~~}|{}|}}~~~~€‚€€~‚ƒ€‰ƒ~€‚€ƒ‚„†ˆ‰‹jjjjloms}€~€€€€€€€€€€‰…‚‚€€€€€~€€€€€€€~~}}}~|y}~~|||||}|}|{~||||}}}|||}}~~}|{|{|}}}~~€€€‚€€€€€‚ƒ‰ƒ€€€€€€€€€€€€€€‚„…‡‡Šmlmklmnu~€€€€€€€€€€€Š„‚€€€€€€€€€€€€€€€€€€~}}~~~~|{}|}}}}}|}}~|{}z{{|}|||||||}€~|z}~}}}}}~€€€€€€€€„€‚Šƒ€€€€€€€€€€€€€€…†‡‡ˆnmoonoqu}€€€€€€€€€€€‚‰…‚€€€€€€€€€€€€€€€€€€}}~~|}~}}}}}}}}}~|{}{|{{|{{||||}~€|zz}}}}}}~~€€€€€€€€€€€€„€€„Œƒ€€€€€€~€€‚‚ƒ„ƒ…†noopqpps}€€€€€€€€€€€€€…‡„‚€€€€€€~€€€€€€~~~~{|}~}}}|}||{|~{{||}||||}}~~|{z}}}}~~€€€€€€€€€€€€‚„€„‹‚€€€‚‚ƒƒ‚ƒnpnnqrrs|€€€€€€€€€€€€€€ˆ„‚‚€€€€€€€€€€€€~~~~}||}~}}}~}|||~}{z||}}~}}}€||z|}|}~~€€€€€€€€€€~€€„ƒ€„‰€€€€€€~€€€‚ƒ…oonprstu{€€€€€€€€…Šƒƒƒ€€€€€€€€€€~}~}}}€€~|}|}~}|z{{}~}}‚€|zy|}}~~~€€€€€€€~‚…‚€€…ˆ‚€€€€€€€€~€€€‚…qrqrssuvz€€€€€€€€€€€†ˆƒ‚€€€€€€€€~€~~~}~}}~~~|}€~~}|}}|{~~~}{z||~€€€€~|y|}}}~~~€‚€€€‚„€€€…‡…€€€€€€€€€€€€ƒ‚‚‚rsrsuvvwy€€€€€€€€€€‚‰…€ƒ‚‚€€€‚‚€€~}€~~~{}~|z{|}}|{|}}}}|{|~}||||||{x{}|~~~~€€€‚€~€€„ƒ€„‡‰‚€€€€€€€€€‚ƒ‚‚‚rrsuwuvwy€€€€€„Š„ƒƒƒ‚€€‚€€€€€€€~~€€~~€€~}}{{{{{{zz{z|~}||}}}~|{|}}}}|zx{~}|~~~€€€€~}}€‚„„…ˆƒ€€€€€€€€€€€€ƒ„‚‚ƒ‚‚stuvxwwwy~€€€€€€€€‰‹ƒ‚„„ƒ‚‚‚€€€€€€€€€€€~~}}~~~~~~~||{||{{{}~}}}}~}}}~~}|{yyxxy}~~~~~€€‚€€€€€~~€€ƒƒ‚‚‚„„††€€€€€€€~€€€‚€rtuwxxxxy}€~€€€€€€€€‚‹ˆ‚‚„„ƒƒ‚‚‚€€€‚€€€€€€€‚~~||}}}}~~~~~€~~}}~}}}}~~~|}}}}}}~€€€€€„ƒ€€€€~€€…ƒ‚ƒ„„…‰€€€€€€€€€‚‚„ƒ€tuwwxxyz{|€€€€€€€€€€€€„‹…ƒ„„„ƒƒ‚€€€‚€€€€€ƒ€€}}|}}}}~~~~~~~}~~~~~}}~~~~}~~~}~~}}}~€€€€ƒ‚€‚‚€€€€€€€€€‚…€ƒ„ƒ„‰„€€€€€€ƒƒ‚ƒƒƒ‚€wwxyyyz{{}€€€€€€€€€€€€‡‹†„„„„ƒ‚‚€‚‚€€€€~~€€€€€€~}||}}}}}~~}}}}~~}}~~}~}}}}|||||~~€€‚„‚~€€€€€€‚„€€€ƒ„ƒ„††€€€€€€€€€€€‚ƒ‚‚‚‚ƒ‚€‚‚‚wxxyy{~€€€€€€€€€€€‹‹†„‚ƒ…„ƒ‚‚‚‚€€€‚€€~~‚ƒ€€€}~€~}}}|||}}|}~~}}~~~~}}}|}}}||}}~~€€‚„‚‚„~~~}z~€€€€‚„‚€€‚„ƒƒ…‰€€€€€€€€€€€‚„‚‚‚‚ƒxz{|~~€€€€€€€€€€ƒŒ‰…„‚„†…„‚‚ƒ‚€€‚€€}}„ƒ€€}}}€€~~}|~}}}}}}}}}}|||}€‚ƒƒ‚€€‚|…„|}}|x}€€€€€ƒ†ƒ€‚ƒ…ƒ‚……Š„€€€€€€€€€€€€‚‚ƒƒƒ‚ƒ„ƒƒ{~€€€€€€€€€€€€€€…Œ‡……ƒƒ„„ƒƒ‚‚‚‚€}}€„„€€~}~€~||~~~~~~}‚€~}}~~~€‚ƒ~|ƒ†€|~€}|€€€„„‚€€‚‚‚…ƒ‚„…‰…€€€€€€€€€€ƒƒ‚‚‚‚‚‚„ƒƒ€€€€€ƒ‚€€€€€‚ˆ‹††…ƒ‚ƒƒƒƒƒƒ‚€‚‚‚~}…‚}|~€€~~€~~~€€€~€ƒ€|zyxxx{~~|€ƒ‚|}…‚~~€€€€€€€‚…ƒ€€‚‚ƒ…‚ƒ……‡ˆ€€€€€€‚€€ƒƒƒƒƒƒ‚ƒ„ƒƒ„ƒ€€€€€€€€‚‚Š‹††…„‚„„„ƒƒƒƒ€€€€€~~€~ƒ…€|}€€€~€€€~ƒ€|xwy|~}}{{z|€ƒ€|€‚}~€~€€€‚„„‚€‚‚ƒ„…‚ƒ……†‰„€€€€€€€…ƒ‚„„ƒƒƒƒ„„‚„„€‚‚‚‚‚‚€€€€‚€€€€‚‹‰†††„‚ƒ„„„ƒƒƒƒ‚€€€€€€€€~‚}†}~€€€€€}|{}}€~ƒ€}yy~€€€€{{|{|}€€€€€€€‚‚ƒ…‚€€‚ƒƒ„„‚ƒ„…†‡…€€€€€ƒ„†„‚„„ƒƒƒ„„ƒ‚‚ƒ‚„……‚€€€€€€€€€€…Œ‡‡‡†„‚ƒ„„„ƒƒƒ‚‚€€€€€€~‚|‚€}|~‚‚€€€||{}}~€€€€‚|zz€€€€€€€‚{|€~{|€€€€€€€€€€‚‚„„€‚‚‚ƒ„ƒƒ„„…†‡ˆ€€€€€ƒ€€‚„††‚‚„ƒ‚ƒ‚‚ƒ„ƒ„„ƒ„‚€€€€€€ˆŒ†††…„‚ƒ†……„„„„‚€‚€€€€‚‚}}~||‚‚€€€‚||~~}€€€€€€|}{€€€€ƒ|}‚~{~€€€€ƒ„ƒ‚€‚‚‚ƒ…„ƒ„„…„†Šƒ€€€€‚ƒ€„…†„ƒ…ƒ‚ƒ‚‚‚‚ƒƒ„…………„ƒ€€€€€€€€€Š‹†………„„…††…„„„„ƒ‚€€€}€€~|}‚‚€€€€}}}€|~~‚€€~}}€€€€‚„„ƒƒ|~ƒ|€€€€‚‚€€‚…„€‚…†„ƒ„„„…†‰‡€€‚‚‚„†…„‚„…ƒ‚‚‚ƒ„‚……„ƒ‚„†„„€~€€‚„‹Š†††……ƒƒ…†…„„„…„ƒ‚‚‚‚‚‚~€}ƒ‚€€~}~‚‚}|}ƒ‚~|‚€€€€€€€~{~€|{‚}€€‡ƒ‚‚ƒ…‚€€€‚ƒ‚‚…†„‚ƒ„………‡ˆ‚€€‚ƒ„„ƒ……„…„ƒƒƒƒ‚‚ƒƒ„‚„„…†……„‚‚€€€€€€€„Љ‡‡‡‡†„„„„„„„„„ƒ‚‚‚‚~~ƒ|€|„‚€€~~…„„…|}€‚‚€~~€‚€€€{|~}{€}z~}~„„„‡†y|ƒ„ƒ‚‚ƒ„ƒƒ…†ƒƒ„„……†‡ˆ†€€„…„ƒƒƒƒƒ…‡ƒƒ‚‚‚‚ƒƒ…„……„…††…„„ƒ~€€€€‡‰‰‡‡‡‡†ƒ„………„„„„„‚€‚‚€|z‚€}€{~~~~~€€~~‚‚‚€~~€~~€~xx|}}}€ƒ~z}{{ƒ„ƒ€}y~€„…ƒ‚ƒ‚‚ƒ…‡…ƒƒ„„…†‡‡ˆ‡€€ƒ„ƒ‚ƒƒ…„‚‚‚ƒ†††††‡‡‡†…‡†€€€€€‚ˆˆ‡‡‡‡††„„††…„ƒ„„ƒƒ€‚€€~z~€€~|~€€~}}|{{}}}}€€€}}~€ƒ€~~~€„€‚ƒ~|z{}~~|z€‚„„ƒ€‚‚‚„††……„„„…†…†ˆˆ„€‚„ƒ‚‚„ƒ‚‚„„‚‚ƒ‚‚„††………†††‡†††€€€€€€†ˆ‡ˆ‡‡‡‡‡†„„„………„„„ƒƒ‚‚€€€€€|~‚ƒ‚€€~}~|{{zzz}~€€~|}~„ƒ€……~|}~€€|{{{yz„…„‚‚‚‚ƒ„††…„„„„„………†‰‰€€‚ƒ‚‚ƒ„…ƒ…†ƒ„ƒ„……ƒƒ†††…„†††††‡†€€„Œ‡‡‡‡‡‡†††„ƒ„…„„„„„ƒƒ‚‚€€‚€z{€ƒ|~~€€~€ƒ|z}‚ƒƒ„‚€~{}‚‚~}‚‚‚~{{|€€ƒ…„‚€‚ƒƒƒ„„†ˆ…„ƒƒ…†‡‡…†‰Šƒƒ‚„„………‡……††††„‚‡†ˆˆ‡‡‰ˆ††‡‡ƒ~€€€‡Š‡†‡‡‡††††„‚…††…………ƒƒ‚‚€‚‚€‚€}}€}yy{€€€€€|{|~}{}~~€€{{€‚ƒzz‚‚‚€€‚ƒ„„ƒ‚‚ƒƒƒ‚ƒ„…‡†„„………‡‡‡††ˆ‰‚€€‚ƒƒ„………………‡ˆ†ƒ‚†‡‰‰ˆ‡‡‡‡‡‡ˆ†€€€‚‰‡‡‡‡ˆ‡‡‡‡‡…ƒ…‡†††††…„ƒ‚‚‚ƒ„ƒƒ‚‚‚€€€€€€€€€€€€€|zz{}€€€~{||}}~}{|€|}€€€€‚‚ƒ……„ƒ‚ƒƒ„ƒƒ…††…ƒƒ„††‡‡‡‡‡‡‰‡…„„„ƒ‚ƒ…………„ƒ……ƒ‚ƒ‡…†‡†„„†‡‡†‡‡ƒ€€‚ƒ†Š‡ˆ‡‡‡††‡‡‡…‚„†††††††……„ƒƒƒ„„ƒ‚‚€€€€€€€€€€€€€€€€€€€~|yyz{~‚€‚‚€€€‚‚„†…„ƒ‚‚„„„„…†‡†…„„„„†‡‡‡‡‡ˆˆˆ†„‚ƒ……ƒ‚€ƒ…††…ƒ„†‚ƒˆ‡†‡ˆ‡‡ˆˆˆ‡‡ˆ„€ƒŠ‰‡‡‡ˆˆˆˆˆ‡‡†„ƒ…†‡…………………„‚‚ƒƒ‚‚‚‚‚‚‚‚‚‚€€€€€‚‚€€€€€€€€€€€€€€€€€€€‚ƒ‚‚‚‚€‚ƒ‚‚„„„ƒƒ‚‚ƒ„„„ƒ…†ˆ†ƒ„…………††‡‡†‡ˆˆ‡ƒ‚‚†…ƒƒ‚ƒ‡‡…†„ƒ…‚€‡‡‡‡‡‡ˆˆˆ‰ˆˆˆƒ‚‡Œˆ‡ˆ‡‡‰Š‰‡‡ˆ†„„…†††……†………„‚ƒƒ‚ƒƒƒ‚‚‚‚‚ƒƒ‚‚‚‚‚‚€~€€‚‚‚‚‚€‚‚‚‚‚‚‚‚‚ƒƒƒƒ„……„ƒ‚‚ƒ„„„…†‡‡…„…†…†‡‡‡ˆˆ‡‡‡ˆˆƒ‚†…ƒ‚ƒƒƒ„……ƒƒƒ‚ƒƒˆ‡ˆ‡†‡‡‰ˆ‡††…„€‚‹‰‡‰‡‡‰‰‰‡‡‰‡„ƒ…‡‡†††…†††…„„ƒƒƒ‚ƒƒ‚‚‚‚‚‚€‚‚€€€€€€‚‚‚‚‚‚‚‚ƒƒƒƒƒ‚‚‚ƒ‚‚‚‚‚‚‚ƒ„…„„„ƒ‚‚ƒ‚ƒƒ…††‡†„„„…††‡††††††‡ˆ‰†„ƒƒ…ƒ‚„„„…†„„ƒƒ‡…‰‡‡‡†††ˆˆ‡‡‡‡†€~‡Š‡‡ˆ‡‰‹Šˆˆˆ‰ˆ…ƒ…††‡††††††……ƒƒ‚‚‚ƒƒ‚‚‚ƒƒ‚‚€€€€€€€€€‚‚‚ƒ‚‚ƒƒ‚‚‚„„ƒ„ƒƒ‚‚‚‚€€‚ƒƒƒ„††„„„ƒƒ„ƒƒ„…††‡…„…††††††…††††‡ˆŠ‡„†††…ƒƒ„†…„…………„…„Šˆ‡††‡‡‡……‡‡‡‡…‚ŠŒŠ‡ˆ‰ˆˆŠ‰‡‡‰‰ˆ†„„……‡†††‡††††„ƒ„„ƒƒ‚‚ƒƒƒƒ‚‚‚‚‚€€€ƒƒ‚‚‚ƒ‚ƒƒ‚‚‚‚ƒƒ‚€€€ƒ„…„††„ƒ„„‚‚„„„„…††‡‡„„…„…††‡ˆ‡†††‡‡‡‰‰ƒ‡†…„„††††………………… \ No newline at end of file diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240.yu12 b/libs/jpegrecoverymap/tests/data/minnie-320x240.yu12 new file mode 100644 index 0000000000..0d66f53029 --- /dev/null +++ b/libs/jpegrecoverymap/tests/data/minnie-320x240.yu12 @@ -0,0 +1,1930 @@ +ØÖÖÓÑÑÏËÈÈÈÅÅÃÃÂÅÂÁÀÁÀ½¹¶³°«¬±´³²±´¯–‘ž°¯¯±´³³´µ¹»½½½»¸¸·¹»¼½¿ÁÃÆÇÉÌÎÏÐÎÍÌËÊÊÉÊÍÌËÊÆÇÈËÊÌÌÉÈÇÆÅÄÄÅÆÇÆÆÇÇÆÅÃÄÂÃÁÁÁÁÁ¿¾¼¼¼¼½»»¸·¹¸·¶µ³²²²²²±°¯¯®®®ª©¦£¤¥¤“}vrswqnpsqqklspmjmnlknw{’™›œ›š›››™›ššœœšœžŸ¡¢¤§«¯³µ¸ºº¼¼»º¹µ²­¥ž—”’”¦®´µ·¸¹º»»¼»¼¼½¾¾¾½¼º¹¸¹º»¼»½ÀÂÃÆÊËÊÊÍÍÌÌÉžºµ±­©©­´¶¶¶³²²³°¬©­²³¯©¢¡¨±´·µµµ³­›…„•¬¸º¹··³¨ ¦¯±£W9ÙÙ×ÕÒÑÏÎÉÇÇÈÅ¿¿ÁÄÂÀÀļ·´±­¬«®±²°­°§Ž›­®¯¯°²²²µ¹»½¼¼¼º¸¸¹»½¾¾ÁÅÇÆÉÍÏÐÏÎÎÌËÌÌËËËËÌÊÇÇÉÊËÌÌÌÊÉÈÈÆÇÇÇÈÇÇÆÆÆÆÆÆÄÃÃÃÂÂÁ¿½¼¼½½¾»¹¹¹¹¹·¶¶µ´µ´²±²±¯¯­­®­¨¤¢¥¤f96999:7464336753/01,..2344N“žœœššœœœœžž¡£¦«®°³µ¸»¼½½¼¼¹´®¨¡›•”–𤫳¶¶·¸º¼½½¾¼½½¾¾¾¿¿½»¹¹ººº»¼¾ÀÂÅÈËÌËÎÏÐÎÊÆÄ½¸µ±¬¨§¯·¶µ´²±²²°­©®²³®§¥¥«°³¶¶µ´¯¨“‚ˆ¡³¶¸···³¨¡§°´²w:ÚÚØÖÓÒÐÐÍÊÇÇŽ¾½¿ÁÁÁÃÄÁ»µ²¯®ª«°°¬¥£¬“¨­®¯°±°±µº½¼¼¼¼»¹¸¹º¼½¿ÁÅÈÉÌÎÐÐÎÍÍÊÊÌÍÍÌÌÌËÉÈÈÈÊËÌËËËÊÊÉÈÈÈÇÆÅÆÆÆÆÆÇÆÆÄÄÃÂÂÁ¿½¼½½½¾¼¹ºº¹¸·¶µµ´´µ´²±±¯¯­­­¬¬§¤ªr'Rouwwvzy{zxyzyxtsvurkgce_F U¢žœ›››œœ››Ÿ ¡£¨«°±³µ¸»½¿¾¾½º·°«£›–••˜ ¨®´¶¶¹»¼½½¾¾¾¾¾¾¾¾¿¿¼»º¹¹º»¼½¿ÁÄÇÊÍÍÍÑÒÏËÉÄÁ»·´±­§¨²·¶´³³³²²¯«©®²³­¨¤¦°³··¶µ³®£‰–®µµ¶¸¸¸²¤ §±¶·OÙ×ÙØÔÑÐÍÎËÈÅÄÃÀ¿¼º¼½ÀÁÂĺµ²¯®­­®©™”ž£–£ª­¯°°±°²¹½½¼»¼º¹¸¹¹»¼¿ÁÄÈÊÌÎÑÑÏÍÌÊÊËÌÌÌËÌÊÉÉÈÈÊËËÊÊËÊÊÈÉÉÈÇÆÆÆÅÆÅÄÆÃÄÄÁÁÂÂÁ¿½»¼½¾½»¼»º¹·¶¶··´´µµ´²±°°®­¯®«ª¥¤@: ¬ª¬©ª¨§§©ª§©¨§©¦£¦¦£•¥£¤•;:—Ÿžœ™š››œ›œœŸ£¥§«®±´·»½¾¿À¿½»¸³¬§Ÿ™•”–ž¦­°³¶¹»½¾½¾½¾¾¾¿¿¿¿¿½¼»º»º»¼¼½ÁÂÇÊÊÌÏÑÑÐÏÌȾ¹´¯¯¬§«´¶¶´µ¶³²±¯ª¨­³²«¨¢©¶¹ºº·¶²¬™~‹¢°±²µ·¹¶®£ ¦±··¯oÛ×Ö×ÕÒÐÌÍÊÇÇÅÁ¿¾º¸·¹¼¿Á¿¹µ²¯®®°«—‰‘œš¡©ª­®²²°³º¼¼¼»»º¹¹¸¸¹¼¿ÀÄÇÈÌÎÐÑÏÎÍËÉÊËËÍÍËËÊÉÈÈËËÊÊËËÊÉÊËËÉÇÆÈÇÇÆÄÄÅÄÄÄÃÃÂÂÁ¿½½½¾¾½»º»»¹¹¸¸¹º·µ¸·¶¶³²³°°°°®­ª 5M¦¡¢¡¢¤¤¦¥£££¤¤¢  ¡ £}>Ž¡ž¥I1‘Ÿœš™™›ššš›œ›œ £¦«¯²´·º»¾¿¿¿¾½»µ®©£›˜–•›£©¯³¶·¹½¾¿¿¿¿½½¾¾¿¿À¿¾½»º¼»¼¼½¿ÁÃÇÊÌÎÏÑÑÐÎÊÆÁ¼¶±«ª¨¨­³µµ´´´´³²¯©§­±®«¦£­´¸º¹¶´°¤‰€’§«­°³¶·²­£ §±¸¹²•ÛÙ×ÓÑÓÏËÊÈÄÄÃÀÁ¿¼º¶´µ¸½ÀÃÿ¹´²°­¬«£ˆˆ”›Ÿ§ª«¬°±¯°º½½½½»»º¹·¸º¼ÀÀÃÆÈËÎÐÐÐÏÍÌÊËÌÌÍÌËËÊÉÇÈÊËÊÊÉÊÊËËÉÈÈÈÉÈÇÆÇÇÆÄÃÄÃÅÄÃÂÂÀ¾¾ÀÀÀ¿½»»¼¼»ºº¹¹¹¸¸¸¸¸µµµ³³³²°¯¬5T§¡£¢¤¥¦¦¦¦¥¥¥¦¤¢¢¡ ¢T1s¥Ÿ¢F)sš›—–————˜™››ž £¦ª¯³¶¸º½¿¿¿¾¾½»¹±­¦Ÿ™•–›£¨®³µ·¹»½¾À¿¾¾¾½½¾¿ÀÀ¾¾¼ºº»»½¾¿ÀÂÄÈÊÍÏÏÐÑÏÌÈÄ¿º´­©§¦§°µ´´³´³³³²¯¨©±±­¨¢¥°¶¸ºº·²¬ƒ‰˜¤¨¬¯²µ¶²­¤¤§²»º²¬ÚØØÕÐÏÎÌËÉÃÁÀ¿¿À¾º·²°²¶»¿Â¼¸´²®¨©ª„Œ”ž©©««®°±°¸»¼½½»»»¹¹¸»½¿ÂÄÆÆÊÏÑÑÑÐÍËÍÎÎÎÌËÊÈÊÊÇÆÉÊÌÌËËÊÊÉÈÇÈÈÇÇÇÇÈÉÈÅÄÄÄÆÄÃÄÂÁÀ¿¿¿ÀÀ¾¾½½½¼»¼»¹¸¹º¹··¶µ´´µ´²²°­™2Z©¤¦¥¦¥¦¥¦¥¥¤¥¤¤¤£¢£‡HeP¡ ¡@&-Hh‰—–’”—ššœ ¤¦©­°µº»½¿¿ÀÀ¿¿¾»¶®©¡œ˜•™¢ª®²¶·ºº¼¾¾¾½½½½»½¿¿À¿¿¾»¼»º¼½¿¿ÂÄÆÊËÎÏÐÒÒÎËÇ»·°«¨¦¥¦­²³´´´´µ´²­¦©¯¯­¥ ¥·¹»»º·°§Š™¥©¬°²µµ´«¡¤©´½»³¯ØÕÖÕÑÌÊÊÉÇÄÁ½»¾¿¿»¸³¯­¯´¹½Á¿»¶²­©§­£…ˆŽ—¥§©«¬®­®·¼¾»¹»¼»ººº»½¿ÂÄÆÇÍÑÑÓÒÏÍÌÌËÌÍÌËËËËÊÈÈÉÉËÌÍÌÊÊÉÈÈÇÆÄÅÅÆÉÉÇÅÄÄÆÅÂÂÃÃÁÀ¿¾¾ÀÀ¿¾½¾¾½»»º¹¸ºº¹¸¸·¶¶¶µµ´³±°–0b­¥¦¦§¥¥¦¦¥¥¤¥¤¤£££¨_gA‘¡š8$A,&6W†—›š›ž ¤§ª®²µ¹»½¿¿ÀÀÀ¾½¼·±©£ž™—™ž¦«¯²¶¸¼½¿¿½»»¼½»»½¿¿¿¿½½»»ººº»½ÀÃÆÉÌÍÐÑÑÒÒÏËÆÀ¹³­©¨¥¤¨¬°²³³´··¶±«¤¨­­©¢ž«¸½»¹·³® „ƒŸ¨«®¯°µ·²§Ÿ£«´»½µ®ÐÌÎÐÒÎÊÇÆÆÅÁ¾¼¼¾¾½¹´°«¬®²·½¿½¶³®ª§¬°‰Œ” ¤¦¨ª©©ª¶¼¼º¹¹¹º»º»½¾ÁÂÅÈÊÍÐÐÒÑÎÌËËÌÌÍÍÌËËÊÈÇÆÆÈËËÊËÊÊÊÉÉÈÆÄÄÅÅÅÆÆÅÆÄÅÅÄÄÄÿ½½½¾¿ÀÁÀ¿½½½»»º¹·¹¹¹»º¹¹¸¶µ´³´³³•/l²§¦¦§§¨¨¨¨§¦¦¥¥¥¤¤—?S_8z¤”06‘}V5$.YšŸž¡¦ª¬®²µ¸¼¼¿ÁÁÂÁÀ¿½·³­§¢™™Ÿ¥¬°±´¸»½ÀÁÀ¾½¾¿¿¼½¿À¿¾¾¿½º¹¹¹¹»¾ÂÄÈÌÍÑÓÓÓÓÐÍÇž·±¬¨¦¤¥ª°²²²³µ··´¯¨£§««¤Ÿ¡±¹½»·µ±ª“‰•¢ª¬¬­²¶¶¯§ ¦­´»¼·­ÂÀÃÈÇËÊÇÅÄÁÁ¿½»»¼¼¹¶²­¬«¬±´º½º´°ª¦§±¨‡‹’¡£§©¨©«´»¼º»¼ºº¹º¼½¾ÁÂÆÆÊÎÑÑÑÐÎÊÊÍÏÍÌÎÍËÊÉÉÈÄÆÉËÌËËËÊÊÊÊÉÉÇÅÅÅÄÄÄÄÅÅÄÄÅÅÅÃÀ¾¼¼¼¾¿¿¾¿¿½½»¹¹¸·¸¹¹»ºº»¸·µ´³´³´”0xµª§¥¥§¨¨¨¦§§¥¤¦¦¤¥vXzvT^ ,C¢¤¡ˆ_;.2Mu•¥¥ª®²µ·»¼½ÁÂÂÃÂÀ¿»¶¯©£¡œš›£¨®²´¸»½¾ÀÀÀ¾½¾¾½¾¿ÁÂÁ¿À¿¾»º¹º»¾ÀÃÈÊÍÐÓÕÕÔÒÎÊÅÀ¹³®¨¦¥£§¬¯±±²²µ¶¶³®¨£§«¦Ÿ›¦µ»¼º·²­¢†ƒœ§­­­¯´µ´¯¤¡§­µ¼½¸°¹µ³¼ÁÄÆÈÆÃÂÀ¾¾»ºººº¶³±¬©¨©®´¹¼¶±¬¨¥§±—‰™ ¢¦©¨©«³º¼½¼»»»ºº½½¾ÀÀÄÇÉÎÑÑÐÑÎÌËÌËÌÍÍËÊÉÉÈÉÆÆÈËÌÍÍËÊËÌËËÊÉÈÉÆÅÅÃÄÅÆÅÃÄÄÄÃÁ¿¾½¿¿À¿¿¿À½½¼ºº¸¸¹ºººº»»¸¶¶´´µµº“2‚¸®«¦¤¥¦¦¦¤¤£¡£§¥¤¤qЧ¦ŒW™ƒ'LŸ›¡¡ŽjI93Cjž­°µ¸»¼¾ÀÃÃÂÁÀ¿½¸³­¦¡ž››ž¦«°´¶º½¾¿À¿À¿½½¾½¾ÀÂÂÁ¿¾½¼º¹»¼¿ÂÄÇÌÎÐÓÕÔÔÓÏËǼµ°ª¥¢£¤©®²³´³µ¸¸¶³®¦¢¨ª¢™š«¸¼»¸´°ª˜€…“¢ª®®¯³²µµ¬¢¡¥¬¶¼¿º²¶´²±·¼¿ÅÆÄÂÁ¾»½¼º¹¸µ²²¯«§¦¨¬¯·¹´®¬§£«®Š˜ ¢¤¨ªªª³º¼¿¾¼»»»»¼½À¿ÀÁÅÈÍÏÐÐÐÍÌËÌÌËËËËÉÊÊÉÈÇÇÇÉËÍÍËÊÌËÊÌËÉÉÉÉÅÅÅÅÆÆÅÄÄÂÃÁÀÀ¿¿ÀÀÀÀ¿¼¾¿¿¿½¼ºº»ºººººº¸¶¶µµ´´¼ˆ3ˆº°­ª¦¦¦¥¦¤¢¡Ÿž£¤¤£ £Ÿ››ƒ$W šš› ¢žŠmR.T­¶¶»½¾ÁÃÄÃÂÁÀÀ»¶±«£Ÿœ›ž¤«¯³¶¸»½¿ÁÀÁÀÀ¾½¾¿ÀÂÂÃÂÀ¾¼¼¼»¾¿ÁÅÇÊÍÐÒÓÓÔÔÐÍÈÿ¹±«§¤¢¢¤¨¬°³¶¶¹¹·¶³¬¤£ª§¡™¡²·¹¹¸´°¦†ƒ‡˜¤«®¯³´µ¸´«¢£§­¸¾¿½²º²°¯±µº½ÂÃÃÁ¿»»»ºº¹¸³±¯¬§¥¤¥§¯³¶³¯«¦¨´ª‡”¢¥¦©«©ª±·»¾¾¾¼ºº¼»¾¿ÁÃÃÆÈÍÐÏÐÎÌËËÌÌËËÊÊÉÊÉÉÈÇÆÇÊÊÌÌÊÈÊÉÊËÌÊËÉÈÇÇÇÅÄÅÅÄÅÄÄÂÀÀ¿ÀÀÀÀÀ¿½½¿¿¾¿½º»»º¹¹¹¸¸¸··¸¶´²¼5Œ¹°®­«ª©§¥£¦x𣢤£¤£¡žœœž€ ^£œœ››i‡­¬¥X/˜º¹¼¿ÁÁÂÄÃÂÁÁ¾¹³®¨¢œ›¤ª¯²µ·º¼¾¿ÁÀÁÀÀ¼»¼¾¾ÀÁÀ¿¿½½½¼¼¾ÁÄÆÊËÎÐÒÔÔÔÑÎÊÆÂ¾·¯©£¢¤¥¦ª®°´·¸¸¹¸µ°«¥£¨¦œ™©´·¹¹·³ªš€‡œ¨¬­°´´¶·²¨ ¤©¯ºÀÀ¼°Á´¯°°±µ»¾¿ÀÀ½»¹¸¹»º·¶³°«¨¤ ž ¤«²·µ­¨¦¬¸™Œž¢¥©«ª¨®¸»¼¾¾¼¹»»º»½ÀÃÅÇÊÍÎÍÎÏÍÊÉËÍÊÌÌÉÉÊÊÊÇÇÇÈÊËËËÊÈÉÉËËÌÍËÊÉÉÇÅÄÃÅÄÃÄÄÄ¿¾¿¿À¾¾¿¿¿¾¾¾¾½»¹ºº¸¹¸¸¹¸¶¶¶¶¶µ±¹s2’·°¯­¬«©¦¤¦—G#L—¥£¤¤¤¡Ÿžœt g¤›œœŸi1t¯¨³o0”¼º½¿ÀÁÁÁÁÁÀ¿º´°ª¦ œ¢©®±´·¹»¿¿ÀÁÀÁÁ¿»º»¼¼½¾¿¾½½¾¾¼»½ÁÄÆÊÍÐÓÓÕÖÒÎËÇþº²ª¥¢£¦¦¨¬°²¶¸¹¹¹¶´®ª¢ ¤ —°µ¹¹·¶°§Ž‡”¢«­­°³¶·¶°¤Ÿ¤¨°»ÁÁ¹±Éº±°±°²µº¼¾¿¿¼¸··¹¸¸¶µ²¯©¥žšš¡§±¸µ¬¨§¯°‘›¢¥§ª«¦¬¸¼¿¿¾½»»¹º»¼¾ÁÄÇÊÌÍÍÏÏËÉÈÈÊËËËÊÉËÊÉÈÆÈÉÉÊÌËÉÉÊÉÊËËËÊÊÊÊÈÆÆÄÄÄÄÄľ¾À¿¿¿¿ÀÀ¿¾¼¾¾¼ººº¹¹¹¶¶¹¹·µµ¶µ³±¸j.—´°¯®­«¨¥¥›B!)#JŸ¤¥¥£ žŸž n"r¤£zOXw±¬²Z;¬½»½¿ÀÂÁÁÂÁ¿»·²¬§¢Ÿœž¤¬¯³··º½ÀÀÀÀÁÁÁ¼»¼¼»½½½½½½½½½¼¼¼ÀÄÇÊÍÑÔÔÕÓÏÊÉÄÀ¼µ®¥¡¡£¦§©«¯²µ¹º»¸¶³¬¥ Ÿ œ™¨¶¹¹¶´±¬‚‚Œ™¥©¬®²¶·¹´­£ £«´¾À¿¸¯Ïµ²±°±´·¹º¾¿½¹¸·µ´¶¶µ³²®¨¡™—˜š£¯¹¹¯¬­¶¢•££¦ªª¨¬¸¼½¾½¼½º¹»¼½¾ÁÄÇËÌÎÐÑÏÌÉÊÉÊËÊËËËÉÈÉÈÇÈÉÊËÍÌÉÊÊÉÊÉÉÉÈÈÉÉÊÉÇÅÄÃÄÄÃÁ¿¾¾¾¿ÀÁÀ¿À¿¿¾¾¼»»»ºº¹¹·¸»»·¶¶µ³²±¶b/𴝱°°­©§¥M'()%dª¤¤£ ¡¡Ÿ¢j"}¥ž¤‹EŒlwµ²¥?`½½¿ÀÂÃÂÁÀ¿¼¸´­©¦ žž¢¨¬²µ¸¹»¾ÁÂÁÀÀÀ¾»»½¾¼½½½½¼¼¼»»º½½ÀÃÈÌÎÒÓÔÓÐÍÈÅÿ¹±¬¥¡ £¥¦©¬°´¶¹º¹¹¶´¯¨Ÿœœ˜°¹»¹·´¯¥Œ‚ˆ‘ž¤©¬¯´¹¸¸´©¢Ÿ¤®·¾Á¾¹°ÏȺµ´²´µµµ¸¹»»º¸¶µ´³³µ²³°«¦ ™–““›¡¨µ»·³¶¶› ¢¥¨©¦ªµº¼¾½»»º¹º½¿ÂÃÅÉÍÎÎÐÐÎÌÊÊÊÉÉËÌÍÍËÉÊÈÈÈÇÉËÌËËËËÌËÊÉÈÈÉÊÈÈÉÇÆÃÁÁÂÁÁ¾½¾¾¿ÀÁÁÁÁÀÀ¾½¼»¹¸¸¹¹¹º»ºº·µ¶µ³³³·^4³®°°°¯ª¬u%&&'&/•¥£¤££ Ÿ¥e'…¥¦•IEzXy´·|5•ļ¿ÀÂÃÃÃÂÀ¾º´®©¤¡ŸŸ¢§¬²¶¸»¼½ÀÃÄÂÀÀ¿½º»½¾¾¿¾½½¼¼¼¼»»½¿¿ÃÊÎÐÓÓÒÐÍÈÅÃÀº³®¨¥¡¡£¤¥©¬±¸º»»¹¹¶³­¥›š™£³º»¹·³«œ‚‚‹–¢¦¨­±¶·¹¸±¤¢Ÿ¥°¹¿Á¿»°ÐÍÀ·¶¶¶´µ¶¶·¸¹··µ´µ³²²³´³°«¥Ž’–›¥³½¿¾¹¦› £¤§§©´¹»¼¾¼»¼¼»¼¿ÃÄÅÊÍÎÏÐÐÏÍËËÊÉÉËÍÎÍËÊÊÈÈÈÈÇÈÉÊÌÌËÌËËËÊÈÉÉÈÇÇÇÅÄÂÂÁÁÀ¾¾¾ÀÁÀÀ¿¿ÀÀÁÀÀ¿¼¹¹ººººº»º¸¶´¶µ´´´¸[9¡±®¯¯°®«¥B%)'&'++y¬¤¥¤£¢Ÿ¥^-§¦dm…g@}¸µPJ¹ÁÀÁÂÃÃÃÄÂÁ½µ°ª¦¢¡Ÿ¡¥ª®´·º¼¾ÀÃÄÄÃÁÁ¿¼»¼¾¿¿ÀÀ¾½½¾½½½»»½ÁÅËÏÑÔÔÑÏÊÆÃ¿»´°«¥¢¡¢¤§§¨«²·ºººº¸µ²«£›™—™©¶¹º¸³¬¦“…™¤§ª¯³¶¸º·¯¦¡¢©³º¿ÀÀ»¯ÑÏĸ··¸·¶µ´´¶¶µ³³³²³³²²²´³¯¦¢”‰‰Š‘•ž­½Æ¿«˜ž¡£¥¤§²·º»½¾¼½½¼½¿ÂÅÇÉÌÍÏÐÑÎËËÊÉÊÊËÌÌÊÊÊÊÊËÊÊÉÈÉËÍÍËËÌËÌËÉÈÉÊÈÈÇÅÄÂÂÁÁÁÀ¿¿ÂÃÂÂÁÁÁ¿ÁÁÁ¿»¹º¼»»º¹¹·µ´¶·¶´´´¶X>£¯®®®¯¯­Ÿ9,+)))0.v¬¦§§¥£¢§Y2–¨¢—®±µq‚½Ÿ8}ÇÂÁÂÃÄÄÄÃÁ½¸±­©¦¢ Ÿ¥©¬°³·¹»¿ÃÃÅÃÂÀ¾½»»½¾¿ÁÀ¿¾¼¾¿¿½½¼¾¿ÄÉÍÏÒÓÒÐÎÈÄÁ¼¶±¬§£ ¡¤¦§§©ª²·¸º¼¹·´¯ª¢™–“ž±ºº¹µ±«œ„‚‡’ž¦§¬²¶¶¸¸µ¬¤œ£­µ¼ÀÀ¾¹°ÒÒʺ¹¸»¹·´³³³²²¯¯±³³´³±°±³²¬§ –Іˆ‰“•Ÿ­´«—™ ££¢¥¯¶¹»½½½½¾¿ÀÀÁÅÈÉÊÌÎÎÏÌËÊËÊËËËÊÊÈÉÈÉÊËÌÊÈÇÉËÍÎÍËÌËËÊÉÇÇÉÉÈÇÅÅÅÃÁÁÂÀ¾¿ÂÄÃÃÂÁÀÁÁÁÁ¼»ºº»½»¹¸¸µµ···¶´µ¶µQB©¯­«¬­­¬ªZ04*+57F—§¦§¦¥¥¤§T6­ª®­­³”›½lA®ÃÀÀÁÂÃÃÂÁ¾¹´­¨¤¢ Ÿ¢¦«¯²´¶¹¼¿ÂÄÄÃÀ¿¼¼»¼½½¾¾½½¼»¼½¿½¼½ÀÄÇÌÏÑÑÐÏÎËÈþ¹³®©¥¡ ¢¤¦¦§ª­³·º»º¸¶²®¨¡™“•§¶»»¹³­¨’€†‹š¢§«°µ··¹¸²§š¢°¹¾¿À½¸®ÏÒξ¸º»º¶µ´³²³±­¬®±±³²°¯®®±¯¬©Ÿ‘†„‡ŠŒ•›—–ž££¢£­¶º»¾¼¼¼¼¿ÀÀÂÄÈÊËËÍÎÎÌÉÇÉÊÉÊÉÊÊÉÉÊÊÉÊÌÌÈÈÉÊÌËÍÍËÊËÊÈÈÈÇÉÈÆÅÅÄÂÂÁÂÁ¿ÀÀÃÃÃÂÁÁÀ¿¾¿¿¿¾½½½¼ºº¹··¸¹¹¸¶¶µ²LI®®¬¬­­­ª®œme=1bm¥£¦©¨¦¤¤¦O<¤¯¯­¯±²³·ªAjÆ¿ÀÀÁÂÂÂÁ¿¼·±«§¢¡ŸŸ¡§­°³¶¸º½ÀÁÂÂÀ½½»ºº¼¾¾½»º¹ºº»»¼½½¾ÂÅÉÌÏÑÒÏÍÍÊǽ¶¯ª¦¢ ¡¢¤¨¨¨«°µ¹º»º¸µ±¬¦ ˜“¯¸¸¸µ±©Ÿ…‚Š–Ÿ¦©­±¶··¸µ©Ÿ˜ž¦°·¾Á¾·¬ÏÐÏź¼»ºº·¶³²²°«©ª­®®¯®­«¬®°®¬§‹‚ƒ…„‡”–‘œž¡ ¡¨³¸»¼»¼½½¿¿¿ÁÅÇËÍÎÎÍÍÊÊÈÉÉÊËÊÉÉÊËÊÊÊÊÌÌÊÉÉÊËËÌÍÌÊÊÊÉÈÈÈÉÈÆÃÂÁÁÁÁÁÀÀÁÀÂÁÁÁÀÁÁ¿¾¿ÀÁ¿½½½½»»»º¹·¹¹¹·µ²¯FO°®­®°¯¯­¬°³¤H7–­£ ¥¥§§¥£¦ªLB«±±°±²³µ½‡:›Ä¾ÂÃÂÃÄþºµ°ª¦¢¡ ¢¦«°³¶·º¼¿ÀÀÁ¿½¼¼¹·¹»»º»¸·¶¸¹º»»¼½¿ÄÇÊÌÎÐÑÐÍËÈÄÀ¸±­¨£ŸŸ¡¢¥¨§¨¬´¸ºº¼»¸µ±¬¦ž””¦µ¹·µ²¬¤“‚…–¡¨¬¯µ···µ®¤œš ¨®¹¾Â¼µ«ÎÒÐȼ¼»ºº¹·¶µ³®©¤¢¤©«««ªªª­¯¯­ª£—‡€‚ƒƒ‡Œ‘Œ”›œž¨²·»½¼»»½¿¿ÀÂÅÇËÎÐÐÏÍËËËËÊÊÊÉÉÉÉËÊÉÈÊÍÍËÉÊÊÉÊÌÍÌÌÉÉÉÈÈÇÈÉÅÃÂÁÁÁÁÀ¿¿¿À¿¿¿¿ÀÁÁÀ¿¿¿¾½¼½½½¼¼¼»¼·µµ´³²±§;Q²¬­®¯­¬¬­¬®ˆ>8}§ £¤¦¨¥¥§ª®IC¯²²±²´¶¸¸XV½À¿ÂÂÃÃÅÂÀ¼µ°­¨¥¢¢¡¤ª­±´·¹¼¾¿¿ÀÀ¿¾¾»ººº»ºº¹¸¹··¹ºº¹»»¾ÂÆÊÍÏÑÐÐÍÉÄÁ»µ±«§¡žž ¡£¦§¨¬³¶¹¹»¹¸¶²«¤ž–¬¶¸¶³°¨ž‡…†š ¦¬´¹¹·¶³¬ ššŸ©±º¿À¿¼´ªÏÏÏÊ¿»¼»¹¸¹·µ²­© Ÿ £§©¨¨¨«¬®°±¬§ ’‚}|€„‰ˆ™™™›§²·»½½½¼½¿ÀÂÃÄÇÈÊÍÑÏÌÌÌÌÌÍÍÌÊÉÉÊÉÉÉÆÈÊËËÈÊÊÊËËÍÌÌËÊÉÈÈÈÉÈÆÄÃÂÂÁÂÁÀ¿¿¾½¾¾¿¿ÁÁÀ¿¿¿¾½½½¿¿¿À¾½¼¸·¶´²±±¥7W¶®¯°±¯­­®®¯—|z¦¢£¥¦§¥¨«®¯GJ³²³³µ¶¶¼›;†ÈÀÂÃÅÄÃÿ»²¬¨¥¤£¢¤©¬¯´·¹º½¾¾¿À¿¾¿¿¼¼»¼¼¼º¹¸¹¹¹º»¼ºº½¿ÃÇÊÍÐÑÑÎËÅÁ½µ°­¨¥ Ÿ ¢¢¢¤¦©®´¶¹ººº¸µ°©£›¡´¸¹µ±­¥‘„Д𠦱·¸¹¸¶±¦ž››¡«³¼¿¿¾¼´¨ÎÍÍ˺¼¼¼¸¶µµ²¬¨¢  Ÿ ¡¡ ¤¨«®°®¬© {|~€†Œˆ‰–™–—¢°¶¹¼¼¼¼¼½¿ÁÃÄÆÈÊËÍÏÍËÌÌÌÍËËËÈÈÈÈÇÈÇÉÊÉÉÉÊÉÉÊÌÍËÊËÊÈÉÊÊÊÉÈÄÄÃÃÃÃÂÀÀÀÁÀÁÁ¿¾ÀÀÁÀ¾ÀÁÀ¿¿¾¾¿À¾¾¼¹¹¸µ´´µ¤5c¹®°±²¯­®°°°²µ²ª¥¥¦§©¨§©¬±°EP¶²²³¶¶·¿oE³ÅÂÃÄÆÆÅľ·¯ª¥£¡£¥¦«°²µ·¹¼¾¿ÀÁ¿¾¿¾¾»»»»»»¹¸¹º¹º½¾½¾ÀÂÅÇÈËÎÑÒÏÌÉÄ¿º±¬©¦¤ ŸŸ¡¢¢£¤©¯³·¸¸¸·µ³­§¡™˜«´¶´²®¦œƒŒ—¤«·¸¹·¸´ª¢™šž¥®¹¾¿¾¾¹²¨ÑÐÒ̹¼¼¼¹·µ´°«©¥¡¡—•——›ž£¦ª­®°±¬¢‹~zz|}€ˆ‡…“”““Ÿ®µ¹º»½½¼»¼¿ÁÂÅÈÊÌÍÍÌËÌÌÌÌËÌËÈÇÈÇÆÈÇÉÊÊÊÊÉÈÉÈÉÊÊÉÉÉÉÉÉÊÊÉÈÇÄÄÄÄÃÂÀ¿ÀÁÂÀ¿¿¾ÀÀ¿¾¿¿ÀÀÁ¿½¾¿¾½¾»¹¹¸¶´µ¶ 6p¼ªª³²¯¬­°°°²¯­©§¨©©©¨¦§«²¯AWº³²´µµº²DlÇÁÂÃÅÇÆÅÃÀ»´¯ª¦¥¤¤§©­±´¶¸¹¼¾¿ÀÀ¿¾¿¾»¸¹º»»ººº»¼¼¼¿¿¿ÂÆÇÈÉÉÌÐÔÒÏÌÇÁ»µ°«§£¡Ÿž   ¢¢¦«°³µ·¸¸µ´²­§ šž®²²±±©¢{‚„Œ”¦²·¸¸¸¶±§žšœŸ¨µ½¿¿½»¸±¦×ÔÓÏȼ¼»··¶´²°¬©¥¢¡œ”Ž”™œ¡£¦©¯°¯® ‹yyy{|~„’ž¬±µ¹º½½¼¼¾¿ÀÂÅÈÊÏÏÎÍËËËËÌÌÍËÉÈÇÆÆÆÈÉÉÉÉÉÉÊÉÈÉËÌËÊÉÉÉÉÉÊÉÇÇÆÇÆÅÅÃÁÀ¿ÁÀÀ¿¾¿ÁÁÀÀÀÀÀ¿À¿¿ÀÀ¿¿¿º¹¸¸·¶¶¹š2yÁ™m¹³²}£±¯°°°®«¬«¬ª©¨§ª¯³­<^¸²²µ¶¶Â8¢ÈÄÄÆÇÇÅÃÁ½¶°¬©§§§§«¯²¶¹ºº¼¿ÀÀÀ¾¾½¾¼»¹º»»»»¼¼½¾¾¿ÂÁÂÅÇÈÊÌÍÐÑÒÐËǽ·±®«¨¤¡ŸŸ Ÿ ¢¤§¬°³´··¶´²°¬§¡œ¥®°°°¬¥™…€ƒ…Ž˜¢®¶¹¸¸ºµ¬¤œœ¡ª¸¿¿À¿»¶®¥ÚÖÓÐʽº¼ºµ´²°®­©¦£¢œ•Љ‹‹“•–ž £§©¬ª§œ†{yz{z~|‡Žª¯´¹¹¼¼½¾¾ÀÁÁÅÊÍÐÒÑÎÌËËÊÊÊÉÉÇÇÆÅÆÇÇÉÈÉÉÈÉÊÉÉÊËÌËÊÉÉÈÈÉÉÈÇÆÆÆÆÆÅÂÁÁÀÀ¿¿À¿ÀÁÀÀÀ¿ÁÀÀÀ¿¿¿ÀÂÁ¿»º¹º¹¸¸¼”.|Á²Ro‚wc²±±²²°®¯¯®®«¬©§®³¶ª;c¶±³·¹½Á]ZÈÅÆÇÈÇÆÂÀ¾º³­©©¨§§«®²¶·¼»¼¿ÁÀÀ¿¾¾½¼»»»»¼¼¼¼¼½¾½¾ÂÄÃÃÅÇÉËÌÎÎÏÏÌÇÁ½·±®«©§¢ž ¥¥¨­±´´´¶µ³²¯«¨¡¦­®®¬¦¢€„†Œ•Ÿ©²¸¹¹¹·¯¥Ÿœ›œ¤®¹¾ÀÀÀ»´«¤ÜÙÓÎÈÀ¹º¹·´±¯­«©¦¤¤ ˜‹ˆ‡ŠŽ“——›Ÿ¢¡¤¢•}yyyxywŠŒŽ™©¯´·¸¹º½¿ÀÂÂÂÆÉÍÐÑÑÏÎÌËÌÊÉÉÈÇÆÆÇÇÈÇÉÈÉÉÊÊÊÉÉÊÌÌËÊÊÊÉÉÉÉÈÇÅÅÅÆÆÅÂÁÂÂÁÁÁÁÂÂÂÁÀÀÂÂÁ¿¿¿ÀÁÁÁÁ¿»»»»ºº¸½“.ƒ¿¼iPsBй²²³²±°±±¯­««©«±³µ¦7f¸²¶¹¼Ä¨A“ÌÄÆÆÆÅÄÁ¾º¶°©§§¨©ª¯±´µ·»»¼¾¾¿Á¿¾¾¼»ºº¹»¼¼¼»º»¼½¾ÁÃÃÄÆÇÈÊÊÌÍÎÎËÆÀ»µ°¬©§£ œ›œž¢¥¨¬¯²³³³´³³±­«¦¡Ÿ§«¬­©£™…€ƒŠ‘›¦¯µ¹¹¸·°¨¡œš›ž§³»ÀÀ¿¾»´ª£àÝÖÍÆÀ¹¶µµ³¯­«ª§¥¤¥¢˜††…ˆ‰ˆŽ’˜—Œ|yyxvsz†ˆ–§®±µ¶¸¼½¾ÁÃÄÅÇÊÏÒÐÏÏÎÍÎÎÌËÌÌËÊÇÇÉÉÈÈÇÇÉÉÉÉÈÇÉÊËÊÊÊÉÉÉÉÉÈÅÅÆÅÄÄÄÁÁÁÁÁÁÁÂÂÃÃÂÂÃÃÃÂÁ¿¿¿À¿¿½¼»¼¼¼»»¸¾Ž/ˆ»¸dž_ª³³²±¯°±²³²¯­««®²³µ¡2h¸²·½¾ÄpRÀÈÅÃÅÆÃÁ¾¾º³®©¦§¨ª­±²µ¶·º½¾¾½¼½¼¼¼ºº¹¹¹º»º¹º»º½¿ÀÂÃÄÅÆÉÊÊËËÍÍÊÇý·±¯¬¨¥¢ž›››œŸ£¦ª®°±±²²²±²±®«¦Ÿ §©ª«¥Ÿ‚…Œ”¡ª²·¹¸¸³¬§Ÿœ›œ¡ª·½À¿½¼¹±©£ÚÙÔËĽ¸¶¶´²°®¬«¦¢ ¤¤ š“‰„ƒ…ˆˆ‡‡‡‚€~‡‹Ž„{yxrlxƒ„Š”¦¬­°´º¼¼¾ÀÂÃÆÉÌÏÒÒÐÏÏÏÎÎÍÍÍÌÌÊÈÇÆÇÈÈÇÆÇÉÉÈÇÇÈÈÈÉÊÊÉÈÉÈÉÈÇÆÆÆÄÄÃÀÀÁÁÁÁÁÂÂÃÄÃÄÄÅÄÄÂÀ¿¿ÀÀ¼»»»»»»¹¹·»Š.ޏµªQa~·±²²°®¯°²³³²°°¯²´´±˜0l¸´º¾Â°EˆÍÄÅÃÅÆÃÀ¾»·²­ª©©ª¬¯²µ·¹»¼½½¾¾¼»»º»»º»¹º¼¼½»»½¼¾ÁÃÄÅÆÇÈÊËÌËÌÌÊÇÿ¸³­©§¦¤ šš›œ¡¥¦ª®¯°²³²±¯°¯­©§Ÿ §¨¨¨¡˜…ƒ‰’œ¤®¶¸·¸¶°«¥ž››ž¦°¸¾ÁÁ½¼·¯¦£ÉÉž»¸¶·´±¯­­ª¦¡¡¢¢¡œ–Œƒ€ƒ…††„€{yxwx~€|vvtlv„‚†”¤««°µº¼½¾¿ÀÂÆËÏÐÒÓÑÏÎÏÎÎÎÍÌÊÉÉÈÈÆÆÇÇÇÈÉÉÉÈÇÈÉÊÉÉÊÈÈÈÇÈÇÆÆÆÆÆÄÅÁÀÀÁÁÁÁÁÃÃÃÃÃÃÅÄÅÄÁ¿¿½¾¾»»»»»»º¹º·¼ˆ-¹¯µ_@¨µ³²²°¯±±²³³³³´µµµ²²+r¸¸¾¾ÇzL¸ÅÃÄÅÅÄÿ¼¸µ²®ª«¬¬¯²µ¹º¼½½¿¿¿¿¿¾¾¼º»½½¼¾¿¿¿½½½¾¿ÂÄÆÇÈÉÊËÌËËËËÉÅÁ¼µ¯©¨¤¢Ÿ›˜™ž¡£¥¨¬¯±±±²°¯­­«©¤ž ¤§§¤Œ‡Œ˜¡«µ¸···´°© š™› ©²º¾¿¿¿¼´ª¤£¼»¹¹¹¼»µµ²°®¬¬ª¦¢¢ žš•†€€ƒ…ƒ€{wtstwvwvtrojq‚ƒ‚¡ªª®´·»¾ÀÀ¿ÂÅÊÎÐÑÑÎÏÎÍÍÍÌÌÌËÉÈÇÆÇÅÆÈÈÉËÊÊÉÇÈÊËÌÊËËÊÉÇÆÅÅÅÅÅÅÅÄÁ¿¾ÀÁÁÁÂÂÁÁÁÁÂÀÁÂÁ¿¾¾½¼½½¼»¹¹»º¹¸¶¹‚-й«­†t³®­®°¯¯°¯±²³³²´¶´³²´Š,~À»¿ÀµItÇÁÅÆÇÆÃÀ¼º¹´°¬ªª«­±´¸¹¼¿¿¿ÁÂÁÀ¿ÁÀ¿½½¿¾¿ÁÀÀ¿¾¾¾ÀÂÅÅÇÉÊËËÌÌËÉÈÇÆÄ¾¹±«©¥¢Ÿœš˜šŸ¡¤¦¦©¬®®¯±±°°®¬¬«£ž¢¦¤ –†€ƒ‰“œ¦°µ¸µ·¶³ª¢œšš¥­·¼¿¿¿½º±¥££³±³³µ·º·°°¯­««©¦¡ Ÿœš˜—”Œ€‚}ytrrrrqoqpmifm‚‚‹Ÿ¬©«±·¼¾¿ÀÁÄÅÈËÎÐÏÏÎÌËÌËÊÊËÉÆÅÆÇÆÆÇÈÈÈÊÊÉÈÈÈÉÊËÌËËÊÊÈÆÇÇÆÅÅÅÇÅÂÀ½¾ÁÂÁÂÂÁÀÂÁÁÀÁÁ¿¾¼½½¼½½»º¹¹»¹¸¶³´’.W§«©¬±°®­­°°®¯±²³´²±±±³²²¼‚2–Á»½Å–:£ÈÅÆÅÅÅþ»¹¸´°­ª©ª­±µ¹¼¾ÁÁÂÃÂÃÄÄÄÄÃÃÃÁÀÁÂÁÀ¾¿ÀÁÄÅÇÆÇÈÊÌËËËÊÇÆÄ¿¹²¬¨§¤¡š˜™›œŸ¢£§ª­®®®¯²²°°®««§£››Ÿ¢¡™‚‡™¡¬´¶¶¸¶³­¤žœ›ž¨²¹½ÀÀ¿¼·®¤¢¢®®¯°²µ¸·±­­«ª©©¥Ÿœžœ˜““„}€}vtrppnkkmkihch{€ƒŠ›«««¯´¹¼¿ÀÃÅÇÊËÍÏÐÏÌÍÌÊÉÉÈÉÈÅÅÅÄÅÇÈÈÇÈÊËÉÇÇÈÉÉÊËÌËÊÉÇÉÈÈÈÇÅÆÆÅÂÁ¿¿ÁÂÂÁÁÁÁÁÂÃÁÁ¾½½¼½½¾¼»ºº¸º»¹¸¶´²¯T'BWcltz‡”› ¥§©©®¯°¯¬¯®® RI³À½½ÆmLÀÅÇÆÅÄÿ¼º·µ²®«ª­¯²¶»¾ÁÄÄÅÇÅÅÇÇÆÇÇÆÅÃÁÂÅÃÁÁÂÂÄÅÅÆÆÈÇÉÊËÊÉÈÅÅÁ¾º´®¨¥¦¢Ÿš™˜šœ £¤¨¬®¯°¯®°±°°®¬©§¢™œ ¡›ƒ…‡’¦²·¶·¶³®¦¡ž››¢¬´»¾ÀÀ¾º´©¢¢¥®®®®¯²µ¸´­«ªª¨¥ ™š››˜“‡€}~~|zxuromkjihggeaev~‚Š›¥§ª­±µº¿ÀÃÅÉÌÎÐÑÑÎÌÎÍËÊÉÉÈÇÅÄÄÄÄÆÇÉÈÆÉÈÆÆÇÈËËËÊËÌËÊÈÊÉÉÉÈÇÇÇÇÃÂÁÁÂÂÂÂÁÀÀÂÂÂÀ¿¼½½½¾¿¿¾ººº·ººº»¹µ²µ¦eD;7630*0348;X‚Ÿ®¶¸··¸º¾ÃÇÂÂÃÂÅdZÇÇÆÆÅÿ¼º¸³¯¯¬ª«°´¸ÀÆÈÉÊÊÊÊÊÉÈÇÉÉÊÊÈÆÅÅÅÅÄÄÅÇÇÇÉÉÊÊÊÊÊÉÇÅÃÀ¾º·³¯«¦¤£¡œšššž ¡¢¤¨­®­¬«®±²±®­«©§¥Ÿ•˜˜—‡}€„Œ•£®´µµ·µ­¦£ œ™› «³¹¾¿¾½¼¹²ª§¢¥ª¹·³¯­¬®®±·®¢Ÿš˜––”’‘’“‘†|{{zurplkifde`XQVlw}„‘ ¦§§©¬²¸¿ÆÊÍÐÐÐÎÌËËÌÊÈÆÄÃÂÀÁÁÀÀÂÃÄÅÇÇÆÇÆÆÇÉÊÉÉÊÊÉÉÈÉÊÊÌÌËÊÊÉÈÅÃÄÅÅÆÅÆÅÄÂÁ¿¼½¼ºº»»¼¿¿½ººººº¹¹¹··¶·µ³´³²³³³´¶·®•nO::GdЧ¶¸¹¹º»½ÀÂÂÆ¯EŠÏÆÆÆÄÁ¿¿¼¹·²¯­««®±¶»ÂÆÈÉÉÉÉÉÊËÉÊËÊËÉÇÅÇÅÆÅÄÆÇÈÇÇÇÈÉÊÊÉÉÇÆÃÁ¿¼¹¶²®«¦¡Ÿ›™˜˜šœŸ£¤£¤«¯¯¬«¬¯°²±­¬ª©¨£›“˜—’}…ˆ‘œ¨³¶³·¶²¬¨£žš˜ž¦°¶»¿À¾¼»·±¬§¥§¬»º·²¯°®¬«°¶§ ›š—”•”‘Œ‘’“Žz{zxuromlgdaa]XRPgv{ŽŸ¤¥¤¤¦¬°¸ÀÇÍÏÍÍËÉÈÈÇÆÆÃÀÀÀÁÀÁ¿ÀÁÁÃÄÅÅÆÇÆÈÈÊÈÇÇÈÊÉÊÉÈÉÉËÌÊÊÌËÉÅÄÆÆÆÆÅÅÄÂÂÁ¾¼¼»¼»º»»½½¼»¼º¹»¹¹¸¶µ¶µµµµ´´´´´µ¶¶¸»¼¯—wP;2;X{˜¯¼ÃÂÂÁ¾É{H¶ËÈÇÆÄÃÀ½º¶²²±¯®®³µºÀÄÇÈÉÊÉÈÈÉÉÉÊÊÊÊÈÆÅÆÇÇÇÈÇÉÈÇÆÆÈÉÉÉÈÇÆÄÂÀ½¸¶µ±®ª¥¡˜–——šœž¡¥¤§«­¬©«®°±±¯«««ª¨¢˜“•”…|‚†˜¤­´³µ¸¶°¨¤š™›¤¬³¹¾ÀÀ½»ºµ¯«¥£¨¯»¹ººµ°¬©§§¯²¢›–““’’ŒŒ‹Ž‘“‘Œƒ|xwuroojfca`\XQK^sx}ŠŸ¡¤£¢¤¤¥¬²¹ÁÅÅÄÃÀÀÀÁÁÁÀ¿½½¿¿¿¾¾¿ÀÁÂÂÀÃÄÅÈÈÈÇÇÆÇÈÈÉÉÉÈÉËÊÊÊËÌËÈÆÆÈÈÆÄÅÄÃÂÁ¿¾¾½½½»º»½¾¼¼½»¹¹¹·µ´³µµµµµ¶µ´µ¶¶··¸··º¿À· bJ:?:–ÖÌÍÍÍÌÍÊÊÒºl>VŸ¢†IFHLMKMLOT]ds‹‹‘—š›Ÿ¢¥§ª¬¯±´¶¸¹··¸´¯¬¨¥£¡ žœšššš›››œœžž¡£¤¦¦¦¨©©¨§§¦¦¥¥¥¤¢Ÿ›’|{¡ª©ª««¬¬«ª««­­­­¯¯¯°²·¹¸·³­¨¥¡¡¢¦©©©«««™Š†„}z{}~€ztqppqrrsvuxxz~€‚‚ˆ•˜›œž  žŸ™‚s_RQaq~Œ”˜›—’“•––˜™ž£¤¤¥¥££¥¥¦¥§«­°²µ¸¹»»»¼¿¿ÁÃÄÅÆÆÇÇÇÈÌÏÒÒÑÐÏÎÏÏÐÐÏц;C>>>¦ÐËËËËÊÊÎÊ•E-:<=9QžÎÈÁÆi'..4>IVfs”˜››’‹‰¹ÄÃÉ—/*'&(&'$"!!6›š™¡a!9q•›œž £¥§«­®²µ·¹»º¹¸µ°«¨¦£¡ŸŸ›››œœœ›žŸ¡£¤¥¥¥¦¥¦©§¥¥¦¥¤£¤ Ÿœ˜ƒ™©¨©ª¬«ª«ªªª©©«««ª¬­­°³³²¯«©¦£ŸŸ¢¥¦¨©©««•‹‡ƒ|xxz{}~~ytpopppqrsttvwwx{}}~‚ˆ††‡ˆ‰€vfVO[n‡’–—™–‘ŽŽ””••”–šžžŸŸž ¡¢¢¢£¦¨ª­°²¶¸¸¹º¼¾ÁÂÄÅÅÆÇÆÆÈËÏÑÑÐÑÏÏÏÏÏÎÍÐw:DA?@­ÎÊÊÊÉÌÒ¯c.3<=?:h¸ÑÉÄÀÄc243310.--/1/1552215¨ÇÂɈ140.,*(''&#""!!  9˜™–›Z " EŽª£§©«®¯±²³·¸¸¹¸·µ°­¨¥£¡¡Ÿš™šš›œš›œœŸ¡¤¤¥§¨¨§§¦¥¥£££¤¦¥¥¥¡Ÿš•€’¦¥§©ª«ª¨¨§¨¦¤¦§§¦¦¥¨ª­­¬ª¨©¨£žœœŸ£¦¨©ªªªš—†€~|ywxyy{}{uqmlmmmnooppopqsrsvvrutqqqrrnnj_UKTl‡””•––’ŽŽ‘’’’”——™™›  ¡£¥¥¦¨©¬­¯³µ·º¼¿ÁÅÅÄÅÆÈÉÊÍÐÑÑÏÐÎÎÏÎÎÌËËk;B@=C´ÌÈÊËÑË„80:=>9B…ÈÑÇÆÃÀÆ^43225689:9877778:<@®ÄÂÊ~.1/+*)'%$#!"! "! @™˜™N!#$%-†¯ª­°±³µ´µ¶¹¹¹¸¶²­©¦¢¡¡žœšš›š››™—˜™œ ¢¥§¨©¨§§§§¦¦¤¢£¤¥¤¤¤ ™˜¡¤¦§©©¦¡ ž™––™ ¡¢£¢£¤¦¨§§§§¨¦¡œ™šž¡£§¨¨©©‘“‘ˆ€|zzwuuvxy{xsmjiiiiikjllijjjijljhggfeeecb]WMIMe{ˆ˜•““‘‘ŽŽ‘Ž‹’••–™šœž ¢¢¤¢¢¥¨ª­°²´·º¼¿ÁÂÃÅÆÈÉÌÐÐÑÑÏÎÌÍÎÍÌÊËÇ[DC@9I»ÊÅÅγc.4=@<9V£ÐÍÆÇÇÃÁÃU.123242024467:9797=¯ÀÁÉs.1-*%*71/.,)(#" HŸ™”’C(%#"!%$&($.”³°³´µ·¶¸¹º»»¹µ°«§¥£¢¡ž›š››œœœ›˜˜™ ¢¤§¨©©§§¦¤¤¥£¢££¢¢¢¢Ÿ›š—’™¡¤¥¦¥¢œ—‘‰€~€‡˜žŸ¢£¡¡¦©©¨¥ ›˜˜›¢¥¤¤¦¦§_ˆ‡~zyxurstwwxtlifeecccedbcc`b``ac``_^^\ZYXTPF@I_x„˜—”’‘”’Ž‹ŽŒ’’’”•™›ž ¢¡   ¡¤¦ª¬®²µ¶¶¹¾ÀÃÄÄÆÇÊÏÐÑÐÎÍÌÌÌÌÉÈʽL<@@8SÀÃÃÈ”D3::;:;t½ÎÇÆÅÅÄÁÀ½K034.B€yl\TJ:788669Z·½¾Åi10++&v “•“Œ…{tmffdx•“ŽŽ@(x†‚~vY3"$(*-&A«·¶·¸¸¹»»ºº¹¶±­©¨¥¤£¡ žœœœžžžœ›šœŸ¢£¦¨ª«ª¨¦¥¤£¢¡¡¤£¢¡ Ÿœš—”“𡣦¤Ÿ™‡ƒyuuu‡’™›œ ¤££¨«¬«¨£Ÿš–™Ÿ¡¡ £¤£¢4^†……‚}{zwtqpqsutmifa`^^][XY[WVTWWWXYXYWUSQOMID;34]w‹“——”’’‘ŽŒŽŒŠŒŒŒ‘’’•–—™Ÿ¡¡Ÿ ž ¡¡£§ª®±³µ¸»¼¿ÃÅÅÄÇÌÎÏÎÍÌËËÊÈÇÆÈ³@48:6U½Â¾s24<881DËÇÀÀÂÁÁÀÀÀ·B256/_ËÇÉÄÀ·¦—”˜©¸¾½¿Äb)-+++‘²©§§¥¢¡Ÿ¡¡žžœ˜“‹47 ¡¤§«®›Q&*,,.'{¾¹»º¸º¼»»¹·²°«¥¦¤¥£¡¡žžž ŸŸŸœœž ¤¥§©ª«ª¨§¦§¦£¡¡£  ¡ œœš–”•› ¥¦£Ÿ•Šytrtv}†˜œ¡¦©ª¬±²±®ª¦£Ÿ¢¥¤£¢¢¢¡ž@5`†ƒ|{vwurooqrtqifc`^\XVURQPMNOOPRRPPOLKID@7,!T}‚ˆ•–”“‘’’‘‰‡‡Š‘“”–˜—šœœžžžž ¤¨«°µ·º¼¾ÂÄÆÆÈÊÍÎÌËÊÈÇÅÃÂÂÄ©93540O¾°V-68760S¤ÆÁ¾½½¾¾¾¿Àó?3451jÈ¿ÀÂÃÃÄÅÌÎÉÆÃÁ¿À¿Â_,.,()’«¤¡Ÿž›š›˜˜˜—•“,= ¡£¥§³«P(..0,O¶»»»»½¼ºº¸µ²¯«¥¤££¡ ¡œžŸ  ŸœœœŸ¤¨¦¦§©ª©§¨¦¦¤¡  ¡ Ÿžœ›™˜–“”›¡¥£š“‡{sqnnpuz‡’›¡¥¨ª®°±¯®ª§¤Ÿ¡¢£¥§§¥¥£ŸD@<^ƒ~~{xwutppprrqoida][YVSPPMHFECDIHHGEB@:0!P|„‡Ž“•–•“”––”‘ŽŒŠ‡†ˆŽ‘“–––˜™™™˜™š››œž ¤¨«®´·¹¿ÃÅÅÆÈÌÌËËÆÃÀ½ºº¹¸»™-./.(LŽA&53350d²Ãº¼¼»»¼¼¼¾¾Á¬>4352nÆ¿ÀÀ¿ÀÀÃÄÃÂÀ¿À¿¿½½X++)&*‘¦¡Ÿœ›————˜š™”’†'F¥Ÿ£¥¨©«¸œ6,/12<¤À¼½¼½¾¾¼¹¶³®©¥£¢ ŸŸžžžžŸŸŸŸŸ›œ¢¨¨¨¨©ª¨¨§¦¦¥¢ Ÿ ¡Ÿœš—•–”’”šš‘ƒ{qnnlmmptx{ƒŽ˜ ¡£§ª««ª¥£¢ žœžŸ¢¥¤¤£¢¢DCA8f||{zutsqnnnmnnjf`][XVRQMKHD>;99<=:3+' Hy‚†Š“”””•””“‘ŽŽ‹‰ˆ‡ˆ‘’‘’”“’”––——˜™š›œž¤§ª®°¶¼¿ÀÂÆÈÌÌÉÆ¿¸²®®®¯°µŽ%)'()+."-.-,1l·º´¶¸¸¹»¼½½½½À§84493qĽ¿ÁÀ¿¾¿ÁÁÂÀ¿¿¾½»ºQ%'%#'ŠŸ›™šœ›–•—™˜˜•‘Ž€#M§¢§©«¬®²·Q*//34“Á¼¾¾¿¿À¾¹¶°ª¦¤£¢ žžŸ   Ÿž ¥¨§©ªªªª¨¥¥¤¢¢ŸŸ Ÿœš™˜•“‘”—“…}tllmjmmptsux‰“™™œ £¢¢›‘‘—œ›™š› ¢¢¢¢¡BCA;=jxwyvtrromllljjhb]ZXUSPMJGB;7542( D|ƒ„ˆ“•–—•’‘ŽŒŠ‹Œ‹ŠŠ‹ŽŽŒŽŽŽ’““““•—˜™˜œž¢¤§ª±¶¸»ÀÃÅÉÊÅ¿µ­¥¡¡¡£¦¯€!&%%%$%))++.y¹¸±²³¶¸¸¸»½»¹ºÀŸ53242tȾ»¼ºº»¾½¼¾¾¼»»º¸ºM%&#"%‡˜–•—–•––“’’”Œ‹Žw\ª¤©­®®²´»_(1263–ýÀÀ¿¼¹·´±¬§¤£¢¡¡ŸžŸ ¢¢¡ Ÿœœž ¤§¨§©«©©¨¦¤¤£¡žž Ÿœ›™˜—•“‘ƒurkijkloqqtstx}†Ž“˜™š˜ƒ||ˆ•˜––˜šž¡¡¢¡¡CCC@7?kuvxurqnlkkjjlhc^ZWTRPMIE@<851( =|‚……ˆŽ”•–•”‘Œ‹Š‡‰Ž‹‰‰ŠŠ‰‰ˆ‰Œ’’‘””“’–™œ £¨®±¶¹¾ÁÄý¶ª¡œ˜˜™›Ÿªp!!!!"$%&&%T¶¯­¯°²³µ···¸¶¶¶½‘/100.R˜¤®·º¼ÀÀ¿½¼»¼¹¸··¶H"$ #‰¢›œ™–’‘”•’‘‘ŠŠŒm e¯§ª¯°±¶¹¿Y/0588¤ÅÁÀ¾½·´²°«¦¡¡¡¢¡Ÿ  ¡¡¢£¢¡ žŸ¢¦§¨§©©§¦¤£££¢Ÿœž ž››š˜–”’މ„tolkiiklnqrsusx~ƒ‰ŒŽ’‘‹€tpqŽ““•–šœžžœ›œCBCA=8>outtrqmmlijhiheaZURRQMID>975(  8x}ƒ„†ŠŠ”•“‘ŽŠŠŠŠ‰ŒŒ‹‰‡†…„„ƒ†‰‹‹ŽŽŽŽ’–›¡¦©­±³¶¹´±© ›—•••˜›¤h#  ""$$3›®©­®¯¯¯¯¯°³´´²»„*--,,'(1>Lau…˜Ÿ¡ §¼¸´±E#!FTSYYZ`dagigkpŠ–c " p±«°³¶¶·¼¬A6346F·ÄÁ¾¼º¶´³®¨¢¡ŸŸŸžžŸ¡¡¢¢¡¢ Ÿ  ¤¤¦¦¥¦§§¦¥¢¢£¢¢ š››˜•”“‹€{vqnkihhkmnqrqtv{‚„‡‰Œ…wpor|‹’’•–™š›š–““AA@@>=5Bqrrtqnmkifffffc]WSROMHB=74* 0r|‚ƒ…ˆˆ’”ŽŒ‹‰‰……‰‹Š‹Š†ƒ€€€ƒ„…†ˆ‰‡ŠŠ‰ˆ‰ˆŠ–˜œŸ¢¦¨««©¥™–”‘”––—ž`5 !";ž§¤¦§¦¥§¨©©­®¬µt$,+)*+(('&$'/028:;5„Áµ±«? @“’“›_ "&"z´¯´·¸¸»Ãl59573tÄ¿¿½»¹¶µ²­¦¡Ÿ ž›œŸ   ¡¢¡ ¡¢¤¤¥¥¥¥¥¥¤¤¢¢¡  žš˜˜š˜“‘Ž‹|trqmlighmlnpqruwz}€ƒ‡ˆytomrx~ˆ‘”˜š›™—‘Šˆ@@@?<=;1Jpprqnmkifcacca`[UUPMFB<9+ 3m|…„†ˆ‰Š‹ŠŒ‹Š…‚„„ƒ‚€€}}}||~~€€‚ƒ€ƒ‚€ƒ‰Ž’–˜™ £¤¤¡œ—•““–––”šQeTM£››Ÿ¡¡¢¥¦¦§§§­e))(*+,+,./25446872‰À³­§>Dš˜š¡^##&$µ°µ¶¸½Å~/9875=®Á½½¼»¹·´°©¤ŸžžœœœœœŸŸŸ ¢¢¡¡£¤£¢¢¤¤¤£¢ žžœœœ™˜˜–—”‘ˆzupljifehkkmooqtwz}~€}yslknv|€†‹”˜››š“Šƒ???>=:75,Nnqppmjgedaa_]^[XQNHB@8$ +  )jx€‚„†ˆŒŒ‹ŒŒŒŒŠˆ„~~|xwz||}|{z{{{z|}~{z{z{|~€ƒˆ‘“–™Ÿ Ÿ™—“‘“–˜•–Io˜2j¡–šœ™ ¢¡£¢£­U%%'$)'%'+,.0214785޼°¨£8 GŸž¨\#&%(&‡Àº¿À¿ªj/7;:82ŠÇ½¾½»¹¶³®©¤Ÿœ›››››œ›œŸžž ¡¢£££¡ ¡¢¤¤£¡ Ÿœ›žŸš••••“‘ŽŽŽ…ytpmhfeeeilmnpswz~}}|zvvnikrx~~‚Š•›ž›–Ž…w?>>=<:85/)Kpllkihfcb_][ZXUOIE@. + %ct{ƒ…†‰‹‹Š‹‹‡…‚€|{zxuwxzywyxvwxwwxxuttuwx||{}€ƒ‡Œ“—›œ—–’Ž‹Œ”–‘Cq™~ x™’”–š››œžŸ¦F!"#)~ŠveTJB:965767’·©£›0K£ ¤¨V$&&)#^ˆƒzs[:+49=<1xƽ¼½»·³°¬¦¢Ÿœšš™š›š›œžŸžŸ¡ŸŸ¡¢ žž¡¢¡    ŸœŸžœš—‘‘“‘Ž‹ŒŠ‚vqmlifdcehkmnsy}yxvvtpghmrx||€Š•šœ•Š‚{w?==;;8641-*Gjkgffdcc_]YVRLLE, + + %_ryƒ„ˆˆŠ‹‰Šˆ‰ˆ„ƒ‚}{zyxvwxwvuvvttttttqorsstwwwww{~…‰•—›š—”ŒŒ’–‘:m‹‘g&„Œ’”•–˜˜™›œ7!! 2¦¸¹»º¸²«¦¢œ™––¨¬¡š+*UTTQONKIHIIKJrª¤¤§N$''))&))%(*/34:6:}Á¿¾¼¼¸³¯«¦¢ž›šš™™š™››œžžž  žžŸŸ ¢¢  Ÿžžœ›œ™–”’’‘ŒŒŠ‹‰spmkidcdfiilqz‚…ƒ}wuusrqkchoqv|€†”›ž—’†xw><;;:54411/):_gfb`ab_ZUMKI:!  + Wrw}‚……‚†‡„‡ˆˆˆ†„€|yzzvwvuutvtssrsrqompqptutwwvwy€‡ŒŽ–›™•“Ž‘Œ6n‡…‰J6Œ’“”—˜—‘.7Ÿ¦«²µºº¼¾½¿¿¾º±¥š“‚%C”‘‘”••—œ£¨§¥¤ªL$((*,-020234213PŸÊÀ¿¾½º³°¬¨¤ œš˜™˜™™™™šš›š›ššššœ›œž ¢¡¡ Ÿž››››š™–•“‘ŽŠ‹‹Š‰‡~vtpnhcbegklqw„ƒxvtqonlhdhnow‡’ ž™ˆƒ{ww><::85434311,1Oa^\\ZVPNL7 + + Lqx|‚ƒ‚‚‚„…ˆŠˆ‡‡‚{{ywwvtuutttrqsrrpooqprrqssttx|†‡–—–“‘‡‡†ˆ‰‰6o~|}‚4G‹ˆŠŽ‘’’”‡%:˜š¢§¬°²³³³´²°¬¥šŒw J‹ˆ‰Œ‘’–—™œ¡§¨ª©¬M&++-/-+.,-1/?b‘ÁÌÂÁ¿»¸±®¬¨¤¡žš˜—˜—–“’“””’‘••”’’–—–™¢¡Ÿš˜—˜˜–“““’‘‹ˆˆˆ‰ˆˆ„}|ysnidbdhmu{ƒˆ„€yuuspolhdefimw…“›¡¡Ÿ™”ƒwuw<;9877655331/.)?ORRQLIA"   + LVas°ÂÉÅÁÁ¿¹·´¯¬©¥ œš•“‘‘ŒŠˆˆŠ‰ŒŽŽ‘Ž‘•–˜˜••–“’‘”“Ž‘’Œˆ‡‡††‡ƒmtxslfcaensy†ˆ†{tpqonkecfghio‚“¡Ÿ›•ˆ‚|xwz;::877442320.--3?A@EG5 + *p{~ƒ„„„……†††††ˆˆ…~€€}{wvtqrrqrnqqqpnmnoppponpqruxz~†Ž’“’‹ˆ„‚„„/"uƒ‚~{€fp‡ƒ……ƒ‡t>‹ˆ‰‰‹ŽŽ‘’Šˆ„€€…a]‡†‹ŒŽ•–™Ÿ¥«­¯±³¶Q-16;<ŸÁÄÉÌÍÏËÅÃÿ¼¶²¯«¨¢™–’Ž‹Šˆ‡ˆ†…„ƒ„„ƒ‚‚…†„ƒƒ„†ŠŒ‹Š‹Œ‹‹‹‹‰†„„ƒƒ„‚aanmjeabhmu|‚„…‚{tqoomlhb`cgiiju„”š—”Žˆ„‚zwz€97788842310/,0;A@;8=4   + $c{€‚„„„…‡††‰‰†…‡…€€~}{xwurqsspnoooonnmnoooppqruuwz}‡Ž’’’Œ„})!wƒƒ€{Y(ƒ~€€~‚jDƒ~€ƒ‚ƒ‚‚€~}~‚Ueƒ†ŒŒ“–—ž¢¨­±³µ´µQ2369H½ÍÉÊÉÇÇÇÆÄÿº·²®ª¦¡œ—‘Š…„€€€}}|}}}|zzzz{z|}|ƒ‚ƒ…†ˆŠ‰ŒŠ‡…†Š‰‡ƒ‚‚€~€‚€bYdhecabgkxƒ„|upnnmmjd__cfhhjmr|‡‹‹ˆ‚€}ww‚5676665332/,1BGDA:90  + _}‚„……„‡ˆˆ‹ŒŒ‹‡†„‚€~}|{zwutttpopnoopqpprrsuvxwxy{{}‚‰’’‘‡v%&w€~~{y‚G=…~||z~_H…|}€}{||yxxz||}„Ks‹‹‘”˜›ž¥§«°´¸¹¸´J1344J¹ÆÇÈÉÈÈÈÆÃ¿»¶¯ª§¢œ˜”ˆ…ƒ~|zzzxwvxywvwvuuvtvyz{y{~~„…ˆ‰†ƒ‚ƒ‚~}}~~}`[`fccbcglw€ƒ‚|wsoonlke_^`ceggjkmsz‚…ƒzxz}‚‡578645542219EIGB>?:   + + Ly‚ƒƒ„„†…†‡‹‹‡……ƒ€~|{xvuusurqqqpqrqrtuy{~ƒƒƒ‚‚ƒ€„‹ŽŽŽŽ‰‚}{m )z}{{|}}~~4Mƒzzx}S.RV[bdhopsvv{~|€~„E"ƒ›—–›ž¢¤¨«­±´¹¸¹¹»´I5693M¿ÆÈÊÉÈÇÆÃ¿º´¯§ š–Šˆ…€}|zxwvwvttutvtrrsrsqppqsuvxyyz|~€‚‚‚~€~~}zzyz}{`[`baabehox€~|xsnmlkhb^]beggeffimx€€}yw|ƒŠ7865533414BJIGE@<@  + :x‚~}€€€ƒ„„……ƒƒ€}{zwxwuvuurrrqrru{€…‰Ž”“’“Ž‘‹‰‹‹„}yui"({zzy{|||v&^ƒxv{M#(+06:R~„=`twz†‰–˜š›žŸ±»¼À·H7776XÆÊËÉÈÆÃÀ½¸²­¦ —’Œ…‚|{yvuttrrrqsttrqqpqooooooqrsuvuvxyx||||||~~|{zvvvvz|x`[___`bcis|€ztnmjigc^\`dfeggbglp{}}{vyˆ•77534322>JPJIFD>2   + + .wƒ~zyz|}}€ƒ…„ƒ„†„ƒ}}}||yxwwvrrtvz†‘—𠢤¤¥¥¢žš—”Œˆ†ˆŠ…|wro_PJE>Gwxzy{yzxx|dl{rwF/€€5!$&)+.1147›ÂÀõF7887hÌÈÈÇÅý¹´±¬¥ž—”Žˆ€|ywwwtrsrsrqprrqponllmmopnopprsrrstuuwyxxxyyzxwvtuvuwzv]Z[]_`delu}‚‚|tojkhie]Z\bdgffhfms{~|{{vv|‡™Ÿ7553229JVTPJHE:/!  + *f€|xxwz||~}~‚‚„†„…††…ƒ‚ƒ€{zyzyvy|~†Ž– §ª¬¯³±²µ´±¬©¢™’‰„ƒ…†…€ytoqsx{{wvwx{{|}|{z€V#&pvyB5€0"""%'(*+,//15ŸÆÁÇ®B:;<7rÍÈÆÃ¿½·²¬¦Ÿ™”Œ†€}zwvutssrrqrrqqpomlkjiklmmnmmooppppprrsttvusstuvuuututuwoZYZ^aceinv}€|vqniigfa[[aeejhhimu|€|}|zvz†”œ¢¨52123ASWTSKE<70,! + + + "[y~zwtrswwyz|€€„††‡†ˆˆ……ƒ€{{{yz‚‰‘™¤­±³¸¹»»»º¹¶´±¨œ„€‚„ƒ‚|wtrqprtsstuyz|}~}}|sjbXOI;[ww@A‚~ƒƒ. ""#&')*-/2359©ÇÂÇŸ76530…ÌÅľ»¶±¬¥ž™Ž†‚|yvutttrrrqqqppoomkjihhhghijijlmmmnopoqrqppqssrsrttstrsssukVW]`efimsy€yrnlgghe\Y\aefjkjnt|„…~{zvx‚‘œ ¤§31/-3AA?A=75320/#  + + + + U{{wrrqrrstyz}€ƒ„…ˆ‰ˆŠŠ‰‡‰‡…‚~~‚…‹“𥭳º»¼¼½¾½º¸µ±ª£˜Š~{|~€€}zyyvuttuuuvz|||~}|}}}|x{}|{xxoaZSKD<62,)$#I‚€†) "!#%')*+-33/<­ÃÂÆ›S\bv‹µÇþº³®§¤ž•†~zvuttsrqppqppoommljhhghhfdghfggijllmmnnmnpqppqqrrpqrpqqrpqtqbadfhjntyƒ}wojigge^XY]cfjjlpsyƒ‰†}yut{‡“œ £¤1.,./.,.43334422' + + + + + Lyxwtqpqrssty|„‡‡‡ŠŠ‰††††‡‰†„‚ˆ‹’›£«²·¹»»»¼½¼¹¶²«¢—Œ~{zz}ƒ‚‚~{zzzzy{zz|{{€‚|zzz{}|z€{zyzywxxwspnlhcYTQl…‰ƒ?2.2300.1656;@FGNSZv¾ÂÅÆÃÂÊÏÒÐÉþ¹´®ª —ކ‚|xvsrrqopooopoollklljjijmlifiigeffiklllmmnlnpomnopomoqponnnprqighjiovƒƒxqlihge`YX]cfijjmsw~…†€zvqs‹”›¡££.,+-.-/043566753   + + + + + + + + Euttsrsssstux{‚†‡‡ˆˆ†‡ˆˆ…†ˆ‡………‹Ž— ©±¶¹»¼¼¾¾¼¹´°­¨š‚zxyz}…………‚€~€~ƒ‚„„„‡…‡†‚‚‚€ƒƒ„„€|{xyz||~}~€‚‚ƒˆŽŽ‹ŠŒŽ‘““•™ž¡¤§¦©¬±¹¼¾ÁÆÍÊÈÉÉËËÈÉÇž·²ª£›‘‰„~yvtsrponmmmnonoomklnnnoorvurpmjihhfegjjilloonoononnmmlopnmlmmqpkijkoxˆ†{tnieeb`XV[_dghkmqw€‡ˆ€zwurx‚Œ”™ £¦,+,./00224677742  + + + + + + + :wwvvuvttuvwxy~ƒ„ˆŠ†…‡‡ˆ‡‡‰Šˆ‰Š‹•œ¢¬²µ·º¹º»º¹³­¤˜‘yutux{}ƒƒ†‡……‡…ƒ„„„„„ˆ‹ŠŠ‹Š‹Œ‰‹‹‰‰‰‹‹ˆƒ~ƒ‡†‡‰ŠŒŒŠ‘“‘“—˜œŸ£¤§¬²¹º¹½ÁÁÃÆÅÇÊËÉÊÊÊÉÈÉÇž·°«¥šˆ‚~{xtqrrponnlmnnnmoomnopprsux{yxvrqonkigfgigiklnnonnolllkkklllkmnpoljkqz‚„‡…zslhfccaZTW^dfhikqv{„‰…zvssu}‰‘˜œ ¤¥+++-/00344455762   + + + + + + + 1s{}}}|zxvvwwvzƒ†‰‹ŠŠˆˆˆ‡ˆˆ‰‹‹‘•™ ¨¬±³¶¸¸¸¸µ°¨ž˜ˆ~usqqqty|€‚†ˆ‰‹ŒŒ‹ŒŒŒŽ’““”“‘‘’“•“’“’Љˆ‡‹ŽŽ’”•““•˜——™˜˜¡¤¦¨©­°°·»¼¾ÁÅÅÇÇÈÊËËÊÊÉÈÇÈÇžº´ª¢›’‰~zwutrqpqpnnnnonooopppqqsstwxz|{{zywvutppjhhhjklmmmlmnljkkjjjjkijnrplov{„‚|rjgedb_]USX^cegimsx€‡Šƒxurrx‚Œ“šž ¡¢)**,-./11214112*  + + + + + + + + +n€„‡‡…„€|zzzzy~…‰‹ŠŠŒŒ‰Š‹‹ŠŒŽ’—𣫭®°²µ··´«¡œ–‹ƒ}vurqqsuw|€ƒ‡‘””••••–˜š›œœž¡Ÿ››žš™™˜•’Ž‘”““•—–™šœŸŸœœŸ§«­¯±´¹¼½¿¿¿ÁÃÆÇÇÈÊËÊÊÊÊÈÈÈȼ¸´¬¡™‹‚~yywuspnonnnmnnnoprrrrsttuuxyz{}}}}}}{|{zxunkjkkmnkllmmlljlkjihhhikmqnov…‡…~yrleddb`\VTV\bdfilot|„Š…}wsot|‡•—œ¡&''(())(''''%$#   + + + + + + + + "k‹‘Œ†ƒ|~}}„‡Š‰‡ŒŽŒŽ‘•™¤ª¬­®¯±´²­£šˆ€|yustsqsx}…‹’—™¡£¤¤¢¡žžŸ¡¤¥¦¨©ª©¨§§§¦¥£¡¡Ÿš–•—ššœ››œœŸ ¡¢¥¨¦¥¥§­°³µ¸¹¼¿ÀÃÄÄÅÆÇÈÉÊÊÊËÊÉÈÈÇÇÇý¸°¦•Œƒ}zxvvspnmmmmlmnpqqstrrstvwwyz{||}}€€}||}}|{xqmlkmmklkllmlkjjjiighfghiojju€ƒ{qjd^``^_ZRVZadefkkox~„…}xvqqy€‹‘“–™œŸ!! # ! !!"!   + + + + + + + + ]˜›š˜•‘Œ‡„„‚~}‚…ˆ‰ˆ‹Ž‘’‘’”–™ ¦ªª¬¬­°±¬¢™…|{xusutsyˆ‘œ¦¬±¯°®°´µ·³¯«©ª¬¬­®±²¯¯®­¬¬ª¨¦¡œŸ¤¦¦¤¡ŸŸ¢¦¦§§©¬®°²³±´·¸»¾¾ÁÄÅÆÇÉÊËÌÌÍÌÌÌËÉÉÈÉÇÆÀ¹´®¦š’ˆ‚{yxvtrpnllkjlnppqrsuvuwvwxz{{{|}~}~~}~}}}||ytqnnljkljljkjjjjhigfggfgjjYU_ijhfg`a^\Z]]ZTRX\`ccdgmpw}€|xtrorz…Ž“•˜™žŸ"""" !!##"#"!    + + + + + + + + Nˆ—Ÿ¡Ÿžš—‘Љ†ƒ†ˆŠ‹ŒŽ‘’’“”–™œ¢§ªª©ª«­­§›’‰‚€~xwvyz~‡˜¥«£‡mWMJJP_œµÂ½³³³³³´µ³´µ´´±¯­«¥¤¦«®¯«¨¦¦§«­®°°²²µ¹¼¾¾¼½½¾ÁÂÅÅÇÈÊËÌÍÍÎÎÌÌÌÊÊÉÈÇþµ¯¦ š‘‹|xvutrqonlloqqssrqstvwz{{{|}~~}~~€~~}~}|||xurppnlllkkjjjihihffdeeeicMJKSSTWYXY[[[[ZUQTX]bdeehlqx~}xurnpv€‰–˜˜š›œž !!"""!"   + + + + + <€‘Ÿ¦¥¥£ž™”І„ƒ…Š‹ŽŽŒ’““–™š¢¥§¦¦§¨ª©¥™‹‡†‡†ƒ…ˆŠ‹‘š¨±–c<0034453048Gw¯Ä»¹ºººº¸¸¸¹º·µ³±°²¸º»¸³±¯¯²´³´·¸¸¹¼¼¼½¾ÀÁÀÁÂÄÆÆÇÉËÌÌÍÍÌÍÍÌËÊÇÇÅÁ¼³­§ ™Š{xursqpponokWZ]aitvuxxxz|~~}~~~€€‚€~~}|||zyxusronnlklljifihfbcbbcf[FIJJMPQRVY[ZZZVQRV[^_cfeimqy}ytrpou~…Š“™™—š›ž !!" + + + + + + + + + + )q”Ÿ¥©©©¥¡›–’‹Š†ƒˆ‹‘Ž’••—››ž¡¡¢¡ £¦§¨¥¡œšœ›š›Ÿ£¤§«·§f0(/7;889:9<::5.@{¼Ã»¼¼¼¼»¼½¼º·¶¶¸¼ÃÆÊÆÅþ¸¹ºº»½¼¼¾¿ÀÀÀÂÄÄÄÄÆÅÆÉÌËÌÍÌÌËÉÊÉÉÈÆÄÀ¼¹°©¢”‡ƒzwutqrqpppoy>j{xxz|{|~~~~‚ƒ„…„…„ƒ‚~}}}||||zywvtrollkkjhghgeaababgM=BFIKNOQVYXYXWQOQVZ^`bdgkotyyuopqot…Š’–•˜šŸ  + + + + + + + + + + + + `Š—¢©«ªª©¤žš–‘Ž‹ˆŠ’“’“‘“–—™šœŸžŸŸžŸ¢§¨ª®²·¶²²²´·¹¼ÄšB'1524:95457;96784.Q©ÉÁÂÀÁÂÁ¾»ºº»¾ÄÇÈÑ—q‚Ž™¶½»»¼¾¾¿ÁÂÃÃÃÄÆÆÆËÐÐÏÎÉÊÉËËÊÈÉÊÇÅÄÁ¾¹´­£§c&*),+Mxstrqppont[f|ywxz{|}~€€„†‰‹‹‹‹‰ˆ…„‚}{{||}~~{zzxusrqnllkjhgfccaaac=3œÉÂÃÃÃÃÂÄÇËÌÌÌÆV16697P»¿¾¼¾ÀÂÃÂÃÄÇÇÇÇÏo0240KÈËÉÉÈÆÄÃÁ¾¹¶°¨ š“‰//qrqqqqpoyHi€|||}~€‚‡Œ”••”“‹ˆ†„~€€€}~}zxxwvurrrpopmlhdd>.379>DFJLNQSSPLOSUZ^^]_dghnsspnoprv|‚‡ˆ‹ŒŽ‘”˜   + + + + + + + + + + + + +  `‘Ÿ¦¬°¯¯®«¨¥¤¡›˜™˜˜›žžœ››™—•’‘‘‘“˜¢°ºÁÅÆÈËÍÍÏz-58::0N•ÁËÊÈÈÉÊÌǯf59;;:.PÀÆÃÂÁÂÅÉÊÌËÄÇ{+633576œÅ¾¿¿ÂÃÄÃÆÅÇÇÉÊË\49:4eÍÇÇÇÅ¿¼¹¹¶­§ ™‹t!-oqqrrsqsck€€‚„†‹‘”˜™˜—•“‘Œ‰……„‚ƒ‚€€€€~|{{zyxuxxvwuturp]/+18;;ADGJMPRQMKQTZ]]^]`dginropootv{|€‚ƒ…„†ŠŒ’–   + + + + + + + + + + + + + + + R€Žœ¤¬²µµ´±­««©£Ÿœœœœœž   žžœ›•’ŽŽ•¨´»ÀÃÇÊÉÑŠ-4696/sÁÑÉÅÅÅÅÅÆÄÁÃÃ…79<1MœÃÄÃÁÂÅÊÌÎËÂÆ¢//10114.kÅ»½ÀÂÄÄÅÇÇÇÈÉÍÁH6885~ÌÄÅÄÀ½º·²­§Ÿ™”‡ƒ`,npqqrsrw1n‚‚ƒ„†ˆ‰Œ“–™™™˜—•’ŽŒ‰‡ˆˆ†††††‚‚‚‚€|~|}|{{{|}|{zyws3%+-3:;?BEHMQRPJJPUY]^_`acdinmlmoswz„…„‡†‰Š‹Œ“   + + + + + + + + + =yˆ˜¡ª±´¶·¶³®­¬«¦¢¡¢  ¡¢¡ Ÿž™“ŽŒŒ‰‹Œ”žª³¸¼¾Àͱ707<71ƒÎÎÇÈÈÈÆÆÅÆÆÅ¾ȉ32a¸ËÃÄÅÆÉËÌÌÉÀ¼¹H&0/0001/?³½¾ÁÂÄÆÆÇÇÇÇÇË«:9874‘ËÁÁ¾º¸³¯ª£œ˜‘Š‚B#kpprspyRtˆ†ˆˆ‰ŒŽ‘•˜™œ›™—•‘ŽŒ‰Š‹ŠŠ‹Š‰‡…„‚~~~~€€}||U#*,7;?ADFKPOKIJOUY\]_``ceiljklov~ƒˆŒŽ‹ŒŽŒŽ‘’  + + + + + + + + + + + + 'j‚Ž™¡¨­¯²´³°¯®®­ª¥£¥¥¥¥¤¢¡ žœ—“‹ˆ‡…†‡ŠŒ’©¯²´¶Äs+:<=2ƒÒÇÅÆÅÅÈÈÇÇÇÆÅÅÃÃÉ„ÆÌÅÆÇÇËÍÌÊÆ¾´¼n!--../1241†ÆÀÁÂÄÅÆÅÆÇÇÇÏ”4:889ÆÀ¾½¹´®©¢”Šƒ|zy.eqrrrshv‰‹‘‘’”˜›ŸŸžœš–’ŽŽ‘ŽŒŒ‹ˆ…„‚€€‚„‚€~|r# + !(,7AGJJJIHJMPUXYZ[\_abeknt~‡Œ’”“”—™˜———˜™˜˜    + + + + + +  + + + + + + + + + + + 0u›¢¦ª­°°²±¯­­©§¥¦§§¦¥¥¥£¢¡›“І„ƒƒ„ƒ„†Š“Ÿ¨°¹¡-+052ÌÄÆÈÈÈÉÈÇÆÆÅÄÅÅÅÅÅÇÆÆÇÇÊËËÌËÅÁº¸³E(0./+::279:9¦ÅÀÁÁÁ¿ÁÄÆÅÂÆa3979E¯¼¸²­§£œ’‹„ƒ~zxu|ADtsrxZ)…‘‘•––––˜™š››¢£¢ žœš™™š™™š™—•’‘ŽŠˆ‡„…ƒƒ„„†ˆ‡Šˆ‰ƒ|j "(+/9?CHIHFGHLNRUXY[\]^_ajr~…‹Ž‘”•———•”—›œžœ  + + + + + + + + + + + + + + + + e€Œ™¢¨«¬¯°³²°®­¬ª¨§§¦¥¤¤¥¥¤£¡˜ŽŠˆ……‚‚‚ƒ…Š“ž¨´‘%(),2¦ÄÁÄÆÆÇÇÈÇÆÅÄÃÃÄÅÆÆÈÇÇÉÉËËËÉÆÂ½¸¾s$/0/01˜x-;8;6oÇÀÂÁÂÂÂÅÆÄÃÃS4765F°¶±ª¤¡™•‚€|yxutn"$%prwo"*.“—šœœ››œ›œ ¢£¢¡ŸŸŸž›™—”‘‹‰‡ˆˆ‰Š‹Ž‘’‘‘Š: &).4BBCDEFFGGIMRRSVUVWX\`_cfhlnoonnsyxy{€… + + + + + +  + + + + + + + + + + + + + + + + + + +  fŠ˜ §«­®°±²°²´³²¯°°°°¯°¯¯­¬©§¢œ–‘Œ†~~~~‰\­¨­°¶º¾ÁÂÂÃÄÅÆÆÆÅÃÄÅÄÄÃÅÃÂÈ£00113+I½À½½½Ãm08552lÇÀÃÄÃÂÀ¾Å‡+,(%!A—Іƒ~|wwxvuutsv1f|}/yš–M\ž›žŸ ¢¤¦¦¨§¥¤£¢¢£¤£¢££¡¡   žœœœž¢¥¨®±³·¸¹ºº¹¶²¬_ + +  (07>AAEGDGHFGJMPPQSTUVXY[_acedgihkmquw~„’˜ + + + + +   + + + + + + + + + + + + + + + + + + + Nz„“ž¥¨¬¬¬¯±²²³µ´±²²±±°²²²¯®«¨¤ž˜“‡ƒ€€~~~!F¥¡§­²¶¹½ÁÃÃÄÅÄÄÃÅÂÂÄÃÃÄÅÄÈ¿N,300/7¤Å»»¼¹¾œ13223C¶ÄÃÅÃÁ¿¾Ãx%&" Eƒ|yxvuuvutttr)p‡hO£š™Hd¦ž¡¢£¤¥¦¦¦§¦¤¤¢¡¢¢¡¡¢¢  ŸžžžŸ¡¦ª¬°´¸º½½½¼»ºµ´‰#  + + &/8?BBFJFEJHGILOOOPQTVXXYZ\^^acdhjnr}ˆ“œ¢¥  +   + + + + + + +  + + + + + + It€Ž›¡¦©ª©¬¯±³µ¶´²±±°°±±°±²²¯­§¡œ•Іƒ~}~†Z]¥¡§¬¬¯´»¾¿À¿¾ÂŪÂÅÂÂÂÄÄÃÊl-630.,€Î¾ºº¹¸¹½M-3251ŽÊÂÅÃÀ¾¹½l!% NŽ~|xutuuuuttui"%z‡‰A&‘£ £F ! k§¢£¤¤¥¦¨¨§©¨¦¥¤¢£¢¡¡  ŸŸŸŸž ¥ª®²µº½¿ÁÂÁÀ¿¼¸µ§J  + + + + $-8?EGIKJFGIIIHLMLLPRSVXWWY]^`bfltŠ™¢£   + +    + + + + + + + + + + + + Tk|‰”£§ªª¬¯±´·¹¸¶µ³²²²°¯°°°¯®¬§¢Ÿ—‹†ƒ~}}€€„6b¤¨«¯´¸¸»»ÂÂ~?vËÁÁÂÃÀÌ19632/.[…Ÿ²¼¿ÁÂÌ€,5460^È¿»¶ºa !V…}{xvuuuuvvuwc*„’œ}"d®££¨M!!"lª£¤¤¥¦¨ªªª«¬¨¦¦¤¤¢¡¢¡Ÿ  Ÿ ¡£¨­²µ¹¾ÀÂÂÃÂÂÁ¿»³®k + + + + + +,7HGFHJKIGIIIGIIIJLOOTUUVY^`cnt„—›œš™•“’“  +  +    + + + + + + + + + + + + + + agm€Œ—¢¨ª«®±´µµ¹¹¶¶´³³³²³³±±±°®¬§£ž•ŽŠ…‚}~€y$G•¦§­®®®³ºÁ®_/83ɾÁÁű?56651--&$,>Qit{…m364459¬ÄÀ¾º´®²S_}{yyzyvwvvxzy|[/›žŸ§V  1¢®ª©«U!$o®¤¥¥¦¦¨ª«ª¬«ª¨¦¦¤¡¡¢¢ ŸŸ ¡¤ª±µ¹½ÀÂÃÅÄÃÃÂÁ¾º°‹* + + + +/HKFHJJKKKIKIGHJKNPSSVZ_cglsŠ‘‰‰Š‰‹‹  + +    + + + + + + + + + + + + $hfg{‹”Ÿ§«¬®±µ¶···¶³²³²²²²³´´´³±°¬¨¤–‹†ƒƒk.g‘§­®°²l9'5997’ÈÁÄÄb287670+*+)*-*+-.0076446,tº¸¶°§§Bk{wwyyyyzzz{}†W0˜¤¥¥«ž2#$s¶­®«¯] #$k®¨©¨§§¨ª¬¬ª©©¨¦¥¢Ÿ ¡¡  ¡¢¦«±¶»¿ÀÂÄÅÆÄÃÂÁÁ»²£F + + + + + + 3HFIJJKNQQNPRPRTWX^`adjnruz|~|~‚ƒ…††     + + + + + + + + + + + + + + 'kda{•ž¦ª®°²³µ·¸¸·¶µ³³´´³³µ´³³³¯¬ª¦¢ž˜‘‹ˆ…€€}{ƒc'E\a[C+ )12482O»½Ä‡3989:3.%#&'+.232367754351D·ºµ²§ š7$xyxxzzz|~~€‚†Š“` 1¨«­«¸v'$=¬²±°¬¯`"%$b±ªªªª©©««¬«ª©§¥£¡Ÿ  ¢¢¤¦©­±µº¿ÂÂÃÄÅÅÃÃÁÀ½¶¯m  + + + +6@DCCGMQWWWYYXX\_adceikmoqrtttvx|}€…Š + +    + + + + + + + + + + + + + + + ,nf_o‡’›¦®±³´³³¶¸¹¸·¶µµµµµ´µ³³³²¯­«©¦¤Ÿš“Ž‹†‚€{z€`!&(,03.<¿À­=79:=6Kš‹`='$(./1466242256*ˆ½±ª¡™’,3yvxy{~ƒ†‡ŠŽ™k"#%3«­°²´³J%$u¹²²±®³l$(%\±¬««ªª©ªª©«ª§¥¤ Ÿ  ££¢¦«¯²·»¿ÂÃÅÆÅÄÃÃÂÁ¿¹´’( + + + +  +4;;:>EGMSTWTUTVXZ]`cegfijklqswwy|†Œ  + + +  + + + + +  + + + + + 4rj]cy‹—£ª¯±²³´´·¹¸¸···¶µ´³³´³³´³±¯¬©§¢ž˜•‘‡…‚}||‚s0 #%)+&J¤ÄºÁ_.;;<:7ż»®”vVGC?<>BD91541)I¯¦Ÿ˜‘"E}wz{|€„…ˆŠ‘–œo#%%(/–¯¯°²²¹•-<«²²³±¯µz(*)#P±®®­««©©©¨§¨¦¤¢žŸ¡¢¤¦§«®³·½ÀÂÅÆÇÅÄÄÄÄÃÀ¼·¦L  + + + + + + + + +#5435;=AEJNPQSQSVWZ^_cdddinsuw{†‡Š‘  + + + + + + + + + + + + + + + + + 9qj^`mƒ“ ¦«®¯±³µ¸¹º¹¸¸¸¸¸µ´³´³µ¶´´±¯­«§¢žš—”Žˆ…~}‚}L"#&f²¿·À-68<<5}ô³³´¹¾»º·´²³¸À–151-,$‡¤—Œy%5/-/...--..(o ¦{!''((±°°³´³»n{º³´³°¯²*(("A¯±±±®­«ªª¨¨©§¥  ¢¢¤¤§¬®²·¼ÀÄÅÆÇÆÅÃÄÄÄÂÀ»²u + + + + + + + + +%0147:;=BHLOSSQRUW[\_cfhlmtx|€„‡’–  + +  + + + + + + + + + + + + Csk^]f|‘𣍫®°³¶¸¸ºº···¹¹¸¶µµµµ¶·µ³²°®­©£Ÿ™”ŽŠ‡‚~ƒuF<е²±¸«>388;4ZÁ¹²²²²³²¶¾ÃÇÉÊÇÆÂN*-*(LŸŠˆp hª¨ˆ*++,*†µ²²´µ´µµµµ¶µ²±°³˜0))$:©´³²±¯¬¬ª©©©¦¢¡£¤¤¥¦©¯´·¼¿ÃÅÆÆÆÆÆÄÄÄÂÀ¼¹@  + + + + + + + + + + + + ,2269:<@DIMPSTRVW[^bfimqvyƒ…ŠŽ’•“•  + +   + + + + + + + + + +  + + + + + + + Gvm_]aoˆ•ž£§¬®±´··¸¸¶µµ¶·¶µ³´¶´µµ´´´³±¯­ª¦£ž›—“މ†…‚‚…‡‰xP/ ,Gz¡«ª«®´x4;864<¨À³±®¬­­°¶¿ÅÅÆÅÃÀÄ‚#)($ %‡……b #j¬¬‘1.-.+~º³µ¶¶¶µ¸º¸¸µ²±°´¢6)*)5Ÿ´²³²±¯­¬¬«¨¤£¤¤¦¦¦©®µº¾ÀÂÅÇÆÆÆÆÅÄÄÄÁ¾¶©w* + + + + + + + + +/48;>BBFLPRUWWY[`dhmty~‡ŒŒ‘†t  + + +   + + + + + + + + + Rwp`\af{™ ¤ª­®³¶··¶·¸µµ¶¶µ´´´´³²³³³²°®­¬©§¥¡ž›—”‘Ї‡ˆ‡ŒŠ‚ztt}£££§ª®²§¤¦ š–ž¾¸µ¬©©©«³¾ÅÈÇÆÄ¿¼¹§/$$ X…‡T $d­®™5,-,-s¸³µ¶¶··¸¹¹·´²±±³¨<(++.“µ²²°°°¯¯®¬§¥¤¤¥¦§©¬´º¾ÂÅÇÈÉÉÇÅÆÅÅÄþº¶‰c' + + + + + + + + + + + + 6:ƒ‚{l_cccedgz‰”Ÿ¨¯²´¶¸º»¼½½¼¼¼¼¼»¹¹·µ¶µ³±±±²²³²²²°¯®®¬­­­«©§¦£¡Ÿš—‘Їˆ†ˆˆ‰Œ•¢§¬³·»¾¿ÂÆÆÄ½»¸±°­ª­ª¨©ª­¯°²¶¸¸º»º¹º»¼¹¸··»½¼¼¼»»º·¶µµµ¶³´´µ´²±¯®®®­­­­°±°°²²´³³µ¸»ÁÃÆÉËÌÎÌËÌÊËËËËÊÈÆÃÀ»·³°­©£¢˜nadgioo` + + + + + + +     $&&$&(&)*(’’‘‘’ŒG  + + + + + + + + + + + + + + + + A‰†~o`bcgihgo‚™¤ª²´µ·¹¹»½¾½½½¾½¼¼»¹···µ³²²±°¯¯¯±°°°±±±²±°¬ª©¦£¢ŸŸ˜•’Ž‹‡†…‡‡ŠŽ“˜¦¯¶¹½ÀÄÄÆÈÈÅÄÁÀ¼º¹¹·¹ºººº»¼¾ÀÃÁÀÀ¾¿¿¾¼ºº»½¾½¼½¼¹¹¶µ´´µµ³´´´´²±°°°±°±±±²³³µ¶¸¸¸¹¼¾ÃÇÉËÌÌÌÌËÊËËËÊÊÈÇÄÁ»¸µ²®¬ª¨¡ž€cbehlrmb" + + + + + +  +  +  +  #&$$'&&())‘Ž‘ŽŽŽŒ‰J   + + + + + + + + + + + + + + + + KŒ†ƒ~saachkkkkv…’𤭱´µ·¸»¾¾½¼¾¾¾½¼¹····¶µ³²²±±¯­®°²²±²³²²³±¯¬ª©¨¦¥¡ž™”Œ‰ˆˆ‰‰Š“œ£«±¸»ÀÇÄÄÅÆÆÆÇÅÃÂÃÄÅÆÅÄÄÅÆÅÆÆÅÄÃÃÃÃÁ¿¾½¾¾¾¼¼¾¼»º¸··¶¶µµ¶´µ¶³°°±²²²µµ¶¶·¸¹º½ÀÂÃÅÇÊËËÌÌËÊÊÊÊËÊÉÉÆÅÿ¼¸´±­ª©¨¤™Šjcfikmund# + + + + + +  + + + + + + + +  "#%$%'(''()ŽŒŒ‹ŠŠ‹J   + + + + + + + + + + + + + T‹‡†‚va^chjlmklx‡•Ÿ«°°²µ·¹½¾¼½¾¾½½»¸¶¶¶¶¶´³³³³³²±²²²²±°±±²´³³²°¯®­¬ª¦¢™•Љ‹Œ‘–ž£­¤OHuÆÁÂÃÄÅÅÆÎÑÐÑÊÊÉÈÈËÒÑÏÈÅÉÎÌÍÎËÉÊËÆÀ¿¾¾¿¾¾½»¹¹¸·¶¶·¶·µ³±±³³´¶¹º¹º¼½¿ÃÅÈÊËÍÍÍÌÊËËÊÉÊÊÊÉÉÇÄ¿º¶³¯¬ª¨§¥¢œŽthfiikntmf$ + + + + + + + + + !!"$$$'&&()(ŽŽ‹Š‹Šˆ‡‡‰D  + + + + + + + + + + + + ^Љ„|b`cfklllgo“§®±´¶¸¹»¾¿¾¼½¼¼»¹¸·µµ¶µ¶´´µ´´µ³´µ³´´´µµ´µµµµ¶´±°¯¯¬ª¤ œ˜—–‘ŽŽ”šŸ¯e ,,žÅ¾ÂÄÄÄÄ~hl˜ÑÊÉÇɽr’ÊÉ­jhfdbgrŒ«ÇÉÂÁÁÀ¿¿½»¼»º¹¹¹¸·¶µ´´¶·¸¹¼¿ÁÂÄÅÇÉÊÌÍÍÌËËÊÊÊËÊÉÉÈÇÅ¿½¹·´¯­ª§¦§§¥Ÿ–‚qiikjkqtlh+ + + + + + + +  +    "#$%%$#'&&‹Ž‹ŒŠ‡†ˆ†…ˆE  + + + + + + + d•Ž‹‡jbeghllmjjx‹™¢©°µ¶·º»½¾¾»»½¾¼¹¹¸·¸¶µ¶·¸¸·µ···¹¹¹¹¹¸¹¸·¸¸¸···¶µ³³±¯ª§¢žš˜•‘‘‘’”œ–*%,)\Ä¿ÀÄÃÃÇ\)//}ÐÈÅɼE.MÃÌ«312:>;5.6^¡ÍÇÄÅÄÄÄÃÃÁÀ¾¼¼»ºº¹»»¼¾½ÀÄÅÇËÌËËÍÍÌÍËÊÉÉÈÈÉÉÉÊÈÆÂ¿»¸µ³±®«ª©¨¨§¥ ›xmjkkknuvml/ + + + + + + + + + + + +  + + +  !"#$$$%&&&(ˆ‰ŠŠ‰‡…‡‡„ƒC  + + + + + + + + + + + + k•Žˆodfgikklkjp} §¯´¶¸º¼½¾½»¼½½»»º¹¸¸·µµ¸¹¹¹¸¹º»¼½½¼½¼½¼»»»»º»¼»¹·¶µ´°®«¦¡ž™••–•‘œa%$**•Áº¾¾¿Çy076,nÉÈÃÇ]6?±È»H6G«º·³–b-3vÊÉÆÇÈÇÇÇÆÆÆÅÃÁÂÂÁÁÂÄÅÅÈÊÊËÍÍËÊÌËÊËÊÉÈÈÇÇÆÆÇÆÄÀ»·³°­ª©©§¨§¨¨¤¡œ“‚qlilklqwxnk1 + + + + + + + + + + + +  +  +"#$#$%&&((‰‰‰Šˆ†„…„ƒ„? + + + + + + + + o–‘މ‚qcceggijjiiuˆ˜£­²µ¸¼¾À¿À¿¿¿¾¼»»º¸¶´³µ¸ºº¹»½¿¿¾¿ÁÁÁÀ¿¾¿¿¿ÀÀ¿¿½»º¸¸¸¶³²¯ª¦£ž›š˜–‘/!&!L´²¶¸»Ä2622,kÌÄÉn36›ÆÅ[1A¹ËÈÉÍÎ’8+eÉÉÇÉÈÉÉÈÉÉÉÈÇÈÉÈÇÈÉÊÊÊÊÌÌÌÌÌÊÊÊÊÊÉÈÉÇÆÅÃÂÁ¿»·±®ª¥¥¥¦¢¢¤¥¨¨¥Ÿš‘{mllnlnt{|pp5 + + + + + + + + + + + + + + +  #$##$%$'('ˆˆ‡‡‡„ƒ„ƒ‚B  + + + + + u—“‘Š„vbccddfggigk~’Ÿ¨¯´º½¾¿¿ÀÁÂÁÀ¾¿½½º·´´¶¹»¼ºº¼¾¿¿ÀÁÁÀÀ¿¿¿¿ÁÂÁÀ¿¿¼½¼»¼¼º¹¶²¬¨£ ž˜œj.r!†¯¬²³¾-1RR10lÈˇ23ˆÇÈo25¢ËÅÅÄÅÐ6.ƒÏÆÇÉÉÉÉÉÊËÊÉÉÊÈÇÉÊËÊÉÊËËËÊËÉÉÊÊÉÉÇÆÂÀ½»»ºµ±­©¦¥¢Ÿ   ¢¤¦¨§¦Ÿ•ˆsonnnlou}|ot= + + + + + + + + + + + + +  +  !!"#&%%$&‡††…„‚ƒ€}}: + + + + + + + !™”‘‹†{ecc`adffeeiu‰™£«±¸»¼¿ÀÀÂÄÄÃÁ¿¾¼¸··¹¼¼¼½¼ºº½½¾¿¿ÀÁÁÁÁÁÀÁÂÁÀÀÁ¿¿¾½½½½»¸µ³®¨¤ ™—7XžEA§£§¬´)+^¬<4-xÏ–32qÉɆ03‡ÎÅÇÆÄÃÅY1E¼ÉÇÈÉÈÈÈÉÈÈÇÈÈÈÇÇÈÈÉÈÈÊÊÊÉÉÉÇÇÆÆÅ¿»¸·´²³­¦£ ŸŸŸœ››ž¡¤§§¦¥›“}npoonmpy€{qx? + + + + + + + + + + + + + + + +   "#$%%%&&……†ƒ~~|}4  + + + + + + + + %…™–‹…{hcc_`ccdeegk|ž¦¬²¶º¾¿ÁÃÃÃÃÁÀ½¹¶¶¸»¼½¿¾»»¼¼»¾¿ÀÂÂÂÂÁ¿ÀÁÂÂÂÁÁÂÁÀ¾½½¼»¶´³¯ª¥št!‚w}¢¤¬Š((RÑ/4,ª72_ÇÆœ56iÍÄÅÃÂÀÈs44™ÌÆÇÈÈÇÇÇÇÇÆÆÇÇÆÆÆÇÇÇÇÆÈÈÆÇÇÄÂÁÀ½¸µµ²²°­«¥Ÿœœ™š™™šœŸ£¦¦¥ ˜soqrrpnr…}szE + + + + + + + + + +   "$%$$$$$…„„‚ƒ€~}z|8   + + + &†–”†|hcba`abcdfgem|–¢§¯³·¹¼¾Á¿¾¾¾½»¹¶¶¸¹·ºº¹¹ºººº»½¾¾¾ÀÁÁÁÀÀÂÁÀ¿¿¿¿¿½»½»º¶³±­¬¥EO”ŠŽE>˜•š ƒ%#I±¶r'-1|;2N¼À¨?7PÀÁÀ¿ÁÃÇb21”ÉÄÅÆÆÅÅÅÆÆÆÅÆÆÅÄÅÅÅÄÄÄÃÃþ¾º·´²®®«©¦§¤œ™–•”’’‘”—šŸ£¦¥¢š“€lpssrppv‚ˆ|v|P + + + + + + + + + + + + + + + + +!#$$$%&%‚„ƒ€~~|}|{yz= + + + + + + + 'ŠŸ›–މ‚kcbb`_``cefedn†›¥¬²µ¹»½¾¼º¸¸¶¶··¶´³³´µ²´µ´µ¶·ºº¹ºº»¾¾½½¼½»¹¸·µµ²°®­¬«©§¦¤žž‡azvtYlЇv; Ÿ¦B %%*,<¯»±A2=´¾½½ÀÊŒ67>¬Á¾¾¾¾À¾¾ÀÀÀÀÁÀ¿¿¿À¿¾¾¾½½¼½¼¸µ²¯¬¨¥¡ Ÿœš•‹Šˆˆ‰Š‘•›Ÿ¢¤ œ“…roqsrqpr|†Š}z€Z + + + + + + + + + + + + + + + + +  !!"%%&'%‚„€~|{zz|{xz? + + + + + + +  +ŽŸš–’‹ofdb``abbdefghwŒŸ§­±¶»»»¼¹¶µ³³µ·¶³±­®­­­­­­¯±²³³¶¶¶·¸¸·³°°°¯«©¨¤¢¡žœ››˜–Žˆ‘D*|y~m-“’Ÿˆ# ##%1¥´±E-.¢¾¸»²0-&c³¯³±±³³³´³²±²³³²²²³³³²²°°¯­©¤¡Ÿžœœ˜—•’Š…ƒ~€ƒ†‰”™Ÿ¤¦¦£™Švmortrqqu€‹‹}~…d + + + + + + + +  + +  "%%%&'€}}|z{{zwv< + + + + + + + + 2‘𗓆thfdba`abdefhej“¢¨¯³¸¹¹¹¸¸¸¶¶¸¸¸´±®¨§¥¤£¥§©§©««««©«««§¢¢¡Ÿžœš™•’Š‹Šˆ……~pKyte*„ƒ„’c#Š¡ =^sfT80„–‘“—™››œœž £¢¡£¢£¥¤¤£¡ Ÿžš——”‹ˆ…„{{}€‚„‹•šŸ§««¨¢”‚qnprsrqsx„ŽŠ{€†g + + + + +  + +   #$$&&'}}~€|{z{xywuC + + + + + +  2’ž›˜’ˆ{jjhda__abfehhfr‰¦°´·¸¹¹º»½¼¼½½¾»¶²ª¤¡žœžœ›ž ¢¡ Ÿ  ž—ŒŠ‹†‡„ƒ{|{vxzussrxB$T[]\]^^L!Zja nsrsx?]rx1">gonsz{ƒ††‹‹Ž‘‘“”—™›žœœœš—”‘Ž‹ˆ…~}{|z{€ƒ‡”—ž¢¦­®¬¦šŠtmorssrru|‡”‹}ƒ…d + + + + +  + + !###'('}}~}|zxwuusuE + + + + 2”Ÿš“‰{mllgcb`abachiho|‘¡¬²¶¸»¼½¿¿¾ÀÁÀÀ¿º´°©¢›š˜™š››››˜–“‹{yyywvtttrrqoolkje<59[kiiffffj]EC@\e@9:_iihhkI+*)Vfg?,1449AIS_hegjmqru{|y{z|€€„‰“—šœš˜–‘ŽŠ†„~{yz|}~„Š˜›Ÿ¤©¬¯®©¡“|mnpttssrv€Ž•‡€‡Œh + + + + + + +  + + + !##$')){|}|{yxturquP + + + + + + + 7— ž›–Šqqnleb_abcceikio‡œ§®´¶º½¿ÀÀÁÃľºµ±«¦¥¡ž›˜˜™š˜™˜”‘Œ„zvtutsrqrqnmmkhgea^]``^]^[^]^``aaa`^^_`cadeddcfcbb```_^`baaaababdfiknoqtutuuwzzz„Š‘•šœœœš—”‘ŽŠ‡„€~|zz|~‡Ž’™ž¡¦ª«°°­¦›smpqtuussy‡””„‚Šo + + + + + + + +  +  #$$&(+|zy{ywttssorR +  + + + + + + + + + + 9›¡ ˜’‹ƒvtumhea`bdcehjhiyœ§¯³·½¿ÀÀÃÄÃÂÿ½¹µ¯­¬¨¡ž›š›œ›™—“Œ„}wrrsqoponjijffggecdddefecddefgeddcbdcddcdedccabb````^^]^`^^_aaddeilmoopqqruwy{}€ƒ…ˆ–ššœš—–‘ŽŠ‰ˆ†‚~{|}}„ˆ‘”™ž£¦«­¯²¯­£”~lnpsstuuv–”„…Ž’u  + +   +!#%%'+{{z}vvtssrpmI  + + + + + + >¢¡˜“Œ†zuwrnhdabdcehiihmƒ”œ¦®µ»¾ÁÂÄÅÄÂÂÀÀ¿¾¹³¯¬§£¡Ÿž›™˜–’І€{wvrppommlnmmmoononnmmopmlmnmjjjhfghhhhghifefeddedbab```a`bccdfdejnpomoprsw{}~€ƒ‡Š”›žœ˜•“‹ˆ†…ƒ~}|~‚‰‘—œ ¥ª«®¯³²°¬žŒomprtutuvyƒ’™•‡’–x + + + + + + + + + +   +0!$%&&+yyzyuvssqpnnP  + + + + + BŸ¡¢ž™”‰}u{yrmgcbcdegjjlku…–¢©±·»ÀÂÄÂÂÁÀÀÁÂÁ»·´°¬¨¥££¡šš–’Œˆƒ|zwttrtvwxwwvvwvvuvtrqsrqpqqpponmmmnlmkjjikihghhgfddcccdcdeefghjmoponqsw{~‚ƒ†‹ŽŽ•š¡¡žœ˜”‰‡…‚€|}}„„Š”™ž£§ª¯±°±´²®¥–zkoqstuuuv~Š—œ”‰•˜y + + + + + + + +  * "#&&)+wxwvttrrollkW + + + +  J¡¢£ž›•މ€uz{uqleabcdfijkkm{™£¬³¸¼¿¿ÀÀÁÀÀÃý¹·³°«©¥¢ Ÿž›—”“ŒˆŠ‡‡„€€€„‡ˆ……‚‚‚€~|{yzxwxwuuttttrrrssrqnmmonmlllkihhhhiiikikmnpqsusrvz‚ˆ‰‘““•˜š›Ÿ¢¢ ™–‘ŽŠˆ†ƒ~|€‚†‰Ž–œŸ£¨«®±´´´´²«‰qmosutuvvz‚‘›Š“—šz + + + + + & "#&()+-ywttrrronlkhb + + +  N¢¢£Ÿ›•‹„vw|yuqkcbccegikkkr‘§®´¹»¾ÀÀÁÂÂÃÄÄ¿½º¶²®¬§¤¡ œ˜–”“‘“Ž“šŸœœ™˜™—•‘‹‹Š…„‚~}}|zzz{|zwvuvuvvuqsqqppnpqosttwy{ƒƒ„†ˆ–———˜›œžžŸ¢£¢Ÿœ˜“Žˆ…„ƒ€}{~‚†Š•›¡¦©«¯²µ¶··³­£‘tnoptvvwxw|ˆ˜œ‹Œ–˜{ + + + + + + + + + +  !' #&*)*+/ttrsrpmllkjfd + + +   P¦£¥¡›•ˆxx~|ysngabbdghjlmox‹— ª¯µ¹»½ÀÂÃÃÄÅÅÁÀ¼¹¶±®ª¦ œ™–””“‘”••˜™£ª±´´²±­®ªª§¥¥¦¤¦¥¤¡ž›œ™˜•””’‘“‘Žˆ†…ƒ‚€€€~~€‚ƒ‚‚„††‡ŠŽ‘™Ÿ¡¡ ¢£¤¤¥¦£¢£¥¤ œ—“Žˆ„~~|}ƒ‡Œ‘—œ ¦©¬¯³¶¹¸¹¸±§œ„lnqswvwyzzƒ›Ÿš‡™›› + + + + + + +  #'#&*,-.13trsqonljjihec + + + +   N§¦¨¥ž˜“‰}v|~zwqkdaccdgjkkmr}™ ª²·º½ÁÄÃÄÆÇÅÃÃÀ¾»·²®¨¢™–“’“‘’“’—ž¤©±·»»»»¸³±±´µµ¶··¸·´²±°°®¬¬ª§¥¦§¦¥¢ž›š™—•”””•–“’’–—™šš™™š›œ›› ¢¢¥©««ªª«­­¬­«©¨¦¦¡™‘‰„||}~ƒ†‹’˜£§ª­±µ¸¹¹··³«ŸŠtmoquwuvyz‰•ž¡–„‘™›  + + + +$&!'*-.035sqrpmlkhhgddd + + + + + +  M¨¦¨¥ œ–‘‹€x|~~{umhbcbefijjlpvƒ˜¢¬´º½ÀÃÄÅÇÈÅÄÄÄ¿º¸µ¯¨¢˜”“‘“•žª±·¼¿À¼»¸¶º¼¾ÀÁ¿ÁÃÄÃÃÃÃÀ¾¾¼½¼º¶¶¸·¶¶²±®­«ª©§¦§©¨¦¦§¨©­¯±³¯°°¯¬¬­¯±²³³²±²²´´³²¯¬¨¨¥ –Š„€~~|}~ƒ†‹“™¤©¬®²¶¹ººº¶²­£–ynopsvwwwy|‚œ¢¢‘†•œœž + + + + + + + + +  &&"$+/0246pnqmmkffffcac( + + + + + + + +   M§¦§¤ ž™•…y|~€{tneabdfgjjkou{‡”ž¦®¶»½ÂÆÆÇÇÄÄÇÆÅÃÀ½»µ°¬¦¢›™•’“™¡¨±º¿À½»»¾±±ª§§¥¥ž—˜—–”—¥·ÂÄÀ½¾¿¿¿¿¼»¼¼»º¸··¸º¸·¶¹¹½¸ª˜‡„Œ•£´¿½»»»»½½ºº»»»¹¸·³°®¬¥œ‘Š…€~}„…‰”˜Ÿ¤¨­´¶·»¼¼»¹´°§œ†nmpsvywvy|‰– §£‹†–Ÿ ¡~ + + + + + + + + +  +)(#!#(/4567mmlhkjgeddcb`3  + + + + + +    S¤¨©¦¡™–’Šzy€ƒ€}xslb`bdfgjlnqv~Š“¥®¶»¿ÅÆÇÆÅÆÈÈÈÇÅÁÂÀ¼¸³¯«¨¦£¥ª²¹¼ÁÀ¿½»»¾¼RA=;;<;98<7476=K\¯ÆÄÀÀÀÀ¾¼½¾¾¾¾¾½½½¼»ÀÁ¨~X?320,.39KpžÂÇÀÀÂÃÄÃÂÁ¿½¼º¸¶´¯¥™„~}€†‹”™ ¦ª®³¸º¼¾¾½»¹²¬ tmmpuxywv{„›¤§¡ˆ‹™¡¡£z + + + + + +  +((&!#*46889jliihhfccc`\[9 + + +   W¨ªªª¥Ÿ›™•‹~w€ƒ€{tpgba`cgijnoru}ˆ“𤝵¼ÁÃÆÆÅÆÈÉÉÈÇÇÈÇÄ¿»¹¶µ´µ¶ºÀ¿¾¼º¹¹¹»±?9:99::8;:6779;;5.@z»Æ¿½¾½¼½¾½¾¿ÀÀ¾½¿Á¢f:+01/430111,*7VŒ½ÆÀÁÁÀÀ¾½¼»¸·µ²ªœˆ€ƒŠ‘—›£§­±¶º½¿ÀÀÀ¾º¶­¡–}lnptwzxxyƒ‰–¡©©›ƒœ¡¡¢w + + + + + + +  +%)'"!!%17::aa`_]^]WVVSRPJ + + + +  cª©¬¬ª§¢Ÿš“ˆ}}„………zunfa^^aejlloru{„–Ÿª·¼ÀÁÂÆÉÊËËÌÎÍÌËÊÊÊÉÉÇÅÃÀ¿¾½½¼¹¶¶µµ•+)))$PŒŒ‘‘„pJ.(10/311’ø¹º»»¼»¹ÂŸ?0111/00),044.++2530...(k¸±®ª§£ š•‹ˆƒ€€‚…‰”šž£©¯´¹½¾¿¿¿¾½º¶±ª‹{opqtw{{{z}„‰œ¦®­¨‰‡š¤¦¥¥j  + + + 3M3$$"%%%,38:___\[XXSSUQPLE + + +  c¬©­®¬¨£Ÿ›–Œ~ƒ‡‡†‚}xslea_``ehjloqv{…‹•Ÿ®·½ÀÄÈÊÊËËÍÎÍÌÌÌËÌËÈÇÈÆÃÀ¿¾¾¹·µ±¯®'&$%q³®±²µ²²´²“K&-,..1;§»µ¹¹¹»ºÂ¥=23/11,);]™ ¡šŒmG--0,,.,"q¯£¢ œ˜”ˆ„‚€‚…ˆŽ”›Ÿ¤ª°µº¿¿ÀÁÁÀ¾¼¹³«¢‘soqswz|{{|€‡Ž• ª¯®¤‰ž¦¨¦¨f + + + + + + + + + + + + +#(1G2$%!$%'-138]\[XWVTRQPMLHB + + + + +  e­«®°®ª¥ ˜‚~ƒŠŒ‡†‚|uphebb`bfhjlnsu{„Š‘ž«¹½ÃÈÊÊËËËÊÌÌÌÌËÊÊÈÈÊÈÆÃ¿¼¹µ³²°®¯Ž#$#$ p²­¯°±°®­²ºµc'-,--'Y¸´µ¶¶¸º¹L010.0++k§½¿ºº½¿¿Ã´:&,)&##$…¢™™•‘Їƒ€‚„†‰Ž•›¡¥©®´¸¾¿ÀÂÃÃÁ¾»·­¦›‡vopquy||||„Š‘™¥­±®˜Ž¢©¨¥¨^ + + + + + + + + + + + + &)*-@/!#"$%+115ZXVRRSROMLHDEB! + + +  j««°°¯¬§¢žœ“‡€‰Œ‹ˆ†€xsmhfdcbahikmqruy€†Ž™¬·¼ÁÄÆÇÉÊÊÊÊÉÊÊÉÊÊÈÊÉǾ»¹¶µ´±®²#$#%#t´­°³µ³°°´´·»]'.+,+-“º³³²³¼q,/-,.(5‹»¶´´¶¸ºººº»Ä«P"&!"?˜““ŒŠˆ†…ƒ„…†‰Ž•¡¦«¯³¸¼ÀÁÁÃÄ¿»·²ª|ooruxz|{|~ƒ‡Œ•Ÿ©°±«“£©¨§§W + + + + + + + + + + &())/A,"$!%%)034VSQONOLJIIFEC? + + + + +  fª¬±²°®¨£ ž–ˆ€ˆŒŒ‰‚~wpmifcbaaeimoostzƒˆš©´¼ÀÃÅÇÈÊÊÉÊÉÈÈÉÉÈÈÈÅÂÀ½º·¶¶²±²Œ$&#$#p³­±³´³´´µ¶´·­>'++,&]º±²±´Ÿ4**)+(5𷬮°±²´´³´´´²¶±Lz•Œ‹‰†…„…‡†ˆŠ”œ¡¥«¯³¹½ÁÂÃÄÄľº±ªžrmoswy}}||€…‹’›¤®±±¨†–¤¨¨§¥S  + + + +  + + + ((()(+7*$ $%'/43SSOMLKLIFEDC?=& +  k«¬²³±®«§£Ÿ™Š€†ŒŽŽˆ„|vrkeddcaafklnqru}ƒŠ¬¶º½ÀÃÆÈÉÉÊÊÉÈÈÈÈÈÇÆÆÃÀ½¸¶µ´±³Ž$%$%$q²®²³´³´´³µµ³¹„'-+,*7§´²±·j#)(')&†¶«®¯¯°±±°°°°±®©ª›1P‘‰‡‡…„ƒ†‰Š”𡦩­²·»¿ÂÅÅÅÅÃÀ»µ«ž“ƒspoqvy{~~|~‚‰— ©¯±°¡}„›¥ªª©§N  + + + + +  ))()))+1*"!"'/35QOMJJFGECAAA><. + +  p«­²²°¯­ª¦ œ€€ˆ‘‘Ž‹ˆ‚|wqiggfbbbeimpqsuzˆŽ—£­²¹¾ÂÄÆÈÈÉÉÈÇÇÈÈÇÈÉÉļ¹¶µ±³Œ$%$%#t³®°²³´´µ´³³²³®;++*+*‰¶®±­@"&&&!X°««¬¬­°±°¯¯®¬«§¤Ÿ£g4‡…„†…„…ˆŠ‹‘–š ¦ª¯¶¹¼¿ÂÅÇÅÆÂ¿»µ¬Ÿ•voprux{}~~|…Œ”œ¤­±´¯—|Œž§ª«§£E  + + + + + + *)(()((*3*#!#-44MIGGHED@?@?=>:/ + + + +  r¨¬²³±°­«§£Ÿ˜‡‡Œ‘’Š…€{vojggeda`fhlnorsy~~‡‘¡­³¸¼ÀÃÅÅÇÈÈÇÈÉÈÇÇÈÉÇÅ¿½ºµµŒ$&&(%w¶±²µ¶¶·¹¸¶µµ´»Z$))+&m¹¯´—+&$$$+”°ª¬®¯±²±°¯®¬ª¨¤£Ÿš‰''€………„†ˆŠŽ‘”£¦¬±µ»ÀÃÅÆÇÇÅÄ¿»¶­¥•„uonqtwy}~~}~ƒŠ– ©¯³²«Ž‘¢«®¬¦=  + + + + + #)()(()))*/-" ".33GFFDBAA?>>=<;73 + + + + +    sª«²´²¯««©¥ šŒ‚‚‹’“Œˆ†ztnihhfcbbegjmqsuv|€…‘¢«³¸½ÂÄÆÆÆÈÈÈÉÉÈÈÈÆÈÇÆÄ¿¹ºŽ%()*'~¹²²¶··¸»»¶¶µ³¹n())+'^¹²·~$'#%$Gªª«®®¯±°®­¬¬ª¨¥¢ ™‘’;'|‡ˆ‡ˆ‰‹Ž‘•œ¡§¬´¹½ÁÆÈÊÊÉÇÇÀ¼º²§™†wnmptx{}~}}‡Œ“›¤­²µ²¦…–¨¯¯¬§›; + + + + + + + #')(((*((''2- '/2GCAA?;<<=;<;876 + + + +     s­¬²´´±­«ª¦£œ…‚Š“”’‘Œ‡„yrmjihgebcejmprruz|€„‘ «²·½ÀÂÅÆÅÅÆÆÇÇÈÉÉÉÇÅÄ¿¼¿(-+,+‚¸³µ···¸¸·¶´³²·z&)')'U±«±j $#%!_«¥©©©ªªªªª§¦¤¢¡™•‘“G%}Š‹‰ŒŽ”˜œ£§¬³º½ÂÇÈÉÉÈÆÃ¿»·°§›‹|tkortx{~~~~„‹— ©±¶¶±Ÿ~‡›©®®¬¨›4  + + + + + + $(('(')('''(0-$,/DB>=<988987875/ + + + + + + +   oª«²µ´³°­©§¦ –‡„Š’••”’‰†‚~wrnkjihfdegimppruxz{Š›¨­´¹½ÀÂÄÄÅÅÆÆÈÉÉÈÆÄÄÃÁ¾Å‰-/+-.Џµ¶··¸·µµ¶¶´²¹%)&(%M®¨°W!"% o©¤¨¨©©ª©ª©¦¤¢¡Ÿš–”Ž“I+…Ž‘•—› ¥«¯·½¿ÂÆÉÈÈÇÄÄÁ½¸±¨Œ}ummptw{~€~~‡Ž”›£®³·´¯˜|Œ©­®­©˜, + + +  + + + + + &('''%&&&'''(/+(,A?<<;999844543- + + + + + {©¬±´´³±®«¨©¦™‰„Š’˜˜˜—”‰†‚{upnllkjhgfgkmpqqtvxz„•¡«±·¼ÂÄÄÄÅÅÅÆÇÇÆÅÄÄÄÃÂÈ|-210/–¾¸¹¸¸¹¹¹¸¶¶µ²ºs%&$&#P¬¤ªP #"«§©«©««ª©¨¥£¢¡ž›–“’E3’“••˜œŸ£¦¬±¶¾ÁÃÅÇÈÈÇÅÂÁ¿»´«¢’ulmrtv{}~€€€„Š’™ §°¶¸³©‡{’ ª¯¯¯©' + + + + + + + + +(''&&'&&'&$%&%/0%*<>>=9866543420/ + + +    0ާ®²³´³°¯­ª©§„ˆ•˜šš—’Œ‰„ztollllljgghknqqrtuwyzœ¦±¼¾ÁÁÂÃÃÃÄÄÅÆÄÆÆÆÄÃÉo59:7:¦Áººººº»º¹·¶´±·Z!$#% Z¬¤ªQ!xª¦§§¨©©©§¥¤¤¢Ÿ™–“‘“=?˜•—™ž¡¥¨­³¹¿ÄÅÇÉÉÉÈÆÅÁ½º´¬¦—‡yrmpuwx|~€€‡Ž—ž¤«²¶·³¢‚‚–¥­°²°©Ž"  + + + + + (''''&&&&&%%%$&+-$'::::754420//.-) + + + + + + + +   T›¡­³µµ´±°®«©¨¢‘……‹’•˜˜—•’Œ…‚~ytommmnmkhiilpqrsttvxy~‰—¢¯¶»¾¿ÁÁÃÃÃÃÃÅÇÆÆÃÁÅ_89>=H²¾»º¹¹¸¸·¸¹µ°¯®?$$"$i«£«Zd©¤¦¦¦¤¥¨§¦¥£Ÿœ›˜–”—Š(Wœ—œŸ£¦«¯²·¿ÆÈÉÊÊÊÈÈÅÃÀ¼³«£—†zrnpuxx|€€„‹•œ¢©¯¶¸¶²—|†š¨¯²²¯ª…  + + + + + + + + !('&'''&%%%%%%$$$-0$776753210.-,++* + +  {¡ ¬³µ´³²°¯®«©¢•Š…“•˜™š™•‘‹†ƒ}xsppnmnolkkknprsstuusuz‰™¦´º½¾¾ÀÁÂÀÀÅÅÄÃüO:9TgtthR4-42.00.‡®‘‡:"fmi'axw~…‹•–œ ¤§©ª«ª««¬°µ¼ÀÂÁ¿¼»»¼¶©”ˆ†‰’Ÿ©®°²´´µ¸¶°£“¨±´´¯¥¦¥“y- + + + + + + + + +  33-,,,++*+*+*)*)))++**)(()&((&' + + +  + + +  =³ÈÊÍÉÅÉÈĽ®—™¡©°´·¸¸¸·¶··¸¹¹¶­ž”Œ‰‡†‚|wsonpruvx+,F=†ƒ‚) %*/3À»¼ÀÁÃÂÃÆÆÆÆĄ̈F4/..-.129ŸÌÈÊÊÊËÊÉÊÊÊÉÉÉÊÉÎÇ‚F523/*3./3A­º¸··¶³³²°®¬«©§¦¦¥¢ž›—“‘‹Œl H˜”w!!ƒ¡ ¢£¤¢¢  Ÿ  Ÿ¢¦®´¹½ÁÃÀ¼»½½¸¬šŒŠ•ž¨¯³²²µ·¹º·³ª–©°³µ¶·¶¯©²¯£F  + ##$##%,,,/0.164.-,+********++*)))**(''((+.0/4956:758;89=@9:;7 B±ÇÎÏÍÉÊÊÆÂ½»¹§—“˜£«±¶¸¸¹¸·¸¹»½¼ºµ®£›’‘˜š˜”‹†„‚€{x(/rrkW}{y%/ššœ ££¤¨œ3$&%'%c·±³µ¹¢1,.,14ƾÁÁÁÁÀ¿Å21,+0T·´µ´²±°°±¯®¬°®®®¬¨¤žš“‘‡†‹‘z$<”“%d¥¢¤¤¤£¡  ŸžŸ” ®°·»½Á¾¼¾½»³¤”ŽŒ‹Ž–£¬±±±²µ¹º»¹²¦‘’ ª²µ¶¶··µ«®¯¥‘…f + + + +$##$$&-/.11-/172-*-+*****,*)()*))***''(),1-.11011345578<<==<5 V½ÇÎÎËÈÊÊÆÁ¾¼¸©š“™¢©®²¶¸º»º¸¸¹¼½¼¶°©Ÿ•Ž”›š—•’‹†…ƒ~}/'suyM#r}|}**„”•“”˜™›¤b  1›¯¬­®®³S&*(+*_¿¹ºº¼¾¾ºÁ}**)(*]µ¯°®®­¬¬«ª¨©s`b_[USKC<81`‘’‚)0Œ•Œ,:¢§¥¤¢ œœœœ JV­¸¶¼¿ÂÀ¼½¾½º¬™Œ‹Œ“ž¨°±±±³¶¹ºº¶¯ •£­´µµ¶¸¹¸­­°©™…z, + + %$$%%)-0/00-,-26.+,,**+**)**(**)))))'(,+-../00//3355565799:95 + vÃÇÎÎËÉÌÉÄÀ¾½º¬”–¡©®²³·º¼¼ººº»¼¼¸°§¢›“‘–››™–’†„„…3"mzvu)E€…‰0*…Ž‘’‹,X¢¢¦§¨§®s$#'&5¡¸±±³¶¸¸¼r%%%#$T®¨¨§¦¦¤¤¢¢¢¡C!Q’‹/(„““@ f«¡¡¡ž››››˜¤w'$R«¿ºÁÀ½»¾¾¼³¤“ŽŽ˜£«±²²±³¶¸»¹³­œ‘—£­´µ¶¸¹¹¸°«¯­¡ˆ€Q + + + &$$%&*///0/-,,-45,++++++++**)))()*)*'&3/000.10038766777596987! &˜ÃÉÏÏËËËÇÃÁ¾¼º¯ –•ž¦¬°´¶¸»¼º¹º¼¼¼º´«¢–’’˜š›™—•ކ‡‡2!o~{}\f‚ŠŽ3+‡ŽŒ‹ŒŒ‘\$|”—›žŸžž’* #j±§ªª«ª«±m""C¥ žžœšš˜™œ›> T‹Œ’4!}‘•_ '~ª Ÿ››š™— ”1"'$D¾¿¾º¼À¾¹¬˜ŽŒ“§¯±±±²´·¸¹·±¨—˜¥­³·¶¸¸¸¸°¨¯±©’€r + + "&$$&(,//0/.--,-.72++++)*(*))))))))**((6324301128;667:88:=8:9:( + =¯ÄËÐÐÌËÊÆÃÀ¾½»²£˜”œ£ª¯´¶¸¹º¹¸¸»½¼¼º°¤›—’“•˜šœš˜•‹‹8"sƒ€~32„‡Œ‘1*„‹ˆ†…ˆ…25‚”˜œŸ¡šžQ2™¢¢¢ ¡¡¤o/Ž“’‘Ž‘–˜“7_ŒŒ’: y’•„'#.~§žšš™– œ='&(&~û¸¹¿Á½¶¥“Ž˜¢«±²±±³µ·¹¹µ¯¥–‘𥮳¶·¶¹¹¸´«°³¬œ€„5 %''&&),...-----,,091,,.,*)**())))()++**<8798549<9:6::<<=AB?@A@7 +  f¹ÄÍÏÎÌÍËÄÂÀ¾¾¼´¥™•›¡§®²¶¸¹¹¹¹¸»¾¾¾»³©—“‘‘’•˜š›š—•’’:!x‹‡ƒŠa^‘“–‘0(€Ž‰‡ˆ‡Žd(6@JOT[`cIh¡—–—™˜›z]‡‚„…‡ˆ‘”–’1_ŽŒ7 v–•žK! )qœ¡›ž¡‹>&%&%9®¼µ¶»Á¾¸­š”𦮲²²²´¶¹º¹¶­£’‘œ§®²µ¶·¸ºº¶­¯³°£‹ˆ[ ((*))+,///.-,-.-,,27.,,-,++*)))))()**)*<<=>AB@CEEFEHHGO + 4™ºÄÎÏÍÌÍÊÆÂÀ¾¿¾·¦›“𢍮²³¶¹¹¸¸¸º¼¾¾¼¸®£™–““–˜˜™—–’4&}Š‹‡>‡™——˜‰&#yŒ‡†‡Š‹6-‹‘І}.,{‚ƒ‰”••H.--/-$fŒ‰†3"{“—Ÿ+ FrxZ&#"$$²²¸ÀÁ½°ŽŽ‘— ª°³³²²´¸»½»·¬ Ž’žª°³´´·¸º¹·®®²±©•ˆy  ()*+,......--,-..-.22,,,+-+****)*)()(();<DFEEFKLLJMNNOKNLMILR]C  .œ³½ÎÐÎÌÍÌÈÄÂÀÀÀÀ»®¡’“Ÿ§­°µ¸ºº¹¸¹º»¼¾½ºµ­¢™•“‘‘’“““”•••tH'-†“˜›šš™™~k‚‚ƒ‡?#xƒ€{{zkJ‚‚‡ŠŠ‰Š’•™˜”–tnЇ+&ƒ—› Ÿ¡b ""!!"#T¸¸®±»Á¿¸«›Œ—Ÿ¨±µ´³³µ¸»½¾¸°¤•Ž—¡«¯±´´¶¹¹ºº³ªª°±¤‹‹k + *()((&).0/.-././12:82270,++++++++*))*+**BBFIJJKKNOQLNOOPNOMLOQPSD \¹°ÁÍÏÌËÎÌÆÃÁÁÁÁÁ½±¤•’𥬲¶¹»»¹ºº»¼¼¼»ºµ°§ž˜”‘“––—•’“••œˆ}—“‘”™œœ›˜–‘{"`„€€„r!'M‚}zx|OG‡ˆŠ‹Œ“•–•”\!u‰†t#+…–œŸšŸV!""$#f½¹¯®µ¿Â¼²¤”ŽŽ”œ¥°³¶´³³µ¹¼¾¾¸­¡”™£ª¯±±³µ¸º»¼·¬ª¯²¬”†ƒ. #*((('(*.0//..////0638Kd…™“‹‹‰ŠŒ‘”šŸ  ›–••–˜› ¢¥¬µ»¿Åľ»¾Â½±£—‘’‘“”›¥¯µ¸·µ³³´·¼ÀÀ¾¶¬Ÿ‘Š™¡ª±´·¸¹»¼½¾¾¾¾¾µ°±³µµ³¤Š‰Y2/+-.++.00.//00/-./1420/...,+,/43.+****+-,SQRSRQPWWWRQQTVUSRTUWVSUTTW9 + PÁÈÈ¿²¿ËÄÁËÒÌÉËÏÒ×ÒÏÍÎÑÔʸ¡—”“”˜¥­²·»¼¼»»¼¿¿ÀÀ¿¾º´®§¢›˜—šœžŸž›™™˜–’’–˜œœŸ    ŸŸž››ž £§ª°³¯¯­©¦¢—““••““””—šœŸ£¥©­¯°±²³´µµµ¶¶µ´³®®­«¨¢œœ ¡•ŽŒ‹Ž‘‘“•˜˜™šš˜’’“”—𣍬³»ÁÄÄ¿¼½Âý·§™“’‘’‘”™¡¬´¸·¶¶µ´¶º¿ÂÀ¼²¦–Ž‹‘œ¤­°µ·¹º¼½¾¾¾¾½¼·±±³µ·¶«‰q4.,--*).///0/.//../07600.-..,+-/44,)**+++*[Y]ZT[[^eaZ]YX[YW\XXXW[XYVSG”ÎÈȺ²ÃÈÂÂÌÒËËÏÓØØÒËÌÍÑÔË»¤—”•”•¡«±¶¹¼½½»½¾¿ÀÁÀ¾»¹³¬§¡ž›š™šœŸžžœš™™”Ž“—œ £¥§¦¥¦£¢¢££§§¤¤¦¨«­¬­¬§¢™‘‘““•—–™œž¢¦ª®²¶¸¸¸¹º¹º¼»¼¿¼¸·µ´°­©§¤£ œ˜“Œ‹Œ‹Š‹‹Ž‘“””’ŽŽ’•˜œ¢¨®³ºÀÆÇÄÁ¾¿Âľµ«”’”’‘“–œ¥°·¸¶µµ´µ¹¾ÁÁ¾¸¯ Š‹•Ÿ§®´·¸¹º¼¾½½¼»»»¶°±³µ·¶²™Š‚E20,)++.0/./../../.0C¾ÍÍÌÀ¬¸ÉÆÃÄÌÔÖ×ÕÔÐËÈÇÉÌÖÚÒ“‘‘”ž¨°µº¼½¾¾¾¾¿ÀÁÁ¿¼¸µ¯¨¤¡  Ÿ ¡¡¡¡ ›š™™—••—˜™™šœŸ£§¨¥££¢¥¥¥¥¢Ÿ›—”ŽŒŽ•—¢¦¬°¸½¿ÂÄÅÆÅÇÇÆÅÄÂÂÂÀ¾½¼º»¼½¾»·´¯¨£œ’ˆ…„‚‚‚„„„††…‡ˆˆ‹‘—¢§«°¶¼ÁÅÇÄÀÁÄÆÂ¹±¥™“’•“‘’˜£®´·¶µ³²´¸¾ÂÅÿ¸­Ÿ‘‡…‘§¯²´¶¸º¼½½º¹¹¹¼¼µ±³´·¸¹¸°š‹†M00.,,00//.-,-./-+,-/0372..--,*,-051-)***PRRWPPRSSTX[RPSRSQROLNPRQPPPNƒÐÌÌʹ¯¼ÉÆÂÃÌÔÖÖÕÒÍÇÅÆÇÍ×ÚÓÆ²š”’˜¢ª±¶»½¿¾¾¾¾ÁÁÁ¿½¹·³­©¦£  ¡ ¡¡¡¢ žœœœœœžžž¡¢¢   ŸŸœ˜–“”••”’‘‘’•˜œ¡¦©®²·º¾ÁÂÃÄÄÄÄÃÂÀ¾½¾½»¹·¶µ´´¶·µ³¯ª¢™ˆ„ƒ€€ƒ„ƒ…†††ˆ‰“™¤¨«®´»ÁÅÇÆÃÁÅÇý²¦›””””‘“Ÿª´¶¶¶µ´µ¸½ÁÄÄÀ»µ¨™Š„‡” ¨¯³µ·º½¾½»º¹¹º¼¼´±³µ¸¹ºº²žŠg/31,,11//0/-,...-,,..132/.--+,,-,/3.+**)TXSTQQSTWVVXPNRTTQSSPNOPNOQNT0 C»ÌÌËŵ±ÀÉÇÂÂËÓÔÕÕÐËÆÃÄÇÌ×ÛÕȶ–’”¥­´¸º½½¾¿¾¿ÁÁÀ¿¾¼»·³®«§¤¤¢¢££¡ ¡  ŸŸ  žž ¡¢£¢¢¡¡¢¢¢¡ ›˜–••––—˜™š ¡£¥ª¯³¶¸»¾ÀÂÃÃÃÃÁÁ¿¾¼º·¸¶³±¯®®®¬«¨¨¦¡œ–‡€€ƒ…„‚‚…„†‹‘•𤍫¯´ºÀÆÉÇÃÃÄÅÄ¿´§““””’‘—¥°µ¶¶µ´³·½ÁÃÿ¹°¡‘…ƒ‹™¤ª®´µ¹»¼½½»¹¸¸»½¼±°´µ¹º»ºµ¥‹Œ~3*1+,/.////.-,-/-----./01-,,--,,+.030*'-UXSRSRWXZVVXPOQSTRPQRQQOOQPPY@ }ËÉÌËŲ²ÆËÈÄÀÉÓÕÕÔÐÉÅÃÅÈËÖÜÕʺ¢—“Ž‹˜¡ª¯µ·»½¿¿À¿ÀÀÁÀÀ¾½º¶³­¬«§¥¥¤£   žŸŸ Ÿ  Ÿ¢£¤¦¦¥¤¥¤¤£¢¡Ÿ›šœœ››œ ¢£¦ªª¬°µº½ÁÁÂÂÂÃÂÂÁ¿¾½º·¶³²³°­ªª«ª§¢ ›˜“‡„ƒ‚‚€~ƒ…ˆ’˜›ž¢¦ª¯´»ÁÅÉÈÄÂÆÇž¶©–’“”’• ­´¶µ´´³µ»ÀÁÃÄÁ¿¸«›Š‚„‘Ÿ¨ª®³·º»¼¾¼º¸¸¸»½º±²¶¶¸ºº¹¶ªˆŽF"//-.00.-..,-./.-,+-/02/,+,*+,,./47.+,TSQQSVTRWSUUPPPQPONPQSSSTUSTYM.«ÊÊÌËÀ¯µÆÊÈÅÁÇÓÕÖÔÏÉÄÂÄÇÍÖÚÕ˼¤–”‘Ž‹Œ’¦¬´¸»¼¾¿¿¿ÁÀÀÁÁÀÁ½¹¶±¯±­©¨§¦¥¢¡žžŸŸ ¢¡¢££¤¥¤¤¤¤£¤£¡¡ŸŸ¡  Ÿ¡¢¤§ª®±´·¼¿ÃÅÅÄÄÂÂÀÀ¿½º¸¶´±¬ª¨¥¤£¢£ œš˜Šˆ…ƒ‚ƒ€}}~‚†ˆ‹’–› £¥¨ª¯¶»ÂÇÉÉÅÂÅÇÄÀµ¨›—”“““Ž“žª²¶µ´³´¶º¿ÂÄÄÃÀ¼µ¥‘…„—£©«®³¸¼¾¾¾»º¹¶¸½½º¯²¶·¸¹¹¹¸®”†Ž_.,,-/.----,--.-,++,.00,*++*,,,,/42-+RPMRUSPQSTSRSPPONRSOPQRQRTQQQS)eÉÉËÌʼ­·ÆËÈÄÁÅÑÕÖÔÍÇÃÃÄÇÍ×ÛÖÍ¿¨™•’ŒŒ˜¡¨°¶º½¾½¾¾ÀÀÁÁÁÁÁÁ¾º¶µ´±­¬«ª©§¥£ ŸŸŸ ¢¡¡¢¢¢  ¡¢£¥¤¤¢ Ÿ ŸŸ ¢¢¥¥¨ª®±µ¹»ÀÃÄÅÆÆÅÄÃÄÂÁ¿½»·µ³±°®©¦¡žš“’‹„}|}„ƒ}~ƒ‡‘–𢤧©¬°µ»ÁÇÊÊÅÂÄÅü²¨œ”“””“’œ§°´¶´³²µ¹¾ÁÂÄÿ¼²žŒƒ‚Žœ¥«¬­³¹½¿À¿»¹¸·¸½½¹¯µ··º¼¹º¹±žŠŠv$,-,../...,,,-..-*+-/.++**,-,+++.4/+SPQUTRVVSSTSTQQRQSQOQNQOQROOOQ=(œÇÇÊËÆ¹«ºÆÊÈÄÀÃÎÒÔÒÌÇÄÃÅÇËÔÚ×Ï«œ–•’Œ“›¤«²¶¹¼¾½¾ÀÀÁÁÀÂÂÁ¿»¹¸¶´´²¯­¬¬ª¦¤¡ ¡¡¢¡    Ÿ¡¢££££¢¡ ¢£¡¢¡£¥©¬®²µ¸»¿ÂÄÅÇÇÈÆÅÅÄÃÂÀ½º·´²°®®­«§¢›–‘Žˆ†~~……ƒ€„†‹‘•–™œ £¦¨©¬°³º¿ÆÈÉÅÂÃÅû±ªŸ–““”“ŽŽ—£­³µµ²²´¹¼ÁÂÂÂÁÀ½¹®š‰ƒ‡•¢¨¬¬­²¹½ÀÀ¾¼¹·¶º¾¿¸°³¶·º¼»»ºµ¤Œ…€=).////0..----+++,-.,*,,*,,*,,*,44-ƒ‚…ƒ€‚ƒ€€€‚„…‚~|~€€€€€€€€‚ƒ„ƒ€€‚ƒ‚‚‚‚€€€€„†{|}}}~~||}~„ƒ€€~ƒ‚€ƒ‚‚€‚‚€ƒ€€€€‚~€€€€~‚‚}~‚†‚~|~‚~zƒƒ‚‚ƒ‚‚‚€€‚‚€‚ƒ€€‚€€€€€€€‚€€€€€€€€‚‚€€‚‚‚‚ƒƒ‚€€‚ƒ…‚zwy{{zz{zzyx‚ˆ€€€€€€‚‚€€€€€€€‚ƒ‚€€‚ƒ€~€€€€€€€ƒ‚‚„„„}~…~tƒƒ„‚‚ƒ„‚€€€€€ƒƒ„€‚ƒƒ‚€€€€‚‚‚‚‚‚ƒ‚€‚‚€ƒ„ƒ‚‚‚‚‚‚‚‚ƒ‚‚€€‚‚……‚‚€ƒ‡€‚‹ˆ…€€€€€€€€€€€€€€ƒƒ‚€€€€€€€€€ƒ€~…ƒƒ€~ƒy„…………ƒƒ€€ƒ€ƒƒ‚€‚€€€€‚„ƒƒ„„„ƒ€€‚„ƒ‚ƒ„ƒ€‚ƒ€€‚‚€€€‚†…€ƒ„ƒƒ‚€…†ƒ€ƒ‚…†„„ƒ‚€~€€€€€€€€€‚€€‚‚€€€€€€€‚‚€„‚ƒ~€‚€„ƒ‚‚„ƒ‚€„ƒ‚‚‚€‚€€€‚‚‚€‚ƒ„ƒƒ‚€€ƒ‚‚‚€ƒƒ‚‚‚ƒ‚‚€‚ƒ‚€‚ƒƒ‚„‚€ƒ„„ƒƒƒ€€€‚‚wy€…††ƒ‚€€€€€~€‚€€€€€€‚€‚‚‚€€€€~€€€€ƒƒ~€€‚ƒ†„‚€€‚ƒƒƒ‚„„ƒ„‚‚‚‚ƒ‚‚‚‚‚‚‚‚‚‚ƒƒ‚‚‚ƒ……ƒ€€‚‚‚ƒ‚‚ƒƒƒƒ‚‚‚‡ƒ‚‚ƒƒ„„‚€~€…†€~z|‰†‚€€‚‚ƒ€‚€€ƒƒ€€€„ƒ€€‚€~~‚€€€‚‚€€€‚„ƒƒƒ€~€€€‚ƒƒ„ƒ‚‚‚ƒ„„„ƒƒƒ„ƒƒ††ƒƒ„ƒ‚‚‚ƒƒƒƒ‚‚„‚‚‚ƒ„ƒ‚ƒƒƒ€‚‚‚€€‚‚ƒ‚ƒ‚‚‰„‚ƒ…‰‰……„ƒ†…€„‡ƒ}†…€‚‚€‚‚€ƒƒ‚‚€‚‚‚„„€€€€‚‚‚„‚€€„†„…„€€‚ƒ‚ƒ…ƒƒƒ‚‚ƒƒ„€„„ƒ„„‚„ƒ€ƒ‚‚ƒƒ‚€€ƒ‚ƒƒƒƒƒ‚ƒ„ƒ‚‚„„‚‚ƒƒ‚‚І„ˆ…ˆ‡…„ƒ€…‚††„€ˆƒ‚„ƒƒƒ‚€€€€€ƒƒƒ‚ƒƒ€€‚‚‚‚€€‚‚‚€‚‚‚ƒ‚‚‚„‚„„€€‚€€€ƒ†„‚ƒƒ†…ƒ‚‚ƒƒƒ‚‚ƒ„ƒ‚‚ƒ‚‚ƒƒƒƒ‚„„ƒ‚ƒƒ„„‚‚ƒ„…„ƒƒ‚ƒ‚‚‰„€€„‰‚‚ƒ€†‚†„‹‚„ƒ‚‚€€€€€€‚‚€ƒ‚‚‚‚‚‚ƒ‚€€€‚‚ƒ~ƒƒ€€‚…„‚€€…„„‚ƒ„„ƒ‚‚‚‚ƒƒ‚‚………„…‡†‚ƒ‚‚€€‚ƒ‚‚ƒ‚„…ƒƒƒƒ€‚ƒ‚ƒ…„ƒƒƒƒ‚ƒƒ„‚‚ƒƒ‚ƒƒ‚ƒƒ‚‚‰ƒ„‚‚€}‚‚‚‰ƒ€††ƒ„ƒƒ‚€‚‚ƒƒ€€‚‚ƒƒ‚‚‚‚ƒ„ƒƒ‚‚‚‚‚~€†„‚„‡†„ƒ‚€‚ƒƒ‚‚ƒ‚ƒƒƒ‚€††…†…ƒ‚„‚‚‚‚‚‚‚„ƒƒ„ƒƒ‚€‚„„ƒƒ‚‚ƒ„…„ƒƒƒƒ„ƒƒ„„ƒƒ„ƒƒ‚‚„…Š„ƒ„„€€ƒ}‚‚ƒ‚‰„ƒ„‚„‰‚ƒƒ‚‚‚€€€€‚ƒƒ€€‚ƒƒƒƒƒƒ„ƒ‚€€€€‚ƒ~ƒ‚‚ƒ……‚‚‚ƒƒ„„‚€€‚ƒ‚‚‚€€„‡‡‡†…„„‚€‚ƒƒ‚‚€‚‚ƒƒ„„ƒ„ƒ€‚‚ƒƒ‚ƒ‚ƒ„ƒ‚‚ƒ‚ƒ„„ƒƒƒƒ‚„ƒ‚…‹„‚ƒ„„ƒ‚ƒƒ‚„„Š…„„‚ˆ‡‚ƒ‚ƒ‚‚€‚ƒ‚‚ƒ‚‚‚‚€€‚‚‚ƒƒƒ‚ƒƒ‚‚‚‚€€‚€€€€„…††ƒ€€ƒ„ƒƒ†…„€€‚ƒ‚‚„…ƒ€€ƒ†‡…†„ƒ‚ƒ‚„ƒ‚ƒ‚‚‚ƒ„ƒ‚‚ƒ„ƒƒƒ‚ƒƒƒ‚‚‚‚ƒƒƒƒƒƒ‚ƒ„ƒ‚ƒŠƒƒ…†ƒƒƒƒ„„…†Œ„ƒƒƒŠƒ„„‚‚‚‚‚ƒƒ‚‚„„‚ƒƒƒ‚‚ƒ‚‚ƒ„ƒƒƒ‚‚‚‚€€€€€€€€‚††…„‚„…‚‚†„ƒ‚‚€€ƒ„„‚‚‚„……†ƒ„ƒƒƒ‚‚……‚‚ƒƒ‚‚ƒƒ‚‚‚‚„„„„……„‚ƒƒ‚„…†……„„„„ƒ„Š‚ˆ‡‡‚„ƒ‚‚ƒƒ„„‚†‰…††„„ƒ‚‚‚€€‚‚‚ƒ‚‚‚ƒƒƒ„ƒƒ‚‚ƒ‚‚‚‚€€€€€€€€€‚††…ƒ‚‚ƒƒ„ƒƒ„…„‚€‚‚€‚ƒƒƒƒ………†‡‡…ƒƒƒ‚‚ƒƒ„‚‚…„‚‚„…„„ƒ‚‚‚‚‚‚ƒ„ƒ„……„ƒ‚‚„„„„ƒƒ„……„„‚„Š€…‡ƒƒƒƒƒƒƒƒ‡Ž…ƒ‚ˆ„‚…†…ƒ‚ƒ‚‚‚€‚‚‚‚‚‚‚ƒ„‚€€‚‚‚€€€‚‚‚‚‚€‚…‡†„„‚‚†ƒ‚‚€„„…ƒ€€€‚‚‚ƒƒƒ……†…ƒ„„‚ƒƒ„ƒ„ƒƒ„„‚‚‚„„ƒƒ‚‚‚ƒƒ‚ƒƒ‚ƒƒ‚ƒ„„„……ƒ„„ƒƒ„„„„„…„ƒ†Š‚…†‚ƒ„„……„„ƒ†Œ„‚…‰ƒ„ƒƒ‚ƒ‚ƒƒ‚€‚‚‚ƒ„„ƒƒ‚‚‚‚‚€‚‚ƒ…„ƒ€„ƒ„ƒ‚‚‚€‚ƒ‚ƒ‚ƒ‚€€€€€ƒ‚‚‚‚……†„ƒ…†…‚ƒ„‚ƒ‚ƒ„ƒ‚ƒƒ‚„„ƒƒƒ„„„ƒƒ„†…„„„„„…„„„…„ƒ„„„„„……ƒƒŠ‡‡†‡‡†…†‡‡†„‡Œ„‰ˆƒ…ƒ‚‚‚‚ƒ‚„„‚€‚‚‚„…„ƒƒƒƒ‚‚‚‚‚€€€€€€‚…ƒƒƒ‚‚€€‚ƒƒƒ‚ƒ„ƒ€ƒ„ƒ„„ƒ‚ƒ……€‚ƒ€€€€‚‚‚ƒ„ƒ„……ƒƒƒƒƒƒƒ‚‚„…„„„ƒ„„‚‚ƒƒ„„„ƒ…†„„„ƒ‚€€€„‡ˆ†„………„„‚‚†„„………ƒ‚‚€‚€€‚ƒ‚‚‚„ƒ‚‚ƒ‚ƒ„ƒƒ‚€€€‚€€‚„„‚ƒ‚‚€‚‚ƒ‚€€‚„‚‚€€‚ƒ‚€€€ƒ‚€€€ƒ„‚€€ƒƒ„…„ƒ„„ƒ„„ƒ‚ƒƒƒ„ƒƒ„„„„‚‚ƒƒƒ„„ƒ‚ƒƒƒƒƒƒƒ„~{zz||}€~}|~€…Š‚ƒƒ„…„‚‚‚€‚‚‚‚‚ƒƒ‚ƒƒƒ‚‚‚‚‚ƒ‚‚€~€‚‚ƒ€€€‚„ƒ‚ƒ‚‚‚€€‚‚‚‚€€‚ƒ€~€‚ƒ‚ƒ…„„ƒ‚‚„…„ƒ„…„„…ƒƒ……„ƒ‚‚ƒƒ„„…†…„„ƒƒ„ƒ„ƒƒƒ„„ƒƒ„……„„……„ƒ‚zz~€…‡††…†‹„„„„ƒ‚‚ƒƒ‚‚ƒ„„…ƒ‚ƒƒ‚ƒƒ„„„ƒ‚‚‚ƒ‚€€€€€€€€€ƒ††€ƒ‚€€€€€‚ƒ„ƒ„‚€‚€€€€‚‚‚‚ƒ„‚ƒ…‚ƒƒƒƒ„††………„ƒƒ„„„„ƒƒƒƒ‚ƒ„…„ƒƒƒ„††……‚ƒ„„…†………„……„ƒƒ„…„„…„ƒ{{{}‚†…‚„„„ƒƒƒ‚‚‚‚„„……ƒ‚ƒ„ƒ‚‚ƒƒ„ƒƒ‚‚ƒ‚€€€€€€~€‚„‡†ƒ‚€€€‚‚‚‚€‚ƒ‚€€€~ƒ…„ƒ‚ƒ„„„„„„„ƒ„…„„…„ƒ„ƒ‚‚ƒ‚ƒ„…„‚‚ƒ………††ƒƒ„„„„„††…„‚ƒƒƒ„„ƒ„………„…ƒ‚}{~ƒ………„ƒƒ„ƒƒ‚‚„……„„„„‚‚ƒƒ„„„„ƒ‚€€€€‚‚‚€€ƒ…†ƒ€€€‚€€ƒ‚€‚‚‚€~€€~€~€}ƒ‚|‚„„„„„„ƒ„ƒƒ„„ƒ‚ƒƒƒƒƒƒƒƒƒƒ„„‚‚„†…„…„„„…†„‚ƒ„……„ƒ„„„„‚ƒ…„ƒ‚‚„…„ƒ„†‡†…„ƒ„ƒƒƒ„„„ƒ„…„…„„„ƒ„ƒƒƒƒƒƒƒƒ‚€€€€€€‚‚€‚ƒ‚ƒ~„……ƒ€€€€‚ƒƒ‚ƒ€…„€‚„…‚‚€€€€ƒ€€‚}|‚„……„ƒƒƒƒ„„‚‚ƒƒƒƒ‚ƒ‚„„ƒ‚ƒ‚‚ƒ„„ƒƒ„…„………„…„„„„ƒƒƒƒƒ‚‚‚ƒ……ƒ‚‚„„ƒ„„„…„ƒ‚‚‚‚‚‚‚ƒ„ƒƒ„„ƒ„ƒƒƒƒƒ‚‚‚‚‚‚€€€€€€~€€€‚„ƒ‚€€„ƒ„‚€‚‚ƒƒƒ‚ƒ‚€€ƒƒ‚‚‚ƒƒ€€‚€‚€€€}}‚ƒƒ„„„ƒ‚‚ƒ„„ƒƒ„ƒƒ‚„ƒƒƒ„„ƒ‚‚„ƒ„ƒ„„„ƒ…††……………ƒ‚ƒƒƒ„„„ƒ„„„„„…„„„„„ƒ„……„„„„„ƒƒ‚‚ƒ„„…„……ƒ„ƒƒ„ƒƒ‚ƒ„ƒ€€€€€€€€‚‚‚‚‚‚}ƒ…‚‚‚‚‚‚ƒ„ƒ‚€€‚ƒƒ€~‚‚€€€€€~~{|}ƒƒ…†…„ƒƒƒƒ„ƒƒ„„„ƒƒ„„…ƒƒ‚‚ƒ„„ƒ„…„„…„‡‹†ƒ„ƒ„„„††……………„„„ƒƒ„……„„……††…†………„„„…ƒ„…„………†…„„…†…„ƒƒƒ‚‚‚ƒƒ‚‚€€‚‚€€€‚‚}„„ƒƒ‚ƒƒƒ…ƒ‚€‚ƒƒ€‚„ƒ€€€€€€~‚ƒƒ‚ƒ„†‡†…„„…„„„„„ƒƒ……„ƒƒƒ„„ƒƒƒƒ„„…†„‡Š„ƒ‚ƒ„…†Š‹Šˆ„„‰‹Šˆ‡†††‡†„„‡Š‰‰ŠŠ‰‰ŠŒ‰…„ˆ‹Šˆˆ‰ˆ‡„‚ƒ………„ƒ‚‚‚‚‚€€€€€€€…‚€‚‚‚ƒ„ƒ‚ƒ‚‚€‚‚‚„€ƒ€€ƒ}}€€‚†‚‚‚„„ƒ‚„†…„„„„ƒ…„„„„ƒ„†…„ƒƒƒƒ‚‚ƒ…„……„…†ƒ„ˆ‰‚}ˆ‰†…‡‰Š‰‰‡‚ˆ‡‚€‚ƒ‡ˆ‚€ƒ„†…ƒƒ‡‹‡ƒ„„„„ƒ‚‚‚‚ƒ‚‚€€€‚€‚~‚€‚ƒ€ƒƒƒ‚€€„…~€|{€€€ƒ€‚„†‚€€€€„…„‚‚ƒƒ„ƒ„…††……„„„„„…„„‚„…ƒ„„…†‡ƒ‚‚…‹Š…|~ƒƒ„‡…}€€€€‰…€…ƒ~z{|}|„€|€~|}ƒŽ‰ƒƒƒ„ƒ‚‚‚ƒƒ€€€€€€€€„‚€‚ƒ……ƒ‚‚‚‚‚€€€€{ƒ€~}€€€€€€€€‚ƒƒƒ„€€ƒƒ…†„ƒ‚ƒ……„„…„„„„„……„……„„ƒ„„„„„„…‰„ƒƒ…Œˆ‚|€„………Š…{|}{z{€‚…‚„|~~}{|€€~~„|x|ƒŽ‡„ƒ‚ƒƒƒƒ„‚€€‚‚€€€€€‚€€‚ƒ„…‚ƒƒ‚‚‚‚€€sz}}|€~€€€‚‚ƒƒ€ƒƒ{~„ƒ‚ƒ„ƒ‚„„„„„‚ƒƒ„…†…ƒƒ„ƒƒ…„ƒ„†„„„‰†‚†Š‡}|…ƒƒƒƒ‡…‚ƒƒ‚‚ƒ‚„„‰‡‚ƒ‚‚‚ƒ‚~ƒŠ‚ƒ‚{|Љƒƒƒƒƒ€€€€‚‚€€€€€€ƒ…‚‚„ƒ‚‚‚ƒ‚€rsx|}}€~€€€~€ƒ‚€~yw‚ƒƒ„ƒƒ‚ƒ…†ƒ‚„„„„††…„„ƒ‚ƒ„………†…„ˆ†„„‚}|…„ƒ‚‚‚‡‚ˆ†ƒƒ…„„„…„…ƒ††ƒƒ‚ƒ„„……€€…†‚……~~ˆ‰ƒ„„ƒ‚€€‚ƒ€€€€€ƒ‚€€‚ƒ…ƒ„„‚€ƒ„‚€sqvy|‚‚€~€€~€}}||{z‚ƒ„„…‡‡…„…††…†…„…†††„„„„ƒ„†…††…„†…€~€…†„ƒ„„„„ƒ…ŠƒˆŒˆ‰‰‰‰‡…ƒ†ƒƒ†‰‰ˆˆ‰‰‡ƒ€€…†ƒ„…ƒ„~‰†……‚‚€€€‚‚€€€€~€‚€~€‚„ƒ„„…ƒ‚ƒ€sqsux€‚‚€€~~~~‚}{}~}~|}ƒ…………†††††‡‡†††‡‡‡†……††…„…„…„ƒƒ‡…€ƒƒŒˆ‚…†……„‰‹…†ˆ††‡ˆƒ‚‡ƒ€‚„„……†‰…€€‡‡ƒ„„†„ƒˆ‚‚„„‚‚‚‚‚‚€‚‚‚€€€€~|~€€€„ƒ€ƒ…ƒ€psuvv{€ƒ|{|}~€||‚‚ƒ„„„„…‡†„„……†‡‡‡……„…„ƒƒ…„ƒ…„„‰„€„ƒƒˆƒ…‡†…ƒ‰‰}~~}}‚‚ˆƒ~}~}~~…ƒ€…‡ˆŠ‹‰…†‚ƒ‚ƒƒ€€€‚‚€€€€€€€€~{{€€€‚€€ƒƒƒ€|{osuuuv{€~‚€~{z|}|{~|z€‚„ƒƒ„…†…†‡…„……………†‡‡††„‚‚†‡†…„…‡„€€‚ƒ„„††…„„‚ˆˆ„…ƒ~}€€ƒ…~€€‚€~„‡„‚…‚€‚‚‚‚‚€€€~|~‚ƒ€€ƒƒ‚ƒƒ€}}rqrsxwxz€‚~~}|{{}~~~}|z}‚ƒ…„…††‡†††…„„†…„†‡††……††„…†††……†€€‚„„‚‰……††‰‡„†††‡†‡‡††„ƒ…„„…„„„ƒ‚ƒ„€€€‚ƒƒƒ‚‚‚‚‚ƒƒƒ‚‚‚€€€€€€€~~€‚„ƒ‚}}€„…„ƒ€stsuwttv|ƒ}}~}~}}|{}~|y|ƒ„„…†„ƒ…‡†…‡†…„ƒƒ………………†‡†„ƒƒ…†‡…~€‚†……ƒˆ‰………†ƒ‚„…†‡ˆ‡†‡‡„‚…„„„„…„„ƒƒƒˆ…€ƒ„…ƒ‚‚ƒ‚‚ƒ€€‚ƒ‚€€‚‚ƒ€€~|}}‚‚€~~~~€…„~€~uvvtsux{z|~|}}}|~~||||}|y{„†…ƒƒ‚‚…††‡‡†…†‡†………†…„…†…„„ƒƒ…ˆ…ƒ„†ˆ††ƒŠ††„„‡ˆˆˆ‡†…ƒ‚‚‡†…„„…„„ƒ‚„Ї‚„ƒƒ‚ƒƒ‚ƒ‚‚‚‚‚‚‚ƒ€€€€ƒ€||~~{|}~ƒ‚€€€~~€€€ƒ…€|}€‚vsstw{{}€~}~~~{||}|}~}}~{y‚…ƒ‚„…………†……„………†„‚‚‚‚ƒ„„„„ƒƒ†…‚€‚…†ˆ‡†„ƒ††…‚~ƒ„„…†ˆ†‚€€„†ˆ‰ŠŠ‹‰„‚…‹…‚„ƒ‚‚ƒ‚ƒƒ€‚€€€€€€~~}{{}}{}~€€€‚~~}~{~‚utuy}€~~}€~||~~~~~}}z‚†„…††††„„…††‡†„††ƒ‚ƒ‚ƒ„…†„„…„……ƒ„††‡†ƒ„„…‚~~~€ƒ†€„‚€ƒ…„‡‹ƒ‚…‰„‚„„„…‚‚ƒ‚€‚€€€€€€€€€€~~|}ƒƒ‚~~}}€~~€su{~‚€~~~}~~~}}€~}|}~„†ƒ„‡†…………†‡‡‡‡††„‚‚„…„„‡ˆ‡‡††‡‡†††………ƒ„ƒƒ„……„‚€€†…„…€€€€ƒ‡„‚€„…„ƒ„ƒ€‚‚‚ƒ‚€€€€€€€€€€€}|€„„€€€~„„‚€„ƒ„vzz|}|}‚}{|}~|~|{}~}~~{‚‡ƒƒ†‡„ƒ„„………†……„‚ƒƒ„„…†††‡†…†‡‡††‡‡‡†…„ƒƒ„„†…„††„„†„‚„…„„ƒ‚€‚€‚„…ƒƒƒ„„……„ƒ€€€€€‚€€€€€€€‚}€€€~}~€‚€€€€ƒ„ƒ‚€€‚†…ƒ}}{|€€||y}~}}~}||}}~~z€…ƒƒ„…„„„…‡†„„ƒƒ„ƒƒ„…†††‡ˆ†„„„…„„„ƒ„„„……ƒƒƒ„„„…††ƒƒ„„ƒƒ„……„ƒ‚ƒ„„…„„„„……„ƒƒ„……„‚‚~‚ƒ‚ƒ‚€€‚‚€€}~€}~€ƒ‚‚€€~€‚„~€€‚„„ƒ€~‚~|z{~~}}~~~~|}{~ƒ„†††††‡‡†……„„……„…‡††‡‡‡…ƒƒ„……„ƒ‚‚„„…„ƒ„„„…„„„„„…„ƒƒƒ„„„„„„…„„……††…„„……††„‚‚‚‚‚‚‚ƒ„……‚€€€€€€€~|ƒ‚€~~€ƒ‚€€‚ƒƒƒ~~~~}}~|~}~}}€~}~|}|~{z€‚…†††‡†……†…ƒ„………†……„††…††„†‰Š‡‡‡‡‡…ƒƒ„„„………„„„ƒƒ„„……„„„ƒ…††…„„…†‡†…„„„……„ƒ‚ƒ…‡†‚‚„„ƒƒ„ƒ„ƒ€€~€€€~{{~€€€~€€€€‚ƒ‚‚‚}}|{|}~|~}€}|{|}}{}}}~}~}|x~‚ƒ‚„„…†……„ƒƒ„„„„„ƒ‚‚ƒƒ†Œ‰‚€ƒ„ƒ…‰‰†„„„…„„ƒ„‰ˆ†„„…„…†…„…†††……†††…ƒƒˆ‡‡…€€€€ƒ„†…„„……„ƒƒ‚‚€€‚€€€~~~}z|~}}~€€‚€€‚ƒƒ‚~|z|~€~||||}|||{|}|||~~||}‚ƒ„„„„„„ƒƒƒ„………„ƒƒ„„„‰‹„€~€€ƒ‚„‹†„„„„…„‡‹Œ†„…„„……ƒ‰Œ††‡…„„„ƒ‚†‚‚…€„€‚€„†„„…„ƒ„ƒ‚€€€‚€€€~{{~}~~|€€€‚€€~{|€€}|~}~}|~||~~}}}~}~}~€‚‚ƒ„„ƒ„„ƒƒƒƒ„……„…†…††ŠŒƒƒƒ‚‚††ˆ†„……„ƒ‡Šƒ‚ˆ‡„†††…†„Šˆ‡„…††…†„ƒƒƒ~ƒ†‚ƒƒ‚€‡‡„„…„ƒƒƒ‚€€€€€€‚‚~z~|€~~~€€€‚‚ƒ…ƒƒƒ}}||z~}||{z~}}||}||~~~}~|}‚‚ƒ‚ƒ„„„ƒ‚‚ƒ„…………††…†‡‡Œƒ}€…‡………ƒƒ…‚„„……„„І„‰‚………†„ƒ‡ƒ…ƒ„…………ƒƒ†‚ƒ€€~††‚‚ƒƒ„ƒƒ‚€€€€€€€|}ƒ€{}}~€‚€€ƒ‚€€„……}~{{}z}€~}€}|~|}~{}~~|}~}|~ƒ…‚ƒ……ƒƒ„ƒƒ„…†††††…„…ˆ‰…†††………ƒ€‚………‡…††€€‰‡…„„…„ƒ…‚‡„†‡†……„‚‚€……‚‚„€€††„ƒ‚„„ƒ‚€€€€€‚‚{{‚€}€€€‚ƒ‚€€ƒ‚z}}}~|||}~~|}}|||{{}€~~~}}‚ƒ‚„„„ƒ„„……„……………†††…‰†€€ƒ…††‡……††……………††Š„ƒ‚ƒ‡‰…„„…„„…†‰ƒ†„„„„„„€€†ˆ‚„ƒ}ˆ†„„„ƒ„„„„‚€€€€‚ƒƒƒ„ƒy{€}€€~~}~€‚‚„……„‚‚~~~|}}{|~}~~~~}|{}}}€~||~€‚„„„„ƒ…„ƒƒ„„…†‡……††ˆ‡€‚„………………†‡†…†††‰‡…†€ƒ…ƒˆ„„†…ƒ„„†‰ƒ„††…„ƒ‚ƒƒ€„ˆ„…‡‚~ƒˆ…„ƒƒƒƒƒ„„‚‚‚‚ƒƒ…ƒ|z~}}}}~~~~~~~~„‡‡†„€€~~{|}~}||}~€~|}~}~€€€}}~ƒƒƒƒ„„„…„ƒ„„…†………†‡‡†ˆ„„‚‚„…††…†………………Ї€ƒ‚ƒ‚…‚‡ˆ†…ƒ††„‰…†……„„ƒƒƒƒ~€ƒ…Š€ƒŠ†ƒ‚‚ƒ„„ƒ€‚ƒƒ‚ƒ‚|€~}€~~~~~‚‚‚‚ƒ„‚~~~~}}{~}|}~~}{~}|}~~~~~~z|„ƒ‚‚ƒƒ………„ƒƒƒ„††‡ˆˆ‡„††…ˆ„…†††………‚„…„ˆ‹ƒ‚‚ƒƒ„…‡Š„„ƒ‚†ƒ‚…ƒ…†…ƒƒ‚„„‡‚€~†‡~€‚‹…‚‚ƒ‚‚‚€‚‚‚ƒƒƒ‚€}}~~€€~}~~~€€‚„~||}~~~~€~|}~}~€~}}~~}x|ƒ‚‚„„ƒ……„„ƒ‚ƒƒ„…†††‡†„‰†ˆˆ„‚ƒ„„‡‰„…†‰ƒ€ƒ††‡‡†ƒ‚†„ƒ„„‡…†…„„„ƒ‚ƒƒ„‰†€‚„Œƒ‚‹ƒ‚„„„„‚‚‚€‚ƒ„„ƒ„…„~|}~~}~~€‚ƒ~~€‚ƒƒ‚ƒƒ|{|~}}~~€€}~~|}~~~}}{|{~y|ƒ‚ƒ„„„„„„ƒ‚ƒ„„„„„†‡†…†ˆ„„ˆˆˆˆˆ‡†Š†„‹†ƒ„††‡‡†‚„ˆƒƒ‚…†…ƒ…†„ƒƒ‚†ˆ‹‚‚ƒ‰‡‚€‚„ƒ„„ƒ„ƒ‚‚‚ƒƒ„ƒƒ„…|}~}~}~‚„~€‚‚‚€‚†…ƒz{|}}|{}~|~}}~}}}~}||~~~~~z~‚ƒƒ„ƒ„………„„„„……„…†‡‡†…„„ƒ†ˆ‡…„…††Š‚ƒƒ‚……„„ƒ€ƒŠ„„‚†„ƒ‚ƒ…ƒ‚„ƒ€†‡Œ‚ƒ€„Šƒ€Œ…ƒ‚ƒƒ„ƒƒ‚‚ƒƒƒ‚‚ƒ‚ƒ„ƒ|~„€~€€~~|~€ƒ~€€€€€€€‚‚ƒ‚ƒ|z|~~}}~~}~|~€€~|||}}~}|~€€y~ƒ€ƒƒƒ‚ƒ„…„„………………†††…‡†ƒ„„„‚ƒ„…„‚…Œ‡‚‚‚ƒ‚‚‚…†‡‡„‚†‚ƒ†‡‡…„Šˆ€…„‹†„ƒƒƒ€ƒŠˆ‚ƒƒ„„ƒƒƒƒ„ƒ‚‚‚ƒƒ„„y€€~€€~}|~}€ƒ~~€€‚‚}~|{|}}|||}|||~}}~~||}~~~~~zƒ…ƒ„ƒ‚ƒ„„„„„„†……„…………„……†„ƒ„ƒ‚ƒƒ‚„…ˆ„‚‚ƒ……„ƒ„…ƒ‡‡„…ƒ‚ƒƒ‚ƒƒ‚ˆŠ€…ˆ‹…„„ƒƒƒ„~„‹ƒƒ‚„…ƒƒƒƒƒ„ƒƒƒƒ„…ƒ~y}~~~}||}€}|}~}€‚ƒ€|{{}~}||{{|}}}~~}~€~~}~}~~~zzƒ…ƒ‚‚ƒƒƒ„„„„„……„„„„……„……††…††„„……†…ƒ‚‚ƒ„…„„ƒ„…„…„ƒ†„„„„‚ƒ„„ƒƒˆˆ‚†ˆ‡ƒƒ„…„ƒ„„‚…ˆ…‚ƒƒ„ƒ„„„…„…„„„„„ƒ}x~}~~}~€~}~}}~}z{€~|zxy€€|{~|~~~}~~}}~~}~~~}}zx‚…‚ƒƒ‚‚‚ƒ„„ƒ„„…………„„„„………†‡‡††‡‡†…†…„ƒƒ„„ƒ„…„„†„ƒ‚ƒƒƒ‚‚‚‚ƒ„„ƒ‚ƒ„„„……ƒƒƒƒ‚‚ƒƒ„„„ƒƒ…„ƒ„…ƒƒ‚}~x|~}}~~}}€~}~€€~|z}~}|{{xusyy||€‚‚||}}}~}||~~~}€~{y‚†‚ƒ‚‚ƒƒƒ„„„„…………„„„…„ƒƒ…††††††…………ƒ„…„ƒ„„……„……„„……„ƒ„„ƒ‚‚‚„„„…„„‡†…ƒƒƒ„ƒƒ„„ƒƒ„………†…ƒƒ„„„„ƒ‚‚x}~~€}~}}~}~~€}zzyyzyyyvuuuuvwux|ƒ~{}}~~~}}}~~~}}{y‚†‚‚‚ƒƒƒ‚‚ƒ„……„…††…………………„„……†‡‡†……„„ƒƒƒƒ……ƒ„…„……„ƒ„„„„„„„……………„……„„…ƒ„………††…†‡‡†‡†…………„ƒ‚ƒx}~}~~}}}~~}~}||{{zyyyxwwvuswxzyy{€~}|}~|{||}||||}}~||€~yx‚…‚€€€ƒ‚ƒ„„……ƒ„…„„…†…………………†…………††‡†††„ƒƒƒ‚‚ƒ„„…†††„ƒƒ„……………††††……„„………†„„……………„†‡†…„…†……„„„ƒ‚~ƒ…w~€€~}}}~}}||{yyzywwwvuusxxz{x{‚~}|{}{|}~~}}}}~~€}~€{x……€€‚„………„……„…†……………„……„††……„„…††…ƒ„„‚ƒ‚‚ƒ„……ƒƒƒ„ƒ‚„„„ƒƒ…†…„„………††…„„„…†………„„…………………††„ƒƒ}~ƒƒv}€~~}}}~~~|z|yxyywxxuuvtxxyxx{€}|}|}€}~~}}zw‚††‚‚€ƒ„„ƒ„……„††…………†…†…„……†…„„…††††……‚ŠŒƒƒ…ƒˆˆƒ‚ƒ‡†‚‡Š‹Š†‚„„…………„„„„„„†‡††…„„…†‡‡†„„„„„‚‚}}u{~€~~~~}}~}}{{{zxwvxvussrux{xuy€~~}~}~}~}}}~~~~~}}}ywƒ…„‚‚€„„„ƒƒ………………………†„„…………††…ƒ„……„…………Љ‡‚„Š€ŒŒŒ„…††……………„„………††……†…„„„…„„„„ƒ~~€‚w|~~€€~€~~}~€~|{xxwvwwusrrwwxvvz€€~}|}~|}}}~}~}}|{{}wxƒ……ƒ‚‚„‚€€‚„„„„„„„„„„…†††††…‡†††„„…†„ƒ„„„‡‰„‰ƒ‚‡‰Œ‡‹‰‹‚ŽŽƒ„…ƒƒ„………„…†…††……„„ƒ‚‚ƒƒƒ„„ƒ‚€ƒ„‚}z~~~~~€~~~}~€~€|||yxwwwwuspqyxvtuz€€~~{}€~}}{|}|~}||}wzƒƒ†„‚ƒ„ƒ„…„„„ƒ……………††…††††‡†…„…………„„„„…ІƒŠ†€‡‰…Žˆƒ‹†‹‚‚€‡„…„………………†‡††…„„ƒƒƒƒ„……ƒ‚ƒ„‚€„ƒy|~}~~€}}€~~~~z{|zxxwwvsoszyxvvz‚|z||}€~~€~||~~~w{ƒ…‡ƒƒƒ‚…†‚„„„……†…„………†…„„„„…‡………††…„ƒƒ‚†‰„ƒ‡ˆ„†ˆ†‹†ˆ‚„Š‚€…އ„„„„„„„„…†…„„„„„ƒ‚ƒ„†††„ƒ‚€€€‚‚‚{{~}}}€€~}}€{|~zyzzvsrrsyxxyyz€}||{}~}|}~{|~~}}x{‚…‡„ƒ‚‚ƒ„ƒƒ‚‚ƒ„…†………†††…„„„„„††…„……††††…Šˆ††‡‡‡‚†‰„‚ˆ‡ˆ„„Œˆ†‹Ž‰„„†…„ƒƒ„„„„…„…†…††…ƒ…†‡††„„~…ƒ‚‚ƒ{{~~~}}~~€~~~}zzz{ywvtuttxxwwvz~z|}~|}}{|}}|}‚€‚€y}‚ƒ…„„‚‚ƒ‚‚ƒ€ƒ„„„„„……†††…„„„„†‡‡…†…‡††‡ˆˆ……ƒƒ†…„‡‡„ƒ„†…„‰‰‹Žˆ‚„ˆˆ††……††…„„„…„ƒ„…„„…‡†„ƒ„ƒ€€‚„…„‡}|~~}€~}}~~}~}|zxxxvvustyxwwwz}€||}~~~|}}|{|~‚}w}ƒ‚…‡†…„„ƒƒ‚€‚„„ƒƒ„„†‡††††………†††…………………†……††……„„„…„††„ƒƒƒ‚ƒ……„„†‡†‡‡‡‡††…„„ƒƒ„……………†……„ƒ€€ƒ…ƒ„{|~}€€}~}~~~~}zzyzzxvvtsxxxwwy|~~~~~~}~}|~}}||}~~~w|ƒ„†‡……††……ƒ„ƒƒ…„„„…††…††…„„„……††††‡‡†……†‡ˆˆ‡‡‡‡††††ˆ‡††…„…†…†‡ˆ‡‡ˆ‡‡‡‡††‡†…ƒƒ„…†‡‡†…………ƒ€‚ƒƒƒ„‚€{|€~~~€€~~}~€{y{yyxwvvvvwxxuuv~€€~}}~||}|}}€~~€€}w}„†……ƒ„†„„ƒ„„‚„…„„„……„…………„„…†††‡ˆˆ‡‡‡†‡‡ˆˆ‡ˆˆ‡‡†ˆˆŠ‰‡†††‡‡†‡‡ˆˆ‡†††††……†…ƒƒƒƒ……†‡††……„‚€€…ƒ†y}€~~}€€~~~€~|zxxxxxwvxxvuussz~}}~~~~~|}~€€}}}|u}„„„„ƒƒ„†…„ƒƒƒ‚ƒ„……„„……„††††…„…†…†‡‡†„„„ƒƒ‚ƒ„„„……†††‡‡‡††‡………„…†‡†……††††…„„„ƒ„…‡†††……††…‚ƒ…ƒƒ……„€ƒƒxz~~}}|~~€{~}zyxxxvssuttwus{}~~}~~}|~~|}}|~€|w„…„…ƒ„„„ƒƒ…„ƒƒ‚‚ƒ„…††††‡†‡‡†‡‡†……†„ƒƒ„„ƒƒ‚ƒ‚ƒ…†…„…‡…„ƒƒƒ‚‚‚‚„……†…„„…†„……†††††††††…ƒ€ƒ†„„†…‚ƒ…zx}}€€}~~}~~~}€‚}{|{zywvurovutvwxy€~}|~~~€~}~}~|~}w……ƒ‚„„……ƒƒ„…ƒ‚‚ƒ‚ƒ„……†††‡‡††††…„„………ŠˆŠŠ‰‰‰ŠŠ†‚„†…„„†…ƒ‰‹Šˆ†ˆŠ†‚ƒ„„„ƒƒ‚…‡††††…†…††…ƒ€…„ƒ‚„…ƒ‚€…ƒzz|~~~~€}}~~€ƒ}x{|zyywttpvuvrswz~~}||||~€€}~~~~~w~„ƒƒƒ„„†…„†…„ƒ‚ƒ…†…ƒ„………††††††††††………‡…„‡‡‰‰‡†‡‹„‚„ƒ…„ƒ‰Œˆ†…††…†‰‹…‚„„„…„„††‡†…„……………„‚ƒ„„„„………‚‡‚z{}}€~}}}~}}€~‚}{z}|zyyvurnqtvttww|}~~~|}~}~~}~~€~x„„„……ƒ„…„…„„‚‚…‡†…„ƒ„„……„…†‡‡††††…††ˆ„ƒƒ„„…ˆŒ……ˆ‚ƒƒƒ‚ˆŠ†‡‰‰Š‹Œ‰…‡Œ†ƒƒ„††††…†…„…††„„„‚ƒ€‚ƒ„†…„…€y{€}€~|~}}~~€‚€}{zxvxuquuwvvvv||~~~~~~~€€~~~}w……„…‡…‚„††…„ƒƒ„…†…†„‚„………………†……††††‡ˆ…ƒ€‚ƒ‚†ˆ„‡ˆƒ…ƒ…†‰Šƒƒ„ƒ†‰„‡‹‡†……„…‡†…„…†††„…ƒ€‚ƒƒ‚ƒ…†„€„†€y{~~~~€~|€€€~||zyywtpwyyxwxz~~~€~~€€€€}}~~~}v€„ƒƒƒ……ƒƒ„…††††„‚‚ƒ…„ƒ„„…†……†…„††††‡‡„†€ƒ…†…„‡Š†Š„„…‡ˆ‰‚ƒ…†……ƒƒ…„ˆ‰††„ƒ…‡†„„…††……„‚‚ƒ„ƒƒ„„ƒ‚……y|}~~~~~€€}}~€~}}~~{zxzzvqovxyxxwuz~}~~~~}~~~~~z€‚‚ƒ„„„„ƒ„…††…„ƒ‚ƒƒ„„„„„………‡‡……†………ˆŠ†ˆƒ†‡††…„‰…‡ƒˆ†ƒŠ„ƒ†……„„…ƒ„ƒ…ˆ………………†„„…………„‚€‚‚„„ƒ‚ˆƒy{|}}}~}}~~}~}}}{|zyyxvryyxyzywz|}~~~~~~~~~}~~€z~ƒ„ƒ„…ƒ‚ƒ„†„„‚‚ƒ„„…†††„ƒ„„…†……††††…‡‹†ˆƒ…†………ƒ‡ƒ††€‰„†Š…………„„„ƒƒ„„…Š„…‡†††‡†…†…„„‚€ƒ„ƒ‚‚„„„„…„†‚€x{~~}~€€~~}}}}{y|yxu{zyyyz{{}~}~~~~€}}}~€€z€‚‚ƒƒ„†„„„„……†………†……„„††„‚„…†††††„‡‹†ˆ‚……„„„ƒ…€…ˆ‚‡„ˆ‡…‡‡……†…ƒƒƒ†‹„………‡†‡†‡…ƒƒ‚€ƒ……„„…†…‡…ƒ††ƒ~v}~|}}~}~~~€}{}}~}|zx|{xuyyz|zxxy}~}~}}~}~}{~~~~}{€‚‚‚ƒƒ††„„„„…†‡†………„ƒ………ƒ„„……††††„‡‡†‡‚„„„ƒƒƒ‡‚†ˆƒ‡„‡…‚‡†……†…„„€„ˆ‰…††‡‡‡‡††…„ƒƒ„„‚ƒ‚„……„„„‚‡†„{~~~~|~~~}}~~€|}}}}|z|zywwwwyzxxz~}}}~~~~~}€z|€ƒ„„ƒ†„‚‚ƒ…††„„†……„††ƒ„†…ƒ„„„……††…‰ˆ…ˆ‚„„„„ƒƒ†‡…†‡†ˆ‚…††………†‚ƒ†‹ˆ…††‡‡‡†…„„„‚‚„„‚„ƒ„…„…ƒ€…„„y€€€€~}}~~}‚|}}~}~€~{}zuuwyxxx{}|}}}|}~}|€~~~~~~~{ƒƒ…„„„……„„ƒƒ„„„…†‡†††…††‡†……ƒƒ„ƒ„…†…‹‰†ˆ‚„…„„„‡…„ˆ‚…‰†Šƒ‚…………„‚††Ž…„†††…†……„ƒƒ‚‚„…„ƒƒ„„„…‡…‚€„††ƒz€€~~~~}}~}~€‚~}}}}}}}~}}{xvxyyzyx{|~|€€~~~}€~|~}{ƒ„ƒƒƒƒ„…†…„ƒ„„„„„…………†……‡‡‡‡‡…ƒ„„………ƒ‹††‚€ƒƒƒƒ‡‡ˆ‰ƒƒ„‰††‡‚ƒ„„„€€…‡‹Šƒ…†……………„‚ƒ‚‚ƒ………ƒ„…„„…†„ƒ‚‡‡†ƒ{}€~}~~~||~€€~~~|zy|~}{yxxz{zz|}|}}~~~~}~€€}|}z}…ƒ‚‚‚ƒƒ„…†„‚ƒƒƒƒ…………†‡†…‡‡‡‡……†……………ƒŠ„ƒƒ„‚ƒ†‡„…Š……†…ˆŠ‚‡‡„„ƒƒƒ††‹ƒ„†‡‡‡†…„ƒ‚ƒƒƒ„††……„††††…ƒ…ˆ†…ƒ€~~}€€‚€}|~€€€~~}{yyz{xy}€}|{zxyyy}~|~€~~}}}||}}~z~…ƒ‚‚‚ƒƒ„††…ƒ‚„…………††…†††„ƒ‚…‡‡…………ƒ‰…„…‡†…†††‹‹„…†††…ˆˆ„…‡†………†Š‡„†‡‡‡†‡„‚‚ƒ…„„……††……………„ƒ†„„‚€}~~€€€~||}€€~||{zy{}zz{~yzzxvyxx{}|~|}~}~}}}}zz…„ƒƒ‚‚‚ƒ…††…ƒ‚ƒƒ……„„„……†…„……†‡†„„††„Љˆ‡‡‡‡‡ˆŠˆ…„†‡‡†…ƒ‡Š‡„ƒƒ„…Žˆ†‡‡‡††……„‚ƒ‚„„ƒƒ‚ƒ†…††……†ƒ‚…„„‚€‚€€~}€}~~~}~}~~~}||||}|{~}|{yx}}yz||}}~~~~|}{{ƒ‡‡ƒ‚‚ƒ„„††„ƒƒƒƒ„……„ƒ„………†ˆ‰‡ˆ‡…„†‡††‡‡†‡ˆˆ‡‡†……‡‡‡‡‡††………ˆŠŠ‹Œ‹Š‡†‡‡†††…„ƒƒ‚„„‚ƒ„……„………†‡†…„†…„„„‚ƒ}}|~~~~‚„‚€~||~{z€{||{{yz|}zy~}~€~~€~~€y|„…†ƒƒƒƒ„„†……„‚‚ƒƒ‚‚‚„…†‰‰‰‰‡‡††‡†††††‡‡…ƒ„„…†‡†………††††…„„………ƒ„†††††††„ƒƒ‚€‚…†††„„…………„€„ˆ…„ƒ‚„„}~~€‚}}€‚ƒ€€~|}||||{{{~|}{{zyyzzzz~}~~}|~|~~||}y~‚„…‚‚„ƒƒ„„„…††…‚‚‚‚…‡‚„………‡‡ˆˆ‡ˆˆˆ‡††‡‰Šˆ‡‡††‡‡†…„…††……†††…††††‡†††…„„ƒ‚~}€ƒ…ƒƒ……„………‚†‡†„ƒ‚„ƒ~‚~~~}~€€~~€~||}}zy{||{{|zyzxxz{yz|~~}}~}~~}~~}}€|z€€…†‚‚ƒ„„„„…††………ƒƒ‚‚ƒ‰††…„„„ƒ‚ƒ‡‡ˆˆˆ‡‡‡ˆˆ‡‡†††………†…††‡†††‡†††ˆ‡††††„‚‚‚~~‚‚ƒ…„„„ƒ…†…„„„……†……ƒ…„ƒ€}~~~}~~}~€~}}~||}|{z||z|}}|}~{}}~{}~}€}~€~z|„ƒ„†‚‚ƒ„……„†‡‡†‡…ƒ‚„ƒ„ˆ€€‚ƒ„‚„‰††„‚…†‡†††‡‡††…„……………†††‡‡‡††‡††…„ƒ„……„‚‚ƒ|…………„Œ‰……‡‡…„„„„‚†††…„„„‚€}€~}~~~~~~€~~~}|z|}}}z||~~~}{|}}€~~~}~‚}}x}‚„„†‚‚ƒƒ„„ƒ„‡‡†‡„‚‚ƒ†‡~}‚ƒ„‹‡†ˆ…‚…†‡‡†…ƒƒ‚‚††…†‡††‡‡††‡‡…ƒ€~€‚‚„„€ƒˆ‰ƒ~†ˆˆ‡‰‹Ž…ƒ…††…„„ƒ…‡…„…„‚ƒ‚€|€~}}~~~}}~~~~}}}}~~~|{}}}~€}}}€~~~~~}}{€…ƒ‚„‚„„„„„…†…†‡…‚ƒ‚ƒˆ…~~|ƒƒŠˆƒ…„„…†††‡„‚‡‡‡„„ƒ„††††††…ƒ€‚„„ˆ‡‡‰‹‰„‚€…††‰……††‰ˆ„„ƒ……„„…‚~‚††…ƒ‚‚‚……ƒƒ{€}~€‚€}|€~~€||}}~}}€}~|}|~~}~}~}{z„ƒ††‚‚ƒ„ƒƒƒ„†††††………„…ƒ‚‚~‡Šƒƒ†‚„…††††ƒŠ††ƒ‚…†‡‡††‡„€…†‘‘ŒŠ„Š‹€…{‰†…†…„„ƒ„„…††…„„‚‚„††…„ƒ‚…„‚€€}~€~}~}}}~~~}}}}||}|}}|||~~~}~}||~~}€}|~xz‚ƒ„††ƒƒƒƒ„„„„†‡†††‡†„„†ƒ‡‚€„‰„‚…ƒ………„„ŠŠƒ‚„†‚‡‡‡†‡†‚ˆ‹‹ˆ†……Љ€‚†|†‰‡‡††…„ƒƒ„†…„……„‚ƒ…††…„„‚ƒ„‚€€‚~|}}~~€€€€||~{|}|{|}{€~~~{}~}|~}€~€~}~|‚ƒƒ„…ƒ‚ƒƒ„„ƒƒ„…†††‡…„ƒ……}†„}ƒ†„…€ƒƒ…†…‚ˆ‰‡ŒŠ„ˆ…†††‡„‚ŠŒŒ„‚„„„„„ŠŠ‚‚†‡€‰†…†……„ƒƒ„………††…ƒƒ…‡…„ƒ„„ƒ††„‚ƒ~€~~€€€€~~}{}~~~~€}~|~~}€€}}~~~}||}‚„„„„†ƒ‚ƒƒ„ƒƒ„„…†††‡††‡‡††‚ƒ„…„ƒƒ„†ƒ†‹…І‹†††„††‡†ƒ‹‰‰ƒ„„…†…„ƒƒƒƒˆ‡€Š‡†…ƒƒƒƒƒ„„……………ƒ…‡†„ƒƒ„ƒ„…„„„ƒ‚~~€~€€~{~~€€~~||€€~~~€‚€€~~|}~~~{|z}„„ƒƒ…ƒ‚ƒƒ„„…„…†„„„„…†‡‡ˆ€|‡…€„„‰ƒ€„„„„ƒ‰…ˆ…€ˆˆ‚‰ƒ„††„†‡†Š‚„……†„ƒ„‚€‚‚„‰„|‰‰……‚ƒ…„ƒ†‡†……„ƒ‚„††„„……‚€„…ƒ‚ƒƒ‚}‚€~~}€|€€€~}~~~~~€‚€}}ƒ~€~€~}~}zz€„…„„††„„ƒ„„„ƒ„…„„ƒƒ„‡‡ˆ‹„~…‡„…ˆ‹ƒ„„ƒ‚ƒˆ†ƒŠ…ˆˆ…ƒ„‚‡…„ˆ‚„…„ƒƒ€€ƒ‚ƒ‡…~ƒˆ‡‚„‚ƒ…‚‚ƒƒ„„…‚„‡†„„„„‚€„„‚ƒƒ„ƒ~‚ƒ~‚€€€€€€€||}}|z~‚€~~}}}~~~~~}}€‚ƒƒƒ„…ƒƒ„„„„ƒƒ…††„„…††‡‰„…‡‡†ƒ…‡‚ƒ„†„…‡ƒƒƒ€‚†„…†‚ƒ†ƒ…†ƒƒ„„ƒ…ˆ…„‚ƒˆ‡Š€„…ˆ€€ƒ„‚|‰Œ…„…„€ƒ„…†„…†„€„ƒ„…ƒƒ‚‚~~€€€€~~~~}|{||}‚ƒ€€€€€„…€}~~}}~|}‚ƒƒƒ„„…ƒ‚ƒ„„…†…††……†……„„ˆ‡‚ƒ‡Œ‰‚…ˆ‚„†„†…€€|~€€…‡‚ƒ‚‰„ƒ†‚ƒ††ƒ…‰†ƒ€ˆ€€†‰‚†ˆ†‰€ƒ’Œ„„ƒ‚„………„†‡„€„†„„„ƒ‚€~~‚€}}|{|}~||{{~€ƒ‚€ƒ‚†|~~~}|~€ƒƒƒ„„„†„‚„„„………………†††…ƒƒŠ‹ƒ„‡‰ˆ†‡‰‚€ƒ„ƒ…„€€‚ƒ„‚…„ˆ‡€…ƒ††„†‹††„Š€‡Š„‚‰ˆ††“ƒ……‚„…††……‡†ƒƒ†‡††…„„ƒ€~‚ƒ~}|||~€€~~~~~‚‚€‚‚…~~~€~|€‚€„ƒƒ„„ƒƒ……ƒ‚‚ƒ…………†††……„ƒ‡Š„…‡………‡‰„ƒ…†ˆ†‚ˆ‡‡ˆ‡†……ƒ„‚„…†‰‚ƒƒ…††‡‡„‡‡Š…‚ˆ‰‚‚ƒ‹…‚€‚”…ƒ…ƒ‚ƒ†††………†„‚ƒ††„„„‚ƒ„ƒ€€€~€~~}~€€€}|}~}~€ƒ‚€€~…ƒ}}|}}‚€„„ƒƒƒ„ƒ„…‚‚‚ƒ„„………††„„„„…„ƒ…………ƒ†‰„‚ƒ„†ƒ†ˆ‡Š‹Š‰ŠŠˆƒ‚‚††‰‹ƒƒƒ‚€€€ƒƒ†‰…„ˆˆƒ…ƒŒ‹ˆ‰Ž†…‡…‚„†……………††‚€‚‡†„…‡…‚‚ƒ‚€€‚€~~~~€~}}~~{|~‚€€€€„ƒ{|~‚€‚ƒƒ„‚ƒ„„ƒ†‡ƒ‚ƒƒƒƒ…††††…„……††††…†‡…†Œ‰‡…‰ŠƒŠ†††‡‡†……‡†„‡‡Œ‹ƒƒƒ}~€ƒ‰„„†‹…„ƒ‚„ˆŠ‹‰„„†…‚„……‡†…………‚ƒ„…„………ƒ‚‚…ƒ€~~|~€€}~~{{~~€€~}€€ƒ‚€€‚€…}{}ƒ……ƒ‚„ƒ„„…‡„‚‚ƒ„„……†‡…„………†‡††…………†ˆ‰ˆ†ŠŽŽŒˆ††‡‡‡‡‡†Š‹ŠŠ„†‡‡‰Œ…ƒ€‚ƒ‹€„ƒƒ……ƒ‚„††…†…‚ƒ…‡‡††……‚……†††…„„ƒ‚‚€ƒ…ƒƒ‚~€ƒ€€€€€}}€ƒ~}|~‚‚€€‚~}~}€…„‚‚‚ƒ……‡‡„‚‚ƒ„……„„…‡††……„„†„„…„„…†††……„…†‡‡‡‡‡†…†††‡ŠŒ‡†††…†Œ‹††‡‰‹ˆƒ‚ƒ†‹‡ƒ„„…„…‡ˆ†…‡„‚…„…………†††‚††…„„„ƒƒƒƒ„ƒ€‚‚‚~€€~‚ƒ~}}}~‚€€ƒ€~‚„ƒ‚……‚‚„„…‡†„‚ƒ„ƒ„ƒƒ„…†‡‡……†‡…„…„„…†††††††………†…„„†‡‡‡††††††††‡††ˆ‹‹Œ‹‰„‚…†…ƒ‚ƒ„………†……††…€……†……„„…†„‚ƒ†‡†…„ƒƒƒƒ„†ƒ‚‚‚‚€~||}~~}~|y„ƒ‚€€~ƒ‚‚~|€„…ƒƒƒƒ„…„„‡‡†ƒ‚ƒƒ‚‚‚ƒ…††……………„„„„…††……†‡†…„„ƒ„„…†‡††‡‡‡ˆˆ‡‡†††††‡†…ƒƒƒƒ„…„„ƒ……„„…†‡………„€„…†‡†„‚ƒ…†ƒ‚„††……„„ƒ‚‚ƒ„ƒƒ‚‚ƒ|{|‚~zz}{|}{€€ƒ‚€€€|~€„€~~…„ƒƒ„„„ƒƒˆˆ†ƒ€ƒ…ƒ‚‚„…†…„ƒ„……„„…†…„„„………„„ƒ„†‡‡‡‡††ˆ‡†††……„ƒƒ„„…†…ƒƒ„„…†…„ƒ…††…†‡†††…‚€ƒ„…†…„………„ƒ‚…‡†„„„„ƒ‚‚ƒ…„‚„€{{|~€€~||€|{z|€„€}€‚‚€€€„€€„†„„‚‚ƒ„„ƒƒ†‡ˆ‡„ƒ‚ƒ……ƒ„„…………„„„„ƒƒ„…„……†††„„……„ƒ…‡‡††††‡†„„„…„ƒ‚ƒƒƒ‚‚‚„………„„„„†‡†††††††…ƒƒ„ƒ…‡†…†‡‡…„ƒ‚…†„‚ƒƒƒ„„…„…†„ƒ€ƒ„€€}{~~}|~~}}~{|€€‚€‚€~€€€…ƒ€‚…‡…ƒ„„ƒ‚…†‡††…‚„…„„„…………††††„ƒ„„„„„„„……„„…………††††††‡†……„„…………„ƒƒƒ…††……„„†‡‡††††††‡†€„„„†‡†…†††…„€……ƒ„…ƒ„††…………„ƒ…€~€}~~{z~}{{~~~ƒ€~€€€~~~†ƒ€ƒ„„‚‚ƒ„…„ƒ‚ƒ…‡†‡†‚ƒ„ƒ‚ƒƒ„„…‡‡‡‡‡†‡†…………†…„„„…‡†‡‡‡‡‡†………†„‚„„‚‚ƒƒƒƒ…‡†……†……‡††…‡‡‡††„‚‚ƒ„…‡‡…„††…‡†ƒƒ‰‡„„…„ƒ„…„„†‡…ƒ‚€„€€€~~~~|~‚€€|ŒŒŒ‹‹‰‰‰ˆ‰‰‡‡‡ˆ‰ˆ‡ˆˆˆ‡‡ˆ‰‰Š‰‡‡ˆ‡ˆ‰ŠŠ‰‰‰‰ˆ‡ˆˆˆˆ‡‡‡‡‡ˆˆˆ‡‡‡‡†††††††††††…†…„ƒƒ‡‰†††ˆ‰‰ˆ‡‡†‚‚„††……………†………†††‡‡……‡‡‡†‡‡‡‡‡‡‡ˆˆˆˆ‡ˆˆ‡‡ˆ‰Š‹Šˆˆˆˆ‹Šˆˆˆ‡Š‰‡‡ˆ‰‹ˆ‡‰Š‰‹‰Ž’ŒŒŒŒŒŠ‰‰ˆ‰Š‰ˆˆ‡‡‡‡‡‰‰ˆ‡‰‰‰ˆˆ‰‰‰‰ˆ‰ŠˆˆŠŠ‰‰‰‰‡‡‡ˆ‡‡‡‡‡‡‡‡‡‡‡††………†……†††…………ƒ‡Š‰‰ŠŠ‰‰‰Š‰‡Š…€„………„„„…†††…††‡‡†…††††††‡‡‡‡‡‡‡‡‡‡‡‡‡‰‰ˆ‰‰ˆˆ‡‡ˆˆ‰ˆ‡ˆ‡ˆ‰‰‡ˆ‰ŠŠ‡‡ˆˆ‰‹ˆ‰“ŽŽŒŒ‹‹‹‹ŠˆˆŠ‹ŠŠŠˆˆˆ‡ˆ‡‡‡‰‰Šˆ‡‰ŠŠ‰‰‰‰ˆˆ‰‰‰ˆˆˆˆ‡‡ˆ‡‡‡‡‡‡‡‡‡‡‡‡…………†††………††…„‚…†………„„„„„„‚„„€ƒƒ„„„„„……†††††……†††††‡‡‡‡‡‡‡‡‡‡‡‡ˆ‡‡ˆ‰ˆ‰‰‰‰ˆ‡ˆ‰‰ˆ‡ˆ‡‰‰‡†ˆŠ‹Š‡‡‡‰Š‰ˆ‰Œ‹Œ‹ŠŠŠ‰ˆ‡ˆŠ‹Šˆ‡ˆ‹Šˆ‡‡‡ˆˆˆˆˆˆ‰‰‰‰Š‰ˆ‰‰‰ˆ‡‡ˆ‡‡‡‡‡‡‡‡‡‡‡‡‡††††………††††…††…„„ƒ„†††…„„„…„†…ƒƒƒ‡‚‚„…„…††……………„…†‡††‡†††‡‡†††‡‡‡‡‡†‡‡ˆ‰ŠŠŠ‰ˆ‡ˆ‰ˆˆ‰‰ˆ‰ˆ‡ˆ‰ŠŠ‰ˆˆ‡ˆŠ‰ˆˆ‹ŠŠŠ‹‹ŠŠ‰ˆ‰‰ˆˆ‰ˆ‡‡‡‰‰‡†‡‡ˆˆ‡‡ˆˆˆ‰‰ˆ‰‰ˆˆ‡‡‡‡ˆ‡‡†‡‡‡‡‡†††‡††††††††……†††…†…„„……„………„„„„„†ˆ‡†ƒ„‹‡‚‚‚‚„…†††††………††…††…†‡‡‡‡‡‡†‡‡†‡‡ˆˆ‰Šˆ‡‡ˆˆˆˆˆˆ‰‰Šˆ‰ˆ‡ˆ‰‰Šˆ‡ˆ‡ˆŠ‰ˆ‰‹ˆ‰‰‰Š‹ŠŠ‰‰‰ˆ‡‡‡‡‡‡ˆ‰ˆ‡‡‡ˆ‡†‡ˆˆˆ‰Š‰ˆˆˆˆ‡‡ˆ‰ˆˆ‡†‡‡‡‡‡†…†††…††…††‡†„„„…………„„ƒ„…„……„ƒ‚…†††„‚„†‡Š‰‚„………†††…†††‡……†††‡‡‡‡††‡‡‡‡ˆˆ‡‰ŠŠˆ‡ˆˆˆˆ‡‡‡ˆ‰Š‰ˆˆˆ‡ˆ‹‰ˆˆˆˆ‰ŠŠ‰‹‹‹‹Š‰‹Œ‰‰Š‰Š‰‡‡‡‡ˆˆ‡‡ˆˆ‡‡‡‡‡‡ˆˆˆ‰‰‡‡‡ˆˆ‡‡ˆˆˆˆˆˆ‡†††‡‡‡‡‡†‡††……………„„„………„„„‚‚………„ƒ€}~€‚ƒƒ…ƒ‚„ƒƒ†„ƒ„„„„………„„……………†††‡†††††††††‡ˆ‡ˆˆ‡‡‡‡ˆˆ‡‡‡‡‡ˆˆ‰‡†ˆ‰Šˆ‡‡‡ˆŠ‰ˆŠŠŠŒ‹‹ŠŠŠˆˆ‰‰‰‰ˆ‰ˆ‡‡ˆ‰‡‡‡‡‡ˆ‡‡‡ˆˆˆˆˆ‡‡‡‡‡ˆˆˆ‡‡‡ˆˆ‡††‡‡††††…†………„„„„„„„„„„„„„‚†…„„~~‚ƒƒ„†„ƒƒƒƒ‚‚……†………„„„„„……………………††††‡†…††‡ˆˆˆ‡‡†ˆˆ‡ˆ†‡‡‡‡ˆ‡‡‡†ˆ‰Š‹ˆ‡†‡‡Š‰‰‰ŠŠŒ‹Š‰‰‰ˆ‰Š‰‰‰ˆˆˆˆ‡‡‰ˆ††††‡ˆˆ‡‡‡‡ˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡†††………„…………„„„„„„…„„„ƒƒƒƒ„‚€†„ƒ„„€€ƒ‚‚ƒ„ƒ„ˆˆ„ƒƒ„„………………„„„„††„„…………„…†‡‡††††‡‡‡‡‡††‡ˆ‡‡ˆ‡‡†‡ˆ†‡ˆˆ‰Š‰ˆ‡‡‡ˆˆˆ‰‰‰ŠŽŒ‹Š‰‰‰‰‰‰‰ˆ‡‡‡‡‡††‡…………†‡‡‡‡‡‡ˆ‰‡†‡†‡‡‡‡‡‡‡‡‡†††††………………„„„„„„„ƒƒƒ„„„„„„‚‚ƒƒƒ††‚……ƒ‚‚€ƒ…‡…ƒƒƒ„„ƒƒ„„„ƒƒ„„……„„…………………†††…†‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡ˆŠŠ‡†‡ˆˆˆ‡‡‡ˆ‰‰Š‰‹ŠŠ‰ˆˆˆˆ‡ˆ‰‡††‡‡†…†……†‡‡‡‡‡‡‡‡‡††‡‡‡‡‡‡‡†‡‡‡‡‡‡‡†…„„„ƒ„……„„„„ƒƒƒ„„„ƒƒ„ƒ‚ƒƒƒ„†„†…‚‚ƒƒ€€ƒƒƒƒ‚„„„„„ƒ„ƒƒƒ„„„„„„„…………„„„………†‡‡‡‡‡‡‡†‡‡‡‡‡†ˆˆˆ‡‡‡‡‡‡Šˆ††ˆˆˆˆˆˆˆˆŠŠŒŠ‹‹ŠŠŠˆ‰Šˆˆˆˆ‡‡‡ˆ‡‡‡‡ˆ‡†‡††‡‡‡‡‡ˆ‡‡‡‡‡‡‡‡‡††‡††…„………„ƒƒƒƒ„…„ƒƒƒ„„„„„ƒ‚ƒƒƒƒƒƒƒƒ…ƒ‚‚‚„‚€‚‚ƒƒƒ„ƒƒƒƒƒƒƒƒ„„ƒƒ„„„„……„„…………††‡†‡‡‡‡†††‡‡‡‡‡†‡‡‡‡‡ˆ‰ˆˆ‰‡††‡ˆ‰‰ˆˆˆ‰‰Š‹Œ‹Š‰‰‰Š‰ˆ‡‡‡‡ˆ‰ˆ†‡‡‡†‡‡‡‡‡‡‡‡‡‡ˆ††‡††††††‡‡†………………„„ƒƒƒƒ„„„ƒƒƒ„„ƒƒƒƒƒ‚‚‚„‚‚‚‚‚ƒƒ‚‚‚…ƒ€‚‚‚ƒ„ƒ‚ƒƒƒƒƒ„„ƒƒƒƒ„„„„„„„„………†‡‡‡‡‡‡†…†‡††‡‡‡‡‡‡‡‡‡…‡‡ˆ‹Š‡…†‡‡‰‰‰‰ˆˆˆˆ‹Œ‹Šˆˆ‡ˆ‡††‡‡‡‡ˆ‡††‡†…†‡‡‡‡‡‡‡‡‡‡†††‡‡‡†††††††††……†…„„ƒ„„„„ƒ‚ƒƒ‚‚‚ƒƒƒƒƒƒƒ‚‚‚„ƒƒ„ƒ„ƒ‚ƒƒƒƒ„„„ƒƒƒƒƒ„„„ƒƒƒ„………††††††‡†……†‡†‡‡††‡‡‡‡‡†††‰‰‡‡‡‡‡‡‡‡‡ˆ‡ˆ‰‰ŠŠ‹Šˆˆ‡‡‡ˆˆˆˆ‡‡‡††…‡‡†‡‡‡‡‡‡††‡‡‡‡††‡‡‡†††††††…††……„ƒƒ„„…„ƒ‚‚ƒƒ„ƒ‚ƒƒƒƒƒ‚€ƒ„…„‚‚‚‚‚€‚ƒƒ‚‚ƒ„ƒ‚„ƒƒ‚‚ƒƒƒ„„„ƒƒƒƒƒ„…††††…†‡‡‡‡‡‡‡‡†††‡‡‡‡‡†††ˆˆ……†‡‡ˆ‰ˆ‡‡ˆ‰Š‰‹ŠŠŒ‹ŠŠŠ‰‰‰ˆˆ‡ˆˆ‡‡‡‡‡‡‡‡†††……†‡‡‡‡†‡‡‡‡‡‡†††………††††…„ƒƒ„ƒ„ƒƒ‚ƒƒ‚‚‚‚‚‚ƒƒ‚‚ƒ…„‚‚‚€‚‚‚‚ƒ‚‚‚‚‚ƒƒƒƒƒ‚ƒ„ƒƒ„„„„„„„„„…†‡†††††††††………………††‡‡‡†‡ˆ‡††…†‡‡ˆˆˆ‡ˆ‰ŠŠˆ‰Š‹Š‹‹ŠŠ‰‰Šˆˆˆ‰ˆ‡‡‡‡†…†…†‡†…†‡‡‡‡‡†‡‡‡‡†††……………††…„„„„„„ƒ‚‚‚‚‚‚‚‚ƒƒ‚‚‚‚~€€~~~~~€‚‚‚‚‚‚‚‚‚ƒƒ„„„„„„„ƒ„„„………††††‡……††…„„…†††……†‡‡‡††ˆ†…„…‡‡ˆˆ‰ˆ‡ˆ‰Š‹ˆŠŠŠŠ‹‰‰‰ŠŠŠŠ‰‰ˆˆ‡‡‡††‡‡††‡‡†…†‡‡‡‡‡‡†‡††…„„„„……†…„„„„…„ƒƒƒ‚‚‚‚‚‚‚€‚…††…‚~~€‚ƒ‚ƒ…„€‚‚‚‚ƒ„ƒ„„„„„……„…………„„……„„…††„„…††††††‡ˆ‡††ˆ†„„†‡‡‡ˆˆ‡‡ˆ‰‰‰ˆ‹Š‰Š‹ŠŠ‰Š‹Š‹Š‰‰‰ˆ‡‡†…†‡†‰ˆ‡†…†‡‡‡‡‡††††……„„„„„„ƒ„„„„„„ƒƒƒ‚‚‚‚‚‚‚‚‚€€„…†ˆˆ‡†ƒ„„…†…ƒ€€‚‚‚‚‚ƒ‚‚ƒƒƒ„„„„…………„„„…†††………†††††††‡‡‡‡††‡‡……††‡‡‡††‡‰‰‰‰ˆŒŒ‹Š‰‰Š‰‰Š‹‹Šˆˆˆˆ†††……†…†‡‡††‡†††………„„„„„„„„„„„„„ƒƒƒƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€‚„‡‡„€~}}~‚‚‚‚‚‚‚‚‚ƒƒ‚‚ƒ„ƒƒƒƒƒƒ„„„„„„………„…………†††…‡‡‡‡ˆ‡†††…†‡‡‡‡†‡‡ˆˆ‡ˆ‰‡‹‰ˆ‡ˆ‰‰Š‰ˆŠ‹‹Š‰‰ˆ‡†………†‡‡‡‡†††……„ƒ„„„„„„ƒ„„„„„„„…„ƒ‚‚ƒƒƒ‚‚‚‚€€€€€€€€€€€€€€‚ƒ†‰ˆ…ƒ„…‚€€‚‚‚ƒƒƒƒ„„ƒƒƒ„„„„„„„ƒ„†…†…„…††‡…†‡‡‡‡ˆˆ‡……†††‡ˆ‡‡‡ˆˆˆˆ‰ˆ†‰‡‡ˆˆˆ‰‰‰‰‰‰ˆ‰ˆ‡‡‡†„ƒ‚…††‡‡…„„„……ƒƒƒƒƒ„„„……„„ƒƒ„„ƒ‚‚‚ƒ‚‚‚‚‚‚€‚‚€€€€€€€‚„†‡‚€€€€€‚‚‚ƒ‚‚ƒƒƒƒ„„„„„„„…††………†††††††ˆŠˆ†…†‡‡‡‡‡‡‡ˆˆˆˆ‰Šˆ‡‡†‡‰‰‰ŠŠŠ‰‰ŠŠŠ‰‡††……„‚‚…‡ˆ‡…„„„„…„„ƒƒ‚ƒƒƒ„„„ƒƒ„…„ƒ‚‚‚‚‚‚‚€€‚‚€€€€€€€€€€€€€€€€€€‚‚‚‚ƒƒƒƒ„„„……„ƒ„…………††††‡‡†‡ˆˆ†„…†‡‡‡‡‡‡‡‡ˆˆˆˆˆ‡‡ˆˆ‡‰ŒŠ‰Š‹ŠŠŠŠ‰‡‡ˆ‡†…„‚ƒ†‡‡†…………„„ƒƒ„ƒ‚‚ƒ‚ƒƒ„ƒƒƒƒ„ƒ‚‚‚‚‚‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒƒƒ‚‚ƒƒƒƒƒ„„ƒƒ„„…„„„„…††‡‡…‡ˆ†„„‡‡††‡‡‡ˆˆ‡‡‡ˆ‡†‡ˆˆˆˆˆ‹‹Š‹Œ‹‰‰‰ˆ‡‡‡ˆ†„ƒ‚‚†ˆ‡†„…………ƒƒ„„ƒƒƒƒ‚‚‚ƒƒ‚‚‚ƒ‚‚‚ƒ‚‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~€€€€‚‚‚€‚ƒƒƒƒ„„ƒƒ„…………„„„†……††‡‡…ƒ†‡‡††‡‡‡‡‡‡‡ˆˆ‡‡‡ˆˆ‰‰‰‰Š‹‹Œ‹‰‰ˆ‡‡‡‡‡‡†„‚ƒ‡‡†„„„„ƒ‚‚ƒ„„ƒƒƒ‚‚‚‚‚‚ƒƒ‚‚‚ƒƒ‚|}€}}}€€€€€€€€€€€~~~€€€€€€€€‚ƒƒƒƒƒ„„„„…††…„…†††††‡ˆ„…††††††‡‡‡‡‡‡‡‡‡‡‡‡ˆˆˆˆ‰‹‹‹‹ŠŠ‰‰ˆ‡‡†‡‡‡‡†††ˆ‡…„ƒƒƒƒƒ‚ƒ„„ƒƒƒƒ‚‚‚‚‚‚‚‚‚‚‚‚~€ƒ‚€€€~|{|€€|{|{{|}|}}~~||}||{{z{|€||}|{||~€€‚‚ƒƒ„„„…„„„…„„„………††‡‡„……………†††††‡ˆˆˆˆ‡‡‡‡‰ˆˆˆ‰Š‹‰ˆ‰ˆ‡ˆˆ‡‡††††………†‡†……„ƒƒƒƒ‚‚‚‚‚‚‚‚‚‚€€€€€ƒ€~}††}~~}}}|{||}€€€~€€~~€‚}~€}{}€€‚‚‚ƒƒƒƒƒ„ƒƒƒ„„„……………††…………†……†††††††‡‡ˆ‡‡ˆˆŠ‰‹Šˆ‰‹‰ˆ‰Š‡‡‡‡‡‡††‡†ƒ„……†…„„„„ƒƒ„„ƒ‚‚‚‚‚€€€‚}{~€ƒ‡…}€ƒ‚‚€€~‚„„„„„‚~€„€€€‚……‚€|}€€€€‚‚‚‚‚ƒ„„ƒƒ„„„…„………………†††††††…„„††…††‡ˆˆ‡‡‡ˆ‘ŒŠ‰‰‹Š‰Š‰‡‡‡†…†‡††…‚ƒˆ†„„……„„„„ƒƒƒƒƒ€€‚€€}}|~€„…‚€~………‡†‡„€€€}}‚ƒ‚ƒ„ƒ€ƒ€~}‚„‡ˆ‚€{~€€€€€‚‚‚‚ƒƒƒ„„ƒ„„……†††………„………†‡‡†…†††‡‡ˆ‰ˆ‡‡†‡•‘‹‰‰‰‰‰‰‡‡‡‡†„…‡††„€€€„Ї………„ƒƒƒƒƒ„ƒƒƒ€€€€€€€|~|{€…„€€€€€€€€€€€€€~}~€€€|z~€€‡…€}|€€€€‚‚‚‚‚ƒ„„ƒƒƒƒ„…†††††…†‡‡‡‡‡‡‡†…††††‡ˆ‰ˆˆ‡ˆ”’‘ŽŠ‰Š‰‰‰ˆˆ‡‡†„ƒƒ‚€€€€€…Ї…„„„„„„„„„ƒ‚‚‚€€€€€€€€€|}~~~„ƒ€€€€~~~}~~€€€~}|~~~€‚€|y~€€‚„~}€€€‚‚‚‚ƒ„ƒƒ‚ƒƒƒ„…†………†††‡‡‡††ˆ‡‡†††‡ˆ‡‡ˆˆ‡‡‡“ŽŠˆ‹‰ˆ‰‰‡††††„€€€€€…ˆ†††„‚ƒ„„„ƒƒ‚‚‚‚€€€€€€€€€{|~€~~~€~|}~}|{{{||~~||{{zy{|{~‚€|{~€€€~€€€€€€€‚‚ƒƒ„„„ƒƒ‚ƒƒ„…††††‡‡‡ˆˆ†††††‡‡‡‡‰Šˆ‡ˆˆˆˆˆ‘Œ‰‹ˆ‡ˆ‡†…†…„~€…ˆ†††…ƒ‚ƒ‚‚‚ƒ‚‚ƒƒ‚€€€€€€€€€€€~}~ƒ}~~~~~~€€€~}}|{~€~~}}}}~z~‚‚||€€€€€€‚ƒ„ƒ‚ƒ‚ƒ„„„„………†‡‡‡‡‡‡†††……‡‡‡‡ˆ‰ˆ‡‡ˆ‰‰‹‘’’‘ŽŠŠ‰ˆ‰ˆ‡†…‚‚€€€€€ƒˆ‡…„ƒƒƒƒ‚‚‚‚‚ƒƒ‚‚€€€€€€€€~~~ƒ€~}~~~}…†……„ƒ}}€‚‚‚‚‚€€}}||}€‚€€€€€ƒƒ‚„ƒƒƒ„„„………†‡‡ˆˆ‡‡†……††‡‡ˆˆˆ‡‡‡‡ˆ‡Š‘‘’‘ŽŒŒ‰ˆ‡‡†…ƒ€€€€€€ƒˆˆ†……ƒƒ„ƒ‚€€‚‚€€‚€€€€€€€€~}~€‚}~~~~~}€‚‚‚~~ƒ„„„„€€€€‚‚‚‚‚ƒƒƒƒƒ„„…‡†ˆˆˆ‡†„„„…‡‡‰‰ˆˆ‡‡‡‡‡ˆ‹Œ‘‘‰‡…ƒ€€€€€€€‚ƒˆˆ†††„„ƒ‚€€€€€€€€€€€€€€~}}€~~}~~}}~~}|}~~~€‚~‚„„‚€€€€€‚‚‚‚‚‚ƒƒƒƒ…„…†‡‡ˆˆˆ‡†„……†‡‡ˆˆ‡‡‡††ˆ‰ŠŒ’‘‰„€€€€€€€€€€€†ˆ†……„„ƒ‚€‚ƒ‚‚‚‚€‚‚€€€€€€€€}~€€€~~~~||||~~€€~}}~~~~€€€‚~|‚€€€€€€€€€‚‚ƒƒ‚‚‚ƒƒƒƒ…„„…†‡‡‡ˆ‡ˆ†‡‡†…†‡‡ˆ‰‹Š‰ˆ‡††ˆŠŒŒŒŠ‡ƒ‚€€€€€€€€€„ˆ…ƒ„„ƒƒƒƒ‚‚‚‚ƒƒƒ‚€€€€€~~€~€~~}€~~~}{{{{||}~€~~||}~~~~~ƒ{|€€€€€€€€€‚ƒ„„…ƒ‚ƒ„……„„…„„…††‡‡†‡Šˆ†……„…‡‡‡ˆŠŠ‰ˆˆˆ‡ˆŠŒ‹‘‘Šˆ†‚€€€€€€€€€€€€„‰„ƒƒƒ‚ƒ‚‚‚€€‚ƒ‚‚€€~€€‚‚~~€€}~~~~~~}}}}|{z}€€~}{{||{zz}‚}|}€€€€€€€‚ƒ„„„„ƒ‚ƒ„„„…„„……††‡‡‡†ˆ‡…„ƒ„†‡‡‡ˆ‰ˆˆˆ‡‡ˆŠ‹‹‰ˆŽˆ†…„€€~€€€€€€€€€„ˆ…„ƒƒ‚‚‚‚‚‚‚€€€~€€€€€‚€€€€€€~~€€~}z}‚~€€€€€€€‚ƒ…„‚‚‚ƒ„„………†‡‡‡‡‡†‡ˆ†…………†‡‡‡ˆˆ‡‡‡ˆˆŠŒŒŠˆ‡‹‰‡…„………€€€€€€€€€€€€‚‡†„ƒƒƒ‚ƒ‚€€€~}€€€~€€€€€€€‚ƒƒ‚‚‚‚€€€‚‚‚‚~}€ƒ‚€€€€€‚ƒ„„„ƒƒƒƒ„„„………†‡‡‡‡‡‡‡†„„†‡‡‡‡‡‡††††‡‡ˆŠ‰‡‡‡ˆ……†…„„„ƒ€€€€€€€€€€€€‚ˆ…‚‚‚‚‚‚€‚€€€€~~~~~€€€€€€€€€€€€€€€~~}}~€€ƒ‚‚‚ƒ„………ƒƒ„……„„†‡‡‡‡‡†……„…‡‡ˆˆ††……†‡‡‰Š‹ˆ†‡‡ƒ…†††…„ƒ€€€€€€€€€€€€‡‡ƒ‚ƒ€€€€€€~~€€€~€€~~~~~~~~~~~~~~~~~}}}}~~~~~~~}~~€€€‚ƒ€€‚ƒƒ„…„……‡†……††‡‡‡‡…†††‡ˆˆˆ†††…†‡ˆ‰‰‰ˆˆˆ‡ƒ„……ƒ‚‚€€€€€~€€€€€€…ˆ…‚‚‚‚‚€€€~€€€€€€~}|}~€~~}}~~~~}~~}}~~~~}}}~~~~~~~~€€€€€‚‚‚ƒ‚ƒ…„„…‡‡††††††‡†…††††‡ˆˆ‡……††‡ˆ‹Š‰ˆˆ‡†ƒƒ‚‚ƒƒ‚€€€‚€€€€€€€€„ˆ†ƒ‚‚€€€€€€€€€€€€€€€€~|}}||}|}~~}~~~~~~~~}}}~~~~~~}}~~~~}~~€€€€}~‚€€€€€€‚ƒƒƒ…………………†††††ˆ†………„…‡ˆˆ‡††††ˆ‰‹‰ˆ‡†‡ˆ…ƒ‚€‚„ƒ€€€€€€€€€€€ƒ‡…‚‚‚€€€€€€€€€€€€€€€€€€€|{}}~~~}{}~~~~~}~}{{||}}}}}|||||}}}}}}}}~}|{{€€€|{z€€€€€€€€€‚ƒƒƒƒ„……„„……†‡††‡ˆ†„…‡„…‡††‡‡††‡‰‹‹ˆˆˆ‡‡‡ƒƒƒ‚‚‚‚€€€€€€€€€€€€~€†‡‚‚‚€€€€€€€€€€€~|~€€ƒ„‚}}|}~~~~~}|||{|}~}}|}}|{{}}}}}}}}}|z€€‚€|{€€€€€‚ƒ„‚‚„„„„…††‡†‡‡‰…„††††††††………‡ˆ‰‰ˆˆˆˆ‡ˆ„ƒƒ€€€€€€€€€€€€„‡ƒ‚‚€€€€€€~{}‚„ƒ€€€~~~~~}|}}{{}}}}}|}}{{}}|}}}|}€}}{€€||€€€‚ƒƒƒ‚ƒƒ„„„…„…†‡‰†‚ƒ…†††‡‡†……‡‡‡‡†‡‡‡ˆˆˆ‡€€€€€€€€€€€~€€€€€…„„€€€€€€€€€€€€}|~†~~~~}~~~}}}||}~~}|}}}}|{|}}|}}}~~}|}€~~|€€€{}~~~€ƒ‚‚„ƒ„„„„„ƒ„†ˆ‡€ƒƒ‚„…†††……†‡‡ˆˆ†††‡ˆˆˆ‡€€€€€€€€€€€€€€€€€€€€††…ƒ€€€€€€€€€€€€€€€€€||ƒ‚~}}~~~~~~~}}}|}{{}€~}~}}}}}}~||}~~~}}€~}{~€~~{}€€€€€‚ƒ„„„„„„…ˆ‹‚~€‚‚ƒ†‡‡††…†‡‡‡ˆ†‡‡‡‡‡‡‡€€€€€€€€€€€€€€€€€€€„‡„‚€€€€€€€€€||‚€~~}~~~~}}}}||||}}~€~~}|}}}~~}~~{||}~~}}~~}}~~‚|}€€€€€€€€€€€€‚„ƒƒƒ‚‚„‰‡‚ƒ‡†……………‡‡‡‡‡ˆˆ‡‡‡‡ˆ€€‚€€€€€€€€€€€€€€€€€€‚†ƒ€€€€€€€€€~€€~~~€~~~~~~}}}}}}|{|}€~}}~}}}}|}|}|{|~~€~~~~~~ƒ€||€€€€€€€€€€€€€‚‚€‚ƒ…Š‚€€‚……ƒƒ„„†††‡‡‡‡‡‡ˆˆˆˆ‰€€€€€€€€€€€€€€€€€€€€„„ƒ‚€€€€€€€€}€~~~~~~~}}}~}|||€~~}}}|}|||}{||~{{{}}~}€€~~~}{}z|€€€€€€€€€€€€ƒ‰†€€ƒƒ‚ƒ„††††‡‡ˆ‰ˆ‡‡‡‡‰‰€€€€€‚€€€€€€€€€€€€„†„‚€€€€€€€~~~~~~|~~~}}~}}}}}}||~€}||||||}}}|~~}{|||}~~~}{}‚~{|€€|y}~€€€€€€€€€€€€€ƒƒƒ‡‰€€€ƒƒ„…†‡……„…‡‡‡‡‡†‡‡‡ˆ€€€€€€€€€€€€€€~€€€€„„ƒ‚€€€€€~~~}~~~}}~}~~||}|{}}}|}~~~}{||}~|z{{|}|}}|||||}~€}|||€‚~|z~‚~z}~~€€€€€€€€€€€‚‚…Šƒ€€€€€€€ƒ„…††…„„„‡‡‡‡‡ˆˆ‰ˆ‡€€€€€€€€€€€€€€€€€„ƒƒƒ€€€€€~~~~~~}}~~}{z{|||}z{}~}}~~}|{z|{||||~~{{|~~}{}~‚z{‚{|~€€€€€€€€€€€‚‰‡€€€€~~ƒ„…†††…†‡‡‡‡‡‡‡‡ˆˆ€€‚€€€€€€€€€€€€€€€€†„„„‚€€€€€€€~~}~}}}~~~€||}~~~~}|}}||~}~~}|||||}y|}{|~€ƒ{|€€€€€€€€€€ƒ†‰‚€€€€€€€€ƒ…ƒ„…†‡††††‡ˆ‡‡ˆ‰‰€€€€‚€€€€€€€€€€€€€€€€„ƒ‚€€€€€€€€~~~~~}~€€€}|}~}||~~}}}}}|||~~~~|||}{{€~{x{}}~~~‚~{|€€€€€€€€€€€ƒ‰„~€€€€€€„„ƒ„…†…†‡……‡ˆ‡‡ˆ‰€€€€€€€€‚€€€€€€€€…€‚„€€€€~~~~~€€€}}~~~~}||{|||{|~}{}}|}}|}}}}{z~{y{|}}}~}~~zz~€€~~€€€€€‚…‰€€€€€€€‚ƒ„„…†‡‡…†ˆ‰Š‰ŠŒ€€€€€€€€€€~~€€€€€€€†€ƒ„‚€€€€€€~~~~~~~}}}}}}}~~~~||}}}}|{{}||||{|~~}~~}}}}}}}}zz~}||||}}}}}€||~€€€€‚…‡‚€€€€€€‚„„…†‡ˆ‡ˆ‰‰ŠŠ‹Œ|~€€€~€€€€€€€€€€€‚ˆ~„„‚€€€€€~~~}~~}}}}}}}}}}}}}}|{|{z|{z|}~~}}}~~~}}~~||}}}}||}}~~~~~€€€€‚„„‡‚€€€€€€€€~ƒƒ„…††‡‡ˆŠŽŽŽqrruy{~€€€€€€€€€€‚‰ƒ€ƒ„ƒ‚€€€€€~~~~~~~}}}}||}~}}}~}}}}{{z{{{{||||||||}}|||||||||||}}}~~~~~~~~~~~~€€€€„…ƒ‡€€€€€€€€€€€~‚„„†‡‡‡‰ŒŒŽŽŽŽmkkmnnv€€€€€€€€€€€€ƒˆ…‚‚‚€€€€€€~~~~~~~~~~~~}}}}}}}||||}}}||{{||||}|||||{{{{{{|}}}}||}}}}}}}}~~~~~~~}~€€€€€€€€‚…‚‚ˆ€€€€€€€€ƒ†‡‡‰‰‰Œ‘‘kjjkkjr€€€€~€€€€€€€€€€€€‚†ƒ‚‚‚‚€€€€€~~~~~~~~}}|}}|}}}|}{{|}}||||{{{{|||||}}|||}|||}}|||}}~~~~~}}~~}~€€€€€€€„‚€ƒˆ‚€€€€€€€€€€€€€„†ˆˆˆŠŒ‘’’kjhhigr€€€€€€€€€€€€€ƒ†ƒ‚‚‚‚ƒ€€€€€~~~~~~~~~}~}}}|}}}|{||{|}}||||{{{{{|||||}}||}||||||||{|}~}}}}}~~~€€€€€€€„„‚‰‚€€€€€€€€€€€€€€€€€ƒ…‡‡ˆ‹‘’hhhhhdp~€€€€€€€†‡€€‚€€€€€~~}~~~}}}|}|||||||}||}}{z|||||{||}|{||{{{||{||}}}|{|||}}}~~~~~€€€€‚„‚ƒ„‰‚€€€€€€€€€€€€€€ƒ„†ˆ‰ŒŒ‘fffefbp€€€€€€€€€€€€€†ˆ€ƒ€€€€~~~~~~~}}}}}}||||||||||{|~}}{z~}}{z~}{{{{z|}}~}}|}}}}}}~~~~~~~~€€€€€€‚ƒƒ‚„‰‚€€€€€€€‚„†ˆ‰‹ŒŒŽffedd`p~€~€€€€€€€‡‰€€€‚‚‚€€€€€~~~~}~~~~}}}~}}|||{{||{{|||}}~~~}~}z}{z|}~}z|}|||||}}}}|}}}~~~€€€€ƒ„„ƒƒ‡ƒ€€€€€€€€€€~€€‚†‡‡ˆ‰‹ŒŽeeeecbr€€€~~€€€€€€€€€€ˆˆ€€€‚€€€€€€~~~}~~}||}}}}|||}}z|~}~~|}}~}~}{||y|||~xz|{||}}}}~~}~~~~~€€€€€€€€€‚ƒ‚‚‚‚‚†„€€€€€€€€€€€€ƒ††‡ˆ‰‹ŽŽccccb`q~~€€€€€~€€€ˆ‡€€€€€€€€€€€~~~~||}}}}|||~|{}}}}}|{{~|{{{{|y{|}~x|}}||}}}}~~~~€€€€~€‚‚‚ƒ‡„€€€€€€€€€€‚„„‡‡ˆŠŽŽŽŽedbaa_p€€€€€€€€€€€€€€€ˆ‡€€€€€€€€‚€€~~~}}~~~~~~}}}}}|{{{{||}}|z{}}|}}}}}}{{|{|}|zz~~}|{}~}}}~~~~~~}}~€‚‚€ƒ‡†€€€€€€€€€~……†‡‡ŠŒŽŽdddcb_q€€€€€€€€€€€€€€~€€Šˆ€€€€€ƒ‚€€€~||}}~~}}}}||{|}}||{|~}{{}}|||{|}|{{}|{{z{~~{{|||||||}}}~~~~€~~~ƒ‚€€‚ƒ‰‡€€€~~~~€€€…†…‡‡Š‹ŒŽcccca`p€€€€€€€€€€€‰‡€€€€€€€€€€€€€€€}}}}}|}}|}}|{|}}||}}}}|||}|}}{{{|~}}}~~~~}{|}}}}||||}}~~~~~‚„ƒ€‚‚ˆ‡~~~ƒ†‡‡‡ˆŠ‹ŒŽcbbccan€€€~~€€€€Ї€€€€€€€€€€€€€€€€~}}}||||}}|{{{{||{{}~}|||{{}}}}}}||}~~}}|z{}}|||{{|}}~~~~€ƒ‚‚€€€‡‡€€~~‚†‡‡‡‰‹Ždddcbal€€~€€€‚€€€‚Š…€€€€€€€€€€€~~}}~~}}}||||{{{{||{{{||{{{zz||||{{{|||{{|{{{{{|}}}}}}~~€~~~~€‚‚€€€€‡‡€€€€€€€€~ƒ†‡‡ˆ‰‹ŒŽ’cccbbaj~€€€€€€€€€~‹„€€€€€€€~~~}~~~}|}}||||}}|||||||{{{{{|{{zzzz{{|{{{{{{{{|}}}~~~~~~~~~~~€€€€€€€€‡‡€€€€€€€€€€€~€„†‡†ˆ‰‹Œ•dcccdcj~€€€€€€€€€€€Š…€€€€€€€€€€€~~~}}||{|}}}}}|}~~~~~}}}}|||{z{{{{}}}~}}}}||||}}}~~~~~}}~~~€‚€€€€€‚†‡€€€€€€ƒ‡‡†‰ˆˆŒ“•ddeeecg|€~€€€€€€€€€€€ˆ„€€€€€€€€€€€€€€~~~~}}||||}}}}{{||{{|z{}~}|}|{|}~~}zz||z{|~~}}}}}~~}~~~~}~~ƒ„€€€€€ƒ€€‡†€€€€€€€€€€†††‰‰ŠŒ’–eeeffdf{€€€€€€€€€€€€€€‰„€€€€€€€€€€€€~~~~}}|||}}|}|zzzzz{{}~}}|{|~}z}}|||{{z{|~~~|}~~~~~~|||}~€€‚„‚€€€ƒ€‡‡€€€€€€€€€€ƒƒ‚…†‡‡ˆ‹Ž‘•fghfhfgz€€€€€€€€€€€€€€Š…€€€€€€€€€€€€€€€€~~~~~}}}||||||}}|{zzz{{{|~}|{~}z{}{zz{{{|}z|~}}}}}}~~}|}~~€ƒƒ€€€~‚‡‡~€€€€€€€ƒ„‚ƒ„…†ˆ‹“efffgegx€€€€€€€€€€~‚‹…‚‚€€€€€€€€€€€€€~~~~}~}|}}}{{||~~}}}{|~|}}||}{||z{}~~}}|~}z|}}}~~~~}}}}~€€€€€€€€ƒˆ†€€€€€€€€€€€€„…„†‡‡ŠŽ‘effhjjiv~€€€€€€€€€€‚‹„‚€€‚€€€€€€€€€€€~~~}}}}|}}}{{||~}|||||}|||||||{{{|}}||}~}}{|~~~~~~~~~}}~€€‚ƒ€€€€~€ƒ€€ƒŠ„~€€€€€€€~€€‚„……†ˆˆŠ‘ijkjkkks~€€€€~€€€‰…‚€€€€€€€€€€€€€€€€~~~~}}}}{{}}~|{{||}|}{{|}~z||{{|}}}~~}|{}|}}~~~~€‚€€~‚ƒ€‰ƒ~€‚€ƒ‚„†ˆ‰‹jjjjloms}€~€€€€€€€€€€‰…‚‚€€€€€~€€€€€€€~~}}}~|y}~~|||||}|}|{~||||}}}|||}}~~}|{|{|}}}~~€€€‚€€€€€‚ƒ‰ƒ€€€€€€€€€€€€€€‚„…‡‡Šmlmklmnu~€€€€€€€€€€€Š„‚€€€€€€€€€€€€€€€€€€~}}~~~~|{}|}}}}}|}}~|{}z{{|}|||||||}€~|z}~}}}}}~€€€€€€€€„€‚Šƒ€€€€€€€€€€€€€€…†‡‡ˆŽnmoonoqu}€€€€€€€€€€€‚‰…‚€€€€€€€€€€€€€€€€€€}}~~|}~}}}}}}}}}~|{}{|{{|{{||||}~€|zz}}}}}}~~€€€€€€€€€€€€„€€„Œƒ€€€€€€~€€‚‚ƒ„ƒ…†‰noopqpps}€€€€€€€€€€€€€…‡„‚€€€€€€~€€€€€€~~~~{|}~}}}|}||{|~{{||}||||}}~~|{z}}}}~~€€€€€€€€€€€€‚„€„‹‚€€€‚‚ƒƒ‚ƒ†npnnqrrs|€€€€€€€€€€€€€€ˆ„‚‚€€€€€€€€€€€€~~~~}||}~}}}~}|||~}{z||}}~}}}€||z|}|}~~€€€€€€€€€€~€€„ƒ€„‰€€€€€€~€€€‚ƒ…†oonprstu{€€€€€€€€…Šƒƒƒ€€€€€€€€€€~}~}}}€€~|}|}~}|z{{}~}}‚€|zy|}}~~~€€€€€€€~‚…‚€€…ˆ‚€€€€€€€€~€€€‚…†qrqrssuvz€€€€€€€€€€€†ˆƒ‚€€€€€€€€~€~~~}~}}~~~|}€~~}|}}|{~~~}{z||~€€€€~|y|}}}~~~€‚€€€‚„€€€…‡…€€€€€€€€€€€€ƒ‚‚‚‚rsrsuvvwy€€€€€€€€€€‚‰…€ƒ‚‚€€€‚‚€€~}€~~~{}~|z{|}}|{|}}}}|{|~}||||||{x{}|~~~~€€€‚€~€€„ƒ€„‡‰‚€€€€€€€€€‚ƒ‚‚‚€rrsuwuvwy€€€€€„Š„ƒƒƒ‚€€‚€€€€€€€~~€€~~€€~}}{{{{{{zz{z|~}||}}}~|{|}}}}|zx{~}|~~~€€€€~}}€‚„„…ˆƒ€€€€€€€€€€€€ƒ„‚‚ƒ‚‚‚stuvxwwwy~€€€€€€€€‰‹ƒ‚„„ƒ‚‚‚€€€€€€€€€€€~~}}~~~~~~~||{||{{{}~}}}}~}}}~~}|{yyxxy}~~~~~€€‚€€€€€~~€€ƒƒ‚‚‚„„††€€€€€€€~€€€‚€ƒrtuwxxxxy}€~€€€€€€€€‚‹ˆ‚‚„„ƒƒ‚‚‚€€€‚€€€€€€€‚~~||}}}}~~~~~€~~}}~}}}}~~~|}}}}}}~€€€€€„ƒ€€€€~€€…ƒ‚ƒ„„…‰€€€€€€€€€‚‚„ƒ€ƒtuwwxxyz{|€€€€€€€€€€€€„‹…ƒ„„„ƒƒ‚€€€‚€€€€€ƒ€€}}|}}}}~~~~~~~}~~~~~}}~~~~}~~~}~~}}}~€€€€ƒ‚€‚‚€€€€€€€€€‚…€ƒ„ƒ„‰„€€€€€€ƒƒ‚ƒƒƒ‚€wwxyyyz{{}€€€€€€€€€€€€‡‹†„„„„ƒ‚‚€‚‚€€€€~~€€€€€€~}||}}}}}~~}}}}~~}}~~}~}}}}|||||~~€€‚„‚~€€€€€€‚„€€€ƒ„ƒ„††€€€€€€€€€€€‚ƒ‚‚‚‚ƒ‚€‚‚‚wxxyy{~€€€€€€€€€€€‹‹†„‚ƒ…„ƒ‚‚‚‚€€€‚€€~~‚ƒ€€€}~€~}}}|||}}|}~~}}~~~~}}}|}}}||}}~~€€‚„‚‚„~~~}z~€€€€‚„‚€€‚„ƒƒ…‰€€€€€€€€€€€‚„‚‚‚‚ƒƒxz{|~~€€€€€€€€€€ƒŒ‰…„‚„†…„‚‚ƒ‚€€‚€€}}„ƒ€€}}}€€~~}|~}}}}}}}}}}|||}€‚ƒƒ‚€€‚|…„|}}|x}€€€€€ƒ†ƒ€‚ƒ…ƒ‚……Š„€€€€€€€€€€€€‚‚ƒƒƒ‚ƒ„ƒƒƒ{~€€€€€€€€€€€€€€…Œ‡……ƒƒ„„ƒƒ‚‚‚‚€}}€„„€€~}~€~||~~~~~~}‚€~}}~~~€‚ƒ~|ƒ†€|~€}|€€€„„‚€€‚‚‚…ƒ‚„…‰…€€€€€€€€€€ƒƒ‚‚‚‚‚‚„ƒƒ‚€€€€€ƒ‚€€€€€‚ˆ‹††…ƒ‚ƒƒƒƒƒƒ‚€‚‚‚~}…‚}|~€€~~€~~~€€€~€ƒ€|zyxxx{~~|€ƒ‚|}…‚~~€€€€€€€‚…ƒ€€‚‚ƒ…‚ƒ……‡ˆ€€€€€€‚€€ƒƒƒƒƒƒ‚ƒ„ƒƒ„ƒ‚€€€€€€€€‚‚Š‹††…„‚„„„ƒƒƒƒ€€€€€~~€~ƒ…€|}€€€~€€€~ƒ€|xwy|~}}{{z|€ƒ€|€‚}~€~€€€‚„„‚€‚‚ƒ„…‚ƒ……†‰„€€€€€€€…ƒ‚„„ƒƒƒƒ„„‚„„ƒ€‚‚‚‚‚‚€€€€‚€€€€‚‹‰†††„‚ƒ„„„ƒƒƒƒ‚€€€€€€€€~‚}†}~€€€€€}|{}}€~ƒ€}yy~€€€€{{|{|}€€€€€€€‚‚ƒ…‚€€‚ƒƒ„„‚ƒ„…†‡…€€€€€ƒ„†„‚„„ƒƒƒ„„ƒ‚‚‚ƒ‚„……‚€€€€€€€€€€…Œ‡‡‡†„‚ƒ„„„ƒƒƒ‚‚€€€€€€~‚|‚€}|~‚‚€€€||{}}~€€€€‚|zz€€€€€€€‚{|€~{|€€€€€€€€€€‚‚„„€‚‚‚ƒ„ƒƒ„„…†‡ˆ€€€€€ƒ€€‚„††‚‚„ƒ‚ƒ‚‚‚ƒ„ƒ„„ƒ„‚€€€€€€ˆŒ†††…„‚ƒ†……„„„„‚€‚€€€€‚‚}}~||‚‚€€€‚||~~}€€€€€€|}{€€€€ƒ|}‚~{~€€€€ƒ„ƒ‚€‚‚‚ƒ…„ƒ„„…„†Šƒ€€€€‚ƒ€„…†„ƒ…ƒ‚ƒ‚‚ƒ‚‚ƒƒ„…………„ƒ€€€€€€€€€Š‹†………„„…††…„„„„ƒ‚€€€}€€~|}‚‚€€€€}}}€|~~‚€€~}}€€€€‚„„ƒƒ|~ƒ|€€€€‚‚€€‚…„€‚…†„ƒ„„„…†‰‡€€‚‚‚„†…„‚„…ƒ‚‚‚ƒ„…‚……„ƒ‚„†„„€~€€‚„‹Š†††……ƒƒ…†…„„„…„ƒ‚‚‚‚‚‚~€}ƒ‚€€~}~‚‚}|}ƒ‚~|‚€€€€€€€~{~€|{‚}€€‡ƒ‚‚ƒ…‚€€€‚ƒ‚‚…†„‚ƒ„………‡ˆ‚€€‚ƒ„„ƒ……„…„ƒƒƒƒ‚‚ƒƒƒ„‚„„…†……„‚‚€€€€€€€„Љ‡‡‡‡†„„„„„„„„„ƒ‚‚‚‚~~ƒ|€|„‚€€~~…„„…|}€‚‚€~~€‚€€€{|~}{€}z~}~„„„‡†y|ƒ„ƒ‚‚ƒ„ƒƒ…†ƒƒ„„……†‡ˆ†€€„…„ƒƒƒƒƒ…‡ƒƒ‚‚‚‚ƒƒƒ…„……„…††…„„ƒ~€€€€‡‰‰‡‡‡‡†ƒ„………„„„„„‚€‚‚€|z‚€}€{~~~~~€€~~‚‚‚€~~€~~€~xx|}}}€ƒ~z}{{ƒ„ƒ€}y~€„…ƒ‚ƒ‚‚ƒ…‡…ƒƒ„„…†‡‡ˆ‡€€ƒ„ƒ‚ƒƒ…„‚‚‚ƒƒ†††††‡‡‡†…‡†€€€€€‚ˆˆ‡‡‡‡††„„††…„ƒ„„ƒƒ€‚€€~z~€€~|~€€~}}|{{}}}}€€€}}~€ƒ€~~~€„€‚ƒ~|z{}~~|z€‚„„ƒ€‚‚‚„††……„„„…†…†ˆˆ„€‚„ƒ‚‚„ƒ‚‚„„‚‚ƒ‚‚„„††………†††‡†††€€€€€€†ˆ‡ˆ‡‡‡‡‡†„„„………„„„ƒƒ‚‚€€€€€|~‚ƒ‚€€~}~|{{zzz}~€€~|}~„ƒ€……~|}~€€|{{{yz„…„‚‚‚‚ƒ„††…„„„„„………†‰‰€€‚ƒ‚‚ƒ„…ƒ…†ƒ„ƒ„……ƒƒ„†††…„†††††‡†€€„Œ‡‡‡‡‡‡†††„ƒ„…„„„„„ƒƒ‚‚€€‚€z{€ƒ|~~€€~€ƒ|z}‚ƒƒ„‚€~{}‚‚~}‚‚‚~{{|€€ƒ…„‚€‚ƒƒƒ„„†ˆ…„ƒƒ…†‡‡…†‰Šƒƒ‚„„………‡……††††„‚ƒ‡†ˆˆ‡‡‰ˆ††‡‡ƒ~€€€‡Š‡†‡‡‡††††„‚…††…………ƒƒ‚‚€‚‚€‚€}}€}yy{€€€€€|{|~}{}~~€€{{€‚ƒzz‚‚‚€€‚ƒ„„ƒ‚‚ƒƒƒ‚ƒ„…‡†„„………‡‡‡††ˆ‰‚€€‚ƒƒ„………………‡ˆ†ƒ‚ƒ†‡‰‰ˆ‡‡‡‡‡‡ˆ†€€€‚‰‡‡‡‡ˆ‡‡‡‡‡…ƒ…‡†††††…„ƒ‚‚‚ƒ„ƒƒ‚‚‚€€€€€€€€€€€€€|zz{}€€€~{||}}~}{|€|}€€€€‚‚ƒ……„ƒ‚ƒƒ„ƒƒ…††…ƒƒ„††‡‡‡‡‡‡‰‡…„„„ƒ‚ƒ…………„ƒ……ƒ‚ƒ„‡…†‡†„„†‡‡†‡‡ƒ€€‚ƒ†Š‡ˆ‡‡‡††‡‡‡…‚„†††††††……„ƒƒƒ„„ƒ‚‚€€€€€€€€€€€€€€€€€€€~|yyz{~‚€‚‚€€€‚‚„†…„ƒ‚‚„„„„…†‡†…„„„„†‡‡‡‡‡ˆˆˆ†„‚ƒ……ƒ‚€ƒ…††…ƒ„†‚ƒ„ˆ‡†‡ˆ‡‡ˆˆˆ‡‡ˆ„€ƒŠ‰‡‡‡ˆˆˆˆˆ‡‡†„ƒ…†‡…………………„‚‚ƒƒ‚‚‚‚‚‚‚‚‚‚€€€€€‚‚€€€€€€€€€€€€€€€€€€€‚ƒ‚‚‚‚€‚ƒ‚‚„„„ƒƒ‚‚ƒ„„„ƒ…†ˆ†ƒ„…………††‡‡†‡ˆˆ‡ƒ‚‚†…ƒƒ‚ƒ‡‡…†„ƒ…‚€„‡‡‡‡‡‡ˆˆˆ‰ˆˆˆƒ‚‡Œˆ‡ˆ‡‡‰Š‰‡‡ˆ†„„…†††……†………„‚ƒƒ‚ƒƒƒ‚‚‚‚‚ƒƒ‚‚‚‚‚‚€~€€‚‚‚‚‚€‚‚‚‚‚‚‚‚‚ƒƒƒƒ„……„ƒ‚‚ƒ„„„…†‡‡…„…†…†‡‡‡ˆˆ‡‡‡ˆˆƒ‚†…ƒ‚ƒƒƒ„……ƒƒƒ‚ƒƒ…ˆ‡ˆ‡†‡‡‰ˆ‡††…„€‚‹‰‡‰‡‡‰‰‰‡‡‰‡„ƒ…‡‡†††…†††…„„ƒƒƒ‚ƒƒ‚‚‚‚‚‚€‚‚€€€€€€‚‚‚‚‚‚‚‚ƒƒƒƒƒ‚‚‚ƒ‚‚‚‚‚‚‚ƒ„…„„„ƒ‚‚ƒ‚ƒƒ…††‡†„„„…††‡††††††‡ˆ‰†„ƒƒ…ƒ‚„„„…†„„ƒƒ‡…„‰‡‡‡†††ˆˆ‡‡‡‡†€~‡Š‡‡ˆ‡‰‹Šˆˆˆ‰ˆ…ƒ…††‡††††††……ƒƒ‚‚‚ƒƒ‚‚‚ƒƒ‚‚€€€€€€€€€‚‚‚ƒ‚‚ƒƒ‚‚‚„„ƒ„ƒƒ‚‚‚‚€€‚ƒƒƒ„††„„„ƒƒ„ƒƒ„…††‡…„…††††††…††††‡ˆŠ‡„†††…ƒƒ„†…„…………„…„ƒŠˆ‡††‡‡‡……‡‡‡‡…‚ŠŒŠ‡ˆ‰ˆˆŠ‰‡‡‰‰ˆ†„„……‡†††‡††††„ƒ„„ƒƒ‚‚ƒƒƒƒ‚‚‚‚‚€€€ƒƒ‚‚‚ƒ‚ƒƒ‚‚‚‚ƒƒ‚€€€ƒ„…„††„ƒ„„‚‚„„„„…††‡‡„„…„…††‡ˆ‡†††‡‡‡‰‰ƒ‡†…„„††††…………………… \ No newline at end of file diff --git a/libs/jpegrecoverymap/tests/jpegencoder_test.cpp b/libs/jpegrecoverymap/tests/jpegencoder_test.cpp new file mode 100644 index 0000000000..2d144f07a9 --- /dev/null +++ b/libs/jpegrecoverymap/tests/jpegencoder_test.cpp @@ -0,0 +1,110 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include + +namespace android::recoverymap { + +#define VALID_IMAGE "/sdcard/Documents/minnie-320x240.yu12" +#define VALID_IMAGE_WIDTH 320 +#define VALID_IMAGE_HEIGHT 240 +#define INVALID_SIZE_IMAGE "/sdcard/Documents/minnie-318x240.yu12" +#define INVALID_SIZE_IMAGE_WIDTH 318 +#define INVALID_SIZE_IMAGE_HEIGHT 240 +#define JPEG_QUALITY 90 + +class JpegEncoderTest : public testing::Test { +public: + struct Image { + std::unique_ptr buffer; + size_t width; + size_t height; + }; + JpegEncoderTest(); + ~JpegEncoderTest(); +protected: + virtual void SetUp(); + virtual void TearDown(); + + Image mValidImage, mInvalidSizeImage; +}; + +JpegEncoderTest::JpegEncoderTest() {} + +JpegEncoderTest::~JpegEncoderTest() {} + +static size_t getFileSize(int fd) { + struct stat st; + if (fstat(fd, &st) < 0) { + ALOGW("%s : fstat failed", __func__); + return 0; + } + return st.st_size; // bytes +} + +static bool loadFile(const char filename[], JpegEncoderTest::Image* result) { + int fd = open(filename, O_CLOEXEC); + if (fd < 0) { + return false; + } + int length = getFileSize(fd); + if (length == 0) { + close(fd); + return false; + } + result->buffer.reset(new uint8_t[length]); + if (read(fd, result->buffer.get(), length) != static_cast(length)) { + close(fd); + return false; + } + close(fd); + return true; +} + +void JpegEncoderTest::SetUp() { + if (!loadFile(VALID_IMAGE, &mValidImage)) { + FAIL() << "Load file " << VALID_IMAGE << " failed"; + } + mValidImage.width = VALID_IMAGE_WIDTH; + mValidImage.height = VALID_IMAGE_HEIGHT; + if (!loadFile(INVALID_SIZE_IMAGE, &mInvalidSizeImage)) { + FAIL() << "Load file " << INVALID_SIZE_IMAGE << " failed"; + } + mInvalidSizeImage.width = INVALID_SIZE_IMAGE_WIDTH; + mInvalidSizeImage.height = INVALID_SIZE_IMAGE_HEIGHT; +} + +void JpegEncoderTest::TearDown() {} + +TEST_F(JpegEncoderTest, validImage) { + JpegEncoder encoder; + EXPECT_TRUE(encoder.compressImage(mValidImage.buffer.get(), mValidImage.width, + mValidImage.height, JPEG_QUALITY, NULL, 0)); + ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); +} + +TEST_F(JpegEncoderTest, invalidSizeImage) { + JpegEncoder encoder; + EXPECT_FALSE(encoder.compressImage(mInvalidSizeImage.buffer.get(), mInvalidSizeImage.width, + mInvalidSizeImage.height, JPEG_QUALITY, NULL, 0)); +} + +} + -- GitLab From a4819140bca93820340f21cadc0a911ee10c2568 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Fri, 21 Oct 2022 04:12:30 +0000 Subject: [PATCH 0403/1310] libjpegrecoverymap: add JPEG encoder for single channel image test: jpegencoder_test bug: b/252835416 Change-Id: I6a13438ae4695569058645ad13c1182b7a626524 --- .../include/jpegrecoverymap/jpegencoder.h | 14 +- libs/jpegrecoverymap/jpegencoder.cpp | 80 +- .../tests/data/minnie-320x240.y | 1930 +++++++++++++++++ .../tests/jpegencoder_test.cpp | 17 +- 4 files changed, 2016 insertions(+), 25 deletions(-) create mode 100644 libs/jpegrecoverymap/tests/data/minnie-320x240.y diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h index ec1291892d..9641fda24c 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h @@ -28,7 +28,8 @@ extern "C" { namespace android::recoverymap { /* - * Encapsulates a converter from YUV420Planer to JPEG format. This class is not thread-safe. + * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format. + * This class is not thread-safe. */ class JpegEncoder { public: @@ -43,7 +44,7 @@ public: * Returns false if errors occur during compression. */ bool compressImage(const void* image, int width, int height, int quality, - const void* iccBuffer, unsigned int iccSize); + const void* iccBuffer, unsigned int iccSize, bool isSingleChannel = false); /* * Returns the compressed JPEG buffer pointer. This method must be called only after calling @@ -67,11 +68,14 @@ private: // Returns false if errors occur. bool encode(const void* inYuv, int width, int height, int jpegQuality, - const void* iccBuffer, unsigned int iccSize); + const void* iccBuffer, unsigned int iccSize, bool isSingleChannel); void setJpegDestination(jpeg_compress_struct* cinfo); - void setJpegCompressStruct(int width, int height, int quality, jpeg_compress_struct* cinfo); + void setJpegCompressStruct(int width, int height, int quality, jpeg_compress_struct* cinfo, + bool isSingleChannel); // Returns false if errors occur. - bool compress(jpeg_compress_struct* cinfo, const uint8_t* yuv); + bool compress(jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel); + bool compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv); + bool compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image); // The block size for encoded jpeg image buffer. static const int kBlockSize = 16384; diff --git a/libs/jpegrecoverymap/jpegencoder.cpp b/libs/jpegrecoverymap/jpegencoder.cpp index b5d3a063ca..d45d9b33c9 100644 --- a/libs/jpegrecoverymap/jpegencoder.cpp +++ b/libs/jpegrecoverymap/jpegencoder.cpp @@ -36,14 +36,15 @@ JpegEncoder::~JpegEncoder() { } bool JpegEncoder::compressImage(const void* image, int width, int height, int quality, - const void* iccBuffer, unsigned int iccSize) { + const void* iccBuffer, unsigned int iccSize, + bool isSingleChannel) { if (width % 8 != 0 || height % 2 != 0) { ALOGE("Image size can not be handled: %dx%d", width, height); return false; } mResultBuffer.clear(); - if (!encode(image, width, height, quality, iccBuffer, iccSize)) { + if (!encode(image, width, height, quality, iccBuffer, iccSize, isSingleChannel)) { return false; } ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes", @@ -91,8 +92,8 @@ void JpegEncoder::outputErrorMessage(j_common_ptr cinfo) { ALOGE("%s\n", buffer); } -bool JpegEncoder::encode(const void* inYuv, int width, int height, int jpegQuality, - const void* iccBuffer, unsigned int iccSize) { +bool JpegEncoder::encode(const void* image, int width, int height, int jpegQuality, + const void* iccBuffer, unsigned int iccSize, bool isSingleChannel) { jpeg_compress_struct cinfo; jpeg_error_mgr jerr; @@ -102,14 +103,14 @@ bool JpegEncoder::encode(const void* inYuv, int width, int height, int jpegQuali jpeg_create_compress(&cinfo); setJpegDestination(&cinfo); - setJpegCompressStruct(width, height, jpegQuality, &cinfo); + setJpegCompressStruct(width, height, jpegQuality, &cinfo, isSingleChannel); jpeg_start_compress(&cinfo, TRUE); if (iccBuffer != nullptr && iccSize > 0) { jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast(iccBuffer), iccSize); } - if (!compress(&cinfo, static_cast(inYuv))) { + if (!compress(&cinfo, static_cast(image), isSingleChannel)) { return false; } jpeg_finish_compress(&cinfo); @@ -128,29 +129,44 @@ void JpegEncoder::setJpegDestination(jpeg_compress_struct* cinfo) { } void JpegEncoder::setJpegCompressStruct(int width, int height, int quality, - jpeg_compress_struct* cinfo) { + jpeg_compress_struct* cinfo, bool isSingleChannel) { cinfo->image_width = width; cinfo->image_height = height; - cinfo->input_components = 3; - cinfo->in_color_space = JCS_YCbCr; + if (isSingleChannel) { + cinfo->input_components = 1; + cinfo->in_color_space = JCS_GRAYSCALE; + } else { + cinfo->input_components = 3; + cinfo->in_color_space = JCS_YCbCr; + } jpeg_set_defaults(cinfo); jpeg_set_quality(cinfo, quality, TRUE); - jpeg_set_colorspace(cinfo, JCS_YCbCr); + jpeg_set_colorspace(cinfo, isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr); cinfo->raw_data_in = TRUE; cinfo->dct_method = JDCT_IFAST; - // Configure sampling factors. The sampling factor is JPEG subsampling 420 because the - // source format is YUV420. - cinfo->comp_info[0].h_samp_factor = 2; - cinfo->comp_info[0].v_samp_factor = 2; - cinfo->comp_info[1].h_samp_factor = 1; - cinfo->comp_info[1].v_samp_factor = 1; - cinfo->comp_info[2].h_samp_factor = 1; - cinfo->comp_info[2].v_samp_factor = 1; + if (!isSingleChannel) { + // Configure sampling factors. The sampling factor is JPEG subsampling 420 because the + // source format is YUV420. + cinfo->comp_info[0].h_samp_factor = 2; + cinfo->comp_info[0].v_samp_factor = 2; + cinfo->comp_info[1].h_samp_factor = 1; + cinfo->comp_info[1].v_samp_factor = 1; + cinfo->comp_info[2].h_samp_factor = 1; + cinfo->comp_info[2].v_samp_factor = 1; + } } -bool JpegEncoder::compress(jpeg_compress_struct* cinfo, const uint8_t* yuv) { +bool JpegEncoder::compress( + jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel) { + if (isSingleChannel) { + return compressSingleChannel(cinfo, image); + } + return compressYuv(cinfo, image); +} + +bool JpegEncoder::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) { JSAMPROW y[kCompressBatchSize]; JSAMPROW cb[kCompressBatchSize / 2]; JSAMPROW cr[kCompressBatchSize / 2]; @@ -194,4 +210,30 @@ bool JpegEncoder::compress(jpeg_compress_struct* cinfo, const uint8_t* yuv) { return true; } +bool JpegEncoder::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image) { + JSAMPROW y[kCompressBatchSize]; + JSAMPARRAY planes[1] {y}; + + uint8_t* y_plane = const_cast(image); + std::unique_ptr empty(new uint8_t[cinfo->image_width]); + memset(empty.get(), 0, cinfo->image_width); + + while (cinfo->next_scanline < cinfo->image_height) { + for (int i = 0; i < kCompressBatchSize; ++i) { + size_t scanline = cinfo->next_scanline + i; + if (scanline < cinfo->image_height) { + y[i] = y_plane + scanline * cinfo->image_width; + } else { + y[i] = empty.get(); + } + } + int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize); + if (processed != kCompressBatchSize / 2) { + ALOGE("Number of processed lines does not equal input lines."); + return false; + } + } + return true; +} + } // namespace android \ No newline at end of file diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240.y b/libs/jpegrecoverymap/tests/data/minnie-320x240.y new file mode 100644 index 0000000000..f9d8371c18 --- /dev/null +++ b/libs/jpegrecoverymap/tests/data/minnie-320x240.y @@ -0,0 +1,1930 @@ +ØÖÖÓÑÑÏËÈÈÈÅÅÃÃÂÅÂÁÀÁÀ½¹¶³°«¬±´³²±´¯–‘ž°¯¯±´³³´µ¹»½½½»¸¸·¹»¼½¿ÁÃÆÇÉÌÎÏÐÎÍÌËÊÊÉÊÍÌËÊÆÇÈËÊÌÌÉÈÇÆÅÄÄÅÆÇÆÆÇÇÆÅÃÄÂÃÁÁÁÁÁ¿¾¼¼¼¼½»»¸·¹¸·¶µ³²²²²²±°¯¯®®®ª©¦£¤¥¤“}vrswqnpsqqklspmjmnlknw{’™›œ›š›››™›ššœœšœžŸ¡¢¤§«¯³µ¸ºº¼¼»º¹µ²­¥ž—”’”¦®´µ·¸¹º»»¼»¼¼½¾¾¾½¼º¹¸¹º»¼»½ÀÂÃÆÊËÊÊÍÍÌÌÉžºµ±­©©­´¶¶¶³²²³°¬©­²³¯©¢¡¨±´·µµµ³­›…„•¬¸º¹··³¨ ¦¯±£W9ÙÙ×ÕÒÑÏÎÉÇÇÈÅ¿¿ÁÄÂÀÀļ·´±­¬«®±²°­°§Ž›­®¯¯°²²²µ¹»½¼¼¼º¸¸¹»½¾¾ÁÅÇÆÉÍÏÐÏÎÎÌËÌÌËËËËÌÊÇÇÉÊËÌÌÌÊÉÈÈÆÇÇÇÈÇÇÆÆÆÆÆÆÄÃÃÃÂÂÁ¿½¼¼½½¾»¹¹¹¹¹·¶¶µ´µ´²±²±¯¯­­®­¨¤¢¥¤f96999:7464336753/01,..2344N“žœœššœœœœžž¡£¦«®°³µ¸»¼½½¼¼¹´®¨¡›•”–𤫳¶¶·¸º¼½½¾¼½½¾¾¾¿¿½»¹¹ººº»¼¾ÀÂÅÈËÌËÎÏÐÎÊÆÄ½¸µ±¬¨§¯·¶µ´²±²²°­©®²³®§¥¥«°³¶¶µ´¯¨“‚ˆ¡³¶¸···³¨¡§°´²w:ÚÚØÖÓÒÐÐÍÊÇÇŽ¾½¿ÁÁÁÃÄÁ»µ²¯®ª«°°¬¥£¬“¨­®¯°±°±µº½¼¼¼¼»¹¸¹º¼½¿ÁÅÈÉÌÎÐÐÎÍÍÊÊÌÍÍÌÌÌËÉÈÈÈÊËÌËËËÊÊÉÈÈÈÇÆÅÆÆÆÆÆÇÆÆÄÄÃÂÂÁ¿½¼½½½¾¼¹ºº¹¸·¶µµ´´µ´²±±¯¯­­­¬¬§¤ªr'Rouwwvzy{zxyzyxtsvurkgce_F U¢žœ›››œœ››Ÿ ¡£¨«°±³µ¸»½¿¾¾½º·°«£›–••˜ ¨®´¶¶¹»¼½½¾¾¾¾¾¾¾¾¿¿¼»º¹¹º»¼½¿ÁÄÇÊÍÍÍÑÒÏËÉÄÁ»·´±­§¨²·¶´³³³²²¯«©®²³­¨¤¦°³··¶µ³®£‰–®µµ¶¸¸¸²¤ §±¶·OÙ×ÙØÔÑÐÍÎËÈÅÄÃÀ¿¼º¼½ÀÁÂĺµ²¯®­­®©™”ž£–£ª­¯°°±°²¹½½¼»¼º¹¸¹¹»¼¿ÁÄÈÊÌÎÑÑÏÍÌÊÊËÌÌÌËÌÊÉÉÈÈÊËËÊÊËÊÊÈÉÉÈÇÆÆÆÅÆÅÄÆÃÄÄÁÁÂÂÁ¿½»¼½¾½»¼»º¹·¶¶··´´µµ´²±°°®­¯®«ª¥¤@: ¬ª¬©ª¨§§©ª§©¨§©¦£¦¦£•¥£¤•;:—Ÿžœ™š››œ›œœŸ£¥§«®±´·»½¾¿À¿½»¸³¬§Ÿ™•”–ž¦­°³¶¹»½¾½¾½¾¾¾¿¿¿¿¿½¼»º»º»¼¼½ÁÂÇÊÊÌÏÑÑÐÏÌȾ¹´¯¯¬§«´¶¶´µ¶³²±¯ª¨­³²«¨¢©¶¹ºº·¶²¬™~‹¢°±²µ·¹¶®£ ¦±··¯oÛ×Ö×ÕÒÐÌÍÊÇÇÅÁ¿¾º¸·¹¼¿Á¿¹µ²¯®®°«—‰‘œš¡©ª­®²²°³º¼¼¼»»º¹¹¸¸¹¼¿ÀÄÇÈÌÎÐÑÏÎÍËÉÊËËÍÍËËÊÉÈÈËËÊÊËËÊÉÊËËÉÇÆÈÇÇÆÄÄÅÄÄÄÃÃÂÂÁ¿½½½¾¾½»º»»¹¹¸¸¹º·µ¸·¶¶³²³°°°°®­ª 5M¦¡¢¡¢¤¤¦¥£££¤¤¢  ¡ £}>Ž¡ž¥I1‘Ÿœš™™›ššš›œ›œ £¦«¯²´·º»¾¿¿¿¾½»µ®©£›˜–•›£©¯³¶·¹½¾¿¿¿¿½½¾¾¿¿À¿¾½»º¼»¼¼½¿ÁÃÇÊÌÎÏÑÑÐÎÊÆÁ¼¶±«ª¨¨­³µµ´´´´³²¯©§­±®«¦£­´¸º¹¶´°¤‰€’§«­°³¶·²­£ §±¸¹²•ÛÙ×ÓÑÓÏËÊÈÄÄÃÀÁ¿¼º¶´µ¸½ÀÃÿ¹´²°­¬«£ˆˆ”›Ÿ§ª«¬°±¯°º½½½½»»º¹·¸º¼ÀÀÃÆÈËÎÐÐÐÏÍÌÊËÌÌÍÌËËÊÉÇÈÊËÊÊÉÊÊËËÉÈÈÈÉÈÇÆÇÇÆÄÃÄÃÅÄÃÂÂÀ¾¾ÀÀÀ¿½»»¼¼»ºº¹¹¹¸¸¸¸¸µµµ³³³²°¯¬5T§¡£¢¤¥¦¦¦¦¥¥¥¦¤¢¢¡ ¢T1s¥Ÿ¢F)sš›—–————˜™››ž £¦ª¯³¶¸º½¿¿¿¾¾½»¹±­¦Ÿ™•–›£¨®³µ·¹»½¾À¿¾¾¾½½¾¿ÀÀ¾¾¼ºº»»½¾¿ÀÂÄÈÊÍÏÏÐÑÏÌÈÄ¿º´­©§¦§°µ´´³´³³³²¯¨©±±­¨¢¥°¶¸ºº·²¬ƒ‰˜¤¨¬¯²µ¶²­¤¤§²»º²¬ÚØØÕÐÏÎÌËÉÃÁÀ¿¿À¾º·²°²¶»¿Â¼¸´²®¨©ª„Œ”ž©©««®°±°¸»¼½½»»»¹¹¸»½¿ÂÄÆÆÊÏÑÑÑÐÍËÍÎÎÎÌËÊÈÊÊÇÆÉÊÌÌËËÊÊÉÈÇÈÈÇÇÇÇÈÉÈÅÄÄÄÆÄÃÄÂÁÀ¿¿¿ÀÀ¾¾½½½¼»¼»¹¸¹º¹··¶µ´´µ´²²°­™2Z©¤¦¥¦¥¦¥¦¥¥¤¥¤¤¤£¢£‡HeP¡ ¡@&-Hh‰—–’”—ššœ ¤¦©­°µº»½¿¿ÀÀ¿¿¾»¶®©¡œ˜•™¢ª®²¶·ºº¼¾¾¾½½½½»½¿¿À¿¿¾»¼»º¼½¿¿ÂÄÆÊËÎÏÐÒÒÎËÇ»·°«¨¦¥¦­²³´´´´µ´²­¦©¯¯­¥ ¥·¹»»º·°§Š™¥©¬°²µµ´«¡¤©´½»³¯ØÕÖÕÑÌÊÊÉÇÄÁ½»¾¿¿»¸³¯­¯´¹½Á¿»¶²­©§­£…ˆŽ—¥§©«¬®­®·¼¾»¹»¼»ººº»½¿ÂÄÆÇÍÑÑÓÒÏÍÌÌËÌÍÌËËËËÊÈÈÉÉËÌÍÌÊÊÉÈÈÇÆÄÅÅÆÉÉÇÅÄÄÆÅÂÂÃÃÁÀ¿¾¾ÀÀ¿¾½¾¾½»»º¹¸ºº¹¸¸·¶¶¶µµ´³±°–0b­¥¦¦§¥¥¦¦¥¥¤¥¤¤£££¨_gA‘¡š8$A,&6W†—›š›ž ¤§ª®²µ¹»½¿¿ÀÀÀ¾½¼·±©£ž™—™ž¦«¯²¶¸¼½¿¿½»»¼½»»½¿¿¿¿½½»»ººº»½ÀÃÆÉÌÍÐÑÑÒÒÏËÆÀ¹³­©¨¥¤¨¬°²³³´··¶±«¤¨­­©¢ž«¸½»¹·³® „ƒŸ¨«®¯°µ·²§Ÿ£«´»½µ®ÐÌÎÐÒÎÊÇÆÆÅÁ¾¼¼¾¾½¹´°«¬®²·½¿½¶³®ª§¬°‰Œ” ¤¦¨ª©©ª¶¼¼º¹¹¹º»º»½¾ÁÂÅÈÊÍÐÐÒÑÎÌËËÌÌÍÍÌËËÊÈÇÆÆÈËËÊËÊÊÊÉÉÈÆÄÄÅÅÅÆÆÅÆÄÅÅÄÄÄÿ½½½¾¿ÀÁÀ¿½½½»»º¹·¹¹¹»º¹¹¸¶µ´³´³³•/l²§¦¦§§¨¨¨¨§¦¦¥¥¥¤¤—?S_8z¤”06‘}V5$.YšŸž¡¦ª¬®²µ¸¼¼¿ÁÁÂÁÀ¿½·³­§¢™™Ÿ¥¬°±´¸»½ÀÁÀ¾½¾¿¿¼½¿À¿¾¾¿½º¹¹¹¹»¾ÂÄÈÌÍÑÓÓÓÓÐÍÇž·±¬¨¦¤¥ª°²²²³µ··´¯¨£§««¤Ÿ¡±¹½»·µ±ª“‰•¢ª¬¬­²¶¶¯§ ¦­´»¼·­ÂÀÃÈÇËÊÇÅÄÁÁ¿½»»¼¼¹¶²­¬«¬±´º½º´°ª¦§±¨‡‹’¡£§©¨©«´»¼º»¼ºº¹º¼½¾ÁÂÆÆÊÎÑÑÑÐÎÊÊÍÏÍÌÎÍËÊÉÉÈÄÆÉËÌËËËÊÊÊÊÉÉÇÅÅÅÄÄÄÄÅÅÄÄÅÅÅÃÀ¾¼¼¼¾¿¿¾¿¿½½»¹¹¸·¸¹¹»ºº»¸·µ´³´³´”0xµª§¥¥§¨¨¨¦§§¥¤¦¦¤¥vXzvT^ ,C¢¤¡ˆ_;.2Mu•¥¥ª®²µ·»¼½ÁÂÂÃÂÀ¿»¶¯©£¡œš›£¨®²´¸»½¾ÀÀÀ¾½¾¾½¾¿ÁÂÁ¿À¿¾»º¹º»¾ÀÃÈÊÍÐÓÕÕÔÒÎÊÅÀ¹³®¨¦¥£§¬¯±±²²µ¶¶³®¨£§«¦Ÿ›¦µ»¼º·²­¢†ƒœ§­­­¯´µ´¯¤¡§­µ¼½¸°¹µ³¼ÁÄÆÈÆÃÂÀ¾¾»ºººº¶³±¬©¨©®´¹¼¶±¬¨¥§±—‰™ ¢¦©¨©«³º¼½¼»»»ºº½½¾ÀÀÄÇÉÎÑÑÐÑÎÌËÌËÌÍÍËÊÉÉÈÉÆÆÈËÌÍÍËÊËÌËËÊÉÈÉÆÅÅÃÄÅÆÅÃÄÄÄÃÁ¿¾½¿¿À¿¿¿À½½¼ºº¸¸¹ºººº»»¸¶¶´´µµº“2‚¸®«¦¤¥¦¦¦¤¤£¡£§¥¤¤qЧ¦ŒW™ƒ'LŸ›¡¡ŽjI93Cjž­°µ¸»¼¾ÀÃÃÂÁÀ¿½¸³­¦¡ž››ž¦«°´¶º½¾¿À¿À¿½½¾½¾ÀÂÂÁ¿¾½¼º¹»¼¿ÂÄÇÌÎÐÓÕÔÔÓÏËǼµ°ª¥¢£¤©®²³´³µ¸¸¶³®¦¢¨ª¢™š«¸¼»¸´°ª˜€…“¢ª®®¯³²µµ¬¢¡¥¬¶¼¿º²¶´²±·¼¿ÅÆÄÂÁ¾»½¼º¹¸µ²²¯«§¦¨¬¯·¹´®¬§£«®Š˜ ¢¤¨ªªª³º¼¿¾¼»»»»¼½À¿ÀÁÅÈÍÏÐÐÐÍÌËÌÌËËËËÉÊÊÉÈÇÇÇÉËÍÍËÊÌËÊÌËÉÉÉÉÅÅÅÅÆÆÅÄÄÂÃÁÀÀ¿¿ÀÀÀÀ¿¼¾¿¿¿½¼ºº»ºººººº¸¶¶µµ´´¼ˆ3ˆº°­ª¦¦¦¥¦¤¢¡Ÿž£¤¤£ £Ÿ››ƒ$W šš› ¢žŠmR.T­¶¶»½¾ÁÃÄÃÂÁÀÀ»¶±«£Ÿœ›ž¤«¯³¶¸»½¿ÁÀÁÀÀ¾½¾¿ÀÂÂÃÂÀ¾¼¼¼»¾¿ÁÅÇÊÍÐÒÓÓÔÔÐÍÈÿ¹±«§¤¢¢¤¨¬°³¶¶¹¹·¶³¬¤£ª§¡™¡²·¹¹¸´°¦†ƒ‡˜¤«®¯³´µ¸´«¢£§­¸¾¿½²º²°¯±µº½ÂÃÃÁ¿»»»ºº¹¸³±¯¬§¥¤¥§¯³¶³¯«¦¨´ª‡”¢¥¦©«©ª±·»¾¾¾¼ºº¼»¾¿ÁÃÃÆÈÍÐÏÐÎÌËËÌÌËËÊÊÉÊÉÉÈÇÆÇÊÊÌÌÊÈÊÉÊËÌÊËÉÈÇÇÇÅÄÅÅÄÅÄÄÂÀÀ¿ÀÀÀÀÀ¿½½¿¿¾¿½º»»º¹¹¹¸¸¸··¸¶´²¼5Œ¹°®­«ª©§¥£¦x𣢤£¤£¡žœœž€ ^£œœ››i‡­¬¥X/˜º¹¼¿ÁÁÂÄÃÂÁÁ¾¹³®¨¢œ›¤ª¯²µ·º¼¾¿ÁÀÁÀÀ¼»¼¾¾ÀÁÀ¿¿½½½¼¼¾ÁÄÆÊËÎÐÒÔÔÔÑÎÊÆÂ¾·¯©£¢¤¥¦ª®°´·¸¸¹¸µ°«¥£¨¦œ™©´·¹¹·³ªš€‡œ¨¬­°´´¶·²¨ ¤©¯ºÀÀ¼°Á´¯°°±µ»¾¿ÀÀ½»¹¸¹»º·¶³°«¨¤ ž ¤«²·µ­¨¦¬¸™Œž¢¥©«ª¨®¸»¼¾¾¼¹»»º»½ÀÃÅÇÊÍÎÍÎÏÍÊÉËÍÊÌÌÉÉÊÊÊÇÇÇÈÊËËËÊÈÉÉËËÌÍËÊÉÉÇÅÄÃÅÄÃÄÄÄ¿¾¿¿À¾¾¿¿¿¾¾¾¾½»¹ºº¸¹¸¸¹¸¶¶¶¶¶µ±¹s2’·°¯­¬«©¦¤¦—G#L—¥£¤¤¤¡Ÿžœt g¤›œœŸi1t¯¨³o0”¼º½¿ÀÁÁÁÁÁÀ¿º´°ª¦ œ¢©®±´·¹»¿¿ÀÁÀÁÁ¿»º»¼¼½¾¿¾½½¾¾¼»½ÁÄÆÊÍÐÓÓÕÖÒÎËÇþº²ª¥¢£¦¦¨¬°²¶¸¹¹¹¶´®ª¢ ¤ —°µ¹¹·¶°§Ž‡”¢«­­°³¶·¶°¤Ÿ¤¨°»ÁÁ¹±Éº±°±°²µº¼¾¿¿¼¸··¹¸¸¶µ²¯©¥žšš¡§±¸µ¬¨§¯°‘›¢¥§ª«¦¬¸¼¿¿¾½»»¹º»¼¾ÁÄÇÊÌÍÍÏÏËÉÈÈÊËËËÊÉËÊÉÈÆÈÉÉÊÌËÉÉÊÉÊËËËÊÊÊÊÈÆÆÄÄÄÄÄľ¾À¿¿¿¿ÀÀ¿¾¼¾¾¼ººº¹¹¹¶¶¹¹·µµ¶µ³±¸j.—´°¯®­«¨¥¥›B!)#JŸ¤¥¥£ žŸž n"r¤£zOXw±¬²Z;¬½»½¿ÀÂÁÁÂÁ¿»·²¬§¢Ÿœž¤¬¯³··º½ÀÀÀÀÁÁÁ¼»¼¼»½½½½½½½½½¼¼¼ÀÄÇÊÍÑÔÔÕÓÏÊÉÄÀ¼µ®¥¡¡£¦§©«¯²µ¹º»¸¶³¬¥ Ÿ œ™¨¶¹¹¶´±¬‚‚Œ™¥©¬®²¶·¹´­£ £«´¾À¿¸¯Ïµ²±°±´·¹º¾¿½¹¸·µ´¶¶µ³²®¨¡™—˜š£¯¹¹¯¬­¶¢•££¦ªª¨¬¸¼½¾½¼½º¹»¼½¾ÁÄÇËÌÎÐÑÏÌÉÊÉÊËÊËËËÉÈÉÈÇÈÉÊËÍÌÉÊÊÉÊÉÉÉÈÈÉÉÊÉÇÅÄÃÄÄÃÁ¿¾¾¾¿ÀÁÀ¿À¿¿¾¾¼»»»ºº¹¹·¸»»·¶¶µ³²±¶b/𴝱°°­©§¥M'()%dª¤¤£ ¡¡Ÿ¢j"}¥ž¤‹EŒlwµ²¥?`½½¿ÀÂÃÂÁÀ¿¼¸´­©¦ žž¢¨¬²µ¸¹»¾ÁÂÁÀÀÀ¾»»½¾¼½½½½¼¼¼»»º½½ÀÃÈÌÎÒÓÔÓÐÍÈÅÿ¹±¬¥¡ £¥¦©¬°´¶¹º¹¹¶´¯¨Ÿœœ˜°¹»¹·´¯¥Œ‚ˆ‘ž¤©¬¯´¹¸¸´©¢Ÿ¤®·¾Á¾¹°ÏȺµ´²´µµµ¸¹»»º¸¶µ´³³µ²³°«¦ ™–““›¡¨µ»·³¶¶› ¢¥¨©¦ªµº¼¾½»»º¹º½¿ÂÃÅÉÍÎÎÐÐÎÌÊÊÊÉÉËÌÍÍËÉÊÈÈÈÇÉËÌËËËËÌËÊÉÈÈÉÊÈÈÉÇÆÃÁÁÂÁÁ¾½¾¾¿ÀÁÁÁÁÀÀ¾½¼»¹¸¸¹¹¹º»ºº·µ¶µ³³³·^4³®°°°¯ª¬u%&&'&/•¥£¤££ Ÿ¥e'…¥¦•IEzXy´·|5•ļ¿ÀÂÃÃÃÂÀ¾º´®©¤¡ŸŸ¢§¬²¶¸»¼½ÀÃÄÂÀÀ¿½º»½¾¾¿¾½½¼¼¼¼»»½¿¿ÃÊÎÐÓÓÒÐÍÈÅÃÀº³®¨¥¡¡£¤¥©¬±¸º»»¹¹¶³­¥›š™£³º»¹·³«œ‚‚‹–¢¦¨­±¶·¹¸±¤¢Ÿ¥°¹¿Á¿»°ÐÍÀ·¶¶¶´µ¶¶·¸¹··µ´µ³²²³´³°«¥Ž’–›¥³½¿¾¹¦› £¤§§©´¹»¼¾¼»¼¼»¼¿ÃÄÅÊÍÎÏÐÐÏÍËËÊÉÉËÍÎÍËÊÊÈÈÈÈÇÈÉÊÌÌËÌËËËÊÈÉÉÈÇÇÇÅÄÂÂÁÁÀ¾¾¾ÀÁÀÀ¿¿ÀÀÁÀÀ¿¼¹¹ººººº»º¸¶´¶µ´´´¸[9¡±®¯¯°®«¥B%)'&'++y¬¤¥¤£¢Ÿ¥^-§¦dm…g@}¸µPJ¹ÁÀÁÂÃÃÃÄÂÁ½µ°ª¦¢¡Ÿ¡¥ª®´·º¼¾ÀÃÄÄÃÁÁ¿¼»¼¾¿¿ÀÀ¾½½¾½½½»»½ÁÅËÏÑÔÔÑÏÊÆÃ¿»´°«¥¢¡¢¤§§¨«²·ºººº¸µ²«£›™—™©¶¹º¸³¬¦“…™¤§ª¯³¶¸º·¯¦¡¢©³º¿ÀÀ»¯ÑÏĸ··¸·¶µ´´¶¶µ³³³²³³²²²´³¯¦¢”‰‰Š‘•ž­½Æ¿«˜ž¡£¥¤§²·º»½¾¼½½¼½¿ÂÅÇÉÌÍÏÐÑÎËËÊÉÊÊËÌÌÊÊÊÊÊËÊÊÉÈÉËÍÍËËÌËÌËÉÈÉÊÈÈÇÅÄÂÂÁÁÁÀ¿¿ÂÃÂÂÁÁÁ¿ÁÁÁ¿»¹º¼»»º¹¹·µ´¶·¶´´´¶X>£¯®®®¯¯­Ÿ9,+)))0.v¬¦§§¥£¢§Y2–¨¢—®±µq‚½Ÿ8}ÇÂÁÂÃÄÄÄÃÁ½¸±­©¦¢ Ÿ¥©¬°³·¹»¿ÃÃÅÃÂÀ¾½»»½¾¿ÁÀ¿¾¼¾¿¿½½¼¾¿ÄÉÍÏÒÓÒÐÎÈÄÁ¼¶±¬§£ ¡¤¦§§©ª²·¸º¼¹·´¯ª¢™–“ž±ºº¹µ±«œ„‚‡’ž¦§¬²¶¶¸¸µ¬¤œ£­µ¼ÀÀ¾¹°ÒÒʺ¹¸»¹·´³³³²²¯¯±³³´³±°±³²¬§ –Іˆ‰“•Ÿ­´«—™ ££¢¥¯¶¹»½½½½¾¿ÀÀÁÅÈÉÊÌÎÎÏÌËÊËÊËËËÊÊÈÉÈÉÊËÌÊÈÇÉËÍÎÍËÌËËÊÉÇÇÉÉÈÇÅÅÅÃÁÁÂÀ¾¿ÂÄÃÃÂÁÀÁÁÁÁ¼»ºº»½»¹¸¸µµ···¶´µ¶µQB©¯­«¬­­¬ªZ04*+57F—§¦§¦¥¥¤§T6­ª®­­³”›½lA®ÃÀÀÁÂÃÃÂÁ¾¹´­¨¤¢ Ÿ¢¦«¯²´¶¹¼¿ÂÄÄÃÀ¿¼¼»¼½½¾¾½½¼»¼½¿½¼½ÀÄÇÌÏÑÑÐÏÎËÈþ¹³®©¥¡ ¢¤¦¦§ª­³·º»º¸¶²®¨¡™“•§¶»»¹³­¨’€†‹š¢§«°µ··¹¸²§š¢°¹¾¿À½¸®ÏÒξ¸º»º¶µ´³²³±­¬®±±³²°¯®®±¯¬©Ÿ‘†„‡ŠŒ•›—–ž££¢£­¶º»¾¼¼¼¼¿ÀÀÂÄÈÊËËÍÎÎÌÉÇÉÊÉÊÉÊÊÉÉÊÊÉÊÌÌÈÈÉÊÌËÍÍËÊËÊÈÈÈÇÉÈÆÅÅÄÂÂÁÂÁ¿ÀÀÃÃÃÂÁÁÀ¿¾¿¿¿¾½½½¼ºº¹··¸¹¹¸¶¶µ²LI®®¬¬­­­ª®œme=1bm¥£¦©¨¦¤¤¦O<¤¯¯­¯±²³·ªAjÆ¿ÀÀÁÂÂÂÁ¿¼·±«§¢¡ŸŸ¡§­°³¶¸º½ÀÁÂÂÀ½½»ºº¼¾¾½»º¹ºº»»¼½½¾ÂÅÉÌÏÑÒÏÍÍÊǽ¶¯ª¦¢ ¡¢¤¨¨¨«°µ¹º»º¸µ±¬¦ ˜“¯¸¸¸µ±©Ÿ…‚Š–Ÿ¦©­±¶··¸µ©Ÿ˜ž¦°·¾Á¾·¬ÏÐÏź¼»ºº·¶³²²°«©ª­®®¯®­«¬®°®¬§‹‚ƒ…„‡”–‘œž¡ ¡¨³¸»¼»¼½½¿¿¿ÁÅÇËÍÎÎÍÍÊÊÈÉÉÊËÊÉÉÊËÊÊÊÊÌÌÊÉÉÊËËÌÍÌÊÊÊÉÈÈÈÉÈÆÃÂÁÁÁÁÁÀÀÁÀÂÁÁÁÀÁÁ¿¾¿ÀÁ¿½½½½»»»º¹·¹¹¹·µ²¯FO°®­®°¯¯­¬°³¤H7–­£ ¥¥§§¥£¦ªLB«±±°±²³µ½‡:›Ä¾ÂÃÂÃÄþºµ°ª¦¢¡ ¢¦«°³¶·º¼¿ÀÀÁ¿½¼¼¹·¹»»º»¸·¶¸¹º»»¼½¿ÄÇÊÌÎÐÑÐÍËÈÄÀ¸±­¨£ŸŸ¡¢¥¨§¨¬´¸ºº¼»¸µ±¬¦ž””¦µ¹·µ²¬¤“‚…–¡¨¬¯µ···µ®¤œš ¨®¹¾Â¼µ«ÎÒÐȼ¼»ºº¹·¶µ³®©¤¢¤©«««ªªª­¯¯­ª£—‡€‚ƒƒ‡Œ‘Œ”›œž¨²·»½¼»»½¿¿ÀÂÅÇËÎÐÐÏÍËËËËÊÊÊÉÉÉÉËÊÉÈÊÍÍËÉÊÊÉÊÌÍÌÌÉÉÉÈÈÇÈÉÅÃÂÁÁÁÁÀ¿¿¿À¿¿¿¿ÀÁÁÀ¿¿¿¾½¼½½½¼¼¼»¼·µµ´³²±§;Q²¬­®¯­¬¬­¬®ˆ>8}§ £¤¦¨¥¥§ª®IC¯²²±²´¶¸¸XV½À¿ÂÂÃÃÅÂÀ¼µ°­¨¥¢¢¡¤ª­±´·¹¼¾¿¿ÀÀ¿¾¾»ººº»ºº¹¸¹··¹ºº¹»»¾ÂÆÊÍÏÑÐÐÍÉÄÁ»µ±«§¡žž ¡£¦§¨¬³¶¹¹»¹¸¶²«¤ž–¬¶¸¶³°¨ž‡…†š ¦¬´¹¹·¶³¬ ššŸ©±º¿À¿¼´ªÏÏÏÊ¿»¼»¹¸¹·µ²­© Ÿ £§©¨¨¨«¬®°±¬§ ’‚}|€„‰ˆ™™™›§²·»½½½¼½¿ÀÂÃÄÇÈÊÍÑÏÌÌÌÌÌÍÍÌÊÉÉÊÉÉÉÆÈÊËËÈÊÊÊËËÍÌÌËÊÉÈÈÈÉÈÆÄÃÂÂÁÂÁÀ¿¿¾½¾¾¿¿ÁÁÀ¿¿¿¾½½½¿¿¿À¾½¼¸·¶´²±±¥7W¶®¯°±¯­­®®¯—|z¦¢£¥¦§¥¨«®¯GJ³²³³µ¶¶¼›;†ÈÀÂÃÅÄÃÿ»²¬¨¥¤£¢¤©¬¯´·¹º½¾¾¿À¿¾¿¿¼¼»¼¼¼º¹¸¹¹¹º»¼ºº½¿ÃÇÊÍÐÑÑÎËÅÁ½µ°­¨¥ Ÿ ¢¢¢¤¦©®´¶¹ººº¸µ°©£›¡´¸¹µ±­¥‘„Д𠦱·¸¹¸¶±¦ž››¡«³¼¿¿¾¼´¨ÎÍÍ˺¼¼¼¸¶µµ²¬¨¢  Ÿ ¡¡ ¤¨«®°®¬© {|~€†Œˆ‰–™–—¢°¶¹¼¼¼¼¼½¿ÁÃÄÆÈÊËÍÏÍËÌÌÌÍËËËÈÈÈÈÇÈÇÉÊÉÉÉÊÉÉÊÌÍËÊËÊÈÉÊÊÊÉÈÄÄÃÃÃÃÂÀÀÀÁÀÁÁ¿¾ÀÀÁÀ¾ÀÁÀ¿¿¾¾¿À¾¾¼¹¹¸µ´´µ¤5c¹®°±²¯­®°°°²µ²ª¥¥¦§©¨§©¬±°EP¶²²³¶¶·¿oE³ÅÂÃÄÆÆÅľ·¯ª¥£¡£¥¦«°²µ·¹¼¾¿ÀÁ¿¾¿¾¾»»»»»»¹¸¹º¹º½¾½¾ÀÂÅÇÈËÎÑÒÏÌÉÄ¿º±¬©¦¤ ŸŸ¡¢¢£¤©¯³·¸¸¸·µ³­§¡™˜«´¶´²®¦œƒŒ—¤«·¸¹·¸´ª¢™šž¥®¹¾¿¾¾¹²¨ÑÐÒ̹¼¼¼¹·µ´°«©¥¡¡—•——›ž£¦ª­®°±¬¢‹~zz|}€ˆ‡…“”““Ÿ®µ¹º»½½¼»¼¿ÁÂÅÈÊÌÍÍÌËÌÌÌÌËÌËÈÇÈÇÆÈÇÉÊÊÊÊÉÈÉÈÉÊÊÉÉÉÉÉÉÊÊÉÈÇÄÄÄÄÃÂÀ¿ÀÁÂÀ¿¿¾ÀÀ¿¾¿¿ÀÀÁ¿½¾¿¾½¾»¹¹¸¶´µ¶ 6p¼ªª³²¯¬­°°°²¯­©§¨©©©¨¦§«²¯AWº³²´µµº²DlÇÁÂÃÅÇÆÅÃÀ»´¯ª¦¥¤¤§©­±´¶¸¹¼¾¿ÀÀ¿¾¿¾»¸¹º»»ººº»¼¼¼¿¿¿ÂÆÇÈÉÉÌÐÔÒÏÌÇÁ»µ°«§£¡Ÿž   ¢¢¦«°³µ·¸¸µ´²­§ šž®²²±±©¢{‚„Œ”¦²·¸¸¸¶±§žšœŸ¨µ½¿¿½»¸±¦×ÔÓÏȼ¼»··¶´²°¬©¥¢¡œ”Ž”™œ¡£¦©¯°¯® ‹yyy{|~„’ž¬±µ¹º½½¼¼¾¿ÀÂÅÈÊÏÏÎÍËËËËÌÌÍËÉÈÇÆÆÆÈÉÉÉÉÉÉÊÉÈÉËÌËÊÉÉÉÉÉÊÉÇÇÆÇÆÅÅÃÁÀ¿ÁÀÀ¿¾¿ÁÁÀÀÀÀÀ¿À¿¿ÀÀ¿¿¿º¹¸¸·¶¶¹š2yÁ™m¹³²}£±¯°°°®«¬«¬ª©¨§ª¯³­<^¸²²µ¶¶Â8¢ÈÄÄÆÇÇÅÃÁ½¶°¬©§§§§«¯²¶¹ºº¼¿ÀÀÀ¾¾½¾¼»¹º»»»»¼¼½¾¾¿ÂÁÂÅÇÈÊÌÍÐÑÒÐËǽ·±®«¨¤¡ŸŸ Ÿ ¢¤§¬°³´··¶´²°¬§¡œ¥®°°°¬¥™…€ƒ…Ž˜¢®¶¹¸¸ºµ¬¤œœ¡ª¸¿¿À¿»¶®¥ÚÖÓÐʽº¼ºµ´²°®­©¦£¢œ•Љ‹‹“•–ž £§©¬ª§œ†{yz{z~|‡Žª¯´¹¹¼¼½¾¾ÀÁÁÅÊÍÐÒÑÎÌËËÊÊÊÉÉÇÇÆÅÆÇÇÉÈÉÉÈÉÊÉÉÊËÌËÊÉÉÈÈÉÉÈÇÆÆÆÆÆÅÂÁÁÀÀ¿¿À¿ÀÁÀÀÀ¿ÁÀÀÀ¿¿¿ÀÂÁ¿»º¹º¹¸¸¼”.|Á²Ro‚wc²±±²²°®¯¯®®«¬©§®³¶ª;c¶±³·¹½Á]ZÈÅÆÇÈÇÆÂÀ¾º³­©©¨§§«®²¶·¼»¼¿ÁÀÀ¿¾¾½¼»»»»¼¼¼¼¼½¾½¾ÂÄÃÃÅÇÉËÌÎÎÏÏÌÇÁ½·±®«©§¢ž ¥¥¨­±´´´¶µ³²¯«¨¡¦­®®¬¦¢€„†Œ•Ÿ©²¸¹¹¹·¯¥Ÿœ›œ¤®¹¾ÀÀÀ»´«¤ÜÙÓÎÈÀ¹º¹·´±¯­«©¦¤¤ ˜‹ˆ‡ŠŽ“——›Ÿ¢¡¤¢•}yyyxywŠŒŽ™©¯´·¸¹º½¿ÀÂÂÂÆÉÍÐÑÑÏÎÌËÌÊÉÉÈÇÆÆÇÇÈÇÉÈÉÉÊÊÊÉÉÊÌÌËÊÊÊÉÉÉÉÈÇÅÅÅÆÆÅÂÁÂÂÁÁÁÁÂÂÂÁÀÀÂÂÁ¿¿¿ÀÁÁÁÁ¿»»»»ºº¸½“.ƒ¿¼iPsBй²²³²±°±±¯­««©«±³µ¦7f¸²¶¹¼Ä¨A“ÌÄÆÆÆÅÄÁ¾º¶°©§§¨©ª¯±´µ·»»¼¾¾¿Á¿¾¾¼»ºº¹»¼¼¼»º»¼½¾ÁÃÃÄÆÇÈÊÊÌÍÎÎËÆÀ»µ°¬©§£ œ›œž¢¥¨¬¯²³³³´³³±­«¦¡Ÿ§«¬­©£™…€ƒŠ‘›¦¯µ¹¹¸·°¨¡œš›ž§³»ÀÀ¿¾»´ª£àÝÖÍÆÀ¹¶µµ³¯­«ª§¥¤¥¢˜††…ˆ‰ˆŽ’˜—Œ|yyxvsz†ˆ–§®±µ¶¸¼½¾ÁÃÄÅÇÊÏÒÐÏÏÎÍÎÎÌËÌÌËÊÇÇÉÉÈÈÇÇÉÉÉÉÈÇÉÊËÊÊÊÉÉÉÉÉÈÅÅÆÅÄÄÄÁÁÁÁÁÁÁÂÂÃÃÂÂÃÃÃÂÁ¿¿¿À¿¿½¼»¼¼¼»»¸¾Ž/ˆ»¸dž_ª³³²±¯°±²³²¯­««®²³µ¡2h¸²·½¾ÄpRÀÈÅÃÅÆÃÁ¾¾º³®©¦§¨ª­±²µ¶·º½¾¾½¼½¼¼¼ºº¹¹¹º»º¹º»º½¿ÀÂÃÄÅÆÉÊÊËËÍÍÊÇý·±¯¬¨¥¢ž›››œŸ£¦ª®°±±²²²±²±®«¦Ÿ §©ª«¥Ÿ‚…Œ”¡ª²·¹¸¸³¬§Ÿœ›œ¡ª·½À¿½¼¹±©£ÚÙÔËĽ¸¶¶´²°®¬«¦¢ ¤¤ š“‰„ƒ…ˆˆ‡‡‡‚€~‡‹Ž„{yxrlxƒ„Š”¦¬­°´º¼¼¾ÀÂÃÆÉÌÏÒÒÐÏÏÏÎÎÍÍÍÌÌÊÈÇÆÇÈÈÇÆÇÉÉÈÇÇÈÈÈÉÊÊÉÈÉÈÉÈÇÆÆÆÄÄÃÀÀÁÁÁÁÁÂÂÃÄÃÄÄÅÄÄÂÀ¿¿ÀÀ¼»»»»»»¹¹·»Š.ޏµªQa~·±²²°®¯°²³³²°°¯²´´±˜0l¸´º¾Â°EˆÍÄÅÃÅÆÃÀ¾»·²­ª©©ª¬¯²µ·¹»¼½½¾¾¼»»º»»º»¹º¼¼½»»½¼¾ÁÃÄÅÆÇÈÊËÌËÌÌÊÇÿ¸³­©§¦¤ šš›œ¡¥¦ª®¯°²³²±¯°¯­©§Ÿ §¨¨¨¡˜…ƒ‰’œ¤®¶¸·¸¶°«¥ž››ž¦°¸¾ÁÁ½¼·¯¦£ÉÉž»¸¶·´±¯­­ª¦¡¡¢¢¡œ–Œƒ€ƒ…††„€{yxwx~€|vvtlv„‚†”¤««°µº¼½¾¿ÀÂÆËÏÐÒÓÑÏÎÏÎÎÎÍÌÊÉÉÈÈÆÆÇÇÇÈÉÉÉÈÇÈÉÊÉÉÊÈÈÈÇÈÇÆÆÆÆÆÄÅÁÀÀÁÁÁÁÁÃÃÃÃÃÃÅÄÅÄÁ¿¿½¾¾»»»»»»º¹º·¼ˆ-¹¯µ_@¨µ³²²°¯±±²³³³³´µµµ²²+r¸¸¾¾ÇzL¸ÅÃÄÅÅÄÿ¼¸µ²®ª«¬¬¯²µ¹º¼½½¿¿¿¿¿¾¾¼º»½½¼¾¿¿¿½½½¾¿ÂÄÆÇÈÉÊËÌËËËËÉÅÁ¼µ¯©¨¤¢Ÿ›˜™ž¡£¥¨¬¯±±±²°¯­­«©¤ž ¤§§¤Œ‡Œ˜¡«µ¸···´°© š™› ©²º¾¿¿¿¼´ª¤£¼»¹¹¹¼»µµ²°®¬¬ª¦¢¢ žš•†€€ƒ…ƒ€{wtstwvwvtrojq‚ƒ‚¡ªª®´·»¾ÀÀ¿ÂÅÊÎÐÑÑÎÏÎÍÍÍÌÌÌËÉÈÇÆÇÅÆÈÈÉËÊÊÉÇÈÊËÌÊËËÊÉÇÆÅÅÅÅÅÅÅÄÁ¿¾ÀÁÁÁÂÂÁÁÁÁÂÀÁÂÁ¿¾¾½¼½½¼»¹¹»º¹¸¶¹‚-й«­†t³®­®°¯¯°¯±²³³²´¶´³²´Š,~À»¿ÀµItÇÁÅÆÇÆÃÀ¼º¹´°¬ªª«­±´¸¹¼¿¿¿ÁÂÁÀ¿ÁÀ¿½½¿¾¿ÁÀÀ¿¾¾¾ÀÂÅÅÇÉÊËËÌÌËÉÈÇÆÄ¾¹±«©¥¢Ÿœš˜šŸ¡¤¦¦©¬®®¯±±°°®¬¬«£ž¢¦¤ –†€ƒ‰“œ¦°µ¸µ·¶³ª¢œšš¥­·¼¿¿¿½º±¥££³±³³µ·º·°°¯­««©¦¡ Ÿœš˜—”Œ€‚}ytrrrrqoqpmifm‚‚‹Ÿ¬©«±·¼¾¿ÀÁÄÅÈËÎÐÏÏÎÌËÌËÊÊËÉÆÅÆÇÆÆÇÈÈÈÊÊÉÈÈÈÉÊËÌËËÊÊÈÆÇÇÆÅÅÅÇÅÂÀ½¾ÁÂÁÂÂÁÀÂÁÁÀÁÁ¿¾¼½½¼½½»º¹¹»¹¸¶³´’.W§«©¬±°®­­°°®¯±²³´²±±±³²²¼‚2–Á»½Å–:£ÈÅÆÅÅÅþ»¹¸´°­ª©ª­±µ¹¼¾ÁÁÂÃÂÃÄÄÄÄÃÃÃÁÀÁÂÁÀ¾¿ÀÁÄÅÇÆÇÈÊÌËËËÊÇÆÄ¿¹²¬¨§¤¡š˜™›œŸ¢£§ª­®®®¯²²°°®««§£››Ÿ¢¡™‚‡™¡¬´¶¶¸¶³­¤žœ›ž¨²¹½ÀÀ¿¼·®¤¢¢®®¯°²µ¸·±­­«ª©©¥Ÿœžœ˜““„}€}vtrppnkkmkihch{€ƒŠ›«««¯´¹¼¿ÀÃÅÇÊËÍÏÐÏÌÍÌÊÉÉÈÉÈÅÅÅÄÅÇÈÈÇÈÊËÉÇÇÈÉÉÊËÌËÊÉÇÉÈÈÈÇÅÆÆÅÂÁ¿¿ÁÂÂÁÁÁÁÁÂÃÁÁ¾½½¼½½¾¼»ºº¸º»¹¸¶´²¯T'BWcltz‡”› ¥§©©®¯°¯¬¯®® RI³À½½ÆmLÀÅÇÆÅÄÿ¼º·µ²®«ª­¯²¶»¾ÁÄÄÅÇÅÅÇÇÆÇÇÆÅÃÁÂÅÃÁÁÂÂÄÅÅÆÆÈÇÉÊËÊÉÈÅÅÁ¾º´®¨¥¦¢Ÿš™˜šœ £¤¨¬®¯°¯®°±°°®¬©§¢™œ ¡›ƒ…‡’¦²·¶·¶³®¦¡ž››¢¬´»¾ÀÀ¾º´©¢¢¥®®®®¯²µ¸´­«ªª¨¥ ™š››˜“‡€}~~|zxuromkjihggeaev~‚Š›¥§ª­±µº¿ÀÃÅÉÌÎÐÑÑÎÌÎÍËÊÉÉÈÇÅÄÄÄÄÆÇÉÈÆÉÈÆÆÇÈËËËÊËÌËÊÈÊÉÉÉÈÇÇÇÇÃÂÁÁÂÂÂÂÁÀÀÂÂÂÀ¿¼½½½¾¿¿¾ººº·ººº»¹µ²µ¦eD;7630*0348;X‚Ÿ®¶¸··¸º¾ÃÇÂÂÃÂÅdZÇÇÆÆÅÿ¼º¸³¯¯¬ª«°´¸ÀÆÈÉÊÊÊÊÊÉÈÇÉÉÊÊÈÆÅÅÅÅÄÄÅÇÇÇÉÉÊÊÊÊÊÉÇÅÃÀ¾º·³¯«¦¤£¡œšššž ¡¢¤¨­®­¬«®±²±®­«©§¥Ÿ•˜˜—‡}€„Œ•£®´µµ·µ­¦£ œ™› «³¹¾¿¾½¼¹²ª§¢¥ª¹·³¯­¬®®±·®¢Ÿš˜––”’‘’“‘†|{{zurplkifde`XQVlw}„‘ ¦§§©¬²¸¿ÆÊÍÐÐÐÎÌËËÌÊÈÆÄÃÂÀÁÁÀÀÂÃÄÅÇÇÆÇÆÆÇÉÊÉÉÊÊÉÉÈÉÊÊÌÌËÊÊÉÈÅÃÄÅÅÆÅÆÅÄÂÁ¿¼½¼ºº»»¼¿¿½ººººº¹¹¹··¶·µ³´³²³³³´¶·®•nO::GdЧ¶¸¹¹º»½ÀÂÂÆ¯EŠÏÆÆÆÄÁ¿¿¼¹·²¯­««®±¶»ÂÆÈÉÉÉÉÉÊËÉÊËÊËÉÇÅÇÅÆÅÄÆÇÈÇÇÇÈÉÊÊÉÉÇÆÃÁ¿¼¹¶²®«¦¡Ÿ›™˜˜šœŸ£¤£¤«¯¯¬«¬¯°²±­¬ª©¨£›“˜—’}…ˆ‘œ¨³¶³·¶²¬¨£žš˜ž¦°¶»¿À¾¼»·±¬§¥§¬»º·²¯°®¬«°¶§ ›š—”•”‘Œ‘’“Žz{zxuromlgdaa]XRPgv{ŽŸ¤¥¤¤¦¬°¸ÀÇÍÏÍÍËÉÈÈÇÆÆÃÀÀÀÁÀÁ¿ÀÁÁÃÄÅÅÆÇÆÈÈÊÈÇÇÈÊÉÊÉÈÉÉËÌÊÊÌËÉÅÄÆÆÆÆÅÅÄÂÂÁ¾¼¼»¼»º»»½½¼»¼º¹»¹¹¸¶µ¶µµµµ´´´´´µ¶¶¸»¼¯—wP;2;X{˜¯¼ÃÂÂÁ¾É{H¶ËÈÇÆÄÃÀ½º¶²²±¯®®³µºÀÄÇÈÉÊÉÈÈÉÉÉÊÊÊÊÈÆÅÆÇÇÇÈÇÉÈÇÆÆÈÉÉÉÈÇÆÄÂÀ½¸¶µ±®ª¥¡˜–——šœž¡¥¤§«­¬©«®°±±¯«««ª¨¢˜“•”…|‚†˜¤­´³µ¸¶°¨¤š™›¤¬³¹¾ÀÀ½»ºµ¯«¥£¨¯»¹ººµ°¬©§§¯²¢›–““’’ŒŒ‹Ž‘“‘Œƒ|xwuroojfca`\XQK^sx}ŠŸ¡¤£¢¤¤¥¬²¹ÁÅÅÄÃÀÀÀÁÁÁÀ¿½½¿¿¿¾¾¿ÀÁÂÂÀÃÄÅÈÈÈÇÇÆÇÈÈÉÉÉÈÉËÊÊÊËÌËÈÆÆÈÈÆÄÅÄÃÂÁ¿¾¾½½½»º»½¾¼¼½»¹¹¹·µ´³µµµµµ¶µ´µ¶¶··¸··º¿À· bJ:?:–ÖÌÍÍÍÌÍÊÊÒºl>VŸ¢†IFHLMKMLOT]ds‹‹‘—š›Ÿ¢¥§ª¬¯±´¶¸¹··¸´¯¬¨¥£¡ žœšššš›››œœžž¡£¤¦¦¦¨©©¨§§¦¦¥¥¥¤¢Ÿ›’|{¡ª©ª««¬¬«ª««­­­­¯¯¯°²·¹¸·³­¨¥¡¡¢¦©©©«««™Š†„}z{}~€ztqppqrrsvuxxz~€‚‚ˆ•˜›œž  žŸ™‚s_RQaq~Œ”˜›—’“•––˜™ž£¤¤¥¥££¥¥¦¥§«­°²µ¸¹»»»¼¿¿ÁÃÄÅÆÆÇÇÇÈÌÏÒÒÑÐÏÎÏÏÐÐÏц;C>>>¦ÐËËËËÊÊÎÊ•E-:<=9QžÎÈÁÆi'..4>IVfs”˜››’‹‰¹ÄÃÉ—/*'&(&'$"!!6›š™¡a!9q•›œž £¥§«­®²µ·¹»º¹¸µ°«¨¦£¡ŸŸ›››œœœ›žŸ¡£¤¥¥¥¦¥¦©§¥¥¦¥¤£¤ Ÿœ˜ƒ™©¨©ª¬«ª«ªªª©©«««ª¬­­°³³²¯«©¦£ŸŸ¢¥¦¨©©««•‹‡ƒ|xxz{}~~ytpopppqrsttvwwx{}}~‚ˆ††‡ˆ‰€vfVO[n‡’–—™–‘ŽŽ””••”–šžžŸŸž ¡¢¢¢£¦¨ª­°²¶¸¸¹º¼¾ÁÂÄÅÅÆÇÆÆÈËÏÑÑÐÑÏÏÏÏÏÎÍÐw:DA?@­ÎÊÊÊÉÌÒ¯c.3<=?:h¸ÑÉÄÀÄc243310.--/1/1552215¨ÇÂɈ140.,*(''&#""!!  9˜™–›Z " EŽª£§©«®¯±²³·¸¸¹¸·µ°­¨¥£¡¡Ÿš™šš›œš›œœŸ¡¤¤¥§¨¨§§¦¥¥£££¤¦¥¥¥¡Ÿš•€’¦¥§©ª«ª¨¨§¨¦¤¦§§¦¦¥¨ª­­¬ª¨©¨£žœœŸ£¦¨©ªªªš—†€~|ywxyy{}{uqmlmmmnooppopqsrsvvrutqqqrrnnj_UKTl‡””•––’ŽŽ‘’’’”——™™›  ¡£¥¥¦¨©¬­¯³µ·º¼¿ÁÅÅÄÅÆÈÉÊÍÐÑÑÏÐÎÎÏÎÎÌËËk;B@=C´ÌÈÊËÑË„80:=>9B…ÈÑÇÆÃÀÆ^43225689:9877778:<@®ÄÂÊ~.1/+*)'%$#!"! "! @™˜™N!#$%-†¯ª­°±³µ´µ¶¹¹¹¸¶²­©¦¢¡¡žœšš›š››™—˜™œ ¢¥§¨©¨§§§§¦¦¤¢£¤¥¤¤¤ ™˜¡¤¦§©©¦¡ ž™––™ ¡¢£¢£¤¦¨§§§§¨¦¡œ™šž¡£§¨¨©©‘“‘ˆ€|zzwuuvxy{xsmjiiiiikjllijjjijljhggfeeecb]WMIMe{ˆ˜•““‘‘ŽŽ‘Ž‹’••–™šœž ¢¢¤¢¢¥¨ª­°²´·º¼¿ÁÂÃÅÆÈÉÌÐÐÑÑÏÎÌÍÎÍÌÊËÇ[DC@9I»ÊÅÅγc.4=@<9V£ÐÍÆÇÇÃÁÃU.123242024467:9797=¯ÀÁÉs.1-*%*71/.,)(#" HŸ™”’C(%#"!%$&($.”³°³´µ·¶¸¹º»»¹µ°«§¥£¢¡ž›š››œœœ›˜˜™ ¢¤§¨©©§§¦¤¤¥£¢££¢¢¢¢Ÿ›š—’™¡¤¥¦¥¢œ—‘‰€~€‡˜žŸ¢£¡¡¦©©¨¥ ›˜˜›¢¥¤¤¦¦§_ˆ‡~zyxurstwwxtlifeecccedbcc`b``ac``_^^\ZYXTPF@I_x„˜—”’‘”’Ž‹ŽŒ’’’”•™›ž ¢¡   ¡¤¦ª¬®²µ¶¶¹¾ÀÃÄÄÆÇÊÏÐÑÐÎÍÌÌÌÌÉÈʽL<@@8SÀÃÃÈ”D3::;:;t½ÎÇÆÅÅÄÁÀ½K034.B€yl\TJ:788669Z·½¾Åi10++&v “•“Œ…{tmffdx•“ŽŽ@(x†‚~vY3"$(*-&A«·¶·¸¸¹»»ºº¹¶±­©¨¥¤£¡ žœœœžžžœ›šœŸ¢£¦¨ª«ª¨¦¥¤£¢¡¡¤£¢¡ Ÿœš—”“𡣦¤Ÿ™‡ƒyuuu‡’™›œ ¤££¨«¬«¨£Ÿš–™Ÿ¡¡ £¤£¢4^†……‚}{zwtqpqsutmifa`^^][XY[WVTWWWXYXYWUSQOMID;34]w‹“——”’’‘ŽŒŽŒŠŒŒŒ‘’’•–—™Ÿ¡¡Ÿ ž ¡¡£§ª®±³µ¸»¼¿ÃÅÅÄÇÌÎÏÎÍÌËËÊÈÇÆÈ³@48:6U½Â¾s24<881DËÇÀÀÂÁÁÀÀÀ·B256/_ËÇÉÄÀ·¦—”˜©¸¾½¿Äb)-+++‘²©§§¥¢¡Ÿ¡¡žžœ˜“‹47 ¡¤§«®›Q&*,,.'{¾¹»º¸º¼»»¹·²°«¥¦¤¥£¡¡žžž ŸŸŸœœž ¤¥§©ª«ª¨§¦§¦£¡¡£  ¡ œœš–”•› ¥¦£Ÿ•Šytrtv}†˜œ¡¦©ª¬±²±®ª¦£Ÿ¢¥¤£¢¢¢¡ž@5`†ƒ|{vwurooqrtqifc`^\XVURQPMNOOPRRPPOLKID@7,!T}‚ˆ•–”“‘’’‘‰‡‡Š‘“”–˜—šœœžžžž ¤¨«°µ·º¼¾ÂÄÆÆÈÊÍÎÌËÊÈÇÅÃÂÂÄ©93540O¾°V-68760S¤ÆÁ¾½½¾¾¾¿Àó?3451jÈ¿ÀÂÃÃÄÅÌÎÉÆÃÁ¿À¿Â_,.,()’«¤¡Ÿž›š›˜˜˜—•“,= ¡£¥§³«P(..0,O¶»»»»½¼ºº¸µ²¯«¥¤££¡ ¡œžŸ  ŸœœœŸ¤¨¦¦§©ª©§¨¦¦¤¡  ¡ Ÿžœ›™˜–“”›¡¥£š“‡{sqnnpuz‡’›¡¥¨ª®°±¯®ª§¤Ÿ¡¢£¥§§¥¥£ŸD@<^ƒ~~{xwutppprrqoida][YVSPPMHFECDIHHGEB@:0!P|„‡Ž“•–•“”––”‘ŽŒŠ‡†ˆŽ‘“–––˜™™™˜™š››œž ¤¨«®´·¹¿ÃÅÅÆÈÌÌËËÆÃÀ½ºº¹¸»™-./.(LŽA&53350d²Ãº¼¼»»¼¼¼¾¾Á¬>4352nÆ¿ÀÀ¿ÀÀÃÄÃÂÀ¿À¿¿½½X++)&*‘¦¡Ÿœ›————˜š™”’†'F¥Ÿ£¥¨©«¸œ6,/12<¤À¼½¼½¾¾¼¹¶³®©¥£¢ ŸŸžžžžŸŸŸŸŸ›œ¢¨¨¨¨©ª¨¨§¦¦¥¢ Ÿ ¡Ÿœš—•–”’”šš‘ƒ{qnnlmmptx{ƒŽ˜ ¡£§ª««ª¥£¢ žœžŸ¢¥¤¤£¢¢DCA8f||{zutsqnnnmnnjf`][XVRQMKHD>;99<=:3+' Hy‚†Š“”””•””“‘ŽŽ‹‰ˆ‡ˆ‘’‘’”“’”––——˜™š›œž¤§ª®°¶¼¿ÀÂÆÈÌÌÉÆ¿¸²®®®¯°µŽ%)'()+."-.-,1l·º´¶¸¸¹»¼½½½½À§84493qĽ¿ÁÀ¿¾¿ÁÁÂÀ¿¿¾½»ºQ%'%#'ŠŸ›™šœ›–•—™˜˜•‘Ž€#M§¢§©«¬®²·Q*//34“Á¼¾¾¿¿À¾¹¶°ª¦¤£¢ žžŸ   Ÿž ¥¨§©ªªªª¨¥¥¤¢¢ŸŸ Ÿœš™˜•“‘”—“…}tllmjmmptsux‰“™™œ £¢¢›‘‘—œ›™š› ¢¢¢¢¡BCA;=jxwyvtrromllljjhb]ZXUSPMJGB;7542( D|ƒ„ˆ“•–—•’‘ŽŒŠ‹Œ‹ŠŠ‹ŽŽŒŽŽŽ’““““•—˜™˜œž¢¤§ª±¶¸»ÀÃÅÉÊÅ¿µ­¥¡¡¡£¦¯€!&%%%$%))++.y¹¸±²³¶¸¸¸»½»¹ºÀŸ53242tȾ»¼ºº»¾½¼¾¾¼»»º¸ºM%&#"%‡˜–•—–•––“’’”Œ‹Žw\ª¤©­®®²´»_(1263–ýÀÀ¿¼¹·´±¬§¤£¢¡¡ŸžŸ ¢¢¡ Ÿœœž ¤§¨§©«©©¨¦¤¤£¡žž Ÿœ›™˜—•“‘ƒurkijkloqqtstx}†Ž“˜™š˜ƒ||ˆ•˜––˜šž¡¡¢¡¡CCC@7?kuvxurqnlkkjjlhc^ZWTRPMIE@<851( =|‚……ˆŽ”•–•”‘Œ‹Š‡‰Ž‹‰‰ŠŠ‰‰ˆ‰Œ’’‘””“’–™œ £¨®±¶¹¾ÁÄý¶ª¡œ˜˜™›Ÿªp!!!!"$%&&%T¶¯­¯°²³µ···¸¶¶¶½‘/100.R˜¤®·º¼ÀÀ¿½¼»¼¹¸··¶H"$ #‰¢›œ™–’‘”•’‘‘ŠŠŒm e¯§ª¯°±¶¹¿Y/0588¤ÅÁÀ¾½·´²°«¦¡¡¡¢¡Ÿ  ¡¡¢£¢¡ žŸ¢¦§¨§©©§¦¤£££¢Ÿœž ž››š˜–”’މ„tolkiiklnqrsusx~ƒ‰ŒŽ’‘‹€tpqŽ““•–šœžžœ›œCBCA=8>outtrqmmlijhiheaZURRQMID>975(  8x}ƒ„†ŠŠ”•“‘ŽŠŠŠŠ‰ŒŒ‹‰‡†…„„ƒ†‰‹‹ŽŽŽŽ’–›¡¦©­±³¶¹´±© ›—•••˜›¤h#  ""$$3›®©­®¯¯¯¯¯°³´´²»„*--,,'(1>Lau…˜Ÿ¡ §¼¸´±E#!FTSYYZ`dagigkpŠ–c " p±«°³¶¶·¼¬A6346F·ÄÁ¾¼º¶´³®¨¢¡ŸŸŸžžŸ¡¡¢¢¡¢ Ÿ  ¤¤¦¦¥¦§§¦¥¢¢£¢¢ š››˜•”“‹€{vqnkihhkmnqrqtv{‚„‡‰Œ…wpor|‹’’•–™š›š–““AA@@>=5Bqrrtqnmkifffffc]WSROMHB=74* 0r|‚ƒ…ˆˆ’”ŽŒ‹‰‰……‰‹Š‹Š†ƒ€€€ƒ„…†ˆ‰‡ŠŠ‰ˆ‰ˆŠ–˜œŸ¢¦¨««©¥™–”‘”––—ž`5 !";ž§¤¦§¦¥§¨©©­®¬µt$,+)*+(('&$'/028:;5„Áµ±«? @“’“›_ "&"z´¯´·¸¸»Ãl59573tÄ¿¿½»¹¶µ²­¦¡Ÿ ž›œŸ   ¡¢¡ ¡¢¤¤¥¥¥¥¥¥¤¤¢¢¡  žš˜˜š˜“‘Ž‹|trqmlighmlnpqruwz}€ƒ‡ˆytomrx~ˆ‘”˜š›™—‘Šˆ@@@?<=;1Jpprqnmkifcacca`[UUPMFB<9+ 3m|…„†ˆ‰Š‹ŠŒ‹Š…‚„„ƒ‚€€}}}||~~€€‚ƒ€ƒ‚€ƒ‰Ž’–˜™ £¤¤¡œ—•““–––”šQeTM£››Ÿ¡¡¢¥¦¦§§§­e))(*+,+,./25446872‰À³­§>Dš˜š¡^##&$µ°µ¶¸½Å~/9875=®Á½½¼»¹·´°©¤ŸžžœœœœœŸŸŸ ¢¢¡¡£¤£¢¢¤¤¤£¢ žžœœœ™˜˜–—”‘ˆzupljifehkkmooqtwz}~€}yslknv|€†‹”˜››š“Šƒ???>=:75,Nnqppmjgedaa_]^[XQNHB@8$ +  )jx€‚„†ˆŒŒ‹ŒŒŒŒŠˆ„~~|xwz||}|{z{{{z|}~{z{z{|~€ƒˆ‘“–™Ÿ Ÿ™—“‘“–˜•–Io˜2j¡–šœ™ ¢¡£¢£­U%%'$)'%'+,.0214785޼°¨£8 GŸž¨\#&%(&‡Àº¿À¿ªj/7;:82ŠÇ½¾½»¹¶³®©¤Ÿœ›››››œ›œŸžž ¡¢£££¡ ¡¢¤¤£¡ Ÿœ›žŸš••••“‘ŽŽŽ…ytpmhfeeeilmnpswz~}}|zvvnikrx~~‚Š•›ž›–Ž…w?>>=<:85/)Kpllkihfcb_][ZXUOIE@. + %ct{ƒ…†‰‹‹Š‹‹‡…‚€|{zxuwxzywyxvwxwwxxuttuwx||{}€ƒ‡Œ“—›œ—–’Ž‹Œ”–‘Cq™~ x™’”–š››œžŸ¦F!"#)~ŠveTJB:965767’·©£›0K£ ¤¨V$&&)#^ˆƒzs[:+49=<1xƽ¼½»·³°¬¦¢Ÿœšš™š›š›œžŸžŸ¡ŸŸ¡¢ žž¡¢¡    ŸœŸžœš—‘‘“‘Ž‹ŒŠ‚vqmlifdcehkmnsy}yxvvtpghmrx||€Š•šœ•Š‚{w?==;;8641-*Gjkgffdcc_]YVRLLE, + + %_ryƒ„ˆˆŠ‹‰Šˆ‰ˆ„ƒ‚}{zyxvwxwvuvvttttttqorsstwwwww{~…‰•—›š—”ŒŒ’–‘:m‹‘g&„Œ’”•–˜˜™›œ7!! 2¦¸¹»º¸²«¦¢œ™––¨¬¡š+*UTTQONKIHIIKJrª¤¤§N$''))&))%(*/34:6:}Á¿¾¼¼¸³¯«¦¢ž›šš™™š™››œžžž  žžŸŸ ¢¢  Ÿžžœ›œ™–”’’‘ŒŒŠ‹‰spmkidcdfiilqz‚…ƒ}wuusrqkchoqv|€†”›ž—’†xw><;;:54411/):_gfb`ab_ZUMKI:!  + Wrw}‚……‚†‡„‡ˆˆˆ†„€|yzzvwvuutvtssrsrqompqptutwwvwy€‡ŒŽ–›™•“Ž‘Œ6n‡…‰J6Œ’“”—˜—‘.7Ÿ¦«²µºº¼¾½¿¿¾º±¥š“‚%C”‘‘”••—œ£¨§¥¤ªL$((*,-020234213PŸÊÀ¿¾½º³°¬¨¤ œš˜™˜™™™™šš›š›ššššœ›œž ¢¡¡ Ÿž››››š™–•“‘ŽŠ‹‹Š‰‡~vtpnhcbegklqw„ƒxvtqonlhdhnow‡’ ž™ˆƒ{ww><::85434311,1Oa^\\ZVPNL7 + + Lqx|‚ƒ‚‚‚„…ˆŠˆ‡‡‚{{ywwvtuutttrqsrrpooqprrqssttx|†‡–—–“‘‡‡†ˆ‰‰6o~|}‚4G‹ˆŠŽ‘’’”‡%:˜š¢§¬°²³³³´²°¬¥šŒw J‹ˆ‰Œ‘’–—™œ¡§¨ª©¬M&++-/-+.,-1/?b‘ÁÌÂÁ¿»¸±®¬¨¤¡žš˜—˜—–“’“””’‘••”’’–—–™¢¡Ÿš˜—˜˜–“““’‘‹ˆˆˆ‰ˆˆ„}|ysnidbdhmu{ƒˆ„€yuuspolhdefimw…“›¡¡Ÿ™”ƒwuw<;9877655331/.)?ORRQLIA"   + LVas°ÂÉÅÁÁ¿¹·´¯¬©¥ œš•“‘‘ŒŠˆˆŠ‰ŒŽŽ‘Ž‘•–˜˜••–“’‘”“Ž‘’Œˆ‡‡††‡ƒmtxslfcaensy†ˆ†{tpqonkecfghio‚“¡Ÿ›•ˆ‚|xwz;::877442320.--3?A@EG5 + *p{~ƒ„„„……†††††ˆˆ…~€€}{wvtqrrqrnqqqpnmnoppponpqruxz~†Ž’“’‹ˆ„‚„„/"uƒ‚~{€fp‡ƒ……ƒ‡t>‹ˆ‰‰‹ŽŽ‘’Šˆ„€€…a]‡†‹ŒŽ•–™Ÿ¥«­¯±³¶Q-16;<ŸÁÄÉÌÍÏËÅÃÿ¼¶²¯«¨¢™–’Ž‹Šˆ‡ˆ†…„ƒ„„ƒ‚‚…†„ƒƒ„†ŠŒ‹Š‹Œ‹‹‹‹‰†„„ƒƒ„‚aanmjeabhmu|‚„…‚{tqoomlhb`cgiiju„”š—”Žˆ„‚zwz€97788842310/,0;A@;8=4   + $c{€‚„„„…‡††‰‰†…‡…€€~}{xwurqsspnoooonnmnoooppqruuwz}‡Ž’’’Œ„})!wƒƒ€{Y(ƒ~€€~‚jDƒ~€ƒ‚ƒ‚‚€~}~‚Ueƒ†ŒŒ“–—ž¢¨­±³µ´µQ2369H½ÍÉÊÉÇÇÇÆÄÿº·²®ª¦¡œ—‘Š…„€€€}}|}}}|zzzz{z|}|ƒ‚ƒ…†ˆŠ‰ŒŠ‡…†Š‰‡ƒ‚‚€~€‚€bYdhecabgkxƒ„|upnnmmjd__cfhhjmr|‡‹‹ˆ‚€}ww‚5676665332/,1BGDA:90  + _}‚„……„‡ˆˆ‹ŒŒ‹‡†„‚€~}|{zwutttpopnoopqpprrsuvxwxy{{}‚‰’’‘‡v%&w€~~{y‚G=…~||z~_H…|}€}{||yxxz||}„Ks‹‹‘”˜›ž¥§«°´¸¹¸´J1344J¹ÆÇÈÉÈÈÈÆÃ¿»¶¯ª§¢œ˜”ˆ…ƒ~|zzzxwvxywvwvuuvtvyz{y{~~„…ˆ‰†ƒ‚ƒ‚~}}~~}`[`fccbcglw€ƒ‚|wsoonlke_^`ceggjkmsz‚…ƒzxz}‚‡578645542219EIGB>?:   + + Ly‚ƒƒ„„†…†‡‹‹‡……ƒ€~|{xvuusurqqqpqrqrtuy{~ƒƒƒ‚‚ƒ€„‹ŽŽŽŽ‰‚}{m )z}{{|}}~~4Mƒzzx}S.RV[bdhopsvv{~|€~„E"ƒ›—–›ž¢¤¨«­±´¹¸¹¹»´I5693M¿ÆÈÊÉÈÇÆÃ¿º´¯§ š–Šˆ…€}|zxwvwvttutvtrrsrsqppqsuvxyyz|~€‚‚‚~€~~}zzyz}{`[`baabehox€~|xsnmlkhb^]beggeffimx€€}yw|ƒŠ7865533414BJIGE@<@  + :x‚~}€€€ƒ„„……ƒƒ€}{zwxwuvuurrrqrru{€…‰Ž”“’“Ž‘‹‰‹‹„}yui"({zzy{|||v&^ƒxv{M#(+06:R~„=`twz†‰–˜š›žŸ±»¼À·H7776XÆÊËÉÈÆÃÀ½¸²­¦ —’Œ…‚|{yvuttrrrqsttrqqpqooooooqrsuvuvxyx||||||~~|{zvvvvz|x`[___`bcis|€ztnmjigc^\`dfeggbglp{}}{vyˆ•77534322>JPJIFD>2   + + .wƒ~zyz|}}€ƒ…„ƒ„†„ƒ}}}||yxwwvrrtvz†‘—𠢤¤¥¥¢žš—”Œˆ†ˆŠ…|wro_PJE>Gwxzy{yzxx|dl{rwF/€€5!$&)+.1147›ÂÀõF7887hÌÈÈÇÅý¹´±¬¥ž—”Žˆ€|ywwwtrsrsrqprrqponllmmopnopprsrrstuuwyxxxyyzxwvtuvuwzv]Z[]_`delu}‚‚|tojkhie]Z\bdgffhfms{~|{{vv|‡™Ÿ7553229JVTPJHE:/!  + *f€|xxwz||~}~‚‚„†„…††…ƒ‚ƒ€{zyzyvy|~†Ž– §ª¬¯³±²µ´±¬©¢™’‰„ƒ…†…€ytoqsx{{wvwx{{|}|{z€V#&pvyB5€0"""%'(*+,//15ŸÆÁÇ®B:;<7rÍÈÆÃ¿½·²¬¦Ÿ™”Œ†€}zwvutssrrqrrqqpomlkjiklmmnmmooppppprrsttvusstuvuuututuwoZYZ^aceinv}€|vqniigfa[[aeejhhimu|€|}|zvz†”œ¢¨52123ASWTSKE<70,! + + + "[y~zwtrswwyz|€€„††‡†ˆˆ……ƒ€{{{yz‚‰‘™¤­±³¸¹»»»º¹¶´±¨œ„€‚„ƒ‚|wtrqprtsstuyz|}~}}|sjbXOI;[ww@A‚~ƒƒ. ""#&')*-/2359©ÇÂÇŸ76530…ÌÅľ»¶±¬¥ž™Ž†‚|yvutttrrrqqqppoomkjihhhghijijlmmmnopoqrqppqssrsrttstrsssukVW]`efimsy€yrnlgghe\Y\aefjkjnt|„…~{zvx‚‘œ ¤§31/-3AA?A=75320/#  + + + + U{{wrrqrrstyz}€ƒ„…ˆ‰ˆŠŠ‰‡‰‡…‚~~‚…‹“𥭳º»¼¼½¾½º¸µ±ª£˜Š~{|~€€}zyyvuttuuuvz|||~}|}}}|x{}|{xxoaZSKD<62,)$#I‚€†) "!#%')*+-33/<­ÃÂÆ›S\bv‹µÇþº³®§¤ž•†~zvuttsrqppqppoommljhhghhfdghfggijllmmnnmnpqppqqrrpqrpqqrpqtqbadfhjntyƒ}wojigge^XY]cfjjlpsyƒ‰†}yut{‡“œ £¤1.,./.,.43334422' + + + + + Lyxwtqpqrssty|„‡‡‡ŠŠ‰††††‡‰†„‚ˆ‹’›£«²·¹»»»¼½¼¹¶²«¢—Œ~{zz}ƒ‚‚~{zzzzy{zz|{{€‚|zzz{}|z€{zyzywxxwspnlhcYTQl…‰ƒ?2.2300.1656;@FGNSZv¾ÂÅÆÃÂÊÏÒÐÉþ¹´®ª —ކ‚|xvsrrqopooopoollklljjijmlifiigeffiklllmmnlnpomnopomoqponnnprqighjiovƒƒxqlihge`YX]cfijjmsw~…†€zvqs‹”›¡££.,+-.-/043566753   + + + + + + + + Euttsrsssstux{‚†‡‡ˆˆ†‡ˆˆ…†ˆ‡………‹Ž— ©±¶¹»¼¼¾¾¼¹´°­¨š‚zxyz}…………‚€~€~ƒ‚„„„‡…‡†‚‚‚€ƒƒ„„€|{xyz||~}~€‚‚ƒˆŽŽ‹ŠŒŽ‘““•™ž¡¤§¦©¬±¹¼¾ÁÆÍÊÈÉÉËËÈÉÇž·²ª£›‘‰„~yvtsrponmmmnonoomklnnnoorvurpmjihhfegjjilloonoononnmmlopnmlmmqpkijkoxˆ†{tnieeb`XV[_dghkmqw€‡ˆ€zwurx‚Œ”™ £¦,+,./00224677742  + + + + + + + :wwvvuvttuvwxy~ƒ„ˆŠ†…‡‡ˆ‡‡‰Šˆ‰Š‹•œ¢¬²µ·º¹º»º¹³­¤˜‘yutux{}ƒƒ†‡……‡…ƒ„„„„„ˆ‹ŠŠ‹Š‹Œ‰‹‹‰‰‰‹‹ˆƒ~ƒ‡†‡‰ŠŒŒŠ‘“‘“—˜œŸ£¤§¬²¹º¹½ÁÁÃÆÅÇÊËÉÊÊÊÉÈÉÇž·°«¥šˆ‚~{xtqrrponnlmnnnmoomnopprsux{yxvrqonkigfgigiklnnonnolllkkklllkmnpoljkqz‚„‡…zslhfccaZTW^dfhikqv{„‰…zvssu}‰‘˜œ ¤¥+++-/00344455762   + + + + + + + 1s{}}}|zxvvwwvzƒ†‰‹ŠŠˆˆˆ‡ˆˆ‰‹‹‘•™ ¨¬±³¶¸¸¸¸µ°¨ž˜ˆ~usqqqty|€‚†ˆ‰‹ŒŒ‹ŒŒŒŽ’““”“‘‘’“•“’“’Љˆ‡‹ŽŽ’”•““•˜——™˜˜¡¤¦¨©­°°·»¼¾ÁÅÅÇÇÈÊËËÊÊÉÈÇÈÇžº´ª¢›’‰~zwutrqpqpnnnnonooopppqqsstwxz|{{zywvutppjhhhjklmmmlmnljkkjjjjkijnrplov{„‚|rjgedb_]USX^cegimsx€‡Šƒxurrx‚Œ“šž ¡¢)**,-./11214112*  + + + + + + + + +n€„‡‡…„€|zzzzy~…‰‹ŠŠŒŒ‰Š‹‹ŠŒŽ’—𣫭®°²µ··´«¡œ–‹ƒ}vurqqsuw|€ƒ‡‘””••••–˜š›œœž¡Ÿ››žš™™˜•’Ž‘”““•—–™šœŸŸœœŸ§«­¯±´¹¼½¿¿¿ÁÃÆÇÇÈÊËÊÊÊÊÈÈÈȼ¸´¬¡™‹‚~yywuspnonnnmnnnoprrrrsttuuxyz{}}}}}}{|{zxunkjkkmnkllmmlljlkjihhhikmqnov…‡…~yrleddb`\VTV\bdfilot|„Š…}wsot|‡•—œ¡&''(())(''''%$#   + + + + + + + + "k‹‘Œ†ƒ|~}}„‡Š‰‡ŒŽŒŽ‘•™¤ª¬­®¯±´²­£šˆ€|yustsqsx}…‹’—™¡£¤¤¢¡žžŸ¡¤¥¦¨©ª©¨§§§¦¥£¡¡Ÿš–•—ššœ››œœŸ ¡¢¥¨¦¥¥§­°³µ¸¹¼¿ÀÃÄÄÅÆÇÈÉÊÊÊËÊÉÈÈÇÇÇý¸°¦•Œƒ}zxvvspnmmmmlmnpqqstrrstvwwyz{||}}€€}||}}|{xqmlkmmklkllmlkjjjiighfghiojju€ƒ{qjd^``^_ZRVZadefkkox~„…}xvqqy€‹‘“–™œŸ!! # ! !!"!   + + + + + + + + ]˜›š˜•‘Œ‡„„‚~}‚…ˆ‰ˆ‹Ž‘’‘’”–™ ¦ªª¬¬­°±¬¢™…|{xusutsyˆ‘œ¦¬±¯°®°´µ·³¯«©ª¬¬­®±²¯¯®­¬¬ª¨¦¡œŸ¤¦¦¤¡ŸŸ¢¦¦§§©¬®°²³±´·¸»¾¾ÁÄÅÆÇÉÊËÌÌÍÌÌÌËÉÉÈÉÇÆÀ¹´®¦š’ˆ‚{yxvtrpnllkjlnppqrsuvuwvwxz{{{|}~}~~}~}}}||ytqnnljkljljkjjjjhigfggfgjjYU_ijhfg`a^\Z]]ZTRX\`ccdgmpw}€|xtrorz…Ž“•˜™žŸ"""" !!##"#"!    + + + + + + + + Nˆ—Ÿ¡Ÿžš—‘Љ†ƒ†ˆŠ‹ŒŽ‘’’“”–™œ¢§ªª©ª«­­§›’‰‚€~xwvyz~‡˜¥«£‡mWMJJP_œµÂ½³³³³³´µ³´µ´´±¯­«¥¤¦«®¯«¨¦¦§«­®°°²²µ¹¼¾¾¼½½¾ÁÂÅÅÇÈÊËÌÍÍÎÎÌÌÌÊÊÉÈÇþµ¯¦ š‘‹|xvutrqonlloqqssrqstvwz{{{|}~~}~~€~~}~}|||xurppnlllkkjjjihihffdeeeicMJKSSTWYXY[[[[ZUQTX]bdeehlqx~}xurnpv€‰–˜˜š›œž !!"""!"   + + + + + <€‘Ÿ¦¥¥£ž™”І„ƒ…Š‹ŽŽŒ’““–™š¢¥§¦¦§¨ª©¥™‹‡†‡†ƒ…ˆŠ‹‘š¨±–c<0034453048Gw¯Ä»¹ºººº¸¸¸¹º·µ³±°²¸º»¸³±¯¯²´³´·¸¸¹¼¼¼½¾ÀÁÀÁÂÄÆÆÇÉËÌÌÍÍÌÍÍÌËÊÇÇÅÁ¼³­§ ™Š{xursqpponokWZ]aitvuxxxz|~~}~~~€€‚€~~}|||zyxusronnlklljifihfbcbbcf[FIJJMPQRVY[ZZZVQRV[^_cfeimqy}ytrpou~…Š“™™—š›ž !!" + + + + + + + + + + )q”Ÿ¥©©©¥¡›–’‹Š†ƒˆ‹‘Ž’••—››ž¡¡¢¡ £¦§¨¥¡œšœ›š›Ÿ£¤§«·§f0(/7;889:9<::5.@{¼Ã»¼¼¼¼»¼½¼º·¶¶¸¼ÃÆÊÆÅþ¸¹ºº»½¼¼¾¿ÀÀÀÂÄÄÄÄÆÅÆÉÌËÌÍÌÌËÉÊÉÉÈÆÄÀ¼¹°©¢”‡ƒzwutqrqpppoy>j{xxz|{|~~~~‚ƒ„…„…„ƒ‚~}}}||||zywvtrollkkjhghgeaababgM=BFIKNOQVYXYXWQOQVZ^`bdgkotyyuopqot…Š’–•˜šŸ  + + + + + + + + + + + + `Š—¢©«ªª©¤žš–‘Ž‹ˆŠ’“’“‘“–—™šœŸžŸŸžŸ¢§¨ª®²·¶²²²´·¹¼ÄšB'1524:95457;96784.Q©ÉÁÂÀÁÂÁ¾»ºº»¾ÄÇÈÑ—q‚Ž™¶½»»¼¾¾¿ÁÂÃÃÃÄÆÆÆËÐÐÏÎÉÊÉËËÊÈÉÊÇÅÄÁ¾¹´­£§c&*),+Mxstrqppont[f|ywxz{|}~€€„†‰‹‹‹‹‰ˆ…„‚}{{||}~~{zzxusrqnllkjhgfccaaac=3œÉÂÃÃÃÃÂÄÇËÌÌÌÆV16697P»¿¾¼¾ÀÂÃÂÃÄÇÇÇÇÏo0240KÈËÉÉÈÆÄÃÁ¾¹¶°¨ š“‰//qrqqqqpoyHi€|||}~€‚‡Œ”••”“‹ˆ†„~€€€}~}zxxwvurrrpopmlhdd>.379>DFJLNQSSPLOSUZ^^]_dghnsspnoprv|‚‡ˆ‹ŒŽ‘”˜   + + + + + + + + + + + + +  `‘Ÿ¦¬°¯¯®«¨¥¤¡›˜™˜˜›žžœ››™—•’‘‘‘“˜¢°ºÁÅÆÈËÍÍÏz-58::0N•ÁËÊÈÈÉÊÌǯf59;;:.PÀÆÃÂÁÂÅÉÊÌËÄÇ{+633576œÅ¾¿¿ÂÃÄÃÆÅÇÇÉÊË\49:4eÍÇÇÇÅ¿¼¹¹¶­§ ™‹t!-oqqrrsqsck€€‚„†‹‘”˜™˜—•“‘Œ‰……„‚ƒ‚€€€€~|{{zyxuxxvwuturp]/+18;;ADGJMPRQMKQTZ]]^]`dginropootv{|€‚ƒ…„†ŠŒ’–   + + + + + + + + + + + + + + + R€Žœ¤¬²µµ´±­««©£Ÿœœœœœž   žžœ›•’ŽŽ•¨´»ÀÃÇÊÉÑŠ-4696/sÁÑÉÅÅÅÅÅÆÄÁÃÃ…79<1MœÃÄÃÁÂÅÊÌÎËÂÆ¢//10114.kÅ»½ÀÂÄÄÅÇÇÇÈÉÍÁH6885~ÌÄÅÄÀ½º·²­§Ÿ™”‡ƒ`,npqqrsrw1n‚‚ƒ„†ˆ‰Œ“–™™™˜—•’ŽŒ‰‡ˆˆ†††††‚‚‚‚€|~|}|{{{|}|{zyws3%+-3:;?BEHMQRPJJPUY]^_`acdinmlmoswz„…„‡†‰Š‹Œ“   + + + + + + + + + =yˆ˜¡ª±´¶·¶³®­¬«¦¢¡¢  ¡¢¡ Ÿž™“ŽŒŒ‰‹Œ”žª³¸¼¾Àͱ707<71ƒÎÎÇÈÈÈÆÆÅÆÆÅ¾ȉ32a¸ËÃÄÅÆÉËÌÌÉÀ¼¹H&0/0001/?³½¾ÁÂÄÆÆÇÇÇÇÇË«:9874‘ËÁÁ¾º¸³¯ª£œ˜‘Š‚B#kpprspyRtˆ†ˆˆ‰ŒŽ‘•˜™œ›™—•‘ŽŒ‰Š‹ŠŠ‹Š‰‡…„‚~~~~€€}||U#*,7;?ADFKPOKIJOUY\]_``ceiljklov~ƒˆŒŽ‹ŒŽŒŽ‘’  + + + + + + + + + + + + 'j‚Ž™¡¨­¯²´³°¯®®­ª¥£¥¥¥¥¤¢¡ žœ—“‹ˆ‡…†‡ŠŒ’©¯²´¶Äs+:<=2ƒÒÇÅÆÅÅÈÈÇÇÇÆÅÅÃÃÉ„ÆÌÅÆÇÇËÍÌÊÆ¾´¼n!--../1241†ÆÀÁÂÄÅÆÅÆÇÇÇÏ”4:889ÆÀ¾½¹´®©¢”Šƒ|zy.eqrrrshv‰‹‘‘’”˜›ŸŸžœš–’ŽŽ‘ŽŒŒ‹ˆ…„‚€€‚„‚€~|r# + !(,7AGJJJIHJMPUXYZ[\_abeknt~‡Œ’”“”—™˜———˜™˜˜    + + + + + +  + + + + + + + + + + + 0u›¢¦ª­°°²±¯­­©§¥¦§§¦¥¥¥£¢¡›“І„ƒƒ„ƒ„†Š“Ÿ¨°¹¡-+052ÌÄÆÈÈÈÉÈÇÆÆÅÄÅÅÅÅÅÇÆÆÇÇÊËËÌËÅÁº¸³E(0./+::279:9¦ÅÀÁÁÁ¿ÁÄÆÅÂÆa3979E¯¼¸²­§£œ’‹„ƒ~zxu|ADtsrxZ)…‘‘•––––˜™š››¢£¢ žœš™™š™™š™—•’‘ŽŠˆ‡„…ƒƒ„„†ˆ‡Šˆ‰ƒ|j "(+/9?CHIHFGHLNRUXY[\]^_ajr~…‹Ž‘”•———•”—›œžœ  + + + + + + + + + + + + + + + + e€Œ™¢¨«¬¯°³²°®­¬ª¨§§¦¥¤¤¥¥¤£¡˜ŽŠˆ……‚‚‚ƒ…Š“ž¨´‘%(),2¦ÄÁÄÆÆÇÇÈÇÆÅÄÃÃÄÅÆÆÈÇÇÉÉËËËÉÆÂ½¸¾s$/0/01˜x-;8;6oÇÀÂÁÂÂÂÅÆÄÃÃS4765F°¶±ª¤¡™•‚€|yxutn"$%prwo"*.“—šœœ››œ›œ ¢£¢¡ŸŸŸž›™—”‘‹‰‡ˆˆ‰Š‹Ž‘’‘‘Š: &).4BBCDEFFGGIMRRSVUVWX\`_cfhlnoonnsyxy{€… + + + + + +  + + + + + + + + + + + + + + + + + + +  fŠ˜ §«­®°±²°²´³²¯°°°°¯°¯¯­¬©§¢œ–‘Œ†~~~~‰\­¨­°¶º¾ÁÂÂÃÄÅÆÆÆÅÃÄÅÄÄÃÅÃÂÈ£00113+I½À½½½Ãm08552lÇÀÃÄÃÂÀ¾Å‡+,(%!A—Іƒ~|wwxvuutsv1f|}/yš–M\ž›žŸ ¢¤¦¦¨§¥¤£¢¢£¤£¢££¡¡   žœœœž¢¥¨®±³·¸¹ºº¹¶²¬_ + +  (07>AAEGDGHFGJMPPQSTUVXY[_acedgihkmquw~„’˜ + + + + +   + + + + + + + + + + + + + + + + + + + Nz„“ž¥¨¬¬¬¯±²²³µ´±²²±±°²²²¯®«¨¤ž˜“‡ƒ€€~~~!F¥¡§­²¶¹½ÁÃÃÄÅÄÄÃÅÂÂÄÃÃÄÅÄÈ¿N,300/7¤Å»»¼¹¾œ13223C¶ÄÃÅÃÁ¿¾Ãx%&" Eƒ|yxvuuvutttr)p‡hO£š™Hd¦ž¡¢£¤¥¦¦¦§¦¤¤¢¡¢¢¡¡¢¢  ŸžžžŸ¡¦ª¬°´¸º½½½¼»ºµ´‰#  + + &/8?BBFJFEJHGILOOOPQTVXXYZ\^^acdhjnr}ˆ“œ¢¥  +   + + + + + + +  + + + + + + It€Ž›¡¦©ª©¬¯±³µ¶´²±±°°±±°±²²¯­§¡œ•Іƒ~}~†Z]¥¡§¬¬¯´»¾¿À¿¾ÂŪÂÅÂÂÂÄÄÃÊl-630.,€Î¾ºº¹¸¹½M-3251ŽÊÂÅÃÀ¾¹½l!% NŽ~|xutuuuuttui"%z‡‰A&‘£ £F ! k§¢£¤¤¥¦¨¨§©¨¦¥¤¢£¢¡¡  ŸŸŸŸž ¥ª®²µº½¿ÁÂÁÀ¿¼¸µ§J  + + + + $-8?EGIKJFGIIIHLMLLPRSVXWWY]^`bfltŠ™¢£   + +    + + + + + + + + + + + + Tk|‰”£§ªª¬¯±´·¹¸¶µ³²²²°¯°°°¯®¬§¢Ÿ—‹†ƒ~}}€€„6b¤¨«¯´¸¸»»ÂÂ~?vËÁÁÂÃÀÌ19632/.[…Ÿ²¼¿ÁÂÌ€,5460^È¿»¶ºa !V…}{xvuuuuvvuwc*„’œ}"d®££¨M!!"lª£¤¤¥¦¨ªªª«¬¨¦¦¤¤¢¡¢¡Ÿ  Ÿ ¡£¨­²µ¹¾ÀÂÂÃÂÂÁ¿»³®k + + + + + +,7HGFHJKIGIIIGIIIJLOOTUUVY^`cnt„—›œš™•“’“  +  +    + + + + + + + + + + + + + + agm€Œ—¢¨ª«®±´µµ¹¹¶¶´³³³²³³±±±°®¬§£ž•ŽŠ…‚}~€y$G•¦§­®®®³ºÁ®_/83ɾÁÁű?56651--&$,>Qit{…m364459¬ÄÀ¾º´®²S_}{yyzyvwvvxzy|[/›žŸ§V  1¢®ª©«U!$o®¤¥¥¦¦¨ª«ª¬«ª¨¦¦¤¡¡¢¢ ŸŸ ¡¤ª±µ¹½ÀÂÃÅÄÃÃÂÁ¾º°‹* + + + +/HKFHJJKKKIKIGHJKNPSSVZ_cglsŠ‘‰‰Š‰‹‹  + +    + + + + + + + + + + + + $hfg{‹”Ÿ§«¬®±µ¶···¶³²³²²²²³´´´³±°¬¨¤–‹†ƒƒk.g‘§­®°²l9'5997’ÈÁÄÄb287670+*+)*-*+-.0076446,tº¸¶°§§Bk{wwyyyyzzz{}†W0˜¤¥¥«ž2#$s¶­®«¯] #$k®¨©¨§§¨ª¬¬ª©©¨¦¥¢Ÿ ¡¡  ¡¢¦«±¶»¿ÀÂÄÅÆÄÃÂÁÁ»²£F + + + + + + 3HFIJJKNQQNPRPRTWX^`adjnruz|~|~‚ƒ…††     + + + + + + + + + + + + + + 'kda{•ž¦ª®°²³µ·¸¸·¶µ³³´´³³µ´³³³¯¬ª¦¢ž˜‘‹ˆ…€€}{ƒc'E\a[C+ )12482O»½Ä‡3989:3.%#&'+.232367754351D·ºµ²§ š7$xyxxzzz|~~€‚†Š“` 1¨«­«¸v'$=¬²±°¬¯`"%$b±ªªªª©©««¬«ª©§¥£¡Ÿ  ¢¢¤¦©­±µº¿ÂÂÃÄÅÅÃÃÁÀ½¶¯m  + + + +6@DCCGMQWWWYYXX\_adceikmoqrtttvx|}€…Š + +    + + + + + + + + + + + + + + + ,nf_o‡’›¦®±³´³³¶¸¹¸·¶µµµµµ´µ³³³²¯­«©¦¤Ÿš“Ž‹†‚€{z€`!&(,03.<¿À­=79:=6Kš‹`='$(./1466242256*ˆ½±ª¡™’,3yvxy{~ƒ†‡ŠŽ™k"#%3«­°²´³J%$u¹²²±®³l$(%\±¬««ªª©ªª©«ª§¥¤ Ÿ  ££¢¦«¯²·»¿ÂÃÅÆÅÄÃÃÂÁ¿¹´’( + + + +  +4;;:>EGMSTWTUTVXZ]`cegfijklqswwy|†Œ  + + +  + + + + +  + + + + + 4rj]cy‹—£ª¯±²³´´·¹¸¸···¶µ´³³´³³´³±¯¬©§¢ž˜•‘‡…‚}||‚s0 #%)+&J¤ÄºÁ_.;;<:7ż»®”vVGC?<>BD91541)I¯¦Ÿ˜‘"E}wz{|€„…ˆŠ‘–œo#%%(/–¯¯°²²¹•-<«²²³±¯µz(*)#P±®®­««©©©¨§¨¦¤¢žŸ¡¢¤¦§«®³·½ÀÂÅÆÇÅÄÄÄÄÃÀ¼·¦L  + + + + + + + + +#5435;=AEJNPQSQSVWZ^_cdddinsuw{†‡Š‘  + + + + + + + + + + + + + + + + + 9qj^`mƒ“ ¦«®¯±³µ¸¹º¹¸¸¸¸¸µ´³´³µ¶´´±¯­«§¢žš—”Žˆ…~}‚}L"#&f²¿·À-68<<5}ô³³´¹¾»º·´²³¸À–151-,$‡¤—Œy%5/-/...--..(o ¦{!''((±°°³´³»n{º³´³°¯²*(("A¯±±±®­«ªª¨¨©§¥  ¢¢¤¤§¬®²·¼ÀÄÅÆÇÆÅÃÄÄÄÂÀ»²u + + + + + + + + +%0147:;=BHLOSSQRUW[\_cfhlmtx|€„‡’–  + +  + + + + + + + + + + + + Csk^]f|‘𣍫®°³¶¸¸ºº···¹¹¸¶µµµµ¶·µ³²°®­©£Ÿ™”ŽŠ‡‚~ƒuF<е²±¸«>388;4ZÁ¹²²²²³²¶¾ÃÇÉÊÇÆÂN*-*(LŸŠˆp hª¨ˆ*++,*†µ²²´µ´µµµµ¶µ²±°³˜0))$:©´³²±¯¬¬ª©©©¦¢¡£¤¤¥¦©¯´·¼¿ÃÅÆÆÆÆÆÄÄÄÂÀ¼¹@  + + + + + + + + + + + + ,2269:<@DIMPSTRVW[^bfimqvyƒ…ŠŽ’•“•  + +   + + + + + + + + + +  + + + + + + + Gvm_]aoˆ•ž£§¬®±´··¸¸¶µµ¶·¶µ³´¶´µµ´´´³±¯­ª¦£ž›—“މ†…‚‚…‡‰xP/ ,Gz¡«ª«®´x4;864<¨À³±®¬­­°¶¿ÅÅÆÅÃÀÄ‚#)($ %‡……b #j¬¬‘1.-.+~º³µ¶¶¶µ¸º¸¸µ²±°´¢6)*)5Ÿ´²³²±¯­¬¬«¨¤£¤¤¦¦¦©®µº¾ÀÂÅÇÆÆÆÆÅÄÄÄÁ¾¶©w* + + + + + + + + +/48;>BBFLPRUWWY[`dhmty~‡ŒŒ‘†t  + + +   + + + + + + + + + Rwp`\af{™ ¤ª­®³¶··¶·¸µµ¶¶µ´´´´³²³³³²°®­¬©§¥¡ž›—”‘Ї‡ˆ‡ŒŠ‚ztt}£££§ª®²§¤¦ š–ž¾¸µ¬©©©«³¾ÅÈÇÆÄ¿¼¹§/$$ X…‡T $d­®™5,-,-s¸³µ¶¶··¸¹¹·´²±±³¨<(++.“µ²²°°°¯¯®¬§¥¤¤¥¦§©¬´º¾ÂÅÇÈÉÉÇÅÆÅÅÄþº¶‰c' + + + + + + + + + + + + 6:ƒ‚{l_cccedgz‰”Ÿ¨¯²´¶¸º»¼½½¼¼¼¼¼»¹¹·µ¶µ³±±±²²³²²²°¯®®¬­­­«©§¦£¡Ÿš—‘Їˆ†ˆˆ‰Œ•¢§¬³·»¾¿ÂÆÆÄ½»¸±°­ª­ª¨©ª­¯°²¶¸¸º»º¹º»¼¹¸··»½¼¼¼»»º·¶µµµ¶³´´µ´²±¯®®®­­­­°±°°²²´³³µ¸»ÁÃÆÉËÌÎÌËÌÊËËËËÊÈÆÃÀ»·³°­©£¢˜nadgioo` + + + + + + +     $&&$&(&)*(’’‘‘’ŒG  + + + + + + + + + + + + + + + + A‰†~o`bcgihgo‚™¤ª²´µ·¹¹»½¾½½½¾½¼¼»¹···µ³²²±°¯¯¯±°°°±±±²±°¬ª©¦£¢ŸŸ˜•’Ž‹‡†…‡‡ŠŽ“˜¦¯¶¹½ÀÄÄÆÈÈÅÄÁÀ¼º¹¹·¹ºººº»¼¾ÀÃÁÀÀ¾¿¿¾¼ºº»½¾½¼½¼¹¹¶µ´´µµ³´´´´²±°°°±°±±±²³³µ¶¸¸¸¹¼¾ÃÇÉËÌÌÌÌËÊËËËÊÊÈÇÄÁ»¸µ²®¬ª¨¡ž€cbehlrmb" + + + + + +  +  +  +  #&$$'&&())‘Ž‘ŽŽŽŒ‰J   + + + + + + + + + + + + + + + + KŒ†ƒ~saachkkkkv…’𤭱´µ·¸»¾¾½¼¾¾¾½¼¹····¶µ³²²±±¯­®°²²±²³²²³±¯¬ª©¨¦¥¡ž™”Œ‰ˆˆ‰‰Š“œ£«±¸»ÀÇÄÄÅÆÆÆÇÅÃÂÃÄÅÆÅÄÄÅÆÅÆÆÅÄÃÃÃÃÁ¿¾½¾¾¾¼¼¾¼»º¸··¶¶µµ¶´µ¶³°°±²²²µµ¶¶·¸¹º½ÀÂÃÅÇÊËËÌÌËÊÊÊÊËÊÉÉÆÅÿ¼¸´±­ª©¨¤™Šjcfikmund# + + + + + +  + + + + + + + +  "#%$%'(''()ŽŒŒ‹ŠŠ‹J   + + + + + + + + + + + + + T‹‡†‚va^chjlmklx‡•Ÿ«°°²µ·¹½¾¼½¾¾½½»¸¶¶¶¶¶´³³³³³²±²²²²±°±±²´³³²°¯®­¬ª¦¢™•Љ‹Œ‘–ž£­¤OHuÆÁÂÃÄÅÅÆÎÑÐÑÊÊÉÈÈËÒÑÏÈÅÉÎÌÍÎËÉÊËÆÀ¿¾¾¿¾¾½»¹¹¸·¶¶·¶·µ³±±³³´¶¹º¹º¼½¿ÃÅÈÊËÍÍÍÌÊËËÊÉÊÊÊÉÉÇÄ¿º¶³¯¬ª¨§¥¢œŽthfiikntmf$ + + + + + + + + + !!"$$$'&&()(ŽŽ‹Š‹Šˆ‡‡‰D  + + + + + + + + + + + + ^Љ„|b`cfklllgo“§®±´¶¸¹»¾¿¾¼½¼¼»¹¸·µµ¶µ¶´´µ´´µ³´µ³´´´µµ´µµµµ¶´±°¯¯¬ª¤ œ˜—–‘ŽŽ”šŸ¯e ,,žÅ¾ÂÄÄÄÄ~hl˜ÑÊÉÇɽr’ÊÉ­jhfdbgrŒ«ÇÉÂÁÁÀ¿¿½»¼»º¹¹¹¸·¶µ´´¶·¸¹¼¿ÁÂÄÅÇÉÊÌÍÍÌËËÊÊÊËÊÉÉÈÇÅ¿½¹·´¯­ª§¦§§¥Ÿ–‚qiikjkqtlh+ + + + + + + +  +    "#$%%$#'&&‹Ž‹ŒŠ‡†ˆ†…ˆE  + + + + + + + d•Ž‹‡jbeghllmjjx‹™¢©°µ¶·º»½¾¾»»½¾¼¹¹¸·¸¶µ¶·¸¸·µ···¹¹¹¹¹¸¹¸·¸¸¸···¶µ³³±¯ª§¢žš˜•‘‘‘’”œ–*%,)\Ä¿ÀÄÃÃÇ\)//}ÐÈÅɼE.MÃÌ«312:>;5.6^¡ÍÇÄÅÄÄÄÃÃÁÀ¾¼¼»ºº¹»»¼¾½ÀÄÅÇËÌËËÍÍÌÍËÊÉÉÈÈÉÉÉÊÈÆÂ¿»¸µ³±®«ª©¨¨§¥ ›xmjkkknuvml/ + + + + + + + + + + + +  + + +  !"#$$$%&&&(ˆ‰ŠŠ‰‡…‡‡„ƒC  + + + + + + + + + + + + k•Žˆodfgikklkjp} §¯´¶¸º¼½¾½»¼½½»»º¹¸¸·µµ¸¹¹¹¸¹º»¼½½¼½¼½¼»»»»º»¼»¹·¶µ´°®«¦¡ž™••–•‘œa%$**•Áº¾¾¿Çy076,nÉÈÃÇ]6?±È»H6G«º·³–b-3vÊÉÆÇÈÇÇÇÆÆÆÅÃÁÂÂÁÁÂÄÅÅÈÊÊËÍÍËÊÌËÊËÊÉÈÈÇÇÆÆÇÆÄÀ»·³°­ª©©§¨§¨¨¤¡œ“‚qlilklqwxnk1 + + + + + + + + + + + +  +  +"#$#$%&&((‰‰‰Šˆ†„…„ƒ„? + + + + + + + + o–‘މ‚qcceggijjiiuˆ˜£­²µ¸¼¾À¿À¿¿¿¾¼»»º¸¶´³µ¸ºº¹»½¿¿¾¿ÁÁÁÀ¿¾¿¿¿ÀÀ¿¿½»º¸¸¸¶³²¯ª¦£ž›š˜–‘/!&!L´²¶¸»Ä2622,kÌÄÉn36›ÆÅ[1A¹ËÈÉÍÎ’8+eÉÉÇÉÈÉÉÈÉÉÉÈÇÈÉÈÇÈÉÊÊÊÊÌÌÌÌÌÊÊÊÊÊÉÈÉÇÆÅÃÂÁ¿»·±®ª¥¥¥¦¢¢¤¥¨¨¥Ÿš‘{mllnlnt{|pp5 + + + + + + + + + + + + + + +  #$##$%$'('ˆˆ‡‡‡„ƒ„ƒ‚B  + + + + + u—“‘Š„vbccddfggigk~’Ÿ¨¯´º½¾¿¿ÀÁÂÁÀ¾¿½½º·´´¶¹»¼ºº¼¾¿¿ÀÁÁÀÀ¿¿¿¿ÁÂÁÀ¿¿¼½¼»¼¼º¹¶²¬¨£ ž˜œj.r!†¯¬²³¾-1RR10lÈˇ23ˆÇÈo25¢ËÅÅÄÅÐ6.ƒÏÆÇÉÉÉÉÉÊËÊÉÉÊÈÇÉÊËÊÉÊËËËÊËÉÉÊÊÉÉÇÆÂÀ½»»ºµ±­©¦¥¢Ÿ   ¢¤¦¨§¦Ÿ•ˆsonnnlou}|ot= + + + + + + + + + + + + +  +  !!"#&%%$&‡††…„‚ƒ€}}: + + + + + + + !™”‘‹†{ecc`adffeeiu‰™£«±¸»¼¿ÀÀÂÄÄÃÁ¿¾¼¸··¹¼¼¼½¼ºº½½¾¿¿ÀÁÁÁÁÁÀÁÂÁÀÀÁ¿¿¾½½½½»¸µ³®¨¤ ™—7XžEA§£§¬´)+^¬<4-xÏ–32qÉɆ03‡ÎÅÇÆÄÃÅY1E¼ÉÇÈÉÈÈÈÉÈÈÇÈÈÈÇÇÈÈÉÈÈÊÊÊÉÉÉÇÇÆÆÅ¿»¸·´²³­¦£ ŸŸŸœ››ž¡¤§§¦¥›“}npoonmpy€{qx? + + + + + + + + + + + + + + + +   "#$%%%&&……†ƒ~~|}4  + + + + + + + + %…™–‹…{hcc_`ccdeegk|ž¦¬²¶º¾¿ÁÃÃÃÃÁÀ½¹¶¶¸»¼½¿¾»»¼¼»¾¿ÀÂÂÂÂÁ¿ÀÁÂÂÂÁÁÂÁÀ¾½½¼»¶´³¯ª¥št!‚w}¢¤¬Š((RÑ/4,ª72_ÇÆœ56iÍÄÅÃÂÀÈs44™ÌÆÇÈÈÇÇÇÇÇÆÆÇÇÆÆÆÇÇÇÇÆÈÈÆÇÇÄÂÁÀ½¸µµ²²°­«¥Ÿœœ™š™™šœŸ£¦¦¥ ˜soqrrpnr…}szE + + + + + + + + + +   "$%$$$$$…„„‚ƒ€~}z|8   + + + &†–”†|hcba`abcdfgem|–¢§¯³·¹¼¾Á¿¾¾¾½»¹¶¶¸¹·ºº¹¹ºººº»½¾¾¾ÀÁÁÁÀÀÂÁÀ¿¿¿¿¿½»½»º¶³±­¬¥EO”ŠŽE>˜•š ƒ%#I±¶r'-1|;2N¼À¨?7PÀÁÀ¿ÁÃÇb21”ÉÄÅÆÆÅÅÅÆÆÆÅÆÆÅÄÅÅÅÄÄÄÃÃþ¾º·´²®®«©¦§¤œ™–•”’’‘”—šŸ£¦¥¢š“€lpssrppv‚ˆ|v|P + + + + + + + + + + + + + + + + +!#$$$%&%‚„ƒ€~~|}|{yz= + + + + + + + 'ŠŸ›–މ‚kcbb`_``cefedn†›¥¬²µ¹»½¾¼º¸¸¶¶··¶´³³´µ²´µ´µ¶·ºº¹ºº»¾¾½½¼½»¹¸·µµ²°®­¬«©§¦¤žž‡azvtYlЇv; Ÿ¦B %%*,<¯»±A2=´¾½½ÀÊŒ67>¬Á¾¾¾¾À¾¾ÀÀÀÀÁÀ¿¿¿À¿¾¾¾½½¼½¼¸µ²¯¬¨¥¡ Ÿœš•‹Šˆˆ‰Š‘•›Ÿ¢¤ œ“…roqsrqpr|†Š}z€Z + + + + + + + + + + + + + + + + +  !!"%%&'%‚„€~|{zz|{xz? + + + + + + +  +ŽŸš–’‹ofdb``abbdefghwŒŸ§­±¶»»»¼¹¶µ³³µ·¶³±­®­­­­­­¯±²³³¶¶¶·¸¸·³°°°¯«©¨¤¢¡žœ››˜–Žˆ‘D*|y~m-“’Ÿˆ# ##%1¥´±E-.¢¾¸»²0-&c³¯³±±³³³´³²±²³³²²²³³³²²°°¯­©¤¡Ÿžœœ˜—•’Š…ƒ~€ƒ†‰”™Ÿ¤¦¦£™Švmortrqqu€‹‹}~…d + + + + + + + +  + +  "%%%&'€}}|z{{zwv< + + + + + + + + 2‘𗓆thfdba`abdefhej“¢¨¯³¸¹¹¹¸¸¸¶¶¸¸¸´±®¨§¥¤£¥§©§©««««©«««§¢¢¡Ÿžœš™•’Š‹Šˆ……~pKyte*„ƒ„’c#Š¡ =^sfT80„–‘“—™››œœž £¢¡£¢£¥¤¤£¡ Ÿžš——”‹ˆ…„{{}€‚„‹•šŸ§««¨¢”‚qnprsrqsx„ŽŠ{€†g + + + + +  + +   #$$&&'}}~€|{z{xywuC + + + + + +  2’ž›˜’ˆ{jjhda__abfehhfr‰¦°´·¸¹¹º»½¼¼½½¾»¶²ª¤¡žœžœ›ž ¢¡ Ÿ  ž—ŒŠ‹†‡„ƒ{|{vxzussrxB$T[]\]^^L!Zja nsrsx?]rx1">gonsz{ƒ††‹‹Ž‘‘“”—™›žœœœš—”‘Ž‹ˆ…~}{|z{€ƒ‡”—ž¢¦­®¬¦šŠtmorssrru|‡”‹}ƒ…d + + + + +  + + !###'('}}~}|zxwuusuE + + + + 2”Ÿš“‰{mllgcb`abachiho|‘¡¬²¶¸»¼½¿¿¾ÀÁÀÀ¿º´°©¢›š˜™š››››˜–“‹{yyywvtttrrqoolkje<59[kiiffffj]EC@\e@9:_iihhkI+*)Vfg?,1449AIS_hegjmqru{|y{z|€€„‰“—šœš˜–‘ŽŠ†„~{yz|}~„Š˜›Ÿ¤©¬¯®©¡“|mnpttssrv€Ž•‡€‡Œh + + + + + + +  + + + !##$')){|}|{yxturquP + + + + + + + 7— ž›–Šqqnleb_abcceikio‡œ§®´¶º½¿ÀÀÁÃľºµ±«¦¥¡ž›˜˜™š˜™˜”‘Œ„zvtutsrqrqnmmkhgea^]``^]^[^]^``aaa`^^_`cadeddcfcbb```_^`baaaababdfiknoqtutuuwzzz„Š‘•šœœœš—”‘ŽŠ‡„€~|zz|~‡Ž’™ž¡¦ª«°°­¦›smpqtuussy‡””„‚Šo + + + + + + + +  +  #$$&(+|zy{ywttssorR +  + + + + + + + + + + 9›¡ ˜’‹ƒvtumhea`bdcehjhiyœ§¯³·½¿ÀÀÃÄÃÂÿ½¹µ¯­¬¨¡ž›š›œ›™—“Œ„}wrrsqoponjijffggecdddefecddefgeddcbdcddcdedccabb````^^]^`^^_aaddeilmoopqqruwy{}€ƒ…ˆ–ššœš—–‘ŽŠ‰ˆ†‚~{|}}„ˆ‘”™ž£¦«­¯²¯­£”~lnpsstuuv–”„…Ž’u  + +   +!#%%'+{{z}vvtssrpmI  + + + + + + >¢¡˜“Œ†zuwrnhdabdcehiihmƒ”œ¦®µ»¾ÁÂÄÅÄÂÂÀÀ¿¾¹³¯¬§£¡Ÿž›™˜–’І€{wvrppommlnmmmoononnmmopmlmnmjjjhfghhhhghifefeddedbab```a`bccdfdejnpomoprsw{}~€ƒ‡Š”›žœ˜•“‹ˆ†…ƒ~}|~‚‰‘—œ ¥ª«®¯³²°¬žŒomprtutuvyƒ’™•‡’–x + + + + + + + + + +   +0!$%&&+yyzyuvssqpnnP  + + + + + BŸ¡¢ž™”‰}u{yrmgcbcdegjjlku…–¢©±·»ÀÂÄÂÂÁÀÀÁÂÁ»·´°¬¨¥££¡šš–’Œˆƒ|zwttrtvwxwwvvwvvuvtrqsrqpqqpponmmmnlmkjjikihghhgfddcccdcdeefghjmoponqsw{~‚ƒ†‹ŽŽ•š¡¡žœ˜”‰‡…‚€|}}„„Š”™ž£§ª¯±°±´²®¥–zkoqstuuuv~Š—œ”‰•˜y + + + + + + + +  * "#&&)+wxwvttrrollkW + + + +  J¡¢£ž›•މ€uz{uqleabcdfijkkm{™£¬³¸¼¿¿ÀÀÁÀÀÃý¹·³°«©¥¢ Ÿž›—”“ŒˆŠ‡‡„€€€„‡ˆ……‚‚‚€~|{yzxwxwuuttttrrrssrqnmmonmlllkihhhhiiikikmnpqsusrvz‚ˆ‰‘““•˜š›Ÿ¢¢ ™–‘ŽŠˆ†ƒ~|€‚†‰Ž–œŸ£¨«®±´´´´²«‰qmosutuvvz‚‘›Š“—šz + + + + + & "#&()+-ywttrrronlkhb + + +  N¢¢£Ÿ›•‹„vw|yuqkcbccegikkkr‘§®´¹»¾ÀÀÁÂÂÃÄÄ¿½º¶²®¬§¤¡ œ˜–”“‘“Ž“šŸœœ™˜™—•‘‹‹Š…„‚~}}|zzz{|zwvuvuvvuqsqqppnpqosttwy{ƒƒ„†ˆ–———˜›œžžŸ¢£¢Ÿœ˜“Žˆ…„ƒ€}{~‚†Š•›¡¦©«¯²µ¶··³­£‘tnoptvvwxw|ˆ˜œ‹Œ–˜{ + + + + + + + + + +  !' #&*)*+/ttrsrpmllkjfd + + +   P¦£¥¡›•ˆxx~|ysngabbdghjlmox‹— ª¯µ¹»½ÀÂÃÃÄÅÅÁÀ¼¹¶±®ª¦ œ™–””“‘”••˜™£ª±´´²±­®ªª§¥¥¦¤¦¥¤¡ž›œ™˜•””’‘“‘Žˆ†…ƒ‚€€€~~€‚ƒ‚‚„††‡ŠŽ‘™Ÿ¡¡ ¢£¤¤¥¦£¢£¥¤ œ—“Žˆ„~~|}ƒ‡Œ‘—œ ¦©¬¯³¶¹¸¹¸±§œ„lnqswvwyzzƒ›Ÿš‡™›› + + + + + + +  #'#&*,-.13trsqonljjihec + + + +   N§¦¨¥ž˜“‰}v|~zwqkdaccdgjkkmr}™ ª²·º½ÁÄÃÄÆÇÅÃÃÀ¾»·²®¨¢™–“’“‘’“’—ž¤©±·»»»»¸³±±´µµ¶··¸·´²±°°®¬¬ª§¥¦§¦¥¢ž›š™—•”””•–“’’–—™šš™™š›œ›› ¢¢¥©««ªª«­­¬­«©¨¦¦¡™‘‰„||}~ƒ†‹’˜£§ª­±µ¸¹¹··³«ŸŠtmoquwuvyz‰•ž¡–„‘™›  + + + +$&!'*-.035sqrpmlkhhgddd + + + + + +  M¨¦¨¥ œ–‘‹€x|~~{umhbcbefijjlpvƒ˜¢¬´º½ÀÃÄÅÇÈÅÄÄÄ¿º¸µ¯¨¢˜”“‘“•žª±·¼¿À¼»¸¶º¼¾ÀÁ¿ÁÃÄÃÃÃÃÀ¾¾¼½¼º¶¶¸·¶¶²±®­«ª©§¦§©¨¦¦§¨©­¯±³¯°°¯¬¬­¯±²³³²±²²´´³²¯¬¨¨¥ –Š„€~~|}~ƒ†‹“™¤©¬®²¶¹ººº¶²­£–ynopsvwwwy|‚œ¢¢‘†•œœž + + + + + + + + +  &&"$+/0246pnqmmkffffcac( + + + + + + + +   M§¦§¤ ž™•…y|~€{tneabdfgjjkou{‡”ž¦®¶»½ÂÆÆÇÇÄÄÇÆÅÃÀ½»µ°¬¦¢›™•’“™¡¨±º¿À½»»¾±±ª§§¥¥ž—˜—–”—¥·ÂÄÀ½¾¿¿¿¿¼»¼¼»º¸··¸º¸·¶¹¹½¸ª˜‡„Œ•£´¿½»»»»½½ºº»»»¹¸·³°®¬¥œ‘Š…€~}„…‰”˜Ÿ¤¨­´¶·»¼¼»¹´°§œ†nmpsvywvy|‰– §£‹†–Ÿ ¡~ + + + + + + + + +  +)(#!#(/4567mmlhkjgeddcb`3  + + + + + +    S¤¨©¦¡™–’Šzy€ƒ€}xslb`bdfgjlnqv~Š“¥®¶»¿ÅÆÇÆÅÆÈÈÈÇÅÁÂÀ¼¸³¯«¨¦£¥ª²¹¼ÁÀ¿½»»¾¼RA=;;<;98<7476=K\¯ÆÄÀÀÀÀ¾¼½¾¾¾¾¾½½½¼»ÀÁ¨~X?320,.39KpžÂÇÀÀÂÃÄÃÂÁ¿½¼º¸¶´¯¥™„~}€†‹”™ ¦ª®³¸º¼¾¾½»¹²¬ tmmpuxywv{„›¤§¡ˆ‹™¡¡£z + + + + + +  +((&!#*46889jliihhfccc`\[9 + + +   W¨ªªª¥Ÿ›™•‹~w€ƒ€{tpgba`cgijnoru}ˆ“𤝵¼ÁÃÆÆÅÆÈÉÉÈÇÇÈÇÄ¿»¹¶µ´µ¶ºÀ¿¾¼º¹¹¹»±?9:99::8;:6779;;5.@z»Æ¿½¾½¼½¾½¾¿ÀÀ¾½¿Á¢f:+01/430111,*7VŒ½ÆÀÁÁÀÀ¾½¼»¸·µ²ªœˆ€ƒŠ‘—›£§­±¶º½¿ÀÀÀ¾º¶­¡–}lnptwzxxyƒ‰–¡©©›ƒœ¡¡¢w + + + + + + +  +%)'"!!%17::aa`_]^]WVVSRPJ + + + +  cª©¬¬ª§¢Ÿš“ˆ}}„………zunfa^^aejlloru{„–Ÿª·¼ÀÁÂÆÉÊËËÌÎÍÌËÊÊÊÉÉÇÅÃÀ¿¾½½¼¹¶¶µµ•+)))$PŒŒ‘‘„pJ.(10/311’ø¹º»»¼»¹ÂŸ?0111/00),044.++2530...(k¸±®ª§£ š•‹ˆƒ€€‚…‰”šž£©¯´¹½¾¿¿¿¾½º¶±ª‹{opqtw{{{z}„‰œ¦®­¨‰‡š¤¦¥¥j  + + + 3M3$$"%%%,38:___\[XXSSUQPLE + + +  c¬©­®¬¨£Ÿ›–Œ~ƒ‡‡†‚}xslea_``ehjloqv{…‹•Ÿ®·½ÀÄÈÊÊËËÍÎÍÌÌÌËÌËÈÇÈÆÃÀ¿¾¾¹·µ±¯®'&$%q³®±²µ²²´²“K&-,..1;§»µ¹¹¹»ºÂ¥=23/11,);]™ ¡šŒmG--0,,.,"q¯£¢ œ˜”ˆ„‚€‚…ˆŽ”›Ÿ¤ª°µº¿¿ÀÁÁÀ¾¼¹³«¢‘soqswz|{{|€‡Ž• ª¯®¤‰ž¦¨¦¨f + + + + + + + + + + + + +#(1G2$%!$%'-138]\[XWVTRQPMLHB + + + + +  e­«®°®ª¥ ˜‚~ƒŠŒ‡†‚|uphebb`bfhjlnsu{„Š‘ž«¹½ÃÈÊÊËËËÊÌÌÌÌËÊÊÈÈÊÈÆÃ¿¼¹µ³²°®¯Ž#$#$ p²­¯°±°®­²ºµc'-,--'Y¸´µ¶¶¸º¹L010.0++k§½¿ºº½¿¿Ã´:&,)&##$…¢™™•‘Їƒ€‚„†‰Ž•›¡¥©®´¸¾¿ÀÂÃÃÁ¾»·­¦›‡vopquy||||„Š‘™¥­±®˜Ž¢©¨¥¨^ + + + + + + + + + + + + &)*-@/!#"$%+115ZXVRRSROMLHDEB! + + +  j««°°¯¬§¢žœ“‡€‰Œ‹ˆ†€xsmhfdcbahikmqruy€†Ž™¬·¼ÁÄÆÇÉÊÊÊÊÉÊÊÉÊÊÈÊÉǾ»¹¶µ´±®²#$#%#t´­°³µ³°°´´·»]'.+,+-“º³³²³¼q,/-,.(5‹»¶´´¶¸ºººº»Ä«P"&!"?˜““ŒŠˆ†…ƒ„…†‰Ž•¡¦«¯³¸¼ÀÁÁÃÄ¿»·²ª|ooruxz|{|~ƒ‡Œ•Ÿ©°±«“£©¨§§W + + + + + + + + + + &())/A,"$!%%)034VSQONOLJIIFEC? + + + + +  fª¬±²°®¨£ ž–ˆ€ˆŒŒ‰‚~wpmifcbaaeimoostzƒˆš©´¼ÀÃÅÇÈÊÊÉÊÉÈÈÉÉÈÈÈÅÂÀ½º·¶¶²±²Œ$&#$#p³­±³´³´´µ¶´·­>'++,&]º±²±´Ÿ4**)+(5𷬮°±²´´³´´´²¶±Lz•Œ‹‰†…„…‡†ˆŠ”œ¡¥«¯³¹½ÁÂÃÄÄľº±ªžrmoswy}}||€…‹’›¤®±±¨†–¤¨¨§¥S  + + + +  + + + ((()(+7*$ $%'/43SSOMLKLIFEDC?=& +  k«¬²³±®«§£Ÿ™Š€†ŒŽŽˆ„|vrkeddcaafklnqru}ƒŠ¬¶º½ÀÃÆÈÉÉÊÊÉÈÈÈÈÈÇÆÆÃÀ½¸¶µ´±³Ž$%$%$q²®²³´³´´³µµ³¹„'-+,*7§´²±·j#)(')&†¶«®¯¯°±±°°°°±®©ª›1P‘‰‡‡…„ƒ†‰Š”𡦩­²·»¿ÂÅÅÅÅÃÀ»µ«ž“ƒspoqvy{~~|~‚‰— ©¯±°¡}„›¥ªª©§N  + + + + +  ))()))+1*"!"'/35QOMJJFGECAAA><. + +  p«­²²°¯­ª¦ œ€€ˆ‘‘Ž‹ˆ‚|wqiggfbbbeimpqsuzˆŽ—£­²¹¾ÂÄÆÈÈÉÉÈÇÇÈÈÇÈÉÉļ¹¶µ±³Œ$%$%#t³®°²³´´µ´³³²³®;++*+*‰¶®±­@"&&&!X°««¬¬­°±°¯¯®¬«§¤Ÿ£g4‡…„†…„…ˆŠ‹‘–š ¦ª¯¶¹¼¿ÂÅÇÅÆÂ¿»µ¬Ÿ•voprux{}~~|…Œ”œ¤­±´¯—|Œž§ª«§£E  + + + + + + *)(()((*3*#!#-44MIGGHED@?@?=>:/ + + + +  r¨¬²³±°­«§£Ÿ˜‡‡Œ‘’Š…€{vojggeda`fhlnorsy~~‡‘¡­³¸¼ÀÃÅÅÇÈÈÇÈÉÈÇÇÈÉÇÅ¿½ºµµŒ$&&(%w¶±²µ¶¶·¹¸¶µµ´»Z$))+&m¹¯´—+&$$$+”°ª¬®¯±²±°¯®¬ª¨¤£Ÿš‰''€………„†ˆŠŽ‘”£¦¬±µ»ÀÃÅÆÇÇÅÄ¿»¶­¥•„uonqtwy}~~}~ƒŠ– ©¯³²«Ž‘¢«®¬¦=  + + + + + #)()(()))*/-" ".33GFFDBAA?>>=<;73 + + + + +    sª«²´²¯««©¥ šŒ‚‚‹’“Œˆ†ztnihhfcbbegjmqsuv|€…‘¢«³¸½ÂÄÆÆÆÈÈÈÉÉÈÈÈÆÈÇÆÄ¿¹ºŽ%()*'~¹²²¶··¸»»¶¶µ³¹n())+'^¹²·~$'#%$Gªª«®®¯±°®­¬¬ª¨¥¢ ™‘’;'|‡ˆ‡ˆ‰‹Ž‘•œ¡§¬´¹½ÁÆÈÊÊÉÇÇÀ¼º²§™†wnmptx{}~}}‡Œ“›¤­²µ²¦…–¨¯¯¬§›; + + + + + + + #')(((*((''2- '/2GCAA?;<<=;<;876 + + + +     s­¬²´´±­«ª¦£œ…‚Š“”’‘Œ‡„yrmjihgebcejmprruz|€„‘ «²·½ÀÂÅÆÅÅÆÆÇÇÈÉÉÉÇÅÄ¿¼¿(-+,+‚¸³µ···¸¸·¶´³²·z&)')'U±«±j $#%!_«¥©©©ªªªªª§¦¤¢¡™•‘“G%}Š‹‰ŒŽ”˜œ£§¬³º½ÂÇÈÉÉÈÆÃ¿»·°§›‹|tkortx{~~~~„‹— ©±¶¶±Ÿ~‡›©®®¬¨›4  + + + + + + $(('(')('''(0-$,/DB>=<988987875/ + + + + + + +   oª«²µ´³°­©§¦ –‡„Š’••”’‰†‚~wrnkjihfdegimppruxz{Š›¨­´¹½ÀÂÄÄÅÅÆÆÈÉÉÈÆÄÄÃÁ¾Å‰-/+-.Џµ¶··¸·µµ¶¶´²¹%)&(%M®¨°W!"% o©¤¨¨©©ª©ª©¦¤¢¡Ÿš–”Ž“I+…Ž‘•—› ¥«¯·½¿ÂÆÉÈÈÇÄÄÁ½¸±¨Œ}ummptw{~€~~‡Ž”›£®³·´¯˜|Œ©­®­©˜, + + +  + + + + + &('''%&&&'''(/+(,A?<<;999844543- + + + + + {©¬±´´³±®«¨©¦™‰„Š’˜˜˜—”‰†‚{upnllkjhgfgkmpqqtvxz„•¡«±·¼ÂÄÄÄÅÅÅÆÇÇÆÅÄÄÄÃÂÈ|-210/–¾¸¹¸¸¹¹¹¸¶¶µ²ºs%&$&#P¬¤ªP #"«§©«©««ª©¨¥£¢¡ž›–“’E3’“••˜œŸ£¦¬±¶¾ÁÃÅÇÈÈÇÅÂÁ¿»´«¢’ulmrtv{}~€€€„Š’™ §°¶¸³©‡{’ ª¯¯¯©' + + + + + + + + +(''&&'&&'&$%&%/0%*<>>=9866543420/ + + +    0ާ®²³´³°¯­ª©§„ˆ•˜šš—’Œ‰„ztollllljgghknqqrtuwyzœ¦±¼¾ÁÁÂÃÃÃÄÄÅÆÄÆÆÆÄÃÉo59:7:¦Áººººº»º¹·¶´±·Z!$#% Z¬¤ªQ!xª¦§§¨©©©§¥¤¤¢Ÿ™–“‘“=?˜•—™ž¡¥¨­³¹¿ÄÅÇÉÉÉÈÆÅÁ½º´¬¦—‡yrmpuwx|~€€‡Ž—ž¤«²¶·³¢‚‚–¥­°²°©Ž"  + + + + + (''''&&&&&%%%$&+-$'::::754420//.-) + + + + + + + +   T›¡­³µµ´±°®«©¨¢‘……‹’•˜˜—•’Œ…‚~ytommmnmkhiilpqrsttvxy~‰—¢¯¶»¾¿ÁÁÃÃÃÃÃÅÇÆÆÃÁÅ_89>=H²¾»º¹¹¸¸·¸¹µ°¯®?$$"$i«£«Zd©¤¦¦¦¤¥¨§¦¥£Ÿœ›˜–”—Š(Wœ—œŸ£¦«¯²·¿ÆÈÉÊÊÊÈÈÅÃÀ¼³«£—†zrnpuxx|€€„‹•œ¢©¯¶¸¶²—|†š¨¯²²¯ª…  + + + + + + + + !('&'''&%%%%%%$$$-0$776753210.-,++* + +  {¡ ¬³µ´³²°¯®«©¢•Š…“•˜™š™•‘‹†ƒ}xsppnmnolkkknprsstuusuz‰™¦´º½¾¾ÀÁÂÀÀÅÅÄÃüO:9TgtthR4-42.00.‡®‘‡:"fmi'axw~…‹•–œ ¤§©ª«ª««¬°µ¼ÀÂÁ¿¼»»¼¶©”ˆ†‰’Ÿ©®°²´´µ¸¶°£“¨±´´¯¥¦¥“y- + + + + + + + + +  33-,,,++*+*+*)*)))++**)(()&((&' + + +  + + +  =³ÈÊÍÉÅÉÈĽ®—™¡©°´·¸¸¸·¶··¸¹¹¶­ž”Œ‰‡†‚|wsonpruvx+,F=†ƒ‚) %*/3À»¼ÀÁÃÂÃÆÆÆÆĄ̈F4/..-.129ŸÌÈÊÊÊËÊÉÊÊÊÉÉÉÊÉÎÇ‚F523/*3./3A­º¸··¶³³²°®¬«©§¦¦¥¢ž›—“‘‹Œl H˜”w!!ƒ¡ ¢£¤¢¢  Ÿ  Ÿ¢¦®´¹½ÁÃÀ¼»½½¸¬šŒŠ•ž¨¯³²²µ·¹º·³ª–©°³µ¶·¶¯©²¯£F  + ##$##%,,,/0.164.-,+********++*)))**(''((+.0/4956:758;89=@9:;7 B±ÇÎÏÍÉÊÊÆÂ½»¹§—“˜£«±¶¸¸¹¸·¸¹»½¼ºµ®£›’‘˜š˜”‹†„‚€{x(/rrkW}{y%/ššœ ££¤¨œ3$&%'%c·±³µ¹¢1,.,14ƾÁÁÁÁÀ¿Å21,+0T·´µ´²±°°±¯®¬°®®®¬¨¤žš“‘‡†‹‘z$<”“%d¥¢¤¤¤£¡  ŸžŸ” ®°·»½Á¾¼¾½»³¤”ŽŒ‹Ž–£¬±±±²µ¹º»¹²¦‘’ ª²µ¶¶··µ«®¯¥‘…f + + + +$##$$&-/.11-/172-*-+*****,*)()*))***''(),1-.11011345578<<==<5 V½ÇÎÎËÈÊÊÆÁ¾¼¸©š“™¢©®²¶¸º»º¸¸¹¼½¼¶°©Ÿ•Ž”›š—•’‹†…ƒ~}/'suyM#r}|}**„”•“”˜™›¤b  1›¯¬­®®³S&*(+*_¿¹ºº¼¾¾ºÁ}**)(*]µ¯°®®­¬¬«ª¨©s`b_[USKC<81`‘’‚)0Œ•Œ,:¢§¥¤¢ œœœœ JV­¸¶¼¿ÂÀ¼½¾½º¬™Œ‹Œ“ž¨°±±±³¶¹ºº¶¯ •£­´µµ¶¸¹¸­­°©™…z, + + %$$%%)-0/00-,-26.+,,**+**)**(**)))))'(,+-../00//3355565799:95 + vÃÇÎÎËÉÌÉÄÀ¾½º¬”–¡©®²³·º¼¼ººº»¼¼¸°§¢›“‘–››™–’†„„…3"mzvu)E€…‰0*…Ž‘’‹,X¢¢¦§¨§®s$#'&5¡¸±±³¶¸¸¼r%%%#$T®¨¨§¦¦¤¤¢¢¢¡C!Q’‹/(„““@ f«¡¡¡ž››››˜¤w'$R«¿ºÁÀ½»¾¾¼³¤“ŽŽ˜£«±²²±³¶¸»¹³­œ‘—£­´µ¶¸¹¹¸°«¯­¡ˆ€Q + + + &$$%&*///0/-,,-45,++++++++**)))()*)*'&3/000.10038766777596987! &˜ÃÉÏÏËËËÇÃÁ¾¼º¯ –•ž¦¬°´¶¸»¼º¹º¼¼¼º´«¢–’’˜š›™—•ކ‡‡2!o~{}\f‚ŠŽ3+‡ŽŒ‹ŒŒ‘\$|”—›žŸžž’* #j±§ªª«ª«±m""C¥ žžœšš˜™œ›> T‹Œ’4!}‘•_ '~ª Ÿ››š™— ”1"'$D¾¿¾º¼À¾¹¬˜ŽŒ“§¯±±±²´·¸¹·±¨—˜¥­³·¶¸¸¸¸°¨¯±©’€r + + "&$$&(,//0/.--,-.72++++)*(*))))))))**((6324301128;667:88:=8:9:( + =¯ÄËÐÐÌËÊÆÃÀ¾½»²£˜”œ£ª¯´¶¸¹º¹¸¸»½¼¼º°¤›—’“•˜šœš˜•‹‹8"sƒ€~32„‡Œ‘1*„‹ˆ†…ˆ…25‚”˜œŸ¡šžQ2™¢¢¢ ¡¡¤o/Ž“’‘Ž‘–˜“7_ŒŒ’: y’•„'#.~§žšš™– œ='&(&~û¸¹¿Á½¶¥“Ž˜¢«±²±±³µ·¹¹µ¯¥–‘𥮳¶·¶¹¹¸´«°³¬œ€„5 %''&&),...-----,,091,,.,*)**())))()++**<8798549<9:6::<<=AB?@A@7 +  f¹ÄÍÏÎÌÍËÄÂÀ¾¾¼´¥™•›¡§®²¶¸¹¹¹¹¸»¾¾¾»³©—“‘‘’•˜š›š—•’’:!x‹‡ƒŠa^‘“–‘0(€Ž‰‡ˆ‡Žd(6@JOT[`cIh¡—–—™˜›z]‡‚„…‡ˆ‘”–’1_ŽŒ7 v–•žK! )qœ¡›ž¡‹>&%&%9®¼µ¶»Á¾¸­š”𦮲²²²´¶¹º¹¶­£’‘œ§®²µ¶·¸ºº¶­¯³°£‹ˆ[ ((*))+,///.-,-.-,,27.,,-,++*)))))()**)*<<=>AB@CEEFEHHGO + 4™ºÄÎÏÍÌÍÊÆÂÀ¾¿¾·¦›“𢍮²³¶¹¹¸¸¸º¼¾¾¼¸®£™–““–˜˜™—–’4&}Š‹‡>‡™——˜‰&#yŒ‡†‡Š‹6-‹‘І}.,{‚ƒ‰”••H.--/-$fŒ‰†3"{“—Ÿ+ FrxZ&#"$$²²¸ÀÁ½°ŽŽ‘— ª°³³²²´¸»½»·¬ Ž’žª°³´´·¸º¹·®®²±©•ˆy  ()*+,......--,-..-.22,,,+-+****)*)()(();<DFEEFKLLJMNNOKNLMILR]C  .œ³½ÎÐÎÌÍÌÈÄÂÀÀÀÀ»®¡’“Ÿ§­°µ¸ºº¹¸¹º»¼¾½ºµ­¢™•“‘‘’“““”•••tH'-†“˜›šš™™~k‚‚ƒ‡?#xƒ€{{zkJ‚‚‡ŠŠ‰Š’•™˜”–tnЇ+&ƒ—› Ÿ¡b ""!!"#T¸¸®±»Á¿¸«›Œ—Ÿ¨±µ´³³µ¸»½¾¸°¤•Ž—¡«¯±´´¶¹¹ºº³ªª°±¤‹‹k + *()((&).0/.-././12:82270,++++++++*))*+**BBFIJJKKNOQLNOOPNOMLOQPSD \¹°ÁÍÏÌËÎÌÆÃÁÁÁÁÁ½±¤•’𥬲¶¹»»¹ºº»¼¼¼»ºµ°§ž˜”‘“––—•’“••œˆ}—“‘”™œœ›˜–‘{"`„€€„r!'M‚}zx|OG‡ˆŠ‹Œ“•–•”\!u‰†t#+…–œŸšŸV!""$#f½¹¯®µ¿Â¼²¤”ŽŽ”œ¥°³¶´³³µ¹¼¾¾¸­¡”™£ª¯±±³µ¸º»¼·¬ª¯²¬”†ƒ. #*((('(*.0//..////0638Kd…™“‹‹‰ŠŒ‘”šŸ  ›–••–˜› ¢¥¬µ»¿Åľ»¾Â½±£—‘’‘“”›¥¯µ¸·µ³³´·¼ÀÀ¾¶¬Ÿ‘Š™¡ª±´·¸¹»¼½¾¾¾¾¾µ°±³µµ³¤Š‰Y2/+-.++.00.//00/-./1420/...,+,/43.+****+-,SQRSRQPWWWRQQTVUSRTUWVSUTTW9 + PÁÈÈ¿²¿ËÄÁËÒÌÉËÏÒ×ÒÏÍÎÑÔʸ¡—”“”˜¥­²·»¼¼»»¼¿¿ÀÀ¿¾º´®§¢›˜—šœžŸž›™™˜–’’–˜œœŸ    ŸŸž››ž £§ª°³¯¯­©¦¢—““••““””—šœŸ£¥©­¯°±²³´µµµ¶¶µ´³®®­«¨¢œœ ¡•ŽŒ‹Ž‘‘“•˜˜™šš˜’’“”—𣍬³»ÁÄÄ¿¼½Âý·§™“’‘’‘”™¡¬´¸·¶¶µ´¶º¿ÂÀ¼²¦–Ž‹‘œ¤­°µ·¹º¼½¾¾¾¾½¼·±±³µ·¶«‰q4.,--*).///0/.//../07600.-..,+-/44,)**+++*[Y]ZT[[^eaZ]YX[YW\XXXW[XYVSG”ÎÈȺ²ÃÈÂÂÌÒËËÏÓØØÒËÌÍÑÔË»¤—”•”•¡«±¶¹¼½½»½¾¿ÀÁÀ¾»¹³¬§¡ž›š™šœŸžžœš™™”Ž“—œ £¥§¦¥¦£¢¢££§§¤¤¦¨«­¬­¬§¢™‘‘““•—–™œž¢¦ª®²¶¸¸¸¹º¹º¼»¼¿¼¸·µ´°­©§¤£ œ˜“Œ‹Œ‹Š‹‹Ž‘“””’ŽŽ’•˜œ¢¨®³ºÀÆÇÄÁ¾¿Âľµ«”’”’‘“–œ¥°·¸¶µµ´µ¹¾ÁÁ¾¸¯ Š‹•Ÿ§®´·¸¹º¼¾½½¼»»»¶°±³µ·¶²™Š‚E20,)++.0/./../../.0C¾ÍÍÌÀ¬¸ÉÆÃÄÌÔÖ×ÕÔÐËÈÇÉÌÖÚÒ“‘‘”ž¨°µº¼½¾¾¾¾¿ÀÁÁ¿¼¸µ¯¨¤¡  Ÿ ¡¡¡¡ ›š™™—••—˜™™šœŸ£§¨¥££¢¥¥¥¥¢Ÿ›—”ŽŒŽ•—¢¦¬°¸½¿ÂÄÅÆÅÇÇÆÅÄÂÂÂÀ¾½¼º»¼½¾»·´¯¨£œ’ˆ…„‚‚‚„„„††…‡ˆˆ‹‘—¢§«°¶¼ÁÅÇÄÀÁÄÆÂ¹±¥™“’•“‘’˜£®´·¶µ³²´¸¾ÂÅÿ¸­Ÿ‘‡…‘§¯²´¶¸º¼½½º¹¹¹¼¼µ±³´·¸¹¸°š‹†M00.,,00//.-,-./-+,-/0372..--,*,-051-)***PRRWPPRSSTX[RPSRSQROLNPRQPPPNƒÐÌÌʹ¯¼ÉÆÂÃÌÔÖÖÕÒÍÇÅÆÇÍ×ÚÓÆ²š”’˜¢ª±¶»½¿¾¾¾¾ÁÁÁ¿½¹·³­©¦£  ¡ ¡¡¡¢ žœœœœœžžž¡¢¢   ŸŸœ˜–“”••”’‘‘’•˜œ¡¦©®²·º¾ÁÂÃÄÄÄÄÃÂÀ¾½¾½»¹·¶µ´´¶·µ³¯ª¢™ˆ„ƒ€€ƒ„ƒ…†††ˆ‰“™¤¨«®´»ÁÅÇÆÃÁÅÇý²¦›””””‘“Ÿª´¶¶¶µ´µ¸½ÁÄÄÀ»µ¨™Š„‡” ¨¯³µ·º½¾½»º¹¹º¼¼´±³µ¸¹ºº²žŠg/31,,11//0/-,...-,,..132/.--+,,-,/3.+**)TXSTQQSTWVVXPNRTTQSSPNOPNOQNT0 C»ÌÌËŵ±ÀÉÇÂÂËÓÔÕÕÐËÆÃÄÇÌ×ÛÕȶ–’”¥­´¸º½½¾¿¾¿ÁÁÀ¿¾¼»·³®«§¤¤¢¢££¡ ¡  ŸŸ  žž ¡¢£¢¢¡¡¢¢¢¡ ›˜–••––—˜™š ¡£¥ª¯³¶¸»¾ÀÂÃÃÃÃÁÁ¿¾¼º·¸¶³±¯®®®¬«¨¨¦¡œ–‡€€ƒ…„‚‚…„†‹‘•𤍫¯´ºÀÆÉÇÃÃÄÅÄ¿´§““””’‘—¥°µ¶¶µ´³·½ÁÃÿ¹°¡‘…ƒ‹™¤ª®´µ¹»¼½½»¹¸¸»½¼±°´µ¹º»ºµ¥‹Œ~3*1+,/.////.-,-/-----./01-,,--,,+.030*'-UXSRSRWXZVVXPOQSTRPQRQQOOQPPY@ }ËÉÌËŲ²ÆËÈÄÀÉÓÕÕÔÐÉÅÃÅÈËÖÜÕʺ¢—“Ž‹˜¡ª¯µ·»½¿¿À¿ÀÀÁÀÀ¾½º¶³­¬«§¥¥¤£   žŸŸ Ÿ  Ÿ¢£¤¦¦¥¤¥¤¤£¢¡Ÿ›šœœ››œ ¢£¦ªª¬°µº½ÁÁÂÂÂÃÂÂÁ¿¾½º·¶³²³°­ªª«ª§¢ ›˜“‡„ƒ‚‚€~ƒ…ˆ’˜›ž¢¦ª¯´»ÁÅÉÈÄÂÆÇž¶©–’“”’• ­´¶µ´´³µ»ÀÁÃÄÁ¿¸«›Š‚„‘Ÿ¨ª®³·º»¼¾¼º¸¸¸»½º±²¶¶¸ºº¹¶ªˆŽF"//-.00.-..,-./.-,+-/02/,+,*+,,./47.+,TSQQSVTRWSUUPPPQPONPQSSSTUSTYM.«ÊÊÌËÀ¯µÆÊÈÅÁÇÓÕÖÔÏÉÄÂÄÇÍÖÚÕ˼¤–”‘Ž‹Œ’¦¬´¸»¼¾¿¿¿ÁÀÀÁÁÀÁ½¹¶±¯±­©¨§¦¥¢¡žžŸŸ ¢¡¢££¤¥¤¤¤¤£¤£¡¡ŸŸ¡  Ÿ¡¢¤§ª®±´·¼¿ÃÅÅÄÄÂÂÀÀ¿½º¸¶´±¬ª¨¥¤£¢£ œš˜Šˆ…ƒ‚ƒ€}}~‚†ˆ‹’–› £¥¨ª¯¶»ÂÇÉÉÅÂÅÇÄÀµ¨›—”“““Ž“žª²¶µ´³´¶º¿ÂÄÄÃÀ¼µ¥‘…„—£©«®³¸¼¾¾¾»º¹¶¸½½º¯²¶·¸¹¹¹¸®”†Ž_.,,-/.----,--.-,++,.00,*++*,,,,/42-+RPMRUSPQSTSRSPPONRSOPQRQRTQQQS)eÉÉËÌʼ­·ÆËÈÄÁÅÑÕÖÔÍÇÃÃÄÇÍ×ÛÖÍ¿¨™•’ŒŒ˜¡¨°¶º½¾½¾¾ÀÀÁÁÁÁÁÁ¾º¶µ´±­¬«ª©§¥£ ŸŸŸ ¢¡¡¢¢¢  ¡¢£¥¤¤¢ Ÿ ŸŸ ¢¢¥¥¨ª®±µ¹»ÀÃÄÅÆÆÅÄÃÄÂÁ¿½»·µ³±°®©¦¡žš“’‹„}|}„ƒ}~ƒ‡‘–𢤧©¬°µ»ÁÇÊÊÅÂÄÅü²¨œ”“””“’œ§°´¶´³²µ¹¾ÁÂÄÿ¼²žŒƒ‚Žœ¥«¬­³¹½¿À¿»¹¸·¸½½¹¯µ··º¼¹º¹±žŠŠv$,-,../...,,,-..-*+-/.++**,-,+++.4/+SPQUTRVVSSTSTQQRQSQOQNQOQROOOQ=(œÇÇÊËÆ¹«ºÆÊÈÄÀÃÎÒÔÒÌÇÄÃÅÇËÔÚ×Ï«œ–•’Œ“›¤«²¶¹¼¾½¾ÀÀÁÁÀÂÂÁ¿»¹¸¶´´²¯­¬¬ª¦¤¡ ¡¡¢¡    Ÿ¡¢££££¢¡ ¢£¡¢¡£¥©¬®²µ¸»¿ÂÄÅÇÇÈÆÅÅÄÃÂÀ½º·´²°®®­«§¢›–‘Žˆ†~~……ƒ€„†‹‘•–™œ £¦¨©¬°³º¿ÆÈÉÅÂÃÅû±ªŸ–““”“ŽŽ—£­³µµ²²´¹¼ÁÂÂÂÁÀ½¹®š‰ƒ‡•¢¨¬¬­²¹½ÀÀ¾¼¹·¶º¾¿¸°³¶·º¼»»ºµ¤Œ…€=).////0..----+++,-.,*,,*,,*,,*,44- \ No newline at end of file diff --git a/libs/jpegrecoverymap/tests/jpegencoder_test.cpp b/libs/jpegrecoverymap/tests/jpegencoder_test.cpp index 2d144f07a9..4cd2a5ef8c 100644 --- a/libs/jpegrecoverymap/tests/jpegencoder_test.cpp +++ b/libs/jpegrecoverymap/tests/jpegencoder_test.cpp @@ -25,6 +25,9 @@ namespace android::recoverymap { #define VALID_IMAGE "/sdcard/Documents/minnie-320x240.yu12" #define VALID_IMAGE_WIDTH 320 #define VALID_IMAGE_HEIGHT 240 +#define SINGLE_CHANNEL_IMAGE "/sdcard/Documents/minnie-320x240.y" +#define SINGLE_CHANNEL_IMAGE_WIDTH VALID_IMAGE_WIDTH +#define SINGLE_CHANNEL_IMAGE_HEIGHT VALID_IMAGE_HEIGHT #define INVALID_SIZE_IMAGE "/sdcard/Documents/minnie-318x240.yu12" #define INVALID_SIZE_IMAGE_WIDTH 318 #define INVALID_SIZE_IMAGE_HEIGHT 240 @@ -43,7 +46,7 @@ protected: virtual void SetUp(); virtual void TearDown(); - Image mValidImage, mInvalidSizeImage; + Image mValidImage, mInvalidSizeImage, mSingleChannelImage; }; JpegEncoderTest::JpegEncoderTest() {} @@ -89,6 +92,11 @@ void JpegEncoderTest::SetUp() { } mInvalidSizeImage.width = INVALID_SIZE_IMAGE_WIDTH; mInvalidSizeImage.height = INVALID_SIZE_IMAGE_HEIGHT; + if (!loadFile(SINGLE_CHANNEL_IMAGE, &mSingleChannelImage)) { + FAIL() << "Load file " << SINGLE_CHANNEL_IMAGE << " failed"; + } + mSingleChannelImage.width = SINGLE_CHANNEL_IMAGE_WIDTH; + mSingleChannelImage.height = SINGLE_CHANNEL_IMAGE_HEIGHT; } void JpegEncoderTest::TearDown() {} @@ -106,5 +114,12 @@ TEST_F(JpegEncoderTest, invalidSizeImage) { mInvalidSizeImage.height, JPEG_QUALITY, NULL, 0)); } +TEST_F(JpegEncoderTest, singleChannelImage) { + JpegEncoder encoder; + EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), mSingleChannelImage.width, + mSingleChannelImage.height, JPEG_QUALITY, NULL, 0, true)); + ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); +} + } -- GitLab From 63740b961ea0aceb7153c374f9761c44506e6b1a Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 20 Oct 2022 10:28:08 -0700 Subject: [PATCH 0404/1310] Use getrandom for device input event ID generation On device, let's go back to using 'getrandom' for the generation of random input event ids. This should be faster than reading from /dev/urandom. This is a partial revert of the previous patch. After this patch, we will only use '/dev/urandom' on host. We may be able to switch to 'getrandom' for both if we ever switch to musl for host. We can revisit it then. Bug: 254215895 Test: m && atest --host libinput_tests Change-Id: Idc6241facbd93f3f71ae90567db831d96a5fc98b --- libs/input/Input.cpp | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index a6f6b14bae..c1eb8e2a12 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -35,6 +35,9 @@ #ifdef __linux__ #include #endif +#if defined(__ANDROID__) +#include +#endif using android::base::StringPrintf; @@ -110,8 +113,11 @@ const char* motionToolTypeToString(int32_t toolType) { } // --- IdGenerator --- - -static status_t getRandomBytes(uint8_t* data, size_t size) { +#if defined(__ANDROID__) +[[maybe_unused]] +#endif +static status_t +getRandomBytes(uint8_t* data, size_t size) { int ret = TEMP_FAILURE_RETRY(open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOFOLLOW)); if (ret == -1) { return -errno; @@ -130,7 +136,22 @@ int32_t IdGenerator::nextId() const { constexpr uint32_t SEQUENCE_NUMBER_MASK = ~SOURCE_MASK; int32_t id = 0; +#if defined(__ANDROID__) + // On device, prefer 'getrandom' to '/dev/urandom' because it's faster. + constexpr size_t BUF_LEN = sizeof(id); + size_t totalBytes = 0; + while (totalBytes < BUF_LEN) { + ssize_t bytes = TEMP_FAILURE_RETRY(getrandom(&id, BUF_LEN, GRND_NONBLOCK)); + if (CC_UNLIKELY(bytes < 0)) { + ALOGW("Failed to fill in random number for sequence number: %s.", strerror(errno)); + id = 0; + break; + } + totalBytes += bytes; + } +#else #if defined(__linux__) + // On host, / GRND_NONBLOCK is not available while (true) { status_t result = getRandomBytes(reinterpret_cast(&id), sizeof(id)); if (result == OK) { @@ -138,6 +159,7 @@ int32_t IdGenerator::nextId() const { } } #endif // __linux__ +#endif // __ANDROID__ return (id & SEQUENCE_NUMBER_MASK) | static_cast(mSource); } -- GitLab From 07e2a48bf1640b68751e782d2ac79db444622c67 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 18 Oct 2022 19:18:16 +0000 Subject: [PATCH 0405/1310] SF: Carve out LayerHandle Move LayerHandle outside of layer so it can be used with the new LayerLifecycleManager. Make Handle fields like layer private and provide access functions so callers have to safely cast from a binder instead of directly casting it. Add layerid to LayerHandle since we want to expose the layer class in fewer places. Finally fold LayerCleaner class into LayerHandler. Bug: 238781169 Test: presubmit Change-Id: I86e08050cfcc89d68e6ed8fa0e8ff30063cf3603 --- services/surfaceflinger/Android.bp | 1 + .../surfaceflinger/FrontEnd/LayerHandle.cpp | 62 +++++++++++++++++++ .../surfaceflinger/FrontEnd/LayerHandle.h | 58 +++++++++++++++++ services/surfaceflinger/Layer.cpp | 27 ++------ services/surfaceflinger/Layer.h | 40 ------------ services/surfaceflinger/SurfaceFlinger.cpp | 40 ++++++------ services/surfaceflinger/SurfaceFlinger.h | 15 ++--- .../fuzzer/surfaceflinger_fuzzer.cpp | 2 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 3 +- .../tests/unittests/TestableSurfaceFlinger.h | 5 +- .../unittests/TransactionApplicationTest.cpp | 2 +- 11 files changed, 156 insertions(+), 99 deletions(-) create mode 100644 services/surfaceflinger/FrontEnd/LayerHandle.cpp create mode 100644 services/surfaceflinger/FrontEnd/LayerHandle.h diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index b65f1b48e3..e76b191807 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -156,6 +156,7 @@ filegroup { "Effects/Daltonizer.cpp", "EventLog/EventLog.cpp", "FrontEnd/LayerCreationArgs.cpp", + "FrontEnd/LayerHandle.cpp", "FrontEnd/TransactionHandler.cpp", "FlagManager.cpp", "FpsReporter.cpp", diff --git a/services/surfaceflinger/FrontEnd/LayerHandle.cpp b/services/surfaceflinger/FrontEnd/LayerHandle.cpp new file mode 100644 index 0000000000..75e4e3ae11 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerHandle.cpp @@ -0,0 +1,62 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "LayerHandle.h" +#include +#include "Layer.h" +#include "LayerCreationArgs.h" +#include "SurfaceFlinger.h" + +namespace android::surfaceflinger { + +LayerHandle::LayerHandle(const sp& flinger, + const sp& layer) + : mFlinger(flinger), mLayer(layer), mLayerId(static_cast(layer->getSequence())) {} + +LayerHandle::~LayerHandle() { + if (mFlinger) { + mFlinger->onHandleDestroyed(this, mLayer, mLayerId); + } +} + +const String16 LayerHandle::kDescriptor = String16("android.Layer.LayerHandle"); + +sp LayerHandle::fromIBinder(const sp& binder) { + if (binder == nullptr) { + return nullptr; + } + + BBinder* b = binder->localBinder(); + if (b == nullptr || b->getInterfaceDescriptor() != LayerHandle::kDescriptor) { + ALOGD("handle does not have a valid descriptor"); + return nullptr; + } + + // We can safely cast this binder since its local and we verified its interface descriptor. + return sp::cast(binder); +} + +sp LayerHandle::getLayer(const sp& binder) { + sp handle = LayerHandle::fromIBinder(binder); + return handle ? handle->mLayer : nullptr; +} + +uint32_t LayerHandle::getLayerId(const sp& binder) { + sp handle = LayerHandle::fromIBinder(binder); + return handle ? handle->mLayerId : UNASSIGNED_LAYER_ID; +} + +} // namespace android::surfaceflinger diff --git a/services/surfaceflinger/FrontEnd/LayerHandle.h b/services/surfaceflinger/FrontEnd/LayerHandle.h new file mode 100644 index 0000000000..5d0f783515 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerHandle.h @@ -0,0 +1,58 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +namespace android { +class SurfaceFlinger; +class Layer; +} // namespace android + +namespace android::surfaceflinger { + +/* + * The layer handle is just a BBinder object passed to the client + * (remote process) -- we don't keep any reference on our side such that + * the dtor is called when the remote side let go of its reference. + * + * ~LayerHandle ensures that mFlinger->onLayerDestroyed() is called for + * this layer when the handle is destroyed. + */ +class LayerHandle : public BBinder { +public: + LayerHandle(const sp& flinger, const sp& layer); + // for testing + LayerHandle(uint32_t layerId) : mFlinger(nullptr), mLayer(nullptr), mLayerId(layerId) {} + ~LayerHandle(); + + // Static functions to access the layer and layer id safely from an incoming binder. + static sp fromIBinder(const sp& handle); + static sp getLayer(const sp& handle); + static uint32_t getLayerId(const sp& handle); + static const String16 kDescriptor; + + const String16& getInterfaceDescriptor() const override { return kDescriptor; } + +private: + sp mFlinger; + sp mLayer; + const uint32_t mLayerId; +}; + +} // namespace android::surfaceflinger diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index def0dfaa49..631cf65de8 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -70,6 +70,7 @@ #include "FrameTimeline.h" #include "FrameTracer/FrameTracer.h" #include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/LayerHandle.h" #include "LayerProtoHelper.h" #include "SurfaceFlinger.h" #include "TimeStats/TimeStats.h" @@ -332,7 +333,7 @@ sp Layer::getHandle() { return nullptr; } mGetHandleCalled = true; - return sp::make(mFlinger, sp::fromExisting(this)); + return sp::make(mFlinger, sp::fromExisting(this)); } // --------------------------------------------------------------------------- @@ -774,7 +775,7 @@ void Layer::setZOrderRelativeOf(const wp& relativeOf) { } bool Layer::setRelativeLayer(const sp& relativeToHandle, int32_t relativeZ) { - sp relative = fromHandle(relativeToHandle).promote(); + sp relative = LayerHandle::getLayer(relativeToHandle); if (relative == nullptr) { return false; } @@ -1543,7 +1544,7 @@ void Layer::setChildrenDrawingParent(const sp& newParent) { bool Layer::reparent(const sp& newParentHandle) { sp newParent; if (newParentHandle != nullptr) { - newParent = fromHandle(newParentHandle).promote(); + newParent = LayerHandle::getLayer(newParentHandle); if (newParent == nullptr) { ALOGE("Unable to promote Layer handle"); return false; @@ -1936,7 +1937,8 @@ void Layer::commitChildList() { void Layer::setInputInfo(const WindowInfo& info) { mDrawingState.inputInfo = info; - mDrawingState.touchableRegionCrop = fromHandle(info.touchableRegionCropHandle.promote()); + mDrawingState.touchableRegionCrop = + LayerHandle::getLayer(info.touchableRegionCropHandle.promote()); mDrawingState.modified = true; mFlinger->mUpdateInputInfo = true; setTransactionFlags(eTransactionNeeded); @@ -2583,23 +2585,6 @@ void Layer::setClonedChild(const sp& clonedChild) { mFlinger->mNumClones++; } -const String16 Layer::Handle::kDescriptor = String16("android.Layer.Handle"); - -wp Layer::fromHandle(const sp& handleBinder) { - if (handleBinder == nullptr) { - return nullptr; - } - - BBinder* b = handleBinder->localBinder(); - if (b == nullptr || b->getInterfaceDescriptor() != Handle::kDescriptor) { - return nullptr; - } - - // We can safely cast this binder since its local and we verified its interface descriptor. - sp handle = sp::cast(handleBinder); - return handle->owner; -} - bool Layer::setDropInputMode(gui::DropInputMode mode) { if (mDrawingState.dropInputMode == mode) { return false; diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 1773c03a0a..c2b816988d 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -226,46 +226,6 @@ public: bool dimmingEnabled = true; }; - /* - * Trivial class, used to ensure that mFlinger->onLayerDestroyed(mLayer) - * is called. - */ - class LayerCleaner { - sp mFlinger; - sp mLayer; - BBinder* mHandle; - - protected: - ~LayerCleaner() { - // destroy client resources - mFlinger->onHandleDestroyed(mHandle, mLayer); - } - - public: - LayerCleaner(const sp& flinger, const sp& layer, BBinder* handle) - : mFlinger(flinger), mLayer(layer), mHandle(handle) {} - }; - - /* - * The layer handle is just a BBinder object passed to the client - * (remote process) -- we don't keep any reference on our side such that - * the dtor is called when the remote side let go of its reference. - * - * LayerCleaner ensures that mFlinger->onLayerDestroyed() is called for - * this layer when the handle is destroyed. - */ - class Handle : public BBinder, public LayerCleaner { - public: - Handle(const sp& flinger, const sp& layer) - : LayerCleaner(flinger, layer, this), owner(layer) {} - const String16& getInterfaceDescriptor() const override { return kDescriptor; } - - static const String16 kDescriptor; - wp owner; - }; - - static wp fromHandle(const sp& handle); - explicit Layer(const LayerCreationArgs& args); virtual ~Layer(); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 029dd9fcdf..f0271c6e8a 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -124,6 +124,7 @@ #include "FrameTimeline/FrameTimeline.h" #include "FrameTracer/FrameTracer.h" #include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/LayerHandle.h" #include "HdrLayerInfoReporter.h" #include "Layer.h" #include "LayerProtoHelper.h" @@ -1579,7 +1580,10 @@ status_t SurfaceFlinger::addRegionSamplingListener(const Rect& samplingArea, return BAD_VALUE; } - const wp stopLayer = fromHandle(stopLayerHandle); + // LayerHandle::getLayer promotes the layer object in a binder thread but we will not destroy + // the layer here since the caller has a strong ref to the layer's handle. + // TODO (b/238781169): replace layer with layer id + const wp stopLayer = LayerHandle::getLayer(stopLayerHandle); mRegionSamplingThread->addListener(samplingArea, stopLayer, listener); return NO_ERROR; } @@ -3701,7 +3705,7 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC using TransactionReadiness = TransactionHandler::TransactionReadiness; auto ready = TransactionReadiness::Ready; flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const layer_state_t& s) -> bool { - sp layer = Layer::fromHandle(s.surface).promote(); + sp layer = LayerHandle::getLayer(s.surface); const auto& transaction = *flushState.transaction; // check for barrier frames if (s.bufferData->hasBarrier && @@ -3969,7 +3973,7 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin setClientStateLocked(frameTimelineInfo, state, desiredPresentTime, isAutoTimestamp, postTime, permissions, transactionId); if ((flags & eAnimation) && state.state.surface) { - if (const auto layer = fromHandle(state.state.surface).promote()) { + if (const auto layer = LayerHandle::getLayer(state.state.surface)) { using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType; mScheduler->recordLayerHistory(layer.get(), isAutoTimestamp ? 0 : desiredPresentTime, @@ -4110,7 +4114,7 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime uint32_t flags = 0; sp layer = nullptr; if (s.surface) { - layer = fromHandle(s.surface).promote(); + layer = LayerHandle::getLayer(s.surface); } else { // The client may provide us a null handle. Treat it as if the layer was removed. ALOGW("Attempt to set client state with a null layer handle"); @@ -4415,7 +4419,7 @@ status_t SurfaceFlinger::mirrorLayer(const LayerCreationArgs& args, sp mirrorFrom; { Mutex::Autolock _l(mStateLock); - mirrorFrom = fromHandle(mirrorFromHandle).promote(); + mirrorFrom = LayerHandle::getLayer(mirrorFromHandle); if (!mirrorFrom) { return NAME_NOT_FOUND; } @@ -4522,19 +4526,15 @@ status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, gui::CreateSurface } args.addToRoot = args.addToRoot && callingThreadHasUnscopedSurfaceFlingerAccess(); - wp parent = fromHandle(args.parentHandle.promote()); + // We can safely promote the parent layer in binder thread because we have a strong reference + // to the layer's handle inside this scope. + sp parent = LayerHandle::getLayer(args.parentHandle.promote()); if (args.parentHandle != nullptr && parent == nullptr) { - ALOGE("Invalid parent handle %p.", args.parentHandle.promote().get()); + ALOGE("Invalid parent handle %p", args.parentHandle.promote().get()); args.addToRoot = false; } - int parentId = -1; - // We can safely promote the layer in binder thread because we have a strong reference - // to the layer's handle inside this scope or we were passed in a sp reference to the layer. - sp parentSp = parent.promote(); - if (parentSp != nullptr) { - parentId = parentSp->getSequence(); - } + const int parentId = parent ? parent->getSequence() : -1; if (mTransactionTracing) { mTransactionTracing->onLayerAdded(outResult.handle->localBinder(), layer->sequence, args.name, args.flags, parentId); @@ -4573,7 +4573,7 @@ void SurfaceFlinger::markLayerPendingRemovalLocked(const sp& layer) { setTransactionFlags(eTransactionNeeded); } -void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp& layer) { +void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp& layer, uint32_t /* layerId */) { Mutex::Autolock lock(mStateLock); markLayerPendingRemovalLocked(layer); mBufferCountTracker.remove(handle); @@ -6178,7 +6178,7 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, { Mutex::Autolock lock(mStateLock); - parent = fromHandle(args.layerHandle).promote(); + parent = LayerHandle::getLayer(args.layerHandle); if (parent == nullptr) { ALOGE("captureLayers called with an invalid or removed parent"); return NAME_NOT_FOUND; @@ -6209,7 +6209,7 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, reqSize = ui::Size(crop.width() * args.frameScaleX, crop.height() * args.frameScaleY); for (const auto& handle : args.excludeHandles) { - sp excludeLayer = fromHandle(handle).promote(); + sp excludeLayer = LayerHandle::getLayer(handle); if (excludeLayer != nullptr) { excludeLayers.emplace(excludeLayer); } else { @@ -6724,10 +6724,6 @@ status_t SurfaceFlinger::getDesiredDisplayModeSpecs(const sp& displayTo return NO_ERROR; } -wp SurfaceFlinger::fromHandle(const sp& handle) const { - return Layer::fromHandle(handle); -} - void SurfaceFlinger::onLayerFirstRef(Layer* layer) { mNumLayers++; if (!layer->isRemovedFromCurrentState()) { @@ -7016,7 +7012,7 @@ bool SurfaceFlinger::commitMirrorDisplays(VsyncId vsyncId) { for (const auto& mirrorDisplay : mirrorDisplays) { // Set mirror layer's default layer stack to -1 so it doesn't end up rendered on a display // accidentally. - sp rootMirrorLayer = Layer::fromHandle(mirrorDisplay.rootHandle).promote(); + sp rootMirrorLayer = LayerHandle::getLayer(mirrorDisplay.rootHandle); rootMirrorLayer->setLayerStack(ui::LayerStack::fromValue(-1)); for (const auto& layer : mDrawingState.layersSortedByZ) { if (layer->getLayerStack() != mirrorDisplay.layerStack || diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index b65dec4dfe..9a2f186aaa 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -272,6 +272,11 @@ public: void removeHierarchyFromOffscreenLayers(Layer* layer); void removeFromOffscreenLayers(Layer* layer); + // Called when all clients have released all their references to + // this layer. The layer may still be kept alive by its parents but + // the client can no longer modify this layer directly. + void onHandleDestroyed(BBinder* handle, sp& layer, uint32_t layerId); + // TODO: Remove atomic if move dtor to main thread CL lands std::atomic mNumClones; @@ -279,12 +284,6 @@ public: return mTransactionCallbackInvoker; } - // Converts from a binder handle to a Layer - // Returns nullptr if the handle does not point to an existing layer. - // Otherwise, returns a weak reference so that callers off the main-thread - // won't accidentally hold onto the last strong reference. - wp fromHandle(const sp& handle) const; - // If set, disables reusing client composition buffers. This can be set by // debug.sf.disable_client_composition_cache bool mDisableClientCompositionCache = false; @@ -771,10 +770,6 @@ private: status_t mirrorDisplay(DisplayId displayId, const LayerCreationArgs& args, gui::CreateSurfaceResult& outResult); - // called when all clients have released all their references to - // this layer meaning it is entirely safe to destroy all - // resources associated to this layer. - void onHandleDestroyed(BBinder* handle, sp& layer); void markLayerPendingRemovalLocked(const sp& layer); // add a layer to SurfaceFlinger diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp index 22d80ca3db..14384a7028 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp @@ -150,7 +150,7 @@ void SurfaceFlingerFuzzer::invokeFlinger() { sp handle = defaultServiceManager()->checkService( String16(mFdp.ConsumeRandomLengthString().c_str())); - mFlinger->fromHandle(handle); + LayerHandle::getLayer(handle); mFlinger->disableExpensiveRendering(); } diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index e2ae4f4d7f..a350020bf9 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -34,6 +34,7 @@ #include "DisplayHardware/ComposerHal.h" #include "FrameTimeline/FrameTimeline.h" #include "FrameTracer/FrameTracer.h" +#include "FrontEnd/LayerHandle.h" #include "Layer.h" #include "NativeWindowSurface.h" #include "Scheduler/EventThread.h" @@ -766,7 +767,7 @@ public: auto& mutableDisplays() { return mFlinger->mDisplays; } auto& mutableDrawingState() { return mFlinger->mDrawingState; } - auto fromHandle(const sp &handle) { return mFlinger->fromHandle(handle); } + auto fromHandle(const sp &handle) { return LayerHandle::getLayer(handle); } ~TestableSurfaceFlinger() { mutableDisplays().clear(); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 89812aad7d..7f471bc8b8 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -34,6 +34,7 @@ #include "FakeVsyncConfiguration.h" #include "FrameTracer/FrameTracer.h" #include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/LayerHandle.h" #include "Layer.h" #include "NativeWindowSurface.h" #include "Scheduler/MessageQueue.h" @@ -530,9 +531,7 @@ public: auto& mutablePrimaryHwcDisplayId() { return getHwComposer().mPrimaryHwcDisplayId; } auto& mutableActiveDisplayId() { return mFlinger->mActiveDisplayId; } - auto fromHandle(const sp& handle) { - return mFlinger->fromHandle(handle); - } + auto fromHandle(const sp& handle) { return LayerHandle::getLayer(handle); } ~TestableSurfaceFlinger() { // All these pointer and container clears help ensure that GMock does diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index de84faa5d2..9888f002fb 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -294,7 +294,7 @@ TEST_F(TransactionApplicationTest, PlaceOnTransactionQueue_SyncInputWindows) { TEST_F(TransactionApplicationTest, FromHandle) { sp badHandle; auto ret = mFlinger.fromHandle(badHandle); - EXPECT_EQ(nullptr, ret.promote().get()); + EXPECT_EQ(nullptr, ret.get()); } class LatchUnsignaledTest : public TransactionApplicationTest { -- GitLab From f75cddb31ac415b13f33d4d6805a2b95b194e062 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 25 Oct 2022 10:42:16 -0700 Subject: [PATCH 0406/1310] Mark addWindowTargetLocked as const While figuring out how to refactor 'findTouchedWindowTargetsLocked', it's helpful to understand where the state is being modified in that function. Let's mark addWindowTargetLocked as const to help with the analysis. Bug: 211379801 Test: build Change-Id: Ifd0a67bc915996e5401f3d624958660a8680ad19 --- services/inputflinger/dispatcher/InputDispatcher.cpp | 2 +- services/inputflinger/dispatcher/InputDispatcher.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index acd3d75fca..e191d93714 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2578,7 +2578,7 @@ void InputDispatcher::addDragEventLocked(const MotionEntry& entry) { void InputDispatcher::addWindowTargetLocked(const sp& windowHandle, int32_t targetFlags, BitSet32 pointerIds, std::optional firstDownTimeInTarget, - std::vector& inputTargets) { + std::vector& inputTargets) const { std::vector::iterator it = std::find_if(inputTargets.begin(), inputTargets.end(), [&windowHandle](const InputTarget& inputTarget) { diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index b20a548eb5..8356f3d27f 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -554,7 +554,7 @@ private: void addWindowTargetLocked(const sp& windowHandle, int32_t targetFlags, BitSet32 pointerIds, std::optional firstDownTimeInTarget, - std::vector& inputTargets) REQUIRES(mLock); + std::vector& inputTargets) const REQUIRES(mLock); void addGlobalMonitoringTargetsLocked(std::vector& inputTargets, int32_t displayId) REQUIRES(mLock); void pokeUserActivityLocked(const EventEntry& eventEntry) REQUIRES(mLock); -- GitLab From 0d12ba8b94924d8a1071a0fc54168f702b1c8e49 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Wed, 19 Oct 2022 20:44:51 +0000 Subject: [PATCH 0407/1310] libjpegrecoverymap: add JPEG decoder for YUV and single channel Bug: b/252835416 Test: jpegdecoder_test Change-Id: I418595d4e54ca0f6a25f442c95dd96a9a150b690 --- libs/jpegrecoverymap/Android.bp | 14 ++ .../include/jpegrecoverymap/jpegdecoder.h | 62 +++++ libs/jpegrecoverymap/jpegdecoder.cpp | 225 ++++++++++++++++++ libs/jpegrecoverymap/tests/Android.bp | 16 ++ .../tests/data/minnie-320x240-y.jpg | Bin 0 -> 20193 bytes .../tests/data/minnie-320x240-yuv.jpg | Bin 0 -> 20193 bytes .../tests/jpegdecoder_test.cpp | 102 ++++++++ 7 files changed, 419 insertions(+) create mode 100644 libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h create mode 100644 libs/jpegrecoverymap/jpegdecoder.cpp create mode 100644 libs/jpegrecoverymap/tests/data/minnie-320x240-y.jpg create mode 100644 libs/jpegrecoverymap/tests/data/minnie-320x240-yuv.jpg create mode 100644 libs/jpegrecoverymap/tests/jpegdecoder_test.cpp diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp index 9b0ee2e2a7..3ab2ba898e 100644 --- a/libs/jpegrecoverymap/Android.bp +++ b/libs/jpegrecoverymap/Android.bp @@ -49,4 +49,18 @@ cc_library_static { srcs: [ "jpegencoder.cpp", ], +} + +cc_library_static { + name: "libjpegdecoder", + + shared_libs: [ + "libjpeg", + ], + + export_include_dirs: ["include"], + + srcs: [ + "jpegdecoder.cpp", + ], } \ No newline at end of file diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h new file mode 100644 index 0000000000..2ab75503a5 --- /dev/null +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h @@ -0,0 +1,62 @@ + +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// We must include cstdio before jpeglib.h. It is a requirement of libjpeg. +#include +extern "C" { +#include +#include +} +#include +#include +namespace android::recoverymap { +/* + * Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format. + * This class is not thread-safe. + */ +class JpegDecoder { +public: + JpegDecoder(); + ~JpegDecoder(); + /* + * Decompresses JPEG image to raw image (YUV420planer or grey-scale) format. After calling + * this method, call getDecompressedImage() to get the image. + * Returns false if decompressing the image fails. + */ + bool decompressImage(const void* image, int length); + /* + * Returns the decompressed raw image buffer pointer. This method must be called only after + * calling decompressImage(). + */ + const void* getDecompressedImagePtr(); + /* + * Returns the decompressed raw image buffer size. This method must be called only after + * calling decompressImage(). + */ + size_t getDecompressedImageSize(); +private: + bool decode(const void* image, int length); + // Returns false if errors occur. + bool decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, bool isSingleChannel); + bool decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest); + bool decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest); + // Process 16 lines of Y and 16 lines of U/V each time. + // We must pass at least 16 scanlines according to libjpeg documentation. + static const int kCompressBatchSize = 16; + // The buffer that holds the compressed result. + std::vector mResultBuffer; +}; +} /* namespace android */ diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoder.cpp new file mode 100644 index 0000000000..22a5389648 --- /dev/null +++ b/libs/jpegrecoverymap/jpegdecoder.cpp @@ -0,0 +1,225 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include + +namespace android::recoverymap { +struct jpegr_source_mgr : jpeg_source_mgr { + jpegr_source_mgr(const uint8_t* ptr, int len); + ~jpegr_source_mgr(); + + const uint8_t* mBufferPtr; + size_t mBufferLength; +}; + +struct jpegrerror_mgr { + struct jpeg_error_mgr pub; + jmp_buf setjmp_buffer; +}; + +static void jpegr_init_source(j_decompress_ptr cinfo) { + jpegr_source_mgr* src = static_cast(cinfo->src); + src->next_input_byte = static_cast(src->mBufferPtr); + src->bytes_in_buffer = src->mBufferLength; +} + +static boolean jpegr_fill_input_buffer(j_decompress_ptr /* cinfo */) { + ALOGE("%s : should not get here", __func__); + return FALSE; +} + +static void jpegr_skip_input_data(j_decompress_ptr cinfo, long num_bytes) { + jpegr_source_mgr* src = static_cast(cinfo->src); + + if (num_bytes > static_cast(src->bytes_in_buffer)) { + ALOGE("jpegr_skip_input_data - num_bytes > (long)src->bytes_in_buffer"); + } else { + src->next_input_byte += num_bytes; + src->bytes_in_buffer -= num_bytes; + } +} + +static void jpegr_term_source(j_decompress_ptr /*cinfo*/) {} + +jpegr_source_mgr::jpegr_source_mgr(const uint8_t* ptr, int len) : + mBufferPtr(ptr), mBufferLength(len) { + init_source = jpegr_init_source; + fill_input_buffer = jpegr_fill_input_buffer; + skip_input_data = jpegr_skip_input_data; + resync_to_restart = jpeg_resync_to_restart; + term_source = jpegr_term_source; +} + +jpegr_source_mgr::~jpegr_source_mgr() {} + +static void jpegrerror_exit(j_common_ptr cinfo) { + jpegrerror_mgr* err = reinterpret_cast(cinfo->err); + longjmp(err->setjmp_buffer, 1); +} + +JpegDecoder::JpegDecoder() { +} + +JpegDecoder::~JpegDecoder() { +} + +bool JpegDecoder::decompressImage(const void* image, int length) { + if (image == nullptr || length <= 0) { + ALOGE("Image size can not be handled: %d", length); + return false; + } + + mResultBuffer.clear(); + if (!decode(image, length)) { + return false; + } + + return true; +} + +const void* JpegDecoder::getDecompressedImagePtr() { + return mResultBuffer.data(); +} + +size_t JpegDecoder::getDecompressedImageSize() { + return mResultBuffer.size(); +} + +bool JpegDecoder::decode(const void* image, int length) { + jpeg_decompress_struct cinfo; + jpegr_source_mgr mgr(static_cast(image), length); + jpegrerror_mgr myerr; + cinfo.err = jpeg_std_error(&myerr.pub); + myerr.pub.error_exit = jpegrerror_exit; + + if (setjmp(myerr.setjmp_buffer)) { + jpeg_destroy_decompress(&cinfo); + return false; + } + jpeg_create_decompress(&cinfo); + + cinfo.src = &mgr; + jpeg_read_header(&cinfo, TRUE); + + if (cinfo.jpeg_color_space == JCS_YCbCr) { + mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0); + } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { + mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0); + } + + cinfo.raw_data_out = TRUE; + cinfo.dct_method = JDCT_IFAST; + cinfo.out_color_space = cinfo.jpeg_color_space; + + jpeg_start_decompress(&cinfo); + + if (!decompress(&cinfo, static_cast(mResultBuffer.data()), + cinfo.jpeg_color_space == JCS_GRAYSCALE)) { + return false; + } + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + + return true; +} + +bool JpegDecoder::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, + bool isSingleChannel) { + if (isSingleChannel) { + return decompressSingleChannel(cinfo, dest); + } + return decompressYUV(cinfo, dest); +} + +bool JpegDecoder::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) { + + JSAMPROW y[kCompressBatchSize]; + JSAMPROW cb[kCompressBatchSize / 2]; + JSAMPROW cr[kCompressBatchSize / 2]; + JSAMPARRAY planes[3] {y, cb, cr}; + + size_t y_plane_size = cinfo->image_width * cinfo->image_height; + size_t uv_plane_size = y_plane_size / 4; + uint8_t* y_plane = const_cast(dest); + uint8_t* u_plane = const_cast(dest + y_plane_size); + uint8_t* v_plane = const_cast(dest + y_plane_size + uv_plane_size); + std::unique_ptr empty(new uint8_t[cinfo->image_width]); + memset(empty.get(), 0, cinfo->image_width); + + while (cinfo->output_scanline < cinfo->image_height) { + for (int i = 0; i < kCompressBatchSize; ++i) { + size_t scanline = cinfo->output_scanline + i; + if (scanline < cinfo->image_height) { + y[i] = y_plane + scanline * cinfo->image_width; + } else { + y[i] = empty.get(); + } + } + // cb, cr only have half scanlines + for (int i = 0; i < kCompressBatchSize / 2; ++i) { + size_t scanline = cinfo->output_scanline / 2 + i; + if (scanline < cinfo->image_height / 2) { + int offset = scanline * (cinfo->image_width / 2); + cb[i] = u_plane + offset; + cr[i] = v_plane + offset; + } else { + cb[i] = cr[i] = empty.get(); + } + } + + int processed = jpeg_read_raw_data(cinfo, planes, kCompressBatchSize); + if (processed != kCompressBatchSize) { + ALOGE("Number of processed lines does not equal input lines."); + return false; + } + } + return true; +} + +bool JpegDecoder::decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest) { + JSAMPROW y[kCompressBatchSize]; + JSAMPARRAY planes[1] {y}; + + uint8_t* y_plane = const_cast(dest); + std::unique_ptr empty(new uint8_t[cinfo->image_width]); + memset(empty.get(), 0, cinfo->image_width); + + while (cinfo->output_scanline < cinfo->image_height) { + for (int i = 0; i < kCompressBatchSize; ++i) { + size_t scanline = cinfo->output_scanline + i; + if (scanline < cinfo->image_height) { + y[i] = y_plane + scanline * cinfo->image_width; + } else { + y[i] = empty.get(); + } + } + + int processed = jpeg_read_raw_data(cinfo, planes, kCompressBatchSize); + if (processed != kCompressBatchSize / 2) { + ALOGE("Number of processed lines does not equal input lines."); + return false; + } + } + return true; +} + +} // namespace android \ No newline at end of file diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp index 6bc4bef806..7f37f611c7 100644 --- a/libs/jpegrecoverymap/tests/Android.bp +++ b/libs/jpegrecoverymap/tests/Android.bp @@ -46,4 +46,20 @@ cc_test { "libjpegencoder", "libgtest", ], +} + +cc_test { + name: "libjpegdecoder_test", + test_suites: ["device-tests"], + srcs: [ + "jpegdecoder_test.cpp", + ], + shared_libs: [ + "libjpeg", + "liblog", + ], + static_libs: [ + "libjpegdecoder", + "libgtest", + ], } \ No newline at end of file diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240-y.jpg b/libs/jpegrecoverymap/tests/data/minnie-320x240-y.jpg new file mode 100644 index 0000000000000000000000000000000000000000..20b5a2c0df5209ebe821d4220055012ac6d94993 GIT binary patch literal 20193 zcmex=oA#DyHnP8$!323`E1Vw_ae#K|QlE+HwUs-~`?sbyknW^Q3= z=I-I?6&w;879J59m7J2AmY$KBRa{b9R$ftA)!fqB*51+CHEHscsne#9glAUcUPH>GPMb z-@gC&`Ro5J1`bfL*)#mE4onHPJpRS>^!*bHyH*}NFFN_}l5Wke0=A0tYg1oMQ{%L` zaa*gBr7B!-?W^;08(D%iI47zI{SqiR{O5OgU}o{osxRwWdXBNjbWQd@_a}L2X_@y7 zPG-3;2P~5czpn8Ab)YsgCE&n2(U`_L%wI%#x~~WrSzndBFiqAlG_7Tt{nDW7oOvE+ zgJ)fsd~m(SkDI1nx9)E*S*?9?4d3=Pj)}HMH|I`GRJ?h}{KF&>p5L?1%QNkK2}YyNQlk-y4Ld#?XcUt7_z_ukyq{@JN!OMX-)vgEQkA2zU*ne%0R zY*U~e*SZH)f)}p+>s_+-aL+pJts5>+yJa)+N0@z=Y3P>Li5|RNPyg61+57eStyD?L z6KAVL*Y4;wx>FT8g<*16TY!|m*X2uV%gqWWMwd(HzMZ-8{MyRn9a|N3&dn`8mYI9t zx0TZ8#@l;CDw*PBCVpCP@|Nw6-{m_K59Tmm+F37sx7R&n)1{C}J%>CC|6aWPaoxU4 zsonlsD+HB&w0?0t`#PWV_K(X4cAcAC#@O}F%;ElB^>^DJ6lCv@E&Futop<##y@eaf zL#~H&Yvl)d{0r?~xqRNz#*=b$v*q{&b$qUsyS#mKd~4wK66F=QB`&lT{=S{b?sWg| z`Gx-(xVk;3oXlFfiH}jMs`*ZMX>Mn3;Ky?p!`67*Zz!nEUa>VvcVpjGq5bUgYaJC% z%562Y;nUJo3X_yL8=AOfLh<~nrFOC(s;feBEWWKO(rQylxneAP%Hn|S>r?-OUtJ7 zspMTLvr1*z!sc_f-`4MweJS+naEQQ$t9CcG@6X|>IeWNa-|VtKJB~ZY)qXhca&hZ? z#)^5b<2G;EeR-YfS+{3ldwN7eh2Gwsx489f=$b9N=d=Fc5B^x*@>SN~d-LUYKHd-C z#{B$nx>M9?g}q5?GY>(Iq8$epT)sZJYlL=BC#&udU4Id&AgxZ$nM+ znvMlEW(+b(dv0k)eS2SNDfuFMX`8@=_qK&G^A?x+9(gHOb6ruSHhU4zEWO@MZnp%D z&SW=!*!DI#a`%;|Ppp#+su?eTS@$^L=)CEQFCr2`*9tMrQdx7CHkIe)g!ms=@v!Xtl1n0-gd!d17nOqeXwWn}m|W?J`0rq743>*p6r%naRd z(ev`_Xt95aZU4SKiulh^wyExe`m(BjTVGi59=~Ps<9y(ju)9qR4;l}?juqM%VJ-8) zFn}re*)d)P#@jq@zE*NAa{IQNw6N8hdQtMYssB?x?*Na3$`jv*O3BaMc=DTQScz6$ zapUc-Px&1_)8)nMByTrQmd-Mlwcnk`{_kvTdZ)=s)BF5cR#`Eoi|^j{WV+-2eUZ-6 za{gt-@iIS{za{P6_+x%?uHDL~t_#lR7w9E#FBO+*Q!;XpWMS~%U-g^A`T4H5ah~Vc zvp&ynlHC6wLPE~pW)|+?-(&sY;{;pnG<%lF?R_TvY?17f zy*BNdd{nB-TKsm~(LE}YJlTA<+fLeh|8J17fNgf?N3~^_PXux(FJo12|MF>pr9@Kp z#_fqbwnkSYPEXf4@bS`wnEd2V30#*iuDupnKChrE?AXdO-M2S{M3VNbKeB7HzoEmC zrUI>P69p!PFg)J5Zrax~WlNebgexwHmUWJwS9Y?CdGfof{*T;tM5$bUm}B#trGE3h zEj5x$bi@i9Ppvrl>*{>L`<2@TA{V?f@-11woy*gB{N$xw{bHtj5?%b(Z+RkWxsE?% zzSiUIRS!!an;87O_P$}S!~2je>t$w_99dk{x3&M#S%0sOQZwI5IMy^zykN1e-u=4V zy^o1Wl|jaDomdTxm9FyY{oI3Lw}Jv-s5b;Bm%roz6jPwg~X)qZ?_aKk${ z;!Ccd?}4HvGbSs4Uhz-5VshL^)30BwmfsAsRgu3(oY|MUwmoz z%G{&pV~-4blgb66K7B_fe|fg6 zzB#IJ|CVbu$t6A8?NoREcH#JA5*FAy?eyEJtmpSky`H_Rvx`0C!DG>rQ8#Aq{BCqT zI5}&>0&9-rS+hL-6&+U`l3@s$zczl$W*+0sYjdMnwn=w2c0KtMzAj}GJ45)~*x1VJ zD*6_tyB8f%mi|0Byi|Jb2ZQr@Q@azZW|hr1exm1Dl-+iD@${o|%GL8{tb3zp&3I{V zCR3(V9>Y`j*D=r9g|lW|pLIZh&vV`GBgGs#l8y&$_^VdvscoCE=E#JqXB$gr-`_Ni z_cN2x@{OX$@7?bdS^26;>QQM%%BGn=7*(Dm?fC2V@Ijf@ld4%~E|*@K^>>rl$(;VY zsbBINN*Q*!s0g;G?ae=YY3--gnk;{_7fo_?<%*toeb%|1y91|&>joY)NT|&^wC%ad z7g1$_*F9H!*|$r)4RPJaEl@S9E$ma{d9_vTpE*@Nhn{@$Y|r~g@`q=i7k>3CZu_Fw zn=+)H&6#vq^g?S|pGV5EA9wi!r?SW$+wnI1aJ+;GxBG0DtA8xlo;a5>>7K96ZR?Hy zblMKu@GS|KPye|5k>A9cYxA@=U)ZCm_I9=2>hzKg`*%#`)_)y0d8vMXP2sY$qVqd! zq`s9K&NzE-mikV^)l8g$yICGD%T7NwPqO`RJg*6VdzH=_m8-LOx!7$i+9h{Pd3?#V zsP;zqnl%A0rxo5lDAC0c;4Y={FocKkCu7XM)dgxvjh-jpM$MDE_-Xcy3AV2nh-I)b zuq~c-)t%ApeO6M`%A_*}T8|hx&Q+}GjStV5`fXleT;{s0T$TQv`;w}!+M3vyOCno@Hwvfk!WmzO`Gv{g=e1jgfO5%3f$4@A#@V z&0o+RtvOT-#BwSXaD$#X2W` zv8XJL+q&kya(BF=^GmwIlRnUd}1$#CH7>$t*oek;~- z7tY)Itp8EQf{&?9l14UPW2#af?c4IBUNM&Su*{~Ne^#%$ymfZBnXX7{wC(DJ%}2_G z4b*~nU)s9%EBgc{zv6pa-o6vPE@9sqzkT}C1D0}MGbNJcPpv&FX*l`HmE7#9CuD20 z=3GH71F+fbJ!NHQJTlrlyNB|qUNMl zTbSH~>+1C%SVZG^6P4|drE!5CneT$e3e)lvy$aksP|hRx8=`{U)r%ZIYjS_XeaZ^bwN+} zT{g@+ynk(Wrr1*nLkrOj5^if86t2nbubGhfo$u_{P*2Ns%m=S+`q3Owy`1Hk|0#|a z*5PZ<8gAYADM7L(LGae97x$#+W&XQdVEJ~|>m{Yzw%?xDytR1R%5QsJmlyFA&NF@; z=e{zl_~rDxQ%^l?eurDGy0d2Kx3&y~CT`o!`ep6x zT;C*Xm-+e|*Y?^6eU{hwVfSp8b@blDtc`Y87jqssmmPPf?z!c;tZnO8s7+sdKhCpX z?-r+;y=&0@OZ)Qv&fT9STmR^N-~J7zzWjb$8X`}YTn}5X_HcV1+clMvcxBe~ZNExem`k>s2eI_B@3cR?wxX>6cfybGBXt^5^6h`@*Jp1RI{EaV?%Yj` z=BruLSoC)?_f@Pr|7iQMz041Nr%ko*be zR*(M-<&SInUd#}t3kxy$}R-U_b2lF=TSs(u9fBX5Pe(yAG->OvEql@%EPue-{@9sYWsYyCt zcN|a3o$zm7YE^m1{tRC6gY%f$k8WS|B*I@tUw*3COZzIL{d%?hHXq|0tA7{#2oIPs zb?23Ba%a;j zmmK{vyCX!blEHyJcyibE4|N^8nT1y{r3kt&SR`CmbkSJHvF!^rv?i{)Nt8 zpY5f1Qx<@<;m;KeQj2`f+Rj#1%%z*Bn@UhWpT&{rBTvS%<#Ni@JN_ z{D}&M>$a<7PUf8Xxc`)Iarr#MU0ZlB_j{e#@nf^>9-Yf~E4}u5Z(}WtQ~kRjH%Ptd zUZlQcn|=9r_s4PHe;Zd!-FlzRxmv09uJ*Cubq3itmvwAVFJn)t4wY1|c=Gre^X08s z5%(88?AcQ#b-g{(v1xrz$@8@(Qsx~W4n}T#KkMC>xt@6ef(q&j)~?$CZi`xK&X)o6U2dPL~Y#h2#R9&dbd?AO-l{QjgS~Q(orgXXfVZ zH@P%Z;O%)=i8#*}w%Lof?EBI9JE3BSXL*sXiMFPZpG0KD$~UY3Gw7-)U!VL&`;C8V zJntU!5A!|Ds#s^38!ZsG>iLn~-TdzE^uH62U)xvpcfbChCm%w$y`Qr6&JsTR=M(-j zNbUa6{w?T(eeXVzisHyC^F!K$_iZ!w)!lj_RK1$9RmQcKTE@5$yXeThl#DH+r5|+xT>E@4Bl`6IQhs>fRPTU$OX6i3absIq%L`ZFmr--?k^X^4o{& z!lw?Nce?uOSL)l@?zmTAw3)_A<}-{M<)^AFv#-~6#`?j#rUkNx+fg-?8s5B<-eHh;xE*3QEdlD~dg zul@0OkI7j0G_=t85vWIoEH>Z*p5E zePD9*H1*WmkLL$${gPt-{KSoD`?y7$-@U(e@wCQ?W0q@9mL9K}WV1ifo4<2ObJKCA zW!pc_S^8=H_ItbK`Tw{~j0{Xk_}8|5>we)ouf$eGrD^v~eG)Hkx@^-A^+(skEGL~f z=J-8T{@OmRa-J;O^}Lb)84l@{9=#A_d|P+jWl7e)M+<&G+jUl~zJK%mTgOf86f&Yx!tdtH zxIJ;soRXte`frymUE1IJ$B@Bh@0&C3>#i{WXGmMie{=KW{|t?Gk~KP8E}5=eA0FKG ztTIlzT5W>B*{O$9_?oY6uv7W*Jm&ApJ=x1MeqM|0b(UApOb3bf<_;2#V6U#ogPKlV5VEF9Rku81f2@!LulQw-lo0vX1i@BlR>U7tG zX)5#2EstEBBc|~9<7(~_r}X7ZgJ!+g`kC1OJYsv*M6JX73S<0xOxXn&DVr-imwnyT zl&pHVaNeEp;KaOd8@f9^0;>4FWYuZSaL|1?q3L;EZT5l>rdyUPsp_mVH=Z|j|0CPX zEj!Fyj#sR2QJ-C4z3XbPx##Z4Ngqyp%bIUwHFsUzys52^_ji8(usZWw`Rt|tEH6A+ z@bAUuyy$lNd>G%Hdmo?h2)9#ct@o!jjGI?Ueuj;F7tsVDX;ryhOF1H};>+)vP zYUgK_d=BqeOFYi!rfl@_+GhQ2z5I{=45ml+^VB#dvjoMQH&WtQ=yb+?#*zp8iE-(_ zHEx_;e*96{?yb}A&bYm*Gg-sW=I}As(7%iA1lxYQcYn!f%-_2*TH%%b^9}O9#Gk*8 z6@T;aHW_jFUa_v^W7`}T6)9g4Ib#k3gUzJu zGxd3EO%*nNNO5I1x@&KD$nbCWj=!`1Go;72|5NuJ&d>ZZsXSH`@!s!?aS}G`BJWX#F|GDSN;}W|G3`zqx}(&sdGi<-uwFe`#!U2 zo)W7Q!m2BF6>#S(U!LXiaqR_*`~Ml3n6@nVc=q(Fwkh-1M&yf#_jejad|wql>p#Pc zznf#qySAQ8x+-K^f7gat`~Bpz#)k|lujjnk-5zso-riO3rl&n%`W3GJZF6bK8NR@Z zb-xeF>F(aJy}Drb!hgT{+#iR3JNwAiIYYW>UiPE4_94^mJwMmucFykojNJ|2k7i^^ z?LD_tdgeO|<*6}@ar!GfSTPc-jKUcxxb==wCIw|(P1*N3ky)Vmy2iVllUId$NPK)B2` z{oeZYwd_ZKt-k-tY`Rd~u2m)*{Z1ZuPDb)TIyOZ!-IT zIN!15Z4z(T&wJ+*jSdu2wNHqJZu z@bCLeD>CmAiedUc1-4WJJlWgxtFhA6L|BSQrJDmq`IK`mXcLLKc+kU$bYz6 zpZ}%wqm3InoLPK+-_i4YGHO0jF=)d3e$k}?GcSYX9_e}+b_TQCQ zuGVXMAnB6e3&YSSm-TgjN*35hrI_QXp`)`?h(r0z~N^Y%nloi<1 z$e?b+b$e!Rjdfax-OC#ZkGB>|En9WzYeM$HnN{cK84@w}}!VpnBOaL*L# zH(r_eIUpYlXI8_!%$DaSe8#cc z51tq5=lbJzVXL@BpY@b*_1{Y#{A=4EoKupmTkX{s5_(!gp)uy#!iSbyUiV&H=kP`= zJ6l}CV5Y2xt!vK94NEt7N|dZ?Iv@2p#c(0x?8dWcuj)BVYb(#3FIi&m`enWSkIBm_ z@*lL#7k%IHKI+}S0^enm^W5hb_AQ?$%3fwu>o5Aa?7sXzrHXCOJoczBaL*N~Rk*z6 zaivoKx4VtcKZsVH{x#a?LP>|2mVq0_`I!7 z??ry?BFnZ%GTdwJo-7?z+Wnti{~>hW#$f2U`!jsTY1Y-FNwz)|$HJW$@qi{(Jq6B+I6%r1S_> zvIsb`?OGkr8?C{7I>^1qDong%pVklVhyPZUoGm|67FD)LG~$-kZV%?#%dNrJ_KHer zOpskXKd|fM)jgiM%U#YqU!L_d{jvSq$qOpXk6e$F+tD2`?U}~Kxo?x*dh#ThRU6M4 z-rBjc#^avC_O`#Wv$Q*|h1|WK&C~xpF7%`Sqc3s{d$(@sx{_snY5i;+U%i7u^?V-e zlh4aF?e=fGryytkVeY(bxpv3$B(EHjuZUd}Zh7F#x@X5dvirXlrfrFKnRM4Phe_M4 z!pqPnZByQsgrbwjt4_{46>;#u`K4(wor`j&C}y|&cT9JW+#?pAFXH%T>#Yif!iiDy zKTPngmYnt|ePX+1vfrg8nM*V8uY5W0(C)oVfjPZ;mCTLNQWn2c|1-=rR1KfE_x0s1 zt9STa4H9pRpY*oC!`{%O+bsIZ@5{1%6)XBh4rOXZSMuaZ`f(^U$1ktn%zni9(R!v# zbL-GKN0cYXzh8gq{%QN(>Ma}GD+SX!nEM>oM4GHvG_@m$J!|obNb`w`4*oJ_>l9v1 z7YnzZk*igGePxCHBadvQ%N|_{PVXeXG6)s^;QDd=VeaC8SM6or<<+>?S~eA{e!X}6 zu<9lOA&&DO`e%mUNcp<{!hQZ9)!rZWO=O$BI3oJxtQsyc>*|Pa8sER$J)a!;&*Vc` z??-0m9T&geR@uL4YT-7=&x&tO+zo9{(w_3e@V9M6@q?#(55GK?{Wk20$o$}42PRIo z6kH*r^}%lW%N)bbp5)MUh?K`!L5OB7Chiv{>*OcSNS*EkLPcF5p}#pZ~1-G?!UV(dcKyP zpc44+^;`Q7#_CuOQvpjy7hH$2m7)Uca@L-v#PiHoqXh; zdDmH$!*f1Wm_FL4^P|)B#^mp7WgLVe`?(gcv|n3S@yF>S&py46X`9!zmNh+k(K4Z@ zVNZ+X*IPUPXur76Tk+|z*ALs}KibOgi|(D}k}MmSD$SC7XTzVJ>!+LEbr1Zg`}f=S z3OTnO^S@6{eiE)Qq4_dHfh}w8_TPp-s?YzhUuYOm{`33~$#~JXQgze6Ie&LxzqB^~ z#iNp+M#V29KK)2hnsnpD8t=Ob3l$la7PKF}bVp7>{c_jG`?tc6{b%^FwnpvRgxvnf zD~(-u_%^Qpc=87y`_6CcXYb*El&Pm2sDI|b!;aVUqrO^K-@UeC!QWS}Om?5Y%X;9% z*LC}ycJ;kV-Cj9OaNe52SzlwNy)L(Ki8;LOi;%j#{qll#olTV$@BE|wOi9(Sd>M56 z=u{#3-Iwn*&#U~;VEan>sL4Ekn+v>_uC>v>g)8>G_uF~3l1nnRMe! zxH|h&?Hhe9;iv7GK8C)3bNJ(n^VKr_ON2gHD?K@#uK6>z)@#C&{!pd;?tdgdaveYN zD|YMO-Z$mf%(>*OyUXtM*gCGAFI3}jVUOS<-!-qRdcw>EKR!EE_qi-I@U-C&^ zE1J$9k&0cnG_g|jYU>rJj69`;zzbh2|JBW~d>KD~{?_@&!+y+uwEoRpwFmoKx6I@^ za;jqY{0W!kLuJKptM9FT|K@)3htG%pGw`}i&wrFF9)6|N;?LR0Yac~+8%_PRZ6k}8 zw&=@~I@QD>r`<&0n$p;>&f9x4GCQ zXm~u4zteS9}$X~Jnv9?{S)n1y4gp<&DS_uvV0YNx?l3E=a&8L zQg>cFRhgu^J?uY2ZPWYx*ljK|Fxo!9t9ua;1+vn2VbL_uf zp3-~#5_^8Xa^A*1Z zy$#pi)jklH!EePEI%d+9Er_D@Ug->r~F|b7J#+AocyO6 z2eOa;)BGc|`Qx|Q-+a9+73T>>-TJ1=`Y`$WtK~iGKiOPA_x3@5cRlO#DH-Bb%Wpi% z`T0ww=i~m}?kCw_*$YS83H~Vl82{LAH_NRPSN?1$xHWCxyN!qTep_8s*JZEu%m0z- zytN<7AM9P;P$QhX_I0_{#EI*-YAl%KD3aT6q7mSfZ^J6f_IS>m>*^oezeQi(xILb5z}%+xa@_F`^^f<= z)A?bZcG-KbvwP3YSVNl)y~Wb=SLOb3T;E>9`B>wR;$w@gzh|58H~hIq$(_U7LfKN` z%gOc0+y1V4%lQ1zrR~?g@c*5(yI(fT;E&(m8To%a`R1AYi9ascaPpbpri7h$8?W0< zy<2x^g7P2hYj3};OSo!Ye?L(DKZ8*Ag_8daH@CZA-e-BI#;L_+!ya9&8*d-#M?cMM z&k;TOpP{mIz4hPSCXIy;?D_9C9+8^Z^Pl0sqx^qLyZ@TB>a$ET<0<<5*|Io-kZh19# z5A#x=FwX1eucQX|p5i|(e%WUJ0?`lI`yLesKAxw2VGr|1rfQ3D-(5Ryt^T*nlRy96 z{FiY@7ghAGn^>-rw}wrI=VR=&sg(xGSxkF7MDM*YydRsuuGPM+FgEC3W))9d+S)Zy zH&?t;pXpZ5l>JOL{hRbh*F_sIm%ddGQh1_r$b(aUGXJ$b?T^?G-WRJeig@}_Y=*Ax z^Lj1Khf`BKZ?ilQwJqR$erZS8tD-FBOB47s9gf;o{tCY~e{3BPZvoXU!HYvTlHDJn#vZ2>hJ5GAFk6b2(wg6eSh$E z?7WX_+&HW|znF?`T>r{Sb^b&ac7_DesE_A6c2(6lUsAiY$2lQiN6K~bDV@`wS6X{0 zG+E7hY@_`l-SxvAsn&V@%YC!i7EAQKwD~Dx^srsJ^EgkXsM`GB3P1P?r(amY{VMO- z`G2jkX;Xj8{xM@LugHw~&#<-F@ccjfxP?nXZMp|AhPVyzJX{BYUOaiSVm? z>zdcrrPf{9ZtVY&>B@PdDNBtcS9E1Z(D-{EOC9QI`J^EN|ntS=b zFD;XQ&iX4^{h(n^Pj&d2`fYbVPCslX*<*S9_R^%CkCp7~Xa0Hpgw1B2mXhUS*_RH> zSA2c8@8RDWTaxaxC{C8~FD|(G?S%ZLEniNY;ghJDbnK?YSJ5x}ht`Y!XUK}=xSdh@ zV13Q?HMcA)PME|fq&zI1b-udx?57^7o`}%dz&74aqX9H8|R&n zT-)=h?RSiI*G!QFQRSzql@Hr(q+Z%-SNuLG&z*ERuf|vQNX!=tZr_V%64;qh52{a8UHXpIIT`{ z=e+yDd-{(QbZ&XLJo4vx4MmS2b0&%RahA(6=S%NVHCvgVxS~F*BI?u2ZPCj+w!GAt z6!!1ciTi4`ouV&-UrshqV4k0~=vT=pzrg)kO}0YWZO@EPu9eQ`6z=`Od~eI!E!^JD zJN`VsEED={o^-^?pMma6lU~VbH?by4@?1UodY^i|nrBMx3bsnq&*|M;XYL7pB(|gY z_m2Mze05_ zk^kmmznj19U+{?Ys?T{MxBUCsf1=H+R^NOT&c(>!ai`AoLIwZP{D*I&EF&g!PmrHC z@9Wp}Hq#UD-zt};rY6qmy0K$Vw**J=jcet%6}Bd&N+jP~vhCX2;zos|(NcRC^UMqp ze3iBJc*mEc>?N<j&Dkh@UtzrAzw7oQZ)$8m{Cn(kY2B18tJBh<+!Mm| zHf?{}`k$fk`g!fg@45e&Uj4)Vh@a(^e(~x|!H!ao79khCgC~o;8TiVrYR#&`cl^6N zU##T4#2>p4xp%kB&)!@cW_eIqadF0Ng?}=Fk^dP4{{;PKU|R6ezAfj-uKvUQVz>Qt z9_a6QdEI@2$b2Vu`6D~*EB#9L1V6S;`#Z}{pkl6iVEMzpfdOU@=V+g7{Le69(!Q1- z-#_L*JpD)S`r563<9+rvy}NjHZ`M)Jd7vW%PP;EsgqbAET?~&&;HV*KSwQVBA53x7PNm{vCbwxQAj8~rr$4n z&Q&|LTgNAC*6KgC<81t+mU8~5N40mn-?sSDR>`lH<{bC)XY`<%|J>%>_(bbH1_JrRNH?QzvKL2U`eEY0#=5k^e?@PCo9})XT$f zy%?9sq}yt`WktB3(f+deTmGh-zwIl+3!bjM`}6z6v#CFpy9(WlnfSd=YA1)lLCxMD z%1yl0=?DKa$e8ZpwR+g7{oV7O_6uIK3_V3YgNm1dS+`=!uf3o8Lc8IrN!r#4Z&Vq+ zugeaf_fV%?z(FIzV2SCwf?QRT`! z#~W+UE0n#e3cZ_txc>I7;u~3JkG@^q;1ks3=I&(6@l~|fyg|pWRjwCKAh1AmrGS{L!)bL`eT zVgF<=Et}=EktL|8V*R|Wv0lFA;XC%!yiGgAe4}jLBiq_mu_y)Gg8PA?2-4{nSHp_ zbno(G$7KpH?c25c%9YhM2g3X6E_`15pP?~A`<=mES9O_>r~Wg%x%ydBgt=#y{Zv~A zg^M-1Hfqn57xk!Jm+z(S?;Xu+)c zhrOpw?FnWuzLU0fi$7;nreWKT{|p{eiZ3s%Jb(Os-?ja0^&10TtXy?+MQ4ZGj!!O$ zW>wnh)_Zq__wRpuq-DPTfp6@8e?PPN&v0LK`NQ+Ktq+@j_Z082k;{2lb~xsSpZX13 zFM%ch*gvdaRAZn2P+#K3gsfXS({4?hTVyPI#HP=x$!ay@T}Ys?Ug@d$6~l@v%;V3{r!I1FD=mVUx3Bx|Gs43zO-;x+MLU0CMtZhkNR@DS10pU*xaUO0qf!{ysU5zEz9VfQB(#LFjo4&{YK3s6j@ZdW3N3OrK>_o0CI_KD5 zX4fMBGT#2V<5XUZzFxO4`1bAH^S?q&psO-!s*` zPqe@4>uKE}?G>AP46?!#ex5k7MmOwQD9eJM{@=47_TT<|#y9!k>YbaP#1{T%VCviZ z_*?14H}@axZ@&5FVOp*wXIiIc^5l~iE9#8?o!ck>qxa$MZJRHz{c>OExMa2W>x>pR zcUd8a#XM^-t53XA{paa#*+1UkkCLSF9b<*5>nC(n7buWk1a%@4O8ow)7)V6AHMF>_b;X|KPO9!|e+ z`Z52l?T1b~{;X-U_P6J)Q@OD2>23L0_xEj{k=pcL;)FtK^m-P#nRYKfL_aoL{89Sh z($;qi4QB^8&bsu<`9FiMtYN#|o28!L&z5|M@LhF&^3G0M-m37H8rh8@GfvtF6~;}> zPnB)maJQ8qeCnT|kAJLU_e{HBk$EG5A^WJ#!pgbtRIQgJPx4<|#oq9Rxv*%H(Z@Nn zUYV->PVag9c1m8~CE4Th_wxU3Sl{)Z;laYoZ6A-f=6%B6>284&U-4EvGw_eoJXImG z;JL{M&p%p6*XZi3=vX?pgKP0h%k|g$8*j1vll+nR@UPtK*|W7J7EVcCoO@D!op$(9 zsf-<$--*i~DvSx6%yj2wWvX9zf7Fwj@Q0VY4SXiv+fjb`mL#`^*1vgM*Dif!bl~=s zsflr0ALMtRPYSyC_uRz;lW*VKyWjI8>%QeVlN~iCN))(e%x;|_IKlARKIy-E|DCUX zT+b899$H;%=NWCcUX%GhgX{H6@@ytkF8&j$uwpte_0rmt%F8vRe_eN9@oL*Pfjytk z328W%r&(~Vp5*zBA^h3-gJ&K0IRAFdm|o_+&2nq|9>)4MgY%028IEUre>=TvhrQIh zxFjFn<+mmJe_Kb$)F0R$e)}KW-fgoF?h?7C6Ls=U?ip2nX`8J_Gve=w27i`6w=e$h z=2DH|hiA90IX}C5_m4B4_c_)?{F7weX4SV*@3OQdQ9c({qYi;mD zeP(5YWcgE;=d>>@*xc{i(8`byKCj00;}g{oM2U{3BA`ch5cTmY;4n zb?36ov&DrnUdvakPyaA&!mixrQwJEtElpb=Y$}Zn553a)D6(tie}8dC{~1`B zcYm%k$h^ROL@KKO@g>WDQ$PKD|4*dlc~`!3tas!&t1@@-FL-?Lw&-VAl~HD54>C0zq^>P z5G{B7kzcveC#1J)&$K*-b*Ih;`!k-F*`^ULIZf0B4(0|Vm~p66>z?r*I>baPMc!{6~DF9o)qTe#%d$zqmdecheb zn--L66cxTWereC+2j*{6>f|#%{>VRKaJF)>lmAV<1&6(JI8Qop{+w*7yyJY~ zUul1&fITsOaXt4w>8Ky^hi|1#PMfiLW1~X9@+9^!yC+ZP7soAF$v2yIXMMd}R4<&BuhZjWy+iVKRFv&B_ftEMdwt1g{ZVq_UrG0|igmk> z?dt!g-l}I-@NT#8tBO^1443yEe)!{HjG?6Ihv#o!Kh%8wk#~C6{Fk5E6;z|7!{-++||86ZKerlwcJU&F!>?} z%lPD1TD?aP<~*1f68F&I;JUKk@>#{N12vYfc(%0HF>gWm{rw+&De&sMVc*{9{R{rL7R`e{?F z*uUUio?jk`rl{|BbBy`j6K3D`pCQqgDSD~-tZysJ#VY$O9(UV+(2@GJ|DpRqU*?C; zCT*)So5?pf$HU0r=aQNG_q?l2{`$u-ZuYaI>5MLnB79q)*IEB)mYbZfq{#E*faL3V z&5GTR_1afvPr4QRdRoD$wHp>rZ>TlCP*=Kr(e1xGl%3nc4u%C!a9?5i@}A^}m$eP+ z)g{-5UD~o`GVF5S(f~u~be_Qii9r8ow)GRi=pXtTJ<7R2= z8adHTi>ZPCrO)jj^-)jtOe3aD++V&xbhh3?l`~~}7hi`y>2v&SR{3?=Qk$x)t4mJ4 zsOS3I@pozs_kRWs{WqT<#@b#!y>sU_@nXKGORdk!N-s)9*{y?s=Z8{M{8T zzqF?Dw^GGE>-o3jy{>Gn{2p`m+wR_XMR94J=bw3+et&xX$XfnGane^x_oh9Ge)%mc zrR>$&^IVH}3pp@fxqe(u;qQ8z{Ph*HK7IC?b?dxO`O$r|zI{BialwO>4JVVY%=u~) ze(RQgqwZIIzRXFl0vt>&b(5>t>)Eb;HkI{*HP7V8U-J)Dlo_^(D9bvmpKDY3k^7Jw z`^T;48f%No@*bo{F5TXm(q(QNGWWof!g&^dwC``xXF8e9WVPsoSCd2$$D3>~f1bM! z-gYovW(ZAupuKw8)2eSr6SHTnZ?<7qJ1657!uep~6ES7humjW$XzAv^_yYu0PdlPjgzYc$((s!mFKkN0os(E+q`pH#q}wkS7+A! z&b;?eSl-p}C*ImC_scPL$(f(W1g@KGbMohnZjWr}Yt{`&RH zI==f}x(6+OaoyPP;p4=H0LQP1VKvSV&u_|jYf%to#?n(}6Rxn=_EpHi@9XwIEN@l& zIKRKfe&OxjPx1GTve|a?%s*}VyD>LaZhm5Ru25^6#DM^74GGa_bqX_FAAOxrxR>dl z%XMeL`vQsAHt+Gx$lKBx&a9Ab>tMw{k|9y4hgIc-Qu0M`0`Vk#=D_yF0jd0@$>6sG!2V}xut4>?a z^Ll40tLJ+8zsv12mi`D{*sJX_QSGDOLcxp(0oOV1%4ZY*K0bHxjelO9@JIEAn#iR; z-2FZtTbXtLX4KIu6P*i<-%ScXnDoZ#;l3Q<>zRM*UF=iiI}B@Fei$!`@p<@F*0z50 zyMwRy88B`9wJ`YS^|f9fq7QvK@mfkj=iG7d_@!aI(4X+X%lCwTtUktk;6C@Y>}>6s zyt`r#l=+wUyes<8@adESquq`t^RC{hvH#muqq#V0?e*ULrytEW{AaM)xMVHwrj4%{ zB+~ygRCb(Sn)_q_w}c;VNaeiaEK~Criaz_#z*)y_elYB6 zY4lr{tg0&q->%(v#_Pz0^p&!AH}Eg|BKhQcOU9@3AEh79@A~6dwALm{$=a>=WI+4r z;2*5TUro*5YM1?f z5;H%Qf{6{{OUrK6FkL$$Q*`z4AK$Bgx(ySB>psOr%T48PDp|kr{gRapK>HuPf|3eg(5LG+&x=`c-kb?iY`@9cMR9;$ND1 zIEg{B)$zFFEYrD~`6byq?9LT^lqr3Ej{g~J#qxy8qqeNIp8pwGzJG|WnEd$cvIQ64 zZ@>6<$?h~|1xsn+kEexyguj~m^V8#epIp(Wa+W=ZSKPeA)4<31C%f8fPE7PVe~DLG zPd3ypy5#Y3p7yCy3)X2%b#m;>p7Si|46$ z-!&%x9P8h5b4vDq?^;?ryY{23jqcKW_wVkXo|!J}tDPda3WS!-v0+#L7edA}Xct6OTbS7a{pju3iuNFs0X zAsd@T8tl88_V-q-e`EUbkUr0k+W{Z$p4qKiy29B>;)9;@$-u9vI`)k5p?^w#v_JVH z{c-ZmQ(@148-=p^CmGydX!=S2jqhVwv%`$QMO*JoJ881nb7#{=Nd=j-)iuh0mzwVH z`e);-^xJ3S*3AZ<#sh&q{xoFw5LpcK=OFg?TsG7p&j1-ZVmCTlMr=8gDMz9NSUO zWEzuRm^b5i)o1?Z{ac>Re(c}9T~4gue^Rd9D`&as^<0x;R!usqG_T`^irisI(XX;s z?-WGsInpSS+54s_H=)1{Gj#Y{=>wOh3K9&xxWw1wn;obqaoKu@bBf&=&n4@;J&S|zWtL* zbt;>7+;X+&;^S?0ejcwlOq_F+CwjCO$~^dD`Z>I3%i$$I76)&=C8Bb43g7JLXYvb0 zXL6i8DQm0!HToO>4~sgf8k0>|*1!F;H#x@tt>o1(?cePB{s=#(>pHXbf2Um+GH4UuX2jl8xM%KRNz3E1 ztFxYev--&6aaO*gMhF9dwcLBkUXou^aQerW^-KPV|GTtL_#66G@4MnT zw%98Ds;?1OW1f#d|`jHYKj?S!>9M_ZFYw|U&k%renIND z@VD~6GuG7c9Qj^uuDQBm)jNB=M_&ax)-JM{@rZq1;b}QP!v?GG`vpx82$q~qvu3}4 zJJd#aamSTAC-}FPSCuF~pYTQYb>rS$|1N#ZRCpIr@i%+N<%=Cp3cF5T-}10h_U+<= zP0PY*@MgT0(cfjfebKuW|1&VzYu)@i*^bBaG@$f`j4`|KK6IGg_UwBi7lHP>CM%-uUDxl8m~tcjO= zd9w0h_1?v*j!Ju`AChX^w%g!2^VTXG_kysqAs^Xh=)a9tekRLXd1TtBh&#I`lzllG zw0o0u_lmgMY%`DElLqf^@XOnK)$Tw1A}!yjBB=0%LqfgP?2sRc56=rFWXnlzxYD%l z>a<69Bd=63xfT91U7YtHZIwza?F=lUF^R@$v3)*^|La{>C0V zB@Z%uu~>IG_J{7$isi2Jy|0zLDCL%HKhN{}SgqqLjyLY6pT76oKbUt^Z^`^a_mqB{ z?iO-gwp_4wUW3P;iC-nxIeYzWHKd~He=fefXnf-F1#t%b>uh_!AF>m^ zWFx)Aaq)|fs;xaXt?W8)s&<=yG<@g(X~_rwL-lMw*kkuxFS)TU_1e1cKc@@dG5+v& zXG0u=ic^}=NDt$eR{xyf)XePo-a&wt~Dwj#7S@GTwij_Wkvg? zRaqKxPx)iIeujTL{G(d--IQHx3Z_jvx#z2vee1sP{|q<%AI8hNbg3${D}Pz(aA4)r zo-LXh%5)SuU$wyIb+p z-|Fk4AB)}oUHhM+H0f7>nk<* zj-Qim7AeK&e7fJUesM|ubpNiR%1ObBe|$@BZ9Uj`A=ke8(rRyxT_29#k}Kl>w0`!o zS-$$gcZ@&n<9{Bv{Ndfe{IwzPSRQQ;f8Mh`@O{j^oew{1=LepAA9MHERF#C4zE`J} zyqMYd;=|!<>!LNb%kQ2#}()AElHPZhGK(``zcb%5@df zYE#d&a6CTGz_mO%v$&+bTv+f`_4UMs6<%&ymy7Cm$8CJLp7(ev$23>21qEBP`t}>!>5UmfrSO)H(7{{L-%V92xf$xBd1xP`Um>Re1KY$l~W7b~D$p zKf1Q*{_aiP&no>L!}o|U|J!#>qP}Cr+0xJanof^a-O4CaC-i`4GPzvnLb(U_mOYuk~{iStJAFyoT~56R4(|{BU62O_H~&+kz>M*CrqU$ zK1(R>Yn%5VMlol;VWmx}+}8g$e{}!Oef3ZLW8Kch(HT-DA0uK<=1lfBnLR^Wa1!IO ztnG*FZv{vHSpWFQCH*a@rK62mCL7<*zyIkm^RwjFQTD?7G;5SDeCdAq_U#sb;p}pb z+hNzvs-~5(Kda<=!1Kw^bG_2v{x8w9_shJRHS5r!OSjH0%G~y+@$U4j=hwOXFD=zL zdr$Gb?zzhkTC!7rJN$9DxbKTOcd4Y@p@RC!Qb+#I`BTn*u)ZtmdHWx?W9N>&a!xln zf9Rk|UYT|MrXok%P5h2g{~0zf{?Cx{@8Uj#nGN?&mmRjLKV7=s>F*NzbpIa_MZNWh z|AojYhjN44yeLlU_uj;`@mRiR38|~BUZ+0DjaK^v9xMT56BAUUCXAH`_Y3Z@KR_fi;DVhq{h@TzY(NWonj{B%|_m(Q7X+^v`p?P`-}s z+w0%nN0&73Onm%x;ms|*3R`yGiafOJj(*^FiS_LZ^L z`C1dpV8M9s$l}kE{}~#?ZSxPWTIV2>v~$+B<>vZ2vr?btYfIOCSax2b@R@?NKTCbd z*6L|}2>oLP}fYInOY%$ZXhy;`?#ztWqqOL~tx z`)%Sa@pShl`47sU)~9`rE_0sx>-#$GN6&iyu6^j4XC(LN z?6uV6vCG^4^3`*R?=49VRhQ+dziIs7$b6|f{g+lg7dK~oYkzxdGUM?HEj2s8rXBjH zwm;6~i_4B3f-5|(EZwEm!?1Y6`Z@Lw=ErjeKYV`hq>XPS|Mb;Adj2k%E^idA=i>c# zUi3%NFL}M6UVilRH}Xoc?_9lHS}j|m$nDtA%f5HdOtLtVc{J(Ac|Y&-+;`tfYH^BQ zcy*o4+HuR@_g-stlwKbe&!{|Me`;G_Pf=gbhx;5^Y`5MVp2K(b#YfqL(WZ}$ebuI~ zd?)!cp8fOs;1BOsY`?auoT<<`X3g2GZaIa^tbbRh)Er*8-eS@{Z|UhleD`LokBOgW zwAX!aan z4}8HXdHlz9*PKOn>fVI~zpxZ}nL5+)rsBy}LD|9Pq8n!t(Nx$j?Ym>YMJ4z14YJzt z?mz0(O4Ho(>bqv`zN0hk_{1NJAMES?sB~1vv^s4b%LH%vDNiM(Cr&s#@!-L={~6lm zZx);Q;iq1-d*3^YcV07vDrdQ#ILE=h@M!h9nGbiE9AQzK{AG1`)byJHjg4>pXPr8- zBKFQ_-tq_Araqc0R;wR*dEphEGbWbr-&}j+ zDK=}4>$fUKrBCix()OLZQWNq)^V9^}!kBGqKN?wE+$>Oe`@M4Iw^@;|+FsuIv7X#U z$XegxBb#6Ar6{<|RWO8FOMA~={CmfY{@{DdYjqb~(OrA$9_J7L2lp9XZ=dyUw&7_UD9A8u_T;~}zV`E0_TK$ zA1_^-6dUw*>4a%X1>G+L?*6@!EpagVgbHW3 z-@Myzr7@qaYreEQ*68$nE5|Qqqrz@vhXr2G7k9nzB&lrMtp_#2jC*#^dfDc^tvGz2 zZnp04-5UGSr+g8uK3G+M<85O5kFK6;g?CR}yB213+heN0uTWoC<~s{tPYh3!3u-+Y zx+CW9s{ah7?w^;ZosLp|s(+jR;hXYZL9aF*ii%eId}YNyv7hn(8B*V#^&SfmsW3IXR%1LN1b^>_PqBi1^3?A`0!xo@4uH{WNqe2 lF_(C>q{!wBU*Uzzceq=0CqL6y?^}Fr*{7@*QaS%`0swF`#cluq literal 0 HcmV?d00001 diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240-yuv.jpg b/libs/jpegrecoverymap/tests/data/minnie-320x240-yuv.jpg new file mode 100644 index 0000000000000000000000000000000000000000..41300f47f1e17313a0bff0afd240a563e96955b5 GIT binary patch literal 20193 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%0AnV$7Y*^)hT?)zIo?ZvUyd;yeG%jaHzVy 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 literal 0 HcmV?d00001 diff --git a/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp b/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp new file mode 100644 index 0000000000..8e013517fb --- /dev/null +++ b/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp @@ -0,0 +1,102 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include + +namespace android::recoverymap { + +#define YUV_IMAGE "/sdcard/Documents/minnie-320x240-yuv.jpg" +#define YUV_IMAGE_SIZE 20193 +#define GREY_IMAGE "/sdcard/Documents/minnie-320x240-y.jpg" +#define GREY_IMAGE_SIZE 20193 + +class JpegDecoderTest : public testing::Test { +public: + struct Image { + std::unique_ptr buffer; + size_t size; + }; + JpegDecoderTest(); + ~JpegDecoderTest(); +protected: + virtual void SetUp(); + virtual void TearDown(); + + Image mYuvImage, mGreyImage; +}; + +JpegDecoderTest::JpegDecoderTest() {} + +JpegDecoderTest::~JpegDecoderTest() {} + +static size_t getFileSize(int fd) { + struct stat st; + if (fstat(fd, &st) < 0) { + ALOGW("%s : fstat failed", __func__); + return 0; + } + return st.st_size; // bytes +} + +static bool loadFile(const char filename[], JpegDecoderTest::Image* result) { + int fd = open(filename, O_CLOEXEC); + if (fd < 0) { + return false; + } + int length = getFileSize(fd); + if (length == 0) { + close(fd); + return false; + } + result->buffer.reset(new uint8_t[length]); + if (read(fd, result->buffer.get(), length) != static_cast(length)) { + close(fd); + return false; + } + close(fd); + return true; +} + +void JpegDecoderTest::SetUp() { + if (!loadFile(YUV_IMAGE, &mYuvImage)) { + FAIL() << "Load file " << YUV_IMAGE << " failed"; + } + mYuvImage.size = YUV_IMAGE_SIZE; + if (!loadFile(GREY_IMAGE, &mGreyImage)) { + FAIL() << "Load file " << GREY_IMAGE << " failed"; + } + mGreyImage.size = GREY_IMAGE_SIZE; +} + +void JpegDecoderTest::TearDown() {} + +TEST_F(JpegDecoderTest, decodeYuvImage) { + JpegDecoder decoder; + EXPECT_TRUE(decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size)); + ASSERT_GT(decoder.getDecompressedImageSize(), static_cast(0)); +} + +TEST_F(JpegDecoderTest, decodeGreyImage) { + JpegDecoder decoder; + EXPECT_TRUE(decoder.decompressImage(mGreyImage.buffer.get(), mGreyImage.size)); + ASSERT_GT(decoder.getDecompressedImageSize(), static_cast(0)); +} + +} \ No newline at end of file -- GitLab From ffcf5b1e5b088dede020a3d5513073659f7eda9e Mon Sep 17 00:00:00 2001 From: Ben Wagner Date: Wed, 26 Oct 2022 21:30:12 +0000 Subject: [PATCH 0408/1310] Include in NDK font headers Both `font.h` and `font_matcher.h` use `uint16_t` and `uint32_t`. These come from `stdint.h` which should be explicitly included. Change-Id: I08d96d9d7a1e661a467a70969182bcbe6d47b4fc --- include/android/font.h | 1 + include/android/font_matcher.h | 1 + 2 files changed, 2 insertions(+) diff --git a/include/android/font.h b/include/android/font.h index 8a3a474f25..45eb81a533 100644 --- a/include/android/font.h +++ b/include/android/font.h @@ -31,6 +31,7 @@ #include #include +#include #include /****************************************************************** diff --git a/include/android/font_matcher.h b/include/android/font_matcher.h index 4417422687..63b0328749 100644 --- a/include/android/font_matcher.h +++ b/include/android/font_matcher.h @@ -75,6 +75,7 @@ #include #include +#include #include #include -- GitLab From 6278ca243f9a154e86714e79198ffc51ea4266fe Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 25 Oct 2022 11:19:19 -0700 Subject: [PATCH 0409/1310] Return touched windows from findTouchedWindowTargetsLocked This is mostly a cosmetic change to modify the function to return things that it promises to return in its name. In the future, we will refactor this further to make it const and possibly to remove goto. Also, do a similar refactor for the focused events. Bug: 211379801 Test: atest inputflinger_tests Change-Id: Ib379628c59a6cc724e1c49e39068483c563ef56c --- .../dispatcher/InputDispatcher.cpp | 132 ++++++++++-------- .../inputflinger/dispatcher/InputDispatcher.h | 13 +- 2 files changed, 82 insertions(+), 63 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index e191d93714..57f6f88ee8 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -541,6 +541,17 @@ Point resolveTouchedPosition(const MotionEntry& entry) { entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y))); } +std::optional getDownTime(const EventEntry& eventEntry) { + if (eventEntry.type == EventEntry::Type::KEY) { + const KeyEntry& keyEntry = static_cast(eventEntry); + return keyEntry.downTime; + } else if (eventEntry.type == EventEntry::Type::MOTION) { + const MotionEntry& motionEntry = static_cast(eventEntry); + return motionEntry.downTime; + } + return std::nullopt; +} + } // namespace // --- InputDispatcher --- @@ -1568,9 +1579,10 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr inputTargets; - InputEventInjectionResult injectionResult = - findFocusedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime); + InputEventInjectionResult injectionResult; + sp focusedWindow = + findFocusedWindowTargetLocked(currentTime, *entry, nextWakeupTime, + /*byref*/ injectionResult); if (injectionResult == InputEventInjectionResult::PENDING) { return false; } @@ -1579,6 +1591,12 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr inputTargets; + addWindowTargetLocked(focusedWindow, + InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, + BitSet32(0), getDownTime(*entry), inputTargets); // Add monitor channels from event's or focused display. addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry)); @@ -1673,13 +1691,27 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr< pilferPointersLocked(mDragState->dragWindow->getToken()); } - injectionResult = - findTouchedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime, - &conflictingPointerActions); + std::vector touchedWindows = + findTouchedWindowTargetsLocked(currentTime, *entry, nextWakeupTime, + &conflictingPointerActions, + /*byref*/ injectionResult); + for (const TouchedWindow& touchedWindow : touchedWindows) { + LOG_ALWAYS_FATAL_IF(injectionResult != InputEventInjectionResult::SUCCEEDED, + "Shouldn't be adding window if the injection didn't succeed."); + addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, + touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget, + inputTargets); + } } else { // Non touch event. (eg. trackball) - injectionResult = - findFocusedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime); + sp focusedWindow = + findFocusedWindowTargetLocked(currentTime, *entry, nextWakeupTime, injectionResult); + if (injectionResult == InputEventInjectionResult::SUCCEEDED) { + LOG_ALWAYS_FATAL_IF(focusedWindow == nullptr); + addWindowTargetLocked(focusedWindow, + InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, + BitSet32(0), getDownTime(*entry), inputTargets); + } } if (injectionResult == InputEventInjectionResult::PENDING) { return false; @@ -1881,21 +1913,11 @@ bool InputDispatcher::shouldWaitToSendKeyLocked(nsecs_t currentTime, return false; } -static std::optional getDownTime(const EventEntry& eventEntry) { - if (eventEntry.type == EventEntry::Type::KEY) { - const KeyEntry& keyEntry = static_cast(eventEntry); - return keyEntry.downTime; - } else if (eventEntry.type == EventEntry::Type::MOTION) { - const MotionEntry& motionEntry = static_cast(eventEntry); - return motionEntry.downTime; - } - return std::nullopt; -} - -InputEventInjectionResult InputDispatcher::findFocusedWindowTargetsLocked( - nsecs_t currentTime, const EventEntry& entry, std::vector& inputTargets, - nsecs_t* nextWakeupTime) { +sp InputDispatcher::findFocusedWindowTargetLocked( + nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime, + InputEventInjectionResult& outInjectionResult) { std::string reason; + outInjectionResult = InputEventInjectionResult::FAILED; // Default result int32_t displayId = getTargetDisplayId(entry); sp focusedWindowHandle = getFocusedWindowHandleLocked(displayId); @@ -1908,12 +1930,12 @@ InputEventInjectionResult InputDispatcher::findFocusedWindowTargetsLocked( ALOGI("Dropping %s event because there is no focused window or focused application in " "display %" PRId32 ".", ftl::enum_string(entry.type).c_str(), displayId); - return InputEventInjectionResult::FAILED; + return nullptr; } // Drop key events if requested by input feature if (focusedWindowHandle != nullptr && shouldDropInput(entry, focusedWindowHandle)) { - return InputEventInjectionResult::FAILED; + return nullptr; } // Compatibility behavior: raise ANR if there is a focused application, but no focused window. @@ -1933,15 +1955,17 @@ InputEventInjectionResult InputDispatcher::findFocusedWindowTargetsLocked( "window when it finishes starting up. Will wait for %" PRId64 "ms", mAwaitedFocusedApplication->getName().c_str(), millis(timeout)); *nextWakeupTime = *mNoFocusedWindowTimeoutTime; - return InputEventInjectionResult::PENDING; + outInjectionResult = InputEventInjectionResult::PENDING; + return nullptr; } else if (currentTime > *mNoFocusedWindowTimeoutTime) { // Already raised ANR. Drop the event ALOGE("Dropping %s event because there is no focused window", ftl::enum_string(entry.type).c_str()); - return InputEventInjectionResult::FAILED; + return nullptr; } else { // Still waiting for the focused window - return InputEventInjectionResult::PENDING; + outInjectionResult = InputEventInjectionResult::PENDING; + return nullptr; } } @@ -1951,13 +1975,15 @@ InputEventInjectionResult InputDispatcher::findFocusedWindowTargetsLocked( // Verify targeted injection. if (const auto err = verifyTargetedInjection(focusedWindowHandle, entry); err) { ALOGW("Dropping injected event: %s", (*err).c_str()); - return InputEventInjectionResult::TARGET_MISMATCH; + outInjectionResult = InputEventInjectionResult::TARGET_MISMATCH; + return nullptr; } if (focusedWindowHandle->getInfo()->inputConfig.test( WindowInfo::InputConfig::PAUSE_DISPATCHING)) { ALOGI("Waiting because %s is paused", focusedWindowHandle->getName().c_str()); - return InputEventInjectionResult::PENDING; + outInjectionResult = InputEventInjectionResult::PENDING; + return nullptr; } // If the event is a key event, then we must wait for all previous events to @@ -1974,17 +2000,13 @@ InputEventInjectionResult InputDispatcher::findFocusedWindowTargetsLocked( if (entry.type == EventEntry::Type::KEY) { if (shouldWaitToSendKeyLocked(currentTime, focusedWindowHandle->getName().c_str())) { *nextWakeupTime = *mKeyIsWaitingForEventsTimeout; - return InputEventInjectionResult::PENDING; + outInjectionResult = InputEventInjectionResult::PENDING; + return nullptr; } } - // Success! Output targets. - addWindowTargetLocked(focusedWindowHandle, - InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, - BitSet32(0), getDownTime(entry), inputTargets); - - // Done. - return InputEventInjectionResult::SUCCEEDED; + outInjectionResult = InputEventInjectionResult::SUCCEEDED; + return focusedWindowHandle; } /** @@ -2013,11 +2035,12 @@ std::vector InputDispatcher::selectResponsiveMonitorsLocked( return responsiveMonitors; } -InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( - nsecs_t currentTime, const MotionEntry& entry, std::vector& inputTargets, - nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) { +std::vector InputDispatcher::findTouchedWindowTargetsLocked( + nsecs_t currentTime, const MotionEntry& entry, nsecs_t* nextWakeupTime, + bool* outConflictingPointerActions, InputEventInjectionResult& outInjectionResult) { ATRACE_CALL(); + std::vector touchedWindows; // For security reasons, we defer updating the touch state until we are sure that // event injection will be allowed. const int32_t displayId = entry.displayId; @@ -2025,7 +2048,7 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( const int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK; // Update the touch state as needed based on the properties of the touch event. - InputEventInjectionResult injectionResult = InputEventInjectionResult::PENDING; + outInjectionResult = InputEventInjectionResult::PENDING; sp newHoverWindowHandle(mLastHoverWindowHandle); sp newTouchedWindowHandle; @@ -2058,7 +2081,7 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( "in display %" PRId32, displayId); // TODO: test multiple simultaneous input streams. - injectionResult = InputEventInjectionResult::FAILED; + outInjectionResult = InputEventInjectionResult::FAILED; switchedDevice = false; wrongDevice = true; goto Failed; @@ -2074,7 +2097,7 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( "in display %" PRId32, displayId); // TODO: test multiple simultaneous input streams. - injectionResult = InputEventInjectionResult::FAILED; + outInjectionResult = InputEventInjectionResult::FAILED; switchedDevice = false; wrongDevice = true; goto Failed; @@ -2100,7 +2123,7 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( // Verify targeted injection. if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) { ALOGW("Dropping injected touch event: %s", (*err).c_str()); - injectionResult = os::InputEventInjectionResult::TARGET_MISMATCH; + outInjectionResult = os::InputEventInjectionResult::TARGET_MISMATCH; newTouchedWindowHandle = nullptr; goto Failed; } @@ -2141,7 +2164,7 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( if (newTouchedWindows.empty()) { ALOGI("Dropping event because there is no touchable window at (%d, %d) on display %d.", x, y, displayId); - injectionResult = InputEventInjectionResult::FAILED; + outInjectionResult = InputEventInjectionResult::FAILED; goto Failed; } @@ -2193,7 +2216,7 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( "dropped the pointer down event in display %" PRId32, displayId); } - injectionResult = InputEventInjectionResult::FAILED; + outInjectionResult = InputEventInjectionResult::FAILED; goto Failed; } @@ -2212,7 +2235,7 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( // Verify targeted injection. if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) { ALOGW("Dropping injected event: %s", (*err).c_str()); - injectionResult = os::InputEventInjectionResult::TARGET_MISMATCH; + outInjectionResult = os::InputEventInjectionResult::TARGET_MISMATCH; newTouchedWindowHandle = nullptr; goto Failed; } @@ -2303,7 +2326,7 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( })) { ALOGI("Dropping event because there is no touched window on display %d to receive it: %s", displayId, entry.getDescription().c_str()); - injectionResult = InputEventInjectionResult::FAILED; + outInjectionResult = InputEventInjectionResult::FAILED; goto Failed; } @@ -2323,7 +2346,7 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( ALOGW("Dropping targeted injection: At least one touched window is not owned by uid " "%d:%s", *entry.injectionState->targetUid, errs.c_str()); - injectionResult = InputEventInjectionResult::TARGET_MISMATCH; + outInjectionResult = InputEventInjectionResult::TARGET_MISMATCH; goto Failed; } } @@ -2380,13 +2403,8 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( } // Success! Output targets. - injectionResult = InputEventInjectionResult::SUCCEEDED; - - for (const TouchedWindow& touchedWindow : tempTouchState.windows) { - addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, - touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget, - inputTargets); - } + touchedWindows = tempTouchState.windows; + outInjectionResult = InputEventInjectionResult::SUCCEEDED; // Drop the outside or hover touch windows since we will not care about them // in the next iteration. @@ -2471,7 +2489,7 @@ Failed: mLastHoverWindowHandle = newHoverWindowHandle; } - return injectionResult; + return touchedWindows; } void InputDispatcher::finishDragAndDrop(int32_t displayId, float x, float y) { diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 8356f3d27f..ad2d1f6c7c 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -542,12 +542,13 @@ private: void resetNoFocusedWindowTimeoutLocked() REQUIRES(mLock); int32_t getTargetDisplayId(const EventEntry& entry); - android::os::InputEventInjectionResult findFocusedWindowTargetsLocked( - nsecs_t currentTime, const EventEntry& entry, std::vector& inputTargets, - nsecs_t* nextWakeupTime) REQUIRES(mLock); - android::os::InputEventInjectionResult findTouchedWindowTargetsLocked( - nsecs_t currentTime, const MotionEntry& entry, std::vector& inputTargets, - nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) REQUIRES(mLock); + sp findFocusedWindowTargetLocked( + nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime, + android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock); + std::vector findTouchedWindowTargetsLocked( + nsecs_t currentTime, const MotionEntry& entry, nsecs_t* nextWakeupTime, + bool* outConflictingPointerActions, + android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock); std::vector selectResponsiveMonitorsLocked( const std::vector& gestureMonitors) const REQUIRES(mLock); -- GitLab From cce7e112242e0400b38c23031056fc278f3f6326 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 25 Oct 2022 13:31:17 -0700 Subject: [PATCH 0410/1310] Separate logic for publishing motion events Before a motion event is published, there's some more processing happening, such as determining which coords should be used. Let's move that into a separate function to make it more tractable. Bug: 211379801 Test: atest inputflinger_tests Change-Id: If373305b546c461e22e4b496d66418f771e9e741 --- .../dispatcher/InputDispatcher.cpp | 102 +++++++++--------- .../inputflinger/dispatcher/InputDispatcher.h | 1 + 2 files changed, 51 insertions(+), 52 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index e191d93714..51ad82deb2 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -3205,6 +3205,55 @@ void InputDispatcher::dispatchPointerDownOutsideFocus(uint32_t source, int32_t a postCommandLocked(std::move(command)); } +status_t InputDispatcher::publishMotionEvent(Connection& connection, + DispatchEntry& dispatchEntry) const { + const EventEntry& eventEntry = *(dispatchEntry.eventEntry); + const MotionEntry& motionEntry = static_cast(eventEntry); + + PointerCoords scaledCoords[MAX_POINTERS]; + const PointerCoords* usingCoords = motionEntry.pointerCoords; + + // Set the X and Y offset and X and Y scale depending on the input source. + if ((motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) && + !(dispatchEntry.targetFlags & InputTarget::FLAG_ZERO_COORDS)) { + float globalScaleFactor = dispatchEntry.globalScaleFactor; + if (globalScaleFactor != 1.0f) { + for (uint32_t i = 0; i < motionEntry.pointerCount; i++) { + scaledCoords[i] = motionEntry.pointerCoords[i]; + // Don't apply window scale here since we don't want scale to affect raw + // coordinates. The scale will be sent back to the client and applied + // later when requesting relative coordinates. + scaledCoords[i].scale(globalScaleFactor, 1 /* windowXScale */, + 1 /* windowYScale */); + } + usingCoords = scaledCoords; + } + } else if (dispatchEntry.targetFlags & InputTarget::FLAG_ZERO_COORDS) { + // We don't want the dispatch target to know the coordinates + for (uint32_t i = 0; i < motionEntry.pointerCount; i++) { + scaledCoords[i].clear(); + } + usingCoords = scaledCoords; + } + + std::array hmac = getSignature(motionEntry, dispatchEntry); + + // Publish the motion event. + return connection.inputPublisher + .publishMotionEvent(dispatchEntry.seq, dispatchEntry.resolvedEventId, + motionEntry.deviceId, motionEntry.source, motionEntry.displayId, + std::move(hmac), dispatchEntry.resolvedAction, + motionEntry.actionButton, dispatchEntry.resolvedFlags, + motionEntry.edgeFlags, motionEntry.metaState, + motionEntry.buttonState, motionEntry.classification, + dispatchEntry.transform, motionEntry.xPrecision, + motionEntry.yPrecision, motionEntry.xCursorPosition, + motionEntry.yCursorPosition, dispatchEntry.rawTransform, + motionEntry.downTime, motionEntry.eventTime, + motionEntry.pointerCount, motionEntry.pointerProperties, + usingCoords); +} + void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, const sp& connection) { if (ATRACE_ENABLED()) { @@ -3244,58 +3293,7 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, } case EventEntry::Type::MOTION: { - const MotionEntry& motionEntry = static_cast(eventEntry); - - PointerCoords scaledCoords[MAX_POINTERS]; - const PointerCoords* usingCoords = motionEntry.pointerCoords; - - // Set the X and Y offset and X and Y scale depending on the input source. - if ((motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) && - !(dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS)) { - float globalScaleFactor = dispatchEntry->globalScaleFactor; - if (globalScaleFactor != 1.0f) { - for (uint32_t i = 0; i < motionEntry.pointerCount; i++) { - scaledCoords[i] = motionEntry.pointerCoords[i]; - // Don't apply window scale here since we don't want scale to affect raw - // coordinates. The scale will be sent back to the client and applied - // later when requesting relative coordinates. - scaledCoords[i].scale(globalScaleFactor, 1 /* windowXScale */, - 1 /* windowYScale */); - } - usingCoords = scaledCoords; - } - } else { - // We don't want the dispatch target to know. - if (dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS) { - for (uint32_t i = 0; i < motionEntry.pointerCount; i++) { - scaledCoords[i].clear(); - } - usingCoords = scaledCoords; - } - } - - std::array hmac = getSignature(motionEntry, *dispatchEntry); - - // Publish the motion event. - status = connection->inputPublisher - .publishMotionEvent(dispatchEntry->seq, - dispatchEntry->resolvedEventId, - motionEntry.deviceId, motionEntry.source, - motionEntry.displayId, std::move(hmac), - dispatchEntry->resolvedAction, - motionEntry.actionButton, - dispatchEntry->resolvedFlags, - motionEntry.edgeFlags, motionEntry.metaState, - motionEntry.buttonState, - motionEntry.classification, - dispatchEntry->transform, - motionEntry.xPrecision, motionEntry.yPrecision, - motionEntry.xCursorPosition, - motionEntry.yCursorPosition, - dispatchEntry->rawTransform, - motionEntry.downTime, motionEntry.eventTime, - motionEntry.pointerCount, - motionEntry.pointerProperties, usingCoords); + status = publishMotionEvent(*connection, *dispatchEntry); break; } diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 8356f3d27f..14f12b24bb 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -601,6 +601,7 @@ private: void enqueueDispatchEntryLocked(const sp& connection, std::shared_ptr, const InputTarget& inputTarget, int32_t dispatchMode) REQUIRES(mLock); + status_t publishMotionEvent(Connection& connection, DispatchEntry& dispatchEntry) const; void startDispatchCycleLocked(nsecs_t currentTime, const sp& connection) REQUIRES(mLock); void finishDispatchCycleLocked(nsecs_t currentTime, const sp& connection, -- GitLab From 16a24cc68aecf8f5b29efb1ef9420b3060559446 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Wed, 26 Oct 2022 15:22:19 +0000 Subject: [PATCH 0411/1310] Use both mouse and touchpad sources for touchpads Previously, touchpads only used `SOURCE_MOUSE`, unless they were captured, in which case they only used `SOURCE_TOUCHPAD`. This made it hard to distinguish connected touchpads from mice, e.g. when deciding whether to display touchpad-specific settings. With this change, uncaptured touchpads will have `SOURCE_MOUSE | SOURCE_TOUCHPAD`. Bug: 245929943 Test: Check output of `dumpsys input` Test: Check sources of incoming touchpad `MotionEvent`s in a test app Test: atest inputflinger_tests Change-Id: I74a75db63b7be211ad247b0c9c6c6b28853a4df8 --- .../reader/mapper/TouchInputMapper.cpp | 2 ++ services/inputflinger/tests/InputReader_test.cpp | 16 ++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 615889ebe3..16bc381a9c 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -897,6 +897,8 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) mDeviceMode = DeviceMode::POINTER; if (hasStylus()) { mSource |= AINPUT_SOURCE_STYLUS; + } else { + mSource |= AINPUT_SOURCE_TOUCHPAD; } } else if (isTouchScreen()) { mSource = AINPUT_SOURCE_TOUCHSCREEN; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index bc70584af8..076be9eb70 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -5521,7 +5521,7 @@ TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndNot prepareAxes(POSITION); SingleTouchInputMapper& mapper = addMapperAndConfigure(); - ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); + ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); } TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchScreen_ReturnsTouchScreen) { @@ -8791,8 +8791,8 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) { prepareAxes(POSITION); MultiTouchInputMapper& mapper = addMapperAndConfigure(); - // Check source is mouse that would obtain the PointerController. - ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); + // Check source is a touchpad that would obtain the PointerController. + ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); NotifyMotionArgs motionArgs; processPosition(mapper, 100, 100); @@ -9795,11 +9795,11 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); - // non captured touchpad should be a mouse source + // A non captured touchpad should have a mouse and touchpad source. mFakePolicy->setPointerCapture(false); configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); + ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); } TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) { @@ -9858,10 +9858,10 @@ TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) { mFakePolicy->setPointerCapture(false); MultiTouchInputMapper& mapper = addMapperAndConfigure(); - // uncaptured touchpad should be a pointer device - ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); + // An uncaptured touchpad should be a pointer device, with additional touchpad source. + ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); - // captured touchpad should be a touchpad device + // A captured touchpad should just have a touchpad source. mFakePolicy->setPointerCapture(true); configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); -- GitLab From da20b174810825da0a1bf2e84942cb73f38f4dd4 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 26 Sep 2022 17:01:18 +0000 Subject: [PATCH 0412/1310] Notify the policy when a stylus gesture starts This will serve as a hint to the policy that a stylus is currently being used. Bug: 243005009 Test: atest inputflinger_tests Change-Id: I2cc187af226fc3a1d4fda36becc280ea7934673a --- .../inputflinger/include/InputReaderBase.h | 2 + services/inputflinger/reader/InputReader.cpp | 28 ++++++- .../inputflinger/tests/InputReader_test.cpp | 83 +++++++++++++++++++ .../tests/TestInputListenerMatchers.h | 8 ++ services/inputflinger/tests/UinputDevice.cpp | 2 + .../tests/fuzzers/MapperHelpers.h | 2 + 6 files changed, 123 insertions(+), 2 deletions(-) diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index cacb63cb8b..8c586b5290 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -393,6 +393,8 @@ public: /* Gets the affine calibration associated with the specified device. */ virtual TouchAffineTransformation getTouchAffineTransformation( const std::string& inputDeviceDescriptor, int32_t surfaceRotation) = 0; + /* Notifies the input reader policy that a stylus gesture has started. */ + virtual void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) = 0; }; } // namespace android diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 428e999c1b..903d072c96 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -58,6 +58,18 @@ static bool isSubDevice(const InputDeviceIdentifier& identifier1, identifier1.location == identifier2.location); } +static bool isStylusPointerGestureStart(const NotifyMotionArgs& motionArgs) { + const auto actionMasked = MotionEvent::getActionMasked(motionArgs.action); + if (actionMasked != AMOTION_EVENT_ACTION_HOVER_ENTER && + actionMasked != AMOTION_EVENT_ACTION_DOWN && + actionMasked != AMOTION_EVENT_ACTION_POINTER_DOWN) { + return false; + } + const auto actionIndex = MotionEvent::getActionIndex(motionArgs.action); + return motionArgs.pointerProperties[actionIndex].toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS || + motionArgs.pointerProperties[actionIndex].toolType == AMOTION_EVENT_TOOL_TYPE_ERASER; +} + // --- InputReader --- InputReader::InputReader(std::shared_ptr eventHub, @@ -101,8 +113,10 @@ status_t InputReader::stop() { void InputReader::loopOnce() { int32_t oldGeneration; int32_t timeoutMillis; + // Copy some state so that we can access it outside the lock later. bool inputDevicesChanged = false; std::vector inputDevices; + std::list notifyArgs; { // acquire lock std::scoped_lock _l(mLock); @@ -127,7 +141,7 @@ void InputReader::loopOnce() { mReaderIsAliveCondition.notify_all(); if (!events.empty()) { - notifyAll(processEventsLocked(events.data(), events.size())); + notifyArgs += processEventsLocked(events.data(), events.size()); } if (mNextTimeout != LLONG_MAX) { @@ -137,7 +151,7 @@ void InputReader::loopOnce() { ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f); } mNextTimeout = LLONG_MAX; - notifyAll(timeoutExpiredLocked(now)); + notifyArgs += timeoutExpiredLocked(now); } } @@ -152,6 +166,16 @@ void InputReader::loopOnce() { mPolicy->notifyInputDevicesChanged(inputDevices); } + // Notify the policy of the start of every new stylus gesture outside the lock. + for (const auto& args : notifyArgs) { + const auto* motionArgs = std::get_if(&args); + if (motionArgs != nullptr && isStylusPointerGestureStart(*motionArgs)) { + mPolicy->notifyStylusGestureStarted(motionArgs->deviceId, motionArgs->eventTime); + } + } + + notifyAll(std::move(notifyArgs)); + // Flush queued events out to the listener. // This must happen outside of the lock because the listener could potentially call // back into the InputReader's methods, such as getScanCodeState, or become blocked diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index bc70584af8..e51379c38f 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -244,6 +244,7 @@ class FakeInputReaderPolicy : public InputReaderPolicyInterface { bool mInputDevicesChanged GUARDED_BY(mLock){false}; std::vector mViewports; TouchAffineTransformation transform; + std::optional mStylusGestureNotified GUARDED_BY(mLock){}; protected: virtual ~FakeInputReaderPolicy() {} @@ -268,6 +269,18 @@ public: }); } + void assertStylusGestureNotified(int32_t deviceId) { + std::scoped_lock lock(mLock); + ASSERT_TRUE(mStylusGestureNotified); + ASSERT_EQ(deviceId, *mStylusGestureNotified); + mStylusGestureNotified.reset(); + } + + void assertStylusGestureNotNotified() { + std::scoped_lock lock(mLock); + ASSERT_FALSE(mStylusGestureNotified); + } + virtual void clearViewports() { mViewports.clear(); mConfig.setDisplayViewports(mViewports); @@ -428,6 +441,11 @@ private: ASSERT_NO_FATAL_FAILURE(processDevicesChanged(devicesChanged)); mInputDevicesChanged = false; } + + void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override { + std::scoped_lock lock(mLock); + mStylusGestureNotified = deviceId; + } }; // --- FakeEventHub --- @@ -2329,6 +2347,15 @@ protected: mTestListener.reset(); mFakePolicy.clear(); } + + std::optional findDeviceByName(const std::string& name) { + const std::vector inputDevices = mFakePolicy->getInputDevices(); + const auto& it = std::find_if(inputDevices.begin(), inputDevices.end(), + [&name](const InputDeviceInfo& info) { + return info.getIdentifier().name == name; + }); + return it != inputDevices.end() ? std::make_optional(*it) : std::nullopt; + } }; TEST_F(InputReaderIntegrationTest, TestInvalidDevice) { @@ -2450,6 +2477,9 @@ protected: mDevice = createUinputDevice(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)); ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); + const auto info = findDeviceByName(mDevice->getName()); + ASSERT_TRUE(info); + mDeviceInfo = *info; } void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, @@ -2473,6 +2503,7 @@ protected: } std::unique_ptr mDevice; + InputDeviceInfo mDeviceInfo; }; TEST_F(TouchIntegrationTest, InputEvent_ProcessSingleTouch) { @@ -2689,6 +2720,58 @@ TEST_F(TouchIntegrationTest, InputEvent_ProcessPalm) { ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); } +TEST_F(TouchIntegrationTest, NotifiesPolicyWhenStylusGestureStarted) { + const Point centerPoint = mDevice->getCenterPoint(); + + // Send down with the pen tool selected. The policy should be notified of the stylus presence. + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_PEN); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId())); + + // Release the stylus touch. + mDevice->sendUp(); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE( + mTestListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP))); + + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotNotified()); + + // Touch down with the finger, without the pen tool selected. The policy is not notified. + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_FINGER); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)))); + + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotNotified()); + + mDevice->sendUp(); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE( + mTestListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP))); + + // Send a move event with the stylus tool without BTN_TOUCH to generate a hover enter. + // The policy should be notified of the stylus presence. + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_PEN); + mDevice->sendMove(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId())); +} + // --- InputDeviceTest --- class InputDeviceTest : public testing::Test { protected: diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h index ff7455b452..e48f1d9082 100644 --- a/services/inputflinger/tests/TestInputListenerMatchers.h +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -19,6 +19,7 @@ #include #include #include +#include namespace android { @@ -62,6 +63,13 @@ MATCHER_P(WithPressure, pressure, "InputEvent with specified pressure") { return argPressure; } +MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") { + const auto argToolType = arg.pointerProperties[0].toolType; + *result_listener << "expected tool type " << motionToolTypeToString(toolType) << ", but got " + << motionToolTypeToString(argToolType); + return argToolType == toolType; +} + MATCHER_P(WithFlags, flags, "InputEvent with specified flags") { *result_listener << "expected flags " << flags << ", but got " << arg.flags; return arg.flags == flags; diff --git a/services/inputflinger/tests/UinputDevice.cpp b/services/inputflinger/tests/UinputDevice.cpp index a23c873656..626ad67c71 100644 --- a/services/inputflinger/tests/UinputDevice.cpp +++ b/services/inputflinger/tests/UinputDevice.cpp @@ -158,6 +158,8 @@ void UinputTouchScreen::configureDevice(int fd, uinput_user_dev* device) { device->absmax[ABS_MT_POSITION_Y] = mSize.bottom - 1; device->absmin[ABS_MT_TRACKING_ID] = RAW_ID_MIN; device->absmax[ABS_MT_TRACKING_ID] = RAW_ID_MAX; + device->absmin[ABS_MT_TOOL_TYPE] = MT_TOOL_FINGER; + device->absmax[ABS_MT_TOOL_TYPE] = MT_TOOL_MAX; } void UinputTouchScreen::sendSlot(int32_t slot) { diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index bd81761981..64316baa3a 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -315,6 +315,7 @@ public: return mTransform; } void setTouchAffineTransformation(const TouchAffineTransformation t) { mTransform = t; } + void notifyStylusGestureStarted(int32_t, nsecs_t) {} }; class FuzzInputListener : public virtual InputListenerInterface { @@ -363,6 +364,7 @@ public: void updateLedMetaState(int32_t metaState) override{}; int32_t getLedMetaState() override { return mFdp->ConsumeIntegral(); }; + void notifyStylusGestureStarted(int32_t, nsecs_t) {} }; } // namespace android -- GitLab From 07525ef5964f8a0a5b4d9949ab5f7fd17f0e2393 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 3 Oct 2022 21:51:26 +0000 Subject: [PATCH 0413/1310] Determine the bluetooth address of an input device from its uniqueId Since the Android Bluetooth stack sets a Bluetooth HID device's unique ID as the Bluetooth address, attempt to parse the address from an input device's uniqueId when connected over the Bluetooth bus. DD: go/inputdevice-battery-notifications Bug: 243005009 Test: manual, check dumpsys output Change-Id: I710868f2b202e6cebf8b6a3853a6a5c79f3bc671 --- include/input/InputDevice.h | 3 +++ include/input/PrintTools.h | 10 +++++++--- services/inputflinger/reader/EventHub.cpp | 21 +++++++++++++++------ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h index ac9c5a5a6f..e911734407 100644 --- a/include/input/InputDevice.h +++ b/include/input/InputDevice.h @@ -58,6 +58,9 @@ struct InputDeviceIdentifier { // reuse values that are not associated with an input anymore. uint16_t nonce; + // The bluetooth address of the device, if known. + std::optional bluetoothAddress; + /** * Return InputDeviceIdentifier.name that has been adjusted as follows: * - all characters besides alphanumerics, dash, diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h index 55f730b287..e24344b3f1 100644 --- a/include/input/PrintTools.h +++ b/include/input/PrintTools.h @@ -24,16 +24,20 @@ namespace android { template -std::string constToString(const T& v) { +inline std::string constToString(const T& v) { return std::to_string(v); } +inline std::string constToString(const std::string& s) { + return s; +} + /** * Convert an optional type to string. */ template -std::string toString(const std::optional& optional, - std::string (*toString)(const T&) = constToString) { +inline std::string toString(const std::optional& optional, + std::string (*toString)(const T&) = constToString) { return optional ? toString(*optional) : ""; } diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index b97c46613b..18d03f8bab 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -134,10 +135,6 @@ const std::unordered_map LIGHT_COLORS = {{"red", LightC {"green", LightColor::GREEN}, {"blue", LightColor::BLUE}}; -static inline const char* toString(bool value) { - return value ? "true" : "false"; -} - static std::string sha1(const std::string& in) { SHA_CTX ctx; SHA1_Init(&ctx); @@ -2128,6 +2125,17 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { identifier.uniqueId = buffer; } + // Attempt to get the bluetooth address of an input device from the uniqueId. + if (identifier.bus == BUS_BLUETOOTH && + std::regex_match(identifier.uniqueId, + std::regex("^[A-Fa-f0-9]{2}(?::[A-Fa-f0-9]{2}){5}$"))) { + identifier.bluetoothAddress = identifier.uniqueId; + // The Bluetooth stack requires alphabetic characters to be uppercase in a valid address. + for (auto& c : *identifier.bluetoothAddress) { + c = ::toupper(c); + } + } + // Fill in the descriptor. assignDescriptorLocked(identifier); @@ -2625,9 +2633,10 @@ void EventHub::dump(std::string& dump) const { dump += StringPrintf(INDENT3 "ControllerNumber: %d\n", device->controllerNumber); dump += StringPrintf(INDENT3 "UniqueId: %s\n", device->identifier.uniqueId.c_str()); dump += StringPrintf(INDENT3 "Identifier: bus=0x%04x, vendor=0x%04x, " - "product=0x%04x, version=0x%04x\n", + "product=0x%04x, version=0x%04x, bluetoothAddress=%s\n", device->identifier.bus, device->identifier.vendor, - device->identifier.product, device->identifier.version); + device->identifier.product, device->identifier.version, + toString(device->identifier.bluetoothAddress).c_str()); dump += StringPrintf(INDENT3 "KeyLayoutFile: %s\n", device->keyMap.keyLayoutFile.c_str()); dump += StringPrintf(INDENT3 "KeyCharacterMapFile: %s\n", -- GitLab From d82e0f03d84b6c19484b4ae0ecfcedeb32724427 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Wed, 26 Oct 2022 15:23:04 -0400 Subject: [PATCH 0414/1310] SF: Rename RefreshRateConfigs "Configs" is vague and overloaded, and collections thereof cannot simply pluralize the name. Also, update the stale class comment. Bug: 241285191 Test: Build Change-Id: I3b6d2259dcaa390f44c07caa07c05361c6cb428b --- services/surfaceflinger/Android.bp | 2 +- services/surfaceflinger/DisplayDevice.cpp | 18 +- services/surfaceflinger/DisplayDevice.h | 19 +- services/surfaceflinger/Layer.cpp | 6 +- .../surfaceflinger/Scheduler/LayerHistory.cpp | 4 +- .../surfaceflinger/Scheduler/LayerHistory.h | 8 +- .../surfaceflinger/Scheduler/LayerInfo.cpp | 10 +- services/surfaceflinger/Scheduler/LayerInfo.h | 6 +- ...ateConfigs.cpp => RefreshRateSelector.cpp} | 131 +- ...eshRateConfigs.h => RefreshRateSelector.h} | 30 +- .../surfaceflinger/Scheduler/Scheduler.cpp | 88 +- services/surfaceflinger/Scheduler/Scheduler.h | 40 +- .../Scheduler/VSyncPredictor.cpp | 4 +- services/surfaceflinger/SurfaceFlinger.cpp | 85 +- services/surfaceflinger/SurfaceFlinger.h | 10 +- .../surfaceflinger/SurfaceFlingerFactory.h | 1 - .../fuzzer/surfaceflinger_fuzzers_utils.h | 27 +- .../surfaceflinger_scheduler_fuzzer.cpp | 63 +- .../fuzzer/surfaceflinger_scheduler_fuzzer.h | 1 - .../surfaceflinger/tests/unittests/Android.bp | 2 +- .../tests/unittests/LayerHistoryTest.cpp | 22 +- ...gsTest.cpp => RefreshRateSelectorTest.cpp} | 1112 ++++++++--------- .../tests/unittests/SchedulerTest.cpp | 24 +- .../tests/unittests/SetFrameRateTest.cpp | 4 +- .../SurfaceFlinger_DisplayModeSwitching.cpp | 12 +- .../tests/unittests/TestableScheduler.h | 14 +- .../tests/unittests/TestableSurfaceFlinger.h | 41 +- 27 files changed, 888 insertions(+), 896 deletions(-) rename services/surfaceflinger/Scheduler/{RefreshRateConfigs.cpp => RefreshRateSelector.cpp} (90%) rename services/surfaceflinger/Scheduler/{RefreshRateConfigs.h => RefreshRateSelector.h} (95%) rename services/surfaceflinger/tests/unittests/{RefreshRateConfigsTest.cpp => RefreshRateSelectorTest.cpp} (61%) diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index b65f1b48e3..cb54cad9b1 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -180,7 +180,7 @@ filegroup { "Scheduler/LayerHistory.cpp", "Scheduler/LayerInfo.cpp", "Scheduler/MessageQueue.cpp", - "Scheduler/RefreshRateConfigs.cpp", + "Scheduler/RefreshRateSelector.cpp", "Scheduler/Scheduler.cpp", "Scheduler/VSyncDispatchTimerQueue.cpp", "Scheduler/VSyncPredictor.cpp", diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index c63d57f6ee..18ddfbca6c 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -69,7 +69,7 @@ DisplayDevice::DisplayDevice(DisplayDeviceCreationArgs& args) mActiveModeFPSHwcTrace("ActiveModeFPS_HWC -" + to_string(getId())), mPhysicalOrientation(args.physicalOrientation), mIsPrimary(args.isPrimary), - mRefreshRateConfigs(std::move(args.refreshRateConfigs)) { + mRefreshRateSelector(std::move(args.refreshRateSelector)) { mCompositionDisplay->editState().isSecure = args.isSecure; mCompositionDisplay->createRenderSurface( compositionengine::RenderSurfaceCreationArgsBuilder() @@ -200,7 +200,7 @@ void DisplayDevice::setActiveMode(DisplayModeId modeId, const display::DisplaySn ATRACE_INT(mActiveModeFPSTrace.c_str(), fps.getIntValue()); - mRefreshRateConfigs->setActiveModeId(modeId); + mRefreshRateSelector->setActiveModeId(modeId); if (mRefreshRateOverlay) { mRefreshRateOverlay->changeRefreshRate(fps); @@ -234,7 +234,7 @@ nsecs_t DisplayDevice::getVsyncPeriodFromHWC() const { return vsyncPeriod; } - return refreshRateConfigs().getActiveModePtr()->getVsyncPeriod(); + return refreshRateSelector().getActiveModePtr()->getVsyncPeriod(); } ui::Dataspace DisplayDevice::getCompositionDataSpace() const { @@ -335,8 +335,8 @@ void DisplayDevice::dump(utils::Dumper& dumper) const { utils::Dumper::Indent indent(dumper); dumper.dump("powerMode"sv, mPowerMode); - if (mRefreshRateConfigs) { - mRefreshRateConfigs->dump(dumper); + if (mRefreshRateSelector) { + mRefreshRateSelector->dump(dumper); } } @@ -430,7 +430,7 @@ void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinnner) { return; } - const auto fpsRange = mRefreshRateConfigs->getSupportedRefreshRateRange(); + const auto fpsRange = mRefreshRateSelector->getSupportedRefreshRateRange(); mRefreshRateOverlay = std::make_unique(fpsRange, showSpinnner); mRefreshRateOverlay->setLayerStack(getLayerStack()); mRefreshRateOverlay->setViewport(getSize()); @@ -439,9 +439,9 @@ void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinnner) { bool DisplayDevice::onKernelTimerChanged(std::optional desiredModeId, bool timerExpired) { - if (mRefreshRateConfigs && mRefreshRateOverlay) { + if (mRefreshRateSelector && mRefreshRateOverlay) { const auto newRefreshRate = - mRefreshRateConfigs->onKernelTimerChanged(desiredModeId, timerExpired); + mRefreshRateSelector->onKernelTimerChanged(desiredModeId, timerExpired); if (newRefreshRate) { mRefreshRateOverlay->changeRefreshRate(*newRefreshRate); return true; @@ -475,7 +475,7 @@ bool DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info) { } // Check if we are already at the desired mode - if (refreshRateConfigs().getActiveModePtr()->getId() == info.mode->getId()) { + if (refreshRateSelector().getActiveModePtr()->getId() == info.mode->getId()) { return false; } diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index 7abb94b84f..6c848bb100 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -45,7 +45,7 @@ #include "DisplayHardware/DisplayMode.h" #include "DisplayHardware/Hal.h" #include "DisplayHardware/PowerAdvisor.h" -#include "Scheduler/RefreshRateConfigs.h" +#include "Scheduler/RefreshRateSelector.h" #include "ThreadContext.h" #include "TracedOrdinal.h" #include "Utils/Dumper.h" @@ -219,7 +219,7 @@ public: } const DisplayMode& getActiveMode() const REQUIRES(kMainThreadContext) { - return mRefreshRateConfigs->getActiveMode(); + return mRefreshRateSelector->getActiveMode(); } // Precondition: DisplaySnapshot must contain a mode with DisplayModeId. @@ -230,14 +230,11 @@ public: hal::VsyncPeriodChangeTimeline* outTimeline) REQUIRES(kMainThreadContext); - // Returns the refresh rate configs for this display. - scheduler::RefreshRateConfigs& refreshRateConfigs() const { return *mRefreshRateConfigs; } + scheduler::RefreshRateSelector& refreshRateSelector() const { return *mRefreshRateSelector; } - // Returns a shared pointer to the refresh rate configs for this display. - // Clients can store this refresh rate configs and use it even if the DisplayDevice - // is destroyed. - std::shared_ptr holdRefreshRateConfigs() const { - return mRefreshRateConfigs; + // Extends the lifetime of the RefreshRateSelector, so it can outlive this DisplayDevice. + std::shared_ptr holdRefreshRateSelector() const { + return mRefreshRateSelector; } // Enables an overlay to be displayed with the current refresh rate @@ -287,7 +284,7 @@ private: std::vector mOverrideHdrTypes; - std::shared_ptr mRefreshRateConfigs; + std::shared_ptr mRefreshRateSelector; std::unique_ptr mRefreshRateOverlay; mutable std::mutex mActiveModeLock; @@ -337,7 +334,7 @@ struct DisplayDeviceCreationArgs { HWComposer& hwComposer; const wp displayToken; const std::shared_ptr compositionDisplay; - std::shared_ptr refreshRateConfigs; + std::shared_ptr refreshRateSelector; int32_t sequenceId{0}; bool isSecure{false}; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index b47188fea4..60a2ee7ef5 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -195,7 +195,7 @@ Layer::Layer(const LayerCreationArgs& args) } mFrameTracker.setDisplayRefreshPeriod( - args.flinger->mScheduler->getVsyncPeriodFromRefreshRateConfigs()); + args.flinger->mScheduler->getVsyncPeriodFromRefreshRateSelector()); mOwnerUid = args.ownerUid; mOwnerPid = args.ownerPid; @@ -1128,7 +1128,7 @@ bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* tran // We return whether this layer ot its children has a vote. We ignore ExactOrMultiple votes for // the same reason we are allowing touch boost for those layers. See - // RefreshRateConfigs::getBestRefreshRate for more details. + // RefreshRateSelector::rankRefreshRates for details. const auto layerVotedWithDefaultCompatibility = frameRate.rate.isValid() && frameRate.type == FrameRateCompatibility::Default; const auto layerVotedWithNoVote = frameRate.type == FrameRateCompatibility::NoVote; @@ -3613,7 +3613,7 @@ void Layer::onPostComposition(const DisplayDevice* display, } if (display) { - const Fps refreshRate = display->refreshRateConfigs().getActiveModePtr()->getFps(); + const Fps refreshRate = display->refreshRateSelector().getActiveModePtr()->getFps(); const std::optional renderRate = mFlinger->mScheduler->getFrameRateOverride(getOwnerUid()); diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp index ae111c3d45..b884dc873d 100644 --- a/services/surfaceflinger/Scheduler/LayerHistory.cpp +++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp @@ -164,7 +164,7 @@ void LayerHistory::setDefaultFrameRateCompatibility(Layer* layer, bool contentDe getVoteType(layer->getDefaultFrameRateCompatibility(), contentDetectionEnabled)); } -auto LayerHistory::summarize(const RefreshRateConfigs& configs, nsecs_t now) -> Summary { +auto LayerHistory::summarize(const RefreshRateSelector& selector, nsecs_t now) -> Summary { Summary summary; std::lock_guard lock(mLock); @@ -178,7 +178,7 @@ auto LayerHistory::summarize(const RefreshRateConfigs& configs, nsecs_t now) -> ALOGV("%s has priority: %d %s focused", info->getName().c_str(), frameRateSelectionPriority, layerFocused ? "" : "not"); - const auto vote = info->getRefreshRateVote(configs, now); + const auto vote = info->getRefreshRateVote(selector, now); // Skip NoVote layer as those don't have any requirements if (vote.type == LayerVoteType::NoVote) { continue; diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h index 12bec8dfc1..5022906ff9 100644 --- a/services/surfaceflinger/Scheduler/LayerHistory.h +++ b/services/surfaceflinger/Scheduler/LayerHistory.h @@ -27,7 +27,7 @@ #include #include -#include "RefreshRateConfigs.h" +#include "RefreshRateSelector.h" namespace android { @@ -39,7 +39,7 @@ class LayerInfo; class LayerHistory { public: - using LayerVoteType = RefreshRateConfigs::LayerVoteType; + using LayerVoteType = RefreshRateSelector::LayerVoteType; LayerHistory(); ~LayerHistory(); @@ -67,10 +67,10 @@ public: // does not set a preference for refresh rate. void setDefaultFrameRateCompatibility(Layer*, bool contentDetectionEnabled); - using Summary = std::vector; + using Summary = std::vector; // Rebuilds sets of active/inactive layers, and accumulates stats for active layers. - Summary summarize(const RefreshRateConfigs&, nsecs_t now); + Summary summarize(const RefreshRateSelector&, nsecs_t now); void clear(); diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp index 943615c45c..7247e4b260 100644 --- a/services/surfaceflinger/Scheduler/LayerInfo.cpp +++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp @@ -187,8 +187,8 @@ std::optional LayerInfo::calculateAverageFrameTime() const { return static_cast(averageFrameTime); } -std::optional LayerInfo::calculateRefreshRateIfPossible( - const RefreshRateConfigs& refreshRateConfigs, nsecs_t now) { +std::optional LayerInfo::calculateRefreshRateIfPossible(const RefreshRateSelector& selector, + nsecs_t now) { static constexpr float MARGIN = 1.0f; // 1Hz if (!hasEnoughDataForHeuristic()) { ALOGV("Not enough data"); @@ -199,7 +199,7 @@ std::optional LayerInfo::calculateRefreshRateIfPossible( const auto refreshRate = Fps::fromPeriodNsecs(*averageFrameTime); const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now); if (refreshRateConsistent) { - const auto knownRefreshRate = refreshRateConfigs.findClosestKnownFrameRate(refreshRate); + const auto knownRefreshRate = selector.findClosestKnownFrameRate(refreshRate); using fps_approx_ops::operator!=; // To avoid oscillation, use the last calculated refresh rate if it is close enough. @@ -222,7 +222,7 @@ std::optional LayerInfo::calculateRefreshRateIfPossible( : std::nullopt; } -LayerInfo::LayerVote LayerInfo::getRefreshRateVote(const RefreshRateConfigs& refreshRateConfigs, +LayerInfo::LayerVote LayerInfo::getRefreshRateVote(const RefreshRateSelector& selector, nsecs_t now) { if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) { ALOGV("%s voted %d ", mName.c_str(), static_cast(mLayerVote.type)); @@ -250,7 +250,7 @@ LayerInfo::LayerVote LayerInfo::getRefreshRateVote(const RefreshRateConfigs& ref clearHistory(now); } - auto refreshRate = calculateRefreshRateIfPossible(refreshRateConfigs, now); + auto refreshRate = calculateRefreshRateIfPossible(selector, now); if (refreshRate.has_value()) { ALOGV("%s calculated refresh rate: %s", mName.c_str(), to_string(*refreshRate).c_str()); return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()}; diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h index 28cb24a64f..a5ffbbecb5 100644 --- a/services/surfaceflinger/Scheduler/LayerInfo.h +++ b/services/surfaceflinger/Scheduler/LayerInfo.h @@ -28,7 +28,7 @@ #include #include "LayerHistory.h" -#include "RefreshRateConfigs.h" +#include "RefreshRateSelector.h" namespace android { @@ -162,7 +162,7 @@ public: uid_t getOwnerUid() const { return mOwnerUid; } - LayerVote getRefreshRateVote(const RefreshRateConfigs&, nsecs_t now); + LayerVote getRefreshRateVote(const RefreshRateSelector&, nsecs_t now); // Return the last updated time. If the present time is farther in the future than the // updated time, the updated time is the present time. @@ -261,7 +261,7 @@ private: bool isFrequent(nsecs_t now) const; bool isAnimating(nsecs_t now) const; bool hasEnoughDataForHeuristic() const; - std::optional calculateRefreshRateIfPossible(const RefreshRateConfigs&, nsecs_t now); + std::optional calculateRefreshRateIfPossible(const RefreshRateSelector&, nsecs_t now); std::optional calculateAverageFrameTime() const; bool isFrameTimeValid(const FrameTimeData&) const; diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp similarity index 90% rename from services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp rename to services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index 39850c7e1e..40af6ee575 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -33,10 +33,10 @@ #include #include "../SurfaceFlingerProperties.h" -#include "RefreshRateConfigs.h" +#include "RefreshRateSelector.h" #undef LOG_TAG -#define LOG_TAG "RefreshRateConfigs" +#define LOG_TAG "RefreshRateSelector" namespace android::scheduler { namespace { @@ -50,9 +50,9 @@ struct RefreshRateScore { } fixedRateBelowThresholdLayersScore; }; -constexpr RefreshRateConfigs::GlobalSignals kNoSignals; +constexpr RefreshRateSelector::GlobalSignals kNoSignals; -std::string formatLayerInfo(const RefreshRateConfigs::LayerRequirement& layer, float weight) { +std::string formatLayerInfo(const RefreshRateSelector::LayerRequirement& layer, float weight) { return base::StringPrintf("%s (type=%s, weight=%.2f, seamlessness=%s) %s", layer.name.c_str(), ftl::enum_string(layer.vote).c_str(), weight, ftl::enum_string(layer.seamlessness).c_str(), @@ -111,7 +111,7 @@ bool canModesSupportFrameRateOverride(const std::vector& so for (const auto it2 : sortedModes) { const auto& mode2 = it2->second; - if (RefreshRateConfigs::getFrameRateDivisor(mode1->getFps(), mode2->getFps()) >= 2) { + if (RefreshRateSelector::getFrameRateDivisor(mode1->getFps(), mode2->getFps()) >= 2) { return true; } } @@ -119,23 +119,23 @@ bool canModesSupportFrameRateOverride(const std::vector& so return false; } -std::string toString(const RefreshRateConfigs::PolicyVariant& policy) { +std::string toString(const RefreshRateSelector::PolicyVariant& policy) { using namespace std::string_literals; return ftl::match( policy, - [](const RefreshRateConfigs::DisplayManagerPolicy& policy) { + [](const RefreshRateSelector::DisplayManagerPolicy& policy) { return "DisplayManagerPolicy"s + policy.toString(); }, - [](const RefreshRateConfigs::OverridePolicy& policy) { + [](const RefreshRateSelector::OverridePolicy& policy) { return "OverridePolicy"s + policy.toString(); }, - [](RefreshRateConfigs::NoOverridePolicy) { return "NoOverridePolicy"s; }); + [](RefreshRateSelector::NoOverridePolicy) { return "NoOverridePolicy"s; }); } } // namespace -struct RefreshRateConfigs::RefreshRateScoreComparator { +struct RefreshRateSelector::RefreshRateScoreComparator { bool operator()(const RefreshRateScore& lhs, const RefreshRateScore& rhs) const { const auto& [modeIt, overallScore, _] = lhs; @@ -162,15 +162,15 @@ struct RefreshRateConfigs::RefreshRateScoreComparator { const RefreshRateOrder refreshRateOrder; }; -std::string RefreshRateConfigs::Policy::toString() const { +std::string RefreshRateSelector::Policy::toString() const { return base::StringPrintf("{defaultModeId=%d, allowGroupSwitching=%s" ", primaryRange=%s, appRequestRange=%s}", defaultMode.value(), allowGroupSwitching ? "true" : "false", to_string(primaryRange).c_str(), to_string(appRequestRange).c_str()); } -std::pair RefreshRateConfigs::getDisplayFrames(nsecs_t layerPeriod, - nsecs_t displayPeriod) const { +std::pair RefreshRateSelector::getDisplayFrames(nsecs_t layerPeriod, + nsecs_t displayPeriod) const { auto [quotient, remainder] = std::div(layerPeriod, displayPeriod); if (remainder <= MARGIN_FOR_PERIOD_CALCULATION || std::abs(remainder - displayPeriod) <= MARGIN_FOR_PERIOD_CALCULATION) { @@ -181,8 +181,8 @@ std::pair RefreshRateConfigs::getDisplayFrames(nsecs_t layerPe return {quotient, remainder}; } -float RefreshRateConfigs::calculateNonExactMatchingLayerScoreLocked(const LayerRequirement& layer, - Fps refreshRate) const { +float RefreshRateSelector::calculateNonExactMatchingLayerScoreLocked(const LayerRequirement& layer, + Fps refreshRate) const { constexpr float kScoreForFractionalPairs = .8f; const auto displayPeriod = refreshRate.getPeriodNsecs(); @@ -243,15 +243,15 @@ float RefreshRateConfigs::calculateNonExactMatchingLayerScoreLocked(const LayerR return 0; } -float RefreshRateConfigs::calculateRefreshRateScoreForFps(Fps refreshRate) const { +float RefreshRateSelector::calculateRefreshRateScoreForFps(Fps refreshRate) const { const float ratio = refreshRate.getValue() / mAppRequestRefreshRates.back()->second->getFps().getValue(); // Use ratio^2 to get a lower score the more we get further from peak return ratio * ratio; } -float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& layer, Fps refreshRate, - bool isSeamlessSwitch) const { +float RefreshRateSelector::calculateLayerScoreLocked(const LayerRequirement& layer, Fps refreshRate, + bool isSeamlessSwitch) const { // Slightly prefer seamless switches. constexpr float kSeamedSwitchPenalty = 0.95f; const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty; @@ -287,8 +287,8 @@ float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& laye kNonExactMatchingPenalty; } -auto RefreshRateConfigs::getRankedRefreshRates(const std::vector& layers, - GlobalSignals signals) const -> RankedRefreshRates { +auto RefreshRateSelector::getRankedRefreshRates(const std::vector& layers, + GlobalSignals signals) const -> RankedRefreshRates { std::lock_guard lock(mLock); if (mGetRankedRefreshRatesCache && @@ -301,8 +301,8 @@ auto RefreshRateConfigs::getRankedRefreshRates(const std::vector& layers, - GlobalSignals signals) const +auto RefreshRateSelector::getRankedRefreshRatesLocked(const std::vector& layers, + GlobalSignals signals) const -> RankedRefreshRates { using namespace fps_approx_ops; ATRACE_CALL(); @@ -608,40 +608,44 @@ auto RefreshRateConfigs::getRankedRefreshRatesLocked(const std::vector> -groupLayersByUid(const std::vector& layers) { - std::unordered_map> layersByUid; +using LayerRequirementPtrs = std::vector; +using PerUidLayerRequirements = std::unordered_map; + +PerUidLayerRequirements groupLayersByUid( + const std::vector& layers) { + PerUidLayerRequirements layersByUid; for (const auto& layer : layers) { - auto iter = layersByUid.emplace(layer.ownerUid, - std::vector()); - auto& layersWithSameUid = iter.first->second; + const auto it = layersByUid.emplace(layer.ownerUid, LayerRequirementPtrs()).first; + auto& layersWithSameUid = it->second; layersWithSameUid.push_back(&layer); } // Remove uids that can't have a frame rate override - for (auto iter = layersByUid.begin(); iter != layersByUid.end();) { - const auto& layersWithSameUid = iter->second; + for (auto it = layersByUid.begin(); it != layersByUid.end();) { + const auto& layersWithSameUid = it->second; bool skipUid = false; for (const auto& layer : layersWithSameUid) { - if (layer->vote == RefreshRateConfigs::LayerVoteType::Max || - layer->vote == RefreshRateConfigs::LayerVoteType::Heuristic) { + using LayerVoteType = RefreshRateSelector::LayerVoteType; + + if (layer->vote == LayerVoteType::Max || layer->vote == LayerVoteType::Heuristic) { skipUid = true; break; } } if (skipUid) { - iter = layersByUid.erase(iter); + it = layersByUid.erase(it); } else { - ++iter; + ++it; } } return layersByUid; } -RefreshRateConfigs::UidToFrameRateOverride RefreshRateConfigs::getFrameRateOverrides( - const std::vector& layers, Fps displayRefreshRate, - GlobalSignals globalSignals) const { +auto RefreshRateSelector::getFrameRateOverrides(const std::vector& layers, + Fps displayRefreshRate, + GlobalSignals globalSignals) const + -> UidToFrameRateOverride { ATRACE_CALL(); ALOGV("%s: %zu layers", __func__, layers.size()); @@ -661,8 +665,7 @@ RefreshRateConfigs::UidToFrameRateOverride RefreshRateConfigs::getFrameRateOverr return isStrictlyLess(mode1->getFps(), mode2->getFps()); }); - std::unordered_map> layersByUid = - groupLayersByUid(layers); + const auto layersByUid = groupLayersByUid(layers); UidToFrameRateOverride frameRateOverrides; for (const auto& [uid, layersWithSameUid] : layersByUid) { // Layers with ExplicitExactOrMultiple expect touch boost @@ -723,7 +726,7 @@ RefreshRateConfigs::UidToFrameRateOverride RefreshRateConfigs::getFrameRateOverr return frameRateOverrides; } -std::optional RefreshRateConfigs::onKernelTimerChanged( +std::optional RefreshRateSelector::onKernelTimerChanged( std::optional desiredActiveModeId, bool timerExpired) const { std::lock_guard lock(mLock); @@ -740,7 +743,7 @@ std::optional RefreshRateConfigs::onKernelTimerChanged( return mode->getFps(); } -const DisplayModePtr& RefreshRateConfigs::getMinRefreshRateByPolicyLocked() const { +const DisplayModePtr& RefreshRateSelector::getMinRefreshRateByPolicyLocked() const { const auto& activeMode = *getActiveModeItLocked()->second; for (const DisplayModeIterator modeIt : mPrimaryRefreshRates) { @@ -757,7 +760,7 @@ const DisplayModePtr& RefreshRateConfigs::getMinRefreshRateByPolicyLocked() cons return mPrimaryRefreshRates.front()->second; } -const DisplayModePtr& RefreshRateConfigs::getMaxRefreshRateByPolicyLocked(int anchorGroup) const { +const DisplayModePtr& RefreshRateSelector::getMaxRefreshRateByPolicyLocked(int anchorGroup) const { for (auto it = mPrimaryRefreshRates.rbegin(); it != mPrimaryRefreshRates.rend(); ++it) { const auto& mode = (*it)->second; if (anchorGroup == mode->getGroup()) { @@ -771,7 +774,7 @@ const DisplayModePtr& RefreshRateConfigs::getMaxRefreshRateByPolicyLocked(int an return mPrimaryRefreshRates.back()->second; } -auto RefreshRateConfigs::rankRefreshRates( +auto RefreshRateSelector::rankRefreshRates( std::optional anchorGroupOpt, RefreshRateOrder refreshRateOrder, std::optional preferredDisplayModeOpt) const -> RefreshRateRanking { std::deque ranking; @@ -817,23 +820,23 @@ auto RefreshRateConfigs::rankRefreshRates( return rankRefreshRates(kNoAnchorGroup, refreshRateOrder, preferredDisplayModeOpt); } -DisplayModePtr RefreshRateConfigs::getActiveModePtr() const { +DisplayModePtr RefreshRateSelector::getActiveModePtr() const { std::lock_guard lock(mLock); return getActiveModeItLocked()->second; } -const DisplayMode& RefreshRateConfigs::getActiveMode() const { +const DisplayMode& RefreshRateSelector::getActiveMode() const { // Reads from kMainThreadContext do not require mLock. ftl::FakeGuard guard(mLock); return *mActiveModeIt->second; } -DisplayModeIterator RefreshRateConfigs::getActiveModeItLocked() const { +DisplayModeIterator RefreshRateSelector::getActiveModeItLocked() const { // Reads under mLock do not require kMainThreadContext. return FTL_FAKE_GUARD(kMainThreadContext, mActiveModeIt); } -void RefreshRateConfigs::setActiveModeId(DisplayModeId modeId) { +void RefreshRateSelector::setActiveModeId(DisplayModeId modeId) { std::lock_guard lock(mLock); // Invalidate the cached invocation to getRankedRefreshRates. This forces @@ -844,14 +847,14 @@ void RefreshRateConfigs::setActiveModeId(DisplayModeId modeId) { LOG_ALWAYS_FATAL_IF(mActiveModeIt == mDisplayModes.end()); } -RefreshRateConfigs::RefreshRateConfigs(DisplayModes modes, DisplayModeId activeModeId, - Config config) +RefreshRateSelector::RefreshRateSelector(DisplayModes modes, DisplayModeId activeModeId, + Config config) : mKnownFrameRates(constructKnownFrameRates(modes)), mConfig(config) { initializeIdleTimer(); FTL_FAKE_GUARD(kMainThreadContext, updateDisplayModes(std::move(modes), activeModeId)); } -void RefreshRateConfigs::initializeIdleTimer() { +void RefreshRateSelector::initializeIdleTimer() { if (mConfig.idleTimerTimeout > 0ms) { mIdleTimer.emplace( "IdleTimer", mConfig.idleTimerTimeout, @@ -870,7 +873,7 @@ void RefreshRateConfigs::initializeIdleTimer() { } } -void RefreshRateConfigs::updateDisplayModes(DisplayModes modes, DisplayModeId activeModeId) { +void RefreshRateSelector::updateDisplayModes(DisplayModes modes, DisplayModeId activeModeId) { std::lock_guard lock(mLock); // Invalidate the cached invocation to getRankedRefreshRates. This forces @@ -896,7 +899,7 @@ void RefreshRateConfigs::updateDisplayModes(DisplayModes modes, DisplayModeId ac constructAvailableRefreshRates(); } -bool RefreshRateConfigs::isPolicyValidLocked(const Policy& policy) const { +bool RefreshRateSelector::isPolicyValidLocked(const Policy& policy) const { // defaultMode must be a valid mode, and within the given refresh rate range. if (const auto mode = mDisplayModes.get(policy.defaultMode)) { if (!policy.primaryRange.includes(mode->get()->getFps())) { @@ -913,7 +916,7 @@ bool RefreshRateConfigs::isPolicyValidLocked(const Policy& policy) const { policy.appRequestRange.max >= policy.primaryRange.max; } -auto RefreshRateConfigs::setPolicy(const PolicyVariant& policy) -> SetPolicyResult { +auto RefreshRateSelector::setPolicy(const PolicyVariant& policy) -> SetPolicyResult { Policy oldPolicy; { std::lock_guard lock(mLock); @@ -969,21 +972,21 @@ auto RefreshRateConfigs::setPolicy(const PolicyVariant& policy) -> SetPolicyResu return SetPolicyResult::Changed; } -const RefreshRateConfigs::Policy* RefreshRateConfigs::getCurrentPolicyLocked() const { +auto RefreshRateSelector::getCurrentPolicyLocked() const -> const Policy* { return mOverridePolicy ? &mOverridePolicy.value() : &mDisplayManagerPolicy; } -RefreshRateConfigs::Policy RefreshRateConfigs::getCurrentPolicy() const { +auto RefreshRateSelector::getCurrentPolicy() const -> Policy { std::lock_guard lock(mLock); return *getCurrentPolicyLocked(); } -RefreshRateConfigs::Policy RefreshRateConfigs::getDisplayManagerPolicy() const { +auto RefreshRateSelector::getDisplayManagerPolicy() const -> Policy { std::lock_guard lock(mLock); return mDisplayManagerPolicy; } -bool RefreshRateConfigs::isModeAllowed(DisplayModeId modeId) const { +bool RefreshRateSelector::isModeAllowed(DisplayModeId modeId) const { std::lock_guard lock(mLock); return std::any_of(mAppRequestRefreshRates.begin(), mAppRequestRefreshRates.end(), [modeId](DisplayModeIterator modeIt) { @@ -991,7 +994,7 @@ bool RefreshRateConfigs::isModeAllowed(DisplayModeId modeId) const { }); } -void RefreshRateConfigs::constructAvailableRefreshRates() { +void RefreshRateSelector::constructAvailableRefreshRates() { // Filter modes based on current policy and sort on refresh rate. const Policy* policy = getCurrentPolicyLocked(); ALOGV("%s: %s ", __func__, policy->toString().c_str()); @@ -1027,7 +1030,7 @@ void RefreshRateConfigs::constructAvailableRefreshRates() { mAppRequestRefreshRates = filterRefreshRates(policy->appRequestRange, "app request"); } -Fps RefreshRateConfigs::findClosestKnownFrameRate(Fps frameRate) const { +Fps RefreshRateSelector::findClosestKnownFrameRate(Fps frameRate) const { using namespace fps_approx_ops; if (frameRate <= mKnownFrameRates.front()) { @@ -1046,7 +1049,7 @@ Fps RefreshRateConfigs::findClosestKnownFrameRate(Fps frameRate) const { return distance1 < distance2 ? *lowerBound : *std::prev(lowerBound); } -RefreshRateConfigs::KernelIdleTimerAction RefreshRateConfigs::getIdleTimerAction() const { +auto RefreshRateSelector::getIdleTimerAction() const -> KernelIdleTimerAction { std::lock_guard lock(mLock); const Fps deviceMinFps = mMinRefreshRateModeIt->second->getFps(); @@ -1074,7 +1077,7 @@ RefreshRateConfigs::KernelIdleTimerAction RefreshRateConfigs::getIdleTimerAction return KernelIdleTimerAction::TurnOn; } -int RefreshRateConfigs::getFrameRateDivisor(Fps displayRefreshRate, Fps layerFrameRate) { +int RefreshRateSelector::getFrameRateDivisor(Fps displayRefreshRate, Fps layerFrameRate) { // This calculation needs to be in sync with the java code // in DisplayManagerService.getDisplayInfoForFrameRateOverride @@ -1090,7 +1093,7 @@ int RefreshRateConfigs::getFrameRateDivisor(Fps displayRefreshRate, Fps layerFra return static_cast(numPeriodsRounded); } -bool RefreshRateConfigs::isFractionalPairOrMultiple(Fps smaller, Fps bigger) { +bool RefreshRateSelector::isFractionalPairOrMultiple(Fps smaller, Fps bigger) { if (isStrictlyLess(bigger, smaller)) { return isFractionalPairOrMultiple(bigger, smaller); } @@ -1101,7 +1104,7 @@ bool RefreshRateConfigs::isFractionalPairOrMultiple(Fps smaller, Fps bigger) { isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier * kCoef)); } -void RefreshRateConfigs::dump(utils::Dumper& dumper) const { +void RefreshRateSelector::dump(utils::Dumper& dumper) const { using namespace std::string_view_literals; std::lock_guard lock(mLock); @@ -1142,7 +1145,7 @@ void RefreshRateConfigs::dump(utils::Dumper& dumper) const { dumper.dump("idleTimer"sv, idleTimer); } -std::chrono::milliseconds RefreshRateConfigs::getIdleTimerTimeout() { +std::chrono::milliseconds RefreshRateSelector::getIdleTimerTimeout() { return mConfig.idleTimerTimeout; } diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h similarity index 95% rename from services/surfaceflinger/Scheduler/RefreshRateConfigs.h rename to services/surfaceflinger/Scheduler/RefreshRateSelector.h index 99f81aa75e..bff16d3010 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h @@ -49,12 +49,10 @@ inline DisplayModeEvent operator|(DisplayModeEvent lhs, DisplayModeEvent rhs) { using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride; -/** - * This class is used to encapsulate configuration for refresh rates. It holds information - * about available refresh rates on the device, and the mapping between the numbers and human - * readable names. - */ -class RefreshRateConfigs { +// Selects the refresh rate of a display by ranking its `DisplayModes` in accordance with +// the DisplayManager (or override) `Policy`, the `LayerRequirement` of each active layer, +// and `GlobalSignals`. +class RefreshRateSelector { public: // Margin used when matching refresh rates to the content desired ones. static constexpr nsecs_t MARGIN_FOR_PERIOD_CALCULATION = @@ -277,14 +275,14 @@ public: std::optional kernelIdleTimerController; }; - RefreshRateConfigs(DisplayModes, DisplayModeId activeModeId, - Config config = {.enableFrameRateOverride = false, - .frameRateMultipleThreshold = 0, - .idleTimerTimeout = 0ms, - .kernelIdleTimerController = {}}); + RefreshRateSelector(DisplayModes, DisplayModeId activeModeId, + Config config = {.enableFrameRateOverride = false, + .frameRateMultipleThreshold = 0, + .idleTimerTimeout = 0ms, + .kernelIdleTimerController = {}}); - RefreshRateConfigs(const RefreshRateConfigs&) = delete; - RefreshRateConfigs& operator=(const RefreshRateConfigs&) = delete; + RefreshRateSelector(const RefreshRateSelector&) = delete; + RefreshRateSelector& operator=(const RefreshRateSelector&) = delete; // Returns whether switching modes (refresh rate or resolution) is possible. // TODO(b/158780872): Consider HAL support, and skip frame rate detection if the modes only @@ -296,8 +294,8 @@ public: // Class to enumerate options around toggling the kernel timer on and off. enum class KernelIdleTimerAction { - TurnOff, // Turn off the idle timer. - TurnOn // Turn on the idle timer. + TurnOff, // Turn off the idle timer. + TurnOn // Turn on the idle timer. }; // Checks whether kernel idle timer should be active depending the policy decisions around @@ -373,7 +371,7 @@ public: std::chrono::milliseconds getIdleTimerTimeout(); private: - friend struct TestableRefreshRateConfigs; + friend struct TestableRefreshRateSelector; void constructAvailableRefreshRates() REQUIRES(mLock); diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index be3ebb7947..499cee68f1 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -68,8 +68,8 @@ Scheduler::~Scheduler() { mDisplayPowerTimer.reset(); mTouchTimer.reset(); - // Stop idle timer and clear callbacks, as the RefreshRateConfigs may outlive the Scheduler. - setRefreshRateConfigs(nullptr); + // Stop idle timer and clear callbacks, as the RefreshRateSelector may outlive the Scheduler. + setRefreshRateSelector(nullptr); } void Scheduler::startTimers() { @@ -94,17 +94,17 @@ void Scheduler::startTimers() { } } -void Scheduler::setRefreshRateConfigs(std::shared_ptr configs) { - // The current RefreshRateConfigs instance may outlive this call, so unbind its idle timer. +void Scheduler::setRefreshRateSelector(std::shared_ptr selectorPtr) { + // The current RefreshRateSelector instance may outlive this call, so unbind its idle timer. { - // mRefreshRateConfigsLock is not locked here to avoid the deadlock + // mRefreshRateSelectorLock is not locked here to avoid the deadlock // as the callback can attempt to acquire the lock before stopIdleTimer can finish // the execution. It's safe to FakeGuard as main thread is the only thread that - // writes to the mRefreshRateConfigs. - ftl::FakeGuard guard(mRefreshRateConfigsLock); - if (mRefreshRateConfigs) { - mRefreshRateConfigs->stopIdleTimer(); - mRefreshRateConfigs->clearIdleTimerCallbacks(); + // writes to the mRefreshRateSelector. + ftl::FakeGuard guard(mRefreshRateSelectorLock); + if (mRefreshRateSelector) { + mRefreshRateSelector->stopIdleTimer(); + mRefreshRateSelector->clearIdleTimerCallbacks(); } } { @@ -113,17 +113,17 @@ void Scheduler::setRefreshRateConfigs(std::shared_ptr config mPolicy = {}; } - std::scoped_lock lock(mRefreshRateConfigsLock); - mRefreshRateConfigs = std::move(configs); - if (!mRefreshRateConfigs) return; + std::scoped_lock lock(mRefreshRateSelectorLock); + mRefreshRateSelector = std::move(selectorPtr); + if (!mRefreshRateSelector) return; - mRefreshRateConfigs->setIdleTimerCallbacks( + mRefreshRateSelector->setIdleTimerCallbacks( {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); }, .onExpired = [this] { idleTimerCallback(TimerState::Expired); }}, .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); }, .onExpired = [this] { kernelIdleTimerCallback(TimerState::Expired); }}}); - mRefreshRateConfigs->startIdleTimer(); + mRefreshRateSelector->startIdleTimer(); } void Scheduler::registerDisplay(sp display) { @@ -174,9 +174,8 @@ std::unique_ptr Scheduler::makePrimaryDispSyncSource( } std::optional Scheduler::getFrameRateOverride(uid_t uid) const { - const auto refreshRateConfigs = holdRefreshRateConfigs(); const bool supportsFrameRateOverrideByContent = - refreshRateConfigs->supportsFrameRateOverrideByContent(); + holdRefreshRateSelector()->supportsFrameRateOverrideByContent(); return mFrameRateOverrideMappings .getFrameRateOverrideForUid(uid, supportsFrameRateOverrideByContent); } @@ -191,7 +190,7 @@ bool Scheduler::isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const } impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() const { - std::scoped_lock lock(mRefreshRateConfigsLock); + std::scoped_lock lock(mRefreshRateSelectorLock); return [this](nsecs_t expectedVsyncTimestamp, uid_t uid) { return !isVsyncValid(TimePoint::fromNs(expectedVsyncTimestamp), uid); @@ -200,7 +199,7 @@ impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const { return [this](uid_t uid) { - const Fps refreshRate = holdRefreshRateConfigs()->getActiveModePtr()->getFps(); + const Fps refreshRate = holdRefreshRateSelector()->getActiveModePtr()->getFps(); const nsecs_t currentPeriod = mVsyncSchedule->period().ns() ?: refreshRate.getPeriodNsecs(); const auto frameRate = getFrameRateOverride(uid); @@ -208,7 +207,7 @@ impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction( return currentPeriod; } - const auto divisor = RefreshRateConfigs::getFrameRateDivisor(refreshRate, *frameRate); + const auto divisor = RefreshRateSelector::getFrameRateDivisor(refreshRate, *frameRate); if (divisor <= 1) { return currentPeriod; } @@ -293,9 +292,8 @@ void Scheduler::onScreenReleased(ConnectionHandle handle) { } void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId) { - const auto refreshRateConfigs = holdRefreshRateConfigs(); const bool supportsFrameRateOverrideByContent = - refreshRateConfigs->supportsFrameRateOverrideByContent(); + holdRefreshRateSelector()->supportsFrameRateOverrideByContent(); std::vector overrides = mFrameRateOverrideMappings.getAllFrameRateOverrides(supportsFrameRateOverrideByContent); @@ -336,8 +334,8 @@ void Scheduler::dispatchCachedReportedMode() { // If the mode is not the current mode, this means that a // mode change is in progress. In that case we shouldn't dispatch an event // as it will be dispatched when the current mode changes. - if (std::scoped_lock lock(mRefreshRateConfigsLock); - mRefreshRateConfigs->getActiveModePtr() != mPolicy.mode) { + if (std::scoped_lock lock(mRefreshRateSelectorLock); + mRefreshRateSelector->getActiveModePtr() != mPolicy.mode) { return; } @@ -431,8 +429,8 @@ void Scheduler::resync() { if (now - last > kIgnoreDelay) { const auto refreshRate = [&] { - std::scoped_lock lock(mRefreshRateConfigsLock); - return mRefreshRateConfigs->getActiveModePtr()->getFps(); + std::scoped_lock lock(mRefreshRateSelectorLock); + return mRefreshRateSelector->getActiveModePtr()->getFps(); }(); resyncToHardwareVsync(false, refreshRate); } @@ -493,8 +491,8 @@ void Scheduler::deregisterLayer(Layer* layer) { void Scheduler::recordLayerHistory(Layer* layer, nsecs_t presentTime, LayerHistory::LayerUpdateType updateType) { { - std::scoped_lock lock(mRefreshRateConfigsLock); - if (!mRefreshRateConfigs->canSwitch()) return; + std::scoped_lock lock(mRefreshRateSelectorLock); + if (!mRefreshRateSelector->canSwitch()) return; } mLayerHistory.record(layer, presentTime, systemTime(), updateType); @@ -510,26 +508,26 @@ void Scheduler::setDefaultFrameRateCompatibility(Layer* layer) { } void Scheduler::chooseRefreshRateForContent() { - const auto configs = holdRefreshRateConfigs(); - if (!configs->canSwitch()) return; + const auto selectorPtr = holdRefreshRateSelector(); + if (!selectorPtr->canSwitch()) return; ATRACE_CALL(); - LayerHistory::Summary summary = mLayerHistory.summarize(*configs, systemTime()); + LayerHistory::Summary summary = mLayerHistory.summarize(*selectorPtr, systemTime()); applyPolicy(&Policy::contentRequirements, std::move(summary)); } void Scheduler::resetIdleTimer() { - std::scoped_lock lock(mRefreshRateConfigsLock); - mRefreshRateConfigs->resetIdleTimer(/*kernelOnly*/ false); + std::scoped_lock lock(mRefreshRateSelectorLock); + mRefreshRateSelector->resetIdleTimer(/*kernelOnly*/ false); } void Scheduler::onTouchHint() { if (mTouchTimer) { mTouchTimer->reset(); - std::scoped_lock lock(mRefreshRateConfigsLock); - mRefreshRateConfigs->resetIdleTimer(/*kernelOnly*/ true); + std::scoped_lock lock(mRefreshRateSelectorLock); + mRefreshRateSelector->resetIdleTimer(/*kernelOnly*/ true); } } @@ -555,8 +553,8 @@ void Scheduler::kernelIdleTimerCallback(TimerState state) { // TODO(145561154): cleanup the kernel idle timer implementation and the refresh rate // magic number const Fps refreshRate = [&] { - std::scoped_lock lock(mRefreshRateConfigsLock); - return mRefreshRateConfigs->getActiveModePtr()->getFps(); + std::scoped_lock lock(mRefreshRateSelectorLock); + return mRefreshRateSelector->getActiveModePtr()->getFps(); }(); constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER = 65_Hz; @@ -623,15 +621,14 @@ void Scheduler::dumpVsync(std::string& out) const { } bool Scheduler::updateFrameRateOverrides(GlobalSignals consideredSignals, Fps displayRefreshRate) { - const auto refreshRateConfigs = holdRefreshRateConfigs(); - // we always update mFrameRateOverridesByContent here // supportsFrameRateOverridesByContent will be checked // when getting FrameRateOverrides from mFrameRateOverrideMappings if (!consideredSignals.idle) { const auto frameRateOverrides = - refreshRateConfigs->getFrameRateOverrides(mPolicy.contentRequirements, - displayRefreshRate, consideredSignals); + holdRefreshRateSelector()->getFrameRateOverrides(mPolicy.contentRequirements, + displayRefreshRate, + consideredSignals); return mFrameRateOverrideMappings.updateFrameRateOverridesByContent(frameRateOverrides); } return false; @@ -645,7 +642,6 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals bool refreshRateChanged = false; bool frameRateOverridesChanged; - const auto refreshRateConfigs = holdRefreshRateConfigs(); { std::lock_guard lock(mPolicyLock); @@ -698,7 +694,7 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap { ATRACE_CALL(); - using RankedRefreshRates = RefreshRateConfigs::RankedRefreshRates; + using RankedRefreshRates = RefreshRateSelector::RankedRefreshRates; display::PhysicalDisplayVector perDisplayRanking; // Tallies the score of a refresh rate across `displayCount` displays. @@ -717,7 +713,7 @@ auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap { for (const auto& [id, display] : mDisplays) { auto rankedRefreshRates = - display->holdRefreshRateConfigs() + display->holdRefreshRateSelector() ->getRankedRefreshRates(mPolicy.contentRequirements, globalSignals); for (const auto& [modePtr, score] : rankedRefreshRates.ranking) { @@ -793,9 +789,9 @@ DisplayModePtr Scheduler::getPreferredDisplayMode() { std::lock_guard lock(mPolicyLock); // Make sure the stored mode is up to date. if (mPolicy.mode) { - const auto configs = holdRefreshRateConfigs(); const auto ranking = - configs->getRankedRefreshRates(mPolicy.contentRequirements, makeGlobalSignals()) + holdRefreshRateSelector() + ->getRankedRefreshRates(mPolicy.contentRequirements, makeGlobalSignals()) .ranking; mPolicy.mode = ranking.front().modePtr; diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 33f612632b..39c41b99da 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -45,7 +45,7 @@ #include "LayerHistory.h" #include "MessageQueue.h" #include "OneShotTimer.h" -#include "RefreshRateConfigs.h" +#include "RefreshRateSelector.h" #include "VsyncSchedule.h" namespace android::scheduler { @@ -87,7 +87,7 @@ class TokenManager; namespace scheduler { -using GlobalSignals = RefreshRateConfigs::GlobalSignals; +using GlobalSignals = RefreshRateSelector::GlobalSignals; struct ISchedulerCallback { virtual void setVsyncEnabled(bool) = 0; @@ -107,8 +107,8 @@ public: virtual ~Scheduler(); void startTimers(); - void setRefreshRateConfigs(std::shared_ptr) - EXCLUDES(mRefreshRateConfigsLock); + void setRefreshRateSelector(std::shared_ptr) + EXCLUDES(mRefreshRateSelectorLock); void registerDisplay(sp); void unregisterDisplay(PhysicalDisplayId); @@ -163,7 +163,7 @@ public: // Otherwise, if hardware vsync is not already enabled then this method will // no-op. void resyncToHardwareVsync(bool makeAvailable, Fps refreshRate); - void resync() EXCLUDES(mRefreshRateConfigsLock); + void resync() EXCLUDES(mRefreshRateSelectorLock); void forceNextResync() { mLastResyncTime = 0; } // Passes a vsync sample to VsyncController. periodFlushed will be true if @@ -175,13 +175,13 @@ public: // Layers are registered on creation, and unregistered when the weak reference expires. void registerLayer(Layer*); void recordLayerHistory(Layer*, nsecs_t presentTime, LayerHistory::LayerUpdateType updateType) - EXCLUDES(mRefreshRateConfigsLock); + EXCLUDES(mRefreshRateSelectorLock); void setModeChangePending(bool pending); void setDefaultFrameRateCompatibility(Layer*); void deregisterLayer(Layer*); // Detects content using layer history, and selects a matching refresh rate. - void chooseRefreshRateForContent() EXCLUDES(mRefreshRateConfigsLock); + void chooseRefreshRateForContent() EXCLUDES(mRefreshRateSelectorLock); void resetIdleTimer(); @@ -226,11 +226,11 @@ public: void setGameModeRefreshRateForUid(FrameRateOverride); // Retrieves the overridden refresh rate for a given uid. - std::optional getFrameRateOverride(uid_t uid) const EXCLUDES(mRefreshRateConfigsLock); + std::optional getFrameRateOverride(uid_t uid) const EXCLUDES(mRefreshRateSelectorLock); - nsecs_t getVsyncPeriodFromRefreshRateConfigs() const EXCLUDES(mRefreshRateConfigsLock) { - std::scoped_lock lock(mRefreshRateConfigsLock); - return mRefreshRateConfigs->getActiveModePtr()->getFps().getPeriodNsecs(); + nsecs_t getVsyncPeriodFromRefreshRateSelector() const EXCLUDES(mRefreshRateSelectorLock) { + std::scoped_lock lock(mRefreshRateSelectorLock); + return mRefreshRateSelector->getActiveModePtr()->getFps().getPeriodNsecs(); } // Returns the framerate of the layer with the given sequence ID @@ -254,7 +254,7 @@ private: EventThread*, EventRegistrationFlags eventRegistration = {}); // Update feature state machine to given state when corresponding timer resets or expires. - void kernelIdleTimerCallback(TimerState) EXCLUDES(mRefreshRateConfigsLock); + void kernelIdleTimerCallback(TimerState) EXCLUDES(mRefreshRateSelectorLock); void idleTimerCallback(TimerState); void touchTimerCallback(TimerState); void displayPowerTimerCallback(TimerState); @@ -293,16 +293,16 @@ private: bool updateFrameRateOverrides(GlobalSignals, Fps displayRefreshRate) REQUIRES(mPolicyLock); - void dispatchCachedReportedMode() REQUIRES(mPolicyLock) EXCLUDES(mRefreshRateConfigsLock); + void dispatchCachedReportedMode() REQUIRES(mPolicyLock) EXCLUDES(mRefreshRateSelectorLock); android::impl::EventThread::ThrottleVsyncCallback makeThrottleVsyncCallback() const - EXCLUDES(mRefreshRateConfigsLock); + EXCLUDES(mRefreshRateSelectorLock); android::impl::EventThread::GetVsyncPeriodFunction makeGetVsyncPeriodFunction() const; - std::shared_ptr holdRefreshRateConfigs() const - EXCLUDES(mRefreshRateConfigsLock) { - std::scoped_lock lock(mRefreshRateConfigsLock); - return mRefreshRateConfigs; + std::shared_ptr holdRefreshRateSelector() const + EXCLUDES(mRefreshRateSelectorLock) { + std::scoped_lock lock(mRefreshRateSelectorLock); + return mRefreshRateSelector; } // Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection. @@ -359,8 +359,8 @@ private: std::optional cachedModeChangedParams; } mPolicy GUARDED_BY(mPolicyLock); - mutable std::mutex mRefreshRateConfigsLock; - std::shared_ptr mRefreshRateConfigs GUARDED_BY(mRefreshRateConfigsLock); + mutable std::mutex mRefreshRateSelectorLock; + std::shared_ptr mRefreshRateSelector GUARDED_BY(mRefreshRateSelectorLock); std::mutex mVsyncTimelineLock; std::optional mLastVsyncPeriodChangeTimeline diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 898e86578a..0ad42364a2 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -34,7 +34,7 @@ #include #include -#include "RefreshRateConfigs.h" +#include "RefreshRateSelector.h" #include "VSyncPredictor.h" namespace android::scheduler { @@ -274,7 +274,7 @@ bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const { std::lock_guard lock(mMutex); const auto divisor = - RefreshRateConfigs::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), frameRate); + RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), frameRate); if (divisor <= 1 || timePoint == 0) { return true; } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index c1eda1793d..167115cd2d 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -184,7 +184,7 @@ using ui::Dataspace; using ui::DisplayPrimaries; using ui::RenderIntent; -using KernelIdleTimerController = scheduler::RefreshRateConfigs::KernelIdleTimerController; +using KernelIdleTimerController = scheduler::RefreshRateSelector::KernelIdleTimerController; namespace hal = android::hardware::graphics::composer::hal; @@ -1030,7 +1030,7 @@ status_t SurfaceFlinger::getDynamicDisplayInfo(const sp& displayToken, const PhysicalDisplayId displayId = snapshot.displayId(); - info->activeDisplayModeId = display->refreshRateConfigs().getActiveModePtr()->getId().value(); + info->activeDisplayModeId = display->refreshRateSelector().getActiveModePtr()->getId().value(); info->activeColorMode = display->getCompositionDisplay()->getState().colorMode; info->hdrCapabilities = display->getHdrCapabilities(); @@ -1127,11 +1127,11 @@ status_t SurfaceFlinger::setActiveModeFromBackdoor(const sprefreshRateConfigs().getCurrentPolicy().allowGroupSwitching; + display->refreshRateSelector().getCurrentPolicy().allowGroupSwitching; - const scheduler::RefreshRateConfigs::DisplayManagerPolicy policy{modeId, - allowGroupSwitching, - {fps, fps}}; + const scheduler::RefreshRateSelector::DisplayManagerPolicy policy{modeId, + allowGroupSwitching, + {fps, fps}}; return setDesiredDisplayModeSpecsInternal(display, policy); }); @@ -1249,7 +1249,7 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { // Desired active mode was set, it is different than the mode currently in use, however // allowed modes might have changed by the time we process the refresh. // Make sure the desired mode is still allowed - const auto displayModeAllowed = display->refreshRateConfigs().isModeAllowed(desiredModeId); + const auto displayModeAllowed = display->refreshRateSelector().isModeAllowed(desiredModeId); if (!displayModeAllowed) { clearDesiredActiveModeState(display); continue; @@ -1271,7 +1271,7 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { continue; } - display->refreshRateConfigs().onModeChangeInitiated(); + display->refreshRateSelector().onModeChangeInitiated(); mScheduler->onNewVsyncPeriodChangeTimeline(outTimeline); if (outTimeline.refreshRequired) { @@ -2771,21 +2771,21 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( const auto [kernelIdleTimerController, idleTimerTimeoutMs] = getKernelIdleTimerProperties(compositionDisplay->getId()); - scheduler::RefreshRateConfigs::Config config = + scheduler::RefreshRateSelector::Config config = {.enableFrameRateOverride = android::sysprop::enable_frame_rate_override(false), .frameRateMultipleThreshold = base::GetIntProperty("debug.sf.frame_rate_multiple_threshold", 0), .idleTimerTimeout = idleTimerTimeoutMs, .kernelIdleTimerController = kernelIdleTimerController}; - creationArgs.refreshRateConfigs = + creationArgs.refreshRateSelector = mPhysicalDisplays.get(physical->id) .transform(&PhysicalDisplay::snapshotRef) .transform([&](const display::DisplaySnapshot& snapshot) { return std::make_shared< - scheduler::RefreshRateConfigs>(snapshot.displayModes(), - creationArgs.activeModeId, - config); + scheduler::RefreshRateSelector>(snapshot.displayModes(), + creationArgs.activeModeId, + config); }) .value_or(nullptr); @@ -2933,7 +2933,7 @@ void SurfaceFlinger::processDisplayAdded(const wp& displayToken, if (mScheduler && !display->isVirtual()) { // Display modes are reloaded on hotplug reconnect. if (display->isPrimary()) { - mScheduler->setRefreshRateConfigs(display->holdRefreshRateConfigs()); + mScheduler->setRefreshRateSelector(display->holdRefreshRateSelector()); } mScheduler->registerDisplay(display); dispatchDisplayHotplugEvent(display->getPhysicalId(), true); @@ -3349,7 +3349,7 @@ void SurfaceFlinger::requestDisplayModes(std::vectorrefreshRateConfigs().isModeAllowed(modePtr->getId())) { + if (display->refreshRateSelector().isModeAllowed(modePtr->getId())) { setDesiredActiveMode(std::move(request)); } else { ALOGV("%s: Mode %d is disallowed for display %s", __func__, modePtr->getId().value(), @@ -3370,7 +3370,7 @@ void SurfaceFlinger::triggerOnFrameRateOverridesChanged() { void SurfaceFlinger::initScheduler(const sp& display) { LOG_ALWAYS_FATAL_IF(mScheduler); - const auto activeModePtr = display->refreshRateConfigs().getActiveModePtr(); + const auto activeModePtr = display->refreshRateSelector().getActiveModePtr(); const Fps activeRefreshRate = activeModePtr->getFps(); mRefreshRateStats = std::make_unique(*mTimeStats, activeRefreshRate, @@ -3397,13 +3397,13 @@ void SurfaceFlinger::initScheduler(const sp& display) { static_cast(*this), features); { - auto configs = display->holdRefreshRateConfigs(); - if (configs->kernelIdleTimerController().has_value()) { + auto selectorPtr = display->holdRefreshRateSelector(); + if (selectorPtr->kernelIdleTimerController()) { features |= Feature::kKernelIdleTimer; } mScheduler->createVsyncSchedule(features); - mScheduler->setRefreshRateConfigs(std::move(configs)); + mScheduler->setRefreshRateSelector(std::move(selectorPtr)); mScheduler->registerDisplay(display); } setVsyncEnabled(false); @@ -4650,7 +4650,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: display->setPowerMode(mode); - const auto refreshRate = display->refreshRateConfigs().getActiveMode().getFps(); + const auto refreshRate = display->refreshRateSelector().getActiveMode().getFps(); if (*currentMode == hal::PowerMode::OFF) { // Turn on the display if (isInternalDisplay && (!activeDisplay || !activeDisplay->isPoweredOn())) { @@ -5189,7 +5189,7 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& comp if (const auto display = getDefaultDisplayDeviceLocked()) { std::string fps, xDpi, yDpi; - if (const auto activeModePtr = display->refreshRateConfigs().getActiveModePtr()) { + if (const auto activeModePtr = display->refreshRateSelector().getActiveModePtr()) { fps = to_string(activeModePtr->getFps()); const auto dpi = activeModePtr->getDpi(); @@ -5735,8 +5735,8 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r // defaultMode. The defaultMode doesn't matter for the override // policy though, since we set allowGroupSwitching to true, so it's // not a problem. - scheduler::RefreshRateConfigs::OverridePolicy overridePolicy; - overridePolicy.defaultMode = display->refreshRateConfigs() + scheduler::RefreshRateSelector::OverridePolicy overridePolicy; + overridePolicy.defaultMode = display->refreshRateSelector() .getDisplayManagerPolicy() .defaultMode; overridePolicy.allowGroupSwitching = true; @@ -5749,7 +5749,8 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()); return setDesiredDisplayModeSpecsInternal( - display, scheduler::RefreshRateConfigs::NoOverridePolicy{}); + display, + scheduler::RefreshRateSelector::NoOverridePolicy{}); }) .get(); } @@ -5860,7 +5861,7 @@ void SurfaceFlinger::kernelTimerChanged(bool expired) { if (!updateOverlay) return; // Update the overlay on the main thread to avoid race conditions with - // mRefreshRateConfigs->getActiveMode() + // RefreshRateSelector::getActiveMode. static_cast(mScheduler->schedule([=] { const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()); if (!display) { @@ -5924,7 +5925,7 @@ void SurfaceFlinger::updateKernelIdleTimer(std::chrono::milliseconds timeout, } void SurfaceFlinger::toggleKernelIdleTimer() { - using KernelIdleTimerAction = scheduler::RefreshRateConfigs::KernelIdleTimerAction; + using KernelIdleTimerAction = scheduler::RefreshRateSelector::KernelIdleTimerAction; const auto display = getDefaultDisplayDeviceLocked(); if (!display) { @@ -5935,12 +5936,12 @@ void SurfaceFlinger::toggleKernelIdleTimer() { // If the support for kernel idle timer is disabled for the active display, // don't do anything. const std::optional kernelIdleTimerController = - display->refreshRateConfigs().kernelIdleTimerController(); + display->refreshRateSelector().kernelIdleTimerController(); if (!kernelIdleTimerController.has_value()) { return; } - const KernelIdleTimerAction action = display->refreshRateConfigs().getIdleTimerAction(); + const KernelIdleTimerAction action = display->refreshRateSelector().getIdleTimerAction(); switch (action) { case KernelIdleTimerAction::TurnOff: @@ -5956,7 +5957,7 @@ void SurfaceFlinger::toggleKernelIdleTimer() { if (!mKernelIdleTimerEnabled) { ATRACE_INT("KernelIdleTimer", 1); const std::chrono::milliseconds timeout = - display->refreshRateConfigs().getIdleTimerTimeout(); + display->refreshRateSelector().getIdleTimerTimeout(); updateKernelIdleTimer(timeout, kernelIdleTimerController.value(), display->getPhysicalId()); mKernelIdleTimerEnabled = true; @@ -6598,7 +6599,7 @@ std::optional> SurfaceFlinger::getPreferredDisplayM status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( const sp& display, - const scheduler::RefreshRateConfigs::PolicyVariant& policy) { + const scheduler::RefreshRateSelector::PolicyVariant& policy) { const auto displayId = display->getPhysicalId(); Mutex::Autolock lock(mStateLock); @@ -6608,10 +6609,10 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( return NO_ERROR; } - auto& configs = display->refreshRateConfigs(); - using SetPolicyResult = scheduler::RefreshRateConfigs::SetPolicyResult; + auto& selector = display->refreshRateSelector(); + using SetPolicyResult = scheduler::RefreshRateSelector::SetPolicyResult; - switch (configs.setPolicy(policy)) { + switch (selector.setPolicy(policy)) { case SetPolicyResult::Invalid: return BAD_VALUE; case SetPolicyResult::Unchanged: @@ -6620,12 +6621,12 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( break; } - const scheduler::RefreshRateConfigs::Policy currentPolicy = configs.getCurrentPolicy(); + const scheduler::RefreshRateSelector::Policy currentPolicy = selector.getCurrentPolicy(); ALOGV("Setting desired display mode specs: %s", currentPolicy.toString().c_str()); // TODO(b/140204874): Leave the event in until we do proper testing with all apps that might // be depending in this callback. - if (const auto activeModePtr = configs.getActiveModePtr(); displayId == mActiveDisplayId) { + if (const auto activeModePtr = selector.getActiveModePtr(); displayId == mActiveDisplayId) { mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, activeModePtr); toggleKernelIdleTimer(); } else { @@ -6644,7 +6645,7 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( ALOGV("Switching to Scheduler preferred mode %d (%s)", preferredModeId.value(), to_string(preferredMode->getFps()).c_str()); - if (!configs.isModeAllowed(preferredModeId)) { + if (!selector.isModeAllowed(preferredModeId)) { ALOGE("%s: Preferred mode %d is disallowed", __func__, preferredModeId.value()); return INVALID_OPERATION; } @@ -6673,7 +6674,7 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecs( ALOGW("Attempt to set desired display modes for virtual display"); return INVALID_OPERATION; } else { - using Policy = scheduler::RefreshRateConfigs::DisplayManagerPolicy; + using Policy = scheduler::RefreshRateSelector::DisplayManagerPolicy; const Policy policy{DisplayModeId(defaultMode), allowGroupSwitching, {Fps::fromValue(primaryRefreshRateMin), @@ -6712,8 +6713,8 @@ status_t SurfaceFlinger::getDesiredDisplayModeSpecs(const sp& displayTo return INVALID_OPERATION; } - scheduler::RefreshRateConfigs::Policy policy = - display->refreshRateConfigs().getDisplayManagerPolicy(); + scheduler::RefreshRateSelector::Policy policy = + display->refreshRateSelector().getDisplayManagerPolicy(); *outDefaultMode = policy.defaultMode.value(); *outAllowGroupSwitching = policy.allowGroupSwitching; *outPrimaryRefreshRateMin = policy.primaryRange.min.getValue(); @@ -6834,7 +6835,7 @@ status_t SurfaceFlinger::getMaxAcquiredBufferCount(int* buffers) const { if (!getHwComposer().isHeadless()) { if (const auto display = getDefaultDisplayDevice()) { - maxRefreshRate = display->refreshRateConfigs().getSupportedRefreshRateRange().max; + maxRefreshRate = display->refreshRateSelector().getSupportedRefreshRateRange().max; } } @@ -6849,7 +6850,7 @@ uint32_t SurfaceFlinger::getMaxAcquiredBufferCountForCurrentRefreshRate(uid_t ui refreshRate = *frameRateOverride; } else if (!getHwComposer().isHeadless()) { if (const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked())) { - refreshRate = display->refreshRateConfigs().getActiveModePtr()->getFps(); + refreshRate = display->refreshRateSelector().getActiveModePtr()->getFps(); } } @@ -6938,7 +6939,7 @@ void SurfaceFlinger::onActiveDisplayChangedLocked(const sp& activ activeDisplay->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true); updateInternalDisplayVsyncLocked(activeDisplay); mScheduler->setModeChangePending(false); - mScheduler->setRefreshRateConfigs(activeDisplay->holdRefreshRateConfigs()); + mScheduler->setRefreshRateSelector(activeDisplay->holdRefreshRateSelector()); onActiveDisplaySizeChanged(activeDisplay); mActiveDisplayTransformHint = activeDisplay->getTransformHint(); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 9ffe6abbe6..d5b3a0737b 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -69,7 +69,7 @@ #include "FrontEnd/LayerCreationArgs.h" #include "FrontEnd/TransactionHandler.h" #include "LayerVector.h" -#include "Scheduler/RefreshRateConfigs.h" +#include "Scheduler/RefreshRateSelector.h" #include "Scheduler/RefreshRateStats.h" #include "Scheduler/Scheduler.h" #include "Scheduler/VsyncModulator.h" @@ -465,7 +465,7 @@ private: typename Handler = VsyncModulator::VsyncConfigOpt (VsyncModulator::*)(Args...)> void modulateVsync(Handler handler, Args... args) { if (const auto config = (*mVsyncModulator.*handler)(args...)) { - const auto vsyncPeriod = mScheduler->getVsyncPeriodFromRefreshRateConfigs(); + const auto vsyncPeriod = mScheduler->getVsyncPeriodFromRefreshRateSelector(); setVsyncConfig(*config, vsyncPeriod); } } @@ -634,7 +634,7 @@ private: // Toggles the kernel idle timer on or off depending the policy decisions around refresh rates. void toggleKernelIdleTimer() REQUIRES(mStateLock); - using KernelIdleTimerController = scheduler::RefreshRateConfigs::KernelIdleTimerController; + using KernelIdleTimerController = scheduler::RefreshRateSelector::KernelIdleTimerController; // Get the controller and timeout that will help decide how the kernel idle timer will be // configured and what value to use as the timeout. @@ -673,8 +673,8 @@ private: std::optional> getPreferredDisplayMode( PhysicalDisplayId, DisplayModeId defaultModeId) const REQUIRES(mStateLock); - status_t setDesiredDisplayModeSpecsInternal(const sp&, - const scheduler::RefreshRateConfigs::PolicyVariant&) + status_t setDesiredDisplayModeSpecsInternal( + const sp&, const scheduler::RefreshRateSelector::PolicyVariant&) EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); void commitTransactions() EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h index 41edd22102..f310c4ac53 100644 --- a/services/surfaceflinger/SurfaceFlingerFactory.h +++ b/services/surfaceflinger/SurfaceFlingerFactory.h @@ -52,7 +52,6 @@ class CompositionEngine; namespace scheduler { class VsyncConfiguration; class VsyncController; -class RefreshRateConfigs; } // namespace scheduler namespace frametimeline { diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 99279dce8b..cf2b29012d 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -38,7 +38,7 @@ #include "NativeWindowSurface.h" #include "Scheduler/EventThread.h" #include "Scheduler/MessageQueue.h" -#include "Scheduler/RefreshRateConfigs.h" +#include "Scheduler/RefreshRateSelector.h" #include "Scheduler/VSyncTracker.h" #include "Scheduler/VsyncConfiguration.h" #include "Scheduler/VsyncController.h" @@ -217,18 +217,19 @@ namespace scheduler { class TestableScheduler : public Scheduler, private ICompositor { public: - TestableScheduler(const std::shared_ptr &refreshRateConfigs, - ISchedulerCallback &callback) + TestableScheduler(const std::shared_ptr& selectorPtr, + ISchedulerCallback& callback) : TestableScheduler(std::make_unique(), - std::make_unique(), refreshRateConfigs, + std::make_unique(), selectorPtr, callback) {} TestableScheduler(std::unique_ptr controller, std::unique_ptr tracker, - std::shared_ptr configs, ISchedulerCallback &callback) + std::shared_ptr selectorPtr, + ISchedulerCallback& callback) : Scheduler(*this, callback, Feature::kContentDetection) { mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller))); - setRefreshRateConfigs(std::move(configs)); + setRefreshRateSelector(std::move(selectorPtr)); } ConnectionHandle createConnection(std::unique_ptr eventThread) { @@ -240,7 +241,7 @@ public: auto &mutableLayerHistory() { return mLayerHistory; } - auto refreshRateConfigs() { return holdRefreshRateConfigs(); } + auto refreshRateSelector() { return holdRefreshRateSelector(); } void replaceTouchTimer(int64_t millis) { if (mTouchTimer) { @@ -309,8 +310,8 @@ public: } std::unique_ptr createScheduler( - const std::shared_ptr &, - scheduler::ISchedulerCallback &) { + const std::shared_ptr&, + scheduler::ISchedulerCallback&) { return nullptr; } @@ -659,9 +660,9 @@ public: modes.try_emplace(kModeId90, mock::createDisplayMode(kModeId90, 90_Hz)); } - mRefreshRateConfigs = std::make_shared(modes, kModeId60); + mRefreshRateSelector = std::make_shared(modes, kModeId60); const auto fps = - FTL_FAKE_GUARD(kMainThreadContext, mRefreshRateConfigs->getActiveMode().getFps()); + FTL_FAKE_GUARD(kMainThreadContext, mRefreshRateSelector->getActiveMode().getFps()); mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps); mFlinger->mVsyncModulator = sp::make( mFlinger->mVsyncConfiguration->getCurrentConfigs()); @@ -670,7 +671,7 @@ public: hal::PowerMode::OFF); mScheduler = new scheduler::TestableScheduler(std::move(vsyncController), - std::move(vsyncTracker), mRefreshRateConfigs, + std::move(vsyncTracker), mRefreshRateSelector, *(callback ?: this)); mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread)); @@ -788,7 +789,7 @@ private: sp mFlinger = sp::make(mFactory, SurfaceFlinger::SkipInitialization); scheduler::TestableScheduler *mScheduler = nullptr; - std::shared_ptr mRefreshRateConfigs; + std::shared_ptr mRefreshRateSelector; }; } // namespace android diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index 2614288016..f2d008d59f 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -23,6 +23,7 @@ #include "Scheduler/DispSyncSource.h" #include "Scheduler/OneShotTimer.h" +#include "Scheduler/RefreshRateSelector.h" #include "Scheduler/VSyncDispatchTimerQueue.h" #include "Scheduler/VSyncPredictor.h" #include "Scheduler/VSyncReactor.h" @@ -38,7 +39,7 @@ constexpr nsecs_t kVsyncPeriods[] = {(30_Hz).getPeriodNsecs(), (60_Hz).getPeriod (72_Hz).getPeriodNsecs(), (90_Hz).getPeriodNsecs(), (120_Hz).getPeriodNsecs()}; -constexpr auto kLayerVoteTypes = ftl::enum_range(); +constexpr auto kLayerVoteTypes = ftl::enum_range(); constexpr PowerMode kPowerModes[] = {PowerMode::ON, PowerMode::DOZE, PowerMode::OFF, PowerMode::DOZE_SUSPEND, PowerMode::ON_SUSPEND}; @@ -59,7 +60,7 @@ public: private: void fuzzRefreshRateSelection(); - void fuzzRefreshRateConfigs(); + void fuzzRefreshRateSelector(); void fuzzPresentLatencyTracker(); void fuzzVSyncModulator(); void fuzzVSyncPredictor(); @@ -238,8 +239,8 @@ void SchedulerFuzzer::fuzzLayerHistory() { time1 += mFdp.PickValueInArray(kVsyncPeriods); time2 += mFdp.PickValueInArray(kVsyncPeriods); } - historyV1.summarize(*scheduler->refreshRateConfigs(), time1); - historyV1.summarize(*scheduler->refreshRateConfigs(), time2); + historyV1.summarize(*scheduler->refreshRateSelector(), time1); + historyV1.summarize(*scheduler->refreshRateSelector(), time2); scheduler->createConnection(std::make_unique()); @@ -327,9 +328,9 @@ void SchedulerFuzzer::fuzzRefreshRateSelection() { layer->setFrameRateSelectionPriority(mFdp.ConsumeIntegral()); } -void SchedulerFuzzer::fuzzRefreshRateConfigs() { - using RefreshRateConfigs = scheduler::RefreshRateConfigs; - using LayerRequirement = RefreshRateConfigs::LayerRequirement; +void SchedulerFuzzer::fuzzRefreshRateSelector() { + using RefreshRateSelector = scheduler::RefreshRateSelector; + using LayerRequirement = RefreshRateSelector::LayerRequirement; using RefreshRateStats = scheduler::RefreshRateStats; const uint16_t minRefreshRate = mFdp.ConsumeIntegralInRange(1, UINT16_MAX >> 1); @@ -345,48 +346,48 @@ void SchedulerFuzzer::fuzzRefreshRateConfigs() { Fps::fromValue(static_cast(fps)))); } - RefreshRateConfigs refreshRateConfigs(displayModes, modeId); + RefreshRateSelector refreshRateSelector(displayModes, modeId); - const RefreshRateConfigs::GlobalSignals globalSignals = {.touch = false, .idle = false}; + const RefreshRateSelector::GlobalSignals globalSignals = {.touch = false, .idle = false}; std::vector layers = {{.weight = mFdp.ConsumeFloatingPoint()}}; - refreshRateConfigs.getRankedRefreshRates(layers, globalSignals); + refreshRateSelector.getRankedRefreshRates(layers, globalSignals); layers[0].name = mFdp.ConsumeRandomLengthString(kRandomStringLength); layers[0].ownerUid = mFdp.ConsumeIntegral(); layers[0].desiredRefreshRate = Fps::fromValue(mFdp.ConsumeFloatingPoint()); layers[0].vote = mFdp.PickValueInArray(kLayerVoteTypes.values); auto frameRateOverrides = - refreshRateConfigs.getFrameRateOverrides(layers, - Fps::fromValue( - mFdp.ConsumeFloatingPoint()), - globalSignals); + refreshRateSelector.getFrameRateOverrides(layers, + Fps::fromValue( + mFdp.ConsumeFloatingPoint()), + globalSignals); { ftl::FakeGuard guard(kMainThreadContext); - refreshRateConfigs.setPolicy( - RefreshRateConfigs:: + refreshRateSelector.setPolicy( + RefreshRateSelector:: DisplayManagerPolicy{modeId, {Fps::fromValue(mFdp.ConsumeFloatingPoint()), Fps::fromValue(mFdp.ConsumeFloatingPoint())}}); - refreshRateConfigs.setPolicy( - RefreshRateConfigs::OverridePolicy{modeId, - {Fps::fromValue( - mFdp.ConsumeFloatingPoint()), - Fps::fromValue( - mFdp.ConsumeFloatingPoint())}}); - refreshRateConfigs.setPolicy(RefreshRateConfigs::NoOverridePolicy{}); + refreshRateSelector.setPolicy( + RefreshRateSelector::OverridePolicy{modeId, + {Fps::fromValue( + mFdp.ConsumeFloatingPoint()), + Fps::fromValue( + mFdp.ConsumeFloatingPoint())}}); + refreshRateSelector.setPolicy(RefreshRateSelector::NoOverridePolicy{}); - refreshRateConfigs.setActiveModeId(modeId); + refreshRateSelector.setActiveModeId(modeId); } - RefreshRateConfigs::isFractionalPairOrMultiple(Fps::fromValue( - mFdp.ConsumeFloatingPoint()), - Fps::fromValue( - mFdp.ConsumeFloatingPoint())); - RefreshRateConfigs::getFrameRateDivisor(Fps::fromValue(mFdp.ConsumeFloatingPoint()), - Fps::fromValue(mFdp.ConsumeFloatingPoint())); + RefreshRateSelector::isFractionalPairOrMultiple(Fps::fromValue( + mFdp.ConsumeFloatingPoint()), + Fps::fromValue( + mFdp.ConsumeFloatingPoint())); + RefreshRateSelector::getFrameRateDivisor(Fps::fromValue(mFdp.ConsumeFloatingPoint()), + Fps::fromValue(mFdp.ConsumeFloatingPoint())); android::mock::TimeStats timeStats; RefreshRateStats refreshRateStats(timeStats, Fps::fromValue(mFdp.ConsumeFloatingPoint()), @@ -407,7 +408,7 @@ void SchedulerFuzzer::fuzzPresentLatencyTracker() { void SchedulerFuzzer::process() { fuzzRefreshRateSelection(); - fuzzRefreshRateConfigs(); + fuzzRefreshRateSelector(); fuzzPresentLatencyTracker(); fuzzVSyncModulator(); fuzzVSyncPredictor(); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h index 1a49ead275..88e32e1bd0 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h @@ -27,7 +27,6 @@ #include "Clock.h" #include "Layer.h" #include "Scheduler/EventThread.h" -#include "Scheduler/RefreshRateConfigs.h" #include "Scheduler/Scheduler.h" #include "Scheduler/VSyncTracker.h" #include "Scheduler/VsyncModulator.h" diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index d2b58137f0..5daa398545 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -111,7 +111,7 @@ cc_test { "SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp", "SchedulerTest.cpp", "SetFrameRateTest.cpp", - "RefreshRateConfigsTest.cpp", + "RefreshRateSelectorTest.cpp", "RefreshRateSelectionTest.cpp", "RefreshRateStatsTest.cpp", "RegionSamplingTest.cpp", diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp index 972198cbdb..979924af58 100644 --- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp @@ -69,11 +69,11 @@ protected: // LayerHistory::summarize makes no guarantee of the order of the elements in the summary // however, for testing only, a stable order is required, therefore we sort the list here. // Any tests requiring ordered results must create layers with names. - auto summary = history().summarize(*mScheduler->refreshRateConfigs(), now); + auto summary = history().summarize(*mScheduler->refreshRateSelector(), now); std::sort(summary.begin(), summary.end(), - [](const RefreshRateConfigs::LayerRequirement& a, - const RefreshRateConfigs::LayerRequirement& b) -> bool { - return a.name < b.name; + [](const RefreshRateSelector::LayerRequirement& lhs, + const RefreshRateSelector::LayerRequirement& rhs) -> bool { + return lhs.name < rhs.name; }); return summary; } @@ -125,16 +125,16 @@ protected: ASSERT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate); } - std::shared_ptr mConfigs = - std::make_shared(makeModes(createDisplayMode(DisplayModeId(0), - LO_FPS), - createDisplayMode(DisplayModeId(1), - HI_FPS)), - DisplayModeId(0)); + std::shared_ptr mSelector = + std::make_shared(makeModes(createDisplayMode(DisplayModeId(0), + LO_FPS), + createDisplayMode(DisplayModeId(1), + HI_FPS)), + DisplayModeId(0)); mock::SchedulerCallback mSchedulerCallback; - TestableScheduler* mScheduler = new TestableScheduler(mConfigs, mSchedulerCallback); + TestableScheduler* mScheduler = new TestableScheduler(mSelector, mSchedulerCallback); TestableSurfaceFlinger mFlinger; }; diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp similarity index 61% rename from services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp rename to services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp index 924c5befde..e7ae53c01a 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp @@ -28,7 +28,7 @@ #include "DisplayHardware/HWC2.h" #include "FpsOps.h" -#include "Scheduler/RefreshRateConfigs.h" +#include "Scheduler/RefreshRateSelector.h" #include "mock/DisplayHardware/MockDisplayMode.h" using namespace std::chrono_literals; @@ -37,26 +37,26 @@ namespace android::scheduler { namespace hal = android::hardware::graphics::composer::hal; -using LayerRequirement = RefreshRateConfigs::LayerRequirement; -using LayerVoteType = RefreshRateConfigs::LayerVoteType; -using SetPolicyResult = RefreshRateConfigs::SetPolicyResult; +using LayerRequirement = RefreshRateSelector::LayerRequirement; +using LayerVoteType = RefreshRateSelector::LayerVoteType; +using SetPolicyResult = RefreshRateSelector::SetPolicyResult; using mock::createDisplayMode; -struct TestableRefreshRateConfigs : RefreshRateConfigs { - using RefreshRateConfigs::RefreshRateOrder; - using RefreshRateConfigs::RefreshRateRanking; +struct TestableRefreshRateSelector : RefreshRateSelector { + using RefreshRateSelector::RefreshRateOrder; + using RefreshRateSelector::RefreshRateRanking; - using RefreshRateConfigs::RefreshRateConfigs; + using RefreshRateSelector::RefreshRateSelector; void setActiveModeId(DisplayModeId modeId) { ftl::FakeGuard guard(kMainThreadContext); - return RefreshRateConfigs::setActiveModeId(modeId); + return RefreshRateSelector::setActiveModeId(modeId); } const DisplayMode& getActiveMode() const { ftl::FakeGuard guard(kMainThreadContext); - return RefreshRateConfigs::getActiveMode(); + return RefreshRateSelector::getActiveMode(); } DisplayModePtr getMinSupportedRefreshRate() const { @@ -82,17 +82,17 @@ struct TestableRefreshRateConfigs : RefreshRateConfigs { RefreshRateRanking rankRefreshRates(std::optional anchorGroupOpt, RefreshRateOrder refreshRateOrder) const { std::lock_guard lock(mLock); - return RefreshRateConfigs::rankRefreshRates(anchorGroupOpt, refreshRateOrder); + return RefreshRateSelector::rankRefreshRates(anchorGroupOpt, refreshRateOrder); } const std::vector& knownFrameRates() const { return mKnownFrameRates; } - using RefreshRateConfigs::GetRankedRefreshRatesCache; + using RefreshRateSelector::GetRankedRefreshRatesCache; auto& mutableGetRankedRefreshRatesCache() { return mGetRankedRefreshRatesCache; } auto getRankedRefreshRates(const std::vector& layers, GlobalSignals signals) const { - const auto result = RefreshRateConfigs::getRankedRefreshRates(layers, signals); + const auto result = RefreshRateSelector::getRankedRefreshRates(layers, signals); EXPECT_TRUE(std::is_sorted(result.ranking.begin(), result.ranking.end(), ScoredRefreshRate::DescendingScore{})); @@ -113,7 +113,7 @@ struct TestableRefreshRateConfigs : RefreshRateConfigs { SetPolicyResult setPolicy(const PolicyVariant& policy) { ftl::FakeGuard guard(kMainThreadContext); - return RefreshRateConfigs::setPolicy(policy); + return RefreshRateSelector::setPolicy(policy); } SetPolicyResult setDisplayManagerPolicy(const DisplayManagerPolicy& policy) { @@ -121,12 +121,12 @@ struct TestableRefreshRateConfigs : RefreshRateConfigs { } }; -class RefreshRateConfigsTest : public testing::Test { +class RefreshRateSelectorTest : public testing::Test { protected: - using RefreshRateOrder = TestableRefreshRateConfigs::RefreshRateOrder; + using RefreshRateOrder = TestableRefreshRateSelector::RefreshRateOrder; - RefreshRateConfigsTest(); - ~RefreshRateConfigsTest(); + RefreshRateSelectorTest(); + ~RefreshRateSelectorTest(); static constexpr DisplayModeId kModeId60{0}; static constexpr DisplayModeId kModeId90{1}; @@ -186,13 +186,13 @@ protected: kMode60Frac); }; -RefreshRateConfigsTest::RefreshRateConfigsTest() { +RefreshRateSelectorTest::RefreshRateSelectorTest() { const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); } -RefreshRateConfigsTest::~RefreshRateConfigsTest() { +RefreshRateSelectorTest::~RefreshRateSelectorTest() { const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); @@ -200,358 +200,359 @@ RefreshRateConfigsTest::~RefreshRateConfigsTest() { namespace { -TEST_F(RefreshRateConfigsTest, oneMode_canSwitch) { - RefreshRateConfigs configs(kModes_60, kModeId60); - EXPECT_FALSE(configs.canSwitch()); +TEST_F(RefreshRateSelectorTest, oneMode_canSwitch) { + RefreshRateSelector selector(kModes_60, kModeId60); + EXPECT_FALSE(selector.canSwitch()); } -TEST_F(RefreshRateConfigsTest, invalidPolicy) { - TestableRefreshRateConfigs configs(kModes_60, kModeId60); +TEST_F(RefreshRateSelectorTest, invalidPolicy) { + TestableRefreshRateSelector selector(kModes_60, kModeId60); EXPECT_EQ(SetPolicyResult::Invalid, - configs.setDisplayManagerPolicy({DisplayModeId(10), {60_Hz, 60_Hz}})); + selector.setDisplayManagerPolicy({DisplayModeId(10), {60_Hz, 60_Hz}})); EXPECT_EQ(SetPolicyResult::Invalid, - configs.setDisplayManagerPolicy({kModeId60, {20_Hz, 40_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {20_Hz, 40_Hz}})); } -TEST_F(RefreshRateConfigsTest, unchangedPolicy) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); +TEST_F(RefreshRateSelectorTest, unchangedPolicy) { + TestableRefreshRateSelector selector(kModes_60_90, kModeId60); EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}})); EXPECT_EQ(SetPolicyResult::Unchanged, - configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}})); // Override to the same policy. EXPECT_EQ(SetPolicyResult::Unchanged, - configs.setPolicy(RefreshRateConfigs::OverridePolicy{kModeId90, {60_Hz, 90_Hz}})); + selector.setPolicy(RefreshRateSelector::OverridePolicy{kModeId90, {60_Hz, 90_Hz}})); // Clear override to restore DisplayManagerPolicy. EXPECT_EQ(SetPolicyResult::Unchanged, - configs.setPolicy(RefreshRateConfigs::NoOverridePolicy{})); + selector.setPolicy(RefreshRateSelector::NoOverridePolicy{})); EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId90, {30_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId90, {30_Hz, 90_Hz}})); } -TEST_F(RefreshRateConfigsTest, twoModes_storesFullRefreshRateMap) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); +TEST_F(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap) { + TestableRefreshRateSelector selector(kModes_60_90, kModeId60); - const auto minRate = configs.getMinSupportedRefreshRate(); - const auto performanceRate = configs.getMaxSupportedRefreshRate(); + const auto minRate = selector.getMinSupportedRefreshRate(); + const auto performanceRate = selector.getMaxSupportedRefreshRate(); EXPECT_EQ(kMode60, minRate); EXPECT_EQ(kMode90, performanceRate); - const auto minRateByPolicy = configs.getMinRefreshRateByPolicy(); - const auto performanceRateByPolicy = configs.getMaxRefreshRateByPolicy(); + const auto minRateByPolicy = selector.getMinRefreshRateByPolicy(); + const auto performanceRateByPolicy = selector.getMaxRefreshRateByPolicy(); EXPECT_EQ(minRateByPolicy, minRate); EXPECT_EQ(performanceRateByPolicy, performanceRate); } -TEST_F(RefreshRateConfigsTest, twoModes_storesFullRefreshRateMap_differentGroups) { - TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60); +TEST_F(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap_differentGroups) { + TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60); - const auto minRate = configs.getMinRefreshRateByPolicy(); - const auto performanceRate = configs.getMaxSupportedRefreshRate(); - const auto minRate60 = configs.getMinRefreshRateByPolicy(); - const auto performanceRate60 = configs.getMaxRefreshRateByPolicy(); + const auto minRate = selector.getMinRefreshRateByPolicy(); + const auto performanceRate = selector.getMaxSupportedRefreshRate(); + const auto minRate60 = selector.getMinRefreshRateByPolicy(); + const auto performanceRate60 = selector.getMaxRefreshRateByPolicy(); EXPECT_EQ(kMode60, minRate); EXPECT_EQ(kMode60, minRate60); EXPECT_EQ(kMode60, performanceRate60); EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}})); - configs.setActiveModeId(kModeId90); + selector.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}})); + selector.setActiveModeId(kModeId90); - const auto minRate90 = configs.getMinRefreshRateByPolicy(); - const auto performanceRate90 = configs.getMaxRefreshRateByPolicy(); + const auto minRate90 = selector.getMinRefreshRateByPolicy(); + const auto performanceRate90 = selector.getMaxRefreshRateByPolicy(); EXPECT_EQ(kMode90_G1, performanceRate); EXPECT_EQ(kMode90_G1, minRate90); EXPECT_EQ(kMode90_G1, performanceRate90); } -TEST_F(RefreshRateConfigsTest, twoModes_storesFullRefreshRateMap_differentResolutions) { - TestableRefreshRateConfigs configs(kModes_60_90_4K, kModeId60); +TEST_F(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap_differentResolutions) { + TestableRefreshRateSelector selector(kModes_60_90_4K, kModeId60); - const auto minRate = configs.getMinRefreshRateByPolicy(); - const auto performanceRate = configs.getMaxSupportedRefreshRate(); - const auto minRate60 = configs.getMinRefreshRateByPolicy(); - const auto performanceRate60 = configs.getMaxRefreshRateByPolicy(); + const auto minRate = selector.getMinRefreshRateByPolicy(); + const auto performanceRate = selector.getMaxSupportedRefreshRate(); + const auto minRate60 = selector.getMinRefreshRateByPolicy(); + const auto performanceRate60 = selector.getMaxRefreshRateByPolicy(); EXPECT_EQ(kMode60, minRate); EXPECT_EQ(kMode60, minRate60); EXPECT_EQ(kMode60, performanceRate60); EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}})); - configs.setActiveModeId(kModeId90); + selector.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}})); + selector.setActiveModeId(kModeId90); - const auto minRate90 = configs.getMinRefreshRateByPolicy(); - const auto performanceRate90 = configs.getMaxRefreshRateByPolicy(); + const auto minRate90 = selector.getMinRefreshRateByPolicy(); + const auto performanceRate90 = selector.getMaxRefreshRateByPolicy(); EXPECT_EQ(kMode90_4K, performanceRate); EXPECT_EQ(kMode90_4K, minRate90); EXPECT_EQ(kMode90_4K, performanceRate90); } -TEST_F(RefreshRateConfigsTest, twoModes_policyChange) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); +TEST_F(RefreshRateSelectorTest, twoModes_policyChange) { + TestableRefreshRateSelector selector(kModes_60_90, kModeId60); - const auto minRate = configs.getMinRefreshRateByPolicy(); - const auto performanceRate = configs.getMaxRefreshRateByPolicy(); + const auto minRate = selector.getMinRefreshRateByPolicy(); + const auto performanceRate = selector.getMaxRefreshRateByPolicy(); EXPECT_EQ(kMode60, minRate); EXPECT_EQ(kMode90, performanceRate); EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}})); - const auto minRate60 = configs.getMinRefreshRateByPolicy(); - const auto performanceRate60 = configs.getMaxRefreshRateByPolicy(); + const auto minRate60 = selector.getMinRefreshRateByPolicy(); + const auto performanceRate60 = selector.getMaxRefreshRateByPolicy(); EXPECT_EQ(kMode60, minRate60); EXPECT_EQ(kMode60, performanceRate60); } -TEST_F(RefreshRateConfigsTest, twoModes_getActiveMode) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); +TEST_F(RefreshRateSelectorTest, twoModes_getActiveMode) { + TestableRefreshRateSelector selector(kModes_60_90, kModeId60); { - const auto& mode = configs.getActiveMode(); + const auto& mode = selector.getActiveMode(); EXPECT_EQ(mode.getId(), kModeId60); } - configs.setActiveModeId(kModeId90); + selector.setActiveModeId(kModeId90); { - const auto& mode = configs.getActiveMode(); + const auto& mode = selector.getActiveMode(); EXPECT_EQ(mode.getId(), kModeId90); } EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}})); { - const auto& mode = configs.getActiveMode(); + const auto& mode = selector.getActiveMode(); EXPECT_EQ(mode.getId(), kModeId90); } } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_noLayers) { +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_noLayers) { { - TestableRefreshRateConfigs configs(kModes_60_72_90, kModeId72); + TestableRefreshRateSelector selector(kModes_60_72_90, kModeId72); // If there are no layers we select the default frame rate, which is the max of the primary // range. - EXPECT_EQ(kMode90, configs.getBestRefreshRate()); + EXPECT_EQ(kMode90, selector.getBestRefreshRate()); EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}})); - EXPECT_EQ(kMode60, configs.getBestRefreshRate()); + selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}})); + EXPECT_EQ(kMode60, selector.getBestRefreshRate()); } { // We select max even when this will cause a non-seamless switch. - TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60); + TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60); constexpr bool kAllowGroupSwitching = true; EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy( + selector.setDisplayManagerPolicy( {kModeId90, kAllowGroupSwitching, {0_Hz, 90_Hz}})); - EXPECT_EQ(kMode90_G1, configs.getBestRefreshRate()); + EXPECT_EQ(kMode90_G1, selector.getBestRefreshRate()); } } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_exactDontChangeRefreshRateWhenNotInPolicy) { - TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId72); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_exactDontChangeRefreshRateWhenNotInPolicy) { + TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId72); std::vector layers = {{.weight = 1.f}}; layers[0].vote = LayerVoteType::ExplicitExact; layers[0].desiredRefreshRate = 120_Hz; EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId72, {0_Hz, 90_Hz}})); - EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers)); + selector.setDisplayManagerPolicy({kModeId72, {0_Hz, 90_Hz}})); + EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers)); } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_60_90) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_60_90) { + TestableRefreshRateSelector selector(kModes_60_90, kModeId60); std::vector layers = {{.weight = 1.f}}; auto& lr = layers[0]; lr.vote = LayerVoteType::Min; lr.name = "Min"; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.vote = LayerVoteType::Max; lr.name = "Max"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 90_Hz; lr.vote = LayerVoteType::Heuristic; lr.name = "90Hz Heuristic"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 60_Hz; lr.name = "60Hz Heuristic"; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 45_Hz; lr.name = "45Hz Heuristic"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 30_Hz; lr.name = "30Hz Heuristic"; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 24_Hz; lr.name = "24Hz Heuristic"; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.name = ""; EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}})); lr.vote = LayerVoteType::Min; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.vote = LayerVoteType::Max; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 90_Hz; lr.vote = LayerVoteType::Heuristic; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 60_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 45_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 30_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 24_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}})); lr.vote = LayerVoteType::Min; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.vote = LayerVoteType::Max; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 90_Hz; lr.vote = LayerVoteType::Heuristic; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 60_Hz; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 45_Hz; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 30_Hz; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 24_Hz; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId60, {0_Hz, 120_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {0_Hz, 120_Hz}})); lr.vote = LayerVoteType::Min; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.vote = LayerVoteType::Max; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 90_Hz; lr.vote = LayerVoteType::Heuristic; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 60_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 45_Hz; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 30_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 24_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_multipleThreshold_60_90) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId60, {.frameRateMultipleThreshold = 90}); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_multipleThreshold_60_90) { + TestableRefreshRateSelector selector(kModes_60_90, kModeId60, + {.frameRateMultipleThreshold = 90}); std::vector layers = {{.weight = 1.f}}; auto& lr = layers[0]; lr.vote = LayerVoteType::Min; lr.name = "Min"; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.vote = LayerVoteType::Max; lr.name = "Max"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 90_Hz; lr.vote = LayerVoteType::Heuristic; lr.name = "90Hz Heuristic"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 60_Hz; lr.name = "60Hz Heuristic"; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 45_Hz; lr.name = "45Hz Heuristic"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 30_Hz; lr.name = "30Hz Heuristic"; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 24_Hz; lr.name = "24Hz Heuristic"; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_60_72_90) { - TestableRefreshRateConfigs configs(kModes_60_72_90, kModeId60); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_60_72_90) { + TestableRefreshRateSelector selector(kModes_60_72_90, kModeId60); std::vector layers = {{.weight = 1.f}}; auto& lr = layers[0]; lr.vote = LayerVoteType::Min; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.vote = LayerVoteType::Max; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 90_Hz; lr.vote = LayerVoteType::Heuristic; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 60_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 45_Hz; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 30_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 24_Hz; - EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers)); } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_72_90_120) { - TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_30_60_72_90_120) { + TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60); std::vector layers = {{.weight = 1.f}, {.weight = 1.f}}; auto& lr1 = layers[0]; @@ -561,23 +562,23 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_72_90_120) { lr1.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 60_Hz; lr2.vote = LayerVoteType::Heuristic; - EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 48_Hz; lr2.vote = LayerVoteType::Heuristic; - EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 48_Hz; lr2.vote = LayerVoteType::Heuristic; - EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers)); } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes) { - TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_30_60_90_120_DifferentTypes) { + TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60); std::vector layers = {{.weight = 1.f}, {.weight = 1.f}}; auto& lr1 = layers[0]; @@ -589,7 +590,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes) { lr2.desiredRefreshRate = 60_Hz; lr2.vote = LayerVoteType::Heuristic; lr2.name = "60Hz Heuristic"; - EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::ExplicitExactOrMultiple; @@ -597,7 +598,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes) { lr2.desiredRefreshRate = 60_Hz; lr2.vote = LayerVoteType::Heuristic; lr2.name = "60Hz Heuristic"; - EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::ExplicitExactOrMultiple; @@ -605,7 +606,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes) { lr2.desiredRefreshRate = 60_Hz; lr2.vote = LayerVoteType::ExplicitDefault; lr2.name = "60Hz ExplicitDefault"; - EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::ExplicitExactOrMultiple; @@ -613,7 +614,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes) { lr2.desiredRefreshRate = 90_Hz; lr2.vote = LayerVoteType::Heuristic; lr2.name = "90Hz Heuristic"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::ExplicitExactOrMultiple; @@ -621,7 +622,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes) { lr2.desiredRefreshRate = 90_Hz; lr2.vote = LayerVoteType::ExplicitDefault; lr2.name = "90Hz Heuristic"; - EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::ExplicitDefault; @@ -629,7 +630,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes) { lr2.desiredRefreshRate = 90_Hz; lr2.vote = LayerVoteType::Heuristic; lr2.name = "90Hz Heuristic"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::Heuristic; @@ -637,7 +638,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes) { lr2.desiredRefreshRate = 90_Hz; lr2.vote = LayerVoteType::ExplicitDefault; lr2.name = "90Hz ExplicitDefault"; - EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::ExplicitExactOrMultiple; @@ -645,7 +646,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes) { lr2.desiredRefreshRate = 90_Hz; lr2.vote = LayerVoteType::ExplicitDefault; lr2.name = "90Hz ExplicitDefault"; - EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::ExplicitDefault; @@ -653,12 +654,12 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes) { lr2.desiredRefreshRate = 90_Hz; lr2.vote = LayerVoteType::ExplicitExactOrMultiple; lr2.name = "90Hz ExplicitExactOrMultiple"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_multipleThreshold) { - TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60, - {.frameRateMultipleThreshold = 120}); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_30_60_90_120_DifferentTypes_multipleThreshold) { + TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60, + {.frameRateMultipleThreshold = 120}); std::vector layers = {{.weight = 1.f}, {.weight = 1.f}, {.weight = 1.f}}; auto& lr1 = layers[0]; @@ -671,7 +672,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_mu lr2.desiredRefreshRate = 60_Hz; lr2.vote = LayerVoteType::Heuristic; lr2.name = "60Hz Heuristic"; - EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::ExplicitExactOrMultiple; @@ -679,7 +680,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_mu lr2.desiredRefreshRate = 60_Hz; lr2.vote = LayerVoteType::Heuristic; lr2.name = "60Hz Heuristic"; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::ExplicitExactOrMultiple; @@ -687,7 +688,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_mu lr2.desiredRefreshRate = 60_Hz; lr2.vote = LayerVoteType::ExplicitDefault; lr2.name = "60Hz ExplicitDefault"; - EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::ExplicitExactOrMultiple; @@ -695,7 +696,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_mu lr2.desiredRefreshRate = 90_Hz; lr2.vote = LayerVoteType::Heuristic; lr2.name = "90Hz Heuristic"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::ExplicitExactOrMultiple; @@ -703,7 +704,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_mu lr2.desiredRefreshRate = 90_Hz; lr2.vote = LayerVoteType::ExplicitDefault; lr2.name = "90Hz Heuristic"; - EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::ExplicitDefault; @@ -711,7 +712,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_mu lr2.desiredRefreshRate = 90_Hz; lr2.vote = LayerVoteType::Heuristic; lr2.name = "90Hz Heuristic"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::Heuristic; @@ -719,7 +720,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_mu lr2.desiredRefreshRate = 90_Hz; lr2.vote = LayerVoteType::ExplicitDefault; lr2.name = "90Hz ExplicitDefault"; - EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::ExplicitExactOrMultiple; @@ -727,7 +728,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_mu lr2.desiredRefreshRate = 90_Hz; lr2.vote = LayerVoteType::ExplicitDefault; lr2.name = "90Hz ExplicitDefault"; - EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::ExplicitDefault; @@ -735,14 +736,14 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_mu lr2.desiredRefreshRate = 90_Hz; lr2.vote = LayerVoteType::ExplicitExactOrMultiple; lr2.name = "90Hz ExplicitExactOrMultiple"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::ExplicitExactOrMultiple; lr1.name = "24Hz ExplicitExactOrMultiple"; lr2.vote = LayerVoteType::Max; lr2.name = "Max"; - EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::ExplicitExactOrMultiple; @@ -750,7 +751,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_mu lr2.desiredRefreshRate = 120_Hz; lr2.vote = LayerVoteType::ExplicitDefault; lr2.name = "120Hz ExplicitDefault"; - EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 24_Hz; lr1.vote = LayerVoteType::ExplicitExactOrMultiple; @@ -758,7 +759,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_mu lr2.desiredRefreshRate = 120_Hz; lr2.vote = LayerVoteType::ExplicitExact; lr2.name = "120Hz ExplicitExact"; - EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 10_Hz; lr1.vote = LayerVoteType::ExplicitExactOrMultiple; @@ -766,7 +767,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_mu lr2.desiredRefreshRate = 120_Hz; lr2.vote = LayerVoteType::Heuristic; lr2.name = "120Hz ExplicitExact"; - EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers)); lr1.desiredRefreshRate = 30_Hz; lr1.vote = LayerVoteType::ExplicitExactOrMultiple; @@ -777,86 +778,86 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_mu lr3.vote = LayerVoteType::Heuristic; lr3.desiredRefreshRate = 120_Hz; lr3.name = "120Hz Heuristic"; - EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers)); } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60) { - TestableRefreshRateConfigs configs(kModes_30_60, kModeId60); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_30_60) { + TestableRefreshRateSelector selector(kModes_30_60, kModeId60); std::vector layers = {{.weight = 1.f}}; auto& lr = layers[0]; lr.vote = LayerVoteType::Min; - EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode30, selector.getBestRefreshRate(layers)); lr.vote = LayerVoteType::Max; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 90_Hz; lr.vote = LayerVoteType::Heuristic; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 60_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 45_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 30_Hz; - EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode30, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 24_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_72_90) { - TestableRefreshRateConfigs configs(kModes_30_60_72_90, kModeId60); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_30_60_72_90) { + TestableRefreshRateSelector selector(kModes_30_60_72_90, kModeId60); std::vector layers = {{.weight = 1.f}}; auto& lr = layers[0]; lr.vote = LayerVoteType::Min; lr.name = "Min"; - EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode30, selector.getBestRefreshRate(layers)); lr.vote = LayerVoteType::Max; lr.name = "Max"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 90_Hz; lr.vote = LayerVoteType::Heuristic; lr.name = "90Hz Heuristic"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.desiredRefreshRate = 60_Hz; lr.name = "60Hz Heuristic"; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true})); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true})); lr.desiredRefreshRate = 45_Hz; lr.name = "45Hz Heuristic"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true})); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true})); lr.desiredRefreshRate = 30_Hz; lr.name = "30Hz Heuristic"; - EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers)); - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true})); + EXPECT_EQ(kMode30, selector.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true})); lr.desiredRefreshRate = 24_Hz; lr.name = "24Hz Heuristic"; - EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers)); - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true})); + EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true})); lr.desiredRefreshRate = 24_Hz; lr.vote = LayerVoteType::ExplicitExactOrMultiple; lr.name = "24Hz ExplicitExactOrMultiple"; - EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers)); - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true})); + EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true})); } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_PriorityTest) { - TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_PriorityTest) { + TestableRefreshRateSelector selector(kModes_30_60_90, kModeId60); std::vector layers = {{.weight = 1.f}, {.weight = 1.f}}; auto& lr1 = layers[0]; @@ -864,43 +865,43 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_PriorityTest) { lr1.vote = LayerVoteType::Min; lr2.vote = LayerVoteType::Max; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr1.vote = LayerVoteType::Min; lr2.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 24_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr1.vote = LayerVoteType::Min; lr2.vote = LayerVoteType::ExplicitExactOrMultiple; lr2.desiredRefreshRate = 24_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr1.vote = LayerVoteType::Max; lr2.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 60_Hz; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr1.vote = LayerVoteType::Max; lr2.vote = LayerVoteType::ExplicitExactOrMultiple; lr2.desiredRefreshRate = 60_Hz; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr1.vote = LayerVoteType::Heuristic; lr1.desiredRefreshRate = 15_Hz; lr2.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 45_Hz; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr1.vote = LayerVoteType::Heuristic; lr1.desiredRefreshRate = 30_Hz; lr2.vote = LayerVoteType::ExplicitExactOrMultiple; lr2.desiredRefreshRate = 45_Hz; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_24FpsVideo) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_24FpsVideo) { + TestableRefreshRateSelector selector(kModes_60_90, kModeId60); std::vector layers = {{.weight = 1.f}}; auto& lr = layers[0]; @@ -908,15 +909,15 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_24FpsVideo) { lr.vote = LayerVoteType::ExplicitExactOrMultiple; for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) { lr.desiredRefreshRate = Fps::fromValue(fps); - const auto mode = configs.getBestRefreshRate(layers); + const auto mode = selector.getBestRefreshRate(layers); EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses " << to_string(mode->getFps()); } } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_24FpsVideo_multipleThreshold_60_120) { - TestableRefreshRateConfigs configs(kModes_60_120, kModeId60, - {.frameRateMultipleThreshold = 120}); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_24FpsVideo_multipleThreshold_60_120) { + TestableRefreshRateSelector selector(kModes_60_120, kModeId60, + {.frameRateMultipleThreshold = 120}); std::vector layers = {{.weight = 1.f}}; auto& lr = layers[0]; @@ -924,14 +925,14 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_24FpsVideo_multipleThreshold_6 lr.vote = LayerVoteType::ExplicitExactOrMultiple; for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) { lr.desiredRefreshRate = Fps::fromValue(fps); - const auto mode = configs.getBestRefreshRate(layers); + const auto mode = selector.getBestRefreshRate(layers); EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses " << to_string(mode->getFps()); } } -TEST_F(RefreshRateConfigsTest, twoModes_getBestRefreshRate_Explicit) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); +TEST_F(RefreshRateSelectorTest, twoModes_getBestRefreshRate_Explicit) { + TestableRefreshRateSelector selector(kModes_60_90, kModeId60); std::vector layers = {{.weight = 1.f}, {.weight = 1.f}}; auto& lr1 = layers[0]; @@ -941,23 +942,23 @@ TEST_F(RefreshRateConfigsTest, twoModes_getBestRefreshRate_Explicit) { lr1.desiredRefreshRate = 60_Hz; lr2.vote = LayerVoteType::ExplicitExactOrMultiple; lr2.desiredRefreshRate = 90_Hz; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr1.vote = LayerVoteType::ExplicitDefault; lr1.desiredRefreshRate = 90_Hz; lr2.vote = LayerVoteType::ExplicitExactOrMultiple; lr2.desiredRefreshRate = 60_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr1.vote = LayerVoteType::Heuristic; lr1.desiredRefreshRate = 90_Hz; lr2.vote = LayerVoteType::ExplicitExactOrMultiple; lr2.desiredRefreshRate = 60_Hz; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_75HzContent) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_75HzContent) { + TestableRefreshRateSelector selector(kModes_60_90, kModeId60); std::vector layers = {{.weight = 1.f}}; auto& lr = layers[0]; @@ -965,14 +966,14 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_75HzContent) { lr.vote = LayerVoteType::ExplicitExactOrMultiple; for (float fps = 75.0f; fps < 100.0f; fps += 0.1f) { lr.desiredRefreshRate = Fps::fromValue(fps); - const auto mode = configs.getBestRefreshRate(layers, {}); + const auto mode = selector.getBestRefreshRate(layers, {}); EXPECT_EQ(kMode90, mode) << lr.desiredRefreshRate << " chooses " << to_string(mode->getFps()); } } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_Multiples) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_Multiples) { + TestableRefreshRateSelector selector(kModes_60_90, kModeId60); std::vector layers = {{.weight = 1.f}, {.weight = 1.f}}; auto& lr1 = layers[0]; @@ -984,7 +985,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_Multiples) { lr2.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 90_Hz; lr2.name = "90Hz Heuristic"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr1.vote = LayerVoteType::ExplicitExactOrMultiple; lr1.desiredRefreshRate = 60_Hz; @@ -992,14 +993,14 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_Multiples) { lr2.vote = LayerVoteType::ExplicitDefault; lr2.desiredRefreshRate = 90_Hz; lr2.name = "90Hz ExplicitDefault"; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr1.vote = LayerVoteType::ExplicitExactOrMultiple; lr1.desiredRefreshRate = 60_Hz; lr1.name = "60Hz ExplicitExactOrMultiple"; lr2.vote = LayerVoteType::Max; lr2.name = "Max"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr1.vote = LayerVoteType::ExplicitExactOrMultiple; lr1.desiredRefreshRate = 30_Hz; @@ -1007,18 +1008,18 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_Multiples) { lr2.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 90_Hz; lr2.name = "90Hz Heuristic"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr1.vote = LayerVoteType::ExplicitExactOrMultiple; lr1.desiredRefreshRate = 30_Hz; lr1.name = "30Hz ExplicitExactOrMultiple"; lr2.vote = LayerVoteType::Max; lr2.name = "Max"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); } -TEST_F(RefreshRateConfigsTest, scrollWhileWatching60fps_60_90) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); +TEST_F(RefreshRateSelectorTest, scrollWhileWatching60fps_60_90) { + TestableRefreshRateSelector selector(kModes_60_90, kModeId60); std::vector layers = {{.weight = 1.f}, {.weight = 1.f}}; auto& lr1 = layers[0]; @@ -1029,28 +1030,28 @@ TEST_F(RefreshRateConfigsTest, scrollWhileWatching60fps_60_90) { lr1.name = "60Hz ExplicitExactOrMultiple"; lr2.vote = LayerVoteType::NoVote; lr2.name = "NoVote"; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr1.vote = LayerVoteType::ExplicitExactOrMultiple; lr1.desiredRefreshRate = 60_Hz; lr1.name = "60Hz ExplicitExactOrMultiple"; lr2.vote = LayerVoteType::NoVote; lr2.name = "NoVote"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true})); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true})); lr1.vote = LayerVoteType::ExplicitExactOrMultiple; lr1.desiredRefreshRate = 60_Hz; lr1.name = "60Hz ExplicitExactOrMultiple"; lr2.vote = LayerVoteType::Max; lr2.name = "Max"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true})); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true})); lr1.vote = LayerVoteType::ExplicitExactOrMultiple; lr1.desiredRefreshRate = 60_Hz; lr1.name = "60Hz ExplicitExactOrMultiple"; lr2.vote = LayerVoteType::Max; lr2.name = "Max"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); // The other layer starts to provide buffers lr1.vote = LayerVoteType::ExplicitExactOrMultiple; @@ -1059,16 +1060,16 @@ TEST_F(RefreshRateConfigsTest, scrollWhileWatching60fps_60_90) { lr2.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 90_Hz; lr2.name = "90Hz Heuristic"; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); } -TEST_F(RefreshRateConfigsTest, getMaxRefreshRatesByPolicy) { +TEST_F(RefreshRateSelectorTest, getMaxRefreshRatesByPolicy) { // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the // different group. - TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60); + TestableRefreshRateSelector selector(kModes_30_60_90, kModeId60); - const auto refreshRates = configs.rankRefreshRates(configs.getActiveMode().getGroup(), - RefreshRateOrder::Descending); + const auto refreshRates = selector.rankRefreshRates(selector.getActiveMode().getGroup(), + RefreshRateOrder::Descending); const std::array expectedRefreshRates = {kMode90, kMode60, kMode30}; ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); @@ -1080,13 +1081,13 @@ TEST_F(RefreshRateConfigsTest, getMaxRefreshRatesByPolicy) { } } -TEST_F(RefreshRateConfigsTest, getMinRefreshRatesByPolicy) { +TEST_F(RefreshRateSelectorTest, getMinRefreshRatesByPolicy) { // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the // different group. - TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60); + TestableRefreshRateSelector selector(kModes_30_60_90, kModeId60); - const auto refreshRates = configs.rankRefreshRates(configs.getActiveMode().getGroup(), - RefreshRateOrder::Ascending); + const auto refreshRates = selector.rankRefreshRates(selector.getActiveMode().getGroup(), + RefreshRateOrder::Ascending); const std::array expectedRefreshRates = {kMode30, kMode60, kMode90}; ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); @@ -1098,16 +1099,16 @@ TEST_F(RefreshRateConfigsTest, getMinRefreshRatesByPolicy) { } } -TEST_F(RefreshRateConfigsTest, getMinRefreshRatesByPolicyOutsideTheGroup) { +TEST_F(RefreshRateSelectorTest, getMinRefreshRatesByPolicyOutsideTheGroup) { // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the // different group. - TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId72); + TestableRefreshRateSelector selector(kModes_30_60_90, kModeId72); EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}})); const auto refreshRates = - configs.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Ascending); + selector.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Ascending); const std::array expectedRefreshRates = {kMode30, kMode60, kMode90}; ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); @@ -1119,16 +1120,16 @@ TEST_F(RefreshRateConfigsTest, getMinRefreshRatesByPolicyOutsideTheGroup) { } } -TEST_F(RefreshRateConfigsTest, getMaxRefreshRatesByPolicyOutsideTheGroup) { +TEST_F(RefreshRateSelectorTest, getMaxRefreshRatesByPolicyOutsideTheGroup) { // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the // different group. - TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId72); + TestableRefreshRateSelector selector(kModes_30_60_90, kModeId72); EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}})); - const auto refreshRates = - configs.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Descending); + const auto refreshRates = selector.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, + RefreshRateOrder::Descending); const std::array expectedRefreshRates = {kMode90, kMode60, kMode30}; ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); @@ -1140,10 +1141,10 @@ TEST_F(RefreshRateConfigsTest, getMaxRefreshRatesByPolicyOutsideTheGroup) { } } -TEST_F(RefreshRateConfigsTest, powerOnImminentConsidered) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); +TEST_F(RefreshRateSelectorTest, powerOnImminentConsidered) { + TestableRefreshRateSelector selector(kModes_60_90, kModeId60); - auto [refreshRates, signals] = configs.getRankedRefreshRates({}, {}); + auto [refreshRates, signals] = selector.getRankedRefreshRates({}, {}); EXPECT_FALSE(signals.powerOnImminent); std::array expectedRefreshRates = {kMode90, kMode60}; @@ -1156,7 +1157,7 @@ TEST_F(RefreshRateConfigsTest, powerOnImminentConsidered) { } std::tie(refreshRates, signals) = - configs.getRankedRefreshRatesAsPair({}, {.powerOnImminent = true}); + selector.getRankedRefreshRatesAsPair({}, {.powerOnImminent = true}); EXPECT_TRUE(signals.powerOnImminent); ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); @@ -1174,7 +1175,7 @@ TEST_F(RefreshRateConfigsTest, powerOnImminentConsidered) { lr1.name = "60Hz ExplicitExactOrMultiple"; std::tie(refreshRates, signals) = - configs.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = true}); + selector.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = true}); EXPECT_TRUE(signals.powerOnImminent); ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); @@ -1186,7 +1187,7 @@ TEST_F(RefreshRateConfigsTest, powerOnImminentConsidered) { } std::tie(refreshRates, signals) = - configs.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = false}); + selector.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = false}); EXPECT_FALSE(signals.powerOnImminent); expectedRefreshRates = {kMode60, kMode90}; @@ -1199,13 +1200,13 @@ TEST_F(RefreshRateConfigsTest, powerOnImminentConsidered) { } } -TEST_F(RefreshRateConfigsTest, touchConsidered) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); +TEST_F(RefreshRateSelectorTest, touchConsidered) { + TestableRefreshRateSelector selector(kModes_60_90, kModeId60); - auto [_, signals] = configs.getRankedRefreshRates({}, {}); + auto [_, signals] = selector.getRankedRefreshRates({}, {}); EXPECT_FALSE(signals.touch); - std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair({}, {.touch = true}); + std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair({}, {.touch = true}); EXPECT_TRUE(signals.touch); std::vector layers = {{.weight = 1.f}, {.weight = 1.f}}; @@ -1218,7 +1219,7 @@ TEST_F(RefreshRateConfigsTest, touchConsidered) { lr2.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 60_Hz; lr2.name = "60Hz Heuristic"; - std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true}); + std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair(layers, {.touch = true}); EXPECT_TRUE(signals.touch); lr1.vote = LayerVoteType::ExplicitDefault; @@ -1227,7 +1228,7 @@ TEST_F(RefreshRateConfigsTest, touchConsidered) { lr2.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 60_Hz; lr2.name = "60Hz Heuristic"; - std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true}); + std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair(layers, {.touch = true}); EXPECT_FALSE(signals.touch); lr1.vote = LayerVoteType::ExplicitExactOrMultiple; @@ -1236,7 +1237,7 @@ TEST_F(RefreshRateConfigsTest, touchConsidered) { lr2.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 60_Hz; lr2.name = "60Hz Heuristic"; - std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true}); + std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair(layers, {.touch = true}); EXPECT_TRUE(signals.touch); lr1.vote = LayerVoteType::ExplicitDefault; @@ -1245,12 +1246,12 @@ TEST_F(RefreshRateConfigsTest, touchConsidered) { lr2.vote = LayerVoteType::Heuristic; lr2.desiredRefreshRate = 60_Hz; lr2.name = "60Hz Heuristic"; - std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true}); + std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair(layers, {.touch = true}); EXPECT_FALSE(signals.touch); } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitDefault) { - TestableRefreshRateConfigs configs(kModes_60_90_72_120, kModeId60); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitDefault) { + TestableRefreshRateSelector selector(kModes_60_90_72_120, kModeId60); std::vector layers = {{.weight = 1.f}}; auto& lr = layers[0]; @@ -1282,57 +1283,57 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitDefault) { ss << "ExplicitDefault " << desired; lr.name = ss.str(); - EXPECT_EQ(expected, configs.getBestRefreshRate(layers)->getFps()); + EXPECT_EQ(expected, selector.getBestRefreshRate(layers)->getFps()); } } -TEST_F(RefreshRateConfigsTest, +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExactOrMultiple_WithFractionalRefreshRates) { std::vector layers = {{.weight = 1.f}}; auto& lr = layers[0]; // Test that 23.976 will choose 24 if 23.976 is not supported { - TestableRefreshRateConfigs configs(makeModes(kMode24, kMode25, kMode30, kMode30Frac, - kMode60, kMode60Frac), - kModeId60); + TestableRefreshRateSelector selector(makeModes(kMode24, kMode25, kMode30, kMode30Frac, + kMode60, kMode60Frac), + kModeId60); lr.vote = LayerVoteType::ExplicitExactOrMultiple; lr.desiredRefreshRate = 23.976_Hz; lr.name = "ExplicitExactOrMultiple 23.976 Hz"; - EXPECT_EQ(kModeId24, configs.getBestRefreshRate(layers)->getId()); + EXPECT_EQ(kModeId24, selector.getBestRefreshRate(layers)->getId()); } // Test that 24 will choose 23.976 if 24 is not supported { - TestableRefreshRateConfigs configs(makeModes(kMode24Frac, kMode25, kMode30, kMode30Frac, - kMode60, kMode60Frac), - kModeId60); + TestableRefreshRateSelector selector(makeModes(kMode24Frac, kMode25, kMode30, kMode30Frac, + kMode60, kMode60Frac), + kModeId60); lr.desiredRefreshRate = 24_Hz; lr.name = "ExplicitExactOrMultiple 24 Hz"; - EXPECT_EQ(kModeId24Frac, configs.getBestRefreshRate(layers)->getId()); + EXPECT_EQ(kModeId24Frac, selector.getBestRefreshRate(layers)->getId()); } // Test that 29.97 will prefer 59.94 over 60 and 30 { - TestableRefreshRateConfigs configs(makeModes(kMode24, kMode24Frac, kMode25, kMode30, - kMode60, kMode60Frac), - kModeId60); + TestableRefreshRateSelector selector(makeModes(kMode24, kMode24Frac, kMode25, kMode30, + kMode60, kMode60Frac), + kModeId60); lr.desiredRefreshRate = 29.97_Hz; lr.name = "ExplicitExactOrMultiple 29.97 Hz"; - EXPECT_EQ(kModeId60Frac, configs.getBestRefreshRate(layers)->getId()); + EXPECT_EQ(kModeId60Frac, selector.getBestRefreshRate(layers)->getId()); } } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExact_WithFractionalRefreshRates) { +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExact_WithFractionalRefreshRates) { std::vector layers = {{.weight = 1.f}}; auto& lr = layers[0]; // Test that voting for supported refresh rate will select this refresh rate { - TestableRefreshRateConfigs configs(kModes_24_25_30_50_60_Frac, kModeId60); + TestableRefreshRateSelector selector(kModes_24_25_30_50_60_Frac, kModeId60); for (auto desired : {23.976_Hz, 24_Hz, 25_Hz, 29.97_Hz, 30_Hz, 50_Hz, 59.94_Hz, 60_Hz}) { lr.vote = LayerVoteType::ExplicitExact; @@ -1341,17 +1342,17 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExact_WithFractionalRe ss << "ExplicitExact " << desired; lr.name = ss.str(); - EXPECT_EQ(lr.desiredRefreshRate, configs.getBestRefreshRate(layers)->getFps()); + EXPECT_EQ(lr.desiredRefreshRate, selector.getBestRefreshRate(layers)->getFps()); } } } -TEST_F(RefreshRateConfigsTest, +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresTouchFlag) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId90); + TestableRefreshRateSelector selector(kModes_60_90, kModeId90); EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}})); std::vector layers = {{.weight = 1.f}}; auto& lr = layers[0]; @@ -1362,18 +1363,18 @@ TEST_F(RefreshRateConfigsTest, lr.focused = true; const auto [mode, signals] = - configs.getRankedRefreshRates(layers, {.touch = true, .idle = true}); + selector.getRankedRefreshRates(layers, {.touch = true, .idle = true}); EXPECT_EQ(mode.begin()->modePtr, kMode60); EXPECT_FALSE(signals.touch); } -TEST_F(RefreshRateConfigsTest, +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresIdleFlag) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); + TestableRefreshRateSelector selector(kModes_60_90, kModeId60); EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 90_Hz}})); std::vector layers = {{.weight = 1.f}}; auto& lr = layers[0]; @@ -1382,11 +1383,11 @@ TEST_F(RefreshRateConfigsTest, lr.desiredRefreshRate = 90_Hz; lr.name = "90Hz ExplicitDefault"; lr.focused = true; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.idle = true})); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.idle = true})); } -TEST_F(RefreshRateConfigsTest, testDisplayModeOrdering) { - TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60); +TEST_F(RefreshRateSelectorTest, testDisplayModeOrdering) { + TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60); std::vector layers = {{.weight = 1.f}, {.weight = 1.f}, @@ -1420,7 +1421,7 @@ TEST_F(RefreshRateConfigsTest, testDisplayModeOrdering) { lr5.focused = true; std::array expectedRanking = {kMode120, kMode90, kMode72, kMode60, kMode30}; - auto actualRanking = configs.getRankedRefreshRates(layers, {}).ranking; + auto actualRanking = selector.getRankedRefreshRates(layers, {}).ranking; ASSERT_EQ(expectedRanking.size(), actualRanking.size()); @@ -1446,7 +1447,7 @@ TEST_F(RefreshRateConfigsTest, testDisplayModeOrdering) { lr5.name = "120Hz"; expectedRanking = {kMode120, kMode90, kMode72, kMode60, kMode30}; - actualRanking = configs.getRankedRefreshRates(layers, {}).ranking; + actualRanking = selector.getRankedRefreshRates(layers, {}).ranking; ASSERT_EQ(expectedRanking.size(), actualRanking.size()); @@ -1470,7 +1471,7 @@ TEST_F(RefreshRateConfigsTest, testDisplayModeOrdering) { lr5.name = "72Hz"; expectedRanking = {kMode30, kMode60, kMode90, kMode120, kMode72}; - actualRanking = configs.getRankedRefreshRates(layers, {}).ranking; + actualRanking = selector.getRankedRefreshRates(layers, {}).ranking; ASSERT_EQ(expectedRanking.size(), actualRanking.size()); @@ -1497,7 +1498,7 @@ TEST_F(RefreshRateConfigsTest, testDisplayModeOrdering) { lr5.name = "120Hz-2"; expectedRanking = {kMode90, kMode60, kMode120, kMode72, kMode30}; - actualRanking = configs.getRankedRefreshRates(layers, {}).ranking; + actualRanking = selector.getRankedRefreshRates(layers, {}).ranking; ASSERT_EQ(expectedRanking.size(), actualRanking.size()); @@ -1508,14 +1509,14 @@ TEST_F(RefreshRateConfigsTest, testDisplayModeOrdering) { } } -TEST_F(RefreshRateConfigsTest, +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_withDisplayManagerRequestingSingleRate_onlySwitchesRatesForExplicitFocusedLayers) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId90); + TestableRefreshRateSelector selector(kModes_60_90, kModeId90); EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}})); - const auto [ranking, signals] = configs.getRankedRefreshRates({}, {}); + const auto [ranking, signals] = selector.getRankedRefreshRates({}, {}); EXPECT_EQ(ranking.front().modePtr, kMode90); EXPECT_FALSE(signals.touch); @@ -1526,50 +1527,50 @@ TEST_F(RefreshRateConfigsTest, lr.desiredRefreshRate = 60_Hz; lr.name = "60Hz ExplicitExactOrMultiple"; lr.focused = false; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.focused = true; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.vote = LayerVoteType::ExplicitDefault; lr.desiredRefreshRate = 60_Hz; lr.name = "60Hz ExplicitDefault"; lr.focused = false; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.focused = true; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); lr.vote = LayerVoteType::Heuristic; lr.desiredRefreshRate = 60_Hz; lr.name = "60Hz Heuristic"; lr.focused = false; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.focused = true; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.vote = LayerVoteType::Max; lr.desiredRefreshRate = 60_Hz; lr.name = "60Hz Max"; lr.focused = false; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.focused = true; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.vote = LayerVoteType::Min; lr.desiredRefreshRate = 60_Hz; lr.name = "60Hz Min"; lr.focused = false; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); lr.focused = true; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); } -TEST_F(RefreshRateConfigsTest, groupSwitchingNotAllowed) { - TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60); +TEST_F(RefreshRateSelectorTest, groupSwitchingNotAllowed) { + TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60); // The default policy doesn't allow group switching. Verify that no // group switches are performed. @@ -1581,16 +1582,16 @@ TEST_F(RefreshRateConfigsTest, groupSwitchingNotAllowed) { layer.name = "90Hz ExplicitDefault"; layer.focused = true; - EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId()); + EXPECT_EQ(kModeId60, selector.getBestRefreshRate(layers)->getId()); } -TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayer) { - TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60); +TEST_F(RefreshRateSelectorTest, groupSwitchingWithOneLayer) { + TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60); - RefreshRateConfigs::DisplayManagerPolicy policy; - policy.defaultMode = configs.getCurrentPolicy().defaultMode; + RefreshRateSelector::DisplayManagerPolicy policy; + policy.defaultMode = selector.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); + EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy)); std::vector layers = {{.weight = 1.f}}; auto& layer = layers[0]; @@ -1599,16 +1600,16 @@ TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayer) { layer.seamlessness = Seamlessness::SeamedAndSeamless; layer.name = "90Hz ExplicitDefault"; layer.focused = true; - EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId()); + EXPECT_EQ(kModeId90, selector.getBestRefreshRate(layers)->getId()); } -TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerOnlySeamless) { - TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60); +TEST_F(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamless) { + TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60); - RefreshRateConfigs::DisplayManagerPolicy policy; - policy.defaultMode = configs.getCurrentPolicy().defaultMode; + RefreshRateSelector::DisplayManagerPolicy policy; + policy.defaultMode = selector.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); + EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy)); // Verify that we won't change the group if seamless switch is required. std::vector layers = {{.weight = 1.f}}; @@ -1618,18 +1619,18 @@ TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerOnlySeamless) { layer.seamlessness = Seamlessness::OnlySeamless; layer.name = "90Hz ExplicitDefault"; layer.focused = true; - EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId()); + EXPECT_EQ(kModeId60, selector.getBestRefreshRate(layers)->getId()); } -TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps) { - TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60); +TEST_F(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps) { + TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60); - RefreshRateConfigs::DisplayManagerPolicy policy; - policy.defaultMode = configs.getCurrentPolicy().defaultMode; + RefreshRateSelector::DisplayManagerPolicy policy; + policy.defaultMode = selector.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); + EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy)); - configs.setActiveModeId(kModeId90); + selector.setActiveModeId(kModeId90); // Verify that we won't do a seamless switch if we request the same mode as the default std::vector layers = {{.weight = 1.f}}; @@ -1639,21 +1640,21 @@ TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps) layer.seamlessness = Seamlessness::OnlySeamless; layer.name = "60Hz ExplicitDefault"; layer.focused = true; - EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId()); + EXPECT_EQ(kModeId90, selector.getBestRefreshRate(layers)->getId()); } -TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerDefaultSeamlessness) { - TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60); +TEST_F(RefreshRateSelectorTest, groupSwitchingWithOneLayerDefaultSeamlessness) { + TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60); - RefreshRateConfigs::DisplayManagerPolicy policy; - policy.defaultMode = configs.getCurrentPolicy().defaultMode; + RefreshRateSelector::DisplayManagerPolicy policy; + policy.defaultMode = selector.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); + EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy)); - configs.setActiveModeId(kModeId90); + selector.setActiveModeId(kModeId90); - // Verify that if the current config is in another group and there are no layers with - // seamlessness=SeamedAndSeamless we'll go back to the default group. + // Verify that if the active mode is in another group and there are no layers with + // Seamlessness::SeamedAndSeamless, we should switch back to the default group. std::vector layers = {{.weight = 1.f}}; auto& layer = layers[0]; @@ -1663,21 +1664,21 @@ TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerDefaultSeamlessness) { layer.name = "60Hz ExplicitDefault"; layer.focused = true; - EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId()); + EXPECT_EQ(kModeId60, selector.getBestRefreshRate(layers)->getId()); } -TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed) { - TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60); +TEST_F(RefreshRateSelectorTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed) { + TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60); - RefreshRateConfigs::DisplayManagerPolicy policy; - policy.defaultMode = configs.getCurrentPolicy().defaultMode; + RefreshRateSelector::DisplayManagerPolicy policy; + policy.defaultMode = selector.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); + EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy)); - configs.setActiveModeId(kModeId90); + selector.setActiveModeId(kModeId90); - // If there's a layer with seamlessness=SeamedAndSeamless, another layer with - // seamlessness=OnlySeamless can't change the mode group. + // If there's a layer with Seamlessness::SeamedAndSeamless, another layer with + // Seamlessness::OnlySeamless can't change the mode group. std::vector layers = {{.weight = 1.f}}; layers[0].vote = LayerVoteType::ExplicitDefault; layers[0].desiredRefreshRate = 60_Hz; @@ -1692,21 +1693,21 @@ TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed) layers[1].name = "90Hz ExplicitDefault"; layers[1].focused = false; - EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId()); + EXPECT_EQ(kModeId90, selector.getBestRefreshRate(layers)->getId()); } -TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeamed) { - TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60); +TEST_F(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeamed) { + TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60); - RefreshRateConfigs::DisplayManagerPolicy policy; - policy.defaultMode = configs.getCurrentPolicy().defaultMode; + RefreshRateSelector::DisplayManagerPolicy policy; + policy.defaultMode = selector.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); + EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy)); - configs.setActiveModeId(kModeId90); + selector.setActiveModeId(kModeId90); - // If there's a focused layer with seamlessness=SeamedAndSeamless, another layer with - // seamlessness=Default can't change the mode group back to the group of the default + // If there's a focused layer with Seamlessness::SeamedAndSeamless, another layer with + // Seamlessness::Default can't change the mode group back to the group of the default // mode. // For example, this may happen when a video playback requests and gets a seamed switch, // but another layer (with default seamlessness) starts animating. The animating layer @@ -1725,21 +1726,21 @@ TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeame layers[1].vote = LayerVoteType::ExplicitDefault; layers[1].name = "90Hz ExplicitDefault"; - EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId()); + EXPECT_EQ(kModeId90, selector.getBestRefreshRate(layers)->getId()); } -TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersDefaultNotFocusedAndSeamed) { - TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60); +TEST_F(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultNotFocusedAndSeamed) { + TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60); - RefreshRateConfigs::DisplayManagerPolicy policy; - policy.defaultMode = configs.getCurrentPolicy().defaultMode; + RefreshRateSelector::DisplayManagerPolicy policy; + policy.defaultMode = selector.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); + EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy)); - configs.setActiveModeId(kModeId90); + selector.setActiveModeId(kModeId90); - // Layer with seamlessness=Default can change the mode group if there's a not - // focused layer with seamlessness=SeamedAndSeamless. This happens for example, + // Layer with Seamlessness::Default can change the mode group if there's an + // unfocused layer with Seamlessness::SeamedAndSeamless. For example, this happens // when in split screen mode the user switches between the two visible applications. std::vector layers = {{.weight = 1.f}}; layers[0].seamlessness = Seamlessness::Default; @@ -1755,17 +1756,17 @@ TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersDefaultNotFocusedAndSe layers[1].vote = LayerVoteType::ExplicitDefault; layers[1].name = "90Hz ExplicitDefault"; - EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId()); + EXPECT_EQ(kModeId60, selector.getBestRefreshRate(layers)->getId()); } -TEST_F(RefreshRateConfigsTest, nonSeamlessVotePrefersSeamlessSwitches) { - TestableRefreshRateConfigs configs(kModes_30_60, kModeId60); +TEST_F(RefreshRateSelectorTest, nonSeamlessVotePrefersSeamlessSwitches) { + TestableRefreshRateSelector selector(kModes_30_60, kModeId60); // Allow group switching. - RefreshRateConfigs::DisplayManagerPolicy policy; - policy.defaultMode = configs.getCurrentPolicy().defaultMode; + RefreshRateSelector::DisplayManagerPolicy policy; + policy.defaultMode = selector.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); + EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy)); std::vector layers = {{.weight = 1.f}}; auto& layer = layers[0]; @@ -1775,20 +1776,20 @@ TEST_F(RefreshRateConfigsTest, nonSeamlessVotePrefersSeamlessSwitches) { layer.name = "60Hz ExplicitExactOrMultiple"; layer.focused = true; - EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId()); + EXPECT_EQ(kModeId60, selector.getBestRefreshRate(layers)->getId()); - configs.setActiveModeId(kModeId120); - EXPECT_EQ(kModeId120, configs.getBestRefreshRate(layers)->getId()); + selector.setActiveModeId(kModeId120); + EXPECT_EQ(kModeId120, selector.getBestRefreshRate(layers)->getId()); } -TEST_F(RefreshRateConfigsTest, nonSeamlessExactAndSeamlessMultipleLayers) { - TestableRefreshRateConfigs configs(kModes_25_30_50_60, kModeId60); +TEST_F(RefreshRateSelectorTest, nonSeamlessExactAndSeamlessMultipleLayers) { + TestableRefreshRateSelector selector(kModes_25_30_50_60, kModeId60); // Allow group switching. - RefreshRateConfigs::DisplayManagerPolicy policy; - policy.defaultMode = configs.getCurrentPolicy().defaultMode; + RefreshRateSelector::DisplayManagerPolicy policy; + policy.defaultMode = selector.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); + EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy)); std::vector layers = {{.name = "60Hz ExplicitDefault", .vote = LayerVoteType::ExplicitDefault, @@ -1803,33 +1804,33 @@ TEST_F(RefreshRateConfigsTest, nonSeamlessExactAndSeamlessMultipleLayers) { .weight = 1.f, .focused = true}}; - EXPECT_EQ(kModeId50, configs.getBestRefreshRate(layers)->getId()); + EXPECT_EQ(kModeId50, selector.getBestRefreshRate(layers)->getId()); auto& seamedLayer = layers[0]; seamedLayer.desiredRefreshRate = 30_Hz; seamedLayer.name = "30Hz ExplicitDefault"; - configs.setActiveModeId(kModeId30); + selector.setActiveModeId(kModeId30); - EXPECT_EQ(kModeId25, configs.getBestRefreshRate(layers)->getId()); + EXPECT_EQ(kModeId25, selector.getBestRefreshRate(layers)->getId()); } -TEST_F(RefreshRateConfigsTest, minLayersDontTrigerSeamedSwitch) { - TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId90); +TEST_F(RefreshRateSelectorTest, minLayersDontTrigerSeamedSwitch) { + TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId90); // Allow group switching. - RefreshRateConfigs::DisplayManagerPolicy policy; - policy.defaultMode = configs.getCurrentPolicy().defaultMode; + RefreshRateSelector::DisplayManagerPolicy policy; + policy.defaultMode = selector.getCurrentPolicy().defaultMode; policy.allowGroupSwitching = true; - EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy)); + EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy)); std::vector layers = { {.name = "Min", .vote = LayerVoteType::Min, .weight = 1.f, .focused = true}}; - EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId()); + EXPECT_EQ(kModeId90, selector.getBestRefreshRate(layers)->getId()); } -TEST_F(RefreshRateConfigsTest, primaryVsAppRequestPolicy) { - TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60); +TEST_F(RefreshRateSelectorTest, primaryVsAppRequestPolicy) { + TestableRefreshRateSelector selector(kModes_30_60_90, kModeId60); std::vector layers = {{.weight = 1.f}}; layers[0].name = "Test layer"; @@ -1839,19 +1840,19 @@ TEST_F(RefreshRateConfigsTest, primaryVsAppRequestPolicy) { bool focused = true; }; - // Return the config ID from calling getBestRefreshRate() for a single layer with the - // given voteType and fps. - auto getFrameRate = [&](LayerVoteType voteType, Fps fps, Args args = {}) -> DisplayModeId { + // Returns the mode selected by getBestRefreshRate for a single layer with the given arguments. + const auto getFrameRate = [&](LayerVoteType voteType, Fps fps, + Args args = {}) -> DisplayModeId { layers[0].vote = voteType; layers[0].desiredRefreshRate = fps; layers[0].focused = args.focused; - return configs.getBestRefreshRate(layers, {.touch = args.touch})->getId(); + return selector.getBestRefreshRate(layers, {.touch = args.touch})->getId(); }; EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 60_Hz}, {30_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 60_Hz}, {30_Hz, 90_Hz}})); - EXPECT_EQ(kModeId60, configs.getBestRefreshRate()->getId()); + EXPECT_EQ(kModeId60, selector.getBestRefreshRate()->getId()); EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz)); EXPECT_EQ(kModeId30, getFrameRate(LayerVoteType::Min, 90_Hz)); EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz)); @@ -1859,7 +1860,7 @@ TEST_F(RefreshRateConfigsTest, primaryVsAppRequestPolicy) { EXPECT_EQ(kModeId90, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz)); EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz)); - // Unfocused layers are not allowed to override primary config. + // Unfocused layers are not allowed to override primary range. EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz, {.focused = false})); EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz, {.focused = false})); @@ -1874,7 +1875,7 @@ TEST_F(RefreshRateConfigsTest, primaryVsAppRequestPolicy) { getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz, {.touch = true})); EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 60_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 60_Hz}})); EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz)); EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Min, 90_Hz)); @@ -1884,8 +1885,8 @@ TEST_F(RefreshRateConfigsTest, primaryVsAppRequestPolicy) { EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz)); } -TEST_F(RefreshRateConfigsTest, idle) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); +TEST_F(RefreshRateSelectorTest, idle) { + TestableRefreshRateSelector selector(kModes_60_90, kModeId60); std::vector layers = {{.weight = 1.f}}; layers[0].name = "Test layer"; @@ -1895,7 +1896,7 @@ TEST_F(RefreshRateConfigsTest, idle) { layers[0].desiredRefreshRate = 90_Hz; const auto [ranking, signals] = - configs.getRankedRefreshRates(layers, {.touch = touchActive, .idle = true}); + selector.getRankedRefreshRates(layers, {.touch = touchActive, .idle = true}); // Refresh rate will be chosen by either touch state or idle state. EXPECT_EQ(!touchActive, signals.idle); @@ -1903,7 +1904,7 @@ TEST_F(RefreshRateConfigsTest, idle) { }; EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}, {60_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}, {60_Hz, 90_Hz}})); // Idle should be lower priority than touch boost. { @@ -1918,10 +1919,10 @@ TEST_F(RefreshRateConfigsTest, idle) { } // With no layers, idle should still be lower priority than touch boost. - EXPECT_EQ(kModeId90, configs.getBestRefreshRate({}, {.touch = true, .idle = true})->getId()); + EXPECT_EQ(kModeId90, selector.getBestRefreshRate({}, {.touch = true, .idle = true})->getId()); // Idle should be higher precedence than other layer frame rate considerations. - configs.setActiveModeId(kModeId90); + selector.setActiveModeId(kModeId90); { constexpr bool kTouchActive = false; @@ -1934,15 +1935,15 @@ TEST_F(RefreshRateConfigsTest, idle) { getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, kTouchActive)); } - // Idle should be applied rather than the current config when there are no layers. - EXPECT_EQ(kModeId60, configs.getBestRefreshRate({}, {.idle = true})->getId()); + // Idle should be applied rather than the active mode when there are no layers. + EXPECT_EQ(kModeId60, selector.getBestRefreshRate({}, {.idle = true})->getId()); } -TEST_F(RefreshRateConfigsTest, findClosestKnownFrameRate) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); +TEST_F(RefreshRateSelectorTest, findClosestKnownFrameRate) { + TestableRefreshRateSelector selector(kModes_60_90, kModeId60); for (float fps = 1.0f; fps <= 120.0f; fps += 0.1f) { - const auto knownFrameRate = configs.findClosestKnownFrameRate(Fps::fromValue(fps)); + const auto knownFrameRate = selector.findClosestKnownFrameRate(Fps::fromValue(fps)); const Fps expectedFrameRate = [fps] { if (fps < 26.91f) return 24_Hz; if (fps < 37.51f) return 30_Hz; @@ -1956,8 +1957,8 @@ TEST_F(RefreshRateConfigsTest, findClosestKnownFrameRate) { } } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_KnownFrameRate) { - TestableRefreshRateConfigs configs(kModes_60_90, kModeId60); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_KnownFrameRate) { + TestableRefreshRateSelector selector(kModes_60_90, kModeId60); struct Expectation { Fps fps; @@ -1970,7 +1971,7 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_KnownFrameRate) { }; // Make sure the test tests all the known frame rate - const auto& knownFrameRates = configs.knownFrameRates(); + const auto& knownFrameRates = selector.knownFrameRates(); const bool equal = std::equal(knownFrameRates.begin(), knownFrameRates.end(), knownFrameRatesExpectations.begin(), [](Fps fps, const Expectation& expected) { @@ -1984,12 +1985,12 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_KnownFrameRate) { for (const auto& [fps, mode] : knownFrameRatesExpectations) { layer.desiredRefreshRate = fps; - EXPECT_EQ(mode, configs.getBestRefreshRate(layers)); + EXPECT_EQ(mode, selector.getBestRefreshRate(layers)); } } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExact) { - TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExact) { + TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60); std::vector layers = {{.weight = 1.f}, {.weight = 0.5f}}; auto& explicitExactLayer = layers[0]; @@ -2003,26 +2004,26 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExact) { explicitExactLayer.name = "ExplicitExact"; explicitExactLayer.desiredRefreshRate = 30_Hz; - EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers)); - EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers, {.touch = true})); + EXPECT_EQ(kMode30, selector.getBestRefreshRate(layers)); + EXPECT_EQ(kMode30, selector.getBestRefreshRate(layers, {.touch = true})); explicitExactOrMultipleLayer.desiredRefreshRate = 120_Hz; explicitExactLayer.desiredRefreshRate = 60_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); explicitExactLayer.desiredRefreshRate = 72_Hz; - EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers)); explicitExactLayer.desiredRefreshRate = 90_Hz; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); explicitExactLayer.desiredRefreshRate = 120_Hz; - EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers)); } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExactEnableFrameRateOverride) { - TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60, - {.enableFrameRateOverride = true}); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExactEnableFrameRateOverride) { + TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60, + {.enableFrameRateOverride = true}); std::vector layers = {{.weight = 1.f}, {.weight = 0.5f}}; auto& explicitExactLayer = layers[0]; @@ -2036,58 +2037,59 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExactEnableFrameRateOv explicitExactLayer.name = "ExplicitExact"; explicitExactLayer.desiredRefreshRate = 30_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); - EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers, {.touch = true})); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); + EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers, {.touch = true})); explicitExactOrMultipleLayer.desiredRefreshRate = 120_Hz; explicitExactLayer.desiredRefreshRate = 60_Hz; - EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers)); explicitExactLayer.desiredRefreshRate = 72_Hz; - EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers)); explicitExactLayer.desiredRefreshRate = 90_Hz; - EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers)); explicitExactLayer.desiredRefreshRate = 120_Hz; - EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers)); } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ReadsCache) { - TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ReadsCache) { + TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60); - using GlobalSignals = RefreshRateConfigs::GlobalSignals; + using GlobalSignals = RefreshRateSelector::GlobalSignals; const auto args = std::make_pair(std::vector{}, GlobalSignals{.touch = true, .idle = true}); - const RefreshRateConfigs::RankedRefreshRates result = {{RefreshRateConfigs::ScoredRefreshRate{ - kMode90}}, - {.touch = true}}; + const RefreshRateSelector::RankedRefreshRates result = {{RefreshRateSelector::ScoredRefreshRate{ + kMode90}}, + {.touch = true}}; - configs.mutableGetRankedRefreshRatesCache() = {args, result}; + selector.mutableGetRankedRefreshRatesCache() = {args, result}; - EXPECT_EQ(result, configs.getRankedRefreshRates(args.first, args.second)); + EXPECT_EQ(result, selector.getRankedRefreshRates(args.first, args.second)); } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_WritesCache) { - TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_WritesCache) { + TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60); - EXPECT_FALSE(configs.mutableGetRankedRefreshRatesCache()); + EXPECT_FALSE(selector.mutableGetRankedRefreshRatesCache()); std::vector layers = {{.weight = 1.f}, {.weight = 0.5f}}; - RefreshRateConfigs::GlobalSignals globalSignals{.touch = true, .idle = true}; + RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true}; - const auto result = configs.getRankedRefreshRates(layers, globalSignals); + const auto result = selector.getRankedRefreshRates(layers, globalSignals); - const auto& cache = configs.mutableGetRankedRefreshRatesCache(); + const auto& cache = selector.mutableGetRankedRefreshRatesCache(); ASSERT_TRUE(cache); EXPECT_EQ(cache->arguments, std::make_pair(layers, globalSignals)); EXPECT_EQ(cache->result, result); } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExactTouchBoost) { - TestableRefreshRateConfigs configs(kModes_60_120, kModeId60, {.enableFrameRateOverride = true}); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExactTouchBoost) { + TestableRefreshRateSelector selector(kModes_60_120, kModeId60, + {.enableFrameRateOverride = true}); std::vector layers = {{.weight = 1.f}, {.weight = 0.5f}}; auto& explicitExactLayer = layers[0]; @@ -2101,18 +2103,18 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExactTouchBoost) { explicitExactLayer.name = "ExplicitExact"; explicitExactLayer.desiredRefreshRate = 30_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); - EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers, {.touch = true})); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); + EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers, {.touch = true})); explicitExactOrMultipleLayer.vote = LayerVoteType::NoVote; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers, {.touch = true})); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers, {.touch = true})); } -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_FractionalRefreshRates_ExactAndDefault) { - TestableRefreshRateConfigs configs(kModes_24_25_30_50_60_Frac, kModeId60, - {.enableFrameRateOverride = true}); +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_FractionalRefreshRates_ExactAndDefault) { + TestableRefreshRateSelector selector(kModes_24_25_30_50_60_Frac, kModeId60, + {.enableFrameRateOverride = true}); std::vector layers = {{.weight = 0.5f}, {.weight = 0.5f}}; auto& explicitDefaultLayer = layers[0]; @@ -2126,11 +2128,11 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_FractionalRefreshRates_ExactAn explicitDefaultLayer.name = "ExplicitDefault"; explicitDefaultLayer.desiredRefreshRate = 59.94_Hz; - EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers)); + EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers)); } // b/190578904 -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_withCloseRefreshRates) { +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_withCloseRefreshRates) { constexpr int kMinRefreshRate = 10; constexpr int kMaxRefreshRate = 240; @@ -2142,14 +2144,14 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_withCloseRefreshRates) { Fps::fromValue(static_cast(fps)))); } - const TestableRefreshRateConfigs configs(std::move(displayModes), - DisplayModeId(kMinRefreshRate)); + const TestableRefreshRateSelector selector(std::move(displayModes), + DisplayModeId(kMinRefreshRate)); std::vector layers = {{.weight = 1.f}}; const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) { layers[0].desiredRefreshRate = fps; layers[0].vote = vote; - EXPECT_EQ(fps.getIntValue(), configs.getBestRefreshRate(layers)->getFps().getIntValue()) + EXPECT_EQ(fps.getIntValue(), selector.getBestRefreshRate(layers)->getFps().getIntValue()) << "Failed for " << ftl::enum_string(vote); }; @@ -2163,15 +2165,15 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_withCloseRefreshRates) { } // b/190578904 -TEST_F(RefreshRateConfigsTest, getBestRefreshRate_conflictingVotes) { +TEST_F(RefreshRateSelectorTest, getBestRefreshRate_conflictingVotes) { constexpr DisplayModeId kActiveModeId{0}; DisplayModes displayModes = makeModes(createDisplayMode(kActiveModeId, 43_Hz), createDisplayMode(DisplayModeId(1), 53_Hz), createDisplayMode(DisplayModeId(2), 55_Hz), createDisplayMode(DisplayModeId(3), 60_Hz)); - const RefreshRateConfigs::GlobalSignals globalSignals = {.touch = false, .idle = false}; - const TestableRefreshRateConfigs configs(std::move(displayModes), kActiveModeId); + const RefreshRateSelector::GlobalSignals globalSignals = {.touch = false, .idle = false}; + const TestableRefreshRateSelector selector(std::move(displayModes), kActiveModeId); const std::vector layers = { { @@ -2188,133 +2190,125 @@ TEST_F(RefreshRateConfigsTest, getBestRefreshRate_conflictingVotes) { }, }; - EXPECT_EQ(53_Hz, configs.getBestRefreshRate(layers, globalSignals)->getFps()); + EXPECT_EQ(53_Hz, selector.getBestRefreshRate(layers, globalSignals)->getFps()); } -TEST_F(RefreshRateConfigsTest, modeComparison) { +TEST_F(RefreshRateSelectorTest, modeComparison) { EXPECT_LT(kMode60->getFps(), kMode90->getFps()); EXPECT_GE(kMode60->getFps(), kMode60->getFps()); EXPECT_GE(kMode90->getFps(), kMode90->getFps()); } -TEST_F(RefreshRateConfigsTest, testKernelIdleTimerAction) { - using KernelIdleTimerAction = RefreshRateConfigs::KernelIdleTimerAction; +TEST_F(RefreshRateSelectorTest, testKernelIdleTimerAction) { + using KernelIdleTimerAction = RefreshRateSelector::KernelIdleTimerAction; - TestableRefreshRateConfigs configs(kModes_60_90, kModeId90); + TestableRefreshRateSelector selector(kModes_60_90, kModeId90); - // setPolicy(60, 90), current 90Hz => TurnOn. - EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction()); + EXPECT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction()); - // setPolicy(60, 90), current 60Hz => TurnOn. EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}})); - EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction()); + selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}})); + EXPECT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction()); - // setPolicy(60, 60), current 60Hz => TurnOff EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}})); - EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction()); + selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}})); + EXPECT_EQ(KernelIdleTimerAction::TurnOff, selector.getIdleTimerAction()); - // setPolicy(90, 90), current 90Hz => TurnOff. EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}})); - EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction()); + selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}})); + EXPECT_EQ(KernelIdleTimerAction::TurnOff, selector.getIdleTimerAction()); } -TEST_F(RefreshRateConfigsTest, testKernelIdleTimerActionFor120Hz) { - using KernelIdleTimerAction = RefreshRateConfigs::KernelIdleTimerAction; +TEST_F(RefreshRateSelectorTest, testKernelIdleTimerActionFor120Hz) { + using KernelIdleTimerAction = RefreshRateSelector::KernelIdleTimerAction; - TestableRefreshRateConfigs configs(kModes_60_120, kModeId120); + TestableRefreshRateSelector selector(kModes_60_120, kModeId120); - // setPolicy(0, 60), current 60Hz => TurnOn. EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId60, {0_Hz, 60_Hz}})); - EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction()); + selector.setDisplayManagerPolicy({kModeId60, {0_Hz, 60_Hz}})); + EXPECT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction()); - // setPolicy(60, 60), current 60Hz => TurnOff. EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}})); - EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction()); + selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}})); + EXPECT_EQ(KernelIdleTimerAction::TurnOff, selector.getIdleTimerAction()); - // setPolicy(60, 120), current 60Hz => TurnOn. EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 120_Hz}})); - EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction()); + selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 120_Hz}})); + EXPECT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction()); - // setPolicy(120, 120), current 120Hz => TurnOff. EXPECT_EQ(SetPolicyResult::Changed, - configs.setDisplayManagerPolicy({kModeId120, {120_Hz, 120_Hz}})); - EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction()); + selector.setDisplayManagerPolicy({kModeId120, {120_Hz, 120_Hz}})); + EXPECT_EQ(KernelIdleTimerAction::TurnOff, selector.getIdleTimerAction()); } -TEST_F(RefreshRateConfigsTest, getFrameRateDivisor) { - TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId30); +TEST_F(RefreshRateSelectorTest, getFrameRateDivisor) { + TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId30); const auto frameRate = 30_Hz; - Fps displayRefreshRate = configs.getActiveMode().getFps(); - EXPECT_EQ(1, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate)); + Fps displayRefreshRate = selector.getActiveMode().getFps(); + EXPECT_EQ(1, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate)); - configs.setActiveModeId(kModeId60); - displayRefreshRate = configs.getActiveMode().getFps(); - EXPECT_EQ(2, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate)); + selector.setActiveModeId(kModeId60); + displayRefreshRate = selector.getActiveMode().getFps(); + EXPECT_EQ(2, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate)); - configs.setActiveModeId(kModeId72); - displayRefreshRate = configs.getActiveMode().getFps(); - EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate)); + selector.setActiveModeId(kModeId72); + displayRefreshRate = selector.getActiveMode().getFps(); + EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate)); - configs.setActiveModeId(kModeId90); - displayRefreshRate = configs.getActiveMode().getFps(); - EXPECT_EQ(3, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate)); + selector.setActiveModeId(kModeId90); + displayRefreshRate = selector.getActiveMode().getFps(); + EXPECT_EQ(3, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate)); - configs.setActiveModeId(kModeId120); - displayRefreshRate = configs.getActiveMode().getFps(); - EXPECT_EQ(4, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate)); + selector.setActiveModeId(kModeId120); + displayRefreshRate = selector.getActiveMode().getFps(); + EXPECT_EQ(4, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate)); - configs.setActiveModeId(kModeId90); - displayRefreshRate = configs.getActiveMode().getFps(); - EXPECT_EQ(4, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, 22.5_Hz)); + selector.setActiveModeId(kModeId90); + displayRefreshRate = selector.getActiveMode().getFps(); + EXPECT_EQ(4, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, 22.5_Hz)); - EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivisor(24_Hz, 25_Hz)); - EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivisor(24_Hz, 23.976_Hz)); - EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivisor(30_Hz, 29.97_Hz)); - EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivisor(60_Hz, 59.94_Hz)); + EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(24_Hz, 25_Hz)); + EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(24_Hz, 23.976_Hz)); + EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(30_Hz, 29.97_Hz)); + EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(60_Hz, 59.94_Hz)); } -TEST_F(RefreshRateConfigsTest, isFractionalPairOrMultiple) { - EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(23.976_Hz, 24_Hz)); - EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(24_Hz, 23.976_Hz)); +TEST_F(RefreshRateSelectorTest, isFractionalPairOrMultiple) { + EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(23.976_Hz, 24_Hz)); + EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(24_Hz, 23.976_Hz)); - EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(29.97_Hz, 30_Hz)); - EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(30_Hz, 29.97_Hz)); + EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(29.97_Hz, 30_Hz)); + EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(30_Hz, 29.97_Hz)); - EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(59.94_Hz, 60_Hz)); - EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(60_Hz, 59.94_Hz)); + EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(59.94_Hz, 60_Hz)); + EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(60_Hz, 59.94_Hz)); - EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(29.97_Hz, 60_Hz)); - EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(60_Hz, 29.97_Hz)); + EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(29.97_Hz, 60_Hz)); + EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(60_Hz, 29.97_Hz)); - EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(59.94_Hz, 30_Hz)); - EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(30_Hz, 59.94_Hz)); + EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(59.94_Hz, 30_Hz)); + EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(30_Hz, 59.94_Hz)); const auto refreshRates = {23.976_Hz, 24_Hz, 25_Hz, 29.97_Hz, 30_Hz, 50_Hz, 59.94_Hz, 60_Hz}; for (auto refreshRate : refreshRates) { - EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(refreshRate, refreshRate)); + EXPECT_FALSE(RefreshRateSelector::isFractionalPairOrMultiple(refreshRate, refreshRate)); } - EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(24_Hz, 25_Hz)); - EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(23.978_Hz, 25_Hz)); - EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(29.97_Hz, 59.94_Hz)); + EXPECT_FALSE(RefreshRateSelector::isFractionalPairOrMultiple(24_Hz, 25_Hz)); + EXPECT_FALSE(RefreshRateSelector::isFractionalPairOrMultiple(23.978_Hz, 25_Hz)); + EXPECT_FALSE(RefreshRateSelector::isFractionalPairOrMultiple(29.97_Hz, 59.94_Hz)); } -TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_noLayers) { - RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120); +TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_noLayers) { + RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120); - EXPECT_TRUE(configs.getFrameRateOverrides({}, 120_Hz, {}).empty()); + EXPECT_TRUE(selector.getFrameRateOverrides({}, 120_Hz, {}).empty()); } -TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_60on120) { - RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120, - {.enableFrameRateOverride = true}); +TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_60on120) { + RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120, + {.enableFrameRateOverride = true}); std::vector layers = {{.weight = 1.f}}; layers[0].name = "Test layer"; @@ -2322,37 +2316,37 @@ TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_60on120) { layers[0].desiredRefreshRate = 60_Hz; layers[0].vote = LayerVoteType::ExplicitDefault; - auto frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {}); + auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); EXPECT_EQ(1u, frameRateOverrides.size()); ASSERT_EQ(1u, frameRateOverrides.count(1234)); EXPECT_EQ(60_Hz, frameRateOverrides.at(1234)); layers[0].vote = LayerVoteType::ExplicitExactOrMultiple; - frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {}); + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); EXPECT_EQ(1u, frameRateOverrides.size()); ASSERT_EQ(1u, frameRateOverrides.count(1234)); EXPECT_EQ(60_Hz, frameRateOverrides.at(1234)); layers[0].vote = LayerVoteType::NoVote; - frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {}); + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); EXPECT_TRUE(frameRateOverrides.empty()); layers[0].vote = LayerVoteType::Min; - frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {}); + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); EXPECT_TRUE(frameRateOverrides.empty()); layers[0].vote = LayerVoteType::Max; - frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {}); + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); EXPECT_TRUE(frameRateOverrides.empty()); layers[0].vote = LayerVoteType::Heuristic; - frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {}); + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); EXPECT_TRUE(frameRateOverrides.empty()); } -TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_twoUids) { - RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120, - {.enableFrameRateOverride = true}); +TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_twoUids) { + RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120, + {.enableFrameRateOverride = true}); std::vector layers = {{.ownerUid = 1234, .weight = 1.f}, {.ownerUid = 5678, .weight = 1.f}}; @@ -2364,7 +2358,7 @@ TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_twoUids) { layers[1].name = "Test layer 5678"; layers[1].desiredRefreshRate = 30_Hz; layers[1].vote = LayerVoteType::ExplicitDefault; - auto frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {}); + auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); EXPECT_EQ(2u, frameRateOverrides.size()); ASSERT_EQ(1u, frameRateOverrides.count(1234)); @@ -2373,53 +2367,53 @@ TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_twoUids) { EXPECT_EQ(30_Hz, frameRateOverrides.at(5678)); layers[1].vote = LayerVoteType::Heuristic; - frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {}); + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); EXPECT_EQ(1u, frameRateOverrides.size()); ASSERT_EQ(1u, frameRateOverrides.count(1234)); EXPECT_EQ(60_Hz, frameRateOverrides.at(1234)); layers[1].ownerUid = 1234; - frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {}); + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); EXPECT_TRUE(frameRateOverrides.empty()); } -TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_touch) { - RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120, - {.enableFrameRateOverride = true}); +TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_touch) { + RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120, + {.enableFrameRateOverride = true}); std::vector layers = {{.ownerUid = 1234, .weight = 1.f}}; layers[0].name = "Test layer"; layers[0].desiredRefreshRate = 60_Hz; layers[0].vote = LayerVoteType::ExplicitDefault; - auto frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {}); + auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); EXPECT_EQ(1u, frameRateOverrides.size()); ASSERT_EQ(1u, frameRateOverrides.count(1234)); EXPECT_EQ(60_Hz, frameRateOverrides.at(1234)); - frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {.touch = true}); + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true}); EXPECT_EQ(1u, frameRateOverrides.size()); ASSERT_EQ(1u, frameRateOverrides.count(1234)); EXPECT_EQ(60_Hz, frameRateOverrides.at(1234)); layers[0].vote = LayerVoteType::ExplicitExact; - frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {}); + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); EXPECT_EQ(1u, frameRateOverrides.size()); ASSERT_EQ(1u, frameRateOverrides.count(1234)); EXPECT_EQ(60_Hz, frameRateOverrides.at(1234)); - frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {.touch = true}); + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true}); EXPECT_EQ(1u, frameRateOverrides.size()); ASSERT_EQ(1u, frameRateOverrides.count(1234)); EXPECT_EQ(60_Hz, frameRateOverrides.at(1234)); layers[0].vote = LayerVoteType::ExplicitExactOrMultiple; - frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {}); + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); EXPECT_EQ(1u, frameRateOverrides.size()); ASSERT_EQ(1u, frameRateOverrides.count(1234)); EXPECT_EQ(60_Hz, frameRateOverrides.at(1234)); - frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {.touch = true}); + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true}); EXPECT_TRUE(frameRateOverrides.empty()); } diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 147433b422..066083fffa 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -22,7 +22,7 @@ #include "FakeDisplayInjector.h" #include "Scheduler/EventThread.h" -#include "Scheduler/RefreshRateConfigs.h" +#include "Scheduler/RefreshRateSelector.h" #include "TestableScheduler.h" #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockDisplayMode.h" @@ -78,12 +78,12 @@ protected: createDisplayMode(kDisplayId3, DisplayModeId(0), 60_Hz); static inline const DisplayModes kDisplay3Modes = makeModes(kDisplay3Mode60); - std::shared_ptr mConfigs = - std::make_shared(makeModes(kDisplay1Mode60), - kDisplay1Mode60->getId()); + std::shared_ptr mSelector = + std::make_shared(makeModes(kDisplay1Mode60), + kDisplay1Mode60->getId()); mock::SchedulerCallback mSchedulerCallback; - TestableScheduler* mScheduler = new TestableScheduler{mConfigs, mSchedulerCallback}; + TestableScheduler* mScheduler = new TestableScheduler{mSelector, mSchedulerCallback}; ConnectionHandle mConnectionHandle; MockEventThread* mEventThread; @@ -196,8 +196,8 @@ TEST_F(SchedulerTest, updateDisplayModes) { sp layer = sp::make(mFlinger.flinger()); ASSERT_EQ(1u, mScheduler->layerHistorySize()); - mScheduler->setRefreshRateConfigs( - std::make_shared(kDisplay1Modes, kDisplay1Mode60->getId())); + mScheduler->setRefreshRateSelector( + std::make_shared(kDisplay1Modes, kDisplay1Mode60->getId())); ASSERT_EQ(0u, mScheduler->getNumActiveLayers()); mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer); @@ -247,7 +247,7 @@ TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) { {.displayId = kDisplayId1}); mScheduler->registerDisplay(display); - mScheduler->setRefreshRateConfigs(display->holdRefreshRateConfigs()); + mScheduler->setRefreshRateSelector(display->holdRefreshRateSelector()); const sp layer = sp::make(mFlinger.flinger()); EXPECT_CALL(*layer, isVisible()).WillOnce(Return(true)); @@ -277,8 +277,8 @@ TEST_F(SchedulerTest, chooseDisplayModesSingleDisplay) { mScheduler->registerDisplay(display); - std::vector layers = - std::vector({{.weight = 1.f}, {.weight = 1.f}}); + std::vector layers = + std::vector({{.weight = 1.f}, {.weight = 1.f}}); mScheduler->setContentRequirements(layers); GlobalSignals globalSignals = {.idle = true}; mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); @@ -343,8 +343,8 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { globalSignals)(kDisplayId2, kDisplay2Mode60, globalSignals); - std::vector layers = {{.weight = 1.f}, - {.weight = 1.f}}; + std::vector layers = {{.weight = 1.f}, + {.weight = 1.f}}; mScheduler->setContentRequirements(layers); mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp index dfcfd912f9..6adcd5259d 100644 --- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp +++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp @@ -383,8 +383,8 @@ TEST_P(SetFrameRateTest, SetOnParentActivatesTree) { history.record(parent.get(), 0, 0, LayerHistory::LayerUpdateType::Buffer); history.record(child.get(), 0, 0, LayerHistory::LayerUpdateType::Buffer); - const auto configs = mFlinger.mutableScheduler().refreshRateConfigs(); - const auto summary = history.summarize(*configs, 0); + const auto selectorPtr = mFlinger.mutableScheduler().refreshRateSelector(); + const auto summary = history.summarize(*selectorPtr, 0); ASSERT_EQ(2u, summary.size()); EXPECT_EQ(FRAME_RATE_VOTE1.rate, summary[0].desiredRefreshRate); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp index 6b7e3533a5..4c25463e6e 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp @@ -42,15 +42,15 @@ public: PrimaryDisplayVariant::setupHwcGetActiveConfigCallExpectations(this); DisplayModes modes = makeModes(kMode60, kMode90, kMode120, kMode90_4K); - auto configs = std::make_shared(modes, kModeId60); + auto selectorPtr = std::make_shared(modes, kModeId60); - setupScheduler(configs); + setupScheduler(selectorPtr); mFlinger.onComposerHalHotplug(PrimaryDisplayVariant::HWC_DISPLAY_ID, Connection::CONNECTED); mFlinger.configureAndCommit(); mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this) - .setDisplayModes(std::move(modes), kModeId60, std::move(configs)) + .setDisplayModes(std::move(modes), kModeId60, std::move(selectorPtr)) .inject(); // isVsyncPeriodSwitchSupported should return true, otherwise the SF's HWC proxy @@ -60,7 +60,7 @@ public: } protected: - void setupScheduler(std::shared_ptr); + void setupScheduler(std::shared_ptr); sp mDisplay; mock::EventThread* mAppEventThread; @@ -80,7 +80,7 @@ protected: }; void DisplayModeSwitchingTest::setupScheduler( - std::shared_ptr configs) { + std::shared_ptr selectorPtr) { auto eventThread = std::make_unique(); mAppEventThread = eventThread.get(); auto sfEventThread = std::make_unique(); @@ -108,7 +108,7 @@ void DisplayModeSwitchingTest::setupScheduler( mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), std::move(eventThread), std::move(sfEventThread), TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp, - std::move(configs)); + std::move(selectorPtr)); } TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRequired) { diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index 26b2b673a2..2814d38b47 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -32,17 +32,19 @@ namespace android::scheduler { class TestableScheduler : public Scheduler, private ICompositor { public: - TestableScheduler(std::shared_ptr configs, ISchedulerCallback& callback) + TestableScheduler(std::shared_ptr selectorPtr, + ISchedulerCallback& callback) : TestableScheduler(std::make_unique(), - std::make_unique(), std::move(configs), + std::make_unique(), std::move(selectorPtr), callback) {} TestableScheduler(std::unique_ptr controller, std::unique_ptr tracker, - std::shared_ptr configs, ISchedulerCallback& callback) + std::shared_ptr selectorPtr, + ISchedulerCallback& callback) : Scheduler(*this, callback, Feature::kContentDetection) { mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller))); - setRefreshRateConfigs(std::move(configs)); + setRefreshRateSelector(std::move(selectorPtr)); ON_CALL(*this, postMessage).WillByDefault([](sp&& handler) { // Execute task to prevent broken promise exception on destruction. @@ -74,7 +76,7 @@ public: return mLayerHistory.mActiveLayerInfos.size() + mLayerHistory.mInactiveLayerInfos.size(); } - auto refreshRateConfigs() { return holdRefreshRateConfigs(); } + auto refreshRateSelector() { return holdRefreshRateSelector(); } size_t getNumActiveLayers() NO_THREAD_SAFETY_ANALYSIS { return mLayerHistory.mActiveLayerInfos.size(); @@ -102,7 +104,7 @@ public: mPolicy.idleTimer = globalSignals.idle ? TimerState::Expired : TimerState::Reset; } - void setContentRequirements(std::vector layers) { + void setContentRequirements(std::vector layers) { std::lock_guard lock(mPolicyLock); mPolicy.contentRequirements = std::move(layers); } diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 89812aad7d..4fd44781e0 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -37,7 +37,7 @@ #include "Layer.h" #include "NativeWindowSurface.h" #include "Scheduler/MessageQueue.h" -#include "Scheduler/RefreshRateConfigs.h" +#include "Scheduler/RefreshRateSelector.h" #include "StartPropertySetThread.h" #include "SurfaceFlinger.h" #include "SurfaceFlingerDefaultFactory.h" @@ -191,10 +191,10 @@ public: static constexpr struct TwoDisplayModes { } kTwoDisplayModes; - using RefreshRateConfigsPtr = std::shared_ptr; + using RefreshRateSelectorPtr = std::shared_ptr; using DisplayModesVariant = - std::variant; + std::variant; void setupScheduler(std::unique_ptr vsyncController, std::unique_ptr vsyncTracker, @@ -203,9 +203,9 @@ public: SchedulerCallbackImpl callbackImpl = SchedulerCallbackImpl::kNoOp, DisplayModesVariant modesVariant = kOneDisplayMode, bool useNiceMock = false) { - RefreshRateConfigsPtr configs; - if (std::holds_alternative(modesVariant)) { - configs = std::move(std::get(modesVariant)); + RefreshRateSelectorPtr selectorPtr; + if (std::holds_alternative(modesVariant)) { + selectorPtr = std::move(std::get(modesVariant)); } else { constexpr DisplayModeId kModeId60{0}; DisplayModes modes = makeModes(mock::createDisplayMode(kModeId60, 60_Hz)); @@ -215,10 +215,10 @@ public: modes.try_emplace(kModeId90, mock::createDisplayMode(kModeId90, 90_Hz)); } - configs = std::make_shared(modes, kModeId60); + selectorPtr = std::make_shared(modes, kModeId60); } - const auto fps = FTL_FAKE_GUARD(kMainThreadContext, configs->getActiveMode().getFps()); + const auto fps = FTL_FAKE_GUARD(kMainThreadContext, selectorPtr->getActiveMode().getFps()); mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps); mFlinger->mVsyncModulator = sp::make( mFlinger->mVsyncConfiguration->getCurrentConfigs()); @@ -236,12 +236,12 @@ public: mScheduler = new testing::NiceMock(std::move(vsyncController), std::move(vsyncTracker), - std::move(configs), + std::move(selectorPtr), callback); } else { mScheduler = new scheduler::TestableScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(configs), callback); + std::move(selectorPtr), callback); } mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread)); @@ -757,16 +757,17 @@ public: return mFlinger.mutableDisplays().get(mDisplayToken)->get(); } - // If `configs` is nullptr, the injector creates RefreshRateConfigs from the `modes`. - // Otherwise, it uses `configs`, which the caller must create using the same `modes`. + // If `selectorPtr` is nullptr, the injector creates RefreshRateSelector from the `modes`. + // Otherwise, it uses `selectorPtr`, which the caller must create using the same `modes`. // - // TODO(b/182939859): Once `modes` can be retrieved from RefreshRateConfigs, remove - // the `configs` parameter in favor of an alternative setRefreshRateConfigs API. - auto& setDisplayModes(DisplayModes modes, DisplayModeId activeModeId, - std::shared_ptr configs = nullptr) { + // TODO(b/182939859): Once `modes` can be retrieved from RefreshRateSelector, remove + // the `selectorPtr` parameter in favor of an alternative setRefreshRateSelector API. + auto& setDisplayModes( + DisplayModes modes, DisplayModeId activeModeId, + std::shared_ptr selectorPtr = nullptr) { mDisplayModes = std::move(modes); mCreationArgs.activeModeId = activeModeId; - mCreationArgs.refreshRateConfigs = std::move(configs); + mCreationArgs.refreshRateSelector = std::move(selectorPtr); return *this; } @@ -813,7 +814,7 @@ public: auto& modes = mDisplayModes; auto& activeModeId = mCreationArgs.activeModeId; - if (displayId && !mCreationArgs.refreshRateConfigs) { + if (displayId && !mCreationArgs.refreshRateSelector) { if (const auto physicalId = PhysicalDisplayId::tryCast(*displayId)) { if (modes.empty()) { constexpr DisplayModeId kModeId{0}; @@ -833,8 +834,8 @@ public: activeModeId = kModeId; } - mCreationArgs.refreshRateConfigs = - std::make_shared(modes, activeModeId); + mCreationArgs.refreshRateSelector = + std::make_shared(modes, activeModeId); } } -- GitLab From b54ffb2a4b2504a39f333b683f83262a70206953 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 27 Oct 2022 18:03:34 +0000 Subject: [PATCH 0415/1310] InputReader: Get Bluetooth address from InputDeviceIdentifier The InputDevice class in Reader stores the InputDeviceIdentifier. When the policy wants to get the Bluetooth address of an InputDevice, fetch it from there. Bug: 243005009 Test: atest inputflinger_tests Change-Id: I5d881a11cb1bc318258faf1f498db1ca29ae8537 --- services/inputflinger/include/InputReaderBase.h | 3 +++ services/inputflinger/reader/InputReader.cpp | 10 ++++++++++ services/inputflinger/reader/include/InputDevice.h | 3 +++ services/inputflinger/reader/include/InputReader.h | 2 ++ services/inputflinger/tests/InputReader_test.cpp | 9 +++++++++ .../inputflinger/tests/fuzzers/InputReaderFuzzer.cpp | 5 +++++ 6 files changed, 32 insertions(+) diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index 8c586b5290..3b0f2ac5a2 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -142,6 +142,9 @@ public: virtual std::optional getLightColor(int32_t deviceId, int32_t lightId) = 0; /* Get light player ID */ virtual std::optional getLightPlayerId(int32_t deviceId, int32_t lightId) = 0; + + /* Get the Bluetooth address of an input device, if known. */ + virtual std::optional getBluetoothAddress(int32_t deviceId) const = 0; }; // --- InputReaderConfiguration --- diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 903d072c96..f8b1b3fa6e 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -875,6 +875,16 @@ std::optional InputReader::getLightPlayerId(int32_t deviceId, int32_t l return std::nullopt; } +std::optional InputReader::getBluetoothAddress(int32_t deviceId) const { + std::scoped_lock _l(mLock); + + InputDevice* device = findInputDeviceLocked(deviceId); + if (device) { + return device->getBluetoothAddress(); + } + return std::nullopt; +} + bool InputReader::isInputDeviceEnabled(int32_t deviceId) { std::scoped_lock _l(mLock); diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index afb1bed1f2..b9a2b4ce92 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -51,6 +51,9 @@ public: inline int32_t getGeneration() const { return mGeneration; } inline const std::string getName() const { return mIdentifier.name; } inline const std::string getDescriptor() { return mIdentifier.descriptor; } + inline std::optional getBluetoothAddress() const { + return mIdentifier.bluetoothAddress; + } inline ftl::Flags getClasses() const { return mClasses; } inline uint32_t getSources() const { return mSources; } inline bool hasEventHubDevices() const { return !mDevices.empty(); } diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h index de268cf66c..4f2503ad5c 100644 --- a/services/inputflinger/reader/include/InputReader.h +++ b/services/inputflinger/reader/include/InputReader.h @@ -113,6 +113,8 @@ public: std::optional getLightPlayerId(int32_t deviceId, int32_t lightId) override; + std::optional getBluetoothAddress(int32_t deviceId) const override; + protected: // These members are protected so they can be instrumented by test cases. virtual std::shared_ptr createDeviceLocked(int32_t deviceId, diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index e51379c38f..f333306f26 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -2782,6 +2782,7 @@ protected: static const int32_t DEVICE_CONTROLLER_NUMBER; static const ftl::Flags DEVICE_CLASSES; static const int32_t EVENTHUB_ID; + static const std::string DEVICE_BLUETOOTH_ADDRESS; std::shared_ptr mFakeEventHub; sp mFakePolicy; @@ -2798,6 +2799,7 @@ protected: InputDeviceIdentifier identifier; identifier.name = DEVICE_NAME; identifier.location = DEVICE_LOCATION; + identifier.bluetoothAddress = DEVICE_BLUETOOTH_ADDRESS; mDevice = std::make_shared(mReader->getContext(), DEVICE_ID, DEVICE_GENERATION, identifier); mReader->pushNextDevice(mDevice); @@ -2819,6 +2821,7 @@ const int32_t InputDeviceTest::DEVICE_CONTROLLER_NUMBER = 0; const ftl::Flags InputDeviceTest::DEVICE_CLASSES = InputDeviceClass::KEYBOARD | InputDeviceClass::TOUCH | InputDeviceClass::JOYSTICK; const int32_t InputDeviceTest::EVENTHUB_ID = 1; +const std::string InputDeviceTest::DEVICE_BLUETOOTH_ADDRESS = "11:AA:22:BB:33:CC"; TEST_F(InputDeviceTest, ImmutableProperties) { ASSERT_EQ(DEVICE_ID, mDevice->getId()); @@ -3085,6 +3088,12 @@ TEST_F(InputDeviceTest, DumpDoesNotCrash) { device.dump(dumpStr, eventHubDevStr); } +TEST_F(InputDeviceTest, GetBluetoothAddress) { + const auto& address = mReader->getBluetoothAddress(DEVICE_ID); + ASSERT_TRUE(address); + ASSERT_EQ(DEVICE_BLUETOOTH_ADDRESS, *address); +} + // --- InputMapperTest --- class InputMapperTest : public testing::Test { diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp index a9f5a3ac86..2eed9977be 100644 --- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp @@ -157,6 +157,10 @@ public: return reader->getKeyCodeForKeyLocation(deviceId, locationKeyCode); } + std::optional getBluetoothAddress(int32_t deviceId) const { + return reader->getBluetoothAddress(deviceId); + } + private: std::unique_ptr reader; }; @@ -273,6 +277,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { std::chrono::microseconds(fdp->ConsumeIntegral()), std::chrono::microseconds(fdp->ConsumeIntegral())); }, + [&]() -> void { reader->getBluetoothAddress(fdp->ConsumeIntegral()); }, })(); } -- GitLab From c4e0c65fba5875c72eb10f0b765c0d2152c04fbf Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 11 Oct 2022 02:46:04 +0000 Subject: [PATCH 0416/1310] KeyboardInputMapper: Remove unused "stem" key mapping There is code in KeyboardInputMapper to statically remap "stem" keys based on one device's IDC file. I'm not sure why this was added, but there are no usages of these key remappings that I could find, so I'm removing these to simplify the code. Bug: 245989146 Test: None Change-Id: I8745de89c3631d7dc0f2c262ff5eb97c55f40ec1 --- .../reader/mapper/KeyboardInputMapper.cpp | 45 ------------------- 1 file changed, 45 deletions(-) diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index 8704d1b211..060bfc2ae8 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -56,33 +56,7 @@ static const int32_t keyCodeRotationMap[][4] = { static const size_t keyCodeRotationMapSize = sizeof(keyCodeRotationMap) / sizeof(keyCodeRotationMap[0]); -static int32_t rotateStemKey(int32_t value, int32_t orientation, const int32_t map[][2], - size_t mapSize) { - if (orientation == DISPLAY_ORIENTATION_180) { - for (size_t i = 0; i < mapSize; i++) { - if (value == map[i][0]) { - return map[i][1]; - } - } - } - return value; -} - -// The mapping can be defined using input device configuration properties keyboard.rotated.stem_X -static int32_t stemKeyRotationMap[][2] = { - // key codes enumerated with the original (unrotated) key first - // no rotation, 180 degree rotation - {AKEYCODE_STEM_PRIMARY, AKEYCODE_STEM_PRIMARY}, - {AKEYCODE_STEM_1, AKEYCODE_STEM_1}, - {AKEYCODE_STEM_2, AKEYCODE_STEM_2}, - {AKEYCODE_STEM_3, AKEYCODE_STEM_3}, -}; - -static const size_t stemKeyRotationMapSize = - sizeof(stemKeyRotationMap) / sizeof(stemKeyRotationMap[0]); - static int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) { - keyCode = rotateStemKey(keyCode, orientation, stemKeyRotationMap, stemKeyRotationMapSize); return rotateValueUsingRotationMap(keyCode, orientation, keyCodeRotationMap, keyCodeRotationMapSize); } @@ -159,30 +133,11 @@ std::list KeyboardInputMapper::configure(nsecs_t when, return out; } -static void mapStemKey(int32_t keyCode, const PropertyMap& config, char const* property) { - int32_t mapped = 0; - if (config.tryGetProperty(property, mapped) && mapped > 0) { - for (size_t i = 0; i < stemKeyRotationMapSize; i++) { - if (stemKeyRotationMap[i][0] == keyCode) { - stemKeyRotationMap[i][1] = mapped; - return; - } - } - } -} - void KeyboardInputMapper::configureParameters() { mParameters.orientationAware = false; const PropertyMap& config = getDeviceContext().getConfiguration(); config.tryGetProperty("keyboard.orientationAware", mParameters.orientationAware); - if (mParameters.orientationAware) { - mapStemKey(AKEYCODE_STEM_PRIMARY, config, "keyboard.rotated.stem_primary"); - mapStemKey(AKEYCODE_STEM_1, config, "keyboard.rotated.stem_1"); - mapStemKey(AKEYCODE_STEM_2, config, "keyboard.rotated.stem_2"); - mapStemKey(AKEYCODE_STEM_3, config, "keyboard.rotated.stem_3"); - } - mParameters.handlesKeyRepeat = false; config.tryGetProperty("keyboard.handlesKeyRepeat", mParameters.handlesKeyRepeat); -- GitLab From 2197cb4c1bf6ec84ceccc79ae20b690f214acc34 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 11 Oct 2022 02:56:14 +0000 Subject: [PATCH 0417/1310] KeyboardInputMapper: Miscelaneous code cleanup Initialize all members at construction, make some functions static, remove some unused parameters, and use an optional return type for clarity. There should be no behavior changes in this CL. Bug: 245989146 Test: atest inputflinger_tests Change-Id: Ia57d1c88f0d60938a5e803f51d99c335e7fcf459 --- .../reader/mapper/KeyboardInputMapper.cpp | 170 +++++++++--------- .../reader/mapper/KeyboardInputMapper.h | 48 +++-- 2 files changed, 102 insertions(+), 116 deletions(-) diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index 060bfc2ae8..fa38186e92 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -24,41 +24,69 @@ namespace android { // --- Static Definitions --- -static int32_t rotateValueUsingRotationMap(int32_t value, int32_t orientation, - const int32_t map[][4], size_t mapSize) { +static int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) { + static constexpr int32_t KEYCODE_ROTATION_MAP[][4] = { + // key codes enumerated counter-clockwise with the original (unrotated) key first + // no rotation, 90 degree rotation, 180 degree rotation, 270 degree rotation + {AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT}, + {AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN}, + {AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT}, + {AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP}, + {AKEYCODE_SYSTEM_NAVIGATION_DOWN, AKEYCODE_SYSTEM_NAVIGATION_RIGHT, + AKEYCODE_SYSTEM_NAVIGATION_UP, AKEYCODE_SYSTEM_NAVIGATION_LEFT}, + {AKEYCODE_SYSTEM_NAVIGATION_RIGHT, AKEYCODE_SYSTEM_NAVIGATION_UP, + AKEYCODE_SYSTEM_NAVIGATION_LEFT, AKEYCODE_SYSTEM_NAVIGATION_DOWN}, + {AKEYCODE_SYSTEM_NAVIGATION_UP, AKEYCODE_SYSTEM_NAVIGATION_LEFT, + AKEYCODE_SYSTEM_NAVIGATION_DOWN, AKEYCODE_SYSTEM_NAVIGATION_RIGHT}, + {AKEYCODE_SYSTEM_NAVIGATION_LEFT, AKEYCODE_SYSTEM_NAVIGATION_DOWN, + AKEYCODE_SYSTEM_NAVIGATION_RIGHT, AKEYCODE_SYSTEM_NAVIGATION_UP}, + }; + + LOG_ALWAYS_FATAL_IF(orientation < 0 || orientation > 3, "Invalid orientation: %d", orientation); if (orientation != DISPLAY_ORIENTATION_0) { - for (size_t i = 0; i < mapSize; i++) { - if (value == map[i][0]) { - return map[i][orientation]; + for (const auto& rotation : KEYCODE_ROTATION_MAP) { + if (rotation[DISPLAY_ORIENTATION_0] == keyCode) { + return rotation[orientation]; } } } - return value; + return keyCode; } -static const int32_t keyCodeRotationMap[][4] = { - // key codes enumerated counter-clockwise with the original (unrotated) key first - // no rotation, 90 degree rotation, 180 degree rotation, 270 degree rotation - {AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT}, - {AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN}, - {AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT}, - {AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP}, - {AKEYCODE_SYSTEM_NAVIGATION_DOWN, AKEYCODE_SYSTEM_NAVIGATION_RIGHT, - AKEYCODE_SYSTEM_NAVIGATION_UP, AKEYCODE_SYSTEM_NAVIGATION_LEFT}, - {AKEYCODE_SYSTEM_NAVIGATION_RIGHT, AKEYCODE_SYSTEM_NAVIGATION_UP, - AKEYCODE_SYSTEM_NAVIGATION_LEFT, AKEYCODE_SYSTEM_NAVIGATION_DOWN}, - {AKEYCODE_SYSTEM_NAVIGATION_UP, AKEYCODE_SYSTEM_NAVIGATION_LEFT, - AKEYCODE_SYSTEM_NAVIGATION_DOWN, AKEYCODE_SYSTEM_NAVIGATION_RIGHT}, - {AKEYCODE_SYSTEM_NAVIGATION_LEFT, AKEYCODE_SYSTEM_NAVIGATION_DOWN, - AKEYCODE_SYSTEM_NAVIGATION_RIGHT, AKEYCODE_SYSTEM_NAVIGATION_UP}, -}; - -static const size_t keyCodeRotationMapSize = - sizeof(keyCodeRotationMap) / sizeof(keyCodeRotationMap[0]); +static bool isKeyboardOrGamepadKey(int32_t scanCode) { + return scanCode < BTN_MOUSE || scanCode >= BTN_WHEEL || + (scanCode >= BTN_MISC && scanCode < BTN_MOUSE) || + (scanCode >= BTN_JOYSTICK && scanCode < BTN_DIGI); +} -static int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) { - return rotateValueUsingRotationMap(keyCode, orientation, keyCodeRotationMap, - keyCodeRotationMapSize); +static bool isMediaKey(int32_t keyCode) { + switch (keyCode) { + case AKEYCODE_MEDIA_PLAY: + case AKEYCODE_MEDIA_PAUSE: + case AKEYCODE_MEDIA_PLAY_PAUSE: + case AKEYCODE_MUTE: + case AKEYCODE_HEADSETHOOK: + case AKEYCODE_MEDIA_STOP: + case AKEYCODE_MEDIA_NEXT: + case AKEYCODE_MEDIA_PREVIOUS: + case AKEYCODE_MEDIA_REWIND: + case AKEYCODE_MEDIA_RECORD: + case AKEYCODE_MEDIA_FAST_FORWARD: + case AKEYCODE_MEDIA_SKIP_FORWARD: + case AKEYCODE_MEDIA_SKIP_BACKWARD: + case AKEYCODE_MEDIA_STEP_FORWARD: + case AKEYCODE_MEDIA_STEP_BACKWARD: + case AKEYCODE_MEDIA_AUDIO_TRACK: + case AKEYCODE_VOLUME_UP: + case AKEYCODE_VOLUME_DOWN: + case AKEYCODE_VOLUME_MUTE: + case AKEYCODE_TV_AUDIO_DESCRIPTION: + case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP: + case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN: + return true; + default: + return false; + } } // --- KeyboardInputMapper --- @@ -67,8 +95,6 @@ KeyboardInputMapper::KeyboardInputMapper(InputDeviceContext& deviceContext, uint int32_t keyboardType) : InputMapper(deviceContext), mSource(source), mKeyboardType(keyboardType) {} -KeyboardInputMapper::~KeyboardInputMapper() {} - uint32_t KeyboardInputMapper::getSources() const { return mSource; } @@ -104,7 +130,7 @@ void KeyboardInputMapper::dump(std::string& dump) { } std::optional KeyboardInputMapper::findViewport( - nsecs_t when, const InputReaderConfiguration* config) { + const InputReaderConfiguration* config) { if (getDeviceContext().getAssociatedViewport()) { return getDeviceContext().getAssociatedViewport(); } @@ -128,7 +154,7 @@ std::list KeyboardInputMapper::configure(nsecs_t when, } if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { - mViewport = findViewport(when, config); + mViewport = findViewport(config); } return out; } @@ -145,7 +171,7 @@ void KeyboardInputMapper::configureParameters() { config.tryGetProperty("keyboard.doNotWakeByDefault", mParameters.doNotWakeByDefault); } -void KeyboardInputMapper::dumpParameters(std::string& dump) { +void KeyboardInputMapper::dumpParameters(std::string& dump) const { dump += INDENT3 "Parameters:\n"; dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware)); dump += StringPrintf(INDENT4 "HandlesKeyRepeat: %s\n", toString(mParameters.handlesKeyRepeat)); @@ -190,41 +216,6 @@ std::list KeyboardInputMapper::process(const RawEvent* rawEvent) { return out; } -bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) { - return scanCode < BTN_MOUSE || scanCode >= BTN_WHEEL || - (scanCode >= BTN_MISC && scanCode < BTN_MOUSE) || - (scanCode >= BTN_JOYSTICK && scanCode < BTN_DIGI); -} - -bool KeyboardInputMapper::isMediaKey(int32_t keyCode) { - switch (keyCode) { - case AKEYCODE_MEDIA_PLAY: - case AKEYCODE_MEDIA_PAUSE: - case AKEYCODE_MEDIA_PLAY_PAUSE: - case AKEYCODE_MUTE: - case AKEYCODE_HEADSETHOOK: - case AKEYCODE_MEDIA_STOP: - case AKEYCODE_MEDIA_NEXT: - case AKEYCODE_MEDIA_PREVIOUS: - case AKEYCODE_MEDIA_REWIND: - case AKEYCODE_MEDIA_RECORD: - case AKEYCODE_MEDIA_FAST_FORWARD: - case AKEYCODE_MEDIA_SKIP_FORWARD: - case AKEYCODE_MEDIA_SKIP_BACKWARD: - case AKEYCODE_MEDIA_STEP_FORWARD: - case AKEYCODE_MEDIA_STEP_BACKWARD: - case AKEYCODE_MEDIA_AUDIO_TRACK: - case AKEYCODE_VOLUME_UP: - case AKEYCODE_VOLUME_DOWN: - case AKEYCODE_VOLUME_MUTE: - case AKEYCODE_TV_AUDIO_DESCRIPTION: - case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP: - case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN: - return true; - } - return false; -} - std::list KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, int32_t scanCode, int32_t usageCode) { std::list out; @@ -240,6 +231,7 @@ std::list KeyboardInputMapper::processKey(nsecs_t when, nsecs_t read } nsecs_t downTime = when; + std::optional keyDownIndex = findKeyDownIndex(scanCode); if (down) { // Rotate key codes according to orientation if needed. if (mParameters.orientationAware) { @@ -247,11 +239,10 @@ std::list KeyboardInputMapper::processKey(nsecs_t when, nsecs_t read } // Add key down. - ssize_t keyDownIndex = findKeyDown(scanCode); - if (keyDownIndex >= 0) { + if (keyDownIndex) { // key repeat, be sure to use same keycode as before in case of rotation - keyCode = mKeyDowns[keyDownIndex].keyCode; - downTime = mKeyDowns[keyDownIndex].downTime; + keyCode = mKeyDowns[*keyDownIndex].keyCode; + downTime = mKeyDowns[*keyDownIndex].downTime; } else { // key down if ((policyFlags & POLICY_FLAG_VIRTUAL) && @@ -270,12 +261,11 @@ std::list KeyboardInputMapper::processKey(nsecs_t when, nsecs_t read } } else { // Remove key down. - ssize_t keyDownIndex = findKeyDown(scanCode); - if (keyDownIndex >= 0) { + if (keyDownIndex) { // key up, be sure to use same keycode as before in case of rotation - keyCode = mKeyDowns[keyDownIndex].keyCode; - downTime = mKeyDowns[keyDownIndex].downTime; - mKeyDowns.erase(mKeyDowns.begin() + (size_t)keyDownIndex); + keyCode = mKeyDowns[*keyDownIndex].keyCode; + downTime = mKeyDowns[*keyDownIndex].downTime; + mKeyDowns.erase(mKeyDowns.begin() + *keyDownIndex); } else { // key was not actually down ALOGI("Dropping key up from device %s because the key was not down. " @@ -308,22 +298,22 @@ std::list KeyboardInputMapper::processKey(nsecs_t when, nsecs_t read policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT; } - out.push_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - getDisplayId(), policyFlags, - down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, - AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, - downTime)); + out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mSource, getDisplayId(), policyFlags, + down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, + AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, + downTime)); return out; } -ssize_t KeyboardInputMapper::findKeyDown(int32_t scanCode) { +std::optional KeyboardInputMapper::findKeyDownIndex(int32_t scanCode) { size_t n = mKeyDowns.size(); for (size_t i = 0; i < n; i++) { if (mKeyDowns[i].scanCode == scanCode) { return i; } } - return -1; + return {}; } int32_t KeyboardInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { @@ -436,12 +426,12 @@ std::list KeyboardInputMapper::cancelAllDownKeys(nsecs_t when) { std::list out; size_t n = mKeyDowns.size(); for (size_t i = 0; i < n; i++) { - out.push_back(NotifyKeyArgs(getContext()->getNextId(), when, - systemTime(SYSTEM_TIME_MONOTONIC), getDeviceId(), mSource, - getDisplayId(), 0 /*policyFlags*/, AKEY_EVENT_ACTION_UP, - AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED, - mKeyDowns[i].keyCode, mKeyDowns[i].scanCode, AMETA_NONE, - mKeyDowns[i].downTime)); + out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, + systemTime(SYSTEM_TIME_MONOTONIC), getDeviceId(), mSource, + getDisplayId(), 0 /*policyFlags*/, AKEY_EVENT_ACTION_UP, + AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED, + mKeyDowns[i].keyCode, mKeyDowns[i].scanCode, AMETA_NONE, + mKeyDowns[i].downTime)); } mKeyDowns.clear(); mMetaState = AMETA_NONE; diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index 8d72ee9629..3dd570d79b 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -23,7 +23,7 @@ namespace android { class KeyboardInputMapper : public InputMapper { public: KeyboardInputMapper(InputDeviceContext& deviceContext, uint32_t source, int32_t keyboardType); - virtual ~KeyboardInputMapper(); + ~KeyboardInputMapper() override = default; uint32_t getSources() const override; void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; @@ -47,58 +47,54 @@ public: private: // The current viewport. - std::optional mViewport; + std::optional mViewport{}; struct KeyDown { - nsecs_t downTime; - int32_t keyCode; - int32_t scanCode; + nsecs_t downTime{}; + int32_t keyCode{}; + int32_t scanCode{}; }; - uint32_t mSource; - int32_t mKeyboardType; + uint32_t mSource{}; + int32_t mKeyboardType{}; - std::vector mKeyDowns; // keys that are down - int32_t mMetaState; + std::vector mKeyDowns{}; // keys that are down + int32_t mMetaState{}; - int32_t mCurrentHidUsage; // most recent HID usage seen this packet, or 0 if none + int32_t mCurrentHidUsage{}; // most recent HID usage seen this packet, or 0 if none struct LedState { - bool avail; // led is available - bool on; // we think the led is currently on + bool avail{}; // led is available + bool on{}; // we think the led is currently on }; - LedState mCapsLockLedState; - LedState mNumLockLedState; - LedState mScrollLockLedState; + LedState mCapsLockLedState{}; + LedState mNumLockLedState{}; + LedState mScrollLockLedState{}; // Immutable configuration parameters. struct Parameters { - bool orientationAware; - bool handlesKeyRepeat; - bool doNotWakeByDefault; - } mParameters; + bool orientationAware{}; + bool handlesKeyRepeat{}; + bool doNotWakeByDefault{}; + } mParameters{}; void configureParameters(); - void dumpParameters(std::string& dump); + void dumpParameters(std::string& dump) const; int32_t getOrientation(); int32_t getDisplayId(); - bool isKeyboardOrGamepadKey(int32_t scanCode); - bool isMediaKey(int32_t keyCode); - [[nodiscard]] std::list processKey(nsecs_t when, nsecs_t readTime, bool down, int32_t scanCode, int32_t usageCode); bool updateMetaStateIfNeeded(int32_t keyCode, bool down); - ssize_t findKeyDown(int32_t scanCode); + std::optional findKeyDownIndex(int32_t scanCode); void resetLedState(); void initializeLedState(LedState& ledState, int32_t led); void updateLedStateForModifier(LedState& ledState, int32_t led, int32_t modifier, bool reset); - std::optional findViewport(nsecs_t when, - const InputReaderConfiguration* config); + std::optional findViewport(const InputReaderConfiguration* config); [[nodiscard]] std::list cancelAllDownKeys(nsecs_t when); }; -- GitLab From e1a41a84725af9321ee12dabd1b14310dd70ac89 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 14 Oct 2022 18:06:50 +0000 Subject: [PATCH 0418/1310] Generate key events for stylus button presses Ensure all devices that report stylus buttons are categorized as a keyboard. This means that KeyboardInputMapper will get a chance to handle the events from these devices. Then, we allow KeyboardInputMapper to process stylus button events. Since stylus button (e.g. BTN_STYLUS) are mapped to Android key codes in the Generic.kl file, KeyEvents with the appropriate key codes will be generated for these button presses. Bug: 246394583 Test: atest inputflinger_tests Change-Id: I92d4a60f23f98f9d239edf1f4dd400e6e528e350 --- services/inputflinger/reader/EventHub.cpp | 14 ++-- .../reader/mapper/KeyboardInputMapper.cpp | 11 ++-- .../inputflinger/tests/InputReader_test.cpp | 66 +++++++++++++++---- .../inputflinger/tests/TestInputListener.cpp | 6 ++ .../inputflinger/tests/TestInputListener.h | 2 + .../tests/TestInputListenerMatchers.h | 13 +++- services/inputflinger/tests/UinputDevice.cpp | 20 ++++-- services/inputflinger/tests/UinputDevice.h | 13 +++- 8 files changed, 114 insertions(+), 31 deletions(-) diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index 18d03f8bab..eaa6f5c8df 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -2171,13 +2171,15 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { device->readDeviceBitMask(EVIOCGBIT(EV_MSC, 0), device->mscBitmask); device->readDeviceBitMask(EVIOCGPROP(0), device->propBitmask); - // See if this is a keyboard. Ignore everything in the button range except for - // joystick and gamepad buttons which are handled like keyboards for the most part. + // See if this is a device with keys. This could be full keyboard, or other devices like + // gamepads, joysticks, and styluses with buttons that should generate key presses. bool haveKeyboardKeys = device->keyBitmask.any(0, BTN_MISC) || device->keyBitmask.any(BTN_WHEEL, KEY_MAX + 1); bool haveGamepadButtons = device->keyBitmask.any(BTN_MISC, BTN_MOUSE) || device->keyBitmask.any(BTN_JOYSTICK, BTN_DIGI); - if (haveKeyboardKeys || haveGamepadButtons) { + bool haveStylusButtons = device->keyBitmask.test(BTN_STYLUS) || + device->keyBitmask.test(BTN_STYLUS2) || device->keyBitmask.test(BTN_STYLUS3); + if (haveKeyboardKeys || haveGamepadButtons || haveStylusButtons) { device->classes |= InputDeviceClass::KEYBOARD; } @@ -2208,14 +2210,10 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { } else if (device->keyBitmask.test(BTN_TOUCH) && device->absBitmask.test(ABS_X) && device->absBitmask.test(ABS_Y)) { device->classes |= InputDeviceClass::TOUCH; - // Is this a BT stylus? + // Is this a stylus that reports contact/pressure independently of touch coordinates? } else if ((device->absBitmask.test(ABS_PRESSURE) || device->keyBitmask.test(BTN_TOUCH)) && !device->absBitmask.test(ABS_X) && !device->absBitmask.test(ABS_Y)) { device->classes |= InputDeviceClass::EXTERNAL_STYLUS; - // Keyboard will try to claim some of the buttons but we really want to reserve those so we - // can fuse it with the touch screen data, so just take them back. Note this means an - // external stylus cannot also be a keyboard device. - device->classes &= ~InputDeviceClass::KEYBOARD; } // See if this device is a joystick. diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index fa38186e92..33b8a1bab9 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -53,10 +53,11 @@ static int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) { return keyCode; } -static bool isKeyboardOrGamepadKey(int32_t scanCode) { - return scanCode < BTN_MOUSE || scanCode >= BTN_WHEEL || - (scanCode >= BTN_MISC && scanCode < BTN_MOUSE) || - (scanCode >= BTN_JOYSTICK && scanCode < BTN_DIGI); +static bool isSupportedScanCode(int32_t scanCode) { + // KeyboardInputMapper handles keys from keyboards, gamepads, and styluses. + return scanCode < BTN_MOUSE || (scanCode >= BTN_JOYSTICK && scanCode < BTN_DIGI) || + scanCode == BTN_STYLUS || scanCode == BTN_STYLUS2 || scanCode == BTN_STYLUS3 || + scanCode >= BTN_WHEEL; } static bool isMediaKey(int32_t keyCode) { @@ -195,7 +196,7 @@ std::list KeyboardInputMapper::process(const RawEvent* rawEvent) { int32_t usageCode = mCurrentHidUsage; mCurrentHidUsage = 0; - if (isKeyboardOrGamepadKey(scanCode)) { + if (isSupportedScanCode(scanCode)) { out += processKey(rawEvent->when, rawEvent->readTime, rawEvent->value != 0, scanCode, usageCode); } diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index f333306f26..9d14a862cd 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -2391,18 +2391,11 @@ TEST_F(InputReaderIntegrationTest, AddNewDevice) { ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); ASSERT_EQ(initialNumDevices + 1, mFakePolicy->getInputDevices().size()); - // Find the test device by its name. - const std::vector inputDevices = mFakePolicy->getInputDevices(); - const auto& it = - std::find_if(inputDevices.begin(), inputDevices.end(), - [&keyboard](const InputDeviceInfo& info) { - return info.getIdentifier().name == keyboard->getName(); - }); - - ASSERT_NE(it, inputDevices.end()); - ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, it->getKeyboardType()); - ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, it->getSources()); - ASSERT_EQ(0U, it->getMotionRanges().size()); + const auto device = findDeviceByName(keyboard->getName()); + ASSERT_TRUE(device.has_value()); + ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, device->getKeyboardType()); + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, device->getSources()); + ASSERT_EQ(0U, device->getMotionRanges().size()); keyboard.reset(); ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); @@ -2437,6 +2430,41 @@ TEST_F(InputReaderIntegrationTest, SendsEventsToInputListener) { ASSERT_LE(keyArgs.eventTime, keyArgs.readTime); } +TEST_F(InputReaderIntegrationTest, ExternalStylusesButtons) { + std::unique_ptr stylus = createUinputDevice(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); + + const auto device = findDeviceByName(stylus->getName()); + ASSERT_TRUE(device.has_value()); + + // An external stylus with buttons should be recognized as a keyboard. + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, device->getSources()) + << "Unexpected source " << inputEventSourceToString(device->getSources()).c_str(); + ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, device->getKeyboardType()); + + const auto DOWN = + AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD)); + const auto UP = AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD)); + + stylus->pressAndReleaseKey(BTN_STYLUS); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(DOWN, WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(UP, WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY)))); + + stylus->pressAndReleaseKey(BTN_STYLUS2); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(DOWN, WithKeyCode(AKEYCODE_STYLUS_BUTTON_SECONDARY)))); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(UP, WithKeyCode(AKEYCODE_STYLUS_BUTTON_SECONDARY)))); + + stylus->pressAndReleaseKey(BTN_STYLUS3); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(DOWN, WithKeyCode(AKEYCODE_STYLUS_BUTTON_TERTIARY)))); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(UP, WithKeyCode(AKEYCODE_STYLUS_BUTTON_TERTIARY)))); +} + /** * The Steam controller sends BTN_GEAR_DOWN and BTN_GEAR_UP for the two "paddle" buttons * on the back. In this test, we make sure that BTN_GEAR_DOWN / BTN_WHEEL and BTN_GEAR_UP @@ -2772,6 +2800,20 @@ TEST_F(TouchIntegrationTest, NotifiesPolicyWhenStylusGestureStarted) { ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId())); } +TEST_F(TouchIntegrationTest, StylusButtonsGenerateKeyEvents) { + mDevice->sendKey(BTN_STYLUS, 1); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY)))); + + mDevice->sendKey(BTN_STYLUS, 0); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY)))); +} + // --- InputDeviceTest --- class InputDeviceTest : public testing::Test { protected: diff --git a/services/inputflinger/tests/TestInputListener.cpp b/services/inputflinger/tests/TestInputListener.cpp index 29093efc67..5e47b8019b 100644 --- a/services/inputflinger/tests/TestInputListener.cpp +++ b/services/inputflinger/tests/TestInputListener.cpp @@ -59,6 +59,12 @@ void TestInputListener::assertNotifyKeyWasCalled(NotifyKeyArgs* outEventArgs) { assertCalled(outEventArgs, "Expected notifyKey() to have been called.")); } +void TestInputListener::assertNotifyKeyWasCalled(const ::testing::Matcher& matcher) { + NotifyKeyArgs outEventArgs; + ASSERT_NO_FATAL_FAILURE(assertNotifyKeyWasCalled(&outEventArgs)); + ASSERT_THAT(outEventArgs, matcher); +} + void TestInputListener::assertNotifyKeyWasNotCalled() { ASSERT_NO_FATAL_FAILURE(assertNotCalled("notifyKey() should not be called.")); } diff --git a/services/inputflinger/tests/TestInputListener.h b/services/inputflinger/tests/TestInputListener.h index 4ad1c4264d..87752e1487 100644 --- a/services/inputflinger/tests/TestInputListener.h +++ b/services/inputflinger/tests/TestInputListener.h @@ -44,6 +44,8 @@ public: void assertNotifyKeyWasCalled(NotifyKeyArgs* outEventArgs = nullptr); + void assertNotifyKeyWasCalled(const ::testing::Matcher& matcher); + void assertNotifyKeyWasNotCalled(); void assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs = nullptr); diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h index e48f1d9082..97ba90b1df 100644 --- a/services/inputflinger/tests/TestInputListenerMatchers.h +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -23,7 +23,7 @@ namespace android { -MATCHER_P(WithMotionAction, action, "InputEvent with specified action") { +MATCHER_P(WithMotionAction, action, "MotionEvent with specified action") { bool matches = action == arg.action; if (!matches) { *result_listener << "expected action " << MotionEvent::actionToString(action) @@ -39,6 +39,12 @@ MATCHER_P(WithMotionAction, action, "InputEvent with specified action") { return matches; } +MATCHER_P(WithKeyAction, action, "KeyEvent with specified action") { + *result_listener << "expected action " << KeyEvent::actionToString(action) << ", but got " + << KeyEvent::actionToString(arg.action); + return arg.action == action; +} + MATCHER_P(WithSource, source, "InputEvent with specified source") { *result_listener << "expected source " << source << ", but got " << arg.source; return arg.source == source; @@ -49,6 +55,11 @@ MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") { return arg.displayId == displayId; } +MATCHER_P(WithKeyCode, keyCode, "KeyEvent with specified key code") { + *result_listener << "expected key code " << keyCode << ", but got " << arg.keyCode; + return arg.keyCode == keyCode; +} + MATCHER_P2(WithCoords, x, y, "InputEvent with specified coords") { const auto argX = arg.pointerCoords[0].getX(); const auto argY = arg.pointerCoords[0].getY(); diff --git a/services/inputflinger/tests/UinputDevice.cpp b/services/inputflinger/tests/UinputDevice.cpp index 626ad67c71..c4830dc815 100644 --- a/services/inputflinger/tests/UinputDevice.cpp +++ b/services/inputflinger/tests/UinputDevice.cpp @@ -76,8 +76,8 @@ void UinputDevice::injectEvent(uint16_t type, uint16_t code, int32_t value) { // --- UinputKeyboard --- -UinputKeyboard::UinputKeyboard(std::initializer_list keys) - : UinputDevice(UinputKeyboard::KEYBOARD_NAME), mKeys(keys.begin(), keys.end()) {} +UinputKeyboard::UinputKeyboard(const char* name, std::initializer_list keys) + : UinputDevice(name), mKeys(keys.begin(), keys.end()) {} void UinputKeyboard::configureDevice(int fd, uinput_user_dev* device) { // enable key press/release event @@ -121,14 +121,19 @@ void UinputKeyboard::pressAndReleaseKey(int key) { // --- UinputHomeKey --- -UinputHomeKey::UinputHomeKey() : UinputKeyboard({KEY_HOME}) {} +UinputHomeKey::UinputHomeKey() : UinputKeyboard("Test Uinput Home Key", {KEY_HOME}) {} void UinputHomeKey::pressAndReleaseHomeKey() { pressAndReleaseKey(KEY_HOME); } // --- UinputSteamController -UinputSteamController::UinputSteamController() : UinputKeyboard({BTN_GEAR_DOWN, BTN_GEAR_UP}) {} +UinputSteamController::UinputSteamController() + : UinputKeyboard("Test Uinput Steam Controller", {BTN_GEAR_DOWN, BTN_GEAR_UP}) {} + +// --- UinputExternalStylus +UinputExternalStylus::UinputExternalStylus() + : UinputKeyboard("Test Uinput External Stylus", {BTN_STYLUS, BTN_STYLUS2, BTN_STYLUS3}) {} // --- UinputTouchScreen --- UinputTouchScreen::UinputTouchScreen(const Rect* size) @@ -147,6 +152,9 @@ void UinputTouchScreen::configureDevice(int fd, uinput_user_dev* device) { ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOOL_TYPE); ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT); ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH); + ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS); + ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS2); + ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS3); device->absmin[ABS_MT_SLOT] = RAW_SLOT_MIN; device->absmax[ABS_MT_SLOT] = RAW_SLOT_MAX; @@ -198,6 +206,10 @@ void UinputTouchScreen::sendSync() { injectEvent(EV_SYN, SYN_REPORT, 0); } +void UinputTouchScreen::sendKey(int32_t scanCode, int32_t value) { + injectEvent(EV_KEY, scanCode, value); +} + // Get the center x, y base on the range definition. const Point UinputTouchScreen::getCenterPoint() { return Point(mSize.left + mSize.width() / 2, mSize.top + mSize.height() / 2); diff --git a/services/inputflinger/tests/UinputDevice.h b/services/inputflinger/tests/UinputDevice.h index e0ff8c3d4c..53dcfd0a68 100644 --- a/services/inputflinger/tests/UinputDevice.h +++ b/services/inputflinger/tests/UinputDevice.h @@ -84,7 +84,7 @@ public: friend std::unique_ptr createUinputDevice(Ts... args); protected: - UinputKeyboard(std::initializer_list keys = {}); + UinputKeyboard(const char* name, std::initializer_list keys = {}); private: void configureDevice(int fd, uinput_user_dev* device) override; @@ -117,6 +117,16 @@ private: UinputSteamController(); }; +// A stylus that reports button presses. +class UinputExternalStylus : public UinputKeyboard { +public: + template + friend std::unique_ptr createUinputDevice(Ts... args); + +private: + UinputExternalStylus(); +}; + // --- UinputTouchScreen --- // A touch screen device with specific size. class UinputTouchScreen : public UinputDevice { @@ -142,6 +152,7 @@ public: void sendUp(); void sendToolType(int32_t toolType); void sendSync(); + void sendKey(int32_t scanCode, int32_t value); const Point getCenterPoint(); -- GitLab From efa77d18072f1ac821e626018f81fc22f66b41c7 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 14 Oct 2022 19:20:16 +0000 Subject: [PATCH 0419/1310] TouchButtonAccumulator: Miscelaneous cleanup Bug: 246394583 Test: atest inputflinger_tests Change-Id: I0d319c650ede4320736932a7be2e5f07eea7eec7 --- .../accumulator/TouchButtonAccumulator.cpp | 21 ---------- .../accumulator/TouchButtonAccumulator.h | 40 +++++++++---------- 2 files changed, 19 insertions(+), 42 deletions(-) diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp index 86153d3f5e..94ec08a906 100644 --- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp @@ -21,10 +21,6 @@ namespace android { -TouchButtonAccumulator::TouchButtonAccumulator() : mHaveBtnTouch(false), mHaveStylus(false) { - clearButtons(); -} - void TouchButtonAccumulator::configure(InputDeviceContext& deviceContext) { mHaveBtnTouch = deviceContext.hasScanCode(BTN_TOUCH); mHaveStylus = deviceContext.hasScanCode(BTN_TOOL_PEN) || @@ -52,23 +48,6 @@ void TouchButtonAccumulator::reset(InputDeviceContext& deviceContext) { mBtnToolQuadTap = deviceContext.isKeyPressed(BTN_TOOL_QUADTAP); } -void TouchButtonAccumulator::clearButtons() { - mBtnTouch = 0; - mBtnStylus = 0; - mBtnStylus2 = 0; - mBtnToolFinger = 0; - mBtnToolPen = 0; - mBtnToolRubber = 0; - mBtnToolBrush = 0; - mBtnToolPencil = 0; - mBtnToolAirbrush = 0; - mBtnToolMouse = 0; - mBtnToolLens = 0; - mBtnToolDoubleTap = 0; - mBtnToolTripleTap = 0; - mBtnToolQuadTap = 0; -} - void TouchButtonAccumulator::process(const RawEvent* rawEvent) { if (rawEvent->type == EV_KEY) { switch (rawEvent->code) { diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h index 7b889be238..e19b7f87ff 100644 --- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h @@ -16,7 +16,7 @@ #pragma once -#include +#include namespace android { @@ -26,7 +26,7 @@ struct RawEvent; /* Keeps track of the state of touch, stylus and tool buttons. */ class TouchButtonAccumulator { public: - TouchButtonAccumulator(); + TouchButtonAccumulator() = default; void configure(InputDeviceContext& deviceContext); void reset(InputDeviceContext& deviceContext); @@ -39,25 +39,23 @@ public: bool hasStylus() const; private: - bool mHaveBtnTouch; - bool mHaveStylus; - - bool mBtnTouch; - bool mBtnStylus; - bool mBtnStylus2; - bool mBtnToolFinger; - bool mBtnToolPen; - bool mBtnToolRubber; - bool mBtnToolBrush; - bool mBtnToolPencil; - bool mBtnToolAirbrush; - bool mBtnToolMouse; - bool mBtnToolLens; - bool mBtnToolDoubleTap; - bool mBtnToolTripleTap; - bool mBtnToolQuadTap; - - void clearButtons(); + bool mHaveBtnTouch{}; + bool mHaveStylus{}; + + bool mBtnTouch{}; + bool mBtnStylus{}; + bool mBtnStylus2{}; + bool mBtnToolFinger{}; + bool mBtnToolPen{}; + bool mBtnToolRubber{}; + bool mBtnToolBrush{}; + bool mBtnToolPencil{}; + bool mBtnToolAirbrush{}; + bool mBtnToolMouse{}; + bool mBtnToolLens{}; + bool mBtnToolDoubleTap{}; + bool mBtnToolTripleTap{}; + bool mBtnToolQuadTap{}; }; } // namespace android -- GitLab From a36218597b151009bd496832718e537ca9235dbc Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 14 Oct 2022 18:57:23 +0000 Subject: [PATCH 0420/1310] Add additional ways to recognize an external stylus Recognize a keyboard device as an external stylus if it can produce any of the stylus key codes. Additionally, add the ability to configure an input device as an external stylus using an IDC file. This will be necessary to classify external styluses that report buttons as custom HID usages, since we don't have a way to tell which HID usages a device supports from userspace yet. Bug: 246394583 Test: atest inputflinger_tests Test: manual, with a Lenovo Precision Pen 3 Change-Id: Ief22aac9537cd168dd43d2a9d63bc0a65f6ba3dc --- services/inputflinger/reader/EventHub.cpp | 22 ++++++++++++++++++- .../inputflinger/tests/InputReader_test.cpp | 4 ++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index eaa6f5c8df..0aaef53c0d 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -149,6 +149,14 @@ static std::string sha1(const std::string& in) { return out; } +/* The set of all Android key codes that correspond to buttons (bit-switches) on a stylus. */ +static constexpr std::array STYLUS_BUTTON_KEYCODES = { + AKEYCODE_STYLUS_BUTTON_PRIMARY, + AKEYCODE_STYLUS_BUTTON_SECONDARY, + AKEYCODE_STYLUS_BUTTON_TERTIARY, + AKEYCODE_STYLUS_BUTTON_TAIL, +}; + /** * Return true if name matches "v4l-touch*" */ @@ -2189,11 +2197,13 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { device->classes |= InputDeviceClass::CURSOR; } - // See if this is a rotary encoder type device. + // See if the device is specially configured to be of a certain type. std::string deviceType; if (device->configuration && device->configuration->tryGetProperty("device.type", deviceType)) { if (deviceType == "rotaryEncoder") { device->classes |= InputDeviceClass::ROTARY_ENCODER; + } else if (deviceType == "externalStylus") { + device->classes |= InputDeviceClass::EXTERNAL_STYLUS; } } @@ -2298,6 +2308,16 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { break; } } + + // See if this device has any stylus buttons that we would want to fuse with touch data. + if (!device->classes.any(InputDeviceClass::TOUCH | InputDeviceClass::TOUCH_MT)) { + for (int32_t keycode : STYLUS_BUTTON_KEYCODES) { + if (device->hasKeycodeLocked(keycode)) { + device->classes |= InputDeviceClass::EXTERNAL_STYLUS; + break; + } + } + } } // If the device isn't recognized as something we handle, don't monitor it. diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 9d14a862cd..e15d09f2ba 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -2437,8 +2437,8 @@ TEST_F(InputReaderIntegrationTest, ExternalStylusesButtons) { const auto device = findDeviceByName(stylus->getName()); ASSERT_TRUE(device.has_value()); - // An external stylus with buttons should be recognized as a keyboard. - ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, device->getSources()) + // An external stylus with buttons should also be recognized as a keyboard. + ASSERT_EQ(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_STYLUS, device->getSources()) << "Unexpected source " << inputEventSourceToString(device->getSources()).c_str(); ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, device->getKeyboardType()); -- GitLab From 21e96e646fb1022589df69e09a2ec8183cd0edfb Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 27 Oct 2022 10:23:37 -0700 Subject: [PATCH 0421/1310] Do not call 'setEnabled' before mapper is configured The mappers have a specific lifecycle: 1. constructor 2. configure(0) 3. reset 4. use it However, currently, this could be broken because the 'reset' function is getting invoked before the first configure(0). If a mapper's 'configure(0)' method isn't called, then there will be uninitialized variables inside. Specifically, in TouchInputMapper, this will mean that: a. mPointerUsage may be set to something like "STYLUS". b. mPointerSimple::down or mPointerSimple::hovering may be set to true The above combination could cause a crash, because it would try to access mPointerController, which isn't yet initialized. This is a speculative fix, because we can't reproduce the crash, since it relies on a specific state of the uninitialized variables. Ideally, we would simply eliminate these possibilities by either using the constructor (and calling "configure" there), or providing some default values. To keep the fix simple, in this CL we just avoid calling 'setEnabled' too early. Bug: 255739891 Bug: 255839467 Test: atest inputflinger_tests Change-Id: I44038c5ce5bfdd5ac4c2933e0dc4fa714c5cf260 --- services/inputflinger/reader/InputDevice.cpp | 5 ++++- services/inputflinger/tests/InputReader_test.cpp | 13 ++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 5291776638..e6ab8722f6 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -313,7 +313,10 @@ std::list InputDevice::configure(nsecs_t when, const InputReaderConf } } - if (!changes || (changes & InputReaderConfiguration::CHANGE_ENABLED_STATE)) { + if (changes & InputReaderConfiguration::CHANGE_ENABLED_STATE) { + // Do not execute this code on the first configure, because 'setEnabled' would call + // InputMapper::reset, and you can't reset a mapper before it has been configured. + // The mappers are configured for the first time at the bottom of this function. auto it = config->disabledDevices.find(mId); bool enabled = it == config->disabledDevices.end(); out += setEnabled(enabled, when); diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index bc70584af8..a8b0fe9c9a 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -39,6 +39,7 @@ #include #include +#include #include "android/hardware/input/InputDeviceCountryCode.h" #include "input/DisplayViewport.h" #include "input/Input.h" @@ -141,6 +142,16 @@ static void assertAxisNotPresent(MultiTouchInputMapper& mapper, int axis) { ASSERT_EQ(nullptr, motionRange); } +[[maybe_unused]] static void dumpReader(InputReader& reader) { + std::string dump; + reader.dump(dump); + std::istringstream iss(dump); + for (std::string line; std::getline(iss, line);) { + ALOGE("%s", line.c_str()); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } +} + // --- FakePointerController --- class FakePointerController : public PointerControllerInterface { @@ -740,7 +751,7 @@ private: status_t getAbsoluteAxisInfo(int32_t deviceId, int axis, RawAbsoluteAxisInfo* outAxisInfo) const override { Device* device = getDevice(deviceId); - if (device && device->enabled) { + if (device) { ssize_t index = device->absoluteAxes.indexOfKey(axis); if (index >= 0) { *outAxisInfo = device->absoluteAxes.valueAt(index); -- GitLab From 84395e6eb555b2e148e62fe85b222fd9135c1450 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 26 Oct 2022 19:52:00 +0000 Subject: [PATCH 0422/1310] Extract HID usage accumulation logic from KeyboardInputMapper Extract the logic into a HidUsageAccumulator class so that when HID usage is used in more than one place, the usage code accumulation logic maintains the same behavior. There should be no behavior change in this CL. Bug: 246394583 Test: atest inputflinger_tests Change-Id: Ia5f9bcac145f07c0d0dca643755dcff1b725d568 --- services/inputflinger/reader/Android.bp | 1 + .../reader/mapper/KeyboardInputMapper.cpp | 18 ++------- .../reader/mapper/KeyboardInputMapper.h | 3 +- .../accumulator/HidUsageAccumulator.cpp | 39 ++++++++++++++++++ .../mapper/accumulator/HidUsageAccumulator.h | 40 +++++++++++++++++++ 5 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.cpp create mode 100644 services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.h diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index c5e1f0ca94..a53fcd763d 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -54,6 +54,7 @@ filegroup { "mapper/VibratorInputMapper.cpp", "mapper/accumulator/CursorButtonAccumulator.cpp", "mapper/accumulator/CursorScrollAccumulator.cpp", + "mapper/accumulator/HidUsageAccumulator.cpp", "mapper/accumulator/SingleTouchMotionAccumulator.cpp", "mapper/accumulator/TouchButtonAccumulator.cpp", ], diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index 33b8a1bab9..da9413e4ca 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -180,7 +180,7 @@ void KeyboardInputMapper::dumpParameters(std::string& dump) const { std::list KeyboardInputMapper::reset(nsecs_t when) { std::list out = cancelAllDownKeys(when); - mCurrentHidUsage = 0; + mHidUsageAccumulator.reset(); resetLedState(); @@ -190,29 +190,17 @@ std::list KeyboardInputMapper::reset(nsecs_t when) { std::list KeyboardInputMapper::process(const RawEvent* rawEvent) { std::list out; + mHidUsageAccumulator.process(*rawEvent); switch (rawEvent->type) { case EV_KEY: { int32_t scanCode = rawEvent->code; - int32_t usageCode = mCurrentHidUsage; - mCurrentHidUsage = 0; if (isSupportedScanCode(scanCode)) { out += processKey(rawEvent->when, rawEvent->readTime, rawEvent->value != 0, - scanCode, usageCode); + scanCode, mHidUsageAccumulator.consumeCurrentHidUsage()); } break; } - case EV_MSC: { - if (rawEvent->code == MSC_SCAN) { - mCurrentHidUsage = rawEvent->value; - } - break; - } - case EV_SYN: { - if (rawEvent->code == SYN_REPORT) { - mCurrentHidUsage = 0; - } - } } return out; } diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index 3dd570d79b..11d5ad26e8 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -16,6 +16,7 @@ #pragma once +#include "HidUsageAccumulator.h" #include "InputMapper.h" namespace android { @@ -61,7 +62,7 @@ private: std::vector mKeyDowns{}; // keys that are down int32_t mMetaState{}; - int32_t mCurrentHidUsage{}; // most recent HID usage seen this packet, or 0 if none + HidUsageAccumulator mHidUsageAccumulator; struct LedState { bool avail{}; // led is available diff --git a/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.cpp new file mode 100644 index 0000000000..2da1d814fa --- /dev/null +++ b/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "HidUsageAccumulator.h" + +namespace android { + +void HidUsageAccumulator::process(const RawEvent& rawEvent) { + if (rawEvent.type == EV_MSC && rawEvent.code == MSC_SCAN) { + mCurrentHidUsage = rawEvent.value; + return; + } + + if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) { + reset(); + return; + } +} + +int32_t HidUsageAccumulator::consumeCurrentHidUsage() { + const int32_t currentHidUsage = mCurrentHidUsage; + reset(); + return currentHidUsage; +} + +} // namespace android diff --git a/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.h b/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.h new file mode 100644 index 0000000000..740a710483 --- /dev/null +++ b/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "EventHub.h" + +#include + +namespace android { + +/* Keeps track of the state of currently reported HID usage code. */ +class HidUsageAccumulator { +public: + explicit HidUsageAccumulator() = default; + inline void reset() { *this = HidUsageAccumulator(); } + + void process(const RawEvent& rawEvent); + + /* This must be called when processing the `EV_KEY` event. Returns 0 if invalid. */ + int32_t consumeCurrentHidUsage(); + +private: + int32_t mCurrentHidUsage{}; +}; + +} // namespace android -- GitLab From 4f05b5fb0dd425ee4dc432f08e625b65d78030c6 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 11 Oct 2022 21:24:07 +0000 Subject: [PATCH 0423/1310] TouchButtonAccumulator: Recognize mapped stylus buttons Input devices can be configured to remap some keys using kl files. Ensure that we use mapped key codes to determine the state of stylus buttons. Bug: 246394583 Test: atest inputflinger_tests Change-Id: Iedf67063f3bf34eefe922342456f500d88580ed9 --- .../inputflinger/reader/include/InputDevice.h | 7 +- .../mapper/ExternalStylusInputMapper.cpp | 6 +- .../reader/mapper/TouchInputMapper.cpp | 5 +- .../accumulator/TouchButtonAccumulator.cpp | 71 +++++++++++++------ .../accumulator/TouchButtonAccumulator.h | 15 +++- .../inputflinger/tests/InputReader_test.cpp | 64 +++++++++++++++++ .../tests/TestInputListenerMatchers.h | 5 ++ 7 files changed, 141 insertions(+), 32 deletions(-) diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index b9a2b4ce92..439123bf1d 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -378,8 +378,11 @@ public: mEventHub->getAbsoluteAxisInfo(mId, code, &info); return info.valid; } - inline bool isKeyPressed(int32_t code) const { - return mEventHub->getScanCodeState(mId, code) == AKEY_STATE_DOWN; + inline bool isKeyPressed(int32_t scanCode) const { + return mEventHub->getScanCodeState(mId, scanCode) == AKEY_STATE_DOWN; + } + inline bool isKeyCodePressed(int32_t keyCode) const { + return mEventHub->getKeyCodeState(mId, keyCode) == AKEY_STATE_DOWN; } inline int32_t getAbsoluteAxisValue(int32_t code) const { int32_t value; diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp index 0404c9acc1..56fc5fa99a 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp @@ -24,7 +24,7 @@ namespace android { ExternalStylusInputMapper::ExternalStylusInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext) {} + : InputMapper(deviceContext), mTouchButtonAccumulator(deviceContext) {} uint32_t ExternalStylusInputMapper::getSources() const { return AINPUT_SOURCE_STYLUS; @@ -48,13 +48,13 @@ std::list ExternalStylusInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) { getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPressureAxis); - mTouchButtonAccumulator.configure(getDeviceContext()); + mTouchButtonAccumulator.configure(); return {}; } std::list ExternalStylusInputMapper::reset(nsecs_t when) { mSingleTouchMotionAccumulator.reset(getDeviceContext()); - mTouchButtonAccumulator.reset(getDeviceContext()); + mTouchButtonAccumulator.reset(); return InputMapper::reset(when); } diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 615889ebe3..d17cdf5559 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -170,6 +170,7 @@ void CookedPointerData::copyFrom(const CookedPointerData& other) { TouchInputMapper::TouchInputMapper(InputDeviceContext& deviceContext) : InputMapper(deviceContext), + mTouchButtonAccumulator(deviceContext), mSource(0), mDeviceMode(DeviceMode::DISABLED), mDisplayWidth(-1), @@ -360,7 +361,7 @@ std::list TouchInputMapper::configure(nsecs_t when, // Configure common accumulators. mCursorScrollAccumulator.configure(getDeviceContext()); - mTouchButtonAccumulator.configure(getDeviceContext()); + mTouchButtonAccumulator.configure(); // Configure absolute axis information. configureRawPointerAxes(); @@ -1449,7 +1450,7 @@ std::list TouchInputMapper::reset(nsecs_t when) { mCursorButtonAccumulator.reset(getDeviceContext()); mCursorScrollAccumulator.reset(getDeviceContext()); - mTouchButtonAccumulator.reset(getDeviceContext()); + mTouchButtonAccumulator.reset(); mPointerVelocityControl.reset(); mWheelXVelocityControl.reset(); diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp index 94ec08a906..5d5bee7feb 100644 --- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp @@ -21,34 +21,39 @@ namespace android { -void TouchButtonAccumulator::configure(InputDeviceContext& deviceContext) { - mHaveBtnTouch = deviceContext.hasScanCode(BTN_TOUCH); - mHaveStylus = deviceContext.hasScanCode(BTN_TOOL_PEN) || - deviceContext.hasScanCode(BTN_TOOL_RUBBER) || - deviceContext.hasScanCode(BTN_TOOL_BRUSH) || - deviceContext.hasScanCode(BTN_TOOL_PENCIL) || - deviceContext.hasScanCode(BTN_TOOL_AIRBRUSH); +void TouchButtonAccumulator::configure() { + mHaveBtnTouch = mDeviceContext.hasScanCode(BTN_TOUCH); + mHaveStylus = mDeviceContext.hasScanCode(BTN_TOOL_PEN) || + mDeviceContext.hasScanCode(BTN_TOOL_RUBBER) || + mDeviceContext.hasScanCode(BTN_TOOL_BRUSH) || + mDeviceContext.hasScanCode(BTN_TOOL_PENCIL) || + mDeviceContext.hasScanCode(BTN_TOOL_AIRBRUSH); } -void TouchButtonAccumulator::reset(InputDeviceContext& deviceContext) { - mBtnTouch = deviceContext.isKeyPressed(BTN_TOUCH); - mBtnStylus = deviceContext.isKeyPressed(BTN_STYLUS); +void TouchButtonAccumulator::reset() { + mBtnTouch = mDeviceContext.isKeyPressed(BTN_TOUCH); + mBtnStylus = mDeviceContext.isKeyPressed(BTN_STYLUS) || + mDeviceContext.isKeyCodePressed(AKEYCODE_STYLUS_BUTTON_PRIMARY); // BTN_0 is what gets mapped for the HID usage Digitizers.SecondaryBarrelSwitch - mBtnStylus2 = deviceContext.isKeyPressed(BTN_STYLUS2) || deviceContext.isKeyPressed(BTN_0); - mBtnToolFinger = deviceContext.isKeyPressed(BTN_TOOL_FINGER); - mBtnToolPen = deviceContext.isKeyPressed(BTN_TOOL_PEN); - mBtnToolRubber = deviceContext.isKeyPressed(BTN_TOOL_RUBBER); - mBtnToolBrush = deviceContext.isKeyPressed(BTN_TOOL_BRUSH); - mBtnToolPencil = deviceContext.isKeyPressed(BTN_TOOL_PENCIL); - mBtnToolAirbrush = deviceContext.isKeyPressed(BTN_TOOL_AIRBRUSH); - mBtnToolMouse = deviceContext.isKeyPressed(BTN_TOOL_MOUSE); - mBtnToolLens = deviceContext.isKeyPressed(BTN_TOOL_LENS); - mBtnToolDoubleTap = deviceContext.isKeyPressed(BTN_TOOL_DOUBLETAP); - mBtnToolTripleTap = deviceContext.isKeyPressed(BTN_TOOL_TRIPLETAP); - mBtnToolQuadTap = deviceContext.isKeyPressed(BTN_TOOL_QUADTAP); + mBtnStylus2 = mDeviceContext.isKeyPressed(BTN_STYLUS2) || mDeviceContext.isKeyPressed(BTN_0) || + mDeviceContext.isKeyCodePressed(AKEYCODE_STYLUS_BUTTON_SECONDARY); + mBtnToolFinger = mDeviceContext.isKeyPressed(BTN_TOOL_FINGER); + mBtnToolPen = mDeviceContext.isKeyPressed(BTN_TOOL_PEN); + mBtnToolRubber = mDeviceContext.isKeyPressed(BTN_TOOL_RUBBER); + mBtnToolBrush = mDeviceContext.isKeyPressed(BTN_TOOL_BRUSH); + mBtnToolPencil = mDeviceContext.isKeyPressed(BTN_TOOL_PENCIL); + mBtnToolAirbrush = mDeviceContext.isKeyPressed(BTN_TOOL_AIRBRUSH); + mBtnToolMouse = mDeviceContext.isKeyPressed(BTN_TOOL_MOUSE); + mBtnToolLens = mDeviceContext.isKeyPressed(BTN_TOOL_LENS); + mBtnToolDoubleTap = mDeviceContext.isKeyPressed(BTN_TOOL_DOUBLETAP); + mBtnToolTripleTap = mDeviceContext.isKeyPressed(BTN_TOOL_TRIPLETAP); + mBtnToolQuadTap = mDeviceContext.isKeyPressed(BTN_TOOL_QUADTAP); + mHidUsageAccumulator.reset(); } void TouchButtonAccumulator::process(const RawEvent* rawEvent) { + mHidUsageAccumulator.process(*rawEvent); + if (rawEvent->type == EV_KEY) { switch (rawEvent->code) { case BTN_TOUCH: @@ -95,7 +100,29 @@ void TouchButtonAccumulator::process(const RawEvent* rawEvent) { case BTN_TOOL_QUADTAP: mBtnToolQuadTap = rawEvent->value; break; + default: + processMappedKey(rawEvent->code, rawEvent->value); } + return; + } +} + +void TouchButtonAccumulator::processMappedKey(int32_t scanCode, bool down) { + int32_t outKeyCode, outMetaState; + uint32_t outFlags; + if (mDeviceContext.mapKey(scanCode, mHidUsageAccumulator.consumeCurrentHidUsage(), + 0 /*metaState*/, &outKeyCode, &outMetaState, &outFlags) != OK) { + return; + } + switch (outKeyCode) { + case AKEYCODE_STYLUS_BUTTON_PRIMARY: + mBtnStylus = down; + break; + case AKEYCODE_STYLUS_BUTTON_SECONDARY: + mBtnStylus2 = down; + break; + default: + break; } } diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h index e19b7f87ff..65b0a62747 100644 --- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h @@ -17,6 +17,7 @@ #pragma once #include +#include "HidUsageAccumulator.h" namespace android { @@ -26,9 +27,11 @@ struct RawEvent; /* Keeps track of the state of touch, stylus and tool buttons. */ class TouchButtonAccumulator { public: - TouchButtonAccumulator() = default; - void configure(InputDeviceContext& deviceContext); - void reset(InputDeviceContext& deviceContext); + explicit TouchButtonAccumulator(InputDeviceContext& deviceContext) + : mDeviceContext(deviceContext){}; + + void configure(); + void reset(); void process(const RawEvent* rawEvent); @@ -56,6 +59,12 @@ private: bool mBtnToolDoubleTap{}; bool mBtnToolTripleTap{}; bool mBtnToolQuadTap{}; + + HidUsageAccumulator mHidUsageAccumulator{}; + + InputDeviceContext& mDeviceContext; + + void processMappedKey(int32_t scanCode, bool down); }; } // namespace android diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index e15d09f2ba..2142070a09 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -7338,6 +7338,7 @@ protected: void processSlot(MultiTouchInputMapper& mapper, int32_t slot); void processToolType(MultiTouchInputMapper& mapper, int32_t toolType); void processKey(MultiTouchInputMapper& mapper, int32_t code, int32_t value); + void processHidUsage(MultiTouchInputMapper& mapper, int32_t usageCode, int32_t value); void processMTSync(MultiTouchInputMapper& mapper); void processSync(MultiTouchInputMapper& mapper); }; @@ -7442,6 +7443,12 @@ void MultiTouchInputMapperTest::processKey(MultiTouchInputMapper& mapper, int32_ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, code, value); } +void MultiTouchInputMapperTest::processHidUsage(MultiTouchInputMapper& mapper, int32_t usageCode, + int32_t value) { + process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, usageCode); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UNKNOWN, value); +} + void MultiTouchInputMapperTest::processMTSync(MultiTouchInputMapper& mapper) { process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_MT_REPORT, 0); } @@ -8557,6 +8564,63 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllButtons) { ASSERT_EQ(0, motionArgs.buttonState); } +TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleMappedStylusButtons) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | ID | SLOT); + MultiTouchInputMapper& mapper = addMapperAndConfigure(); + + mFakeEventHub->addKey(EVENTHUB_ID, BTN_A, 0, AKEYCODE_STYLUS_BUTTON_PRIMARY, 0); + mFakeEventHub->addKey(EVENTHUB_ID, 0, 0xabcd, AKEYCODE_STYLUS_BUTTON_SECONDARY, 0); + + // Touch down. + processId(mapper, 1); + processPosition(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithButtonState(0)))); + + // Press and release button mapped to the primary stylus button. + processKey(mapper, BTN_A, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + + processKey(mapper, BTN_A, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithButtonState(0)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithButtonState(0)))); + + // Press and release the HID usage mapped to the secondary stylus button. + processHidUsage(mapper, 0xabcd, 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_SECONDARY)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_SECONDARY)))); + + processHidUsage(mapper, 0xabcd, 0); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithButtonState(0)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithButtonState(0)))); + + // Release touch. + processId(mapper, -1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0)))); +} + TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(DISPLAY_ORIENTATION_0); diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h index 97ba90b1df..5107af7e27 100644 --- a/services/inputflinger/tests/TestInputListenerMatchers.h +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -86,4 +86,9 @@ MATCHER_P(WithFlags, flags, "InputEvent with specified flags") { return arg.flags == flags; } +MATCHER_P(WithButtonState, buttons, "InputEvent with specified button state") { + *result_listener << "expected button state " << buttons << ", but got " << arg.buttonState; + return arg.buttonState == buttons; +} + } // namespace android -- GitLab From 4fe573983f3f0d4975fb7cbe66f9f24c5c6f91d7 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 25 Oct 2022 13:44:30 -0700 Subject: [PATCH 0424/1310] Do not send nextWakeupTime to findTouchedWindowTargetsLocked This function does not need this variable, so let's not send it, to prevent confusion. Bug: 211379801 Test: build only Change-Id: I487cac5de0cca72d5c55ed0943bd630661b7b5ce --- services/inputflinger/dispatcher/InputDispatcher.cpp | 7 +++---- services/inputflinger/dispatcher/InputDispatcher.h | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 694c127e40..ab73af9199 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -1692,8 +1692,7 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr< } std::vector touchedWindows = - findTouchedWindowTargetsLocked(currentTime, *entry, nextWakeupTime, - &conflictingPointerActions, + findTouchedWindowTargetsLocked(currentTime, *entry, &conflictingPointerActions, /*byref*/ injectionResult); for (const TouchedWindow& touchedWindow : touchedWindows) { LOG_ALWAYS_FATAL_IF(injectionResult != InputEventInjectionResult::SUCCEEDED, @@ -2036,8 +2035,8 @@ std::vector InputDispatcher::selectResponsiveMonitorsLocked( } std::vector InputDispatcher::findTouchedWindowTargetsLocked( - nsecs_t currentTime, const MotionEntry& entry, nsecs_t* nextWakeupTime, - bool* outConflictingPointerActions, InputEventInjectionResult& outInjectionResult) { + nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions, + InputEventInjectionResult& outInjectionResult) { ATRACE_CALL(); std::vector touchedWindows; diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index dea2caedf8..c2fe19614f 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -546,8 +546,7 @@ private: nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime, android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock); std::vector findTouchedWindowTargetsLocked( - nsecs_t currentTime, const MotionEntry& entry, nsecs_t* nextWakeupTime, - bool* outConflictingPointerActions, + nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions, android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock); std::vector selectResponsiveMonitorsLocked( const std::vector& gestureMonitors) const REQUIRES(mLock); -- GitLab From 989c51090b0b33192dd97653f61ce3944b580d22 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Fri, 28 Oct 2022 06:08:35 +0000 Subject: [PATCH 0425/1310] SF: Remove layer map from Client Cleanup before layer refbase removal. We can lookup the layer from the handle instead of maintaining a map. Bug: 238781169 Test: presubmit Change-Id: Ie6543e4c344359be6a221c164d193656e9190cc8 --- services/surfaceflinger/Client.cpp | 35 ++-------------------- services/surfaceflinger/Client.h | 9 ------ services/surfaceflinger/Layer.cpp | 5 ---- services/surfaceflinger/SurfaceFlinger.cpp | 5 ---- 4 files changed, 3 insertions(+), 51 deletions(-) diff --git a/services/surfaceflinger/Client.cpp b/services/surfaceflinger/Client.cpp index 7202bef606..bdbc79b8e1 100644 --- a/services/surfaceflinger/Client.cpp +++ b/services/surfaceflinger/Client.cpp @@ -25,6 +25,7 @@ #include "Client.h" #include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/LayerHandle.h" #include "Layer.h" #include "SurfaceFlinger.h" @@ -47,36 +48,6 @@ status_t Client::initCheck() const { return NO_ERROR; } -void Client::attachLayer(const sp& handle, const sp& layer) -{ - Mutex::Autolock _l(mLock); - mLayers.add(handle, layer); -} - -void Client::detachLayer(const Layer* layer) -{ - Mutex::Autolock _l(mLock); - // we do a linear search here, because this doesn't happen often - const size_t count = mLayers.size(); - for (size_t i=0 ; i Client::getLayerUser(const sp& handle) const -{ - Mutex::Autolock _l(mLock); - sp lbc; - wp layer(mLayers.valueFor(handle)); - if (layer != 0) { - lbc = layer.promote(); - ALOGE_IF(lbc==0, "getLayerUser(name=%p) is dead", handle.get()); - } - return lbc; -} - binder::Status Client::createSurface(const std::string& name, int32_t flags, const sp& parent, const gui::LayerMetadata& metadata, gui::CreateSurfaceResult* outResult) { @@ -91,7 +62,7 @@ binder::Status Client::createSurface(const std::string& name, int32_t flags, binder::Status Client::clearLayerFrameStats(const sp& handle) { status_t status; - sp layer = getLayerUser(handle); + sp layer = LayerHandle::getLayer(handle); if (layer == nullptr) { status = NAME_NOT_FOUND; } else { @@ -103,7 +74,7 @@ binder::Status Client::clearLayerFrameStats(const sp& handle) { binder::Status Client::getLayerFrameStats(const sp& handle, gui::FrameStats* outStats) { status_t status; - sp layer = getLayerUser(handle); + sp layer = LayerHandle::getLayer(handle); if (layer == nullptr) { status = NAME_NOT_FOUND; } else { diff --git a/services/surfaceflinger/Client.h b/services/surfaceflinger/Client.h index 02079a3b3f..af410ea19c 100644 --- a/services/surfaceflinger/Client.h +++ b/services/surfaceflinger/Client.h @@ -38,12 +38,6 @@ public: status_t initCheck() const; - // protected by SurfaceFlinger::mStateLock - void attachLayer(const sp& handle, const sp& layer); - void detachLayer(const Layer* layer); - - sp getLayerUser(const sp& handle) const; - private: // ISurfaceComposerClient interface @@ -64,9 +58,6 @@ private: // constant sp mFlinger; - // protected by mLock - DefaultKeyedVector< wp, wp > mLayers; - // thread-safe mutable Mutex mLock; }; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 977709286b..97e47e8e68 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -240,11 +240,6 @@ Layer::~Layer() { mFlinger->mTimeStats->onDestroy(layerId); mFlinger->mFrameTracer->onDestroy(layerId); - sp c(mClientRef.promote()); - if (c != 0) { - c->detachLayer(this); - } - mFrameTracker.logAndResetStats(mName); mFlinger->onLayerDestroyed(this); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index cfebec70cb..e2ca129587 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3640,11 +3640,6 @@ status_t SurfaceFlinger::addClientLayer(const LayerCreationArgs& args, const sp< mCreatedLayers.emplace_back(layer, parent, args.addToRoot); } - // attach this layer to the client - if (args.client != nullptr) { - args.client->attachLayer(handle, layer); - } - setTransactionFlags(eTransactionNeeded); return NO_ERROR; } -- GitLab From 3d398a089bd8b98b702a778f420f328fee30befe Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Fri, 28 Oct 2022 09:03:37 +0000 Subject: [PATCH 0426/1310] Clean up PowerAdvisor callbacks in Display Move passing skippedValidate info to the rest of the callbacks, and send "requiresClientComposition" information as early as possible to make potential load change hints easier. Test: manual Change-Id: I28ac3c9826ed2bad8c304eb38c9b92c9160c3934 --- .../CompositionEngine/src/Display.cpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp index 0b69d44ac4..1c5cbedd88 100644 --- a/services/surfaceflinger/CompositionEngine/src/Display.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp @@ -248,12 +248,17 @@ bool Display::chooseCompositionStrategy( return false; } - const TimePoint startTime = TimePoint::now(); - // Get any composition changes requested by the HWC device, and apply them. std::optional changes; auto& hwc = getCompositionEngine().getHwComposer(); const bool requiresClientComposition = anyLayersRequireClientComposition(); + + if (isPowerHintSessionEnabled()) { + mPowerAdvisor->setRequiresClientComposition(mId, requiresClientComposition); + } + + const TimePoint hwcValidateStartTime = TimePoint::now(); + if (status_t result = hwc.getDeviceCompositionChanges(*halDisplayId, requiresClientComposition, getState().earliestPresentTime, @@ -266,8 +271,10 @@ bool Display::chooseCompositionStrategy( } if (isPowerHintSessionEnabled()) { - mPowerAdvisor->setHwcValidateTiming(mId, startTime, TimePoint::now()); - mPowerAdvisor->setRequiresClientComposition(mId, requiresClientComposition); + mPowerAdvisor->setHwcValidateTiming(mId, hwcValidateStartTime, TimePoint::now()); + if (auto halDisplayId = HalDisplayId::tryCast(mId)) { + mPowerAdvisor->setSkippedValidate(mId, hwc.getValidateSkipped(*halDisplayId)); + } } return true; @@ -432,13 +439,6 @@ void Display::finishFrame(const compositionengine::CompositionRefreshArgs& refre } impl::Output::finishFrame(refreshArgs, std::move(result)); - - if (isPowerHintSessionEnabled()) { - auto& hwc = getCompositionEngine().getHwComposer(); - if (auto halDisplayId = HalDisplayId::tryCast(mId)) { - mPowerAdvisor->setSkippedValidate(mId, hwc.getValidateSkipped(*halDisplayId)); - } - } } } // namespace android::compositionengine::impl -- GitLab From 9d77a5cf9d05b87b547f139dedf50ccfbebb140e Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Fri, 28 Oct 2022 06:31:21 +0000 Subject: [PATCH 0427/1310] SF: Use layer id for region sampling stop layer Cleanup before layer refbase removal. We can lookup the stop layer by using a unique id. Bug: 238781169 Test: presubmit Change-Id: I33da6899adebc33c814656591f78187f08c53e80 --- services/surfaceflinger/RegionSamplingThread.cpp | 9 +++++---- services/surfaceflinger/RegionSamplingThread.h | 5 +++-- services/surfaceflinger/SurfaceFlinger.cpp | 7 ++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp index 8dd3b0f59d..6e64e0a13b 100644 --- a/services/surfaceflinger/RegionSamplingThread.cpp +++ b/services/surfaceflinger/RegionSamplingThread.cpp @@ -40,6 +40,7 @@ #include "DisplayDevice.h" #include "DisplayRenderArea.h" +#include "FrontEnd/LayerCreationArgs.h" #include "Layer.h" #include "Scheduler/VsyncController.h" #include "SurfaceFlinger.h" @@ -129,12 +130,12 @@ RegionSamplingThread::~RegionSamplingThread() { } } -void RegionSamplingThread::addListener(const Rect& samplingArea, const wp& stopLayer, +void RegionSamplingThread::addListener(const Rect& samplingArea, uint32_t stopLayerId, const sp& listener) { sp asBinder = IInterface::asBinder(listener); asBinder->linkToDeath(sp::fromExisting(this)); std::lock_guard lock(mSamplingMutex); - mDescriptors.emplace(wp(asBinder), Descriptor{samplingArea, stopLayer, listener}); + mDescriptors.emplace(wp(asBinder), Descriptor{samplingArea, stopLayerId, listener}); } void RegionSamplingThread::removeListener(const sp& listener) { @@ -291,8 +292,8 @@ void RegionSamplingThread::captureSample() { if (stopLayerFound) return; // Likewise if we just found a stop layer, set the flag and abort - for (const auto& [area, stopLayer, listener] : descriptors) { - if (layer == stopLayer.promote().get()) { + for (const auto& [area, stopLayerId, listener] : descriptors) { + if (stopLayerId != UNASSIGNED_LAYER_ID && layer->getSequence() == stopLayerId) { stopLayerFound = true; return; } diff --git a/services/surfaceflinger/RegionSamplingThread.h b/services/surfaceflinger/RegionSamplingThread.h index 686b4b1e1f..b62b15cb6d 100644 --- a/services/surfaceflinger/RegionSamplingThread.h +++ b/services/surfaceflinger/RegionSamplingThread.h @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -73,7 +74,7 @@ public: // Add a listener to receive luma notifications. The luma reported via listener will // report the median luma for the layers under the stopLayerHandle, in the samplingArea region. - void addListener(const Rect& samplingArea, const wp& stopLayer, + void addListener(const Rect& samplingArea, uint32_t stopLayerId, const sp& listener); // Remove the listener to stop receiving median luma notifications. void removeListener(const sp& listener); @@ -87,7 +88,7 @@ public: private: struct Descriptor { Rect area = Rect::EMPTY_RECT; - wp stopLayer; + uint32_t stopLayerId; sp listener; }; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index cfebec70cb..8fefcb1a62 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1580,9 +1580,10 @@ status_t SurfaceFlinger::addRegionSamplingListener(const Rect& samplingArea, // LayerHandle::getLayer promotes the layer object in a binder thread but we will not destroy // the layer here since the caller has a strong ref to the layer's handle. - // TODO (b/238781169): replace layer with layer id - const wp stopLayer = LayerHandle::getLayer(stopLayerHandle); - mRegionSamplingThread->addListener(samplingArea, stopLayer, listener); + const sp stopLayer = LayerHandle::getLayer(stopLayerHandle); + mRegionSamplingThread->addListener(samplingArea, + stopLayer ? stopLayer->getSequence() : UNASSIGNED_LAYER_ID, + listener); return NO_ERROR; } -- GitLab From 790921855c4657937ca41cc505b9a0055ee7847f Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Sat, 29 Oct 2022 00:52:54 +0000 Subject: [PATCH 0428/1310] graphics.common V3 -> V4 Bug: 251177105 Test: builds Change-Id: I59a458554223fdbf01b2f0288b249ae8299af6fd --- libs/gralloc/types/Android.bp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gralloc/types/Android.bp b/libs/gralloc/types/Android.bp index 3d81c3292a..6d1dfe8124 100644 --- a/libs/gralloc/types/Android.bp +++ b/libs/gralloc/types/Android.bp @@ -58,7 +58,7 @@ cc_library { ], export_shared_lib_headers: [ - "android.hardware.graphics.common-V3-ndk", + "android.hardware.graphics.common-V4-ndk", "android.hardware.graphics.mapper@4.0", "libhidlbase", ], -- GitLab From 2e7c8953727e956ab12ccbec66239b6563653849 Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Wed, 21 Sep 2022 07:34:10 +0000 Subject: [PATCH 0429/1310] Add sendHint to performance_hint header and update builds Add sendHint functionality to JNI by incrementing powermanager AIDL version and adding sendHint method to the performance_hint header. Bug: b/243973548 Test: manual Change-Id: Ib449706dc5540942cb166df1ce861f8a1acb1b43 --- include/android/performance_hint.h | 41 +++++++++++++++++++++ services/powermanager/Android.bp | 2 +- services/powermanager/benchmarks/Android.bp | 2 +- services/powermanager/tests/Android.bp | 2 +- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h index 5fa47f64be..eed6b3339f 100644 --- a/include/android/performance_hint.h +++ b/include/android/performance_hint.h @@ -87,6 +87,36 @@ typedef struct APerformanceHintManager APerformanceHintManager; */ typedef struct APerformanceHintSession APerformanceHintSession; +/** + * Hints for the session used by {@link APerformanceHint_sendHint} to signal upcoming changes + * in the mode or workload. + */ +enum SessionHint { + /** + * This hint indicates a sudden increase in CPU workload intensity. It means + * that this hint session needs extra CPU resources immediately to meet the + * target duration for the current work cycle. + */ + CPU_LOAD_UP = 0, + /** + * This hint indicates a decrease in CPU workload intensity. It means that + * this hint session can reduce CPU resources and still meet the target duration. + */ + CPU_LOAD_DOWN = 1, + /* + * This hint indicates an upcoming CPU workload that is completely changed and + * unknown. It means that the hint session should reset CPU resources to a known + * baseline to prepare for an arbitrary load, and must wake up if inactive. + */ + CPU_LOAD_RESET = 2, + /* + * This hint indicates that the most recent CPU workload is resuming after a + * period of inactivity. It means that the hint session should allocate similar + * CPU resources to what was used previously, and must wake up if inactive. + */ + CPU_LOAD_RESUME = 3, +}; + /** * Acquire an instance of the performance hint manager. * @@ -159,6 +189,17 @@ int APerformanceHint_reportActualWorkDuration( void APerformanceHint_closeSession( APerformanceHintSession* session) __INTRODUCED_IN(__ANDROID_API_T__); +/** + * Sends performance hints to inform the hint session of changes in the workload. + * + * @param session The performance hint session instance to update. + * @param hint The hint to send to the session. + * @return 0 on success + * EPIPE if communication with the system service has failed. + */ +int APerformanceHint_sendHint( + APerformanceHintSession* session, int hint) __INTRODUCED_IN(__ANDROID_API_U__); + __END_DECLS #endif // ANDROID_NATIVE_PERFORMANCE_HINT_H diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp index b7de61982e..7fb33e5dcf 100644 --- a/services/powermanager/Android.bp +++ b/services/powermanager/Android.bp @@ -40,7 +40,7 @@ cc_library_shared { "android.hardware.power@1.1", "android.hardware.power@1.2", "android.hardware.power@1.3", - "android.hardware.power-V3-cpp", + "android.hardware.power-V4-cpp", ], cflags: [ diff --git a/services/powermanager/benchmarks/Android.bp b/services/powermanager/benchmarks/Android.bp index 0286a8160c..4343aec227 100644 --- a/services/powermanager/benchmarks/Android.bp +++ b/services/powermanager/benchmarks/Android.bp @@ -40,7 +40,7 @@ cc_benchmark { "android.hardware.power@1.1", "android.hardware.power@1.2", "android.hardware.power@1.3", - "android.hardware.power-V3-cpp", + "android.hardware.power-V4-cpp", ], static_libs: [ "libtestUtil", diff --git a/services/powermanager/tests/Android.bp b/services/powermanager/tests/Android.bp index eec6801b1f..54dffcf817 100644 --- a/services/powermanager/tests/Android.bp +++ b/services/powermanager/tests/Android.bp @@ -51,7 +51,7 @@ cc_test { "android.hardware.power@1.1", "android.hardware.power@1.2", "android.hardware.power@1.3", - "android.hardware.power-V3-cpp", + "android.hardware.power-V4-cpp", ], static_libs: [ "libgmock", -- GitLab From 2558041f7153f9c4c5598250b573da26793c2652 Mon Sep 17 00:00:00 2001 From: Ben Wagner Date: Mon, 31 Oct 2022 19:18:31 +0000 Subject: [PATCH 0430/1310] Typedef font structs Use the `struct S; typedef struct S S;` pattern in NDK font headers. This allows these headers to compile in C and matches the pattern used by other NDK headers. Change-Id: I912b2a517931eabbf2405f322ee1f5ddb53dd19f --- include/android/font.h | 3 ++- include/android/font_matcher.h | 3 ++- include/android/system_fonts.h | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/include/android/font.h b/include/android/font.h index 45eb81a533..022572535b 100644 --- a/include/android/font.h +++ b/include/android/font.h @@ -87,10 +87,11 @@ enum { AFONT_WEIGHT_MAX = 1000 }; +struct AFont; /** * AFont provides information of the single font configuration. */ -struct AFont; +typedef struct AFont AFont; /** * Close an AFont. diff --git a/include/android/font_matcher.h b/include/android/font_matcher.h index 63b0328749..60ff95e123 100644 --- a/include/android/font_matcher.h +++ b/include/android/font_matcher.h @@ -117,11 +117,12 @@ enum { AFAMILY_VARIANT_ELEGANT = 2, }; +struct AFontMatcher; /** * AFontMatcher performs match operation on given parameters and available font files. * This matcher is not a thread-safe object. Do not pass this matcher to other threads. */ -struct AFontMatcher; +typedef struct AFontMatcher AFontMatcher; /** * Select the best font from given parameters. diff --git a/include/android/system_fonts.h b/include/android/system_fonts.h index b0bbb954a9..94484eaf54 100644 --- a/include/android/system_fonts.h +++ b/include/android/system_fonts.h @@ -87,13 +87,14 @@ __BEGIN_DECLS +struct ASystemFontIterator; /** * ASystemFontIterator provides access to the system font configuration. * * ASystemFontIterator is an iterator for all available system font settings. * This iterator is not a thread-safe object. Do not pass this iterator to other threads. */ -struct ASystemFontIterator; +typedef struct ASystemFontIterator ASystemFontIterator; /** * Create a system font iterator. -- GitLab From 0a525a4567bca5917bba8808edb451e16c60d8bf Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Wed, 26 Oct 2022 20:20:50 +0000 Subject: [PATCH 0431/1310] SF: Change RenderEngine ownership This change allows multiple instances of CompositionEngine to use the same RenderEngine instance which enables creating new instances of CompositionEngine to run off the main thread. This will be used in the refactor of SurfaceFlinger::renderScreenImpl to generate LayerSettings via CompositionEngine. Bug: 238643986 Test: presubmits Change-Id: I0c8dc30e091578d0d77afb434a52038db6e82288 --- .../include/compositionengine/CompositionEngine.h | 2 +- .../include/compositionengine/impl/CompositionEngine.h | 4 ++-- .../include/compositionengine/mock/CompositionEngine.h | 2 +- .../CompositionEngine/src/CompositionEngine.cpp | 6 +++--- .../CompositionEngine/tests/CompositionEngineTest.cpp | 7 +++---- services/surfaceflinger/SurfaceFlinger.cpp | 5 +++-- services/surfaceflinger/SurfaceFlinger.h | 1 + .../surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h | 7 ++++--- .../tests/unittests/TestableSurfaceFlinger.h | 7 ++++--- 9 files changed, 22 insertions(+), 19 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h index 6832ae12df..8661063e12 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h @@ -56,7 +56,7 @@ public: virtual void setHwComposer(std::unique_ptr) = 0; virtual renderengine::RenderEngine& getRenderEngine() const = 0; - virtual void setRenderEngine(std::unique_ptr) = 0; + virtual void setRenderEngine(renderengine::RenderEngine*) = 0; virtual TimeStats& getTimeStats() const = 0; virtual void setTimeStats(const std::shared_ptr&) = 0; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h index dd4dbe9318..2af6c80f1e 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h @@ -34,7 +34,7 @@ public: void setHwComposer(std::unique_ptr) override; renderengine::RenderEngine& getRenderEngine() const override; - void setRenderEngine(std::unique_ptr) override; + void setRenderEngine(renderengine::RenderEngine*) override; TimeStats& getTimeStats() const override; void setTimeStats(const std::shared_ptr&) override; @@ -58,7 +58,7 @@ public: private: std::unique_ptr mHwComposer; - std::unique_ptr mRenderEngine; + renderengine::RenderEngine* mRenderEngine; std::shared_ptr mTimeStats; bool mNeedsAnotherUpdate = false; nsecs_t mRefreshStartTime = 0; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h index a48cc6f975..fc71649949 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h @@ -40,7 +40,7 @@ public: MOCK_METHOD1(setHwComposer, void(std::unique_ptr)); MOCK_CONST_METHOD0(getRenderEngine, renderengine::RenderEngine&()); - MOCK_METHOD1(setRenderEngine, void(std::unique_ptr)); + MOCK_METHOD1(setRenderEngine, void(renderengine::RenderEngine*)); MOCK_CONST_METHOD0(getTimeStats, TimeStats&()); MOCK_METHOD1(setTimeStats, void(const std::shared_ptr&)); diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp index a4e1fff2ad..e42f11a9b9 100644 --- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp +++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp @@ -65,11 +65,11 @@ void CompositionEngine::setHwComposer(std::unique_ptr hwComposer) { } renderengine::RenderEngine& CompositionEngine::getRenderEngine() const { - return *mRenderEngine.get(); + return *mRenderEngine; } -void CompositionEngine::setRenderEngine(std::unique_ptr renderEngine) { - mRenderEngine = std::move(renderEngine); +void CompositionEngine::setRenderEngine(renderengine::RenderEngine* renderEngine) { + mRenderEngine = renderEngine; } TimeStats& CompositionEngine::getTimeStats() const { diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp index b570979f1f..8d99f894a6 100644 --- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp @@ -62,11 +62,10 @@ TEST_F(CompositionEngineTest, canSetHWComposer) { } TEST_F(CompositionEngineTest, canSetRenderEngine) { - renderengine::mock::RenderEngine* renderEngine = - new StrictMock(); - mEngine.setRenderEngine(std::unique_ptr(renderEngine)); + auto renderEngine = std::make_unique>(); + mEngine.setRenderEngine(renderEngine.get()); - EXPECT_EQ(renderEngine, &mEngine.getRenderEngine()); + EXPECT_EQ(renderEngine.get(), &mEngine.getRenderEngine()); } TEST_F(CompositionEngineTest, canSetTimeStats) { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index cfebec70cb..610732e8ab 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -602,7 +602,7 @@ HWComposer& SurfaceFlinger::getHwComposer() const { } renderengine::RenderEngine& SurfaceFlinger::getRenderEngine() const { - return mCompositionEngine->getRenderEngine(); + return *mRenderEngine; } compositionengine::CompositionEngine& SurfaceFlinger::getCompositionEngine() const { @@ -763,7 +763,8 @@ void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { if (auto type = chooseRenderEngineTypeViaSysProp()) { builder.setRenderEngineType(type.value()); } - mCompositionEngine->setRenderEngine(renderengine::RenderEngine::create(builder.build())); + mRenderEngine = renderengine::RenderEngine::create(builder.build()); + mCompositionEngine->setRenderEngine(mRenderEngine.get()); mMaxRenderTargetSize = std::min(getRenderEngine().getMaxTextureSize(), getRenderEngine().getMaxViewportDims()); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 85c194bbb5..1009402d8d 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1248,6 +1248,7 @@ private: ui::Dataspace mColorSpaceAgnosticDataspace; float mDimmingRatio = -1.f; + std::unique_ptr mRenderEngine; std::unique_ptr mCompositionEngine; // mMaxRenderTargetSize is only set once in init() so it doesn't need to be protected by // any mutex. diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index e55586774f..5826d83af1 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -633,7 +633,8 @@ public: } void setupRenderEngine(std::unique_ptr renderEngine) { - mFlinger->mCompositionEngine->setRenderEngine(std::move(renderEngine)); + mFlinger->mRenderEngine = std::move(renderEngine); + mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get()); } void setupComposer(std::unique_ptr composer) { @@ -775,8 +776,8 @@ public: mutableDrawingState().displays.clear(); mFlinger->mScheduler.reset(); mFlinger->mCompositionEngine->setHwComposer(std::unique_ptr()); - mFlinger->mCompositionEngine->setRenderEngine( - std::unique_ptr()); + mFlinger->mRenderEngine = std::unique_ptr(); + mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get()); } private: diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 7f471bc8b8..31df4c675e 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -168,7 +168,8 @@ public: // functions. void setupRenderEngine(std::unique_ptr renderEngine) { - mFlinger->mCompositionEngine->setRenderEngine(std::move(renderEngine)); + mFlinger->mRenderEngine = std::move(renderEngine); + mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get()); } void setupComposer(std::unique_ptr composer) { @@ -543,8 +544,8 @@ public: mutableDrawingState().displays.clear(); mFlinger->mScheduler.reset(); mFlinger->mCompositionEngine->setHwComposer(std::unique_ptr()); - mFlinger->mCompositionEngine->setRenderEngine( - std::unique_ptr()); + mFlinger->mRenderEngine = std::unique_ptr(); + mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get()); } /* ------------------------------------------------------------------------ -- GitLab From ae4d05384bebad96510969e859dc2af1bd6b81c4 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 31 Oct 2022 17:50:04 +0000 Subject: [PATCH 0432/1310] Use std::array and default copy assignment for PointerCoords Bug: 245989146 Test: atest inputflinger_tests Change-Id: I5e008d03184204a2f34f369e2d5958f6cd4de952 --- include/input/Input.h | 5 +++-- libs/input/Input.cpp | 8 -------- services/inputflinger/InputCommonConverter.cpp | 4 ++-- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/include/input/Input.h b/include/input/Input.h index 172e5b46d8..dd74a51e5e 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -366,7 +366,7 @@ struct PointerCoords { // Values of axes that are stored in this structure packed in order by axis id // for each axis that is present in the structure according to 'bits'. - float values[MAX_AXES]; + std::array values; inline void clear() { BitSet64::clear(bits); @@ -406,7 +406,8 @@ struct PointerCoords { return !(*this == other); } - void copyFrom(const PointerCoords& other); + inline void copyFrom(const PointerCoords& other) { *this = other; } + PointerCoords& operator=(const PointerCoords&) = default; private: void tooManyAxes(int axis); diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index c1eb8e2a12..cf5a7e7b05 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -438,14 +438,6 @@ bool PointerCoords::operator==(const PointerCoords& other) const { return true; } -void PointerCoords::copyFrom(const PointerCoords& other) { - bits = other.bits; - uint32_t count = BitSet64::count(bits); - for (uint32_t i = 0; i < count; i++) { - values[i] = other.values[i]; - } -} - void PointerCoords::transform(const ui::Transform& transform) { const vec2 xy = transform.transform(getXYValue()); setAxisValue(AMOTION_EVENT_AXIS_X, xy.x); diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp index 6db89d4759..628ce6fc9a 100644 --- a/services/inputflinger/InputCommonConverter.cpp +++ b/services/inputflinger/InputCommonConverter.cpp @@ -304,8 +304,8 @@ static void getHalPropertiesAndCoords(const NotifyMotionArgs& args, common::PointerCoords coords; // OK to copy bits because we have static_assert for pointerCoords axes coords.bits = args.pointerCoords[i].bits; - coords.values = std::vector(args.pointerCoords[i].values, - args.pointerCoords[i].values + + coords.values = std::vector(args.pointerCoords[i].values.cbegin(), + args.pointerCoords[i].values.cbegin() + BitSet64::count(args.pointerCoords[i].bits)); outPointerCoords.push_back(coords); } -- GitLab From 211ba627f011fb25c4457a12bee7919c93334361 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 31 Oct 2022 21:09:21 +0000 Subject: [PATCH 0433/1310] Use the last cooked touch state for generating button release events This bug was exposed by the refactor in ag/20068126 that cleans up the CookedPointerState by ensuring it is always zero-initialized. Button release events are generated before the touch state change events are generated. If the touch goes up before or at the same time as the button goes up, the "current" touch state will have no pointers. The old logic got around this by using the getting the values for the "last" touch state's pointers from the "current" touch state. This wrong, because the "last" state's pointers no longer exist in the "current" state. However, because the CookedState copying logic never reset the underlying PointerCoords data after the pointer is lifted, the "last" state's pointer's values were essentially preserved in the "current" state in some cases. This meant the expected behavior was observed most of the time even though the logic was incorrect. This CL corrects the button release event generation logic to ensure that "last" state is used when generating those events. Bug: 245989146 Test: atest inputflinger_tests Change-Id: I9889e7f816f4ba885eae783ebf4c7721eb60db3a --- .../reader/mapper/TouchInputMapper.cpp | 6 +-- .../inputflinger/tests/InputReader_test.cpp | 38 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 615889ebe3..0517d10fdc 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -2159,9 +2159,9 @@ std::list TouchInputMapper::dispatchButtonRelease(nsecs_t when, nsec out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0, metaState, buttonState, 0, - mCurrentCookedState.cookedPointerData.pointerProperties, - mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, idBits, -1, + mLastCookedState.cookedPointerData.pointerProperties, + mLastCookedState.cookedPointerData.pointerCoords, + mLastCookedState.cookedPointerData.idToIndex, idBits, -1, mOrientedXPrecision, mOrientedYPrecision, mDownTime, MotionClassification::NONE)); } diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index f333306f26..54bd49538a 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -7112,6 +7112,44 @@ TEST_F(SingleTouchInputMapperTest, ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasNotCalled()); } +TEST_F(SingleTouchInputMapperTest, ButtonIsReleasedOnTouchUp) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + prepareAxes(POSITION); + SingleTouchInputMapper& mapper = addMapperAndConfigure(); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); + + // Press a stylus button. + processKey(mapper, BTN_STYLUS, 1); + processSync(mapper); + + // Start a touch gesture and ensure the BUTTON_PRESS event is generated. + processDown(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(toDisplayX(100), toDisplayY(200)), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithCoords(toDisplayX(100), toDisplayY(200)), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + + // Release the touch gesture. Ensure that the BUTTON_RELEASE event is generated even though + // the button has not actually been released, since there will be no pointers through which the + // button state can be reported. The event is generated at the location of the pointer before + // it went up. + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithCoords(toDisplayX(100), toDisplayY(200)), WithButtonState(0)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(toDisplayX(100), toDisplayY(200)), WithButtonState(0)))); +} + // --- TouchDisplayProjectionTest --- class TouchDisplayProjectionTest : public SingleTouchInputMapperTest { -- GitLab From d6ccedb2282b6653f60de94d8924ecd6f2cd72e7 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 27 Sep 2022 21:04:06 +0000 Subject: [PATCH 0434/1310] TouchInputMapper: Rely on default c'tor and copy c'tors for structs Manually defining copyFrom(), reset(), and clear() operations on structs that should be POD types is errorprone, since each member must be copied or reinitialized correctly in all of them. Also, default-initialization can leave many of these non-class values in an undefined state. Instead, we can rely on initializing members wherever they are defined, which allows us to remove manually added c'tors and copy functions and rely on the compiler generated equivalents. This also reduces the liklihood of issues arising because of uninitialized data. We prefer brace initialization over other forms of initialization. Using braces for all types results in the most consistent behavior of calling the default c'tor for class types, and 0-initializing non class types. Bug: 245989146 Test: atest inputflinger_tests Change-Id: I0b34480ec441bd06097a626ff4c6dc5d2e046e38 --- .../inputflinger/reader/include/EventHub.h | 25 +-- .../reader/mapper/TouchInputMapper.cpp | 154 ++++----------- .../reader/mapper/TouchInputMapper.h | 181 +++++++----------- 3 files changed, 120 insertions(+), 240 deletions(-) diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index 6933ec7399..8e5f15f338 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -67,22 +67,15 @@ struct RawEvent { /* Describes an absolute axis. */ struct RawAbsoluteAxisInfo { - bool valid; // true if the information is valid, false otherwise - - int32_t minValue; // minimum value - int32_t maxValue; // maximum value - int32_t flat; // center flat position, eg. flat == 8 means center is between -8 and 8 - int32_t fuzz; // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise - int32_t resolution; // resolution in units per mm or radians per mm - - inline void clear() { - valid = false; - minValue = 0; - maxValue = 0; - flat = 0; - fuzz = 0; - resolution = 0; - } + bool valid{false}; // true if the information is valid, false otherwise + + int32_t minValue{}; // minimum value + int32_t maxValue{}; // maximum value + int32_t flat{}; // center flat position, eg. flat == 8 means center is between -8 and 8 + int32_t fuzz{}; // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise + int32_t resolution{}; // resolution in units per mm or radians per mm + + inline void clear() { *this = RawAbsoluteAxisInfo(); } }; /* diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 0517d10fdc..e8cf139e33 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -73,53 +73,8 @@ inline static int32_t signExtendNybble(int32_t value) { return value >= 8 ? value - 16 : value; } -// --- RawPointerAxes --- - -RawPointerAxes::RawPointerAxes() { - clear(); -} - -void RawPointerAxes::clear() { - x.clear(); - y.clear(); - pressure.clear(); - touchMajor.clear(); - touchMinor.clear(); - toolMajor.clear(); - toolMinor.clear(); - orientation.clear(); - distance.clear(); - tiltX.clear(); - tiltY.clear(); - trackingId.clear(); - slot.clear(); -} - // --- RawPointerData --- -RawPointerData::RawPointerData() { - clear(); -} - -void RawPointerData::clear() { - pointerCount = 0; - clearIdBits(); -} - -void RawPointerData::copyFrom(const RawPointerData& other) { - pointerCount = other.pointerCount; - hoveringIdBits = other.hoveringIdBits; - touchingIdBits = other.touchingIdBits; - canceledIdBits = other.canceledIdBits; - - for (uint32_t i = 0; i < pointerCount; i++) { - pointers[i] = other.pointers[i]; - - int id = pointers[i].id; - idToIndex[id] = other.idToIndex[id]; - } -} - void RawPointerData::getCentroidOfTouchingPointers(float* outX, float* outY) const { float x = 0, y = 0; uint32_t count = touchingIdBits.count(); @@ -137,35 +92,6 @@ void RawPointerData::getCentroidOfTouchingPointers(float* outX, float* outY) con *outY = y; } -// --- CookedPointerData --- - -CookedPointerData::CookedPointerData() { - clear(); -} - -void CookedPointerData::clear() { - pointerCount = 0; - hoveringIdBits.clear(); - touchingIdBits.clear(); - canceledIdBits.clear(); - validIdBits.clear(); -} - -void CookedPointerData::copyFrom(const CookedPointerData& other) { - pointerCount = other.pointerCount; - hoveringIdBits = other.hoveringIdBits; - touchingIdBits = other.touchingIdBits; - validIdBits = other.validIdBits; - - for (uint32_t i = 0; i < pointerCount; i++) { - pointerProperties[i].copyFrom(other.pointerProperties[i]); - pointerCoords[i].copyFrom(other.pointerCoords[i]); - - int id = pointerProperties[i].id; - idToIndex[id] = other.idToIndex[id]; - } -} - // --- TouchInputMapper --- TouchInputMapper::TouchInputMapper(InputDeviceContext& deviceContext) @@ -1582,7 +1508,7 @@ std::list TouchInputMapper::processRawTouches(bool timeout) { // All ready to go. clearStylusDataPendingFlags(); - mCurrentRawState.copyFrom(next); + mCurrentRawState = next; if (mCurrentRawState.when < mLastRawState.when) { mCurrentRawState.when = mLastRawState.when; mCurrentRawState.readTime = mLastRawState.readTime; @@ -1597,7 +1523,7 @@ std::list TouchInputMapper::processRawTouches(bool timeout) { if (timeout) { nsecs_t when = mExternalStylusFusionTimeout - STYLUS_DATA_LATENCY; clearStylusDataPendingFlags(); - mCurrentRawState.copyFrom(mLastRawState); + mCurrentRawState = mLastRawState; ALOGD_IF(DEBUG_STYLUS_FUSION, "Timeout expired, synthesizing event with new stylus data"); const nsecs_t readTime = when; // consider this synthetic event to be zero latency @@ -1722,8 +1648,8 @@ std::list TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t re mCurrentRawState.rawHScroll = 0; // Copy current touch to last touch in preparation for the next cycle. - mLastRawState.copyFrom(mCurrentRawState); - mLastCookedState.copyFrom(mCurrentCookedState); + mLastRawState = mCurrentRawState; + mLastCookedState = mCurrentCookedState; return out; } @@ -1743,8 +1669,8 @@ void TouchInputMapper::updateTouchSpots() { mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); mPointerController->setButtonState(mCurrentRawState.buttonState); - mPointerController->setSpots(mCurrentCookedState.cookedPointerData.pointerCoords, - mCurrentCookedState.cookedPointerData.idToIndex, + mPointerController->setSpots(mCurrentCookedState.cookedPointerData.pointerCoords.cbegin(), + mCurrentCookedState.cookedPointerData.idToIndex.cbegin(), mCurrentCookedState.cookedPointerData.touchingIdBits, mViewport.displayId); } @@ -1992,6 +1918,36 @@ std::list TouchInputMapper::abortTouches(nsecs_t when, nsecs_t readT return out; } +// Updates pointer coords and properties for pointers with specified ids that have moved. +// Returns true if any of them changed. +static bool updateMovedPointers(const PropertiesArray& inProperties, CoordsArray& inCoords, + const IdToIndexArray& inIdToIndex, PropertiesArray& outProperties, + CoordsArray& outCoords, IdToIndexArray& outIdToIndex, + BitSet32 idBits) { + bool changed = false; + while (!idBits.isEmpty()) { + uint32_t id = idBits.clearFirstMarkedBit(); + uint32_t inIndex = inIdToIndex[id]; + uint32_t outIndex = outIdToIndex[id]; + + const PointerProperties& curInProperties = inProperties[inIndex]; + const PointerCoords& curInCoords = inCoords[inIndex]; + PointerProperties& curOutProperties = outProperties[outIndex]; + PointerCoords& curOutCoords = outCoords[outIndex]; + + if (curInProperties != curOutProperties) { + curOutProperties.copyFrom(curInProperties); + changed = true; + } + + if (curInCoords != curOutCoords) { + curOutCoords.copyFrom(curInCoords); + changed = true; + } + } + return changed; +} + std::list TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { std::list out; @@ -2538,8 +2494,8 @@ std::list TouchInputMapper::dispatchPointerGestures(nsecs_t when, ns } if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) { - mPointerController->setSpots(mPointerGesture.currentGestureCoords, - mPointerGesture.currentGestureIdToIndex, + mPointerController->setSpots(mPointerGesture.currentGestureCoords.cbegin(), + mPointerGesture.currentGestureIdToIndex.cbegin(), mPointerGesture.currentGestureIdBits, mPointerController->getDisplayId()); } @@ -3742,8 +3698,8 @@ std::list TouchInputMapper::abortPointerSimple(nsecs_t when, nsecs_t NotifyMotionArgs TouchInputMapper::dispatchMotion( nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action, int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState, - int32_t edgeFlags, const PointerProperties* properties, const PointerCoords* coords, - const uint32_t* idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision, + int32_t edgeFlags, const PropertiesArray& properties, const CoordsArray& coords, + const IdToIndexArray& idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime, MotionClassification classification) { PointerCoords pointerCoords[MAX_POINTERS]; PointerProperties pointerProperties[MAX_POINTERS]; @@ -3797,36 +3753,6 @@ NotifyMotionArgs TouchInputMapper::dispatchMotion( downTime, std::move(frames)); } -bool TouchInputMapper::updateMovedPointers(const PointerProperties* inProperties, - const PointerCoords* inCoords, - const uint32_t* inIdToIndex, - PointerProperties* outProperties, - PointerCoords* outCoords, const uint32_t* outIdToIndex, - BitSet32 idBits) const { - bool changed = false; - while (!idBits.isEmpty()) { - uint32_t id = idBits.clearFirstMarkedBit(); - uint32_t inIndex = inIdToIndex[id]; - uint32_t outIndex = outIdToIndex[id]; - - const PointerProperties& curInProperties = inProperties[inIndex]; - const PointerCoords& curInCoords = inCoords[inIndex]; - PointerProperties& curOutProperties = outProperties[outIndex]; - PointerCoords& curOutCoords = outCoords[outIndex]; - - if (curInProperties != curOutProperties) { - curOutProperties.copyFrom(curInProperties); - changed = true; - } - - if (curInCoords != curOutCoords) { - curOutCoords.copyFrom(curInCoords); - changed = true; - } - } - return changed; -} - std::list TouchInputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) { std::list out; out += abortPointerUsage(when, readTime, 0 /*policyFlags*/); diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 7680090188..2bb9ecebe4 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -29,53 +29,56 @@ namespace android { /* Raw axis information from the driver. */ struct RawPointerAxes { - RawAbsoluteAxisInfo x; - RawAbsoluteAxisInfo y; - RawAbsoluteAxisInfo pressure; - RawAbsoluteAxisInfo touchMajor; - RawAbsoluteAxisInfo touchMinor; - RawAbsoluteAxisInfo toolMajor; - RawAbsoluteAxisInfo toolMinor; - RawAbsoluteAxisInfo orientation; - RawAbsoluteAxisInfo distance; - RawAbsoluteAxisInfo tiltX; - RawAbsoluteAxisInfo tiltY; - RawAbsoluteAxisInfo trackingId; - RawAbsoluteAxisInfo slot; - - RawPointerAxes(); + RawAbsoluteAxisInfo x{}; + RawAbsoluteAxisInfo y{}; + RawAbsoluteAxisInfo pressure{}; + RawAbsoluteAxisInfo touchMajor{}; + RawAbsoluteAxisInfo touchMinor{}; + RawAbsoluteAxisInfo toolMajor{}; + RawAbsoluteAxisInfo toolMinor{}; + RawAbsoluteAxisInfo orientation{}; + RawAbsoluteAxisInfo distance{}; + RawAbsoluteAxisInfo tiltX{}; + RawAbsoluteAxisInfo tiltY{}; + RawAbsoluteAxisInfo trackingId{}; + RawAbsoluteAxisInfo slot{}; + inline int32_t getRawWidth() const { return x.maxValue - x.minValue + 1; } inline int32_t getRawHeight() const { return y.maxValue - y.minValue + 1; } - void clear(); + inline void clear() { *this = RawPointerAxes(); } }; +using PropertiesArray = std::array; +using CoordsArray = std::array; +using IdToIndexArray = std::array; + /* Raw data for a collection of pointers including a pointer id mapping table. */ struct RawPointerData { struct Pointer { - uint32_t id; - int32_t x; - int32_t y; - int32_t pressure; - int32_t touchMajor; - int32_t touchMinor; - int32_t toolMajor; - int32_t toolMinor; - int32_t orientation; - int32_t distance; - int32_t tiltX; - int32_t tiltY; - int32_t toolType; // a fully decoded AMOTION_EVENT_TOOL_TYPE constant - bool isHovering; + uint32_t id{0xFFFFFFFF}; + int32_t x{}; + int32_t y{}; + int32_t pressure{}; + int32_t touchMajor{}; + int32_t touchMinor{}; + int32_t toolMajor{}; + int32_t toolMinor{}; + int32_t orientation{}; + int32_t distance{}; + int32_t tiltX{}; + int32_t tiltY{}; + // A fully decoded AMOTION_EVENT_TOOL_TYPE constant. + int32_t toolType{AMOTION_EVENT_TOOL_TYPE_UNKNOWN}; + bool isHovering{false}; }; - uint32_t pointerCount; - Pointer pointers[MAX_POINTERS]; - BitSet32 hoveringIdBits, touchingIdBits, canceledIdBits; - uint32_t idToIndex[MAX_POINTER_ID + 1]; + uint32_t pointerCount{}; + std::array pointers{}; + BitSet32 hoveringIdBits{}, touchingIdBits{}, canceledIdBits{}; + IdToIndexArray idToIndex{}; + + inline void clear() { *this = RawPointerData(); } - RawPointerData(); - void clear(); - void copyFrom(const RawPointerData& other); void getCentroidOfTouchingPointers(float* outX, float* outY) const; inline void markIdBit(uint32_t id, bool isHovering) { @@ -99,15 +102,13 @@ struct RawPointerData { /* Cooked data for a collection of pointers including a pointer id mapping table. */ struct CookedPointerData { - uint32_t pointerCount; - PointerProperties pointerProperties[MAX_POINTERS]; - PointerCoords pointerCoords[MAX_POINTERS]; - BitSet32 hoveringIdBits, touchingIdBits, canceledIdBits, validIdBits; - uint32_t idToIndex[MAX_POINTER_ID + 1]; + uint32_t pointerCount{}; + PropertiesArray pointerProperties{}; + CoordsArray pointerCoords{}; + BitSet32 hoveringIdBits{}, touchingIdBits{}, canceledIdBits{}, validIdBits{}; + IdToIndexArray idToIndex{}; - CookedPointerData(); - void clear(); - void copyFrom(const CookedPointerData& other); + inline void clear() { *this = CookedPointerData(); } inline const PointerCoords& pointerCoordsForId(uint32_t id) const { return pointerCoords[idToIndex[id]]; @@ -314,65 +315,33 @@ protected: RawPointerAxes mRawPointerAxes; struct RawState { - nsecs_t when; - nsecs_t readTime; + nsecs_t when{}; + nsecs_t readTime{}; // Raw pointer sample data. - RawPointerData rawPointerData; + RawPointerData rawPointerData{}; - int32_t buttonState; + int32_t buttonState{}; // Scroll state. - int32_t rawVScroll; - int32_t rawHScroll; - - explicit inline RawState() { clear(); } - - void copyFrom(const RawState& other) { - when = other.when; - readTime = other.readTime; - rawPointerData.copyFrom(other.rawPointerData); - buttonState = other.buttonState; - rawVScroll = other.rawVScroll; - rawHScroll = other.rawHScroll; - } + int32_t rawVScroll{}; + int32_t rawHScroll{}; - void clear() { - when = 0; - readTime = 0; - rawPointerData.clear(); - buttonState = 0; - rawVScroll = 0; - rawHScroll = 0; - } + inline void clear() { *this = RawState(); } }; struct CookedState { // Cooked pointer sample data. - CookedPointerData cookedPointerData; + CookedPointerData cookedPointerData{}; // Id bits used to differentiate fingers, stylus and mouse tools. - BitSet32 fingerIdBits; - BitSet32 stylusIdBits; - BitSet32 mouseIdBits; - - int32_t buttonState; - - void copyFrom(const CookedState& other) { - cookedPointerData.copyFrom(other.cookedPointerData); - fingerIdBits = other.fingerIdBits; - stylusIdBits = other.stylusIdBits; - mouseIdBits = other.mouseIdBits; - buttonState = other.buttonState; - } + BitSet32 fingerIdBits{}; + BitSet32 stylusIdBits{}; + BitSet32 mouseIdBits{}; - void clear() { - cookedPointerData.clear(); - fingerIdBits.clear(); - stylusIdBits.clear(); - mouseIdBits.clear(); - buttonState = 0; - } + int32_t buttonState{}; + + inline void clear() { *this = CookedState(); } }; std::vector mRawStatesPending; @@ -528,9 +497,9 @@ private: float mPointerGestureMaxSwipeWidth; struct PointerDistanceHeapElement { - uint32_t currentPointerIndex : 8; - uint32_t lastPointerIndex : 8; - uint64_t distance : 48; // squared distance + uint32_t currentPointerIndex : 8 {}; + uint32_t lastPointerIndex : 8 {}; + uint64_t distance : 48 {}; // squared distance }; enum class PointerUsage { @@ -627,15 +596,15 @@ private: // Pointer coords and ids for the current and previous pointer gesture. Mode currentGestureMode; BitSet32 currentGestureIdBits; - uint32_t currentGestureIdToIndex[MAX_POINTER_ID + 1]; - PointerProperties currentGestureProperties[MAX_POINTERS]; - PointerCoords currentGestureCoords[MAX_POINTERS]; + IdToIndexArray currentGestureIdToIndex{}; + PropertiesArray currentGestureProperties{}; + CoordsArray currentGestureCoords{}; Mode lastGestureMode; BitSet32 lastGestureIdBits; - uint32_t lastGestureIdToIndex[MAX_POINTER_ID + 1]; - PointerProperties lastGestureProperties[MAX_POINTERS]; - PointerCoords lastGestureCoords[MAX_POINTERS]; + IdToIndexArray lastGestureIdToIndex{}; + PropertiesArray lastGestureProperties{}; + CoordsArray lastGestureCoords{}; // Time the pointer gesture last went down. nsecs_t downTime; @@ -812,17 +781,10 @@ private: [[nodiscard]] NotifyMotionArgs dispatchMotion( nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action, int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState, - int32_t edgeFlags, const PointerProperties* properties, const PointerCoords* coords, - const uint32_t* idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision, + int32_t edgeFlags, const PropertiesArray& properties, const CoordsArray& coords, + const IdToIndexArray& idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime, MotionClassification classification); - // Updates pointer coords and properties for pointers with specified ids that have moved. - // Returns true if any of them changed. - bool updateMovedPointers(const PointerProperties* inProperties, const PointerCoords* inCoords, - const uint32_t* inIdToIndex, PointerProperties* outProperties, - PointerCoords* outCoords, const uint32_t* outIdToIndex, - BitSet32 idBits) const; - // Returns if this touch device is a touch screen with an associated display. bool isTouchScreen(); // Updates touch spots if they are enabled. Should only be used when this device is a @@ -834,7 +796,6 @@ private: static void assignPointerIds(const RawState& last, RawState& current); - const char* modeToString(DeviceMode deviceMode); void rotateAndScale(float& x, float& y) const; }; -- GitLab From 28fe2e694f5cafaf0eaad1417e15fdee2943b15b Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 1 Nov 2022 14:29:10 -0700 Subject: [PATCH 0435/1310] BBQ: Check if the buffer is already in the pending release queue before logging As a workaround for lost release callbacks, we try to release buffers in the submitted queue. If the buffer was released previously but held in the pending release queue, we would log incorrectly. This fixes the misleading logs. Test: presubmit Test: logcat Fixes: 255679881 Change-Id: I7e46f21f4c4fa1ee8c70e3ee8cd3f3665fe7442a --- libs/gui/BLASTBufferQueue.cpp | 22 +++++++++++++++------- libs/gui/include/gui/BLASTBufferQueue.h | 3 ++- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index 0021bd6cc3..97e45c6d47 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -360,11 +360,12 @@ void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp& releaseFence, std::optional currentMaxAcquiredBufferCount) { +void BLASTBufferQueue::releaseBufferCallbackLocked( + const ReleaseCallbackId& id, const sp& releaseFence, + std::optional currentMaxAcquiredBufferCount, bool fakeRelease) { ATRACE_CALL(); BQA_LOGV("releaseBufferCallback %s", id.to_string().c_str()); @@ -435,6 +438,11 @@ void BLASTBufferQueue::releaseBufferCallbackLocked(const ReleaseCallbackId& id, auto rb = ReleasedBuffer{id, releaseFence}; if (std::find(mPendingRelease.begin(), mPendingRelease.end(), rb) == mPendingRelease.end()) { mPendingRelease.emplace_back(rb); + if (fakeRelease) { + BQA_LOGE("Faking releaseBufferCallback from transactionCompleteCallback %" PRIu64, + id.framenumber); + BBQ_TRACE("FakeReleaseCallback"); + } } // Release all buffers that are beyond the ones that we need to hold diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h index 957652e1f1..47dcc42e16 100644 --- a/libs/gui/include/gui/BLASTBufferQueue.h +++ b/libs/gui/include/gui/BLASTBufferQueue.h @@ -93,7 +93,8 @@ public: void releaseBufferCallback(const ReleaseCallbackId& id, const sp& releaseFence, std::optional currentMaxAcquiredBufferCount); void releaseBufferCallbackLocked(const ReleaseCallbackId& id, const sp& releaseFence, - std::optional currentMaxAcquiredBufferCount); + std::optional currentMaxAcquiredBufferCount, + bool fakeRelease); void syncNextTransaction(std::function callback, bool acquireSingleBuffer = true); void stopContinuousSyncTransaction(); -- GitLab From b10b706a9bc9b55e511f35eb98c28f1445075d0f Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Tue, 1 Nov 2022 21:01:06 +0000 Subject: [PATCH 0436/1310] Update SurfaceFlinger to use power V4 AIDL Increment the power version to enable several changes using new load change hint API. Bug: b/256105073 Bug: b/256918431 Test: atest libsurfaceflinger_unittest Change-Id: Ia72808ac2def7d3cec2169d57b7d653200119601 --- libs/gui/fuzzer/Android.bp | 2 +- services/surfaceflinger/Android.bp | 2 +- services/surfaceflinger/CompositionEngine/Android.bp | 2 +- services/surfaceflinger/tests/unittests/Android.bp | 2 +- .../unittests/mock/DisplayHardware/MockIPowerHintSession.h | 2 ++ 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/libs/gui/fuzzer/Android.bp b/libs/gui/fuzzer/Android.bp index cdc9376a44..1c61d6bb8b 100644 --- a/libs/gui/fuzzer/Android.bp +++ b/libs/gui/fuzzer/Android.bp @@ -46,7 +46,7 @@ cc_defaults { "android.hardware.configstore-utils", "android.hardware.graphics.bufferqueue@1.0", "android.hardware.graphics.bufferqueue@2.0", - "android.hardware.power-V2-cpp", + "android.hardware.power-V4-cpp", "android.hidl.token@1.0", "libSurfaceFlingerProp", "libgui", diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index e76b191807..45f3cb8bfd 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -49,7 +49,7 @@ cc_defaults { "android.hardware.graphics.composer@2.4", "android.hardware.power@1.0", "android.hardware.power@1.3", - "android.hardware.power-V2-cpp", + "android.hardware.power-V4-cpp", "libbase", "libbinder", "libbinder_ndk", diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp index 0ae8bf98bf..c1460cfc95 100644 --- a/services/surfaceflinger/CompositionEngine/Android.bp +++ b/services/surfaceflinger/CompositionEngine/Android.bp @@ -25,7 +25,7 @@ cc_defaults { "android.hardware.graphics.composer@2.4", "android.hardware.power@1.0", "android.hardware.power@1.3", - "android.hardware.power-V2-cpp", + "android.hardware.power-V4-cpp", "libbase", "libcutils", "libgui", diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index d2b58137f0..31ceaa7f9a 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -150,7 +150,7 @@ cc_defaults { "android.hardware.power@1.1", "android.hardware.power@1.2", "android.hardware.power@1.3", - "android.hardware.power-V2-cpp", + "android.hardware.power-V4-cpp", "libaidlcommonsupport", "libcompositionengine_mocks", "libcompositionengine", diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h index 439f6f4e75..5f749dfbcc 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h @@ -23,6 +23,7 @@ using android::binder::Status; using android::hardware::power::IPowerHintSession; +using android::hardware::power::SessionHint; using namespace android::hardware::power; @@ -40,6 +41,7 @@ public: MOCK_METHOD(std::string, getInterfaceHash, (), (override)); MOCK_METHOD(Status, updateTargetWorkDuration, (int64_t), (override)); MOCK_METHOD(Status, reportActualWorkDuration, (const ::std::vector&), (override)); + MOCK_METHOD(Status, sendHint, (SessionHint), (override)); }; } // namespace android::Hwc2::mock -- GitLab From 79c3266e58c3963840d929513aa118199720eb20 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 25 Oct 2022 17:56:25 -0700 Subject: [PATCH 0437/1310] Remove 2 goto statements for wrongDevice If wrongDevice is set to true, we can just return right away. And since we are returning right away, we don't have to handle that case at the bottom of the function. This CL reduces the identation and simplifies the logic a bit. There should be no functional change. Bug: 211379801 Test: atest inputflinger_tests Change-Id: I3b11f27bc4efa7d2623ea54ce0a62eb4e6d1526b --- .../dispatcher/InputDispatcher.cpp | 132 +++++++++--------- 1 file changed, 65 insertions(+), 67 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 694c127e40..c8aff9a44e 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2084,7 +2084,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( outInjectionResult = InputEventInjectionResult::FAILED; switchedDevice = false; wrongDevice = true; - goto Failed; + return touchedWindows; } tempTouchState.reset(); tempTouchState.down = down; @@ -2100,7 +2100,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( outInjectionResult = InputEventInjectionResult::FAILED; switchedDevice = false; wrongDevice = true; - goto Failed; + return touchedWindows; } if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) { @@ -2412,83 +2412,81 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( Failed: // Update final pieces of touch state if the injector had permission. - if (!wrongDevice) { - if (switchedDevice) { + if (switchedDevice) { + if (DEBUG_FOCUS) { + ALOGD("Conflicting pointer actions: Switched to a different device."); + } + *outConflictingPointerActions = true; + } + + if (isHoverAction) { + // Started hovering, therefore no longer down. + if (oldState && oldState->down) { if (DEBUG_FOCUS) { - ALOGD("Conflicting pointer actions: Switched to a different device."); + ALOGD("Conflicting pointer actions: Hover received while pointer was " + "down."); } *outConflictingPointerActions = true; } - - if (isHoverAction) { - // Started hovering, therefore no longer down. - if (oldState && oldState->down) { - if (DEBUG_FOCUS) { - ALOGD("Conflicting pointer actions: Hover received while pointer was " - "down."); - } - *outConflictingPointerActions = true; - } - tempTouchState.reset(); - if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER || - maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) { - tempTouchState.deviceId = entry.deviceId; - tempTouchState.source = entry.source; - tempTouchState.displayId = displayId; - } - } else if (maskedAction == AMOTION_EVENT_ACTION_UP || - maskedAction == AMOTION_EVENT_ACTION_CANCEL) { - // All pointers up or canceled. - tempTouchState.reset(); - } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { - // First pointer went down. - if (oldState && oldState->down) { - if (DEBUG_FOCUS) { - ALOGD("Conflicting pointer actions: Down received while already down."); - } - *outConflictingPointerActions = true; - } - } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) { - // One pointer went up. - int32_t pointerIndex = getMotionEventActionPointerIndex(action); - uint32_t pointerId = entry.pointerProperties[pointerIndex].id; - - for (size_t i = 0; i < tempTouchState.windows.size();) { - TouchedWindow& touchedWindow = tempTouchState.windows[i]; - touchedWindow.pointerIds.clearBit(pointerId); - if (touchedWindow.pointerIds.isEmpty()) { - tempTouchState.windows.erase(tempTouchState.windows.begin() + i); - continue; - } - i += 1; - } - } else if (!isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) { - // If no split, we suppose all touched windows should receive pointer down. - const int32_t pointerIndex = getMotionEventActionPointerIndex(action); - for (size_t i = 0; i < tempTouchState.windows.size(); i++) { - TouchedWindow& touchedWindow = tempTouchState.windows[i]; - // Ignore drag window for it should just track one pointer. - if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) { - continue; - } - touchedWindow.pointerIds.markBit(entry.pointerProperties[pointerIndex].id); + tempTouchState.reset(); + if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER || + maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) { + tempTouchState.deviceId = entry.deviceId; + tempTouchState.source = entry.source; + tempTouchState.displayId = displayId; + } + } else if (maskedAction == AMOTION_EVENT_ACTION_UP || + maskedAction == AMOTION_EVENT_ACTION_CANCEL) { + // All pointers up or canceled. + tempTouchState.reset(); + } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { + // First pointer went down. + if (oldState && oldState->down) { + if (DEBUG_FOCUS) { + ALOGD("Conflicting pointer actions: Down received while already down."); } + *outConflictingPointerActions = true; } + } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) { + // One pointer went up. + int32_t pointerIndex = getMotionEventActionPointerIndex(action); + uint32_t pointerId = entry.pointerProperties[pointerIndex].id; - // Save changes unless the action was scroll in which case the temporary touch - // state was only valid for this one action. - if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) { - if (tempTouchState.displayId >= 0) { - mTouchStatesByDisplay[displayId] = tempTouchState; - } else { - mTouchStatesByDisplay.erase(displayId); + for (size_t i = 0; i < tempTouchState.windows.size();) { + TouchedWindow& touchedWindow = tempTouchState.windows[i]; + touchedWindow.pointerIds.clearBit(pointerId); + if (touchedWindow.pointerIds.isEmpty()) { + tempTouchState.windows.erase(tempTouchState.windows.begin() + i); + continue; + } + i += 1; + } + } else if (!isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) { + // If no split, we suppose all touched windows should receive pointer down. + const int32_t pointerIndex = getMotionEventActionPointerIndex(action); + for (size_t i = 0; i < tempTouchState.windows.size(); i++) { + TouchedWindow& touchedWindow = tempTouchState.windows[i]; + // Ignore drag window for it should just track one pointer. + if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) { + continue; } + touchedWindow.pointerIds.markBit(entry.pointerProperties[pointerIndex].id); } + } - // Update hover state. - mLastHoverWindowHandle = newHoverWindowHandle; + // Save changes unless the action was scroll in which case the temporary touch + // state was only valid for this one action. + if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) { + if (tempTouchState.displayId >= 0) { + mTouchStatesByDisplay[displayId] = tempTouchState; + } else { + mTouchStatesByDisplay.erase(displayId); + } } + // Update hover state. + mLastHoverWindowHandle = newHoverWindowHandle; + return touchedWindows; } -- GitLab From 64a985343395e051a5f5520a9646bb6175e200fa Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 25 Oct 2022 15:20:24 -0700 Subject: [PATCH 0438/1310] Remove TouchState::split variable Instead of storing this state and mutating it in various places, let's replace the variable with some logic about whether a touch should be split. Ideally, we wouldn't even have custom logic for split vs non-split, but this is a starting point before we can refactor the code further. The short-term goal is to simplify the findTouchedWindowTargetsLocked function. Bug: 211379801 Test: atest inputflinger_tests Change-Id: I799c2e11a72052a2867710593b18e05011ca44b6 --- .../dispatcher/InputDispatcher.cpp | 51 ++++++++++++++----- .../inputflinger/dispatcher/InputDispatcher.h | 1 + .../inputflinger/dispatcher/TouchState.cpp | 9 ---- services/inputflinger/dispatcher/TouchState.h | 2 - 4 files changed, 38 insertions(+), 25 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index ead093c6bd..63a241887b 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2034,6 +2034,36 @@ std::vector InputDispatcher::selectResponsiveMonitorsLocked( return responsiveMonitors; } +/** + * In general, touch should be always split between windows. Some exceptions: + * 1. Don't split touch is if we have an active pointer down, and a new pointer is going down that's + * from the same device, *and* the window that's receiving the current pointer does not support + * split touch. + * 2. Don't split mouse events + */ +bool InputDispatcher::shouldSplitTouch(const TouchState& touchState, + const MotionEntry& entry) const { + if (isFromSource(entry.source, AINPUT_SOURCE_MOUSE)) { + // We should never split mouse events + return false; + } + for (const TouchedWindow& touchedWindow : touchState.windows) { + if (touchedWindow.windowHandle->getInfo()->isSpy()) { + // Spy windows should not affect whether or not touch is split. + continue; + } + if (touchedWindow.windowHandle->getInfo()->supportsSplitTouch()) { + continue; + } + // Eventually, touchedWindow will contain the deviceId of each pointer that's currently + // being sent there. For now, use deviceId from touch state. + if (entry.deviceId == touchState.deviceId && !touchedWindow.pointerIds.isEmpty()) { + return false; + } + } + return true; +} + std::vector InputDispatcher::findTouchedWindowTargetsLocked( nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions, InputEventInjectionResult& outInjectionResult) { @@ -2061,7 +2091,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( tempTouchState = *oldState; } - bool isSplit = tempTouchState.split; + bool isSplit = shouldSplitTouch(tempTouchState, entry); bool switchedDevice = tempTouchState.deviceId >= 0 && tempTouchState.displayId >= 0 && (tempTouchState.deviceId != entry.deviceId || tempTouchState.source != entry.source || tempTouchState.displayId != displayId); @@ -2141,7 +2171,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( // No window is touched, so set split to true. This will allow the next pointer down to // be delivered to a new window which supports split touch. Pointers from a mouse device // should never be split. - tempTouchState.split = isSplit = !isFromMouse; + isSplit = !isFromMouse; } // Update hover state. @@ -5301,9 +5331,9 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) { dump += StringPrintf(INDENT "TouchStatesByDisplay:\n"); for (const std::pair& pair : mTouchStatesByDisplay) { const TouchState& state = pair.second; - dump += StringPrintf(INDENT2 "%d: down=%s, split=%s, deviceId=%d, source=0x%08x\n", - state.displayId, toString(state.down), toString(state.split), - state.deviceId, state.source); + dump += StringPrintf(INDENT2 "%d: down=%s, deviceId=%d, source=0x%08x\n", + state.displayId, toString(state.down), state.deviceId, + state.source); if (!state.windows.empty()) { dump += INDENT3 "Windows:\n"; for (size_t i = 0; i < state.windows.size(); i++) { @@ -5676,10 +5706,7 @@ status_t InputDispatcher::pilferPointersLocked(const sp& token) { "input channel stole pointer stream"); options.deviceId = state.deviceId; options.displayId = state.displayId; - if (state.split) { - // If split pointers then selectively cancel pointers otherwise cancel all pointers - options.pointerIds = window.pointerIds; - } + options.pointerIds = window.pointerIds; std::string canceledWindows; for (const TouchedWindow& w : state.windows) { const std::shared_ptr channel = @@ -5698,11 +5725,7 @@ status_t InputDispatcher::pilferPointersLocked(const sp& token) { // This only blocks relevant pointers to be sent to other windows window.isPilferingPointers = true; - if (state.split) { - state.cancelPointersForWindowsExcept(window.pointerIds, token); - } else { - state.filterWindowsExcept(token); - } + state.cancelPointersForWindowsExcept(window.pointerIds, token); return OK; } diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index c2fe19614f..3b44a8e85d 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -541,6 +541,7 @@ private: // shade is pulled down while we are counting down the timeout). void resetNoFocusedWindowTimeoutLocked() REQUIRES(mLock); + bool shouldSplitTouch(const TouchState& touchState, const MotionEntry& entry) const; int32_t getTargetDisplayId(const EventEntry& entry); sp findFocusedWindowTargetLocked( nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime, diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index cf0c38af33..eb31046d9c 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -31,10 +31,6 @@ void TouchState::reset() { void TouchState::addOrUpdateWindow(const sp& windowHandle, int32_t targetFlags, BitSet32 pointerIds, std::optional eventTime) { - if (targetFlags & InputTarget::FLAG_SPLIT) { - split = true; - } - for (size_t i = 0; i < windows.size(); i++) { TouchedWindow& touchedWindow = windows[i]; if (touchedWindow.windowHandle == windowHandle) { @@ -105,11 +101,6 @@ void TouchState::cancelPointersForNonPilferingWindows(const BitSet32 pointerIds) std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.isEmpty(); }); } -void TouchState::filterWindowsExcept(const sp& token) { - std::erase_if(windows, - [&token](const TouchedWindow& w) { return w.windowHandle->getToken() != token; }); -} - sp TouchState::getFirstForegroundWindowHandle() const { for (size_t i = 0; i < windows.size(); i++) { const TouchedWindow& window = windows[i]; diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index cf5f1e58c6..d32461184b 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -29,7 +29,6 @@ namespace inputdispatcher { struct TouchState { bool down = false; - bool split = false; // id of the device that is currently down, others are rejected int32_t deviceId = -1; @@ -50,7 +49,6 @@ struct TouchState { std::optional eventTime = std::nullopt); void removeWindowByToken(const sp& token); void filterNonAsIsTouchWindows(); - void filterWindowsExcept(const sp& token); // Cancel pointers for current set of windows except the window with particular binder token. void cancelPointersForWindowsExcept(const BitSet32 pointerIds, const sp& token); -- GitLab From b5a094b997f4db0b2f21ea6b2be6fa8bc6ba2f57 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Thu, 27 Oct 2022 12:00:12 -0400 Subject: [PATCH 0439/1310] SF: Avoid registering DisplayDevice with Scheduler The Scheduler should only care about the RefreshRateSelector part, and should not needlessly extend the compositionengine::Display's lifetime until the DisplayDevice is unregistered. Make Scheduler::registerDisplay infallible, such that SurfaceFlinger:: processDisplayChanged does not need to unregister before registering. Bug: 241285191 Test: libsurfaceflinger_unittest Change-Id: I12b3855167e98f48ae368d39264edcb456efb293 --- .../surfaceflinger/Scheduler/Scheduler.cpp | 23 ++++---- services/surfaceflinger/Scheduler/Scheduler.h | 16 ++--- services/surfaceflinger/SurfaceFlinger.cpp | 14 +++-- .../tests/unittests/SchedulerTest.cpp | 59 ++++++------------- .../tests/unittests/TestableScheduler.h | 13 ++-- .../tests/unittests/TestableSurfaceFlinger.h | 3 +- 6 files changed, 52 insertions(+), 76 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 499cee68f1..0e1b775a8c 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -94,7 +94,7 @@ void Scheduler::startTimers() { } } -void Scheduler::setRefreshRateSelector(std::shared_ptr selectorPtr) { +void Scheduler::setRefreshRateSelector(RefreshRateSelectorPtr selectorPtr) { // The current RefreshRateSelector instance may outlive this call, so unbind its idle timer. { // mRefreshRateSelectorLock is not locked here to avoid the deadlock @@ -126,13 +126,12 @@ void Scheduler::setRefreshRateSelector(std::shared_ptr sele mRefreshRateSelector->startIdleTimer(); } -void Scheduler::registerDisplay(sp display) { - if (display->isPrimary()) { - mLeaderDisplayId = display->getPhysicalId(); +void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) { + if (!mLeaderDisplayId) { + mLeaderDisplayId = displayId; } - const bool ok = mDisplays.try_emplace(display->getPhysicalId(), std::move(display)).second; - ALOGE_IF(!ok, "%s: Duplicate display", __func__); + mRefreshRateSelectors.emplace_or_replace(displayId, std::move(selectorPtr)); } void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { @@ -140,7 +139,7 @@ void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { mLeaderDisplayId.reset(); } - mDisplays.erase(displayId); + mRefreshRateSelectors.erase(displayId); } void Scheduler::run() { @@ -711,10 +710,9 @@ auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap { const auto globalSignals = makeGlobalSignals(); - for (const auto& [id, display] : mDisplays) { + for (const auto& [id, selectorPtr] : mRefreshRateSelectors) { auto rankedRefreshRates = - display->holdRefreshRateSelector() - ->getRankedRefreshRates(mPolicy.contentRequirements, globalSignals); + selectorPtr->getRankedRefreshRates(mPolicy.contentRequirements, globalSignals); for (const auto& [modePtr, score] : rankedRefreshRates.ranking) { const auto [it, inserted] = refreshRateTallies.try_emplace(modePtr->getFps(), score); @@ -733,7 +731,7 @@ auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap { // Find the first refresh rate common to all displays. while (maxScoreIt != refreshRateTallies.cend() && - maxScoreIt->second.displayCount != mDisplays.size()) { + maxScoreIt->second.displayCount != mRefreshRateSelectors.size()) { ++maxScoreIt; } @@ -742,7 +740,8 @@ auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap { for (auto it = maxScoreIt + 1; it != refreshRateTallies.cend(); ++it) { const auto [fps, tally] = *it; - if (tally.displayCount == mDisplays.size() && tally.score > maxScoreIt->second.score) { + if (tally.displayCount == mRefreshRateSelectors.size() && + tally.score > maxScoreIt->second.score) { maxScoreIt = it; } } diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 39c41b99da..901cf74558 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -39,7 +39,6 @@ #include "Display/DisplayMap.h" #include "Display/DisplayModeRequest.h" -#include "DisplayDevice.h" #include "EventThread.h" #include "FrameRateOverrideMappings.h" #include "LayerHistory.h" @@ -107,10 +106,11 @@ public: virtual ~Scheduler(); void startTimers(); - void setRefreshRateSelector(std::shared_ptr) - EXCLUDES(mRefreshRateSelectorLock); - void registerDisplay(sp); + using RefreshRateSelectorPtr = std::shared_ptr; + void setRefreshRateSelector(RefreshRateSelectorPtr) EXCLUDES(mRefreshRateSelectorLock); + + void registerDisplay(PhysicalDisplayId, RefreshRateSelectorPtr); void unregisterDisplay(PhysicalDisplayId); void run(); @@ -299,8 +299,7 @@ private: EXCLUDES(mRefreshRateSelectorLock); android::impl::EventThread::GetVsyncPeriodFunction makeGetVsyncPeriodFunction() const; - std::shared_ptr holdRefreshRateSelector() const - EXCLUDES(mRefreshRateSelectorLock) { + RefreshRateSelectorPtr holdRefreshRateSelector() const EXCLUDES(mRefreshRateSelectorLock) { std::scoped_lock lock(mRefreshRateSelectorLock); return mRefreshRateSelector; } @@ -336,7 +335,7 @@ private: mutable std::mutex mPolicyLock; - display::PhysicalDisplayMap> mDisplays; + display::PhysicalDisplayMap mRefreshRateSelectors; std::optional mLeaderDisplayId; struct Policy { @@ -359,8 +358,9 @@ private: std::optional cachedModeChangedParams; } mPolicy GUARDED_BY(mPolicyLock); + // TODO(b/255635821): Remove this by instead looking up the `mLeaderDisplayId` selector. mutable std::mutex mRefreshRateSelectorLock; - std::shared_ptr mRefreshRateSelector GUARDED_BY(mRefreshRateSelectorLock); + RefreshRateSelectorPtr mRefreshRateSelector GUARDED_BY(mRefreshRateSelectorLock); std::mutex mVsyncTimelineLock; std::optional mLastVsyncPeriodChangeTimeline diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 89d905a4ad..aa930bcff3 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2937,11 +2937,15 @@ void SurfaceFlinger::processDisplayAdded(const wp& displayToken, displaySurface, producer); if (mScheduler && !display->isVirtual()) { + auto selectorPtr = display->holdRefreshRateSelector(); + // Display modes are reloaded on hotplug reconnect. if (display->isPrimary()) { - mScheduler->setRefreshRateSelector(display->holdRefreshRateSelector()); + mScheduler->setRefreshRateSelector(selectorPtr); } - mScheduler->registerDisplay(display); + + const auto displayId = display->getPhysicalId(); + mScheduler->registerDisplay(displayId, std::move(selectorPtr)); dispatchDisplayHotplugEvent(display->getPhysicalId(), true); } @@ -2994,8 +2998,6 @@ void SurfaceFlinger::processDisplayChanged(const wp& displayToken, display->disconnect(); if (display->isVirtual()) { releaseVirtualDisplay(display->getVirtualId()); - } else { - mScheduler->unregisterDisplay(display->getPhysicalId()); } } @@ -3409,8 +3411,8 @@ void SurfaceFlinger::initScheduler(const sp& display) { } mScheduler->createVsyncSchedule(features); - mScheduler->setRefreshRateSelector(std::move(selectorPtr)); - mScheduler->registerDisplay(display); + mScheduler->setRefreshRateSelector(selectorPtr); + mScheduler->registerDisplay(display->getPhysicalId(), std::move(selectorPtr)); } setVsyncEnabled(false); mScheduler->startTimers(); diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 066083fffa..ea4666ed4b 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -20,7 +20,6 @@ #include -#include "FakeDisplayInjector.h" #include "Scheduler/EventThread.h" #include "Scheduler/RefreshRateSelector.h" #include "TestableScheduler.h" @@ -41,7 +40,6 @@ namespace { using MockEventThread = android::mock::EventThread; using MockLayer = android::mock::MockLayer; -using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector; class SchedulerTest : public testing::Test { protected: @@ -90,10 +88,6 @@ protected: sp mEventThreadConnection; TestableSurfaceFlinger mFlinger; - Hwc2::mock::PowerAdvisor mPowerAdvisor; - sp mNativeWindow = sp::make(); - - FakeDisplayInjector mFakeDisplayInjector{mFlinger, mPowerAdvisor, mNativeWindow}; }; SchedulerTest::SchedulerTest() { @@ -240,14 +234,11 @@ MATCHER(Is120Hz, "") { } TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) { - const auto display = mFakeDisplayInjector.injectInternalDisplay( - [&](FakeDisplayDeviceInjector& injector) { - injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId()); - }, - {.displayId = kDisplayId1}); + const auto selectorPtr = + std::make_shared(kDisplay1Modes, kDisplay1Mode60->getId()); - mScheduler->registerDisplay(display); - mScheduler->setRefreshRateSelector(display->holdRefreshRateSelector()); + mScheduler->registerDisplay(kDisplayId1, selectorPtr); + mScheduler->setRefreshRateSelector(selectorPtr); const sp layer = sp::make(mFlinger.flinger()); EXPECT_CALL(*layer, isVisible()).WillOnce(Return(true)); @@ -269,13 +260,9 @@ TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) { } TEST_F(SchedulerTest, chooseDisplayModesSingleDisplay) { - const auto display = mFakeDisplayInjector.injectInternalDisplay( - [&](FakeDisplayDeviceInjector& injector) { - injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId()); - }, - {.displayId = kDisplayId1}); - - mScheduler->registerDisplay(display); + mScheduler->registerDisplay(kDisplayId1, + std::make_shared(kDisplay1Modes, + kDisplay1Mode60->getId())); std::vector layers = std::vector({{.weight = 1.f}, {.weight = 1.f}}); @@ -314,23 +301,16 @@ TEST_F(SchedulerTest, chooseDisplayModesSingleDisplay) { EXPECT_EQ(choice->get(), DisplayModeChoice(kDisplay1Mode120, globalSignals)); mScheduler->unregisterDisplay(kDisplayId1); - EXPECT_TRUE(mScheduler->mutableDisplays().empty()); + EXPECT_FALSE(mScheduler->hasRefreshRateSelectors()); } TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { - const auto display1 = mFakeDisplayInjector.injectInternalDisplay( - [&](FakeDisplayDeviceInjector& injector) { - injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId()); - }, - {.displayId = kDisplayId1, .hwcDisplayId = 42, .isPrimary = true}); - const auto display2 = mFakeDisplayInjector.injectInternalDisplay( - [&](FakeDisplayDeviceInjector& injector) { - injector.setDisplayModes(kDisplay2Modes, kDisplay2Mode60->getId()); - }, - {.displayId = kDisplayId2, .hwcDisplayId = 41, .isPrimary = false}); - - mScheduler->registerDisplay(display1); - mScheduler->registerDisplay(display2); + mScheduler->registerDisplay(kDisplayId1, + std::make_shared(kDisplay1Modes, + kDisplay1Mode60->getId())); + mScheduler->registerDisplay(kDisplayId2, + std::make_shared(kDisplay2Modes, + kDisplay2Mode60->getId())); using DisplayModeChoice = TestableScheduler::DisplayModeChoice; TestableScheduler::DisplayModeChoiceMap expectedChoices; @@ -380,13 +360,10 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { } { // This display does not support 120 Hz, so we should choose 60 Hz despite the touch signal. - const auto display3 = mFakeDisplayInjector.injectInternalDisplay( - [&](FakeDisplayDeviceInjector& injector) { - injector.setDisplayModes(kDisplay3Modes, kDisplay3Mode60->getId()); - }, - {.displayId = kDisplayId3, .hwcDisplayId = 40, .isPrimary = false}); - - mScheduler->registerDisplay(display3); + mScheduler + ->registerDisplay(kDisplayId3, + std::make_shared(kDisplay3Modes, + kDisplay3Mode60->getId())); const GlobalSignals globalSignals = {.touch = true}; mScheduler->replaceTouchTimer(10); diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index 2814d38b47..95c99150b3 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -32,15 +32,13 @@ namespace android::scheduler { class TestableScheduler : public Scheduler, private ICompositor { public: - TestableScheduler(std::shared_ptr selectorPtr, - ISchedulerCallback& callback) + TestableScheduler(RefreshRateSelectorPtr selectorPtr, ISchedulerCallback& callback) : TestableScheduler(std::make_unique(), std::make_unique(), std::move(selectorPtr), callback) {} TestableScheduler(std::unique_ptr controller, - std::unique_ptr tracker, - std::shared_ptr selectorPtr, + std::unique_ptr tracker, RefreshRateSelectorPtr selectorPtr, ISchedulerCallback& callback) : Scheduler(*this, callback, Feature::kContentDetection) { mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller))); @@ -68,16 +66,15 @@ public: auto& mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; } auto& mutableHWVsyncAvailable() { return mHWVsyncAvailable; } - auto& mutableLayerHistory() { return mLayerHistory; } + auto refreshRateSelector() { return holdRefreshRateSelector(); } + bool hasRefreshRateSelectors() const { return !mRefreshRateSelectors.empty(); } - auto& mutableDisplays() { return mDisplays; } + auto& mutableLayerHistory() { return mLayerHistory; } size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS { return mLayerHistory.mActiveLayerInfos.size() + mLayerHistory.mInactiveLayerInfos.size(); } - auto refreshRateSelector() { return holdRefreshRateSelector(); } - size_t getNumActiveLayers() NO_THREAD_SAFETY_ANALYSIS { return mLayerHistory.mActiveLayerInfos.size(); } diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index ff79ce099e..35c037c051 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -842,7 +842,8 @@ public: sp display = sp::make(mCreationArgs); mFlinger.mutableDisplays().emplace_or_replace(mDisplayToken, display); if (mFlinger.scheduler()) { - mFlinger.scheduler()->registerDisplay(display); + mFlinger.scheduler()->registerDisplay(display->getPhysicalId(), + display->holdRefreshRateSelector()); } DisplayDeviceState state; -- GitLab From f0ab2c87b0908c0bdfeb01dc39a7393b1d932859 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 25 Oct 2022 18:15:28 -0700 Subject: [PATCH 0440/1310] Remove wrongDevice variable After the last refactor, this variable is no longer used. Bug: 211379801 Test: atest inputflinger_tests Change-Id: I3ed706ef806c4f80f27bbbf5cf90460592e7a318 --- services/inputflinger/dispatcher/InputDispatcher.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 63a241887b..9a6ebaaed2 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2092,7 +2092,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } bool isSplit = shouldSplitTouch(tempTouchState, entry); - bool switchedDevice = tempTouchState.deviceId >= 0 && tempTouchState.displayId >= 0 && + const bool switchedDevice = tempTouchState.deviceId >= 0 && tempTouchState.displayId >= 0 && (tempTouchState.deviceId != entry.deviceId || tempTouchState.source != entry.source || tempTouchState.displayId != displayId); @@ -2102,7 +2102,6 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( const bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN || maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction); const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE); - bool wrongDevice = false; if (newGesture) { bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN; if (switchedDevice && tempTouchState.down && !down && !isHoverAction) { @@ -2111,9 +2110,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( displayId); // TODO: test multiple simultaneous input streams. outInjectionResult = InputEventInjectionResult::FAILED; - switchedDevice = false; - wrongDevice = true; - return touchedWindows; + return touchedWindows; // wrong device } tempTouchState.reset(); tempTouchState.down = down; @@ -2127,9 +2124,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( displayId); // TODO: test multiple simultaneous input streams. outInjectionResult = InputEventInjectionResult::FAILED; - switchedDevice = false; - wrongDevice = true; - return touchedWindows; + return touchedWindows; // wrong device } if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) { -- GitLab From 80b7248c70cf37c5d1080c41da18595856c60994 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Wed, 2 Nov 2022 01:55:35 +0000 Subject: [PATCH 0441/1310] jpegrecoverymap: Add error code Test: build Bug: b/252835416 Change-Id: Ie25f722ccc62d121a61cb4ff63b73076ce1477e2 --- .../include/jpegrecoverymap/jpegrerrorcode.h | 46 +++++++++++++++++++ .../include/jpegrecoverymap/recoverymap.h | 2 +- libs/jpegrecoverymap/recoverymap.cpp | 20 ++++---- 3 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h new file mode 100644 index 0000000000..49ab34d154 --- /dev/null +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +namespace android::recoverymap { + +enum { + // status_t map for errors in the media framework + // OK or NO_ERROR or 0 represents no error. + + // See system/core/include/utils/Errors.h + // System standard errors from -1 through (possibly) -133 + // + // Errors with special meanings and side effects. + // INVALID_OPERATION: Operation attempted in an illegal state (will try to signal to app). + // DEAD_OBJECT: Signal from CodecBase to MediaCodec that MediaServer has died. + // NAME_NOT_FOUND: Signal from CodecBase to MediaCodec that the component was not found. + + // JPEGR errors + JPEGR_IO_ERROR_BASE = -10000, + ERROR_JPEGR_INVALID_INPUT_TYPE = JPEGR_IO_ERROR_BASE, + ERROR_JPEGR_INVALID_OUTPUT_TYPE = JPEGR_IO_ERROR_BASE - 1, + ERROR_JPEGR_INVALID_NULL_PTR = JPEGR_IO_ERROR_BASE - 2, + + JPEGR_RUNTIME_ERROR_BASE = -20000, + ERROR_JPEGR_ENCODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 1, + ERROR_JPEGR_DECODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 2, + ERROR_JPEGR_CALCULATION_ERROR = JPEGR_RUNTIME_ERROR_BASE - 3, + ERROR_JPEGR_METADATA_ERROR = JPEGR_RUNTIME_ERROR_BASE - 4, +}; + +} // namespace android::recoverymap \ No newline at end of file diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h index 15eca1e705..31f1872491 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -14,7 +14,7 @@ * limitations under the License. */ - #include +#include "jpegrerrorcode.h" namespace android::recoverymap { diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index 5d2572213b..67c23e9788 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -24,7 +24,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, if (uncompressed_p010_image == nullptr || uncompressed_yuv_420_image == nullptr || dest == nullptr) { - return BAD_VALUE; + return ERROR_JPEGR_INVALID_NULL_PTR; } // TBD @@ -40,7 +40,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, || uncompressed_yuv_420_image == nullptr || compressed_jpeg_image == nullptr || dest == nullptr) { - return BAD_VALUE; + return ERROR_JPEGR_INVALID_NULL_PTR; } // TBD @@ -53,7 +53,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, if (uncompressed_p010_image == nullptr || compressed_jpeg_image == nullptr || dest == nullptr) { - return BAD_VALUE; + return ERROR_JPEGR_INVALID_NULL_PTR; } // TBD @@ -62,7 +62,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, status_t RecoveryMap::decodeJPEGR(void* compressed_jpegr_image, jr_uncompressed_ptr dest) { if (compressed_jpegr_image == nullptr || dest == nullptr) { - return BAD_VALUE; + return ERROR_JPEGR_INVALID_NULL_PTR; } // TBD @@ -72,7 +72,7 @@ status_t RecoveryMap::decodeJPEGR(void* compressed_jpegr_image, jr_uncompressed_ status_t RecoveryMap::decodeRecoveryMap(jr_compressed_ptr compressed_recovery_map, jr_uncompressed_ptr dest) { if (compressed_recovery_map == nullptr || dest == nullptr) { - return BAD_VALUE; + return ERROR_JPEGR_INVALID_NULL_PTR; } // TBD @@ -82,7 +82,7 @@ status_t RecoveryMap::decodeRecoveryMap(jr_compressed_ptr compressed_recovery_ma status_t RecoveryMap::encodeRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, jr_compressed_ptr dest) { if (uncompressed_recovery_map == nullptr || dest == nullptr) { - return BAD_VALUE; + return ERROR_JPEGR_INVALID_NULL_PTR; } // TBD @@ -95,7 +95,7 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 if (uncompressed_yuv_420_image == nullptr || uncompressed_p010_image == nullptr || dest == nullptr) { - return BAD_VALUE; + return ERROR_JPEGR_INVALID_NULL_PTR; } // TBD @@ -108,7 +108,7 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ if (uncompressed_yuv_420_image == nullptr || uncompressed_recovery_map == nullptr || dest == nullptr) { - return BAD_VALUE; + return ERROR_JPEGR_INVALID_NULL_PTR; } // TBD @@ -117,7 +117,7 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ status_t RecoveryMap::extractRecoveryMap(void* compressed_jpegr_image, jr_compressed_ptr dest) { if (compressed_jpegr_image == nullptr || dest == nullptr) { - return BAD_VALUE; + return ERROR_JPEGR_INVALID_NULL_PTR; } // TBD @@ -130,7 +130,7 @@ status_t RecoveryMap::appendRecoveryMap(void* compressed_jpeg_image, if (compressed_jpeg_image == nullptr || compressed_recovery_map == nullptr || dest == nullptr) { - return BAD_VALUE; + return ERROR_JPEGR_INVALID_NULL_PTR; } // TBD -- GitLab From 7bffbf5dfbbdecfc77bdec87f4f44826e784d752 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 14 Oct 2022 20:31:53 +0000 Subject: [PATCH 0442/1310] Add integration tests for stylus button behavior Bug: 246394583 Test: atest inputflinger_tests Change-Id: I80d295132de279ec631be12cb4c521067ef7e0b1 --- .../inputflinger/tests/InputReader_test.cpp | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 0c2742fd16..04382ee761 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -2825,6 +2825,96 @@ TEST_F(TouchIntegrationTest, StylusButtonsGenerateKeyEvents) { WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY)))); } +TEST_F(TouchIntegrationTest, StylusButtonsSurroundingTouchGesture) { + const Point centerPoint = mDevice->getCenterPoint(); + + // Press the stylus button. + mDevice->sendKey(BTN_STYLUS, 1); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY)))); + + // Start and finish a stylus gesture. + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_PEN); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + + mDevice->sendTrackingId(INVALID_TRACKING_ID); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0)))); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0)))); + + // Release the stylus button. + mDevice->sendKey(BTN_STYLUS, 0); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY)))); +} + +TEST_F(TouchIntegrationTest, StylusButtonsWithinTouchGesture) { + const Point centerPoint = mDevice->getCenterPoint(); + + // Start a stylus gesture. + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_PEN); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0)))); + + // Press and release a stylus button. Each change in button state also generates a MOVE event. + mDevice->sendKey(BTN_STYLUS, 1); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + + mDevice->sendKey(BTN_STYLUS, 0); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0)))); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0)))); + + // Finish the stylus gesture. + mDevice->sendTrackingId(INVALID_TRACKING_ID); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0)))); +} + // --- InputDeviceTest --- class InputDeviceTest : public testing::Test { protected: -- GitLab From b7d434e2fa7250a6955035ef14a96c08f857075b Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 14 Oct 2022 22:41:38 +0000 Subject: [PATCH 0443/1310] Differentiate Uinput devices by productId for testing Unless we somehow differentiate test Uinput devices, if more than one of those devices are created, they will be merged together into one InputDevice in InputReader. Therefore we use a different productId for each Uinput device class. Bug: 246394583 Test: atest inputflinger_tests Change-Id: I5669af54d8457cd572fa5bfbff670e7926237fa4 --- .../inputflinger/tests/InputReader_test.cpp | 2 +- services/inputflinger/tests/UinputDevice.cpp | 26 +++++++------ services/inputflinger/tests/UinputDevice.h | 39 ++++++++++++++----- 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 04382ee761..10acdd2655 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -2373,7 +2373,7 @@ TEST_F(InputReaderIntegrationTest, TestInvalidDevice) { // An invalid input device that is only used for this test. class InvalidUinputDevice : public UinputDevice { public: - InvalidUinputDevice() : UinputDevice("Invalid Device") {} + InvalidUinputDevice() : UinputDevice("Invalid Device", 99 /*productId*/) {} private: void configureDevice(int fd, uinput_user_dev* device) override {} diff --git a/services/inputflinger/tests/UinputDevice.cpp b/services/inputflinger/tests/UinputDevice.cpp index c4830dc815..bc695b8bd8 100644 --- a/services/inputflinger/tests/UinputDevice.cpp +++ b/services/inputflinger/tests/UinputDevice.cpp @@ -24,7 +24,8 @@ namespace android { // --- UinputDevice --- -UinputDevice::UinputDevice(const char* name) : mName(name) {} +UinputDevice::UinputDevice(const char* name, int16_t productId) + : mName(name), mProductId(productId) {} UinputDevice::~UinputDevice() { if (ioctl(mDeviceFd, UI_DEV_DESTROY)) { @@ -43,7 +44,7 @@ void UinputDevice::init() { strlcpy(device.name, mName, UINPUT_MAX_NAME_SIZE); device.id.bustype = BUS_USB; device.id.vendor = 0x01; - device.id.product = 0x01; + device.id.product = mProductId; device.id.version = 1; ASSERT_NO_FATAL_FAILURE(configureDevice(mDeviceFd, &device)); @@ -76,8 +77,8 @@ void UinputDevice::injectEvent(uint16_t type, uint16_t code, int32_t value) { // --- UinputKeyboard --- -UinputKeyboard::UinputKeyboard(const char* name, std::initializer_list keys) - : UinputDevice(name), mKeys(keys.begin(), keys.end()) {} +UinputKeyboard::UinputKeyboard(const char* name, int16_t productId, std::initializer_list keys) + : UinputDevice(name, productId), mKeys(keys.begin(), keys.end()) {} void UinputKeyboard::configureDevice(int fd, uinput_user_dev* device) { // enable key press/release event @@ -121,23 +122,26 @@ void UinputKeyboard::pressAndReleaseKey(int key) { // --- UinputHomeKey --- -UinputHomeKey::UinputHomeKey() : UinputKeyboard("Test Uinput Home Key", {KEY_HOME}) {} +UinputHomeKey::UinputHomeKey() : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {KEY_HOME}) {} void UinputHomeKey::pressAndReleaseHomeKey() { pressAndReleaseKey(KEY_HOME); } -// --- UinputSteamController +// --- UinputSteamController --- + UinputSteamController::UinputSteamController() - : UinputKeyboard("Test Uinput Steam Controller", {BTN_GEAR_DOWN, BTN_GEAR_UP}) {} + : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {BTN_GEAR_DOWN, BTN_GEAR_UP}) {} + +// --- UinputExternalStylus --- -// --- UinputExternalStylus UinputExternalStylus::UinputExternalStylus() - : UinputKeyboard("Test Uinput External Stylus", {BTN_STYLUS, BTN_STYLUS2, BTN_STYLUS3}) {} + : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {BTN_STYLUS, BTN_STYLUS2, BTN_STYLUS3}) {} // --- UinputTouchScreen --- -UinputTouchScreen::UinputTouchScreen(const Rect* size) - : UinputDevice(UinputTouchScreen::DEVICE_NAME), mSize(*size) {} + +UinputTouchScreen::UinputTouchScreen(const Rect& size) + : UinputDevice(DEVICE_NAME, PRODUCT_ID), mSize(size) {} void UinputTouchScreen::configureDevice(int fd, uinput_user_dev* device) { // Setup the touch screen device diff --git a/services/inputflinger/tests/UinputDevice.h b/services/inputflinger/tests/UinputDevice.h index 53dcfd0a68..d661bd36d3 100644 --- a/services/inputflinger/tests/UinputDevice.h +++ b/services/inputflinger/tests/UinputDevice.h @@ -32,7 +32,7 @@ namespace android { template std::unique_ptr createUinputDevice(Ts... args) { // Using `new` to access non-public constructors. - std::unique_ptr dev(new D(&args...)); + std::unique_ptr dev(new D(args...)); EXPECT_NO_FATAL_FAILURE(dev->init()); return dev; } @@ -51,8 +51,9 @@ public: protected: const char* mName; + const int16_t mProductId; - UinputDevice(const char* name); + explicit UinputDevice(const char* name, int16_t productId); // Signals which types of events this device supports before it is created. // This must be overridden by subclasses. @@ -71,7 +72,8 @@ private: class UinputKeyboard : public UinputDevice { public: - static constexpr const char* KEYBOARD_NAME = "Test Keyboard Device"; + static constexpr const char* KEYBOARD_NAME = "Test Uinput Keyboard Device"; + static constexpr int16_t PRODUCT_ID = 42; // Injects key press and sync. void pressKey(int key); @@ -84,7 +86,8 @@ public: friend std::unique_ptr createUinputDevice(Ts... args); protected: - UinputKeyboard(const char* name, std::initializer_list keys = {}); + explicit UinputKeyboard(const char* name, int16_t productId = PRODUCT_ID, + std::initializer_list keys = {}); private: void configureDevice(int fd, uinput_user_dev* device) override; @@ -97,6 +100,9 @@ private: // A keyboard device that has a single HOME key. class UinputHomeKey : public UinputKeyboard { public: + static constexpr const char* DEVICE_NAME = "Test Uinput Home Key"; + static constexpr int16_t PRODUCT_ID = 43; + // Injects 4 events: key press, sync, key release, and sync. void pressAndReleaseHomeKey(); @@ -104,34 +110,47 @@ public: friend std::unique_ptr createUinputDevice(Ts... args); private: - UinputHomeKey(); + explicit UinputHomeKey(); }; +// --- UinputSteamController --- + // A joystick device that sends a BTN_GEAR_DOWN / BTN_WHEEL key. class UinputSteamController : public UinputKeyboard { public: + static constexpr const char* DEVICE_NAME = "Test Uinput Steam Controller"; + static constexpr int16_t PRODUCT_ID = 44; + template friend std::unique_ptr createUinputDevice(Ts... args); private: - UinputSteamController(); + explicit UinputSteamController(); }; +// --- UinputExternalStylus --- + // A stylus that reports button presses. class UinputExternalStylus : public UinputKeyboard { public: + static constexpr const char* DEVICE_NAME = "Test Uinput External Stylus"; + static constexpr int16_t PRODUCT_ID = 45; + template friend std::unique_ptr createUinputDevice(Ts... args); private: - UinputExternalStylus(); + explicit UinputExternalStylus(); }; // --- UinputTouchScreen --- -// A touch screen device with specific size. + +// A multi-touch touchscreen device with specific size that also supports styluses. class UinputTouchScreen : public UinputDevice { public: - static constexpr const char* DEVICE_NAME = "Test Touch Screen"; + static constexpr const char* DEVICE_NAME = "Test Uinput Touch Screen"; + static constexpr int16_t PRODUCT_ID = 46; + static const int32_t RAW_TOUCH_MIN = 0; static const int32_t RAW_TOUCH_MAX = 31; static const int32_t RAW_ID_MIN = 0; @@ -157,7 +176,7 @@ public: const Point getCenterPoint(); protected: - UinputTouchScreen(const Rect* size); + explicit UinputTouchScreen(const Rect& size); private: void configureDevice(int fd, uinput_user_dev* device) override; -- GitLab From 09e40f6444f1b7b14612ec6345271b4396af8ebb Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 2 Nov 2022 20:53:10 +0000 Subject: [PATCH 0444/1310] Address missed comments from ag/20198235 Bug: 246394583 Test: atest inputflinger_tests Change-Id: I64448ea30e295e4d6c2a89e6ef90a7a9fe225363 --- .../reader/mapper/accumulator/TouchButtonAccumulator.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp index 5d5bee7feb..1891205ed6 100644 --- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp @@ -108,13 +108,13 @@ void TouchButtonAccumulator::process(const RawEvent* rawEvent) { } void TouchButtonAccumulator::processMappedKey(int32_t scanCode, bool down) { - int32_t outKeyCode, outMetaState; - uint32_t outFlags; + int32_t keyCode, metaState; + uint32_t flags; if (mDeviceContext.mapKey(scanCode, mHidUsageAccumulator.consumeCurrentHidUsage(), - 0 /*metaState*/, &outKeyCode, &outMetaState, &outFlags) != OK) { + 0 /*metaState*/, &keyCode, &metaState, &flags) != OK) { return; } - switch (outKeyCode) { + switch (keyCode) { case AKEYCODE_STYLUS_BUTTON_PRIMARY: mBtnStylus = down; break; -- GitLab From 4b3e4a9bb25fa90a45f8f43fc7a83db528e9ad2b Mon Sep 17 00:00:00 2001 From: Sungtak Lee Date: Tue, 1 Nov 2022 23:39:37 +0000 Subject: [PATCH 0445/1310] Make ANativeWindow available over stable AIDL Bug: 251850069 Test: m Change-Id: Ia47a5c6a83a7c51b4034c901f6ffc9813d3a6eb3 --- aidl/gui/android/view/Surface.aidl | 2 +- libs/gui/include/gui/Surface.h | 18 ++ libs/nativewindow/ANativeWindow.cpp | 48 ++++++ libs/nativewindow/Android.bp | 2 + .../include/android/native_window.h | 2 +- .../include/android/native_window_aidl.h | 161 ++++++++++++++++++ libs/nativewindow/libnativewindow.map.txt | 2 + 7 files changed, 233 insertions(+), 2 deletions(-) create mode 100644 libs/nativewindow/include/android/native_window_aidl.h diff --git a/aidl/gui/android/view/Surface.aidl b/aidl/gui/android/view/Surface.aidl index 7e892205d5..bb3faaff79 100644 --- a/aidl/gui/android/view/Surface.aidl +++ b/aidl/gui/android/view/Surface.aidl @@ -17,4 +17,4 @@ package android.view; -parcelable Surface cpp_header "gui/view/Surface.h"; +@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable Surface cpp_header "gui/view/Surface.h" ndk_header "android/native_window_aidl.h"; diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h index 1f19f4e1da..7aec0bf09d 100644 --- a/libs/gui/include/gui/Surface.h +++ b/libs/gui/include/gui/Surface.h @@ -113,6 +113,24 @@ public: return surface != nullptr && surface->getIGraphicBufferProducer() != nullptr; } + static sp getIGraphicBufferProducer(ANativeWindow* window) { + int val; + if (window->query(window, NATIVE_WINDOW_CONCRETE_TYPE, &val) >= 0 && + val == NATIVE_WINDOW_SURFACE) { + return ((Surface*) window)->mGraphicBufferProducer; + } + return nullptr; + } + + static sp getSurfaceControlHandle(ANativeWindow* window) { + int val; + if (window->query(window, NATIVE_WINDOW_CONCRETE_TYPE, &val) >= 0 && + val == NATIVE_WINDOW_SURFACE) { + return ((Surface*) window)->mSurfaceControlHandle; + } + return nullptr; + } + /* Attaches a sideband buffer stream to the Surface's IGraphicBufferProducer. * * A sideband stream is a device-specific mechanism for passing buffers diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp index b0750809f8..c345385839 100644 --- a/libs/nativewindow/ANativeWindow.cpp +++ b/libs/nativewindow/ANativeWindow.cpp @@ -20,10 +20,15 @@ // from nativewindow/includes/system/window.h // (not to be confused with the compatibility-only window.h from system/core/includes) #include +#include #include +#include #include +#include +#include +#include using namespace android; @@ -59,6 +64,13 @@ static bool isDataSpaceValid(ANativeWindow* window, int32_t dataSpace) { return false; } } +static sp IGraphicBufferProducer_from_ANativeWindow(ANativeWindow* window) { + return Surface::getIGraphicBufferProducer(window); +} + +static sp SurfaceControlHandle_from_ANativeWindow(ANativeWindow* window) { + return Surface::getSurfaceControlHandle(window); +} /************************************************************************************************** * NDK @@ -350,6 +362,42 @@ int ANativeWindow_setAutoPrerotation(ANativeWindow* window, bool autoPrerotation return native_window_set_auto_prerotation(window, autoPrerotation); } +binder_status_t ANativeWindow_readFromParcel( + const AParcel* _Nonnull parcel, ANativeWindow* _Nullable* _Nonnull outWindow) { + const Parcel* nativeParcel = AParcel_viewPlatformParcel(parcel); + + // Use a android::view::Surface to unparcel the window + std::shared_ptr shimSurface = std::shared_ptr(); + status_t ret = shimSurface->readFromParcel(nativeParcel); + if (ret != OK) { + ALOGE("%s: Error: Failed to create android::view::Surface from AParcel", __FUNCTION__); + return STATUS_BAD_VALUE; + } + sp surface = sp::make( + shimSurface->graphicBufferProducer, false, shimSurface->surfaceControlHandle); + ANativeWindow* anw = surface.get(); + ANativeWindow_acquire(anw); + *outWindow = anw; + return STATUS_OK; +} + +binder_status_t ANativeWindow_writeToParcel( + ANativeWindow* _Nonnull window, AParcel* _Nonnull parcel) { + int value; + int err = (*window->query)(window, NATIVE_WINDOW_CONCRETE_TYPE, &value); + if (err != OK || value != NATIVE_WINDOW_SURFACE) { + ALOGE("Error: ANativeWindow is not backed by Surface"); + return STATUS_BAD_VALUE; + } + // Use a android::view::Surface to parcelize the window + std::shared_ptr shimSurface = std::shared_ptr(); + shimSurface->graphicBufferProducer = IGraphicBufferProducer_from_ANativeWindow(window); + shimSurface->surfaceControlHandle = SurfaceControlHandle_from_ANativeWindow(window); + + Parcel* nativeParcel = AParcel_viewPlatformParcel(parcel); + return shimSurface->writeToParcel(nativeParcel); +} + /************************************************************************************************** * apex-stable **************************************************************************************************/ diff --git a/libs/nativewindow/Android.bp b/libs/nativewindow/Android.bp index 3b58265440..bc0bfc52d5 100644 --- a/libs/nativewindow/Android.bp +++ b/libs/nativewindow/Android.bp @@ -110,9 +110,11 @@ cc_library { static_libs: [ "libarect", "libgrallocusage", + "libgui_aidl_static", ], header_libs: [ + "libgui_headers", "libarect_headers", "libnativebase_headers", "libnativewindow_headers", diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h index 281ec52528..a27e3dd503 100644 --- a/libs/nativewindow/include/android/native_window.h +++ b/libs/nativewindow/include/android/native_window.h @@ -376,7 +376,7 @@ int32_t ANativeWindow_clearFrameRate(ANativeWindow* window) __INTRODUCED_IN(__ANDROID_API_U__); #ifdef __cplusplus -}; +} #endif #endif // ANDROID_NATIVE_WINDOW_H diff --git a/libs/nativewindow/include/android/native_window_aidl.h b/libs/nativewindow/include/android/native_window_aidl.h new file mode 100644 index 0000000000..a252245a10 --- /dev/null +++ b/libs/nativewindow/include/android/native_window_aidl.h @@ -0,0 +1,161 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file native_window_aidl.h + * @brief NativeWindow NDK AIDL glue code + */ + +/** + * @addtogroup ANativeWindow + * + * Parcelable support for ANativeWindow. Can be used with libbinder_ndk + * + * @{ + */ + +#ifndef ANDROID_NATIVE_WINDOW_AIDL_H +#define ANDROID_NATIVE_WINDOW_AIDL_H + +#include +#include +#include + +__BEGIN_DECLS + +/** + * Read an ANativeWindow from a AParcel. The output buffer will have an + * initial reference acquired and will need to be released with + * ANativeWindow_release. + * + * Available since API level 34. + * + * \return STATUS_OK on success + * STATUS_BAD_VALUE if the parcel or outBuffer is null, or if there's an + * issue deserializing (eg, corrupted parcel) + * STATUS_BAD_TYPE if the parcel's current data position is not that of + * an ANativeWindow type + * STATUS_NO_MEMORY if an allocation fails + */ +binder_status_t ANativeWindow_readFromParcel(const AParcel* _Nonnull parcel, + ANativeWindow* _Nullable* _Nonnull outWindow) __INTRODUCED_IN(__ANDROID_API_U__); + +/** + * Write an ANativeWindow to an AParcel. + * + * Available since API level 34. + * + * \return STATUS_OK on success. + * STATUS_BAD_VALUE if either buffer or parcel is null, or if the ANativeWindow* + * fails to serialize (eg, internally corrupted) + * STATUS_NO_MEMORY if the parcel runs out of space to store the buffer & is + * unable to allocate more + * STATUS_FDS_NOT_ALLOWED if the parcel does not allow storing FDs + */ +binder_status_t ANativeWindow_writeToParcel(ANativeWindow* _Nonnull window, + AParcel* _Nonnull parcel) __INTRODUCED_IN(__ANDROID_API_U__); + +__END_DECLS + +// Only enable the AIDL glue helper if this is C++ +#ifdef __cplusplus + +namespace aidl::android::hardware { + +/** + * Wrapper class that enables interop with AIDL NDK generation + * Takes ownership of the ANativeWindow* given to it in reset() and will automatically + * destroy it in the destructor, similar to a smart pointer container + */ +class NativeWindow { +public: + NativeWindow() noexcept {} + explicit NativeWindow(ANativeWindow* _Nullable window) { + reset(window); + } + + explicit NativeWindow(NativeWindow&& other) noexcept { + mWindow = other.release(); // steal ownership from r-value + } + + ~NativeWindow() { + reset(); + } + + binder_status_t readFromParcel(const AParcel* _Nonnull parcel) { + reset(); + return ANativeWindow_readFromParcel(parcel, &mWindow); + } + + binder_status_t writeToParcel(AParcel* _Nonnull parcel) const { + if (!mWindow) { + return STATUS_BAD_VALUE; + } + return ANativeWindow_writeToParcel(mWindow, parcel); + } + + /** + * Destroys any currently owned ANativeWindow* and takes ownership of the given + * ANativeWindow* + * + * @param buffer The buffer to take ownership of + */ + void reset(ANativeWindow* _Nullable window = nullptr) noexcept { + if (mWindow) { + ANativeWindow_release(mWindow); + mWindow = nullptr; + } + if (window != nullptr) { + ANativeWindow_acquire(window); + } + mWindow = window; + } + inline ANativeWindow* _Nullable operator-> () const { return mWindow; } + inline ANativeWindow* _Nullable get() const { return mWindow; } + inline explicit operator bool () const { return mWindow != nullptr; } + + NativeWindow& operator=(NativeWindow&& other) noexcept { + mWindow = other.release(); // steal ownership from r-value + return *this; + } + + /** + * Stops managing any contained ANativeWindow*, returning it to the caller. Ownership + * is released. + * @return ANativeWindow* or null if this was empty + */ + [[nodiscard]] ANativeWindow* _Nullable release() noexcept { + ANativeWindow* _Nullable ret = mWindow; + mWindow = nullptr; + return ret; + } +private: + ANativeWindow* _Nullable mWindow = nullptr; + NativeWindow(const NativeWindow &other) = delete; + NativeWindow& operator=(const NativeWindow &other) = delete; +}; + +} // aidl::android::hardware + // +namespace aidl::android::view { + using Surface = aidl::android::hardware::NativeWindow; +} + +#endif // __cplusplus + +#endif // ANDROID_NATIVE_WINDOW_AIDL_H + +/** @} */ diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt index ce108b6096..76d23fab1d 100644 --- a/libs/nativewindow/libnativewindow.map.txt +++ b/libs/nativewindow/libnativewindow.map.txt @@ -57,6 +57,8 @@ LIBNATIVEWINDOW { ANativeWindow_setUsage; # llndk ANativeWindow_tryAllocateBuffers; # introduced=30 ANativeWindow_unlockAndPost; + ANativeWindow_readFromParcel; # introduced=UpsideDownCake + ANativeWindow_writeToParcel; # introduced=UpsideDownCake local: *; }; -- GitLab From 98f204da7bbfe41a95dd2195810783fd92f25c96 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 3 Nov 2022 08:39:31 -0700 Subject: [PATCH 0446/1310] Remove unused function from TouchState The getWindow function is not used. Let's delete it. Bug: 211379801 Test: atest inputflinger_tests Change-Id: I66992111d93f7d522c457a909e451a57a3c9a3d7 --- services/inputflinger/dispatcher/TouchState.cpp | 10 ---------- services/inputflinger/dispatcher/TouchState.h | 1 - 2 files changed, 11 deletions(-) diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index eb31046d9c..a3f45cfce3 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -138,14 +138,4 @@ sp TouchState::getWallpaperWindow() const { return nullptr; } -sp TouchState::getWindow(const sp& token) const { - for (const TouchedWindow& touchedWindow : windows) { - const auto& windowHandle = touchedWindow.windowHandle; - if (windowHandle->getToken() == token) { - return windowHandle; - } - } - return nullptr; -} - } // namespace android::inputdispatcher diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index d32461184b..f6e9fb69f8 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -59,7 +59,6 @@ struct TouchState { sp getFirstForegroundWindowHandle() const; bool isSlippery() const; sp getWallpaperWindow() const; - sp getWindow(const sp&) const; }; } // namespace inputdispatcher -- GitLab From 285f8c162428bd7a6273b46fcc9d86ca2935cf85 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Tue, 11 Oct 2022 17:12:14 -0700 Subject: [PATCH 0447/1310] SF: store the render frame rate in RefreshRateConfigs::Policy Add plumbing for the render frame rate passed from DM and update the policy accordingly. Test: SF unit tests Bug: 241460058 Change-Id: I86088001d6d6e5302516f42aa5c9ede4a918dae1 --- libs/gui/SurfaceComposerClient.cpp | 34 ++----- .../aidl/android/gui/DisplayModeSpecs.aidl | 56 ++++++++++- .../aidl/android/gui/ISurfaceComposer.aidl | 22 +---- libs/gui/fuzzer/libgui_fuzzer_utils.h | 4 +- .../libgui_surfaceComposerClient_fuzzer.cpp | 34 +++++-- libs/gui/include/gui/SurfaceComposerClient.h | 13 +-- libs/gui/tests/Surface_test.cpp | 8 +- .../Scheduler/RefreshRateSelector.cpp | 21 ++-- .../Scheduler/RefreshRateSelector.h | 41 ++++---- .../Scheduler/include/scheduler/Fps.h | 23 +++++ services/surfaceflinger/SurfaceFlinger.cpp | 96 ++++++++----------- services/surfaceflinger/SurfaceFlinger.h | 20 +--- .../fuzzer/surfaceflinger_fuzzers_utils.h | 12 +-- .../surfaceflinger/tests/Credentials_test.cpp | 19 +--- .../tests/DisplayConfigs_test.cpp | 92 +++++------------- .../unittests/RefreshRateSelectorTest.cpp | 29 ++++-- .../SurfaceFlinger_DisplayModeSwitching.cpp | 26 +++-- .../tests/unittests/TestableSurfaceFlinger.h | 11 +-- .../unittests/mock/MockDisplayModeSpecs.h | 35 +++++++ 19 files changed, 297 insertions(+), 299 deletions(-) create mode 100644 services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 9c2ce0f242..9e175ec42e 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2375,42 +2375,22 @@ status_t SurfaceComposerClient::getActiveDisplayMode(const sp& display, return NAME_NOT_FOUND; } -status_t SurfaceComposerClient::setDesiredDisplayModeSpecs( - const sp& displayToken, ui::DisplayModeId defaultMode, bool allowGroupSwitching, - float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin, - float appRequestRefreshRateMax) { +status_t SurfaceComposerClient::setDesiredDisplayModeSpecs(const sp& displayToken, + const gui::DisplayModeSpecs& specs) { binder::Status status = - ComposerServiceAIDL::getComposerService() - ->setDesiredDisplayModeSpecs(displayToken, defaultMode, allowGroupSwitching, - primaryRefreshRateMin, primaryRefreshRateMax, - appRequestRefreshRateMin, - appRequestRefreshRateMax); + ComposerServiceAIDL::getComposerService()->setDesiredDisplayModeSpecs(displayToken, + specs); return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getDesiredDisplayModeSpecs(const sp& displayToken, - ui::DisplayModeId* outDefaultMode, - bool* outAllowGroupSwitching, - float* outPrimaryRefreshRateMin, - float* outPrimaryRefreshRateMax, - float* outAppRequestRefreshRateMin, - float* outAppRequestRefreshRateMax) { - if (!outDefaultMode || !outAllowGroupSwitching || !outPrimaryRefreshRateMin || - !outPrimaryRefreshRateMax || !outAppRequestRefreshRateMin || !outAppRequestRefreshRateMax) { + gui::DisplayModeSpecs* outSpecs) { + if (!outSpecs) { return BAD_VALUE; } - gui::DisplayModeSpecs specs; binder::Status status = ComposerServiceAIDL::getComposerService()->getDesiredDisplayModeSpecs(displayToken, - &specs); - if (status.isOk()) { - *outDefaultMode = specs.defaultMode; - *outAllowGroupSwitching = specs.allowGroupSwitching; - *outPrimaryRefreshRateMin = specs.primaryRefreshRateMin; - *outPrimaryRefreshRateMax = specs.primaryRefreshRateMax; - *outAppRequestRefreshRateMin = specs.appRequestRefreshRateMin; - *outAppRequestRefreshRateMax = specs.appRequestRefreshRateMax; - } + outSpecs); return statusTFromBinderStatus(status); } diff --git a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl index fb4fcdf8e8..af138c7539 100644 --- a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl +++ b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl @@ -18,10 +18,58 @@ package android.gui; /** @hide */ parcelable DisplayModeSpecs { + /** + * Defines the refresh rates ranges that should be used by SF. + */ + parcelable RefreshRateRanges { + /** + * Defines a range of refresh rates. + */ + parcelable RefreshRateRange { + float min; + float max; + } + + /** + * The range of refresh rates that the display should run at. + */ + RefreshRateRange physical; + + /** + * The range of refresh rates that apps should render at. + */ + RefreshRateRange render; + } + + /** + * Base mode ID. This is what system defaults to for all other settings, or + * if the refresh rate range is not available. + */ int defaultMode; + + /** + * If true this will allow switching between modes in different display configuration + * groups. This way the user may see visual interruptions when the display mode changes. + */ + boolean allowGroupSwitching; - float primaryRefreshRateMin; - float primaryRefreshRateMax; - float appRequestRefreshRateMin; - float appRequestRefreshRateMax; + + /** + * The primary physical and render refresh rate ranges represent DisplayManager's general + * guidance on the display modes SurfaceFlinger will consider when switching refresh + * rates and scheduling the frame rate. Unless SurfaceFlinger has a specific reason to do + * otherwise, it will stay within this range. + */ + RefreshRateRanges primaryRanges; + + /** + * The app request physical and render refresh rate ranges allow SurfaceFlinger to consider + * more display modes when switching refresh rates. Although SurfaceFlinger will + * generally stay within the primary range, specific considerations, such as layer frame + * rate settings specified via the setFrameRate() API, may cause SurfaceFlinger to go + * outside the primary range. SurfaceFlinger never goes outside the app request range. + * The app request range will be greater than or equal to the primary refresh rate range, + * never smaller. + */ + RefreshRateRanges appRequestRanges; } diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 92d9e7799c..40410fb59e 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -327,25 +327,9 @@ interface ISurfaceComposer { /** * Sets the refresh rate boundaries for the display. * - * The primary refresh rate range represents display manager's general guidance on the display - * modes we'll consider when switching refresh rates. Unless we get an explicit signal from an - * app, we should stay within this range. - * - * The app request refresh rate range allows us to consider more display modes when switching - * refresh rates. Although we should generally stay within the primary range, specific - * considerations, such as layer frame rate settings specified via the setFrameRate() api, may - * cause us to go outside the primary range. We never go outside the app request range. The app - * request range will be greater than or equal to the primary refresh rate range, never smaller. - * - * defaultMode is used to narrow the list of display modes SurfaceFlinger will consider - * switching between. Only modes with a mode group and resolution matching defaultMode - * will be considered for switching. The defaultMode corresponds to an ID of mode in the list - * of supported modes returned from getDynamicDisplayInfo(). - */ - void setDesiredDisplayModeSpecs( - IBinder displayToken, int defaultMode, - boolean allowGroupSwitching, float primaryRefreshRateMin, float primaryRefreshRateMax, - float appRequestRefreshRateMin, float appRequestRefreshRateMax); + * @see DisplayModeSpecs.aidl for details. + */ + void setDesiredDisplayModeSpecs(IBinder displayToken, in DisplayModeSpecs specs); DisplayModeSpecs getDesiredDisplayModeSpecs(IBinder displayToken); diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h index 202517067f..9d1ee8f65b 100644 --- a/libs/gui/fuzzer/libgui_fuzzer_utils.h +++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h @@ -127,9 +127,7 @@ public: MOCK_METHOD(binder::Status, removeTunnelModeEnabledListener, (const sp&), (override)); MOCK_METHOD(binder::Status, setDesiredDisplayModeSpecs, - (const sp&, int32_t, bool, float, float, float, - float appRequestRefreshRateMax), - (override)); + (const sp&, const gui::DisplayModeSpecs&), (override)); MOCK_METHOD(binder::Status, getDesiredDisplayModeSpecs, (const sp&, gui::DisplayModeSpecs*), (override)); MOCK_METHOD(binder::Status, getDisplayBrightnessSupport, (const sp&, bool*), diff --git a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp index eecbe0fe21..57720dd513 100644 --- a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp +++ b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp @@ -123,10 +123,37 @@ private: sp makeSurfaceControl(); BlurRegion getBlurRegion(); void fuzzOnPullAtom(); + gui::DisplayModeSpecs getDisplayModeSpecs(); FuzzedDataProvider mFdp; }; +gui::DisplayModeSpecs SurfaceComposerClientFuzzer::getDisplayModeSpecs() { + const auto getRefreshRateRange = [&] { + gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange range; + range.min = mFdp.ConsumeFloatingPoint(); + range.max = mFdp.ConsumeFloatingPoint(); + return range; + }; + + const auto getRefreshRateRanges = [&] { + gui::DisplayModeSpecs::RefreshRateRanges ranges; + ranges.physical = getRefreshRateRange(); + ranges.render = getRefreshRateRange(); + return ranges; + }; + + String8 displayName((mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes)).c_str()); + sp displayToken = + SurfaceComposerClient::createDisplay(displayName, mFdp.ConsumeBool() /*secure*/); + gui::DisplayModeSpecs specs; + specs.defaultMode = mFdp.ConsumeIntegral(); + specs.allowGroupSwitching = mFdp.ConsumeBool(); + specs.primaryRanges = getRefreshRateRanges(); + specs.appRequestRanges = getRefreshRateRanges(); + return specs; +} + BlurRegion SurfaceComposerClientFuzzer::getBlurRegion() { int32_t left = mFdp.ConsumeIntegral(); int32_t right = mFdp.ConsumeIntegral(); @@ -247,12 +274,7 @@ void SurfaceComposerClientFuzzer::invokeSurfaceComposerClient() { String8 displayName((mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes)).c_str()); sp displayToken = SurfaceComposerClient::createDisplay(displayName, mFdp.ConsumeBool() /*secure*/); - SurfaceComposerClient::setDesiredDisplayModeSpecs(displayToken, mFdp.ConsumeIntegral(), - mFdp.ConsumeBool() /*allowGroupSwitching*/, - mFdp.ConsumeFloatingPoint(), - mFdp.ConsumeFloatingPoint(), - mFdp.ConsumeFloatingPoint(), - mFdp.ConsumeFloatingPoint()); + SurfaceComposerClient::setDesiredDisplayModeSpecs(displayToken, getDisplayModeSpecs()); ui::ColorMode colorMode = mFdp.PickValueInArray(kColormodes); SurfaceComposerClient::setActiveColorMode(displayToken, colorMode); diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index c450e85857..2038f1477a 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -159,18 +159,11 @@ public: static status_t getActiveDisplayMode(const sp& display, ui::DisplayMode*); // Sets the refresh rate boundaries for the display. - static status_t setDesiredDisplayModeSpecs( - const sp& displayToken, ui::DisplayModeId defaultMode, - bool allowGroupSwitching, float primaryRefreshRateMin, float primaryRefreshRateMax, - float appRequestRefreshRateMin, float appRequestRefreshRateMax); + static status_t setDesiredDisplayModeSpecs(const sp& displayToken, + const gui::DisplayModeSpecs&); // Gets the refresh rate boundaries for the display. static status_t getDesiredDisplayModeSpecs(const sp& displayToken, - ui::DisplayModeId* outDefaultMode, - bool* outAllowGroupSwitching, - float* outPrimaryRefreshRateMin, - float* outPrimaryRefreshRateMax, - float* outAppRequestRefreshRateMin, - float* outAppRequestRefreshRateMax); + gui::DisplayModeSpecs*); // Get the coordinates of the display's native color primaries static status_t getDisplayNativePrimaries(const sp& display, diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 346b686466..67c669ddb7 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -920,16 +920,12 @@ public: } binder::Status setDesiredDisplayModeSpecs(const sp& /*displayToken*/, - int32_t /*defaultMode*/, bool /*allowGroupSwitching*/, - float /*primaryRefreshRateMin*/, - float /*primaryRefreshRateMax*/, - float /*appRequestRefreshRateMin*/, - float /*appRequestRefreshRateMax*/) override { + const gui::DisplayModeSpecs&) override { return binder::Status::ok(); } binder::Status getDesiredDisplayModeSpecs(const sp& /*displayToken*/, - gui::DisplayModeSpecs* /*outSpecs*/) override { + gui::DisplayModeSpecs*) override { return binder::Status::ok(); } diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index 40af6ee575..f22f9e7249 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -164,9 +164,10 @@ struct RefreshRateSelector::RefreshRateScoreComparator { std::string RefreshRateSelector::Policy::toString() const { return base::StringPrintf("{defaultModeId=%d, allowGroupSwitching=%s" - ", primaryRange=%s, appRequestRange=%s}", + ", primaryRanges=%s, appRequestRanges=%s}", defaultMode.value(), allowGroupSwitching ? "true" : "false", - to_string(primaryRange).c_str(), to_string(appRequestRange).c_str()); + to_string(primaryRanges).c_str(), + to_string(appRequestRanges).c_str()); } std::pair RefreshRateSelector::getDisplayFrames(nsecs_t layerPeriod, @@ -381,7 +382,7 @@ auto RefreshRateSelector::getRankedRefreshRatesLocked(const std::vectorprimaryRange.min, policy->primaryRange.max); + isApproxEqual(policy->primaryRanges.physical.min, policy->primaryRanges.physical.max); if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) { ALOGV("Idle"); @@ -450,7 +451,7 @@ auto RefreshRateSelector::getRankedRefreshRatesLocked(const std::vectorprimaryRange.includes(mode->getFps()); + const bool inPrimaryRange = policy->primaryRanges.physical.includes(mode->getFps()); if ((primaryRangeIsSingleRate || !inPrimaryRange) && !(layer.focused && (layer.vote == LayerVoteType::ExplicitDefault || @@ -902,7 +903,7 @@ void RefreshRateSelector::updateDisplayModes(DisplayModes modes, DisplayModeId a bool RefreshRateSelector::isPolicyValidLocked(const Policy& policy) const { // defaultMode must be a valid mode, and within the given refresh rate range. if (const auto mode = mDisplayModes.get(policy.defaultMode)) { - if (!policy.primaryRange.includes(mode->get()->getFps())) { + if (!policy.primaryRanges.physical.includes(mode->get()->getFps())) { ALOGE("Default mode is not in the primary range."); return false; } @@ -912,8 +913,8 @@ bool RefreshRateSelector::isPolicyValidLocked(const Policy& policy) const { } using namespace fps_approx_ops; - return policy.appRequestRange.min <= policy.primaryRange.min && - policy.appRequestRange.max >= policy.primaryRange.max; + return policy.appRequestRanges.physical.min <= policy.primaryRanges.physical.min && + policy.appRequestRanges.physical.max >= policy.primaryRanges.physical.max; } auto RefreshRateSelector::setPolicy(const PolicyVariant& policy) -> SetPolicyResult { @@ -1026,8 +1027,8 @@ void RefreshRateSelector::constructAvailableRefreshRates() { return modes; }; - mPrimaryRefreshRates = filterRefreshRates(policy->primaryRange, "primary"); - mAppRequestRefreshRates = filterRefreshRates(policy->appRequestRange, "app request"); + mPrimaryRefreshRates = filterRefreshRates(policy->primaryRanges.physical, "primary"); + mAppRequestRefreshRates = filterRefreshRates(policy->appRequestRanges.physical, "app request"); } Fps RefreshRateSelector::findClosestKnownFrameRate(Fps frameRate) const { @@ -1067,7 +1068,7 @@ auto RefreshRateSelector::getIdleTimerAction() const -> KernelIdleTimerAction { if (minByPolicy == maxByPolicy) { // Turn on the timer when the min of the primary range is below the device min. if (const Policy* currentPolicy = getCurrentPolicyLocked(); - isApproxLess(currentPolicy->primaryRange.min, deviceMinFps)) { + isApproxLess(currentPolicy->primaryRanges.physical.min, deviceMinFps)) { return KernelIdleTimerAction::TurnOn; } return KernelIdleTimerAction::TurnOff; diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h index bff16d3010..887d81566a 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h @@ -67,40 +67,31 @@ public: DisplayModeId defaultMode; // Whether or not we switch mode groups to get the best frame rate. bool allowGroupSwitching = kAllowGroupSwitchingDefault; - // The primary refresh rate range represents display manager's general guidance on the - // display modes we'll consider when switching refresh rates. Unless we get an explicit - // signal from an app, we should stay within this range. - FpsRange primaryRange; - // The app request refresh rate range allows us to consider more display modes when - // switching refresh rates. Although we should generally stay within the primary range, - // specific considerations, such as layer frame rate settings specified via the - // setFrameRate() api, may cause us to go outside the primary range. We never go outside the - // app request range. The app request range will be greater than or equal to the primary - // refresh rate range, never smaller. - FpsRange appRequestRange; + // The primary refresh rate ranges. @see DisplayModeSpecs.aidl for details. + // TODO(b/257072060): use the render range when selecting SF render rate + // or the app override frame rate + FpsRanges primaryRanges; + // The app request refresh rate ranges. @see DisplayModeSpecs.aidl for details. + FpsRanges appRequestRanges; Policy() = default; - Policy(DisplayModeId defaultMode, FpsRange range) - : Policy(defaultMode, kAllowGroupSwitchingDefault, range, range) {} + Policy(DisplayModeId defaultMode, FpsRange range, + bool allowGroupSwitching = kAllowGroupSwitchingDefault) + : Policy(defaultMode, FpsRanges{range, range}, FpsRanges{range, range}, + allowGroupSwitching) {} - Policy(DisplayModeId defaultMode, bool allowGroupSwitching, FpsRange range) - : Policy(defaultMode, allowGroupSwitching, range, range) {} - - Policy(DisplayModeId defaultMode, FpsRange primaryRange, FpsRange appRequestRange) - : Policy(defaultMode, kAllowGroupSwitchingDefault, primaryRange, appRequestRange) {} - - Policy(DisplayModeId defaultMode, bool allowGroupSwitching, FpsRange primaryRange, - FpsRange appRequestRange) + Policy(DisplayModeId defaultMode, FpsRanges primaryRanges, FpsRanges appRequestRanges, + bool allowGroupSwitching = kAllowGroupSwitchingDefault) : defaultMode(defaultMode), allowGroupSwitching(allowGroupSwitching), - primaryRange(primaryRange), - appRequestRange(appRequestRange) {} + primaryRanges(primaryRanges), + appRequestRanges(appRequestRanges) {} bool operator==(const Policy& other) const { using namespace fps_approx_ops; - return defaultMode == other.defaultMode && primaryRange == other.primaryRange && - appRequestRange == other.appRequestRange && + return defaultMode == other.defaultMode && primaryRanges == other.primaryRanges && + appRequestRanges == other.appRequestRanges && allowGroupSwitching == other.allowGroupSwitching; } diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h index bd4f40989d..d89f685678 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h @@ -68,6 +68,15 @@ struct FpsRange { bool includes(Fps) const; }; +struct FpsRanges { + // The range of refresh rates that refers to the display mode setting. + FpsRange physical; + + // the range of frame rates that refers to the render rate, which is + // the rate that frames are swapped. + FpsRange render; +}; + static_assert(std::is_trivially_copyable_v); constexpr Fps operator""_Hz(unsigned long long frequency) { @@ -127,6 +136,14 @@ inline bool operator!=(FpsRange lhs, FpsRange rhs) { return !(lhs == rhs); } +inline bool operator==(const FpsRanges& lhs, const FpsRanges& rhs) { + return lhs.physical == rhs.physical && lhs.render == rhs.render; +} + +inline bool operator!=(const FpsRanges& lhs, const FpsRanges& rhs) { + return !(lhs == rhs); +} + } // namespace fps_approx_ops inline bool FpsRange::includes(Fps fps) const { @@ -151,4 +168,10 @@ inline std::string to_string(FpsRange range) { return base::StringPrintf("[%s, %s]", to_string(min).c_str(), to_string(max).c_str()); } +inline std::string to_string(FpsRanges ranges) { + const auto& [physical, render] = ranges; + return base::StringPrintf("{physical=%s, render=%s}", to_string(physical).c_str(), + to_string(render).c_str()); +} + } // namespace android diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 89d905a4ad..2ca39d07bd 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1132,8 +1132,8 @@ status_t SurfaceFlinger::setActiveModeFromBackdoor(const sprefreshRateSelector().getCurrentPolicy().allowGroupSwitching; const scheduler::RefreshRateSelector::DisplayManagerPolicy policy{modeId, - allowGroupSwitching, - {fps, fps}}; + {fps, fps}, + allowGroupSwitching}; return setDesiredDisplayModeSpecsInternal(display, policy); }); @@ -6651,10 +6651,33 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( return NO_ERROR; } -status_t SurfaceFlinger::setDesiredDisplayModeSpecs( - const sp& displayToken, ui::DisplayModeId defaultMode, bool allowGroupSwitching, - float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin, - float appRequestRefreshRateMax) { +namespace { +FpsRange translate(const gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange& aidlRange) { + return FpsRange{Fps::fromValue(aidlRange.min), Fps::fromValue(aidlRange.max)}; +} + +FpsRanges translate(const gui::DisplayModeSpecs::RefreshRateRanges& aidlRanges) { + return FpsRanges{translate(aidlRanges.physical), translate(aidlRanges.render)}; +} + +gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange translate(const FpsRange& range) { + gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange aidlRange; + aidlRange.min = range.min.getValue(); + aidlRange.max = range.max.getValue(); + return aidlRange; +} + +gui::DisplayModeSpecs::RefreshRateRanges translate(const FpsRanges& ranges) { + gui::DisplayModeSpecs::RefreshRateRanges aidlRanges; + aidlRanges.physical = translate(ranges.physical); + aidlRanges.render = translate(ranges.render); + return aidlRanges; +} + +} // namespace + +status_t SurfaceFlinger::setDesiredDisplayModeSpecs(const sp& displayToken, + const gui::DisplayModeSpecs& specs) { ATRACE_CALL(); if (!displayToken) { @@ -6672,12 +6695,8 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecs( return INVALID_OPERATION; } else { using Policy = scheduler::RefreshRateSelector::DisplayManagerPolicy; - const Policy policy{DisplayModeId(defaultMode), - allowGroupSwitching, - {Fps::fromValue(primaryRefreshRateMin), - Fps::fromValue(primaryRefreshRateMax)}, - {Fps::fromValue(appRequestRefreshRateMin), - Fps::fromValue(appRequestRefreshRateMax)}}; + const Policy policy{DisplayModeId(specs.defaultMode), translate(specs.primaryRanges), + translate(specs.appRequestRanges), specs.allowGroupSwitching}; return setDesiredDisplayModeSpecsInternal(display, policy); } @@ -6687,16 +6706,10 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecs( } status_t SurfaceFlinger::getDesiredDisplayModeSpecs(const sp& displayToken, - ui::DisplayModeId* outDefaultMode, - bool* outAllowGroupSwitching, - float* outPrimaryRefreshRateMin, - float* outPrimaryRefreshRateMax, - float* outAppRequestRefreshRateMin, - float* outAppRequestRefreshRateMax) { + gui::DisplayModeSpecs* outSpecs) { ATRACE_CALL(); - if (!displayToken || !outDefaultMode || !outPrimaryRefreshRateMin || - !outPrimaryRefreshRateMax || !outAppRequestRefreshRateMin || !outAppRequestRefreshRateMax) { + if (!displayToken || !outSpecs) { return BAD_VALUE; } @@ -6712,12 +6725,10 @@ status_t SurfaceFlinger::getDesiredDisplayModeSpecs(const sp& displayTo scheduler::RefreshRateSelector::Policy policy = display->refreshRateSelector().getDisplayManagerPolicy(); - *outDefaultMode = policy.defaultMode.value(); - *outAllowGroupSwitching = policy.allowGroupSwitching; - *outPrimaryRefreshRateMin = policy.primaryRange.min.getValue(); - *outPrimaryRefreshRateMax = policy.primaryRange.max.getValue(); - *outAppRequestRefreshRateMin = policy.appRequestRange.min.getValue(); - *outAppRequestRefreshRateMax = policy.appRequestRange.max.getValue(); + outSpecs->defaultMode = policy.defaultMode.value(); + outSpecs->allowGroupSwitching = policy.allowGroupSwitching; + outSpecs->primaryRanges = translate(policy.primaryRanges); + outSpecs->appRequestRanges = translate(policy.appRequestRanges); return NO_ERROR; } @@ -7608,18 +7619,11 @@ binder::Status SurfaceComposerAIDL::removeTunnelModeEnabledListener( return binderStatusFromStatusT(status); } -binder::Status SurfaceComposerAIDL::setDesiredDisplayModeSpecs( - const sp& displayToken, int32_t defaultMode, bool allowGroupSwitching, - float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin, - float appRequestRefreshRateMax) { +binder::Status SurfaceComposerAIDL::setDesiredDisplayModeSpecs(const sp& displayToken, + const gui::DisplayModeSpecs& specs) { status_t status = checkAccessPermission(); if (status == OK) { - status = mFlinger->setDesiredDisplayModeSpecs(displayToken, - static_cast(defaultMode), - allowGroupSwitching, primaryRefreshRateMin, - primaryRefreshRateMax, - appRequestRefreshRateMin, - appRequestRefreshRateMax); + status = mFlinger->setDesiredDisplayModeSpecs(displayToken, specs); } return binderStatusFromStatusT(status); } @@ -7635,25 +7639,7 @@ binder::Status SurfaceComposerAIDL::getDesiredDisplayModeSpecs(const sp return binderStatusFromStatusT(status); } - ui::DisplayModeId displayModeId; - bool allowGroupSwitching; - float primaryRefreshRateMin; - float primaryRefreshRateMax; - float appRequestRefreshRateMin; - float appRequestRefreshRateMax; - status = mFlinger->getDesiredDisplayModeSpecs(displayToken, &displayModeId, - &allowGroupSwitching, &primaryRefreshRateMin, - &primaryRefreshRateMax, &appRequestRefreshRateMin, - &appRequestRefreshRateMax); - if (status == NO_ERROR) { - outSpecs->defaultMode = displayModeId; - outSpecs->allowGroupSwitching = allowGroupSwitching; - outSpecs->primaryRefreshRateMin = primaryRefreshRateMin; - outSpecs->primaryRefreshRateMax = primaryRefreshRateMax; - outSpecs->appRequestRefreshRateMin = appRequestRefreshRateMin; - outSpecs->appRequestRefreshRateMax = appRequestRefreshRateMax; - } - + status = mFlinger->getDesiredDisplayModeSpecs(displayToken, outSpecs); return binderStatusFromStatusT(status); } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index d2907626e0..c07e19ca6c 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -551,17 +551,8 @@ private: status_t addTunnelModeEnabledListener(const sp& listener); status_t removeTunnelModeEnabledListener(const sp& listener); status_t setDesiredDisplayModeSpecs(const sp& displayToken, - ui::DisplayModeId displayModeId, bool allowGroupSwitching, - float primaryRefreshRateMin, float primaryRefreshRateMax, - float appRequestRefreshRateMin, - float appRequestRefreshRateMax); - status_t getDesiredDisplayModeSpecs(const sp& displayToken, - ui::DisplayModeId* outDefaultMode, - bool* outAllowGroupSwitching, - float* outPrimaryRefreshRateMin, - float* outPrimaryRefreshRateMax, - float* outAppRequestRefreshRateMin, - float* outAppRequestRefreshRateMax); + const gui::DisplayModeSpecs&); + status_t getDesiredDisplayModeSpecs(const sp& displayToken, gui::DisplayModeSpecs*); status_t getDisplayBrightnessSupport(const sp& displayToken, bool* outSupport) const; status_t setDisplayBrightness(const sp& displayToken, const gui::DisplayBrightness& brightness); @@ -1451,11 +1442,8 @@ public: const sp& listener) override; binder::Status removeTunnelModeEnabledListener( const sp& listener) override; - binder::Status setDesiredDisplayModeSpecs(const sp& displayToken, int32_t defaultMode, - bool allowGroupSwitching, float primaryRefreshRateMin, - float primaryRefreshRateMax, - float appRequestRefreshRateMin, - float appRequestRefreshRateMax) override; + binder::Status setDesiredDisplayModeSpecs(const sp& displayToken, + const gui::DisplayModeSpecs&) override; binder::Status getDesiredDisplayModeSpecs(const sp& displayToken, gui::DisplayModeSpecs* outSpecs) override; binder::Status getDisplayBrightnessSupport(const sp& displayToken, diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 94de6e5274..9ba9b90b82 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -505,16 +505,8 @@ public: } void getDesiredDisplayModeSpecs(sp &display) { - ui::DisplayModeId outDefaultMode; - bool outAllowGroupSwitching; - float outPrimaryRefreshRateMin; - float outPrimaryRefreshRateMax; - float outAppRequestRefreshRateMin; - float outAppRequestRefreshRateMax; - mFlinger->getDesiredDisplayModeSpecs(display, &outDefaultMode, &outAllowGroupSwitching, - &outPrimaryRefreshRateMin, &outPrimaryRefreshRateMax, - &outAppRequestRefreshRateMin, - &outAppRequestRefreshRateMax); + gui::DisplayModeSpecs _; + mFlinger->getDesiredDisplayModeSpecs(display, &_); } void setVsyncConfig(FuzzedDataProvider *fdp) { diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp index 4f04934d34..16768441f0 100644 --- a/services/surfaceflinger/tests/Credentials_test.cpp +++ b/services/surfaceflinger/tests/Credentials_test.cpp @@ -207,23 +207,12 @@ TEST_F(CredentialsTest, GetDisplayNativePrimariesTest) { TEST_F(CredentialsTest, SetDesiredDisplayConfigsTest) { const auto display = getFirstDisplayToken(); - ui::DisplayModeId defaultMode; - bool allowGroupSwitching; - float primaryFpsMin; - float primaryFpsMax; - float appRequestFpsMin; - float appRequestFpsMax; - status_t res = - SurfaceComposerClient::getDesiredDisplayModeSpecs(display, &defaultMode, - &allowGroupSwitching, &primaryFpsMin, - &primaryFpsMax, &appRequestFpsMin, - &appRequestFpsMax); + gui::DisplayModeSpecs specs; + status_t res = SurfaceComposerClient::getDesiredDisplayModeSpecs(display, &specs); ASSERT_EQ(res, NO_ERROR); + gui::DisplayModeSpecs setSpecs; std::function condition = [=]() { - return SurfaceComposerClient::setDesiredDisplayModeSpecs(display, defaultMode, - allowGroupSwitching, primaryFpsMin, - primaryFpsMax, appRequestFpsMin, - appRequestFpsMax); + return SurfaceComposerClient::setDesiredDisplayModeSpecs(display, specs); }; ASSERT_NO_FATAL_FAILURE(checkWithPrivileges(condition, NO_ERROR, PERMISSION_DENIED)); } diff --git a/services/surfaceflinger/tests/DisplayConfigs_test.cpp b/services/surfaceflinger/tests/DisplayConfigs_test.cpp index 02c934e576..10dae4636e 100644 --- a/services/surfaceflinger/tests/DisplayConfigs_test.cpp +++ b/services/surfaceflinger/tests/DisplayConfigs_test.cpp @@ -39,37 +39,19 @@ namespace android { */ class RefreshRateRangeTest : public ::testing::Test { private: - ui::DisplayModeId initialDefaultMode; - bool initialAllowGroupSwitching; - float initialPrimaryMin; - float initialPrimaryMax; - float initialAppRequestMin; - float initialAppRequestMax; + gui::DisplayModeSpecs mSpecs; protected: void SetUp() override { const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); ASSERT_FALSE(ids.empty()); mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); - status_t res = - SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, - &initialDefaultMode, - &initialAllowGroupSwitching, - &initialPrimaryMin, - &initialPrimaryMax, - &initialAppRequestMin, - &initialAppRequestMax); + status_t res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &mSpecs); ASSERT_EQ(res, NO_ERROR); } void TearDown() override { - status_t res = - SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, initialDefaultMode, - initialAllowGroupSwitching, - initialPrimaryMin, - initialPrimaryMax, - initialAppRequestMin, - initialAppRequestMax); + status_t res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, mSpecs); ASSERT_EQ(res, NO_ERROR); } @@ -85,61 +67,39 @@ TEST_F(RefreshRateRangeTest, setAllConfigs) { ASSERT_EQ(res, NO_ERROR); ASSERT_GT(modes.size(), 0); + gui::DisplayModeSpecs setSpecs; + setSpecs.allowGroupSwitching = false; for (size_t i = 0; i < modes.size(); i++) { - res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, modes[i].id, false, - modes[i].refreshRate, - modes[i].refreshRate, - modes[i].refreshRate, - modes[i].refreshRate); + setSpecs.defaultMode = modes[i].id; + setSpecs.primaryRanges.physical.min = modes[i].refreshRate; + setSpecs.primaryRanges.physical.max = modes[i].refreshRate; + setSpecs.primaryRanges.render = setSpecs.primaryRanges.physical; + setSpecs.appRequestRanges = setSpecs.primaryRanges; + res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, setSpecs); ASSERT_EQ(res, NO_ERROR); - ui::DisplayModeId defaultConfig; - bool allowGroupSwitching; - float primaryRefreshRateMin; - float primaryRefreshRateMax; - float appRequestRefreshRateMin; - float appRequestRefreshRateMax; - res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &defaultConfig, - &allowGroupSwitching, - &primaryRefreshRateMin, - &primaryRefreshRateMax, - &appRequestRefreshRateMin, - &appRequestRefreshRateMax); + gui::DisplayModeSpecs getSpecs; + res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &getSpecs); ASSERT_EQ(res, NO_ERROR); - ASSERT_EQ(defaultConfig, i); - ASSERT_EQ(allowGroupSwitching, false); - ASSERT_EQ(primaryRefreshRateMin, modes[i].refreshRate); - ASSERT_EQ(primaryRefreshRateMax, modes[i].refreshRate); - ASSERT_EQ(appRequestRefreshRateMin, modes[i].refreshRate); - ASSERT_EQ(appRequestRefreshRateMax, modes[i].refreshRate); + ASSERT_EQ(setSpecs, getSpecs); } } void RefreshRateRangeTest::testSetAllowGroupSwitching(bool allowGroupSwitching) { - status_t res = - SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, 0, allowGroupSwitching, - 0.f, 90.f, 0.f, 90.f); + gui::DisplayModeSpecs setSpecs; + setSpecs.defaultMode = 0; + setSpecs.allowGroupSwitching = allowGroupSwitching; + setSpecs.primaryRanges.physical.min = 0; + setSpecs.primaryRanges.physical.max = 90; + setSpecs.primaryRanges.render = setSpecs.primaryRanges.physical; + setSpecs.appRequestRanges = setSpecs.primaryRanges; + + status_t res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, setSpecs); ASSERT_EQ(res, NO_ERROR); - ui::DisplayModeId defaultConfig; - bool newAllowGroupSwitching; - float primaryRefreshRateMin; - float primaryRefreshRateMax; - float appRequestRefreshRateMin; - float appRequestRefreshRateMax; - - res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &defaultConfig, - &newAllowGroupSwitching, - &primaryRefreshRateMin, - &primaryRefreshRateMax, - &appRequestRefreshRateMin, - &appRequestRefreshRateMax); + gui::DisplayModeSpecs getSpecs; + res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &getSpecs); ASSERT_EQ(res, NO_ERROR); - ASSERT_EQ(defaultConfig, 0); - ASSERT_EQ(newAllowGroupSwitching, allowGroupSwitching); - ASSERT_EQ(primaryRefreshRateMin, 0.f); - ASSERT_EQ(primaryRefreshRateMax, 90.f); - ASSERT_EQ(appRequestRefreshRateMin, 0.f); - ASSERT_EQ(appRequestRefreshRateMax, 90.f); + ASSERT_EQ(setSpecs, getSpecs); } TEST_F(RefreshRateRangeTest, setAllowGroupSwitching) { diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp index e7ae53c01a..9689ddbb51 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp @@ -357,7 +357,7 @@ TEST_F(RefreshRateSelectorTest, getBestRefreshRate_noLayers) { constexpr bool kAllowGroupSwitching = true; EXPECT_EQ(SetPolicyResult::Changed, selector.setDisplayManagerPolicy( - {kModeId90, kAllowGroupSwitching, {0_Hz, 90_Hz}})); + {kModeId90, {0_Hz, 90_Hz}, kAllowGroupSwitching})); EXPECT_EQ(kMode90_G1, selector.getBestRefreshRate()); } } @@ -1105,7 +1105,7 @@ TEST_F(RefreshRateSelectorTest, getMinRefreshRatesByPolicyOutsideTheGroup) { TestableRefreshRateSelector selector(kModes_30_60_90, kModeId72); EXPECT_EQ(SetPolicyResult::Changed, - selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}})); const auto refreshRates = selector.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Ascending); @@ -1126,7 +1126,7 @@ TEST_F(RefreshRateSelectorTest, getMaxRefreshRatesByPolicyOutsideTheGroup) { TestableRefreshRateSelector selector(kModes_30_60_90, kModeId72); EXPECT_EQ(SetPolicyResult::Changed, - selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}})); const auto refreshRates = selector.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Descending); @@ -1351,8 +1351,10 @@ TEST_F(RefreshRateSelectorTest, getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresTouchFlag) { TestableRefreshRateSelector selector(kModes_60_90, kModeId90); + constexpr FpsRange k90 = {90_Hz, 90_Hz}; + constexpr FpsRange k60_90 = {60_Hz, 90_Hz}; EXPECT_EQ(SetPolicyResult::Changed, - selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}})); std::vector layers = {{.weight = 1.f}}; auto& lr = layers[0]; @@ -1373,8 +1375,11 @@ TEST_F(RefreshRateSelectorTest, getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresIdleFlag) { TestableRefreshRateSelector selector(kModes_60_90, kModeId60); + constexpr FpsRange k60 = {60_Hz, 60_Hz}; + constexpr FpsRange k60_90 = {60_Hz, 90_Hz}; + EXPECT_EQ(SetPolicyResult::Changed, - selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {k60, k60}, {k60_90, k60_90}})); std::vector layers = {{.weight = 1.f}}; auto& lr = layers[0]; @@ -1513,8 +1518,11 @@ TEST_F(RefreshRateSelectorTest, getBestRefreshRate_withDisplayManagerRequestingSingleRate_onlySwitchesRatesForExplicitFocusedLayers) { TestableRefreshRateSelector selector(kModes_60_90, kModeId90); + constexpr FpsRange k90 = {90_Hz, 90_Hz}; + constexpr FpsRange k60_90 = {60_Hz, 90_Hz}; + EXPECT_EQ(SetPolicyResult::Changed, - selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}})); const auto [ranking, signals] = selector.getRankedRefreshRates({}, {}); EXPECT_EQ(ranking.front().modePtr, kMode90); @@ -1849,8 +1857,11 @@ TEST_F(RefreshRateSelectorTest, primaryVsAppRequestPolicy) { return selector.getBestRefreshRate(layers, {.touch = args.touch})->getId(); }; + constexpr FpsRange k30_60 = {30_Hz, 60_Hz}; + constexpr FpsRange k30_90 = {30_Hz, 90_Hz}; + EXPECT_EQ(SetPolicyResult::Changed, - selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 60_Hz}, {30_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {k30_60, k30_60}, {k30_90, k30_90}})); EXPECT_EQ(kModeId60, selector.getBestRefreshRate()->getId()); EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz)); @@ -1875,7 +1886,7 @@ TEST_F(RefreshRateSelectorTest, primaryVsAppRequestPolicy) { getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz, {.touch = true})); EXPECT_EQ(SetPolicyResult::Changed, - selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 60_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}})); EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz)); EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Min, 90_Hz)); @@ -1904,7 +1915,7 @@ TEST_F(RefreshRateSelectorTest, idle) { }; EXPECT_EQ(SetPolicyResult::Changed, - selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}, {60_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}})); // Idle should be lower priority than touch boost. { diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp index 4c25463e6e..05d0ebf773 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "mock/MockDisplayModeSpecs.h" #include "mock/MockEventThread.h" #undef LOG_TAG #define LOG_TAG "LibSurfaceFlingerUnittests" @@ -119,8 +120,9 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRe mFlinger.onActiveDisplayChanged(mDisplay); - mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(), - false, 0.f, 120.f, 0.f, 120.f); + mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), + mock::createDisplayModeSpecs(kModeId90.value(), false, 0, + 120)); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90); @@ -157,8 +159,9 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithoutRefres mFlinger.onActiveDisplayChanged(mDisplay); - mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(), - true, 0.f, 120.f, 0.f, 120.f); + mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), + mock::createDisplayModeSpecs(kModeId90.value(), true, 0, + 120)); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90); @@ -191,8 +194,9 @@ TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) { mFlinger.onActiveDisplayChanged(mDisplay); - mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(), - false, 0.f, 120.f, 0.f, 120.f); + mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), + mock::createDisplayModeSpecs(kModeId90.value(), false, 0, + 120)); const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; EXPECT_CALL(*mComposer, @@ -202,8 +206,9 @@ TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) { mFlinger.commit(); - mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId120.value(), - false, 0.f, 180.f, 0.f, 180.f); + mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), + mock::createDisplayModeSpecs(kModeId120.value(), false, 0, + 180)); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId120); @@ -232,8 +237,9 @@ TEST_F(DisplayModeSwitchingTest, changeResolution_OnActiveDisplay_WithoutRefresh mFlinger.onActiveDisplayChanged(mDisplay); - mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90_4K.value(), - false, 0.f, 120.f, 0.f, 120.f); + mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), + mock::createDisplayModeSpecs(kModeId90_4K.value(), false, 0, + 120)); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90_4K); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index ff79ce099e..8b2f95312f 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -455,14 +455,9 @@ public: return SurfaceFlinger::calculateMaxAcquiredBufferCount(refreshRate, presentLatency); } - auto setDesiredDisplayModeSpecs(const sp& displayToken, ui::DisplayModeId defaultMode, - bool allowGroupSwitching, float primaryRefreshRateMin, - float primaryRefreshRateMax, float appRequestRefreshRateMin, - float appRequestRefreshRateMax) { - return mFlinger->setDesiredDisplayModeSpecs(displayToken, defaultMode, allowGroupSwitching, - primaryRefreshRateMin, primaryRefreshRateMax, - appRequestRefreshRateMin, - appRequestRefreshRateMax); + auto setDesiredDisplayModeSpecs(const sp& displayToken, + const gui::DisplayModeSpecs& specs) { + return mFlinger->setDesiredDisplayModeSpecs(displayToken, specs); } void onActiveDisplayChanged(const sp& activeDisplay) { diff --git a/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h b/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h new file mode 100644 index 0000000000..a71e82cc75 --- /dev/null +++ b/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h @@ -0,0 +1,35 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +namespace android::mock { + +inline gui::DisplayModeSpecs createDisplayModeSpecs(int32_t defaultMode, bool allowGroupSwitching, + float minFps, float maxFps) { + gui::DisplayModeSpecs specs; + specs.defaultMode = defaultMode; + specs.allowGroupSwitching = allowGroupSwitching; + specs.primaryRanges.physical.min = minFps; + specs.primaryRanges.physical.max = maxFps; + specs.primaryRanges.render = specs.primaryRanges.physical; + specs.appRequestRanges = specs.primaryRanges; + return specs; +} + +} // namespace android::mock -- GitLab From 50c19b1ae3f6a80d18a5d9dea77146b94c71b009 Mon Sep 17 00:00:00 2001 From: Yuncheol Heo Date: Wed, 2 Nov 2022 20:33:08 -0700 Subject: [PATCH 0448/1310] Add a config to allow touches while the display is off. Bug: 255462169 Test: atest inputflinger_tests Change-Id: I4904ccda5c7ba3c204c89af61fbf0bfca22475ba --- .../reader/mapper/TouchInputMapper.cpp | 8 ++++++- .../reader/mapper/TouchInputMapper.h | 3 +++ .../inputflinger/tests/InputReader_test.cpp | 24 +++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index ba3d9802a1..d14b0f08f1 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -437,6 +437,10 @@ void TouchInputMapper::configureParameters() { mParameters.supportsUsi = false; getDeviceContext().getConfiguration().tryGetProperty("touch.supportsUsi", mParameters.supportsUsi); + + mParameters.enableForInactiveViewport = false; + getDeviceContext().getConfiguration().tryGetProperty("touch.enableForInactiveViewport", + mParameters.enableForInactiveViewport); } void TouchInputMapper::dumpParameters(std::string& dump) { @@ -454,6 +458,8 @@ void TouchInputMapper::dumpParameters(std::string& dump) { dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware)); dump += INDENT4 "Orientation: " + ftl::enum_string(mParameters.orientation) + "\n"; dump += StringPrintf(INDENT4 "SupportsUsi: %s\n", toString(mParameters.supportsUsi)); + dump += StringPrintf(INDENT4 "EnableForInactiveViewport: %s\n", + toString(mParameters.enableForInactiveViewport)); } void TouchInputMapper::configureRawPointerAxes() { @@ -856,7 +862,7 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) "becomes available.", getDeviceName().c_str()); mDeviceMode = DeviceMode::DISABLED; - } else if (!newViewportOpt->isActive) { + } else if (!mParameters.enableForInactiveViewport && !newViewportOpt->isActive) { ALOGI("Disabling %s (device %i) because the associated viewport is not active", getDeviceName().c_str(), getDeviceId()); mDeviceMode = DeviceMode::DISABLED; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 2bb9ecebe4..bab15cc47d 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -236,6 +236,9 @@ protected: // Whether the device supports the Universal Stylus Initiative (USI) protocol for styluses. bool supportsUsi; + + // Allows touches while the display is off. + bool enableForInactiveViewport; } mParameters; // Immutable calibration parameters in parsed form. diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 0c2742fd16..39f521b9f0 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -9082,6 +9082,7 @@ TEST_F(MultiTouchInputMapperTest, Process_SendsReadTime) { */ TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreDropped) { addConfigurationProperty("touch.deviceType", "touchScreen"); + // Don't set touch.enableForInactiveViewport to verify the default behavior. mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0, false /*isActive*/, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); @@ -9096,8 +9097,31 @@ TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreDropped) { mFakeListener->assertNotifyMotionWasNotCalled(); } +/** + * When the viewport is not active (isActive=false) and touch.enableForInactiveViewport is true, + * the touch mapper can process the events and the events can be delivered to the listener. + */ +TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreProcessed) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + addConfigurationProperty("touch.enableForInactiveViewport", "1"); + mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, + DISPLAY_ORIENTATION_0, false /*isActive*/, UNIQUE_ID, NO_PORT, + ViewportType::INTERNAL); + configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + prepareAxes(POSITION); + MultiTouchInputMapper& mapper = addMapperAndConfigure(); + + NotifyMotionArgs motionArgs; + processPosition(mapper, 100, 100); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); +} + TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_AbortTouches) { addConfigurationProperty("touch.deviceType", "touchScreen"); + addConfigurationProperty("touch.enableForInactiveViewport", "0"); mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0, true /*isActive*/, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); -- GitLab From b80b6c0e6d9c69957fb9ffa3d0467ad626af2fbe Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 2 Nov 2022 20:05:13 +0000 Subject: [PATCH 0449/1310] Do not assume we are in POINTER mode when canceling pointer gestures The change in ag/19758680 made it so that we now reset TouchInputMapper after the viewport is disabled. When resetting, we cancel any ongoing gestures. Since the mapper is re-configured before the gesture is canceled, this means that as the viewport is getting disabled, the mapper (which was previously in POINTER mode) is set to DISABLED mode. When the pointer gesture is canceled afterwards, it is happening while the mapper is already DISABLED. In this CL, we allow pointer gestures to be aborted even when the mapper is not in POINTER mode. Bug: 257071757 Test: atest inputflinger_tests Change-Id: I5c80a5c1c411d16f70ed4f7cce6dd97ed91e124f --- .../reader/mapper/TouchInputMapper.cpp | 29 ++++++++++++-- .../reader/mapper/TouchInputMapper.h | 10 +++++ .../inputflinger/tests/InputReader_test.cpp | 40 +++++++++++++++++++ 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index ba3d9802a1..9fef4663d0 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -3556,6 +3556,8 @@ std::list TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, bool down, bool hovering) { + LOG_ALWAYS_FATAL_IF(mDeviceMode != DeviceMode::POINTER, + "%s cannot be used when the device is not in POINTER mode.", __func__); std::list out; int32_t metaState = getContext()->getGlobalMetaState(); @@ -3682,6 +3684,10 @@ std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsec if (down || hovering) { mPointerSimple.lastCoords.copyFrom(mPointerSimple.currentCoords); mPointerSimple.lastProperties.copyFrom(mPointerSimple.currentProperties); + mPointerSimple.displayId = displayId; + mPointerSimple.source = mSource; + mPointerSimple.lastCursorX = xCursorPosition; + mPointerSimple.lastCursorY = yCursorPosition; } else { mPointerSimple.reset(); } @@ -3690,10 +3696,25 @@ std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsec std::list TouchInputMapper::abortPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) { - mPointerSimple.currentCoords.clear(); - mPointerSimple.currentProperties.clear(); - - return dispatchPointerSimple(when, readTime, policyFlags, false, false); + std::list out; + if (mPointerSimple.down || mPointerSimple.hovering) { + int32_t metaState = getContext()->getGlobalMetaState(); + out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), + mPointerSimple.source, mPointerSimple.displayId, policyFlags, + AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED, + metaState, mLastRawState.buttonState, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, + &mPointerSimple.lastProperties, &mPointerSimple.lastCoords, + mOrientedXPrecision, mOrientedYPrecision, + mPointerSimple.lastCursorX, mPointerSimple.lastCursorY, + mPointerSimple.downTime, + /* videoFrames */ {})); + if (mPointerController != nullptr) { + mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); + } + } + mPointerSimple.reset(); + return out; } NotifyMotionArgs TouchInputMapper::dispatchMotion( diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 2bb9ecebe4..80c28ab826 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -678,6 +678,12 @@ private: // Time the pointer last went down. nsecs_t downTime; + // Values reported for the last pointer event. + uint32_t source; + int32_t displayId; + float lastCursorX; + float lastCursorY; + void reset() { currentCoords.clear(); currentProperties.clear(); @@ -686,6 +692,10 @@ private: down = false; hovering = false; downTime = 0; + source = 0; + displayId = ADISPLAY_ID_NONE; + lastCursorX = 0.f; + lastCursorY = 0.f; } } mPointerSimple; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 0c2742fd16..e7313a2b93 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -10420,6 +10420,46 @@ TEST_F(MultiTouchPointerModeTest, TwoFingerSwipeOffsets) { ASSERT_GT(motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET), 0); } +TEST_F(MultiTouchPointerModeTest, WhenViewportActiveStatusChanged_PointerGestureIsReset) { + preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/); + mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); + MultiTouchInputMapper& mapper = addMapperAndConfigure(); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); + + // Start a stylus gesture. + processKey(mapper, BTN_TOOL_PEN, 1); + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + // TODO(b/257078296): Pointer mode generates extra event. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + + // Make the viewport inactive. This will put the device in disabled mode, and the ongoing stylus + // gesture should be disabled. + auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); + viewport->isActive = false; + mFakePolicy->updateViewport(*viewport); + configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL), + WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + // TODO(b/257078296): Pointer mode generates extra event. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL), + WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + // --- JoystickInputMapperTest --- class JoystickInputMapperTest : public InputMapperTest { -- GitLab From 8aed5d2b7b621849e66cb638951df4de301c9a12 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Mon, 31 Oct 2022 22:18:10 +0000 Subject: [PATCH 0450/1310] RE: move useProtectedContext calls to drawLayers This change removes a race condition when calling RenderEngine from multiple threads. Before this change, two competing threads could call RenderEngine::useProtectedContext with different values, potentially causing RenderEngine::drawLayers to use the wrong context. We currently call RenderEngine from a single thread but that will change with the upcoming refactor of SurfaceFlinger::renderScreenImpl. Bug: 238643986 Test: presubmits Change-Id: I78ca00797b1604cedae1f134e963b6b78f2b802f --- libs/renderengine/RenderEngine.cpp | 13 +++ libs/renderengine/gl/GLESRenderEngine.h | 2 +- .../include/renderengine/RenderEngine.h | 11 ++- libs/renderengine/skia/SkiaRenderEngine.h | 3 +- .../tests/RenderEngineThreadedTest.cpp | 98 ++++++++++++------- .../threaded/RenderEngineThreaded.cpp | 32 +----- .../threaded/RenderEngineThreaded.h | 6 +- .../CompositionEngine/src/Output.cpp | 8 +- .../CompositionEngine/tests/OutputTest.cpp | 64 ------------ services/surfaceflinger/SurfaceFlinger.cpp | 4 - 10 files changed, 92 insertions(+), 149 deletions(-) diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp index 9d9cb6b2bc..f1fc0a45ad 100644 --- a/libs/renderengine/RenderEngine.cpp +++ b/libs/renderengine/RenderEngine.cpp @@ -19,6 +19,7 @@ #include #include #include "gl/GLESRenderEngine.h" +#include "renderengine/ExternalTexture.h" #include "threaded/RenderEngineThreaded.h" #include "skia/SkiaGLRenderEngine.h" @@ -70,10 +71,22 @@ ftl::Future RenderEngine::drawLayers(const DisplaySettings& display base::unique_fd&& bufferFence) { const auto resultPromise = std::make_shared>(); std::future resultFuture = resultPromise->get_future(); + updateProtectedContext(layers, buffer); drawLayersInternal(std::move(resultPromise), display, layers, buffer, useFramebufferCache, std::move(bufferFence)); return resultFuture; } +void RenderEngine::updateProtectedContext(const std::vector& layers, + const std::shared_ptr& buffer) { + const bool needsProtectedContext = + (buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED)) || + std::any_of(layers.begin(), layers.end(), [](const LayerSettings& layer) { + const std::shared_ptr& buffer = layer.source.buffer.buffer; + return buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED); + }); + useProtectedContext(needsProtectedContext); +} + } // namespace renderengine } // namespace android diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h index 1ee5cbaa3d..1b3492154b 100644 --- a/libs/renderengine/gl/GLESRenderEngine.h +++ b/libs/renderengine/gl/GLESRenderEngine.h @@ -61,7 +61,7 @@ public: std::future primeCache() override; void genTextures(size_t count, uint32_t* names) override; void deleteTextures(size_t count, uint32_t const* names) override; - bool isProtected() const override { return mInProtectedContext; } + bool isProtected() const { return mInProtectedContext; } bool supportsProtectedContent() const override; void useProtectedContext(bool useProtectedContext) override; void cleanupPostRender() override; diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h index 199392c160..9182febbe0 100644 --- a/libs/renderengine/include/renderengine/RenderEngine.h +++ b/libs/renderengine/include/renderengine/RenderEngine.h @@ -126,12 +126,8 @@ public: // ----- BEGIN NEW INTERFACE ----- // queries that are required to be thread safe - virtual bool isProtected() const = 0; virtual bool supportsProtectedContent() const = 0; - // Attempt to switch RenderEngine into and out of protectedContext mode - virtual void useProtectedContext(bool useProtectedContext) = 0; - // Notify RenderEngine of changes to the dimensions of the active display // so that it can configure its internal caches accordingly. virtual void onActiveDisplaySizeChanged(ui::Size size) = 0; @@ -238,6 +234,13 @@ protected: friend class RenderEngineTest_cleanupPostRender_cleansUpOnce_Test; const RenderEngineType mRenderEngineType; + // Update protectedContext mode depending on whether or not any layer has a protected buffer. + void updateProtectedContext(const std::vector&, + const std::shared_ptr&); + + // Attempt to switch RenderEngine into and out of protectedContext mode + virtual void useProtectedContext(bool useProtectedContext) = 0; + virtual void drawLayersInternal( const std::shared_ptr>&& resultPromise, const DisplaySettings& display, const std::vector& layers, diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h index e7c5b8f0ab..1973c7d065 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.h +++ b/libs/renderengine/skia/SkiaRenderEngine.h @@ -68,7 +68,6 @@ public: std::future primeCache() override final; void cleanupPostRender() override final; void cleanFramebufferCache() override final{ } - bool isProtected() const override final{ return mInProtectedContext; } bool supportsBackgroundBlur() override final { return mBlurFilter != nullptr; } @@ -102,6 +101,8 @@ protected: size_t getMaxViewportDims() const override final; GrDirectContext* getActiveGrContext(); + bool isProtected() const { return mInProtectedContext; } + // Implements PersistentCache as a way to monitor what SkSL shaders Skia has // cached. class SkSLCacheMonitor : public GrContextOptions::PersistentCache { diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp index 1a96289bc0..fe3a16d4bf 100644 --- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp +++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp @@ -17,8 +17,10 @@ #include #include #include +#include #include #include +#include #include "../threaded/RenderEngineThreaded.h" namespace android { @@ -95,18 +97,6 @@ TEST_F(RenderEngineThreadedTest, getMaxViewportDims_returns0) { ASSERT_EQ(dims, result); } -TEST_F(RenderEngineThreadedTest, isProtected_returnsFalse) { - EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(false)); - status_t result = mThreadedRE->isProtected(); - ASSERT_EQ(false, result); -} - -TEST_F(RenderEngineThreadedTest, isProtected_returnsTrue) { - EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(true)); - size_t result = mThreadedRE->isProtected(); - ASSERT_EQ(true, result); -} - TEST_F(RenderEngineThreadedTest, supportsProtectedContent_returnsFalse) { EXPECT_CALL(*mRenderEngine, supportsProtectedContent()).WillOnce(Return(false)); status_t result = mThreadedRE->supportsProtectedContent(); @@ -119,28 +109,6 @@ TEST_F(RenderEngineThreadedTest, supportsProtectedContent_returnsTrue) { ASSERT_EQ(true, result); } -TEST_F(RenderEngineThreadedTest, useProtectedContext) { - EXPECT_CALL(*mRenderEngine, useProtectedContext(true)); - auto& ipExpect = EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(false)); - EXPECT_CALL(*mRenderEngine, supportsProtectedContent()).WillOnce(Return(true)); - EXPECT_CALL(*mRenderEngine, isProtected()).After(ipExpect).WillOnce(Return(true)); - - mThreadedRE->useProtectedContext(true); - ASSERT_EQ(true, mThreadedRE->isProtected()); - - // call ANY synchronous function to ensure that useProtectedContext has completed. - mThreadedRE->getContextPriority(); - ASSERT_EQ(true, mThreadedRE->isProtected()); -} - -TEST_F(RenderEngineThreadedTest, useProtectedContext_quickReject) { - EXPECT_CALL(*mRenderEngine, useProtectedContext(false)).Times(0); - EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(false)); - mThreadedRE->useProtectedContext(false); - // call ANY synchronous function to ensure that useProtectedContext has completed. - mThreadedRE->getContextPriority(); -} - TEST_F(RenderEngineThreadedTest, PostRenderCleanup_skipped) { EXPECT_CALL(*mRenderEngine, canSkipPostRenderCleanup()).WillOnce(Return(true)); EXPECT_CALL(*mRenderEngine, cleanupPostRender()).Times(0); @@ -182,6 +150,68 @@ TEST_F(RenderEngineThreadedTest, drawLayers) { base::unique_fd bufferFence; + EXPECT_CALL(*mRenderEngine, useProtectedContext(false)); + EXPECT_CALL(*mRenderEngine, drawLayersInternal) + .WillOnce([&](const std::shared_ptr>&& resultPromise, + const renderengine::DisplaySettings&, + const std::vector&, + const std::shared_ptr&, const bool, + base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); }); + + ftl::Future future = + mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence)); + ASSERT_TRUE(future.valid()); + auto result = future.get(); + ASSERT_TRUE(result.ok()); +} + +TEST_F(RenderEngineThreadedTest, drawLayers_protectedLayer) { + renderengine::DisplaySettings settings; + auto layerBuffer = sp::make(); + layerBuffer->usage |= GRALLOC_USAGE_PROTECTED; + renderengine::LayerSettings layer; + layer.source.buffer.buffer = std::make_shared< + renderengine::impl::ExternalTexture>(std::move(layerBuffer), *mRenderEngine, + renderengine::impl::ExternalTexture::Usage:: + READABLE); + std::vector layers = {std::move(layer)}; + std::shared_ptr buffer = std::make_shared< + renderengine::impl:: + ExternalTexture>(sp::make(), *mRenderEngine, + renderengine::impl::ExternalTexture::Usage::READABLE | + renderengine::impl::ExternalTexture::Usage::WRITEABLE); + + base::unique_fd bufferFence; + + EXPECT_CALL(*mRenderEngine, useProtectedContext(true)); + EXPECT_CALL(*mRenderEngine, drawLayersInternal) + .WillOnce([&](const std::shared_ptr>&& resultPromise, + const renderengine::DisplaySettings&, + const std::vector&, + const std::shared_ptr&, const bool, + base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); }); + + ftl::Future future = + mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence)); + ASSERT_TRUE(future.valid()); + auto result = future.get(); + ASSERT_TRUE(result.ok()); +} + +TEST_F(RenderEngineThreadedTest, drawLayers_protectedOutputBuffer) { + renderengine::DisplaySettings settings; + std::vector layers; + auto graphicBuffer = sp::make(); + graphicBuffer->usage |= GRALLOC_USAGE_PROTECTED; + std::shared_ptr buffer = std::make_shared< + renderengine::impl:: + ExternalTexture>(std::move(graphicBuffer), *mRenderEngine, + renderengine::impl::ExternalTexture::Usage::READABLE | + renderengine::impl::ExternalTexture::Usage::WRITEABLE); + + base::unique_fd bufferFence; + + EXPECT_CALL(*mRenderEngine, useProtectedContext(true)); EXPECT_CALL(*mRenderEngine, drawLayersInternal) .WillOnce([&](const std::shared_ptr>&& resultPromise, const renderengine::DisplaySettings&, diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp index b41e8432a9..8aa41b3e50 100644 --- a/libs/renderengine/threaded/RenderEngineThreaded.cpp +++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp @@ -90,7 +90,6 @@ void RenderEngineThreaded::threadMain(CreateInstanceFactory factory) NO_THREAD_S } mRenderEngine = factory(); - mIsProtected = mRenderEngine->isProtected(); pthread_setname_np(pthread_self(), mThreadName); @@ -255,41 +254,11 @@ size_t RenderEngineThreaded::getMaxViewportDims() const { return mRenderEngine->getMaxViewportDims(); } -bool RenderEngineThreaded::isProtected() const { - waitUntilInitialized(); - std::lock_guard lock(mThreadMutex); - return mIsProtected; -} - bool RenderEngineThreaded::supportsProtectedContent() const { waitUntilInitialized(); return mRenderEngine->supportsProtectedContent(); } -void RenderEngineThreaded::useProtectedContext(bool useProtectedContext) { - if (isProtected() == useProtectedContext || - (useProtectedContext && !supportsProtectedContent())) { - return; - } - - { - std::lock_guard lock(mThreadMutex); - mFunctionCalls.push([useProtectedContext, this](renderengine::RenderEngine& instance) { - ATRACE_NAME("REThreaded::useProtectedContext"); - instance.useProtectedContext(useProtectedContext); - if (instance.isProtected() != useProtectedContext) { - ALOGE("Failed to switch RenderEngine context."); - // reset the cached mIsProtected value to a good state, but this does not - // prevent other callers of this method and isProtected from reading the - // invalid cached value. - mIsProtected = instance.isProtected(); - } - }); - mIsProtected = useProtectedContext; - } - mCondition.notify_one(); -} - void RenderEngineThreaded::cleanupPostRender() { if (canSkipPostRenderCleanup()) { return; @@ -334,6 +303,7 @@ ftl::Future RenderEngineThreaded::drawLayers( mFunctionCalls.push([resultPromise, display, layers, buffer, useFramebufferCache, fd](renderengine::RenderEngine& instance) { ATRACE_NAME("REThreaded::drawLayers"); + instance.updateProtectedContext(layers, buffer); instance.drawLayersInternal(std::move(resultPromise), display, layers, buffer, useFramebufferCache, base::unique_fd(fd)); }); diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h index bf2ebea2a0..168e2d2b06 100644 --- a/libs/renderengine/threaded/RenderEngineThreaded.h +++ b/libs/renderengine/threaded/RenderEngineThreaded.h @@ -51,9 +51,7 @@ public: size_t getMaxTextureSize() const override; size_t getMaxViewportDims() const override; - bool isProtected() const override; bool supportsProtectedContent() const override; - void useProtectedContext(bool useProtectedContext) override; void cleanupPostRender() override; ftl::Future drawLayers(const DisplaySettings& display, @@ -84,6 +82,9 @@ private: void waitUntilInitialized() const; static status_t setSchedFifo(bool enabled); + // No-op. This method is only called on leaf implementations of RenderEngine. + void useProtectedContext(bool) override {} + /* ------------------------------------------------------------------------ * Threading */ @@ -107,7 +108,6 @@ private: * Render Engine */ std::unique_ptr mRenderEngine; - std::atomic mIsProtected = false; }; } // namespace threaded } // namespace renderengine diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 0622534491..17aefc5492 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -1178,15 +1178,9 @@ void Output::updateProtectedContentState() { bool needsProtected = std::any_of(layers.begin(), layers.end(), [](auto* layer) { return layer->getLayerFE().getCompositionState()->hasProtectedContent; }); - if (needsProtected != renderEngine.isProtected()) { - renderEngine.useProtectedContext(needsProtected); - } - if (needsProtected != mRenderSurface->isProtected() && - needsProtected == renderEngine.isProtected()) { + if (needsProtected != mRenderSurface->isProtected()) { mRenderSurface->setProtected(needsProtected); } - } else if (!outputState.isSecure && renderEngine.isProtected()) { - renderEngine.useProtectedContext(false); } } diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index 514a8ff8fc..a955178b17 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -4010,39 +4010,11 @@ struct OutputComposeSurfacesTest_HandlesProtectedContent : public OutputComposeS Layer mLayer2; }; -TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifDisplayIsNotSecure) { - mOutput.mState.isSecure = false; - mLayer2.mLayerFEState.hasProtectedContent = true; - EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)); - EXPECT_CALL(mRenderEngine, useProtectedContext(false)); - - base::unique_fd fd; - std::shared_ptr tex; - mOutput.updateProtectedContentState(); - mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); -} - -TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifRenderEngineDoesNotSupportIt) { - mOutput.mState.isSecure = true; - mLayer2.mLayerFEState.hasProtectedContent = true; - EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false)); - - base::unique_fd fd; - std::shared_ptr tex; - mOutput.updateProtectedContentState(); - mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); -} - TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNoProtectedContentLayers) { mOutput.mState.isSecure = true; mLayer2.mLayerFEState.hasProtectedContent = false; EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)).WillOnce(Return(false)); EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true)); - EXPECT_CALL(mRenderEngine, useProtectedContext(false)); EXPECT_CALL(*mRenderSurface, setProtected(false)); base::unique_fd fd; @@ -4060,10 +4032,7 @@ TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNotEnabled) { // For this test, we also check the call order of key functions. InSequence seq; - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(false)); - EXPECT_CALL(mRenderEngine, useProtectedContext(true)); EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)); EXPECT_CALL(*mRenderSurface, setProtected(true)); // Must happen after setting the protected content state. EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer)); @@ -4081,7 +4050,6 @@ TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledEveryw mOutput.mState.isSecure = true; mLayer2.mLayerFEState.hasProtectedContent = true; EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)); EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true)); base::unique_fd fd; @@ -4091,43 +4059,11 @@ TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledEveryw mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); } -TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifFailsToEnableInRenderEngine) { - mOutput.mState.isSecure = true; - mLayer2.mLayerFEState.hasProtectedContent = true; - EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(false)).WillOnce(Return(false)); - EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false)); - EXPECT_CALL(mRenderEngine, useProtectedContext(true)); - - base::unique_fd fd; - std::shared_ptr tex; - mOutput.updateProtectedContentState(); - mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); -} - -TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledInRenderEngine) { - mOutput.mState.isSecure = true; - mLayer2.mLayerFEState.hasProtectedContent = true; - EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)).WillOnce(Return(true)); - EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false)); - EXPECT_CALL(*mRenderSurface, setProtected(true)); - - base::unique_fd fd; - std::shared_ptr tex; - mOutput.updateProtectedContentState(); - mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); -} - TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledInRenderSurface) { mOutput.mState.isSecure = true; mLayer2.mLayerFEState.hasProtectedContent = true; EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(false)); EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true)); - EXPECT_CALL(mRenderEngine, useProtectedContext(true)); base::unique_fd fd; std::shared_ptr tex; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 23843ab11d..68fab24194 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -6522,7 +6522,6 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( // Use an empty fence for the buffer fence, since we just created the buffer so // there is no need for synchronization with the GPU. base::unique_fd bufferFence; - getRenderEngine().useProtectedContext(useProtected); constexpr bool kUseFramebufferCache = false; const auto future = getRenderEngine() @@ -6534,9 +6533,6 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( layer->onLayerDisplayed(future); } - // Always switch back to unprotected context. - getRenderEngine().useProtectedContext(false); - return future; } -- GitLab From 74c0bf652445aafef5b78bdd8b0583e908afa1cc Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Wed, 2 Nov 2022 23:59:26 +0000 Subject: [PATCH 0451/1310] CE: Make TimeStats nullable This CL allows CE instances without TimeStats. This will be used by the short lived CE instances created in the upcoming renderScreenImpl refactor. Bug: 238643986 Test: presubmits Change-Id: I7f996f3ce660b335f6228069aa04bba617806b68 --- .../include/compositionengine/CompositionEngine.h | 2 +- .../compositionengine/impl/CompositionEngine.h | 2 +- .../compositionengine/mock/CompositionEngine.h | 2 +- .../CompositionEngine/src/CompositionEngine.cpp | 4 ++-- .../surfaceflinger/CompositionEngine/src/Output.cpp | 11 +++++++---- .../CompositionEngine/tests/CompositionEngineTest.cpp | 2 +- .../CompositionEngine/tests/OutputTest.cpp | 3 +-- 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h index 8661063e12..7c10fa57ec 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h @@ -58,7 +58,7 @@ public: virtual renderengine::RenderEngine& getRenderEngine() const = 0; virtual void setRenderEngine(renderengine::RenderEngine*) = 0; - virtual TimeStats& getTimeStats() const = 0; + virtual TimeStats* getTimeStats() const = 0; virtual void setTimeStats(const std::shared_ptr&) = 0; virtual bool needsAnotherUpdate() const = 0; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h index 2af6c80f1e..c6995576a1 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h @@ -36,7 +36,7 @@ public: renderengine::RenderEngine& getRenderEngine() const override; void setRenderEngine(renderengine::RenderEngine*) override; - TimeStats& getTimeStats() const override; + TimeStats* getTimeStats() const override; void setTimeStats(const std::shared_ptr&) override; bool needsAnotherUpdate() const override; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h index fc71649949..9b2387b966 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h @@ -42,7 +42,7 @@ public: MOCK_CONST_METHOD0(getRenderEngine, renderengine::RenderEngine&()); MOCK_METHOD1(setRenderEngine, void(renderengine::RenderEngine*)); - MOCK_CONST_METHOD0(getTimeStats, TimeStats&()); + MOCK_CONST_METHOD0(getTimeStats, TimeStats*()); MOCK_METHOD1(setTimeStats, void(const std::shared_ptr&)); MOCK_CONST_METHOD0(needsAnotherUpdate, bool()); diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp index e42f11a9b9..15fadbc8ee 100644 --- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp +++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp @@ -72,8 +72,8 @@ void CompositionEngine::setRenderEngine(renderengine::RenderEngine* renderEngine mRenderEngine = renderEngine; } -TimeStats& CompositionEngine::getTimeStats() const { - return *mTimeStats.get(); +TimeStats* CompositionEngine::getTimeStats() const { + return mTimeStats.get(); } void CompositionEngine::setTimeStats(const std::shared_ptr& timeStats) { diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 17aefc5492..c2b1f0679c 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -1334,10 +1334,13 @@ std::optional Output::composeSurfaces( const auto fence = std::move(fenceResult).value_or(Fence::NO_FENCE); - if (auto& timeStats = getCompositionEngine().getTimeStats(); fence->isValid()) { - timeStats.recordRenderEngineDuration(renderEngineStart, std::make_shared(fence)); - } else { - timeStats.recordRenderEngineDuration(renderEngineStart, systemTime()); + if (auto timeStats = getCompositionEngine().getTimeStats()) { + if (fence->isValid()) { + timeStats->recordRenderEngineDuration(renderEngineStart, + std::make_shared(fence)); + } else { + timeStats->recordRenderEngineDuration(renderEngineStart, systemTime()); + } } for (auto* clientComposedLayer : clientCompositionLayersFE) { diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp index 8d99f894a6..60ed660c7a 100644 --- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp @@ -71,7 +71,7 @@ TEST_F(CompositionEngineTest, canSetRenderEngine) { TEST_F(CompositionEngineTest, canSetTimeStats) { mEngine.setTimeStats(mTimeStats); - EXPECT_EQ(mTimeStats.get(), &mEngine.getTimeStats()); + EXPECT_EQ(mTimeStats.get(), mEngine.getTimeStats()); } /* diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index a955178b17..21099876f4 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -3332,8 +3332,7 @@ struct OutputComposeSurfacesTest : public testing::Test { EXPECT_CALL(mOutput, getCompositionEngine()).WillRepeatedly(ReturnRef(mCompositionEngine)); EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine)); - EXPECT_CALL(mCompositionEngine, getTimeStats()) - .WillRepeatedly(ReturnRef(*mTimeStats.get())); + EXPECT_CALL(mCompositionEngine, getTimeStats()).WillRepeatedly(Return(mTimeStats.get())); EXPECT_CALL(*mDisplayColorProfile, getHdrCapabilities()) .WillRepeatedly(ReturnRef(kHdrCapabilities)); } -- GitLab From ffa3401581a41d2ad62d368a966601c28d74b7cf Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Thu, 3 Nov 2022 23:21:13 +0000 Subject: [PATCH 0452/1310] libjpegrecoverymap: refactor of public APIs 1. Add color space enumerator and add this field to jpegr_compressed/uncompressed_struct 2. Add EXIF structure and add this field to encode/decode methods. 3. Add request_sdr flag in the decode API. 4. Add quality parameter in the encode API. 5. Change in comments. Bug: b/252835416 Test: build Change-Id: Ic28041cd7ba2500dc9e8608aeb273fa79a8b2fd1 --- .../include/jpegrecoverymap/recoverymap.h | 64 +++++++++++++++++-- libs/jpegrecoverymap/recoverymap.cpp | 20 ++++-- 2 files changed, 75 insertions(+), 9 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h index 31f1872491..d8647b6737 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -18,6 +18,13 @@ namespace android::recoverymap { +typedef enum { + JPEGR_COLORSPACE_UNSPECIFIED, + JPEGR_COLORSPACE_BT709, + JPEGR_COLORSPACE_P3, + JPEGR_COLORSPACE_BT2100, +} jpegr_color_space; + /* * Holds information for uncompressed image or recovery map. */ @@ -28,12 +35,26 @@ struct jpegr_uncompressed_struct { int width; // Height of the recovery map or image in pixels. int height; + // Color space. + jpegr_color_space colorSpace; }; /* * Holds information for compressed image or recovery map. */ struct jpegr_compressed_struct { + // Pointer to the data location. + void* data; + // Data length. + int length; + // Color space. + jpegr_color_space colorSpace; +}; + +/* + * Holds information for EXIF metadata. + */ +struct jpegr_exif_struct { // Pointer to the data location. void* data; // Data length; @@ -42,6 +63,7 @@ struct jpegr_compressed_struct { typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr; typedef struct jpegr_compressed_struct* jr_compressed_ptr; +typedef struct jpegr_exif_struct* jr_exif_ptr; class RecoveryMap { public: @@ -53,14 +75,24 @@ public: * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format * @param dest destination of the compressed JPEGR image + * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is + * the highest quality + * @param exif pointer to the exif metadata. + * @param hdr_ratio HDR ratio. If not configured, this value will be calculated by the JPEG/R + * encoder. * @return NO_ERROR if encoding succeeds, error code if error occurs. */ status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, - void* dest); + void* dest, + int quality, + jr_exif_ptr exif, + float hdr_ratio = 0.0f); /* - * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. + * Compress JPEGR image from 10-bit HDR YUV, 8-bit SDR YUV and compressed 8-bit JPEG. + * + * This method requires HAL Hardware JPEG encoder. * * Generate recovery map from the HDR and SDR inputs, append the recovery map to the end of the * compressed JPEG. @@ -68,35 +100,57 @@ public: * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format * @param compressed_jpeg_image compressed 8-bit JPEG image * @param dest destination of the compressed JPEGR image + * @param hdr_ratio HDR ratio. If not configured, this value will be calculated by the JPEG/R + * encoder. * @return NO_ERROR if encoding succeeds, error code if error occurs. */ status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, void* compressed_jpeg_image, - void* dest); + void* dest, + float hdr_ratio = 0.0f); /* * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. * + * This method requires HAL Hardware JPEG encoder. + * * Decode the compressed 8-bit JPEG image to YUV SDR, generate recovery map from the HDR input * and the decoded SDR result, append the recovery map to the end of the compressed JPEG. * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param compressed_jpeg_image compressed 8-bit JPEG image * @param dest destination of the compressed JPEGR image + * @param hdr_ratio HDR ratio. If not configured, this value will be calculated by the JPEG/R + * encoder. * @return NO_ERROR if encoding succeeds, error code if error occurs. */ status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, void* compressed_jpeg_image, - void* dest); + void* dest, + float hdr_ratio = 0.0f); /* * Decompress JPEGR image. * * @param compressed_jpegr_image compressed JPEGR image * @param dest destination of the uncompressed JPEGR image + * @param exif destination of the decoded EXIF metadata. Default value is nullptr where EXIF + * metadata will not be decoded. + * @param request_sdr flag that request SDR output, default to false (request HDR output). If + * set to true, decoder will only decode the primary image which is SDR. + * Setting of request_sdr and input source (HDR or SDR) can be found in + * the table below: + * | input source | request_sdr | output of decoding | + * | HDR | true | SDR | + * | HDR | false | HDR | + * | SDR | true | SDR | + * | SDR | false | SDR | * @return NO_ERROR if decoding succeeds, error code if error occurs. */ - status_t decodeJPEGR(void* compressed_jpegr_image, jr_uncompressed_ptr dest); + status_t decodeJPEGR(void* compressed_jpegr_image, + jr_uncompressed_ptr dest, + jr_exif_ptr exif = nullptr, + bool request_sdr = false); private: /* * This method is called in the decoding pipeline. It will decode the recovery map. diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index 67c23e9788..7cedae9e0b 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -20,13 +20,20 @@ namespace android::recoverymap { status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, - void* dest) { + void* dest, + int quality, + jr_exif_ptr /* exif */, + float /* hdr_ratio */) { if (uncompressed_p010_image == nullptr || uncompressed_yuv_420_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } + if (quality < 0 || quality > 100) { + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + // TBD return NO_ERROR; } @@ -34,7 +41,8 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, void* compressed_jpeg_image, - void* dest) { + void* dest, + float /* hdr_ratio */) { if (uncompressed_p010_image == nullptr || uncompressed_yuv_420_image == nullptr @@ -49,7 +57,8 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, void* compressed_jpeg_image, - void* dest) { + void* dest, + float /* hdr_ratio */) { if (uncompressed_p010_image == nullptr || compressed_jpeg_image == nullptr || dest == nullptr) { @@ -60,7 +69,10 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return NO_ERROR; } -status_t RecoveryMap::decodeJPEGR(void* compressed_jpegr_image, jr_uncompressed_ptr dest) { +status_t RecoveryMap::decodeJPEGR(void* compressed_jpegr_image, + jr_uncompressed_ptr dest, + jr_exif_ptr /* exif */, + bool /* request_sdr */) { if (compressed_jpegr_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } -- GitLab From e7613db223a9f2209afcdc59e88bee246d61ffb1 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Fri, 4 Nov 2022 00:07:06 -0700 Subject: [PATCH 0453/1310] SF: Fix blur region transform regression Fixes a regression caused by ag/19611172 (bedb44bb15cd11689b96770a45eccf475778e9eb) where we used the local to screen space transform inv instead of local transform inv for blur. Test: atest BlurTests Bug: 248609209 Change-Id: I06a97ca32c7689a252029a319a9519d2bffdbd25 --- services/surfaceflinger/Layer.cpp | 2 +- services/surfaceflinger/LayerFE.cpp | 4 ++-- services/surfaceflinger/LayerFE.h | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 977709286b..fa6e6d488f 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -477,7 +477,7 @@ void Layer::prepareBasicGeometryCompositionState() { snapshot->geomLayerTransform = getTransform(); snapshot->geomInverseLayerTransform = snapshot->geomLayerTransform.inverse(); snapshot->transparentRegionHint = getActiveTransparentRegion(drawingState); - + snapshot->blurRegionTransform = getActiveTransform(drawingState).inverse(); snapshot->blendMode = static_cast(blendMode); snapshot->alpha = alpha; snapshot->backgroundBlurRadius = drawingState.backgroundBlurRadius; diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp index 3bdb521d0e..363adc641e 100644 --- a/services/surfaceflinger/LayerFE.cpp +++ b/services/surfaceflinger/LayerFE.cpp @@ -148,14 +148,14 @@ std::optional LayerFE::prepareClientC case LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled: layerSettings.backgroundBlurRadius = mSnapshot->backgroundBlurRadius; layerSettings.blurRegions = mSnapshot->blurRegions; - layerSettings.blurRegionTransform = mSnapshot->geomInverseLayerTransform.asMatrix4(); + layerSettings.blurRegionTransform = mSnapshot->blurRegionTransform.asMatrix4(); break; case LayerFE::ClientCompositionTargetSettings::BlurSetting::BackgroundBlurOnly: layerSettings.backgroundBlurRadius = mSnapshot->backgroundBlurRadius; break; case LayerFE::ClientCompositionTargetSettings::BlurSetting::BlurRegionsOnly: layerSettings.blurRegions = mSnapshot->blurRegions; - layerSettings.blurRegionTransform = mSnapshot->geomInverseLayerTransform.asMatrix4(); + layerSettings.blurRegionTransform = mSnapshot->blurRegionTransform.asMatrix4(); break; case LayerFE::ClientCompositionTargetSettings::BlurSetting::Disabled: default: diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h index e4f6889762..822bcb7a94 100644 --- a/services/surfaceflinger/LayerFE.h +++ b/services/surfaceflinger/LayerFE.h @@ -57,6 +57,7 @@ struct LayerSnapshot : public compositionengine::LayerFECompositionState { gui::LayerMetadata relativeLayerMetadata; bool contentDirty; bool hasReadyFrame; + ui::Transform blurRegionTransform; }; struct CompositionResult { -- GitLab From 779dc1fcb8f65367c106df8433672627acc226b2 Mon Sep 17 00:00:00 2001 From: Carlos Martinez Romero Date: Thu, 27 Oct 2022 23:23:30 +0000 Subject: [PATCH 0454/1310] Remove redundant debug flag. dumpsys result https://paste.googleplex.com/4984316764356608 Bug: 233818658 Test: manual Change-Id: Ib89d765d2861cd6b9c61bef78ac15338581d97bb --- services/surfaceflinger/SurfaceFlinger.cpp | 8 +------- services/surfaceflinger/SurfaceFlinger.h | 1 - 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 53066024d0..87cb6bfa1a 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -357,9 +357,6 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI // debugging stuff... char value[PROPERTY_VALUE_MAX]; - property_get("ro.bq.gpu_to_cpu_unsupported", value, "0"); - mGpuToCpuSupported = !atoi(value); - property_get("ro.build.type", value, "user"); mIsUserBuild = strcmp(value, "user") == 0; @@ -5181,10 +5178,7 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& comp StringAppendF(&result, " orientation=%s, isPoweredOn=%d\n", toCString(display->getOrientation()), display->isPoweredOn()); } - StringAppendF(&result, - " transaction-flags : %08x\n" - " gpu_to_cpu_unsupported : %d\n", - mTransactionFlags.load(), !mGpuToCpuSupported); + StringAppendF(&result, " transaction-flags : %08x\n", mTransactionFlags.load()); if (const auto display = getDefaultDisplayDeviceLocked()) { std::string fps, xDpi, yDpi; diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index d2907626e0..4b7a32fb9a 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1109,7 +1109,6 @@ private: // constant members (no synchronization needed for access) const nsecs_t mBootTime = systemTime(); - bool mGpuToCpuSupported = false; bool mIsUserBuild = true; // Can only accessed from the main thread, these members -- GitLab From 63b3c872ed7008067bc7d8ecc640a00006a774e2 Mon Sep 17 00:00:00 2001 From: Nataniel Borges Date: Thu, 3 Nov 2022 16:22:24 +0000 Subject: [PATCH 0455/1310] Support skipping layer dump on virtual displays Virtual displays consume significant buffer space and frequently fill the in memory ring buffer during the transition, causing test flakiness. To mitigate this problem, do'nt dump traces from virtual displays by default Bug: 255715397 Test: atest FlickerLibTest Change-Id: I4a5d8a1fac52899f126edccf6534bed4876b10e0 --- services/surfaceflinger/SurfaceFlinger.cpp | 14 ++++++++++++++ services/surfaceflinger/Tracing/LayerTracing.h | 1 + 2 files changed, 15 insertions(+) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 89d905a4ad..aec4008c8b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5016,8 +5016,22 @@ void SurfaceFlinger::dumpWideColorInfo(std::string& result) const { } LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const { + std::unordered_set stackIdsToSkip; + + // Determine if virtual layers display should be skipped + if ((traceFlags & LayerTracing::TRACE_VIRTUAL_DISPLAYS) == 0) { + for (const auto& [_, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) { + if (display->isVirtual()) { + stackIdsToSkip.insert(display->getLayerStack().id); + } + } + } + LayersProto layersProto; for (const sp& layer : mDrawingState.layersSortedByZ) { + if (stackIdsToSkip.find(layer->getLayerStack().id) != stackIdsToSkip.end()) { + continue; + } layer->writeToProto(layersProto, traceFlags); } diff --git a/services/surfaceflinger/Tracing/LayerTracing.h b/services/surfaceflinger/Tracing/LayerTracing.h index e73dac62e4..b32001cc63 100644 --- a/services/surfaceflinger/Tracing/LayerTracing.h +++ b/services/surfaceflinger/Tracing/LayerTracing.h @@ -55,6 +55,7 @@ public: TRACE_EXTRA = 1 << 3, TRACE_HWC = 1 << 4, TRACE_BUFFERS = 1 << 5, + TRACE_VIRTUAL_DISPLAYS = 1 << 6, TRACE_ALL = TRACE_INPUT | TRACE_COMPOSITION | TRACE_EXTRA, }; void setTraceFlags(uint32_t flags); -- GitLab From 3ad385bfc5db16ae0ed2c54ff9c7f474c77aff82 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 4 Nov 2022 10:09:53 -0700 Subject: [PATCH 0456/1310] Remove TouchState::down variable This variable determines whether touch is currently down. However, we are already storing the pointers that are down for each window. We can just check whether any of the touched windows have pointers inside to replace it. Bug: 211379801 Test: m inputflinger_tests && adb sync data && adb shell -t /data/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: I4137e180835da1e689c89feff7c8f223b79aa85e --- .../inputflinger/dispatcher/InputDispatcher.cpp | 16 +++++++--------- services/inputflinger/dispatcher/TouchState.cpp | 5 +++++ services/inputflinger/dispatcher/TouchState.h | 4 ++-- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 9a6ebaaed2..a47f40ccbb 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2104,7 +2104,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE); if (newGesture) { bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN; - if (switchedDevice && tempTouchState.down && !down && !isHoverAction) { + if (switchedDevice && tempTouchState.isDown() && !down && !isHoverAction) { ALOGI("Dropping event because a pointer for a different device is already down " "in display %" PRId32, displayId); @@ -2113,7 +2113,6 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( return touchedWindows; // wrong device } tempTouchState.reset(); - tempTouchState.down = down; tempTouchState.deviceId = entry.deviceId; tempTouchState.source = entry.source; tempTouchState.displayId = displayId; @@ -2234,7 +2233,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */ // If the pointer is not currently down, then ignore the event. - if (!tempTouchState.down) { + if (!tempTouchState.isDown()) { if (DEBUG_FOCUS) { ALOGD("Dropping event because the pointer is not down or we previously " "dropped the pointer down event in display %" PRId32, @@ -2445,7 +2444,7 @@ Failed: if (isHoverAction) { // Started hovering, therefore no longer down. - if (oldState && oldState->down) { + if (oldState && oldState->isDown()) { if (DEBUG_FOCUS) { ALOGD("Conflicting pointer actions: Hover received while pointer was " "down."); @@ -2465,7 +2464,7 @@ Failed: tempTouchState.reset(); } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { // First pointer went down. - if (oldState && oldState->down) { + if (oldState && oldState->isDown()) { if (DEBUG_FOCUS) { ALOGD("Conflicting pointer actions: Down received while already down."); } @@ -5326,9 +5325,8 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) { dump += StringPrintf(INDENT "TouchStatesByDisplay:\n"); for (const std::pair& pair : mTouchStatesByDisplay) { const TouchState& state = pair.second; - dump += StringPrintf(INDENT2 "%d: down=%s, deviceId=%d, source=0x%08x\n", - state.displayId, toString(state.down), state.deviceId, - state.source); + dump += StringPrintf(INDENT2 "%d: deviceId=%d, source=0x%08x\n", state.displayId, + state.deviceId, state.source); if (!state.windows.empty()) { dump += INDENT3 "Windows:\n"; for (size_t i = 0; i < state.windows.size(); i++) { @@ -5688,7 +5686,7 @@ status_t InputDispatcher::pilferPointersLocked(const sp& token) { } auto [statePtr, windowPtr] = findTouchStateAndWindowLocked(token); - if (statePtr == nullptr || windowPtr == nullptr || !statePtr->down) { + if (statePtr == nullptr || windowPtr == nullptr || windowPtr->pointerIds.isEmpty()) { ALOGW("Attempted to pilfer points from a channel without any on-going pointer streams." " Ignoring."); return BAD_VALUE; diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index a3f45cfce3..f5b7cb88d9 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -138,4 +138,9 @@ sp TouchState::getWallpaperWindow() const { return nullptr; } +bool TouchState::isDown() const { + return std::any_of(windows.begin(), windows.end(), + [](const TouchedWindow& window) { return !window.pointerIds.isEmpty(); }); +} + } // namespace android::inputdispatcher diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index f6e9fb69f8..ceeeb1eb3d 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -28,8 +28,6 @@ class WindowInfoHandle; namespace inputdispatcher { struct TouchState { - bool down = false; - // id of the device that is currently down, others are rejected int32_t deviceId = -1; // source of the device that is current down, others are rejected @@ -59,6 +57,8 @@ struct TouchState { sp getFirstForegroundWindowHandle() const; bool isSlippery() const; sp getWallpaperWindow() const; + // Whether any of the windows are currently being touched + bool isDown() const; }; } // namespace inputdispatcher -- GitLab From 72fd2b12e104193e5aab763a33e0555029931211 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Tue, 1 Nov 2022 06:11:50 +0000 Subject: [PATCH 0457/1310] Generate xml Bug: b/252835416 Test: use sample code in ag/20349112, output xmp metadata should be identical to the sample Change-Id: I1fa8d40c3fab1a8e3ebe70db42582d632bcdb7d5 --- libs/jpegrecoverymap/Android.bp | 3 +- .../include/jpegrecoverymap/recoverymap.h | 41 +++++++++++ libs/jpegrecoverymap/recoverymap.cpp | 68 +++++++++++++++++++ 3 files changed, 111 insertions(+), 1 deletion(-) diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp index 3ab2ba898e..a9248ab2e8 100644 --- a/libs/jpegrecoverymap/Android.bp +++ b/libs/jpegrecoverymap/Android.bp @@ -23,7 +23,7 @@ package { cc_library_static { name: "libjpegrecoverymap", - vendor_available: true, + host_supported: true, export_include_dirs: ["include"], local_include_dirs: ["include"], @@ -33,6 +33,7 @@ cc_library_static { ], shared_libs: [ + "libimage_io", "libutils", ], } diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h index 15eca1e705..aa580cca23 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -168,6 +168,47 @@ private: status_t appendRecoveryMap(void* compressed_jpeg_image, jr_compressed_ptr compressed_recovery_map, void* dest); + + /* + * This method generates XMP metadata. + * + * below is an example of the XMP metadata that this function generates where + * secondary_image_length = 1000 + * hdr_ratio = 1.25 + * + * + * + * + * 1 + * 1.25 + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @param secondary_image_length length of secondary image + * @param hdr_ratio hdr ratio + * @return XMP metadata in type of string + */ + std::string generateXmp(int secondary_image_length, float hdr_ratio); }; } // namespace android::recoverymap diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index 5d2572213b..8d3cd00e80 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -14,10 +14,29 @@ * limitations under the License. */ +#include "image_io/xml/xml_writer.h" + #include +#include +#include + +using namespace std; namespace android::recoverymap { +/* + * Helper function used for generating XMP metadata. + * + * @param prefix The prefix part of the name. + * @param suffix The suffix part of the name. + * @return A name of the form "prefix:suffix". + */ +string Name(const string &prefix, const string &suffix) { + std::stringstream ss; + ss << prefix << ":" << suffix; + return ss.str(); +} + status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, void* dest) { @@ -137,4 +156,53 @@ status_t RecoveryMap::appendRecoveryMap(void* compressed_jpeg_image, return NO_ERROR; } +string RecoveryMap::generateXmp(int secondary_image_length, float hdr_ratio) { + const string kContainerPrefix = "GContainer"; + const string kContainerUri = "http://ns.google.com/photos/1.0/container/"; + const string kItemPrefix = "Item"; + const string kRecoveryMap = "RecoveryMap"; + const string kDirectory = "Directory"; + const string kImageJpeg = "image/jpeg"; + const string kItem = "Item"; + const string kLength = "Length"; + const string kMime = "Mime"; + const string kPrimary = "Primary"; + const string kSemantic = "Semantic"; + const string kVersion = "Version"; + const int kVersionValue = 1; + + const string kConDir = Name(kContainerPrefix, kDirectory); + const string kContainerItem = Name(kContainerPrefix, kItem); + const string kItemLength = Name(kItemPrefix, kLength); + const string kItemMime = Name(kItemPrefix, kMime); + const string kItemSemantic = Name(kItemPrefix, kSemantic); + + const vector kConDirSeq({kConDir, string("rdf:Seq")}); + const vector kLiItem({string("rdf:li"), kContainerItem}); + + std::stringstream ss; + photos_editing_formats::image_io::XmlWriter writer(ss); + writer.StartWritingElement("x:xmpmeta"); + writer.WriteXmlns("x", "adobe:ns:meta/"); + writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2"); + writer.StartWritingElement("rdf:RDF"); + writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); + writer.StartWritingElement("rdf:Description"); + writer.WriteXmlns(kContainerPrefix, kContainerUri); + writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), kVersionValue); + writer.WriteElementAndContent(Name(kContainerPrefix, "HdrRatio"), hdr_ratio); + writer.StartWritingElements(kConDirSeq); + size_t item_depth = writer.StartWritingElements(kLiItem); + writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary); + writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg); + writer.FinishWritingElementsToDepth(item_depth); + writer.StartWritingElements(kLiItem); + writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap); + writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg); + writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length); + writer.FinishWriting(); + + return ss.str(); +} + } // namespace android::recoverymap -- GitLab From 59db956b74a09712b3289316c40eae19c4cf2279 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Thu, 27 Oct 2022 16:18:53 -0400 Subject: [PATCH 0458/1310] SF: Split Scheduler::setRefreshRateSelector Add helper functions to bind/unbind the idle timer. Bug: 255635821 Test: Build (-Wthread-safety) Change-Id: I68cd1274e2b0591652a259b7f60d0a370883e512 --- .../surfaceflinger/Scheduler/Scheduler.cpp | 47 ++++++++++--------- services/surfaceflinger/Scheduler/Scheduler.h | 10 +++- services/surfaceflinger/SurfaceFlinger.cpp | 2 + services/surfaceflinger/SurfaceFlinger.h | 2 +- .../tests/unittests/TestableScheduler.h | 6 +++ 5 files changed, 43 insertions(+), 24 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 0e1b775a8c..6e34a68fff 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -94,36 +94,24 @@ void Scheduler::startTimers() { } } -void Scheduler::setRefreshRateSelector(RefreshRateSelectorPtr selectorPtr) { - // The current RefreshRateSelector instance may outlive this call, so unbind its idle timer. - { - // mRefreshRateSelectorLock is not locked here to avoid the deadlock - // as the callback can attempt to acquire the lock before stopIdleTimer can finish - // the execution. It's safe to FakeGuard as main thread is the only thread that - // writes to the mRefreshRateSelector. - ftl::FakeGuard guard(mRefreshRateSelectorLock); - if (mRefreshRateSelector) { - mRefreshRateSelector->stopIdleTimer(); - mRefreshRateSelector->clearIdleTimerCallbacks(); - } +void Scheduler::setRefreshRateSelector(RefreshRateSelectorPtr newSelectorPtr) { + // No need to lock for reads on kMainThreadContext. + if (const auto& selectorPtr = FTL_FAKE_GUARD(mRefreshRateSelectorLock, mRefreshRateSelector)) { + unbindIdleTimer(*selectorPtr); } + { - // Clear state that depends on the current instance. + // Clear state that depends on the current RefreshRateSelector. std::scoped_lock lock(mPolicyLock); mPolicy = {}; } std::scoped_lock lock(mRefreshRateSelectorLock); - mRefreshRateSelector = std::move(selectorPtr); - if (!mRefreshRateSelector) return; + mRefreshRateSelector = std::move(newSelectorPtr); - mRefreshRateSelector->setIdleTimerCallbacks( - {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); }, - .onExpired = [this] { idleTimerCallback(TimerState::Expired); }}, - .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); }, - .onExpired = [this] { kernelIdleTimerCallback(TimerState::Expired); }}}); - - mRefreshRateSelector->startIdleTimer(); + if (mRefreshRateSelector) { + bindIdleTimer(*mRefreshRateSelector); + } } void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) { @@ -546,6 +534,21 @@ void Scheduler::setDisplayPowerMode(hal::PowerMode powerMode) { mLayerHistory.clear(); } +void Scheduler::bindIdleTimer(RefreshRateSelector& selector) { + selector.setIdleTimerCallbacks( + {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); }, + .onExpired = [this] { idleTimerCallback(TimerState::Expired); }}, + .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); }, + .onExpired = [this] { kernelIdleTimerCallback(TimerState::Expired); }}}); + + selector.startIdleTimer(); +} + +void Scheduler::unbindIdleTimer(RefreshRateSelector& selector) { + selector.stopIdleTimer(); + selector.clearIdleTimerCallbacks(); +} + void Scheduler::kernelIdleTimerCallback(TimerState state) { ATRACE_INT("ExpiredKernelIdleTimer", static_cast(state)); diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 901cf74558..797ec96d0b 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -108,7 +108,8 @@ public: void startTimers(); using RefreshRateSelectorPtr = std::shared_ptr; - void setRefreshRateSelector(RefreshRateSelectorPtr) EXCLUDES(mRefreshRateSelectorLock); + void setRefreshRateSelector(RefreshRateSelectorPtr) REQUIRES(kMainThreadContext) + EXCLUDES(mRefreshRateSelectorLock); void registerDisplay(PhysicalDisplayId, RefreshRateSelectorPtr); void unregisterDisplay(PhysicalDisplayId); @@ -253,6 +254,13 @@ private: sp createConnectionInternal( EventThread*, EventRegistrationFlags eventRegistration = {}); + void bindIdleTimer(RefreshRateSelector&) REQUIRES(kMainThreadContext, mRefreshRateSelectorLock); + + // Blocks until the timer thread exits. `mRefreshRateSelectorLock` must not be locked by the + // caller on the main thread to avoid deadlock, since the timer thread locks it before exit. + static void unbindIdleTimer(RefreshRateSelector&) REQUIRES(kMainThreadContext) + EXCLUDES(mRefreshRateSelectorLock); + // Update feature state machine to given state when corresponding timer resets or expires. void kernelIdleTimerCallback(TimerState) EXCLUDES(mRefreshRateSelectorLock); void idleTimerCallback(TimerState); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 53066024d0..864245ad48 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2941,6 +2941,8 @@ void SurfaceFlinger::processDisplayAdded(const wp& displayToken, // Display modes are reloaded on hotplug reconnect. if (display->isPrimary()) { + // TODO(b/241285876): Annotate `processDisplayAdded` instead. + ftl::FakeGuard guard(kMainThreadContext); mScheduler->setRefreshRateSelector(selectorPtr); } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index d2907626e0..897f4a3701 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -694,7 +694,7 @@ private: void commitInputWindowCommands() REQUIRES(mStateLock); void updateCursorAsync(); - void initScheduler(const sp&) REQUIRES(mStateLock); + void initScheduler(const sp&) REQUIRES(kMainThreadContext, mStateLock); void updatePhaseConfiguration(const Fps&) REQUIRES(mStateLock); void setVsyncConfig(const VsyncModulator::VsyncConfig&, nsecs_t vsyncPeriod); diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index 95c99150b3..ba214d534f 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include #include @@ -69,6 +70,11 @@ public: auto refreshRateSelector() { return holdRefreshRateSelector(); } bool hasRefreshRateSelectors() const { return !mRefreshRateSelectors.empty(); } + void setRefreshRateSelector(RefreshRateSelectorPtr selectorPtr) { + ftl::FakeGuard guard(kMainThreadContext); + return Scheduler::setRefreshRateSelector(std::move(selectorPtr)); + } + auto& mutableLayerHistory() { return mLayerHistory; } size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS { -- GitLab From 5d7de5f8e6f41f59e04f9b589a0165193a37c1ad Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Thu, 3 Nov 2022 12:38:32 -0400 Subject: [PATCH 0459/1310] SF: Clean up dumpsys for displays Remove DisplayDevice::getDebugName. Add Dumper::Section to automate indented sections with a heading. Bug: 241285876 Test: dumpsys SurfaceFlinger --displays Change-Id: I21a8e98bf26163e374810f0afbe649e694e5dda6 --- services/surfaceflinger/DisplayDevice.cpp | 18 +----------------- services/surfaceflinger/DisplayDevice.h | 4 ---- services/surfaceflinger/SurfaceFlinger.cpp | 15 +++++++++------ services/surfaceflinger/Utils/Dumper.h | 15 +++++++++++++++ 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 18ddfbca6c..9868c8ead8 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -313,26 +313,10 @@ ui::Transform::RotationFlags DisplayDevice::getPrimaryDisplayRotationFlags() { return sPrimaryDisplayRotationFlags; } -std::string DisplayDevice::getDebugName() const { - using namespace std::string_literals; - - std::string name = "Display "s + to_string(getId()) + " ("s; - - name += isVirtual() ? "virtual"s : "physical"s; - - if (isPrimary()) { - name += ", primary"s; - } - - return name + ", \""s + mDisplayName + "\")"s; -} - void DisplayDevice::dump(utils::Dumper& dumper) const { using namespace std::string_view_literals; - dumper.dump({}, getDebugName()); - - utils::Dumper::Indent indent(dumper); + dumper.dump("name"sv, '"' + mDisplayName + '"'); dumper.dump("powerMode"sv, mPowerMode); if (mRefreshRateSelector) { diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index 6c848bb100..1602a71709 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -248,10 +248,6 @@ public: // release HWC resources (if any) for removable displays void disconnect(); - /* ------------------------------------------------------------------------ - * Debugging - */ - std::string getDebugName() const; void dump(utils::Dumper&) const; private: diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 864245ad48..bc19faf47e 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -3264,7 +3265,7 @@ void SurfaceFlinger::persistDisplayBrightness(bool needsComposite) { ALOGE_IF(error != NO_ERROR, "Error setting display brightness for display %s: %d (%s)", - display->getDebugName().c_str(), error, strerror(error)); + to_string(display->getId()).c_str(), error, strerror(error)); } display->persistBrightness(needsComposite); } @@ -4929,19 +4930,21 @@ void SurfaceFlinger::dumpDisplays(std::string& result) const { utils::Dumper dumper{result}; for (const auto& [id, display] : mPhysicalDisplays) { + utils::Dumper::Section section(dumper, ftl::Concat("Display ", id.value).str()); + + display.snapshot().dump(dumper); + if (const auto device = getDisplayDeviceLocked(id)) { device->dump(dumper); } - - utils::Dumper::Indent indent(dumper); - display.snapshot().dump(dumper); - dumper.eol(); } for (const auto& [token, display] : mDisplays) { if (display->isVirtual()) { + const auto displayId = display->getId(); + utils::Dumper::Section section(dumper, + ftl::Concat("Virtual Display ", displayId.value).str()); display->dump(dumper); - dumper.eol(); } } } diff --git a/services/surfaceflinger/Utils/Dumper.h b/services/surfaceflinger/Utils/Dumper.h index 3761f9e806..39642ff17c 100644 --- a/services/surfaceflinger/Utils/Dumper.h +++ b/services/surfaceflinger/Utils/Dumper.h @@ -63,6 +63,21 @@ public: Dumper& dumper; }; + struct Section { + Section(Dumper& dumper, std::string_view heading) : dumper(dumper) { + dumper.dump({}, heading); + indent.emplace(dumper); + } + + ~Section() { + indent.reset(); + dumper.eol(); + } + + Dumper& dumper; + std::optional indent; + }; + private: std::string& mOut; int mIndent = 0; -- GitLab From 03cfce8f89c317973b3c313725d235f5edf7afba Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Wed, 2 Nov 2022 12:13:29 -0400 Subject: [PATCH 0460/1310] SF: Clean up dumpsys for Scheduler Split dump into three flags corresponding to Scheduler, EventThread, and VsyncReactor. Expand utils::Dumper to automate more types. Bug: 241285191 Test: dumpsys SurfaceFlinger --scheduler Test: dumpsys SurfaceFlinger --events Test: dumpsys SurfaceFlinger --vsync Change-Id: I17b3b772cd164305e2fe0239638b1a08236c12a2 --- services/surfaceflinger/DisplayHardware/Hal.h | 9 ++- .../surfaceflinger/Scheduler/EventThread.cpp | 1 + .../Scheduler/FrameRateOverrideMappings.cpp | 43 +++++++------ .../Scheduler/FrameRateOverrideMappings.h | 15 ++++- .../surfaceflinger/Scheduler/LayerHistory.cpp | 2 +- .../surfaceflinger/Scheduler/OneShotTimer.cpp | 6 -- .../surfaceflinger/Scheduler/OneShotTimer.h | 5 +- .../Scheduler/RefreshRateSelector.cpp | 21 +++--- .../Scheduler/RefreshRateSelector.h | 6 +- .../surfaceflinger/Scheduler/Scheduler.cpp | 37 +++++++---- services/surfaceflinger/Scheduler/Scheduler.h | 7 +- .../Scheduler/include/scheduler/Time.h | 6 ++ services/surfaceflinger/SurfaceFlinger.cpp | 64 ++++++++++++------- services/surfaceflinger/SurfaceFlinger.h | 4 +- services/surfaceflinger/Utils/Dumper.h | 41 ++++++++++-- .../surfaceflinger_scheduler_fuzzer.cpp | 4 +- 16 files changed, 181 insertions(+), 90 deletions(-) diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h index 33a7bca038..82388289ed 100644 --- a/services/surfaceflinger/DisplayHardware/Hal.h +++ b/services/surfaceflinger/DisplayHardware/Hal.h @@ -178,7 +178,8 @@ inline std::string to_string(hardware::graphics::composer::hal::Error error) { } // For utils::Dumper ADL. -namespace hardware::graphics::composer::V2_2 { +namespace hardware::graphics::composer { +namespace V2_2 { inline std::string to_string(hardware::graphics::composer::hal::PowerMode mode) { switch (mode) { @@ -197,7 +198,9 @@ inline std::string to_string(hardware::graphics::composer::hal::PowerMode mode) } } -} // namespace hardware::graphics::composer::V2_2 +} // namespace V2_2 + +namespace V2_1 { inline std::string to_string(hardware::graphics::composer::hal::Vsync vsync) { switch (vsync) { @@ -210,4 +213,6 @@ inline std::string to_string(hardware::graphics::composer::hal::Vsync vsync) { } } +} // namespace V2_1 +} // namespace hardware::graphics::composer } // namespace android diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index a6cd47b253..1acf15a55c 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -668,6 +668,7 @@ void EventThread::dump(std::string& result) const { StringAppendF(&result, " %s\n", toString(*connection).c_str()); } } + result += '\n'; } const char* EventThread::toCString(State state) { diff --git a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp index c233455994..cb9bfe93db 100644 --- a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp +++ b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp @@ -54,10 +54,9 @@ std::optional FrameRateOverrideMappings::getFrameRateOverrideForUid( std::vector FrameRateOverrideMappings::getAllFrameRateOverrides( bool supportsFrameRateOverrideByContent) { std::lock_guard lock(mFrameRateOverridesLock); + std::vector overrides; - overrides.reserve(std::max({mFrameRateOverridesFromGameManager.size(), - mFrameRateOverridesFromBackdoor.size(), - mFrameRateOverridesByContent.size()})); + overrides.reserve(maxOverridesCount()); for (const auto& [uid, frameRate] : mFrameRateOverridesFromBackdoor) { overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()}); @@ -83,28 +82,34 @@ std::vector FrameRateOverrideMappings::getAllFrameRateOverrid return overrides; } -void FrameRateOverrideMappings::dump(std::string& result) const { - using base::StringAppendF; +void FrameRateOverrideMappings::dump(utils::Dumper& dumper) const { + using namespace std::string_view_literals; std::lock_guard lock(mFrameRateOverridesLock); - StringAppendF(&result, "Frame Rate Overrides (backdoor): {"); - for (const auto& [uid, frameRate] : mFrameRateOverridesFromBackdoor) { - StringAppendF(&result, "[uid: %d frameRate: %s], ", uid, to_string(frameRate).c_str()); - } - StringAppendF(&result, "}\n"); + const bool hasOverrides = maxOverridesCount() > 0; + dumper.dump("FrameRateOverrides"sv, hasOverrides ? ""sv : "none"sv); - StringAppendF(&result, "Frame Rate Overrides (GameManager): {"); - for (const auto& [uid, frameRate] : mFrameRateOverridesFromGameManager) { - StringAppendF(&result, "[uid: %d frameRate: %s], ", uid, to_string(frameRate).c_str()); - } - StringAppendF(&result, "}\n"); + if (!hasOverrides) return; - StringAppendF(&result, "Frame Rate Overrides (setFrameRate): {"); - for (const auto& [uid, frameRate] : mFrameRateOverridesByContent) { - StringAppendF(&result, "[uid: %d frameRate: %s], ", uid, to_string(frameRate).c_str()); + dump(dumper, "setFrameRate"sv, mFrameRateOverridesByContent); + dump(dumper, "GameManager"sv, mFrameRateOverridesFromGameManager); + dump(dumper, "Backdoor"sv, mFrameRateOverridesFromBackdoor); +} + +void FrameRateOverrideMappings::dump(utils::Dumper& dumper, std::string_view name, + const UidToFrameRateOverride& overrides) const { + if (overrides.empty()) return; + + utils::Dumper::Indent indent(dumper); + dumper.dump(name); + { + utils::Dumper::Indent indent(dumper); + for (const auto& [uid, frameRate] : overrides) { + using namespace std::string_view_literals; + dumper.dump("(uid, frameRate)"sv, uid, frameRate); + } } - StringAppendF(&result, "}\n"); } bool FrameRateOverrideMappings::updateFrameRateOverridesByContent( diff --git a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h index 4185a4c0ab..da0f276a3b 100644 --- a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h +++ b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h @@ -23,7 +23,10 @@ #include #include +#include "Utils/Dumper.h" + namespace android::scheduler { + class FrameRateOverrideMappings { using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride; using UidToFrameRateOverride = std::map; @@ -34,7 +37,6 @@ public: EXCLUDES(mFrameRateOverridesLock); std::vector getAllFrameRateOverrides(bool supportsFrameRateOverrideByContent) EXCLUDES(mFrameRateOverridesLock); - void dump(std::string& result) const; bool updateFrameRateOverridesByContent(const UidToFrameRateOverride& frameRateOverrides) EXCLUDES(mFrameRateOverridesLock); void setGameModeRefreshRateForUid(FrameRateOverride frameRateOverride) @@ -42,7 +44,17 @@ public: void setPreferredRefreshRateForUid(FrameRateOverride frameRateOverride) EXCLUDES(mFrameRateOverridesLock); + void dump(utils::Dumper&) const; + private: + size_t maxOverridesCount() const REQUIRES(mFrameRateOverridesLock) { + return std::max({mFrameRateOverridesByContent.size(), + mFrameRateOverridesFromGameManager.size(), + mFrameRateOverridesFromBackdoor.size()}); + } + + void dump(utils::Dumper&, std::string_view name, const UidToFrameRateOverride&) const; + // The frame rate override lists need their own mutex as they are being read // by SurfaceFlinger, Scheduler and EventThread (as a callback) to prevent deadlocks mutable std::mutex mFrameRateOverridesLock; @@ -53,4 +65,5 @@ private: UidToFrameRateOverride mFrameRateOverridesFromBackdoor GUARDED_BY(mFrameRateOverridesLock); UidToFrameRateOverride mFrameRateOverridesFromGameManager GUARDED_BY(mFrameRateOverridesLock); }; + } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp index b884dc873d..7c9cedfab8 100644 --- a/services/surfaceflinger/Scheduler/LayerHistory.cpp +++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp @@ -275,7 +275,7 @@ void LayerHistory::clear() { std::string LayerHistory::dump() const { std::lock_guard lock(mLock); - return base::StringPrintf("LayerHistory{size=%zu, active=%zu}", + return base::StringPrintf("{size=%zu, active=%zu}", mActiveLayerInfos.size() + mInactiveLayerInfos.size(), mActiveLayerInfos.size()); } diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.cpp b/services/surfaceflinger/Scheduler/OneShotTimer.cpp index 3c8dc64f10..cd45bfdab3 100644 --- a/services/surfaceflinger/Scheduler/OneShotTimer.cpp +++ b/services/surfaceflinger/Scheduler/OneShotTimer.cpp @@ -179,11 +179,5 @@ void OneShotTimer::reset() { } } -std::string OneShotTimer::dump() const { - std::ostringstream stream; - stream << mInterval.count() << " ms"; - return stream.str(); -} - } // namespace scheduler } // namespace android diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.h b/services/surfaceflinger/Scheduler/OneShotTimer.h index 2017c31513..f95646c48f 100644 --- a/services/surfaceflinger/Scheduler/OneShotTimer.h +++ b/services/surfaceflinger/Scheduler/OneShotTimer.h @@ -23,6 +23,7 @@ #include "../Clock.h" #include +#include namespace android { namespace scheduler { @@ -42,6 +43,8 @@ public: std::unique_ptr clock = std::make_unique()); ~OneShotTimer(); + Duration interval() const { return mInterval; } + // Initializes and turns on the idle timer. void start(); // Stops the idle timer and any held resources. @@ -49,8 +52,6 @@ public: // Resets the wakeup time and fires the reset callback. void reset(); - std::string dump() const; - private: // Enum to track in what state is the timer. enum class TimerState { diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index 40af6ee575..263ebabb0e 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -1129,20 +1129,15 @@ void RefreshRateSelector::dump(utils::Dumper& dumper) const { dumper.dump("supportsFrameRateOverrideByContent"sv, mSupportsFrameRateOverrideByContent); - std::string idleTimer; - if (mIdleTimer) { - idleTimer = mIdleTimer->dump(); - } else { - idleTimer = "off"sv; - } - - if (const auto controller = mConfig.kernelIdleTimerController) { - base::StringAppendF(&idleTimer, " (kernel via %s)", ftl::enum_string(*controller).c_str()); - } else { - idleTimer += " (platform)"sv; + dumper.dump("idleTimer"sv); + { + utils::Dumper::Indent indent(dumper); + dumper.dump("interval"sv, mIdleTimer.transform(&OneShotTimer::interval)); + dumper.dump("controller"sv, + mConfig.kernelIdleTimerController + .and_then(&ftl::enum_name) + .value_or("Platform"sv)); } - - dumper.dump("idleTimer"sv, idleTimer); } std::chrono::milliseconds RefreshRateSelector::getIdleTimerTimeout() { diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h index bff16d3010..688d43d079 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h @@ -18,12 +18,12 @@ #include #include -#include #include #include #include #include +#include #include #include @@ -272,7 +272,7 @@ public: // The controller representing how the kernel idle timer will be configured // either on the HWC api or sysprop. - std::optional kernelIdleTimerController; + ftl::Optional kernelIdleTimerController; }; RefreshRateSelector(DisplayModes, DisplayModeId activeModeId, @@ -467,7 +467,7 @@ private: std::mutex mIdleTimerCallbacksMutex; std::optional mIdleTimerCallbacks GUARDED_BY(mIdleTimerCallbacksMutex); // Used to detect (lack of) frame activity. - std::optional mIdleTimer; + ftl::Optional mIdleTimer; }; } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 6e34a68fff..6108d92a50 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -599,22 +600,36 @@ void Scheduler::displayPowerTimerCallback(TimerState state) { ATRACE_INT("ExpiredDisplayPowerTimer", static_cast(state)); } -void Scheduler::dump(std::string& result) const { - using base::StringAppendF; +void Scheduler::dump(utils::Dumper& dumper) const { + using namespace std::string_view_literals; - StringAppendF(&result, "+ Touch timer: %s\n", - mTouchTimer ? mTouchTimer->dump().c_str() : "off"); - StringAppendF(&result, "+ Content detection: %s %s\n\n", - mFeatures.test(Feature::kContentDetection) ? "on" : "off", - mLayerHistory.dump().c_str()); + { + utils::Dumper::Section section(dumper, "Features"sv); - mFrameRateOverrideMappings.dump(result); + for (Feature feature : ftl::enum_range()) { + if (const auto flagOpt = ftl::flag_name(feature)) { + dumper.dump(flagOpt->substr(1), mFeatures.test(feature)); + } + } + } + { + utils::Dumper::Section section(dumper, "Policy"sv); + + dumper.dump("layerHistory"sv, mLayerHistory.dump()); + dumper.dump("touchTimer"sv, mTouchTimer.transform(&OneShotTimer::interval)); + dumper.dump("displayPowerTimer"sv, mDisplayPowerTimer.transform(&OneShotTimer::interval)); + } + + mFrameRateOverrideMappings.dump(dumper); + dumper.eol(); { + utils::Dumper::Section section(dumper, "Hardware VSYNC"sv); + std::lock_guard lock(mHWVsyncLock); - StringAppendF(&result, - "mScreenAcquired=%d mPrimaryHWVsyncEnabled=%d mHWVsyncAvailable=%d\n", - mScreenAcquired.load(), mPrimaryHWVsyncEnabled, mHWVsyncAvailable); + dumper.dump("screenAcquired"sv, mScreenAcquired.load()); + dumper.dump("hwVsyncAvailable"sv, mHWVsyncAvailable); + dumper.dump("hwVsyncEnabled"sv, mPrimaryHWVsyncEnabled); } } diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 797ec96d0b..04f3b69b98 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -45,6 +45,7 @@ #include "MessageQueue.h" #include "OneShotTimer.h" #include "RefreshRateSelector.h" +#include "Utils/Dumper.h" #include "VsyncSchedule.h" namespace android::scheduler { @@ -197,7 +198,7 @@ public: // for a given uid bool isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const; - void dump(std::string&) const; + void dump(utils::Dumper&) const; void dump(ConnectionHandle, std::string&) const; void dumpVsync(std::string&) const; @@ -335,9 +336,9 @@ private: LayerHistory mLayerHistory; // Timer used to monitor touch events. - std::optional mTouchTimer; + ftl::Optional mTouchTimer; // Timer used to monitor display power mode. - std::optional mDisplayPowerTimer; + ftl::Optional mDisplayPowerTimer; ISchedulerCallback& mSchedulerCallback; diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Time.h b/services/surfaceflinger/Scheduler/include/scheduler/Time.h index 2ca55d41ee..bd4e3c27b3 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/Time.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/Time.h @@ -17,7 +17,9 @@ #pragma once #include +#include +#include #include namespace android { @@ -79,4 +81,8 @@ constexpr Rep ticks(Duration d) { return std::chrono::duration_cast(d).count(); } +inline std::string to_string(Duration d) { + return base::StringPrintf("%.3f ms", ticks(d)); +} + } // namespace android diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index bc19faf47e..738051436f 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -161,6 +161,7 @@ namespace android { using namespace std::chrono_literals; using namespace std::string_literals; +using namespace std::string_view_literals; using namespace hardware::configstore; using namespace hardware::configstore::V1_0; @@ -3404,19 +3405,18 @@ void SurfaceFlinger::initScheduler(const sp& display) { features |= Feature::kPresentFences; } + auto selectorPtr = display->holdRefreshRateSelector(); + if (selectorPtr->kernelIdleTimerController()) { + features |= Feature::kKernelIdleTimer; + } + mScheduler = std::make_unique(static_cast(*this), static_cast(*this), features); - { - auto selectorPtr = display->holdRefreshRateSelector(); - if (selectorPtr->kernelIdleTimerController()) { - features |= Feature::kKernelIdleTimer; - } + mScheduler->createVsyncSchedule(features); + mScheduler->setRefreshRateSelector(selectorPtr); + mScheduler->registerDisplay(display->getPhysicalId(), std::move(selectorPtr)); - mScheduler->createVsyncSchedule(features); - mScheduler->setRefreshRateSelector(selectorPtr); - mScheduler->registerDisplay(display->getPhysicalId(), std::move(selectorPtr)); - } setVsyncEnabled(false); mScheduler->startTimers(); @@ -4758,17 +4758,18 @@ status_t SurfaceFlinger::doDump(int fd, const DumpArgs& args, bool asProto) { {"--comp-displays"s, dumper(&SurfaceFlinger::dumpCompositionDisplays)}, {"--display-id"s, dumper(&SurfaceFlinger::dumpDisplayIdentificationData)}, {"--displays"s, dumper(&SurfaceFlinger::dumpDisplays)}, - {"--dispsync"s, dumper([this](std::string& s) { mScheduler->dumpVsync(s); })}, {"--edid"s, argsDumper(&SurfaceFlinger::dumpRawDisplayIdentificationData)}, + {"--events"s, dumper(&SurfaceFlinger::dumpEvents)}, + {"--frametimeline"s, argsDumper(&SurfaceFlinger::dumpFrameTimeline)}, + {"--hwclayers"s, dumper(&SurfaceFlinger::dumpHwcLayersMinidumpLocked)}, {"--latency"s, argsDumper(&SurfaceFlinger::dumpStatsLocked)}, {"--latency-clear"s, argsDumper(&SurfaceFlinger::clearStatsLocked)}, {"--list"s, dumper(&SurfaceFlinger::listLayersLocked)}, {"--planner"s, argsDumper(&SurfaceFlinger::dumpPlannerInfo)}, + {"--scheduler"s, dumper(&SurfaceFlinger::dumpScheduler)}, {"--timestats"s, protoDumper(&SurfaceFlinger::dumpTimeStats)}, - {"--vsync"s, dumper(&SurfaceFlinger::dumpVSync)}, + {"--vsync"s, dumper(&SurfaceFlinger::dumpVsync)}, {"--wide-color"s, dumper(&SurfaceFlinger::dumpWideColorInfo)}, - {"--frametimeline"s, argsDumper(&SurfaceFlinger::dumpFrameTimeline)}, - {"--hwclayers"s, dumper(&SurfaceFlinger::dumpHwcLayersMinidumpLocked)}, }; const auto flag = args.empty() ? ""s : std::string(String8(args[0])); @@ -4892,24 +4893,39 @@ void SurfaceFlinger::appendSfConfigString(std::string& result) const { result.append("]"); } -void SurfaceFlinger::dumpVSync(std::string& result) const { - mScheduler->dump(result); +void SurfaceFlinger::dumpScheduler(std::string& result) const { + utils::Dumper dumper{result}; + + mScheduler->dump(dumper); + + // TODO(b/241286146): Move to Scheduler. + { + utils::Dumper::Indent indent(dumper); + dumper.dump("lastHwcVsyncState"sv, mLastHWCVsyncState); + dumper.dump("pendingHwcVsyncState"sv, mHWCVsyncPendingState); + } + dumper.eol(); + + // TODO(b/241285876): Move to DisplayModeController. + dumper.dump("debugDisplayModeSetByBackdoor"sv, mDebugDisplayModeSetByBackdoor); + dumper.eol(); mRefreshRateStats->dump(result); - result.append("\n"); + dumper.eol(); mVsyncConfiguration->dump(result); StringAppendF(&result, - " present offset: %9" PRId64 " ns\t VSYNC period: %9" PRId64 " ns\n\n", + " present offset: %9" PRId64 " ns\t VSYNC period: %9" PRId64 + " ns\n\n", dispSyncPresentTimeOffset, getVsyncPeriodFromHWC()); +} - StringAppendF(&result, "(mode override by backdoor: %s)\n\n", - mDebugDisplayModeSetByBackdoor ? "yes" : "no"); - +void SurfaceFlinger::dumpEvents(std::string& result) const { mScheduler->dump(mAppConnectionHandle, result); +} + +void SurfaceFlinger::dumpVsync(std::string& result) const { mScheduler->dumpVsync(result); - StringAppendF(&result, "mHWCVsyncPendingState=%s mLastHWCVsyncState=%s\n", - to_string(mHWCVsyncPendingState).c_str(), to_string(mLastHWCVsyncState).c_str()); } void SurfaceFlinger::dumpPlannerInfo(const DumpArgs& args, std::string& result) const { @@ -5141,7 +5157,9 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& comp colorizer.bold(result); result.append("Scheduler:\n"); colorizer.reset(result); - dumpVSync(result); + dumpScheduler(result); + dumpEvents(result); + dumpVsync(result); result.append("\n"); StringAppendF(&result, "Total missed frame count: %u\n", mFrameMissedCount.load()); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 897f4a3701..9245d78559 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1022,7 +1022,9 @@ private: void dumpFrameTimeline(const DumpArgs& args, std::string& result) const; void logFrameStats(TimePoint now) REQUIRES(kMainThreadContext); - void dumpVSync(std::string& result) const REQUIRES(mStateLock); + void dumpScheduler(std::string& result) const REQUIRES(mStateLock); + void dumpEvents(std::string& result) const REQUIRES(mStateLock); + void dumpVsync(std::string& result) const REQUIRES(mStateLock); void dumpCompositionDisplays(std::string& result) const REQUIRES(mStateLock); void dumpDisplays(std::string& result) const REQUIRES(mStateLock); diff --git a/services/surfaceflinger/Utils/Dumper.h b/services/surfaceflinger/Utils/Dumper.h index 39642ff17c..ee942177e5 100644 --- a/services/surfaceflinger/Utils/Dumper.h +++ b/services/surfaceflinger/Utils/Dumper.h @@ -16,10 +16,11 @@ #pragma once -#include #include #include +#include + namespace android::utils { // Dumps variables by appending their name and value to the output string. A variable is formatted @@ -44,16 +45,48 @@ public: eol(); } + void dump(std::string_view name, const std::string& value) { + dump(name, static_cast(value)); + } + void dump(std::string_view name, bool value) { using namespace std::string_view_literals; dump(name, value ? "true"sv : "false"sv); } template - void dump(std::string_view name, const std::optional& value) { - using namespace std::string_view_literals; + void dump(std::string_view name, const std::optional& opt) { + if (opt) { + dump(name, *opt); + } else { + using namespace std::string_view_literals; + dump(name, "nullopt"sv); + } + } + + template + void dump(std::string_view name, const ftl::Optional& opt) { + dump(name, static_cast&>(opt)); + } + + template + void dump(std::string_view name, const T& value, const Ts&... rest) { + std::string string; + + constexpr bool kIsTuple = sizeof...(Ts) > 0; + if constexpr (kIsTuple) { + string += '{'; + } + using std::to_string; - dump(name, value ? to_string(*value) : "nullopt"sv); + string += to_string(value); + + if constexpr (kIsTuple) { + string += ((", " + to_string(rest)) + ...); + string += '}'; + } + + dump(name, string); } struct Indent { diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index f2d008d59f..bd11a37a06 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -249,7 +249,9 @@ void SchedulerFuzzer::fuzzLayerHistory() { scheduler->setDuration(handle, (std::chrono::nanoseconds)mFdp.ConsumeIntegral(), (std::chrono::nanoseconds)mFdp.ConsumeIntegral()); - dump(scheduler, &mFdp); + std::string result = mFdp.ConsumeRandomLengthString(kRandomStringLength); + utils::Dumper dumper(result); + scheduler->dump(dumper); } void SchedulerFuzzer::fuzzVSyncReactor() { -- GitLab From ef85832d477f6e38f90411a5c2beb71703116813 Mon Sep 17 00:00:00 2001 From: Chris Forbes Date: Fri, 4 Nov 2022 17:13:44 +1300 Subject: [PATCH 0461/1310] Add deqp conformance level feature files for 2023 Bug: b/244942205 Change-Id: I74489b768b2314b29d22a7217ba2fcd9eea88a96 --- ...oftware.opengles.deqp.level-2023-03-01.xml | 21 +++++++++++++++++++ ....software.vulkan.deqp.level-2023-03-01.xml | 21 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 data/etc/android.software.opengles.deqp.level-2023-03-01.xml create mode 100644 data/etc/android.software.vulkan.deqp.level-2023-03-01.xml diff --git a/data/etc/android.software.opengles.deqp.level-2023-03-01.xml b/data/etc/android.software.opengles.deqp.level-2023-03-01.xml new file mode 100644 index 0000000000..d0b594c73d --- /dev/null +++ b/data/etc/android.software.opengles.deqp.level-2023-03-01.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/data/etc/android.software.vulkan.deqp.level-2023-03-01.xml b/data/etc/android.software.vulkan.deqp.level-2023-03-01.xml new file mode 100644 index 0000000000..6ae248ac3c --- /dev/null +++ b/data/etc/android.software.vulkan.deqp.level-2023-03-01.xml @@ -0,0 +1,21 @@ + + + + + + + -- GitLab From 8ca643a361391e9b975cc75dc593540885306c10 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Tue, 18 Oct 2022 18:26:47 -0700 Subject: [PATCH 0462/1310] SF: Generalize frame rate override to any frame rate Add support to be able to override the frame rate of an app to any frame rate, as long as it is a divisor of the display refresh rate. Test: SF unit tests Bug: 241460058 Bug: 241447632 Change-Id: Ibf8fa600127d3d5672a4c2a58d0a93b190854cc1 --- .../Scheduler/RefreshRateSelector.cpp | 101 +++++++----- .../Scheduler/RefreshRateSelector.h | 32 +++- .../Scheduler/include/scheduler/Fps.h | 8 + services/surfaceflinger/SurfaceFlinger.cpp | 15 +- .../SurfaceFlingerProperties.cpp | 4 + .../surfaceflinger/SurfaceFlingerProperties.h | 2 + .../sysprop/SurfaceFlingerProperties.sysprop | 12 ++ .../api/SurfaceFlingerProperties-current.txt | 4 + .../unittests/RefreshRateSelectorTest.cpp | 150 +++++++++++++++++- 9 files changed, 276 insertions(+), 52 deletions(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index f22f9e7249..c913891b62 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include "../SurfaceFlingerProperties.h" @@ -105,7 +106,7 @@ std::vector sortByRefreshRate(const DisplayModes& modes, Fi return sortedModes; } -bool canModesSupportFrameRateOverride(const std::vector& sortedModes) { +bool shouldEnableFrameRateOverride(const std::vector& sortedModes) { for (const auto it1 : sortedModes) { const auto& mode1 = it1->second; for (const auto it2 : sortedModes) { @@ -264,7 +265,7 @@ float RefreshRateSelector::calculateLayerScoreLocked(const LayerRequirement& lay if (layer.vote == LayerVoteType::ExplicitExact) { const int divisor = getFrameRateDivisor(refreshRate, layer.desiredRefreshRate); - if (mSupportsFrameRateOverrideByContent) { + if (supportsFrameRateOverrideByContent()) { // Since we support frame rate override, allow refresh rates which are // multiples of the layer's request, as those apps would be throttled // down to run at the desired refresh rate. @@ -579,7 +580,7 @@ auto RefreshRateSelector::getRankedRefreshRatesLocked(const std::vector UidToFrameRateOverride { ATRACE_CALL(); - ALOGV("%s: %zu layers", __func__, layers.size()); std::lock_guard lock(mLock); - std::vector scores; - scores.reserve(mDisplayModes.size()); - - for (auto it = mDisplayModes.begin(); it != mDisplayModes.end(); ++it) { - scores.emplace_back(RefreshRateScore{it, 0.0f}); + // Prepare a set of supported display refresh rates for easy lookup + constexpr size_t kStaticCapacity = 8; + ftl::SmallMap supportedDisplayRefreshRates; + if (mConfig.enableFrameRateOverride == + Config::FrameRateOverride::EnabledForNativeRefreshRates) { + for (const auto& [_, modePtr] : mDisplayModes) { + supportedDisplayRefreshRates.try_emplace(modePtr->getFps(), ftl::unit); + } } - std::sort(scores.begin(), scores.end(), [](const auto& lhs, const auto& rhs) { - const auto& mode1 = lhs.modeIt->second; - const auto& mode2 = rhs.modeIt->second; - return isStrictlyLess(mode1->getFps(), mode2->getFps()); - }); + const auto* policyPtr = getCurrentPolicyLocked(); + // We don't want to run lower than 30fps + const Fps minFrameRate = std::max(policyPtr->appRequestRanges.render.min, 30_Hz, isApproxLess); + + using fps_approx_ops::operator/; + const unsigned numMultiples = displayRefreshRate / minFrameRate; + + std::vector> scoredFrameRates; + scoredFrameRates.reserve(numMultiples); + + for (unsigned n = numMultiples; n > 0; n--) { + const Fps divisor = displayRefreshRate / n; + if (mConfig.enableFrameRateOverride == + Config::FrameRateOverride::EnabledForNativeRefreshRates && + !supportedDisplayRefreshRates.contains(divisor)) { + continue; + } + + if (policyPtr->appRequestRanges.render.includes(divisor)) { + ALOGV("%s: adding %s as a potential frame rate", __func__, to_string(divisor).c_str()); + scoredFrameRates.emplace_back(divisor, 0); + } + } const auto layersByUid = groupLayersByUid(layers); UidToFrameRateOverride frameRateOverrides; @@ -680,7 +701,7 @@ auto RefreshRateSelector::getFrameRateOverrides(const std::vectorvote != LayerVoteType::ExplicitDefault && layer->vote != LayerVoteType::ExplicitExactOrMultiple && layer->vote != LayerVoteType::ExplicitExact); - for (auto& [modeIt, score, _] : scores) { + for (auto& [fps, score] : scoredFrameRates) { constexpr bool isSeamlessSwitch = true; - const auto layerScore = calculateLayerScoreLocked(*layer, modeIt->second->getFps(), - isSeamlessSwitch); + const auto layerScore = calculateLayerScoreLocked(*layer, fps, isSeamlessSwitch); score += layer->weight * layerScore; } } - // We just care about the refresh rates which are a divisor of the - // display refresh rate - const auto it = std::remove_if(scores.begin(), scores.end(), [&](RefreshRateScore score) { - const auto& [id, mode] = *score.modeIt; - return getFrameRateDivisor(displayRefreshRate, mode->getFps()) == 0; - }); - scores.erase(it, scores.end()); - // If we never scored any layers, we don't have a preferred frame rate - if (std::all_of(scores.begin(), scores.end(), - [](RefreshRateScore score) { return score.overallScore == 0; })) { + if (std::all_of(scoredFrameRates.begin(), scoredFrameRates.end(), + [](const auto& scoredFrameRate) { + const auto [_, score] = scoredFrameRate; + return score == 0; + })) { continue; } // Now that we scored all the refresh rates we need to pick the lowest refresh rate // that got the highest score. - const DisplayModePtr& bestRefreshRate = - std::min_element(scores.begin(), scores.end(), - RefreshRateScoreComparator{.refreshRateOrder = - RefreshRateOrder::Ascending}) - ->modeIt->second; - frameRateOverrides.emplace(uid, bestRefreshRate->getFps()); + const auto [overrideFps, _] = + *std::max_element(scoredFrameRates.begin(), scoredFrameRates.end(), + [](const auto& lhsPair, const auto& rhsPair) { + const float lhs = lhsPair.second; + const float rhs = rhsPair.second; + return lhs < rhs && !ScoredRefreshRate::scoresEqual(lhs, rhs); + }); + ALOGV("%s: overriding to %s for uid=%d", __func__, to_string(overrideFps).c_str(), uid); + frameRateOverrides.emplace(uid, overrideFps); } return frameRateOverrides; @@ -894,8 +912,17 @@ void RefreshRateSelector::updateDisplayModes(DisplayModes modes, DisplayModeId a mDisplayManagerPolicy = {}; mDisplayManagerPolicy.defaultMode = activeModeId; - mSupportsFrameRateOverrideByContent = - mConfig.enableFrameRateOverride && canModesSupportFrameRateOverride(sortedModes); + mFrameRateOverrideConfig = [&] { + switch (mConfig.enableFrameRateOverride) { + case Config::FrameRateOverride::Disabled: + case Config::FrameRateOverride::Enabled: + return mConfig.enableFrameRateOverride; + case Config::FrameRateOverride::EnabledForNativeRefreshRates: + return shouldEnableFrameRateOverride(sortedModes) + ? Config::FrameRateOverride::EnabledForNativeRefreshRates + : Config::FrameRateOverride::Disabled; + } + }(); constructAvailableRefreshRates(); } @@ -1128,7 +1155,7 @@ void RefreshRateSelector::dump(utils::Dumper& dumper) const { dumper.dump("overridePolicy"sv, currentPolicy.toString()); } - dumper.dump("supportsFrameRateOverrideByContent"sv, mSupportsFrameRateOverrideByContent); + dumper.dump("frameRateOverrideConfig"sv, *ftl::enum_name(mFrameRateOverrideConfig)); std::string idleTimer; if (mIdleTimer) { diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h index 887d81566a..abbd30465a 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h @@ -251,7 +251,20 @@ public: // Configuration flags. struct Config { - bool enableFrameRateOverride = false; + enum class FrameRateOverride { + // Do not override the frame rate for an app + Disabled, + + // Override the frame rate for an app to a value which is also + // a display refresh rate + EnabledForNativeRefreshRates, + + // Override the frame rate for an app to any value + Enabled, + + ftl_last = Enabled + }; + FrameRateOverride enableFrameRateOverride = FrameRateOverride::Disabled; // Specifies the upper refresh rate threshold (inclusive) for layer vote types of multiple // or heuristic, such that refresh rates higher than this value will not be voted for. 0 if @@ -266,11 +279,12 @@ public: std::optional kernelIdleTimerController; }; - RefreshRateSelector(DisplayModes, DisplayModeId activeModeId, - Config config = {.enableFrameRateOverride = false, - .frameRateMultipleThreshold = 0, - .idleTimerTimeout = 0ms, - .kernelIdleTimerController = {}}); + RefreshRateSelector( + DisplayModes, DisplayModeId activeModeId, + Config config = {.enableFrameRateOverride = Config::FrameRateOverride::Disabled, + .frameRateMultipleThreshold = 0, + .idleTimerTimeout = 0ms, + .kernelIdleTimerController = {}}); RefreshRateSelector(const RefreshRateSelector&) = delete; RefreshRateSelector& operator=(const RefreshRateSelector&) = delete; @@ -293,7 +307,9 @@ public: // refresh rates. KernelIdleTimerAction getIdleTimerAction() const; - bool supportsFrameRateOverrideByContent() const { return mSupportsFrameRateOverrideByContent; } + bool supportsFrameRateOverrideByContent() const { + return mFrameRateOverrideConfig != Config::FrameRateOverride::Disabled; + } // Return the display refresh rate divisor to match the layer // frame rate, or 0 if the display refresh rate is not a multiple of the @@ -446,7 +462,7 @@ private: const std::vector mKnownFrameRates; const Config mConfig; - bool mSupportsFrameRateOverrideByContent; + Config::FrameRateOverride mFrameRateOverrideConfig; struct GetRankedRefreshRatesCache { std::pair, GlobalSignals> arguments; diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h index d89f685678..31b1d6901c 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h @@ -144,8 +144,16 @@ inline bool operator!=(const FpsRanges& lhs, const FpsRanges& rhs) { return !(lhs == rhs); } +inline unsigned operator/(Fps lhs, Fps rhs) { + return static_cast(std::ceil(lhs.getValue() / rhs.getValue())); +} + } // namespace fps_approx_ops +constexpr Fps operator/(Fps fps, unsigned divisor) { + return Fps::fromPeriodNsecs(fps.getPeriodNsecs() * static_cast(divisor)); +} + inline bool FpsRange::includes(Fps fps) const { using fps_approx_ops::operator<=; return min <= fps && fps <= max; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 2ca39d07bd..ee22ecc6a3 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2777,8 +2777,21 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( const auto [kernelIdleTimerController, idleTimerTimeoutMs] = getKernelIdleTimerProperties(compositionDisplay->getId()); + const auto enableFrameRateOverride = [&] { + using Config = scheduler::RefreshRateSelector::Config; + if (!sysprop::enable_frame_rate_override(false)) { + return Config::FrameRateOverride::Disabled; + } + + if (sysprop::frame_rate_override_for_native_rates(true)) { + return Config::FrameRateOverride::EnabledForNativeRefreshRates; + } + + return Config::FrameRateOverride::Enabled; + }(); + scheduler::RefreshRateSelector::Config config = - {.enableFrameRateOverride = android::sysprop::enable_frame_rate_override(false), + {.enableFrameRateOverride = enableFrameRateOverride, .frameRateMultipleThreshold = base::GetIntProperty("debug.sf.frame_rate_multiple_threshold", 0), .idleTimerTimeout = idleTimerTimeoutMs, diff --git a/services/surfaceflinger/SurfaceFlingerProperties.cpp b/services/surfaceflinger/SurfaceFlingerProperties.cpp index 20fa091730..c8c71dfad1 100644 --- a/services/surfaceflinger/SurfaceFlingerProperties.cpp +++ b/services/surfaceflinger/SurfaceFlingerProperties.cpp @@ -367,6 +367,10 @@ bool enable_frame_rate_override(bool defaultValue) { return SurfaceFlingerProperties::enable_frame_rate_override().value_or(defaultValue); } +bool frame_rate_override_for_native_rates(bool defaultValue) { + return SurfaceFlingerProperties::frame_rate_override_for_native_rates().value_or(defaultValue); +} + bool enable_layer_caching(bool defaultValue) { return SurfaceFlingerProperties::enable_layer_caching().value_or(defaultValue); } diff --git a/services/surfaceflinger/SurfaceFlingerProperties.h b/services/surfaceflinger/SurfaceFlingerProperties.h index 080feee686..5e316cfa0e 100644 --- a/services/surfaceflinger/SurfaceFlingerProperties.h +++ b/services/surfaceflinger/SurfaceFlingerProperties.h @@ -96,6 +96,8 @@ bool update_device_product_info_on_hotplug_reconnect(bool defaultValue); bool enable_frame_rate_override(bool defaultValue); +bool frame_rate_override_for_native_rates(bool defaultValue); + bool enable_layer_caching(bool defaultValue); bool enable_sdr_dimming(bool defaultValue); diff --git a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop index bcbe21a483..28da81ff50 100644 --- a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop +++ b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop @@ -445,6 +445,18 @@ prop { prop_name: "ro.surface_flinger.enable_frame_rate_override" } +# Limits the frame rate override feature (enable_frame_rate_override) to override the refresh rate +# to native display refresh rates only. Before introducing this flag, native display refresh rates +# was the default behvaiour. With this flag we can control which behaviour we want explicitly. +# This flag is intoruduced as a fail-safe machanism and planned to be defaulted to false. +prop { + api_name: "frame_rate_override_for_native_rates" + type: Boolean + scope: Public + access: Readonly + prop_name: "ro.surface_flinger.frame_rate_override_for_native_rates" +} + # Enables Layer Caching prop { api_name: "enable_layer_caching" diff --git a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt index 348a462038..0dfb80e5df 100644 --- a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt +++ b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt @@ -60,6 +60,10 @@ props { api_name: "force_hwc_copy_for_virtual_displays" prop_name: "ro.surface_flinger.force_hwc_copy_for_virtual_displays" } + prop { + api_name: "frame_rate_override_for_native_rates" + prop_name: "ro.surface_flinger.frame_rate_override_for_native_rates" + } prop { api_name: "has_HDR_display" prop_name: "ro.surface_flinger.has_HDR_display" diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp index 9689ddbb51..cedb7eb6ee 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp @@ -37,6 +37,7 @@ namespace android::scheduler { namespace hal = android::hardware::graphics::composer::hal; +using Config = RefreshRateSelector::Config; using LayerRequirement = RefreshRateSelector::LayerRequirement; using LayerVoteType = RefreshRateSelector::LayerVoteType; using SetPolicyResult = RefreshRateSelector::SetPolicyResult; @@ -2034,7 +2035,8 @@ TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExact) { TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExactEnableFrameRateOverride) { TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60, - {.enableFrameRateOverride = true}); + {.enableFrameRateOverride = + Config::FrameRateOverride::Enabled}); std::vector layers = {{.weight = 1.f}, {.weight = 0.5f}}; auto& explicitExactLayer = layers[0]; @@ -2100,7 +2102,8 @@ TEST_F(RefreshRateSelectorTest, getBestRefreshRate_WritesCache) { TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExactTouchBoost) { TestableRefreshRateSelector selector(kModes_60_120, kModeId60, - {.enableFrameRateOverride = true}); + {.enableFrameRateOverride = + Config::FrameRateOverride::Enabled}); std::vector layers = {{.weight = 1.f}, {.weight = 0.5f}}; auto& explicitExactLayer = layers[0]; @@ -2125,7 +2128,8 @@ TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExactTouchBoost) { TEST_F(RefreshRateSelectorTest, getBestRefreshRate_FractionalRefreshRates_ExactAndDefault) { TestableRefreshRateSelector selector(kModes_24_25_30_50_60_Frac, kModeId60, - {.enableFrameRateOverride = true}); + {.enableFrameRateOverride = + Config::FrameRateOverride::Enabled}); std::vector layers = {{.weight = 0.5f}, {.weight = 0.5f}}; auto& explicitDefaultLayer = layers[0]; @@ -2319,7 +2323,7 @@ TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_noLayers) { TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_60on120) { RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120, - {.enableFrameRateOverride = true}); + {.enableFrameRateOverride = Config::FrameRateOverride::Enabled}); std::vector layers = {{.weight = 1.f}}; layers[0].name = "Test layer"; @@ -2357,7 +2361,7 @@ TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_60on120) { TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_twoUids) { RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120, - {.enableFrameRateOverride = true}); + {.enableFrameRateOverride = Config::FrameRateOverride::Enabled}); std::vector layers = {{.ownerUid = 1234, .weight = 1.f}, {.ownerUid = 5678, .weight = 1.f}}; @@ -2390,7 +2394,7 @@ TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_twoUids) { TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_touch) { RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120, - {.enableFrameRateOverride = true}); + {.enableFrameRateOverride = Config::FrameRateOverride::Enabled}); std::vector layers = {{.ownerUid = 1234, .weight = 1.f}}; layers[0].name = "Test layer"; @@ -2428,5 +2432,139 @@ TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_touch) { EXPECT_TRUE(frameRateOverrides.empty()); } +TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_DivisorIsNotDisplayRefreshRate_Enabled) { + RefreshRateSelector selector(kModes_60_120, kModeId120, + {.enableFrameRateOverride = Config::FrameRateOverride::Enabled}); + + std::vector layers = {{.weight = 1.f}}; + layers[0].name = "Test layer"; + layers[0].ownerUid = 1234; + layers[0].desiredRefreshRate = 30_Hz; + layers[0].vote = LayerVoteType::ExplicitDefault; + + auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_EQ(1u, frameRateOverrides.size()); + ASSERT_EQ(1u, frameRateOverrides.count(1234)); + EXPECT_EQ(30_Hz, frameRateOverrides.at(1234)); + + layers[0].vote = LayerVoteType::ExplicitExactOrMultiple; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_EQ(1u, frameRateOverrides.size()); + ASSERT_EQ(1u, frameRateOverrides.count(1234)); + EXPECT_EQ(30_Hz, frameRateOverrides.at(1234)); + + layers[0].vote = LayerVoteType::NoVote; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_TRUE(frameRateOverrides.empty()); + + layers[0].vote = LayerVoteType::Min; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_TRUE(frameRateOverrides.empty()); + + layers[0].vote = LayerVoteType::Max; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_TRUE(frameRateOverrides.empty()); + + layers[0].vote = LayerVoteType::Heuristic; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_TRUE(frameRateOverrides.empty()); +} + +TEST_F(RefreshRateSelectorTest, + getFrameRateOverrides_DivisorIsNotDisplayRefreshRate_EnabledForNativeRefreshRates) { + RefreshRateSelector selector(kModes_60_120, kModeId120, + {.enableFrameRateOverride = + Config::FrameRateOverride::EnabledForNativeRefreshRates}); + + std::vector layers = {{.weight = 1.f}}; + layers[0].name = "Test layer"; + layers[0].ownerUid = 1234; + layers[0].desiredRefreshRate = 30_Hz; + layers[0].vote = LayerVoteType::ExplicitDefault; + + auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_EQ(1u, frameRateOverrides.size()); + ASSERT_EQ(1u, frameRateOverrides.count(1234)); + EXPECT_EQ(60_Hz, frameRateOverrides.at(1234)); + + layers[0].vote = LayerVoteType::ExplicitExactOrMultiple; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_EQ(1u, frameRateOverrides.size()); + ASSERT_EQ(1u, frameRateOverrides.count(1234)); + EXPECT_EQ(60_Hz, frameRateOverrides.at(1234)); + + layers[0].vote = LayerVoteType::NoVote; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_TRUE(frameRateOverrides.empty()); + + layers[0].vote = LayerVoteType::Min; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_TRUE(frameRateOverrides.empty()); + + layers[0].vote = LayerVoteType::Max; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_TRUE(frameRateOverrides.empty()); + + layers[0].vote = LayerVoteType::Heuristic; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_TRUE(frameRateOverrides.empty()); +} + +TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_InPolicy) { + TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120, + {.enableFrameRateOverride = + Config::FrameRateOverride::Enabled}); + + std::vector layers = {{.weight = 1.f}}; + { + const FpsRange physical = {120_Hz, 120_Hz}; + const FpsRange render = {60_Hz, 90_Hz}; + EXPECT_EQ(SetPolicyResult::Changed, + selector.setDisplayManagerPolicy( + {kModeId120, {physical, render}, {physical, render}})); + } + + layers[0].name = "30Hz"; + layers[0].ownerUid = 1234; + layers[0].desiredRefreshRate = 30_Hz; + layers[0].vote = LayerVoteType::ExplicitDefault; + + auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_EQ(1u, frameRateOverrides.size()); + EXPECT_EQ(1u, frameRateOverrides.count(1234)); + EXPECT_EQ(60_Hz, frameRateOverrides.at(1234)); + + { + const FpsRange physical = {120_Hz, 120_Hz}; + const FpsRange render = {30_Hz, 90_Hz}; + EXPECT_EQ(SetPolicyResult::Changed, + selector.setDisplayManagerPolicy( + {kModeId120, {physical, render}, {physical, render}})); + } + + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_EQ(1u, frameRateOverrides.size()); + EXPECT_EQ(1u, frameRateOverrides.count(1234)); + EXPECT_EQ(30_Hz, frameRateOverrides.at(1234)); + + { + const FpsRange physical = {120_Hz, 120_Hz}; + const FpsRange render = {30_Hz, 30_Hz}; + EXPECT_EQ(SetPolicyResult::Changed, + selector.setDisplayManagerPolicy( + {kModeId120, {physical, render}, {physical, render}})); + } + + layers[0].name = "60Hz"; + layers[0].ownerUid = 1234; + layers[0].desiredRefreshRate = 60_Hz; + layers[0].vote = LayerVoteType::ExplicitDefault; + + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_EQ(1u, frameRateOverrides.size()); + EXPECT_EQ(1u, frameRateOverrides.count(1234)); + EXPECT_EQ(30_Hz, frameRateOverrides.at(1234)); +} + } // namespace } // namespace android::scheduler -- GitLab From f6bca5a5233679a3dd59900c81e622a9c163599c Mon Sep 17 00:00:00 2001 From: Nick Deakin Date: Fri, 4 Nov 2022 10:43:43 -0400 Subject: [PATCH 0463/1310] jpegrecoverymap: add initial recovery map calculations. This change adds the starting point for generating and applying the recovery map. A follow-up change will add more robust tests for this update (eg. unit testing color conversions). There are a few other known TODOs remaining for these map operations: * Clean up handling around hdr_ratio (ie. utilizing XMP) * Add color space information for inputs and utilize it for ICC data * Add handling for PQ encode/decode No-Typo-Check: Lint suggesting typo in code as if it's a comment Test: build Bug: b/252835416 Change-Id: I2f6a1bf3b046036292afe46bbd2396a87cdc5164 --- libs/jpegrecoverymap/Android.bp | 4 +- .../include/jpegrecoverymap/jpegdecoder.h | 23 +- .../include/jpegrecoverymap/jpegencoder.h | 9 +- .../include/jpegrecoverymap/jpegrerrorcode.h | 4 +- .../include/jpegrecoverymap/recoverymap.h | 40 +-- .../include/jpegrecoverymap/recoverymapmath.h | 106 +++++++ libs/jpegrecoverymap/jpegdecoder.cpp | 15 +- libs/jpegrecoverymap/jpegencoder.cpp | 4 +- libs/jpegrecoverymap/recoverymap.cpp | 259 ++++++++++++++++-- libs/jpegrecoverymap/recoverymapmath.cpp | 169 ++++++++++++ libs/jpegrecoverymap/tests/Android.bp | 2 +- 11 files changed, 584 insertions(+), 51 deletions(-) create mode 100644 libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h create mode 100644 libs/jpegrecoverymap/recoverymapmath.cpp diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp index a9248ab2e8..0375915839 100644 --- a/libs/jpegrecoverymap/Android.bp +++ b/libs/jpegrecoverymap/Android.bp @@ -30,10 +30,12 @@ cc_library_static { srcs: [ "recoverymap.cpp", + "recoverymapmath.cpp", ], shared_libs: [ "libimage_io", + "libjpeg", "libutils", ], } @@ -64,4 +66,4 @@ cc_library_static { srcs: [ "jpegdecoder.cpp", ], -} \ No newline at end of file +} diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h index 2ab75503a5..df24b10ebc 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h @@ -14,6 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +#ifndef ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H +#define ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H + // We must include cstdio before jpeglib.h. It is a requirement of libjpeg. #include extern "C" { @@ -41,12 +45,22 @@ public: * Returns the decompressed raw image buffer pointer. This method must be called only after * calling decompressImage(). */ - const void* getDecompressedImagePtr(); + void* getDecompressedImagePtr(); /* * Returns the decompressed raw image buffer size. This method must be called only after * calling decompressImage(). */ size_t getDecompressedImageSize(); + /* + * Returns the image width in pixels. This method must be called only after calling + * decompressImage(). + */ + size_t getDecompressedImageWidth(); + /* + * Returns the image width in pixels. This method must be called only after calling + * decompressImage(). + */ + size_t getDecompressedImageHeight(); private: bool decode(const void* image, int length); // Returns false if errors occur. @@ -56,7 +70,12 @@ private: // Process 16 lines of Y and 16 lines of U/V each time. // We must pass at least 16 scanlines according to libjpeg documentation. static const int kCompressBatchSize = 16; - // The buffer that holds the compressed result. + // The buffer that holds the decompressed result. std::vector mResultBuffer; + // Resolution of the decompressed image. + size_t mWidth; + size_t mHeight; }; } /* namespace android */ + +#endif // ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h index 9641fda24c..61aeb8ace7 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h @@ -14,6 +14,9 @@ * limitations under the License. */ +#ifndef ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H +#define ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H + // We must include cstdio before jpeglib.h. It is a requirement of libjpeg. #include @@ -50,7 +53,7 @@ public: * Returns the compressed JPEG buffer pointer. This method must be called only after calling * compressImage(). */ - const void* getCompressedImagePtr(); + void* getCompressedImagePtr(); /* * Returns the compressed JPEG buffer size. This method must be called only after calling @@ -87,4 +90,6 @@ private: std::vector mResultBuffer; }; -} /* namespace android */ \ No newline at end of file +} /* namespace android */ + +#endif // ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h index 49ab34d154..194cd2f403 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h @@ -35,6 +35,8 @@ enum { ERROR_JPEGR_INVALID_INPUT_TYPE = JPEGR_IO_ERROR_BASE, ERROR_JPEGR_INVALID_OUTPUT_TYPE = JPEGR_IO_ERROR_BASE - 1, ERROR_JPEGR_INVALID_NULL_PTR = JPEGR_IO_ERROR_BASE - 2, + ERROR_JPEGR_RESOLUTION_MISMATCH = JPEGR_IO_ERROR_BASE - 3, + ERROR_JPEGR_BUFFER_TOO_SMALL = JPEGR_IO_ERROR_BASE - 4, JPEGR_RUNTIME_ERROR_BASE = -20000, ERROR_JPEGR_ENCODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 1, @@ -43,4 +45,4 @@ enum { ERROR_JPEGR_METADATA_ERROR = JPEGR_RUNTIME_ERROR_BASE - 4, }; -} // namespace android::recoverymap \ No newline at end of file +} // namespace android::recoverymap diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h index 06b2ab5874..94b7543e14 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -14,6 +14,9 @@ * limitations under the License. */ +#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H +#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H + #include "jpegrerrorcode.h" namespace android::recoverymap { @@ -71,7 +74,8 @@ public: * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. * * Generate recovery map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append - * the recovery map to the end of the compressed JPEG. + * the recovery map to the end of the compressed JPEG. HDR and SDR inputs must be the same + * resolution and color space. * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format * @param dest destination of the compressed JPEGR image @@ -84,7 +88,7 @@ public: */ status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, - void* dest, + jr_compressed_ptr dest, int quality, jr_exif_ptr exif, float hdr_ratio = 0.0f); @@ -95,7 +99,7 @@ public: * This method requires HAL Hardware JPEG encoder. * * Generate recovery map from the HDR and SDR inputs, append the recovery map to the end of the - * compressed JPEG. + * compressed JPEG. HDR and SDR inputs must be the same resolution and color space. * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format * @param compressed_jpeg_image compressed 8-bit JPEG image @@ -106,8 +110,8 @@ public: */ status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, - void* compressed_jpeg_image, - void* dest, + jr_compressed_ptr compressed_jpeg_image, + jr_compressed_ptr dest, float hdr_ratio = 0.0f); /* @@ -116,7 +120,8 @@ public: * This method requires HAL Hardware JPEG encoder. * * Decode the compressed 8-bit JPEG image to YUV SDR, generate recovery map from the HDR input - * and the decoded SDR result, append the recovery map to the end of the compressed JPEG. + * and the decoded SDR result, append the recovery map to the end of the compressed JPEG. HDR + * and SDR inputs must be the same resolution and color space. * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param compressed_jpeg_image compressed 8-bit JPEG image * @param dest destination of the compressed JPEGR image @@ -125,8 +130,8 @@ public: * @return NO_ERROR if encoding succeeds, error code if error occurs. */ status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, - void* compressed_jpeg_image, - void* dest, + jr_compressed_ptr compressed_jpeg_image, + jr_compressed_ptr dest, float hdr_ratio = 0.0f); /* @@ -147,7 +152,7 @@ public: * | SDR | false | SDR | * @return NO_ERROR if decoding succeeds, error code if error occurs. */ - status_t decodeJPEGR(void* compressed_jpegr_image, + status_t decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, jr_uncompressed_ptr dest, jr_exif_ptr exif = nullptr, bool request_sdr = false); @@ -159,7 +164,7 @@ private: * @param dest decoded recover map * @return NO_ERROR if decoding succeeds, error code if error occurs. */ - status_t decodeRecoveryMap(jr_compressed_ptr compressed_recovery_map, + status_t decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map, jr_uncompressed_ptr dest); /* @@ -169,16 +174,17 @@ private: * @param dest encoded recover map * @return NO_ERROR if encoding succeeds, error code if error occurs. */ - status_t encodeRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, + status_t compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, jr_compressed_ptr dest); /* * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and - * 10-bit yuv images as input, and calculate the uncompressed recovery map. + * 10-bit yuv images as input, and calculate the uncompressed recovery map. The input images + * must be the same resolution. * * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format * @param uncompressed_p010_image uncompressed HDR image in P010 color format - * @param dest recover map + * @param dest recovery map; caller responsible for memory of data * @return NO_ERROR if calculation succeeds, error code if error occurs. */ status_t generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, @@ -207,7 +213,7 @@ private: * @param dest destination of compressed recovery map * @return NO_ERROR if calculation succeeds, error code if error occurs. */ - status_t extractRecoveryMap(void* compressed_jpegr_image, jr_compressed_ptr dest); + status_t extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image, jr_compressed_ptr dest); /* * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image @@ -219,9 +225,9 @@ private: * @param dest compressed JPEGR image * @return NO_ERROR if calculation succeeds, error code if error occurs. */ - status_t appendRecoveryMap(void* compressed_jpeg_image, + status_t appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, jr_compressed_ptr compressed_recovery_map, - void* dest); + jr_compressed_ptr dest); /* * This method generates XMP metadata. @@ -266,3 +272,5 @@ private: }; } // namespace android::recoverymap + +#endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h new file mode 100644 index 0000000000..8e9b07bf3d --- /dev/null +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h @@ -0,0 +1,106 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H +#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H + +#include + +#include + +namespace android::recoverymap { + +const float kSdrWhiteNits = 100.0f; + +struct Color { + union { + struct { + float r; + float g; + float b; + }; + struct { + float y; + float u; + float v; + }; + }; +}; + +/* + * Convert from OETF'd bt.2100 RGB to YUV, according to BT.2100 + */ +Color bt2100RgbToYuv(Color e); + +/* + * Convert srgb YUV to RGB, according to ECMA TR/98. + */ +Color srgbYuvToRgb(Color e); + +/* + * TODO: better source for srgb transfer function + * Convert from srgb to linear, according to https://en.wikipedia.org/wiki/SRGB. + * [0.0, 1.0] range in and out. + */ +float srgbInvOetf(float e); +Color srgbInvOetf(Color e); + +/* + * Convert from HLG to scene luminance in nits, according to BT.2100. + */ +float hlgInvOetf(float e); + +/* + * Convert from scene luminance in nits to HLG, according to BT.2100. + */ +float hlgOetf(float e); +Color hlgOetf(Color e); + +/* + * Calculate the 8-bit unsigned integer recovery value for the given SDR and HDR + * luminances in linear space, and the hdr ratio to encode against. + */ +uint8_t encodeRecovery(float y_sdr, float y_hdr, float hdr_ratio); + +/* + * Calculates the linear luminance in nits after applying the given recovery + * value, with the given hdr ratio, to the given sdr input in the range [0, 1]. + */ +Color applyRecovery(Color e, float recovery, float hdr_ratio); + +/* + * Helper for sampling from images. + */ +Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y); + +/* + * Sample the recovery value for the map from a given x,y coordinate on a scale + * that is map scale factor larger than the map size. + */ +float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y); + +/* + * Sample the image Y value at the provided location, with a weighting based on nearby pixels + * and the map scale factor. + * + * Expect narrow-range image data for P010. + */ +float sampleYuv420Y(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y); +float sampleP010Y(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y); + +} // namespace android::recoverymap + +#endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoder.cpp index 22a5389648..c1fb6c3f1d 100644 --- a/libs/jpegrecoverymap/jpegdecoder.cpp +++ b/libs/jpegrecoverymap/jpegdecoder.cpp @@ -95,7 +95,7 @@ bool JpegDecoder::decompressImage(const void* image, int length) { return true; } -const void* JpegDecoder::getDecompressedImagePtr() { +void* JpegDecoder::getDecompressedImagePtr() { return mResultBuffer.data(); } @@ -103,6 +103,14 @@ size_t JpegDecoder::getDecompressedImageSize() { return mResultBuffer.size(); } +size_t JpegDecoder::getDecompressedImageWidth() { + return mWidth; +} + +size_t JpegDecoder::getDecompressedImageHeight() { + return mHeight; +} + bool JpegDecoder::decode(const void* image, int length) { jpeg_decompress_struct cinfo; jpegr_source_mgr mgr(static_cast(image), length); @@ -119,6 +127,9 @@ bool JpegDecoder::decode(const void* image, int length) { cinfo.src = &mgr; jpeg_read_header(&cinfo, TRUE); + mWidth = cinfo.image_width; + mHeight = cinfo.image_height; + if (cinfo.jpeg_color_space == JCS_YCbCr) { mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0); } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { @@ -222,4 +233,4 @@ bool JpegDecoder::decompressSingleChannel(jpeg_decompress_struct* cinfo, const u return true; } -} // namespace android \ No newline at end of file +} // namespace android diff --git a/libs/jpegrecoverymap/jpegencoder.cpp b/libs/jpegrecoverymap/jpegencoder.cpp index d45d9b33c9..1997bf9ea3 100644 --- a/libs/jpegrecoverymap/jpegencoder.cpp +++ b/libs/jpegrecoverymap/jpegencoder.cpp @@ -52,7 +52,7 @@ bool JpegEncoder::compressImage(const void* image, int width, int height, int qu return true; } -const void* JpegEncoder::getCompressedImagePtr() { +void* JpegEncoder::getCompressedImagePtr() { return mResultBuffer.data(); } @@ -236,4 +236,4 @@ bool JpegEncoder::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8 return true; } -} // namespace android \ No newline at end of file +} // namespace android diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index 71e5f6f21a..4a90053b12 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -14,9 +14,20 @@ * limitations under the License. */ -#include "image_io/xml/xml_writer.h" +// TODO: need to clean up handling around hdr_ratio and passing it around +// TODO: need to handle color space information; currently we assume everything +// is srgb in. +// TODO: handle PQ encode/decode (currently only HLG) #include + +#include +#include +#include + +#include + +#include #include #include @@ -24,6 +35,18 @@ using namespace std; namespace android::recoverymap { +#define JPEGR_CHECK(x) \ + { \ + status_t status = (x); \ + if ((status) != NO_ERROR) { \ + return status; \ + } \ + } + +// Map is quarter res / sixteenth size +static const size_t kMapDimensionScaleFactor = 4; + + /* * Helper function used for generating XMP metadata. * @@ -39,7 +62,7 @@ string Name(const string &prefix, const string &suffix) { status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, - void* dest, + jr_compressed_ptr dest, int quality, jr_exif_ptr /* exif */, float /* hdr_ratio */) { @@ -53,16 +76,44 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return ERROR_JPEGR_INVALID_INPUT_TYPE; } - // TBD + if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width + || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) { + return ERROR_JPEGR_RESOLUTION_MISMATCH; + } + + jpegr_uncompressed_struct map; + JPEGR_CHECK(generateRecoveryMap(uncompressed_yuv_420_image, uncompressed_p010_image, &map)); + std::unique_ptr map_data; + map_data.reset(reinterpret_cast(map.data)); + + jpegr_compressed_struct compressed_map; + std::unique_ptr compressed_map_data = + std::make_unique(map.width * map.height); + compressed_map.data = compressed_map_data.get(); + JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); + + JpegEncoder jpeg_encoder; + // TODO: what quality to use? + // TODO: ICC data - need color space information + if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data, + uncompressed_yuv_420_image->width, + uncompressed_yuv_420_image->height, 95, nullptr, 0)) { + return ERROR_JPEGR_ENCODE_ERROR; + } + jpegr_compressed_struct jpeg; + jpeg.data = jpeg_encoder.getCompressedImagePtr(); + jpeg.length = jpeg_encoder.getCompressedImageSize(); + + JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, dest)); + return NO_ERROR; } status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, - void* compressed_jpeg_image, - void* dest, + jr_compressed_ptr compressed_jpeg_image, + jr_compressed_ptr dest, float /* hdr_ratio */) { - if (uncompressed_p010_image == nullptr || uncompressed_yuv_420_image == nullptr || compressed_jpeg_image == nullptr @@ -70,13 +121,30 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return ERROR_JPEGR_INVALID_NULL_PTR; } - // TBD + if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width + || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) { + return ERROR_JPEGR_RESOLUTION_MISMATCH; + } + + jpegr_uncompressed_struct map; + JPEGR_CHECK(generateRecoveryMap(uncompressed_yuv_420_image, uncompressed_p010_image, &map)); + std::unique_ptr map_data; + map_data.reset(reinterpret_cast(map.data)); + + jpegr_compressed_struct compressed_map; + std::unique_ptr compressed_map_data = + std::make_unique(map.width * map.height); + compressed_map.data = compressed_map_data.get(); + JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); + + JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, dest)); + return NO_ERROR; } status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, - void* compressed_jpeg_image, - void* dest, + jr_compressed_ptr compressed_jpeg_image, + jr_compressed_ptr dest, float /* hdr_ratio */) { if (uncompressed_p010_image == nullptr || compressed_jpeg_image == nullptr @@ -84,11 +152,37 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return ERROR_JPEGR_INVALID_NULL_PTR; } - // TBD + JpegDecoder jpeg_decoder; + if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) { + return ERROR_JPEGR_DECODE_ERROR; + } + jpegr_uncompressed_struct uncompressed_yuv_420_image; + uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr(); + uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth(); + uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight(); + + if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width + || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) { + return ERROR_JPEGR_RESOLUTION_MISMATCH; + } + + jpegr_uncompressed_struct map; + JPEGR_CHECK(generateRecoveryMap(&uncompressed_yuv_420_image, uncompressed_p010_image, &map)); + std::unique_ptr map_data; + map_data.reset(reinterpret_cast(map.data)); + + jpegr_compressed_struct compressed_map; + std::unique_ptr compressed_map_data = + std::make_unique(map.width * map.height); + compressed_map.data = compressed_map_data.get(); + JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); + + JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, dest)); + return NO_ERROR; } -status_t RecoveryMap::decodeJPEGR(void* compressed_jpegr_image, +status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, jr_uncompressed_ptr dest, jr_exif_ptr /* exif */, bool /* request_sdr */) { @@ -96,27 +190,67 @@ status_t RecoveryMap::decodeJPEGR(void* compressed_jpegr_image, return ERROR_JPEGR_INVALID_NULL_PTR; } - // TBD + jpegr_compressed_struct compressed_map; + JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map)); + + jpegr_uncompressed_struct map; + JPEGR_CHECK(decompressRecoveryMap(&compressed_map, &map)); + + JpegDecoder jpeg_decoder; + if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) { + return ERROR_JPEGR_DECODE_ERROR; + } + + jpegr_uncompressed_struct uncompressed_yuv_420_image; + uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr(); + uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth(); + uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight(); + + JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, dest)); + return NO_ERROR; } -status_t RecoveryMap::decodeRecoveryMap(jr_compressed_ptr compressed_recovery_map, - jr_uncompressed_ptr dest) { +status_t RecoveryMap::decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map, + jr_uncompressed_ptr dest) { if (compressed_recovery_map == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } - // TBD + JpegDecoder jpeg_decoder; + if (!jpeg_decoder.decompressImage(compressed_recovery_map->data, + compressed_recovery_map->length)) { + return ERROR_JPEGR_DECODE_ERROR; + } + + dest->data = jpeg_decoder.getDecompressedImagePtr(); + dest->width = jpeg_decoder.getDecompressedImageWidth(); + dest->height = jpeg_decoder.getDecompressedImageHeight(); + return NO_ERROR; } -status_t RecoveryMap::encodeRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, - jr_compressed_ptr dest) { +status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, + jr_compressed_ptr dest) { if (uncompressed_recovery_map == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } - // TBD + // TODO: should we have ICC data? + JpegEncoder jpeg_encoder; + if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data, uncompressed_recovery_map->width, + uncompressed_recovery_map->height, 85, nullptr, 0, + true /* isSingleChannel */)) { + return ERROR_JPEGR_ENCODE_ERROR; + } + + if (dest->length < jpeg_encoder.getCompressedImageSize()) { + return ERROR_JPEGR_BUFFER_TOO_SMALL; + } + + memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize()); + dest->length = jpeg_encoder.getCompressedImageSize(); + return NO_ERROR; } @@ -129,7 +263,51 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 return ERROR_JPEGR_INVALID_NULL_PTR; } - // TBD + if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width + || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) { + return ERROR_JPEGR_RESOLUTION_MISMATCH; + } + + size_t image_width = uncompressed_yuv_420_image->width; + size_t image_height = uncompressed_yuv_420_image->height; + size_t map_width = image_width / kMapDimensionScaleFactor; + size_t map_height = image_height / kMapDimensionScaleFactor; + + dest->width = map_width; + dest->height = map_height; + dest->data = new uint8_t[map_width * map_height]; + std::unique_ptr map_data; + map_data.reset(reinterpret_cast(dest->data)); + + uint16_t yp_hdr_max = 0; + for (size_t y = 0; y < image_height; ++y) { + for (size_t x = 0; x < image_width; ++x) { + size_t pixel_idx = x + y * image_width; + uint16_t yp_hdr = reinterpret_cast(uncompressed_yuv_420_image->data)[pixel_idx]; + if (yp_hdr > yp_hdr_max) { + yp_hdr_max = yp_hdr; + } + } + } + + float y_hdr_max_nits = hlgInvOetf(yp_hdr_max); + float hdr_ratio = y_hdr_max_nits / kSdrWhiteNits; + + for (size_t y = 0; y < map_height; ++y) { + for (size_t x = 0; x < map_width; ++x) { + float yp_sdr = sampleYuv420Y(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y); + float yp_hdr = sampleP010Y(uncompressed_p010_image, kMapDimensionScaleFactor, x, y); + + float y_sdr_nits = srgbInvOetf(yp_sdr); + float y_hdr_nits = hlgInvOetf(yp_hdr); + + size_t pixel_idx = x + y * map_width; + reinterpret_cast(dest->data)[pixel_idx] = + encodeRecovery(y_sdr_nits, y_hdr_nits, hdr_ratio); + } + } + + map_data.release(); return NO_ERROR; } @@ -142,11 +320,44 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ return ERROR_JPEGR_INVALID_NULL_PTR; } - // TBD + // TODO: need to get this from the XMP; should probably be a function + // parameter + float hdr_ratio = 4.0f; + + size_t width = uncompressed_yuv_420_image->width; + size_t height = uncompressed_yuv_420_image->height; + + dest->width = width; + dest->height = height; + size_t pixel_count = width * height; + + for (size_t y = 0; y < height; ++y) { + for (size_t x = 0; x < width; ++x) { + size_t pixel_y_idx = x + y * width; + + size_t pixel_uv_idx = x / 2 + (y / 2) * (width / 2); + + Color ypuv_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y); + Color rgbp_sdr = srgbYuvToRgb(ypuv_sdr); + Color rgb_sdr = srgbInvOetf(rgbp_sdr); + + float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y); + Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio); + + Color rgbp_hdr = hlgOetf(rgb_hdr); + Color ypuv_hdr = bt2100RgbToYuv(rgbp_hdr); + + reinterpret_cast(dest->data)[pixel_y_idx] = ypuv_hdr.r; + reinterpret_cast(dest->data)[pixel_count + pixel_uv_idx] = ypuv_hdr.g; + reinterpret_cast(dest->data)[pixel_count + pixel_uv_idx + 1] = ypuv_hdr.b; + } + } + return NO_ERROR; } -status_t RecoveryMap::extractRecoveryMap(void* compressed_jpegr_image, jr_compressed_ptr dest) { +status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image, + jr_compressed_ptr dest) { if (compressed_jpegr_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } @@ -155,9 +366,9 @@ status_t RecoveryMap::extractRecoveryMap(void* compressed_jpegr_image, jr_compre return NO_ERROR; } -status_t RecoveryMap::appendRecoveryMap(void* compressed_jpeg_image, - jr_compressed_ptr compressed_recovery_map, - void* dest) { +status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, + jr_compressed_ptr compressed_recovery_map, + jr_compressed_ptr dest) { if (compressed_jpeg_image == nullptr || compressed_recovery_map == nullptr || dest == nullptr) { diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp new file mode 100644 index 0000000000..3e110bcd26 --- /dev/null +++ b/libs/jpegrecoverymap/recoverymapmath.cpp @@ -0,0 +1,169 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +namespace android::recoverymap { + +static const float kBt2100R = 0.2627f, kBt2100G = 0.6780f, kBt2100B = 0.0593f; +static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f; + +Color bt2100RgbToYuv(Color e) { + float yp = kBt2100R * e.r + kBt2100G * e.g + kBt2100B * e.b; + return {{{yp, (e.b - yp) / kBt2100Cb, (e.r - yp) / kBt2100Cr }}}; +} + +static const float kSrgbRCr = 1.402f, kSrgbGCb = 0.34414f, kSrgbGCr = 0.71414f, kSrgbBCb = 1.772f; + +Color srgbYuvToRgb(Color e) { + return {{{ e.y + kSrgbRCr * e.v, e.y - kSrgbGCb * e.u - kSrgbGCr * e.v, e.y + kSrgbBCb * e.u }}}; +} + +float srgbInvOetf(float e) { + if (e <= 0.04045f) { + return e / 12.92f; + } else { + return pow((e + 0.055f) / 1.055f, 2.4); + } +} + +Color srgbInvOetf(Color e) { + return {{{ srgbInvOetf(e.r), srgbInvOetf(e.g), srgbInvOetf(e.b) }}}; +} + +static const float kHlgA = 0.17883277f, kHlgB = 0.28466892f, kHlgC = 0.55991073; + +float hlgInvOetf(float e) { + if (e <= 0.5f) { + return pow(e, 2.0f) / 3.0f; + } else { + return (exp((e - kHlgC) / kHlgA) + kHlgB) / 12.0f; + } +} + +float hlgOetf(float e) { + if (e <= 1.0f/12.0f) { + return sqrt(3.0f * e); + } else { + return kHlgA * log(12.0f * e - kHlgB) + kHlgC; + } +} + +Color hlgOetf(Color e) { + return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}}; +} + +uint8_t EncodeRecovery(float y_sdr, float y_hdr, float hdr_ratio) { + float gain = 1.0f; + if (y_sdr > 0.0f) { + gain = y_hdr / y_sdr; + } + + if (gain < -hdr_ratio) gain = -hdr_ratio; + if (gain > hdr_ratio) gain = hdr_ratio; + + return static_cast(log2(gain) / log2(hdr_ratio) * 127.5f + 127.5f); +} + +float applyRecovery(float y_sdr, float recovery, float hdr_ratio) { + return exp2(log2(y_sdr) + recovery * log2(hdr_ratio)); +} + +// TODO: do we need something more clever for filtering either the map or images +// to generate the map? + +static float mapUintToFloat(uint8_t map_uint) { + return (static_cast(map_uint) - 127.5f) / 127.5f; +} + +float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y) { + float x_map = static_cast(x) / static_cast(map_scale_factor); + float y_map = static_cast(y) / static_cast(map_scale_factor); + + size_t x_lower = static_cast(floor(x_map)); + size_t x_upper = x_lower + 1; + size_t y_lower = static_cast(floor(y_map)); + size_t y_upper = y_lower + 1; + + float x_influence = x_map - static_cast(x_lower); + float y_influence = y_map - static_cast(y_lower); + + float e1 = mapUintToFloat(reinterpret_cast(map->data)[x_lower + y_lower * map->width]); + float e2 = mapUintToFloat(reinterpret_cast(map->data)[x_lower + y_upper * map->width]); + float e3 = mapUintToFloat(reinterpret_cast(map->data)[x_upper + y_lower * map->width]); + float e4 = mapUintToFloat(reinterpret_cast(map->data)[x_upper + y_upper * map->width]); + + return e1 * (x_influence + y_influence) / 2.0f + + e2 * (x_influence + 1.0f - y_influence) / 2.0f + + e3 * (1.0f - x_influence + y_influence) / 2.0f + + e4 * (1.0f - x_influence + 1.0f - y_influence) / 2.0f; +} + +Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) { + size_t pixel_count = image->width * image->height; + + size_t pixel_y_idx = x + y * image->width; + size_t pixel_uv_idx = x / 2 + (y / 2) * (image->width / 2); + + uint8_t y_uint = reinterpret_cast(image->data)[pixel_y_idx]; + uint8_t u_uint = reinterpret_cast(image->data)[pixel_count + pixel_uv_idx]; + uint8_t v_uint = reinterpret_cast(image->data)[pixel_count * 5 / 4 + pixel_uv_idx]; + + // 128 bias for UV given we are using jpeglib; see: + // https://github.com/kornelski/libjpeg/blob/master/structure.doc + return {{{ static_cast(y_uint) / 255.0f, + (static_cast(u_uint) - 128.0f) / 255.0f, + (static_cast(v_uint) - 128.0f) / 255.0f }}}; +} + +typedef float (*sampleComponentFn)(jr_uncompressed_ptr, size_t, size_t); + +static float sampleComponent(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y, + sampleComponentFn sample_fn) { + float e = 0.0f; + for (size_t dy = 0; dy < map_scale_factor; ++dy) { + for (size_t dx = 0; dx < map_scale_factor; ++dx) { + e += sample_fn(image, x * map_scale_factor + dx, y * map_scale_factor + dy); + } + } + + return e / static_cast(map_scale_factor * map_scale_factor); +} + +static float getYuv420Y(jr_uncompressed_ptr image, size_t x, size_t y) { + size_t pixel_idx = x + y * image->width; + uint8_t y_uint = reinterpret_cast(image->data)[pixel_idx]; + return static_cast(y_uint) / 255.0f; +} + + +float sampleYuv420Y(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) { + return sampleComponent(image, map_scale_factor, x, y, getYuv420Y); +} + +static float getP010Y(jr_uncompressed_ptr image, size_t x, size_t y) { + size_t pixel_idx = x + y * image->width; + uint8_t y_uint = reinterpret_cast(image->data)[pixel_idx]; + // Expecting narrow range input + return (static_cast(y_uint) - 64.0f) / 960.0f; +} + +float sampleP010Y(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) { + return sampleComponent(image, map_scale_factor, x, y, getP010Y); +} +} // namespace android::recoverymap diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp index 7f37f611c7..41af991093 100644 --- a/libs/jpegrecoverymap/tests/Android.bp +++ b/libs/jpegrecoverymap/tests/Android.bp @@ -62,4 +62,4 @@ cc_test { "libjpegdecoder", "libgtest", ], -} \ No newline at end of file +} -- GitLab From 1af0000a9998d53277c2b8e5c750ded201c7070e Mon Sep 17 00:00:00 2001 From: Peiyong Lin Date: Fri, 4 Nov 2022 23:49:47 +0000 Subject: [PATCH 0464/1310] Update IPowerHintSession mock. Bug: b/244216750 Test: atest AidlPowerHalWrapperTest Change-Id: Ia65b9aeb3d2160de4bfb0685ce92acfc6b2590fa --- .../tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h | 1 + 1 file changed, 1 insertion(+) diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h index 5f749dfbcc..f4ded216cb 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h @@ -42,6 +42,7 @@ public: MOCK_METHOD(Status, updateTargetWorkDuration, (int64_t), (override)); MOCK_METHOD(Status, reportActualWorkDuration, (const ::std::vector&), (override)); MOCK_METHOD(Status, sendHint, (SessionHint), (override)); + MOCK_METHOD(Status, setThreads, (const ::std::vector&), (override)); }; } // namespace android::Hwc2::mock -- GitLab From 3c51bc5024b849c9ed5defbea058133399cdda3e Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Fri, 4 Nov 2022 01:28:55 +0000 Subject: [PATCH 0465/1310] Improve tonemapping utilities * Add util for getting buffer dataspace from metadata, since some ANativeWindow queries are unreliable when Surface endpoints are passed between processes, e.g., camera * Let libshaders generate SkSL for SkColorFilters Bug: 238395777 Test: Switching HDR cameras don't color shift Change-Id: I7c3b917eeafcf8d028f8f52f38aa1389025bc607 --- libs/nativewindow/AHardwareBuffer.cpp | 8 ++++++ .../private/android/AHardwareBufferHelpers.h | 5 ++++ libs/nativewindow/libnativewindow.map.txt | 1 + libs/shaders/include/shaders/shaders.h | 7 ++++++ libs/shaders/shaders.cpp | 25 +++++++++++++------ 5 files changed, 39 insertions(+), 7 deletions(-) diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp index 180dce9ed7..bbafbffae0 100644 --- a/libs/nativewindow/AHardwareBuffer.cpp +++ b/libs/nativewindow/AHardwareBuffer.cpp @@ -702,6 +702,14 @@ uint32_t AHardwareBuffer_convertToPixelFormat(uint32_t ahardwarebuffer_format) { return ahardwarebuffer_format; } +int32_t AHardwareBuffer_getDataSpace(AHardwareBuffer* buffer) { + GraphicBuffer* gb = AHardwareBuffer_to_GraphicBuffer(buffer); + auto& mapper = GraphicBufferMapper::get(); + ui::Dataspace dataspace = ui::Dataspace::UNKNOWN; + mapper.getDataspace(gb->handle, &dataspace); + return static_cast(dataspace); +} + uint64_t AHardwareBuffer_convertToGrallocUsageBits(uint64_t usage) { using android::hardware::graphics::common::V1_1::BufferUsage; static_assert(AHARDWAREBUFFER_USAGE_CPU_READ_NEVER == (uint64_t)BufferUsage::CPU_READ_NEVER, diff --git a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h index ddfd1d1918..6d3d295a0c 100644 --- a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h +++ b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h @@ -52,6 +52,11 @@ uint32_t AHardwareBuffer_convertFromPixelFormat(uint32_t format); // convert HAL format to AHardwareBuffer format (note: this is a no-op) uint32_t AHardwareBuffer_convertToPixelFormat(uint32_t format); +// retrieves a dataspace from the AHardwareBuffer metadata, if the device +// support gralloc metadata. Returns UNKNOWN if gralloc metadata is not +// supported. +int32_t AHardwareBuffer_getDataSpace(AHardwareBuffer* buffer); + // convert AHardwareBuffer usage bits to HAL usage bits (note: this is a no-op) uint64_t AHardwareBuffer_convertFromGrallocUsageBits(uint64_t usage); diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt index 76d23fab1d..e1ee490dc4 100644 --- a/libs/nativewindow/libnativewindow.map.txt +++ b/libs/nativewindow/libnativewindow.map.txt @@ -71,6 +71,7 @@ LIBNATIVEWINDOW_PLATFORM { android::AHardwareBuffer_convertToPixelFormat*; android::AHardwareBuffer_convertFromGrallocUsageBits*; android::AHardwareBuffer_convertToGrallocUsageBits*; + android::AHardwareBuffer_getDataSpace*; android::AHardwareBuffer_to_GraphicBuffer*; android::AHardwareBuffer_to_ANativeWindowBuffer*; android::AHardwareBuffer_from_GraphicBuffer*; diff --git a/libs/shaders/include/shaders/shaders.h b/libs/shaders/include/shaders/shaders.h index 2a4a370078..42b0cc131c 100644 --- a/libs/shaders/include/shaders/shaders.h +++ b/libs/shaders/include/shaders/shaders.h @@ -68,6 +68,9 @@ struct LinearEffect { // fakeInputDataspace is used to essentially masquerade the input dataspace to be the output // dataspace for correct conversion to linear colors. ui::Dataspace fakeInputDataspace = ui::Dataspace::UNKNOWN; + + enum SkSLType { Shader, ColorFilter }; + SkSLType type = Shader; }; static inline bool operator==(const LinearEffect& lhs, const LinearEffect& rhs) { @@ -96,6 +99,10 @@ struct LinearEffectHasher { // 2. Apply color transform matrices in linear space std::string buildLinearEffectSkSL(const LinearEffect& linearEffect); +// Generates a shader string that applies color transforms in linear space. +// This is intended to be plugged into an SkColorFilter +std::string buildLinearEffectSkSLForColorFilter(const LinearEffect& linearEffect); + // Generates a list of uniforms to set on the LinearEffect shader above. std::vector buildLinearEffectUniforms( const LinearEffect& linearEffect, const mat4& colorTransform, float maxDisplayLuminance, diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp index f80e93f6f8..a3c403e551 100644 --- a/libs/shaders/shaders.cpp +++ b/libs/shaders/shaders.cpp @@ -386,12 +386,23 @@ void generateOETF(ui::Dataspace dataspace, std::string& shader) { } } -void generateEffectiveOOTF(bool undoPremultipliedAlpha, std::string& shader) { - shader.append(R"( - uniform shader child; - half4 main(float2 xy) { - float4 c = float4(child.eval(xy)); - )"); +void generateEffectiveOOTF(bool undoPremultipliedAlpha, LinearEffect::SkSLType type, + std::string& shader) { + switch (type) { + case LinearEffect::SkSLType::ColorFilter: + shader.append(R"( + half4 main(half4 inputColor) { + float4 c = float4(inputColor); + )"); + break; + case LinearEffect::SkSLType::Shader: + shader.append(R"( + uniform shader child; + half4 main(float2 xy) { + float4 c = float4(child.eval(xy)); + )"); + break; + } if (undoPremultipliedAlpha) { shader.append(R"( c.rgb = c.rgb / (c.a + 0.0019); @@ -459,7 +470,7 @@ std::string buildLinearEffectSkSL(const LinearEffect& linearEffect) { generateXYZTransforms(shaderString); generateOOTF(linearEffect.inputDataspace, linearEffect.outputDataspace, shaderString); generateOETF(linearEffect.outputDataspace, shaderString); - generateEffectiveOOTF(linearEffect.undoPremultipliedAlpha, shaderString); + generateEffectiveOOTF(linearEffect.undoPremultipliedAlpha, linearEffect.type, shaderString); return shaderString; } -- GitLab From 2d77f5589688e6a71e71557e323bb7068aa53e25 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Mon, 7 Nov 2022 12:10:56 +0000 Subject: [PATCH 0466/1310] Put MultiTouchMotionAccumulator in its own files This will let us use it in the new TouchpadInputMapper. The change mostly just moves the code into new files, but also makes a few other small cleanups: * Directly includes stuff the code depends on, rather than indirectly through EventHub.h * Renames private variables of Slot to capitalize acronyms consistently and in line with Google C++ style (i.e. any case of "AbsMT" replaced with "AbsMt") * Rewraps a comment in ::configure * Uses ALOGW_IF to avoid another nested if block in ::process * Put all the initializers on one line in the constructor, to satisfy clang-format The inline was also removed from Slot::getToolType, due to compile errors complaining that it wasn't defined otherwise. Bug: 251196347 Test: atest inputflinger_tests Test: manual on device Change-Id: I538178a2222904c5bab5622d257d7747842358a7 --- services/inputflinger/reader/Android.bp | 1 + .../reader/mapper/MultiTouchInputMapper.cpp | 157 ---------------- .../reader/mapper/MultiTouchInputMapper.h | 69 +------ .../MultiTouchMotionAccumulator.cpp | 176 ++++++++++++++++++ .../accumulator/MultiTouchMotionAccumulator.h | 96 ++++++++++ 5 files changed, 274 insertions(+), 225 deletions(-) create mode 100644 services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp create mode 100644 services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index a53fcd763d..24168a12a6 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -55,6 +55,7 @@ filegroup { "mapper/accumulator/CursorButtonAccumulator.cpp", "mapper/accumulator/CursorScrollAccumulator.cpp", "mapper/accumulator/HidUsageAccumulator.cpp", + "mapper/accumulator/MultiTouchMotionAccumulator.cpp", "mapper/accumulator/SingleTouchMotionAccumulator.cpp", "mapper/accumulator/TouchButtonAccumulator.cpp", ], diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp index acba4f6513..8e757a5bf7 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp @@ -28,163 +28,6 @@ namespace android { // Maximum number of slots supported when using the slot-based Multitouch Protocol B. static constexpr size_t MAX_SLOTS = 32; -// --- MultiTouchMotionAccumulator --- - -MultiTouchMotionAccumulator::MultiTouchMotionAccumulator() - : mCurrentSlot(-1), - mUsingSlotsProtocol(false), - mHaveStylus(false) {} - -void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, size_t slotCount, - bool usingSlotsProtocol) { - mUsingSlotsProtocol = usingSlotsProtocol; - mHaveStylus = deviceContext.hasAbsoluteAxis(ABS_MT_TOOL_TYPE); - mSlots = std::vector(slotCount); - - mCurrentSlot = -1; - if (mUsingSlotsProtocol) { - // Query the driver for the current slot index and use it as the initial slot - // before we start reading events from the device. It is possible that the - // current slot index will not be the same as it was when the first event was - // written into the evdev buffer, which means the input mapper could start - // out of sync with the initial state of the events in the evdev buffer. - // In the extremely unlikely case that this happens, the data from - // two slots will be confused until the next ABS_MT_SLOT event is received. - // This can cause the touch point to "jump", but at least there will be - // no stuck touches. - int32_t initialSlot; - if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); - status == OK) { - mCurrentSlot = initialSlot; - } else { - ALOGD("Could not retrieve current multi-touch slot index. status=%d", status); - } - } -} - -void MultiTouchMotionAccumulator::resetSlots() { - for (Slot& slot : mSlots) { - slot.clear(); - } - mCurrentSlot = -1; -} - -void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { - if (rawEvent->type == EV_ABS) { - bool newSlot = false; - if (mUsingSlotsProtocol) { - if (rawEvent->code == ABS_MT_SLOT) { - mCurrentSlot = rawEvent->value; - newSlot = true; - } - } else if (mCurrentSlot < 0) { - mCurrentSlot = 0; - } - - if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlots.size()) { - if (DEBUG_POINTERS) { - if (newSlot) { - ALOGW("MultiTouch device emitted invalid slot index %d but it " - "should be between 0 and %zd; ignoring this slot.", - mCurrentSlot, mSlots.size() - 1); - } - } - } else { - Slot& slot = mSlots[mCurrentSlot]; - // If mUsingSlotsProtocol is true, it means the raw pointer has axis info of - // ABS_MT_TRACKING_ID and ABS_MT_SLOT, so driver should send a valid trackingId while - // updating the slot. - if (!mUsingSlotsProtocol) { - slot.mInUse = true; - } - - switch (rawEvent->code) { - case ABS_MT_POSITION_X: - slot.mAbsMTPositionX = rawEvent->value; - warnIfNotInUse(*rawEvent, slot); - break; - case ABS_MT_POSITION_Y: - slot.mAbsMTPositionY = rawEvent->value; - warnIfNotInUse(*rawEvent, slot); - break; - case ABS_MT_TOUCH_MAJOR: - slot.mAbsMTTouchMajor = rawEvent->value; - break; - case ABS_MT_TOUCH_MINOR: - slot.mAbsMTTouchMinor = rawEvent->value; - slot.mHaveAbsMTTouchMinor = true; - break; - case ABS_MT_WIDTH_MAJOR: - slot.mAbsMTWidthMajor = rawEvent->value; - break; - case ABS_MT_WIDTH_MINOR: - slot.mAbsMTWidthMinor = rawEvent->value; - slot.mHaveAbsMTWidthMinor = true; - break; - case ABS_MT_ORIENTATION: - slot.mAbsMTOrientation = rawEvent->value; - break; - case ABS_MT_TRACKING_ID: - if (mUsingSlotsProtocol && rawEvent->value < 0) { - // The slot is no longer in use but it retains its previous contents, - // which may be reused for subsequent touches. - slot.mInUse = false; - } else { - slot.mInUse = true; - slot.mAbsMTTrackingId = rawEvent->value; - } - break; - case ABS_MT_PRESSURE: - slot.mAbsMTPressure = rawEvent->value; - break; - case ABS_MT_DISTANCE: - slot.mAbsMTDistance = rawEvent->value; - break; - case ABS_MT_TOOL_TYPE: - slot.mAbsMTToolType = rawEvent->value; - slot.mHaveAbsMTToolType = true; - break; - } - } - } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) { - // MultiTouch Sync: The driver has returned all data for *one* of the pointers. - mCurrentSlot += 1; - } -} - -void MultiTouchMotionAccumulator::finishSync() { - if (!mUsingSlotsProtocol) { - resetSlots(); - } -} - -bool MultiTouchMotionAccumulator::hasStylus() const { - return mHaveStylus; -} - -void MultiTouchMotionAccumulator::warnIfNotInUse(const RawEvent& event, const Slot& slot) { - if (!slot.mInUse) { - ALOGW("Received unexpected event (0x%0x, 0x%0x) for slot %i with tracking id %i", - event.code, event.value, mCurrentSlot, slot.mAbsMTTrackingId); - } -} - -// --- MultiTouchMotionAccumulator::Slot --- - -int32_t MultiTouchMotionAccumulator::Slot::getToolType() const { - if (mHaveAbsMTToolType) { - switch (mAbsMTToolType) { - case MT_TOOL_FINGER: - return AMOTION_EVENT_TOOL_TYPE_FINGER; - case MT_TOOL_PEN: - return AMOTION_EVENT_TOOL_TYPE_STYLUS; - case MT_TOOL_PALM: - return AMOTION_EVENT_TOOL_TYPE_PALM; - } - } - return AMOTION_EVENT_TOOL_TYPE_UNKNOWN; -} - // --- MultiTouchInputMapper --- MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext) diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h index 047e62de62..ddf9e80a6c 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h @@ -17,77 +17,10 @@ #pragma once #include "TouchInputMapper.h" +#include "accumulator/MultiTouchMotionAccumulator.h" namespace android { -/* Keeps track of the state of multi-touch protocol. */ -class MultiTouchMotionAccumulator { -public: - class Slot { - public: - inline bool isInUse() const { return mInUse; } - inline int32_t getX() const { return mAbsMTPositionX; } - inline int32_t getY() const { return mAbsMTPositionY; } - inline int32_t getTouchMajor() const { return mAbsMTTouchMajor; } - inline int32_t getTouchMinor() const { - return mHaveAbsMTTouchMinor ? mAbsMTTouchMinor : mAbsMTTouchMajor; - } - inline int32_t getToolMajor() const { return mAbsMTWidthMajor; } - inline int32_t getToolMinor() const { - return mHaveAbsMTWidthMinor ? mAbsMTWidthMinor : mAbsMTWidthMajor; - } - inline int32_t getOrientation() const { return mAbsMTOrientation; } - inline int32_t getTrackingId() const { return mAbsMTTrackingId; } - inline int32_t getPressure() const { return mAbsMTPressure; } - inline int32_t getDistance() const { return mAbsMTDistance; } - inline int32_t getToolType() const; - - private: - friend class MultiTouchMotionAccumulator; - - bool mInUse = false; - bool mHaveAbsMTTouchMinor = false; - bool mHaveAbsMTWidthMinor = false; - bool mHaveAbsMTToolType = false; - - int32_t mAbsMTPositionX = 0; - int32_t mAbsMTPositionY = 0; - int32_t mAbsMTTouchMajor = 0; - int32_t mAbsMTTouchMinor = 0; - int32_t mAbsMTWidthMajor = 0; - int32_t mAbsMTWidthMinor = 0; - int32_t mAbsMTOrientation = 0; - int32_t mAbsMTTrackingId = -1; - int32_t mAbsMTPressure = 0; - int32_t mAbsMTDistance = 0; - int32_t mAbsMTToolType = 0; - - void clear() { *this = Slot(); } - }; - - MultiTouchMotionAccumulator(); - - void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol); - void process(const RawEvent* rawEvent); - void finishSync(); - bool hasStylus() const; - - inline size_t getSlotCount() const { return mSlots.size(); } - inline const Slot& getSlot(size_t index) const { - LOG_ALWAYS_FATAL_IF(index < 0 || index >= mSlots.size(), "Invalid index: %zu", index); - return mSlots[index]; - } - -private: - int32_t mCurrentSlot; - std::vector mSlots; - bool mUsingSlotsProtocol; - bool mHaveStylus; - - void resetSlots(); - void warnIfNotInUse(const RawEvent& event, const Slot& slot); -}; - class MultiTouchInputMapper : public TouchInputMapper { public: explicit MultiTouchInputMapper(InputDeviceContext& deviceContext); diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp new file mode 100644 index 0000000000..b0cef676fb --- /dev/null +++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// clang-format off +#include "../Macros.h" +// clang-format on +#include "MultiTouchMotionAccumulator.h" + +namespace android { + +// --- MultiTouchMotionAccumulator --- + +MultiTouchMotionAccumulator::MultiTouchMotionAccumulator() + : mCurrentSlot(-1), mUsingSlotsProtocol(false), mHaveStylus(false) {} + +void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, size_t slotCount, + bool usingSlotsProtocol) { + mUsingSlotsProtocol = usingSlotsProtocol; + mHaveStylus = deviceContext.hasAbsoluteAxis(ABS_MT_TOOL_TYPE); + mSlots = std::vector(slotCount); + + mCurrentSlot = -1; + if (mUsingSlotsProtocol) { + // Query the driver for the current slot index and use it as the initial slot before we + // start reading events from the device. It is possible that the current slot index will + // not be the same as it was when the first event was written into the evdev buffer, which + // means the input mapper could start out of sync with the initial state of the events in + // the evdev buffer. In the extremely unlikely case that this happens, the data from two + // slots will be confused until the next ABS_MT_SLOT event is received. This can cause the + // touch point to "jump", but at least there will be no stuck touches. + int32_t initialSlot; + if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); + status == OK) { + mCurrentSlot = initialSlot; + } else { + ALOGD("Could not retrieve current multi-touch slot index. status=%d", status); + } + } +} + +void MultiTouchMotionAccumulator::resetSlots() { + for (Slot& slot : mSlots) { + slot.clear(); + } + mCurrentSlot = -1; +} + +void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { + if (rawEvent->type == EV_ABS) { + bool newSlot = false; + if (mUsingSlotsProtocol) { + if (rawEvent->code == ABS_MT_SLOT) { + mCurrentSlot = rawEvent->value; + newSlot = true; + } + } else if (mCurrentSlot < 0) { + mCurrentSlot = 0; + } + + if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlots.size()) { + if (newSlot) { + ALOGW_IF(DEBUG_POINTERS, + "MultiTouch device emitted invalid slot index %d but it " + "should be between 0 and %zd; ignoring this slot.", + mCurrentSlot, mSlots.size() - 1); + } + } else { + Slot& slot = mSlots[mCurrentSlot]; + // If mUsingSlotsProtocol is true, it means the raw pointer has axis info of + // ABS_MT_TRACKING_ID and ABS_MT_SLOT, so driver should send a valid trackingId while + // updating the slot. + if (!mUsingSlotsProtocol) { + slot.mInUse = true; + } + + switch (rawEvent->code) { + case ABS_MT_POSITION_X: + slot.mAbsMtPositionX = rawEvent->value; + warnIfNotInUse(*rawEvent, slot); + break; + case ABS_MT_POSITION_Y: + slot.mAbsMtPositionY = rawEvent->value; + warnIfNotInUse(*rawEvent, slot); + break; + case ABS_MT_TOUCH_MAJOR: + slot.mAbsMtTouchMajor = rawEvent->value; + break; + case ABS_MT_TOUCH_MINOR: + slot.mAbsMtTouchMinor = rawEvent->value; + slot.mHaveAbsMtTouchMinor = true; + break; + case ABS_MT_WIDTH_MAJOR: + slot.mAbsMtWidthMajor = rawEvent->value; + break; + case ABS_MT_WIDTH_MINOR: + slot.mAbsMtWidthMinor = rawEvent->value; + slot.mHaveAbsMtWidthMinor = true; + break; + case ABS_MT_ORIENTATION: + slot.mAbsMtOrientation = rawEvent->value; + break; + case ABS_MT_TRACKING_ID: + if (mUsingSlotsProtocol && rawEvent->value < 0) { + // The slot is no longer in use but it retains its previous contents, + // which may be reused for subsequent touches. + slot.mInUse = false; + } else { + slot.mInUse = true; + slot.mAbsMtTrackingId = rawEvent->value; + } + break; + case ABS_MT_PRESSURE: + slot.mAbsMtPressure = rawEvent->value; + break; + case ABS_MT_DISTANCE: + slot.mAbsMtDistance = rawEvent->value; + break; + case ABS_MT_TOOL_TYPE: + slot.mAbsMtToolType = rawEvent->value; + slot.mHaveAbsMtToolType = true; + break; + } + } + } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) { + // MultiTouch Sync: The driver has returned all data for *one* of the pointers. + mCurrentSlot += 1; + } +} + +void MultiTouchMotionAccumulator::finishSync() { + if (!mUsingSlotsProtocol) { + resetSlots(); + } +} + +bool MultiTouchMotionAccumulator::hasStylus() const { + return mHaveStylus; +} + +void MultiTouchMotionAccumulator::warnIfNotInUse(const RawEvent& event, const Slot& slot) { + if (!slot.mInUse) { + ALOGW("Received unexpected event (0x%0x, 0x%0x) for slot %i with tracking id %i", + event.code, event.value, mCurrentSlot, slot.mAbsMtTrackingId); + } +} + +// --- MultiTouchMotionAccumulator::Slot --- + +int32_t MultiTouchMotionAccumulator::Slot::getToolType() const { + if (mHaveAbsMtToolType) { + switch (mAbsMtToolType) { + case MT_TOOL_FINGER: + return AMOTION_EVENT_TOOL_TYPE_FINGER; + case MT_TOOL_PEN: + return AMOTION_EVENT_TOOL_TYPE_STYLUS; + case MT_TOOL_PALM: + return AMOTION_EVENT_TOOL_TYPE_PALM; + } + } + return AMOTION_EVENT_TOOL_TYPE_UNKNOWN; +} + +} // namespace android diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h new file mode 100644 index 0000000000..625a00fdd4 --- /dev/null +++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include "EventHub.h" +#include "InputDevice.h" + +namespace android { + +/* Keeps track of the state of multi-touch protocol. */ +class MultiTouchMotionAccumulator { +public: + class Slot { + public: + inline bool isInUse() const { return mInUse; } + inline int32_t getX() const { return mAbsMtPositionX; } + inline int32_t getY() const { return mAbsMtPositionY; } + inline int32_t getTouchMajor() const { return mAbsMtTouchMajor; } + inline int32_t getTouchMinor() const { + return mHaveAbsMtTouchMinor ? mAbsMtTouchMinor : mAbsMtTouchMajor; + } + inline int32_t getToolMajor() const { return mAbsMtWidthMajor; } + inline int32_t getToolMinor() const { + return mHaveAbsMtWidthMinor ? mAbsMtWidthMinor : mAbsMtWidthMajor; + } + inline int32_t getOrientation() const { return mAbsMtOrientation; } + inline int32_t getTrackingId() const { return mAbsMtTrackingId; } + inline int32_t getPressure() const { return mAbsMtPressure; } + inline int32_t getDistance() const { return mAbsMtDistance; } + int32_t getToolType() const; + + private: + friend class MultiTouchMotionAccumulator; + + bool mInUse = false; + bool mHaveAbsMtTouchMinor = false; + bool mHaveAbsMtWidthMinor = false; + bool mHaveAbsMtToolType = false; + + int32_t mAbsMtPositionX = 0; + int32_t mAbsMtPositionY = 0; + int32_t mAbsMtTouchMajor = 0; + int32_t mAbsMtTouchMinor = 0; + int32_t mAbsMtWidthMajor = 0; + int32_t mAbsMtWidthMinor = 0; + int32_t mAbsMtOrientation = 0; + int32_t mAbsMtTrackingId = -1; + int32_t mAbsMtPressure = 0; + int32_t mAbsMtDistance = 0; + int32_t mAbsMtToolType = 0; + + void clear() { *this = Slot(); } + }; + + MultiTouchMotionAccumulator(); + + void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol); + void process(const RawEvent* rawEvent); + void finishSync(); + bool hasStylus() const; + + inline size_t getSlotCount() const { return mSlots.size(); } + inline const Slot& getSlot(size_t index) const { + LOG_ALWAYS_FATAL_IF(index < 0 || index >= mSlots.size(), "Invalid index: %zu", index); + return mSlots[index]; + } + +private: + int32_t mCurrentSlot; + std::vector mSlots; + bool mUsingSlotsProtocol; + bool mHaveStylus; + + void resetSlots(); + void warnIfNotInUse(const RawEvent& event, const Slot& slot); +}; + +} // namespace android -- GitLab From 95cbb9fb54bfd588c73e8fb68e724cdeb285405f Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Mon, 7 Nov 2022 18:32:05 +0000 Subject: [PATCH 0467/1310] libjpegrecoverymap: refactor encode APIs 1. remove hdr_ratio as an input parameter 2. pass quality into encoder Bug: b/252835416 Test: build Change-Id: If877e153f786e9b7daeacb7a65ab1296455faf67 --- .../include/jpegrecoverymap/recoverymap.h | 15 +++------------ libs/jpegrecoverymap/recoverymap.cpp | 12 ++++-------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h index 94b7543e14..8e36250e60 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -82,16 +82,13 @@ public: * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is * the highest quality * @param exif pointer to the exif metadata. - * @param hdr_ratio HDR ratio. If not configured, this value will be calculated by the JPEG/R - * encoder. * @return NO_ERROR if encoding succeeds, error code if error occurs. */ status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, jr_compressed_ptr dest, int quality, - jr_exif_ptr exif, - float hdr_ratio = 0.0f); + jr_exif_ptr exif); /* * Compress JPEGR image from 10-bit HDR YUV, 8-bit SDR YUV and compressed 8-bit JPEG. @@ -104,15 +101,12 @@ public: * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format * @param compressed_jpeg_image compressed 8-bit JPEG image * @param dest destination of the compressed JPEGR image - * @param hdr_ratio HDR ratio. If not configured, this value will be calculated by the JPEG/R - * encoder. * @return NO_ERROR if encoding succeeds, error code if error occurs. */ status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, jr_compressed_ptr compressed_jpeg_image, - jr_compressed_ptr dest, - float hdr_ratio = 0.0f); + jr_compressed_ptr dest); /* * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. @@ -125,14 +119,11 @@ public: * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param compressed_jpeg_image compressed 8-bit JPEG image * @param dest destination of the compressed JPEGR image - * @param hdr_ratio HDR ratio. If not configured, this value will be calculated by the JPEG/R - * encoder. * @return NO_ERROR if encoding succeeds, error code if error occurs. */ status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_compressed_ptr compressed_jpeg_image, - jr_compressed_ptr dest, - float hdr_ratio = 0.0f); + jr_compressed_ptr dest); /* * Decompress JPEGR image. diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index 4a90053b12..d46025c441 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -64,8 +64,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, jr_compressed_ptr dest, int quality, - jr_exif_ptr /* exif */, - float /* hdr_ratio */) { + jr_exif_ptr /* exif */) { if (uncompressed_p010_image == nullptr || uncompressed_yuv_420_image == nullptr || dest == nullptr) { @@ -93,11 +92,10 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); JpegEncoder jpeg_encoder; - // TODO: what quality to use? // TODO: ICC data - need color space information if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data, uncompressed_yuv_420_image->width, - uncompressed_yuv_420_image->height, 95, nullptr, 0)) { + uncompressed_yuv_420_image->height, quality, nullptr, 0)) { return ERROR_JPEGR_ENCODE_ERROR; } jpegr_compressed_struct jpeg; @@ -112,8 +110,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, jr_compressed_ptr compressed_jpeg_image, - jr_compressed_ptr dest, - float /* hdr_ratio */) { + jr_compressed_ptr dest) { if (uncompressed_p010_image == nullptr || uncompressed_yuv_420_image == nullptr || compressed_jpeg_image == nullptr @@ -144,8 +141,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_compressed_ptr compressed_jpeg_image, - jr_compressed_ptr dest, - float /* hdr_ratio */) { + jr_compressed_ptr dest) { if (uncompressed_p010_image == nullptr || compressed_jpeg_image == nullptr || dest == nullptr) { -- GitLab From a876626e7f1f622cae6f9ac31be378a683401839 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Mon, 7 Nov 2022 23:48:24 +0000 Subject: [PATCH 0468/1310] libjpegrecoverymap: implement appendRecoveryMap() method Test: build, cherry-pick ag/20409161 and check the output file. Bug: b/252835416 Change-Id: I5e10d37b94665a8a91ecdbb51d2c98856e63395c --- .../include/jpegrecoverymap/recoverymap.h | 6 +- libs/jpegrecoverymap/recoverymap.cpp | 79 ++++++++++++++++--- 2 files changed, 74 insertions(+), 11 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h index 94b7543e14..941b0eace2 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -185,11 +185,13 @@ private: * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param dest recovery map; caller responsible for memory of data + * @param hdr_ratio HDR ratio will be updated in this method * @return NO_ERROR if calculation succeeds, error code if error occurs. */ status_t generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, jr_uncompressed_ptr uncompressed_p010_image, - jr_uncompressed_ptr dest); + jr_uncompressed_ptr dest, + float &hdr_ratio); /* * This method is called in the decoding pipeline. It will take the uncompressed (decoded) @@ -222,11 +224,13 @@ private: * * @param compressed_jpeg_image compressed 8-bit JPEG image * @param compress_recovery_map compressed recover map + * @param hdr_ratio HDR ratio * @param dest compressed JPEGR image * @return NO_ERROR if calculation succeeds, error code if error occurs. */ status_t appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, jr_compressed_ptr compressed_recovery_map, + float hdr_ratio, jr_compressed_ptr dest); /* diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index 4a90053b12..b79db1a04b 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -20,11 +20,11 @@ // TODO: handle PQ encode/decode (currently only HLG) #include - #include #include #include +#include #include #include @@ -60,6 +60,25 @@ string Name(const string &prefix, const string &suffix) { return ss.str(); } +/* + * Helper function used for writing data to destination. + * + * @param destination destination of the data to be written. + * @param source source of data being written. + * @param length length of the data to be written. + * @param position cursor in desitination where the data is to be written. + * @return status of succeed or error code. + */ +status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) { + if (position + length > destination->length) { + return ERROR_JPEGR_BUFFER_TOO_SMALL; + } + + memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length); + position += length; + return NO_ERROR; +} + status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, jr_compressed_ptr dest, @@ -82,7 +101,9 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, } jpegr_uncompressed_struct map; - JPEGR_CHECK(generateRecoveryMap(uncompressed_yuv_420_image, uncompressed_p010_image, &map)); + float hdr_ratio = 0.0f; + JPEGR_CHECK(generateRecoveryMap( + uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio)); std::unique_ptr map_data; map_data.reset(reinterpret_cast(map.data)); @@ -104,7 +125,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpeg.data = jpeg_encoder.getCompressedImagePtr(); jpeg.length = jpeg_encoder.getCompressedImageSize(); - JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, dest)); + JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, hdr_ratio, dest)); return NO_ERROR; } @@ -127,7 +148,9 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, } jpegr_uncompressed_struct map; - JPEGR_CHECK(generateRecoveryMap(uncompressed_yuv_420_image, uncompressed_p010_image, &map)); + float hdr_ratio = 0.0f; + JPEGR_CHECK(generateRecoveryMap( + uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio)); std::unique_ptr map_data; map_data.reset(reinterpret_cast(map.data)); @@ -137,7 +160,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, compressed_map.data = compressed_map_data.get(); JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); - JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, dest)); + JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, hdr_ratio, dest)); return NO_ERROR; } @@ -167,7 +190,9 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, } jpegr_uncompressed_struct map; - JPEGR_CHECK(generateRecoveryMap(&uncompressed_yuv_420_image, uncompressed_p010_image, &map)); + float hdr_ratio = 0.0f; + JPEGR_CHECK(generateRecoveryMap( + &uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio)); std::unique_ptr map_data; map_data.reset(reinterpret_cast(map.data)); @@ -177,7 +202,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, compressed_map.data = compressed_map_data.get(); JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); - JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, dest)); + JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, hdr_ratio, dest)); return NO_ERROR; } @@ -256,7 +281,8 @@ status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recov status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, jr_uncompressed_ptr uncompressed_p010_image, - jr_uncompressed_ptr dest) { + jr_uncompressed_ptr dest, + float &hdr_ratio) { if (uncompressed_yuv_420_image == nullptr || uncompressed_p010_image == nullptr || dest == nullptr) { @@ -291,7 +317,7 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 } float y_hdr_max_nits = hlgInvOetf(yp_hdr_max); - float hdr_ratio = y_hdr_max_nits / kSdrWhiteNits; + hdr_ratio = y_hdr_max_nits / kSdrWhiteNits; for (size_t y = 0; y < map_height; ++y) { for (size_t x = 0; x < map_width; ++x) { @@ -368,6 +394,7 @@ status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_imag status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, jr_compressed_ptr compressed_recovery_map, + float hdr_ratio, jr_compressed_ptr dest) { if (compressed_jpeg_image == nullptr || compressed_recovery_map == nullptr @@ -375,7 +402,39 @@ status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, return ERROR_JPEGR_INVALID_NULL_PTR; } - // TBD + string xmp = generateXmp(compressed_recovery_map->length, hdr_ratio); + string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; + + // 2 bytes: APP1 sign (ff e1) + // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0" + // x bytes: length of xmp packet + int length = 2 + nameSpace.size() + xmp.size(); + uint8_t lengthH = ((length >> 8) & 0xff); + uint8_t lengthL = (length & 0xff); + + int pos = 0; + + // JPEG/R structure: + // SOI (ff d8) + // APP1 (ff e1) + // 2 bytes of length (2 + 29 + length of xmp packet) + // name space ("http://ns.adobe.com/xap/1.0/\0") + // xmp + // primary image (without the first two bytes, the SOI sign) + // secondary image (the recovery map) + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos)); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos)); + JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); + JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); + JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpace.size(), pos)); + JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos)); + JPEGR_CHECK(Write(dest, + (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos)); + JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos)); + dest->length = pos; + return NO_ERROR; } -- GitLab From 40b8fbd17b1a483e2e51c7832ff9d01d36b519d7 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 4 Nov 2022 10:50:26 -0700 Subject: [PATCH 0469/1310] Remove TouchState::displayId TouchStates are already per-display, so we don't need to store the display id inside them. To check if touch state is "valid", we can just check if a new one was created. A new touch state was created if the old one is null. The inverse of that can be used to detect if "switchedDevice" should be true. Bug: 211379801 Test: m inputflinger_tests && adb sync data && adb shell -t /data/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: I57fad4d6c28409b89fb5c6c13fc37b930e4454da --- .../dispatcher/InputDispatcher.cpp | 29 ++++++++----------- .../inputflinger/dispatcher/InputDispatcher.h | 4 +-- services/inputflinger/dispatcher/TouchState.h | 2 -- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index a47f40ccbb..f6982173af 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2092,9 +2092,8 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } bool isSplit = shouldSplitTouch(tempTouchState, entry); - const bool switchedDevice = tempTouchState.deviceId >= 0 && tempTouchState.displayId >= 0 && - (tempTouchState.deviceId != entry.deviceId || tempTouchState.source != entry.source || - tempTouchState.displayId != displayId); + const bool switchedDevice = (oldState != nullptr) && + (tempTouchState.deviceId != entry.deviceId || tempTouchState.source != entry.source); const bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE || maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER || @@ -2115,7 +2114,6 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( tempTouchState.reset(); tempTouchState.deviceId = entry.deviceId; tempTouchState.source = entry.source; - tempTouchState.displayId = displayId; isSplit = false; } else if (switchedDevice && maskedAction == AMOTION_EVENT_ACTION_MOVE) { ALOGI("Dropping move event because a pointer for a different device is already active " @@ -2456,7 +2454,6 @@ Failed: maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) { tempTouchState.deviceId = entry.deviceId; tempTouchState.source = entry.source; - tempTouchState.displayId = displayId; } } else if (maskedAction == AMOTION_EVENT_ACTION_UP || maskedAction == AMOTION_EVENT_ACTION_CANCEL) { @@ -2500,7 +2497,7 @@ Failed: // Save changes unless the action was scroll in which case the temporary touch // state was only valid for this one action. if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) { - if (tempTouchState.displayId >= 0) { + if (displayId >= 0) { mTouchStatesByDisplay[displayId] = tempTouchState; } else { mTouchStatesByDisplay.erase(displayId); @@ -5103,16 +5100,16 @@ void InputDispatcher::setMaximumObscuringOpacityForTouch(float opacity) { mMaximumObscuringOpacityForTouch = opacity; } -std::pair InputDispatcher::findTouchStateAndWindowLocked( - const sp& token) { +std::tuple +InputDispatcher::findTouchStateWindowAndDisplayLocked(const sp& token) { for (auto& [displayId, state] : mTouchStatesByDisplay) { for (TouchedWindow& w : state.windows) { if (w.windowHandle->getToken() == token) { - return std::make_pair(&state, &w); + return std::make_tuple(&state, &w, displayId); } } } - return std::make_pair(nullptr, nullptr); + return std::make_tuple(nullptr, nullptr, ADISPLAY_ID_DEFAULT); } bool InputDispatcher::transferTouchFocus(const sp& fromToken, const sp& toToken, @@ -5128,13 +5125,12 @@ bool InputDispatcher::transferTouchFocus(const sp& fromToken, const sp< std::scoped_lock _l(mLock); // Find the target touch state and touched window by fromToken. - auto [state, touchedWindow] = findTouchStateAndWindowLocked(fromToken); + auto [state, touchedWindow, displayId] = findTouchStateWindowAndDisplayLocked(fromToken); if (state == nullptr || touchedWindow == nullptr) { ALOGD("Focus transfer failed because from window is not being touched."); return false; } - const int32_t displayId = state->displayId; sp toWindowHandle = getWindowHandleLocked(toToken, displayId); if (toWindowHandle == nullptr) { ALOGW("Cannot transfer focus because to window not found."); @@ -5323,9 +5319,8 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) { if (!mTouchStatesByDisplay.empty()) { dump += StringPrintf(INDENT "TouchStatesByDisplay:\n"); - for (const std::pair& pair : mTouchStatesByDisplay) { - const TouchState& state = pair.second; - dump += StringPrintf(INDENT2 "%d: deviceId=%d, source=0x%08x\n", state.displayId, + for (const auto& [displayId, state] : mTouchStatesByDisplay) { + dump += StringPrintf(INDENT2 "%d: deviceId=%d, source=0x%08x\n", displayId, state.deviceId, state.source); if (!state.windows.empty()) { dump += INDENT3 "Windows:\n"; @@ -5685,7 +5680,7 @@ status_t InputDispatcher::pilferPointersLocked(const sp& token) { return BAD_VALUE; } - auto [statePtr, windowPtr] = findTouchStateAndWindowLocked(token); + auto [statePtr, windowPtr, displayId] = findTouchStateWindowAndDisplayLocked(token); if (statePtr == nullptr || windowPtr == nullptr || windowPtr->pointerIds.isEmpty()) { ALOGW("Attempted to pilfer points from a channel without any on-going pointer streams." " Ignoring."); @@ -5698,7 +5693,7 @@ status_t InputDispatcher::pilferPointersLocked(const sp& token) { CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, "input channel stole pointer stream"); options.deviceId = state.deviceId; - options.displayId = state.displayId; + options.displayId = displayId; options.pointerIds = window.pointerIds; std::string canceledWindows; for (const TouchedWindow& w : state.windows) { diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 3b44a8e85d..0ddbbeb27e 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -676,8 +676,8 @@ private: bool handled) REQUIRES(mLock); // Find touched state and touched window by token. - std::pair findTouchStateAndWindowLocked(const sp& token) - REQUIRES(mLock); + std::tuple + findTouchStateWindowAndDisplayLocked(const sp& token) REQUIRES(mLock); // Statistics gathering. LatencyAggregator mLatencyAggregator GUARDED_BY(mLock); diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index ceeeb1eb3d..d1d3e9a1bc 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -32,8 +32,6 @@ struct TouchState { int32_t deviceId = -1; // source of the device that is current down, others are rejected uint32_t source = 0; - // id to the display that currently has a touch, others are rejected - int32_t displayId = ADISPLAY_ID_NONE; std::vector windows; -- GitLab From 5c259c0e07395b7d80a43a36d500ba6412606689 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Mon, 7 Nov 2022 23:47:01 +0000 Subject: [PATCH 0470/1310] SF: Use strong pointers to Layer when moving snapshot Bug: 258009724 Test: presubmits Change-Id: Ie5c22a08bf8539a5ea3f8fb9d72bdaa1a1477e91 --- services/surfaceflinger/Layer.cpp | 9 ++++++--- services/surfaceflinger/SurfaceFlinger.cpp | 17 ++++++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 3478eaaa92..799d79f300 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -3965,14 +3965,17 @@ void Layer::updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMet } LayerSnapshotGuard::LayerSnapshotGuard(Layer* layer) : mLayer(layer) { - if (mLayer) { - mLayer->mLayerFE->mSnapshot = std::move(mLayer->mSnapshot); - } + LOG_ALWAYS_FATAL_IF(!mLayer, "LayerSnapshotGuard received a null layer."); + mLayer->mLayerFE->mSnapshot = std::move(mLayer->mSnapshot); + LOG_ALWAYS_FATAL_IF(!mLayer->mLayerFE->mSnapshot, + "LayerFE snapshot null after taking ownership from layer"); } LayerSnapshotGuard::~LayerSnapshotGuard() { if (mLayer) { mLayer->mSnapshot = std::move(mLayer->mLayerFE->mSnapshot); + LOG_ALWAYS_FATAL_IF(!mLayer->mSnapshot, + "Layer snapshot null after taking ownership from LayerFE"); } } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 53066024d0..b930231f73 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2182,13 +2182,14 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty; refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty; - std::vector layers; + std::vector> layers; mDrawingState.traverseInZOrder([&refreshArgs, &layers](Layer* layer) { + auto strongLayer = sp::fromExisting(layer); if (auto layerFE = layer->getCompositionEngineLayerFE()) { layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame); refreshArgs.layers.push_back(layerFE); - layers.push_back(layer); + layers.push_back(std::move(strongLayer)); } }); refreshArgs.blursAreExpensive = mBlursAreExpensive; @@ -2220,8 +2221,8 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) { std::vector layerSnapshotGuards; - for (Layer* layer : layers) { - layerSnapshotGuards.emplace_back(layer); + for (auto& layer : layers) { + layerSnapshotGuards.emplace_back(layer.get()); } mCompositionEngine->present(refreshArgs); } @@ -3327,7 +3328,12 @@ void SurfaceFlinger::updateCursorAsync() { std::vector layerSnapshotGuards; mDrawingState.traverse([&layerSnapshotGuards](Layer* layer) { - if (layer->getLayerSnapshot()->compositionType == + auto strongLayer = sp::fromExisting(layer); + const LayerSnapshot* snapshot = layer->getLayerSnapshot(); + if (!snapshot) { + LOG_ALWAYS_FATAL("Layer snapshot unexpectedly null"); + } + if (snapshot->compositionType == aidl::android::hardware::graphics::composer3::Composition::CURSOR) { layer->updateSnapshot(false /* updateGeometry */); layerSnapshotGuards.emplace_back(layer); @@ -6461,6 +6467,7 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( std::vector renderedLayers; bool disableBlurs = false; traverseLayers([&](Layer* layer) FTL_FAKE_GUARD(kMainThreadContext) { + auto strongLayer = sp::fromExisting(layer); auto layerFE = layer->getCompositionEngineLayerFE(); if (!layerFE) { return; -- GitLab From 5d32e628450494b72473c039c7d6f6ce57540bb1 Mon Sep 17 00:00:00 2001 From: Cody Northrop Date: Tue, 1 Nov 2022 13:54:57 -0600 Subject: [PATCH 0471/1310] GraphicsEnv: Fix isDebuggable Update the logic used in GraphicsEnv to decide whether shared objects can be inserted into the process. This is used by Vulkan layers, GLES layers, and ANGLE when deciding whether libraries from outside packages can be used. The new logic doesn't just use PR_GET_DUMPABLE which is no longer set by default in platform debug builds. It also incorporates ANDROID_DEBUGGABLE, which is defined when `debuggable` is true in Android.bp. This happens for eng or userdebug builds of the platform. The use of `debuggable` is the replacement for reading ro.debuggable which can no longer be read at runtime (see b/193912100). Tested with: export APP_PACKAGE= adb shell settings put global angle_debug_package org.chromium.angle adb shell settings put global angle_gl_driver_selection_pkgs $APP_PACKAGE adb shell settings put global angle_gl_driver_selection_values angle Test: Released app able to load from angle_debug_package on userdebug Test: Released app cannot use angle_debug_package on user build Bug: b/253678459 Change-Id: I3dda4258e23871ee2fab2cf5ba367612e00de0e2 --- libs/graphicsenv/Android.bp | 16 ++++++++++++++-- libs/graphicsenv/GraphicsEnv.cpp | 15 ++++++++++++++- .../include/graphicsenv/GraphicsEnv.h | 2 +- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/libs/graphicsenv/Android.bp b/libs/graphicsenv/Android.bp index a96a07a9b8..af50a2980c 100644 --- a/libs/graphicsenv/Android.bp +++ b/libs/graphicsenv/Android.bp @@ -27,10 +27,13 @@ cc_library_shared { srcs: [ "GpuStatsInfo.cpp", "GraphicsEnv.cpp", - "IGpuService.cpp" + "IGpuService.cpp", ], - cflags: ["-Wall", "-Werror"], + cflags: [ + "-Wall", + "-Werror", + ], shared_libs: [ "libbase", @@ -46,4 +49,13 @@ cc_library_shared { ], export_include_dirs: ["include"], + + product_variables: { + // `debuggable` is set for eng and userdebug builds + debuggable: { + cflags: [ + "-DANDROID_DEBUGGABLE", + ], + }, + }, } diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp index 4a0a839948..5f5f85a2ad 100644 --- a/libs/graphicsenv/GraphicsEnv.cpp +++ b/libs/graphicsenv/GraphicsEnv.cpp @@ -126,7 +126,20 @@ static const std::string getSystemNativeLibraries(NativeLibrary type) { } bool GraphicsEnv::isDebuggable() { - return prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) > 0; + // This flag determines if the application is marked debuggable + bool appDebuggable = prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) > 0; + + // This flag is set only in `debuggable` builds of the platform +#if defined(ANDROID_DEBUGGABLE) + bool platformDebuggable = true; +#else + bool platformDebuggable = false; +#endif + + ALOGV("GraphicsEnv::isDebuggable returning appDebuggable=%s || platformDebuggable=%s", + appDebuggable ? "true" : "false", platformDebuggable ? "true" : "false"); + + return appDebuggable || platformDebuggable; } void GraphicsEnv::setDriverPathAndSphalLibraries(const std::string path, diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h index 82a6b6c2c0..73d3196948 100644 --- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h +++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h @@ -35,7 +35,7 @@ public: // Check if the process is debuggable. It returns false except in any of the // following circumstances: - // 1. ro.debuggable=1 (global debuggable enabled). + // 1. ANDROID_DEBUGGABLE is defined (global debuggable enabled). // 2. android:debuggable="true" in the manifest for an individual app. // 3. An app which explicitly calls prctl(PR_SET_DUMPABLE, 1). // 4. GraphicsEnv calls prctl(PR_SET_DUMPABLE, 1) in the presence of -- GitLab From 6e1e987e52372f0d99fada08e74c7d5e068b6b53 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 8 Nov 2022 17:51:35 -0800 Subject: [PATCH 0472/1310] Dump TouchState and TouchedWindow These dumps will be expanded in the future as move state is moved into these structures. Bug: 211379801 Test: m inputflinger_tests && adb sync data && adb shell -t /data/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: Ic945e254f5e765ccb3c8b61e6a8e3f44095d07bf --- services/inputflinger/dispatcher/Android.bp | 1 + .../dispatcher/InputDispatcher.cpp | 38 +++++-------------- .../inputflinger/dispatcher/TouchState.cpp | 18 ++++++++- services/inputflinger/dispatcher/TouchState.h | 2 +- .../inputflinger/dispatcher/TouchedWindow.cpp | 36 ++++++++++++++++++ .../inputflinger/dispatcher/TouchedWindow.h | 8 ++-- 6 files changed, 69 insertions(+), 34 deletions(-) create mode 100644 services/inputflinger/dispatcher/TouchedWindow.cpp diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp index 99c4936f32..ab5c5ef99c 100644 --- a/services/inputflinger/dispatcher/Android.bp +++ b/services/inputflinger/dispatcher/Android.bp @@ -46,6 +46,7 @@ filegroup { "LatencyAggregator.cpp", "LatencyTracker.cpp", "Monitor.cpp", + "TouchedWindow.cpp", "TouchState.cpp", ], } diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index f6982173af..8ae59390c0 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -29,6 +29,7 @@ #include #endif #include +#include #include #include #include @@ -2232,11 +2233,10 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( // If the pointer is not currently down, then ignore the event. if (!tempTouchState.isDown()) { - if (DEBUG_FOCUS) { - ALOGD("Dropping event because the pointer is not down or we previously " - "dropped the pointer down event in display %" PRId32, - displayId); - } + ALOGD_IF(DEBUG_FOCUS, + "Dropping event because the pointer is not down or we previously " + "dropped the pointer down event in display %" PRId32 ": %s", + displayId, entry.getDescription().c_str()); outInjectionResult = InputEventInjectionResult::FAILED; goto Failed; } @@ -2443,10 +2443,8 @@ Failed: if (isHoverAction) { // Started hovering, therefore no longer down. if (oldState && oldState->isDown()) { - if (DEBUG_FOCUS) { - ALOGD("Conflicting pointer actions: Hover received while pointer was " - "down."); - } + ALOGD_IF(DEBUG_FOCUS, + "Conflicting pointer actions: Hover received while pointer was down."); *outConflictingPointerActions = true; } tempTouchState.reset(); @@ -2462,9 +2460,7 @@ Failed: } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { // First pointer went down. if (oldState && oldState->isDown()) { - if (DEBUG_FOCUS) { - ALOGD("Conflicting pointer actions: Down received while already down."); - } + ALOGD("Conflicting pointer actions: Down received while already down."); *outConflictingPointerActions = true; } } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) { @@ -5320,22 +5316,8 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) { if (!mTouchStatesByDisplay.empty()) { dump += StringPrintf(INDENT "TouchStatesByDisplay:\n"); for (const auto& [displayId, state] : mTouchStatesByDisplay) { - dump += StringPrintf(INDENT2 "%d: deviceId=%d, source=0x%08x\n", displayId, - state.deviceId, state.source); - if (!state.windows.empty()) { - dump += INDENT3 "Windows:\n"; - for (size_t i = 0; i < state.windows.size(); i++) { - const TouchedWindow& touchedWindow = state.windows[i]; - dump += StringPrintf(INDENT4 "%zu: name='%s', pointerIds=0x%0x, " - "targetFlags=0x%x, firstDownTimeInTarget=%" PRId64 - "ms\n", - i, touchedWindow.windowHandle->getName().c_str(), - touchedWindow.pointerIds.value, touchedWindow.targetFlags, - ns2ms(touchedWindow.firstDownTimeInTarget.value_or(0))); - } - } else { - dump += INDENT3 "Windows: \n"; - } + std::string touchStateDump = addLinePrefix(state.dump(), INDENT2); + dump += INDENT2 + std::to_string(displayId) + " : " + touchStateDump; } } else { dump += INDENT "TouchStates: \n"; diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index f5b7cb88d9..0cc2c6da9b 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -14,12 +14,13 @@ * limitations under the License. */ +#include #include #include "InputTarget.h" - #include "TouchState.h" +using android::base::StringPrintf; using android::gui::WindowInfo; using android::gui::WindowInfoHandle; @@ -143,4 +144,19 @@ bool TouchState::isDown() const { [](const TouchedWindow& window) { return !window.pointerIds.isEmpty(); }); } +std::string TouchState::dump() const { + std::string out; + out += StringPrintf("deviceId=%d, source=0x%08x\n", deviceId, source); + if (!windows.empty()) { + out += " Windows:\n"; + for (size_t i = 0; i < windows.size(); i++) { + const TouchedWindow& touchedWindow = windows[i]; + out += StringPrintf(" %zu : ", i) + touchedWindow.dump(); + } + } else { + out += " Windows: \n"; + } + return out; +} + } // namespace android::inputdispatcher diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index d1d3e9a1bc..e4e59fd132 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -16,7 +16,6 @@ #pragma once -#include "Monitor.h" #include "TouchedWindow.h" namespace android { @@ -57,6 +56,7 @@ struct TouchState { sp getWallpaperWindow() const; // Whether any of the windows are currently being touched bool isDown() const; + std::string dump() const; }; } // namespace inputdispatcher diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp new file mode 100644 index 0000000000..ec51662abb --- /dev/null +++ b/services/inputflinger/dispatcher/TouchedWindow.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TouchedWindow.h" + +#include +#include + +using android::base::StringPrintf; + +namespace android { + +namespace inputdispatcher { + +std::string TouchedWindow::dump() const { + return StringPrintf("name='%s', pointerIds=0x%0x, " + "targetFlags=0x%x, firstDownTimeInTarget=%s\n", + windowHandle->getName().c_str(), pointerIds.value, targetFlags, + toString(firstDownTimeInTarget).c_str()); +} + +} // namespace inputdispatcher +} // namespace android diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h index a6c505b854..0cc99999d0 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -16,11 +16,10 @@ #pragma once -namespace android { +#include +#include -namespace gui { -class WindowInfoHandle; -} +namespace android { namespace inputdispatcher { @@ -33,6 +32,7 @@ struct TouchedWindow { // Time at which the first action down occurred on this window. // NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE scenario. std::optional firstDownTimeInTarget; + std::string dump() const; }; } // namespace inputdispatcher -- GitLab From 7584c6a6ca02a5780ac68c8fcfa1d3408671e04d Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Sat, 29 Oct 2022 02:10:58 +0000 Subject: [PATCH 0473/1310] SF: refactor renderScreenImpl to use CompositionEngine Bug: 238643986 Test: presubmits Test: go/wm-smoke Change-Id: I6dbbcd5ce5070ec1d801dca55b5bb89fafe839cb --- services/surfaceflinger/Android.bp | 1 + .../include/compositionengine/impl/Output.h | 2 + .../CompositionEngine/src/Output.cpp | 82 ++++--- services/surfaceflinger/Layer.cpp | 6 + services/surfaceflinger/Layer.h | 1 + .../surfaceflinger/ScreenCaptureOutput.cpp | 107 ++++++++++ services/surfaceflinger/ScreenCaptureOutput.h | 69 ++++++ .../ScreenCaptureRenderSurface.h | 81 +++++++ services/surfaceflinger/SurfaceFlinger.cpp | 201 +++++++++--------- services/surfaceflinger/SurfaceFlinger.h | 2 +- .../tests/unittests/CompositionTest.cpp | 4 +- .../tests/unittests/TestableSurfaceFlinger.h | 13 +- 12 files changed, 419 insertions(+), 150 deletions(-) create mode 100644 services/surfaceflinger/ScreenCaptureOutput.cpp create mode 100644 services/surfaceflinger/ScreenCaptureOutput.h create mode 100644 services/surfaceflinger/ScreenCaptureRenderSurface.h diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index 999c03f96b..14fdd126dd 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -189,6 +189,7 @@ filegroup { "Scheduler/VsyncConfiguration.cpp", "Scheduler/VsyncModulator.cpp", "Scheduler/VsyncSchedule.cpp", + "ScreenCaptureOutput.cpp", "StartPropertySetThread.cpp", "SurfaceFlinger.cpp", "SurfaceFlingerDefaultFactory.cpp", diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h index 23d5570096..e06da33025 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h @@ -134,9 +134,11 @@ protected: void applyCompositionStrategy(const std::optional&) override{}; bool getSkipColorTransform() const override; compositionengine::Output::FrameFences presentAndGetFrameFences() override; + virtual renderengine::DisplaySettings generateClientCompositionDisplaySettings() const; std::vector generateClientCompositionRequests( bool supportsProtectedContent, ui::Dataspace outputDataspace, std::vector &outLayerFEs) override; + virtual bool layerNeedsFiltering(const OutputLayer*) const; void appendRegionFlashRequests(const Region&, std::vector&) override; void setExpensiveRenderingExpected(bool enabled) override; void setHintSessionGpuFence(std::unique_ptr&& gpuFence) override; diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index c2b1f0679c..d1daca6090 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -1226,40 +1226,8 @@ std::optional Output::composeSurfaces( ALOGV("hasClientComposition"); - renderengine::DisplaySettings clientCompositionDisplay; - clientCompositionDisplay.physicalDisplay = outputState.framebufferSpace.getContent(); - clientCompositionDisplay.clip = outputState.layerStackSpace.getContent(); - clientCompositionDisplay.orientation = - ui::Transform::toRotationFlags(outputState.displaySpace.getOrientation()); - clientCompositionDisplay.outputDataspace = mDisplayColorProfile->hasWideColorGamut() - ? outputState.dataspace - : ui::Dataspace::UNKNOWN; - - // If we have a valid current display brightness use that, otherwise fall back to the - // display's max desired - clientCompositionDisplay.currentLuminanceNits = outputState.displayBrightnessNits > 0.f - ? outputState.displayBrightnessNits - : mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance(); - clientCompositionDisplay.maxLuminance = - mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance(); - clientCompositionDisplay.targetLuminanceNits = - outputState.clientTargetBrightness * outputState.displayBrightnessNits; - clientCompositionDisplay.dimmingStage = outputState.clientTargetDimmingStage; - clientCompositionDisplay.renderIntent = - static_cast( - outputState.renderIntent); - - // Compute the global color transform matrix. - clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix; - for (auto& info : outputState.borderInfoList) { - renderengine::BorderRenderInfo borderInfo; - borderInfo.width = info.width; - borderInfo.color = info.color; - borderInfo.combinedRegion = info.combinedRegion; - clientCompositionDisplay.borderInfoList.emplace_back(std::move(borderInfo)); - } - clientCompositionDisplay.deviceHandlesColorTransform = - outputState.usesDeviceComposition || getSkipColorTransform(); + renderengine::DisplaySettings clientCompositionDisplay = + generateClientCompositionDisplaySettings(); // Generate the client composition requests for the layers on this output. auto& renderEngine = getCompositionEngine().getRenderEngine(); @@ -1350,6 +1318,46 @@ std::optional Output::composeSurfaces( return base::unique_fd(fence->dup()); } +renderengine::DisplaySettings Output::generateClientCompositionDisplaySettings() const { + const auto& outputState = getState(); + + renderengine::DisplaySettings clientCompositionDisplay; + clientCompositionDisplay.physicalDisplay = outputState.framebufferSpace.getContent(); + clientCompositionDisplay.clip = outputState.layerStackSpace.getContent(); + clientCompositionDisplay.orientation = + ui::Transform::toRotationFlags(outputState.displaySpace.getOrientation()); + clientCompositionDisplay.outputDataspace = mDisplayColorProfile->hasWideColorGamut() + ? outputState.dataspace + : ui::Dataspace::UNKNOWN; + + // If we have a valid current display brightness use that, otherwise fall back to the + // display's max desired + clientCompositionDisplay.currentLuminanceNits = outputState.displayBrightnessNits > 0.f + ? outputState.displayBrightnessNits + : mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance(); + clientCompositionDisplay.maxLuminance = + mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance(); + clientCompositionDisplay.targetLuminanceNits = + outputState.clientTargetBrightness * outputState.displayBrightnessNits; + clientCompositionDisplay.dimmingStage = outputState.clientTargetDimmingStage; + clientCompositionDisplay.renderIntent = + static_cast( + outputState.renderIntent); + + // Compute the global color transform matrix. + clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix; + for (auto& info : outputState.borderInfoList) { + renderengine::BorderRenderInfo borderInfo; + borderInfo.width = info.width; + borderInfo.color = info.color; + borderInfo.combinedRegion = info.combinedRegion; + clientCompositionDisplay.borderInfoList.emplace_back(std::move(borderInfo)); + } + clientCompositionDisplay.deviceHandlesColorTransform = + outputState.usesDeviceComposition || getSkipColorTransform(); + return clientCompositionDisplay; +} + std::vector Output::generateClientCompositionRequests( bool supportsProtectedContent, ui::Dataspace outputDataspace, std::vector& outLayerFEs) { std::vector clientCompositionLayers; @@ -1415,7 +1423,7 @@ std::vector Output::generateClientCompositionRequests( Enabled); compositionengine::LayerFE::ClientCompositionTargetSettings targetSettings{.clip = clip, - .needsFiltering = layer->needsFiltering() || + .needsFiltering = layerNeedsFiltering(layer) || outputState.needsFiltering, .isSecure = outputState.isSecure, .supportsProtectedContent = supportsProtectedContent, @@ -1446,6 +1454,10 @@ std::vector Output::generateClientCompositionRequests( return clientCompositionLayers; } +bool Output::layerNeedsFiltering(const compositionengine::OutputLayer* layer) const { + return layer->needsFiltering(); +} + void Output::appendRegionFlashRequests( const Region& flashRegion, std::vector& clientCompositionLayers) { if (flashRegion.isEmpty()) { diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 83a46ae3df..2a18521b5b 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -3499,6 +3499,12 @@ const compositionengine::LayerFECompositionState* Layer::getCompositionState() c return mSnapshot.get(); } +sp Layer::copyCompositionEngineLayerFE() const { + auto result = mFlinger->getFactory().createLayerFE(mLayerFE->getDebugName()); + result->mSnapshot = std::make_unique(*mSnapshot); + return result; +} + void Layer::useSurfaceDamage() { if (mFlinger->mForceFullDamage) { surfaceDamageRegion = Region::INVALID_REGION; diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index a3c4e5966c..9585fa9b30 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -323,6 +323,7 @@ public: ui::Dataspace getRequestedDataSpace() const; virtual sp getCompositionEngineLayerFE() const; + virtual sp copyCompositionEngineLayerFE() const; const LayerSnapshot* getLayerSnapshot() const; LayerSnapshot* editLayerSnapshot(); diff --git a/services/surfaceflinger/ScreenCaptureOutput.cpp b/services/surfaceflinger/ScreenCaptureOutput.cpp new file mode 100644 index 0000000000..8f93ba40ee --- /dev/null +++ b/services/surfaceflinger/ScreenCaptureOutput.cpp @@ -0,0 +1,107 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ScreenCaptureOutput.h" +#include "ScreenCaptureRenderSurface.h" + +#include +#include +#include +#include + +namespace android { + +std::shared_ptr createScreenCaptureOutput(ScreenCaptureOutputArgs&& args) { + std::shared_ptr output = compositionengine::impl::createOutputTemplated< + ScreenCaptureOutput, compositionengine::CompositionEngine, + ScreenCaptureOutputArgs&&>(args.compositionEngine, std::move(args)); + output->editState().isSecure = args.renderArea.isSecure(); + output->setCompositionEnabled(true); + output->setLayerFilter({args.layerStack}); + output->setRenderSurface(std::make_unique(std::move(args.buffer))); + output->setDisplayBrightness(args.sdrWhitePointNits, args.displayBrightnessNits); + + output->setDisplayColorProfile(std::make_unique( + compositionengine::DisplayColorProfileCreationArgsBuilder() + .setHasWideColorGamut(true) + .Build())); + + ui::Rotation orientation = ui::Transform::toRotation(args.renderArea.getRotationFlags()); + Rect orientedDisplaySpaceRect{args.renderArea.getReqWidth(), args.renderArea.getReqHeight()}; + output->setProjection(orientation, args.renderArea.getLayerStackSpaceRect(), + orientedDisplaySpaceRect); + + Rect sourceCrop = args.renderArea.getSourceCrop(); + output->setDisplaySize({sourceCrop.getWidth(), sourceCrop.getHeight()}); + + return output; +} + +ScreenCaptureOutput::ScreenCaptureOutput(ScreenCaptureOutputArgs&& args) + : mRenderArea(args.renderArea), + mFilterForScreenshot(std::move(args.filterForScreenshot)), + mColorProfile(args.colorProfile), + mRegionSampling(args.regionSampling) {} + +void ScreenCaptureOutput::updateColorProfile(const compositionengine::CompositionRefreshArgs&) { + auto& outputState = editState(); + outputState.dataspace = mColorProfile.dataspace; + outputState.renderIntent = mColorProfile.renderIntent; +} + +renderengine::DisplaySettings ScreenCaptureOutput::generateClientCompositionDisplaySettings() + const { + auto clientCompositionDisplay = + compositionengine::impl::Output::generateClientCompositionDisplaySettings(); + clientCompositionDisplay.clip = mRenderArea.getSourceCrop(); + clientCompositionDisplay.targetLuminanceNits = -1; + return clientCompositionDisplay; +} + +std::vector +ScreenCaptureOutput::generateClientCompositionRequests( + bool supportsProtectedContent, ui::Dataspace outputDataspace, + std::vector& outLayerFEs) { + auto clientCompositionLayers = compositionengine::impl::Output:: + generateClientCompositionRequests(supportsProtectedContent, outputDataspace, + outLayerFEs); + + if (mRegionSampling) { + for (auto& layer : clientCompositionLayers) { + layer.backgroundBlurRadius = 0; + layer.blurRegions.clear(); + } + } + + Rect sourceCrop = mRenderArea.getSourceCrop(); + compositionengine::LayerFE::LayerSettings fillLayer; + fillLayer.source.buffer.buffer = nullptr; + fillLayer.source.solidColor = half3(0.0f, 0.0f, 0.0f); + fillLayer.geometry.boundaries = + FloatRect(static_cast(sourceCrop.left), static_cast(sourceCrop.top), + static_cast(sourceCrop.right), static_cast(sourceCrop.bottom)); + fillLayer.alpha = half(RenderArea::getCaptureFillValue(mRenderArea.getCaptureFill())); + clientCompositionLayers.insert(clientCompositionLayers.begin(), fillLayer); + + return clientCompositionLayers; +} + +bool ScreenCaptureOutput::layerNeedsFiltering(const compositionengine::OutputLayer* layer) const { + return mRenderArea.needsFiltering() || + mFilterForScreenshot.find(&layer->getLayerFE()) != mFilterForScreenshot.end(); +} + +} // namespace android diff --git a/services/surfaceflinger/ScreenCaptureOutput.h b/services/surfaceflinger/ScreenCaptureOutput.h new file mode 100644 index 0000000000..61b5ddb1bb --- /dev/null +++ b/services/surfaceflinger/ScreenCaptureOutput.h @@ -0,0 +1,69 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include "RenderArea.h" + +namespace android { + +struct ScreenCaptureOutputArgs { + const compositionengine::CompositionEngine& compositionEngine; + const compositionengine::Output::ColorProfile& colorProfile; + const RenderArea& renderArea; + ui::LayerStack layerStack; + std::shared_ptr buffer; + float sdrWhitePointNits; + float displayBrightnessNits; + std::unordered_set filterForScreenshot; + bool regionSampling; +}; + +// ScreenCaptureOutput is used to compose a set of layers into a preallocated buffer. +// +// SurfaceFlinger passes instances of ScreenCaptureOutput to CompositionEngine in calls to +// SurfaceFlinger::captureLayers and SurfaceFlinger::captureDisplay. +class ScreenCaptureOutput : public compositionengine::impl::Output { +public: + ScreenCaptureOutput(ScreenCaptureOutputArgs&&); + + void updateColorProfile(const compositionengine::CompositionRefreshArgs&) override; + + std::vector generateClientCompositionRequests( + bool supportsProtectedContent, ui::Dataspace outputDataspace, + std::vector& outLayerFEs) override; + + bool layerNeedsFiltering(const compositionengine::OutputLayer*) const override; + +protected: + bool getSkipColorTransform() const override { return false; } + renderengine::DisplaySettings generateClientCompositionDisplaySettings() const override; + +private: + const RenderArea& mRenderArea; + const std::unordered_set mFilterForScreenshot; + const compositionengine::Output::ColorProfile& mColorProfile; + const bool mRegionSampling; +}; + +std::shared_ptr createScreenCaptureOutput(ScreenCaptureOutputArgs&&); + +} // namespace android diff --git a/services/surfaceflinger/ScreenCaptureRenderSurface.h b/services/surfaceflinger/ScreenCaptureRenderSurface.h new file mode 100644 index 0000000000..20973003d5 --- /dev/null +++ b/services/surfaceflinger/ScreenCaptureRenderSurface.h @@ -0,0 +1,81 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include +#include + +namespace android { + +// ScreenCaptureRenderSurface is a RenderSurface that returns a preallocated buffer used by +// ScreenCaptureOutput. +class ScreenCaptureRenderSurface : public compositionengine::RenderSurface { +public: + ScreenCaptureRenderSurface(std::shared_ptr buffer) + : mBuffer(std::move(buffer)){}; + + std::shared_ptr dequeueBuffer( + base::unique_fd* /* bufferFence */) override { + return mBuffer; + } + + void queueBuffer(base::unique_fd readyFence) override { + mRenderFence = sp::make(readyFence.release()); + } + + const sp& getClientTargetAcquireFence() const override { return mRenderFence; } + + bool supportsCompositionStrategyPrediction() const override { return false; } + + bool isValid() const override { return true; } + + void initialize() override {} + + const ui::Size& getSize() const override { return mSize; } + + bool isProtected() const override { return mBuffer->getUsage() & GRALLOC_USAGE_PROTECTED; } + + void setDisplaySize(const ui::Size&) override {} + + void setBufferDataspace(ui::Dataspace) override {} + + void setBufferPixelFormat(ui::PixelFormat) override {} + + void setProtected(bool /* useProtected */) override {} + + status_t beginFrame(bool /* mustRecompose */) override { return OK; } + + void prepareFrame(bool /* usesClientComposition */, bool /* usesDeviceComposition */) override { + } + + void onPresentDisplayCompleted() override {} + + void dump(std::string& /* result */) const override {} + +private: + std::shared_ptr mBuffer; + + sp mRenderFence = Fence::NO_FENCE; + + ui::Size mSize; +}; + +} // namespace android diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 999af304ef..56fa96be3e 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -44,10 +44,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -137,6 +139,7 @@ #include "Scheduler/LayerHistory.h" #include "Scheduler/Scheduler.h" #include "Scheduler/VsyncConfiguration.h" +#include "ScreenCaptureOutput.h" #include "StartPropertySetThread.h" #include "SurfaceFlingerProperties.h" #include "TimeStats/TimeStats.h" @@ -6256,7 +6259,8 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, return BAD_VALUE; } - Rect layerStackSpaceRect(0, 0, reqSize.width, reqSize.height); + Rect layerStackSpaceRect(crop.left, crop.top, crop.left + reqSize.width, + crop.top + reqSize.height); bool childrenOnly = args.childrenOnly; RenderAreaFuture renderAreaFuture = ftl::defer([=]() -> std::unique_ptr { return std::make_unique(*this, parent, crop, reqSize, dataspace, @@ -6376,7 +6380,7 @@ ftl::SharedFuture SurfaceFlinger::captureScreenCommon( ftl::SharedFuture renderFuture; renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) { - renderFuture = renderScreenImpl(*renderArea, traverseLayers, buffer, + renderFuture = renderScreenImpl(std::move(renderArea), traverseLayers, buffer, canCaptureBlackoutContent, regionSampling, grayscale, captureResults); }); @@ -6404,19 +6408,19 @@ ftl::SharedFuture SurfaceFlinger::captureScreenCommon( } ftl::SharedFuture SurfaceFlinger::renderScreenImpl( - const RenderArea& renderArea, TraverseLayersFunction traverseLayers, + std::unique_ptr renderArea, TraverseLayersFunction traverseLayers, const std::shared_ptr& buffer, bool canCaptureBlackoutContent, bool regionSampling, bool grayscale, ScreenCaptureResults& captureResults) { ATRACE_CALL(); + size_t layerCount = 0; traverseLayers([&](Layer* layer) { + layerCount++; captureResults.capturedSecureLayers = captureResults.capturedSecureLayers || (layer->isVisible() && layer->isSecure()); }); - const bool useProtected = buffer->getUsage() & GRALLOC_USAGE_PROTECTED; - // We allow the system server to take screenshots of secure layers for // use in situations like the Screen-rotation animation and place // the impetus on WindowManager to not persist them. @@ -6426,8 +6430,8 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( } captureResults.buffer = buffer->getBuffer(); - auto dataspace = renderArea.getReqDataSpace(); - auto parent = renderArea.getParentLayer(); + auto dataspace = renderArea->getReqDataSpace(); + auto parent = renderArea->getParentLayer(); auto renderIntent = RenderIntent::TONE_MAP_COLORIMETRIC; auto sdrWhitePointNits = DisplayDevice::sDefaultMaxLumiance; auto displayBrightnessNits = DisplayDevice::sDefaultMaxLumiance; @@ -6449,122 +6453,107 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( } captureResults.capturedDataspace = dataspace; - const auto reqWidth = renderArea.getReqWidth(); - const auto reqHeight = renderArea.getReqHeight(); - const auto sourceCrop = renderArea.getSourceCrop(); - const auto transform = renderArea.getTransform(); - const auto rotation = renderArea.getRotationFlags(); - const auto& layerStackSpaceRect = renderArea.getLayerStackSpaceRect(); - - renderengine::DisplaySettings clientCompositionDisplay; - std::vector clientCompositionLayers; - - // assume that bounds are never offset, and that they are the same as the - // buffer bounds. - clientCompositionDisplay.physicalDisplay = Rect(reqWidth, reqHeight); - clientCompositionDisplay.clip = sourceCrop; - clientCompositionDisplay.orientation = rotation; - - clientCompositionDisplay.outputDataspace = dataspace; - clientCompositionDisplay.currentLuminanceNits = displayBrightnessNits; - clientCompositionDisplay.maxLuminance = DisplayDevice::sDefaultMaxLumiance; - clientCompositionDisplay.renderIntent = - static_cast(renderIntent); - - const float colorSaturation = grayscale ? 0 : 1; - clientCompositionDisplay.colorTransform = calculateColorMatrix(colorSaturation); - - const float alpha = RenderArea::getCaptureFillValue(renderArea.getCaptureFill()); - - compositionengine::LayerFE::LayerSettings fillLayer; - fillLayer.source.buffer.buffer = nullptr; - fillLayer.source.solidColor = half3(0.0, 0.0, 0.0); - fillLayer.geometry.boundaries = - FloatRect(sourceCrop.left, sourceCrop.top, sourceCrop.right, sourceCrop.bottom); - fillLayer.alpha = half(alpha); - clientCompositionLayers.push_back(fillLayer); - - const auto display = renderArea.getDisplayDevice(); - std::vector renderedLayers; - bool disableBlurs = false; - traverseLayers([&](Layer* layer) FTL_FAKE_GUARD(kMainThreadContext) { + const auto transform = renderArea->getTransform(); + const auto display = renderArea->getDisplayDevice(); + + std::vector>> layers; + layers.reserve(layerCount); + std::unordered_set filterForScreenshot; + traverseLayers([&](Layer* layer) { auto strongLayer = sp::fromExisting(layer); - auto layerFE = layer->getCompositionEngineLayerFE(); - if (!layerFE) { - return; - } + captureResults.capturedHdrLayers |= isHdrLayer(layer); // Layer::prepareClientComposition uses the layer's snapshot to populate the resulting // LayerSettings. Calling Layer::updateSnapshot ensures that LayerSettings are // generated with the layer's current buffer and geometry. layer->updateSnapshot(true /* updateGeometry */); - disableBlurs |= layer->getDrawingState().sidebandStream != nullptr; - - Region clip(renderArea.getBounds()); - compositionengine::LayerFE::ClientCompositionTargetSettings targetSettings{ - clip, - layer->needsFilteringForScreenshots(display.get(), transform) || - renderArea.needsFiltering(), - renderArea.isSecure(), - useProtected, - layerStackSpaceRect, - clientCompositionDisplay.outputDataspace, - true, /* realContentIsVisible */ - false, /* clearContent */ - disableBlurs ? compositionengine::LayerFE::ClientCompositionTargetSettings:: - BlurSetting::Disabled - : compositionengine::LayerFE::ClientCompositionTargetSettings:: - BlurSetting::Enabled, - isHdrLayer(layer) ? displayBrightnessNits : sdrWhitePointNits, + layers.emplace_back(layer, layer->copyCompositionEngineLayerFE()); - }; - std::optional settings; - { - LayerSnapshotGuard layerSnapshotGuard(layer); - settings = layerFE->prepareClientComposition(targetSettings); - } + sp& layerFE = layers.back().second; - if (!settings) { - return; - } + layerFE->mSnapshot->geomLayerTransform = + renderArea->getTransform() * layerFE->mSnapshot->geomLayerTransform; - settings->geometry.positionTransform = - transform.asMatrix4() * settings->geometry.positionTransform; - // There's no need to process blurs when we're executing region sampling, - // we're just trying to understand what we're drawing, and doing so without - // blurs is already a pretty good approximation. - if (regionSampling) { - settings->backgroundBlurRadius = 0; - settings->blurRegions.clear(); + if (layer->needsFilteringForScreenshots(display.get(), transform)) { + filterForScreenshot.insert(layerFE.get()); } - captureResults.capturedHdrLayers |= isHdrLayer(layer); - - clientCompositionLayers.push_back(std::move(*settings)); - renderedLayers.push_back(layer); }); - std::vector clientRenderEngineLayers; - clientRenderEngineLayers.reserve(clientCompositionLayers.size()); - std::transform(clientCompositionLayers.begin(), clientCompositionLayers.end(), - std::back_inserter(clientRenderEngineLayers), - [](compositionengine::LayerFE::LayerSettings& settings) - -> renderengine::LayerSettings { return settings; }); + ui::LayerStack layerStack{ui::DEFAULT_LAYER_STACK}; + if (!layers.empty()) { + const sp& layerFE = layers.back().second; + layerStack = layerFE->getCompositionState()->outputFilter.layerStack; + } - // Use an empty fence for the buffer fence, since we just created the buffer so - // there is no need for synchronization with the GPU. - base::unique_fd bufferFence; + auto copyLayerFEs = [&layers]() { + std::vector> layerFEs; + layerFEs.reserve(layers.size()); + for (const auto& [_, layerFE] : layers) { + layerFEs.push_back(layerFE); + } + return layerFEs; + }; - constexpr bool kUseFramebufferCache = false; - const auto future = getRenderEngine() - .drawLayers(clientCompositionDisplay, clientRenderEngineLayers, - buffer, kUseFramebufferCache, std::move(bufferFence)) - .share(); + auto present = [this, buffer = std::move(buffer), dataspace, sdrWhitePointNits, + displayBrightnessNits, filterForScreenshot = std::move(filterForScreenshot), + grayscale, layerFEs = copyLayerFEs(), layerStack, regionSampling, + renderArea = std::move(renderArea), renderIntent]() -> FenceResult { + std::unique_ptr compositionEngine = + mFactory.createCompositionEngine(); + compositionEngine->setRenderEngine(mRenderEngine.get()); + + compositionengine::Output::ColorProfile colorProfile{.dataspace = dataspace, + .renderIntent = renderIntent}; + + std::shared_ptr output = createScreenCaptureOutput( + ScreenCaptureOutputArgs{.compositionEngine = *compositionEngine, + .colorProfile = colorProfile, + .renderArea = *renderArea, + .layerStack = layerStack, + .buffer = std::move(buffer), + .sdrWhitePointNits = sdrWhitePointNits, + .displayBrightnessNits = displayBrightnessNits, + .filterForScreenshot = std::move(filterForScreenshot), + .regionSampling = regionSampling}); + + const float colorSaturation = grayscale ? 0 : 1; + compositionengine::CompositionRefreshArgs refreshArgs{ + .outputs = {output}, + .layers = std::move(layerFEs), + .updatingOutputGeometryThisFrame = true, + .updatingGeometryThisFrame = true, + .colorTransformMatrix = calculateColorMatrix(colorSaturation), + }; + compositionEngine->present(refreshArgs); - for (auto* layer : renderedLayers) { - layer->onLayerDisplayed(future); + return output->getRenderSurface()->getClientTargetAcquireFence(); + }; + + // If RenderEngine is threaded, we can safely call CompositionEngine::present off the main + // thread as the RenderEngine::drawLayers call will run on RenderEngine's thread. Otherwise, + // we need RenderEngine to run on the main thread so we call CompositionEngine::present + // immediately. + // + // TODO(b/196334700) Once we use RenderEngineThreaded everywhere we can always defer the call + // to CompositionEngine::present. + const bool renderEngineIsThreaded = [&]() { + using Type = renderengine::RenderEngine::RenderEngineType; + const auto type = mRenderEngine->getRenderEngineType(); + return type == Type::THREADED || type == Type::SKIA_GL_THREADED; + }(); + auto presentFuture = renderEngineIsThreaded ? ftl::defer(std::move(present)).share() + : ftl::yield(present()).share(); + + for (auto& [layer, layerFE] : layers) { + layer->onLayerDisplayed( + ftl::Future(presentFuture) + .then([layerFE = std::move(layerFE)](FenceResult) { + return layerFE->stealCompositionResult().releaseFences.back().get(); + }) + .share()); } - return future; + return presentFuture; } // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 3d93d90b30..ee2810da36 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -780,7 +780,7 @@ private: const std::shared_ptr&, bool regionSampling, bool grayscale, const sp&); ftl::SharedFuture renderScreenImpl( - const RenderArea&, TraverseLayersFunction, + std::unique_ptr, TraverseLayersFunction, const std::shared_ptr&, bool canCaptureBlackoutContent, bool regionSampling, bool grayscale, ScreenCaptureResults&) EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 7148c117c5..06b9caa7cb 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -244,8 +244,8 @@ void CompositionTest::captureScreenComposition() { HAL_PIXEL_FORMAT_RGBA_8888, 1, usage); - auto future = mFlinger.renderScreenImpl(*renderArea, traverseLayers, mCaptureScreenBuffer, - forSystem, regionSampling); + auto future = mFlinger.renderScreenImpl(std::move(renderArea), traverseLayers, + mCaptureScreenBuffer, forSystem, regionSampling); ASSERT_TRUE(future.valid()); const auto fenceResult = future.get(); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 46eca69c6a..df53f1560a 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -37,6 +37,7 @@ #include "FrontEnd/LayerHandle.h" #include "Layer.h" #include "NativeWindowSurface.h" +#include "RenderArea.h" #include "Scheduler/MessageQueue.h" #include "Scheduler/RefreshRateSelector.h" #include "StartPropertySetThread.h" @@ -399,14 +400,14 @@ public: return mFlinger->setPowerModeInternal(display, mode); } - auto renderScreenImpl(const RenderArea& renderArea, - SurfaceFlinger::TraverseLayersFunction traverseLayers, - const std::shared_ptr& buffer, - bool forSystem, bool regionSampling) { + auto renderScreenImpl(std::unique_ptr renderArea, + SurfaceFlinger::TraverseLayersFunction traverseLayers, + const std::shared_ptr& buffer, + bool forSystem, bool regionSampling) { ScreenCaptureResults captureResults; return FTL_FAKE_GUARD(kMainThreadContext, - mFlinger->renderScreenImpl(renderArea, traverseLayers, buffer, - forSystem, regionSampling, + mFlinger->renderScreenImpl(std::move(renderArea), traverseLayers, + buffer, forSystem, regionSampling, false /* grayscale */, captureResults)); } -- GitLab From 2f37bcbe4f4c32a3457ebb9821fa8d8f8763421b Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 8 Nov 2022 20:41:28 +0000 Subject: [PATCH 0474/1310] Add timestamp smoothening for Bluetooth mice and touchpads While Bluetooth input devices are expected to produce input events at a consistent rate, the device may batch together several input events and send them all at once to Android to avoid excessive Bluetooth communication. When doing so, if the event timestamps are not processed correctly by the kernel or the device driver, these batched events will reach inputflinger with very similar timestamps even though they were generated at a fixed interval. To prevent negative user experiences with this type of Bluetooth batching, we enforce an assumption that all Bluetooth devices generate events at a maximum rate of 250Hz, which means each successive input event from a Bluetooth device must have a timestamp that is at least 4 milliseconds later than the preceeding event. Bug: 257124950 Test: atest inputflinger_tests Test: manual, with Wacom Intuos S Test: manual, with Apple Magic Trackpad 2 Test: manual, with Logitech MX Master 3 Test: manual, with Sony DualSense Contoller Change-Id: Ia32bb594d0897e69796bb39f59fcc067cf487ff2 --- .../reader/mapper/CursorInputMapper.cpp | 9 +- .../reader/mapper/CursorInputMapper.h | 1 + .../mapper/TouchCursorInputMapperCommon.h | 26 +++ .../reader/mapper/TouchInputMapper.cpp | 3 + .../reader/mapper/TouchInputMapper.h | 2 +- .../inputflinger/tests/InputReader_test.cpp | 177 +++++++++++++++++- .../tests/TestInputListenerMatchers.h | 5 + 7 files changed, 211 insertions(+), 12 deletions(-) diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index c691ca943f..a4f257c4b6 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -67,7 +67,7 @@ void CursorMotionAccumulator::finishSync() { // --- CursorInputMapper --- CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext) {} + : InputMapper(deviceContext), mLastEventTime(std::numeric_limits::min()) {} CursorInputMapper::~CursorInputMapper() { if (mPointerController != nullptr) { @@ -276,6 +276,7 @@ void CursorInputMapper::dumpParameters(std::string& dump) { std::list CursorInputMapper::reset(nsecs_t when) { mButtonState = 0; mDownTime = 0; + mLastEventTime = std::numeric_limits::min(); mPointerVelocityControl.reset(); mWheelXVelocityControl.reset(); @@ -295,7 +296,11 @@ std::list CursorInputMapper::process(const RawEvent* rawEvent) { mCursorScrollAccumulator.process(rawEvent); if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { - out += sync(rawEvent->when, rawEvent->readTime); + const nsecs_t eventTime = + applyBluetoothTimestampSmoothening(getDeviceContext().getDeviceIdentifier(), + rawEvent->when, mLastEventTime); + out += sync(eventTime, rawEvent->readTime); + mLastEventTime = eventTime; } return out; } diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index 6a4275ed54..20746e5bb0 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -121,6 +121,7 @@ private: int32_t mButtonState; nsecs_t mDownTime; + nsecs_t mLastEventTime; void configureParameters(); void dumpParameters(std::string& dump); diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h index 5a7ba9a6ed..0b7ff84c99 100644 --- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h +++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h @@ -101,4 +101,30 @@ static bool isPointerDown(int32_t buttonState) { return out; } +// For devices connected over Bluetooth, although they may produce events at a consistent rate, +// the events might end up reaching Android in a "batched" manner through the Bluetooth +// stack, where a few events may be clumped together and processed around the same time. +// In this case, if the input device or its driver does not send or process the actual event +// generation timestamps, the event time will set to whenever the kernel received the event. +// When the timestamp deltas are minuscule for these batched events, any changes in x or y +// coordinates result in extremely large instantaneous velocities, which can negatively impact +// user experience. To avoid this, we augment the timestamps so that subsequent event timestamps +// differ by at least a minimum delta value. +static nsecs_t applyBluetoothTimestampSmoothening(const InputDeviceIdentifier& identifier, + nsecs_t currentEventTime, nsecs_t lastEventTime) { + if (identifier.bus != BUS_BLUETOOTH) { + return currentEventTime; + } + + // Assume the fastest rate at which a Bluetooth touch device can report input events is one + // every 4 milliseconds, or 250 Hz. Timestamps for successive events from a Bluetooth device + // will be separated by at least this amount. + constexpr static nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4); + // We define a maximum smoothing time delta so that we don't generate events too far into the + // future. + constexpr static nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32); + return std::min(std::max(currentEventTime, lastEventTime + MIN_BLUETOOTH_TIMESTAMP_DELTA), + currentEventTime + MAX_BLUETOOTH_SMOOTHING_DELTA); +} + } // namespace android diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index ba3d9802a1..605b8f8377 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -1461,6 +1461,9 @@ std::list TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { const RawState& last = mRawStatesPending.size() == 1 ? mCurrentRawState : mRawStatesPending.rbegin()[1]; + next.when = applyBluetoothTimestampSmoothening(getDeviceContext().getDeviceIdentifier(), when, + last.when); + // Assign pointer ids. if (!mHavePointerIds) { assignPointerIds(last, next); diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 2bb9ecebe4..68cdef042c 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -315,7 +315,7 @@ protected: RawPointerAxes mRawPointerAxes; struct RawState { - nsecs_t when{}; + nsecs_t when{std::numeric_limits::min()}; nsecs_t readTime{}; // Raw pointer sample data. diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 0c2742fd16..6a174d076e 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -99,6 +99,11 @@ static constexpr int32_t ACTION_POINTER_1_UP = // Error tolerance for floating point assertions. static const float EPSILON = 0.001f; +// Minimum timestamp separation between subsequent input events from a Bluetooth device. +static constexpr nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4); +// Maximum smoothing time delta so that we don't generate events too far into the future. +constexpr static nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32); + template static inline T min(T a, T b) { return a < b ? a : b; @@ -530,10 +535,11 @@ public: FakeEventHub() { } - void addDevice(int32_t deviceId, const std::string& name, - ftl::Flags classes) { + void addDevice(int32_t deviceId, const std::string& name, ftl::Flags classes, + int bus = 0) { Device* device = new Device(classes); device->identifier.name = name; + device->identifier.bus = bus; mDevices.add(deviceId, device); enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0); @@ -3165,13 +3171,13 @@ protected: std::unique_ptr mReader; std::shared_ptr mDevice; - virtual void SetUp(ftl::Flags classes) { + virtual void SetUp(ftl::Flags classes, int bus = 0) { mFakeEventHub = std::make_unique(); mFakePolicy = sp::make(); mFakeListener = std::make_unique(); mReader = std::make_unique(mFakeEventHub, mFakePolicy, *mFakeListener); - mDevice = newDevice(DEVICE_ID, DEVICE_NAME, DEVICE_LOCATION, EVENTHUB_ID, classes); + mDevice = newDevice(DEVICE_ID, DEVICE_NAME, DEVICE_LOCATION, EVENTHUB_ID, classes, bus); // Consume the device reset notification generated when adding a new device. mFakeListener->assertNotifyDeviceResetWasCalled(); } @@ -3209,15 +3215,16 @@ protected: std::shared_ptr newDevice(int32_t deviceId, const std::string& name, const std::string& location, int32_t eventHubId, - ftl::Flags classes) { + ftl::Flags classes, int bus = 0) { InputDeviceIdentifier identifier; identifier.name = name; identifier.location = location; + identifier.bus = bus; std::shared_ptr device = std::make_shared(mReader->getContext(), deviceId, DEVICE_GENERATION, identifier); mReader->pushNextDevice(device); - mFakeEventHub->addDevice(eventHubId, name, classes); + mFakeEventHub->addDevice(eventHubId, name, classes, bus); mReader->loopOnce(); return device; } @@ -5396,6 +5403,106 @@ TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPoint ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } +// --- BluetoothCursorInputMapperTest --- + +class BluetoothCursorInputMapperTest : public CursorInputMapperTest { +protected: + void SetUp() override { + InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH); + + mFakePointerController = std::make_shared(); + mFakePolicy->setPointerController(mFakePointerController); + } +}; + +TEST_F(BluetoothCursorInputMapperTest, TimestampSmoothening) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = addMapperAndConfigure(); + + nsecs_t kernelEventTime = ARBITRARY_TIME; + nsecs_t expectedEventTime = ARBITRARY_TIME; + process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); + process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); + + // Process several events that come in quick succession, according to their timestamps. + for (int i = 0; i < 3; i++) { + constexpr static nsecs_t delta = ms2ns(1); + static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA); + kernelEventTime += delta; + expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; + + process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); + process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); + } +} + +TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningIsCapped) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = addMapperAndConfigure(); + + nsecs_t expectedEventTime = ARBITRARY_TIME; + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); + + // Process several events with the same timestamp from the kernel. + // Ensure that we do not generate events too far into the future. + constexpr static int32_t numEvents = + MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA; + for (int i = 0; i < numEvents; i++) { + expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; + + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); + } + + // By processing more events with the same timestamp, we should not generate events with a + // timestamp that is more than the specified max time delta from the timestamp at its injection. + const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA; + for (int i = 0; i < 3; i++) { + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(cappedEventTime)))); + } +} + +TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningNotUsed) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = addMapperAndConfigure(); + + nsecs_t kernelEventTime = ARBITRARY_TIME; + nsecs_t expectedEventTime = ARBITRARY_TIME; + process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); + process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); + + // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp + // smoothening is not needed, its timestamp is not affected. + kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1); + expectedEventTime = kernelEventTime; + + process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); + process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); +} + // --- TouchInputMapperTest --- class TouchInputMapperTest : public InputMapperTest { @@ -7389,7 +7496,8 @@ protected: void processKey(MultiTouchInputMapper& mapper, int32_t code, int32_t value); void processHidUsage(MultiTouchInputMapper& mapper, int32_t usageCode, int32_t value); void processMTSync(MultiTouchInputMapper& mapper); - void processSync(MultiTouchInputMapper& mapper); + void processSync(MultiTouchInputMapper& mapper, nsecs_t eventTime = ARBITRARY_TIME, + nsecs_t readTime = READ_TIME); }; void MultiTouchInputMapperTest::prepareAxes(int axes) { @@ -7502,8 +7610,9 @@ void MultiTouchInputMapperTest::processMTSync(MultiTouchInputMapper& mapper) { process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_MT_REPORT, 0); } -void MultiTouchInputMapperTest::processSync(MultiTouchInputMapper& mapper) { - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); +void MultiTouchInputMapperTest::processSync(MultiTouchInputMapper& mapper, nsecs_t eventTime, + nsecs_t readTime) { + process(mapper, eventTime, readTime, EV_SYN, SYN_REPORT, 0); } TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackingIds) { @@ -10114,6 +10223,56 @@ TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) { ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); } +// --- BluetoothMultiTouchInputMapperTest --- + +class BluetoothMultiTouchInputMapperTest : public MultiTouchInputMapperTest { +protected: + void SetUp() override { + InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH); + } +}; + +TEST_F(BluetoothMultiTouchInputMapperTest, TimestampSmoothening) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | ID | SLOT | PRESSURE); + MultiTouchInputMapper& mapper = addMapperAndConfigure(); + + nsecs_t kernelEventTime = ARBITRARY_TIME; + nsecs_t expectedEventTime = ARBITRARY_TIME; + // Touch down. + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, 100, 200); + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper, ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithEventTime(ARBITRARY_TIME)))); + + // Process several events that come in quick succession, according to their timestamps. + for (int i = 0; i < 3; i++) { + constexpr static nsecs_t delta = ms2ns(1); + static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA); + kernelEventTime += delta; + expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; + + processPosition(mapper, 101 + i, 201 + i); + processSync(mapper, kernelEventTime); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithEventTime(expectedEventTime)))); + } + + // Release the touch. + processId(mapper, INVALID_TRACKING_ID); + processPressure(mapper, RAW_PRESSURE_MIN); + processSync(mapper, ARBITRARY_TIME + ms2ns(50)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithEventTime(ARBITRARY_TIME + ms2ns(50))))); +} + +// --- MultiTouchPointerModeTest --- + class MultiTouchPointerModeTest : public MultiTouchInputMapperTest { protected: float mPointerMovementScale; diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h index 5107af7e27..9a47e3e4e7 100644 --- a/services/inputflinger/tests/TestInputListenerMatchers.h +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -91,4 +91,9 @@ MATCHER_P(WithButtonState, buttons, "InputEvent with specified button state") { return arg.buttonState == buttons; } +MATCHER_P(WithEventTime, eventTime, "InputEvent with specified eventTime") { + *result_listener << "expected event time " << eventTime << ", but got " << arg.eventTime; + return arg.eventTime == eventTime; +} + } // namespace android -- GitLab From 140c6a4e1c857306d6f589c82bb0437309bf72c0 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Wed, 9 Nov 2022 14:41:33 -0500 Subject: [PATCH 0475/1310] Use mDrawingState for printing hwc layers mCurrentState does not show mirror layers, so the the --hwclayers section was showing no layers on a display that simply contains mirror layers. Use mDrawingState, which does, so that all layers are dumped. Fixes: 248568076 Test: dumpsys SurfaceFlinger Change-Id: Ia82490e5d8b213cd248ef6f0a9838cbe4b75919b --- services/surfaceflinger/SurfaceFlinger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 6a25104a95..02f1c8ca14 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5120,7 +5120,7 @@ void SurfaceFlinger::dumpHwcLayersMinidumpLocked(std::string& result) const { Layer::miniDumpHeader(result); const DisplayDevice& ref = *display; - mCurrentState.traverseInZOrder([&](Layer* layer) { layer->miniDump(result, ref); }); + mDrawingState.traverseInZOrder([&](Layer* layer) { layer->miniDump(result, ref); }); result.append("\n"); } } -- GitLab From 40fff5cdf1db782c04b1bae6c9f45d56797f1b02 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Fri, 4 Nov 2022 02:46:28 +0000 Subject: [PATCH 0476/1310] SF: Look up buffer caches in binder thread Avoid locking inside the main thread and contention with binder thread (via client token binder died). Test: presubmit Bug: 238781169 Change-Id: I8a440e9fe3e6f41761d90196ec6128d756735eee --- libs/gui/ISurfaceComposer.cpp | 10 +-- libs/gui/SurfaceComposerClient.cpp | 8 +-- libs/gui/include/gui/ISurfaceComposer.h | 4 +- libs/gui/include/gui/LayerState.h | 3 +- libs/gui/tests/Surface_test.cpp | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 61 +++++++++++-------- services/surfaceflinger/SurfaceFlinger.h | 10 +-- .../Tracing/TransactionProtoParser.cpp | 4 +- .../Tracing/tools/LayerTraceGenerator.cpp | 8 +-- services/surfaceflinger/TransactionState.h | 33 ++++++---- .../fuzzer/surfaceflinger_fuzzers_utils.h | 14 +++-- .../tests/unittests/TestableSurfaceFlinger.h | 18 ++++-- .../unittests/TransactionApplicationTest.cpp | 25 +++++--- .../unittests/TransactionProtoParserTest.cpp | 4 +- .../unittests/TransactionTracingTest.cpp | 20 +++--- 15 files changed, 131 insertions(+), 93 deletions(-) diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index 4c887ec96d..a77ca04943 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -60,11 +60,11 @@ public: virtual ~BpSurfaceComposer(); status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo, - const Vector& state, - const Vector& displays, uint32_t flags, - const sp& applyToken, const InputWindowCommands& commands, - int64_t desiredPresentTime, bool isAutoTimestamp, - const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, + Vector& state, const Vector& displays, + uint32_t flags, const sp& applyToken, + const InputWindowCommands& commands, int64_t desiredPresentTime, + bool isAutoTimestamp, const client_cache_t& uncacheBuffer, + bool hasListenerCallbacks, const std::vector& listenerCallbacks, uint64_t transactionId) override { Parcel data, reply; diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 9c2ce0f242..b60e195c81 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -912,11 +912,11 @@ void SurfaceComposerClient::doUncacheBufferTransaction(uint64_t cacheId) { client_cache_t uncacheBuffer; uncacheBuffer.token = BufferCache::getInstance().getToken(); uncacheBuffer.id = cacheId; - + Vector composerStates; status_t status = - sf->setTransactionState(FrameTimelineInfo{}, {}, {}, ISurfaceComposer::eOneWay, - Transaction::getDefaultApplyToken(), {}, systemTime(), true, - uncacheBuffer, false, {}, generateId()); + sf->setTransactionState(FrameTimelineInfo{}, composerStates, {}, + ISurfaceComposer::eOneWay, Transaction::getDefaultApplyToken(), + {}, systemTime(), true, uncacheBuffer, false, {}, generateId()); if (status != NO_ERROR) { ALOGE_AND_TRACE("SurfaceComposerClient::doUncacheBufferTransaction - %s", strerror(-status)); diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index e91d75467d..d517e99fda 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -55,7 +55,7 @@ namespace android { struct client_cache_t; -struct ComposerState; +class ComposerState; struct DisplayStatInfo; struct DisplayState; struct InputWindowCommands; @@ -110,7 +110,7 @@ public: /* open/close transactions. requires ACCESS_SURFACE_FLINGER permission */ virtual status_t setTransactionState( - const FrameTimelineInfo& frameTimelineInfo, const Vector& state, + const FrameTimelineInfo& frameTimelineInfo, Vector& state, const Vector& displays, uint32_t flags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 45272e7431..09f171db04 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -311,7 +311,8 @@ struct layer_state_t { bool dimmingEnabled; }; -struct ComposerState { +class ComposerState { +public: layer_state_t state; status_t write(Parcel& output) const; status_t read(const Parcel& input); diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 346b686466..54fc578ac7 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -696,7 +696,7 @@ public: } status_t setTransactionState(const FrameTimelineInfo& /*frameTimelineInfo*/, - const Vector& /*state*/, + Vector& /*state*/, const Vector& /*displays*/, uint32_t /*flags*/, const sp& /*applyToken*/, const InputWindowCommands& /*inputWindowCommands*/, diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index cfebec70cb..849fd9c7ce 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -104,6 +104,7 @@ #include #include #include +#include #include #include "BackgroundExecutor.h" @@ -3878,7 +3879,7 @@ bool SurfaceFlinger::shouldLatchUnsignaled(const sp& layer, const layer_s } status_t SurfaceFlinger::setTransactionState( - const FrameTimelineInfo& frameTimelineInfo, const Vector& states, + const FrameTimelineInfo& frameTimelineInfo, Vector& states, const Vector& displays, uint32_t flags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, @@ -3910,7 +3911,24 @@ status_t SurfaceFlinger::setTransactionState( IPCThreadState* ipc = IPCThreadState::self(); const int originPid = ipc->getCallingPid(); const int originUid = ipc->getCallingUid(); - TransactionState state{frameTimelineInfo, states, + + std::vector resolvedStates; + resolvedStates.reserve(states.size()); + for (auto& state : states) { + resolvedStates.emplace_back(std::move(state)); + auto& resolvedState = resolvedStates.back(); + if (resolvedState.state.hasBufferChanges() && resolvedState.state.hasValidBuffer() && + resolvedState.state.surface) { + resolvedState.externalTexture = + getExternalTextureFromBufferData(*resolvedState.state.bufferData, + std::to_string(resolvedState.state.layerId) + .c_str(), + transactionId); + mBufferCountTracker.increment(resolvedState.state.surface->localBinder()); + } + } + + TransactionState state{frameTimelineInfo, resolvedStates, displays, flags, applyToken, inputWindowCommands, desiredPresentTime, isAutoTimestamp, @@ -3919,11 +3937,6 @@ status_t SurfaceFlinger::setTransactionState( listenerCallbacks, originPid, originUid, transactionId}; - // Check for incoming buffer updates and increment the pending buffer count. - state.traverseStatesWithBuffers([&](const layer_state_t& state) { - mBufferCountTracker.increment(state.surface->localBinder()); - }); - if (mTransactionTracing) { mTransactionTracing->addQueuedTransaction(state); } @@ -3942,7 +3955,7 @@ status_t SurfaceFlinger::setTransactionState( } bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelineInfo, - Vector& states, + std::vector& states, const Vector& displays, uint32_t flags, const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime, bool isAutoTimestamp, @@ -3964,13 +3977,12 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin } uint32_t clientStateFlags = 0; - for (int i = 0; i < states.size(); i++) { - ComposerState& state = states.editItemAt(i); + for (auto& resolvedState : states) { clientStateFlags |= - setClientStateLocked(frameTimelineInfo, state, desiredPresentTime, isAutoTimestamp, - postTime, permissions, transactionId); - if ((flags & eAnimation) && state.state.surface) { - if (const auto layer = LayerHandle::getLayer(state.state.surface)) { + setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime, + isAutoTimestamp, postTime, permissions, transactionId); + if ((flags & eAnimation) && resolvedState.state.surface) { + if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) { using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType; mScheduler->recordLayerHistory(layer.get(), isAutoTimestamp ? 0 : desiredPresentTime, @@ -4082,7 +4094,7 @@ bool SurfaceFlinger::callingThreadHasUnscopedSurfaceFlingerAccess(bool usePermis } uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTimelineInfo, - ComposerState& composerState, + ResolvedComposerState& composerState, int64_t desiredPresentTime, bool isAutoTimestamp, int64_t postTime, uint32_t permissions, uint64_t transactionId) { @@ -4377,11 +4389,9 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime } if (what & layer_state_t::eBufferChanged) { - std::shared_ptr buffer = - getExternalTextureFromBufferData(*s.bufferData, layer->getDebugName(), - transactionId); - if (layer->setBuffer(buffer, *s.bufferData, postTime, desiredPresentTime, isAutoTimestamp, - dequeueBufferTimestamp, frameTimelineInfo)) { + if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime, + desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp, + frameTimelineInfo)) { flags |= eTraversalNeeded; } } else if (frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) { @@ -4589,7 +4599,7 @@ void SurfaceFlinger::onInitializeDisplays() { LOG_ALWAYS_FATAL_IF(token == nullptr); // reset screen orientation and use primary layer stack - Vector state; + std::vector state; Vector displays; DisplayState d; d.what = DisplayState::eDisplayProjectionChanged | @@ -6974,9 +6984,12 @@ std::shared_ptr SurfaceFlinger::getExternalTextur } if (result.error() == ClientCache::AddError::CacheFull) { - mTransactionHandler - .onTransactionQueueStalled(transactionId, bufferData.releaseBufferListener, - "Buffer processing hung due to full buffer cache"); + ALOGE("Attempted to create an ExternalTexture for layer %s but CacheFull", layerName); + + if (bufferData.releaseBufferListener) { + bufferData.releaseBufferListener->onTransactionQueueStalled( + String8("Buffer processing hung due to full buffer cache")); + } } return nullptr; diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 85c194bbb5..e09d2b5e5c 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -490,9 +490,8 @@ private: sp getPhysicalDisplayToken(PhysicalDisplayId displayId) const; status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo, - const Vector& state, - const Vector& displays, uint32_t flags, - const sp& applyToken, + Vector& state, const Vector& displays, + uint32_t flags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, @@ -702,7 +701,8 @@ private: /* * Transactions */ - bool applyTransactionState(const FrameTimelineInfo& info, Vector& state, + bool applyTransactionState(const FrameTimelineInfo& info, + std::vector& state, const Vector& displays, uint32_t flags, const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime, bool isAutoTimestamp, @@ -723,7 +723,7 @@ private: const TransactionHandler::TransactionFlushState& flushState) REQUIRES(kMainThreadContext); - uint32_t setClientStateLocked(const FrameTimelineInfo&, ComposerState&, + uint32_t setClientStateLocked(const FrameTimelineInfo&, ResolvedComposerState&, int64_t desiredPresentTime, bool isAutoTimestamp, int64_t postTime, uint32_t permissions, uint64_t transactionId) REQUIRES(mStateLock); diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp index 3418c82f9e..2f464873ea 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp @@ -310,10 +310,10 @@ TransactionState TransactionProtoParser::fromProto(const proto::TransactionState int32_t layerCount = proto.layer_changes_size(); t.states.reserve(static_cast(layerCount)); for (int i = 0; i < layerCount; i++) { - ComposerState s; + ResolvedComposerState s; s.state.what = 0; fromProto(proto.layer_changes(i), s.state); - t.states.add(s); + t.states.emplace_back(s); } int32_t displayCount = proto.display_changes_size(); diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index 25fdd26ef1..f1a6c0e2fa 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -240,13 +240,7 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, for (int j = 0; j < entry.transactions_size(); j++) { // apply transactions TransactionState transaction = parser.fromProto(entry.transactions(j)); - mFlinger.setTransactionState(transaction.frameTimelineInfo, transaction.states, - transaction.displays, transaction.flags, - transaction.applyToken, transaction.inputWindowCommands, - transaction.desiredPresentTime, - transaction.isAutoTimestamp, {}, - transaction.hasListenerCallbacks, - transaction.listenerCallbacks, transaction.id); + mFlinger.setTransactionStateInternal(transaction); } const auto frameTime = TimePoint::fromNs(entry.elapsed_realtime_nanos()); diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h index 3cbfe811ea..f1ef31d81c 100644 --- a/services/surfaceflinger/TransactionState.h +++ b/services/surfaceflinger/TransactionState.h @@ -20,17 +20,26 @@ #include #include #include +#include "renderengine/ExternalTexture.h" #include #include namespace android { +// Extends the client side composer state by resolving buffer cache ids. +class ResolvedComposerState : public ComposerState { +public: + ResolvedComposerState() = default; + ResolvedComposerState(ComposerState&& source) { state = std::move(source.state); } + std::shared_ptr externalTexture; +}; + struct TransactionState { TransactionState() = default; TransactionState(const FrameTimelineInfo& frameTimelineInfo, - const Vector& composerStates, + std::vector& composerStates, const Vector& displayStates, uint32_t transactionFlags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, @@ -38,7 +47,7 @@ struct TransactionState { bool hasListenerCallbacks, std::vector listenerCallbacks, int originPid, int originUid, uint64_t transactionId) : frameTimelineInfo(frameTimelineInfo), - states(composerStates), + states(std::move(composerStates)), displays(displayStates), flags(transactionFlags), applyToken(applyToken), @@ -57,18 +66,20 @@ struct TransactionState { // Invokes `void(const layer_state_t&)` visitor for matching layers. template void traverseStatesWithBuffers(Visitor&& visitor) const { - for (const auto& [state] : states) { - if (state.hasBufferChanges() && state.hasValidBuffer() && state.surface) { - visitor(state); + for (const auto& state : states) { + if (state.state.hasBufferChanges() && state.state.hasValidBuffer() && + state.state.surface) { + visitor(state.state); } } } template void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) const { - for (const auto& [state] : states) { - if (state.hasBufferChanges() && state.hasValidBuffer() && state.surface) { - if (!visitor(state)) return; + for (const auto& state : states) { + if (state.state.hasBufferChanges() && state.state.hasValidBuffer() && + state.state.surface) { + if (!visitor(state.state)) return; } } } @@ -79,8 +90,8 @@ struct TransactionState { bool isFrameActive() const { if (!displays.empty()) return true; - for (const auto& [state] : states) { - if (state.frameRateCompatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE) { + for (const auto& state : states) { + if (state.state.frameRateCompatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE) { return true; } } @@ -89,7 +100,7 @@ struct TransactionState { } FrameTimelineInfo frameTimelineInfo; - Vector states; + std::vector states; Vector displays; uint32_t flags; sp applyToken; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index e55586774f..cc0b012391 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -736,12 +736,14 @@ public: return mFlinger->mTransactionHandler.mPendingTransactionQueues; } - auto setTransactionState( - const FrameTimelineInfo &frameTimelineInfo, const Vector &states, - const Vector &displays, uint32_t flags, const sp &applyToken, - const InputWindowCommands &inputWindowCommands, int64_t desiredPresentTime, - bool isAutoTimestamp, const client_cache_t &uncacheBuffer, bool hasListenerCallbacks, - std::vector &listenerCallbacks, uint64_t transactionId) { + auto setTransactionState(const FrameTimelineInfo &frameTimelineInfo, + Vector &states, const Vector &displays, + uint32_t flags, const sp &applyToken, + const InputWindowCommands &inputWindowCommands, + int64_t desiredPresentTime, bool isAutoTimestamp, + const client_cache_t &uncacheBuffer, bool hasListenerCallbacks, + std::vector &listenerCallbacks, + uint64_t transactionId) { return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken, inputWindowCommands, desiredPresentTime, isAutoTimestamp, uncacheBuffer, hasListenerCallbacks, diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 7f471bc8b8..935d95339f 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -427,18 +427,24 @@ public: return mFlinger->mTransactionHandler.mPendingTransactionCount.load(); } - auto setTransactionState( - const FrameTimelineInfo& frameTimelineInfo, const Vector& states, - const Vector& displays, uint32_t flags, const sp& applyToken, - const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, - bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, - std::vector& listenerCallbacks, uint64_t transactionId) { + auto setTransactionState(const FrameTimelineInfo& frameTimelineInfo, + Vector& states, const Vector& displays, + uint32_t flags, const sp& applyToken, + const InputWindowCommands& inputWindowCommands, + int64_t desiredPresentTime, bool isAutoTimestamp, + const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, + std::vector& listenerCallbacks, + uint64_t transactionId) { return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken, inputWindowCommands, desiredPresentTime, isAutoTimestamp, uncacheBuffer, hasListenerCallbacks, listenerCallbacks, transactionId); } + auto setTransactionStateInternal(TransactionState& transaction) { + return mFlinger->mTransactionHandler.queueTransaction(std::move(transaction)); + } + auto flushTransactionQueues() { return FTL_FAKE_GUARD(kMainThreadContext, mFlinger->flushTransactionQueues(kVsyncId)); } diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index 9888f002fb..488d4a9c58 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -32,6 +32,7 @@ #include "FrontEnd/TransactionHandler.h" #include "TestableSurfaceFlinger.h" +#include "TransactionState.h" #include "mock/MockEventThread.h" #include "mock/MockVsyncController.h" @@ -359,13 +360,23 @@ public: EXPECT_TRUE(mFlinger.getTransactionQueue().isEmpty()); EXPECT_EQ(0u, mFlinger.getPendingTransactionQueue().size()); - for (const auto& transaction : transactions) { - mFlinger.setTransactionState(transaction.frameTimelineInfo, transaction.states, - transaction.displays, transaction.flags, - transaction.applyToken, transaction.inputWindowCommands, - transaction.desiredPresentTime, - transaction.isAutoTimestamp, transaction.uncacheBuffer, - mHasListenerCallbacks, mCallbacks, transaction.id); + for (auto transaction : transactions) { + std::vector resolvedStates; + resolvedStates.reserve(transaction.states.size()); + for (auto& state : transaction.states) { + resolvedStates.emplace_back(std::move(state)); + } + + TransactionState transactionState(transaction.frameTimelineInfo, resolvedStates, + transaction.displays, transaction.flags, + transaction.applyToken, + transaction.inputWindowCommands, + transaction.desiredPresentTime, + transaction.isAutoTimestamp, + transaction.uncacheBuffer, systemTime(), 0, + mHasListenerCallbacks, mCallbacks, getpid(), + static_cast(getuid()), transaction.id); + mFlinger.setTransactionStateInternal(transactionState); } mFlinger.flushTransactionQueues(); EXPECT_TRUE(mFlinger.getTransactionQueue().isEmpty()); diff --git a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp index 14e1aac793..b6427c0ffb 100644 --- a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp @@ -46,14 +46,14 @@ TEST(TransactionProtoParserTest, parse) { size_t layerCount = 2; t1.states.reserve(layerCount); for (uint32_t i = 0; i < layerCount; i++) { - ComposerState s; + ResolvedComposerState s; if (i == 1) { layer.parentSurfaceControlForChild = sp::make(SurfaceComposerClient::getDefault(), layerHandle, 42, "#42"); } s.state = layer; - t1.states.add(s); + t1.states.emplace_back(s); } size_t displayCount = 2; diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp index 2dbcfbdb18..482c3a8e50 100644 --- a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp @@ -112,16 +112,16 @@ protected: { TransactionState transaction; transaction.id = 50; - ComposerState layerState; + ResolvedComposerState layerState; layerState.state.surface = fakeLayerHandle; layerState.state.what = layer_state_t::eLayerChanged; layerState.state.z = 42; - transaction.states.add(layerState); - ComposerState childState; + transaction.states.emplace_back(layerState); + ResolvedComposerState childState; childState.state.surface = fakeChildLayerHandle; childState.state.what = layer_state_t::eLayerChanged; childState.state.z = 43; - transaction.states.add(childState); + transaction.states.emplace_back(childState); mTracing.addQueuedTransaction(transaction); std::vector transactions; @@ -138,12 +138,12 @@ protected: { TransactionState transaction; transaction.id = 51; - ComposerState layerState; + ResolvedComposerState layerState; layerState.state.surface = fakeLayerHandle; layerState.state.what = layer_state_t::eLayerChanged | layer_state_t::ePositionChanged; layerState.state.z = 41; layerState.state.x = 22; - transaction.states.add(layerState); + transaction.states.emplace_back(layerState); mTracing.addQueuedTransaction(transaction); std::vector transactions; @@ -247,16 +247,16 @@ protected: { TransactionState transaction; transaction.id = 50; - ComposerState layerState; + ResolvedComposerState layerState; layerState.state.surface = fakeLayerHandle; layerState.state.what = layer_state_t::eLayerChanged; layerState.state.z = 42; - transaction.states.add(layerState); - ComposerState mirrorState; + transaction.states.emplace_back(layerState); + ResolvedComposerState mirrorState; mirrorState.state.surface = fakeMirrorLayerHandle; mirrorState.state.what = layer_state_t::eLayerChanged; mirrorState.state.z = 43; - transaction.states.add(mirrorState); + transaction.states.emplace_back(mirrorState); mTracing.addQueuedTransaction(transaction); std::vector transactions; -- GitLab From 7d04c4bcd2a410f8f380e1c43ce332c3b514d804 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 28 Oct 2022 19:23:26 +0000 Subject: [PATCH 0477/1310] Add unit tests for external stylus fusion Add unit tests to demystify the external stylus fusion process and to document its current behavior. A fused external stylus reports pressure through a separate input device. That pressure information is then fused with touches from the touchscreen. Bug: 246394583 Test: atest inputflinger_tests Change-Id: I289e4470eb9383bc9e203bff8f76609b0d533e51 --- .../reader/mapper/TouchInputMapper.cpp | 9 +- .../reader/mapper/TouchInputMapper.h | 7 + .../inputflinger/tests/InputReader_test.cpp | 391 ++++++++++++++++++ .../tests/TestInputListenerMatchers.h | 4 +- 4 files changed, 401 insertions(+), 10 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 3947cf7b9f..286e1f53b2 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -31,13 +31,6 @@ namespace android { // --- Constants --- -// Maximum amount of latency to add to touch events while waiting for data from an -// external stylus. -static constexpr nsecs_t EXTERNAL_STYLUS_DATA_TIMEOUT = ms2ns(72); - -// Maximum amount of time to wait on touch data before pushing out new pressure data. -static constexpr nsecs_t TOUCH_DATA_TIMEOUT = ms2ns(20); - // Artificial latency on synthetic events created from stylus data without corresponding touch // data. static constexpr nsecs_t STYLUS_DATA_LATENCY = ms2ns(10); @@ -1760,7 +1753,7 @@ std::list TouchInputMapper::timeoutExpired(nsecs_t when) { out += dispatchPointerGestures(when, readTime, 0 /*policyFlags*/, true /*isTimeout*/); } } else if (mDeviceMode == DeviceMode::DIRECT) { - if (mExternalStylusFusionTimeout < when) { + if (mExternalStylusFusionTimeout <= when) { out += processRawTouches(true /*timeout*/); } else if (mExternalStylusFusionTimeout != LLONG_MAX) { getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout); diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index d5e4d5ae28..34cecf99db 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -27,6 +27,13 @@ namespace android { +// Maximum amount of latency to add to touch events while waiting for data from an +// external stylus. +static constexpr nsecs_t EXTERNAL_STYLUS_DATA_TIMEOUT = ms2ns(72); + +// Maximum amount of time to wait on touch data before pushing out new pressure data. +static constexpr nsecs_t TOUCH_DATA_TIMEOUT = ms2ns(20); + /* Raw axis information from the driver. */ struct RawPointerAxes { RawAbsoluteAxisInfo x{}; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index e4e22df5de..63bf633ff8 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -1343,6 +1343,8 @@ protected: int32_t mGlobalMetaState; bool mUpdateGlobalMetaStateWasCalled; int32_t mGeneration; + std::optional mRequestedTimeout; + std::vector mExternalStylusDevices; public: FakeInputReaderContext(InputReader* reader) @@ -1376,6 +1378,29 @@ protected: mGeneration = ContextImpl::bumpGeneration(); return mGeneration; } + + void requestTimeoutAtTime(nsecs_t when) override { mRequestedTimeout = when; } + + void assertTimeoutWasRequested(nsecs_t when) { + ASSERT_TRUE(mRequestedTimeout) << "Expected timeout at time " << when + << " but there was no timeout requested."; + ASSERT_EQ(when, *mRequestedTimeout); + mRequestedTimeout.reset(); + } + + void assertTimeoutWasNotRequested() { + ASSERT_FALSE(mRequestedTimeout) << "Expected no timeout to have been requested," + " but one was requested at time " + << *mRequestedTimeout; + } + + void getExternalStylusDevices(std::vector& outDevices) override { + outDevices = mExternalStylusDevices; + } + + void setExternalStylusDevices(std::vector&& devices) { + mExternalStylusDevices = devices; + } } mFakeContext; friend class InputReaderTest; @@ -3365,6 +3390,16 @@ protected: mReader->loopOnce(); } + std::list handleTimeout(InputMapper& mapper, nsecs_t when) { + std::list generatedArgs = mapper.timeoutExpired(when); + for (const NotifyArgs& args : generatedArgs) { + mFakeListener->notify(args); + } + // Loop the reader to flush the input listener queue. + mReader->loopOnce(); + return generatedArgs; + } + static void assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source, float min, float max, float flat, float fuzz) { const InputDeviceInfo::MotionRange* range = info.getMotionRange(axis, source); @@ -7459,6 +7494,362 @@ TEST_F(TouchDisplayProjectionTest, EmitsTouchDownAfterEnteringPhysicalDisplay) { } } +// --- ExternalStylusFusionTest --- + +class ExternalStylusFusionTest : public SingleTouchInputMapperTest { +public: + SingleTouchInputMapper& initializeInputMapperWithExternalStylus() { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + prepareAxes(POSITION); + auto& mapper = addMapperAndConfigure(); + + mStylusState.when = ARBITRARY_TIME; + mStylusState.pressure = 0.f; + mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + mReader->getContext()->setExternalStylusDevices({mExternalStylusDeviceInfo}); + configureDevice(InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE); + processExternalStylusState(mapper); + return mapper; + } + + std::list processExternalStylusState(InputMapper& mapper) { + std::list generatedArgs = mapper.updateExternalStylusState(mStylusState); + for (const NotifyArgs& args : generatedArgs) { + mFakeListener->notify(args); + } + // Loop the reader to flush the input listener queue. + mReader->loopOnce(); + return generatedArgs; + } + +protected: + StylusState mStylusState{}; + static constexpr uint32_t EXPECTED_SOURCE = + AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_BLUETOOTH_STYLUS; + + void testStartFusedStylusGesture(SingleTouchInputMapper& mapper) { + auto toolTypeSource = + AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)); + + // The first pointer is withheld. + processDown(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasRequested( + ARBITRARY_TIME + EXTERNAL_STYLUS_DATA_TIMEOUT)); + + // The external stylus reports pressure. The withheld finger pointer is released as a + // stylus. + mStylusState.pressure = 1.f; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_DOWN)))); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + + // Subsequent pointer events are not withheld. + processMove(mapper, 101, 201); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE)))); + + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + } + + void testSuccessfulFusionGesture(SingleTouchInputMapper& mapper) { + ASSERT_NO_FATAL_FAILURE(testStartFusedStylusGesture(mapper)); + + // Releasing the touch pointer ends the gesture. + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(EXPECTED_SOURCE), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + + mStylusState.pressure = 0.f; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + } + + void testUnsuccessfulFusionGesture(SingleTouchInputMapper& mapper) { + auto toolTypeSource = + AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)); + + // The first pointer is withheld when an external stylus is connected, + // and a timeout is requested. + processDown(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasRequested( + ARBITRARY_TIME + EXTERNAL_STYLUS_DATA_TIMEOUT)); + + // If the timeout expires early, it is requested again. + handleTimeout(mapper, ARBITRARY_TIME + 1); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasRequested( + ARBITRARY_TIME + EXTERNAL_STYLUS_DATA_TIMEOUT)); + + // When the timeout expires, the withheld touch is released as a finger pointer. + handleTimeout(mapper, ARBITRARY_TIME + EXTERNAL_STYLUS_DATA_TIMEOUT); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_DOWN)))); + + // Subsequent pointer events are not withheld. + processMove(mapper, 101, 201); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE)))); + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_UP)))); + + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + } + +private: + InputDeviceInfo mExternalStylusDeviceInfo{}; +}; + +TEST_F(ExternalStylusFusionTest, UsesBluetoothStylusSource) { + SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); + ASSERT_EQ(EXPECTED_SOURCE, mapper.getSources()); +} + +TEST_F(ExternalStylusFusionTest, UnsuccessfulFusion) { + SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); + ASSERT_NO_FATAL_FAILURE(testUnsuccessfulFusionGesture(mapper)); +} + +TEST_F(ExternalStylusFusionTest, SuccessfulFusion_TouchFirst) { + SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); + ASSERT_NO_FATAL_FAILURE(testSuccessfulFusionGesture(mapper)); +} + +// Test a successful stylus fusion gesture where the pressure is reported by the external +// before the touch is reported by the touchscreen. +TEST_F(ExternalStylusFusionTest, SuccessfulFusion_PressureFirst) { + SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); + auto toolTypeSource = + AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)); + + // The external stylus reports pressure first. It is ignored for now. + mStylusState.pressure = 1.f; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + + // When the touch goes down afterwards, it is reported as a stylus pointer. + processDown(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_DOWN)))); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + + processMove(mapper, 101, 201); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE)))); + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_UP)))); + + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + +TEST_F(ExternalStylusFusionTest, FusionIsRepeatedForEachNewGesture) { + SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); + + ASSERT_NO_FATAL_FAILURE(testSuccessfulFusionGesture(mapper)); + ASSERT_NO_FATAL_FAILURE(testUnsuccessfulFusionGesture(mapper)); + + ASSERT_NO_FATAL_FAILURE(testSuccessfulFusionGesture(mapper)); + ASSERT_NO_FATAL_FAILURE(testSuccessfulFusionGesture(mapper)); + ASSERT_NO_FATAL_FAILURE(testUnsuccessfulFusionGesture(mapper)); + ASSERT_NO_FATAL_FAILURE(testUnsuccessfulFusionGesture(mapper)); +} + +TEST_F(ExternalStylusFusionTest, FusedPointerReportsPressureChanges) { + SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); + auto toolTypeSource = + AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)); + + mStylusState.pressure = 0.8f; + processExternalStylusState(mapper); + processDown(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithPressure(0.8f)))); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + + // The external stylus reports a pressure change. We wait for some time for a touch event. + mStylusState.pressure = 0.6f; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE( + mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT)); + + // If a touch is reported within the timeout, it reports the updated pressure. + processMove(mapper, 101, 201); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithPressure(0.6f)))); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + + // There is another pressure change. + mStylusState.pressure = 0.5f; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE( + mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT)); + + // If a touch is not reported within the timeout, a move event is generated to report + // the new pressure. + handleTimeout(mapper, ARBITRARY_TIME + TOUCH_DATA_TIMEOUT); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithPressure(0.5f)))); + + // If a zero pressure is reported before the touch goes up, the previous pressure value is + // repeated indefinitely. + mStylusState.pressure = 0.0f; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE( + mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT)); + processMove(mapper, 102, 202); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithPressure(0.5f)))); + processMove(mapper, 103, 203); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithPressure(0.5f)))); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(EXPECTED_SOURCE), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + +TEST_F(ExternalStylusFusionTest, FusedPointerReportsToolTypeChanges) { + SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); + auto source = WithSource(EXPECTED_SOURCE); + + mStylusState.pressure = 1.f; + mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_ERASER; + processExternalStylusState(mapper); + processDown(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(AMOTION_EVENT_TOOL_TYPE_ERASER)))); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + + // The external stylus reports a tool change. We wait for some time for a touch event. + mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE( + mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT)); + + // If a touch is reported within the timeout, it reports the updated pressure. + processMove(mapper, 101, 201); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + + // There is another tool type change. + mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE( + mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT)); + + // If a touch is not reported within the timeout, a move event is generated to report + // the new tool type. + handleTimeout(mapper, ARBITRARY_TIME + TOUCH_DATA_TIMEOUT); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)))); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)))); + + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + +TEST_F(ExternalStylusFusionTest, FusedPointerReportsButtons) { + SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); + auto toolTypeSource = + AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)); + + ASSERT_NO_FATAL_FAILURE(testStartFusedStylusGesture(mapper)); + + // The external stylus reports a button change. We wait for some time for a touch event. + mStylusState.buttons = AMOTION_EVENT_BUTTON_STYLUS_PRIMARY; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE( + mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT)); + + // If a touch is reported within the timeout, it reports the updated button state. + processMove(mapper, 101, 201); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + + // The button is now released. + mStylusState.buttons = 0; + processExternalStylusState(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE( + mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT)); + + // If a touch is not reported within the timeout, a move event is generated to report + // the new button state. + handleTimeout(mapper, ARBITRARY_TIME + TOUCH_DATA_TIMEOUT); + // TODO(prabirmsp): Fix fused stylus button releases being handled inconsistently. + // The button release event should be sent here, but isn't. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + + processUp(mapper); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithButtonState(0)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0)))); + + ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + // --- MultiTouchInputMapperTest --- class MultiTouchInputMapperTest : public TouchInputMapperTest { diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h index 5107af7e27..5470bee685 100644 --- a/services/inputflinger/tests/TestInputListenerMatchers.h +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -70,8 +70,8 @@ MATCHER_P2(WithCoords, x, y, "InputEvent with specified coords") { MATCHER_P(WithPressure, pressure, "InputEvent with specified pressure") { const auto argPressure = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE); - *result_listener << "expected pressure " << pressure << ", but got " << pressure; - return argPressure; + *result_listener << "expected pressure " << pressure << ", but got " << argPressure; + return argPressure == pressure; } MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") { -- GitLab From 484d55ace1ea8f93f2e2be51285e1092ee5979a7 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 14 Oct 2022 23:17:16 +0000 Subject: [PATCH 0478/1310] Add integration tests to verify the behavior of a fused external stylus A fused external stylus reports pressure through a separate input device. That pressure information is then fused with touches from the touchscreen. Bug: 246394583 Test: atest inputflinger_tests Change-Id: I2c00fca0f5e3d5214ebb2d0af04ed4efe14de9f7 --- .../inputflinger/tests/InputReader_test.cpp | 121 +++++++++++++++++- .../inputflinger/tests/TestInputListener.cpp | 14 +- .../inputflinger/tests/TestInputListener.h | 6 +- .../tests/TestInputListenerMatchers.h | 5 + services/inputflinger/tests/UinputDevice.cpp | 19 +++ services/inputflinger/tests/UinputDevice.h | 26 +++- 6 files changed, 180 insertions(+), 11 deletions(-) diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 63bf633ff8..56c1ade820 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -2523,7 +2523,8 @@ TEST_F(InputReaderIntegrationTest, SendsGearDownAndUpToInputListener) { ASSERT_EQ(BTN_GEAR_UP, keyArgs.scanCode); } -// --- TouchProcessTest --- +// --- TouchIntegrationTest --- + class TouchIntegrationTest : public InputReaderIntegrationTest { protected: const std::string UNIQUE_ID = "local:0"; @@ -2940,6 +2941,124 @@ TEST_F(TouchIntegrationTest, StylusButtonsWithinTouchGesture) { WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0)))); } +// --- ExternalStylusIntegrationTest --- + +// Verify the behavior of an external stylus. An external stylus can report pressure or button +// data independently of the touchscreen, which is then sent as a MotionEvent as part of an +// ongoing stylus gesture that is being emitted by the touchscreen. +using ExternalStylusIntegrationTest = TouchIntegrationTest; + +TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureReported) { + const Point centerPoint = mDevice->getCenterPoint(); + + // Create an external stylus capable of reporting pressure data that + // should be fused with a touch pointer. + std::unique_ptr stylus = + createUinputDevice(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); + const auto stylusInfo = findDeviceByName(stylus->getName()); + ASSERT_TRUE(stylusInfo); + + ASSERT_EQ(AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, stylusInfo->getSources()); + + const auto touchscreenId = mDeviceInfo.getId(); + + // Set a pressure value on the stylus. It doesn't generate any events. + const auto& RAW_PRESSURE_MAX = UinputExternalStylusWithPressure::RAW_PRESSURE_MAX; + stylus->setPressure(100); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled()); + + // Start a finger gesture, and ensure it shows up as stylus gesture + // with the pressure set by the external stylus. + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_FINGER); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0), + WithDeviceId(touchscreenId), WithPressure(100.f / RAW_PRESSURE_MAX)))); + + // Change the pressure on the external stylus, and ensure the touchscreen generates a MOVE + // event with the updated pressure. + stylus->setPressure(200); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0), + WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX)))); + + // The external stylus did not generate any events. + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled()); +} + +TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureNotReported) { + const Point centerPoint = mDevice->getCenterPoint(); + + // Create an external stylus capable of reporting pressure data that + // should be fused with a touch pointer. + std::unique_ptr stylus = + createUinputDevice(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); + const auto stylusInfo = findDeviceByName(stylus->getName()); + ASSERT_TRUE(stylusInfo); + + ASSERT_EQ(AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, stylusInfo->getSources()); + + const auto touchscreenId = mDeviceInfo.getId(); + + // Set a pressure value of 0 on the stylus. It doesn't generate any events. + const auto& RAW_PRESSURE_MAX = UinputExternalStylusWithPressure::RAW_PRESSURE_MAX; + stylus->setPressure(0); + + // Start a finger gesture. The touch device will withhold generating any touches for + // up to 72 milliseconds while waiting for pressure data from the external stylus. + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_FINGER); + mDevice->sendDown(centerPoint); + auto waitUntil = std::chrono::system_clock::now() + + std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT)); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled(waitUntil)); + + // Since the external stylus did not report a pressure value within the timeout, + // it shows up as a finger pointer. + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithDeviceId(touchscreenId), + WithPressure(1.f)))); + + // Change the pressure on the external stylus. Since the pressure was not present at the start + // of the gesture, it is ignored for now. + stylus->setPressure(200); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled()); + + // Finish the finger gesture. + mDevice->sendTrackingId(INVALID_TRACKING_ID); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)))); + + // Start a new gesture. Since we have a valid pressure value, it shows up as a stylus. + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_FINGER); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0), + WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX)))); + + // The external stylus did not generate any events. + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled()); +} + // --- InputDeviceTest --- class InputDeviceTest : public testing::Test { protected: diff --git a/services/inputflinger/tests/TestInputListener.cpp b/services/inputflinger/tests/TestInputListener.cpp index 5e47b8019b..a1299eedb4 100644 --- a/services/inputflinger/tests/TestInputListener.cpp +++ b/services/inputflinger/tests/TestInputListener.cpp @@ -82,9 +82,9 @@ void TestInputListener::assertNotifyMotionWasCalled( ASSERT_THAT(outEventArgs, matcher); } -void TestInputListener::assertNotifyMotionWasNotCalled() { +void TestInputListener::assertNotifyMotionWasNotCalled(std::optional waitUntil) { ASSERT_NO_FATAL_FAILURE( - assertNotCalled("notifyMotion() should not be called.")); + assertNotCalled("notifyMotion() should not be called.", waitUntil)); } void TestInputListener::assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs) { @@ -139,14 +139,16 @@ void TestInputListener::assertCalled(NotifyArgsType* outEventArgs, std::string m } template -void TestInputListener::assertNotCalled(std::string message) { +void TestInputListener::assertNotCalled(std::string message, std::optional waitUntil) { std::unique_lock lock(mLock); base::ScopedLockAssertion assumeLocked(mLock); std::vector& queue = std::get>(mQueues); - const bool eventReceived = - mCondition.wait_for(lock, mEventDidNotHappenTimeout, - [&queue]() REQUIRES(mLock) { return !queue.empty(); }); + const auto time = + waitUntil.value_or(std::chrono::system_clock::now() + mEventDidNotHappenTimeout); + const bool eventReceived = mCondition.wait_until(lock, time, [&queue]() REQUIRES(mLock) { + return !queue.empty(); + }); if (eventReceived) { FAIL() << "Unexpected event: " << message.c_str(); } diff --git a/services/inputflinger/tests/TestInputListener.h b/services/inputflinger/tests/TestInputListener.h index 87752e1487..c53f8e0497 100644 --- a/services/inputflinger/tests/TestInputListener.h +++ b/services/inputflinger/tests/TestInputListener.h @@ -33,6 +33,8 @@ public: std::chrono::milliseconds eventDidNotHappenTimeout = 0ms); virtual ~TestInputListener(); + using TimePoint = std::chrono::time_point; + void assertNotifyConfigurationChangedWasCalled( NotifyConfigurationChangedArgs* outEventArgs = nullptr); @@ -52,7 +54,7 @@ public: void assertNotifyMotionWasCalled(const ::testing::Matcher& matcher); - void assertNotifyMotionWasNotCalled(); + void assertNotifyMotionWasNotCalled(std::optional waitUntil = {}); void assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs = nullptr); @@ -66,7 +68,7 @@ private: void assertCalled(NotifyArgsType* outEventArgs, std::string message); template - void assertNotCalled(std::string message); + void assertNotCalled(std::string message, std::optional timeout = {}); template void addToQueue(const NotifyArgsType* args); diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h index 5470bee685..438bb9a5b8 100644 --- a/services/inputflinger/tests/TestInputListenerMatchers.h +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -55,6 +55,11 @@ MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") { return arg.displayId == displayId; } +MATCHER_P(WithDeviceId, deviceId, "InputEvent with specified deviceId") { + *result_listener << "expected deviceId " << deviceId << ", but got " << arg.deviceId; + return arg.deviceId == deviceId; +} + MATCHER_P(WithKeyCode, keyCode, "KeyEvent with specified key code") { *result_listener << "expected key code " << keyCode << ", but got " << arg.keyCode; return arg.keyCode == keyCode; diff --git a/services/inputflinger/tests/UinputDevice.cpp b/services/inputflinger/tests/UinputDevice.cpp index bc695b8bd8..1051aa93e5 100644 --- a/services/inputflinger/tests/UinputDevice.cpp +++ b/services/inputflinger/tests/UinputDevice.cpp @@ -138,6 +138,25 @@ UinputSteamController::UinputSteamController() UinputExternalStylus::UinputExternalStylus() : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {BTN_STYLUS, BTN_STYLUS2, BTN_STYLUS3}) {} +// --- UinputExternalStylusWithPressure --- + +UinputExternalStylusWithPressure::UinputExternalStylusWithPressure() + : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {BTN_STYLUS, BTN_STYLUS2, BTN_STYLUS3}) {} + +void UinputExternalStylusWithPressure::configureDevice(int fd, uinput_user_dev* device) { + UinputKeyboard::configureDevice(fd, device); + + ioctl(fd, UI_SET_EVBIT, EV_ABS); + ioctl(fd, UI_SET_ABSBIT, ABS_PRESSURE); + device->absmin[ABS_PRESSURE] = RAW_PRESSURE_MIN; + device->absmax[ABS_PRESSURE] = RAW_PRESSURE_MAX; +} + +void UinputExternalStylusWithPressure::setPressure(int32_t pressure) { + injectEvent(EV_ABS, ABS_PRESSURE, pressure); + injectEvent(EV_SYN, SYN_REPORT, 0); +} + // --- UinputTouchScreen --- UinputTouchScreen::UinputTouchScreen(const Rect& size) diff --git a/services/inputflinger/tests/UinputDevice.h b/services/inputflinger/tests/UinputDevice.h index d661bd36d3..ad6125b47a 100644 --- a/services/inputflinger/tests/UinputDevice.h +++ b/services/inputflinger/tests/UinputDevice.h @@ -89,9 +89,9 @@ protected: explicit UinputKeyboard(const char* name, int16_t productId = PRODUCT_ID, std::initializer_list keys = {}); -private: void configureDevice(int fd, uinput_user_dev* device) override; +private: std::set mKeys; }; @@ -143,13 +143,35 @@ private: explicit UinputExternalStylus(); }; +// --- UinputExternalStylusWithPressure --- + +// A stylus that reports button presses and pressure values. +class UinputExternalStylusWithPressure : public UinputKeyboard { +public: + static constexpr const char* DEVICE_NAME = "Test Uinput External Stylus With Pressure"; + static constexpr int16_t PRODUCT_ID = 46; + + static constexpr int32_t RAW_PRESSURE_MIN = 0; + static constexpr int32_t RAW_PRESSURE_MAX = 255; + + void setPressure(int32_t pressure); + + template + friend std::unique_ptr createUinputDevice(Ts... args); + +private: + void configureDevice(int fd, uinput_user_dev* device) override; + + explicit UinputExternalStylusWithPressure(); +}; + // --- UinputTouchScreen --- // A multi-touch touchscreen device with specific size that also supports styluses. class UinputTouchScreen : public UinputDevice { public: static constexpr const char* DEVICE_NAME = "Test Uinput Touch Screen"; - static constexpr int16_t PRODUCT_ID = 46; + static constexpr int16_t PRODUCT_ID = 47; static const int32_t RAW_TOUCH_MIN = 0; static const int32_t RAW_TOUCH_MAX = 31; -- GitLab From b54b40bb752f0f4d6a2ec3e5100d7d72c09ea40b Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Mon, 7 Nov 2022 13:11:54 -0500 Subject: [PATCH 0479/1310] FTL: Add wrapper for shared_mutex std::shared_mutex on Android is missing capabilities necessary for threading annotations. b/135688034 tracks adding them, but is blocked on b/175635923. In the meantime, add a simple wrapper so that we can start using annotations. Only add the methods we currently need. Other methods can be added as needed. Bug: 185536303 Test: ftl_test Change-Id: Ic7c2152bc7e46b31eecdba42fac1126b26aafd60 --- include/ftl/shared_mutex.h | 47 ++++++++++++++++++++++++++ libs/ftl/Android.bp | 1 + libs/ftl/shared_mutex_test.cpp | 60 ++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 include/ftl/shared_mutex.h create mode 100644 libs/ftl/shared_mutex_test.cpp diff --git a/include/ftl/shared_mutex.h b/include/ftl/shared_mutex.h new file mode 100644 index 0000000000..146f5ba4a9 --- /dev/null +++ b/include/ftl/shared_mutex.h @@ -0,0 +1,47 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +namespace android::ftl { + +// Wrapper around std::shared_mutex to provide capabilities for thread-safety +// annotations. +// TODO(b/257958323): This class is no longer needed once b/135688034 is fixed (currently blocked on +// b/175635923). +class [[clang::capability("shared_mutex")]] SharedMutex final { + public: + [[clang::acquire_capability()]] void lock() { + mutex_.lock(); + } + [[clang::release_capability()]] void unlock() { + mutex_.unlock(); + } + + [[clang::acquire_shared_capability()]] void lock_shared() { + mutex_.lock_shared(); + } + [[clang::release_shared_capability()]] void unlock_shared() { + mutex_.unlock_shared(); + } + + private: + std::shared_mutex mutex_; +}; + +} // namespace android::ftl diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp index 81113bc211..df0b271a9b 100644 --- a/libs/ftl/Android.bp +++ b/libs/ftl/Android.bp @@ -24,6 +24,7 @@ cc_test { "match_test.cpp", "non_null_test.cpp", "optional_test.cpp", + "shared_mutex_test.cpp", "small_map_test.cpp", "small_vector_test.cpp", "static_vector_test.cpp", diff --git a/libs/ftl/shared_mutex_test.cpp b/libs/ftl/shared_mutex_test.cpp new file mode 100644 index 0000000000..6da7061ae0 --- /dev/null +++ b/libs/ftl/shared_mutex_test.cpp @@ -0,0 +1,60 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +namespace android::test { + +TEST(SharedMutex, SharedLock) { + ftl::SharedMutex mutex; + std::shared_lock shared_lock(mutex); + + { std::shared_lock shared_lock2(mutex); } +} + +TEST(SharedMutex, ExclusiveLock) { + ftl::SharedMutex mutex; + std::unique_lock unique_lock(mutex); +} + +TEST(SharedMutex, Annotations) { + struct { + void foo() FTL_ATTRIBUTE(requires_shared_capability(mutex)) { num++; } + void bar() FTL_ATTRIBUTE(requires_capability(mutex)) { num++; } + void baz() { + std::shared_lock shared_lock(mutex); + num++; + } + ftl::SharedMutex mutex; + int num = 0; + + } s; + + { + // TODO(b/257958323): Use an RAII class instead of locking manually. + s.mutex.lock_shared(); + s.foo(); + s.baz(); + s.mutex.unlock_shared(); + } + s.mutex.lock(); + s.bar(); + s.mutex.unlock(); +} + +} // namespace android::test -- GitLab From 09f251b449e35af00ab0f703e004561f33aac1c3 Mon Sep 17 00:00:00 2001 From: Carlos Martinez Romero Date: Thu, 10 Nov 2022 00:29:14 +0000 Subject: [PATCH 0480/1310] Updated the default value of debug.sf.enable_gl_backpressure to 1. Bug: 233818658 Test: Manual Change-Id: I7cb62237804c3e04bb4525f9ff40cd842b2b4d2e --- services/surfaceflinger/SurfaceFlinger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index a4fb0dc774..57248008f7 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -369,7 +369,7 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI int debugDdms = atoi(value); ALOGI_IF(debugDdms, "DDMS debugging not supported"); - property_get("debug.sf.enable_gl_backpressure", value, "0"); + property_get("debug.sf.enable_gl_backpressure", value, "1"); mPropagateBackpressureClientComposition = atoi(value); ALOGI_IF(mPropagateBackpressureClientComposition, "Enabling backpressure propagation for Client Composition"); -- GitLab From e24d78f129d9de8be75dfeb4e3d3e81d5571a93a Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Tue, 20 Sep 2022 16:38:19 -0400 Subject: [PATCH 0481/1310] AidlComposer: use a reader/writer per display In order to call HWC from different threads, with one thread per display, separate commands and their results per display. AidlComposer: - Add a maps from display to a ComposerClientReader and a ComposerClientWriter. Each reader/writer will be used by a single display. AidlComposer is generally threadsafe, except for these objects. (The other members are pointers to proxy binders, which can have their methods safely called from multiple threads.) Use an ftl::SharedMutex to guard access to the maps. The client is responsible for ensuring they do not attempt to access the same reader/writer concurrently from multiple threads. Different threads can access different readers/writers concurrently, but the mutex ensures that adding or deleting an entry does not impact access to the objects. - Add a `Display` parameter to execute[Commands] and resetCommands, so that it only affects the intended writer. The callers already know which Display they care about, so pass it in. - If no displays support DisplayCapability.MULTI_THREADED_PRESENT, use a single reader for all displays. This is required for backwards compatibility. [Hidl]ComposerHal, MockComposer: - update APIs for executeCommands and resetCommands - implement onHotPlug[Connect/Disconnect] HWComposer, fuzzer: - pass the display to new APIs Bug: 241285491 Test: make, boot Change-Id: I2b62e4965b12b3c653e6c00f9f6ab4f48b506b18 --- .../DisplayHardware/AidlComposerHal.cpp | 542 ++++++++++++++---- .../DisplayHardware/AidlComposerHal.h | 44 +- .../DisplayHardware/ComposerHal.h | 6 +- .../DisplayHardware/HWComposer.cpp | 5 +- .../DisplayHardware/HidlComposerHal.cpp | 7 +- .../DisplayHardware/HidlComposerHal.h | 6 +- .../surfaceflinger_displayhardware_fuzzer.cpp | 4 +- .../tests/unittests/HWComposerTest.cpp | 1 + .../mock/DisplayHardware/MockComposer.h | 6 +- 9 files changed, 507 insertions(+), 114 deletions(-) diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp index 0e41962afd..eff5130f5a 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp @@ -230,6 +230,8 @@ AidlComposer::AidlComposer(const std::string& serviceName) { return; } + addReader(translate(kSingleReaderKey)); + ALOGI("Loaded AIDL composer3 HAL service"); } @@ -298,12 +300,19 @@ void AidlComposer::registerCallback(HWC2::ComposerCallback& callback) { } } -void AidlComposer::resetCommands() { - mWriter.reset(); +void AidlComposer::resetCommands(Display display) { + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().reset(); + } + mMutex.unlock_shared(); } -Error AidlComposer::executeCommands() { - return execute(); +Error AidlComposer::executeCommands(Display display) { + mMutex.lock_shared(); + auto error = execute(display); + mMutex.unlock_shared(); + return error; } uint32_t AidlComposer::getMaxVirtualDisplayCount() { @@ -334,6 +343,7 @@ Error AidlComposer::createVirtualDisplay(uint32_t width, uint32_t height, PixelF *outDisplay = translate(virtualDisplay.display); *format = static_cast(virtualDisplay.format); + addDisplay(translate(virtualDisplay.display)); return Error::NONE; } @@ -343,12 +353,20 @@ Error AidlComposer::destroyVirtualDisplay(Display display) { ALOGE("destroyVirtualDisplay failed %s", status.getDescription().c_str()); return static_cast(status.getServiceSpecificError()); } + removeDisplay(display); return Error::NONE; } Error AidlComposer::acceptDisplayChanges(Display display) { - mWriter.acceptDisplayChanges(translate(display)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().acceptDisplayChanges(translate(display)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::createLayer(Display display, Layer* outLayer) { @@ -388,7 +406,17 @@ Error AidlComposer::getActiveConfig(Display display, Config* outConfig) { Error AidlComposer::getChangedCompositionTypes( Display display, std::vector* outLayers, std::vector* outTypes) { - const auto changedLayers = mReader.takeChangedCompositionTypes(translate(display)); + std::vector changedLayers; + Error error = Error::NONE; + { + mMutex.lock_shared(); + if (auto reader = getReader(display)) { + changedLayers = reader->get().takeChangedCompositionTypes(translate(display)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + } outLayers->reserve(changedLayers.size()); outTypes->reserve(changedLayers.size()); @@ -396,7 +424,7 @@ Error AidlComposer::getChangedCompositionTypes( outLayers->emplace_back(translate(layer.layer)); outTypes->emplace_back(layer.composition); } - return Error::NONE; + return error; } Error AidlComposer::getColorModes(Display display, std::vector* outModes) { @@ -448,7 +476,17 @@ Error AidlComposer::getDisplayName(Display display, std::string* outName) { Error AidlComposer::getDisplayRequests(Display display, uint32_t* outDisplayRequestMask, std::vector* outLayers, std::vector* outLayerRequestMasks) { - const auto displayRequests = mReader.takeDisplayRequests(translate(display)); + Error error = Error::NONE; + DisplayRequest displayRequests; + { + mMutex.lock_shared(); + if (auto reader = getReader(display)) { + displayRequests = reader->get().takeDisplayRequests(translate(display)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + } *outDisplayRequestMask = translate(displayRequests.mask); outLayers->reserve(displayRequests.layerRequests.size()); outLayerRequestMasks->reserve(displayRequests.layerRequests.size()); @@ -457,7 +495,7 @@ Error AidlComposer::getDisplayRequests(Display display, uint32_t* outDisplayRequ outLayers->emplace_back(translate(layer.layer)); outLayerRequestMasks->emplace_back(translate(layer.mask)); } - return Error::NONE; + return error; } Error AidlComposer::getDozeSupport(Display display, bool* outSupport) { @@ -511,7 +549,17 @@ Error AidlComposer::getOverlaySupport(AidlOverlayProperties* /*outProperties*/) Error AidlComposer::getReleaseFences(Display display, std::vector* outLayers, std::vector* outReleaseFences) { - auto fences = mReader.takeReleaseFences(translate(display)); + Error error = Error::NONE; + std::vector fences; + { + mMutex.lock_shared(); + if (auto reader = getReader(display)) { + fences = reader->get().takeReleaseFences(translate(display)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + } outLayers->reserve(fences.size()); outReleaseFences->reserve(fences.size()); @@ -522,19 +570,29 @@ Error AidlComposer::getReleaseFences(Display display, std::vector* outLay *fence.fence.getR() = -1; outReleaseFences->emplace_back(fenceOwner); } - return Error::NONE; + return error; } Error AidlComposer::presentDisplay(Display display, int* outPresentFence) { ATRACE_NAME("HwcPresentDisplay"); - mWriter.presentDisplay(translate(display)); + Error error = Error::NONE; + mMutex.lock_shared(); + auto writer = getWriter(display); + auto reader = getReader(display); + if (writer && reader) { + writer->get().presentDisplay(translate(display)); + error = execute(display); + } else { + error = Error::BAD_DISPLAY; + } - Error error = execute(); if (error != Error::NONE) { + mMutex.unlock_shared(); return error; } - auto fence = mReader.takePresentFence(translate(display)); + auto fence = reader->get().takePresentFence(translate(display)); + mMutex.unlock_shared(); // take ownership *outPresentFence = fence.get(); *fence.getR() = -1; @@ -559,11 +617,19 @@ Error AidlComposer::setClientTarget(Display display, uint32_t slot, const spgetNativeBuffer()->handle; } - mWriter.setClientTarget(translate(display), slot, handle, acquireFence, - translate( - dataspace), - translate(damage)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get() + .setClientTarget(translate(display), slot, handle, acquireFence, + translate( + dataspace), + translate(damage)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) { @@ -579,14 +645,28 @@ Error AidlComposer::setColorMode(Display display, ColorMode mode, RenderIntent r } Error AidlComposer::setColorTransform(Display display, const float* matrix) { - mWriter.setColorTransform(translate(display), matrix); - return Error::NONE; + auto error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setColorTransform(translate(display), matrix); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setOutputBuffer(Display display, const native_handle_t* buffer, int releaseFence) { - mWriter.setOutputBuffer(translate(display), 0, buffer, dup(releaseFence)); - return Error::NONE; + auto error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setOutputBuffer(translate(display), 0, buffer, dup(releaseFence)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setPowerMode(Display display, IComposerClient::PowerMode mode) { @@ -624,16 +704,26 @@ Error AidlComposer::setClientTargetSlotCount(Display display) { Error AidlComposer::validateDisplay(Display display, nsecs_t expectedPresentTime, uint32_t* outNumTypes, uint32_t* outNumRequests) { ATRACE_NAME("HwcValidateDisplay"); - mWriter.validateDisplay(translate(display), - ClockMonotonicTimestamp{expectedPresentTime}); + const auto displayId = translate(display); + Error error = Error::NONE; + mMutex.lock_shared(); + auto writer = getWriter(display); + auto reader = getReader(display); + if (writer && reader) { + writer->get().validateDisplay(displayId, ClockMonotonicTimestamp{expectedPresentTime}); + error = execute(display); + } else { + error = Error::BAD_DISPLAY; + } - Error error = execute(); if (error != Error::NONE) { + mMutex.unlock_shared(); return error; } - mReader.hasChanges(translate(display), outNumTypes, outNumRequests); + reader->get().hasChanges(displayId, outNumTypes, outNumRequests); + mMutex.unlock_shared(); return Error::NONE; } @@ -641,39 +731,59 @@ Error AidlComposer::presentOrValidateDisplay(Display display, nsecs_t expectedPr uint32_t* outNumTypes, uint32_t* outNumRequests, int* outPresentFence, uint32_t* state) { ATRACE_NAME("HwcPresentOrValidateDisplay"); - mWriter.presentOrvalidateDisplay(translate(display), - ClockMonotonicTimestamp{expectedPresentTime}); + const auto displayId = translate(display); + Error error = Error::NONE; + mMutex.lock_shared(); + auto writer = getWriter(display); + auto reader = getReader(display); + if (writer && reader) { + writer->get().presentOrvalidateDisplay(displayId, + ClockMonotonicTimestamp{expectedPresentTime}); + error = execute(display); + } else { + error = Error::BAD_DISPLAY; + } - Error error = execute(); if (error != Error::NONE) { + mMutex.unlock_shared(); return error; } - const auto result = mReader.takePresentOrValidateStage(translate(display)); + const auto result = reader->get().takePresentOrValidateStage(displayId); if (!result.has_value()) { *state = translate(-1); + mMutex.unlock_shared(); return Error::NO_RESOURCES; } *state = translate(*result); if (*result == PresentOrValidate::Result::Presented) { - auto fence = mReader.takePresentFence(translate(display)); + auto fence = reader->get().takePresentFence(displayId); // take ownership *outPresentFence = fence.get(); *fence.getR() = -1; } if (*result == PresentOrValidate::Result::Validated) { - mReader.hasChanges(translate(display), outNumTypes, outNumRequests); + reader->get().hasChanges(displayId, outNumTypes, outNumRequests); } + mMutex.unlock_shared(); return Error::NONE; } Error AidlComposer::setCursorPosition(Display display, Layer layer, int32_t x, int32_t y) { - mWriter.setLayerCursorPosition(translate(display), translate(layer), x, y); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerCursorPosition(translate(display), translate(layer), + x, y); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerBuffer(Display display, Layer layer, uint32_t slot, @@ -683,90 +793,190 @@ Error AidlComposer::setLayerBuffer(Display display, Layer layer, uint32_t slot, handle = buffer->getNativeBuffer()->handle; } - mWriter.setLayerBuffer(translate(display), translate(layer), slot, handle, - acquireFence); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerBuffer(translate(display), translate(layer), slot, + handle, acquireFence); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerSurfaceDamage(Display display, Layer layer, const std::vector& damage) { - mWriter.setLayerSurfaceDamage(translate(display), translate(layer), - translate(damage)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerSurfaceDamage(translate(display), translate(layer), + translate(damage)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerBlendMode(Display display, Layer layer, IComposerClient::BlendMode mode) { - mWriter.setLayerBlendMode(translate(display), translate(layer), - translate(mode)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerBlendMode(translate(display), translate(layer), + translate(mode)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerColor(Display display, Layer layer, const Color& color) { - mWriter.setLayerColor(translate(display), translate(layer), color); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerColor(translate(display), translate(layer), color); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerCompositionType( Display display, Layer layer, aidl::android::hardware::graphics::composer3::Composition type) { - mWriter.setLayerCompositionType(translate(display), translate(layer), type); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerCompositionType(translate(display), + translate(layer), type); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerDataspace(Display display, Layer layer, Dataspace dataspace) { - mWriter.setLayerDataspace(translate(display), translate(layer), - translate(dataspace)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerDataspace(translate(display), translate(layer), + translate(dataspace)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerDisplayFrame(Display display, Layer layer, const IComposerClient::Rect& frame) { - mWriter.setLayerDisplayFrame(translate(display), translate(layer), - translate(frame)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerDisplayFrame(translate(display), translate(layer), + translate(frame)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerPlaneAlpha(Display display, Layer layer, float alpha) { - mWriter.setLayerPlaneAlpha(translate(display), translate(layer), alpha); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerPlaneAlpha(translate(display), translate(layer), + alpha); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerSidebandStream(Display display, Layer layer, const native_handle_t* stream) { - mWriter.setLayerSidebandStream(translate(display), translate(layer), stream); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerSidebandStream(translate(display), translate(layer), + stream); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerSourceCrop(Display display, Layer layer, const IComposerClient::FRect& crop) { - mWriter.setLayerSourceCrop(translate(display), translate(layer), - translate(crop)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerSourceCrop(translate(display), translate(layer), + translate(crop)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerTransform(Display display, Layer layer, Transform transform) { - mWriter.setLayerTransform(translate(display), translate(layer), - translate(transform)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerTransform(translate(display), translate(layer), + translate(transform)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerVisibleRegion(Display display, Layer layer, const std::vector& visible) { - mWriter.setLayerVisibleRegion(translate(display), translate(layer), - translate(visible)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerVisibleRegion(translate(display), translate(layer), + translate(visible)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerZOrder(Display display, Layer layer, uint32_t z) { - mWriter.setLayerZOrder(translate(display), translate(layer), z); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerZOrder(translate(display), translate(layer), z); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } -Error AidlComposer::execute() { - const auto& commands = mWriter.getPendingCommands(); +Error AidlComposer::execute(Display display) { + auto writer = getWriter(display); + auto reader = getReader(display); + if (!writer || !reader) { + return Error::BAD_DISPLAY; + } + + const auto& commands = writer->get().getPendingCommands(); if (commands.empty()) { - mWriter.reset(); + writer->get().reset(); return Error::NONE; } @@ -778,9 +988,9 @@ Error AidlComposer::execute() { return static_cast(status.getServiceSpecificError()); } - mReader.parse(std::move(results)); + reader->get().parse(std::move(results)); } - const auto commandErrors = mReader.takeErrors(); + const auto commandErrors = reader->get().takeErrors(); Error error = Error::NONE; for (const auto& cmdErr : commandErrors) { const auto index = static_cast(cmdErr.commandIndex); @@ -798,7 +1008,7 @@ Error AidlComposer::execute() { } } - mWriter.reset(); + writer->get().reset(); return error; } @@ -806,9 +1016,17 @@ Error AidlComposer::execute() { Error AidlComposer::setLayerPerFrameMetadata( Display display, Layer layer, const std::vector& perFrameMetadatas) { - mWriter.setLayerPerFrameMetadata(translate(display), translate(layer), - translate(perFrameMetadatas)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerPerFrameMetadata(translate(display), + translate(layer), + translate(perFrameMetadatas)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } std::vector AidlComposer::getPerFrameMetadataKeys( @@ -868,8 +1086,16 @@ Error AidlComposer::getDisplayIdentificationData(Display display, uint8_t* outPo } Error AidlComposer::setLayerColorTransform(Display display, Layer layer, const float* matrix) { - mWriter.setLayerColorTransform(translate(display), translate(layer), matrix); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerColorTransform(translate(display), translate(layer), + matrix); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::getDisplayedContentSamplingAttributes(Display display, PixelFormat* outFormat, @@ -932,20 +1158,36 @@ Error AidlComposer::getDisplayedContentSample(Display display, uint64_t maxFrame Error AidlComposer::setLayerPerFrameMetadataBlobs( Display display, Layer layer, const std::vector& metadata) { - mWriter.setLayerPerFrameMetadataBlobs(translate(display), translate(layer), - translate(metadata)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerPerFrameMetadataBlobs(translate(display), + translate(layer), + translate(metadata)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setDisplayBrightness(Display display, float brightness, float brightnessNits, const DisplayBrightnessOptions& options) { - mWriter.setDisplayBrightness(translate(display), brightness, brightnessNits); - - if (options.applyImmediately) { - return execute(); + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setDisplayBrightness(translate(display), brightness, brightnessNits); + + if (options.applyImmediately) { + error = execute(display); + mMutex.unlock_shared(); + return error; + } + } else { + error = Error::BAD_DISPLAY; } - - return Error::NONE; + mMutex.unlock_shared(); + return error; } Error AidlComposer::getDisplayCapabilities(Display display, @@ -1085,20 +1327,43 @@ Error AidlComposer::getPreferredBootDisplayConfig(Display display, Config* confi Error AidlComposer::getClientTargetProperty( Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) { - *outClientTargetProperty = mReader.takeClientTargetProperty(translate(display)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto reader = getReader(display)) { + *outClientTargetProperty = + reader->get().takeClientTargetProperty(translate(display)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerBrightness(Display display, Layer layer, float brightness) { - mWriter.setLayerBrightness(translate(display), translate(layer), brightness); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerBrightness(translate(display), translate(layer), + brightness); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::setLayerBlockingRegion(Display display, Layer layer, const std::vector& blocking) { - mWriter.setLayerBlockingRegion(translate(display), translate(layer), - translate(blocking)); - return Error::NONE; + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerBlockingRegion(translate(display), translate(layer), + translate(blocking)); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; } Error AidlComposer::getDisplayDecorationSupport(Display display, @@ -1136,5 +1401,88 @@ Error AidlComposer::getPhysicalDisplayOrientation(Display displayId, return Error::NONE; } +ftl::Optional> AidlComposer::getWriter(Display display) + REQUIRES_SHARED(mMutex) { + return mWriters.get(display); +} + +ftl::Optional> AidlComposer::getReader(Display display) + REQUIRES_SHARED(mMutex) { + if (mSingleReader) { + display = translate(kSingleReaderKey); + } + return mReaders.get(display); +} + +void AidlComposer::removeDisplay(Display display) { + mMutex.lock(); + bool wasErased = mWriters.erase(display); + ALOGW_IF(!wasErased, + "Attempting to remove writer for display %" PRId64 " which is not connected", + translate(display)); + if (!mSingleReader) { + removeReader(display); + } + mMutex.unlock(); +} + +void AidlComposer::onHotplugDisconnect(Display display) { + removeDisplay(display); +} + +bool AidlComposer::hasMultiThreadedPresentSupport(Display display) { + const auto displayId = translate(display); + std::vector capabilities; + const auto status = mAidlComposerClient->getDisplayCapabilities(displayId, &capabilities); + if (!status.isOk()) { + ALOGE("getDisplayCapabilities failed %s", status.getDescription().c_str()); + return false; + } + return std::find(capabilities.begin(), capabilities.end(), + AidlDisplayCapability::MULTI_THREADED_PRESENT) != capabilities.end(); +} + +void AidlComposer::addReader(Display display) { + const auto displayId = translate(display); + std::optional displayOpt; + if (displayId != kSingleReaderKey) { + displayOpt.emplace(displayId); + } + auto [it, added] = mReaders.try_emplace(display, std::move(displayOpt)); + ALOGW_IF(!added, "Attempting to add writer for display %" PRId64 " which is already connected", + displayId); +} + +void AidlComposer::removeReader(Display display) { + bool wasErased = mReaders.erase(display); + ALOGW_IF(!wasErased, + "Attempting to remove reader for display %" PRId64 " which is not connected", + translate(display)); +} + +void AidlComposer::addDisplay(Display display) { + const auto displayId = translate(display); + mMutex.lock(); + auto [it, added] = mWriters.try_emplace(display, displayId); + ALOGW_IF(!added, "Attempting to add writer for display %" PRId64 " which is already connected", + displayId); + if (mSingleReader) { + if (hasMultiThreadedPresentSupport(display)) { + mSingleReader = false; + removeReader(translate(kSingleReaderKey)); + // Note that this includes the new display. + for (const auto& [existingDisplay, _] : mWriters) { + addReader(existingDisplay); + } + } + } else { + addReader(display); + } + mMutex.unlock(); +} + +void AidlComposer::onHotplugConnect(Display display) { + addDisplay(display); +} } // namespace Hwc2 } // namespace android diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h index f2a59a5b95..d84efe7d38 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h @@ -17,10 +17,12 @@ #pragma once #include "ComposerHal.h" +#include +#include +#include #include #include -#include #include #include @@ -70,10 +72,10 @@ public: // Reset all pending commands in the command buffer. Useful if you want to // skip a frame but have already queued some commands. - void resetCommands() override; + void resetCommands(Display) override; // Explicitly flush all pending commands in the command buffer. - Error executeCommands() override; + Error executeCommands(Display) override; uint32_t getMaxVirtualDisplayCount() override; Error createVirtualDisplay(uint32_t width, uint32_t height, PixelFormat* format, @@ -228,16 +230,29 @@ public: Error getPhysicalDisplayOrientation(Display displayId, AidlTransform* outDisplayOrientation) override; + void onHotplugConnect(Display) override; + void onHotplugDisconnect(Display) override; private: // Many public functions above simply write a command into the command // queue to batch the calls. validateDisplay and presentDisplay will call // this function to execute the command queue. - Error execute(); + Error execute(Display) REQUIRES_SHARED(mMutex); // returns the default instance name for the given service static std::string instance(const std::string& serviceName); + ftl::Optional> getWriter(Display) + REQUIRES_SHARED(mMutex); + ftl::Optional> getReader(Display) + REQUIRES_SHARED(mMutex); + void addDisplay(Display) EXCLUDES(mMutex); + void removeDisplay(Display) EXCLUDES(mMutex); + void addReader(Display) REQUIRES(mMutex); + void removeReader(Display) REQUIRES(mMutex); + + bool hasMultiThreadedPresentSupport(Display); + // 64KiB minus a small space for metadata such as read/write pointers static constexpr size_t kWriterInitialSize = 64 * 1024 / sizeof(uint32_t) - 16; // Max number of buffers that may be cached for a given layer @@ -245,8 +260,25 @@ private: // 1. Tightly coupling this cache to the max size of BufferQueue // 2. Adding an additional slot for the layer caching feature in SurfaceFlinger (see: Planner.h) static const constexpr uint32_t kMaxLayerBufferCount = BufferQueue::NUM_BUFFER_SLOTS + 1; - ComposerClientWriter mWriter; - ComposerClientReader mReader; + + // Without DisplayCapability::MULTI_THREADED_PRESENT, we use a single reader + // for all displays. With the capability, we use a separate reader for each + // display. + bool mSingleReader = true; + // Invalid displayId used as a key to mReaders when mSingleReader is true. + static constexpr int64_t kSingleReaderKey = 0; + + // TODO (b/256881188): Use display::PhysicalDisplayMap instead of hard-coded `3` + ftl::SmallMap mWriters GUARDED_BY(mMutex); + ftl::SmallMap mReaders GUARDED_BY(mMutex); + // Protect access to mWriters and mReaders with a shared_mutex. Adding and + // removing a display require exclusive access, since the iterator or the + // writer/reader may be invalidated. Other calls need shared access while + // using the writer/reader, so they can use their display's writer/reader + // without it being deleted or the iterator being invalidated. + // TODO (b/257958323): Use std::shared_mutex and RAII once they support + // threading annotations. + ftl::SharedMutex mMutex; // Aidl interface using AidlIComposer = aidl::android::hardware::graphics::composer3::IComposer; diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h index b02f867b85..a9bf282d94 100644 --- a/services/surfaceflinger/DisplayHardware/ComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h @@ -110,10 +110,10 @@ public: // Reset all pending commands in the command buffer. Useful if you want to // skip a frame but have already queued some commands. - virtual void resetCommands() = 0; + virtual void resetCommands(Display) = 0; // Explicitly flush all pending commands in the command buffer. - virtual Error executeCommands() = 0; + virtual Error executeCommands(Display) = 0; virtual uint32_t getMaxVirtualDisplayCount() = 0; virtual Error createVirtualDisplay(uint32_t width, uint32_t height, PixelFormat*, @@ -283,6 +283,8 @@ public: virtual Error getPhysicalDisplayOrientation(Display displayId, AidlTransform* outDisplayOrientation) = 0; virtual Error getOverlaySupport(V3_0::OverlayProperties* outProperties) = 0; + virtual void onHotplugConnect(Display) = 0; + virtual void onHotplugDisconnect(Display) = 0; }; } // namespace Hwc2 diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp index 168e2ddabb..5f11cb8e30 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -513,7 +513,7 @@ status_t HWComposer::presentAndGetReleaseFences( if (displayData.validateWasSkipped) { // explicitly flush all pending commands - auto error = static_cast(mComposer->executeCommands()); + auto error = static_cast(mComposer->executeCommands(hwcDisplay->getId())); RETURN_IF_HWC_ERROR_FOR("executeCommands", error, displayId, UNKNOWN_ERROR); RETURN_IF_HWC_ERROR_FOR("present", displayData.presentError, displayId, UNKNOWN_ERROR); return NO_ERROR; @@ -933,6 +933,8 @@ std::optional HWComposer::onHotplugConnect( : "Secondary display", .deviceProductInfo = std::nullopt}; }(); + + mComposer->onHotplugConnect(hwcDisplayId); } if (!isConnected(info->id)) { @@ -960,6 +962,7 @@ std::optional HWComposer::onHotplugDisconnect( // The display will later be destroyed by a call to HWComposer::disconnectDisplay. For now, mark // it as disconnected. mDisplayData.at(*displayId).hwcDisplay->setConnected(false); + mComposer->onHotplugDisconnect(hwcDisplayId); return DisplayIdentificationInfo{.id = *displayId}; } diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp index a664d2cf38..f8522e2ef0 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp @@ -273,11 +273,11 @@ void HidlComposer::registerCallback(const sp& callback) { } } -void HidlComposer::resetCommands() { +void HidlComposer::resetCommands(Display) { mWriter.reset(); } -Error HidlComposer::executeCommands() { +Error HidlComposer::executeCommands(Display) { return execute(); } @@ -1357,6 +1357,9 @@ void HidlComposer::registerCallback(ComposerCallback& callback) { registerCallback(sp::make(callback, vsyncSwitchingSupported)); } +void HidlComposer::onHotplugConnect(Display) {} +void HidlComposer::onHotplugDisconnect(Display) {} + CommandReader::~CommandReader() { resetData(); } diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h index b436408fb1..48b720c108 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h @@ -177,10 +177,10 @@ public: // Reset all pending commands in the command buffer. Useful if you want to // skip a frame but have already queued some commands. - void resetCommands() override; + void resetCommands(Display) override; // Explicitly flush all pending commands in the command buffer. - Error executeCommands() override; + Error executeCommands(Display) override; uint32_t getMaxVirtualDisplayCount() override; Error createVirtualDisplay(uint32_t width, uint32_t height, PixelFormat* format, @@ -339,6 +339,8 @@ public: Error getPhysicalDisplayOrientation(Display displayId, AidlTransform* outDisplayOrientation) override; + void onHotplugConnect(Display) override; + void onHotplugDisconnect(Display) override; private: class CommandWriter : public CommandWriterBase { diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp index f8fc6f5a40..8a6af10f58 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp @@ -326,8 +326,8 @@ void DisplayHardwareFuzzer::invokeAidlComposer() { invokeComposerHal2_3(&composer, display, outLayer); invokeComposerHal2_4(&composer, display, outLayer); - composer.executeCommands(); - composer.resetCommands(); + composer.executeCommands(display); + composer.resetCommands(display); composer.destroyLayer(display, outLayer); composer.destroyVirtualDisplay(display); diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp index 9d8e0a25e6..342c646847 100644 --- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp +++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp @@ -73,6 +73,7 @@ struct HWComposerTest : testing::Test { EXPECT_CALL(*mHal, setClientTargetSlotCount(_)); EXPECT_CALL(*mHal, setVsyncEnabled(hwcDisplayId, Hwc2::IComposerClient::Vsync::DISABLE)); + EXPECT_CALL(*mHal, onHotplugConnect(hwcDisplayId)); } }; diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h index 3808487d0f..5ee38ec621 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h @@ -56,8 +56,8 @@ public: std::vector()); MOCK_METHOD0(dumpDebugInfo, std::string()); MOCK_METHOD1(registerCallback, void(HWC2::ComposerCallback&)); - MOCK_METHOD0(resetCommands, void()); - MOCK_METHOD0(executeCommands, Error()); + MOCK_METHOD1(resetCommands, void(Display)); + MOCK_METHOD1(executeCommands, Error(Display)); MOCK_METHOD0(getMaxVirtualDisplayCount, uint32_t()); MOCK_METHOD4(createVirtualDisplay, Error(uint32_t, uint32_t, PixelFormat*, Display*)); MOCK_METHOD1(destroyVirtualDisplay, Error(Display)); @@ -166,6 +166,8 @@ public: MOCK_METHOD2(getPhysicalDisplayOrientation, Error(Display, AidlTransform*)); MOCK_METHOD1(getOverlaySupport, Error(aidl::android::hardware::graphics::composer3::OverlayProperties*)); + MOCK_METHOD1(onHotplugConnect, void(Display)); + MOCK_METHOD1(onHotplugDisconnect, void(Display)); }; } // namespace Hwc2::mock -- GitLab From 253f464be4eec003d3b875f25a6318eca9b1c644 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 9 Nov 2022 13:42:06 -0800 Subject: [PATCH 0482/1310] Use ftl::Flags for InputTarget flags These flags will now be type-safe. Bug: 211379801 Test: m inputflinger_tests && adb sync data && adb shell -t /data/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: If1835151df74cfebf39ec3471f826082bea30e26 --- include/ftl/flags.h | 4 +- services/inputflinger/dispatcher/Entry.cpp | 3 +- services/inputflinger/dispatcher/Entry.h | 14 +- .../dispatcher/InputDispatcher.cpp | 161 +++++++++--------- .../inputflinger/dispatcher/InputDispatcher.h | 6 +- .../inputflinger/dispatcher/InputTarget.cpp | 18 -- .../inputflinger/dispatcher/InputTarget.h | 39 ++--- .../inputflinger/dispatcher/TouchState.cpp | 22 +-- services/inputflinger/dispatcher/TouchState.h | 2 +- .../inputflinger/dispatcher/TouchedWindow.cpp | 6 +- .../inputflinger/dispatcher/TouchedWindow.h | 3 +- 11 files changed, 136 insertions(+), 142 deletions(-) diff --git a/include/ftl/flags.h b/include/ftl/flags.h index 70aaa0e6dd..cdb4e840a4 100644 --- a/include/ftl/flags.h +++ b/include/ftl/flags.h @@ -125,7 +125,7 @@ public: /* Tests whether all of the given flags are set */ bool all(Flags f) const { return (mFlags & f.mFlags) == f.mFlags; } - Flags operator|(Flags rhs) const { return static_cast(mFlags | rhs.mFlags); } + constexpr Flags operator|(Flags rhs) const { return static_cast(mFlags | rhs.mFlags); } Flags& operator|=(Flags rhs) { mFlags = mFlags | rhs.mFlags; return *this; @@ -217,7 +217,7 @@ inline Flags operator~(F f) { } template >> -Flags operator|(F lhs, F rhs) { +constexpr Flags operator|(F lhs, F rhs) { return static_cast(to_underlying(lhs) | to_underlying(rhs)); } diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp index 33e7e17ec0..ec9701ac24 100644 --- a/services/inputflinger/dispatcher/Entry.cpp +++ b/services/inputflinger/dispatcher/Entry.cpp @@ -308,7 +308,8 @@ std::string SensorEntry::getDescription() const { volatile int32_t DispatchEntry::sNextSeqAtomic; -DispatchEntry::DispatchEntry(std::shared_ptr eventEntry, int32_t targetFlags, +DispatchEntry::DispatchEntry(std::shared_ptr eventEntry, + ftl::Flags targetFlags, const ui::Transform& transform, const ui::Transform& rawTransform, float globalScaleFactor) : seq(nextSeq()), diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h index 60f319a056..f8019126f3 100644 --- a/services/inputflinger/dispatcher/Entry.h +++ b/services/inputflinger/dispatcher/Entry.h @@ -223,7 +223,7 @@ struct DispatchEntry { const uint32_t seq; // unique sequence number, never 0 std::shared_ptr eventEntry; // the event to dispatch - int32_t targetFlags; + ftl::Flags targetFlags; ui::Transform transform; ui::Transform rawTransform; float globalScaleFactor; @@ -238,13 +238,15 @@ struct DispatchEntry { int32_t resolvedAction; int32_t resolvedFlags; - DispatchEntry(std::shared_ptr eventEntry, int32_t targetFlags, - const ui::Transform& transform, const ui::Transform& rawTransform, - float globalScaleFactor); + DispatchEntry(std::shared_ptr eventEntry, + ftl::Flags targetFlags, const ui::Transform& transform, + const ui::Transform& rawTransform, float globalScaleFactor); - inline bool hasForegroundTarget() const { return targetFlags & InputTarget::FLAG_FOREGROUND; } + inline bool hasForegroundTarget() const { + return targetFlags.test(InputTarget::Flags::FOREGROUND); + } - inline bool isSplit() const { return targetFlags & InputTarget::FLAG_SPLIT; } + inline bool isSplit() const { return targetFlags.test(InputTarget::Flags::SPLIT); } private: static volatile int32_t sNextSeqAtomic; diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 8ae59390c0..7b7c42a211 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -51,6 +51,7 @@ #define INDENT3 " " #define INDENT4 " " +using namespace android::ftl::flag_operators; using android::base::HwTimeoutMultiplier; using android::base::Result; using android::base::StringPrintf; @@ -242,9 +243,9 @@ std::string dumpQueue(const std::deque& queue, nsecs_t currentTi } dump.append(INDENT4); dump += entry.eventEntry->getDescription(); - dump += StringPrintf(", seq=%" PRIu32 - ", targetFlags=0x%08x, resolvedAction=%d, age=%" PRId64 "ms", - entry.seq, entry.targetFlags, entry.resolvedAction, + dump += StringPrintf(", seq=%" PRIu32 ", targetFlags=%s, resolvedAction=%d, age=%" PRId64 + "ms", + entry.seq, entry.targetFlags.string().c_str(), entry.resolvedAction, ns2ms(currentTime - entry.eventEntry->eventTime)); if (entry.deliveryTime != 0) { // This entry was delivered, so add information on how long we've been waiting @@ -289,9 +290,9 @@ bool haveSameApplicationToken(const WindowInfo* first, const WindowInfo* second) first->applicationInfo.token == second->applicationInfo.token; } -std::unique_ptr createDispatchEntry(const InputTarget& inputTarget, - std::shared_ptr eventEntry, - int32_t inputTargetFlags) { +std::unique_ptr createDispatchEntry( + const InputTarget& inputTarget, std::shared_ptr eventEntry, + ftl::Flags inputTargetFlags) { if (inputTarget.useDefaultPointerTransform()) { const ui::Transform& transform = inputTarget.getDefaultPointerTransform(); return std::make_unique(eventEntry, inputTargetFlags, transform, @@ -484,11 +485,11 @@ bool isPointerFromStylus(const MotionEntry& entry, int32_t pointerIndex) { entry.pointerProperties[pointerIndex].toolType == AMOTION_EVENT_TOOL_TYPE_ERASER); } -// Determines if the given window can be targeted as InputTarget::FLAG_FOREGROUND. +// Determines if the given window can be targeted as InputTarget::Flags::FOREGROUND. // Foreground events are only sent to "foreground targetable" windows, but not all gestures sent to // such window are necessarily targeted with the flag. For example, an event with ACTION_OUTSIDE can // be sent to such a window, but it is not a foreground event and doesn't use -// InputTarget::FLAG_FOREGROUND. +// InputTarget::Flags::FOREGROUND. bool canReceiveForegroundTouches(const WindowInfo& info) { // A non-touchable window can still receive touch events (e.g. in the case of // STYLUS_INTERCEPTOR), so prevent such windows from receiving foreground events for touches. @@ -1101,7 +1102,7 @@ sp InputDispatcher::findTouchedWindowAtLocked(int32_t displayI if (addOutsideTargets && info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) { - touchState->addOrUpdateWindow(windowHandle, InputTarget::FLAG_DISPATCH_AS_OUTSIDE, + touchState->addOrUpdateWindow(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE, BitSet32(0)); } } @@ -1372,7 +1373,7 @@ void InputDispatcher::dispatchFocusLocked(nsecs_t currentTime, std::shared_ptrdispatchInProgress = true; std::string message = std::string("Focus ") + (entry->hasFocus ? "entering " : "leaving ") + channel->getName(); @@ -1446,7 +1447,7 @@ void InputDispatcher::dispatchPointerCaptureChangedLocked( } InputTarget target; target.inputChannel = channel; - target.flags = InputTarget::FLAG_DISPATCH_AS_IS; + target.flags = InputTarget::Flags::DISPATCH_AS_IS; entry->dispatchInProgress = true; dispatchEventLocked(currentTime, entry, {target}); @@ -1483,7 +1484,7 @@ std::vector InputDispatcher::getInputTargetsFromWindowHandlesLocked } InputTarget target; target.inputChannel = channel; - target.flags = InputTarget::FLAG_DISPATCH_AS_IS; + target.flags = InputTarget::Flags::DISPATCH_AS_IS; inputTargets.push_back(target); } return inputTargets; @@ -1596,7 +1597,7 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr inputTargets; addWindowTargetLocked(focusedWindow, - InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, + InputTarget::Flags::FOREGROUND | InputTarget::Flags::DISPATCH_AS_IS, BitSet32(0), getDownTime(*entry), inputTargets); // Add monitor channels from event's or focused display. @@ -1709,7 +1710,8 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr< if (injectionResult == InputEventInjectionResult::SUCCEEDED) { LOG_ALWAYS_FATAL_IF(focusedWindow == nullptr); addWindowTargetLocked(focusedWindow, - InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, + InputTarget::Flags::FOREGROUND | + InputTarget::Flags::DISPATCH_AS_IS, BitSet32(0), getDownTime(*entry), inputTargets); } } @@ -1761,7 +1763,7 @@ void InputDispatcher::dispatchDragLocked(nsecs_t currentTime, std::shared_ptrdispatchInProgress = true; dispatchEventLocked(currentTime, entry, {target}); } @@ -2196,20 +2198,20 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } // Set target flags. - int32_t targetFlags = InputTarget::FLAG_DISPATCH_AS_IS; + ftl::Flags targetFlags = InputTarget::Flags::DISPATCH_AS_IS; if (canReceiveForegroundTouches(*windowHandle->getInfo())) { // There should only be one touched window that can be "foreground" for the pointer. - targetFlags |= InputTarget::FLAG_FOREGROUND; + targetFlags |= InputTarget::Flags::FOREGROUND; } if (isSplit) { - targetFlags |= InputTarget::FLAG_SPLIT; + targetFlags |= InputTarget::Flags::SPLIT; } if (isWindowObscuredAtPointLocked(windowHandle, x, y)) { - targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; + targetFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED; } else if (isWindowObscuredLocked(windowHandle)) { - targetFlags |= InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED; + targetFlags |= InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED; } // Update the temporary touch state. @@ -2276,7 +2278,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } // Make a slippery exit from the old window. tempTouchState.addOrUpdateWindow(oldTouchedWindowHandle, - InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT, + InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT, BitSet32(0)); // Make a slippery entrance into the new window. @@ -2284,17 +2286,18 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( isSplit = !isFromMouse; } - int32_t targetFlags = InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER; + ftl::Flags targetFlags = + InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER; if (canReceiveForegroundTouches(*newTouchedWindowHandle->getInfo())) { - targetFlags |= InputTarget::FLAG_FOREGROUND; + targetFlags |= InputTarget::Flags::FOREGROUND; } if (isSplit) { - targetFlags |= InputTarget::FLAG_SPLIT; + targetFlags |= InputTarget::Flags::SPLIT; } if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) { - targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; + targetFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED; } else if (isWindowObscuredLocked(newTouchedWindowHandle)) { - targetFlags |= InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED; + targetFlags |= InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED; } BitSet32 pointerIds; @@ -2317,7 +2320,8 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( mLastHoverWindowHandle->getName().c_str()); } tempTouchState.addOrUpdateWindow(mLastHoverWindowHandle, - InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT, BitSet32(0)); + InputTarget::Flags::DISPATCH_AS_HOVER_EXIT, + BitSet32(0)); } // Let the new window know that the hover sequence is starting, unless we already did it @@ -2330,7 +2334,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( newHoverWindowHandle->getName().c_str()); } tempTouchState.addOrUpdateWindow(newHoverWindowHandle, - InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER, + InputTarget::Flags::DISPATCH_AS_HOVER_ENTER, BitSet32(0)); } } @@ -2343,7 +2347,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( [](const TouchedWindow& touchedWindow) { return !canReceiveForegroundTouches( *touchedWindow.windowHandle->getInfo()) || - (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) != 0; + touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND); })) { ALOGI("Dropping event because there is no touched window on display %d to receive it: %s", displayId, entry.getDescription().c_str()); @@ -2355,7 +2359,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( if (entry.injectionState != nullptr) { std::string errs; for (const TouchedWindow& touchedWindow : tempTouchState.windows) { - if (touchedWindow.targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) { + if (touchedWindow.targetFlags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) { // Allow ACTION_OUTSIDE events generated by targeted injection to be // dispatched to any uid, since the coords will be zeroed out later. continue; @@ -2380,11 +2384,11 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( if (foregroundWindowHandle) { const int32_t foregroundWindowUid = foregroundWindowHandle->getInfo()->ownerUid; for (const TouchedWindow& touchedWindow : tempTouchState.windows) { - if (touchedWindow.targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) { + if (touchedWindow.targetFlags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) { sp windowInfoHandle = touchedWindow.windowHandle; if (windowInfoHandle->getInfo()->ownerUid != foregroundWindowUid) { tempTouchState.addOrUpdateWindow(windowInfoHandle, - InputTarget::FLAG_ZERO_COORDS, + InputTarget::Flags::ZERO_COORDS, BitSet32(0)); } } @@ -2411,13 +2415,12 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( if (info->displayId == displayId && windowHandle->getInfo()->inputConfig.test( WindowInfo::InputConfig::IS_WALLPAPER)) { - tempTouchState - .addOrUpdateWindow(windowHandle, - InputTarget::FLAG_WINDOW_IS_OBSCURED | - InputTarget:: - FLAG_WINDOW_IS_PARTIALLY_OBSCURED | - InputTarget::FLAG_DISPATCH_AS_IS, - BitSet32(0), entry.eventTime); + tempTouchState.addOrUpdateWindow(windowHandle, + InputTarget::Flags::WINDOW_IS_OBSCURED | + InputTarget::Flags:: + WINDOW_IS_PARTIALLY_OBSCURED | + InputTarget::Flags::DISPATCH_AS_IS, + BitSet32(0), entry.eventTime); } } } @@ -2608,7 +2611,8 @@ void InputDispatcher::addDragEventLocked(const MotionEntry& entry) { } void InputDispatcher::addWindowTargetLocked(const sp& windowHandle, - int32_t targetFlags, BitSet32 pointerIds, + ftl::Flags targetFlags, + BitSet32 pointerIds, std::optional firstDownTimeInTarget, std::vector& inputTargets) const { std::vector::iterator it = @@ -2656,7 +2660,7 @@ void InputDispatcher::addGlobalMonitoringTargetsLocked(std::vector& for (const Monitor& monitor : selectResponsiveMonitorsLocked(monitorsIt->second)) { InputTarget target; target.inputChannel = monitor.inputChannel; - target.flags = InputTarget::FLAG_DISPATCH_AS_IS; + target.flags = InputTarget::Flags::DISPATCH_AS_IS; // target.firstDownTimeInTarget is not set for global monitors. It is only required in split // touch and global monitoring works as intended even without setting firstDownTimeInTarget if (const auto& it = mDisplayInfos.find(displayId); it != mDisplayInfos.end()) { @@ -2688,7 +2692,7 @@ static bool canBeObscuredBy(const sp& windowHandle, // We do want to potentially flag touchable windows even if they have 0 // opacity, since they can consume touches and alter the effects of the // user interaction (eg. apps that rely on - // FLAG_WINDOW_IS_PARTIALLY_OBSCURED should still be told about those + // Flags::WINDOW_IS_PARTIALLY_OBSCURED should still be told about those // windows), hence we also check for FLAG_NOT_TOUCHABLE. return false; } else if (info->ownerUid == otherInfo->ownerUid) { @@ -2917,9 +2921,9 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, ATRACE_NAME(message.c_str()); } if (DEBUG_DISPATCH_CYCLE) { - ALOGD("channel '%s' ~ prepareDispatchCycle - flags=0x%08x, " + ALOGD("channel '%s' ~ prepareDispatchCycle - flags=%s, " "globalScaleFactor=%f, pointerIds=0x%x %s", - connection->getInputChannelName().c_str(), inputTarget.flags, + connection->getInputChannelName().c_str(), inputTarget.flags.string().c_str(), inputTarget.globalScaleFactor, inputTarget.pointerIds.value, inputTarget.getPointerInfoString().c_str()); } @@ -2936,9 +2940,9 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, } // Split a motion event if needed. - if (inputTarget.flags & InputTarget::FLAG_SPLIT) { + if (inputTarget.flags.test(InputTarget::Flags::SPLIT)) { LOG_ALWAYS_FATAL_IF(eventEntry->type != EventEntry::Type::MOTION, - "Entry type %s should not have FLAG_SPLIT", + "Entry type %s should not have Flags::SPLIT", ftl::enum_string(eventEntry->type).c_str()); const MotionEntry& originalMotionEntry = static_cast(*eventEntry); @@ -2987,17 +2991,17 @@ void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime, // Enqueue dispatch entries for the requested modes. enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, - InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT); + InputTarget::Flags::DISPATCH_AS_HOVER_EXIT); enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, - InputTarget::FLAG_DISPATCH_AS_OUTSIDE); + InputTarget::Flags::DISPATCH_AS_OUTSIDE); enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, - InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER); + InputTarget::Flags::DISPATCH_AS_HOVER_ENTER); enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, - InputTarget::FLAG_DISPATCH_AS_IS); + InputTarget::Flags::DISPATCH_AS_IS); enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, - InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT); + InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT); enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, - InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER); + InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER); // If the outbound queue was previously empty, start the dispatch cycle going. if (wasEmpty && !connection->outboundQueue.empty()) { @@ -3008,18 +3012,20 @@ void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime, void InputDispatcher::enqueueDispatchEntryLocked(const sp& connection, std::shared_ptr eventEntry, const InputTarget& inputTarget, - int32_t dispatchMode) { + ftl::Flags dispatchMode) { if (ATRACE_ENABLED()) { std::string message = StringPrintf("enqueueDispatchEntry(inputChannel=%s, dispatchMode=%s)", connection->getInputChannelName().c_str(), - dispatchModeToString(dispatchMode).c_str()); + dispatchMode.string().c_str()); ATRACE_NAME(message.c_str()); } - int32_t inputTargetFlags = inputTarget.flags; - if (!(inputTargetFlags & dispatchMode)) { + ftl::Flags inputTargetFlags = inputTarget.flags; + if (!inputTargetFlags.any(dispatchMode)) { return; } - inputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode; + + inputTargetFlags.clear(InputTarget::DISPATCH_MASK); + inputTargetFlags |= dispatchMode; // This is a new event. // Enqueue a new dispatch entry onto the outbound queue for this connection. @@ -3056,15 +3062,15 @@ void InputDispatcher::enqueueDispatchEntryLocked(const sp& connectio constexpr int32_t DEFAULT_RESOLVED_EVENT_ID = static_cast(IdGenerator::Source::OTHER); dispatchEntry->resolvedEventId = DEFAULT_RESOLVED_EVENT_ID; - if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) { + if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) { dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_OUTSIDE; - } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT) { + } else if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_HOVER_EXIT)) { dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_EXIT; - } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER) { + } else if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_HOVER_ENTER)) { dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER; - } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) { + } else if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT)) { dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_CANCEL; - } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER) { + } else if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER)) { dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_DOWN; } else { dispatchEntry->resolvedAction = motionEntry.action; @@ -3084,10 +3090,10 @@ void InputDispatcher::enqueueDispatchEntryLocked(const sp& connectio } dispatchEntry->resolvedFlags = motionEntry.flags; - if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) { + if (dispatchEntry->targetFlags.test(InputTarget::Flags::WINDOW_IS_OBSCURED)) { dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; } - if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED) { + if (dispatchEntry->targetFlags.test(InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED)) { dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; } @@ -3187,8 +3193,7 @@ void InputDispatcher::updateInteractionTokensLocked(const EventEntry& entry, std::unordered_set, StrongPointerHash> newConnectionTokens; std::vector> newConnections; for (const InputTarget& target : targets) { - if ((target.flags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) == - InputTarget::FLAG_DISPATCH_AS_OUTSIDE) { + if (target.flags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) { continue; // Skip windows that receive ACTION_OUTSIDE } @@ -3247,7 +3252,7 @@ status_t InputDispatcher::publishMotionEvent(Connection& connection, // Set the X and Y offset and X and Y scale depending on the input source. if ((motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) && - !(dispatchEntry.targetFlags & InputTarget::FLAG_ZERO_COORDS)) { + !(dispatchEntry.targetFlags.test(InputTarget::Flags::ZERO_COORDS))) { float globalScaleFactor = dispatchEntry.globalScaleFactor; if (globalScaleFactor != 1.0f) { for (uint32_t i = 0; i < motionEntry.pointerCount; i++) { @@ -3260,7 +3265,7 @@ status_t InputDispatcher::publishMotionEvent(Connection& connection, } usingCoords = scaledCoords; } - } else if (dispatchEntry.targetFlags & InputTarget::FLAG_ZERO_COORDS) { + } else if (dispatchEntry.targetFlags.test(InputTarget::Flags::ZERO_COORDS)) { // We don't want the dispatch target to know the coordinates for (uint32_t i = 0; i < motionEntry.pointerCount; i++) { scaledCoords[i].clear(); @@ -3662,7 +3667,7 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( target.globalScaleFactor = windowInfo->globalScaleFactor; } target.inputChannel = connection->inputChannel; - target.flags = InputTarget::FLAG_DISPATCH_AS_IS; + target.flags = InputTarget::Flags::DISPATCH_AS_IS; const bool wasEmpty = connection->outboundQueue.empty(); @@ -3697,7 +3702,7 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( } enqueueDispatchEntryLocked(connection, std::move(cancelationEventEntry), target, - InputTarget::FLAG_DISPATCH_AS_IS); + InputTarget::Flags::DISPATCH_AS_IS); } // If the outbound queue was previously empty, start the dispatch cycle going. @@ -3733,7 +3738,7 @@ void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( target.globalScaleFactor = windowInfo->globalScaleFactor; } target.inputChannel = connection->inputChannel; - target.flags = InputTarget::FLAG_DISPATCH_AS_IS; + target.flags = InputTarget::Flags::DISPATCH_AS_IS; const bool wasEmpty = connection->outboundQueue.empty(); for (std::unique_ptr& downEventEntry : downEvents) { @@ -3759,7 +3764,7 @@ void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( } enqueueDispatchEntryLocked(connection, std::move(downEventEntry), target, - InputTarget::FLAG_DISPATCH_AS_IS); + InputTarget::Flags::DISPATCH_AS_IS); } // If the outbound queue was previously empty, start the dispatch cycle going. @@ -4822,7 +4827,7 @@ void InputDispatcher::setInputWindowsLocked( synthesizeCancelationEventsForInputChannelLocked(touchedInputChannel, options); // Since we are about to drop the touch, cancel the events for the wallpaper as // well. - if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND && + if (touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND) && touchedWindow.windowHandle->getInfo()->inputConfig.test( gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) { sp wallpaper = state.getWallpaperWindow(); @@ -5140,16 +5145,16 @@ bool InputDispatcher::transferTouchFocus(const sp& fromToken, const sp< } // Erase old window. - int32_t oldTargetFlags = touchedWindow->targetFlags; + ftl::Flags oldTargetFlags = touchedWindow->targetFlags; BitSet32 pointerIds = touchedWindow->pointerIds; state->removeWindowByToken(fromToken); // Add new window. nsecs_t downTimeInTarget = now(); - int32_t newTargetFlags = - oldTargetFlags & (InputTarget::FLAG_SPLIT | InputTarget::FLAG_DISPATCH_AS_IS); + ftl::Flags newTargetFlags = + oldTargetFlags & (InputTarget::Flags::SPLIT | InputTarget::Flags::DISPATCH_AS_IS); if (canReceiveForegroundTouches(*toWindowHandle->getInfo())) { - newTargetFlags |= InputTarget::FLAG_FOREGROUND; + newTargetFlags |= InputTarget::Flags::FOREGROUND; } state->addOrUpdateWindow(toWindowHandle, newTargetFlags, pointerIds, downTimeInTarget); @@ -5203,7 +5208,7 @@ sp InputDispatcher::findTouchedForegroundWindowLocked(int32_t sp touchedForegroundWindow; // If multiple foreground windows are touched, return nullptr for (const TouchedWindow& window : state.windows) { - if (window.targetFlags & InputTarget::FLAG_FOREGROUND) { + if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) { if (touchedForegroundWindow != nullptr) { ALOGI("Two or more foreground windows: %s and %s", touchedForegroundWindow->getName().c_str(), diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 0ddbbeb27e..5efb39e0f2 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -553,7 +553,7 @@ private: const std::vector& gestureMonitors) const REQUIRES(mLock); void addWindowTargetLocked(const sp& windowHandle, - int32_t targetFlags, BitSet32 pointerIds, + ftl::Flags targetFlags, BitSet32 pointerIds, std::optional firstDownTimeInTarget, std::vector& inputTargets) const REQUIRES(mLock); void addGlobalMonitoringTargetsLocked(std::vector& inputTargets, int32_t displayId) @@ -600,8 +600,8 @@ private: std::shared_ptr, const InputTarget& inputTarget) REQUIRES(mLock); void enqueueDispatchEntryLocked(const sp& connection, std::shared_ptr, - const InputTarget& inputTarget, int32_t dispatchMode) - REQUIRES(mLock); + const InputTarget& inputTarget, + ftl::Flags dispatchMode) REQUIRES(mLock); status_t publishMotionEvent(Connection& connection, DispatchEntry& dispatchEntry) const; void startDispatchCycleLocked(nsecs_t currentTime, const sp& connection) REQUIRES(mLock); diff --git a/services/inputflinger/dispatcher/InputTarget.cpp b/services/inputflinger/dispatcher/InputTarget.cpp index 2df97d9a12..2f39480555 100644 --- a/services/inputflinger/dispatcher/InputTarget.cpp +++ b/services/inputflinger/dispatcher/InputTarget.cpp @@ -24,24 +24,6 @@ using android::base::StringPrintf; namespace android::inputdispatcher { -std::string dispatchModeToString(int32_t dispatchMode) { - switch (dispatchMode) { - case InputTarget::FLAG_DISPATCH_AS_IS: - return "DISPATCH_AS_IS"; - case InputTarget::FLAG_DISPATCH_AS_OUTSIDE: - return "DISPATCH_AS_OUTSIDE"; - case InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER: - return "DISPATCH_AS_HOVER_ENTER"; - case InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT: - return "DISPATCH_AS_HOVER_EXIT"; - case InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT: - return "DISPATCH_AS_SLIPPERY_EXIT"; - case InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER: - return "DISPATCH_AS_SLIPPERY_ENTER"; - } - return StringPrintf("%" PRId32, dispatchMode); -} - void InputTarget::addPointers(BitSet32 newPointerIds, const ui::Transform& transform) { // The pointerIds can be empty, but still a valid InputTarget. This can happen when there is no // valid pointer property from the input event. diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h index b2966f680c..61b07feb3a 100644 --- a/services/inputflinger/dispatcher/InputTarget.h +++ b/services/inputflinger/dispatcher/InputTarget.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include #include @@ -30,70 +31,70 @@ namespace android::inputdispatcher { * window area. */ struct InputTarget { - enum { + enum class Flags : uint32_t { /* This flag indicates that the event is being delivered to a foreground application. */ - FLAG_FOREGROUND = 1 << 0, + FOREGROUND = 1 << 0, /* This flag indicates that the MotionEvent falls within the area of the target * obscured by another visible window above it. The motion event should be * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */ - FLAG_WINDOW_IS_OBSCURED = 1 << 1, + WINDOW_IS_OBSCURED = 1 << 1, /* This flag indicates that a motion event is being split across multiple windows. */ - FLAG_SPLIT = 1 << 2, + SPLIT = 1 << 2, /* This flag indicates that the pointer coordinates dispatched to the application * will be zeroed out to avoid revealing information to an application. This is * used in conjunction with FLAG_DISPATCH_AS_OUTSIDE to prevent apps not sharing * the same UID from watching all touches. */ - FLAG_ZERO_COORDS = 1 << 3, + ZERO_COORDS = 1 << 3, /* This flag indicates that the event should be sent as is. * Should always be set unless the event is to be transmuted. */ - FLAG_DISPATCH_AS_IS = 1 << 8, + DISPATCH_AS_IS = 1 << 8, /* This flag indicates that a MotionEvent with AMOTION_EVENT_ACTION_DOWN falls outside * of the area of this target and so should instead be delivered as an * AMOTION_EVENT_ACTION_OUTSIDE to this target. */ - FLAG_DISPATCH_AS_OUTSIDE = 1 << 9, + DISPATCH_AS_OUTSIDE = 1 << 9, /* This flag indicates that a hover sequence is starting in the given window. * The event is transmuted into ACTION_HOVER_ENTER. */ - FLAG_DISPATCH_AS_HOVER_ENTER = 1 << 10, + DISPATCH_AS_HOVER_ENTER = 1 << 10, /* This flag indicates that a hover event happened outside of a window which handled * previous hover events, signifying the end of the current hover sequence for that * window. * The event is transmuted into ACTION_HOVER_ENTER. */ - FLAG_DISPATCH_AS_HOVER_EXIT = 1 << 11, + DISPATCH_AS_HOVER_EXIT = 1 << 11, /* This flag indicates that the event should be canceled. * It is used to transmute ACTION_MOVE into ACTION_CANCEL when a touch slips * outside of a window. */ - FLAG_DISPATCH_AS_SLIPPERY_EXIT = 1 << 12, + DISPATCH_AS_SLIPPERY_EXIT = 1 << 12, /* This flag indicates that the event should be dispatched as an initial down. * It is used to transmute ACTION_MOVE into ACTION_DOWN when a touch slips * into a new window. */ - FLAG_DISPATCH_AS_SLIPPERY_ENTER = 1 << 13, - - /* Mask for all dispatch modes. */ - FLAG_DISPATCH_MASK = FLAG_DISPATCH_AS_IS | FLAG_DISPATCH_AS_OUTSIDE | - FLAG_DISPATCH_AS_HOVER_ENTER | FLAG_DISPATCH_AS_HOVER_EXIT | - FLAG_DISPATCH_AS_SLIPPERY_EXIT | FLAG_DISPATCH_AS_SLIPPERY_ENTER, + DISPATCH_AS_SLIPPERY_ENTER = 1 << 13, /* This flag indicates that the target of a MotionEvent is partly or wholly * obscured by another visible window above it. The motion event should be * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED. */ - FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 1 << 14, - + WINDOW_IS_PARTIALLY_OBSCURED = 1 << 14, }; + /* Mask for all dispatch modes. */ + static constexpr const ftl::Flags DISPATCH_MASK = + ftl::Flags() | Flags::DISPATCH_AS_IS | Flags::DISPATCH_AS_OUTSIDE | + Flags::DISPATCH_AS_HOVER_ENTER | Flags::DISPATCH_AS_HOVER_EXIT | + Flags::DISPATCH_AS_SLIPPERY_EXIT | Flags::DISPATCH_AS_SLIPPERY_ENTER; + // The input channel to be targeted. std::shared_ptr inputChannel; // Flags for the input target. - int32_t flags = 0; + ftl::Flags flags; // Scaling factor to apply to MotionEvent as it is delivered. // (ignored for KeyEvents) diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index 0cc2c6da9b..ee7da93975 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -20,6 +20,7 @@ #include "InputTarget.h" #include "TouchState.h" +using namespace android::ftl::flag_operators; using android::base::StringPrintf; using android::gui::WindowInfo; using android::gui::WindowInfoHandle; @@ -30,14 +31,15 @@ void TouchState::reset() { *this = TouchState(); } -void TouchState::addOrUpdateWindow(const sp& windowHandle, int32_t targetFlags, - BitSet32 pointerIds, std::optional eventTime) { +void TouchState::addOrUpdateWindow(const sp& windowHandle, + ftl::Flags targetFlags, BitSet32 pointerIds, + std::optional eventTime) { for (size_t i = 0; i < windows.size(); i++) { TouchedWindow& touchedWindow = windows[i]; if (touchedWindow.windowHandle == windowHandle) { touchedWindow.targetFlags |= targetFlags; - if (targetFlags & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) { - touchedWindow.targetFlags &= ~InputTarget::FLAG_DISPATCH_AS_IS; + if (targetFlags.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT)) { + touchedWindow.targetFlags.clear(InputTarget::Flags::DISPATCH_AS_IS); } // For cases like hover enter/exit or DISPATCH_AS_OUTSIDE a touch window might not have // downTime set initially. Need to update existing window when an pointer is down for @@ -70,10 +72,10 @@ void TouchState::removeWindowByToken(const sp& token) { void TouchState::filterNonAsIsTouchWindows() { for (size_t i = 0; i < windows.size();) { TouchedWindow& window = windows[i]; - if (window.targetFlags & - (InputTarget::FLAG_DISPATCH_AS_IS | InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER)) { - window.targetFlags &= ~InputTarget::FLAG_DISPATCH_MASK; - window.targetFlags |= InputTarget::FLAG_DISPATCH_AS_IS; + if (window.targetFlags.any(InputTarget::Flags::DISPATCH_AS_IS | + InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER)) { + window.targetFlags.clear(InputTarget::DISPATCH_MASK); + window.targetFlags |= InputTarget::Flags::DISPATCH_AS_IS; i += 1; } else { windows.erase(windows.begin() + i); @@ -105,7 +107,7 @@ void TouchState::cancelPointersForNonPilferingWindows(const BitSet32 pointerIds) sp TouchState::getFirstForegroundWindowHandle() const { for (size_t i = 0; i < windows.size(); i++) { const TouchedWindow& window = windows[i]; - if (window.targetFlags & InputTarget::FLAG_FOREGROUND) { + if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) { return window.windowHandle; } } @@ -116,7 +118,7 @@ bool TouchState::isSlippery() const { // Must have exactly one foreground window. bool haveSlipperyForegroundWindow = false; for (const TouchedWindow& window : windows) { - if (window.targetFlags & InputTarget::FLAG_FOREGROUND) { + if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) { if (haveSlipperyForegroundWindow || !window.windowHandle->getInfo()->inputConfig.test( WindowInfo::InputConfig::SLIPPERY)) { diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index e4e59fd132..77c1cdf50a 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -40,7 +40,7 @@ struct TouchState { void reset(); void addOrUpdateWindow(const sp& windowHandle, - int32_t targetFlags, BitSet32 pointerIds, + ftl::Flags targetFlags, BitSet32 pointerIds, std::optional eventTime = std::nullopt); void removeWindowByToken(const sp& token); void filterNonAsIsTouchWindows(); diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp index ec51662abb..af745988ad 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.cpp +++ b/services/inputflinger/dispatcher/TouchedWindow.cpp @@ -27,9 +27,9 @@ namespace inputdispatcher { std::string TouchedWindow::dump() const { return StringPrintf("name='%s', pointerIds=0x%0x, " - "targetFlags=0x%x, firstDownTimeInTarget=%s\n", - windowHandle->getName().c_str(), pointerIds.value, targetFlags, - toString(firstDownTimeInTarget).c_str()); + "targetFlags=%s, firstDownTimeInTarget=%s\n", + windowHandle->getName().c_str(), pointerIds.value, + targetFlags.string().c_str(), toString(firstDownTimeInTarget).c_str()); } } // namespace inputdispatcher diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h index 0cc99999d0..dd08323dd4 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -18,6 +18,7 @@ #include #include +#include "InputTarget.h" namespace android { @@ -26,7 +27,7 @@ namespace inputdispatcher { // Focus tracking for touch. struct TouchedWindow { sp windowHandle; - int32_t targetFlags; + ftl::Flags targetFlags; BitSet32 pointerIds; bool isPilferingPointers = false; // Time at which the first action down occurred on this window. -- GitLab From 08f7a6adfbf8e9dcabe89417b1305280093d7cce Mon Sep 17 00:00:00 2001 From: Michael Wright Date: Sat, 22 Oct 2022 03:14:39 +0100 Subject: [PATCH 0483/1310] Add ftl_last to PointerControllerInterface::Presentation This way it can be printed using ftl::enum_string. Bug: 254277939 Test: manual Change-Id: Ie50e4a68073765af24718e84840dce7b03a18c8e --- services/inputflinger/include/PointerControllerInterface.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h index 647e10c974..7e0c1c77eb 100644 --- a/services/inputflinger/include/PointerControllerInterface.h +++ b/services/inputflinger/include/PointerControllerInterface.h @@ -79,6 +79,8 @@ public: POINTER, // Show spots and a spot anchor in place of the mouse pointer. SPOT, + + ftl_last = SPOT, }; /* Sets the mode of the pointer controller. */ -- GitLab From c9e53047c614d96785e497e4be0361b0b42c9a68 Mon Sep 17 00:00:00 2001 From: Michael Wright Date: Sat, 22 Oct 2022 03:18:32 +0100 Subject: [PATCH 0484/1310] Add dump to DisplayInfo. This way it can be dumped as part of PointerController's dump. Bug: 254277939 Test: manually inspect `dumpsys input` Change-Id: I9ad71d14c81af390f5b9ccb4fa75439bd4496c26 --- libs/gui/DisplayInfo.cpp | 18 ++++++++++++++++++ libs/gui/include/gui/DisplayInfo.h | 2 ++ 2 files changed, 20 insertions(+) diff --git a/libs/gui/DisplayInfo.cpp b/libs/gui/DisplayInfo.cpp index 52d9540eeb..bd640df81e 100644 --- a/libs/gui/DisplayInfo.cpp +++ b/libs/gui/DisplayInfo.cpp @@ -20,8 +20,13 @@ #include #include +#include #include +#include + +#define INDENT " " + namespace android::gui { // --- DisplayInfo --- @@ -67,4 +72,17 @@ status_t DisplayInfo::writeToParcel(android::Parcel* parcel) const { return OK; } +void DisplayInfo::dump(std::string& out, const char* prefix) const { + using android::base::StringAppendF; + + out += prefix; + StringAppendF(&out, "DisplayViewport[id=%" PRId32 "]\n", displayId); + out += prefix; + StringAppendF(&out, INDENT "Width=%" PRId32 ", Height=%" PRId32 "\n", logicalWidth, + logicalHeight); + std::string transformPrefix(prefix); + transformPrefix.append(INDENT); + transform.dump(out, "Transform", transformPrefix.c_str()); +} + } // namespace android::gui diff --git a/libs/gui/include/gui/DisplayInfo.h b/libs/gui/include/gui/DisplayInfo.h index 74f33a2a87..42b62c755c 100644 --- a/libs/gui/include/gui/DisplayInfo.h +++ b/libs/gui/include/gui/DisplayInfo.h @@ -41,6 +41,8 @@ struct DisplayInfo : public Parcelable { status_t writeToParcel(android::Parcel*) const override; status_t readFromParcel(const android::Parcel*) override; + + void dump(std::string& result, const char* prefix = "") const; }; } // namespace android::gui \ No newline at end of file -- GitLab From bd5ee8e808ea97bba6ebb999134badc8411c264b Mon Sep 17 00:00:00 2001 From: Bo Liu Date: Fri, 11 Nov 2022 14:59:37 -0500 Subject: [PATCH 0485/1310] Rename SC ndk jni API Rename to ASurfaceControl_fromJava / ASurfaceTransaction_fromJava. Test: Compiles Bug: 258245761 Change-Id: If26e5182218e4bdc95d6954628617e2db9faa6ee --- include/android/surface_control_jni.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/android/surface_control_jni.h b/include/android/surface_control_jni.h index a0a1fdb372..840f6e724b 100644 --- a/include/android/surface_control_jni.h +++ b/include/android/surface_control_jni.h @@ -44,7 +44,7 @@ __BEGIN_DECLS * * Available since API level 34. */ -ASurfaceControl* _Nonnull ASurfaceControl_fromSurfaceControl(JNIEnv* _Nonnull env, +ASurfaceControl* _Nonnull ASurfaceControl_fromJava(JNIEnv* _Nonnull env, jobject _Nonnull surfaceControlObj) __INTRODUCED_IN(__ANDROID_API_U__); /** @@ -59,7 +59,7 @@ ASurfaceControl* _Nonnull ASurfaceControl_fromSurfaceControl(JNIEnv* _Nonnull en * * Available since API level 34. */ -ASurfaceTransaction* _Nonnull ASurfaceTransaction_fromTransaction(JNIEnv* _Nonnull env, +ASurfaceTransaction* _Nonnull ASurfaceTransaction_fromJava(JNIEnv* _Nonnull env, jobject _Nonnull transactionObj) __INTRODUCED_IN(__ANDROID_API_U__); __END_DECLS -- GitLab From c6086ddd99c669422f9ebc9a17aa3a6a860a2db1 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Fri, 4 Nov 2022 04:39:35 +0000 Subject: [PATCH 0486/1310] SF: Get hwc slot in binder thread Avoid locking inside the main thread and contention with binder thread (via client token binder died). Test: presubmit Bug: 238781169 Change-Id: I61fe1d1f8babb0d301e894ed23c9c5d6be42c8a7 --- services/surfaceflinger/Layer.cpp | 11 +++++---- services/surfaceflinger/Layer.h | 5 +++- services/surfaceflinger/SurfaceFlinger.cpp | 13 ++++++---- services/surfaceflinger/TransactionState.h | 1 + .../fuzzer/surfaceflinger_layer_fuzzer.cpp | 2 +- .../unittests/TransactionFrameTracerTest.cpp | 2 +- .../unittests/TransactionSurfaceFrameTest.cpp | 24 +++++++++---------- 7 files changed, 35 insertions(+), 23 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 977709286b..5f5e2fde99 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -2846,7 +2846,7 @@ bool Layer::setPosition(float x, float y) { bool Layer::setBuffer(std::shared_ptr& buffer, const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime, bool isAutoTimestamp, std::optional dequeueTime, - const FrameTimelineInfo& info) { + const FrameTimelineInfo& info, int hwcBufferSlot) { ATRACE_FORMAT("setBuffer %s - hasBuffer=%s", getDebugName(), (buffer ? "true" : "false")); if (!buffer) { return false; @@ -2892,7 +2892,7 @@ bool Layer::setBuffer(std::shared_ptr& buffer, mDrawingState.releaseBufferListener = bufferData.releaseBufferListener; mDrawingState.buffer = std::move(buffer); mDrawingState.clientCacheId = bufferData.cachedBuffer; - + mDrawingState.hwcBufferSlot = hwcBufferSlot; mDrawingState.acquireFence = bufferData.flags.test(BufferData::BufferDataChange::fenceChanged) ? bufferData.acquireFence : Fence::NO_FENCE; @@ -3191,7 +3191,7 @@ void Layer::gatherBufferInfo() { mBufferInfo.mHdrMetadata = mDrawingState.hdrMetadata; mBufferInfo.mApi = mDrawingState.api; mBufferInfo.mTransformToDisplayInverse = mDrawingState.transformToDisplayInverse; - mBufferInfo.mBufferSlot = mHwcSlotGenerator->getHwcCacheSlot(mDrawingState.clientCacheId); + mBufferInfo.mBufferSlot = mDrawingState.hwcBufferSlot; } Rect Layer::computeBufferCrop(const State& s) { @@ -3210,7 +3210,6 @@ sp Layer::createClone() { LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata()); args.textureName = mTextureName; sp layer = mFlinger->getFactory().createBufferStateLayer(args); - layer->mHwcSlotGenerator = mHwcSlotGenerator; layer->setInitialValuesForClone(sp::fromExisting(this)); return layer; } @@ -3969,6 +3968,10 @@ void Layer::updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMet } } +int Layer::getHwcCacheSlot(const client_cache_t& clientCacheId) { + return mHwcSlotGenerator->getHwcCacheSlot(clientCacheId); +} + LayerSnapshotGuard::LayerSnapshotGuard(Layer* layer) : mLayer(layer) { if (mLayer) { mLayer->mLayerFE->mSnapshot = std::move(mLayer->mSnapshot); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index a3c4e5966c..57140aab9c 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -146,6 +146,7 @@ public: bool transformToDisplayInverse; Region transparentRegionHint; std::shared_ptr buffer; + int hwcBufferSlot; client_cache_t clientCacheId; sp acquireFence; std::shared_ptr acquireFenceTime; @@ -297,7 +298,8 @@ public: bool setBuffer(std::shared_ptr& /* buffer */, const BufferData& /* bufferData */, nsecs_t /* postTime */, nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/, - std::optional /* dequeueTime */, const FrameTimelineInfo& /*info*/); + std::optional /* dequeueTime */, const FrameTimelineInfo& /*info*/, + int /* hwcBufferSlot */); bool setDataspace(ui::Dataspace /*dataspace*/); bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/); bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/); @@ -810,6 +812,7 @@ public: void updateMetadataSnapshot(const LayerMetadata& parentMetadata); void updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata, std::unordered_set& visited); + int getHwcCacheSlot(const client_cache_t& clientCacheId); protected: // For unit tests diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 849fd9c7ce..42e9a64ea6 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3919,12 +3919,17 @@ status_t SurfaceFlinger::setTransactionState( auto& resolvedState = resolvedStates.back(); if (resolvedState.state.hasBufferChanges() && resolvedState.state.hasValidBuffer() && resolvedState.state.surface) { + sp layer = LayerHandle::getLayer(resolvedState.state.surface); + std::string layerName = (layer) ? + layer->getDebugName() : std::to_string(resolvedState.state.layerId); resolvedState.externalTexture = getExternalTextureFromBufferData(*resolvedState.state.bufferData, - std::to_string(resolvedState.state.layerId) - .c_str(), - transactionId); + layerName.c_str(), transactionId); mBufferCountTracker.increment(resolvedState.state.surface->localBinder()); + if (layer) { + resolvedState.hwcBufferSlot = + layer->getHwcCacheSlot(resolvedState.state.bufferData->cachedBuffer); + } } } @@ -4391,7 +4396,7 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime if (what & layer_state_t::eBufferChanged) { if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime, desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp, - frameTimelineInfo)) { + frameTimelineInfo, composerState.hwcBufferSlot)) { flags |= eTraversalNeeded; } } else if (frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) { diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h index f1ef31d81c..7bde2c1e23 100644 --- a/services/surfaceflinger/TransactionState.h +++ b/services/surfaceflinger/TransactionState.h @@ -33,6 +33,7 @@ public: ResolvedComposerState() = default; ResolvedComposerState(ComposerState&& source) { state = std::move(source.state); } std::shared_ptr externalTexture; + int hwcBufferSlot = 0; }; struct TransactionState { diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp index acfc1d4dc8..c5b3fa67ae 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp @@ -160,7 +160,7 @@ void LayerFuzzer::invokeBufferStateLayer() { layer->setBuffer(texture, {} /*bufferData*/, mFdp.ConsumeIntegral() /*postTime*/, mFdp.ConsumeIntegral() /*desiredTime*/, mFdp.ConsumeBool() /*isAutoTimestamp*/, - {mFdp.ConsumeIntegral()} /*dequeue*/, {} /*info*/); + {mFdp.ConsumeIntegral()} /*dequeue*/, {} /*info*/, 0 /* hwcslot */); LayerRenderArea layerArea(*(flinger.flinger()), layer, getFuzzedRect(), {mFdp.ConsumeIntegral(), diff --git a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp index 1173d1c876..09d002f6e8 100644 --- a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp @@ -126,7 +126,7 @@ public: HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); layer->setBuffer(externalTexture, bufferData, postTime, /*desiredPresentTime*/ 30, false, - dequeueTime, FrameTimelineInfo{}); + dequeueTime, FrameTimelineInfo{}, 0); commitTransaction(layer.get()); nsecs_t latchTime = 25; diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp index ae03db43a7..7dfbcc00d4 100644 --- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp @@ -131,7 +131,7 @@ public: FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo); + layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo, 0); acquireFence->signalForTest(12); commitTransaction(layer.get()); @@ -166,7 +166,7 @@ public: FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; - layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo); + layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo, 0); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX; @@ -183,7 +183,7 @@ public: 2ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo); + layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo, 0); nsecs_t end = systemTime(); acquireFence2->signalForTest(12); @@ -229,7 +229,7 @@ public: 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo); + layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo, 0); acquireFence->signalForTest(12); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); @@ -264,7 +264,7 @@ public: FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo); + layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo, 0); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); @@ -307,7 +307,7 @@ public: FrameTimelineInfo ftInfo3; ftInfo3.vsyncId = 3; ftInfo3.inputEventId = 0; - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo3); + layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo3, 0); EXPECT_EQ(2u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); const auto bufferSurfaceFrameTX = layer->mDrawingState.bufferSurfaceFrameTX; @@ -352,7 +352,7 @@ public: FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; - layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo); + layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo, 0); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX; @@ -367,7 +367,7 @@ public: 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo); + layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo, 0); acquireFence2->signalForTest(12); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); @@ -404,7 +404,7 @@ public: FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; - layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo); + layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo, 0); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); const auto droppedSurfaceFrame1 = layer->mDrawingState.bufferSurfaceFrameTX; @@ -424,7 +424,7 @@ public: FrameTimelineInfo ftInfoInv; ftInfoInv.vsyncId = FrameTimelineInfo::INVALID_VSYNC_ID; ftInfoInv.inputEventId = 0; - layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfoInv); + layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfoInv, 0); auto dropEndTime1 = systemTime(); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); @@ -445,7 +445,7 @@ public: FrameTimelineInfo ftInfo2; ftInfo2.vsyncId = 2; ftInfo2.inputEventId = 0; - layer->setBuffer(externalTexture3, bufferData, 10, 20, false, std::nullopt, ftInfo2); + layer->setBuffer(externalTexture3, bufferData, 10, 20, false, std::nullopt, ftInfo2, 0); auto dropEndTime2 = systemTime(); acquireFence3->signalForTest(12); @@ -494,7 +494,7 @@ public: FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo); + layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo, 0); FrameTimelineInfo ftInfo2; ftInfo2.vsyncId = 2; ftInfo2.inputEventId = 0; -- GitLab From 5a655b8d00ba8fbd0c876ed0c075e2e8b649935d Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Wed, 7 Sep 2022 13:17:09 -0400 Subject: [PATCH 0487/1310] Add display name and id to traces On a multi-display device, it is helpful to know which display particular methods are called for. Add information to traces specifying the name and id of the display. The device still needs to specify different names for displays (see b/254851304), but the traces will now show the id, which is helpful until the device is updated, at which point the names will be immediately helpful. Output: - Store a string that combines the readable name and the DisplayId. This saves allocation costs for the various places where we add this info to the trace. - Print the name and id in present and postFramebuffer. Although postFramebuffer is called by present, future CLs will move it to a separate thread, and this allows tracking which call applies to which display. DisplaySettings: - Add a field with the display name and id. This allows SkiaRenderEngine to print them as well. SkiaRenderEngine: - Print the name and id in drawLayersInternal - Replace the outdated text for the function name referring to the class as "SkiaGL" with a simple call to __func__. Display: - Print the name and id in chooseClientCompositionStrategy, which may also run in another thread. Bug: 241285473 Test: manual; look at traces Change-Id: I3081c4e4a7b5874139af6b5dd74a6a8ab0ad8cf7 --- .../include/renderengine/DisplaySettings.h | 12 ++++++++++-- libs/renderengine/skia/SkiaRenderEngine.cpp | 2 +- .../include/compositionengine/impl/Output.h | 3 +++ .../surfaceflinger/CompositionEngine/src/Display.cpp | 3 ++- .../surfaceflinger/CompositionEngine/src/Output.cpp | 9 +++++++-- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h index 25fe9f2d8e..8d7c13cb18 100644 --- a/libs/renderengine/include/renderengine/DisplaySettings.h +++ b/libs/renderengine/include/renderengine/DisplaySettings.h @@ -23,17 +23,24 @@ #include #include #include +#include #include #include #include #include +#include + namespace android { namespace renderengine { // DisplaySettings contains the settings that are applicable when drawing all // layers for a given display. struct DisplaySettings { + // A string containing the name of the display, along with its id, if it has + // one. + std::string namePlusId; + // Rectangle describing the physical display. We will project from the // logical clip onto this rectangle. Rect physicalDisplay = Rect::INVALID_RECT; @@ -85,8 +92,8 @@ struct DisplaySettings { }; static inline bool operator==(const DisplaySettings& lhs, const DisplaySettings& rhs) { - return lhs.physicalDisplay == rhs.physicalDisplay && lhs.clip == rhs.clip && - lhs.maxLuminance == rhs.maxLuminance && + return lhs.namePlusId == rhs.namePlusId && lhs.physicalDisplay == rhs.physicalDisplay && + lhs.clip == rhs.clip && lhs.maxLuminance == rhs.maxLuminance && lhs.currentLuminanceNits == rhs.currentLuminanceNits && lhs.outputDataspace == rhs.outputDataspace && lhs.colorTransform == rhs.colorTransform && @@ -121,6 +128,7 @@ static const char* orientation_to_string(uint32_t orientation) { static inline void PrintTo(const DisplaySettings& settings, ::std::ostream* os) { *os << "DisplaySettings {"; + *os << "\n .display = " << settings.namePlusId; *os << "\n .physicalDisplay = "; PrintTo(settings.physicalDisplay, os); *os << "\n .clip = "; diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index b9aa5acd30..fca6c0e486 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -636,7 +636,7 @@ void SkiaRenderEngine::drawLayersInternal( const DisplaySettings& display, const std::vector& layers, const std::shared_ptr& buffer, const bool /*useFramebufferCache*/, base::unique_fd&& bufferFence) { - ATRACE_NAME("SkiaGL::drawLayersInternal"); + ATRACE_FORMAT("%s for %s", __func__, display.namePlusId.c_str()); std::lock_guard lock(mRenderingMutex); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h index e06da33025..9ca5da95bb 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h @@ -155,6 +155,8 @@ protected: bool mustRecompose() const; + const std::string& getNamePlusId() const { return mNamePlusId; } + private: void dirtyEntireOutput(); void updateCompositionStateForBorder(const compositionengine::CompositionRefreshArgs&); @@ -165,6 +167,7 @@ private: const compositionengine::CompositionRefreshArgs&) const; std::string mName; + std::string mNamePlusId; std::unique_ptr mDisplayColorProfile; std::unique_ptr mRenderSurface; diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp index 1c5cbedd88..24669c2ff5 100644 --- a/services/surfaceflinger/CompositionEngine/src/Display.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -235,7 +236,7 @@ void Display::beginFrame() { bool Display::chooseCompositionStrategy( std::optional* outChanges) { - ATRACE_CALL(); + ATRACE_FORMAT("%s for %s", __func__, getNamePlusId().c_str()); ALOGV(__FUNCTION__); if (mIsDisconnected) { diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index d1daca6090..3ee8017d39 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include @@ -116,6 +117,9 @@ const std::string& Output::getName() const { void Output::setName(const std::string& name) { mName = name; + auto displayIdOpt = getDisplayId(); + mNamePlusId = base::StringPrintf("%s (%s)", mName.c_str(), + displayIdOpt ? to_string(*displayIdOpt).c_str() : "NA"); } void Output::setCompositionEnabled(bool enabled) { @@ -427,7 +431,7 @@ void Output::prepare(const compositionengine::CompositionRefreshArgs& refreshArg } void Output::present(const compositionengine::CompositionRefreshArgs& refreshArgs) { - ATRACE_CALL(); + ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str()); ALOGV(__FUNCTION__); updateColorProfile(refreshArgs); @@ -1322,6 +1326,7 @@ renderengine::DisplaySettings Output::generateClientCompositionDisplaySettings() const auto& outputState = getState(); renderengine::DisplaySettings clientCompositionDisplay; + clientCompositionDisplay.namePlusId = mNamePlusId; clientCompositionDisplay.physicalDisplay = outputState.framebufferSpace.getContent(); clientCompositionDisplay.clip = outputState.layerStackSpace.getContent(); clientCompositionDisplay.orientation = @@ -1488,7 +1493,7 @@ bool Output::isPowerHintSessionEnabled() { } void Output::postFramebuffer() { - ATRACE_CALL(); + ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str()); ALOGV(__FUNCTION__); if (!getState().isEnabled) { -- GitLab From f0c5ccab2a66b2b5829daf659ae82de00aa56525 Mon Sep 17 00:00:00 2001 From: Xiang Wang Date: Wed, 9 Nov 2022 18:03:09 -0800 Subject: [PATCH 0488/1310] Add CUSTOM mode to native stats Bug: b/240335717 Test: atest TimeStatsTest Change-Id: I1f88b7182bee78b5bc56be4d59a0e9b85771d834 --- libs/gui/include/gui/LayerMetadata.h | 3 +- .../surfaceflinger/TimeStats/TimeStats.cpp | 2 ++ .../timestatsatomsproto/timestats_atoms.proto | 1 + .../tests/unittests/TimeStatsTest.cpp | 31 +++++++++++++++---- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/libs/gui/include/gui/LayerMetadata.h b/libs/gui/include/gui/LayerMetadata.h index e16f89c6a5..9cf62bc7d6 100644 --- a/libs/gui/include/gui/LayerMetadata.h +++ b/libs/gui/include/gui/LayerMetadata.h @@ -66,8 +66,9 @@ enum class GameMode : int32_t { Standard = 1, Performance = 2, Battery = 3, + Custom = 4, - ftl_last = Battery + ftl_last = Custom }; } // namespace android::gui diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp index e5a9dd47c3..e860d88cd1 100644 --- a/services/surfaceflinger/TimeStats/TimeStats.cpp +++ b/services/surfaceflinger/TimeStats/TimeStats.cpp @@ -68,6 +68,8 @@ SurfaceflingerStatsLayerInfo_GameMode gameModeToProto(GameMode gameMode) { return SurfaceflingerStatsLayerInfo::GAME_MODE_PERFORMANCE; case GameMode::Battery: return SurfaceflingerStatsLayerInfo::GAME_MODE_BATTERY; + case GameMode::Custom: + return SurfaceflingerStatsLayerInfo::GAME_MODE_CUSTOM; default: return SurfaceflingerStatsLayerInfo::GAME_MODE_UNSPECIFIED; } diff --git a/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto b/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto index e45757ddfd..d4d444e85e 100644 --- a/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto +++ b/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto @@ -173,6 +173,7 @@ message SurfaceflingerStatsLayerInfo { GAME_MODE_STANDARD = 2; GAME_MODE_PERFORMANCE = 3; GAME_MODE_BATTERY = 4; + GAME_MODE_CUSTOM = 5; } // Game mode that the layer was running at. Used to track user engagement diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp index 6ffc0396d7..1dd4f254fb 100644 --- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp +++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp @@ -1320,6 +1320,7 @@ TEST_F(TimeStatsTest, layerStatsCallback_multipleGameModes) { insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 3, 3000000, {}, GameMode::Performance); insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 4, 4000000, {}, GameMode::Battery); insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 5, 4000000, {}, GameMode::Battery); + insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 6, 5000000, {}, GameMode::Custom); std::string pulledData; EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData)); @@ -1327,9 +1328,9 @@ TEST_F(TimeStatsTest, layerStatsCallback_multipleGameModes) { SurfaceflingerStatsLayerInfoWrapper atomList; ASSERT_TRUE(atomList.ParseFromString(pulledData)); // The first time record is never uploaded to stats. - ASSERT_EQ(atomList.atom_size(), 3); + ASSERT_EQ(atomList.atom_size(), 4); // Layers are ordered based on the hash in LayerStatsKey. For this test, the order happens to - // be: 0 - Battery 1 - Performance 2 - Standard + // be: 0 - Battery 1 - Custom 2 - Performance 3 - Standard const SurfaceflingerStatsLayerInfo& atom0 = atomList.atom(0); EXPECT_EQ(atom0.layer_name(), genLayerName(LAYER_ID_0)); @@ -1364,7 +1365,7 @@ TEST_F(TimeStatsTest, layerStatsCallback_multipleGameModes) { EXPECT_EQ(atom1.uid(), UID_0); EXPECT_EQ(atom1.display_refresh_rate_bucket(), REFRESH_RATE_BUCKET_0); EXPECT_EQ(atom1.render_rate_bucket(), RENDER_RATE_BUCKET_0); - EXPECT_EQ(atom1.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_PERFORMANCE); + EXPECT_EQ(atom1.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_CUSTOM); const SurfaceflingerStatsLayerInfo& atom2 = atomList.atom(2); @@ -1377,12 +1378,30 @@ TEST_F(TimeStatsTest, layerStatsCallback_multipleGameModes) { EXPECT_THAT(atom2.latch_to_present(), HistogramEq(buildExpectedHistogram({2}, {1}))); EXPECT_THAT(atom2.desired_to_present(), HistogramEq(buildExpectedHistogram({1}, {1}))); EXPECT_THAT(atom2.post_to_acquire(), HistogramEq(buildExpectedHistogram({1}, {1}))); - EXPECT_EQ(atom2.late_acquire_frames(), LATE_ACQUIRE_FRAMES); - EXPECT_EQ(atom2.bad_desired_present_frames(), BAD_DESIRED_PRESENT_FRAMES); + EXPECT_EQ(atom2.late_acquire_frames(), 0); + EXPECT_EQ(atom2.bad_desired_present_frames(), 0); EXPECT_EQ(atom2.uid(), UID_0); EXPECT_EQ(atom2.display_refresh_rate_bucket(), REFRESH_RATE_BUCKET_0); EXPECT_EQ(atom2.render_rate_bucket(), RENDER_RATE_BUCKET_0); - EXPECT_EQ(atom2.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_STANDARD); + EXPECT_EQ(atom2.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_PERFORMANCE); + + const SurfaceflingerStatsLayerInfo& atom3 = atomList.atom(3); + + EXPECT_EQ(atom3.layer_name(), genLayerName(LAYER_ID_0)); + EXPECT_EQ(atom3.total_frames(), 1); + EXPECT_EQ(atom3.dropped_frames(), 0); + EXPECT_THAT(atom3.present_to_present(), HistogramEq(buildExpectedHistogram({1}, {1}))); + EXPECT_THAT(atom3.post_to_present(), HistogramEq(buildExpectedHistogram({4}, {1}))); + EXPECT_THAT(atom3.acquire_to_present(), HistogramEq(buildExpectedHistogram({3}, {1}))); + EXPECT_THAT(atom3.latch_to_present(), HistogramEq(buildExpectedHistogram({2}, {1}))); + EXPECT_THAT(atom3.desired_to_present(), HistogramEq(buildExpectedHistogram({1}, {1}))); + EXPECT_THAT(atom3.post_to_acquire(), HistogramEq(buildExpectedHistogram({1}, {1}))); + EXPECT_EQ(atom3.late_acquire_frames(), LATE_ACQUIRE_FRAMES); + EXPECT_EQ(atom3.bad_desired_present_frames(), BAD_DESIRED_PRESENT_FRAMES); + EXPECT_EQ(atom3.uid(), UID_0); + EXPECT_EQ(atom3.display_refresh_rate_bucket(), REFRESH_RATE_BUCKET_0); + EXPECT_EQ(atom3.render_rate_bucket(), RENDER_RATE_BUCKET_0); + EXPECT_EQ(atom3.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_STANDARD); } TEST_F(TimeStatsTest, layerStatsCallback_pullsMultipleLayers) { -- GitLab From 3388bf9c1901881d7db25f6f681b99416a88a421 Mon Sep 17 00:00:00 2001 From: Roman Kiryanov Date: Fri, 11 Nov 2022 15:35:16 -0800 Subject: [PATCH 0489/1310] Do not log an error about ro.sf.lcd_density on emulators The code already has all support, but still logs an error. Bug: 258856313 Test: boot emulator, check logcat Change-Id: I69630586ca234db21ea1fbe0d70bbe21155f0b4d Signed-off-by: Roman Kiryanov --- services/surfaceflinger/SurfaceFlinger.cpp | 3 ++- services/surfaceflinger/SurfaceFlinger.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index a4fb0dc774..59ed80528d 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -310,8 +310,9 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory, SkipInitializationTag) mCompositionEngine(mFactory.createCompositionEngine()), mHwcServiceName(base::GetProperty("debug.sf.hwc_service_name"s, "default"s)), mTunnelModeEnabledReporter(sp::make()), - mInternalDisplayDensity(getDensityFromProperty("ro.sf.lcd_density", true)), mEmulatedDisplayDensity(getDensityFromProperty("qemu.sf.lcd_density", false)), + mInternalDisplayDensity( + getDensityFromProperty("ro.sf.lcd_density", !mEmulatedDisplayDensity)), mPowerAdvisor(std::make_unique(*this)), mWindowInfosListenerInvoker(sp::make()) { ALOGI("Using HWComposer service: %s", mHwcServiceName.c_str()); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index df9006e284..5e2af7b45d 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1285,8 +1285,8 @@ private: sp mTunnelModeEnabledReporter; ui::DisplayPrimaries mInternalDisplayPrimaries; - const float mInternalDisplayDensity; const float mEmulatedDisplayDensity; + const float mInternalDisplayDensity; // Should only be accessed by the main thread. sp mInputFlinger; -- GitLab From 01c31167ce5880ec5f6f97ac82c81c85c9807f19 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Mon, 14 Nov 2022 17:45:19 +0000 Subject: [PATCH 0490/1310] SF: Fix use after move bug in screen capture refactor Bug: 238643986 Test: presubmits Change-Id: I1ff3d214eafcf02a1a50e2dc80a0ff4c01807aaf --- .../surfaceflinger/ScreenCaptureOutput.cpp | 25 +++++++++++++------ services/surfaceflinger/ScreenCaptureOutput.h | 7 ++++-- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/services/surfaceflinger/ScreenCaptureOutput.cpp b/services/surfaceflinger/ScreenCaptureOutput.cpp index 8f93ba40ee..37b3218138 100644 --- a/services/surfaceflinger/ScreenCaptureOutput.cpp +++ b/services/surfaceflinger/ScreenCaptureOutput.cpp @@ -24,10 +24,16 @@ namespace android { -std::shared_ptr createScreenCaptureOutput(ScreenCaptureOutputArgs&& args) { +std::shared_ptr createScreenCaptureOutput(ScreenCaptureOutputArgs args) { std::shared_ptr output = compositionengine::impl::createOutputTemplated< - ScreenCaptureOutput, compositionengine::CompositionEngine, - ScreenCaptureOutputArgs&&>(args.compositionEngine, std::move(args)); + ScreenCaptureOutput, compositionengine::CompositionEngine, const RenderArea&, + std::unordered_set, + const compositionengine::Output::ColorProfile&, bool>(args.compositionEngine, + args.renderArea, + std::move( + args.filterForScreenshot), + args.colorProfile, + args.regionSampling); output->editState().isSecure = args.renderArea.isSecure(); output->setCompositionEnabled(true); output->setLayerFilter({args.layerStack}); @@ -50,11 +56,14 @@ std::shared_ptr createScreenCaptureOutput(ScreenCaptureOutp return output; } -ScreenCaptureOutput::ScreenCaptureOutput(ScreenCaptureOutputArgs&& args) - : mRenderArea(args.renderArea), - mFilterForScreenshot(std::move(args.filterForScreenshot)), - mColorProfile(args.colorProfile), - mRegionSampling(args.regionSampling) {} +ScreenCaptureOutput::ScreenCaptureOutput( + const RenderArea& renderArea, + std::unordered_set filterForScreenshot, + const compositionengine::Output::ColorProfile& colorProfile, bool regionSampling) + : mRenderArea(renderArea), + mFilterForScreenshot(std::move(filterForScreenshot)), + mColorProfile(colorProfile), + mRegionSampling(regionSampling) {} void ScreenCaptureOutput::updateColorProfile(const compositionengine::CompositionRefreshArgs&) { auto& outputState = editState(); diff --git a/services/surfaceflinger/ScreenCaptureOutput.h b/services/surfaceflinger/ScreenCaptureOutput.h index 61b5ddb1bb..5dffc1d530 100644 --- a/services/surfaceflinger/ScreenCaptureOutput.h +++ b/services/surfaceflinger/ScreenCaptureOutput.h @@ -43,7 +43,10 @@ struct ScreenCaptureOutputArgs { // SurfaceFlinger::captureLayers and SurfaceFlinger::captureDisplay. class ScreenCaptureOutput : public compositionengine::impl::Output { public: - ScreenCaptureOutput(ScreenCaptureOutputArgs&&); + ScreenCaptureOutput(const RenderArea& renderArea, + std::unordered_set filterForScreenshot, + const compositionengine::Output::ColorProfile& colorProfile, + bool regionSampling); void updateColorProfile(const compositionengine::CompositionRefreshArgs&) override; @@ -64,6 +67,6 @@ private: const bool mRegionSampling; }; -std::shared_ptr createScreenCaptureOutput(ScreenCaptureOutputArgs&&); +std::shared_ptr createScreenCaptureOutput(ScreenCaptureOutputArgs); } // namespace android -- GitLab From 1f0911e6a13ce402c9d9514813b5c7a772735cb4 Mon Sep 17 00:00:00 2001 From: Ian Elliott Date: Fri, 9 Sep 2022 16:31:47 -0600 Subject: [PATCH 0491/1310] Add SkiaVk backend to RenderEngine This CL adds a new backend, SkiaVk, to RenderEngine. The new functionality is to create a Vulkan device/instance (possibly protected) and handle flush/waitFence via VkSemaphores fed to GrBackendSemaphores. + make ctors of GLES/Vk RE's private so as to ensure GrContexts are created Test: atest librenderengine_test Bug: 236390072 Change-Id: I69119623b194885bcc4cf2ddc8e592576b713b19 --- libs/gui/SyncFeatures.cpp | 10 +- libs/renderengine/Android.bp | 2 + libs/renderengine/RenderEngine.cpp | 11 + .../benchmark/RenderEngineBench.cpp | 4 + .../include/renderengine/RenderEngine.h | 15 +- libs/renderengine/skia/SkiaGLRenderEngine.cpp | 6 +- libs/renderengine/skia/SkiaGLRenderEngine.h | 6 +- libs/renderengine/skia/SkiaRenderEngine.cpp | 8 +- libs/renderengine/skia/SkiaVkRenderEngine.cpp | 675 ++++++++++++++++++ libs/renderengine/skia/SkiaVkRenderEngine.h | 58 ++ libs/renderengine/tests/RenderEngineTest.cpp | 306 +++++++- .../CompositionEngine/Android.bp | 5 + services/surfaceflinger/SurfaceFlinger.cpp | 4 + 13 files changed, 1084 insertions(+), 26 deletions(-) create mode 100644 libs/renderengine/skia/SkiaVkRenderEngine.cpp create mode 100644 libs/renderengine/skia/SkiaVkRenderEngine.h diff --git a/libs/gui/SyncFeatures.cpp b/libs/gui/SyncFeatures.cpp index 1a8fc1a00a..2d863c2585 100644 --- a/libs/gui/SyncFeatures.cpp +++ b/libs/gui/SyncFeatures.cpp @@ -36,8 +36,12 @@ SyncFeatures::SyncFeatures() : Singleton(), mHasFenceSync(false), mHasWaitSync(false) { EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY); - // This can only be called after EGL has been initialized; otherwise the - // check below will abort. + // eglQueryString can only be called after EGL has been initialized; + // otherwise the check below will abort. If RenderEngine is using SkiaVk, + // EGL will not have been initialized. There's no problem with initializing + // it again here (it is ref counted), and then terminating it later. + EGLBoolean initialized = eglInitialize(dpy, nullptr, nullptr); + LOG_ALWAYS_FATAL_IF(!initialized, "eglInitialize failed"); const char* exts = eglQueryString(dpy, EGL_EXTENSIONS); LOG_ALWAYS_FATAL_IF(exts == nullptr, "eglQueryString failed"); if (strstr(exts, "EGL_ANDROID_native_fence_sync")) { @@ -63,6 +67,8 @@ SyncFeatures::SyncFeatures() : Singleton(), mString.append(" EGL_KHR_wait_sync"); } mString.append("]"); + // Terminate EGL to match the eglInitialize above + eglTerminate(dpy); } bool SyncFeatures::useNativeFenceSync() const { diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp index 054053898c..04e24ed9ed 100644 --- a/libs/renderengine/Android.bp +++ b/libs/renderengine/Android.bp @@ -42,6 +42,7 @@ cc_defaults { "libsync", "libui", "libutils", + "libvulkan", ], static_libs: [ @@ -97,6 +98,7 @@ filegroup { "skia/ColorSpaces.cpp", "skia/SkiaRenderEngine.cpp", "skia/SkiaGLRenderEngine.cpp", + "skia/SkiaVkRenderEngine.cpp", "skia/debug/CaptureTimer.cpp", "skia/debug/CommonPool.cpp", "skia/debug/SkiaCapture.cpp", diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp index f1fc0a45ad..d08c2213ad 100644 --- a/libs/renderengine/RenderEngine.cpp +++ b/libs/renderengine/RenderEngine.cpp @@ -23,6 +23,7 @@ #include "threaded/RenderEngineThreaded.h" #include "skia/SkiaGLRenderEngine.h" +#include "skia/SkiaVkRenderEngine.h" namespace android { namespace renderengine { @@ -37,6 +38,9 @@ std::unique_ptr RenderEngine::create(const RenderEngineCreationArg case RenderEngineType::SKIA_GL: ALOGD("RenderEngine with SkiaGL Backend"); return renderengine::skia::SkiaGLRenderEngine::create(args); + case RenderEngineType::SKIA_VK: + ALOGD("RenderEngine with SkiaVK Backend"); + return renderengine::skia::SkiaVkRenderEngine::create(args); case RenderEngineType::SKIA_GL_THREADED: { ALOGD("Threaded RenderEngine with SkiaGL Backend"); return renderengine::threaded::RenderEngineThreaded::create( @@ -45,6 +49,13 @@ std::unique_ptr RenderEngine::create(const RenderEngineCreationArg }, args.renderEngineType); } + case RenderEngineType::SKIA_VK_THREADED: + ALOGD("Threaded RenderEngine with SkiaVK Backend"); + return renderengine::threaded::RenderEngineThreaded::create( + [args]() { + return android::renderengine::skia::SkiaVkRenderEngine::create(args); + }, + args.renderEngineType); case RenderEngineType::GLES: default: ALOGD("RenderEngine with GLES Backend"); diff --git a/libs/renderengine/benchmark/RenderEngineBench.cpp b/libs/renderengine/benchmark/RenderEngineBench.cpp index d44eb463f7..bd7b617ae7 100644 --- a/libs/renderengine/benchmark/RenderEngineBench.cpp +++ b/libs/renderengine/benchmark/RenderEngineBench.cpp @@ -39,6 +39,10 @@ std::string RenderEngineTypeName(RenderEngine::RenderEngineType type) { return "skiaglthreaded"; case RenderEngine::RenderEngineType::SKIA_GL: return "skiagl"; + case RenderEngine::RenderEngineType::SKIA_VK: + return "skiavk"; + case RenderEngine::RenderEngineType::SKIA_VK_THREADED: + return "skiavkthreaded"; case RenderEngine::RenderEngineType::GLES: case RenderEngine::RenderEngineType::THREADED: LOG_ALWAYS_FATAL("GLESRenderEngine is deprecated - why time it?"); diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h index 9182febbe0..39621cd080 100644 --- a/libs/renderengine/include/renderengine/RenderEngine.h +++ b/libs/renderengine/include/renderengine/RenderEngine.h @@ -99,6 +99,8 @@ public: THREADED = 2, SKIA_GL = 3, SKIA_GL_THREADED = 4, + SKIA_VK = 5, + SKIA_VK_THREADED = 6, }; static std::unique_ptr create(const RenderEngineCreationArgs& args); @@ -170,9 +172,16 @@ public: virtual void cleanupPostRender() = 0; virtual void cleanFramebufferCache() = 0; - // Returns the priority this context was actually created with. Note: this may not be - // the same as specified at context creation time, due to implementation limits on the - // number of contexts that can be created at a specific priority level in the system. + + // Returns the priority this context was actually created with. Note: this + // may not be the same as specified at context creation time, due to + // implementation limits on the number of contexts that can be created at a + // specific priority level in the system. + // + // This should return a valid EGL context priority enum as described by + // https://registry.khronos.org/EGL/extensions/IMG/EGL_IMG_context_priority.txt + // or + // https://registry.khronos.org/EGL/extensions/NV/EGL_NV_context_priority_realtime.txt virtual int getContextPriority() = 0; // Returns true if blur was requested in the RenderEngineCreationArgs and the implementation diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index 347b8b7856..ff598e7ab5 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -193,9 +193,9 @@ std::unique_ptr SkiaGLRenderEngine::create( } // initialize the renderer while GL is current - std::unique_ptr engine = - std::make_unique(args, display, ctxt, placeholder, protectedContext, - protectedPlaceholder); + std::unique_ptr engine(new SkiaGLRenderEngine(args, display, ctxt, + placeholder, protectedContext, + protectedPlaceholder)); engine->ensureGrContextsCreated(); ALOGI("OpenGL ES informations:"); diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h index 4a37ffee83..af3311041d 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.h +++ b/libs/renderengine/skia/SkiaGLRenderEngine.h @@ -52,9 +52,6 @@ namespace skia { class SkiaGLRenderEngine : public skia::SkiaRenderEngine { public: static std::unique_ptr create(const RenderEngineCreationArgs& args); - SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display, EGLContext ctxt, - EGLSurface placeholder, EGLContext protectedContext, - EGLSurface protectedPlaceholder); ~SkiaGLRenderEngine() override; int getContextPriority() override; @@ -70,6 +67,9 @@ protected: void appendBackendSpecificInfoToDump(std::string& result) override; private: + SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display, EGLContext ctxt, + EGLSurface placeholder, EGLContext protectedContext, + EGLSurface protectedPlaceholder); bool waitGpuFence(base::borrowed_fd fenceFd); base::unique_fd flush(); static EGLConfig chooseEglConfig(EGLDisplay display, int format, bool logConfig); diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index b9aa5acd30..c3f66497f0 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -388,9 +388,11 @@ void SkiaRenderEngine::ensureGrContextsCreated() { void SkiaRenderEngine::mapExternalTextureBuffer(const sp& buffer, bool isRenderable) { - // Only run this if RE is running on its own thread. This way the access to GL - // operations is guaranteed to be happening on the same thread. - if (mRenderEngineType != RenderEngineType::SKIA_GL_THREADED) { + // Only run this if RE is running on its own thread. This + // way the access to GL operations is guaranteed to be happening on the + // same thread. + if (mRenderEngineType != RenderEngineType::SKIA_GL_THREADED && + mRenderEngineType != RenderEngineType::SKIA_VK_THREADED) { return; } // We currently don't attempt to map a buffer if the buffer contains protected content diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp new file mode 100644 index 0000000000..f9424f0f15 --- /dev/null +++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp @@ -0,0 +1,675 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// #define LOG_NDEBUG 0 +#undef LOG_TAG +#define LOG_TAG "RenderEngine" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + +#include "SkiaVkRenderEngine.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include "log/log_main.h" + +namespace android { +namespace renderengine { + +struct VulkanFuncs { + PFN_vkCreateSemaphore vkCreateSemaphore = nullptr; + PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR = nullptr; + PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR = nullptr; + PFN_vkDestroySemaphore vkDestroySemaphore = nullptr; + + PFN_vkDeviceWaitIdle vkDeviceWaitIdle = nullptr; + PFN_vkDestroyDevice vkDestroyDevice = nullptr; + PFN_vkDestroyInstance vkDestroyInstance = nullptr; +}; + +struct VulkanInterface { + bool initialized = false; + VkInstance instance; + VkPhysicalDevice physicalDevice; + VkDevice device; + VkQueue queue; + int queueIndex; + uint32_t apiVersion; + GrVkExtensions grExtensions; + VkPhysicalDeviceFeatures2* physicalDeviceFeatures2 = nullptr; + VkPhysicalDeviceSamplerYcbcrConversionFeatures* samplerYcbcrConversionFeatures = nullptr; + VkPhysicalDeviceProtectedMemoryProperties* protectedMemoryFeatures = nullptr; + GrVkGetProc grGetProc; + bool isProtected; + bool isRealtimePriority; + + VulkanFuncs funcs; + + std::vector instanceExtensionNames; + std::vector deviceExtensionNames; + + GrVkBackendContext getBackendContext() { + GrVkBackendContext backendContext; + backendContext.fInstance = instance; + backendContext.fPhysicalDevice = physicalDevice; + backendContext.fDevice = device; + backendContext.fQueue = queue; + backendContext.fGraphicsQueueIndex = queueIndex; + backendContext.fMaxAPIVersion = apiVersion; + backendContext.fVkExtensions = &grExtensions; + backendContext.fDeviceFeatures2 = physicalDeviceFeatures2; + backendContext.fGetProc = grGetProc; + backendContext.fProtectedContext = isProtected ? GrProtected::kYes : GrProtected::kNo; + return backendContext; + }; + + VkSemaphore createExportableSemaphore() { + VkExportSemaphoreCreateInfo exportInfo; + exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO; + exportInfo.pNext = nullptr; + exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + + VkSemaphoreCreateInfo semaphoreInfo; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + semaphoreInfo.pNext = &exportInfo; + semaphoreInfo.flags = 0; + + VkSemaphore semaphore; + VkResult err = funcs.vkCreateSemaphore(device, &semaphoreInfo, nullptr, &semaphore); + if (VK_SUCCESS != err) { + ALOGE("%s: failed to create semaphore. err %d\n", __func__, err); + return VK_NULL_HANDLE; + } + + return semaphore; + } + + // syncFd cannot be <= 0 + VkSemaphore importSemaphoreFromSyncFd(int syncFd) { + VkSemaphoreCreateInfo semaphoreInfo; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + semaphoreInfo.pNext = nullptr; + semaphoreInfo.flags = 0; + + VkSemaphore semaphore; + VkResult err = funcs.vkCreateSemaphore(device, &semaphoreInfo, nullptr, &semaphore); + if (VK_SUCCESS != err) { + ALOGE("%s: failed to create import semaphore", __func__); + return VK_NULL_HANDLE; + } + + VkImportSemaphoreFdInfoKHR importInfo; + importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR; + importInfo.pNext = nullptr; + importInfo.semaphore = semaphore; + importInfo.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT; + importInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + importInfo.fd = syncFd; + + err = funcs.vkImportSemaphoreFdKHR(device, &importInfo); + if (VK_SUCCESS != err) { + funcs.vkDestroySemaphore(device, semaphore, nullptr); + ALOGE("%s: failed to import semaphore", __func__); + return VK_NULL_HANDLE; + } + + return semaphore; + } + + int exportSemaphoreSyncFd(VkSemaphore semaphore) { + int res; + + VkSemaphoreGetFdInfoKHR getFdInfo; + getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR; + getFdInfo.pNext = nullptr; + getFdInfo.semaphore = semaphore; + getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + + VkResult err = funcs.vkGetSemaphoreFdKHR(device, &getFdInfo, &res); + if (VK_SUCCESS != err) { + ALOGE("%s: failed to export semaphore, err: %d", __func__, err); + return -1; + } + return res; + } + + void destroySemaphore(VkSemaphore semaphore) { + funcs.vkDestroySemaphore(device, semaphore, nullptr); + } +}; + +static GrVkGetProc sGetProc = [](const char* proc_name, VkInstance instance, VkDevice device) { + if (device != VK_NULL_HANDLE) { + return vkGetDeviceProcAddr(device, proc_name); + } + return vkGetInstanceProcAddr(instance, proc_name); +}; + +#define BAIL(fmt, ...) \ + { \ + ALOGE("%s: " fmt ", bailing", __func__, ##__VA_ARGS__); \ + return interface; \ + } + +#define CHECK_NONNULL(expr) \ + if ((expr) == nullptr) { \ + BAIL("[%s] null", #expr); \ + } + +#define VK_CHECK(expr) \ + if ((expr) != VK_SUCCESS) { \ + BAIL("[%s] failed. err = %d", #expr, expr); \ + return interface; \ + } + +#define VK_GET_PROC(F) \ + PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F); \ + CHECK_NONNULL(vk##F) +#define VK_GET_INST_PROC(instance, F) \ + PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(instance, "vk" #F); \ + CHECK_NONNULL(vk##F) +#define VK_GET_DEV_PROC(device, F) \ + PFN_vk##F vk##F = (PFN_vk##F)vkGetDeviceProcAddr(device, "vk" #F); \ + CHECK_NONNULL(vk##F) + +VulkanInterface initVulkanInterface(bool protectedContent = false) { + VulkanInterface interface; + + VK_GET_PROC(EnumerateInstanceVersion); + uint32_t instanceVersion; + VK_CHECK(vkEnumerateInstanceVersion(&instanceVersion)); + + if (instanceVersion < VK_MAKE_VERSION(1, 1, 0)) { + return interface; + } + + const VkApplicationInfo appInfo = { + VK_STRUCTURE_TYPE_APPLICATION_INFO, nullptr, "surfaceflinger", 0, "android platform", 0, + VK_MAKE_VERSION(1, 1, 0), + }; + + VK_GET_PROC(EnumerateInstanceExtensionProperties); + + uint32_t extensionCount = 0; + VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr)); + std::vector instanceExtensions(extensionCount); + VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, + instanceExtensions.data())); + std::vector enabledInstanceExtensionNames; + enabledInstanceExtensionNames.reserve(instanceExtensions.size()); + interface.instanceExtensionNames.reserve(instanceExtensions.size()); + for (const auto& instExt : instanceExtensions) { + enabledInstanceExtensionNames.push_back(instExt.extensionName); + interface.instanceExtensionNames.push_back(instExt.extensionName); + } + + const VkInstanceCreateInfo instanceCreateInfo = { + VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + nullptr, + 0, + &appInfo, + 0, + nullptr, + (uint32_t)enabledInstanceExtensionNames.size(), + enabledInstanceExtensionNames.data(), + }; + + VK_GET_PROC(CreateInstance); + VkInstance instance; + VK_CHECK(vkCreateInstance(&instanceCreateInfo, nullptr, &instance)); + + VK_GET_INST_PROC(instance, DestroyInstance); + interface.funcs.vkDestroyInstance = vkDestroyInstance; + VK_GET_INST_PROC(instance, EnumeratePhysicalDevices); + VK_GET_INST_PROC(instance, EnumerateDeviceExtensionProperties); + VK_GET_INST_PROC(instance, GetPhysicalDeviceProperties2); + VK_GET_INST_PROC(instance, GetPhysicalDeviceExternalSemaphoreProperties); + VK_GET_INST_PROC(instance, GetPhysicalDeviceQueueFamilyProperties); + VK_GET_INST_PROC(instance, GetPhysicalDeviceFeatures2); + VK_GET_INST_PROC(instance, CreateDevice); + + uint32_t physdevCount; + VK_CHECK(vkEnumeratePhysicalDevices(instance, &physdevCount, nullptr)); + if (physdevCount == 0) { + BAIL("Could not find any physical devices"); + } + + physdevCount = 1; + VkPhysicalDevice physicalDevice; + VkResult enumeratePhysDevsErr = + vkEnumeratePhysicalDevices(instance, &physdevCount, &physicalDevice); + if (enumeratePhysDevsErr != VK_SUCCESS && VK_INCOMPLETE != enumeratePhysDevsErr) { + BAIL("vkEnumeratePhysicalDevices failed with non-VK_INCOMPLETE error: %d", + enumeratePhysDevsErr); + } + + VkPhysicalDeviceProperties2 physDevProps = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, + 0, + {}, + }; + VkPhysicalDeviceProtectedMemoryProperties protMemProps = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES, + 0, + {}, + }; + + if (protectedContent) { + physDevProps.pNext = &protMemProps; + } + + vkGetPhysicalDeviceProperties2(physicalDevice, &physDevProps); + if (physDevProps.properties.apiVersion < VK_MAKE_VERSION(1, 1, 0)) { + BAIL("Could not find a Vulkan 1.1+ physical device"); + } + + // Check for syncfd support. Bail if we cannot both import and export them. + VkPhysicalDeviceExternalSemaphoreInfo semInfo = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO, + nullptr, + VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + }; + VkExternalSemaphoreProperties semProps = { + VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES, nullptr, 0, 0, 0, + }; + vkGetPhysicalDeviceExternalSemaphoreProperties(physicalDevice, &semInfo, &semProps); + + bool sufficientSemaphoreSyncFdSupport = (semProps.exportFromImportedHandleTypes & + VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) && + (semProps.compatibleHandleTypes & VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) && + (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT) && + (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT); + + if (!sufficientSemaphoreSyncFdSupport) { + BAIL("Vulkan device does not support sufficient external semaphore sync fd features. " + "exportFromImportedHandleTypes 0x%x (needed 0x%x) " + "compatibleHandleTypes 0x%x (needed 0x%x) " + "externalSemaphoreFeatures 0x%x (needed 0x%x) ", + semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + semProps.externalSemaphoreFeatures, + VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT | + VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT); + } else { + ALOGD("Vulkan device supports sufficient external semaphore sync fd features. " + "exportFromImportedHandleTypes 0x%x (needed 0x%x) " + "compatibleHandleTypes 0x%x (needed 0x%x) " + "externalSemaphoreFeatures 0x%x (needed 0x%x) ", + semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + semProps.externalSemaphoreFeatures, + VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT | + VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT); + } + + uint32_t queueCount; + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, nullptr); + if (queueCount == 0) { + BAIL("Could not find queues for physical device"); + } + + std::vector queueProps(queueCount); + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, queueProps.data()); + + int graphicsQueueIndex = -1; + for (uint32_t i = 0; i < queueCount; ++i) { + if (queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + graphicsQueueIndex = i; + break; + } + } + + if (graphicsQueueIndex == -1) { + BAIL("Could not find a graphics queue family"); + } + + uint32_t deviceExtensionCount; + VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount, + nullptr)); + std::vector deviceExtensions(deviceExtensionCount); + VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount, + deviceExtensions.data())); + + std::vector enabledDeviceExtensionNames; + enabledDeviceExtensionNames.reserve(deviceExtensions.size()); + interface.deviceExtensionNames.reserve(deviceExtensions.size()); + for (const auto& devExt : deviceExtensions) { + enabledDeviceExtensionNames.push_back(devExt.extensionName); + interface.deviceExtensionNames.push_back(devExt.extensionName); + } + + interface.grExtensions.init(sGetProc, instance, physicalDevice, + enabledInstanceExtensionNames.size(), + enabledInstanceExtensionNames.data(), + enabledDeviceExtensionNames.size(), + enabledDeviceExtensionNames.data()); + + if (!interface.grExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) { + BAIL("Vulkan driver doesn't support external semaphore fd"); + } + + interface.physicalDeviceFeatures2 = new VkPhysicalDeviceFeatures2; + interface.physicalDeviceFeatures2->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + interface.physicalDeviceFeatures2->pNext = nullptr; + + interface.samplerYcbcrConversionFeatures = new VkPhysicalDeviceSamplerYcbcrConversionFeatures; + interface.samplerYcbcrConversionFeatures->sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES; + interface.samplerYcbcrConversionFeatures->pNext = nullptr; + + interface.physicalDeviceFeatures2->pNext = interface.samplerYcbcrConversionFeatures; + void** tailPnext = &interface.samplerYcbcrConversionFeatures->pNext; + + if (protectedContent) { + interface.protectedMemoryFeatures = new VkPhysicalDeviceProtectedMemoryProperties; + interface.protectedMemoryFeatures->sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES; + interface.protectedMemoryFeatures->pNext = nullptr; + *tailPnext = interface.protectedMemoryFeatures; + tailPnext = &interface.protectedMemoryFeatures->pNext; + } + + vkGetPhysicalDeviceFeatures2(physicalDevice, interface.physicalDeviceFeatures2); + // Looks like this would slow things down and we can't depend on it on all platforms + interface.physicalDeviceFeatures2->features.robustBufferAccess = VK_FALSE; + + float queuePriorities[1] = {0.0f}; + void* queueNextPtr = nullptr; + + VkDeviceQueueGlobalPriorityCreateInfoEXT queuePriorityCreateInfo = { + VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT, + nullptr, + // If queue priority is supported, RE should always have realtime priority. + VK_QUEUE_GLOBAL_PRIORITY_REALTIME_EXT, + }; + + if (interface.grExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) { + queueNextPtr = &queuePriorityCreateInfo; + interface.isRealtimePriority = true; + } + + VkDeviceQueueCreateFlags deviceQueueCreateFlags = + (VkDeviceQueueCreateFlags)(protectedContent ? VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT : 0); + + const VkDeviceQueueCreateInfo queueInfo = { + VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + queueNextPtr, + deviceQueueCreateFlags, + (uint32_t)graphicsQueueIndex, + 1, + queuePriorities, + }; + + const VkDeviceCreateInfo deviceInfo = { + VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + interface.physicalDeviceFeatures2, + 0, + 1, + &queueInfo, + 0, + nullptr, + (uint32_t)enabledDeviceExtensionNames.size(), + enabledDeviceExtensionNames.data(), + nullptr, + }; + + ALOGD("Trying to create Vk device with protectedContent=%d", protectedContent); + VkDevice device; + VK_CHECK(vkCreateDevice(physicalDevice, &deviceInfo, nullptr, &device)); + ALOGD("Trying to create Vk device with protectedContent=%d (success)", protectedContent); + + VkQueue graphicsQueue; + VK_GET_DEV_PROC(device, GetDeviceQueue); + vkGetDeviceQueue(device, graphicsQueueIndex, 0, &graphicsQueue); + + VK_GET_DEV_PROC(device, DeviceWaitIdle); + VK_GET_DEV_PROC(device, DestroyDevice); + interface.funcs.vkDeviceWaitIdle = vkDeviceWaitIdle; + interface.funcs.vkDestroyDevice = vkDestroyDevice; + + VK_GET_DEV_PROC(device, CreateSemaphore); + VK_GET_DEV_PROC(device, ImportSemaphoreFdKHR); + VK_GET_DEV_PROC(device, GetSemaphoreFdKHR); + VK_GET_DEV_PROC(device, DestroySemaphore); + interface.funcs.vkCreateSemaphore = vkCreateSemaphore; + interface.funcs.vkImportSemaphoreFdKHR = vkImportSemaphoreFdKHR; + interface.funcs.vkGetSemaphoreFdKHR = vkGetSemaphoreFdKHR; + interface.funcs.vkDestroySemaphore = vkDestroySemaphore; + + // At this point, everything's succeeded and we can continue + interface.initialized = true; + interface.instance = instance; + interface.physicalDevice = physicalDevice; + interface.device = device; + interface.queue = graphicsQueue; + interface.queueIndex = graphicsQueueIndex; + interface.apiVersion = physDevProps.properties.apiVersion; + // grExtensions already constructed + // feature pointers already constructed + interface.grGetProc = sGetProc; + interface.isProtected = protectedContent; + // funcs already initialized + + ALOGD("%s: Success init Vulkan interface", __func__); + return interface; +} + +void teardownVulkanInterface(VulkanInterface* interface) { + interface->initialized = false; + + if (interface->device != VK_NULL_HANDLE) { + interface->funcs.vkDeviceWaitIdle(interface->device); + interface->funcs.vkDestroyDevice(interface->device, nullptr); + interface->device = VK_NULL_HANDLE; + } + if (interface->instance != VK_NULL_HANDLE) { + interface->funcs.vkDestroyInstance(interface->instance, nullptr); + interface->instance = VK_NULL_HANDLE; + } + + if (interface->protectedMemoryFeatures) { + delete interface->protectedMemoryFeatures; + } + + if (interface->samplerYcbcrConversionFeatures) { + delete interface->samplerYcbcrConversionFeatures; + } + + if (interface->physicalDeviceFeatures2) { + delete interface->physicalDeviceFeatures2; + } + + interface->samplerYcbcrConversionFeatures = nullptr; + interface->physicalDeviceFeatures2 = nullptr; + interface->protectedMemoryFeatures = nullptr; +} + +static VulkanInterface sVulkanInterface; +static VulkanInterface sProtectedContentVulkanInterface; + +static void sSetupVulkanInterface() { + if (!sVulkanInterface.initialized) { + sVulkanInterface = initVulkanInterface(false /* no protected content */); + // We will have to abort if non-protected VkDevice creation fails (then nothing works). + LOG_ALWAYS_FATAL_IF(!sVulkanInterface.initialized, + "Could not initialize Vulkan RenderEngine!"); + } + if (!sProtectedContentVulkanInterface.initialized) { + sProtectedContentVulkanInterface = initVulkanInterface(true /* protected content */); + if (!sProtectedContentVulkanInterface.initialized) { + ALOGE("Could not initialize protected content Vulkan RenderEngine."); + } + } +} + +namespace skia { + +using base::StringAppendF; + +bool SkiaVkRenderEngine::canSupportSkiaVkRenderEngine() { + VulkanInterface temp = initVulkanInterface(false /* no protected content */); + ALOGD("SkiaVkRenderEngine::canSupportSkiaVkRenderEngine(): initialized == %s.", + temp.initialized ? "true" : "false"); + return temp.initialized; +} + +std::unique_ptr SkiaVkRenderEngine::create( + const RenderEngineCreationArgs& args) { + std::unique_ptr engine(new SkiaVkRenderEngine(args)); + engine->ensureGrContextsCreated(); + + if (sVulkanInterface.initialized) { + ALOGD("SkiaVkRenderEngine::%s: successfully initialized SkiaVkRenderEngine", __func__); + return engine; + } else { + ALOGD("SkiaVkRenderEngine::%s: could not create SkiaVkRenderEngine. " + "Likely insufficient Vulkan support", + __func__); + return {}; + } +} + +SkiaVkRenderEngine::SkiaVkRenderEngine(const RenderEngineCreationArgs& args) + : SkiaRenderEngine(args.renderEngineType, static_cast(args.pixelFormat), + args.useColorManagement, args.supportsBackgroundBlur) {} + +SkiaVkRenderEngine::~SkiaVkRenderEngine() { + finishRenderingAndAbandonContext(); +} + +SkiaRenderEngine::Contexts SkiaVkRenderEngine::createDirectContexts( + const GrContextOptions& options) { + sSetupVulkanInterface(); + + SkiaRenderEngine::Contexts contexts; + contexts.first = GrDirectContext::MakeVulkan(sVulkanInterface.getBackendContext(), options); + if (supportsProtectedContentImpl()) { + contexts.second = + GrDirectContext::MakeVulkan(sProtectedContentVulkanInterface.getBackendContext(), + options); + } + + return contexts; +} + +bool SkiaVkRenderEngine::supportsProtectedContentImpl() const { + return sProtectedContentVulkanInterface.initialized; +} + +bool SkiaVkRenderEngine::useProtectedContextImpl(GrProtected) { + return true; +} + +static void delete_semaphore(void* _semaphore) { + VkSemaphore semaphore = (VkSemaphore)_semaphore; + sVulkanInterface.destroySemaphore(semaphore); +} + +static void delete_semaphore_protected(void* _semaphore) { + VkSemaphore semaphore = (VkSemaphore)_semaphore; + sProtectedContentVulkanInterface.destroySemaphore(semaphore); +} + +static VulkanInterface& getVulkanInterface(bool protectedContext) { + if (protectedContext) { + return sProtectedContentVulkanInterface; + } + return sVulkanInterface; +} + +void SkiaVkRenderEngine::waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) { + if (fenceFd.get() < 0) return; + + int dupedFd = dup(fenceFd.get()); + if (dupedFd < 0) { + ALOGE("failed to create duplicate fence fd: %d", dupedFd); + sync_wait(fenceFd.get(), -1); + return; + } + + base::unique_fd fenceDup(dupedFd); + VkSemaphore waitSemaphore = + getVulkanInterface(isProtected()).importSemaphoreFromSyncFd(fenceDup.release()); + GrBackendSemaphore beSemaphore; + beSemaphore.initVulkan(waitSemaphore); + grContext->wait(1, &beSemaphore, true /* delete after wait */); +} + +base::unique_fd SkiaVkRenderEngine::flushAndSubmit(GrDirectContext* grContext) { + VkSemaphore signalSemaphore = getVulkanInterface(isProtected()).createExportableSemaphore(); + GrBackendSemaphore beSignalSemaphore; + beSignalSemaphore.initVulkan(signalSemaphore); + GrFlushInfo flushInfo; + flushInfo.fNumSemaphores = 1; + flushInfo.fSignalSemaphores = &beSignalSemaphore; + flushInfo.fFinishedProc = isProtected() ? delete_semaphore_protected : delete_semaphore; + flushInfo.fFinishedContext = (void*)signalSemaphore; + GrSemaphoresSubmitted submitted = grContext->flush(flushInfo); + grContext->submit(false /* no cpu sync */); + int drawFenceFd = -1; + if (GrSemaphoresSubmitted::kYes == submitted) { + drawFenceFd = getVulkanInterface(isProtected()).exportSemaphoreSyncFd(signalSemaphore); + } + base::unique_fd res(drawFenceFd); + return res; +} + +int SkiaVkRenderEngine::getContextPriority() { + // EGL_CONTEXT_PRIORITY_REALTIME_NV + constexpr int kRealtimePriority = 0x3357; + if (getVulkanInterface(isProtected()).isRealtimePriority) { + return kRealtimePriority; + } else { + return 0; + } +} + +void SkiaVkRenderEngine::appendBackendSpecificInfoToDump(std::string& result) { + StringAppendF(&result, "\n ------------RE Vulkan----------\n"); + StringAppendF(&result, "\n Vulkan device initialized: %d\n", sVulkanInterface.initialized); + StringAppendF(&result, "\n Vulkan protected device initialized: %d\n", + sProtectedContentVulkanInterface.initialized); + + if (!sVulkanInterface.initialized) { + return; + } + + StringAppendF(&result, "\n Instance extensions:\n"); + for (const auto& name : sVulkanInterface.instanceExtensionNames) { + StringAppendF(&result, "\n %s\n", name.c_str()); + } + + StringAppendF(&result, "\n Device extensions:\n"); + for (const auto& name : sVulkanInterface.deviceExtensionNames) { + StringAppendF(&result, "\n %s\n", name.c_str()); + } +} + +} // namespace skia +} // namespace renderengine +} // namespace android diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h new file mode 100644 index 0000000000..2e0cf45220 --- /dev/null +++ b/libs/renderengine/skia/SkiaVkRenderEngine.h @@ -0,0 +1,58 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SF_SKIAVKRENDERENGINE_H_ +#define SF_SKIAVKRENDERENGINE_H_ + +#include + +#include "SkiaRenderEngine.h" + +namespace android { +namespace renderengine { +namespace skia { + +class SkiaVkRenderEngine : public SkiaRenderEngine { +public: + // Returns false if Vulkan implementation can't support SkiaVkRenderEngine. + static bool canSupportSkiaVkRenderEngine(); + static std::unique_ptr create(const RenderEngineCreationArgs& args); + ~SkiaVkRenderEngine() override; + + int getContextPriority() override; + +protected: + // Implementations of abstract SkiaRenderEngine functions specific to + // rendering backend + virtual SkiaRenderEngine::Contexts createDirectContexts(const GrContextOptions& options); + bool supportsProtectedContentImpl() const override; + bool useProtectedContextImpl(GrProtected isProtected) override; + void waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) override; + base::unique_fd flushAndSubmit(GrDirectContext* context) override; + void appendBackendSpecificInfoToDump(std::string& result) override; + +private: + SkiaVkRenderEngine(const RenderEngineCreationArgs& args); + base::unique_fd flush(); + + GrVkBackendContext mBackendContext; +}; + +} // namespace skia +} // namespace renderengine +} // namespace android + +#endif diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index 777d02f415..f3f2da8a0e 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -38,6 +38,7 @@ #include #include "../skia/SkiaGLRenderEngine.h" +#include "../skia/SkiaVkRenderEngine.h" #include "../threaded/RenderEngineThreaded.h" constexpr int DEFAULT_DISPLAY_WIDTH = 128; @@ -107,9 +108,50 @@ public: virtual std::string name() = 0; virtual renderengine::RenderEngine::RenderEngineType type() = 0; virtual std::unique_ptr createRenderEngine() = 0; + virtual bool typeSupported() = 0; virtual bool useColorManagement() const = 0; }; +class SkiaVkRenderEngineFactory : public RenderEngineFactory { +public: + std::string name() override { return "SkiaVkRenderEngineFactory"; } + + renderengine::RenderEngine::RenderEngineType type() { + return renderengine::RenderEngine::RenderEngineType::SKIA_VK; + } + + std::unique_ptr createRenderEngine() override { + std::unique_ptr re = createSkiaVkRenderEngine(); + return re; + } + + std::unique_ptr createSkiaVkRenderEngine() { + renderengine::RenderEngineCreationArgs reCreationArgs = + renderengine::RenderEngineCreationArgs::Builder() + .setPixelFormat(static_cast(ui::PixelFormat::RGBA_8888)) + .setImageCacheSize(1) + .setUseColorManagerment(false) + .setEnableProtectedContext(false) + .setPrecacheToneMapperShaderOnly(false) + .setSupportsBackgroundBlur(true) + .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM) + .setRenderEngineType(type()) + .setUseColorManagerment(useColorManagement()) + .build(); + return renderengine::skia::SkiaVkRenderEngine::create(reCreationArgs); + } + + bool typeSupported() override { + return skia::SkiaVkRenderEngine::canSupportSkiaVkRenderEngine(); + } + bool useColorManagement() const override { return false; } + void skip() { GTEST_SKIP(); } +}; + +class SkiaVkCMRenderEngineFactory : public SkiaVkRenderEngineFactory { +public: + bool useColorManagement() const override { return true; } +}; class SkiaGLESRenderEngineFactory : public RenderEngineFactory { public: std::string name() override { return "SkiaGLRenderEngineFactory"; } @@ -133,6 +175,7 @@ public: return renderengine::skia::SkiaGLRenderEngine::create(reCreationArgs); } + bool typeSupported() override { return true; } bool useColorManagement() const override { return false; } }; @@ -159,6 +202,7 @@ public: return renderengine::skia::SkiaGLRenderEngine::create(reCreationArgs); } + bool typeSupported() override { return true; } bool useColorManagement() const override { return true; } }; @@ -1515,14 +1559,22 @@ void RenderEngineTest::tonemap(ui::Dataspace sourceDataspace, std::function(), - std::make_shared())); + std::make_shared(), + std::make_shared(), + std::make_shared())); TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); drawEmptyLayers(); } TEST_P(RenderEngineTest, drawLayers_fillRedBufferAndEmptyBuffer) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); renderengine::DisplaySettings settings; settings.physicalDisplay = fullscreenRect(); @@ -1547,6 +1599,9 @@ TEST_P(RenderEngineTest, drawLayers_fillRedBufferAndEmptyBuffer) { } TEST_P(RenderEngineTest, drawLayers_withoutBuffers_withColorTransform) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); renderengine::DisplaySettings settings; @@ -1578,6 +1633,9 @@ TEST_P(RenderEngineTest, drawLayers_withoutBuffers_withColorTransform) { } TEST_P(RenderEngineTest, drawLayers_nullOutputBuffer) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); renderengine::DisplaySettings settings; @@ -1597,56 +1655,89 @@ TEST_P(RenderEngineTest, drawLayers_nullOutputBuffer) { } TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillRedBuffer(); } TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillGreenBuffer(); } TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBlueBuffer(); } TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillRedTransparentBuffer(); } TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferPhysicalOffset(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate0(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate90(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate180(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate270(); } TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferLayerTransform(); } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferColorTransform(); } @@ -1654,7 +1745,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_colorSource) { TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_sourceDataspace) { const auto& renderEngineFactory = GetParam(); // skip for non color management - if (!renderEngineFactory->useColorManagement()) { + if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) { GTEST_SKIP(); } @@ -1665,7 +1756,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_sourceDataspace) { TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_outputDataspace) { const auto& renderEngineFactory = GetParam(); // skip for non color management - if (!renderEngineFactory->useColorManagement()) { + if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) { GTEST_SKIP(); } @@ -1674,81 +1765,129 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_outputDataspace) { } TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferWithRoundedCorners(); } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferColorTransformZeroLayerAlpha(); } TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferAndBlurBackground(); } TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillSmallLayerAndBlurBackground(); } TEST_P(RenderEngineTest, drawLayers_overlayCorners_colorSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); overlayCorners(); } TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillRedBuffer>(); } TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillGreenBuffer>(); } TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBlueBuffer>(); } TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillRedTransparentBuffer>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferPhysicalOffset>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate0>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate90>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate180>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate270>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferLayerTransform>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferColorTransform>(); } @@ -1756,7 +1895,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_opaqueBufferSource) TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_opaqueBufferSource) { const auto& renderEngineFactory = GetParam(); // skip for non color management - if (!renderEngineFactory->useColorManagement()) { + if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) { GTEST_SKIP(); } @@ -1767,7 +1906,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_o TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_opaqueBufferSource) { const auto& renderEngineFactory = GetParam(); // skip for non color management - if (!renderEngineFactory->useColorManagement()) { + if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) { GTEST_SKIP(); } @@ -1776,81 +1915,129 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_o } TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferWithRoundedCorners>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferColorTransformZeroLayerAlpha>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferAndBlurBackground>(); } TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillSmallLayerAndBlurBackground>(); } TEST_P(RenderEngineTest, drawLayers_overlayCorners_opaqueBufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); overlayCorners>(); } TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillRedBuffer>(); } TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillGreenBuffer>(); } TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBlueBuffer>(); } TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillRedTransparentBuffer>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferPhysicalOffset>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate0>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate90>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate180>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferCheckersRotate270>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferLayerTransform>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferColorTransform>(); } @@ -1858,7 +2045,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_bufferSource) { TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_bufferSource) { const auto& renderEngineFactory = GetParam(); // skip for non color management - if (!renderEngineFactory->useColorManagement()) { + if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) { GTEST_SKIP(); } @@ -1869,7 +2056,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_b TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_bufferSource) { const auto& renderEngineFactory = GetParam(); // skip for non color management - if (!renderEngineFactory->useColorManagement()) { + if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) { GTEST_SKIP(); } @@ -1878,46 +2065,73 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_b } TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferWithRoundedCorners>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferColorTransformZeroLayerAlpha>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferAndBlurBackground>(); } TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillSmallLayerAndBlurBackground>(); } TEST_P(RenderEngineTest, drawLayers_overlayCorners_bufferSource) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); overlayCorners>(); } TEST_P(RenderEngineTest, drawLayers_fillBufferTextureTransform) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferTextureTransform(); } TEST_P(RenderEngineTest, drawLayers_fillBuffer_premultipliesAlpha) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferWithPremultiplyAlpha(); } TEST_P(RenderEngineTest, drawLayers_fillBuffer_withoutPremultiplyingAlpha) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); fillBufferWithoutPremultiplyAlpha(); } TEST_P(RenderEngineTest, drawLayers_fillShadow_castsWithoutCasterLayer) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const ubyte4 backgroundColor(static_cast(255), static_cast(255), @@ -1934,6 +2148,9 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_castsWithoutCasterLayer) { } TEST_P(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const ubyte4 casterColor(static_cast(255), static_cast(0), @@ -1955,6 +2172,9 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) { } TEST_P(RenderEngineTest, drawLayers_fillShadow_casterColorLayer) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const ubyte4 casterColor(static_cast(255), static_cast(0), @@ -1977,6 +2197,9 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterColorLayer) { } TEST_P(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const ubyte4 casterColor(static_cast(255), static_cast(0), @@ -2000,6 +2223,9 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) { } TEST_P(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const ubyte4 casterColor(static_cast(255), static_cast(0), @@ -2024,6 +2250,9 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) { } TEST_P(RenderEngineTest, drawLayers_fillShadow_translucentCasterWithAlpha) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const ubyte4 casterColor(255, 0, 0, 255); @@ -2051,6 +2280,9 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_translucentCasterWithAlpha) { } TEST_P(RenderEngineTest, cleanupPostRender_cleansUpOnce) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); renderengine::DisplaySettings settings; @@ -2081,12 +2313,20 @@ TEST_P(RenderEngineTest, cleanupPostRender_cleansUpOnce) { fenceTwo->waitForever(LOG_TAG); // Only cleanup the first time. - EXPECT_FALSE(mRE->canSkipPostRenderCleanup()); - mRE->cleanupPostRender(); - EXPECT_TRUE(mRE->canSkipPostRenderCleanup()); + if (mRE->canSkipPostRenderCleanup()) { + // Skia's Vk backend may keep the texture alive beyond drawLayersInternal, so + // it never gets added to the cleanup list. In those cases, we can skip. + EXPECT_TRUE(GetParam()->type() == renderengine::RenderEngine::RenderEngineType::SKIA_VK); + } else { + mRE->cleanupPostRender(); + EXPECT_TRUE(mRE->canSkipPostRenderCleanup()); + } } TEST_P(RenderEngineTest, testRoundedCornersCrop) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); renderengine::DisplaySettings settings; @@ -2137,6 +2377,9 @@ TEST_P(RenderEngineTest, testRoundedCornersCrop) { } TEST_P(RenderEngineTest, testRoundedCornersParentCrop) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); renderengine::DisplaySettings settings; @@ -2182,6 +2425,9 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCrop) { } TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); renderengine::DisplaySettings settings; @@ -2259,6 +2505,9 @@ TEST_P(RenderEngineTest, testRoundedCornersXY) { } TEST_P(RenderEngineTest, testClear) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const auto rect = fullscreenRect(); @@ -2288,6 +2537,9 @@ TEST_P(RenderEngineTest, testClear) { } TEST_P(RenderEngineTest, testDisableBlendingBuffer) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const auto rect = Rect(0, 0, 1, 1); @@ -2385,6 +2637,9 @@ TEST_P(RenderEngineTest, testBorder) { } TEST_P(RenderEngineTest, testDimming) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const ui::Dataspace dataspace = ui::Dataspace::V0_SRGB_LINEAR; @@ -2457,6 +2712,9 @@ TEST_P(RenderEngineTest, testDimming) { } TEST_P(RenderEngineTest, testDimming_inGammaSpace) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const ui::Dataspace dataspace = static_cast(ui::Dataspace::STANDARD_BT709 | @@ -2532,6 +2790,9 @@ TEST_P(RenderEngineTest, testDimming_inGammaSpace) { } TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const ui::Dataspace dataspace = static_cast(ui::Dataspace::STANDARD_BT709 | @@ -2592,6 +2853,9 @@ TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform) { } TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform_deviceHandles) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const ui::Dataspace dataspace = static_cast(ui::Dataspace::STANDARD_BT709 | @@ -2653,6 +2917,9 @@ TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform_devi } TEST_P(RenderEngineTest, testDimming_withoutTargetLuminance) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const auto displayRect = Rect(2, 1); @@ -2704,6 +2971,9 @@ TEST_P(RenderEngineTest, testDimming_withoutTargetLuminance) { } TEST_P(RenderEngineTest, test_isOpaque) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const auto rect = Rect(0, 0, 1, 1); @@ -2755,7 +3025,7 @@ TEST_P(RenderEngineTest, test_isOpaque) { } TEST_P(RenderEngineTest, test_tonemapPQMatches) { - if (!GetParam()->useColorManagement()) { + if (!GetParam()->typeSupported() || !GetParam()->useColorManagement()) { GTEST_SKIP(); } @@ -2772,7 +3042,7 @@ TEST_P(RenderEngineTest, test_tonemapPQMatches) { } TEST_P(RenderEngineTest, test_tonemapHLGMatches) { - if (!GetParam()->useColorManagement()) { + if (!GetParam()->typeSupported() || !GetParam()->useColorManagement()) { GTEST_SKIP(); } @@ -2789,6 +3059,9 @@ TEST_P(RenderEngineTest, test_tonemapHLGMatches) { } TEST_P(RenderEngineTest, r8_behaves_as_mask) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const auto r8Buffer = allocateR8Buffer(2, 1); @@ -2846,6 +3119,9 @@ TEST_P(RenderEngineTest, r8_behaves_as_mask) { } TEST_P(RenderEngineTest, r8_respects_color_transform) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const auto r8Buffer = allocateR8Buffer(2, 1); @@ -2908,6 +3184,9 @@ TEST_P(RenderEngineTest, r8_respects_color_transform) { } TEST_P(RenderEngineTest, r8_respects_color_transform_when_device_handles) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); const auto r8Buffer = allocateR8Buffer(2, 1); @@ -2973,6 +3252,9 @@ TEST_P(RenderEngineTest, r8_respects_color_transform_when_device_handles) { } TEST_P(RenderEngineTest, primeShaderCache) { + if (!GetParam()->typeSupported()) { + GTEST_SKIP(); + } initializeRenderEngine(); auto fut = mRE->primeCache(); diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp index c1460cfc95..30d34a581b 100644 --- a/services/surfaceflinger/CompositionEngine/Android.bp +++ b/services/surfaceflinger/CompositionEngine/Android.bp @@ -140,6 +140,11 @@ cc_test { "libgmock", "libgtest", ], + // For some reason, libvulkan isn't picked up from librenderengine + // Probably ASAN related? + shared_libs: [ + "libvulkan", + ], sanitize: { // By using the address sanitizer, we not only uncover any issues // with the test, but also any issues with the code under test. diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index a4fb0dc774..198ef892b1 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -731,6 +731,10 @@ chooseRenderEngineTypeViaSysProp() { return renderengine::RenderEngine::RenderEngineType::SKIA_GL; } else if (strcmp(prop, "skiaglthreaded") == 0) { return renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED; + } else if (strcmp(prop, "skiavk") == 0) { + return renderengine::RenderEngine::RenderEngineType::SKIA_VK; + } else if (strcmp(prop, "skiavkthreaded") == 0) { + return renderengine::RenderEngine::RenderEngineType::SKIA_VK_THREADED; } else { ALOGE("Unrecognized RenderEngineType %s; ignoring!", prop); return {}; -- GitLab From ae9ab5331abd646e601b59f9c13f3361e351e49e Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Mon, 14 Nov 2022 18:41:38 +0000 Subject: [PATCH 0492/1310] SF: Trigger ANR when buffer exceeds max size Bug: 244218818 Test: presubmits Change-Id: I1116f159c002ae896379a18c14bbe76406b67d44 --- services/surfaceflinger/SurfaceFlinger.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 13bfd6235a..ec5dc0e67d 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -7022,9 +7022,16 @@ std::shared_ptr SurfaceFlinger::getExternalTextur BufferData& bufferData, const char* layerName, uint64_t transactionId) { if (bufferData.buffer && exceedsMaxRenderTargetSize(bufferData.buffer->getWidth(), bufferData.buffer->getHeight())) { - ALOGE("Attempted to create an ExternalTexture for layer %s that exceeds render target " - "size limit.", - layerName); + std::string errorMessage = + base::StringPrintf("Attempted to create an ExternalTexture with size (%u, %u) for " + "layer %s that exceeds render target size limit of %u.", + bufferData.buffer->getWidth(), bufferData.buffer->getHeight(), + layerName, static_cast(mMaxRenderTargetSize)); + ALOGD("%s", errorMessage.c_str()); + if (bufferData.releaseBufferListener) { + bufferData.releaseBufferListener->onTransactionQueueStalled( + String8(errorMessage.c_str())); + } return nullptr; } -- GitLab From 79b76d190b1c6bf78fe4ade91c0d6f1a4c637ee7 Mon Sep 17 00:00:00 2001 From: Zixuan Qu Date: Fri, 11 Nov 2022 04:47:51 +0000 Subject: [PATCH 0493/1310] Dump WheelVelocityControlParameters in individual CursorInputMappers. This information is currently only dumped from InputReader as a global state. However device can have different configurations in the related CursorInputMapper. Test: Manual - adb shell dumpsys input Change-Id: Ibec2b5ff2533c74924a542fe98d13fa8a88e40ca --- include/input/VelocityControl.h | 12 ++++++++++++ libs/input/VelocityControl.cpp | 4 ++++ .../inputflinger/reader/mapper/CursorInputMapper.cpp | 4 ++++ 3 files changed, 20 insertions(+) diff --git a/include/input/VelocityControl.h b/include/input/VelocityControl.h index f72a1bdded..f3c201e7c4 100644 --- a/include/input/VelocityControl.h +++ b/include/input/VelocityControl.h @@ -16,10 +16,13 @@ #pragma once +#include #include & rhs) { + return static_cast>(lhs) == static_cast>(rhs); +} + +template +constexpr bool operator!=(const Optional& lhs, const Optional& rhs) { + return !(lhs == rhs); +} + // Deduction guides. template Optional(T) -> Optional; diff --git a/libs/ftl/optional_test.cpp b/libs/ftl/optional_test.cpp index f7410c2aa9..6b3b6c49e5 100644 --- a/libs/ftl/optional_test.cpp +++ b/libs/ftl/optional_test.cpp @@ -164,4 +164,35 @@ TEST(Optional, AndThen) { })); } +// Comparison. +namespace { + +constexpr Optional kOptional1 = 1; +constexpr Optional kAnotherOptional1 = 1; +constexpr Optional kOptional2 = 2; +constexpr Optional kOptionalEmpty, kAnotherOptionalEmpty; + +constexpr std::optional kStdOptional1 = 1; + +static_assert(kOptional1 == kAnotherOptional1); + +static_assert(kOptional1 != kOptional2); +static_assert(kOptional2 != kOptional1); + +static_assert(kOptional1 != kOptionalEmpty); +static_assert(kOptionalEmpty != kOptional1); + +static_assert(kOptionalEmpty == kAnotherOptionalEmpty); + +static_assert(kOptional1 == kStdOptional1); +static_assert(kStdOptional1 == kOptional1); + +static_assert(kOptional2 != kStdOptional1); +static_assert(kStdOptional1 != kOptional2); + +static_assert(kOptional2 != kOptionalEmpty); +static_assert(kOptionalEmpty != kOptional2); + +} // namespace + } // namespace android::test -- GitLab From 15ecd1ce8e59ee91c273e6a1eba5e5aca3d88314 Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Tue, 1 Nov 2022 21:57:16 +0000 Subject: [PATCH 0530/1310] Send load reset hint from SF in advance of frame Send a load reset hint from SF to wake the session, to help move responsibility for this to individual sessions and away from DISPLAY_UPDATE_IMMINENT. Bug: b/256918431 Test: atest libsurfaceflinger_unittest Change-Id: I5a3c04682993866d9c0b4531ab69cfb903500813 --- .../CompositionEngine/tests/MockPowerAdvisor.h | 2 +- .../DisplayHardware/PowerAdvisor.cpp | 15 ++++++++++----- .../surfaceflinger/DisplayHardware/PowerAdvisor.h | 8 ++++---- services/surfaceflinger/SurfaceFlinger.cpp | 2 +- .../DisplayHardware/MockAidlPowerHalWrapper.h | 2 +- .../mock/DisplayHardware/MockPowerAdvisor.h | 2 +- 6 files changed, 18 insertions(+), 13 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h index e220541461..c555b39db1 100644 --- a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h +++ b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h @@ -34,7 +34,7 @@ public: MOCK_METHOD(void, setExpensiveRenderingExpected, (DisplayId displayId, bool expected), (override)); MOCK_METHOD(bool, isUsingExpensiveRendering, (), (override)); - MOCK_METHOD(void, notifyDisplayUpdateImminent, (), (override)); + MOCK_METHOD(void, notifyDisplayUpdateImminentAndCpuReset, (), (override)); MOCK_METHOD(bool, usePowerHintSession, (), (override)); MOCK_METHOD(bool, supportsPowerHintSession, (), (override)); MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override)); diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp index cb2c8c53ef..f05223cce0 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp @@ -57,6 +57,7 @@ using android::hardware::power::Boost; using android::hardware::power::IPower; using android::hardware::power::IPowerHintSession; using android::hardware::power::Mode; +using android::hardware::power::SessionHint; using android::hardware::power::WorkDuration; PowerAdvisor::~PowerAdvisor() = default; @@ -140,7 +141,7 @@ void PowerAdvisor::setExpensiveRenderingExpected(DisplayId displayId, bool expec } } -void PowerAdvisor::notifyDisplayUpdateImminent() { +void PowerAdvisor::notifyDisplayUpdateImminentAndCpuReset() { // Only start sending this notification once the system has booted so we don't introduce an // early-boot dependency on Power HAL if (!mBootFinished.load()) { @@ -154,7 +155,7 @@ void PowerAdvisor::notifyDisplayUpdateImminent() { return; } - if (!halWrapper->notifyDisplayUpdateImminent()) { + if (!halWrapper->notifyDisplayUpdateImminentAndCpuReset()) { // The HAL has become unavailable; attempt to reconnect later mReconnectPowerHal = true; return; @@ -599,7 +600,7 @@ public: return ret.isOk(); } - bool notifyDisplayUpdateImminent() override { + bool notifyDisplayUpdateImminentAndCpuReset() override { // Power HAL 1.x doesn't have a notification for this ALOGV("HIDL notifyUpdateImminent received but can't send"); return true; @@ -675,8 +676,12 @@ bool AidlPowerHalWrapper::setExpensiveRendering(bool enabled) { return ret.isOk(); } -bool AidlPowerHalWrapper::notifyDisplayUpdateImminent() { - ALOGV("AIDL notifyDisplayUpdateImminent"); +bool AidlPowerHalWrapper::notifyDisplayUpdateImminentAndCpuReset() { + ALOGV("AIDL notifyDisplayUpdateImminentAndCpuReset"); + if (isPowerHintSessionRunning()) { + mPowerHintSession->sendHint(SessionHint::CPU_LOAD_RESET); + } + if (!mHasDisplayUpdateImminent) { ALOGV("Skipped sending DISPLAY_UPDATE_IMMINENT because HAL doesn't support it"); return true; diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h index 1c9d123a1f..d45e7cb572 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h @@ -48,7 +48,7 @@ public: virtual void onBootFinished() = 0; virtual void setExpensiveRenderingExpected(DisplayId displayId, bool expected) = 0; virtual bool isUsingExpensiveRendering() = 0; - virtual void notifyDisplayUpdateImminent() = 0; + virtual void notifyDisplayUpdateImminentAndCpuReset() = 0; // Checks both if it supports and if it's enabled virtual bool usePowerHintSession() = 0; virtual bool supportsPowerHintSession() = 0; @@ -106,7 +106,7 @@ public: virtual ~HalWrapper() = default; virtual bool setExpensiveRendering(bool enabled) = 0; - virtual bool notifyDisplayUpdateImminent() = 0; + virtual bool notifyDisplayUpdateImminentAndCpuReset() = 0; virtual bool supportsPowerHintSession() = 0; virtual bool isPowerHintSessionRunning() = 0; virtual void restartPowerHintSession() = 0; @@ -126,7 +126,7 @@ public: void onBootFinished() override; void setExpensiveRenderingExpected(DisplayId displayId, bool expected) override; bool isUsingExpensiveRendering() override { return mNotifiedExpensiveRendering; }; - void notifyDisplayUpdateImminent() override; + void notifyDisplayUpdateImminentAndCpuReset() override; bool usePowerHintSession() override; bool supportsPowerHintSession() override; bool isPowerHintSessionRunning() override; @@ -289,7 +289,7 @@ public: static std::unique_ptr connect(); bool setExpensiveRendering(bool enabled) override; - bool notifyDisplayUpdateImminent() override; + bool notifyDisplayUpdateImminentAndCpuReset() override; bool supportsPowerHintSession() override; bool isPowerHintSessionRunning() override; void restartPowerHintSession() override; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 032541c8bf..4e2bfff65f 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1806,7 +1806,7 @@ void SurfaceFlinger::scheduleCommit(FrameHint hint) { if (hint == FrameHint::kActive) { mScheduler->resetIdleTimer(); } - mPowerAdvisor->notifyDisplayUpdateImminent(); + mPowerAdvisor->notifyDisplayUpdateImminentAndCpuReset(); mScheduler->scheduleFrame(); } diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h index c2c3d77364..5654691884 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h @@ -36,7 +36,7 @@ public: MockAidlPowerHalWrapper(); ~MockAidlPowerHalWrapper() override; MOCK_METHOD(bool, setExpensiveRendering, (bool enabled), (override)); - MOCK_METHOD(bool, notifyDisplayUpdateImminent, (), (override)); + MOCK_METHOD(bool, notifyDisplayUpdateImminentAndCpuReset, (), (override)); MOCK_METHOD(bool, supportsPowerHintSession, (), (override)); MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override)); MOCK_METHOD(void, restartPowerHintSession, (), (override)); diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h index fb1b3946a4..7fc625c4b6 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h @@ -32,7 +32,7 @@ public: MOCK_METHOD(void, setExpensiveRenderingExpected, (DisplayId displayId, bool expected), (override)); MOCK_METHOD(bool, isUsingExpensiveRendering, (), (override)); - MOCK_METHOD(void, notifyDisplayUpdateImminent, (), (override)); + MOCK_METHOD(void, notifyDisplayUpdateImminentAndCpuReset, (), (override)); MOCK_METHOD(bool, usePowerHintSession, (), (override)); MOCK_METHOD(bool, supportsPowerHintSession, (), (override)); MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override)); -- GitLab From 3f726401556680204be2e4bc6bba722beb80c037 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Mon, 21 Nov 2022 17:21:22 -0500 Subject: [PATCH 0531/1310] SF: Obey active display's RefreshRateSelector When a display becomes active, apply its RefreshRateSelector's policy, as it may have changed while the display was inactive. When booting while folded, DisplayManager first sends DisplayModeSpecs for each display, and then powers on the outer display. Before this CL, the outer display would become the new active/leader display, but its DisplayManagerPolicy would never be applied. Also, remove calls during boot (or restart) that are now redundant given that onActiveDisplayChangedLocked is called when powering on the primary display in onInitializeDisplays. Bug: 250421145 Test: Force 120 Hz, and reboot while folded. Test: Apply Ifaa46027bad8ff0945db9da5c30f2f31b6c8d10c and repeat. Change-Id: I15e0f5a280e62baf6d4e6ea2748d95342e79ac44 --- services/surfaceflinger/SurfaceFlinger.cpp | 25 ++++++++-------------- services/surfaceflinger/SurfaceFlinger.h | 5 +++++ 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index e4cb64764b..5df09d6ebe 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -845,8 +845,6 @@ void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { } } - onActiveDisplaySizeChanged(display); - // Inform native graphics APIs whether the present timestamp is supported: const bool presentFenceReliable = @@ -3433,14 +3431,6 @@ void SurfaceFlinger::initScheduler(const sp& display) { sp::make(*this, RegionSamplingThread::EnvironmentTimingTunables()); mFpsReporter = sp::make(*mFrameTimeline, *this); - // Dispatch a mode change request for the primary display on scheduler - // initialization, so that the EventThreads always contain a reference to a - // prior configuration. - // - // This is a bit hacky, but this avoids a back-pointer into the main SF - // classes from EventThread, and there should be no run-time binder cost - // anyway since there are no connected apps at this point. - mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, activeModePtr); } void SurfaceFlinger::updatePhaseConfiguration(const Fps& refreshRate) { @@ -4623,8 +4613,6 @@ void SurfaceFlinger::onInitializeDisplays() { {}, mPid, getuid(), transactionId); setPowerModeInternal(display, hal::PowerMode::ON); - - mActiveDisplayTransformHint = display->getTransformHint(); } void SurfaceFlinger::initializeDisplays() { @@ -6642,9 +6630,12 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( case SetPolicyResult::Unchanged: return NO_ERROR; case SetPolicyResult::Changed: - break; + return applyRefreshRateSelectorPolicy(displayId, selector); } +} +status_t SurfaceFlinger::applyRefreshRateSelectorPolicy( + PhysicalDisplayId displayId, const scheduler::RefreshRateSelector& selector) { const scheduler::RefreshRateSelector::Policy currentPolicy = selector.getCurrentPolicy(); ALOGV("Setting desired display mode specs: %s", currentPolicy.toString().c_str()); @@ -6976,9 +6967,11 @@ void SurfaceFlinger::onActiveDisplayChangedLocked(const sp& activ onActiveDisplaySizeChanged(activeDisplay); mActiveDisplayTransformHint = activeDisplay->getTransformHint(); - // Update the kernel timer for the current active display, since the policy - // for this display might have changed when it was not the active display. - toggleKernelIdleTimer(); + // The policy of the new active/leader display may have changed while it was inactive. In that + // case, its preferred mode has not been propagated to HWC (via setDesiredActiveMode). In either + // case, the Scheduler's cachedModeChangedParams must be initialized to the newly active mode, + // and the kernel idle timer of the newly active display must be toggled. + applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay->refreshRateSelector()); } status_t SurfaceFlinger::addWindowInfosListener( diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 7c0926a1ee..8776904907 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -666,6 +666,11 @@ private: const sp&, const scheduler::RefreshRateSelector::PolicyVariant&) EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); + // TODO(b/241285191): Look up RefreshRateSelector on Scheduler to remove redundant parameter. + status_t applyRefreshRateSelectorPolicy(PhysicalDisplayId, + const scheduler::RefreshRateSelector&) + REQUIRES(mStateLock, kMainThreadContext); + void commitTransactions() EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); void commitTransactionsLocked(uint32_t transactionFlags) REQUIRES(mStateLock, kMainThreadContext); -- GitLab From 6c87e7b619da1814efab675a9ce0774d877cc385 Mon Sep 17 00:00:00 2001 From: Fyodor Kyslov Date: Wed, 23 Nov 2022 03:12:14 +0000 Subject: [PATCH 0532/1310] JPEGR Decoder: Add XMP data parsing Parse XMP packet and get Range Scaling Factor Test: build Bug: b/252835416 Change-Id: Id05269286e0a32f789a26e2af2ce3ebc4c89b4a6 --- libs/jpegrecoverymap/recoverymaputils.cpp | 134 +++++++++++++++++++++- 1 file changed, 128 insertions(+), 6 deletions(-) diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/recoverymaputils.cpp index f8549f1f9d..fe46cbad91 100644 --- a/libs/jpegrecoverymap/recoverymaputils.cpp +++ b/libs/jpegrecoverymap/recoverymaputils.cpp @@ -16,16 +16,138 @@ #include #include +#include +#include +#include +#include +#include + +#include +#include + +using namespace photos_editing_formats::image_io; +using namespace std; namespace android::recoverymap { + +// Extremely simple XML Handler - just searches for interesting elements +class XMPXmlHandler : public XmlHandler { +public: + + XMPXmlHandler() : XmlHandler() { + rangeScalingFactorState = NotStrarted; + } + + enum ParseState { + NotStrarted, + Started, + Done + }; + + virtual DataMatchResult StartElement(const XmlTokenContext& context) { + string val; + if (context.BuildTokenValue(&val)) { + if (!val.compare(rangeScalingFactorName)) { + rangeScalingFactorState = Started; + } else { + if (rangeScalingFactorState != Done) { + rangeScalingFactorState = NotStrarted; + } + } + } + return context.GetResult(); + } + + virtual DataMatchResult FinishElement(const XmlTokenContext& context) { + if (rangeScalingFactorState == Started) { + rangeScalingFactorState = Done; + } + return context.GetResult(); + } + + virtual DataMatchResult ElementContent(const XmlTokenContext& context) { + string val; + if (rangeScalingFactorState == Started) { + if (context.BuildTokenValue(&val)) { + rangeScalingFactorStr.assign(val); + } + } + return context.GetResult(); + } + + bool getRangeScalingFactor(float* scaling_factor) { + if (rangeScalingFactorState == Done) { + stringstream ss(rangeScalingFactorStr); + float val; + if (ss >> val) { + *scaling_factor = val; + return true; + } else { + return false; + } + } else { + return false; + } + } + + bool getTransferFunction(jpegr_transfer_function* transfer_function) { + *transfer_function = JPEGR_TF_HLG; + return true; + } + +private: + static const string rangeScalingFactorName; + string rangeScalingFactorStr; + ParseState rangeScalingFactorState; +}; + +const string XMPXmlHandler::rangeScalingFactorName = "GContainer:rangeScalingFactor"; + + bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) { - // TODO: Parse XMP Data - (void)xmp_data; - (void)xmp_size; - metadata->rangeScalingFactor = 0.0708864; - metadata->transferFunction = JPEGR_TF_HLG; + string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; + + if (xmp_size < nameSpace.size()+2) { + // Data too short + return false; + } + + if (strncmp(reinterpret_cast(xmp_data), nameSpace.c_str(), nameSpace.size())) { + // Not correct namespace + return false; + } + + // Position the pointers to the start of XMP XML portion + xmp_data += nameSpace.size()+1; + xmp_size -= nameSpace.size()+1; + XMPXmlHandler handler; + + // We need to remove tail data until the closing tag. Otherwise parser will throw an error. + while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) { + xmp_size--; + } + + string str(reinterpret_cast(xmp_data), xmp_size); + MessageHandler msg_handler; + unique_ptr rule(new XmlElementRule); + XmlReader reader(&handler, &msg_handler); + reader.StartParse(std::move(rule)); + reader.Parse(str); + reader.FinishParse(); + if (reader.HasErrors()) { + // Parse error + return false; + } + + if (!handler.getRangeScalingFactor(&metadata->rangeScalingFactor)) { + return false; + } + + if (!handler.getTransferFunction(&metadata->transferFunction)) { + return false; + } return true; } -} \ No newline at end of file +} // namespace android::recoverymap \ No newline at end of file -- GitLab From 32f9e69927c2efefe6f7a138bfd291f130ccc486 Mon Sep 17 00:00:00 2001 From: Ian Elliott Date: Tue, 22 Nov 2022 15:30:29 -0700 Subject: [PATCH 0533/1310] Allow SkiaVkRenderEngine to not be compiled A device will use RenderEngine with either SkiaVk or SkiaGL, but never both at the same time. Given that the choice is controlled by a build-time value in the device's "device.mk" file, to save space, we should allow the RenderEngine back-ends to be optionally built. For now, we'll only do this for SkiaVkRenderEngine. Test: Compile Bug: 259323498 Change-Id: Id547feaee7e8aeb4e46759d3d7c9c5dbe18f5211 --- libs/renderengine/RenderEngine.cpp | 8 ++++++++ libs/renderengine/skia/SkiaVkRenderEngine.cpp | 5 +++++ libs/renderengine/skia/SkiaVkRenderEngine.h | 7 ++++++- libs/renderengine/tests/RenderEngineTest.cpp | 9 +++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp index d08c2213ad..341c011dc9 100644 --- a/libs/renderengine/RenderEngine.cpp +++ b/libs/renderengine/RenderEngine.cpp @@ -39,8 +39,12 @@ std::unique_ptr RenderEngine::create(const RenderEngineCreationArg ALOGD("RenderEngine with SkiaGL Backend"); return renderengine::skia::SkiaGLRenderEngine::create(args); case RenderEngineType::SKIA_VK: +#ifdef RE_SKIAVK ALOGD("RenderEngine with SkiaVK Backend"); return renderengine::skia::SkiaVkRenderEngine::create(args); +#else + LOG_ALWAYS_FATAL("Requested VK backend, but RE_SKIAVK is not defined!"); +#endif case RenderEngineType::SKIA_GL_THREADED: { ALOGD("Threaded RenderEngine with SkiaGL Backend"); return renderengine::threaded::RenderEngineThreaded::create( @@ -50,12 +54,16 @@ std::unique_ptr RenderEngine::create(const RenderEngineCreationArg args.renderEngineType); } case RenderEngineType::SKIA_VK_THREADED: +#ifdef RE_SKIAVK ALOGD("Threaded RenderEngine with SkiaVK Backend"); return renderengine::threaded::RenderEngineThreaded::create( [args]() { return android::renderengine::skia::SkiaVkRenderEngine::create(args); }, args.renderEngineType); +#else + LOG_ALWAYS_FATAL("Requested VK backend, but RE_SKIAVK is not defined!"); +#endif case RenderEngineType::GLES: default: ALOGD("RenderEngine with GLES Backend"); diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp index f9424f0f15..2b8495c3f7 100644 --- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp @@ -14,6 +14,10 @@ * limitations under the License. */ +// Allow the SkiaVkRenderEngine class to not be compiled, to save space +// NOTE: In order to build this class, define `RE_SKIAVK` in a build file. +#ifdef RE_SKIAVK + // #define LOG_NDEBUG 0 #undef LOG_TAG #define LOG_TAG "RenderEngine" @@ -673,3 +677,4 @@ void SkiaVkRenderEngine::appendBackendSpecificInfoToDump(std::string& result) { } // namespace skia } // namespace renderengine } // namespace android +#endif // RE_SKIAVK diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h index 2e0cf45220..1e42b80c10 100644 --- a/libs/renderengine/skia/SkiaVkRenderEngine.h +++ b/libs/renderengine/skia/SkiaVkRenderEngine.h @@ -17,6 +17,10 @@ #ifndef SF_SKIAVKRENDERENGINE_H_ #define SF_SKIAVKRENDERENGINE_H_ +// Allow the SkiaVkRenderEngine class to not be compiled, to save space +// NOTE: In order to build this class, define `RE_SKIAVK` in a build file. +#ifdef RE_SKIAVK + #include #include "SkiaRenderEngine.h" @@ -55,4 +59,5 @@ private: } // namespace renderengine } // namespace android -#endif +#endif // RE_SKIAVK +#endif // SF_SKIAVKRENDERENGINE_H_ diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index f3f2da8a0e..7db95a7ea0 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -112,6 +112,7 @@ public: virtual bool useColorManagement() const = 0; }; +#ifdef RE_SKIAVK class SkiaVkRenderEngineFactory : public RenderEngineFactory { public: std::string name() override { return "SkiaVkRenderEngineFactory"; } @@ -152,6 +153,8 @@ class SkiaVkCMRenderEngineFactory : public SkiaVkRenderEngineFactory { public: bool useColorManagement() const override { return true; } }; +#endif // RE_SKIAVK + class SkiaGLESRenderEngineFactory : public RenderEngineFactory { public: std::string name() override { return "SkiaGLRenderEngineFactory"; } @@ -1557,11 +1560,17 @@ void RenderEngineTest::tonemap(ui::Dataspace sourceDataspace, std::function(), std::make_shared(), std::make_shared(), std::make_shared())); +#else // RE_SKIAVK +INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest, + testing::Values(std::make_shared(), + std::make_shared())); +#endif // RE_SKIAVK TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) { if (!GetParam()->typeSupported()) { -- GitLab From dae9dfcf89fb79cdf27353d7f4220a36f83b343d Mon Sep 17 00:00:00 2001 From: Tommy Nordgren Date: Thu, 13 Oct 2022 11:25:57 +0200 Subject: [PATCH 0534/1310] Check display id of the last hover window before it is cleared The display id of the last hover window must be checked before it is cleared when a window changes for a display. If display id of the last hover window is not checked, then the last hover window is cleared every time a window changes on a different display which is faulty. The last hover window should only be cleared when the window is removed from the same display as the display of last hover window. Add test that ensure that only one hover enter is generated followed by several hover move when the mouse is moved in a window on primary display when windows on second display are removed. The CTS test, VirtualMouseTest, must also be updated. Reference patch: Correct CTS test VirtualMouseTest#sendRelativeEvent Ie4a54ee3469ed0b7c88abea946f32f66203da38f Bug: 239687726 Test: atest VirtualMouseTest Change-Id: I70ebcb994ebc31327b85303110753ce3d40329be --- .../dispatcher/InputDispatcher.cpp | 11 ++-- .../tests/InputDispatcher_test.cpp | 53 +++++++++++++++++++ 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 6d300903d2..05f32069f7 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -4803,10 +4803,13 @@ void InputDispatcher::setInputWindowsLocked( updateWindowHandlesForDisplayLocked(windowInfoHandles, displayId); const std::vector>& windowHandles = getWindowHandlesLocked(displayId); - if (mLastHoverWindowHandle && - std::find(windowHandles.begin(), windowHandles.end(), mLastHoverWindowHandle) == - windowHandles.end()) { - mLastHoverWindowHandle = nullptr; + if (mLastHoverWindowHandle) { + const WindowInfo* lastHoverWindowInfo = mLastHoverWindowHandle->getInfo(); + if (lastHoverWindowInfo->displayId == displayId && + std::find(windowHandles.begin(), windowHandles.end(), mLastHoverWindowHandle) == + windowHandles.end()) { + mLastHoverWindowHandle = nullptr; + } } std::optional changes = diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 7817eca43b..ff064e416d 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -2279,6 +2279,59 @@ TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) { WithSource(AINPUT_SOURCE_TOUCHSCREEN)))); } +TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) { + std::shared_ptr application = std::make_shared(); + sp windowDefaultDisplay = + sp::make(application, mDispatcher, "DefaultDisplay", + ADISPLAY_ID_DEFAULT); + windowDefaultDisplay->setFrame(Rect(0, 0, 600, 800)); + sp windowSecondDisplay = + sp::make(application, mDispatcher, "SecondDisplay", + SECOND_DISPLAY_ID); + windowSecondDisplay->setFrame(Rect(0, 0, 600, 800)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowDefaultDisplay}}, + {SECOND_DISPLAY_ID, {windowSecondDisplay}}}); + + // Set cursor position in window in default display and check that hover enter and move + // events are generated. + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, + AINPUT_SOURCE_MOUSE) + .displayId(ADISPLAY_ID_DEFAULT) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .x(300) + .y(600)) + .build())); + windowDefaultDisplay->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER, + ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + windowDefaultDisplay->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_MOVE, + ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + + // Remove all windows in secondary display and check that no event happens on window in + // primary display. + mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {}}}); + windowDefaultDisplay->assertNoEvents(); + + // Move cursor position in window in default display and check that only hover move + // event is generated and not hover enter event. + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowDefaultDisplay}}, + {SECOND_DISPLAY_ID, {windowSecondDisplay}}}); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, + AINPUT_SOURCE_MOUSE) + .displayId(ADISPLAY_ID_DEFAULT) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .x(400) + .y(700)) + .build())); + windowDefaultDisplay->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_MOVE, + ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + windowDefaultDisplay->assertNoEvents(); +} + TEST_F(InputDispatcherTest, DispatchMouseEventsUnderCursor) { std::shared_ptr application = std::make_shared(); -- GitLab From aa548fdb280f58ca249f5c5d96b3911cda966abb Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 23 Nov 2022 18:50:09 +0000 Subject: [PATCH 0535/1310] SF: Avoid accessing an invalidated iterator If we erase the element currently pointed to by the iterator, the iterator will be invalidated. Fix this by updating the iterator. Bug: 238781169 Test: presubmit Change-Id: I5306cb15f7665f53475ce2fa3b5ef6a897f73dca --- services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp index 1108246e47..7afa1444df 100644 --- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp +++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp @@ -123,7 +123,11 @@ void LayerLifecycleManager::onHandlesDestroyed(const std::vector& dest ALOGV("%s destroyed layer %s", __func__, layer->getDebugStringShort().c_str()); std::iter_swap(it, mLayers.end() - 1); mDestroyedLayers.emplace_back(std::move(mLayers.back())); - mLayers.erase(mLayers.end() - 1); + if (it == mLayers.end() - 1) { + it = mLayers.erase(mLayers.end() - 1); + } else { + mLayers.erase(mLayers.end() - 1); + } } else { it++; } -- GitLab From 74c248d80897440fcfda597a1ca9956d64c265eb Mon Sep 17 00:00:00 2001 From: Arthur Hung Date: Wed, 23 Nov 2022 07:09:59 +0000 Subject: [PATCH 0536/1310] Fix wallpaper window can't receive up event If the wallpaper window is touchable and the above touched window has `DUPLICATE_TOUCH_TO_WALLPAPER` flag, it would also deliver the same touch event to the wallpaper window, and it have to set the corresponding pointer ids to prevent it being erased in next pointer up. Bug: 240308355 Test: atest inputflinger_tests Change-Id: Ibf82b494e45e35babe9ba9f3dd1236fee8bcea6d --- .../dispatcher/InputDispatcher.cpp | 4 +- .../tests/InputDispatcher_test.cpp | 58 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 6d300903d2..01aa221b05 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2429,12 +2429,14 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( if (info->displayId == displayId && windowHandle->getInfo()->inputConfig.test( WindowInfo::InputConfig::IS_WALLPAPER)) { + BitSet32 pointerIds; + pointerIds.markBit(entry.pointerProperties[0].id); tempTouchState.addOrUpdateWindow(windowHandle, InputTarget::Flags::WINDOW_IS_OBSCURED | InputTarget::Flags:: WINDOW_IS_PARTIALLY_OBSCURED | InputTarget::Flags::DISPATCH_AS_IS, - BitSet32(0), entry.eventTime); + pointerIds, entry.eventTime); } } } diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 7817eca43b..984895007b 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -1922,6 +1922,64 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { wallpaperWindow->assertNoEvents(); } +TEST_F(InputDispatcherTest, WallpaperWindowReceivesMultiTouch) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); + window->setDupTouchToWallpaper(true); + + sp wallpaperWindow = + sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); + wallpaperWindow->setIsWallpaper(true); + constexpr int expectedWallpaperFlags = + AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; + wallpaperWindow->setPreventSplitting(true); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, wallpaperWindow}}}); + + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {50, 50})) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + + const MotionEvent secondFingerDownEvent = + MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .displayId(ADISPLAY_ID_DEFAULT) + .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) + .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10)) + .build(); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, + InputEventInjectionSync::WAIT_FOR_RESULT)) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + + window->consumeMotionPointerDown(1); + wallpaperWindow->consumeMotionPointerDown(1, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + + const MotionEvent secondFingerUpEvent = + MotionEventBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN) + .displayId(ADISPLAY_ID_DEFAULT) + .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) + .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10)) + .build(); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT, + InputEventInjectionSync::WAIT_FOR_RESULT)) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + window->consumeMotionPointerUp(1); + wallpaperWindow->consumeMotionPointerUp(1, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {50, 50})) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + window->consumeMotionUp(ADISPLAY_ID_DEFAULT); + wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); +} + /** * On the display, have a single window, and also an area where there's no window. * First pointer touches the "no window" area of the screen. Second pointer touches the window. -- GitLab From 1be799e7263029c60e36f4e58d7ecd4f79921e9e Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 23 Nov 2022 12:47:14 -0800 Subject: [PATCH 0537/1310] Fail when file is not found Currently, we just log an error and exit. Instead, we should fail when file is not found. Bug: 260115731 Test: m && validatekeymaps input.idc Change-Id: Ia84f23533b6dafc4b9748d781ecefec108e8e20b --- libs/input/PropertyMap.cpp | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/libs/input/PropertyMap.cpp b/libs/input/PropertyMap.cpp index 16ffa10223..ed9ac9fc72 100644 --- a/libs/input/PropertyMap.cpp +++ b/libs/input/PropertyMap.cpp @@ -116,25 +116,24 @@ android::base::Result> PropertyMap::load(const char Tokenizer* rawTokenizer; status_t status = Tokenizer::open(String8(filename), &rawTokenizer); - std::unique_ptr tokenizer(rawTokenizer); if (status) { - ALOGE("Error %d opening property file %s.", status, filename); - } else { + return android::base::Error(-status) << "Could not open file: " << filename; + } #if DEBUG_PARSER_PERFORMANCE - nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC); + nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC); #endif - Parser parser(outMap.get(), tokenizer.get()); - status = parser.parse(); + std::unique_ptr tokenizer(rawTokenizer); + Parser parser(outMap.get(), tokenizer.get()); + status = parser.parse(); #if DEBUG_PARSER_PERFORMANCE - nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime; - ALOGD("Parsed property file '%s' %d lines in %0.3fms.", - tokenizer->getFilename().string(), tokenizer->getLineNumber(), - elapsedTime / 1000000.0); + nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime; + ALOGD("Parsed property file '%s' %d lines in %0.3fms.", tokenizer->getFilename().string(), + tokenizer->getLineNumber(), elapsedTime / 1000000.0); #endif - if (status) { - return android::base::Error(BAD_VALUE) << "Could not parse " << filename; - } + if (status) { + return android::base::Error(BAD_VALUE) << "Could not parse " << filename; } + return std::move(outMap); } -- GitLab From 1f2db4f98af16aca68216ab99c783b4cb6c2626f Mon Sep 17 00:00:00 2001 From: Michael Wright Date: Thu, 24 Nov 2022 16:47:38 +0000 Subject: [PATCH 0538/1310] Add explicit copy assignment operator for NotifyMotionArgs. Right now we rely on an implicit definition of this operator, which is deprecated since we have an explicit copy constructor. See -Wdeprecated-copy-with-user-provided-copy for details on why this isn't desirable. Fortunately, a default implementation of the copy assignment operator is sufficient, so there's no actual bug in the current usages. Test: atest NotifyArgs_Test.cpp Change-Id: I58a913ad1c37414e86167557fdd4deb29499f432 --- services/inputflinger/include/NotifyArgs.h | 2 + services/inputflinger/tests/Android.bp | 1 + .../inputflinger/tests/NotifyArgs_test.cpp | 85 +++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 services/inputflinger/tests/NotifyArgs_test.cpp diff --git a/services/inputflinger/include/NotifyArgs.h b/services/inputflinger/include/NotifyArgs.h index f28dbf3039..c46f90501f 100644 --- a/services/inputflinger/include/NotifyArgs.h +++ b/services/inputflinger/include/NotifyArgs.h @@ -116,6 +116,8 @@ struct NotifyMotionArgs { NotifyMotionArgs(const NotifyMotionArgs& other); + NotifyMotionArgs& operator=(const android::NotifyMotionArgs&) = default; + bool operator==(const NotifyMotionArgs& rhs) const; std::string dump() const; diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index e55e121583..cf2a96a876 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -46,6 +46,7 @@ cc_test { "InputDispatcher_test.cpp", "InputReader_test.cpp", "LatencyTracker_test.cpp", + "NotifyArgs_test.cpp", "PreferStylusOverTouch_test.cpp", "TestInputListener.cpp", "UinputDevice.cpp", diff --git a/services/inputflinger/tests/NotifyArgs_test.cpp b/services/inputflinger/tests/NotifyArgs_test.cpp new file mode 100644 index 0000000000..671558509d --- /dev/null +++ b/services/inputflinger/tests/NotifyArgs_test.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include "android/input.h" +#include "input/Input.h" +#include "input/TouchVideoFrame.h" + +namespace android { + +// --- NotifyArgsTest --- + +/** + * Validate basic copy assignment. + */ +TEST(NotifyMotionArgsTest, TestCopyAssignmentOperator) { + int32_t id = 123; + nsecs_t downTime = systemTime(); + nsecs_t eventTime = downTime++; + nsecs_t readTime = downTime++; + int32_t deviceId = 7; + uint32_t source = AINPUT_SOURCE_TOUCHSCREEN; + int32_t displayId = 42; + uint32_t policyFlags = POLICY_FLAG_GESTURE; + int32_t action = AMOTION_EVENT_ACTION_HOVER_MOVE; + int32_t actionButton = AMOTION_EVENT_BUTTON_PRIMARY; + int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; + int32_t metaState = AMETA_SCROLL_LOCK_ON; + uint32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY | AMOTION_EVENT_BUTTON_SECONDARY; + MotionClassification classification = MotionClassification::DEEP_PRESS; + int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP; + uint32_t pointerCount = 2; + PointerProperties pointerProperties[pointerCount]; + PointerCoords pointerCoords[pointerCount]; + float x = 0; + float y = 10; + + for (size_t i = 0; i < pointerCount; i++) { + pointerProperties[i].clear(); + pointerProperties[i].id = i; + pointerProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + + pointerCoords[i].clear(); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, x++); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, y++); + } + + float xPrecision = 1.2f; + float yPrecision = 3.4f; + float xCursorPosition = 5.6f; + float yCursorPosition = 7.8f; + + std::vector videoData = {1, 2, 3, 4}; + timeval timestamp = {5, 6}; + TouchVideoFrame frame(2, 2, std::move(videoData), timestamp); + std::vector videoFrames = {frame}; + const NotifyMotionArgs args(id, eventTime, readTime, deviceId, source, displayId, policyFlags, + action, actionButton, flags, metaState, buttonState, classification, + edgeFlags, pointerCount, pointerProperties, pointerCoords, + xPrecision, yPrecision, xCursorPosition, yCursorPosition, downTime, + videoFrames); + + NotifyMotionArgs otherArgs{}; + otherArgs = args; + + EXPECT_EQ(args, otherArgs); +} + +} // namespace android \ No newline at end of file -- GitLab From 3cec44662025c65805480b5fc490c7bfcf841faa Mon Sep 17 00:00:00 2001 From: Michael Wright Date: Thu, 24 Nov 2022 22:05:46 +0000 Subject: [PATCH 0539/1310] Fix for-loop type in dumpDispatchStateLocked Technically the iterator provides a std::pair&, so we're constructing a std::pair from it and then taking a const reference to that. This creates an unnecessary copy but then obscures that by taking a reference to it. We could use the correct type to avoid this, but a structured binding is even nicer. Test: compile Change-Id: I360c4318001eff503b539693166386160bd605eb --- services/inputflinger/dispatcher/InputDispatcher.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index eb938c84f8..de0c0e45eb 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -5435,9 +5435,7 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) { if (!mReplacedKeys.empty()) { dump += INDENT "ReplacedKeys:\n"; - for (const std::pair& pair : mReplacedKeys) { - const KeyReplacement& replacement = pair.first; - int32_t newKeyCode = pair.second; + for (const auto& [replacement, newKeyCode] : mReplacedKeys) { dump += StringPrintf(INDENT2 "originalKeyCode=%d, deviceId=%d -> newKeyCode=%d\n", replacement.keyCode, replacement.deviceId, newKeyCode); } -- GitLab From fb04fd5e40044e0a1d5500bc48abee5a2d4ea67a Mon Sep 17 00:00:00 2001 From: Michael Wright Date: Thu, 24 Nov 2022 22:31:11 +0000 Subject: [PATCH 0540/1310] Convert CancelationOptions::Mode to an enum class. C++11 offers better type-safety, we should use it. Test: compiles Change-Id: Ied8edcabf2c2a0c9f52c7077e45228c72134e330 --- .../dispatcher/CancelationOptions.h | 2 +- .../dispatcher/InputDispatcher.cpp | 39 ++++++++++--------- .../inputflinger/dispatcher/InputState.cpp | 12 +++--- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/services/inputflinger/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h index d210e9e846..512cb6e692 100644 --- a/services/inputflinger/dispatcher/CancelationOptions.h +++ b/services/inputflinger/dispatcher/CancelationOptions.h @@ -24,7 +24,7 @@ namespace inputdispatcher { /* Specifies which events are to be canceled and why. */ struct CancelationOptions { - enum Mode { + enum class Mode { CANCEL_ALL_EVENTS = 0, CANCEL_POINTER_EVENTS = 1, CANCEL_NON_POINTER_EVENTS = 2, diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index de0c0e45eb..6abd93fe18 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -1169,17 +1169,18 @@ void InputDispatcher::dropInboundEventLocked(const EventEntry& entry, DropReason switch (entry.type) { case EventEntry::Type::KEY: { - CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason); + CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, reason); synthesizeCancelationEventsForAllConnectionsLocked(options); break; } case EventEntry::Type::MOTION: { const MotionEntry& motionEntry = static_cast(entry); if (motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) { - CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, reason); + CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, reason); synthesizeCancelationEventsForAllConnectionsLocked(options); } else { - CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason); + CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, + reason); synthesizeCancelationEventsForAllConnectionsLocked(options); } break; @@ -1334,7 +1335,7 @@ bool InputDispatcher::dispatchDeviceResetLocked(nsecs_t currentTime, resetKeyRepeatLocked(); } - CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS, "device was reset"); + CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, "device was reset"); options.deviceId = entry.deviceId; synthesizeCancelationEventsForAllConnectionsLocked(options); return true; @@ -1723,9 +1724,9 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr< return true; } if (injectionResult != InputEventInjectionResult::SUCCEEDED) { - CancelationOptions::Mode mode(isPointerEvent - ? CancelationOptions::CANCEL_POINTER_EVENTS - : CancelationOptions::CANCEL_NON_POINTER_EVENTS); + CancelationOptions::Mode mode( + isPointerEvent ? CancelationOptions::Mode::CANCEL_POINTER_EVENTS + : CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS); CancelationOptions options(mode, "input event injection failed"); synthesizeCancelationEventsForMonitorsLocked(options); return true; @@ -1736,7 +1737,7 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr< // Dispatch the motion. if (conflictingPointerActions) { - CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, + CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, "conflicting pointer actions"); synthesizeCancelationEventsForAllConnectionsLocked(options); } @@ -1837,7 +1838,7 @@ void InputDispatcher::cancelEventsForAnrLocked(const sp& connection) ALOGW("Canceling events for %s because it is unresponsive", connection->inputChannel->getName().c_str()); if (connection->status == Connection::Status::NORMAL) { - CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS, + CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, "application not responding"); synthesizeCancelationEventsForConnectionLocked(connection, options); } @@ -4826,7 +4827,7 @@ void InputDispatcher::setInputWindowsLocked( std::shared_ptr touchedInputChannel = getInputChannelLocked(touchedWindow.windowHandle->getToken()); if (touchedInputChannel != nullptr) { - CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, + CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, "touched window was removed"); synthesizeCancelationEventsForInputChannelLocked(touchedInputChannel, options); // Since we are about to drop the touch, cancel the events for the wallpaper as @@ -4872,7 +4873,7 @@ void InputDispatcher::setInputWindowsLocked( std::shared_ptr inputChannel = getInputChannelLocked(newWindowHandle->getToken()); if (inputChannel != nullptr) { - CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, + CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, "touched window's orientation changed"); synthesizeCancelationEventsForInputChannelLocked(inputChannel, options); } @@ -4953,7 +4954,7 @@ void InputDispatcher::setFocusedDisplay(int32_t displayId) { getInputChannelLocked(oldFocusedWindowToken); if (inputChannel != nullptr) { CancelationOptions - options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, + options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, "The display which contains this window no longer has focus."); options.displayId = ADISPLAY_ID_NONE; synthesizeCancelationEventsForInputChannelLocked(inputChannel, options); @@ -5180,7 +5181,7 @@ bool InputDispatcher::transferTouchFocus(const sp& fromToken, const sp< if (fromConnection != nullptr && toConnection != nullptr) { fromConnection->inputState.mergePointerStateTo(toConnection->inputState); CancelationOptions - options(CancelationOptions::CANCEL_POINTER_EVENTS, + options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, "transferring touch focus from this window to another window"); synthesizeCancelationEventsForConnectionLocked(fromConnection, options); synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection); @@ -5254,7 +5255,7 @@ void InputDispatcher::resetAndDropEverythingLocked(const char* reason) { ALOGD("Resetting and dropping all events (%s).", reason); } - CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS, reason); + CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, reason); synthesizeCancelationEventsForAllConnectionsLocked(options); resetKeyRepeatLocked(); @@ -5679,7 +5680,7 @@ status_t InputDispatcher::pilferPointersLocked(const sp& token) { TouchState& state = *statePtr; TouchedWindow& window = *windowPtr; // Send cancel events to all the input channels we're stealing from. - CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, + CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, "input channel stole pointer stream"); options.deviceId = state.deviceId; options.displayId = displayId; @@ -6079,7 +6080,7 @@ bool InputDispatcher::afterKeyEventLockedInterruptable(const sp& con // Cancel the fallback key. if (fallbackKeyCode != AKEYCODE_UNKNOWN) { - CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS, + CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS, "application handled the original non-fallback key " "or is no longer a foreground target, " "canceling previously dispatched fallback key"); @@ -6156,7 +6157,7 @@ bool InputDispatcher::afterKeyEventLockedInterruptable(const sp& con } } - CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS, + CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS, "canceling fallback, policy no longer desires it"); options.keyCode = fallbackKeyCode; synthesizeCancelationEventsForConnectionLocked(connection, options); @@ -6309,7 +6310,7 @@ void InputDispatcher::onFocusChangedLocked(const FocusResolver::FocusChanges& ch if (changes.oldFocus) { std::shared_ptr focusedInputChannel = getInputChannelLocked(changes.oldFocus); if (focusedInputChannel) { - CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, + CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, "focus left window"); synthesizeCancelationEventsForInputChannelLocked(focusedInputChannel, options); enqueueFocusEventLocked(changes.oldFocus, false /*hasFocus*/, changes.reason); @@ -6449,7 +6450,7 @@ void InputDispatcher::cancelCurrentTouch() { { std::scoped_lock _l(mLock); ALOGD("Canceling all ongoing pointer gestures on all displays."); - CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, + CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, "cancel current touch"); synthesizeCancelationEventsForAllConnectionsLocked(options); diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp index 047c628e67..563868d10e 100644 --- a/services/inputflinger/dispatcher/InputState.cpp +++ b/services/inputflinger/dispatcher/InputState.cpp @@ -500,10 +500,10 @@ bool InputState::shouldCancelKey(const KeyMemento& memento, const CancelationOpt } switch (options.mode) { - case CancelationOptions::CANCEL_ALL_EVENTS: - case CancelationOptions::CANCEL_NON_POINTER_EVENTS: + case CancelationOptions::Mode::CANCEL_ALL_EVENTS: + case CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS: return true; - case CancelationOptions::CANCEL_FALLBACK_EVENTS: + case CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS: return memento.flags & AKEY_EVENT_FLAG_FALLBACK; default: return false; @@ -521,11 +521,11 @@ bool InputState::shouldCancelMotion(const MotionMemento& memento, } switch (options.mode) { - case CancelationOptions::CANCEL_ALL_EVENTS: + case CancelationOptions::Mode::CANCEL_ALL_EVENTS: return true; - case CancelationOptions::CANCEL_POINTER_EVENTS: + case CancelationOptions::Mode::CANCEL_POINTER_EVENTS: return memento.source & AINPUT_SOURCE_CLASS_POINTER; - case CancelationOptions::CANCEL_NON_POINTER_EVENTS: + case CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS: return !(memento.source & AINPUT_SOURCE_CLASS_POINTER); default: return false; -- GitLab From 5caf55a943a67daa3652f307b83fcd6e092b3b81 Mon Sep 17 00:00:00 2001 From: Michael Wright Date: Thu, 24 Nov 2022 22:31:42 +0000 Subject: [PATCH 0541/1310] Convert KeyEntry::InterceptKeyResult to enum class. Test: compiles Change-Id: I4fdbdad3eaaf47e96f40c7ab14fbe1cf5e866a9d --- services/inputflinger/dispatcher/Entry.cpp | 4 ++-- services/inputflinger/dispatcher/Entry.h | 10 ++++----- .../dispatcher/InputDispatcher.cpp | 21 +++++++++---------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp index ec9701ac24..7bbfb95b88 100644 --- a/services/inputflinger/dispatcher/Entry.cpp +++ b/services/inputflinger/dispatcher/Entry.cpp @@ -166,7 +166,7 @@ KeyEntry::KeyEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t sou repeatCount(repeatCount), downTime(downTime), syntheticRepeat(false), - interceptKeyResult(KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN), + interceptKeyResult(KeyEntry::InterceptKeyResult::UNKNOWN), interceptKeyWakeupTime(0) {} KeyEntry::~KeyEntry() {} @@ -189,7 +189,7 @@ void KeyEntry::recycle() { dispatchInProgress = false; syntheticRepeat = false; - interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN; + interceptKeyResult = KeyEntry::InterceptKeyResult::UNKNOWN; interceptKeyWakeupTime = 0; } diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h index f8019126f3..3799814f67 100644 --- a/services/inputflinger/dispatcher/Entry.h +++ b/services/inputflinger/dispatcher/Entry.h @@ -140,11 +140,11 @@ struct KeyEntry : EventEntry { bool syntheticRepeat; // set to true for synthetic key repeats - enum InterceptKeyResult { - INTERCEPT_KEY_RESULT_UNKNOWN, - INTERCEPT_KEY_RESULT_SKIP, - INTERCEPT_KEY_RESULT_CONTINUE, - INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER, + enum class InterceptKeyResult { + UNKNOWN, + SKIP, + CONTINUE, + TRY_AGAIN_LATER, }; InterceptKeyResult interceptKeyResult; // set based on the interception result nsecs_t interceptKeyWakeupTime; // used with INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 6abd93fe18..d36b6ff41d 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -60,7 +60,6 @@ using android::gui::FocusRequest; using android::gui::TouchOcclusionMode; using android::gui::WindowInfo; using android::gui::WindowInfoHandle; -using android::os::IInputConstants; using android::os::InputEventInjectionResult; using android::os::InputEventInjectionSync; @@ -1033,8 +1032,8 @@ bool InputDispatcher::enqueueInboundEventLocked(std::unique_ptr newE KeyEntry& pendingKey = static_cast(*mPendingEvent); if (pendingKey.keyCode == keyEntry.keyCode && pendingKey.interceptKeyResult == - KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) { - pendingKey.interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN; + KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) { + pendingKey.interceptKeyResult = KeyEntry::InterceptKeyResult::UNKNOWN; pendingKey.interceptKeyWakeupTime = 0; needWake = true; } @@ -1540,19 +1539,19 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptrinterceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) { + if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) { if (currentTime < entry->interceptKeyWakeupTime) { if (entry->interceptKeyWakeupTime < *nextWakeupTime) { *nextWakeupTime = entry->interceptKeyWakeupTime; } return false; // wait until next wakeup } - entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN; + entry->interceptKeyResult = KeyEntry::InterceptKeyResult::UNKNOWN; entry->interceptKeyWakeupTime = 0; } // Give the policy a chance to intercept the key. - if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) { + if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::UNKNOWN) { if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) { sp focusedWindowToken = mFocusResolver.getFocusedWindowToken(getTargetDisplayId(*entry)); @@ -1563,9 +1562,9 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptrinterceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE; + entry->interceptKeyResult = KeyEntry::InterceptKeyResult::CONTINUE; } - } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) { + } else if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::SKIP) { if (*dropReason == DropReason::NOT_DROPPED) { *dropReason = DropReason::POLICY; } @@ -5966,11 +5965,11 @@ void InputDispatcher::doInterceptKeyBeforeDispatchingCommand(const sp& } // acquire lock if (delay < 0) { - entry.interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP; + entry.interceptKeyResult = KeyEntry::InterceptKeyResult::SKIP; } else if (delay == 0) { - entry.interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE; + entry.interceptKeyResult = KeyEntry::InterceptKeyResult::CONTINUE; } else { - entry.interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER; + entry.interceptKeyResult = KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER; entry.interceptKeyWakeupTime = now() + delay; } } -- GitLab From a5b712925a0c51cc99304cb1fa3c57c1729ff01b Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Mon, 28 Nov 2022 12:56:17 +0000 Subject: [PATCH 0542/1310] inputflinger_tests: Put `FakeEventHub` in its own file I would like to be able to put automated tests for the new `TouchpadEventMapper` in their own file, rather than InputReader_tests.cpp. To do this I'll need some of the test utilities in their own files, too. Aside from extracting the `FakeEventHub` definition into its own file and putting method bodies into a .cpp file, I've made a few other minor refactors: * Use an early return to reduce nesting in `markSupportedKeyCodes` * Use a ternary instead of an `if` in `getScanCodeState` * Remove some single-use `device` variables and replace their use with a call to `getDevice` * Reordered the methods in the .h to make the grouping a little more logical * Tried to follow "Include What You Use" in the new files Bug: 251196347 Test: atest inputflinger_tests Change-Id: I55fe8267976d7aba58e1e4067b041d92dfd5347d to fixup Change-Id: I59df4e03db7e30cf7d17425ed38fe7f639dec836 --- services/inputflinger/tests/Android.bp | 1 + services/inputflinger/tests/FakeEventHub.cpp | 590 +++++++++++++++ services/inputflinger/tests/FakeEventHub.h | 224 ++++++ .../inputflinger/tests/InputReader_test.cpp | 682 +----------------- services/inputflinger/tests/TestConstants.h | 30 + 5 files changed, 860 insertions(+), 667 deletions(-) create mode 100644 services/inputflinger/tests/FakeEventHub.cpp create mode 100644 services/inputflinger/tests/FakeEventHub.h create mode 100644 services/inputflinger/tests/TestConstants.h diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index b6d0709af0..6d11e602a1 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -40,6 +40,7 @@ cc_test { "AnrTracker_test.cpp", "BlockingQueue_test.cpp", "EventHub_test.cpp", + "FakeEventHub.cpp", "FocusResolver_test.cpp", "InputProcessor_test.cpp", "InputProcessorConverter_test.cpp", diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp new file mode 100644 index 0000000000..f6cf1cc304 --- /dev/null +++ b/services/inputflinger/tests/FakeEventHub.cpp @@ -0,0 +1,590 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "FakeEventHub.h" + +#include +#include +#include + +#include "TestConstants.h" + +namespace android { + +const std::string FakeEventHub::BATTERY_DEVPATH = "/sys/devices/mydevice/power_supply/mybattery"; + +FakeEventHub::~FakeEventHub() { + for (size_t i = 0; i < mDevices.size(); i++) { + delete mDevices.valueAt(i); + } +} + +void FakeEventHub::addDevice(int32_t deviceId, const std::string& name, + ftl::Flags classes, int bus) { + Device* device = new Device(classes); + device->identifier.name = name; + device->identifier.bus = bus; + mDevices.add(deviceId, device); + + enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0); +} + +void FakeEventHub::removeDevice(int32_t deviceId) { + delete mDevices.valueFor(deviceId); + mDevices.removeItem(deviceId); + + enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0); +} + +bool FakeEventHub::isDeviceEnabled(int32_t deviceId) const { + Device* device = getDevice(deviceId); + if (device == nullptr) { + ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); + return false; + } + return device->enabled; +} + +status_t FakeEventHub::enableDevice(int32_t deviceId) { + status_t result; + Device* device = getDevice(deviceId); + if (device == nullptr) { + ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); + return BAD_VALUE; + } + if (device->enabled) { + ALOGW("Duplicate call to %s, device %" PRId32 " already enabled", __func__, deviceId); + return OK; + } + result = device->enable(); + return result; +} + +status_t FakeEventHub::disableDevice(int32_t deviceId) { + Device* device = getDevice(deviceId); + if (device == nullptr) { + ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); + return BAD_VALUE; + } + if (!device->enabled) { + ALOGW("Duplicate call to %s, device %" PRId32 " already disabled", __func__, deviceId); + return OK; + } + return device->disable(); +} + +void FakeEventHub::finishDeviceScan() { + enqueueEvent(ARBITRARY_TIME, READ_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0); +} + +void FakeEventHub::addConfigurationProperty(int32_t deviceId, const char* key, const char* value) { + getDevice(deviceId)->configuration.addProperty(key, value); +} + +void FakeEventHub::addConfigurationMap(int32_t deviceId, const PropertyMap* configuration) { + getDevice(deviceId)->configuration.addAll(configuration); +} + +void FakeEventHub::addAbsoluteAxis(int32_t deviceId, int axis, int32_t minValue, int32_t maxValue, + int flat, int fuzz, int resolution) { + Device* device = getDevice(deviceId); + + RawAbsoluteAxisInfo info; + info.valid = true; + info.minValue = minValue; + info.maxValue = maxValue; + info.flat = flat; + info.fuzz = fuzz; + info.resolution = resolution; + device->absoluteAxes.add(axis, info); +} + +void FakeEventHub::addRelativeAxis(int32_t deviceId, int32_t axis) { + getDevice(deviceId)->relativeAxes.add(axis, true); +} + +void FakeEventHub::setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state) { + getDevice(deviceId)->keyCodeStates.replaceValueFor(keyCode, state); +} + +void FakeEventHub::setCountryCode(int32_t deviceId, InputDeviceCountryCode countryCode) { + getDevice(deviceId)->countryCode = countryCode; +} + +void FakeEventHub::setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state) { + getDevice(deviceId)->scanCodeStates.replaceValueFor(scanCode, state); +} + +void FakeEventHub::setSwitchState(int32_t deviceId, int32_t switchCode, int32_t state) { + getDevice(deviceId)->switchStates.replaceValueFor(switchCode, state); +} + +void FakeEventHub::setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value) { + getDevice(deviceId)->absoluteAxisValue.replaceValueFor(axis, value); +} + +void FakeEventHub::addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t keyCode, + uint32_t flags) { + Device* device = getDevice(deviceId); + KeyInfo info; + info.keyCode = keyCode; + info.flags = flags; + if (scanCode) { + device->keysByScanCode.add(scanCode, info); + } + if (usageCode) { + device->keysByUsageCode.add(usageCode, info); + } +} + +void FakeEventHub::addKeyCodeMapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) { + getDevice(deviceId)->keyCodeMapping.insert_or_assign(fromKeyCode, toKeyCode); +} + +void FakeEventHub::addLed(int32_t deviceId, int32_t led, bool initialState) { + getDevice(deviceId)->leds.add(led, initialState); +} + +void FakeEventHub::addSensorAxis(int32_t deviceId, int32_t absCode, + InputDeviceSensorType sensorType, int32_t sensorDataIndex) { + SensorInfo info; + info.sensorType = sensorType; + info.sensorDataIndex = sensorDataIndex; + getDevice(deviceId)->sensorsByAbsCode.emplace(absCode, info); +} + +void FakeEventHub::setMscEvent(int32_t deviceId, int32_t mscEvent) { + typename BitArray::Buffer buffer; + buffer[mscEvent / 32] = 1 << mscEvent % 32; + getDevice(deviceId)->mscBitmask.loadFromBuffer(buffer); +} + +void FakeEventHub::addRawLightInfo(int32_t rawId, RawLightInfo&& info) { + mRawLightInfos.emplace(rawId, std::move(info)); +} + +void FakeEventHub::fakeLightBrightness(int32_t rawId, int32_t brightness) { + mLightBrightness.emplace(rawId, brightness); +} + +void FakeEventHub::fakeLightIntensities(int32_t rawId, + const std::unordered_map intensities) { + mLightIntensities.emplace(rawId, std::move(intensities)); +} + +bool FakeEventHub::getLedState(int32_t deviceId, int32_t led) { + return getDevice(deviceId)->leds.valueFor(led); +} + +std::vector& FakeEventHub::getExcludedDevices() { + return mExcludedDevices; +} + +void FakeEventHub::addVirtualKeyDefinition(int32_t deviceId, + const VirtualKeyDefinition& definition) { + getDevice(deviceId)->virtualKeys.push_back(definition); +} + +void FakeEventHub::enqueueEvent(nsecs_t when, nsecs_t readTime, int32_t deviceId, int32_t type, + int32_t code, int32_t value) { + std::scoped_lock lock(mLock); + RawEvent event; + event.when = when; + event.readTime = readTime; + event.deviceId = deviceId; + event.type = type; + event.code = code; + event.value = value; + mEvents.push_back(event); + + if (type == EV_ABS) { + setAbsoluteAxisValue(deviceId, code, value); + } +} + +void FakeEventHub::setVideoFrames( + std::unordered_map> videoFrames) { + mVideoFrames = std::move(videoFrames); +} + +void FakeEventHub::assertQueueIsEmpty() { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + const bool queueIsEmpty = + mEventsCondition.wait_for(lock, WAIT_TIMEOUT, + [this]() REQUIRES(mLock) { return mEvents.size() == 0; }); + if (!queueIsEmpty) { + FAIL() << "Timed out waiting for EventHub queue to be emptied."; + } +} + +FakeEventHub::Device* FakeEventHub::getDevice(int32_t deviceId) const { + ssize_t index = mDevices.indexOfKey(deviceId); + return index >= 0 ? mDevices.valueAt(index) : nullptr; +} + +ftl::Flags FakeEventHub::getDeviceClasses(int32_t deviceId) const { + Device* device = getDevice(deviceId); + return device ? device->classes : ftl::Flags(0); +} + +InputDeviceIdentifier FakeEventHub::getDeviceIdentifier(int32_t deviceId) const { + Device* device = getDevice(deviceId); + return device ? device->identifier : InputDeviceIdentifier(); +} + +int32_t FakeEventHub::getDeviceControllerNumber(int32_t) const { + return 0; +} + +void FakeEventHub::getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const { + Device* device = getDevice(deviceId); + if (device) { + *outConfiguration = device->configuration; + } +} + +status_t FakeEventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis, + RawAbsoluteAxisInfo* outAxisInfo) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->absoluteAxes.indexOfKey(axis); + if (index >= 0) { + *outAxisInfo = device->absoluteAxes.valueAt(index); + return OK; + } + } + outAxisInfo->clear(); + return -1; +} + +bool FakeEventHub::hasRelativeAxis(int32_t deviceId, int axis) const { + Device* device = getDevice(deviceId); + if (device) { + return device->relativeAxes.indexOfKey(axis) >= 0; + } + return false; +} + +bool FakeEventHub::hasInputProperty(int32_t, int) const { + return false; +} + +bool FakeEventHub::hasMscEvent(int32_t deviceId, int mscEvent) const { + Device* device = getDevice(deviceId); + if (device) { + return mscEvent >= 0 && mscEvent <= MSC_MAX ? device->mscBitmask.test(mscEvent) : false; + } + return false; +} + +status_t FakeEventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, + int32_t metaState, int32_t* outKeycode, int32_t* outMetaState, + uint32_t* outFlags) const { + Device* device = getDevice(deviceId); + if (device) { + const KeyInfo* key = getKey(device, scanCode, usageCode); + if (key) { + if (outKeycode) { + *outKeycode = key->keyCode; + } + if (outFlags) { + *outFlags = key->flags; + } + if (outMetaState) { + *outMetaState = metaState; + } + return OK; + } + } + return NAME_NOT_FOUND; +} + +const FakeEventHub::KeyInfo* FakeEventHub::getKey(Device* device, int32_t scanCode, + int32_t usageCode) const { + if (usageCode) { + ssize_t index = device->keysByUsageCode.indexOfKey(usageCode); + if (index >= 0) { + return &device->keysByUsageCode.valueAt(index); + } + } + if (scanCode) { + ssize_t index = device->keysByScanCode.indexOfKey(scanCode); + if (index >= 0) { + return &device->keysByScanCode.valueAt(index); + } + } + return nullptr; +} + +status_t FakeEventHub::mapAxis(int32_t, int32_t, AxisInfo*) const { + return NAME_NOT_FOUND; +} + +base::Result> FakeEventHub::mapSensor( + int32_t deviceId, int32_t absCode) const { + Device* device = getDevice(deviceId); + if (!device) { + return Errorf("Sensor device not found."); + } + auto it = device->sensorsByAbsCode.find(absCode); + if (it == device->sensorsByAbsCode.end()) { + return Errorf("Sensor map not found."); + } + const SensorInfo& info = it->second; + return std::make_pair(info.sensorType, info.sensorDataIndex); +} + +void FakeEventHub::setExcludedDevices(const std::vector& devices) { + mExcludedDevices = devices; +} + +std::vector FakeEventHub::getEvents(int) { + std::scoped_lock lock(mLock); + + std::vector buffer; + std::swap(buffer, mEvents); + + mEventsCondition.notify_all(); + return buffer; +} + +std::vector FakeEventHub::getVideoFrames(int32_t deviceId) { + auto it = mVideoFrames.find(deviceId); + if (it != mVideoFrames.end()) { + std::vector frames = std::move(it->second); + mVideoFrames.erase(deviceId); + return frames; + } + return {}; +} + +int32_t FakeEventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->scanCodeStates.indexOfKey(scanCode); + if (index >= 0) { + return device->scanCodeStates.valueAt(index); + } + } + return AKEY_STATE_UNKNOWN; +} + +InputDeviceCountryCode FakeEventHub::getCountryCode(int32_t deviceId) const { + Device* device = getDevice(deviceId); + return device ? device->countryCode : InputDeviceCountryCode::INVALID; +} + +int32_t FakeEventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->keyCodeStates.indexOfKey(keyCode); + if (index >= 0) { + return device->keyCodeStates.valueAt(index); + } + } + return AKEY_STATE_UNKNOWN; +} + +int32_t FakeEventHub::getSwitchState(int32_t deviceId, int32_t sw) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->switchStates.indexOfKey(sw); + if (index >= 0) { + return device->switchStates.valueAt(index); + } + } + return AKEY_STATE_UNKNOWN; +} + +status_t FakeEventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, + int32_t* outValue) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->absoluteAxisValue.indexOfKey(axis); + if (index >= 0) { + *outValue = device->absoluteAxisValue.valueAt(index); + return OK; + } + } + *outValue = 0; + return -1; +} + +int32_t FakeEventHub::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const { + Device* device = getDevice(deviceId); + if (!device) { + return AKEYCODE_UNKNOWN; + } + auto it = device->keyCodeMapping.find(locationKeyCode); + return it != device->keyCodeMapping.end() ? it->second : locationKeyCode; +} + +// Return true if the device has non-empty key layout. +bool FakeEventHub::markSupportedKeyCodes(int32_t deviceId, const std::vector& keyCodes, + uint8_t* outFlags) const { + Device* device = getDevice(deviceId); + if (!device) return false; + + bool result = device->keysByScanCode.size() > 0 || device->keysByUsageCode.size() > 0; + for (size_t i = 0; i < keyCodes.size(); i++) { + for (size_t j = 0; j < device->keysByScanCode.size(); j++) { + if (keyCodes[i] == device->keysByScanCode.valueAt(j).keyCode) { + outFlags[i] = 1; + } + } + for (size_t j = 0; j < device->keysByUsageCode.size(); j++) { + if (keyCodes[i] == device->keysByUsageCode.valueAt(j).keyCode) { + outFlags[i] = 1; + } + } + } + return result; +} + +bool FakeEventHub::hasScanCode(int32_t deviceId, int32_t scanCode) const { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->keysByScanCode.indexOfKey(scanCode); + return index >= 0; + } + return false; +} + +bool FakeEventHub::hasKeyCode(int32_t deviceId, int32_t keyCode) const { + Device* device = getDevice(deviceId); + if (!device) { + return false; + } + for (size_t i = 0; i < device->keysByScanCode.size(); i++) { + if (keyCode == device->keysByScanCode.valueAt(i).keyCode) { + return true; + } + } + for (size_t j = 0; j < device->keysByUsageCode.size(); j++) { + if (keyCode == device->keysByUsageCode.valueAt(j).keyCode) { + return true; + } + } + return false; +} + +bool FakeEventHub::hasLed(int32_t deviceId, int32_t led) const { + Device* device = getDevice(deviceId); + return device && device->leds.indexOfKey(led) >= 0; +} + +void FakeEventHub::setLedState(int32_t deviceId, int32_t led, bool on) { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->leds.indexOfKey(led); + if (index >= 0) { + device->leds.replaceValueAt(led, on); + } else { + ADD_FAILURE() << "Attempted to set the state of an LED that the EventHub declared " + "was not present. led=" + << led; + } + } +} + +void FakeEventHub::getVirtualKeyDefinitions( + int32_t deviceId, std::vector& outVirtualKeys) const { + outVirtualKeys.clear(); + + Device* device = getDevice(deviceId); + if (device) { + outVirtualKeys = device->virtualKeys; + } +} + +const std::shared_ptr FakeEventHub::getKeyCharacterMap(int32_t) const { + return nullptr; +} + +bool FakeEventHub::setKeyboardLayoutOverlay(int32_t, std::shared_ptr) { + return false; +} + +std::vector FakeEventHub::getVibratorIds(int32_t deviceId) const { + return mVibrators; +} + +std::optional FakeEventHub::getBatteryCapacity(int32_t, int32_t) const { + return BATTERY_CAPACITY; +} + +std::optional FakeEventHub::getBatteryStatus(int32_t, int32_t) const { + return BATTERY_STATUS; +} + +std::vector FakeEventHub::getRawBatteryIds(int32_t deviceId) const { + return {DEFAULT_BATTERY}; +} + +std::optional FakeEventHub::getRawBatteryInfo(int32_t deviceId, + int32_t batteryId) const { + if (batteryId != DEFAULT_BATTERY) return {}; + static const auto BATTERY_INFO = RawBatteryInfo{.id = DEFAULT_BATTERY, + .name = "default battery", + .flags = InputBatteryClass::CAPACITY, + .path = BATTERY_DEVPATH}; + return BATTERY_INFO; +} + +std::vector FakeEventHub::getRawLightIds(int32_t deviceId) const { + std::vector ids; + for (const auto& [rawId, info] : mRawLightInfos) { + ids.push_back(rawId); + } + return ids; +} + +std::optional FakeEventHub::getRawLightInfo(int32_t deviceId, int32_t lightId) const { + auto it = mRawLightInfos.find(lightId); + if (it == mRawLightInfos.end()) { + return std::nullopt; + } + return it->second; +} + +void FakeEventHub::setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) { + mLightBrightness.emplace(lightId, brightness); +} + +void FakeEventHub::setLightIntensities(int32_t deviceId, int32_t lightId, + std::unordered_map intensities) { + mLightIntensities.emplace(lightId, intensities); +}; + +std::optional FakeEventHub::getLightBrightness(int32_t deviceId, int32_t lightId) const { + auto lightIt = mLightBrightness.find(lightId); + if (lightIt == mLightBrightness.end()) { + return std::nullopt; + } + return lightIt->second; +} + +std::optional> FakeEventHub::getLightIntensities( + int32_t deviceId, int32_t lightId) const { + auto lightIt = mLightIntensities.find(lightId); + if (lightIt == mLightIntensities.end()) { + return std::nullopt; + } + return lightIt->second; +}; + +} // namespace android diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h new file mode 100644 index 0000000000..21cb2f1963 --- /dev/null +++ b/services/inputflinger/tests/FakeEventHub.h @@ -0,0 +1,224 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "android/hardware/input/InputDeviceCountryCode.h" + +using android::hardware::input::InputDeviceCountryCode; + +namespace android { + +class FakeEventHub : public EventHubInterface { + struct KeyInfo { + int32_t keyCode; + uint32_t flags; + }; + + struct SensorInfo { + InputDeviceSensorType sensorType; + int32_t sensorDataIndex; + }; + + struct Device { + InputDeviceIdentifier identifier; + ftl::Flags classes; + PropertyMap configuration; + KeyedVector absoluteAxes; + KeyedVector relativeAxes; + KeyedVector keyCodeStates; + KeyedVector scanCodeStates; + KeyedVector switchStates; + KeyedVector absoluteAxisValue; + KeyedVector keysByScanCode; + KeyedVector keysByUsageCode; + KeyedVector leds; + // fake mapping which would normally come from keyCharacterMap + std::unordered_map keyCodeMapping; + std::unordered_map sensorsByAbsCode; + BitArray mscBitmask; + std::vector virtualKeys; + bool enabled; + InputDeviceCountryCode countryCode; + + status_t enable() { + enabled = true; + return OK; + } + + status_t disable() { + enabled = false; + return OK; + } + + explicit Device(ftl::Flags classes) : classes(classes), enabled(true) {} + }; + + std::mutex mLock; + std::condition_variable mEventsCondition; + + KeyedVector mDevices; + std::vector mExcludedDevices; + std::vector mEvents GUARDED_BY(mLock); + std::unordered_map> mVideoFrames; + std::vector mVibrators = {0, 1}; + std::unordered_map mRawLightInfos; + // Simulates a device light brightness, from light id to light brightness. + std::unordered_map mLightBrightness; + // Simulates a device light intensities, from light id to light intensities map. + std::unordered_map> + mLightIntensities; + +public: + static constexpr int32_t DEFAULT_BATTERY = 1; + static constexpr int32_t BATTERY_STATUS = 4; + static constexpr int32_t BATTERY_CAPACITY = 66; + static const std::string BATTERY_DEVPATH; + + virtual ~FakeEventHub(); + FakeEventHub() {} + + void addDevice(int32_t deviceId, const std::string& name, ftl::Flags classes, + int bus = 0); + void removeDevice(int32_t deviceId); + + bool isDeviceEnabled(int32_t deviceId) const override; + status_t enableDevice(int32_t deviceId) override; + status_t disableDevice(int32_t deviceId) override; + + void finishDeviceScan(); + + void addConfigurationProperty(int32_t deviceId, const char* key, const char* value); + void addConfigurationMap(int32_t deviceId, const PropertyMap* configuration); + + void addAbsoluteAxis(int32_t deviceId, int axis, int32_t minValue, int32_t maxValue, int flat, + int fuzz, int resolution = 0); + void addRelativeAxis(int32_t deviceId, int32_t axis); + void setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value); + + void setCountryCode(int32_t deviceId, InputDeviceCountryCode countryCode); + + void setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state); + void setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state); + void setSwitchState(int32_t deviceId, int32_t switchCode, int32_t state); + + void addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t keyCode, + uint32_t flags); + void addKeyCodeMapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode); + void addVirtualKeyDefinition(int32_t deviceId, const VirtualKeyDefinition& definition); + + void addSensorAxis(int32_t deviceId, int32_t absCode, InputDeviceSensorType sensorType, + int32_t sensorDataIndex); + + void setMscEvent(int32_t deviceId, int32_t mscEvent); + + void addLed(int32_t deviceId, int32_t led, bool initialState); + void addRawLightInfo(int32_t rawId, RawLightInfo&& info); + void fakeLightBrightness(int32_t rawId, int32_t brightness); + void fakeLightIntensities(int32_t rawId, + const std::unordered_map intensities); + bool getLedState(int32_t deviceId, int32_t led); + + std::vector& getExcludedDevices(); + + void setVideoFrames( + std::unordered_map> videoFrames); + + void enqueueEvent(nsecs_t when, nsecs_t readTime, int32_t deviceId, int32_t type, int32_t code, + int32_t value); + void assertQueueIsEmpty(); + +private: + Device* getDevice(int32_t deviceId) const; + + ftl::Flags getDeviceClasses(int32_t deviceId) const override; + InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override; + int32_t getDeviceControllerNumber(int32_t) const override; + void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const override; + status_t getAbsoluteAxisInfo(int32_t deviceId, int axis, + RawAbsoluteAxisInfo* outAxisInfo) const override; + bool hasRelativeAxis(int32_t deviceId, int axis) const override; + bool hasInputProperty(int32_t, int) const override; + bool hasMscEvent(int32_t deviceId, int mscEvent) const override final; + status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState, + int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const override; + const KeyInfo* getKey(Device* device, int32_t scanCode, int32_t usageCode) const; + + status_t mapAxis(int32_t, int32_t, AxisInfo*) const override; + base::Result> mapSensor( + int32_t deviceId, int32_t absCode) const override; + void setExcludedDevices(const std::vector& devices) override; + std::vector getEvents(int) override; + std::vector getVideoFrames(int32_t deviceId) override; + int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override; + InputDeviceCountryCode getCountryCode(int32_t deviceId) const override; + int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override; + int32_t getSwitchState(int32_t deviceId, int32_t sw) const override; + status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const override; + int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override; + + // Return true if the device has non-empty key layout. + bool markSupportedKeyCodes(int32_t deviceId, const std::vector& keyCodes, + uint8_t* outFlags) const override; + bool hasScanCode(int32_t deviceId, int32_t scanCode) const override; + bool hasKeyCode(int32_t deviceId, int32_t keyCode) const override; + bool hasLed(int32_t deviceId, int32_t led) const override; + void setLedState(int32_t deviceId, int32_t led, bool on) override; + void getVirtualKeyDefinitions(int32_t deviceId, + std::vector& outVirtualKeys) const override; + const std::shared_ptr getKeyCharacterMap(int32_t) const override; + bool setKeyboardLayoutOverlay(int32_t, std::shared_ptr) override; + + void vibrate(int32_t, const VibrationElement&) override {} + void cancelVibrate(int32_t) override {} + std::vector getVibratorIds(int32_t deviceId) const override; + + std::optional getBatteryCapacity(int32_t, int32_t) const override; + std::optional getBatteryStatus(int32_t, int32_t) const override; + std::vector getRawBatteryIds(int32_t deviceId) const override; + std::optional getRawBatteryInfo(int32_t deviceId, + int32_t batteryId) const override; + + std::vector getRawLightIds(int32_t deviceId) const override; + std::optional getRawLightInfo(int32_t deviceId, int32_t lightId) const override; + void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) override; + void setLightIntensities(int32_t deviceId, int32_t lightId, + std::unordered_map intensities) override; + std::optional getLightBrightness(int32_t deviceId, int32_t lightId) const override; + std::optional> getLightIntensities( + int32_t deviceId, int32_t lightId) const override; + + void dump(std::string&) const override {} + void monitor() const override {} + void requestReopenDevices() override {} + void wake() override {} +}; + +} // namespace android diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 879d36e6fd..068216f695 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -40,6 +40,8 @@ #include #include +#include "FakeEventHub.h" +#include "TestConstants.h" #include "android/hardware/input/InputDeviceCountryCode.h" #include "input/DisplayViewport.h" #include "input/Input.h" @@ -52,13 +54,6 @@ using namespace ftl::flag_operators; using testing::AllOf; using std::chrono_literals::operator""ms; -// Timeout for waiting for an expected event -static constexpr std::chrono::duration WAIT_TIMEOUT = 100ms; - -// An arbitrary time value. -static constexpr nsecs_t ARBITRARY_TIME = 1234; -static constexpr nsecs_t READ_TIME = 4321; - // Arbitrary display properties. static constexpr int32_t DISPLAY_ID = 0; static const std::string DISPLAY_UNIQUE_ID = "local:1"; @@ -79,10 +74,6 @@ static constexpr int32_t INVALID_TRACKING_ID = -1; static constexpr int32_t FIRST_TRACKING_ID = 0; static constexpr int32_t SECOND_TRACKING_ID = 1; static constexpr int32_t THIRD_TRACKING_ID = 2; -static constexpr int32_t DEFAULT_BATTERY = 1; -static constexpr int32_t BATTERY_STATUS = 4; -static constexpr int32_t BATTERY_CAPACITY = 66; -static const std::string BATTERY_DEVPATH = "/sys/devices/mydevice/power_supply/mybattery"; static constexpr int32_t LIGHT_BRIGHTNESS = 0x55000000; static constexpr int32_t LIGHT_COLOR = 0x7F448866; static constexpr int32_t LIGHT_PLAYER_ID = 2; @@ -464,653 +455,6 @@ private: } }; -// --- FakeEventHub --- - -class FakeEventHub : public EventHubInterface { - struct KeyInfo { - int32_t keyCode; - uint32_t flags; - }; - - struct SensorInfo { - InputDeviceSensorType sensorType; - int32_t sensorDataIndex; - }; - - struct Device { - InputDeviceIdentifier identifier; - ftl::Flags classes; - PropertyMap configuration; - KeyedVector absoluteAxes; - KeyedVector relativeAxes; - KeyedVector keyCodeStates; - KeyedVector scanCodeStates; - KeyedVector switchStates; - KeyedVector absoluteAxisValue; - KeyedVector keysByScanCode; - KeyedVector keysByUsageCode; - KeyedVector leds; - // fake mapping which would normally come from keyCharacterMap - std::unordered_map keyCodeMapping; - std::unordered_map sensorsByAbsCode; - BitArray mscBitmask; - std::vector virtualKeys; - bool enabled; - InputDeviceCountryCode countryCode; - - status_t enable() { - enabled = true; - return OK; - } - - status_t disable() { - enabled = false; - return OK; - } - - explicit Device(ftl::Flags classes) : classes(classes), enabled(true) {} - }; - - std::mutex mLock; - std::condition_variable mEventsCondition; - - KeyedVector mDevices; - std::vector mExcludedDevices; - std::vector mEvents GUARDED_BY(mLock); - std::unordered_map> mVideoFrames; - std::vector mVibrators = {0, 1}; - std::unordered_map mRawLightInfos; - // Simulates a device light brightness, from light id to light brightness. - std::unordered_map mLightBrightness; - // Simulates a device light intensities, from light id to light intensities map. - std::unordered_map> - mLightIntensities; - -public: - virtual ~FakeEventHub() { - for (size_t i = 0; i < mDevices.size(); i++) { - delete mDevices.valueAt(i); - } - } - - FakeEventHub() { } - - void addDevice(int32_t deviceId, const std::string& name, ftl::Flags classes, - int bus = 0) { - Device* device = new Device(classes); - device->identifier.name = name; - device->identifier.bus = bus; - mDevices.add(deviceId, device); - - enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0); - } - - void removeDevice(int32_t deviceId) { - delete mDevices.valueFor(deviceId); - mDevices.removeItem(deviceId); - - enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0); - } - - bool isDeviceEnabled(int32_t deviceId) const override { - Device* device = getDevice(deviceId); - if (device == nullptr) { - ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); - return false; - } - return device->enabled; - } - - status_t enableDevice(int32_t deviceId) override { - status_t result; - Device* device = getDevice(deviceId); - if (device == nullptr) { - ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); - return BAD_VALUE; - } - if (device->enabled) { - ALOGW("Duplicate call to %s, device %" PRId32 " already enabled", __func__, deviceId); - return OK; - } - result = device->enable(); - return result; - } - - status_t disableDevice(int32_t deviceId) override { - Device* device = getDevice(deviceId); - if (device == nullptr) { - ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); - return BAD_VALUE; - } - if (!device->enabled) { - ALOGW("Duplicate call to %s, device %" PRId32 " already disabled", __func__, deviceId); - return OK; - } - return device->disable(); - } - - void finishDeviceScan() { - enqueueEvent(ARBITRARY_TIME, READ_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0); - } - - void addConfigurationProperty(int32_t deviceId, const char* key, const char* value) { - Device* device = getDevice(deviceId); - device->configuration.addProperty(key, value); - } - - void addConfigurationMap(int32_t deviceId, const PropertyMap* configuration) { - Device* device = getDevice(deviceId); - device->configuration.addAll(configuration); - } - - void addAbsoluteAxis(int32_t deviceId, int axis, - int32_t minValue, int32_t maxValue, int flat, int fuzz, int resolution = 0) { - Device* device = getDevice(deviceId); - - RawAbsoluteAxisInfo info; - info.valid = true; - info.minValue = minValue; - info.maxValue = maxValue; - info.flat = flat; - info.fuzz = fuzz; - info.resolution = resolution; - device->absoluteAxes.add(axis, info); - } - - void addRelativeAxis(int32_t deviceId, int32_t axis) { - Device* device = getDevice(deviceId); - device->relativeAxes.add(axis, true); - } - - void setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state) { - Device* device = getDevice(deviceId); - device->keyCodeStates.replaceValueFor(keyCode, state); - } - - void setCountryCode(int32_t deviceId, InputDeviceCountryCode countryCode) { - Device* device = getDevice(deviceId); - device->countryCode = countryCode; - } - - void setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state) { - Device* device = getDevice(deviceId); - device->scanCodeStates.replaceValueFor(scanCode, state); - } - - void setSwitchState(int32_t deviceId, int32_t switchCode, int32_t state) { - Device* device = getDevice(deviceId); - device->switchStates.replaceValueFor(switchCode, state); - } - - void setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value) { - Device* device = getDevice(deviceId); - device->absoluteAxisValue.replaceValueFor(axis, value); - } - - void addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, - int32_t keyCode, uint32_t flags) { - Device* device = getDevice(deviceId); - KeyInfo info; - info.keyCode = keyCode; - info.flags = flags; - if (scanCode) { - device->keysByScanCode.add(scanCode, info); - } - if (usageCode) { - device->keysByUsageCode.add(usageCode, info); - } - } - - void addKeyCodeMapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) { - Device* device = getDevice(deviceId); - device->keyCodeMapping.insert_or_assign(fromKeyCode, toKeyCode); - } - - void addLed(int32_t deviceId, int32_t led, bool initialState) { - Device* device = getDevice(deviceId); - device->leds.add(led, initialState); - } - - void addSensorAxis(int32_t deviceId, int32_t absCode, InputDeviceSensorType sensorType, - int32_t sensorDataIndex) { - Device* device = getDevice(deviceId); - SensorInfo info; - info.sensorType = sensorType; - info.sensorDataIndex = sensorDataIndex; - device->sensorsByAbsCode.emplace(absCode, info); - } - - void setMscEvent(int32_t deviceId, int32_t mscEvent) { - Device* device = getDevice(deviceId); - typename BitArray::Buffer buffer; - buffer[mscEvent / 32] = 1 << mscEvent % 32; - device->mscBitmask.loadFromBuffer(buffer); - } - - void addRawLightInfo(int32_t rawId, RawLightInfo&& info) { - mRawLightInfos.emplace(rawId, std::move(info)); - } - - void fakeLightBrightness(int32_t rawId, int32_t brightness) { - mLightBrightness.emplace(rawId, brightness); - } - - void fakeLightIntensities(int32_t rawId, - const std::unordered_map intensities) { - mLightIntensities.emplace(rawId, std::move(intensities)); - } - - bool getLedState(int32_t deviceId, int32_t led) { - Device* device = getDevice(deviceId); - return device->leds.valueFor(led); - } - - std::vector& getExcludedDevices() { - return mExcludedDevices; - } - - void addVirtualKeyDefinition(int32_t deviceId, const VirtualKeyDefinition& definition) { - Device* device = getDevice(deviceId); - device->virtualKeys.push_back(definition); - } - - void enqueueEvent(nsecs_t when, nsecs_t readTime, int32_t deviceId, int32_t type, int32_t code, - int32_t value) { - std::scoped_lock lock(mLock); - RawEvent event; - event.when = when; - event.readTime = readTime; - event.deviceId = deviceId; - event.type = type; - event.code = code; - event.value = value; - mEvents.push_back(event); - - if (type == EV_ABS) { - setAbsoluteAxisValue(deviceId, code, value); - } - } - - void setVideoFrames(std::unordered_map> videoFrames) { - mVideoFrames = std::move(videoFrames); - } - - void assertQueueIsEmpty() { - std::unique_lock lock(mLock); - base::ScopedLockAssertion assumeLocked(mLock); - const bool queueIsEmpty = - mEventsCondition.wait_for(lock, WAIT_TIMEOUT, - [this]() REQUIRES(mLock) { return mEvents.size() == 0; }); - if (!queueIsEmpty) { - FAIL() << "Timed out waiting for EventHub queue to be emptied."; - } - } - -private: - Device* getDevice(int32_t deviceId) const { - ssize_t index = mDevices.indexOfKey(deviceId); - return index >= 0 ? mDevices.valueAt(index) : nullptr; - } - - ftl::Flags getDeviceClasses(int32_t deviceId) const override { - Device* device = getDevice(deviceId); - return device ? device->classes : ftl::Flags(0); - } - - InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override { - Device* device = getDevice(deviceId); - return device ? device->identifier : InputDeviceIdentifier(); - } - - int32_t getDeviceControllerNumber(int32_t) const override { return 0; } - - void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const override { - Device* device = getDevice(deviceId); - if (device) { - *outConfiguration = device->configuration; - } - } - - status_t getAbsoluteAxisInfo(int32_t deviceId, int axis, - RawAbsoluteAxisInfo* outAxisInfo) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->absoluteAxes.indexOfKey(axis); - if (index >= 0) { - *outAxisInfo = device->absoluteAxes.valueAt(index); - return OK; - } - } - outAxisInfo->clear(); - return -1; - } - - bool hasRelativeAxis(int32_t deviceId, int axis) const override { - Device* device = getDevice(deviceId); - if (device) { - return device->relativeAxes.indexOfKey(axis) >= 0; - } - return false; - } - - bool hasInputProperty(int32_t, int) const override { return false; } - - bool hasMscEvent(int32_t deviceId, int mscEvent) const override final { - Device* device = getDevice(deviceId); - if (device) { - return mscEvent >= 0 && mscEvent <= MSC_MAX ? device->mscBitmask.test(mscEvent) : false; - } - return false; - } - - status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState, - int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const override { - Device* device = getDevice(deviceId); - if (device) { - const KeyInfo* key = getKey(device, scanCode, usageCode); - if (key) { - if (outKeycode) { - *outKeycode = key->keyCode; - } - if (outFlags) { - *outFlags = key->flags; - } - if (outMetaState) { - *outMetaState = metaState; - } - return OK; - } - } - return NAME_NOT_FOUND; - } - - const KeyInfo* getKey(Device* device, int32_t scanCode, int32_t usageCode) const { - if (usageCode) { - ssize_t index = device->keysByUsageCode.indexOfKey(usageCode); - if (index >= 0) { - return &device->keysByUsageCode.valueAt(index); - } - } - if (scanCode) { - ssize_t index = device->keysByScanCode.indexOfKey(scanCode); - if (index >= 0) { - return &device->keysByScanCode.valueAt(index); - } - } - return nullptr; - } - - status_t mapAxis(int32_t, int32_t, AxisInfo*) const override { return NAME_NOT_FOUND; } - - base::Result> mapSensor( - int32_t deviceId, int32_t absCode) const override { - Device* device = getDevice(deviceId); - if (!device) { - return Errorf("Sensor device not found."); - } - auto it = device->sensorsByAbsCode.find(absCode); - if (it == device->sensorsByAbsCode.end()) { - return Errorf("Sensor map not found."); - } - const SensorInfo& info = it->second; - return std::make_pair(info.sensorType, info.sensorDataIndex); - } - - void setExcludedDevices(const std::vector& devices) override { - mExcludedDevices = devices; - } - - std::vector getEvents(int) override { - std::scoped_lock lock(mLock); - - std::vector buffer; - std::swap(buffer, mEvents); - - mEventsCondition.notify_all(); - return buffer; - } - - std::vector getVideoFrames(int32_t deviceId) override { - auto it = mVideoFrames.find(deviceId); - if (it != mVideoFrames.end()) { - std::vector frames = std::move(it->second); - mVideoFrames.erase(deviceId); - return frames; - } - return {}; - } - - int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->scanCodeStates.indexOfKey(scanCode); - if (index >= 0) { - return device->scanCodeStates.valueAt(index); - } - } - return AKEY_STATE_UNKNOWN; - } - - InputDeviceCountryCode getCountryCode(int32_t deviceId) const override { - Device* device = getDevice(deviceId); - if (device) { - return device->countryCode; - } - return InputDeviceCountryCode::INVALID; - } - - int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->keyCodeStates.indexOfKey(keyCode); - if (index >= 0) { - return device->keyCodeStates.valueAt(index); - } - } - return AKEY_STATE_UNKNOWN; - } - - int32_t getSwitchState(int32_t deviceId, int32_t sw) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->switchStates.indexOfKey(sw); - if (index >= 0) { - return device->switchStates.valueAt(index); - } - } - return AKEY_STATE_UNKNOWN; - } - - status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, - int32_t* outValue) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->absoluteAxisValue.indexOfKey(axis); - if (index >= 0) { - *outValue = device->absoluteAxisValue.valueAt(index); - return OK; - } - } - *outValue = 0; - return -1; - } - - int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override { - Device* device = getDevice(deviceId); - if (!device) { - return AKEYCODE_UNKNOWN; - } - auto it = device->keyCodeMapping.find(locationKeyCode); - return it != device->keyCodeMapping.end() ? it->second : locationKeyCode; - } - - // Return true if the device has non-empty key layout. - bool markSupportedKeyCodes(int32_t deviceId, const std::vector& keyCodes, - uint8_t* outFlags) const override { - bool result = false; - Device* device = getDevice(deviceId); - if (device) { - result = device->keysByScanCode.size() > 0 || device->keysByUsageCode.size() > 0; - for (size_t i = 0; i < keyCodes.size(); i++) { - for (size_t j = 0; j < device->keysByScanCode.size(); j++) { - if (keyCodes[i] == device->keysByScanCode.valueAt(j).keyCode) { - outFlags[i] = 1; - } - } - for (size_t j = 0; j < device->keysByUsageCode.size(); j++) { - if (keyCodes[i] == device->keysByUsageCode.valueAt(j).keyCode) { - outFlags[i] = 1; - } - } - } - } - return result; - } - - bool hasScanCode(int32_t deviceId, int32_t scanCode) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->keysByScanCode.indexOfKey(scanCode); - return index >= 0; - } - return false; - } - - bool hasKeyCode(int32_t deviceId, int32_t keyCode) const override { - Device* device = getDevice(deviceId); - if (!device) { - return false; - } - for (size_t i = 0; i < device->keysByScanCode.size(); i++) { - if (keyCode == device->keysByScanCode.valueAt(i).keyCode) { - return true; - } - } - for (size_t j = 0; j < device->keysByUsageCode.size(); j++) { - if (keyCode == device->keysByUsageCode.valueAt(j).keyCode) { - return true; - } - } - return false; - } - - bool hasLed(int32_t deviceId, int32_t led) const override { - Device* device = getDevice(deviceId); - return device && device->leds.indexOfKey(led) >= 0; - } - - void setLedState(int32_t deviceId, int32_t led, bool on) override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->leds.indexOfKey(led); - if (index >= 0) { - device->leds.replaceValueAt(led, on); - } else { - ADD_FAILURE() - << "Attempted to set the state of an LED that the EventHub declared " - "was not present. led=" << led; - } - } - } - - void getVirtualKeyDefinitions( - int32_t deviceId, std::vector& outVirtualKeys) const override { - outVirtualKeys.clear(); - - Device* device = getDevice(deviceId); - if (device) { - outVirtualKeys = device->virtualKeys; - } - } - - const std::shared_ptr getKeyCharacterMap(int32_t) const override { - return nullptr; - } - - bool setKeyboardLayoutOverlay(int32_t, std::shared_ptr) override { - return false; - } - - void vibrate(int32_t, const VibrationElement&) override {} - - void cancelVibrate(int32_t) override {} - - std::vector getVibratorIds(int32_t deviceId) const override { return mVibrators; }; - - std::optional getBatteryCapacity(int32_t, int32_t) const override { - return BATTERY_CAPACITY; - } - - std::optional getBatteryStatus(int32_t, int32_t) const override { - return BATTERY_STATUS; - } - - std::vector getRawBatteryIds(int32_t deviceId) const override { - return {DEFAULT_BATTERY}; - } - - std::optional getRawBatteryInfo(int32_t deviceId, - int32_t batteryId) const override { - if (batteryId != DEFAULT_BATTERY) return {}; - static const auto BATTERY_INFO = RawBatteryInfo{.id = DEFAULT_BATTERY, - .name = "default battery", - .flags = InputBatteryClass::CAPACITY, - .path = BATTERY_DEVPATH}; - return BATTERY_INFO; - } - - std::vector getRawLightIds(int32_t deviceId) const override { - std::vector ids; - for (const auto& [rawId, info] : mRawLightInfos) { - ids.push_back(rawId); - } - return ids; - } - - std::optional getRawLightInfo(int32_t deviceId, int32_t lightId) const override { - auto it = mRawLightInfos.find(lightId); - if (it == mRawLightInfos.end()) { - return std::nullopt; - } - return it->second; - } - - void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) override { - mLightBrightness.emplace(lightId, brightness); - } - - void setLightIntensities(int32_t deviceId, int32_t lightId, - std::unordered_map intensities) override { - mLightIntensities.emplace(lightId, intensities); - }; - - std::optional getLightBrightness(int32_t deviceId, int32_t lightId) const override { - auto lightIt = mLightBrightness.find(lightId); - if (lightIt == mLightBrightness.end()) { - return std::nullopt; - } - return lightIt->second; - } - - std::optional> getLightIntensities( - int32_t deviceId, int32_t lightId) const override { - auto lightIt = mLightIntensities.find(lightId); - if (lightIt == mLightIntensities.end()) { - return std::nullopt; - } - return lightIt->second; - }; - - void dump(std::string&) const override {} - - void monitor() const override {} - - void requestReopenDevices() override {} - - void wake() override {} -}; - // --- FakeInputMapper --- class FakeInputMapper : public InputMapper { @@ -2258,8 +1602,9 @@ TEST_F(InputReaderTest, BatteryGetCapacity) { ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); - ASSERT_EQ(controller.getBatteryCapacity(DEFAULT_BATTERY), BATTERY_CAPACITY); - ASSERT_EQ(mReader->getBatteryCapacity(deviceId), BATTERY_CAPACITY); + ASSERT_EQ(controller.getBatteryCapacity(FakeEventHub::DEFAULT_BATTERY), + FakeEventHub::BATTERY_CAPACITY); + ASSERT_EQ(mReader->getBatteryCapacity(deviceId), FakeEventHub::BATTERY_CAPACITY); } TEST_F(InputReaderTest, BatteryGetStatus) { @@ -2275,8 +1620,9 @@ TEST_F(InputReaderTest, BatteryGetStatus) { ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); - ASSERT_EQ(controller.getBatteryStatus(DEFAULT_BATTERY), BATTERY_STATUS); - ASSERT_EQ(mReader->getBatteryStatus(deviceId), BATTERY_STATUS); + ASSERT_EQ(controller.getBatteryStatus(FakeEventHub::DEFAULT_BATTERY), + FakeEventHub::BATTERY_STATUS); + ASSERT_EQ(mReader->getBatteryStatus(deviceId), FakeEventHub::BATTERY_STATUS); } TEST_F(InputReaderTest, BatteryGetDevicePath) { @@ -2291,7 +1637,7 @@ TEST_F(InputReaderTest, BatteryGetDevicePath) { ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); - ASSERT_EQ(mReader->getBatteryDevicePath(deviceId), BATTERY_DEVPATH); + ASSERT_EQ(mReader->getBatteryDevicePath(deviceId), FakeEventHub::BATTERY_DEVPATH); } TEST_F(InputReaderTest, LightGetColor) { @@ -10879,15 +10225,17 @@ protected: TEST_F(BatteryControllerTest, GetBatteryCapacity) { PeripheralController& controller = addControllerAndConfigure(); - ASSERT_TRUE(controller.getBatteryCapacity(DEFAULT_BATTERY)); - ASSERT_EQ(controller.getBatteryCapacity(DEFAULT_BATTERY).value_or(-1), BATTERY_CAPACITY); + ASSERT_TRUE(controller.getBatteryCapacity(FakeEventHub::DEFAULT_BATTERY)); + ASSERT_EQ(controller.getBatteryCapacity(FakeEventHub::DEFAULT_BATTERY).value_or(-1), + FakeEventHub::BATTERY_CAPACITY); } TEST_F(BatteryControllerTest, GetBatteryStatus) { PeripheralController& controller = addControllerAndConfigure(); - ASSERT_TRUE(controller.getBatteryStatus(DEFAULT_BATTERY)); - ASSERT_EQ(controller.getBatteryStatus(DEFAULT_BATTERY).value_or(-1), BATTERY_STATUS); + ASSERT_TRUE(controller.getBatteryStatus(FakeEventHub::DEFAULT_BATTERY)); + ASSERT_EQ(controller.getBatteryStatus(FakeEventHub::DEFAULT_BATTERY).value_or(-1), + FakeEventHub::BATTERY_STATUS); } // --- LightControllerTest --- diff --git a/services/inputflinger/tests/TestConstants.h b/services/inputflinger/tests/TestConstants.h new file mode 100644 index 0000000000..8dc9d71392 --- /dev/null +++ b/services/inputflinger/tests/TestConstants.h @@ -0,0 +1,30 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +namespace android { + +using std::chrono_literals::operator""ms; + +// Timeout for waiting for an expected event +static constexpr std::chrono::duration WAIT_TIMEOUT = 100ms; + +// An arbitrary time value. +static constexpr nsecs_t ARBITRARY_TIME = 1234; +static constexpr nsecs_t READ_TIME = 4321; + +} // namespace android -- GitLab From ace3d05494a29ab9666c37f8646879635fb9b583 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Thu, 17 Nov 2022 16:25:05 -0800 Subject: [PATCH 0543/1310] SF: add render frame rate to the scheduler Schedule SF at the rate of the render frame rate instead of the display refresh rate. Test: SF unit tests Bug: 257072060 Change-Id: Idaf9be5f25373d38c0ef6440f9f401dc90de7a91 --- .../Display/DisplayModeRequest.h | 6 +- services/surfaceflinger/DisplayDevice.cpp | 56 +++--- services/surfaceflinger/DisplayDevice.h | 22 ++- services/surfaceflinger/Layer.cpp | 2 +- .../Scheduler/RefreshRateSelector.cpp | 67 ++++--- .../Scheduler/RefreshRateSelector.h | 17 +- .../surfaceflinger/Scheduler/Scheduler.cpp | 82 ++++---- services/surfaceflinger/Scheduler/Scheduler.h | 25 +-- .../Scheduler/VSyncPredictor.cpp | 24 ++- .../surfaceflinger/Scheduler/VSyncPredictor.h | 6 + .../surfaceflinger/Scheduler/VSyncTracker.h | 11 ++ .../include/scheduler/FrameRateMode.h | 10 +- services/surfaceflinger/SurfaceFlinger.cpp | 133 +++++++------ services/surfaceflinger/SurfaceFlinger.h | 2 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 8 +- .../surfaceflinger_scheduler_fuzzer.cpp | 3 +- .../fuzzer/surfaceflinger_scheduler_fuzzer.h | 2 + .../DisplayDevice_InitiateModeChange.cpp | 66 +++++-- .../unittests/RefreshRateSelectorTest.cpp | 176 ++++++++++-------- .../tests/unittests/SchedulerTest.cpp | 69 ++++--- .../SurfaceFlinger_DisplayModeSwitching.cpp | 32 ++-- ...nger_SetupNewDisplayDeviceInternalTest.cpp | 2 +- .../tests/unittests/TestableScheduler.h | 5 +- .../tests/unittests/TestableSurfaceFlinger.h | 15 +- .../unittests/VSyncDispatchRealtimeTest.cpp | 2 + .../unittests/VSyncDispatchTimerQueueTest.cpp | 1 + .../tests/unittests/VSyncPredictorTest.cpp | 20 ++ .../tests/unittests/VSyncReactorTest.cpp | 1 + .../tests/unittests/mock/MockFrameRateMode.h | 23 +++ .../tests/unittests/mock/MockVSyncTracker.h | 1 + 30 files changed, 542 insertions(+), 347 deletions(-) create mode 100644 services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h diff --git a/services/surfaceflinger/Display/DisplayModeRequest.h b/services/surfaceflinger/Display/DisplayModeRequest.h index ac25fe08a5..d07cdf55d2 100644 --- a/services/surfaceflinger/Display/DisplayModeRequest.h +++ b/services/surfaceflinger/Display/DisplayModeRequest.h @@ -18,19 +18,19 @@ #include -#include "DisplayHardware/DisplayMode.h" +#include namespace android::display { struct DisplayModeRequest { - ftl::NonNull modePtr; + scheduler::FrameRateMode mode; // Whether to emit DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE. bool emitEvent = false; }; inline bool operator==(const DisplayModeRequest& lhs, const DisplayModeRequest& rhs) { - return lhs.modePtr == rhs.modePtr && lhs.emitEvent == rhs.emitEvent; + return lhs.mode == rhs.mode && lhs.emitEvent == rhs.emitEvent; } } // namespace android::display diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index b40c6d1aac..269a5ea561 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -68,6 +68,7 @@ DisplayDevice::DisplayDevice(DisplayDeviceCreationArgs& args) mCompositionDisplay{args.compositionDisplay}, mActiveModeFPSTrace("ActiveModeFPS -" + to_string(getId())), mActiveModeFPSHwcTrace("ActiveModeFPS_HWC -" + to_string(getId())), + mRenderFrameRateFPSTrace("RenderRateFPS -" + to_string(getId())), mPhysicalOrientation(args.physicalOrientation), mIsPrimary(args.isPrimary), mRefreshRateSelector(std::move(args.refreshRateSelector)) { @@ -195,35 +196,32 @@ bool DisplayDevice::isPoweredOn() const { return mPowerMode && *mPowerMode != hal::PowerMode::OFF; } -void DisplayDevice::setActiveMode(DisplayModeId modeId, const display::DisplaySnapshot& snapshot) { - const auto fpsOpt = snapshot.displayModes().get(modeId).transform( - [](const DisplayModePtr& mode) { return mode->getFps(); }); +void DisplayDevice::setActiveMode(DisplayModeId modeId, Fps displayFps, Fps renderFps) { + ATRACE_INT(mActiveModeFPSTrace.c_str(), displayFps.getIntValue()); + ATRACE_INT(mRenderFrameRateFPSTrace.c_str(), renderFps.getIntValue()); - LOG_ALWAYS_FATAL_IF(!fpsOpt, "Unknown mode"); - const Fps fps = *fpsOpt; - - ATRACE_INT(mActiveModeFPSTrace.c_str(), fps.getIntValue()); - - mRefreshRateSelector->setActiveModeId(modeId); + mRefreshRateSelector->setActiveMode(modeId, renderFps); if (mRefreshRateOverlay) { - mRefreshRateOverlay->changeRefreshRate(fps); + mRefreshRateOverlay->changeRefreshRate(displayFps); } } status_t DisplayDevice::initiateModeChange(const ActiveModeInfo& info, const hal::VsyncPeriodChangeConstraints& constraints, hal::VsyncPeriodChangeTimeline* outTimeline) { - if (!info.mode || info.mode->getPhysicalDisplayId() != getPhysicalId()) { + if (!info.modeOpt || info.modeOpt->modePtr->getPhysicalDisplayId() != getPhysicalId()) { ALOGE("Trying to initiate a mode change to invalid mode %s on display %s", - info.mode ? std::to_string(info.mode->getId().value()).c_str() : "null", + info.modeOpt ? std::to_string(info.modeOpt->modePtr->getId().value()).c_str() + : "null", to_string(getId()).c_str()); return BAD_VALUE; } mUpcomingActiveMode = info; - ATRACE_INT(mActiveModeFPSHwcTrace.c_str(), info.mode->getFps().getIntValue()); - return mHwComposer.setActiveModeWithConstraints(getPhysicalId(), info.mode->getHwcId(), - constraints, outTimeline); + ATRACE_INT(mActiveModeFPSHwcTrace.c_str(), info.modeOpt->modePtr->getFps().getIntValue()); + return mHwComposer.setActiveModeWithConstraints(getPhysicalId(), + info.modeOpt->modePtr->getHwcId(), constraints, + outTimeline); } nsecs_t DisplayDevice::getVsyncPeriodFromHWC() const { @@ -238,7 +236,7 @@ nsecs_t DisplayDevice::getVsyncPeriodFromHWC() const { return vsyncPeriod; } - return refreshRateSelector().getActiveModePtr()->getVsyncPeriod(); + return refreshRateSelector().getActiveMode().modePtr->getVsyncPeriod(); } ui::Dataspace DisplayDevice::getCompositionDataSpace() const { @@ -422,7 +420,7 @@ void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinnner) { mRefreshRateOverlay = std::make_unique(fpsRange, showSpinnner); mRefreshRateOverlay->setLayerStack(getLayerStack()); mRefreshRateOverlay->setViewport(getSize()); - mRefreshRateOverlay->changeRefreshRate(getActiveMode().getFps()); + mRefreshRateOverlay->changeRefreshRate(getActiveMode().modePtr->getFps()); } bool DisplayDevice::onKernelTimerChanged(std::optional desiredModeId, @@ -445,13 +443,14 @@ void DisplayDevice::animateRefreshRateOverlay() { } } -bool DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info) { +auto DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info) -> DesiredActiveModeAction { ATRACE_CALL(); - LOG_ALWAYS_FATAL_IF(!info.mode, "desired mode not provided"); - LOG_ALWAYS_FATAL_IF(getPhysicalId() != info.mode->getPhysicalDisplayId(), "DisplayId mismatch"); + LOG_ALWAYS_FATAL_IF(!info.modeOpt, "desired mode not provided"); + LOG_ALWAYS_FATAL_IF(getPhysicalId() != info.modeOpt->modePtr->getPhysicalDisplayId(), + "DisplayId mismatch"); - ALOGV("%s(%s)", __func__, to_string(*info.mode).c_str()); + ALOGV("%s(%s)", __func__, to_string(*info.modeOpt->modePtr).c_str()); std::scoped_lock lock(mActiveModeLock); if (mDesiredActiveModeChanged) { @@ -459,18 +458,25 @@ bool DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info) { const auto prevConfig = mDesiredActiveMode.event; mDesiredActiveMode = info; mDesiredActiveMode.event = mDesiredActiveMode.event | prevConfig; - return false; + return DesiredActiveModeAction::None; } + const auto& desiredMode = *info.modeOpt->modePtr; + // Check if we are already at the desired mode - if (refreshRateSelector().getActiveModePtr()->getId() == info.mode->getId()) { - return false; + if (refreshRateSelector().getActiveMode().modePtr->getId() == desiredMode.getId()) { + if (refreshRateSelector().getActiveMode() == info.modeOpt) { + return DesiredActiveModeAction::None; + } + + setActiveMode(desiredMode.getId(), desiredMode.getFps(), info.modeOpt->fps); + return DesiredActiveModeAction::InitiateRenderRateSwitch; } // Initiate a mode change. mDesiredActiveModeChanged = true; mDesiredActiveMode = info; - return true; + return DesiredActiveModeAction::InitiateDisplayModeSwitch; } std::optional DisplayDevice::getDesiredActiveMode() const { diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index 4b64aab2c5..852a8a297d 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -190,33 +190,38 @@ public: using Event = scheduler::DisplayModeEvent; ActiveModeInfo() = default; - ActiveModeInfo(DisplayModePtr mode, Event event) : mode(std::move(mode)), event(event) {} + ActiveModeInfo(scheduler::FrameRateMode mode, Event event) + : modeOpt(std::move(mode)), event(event) {} explicit ActiveModeInfo(display::DisplayModeRequest&& request) - : ActiveModeInfo(std::move(request.modePtr).take(), + : ActiveModeInfo(std::move(request.mode), request.emitEvent ? Event::Changed : Event::None) {} - DisplayModePtr mode; + ftl::Optional modeOpt; Event event = Event::None; bool operator!=(const ActiveModeInfo& other) const { - return mode != other.mode || event != other.event; + return modeOpt != other.modeOpt || event != other.event; } }; - bool setDesiredActiveMode(const ActiveModeInfo&) EXCLUDES(mActiveModeLock); + enum class DesiredActiveModeAction { + None, + InitiateDisplayModeSwitch, + InitiateRenderRateSwitch + }; + DesiredActiveModeAction setDesiredActiveMode(const ActiveModeInfo&) EXCLUDES(mActiveModeLock); std::optional getDesiredActiveMode() const EXCLUDES(mActiveModeLock); void clearDesiredActiveModeState() EXCLUDES(mActiveModeLock); ActiveModeInfo getUpcomingActiveMode() const REQUIRES(kMainThreadContext) { return mUpcomingActiveMode; } - const DisplayMode& getActiveMode() const REQUIRES(kMainThreadContext) { + scheduler::FrameRateMode getActiveMode() const REQUIRES(kMainThreadContext) { return mRefreshRateSelector->getActiveMode(); } - // Precondition: DisplaySnapshot must contain a mode with DisplayModeId. - void setActiveMode(DisplayModeId, const display::DisplaySnapshot&) REQUIRES(kMainThreadContext); + void setActiveMode(DisplayModeId, Fps displayFps, Fps renderFps); status_t initiateModeChange(const ActiveModeInfo&, const hal::VsyncPeriodChangeConstraints& constraints, @@ -254,6 +259,7 @@ private: std::string mDisplayName; std::string mActiveModeFPSTrace; std::string mActiveModeFPSHwcTrace; + std::string mRenderFrameRateFPSTrace; const ui::Rotation mPhysicalOrientation; ui::Rotation mOrientation = ui::ROTATION_0; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index d53f0b167c..0017af0476 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -3597,7 +3597,7 @@ void Layer::onPostComposition(const DisplayDevice* display, } if (display) { - const Fps refreshRate = display->refreshRateSelector().getActiveModePtr()->getFps(); + const Fps refreshRate = display->refreshRateSelector().getActiveMode().fps; const std::optional renderRate = mFlinger->mScheduler->getFrameRateOverride(getOwnerUid()); diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index fd1a733b7a..4be1ac7c6f 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -213,7 +213,7 @@ auto RefreshRateSelector::createFrameRateModes( std::vector frameRateModes; frameRateModes.reserve(ratesMap.size()); for (const auto& [key, mode] : ratesMap) { - frameRateModes.emplace_back(FrameRateMode{key.fps, mode->second}); + frameRateModes.emplace_back(FrameRateMode{key.fps, ftl::as_non_null(mode->second)}); } // We always want that the lowest frame rate will be corresponding to the @@ -409,7 +409,7 @@ auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vectorsecond; + const auto& activeMode = *getActiveModeLocked().modePtr; // Keep the display at max frame rate for the duration of powering on the display. if (signals.powerOnImminent) { @@ -842,7 +842,7 @@ std::optional RefreshRateSelector::onKernelTimerChanged( const DisplayModePtr& current = desiredActiveModeId ? mDisplayModes.get(*desiredActiveModeId)->get() - : getActiveModeItLocked()->second; + : getActiveModeLocked().modePtr.get(); const DisplayModePtr& min = mMinRefreshRateModeIt->second; if (current == min) { @@ -854,11 +854,11 @@ std::optional RefreshRateSelector::onKernelTimerChanged( } const DisplayModePtr& RefreshRateSelector::getMinRefreshRateByPolicyLocked() const { - const auto& activeMode = *getActiveModeItLocked()->second; + const auto& activeMode = *getActiveModeLocked().modePtr; for (const FrameRateMode& mode : mPrimaryFrameRates) { if (activeMode.getGroup() == mode.modePtr->getGroup()) { - return mode.modePtr; + return mode.modePtr.get(); } } @@ -866,12 +866,12 @@ const DisplayModePtr& RefreshRateSelector::getMinRefreshRateByPolicyLocked() con to_string(activeMode).c_str()); // Default to the lowest refresh rate. - return mPrimaryFrameRates.front().modePtr; + return mPrimaryFrameRates.front().modePtr.get(); } const DisplayModePtr& RefreshRateSelector::getMaxRefreshRateByPolicyLocked(int anchorGroup) const { - const DisplayModePtr* maxByAnchor = &mPrimaryFrameRates.back().modePtr; - const DisplayModePtr* max = &mPrimaryFrameRates.back().modePtr; + const ftl::NonNull* maxByAnchor = &mPrimaryFrameRates.back().modePtr; + const ftl::NonNull* max = &mPrimaryFrameRates.back().modePtr; bool maxByAnchorFound = false; for (auto it = mPrimaryFrameRates.rbegin(); it != mPrimaryFrameRates.rend(); ++it) { @@ -888,13 +888,13 @@ const DisplayModePtr& RefreshRateSelector::getMaxRefreshRateByPolicyLocked(int a } if (maxByAnchorFound) { - return *maxByAnchor; + return maxByAnchor->get(); } ALOGE("Can't find max refresh rate by policy with the same group %d", anchorGroup); // Default to the highest refresh rate. - return *max; + return max->get(); } auto RefreshRateSelector::rankFrameRates(std::optional anchorGroupOpt, @@ -946,31 +946,26 @@ auto RefreshRateSelector::rankFrameRates(std::optional anchorGroupOpt, return rankFrameRates(kNoAnchorGroup, refreshRateOrder, preferredDisplayModeOpt); } -DisplayModePtr RefreshRateSelector::getActiveModePtr() const { +FrameRateMode RefreshRateSelector::getActiveMode() const { std::lock_guard lock(mLock); - return getActiveModeItLocked()->second; + return getActiveModeLocked(); } -const DisplayMode& RefreshRateSelector::getActiveMode() const { - // Reads from kMainThreadContext do not require mLock. - ftl::FakeGuard guard(mLock); - return *mActiveModeIt->second; +const FrameRateMode& RefreshRateSelector::getActiveModeLocked() const { + return *mActiveModeOpt; } -DisplayModeIterator RefreshRateSelector::getActiveModeItLocked() const { - // Reads under mLock do not require kMainThreadContext. - return FTL_FAKE_GUARD(kMainThreadContext, mActiveModeIt); -} - -void RefreshRateSelector::setActiveModeId(DisplayModeId modeId) { +void RefreshRateSelector::setActiveMode(DisplayModeId modeId, Fps renderFrameRate) { std::lock_guard lock(mLock); // Invalidate the cached invocation to getRankedFrameRates. This forces // the refresh rate to be recomputed on the next call to getRankedFrameRates. mGetRankedFrameRatesCache.reset(); - mActiveModeIt = mDisplayModes.find(modeId); - LOG_ALWAYS_FATAL_IF(mActiveModeIt == mDisplayModes.end()); + const auto activeModeOpt = mDisplayModes.get(modeId); + LOG_ALWAYS_FATAL_IF(!activeModeOpt); + + mActiveModeOpt.emplace(FrameRateMode{renderFrameRate, ftl::as_non_null(activeModeOpt->get())}); } RefreshRateSelector::RefreshRateSelector(DisplayModes modes, DisplayModeId activeModeId, @@ -1007,8 +1002,10 @@ void RefreshRateSelector::updateDisplayModes(DisplayModes modes, DisplayModeId a mGetRankedFrameRatesCache.reset(); mDisplayModes = std::move(modes); - mActiveModeIt = mDisplayModes.find(activeModeId); - LOG_ALWAYS_FATAL_IF(mActiveModeIt == mDisplayModes.end()); + const auto activeModeOpt = mDisplayModes.get(activeModeId); + LOG_ALWAYS_FATAL_IF(!activeModeOpt); + mActiveModeOpt = + FrameRateMode{activeModeOpt->get()->getFps(), ftl::as_non_null(activeModeOpt->get())}; const auto sortedModes = sortByRefreshRate(mDisplayModes); mMinRefreshRateModeIt = sortedModes.front(); @@ -1064,6 +1061,7 @@ bool RefreshRateSelector::isPolicyValidLocked(const Policy& policy) const { auto RefreshRateSelector::setPolicy(const PolicyVariant& policy) -> SetPolicyResult { Policy oldPolicy; + PhysicalDisplayId displayId; { std::lock_guard lock(mLock); oldPolicy = *getCurrentPolicyLocked(); @@ -1103,9 +1101,10 @@ auto RefreshRateSelector::setPolicy(const PolicyVariant& policy) -> SetPolicyRes return SetPolicyResult::Unchanged; } constructAvailableRefreshRates(); + + displayId = getActiveModeLocked().modePtr->getPhysicalDisplayId(); } - const auto displayId = getActiveMode().getPhysicalDisplayId(); const unsigned numModeChanges = std::exchange(mNumModeSwitchesInPolicy, 0u); ALOGI("Display %s policy changed\n" @@ -1132,12 +1131,10 @@ auto RefreshRateSelector::getDisplayManagerPolicy() const -> Policy { return mDisplayManagerPolicy; } -bool RefreshRateSelector::isModeAllowed(DisplayModeId modeId) const { +bool RefreshRateSelector::isModeAllowed(const FrameRateMode& mode) const { std::lock_guard lock(mLock); - return std::any_of(mAppRequestFrameRates.begin(), mAppRequestFrameRates.end(), - [modeId](const FrameRateMode& frameRateMode) { - return frameRateMode.modePtr->getId() == modeId; - }); + return std::find(mAppRequestFrameRates.begin(), mAppRequestFrameRates.end(), mode) != + mAppRequestFrameRates.end(); } void RefreshRateSelector::constructAvailableRefreshRates() { @@ -1211,7 +1208,7 @@ auto RefreshRateSelector::getIdleTimerAction() const -> KernelIdleTimerAction { } const DisplayModePtr& maxByPolicy = - getMaxRefreshRateByPolicyLocked(getActiveModeItLocked()->second->getGroup()); + getMaxRefreshRateByPolicyLocked(getActiveModeLocked().modePtr->getGroup()); if (minByPolicy == maxByPolicy) { // Turn on the timer when the min of the primary range is below the device min. if (const Policy* currentPolicy = getCurrentPolicyLocked(); @@ -1257,8 +1254,8 @@ void RefreshRateSelector::dump(utils::Dumper& dumper) const { std::lock_guard lock(mLock); - const auto activeModeId = getActiveModeItLocked()->first; - dumper.dump("activeModeId"sv, std::to_string(activeModeId.value())); + const auto activeMode = getActiveModeLocked(); + dumper.dump("activeMode"sv, to_string(activeMode)); dumper.dump("displayModes"sv); { diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h index 89ebeeaff7..1ed16c6a52 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h @@ -133,7 +133,7 @@ public: Policy getDisplayManagerPolicy() const EXCLUDES(mLock); // Returns true if mode is allowed by the current policy. - bool isModeAllowed(DisplayModeId) const EXCLUDES(mLock); + bool isModeAllowed(const FrameRateMode&) const EXCLUDES(mLock); // Describes the different options the layer voted for refresh rate enum class LayerVoteType { @@ -243,11 +243,10 @@ public: std::optional onKernelTimerChanged(std::optional desiredActiveModeId, bool timerExpired) const EXCLUDES(mLock); - void setActiveModeId(DisplayModeId) EXCLUDES(mLock) REQUIRES(kMainThreadContext); + void setActiveMode(DisplayModeId, Fps renderFrameRate) EXCLUDES(mLock); - // See mActiveModeIt for thread safety. - DisplayModePtr getActiveModePtr() const EXCLUDES(mLock); - const DisplayMode& getActiveMode() const REQUIRES(kMainThreadContext); + // See mActiveModeOpt for thread safety. + FrameRateMode getActiveMode() const EXCLUDES(mLock); // Returns a known frame rate that is the closest to frameRate Fps findClosestKnownFrameRate(Fps frameRate) const; @@ -399,8 +398,8 @@ private: void constructAvailableRefreshRates() REQUIRES(mLock); - // See mActiveModeIt for thread safety. - DisplayModeIterator getActiveModeItLocked() const REQUIRES(mLock); + // See mActiveModeOpt for thread safety. + const FrameRateMode& getActiveModeLocked() const REQUIRES(mLock); RankedFrameRates getRankedFrameRatesLocked(const std::vector& layers, GlobalSignals signals) const REQUIRES(mLock); @@ -478,9 +477,7 @@ private: // when FrameRateOverride::AppOverrideNativeRefreshRates is in use. ftl::SmallMap mAppOverrideNativeRefreshRates; - // Written under mLock exclusively from kMainThreadContext, so reads from kMainThreadContext - // need not be under mLock. - DisplayModeIterator mActiveModeIt GUARDED_BY(mLock) GUARDED_BY(kMainThreadContext); + ftl::Optional mActiveModeOpt GUARDED_BY(mLock); DisplayModeIterator mMinRefreshRateModeIt GUARDED_BY(mLock); DisplayModeIterator mMaxRefreshRateModeIt GUARDED_BY(mLock); diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 0c541f9a5a..7f8f600d30 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -174,7 +174,7 @@ impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const { return [this](uid_t uid) { - const Fps refreshRate = leaderSelectorPtr()->getActiveModePtr()->getFps(); + const Fps refreshRate = leaderSelectorPtr()->getActiveMode().fps; const nsecs_t currentPeriod = mVsyncSchedule->period().ns() ?: refreshRate.getPeriodNsecs(); const auto frameRate = getFrameRateOverride(uid); @@ -282,7 +282,7 @@ void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDis thread->onFrameRateOverridesChanged(displayId, std::move(overrides)); } -void Scheduler::onPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) { +void Scheduler::onPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode& mode) { { std::lock_guard lock(mPolicyLock); // Cache the last reported modes for primary display. @@ -297,7 +297,7 @@ void Scheduler::onPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayMode void Scheduler::dispatchCachedReportedMode() { // Check optional fields first. - if (!mPolicy.mode) { + if (!mPolicy.modeOpt) { ALOGW("No mode ID found, not dispatching cached mode."); return; } @@ -309,28 +309,28 @@ void Scheduler::dispatchCachedReportedMode() { // If the mode is not the current mode, this means that a // mode change is in progress. In that case we shouldn't dispatch an event // as it will be dispatched when the current mode changes. - if (leaderSelectorPtr()->getActiveModePtr() != mPolicy.mode) { + if (leaderSelectorPtr()->getActiveMode() != mPolicy.modeOpt) { return; } // If there is no change from cached mode, there is no need to dispatch an event - if (mPolicy.mode == mPolicy.cachedModeChangedParams->mode) { + if (*mPolicy.modeOpt == mPolicy.cachedModeChangedParams->mode) { return; } - mPolicy.cachedModeChangedParams->mode = mPolicy.mode; + mPolicy.cachedModeChangedParams->mode = *mPolicy.modeOpt; onNonPrimaryDisplayModeChanged(mPolicy.cachedModeChangedParams->handle, mPolicy.cachedModeChangedParams->mode); } -void Scheduler::onNonPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) { +void Scheduler::onNonPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode& mode) { android::EventThread* thread; { std::lock_guard lock(mConnectionsLock); RETURN_IF_INVALID_HANDLE(handle); thread = mConnections[handle].thread.get(); } - thread->onModeChanged(mode); + thread->onModeChanged(mode.modePtr.get()); } size_t Scheduler::getEventThreadConnectionCount(ConnectionHandle handle) { @@ -395,6 +395,24 @@ void Scheduler::resyncToHardwareVsync(bool makeAvailable, Fps refreshRate) { setVsyncPeriod(refreshRate.getPeriodNsecs()); } +void Scheduler::setRenderRate(Fps renderFrameRate) { + const auto mode = leaderSelectorPtr()->getActiveMode(); + + using fps_approx_ops::operator!=; + LOG_ALWAYS_FATAL_IF(renderFrameRate != mode.fps, + "Mismatch in render frame rates. Selector: %s, Scheduler: %s", + to_string(mode.fps).c_str(), to_string(renderFrameRate).c_str()); + + ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(), + to_string(mode.modePtr->getFps()).c_str()); + + const auto divisor = RefreshRateSelector::getFrameRateDivisor(mode.modePtr->getFps(), mode.fps); + LOG_ALWAYS_FATAL_IF(divisor == 0, "%s <> %s -- not divisors", to_string(mode.fps).c_str(), + to_string(mode.fps).c_str()); + + mVsyncSchedule->getTracker().setDivisor(static_cast(divisor)); +} + void Scheduler::resync() { static constexpr nsecs_t kIgnoreDelay = ms2ns(750); @@ -402,7 +420,7 @@ void Scheduler::resync() { const nsecs_t last = mLastResyncTime.exchange(now); if (now - last > kIgnoreDelay) { - const auto refreshRate = leaderSelectorPtr()->getActiveModePtr()->getFps(); + const auto refreshRate = leaderSelectorPtr()->getActiveMode().modePtr->getFps(); resyncToHardwareVsync(false, refreshRate); } } @@ -517,7 +535,7 @@ void Scheduler::kernelIdleTimerCallback(TimerState state) { // TODO(145561154): cleanup the kernel idle timer implementation and the refresh rate // magic number - const Fps refreshRate = leaderSelectorPtr()->getActiveModePtr()->getFps(); + const Fps refreshRate = leaderSelectorPtr()->getActiveMode().modePtr->getFps(); constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER = 65_Hz; using namespace fps_approx_ops; @@ -657,7 +675,7 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals currentState = std::forward(newState); DisplayModeChoiceMap modeChoices; - DisplayModePtr modePtr; + ftl::Optional modeOpt; { std::scoped_lock lock(mDisplayLock); ftl::FakeGuard guard(kMainThreadContext); @@ -666,10 +684,10 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals // TODO(b/240743786): The leader display's mode must change for any DisplayModeRequest // to go through. Fix this by tracking per-display Scheduler::Policy and timers. - std::tie(modePtr, consideredSignals) = + std::tie(modeOpt, consideredSignals) = modeChoices.get(*mLeaderDisplayId) .transform([](const DisplayModeChoice& choice) { - return std::make_pair(choice.modePtr, choice.consideredSignals); + return std::make_pair(choice.mode, choice.consideredSignals); }) .value(); } @@ -677,15 +695,14 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals modeRequests.reserve(modeChoices.size()); for (auto& [id, choice] : modeChoices) { modeRequests.emplace_back( - display::DisplayModeRequest{.modePtr = - ftl::as_non_null(std::move(choice.modePtr)), + display::DisplayModeRequest{.mode = std::move(choice.mode), .emitEvent = !choice.consideredSignals.idle}); } - frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, modePtr->getFps()); + frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, modeOpt->fps); - if (mPolicy.mode != modePtr) { - mPolicy.mode = modePtr; + if (mPolicy.modeOpt != modeOpt) { + mPolicy.modeOpt = modeOpt; refreshRateChanged = true; } else { // We don't need to change the display mode, but we might need to send an event @@ -725,12 +742,11 @@ auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap { const auto globalSignals = makeGlobalSignals(); for (const auto& [id, selectorPtr] : mRefreshRateSelectors) { - auto rankedRefreshRates = + auto rankedFrameRates = selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, globalSignals); - for (const auto& [frameRateMode, score] : rankedRefreshRates.ranking) { - const auto& modePtr = frameRateMode.modePtr; - const auto [it, inserted] = refreshRateTallies.try_emplace(modePtr->getFps(), score); + for (const auto& [frameRateMode, score] : rankedFrameRates.ranking) { + const auto [it, inserted] = refreshRateTallies.try_emplace(frameRateMode.fps, score); if (!inserted) { auto& tally = it->second; @@ -739,7 +755,7 @@ auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap { } } - perDisplayRanking.push_back(std::move(rankedRefreshRates)); + perDisplayRanking.push_back(std::move(rankedFrameRates)); } auto maxScoreIt = refreshRateTallies.cbegin(); @@ -773,17 +789,15 @@ auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap { for (auto& [ranking, signals] : perDisplayRanking) { if (!chosenFps) { const auto& [frameRateMode, _] = ranking.front(); - const auto& modePtr = frameRateMode.modePtr; - modeChoices.try_emplace(modePtr->getPhysicalDisplayId(), - DisplayModeChoice{modePtr, signals}); + modeChoices.try_emplace(frameRateMode.modePtr->getPhysicalDisplayId(), + DisplayModeChoice{frameRateMode, signals}); continue; } for (auto& [frameRateMode, _] : ranking) { - const auto& modePtr = frameRateMode.modePtr; - if (modePtr->getFps() == *chosenFps) { - modeChoices.try_emplace(modePtr->getPhysicalDisplayId(), - DisplayModeChoice{modePtr, signals}); + if (frameRateMode.fps == *chosenFps) { + modeChoices.try_emplace(frameRateMode.modePtr->getPhysicalDisplayId(), + DisplayModeChoice{frameRateMode, signals}); break; } } @@ -801,18 +815,18 @@ GlobalSignals Scheduler::makeGlobalSignals() const { .powerOnImminent = powerOnImminent}; } -DisplayModePtr Scheduler::getPreferredDisplayMode() { +ftl::Optional Scheduler::getPreferredDisplayMode() { std::lock_guard lock(mPolicyLock); // Make sure the stored mode is up to date. - if (mPolicy.mode) { + if (mPolicy.modeOpt) { const auto ranking = leaderSelectorPtr() ->getRankedFrameRates(mPolicy.contentRequirements, makeGlobalSignals()) .ranking; - mPolicy.mode = ranking.front().frameRateMode.modePtr; + mPolicy.modeOpt = ranking.front().frameRateMode; } - return mPolicy.mode; + return mPolicy.modeOpt; } void Scheduler::onNewVsyncPeriodChangeTimeline(const hal::VsyncPeriodChangeTimeline& timeline) { diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index fb2307100f..cf2ffb8cdd 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -149,8 +149,8 @@ public: sp getEventConnection(ConnectionHandle); void onHotplugReceived(ConnectionHandle, PhysicalDisplayId, bool connected); - void onPrimaryDisplayModeChanged(ConnectionHandle, DisplayModePtr) EXCLUDES(mPolicyLock); - void onNonPrimaryDisplayModeChanged(ConnectionHandle, DisplayModePtr); + void onPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&) EXCLUDES(mPolicyLock); + void onNonPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&); void onScreenAcquired(ConnectionHandle); void onScreenReleased(ConnectionHandle); @@ -161,6 +161,9 @@ public: void setDuration(ConnectionHandle, std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration); + // Sets the render rate for the scheduler to run at. + void setRenderRate(Fps); + void enableHardwareVsync(); void disableHardwareVsync(bool makeUnavailable); @@ -207,7 +210,7 @@ public: void dumpVsync(std::string&) const; // Get the appropriate refresh for current conditions. - DisplayModePtr getPreferredDisplayMode(); + ftl::Optional getPreferredDisplayMode(); // Notifies the scheduler about a refresh rate timeline change. void onNewVsyncPeriodChangeTimeline(const hal::VsyncPeriodChangeTimeline& timeline); @@ -235,7 +238,7 @@ public: std::optional getFrameRateOverride(uid_t) const EXCLUDES(mDisplayLock); nsecs_t getLeaderVsyncPeriod() const EXCLUDES(mDisplayLock) { - return leaderSelectorPtr()->getActiveModePtr()->getFps().getPeriodNsecs(); + return leaderSelectorPtr()->getActiveMode().fps.getPeriodNsecs(); } // Returns the framerate of the layer with the given sequence ID @@ -283,19 +286,19 @@ private: GlobalSignals applyPolicy(S Policy::*, T&&) EXCLUDES(mPolicyLock); struct DisplayModeChoice { - DisplayModeChoice(DisplayModePtr modePtr, GlobalSignals consideredSignals) - : modePtr(std::move(modePtr)), consideredSignals(consideredSignals) {} + DisplayModeChoice(FrameRateMode mode, GlobalSignals consideredSignals) + : mode(std::move(mode)), consideredSignals(consideredSignals) {} - DisplayModePtr modePtr; + FrameRateMode mode; GlobalSignals consideredSignals; bool operator==(const DisplayModeChoice& other) const { - return modePtr == other.modePtr && consideredSignals == other.consideredSignals; + return mode == other.mode && consideredSignals == other.consideredSignals; } // For tests. friend std::ostream& operator<<(std::ostream& stream, const DisplayModeChoice& choice) { - return stream << '{' << to_string(*choice.modePtr) << " considering " + return stream << '{' << to_string(*choice.mode.modePtr) << " considering " << choice.consideredSignals.toString().c_str() << '}'; } }; @@ -382,11 +385,11 @@ private: hal::PowerMode displayPowerMode = hal::PowerMode::ON; // Chosen display mode. - DisplayModePtr mode; + ftl::Optional modeOpt; struct ModeChangedParams { ConnectionHandle handle; - DisplayModePtr mode; + FrameRateMode mode; }; // Parameters for latest dispatch of mode change event. diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 0ad42364a2..ed4d25e49b 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -253,7 +253,13 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) co nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const { std::lock_guard lock(mMutex); - return nextAnticipatedVSyncTimeFromLocked(timePoint); + + // TODO(b/246164114): This implementation is not efficient at all. Refactor. + nsecs_t nextVsync = nextAnticipatedVSyncTimeFromLocked(timePoint); + while (!isVSyncInPhaseLocked(nextVsync, mDivisor)) { + nextVsync = nextAnticipatedVSyncTimeFromLocked(nextVsync + 1); + } + return nextVsync; } /* @@ -265,6 +271,13 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const { * isVSyncInPhase(50.0, 30) = true */ bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const { + std::lock_guard lock(mMutex); + const auto divisor = + RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), frameRate); + return isVSyncInPhaseLocked(timePoint, static_cast(divisor)); +} + +bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const { struct VsyncError { nsecs_t vsyncTimestamp; float error; @@ -272,9 +285,6 @@ bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const { bool operator<(const VsyncError& other) const { return error < other.error; } }; - std::lock_guard lock(mMutex); - const auto divisor = - RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), frameRate); if (divisor <= 1 || timePoint == 0) { return true; } @@ -312,6 +322,12 @@ bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const { return std::abs(minVsyncError->vsyncTimestamp - timePoint) < period / 2; } +void VSyncPredictor::setDivisor(unsigned divisor) { + ALOGV("%s: %d", __func__, divisor); + std::lock_guard lock(mMutex); + mDivisor = divisor; +} + VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const { std::lock_guard lock(mMutex); const auto model = VSyncPredictor::getVSyncPredictionModelLocked(); diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index 3181102663..4a3ba67419 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -67,6 +67,8 @@ public: bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex); + void setDivisor(unsigned divisor) final EXCLUDES(mMutex); + void dump(std::string& result) const final EXCLUDES(mMutex); private: @@ -89,6 +91,8 @@ private: nsecs_t nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const REQUIRES(mMutex); + bool isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const REQUIRES(mMutex); + nsecs_t mIdealPeriod GUARDED_BY(mMutex); std::optional mKnownTimestamp GUARDED_BY(mMutex); @@ -100,6 +104,8 @@ private: size_t mLastTimestampIndex GUARDED_BY(mMutex) = 0; std::vector mTimestamps GUARDED_BY(mMutex); + + unsigned mDivisor GUARDED_BY(mMutex) = 1; }; } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h index 76315d2b5b..8d1629faae 100644 --- a/services/surfaceflinger/Scheduler/VSyncTracker.h +++ b/services/surfaceflinger/Scheduler/VSyncTracker.h @@ -79,6 +79,17 @@ public: */ virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0; + /* + * Sets a divisor on the rate (which is a multiplier of the period). + * The tracker will continue to track the vsync timeline and expect it + * to match the current period, however, nextAnticipatedVSyncTimeFrom will + * return vsyncs according to the divisor set. Setting a divisor is useful + * when a display is running at 120Hz but the render frame rate is 60Hz. + * + * \param [in] divisor The rate divisor the tracker should operate at. + */ + virtual void setDivisor(unsigned divisor) = 0; + virtual void dump(std::string& result) const = 0; protected: diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h index 670ab4583a..db38ebe658 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h @@ -16,6 +16,7 @@ #pragma once +#include #include // TODO(b/241285191): Pull this to @@ -25,7 +26,7 @@ namespace android::scheduler { struct FrameRateMode { Fps fps; // The render frame rate, which is a divisor of modePtr->getFps(). - DisplayModePtr modePtr; + ftl::NonNull modePtr; bool operator==(const FrameRateMode& other) const { return isApproxEqual(fps, other.fps) && modePtr == other.modePtr; @@ -35,10 +36,7 @@ struct FrameRateMode { }; inline std::string to_string(const FrameRateMode& mode) { - if (mode.modePtr) { - return to_string(mode.fps) + " (" + to_string(mode.modePtr->getFps()) + ")"; - } - return "{invalid}"; + return to_string(mode.fps) + " (" + to_string(mode.modePtr->getFps()) + ")"; } -} // namespace android::scheduler \ No newline at end of file +} // namespace android::scheduler diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 6961f7cecf..111b927507 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1039,7 +1039,8 @@ status_t SurfaceFlinger::getDynamicDisplayInfo(const sp& displayToken, const PhysicalDisplayId displayId = snapshot.displayId(); - info->activeDisplayModeId = display->refreshRateSelector().getActiveModePtr()->getId().value(); + info->activeDisplayModeId = + display->refreshRateSelector().getActiveMode().modePtr->getId().value(); info->activeColorMode = display->getCompositionDisplay()->getState().colorMode; info->hdrCapabilities = display->getHdrCapabilities(); @@ -1076,26 +1077,39 @@ status_t SurfaceFlinger::getDisplayStats(const sp&, DisplayStatInfo* ou void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request) { ATRACE_CALL(); - auto display = getDisplayDeviceLocked(request.modePtr->getPhysicalDisplayId()); + auto display = getDisplayDeviceLocked(request.mode.modePtr->getPhysicalDisplayId()); if (!display) { ALOGW("%s: display is no longer valid", __func__); return; } - const Fps refreshRate = request.modePtr->getFps(); + const Fps renderFps = request.mode.fps; + const Fps displayFps = request.mode.modePtr->getFps(); - if (display->setDesiredActiveMode(DisplayDevice::ActiveModeInfo(std::move(request)))) { - scheduleComposite(FrameHint::kNone); + switch (display->setDesiredActiveMode(DisplayDevice::ActiveModeInfo(std::move(request)))) { + case DisplayDevice::DesiredActiveModeAction::InitiateDisplayModeSwitch: + scheduleComposite(FrameHint::kNone); - // Start receiving vsync samples now, so that we can detect a period - // switch. - mScheduler->resyncToHardwareVsync(true, refreshRate); - // As we called to set period, we will call to onRefreshRateChangeCompleted once - // VsyncController model is locked. - modulateVsync(&VsyncModulator::onRefreshRateChangeInitiated); + // Start receiving vsync samples now, so that we can detect a period + // switch. + mScheduler->resyncToHardwareVsync(true, displayFps); + // As we called to set period, we will call to onRefreshRateChangeCompleted once + // VsyncController model is locked. + modulateVsync(&VsyncModulator::onRefreshRateChangeInitiated); - updatePhaseConfiguration(refreshRate); - mScheduler->setModeChangePending(true); + updatePhaseConfiguration(renderFps); + mScheduler->setModeChangePending(true); + break; + case DisplayDevice::DesiredActiveModeAction::InitiateRenderRateSwitch: + mScheduler->setRenderRate(renderFps); + updatePhaseConfiguration(renderFps); + mRefreshRateStats->setRefreshRate(renderFps); + + // TODO(b/259740021): send event to display manager about + // the render rate change + break; + case DisplayDevice::DesiredActiveModeAction::None: + break; } } @@ -1157,18 +1171,19 @@ void SurfaceFlinger::updateInternalStateWithChangedMode() { } const auto upcomingModeInfo = display->getUpcomingActiveMode(); - if (!upcomingModeInfo.mode) { + if (!upcomingModeInfo.modeOpt) { // There is no pending mode change. This can happen if the active // display changed and the mode change happened on a different display. return; } - if (display->getActiveMode().getResolution() != upcomingModeInfo.mode->getResolution()) { + if (display->getActiveMode().modePtr->getResolution() != + upcomingModeInfo.modeOpt->modePtr->getResolution()) { auto& state = mCurrentState.displays.editValueFor(display->getDisplayToken()); // We need to generate new sequenceId in order to recreate the display (and this // way the framebuffer). state.sequenceId = DisplayDeviceState{}.sequenceId; - state.physical->activeMode = upcomingModeInfo.mode; + state.physical->activeMode = upcomingModeInfo.modeOpt->modePtr.get(); processDisplayChangesLocked(); // processDisplayChangesLocked will update all necessary components so we're done here. @@ -1179,15 +1194,17 @@ void SurfaceFlinger::updateInternalStateWithChangedMode() { .transform(&PhysicalDisplay::snapshotRef) .transform(ftl::unit_fn([&](const display::DisplaySnapshot& snapshot) { FTL_FAKE_GUARD(kMainThreadContext, - display->setActiveMode(upcomingModeInfo.mode->getId(), snapshot)); + display->setActiveMode(upcomingModeInfo.modeOpt->modePtr->getId(), + upcomingModeInfo.modeOpt->modePtr->getFps(), + upcomingModeInfo.modeOpt->fps)); })); - const Fps refreshRate = upcomingModeInfo.mode->getFps(); + const Fps refreshRate = upcomingModeInfo.modeOpt->fps; mRefreshRateStats->setRefreshRate(refreshRate); updatePhaseConfiguration(refreshRate); if (upcomingModeInfo.event != scheduler::DisplayModeEvent::None) { - mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, upcomingModeInfo.mode); + mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, *upcomingModeInfo.modeOpt); } } @@ -1199,10 +1216,12 @@ void SurfaceFlinger::clearDesiredActiveModeState(const sp& displa } void SurfaceFlinger::desiredActiveModeChangeDone(const sp& display) { - const auto refreshRate = display->getDesiredActiveMode()->mode->getFps(); + const auto displayFps = display->getDesiredActiveMode()->modeOpt->modePtr->getFps(); + const auto renderFps = display->getDesiredActiveMode()->modeOpt->fps; clearDesiredActiveModeState(display); - mScheduler->resyncToHardwareVsync(true, refreshRate); - updatePhaseConfiguration(refreshRate); + mScheduler->resyncToHardwareVsync(true, displayFps); + mScheduler->setRenderRate(renderFps); + updatePhaseConfiguration(renderFps); } void SurfaceFlinger::setActiveModeInHwcIfNeeded() { @@ -1233,13 +1252,10 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { continue; } - const auto desiredModeId = desiredActiveMode->mode->getId(); - const auto refreshRateOpt = - snapshot.displayModes() - .get(desiredModeId) - .transform([](const DisplayModePtr& mode) { return mode->getFps(); }); + const auto desiredModeId = desiredActiveMode->modeOpt->modePtr->getId(); + const auto displayModePtrOpt = snapshot.displayModes().get(desiredModeId); - if (!refreshRateOpt) { + if (!displayModePtrOpt) { ALOGW("Desired display mode is no longer supported. Mode ID = %d", desiredModeId.value()); clearDesiredActiveModeState(display); @@ -1247,9 +1263,10 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { } ALOGV("%s changing active mode to %d(%s) for display %s", __func__, desiredModeId.value(), - to_string(*refreshRateOpt).c_str(), to_string(display->getId()).c_str()); + to_string(displayModePtrOpt->get()->getFps()).c_str(), + to_string(display->getId()).c_str()); - if (display->getActiveMode().getId() == desiredModeId) { + if (display->getActiveMode() == desiredActiveMode->modeOpt) { // we are already in the requested mode, there is nothing left to do desiredActiveModeChangeDone(display); continue; @@ -1258,7 +1275,8 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { // Desired active mode was set, it is different than the mode currently in use, however // allowed modes might have changed by the time we process the refresh. // Make sure the desired mode is still allowed - const auto displayModeAllowed = display->refreshRateSelector().isModeAllowed(desiredModeId); + const auto displayModeAllowed = + display->refreshRateSelector().isModeAllowed(*desiredActiveMode->modeOpt); if (!displayModeAllowed) { clearDesiredActiveModeState(display); continue; @@ -1299,8 +1317,7 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() { const auto display = getDisplayDeviceLocked(*displayToUpdateImmediately); const auto desiredActiveMode = display->getDesiredActiveMode(); - if (desiredActiveMode && - display->getActiveMode().getId() == desiredActiveMode->mode->getId()) { + if (desiredActiveMode && display->getActiveMode() == desiredActiveMode->modeOpt) { desiredActiveModeChangeDone(display); } } @@ -2069,7 +2086,7 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe activeDisplay->getPowerMode() == hal::PowerMode::ON; if (mPowerHintSessionEnabled) { const auto& display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get(); - const Period vsyncPeriod = Period::fromNs(display->getActiveMode().getVsyncPeriod()); + const Period vsyncPeriod = Period::fromNs(display->getActiveMode().fps.getPeriodNsecs()); mPowerAdvisor->setCommitStart(frameTime); mPowerAdvisor->setExpectedPresentTime(mExpectedPresentTime); @@ -2898,7 +2915,9 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( .transform(&PhysicalDisplay::snapshotRef) .transform(ftl::unit_fn([&](const display::DisplaySnapshot& snapshot) { FTL_FAKE_GUARD(kMainThreadContext, - display->setActiveMode(physical->activeMode->getId(), snapshot)); + display->setActiveMode(physical->activeMode->getId(), + physical->activeMode->getFps(), + physical->activeMode->getFps())); })); } @@ -3094,7 +3113,7 @@ void SurfaceFlinger::processDisplayChanged(const wp& displayToken, void SurfaceFlinger::updateInternalDisplayVsyncLocked(const sp& activeDisplay) { mVsyncConfiguration->reset(); - const Fps refreshRate = activeDisplay->getActiveMode().getFps(); + const Fps refreshRate = activeDisplay->getActiveMode().fps; updatePhaseConfiguration(refreshRate); mRefreshRateStats->setRefreshRate(refreshRate); } @@ -3376,12 +3395,12 @@ void SurfaceFlinger::requestDisplayModes(std::vectorgetPhysicalDisplayId()); if (!display) continue; - if (display->refreshRateSelector().isModeAllowed(modePtr->getId())) { + if (display->refreshRateSelector().isModeAllowed(request.mode)) { setDesiredActiveMode(std::move(request)); } else { ALOGV("%s: Mode %d is disallowed for display %s", __func__, modePtr->getId().value(), @@ -3402,8 +3421,8 @@ void SurfaceFlinger::triggerOnFrameRateOverridesChanged() { void SurfaceFlinger::initScheduler(const sp& display) { LOG_ALWAYS_FATAL_IF(mScheduler); - const auto activeModePtr = display->refreshRateSelector().getActiveModePtr(); - const Fps activeRefreshRate = activeModePtr->getFps(); + const auto activeMode = display->refreshRateSelector().getActiveMode(); + const Fps activeRefreshRate = activeMode.fps; mRefreshRateStats = std::make_unique(*mTimeStats, activeRefreshRate, hal::PowerMode::OFF); @@ -4673,7 +4692,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: display->setPowerMode(mode); - const auto refreshRate = display->refreshRateSelector().getActiveMode().getFps(); + const auto refreshRate = display->refreshRateSelector().getActiveMode().modePtr->getFps(); if (!currentModeOpt || *currentModeOpt == hal::PowerMode::OFF) { // Turn on the display if (isInternalDisplay && (!activeDisplay || !activeDisplay->isPoweredOn())) { @@ -5243,7 +5262,8 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& comp if (const auto display = getDefaultDisplayDeviceLocked()) { std::string fps, xDpi, yDpi; - if (const auto activeModePtr = display->refreshRateSelector().getActiveModePtr()) { + if (const auto activeModePtr = + display->refreshRateSelector().getActiveMode().modePtr.get()) { fps = to_string(activeModePtr->getFps()); const auto dpi = activeModePtr->getDpi(); @@ -5915,7 +5935,7 @@ void SurfaceFlinger::kernelTimerChanged(bool expired) { if (!updateOverlay) return; // Update the overlay on the main thread to avoid race conditions with - // RefreshRateSelector::getActiveMode. + // RefreshRateSelector::getActiveMode static_cast(mScheduler->schedule([=] { const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()); if (!display) { @@ -5926,7 +5946,8 @@ void SurfaceFlinger::kernelTimerChanged(bool expired) { const auto desiredActiveMode = display->getDesiredActiveMode(); const std::optional desiredModeId = desiredActiveMode - ? std::make_optional(desiredActiveMode->mode->getId()) + ? std::make_optional(desiredActiveMode->modeOpt->modePtr->getId()) + : std::nullopt; const bool timerExpired = mKernelIdleTimerEnabled && expired; @@ -6620,11 +6641,11 @@ void SurfaceFlinger::traverseLayersInLayerStack(ui::LayerStack layerStack, const } } -std::optional> SurfaceFlinger::getPreferredDisplayMode( +ftl::Optional SurfaceFlinger::getPreferredDisplayMode( PhysicalDisplayId displayId, DisplayModeId defaultModeId) const { if (const auto schedulerMode = mScheduler->getPreferredDisplayMode(); - schedulerMode && schedulerMode->getPhysicalDisplayId() == displayId) { - return ftl::as_non_null(schedulerMode); + schedulerMode && schedulerMode->modePtr->getPhysicalDisplayId() == displayId) { + return schedulerMode; } return mPhysicalDisplays.get(displayId) @@ -6632,7 +6653,9 @@ std::optional> SurfaceFlinger::getPreferredDisplayM .and_then([&](const display::DisplaySnapshot& snapshot) { return snapshot.displayModes().get(defaultModeId); }) - .transform(&ftl::as_non_null); + .transform([](const DisplayModePtr& modePtr) { + return scheduler::FrameRateMode{modePtr->getFps(), ftl::as_non_null(modePtr)}; + }); } status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( @@ -6667,11 +6690,11 @@ status_t SurfaceFlinger::applyRefreshRateSelectorPolicy( // TODO(b/140204874): Leave the event in until we do proper testing with all apps that might // be depending in this callback. - if (const auto activeModePtr = selector.getActiveModePtr(); displayId == mActiveDisplayId) { - mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, activeModePtr); + if (const auto activeMode = selector.getActiveMode(); displayId == mActiveDisplayId) { + mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, activeMode); toggleKernelIdleTimer(); } else { - mScheduler->onNonPrimaryDisplayModeChanged(mAppConnectionHandle, activeModePtr); + mScheduler->onNonPrimaryDisplayModeChanged(mAppConnectionHandle, activeMode); } auto preferredModeOpt = getPreferredDisplayMode(displayId, currentPolicy.defaultMode); @@ -6681,12 +6704,12 @@ status_t SurfaceFlinger::applyRefreshRateSelectorPolicy( } auto preferredMode = std::move(*preferredModeOpt); - const auto preferredModeId = preferredMode->getId(); + const auto preferredModeId = preferredMode.modePtr->getId(); ALOGV("Switching to Scheduler preferred mode %d (%s)", preferredModeId.value(), - to_string(preferredMode->getFps()).c_str()); + to_string(preferredMode.fps).c_str()); - if (!selector.isModeAllowed(preferredModeId)) { + if (!selector.isModeAllowed(preferredMode)) { ALOGE("%s: Preferred mode %d is disallowed", __func__, preferredModeId.value()); return INVALID_OPERATION; } @@ -6898,7 +6921,7 @@ uint32_t SurfaceFlinger::getMaxAcquiredBufferCountForCurrentRefreshRate(uid_t ui refreshRate = *frameRateOverride; } else if (!getHwComposer().isHeadless()) { if (const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked())) { - refreshRate = display->refreshRateSelector().getActiveModePtr()->getFps(); + refreshRate = display->refreshRateSelector().getActiveMode().fps; } } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 751d1e5563..e3217a3228 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -660,7 +660,7 @@ private: // Returns the preferred mode for PhysicalDisplayId if the Scheduler has selected one for that // display. Falls back to the display's defaultModeId otherwise. - std::optional> getPreferredDisplayMode( + ftl::Optional getPreferredDisplayMode( PhysicalDisplayId, DisplayModeId defaultModeId) const REQUIRES(mStateLock); status_t setDesiredDisplayModeSpecsInternal( diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 577f84eb10..dbfce78283 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -231,8 +231,7 @@ public: : Scheduler(*this, callback, Feature::kContentDetection) { mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller))); - const auto displayId = FTL_FAKE_GUARD(kMainThreadContext, - selectorPtr->getActiveMode().getPhysicalDisplayId()); + const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId(); registerDisplay(displayId, std::move(selectorPtr)); } @@ -273,7 +272,7 @@ public: mPolicy.cachedModeChangedParams.reset(); } - void onNonPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) { + void onNonPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode &mode) { return Scheduler::onNonPrimaryDisplayModeChanged(handle, mode); } @@ -658,8 +657,7 @@ public: } mRefreshRateSelector = std::make_shared(modes, kModeId60); - const auto fps = - FTL_FAKE_GUARD(kMainThreadContext, mRefreshRateSelector->getActiveMode().getFps()); + const auto fps = mRefreshRateSelector->getActiveMode().modePtr->getFps(); mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps); mFlinger->mVsyncModulator = sp::make( mFlinger->mVsyncConfiguration->getCurrentConfigs()); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index 950e6d36bf..7959e52fba 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -381,7 +381,8 @@ void SchedulerFuzzer::fuzzRefreshRateSelector() { mFdp.ConsumeFloatingPoint())}}); refreshRateSelector.setPolicy(RefreshRateSelector::NoOverridePolicy{}); - refreshRateSelector.setActiveModeId(modeId); + refreshRateSelector.setActiveMode(modeId, + Fps::fromValue(mFdp.ConsumeFloatingPoint())); } RefreshRateSelector::isFractionalPairOrMultiple(Fps::fromValue( diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h index 88e32e1bd0..2bc5b4676f 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h @@ -116,6 +116,8 @@ public: return true; } + void setDivisor(unsigned) override {} + nsecs_t nextVSyncTime(nsecs_t timePoint) const { if (timePoint % mPeriod == 0) { return timePoint; diff --git a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp b/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp index 982b9ff9ea..60ad7a3a03 100644 --- a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp +++ b/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp @@ -18,6 +18,7 @@ #define LOG_TAG "LibSurfaceFlingerUnittests" #include "DisplayTransactionTestHelpers.h" +#include "mock/MockFrameRateMode.h" #include #include @@ -29,6 +30,7 @@ using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjec class InitiateModeChangeTest : public DisplayTransactionTest { public: + using Action = DisplayDevice::DesiredActiveModeAction; using Event = scheduler::DisplayModeEvent; void SetUp() override { @@ -56,31 +58,42 @@ protected: static constexpr DisplayModeId kModeId90{1}; static constexpr DisplayModeId kModeId120{2}; - static inline const DisplayModePtr kMode60 = createDisplayMode(kModeId60, 60_Hz); - static inline const DisplayModePtr kMode90 = createDisplayMode(kModeId90, 90_Hz); - static inline const DisplayModePtr kMode120 = createDisplayMode(kModeId120, 120_Hz); + static inline const ftl::NonNull kMode60 = + ftl::as_non_null(createDisplayMode(kModeId60, 60_Hz)); + static inline const ftl::NonNull kMode90 = + ftl::as_non_null(createDisplayMode(kModeId90, 90_Hz)); + static inline const ftl::NonNull kMode120 = + ftl::as_non_null(createDisplayMode(kModeId120, 120_Hz)); }; TEST_F(InitiateModeChangeTest, setDesiredActiveMode_setCurrentMode) { - EXPECT_FALSE(mDisplay->setDesiredActiveMode({kMode60, Event::None})); + EXPECT_EQ(Action::None, + mDisplay->setDesiredActiveMode( + {scheduler::FrameRateMode{60_Hz, kMode60}, Event::None})); EXPECT_EQ(std::nullopt, mDisplay->getDesiredActiveMode()); } TEST_F(InitiateModeChangeTest, setDesiredActiveMode_setNewMode) { - EXPECT_TRUE(mDisplay->setDesiredActiveMode({kMode90, Event::None})); + EXPECT_EQ(Action::InitiateDisplayModeSwitch, + mDisplay->setDesiredActiveMode( + {scheduler::FrameRateMode{90_Hz, kMode90}, Event::None})); ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode()); - EXPECT_EQ(kMode90, mDisplay->getDesiredActiveMode()->mode); + EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, mDisplay->getDesiredActiveMode()->modeOpt); EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event); - // Setting another mode should be cached but return false - EXPECT_FALSE(mDisplay->setDesiredActiveMode({kMode120, Event::None})); + // Setting another mode should be cached but return None + EXPECT_EQ(Action::None, + mDisplay->setDesiredActiveMode( + {scheduler::FrameRateMode{120_Hz, kMode120}, Event::None})); ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode()); - EXPECT_EQ(kMode120, mDisplay->getDesiredActiveMode()->mode); + EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, mDisplay->getDesiredActiveMode()->modeOpt); EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event); } TEST_F(InitiateModeChangeTest, clearDesiredActiveModeState) { - EXPECT_TRUE(mDisplay->setDesiredActiveMode({kMode90, Event::None})); + EXPECT_EQ(Action::InitiateDisplayModeSwitch, + mDisplay->setDesiredActiveMode( + {scheduler::FrameRateMode{90_Hz, kMode90}, Event::None})); ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode()); mDisplay->clearDesiredActiveModeState(); @@ -88,9 +101,11 @@ TEST_F(InitiateModeChangeTest, clearDesiredActiveModeState) { } TEST_F(InitiateModeChangeTest, initiateModeChange) NO_THREAD_SAFETY_ANALYSIS { - EXPECT_TRUE(mDisplay->setDesiredActiveMode({kMode90, Event::None})); + EXPECT_EQ(Action::InitiateDisplayModeSwitch, + mDisplay->setDesiredActiveMode( + {scheduler::FrameRateMode{90_Hz, kMode90}, Event::None})); ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode()); - EXPECT_EQ(kMode90, mDisplay->getDesiredActiveMode()->mode); + EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, mDisplay->getDesiredActiveMode()->modeOpt); EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event); hal::VsyncPeriodChangeConstraints constraints{ @@ -101,18 +116,27 @@ TEST_F(InitiateModeChangeTest, initiateModeChange) NO_THREAD_SAFETY_ANALYSIS { EXPECT_EQ(OK, mDisplay->initiateModeChange(*mDisplay->getDesiredActiveMode(), constraints, &timeline)); - EXPECT_EQ(kMode90, mDisplay->getUpcomingActiveMode().mode); + EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getUpcomingActiveMode().modeOpt); EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event); mDisplay->clearDesiredActiveModeState(); ASSERT_EQ(std::nullopt, mDisplay->getDesiredActiveMode()); } +TEST_F(InitiateModeChangeTest, initiateRenderRateChange) { + EXPECT_EQ(Action::InitiateRenderRateSwitch, + mDisplay->setDesiredActiveMode( + {scheduler::FrameRateMode{30_Hz, kMode60}, Event::None})); + EXPECT_EQ(std::nullopt, mDisplay->getDesiredActiveMode()); +} + TEST_F(InitiateModeChangeTest, getUpcomingActiveMode_desiredActiveModeChanged) NO_THREAD_SAFETY_ANALYSIS { - EXPECT_TRUE(mDisplay->setDesiredActiveMode({kMode90, Event::None})); + EXPECT_EQ(Action::InitiateDisplayModeSwitch, + mDisplay->setDesiredActiveMode( + {scheduler::FrameRateMode{90_Hz, kMode90}, Event::None})); ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode()); - EXPECT_EQ(kMode90, mDisplay->getDesiredActiveMode()->mode); + EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, mDisplay->getDesiredActiveMode()->modeOpt); EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event); hal::VsyncPeriodChangeConstraints constraints{ @@ -123,21 +147,23 @@ NO_THREAD_SAFETY_ANALYSIS { EXPECT_EQ(OK, mDisplay->initiateModeChange(*mDisplay->getDesiredActiveMode(), constraints, &timeline)); - EXPECT_EQ(kMode90, mDisplay->getUpcomingActiveMode().mode); + EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getUpcomingActiveMode().modeOpt); EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event); - EXPECT_FALSE(mDisplay->setDesiredActiveMode({kMode120, Event::None})); + EXPECT_EQ(Action::None, + mDisplay->setDesiredActiveMode( + {scheduler::FrameRateMode{120_Hz, kMode120}, Event::None})); ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode()); - EXPECT_EQ(kMode120, mDisplay->getDesiredActiveMode()->mode); + EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, mDisplay->getDesiredActiveMode()->modeOpt); EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event); - EXPECT_EQ(kMode90, mDisplay->getUpcomingActiveMode().mode); + EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getUpcomingActiveMode().modeOpt); EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event); EXPECT_EQ(OK, mDisplay->initiateModeChange(*mDisplay->getDesiredActiveMode(), constraints, &timeline)); - EXPECT_EQ(kMode120, mDisplay->getUpcomingActiveMode().mode); + EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, *mDisplay->getUpcomingActiveMode().modeOpt); EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event); mDisplay->clearDesiredActiveModeState(); diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp index 8757e63c91..fdf2ffe3ce 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp @@ -31,6 +31,9 @@ #include "FpsOps.h" #include "Scheduler/RefreshRateSelector.h" #include "mock/DisplayHardware/MockDisplayMode.h" +#include "mock/MockFrameRateMode.h" + +#include "libsurfaceflinger_unittest_main.h" using namespace std::chrono_literals; @@ -45,44 +48,41 @@ using SetPolicyResult = RefreshRateSelector::SetPolicyResult; using mock::createDisplayMode; -// Use a C style macro to keep the line numbers printed in gtest -#define EXPECT_SCORED_FRAME_RATE(modePtr, fps, scored) \ - EXPECT_EQ((FrameRateMode{(fps), (modePtr)}), (scored).frameRateMode) - struct TestableRefreshRateSelector : RefreshRateSelector { using RefreshRateSelector::FrameRateRanking; using RefreshRateSelector::RefreshRateOrder; using RefreshRateSelector::RefreshRateSelector; - void setActiveModeId(DisplayModeId modeId) { + void setActiveMode(DisplayModeId modeId, Fps renderFrameRate) { ftl::FakeGuard guard(kMainThreadContext); - return RefreshRateSelector::setActiveModeId(modeId); + return RefreshRateSelector::setActiveMode(modeId, renderFrameRate); } const DisplayMode& getActiveMode() const { - ftl::FakeGuard guard(kMainThreadContext); - return RefreshRateSelector::getActiveMode(); + std::lock_guard lock(mLock); + return *RefreshRateSelector::getActiveModeLocked().modePtr; } - DisplayModePtr getMinSupportedRefreshRate() const { + ftl::NonNull getMinSupportedRefreshRate() const { std::lock_guard lock(mLock); - return mMinRefreshRateModeIt->second; + return ftl::as_non_null(mMinRefreshRateModeIt->second); } - DisplayModePtr getMaxSupportedRefreshRate() const { + ftl::NonNull getMaxSupportedRefreshRate() const { std::lock_guard lock(mLock); - return mMaxRefreshRateModeIt->second; + return ftl::as_non_null(mMaxRefreshRateModeIt->second); } - DisplayModePtr getMinRefreshRateByPolicy() const { + ftl::NonNull getMinRefreshRateByPolicy() const { std::lock_guard lock(mLock); - return getMinRefreshRateByPolicyLocked(); + return ftl::as_non_null(getMinRefreshRateByPolicyLocked()); } - DisplayModePtr getMaxRefreshRateByPolicy() const { + ftl::NonNull getMaxRefreshRateByPolicy() const { std::lock_guard lock(mLock); - return getMaxRefreshRateByPolicyLocked(getActiveModeItLocked()->second->getGroup()); + return ftl::as_non_null( + getMaxRefreshRateByPolicyLocked(getActiveModeLocked().modePtr->getGroup())); } FrameRateRanking rankRefreshRates(std::optional anchorGroupOpt, @@ -112,8 +112,8 @@ struct TestableRefreshRateSelector : RefreshRateSelector { return std::make_pair(ranking, consideredSignals); } - DisplayModePtr getBestFrameRateMode(const std::vector& layers = {}, - GlobalSignals signals = {}) const { + ftl::NonNull getBestFrameRateMode( + const std::vector& layers = {}, GlobalSignals signals = {}) const { return getRankedFrameRates(layers, signals).ranking.front().frameRateMode.modePtr; } @@ -154,25 +154,42 @@ protected: static constexpr DisplayModeId kModeId60Frac{10}; static constexpr DisplayModeId kModeId35{11}; - static inline const DisplayModePtr kMode60 = createDisplayMode(kModeId60, 60_Hz); - static inline const DisplayModePtr kMode60Frac = createDisplayMode(kModeId60Frac, 59.94_Hz); - static inline const DisplayModePtr kMode90 = createDisplayMode(kModeId90, 90_Hz); - static inline const DisplayModePtr kMode90_G1 = createDisplayMode(kModeId90, 90_Hz, 1); - static inline const DisplayModePtr kMode90_4K = - createDisplayMode(kModeId90, 90_Hz, 0, {3840, 2160}); - static inline const DisplayModePtr kMode72 = createDisplayMode(kModeId72, 72_Hz); - static inline const DisplayModePtr kMode72_G1 = createDisplayMode(kModeId72, 72_Hz, 1); - static inline const DisplayModePtr kMode120 = createDisplayMode(kModeId120, 120_Hz); - static inline const DisplayModePtr kMode120_G1 = createDisplayMode(kModeId120, 120_Hz, 1); - static inline const DisplayModePtr kMode30 = createDisplayMode(kModeId30, 30_Hz); - static inline const DisplayModePtr kMode30_G1 = createDisplayMode(kModeId30, 30_Hz, 1); - static inline const DisplayModePtr kMode30Frac = createDisplayMode(kModeId30Frac, 29.97_Hz); - static inline const DisplayModePtr kMode25 = createDisplayMode(kModeId25, 25_Hz); - static inline const DisplayModePtr kMode25_G1 = createDisplayMode(kModeId25, 25_Hz, 1); - static inline const DisplayModePtr kMode35 = createDisplayMode(kModeId35, 35_Hz); - static inline const DisplayModePtr kMode50 = createDisplayMode(kModeId50, 50_Hz); - static inline const DisplayModePtr kMode24 = createDisplayMode(kModeId24, 24_Hz); - static inline const DisplayModePtr kMode24Frac = createDisplayMode(kModeId24Frac, 23.976_Hz); + static inline const ftl::NonNull kMode60 = + ftl::as_non_null(createDisplayMode(kModeId60, 60_Hz)); + static inline const ftl::NonNull kMode60Frac = + ftl::as_non_null(createDisplayMode(kModeId60Frac, 59.94_Hz)); + static inline const ftl::NonNull kMode90 = + ftl::as_non_null(createDisplayMode(kModeId90, 90_Hz)); + static inline const ftl::NonNull kMode90_G1 = + ftl::as_non_null(createDisplayMode(kModeId90, 90_Hz, 1)); + static inline const ftl::NonNull kMode90_4K = + ftl::as_non_null(createDisplayMode(kModeId90, 90_Hz, 0, {3840, 2160})); + static inline const ftl::NonNull kMode72 = + ftl::as_non_null(createDisplayMode(kModeId72, 72_Hz)); + static inline const ftl::NonNull kMode72_G1 = + ftl::as_non_null(createDisplayMode(kModeId72, 72_Hz, 1)); + static inline const ftl::NonNull kMode120 = + ftl::as_non_null(createDisplayMode(kModeId120, 120_Hz)); + static inline const ftl::NonNull kMode120_G1 = + ftl::as_non_null(createDisplayMode(kModeId120, 120_Hz, 1)); + static inline const ftl::NonNull kMode30 = + ftl::as_non_null(createDisplayMode(kModeId30, 30_Hz)); + static inline const ftl::NonNull kMode30_G1 = + ftl::as_non_null(createDisplayMode(kModeId30, 30_Hz, 1)); + static inline const ftl::NonNull kMode30Frac = + ftl::as_non_null(createDisplayMode(kModeId30Frac, 29.97_Hz)); + static inline const ftl::NonNull kMode25 = + ftl::as_non_null(createDisplayMode(kModeId25, 25_Hz)); + static inline const ftl::NonNull kMode25_G1 = + ftl::as_non_null(createDisplayMode(kModeId25, 25_Hz, 1)); + static inline const ftl::NonNull kMode35 = + ftl::as_non_null(createDisplayMode(kModeId35, 35_Hz)); + static inline const ftl::NonNull kMode50 = + ftl::as_non_null(createDisplayMode(kModeId50, 50_Hz)); + static inline const ftl::NonNull kMode24 = + ftl::as_non_null(createDisplayMode(kModeId24, 24_Hz)); + static inline const ftl::NonNull kMode24Frac = + ftl::as_non_null(createDisplayMode(kModeId24Frac, 23.976_Hz)); // Test configurations. static inline const DisplayModes kModes_60 = makeModes(kMode60); @@ -298,7 +315,7 @@ TEST_P(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap_differentGroup EXPECT_EQ(SetPolicyResult::Changed, selector.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}})); - selector.setActiveModeId(kModeId90); + selector.setActiveMode(kModeId90, 90_Hz); const auto minRate90 = selector.getMinRefreshRateByPolicy(); const auto performanceRate90 = selector.getMaxRefreshRateByPolicy(); @@ -322,7 +339,7 @@ TEST_P(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap_differentResol EXPECT_EQ(SetPolicyResult::Changed, selector.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}})); - selector.setActiveModeId(kModeId90); + selector.setActiveMode(kModeId90, 90_Hz); const auto minRate90 = selector.getMinRefreshRateByPolicy(); const auto performanceRate90 = selector.getMaxRefreshRateByPolicy(); @@ -358,7 +375,7 @@ TEST_P(RefreshRateSelectorTest, twoModes_getActiveMode) { EXPECT_EQ(mode.getId(), kModeId60); } - selector.setActiveModeId(kModeId90); + selector.setActiveMode(kModeId90, 90_Hz); { const auto& mode = selector.getActiveMode(); EXPECT_EQ(mode.getId(), kModeId90); @@ -1805,7 +1822,7 @@ TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps policy.allowGroupSwitching = true; EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy)); - selector.setActiveModeId(kModeId90); + selector.setActiveMode(kModeId90, 90_Hz); // Verify that we won't do a seamless switch if we request the same mode as the default std::vector layers = {{.weight = 1.f}}; @@ -1826,7 +1843,7 @@ TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerDefaultSeamlessness) { policy.allowGroupSwitching = true; EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy)); - selector.setActiveModeId(kModeId90); + selector.setActiveMode(kModeId90, 90_Hz); // Verify that if the active mode is in another group and there are no layers with // Seamlessness::SeamedAndSeamless, we should switch back to the default group. @@ -1850,7 +1867,7 @@ TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed policy.allowGroupSwitching = true; EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy)); - selector.setActiveModeId(kModeId90); + selector.setActiveMode(kModeId90, 90_Hz); // If there's a layer with Seamlessness::SeamedAndSeamless, another layer with // Seamlessness::OnlySeamless can't change the mode group. @@ -1879,7 +1896,7 @@ TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeam policy.allowGroupSwitching = true; EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy)); - selector.setActiveModeId(kModeId90); + selector.setActiveMode(kModeId90, 90_Hz); // If there's a focused layer with Seamlessness::SeamedAndSeamless, another layer with // Seamlessness::Default can't change the mode group back to the group of the default @@ -1912,7 +1929,7 @@ TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultNotFocusedAndS policy.allowGroupSwitching = true; EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy)); - selector.setActiveModeId(kModeId90); + selector.setActiveMode(kModeId90, 90_Hz); // Layer with Seamlessness::Default can change the mode group if there's an // unfocused layer with Seamlessness::SeamedAndSeamless. For example, this happens @@ -1953,7 +1970,7 @@ TEST_P(RefreshRateSelectorTest, nonSeamlessVotePrefersSeamlessSwitches) { EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId()); - selector.setActiveModeId(kModeId120); + selector.setActiveMode(kModeId120, 120_Hz); EXPECT_EQ(kModeId120, selector.getBestFrameRateMode(layers)->getId()); } @@ -1984,7 +2001,7 @@ TEST_P(RefreshRateSelectorTest, nonSeamlessExactAndSeamlessMultipleLayers) { auto& seamedLayer = layers[0]; seamedLayer.desiredRefreshRate = 30_Hz; seamedLayer.name = "30Hz ExplicitDefault"; - selector.setActiveModeId(kModeId30); + selector.setActiveMode(kModeId30, 30_Hz); EXPECT_EQ(kModeId25, selector.getBestFrameRateMode(layers)->getId()); } @@ -2102,7 +2119,7 @@ TEST_P(RefreshRateSelectorTest, idle) { EXPECT_EQ(kModeId90, selector.getBestFrameRateMode({}, {.touch = true, .idle = true})->getId()); // Idle should be higher precedence than other layer frame rate considerations. - selector.setActiveModeId(kModeId90); + selector.setActiveMode(kModeId90, 90_Hz); { constexpr bool kTouchActive = false; @@ -2142,7 +2159,7 @@ TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_KnownFrameRate) { struct Expectation { Fps fps; - DisplayModePtr mode; + ftl::NonNull mode; }; const std::initializer_list knownFrameRatesExpectations = { @@ -2185,33 +2202,39 @@ TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitExact) { explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz; if (GetParam() == Config::FrameRateOverride::Disabled) { - EXPECT_SCORED_FRAME_RATE(kMode30, 30_Hz, selector.getBestScoredFrameRate(layers)); - EXPECT_SCORED_FRAME_RATE(kMode30, 30_Hz, - selector.getBestScoredFrameRate(layers, {.touch = true})); + EXPECT_FRAME_RATE_MODE(kMode30, 30_Hz, + selector.getBestScoredFrameRate(layers).frameRateMode); + EXPECT_FRAME_RATE_MODE(kMode30, 30_Hz, + selector.getBestScoredFrameRate(layers, {.touch = true}) + .frameRateMode); } else { - EXPECT_SCORED_FRAME_RATE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers)); - EXPECT_SCORED_FRAME_RATE(kMode120, 120_Hz, - selector.getBestScoredFrameRate(layers, {.touch = true})); + EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, + selector.getBestScoredFrameRate(layers).frameRateMode); + EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, + selector.getBestScoredFrameRate(layers, {.touch = true}) + .frameRateMode); } explicitExactOrMultipleLayer.desiredRefreshRate = 120_Hz; explicitExactLayer.desiredRefreshRate = 60_Hz; if (GetParam() == Config::FrameRateOverride::Disabled) { - EXPECT_SCORED_FRAME_RATE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers)); + EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, + selector.getBestScoredFrameRate(layers).frameRateMode); } else { - EXPECT_SCORED_FRAME_RATE(kMode120, 120_Hz, selector.getBestScoredFrameRate(layers)); + EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, + selector.getBestScoredFrameRate(layers).frameRateMode); } explicitExactLayer.desiredRefreshRate = 72_Hz; - EXPECT_SCORED_FRAME_RATE(kMode72, 72_Hz, selector.getBestScoredFrameRate(layers)); + EXPECT_FRAME_RATE_MODE(kMode72, 72_Hz, selector.getBestScoredFrameRate(layers).frameRateMode); explicitExactLayer.desiredRefreshRate = 90_Hz; - EXPECT_SCORED_FRAME_RATE(kMode90, 90_Hz, selector.getBestScoredFrameRate(layers)); + EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, selector.getBestScoredFrameRate(layers).frameRateMode); explicitExactLayer.desiredRefreshRate = 120_Hz; - EXPECT_SCORED_FRAME_RATE(kMode120, 120_Hz, selector.getBestScoredFrameRate(layers)); + EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, selector.getBestScoredFrameRate(layers).frameRateMode); } TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ReadsCache) { @@ -2295,6 +2318,10 @@ TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_FractionalRefreshRates_Exac // b/190578904 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withCloseRefreshRates) { + if (g_noSlowTests) { + GTEST_SKIP(); + } + const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue(); constexpr int kMaxRefreshRate = 240; @@ -2409,23 +2436,23 @@ TEST_P(RefreshRateSelectorTest, getFrameRateDivisor) { Fps displayRefreshRate = selector.getActiveMode().getFps(); EXPECT_EQ(1, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate)); - selector.setActiveModeId(kModeId60); + selector.setActiveMode(kModeId60, 60_Hz); displayRefreshRate = selector.getActiveMode().getFps(); EXPECT_EQ(2, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate)); - selector.setActiveModeId(kModeId72); + selector.setActiveMode(kModeId72, 72_Hz); displayRefreshRate = selector.getActiveMode().getFps(); EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate)); - selector.setActiveModeId(kModeId90); + selector.setActiveMode(kModeId90, 90_Hz); displayRefreshRate = selector.getActiveMode().getFps(); EXPECT_EQ(3, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate)); - selector.setActiveModeId(kModeId120); + selector.setActiveMode(kModeId120, 120_Hz); displayRefreshRate = selector.getActiveMode().getFps(); EXPECT_EQ(4, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate)); - selector.setActiveModeId(kModeId90); + selector.setActiveMode(kModeId90, 90_Hz); displayRefreshRate = selector.getActiveMode().getFps(); EXPECT_EQ(4, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, 22.5_Hz)); @@ -2808,7 +2835,7 @@ TEST_P(RefreshRateSelectorTest, refreshRateIsCappedWithRenderFrameRate) { constexpr FpsRanges kAppRequest = {/*physical*/ k0_120Hz, /*render*/ k0_120Hz}; - EXPECT_SCORED_FRAME_RATE(kMode120, 120_Hz, selector.getBestScoredFrameRate()); + EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, selector.getBestScoredFrameRate().frameRateMode); { constexpr FpsRanges kPrimary = {/*physical*/ k0_120Hz, /*render*/ k0_120Hz}; @@ -2819,7 +2846,7 @@ TEST_P(RefreshRateSelectorTest, refreshRateIsCappedWithRenderFrameRate) { /*appRequestRanges*/ kAppRequest})); } - EXPECT_SCORED_FRAME_RATE(kMode120, 120_Hz, selector.getBestScoredFrameRate()); + EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, selector.getBestScoredFrameRate().frameRateMode); { constexpr FpsRanges kPrimary = {/*physical*/ k0_60Hz, @@ -2831,7 +2858,7 @@ TEST_P(RefreshRateSelectorTest, refreshRateIsCappedWithRenderFrameRate) { /*appRequestRanges*/ kAppRequest})); } - EXPECT_SCORED_FRAME_RATE(kMode60, 60_Hz, selector.getBestScoredFrameRate()); + EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate().frameRateMode); { constexpr FpsRanges kPrimary = {/*physical*/ k0_120Hz, @@ -2843,7 +2870,7 @@ TEST_P(RefreshRateSelectorTest, refreshRateIsCappedWithRenderFrameRate) { /*appRequestRanges*/ kAppRequest})); } - EXPECT_SCORED_FRAME_RATE(kMode60, 60_Hz, selector.getBestScoredFrameRate()); + EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate().frameRateMode); } TEST_P(RefreshRateSelectorTest, renderFrameRates_60_120) { @@ -2858,17 +2885,20 @@ TEST_P(RefreshRateSelectorTest, renderFrameRates_60_120) { layer.name = "30Hz ExplicitDefault"; layer.desiredRefreshRate = 30_Hz; layer.vote = LayerVoteType::ExplicitDefault; - EXPECT_SCORED_FRAME_RATE(kMode60, expectedRenderRate, selector.getBestScoredFrameRate(layers)); + EXPECT_FRAME_RATE_MODE(kMode60, expectedRenderRate, + selector.getBestScoredFrameRate(layers).frameRateMode); layer.name = "30Hz Heuristic"; layer.desiredRefreshRate = 30_Hz; layer.vote = LayerVoteType::Heuristic; - EXPECT_SCORED_FRAME_RATE(kMode60, expectedRenderRate, selector.getBestScoredFrameRate(layers)); + EXPECT_FRAME_RATE_MODE(kMode60, expectedRenderRate, + selector.getBestScoredFrameRate(layers).frameRateMode); layer.name = "30Hz ExplicitExactOrMultiple"; layer.desiredRefreshRate = 30_Hz; layer.vote = LayerVoteType::ExplicitExactOrMultiple; - EXPECT_SCORED_FRAME_RATE(kMode60, expectedRenderRate, selector.getBestScoredFrameRate(layers)); + EXPECT_FRAME_RATE_MODE(kMode60, expectedRenderRate, + selector.getBestScoredFrameRate(layers).frameRateMode); } TEST_P(RefreshRateSelectorTest, idleWhenLowestRefreshRateIsNotDivisor) { @@ -2897,7 +2927,7 @@ TEST_P(RefreshRateSelectorTest, idleWhenLowestRefreshRateIsNotDivisor) { EXPECT_EQ(kModeId90, selector.getBestFrameRateMode({}, {.touch = true, .idle = true})->getId()); // Idle should be higher precedence than other layer frame rate considerations. - selector.setActiveModeId(kModeId90); + selector.setActiveMode(kModeId90, 90_Hz); { constexpr bool kTouchActive = false; EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::NoVote, kTouchActive)); diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 8e333a3482..3ee53c94c2 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -58,22 +58,22 @@ protected: SchedulerTest(); static constexpr PhysicalDisplayId kDisplayId1 = PhysicalDisplayId::fromPort(255u); - static inline const DisplayModePtr kDisplay1Mode60 = - createDisplayMode(kDisplayId1, DisplayModeId(0), 60_Hz); - static inline const DisplayModePtr kDisplay1Mode120 = - createDisplayMode(kDisplayId1, DisplayModeId(1), 120_Hz); + static inline const ftl::NonNull kDisplay1Mode60 = + ftl::as_non_null(createDisplayMode(kDisplayId1, DisplayModeId(0), 60_Hz)); + static inline const ftl::NonNull kDisplay1Mode120 = + ftl::as_non_null(createDisplayMode(kDisplayId1, DisplayModeId(1), 120_Hz)); static inline const DisplayModes kDisplay1Modes = makeModes(kDisplay1Mode60, kDisplay1Mode120); static constexpr PhysicalDisplayId kDisplayId2 = PhysicalDisplayId::fromPort(254u); - static inline const DisplayModePtr kDisplay2Mode60 = - createDisplayMode(kDisplayId2, DisplayModeId(0), 60_Hz); - static inline const DisplayModePtr kDisplay2Mode120 = - createDisplayMode(kDisplayId2, DisplayModeId(1), 120_Hz); + static inline const ftl::NonNull kDisplay2Mode60 = + ftl::as_non_null(createDisplayMode(kDisplayId2, DisplayModeId(0), 60_Hz)); + static inline const ftl::NonNull kDisplay2Mode120 = + ftl::as_non_null(createDisplayMode(kDisplayId2, DisplayModeId(1), 120_Hz)); static inline const DisplayModes kDisplay2Modes = makeModes(kDisplay2Mode60, kDisplay2Mode120); static constexpr PhysicalDisplayId kDisplayId3 = PhysicalDisplayId::fromPort(253u); - static inline const DisplayModePtr kDisplay3Mode60 = - createDisplayMode(kDisplayId3, DisplayModeId(0), 60_Hz); + static inline const ftl::NonNull kDisplay3Mode60 = + ftl::as_non_null(createDisplayMode(kDisplayId3, DisplayModeId(0), 60_Hz)); static inline const DisplayModes kDisplay3Modes = makeModes(kDisplay3Mode60); std::shared_ptr mSelector = @@ -217,7 +217,9 @@ TEST_F(SchedulerTest, onNonPrimaryDisplayModeChanged_invalidParameters) { // If the handle is incorrect, the function should return before // onModeChange is called. ConnectionHandle invalidHandle = {.id = 123}; - EXPECT_NO_FATAL_FAILURE(mScheduler->onNonPrimaryDisplayModeChanged(invalidHandle, mode)); + EXPECT_NO_FATAL_FAILURE( + mScheduler->onNonPrimaryDisplayModeChanged(invalidHandle, + {90_Hz, ftl::as_non_null(mode)})); EXPECT_CALL(*mEventThread, onModeChanged(_)).Times(0); } @@ -232,7 +234,7 @@ TEST_F(SchedulerTest, calculateMaxAcquiredBufferCount) { } MATCHER(Is120Hz, "") { - return isApproxEqual(arg.front().modePtr->getFps(), 120_Hz); + return isApproxEqual(arg.front().mode.fps, 120_Hz); } TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) { @@ -277,7 +279,7 @@ TEST_F(SchedulerTest, chooseDisplayModesSingleDisplay) { auto choice = modeChoices.get(kDisplayId1); ASSERT_TRUE(choice); - EXPECT_EQ(choice->get(), DisplayModeChoice(kDisplay1Mode60, globalSignals)); + EXPECT_EQ(choice->get(), DisplayModeChoice({60_Hz, kDisplay1Mode60}, globalSignals)); globalSignals = {.idle = false}; mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); @@ -287,7 +289,7 @@ TEST_F(SchedulerTest, chooseDisplayModesSingleDisplay) { choice = modeChoices.get(kDisplayId1); ASSERT_TRUE(choice); - EXPECT_EQ(choice->get(), DisplayModeChoice(kDisplay1Mode120, globalSignals)); + EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, globalSignals)); globalSignals = {.touch = true}; mScheduler->replaceTouchTimer(10); @@ -298,7 +300,7 @@ TEST_F(SchedulerTest, chooseDisplayModesSingleDisplay) { choice = modeChoices.get(kDisplayId1); ASSERT_TRUE(choice); - EXPECT_EQ(choice->get(), DisplayModeChoice(kDisplay1Mode120, globalSignals)); + EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, globalSignals)); mScheduler->unregisterDisplay(kDisplayId1); EXPECT_FALSE(mScheduler->hasRefreshRateSelectors()); @@ -319,8 +321,11 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { const GlobalSignals globalSignals = {.idle = true}; expectedChoices = ftl::init::map(kDisplayId1, kDisplay1Mode60, - globalSignals)(kDisplayId2, kDisplay2Mode60, + DisplayModeChoice>(kDisplayId1, + FrameRateMode{60_Hz, kDisplay1Mode60}, + globalSignals)(kDisplayId2, + FrameRateMode{60_Hz, + kDisplay2Mode60}, globalSignals); std::vector layers = {{.weight = 1.f}, @@ -335,8 +340,11 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { const GlobalSignals globalSignals = {.idle = false}; expectedChoices = ftl::init::map(kDisplayId1, kDisplay1Mode120, - globalSignals)(kDisplayId2, kDisplay2Mode120, + DisplayModeChoice>(kDisplayId1, + FrameRateMode{120_Hz, kDisplay1Mode120}, + globalSignals)(kDisplayId2, + FrameRateMode{120_Hz, + kDisplay2Mode120}, globalSignals); mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); @@ -351,8 +359,11 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { expectedChoices = ftl::init::map(kDisplayId1, kDisplay1Mode120, - globalSignals)(kDisplayId2, kDisplay2Mode120, + DisplayModeChoice>(kDisplayId1, + FrameRateMode{120_Hz, kDisplay1Mode120}, + globalSignals)(kDisplayId2, + FrameRateMode{120_Hz, + kDisplay2Mode120}, globalSignals); const auto actualChoices = mScheduler->chooseDisplayModes(); @@ -369,13 +380,15 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { mScheduler->replaceTouchTimer(10); mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); - expectedChoices = - ftl::init::map(kDisplayId1, kDisplay1Mode60, - globalSignals)(kDisplayId2, kDisplay2Mode60, - globalSignals)(kDisplayId3, - kDisplay3Mode60, - globalSignals); + expectedChoices = ftl::init::map< + const PhysicalDisplayId&, + DisplayModeChoice>(kDisplayId1, FrameRateMode{60_Hz, kDisplay1Mode60}, + globalSignals)(kDisplayId2, + FrameRateMode{60_Hz, kDisplay2Mode60}, + globalSignals)(kDisplayId3, + FrameRateMode{60_Hz, + kDisplay3Mode60}, + globalSignals); const auto actualChoices = mScheduler->chooseDisplayModes(); EXPECT_EQ(expectedChoices, actualChoices); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp index 05d0ebf773..b81693a0c2 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp @@ -116,7 +116,7 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRe ftl::FakeGuard guard(kMainThreadContext); ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60); + ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); mFlinger.onActiveDisplayChanged(mDisplay); @@ -125,8 +125,8 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRe 120)); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90); - ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60); + ASSERT_EQ(mDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId90); + ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); // Verify that next commit will call setActiveConfigWithConstraints in HWC const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; @@ -139,7 +139,7 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRe Mock::VerifyAndClearExpectations(mComposer); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60); + ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); // Verify that the next commit will complete the mode change and send // a onModeChanged event to the framework. @@ -149,7 +149,7 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRe Mock::VerifyAndClearExpectations(mAppEventThread); ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId90); + ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90); } TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithoutRefreshRequired) { @@ -164,8 +164,8 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithoutRefres 120)); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90); - ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60); + ASSERT_EQ(mDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId90); + ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); // Verify that next commit will call setActiveConfigWithConstraints in HWC // and complete the mode change. @@ -180,7 +180,7 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithoutRefres mFlinger.commit(); ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId90); + ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90); } TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) { @@ -190,7 +190,7 @@ TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) { // is still being processed the later call will be respected. ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60); + ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); mFlinger.onActiveDisplayChanged(mDisplay); @@ -211,7 +211,7 @@ TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) { 180)); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId120); + ASSERT_EQ(mDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId120); EXPECT_CALL(*mComposer, setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID, @@ -221,19 +221,19 @@ TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) { mFlinger.commit(); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId120); + ASSERT_EQ(mDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId120); mFlinger.commit(); ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId120); + ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId120); } TEST_F(DisplayModeSwitchingTest, changeResolution_OnActiveDisplay_WithoutRefreshRequired) { ftl::FakeGuard guard(kMainThreadContext); ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60); + ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); mFlinger.onActiveDisplayChanged(mDisplay); @@ -242,8 +242,8 @@ TEST_F(DisplayModeSwitchingTest, changeResolution_OnActiveDisplay_WithoutRefresh 120)); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90_4K); - ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60); + ASSERT_EQ(mDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId90_4K); + ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); // Verify that next commit will call setActiveConfigWithConstraints in HWC // and complete the mode change. @@ -278,7 +278,7 @@ TEST_F(DisplayModeSwitchingTest, changeResolution_OnActiveDisplay_WithoutRefresh mDisplay = mFlinger.getDisplay(displayToken); ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); - ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId90_4K); + ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90_4K); } } // namespace diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp index 0384568707..c0796df6cb 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp @@ -288,7 +288,7 @@ void SetupNewDisplayDeviceInternalTest::setupNewDisplayDeviceInternalTest() { if constexpr (Case::Display::CONNECTION_TYPE::value) { ftl::FakeGuard guard(kMainThreadContext); - EXPECT_EQ(Case::Display::HWC_ACTIVE_CONFIG_ID, device->getActiveMode().getHwcId()); + EXPECT_EQ(Case::Display::HWC_ACTIVE_CONFIG_ID, device->getActiveMode().modePtr->getHwcId()); } } diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index 3f8fe0d036..54c10c51e8 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -44,8 +44,7 @@ public: : Scheduler(*this, callback, Feature::kContentDetection) { mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller))); - const auto displayId = FTL_FAKE_GUARD(kMainThreadContext, - selectorPtr->getActiveMode().getPhysicalDisplayId()); + const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId(); registerDisplay(displayId, std::move(selectorPtr)); ON_CALL(*this, postMessage).WillByDefault([](sp&& handler) { @@ -147,7 +146,7 @@ public: mPolicy.cachedModeChangedParams.reset(); } - void onNonPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) { + void onNonPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode& mode) { Scheduler::onNonPrimaryDisplayModeChanged(handle, mode); } diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index c15b3c83d7..e29dd67c21 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -221,7 +221,7 @@ public: selectorPtr = std::make_shared(modes, kModeId60); } - const auto fps = FTL_FAKE_GUARD(kMainThreadContext, selectorPtr->getActiveMode().getFps()); + const auto fps = selectorPtr->getActiveMode().fps; mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps); mFlinger->mVsyncModulator = sp::make( mFlinger->mVsyncConfiguration->getCurrentConfigs()); @@ -857,23 +857,24 @@ public: const auto activeMode = modes.get(activeModeId); LOG_ALWAYS_FATAL_IF(!activeMode); + const auto fps = activeMode->get()->getFps(); state.physical = {.id = physicalId, .hwcDisplayId = *mHwcDisplayId, .activeMode = activeMode->get()}; - const auto it = mFlinger.mutablePhysicalDisplays() - .emplace_or_replace(physicalId, mDisplayToken, physicalId, - *mConnectionType, std::move(modes), - ui::ColorModes(), std::nullopt) - .first; + mFlinger.mutablePhysicalDisplays().emplace_or_replace(physicalId, mDisplayToken, + physicalId, *mConnectionType, + std::move(modes), + ui::ColorModes(), + std::nullopt); if (mFlinger.scheduler()) { mFlinger.scheduler()->registerDisplay(physicalId, display->holdRefreshRateSelector()); } - display->setActiveMode(activeModeId, it->second.snapshot()); + display->setActiveMode(activeModeId, fps, fps); } mFlinger.mutableCurrentState().displays.add(mDisplayToken, state); diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp index 2da266bd33..47c2deef51 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp @@ -54,6 +54,7 @@ public: void resetModel() final {} bool needsMoreSamples() const final { return false; } bool isVSyncInPhase(nsecs_t, Fps) const final { return false; } + void setDivisor(unsigned) final {} void dump(std::string&) const final {} private: @@ -91,6 +92,7 @@ public: void resetModel() final {} bool needsMoreSamples() const final { return false; } bool isVSyncInPhase(nsecs_t, Fps) const final { return false; } + void setDivisor(unsigned) final {} void dump(std::string&) const final {} private: diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp index f66075362e..2b86e94244 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp @@ -55,6 +55,7 @@ public: MOCK_METHOD0(resetModel, void()); MOCK_CONST_METHOD0(needsMoreSamples, bool()); MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps)); + MOCK_METHOD(void, setDivisor, (unsigned), (override)); MOCK_CONST_METHOD1(dump, void(std::string&)); nsecs_t nextVSyncTime(nsecs_t timePoint) const { diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index 74d2b7d9b6..3095e8aa9a 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -532,6 +532,26 @@ TEST_F(VSyncPredictorTest, robustToDuplicateTimestamps_60hzRealTraceData) { EXPECT_THAT(intercept, IsCloseTo(expectedIntercept, mMaxRoundingError)); } +TEST_F(VSyncPredictorTest, setDivisorIsRespected) { + auto last = mNow; + for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); + mNow += mPeriod; + last = mNow; + tracker.addVsyncTimestamp(mNow); + } + + tracker.setDivisor(3); + + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1100), Eq(mNow + 4 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 2100), Eq(mNow + 4 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3100), Eq(mNow + 4 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 4100), Eq(mNow + 7 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 7 * mPeriod)); +} + } // namespace android::scheduler // TODO(b/129481165): remove the #pragma below and fix conversion issues diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp index a35ff96815..8bd689a61d 100644 --- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp @@ -50,6 +50,7 @@ public: MOCK_METHOD0(resetModel, void()); MOCK_CONST_METHOD0(needsMoreSamples, bool()); MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps)); + MOCK_METHOD(void, setDivisor, (unsigned), (override)); MOCK_CONST_METHOD1(dump, void(std::string&)); }; diff --git a/services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h b/services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h new file mode 100644 index 0000000000..ef9cd9bc43 --- /dev/null +++ b/services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h @@ -0,0 +1,23 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +// Use a C style macro to keep the line numbers printed in gtest +#define EXPECT_FRAME_RATE_MODE(modePtr, fps, mode) \ + EXPECT_EQ((scheduler::FrameRateMode{(fps), (modePtr)}), (mode)) diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h index 5b0c1f38be..6893154259 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h +++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h @@ -34,6 +34,7 @@ public: MOCK_METHOD0(resetModel, void()); MOCK_CONST_METHOD0(needsMoreSamples, bool()); MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps)); + MOCK_METHOD(void, setDivisor, (unsigned), (override)); MOCK_CONST_METHOD1(dump, void(std::string&)); }; -- GitLab From ace701cd539259b858bc912342a47d804bae87df Mon Sep 17 00:00:00 2001 From: Rachel Lee Date: Wed, 5 Oct 2022 10:23:47 -0700 Subject: [PATCH 0544/1310] Move Choreographer impl to new file. Separates implementation from the API so it is neater. Bug: 255838011 Test: make, flash, atest ChoreographerNativeTest Change-Id: I8ac97ed4ebc1a4ed9d6315a473e178bf3d655252 --- libs/nativedisplay/AChoreographer.cpp | 473 +----------------- libs/nativedisplay/Android.bp | 1 + libs/nativedisplay/Choreographer.cpp | 390 +++++++++++++++ .../include/nativedisplay/Choreographer.h | 138 +++++ 4 files changed, 536 insertions(+), 466 deletions(-) create mode 100644 libs/nativedisplay/Choreographer.cpp create mode 100644 libs/nativedisplay/include/nativedisplay/Choreographer.h diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp index 539cbaa341..e64165fef3 100644 --- a/libs/nativedisplay/AChoreographer.cpp +++ b/libs/nativedisplay/AChoreographer.cpp @@ -14,13 +14,10 @@ * limitations under the License. */ -#define LOG_TAG "Choreographer" -//#define LOG_NDEBUG 0 - #include #include -#include #include +#include #include #include #include @@ -31,444 +28,9 @@ #include #include -namespace { -struct { - // Global JVM that is provided by zygote - JavaVM* jvm = nullptr; - struct { - jclass clazz; - jmethodID getInstance; - jmethodID registerNativeChoreographerForRefreshRateCallbacks; - jmethodID unregisterNativeChoreographerForRefreshRateCallbacks; - } displayManagerGlobal; -} gJni; - -// Gets the JNIEnv* for this thread, and performs one-off initialization if we -// have never retrieved a JNIEnv* pointer before. -JNIEnv* getJniEnv() { - if (gJni.jvm == nullptr) { - ALOGW("AChoreographer: No JVM provided!"); - return nullptr; - } - - JNIEnv* env = nullptr; - if (gJni.jvm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) { - ALOGD("Attaching thread to JVM for AChoreographer"); - JavaVMAttachArgs args = {JNI_VERSION_1_4, "AChoreographer_env", NULL}; - jint attachResult = gJni.jvm->AttachCurrentThreadAsDaemon(&env, (void*)&args); - if (attachResult != JNI_OK) { - ALOGE("Unable to attach thread. Error: %d", attachResult); - return nullptr; - } - } - if (env == nullptr) { - ALOGW("AChoreographer: No JNI env available!"); - } - return env; -} - -inline const char* toString(bool value) { - return value ? "true" : "false"; -} -} // namespace - -namespace android { -using gui::VsyncEventData; - -struct FrameCallback { - AChoreographer_frameCallback callback; - AChoreographer_frameCallback64 callback64; - AChoreographer_vsyncCallback vsyncCallback; - void* data; - nsecs_t dueTime; - - inline bool operator<(const FrameCallback& rhs) const { - // Note that this is intentionally flipped because we want callbacks due sooner to be at - // the head of the queue - return dueTime > rhs.dueTime; - } -}; - -struct RefreshRateCallback { - AChoreographer_refreshRateCallback callback; - void* data; - bool firstCallbackFired = false; -}; - -class Choreographer; - -/** - * Implementation of AChoreographerFrameCallbackData. - */ -struct ChoreographerFrameCallbackDataImpl { - int64_t frameTimeNanos{0}; - - VsyncEventData vsyncEventData; - - const Choreographer* choreographer; -}; - -struct { - std::mutex lock; - std::vector ptrs GUARDED_BY(lock); - std::map startTimes GUARDED_BY(lock); - bool registeredToDisplayManager GUARDED_BY(lock) = false; - - std::atomic mLastKnownVsync = -1; -} gChoreographers; - -class Choreographer : public DisplayEventDispatcher, public MessageHandler { -public: - explicit Choreographer(const sp& looper) EXCLUDES(gChoreographers.lock); - void postFrameCallbackDelayed(AChoreographer_frameCallback cb, - AChoreographer_frameCallback64 cb64, - AChoreographer_vsyncCallback vsyncCallback, void* data, - nsecs_t delay); - void registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data) - EXCLUDES(gChoreographers.lock); - void unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data); - // Drains the queue of pending vsync periods and dispatches refresh rate - // updates to callbacks. - // The assumption is that this method is only called on a single - // processing thread, either by looper or by AChoreographer_handleEvents - void handleRefreshRateUpdates(); - void scheduleLatestConfigRequest(); - - enum { - MSG_SCHEDULE_CALLBACKS = 0, - MSG_SCHEDULE_VSYNC = 1, - MSG_HANDLE_REFRESH_RATE_UPDATES = 2, - }; - virtual void handleMessage(const Message& message) override; - - static Choreographer* getForThread(); - virtual ~Choreographer() override EXCLUDES(gChoreographers.lock); - int64_t getFrameInterval() const; - bool inCallback() const; - -private: - Choreographer(const Choreographer&) = delete; - - void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count, - VsyncEventData vsyncEventData) override; - void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override; - void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId, - nsecs_t vsyncPeriod) override; - void dispatchNullEvent(nsecs_t, PhysicalDisplayId) override; - void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId, - std::vector overrides) override; - - void scheduleCallbacks(); - - ChoreographerFrameCallbackDataImpl createFrameCallbackData(nsecs_t timestamp) const; - void registerStartTime() const; - - std::mutex mLock; - // Protected by mLock - std::priority_queue mFrameCallbacks; - std::vector mRefreshRateCallbacks; - - nsecs_t mLatestVsyncPeriod = -1; - VsyncEventData mLastVsyncEventData; - bool mInCallback = false; - - const sp mLooper; - const std::thread::id mThreadId; - - // Approximation of num_threads_using_choreographer * num_frames_of_history with leeway. - static constexpr size_t kMaxStartTimes = 250; -}; - -static thread_local Choreographer* gChoreographer; -Choreographer* Choreographer::getForThread() { - if (gChoreographer == nullptr) { - sp looper = Looper::getForThread(); - if (!looper.get()) { - ALOGW("No looper prepared for thread"); - return nullptr; - } - gChoreographer = new Choreographer(looper); - status_t result = gChoreographer->initialize(); - if (result != OK) { - ALOGW("Failed to initialize"); - return nullptr; - } - } - return gChoreographer; -} - -Choreographer::Choreographer(const sp& looper) - : DisplayEventDispatcher(looper, gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp), - mLooper(looper), - mThreadId(std::this_thread::get_id()) { - std::lock_guard _l(gChoreographers.lock); - gChoreographers.ptrs.push_back(this); -} - -Choreographer::~Choreographer() { - std::lock_guard _l(gChoreographers.lock); - gChoreographers.ptrs.erase(std::remove_if(gChoreographers.ptrs.begin(), - gChoreographers.ptrs.end(), - [=](Choreographer* c) { return c == this; }), - gChoreographers.ptrs.end()); - // Only poke DisplayManagerGlobal to unregister if we previously registered - // callbacks. - if (gChoreographers.ptrs.empty() && gChoreographers.registeredToDisplayManager) { - gChoreographers.registeredToDisplayManager = false; - JNIEnv* env = getJniEnv(); - if (env == nullptr) { - ALOGW("JNI environment is unavailable, skipping choreographer cleanup"); - return; - } - jobject dmg = env->CallStaticObjectMethod(gJni.displayManagerGlobal.clazz, - gJni.displayManagerGlobal.getInstance); - if (dmg == nullptr) { - ALOGW("DMS is not initialized yet, skipping choreographer cleanup"); - } else { - env->CallVoidMethod(dmg, - gJni.displayManagerGlobal - .unregisterNativeChoreographerForRefreshRateCallbacks); - env->DeleteLocalRef(dmg); - } - } -} - -void Choreographer::postFrameCallbackDelayed(AChoreographer_frameCallback cb, - AChoreographer_frameCallback64 cb64, - AChoreographer_vsyncCallback vsyncCallback, void* data, - nsecs_t delay) { - nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); - FrameCallback callback{cb, cb64, vsyncCallback, data, now + delay}; - { - std::lock_guard _l{mLock}; - mFrameCallbacks.push(callback); - } - if (callback.dueTime <= now) { - if (std::this_thread::get_id() != mThreadId) { - if (mLooper != nullptr) { - Message m{MSG_SCHEDULE_VSYNC}; - mLooper->sendMessage(this, m); - } else { - scheduleVsync(); - } - } else { - scheduleVsync(); - } - } else { - if (mLooper != nullptr) { - Message m{MSG_SCHEDULE_CALLBACKS}; - mLooper->sendMessageDelayed(delay, this, m); - } else { - scheduleCallbacks(); - } - } -} - -void Choreographer::registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data) { - std::lock_guard _l{mLock}; - for (const auto& callback : mRefreshRateCallbacks) { - // Don't re-add callbacks. - if (cb == callback.callback && data == callback.data) { - return; - } - } - mRefreshRateCallbacks.emplace_back( - RefreshRateCallback{.callback = cb, .data = data, .firstCallbackFired = false}); - bool needsRegistration = false; - { - std::lock_guard _l2(gChoreographers.lock); - needsRegistration = !gChoreographers.registeredToDisplayManager; - } - if (needsRegistration) { - JNIEnv* env = getJniEnv(); - if (env == nullptr) { - ALOGW("JNI environment is unavailable, skipping registration"); - return; - } - jobject dmg = env->CallStaticObjectMethod(gJni.displayManagerGlobal.clazz, - gJni.displayManagerGlobal.getInstance); - if (dmg == nullptr) { - ALOGW("DMS is not initialized yet: skipping registration"); - return; - } else { - env->CallVoidMethod(dmg, - gJni.displayManagerGlobal - .registerNativeChoreographerForRefreshRateCallbacks, - reinterpret_cast(this)); - env->DeleteLocalRef(dmg); - { - std::lock_guard _l2(gChoreographers.lock); - gChoreographers.registeredToDisplayManager = true; - } - } - } else { - scheduleLatestConfigRequest(); - } -} - -void Choreographer::unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb, - void* data) { - std::lock_guard _l{mLock}; - mRefreshRateCallbacks.erase(std::remove_if(mRefreshRateCallbacks.begin(), - mRefreshRateCallbacks.end(), - [&](const RefreshRateCallback& callback) { - return cb == callback.callback && - data == callback.data; - }), - mRefreshRateCallbacks.end()); -} - -void Choreographer::scheduleLatestConfigRequest() { - if (mLooper != nullptr) { - Message m{MSG_HANDLE_REFRESH_RATE_UPDATES}; - mLooper->sendMessage(this, m); - } else { - // If the looper thread is detached from Choreographer, then refresh rate - // changes will be handled in AChoreographer_handlePendingEvents, so we - // need to wake up the looper thread by writing to the write-end of the - // socket the looper is listening on. - // Fortunately, these events are small so sending packets across the - // socket should be atomic across processes. - DisplayEventReceiver::Event event; - event.header = - DisplayEventReceiver::Event::Header{DisplayEventReceiver::DISPLAY_EVENT_NULL, - PhysicalDisplayId::fromPort(0), systemTime()}; - injectEvent(event); - } -} - -void Choreographer::scheduleCallbacks() { - const nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); - nsecs_t dueTime; - { - std::lock_guard _l{mLock}; - // If there are no pending callbacks then don't schedule a vsync - if (mFrameCallbacks.empty()) { - return; - } - dueTime = mFrameCallbacks.top().dueTime; - } - - if (dueTime <= now) { - ALOGV("choreographer %p ~ scheduling vsync", this); - scheduleVsync(); - return; - } -} - -void Choreographer::handleRefreshRateUpdates() { - std::vector callbacks{}; - const nsecs_t pendingPeriod = gChoreographers.mLastKnownVsync.load(); - const nsecs_t lastPeriod = mLatestVsyncPeriod; - if (pendingPeriod > 0) { - mLatestVsyncPeriod = pendingPeriod; - } - { - std::lock_guard _l{mLock}; - for (auto& cb : mRefreshRateCallbacks) { - callbacks.push_back(cb); - cb.firstCallbackFired = true; - } - } - - for (auto& cb : callbacks) { - if (!cb.firstCallbackFired || (pendingPeriod > 0 && pendingPeriod != lastPeriod)) { - cb.callback(pendingPeriod, cb.data); - } - } -} - -// TODO(b/74619554): The PhysicalDisplayId is ignored because SF only emits VSYNC events for the -// internal display and DisplayEventReceiver::requestNextVsync only allows requesting VSYNC for -// the internal display implicitly. -void Choreographer::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId, uint32_t, - VsyncEventData vsyncEventData) { - std::vector callbacks{}; - { - std::lock_guard _l{mLock}; - nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); - while (!mFrameCallbacks.empty() && mFrameCallbacks.top().dueTime < now) { - callbacks.push_back(mFrameCallbacks.top()); - mFrameCallbacks.pop(); - } - } - mLastVsyncEventData = vsyncEventData; - for (const auto& cb : callbacks) { - if (cb.vsyncCallback != nullptr) { - const ChoreographerFrameCallbackDataImpl frameCallbackData = - createFrameCallbackData(timestamp); - registerStartTime(); - mInCallback = true; - cb.vsyncCallback(reinterpret_cast( - &frameCallbackData), - cb.data); - mInCallback = false; - } else if (cb.callback64 != nullptr) { - cb.callback64(timestamp, cb.data); - } else if (cb.callback != nullptr) { - cb.callback(timestamp, cb.data); - } - } -} - -void Choreographer::dispatchHotplug(nsecs_t, PhysicalDisplayId displayId, bool connected) { - ALOGV("choreographer %p ~ received hotplug event (displayId=%s, connected=%s), ignoring.", this, - to_string(displayId).c_str(), toString(connected)); -} - -void Choreographer::dispatchModeChanged(nsecs_t, PhysicalDisplayId, int32_t, nsecs_t) { - LOG_ALWAYS_FATAL("dispatchModeChanged was called but was never registered"); -} - -void Choreographer::dispatchFrameRateOverrides(nsecs_t, PhysicalDisplayId, - std::vector) { - LOG_ALWAYS_FATAL("dispatchFrameRateOverrides was called but was never registered"); -} - -void Choreographer::dispatchNullEvent(nsecs_t, PhysicalDisplayId) { - ALOGV("choreographer %p ~ received null event.", this); - handleRefreshRateUpdates(); -} - -void Choreographer::handleMessage(const Message& message) { - switch (message.what) { - case MSG_SCHEDULE_CALLBACKS: - scheduleCallbacks(); - break; - case MSG_SCHEDULE_VSYNC: - scheduleVsync(); - break; - case MSG_HANDLE_REFRESH_RATE_UPDATES: - handleRefreshRateUpdates(); - break; - } -} - -int64_t Choreographer::getFrameInterval() const { - return mLastVsyncEventData.frameInterval; -} - -bool Choreographer::inCallback() const { - return mInCallback; -} - -ChoreographerFrameCallbackDataImpl Choreographer::createFrameCallbackData(nsecs_t timestamp) const { - return {.frameTimeNanos = timestamp, - .vsyncEventData = mLastVsyncEventData, - .choreographer = this}; -} - -void Choreographer::registerStartTime() const { - std::scoped_lock _l(gChoreographers.lock); - for (VsyncEventData::FrameTimeline frameTimeline : mLastVsyncEventData.frameTimelines) { - while (gChoreographers.startTimes.size() >= kMaxStartTimes) { - gChoreographers.startTimes.erase(gChoreographers.startTimes.begin()); - } - gChoreographers.startTimes[frameTimeline.vsyncId] = systemTime(SYSTEM_TIME_MONOTONIC); - } -} +#undef LOG_TAG +#define LOG_TAG "AChoreographer" -} // namespace android using namespace android; static inline Choreographer* AChoreographer_to_Choreographer(AChoreographer* choreographer) { @@ -488,27 +50,12 @@ AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl( // Glue for private C api namespace android { -void AChoreographer_signalRefreshRateCallbacks(nsecs_t vsyncPeriod) EXCLUDES(gChoreographers.lock) { - std::lock_guard _l(gChoreographers.lock); - gChoreographers.mLastKnownVsync.store(vsyncPeriod); - for (auto c : gChoreographers.ptrs) { - c->scheduleLatestConfigRequest(); - } +void AChoreographer_signalRefreshRateCallbacks(nsecs_t vsyncPeriod) { + Choreographer::signalRefreshRateCallbacks(vsyncPeriod); } void AChoreographer_initJVM(JNIEnv* env) { - env->GetJavaVM(&gJni.jvm); - // Now we need to find the java classes. - jclass dmgClass = env->FindClass("android/hardware/display/DisplayManagerGlobal"); - gJni.displayManagerGlobal.clazz = static_cast(env->NewGlobalRef(dmgClass)); - gJni.displayManagerGlobal.getInstance = - env->GetStaticMethodID(dmgClass, "getInstance", - "()Landroid/hardware/display/DisplayManagerGlobal;"); - gJni.displayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks = - env->GetMethodID(dmgClass, "registerNativeChoreographerForRefreshRateCallbacks", "()V"); - gJni.displayManagerGlobal.unregisterNativeChoreographerForRefreshRateCallbacks = - env->GetMethodID(dmgClass, "unregisterNativeChoreographerForRefreshRateCallbacks", - "()V"); + Choreographer::initJVM(env); } AChoreographer* AChoreographer_routeGetInstance() { @@ -583,13 +130,7 @@ int64_t AChoreographer_getFrameInterval(const AChoreographer* choreographer) { } int64_t AChoreographer_getStartTimeNanosForVsyncId(AVsyncId vsyncId) { - std::scoped_lock _l(gChoreographers.lock); - const auto iter = gChoreographers.startTimes.find(vsyncId); - if (iter == gChoreographers.startTimes.end()) { - ALOGW("Start time was not found for vsync id: %" PRId64, vsyncId); - return 0; - } - return iter->second; + return Choreographer::getStartTimeNanosForVsyncId(vsyncId); } } // namespace android diff --git a/libs/nativedisplay/Android.bp b/libs/nativedisplay/Android.bp index 8d8a2bc244..70de33da12 100644 --- a/libs/nativedisplay/Android.bp +++ b/libs/nativedisplay/Android.bp @@ -56,6 +56,7 @@ cc_library_shared { ":libgui_frame_event_aidl", "AChoreographer.cpp", "ADisplay.cpp", + "Choreographer.cpp", "surfacetexture/surface_texture.cpp", "surfacetexture/SurfaceTexture.cpp", "surfacetexture/ImageConsumer.cpp", diff --git a/libs/nativedisplay/Choreographer.cpp b/libs/nativedisplay/Choreographer.cpp new file mode 100644 index 0000000000..01e9f04d15 --- /dev/null +++ b/libs/nativedisplay/Choreographer.cpp @@ -0,0 +1,390 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// #define LOG_NDEBUG 0 + +#include +#include + +#undef LOG_TAG +#define LOG_TAG "AChoreographer" + +namespace { +struct { + // Global JVM that is provided by zygote + JavaVM* jvm = nullptr; + struct { + jclass clazz; + jmethodID getInstance; + jmethodID registerNativeChoreographerForRefreshRateCallbacks; + jmethodID unregisterNativeChoreographerForRefreshRateCallbacks; + } displayManagerGlobal; +} gJni; + +// Gets the JNIEnv* for this thread, and performs one-off initialization if we +// have never retrieved a JNIEnv* pointer before. +JNIEnv* getJniEnv() { + if (gJni.jvm == nullptr) { + ALOGW("AChoreographer: No JVM provided!"); + return nullptr; + } + + JNIEnv* env = nullptr; + if (gJni.jvm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) { + ALOGD("Attaching thread to JVM for AChoreographer"); + JavaVMAttachArgs args = {JNI_VERSION_1_4, "AChoreographer_env", NULL}; + jint attachResult = gJni.jvm->AttachCurrentThreadAsDaemon(&env, (void*)&args); + if (attachResult != JNI_OK) { + ALOGE("Unable to attach thread. Error: %d", attachResult); + return nullptr; + } + } + if (env == nullptr) { + ALOGW("AChoreographer: No JNI env available!"); + } + return env; +} + +inline const char* toString(bool value) { + return value ? "true" : "false"; +} +} // namespace + +namespace android { + +Choreographer::Context Choreographer::gChoreographers; + +static thread_local Choreographer* gChoreographer; + +void Choreographer::initJVM(JNIEnv* env) { + env->GetJavaVM(&gJni.jvm); + // Now we need to find the java classes. + jclass dmgClass = env->FindClass("android/hardware/display/DisplayManagerGlobal"); + gJni.displayManagerGlobal.clazz = static_cast(env->NewGlobalRef(dmgClass)); + gJni.displayManagerGlobal.getInstance = + env->GetStaticMethodID(dmgClass, "getInstance", + "()Landroid/hardware/display/DisplayManagerGlobal;"); + gJni.displayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks = + env->GetMethodID(dmgClass, "registerNativeChoreographerForRefreshRateCallbacks", "()V"); + gJni.displayManagerGlobal.unregisterNativeChoreographerForRefreshRateCallbacks = + env->GetMethodID(dmgClass, "unregisterNativeChoreographerForRefreshRateCallbacks", + "()V"); +} + +Choreographer* Choreographer::getForThread() { + if (gChoreographer == nullptr) { + sp looper = Looper::getForThread(); + if (!looper.get()) { + ALOGW("No looper prepared for thread"); + return nullptr; + } + gChoreographer = new Choreographer(looper); + status_t result = gChoreographer->initialize(); + if (result != OK) { + ALOGW("Failed to initialize"); + return nullptr; + } + } + return gChoreographer; +} + +Choreographer::Choreographer(const sp& looper) + : DisplayEventDispatcher(looper, gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp), + mLooper(looper), + mThreadId(std::this_thread::get_id()) { + std::lock_guard _l(gChoreographers.lock); + gChoreographers.ptrs.push_back(this); +} + +Choreographer::~Choreographer() { + std::lock_guard _l(gChoreographers.lock); + gChoreographers.ptrs.erase(std::remove_if(gChoreographers.ptrs.begin(), + gChoreographers.ptrs.end(), + [=](Choreographer* c) { return c == this; }), + gChoreographers.ptrs.end()); + // Only poke DisplayManagerGlobal to unregister if we previously registered + // callbacks. + if (gChoreographers.ptrs.empty() && gChoreographers.registeredToDisplayManager) { + gChoreographers.registeredToDisplayManager = false; + JNIEnv* env = getJniEnv(); + if (env == nullptr) { + ALOGW("JNI environment is unavailable, skipping choreographer cleanup"); + return; + } + jobject dmg = env->CallStaticObjectMethod(gJni.displayManagerGlobal.clazz, + gJni.displayManagerGlobal.getInstance); + if (dmg == nullptr) { + ALOGW("DMS is not initialized yet, skipping choreographer cleanup"); + } else { + env->CallVoidMethod(dmg, + gJni.displayManagerGlobal + .unregisterNativeChoreographerForRefreshRateCallbacks); + env->DeleteLocalRef(dmg); + } + } +} + +void Choreographer::postFrameCallbackDelayed(AChoreographer_frameCallback cb, + AChoreographer_frameCallback64 cb64, + AChoreographer_vsyncCallback vsyncCallback, void* data, + nsecs_t delay) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + FrameCallback callback{cb, cb64, vsyncCallback, data, now + delay}; + { + std::lock_guard _l{mLock}; + mFrameCallbacks.push(callback); + } + if (callback.dueTime <= now) { + if (std::this_thread::get_id() != mThreadId) { + if (mLooper != nullptr) { + Message m{MSG_SCHEDULE_VSYNC}; + mLooper->sendMessage(this, m); + } else { + scheduleVsync(); + } + } else { + scheduleVsync(); + } + } else { + if (mLooper != nullptr) { + Message m{MSG_SCHEDULE_CALLBACKS}; + mLooper->sendMessageDelayed(delay, this, m); + } else { + scheduleCallbacks(); + } + } +} + +void Choreographer::registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data) { + std::lock_guard _l{mLock}; + for (const auto& callback : mRefreshRateCallbacks) { + // Don't re-add callbacks. + if (cb == callback.callback && data == callback.data) { + return; + } + } + mRefreshRateCallbacks.emplace_back( + RefreshRateCallback{.callback = cb, .data = data, .firstCallbackFired = false}); + bool needsRegistration = false; + { + std::lock_guard _l2(gChoreographers.lock); + needsRegistration = !gChoreographers.registeredToDisplayManager; + } + if (needsRegistration) { + JNIEnv* env = getJniEnv(); + if (env == nullptr) { + ALOGW("JNI environment is unavailable, skipping registration"); + return; + } + jobject dmg = env->CallStaticObjectMethod(gJni.displayManagerGlobal.clazz, + gJni.displayManagerGlobal.getInstance); + if (dmg == nullptr) { + ALOGW("DMS is not initialized yet: skipping registration"); + return; + } else { + env->CallVoidMethod(dmg, + gJni.displayManagerGlobal + .registerNativeChoreographerForRefreshRateCallbacks, + reinterpret_cast(this)); + env->DeleteLocalRef(dmg); + { + std::lock_guard _l2(gChoreographers.lock); + gChoreographers.registeredToDisplayManager = true; + } + } + } else { + scheduleLatestConfigRequest(); + } +} + +void Choreographer::unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb, + void* data) { + std::lock_guard _l{mLock}; + mRefreshRateCallbacks.erase(std::remove_if(mRefreshRateCallbacks.begin(), + mRefreshRateCallbacks.end(), + [&](const RefreshRateCallback& callback) { + return cb == callback.callback && + data == callback.data; + }), + mRefreshRateCallbacks.end()); +} + +void Choreographer::scheduleLatestConfigRequest() { + if (mLooper != nullptr) { + Message m{MSG_HANDLE_REFRESH_RATE_UPDATES}; + mLooper->sendMessage(this, m); + } else { + // If the looper thread is detached from Choreographer, then refresh rate + // changes will be handled in AChoreographer_handlePendingEvents, so we + // need to wake up the looper thread by writing to the write-end of the + // socket the looper is listening on. + // Fortunately, these events are small so sending packets across the + // socket should be atomic across processes. + DisplayEventReceiver::Event event; + event.header = + DisplayEventReceiver::Event::Header{DisplayEventReceiver::DISPLAY_EVENT_NULL, + PhysicalDisplayId::fromPort(0), systemTime()}; + injectEvent(event); + } +} + +void Choreographer::scheduleCallbacks() { + const nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + nsecs_t dueTime; + { + std::lock_guard _l{mLock}; + // If there are no pending callbacks then don't schedule a vsync + if (mFrameCallbacks.empty()) { + return; + } + dueTime = mFrameCallbacks.top().dueTime; + } + + if (dueTime <= now) { + ALOGV("choreographer %p ~ scheduling vsync", this); + scheduleVsync(); + return; + } +} + +void Choreographer::handleRefreshRateUpdates() { + std::vector callbacks{}; + const nsecs_t pendingPeriod = gChoreographers.mLastKnownVsync.load(); + const nsecs_t lastPeriod = mLatestVsyncPeriod; + if (pendingPeriod > 0) { + mLatestVsyncPeriod = pendingPeriod; + } + { + std::lock_guard _l{mLock}; + for (auto& cb : mRefreshRateCallbacks) { + callbacks.push_back(cb); + cb.firstCallbackFired = true; + } + } + + for (auto& cb : callbacks) { + if (!cb.firstCallbackFired || (pendingPeriod > 0 && pendingPeriod != lastPeriod)) { + cb.callback(pendingPeriod, cb.data); + } + } +} + +void Choreographer::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId, uint32_t, + VsyncEventData vsyncEventData) { + std::vector callbacks{}; + { + std::lock_guard _l{mLock}; + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + while (!mFrameCallbacks.empty() && mFrameCallbacks.top().dueTime < now) { + callbacks.push_back(mFrameCallbacks.top()); + mFrameCallbacks.pop(); + } + } + mLastVsyncEventData = vsyncEventData; + for (const auto& cb : callbacks) { + if (cb.vsyncCallback != nullptr) { + const ChoreographerFrameCallbackDataImpl frameCallbackData = + createFrameCallbackData(timestamp); + registerStartTime(); + mInCallback = true; + cb.vsyncCallback(reinterpret_cast( + &frameCallbackData), + cb.data); + mInCallback = false; + } else if (cb.callback64 != nullptr) { + cb.callback64(timestamp, cb.data); + } else if (cb.callback != nullptr) { + cb.callback(timestamp, cb.data); + } + } +} + +void Choreographer::dispatchHotplug(nsecs_t, PhysicalDisplayId displayId, bool connected) { + ALOGV("choreographer %p ~ received hotplug event (displayId=%s, connected=%s), ignoring.", this, + to_string(displayId).c_str(), toString(connected)); +} + +void Choreographer::dispatchModeChanged(nsecs_t, PhysicalDisplayId, int32_t, nsecs_t) { + LOG_ALWAYS_FATAL("dispatchModeChanged was called but was never registered"); +} + +void Choreographer::dispatchFrameRateOverrides(nsecs_t, PhysicalDisplayId, + std::vector) { + LOG_ALWAYS_FATAL("dispatchFrameRateOverrides was called but was never registered"); +} + +void Choreographer::dispatchNullEvent(nsecs_t, PhysicalDisplayId) { + ALOGV("choreographer %p ~ received null event.", this); + handleRefreshRateUpdates(); +} + +void Choreographer::handleMessage(const Message& message) { + switch (message.what) { + case MSG_SCHEDULE_CALLBACKS: + scheduleCallbacks(); + break; + case MSG_SCHEDULE_VSYNC: + scheduleVsync(); + break; + case MSG_HANDLE_REFRESH_RATE_UPDATES: + handleRefreshRateUpdates(); + break; + } +} + +int64_t Choreographer::getFrameInterval() const { + return mLastVsyncEventData.frameInterval; +} + +bool Choreographer::inCallback() const { + return mInCallback; +} + +ChoreographerFrameCallbackDataImpl Choreographer::createFrameCallbackData(nsecs_t timestamp) const { + return {.frameTimeNanos = timestamp, + .vsyncEventData = mLastVsyncEventData, + .choreographer = this}; +} + +void Choreographer::registerStartTime() const { + std::scoped_lock _l(gChoreographers.lock); + for (VsyncEventData::FrameTimeline frameTimeline : mLastVsyncEventData.frameTimelines) { + while (gChoreographers.startTimes.size() >= kMaxStartTimes) { + gChoreographers.startTimes.erase(gChoreographers.startTimes.begin()); + } + gChoreographers.startTimes[frameTimeline.vsyncId] = systemTime(SYSTEM_TIME_MONOTONIC); + } +} + +void Choreographer::signalRefreshRateCallbacks(nsecs_t vsyncPeriod) { + std::lock_guard _l(gChoreographers.lock); + gChoreographers.mLastKnownVsync.store(vsyncPeriod); + for (auto c : gChoreographers.ptrs) { + c->scheduleLatestConfigRequest(); + } +} + +int64_t Choreographer::getStartTimeNanosForVsyncId(AVsyncId vsyncId) { + std::scoped_lock _l(gChoreographers.lock); + const auto iter = gChoreographers.startTimes.find(vsyncId); + if (iter == gChoreographers.startTimes.end()) { + ALOGW("Start time was not found for vsync id: %" PRId64, vsyncId); + return 0; + } + return iter->second; +} + +} // namespace android \ No newline at end of file diff --git a/libs/nativedisplay/include/nativedisplay/Choreographer.h b/libs/nativedisplay/include/nativedisplay/Choreographer.h new file mode 100644 index 0000000000..bb63f291f9 --- /dev/null +++ b/libs/nativedisplay/include/nativedisplay/Choreographer.h @@ -0,0 +1,138 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace android { +using gui::VsyncEventData; + +struct FrameCallback { + AChoreographer_frameCallback callback; + AChoreographer_frameCallback64 callback64; + AChoreographer_vsyncCallback vsyncCallback; + void* data; + nsecs_t dueTime; + + inline bool operator<(const FrameCallback& rhs) const { + // Note that this is intentionally flipped because we want callbacks due sooner to be at + // the head of the queue + return dueTime > rhs.dueTime; + } +}; + +struct RefreshRateCallback { + AChoreographer_refreshRateCallback callback; + void* data; + bool firstCallbackFired = false; +}; + +class Choreographer; + +/** + * Implementation of AChoreographerFrameCallbackData. + */ +struct ChoreographerFrameCallbackDataImpl { + int64_t frameTimeNanos{0}; + + VsyncEventData vsyncEventData; + + const Choreographer* choreographer; +}; + +class Choreographer : public DisplayEventDispatcher, public MessageHandler { +public: + struct Context { + std::mutex lock; + std::vector ptrs GUARDED_BY(lock); + std::map startTimes GUARDED_BY(lock); + bool registeredToDisplayManager GUARDED_BY(lock) = false; + + std::atomic mLastKnownVsync = -1; + }; + static Context gChoreographers; + + explicit Choreographer(const sp& looper) EXCLUDES(gChoreographers.lock); + void postFrameCallbackDelayed(AChoreographer_frameCallback cb, + AChoreographer_frameCallback64 cb64, + AChoreographer_vsyncCallback vsyncCallback, void* data, + nsecs_t delay); + void registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data) + EXCLUDES(gChoreographers.lock); + void unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data); + // Drains the queue of pending vsync periods and dispatches refresh rate + // updates to callbacks. + // The assumption is that this method is only called on a single + // processing thread, either by looper or by AChoreographer_handleEvents + void handleRefreshRateUpdates(); + void scheduleLatestConfigRequest(); + + enum { + MSG_SCHEDULE_CALLBACKS = 0, + MSG_SCHEDULE_VSYNC = 1, + MSG_HANDLE_REFRESH_RATE_UPDATES = 2, + }; + virtual void handleMessage(const Message& message) override; + + static void initJVM(JNIEnv* env); + static 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); + int64_t getFrameInterval() const; + bool inCallback() const; + +private: + Choreographer(const Choreographer&) = delete; + + void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count, + VsyncEventData vsyncEventData) override; + void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override; + void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId, + nsecs_t vsyncPeriod) override; + void dispatchNullEvent(nsecs_t, PhysicalDisplayId) override; + void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId, + std::vector overrides) override; + + void scheduleCallbacks(); + + ChoreographerFrameCallbackDataImpl createFrameCallbackData(nsecs_t timestamp) const; + void registerStartTime() const; + + std::mutex mLock; + // Protected by mLock + std::priority_queue mFrameCallbacks; + std::vector mRefreshRateCallbacks; + + nsecs_t mLatestVsyncPeriod = -1; + VsyncEventData mLastVsyncEventData; + bool mInCallback = false; + + const sp mLooper; + const std::thread::id mThreadId; + + // Approximation of num_threads_using_choreographer * num_frames_of_history with leeway. + static constexpr size_t kMaxStartTimes = 250; +}; + +} // namespace android \ No newline at end of file -- GitLab From 884bf013f64f41af6daac71c7abc2dfff6ed465e Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Mon, 28 Nov 2022 15:15:13 -0800 Subject: [PATCH 0545/1310] SF: use SurfaceFlingerProperties for frame_rate_override_global This flag is accessed from DisplayManager in addition to SF, so add it to SurfaceFlingerProperties instead of hardcoded flag. Test: atest DisplayModeDirectorTests Bug: 241447632 Change-Id: I1951bc5e17ec0f6304ecb91aa83555b8cce5e73a --- services/surfaceflinger/SurfaceFlinger.cpp | 2 +- .../surfaceflinger/SurfaceFlingerProperties.cpp | 4 ++++ services/surfaceflinger/SurfaceFlingerProperties.h | 2 ++ .../sysprop/SurfaceFlingerProperties.sysprop | 14 ++++++++++++-- .../api/SurfaceFlingerProperties-current.txt | 4 ++++ 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 111b927507..0570c7314b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2830,7 +2830,7 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( return Config::FrameRateOverride::AppOverrideNativeRefreshRates; } - if (!base::GetBoolProperty("debug.sf.frame_rate_override_global"s, false)) { + if (!sysprop::frame_rate_override_global(false)) { return Config::FrameRateOverride::AppOverride; } diff --git a/services/surfaceflinger/SurfaceFlingerProperties.cpp b/services/surfaceflinger/SurfaceFlingerProperties.cpp index c8c71dfad1..5b7303090d 100644 --- a/services/surfaceflinger/SurfaceFlingerProperties.cpp +++ b/services/surfaceflinger/SurfaceFlingerProperties.cpp @@ -371,6 +371,10 @@ bool frame_rate_override_for_native_rates(bool defaultValue) { return SurfaceFlingerProperties::frame_rate_override_for_native_rates().value_or(defaultValue); } +bool frame_rate_override_global(bool defaultValue) { + return SurfaceFlingerProperties::frame_rate_override_global().value_or(defaultValue); +} + bool enable_layer_caching(bool defaultValue) { return SurfaceFlingerProperties::enable_layer_caching().value_or(defaultValue); } diff --git a/services/surfaceflinger/SurfaceFlingerProperties.h b/services/surfaceflinger/SurfaceFlingerProperties.h index 5e316cfa0e..09629cf93e 100644 --- a/services/surfaceflinger/SurfaceFlingerProperties.h +++ b/services/surfaceflinger/SurfaceFlingerProperties.h @@ -98,6 +98,8 @@ bool enable_frame_rate_override(bool defaultValue); bool frame_rate_override_for_native_rates(bool defaultValue); +bool frame_rate_override_global(bool defaultValue); + bool enable_layer_caching(bool defaultValue); bool enable_sdr_dimming(bool defaultValue); diff --git a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop index 28da81ff50..8540c3dcfc 100644 --- a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop +++ b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop @@ -447,8 +447,8 @@ prop { # Limits the frame rate override feature (enable_frame_rate_override) to override the refresh rate # to native display refresh rates only. Before introducing this flag, native display refresh rates -# was the default behvaiour. With this flag we can control which behaviour we want explicitly. -# This flag is intoruduced as a fail-safe machanism and planned to be defaulted to false. +# was the default behaviour. With this flag we can control which behaviour we want explicitly. +# This flag is introduced as a fail-safe mechanism and planned to be defaulted to false. prop { api_name: "frame_rate_override_for_native_rates" type: Boolean @@ -457,6 +457,16 @@ prop { prop_name: "ro.surface_flinger.frame_rate_override_for_native_rates" } +# Enables the frame rate override feature (enable_frame_rate_override) to +# override the frame rate globally instead of only for individual apps. +prop { + api_name: "frame_rate_override_global" + type: Boolean + scope: Public + access: Readonly + prop_name: "ro.surface_flinger.frame_rate_override_global" +} + # Enables Layer Caching prop { api_name: "enable_layer_caching" diff --git a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt index 0dfb80e5df..93381333a8 100644 --- a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt +++ b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt @@ -64,6 +64,10 @@ props { api_name: "frame_rate_override_for_native_rates" prop_name: "ro.surface_flinger.frame_rate_override_for_native_rates" } + prop { + api_name: "frame_rate_override_global" + prop_name: "ro.surface_flinger.frame_rate_override_global" + } prop { api_name: "has_HDR_display" prop_name: "ro.surface_flinger.has_HDR_display" -- GitLab From f76bba5f972328b778050aa46a149dd1e6f15018 Mon Sep 17 00:00:00 2001 From: Vladimir Komsiyski Date: Sun, 23 Oct 2022 10:56:06 +0200 Subject: [PATCH 0546/1310] Virtual device sensor support in sensor service. The virutal device sensors are runtime sensors, using handle range of [0x5F000000, 0x5FFFFFFFF]. As opposed to the HAL dynamic sensors, they do not rely on the existence of a SENSOR_TYPE_DYNAMIC_SENSOR_META. Instead of sensor events representing registration/unregistration of dynamic sensors, they are handled syncronously in the sensor service. The virtual dynamic sensors are not exposed via the dynamic sensors API. They are provided to SensorManager through a separate JNI call. Bug: 237278244 Test: atest cts/tests/sensor Change-Id: I09e5b089d1ae3bed7a25a5454a31aba9cf594a05 --- libs/sensor/ISensorServer.cpp | 32 +++++ libs/sensor/SensorManager.cpp | 13 ++ libs/sensor/include/sensor/ISensorServer.h | 1 + libs/sensor/include/sensor/SensorManager.h | 1 + services/sensorservice/SensorInterface.cpp | 36 +++++ services/sensorservice/SensorInterface.h | 26 ++++ services/sensorservice/SensorList.cpp | 28 +++- services/sensorservice/SensorList.h | 9 +- services/sensorservice/SensorService.cpp | 150 +++++++++++++++++++-- services/sensorservice/SensorService.h | 23 +++- 10 files changed, 296 insertions(+), 23 deletions(-) diff --git a/libs/sensor/ISensorServer.cpp b/libs/sensor/ISensorServer.cpp index 78f692bb0c..2278d391b5 100644 --- a/libs/sensor/ISensorServer.cpp +++ b/libs/sensor/ISensorServer.cpp @@ -42,6 +42,7 @@ enum { GET_DYNAMIC_SENSOR_LIST, CREATE_SENSOR_DIRECT_CONNECTION, SET_OPERATION_PARAMETER, + GET_RUNTIME_SENSOR_LIST, }; class BpSensorServer : public BpInterface @@ -90,6 +91,25 @@ public: return v; } + virtual Vector getRuntimeSensorList(const String16& opPackageName, int deviceId) + { + Parcel data, reply; + data.writeInterfaceToken(ISensorServer::getInterfaceDescriptor()); + data.writeString16(opPackageName); + data.writeInt32(deviceId); + remote()->transact(GET_RUNTIME_SENSOR_LIST, data, &reply); + Sensor s; + Vector v; + uint32_t n = reply.readUint32(); + v.setCapacity(n); + while (n) { + n--; + reply.read(s); + v.add(s); + } + return v; + } + virtual sp createSensorEventConnection(const String8& packageName, int mode, const String16& opPackageName, const String16& attributionTag) { @@ -194,6 +214,18 @@ status_t BnSensorServer::onTransact( } return NO_ERROR; } + case GET_RUNTIME_SENSOR_LIST: { + CHECK_INTERFACE(ISensorServer, data, reply); + const String16& opPackageName = data.readString16(); + const int deviceId = data.readInt32(); + Vector v(getRuntimeSensorList(opPackageName, deviceId)); + size_t n = v.size(); + reply->writeUint32(static_cast(n)); + for (size_t i = 0; i < n; i++) { + reply->write(v[i]); + } + return NO_ERROR; + } case CREATE_SENSOR_DIRECT_CONNECTION: { CHECK_INTERFACE(ISensorServer, data, reply); const String16& opPackageName = data.readString16(); diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp index 0ba9704263..27482768f2 100644 --- a/libs/sensor/SensorManager.cpp +++ b/libs/sensor/SensorManager.cpp @@ -201,6 +201,19 @@ ssize_t SensorManager::getDynamicSensorList(Vector & dynamicSensors) { return static_cast(count); } +ssize_t SensorManager::getRuntimeSensorList(int deviceId, Vector& runtimeSensors) { + Mutex::Autolock _l(mLock); + status_t err = assertStateLocked(); + if (err < 0) { + return static_cast(err); + } + + runtimeSensors = mSensorServer->getRuntimeSensorList(mOpPackageName, deviceId); + size_t count = runtimeSensors.size(); + + return static_cast(count); +} + ssize_t SensorManager::getDynamicSensorList(Sensor const* const** list) { Mutex::Autolock _l(mLock); status_t err = assertStateLocked(); diff --git a/libs/sensor/include/sensor/ISensorServer.h b/libs/sensor/include/sensor/ISensorServer.h index ce5c672da0..3295196ac4 100644 --- a/libs/sensor/include/sensor/ISensorServer.h +++ b/libs/sensor/include/sensor/ISensorServer.h @@ -43,6 +43,7 @@ public: virtual Vector getSensorList(const String16& opPackageName) = 0; virtual Vector getDynamicSensorList(const String16& opPackageName) = 0; + virtual Vector getRuntimeSensorList(const String16& opPackageName, int deviceId) = 0; virtual sp createSensorEventConnection(const String8& packageName, int mode, const String16& opPackageName, const String16& attributionTag) = 0; diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h index 8d0a8a45d9..0798da292a 100644 --- a/libs/sensor/include/sensor/SensorManager.h +++ b/libs/sensor/include/sensor/SensorManager.h @@ -59,6 +59,7 @@ public: ssize_t getSensorList(Sensor const* const** list); ssize_t getDynamicSensorList(Vector& list); ssize_t getDynamicSensorList(Sensor const* const** list); + ssize_t getRuntimeSensorList(int deviceId, Vector& list); Sensor const* getDefaultSensor(int type); sp createEventQueue( String8 packageName = String8(""), int mode = 0, String16 attributionTag = String16("")); diff --git a/services/sensorservice/SensorInterface.cpp b/services/sensorservice/SensorInterface.cpp index 46f00e8329..398cdf9a0e 100644 --- a/services/sensorservice/SensorInterface.cpp +++ b/services/sensorservice/SensorInterface.cpp @@ -87,6 +87,42 @@ VirtualSensor::VirtualSensor() : // --------------------------------------------------------------------------- +RuntimeSensor::RuntimeSensor(const sensor_t& sensor, sp callback) + : BaseSensor(sensor), mCallback(std::move(callback)) { +} + +status_t RuntimeSensor::activate(void*, bool enabled) { + if (enabled != mEnabled) { + mEnabled = enabled; + mCallback->onStateChanged(mEnabled, mSamplingPeriodNs, mBatchReportLatencyNs); + } + return OK; +} + +status_t RuntimeSensor::batch(void*, int, int, int64_t samplingPeriodNs, + int64_t maxBatchReportLatencyNs) { + if (mSamplingPeriodNs != samplingPeriodNs || mBatchReportLatencyNs != maxBatchReportLatencyNs) { + mSamplingPeriodNs = samplingPeriodNs; + mBatchReportLatencyNs = maxBatchReportLatencyNs; + if (mEnabled) { + mCallback->onStateChanged(mEnabled, mSamplingPeriodNs, mBatchReportLatencyNs); + } + } + return OK; +} + +status_t RuntimeSensor::setDelay(void*, int, int64_t ns) { + if (mSamplingPeriodNs != ns) { + mSamplingPeriodNs = ns; + if (mEnabled) { + mCallback->onStateChanged(mEnabled, mSamplingPeriodNs, mBatchReportLatencyNs); + } + } + return OK; +} + +// --------------------------------------------------------------------------- + ProximitySensor::ProximitySensor(const sensor_t& sensor, SensorService& service) : HardwareSensor(sensor), mSensorService(service) { } diff --git a/services/sensorservice/SensorInterface.h b/services/sensorservice/SensorInterface.h index 57043592c5..5ee5e1224a 100644 --- a/services/sensorservice/SensorInterface.h +++ b/services/sensorservice/SensorInterface.h @@ -104,6 +104,32 @@ protected: // --------------------------------------------------------------------------- +class RuntimeSensor : public BaseSensor { +public: + static constexpr int DEFAULT_DEVICE_ID = 0; + + class StateChangeCallback : public virtual RefBase { + public: + virtual void onStateChanged(bool enabled, int64_t samplingPeriodNs, + int64_t batchReportLatencyNs) = 0; + }; + RuntimeSensor(const sensor_t& sensor, sp callback); + virtual status_t activate(void* ident, bool enabled) override; + virtual status_t batch(void* ident, int handle, int flags, int64_t samplingPeriodNs, + int64_t maxBatchReportLatencyNs) override; + virtual status_t setDelay(void* ident, int handle, int64_t ns) override; + virtual bool process(sensors_event_t*, const sensors_event_t&) { return false; } + virtual bool isVirtual() const override { return false; } + +private: + bool mEnabled = false; + int64_t mSamplingPeriodNs = 0; + int64_t mBatchReportLatencyNs = 0; + sp mCallback; +}; + +// --------------------------------------------------------------------------- + class ProximitySensor : public HardwareSensor { public: explicit ProximitySensor(const sensor_t& sensor, SensorService& service); diff --git a/services/sensorservice/SensorList.cpp b/services/sensorservice/SensorList.cpp index 85ce0f0018..6d36b4789b 100644 --- a/services/sensorservice/SensorList.cpp +++ b/services/sensorservice/SensorList.cpp @@ -29,12 +29,12 @@ namespace SensorServiceUtil { const Sensor SensorList::mNonSensor = Sensor("unknown"); bool SensorList::add( - int handle, SensorInterface* si, bool isForDebug, bool isVirtual) { + int handle, SensorInterface* si, bool isForDebug, bool isVirtual, int deviceId) { std::lock_guard lk(mLock); if (handle == si->getSensor().getHandle() && mUsedHandle.insert(handle).second) { // will succeed as the mUsedHandle does not have this handle - mHandleMap.emplace(handle, Entry(si, isForDebug, isVirtual)); + mHandleMap.emplace(handle, Entry(si, isForDebug, isVirtual, deviceId)); return true; } // handle exist already or handle mismatch @@ -79,7 +79,8 @@ const Vector SensorList::getUserSensors() const { Vector sensors; forEachEntry( [&sensors] (const Entry& e) -> bool { - if (!e.isForDebug && !e.si->getSensor().isDynamicSensor()) { + if (!e.isForDebug && !e.si->getSensor().isDynamicSensor() + && e.deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) { sensors.add(e.si->getSensor()); } return true; @@ -92,7 +93,8 @@ const Vector SensorList::getUserDebugSensors() const { Vector sensors; forEachEntry( [&sensors] (const Entry& e) -> bool { - if (!e.si->getSensor().isDynamicSensor()) { + if (!e.si->getSensor().isDynamicSensor() + && e.deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) { sensors.add(e.si->getSensor()); } return true; @@ -105,7 +107,8 @@ const Vector SensorList::getDynamicSensors() const { Vector sensors; forEachEntry( [&sensors] (const Entry& e) -> bool { - if (!e.isForDebug && e.si->getSensor().isDynamicSensor()) { + if (!e.isForDebug && e.si->getSensor().isDynamicSensor() + && e.deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) { sensors.add(e.si->getSensor()); } return true; @@ -118,7 +121,20 @@ const Vector SensorList::getVirtualSensors() const { Vector sensors; forEachEntry( [&sensors] (const Entry& e) -> bool { - if (e.isVirtual) { + if (e.isVirtual && e.deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) { + sensors.add(e.si->getSensor()); + } + return true; + }); + return sensors; +} + +const Vector SensorList::getRuntimeSensors(int deviceId) const { + // lock in forEachEntry + Vector sensors; + forEachEntry( + [&sensors, deviceId] (const Entry& e) -> bool { + if (!e.isForDebug && e.deviceId == deviceId) { sensors.add(e.si->getSensor()); } return true; diff --git a/services/sensorservice/SensorList.h b/services/sensorservice/SensorList.h index 049ae7c855..79f6701922 100644 --- a/services/sensorservice/SensorList.h +++ b/services/sensorservice/SensorList.h @@ -40,14 +40,16 @@ public: sp si; const bool isForDebug; const bool isVirtual; - Entry(SensorInterface* si_, bool debug_, bool virtual_) : - si(si_), isForDebug(debug_), isVirtual(virtual_) { + const int deviceId; + Entry(SensorInterface* si_, bool debug_, bool virtual_, int deviceId_) : + si(si_), isForDebug(debug_), isVirtual(virtual_), deviceId(deviceId_) { } }; // After SensorInterface * is added into SensorList, it can be assumed that SensorList own the // object it pointed to and the object should not be released elsewhere. - bool add(int handle, SensorInterface* si, bool isForDebug = false, bool isVirtual = false); + bool add(int handle, SensorInterface* si, bool isForDebug = false, bool isVirtual = false, + int deviceId = RuntimeSensor::DEFAULT_DEVICE_ID); // After a handle is removed, the object that SensorInterface * pointing to may get deleted if // no more sp<> of the same object exist. @@ -60,6 +62,7 @@ public: const Vector getUserDebugSensors() const; const Vector getDynamicSensors() const; const Vector getVirtualSensors() const; + const Vector getRuntimeSensors(int deviceId) const; String8 getName(int handle) const; String8 getStringType(int handle) const; diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp index 21d6b6b16f..0c9fef5652 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -102,6 +102,33 @@ static const String16 sDumpPermission("android.permission.DUMP"); static const String16 sLocationHardwarePermission("android.permission.LOCATION_HARDWARE"); static const String16 sManageSensorsPermission("android.permission.MANAGE_SENSORS"); +namespace { + +// TODO(b/259227294): Move the sensor ranges to the HAL. +int32_t nextRuntimeSensorHandle() { + static constexpr int32_t kRuntimeHandleBase = 0x5F000000; + static constexpr int32_t kRuntimeHandleEnd = 0x5FFFFFFF; + static int32_t nextHandle = kRuntimeHandleBase; + if (nextHandle == kRuntimeHandleEnd) { + return -1; + } + return nextHandle++; +} + +class RuntimeSensorCallbackProxy : public RuntimeSensor::StateChangeCallback { + public: + RuntimeSensorCallbackProxy(sp callback) + : mCallback(std::move(callback)) {} + void onStateChanged(bool enabled, int64_t samplingPeriodNs, + int64_t batchReportLatencyNs) override { + mCallback->onStateChanged(enabled, samplingPeriodNs, batchReportLatencyNs); + } + private: + sp mCallback; +}; + +} // namespace + static bool isAutomotive() { sp serviceManager = defaultServiceManager(); if (serviceManager.get() == nullptr) { @@ -137,6 +164,60 @@ SensorService::SensorService() mMicSensorPrivacyPolicy = new MicrophonePrivacyPolicy(this); } +int SensorService::registerRuntimeSensor( + const sensor_t& sensor, int deviceId, sp callback) { + int handle = 0; + while (handle == 0 || !mSensors.isNewHandle(handle)) { + handle = nextRuntimeSensorHandle(); + if (handle < 0) { + // Ran out of the dedicated range for runtime sensors. + return handle; + } + } + + ALOGI("Registering runtime sensor handle 0x%x, type %d, name %s", + handle, sensor.type, sensor.name); + + sp runtimeSensorCallback( + new RuntimeSensorCallbackProxy(std::move(callback))); + sensor_t runtimeSensor = sensor; + // force the handle to be consistent + runtimeSensor.handle = handle; + SensorInterface *si = new RuntimeSensor(runtimeSensor, std::move(runtimeSensorCallback)); + + Mutex::Autolock _l(mLock); + const Sensor& s = registerSensor(si, /* isDebug= */ false, /* isVirtual= */ false, deviceId); + + if (s.getHandle() != handle) { + // The registration was unsuccessful. + return s.getHandle(); + } + return handle; +} + +status_t SensorService::unregisterRuntimeSensor(int handle) { + ALOGI("Unregistering runtime sensor handle 0x%x disconnected", handle); + { + Mutex::Autolock _l(mLock); + if (!unregisterDynamicSensorLocked(handle)) { + ALOGE("Runtime sensor release error."); + return UNKNOWN_ERROR; + } + } + + ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock); + for (const sp& connection : connLock.getActiveConnections()) { + connection->removeSensor(handle); + } + return OK; +} + +status_t SensorService::sendRuntimeSensorEvent(const sensors_event_t& event) { + Mutex::Autolock _l(mLock); + mRuntimeSensorEventQueue.push(event); + return OK; +} + bool SensorService::initializeHmacKey() { int fd = open(SENSOR_SERVICE_HMAC_KEY_FILE, O_RDONLY|O_CLOEXEC); if (fd != -1) { @@ -407,10 +488,11 @@ bool SensorService::hasSensorAccessLocked(uid_t uid, const String16& opPackageNa && isUidActive(uid) && !isOperationRestrictedLocked(opPackageName); } -const Sensor& SensorService::registerSensor(SensorInterface* s, bool isDebug, bool isVirtual) { +const Sensor& SensorService::registerSensor(SensorInterface* s, bool isDebug, bool isVirtual, + int deviceId) { int handle = s->getSensor().getHandle(); int type = s->getSensor().getType(); - if (mSensors.add(handle, s, isDebug, isVirtual)){ + if (mSensors.add(handle, s, isDebug, isVirtual, deviceId)) { mRecentEvent.emplace(handle, new SensorServiceUtil::RecentEventLogger(type)); return s->getSensor(); } else { @@ -1003,6 +1085,7 @@ bool SensorService::threadLoop() { recordLastValueLocked(mSensorEventBuffer, count); // handle virtual sensors + bool bufferNeedsSorting = false; if (count && vcount) { sensors_event_t const * const event = mSensorEventBuffer; if (!mActiveVirtualSensors.empty()) { @@ -1038,12 +1121,37 @@ bool SensorService::threadLoop() { // record the last synthesized values recordLastValueLocked(&mSensorEventBuffer[count], k); count += k; - // sort the buffer by time-stamps - sortEventBuffer(mSensorEventBuffer, count); + bufferNeedsSorting = true; } } } + // handle runtime sensors + { + size_t k = 0; + while (!mRuntimeSensorEventQueue.empty()) { + if (count + k >= minBufferSize) { + ALOGE("buffer too small to hold all events: count=%zd, k=%zu, size=%zu", + count, k, minBufferSize); + break; + } + mSensorEventBuffer[count + k] = mRuntimeSensorEventQueue.front(); + mRuntimeSensorEventQueue.pop(); + k++; + } + if (k) { + // record the last synthesized values + recordLastValueLocked(&mSensorEventBuffer[count], k); + count += k; + bufferNeedsSorting = true; + } + } + + if (bufferNeedsSorting) { + // sort the buffer by time-stamps + sortEventBuffer(mSensorEventBuffer, count); + } + // handle backward compatibility for RotationVector sensor if (halVersion < SENSORS_DEVICE_API_VERSION_1_0) { for (int i = 0; i < count; i++) { @@ -1342,19 +1450,37 @@ Vector SensorService::getSensorList(const String16& opPackageName) { return accessibleSensorList; } +void SensorService::addSensorIfAccessible(const String16& opPackageName, const Sensor& sensor, + Vector& accessibleSensorList) { + if (canAccessSensor(sensor, "can't see", opPackageName)) { + accessibleSensorList.add(sensor); + } else if (sensor.getType() != SENSOR_TYPE_HEAD_TRACKER) { + ALOGI("Skipped sensor %s because it requires permission %s and app op %" PRId32, + sensor.getName().string(), sensor.getRequiredPermission().string(), + sensor.getRequiredAppOp()); + } +} + Vector SensorService::getDynamicSensorList(const String16& opPackageName) { Vector accessibleSensorList; mSensors.forEachSensor( [this, &opPackageName, &accessibleSensorList] (const Sensor& sensor) -> bool { if (sensor.isDynamicSensor()) { - if (canAccessSensor(sensor, "can't see", opPackageName)) { - accessibleSensorList.add(sensor); - } else if (sensor.getType() != SENSOR_TYPE_HEAD_TRACKER) { - ALOGI("Skipped sensor %s because it requires permission %s and app op %" PRId32, - sensor.getName().string(), - sensor.getRequiredPermission().string(), - sensor.getRequiredAppOp()); - } + addSensorIfAccessible(opPackageName, sensor, accessibleSensorList); + } + return true; + }); + makeUuidsIntoIdsForSensorList(accessibleSensorList); + return accessibleSensorList; +} + +Vector SensorService::getRuntimeSensorList(const String16& opPackageName, int deviceId) { + Vector accessibleSensorList; + mSensors.forEachEntry( + [this, &opPackageName, deviceId, &accessibleSensorList] ( + const SensorServiceUtil::SensorList::Entry& e) -> bool { + if (e.deviceId == deviceId) { + addSensorIfAccessible(opPackageName, e.si->getSensor(), accessibleSensorList); } return true; }); diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h index 4ba3c51985..e4903987bc 100644 --- a/services/sensorservice/SensorService.h +++ b/services/sensorservice/SensorService.h @@ -42,6 +42,7 @@ #include #include +#include #include #include #include @@ -143,6 +144,14 @@ public: virtual void onProximityActive(bool isActive) = 0; }; + class RuntimeSensorStateChangeCallback : public virtual RefBase { + public: + // Note that the callback is invoked from an async thread and can interact with the + // SensorService directly. + virtual void onStateChanged(bool enabled, int64_t samplingPeriodNanos, + int64_t batchReportLatencyNanos) = 0; + }; + static char const* getServiceName() ANDROID_API { return "sensorservice"; } SensorService() ANDROID_API; @@ -169,6 +178,11 @@ public: status_t addProximityActiveListener(const sp& callback) ANDROID_API; status_t removeProximityActiveListener(const sp& callback) ANDROID_API; + int registerRuntimeSensor(const sensor_t& sensor, int deviceId, + sp callback) ANDROID_API; + status_t unregisterRuntimeSensor(int handle) ANDROID_API; + status_t sendRuntimeSensorEvent(const sensors_event_t& event) ANDROID_API; + // Returns true if a sensor should be throttled according to our rate-throttling rules. static bool isSensorInCappedSet(int sensorType); @@ -346,6 +360,7 @@ private: // ISensorServer interface virtual Vector getSensorList(const String16& opPackageName); virtual Vector getDynamicSensorList(const String16& opPackageName); + virtual Vector getRuntimeSensorList(const String16& opPackageName, int deviceId); virtual sp createSensorEventConnection( const String8& packageName, int requestedMode, const String16& opPackageName, const String16& attributionTag); @@ -364,8 +379,9 @@ private: bool isWakeUpSensor(int type) const; void recordLastValueLocked(sensors_event_t const* buffer, size_t count); static void sortEventBuffer(sensors_event_t* buffer, size_t count); - const Sensor& registerSensor(SensorInterface* sensor, - bool isDebug = false, bool isVirtual = false); + const Sensor& registerSensor(SensorInterface* sensor, bool isDebug = false, + bool isVirtual = false, + int deviceId = RuntimeSensor::DEFAULT_DEVICE_ID); const Sensor& registerVirtualSensor(SensorInterface* sensor, bool isDebug = false); const Sensor& registerDynamicSensorLocked(SensorInterface* sensor, bool isDebug = false); bool unregisterDynamicSensorLocked(int handle); @@ -375,6 +391,8 @@ private: sensors_event_t const* buffer, const int count); bool canAccessSensor(const Sensor& sensor, const char* operation, const String16& opPackageName); + void addSensorIfAccessible(const String16& opPackageName, const Sensor& sensor, + Vector& accessibleSensorList); static bool hasPermissionForSensor(const Sensor& sensor); static int getTargetSdkVersion(const String16& opPackageName); static void resetTargetSdkVersionCache(const String16& opPackageName); @@ -492,6 +510,7 @@ private: wp * mMapFlushEventsToConnections; std::unordered_map mRecentEvent; Mode mCurrentOperatingMode; + std::queue mRuntimeSensorEventQueue; // true if the head tracker sensor type is currently restricted to system usage only // (can only be unrestricted for testing, via shell cmd) -- GitLab From 0833890d7bc5b18675f5704da5a3995ddd91c708 Mon Sep 17 00:00:00 2001 From: Sandro Meier Date: Mon, 7 Nov 2022 10:27:23 +0000 Subject: [PATCH 0547/1310] Fix documentation of setInTouchMode Touchmode can be set per display. The documentation still stated that touch mode is a global state. Test: None, documentation change only Change-Id: I0b4c0860f971f9d175378ff1ae4267291766b2b5 --- .../dispatcher/include/InputDispatcherInterface.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h index 484b0d3b7f..76dce63aac 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h @@ -125,9 +125,12 @@ public: /** * Set the touch mode state. - * Touch mode is a global state that apps may enter / exit based on specific - * user interactions with input devices. - * If true, the device is in touch mode. + * Touch mode is a per display state that apps may enter / exit based on specific user + * interactions with input devices. If inTouchMode is set to true, the display + * identified by displayId will be changed to touch mode. Performs a permission + * check if hasPermission is set to false. + * + * This method also enqueues a a TouchModeEntry message for dispatching. * * Returns true when changing touch mode state. */ -- GitLab From c5e1ca43626c61806be159577057ac03ed3368ce Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 28 Nov 2022 22:01:56 +0000 Subject: [PATCH 0548/1310] Build input reader as a static library Bug: 251196347 Test: Build Change-Id: I22ab6d1e4cb019ed7952d083146c67a7a2952826 --- services/inputflinger/reader/Android.bp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index 24168a12a6..cf020161cc 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -79,6 +79,7 @@ cc_defaults { ], header_libs: [ "libbatteryservice_headers", + "libchrome-gestures_headers", "libinputreader_headers", ], target: { @@ -97,6 +98,22 @@ cc_defaults { }, } +cc_library_static { + name: "libinputreader_static", + defaults: [ + "inputflinger_defaults", + "libinputreader_defaults", + ], + shared_libs: [ + "libinputflinger_base", + ], + export_header_lib_headers: [ + "libbatteryservice_headers", + "libchrome-gestures_headers", + "libinputreader_headers", + ], +} + cc_library_shared { name: "libinputreader", host_supported: true, -- GitLab From 7ddbc952e9f1d4cc600ad3bb4ccc5064c580f916 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 9 Nov 2022 22:03:40 +0000 Subject: [PATCH 0549/1310] TouchInputMapper: Use ui::Size and Rect for display info There should be no behavior changes in this CL. Bug: 236798672 Test: atest inputflinger_tests Change-Id: Iba3bd7280ca9c3dddd224efef42b8b00b6f2bab8 --- .../reader/mapper/TouchInputMapper.cpp | 96 +++++++++---------- .../reader/mapper/TouchInputMapper.h | 14 +-- 2 files changed, 49 insertions(+), 61 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 5be694f35f..de1ed01495 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -42,6 +42,19 @@ static const float MIN_FREEFORM_GESTURE_WIDTH_IN_MILLIMETER = 30; static const DisplayViewport kUninitializedViewport; +static std::string toString(const Rect& rect) { + return base::StringPrintf("Rect{%d, %d, %d, %d}", rect.left, rect.top, rect.right, rect.bottom); +} + +static std::string toString(const ui::Size& size) { + return base::StringPrintf("%dx%d", size.width, size.height); +} + +static bool isPointInRect(const Rect& rect, int32_t x, int32_t y) { + // Consider all four sides as "inclusive". + return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom; +} + template inline static void swap(T& a, T& b) { T temp = a; @@ -93,12 +106,6 @@ TouchInputMapper::TouchInputMapper(InputDeviceContext& deviceContext) mTouchButtonAccumulator(deviceContext), mSource(0), mDeviceMode(DeviceMode::DISABLED), - mDisplayWidth(-1), - mDisplayHeight(-1), - mPhysicalWidth(-1), - mPhysicalHeight(-1), - mPhysicalLeft(0), - mPhysicalTop(0), mInputDeviceOrientation(DISPLAY_ORIENTATION_0) {} TouchInputMapper::~TouchInputMapper() {} @@ -564,7 +571,7 @@ void TouchInputMapper::initializeSizeRanges() { } // Size of diagonal axis. - const float diagonalSize = hypotf(mDisplayWidth, mDisplayHeight); + const float diagonalSize = hypotf(mDisplayBounds.width, mDisplayBounds.height); // Size factors. if (mRawPointerAxes.touchMajor.valid && mRawPointerAxes.touchMajor.maxValue != 0) { @@ -647,8 +654,8 @@ void TouchInputMapper::initializeSizeRanges() { void TouchInputMapper::initializeOrientedRanges() { // Configure X and Y factors. - mXScale = float(mDisplayWidth) / mRawPointerAxes.getRawWidth(); - mYScale = float(mDisplayHeight) / mRawPointerAxes.getRawHeight(); + mXScale = float(mDisplayBounds.width) / mRawPointerAxes.getRawWidth(); + mYScale = float(mDisplayBounds.height) / mRawPointerAxes.getRawHeight(); mXPrecision = 1.0f / mXScale; mYPrecision = 1.0f / mYScale; @@ -784,13 +791,13 @@ void TouchInputMapper::initializeOrientedRanges() { mOrientedYPrecision = mXPrecision; mOrientedRanges.x.min = 0; - mOrientedRanges.x.max = mDisplayHeight - 1; + mOrientedRanges.x.max = mDisplayBounds.height - 1; mOrientedRanges.x.flat = 0; mOrientedRanges.x.fuzz = 0; mOrientedRanges.x.resolution = mRawPointerAxes.y.resolution * mYScale; mOrientedRanges.y.min = 0; - mOrientedRanges.y.max = mDisplayWidth - 1; + mOrientedRanges.y.max = mDisplayBounds.width - 1; mOrientedRanges.y.flat = 0; mOrientedRanges.y.fuzz = 0; mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mXScale; @@ -801,13 +808,13 @@ void TouchInputMapper::initializeOrientedRanges() { mOrientedYPrecision = mYPrecision; mOrientedRanges.x.min = 0; - mOrientedRanges.x.max = mDisplayWidth - 1; + mOrientedRanges.x.max = mDisplayBounds.width - 1; mOrientedRanges.x.flat = 0; mOrientedRanges.x.fuzz = 0; mOrientedRanges.x.resolution = mRawPointerAxes.x.resolution * mXScale; mOrientedRanges.y.min = 0; - mOrientedRanges.y.max = mDisplayHeight - 1; + mOrientedRanges.y.max = mDisplayBounds.height - 1; mOrientedRanges.y.flat = 0; mOrientedRanges.y.fuzz = 0; mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mYScale; @@ -868,8 +875,7 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) } // Raw width and height in the natural orientation. - const int32_t rawWidth = mRawPointerAxes.getRawWidth(); - const int32_t rawHeight = mRawPointerAxes.getRawHeight(); + const ui::Size rawSize{mRawPointerAxes.getRawWidth(), mRawPointerAxes.getRawHeight()}; const int32_t rawXResolution = mRawPointerAxes.x.resolution; const int32_t rawYResolution = mRawPointerAxes.y.resolution; // Calculate the mean resolution when both x and y resolution are set, otherwise set it to 0. @@ -937,15 +943,12 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) naturalPhysicalWidth = naturalPhysicalWidth == 0 ? 1 : naturalPhysicalWidth; } - mPhysicalWidth = naturalPhysicalWidth; - mPhysicalHeight = naturalPhysicalHeight; - mPhysicalLeft = naturalPhysicalLeft; - mPhysicalTop = naturalPhysicalTop; + mPhysicalFrameInDisplay = Rect{naturalPhysicalLeft, naturalPhysicalTop, + naturalPhysicalLeft + naturalPhysicalWidth, + naturalPhysicalTop + naturalPhysicalHeight}; - const int32_t oldDisplayWidth = mDisplayWidth; - const int32_t oldDisplayHeight = mDisplayHeight; - mDisplayWidth = naturalDeviceWidth; - mDisplayHeight = naturalDeviceHeight; + const auto oldDisplayBounds = mDisplayBounds; + mDisplayBounds = ui::Size{naturalDeviceWidth, naturalDeviceHeight}; // InputReader works in the un-rotated display coordinate space, so we don't need to do // anything if the device is already orientation-aware. If the device is not @@ -958,20 +961,14 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) // For orientation-aware devices that work in the un-rotated coordinate space, the // viewport update should be skipped if it is only a change in the orientation. skipViewportUpdate = !viewportDisplayIdChanged && mParameters.orientationAware && - mDisplayWidth == oldDisplayWidth && mDisplayHeight == oldDisplayHeight && - viewportOrientationChanged; + mDisplayBounds == oldDisplayBounds && viewportOrientationChanged; // Apply the input device orientation for the device. mInputDeviceOrientation = (mInputDeviceOrientation + static_cast(mParameters.orientation)) % 4; } else { - mPhysicalWidth = rawWidth; - mPhysicalHeight = rawHeight; - mPhysicalLeft = 0; - mPhysicalTop = 0; - - mDisplayWidth = rawWidth; - mDisplayHeight = rawHeight; + mDisplayBounds = rawSize; + mPhysicalFrameInDisplay = Rect{mDisplayBounds}; mInputDeviceOrientation = DISPLAY_ORIENTATION_0; } } @@ -1003,9 +1000,9 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) } if ((viewportChanged && !skipViewportUpdate) || deviceModeChanged) { - ALOGI("Device reconfigured: id=%d, name='%s', size %dx%d, orientation %d, mode %d, " + ALOGI("Device reconfigured: id=%d, name='%s', size %s, orientation %d, mode %d, " "display id %d", - getDeviceId(), getDeviceName().c_str(), mDisplayWidth, mDisplayHeight, + getDeviceId(), getDeviceName().c_str(), toString(mDisplayBounds).c_str(), mInputDeviceOrientation, mDeviceMode, mViewport.displayId); configureVirtualKeys(); @@ -1017,8 +1014,8 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) if (mDeviceMode == DeviceMode::POINTER) { // Compute pointer gesture detection parameters. - float rawDiagonal = hypotf(rawWidth, rawHeight); - float displayDiagonal = hypotf(mDisplayWidth, mDisplayHeight); + float rawDiagonal = hypotf(rawSize.width, rawSize.height); + float displayDiagonal = hypotf(mDisplayBounds.width, mDisplayBounds.height); // Scale movements such that one whole swipe of the touch pad covers a // given area relative to the diagonal size of the display when no acceleration @@ -1054,12 +1051,8 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) void TouchInputMapper::dumpDisplay(std::string& dump) { dump += StringPrintf(INDENT3 "%s\n", mViewport.toString().c_str()); - dump += StringPrintf(INDENT3 "DisplayWidth: %dpx\n", mDisplayWidth); - dump += StringPrintf(INDENT3 "DisplayHeight: %dpx\n", mDisplayHeight); - dump += StringPrintf(INDENT3 "PhysicalWidth: %dpx\n", mPhysicalWidth); - dump += StringPrintf(INDENT3 "PhysicalHeight: %dpx\n", mPhysicalHeight); - dump += StringPrintf(INDENT3 "PhysicalLeft: %d\n", mPhysicalLeft); - dump += StringPrintf(INDENT3 "PhysicalTop: %d\n", mPhysicalTop); + dump += StringPrintf(INDENT3 "DisplayBounds: %s\n", toString(mDisplayBounds).c_str()); + dump += StringPrintf(INDENT3 "PhysicalFrame: %s\n", toString(mPhysicalFrameInDisplay).c_str()); dump += StringPrintf(INDENT3 "InputDeviceOrientation: %d\n", mInputDeviceOrientation); } @@ -1098,17 +1091,17 @@ void TouchInputMapper::configureVirtualKeys() { int32_t halfWidth = virtualKeyDefinition.width / 2; int32_t halfHeight = virtualKeyDefinition.height / 2; - virtualKey.hitLeft = - (virtualKeyDefinition.centerX - halfWidth) * touchScreenWidth / mDisplayWidth + + virtualKey.hitLeft = (virtualKeyDefinition.centerX - halfWidth) * touchScreenWidth / + mDisplayBounds.width + touchScreenLeft; - virtualKey.hitRight = - (virtualKeyDefinition.centerX + halfWidth) * touchScreenWidth / mDisplayWidth + + virtualKey.hitRight = (virtualKeyDefinition.centerX + halfWidth) * touchScreenWidth / + mDisplayBounds.width + touchScreenLeft; - virtualKey.hitTop = - (virtualKeyDefinition.centerY - halfHeight) * touchScreenHeight / mDisplayHeight + + virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight) * touchScreenHeight / + mDisplayBounds.height + touchScreenTop; - virtualKey.hitBottom = - (virtualKeyDefinition.centerY + halfHeight) * touchScreenHeight / mDisplayHeight + + virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight) * touchScreenHeight / + mDisplayBounds.height + touchScreenTop; mVirtualKeys.push_back(virtualKey); } @@ -3859,9 +3852,8 @@ bool TouchInputMapper::isPointInsidePhysicalFrame(int32_t x, int32_t y) const { const float yScaled = (y - mRawPointerAxes.y.minValue) * mYScale; return x >= mRawPointerAxes.x.minValue && x <= mRawPointerAxes.x.maxValue && - xScaled >= mPhysicalLeft && xScaled <= (mPhysicalLeft + mPhysicalWidth) && y >= mRawPointerAxes.y.minValue && y <= mRawPointerAxes.y.maxValue && - yScaled >= mPhysicalTop && yScaled <= (mPhysicalTop + mPhysicalHeight); + isPointInRect(mPhysicalFrameInDisplay, xScaled, yScaled); } const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHit(int32_t x, int32_t y) { diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 788ec586e2..769caf0c83 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -412,17 +412,13 @@ private: // The components of the viewport are specified in the display's rotated orientation. DisplayViewport mViewport; - // The width and height are obtained from the viewport and are specified - // in the natural orientation. - int32_t mDisplayWidth; - int32_t mDisplayHeight; + // The display size obtained from the viewport in the natural orientation. + // Always starts at (0, 0). + ui::Size mDisplayBounds{ui::kInvalidSize}; - // The physical frame is the rectangle in the display's coordinate space that maps to the + // The physical frame is the rectangle in the display's coordinate space that maps to // the logical display frame. - int32_t mPhysicalWidth; - int32_t mPhysicalHeight; - int32_t mPhysicalLeft; - int32_t mPhysicalTop; + Rect mPhysicalFrameInDisplay{Rect::INVALID_RECT}; // The orientation of the input device relative to that of the display panel. It specifies // the rotation of the input device coordinates required to produce the display panel -- GitLab From 2d613f41c742f70fe73e2f476183ee79b538732b Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 10 Nov 2022 20:22:06 +0000 Subject: [PATCH 0550/1310] TouchInputMapper: Use ui::Transform to calculate natural display info There should be no behavior changes in this CL. Bug: 236798672 Test: atest inputflinger_tests Change-Id: Iedde603fc35690046e5bdd26e07ec12d52077688 --- .../reader/mapper/TouchInputMapper.cpp | 81 +++++++------------ .../reader/mapper/TouchInputMapper.h | 5 +- 2 files changed, 33 insertions(+), 53 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index de1ed01495..5631a10253 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -80,6 +80,33 @@ inline static int32_t signExtendNybble(int32_t value) { return value >= 8 ? value - 16 : value; } +static std::tuple getNaturalDisplayInfo( + const DisplayViewport& viewport, int32_t naturalOrientation) { + const auto rotation = ui::toRotation(naturalOrientation); + + ui::Size rotatedDisplaySize{viewport.deviceWidth, viewport.deviceHeight}; + if (rotation == ui::ROTATION_90 || rotation == ui::ROTATION_270) { + std::swap(rotatedDisplaySize.width, rotatedDisplaySize.height); + } + + ui::Transform rotate(ui::Transform::toRotationFlags(rotation), rotatedDisplaySize.width, + rotatedDisplaySize.height); + + Rect physicalFrame{viewport.physicalLeft, viewport.physicalTop, viewport.physicalRight, + viewport.physicalBottom}; + physicalFrame = rotate.transform(physicalFrame); + + LOG_ALWAYS_FATAL_IF(!physicalFrame.isValid()); + if (physicalFrame.isEmpty()) { + ALOGE("Viewport is not set properly: %s", viewport.toString().c_str()); + physicalFrame.right = + physicalFrame.left + (physicalFrame.width() == 0 ? 1 : physicalFrame.width()); + physicalFrame.bottom = + physicalFrame.top + (physicalFrame.height() == 0 ? 1 : physicalFrame.height()); + } + return {rotatedDisplaySize, physicalFrame}; +} + // --- RawPointerData --- void RawPointerData::getCentroidOfTouchingPointers(float* outX, float* outY) const { @@ -891,64 +918,16 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) mViewport = newViewport; if (mDeviceMode == DeviceMode::DIRECT || mDeviceMode == DeviceMode::POINTER) { - // Convert rotated viewport to the natural orientation. - int32_t naturalPhysicalWidth, naturalPhysicalHeight; - int32_t naturalPhysicalLeft, naturalPhysicalTop; - int32_t naturalDeviceWidth, naturalDeviceHeight; + const auto oldDisplayBounds = mDisplayBounds; // Apply the inverse of the input device orientation so that the input device is // configured in the same orientation as the viewport. The input device orientation will // be re-applied by mInputDeviceOrientation. const int32_t naturalDeviceOrientation = (mViewport.orientation - static_cast(mParameters.orientation) + 4) % 4; - switch (naturalDeviceOrientation) { - case DISPLAY_ORIENTATION_90: - naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop; - naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft; - naturalPhysicalLeft = mViewport.deviceHeight - mViewport.physicalBottom; - naturalPhysicalTop = mViewport.physicalLeft; - naturalDeviceWidth = mViewport.deviceHeight; - naturalDeviceHeight = mViewport.deviceWidth; - break; - case DISPLAY_ORIENTATION_180: - naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft; - naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop; - naturalPhysicalLeft = mViewport.deviceWidth - mViewport.physicalRight; - naturalPhysicalTop = mViewport.deviceHeight - mViewport.physicalBottom; - naturalDeviceWidth = mViewport.deviceWidth; - naturalDeviceHeight = mViewport.deviceHeight; - break; - case DISPLAY_ORIENTATION_270: - naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop; - naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft; - naturalPhysicalLeft = mViewport.physicalTop; - naturalPhysicalTop = mViewport.deviceWidth - mViewport.physicalRight; - naturalDeviceWidth = mViewport.deviceHeight; - naturalDeviceHeight = mViewport.deviceWidth; - break; - case DISPLAY_ORIENTATION_0: - default: - naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft; - naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop; - naturalPhysicalLeft = mViewport.physicalLeft; - naturalPhysicalTop = mViewport.physicalTop; - naturalDeviceWidth = mViewport.deviceWidth; - naturalDeviceHeight = mViewport.deviceHeight; - break; - } - if (naturalPhysicalHeight == 0 || naturalPhysicalWidth == 0) { - ALOGE("Viewport is not set properly: %s", mViewport.toString().c_str()); - naturalPhysicalHeight = naturalPhysicalHeight == 0 ? 1 : naturalPhysicalHeight; - naturalPhysicalWidth = naturalPhysicalWidth == 0 ? 1 : naturalPhysicalWidth; - } - - mPhysicalFrameInDisplay = Rect{naturalPhysicalLeft, naturalPhysicalTop, - naturalPhysicalLeft + naturalPhysicalWidth, - naturalPhysicalTop + naturalPhysicalHeight}; - - const auto oldDisplayBounds = mDisplayBounds; - mDisplayBounds = ui::Size{naturalDeviceWidth, naturalDeviceHeight}; + std::tie(mDisplayBounds, mPhysicalFrameInDisplay) = + getNaturalDisplayInfo(mViewport, naturalDeviceOrientation); // InputReader works in the un-rotated display coordinate space, so we don't need to do // anything if the device is already orientation-aware. If the device is not diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 769caf0c83..3962b2a2fc 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -412,11 +412,12 @@ private: // The components of the viewport are specified in the display's rotated orientation. DisplayViewport mViewport; - // The display size obtained from the viewport in the natural orientation. + // We refer to the display as being in the "natural orientation" when there is no rotation + // applied. The display size obtained from the viewport in the natural orientation. // Always starts at (0, 0). ui::Size mDisplayBounds{ui::kInvalidSize}; - // The physical frame is the rectangle in the display's coordinate space that maps to + // The physical frame is the rectangle in the natural display's coordinate space that maps to // the logical display frame. Rect mPhysicalFrameInDisplay{Rect::INVALID_RECT}; -- GitLab From 65f492ab8038f6ec25547faad1c659bc62f8480b Mon Sep 17 00:00:00 2001 From: Nick Deakin Date: Tue, 29 Nov 2022 22:47:40 -0500 Subject: [PATCH 0551/1310] Add RecoveryMapMath tests; also some fixes. Add thorough tests for recovery map math. Also, the following fixes for issues discovered along the way: * Added proper scaling of luminances during map generation * Corrected some luminance and color conversions using incorrect luminance/luma cooeficients * Corrected PQ inverse OETF * Corrected clipping of gain when encoding recovery * Corrected sampleMap to use a better, working sampling algorithm instead of the previous bad and incorrect one * Clarified expected ranges in and out of some transformation functions * Clarified references for a bunch of transformations Bug: 252835416 Test: builds, new tests pass Change-Id: I3c2192e840b784774c60cf212aaf188501915340 --- .../include/jpegrecoverymap/recoverymapmath.h | 55 +- libs/jpegrecoverymap/recoverymap.cpp | 9 +- libs/jpegrecoverymap/recoverymapmath.cpp | 157 ++-- libs/jpegrecoverymap/tests/Android.bp | 4 +- .../tests/recoverymapmath_test.cpp | 882 ++++++++++++++++++ 5 files changed, 1032 insertions(+), 75 deletions(-) create mode 100644 libs/jpegrecoverymap/tests/recoverymapmath_test.cpp diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h index fe7a651ffb..0fb64d3c15 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h @@ -27,6 +27,8 @@ namespace android::recoverymap { // Framework const float kSdrWhiteNits = 100.0f; +const float kHlgMaxNits = 1000.0f; +const float kPqMaxNits = 10000.0f; struct Color { union { @@ -113,9 +115,14 @@ inline Color operator/(const Color& lhs, const float rhs) { //////////////////////////////////////////////////////////////////////////////// // sRGB transformations +// NOTE: sRGB has the same color primaries as BT.709, but different transfer +// function. For this reason, all sRGB transformations here apply to BT.709, +// except for those concerning transfer functions. /* * Calculate the luminance of a linear RGB sRGB pixel, according to IEC 61966-2-1. + * + * [0.0, 1.0] range in and out. */ float srgbLuminance(Color e); @@ -142,7 +149,9 @@ Color srgbInvOetf(Color e_gamma); // Display-P3 transformations /* - * Calculated the luminance of a linear RGB P3 pixel, according to EG 432-1. + * Calculated the luminance of a linear RGB P3 pixel, according to SMPTE EG 432-1. + * + * [0.0, 1.0] range in and out. */ float p3Luminance(Color e); @@ -152,6 +161,8 @@ float p3Luminance(Color e); /* * Calculate the luminance of a linear RGB BT.2100 pixel. + * + * [0.0, 1.0] range in and out. */ float bt2100Luminance(Color e); @@ -166,23 +177,35 @@ Color bt2100RgbToYuv(Color e_gamma); Color bt2100YuvToRgb(Color e_gamma); /* - * Convert from scene luminance in nits to HLG. + * Convert from scene luminance to HLG. + * + * [0.0, 1.0] range in and out. */ +float hlgOetf(float e); Color hlgOetf(Color e); /* - * Convert from HLG to scene luminance in nits. + * Convert from HLG to scene luminance. + * + * [0.0, 1.0] range in and out. */ +float hlgInvOetf(float e_gamma); Color hlgInvOetf(Color e_gamma); /* - * Convert from scene luminance in nits to PQ. + * Convert from scene luminance to PQ. + * + * [0.0, 1.0] range in and out. */ +float pqOetf(float e); Color pqOetf(Color e); /* * Convert from PQ to scene luminance in nits. + * + * [0.0, 1.0] range in and out. */ +float pqInvOetf(float e_gamma); Color pqInvOetf(Color e_gamma); @@ -230,34 +253,36 @@ uint8_t encodeRecovery(float y_sdr, float y_hdr, float hdr_ratio); Color applyRecovery(Color e, float recovery, float hdr_ratio); /* - * Helper for sampling from images. + * Helper for sampling from YUV 420 images. */ Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y); /* - * Helper for sampling from images. + * Helper for sampling from P010 images. + * + * Expect narrow-range image data for P010. */ Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y); /* - * Sample the recovery value for the map from a given x,y coordinate on a scale - * that is map scale factor larger than the map size. + * Sample the image at the provided location, with a weighting based on nearby + * pixels and the map scale factor. */ -float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y); +Color sampleYuv420(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y); /* - * Sample the image Y value at the provided location, with a weighting based on nearby pixels - * and the map scale factor. + * Sample the image at the provided location, with a weighting based on nearby + * pixels and the map scale factor. * * Expect narrow-range image data for P010. */ -Color sampleYuv420(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y); +Color sampleP010(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y); /* - * Sample the image Y value at the provided location, with a weighting based on nearby pixels - * and the map scale factor. Assumes narrow-range image data for P010. + * Sample the recovery value for the map from a given x,y coordinate on a scale + * that is map scale factor larger than the map size. */ -Color sampleP010(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y); +float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y); /* * Convert from Color to RGBA1010102. diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index 4a209ec381..f7f36223d4 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -390,12 +390,15 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 map_data.reset(reinterpret_cast(dest->data)); ColorTransformFn hdrInvOetf = nullptr; + float hdr_white_nits = 0.0f; switch (metadata->transferFunction) { case JPEGR_TF_HLG: hdrInvOetf = hlgInvOetf; + hdr_white_nits = kHlgMaxNits; break; case JPEGR_TF_PQ: hdrInvOetf = pqInvOetf; + hdr_white_nits = kPqMaxNits; break; } @@ -426,7 +429,7 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma); Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma); hdr_rgb = hdrGamutConversionFn(hdr_rgb); - float hdr_y_nits = luminanceFn(hdr_rgb); + float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits; hdr_y_nits_avg += hdr_y_nits; if (hdr_y_nits > hdr_y_nits_max) { @@ -448,13 +451,13 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 kMapDimensionScaleFactor, x, y); Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma); Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma); - float sdr_y_nits = luminanceFn(sdr_rgb); + float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits; Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y); Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma); Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma); hdr_rgb = hdrGamutConversionFn(hdr_rgb); - float hdr_y_nits = luminanceFn(hdr_rgb); + float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits; size_t pixel_idx = x + y * map_width; reinterpret_cast(dest->data)[pixel_idx] = diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp index 6dcbca3707..e838f43b58 100644 --- a/libs/jpegrecoverymap/recoverymapmath.cpp +++ b/libs/jpegrecoverymap/recoverymapmath.cpp @@ -23,12 +23,14 @@ namespace android::recoverymap { //////////////////////////////////////////////////////////////////////////////// // sRGB transformations -static const float kSrgbR = 0.299f, kSrgbG = 0.587f, kSrgbB = 0.114f; +// See IEC 61966-2-1, Equation F.7. +static const float kSrgbR = 0.2126f, kSrgbG = 0.7152f, kSrgbB = 0.0722f; float srgbLuminance(Color e) { return kSrgbR * e.r + kSrgbG * e.g + kSrgbB * e.b; } +// See ECMA TR/98, Section 7. static const float kSrgbRCr = 1.402f, kSrgbGCb = 0.34414f, kSrgbGCr = 0.71414f, kSrgbBCb = 1.772f; Color srgbYuvToRgb(Color e_gamma) { @@ -37,15 +39,18 @@ Color srgbYuvToRgb(Color e_gamma) { e_gamma.y + kSrgbBCb * e_gamma.u }}}; } +// See ECMA TR/98, Section 7. +static const float kSrgbYR = 0.299f, kSrgbYG = 0.587f, kSrgbYB = 0.114f; static const float kSrgbUR = -0.1687f, kSrgbUG = -0.3313f, kSrgbUB = 0.5f; static const float kSrgbVR = 0.5f, kSrgbVG = -0.4187f, kSrgbVB = -0.0813f; Color srgbRgbToYuv(Color e_gamma) { - return {{{ kSrgbR * e_gamma.r + kSrgbG * e_gamma.g + kSrgbB * e_gamma.b, + return {{{ kSrgbYR * e_gamma.r + kSrgbYG * e_gamma.g + kSrgbYB * e_gamma.b, kSrgbUR * e_gamma.r + kSrgbUG * e_gamma.g + kSrgbUB * e_gamma.b, kSrgbVR * e_gamma.r + kSrgbVG * e_gamma.g + kSrgbVB * e_gamma.b }}}; } +// See IEC 61966-2-1, Equations F.5 and F.6. float srgbInvOetf(float e_gamma) { if (e_gamma <= 0.04045f) { return e_gamma / 12.92f; @@ -64,7 +69,8 @@ Color srgbInvOetf(Color e_gamma) { //////////////////////////////////////////////////////////////////////////////// // Display-P3 transformations -static const float kP3R = 0.22897f, kP3G = 0.69174f, kP3B = 0.07929f; +// See SMPTE EG 432-1, Table 7-2. +static const float kP3R = 0.20949f, kP3G = 0.72160f, kP3B = 0.06891f; float p3Luminance(Color e) { return kP3R * e.r + kP3G * e.g + kP3B * e.b; @@ -74,12 +80,14 @@ float p3Luminance(Color e) { //////////////////////////////////////////////////////////////////////////////// // BT.2100 transformations - according to ITU-R BT.2100-2 +// See ITU-R BT.2100-2, Table 5, HLG Reference OOTF static const float kBt2100R = 0.2627f, kBt2100G = 0.6780f, kBt2100B = 0.0593f; float bt2100Luminance(Color e) { return kBt2100R * e.r + kBt2100G * e.g + kBt2100B * e.b; } +// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals. static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f; Color bt2100RgbToYuv(Color e_gamma) { @@ -89,9 +97,9 @@ Color bt2100RgbToYuv(Color e_gamma) { (e_gamma.r - y_gamma) / kBt2100Cr }}}; } -// Derived from the reverse of bt2100RgbToYuv. The derivation for R and B are -// pretty straight forward; we just reverse the formulas for U and V above. But -// deriving the formula for G is a bit more complicated: +// Derived by inversing bt2100RgbToYuv. The derivation for R and B are pretty +// straight forward; we just invert the formulas for U and V above. But deriving +// the formula for G is a bit more complicated: // // Start with equation for luminance: // Y = kBt2100R * R + kBt2100G * G + kBt2100B * B @@ -119,9 +127,10 @@ Color bt2100YuvToRgb(Color e_gamma) { e_gamma.y + kBt2100Cb * e_gamma.u }}}; } +// See ITU-R BT.2100-2, Table 5, HLG Reference OETF. static const float kHlgA = 0.17883277f, kHlgB = 0.28466892f, kHlgC = 0.55991073; -static float hlgOetf(float e) { +float hlgOetf(float e) { if (e <= 1.0f/12.0f) { return sqrt(3.0f * e); } else { @@ -133,7 +142,8 @@ Color hlgOetf(Color e) { return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}}; } -static float hlgInvOetf(float e_gamma) { +// See ITU-R BT.2100-2, Table 5, HLG Reference EOTF. +float hlgInvOetf(float e_gamma) { if (e_gamma <= 0.5f) { return pow(e_gamma, 2.0f) / 3.0f; } else { @@ -147,13 +157,14 @@ Color hlgInvOetf(Color e_gamma) { hlgInvOetf(e_gamma.b) }}}; } +// See ITU-R BT.2100-2, Table 4, Reference PQ OETF. static const float kPqM1 = 2610.0f / 16384.0f, kPqM2 = 2523.0f / 4096.0f * 128.0f; static const float kPqC1 = 3424.0f / 4096.0f, kPqC2 = 2413.0f / 4096.0f * 32.0f, kPqC3 = 2392.0f / 4096.0f * 32.0f; -static float pqOetf(float e) { - if (e < 0.0f) e = 0.0f; - return pow((kPqC1 + kPqC2 * pow(e / 10000.0f, kPqM1)) / (1 + kPqC3 * pow(e / 10000.0f, kPqM1)), +float pqOetf(float e) { + if (e <= 0.0f) return 0.0f; + return pow((kPqC1 + kPqC2 * pow(e, kPqM1)) / (1 + kPqC3 * pow(e, kPqM1)), kPqM2); } @@ -161,10 +172,18 @@ Color pqOetf(Color e) { return {{{ pqOetf(e.r), pqOetf(e.g), pqOetf(e.b) }}}; } -static float pqInvOetf(float e_gamma) { - static const float kPqInvOetfCoef = log2(-(pow(kPqM1, 1.0f / kPqM2) - kPqC1) - / (kPqC3 * pow(kPqM1, 1.0f / kPqM2) - kPqC2)); - return kPqInvOetfCoef / log2(e_gamma * 10000.0f); +// Derived from the inverse of the Reference PQ OETF. +static const float kPqInvA = 128.0f, kPqInvB = 107.0f, kPqInvC = 2413.0f, kPqInvD = 2392.0f, + kPqInvE = 6.2773946361f, kPqInvF = 0.0126833f; + +float pqInvOetf(float e_gamma) { + // This equation blows up if e_gamma is 0.0, and checking on <= 0.0 doesn't + // always catch 0.0. So, check on 0.0001, since anything this small will + // effectively be crushed to zero anyways. + if (e_gamma <= 0.0001f) return 0.0f; + return pow((kPqInvA * pow(e_gamma, kPqInvF) - kPqInvB) + / (kPqInvC - kPqInvD * pow(e_gamma, kPqInvF)), + kPqInvE); } Color pqInvOetf(Color e_gamma) { @@ -217,7 +236,7 @@ Color bt2100ToP3(Color e) { // TODO: confirm we always want to convert like this before calculating // luminance. ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gamut hdr_gamut) { - switch (sdr_gamut) { + switch (sdr_gamut) { case JPEGR_COLORGAMUT_BT709: switch (hdr_gamut) { case JPEGR_COLORGAMUT_BT709: @@ -269,13 +288,14 @@ uint8_t encodeRecovery(float y_sdr, float y_hdr, float hdr_ratio) { gain = y_hdr / y_sdr; } - if (gain < -hdr_ratio) gain = -hdr_ratio; + if (gain < (1.0f / hdr_ratio)) gain = 1.0f / hdr_ratio; if (gain > hdr_ratio) gain = hdr_ratio; return static_cast(log2(gain) / log2(hdr_ratio) * 127.5f + 127.5f); } static float applyRecovery(float e, float recovery, float hdr_ratio) { + if (e <= 0.0f) return 0.0f; return exp2(log2(e) + recovery * log2(hdr_ratio)); } @@ -285,45 +305,6 @@ Color applyRecovery(Color e, float recovery, float hdr_ratio) { applyRecovery(e.b, recovery, hdr_ratio) }}}; } -// TODO: do we need something more clever for filtering either the map or images -// to generate the map? - -static size_t clamp(const size_t& val, const size_t& low, const size_t& high) { - return val < low ? low : (high < val ? high : val); -} - -static float mapUintToFloat(uint8_t map_uint) { - return (static_cast(map_uint) - 127.5f) / 127.5f; -} - -float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y) { - float x_map = static_cast(x) / static_cast(map_scale_factor); - float y_map = static_cast(y) / static_cast(map_scale_factor); - - size_t x_lower = static_cast(floor(x_map)); - size_t x_upper = x_lower + 1; - size_t y_lower = static_cast(floor(y_map)); - size_t y_upper = y_lower + 1; - - x_lower = clamp(x_lower, 0, map->width - 1); - x_upper = clamp(x_upper, 0, map->width - 1); - y_lower = clamp(y_lower, 0, map->height - 1); - y_upper = clamp(y_upper, 0, map->height - 1); - - float x_influence = x_map - static_cast(x_lower); - float y_influence = y_map - static_cast(y_lower); - - float e1 = mapUintToFloat(reinterpret_cast(map->data)[x_lower + y_lower * map->width]); - float e2 = mapUintToFloat(reinterpret_cast(map->data)[x_lower + y_upper * map->width]); - float e3 = mapUintToFloat(reinterpret_cast(map->data)[x_upper + y_lower * map->width]); - float e4 = mapUintToFloat(reinterpret_cast(map->data)[x_upper + y_upper * map->width]); - - return e1 * (x_influence + y_influence) / 2.0f - + e2 * (x_influence + 1.0f - y_influence) / 2.0f - + e3 * (1.0f - x_influence + y_influence) / 2.0f - + e4 * (1.0f - x_influence + 1.0f - y_influence) / 2.0f; -} - Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) { size_t pixel_count = image->width * image->height; @@ -382,6 +363,70 @@ Color sampleP010(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, s return samplePixels(image, map_scale_factor, x, y, getP010Pixel); } +// TODO: do we need something more clever for filtering either the map or images +// to generate the map? + +static size_t clamp(const size_t& val, const size_t& low, const size_t& high) { + return val < low ? low : (high < val ? high : val); +} + +static float mapUintToFloat(uint8_t map_uint) { + return (static_cast(map_uint) - 127.5f) / 127.5f; +} + +static float pythDistance(float x_diff, float y_diff) { + return sqrt(pow(x_diff, 2.0f) + pow(y_diff, 2.0f)); +} + +float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y) { + float x_map = static_cast(x) / static_cast(map_scale_factor); + float y_map = static_cast(y) / static_cast(map_scale_factor); + + size_t x_lower = static_cast(floor(x_map)); + size_t x_upper = x_lower + 1; + size_t y_lower = static_cast(floor(y_map)); + size_t y_upper = y_lower + 1; + + x_lower = clamp(x_lower, 0, map->width - 1); + x_upper = clamp(x_upper, 0, map->width - 1); + y_lower = clamp(y_lower, 0, map->height - 1); + y_upper = clamp(y_upper, 0, map->height - 1); + + // Use Shepard's method for inverse distance weighting. For more information: + // en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method + + float e1 = mapUintToFloat(reinterpret_cast(map->data)[x_lower + y_lower * map->width]); + float e1_dist = pythDistance(x_map - static_cast(x_lower), + y_map - static_cast(y_lower)); + if (e1_dist == 0.0f) return e1; + + float e2 = mapUintToFloat(reinterpret_cast(map->data)[x_lower + y_upper * map->width]); + float e2_dist = pythDistance(x_map - static_cast(x_lower), + y_map - static_cast(y_upper)); + if (e2_dist == 0.0f) return e2; + + float e3 = mapUintToFloat(reinterpret_cast(map->data)[x_upper + y_lower * map->width]); + float e3_dist = pythDistance(x_map - static_cast(x_upper), + y_map - static_cast(y_lower)); + if (e3_dist == 0.0f) return e3; + + float e4 = mapUintToFloat(reinterpret_cast(map->data)[x_upper + y_upper * map->width]); + float e4_dist = pythDistance(x_map - static_cast(x_upper), + y_map - static_cast(y_upper)); + if (e4_dist == 0.0f) return e2; + + float e1_weight = 1.0f / e1_dist; + float e2_weight = 1.0f / e2_dist; + float e3_weight = 1.0f / e3_dist; + float e4_weight = 1.0f / e4_dist; + float total_weight = e1_weight + e2_weight + e3_weight + e4_weight; + + return e1 * (e1_weight / total_weight) + + e2 * (e2_weight / total_weight) + + e3 * (e3_weight / total_weight) + + e4 * (e4_weight / total_weight); +} + uint32_t colorToRgba1010102(Color e_gamma) { return (0x3ff & static_cast(e_gamma.r * 1023.0f)) | ((0x3ff & static_cast(e_gamma.g * 1023.0f)) << 10) diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp index 8f37954841..b509478e73 100644 --- a/libs/jpegrecoverymap/tests/Android.bp +++ b/libs/jpegrecoverymap/tests/Android.bp @@ -26,13 +26,15 @@ cc_test { test_suites: ["device-tests"], srcs: [ "recoverymap_test.cpp", + "recoverymapmath_test.cpp", ], shared_libs: [ - "libimage_io", "libjpeg", "liblog", ], static_libs: [ + "libimage_io", + "libgmock", "libgtest", "libjpegdecoder", "libjpegencoder", diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp new file mode 100644 index 0000000000..169201c73b --- /dev/null +++ b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp @@ -0,0 +1,882 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +namespace android::recoverymap { + +class RecoveryMapMathTest : public testing::Test { +public: + RecoveryMapMathTest(); + ~RecoveryMapMathTest(); + + float ComparisonEpsilon() { return 1e-4f; } + float LuminanceEpsilon() { return 1e-2f; } + + Color Yuv420(uint8_t y, uint8_t u, uint8_t v) { + return {{{ static_cast(y) / 255.0f, + (static_cast(u) - 128.0f) / 255.0f, + (static_cast(v) - 128.0f) / 255.0f }}}; + } + + Color P010(uint16_t y, uint16_t u, uint16_t v) { + return {{{ static_cast(y) / 940.0f, + (static_cast(u) - 64.0f) / 940.0f - 0.5f, + (static_cast(v) - 64.0f) / 940.0f - 0.5f }}}; + } + + float Map(uint8_t e) { + return (static_cast(e) - 127.5f) / 127.5f; + } + + Color ColorMin(Color e1, Color e2) { + return {{{ fmin(e1.r, e2.r), fmin(e1.g, e2.g), fmin(e1.b, e2.b) }}}; + } + + Color ColorMax(Color e1, Color e2) { + return {{{ fmax(e1.r, e2.r), fmax(e1.g, e2.g), fmax(e1.b, e2.b) }}}; + } + + Color RgbBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; } + Color RgbWhite() { return {{{ 1.0f, 1.0f, 1.0f }}}; } + + Color RgbRed() { return {{{ 1.0f, 0.0f, 0.0f }}}; } + Color RgbGreen() { return {{{ 0.0f, 1.0f, 0.0f }}}; } + Color RgbBlue() { return {{{ 0.0f, 0.0f, 1.0f }}}; } + + Color YuvBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; } + Color YuvWhite() { return {{{ 1.0f, 0.0f, 0.0f }}}; } + + Color SrgbYuvRed() { return {{{ 0.299f, -0.1687f, 0.5f }}}; } + Color SrgbYuvGreen() { return {{{ 0.587f, -0.3313f, -0.4187f }}}; } + Color SrgbYuvBlue() { return {{{ 0.114f, 0.5f, -0.0813f }}}; } + + Color Bt2100YuvRed() { return {{{ 0.2627f, -0.13963f, 0.5f }}}; } + Color Bt2100YuvGreen() { return {{{ 0.6780f, -0.36037f, -0.45979f }}}; } + Color Bt2100YuvBlue() { return {{{ 0.0593f, 0.5f, -0.04021f }}}; } + + float SrgbYuvToLuminance(Color yuv_gamma, ColorCalculationFn luminanceFn) { + Color rgb_gamma = srgbYuvToRgb(yuv_gamma); + Color rgb = srgbInvOetf(rgb_gamma); + float luminance_scaled = luminanceFn(rgb); + return luminance_scaled * kSdrWhiteNits; + } + + float Bt2100YuvToLuminance(Color yuv_gamma, ColorTransformFn hdrInvOetf, + ColorTransformFn gamutConversionFn, ColorCalculationFn luminanceFn, + float scale_factor) { + Color rgb_gamma = bt2100YuvToRgb(yuv_gamma); + Color rgb = hdrInvOetf(rgb_gamma); + rgb = gamutConversionFn(rgb); + float luminance_scaled = luminanceFn(rgb); + return luminance_scaled * scale_factor; + } + + Color Recover(Color yuv_gamma, float recovery, float range_scaling_factor) { + Color rgb_gamma = srgbYuvToRgb(yuv_gamma); + Color rgb = srgbInvOetf(rgb_gamma); + return applyRecovery(rgb, recovery, range_scaling_factor); + } + + jpegr_uncompressed_struct Yuv420Image() { + static uint8_t pixels[] = { + // Y + 0x00, 0x10, 0x20, 0x30, + 0x01, 0x11, 0x21, 0x31, + 0x02, 0x12, 0x22, 0x32, + 0x03, 0x13, 0x23, 0x33, + // U + 0xA0, 0xA1, + 0xA2, 0xA3, + // V + 0xB0, 0xB1, + 0xB2, 0xB3, + }; + return { pixels, 4, 4, JPEGR_COLORGAMUT_BT709 }; + } + + Color (*Yuv420Colors())[4] { + static Color colors[4][4] = { + { + Yuv420(0x00, 0xA0, 0xB0), Yuv420(0x10, 0xA0, 0xB0), + Yuv420(0x20, 0xA1, 0xB1), Yuv420(0x30, 0xA1, 0xB1), + }, { + Yuv420(0x01, 0xA0, 0xB0), Yuv420(0x11, 0xA0, 0xB0), + Yuv420(0x21, 0xA1, 0xB1), Yuv420(0x31, 0xA1, 0xB1), + }, { + Yuv420(0x02, 0xA2, 0xB2), Yuv420(0x12, 0xA2, 0xB2), + Yuv420(0x22, 0xA3, 0xB3), Yuv420(0x32, 0xA3, 0xB3), + }, { + Yuv420(0x03, 0xA2, 0xB2), Yuv420(0x13, 0xA2, 0xB2), + Yuv420(0x23, 0xA3, 0xB3), Yuv420(0x33, 0xA3, 0xB3), + }, + }; + return colors; + } + + jpegr_uncompressed_struct P010Image() { + static uint16_t pixels[] = { + // Y + 0x00 << 6, 0x10 << 6, 0x20 << 6, 0x30 << 6, + 0x01 << 6, 0x11 << 6, 0x21 << 6, 0x31 << 6, + 0x02 << 6, 0x12 << 6, 0x22 << 6, 0x32 << 6, + 0x03 << 6, 0x13 << 6, 0x23 << 6, 0x33 << 6, + // UV + 0xA0 << 6, 0xB0 << 6, 0xA1 << 6, 0xB1 << 6, + 0xA2 << 6, 0xB2 << 6, 0xA3 << 6, 0xB3 << 6, + }; + return { pixels, 4, 4, JPEGR_COLORGAMUT_BT709 }; + } + + Color (*P010Colors())[4] { + static Color colors[4][4] = { + { + P010(0x00, 0xA0, 0xB0), P010(0x10, 0xA0, 0xB0), + P010(0x20, 0xA1, 0xB1), P010(0x30, 0xA1, 0xB1), + }, { + P010(0x01, 0xA0, 0xB0), P010(0x11, 0xA0, 0xB0), + P010(0x21, 0xA1, 0xB1), P010(0x31, 0xA1, 0xB1), + }, { + P010(0x02, 0xA2, 0xB2), P010(0x12, 0xA2, 0xB2), + P010(0x22, 0xA3, 0xB3), P010(0x32, 0xA3, 0xB3), + }, { + P010(0x03, 0xA2, 0xB2), P010(0x13, 0xA2, 0xB2), + P010(0x23, 0xA3, 0xB3), P010(0x33, 0xA3, 0xB3), + }, + }; + return colors; + } + + jpegr_uncompressed_struct MapImage() { + static uint8_t pixels[] = { + 0x00, 0x10, 0x20, 0x30, + 0x01, 0x11, 0x21, 0x31, + 0x02, 0x12, 0x22, 0x32, + 0x03, 0x13, 0x23, 0x33, + }; + return { pixels, 4, 4, JPEGR_COLORGAMUT_UNSPECIFIED }; + } + + float (*MapValues())[4] { + static float values[4][4] = { + { + Map(0x00), Map(0x10), Map(0x20), Map(0x30), + }, { + Map(0x01), Map(0x11), Map(0x21), Map(0x31), + }, { + Map(0x02), Map(0x12), Map(0x22), Map(0x32), + }, { + Map(0x03), Map(0x13), Map(0x23), Map(0x33), + }, + }; + return values; + } + +protected: + virtual void SetUp(); + virtual void TearDown(); +}; + +RecoveryMapMathTest::RecoveryMapMathTest() {} +RecoveryMapMathTest::~RecoveryMapMathTest() {} + +void RecoveryMapMathTest::SetUp() {} +void RecoveryMapMathTest::TearDown() {} + +#define EXPECT_RGB_EQ(e1, e2) \ + EXPECT_FLOAT_EQ((e1).r, (e2).r); \ + EXPECT_FLOAT_EQ((e1).g, (e2).g); \ + EXPECT_FLOAT_EQ((e1).b, (e2).b) + +#define EXPECT_RGB_NEAR(e1, e2) \ + EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon()); \ + EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon()); \ + EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon()) + +#define EXPECT_RGB_CLOSE(e1, e2) \ + EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon() * 10.0f); \ + EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon() * 10.0f); \ + EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon() * 10.0f) + +#define EXPECT_YUV_EQ(e1, e2) \ + EXPECT_FLOAT_EQ((e1).y, (e2).y); \ + EXPECT_FLOAT_EQ((e1).u, (e2).u); \ + EXPECT_FLOAT_EQ((e1).v, (e2).v) + +#define EXPECT_YUV_NEAR(e1, e2) \ + EXPECT_NEAR((e1).y, (e2).y, ComparisonEpsilon()); \ + EXPECT_NEAR((e1).u, (e2).u, ComparisonEpsilon()); \ + EXPECT_NEAR((e1).v, (e2).v, ComparisonEpsilon()) + +#define EXPECT_YUV_BETWEEN(e, min, max) \ + EXPECT_THAT((e).y, testing::AllOf(testing::Ge((min).y), testing::Le((max).y))); \ + EXPECT_THAT((e).u, testing::AllOf(testing::Ge((min).u), testing::Le((max).u))); \ + EXPECT_THAT((e).v, testing::AllOf(testing::Ge((min).v), testing::Le((max).v))) + +// TODO: a bunch of these tests can be parameterized. + +TEST_F(RecoveryMapMathTest, ColorConstruct) { + Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; + + EXPECT_FLOAT_EQ(e1.r, 0.1f); + EXPECT_FLOAT_EQ(e1.g, 0.2f); + EXPECT_FLOAT_EQ(e1.b, 0.3f); + + EXPECT_FLOAT_EQ(e1.y, 0.1f); + EXPECT_FLOAT_EQ(e1.u, 0.2f); + EXPECT_FLOAT_EQ(e1.v, 0.3f); +} + +TEST_F(RecoveryMapMathTest, ColorAddColor) { + Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; + + Color e2 = e1 + e1; + EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f); + EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f); + EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f); + + e2 += e1; + EXPECT_FLOAT_EQ(e2.r, e1.r * 3.0f); + EXPECT_FLOAT_EQ(e2.g, e1.g * 3.0f); + EXPECT_FLOAT_EQ(e2.b, e1.b * 3.0f); +} + +TEST_F(RecoveryMapMathTest, ColorAddFloat) { + Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; + + Color e2 = e1 + 0.1f; + EXPECT_FLOAT_EQ(e2.r, e1.r + 0.1f); + EXPECT_FLOAT_EQ(e2.g, e1.g + 0.1f); + EXPECT_FLOAT_EQ(e2.b, e1.b + 0.1f); + + e2 += 0.1f; + EXPECT_FLOAT_EQ(e2.r, e1.r + 0.2f); + EXPECT_FLOAT_EQ(e2.g, e1.g + 0.2f); + EXPECT_FLOAT_EQ(e2.b, e1.b + 0.2f); +} + +TEST_F(RecoveryMapMathTest, ColorSubtractColor) { + Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; + + Color e2 = e1 - e1; + EXPECT_FLOAT_EQ(e2.r, 0.0f); + EXPECT_FLOAT_EQ(e2.g, 0.0f); + EXPECT_FLOAT_EQ(e2.b, 0.0f); + + e2 -= e1; + EXPECT_FLOAT_EQ(e2.r, -e1.r); + EXPECT_FLOAT_EQ(e2.g, -e1.g); + EXPECT_FLOAT_EQ(e2.b, -e1.b); +} + +TEST_F(RecoveryMapMathTest, ColorSubtractFloat) { + Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; + + Color e2 = e1 - 0.1f; + EXPECT_FLOAT_EQ(e2.r, e1.r - 0.1f); + EXPECT_FLOAT_EQ(e2.g, e1.g - 0.1f); + EXPECT_FLOAT_EQ(e2.b, e1.b - 0.1f); + + e2 -= 0.1f; + EXPECT_FLOAT_EQ(e2.r, e1.r - 0.2f); + EXPECT_FLOAT_EQ(e2.g, e1.g - 0.2f); + EXPECT_FLOAT_EQ(e2.b, e1.b - 0.2f); +} + +TEST_F(RecoveryMapMathTest, ColorMultiplyFloat) { + Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; + + Color e2 = e1 * 2.0f; + EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f); + EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f); + EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f); + + e2 *= 2.0f; + EXPECT_FLOAT_EQ(e2.r, e1.r * 4.0f); + EXPECT_FLOAT_EQ(e2.g, e1.g * 4.0f); + EXPECT_FLOAT_EQ(e2.b, e1.b * 4.0f); +} + +TEST_F(RecoveryMapMathTest, ColorDivideFloat) { + Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; + + Color e2 = e1 / 2.0f; + EXPECT_FLOAT_EQ(e2.r, e1.r / 2.0f); + EXPECT_FLOAT_EQ(e2.g, e1.g / 2.0f); + EXPECT_FLOAT_EQ(e2.b, e1.b / 2.0f); + + e2 /= 2.0f; + EXPECT_FLOAT_EQ(e2.r, e1.r / 4.0f); + EXPECT_FLOAT_EQ(e2.g, e1.g / 4.0f); + EXPECT_FLOAT_EQ(e2.b, e1.b / 4.0f); +} + +TEST_F(RecoveryMapMathTest, SrgbLuminance) { + EXPECT_FLOAT_EQ(srgbLuminance(RgbBlack()), 0.0f); + EXPECT_FLOAT_EQ(srgbLuminance(RgbWhite()), 1.0f); + EXPECT_FLOAT_EQ(srgbLuminance(RgbRed()), 0.2126f); + EXPECT_FLOAT_EQ(srgbLuminance(RgbGreen()), 0.7152f); + EXPECT_FLOAT_EQ(srgbLuminance(RgbBlue()), 0.0722f); +} + +TEST_F(RecoveryMapMathTest, SrgbYuvToRgb) { + Color rgb_black = srgbYuvToRgb(YuvBlack()); + EXPECT_RGB_NEAR(rgb_black, RgbBlack()); + + Color rgb_white = srgbYuvToRgb(YuvWhite()); + EXPECT_RGB_NEAR(rgb_white, RgbWhite()); + + Color rgb_r = srgbYuvToRgb(SrgbYuvRed()); + EXPECT_RGB_NEAR(rgb_r, RgbRed()); + + Color rgb_g = srgbYuvToRgb(SrgbYuvGreen()); + EXPECT_RGB_NEAR(rgb_g, RgbGreen()); + + Color rgb_b = srgbYuvToRgb(SrgbYuvBlue()); + EXPECT_RGB_NEAR(rgb_b, RgbBlue()); +} + +TEST_F(RecoveryMapMathTest, SrgbRgbToYuv) { + Color yuv_black = srgbRgbToYuv(RgbBlack()); + EXPECT_YUV_NEAR(yuv_black, YuvBlack()); + + Color yuv_white = srgbRgbToYuv(RgbWhite()); + EXPECT_YUV_NEAR(yuv_white, YuvWhite()); + + Color yuv_r = srgbRgbToYuv(RgbRed()); + EXPECT_YUV_NEAR(yuv_r, SrgbYuvRed()); + + Color yuv_g = srgbRgbToYuv(RgbGreen()); + EXPECT_YUV_NEAR(yuv_g, SrgbYuvGreen()); + + Color yuv_b = srgbRgbToYuv(RgbBlue()); + EXPECT_YUV_NEAR(yuv_b, SrgbYuvBlue()); +} + +TEST_F(RecoveryMapMathTest, SrgbRgbYuvRoundtrip) { + Color rgb_black = srgbYuvToRgb(srgbRgbToYuv(RgbBlack())); + EXPECT_RGB_NEAR(rgb_black, RgbBlack()); + + Color rgb_white = srgbYuvToRgb(srgbRgbToYuv(RgbWhite())); + EXPECT_RGB_NEAR(rgb_white, RgbWhite()); + + Color rgb_r = srgbYuvToRgb(srgbRgbToYuv(RgbRed())); + EXPECT_RGB_NEAR(rgb_r, RgbRed()); + + Color rgb_g = srgbYuvToRgb(srgbRgbToYuv(RgbGreen())); + EXPECT_RGB_NEAR(rgb_g, RgbGreen()); + + Color rgb_b = srgbYuvToRgb(srgbRgbToYuv(RgbBlue())); + EXPECT_RGB_NEAR(rgb_b, RgbBlue()); +} + +TEST_F(RecoveryMapMathTest, SrgbTransferFunction) { + EXPECT_FLOAT_EQ(srgbInvOetf(0.0f), 0.0f); + EXPECT_NEAR(srgbInvOetf(0.02f), 0.00154f, ComparisonEpsilon()); + EXPECT_NEAR(srgbInvOetf(0.04045f), 0.00313f, ComparisonEpsilon()); + EXPECT_NEAR(srgbInvOetf(0.5f), 0.21404f, ComparisonEpsilon()); + EXPECT_FLOAT_EQ(srgbInvOetf(1.0f), 1.0f); +} + +TEST_F(RecoveryMapMathTest, P3Luminance) { + EXPECT_FLOAT_EQ(p3Luminance(RgbBlack()), 0.0f); + EXPECT_FLOAT_EQ(p3Luminance(RgbWhite()), 1.0f); + EXPECT_FLOAT_EQ(p3Luminance(RgbRed()), 0.20949f); + EXPECT_FLOAT_EQ(p3Luminance(RgbGreen()), 0.72160f); + EXPECT_FLOAT_EQ(p3Luminance(RgbBlue()), 0.06891f); +} + +TEST_F(RecoveryMapMathTest, Bt2100Luminance) { + EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlack()), 0.0f); + EXPECT_FLOAT_EQ(bt2100Luminance(RgbWhite()), 1.0f); + EXPECT_FLOAT_EQ(bt2100Luminance(RgbRed()), 0.2627f); + EXPECT_FLOAT_EQ(bt2100Luminance(RgbGreen()), 0.6780f); + EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlue()), 0.0593f); +} + +TEST_F(RecoveryMapMathTest, Bt2100YuvToRgb) { + Color rgb_black = bt2100YuvToRgb(YuvBlack()); + EXPECT_RGB_NEAR(rgb_black, RgbBlack()); + + Color rgb_white = bt2100YuvToRgb(YuvWhite()); + EXPECT_RGB_NEAR(rgb_white, RgbWhite()); + + Color rgb_r = bt2100YuvToRgb(Bt2100YuvRed()); + EXPECT_RGB_NEAR(rgb_r, RgbRed()); + + Color rgb_g = bt2100YuvToRgb(Bt2100YuvGreen()); + EXPECT_RGB_NEAR(rgb_g, RgbGreen()); + + Color rgb_b = bt2100YuvToRgb(Bt2100YuvBlue()); + EXPECT_RGB_NEAR(rgb_b, RgbBlue()); +} + +TEST_F(RecoveryMapMathTest, Bt2100RgbToYuv) { + Color yuv_black = bt2100RgbToYuv(RgbBlack()); + EXPECT_YUV_NEAR(yuv_black, YuvBlack()); + + Color yuv_white = bt2100RgbToYuv(RgbWhite()); + EXPECT_YUV_NEAR(yuv_white, YuvWhite()); + + Color yuv_r = bt2100RgbToYuv(RgbRed()); + EXPECT_YUV_NEAR(yuv_r, Bt2100YuvRed()); + + Color yuv_g = bt2100RgbToYuv(RgbGreen()); + EXPECT_YUV_NEAR(yuv_g, Bt2100YuvGreen()); + + Color yuv_b = bt2100RgbToYuv(RgbBlue()); + EXPECT_YUV_NEAR(yuv_b, Bt2100YuvBlue()); +} + +TEST_F(RecoveryMapMathTest, Bt2100RgbYuvRoundtrip) { + Color rgb_black = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlack())); + EXPECT_RGB_NEAR(rgb_black, RgbBlack()); + + Color rgb_white = bt2100YuvToRgb(bt2100RgbToYuv(RgbWhite())); + EXPECT_RGB_NEAR(rgb_white, RgbWhite()); + + Color rgb_r = bt2100YuvToRgb(bt2100RgbToYuv(RgbRed())); + EXPECT_RGB_NEAR(rgb_r, RgbRed()); + + Color rgb_g = bt2100YuvToRgb(bt2100RgbToYuv(RgbGreen())); + EXPECT_RGB_NEAR(rgb_g, RgbGreen()); + + Color rgb_b = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlue())); + EXPECT_RGB_NEAR(rgb_b, RgbBlue()); +} + +TEST_F(RecoveryMapMathTest, HlgOetf) { + EXPECT_FLOAT_EQ(hlgOetf(0.0f), 0.0f); + EXPECT_NEAR(hlgOetf(0.04167f), 0.35357f, ComparisonEpsilon()); + EXPECT_NEAR(hlgOetf(0.08333f), 0.5f, ComparisonEpsilon()); + EXPECT_NEAR(hlgOetf(0.5f), 0.87164f, ComparisonEpsilon()); + EXPECT_FLOAT_EQ(hlgOetf(1.0f), 1.0f); + + Color e = {{{ 0.04167f, 0.08333f, 0.5f }}}; + Color e_gamma = {{{ 0.35357f, 0.5f, 0.87164f }}}; + EXPECT_RGB_NEAR(hlgOetf(e), e_gamma); +} + +TEST_F(RecoveryMapMathTest, HlgInvOetf) { + EXPECT_FLOAT_EQ(hlgInvOetf(0.0f), 0.0f); + EXPECT_NEAR(hlgInvOetf(0.25f), 0.02083f, ComparisonEpsilon()); + EXPECT_NEAR(hlgInvOetf(0.5f), 0.08333f, ComparisonEpsilon()); + EXPECT_NEAR(hlgInvOetf(0.75f), 0.26496f, ComparisonEpsilon()); + EXPECT_FLOAT_EQ(hlgInvOetf(1.0f), 1.0f); + + Color e_gamma = {{{ 0.25f, 0.5f, 0.75f }}}; + Color e = {{{ 0.02083f, 0.08333f, 0.26496f }}}; + EXPECT_RGB_NEAR(hlgInvOetf(e_gamma), e); +} + +TEST_F(RecoveryMapMathTest, HlgTransferFunctionRoundtrip) { + EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(0.0f)), 0.0f); + EXPECT_NEAR(hlgInvOetf(hlgOetf(0.04167f)), 0.04167f, ComparisonEpsilon()); + EXPECT_NEAR(hlgInvOetf(hlgOetf(0.08333f)), 0.08333f, ComparisonEpsilon()); + EXPECT_NEAR(hlgInvOetf(hlgOetf(0.5f)), 0.5f, ComparisonEpsilon()); + EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(1.0f)), 1.0f); +} + +TEST_F(RecoveryMapMathTest, PqOetf) { + EXPECT_FLOAT_EQ(pqOetf(0.0f), 0.0f); + EXPECT_NEAR(pqOetf(0.01f), 0.50808f, ComparisonEpsilon()); + EXPECT_NEAR(pqOetf(0.5f), 0.92655f, ComparisonEpsilon()); + EXPECT_NEAR(pqOetf(0.99f), 0.99895f, ComparisonEpsilon()); + EXPECT_FLOAT_EQ(pqOetf(1.0f), 1.0f); + + Color e = {{{ 0.01f, 0.5f, 0.99f }}}; + Color e_gamma = {{{ 0.50808f, 0.92655f, 0.99895f }}}; + EXPECT_RGB_NEAR(pqOetf(e), e_gamma); +} + +TEST_F(RecoveryMapMathTest, PqInvOetf) { + EXPECT_FLOAT_EQ(pqInvOetf(0.0f), 0.0f); + EXPECT_NEAR(pqInvOetf(0.01f), 2.31017e-7f, ComparisonEpsilon()); + EXPECT_NEAR(pqInvOetf(0.5f), 0.00922f, ComparisonEpsilon()); + EXPECT_NEAR(pqInvOetf(0.99f), 0.90903f, ComparisonEpsilon()); + EXPECT_FLOAT_EQ(pqInvOetf(1.0f), 1.0f); + + Color e_gamma = {{{ 0.01f, 0.5f, 0.99f }}}; + Color e = {{{ 2.31017e-7f, 0.00922f, 0.90903f }}}; + EXPECT_RGB_NEAR(pqInvOetf(e_gamma), e); +} + +TEST_F(RecoveryMapMathTest, PqTransferFunctionRoundtrip) { + EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(0.0f)), 0.0f); + EXPECT_NEAR(pqInvOetf(pqOetf(0.01f)), 0.01f, ComparisonEpsilon()); + EXPECT_NEAR(pqInvOetf(pqOetf(0.5f)), 0.5f, ComparisonEpsilon()); + EXPECT_NEAR(pqInvOetf(pqOetf(0.99f)), 0.99f, ComparisonEpsilon()); + EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(1.0f)), 1.0f); +} + +TEST_F(RecoveryMapMathTest, ColorConversionLookup) { + EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_UNSPECIFIED), + nullptr); + EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_BT709), + identityConversion); + EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_P3), + p3ToBt709); + EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_BT2100), + bt2100ToBt709); + + EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_UNSPECIFIED), + nullptr); + EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_BT709), + bt709ToP3); + EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_P3), + identityConversion); + EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_BT2100), + bt2100ToP3); + + EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_UNSPECIFIED), + nullptr); + EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_BT709), + bt709ToBt2100); + EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_P3), + p3ToBt2100); + EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_BT2100), + identityConversion); + + EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_UNSPECIFIED), + nullptr); + EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_BT709), + nullptr); + EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_P3), + nullptr); + EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_BT2100), + nullptr); +} + +TEST_F(RecoveryMapMathTest, EncodeRecovery) { + EXPECT_EQ(encodeRecovery(0.0f, 0.0f, 4.0f), 127); + EXPECT_EQ(encodeRecovery(0.0f, 1.0f, 4.0f), 127); + EXPECT_EQ(encodeRecovery(1.0f, 0.0f, 4.0f), 0); + EXPECT_EQ(encodeRecovery(0.5f, 0.0f, 4.0f), 0); + + EXPECT_EQ(encodeRecovery(1.0f, 1.0f, 4.0f), 127); + EXPECT_EQ(encodeRecovery(1.0f, 4.0f, 4.0f), 255); + EXPECT_EQ(encodeRecovery(1.0f, 5.0f, 4.0f), 255); + EXPECT_EQ(encodeRecovery(4.0f, 1.0f, 4.0f), 0); + EXPECT_EQ(encodeRecovery(4.0f, 0.5f, 4.0f), 0); + EXPECT_EQ(encodeRecovery(1.0f, 2.0f, 4.0f), 191); + EXPECT_EQ(encodeRecovery(2.0f, 1.0f, 4.0f), 63); + + EXPECT_EQ(encodeRecovery(1.0f, 2.0f, 2.0f), 255); + EXPECT_EQ(encodeRecovery(2.0f, 1.0f, 2.0f), 0); + EXPECT_EQ(encodeRecovery(1.0f, 1.41421f, 2.0f), 191); + EXPECT_EQ(encodeRecovery(1.41421f, 1.0f, 2.0f), 63); + + EXPECT_EQ(encodeRecovery(1.0f, 8.0f, 8.0f), 255); + EXPECT_EQ(encodeRecovery(8.0f, 1.0f, 8.0f), 0); + EXPECT_EQ(encodeRecovery(1.0f, 2.82843f, 8.0f), 191); + EXPECT_EQ(encodeRecovery(2.82843f, 1.0f, 8.0f), 63); +} + +TEST_F(RecoveryMapMathTest, ApplyRecovery) { + EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), -1.0f, 4.0f), RgbBlack()); + EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.0f, 4.0f), RgbBlack()); + EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 1.0f, 4.0f), RgbBlack()); + + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -1.0f, 4.0f), RgbWhite() / 4.0f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -0.5f, 4.0f), RgbWhite() / 2.0f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, 4.0f), RgbWhite()); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, 4.0f), RgbWhite() * 2.0f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, 4.0f), RgbWhite() * 4.0f); + + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -1.0f, 2.0f), RgbWhite() / 2.0f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -0.5f, 2.0f), RgbWhite() / 1.41421f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, 2.0f), RgbWhite()); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, 2.0f), RgbWhite() * 1.41421f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, 2.0f), RgbWhite() * 2.0f); + + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -1.0f, 8.0f), RgbWhite() / 8.0f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -0.5f, 8.0f), RgbWhite() / 2.82843f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, 8.0f), RgbWhite()); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, 8.0f), RgbWhite() * 2.82843f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, 8.0f), RgbWhite() * 8.0f); + + Color e = {{{ 0.0f, 0.5f, 1.0f }}}; + + EXPECT_RGB_NEAR(applyRecovery(e, -1.0f, 4.0f), e / 4.0f); + EXPECT_RGB_NEAR(applyRecovery(e, -0.5f, 4.0f), e / 2.0f); + EXPECT_RGB_NEAR(applyRecovery(e, 0.0f, 4.0f), e); + EXPECT_RGB_NEAR(applyRecovery(e, 0.5f, 4.0f), e * 2.0f); + EXPECT_RGB_NEAR(applyRecovery(e, 1.0f, 4.0f), e * 4.0f); +} + +TEST_F(RecoveryMapMathTest, GetYuv420Pixel) { + jpegr_uncompressed_struct image = Yuv420Image(); + Color (*colors)[4] = Yuv420Colors(); + + for (size_t y = 0; y < 4; ++y) { + for (size_t x = 0; x < 4; ++x) { + EXPECT_YUV_NEAR(getYuv420Pixel(&image, x, y), colors[y][x]); + } + } +} + +TEST_F(RecoveryMapMathTest, GetP010Pixel) { + jpegr_uncompressed_struct image = P010Image(); + Color (*colors)[4] = P010Colors(); + + for (size_t y = 0; y < 4; ++y) { + for (size_t x = 0; x < 4; ++x) { + EXPECT_YUV_NEAR(getP010Pixel(&image, x, y), colors[y][x]); + } + } +} + +TEST_F(RecoveryMapMathTest, SampleYuv420) { + jpegr_uncompressed_struct image = Yuv420Image(); + Color (*colors)[4] = Yuv420Colors(); + + static const size_t kMapScaleFactor = 2; + for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) { + for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) { + Color min = {{{ 1.0f, 1.0f, 1.0f }}}; + Color max = {{{ -1.0f, -1.0f, -1.0f }}}; + + for (size_t dy = 0; dy < kMapScaleFactor; ++dy) { + for (size_t dx = 0; dx < kMapScaleFactor; ++dx) { + Color e = colors[y * kMapScaleFactor + dy][x * kMapScaleFactor + dx]; + min = ColorMin(min, e); + max = ColorMax(max, e); + } + } + + // Instead of reimplementing the sampling algorithm, confirm that the + // sample output is within the range of the min and max of the nearest + // points. + EXPECT_YUV_BETWEEN(sampleYuv420(&image, kMapScaleFactor, x, y), min, max); + } + } +} + +TEST_F(RecoveryMapMathTest, SampleP010) { + jpegr_uncompressed_struct image = P010Image(); + Color (*colors)[4] = P010Colors(); + + static const size_t kMapScaleFactor = 2; + for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) { + for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) { + Color min = {{{ 1.0f, 1.0f, 1.0f }}}; + Color max = {{{ -1.0f, -1.0f, -1.0f }}}; + + for (size_t dy = 0; dy < kMapScaleFactor; ++dy) { + for (size_t dx = 0; dx < kMapScaleFactor; ++dx) { + Color e = colors[y * kMapScaleFactor + dy][x * kMapScaleFactor + dx]; + min = ColorMin(min, e); + max = ColorMax(max, e); + } + } + + // Instead of reimplementing the sampling algorithm, confirm that the + // sample output is within the range of the min and max of the nearest + // points. + EXPECT_YUV_BETWEEN(sampleP010(&image, kMapScaleFactor, x, y), min, max); + } + } +} + +TEST_F(RecoveryMapMathTest, SampleMap) { + jpegr_uncompressed_struct image = MapImage(); + float (*values)[4] = MapValues(); + + static const size_t kMapScaleFactor = 2; + for (size_t y = 0; y < 4 * kMapScaleFactor; ++y) { + for (size_t x = 0; x < 4 * kMapScaleFactor; ++x) { + size_t x_base = x / kMapScaleFactor; + size_t y_base = y / kMapScaleFactor; + + float min = 1.0f; + float max = -1.0f; + + min = fmin(min, values[y_base][x_base]); + max = fmax(max, values[y_base][x_base]); + if (y_base + 1 < 4) { + min = fmin(min, values[y_base + 1][x_base]); + max = fmax(max, values[y_base + 1][x_base]); + } + if (x_base + 1 < 4) { + min = fmin(min, values[y_base][x_base + 1]); + max = fmax(max, values[y_base][x_base + 1]); + } + if (y_base + 1 < 4 && x_base + 1 < 4) { + min = fmin(min, values[y_base + 1][x_base + 1]); + max = fmax(max, values[y_base + 1][x_base + 1]); + } + + // Instead of reimplementing the sampling algorithm, confirm that the + // sample output is within the range of the min and max of the nearest + // points. + EXPECT_THAT(sampleMap(&image, kMapScaleFactor, x, y), + testing::AllOf(testing::Ge(min), testing::Le(max))); + } + } +} + +TEST_F(RecoveryMapMathTest, ColorToRgba1010102) { + EXPECT_EQ(colorToRgba1010102(RgbBlack()), 0x3 << 30); + EXPECT_EQ(colorToRgba1010102(RgbWhite()), 0xFFFFFFFF); + EXPECT_EQ(colorToRgba1010102(RgbRed()), 0x3 << 30 | 0x3ff); + EXPECT_EQ(colorToRgba1010102(RgbGreen()), 0x3 << 30 | 0x3ff << 10); + EXPECT_EQ(colorToRgba1010102(RgbBlue()), 0x3 << 30 | 0x3ff << 20); + + Color e_gamma = {{{ 0.1f, 0.2f, 0.3f }}}; + EXPECT_EQ(colorToRgba1010102(e_gamma), + 0x3 << 30 + | static_cast(0.1f * static_cast(0x3ff)) + | static_cast(0.2f * static_cast(0x3ff)) << 10 + | static_cast(0.3f * static_cast(0x3ff)) << 20); +} + +TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgb) { + EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), srgbLuminance), + 0.0f); + EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), srgbLuminance), + kSdrWhiteNits); + EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), srgbLuminance), + srgbLuminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon()); + EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), srgbLuminance), + srgbLuminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon()); + EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), srgbLuminance), + srgbLuminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon()); +} + +TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgbP3) { + EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), p3Luminance), + 0.0f); + EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), p3Luminance), + kSdrWhiteNits); + EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), p3Luminance), + p3Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon()); + EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), p3Luminance), + p3Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon()); + EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), p3Luminance), + p3Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon()); +} + +TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgbBt2100) { + EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), bt2100Luminance), + 0.0f); + EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), bt2100Luminance), + kSdrWhiteNits); + EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), bt2100Luminance), + bt2100Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon()); + EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), bt2100Luminance), + bt2100Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon()); + EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), bt2100Luminance), + bt2100Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon()); +} + +TEST_F(RecoveryMapMathTest, GenerateMapLuminanceHlg) { + EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), hlgInvOetf, identityConversion, + bt2100Luminance, kHlgMaxNits), + 0.0f); + EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), hlgInvOetf, identityConversion, + bt2100Luminance, kHlgMaxNits), + kHlgMaxNits); + EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), hlgInvOetf, identityConversion, + bt2100Luminance, kHlgMaxNits), + bt2100Luminance(RgbRed()) * kHlgMaxNits, LuminanceEpsilon()); + EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), hlgInvOetf, identityConversion, + bt2100Luminance, kHlgMaxNits), + bt2100Luminance(RgbGreen()) * kHlgMaxNits, LuminanceEpsilon()); + EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), hlgInvOetf, identityConversion, + bt2100Luminance, kHlgMaxNits), + bt2100Luminance(RgbBlue()) * kHlgMaxNits, LuminanceEpsilon()); +} + +TEST_F(RecoveryMapMathTest, GenerateMapLuminancePq) { + EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), pqInvOetf, identityConversion, + bt2100Luminance, kPqMaxNits), + 0.0f); + EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), pqInvOetf, identityConversion, + bt2100Luminance, kPqMaxNits), + kPqMaxNits); + EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), pqInvOetf, identityConversion, + bt2100Luminance, kPqMaxNits), + bt2100Luminance(RgbRed()) * kPqMaxNits, LuminanceEpsilon()); + EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), pqInvOetf, identityConversion, + bt2100Luminance, kPqMaxNits), + bt2100Luminance(RgbGreen()) * kPqMaxNits, LuminanceEpsilon()); + EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), pqInvOetf, identityConversion, + bt2100Luminance, kPqMaxNits), + bt2100Luminance(RgbBlue()) * kPqMaxNits, LuminanceEpsilon()); +} + +//Color Recover(Color yuv_gamma, float recovery, float range_scaling_factor) { +TEST_F(RecoveryMapMathTest, ApplyMap) { + EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, 8.0f), + RgbWhite() * 8.0f); + EXPECT_RGB_EQ(Recover(YuvBlack(), 1.0f, 8.0f), + RgbBlack()); + EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 1.0f, 8.0f), + RgbRed() * 8.0f); + EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 1.0f, 8.0f), + RgbGreen() * 8.0f); + EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 1.0f, 8.0f), + RgbBlue() * 8.0f); + + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, 8.0f), + RgbWhite() * sqrt(8.0f)); + EXPECT_RGB_EQ(Recover(YuvBlack(), 0.5f, 8.0f), + RgbBlack()); + EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.5f, 8.0f), + RgbRed() * sqrt(8.0f)); + EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.5f, 8.0f), + RgbGreen() * sqrt(8.0f)); + EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.5f, 8.0f), + RgbBlue() * sqrt(8.0f)); + + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, 8.0f), + RgbWhite()); + EXPECT_RGB_EQ(Recover(YuvBlack(), 0.0f, 8.0f), + RgbBlack()); + EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.0f, 8.0f), + RgbRed()); + EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, 8.0f), + RgbGreen()); + EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.0f, 8.0f), + RgbBlue()); + + EXPECT_RGB_EQ(Recover(YuvWhite(), -0.5f, 8.0f), + RgbWhite() / sqrt(8.0f)); + EXPECT_RGB_EQ(Recover(YuvBlack(), -0.5f, 8.0f), + RgbBlack()); + EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), -0.5f, 8.0f), + RgbRed() / sqrt(8.0f)); + EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), -0.5f, 8.0f), + RgbGreen() / sqrt(8.0f)); + EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), -0.5f, 8.0f), + RgbBlue() / sqrt(8.0f)); + + EXPECT_RGB_EQ(Recover(YuvWhite(), -1.0f, 8.0f), + RgbWhite() / 8.0f); + EXPECT_RGB_EQ(Recover(YuvBlack(), -1.0f, 8.0f), + RgbBlack()); + EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), -1.0f, 8.0f), + RgbRed() / 8.0f); + EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), -1.0f, 8.0f), + RgbGreen() / 8.0f); + EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), -1.0f, 8.0f), + RgbBlue() / 8.0f); +} + +} // namespace android::recoverymap -- GitLab From 5024482df5f480663f4ec0ec25603bfe2ebfdbd8 Mon Sep 17 00:00:00 2001 From: Ben Murdoch Date: Mon, 31 Oct 2022 10:41:01 +0000 Subject: [PATCH 0552/1310] Add glue for Gestures logging, and link gestures stack in input reader. Bug: 251196347 Test: Treehugger Change-Id: I4307f577ecd4338d16af8264fe05293c1d2e5920 --- services/inputflinger/reader/Android.bp | 3 ++ .../mapper/gestures/GesturesLogging.cpp | 41 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index cf020161cc..46e86de84c 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -58,6 +58,7 @@ filegroup { "mapper/accumulator/MultiTouchMotionAccumulator.cpp", "mapper/accumulator/SingleTouchMotionAccumulator.cpp", "mapper/accumulator/TouchButtonAccumulator.cpp", + "mapper/gestures/GesturesLogging.cpp", ], } @@ -69,12 +70,14 @@ cc_defaults { "libcap", "libcrypto", "libcutils", + "libjsoncpp", "liblog", "libstatslog", "libutils", ], static_libs: [ "libc++fs", + "libchrome-gestures", "libui-types", ], header_libs: [ diff --git a/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp b/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp new file mode 100644 index 0000000000..81b4968df8 --- /dev/null +++ b/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LOG_TAG +#define LOG_TAG "Gestures" +#endif + +#include + +#include + +#include "include/gestures.h" + +extern "C" { + +void gestures_log(int verb, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + if (verb == GESTURES_LOG_ERROR) { + LOG_PRI_VA(ANDROID_LOG_ERROR, LOG_TAG, fmt, args); + } else if (verb == GESTURES_LOG_INFO) { + LOG_PRI_VA(ANDROID_LOG_INFO, LOG_TAG, fmt, args); + } else { + LOG_PRI_VA(ANDROID_LOG_DEBUG, LOG_TAG, fmt, args); + } + va_end(args); +} +} -- GitLab From b57f170fb5d5c26eaad3f732de4153114ad27ac9 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Mon, 28 Nov 2022 15:34:22 +0000 Subject: [PATCH 0553/1310] inputflinger_tests: Put `FakePointerController` in its own file I would like to be able to put automated tests for the new `TouchpadEventMapper` in their own file, rather than InputReader_tests.cpp. To do this I'll need some of the test utilities in their own files, too. Aside from extracting the `FakePointerController` definition into its own file and putting method bodies into a .cpp file, I've also moved private members into one place in the header file, and initialized them there to allow the default constructor to be used. Bug: 251196347 Test: atest inputflinger_tests Change-Id: I8f9704c543a07eae5b7135990139ac37eda82ae5 --- services/inputflinger/tests/Android.bp | 1 + .../tests/FakePointerController.cpp | 93 +++++++++++++++++++ .../tests/FakePointerController.h | 60 ++++++++++++ .../inputflinger/tests/InputReader_test.cpp | 92 +----------------- 4 files changed, 155 insertions(+), 91 deletions(-) create mode 100644 services/inputflinger/tests/FakePointerController.cpp create mode 100644 services/inputflinger/tests/FakePointerController.h diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 6d11e602a1..e0466d5ea8 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -41,6 +41,7 @@ cc_test { "BlockingQueue_test.cpp", "EventHub_test.cpp", "FakeEventHub.cpp", + "FakePointerController.cpp", "FocusResolver_test.cpp", "InputProcessor_test.cpp", "InputProcessorConverter_test.cpp", diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp new file mode 100644 index 0000000000..635366bff2 --- /dev/null +++ b/services/inputflinger/tests/FakePointerController.cpp @@ -0,0 +1,93 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "FakePointerController.h" + +namespace android { + +void FakePointerController::setBounds(float minX, float minY, float maxX, float maxY) { + mHaveBounds = true; + mMinX = minX; + mMinY = minY; + mMaxX = maxX; + mMaxY = maxY; +} + +const std::map>& FakePointerController::getSpots() { + return mSpotsByDisplay; +} + +void FakePointerController::setPosition(float x, float y) { + mX = x; + mY = y; +} + +void FakePointerController::setButtonState(int32_t buttonState) { + mButtonState = buttonState; +} + +int32_t FakePointerController::getButtonState() const { + return mButtonState; +} + +void FakePointerController::getPosition(float* outX, float* outY) const { + *outX = mX; + *outY = mY; +} + +int32_t FakePointerController::getDisplayId() const { + return mDisplayId; +} + +void FakePointerController::setDisplayViewport(const DisplayViewport& viewport) { + mDisplayId = viewport.displayId; +} + +bool FakePointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX, + float* outMaxY) const { + *outMinX = mMinX; + *outMinY = mMinY; + *outMaxX = mMaxX; + *outMaxY = mMaxY; + return mHaveBounds; +} + +void FakePointerController::move(float deltaX, float deltaY) { + mX += deltaX; + if (mX < mMinX) mX = mMinX; + if (mX > mMaxX) mX = mMaxX; + mY += deltaY; + if (mY < mMinY) mY = mMinY; + if (mY > mMaxY) mY = mMaxY; +} + +void FakePointerController::setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits, + int32_t displayId) { + std::vector newSpots; + // Add spots for fingers that are down. + for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) { + uint32_t id = idBits.clearFirstMarkedBit(); + newSpots.push_back(id); + } + + mSpotsByDisplay[displayId] = newSpots; +} + +void FakePointerController::clearSpots() { + mSpotsByDisplay.clear(); +} + +} // namespace android diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h new file mode 100644 index 0000000000..f00870f13c --- /dev/null +++ b/services/inputflinger/tests/FakePointerController.h @@ -0,0 +1,60 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace android { + +class FakePointerController : public PointerControllerInterface { +public: + virtual ~FakePointerController() {} + + void setBounds(float minX, float minY, float maxX, float maxY); + const std::map>& getSpots(); + + void setPosition(float x, float y) override; + void setButtonState(int32_t buttonState) override; + int32_t getButtonState() const override; + void getPosition(float* outX, float* outY) const override; + int32_t getDisplayId() const override; + void setDisplayViewport(const DisplayViewport& viewport) override; + +private: + bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override; + void move(float deltaX, float deltaY) override; + void fade(Transition) override {} + void unfade(Transition) override {} + void setPresentation(Presentation) override {} + void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits, + int32_t displayId) override; + void clearSpots() override; + + bool mHaveBounds{false}; + float mMinX{0}, mMinY{0}, mMaxX{0}, mMaxY{0}; + float mX{0}, mY{0}; + int32_t mButtonState{0}; + int32_t mDisplayId{ADISPLAY_ID_DEFAULT}; + + std::map> mSpotsByDisplay; +}; + +} // namespace android diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 068216f695..2beb631db3 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -41,6 +41,7 @@ #include #include "FakeEventHub.h" +#include "FakePointerController.h" #include "TestConstants.h" #include "android/hardware/input/InputDeviceCountryCode.h" #include "input/DisplayViewport.h" @@ -148,97 +149,6 @@ static void assertAxisNotPresent(MultiTouchInputMapper& mapper, int axis) { } } -// --- FakePointerController --- - -class FakePointerController : public PointerControllerInterface { - bool mHaveBounds; - float mMinX, mMinY, mMaxX, mMaxY; - float mX, mY; - int32_t mButtonState; - int32_t mDisplayId; - -public: - FakePointerController() : - mHaveBounds(false), mMinX(0), mMinY(0), mMaxX(0), mMaxY(0), mX(0), mY(0), - mButtonState(0), mDisplayId(ADISPLAY_ID_DEFAULT) { - } - - virtual ~FakePointerController() {} - - void setBounds(float minX, float minY, float maxX, float maxY) { - mHaveBounds = true; - mMinX = minX; - mMinY = minY; - mMaxX = maxX; - mMaxY = maxY; - } - - void setPosition(float x, float y) override { - mX = x; - mY = y; - } - - void setButtonState(int32_t buttonState) override { mButtonState = buttonState; } - - int32_t getButtonState() const override { return mButtonState; } - - void getPosition(float* outX, float* outY) const override { - *outX = mX; - *outY = mY; - } - - int32_t getDisplayId() const override { return mDisplayId; } - - void setDisplayViewport(const DisplayViewport& viewport) override { - mDisplayId = viewport.displayId; - } - - const std::map>& getSpots() { - return mSpotsByDisplay; - } - -private: - bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override { - *outMinX = mMinX; - *outMinY = mMinY; - *outMaxX = mMaxX; - *outMaxY = mMaxY; - return mHaveBounds; - } - - void move(float deltaX, float deltaY) override { - mX += deltaX; - if (mX < mMinX) mX = mMinX; - if (mX > mMaxX) mX = mMaxX; - mY += deltaY; - if (mY < mMinY) mY = mMinY; - if (mY > mMaxY) mY = mMaxY; - } - - void fade(Transition) override {} - - void unfade(Transition) override {} - - void setPresentation(Presentation) override {} - - void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits, - int32_t displayId) override { - std::vector newSpots; - // Add spots for fingers that are down. - for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) { - uint32_t id = idBits.clearFirstMarkedBit(); - newSpots.push_back(id); - } - - mSpotsByDisplay[displayId] = newSpots; - } - - void clearSpots() override { mSpotsByDisplay.clear(); } - - std::map> mSpotsByDisplay; -}; - - // --- FakeInputReaderPolicy --- class FakeInputReaderPolicy : public InputReaderPolicyInterface { -- GitLab From 6b5fbc58b017e5f96fe9045ecb422c63bfb51f9d Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Mon, 28 Nov 2022 16:37:43 +0000 Subject: [PATCH 0554/1310] inputflinger_tests: Put `FakeInputReaderPolicy` in its own file I would like to be able to put automated tests for the new `TouchpadEventMapper` in their own file, rather than InputReader_tests.cpp. To do this I'll need some of the test utilities in their own files, too. Bug: 251196347 Test: atest inputflinger_tests Change-Id: Ibbe27ced5adec2186decd8fb1c359b8ed4cbf870 --- services/inputflinger/tests/Android.bp | 1 + .../tests/FakeInputReaderPolicy.cpp | 237 ++++++++++++++++++ .../tests/FakeInputReaderPolicy.h | 101 ++++++++ .../inputflinger/tests/InputReader_test.cpp | 217 +--------------- 4 files changed, 340 insertions(+), 216 deletions(-) create mode 100644 services/inputflinger/tests/FakeInputReaderPolicy.cpp create mode 100644 services/inputflinger/tests/FakeInputReaderPolicy.h diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index e0466d5ea8..611fe3d625 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -41,6 +41,7 @@ cc_test { "BlockingQueue_test.cpp", "EventHub_test.cpp", "FakeEventHub.cpp", + "FakeInputReaderPolicy.cpp", "FakePointerController.cpp", "FocusResolver_test.cpp", "InputProcessor_test.cpp", diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp new file mode 100644 index 0000000000..5c6a1b8f6d --- /dev/null +++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp @@ -0,0 +1,237 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "FakeInputReaderPolicy.h" + +#include +#include + +#include "TestConstants.h" + +namespace android { + +void FakeInputReaderPolicy::assertInputDevicesChanged() { + waitForInputDevices([](bool devicesChanged) { + if (!devicesChanged) { + FAIL() << "Timed out waiting for notifyInputDevicesChanged() to be called."; + } + }); +} + +void FakeInputReaderPolicy::assertInputDevicesNotChanged() { + waitForInputDevices([](bool devicesChanged) { + if (devicesChanged) { + FAIL() << "Expected notifyInputDevicesChanged() to not be called."; + } + }); +} + +void FakeInputReaderPolicy::assertStylusGestureNotified(int32_t deviceId) { + std::scoped_lock lock(mLock); + ASSERT_TRUE(mStylusGestureNotified); + ASSERT_EQ(deviceId, *mStylusGestureNotified); + mStylusGestureNotified.reset(); +} + +void FakeInputReaderPolicy::assertStylusGestureNotNotified() { + std::scoped_lock lock(mLock); + ASSERT_FALSE(mStylusGestureNotified); +} + +void FakeInputReaderPolicy::clearViewports() { + mViewports.clear(); + mConfig.setDisplayViewports(mViewports); +} + +std::optional FakeInputReaderPolicy::getDisplayViewportByUniqueId( + const std::string& uniqueId) const { + return mConfig.getDisplayViewportByUniqueId(uniqueId); +} +std::optional FakeInputReaderPolicy::getDisplayViewportByType( + ViewportType type) const { + return mConfig.getDisplayViewportByType(type); +} + +std::optional FakeInputReaderPolicy::getDisplayViewportByPort( + uint8_t displayPort) const { + return mConfig.getDisplayViewportByPort(displayPort); +} + +void FakeInputReaderPolicy::addDisplayViewport(DisplayViewport viewport) { + mViewports.push_back(std::move(viewport)); + mConfig.setDisplayViewports(mViewports); +} + +void FakeInputReaderPolicy::addDisplayViewport(int32_t displayId, int32_t width, int32_t height, + int32_t orientation, bool isActive, + const std::string& uniqueId, + std::optional physicalPort, + ViewportType type) { + const bool isRotated = + (orientation == DISPLAY_ORIENTATION_90 || orientation == DISPLAY_ORIENTATION_270); + DisplayViewport v; + v.displayId = displayId; + v.orientation = orientation; + v.logicalLeft = 0; + v.logicalTop = 0; + v.logicalRight = isRotated ? height : width; + v.logicalBottom = isRotated ? width : height; + v.physicalLeft = 0; + v.physicalTop = 0; + v.physicalRight = isRotated ? height : width; + v.physicalBottom = isRotated ? width : height; + v.deviceWidth = isRotated ? height : width; + v.deviceHeight = isRotated ? width : height; + v.isActive = isActive; + v.uniqueId = uniqueId; + v.physicalPort = physicalPort; + v.type = type; + + addDisplayViewport(v); +} + +bool FakeInputReaderPolicy::updateViewport(const DisplayViewport& viewport) { + size_t count = mViewports.size(); + for (size_t i = 0; i < count; i++) { + const DisplayViewport& currentViewport = mViewports[i]; + if (currentViewport.displayId == viewport.displayId) { + mViewports[i] = viewport; + mConfig.setDisplayViewports(mViewports); + return true; + } + } + // no viewport found. + return false; +} + +void FakeInputReaderPolicy::addExcludedDeviceName(const std::string& deviceName) { + mConfig.excludedDeviceNames.push_back(deviceName); +} + +void FakeInputReaderPolicy::addInputPortAssociation(const std::string& inputPort, + uint8_t displayPort) { + mConfig.portAssociations.insert({inputPort, displayPort}); +} + +void FakeInputReaderPolicy::addInputUniqueIdAssociation(const std::string& inputUniqueId, + const std::string& displayUniqueId) { + mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId}); +} + +void FakeInputReaderPolicy::addDisabledDevice(int32_t deviceId) { + mConfig.disabledDevices.insert(deviceId); +} + +void FakeInputReaderPolicy::removeDisabledDevice(int32_t deviceId) { + mConfig.disabledDevices.erase(deviceId); +} + +void FakeInputReaderPolicy::setPointerController( + std::shared_ptr controller) { + mPointerController = std::move(controller); +} + +const InputReaderConfiguration* FakeInputReaderPolicy::getReaderConfiguration() const { + return &mConfig; +} + +const std::vector& FakeInputReaderPolicy::getInputDevices() const { + return mInputDevices; +} + +TouchAffineTransformation FakeInputReaderPolicy::getTouchAffineTransformation( + const std::string& inputDeviceDescriptor, int32_t surfaceRotation) { + return transform; +} + +void FakeInputReaderPolicy::setTouchAffineTransformation(const TouchAffineTransformation t) { + transform = t; +} + +PointerCaptureRequest FakeInputReaderPolicy::setPointerCapture(bool enabled) { + mConfig.pointerCaptureRequest = {enabled, mNextPointerCaptureSequenceNumber++}; + return mConfig.pointerCaptureRequest; +} + +void FakeInputReaderPolicy::setShowTouches(bool enabled) { + mConfig.showTouches = enabled; +} + +void FakeInputReaderPolicy::setDefaultPointerDisplayId(int32_t pointerDisplayId) { + mConfig.defaultPointerDisplayId = pointerDisplayId; +} + +void FakeInputReaderPolicy::setPointerGestureEnabled(bool enabled) { + mConfig.pointerGesturesEnabled = enabled; +} + +float FakeInputReaderPolicy::getPointerGestureMovementSpeedRatio() { + return mConfig.pointerGestureMovementSpeedRatio; +} + +float FakeInputReaderPolicy::getPointerGestureZoomSpeedRatio() { + return mConfig.pointerGestureZoomSpeedRatio; +} + +void FakeInputReaderPolicy::setVelocityControlParams(const VelocityControlParameters& params) { + mConfig.pointerVelocityControlParameters = params; + mConfig.wheelVelocityControlParameters = params; +} + +void FakeInputReaderPolicy::getReaderConfiguration(InputReaderConfiguration* outConfig) { + *outConfig = mConfig; +} + +std::shared_ptr FakeInputReaderPolicy::obtainPointerController( + int32_t /*deviceId*/) { + return mPointerController; +} + +void FakeInputReaderPolicy::notifyInputDevicesChanged( + const std::vector& inputDevices) { + std::scoped_lock lock(mLock); + mInputDevices = inputDevices; + mInputDevicesChanged = true; + mDevicesChangedCondition.notify_all(); +} + +std::shared_ptr FakeInputReaderPolicy::getKeyboardLayoutOverlay( + const InputDeviceIdentifier&) { + return nullptr; +} + +std::string FakeInputReaderPolicy::getDeviceAlias(const InputDeviceIdentifier&) { + return ""; +} + +void FakeInputReaderPolicy::waitForInputDevices(std::function processDevicesChanged) { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + + const bool devicesChanged = + mDevicesChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) { + return mInputDevicesChanged; + }); + ASSERT_NO_FATAL_FAILURE(processDevicesChanged(devicesChanged)); + mInputDevicesChanged = false; +} + +void FakeInputReaderPolicy::notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) { + std::scoped_lock lock(mLock); + mStylusGestureNotified = deviceId; +} + +} // namespace android diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h new file mode 100644 index 0000000000..65fe08f87b --- /dev/null +++ b/services/inputflinger/tests/FakeInputReaderPolicy.h @@ -0,0 +1,101 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "FakePointerController.h" +#include "input/DisplayViewport.h" +#include "input/InputDevice.h" + +namespace android { + +class FakeInputReaderPolicy : public InputReaderPolicyInterface { +protected: + virtual ~FakeInputReaderPolicy() {} + +public: + FakeInputReaderPolicy() {} + + void assertInputDevicesChanged(); + void assertInputDevicesNotChanged(); + void assertStylusGestureNotified(int32_t deviceId); + void assertStylusGestureNotNotified(); + + virtual void clearViewports(); + std::optional getDisplayViewportByUniqueId(const std::string& uniqueId) const; + std::optional getDisplayViewportByType(ViewportType type) const; + std::optional getDisplayViewportByPort(uint8_t displayPort) const; + void addDisplayViewport(DisplayViewport viewport); + void addDisplayViewport(int32_t displayId, int32_t width, int32_t height, int32_t orientation, + bool isActive, const std::string& uniqueId, + std::optional physicalPort, ViewportType type); + bool updateViewport(const DisplayViewport& viewport); + void addExcludedDeviceName(const std::string& deviceName); + void addInputPortAssociation(const std::string& inputPort, uint8_t displayPort); + void addInputUniqueIdAssociation(const std::string& inputUniqueId, + const std::string& displayUniqueId); + void addDisabledDevice(int32_t deviceId); + void removeDisabledDevice(int32_t deviceId); + void setPointerController(std::shared_ptr controller); + const InputReaderConfiguration* getReaderConfiguration() const; + const std::vector& getInputDevices() const; + TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor, + int32_t surfaceRotation); + void setTouchAffineTransformation(const TouchAffineTransformation t); + PointerCaptureRequest setPointerCapture(bool enabled); + void setShowTouches(bool enabled); + void setDefaultPointerDisplayId(int32_t pointerDisplayId); + void setPointerGestureEnabled(bool enabled); + float getPointerGestureMovementSpeedRatio(); + float getPointerGestureZoomSpeedRatio(); + void setVelocityControlParams(const VelocityControlParameters& params); + +private: + void getReaderConfiguration(InputReaderConfiguration* outConfig) override; + std::shared_ptr obtainPointerController( + int32_t /*deviceId*/) override; + void notifyInputDevicesChanged(const std::vector& inputDevices) override; + std::shared_ptr getKeyboardLayoutOverlay( + const InputDeviceIdentifier&) override; + std::string getDeviceAlias(const InputDeviceIdentifier&) override; + void waitForInputDevices(std::function processDevicesChanged); + void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override; + + std::mutex mLock; + std::condition_variable mDevicesChangedCondition; + + InputReaderConfiguration mConfig; + std::shared_ptr mPointerController; + std::vector mInputDevices GUARDED_BY(mLock); + bool mInputDevicesChanged GUARDED_BY(mLock){false}; + std::vector mViewports; + TouchAffineTransformation transform; + std::optional mStylusGestureNotified GUARDED_BY(mLock){}; + + uint32_t mNextPointerCaptureSequenceNumber{0}; +}; + +} // namespace android diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 2beb631db3..8aaf066929 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -41,6 +41,7 @@ #include #include "FakeEventHub.h" +#include "FakeInputReaderPolicy.h" #include "FakePointerController.h" #include "TestConstants.h" #include "android/hardware/input/InputDeviceCountryCode.h" @@ -149,222 +150,6 @@ static void assertAxisNotPresent(MultiTouchInputMapper& mapper, int axis) { } } -// --- FakeInputReaderPolicy --- - -class FakeInputReaderPolicy : public InputReaderPolicyInterface { - std::mutex mLock; - std::condition_variable mDevicesChangedCondition; - - InputReaderConfiguration mConfig; - std::shared_ptr mPointerController; - std::vector mInputDevices GUARDED_BY(mLock); - bool mInputDevicesChanged GUARDED_BY(mLock){false}; - std::vector mViewports; - TouchAffineTransformation transform; - std::optional mStylusGestureNotified GUARDED_BY(mLock){}; - -protected: - virtual ~FakeInputReaderPolicy() {} - -public: - FakeInputReaderPolicy() { - } - - void assertInputDevicesChanged() { - waitForInputDevices([](bool devicesChanged) { - if (!devicesChanged) { - FAIL() << "Timed out waiting for notifyInputDevicesChanged() to be called."; - } - }); - } - - void assertInputDevicesNotChanged() { - waitForInputDevices([](bool devicesChanged) { - if (devicesChanged) { - FAIL() << "Expected notifyInputDevicesChanged() to not be called."; - } - }); - } - - void assertStylusGestureNotified(int32_t deviceId) { - std::scoped_lock lock(mLock); - ASSERT_TRUE(mStylusGestureNotified); - ASSERT_EQ(deviceId, *mStylusGestureNotified); - mStylusGestureNotified.reset(); - } - - void assertStylusGestureNotNotified() { - std::scoped_lock lock(mLock); - ASSERT_FALSE(mStylusGestureNotified); - } - - virtual void clearViewports() { - mViewports.clear(); - mConfig.setDisplayViewports(mViewports); - } - - std::optional getDisplayViewportByUniqueId(const std::string& uniqueId) const { - return mConfig.getDisplayViewportByUniqueId(uniqueId); - } - std::optional getDisplayViewportByType(ViewportType type) const { - return mConfig.getDisplayViewportByType(type); - } - - std::optional getDisplayViewportByPort(uint8_t displayPort) const { - return mConfig.getDisplayViewportByPort(displayPort); - } - - void addDisplayViewport(DisplayViewport viewport) { - mViewports.push_back(std::move(viewport)); - mConfig.setDisplayViewports(mViewports); - } - - void addDisplayViewport(int32_t displayId, int32_t width, int32_t height, int32_t orientation, - bool isActive, const std::string& uniqueId, - std::optional physicalPort, ViewportType type) { - const bool isRotated = - (orientation == DISPLAY_ORIENTATION_90 || orientation == DISPLAY_ORIENTATION_270); - DisplayViewport v; - v.displayId = displayId; - v.orientation = orientation; - v.logicalLeft = 0; - v.logicalTop = 0; - v.logicalRight = isRotated ? height : width; - v.logicalBottom = isRotated ? width : height; - v.physicalLeft = 0; - v.physicalTop = 0; - v.physicalRight = isRotated ? height : width; - v.physicalBottom = isRotated ? width : height; - v.deviceWidth = isRotated ? height : width; - v.deviceHeight = isRotated ? width : height; - v.isActive = isActive; - v.uniqueId = uniqueId; - v.physicalPort = physicalPort; - v.type = type; - - addDisplayViewport(v); - } - - bool updateViewport(const DisplayViewport& viewport) { - size_t count = mViewports.size(); - for (size_t i = 0; i < count; i++) { - const DisplayViewport& currentViewport = mViewports[i]; - if (currentViewport.displayId == viewport.displayId) { - mViewports[i] = viewport; - mConfig.setDisplayViewports(mViewports); - return true; - } - } - // no viewport found. - return false; - } - - void addExcludedDeviceName(const std::string& deviceName) { - mConfig.excludedDeviceNames.push_back(deviceName); - } - - void addInputPortAssociation(const std::string& inputPort, uint8_t displayPort) { - mConfig.portAssociations.insert({inputPort, displayPort}); - } - - void addInputUniqueIdAssociation(const std::string& inputUniqueId, - const std::string& displayUniqueId) { - mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId}); - } - - void addDisabledDevice(int32_t deviceId) { mConfig.disabledDevices.insert(deviceId); } - - void removeDisabledDevice(int32_t deviceId) { mConfig.disabledDevices.erase(deviceId); } - - void setPointerController(std::shared_ptr controller) { - mPointerController = std::move(controller); - } - - const InputReaderConfiguration* getReaderConfiguration() const { - return &mConfig; - } - - const std::vector& getInputDevices() const { - return mInputDevices; - } - - TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor, - int32_t surfaceRotation) { - return transform; - } - - void setTouchAffineTransformation(const TouchAffineTransformation t) { - transform = t; - } - - PointerCaptureRequest setPointerCapture(bool enabled) { - mConfig.pointerCaptureRequest = {enabled, mNextPointerCaptureSequenceNumber++}; - return mConfig.pointerCaptureRequest; - } - - void setShowTouches(bool enabled) { - mConfig.showTouches = enabled; - } - - void setDefaultPointerDisplayId(int32_t pointerDisplayId) { - mConfig.defaultPointerDisplayId = pointerDisplayId; - } - - void setPointerGestureEnabled(bool enabled) { mConfig.pointerGesturesEnabled = enabled; } - - float getPointerGestureMovementSpeedRatio() { return mConfig.pointerGestureMovementSpeedRatio; } - - float getPointerGestureZoomSpeedRatio() { return mConfig.pointerGestureZoomSpeedRatio; } - - void setVelocityControlParams(const VelocityControlParameters& params) { - mConfig.pointerVelocityControlParameters = params; - mConfig.wheelVelocityControlParameters = params; - } - -private: - uint32_t mNextPointerCaptureSequenceNumber = 0; - - void getReaderConfiguration(InputReaderConfiguration* outConfig) override { - *outConfig = mConfig; - } - - std::shared_ptr obtainPointerController( - int32_t /*deviceId*/) override { - return mPointerController; - } - - void notifyInputDevicesChanged(const std::vector& inputDevices) override { - std::scoped_lock lock(mLock); - mInputDevices = inputDevices; - mInputDevicesChanged = true; - mDevicesChangedCondition.notify_all(); - } - - std::shared_ptr getKeyboardLayoutOverlay( - const InputDeviceIdentifier&) override { - return nullptr; - } - - std::string getDeviceAlias(const InputDeviceIdentifier&) override { return ""; } - - void waitForInputDevices(std::function processDevicesChanged) { - std::unique_lock lock(mLock); - base::ScopedLockAssertion assumeLocked(mLock); - - const bool devicesChanged = - mDevicesChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) { - return mInputDevicesChanged; - }); - ASSERT_NO_FATAL_FAILURE(processDevicesChanged(devicesChanged)); - mInputDevicesChanged = false; - } - - void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override { - std::scoped_lock lock(mLock); - mStylusGestureNotified = deviceId; - } -}; - // --- FakeInputMapper --- class FakeInputMapper : public InputMapper { -- GitLab From 36c1e73d40f3eca546a0b9c983e37a5e7814808d Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Wed, 23 Nov 2022 01:25:34 +0000 Subject: [PATCH 0555/1310] libjpegrecoverymap: add unit test for encode decode Bug: b/252835416 Change-Id: Ie3c6dba85ba589cd432f510247caf13ef38371a2 Test: push the input files to `/sdcard/Documents` then run `atest libjpegdecoder_test` --- .../jpegrecoverymap/tests/data/jpeg_image.jpg | Bin 0 -> 24430 bytes .../tests/data/raw_p010_image.p010 | Bin 0 -> 2764800 bytes .../tests/recoverymap_test.cpp | 109 +++++++++++++++++- 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 libs/jpegrecoverymap/tests/data/jpeg_image.jpg create mode 100644 libs/jpegrecoverymap/tests/data/raw_p010_image.p010 diff --git a/libs/jpegrecoverymap/tests/data/jpeg_image.jpg b/libs/jpegrecoverymap/tests/data/jpeg_image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e2857425e7734496457a483e4f11ea284c5a8773 GIT binary patch literal 24430 zcmex=o+2Ft9N&FfxOr7#LU? z7#N}K3rq|Q{EX}%S%xeI1_l8}b_P)f28IF#1_mQWCI%4(28J*O28PUb7O>tn1_&@R zFfy_-G_*1@QZO>MGBC9=Heztk&ri=ubuKL_$w^JiEY47H_YGF4urP}^Ghq;8nAOAz zHsK5-1H)V>`w1fhgL((VRVg5YC$KZHFfcF}7#SHbPk`7yg^_`Q1N1`HFC*prahj0+%o z|NUoRU|In2H7LZGp=?lyF)e|p0Xdl&iOsSE;+`Xn3=FJL@#E$U3_MUaBS-@%WU+)E zwh(>Mz`!7&4$%bCI}H>=>I@8P-ay2^Fhb~rTnNou0-^ukW^iWUU}IxvW949HXXoVP z;NlVC<>BV$krWc<7m<~cmy?x}kx@|5Q&&*ZQI?U>FxS*EFf=hSkyp30wJ@^LGd3{- z8N$fP$;rdbBf-lnVWcRdXhbsje}F-dgXsb*12dx%1Ct;lvmoRDBMkBk42-OdU;yzP z0|O%yGYcylI|nBh_x~ddTLl=H7@3)wSeRK^Sy&ht7;71sm>F0ES%nl09od8f6WNst zMT{CJF62;l+IUbj=;8+zR; z@QBE$5`HGdRHf`Rr zb=&qGJ9iyAeB|h{<0np@x^(%<)oa&p+`RSh(c>pipFMx^^3}&rpTB(l_Wj4tUm$-m zGBAUE1tK6mL-Us)0}~??3kx#~JIG&*OywYt3$m~(8nOvF2C^p>3M&~ka)>xhT)6Qd zr?PR-2hpUWi(FzVCJ$9Vg1iRy8F3zKBFkrRk0JbZi-Cukk%38&S&+e=;rlL)MQb)J z>e67K5oVIC`|vAR^V8plUzUY;cmFf~w@b=BVNsX%j0MElxaRkNhOe*xz5i;uF!K9< zhS%5s-hY+t6)$A6^y8wnB7w2)34yL63`Af?p8GYmT2_RpikGPk4EQl|xvNO{rYc5PkpNm_79&tx1wZ|L_@&Q! zaq&Ote@jgI4!Vkj8#)qWJ0hB#HoyPRaDDyn{a3C{*{71}{n#~1W6`d@1H{DpqAm?4 z$$dY5g_Np*jB{EwgJ_kUWE1&z9}Df=vbEn4_d*{@4ueaaW6E{z4W z#vG}jxLSVtd;6uCaeDec&i{ImDcjVgu`a0zYCB)~TjT!>=PT{sZ+5?x{-0sqr~3P8 zix%yhd?V;z=y$n#o86QCF023fUj6U4jNd;089v|h|NZ7lNZ=dIdad8h^I}idUAF)8 zee%EGCbdvySy~$3MD|auJ$PR4$^Ojw|Ef>^XQ-WJ2UTV&DpJiAe`?=@=hKVgx19ga zVEg1h!@jffaD`o6wypY4;|q#U@6?Aos2`!y)m84$^iS(|SU$Zo9jnv(6TN?i-;w$B z&imWU{|wLf%>RCKWm5UXqBm!XdnV6X9j5TR{dL6krG6&21HGS=B&MoIs{LnJEp*XU z^!dC4*dh{(80lq(=-C>FT{|{?a9doMZL`5=UffNkqBT1wIv$kyqP8pQsh!Gyh7*k| z|1;dy|0^}6cf(1m3;w(QoEQ1e(6ac5{1@3P{~12$`F~vePpDs@{z7^1e})4)?T>9* zvC?kyt1b2uYfoGyq;i4XYUiKgcY5Q0c-c7qXK=jh|DS>B$$thZ`-d7I+5fVa+W+C6 z{3lzhi+QLTxPIyCr(VC-t&}s%_Y(POsWk1kzQdv}&3Oh`A`nzDFqU2Z8~C50YWWIX z`>XQbS5-Y&`*HKlL-nlnu})nYYx*W&D~?vKwSWB7UjAvo6=?Qet65X{)z$ft+gy>r zxRWnfMFJgYjRnB@*?;z_qqR6RINYr^Iw<7O1l%d%5CjeyM6}kTfMBG-|Dx#{RNFhU5gKK zX)J2MhV^dMud;t1@|AV@uf_kq`p1awGpn}nf3W`bia=M6b(Pet%m3_x zKmH3{s^4d~VnvO|+vGbp> zRl)pY@z&_l; zL;_ulMPN1+iv+sXdOpJl9#{zjj@uX+tCs%P2Qyna*jpTS{KmsURqt_(kn8(&FX8tYfu z;B5DT<95~8B>o+BTieus_`Ez+FIgYUnX#zr)Af4q1NBu~; z{QJ#6>pV0Tb!|R_E!%UUwnnC2y^b8?OY&R)+1Q3ZtNG91DiSh31(sUTY04Q zhE{n5Q|{jM&-!~TRc|bGjsJIY{_l_f(*J0{8Q;F_pIQ6xq~1|3t^W+)eEu`s@BeK7 z&>g|rzVy%O_k5mZ8aRAlyI22Nd_|G>MyEB7-}$dChncL&yZd%cU&!S1qP%o{xiIt{;${T)obCSw``B?e^`Ci=;|ZSWjD7jRna)STIPZ91-UD|8_sH7$lvwn z{OlL&9sV=4IzHn6`RB6z`#1CdxXOQ6VdMCp!G2cy-@lXpGjy!9+x$Ajes1lH&%d-U z*gp)YX#Q7ka{l*k_5Tcl?tirS1?tZ~oB5xiKIK2dBMttw0sl(BFFsG$*adc5oqt}x z*KfaQFY`Z9_73~!pMCx_?B7)XQKbIRf{*Nf{$}fNQp2yS{Lka7Z2on9;r|gL zr|_R){#oOH|4#mAI1>1ufvfq5{O6y`>hIsQ|Ix)vQ-`i!_4+89|KqNQ^h=ws=9)8S zl7H>|1%a+2=OxffAsB!77Un2T4D@CPV!)#{{r-Q3_3_{9SFY8p@whboSl5<7*HT%W zO?9*;JQ)=>v=}0_XoIOA#@!@b^$}Pc5l>v__y1>z+yCxALvU2kkBLk5dqp!Ab-l92 z8{HZvy zew@C!S{mIYo)Ph@8ktGz+E^^f> zg1fZy!7by89s3{Kzh1bcj7wwD&Mu-%M2aTS`St(y*5CcNY|GM*%3kqOS{GeKuatq~ z71n&lLkpb!e*Dtnm1>}9yY6e>@}uYR-*5H$Cs{=T!*Fz}MP<$x8Qz@~7-Dx@{d@2yliF?m@0LHwUj53V|LLFbpL72++>AK?h&M(KV!dXN&i0X=rUwz6y>qY-1{+-(M z&;Mxs%(||HP@~^&sxP%ycE3_6fBMJz&-`!Jq<^Z@{&V_EC_mI_s8bhr{EI(YKchZ1 z*?#iJ_-FBpRzN(M0d?tD8TU{BwErCbtu6Uye%F5nqralgP_v;ur7^ti#HIHXh3R6@2j@G{%1S?!@p8fc&uON{}b_R{h1dB|1<0f{?8!v z=gcjQMf+y_CU-8Ax!CPLL%;cdhIQ>fxh|~vEB(*r{=Z%FPqdx(Ih*`vsGfiNlY1|C z`jS;7Fs||(dIARVhsK~H@(?5j8xgZ~pKlfAz5b`#{^6fjS+Lk%C;wyP*Z7$ioBuOZ zEdQtabLN&n*IL8xiP#!8M2N9HL(ajGzs&zs?*Ch2eiGGq?{`=0q_(?W7;E6JgMO~lI6=3UE!TLRe zdu9(`Ag~T(mvzND$Lul&p^68!Sy5UmPfyB(%^!KKsw`{p8}4 zlmAuj|NALBtm~`&R{Nbl{xck~z5n+8uW}H}XwOXh=d=Ga+*j*|X;>Sj@$bsN=a2t0 zFxYS3|MEVFaq3gbe};Ma{~2m#*}ybx%~)!Bbw{$2fV?mznvJlI?Ya~S`qy9PmcF~urI=E@jND4)0ZM(8it6v29C>F`_E zr@YO+{Yn0s-NxUyqy95|0Ifw*z?wrwJ60I0*hueKLCadK0ZfG4@Sf$t+BJeTB?h#M zMByOauw6re(ZxY-6%1r|Xa3sPV!zn>?Ut8J+GFtc&PIlar%gR6jaT}_0>Me}?Dhga0$^5B$&Yv84Xc{SW^c{``DZ|NWKy zKfOIcU9;!?XZU8z_x{6w2IX7-86M{UsQ*{<)&BQa`Tq>!?tgUuGw}asI6rUse}=ln z{}~>=KB}?k;opAe-GA&)eB1vc`k(xNhWWPs|NgrFXE=KCKLdCD;r|SupRfGSaDT;r zh9A2=dTH#aPrmT(KLh_K+x5gaNKgL87wi3p|MX}7XK3C3@IS-P&sY94yuVWaCszK$ z_CNOj8SJX&|NYhfpP_rb$fEA{fpdK63(-$YYr}nw&z>C#xhy$!NZ)DgOH^g3&P@4R!uKeT_n5(#uoDZ$##9j&COP)VWB|4#Wa`9Fga+w%Vm zIs3n41(_fE&mbqa`mH|BrT9m)rfQ^?%;KPYeH}1nP|}5S#g*pVu!&h0U5(Vk zO6@GoUx6k@&frQoqigOE1LLD>?r6F_39>aQ$b&PF(I(hz`*;7R@NI`Ji>7DSm;4LK z&3`xl)XpxUuGqDIFaHeOx_?)_7K4{&8A!3)wSO=FtYFkwdh7mO`>E2=zn6bnz=Eu= z0joAgmQt1gCfHybh^G30o8L!|=bF+RzA~HK-+cZ*gH6hRhJC(ByM-r;it68d{-0r< zQT=_jeuR?9XM%x2f9KWze60TWQ^s$b{|ujx`Tu@$fh8nlfBO7?6(>Qn7q07n>I?U4F{BPw`Tq=??$n>(v;X_S z1soYG_nj{M&tUiIKf`^t{y{6w`4+P4Aa(7Y(}KJr4bOMYntgIQWO@p*jY!hf9x9me zDHS&DbmApeVQ}=Q#lxn$AZBGSP6_@qvmQ3D1(gNIEZjUBn0x~ca}h&B>67*Ue6Iif z+C$@7%O7K-G--2&n5~17nCD5uCocLtji(i*tPWpTC>zdW|d~rQuPfP+`E7T6c zmSRwCCpfdy;lIxHd;hXVt=cC4O8W2n7g|?~uKt*LJp6H@t4LTHY(WTHtw>+b^mqMhQ=@+reM#7^ zxTs6}pc#Dd>pWt8Gi3{BrNm&d5W3+%L%{v_^^Reyr>#G7&T79jm&T&a#xN_5Uo7gn z@e$N&K_5-S%HNhPwJm!$>$iRGe)6ELoA=?tYAo15?{te!L zpZqyn^4Wdo_49I1qtL`kYxjW)g8^gCX8IX`I6 z*?a6eN6B1%gf&gm(yW>KcjcpMLCI|E)cl91FE-t2>(W@y0$Ra~ak2=mUIRYy^NYH) zlaIqPGeP?1nyj1a-iY7&DQi);&{ZV766?-uoYe^pj$on4E<)8Cp~OZ&ezdZ>Hs9lv zM8dVw;3-nEjd|gVmVKIj<>FKQmFv}CP2FARHs?fJ*X&dA!R}M`E$d$u_Vy>cks@5p zVvrKYGaVT#_bmNtQtACF`xAs$yu3Y^ z*W^%_#+qZqCY|()uA&V{A1u!`ChS% zMO|O}9)J{$6ujzc+T^VN3?+4;C%YDEZF~A6B)02IXz%L37k>tBt>0y@_2gHp-|JsL zMbDuM+bs;<^>3f!FcIyk(vCja{jYNQ-%lE!lKwNy%lyw!({=e(@w-0$L}j(EYY0J` z8T+5ls=u$)zw-R9!6pYQKSJ*L#D6|(|9+xhXsAReLp9f>KBld!bSXaOP%A5V`XU25QJ{#}sK5Jjw!bSaHp&3O=`KGnJa#_Em+Pdua z2XFefFY!d@o~vI>Dp$YAlG6C~*YEVNpY2KrvlhSpYOVkJnGA78HNU-BU;3Xx(f#(K zf3N>JZGN%VZl~Re2j4!K*xeQX9{)+Cc9;IU{ZF**x>QgTGlu?IQ#P)qgf;wsS zJEpAuAfnRZKf@z^|Ia3Epe-YUxK2w$8;KbVn*fJOUy==!{enS6oS*%V?#qA9DuTvm dkH8MaLf{lu7Ha?i literal 0 HcmV?d00001 diff --git a/libs/jpegrecoverymap/tests/data/raw_p010_image.p010 b/libs/jpegrecoverymap/tests/data/raw_p010_image.p010 new file mode 100644 index 0000000000000000000000000000000000000000..01673bf6d5bbfd11737acccd484b981ac6f8fbf8 GIT binary patch literal 2764800 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 z4FU2(VBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc z7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh z`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3 zwthx!qfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;l zw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp z!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ??YRhN{ z3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2( zVBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr# z)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh`9^h( zhQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx! zqfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J z@zD?%4FU2(VBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q# zMMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ??YRhN{3{(h= zw*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIp zBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$ zpic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh`9^h(hQMeD zjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ?? zYRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?% 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 HhzS7z&%p-3 literal 0 HcmV?d00001 diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp index b3cd37e7e8..ade33a0e4f 100644 --- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp +++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp @@ -14,8 +14,19 @@ * limitations under the License. */ -#include #include +#include +#include +#include +#include + +#define RAW_P010_IMAGE "/sdcard/Documents/raw_p010_image.p010" +#define RAW_P010_IMAGE_WIDTH 1280 +#define RAW_P010_IMAGE_HEIGHT 720 +#define JPEG_IMAGE "/sdcard/Documents/jpeg_image.jpg" + +#define SAVE_ENCODING_RESULT true +#define SAVE_DECODING_RESULT true namespace android::recoverymap { @@ -26,13 +37,50 @@ public: protected: virtual void SetUp(); virtual void TearDown(); + + struct jpegr_uncompressed_struct mRawP010Image; + struct jpegr_compressed_struct mJpegImage; }; RecoveryMapTest::RecoveryMapTest() {} RecoveryMapTest::~RecoveryMapTest() {} void RecoveryMapTest::SetUp() {} -void RecoveryMapTest::TearDown() {} +void RecoveryMapTest::TearDown() { + free(mRawP010Image.data); + free(mJpegImage.data); +} + +static size_t getFileSize(int fd) { + struct stat st; + if (fstat(fd, &st) < 0) { + ALOGW("%s : fstat failed", __func__); + return 0; + } + return st.st_size; // bytes +} + +static bool loadFile(const char filename[], void*& result, int* fileLength) { + int fd = open(filename, O_CLOEXEC); + if (fd < 0) { + return false; + } + int length = getFileSize(fd); + if (length == 0) { + close(fd); + return false; + } + if (fileLength != nullptr) { + *fileLength = length; + } + result = malloc(length); + if (read(fd, result, length) != static_cast(length)) { + close(fd); + return false; + } + close(fd); + return true; +} TEST_F(RecoveryMapTest, build) { // Force all of the recovery map lib to be linked by calling all public functions. @@ -45,4 +93,61 @@ TEST_F(RecoveryMapTest, build) { recovery_map.decodeJPEGR(nullptr, nullptr, nullptr, false); } +TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) { + int ret; + + // Load input files. + if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + mRawP010Image.width = RAW_P010_IMAGE_WIDTH; + mRawP010Image.height = RAW_P010_IMAGE_HEIGHT; + mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100; + + if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) { + FAIL() << "Load file " << JPEG_IMAGE << " failed"; + } + mJpegImage.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709; + + RecoveryMap recoveryMap; + + jpegr_compressed_struct jpegR; + jpegR.maxLength = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + ret = recoveryMap.encodeJPEGR( + &mRawP010Image, &mJpegImage, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + if (SAVE_ENCODING_RESULT) { + // Output image data to file + std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)jpegR.data, jpegR.length); + } + + jpegr_uncompressed_struct decodedJpegR; + int decodedJpegRSize = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * 4; + decodedJpegR.data = malloc(decodedJpegRSize); + ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + if (SAVE_DECODING_RESULT) { + // Output image data to file + std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize); + } + + free(jpegR.data); + free(decodedJpegR.data); +} + } // namespace android::recoverymap -- GitLab From 08048ce290dae92b21d436af7160fdf2a66c10bd Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Wed, 30 Nov 2022 18:08:00 -0800 Subject: [PATCH 0556/1310] SF: fix a bug when FpsRange is Infinity Bug: 260874985 Test: adb shell /data/nativetest64/libsurfaceflinger_unittest/libsurfaceflinger_unittest Change-Id: I2bd5eeb023cbabc55febaf1fd2ced3e7b8ec0bc5 --- .../Scheduler/RefreshRateSelector.cpp | 15 ++++++++++----- .../tests/unittests/RefreshRateSelectorTest.cpp | 13 +++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index 4be1ac7c6f..c781a6d922 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -107,7 +107,8 @@ std::pair divisorRange(Fps fps, FpsRange range, } using fps_approx_ops::operator/; - const auto start = std::max(1u, fps / range.max - 1); + // use signed type as `fps / range.max` might be 0 + const auto start = std::max(1, static_cast(fps / range.max) - 1); const auto end = fps / std::max(range.min, RefreshRateSelector::kMinSupportedFrameRate, fps_approx_ops::operator<); @@ -1053,8 +1054,12 @@ bool RefreshRateSelector::isPolicyValidLocked(const Policy& policy) const { const auto& primaryRanges = policy.primaryRanges; const auto& appRequestRanges = policy.appRequestRanges; ALOGE_IF(!appRequestRanges.physical.includes(primaryRanges.physical), - "Physical range is invalid"); - ALOGE_IF(!appRequestRanges.render.includes(primaryRanges.render), "Render range is invalid"); + "Physical range is invalid: primary: %s appRequest: %s", + to_string(primaryRanges.physical).c_str(), + to_string(appRequestRanges.physical).c_str()); + ALOGE_IF(!appRequestRanges.render.includes(primaryRanges.render), + "Render range is invalid: primary: %s appRequest: %s", + to_string(primaryRanges.render).c_str(), to_string(appRequestRanges.render).c_str()); return primaryRanges.valid() && appRequestRanges.valid(); } @@ -1156,8 +1161,8 @@ void RefreshRateSelector::constructAvailableRefreshRates() { const auto frameRateModes = createFrameRateModes(filterModes, ranges.render); LOG_ALWAYS_FATAL_IF(frameRateModes.empty(), - "No matching frame rate modes for %s physicalRange %s", rangeName, - to_string(ranges.physical).c_str()); + "No matching frame rate modes for %s range. policy: %s", rangeName, + policy->toString().c_str()); const auto stringifyModes = [&] { std::string str; diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp index fdf2ffe3ce..a3b3c4cf58 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp @@ -2943,5 +2943,18 @@ TEST_P(RefreshRateSelectorTest, idleWhenLowestRefreshRateIsNotDivisor) { EXPECT_EQ(kModeId35, selector.getBestFrameRateMode({}, {.idle = true})->getId()); } +TEST_P(RefreshRateSelectorTest, policyCanBeInfinity) { + auto selector = createSelector(kModes_60_120, kModeId120); + + constexpr Fps inf = Fps::fromValue(std::numeric_limits::infinity()); + + using namespace fps_approx_ops; + selector.setDisplayManagerPolicy({kModeId60, {0_Hz, inf}}); + + // With no layers, idle should still be lower priority than touch boost. + EXPECT_EQ(kMode120, selector.getMaxRefreshRateByPolicy()); + EXPECT_EQ(kMode60, selector.getMinRefreshRateByPolicy()); +} + } // namespace } // namespace android::scheduler -- GitLab From 144ff545d19604a93303a232cbf096ee73aeeef2 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Mon, 28 Nov 2022 17:41:06 +0000 Subject: [PATCH 0557/1310] inputflinger_tests: Put `InstrumentedInputReader` in its own file I would like to be able to put automated tests for the new `TouchpadEventMapper` in their own file, rather than InputReader_tests.cpp. To do this I'll need some of the test utilities in their own files, too. As well as moving the code to separate .h and .cpp files, the private members of `FakeInputReaderContext` were also moved to the bottom of the definition and the `private:` made explicit. Bug: 251196347 Test: atest inputflinger_tests Change-Id: Iad4cb00fc23ecac0e2f71ea21481585fcc80c92f --- services/inputflinger/tests/Android.bp | 1 + .../inputflinger/tests/InputReader_test.cpp | 112 +--------------- .../tests/InstrumentedInputReader.cpp | 50 +++++++ .../tests/InstrumentedInputReader.h | 123 ++++++++++++++++++ 4 files changed, 175 insertions(+), 111 deletions(-) create mode 100644 services/inputflinger/tests/InstrumentedInputReader.cpp create mode 100644 services/inputflinger/tests/InstrumentedInputReader.h diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 2e5bec9999..2a8b359837 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -48,6 +48,7 @@ cc_test { "InputProcessorConverter_test.cpp", "InputDispatcher_test.cpp", "InputReader_test.cpp", + "InstrumentedInputReader.cpp", "LatencyTracker_test.cpp", "NotifyArgs_test.cpp", "PreferStylusOverTouch_test.cpp", diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index c72d01fad8..a1dad1ec9f 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -43,6 +43,7 @@ #include "FakeEventHub.h" #include "FakeInputReaderPolicy.h" #include "FakePointerController.h" +#include "InstrumentedInputReader.h" #include "TestConstants.h" #include "android/hardware/input/InputDeviceCountryCode.h" #include "input/DisplayViewport.h" @@ -343,117 +344,6 @@ private: } }; - -// --- InstrumentedInputReader --- - -class InstrumentedInputReader : public InputReader { - std::queue> mNextDevices; - -public: - InstrumentedInputReader(std::shared_ptr eventHub, - const sp& policy, - InputListenerInterface& listener) - : InputReader(eventHub, policy, listener), mFakeContext(this) {} - - virtual ~InstrumentedInputReader() {} - - void pushNextDevice(std::shared_ptr device) { mNextDevices.push(device); } - - std::shared_ptr newDevice(int32_t deviceId, const std::string& name, - const std::string& location = "") { - InputDeviceIdentifier identifier; - identifier.name = name; - identifier.location = location; - int32_t generation = deviceId + 1; - return std::make_shared(&mFakeContext, deviceId, generation, identifier); - } - - // Make the protected loopOnce method accessible to tests. - using InputReader::loopOnce; - -protected: - virtual std::shared_ptr createDeviceLocked(int32_t eventHubId, - const InputDeviceIdentifier& identifier) - REQUIRES(mLock) { - if (!mNextDevices.empty()) { - std::shared_ptr device(std::move(mNextDevices.front())); - mNextDevices.pop(); - return device; - } - return InputReader::createDeviceLocked(eventHubId, identifier); - } - - // --- FakeInputReaderContext --- - class FakeInputReaderContext : public ContextImpl { - int32_t mGlobalMetaState; - bool mUpdateGlobalMetaStateWasCalled; - int32_t mGeneration; - std::optional mRequestedTimeout; - std::vector mExternalStylusDevices; - - public: - FakeInputReaderContext(InputReader* reader) - : ContextImpl(reader), - mGlobalMetaState(0), - mUpdateGlobalMetaStateWasCalled(false), - mGeneration(1) {} - - virtual ~FakeInputReaderContext() {} - - void assertUpdateGlobalMetaStateWasCalled() { - ASSERT_TRUE(mUpdateGlobalMetaStateWasCalled) - << "Expected updateGlobalMetaState() to have been called."; - mUpdateGlobalMetaStateWasCalled = false; - } - - void setGlobalMetaState(int32_t state) { mGlobalMetaState = state; } - - uint32_t getGeneration() { return mGeneration; } - - void updateGlobalMetaState() override { - mUpdateGlobalMetaStateWasCalled = true; - ContextImpl::updateGlobalMetaState(); - } - - int32_t getGlobalMetaState() override { - return mGlobalMetaState | ContextImpl::getGlobalMetaState(); - } - - int32_t bumpGeneration() override { - mGeneration = ContextImpl::bumpGeneration(); - return mGeneration; - } - - void requestTimeoutAtTime(nsecs_t when) override { mRequestedTimeout = when; } - - void assertTimeoutWasRequested(nsecs_t when) { - ASSERT_TRUE(mRequestedTimeout) << "Expected timeout at time " << when - << " but there was no timeout requested."; - ASSERT_EQ(when, *mRequestedTimeout); - mRequestedTimeout.reset(); - } - - void assertTimeoutWasNotRequested() { - ASSERT_FALSE(mRequestedTimeout) << "Expected no timeout to have been requested," - " but one was requested at time " - << *mRequestedTimeout; - } - - void getExternalStylusDevices(std::vector& outDevices) override { - outDevices = mExternalStylusDevices; - } - - void setExternalStylusDevices(std::vector&& devices) { - mExternalStylusDevices = devices; - } - } mFakeContext; - - friend class InputReaderTest; - -public: - FakeInputReaderContext* getContext() { return &mFakeContext; } -}; - // --- InputReaderPolicyTest --- class InputReaderPolicyTest : public testing::Test { protected: diff --git a/services/inputflinger/tests/InstrumentedInputReader.cpp b/services/inputflinger/tests/InstrumentedInputReader.cpp new file mode 100644 index 0000000000..1f8cd12b75 --- /dev/null +++ b/services/inputflinger/tests/InstrumentedInputReader.cpp @@ -0,0 +1,50 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "InstrumentedInputReader.h" + +namespace android { + +InstrumentedInputReader::InstrumentedInputReader(std::shared_ptr eventHub, + const sp& policy, + InputListenerInterface& listener) + : InputReader(eventHub, policy, listener), mFakeContext(this) {} + +void InstrumentedInputReader::pushNextDevice(std::shared_ptr device) { + mNextDevices.push(device); +} + +std::shared_ptr InstrumentedInputReader::newDevice(int32_t deviceId, + const std::string& name, + const std::string& location) { + InputDeviceIdentifier identifier; + identifier.name = name; + identifier.location = location; + int32_t generation = deviceId + 1; + return std::make_shared(&mFakeContext, deviceId, generation, identifier); +} + +std::shared_ptr InstrumentedInputReader::createDeviceLocked( + int32_t eventHubId, const InputDeviceIdentifier& identifier) REQUIRES(mLock) { + if (!mNextDevices.empty()) { + std::shared_ptr device(std::move(mNextDevices.front())); + mNextDevices.pop(); + return device; + } + return InputReader::createDeviceLocked(eventHubId, identifier); +} + +} // namespace android diff --git a/services/inputflinger/tests/InstrumentedInputReader.h b/services/inputflinger/tests/InstrumentedInputReader.h new file mode 100644 index 0000000000..7f8d5562ef --- /dev/null +++ b/services/inputflinger/tests/InstrumentedInputReader.h @@ -0,0 +1,123 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace android { + +class InstrumentedInputReader : public InputReader { +public: + InstrumentedInputReader(std::shared_ptr eventHub, + const sp& policy, + InputListenerInterface& listener); + virtual ~InstrumentedInputReader() {} + + void pushNextDevice(std::shared_ptr device); + + std::shared_ptr newDevice(int32_t deviceId, const std::string& name, + const std::string& location = ""); + + // Make the protected loopOnce method accessible to tests. + using InputReader::loopOnce; + +protected: + virtual std::shared_ptr createDeviceLocked( + int32_t eventHubId, const InputDeviceIdentifier& identifier); + + class FakeInputReaderContext : public ContextImpl { + public: + FakeInputReaderContext(InputReader* reader) + : ContextImpl(reader), + mGlobalMetaState(0), + mUpdateGlobalMetaStateWasCalled(false), + mGeneration(1) {} + + virtual ~FakeInputReaderContext() {} + + void assertUpdateGlobalMetaStateWasCalled() { + ASSERT_TRUE(mUpdateGlobalMetaStateWasCalled) + << "Expected updateGlobalMetaState() to have been called."; + mUpdateGlobalMetaStateWasCalled = false; + } + + void setGlobalMetaState(int32_t state) { mGlobalMetaState = state; } + + uint32_t getGeneration() { return mGeneration; } + + void updateGlobalMetaState() override { + mUpdateGlobalMetaStateWasCalled = true; + ContextImpl::updateGlobalMetaState(); + } + + int32_t getGlobalMetaState() override { + return mGlobalMetaState | ContextImpl::getGlobalMetaState(); + } + + int32_t bumpGeneration() override { + mGeneration = ContextImpl::bumpGeneration(); + return mGeneration; + } + + void requestTimeoutAtTime(nsecs_t when) override { mRequestedTimeout = when; } + + void assertTimeoutWasRequested(nsecs_t when) { + ASSERT_TRUE(mRequestedTimeout) << "Expected timeout at time " << when + << " but there was no timeout requested."; + ASSERT_EQ(when, *mRequestedTimeout); + mRequestedTimeout.reset(); + } + + void assertTimeoutWasNotRequested() { + ASSERT_FALSE(mRequestedTimeout) << "Expected no timeout to have been requested," + " but one was requested at time " + << *mRequestedTimeout; + } + + void getExternalStylusDevices(std::vector& outDevices) override { + outDevices = mExternalStylusDevices; + } + + void setExternalStylusDevices(std::vector&& devices) { + mExternalStylusDevices = devices; + } + + private: + int32_t mGlobalMetaState; + bool mUpdateGlobalMetaStateWasCalled; + int32_t mGeneration; + std::optional mRequestedTimeout; + std::vector mExternalStylusDevices; + } mFakeContext; + + friend class InputReaderTest; + +public: + FakeInputReaderContext* getContext() { return &mFakeContext; } + +private: + std::queue> mNextDevices; +}; + +} // namespace android -- GitLab From 79cc9fa5f0d40938bdfcd971d5af9a0bcc8b440c Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Fri, 28 Oct 2022 15:32:39 +0000 Subject: [PATCH 0558/1310] Add new `TouchpadInputMapper` class This will replace `MultiTouchInputMapper` and `SingleTouchInputMapper` for touchpad devices, and will be the wrapper for the gestures library. Bug: 251196347 Test: connect Apple Magic Trackpad 2 to device, check logs come out Test: atest inputflinger_tests Change-Id: Ia8ee1779a40c6c6f98373e114c040bf13b7f213d --- services/inputflinger/reader/Android.bp | 1 + services/inputflinger/reader/EventHub.cpp | 4 +++ services/inputflinger/reader/InputDevice.cpp | 7 +++- .../inputflinger/reader/include/EventHub.h | 5 ++- .../reader/mapper/TouchpadInputMapper.cpp | 36 +++++++++++++++++++ .../reader/mapper/TouchpadInputMapper.h | 34 ++++++++++++++++++ 6 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 services/inputflinger/reader/mapper/TouchpadInputMapper.cpp create mode 100644 services/inputflinger/reader/mapper/TouchpadInputMapper.h diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index 46e86de84c..551ad7a398 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -51,6 +51,7 @@ filegroup { "mapper/SingleTouchInputMapper.cpp", "mapper/SwitchInputMapper.cpp", "mapper/TouchInputMapper.cpp", + "mapper/TouchpadInputMapper.cpp", "mapper/VibratorInputMapper.cpp", "mapper/accumulator/CursorButtonAccumulator.cpp", "mapper/accumulator/CursorScrollAccumulator.cpp", diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index 0aaef53c0d..f2ea90c291 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -2215,6 +2215,10 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { // a touch screen. if (device->keyBitmask.test(BTN_TOUCH) || !haveGamepadButtons) { device->classes |= (InputDeviceClass::TOUCH | InputDeviceClass::TOUCH_MT); + if (device->propBitmask.test(INPUT_PROP_POINTER) && + !device->keyBitmask.any(BTN_TOOL_PEN, BTN_TOOL_FINGER) && !haveStylusButtons) { + device->classes |= InputDeviceClass::TOUCHPAD; + } } // Is this an old style single-touch driver? } else if (device->keyBitmask.test(BTN_TOUCH) && device->absBitmask.test(ABS_X) && diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index e6ab8722f6..6ca629835d 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -33,6 +33,7 @@ #include "SensorInputMapper.h" #include "SingleTouchInputMapper.h" #include "SwitchInputMapper.h" +#include "TouchpadInputMapper.h" #include "VibratorInputMapper.h" using android::hardware::input::InputDeviceCountryCode; @@ -208,7 +209,11 @@ void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) { } // Touchscreens and touchpad devices. - if (classes.test(InputDeviceClass::TOUCH_MT)) { + // TODO(b/251196347): replace this with a proper flag. + constexpr bool ENABLE_NEW_TOUCHPAD_STACK = false; + if (ENABLE_NEW_TOUCHPAD_STACK && classes.test(InputDeviceClass::TOUCHPAD)) { + mappers.push_back(std::make_unique(*contextPtr)); + } else if (classes.test(InputDeviceClass::TOUCH_MT)) { mappers.push_back(std::make_unique(*contextPtr)); } else if (classes.test(InputDeviceClass::TOUCH)) { mappers.push_back(std::make_unique(*contextPtr)); diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index 8e5f15f338..42ca482a68 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -94,7 +94,7 @@ enum class InputDeviceClass : uint32_t { /* The input device is a cursor device such as a trackball or mouse. */ CURSOR = 0x00000008, - /* The input device is a multi-touch touchscreen. */ + /* The input device is a multi-touch touchscreen or touchpad. */ TOUCH_MT = 0x00000010, /* The input device is a directional pad (implies keyboard, has DPAD keys). */ @@ -130,6 +130,9 @@ enum class InputDeviceClass : uint32_t { /* The input device has sysfs controllable lights */ LIGHT = 0x00008000, + /* The input device is a touchpad, requiring an on-screen cursor. */ + TOUCHPAD = 0x00010000, + /* The input device is virtual (not a real device, not part of UI configuration). */ VIRTUAL = 0x40000000, diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp new file mode 100644 index 0000000000..3ad1de4cd4 --- /dev/null +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -0,0 +1,36 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../Macros.h" + +#include "TouchpadInputMapper.h" + +namespace android { + +TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext) + : InputMapper(deviceContext) {} + +uint32_t TouchpadInputMapper::getSources() const { + return AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD; +} + +std::list TouchpadInputMapper::process(const RawEvent* rawEvent) { + ALOGD("TODO: process event type=0x%x code=0x%x value=0x%x", rawEvent->type, rawEvent->code, + rawEvent->value); + return {}; +} + +} // namespace android diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h new file mode 100644 index 0000000000..81a76d27be --- /dev/null +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -0,0 +1,34 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "EventHub.h" +#include "InputDevice.h" +#include "InputMapper.h" +#include "NotifyArgs.h" + +namespace android { + +class TouchpadInputMapper : public InputMapper { +public: + explicit TouchpadInputMapper(InputDeviceContext& deviceContext); + + virtual uint32_t getSources() const override; + [[nodiscard]] virtual std::list process(const RawEvent* rawEvent) override; +}; + +} // namespace android -- GitLab From c132a15668ee26b5c6e193f3898808a80174c57b Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Wed, 30 Nov 2022 15:44:52 -0500 Subject: [PATCH 0559/1310] SF: Fix condition to activate display on power-on Since [1], getDefaultDisplayDeviceLocked's fallback when the active display is outdated is to return the primary display, not `nullptr`. During the initial call to setPowerModeInternal, `activeDisplay` is no longer `nullptr`, but the primary display. After [2], onActiveDisplayChangedLocked is expected to be called for the primary display during boot. This was not the case though, since the DisplayDevice::setPowerMode() update precedes the DisplayDevice:: isPoweredOn() query. Fix this by querying before updating. Also, clean up `nullptr` checks around onActiveDisplayChangedLocked. [1] If83c908446e5e5267dfcb15189a26b779d75b216 [2] I15e0f5a280e62baf6d4e6ea2748d95342e79ac44 Fixes: 260663220 Test: The leader display ID is logged during single-display boot. Test: SetPowerModeInternalTest.designatesLeaderDisplay Change-Id: I0d20ce7f7ab3f9e90ef1c72263b8166865486d62 --- services/surfaceflinger/SurfaceFlinger.cpp | 23 +++++------ services/surfaceflinger/SurfaceFlinger.h | 5 ++- .../unittests/DisplayTransactionTestHelpers.h | 12 ++++-- .../SurfaceFlinger_PowerHintTest.cpp | 1 - ...urfaceFlinger_SetPowerModeInternalTest.cpp | 38 ++++++++++++++++--- .../tests/unittests/TestableScheduler.h | 4 ++ .../tests/unittests/TestableSurfaceFlinger.h | 18 ++++++--- 7 files changed, 71 insertions(+), 30 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 365ffb7732..1bbaf7b9e4 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4685,18 +4685,18 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: .value_or(false); const auto activeDisplay = getDisplayDeviceLocked(mActiveDisplayId); - if (isInternalDisplay && activeDisplay != display && activeDisplay && - activeDisplay->isPoweredOn()) { - ALOGW("Trying to change power mode on non active display while the active display is ON"); - } + const bool isActiveDisplayPoweredOn = activeDisplay && activeDisplay->isPoweredOn(); + + ALOGW_IF(display != activeDisplay && isInternalDisplay && isActiveDisplayPoweredOn, + "Trying to change power mode on inactive display without powering off active display"); display->setPowerMode(mode); const auto refreshRate = display->refreshRateSelector().getActiveMode().modePtr->getFps(); if (!currentModeOpt || *currentModeOpt == hal::PowerMode::OFF) { // Turn on the display - if (isInternalDisplay && (!activeDisplay || !activeDisplay->isPoweredOn())) { - onActiveDisplayChangedLocked(display); + if (isInternalDisplay && !isActiveDisplayPoweredOn) { + onActiveDisplayChangedLocked(activeDisplay, display); } // Keep uclamp in a separate syscall and set it before changing to RT due to b/190237315. // We can merge the syscall later. @@ -6995,17 +6995,14 @@ void SurfaceFlinger::onActiveDisplaySizeChanged(const sp& a getRenderEngine().onActiveDisplaySizeChanged(activeDisplay->getSize()); } -void SurfaceFlinger::onActiveDisplayChangedLocked(const sp& activeDisplay) { +void SurfaceFlinger::onActiveDisplayChangedLocked(const sp& inactiveDisplay, + const sp& activeDisplay) { ATRACE_CALL(); - if (const auto display = getDisplayDeviceLocked(mActiveDisplayId)) { - display->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false); + if (inactiveDisplay) { + inactiveDisplay->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false); } - if (!activeDisplay) { - ALOGE("%s: activeDisplay is null", __func__); - return; - } mActiveDisplayId = activeDisplay->getPhysicalId(); activeDisplay->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index e3217a3228..ff69245ddb 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1000,7 +1000,10 @@ private: VirtualDisplayId acquireVirtualDisplay(ui::Size, ui::PixelFormat) REQUIRES(mStateLock); void releaseVirtualDisplay(VirtualDisplayId); - void onActiveDisplayChangedLocked(const sp& activeDisplay) + // TODO(b/255635821): Replace pointers with references. `inactiveDisplay` is only ever `nullptr` + // in tests, and `activeDisplay` must not be `nullptr` as a precondition. + void onActiveDisplayChangedLocked(const sp& inactiveDisplay, + const sp& activeDisplay) REQUIRES(mStateLock, kMainThreadContext); void onActiveDisplaySizeChanged(const sp&); diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h index 19c7d5cd11..d58e644506 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h @@ -359,6 +359,7 @@ struct HwcDisplayVariant { } // Called by tests to inject a HWC display setup + template static void injectHwcDisplayWithNoDefaultCapabilities(DisplayTransactionTest* test) { const auto displayId = DisplayVariant::DISPLAY_ID::get(); ASSERT_FALSE(GpuVirtualDisplayId::tryCast(displayId)); @@ -367,18 +368,21 @@ struct HwcDisplayVariant { .setHwcDisplayId(HWC_DISPLAY_ID) .setResolution(DisplayVariant::RESOLUTION) .setActiveConfig(HWC_ACTIVE_CONFIG_ID) - .setPowerMode(INIT_POWER_MODE) + .setPowerMode(kInitPowerMode ? std::make_optional(INIT_POWER_MODE) : std::nullopt) .inject(&test->mFlinger, test->mComposer); } // Called by tests to inject a HWC display setup + template static void injectHwcDisplay(DisplayTransactionTest* test) { EXPECT_CALL(*test->mComposer, getDisplayCapabilities(HWC_DISPLAY_ID, _)) .WillOnce(DoAll(SetArgPointee<1>(std::vector({})), Return(Error::NONE))); - EXPECT_CALL(*test->mComposer, setPowerMode(HWC_DISPLAY_ID, INIT_POWER_MODE)) - .WillOnce(Return(Error::NONE)); - injectHwcDisplayWithNoDefaultCapabilities(test); + if constexpr (kInitPowerMode) { + EXPECT_CALL(*test->mComposer, setPowerMode(HWC_DISPLAY_ID, INIT_POWER_MODE)) + .WillOnce(Return(Error::NONE)); + } + injectHwcDisplayWithNoDefaultCapabilities(test); } static std::shared_ptr injectCompositionDisplay( diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp index bc66961f44..622717f290 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp @@ -97,7 +97,6 @@ void SurfaceFlingerPowerHintTest::SetUp() { .setNativeWindow(mNativeWindow) .setPowerMode(hal::PowerMode::ON) .inject(); - mFlinger.mutableActiveDisplayId() = mDisplay->getPhysicalId(); } void SurfaceFlingerPowerHintTest::setupScheduler() { diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp index 25857ecb4e..0fb8e2bbaa 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp @@ -259,12 +259,6 @@ struct DisplayPowerCase { auto injector = Display::makeFakeExistingDisplayInjector(test); const auto display = injector.inject(); display->setPowerMode(mode); - if (injector.physicalDisplay() - .transform(&display::PhysicalDisplay::isInternal) - .value_or(false)) { - test->mFlinger.mutableActiveDisplayId() = display->getPhysicalId(); - } - return display; } @@ -490,5 +484,37 @@ TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOnToUnknownExternalDispla transitionDisplayCommon>(); } +TEST_F(SetPowerModeInternalTest, designatesLeaderDisplay) { + using Case = SimplePrimaryDisplayCase; + + // -------------------------------------------------------------------- + // Preconditions + + // Inject a primary display with uninitialized power mode. + constexpr bool kInitPowerMode = false; + Case::Display::injectHwcDisplay(this); + auto injector = Case::Display::makeFakeExistingDisplayInjector(this); + injector.setPowerMode(std::nullopt); + const auto display = injector.inject(); + + // -------------------------------------------------------------------- + // Invocation + + // FakeDisplayDeviceInjector registers the display with Scheduler, so it has already been + // designated as the leader. Set an arbitrary leader to verify that `setPowerModeInternal` + // designates a leader regardless of any preceding `Scheduler::registerDisplay` call(s). + constexpr PhysicalDisplayId kPlaceholderId = PhysicalDisplayId::fromPort(42); + ASSERT_NE(display->getPhysicalId(), kPlaceholderId); + mFlinger.scheduler()->setLeaderDisplay(kPlaceholderId); + + mFlinger.setPowerModeInternal(display, PowerMode::ON); + + // -------------------------------------------------------------------- + // Postconditions + + // The primary display should be designated as the leader. + EXPECT_EQ(mFlinger.scheduler()->leaderDisplayId(), display->getPhysicalId()); +} + } // namespace } // namespace android diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index 54c10c51e8..b8a60638ae 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -87,6 +87,10 @@ public: Scheduler::unregisterDisplay(displayId); } + std::optional leaderDisplayId() const NO_THREAD_SAFETY_ANALYSIS { + return mLeaderDisplayId; + } + void setLeaderDisplay(PhysicalDisplayId displayId) { ftl::FakeGuard guard(kMainThreadContext); Scheduler::setLeaderDisplay(displayId); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index e29dd67c21..fb473f5073 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -470,7 +470,7 @@ public: void onActiveDisplayChanged(const sp& activeDisplay) { Mutex::Autolock lock(mFlinger->mStateLock); ftl::FakeGuard guard(kMainThreadContext); - mFlinger->onActiveDisplayChangedLocked(activeDisplay); + mFlinger->onActiveDisplayChangedLocked(nullptr, activeDisplay); } auto createLayer(LayerCreationArgs& args, const sp& parentHandle, @@ -621,7 +621,7 @@ public: return *this; } - auto& setPowerMode(hal::PowerMode mode) { + auto& setPowerMode(std::optional mode) { mPowerMode = mode; return *this; } @@ -645,7 +645,11 @@ public: mHwcDisplayType); display->mutableIsConnected() = true; - display->setPowerMode(mPowerMode); + + if (mPowerMode) { + display->setPowerMode(*mPowerMode); + } + flinger->mutableHwcDisplayData()[mDisplayId].hwcDisplay = std::move(display); EXPECT_CALL(*composer, getDisplayConfigs(mHwcDisplayId, _)) @@ -711,7 +715,7 @@ public: int32_t mDpiY = DEFAULT_DPI; int32_t mConfigGroup = DEFAULT_CONFIG_GROUP; hal::HWConfigId mActiveConfig = DEFAULT_ACTIVE_CONFIG; - hal::PowerMode mPowerMode = DEFAULT_POWER_MODE; + std::optional mPowerMode = DEFAULT_POWER_MODE; const std::unordered_set* mCapabilities = nullptr; }; @@ -788,7 +792,7 @@ public: return *this; } - auto& setPowerMode(hal::PowerMode mode) { + auto& setPowerMode(std::optional mode) { mCreationArgs.initialPowerMode = mode; return *this; } @@ -853,6 +857,10 @@ public: LOG_ALWAYS_FATAL_IF(!physicalIdOpt); const auto physicalId = *physicalIdOpt; + if (mCreationArgs.isPrimary) { + mFlinger.mutableActiveDisplayId() = physicalId; + } + LOG_ALWAYS_FATAL_IF(!mHwcDisplayId); const auto activeMode = modes.get(activeModeId); -- GitLab From 884549f7bd0b672fe20e9b42b79e1c40ca2f97f7 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Thu, 1 Dec 2022 17:20:59 -0800 Subject: [PATCH 0560/1310] SF: add WAKEUP_SURFACE_FLINGER permission Allow launcher and sysui to provide a hint to SurfaceFlinger using eEarlyWakeupStart and eEarlyWakeupEnd transaction flags. This lets SurfaceFlinger know that this transaction and others will likely require expensive composition and SurfaceFlinger should wake up earlier. Test: presubmit Fixes: 261103978 Change-Id: I1cfec501885e20bafbf1025ecab2a72060d006be --- libs/gui/include/gui/ISurfaceComposer.h | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 30 ++++++++++------------ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index d517e99fda..045cc2a184 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -102,7 +102,7 @@ public: // (sf vsync offset - debug.sf.early_phase_offset_ns). SurfaceFlinger will continue to be // in the early configuration until it receives eEarlyWakeupEnd. These flags are // expected to be used by WindowManager only and are guarded by - // android.permission.ACCESS_SURFACE_FLINGER + // android.permission.WAKEUP_SURFACE_FLINGER eEarlyWakeupStart = 0x08, eEarlyWakeupEnd = 0x10, eOneWay = 0x20 diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 87cf03772a..dbace146c9 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -257,6 +257,7 @@ const String16 sControlDisplayBrightness("android.permission.CONTROL_DISPLAY_BRI const String16 sDump("android.permission.DUMP"); const String16 sCaptureBlackoutContent("android.permission.CAPTURE_BLACKOUT_CONTENT"); const String16 sInternalSystemWindow("android.permission.INTERNAL_SYSTEM_WINDOW"); +const String16 sWakeupSurfaceFlinger("android.permission.WAKEUP_SURFACE_FLINGER"); const char* KERNEL_IDLE_TIMER_PROP = "graphics.display.kernel_idle_timer.enabled"; @@ -290,20 +291,12 @@ std::string decodeDisplayColorSetting(DisplayColorSetting displayColorSetting) { } } -bool callingThreadHasRotateSurfaceFlingerAccess() { +bool callingThreadHasPermission(const String16& permission) { IPCThreadState* ipc = IPCThreadState::self(); const int pid = ipc->getCallingPid(); const int uid = ipc->getCallingUid(); return uid == AID_GRAPHICS || uid == AID_SYSTEM || - PermissionCache::checkPermission(sRotateSurfaceFlinger, pid, uid); -} - -bool callingThreadHasInternalSystemWindowAccess() { - IPCThreadState* ipc = IPCThreadState::self(); - const int pid = ipc->getCallingPid(); - const int uid = ipc->getCallingUid(); - return uid == AID_GRAPHICS || uid == AID_SYSTEM || - PermissionCache::checkPermission(sInternalSystemWindow, pid, uid); + PermissionCache::checkPermission(permission, pid, uid); } SurfaceFlinger::SurfaceFlinger(Factory& factory, SkipInitializationTag) @@ -3902,18 +3895,23 @@ status_t SurfaceFlinger::setTransactionState( // Avoid checking for rotation permissions if the caller already has ACCESS_SURFACE_FLINGER // permissions. if ((permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) || - callingThreadHasRotateSurfaceFlingerAccess()) { + callingThreadHasPermission(sRotateSurfaceFlinger)) { permissions |= layer_state_t::Permission::ROTATE_SURFACE_FLINGER; } - if (callingThreadHasInternalSystemWindowAccess()) { + if (callingThreadHasPermission(sInternalSystemWindow)) { permissions |= layer_state_t::Permission::INTERNAL_SYSTEM_WINDOW; } - if (!(permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) && - (flags & (eEarlyWakeupStart | eEarlyWakeupEnd))) { - ALOGE("Only WindowManager is allowed to use eEarlyWakeup[Start|End] flags"); - flags &= ~(eEarlyWakeupStart | eEarlyWakeupEnd); + if (flags & (eEarlyWakeupStart | eEarlyWakeupEnd)) { + const bool hasPermission = + (permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) || + callingThreadHasPermission(sWakeupSurfaceFlinger); + if (!hasPermission) { + ALOGE("Caller needs permission android.permission.WAKEUP_SURFACE_FLINGER to use " + "eEarlyWakeup[Start|End] flags"); + flags &= ~(eEarlyWakeupStart | eEarlyWakeupEnd); + } } const int64_t postTime = systemTime(); -- GitLab From 9fe1451fa12e7e1c800346a34534a7c147574052 Mon Sep 17 00:00:00 2001 From: Alberto Gonzalez Date: Thu, 29 Sep 2022 21:27:34 +0000 Subject: [PATCH 0561/1310] Record rendering stability histogram. The new present2presentDelta histogram records the stability in present2present timings between consecutive frames. Bug: 239083860 Test: atest libsurfaceflinger_unittest Change-Id: I9559daaab831c85ecb2aab0ef8ba0fdeb84bb345 --- .../surfaceflinger/TimeStats/TimeStats.cpp | 14 ++++++ services/surfaceflinger/TimeStats/TimeStats.h | 1 + .../timestatsatomsproto/timestats_atoms.proto | 6 ++- .../tests/unittests/TimeStatsTest.cpp | 47 ++++++++++++++++++- 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp index e5a9dd47c3..c8eef462f1 100644 --- a/services/surfaceflinger/TimeStats/TimeStats.cpp +++ b/services/surfaceflinger/TimeStats/TimeStats.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include "TimeStats.h" @@ -177,6 +178,12 @@ bool TimeStats::populateLayerAtom(std::string* pulledData) { *atom->mutable_present_to_present() = histogramToProto(present2PresentHist->second.hist, mMaxPulledHistogramBuckets); } + const auto& present2PresentDeltaHist = layer->deltas.find("present2presentDelta"); + if (present2PresentDeltaHist != layer->deltas.cend()) { + *atom->mutable_present_to_present_delta() = + histogramToProto(present2PresentDeltaHist->second.hist, + mMaxPulledHistogramBuckets); + } const auto& post2presentHist = layer->deltas.find("post2present"); if (post2presentHist != layer->deltas.cend()) { *atom->mutable_post_to_present() = @@ -448,6 +455,7 @@ void TimeStats::flushAvailableRecordsToStatsLocked(int32_t layerId, Fps displayR LayerRecord& layerRecord = mTimeStatsTracker[layerId]; TimeRecord& prevTimeRecord = layerRecord.prevTimeRecord; + std::optional& prevPresentToPresentMs = layerRecord.prevPresentToPresentMs; std::deque& timeRecords = layerRecord.timeRecords; const int32_t refreshRateBucket = clampToNearestBucket(displayRefreshRate, REFRESH_RATE_BUCKET_WIDTH); @@ -525,6 +533,12 @@ void TimeStats::flushAvailableRecordsToStatsLocked(int32_t layerId, Fps displayR ALOGV("[%d]-[%" PRIu64 "]-present2present[%d]", layerId, timeRecords[0].frameTime.frameNumber, presentToPresentMs); timeStatsLayer.deltas["present2present"].insert(presentToPresentMs); + if (prevPresentToPresentMs) { + const int32_t presentToPresentDeltaMs = + std::abs(presentToPresentMs - *prevPresentToPresentMs); + timeStatsLayer.deltas["present2presentDelta"].insert(presentToPresentDeltaMs); + } + prevPresentToPresentMs = presentToPresentMs; } prevTimeRecord = timeRecords[0]; timeRecords.pop_front(); diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h index 61d7c22a2a..09c32174f7 100644 --- a/services/surfaceflinger/TimeStats/TimeStats.h +++ b/services/surfaceflinger/TimeStats/TimeStats.h @@ -219,6 +219,7 @@ class TimeStats : public android::TimeStats { uint32_t lateAcquireFrames = 0; uint32_t badDesiredPresentFrames = 0; TimeRecord prevTimeRecord; + std::optional prevPresentToPresentMs; std::deque timeRecords; }; diff --git a/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto b/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto index e45757ddfd..a46ecd1a6c 100644 --- a/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto +++ b/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto @@ -288,7 +288,11 @@ message SurfaceflingerStatsLayerInfo { // Introduced in Android 12. optional FrameTimingHistogram app_deadline_misses = 25; - // Next ID: 27 + // Variability histogram of present_to_present timings. + // Introduced in Android 14. + optional FrameTimingHistogram present_to_present_delta = 27; + + // Next ID: 28 } /** diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp index 6ffc0396d7..d5a5bae3f5 100644 --- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp +++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp @@ -44,11 +44,14 @@ namespace android { namespace { using testing::_; +using testing::AllOf; using testing::AnyNumber; using testing::Contains; +using testing::ElementsAre; using testing::HasSubstr; using testing::InSequence; using testing::Not; +using testing::Property; using testing::SizeIs; using testing::StrEq; using testing::UnorderedElementsAre; @@ -645,7 +648,7 @@ TEST_F(TimeStatsTest, canInsertOneLayerTimeStats) { ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO))); ASSERT_EQ(1, globalProto.stats_size()); - const SFTimeStatsLayerProto& layerProto = globalProto.stats().Get(0); + const SFTimeStatsLayerProto& layerProto = globalProto.stats(0); ASSERT_TRUE(layerProto.has_layer_name()); EXPECT_EQ(genLayerName(LAYER_ID_0), layerProto.layer_name()); ASSERT_TRUE(layerProto.has_total_frames()); @@ -653,7 +656,7 @@ TEST_F(TimeStatsTest, canInsertOneLayerTimeStats) { ASSERT_EQ(6, layerProto.deltas_size()); for (const SFTimeStatsDeltaProto& deltaProto : layerProto.deltas()) { ASSERT_EQ(1, deltaProto.histograms_size()); - const SFTimeStatsHistogramBucketProto& histogramProto = deltaProto.histograms().Get(0); + const SFTimeStatsHistogramBucketProto& histogramProto = deltaProto.histograms(0); EXPECT_EQ(1, histogramProto.frame_count()); if ("post2acquire" == deltaProto.delta_name()) { EXPECT_EQ(1, histogramProto.time_millis()); @@ -673,6 +676,46 @@ TEST_F(TimeStatsTest, canInsertOneLayerTimeStats) { } } +using LayerProto = SFTimeStatsLayerProto; +using DeltaProto = SFTimeStatsDeltaProto; +using BucketProto = SFTimeStatsHistogramBucketProto; + +TEST_F(TimeStatsTest, canComputeLayerStabilityHistogram) { + EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty()); + + insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000); + insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000); + insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 3, 3000000); // 0ms delta + // Slightly unstable frames + insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 4, 5000000); // 1ms delta + insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 5, 6000000); // 1ms delta + + SFTimeStatsGlobalProto globalProto; + ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO))); + + EXPECT_THAT(globalProto.stats(), + ElementsAre(AllOf( + Property(&LayerProto::layer_name, genLayerName(LAYER_ID_0)), + Property(&LayerProto::total_frames, 4), + Property(&LayerProto::deltas, + Contains(AllOf(Property(&DeltaProto::delta_name, + "present2presentDelta"), + Property(&DeltaProto::histograms, + UnorderedElementsAre( + AllOf(Property(&BucketProto:: + time_millis, + 0), + Property(&BucketProto:: + frame_count, + 1)), + AllOf(Property(&BucketProto:: + time_millis, + 1), + Property(&BucketProto:: + frame_count, + 2)))))))))); +} + TEST_F(TimeStatsTest, canNotInsertInvalidLayerNameTimeStats) { EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty()); -- GitLab From 69df5c022b9e212985179b588da3d2cb2fc74262 Mon Sep 17 00:00:00 2001 From: Kriti Dang Date: Fri, 2 Dec 2022 14:00:08 +0100 Subject: [PATCH 0562/1310] Inlining the clearFrameRate function. Bug: 246597190 Test: m Change-Id: Ia11f47de9b52ecfc41119fcf2605b5ac56b07275 --- libs/nativewindow/ANativeWindow.cpp | 9 --------- libs/nativewindow/include/android/native_window.h | 8 ++++++-- libs/nativewindow/libnativewindow.map.txt | 1 - 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp index c345385839..b7b2926c73 100644 --- a/libs/nativewindow/ANativeWindow.cpp +++ b/libs/nativewindow/ANativeWindow.cpp @@ -232,15 +232,6 @@ int32_t ANativeWindow_setFrameRateWithChangeStrategy(ANativeWindow* window, floa return native_window_set_frame_rate(window, frameRate, compatibility, changeFrameRateStrategy); } -int32_t ANativeWindow_clearFrameRate(ANativeWindow* window) { - if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) { - return -EINVAL; - } - return native_window_set_frame_rate(window, 0, - ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, - ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS); -} - /************************************************************************************************** * vndk-stable **************************************************************************************************/ diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h index a27e3dd503..be6623ee75 100644 --- a/libs/nativewindow/include/android/native_window.h +++ b/libs/nativewindow/include/android/native_window.h @@ -372,8 +372,12 @@ int32_t ANativeWindow_setFrameRateWithChangeStrategy(ANativeWindow* window, floa * * \return 0 for success, -EINVAL if the window value is invalid. */ -int32_t ANativeWindow_clearFrameRate(ANativeWindow* window) - __INTRODUCED_IN(__ANDROID_API_U__); +inline int32_t ANativeWindow_clearFrameRate(ANativeWindow* window) + __INTRODUCED_IN(__ANDROID_API_U__) { + return ANativeWindow_setFrameRateWithChangeStrategy(window, 0, + ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, + ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS); +} #ifdef __cplusplus } diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt index 76d23fab1d..63fdcc9205 100644 --- a/libs/nativewindow/libnativewindow.map.txt +++ b/libs/nativewindow/libnativewindow.map.txt @@ -51,7 +51,6 @@ LIBNATIVEWINDOW { ANativeWindow_setDequeueTimeout; # systemapi # introduced=30 ANativeWindow_setFrameRate; # introduced=30 ANativeWindow_setFrameRateWithChangeStrategy; # introduced=31 - ANativeWindow_clearFrameRate; # introduced=UpsideDownCake ANativeWindow_setSharedBufferMode; # llndk ANativeWindow_setSwapInterval; # llndk ANativeWindow_setUsage; # llndk -- GitLab From a9cf419caeebb019a6ca39fd43aab69f0956ae85 Mon Sep 17 00:00:00 2001 From: Michael Wright Date: Thu, 1 Dec 2022 23:46:39 +0000 Subject: [PATCH 0563/1310] Convert orientation values in input to ui::Rotation. ui::Rotation both provides better typesafety as well as some convenience functions (e.g. operator+, operator-). Test: atest TouchVideoFrame_test.cpp InputReader_test.cpp Change-Id: Ib423457c742ed3d41f2e3fc269ddf86809cbf247 --- include/input/DisplayViewport.h | 14 +- include/input/Input.h | 2 +- include/input/TouchVideoFrame.h | 4 +- libs/input/Input.cpp | 10 +- libs/input/TouchVideoFrame.cpp | 11 +- libs/input/tests/TouchVideoFrame_test.cpp | 46 +-- libs/ui/include/ui/Rotation.h | 9 +- .../inputflinger/include/InputReaderBase.h | 3 +- .../reader/mapper/CursorInputMapper.cpp | 2 +- .../reader/mapper/CursorInputMapper.h | 3 +- .../reader/mapper/KeyboardInputMapper.cpp | 15 +- .../reader/mapper/KeyboardInputMapper.h | 2 +- .../mapper/RotaryEncoderInputMapper.cpp | 6 +- .../reader/mapper/RotaryEncoderInputMapper.h | 4 +- .../mapper/TouchCursorInputMapperCommon.h | 19 +- .../reader/mapper/TouchInputMapper.cpp | 50 ++- .../reader/mapper/TouchInputMapper.h | 13 +- .../tests/FakeInputReaderPolicy.cpp | 8 +- .../tests/FakeInputReaderPolicy.h | 6 +- .../inputflinger/tests/InputReader_test.cpp | 345 +++++++++--------- .../tests/fuzzers/MapperHelpers.h | 2 +- 21 files changed, 282 insertions(+), 292 deletions(-) diff --git a/include/input/DisplayViewport.h b/include/input/DisplayViewport.h index 98a18c9560..7457496784 100644 --- a/include/input/DisplayViewport.h +++ b/include/input/DisplayViewport.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -29,13 +30,6 @@ using android::base::StringPrintf; namespace android { -enum { - DISPLAY_ORIENTATION_0 = 0, - DISPLAY_ORIENTATION_90 = 1, - DISPLAY_ORIENTATION_180 = 2, - DISPLAY_ORIENTATION_270 = 3 -}; - /** * Describes the different type of viewports supported by input flinger. * Keep in sync with values in InputManagerService.java. @@ -54,7 +48,7 @@ enum class ViewportType : int32_t { */ struct DisplayViewport { int32_t displayId; // -1 if invalid - int32_t orientation; + ui::Rotation orientation; int32_t logicalLeft; int32_t logicalTop; int32_t logicalRight; @@ -74,7 +68,7 @@ struct DisplayViewport { DisplayViewport() : displayId(ADISPLAY_ID_NONE), - orientation(DISPLAY_ORIENTATION_0), + orientation(ui::ROTATION_0), logicalLeft(0), logicalTop(0), logicalRight(0), @@ -111,7 +105,7 @@ struct DisplayViewport { void setNonDisplayViewport(int32_t width, int32_t height) { displayId = ADISPLAY_ID_NONE; - orientation = DISPLAY_ORIENTATION_0; + orientation = ui::ROTATION_0; logicalLeft = 0; logicalTop = 0; logicalRight = width; diff --git a/include/input/Input.h b/include/input/Input.h index d298d817f5..07a566a63c 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -577,7 +577,7 @@ public: inline const ui::Transform& getTransform() const { return mTransform; } - int getSurfaceRotation() const; + int32_t getSurfaceRotation() const; inline float getXPrecision() const { return mXPrecision; } diff --git a/include/input/TouchVideoFrame.h b/include/input/TouchVideoFrame.h index a616a95ab1..1e4f6e7a4c 100644 --- a/include/input/TouchVideoFrame.h +++ b/include/input/TouchVideoFrame.h @@ -16,6 +16,8 @@ #pragma once +#include + #include #include #include @@ -58,7 +60,7 @@ public: * Rotate the video frame. * The rotation value is an enum from ui/Rotation.h */ - void rotate(int32_t orientation); + void rotate(ui::Rotation orientation); private: uint32_t mHeight; diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index 3685f54a53..162b757743 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -552,19 +552,19 @@ void MotionEvent::addSample( &pointerCoords[getPointerCount()]); } -int MotionEvent::getSurfaceRotation() const { +int32_t MotionEvent::getSurfaceRotation() const { // The surface rotation is the rotation from the window's coordinate space to that of the // display. Since the event's transform takes display space coordinates to window space, the // returned surface rotation is the inverse of the rotation for the surface. switch (mTransform.getOrientation()) { case ui::Transform::ROT_0: - return DISPLAY_ORIENTATION_0; + return static_cast(ui::ROTATION_0); case ui::Transform::ROT_90: - return DISPLAY_ORIENTATION_270; + return static_cast(ui::ROTATION_270); case ui::Transform::ROT_180: - return DISPLAY_ORIENTATION_180; + return static_cast(ui::ROTATION_180); case ui::Transform::ROT_270: - return DISPLAY_ORIENTATION_90; + return static_cast(ui::ROTATION_90); default: return -1; } diff --git a/libs/input/TouchVideoFrame.cpp b/libs/input/TouchVideoFrame.cpp index c62e0985f1..c9393f4451 100644 --- a/libs/input/TouchVideoFrame.cpp +++ b/libs/input/TouchVideoFrame.cpp @@ -40,17 +40,20 @@ const std::vector& TouchVideoFrame::getData() const { return mData; } const struct timeval& TouchVideoFrame::getTimestamp() const { return mTimestamp; } -void TouchVideoFrame::rotate(int32_t orientation) { +void TouchVideoFrame::rotate(ui::Rotation orientation) { switch (orientation) { - case DISPLAY_ORIENTATION_90: + case ui::ROTATION_90: rotateQuarterTurn(false /*clockwise*/); break; - case DISPLAY_ORIENTATION_180: + case ui::ROTATION_180: rotate180(); break; - case DISPLAY_ORIENTATION_270: + case ui::ROTATION_270: rotateQuarterTurn(true /*clockwise*/); break; + case ui::ROTATION_0: + // No need to rotate if there's no rotation. + break; } } diff --git a/libs/input/tests/TouchVideoFrame_test.cpp b/libs/input/tests/TouchVideoFrame_test.cpp index 654b236bda..081a995a6f 100644 --- a/libs/input/tests/TouchVideoFrame_test.cpp +++ b/libs/input/tests/TouchVideoFrame_test.cpp @@ -73,38 +73,38 @@ TEST(TouchVideoFrame, Equality) { TEST(TouchVideoFrame, Rotate90_0x0) { TouchVideoFrame frame(0, 0, {}, TIMESTAMP); TouchVideoFrame frameRotated(0, 0, {}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_90); + frame.rotate(ui::ROTATION_90); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate90_1x1) { TouchVideoFrame frame(1, 1, {1}, TIMESTAMP); TouchVideoFrame frameRotated(1, 1, {1}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_90); + frame.rotate(ui::ROTATION_90); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate90_2x2) { TouchVideoFrame frame(2, 2, {1, 2, 3, 4}, TIMESTAMP); TouchVideoFrame frameRotated(2, 2, {2, 4, 1, 3}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_90); + frame.rotate(ui::ROTATION_90); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate90_3x2) { TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP); TouchVideoFrame frameRotated(2, 3, {2, 4, 6, 1, 3, 5}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_90); + frame.rotate(ui::ROTATION_90); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate90_3x2_4times) { TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP); TouchVideoFrame frameOriginal(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_90); - frame.rotate(DISPLAY_ORIENTATION_90); - frame.rotate(DISPLAY_ORIENTATION_90); - frame.rotate(DISPLAY_ORIENTATION_90); + frame.rotate(ui::ROTATION_90); + frame.rotate(ui::ROTATION_90); + frame.rotate(ui::ROTATION_90); + frame.rotate(ui::ROTATION_90); ASSERT_EQ(frame, frameOriginal); } @@ -113,43 +113,43 @@ TEST(TouchVideoFrame, Rotate90_3x2_4times) { TEST(TouchVideoFrame, Rotate180_0x0) { TouchVideoFrame frame(0, 0, {}, TIMESTAMP); TouchVideoFrame frameRotated(0, 0, {}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_180); + frame.rotate(ui::ROTATION_180); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate180_1x1) { TouchVideoFrame frame(1, 1, {1}, TIMESTAMP); TouchVideoFrame frameRotated(1, 1, {1}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_180); + frame.rotate(ui::ROTATION_180); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate180_2x2) { TouchVideoFrame frame(2, 2, {1, 2, 3, 4}, TIMESTAMP); TouchVideoFrame frameRotated(2, 2, {4, 3, 2, 1}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_180); + frame.rotate(ui::ROTATION_180); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate180_3x2) { TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP); TouchVideoFrame frameRotated(3, 2, {6, 5, 4, 3, 2, 1}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_180); + frame.rotate(ui::ROTATION_180); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate180_3x2_2times) { TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP); TouchVideoFrame frameOriginal(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_180); - frame.rotate(DISPLAY_ORIENTATION_180); + frame.rotate(ui::ROTATION_180); + frame.rotate(ui::ROTATION_180); ASSERT_EQ(frame, frameOriginal); } TEST(TouchVideoFrame, Rotate180_3x3) { TouchVideoFrame frame(3, 3, {1, 2, 3, 4, 5, 6, 7, 8, 9}, TIMESTAMP); TouchVideoFrame frameRotated(3, 3, {9, 8, 7, 6, 5, 4, 3, 2, 1}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_180); + frame.rotate(ui::ROTATION_180); ASSERT_EQ(frame, frameRotated); } @@ -158,38 +158,38 @@ TEST(TouchVideoFrame, Rotate180_3x3) { TEST(TouchVideoFrame, Rotate270_0x0) { TouchVideoFrame frame(0, 0, {}, TIMESTAMP); TouchVideoFrame frameRotated(0, 0, {}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_270); + frame.rotate(ui::ROTATION_270); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate270_1x1) { TouchVideoFrame frame(1, 1, {1}, TIMESTAMP); TouchVideoFrame frameRotated(1, 1, {1}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_270); + frame.rotate(ui::ROTATION_270); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate270_2x2) { TouchVideoFrame frame(2, 2, {1, 2, 3, 4}, TIMESTAMP); TouchVideoFrame frameRotated(2, 2, {3, 1, 4, 2}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_270); + frame.rotate(ui::ROTATION_270); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate270_3x2) { TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP); TouchVideoFrame frameRotated(2, 3, {5, 3, 1, 6, 4, 2}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_270); + frame.rotate(ui::ROTATION_270); ASSERT_EQ(frame, frameRotated); } TEST(TouchVideoFrame, Rotate270_3x2_4times) { TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP); TouchVideoFrame frameOriginal(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP); - frame.rotate(DISPLAY_ORIENTATION_270); - frame.rotate(DISPLAY_ORIENTATION_270); - frame.rotate(DISPLAY_ORIENTATION_270); - frame.rotate(DISPLAY_ORIENTATION_270); + frame.rotate(ui::ROTATION_270); + frame.rotate(ui::ROTATION_270); + frame.rotate(ui::ROTATION_270); + frame.rotate(ui::ROTATION_270); ASSERT_EQ(frame, frameOriginal); } diff --git a/libs/ui/include/ui/Rotation.h b/libs/ui/include/ui/Rotation.h index 83d431dea3..c1d60f4f6c 100644 --- a/libs/ui/include/ui/Rotation.h +++ b/libs/ui/include/ui/Rotation.h @@ -20,7 +20,14 @@ namespace android::ui { -enum class Rotation { Rotation0 = 0, Rotation90 = 1, Rotation180 = 2, Rotation270 = 3 }; +enum class Rotation { + Rotation0 = 0, + Rotation90 = 1, + Rotation180 = 2, + Rotation270 = 3, + + ftl_last = Rotation270 +}; // Equivalent to Surface.java constants. constexpr auto ROTATION_0 = Rotation::Rotation0; diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index 3b0f2ac5a2..6d6cefb0c0 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -395,7 +396,7 @@ public: /* Gets the affine calibration associated with the specified device. */ virtual TouchAffineTransformation getTouchAffineTransformation( - const std::string& inputDeviceDescriptor, int32_t surfaceRotation) = 0; + const std::string& inputDeviceDescriptor, ui::Rotation surfaceRotation) = 0; /* Notifies the input reader policy that a stylus gesture has started. */ virtual void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) = 0; }; diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index a1a2af9500..13e4d0cfbe 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -227,7 +227,7 @@ std::list CursorInputMapper::configure(nsecs_t when, mDisplayId = mPointerController->getDisplayId(); } - mOrientation = DISPLAY_ORIENTATION_0; + mOrientation = ui::ROTATION_0; const bool isOrientedDevice = (mParameters.orientationAware && mParameters.hasAssociatedDisplay); // InputReader works in the un-rotated display coordinate space, so we don't need to do diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index 20746e5bb0..939cceb6b0 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -22,6 +22,7 @@ #include #include +#include namespace android { @@ -115,7 +116,7 @@ private: // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e. // std::nullopt), all events will be ignored. std::optional mDisplayId; - int32_t mOrientation; + ui::Rotation mOrientation; std::shared_ptr mPointerController; diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index da9413e4ca..44f0dfe3b6 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -20,11 +20,13 @@ #include "KeyboardInputMapper.h" +#include + namespace android { // --- Static Definitions --- -static int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) { +static int32_t rotateKeyCode(int32_t keyCode, ui::Rotation orientation) { static constexpr int32_t KEYCODE_ROTATION_MAP[][4] = { // key codes enumerated counter-clockwise with the original (unrotated) key first // no rotation, 90 degree rotation, 180 degree rotation, 270 degree rotation @@ -42,11 +44,10 @@ static int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) { AKEYCODE_SYSTEM_NAVIGATION_RIGHT, AKEYCODE_SYSTEM_NAVIGATION_UP}, }; - LOG_ALWAYS_FATAL_IF(orientation < 0 || orientation > 3, "Invalid orientation: %d", orientation); - if (orientation != DISPLAY_ORIENTATION_0) { + if (orientation != ui::ROTATION_0) { for (const auto& rotation : KEYCODE_ROTATION_MAP) { - if (rotation[DISPLAY_ORIENTATION_0] == keyCode) { - return rotation[orientation]; + if (rotation[static_cast(ui::ROTATION_0)] == keyCode) { + return rotation[static_cast(orientation)]; } } } @@ -100,11 +101,11 @@ uint32_t KeyboardInputMapper::getSources() const { return mSource; } -int32_t KeyboardInputMapper::getOrientation() { +ui::Rotation KeyboardInputMapper::getOrientation() { if (mViewport) { return mViewport->orientation; } - return DISPLAY_ORIENTATION_0; + return ui::ROTATION_0; } int32_t KeyboardInputMapper::getDisplayId() { diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index 11d5ad26e8..0526fd89de 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -82,7 +82,7 @@ private: void configureParameters(); void dumpParameters(std::string& dump) const; - int32_t getOrientation(); + ui::Rotation getOrientation(); int32_t getDisplayId(); [[nodiscard]] std::list processKey(nsecs_t when, nsecs_t readTime, bool down, diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp index 06d4dc342d..19a79d7751 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp @@ -25,7 +25,7 @@ namespace android { RotaryEncoderInputMapper::RotaryEncoderInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext), mOrientation(DISPLAY_ORIENTATION_0) { + : InputMapper(deviceContext), mOrientation(ui::ROTATION_0) { mSource = AINPUT_SOURCE_ROTARY_ENCODER; } @@ -73,7 +73,7 @@ std::list RotaryEncoderInputMapper::configure(nsecs_t when, if (internalViewport) { mOrientation = internalViewport->orientation; } else { - mOrientation = DISPLAY_ORIENTATION_0; + mOrientation = ui::ROTATION_0; } } return out; @@ -107,7 +107,7 @@ std::list RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readT // This is not a pointer, so it's not associated with a display. int32_t displayId = ADISPLAY_ID_NONE; - if (mOrientation == DISPLAY_ORIENTATION_180) { + if (mOrientation == ui::ROTATION_180) { scroll = -scroll; } diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h index f4352e76a0..cb5fd88209 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h @@ -16,6 +16,8 @@ #pragma once +#include + #include "CursorScrollAccumulator.h" #include "InputMapper.h" @@ -40,7 +42,7 @@ private: int32_t mSource; float mScalingFactor; - int32_t mOrientation; + ui::Rotation mOrientation; [[nodiscard]] std::list sync(nsecs_t when, nsecs_t readTime); }; diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h index d8a4d34d20..1c3ca975ac 100644 --- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h +++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h @@ -18,6 +18,7 @@ #include #include +#include #include "EventHub.h" #include "InputListener.h" @@ -27,32 +28,32 @@ namespace android { // --- Static Definitions --- -static int32_t getInverseRotation(int32_t orientation) { +static ui::Rotation getInverseRotation(ui::Rotation orientation) { switch (orientation) { - case DISPLAY_ORIENTATION_90: - return DISPLAY_ORIENTATION_270; - case DISPLAY_ORIENTATION_270: - return DISPLAY_ORIENTATION_90; + case ui::ROTATION_90: + return ui::ROTATION_270; + case ui::ROTATION_270: + return ui::ROTATION_90; default: return orientation; } } -static void rotateDelta(int32_t orientation, float* deltaX, float* deltaY) { +static void rotateDelta(ui::Rotation orientation, float* deltaX, float* deltaY) { float temp; switch (orientation) { - case DISPLAY_ORIENTATION_90: + case ui::ROTATION_90: temp = *deltaX; *deltaX = *deltaY; *deltaY = -temp; break; - case DISPLAY_ORIENTATION_180: + case ui::ROTATION_180: *deltaX = -*deltaX; *deltaY = -*deltaY; break; - case DISPLAY_ORIENTATION_270: + case ui::ROTATION_270: temp = *deltaX; *deltaX = -*deltaY; *deltaY = temp; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 5631a10253..cefc44ef7a 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -27,6 +27,7 @@ #include "CursorScrollAccumulator.h" #include "TouchButtonAccumulator.h" #include "TouchCursorInputMapperCommon.h" +#include "ui/Rotation.h" namespace android { @@ -81,16 +82,14 @@ inline static int32_t signExtendNybble(int32_t value) { } static std::tuple getNaturalDisplayInfo( - const DisplayViewport& viewport, int32_t naturalOrientation) { - const auto rotation = ui::toRotation(naturalOrientation); - + const DisplayViewport& viewport, ui::Rotation naturalOrientation) { ui::Size rotatedDisplaySize{viewport.deviceWidth, viewport.deviceHeight}; - if (rotation == ui::ROTATION_90 || rotation == ui::ROTATION_270) { + if (naturalOrientation == ui::ROTATION_90 || naturalOrientation == ui::ROTATION_270) { std::swap(rotatedDisplaySize.width, rotatedDisplaySize.height); } - ui::Transform rotate(ui::Transform::toRotationFlags(rotation), rotatedDisplaySize.width, - rotatedDisplaySize.height); + ui::Transform rotate(ui::Transform::toRotationFlags(naturalOrientation), + rotatedDisplaySize.width, rotatedDisplaySize.height); Rect physicalFrame{viewport.physicalLeft, viewport.physicalTop, viewport.physicalRight, viewport.physicalBottom}; @@ -133,7 +132,7 @@ TouchInputMapper::TouchInputMapper(InputDeviceContext& deviceContext) mTouchButtonAccumulator(deviceContext), mSource(0), mDeviceMode(DeviceMode::DISABLED), - mInputDeviceOrientation(DISPLAY_ORIENTATION_0) {} + mInputDeviceOrientation(ui::ROTATION_0) {} TouchInputMapper::~TouchInputMapper() {} @@ -424,18 +423,18 @@ void TouchInputMapper::configureParameters() { getDeviceContext().getConfiguration().tryGetProperty("touch.orientationAware", mParameters.orientationAware); - mParameters.orientation = Parameters::Orientation::ORIENTATION_0; + mParameters.orientation = ui::ROTATION_0; std::string orientationString; if (getDeviceContext().getConfiguration().tryGetProperty("touch.orientation", orientationString)) { if (mParameters.deviceType != Parameters::DeviceType::TOUCH_SCREEN) { ALOGW("The configuration 'touch.orientation' is only supported for touchscreens."); } else if (orientationString == "ORIENTATION_90") { - mParameters.orientation = Parameters::Orientation::ORIENTATION_90; + mParameters.orientation = ui::ROTATION_90; } else if (orientationString == "ORIENTATION_180") { - mParameters.orientation = Parameters::Orientation::ORIENTATION_180; + mParameters.orientation = ui::ROTATION_180; } else if (orientationString == "ORIENTATION_270") { - mParameters.orientation = Parameters::Orientation::ORIENTATION_270; + mParameters.orientation = ui::ROTATION_270; } else if (orientationString != "ORIENTATION_0") { ALOGW("Invalid value for touch.orientation: '%s'", orientationString.c_str()); } @@ -812,8 +811,8 @@ void TouchInputMapper::initializeOrientedRanges() { // Note that the maximum value reported is an inclusive maximum value so it is one // unit less than the total width or height of the display. switch (mInputDeviceOrientation) { - case DISPLAY_ORIENTATION_90: - case DISPLAY_ORIENTATION_270: + case ui::ROTATION_90: + case ui::ROTATION_270: mOrientedXPrecision = mYPrecision; mOrientedYPrecision = mXPrecision; @@ -923,8 +922,8 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) // Apply the inverse of the input device orientation so that the input device is // configured in the same orientation as the viewport. The input device orientation will // be re-applied by mInputDeviceOrientation. - const int32_t naturalDeviceOrientation = - (mViewport.orientation - static_cast(mParameters.orientation) + 4) % 4; + const ui::Rotation naturalDeviceOrientation = + mViewport.orientation - mParameters.orientation; std::tie(mDisplayBounds, mPhysicalFrameInDisplay) = getNaturalDisplayInfo(mViewport, naturalDeviceOrientation); @@ -935,7 +934,7 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) // when the display rotation is applied later as a part of the per-window transform, we // get the expected screen coordinates. mInputDeviceOrientation = mParameters.orientationAware - ? DISPLAY_ORIENTATION_0 + ? ui::ROTATION_0 : getInverseRotation(mViewport.orientation); // For orientation-aware devices that work in the un-rotated coordinate space, the // viewport update should be skipped if it is only a change in the orientation. @@ -943,12 +942,11 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) mDisplayBounds == oldDisplayBounds && viewportOrientationChanged; // Apply the input device orientation for the device. - mInputDeviceOrientation = - (mInputDeviceOrientation + static_cast(mParameters.orientation)) % 4; + mInputDeviceOrientation = mInputDeviceOrientation + mParameters.orientation; } else { mDisplayBounds = rawSize; mPhysicalFrameInDisplay = Rect{mDisplayBounds}; - mInputDeviceOrientation = DISPLAY_ORIENTATION_0; + mInputDeviceOrientation = ui::ROTATION_0; } } @@ -2349,7 +2347,7 @@ void TouchInputMapper::cookPointerData() { float left, top, right, bottom; switch (mInputDeviceOrientation) { - case DISPLAY_ORIENTATION_90: + case ui::ROTATION_90: left = float(rawTop - mRawPointerAxes.y.minValue) * mYScale; right = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale; bottom = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale; @@ -2360,7 +2358,7 @@ void TouchInputMapper::cookPointerData() { (mOrientedRanges.orientation->max - mOrientedRanges.orientation->min); } break; - case DISPLAY_ORIENTATION_180: + case ui::ROTATION_180: left = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale; right = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale; bottom = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale; @@ -2371,7 +2369,7 @@ void TouchInputMapper::cookPointerData() { (mOrientedRanges.orientation->max - mOrientedRanges.orientation->min); } break; - case DISPLAY_ORIENTATION_270: + case ui::ROTATION_270: left = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale; right = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale; bottom = float(rawRight - mRawPointerAxes.x.minValue) * mXScale; @@ -3805,19 +3803,19 @@ void TouchInputMapper::rotateAndScale(float& x, float& y) const { // 180 - reverse x, y. // 270 - swap x/y and reverse x. switch (mInputDeviceOrientation) { - case DISPLAY_ORIENTATION_0: + case ui::ROTATION_0: x = xScaled; y = yScaled; break; - case DISPLAY_ORIENTATION_90: + case ui::ROTATION_90: y = xScaledMax; x = yScaled; break; - case DISPLAY_ORIENTATION_180: + case ui::ROTATION_180: x = xScaledMax; y = yScaledMax; break; - case DISPLAY_ORIENTATION_270: + case ui::ROTATION_270: y = xScaled; x = yScaledMax; break; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 3962b2a2fc..34ba62515f 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include "CursorButtonAccumulator.h" #include "CursorScrollAccumulator.h" @@ -218,15 +219,7 @@ protected: bool associatedDisplayIsExternal; bool orientationAware; - enum class Orientation : int32_t { - ORIENTATION_0 = DISPLAY_ORIENTATION_0, - ORIENTATION_90 = DISPLAY_ORIENTATION_90, - ORIENTATION_180 = DISPLAY_ORIENTATION_180, - ORIENTATION_270 = DISPLAY_ORIENTATION_270, - - ftl_last = ORIENTATION_270 - }; - Orientation orientation; + ui::Rotation orientation; bool hasButtonUnderPad; std::string uniqueDisplayId; @@ -424,7 +417,7 @@ private: // The orientation of the input device relative to that of the display panel. It specifies // the rotation of the input device coordinates required to produce the display panel // orientation, so it will depend on whether the device is orientation aware. - int32_t mInputDeviceOrientation; + ui::Rotation mInputDeviceOrientation; // Translation and scaling factors, orientation-independent. float mXScale; diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp index 5c6a1b8f6d..3af4298434 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp +++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp @@ -20,6 +20,7 @@ #include #include "TestConstants.h" +#include "ui/Rotation.h" namespace android { @@ -76,12 +77,11 @@ void FakeInputReaderPolicy::addDisplayViewport(DisplayViewport viewport) { } void FakeInputReaderPolicy::addDisplayViewport(int32_t displayId, int32_t width, int32_t height, - int32_t orientation, bool isActive, + ui::Rotation orientation, bool isActive, const std::string& uniqueId, std::optional physicalPort, ViewportType type) { - const bool isRotated = - (orientation == DISPLAY_ORIENTATION_90 || orientation == DISPLAY_ORIENTATION_270); + const bool isRotated = orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270; DisplayViewport v; v.displayId = displayId; v.orientation = orientation; @@ -153,7 +153,7 @@ const std::vector& FakeInputReaderPolicy::getInputDevices() con } TouchAffineTransformation FakeInputReaderPolicy::getTouchAffineTransformation( - const std::string& inputDeviceDescriptor, int32_t surfaceRotation) { + const std::string& inputDeviceDescriptor, ui::Rotation surfaceRotation) { return transform; } diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h index 65fe08f87b..c16cda404e 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.h +++ b/services/inputflinger/tests/FakeInputReaderPolicy.h @@ -49,8 +49,8 @@ public: std::optional getDisplayViewportByType(ViewportType type) const; std::optional getDisplayViewportByPort(uint8_t displayPort) const; void addDisplayViewport(DisplayViewport viewport); - void addDisplayViewport(int32_t displayId, int32_t width, int32_t height, int32_t orientation, - bool isActive, const std::string& uniqueId, + void addDisplayViewport(int32_t displayId, int32_t width, int32_t height, + ui::Rotation orientation, bool isActive, const std::string& uniqueId, std::optional physicalPort, ViewportType type); bool updateViewport(const DisplayViewport& viewport); void addExcludedDeviceName(const std::string& deviceName); @@ -63,7 +63,7 @@ public: const InputReaderConfiguration* getReaderConfiguration() const; const std::vector& getInputDevices() const; TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor, - int32_t surfaceRotation); + ui::Rotation surfaceRotation); void setTouchAffineTransformation(const TouchAffineTransformation t); PointerCaptureRequest setPointerCapture(bool enabled); void setShowTouches(bool enabled); diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index c72d01fad8..01539671d8 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -36,8 +36,10 @@ #include #include #include +#include #include #include +#include #include #include "FakeEventHub.h" @@ -111,12 +113,12 @@ const std::unordered_map LIGHT_COLORS = {{"red", LightC {"green", LightColor::GREEN}, {"blue", LightColor::BLUE}}; -static int32_t getInverseRotation(int32_t orientation) { +static ui::Rotation getInverseRotation(ui::Rotation orientation) { switch (orientation) { - case DISPLAY_ORIENTATION_90: - return DISPLAY_ORIENTATION_270; - case DISPLAY_ORIENTATION_270: - return DISPLAY_ORIENTATION_90; + case ui::ROTATION_90: + return ui::ROTATION_270; + case ui::ROTATION_270: + return ui::ROTATION_90; default: return orientation; } @@ -479,9 +481,8 @@ TEST_F(InputReaderPolicyTest, Viewports_GetCleared) { ASSERT_FALSE(internalViewport); // Add an internal viewport, then clear it - mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId, NO_PORT, - ViewportType::INTERNAL); + mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + true /*isActive*/, uniqueId, NO_PORT, ViewportType::INTERNAL); // Check matching by uniqueId internalViewport = mFakePolicy->getDisplayViewportByUniqueId(uniqueId); @@ -510,21 +511,21 @@ TEST_F(InputReaderPolicyTest, Viewports_GetByType) { constexpr int32_t virtualDisplayId2 = 3; // Add an internal viewport - mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, internalUniqueId, - NO_PORT, ViewportType::INTERNAL); + mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + true /*isActive*/, internalUniqueId, NO_PORT, + ViewportType::INTERNAL); // Add an external viewport - mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, externalUniqueId, - NO_PORT, ViewportType::EXTERNAL); + mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + true /*isActive*/, externalUniqueId, NO_PORT, + ViewportType::EXTERNAL); // Add an virtual viewport mFakePolicy->addDisplayViewport(virtualDisplayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, virtualUniqueId1, - NO_PORT, ViewportType::VIRTUAL); + ui::ROTATION_0, true /*isActive*/, virtualUniqueId1, NO_PORT, + ViewportType::VIRTUAL); // Add another virtual viewport mFakePolicy->addDisplayViewport(virtualDisplayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, virtualUniqueId2, - NO_PORT, ViewportType::VIRTUAL); + ui::ROTATION_0, true /*isActive*/, virtualUniqueId2, NO_PORT, + ViewportType::VIRTUAL); // Check matching by type for internal std::optional internalViewport = @@ -572,13 +573,11 @@ TEST_F(InputReaderPolicyTest, Viewports_TwoOfSameType) { for (const ViewportType& type : types) { mFakePolicy->clearViewports(); // Add a viewport - mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1, - NO_PORT, type); + mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + true /*isActive*/, uniqueId1, NO_PORT, type); // Add another viewport - mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, - NO_PORT, type); + mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + true /*isActive*/, uniqueId2, NO_PORT, type); // Check that correct display viewport was returned by comparing the display IDs. std::optional viewport1 = @@ -618,10 +617,10 @@ TEST_F(InputReaderPolicyTest, Viewports_ByTypeReturnsDefaultForInternal) { // Add the default display first and ensure it gets returned. mFakePolicy->clearViewports(); mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1, NO_PORT, + ui::ROTATION_0, true /*isActive*/, uniqueId1, NO_PORT, ViewportType::INTERNAL); mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, NO_PORT, + ui::ROTATION_0, true /*isActive*/, uniqueId2, NO_PORT, ViewportType::INTERNAL); std::optional viewport = @@ -633,10 +632,10 @@ TEST_F(InputReaderPolicyTest, Viewports_ByTypeReturnsDefaultForInternal) { // Add the default display second to make sure order doesn't matter. mFakePolicy->clearViewports(); mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, NO_PORT, + ui::ROTATION_0, true /*isActive*/, uniqueId2, NO_PORT, ViewportType::INTERNAL); mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1, NO_PORT, + ui::ROTATION_0, true /*isActive*/, uniqueId1, NO_PORT, ViewportType::INTERNAL); viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); @@ -660,13 +659,11 @@ TEST_F(InputReaderPolicyTest, Viewports_GetByPort) { mFakePolicy->clearViewports(); // Add a viewport that's associated with some display port that's not of interest. - mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1, hdmi3, - type); + mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + true /*isActive*/, uniqueId1, hdmi3, type); // Add another viewport, connected to HDMI1 port - mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, hdmi1, - type); + mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + true /*isActive*/, uniqueId2, hdmi1, type); // Check that correct display viewport was returned by comparing the display ports. std::optional hdmi1Viewport = mFakePolicy->getDisplayViewportByPort(hdmi1); @@ -1119,11 +1116,10 @@ TEST_F(InputReaderTest, Device_CanDispatchToDisplay) { // Add default and second display. mFakePolicy->clearViewports(); - mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, "local:0", NO_PORT, - ViewportType::INTERNAL); + mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + true /*isActive*/, "local:0", NO_PORT, ViewportType::INTERNAL); mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, "local:1", hdmi1, + ui::ROTATION_0, true /*isActive*/, "local:1", hdmi1, ViewportType::EXTERNAL); mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO); mReader->loopOnce(); @@ -1582,9 +1578,8 @@ protected: #endif InputReaderIntegrationTest::SetUp(); // At least add an internal display. - setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, UNIQUE_ID, NO_PORT, - ViewportType::INTERNAL); + setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); mDevice = createUinputDevice(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)); ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); @@ -1595,7 +1590,7 @@ protected: } void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, - int32_t orientation, const std::string& uniqueId, + ui::Rotation orientation, const std::string& uniqueId, std::optional physicalPort, ViewportType viewportType) { mFakePolicy->addDisplayViewport(displayId, width, height, orientation, true /*isActive*/, @@ -2533,7 +2528,7 @@ TEST_F(InputDeviceTest, Configure_AssignsDisplayPort) { // Prepare displays. mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, UNIQUE_ID, hdmi, + ui::ROTATION_0, true /*isActive*/, UNIQUE_ID, hdmi, ViewportType::INTERNAL); unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), InputReaderConfiguration::CHANGE_DISPLAY_INFO); @@ -2568,7 +2563,7 @@ TEST_F(InputDeviceTest, Configure_AssignsDisplayUniqueId) { // Device should be enabled when a display is found. mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID, + ui::ROTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), InputReaderConfiguration::CHANGE_DISPLAY_INFO); @@ -2594,7 +2589,7 @@ TEST_F(InputDeviceTest, Configure_UniqueId_CorrectlyMatches) { mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID, + ui::ROTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), InputReaderConfiguration::CHANGE_DISPLAY_INFO); @@ -2713,8 +2708,9 @@ protected: } void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, - int32_t orientation, const std::string& uniqueId, - std::optional physicalPort, ViewportType viewportType) { + ui::Rotation orientation, const std::string& uniqueId, + std::optional physicalPort, + ViewportType viewportType) { mFakePolicy->addDisplayViewport(displayId, width, height, orientation, true /*isActive*/, uniqueId, physicalPort, viewportType); configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); @@ -2744,7 +2740,7 @@ protected: void resetMapper(InputMapper& mapper, nsecs_t when) { const auto resetArgs = mapper.reset(when); - for (const auto args : resetArgs) { + for (const auto& args : resetArgs) { mFakeListener->notify(args); } // Loop the reader to flush the input listener queue. @@ -3069,7 +3065,7 @@ class KeyboardInputMapperTest : public InputMapperTest { protected: const std::string UNIQUE_ID = "local:0"; - void prepareDisplay(int32_t orientation); + void prepareDisplay(ui::Rotation orientation); void testDPadKeyRotation(KeyboardInputMapper& mapper, int32_t originalScanCode, int32_t originalKeyCode, int32_t rotatedKeyCode, @@ -3079,7 +3075,7 @@ protected: /* Similar to setDisplayInfoAndReconfigure, but pre-populates all parameters except for the * orientation. */ -void KeyboardInputMapperTest::prepareDisplay(int32_t orientation) { +void KeyboardInputMapperTest::prepareDisplay(ui::Rotation orientation) { setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); } @@ -3291,7 +3287,7 @@ TEST_F(KeyboardInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateD addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); - prepareDisplay(DISPLAY_ORIENTATION_90); + prepareDisplay(ui::ROTATION_90); ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP)); ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, @@ -3313,7 +3309,7 @@ TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) { addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); ASSERT_NO_FATAL_FAILURE( testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP, DISPLAY_ID)); ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT, @@ -3324,7 +3320,7 @@ TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) { AKEYCODE_DPAD_LEFT, DISPLAY_ID)); clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_90); + prepareDisplay(ui::ROTATION_90); ASSERT_NO_FATAL_FAILURE( testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, DISPLAY_ID)); ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT, @@ -3335,7 +3331,7 @@ TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) { AKEYCODE_DPAD_DOWN, DISPLAY_ID)); clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_180); + prepareDisplay(ui::ROTATION_180); ASSERT_NO_FATAL_FAILURE( testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_DOWN, DISPLAY_ID)); ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT, @@ -3346,7 +3342,7 @@ TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) { AKEYCODE_DPAD_RIGHT, DISPLAY_ID)); clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_270); + prepareDisplay(ui::ROTATION_270); ASSERT_NO_FATAL_FAILURE( testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_RIGHT, DISPLAY_ID)); ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT, @@ -3360,7 +3356,7 @@ TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) { // in the key up as we did in the key down. NotifyKeyArgs args; clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_270); + prepareDisplay(ui::ROTATION_270); process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action); @@ -3368,7 +3364,7 @@ TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) { ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode); clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_180); + prepareDisplay(ui::ROTATION_180); process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); @@ -3393,7 +3389,7 @@ TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_NotOrientationAware ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); ASSERT_EQ(ADISPLAY_ID_NONE, args.displayId); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0); @@ -3415,7 +3411,7 @@ TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_OrientationAware) { // Display id should be ADISPLAY_ID_NONE without any display configuration. // ^--- already checked by the previous test - setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0, + setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); @@ -3425,7 +3421,7 @@ TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_OrientationAware) { constexpr int32_t newDisplayId = 2; clearViewports(); - setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0, + setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); @@ -3635,9 +3631,9 @@ TEST_F(KeyboardInputMapperTest, Configure_AssignsDisplayPort) { // Prepare second display. constexpr int32_t newDisplayId = 2; - setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0, + setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, UNIQUE_ID, hdmi1, ViewportType::INTERNAL); - setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0, + setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, SECONDARY_UNIQUE_ID, hdmi2, ViewportType::EXTERNAL); // Default device will reconfigure above, need additional reconfiguration for another device. unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), @@ -3968,14 +3964,14 @@ protected: void testMotionRotation(CursorInputMapper& mapper, int32_t originalX, int32_t originalY, int32_t rotatedX, int32_t rotatedY); - void prepareDisplay(int32_t orientation) { + void prepareDisplay(ui::Rotation orientation) { setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation, DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); } void prepareSecondaryDisplay() { setDisplayInfoAndReconfigure(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, SECONDARY_DISPLAY_UNIQUE_ID, NO_PORT, + ui::ROTATION_0, SECONDARY_DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::EXTERNAL); } @@ -4260,7 +4256,7 @@ TEST_F(CursorInputMapperTest, Process_WhenOrientationAware_ShouldNotRotateMotion addConfigurationProperty("cursor.orientationAware", "1"); CursorInputMapper& mapper = addMapperAndConfigure(); - prepareDisplay(DISPLAY_ORIENTATION_90); + prepareDisplay(ui::ROTATION_90); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, 1)); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, 1)); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 1, 0)); @@ -4279,7 +4275,7 @@ TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotion CursorInputMapper& mapper = addMapperAndConfigure(); clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, 1)); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, 1)); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 1, 0)); @@ -4290,7 +4286,7 @@ TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotion ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, 1)); clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_90); + prepareDisplay(ui::ROTATION_90); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, -1, 0)); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, -1, 1)); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 0, 1)); @@ -4301,7 +4297,7 @@ TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotion ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, -1)); clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_180); + prepareDisplay(ui::ROTATION_180); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, -1)); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, -1, -1)); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, -1, 0)); @@ -4312,7 +4308,7 @@ TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotion ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, 1, -1)); clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_270); + prepareDisplay(ui::ROTATION_270); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 1, 0)); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, -1)); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 0, -1)); @@ -4776,7 +4772,7 @@ TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) { ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); // Ensure the display is rotated. - prepareDisplay(DISPLAY_ORIENTATION_90); + prepareDisplay(ui::ROTATION_90); NotifyMotionArgs args; @@ -4812,7 +4808,7 @@ TEST_F(CursorInputMapperTest, ConfigureDisplayId_NoAssociatedViewport) { CursorInputMapper& mapper = addMapperAndConfigure(); // Set up the default display. - prepareDisplay(DISPLAY_ORIENTATION_90); + prepareDisplay(ui::ROTATION_90); // Set up the secondary display as the display on which the pointer should be shown. // The InputDevice is not associated with any display. @@ -4839,7 +4835,7 @@ TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) { CursorInputMapper& mapper = addMapperAndConfigure(); // Set up the default display. - prepareDisplay(DISPLAY_ORIENTATION_90); + prepareDisplay(ui::ROTATION_90); // Set up the secondary display as the display on which the pointer should be shown, // and associate the InputDevice with the secondary display. @@ -4866,7 +4862,7 @@ TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPoint CursorInputMapper& mapper = addMapperAndConfigure(); // Set up the default display as the display on which the pointer should be shown. - prepareDisplay(DISPLAY_ORIENTATION_90); + prepareDisplay(ui::ROTATION_90); mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); // Associate the InputDevice with the secondary display. @@ -5033,9 +5029,9 @@ protected: TOOL_TYPE = 1 << 10, }; - void prepareDisplay(int32_t orientation, std::optional port = NO_PORT); + void prepareDisplay(ui::Rotation orientation, std::optional port = NO_PORT); void prepareSecondaryDisplay(ViewportType type, std::optional port = NO_PORT); - void prepareVirtualDisplay(int32_t orientation); + void prepareVirtualDisplay(ui::Rotation orientation); void prepareVirtualKeys(); void prepareLocationCalibration(); int32_t toRawX(float displayX); @@ -5089,17 +5085,17 @@ const VirtualKeyDefinition TouchInputMapperTest::VIRTUAL_KEYS[2] = { { KEY_MENU, DISPLAY_HEIGHT - 60, DISPLAY_WIDTH + 15, 20, 20 }, }; -void TouchInputMapperTest::prepareDisplay(int32_t orientation, std::optional port) { +void TouchInputMapperTest::prepareDisplay(ui::Rotation orientation, std::optional port) { setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation, UNIQUE_ID, port, ViewportType::INTERNAL); } void TouchInputMapperTest::prepareSecondaryDisplay(ViewportType type, std::optional port) { setDisplayInfoAndReconfigure(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, SECONDARY_UNIQUE_ID, port, type); + ui::ROTATION_0, SECONDARY_UNIQUE_ID, port, type); } -void TouchInputMapperTest::prepareVirtualDisplay(int32_t orientation) { +void TouchInputMapperTest::prepareVirtualDisplay(ui::Rotation orientation) { setDisplayInfoAndReconfigure(VIRTUAL_DISPLAY_ID, VIRTUAL_DISPLAY_WIDTH, VIRTUAL_DISPLAY_HEIGHT, orientation, VIRTUAL_DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::VIRTUAL); @@ -5266,7 +5262,7 @@ TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchScreen_Return TEST_F(SingleTouchInputMapperTest, GetKeyCodeState) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); prepareVirtualKeys(); @@ -5294,7 +5290,7 @@ TEST_F(SingleTouchInputMapperTest, GetKeyCodeState) { TEST_F(SingleTouchInputMapperTest, GetScanCodeState) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); prepareVirtualKeys(); @@ -5322,7 +5318,7 @@ TEST_F(SingleTouchInputMapperTest, GetScanCodeState) { TEST_F(SingleTouchInputMapperTest, MarkSupportedKeyCodes) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); prepareVirtualKeys(); @@ -5337,7 +5333,7 @@ TEST_F(SingleTouchInputMapperTest, MarkSupportedKeyCodes) { TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndReleasedNormally_SendsKeyDownAndKeyUp) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); prepareVirtualKeys(); @@ -5387,7 +5383,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndReleasedNor TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfBounds_SendsKeyDownAndKeyCancel) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); prepareVirtualKeys(); @@ -5508,7 +5504,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfB TEST_F(SingleTouchInputMapperTest, Process_WhenTouchStartsOutsideDisplayAndMovesIn_SendsDownAsTouchEntersDisplay) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); prepareVirtualKeys(); @@ -5583,7 +5579,7 @@ TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture_VirtualDispl addConfigurationProperty("touch.deviceType", "touchScreen"); addConfigurationProperty("touch.displayId", VIRTUAL_DISPLAY_UNIQUE_ID); - prepareVirtualDisplay(DISPLAY_ORIENTATION_0); + prepareVirtualDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); prepareVirtualKeys(); @@ -5679,7 +5675,7 @@ TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture_VirtualDispl TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); prepareVirtualKeys(); @@ -5778,7 +5774,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationAware_DoesNotRotateMot NotifyMotionArgs args; // Rotation 90. - prepareDisplay(DISPLAY_ORIENTATION_90); + prepareDisplay(ui::ROTATION_90); processDown(mapper, toRawX(50), toRawY(75)); processSync(mapper); @@ -5804,7 +5800,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_RotatesMotion // Rotation 0. clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); processDown(mapper, toRawX(50), toRawY(75)); processSync(mapper); @@ -5818,7 +5814,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_RotatesMotion // Rotation 90. clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_90); + prepareDisplay(ui::ROTATION_90); processDown(mapper, toRawX(75), RAW_Y_MAX - toRawY(50) + RAW_Y_MIN); processSync(mapper); @@ -5832,7 +5828,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_RotatesMotion // Rotation 180. clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_180); + prepareDisplay(ui::ROTATION_180); processDown(mapper, RAW_X_MAX - toRawX(50) + RAW_X_MIN, RAW_Y_MAX - toRawY(75) + RAW_Y_MIN); processSync(mapper); @@ -5846,7 +5842,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_RotatesMotion // Rotation 270. clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_270); + prepareDisplay(ui::ROTATION_270); processDown(mapper, RAW_X_MAX - toRawX(75) + RAW_X_MIN, toRawY(50)); processSync(mapper); @@ -5866,7 +5862,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation0_RotatesMotions) { addConfigurationProperty("touch.orientationAware", "1"); addConfigurationProperty("touch.orientation", "ORIENTATION_0"); clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); auto& mapper = addMapperAndConfigure(); NotifyMotionArgs args; @@ -5890,7 +5886,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation90_RotatesMotions) { addConfigurationProperty("touch.orientationAware", "1"); addConfigurationProperty("touch.orientation", "ORIENTATION_90"); clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); auto& mapper = addMapperAndConfigure(); NotifyMotionArgs args; @@ -5914,7 +5910,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation180_RotatesMotions) { addConfigurationProperty("touch.orientationAware", "1"); addConfigurationProperty("touch.orientation", "ORIENTATION_180"); clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); auto& mapper = addMapperAndConfigure(); NotifyMotionArgs args; @@ -5938,7 +5934,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation270_RotatesMotions) { addConfigurationProperty("touch.orientationAware", "1"); addConfigurationProperty("touch.orientation", "ORIENTATION_270"); clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); auto& mapper = addMapperAndConfigure(); NotifyMotionArgs args; @@ -5969,7 +5965,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationSpecified_RotatesMotio // Orientation 90, Rotation 0. clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); processDown(mapper, RAW_X_MAX - toRotatedRawX(75) + RAW_X_MIN, toRotatedRawY(50)); processSync(mapper); @@ -5983,7 +5979,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationSpecified_RotatesMotio // Orientation 90, Rotation 90. clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_90); + prepareDisplay(ui::ROTATION_90); processDown(mapper, toRotatedRawX(50), toRotatedRawY(75)); processSync(mapper); @@ -5997,7 +5993,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationSpecified_RotatesMotio // Orientation 90, Rotation 180. clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_180); + prepareDisplay(ui::ROTATION_180); processDown(mapper, toRotatedRawX(75), RAW_Y_MAX - toRotatedRawY(50) + RAW_Y_MIN); processSync(mapper); @@ -6011,7 +6007,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationSpecified_RotatesMotio // Orientation 90, Rotation 270. clearViewports(); - prepareDisplay(DISPLAY_ORIENTATION_270); + prepareDisplay(ui::ROTATION_270); processDown(mapper, RAW_X_MAX - toRotatedRawX(50) + RAW_X_MIN, RAW_Y_MAX - toRotatedRawY(75) + RAW_Y_MIN); processSync(mapper); @@ -6027,7 +6023,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationSpecified_RotatesMotio TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION | PRESSURE | TOOL | DISTANCE | TILT); SingleTouchInputMapper& mapper = addMapperAndConfigure(); @@ -6071,7 +6067,7 @@ TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) { TEST_F(SingleTouchInputMapperTest, Process_XYAxes_AffineCalibration) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareLocationCalibration(); prepareButtons(); prepareAxes(POSITION); @@ -6094,7 +6090,7 @@ TEST_F(SingleTouchInputMapperTest, Process_XYAxes_AffineCalibration) { TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllButtons) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); SingleTouchInputMapper& mapper = addMapperAndConfigure(); @@ -6337,7 +6333,7 @@ TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllButtons) { TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); SingleTouchInputMapper& mapper = addMapperAndConfigure(); @@ -6472,7 +6468,7 @@ TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { TEST_F(SingleTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIsZero) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_FINGER, 0, AKEYCODE_UNKNOWN, 0); @@ -6544,7 +6540,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueI TEST_F(SingleTouchInputMapperTest, Process_WhenAbsPressureIsPresent_HoversIfItsValueIsZero) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION | PRESSURE); SingleTouchInputMapper& mapper = addMapperAndConfigure(); @@ -6615,7 +6611,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenAbsPressureIsPresent_HoversIfItsV TEST_F(SingleTouchInputMapperTest, Reset_CancelsOngoingGesture) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION | PRESSURE); SingleTouchInputMapper& mapper = addMapperAndConfigure(); @@ -6637,7 +6633,7 @@ TEST_F(SingleTouchInputMapperTest, Reset_CancelsOngoingGesture) { TEST_F(SingleTouchInputMapperTest, Reset_RecreatesTouchState) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION | PRESSURE); SingleTouchInputMapper& mapper = addMapperAndConfigure(); @@ -6665,7 +6661,7 @@ TEST_F(SingleTouchInputMapperTest, Reset_RecreatesTouchState) { TEST_F(SingleTouchInputMapperTest, Process_WhenViewportDisplayIdChanged_TouchIsCanceledAndDeviceIsReset) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); SingleTouchInputMapper& mapper = addMapperAndConfigure(); @@ -6693,7 +6689,7 @@ TEST_F(SingleTouchInputMapperTest, TEST_F(SingleTouchInputMapperTest, Process_WhenViewportActiveStatusChanged_TouchIsCanceledAndDeviceIsReset) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); SingleTouchInputMapper& mapper = addMapperAndConfigure(); @@ -6753,7 +6749,7 @@ TEST_F(SingleTouchInputMapperTest, TEST_F(SingleTouchInputMapperTest, ButtonIsReleasedOnTouchUp) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); SingleTouchInputMapper& mapper = addMapperAndConfigure(); @@ -6796,27 +6792,25 @@ public: // The values inside DisplayViewport are expected to be pre-rotated. This updates the current // DisplayViewport to pre-rotate the values. The viewport's physical display will be set to the // rotated equivalent of the given un-rotated physical display bounds. - void configurePhysicalDisplay(int32_t orientation, Rect naturalPhysicalDisplay) { + void configurePhysicalDisplay(ui::Rotation orientation, Rect naturalPhysicalDisplay) { uint32_t inverseRotationFlags; auto width = DISPLAY_WIDTH; auto height = DISPLAY_HEIGHT; switch (orientation) { - case DISPLAY_ORIENTATION_90: + case ui::ROTATION_90: inverseRotationFlags = ui::Transform::ROT_270; std::swap(width, height); break; - case DISPLAY_ORIENTATION_180: + case ui::ROTATION_180: inverseRotationFlags = ui::Transform::ROT_180; break; - case DISPLAY_ORIENTATION_270: + case ui::ROTATION_270: inverseRotationFlags = ui::Transform::ROT_90; std::swap(width, height); break; - case DISPLAY_ORIENTATION_0: + case ui::ROTATION_0: inverseRotationFlags = ui::Transform::ROT_0; break; - default: - FAIL() << "Invalid orientation: " << orientation; } const ui::Transform rotation(inverseRotationFlags, width, height); @@ -6860,7 +6854,7 @@ public: TEST_F(TouchDisplayProjectionTest, IgnoresTouchesOutsidePhysicalDisplay) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); @@ -6875,8 +6869,7 @@ TEST_F(TouchDisplayProjectionTest, IgnoresTouchesOutsidePhysicalDisplay) { static const std::array kPointsOutsidePhysicalDisplay{ {{-10, -10}, {0, 0}, {5, 100}, {50, 15}, {75, 100}, {50, 165}}}; - for (auto orientation : {DISPLAY_ORIENTATION_0, DISPLAY_ORIENTATION_90, DISPLAY_ORIENTATION_180, - DISPLAY_ORIENTATION_270}) { + for (auto orientation : {ui::ROTATION_0, ui::ROTATION_90, ui::ROTATION_180, ui::ROTATION_270}) { configurePhysicalDisplay(orientation, kPhysicalDisplay); // Touches outside the physical display should be ignored, and should not generate any @@ -6896,7 +6889,7 @@ TEST_F(TouchDisplayProjectionTest, IgnoresTouchesOutsidePhysicalDisplay) { TEST_F(TouchDisplayProjectionTest, EmitsTouchDownAfterEnteringPhysicalDisplay) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); @@ -6909,8 +6902,7 @@ TEST_F(TouchDisplayProjectionTest, EmitsTouchDownAfterEnteringPhysicalDisplay) { // points (10, 20) and (70, 160) inside the display space, which is of the size 400 x 800. static const Rect kPhysicalDisplay{10, 20, 70, 160}; - for (auto orientation : {DISPLAY_ORIENTATION_0, DISPLAY_ORIENTATION_90, DISPLAY_ORIENTATION_180, - DISPLAY_ORIENTATION_270}) { + for (auto orientation : {ui::ROTATION_0, ui::ROTATION_90, ui::ROTATION_180, ui::ROTATION_270}) { configurePhysicalDisplay(orientation, kPhysicalDisplay); // Touches that start outside the physical display should be ignored until it enters the @@ -6961,7 +6953,7 @@ class ExternalStylusFusionTest : public SingleTouchInputMapperTest { public: SingleTouchInputMapper& initializeInputMapperWithExternalStylus() { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); auto& mapper = addMapperAndConfigure(); @@ -7450,7 +7442,7 @@ void MultiTouchInputMapperTest::processSync(MultiTouchInputMapper& mapper, nsecs TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackingIds) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION); prepareVirtualKeys(); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -7722,7 +7714,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin TEST_F(MultiTouchInputMapperTest, AxisResolution_IsPopulated) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, /*flat*/ 0, /*fuzz*/ 0, /*resolution*/ 10); @@ -7752,7 +7744,7 @@ TEST_F(MultiTouchInputMapperTest, AxisResolution_IsPopulated) { TEST_F(MultiTouchInputMapperTest, TouchMajorAndMinorAxes_DoNotAppearIfNotSupported) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, /*flat*/ 0, /*fuzz*/ 0, /*resolution*/ 10); @@ -7773,7 +7765,7 @@ TEST_F(MultiTouchInputMapperTest, TouchMajorAndMinorAxes_DoNotAppearIfNotSupport TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingIds) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID); prepareVirtualKeys(); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -7944,7 +7936,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT); prepareVirtualKeys(); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -8110,7 +8102,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { TEST_F(MultiTouchInputMapperTest, Process_AllAxes_WithDefaultCalibration) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | TOUCH | TOOL | PRESSURE | ORIENTATION | ID | MINOR | DISTANCE); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -8159,7 +8151,7 @@ TEST_F(MultiTouchInputMapperTest, Process_AllAxes_WithDefaultCalibration) { TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_GeometricCalibration) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | TOUCH | TOOL | MINOR); addConfigurationProperty("touch.size.calibration", "geometric"); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -8196,7 +8188,7 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_GeometricCalibration) TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_SummedLinearCalibration) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | TOUCH | TOOL); addConfigurationProperty("touch.size.calibration", "diameter"); addConfigurationProperty("touch.size.scale", "10"); @@ -8247,7 +8239,7 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_SummedLinearCalibrati TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_AreaCalibration) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | TOUCH | TOOL); addConfigurationProperty("touch.size.calibration", "area"); addConfigurationProperty("touch.size.scale", "43"); @@ -8280,7 +8272,7 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_AreaCalibration) { TEST_F(MultiTouchInputMapperTest, Process_PressureAxis_AmplitudeCalibration) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | PRESSURE); addConfigurationProperty("touch.pressure.calibration", "amplitude"); addConfigurationProperty("touch.pressure.scale", "0.01"); @@ -8314,7 +8306,7 @@ TEST_F(MultiTouchInputMapperTest, Process_PressureAxis_AmplitudeCalibration) { TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllButtons) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -8557,7 +8549,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllButtons) { TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleMappedStylusButtons) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -8614,7 +8606,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleMappedStylusButtons) { TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -8764,7 +8756,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { TEST_F(MultiTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIsZero) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT); mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -8835,7 +8827,7 @@ TEST_F(MultiTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIs TEST_F(MultiTouchInputMapperTest, Process_WhenAbsMTPressureIsPresent_HoversIfItsValueIsZero) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -8935,7 +8927,7 @@ TEST_F(MultiTouchInputMapperTest, Configure_AssignsDisplayPort) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); // Add viewport for display 1 on hdmi1 - prepareDisplay(DISPLAY_ORIENTATION_0, hdmi1); + prepareDisplay(ui::ROTATION_0, hdmi1); // Send a touch event again processPosition(mapper, 100, 100); processSync(mapper); @@ -8952,8 +8944,8 @@ TEST_F(MultiTouchInputMapperTest, Configure_AssignsDisplayUniqueId) { mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, VIRTUAL_DISPLAY_UNIQUE_ID); - prepareDisplay(DISPLAY_ORIENTATION_0); - prepareVirtualDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); + prepareVirtualDisplay(ui::ROTATION_0); // Send a touch event processPosition(mapper, 100, 100); @@ -8976,7 +8968,7 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) { mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); prepareSecondaryDisplay(ViewportType::EXTERNAL); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -9000,7 +8992,7 @@ TEST_F(MultiTouchInputMapperTest, Process_SendsReadTime) { prepareAxes(POSITION); MultiTouchInputMapper& mapper = addMapperAndConfigure(); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); process(mapper, 10, 11 /*readTime*/, EV_ABS, ABS_MT_TRACKING_ID, 1); process(mapper, 15, 16 /*readTime*/, EV_ABS, ABS_MT_POSITION_X, 100); process(mapper, 20, 21 /*readTime*/, EV_ABS, ABS_MT_POSITION_Y, 100); @@ -9025,9 +9017,8 @@ TEST_F(MultiTouchInputMapperTest, Process_SendsReadTime) { TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreDropped) { addConfigurationProperty("touch.deviceType", "touchScreen"); // Don't set touch.enableForInactiveViewport to verify the default behavior. - mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, false /*isActive*/, UNIQUE_ID, NO_PORT, - ViewportType::INTERNAL); + mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + false /*isActive*/, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); prepareAxes(POSITION); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -9046,9 +9037,8 @@ TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreDropped) { TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreProcessed) { addConfigurationProperty("touch.deviceType", "touchScreen"); addConfigurationProperty("touch.enableForInactiveViewport", "1"); - mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, false /*isActive*/, UNIQUE_ID, NO_PORT, - ViewportType::INTERNAL); + mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + false /*isActive*/, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); prepareAxes(POSITION); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -9064,9 +9054,8 @@ TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreProcessed) { TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_AbortTouches) { addConfigurationProperty("touch.deviceType", "touchScreen"); addConfigurationProperty("touch.enableForInactiveViewport", "0"); - mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, UNIQUE_ID, NO_PORT, - ViewportType::INTERNAL); + mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + true /*isActive*/, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); std::optional optionalDisplayViewport = mFakePolicy->getDisplayViewportByUniqueId(UNIQUE_ID); ASSERT_TRUE(optionalDisplayViewport.has_value()); @@ -9160,7 +9149,7 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { mFakePolicy->setShowTouches(true); // Create displays. - prepareDisplay(DISPLAY_ORIENTATION_0, hdmi1); + prepareDisplay(ui::ROTATION_0, hdmi1); prepareSecondaryDisplay(ViewportType::EXTERNAL, hdmi2); // Default device will reconfigure above, need additional reconfiguration for another device. @@ -9205,7 +9194,7 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { TEST_F(MultiTouchInputMapperTest, VideoFrames_ReceivedByListener) { prepareAxes(POSITION); addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); MultiTouchInputMapper& mapper = addMapperAndConfigure(); NotifyMotionArgs motionArgs; @@ -9236,8 +9225,7 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_AreNotRotated) { NotifyMotionArgs motionArgs; // Test all 4 orientations - for (int32_t orientation : {DISPLAY_ORIENTATION_0, DISPLAY_ORIENTATION_90, - DISPLAY_ORIENTATION_180, DISPLAY_ORIENTATION_270}) { + for (ui::Rotation orientation : ftl::enum_range()) { SCOPED_TRACE("Orientation " + StringPrintf("%i", orientation)); clearViewports(); prepareDisplay(orientation); @@ -9262,8 +9250,7 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_WhenNotOrientationAware_AreRotated NotifyMotionArgs motionArgs; // Test all 4 orientations - for (int32_t orientation : {DISPLAY_ORIENTATION_0, DISPLAY_ORIENTATION_90, - DISPLAY_ORIENTATION_180, DISPLAY_ORIENTATION_270}) { + for (ui::Rotation orientation : ftl::enum_range()) { SCOPED_TRACE("Orientation " + StringPrintf("%i", orientation)); clearViewports(); prepareDisplay(orientation); @@ -9297,7 +9284,7 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_MultipleFramesAreNotRotated) { std::vector frames{frame1, frame2, frame3}; NotifyMotionArgs motionArgs; - prepareDisplay(DISPLAY_ORIENTATION_90); + prepareDisplay(ui::ROTATION_90); mFakeEventHub->setVideoFrames({{EVENTHUB_ID, frames}}); processPosition(mapper, 100, 200); processSync(mapper); @@ -9320,7 +9307,7 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_WhenNotOrientationAware_MultipleFr std::vector frames{frame1, frame2, frame3}; NotifyMotionArgs motionArgs; - prepareDisplay(DISPLAY_ORIENTATION_90); + prepareDisplay(ui::ROTATION_90); mFakeEventHub->setVideoFrames({{EVENTHUB_ID, frames}}); processPosition(mapper, 100, 200); processSync(mapper); @@ -9330,7 +9317,7 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_WhenNotOrientationAware_MultipleFr // compared to the display. This is so that when the window transform (which contains the // display rotation) is applied later by InputDispatcher, the coordinates end up in the // window's coordinate space. - frame.rotate(getInverseRotation(DISPLAY_ORIENTATION_90)); + frame.rotate(getInverseRotation(ui::ROTATION_90)); }); ASSERT_EQ(frames, motionArgs.videoFrames); } @@ -9367,7 +9354,7 @@ TEST_F(MultiTouchInputMapperTest, Configure_EnabledForAssociatedDisplay) { TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleSingleTouch) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -9412,7 +9399,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleSingleTouch) { */ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_SinglePointer) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -9460,7 +9447,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_SinglePointer */ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_TwoPointers) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -9535,7 +9522,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_TwoPointers) */ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_ShouldCancelWhenAllTouchIsPalm) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -9633,7 +9620,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_ShouldCancelW */ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_KeepFirstPointer) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -9705,7 +9692,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_KeepFirstPoin */ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -9762,7 +9749,7 @@ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) { TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -9803,7 +9790,7 @@ TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) { TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState_NoPointersDown) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -9831,7 +9818,7 @@ TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState_NoPointersDown) TEST_F(MultiTouchInputMapperTest, StylusSourceIsAddedDynamicallyFromToolType) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE | TOOL_TYPE); MultiTouchInputMapper& mapper = addMapperAndConfigure(); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); @@ -9890,7 +9877,7 @@ protected: TEST_F(MultiTouchInputMapperTest_ExternalDevice, Viewports_Fallback) { prepareAxes(POSITION); addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); MultiTouchInputMapper& mapper = addMapperAndConfigure(); ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources()); @@ -9921,7 +9908,7 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) { fakePointerController->setButtonState(0); // prepare device and capture - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT); mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0); mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0); @@ -10071,7 +10058,7 @@ TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) { fakePointerController->setButtonState(0); // prepare device and capture - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT); mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0); mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0); @@ -10112,7 +10099,7 @@ TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) { std::shared_ptr fakePointerController = std::make_shared(); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT); mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0); mFakePolicy->setPointerController(fakePointerController); @@ -10139,7 +10126,7 @@ protected: TEST_F(BluetoothMultiTouchInputMapperTest, TimestampSmoothening) { addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -10189,7 +10176,7 @@ protected: fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); fakePointerController->setPosition(0, 0); fakePointerController->setButtonState(0); - prepareDisplay(DISPLAY_ORIENTATION_0); + prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION); prepareAbsoluteAxisResolution(xAxisResolution, yAxisResolution); @@ -10549,7 +10536,7 @@ protected: process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); } - void prepareVirtualDisplay(int32_t orientation) { + void prepareVirtualDisplay(ui::Rotation orientation) { setDisplayInfoAndReconfigure(VIRTUAL_DISPLAY_ID, VIRTUAL_DISPLAY_WIDTH, VIRTUAL_DISPLAY_HEIGHT, orientation, VIRTUAL_DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::VIRTUAL); @@ -10567,7 +10554,7 @@ TEST_F(JoystickInputMapperTest, Configure_AssignsDisplayUniqueId) { mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, VIRTUAL_DISPLAY_UNIQUE_ID); - prepareVirtualDisplay(DISPLAY_ORIENTATION_0); + prepareVirtualDisplay(ui::ROTATION_0); // Send an axis event processAxis(mapper, ABS_X, 100); diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index 64316baa3a..445ed182fe 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -311,7 +311,7 @@ public: return mFdp->ConsumeRandomLengthString(32); } TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor, - int32_t surfaceRotation) override { + ui::Rotation surfaceRotation) override { return mTransform; } void setTouchAffineTransformation(const TouchAffineTransformation t) { mTransform = t; } -- GitLab From 635422be3e0e02b7221633b2eb1b112fc18bb65e Mon Sep 17 00:00:00 2001 From: Michael Wright Date: Fri, 2 Dec 2022 00:43:56 +0000 Subject: [PATCH 0564/1310] Convert MotionEvent#getSurfaceRotation to ui::Rotation Because it might have an invalid rotation it needs to return std::optional, but at least we're using the types effectively here. Test: compiles Change-Id: I27076edcc6ce33594552863caa8ee643027a81e7 --- include/input/Input.h | 2 +- libs/input/Input.cpp | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/include/input/Input.h b/include/input/Input.h index 07a566a63c..015efdd064 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -577,7 +577,7 @@ public: inline const ui::Transform& getTransform() const { return mTransform; } - int32_t getSurfaceRotation() const; + std::optional getSurfaceRotation() const; inline float getXPrecision() const { return mXPrecision; } diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index 162b757743..9e8ebf30c5 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -552,21 +553,21 @@ void MotionEvent::addSample( &pointerCoords[getPointerCount()]); } -int32_t MotionEvent::getSurfaceRotation() const { +std::optional MotionEvent::getSurfaceRotation() const { // The surface rotation is the rotation from the window's coordinate space to that of the // display. Since the event's transform takes display space coordinates to window space, the // returned surface rotation is the inverse of the rotation for the surface. switch (mTransform.getOrientation()) { case ui::Transform::ROT_0: - return static_cast(ui::ROTATION_0); + return ui::ROTATION_0; case ui::Transform::ROT_90: - return static_cast(ui::ROTATION_270); + return ui::ROTATION_270; case ui::Transform::ROT_180: - return static_cast(ui::ROTATION_180); + return ui::ROTATION_180; case ui::Transform::ROT_270: - return static_cast(ui::ROTATION_90); + return ui::ROTATION_90; default: - return -1; + return std::nullopt; } } -- GitLab From b19461b1d185192958d9f7a5d155e2720e132526 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Thu, 1 Dec 2022 12:33:43 -0500 Subject: [PATCH 0565/1310] Prevent calling new ftl::Optional This class inherits from std::optional, which does not have a virtual destructor. As such, deleting an object of ftl::Optional using a pointer to its base class has undefined behavior. Prevent this by removing new from ftl::Optional. This is generally not the right way to use it anyway, and in fact this builds as is. Delete new[] while we're at it. This should be enough of a signal not to try to heap allocate these. It's still possible for a programmer to circumvent these deletions, but they prevent the straightforward (broken) use case. Bug: 261035092 Test: make Change-Id: Iafdaf98ed88920162af2b445caf0e4a69be51ab5 --- include/ftl/optional.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/ftl/optional.h b/include/ftl/optional.h index 7b02bac340..a818128c92 100644 --- a/include/ftl/optional.h +++ b/include/ftl/optional.h @@ -95,6 +95,14 @@ struct Optional final : std::optional { if (has_value()) return std::invoke(std::forward(f), std::move(value())); return R(); } + + // Delete new for this class. Its base doesn't have a virtual destructor, and + // if it got deleted via base class pointer, it would cause undefined + // behavior. There's not a good reason to allocate this object on the heap + // anyway. + static void* operator new(size_t) = delete; + static void* operator new[](size_t) = delete; + }; template -- GitLab From 1f48a4458506da6384ee15e2bdef2e2941b3fcc7 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Tue, 15 Nov 2022 17:38:36 +0000 Subject: [PATCH 0566/1310] Set up a GestureInterpreter and feed it events This is a minimal setup of the gestures library, in that it doesn't have a timer provider or property provider, but it's enough to be able to feed it events and have it report the gestures it has detected. This CL depends on https://r.android.com/2310648 to pull in the Android.bp file and `SetCallback` rename from upstream, and https://r.android.com/2314880 to pull in the deprecation of screen DPI hardware properties. Bug: 251196347 Test: connect Apple Magic Trackpad 2, make gestures, check logcat Change-Id: Ib40880360c3865f0a98b7a067b7efe9fd8178212 --- services/inputflinger/reader/Android.bp | 2 + services/inputflinger/reader/InputDevice.cpp | 3 +- .../reader/mapper/TouchpadInputMapper.cpp | 150 +++++++++++++++++- .../reader/mapper/TouchpadInputMapper.h | 23 ++- .../accumulator/CursorButtonAccumulator.h | 8 + .../accumulator/TouchButtonAccumulator.cpp | 20 ++- .../accumulator/TouchButtonAccumulator.h | 2 + 7 files changed, 200 insertions(+), 8 deletions(-) diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index 551ad7a398..f5ac8de53d 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -132,6 +132,7 @@ cc_library_shared { // This should consist only of dependencies from inputflinger. Other dependencies should be // in cc_defaults so that they are included in the tests. "libinputflinger_base", + "libjsoncpp", ], export_header_lib_headers: [ "libinputreader_headers", @@ -146,5 +147,6 @@ cc_library_shared { }, static_libs: [ "libc++fs", + "libchrome-gestures", ], } diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 6ca629835d..150a8aac0d 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -211,7 +211,8 @@ void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) { // Touchscreens and touchpad devices. // TODO(b/251196347): replace this with a proper flag. constexpr bool ENABLE_NEW_TOUCHPAD_STACK = false; - if (ENABLE_NEW_TOUCHPAD_STACK && classes.test(InputDeviceClass::TOUCHPAD)) { + if (ENABLE_NEW_TOUCHPAD_STACK && classes.test(InputDeviceClass::TOUCHPAD) && + classes.test(InputDeviceClass::TOUCH_MT)) { mappers.push_back(std::make_unique(*contextPtr)); } else if (classes.test(InputDeviceClass::TOUCH_MT)) { mappers.push_back(std::make_unique(*contextPtr)); diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 3ad1de4cd4..8c5bce7d13 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -16,21 +16,165 @@ #include "../Macros.h" +#include +#include #include "TouchpadInputMapper.h" namespace android { +namespace { + +short getMaxTouchCount(const InputDeviceContext& context) { + if (context.hasKeyCode(BTN_TOOL_QUINTTAP)) return 5; + if (context.hasKeyCode(BTN_TOOL_QUADTAP)) return 4; + if (context.hasKeyCode(BTN_TOOL_TRIPLETAP)) return 3; + if (context.hasKeyCode(BTN_TOOL_DOUBLETAP)) return 2; + if (context.hasKeyCode(BTN_TOOL_FINGER)) return 1; + return 0; +} + +HardwareProperties createHardwareProperties(const InputDeviceContext& context) { + HardwareProperties props; + RawAbsoluteAxisInfo absMtPositionX; + context.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &absMtPositionX); + props.left = absMtPositionX.minValue; + props.right = absMtPositionX.maxValue; + props.res_x = absMtPositionX.resolution; + + RawAbsoluteAxisInfo absMtPositionY; + context.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &absMtPositionY); + props.top = absMtPositionY.minValue; + props.bottom = absMtPositionY.maxValue; + props.res_y = absMtPositionY.resolution; + + RawAbsoluteAxisInfo absMtOrientation; + context.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &absMtOrientation); + props.orientation_minimum = absMtOrientation.minValue; + props.orientation_maximum = absMtOrientation.maxValue; + + RawAbsoluteAxisInfo absMtSlot; + context.getAbsoluteAxisInfo(ABS_MT_SLOT, &absMtSlot); + props.max_finger_cnt = absMtSlot.maxValue - absMtSlot.minValue + 1; + props.max_touch_cnt = getMaxTouchCount(context); + + // T5R2 ("Track 5, Report 2") is a feature of some old Synaptics touchpads that could track 5 + // fingers but only report the coordinates of 2 of them. We don't know of any external touchpads + // that did this, so assume false. + props.supports_t5r2 = false; + + props.support_semi_mt = context.hasInputProperty(INPUT_PROP_SEMI_MT); + props.is_button_pad = context.hasInputProperty(INPUT_PROP_BUTTONPAD); + + // Mouse-only properties, which will always be false. + props.has_wheel = false; + props.wheel_is_hi_res = false; + + // Linux Kernel haptic touchpad support isn't merged yet, so for now assume that no touchpads + // are haptic. + props.is_haptic_pad = false; + return props; +} + +void gestureInterpreterCallback(void* clientData, const struct Gesture* gesture) { + // TODO(b/251196347): turn the gesture into a NotifyArgs and dispatch it. + ALOGD("Gesture ready: %s", gesture->String().c_str()); +} + +} // namespace + TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext) {} + : InputMapper(deviceContext), + mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter), + mTouchButtonAccumulator(deviceContext) { + mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD); + mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext)); + mGestureInterpreter->SetCallback(gestureInterpreterCallback, nullptr); + // TODO(b/251196347): set a property provider, so we can change gesture properties. + // TODO(b/251196347): set a timer provider, so the library can use timers. + + RawAbsoluteAxisInfo slotAxisInfo; + getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo); + if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) { + ALOGW("Touchpad \"%s\" doesn't have a valid ABS_MT_SLOT axis, and probably won't work " + "properly.", + getDeviceName().c_str()); + } + mMotionAccumulator.configure(getDeviceContext(), slotAxisInfo.maxValue + 1, true); + mTouchButtonAccumulator.configure(); +} uint32_t TouchpadInputMapper::getSources() const { return AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD; } +std::list TouchpadInputMapper::reset(nsecs_t when) { + mCursorButtonAccumulator.reset(getDeviceContext()); + mTouchButtonAccumulator.reset(); + mMscTimestamp = 0; + return InputMapper::reset(when); +} + std::list TouchpadInputMapper::process(const RawEvent* rawEvent) { - ALOGD("TODO: process event type=0x%x code=0x%x value=0x%x", rawEvent->type, rawEvent->code, - rawEvent->value); + if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { + sync(rawEvent->when); + } + if (rawEvent->type == EV_MSC && rawEvent->code == MSC_TIMESTAMP) { + mMscTimestamp = rawEvent->value; + } + mCursorButtonAccumulator.process(rawEvent); + mMotionAccumulator.process(rawEvent); + mTouchButtonAccumulator.process(rawEvent); return {}; } +void TouchpadInputMapper::sync(nsecs_t when) { + HardwareState hwState; + // The gestures library uses doubles to represent timestamps in seconds. + hwState.timestamp = std::chrono::duration(std::chrono::nanoseconds(when)).count(); + hwState.msc_timestamp = + std::chrono::duration(std::chrono::microseconds(mMscTimestamp)).count(); + + hwState.buttons_down = 0; + if (mCursorButtonAccumulator.isLeftPressed()) { + hwState.buttons_down |= GESTURES_BUTTON_LEFT; + } + if (mCursorButtonAccumulator.isMiddlePressed()) { + hwState.buttons_down |= GESTURES_BUTTON_MIDDLE; + } + if (mCursorButtonAccumulator.isRightPressed()) { + hwState.buttons_down |= GESTURES_BUTTON_RIGHT; + } + if (mCursorButtonAccumulator.isBackPressed() || mCursorButtonAccumulator.isSidePressed()) { + hwState.buttons_down |= GESTURES_BUTTON_BACK; + } + if (mCursorButtonAccumulator.isForwardPressed() || mCursorButtonAccumulator.isExtraPressed()) { + hwState.buttons_down |= GESTURES_BUTTON_FORWARD; + } + + std::vector fingers; + for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) { + MultiTouchMotionAccumulator::Slot slot = mMotionAccumulator.getSlot(i); + if (slot.isInUse()) { + FingerState& fingerState = fingers.emplace_back(); + fingerState = {}; + fingerState.touch_major = slot.getTouchMajor(); + fingerState.touch_minor = slot.getTouchMinor(); + fingerState.width_major = slot.getToolMajor(); + fingerState.width_minor = slot.getToolMinor(); + fingerState.pressure = slot.getPressure(); + fingerState.orientation = slot.getOrientation(); + fingerState.position_x = slot.getX(); + fingerState.position_y = slot.getY(); + fingerState.tracking_id = slot.getTrackingId(); + } + } + hwState.fingers = fingers.data(); + hwState.finger_cnt = fingers.size(); + hwState.touch_cnt = mTouchButtonAccumulator.getTouchCount(); + + mGestureInterpreter->PushHardwareState(&hwState); + mMotionAccumulator.finishSync(); + mMscTimestamp = 0; +} + } // namespace android diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index 81a76d27be..9d3a4b3e0d 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -16,10 +16,17 @@ #pragma once +#include + #include "EventHub.h" #include "InputDevice.h" #include "InputMapper.h" #include "NotifyArgs.h" +#include "accumulator/CursorButtonAccumulator.h" +#include "accumulator/MultiTouchMotionAccumulator.h" +#include "accumulator/TouchButtonAccumulator.h" + +#include "include/gestures.h" namespace android { @@ -27,8 +34,20 @@ class TouchpadInputMapper : public InputMapper { public: explicit TouchpadInputMapper(InputDeviceContext& deviceContext); - virtual uint32_t getSources() const override; - [[nodiscard]] virtual std::list process(const RawEvent* rawEvent) override; + uint32_t getSources() const override; + [[nodiscard]] std::list reset(nsecs_t when) override; + [[nodiscard]] std::list process(const RawEvent* rawEvent) override; + +private: + void sync(nsecs_t when); + + std::unique_ptr + mGestureInterpreter; + + CursorButtonAccumulator mCursorButtonAccumulator; + MultiTouchMotionAccumulator mMotionAccumulator; + TouchButtonAccumulator mTouchButtonAccumulator; + int32_t mMscTimestamp = 0; }; } // namespace android diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h index ed4c789cfd..138060483d 100644 --- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h @@ -32,6 +32,14 @@ public: void process(const RawEvent* rawEvent); uint32_t getButtonState() const; + inline bool isLeftPressed() const { return mBtnLeft; } + inline bool isRightPressed() const { return mBtnRight; } + inline bool isMiddlePressed() const { return mBtnMiddle; } + inline bool isBackPressed() const { return mBtnBack; } + inline bool isSidePressed() const { return mBtnSide; } + inline bool isForwardPressed() const { return mBtnForward; } + inline bool isExtraPressed() const { return mBtnExtra; } + inline bool isTaskPressed() const { return mBtnTask; } private: bool mBtnLeft; diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp index bc23a8ec91..66017024eb 100644 --- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp @@ -48,6 +48,7 @@ void TouchButtonAccumulator::reset() { mBtnToolDoubleTap = mDeviceContext.isKeyPressed(BTN_TOOL_DOUBLETAP); mBtnToolTripleTap = mDeviceContext.isKeyPressed(BTN_TOOL_TRIPLETAP); mBtnToolQuadTap = mDeviceContext.isKeyPressed(BTN_TOOL_QUADTAP); + mBtnToolQuintTap = mDeviceContext.isKeyPressed(BTN_TOOL_QUINTTAP); mHidUsageAccumulator.reset(); } @@ -100,6 +101,9 @@ void TouchButtonAccumulator::process(const RawEvent* rawEvent) { case BTN_TOOL_QUADTAP: mBtnToolQuadTap = rawEvent->value; break; + case BTN_TOOL_QUINTTAP: + mBtnToolQuintTap = rawEvent->value; + break; default: processMappedKey(rawEvent->code, rawEvent->value); } @@ -147,7 +151,8 @@ int32_t TouchButtonAccumulator::getToolType() const { if (mBtnToolPen || mBtnToolBrush || mBtnToolPencil || mBtnToolAirbrush) { return AMOTION_EVENT_TOOL_TYPE_STYLUS; } - if (mBtnToolFinger || mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap) { + if (mBtnToolFinger || mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap || + mBtnToolQuintTap) { return AMOTION_EVENT_TOOL_TYPE_FINGER; } return AMOTION_EVENT_TOOL_TYPE_UNKNOWN; @@ -156,7 +161,7 @@ int32_t TouchButtonAccumulator::getToolType() const { bool TouchButtonAccumulator::isToolActive() const { return mBtnTouch || mBtnToolFinger || mBtnToolPen || mBtnToolRubber || mBtnToolBrush || mBtnToolPencil || mBtnToolAirbrush || mBtnToolMouse || mBtnToolLens || - mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap; + mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap || mBtnToolQuintTap; } bool TouchButtonAccumulator::isHovering() const { @@ -171,4 +176,15 @@ bool TouchButtonAccumulator::hasButtonTouch() const { return mHaveBtnTouch; } +int TouchButtonAccumulator::getTouchCount() const { + if (mBtnTouch) { + if (mBtnToolQuintTap) return 5; + if (mBtnToolQuadTap) return 4; + if (mBtnToolTripleTap) return 3; + if (mBtnToolDoubleTap) return 2; + if (mBtnToolFinger) return 1; + } + return 0; +} + } // namespace android diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h index c2de23cee4..2e70e2e50d 100644 --- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h @@ -41,6 +41,7 @@ public: bool isHovering() const; bool hasStylus() const; bool hasButtonTouch() const; + int getTouchCount() const; private: bool mHaveBtnTouch{}; @@ -60,6 +61,7 @@ private: bool mBtnToolDoubleTap{}; bool mBtnToolTripleTap{}; bool mBtnToolQuadTap{}; + bool mBtnToolQuintTap{}; HidUsageAccumulator mHidUsageAccumulator{}; -- GitLab From 0aa373acd246500983d9305c53ad975e380dd8f6 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Tue, 22 Nov 2022 13:56:50 -0800 Subject: [PATCH 0567/1310] SF: add render rate to the refresh rate overlay Test: Enable refresh rate overlay Bug: 257072060 Change-Id: I7c81bdff7ed10beb7487b930448f8c5dc7770073 --- services/surfaceflinger/DisplayDevice.cpp | 23 ++-- services/surfaceflinger/DisplayDevice.h | 3 +- .../surfaceflinger/RefreshRateOverlay.cpp | 103 ++++++++++-------- services/surfaceflinger/RefreshRateOverlay.h | 29 +++-- .../Scheduler/RefreshRateSelector.cpp | 18 +-- .../Scheduler/RefreshRateSelector.h | 5 +- services/surfaceflinger/SurfaceFlinger.cpp | 7 +- services/surfaceflinger/SurfaceFlinger.h | 2 + 8 files changed, 120 insertions(+), 70 deletions(-) diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 269a5ea561..46b857b189 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -203,7 +203,7 @@ void DisplayDevice::setActiveMode(DisplayModeId modeId, Fps displayFps, Fps rend mRefreshRateSelector->setActiveMode(modeId, renderFps); if (mRefreshRateOverlay) { - mRefreshRateOverlay->changeRefreshRate(displayFps); + mRefreshRateOverlay->changeRefreshRate(displayFps, renderFps); } } @@ -410,26 +410,35 @@ HdrCapabilities DisplayDevice::getHdrCapabilities() const { capabilities.getDesiredMinLuminance()); } -void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinnner) { +void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinner, bool showRenderRate) { if (!enable) { mRefreshRateOverlay.reset(); return; } + ftl::Flags features; + if (showSpinner) { + features |= RefreshRateOverlay::Features::Spinner; + } + + if (showRenderRate) { + features |= RefreshRateOverlay::Features::RenderRate; + } + const auto fpsRange = mRefreshRateSelector->getSupportedRefreshRateRange(); - mRefreshRateOverlay = std::make_unique(fpsRange, showSpinnner); + mRefreshRateOverlay = std::make_unique(fpsRange, features); mRefreshRateOverlay->setLayerStack(getLayerStack()); mRefreshRateOverlay->setViewport(getSize()); - mRefreshRateOverlay->changeRefreshRate(getActiveMode().modePtr->getFps()); + mRefreshRateOverlay->changeRefreshRate(getActiveMode().modePtr->getFps(), getActiveMode().fps); } bool DisplayDevice::onKernelTimerChanged(std::optional desiredModeId, bool timerExpired) { if (mRefreshRateSelector && mRefreshRateOverlay) { - const auto newRefreshRate = + const auto newMode = mRefreshRateSelector->onKernelTimerChanged(desiredModeId, timerExpired); - if (newRefreshRate) { - mRefreshRateOverlay->changeRefreshRate(*newRefreshRate); + if (newMode) { + mRefreshRateOverlay->changeRefreshRate(newMode->modePtr->getFps(), newMode->fps); return true; } } diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index 852a8a297d..8f9b2a19a6 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -236,7 +236,8 @@ public: } // Enables an overlay to be displayed with the current refresh rate - void enableRefreshRateOverlay(bool enable, bool showSpinner) REQUIRES(kMainThreadContext); + void enableRefreshRateOverlay(bool enable, bool showSpinner, bool showRenderRate) + REQUIRES(kMainThreadContext); bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; } bool onKernelTimerChanged(std::optional, bool timerExpired); void animateRefreshRateOverlay(); diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp index 9b19afbdac..7aa7e17b3b 100644 --- a/services/surfaceflinger/RefreshRateOverlay.cpp +++ b/services/surfaceflinger/RefreshRateOverlay.cpp @@ -42,8 +42,8 @@ constexpr int kDigitWidth = 64; constexpr int kDigitHeight = 100; constexpr int kDigitSpace = 16; -// Layout is digit, space, digit, space, digit, space, spinner. -constexpr int kBufferWidth = 4 * kDigitWidth + 3 * kDigitSpace; +constexpr int kMaxDigits = /*displayFps*/ 3 + /*renderFps*/ 3 + /*spinner*/ 1; +constexpr int kBufferWidth = kMaxDigits * kDigitWidth + (kMaxDigits - 1) * kDigitSpace; constexpr int kBufferHeight = kDigitHeight; SurfaceComposerClient::Transaction createTransaction(const sp& surface) { @@ -121,16 +121,10 @@ void RefreshRateOverlay::SevenSegmentDrawer::drawDigit(int digit, int left, SkCo drawSegment(Segment::Bottom, left, color, canvas); } -auto RefreshRateOverlay::SevenSegmentDrawer::draw(int number, SkColor color, +auto RefreshRateOverlay::SevenSegmentDrawer::draw(int displayFps, int renderFps, SkColor color, ui::Transform::RotationFlags rotation, - bool showSpinner) -> Buffers { - if (number < 0 || number > 1000) return {}; - - const auto hundreds = number / 100; - const auto tens = (number / 10) % 10; - const auto ones = number % 10; - - const size_t loopCount = showSpinner ? 6 : 1; + ftl::Flags features) -> Buffers { + const size_t loopCount = features.test(Features::Spinner) ? 6 : 1; Buffers buffers; buffers.reserve(loopCount); @@ -169,20 +163,9 @@ auto RefreshRateOverlay::SevenSegmentDrawer::draw(int number, SkColor color, canvas->setMatrix(canvasTransform); int left = 0; - if (hundreds != 0) { - drawDigit(hundreds, left, color, *canvas); - } - left += kDigitWidth + kDigitSpace; - - if (tens != 0) { - drawDigit(tens, left, color, *canvas); - } - left += kDigitWidth + kDigitSpace; - - drawDigit(ones, left, color, *canvas); - left += kDigitWidth + kDigitSpace; - - if (showSpinner) { + drawNumber(displayFps, left, color, *canvas); + left += 3 * (kDigitWidth + kDigitSpace); + if (features.test(Features::Spinner)) { switch (i) { case 0: drawSegment(Segment::Upper, left, color, *canvas); @@ -205,6 +188,13 @@ auto RefreshRateOverlay::SevenSegmentDrawer::draw(int number, SkColor color, } } + left += kDigitWidth + kDigitSpace; + + if (features.test(Features::RenderRate)) { + drawNumber(renderFps, left, color, *canvas); + } + left += 3 * (kDigitWidth + kDigitSpace); + void* pixels = nullptr; buffer->lock(GRALLOC_USAGE_SW_WRITE_RARELY, reinterpret_cast(&pixels)); @@ -219,6 +209,23 @@ auto RefreshRateOverlay::SevenSegmentDrawer::draw(int number, SkColor color, return buffers; } +void RefreshRateOverlay::SevenSegmentDrawer::drawNumber(int number, int left, SkColor color, + SkCanvas& canvas) { + if (number < 0 || number >= 1000) return; + + if (number >= 100) { + drawDigit(number / 100, left, color, canvas); + } + left += kDigitWidth + kDigitSpace; + + if (number >= 10) { + drawDigit((number / 10) % 10, left, color, canvas); + } + left += kDigitWidth + kDigitSpace; + + drawDigit(number % 10, left, color, canvas); +} + std::unique_ptr createSurfaceControlHolder() { sp surfaceControl = SurfaceComposerClient::getDefault() @@ -228,10 +235,8 @@ std::unique_ptr createSurfaceControlHolder() { return std::make_unique(std::move(surfaceControl)); } -RefreshRateOverlay::RefreshRateOverlay(FpsRange fpsRange, bool showSpinner) - : mFpsRange(fpsRange), - mShowSpinner(showSpinner), - mSurfaceControl(createSurfaceControlHolder()) { +RefreshRateOverlay::RefreshRateOverlay(FpsRange fpsRange, ftl::Flags features) + : mFpsRange(fpsRange), mFeatures(features), mSurfaceControl(createSurfaceControlHolder()) { if (!mSurfaceControl) { ALOGE("%s: Failed to create buffer state layer", __func__); return; @@ -243,10 +248,15 @@ RefreshRateOverlay::RefreshRateOverlay(FpsRange fpsRange, bool showSpinner) .apply(); } -auto RefreshRateOverlay::getOrCreateBuffers(Fps fps) -> const Buffers& { +auto RefreshRateOverlay::getOrCreateBuffers(Fps displayFps, Fps renderFps) -> const Buffers& { static const Buffers kNoBuffers; if (!mSurfaceControl) return kNoBuffers; + // avoid caching different render rates if RenderRate is anyway not visible + if (!mFeatures.test(Features::RenderRate)) { + renderFps = 0_Hz; + } + const auto transformHint = static_cast(mSurfaceControl->get()->getTransformHint()); @@ -266,17 +276,20 @@ auto RefreshRateOverlay::getOrCreateBuffers(Fps fps) -> const Buffers& { .setTransform(mSurfaceControl->get(), transform) .apply(); - BufferCache::const_iterator it = mBufferCache.find({fps.getIntValue(), transformHint}); + BufferCache::const_iterator it = + mBufferCache.find({displayFps.getIntValue(), renderFps.getIntValue(), transformHint}); if (it == mBufferCache.end()) { const int minFps = mFpsRange.min.getIntValue(); const int maxFps = mFpsRange.max.getIntValue(); - // Clamp to the range. The current fps may be outside of this range if the display has - // changed its set of supported refresh rates. - const int intFps = std::clamp(fps.getIntValue(), minFps, maxFps); + // Clamp to the range. The current displayFps may be outside of this range if the display + // has changed its set of supported refresh rates. + const int displayIntFps = std::clamp(displayFps.getIntValue(), minFps, maxFps); + const int renderIntFps = renderFps.getIntValue(); // Ensure non-zero range to avoid division by zero. - const float fpsScale = static_cast(intFps - minFps) / std::max(1, maxFps - minFps); + const float fpsScale = + static_cast(displayIntFps - minFps) / std::max(1, maxFps - minFps); constexpr SkColor kMinFpsColor = SK_ColorRED; constexpr SkColor kMaxFpsColor = SK_ColorGREEN; @@ -292,8 +305,11 @@ auto RefreshRateOverlay::getOrCreateBuffers(Fps fps) -> const Buffers& { const SkColor color = colorBase.toSkColor(); - auto buffers = SevenSegmentDrawer::draw(intFps, color, transformHint, mShowSpinner); - it = mBufferCache.try_emplace({intFps, transformHint}, std::move(buffers)).first; + auto buffers = SevenSegmentDrawer::draw(displayIntFps, renderIntFps, color, transformHint, + mFeatures); + it = mBufferCache + .try_emplace({displayIntFps, renderIntFps, transformHint}, std::move(buffers)) + .first; } return it->second; @@ -303,7 +319,7 @@ void RefreshRateOverlay::setViewport(ui::Size viewport) { constexpr int32_t kMaxWidth = 1000; const auto width = std::min({kMaxWidth, viewport.width, viewport.height}); const auto height = 2 * width; - Rect frame((3 * width) >> 4, height >> 5); + Rect frame((5 * width) >> 4, height >> 5); frame.offsetBy(width >> 5, height >> 4); createTransaction(mSurfaceControl->get()) @@ -317,16 +333,17 @@ void RefreshRateOverlay::setLayerStack(ui::LayerStack stack) { createTransaction(mSurfaceControl->get()).setLayerStack(mSurfaceControl->get(), stack).apply(); } -void RefreshRateOverlay::changeRefreshRate(Fps fps) { - mCurrentFps = fps; - const auto buffer = getOrCreateBuffers(fps)[mFrame]; +void RefreshRateOverlay::changeRefreshRate(Fps displayFps, Fps renderFps) { + mDisplayFps = displayFps; + mRenderFps = renderFps; + const auto buffer = getOrCreateBuffers(displayFps, renderFps)[mFrame]; createTransaction(mSurfaceControl->get()).setBuffer(mSurfaceControl->get(), buffer).apply(); } void RefreshRateOverlay::animate() { - if (!mShowSpinner || !mCurrentFps) return; + if (!mFeatures.test(Features::Spinner) || !mDisplayFps) return; - const auto& buffers = getOrCreateBuffers(*mCurrentFps); + const auto& buffers = getOrCreateBuffers(*mDisplayFps, *mRenderFps); mFrame = (mFrame + 1) % buffers.size(); const auto buffer = buffers[mFrame]; createTransaction(mSurfaceControl->get()).setBuffer(mSurfaceControl->get(), buffer).apply(); diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h index a2966e654e..d6f828f7f1 100644 --- a/services/surfaceflinger/RefreshRateOverlay.h +++ b/services/surfaceflinger/RefreshRateOverlay.h @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -50,11 +51,16 @@ private: class RefreshRateOverlay { public: - RefreshRateOverlay(FpsRange, bool showSpinner); + enum class Features { + Spinner = 1 << 0, + RenderRate = 1 << 1, + }; + + RefreshRateOverlay(FpsRange, ftl::Flags); void setLayerStack(ui::LayerStack); void setViewport(ui::Size); - void changeRefreshRate(Fps); + void changeRefreshRate(Fps, Fps); void animate(); private: @@ -62,32 +68,39 @@ private: class SevenSegmentDrawer { public: - static Buffers draw(int number, SkColor, ui::Transform::RotationFlags, bool showSpinner); + static Buffers draw(int displayFps, int renderFps, SkColor, ui::Transform::RotationFlags, + ftl::Flags); private: enum class Segment { Upper, UpperLeft, UpperRight, Middle, LowerLeft, LowerRight, Bottom }; static void drawSegment(Segment, int left, SkColor, SkCanvas&); static void drawDigit(int digit, int left, SkColor, SkCanvas&); + static void drawNumber(int number, int left, SkColor, SkCanvas&); }; - const Buffers& getOrCreateBuffers(Fps); + const Buffers& getOrCreateBuffers(Fps, Fps); struct Key { - int fps; + int displayFps; + int renderFps; ui::Transform::RotationFlags flags; - bool operator==(Key other) const { return fps == other.fps && flags == other.flags; } + bool operator==(Key other) const { + return displayFps == other.displayFps && renderFps == other.renderFps && + flags == other.flags; + } }; using BufferCache = ftl::SmallMap; BufferCache mBufferCache; - std::optional mCurrentFps; + std::optional mDisplayFps; + std::optional mRenderFps; size_t mFrame = 0; const FpsRange mFpsRange; // For color interpolation. - const bool mShowSpinner; + const ftl::Flags mFeatures; const std::unique_ptr mSurfaceControl; }; diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index 4be1ac7c6f..59f8899001 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -836,21 +836,25 @@ auto RefreshRateSelector::getFrameRateOverrides(const std::vector RefreshRateSelector::onKernelTimerChanged( +ftl::Optional RefreshRateSelector::onKernelTimerChanged( std::optional desiredActiveModeId, bool timerExpired) const { std::lock_guard lock(mLock); - const DisplayModePtr& current = desiredActiveModeId - ? mDisplayModes.get(*desiredActiveModeId)->get() - : getActiveModeLocked().modePtr.get(); + const auto current = [&]() REQUIRES(mLock) -> FrameRateMode { + if (desiredActiveModeId) { + const auto& modePtr = mDisplayModes.get(*desiredActiveModeId)->get(); + return FrameRateMode{modePtr->getFps(), ftl::as_non_null(modePtr)}; + } + + return getActiveModeLocked(); + }(); const DisplayModePtr& min = mMinRefreshRateModeIt->second; - if (current == min) { + if (current.modePtr->getId() == min->getId()) { return {}; } - const auto& mode = timerExpired ? min : current; - return mode->getFps(); + return timerExpired ? FrameRateMode{min->getFps(), ftl::as_non_null(min)} : current; } const DisplayModePtr& RefreshRateSelector::getMinRefreshRateByPolicyLocked() const { diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h index 1ed16c6a52..4f5842a67a 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h @@ -240,8 +240,9 @@ public: return {mMinRefreshRateModeIt->second->getFps(), mMaxRefreshRateModeIt->second->getFps()}; } - std::optional onKernelTimerChanged(std::optional desiredActiveModeId, - bool timerExpired) const EXCLUDES(mLock); + ftl::Optional onKernelTimerChanged( + std::optional desiredActiveModeId, bool timerExpired) const + EXCLUDES(mLock); void setActiveMode(DisplayModeId, Fps renderFrameRate) EXCLUDES(mLock); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 111b927507..77b2778149 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -422,7 +422,9 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI android::hardware::details::setTrebleTestingOverride(true); } - mRefreshRateOverlaySpinner = property_get_bool("sf.debug.show_refresh_rate_overlay_spinner", 0); + mRefreshRateOverlaySpinner = property_get_bool("debug.sf.show_refresh_rate_overlay_spinner", 0); + mRefreshRateOverlayRenderRate = + property_get_bool("debug.sf.show_refresh_rate_overlay_render_rate", 0); if (!mIsUserBuild && base::GetBoolProperty("debug.sf.enable_transaction_tracing"s, true)) { mTransactionTracing.emplace(); @@ -6882,7 +6884,8 @@ void SurfaceFlinger::enableRefreshRateOverlay(bool enable) { for (const auto& [id, display] : mPhysicalDisplays) { if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal) { if (const auto device = getDisplayDeviceLocked(id)) { - device->enableRefreshRateOverlay(enable, mRefreshRateOverlaySpinner); + device->enableRefreshRateOverlay(enable, mRefreshRateOverlaySpinner, + mRefreshRateOverlayRenderRate); } } } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index e3217a3228..b9903a7768 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -639,6 +639,8 @@ private: bool mKernelIdleTimerEnabled = false; // Show spinner with refresh rate overlay bool mRefreshRateOverlaySpinner = false; + // Show render rate with refresh rate overlay + bool mRefreshRateOverlayRenderRate = false; void setDesiredActiveMode(display::DisplayModeRequest&&) REQUIRES(mStateLock); -- GitLab From 5cee1e36d90b42aa13eb181d6e4ec02b238ccc9c Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 29 Nov 2022 12:35:39 -0800 Subject: [PATCH 0568/1310] Add test for hover events over spy window In this test, we check whether spy windows receive hover events. Bug: 211379801 Test: m inputflinger_tests && adb sync data && adb shell -t /data/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: Ia4c0cbb889d1c9500b0b1696dc2f5690ef4550fe --- .../dispatcher/InputDispatcher.cpp | 17 +--- .../inputflinger/dispatcher/TouchState.cpp | 3 +- .../tests/InputDispatcher_test.cpp | 96 ++++++++++++------- 3 files changed, 65 insertions(+), 51 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 466c51eb88..0e95ee4bb4 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2096,7 +2096,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( bool isSplit = shouldSplitTouch(tempTouchState, entry); const bool switchedDevice = (oldState != nullptr) && - (tempTouchState.deviceId != entry.deviceId || tempTouchState.source != entry.source); + (oldState->deviceId != entry.deviceId || oldState->source != entry.source); const bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE || maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER || @@ -2104,6 +2104,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( const bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN || maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction); const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE); + if (newGesture) { bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN; if (switchedDevice && tempTouchState.isDown() && !down && !isHoverAction) { @@ -2995,6 +2996,8 @@ void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime, connection->getInputChannelName().c_str(), eventEntry->id); ATRACE_NAME(message.c_str()); } + LOG_ALWAYS_FATAL_IF(!inputTarget.flags.any(InputTarget::DISPATCH_MASK), + "No dispatch flags are set for %s", eventEntry->getDescription().c_str()); const bool wasEmpty = connection->outboundQueue.empty(); @@ -4981,10 +4984,6 @@ void InputDispatcher::setFocusedDisplay(int32_t displayId) { } } } - - if (DEBUG_FOCUS) { - logDispatchStateLocked(); - } } // release lock // Wake up poll loop since it may need to make new input dispatching choices. @@ -5015,10 +5014,6 @@ void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) { } else { changed = false; } - - if (DEBUG_FOCUS) { - logDispatchStateLocked(); - } } // release lock if (changed) { @@ -5193,10 +5188,6 @@ bool InputDispatcher::transferTouchFocus(const sp& fromToken, const sp< synthesizeCancelationEventsForConnectionLocked(fromConnection, options); synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection); } - - if (DEBUG_FOCUS) { - logDispatchStateLocked(); - } } // release lock // Wake up poll loop since it may need to make new input dispatching choices. diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index 114e0bf130..c21af9e0b5 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -34,8 +34,7 @@ void TouchState::reset() { void TouchState::addOrUpdateWindow(const sp& windowHandle, ftl::Flags targetFlags, BitSet32 pointerIds, std::optional eventTime) { - for (size_t i = 0; i < windows.size(); i++) { - TouchedWindow& touchedWindow = windows[i]; + for (TouchedWindow& touchedWindow : windows) { if (touchedWindow.windowHandle == windowHandle) { touchedWindow.targetFlags |= targetFlags; if (targetFlags.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT)) { diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index f3239caa6a..a5b5bb8cf8 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -1231,7 +1231,7 @@ public: void consumeMotionEvent(const ::testing::Matcher& matcher) { MotionEvent* motionEvent = consumeMotion(); - ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event"; + ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event, but expected " << matcher; ASSERT_THAT(*motionEvent, matcher); } @@ -2128,10 +2128,8 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .x(900) .y(400)) .build())); - windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); - windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_MOVE, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); // Move cursor into left window ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -2142,12 +2140,9 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .x(300) .y(400)) .build())); - windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_EXIT, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); - windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); - windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_MOVE, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); + windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); // Inject a series of mouse events for a mouse click ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -2170,8 +2165,7 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .x(300) .y(400)) .build())); - windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_BUTTON_PRESS, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, @@ -2183,8 +2177,7 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .x(300) .y(400)) .build())); - windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_BUTTON_RELEASE, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE)); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, @@ -2205,12 +2198,47 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .x(900) .y(400)) .build())); - windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_EXIT, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); - windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); - windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_MOVE, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); + windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); + + // No more events + windowLeft->assertNoEvents(); + windowRight->assertNoEvents(); +} + +TEST_F(InputDispatcherTest, HoverWithSpyWindows) { + std::shared_ptr application = std::make_shared(); + + sp spyWindow = + sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 600, 800)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 600, 800)); + + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}}); + + // Send mouse cursor to the window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, + AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .x(100) + .y(100)) + .build())); + + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithSource(AINPUT_SOURCE_MOUSE))); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithSource(AINPUT_SOURCE_MOUSE))); + + window->assertNoEvents(); + spyWindow->assertNoEvents(); } // This test is different from the test above that HOVER_ENTER and HOVER_EXIT events are injected @@ -2233,8 +2261,7 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { .x(300) .y(400)) .build())); - window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); // Inject a series of mouse events for a mouse click ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -2257,8 +2284,7 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { .x(300) .y(400)) .build())); - window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_BUTTON_PRESS, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, @@ -2270,8 +2296,7 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { .x(300) .y(400)) .build())); - window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_BUTTON_RELEASE, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE)); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, @@ -2291,8 +2316,7 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { .x(300) .y(400)) .build())); - window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_EXIT, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); } /** @@ -2362,14 +2386,13 @@ TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) { .x(300) .y(600)) .build())); - windowDefaultDisplay->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); - windowDefaultDisplay->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_MOVE, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + windowDefaultDisplay->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + windowDefaultDisplay->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); // Remove all windows in secondary display and check that no event happens on window in // primary display. - mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {}}}); + mDispatcher->setInputWindows( + {{ADISPLAY_ID_DEFAULT, {windowDefaultDisplay}}, {SECOND_DISPLAY_ID, {}}}); windowDefaultDisplay->assertNoEvents(); // Move cursor position in window in default display and check that only hover move @@ -2385,8 +2408,9 @@ TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) { .x(400) .y(700)) .build())); - windowDefaultDisplay->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_MOVE, - ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */); + windowDefaultDisplay->consumeMotionEvent( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithSource(AINPUT_SOURCE_MOUSE))); windowDefaultDisplay->assertNoEvents(); } -- GitLab From bdf7e4b411b312687559e5610d35cd0ca2e1d86e Mon Sep 17 00:00:00 2001 From: Marc Kassis Date: Fri, 4 Nov 2022 17:26:48 +0100 Subject: [PATCH 0569/1310] Each Display Mode now has a list of supported HDR types. SurfaceFlinger removes Dolby Vision as a supported HDR type when a certain mode does not support it Bug: 241349060 Test: atest ExcludeDolbyVisionTest Change-Id: I481cc13417f65cbcb1f4658be410adb6e99c760b --- libs/gui/SurfaceComposerClient.cpp | 3 + libs/gui/aidl/android/gui/DisplayMode.aidl | 1 + libs/ui/include/ui/DisplayMode.h | 2 + libs/ui/include/ui/GraphicTypes.h | 3 +- .../DisplayHardware/AidlComposerHal.cpp | 2 +- .../DisplayHardware/AidlComposerHal.h | 2 +- .../DisplayHardware/ComposerHal.h | 4 +- .../surfaceflinger/DisplayHardware/HWC2.cpp | 10 +- services/surfaceflinger/DisplayHardware/Hal.h | 3 +- .../DisplayHardware/HidlComposerHal.cpp | 14 +-- .../DisplayHardware/HidlComposerHal.h | 3 +- services/surfaceflinger/SurfaceFlinger.cpp | 50 +++++++- .../fuzzer/surfaceflinger_fuzzers_utils.h | 1 - .../surfaceflinger/tests/unittests/Android.bp | 1 + .../SurfaceFlinger_ExcludeDolbyVisionTest.cpp | 109 ++++++++++++++++++ .../tests/unittests/TestableSurfaceFlinger.h | 8 +- .../mock/DisplayHardware/MockComposer.h | 1 - 17 files changed, 189 insertions(+), 28 deletions(-) create mode 100644 services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 1e43700d06..aaa2102a0d 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2330,6 +2330,9 @@ status_t SurfaceComposerClient::getDynamicDisplayInfo(const sp& display outMode.sfVsyncOffset = mode.sfVsyncOffset; outMode.presentationDeadline = mode.presentationDeadline; outMode.group = mode.group; + std::transform(mode.supportedHdrTypes.begin(), mode.supportedHdrTypes.end(), + std::back_inserter(outMode.supportedHdrTypes), + [](const int32_t& value) { return static_cast(value); }); outInfo->supportedDisplayModes.push_back(outMode); } diff --git a/libs/gui/aidl/android/gui/DisplayMode.aidl b/libs/gui/aidl/android/gui/DisplayMode.aidl index 3cd77f82d7..ce30426cb5 100644 --- a/libs/gui/aidl/android/gui/DisplayMode.aidl +++ b/libs/gui/aidl/android/gui/DisplayMode.aidl @@ -27,6 +27,7 @@ parcelable DisplayMode { Size resolution; float xDpi = 0.0f; float yDpi = 0.0f; + int[] supportedHdrTypes; float refreshRate = 0.0f; long appVsyncOffset = 0; diff --git a/libs/ui/include/ui/DisplayMode.h b/libs/ui/include/ui/DisplayMode.h index a2791a6d44..65a8769c98 100644 --- a/libs/ui/include/ui/DisplayMode.h +++ b/libs/ui/include/ui/DisplayMode.h @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -34,6 +35,7 @@ struct DisplayMode { ui::Size resolution; float xDpi = 0; float yDpi = 0; + std::vector supportedHdrTypes; float refreshRate = 0; nsecs_t appVsyncOffset = 0; diff --git a/libs/ui/include/ui/GraphicTypes.h b/libs/ui/include/ui/GraphicTypes.h index 8661c36676..1775d390f0 100644 --- a/libs/ui/include/ui/GraphicTypes.h +++ b/libs/ui/include/ui/GraphicTypes.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -42,7 +43,6 @@ namespace ui { using android::hardware::graphics::common::V1_1::RenderIntent; using android::hardware::graphics::common::V1_2::ColorMode; using android::hardware::graphics::common::V1_2::Dataspace; -using android::hardware::graphics::common::V1_2::Hdr; using android::hardware::graphics::common::V1_2::PixelFormat; /** @@ -50,6 +50,7 @@ using android::hardware::graphics::common::V1_2::PixelFormat; */ using aidl::android::hardware::graphics::common::BlendMode; using aidl::android::hardware::graphics::common::Cta861_3; +using aidl::android::hardware::graphics::common::Hdr; using aidl::android::hardware::graphics::common::PlaneLayout; using aidl::android::hardware::graphics::common::Smpte2086; diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp index 3782c6c236..800e36db48 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp @@ -535,7 +535,7 @@ Error AidlComposer::getHdrCapabilities(Display display, std::vector* outTyp return static_cast(status.getServiceSpecificError()); } - *outTypes = translate(capabilities.types); + *outTypes = capabilities.types; *outMaxLuminance = capabilities.maxLuminance; *outMaxAverageLuminance = capabilities.maxAverageLuminance; *outMinLuminance = capabilities.minLuminance; diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h index d84efe7d38..2a043fd0db 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h @@ -104,7 +104,7 @@ public: Error getDozeSupport(Display display, bool* outSupport) override; Error hasDisplayIdleTimerCapability(Display display, bool* outSupport) override; - Error getHdrCapabilities(Display display, std::vector* outTypes, float* outMaxLuminance, + Error getHdrCapabilities(Display display, std::vector* outHdrTypes, float* outMaxLuminance, float* outMaxAverageLuminance, float* outMinLuminance) override; Error getOverlaySupport(OverlayProperties* outProperties) override; diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h index a9bf282d94..ec23935dbd 100644 --- a/services/surfaceflinger/DisplayHardware/ComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h @@ -66,7 +66,6 @@ using types::V1_0::Transform; using types::V1_1::RenderIntent; using types::V1_2::ColorMode; using types::V1_2::Dataspace; -using types::V1_2::Hdr; using types::V1_2::PixelFormat; using V2_1::Config; @@ -84,6 +83,7 @@ using PerFrameMetadata = IComposerClient::PerFrameMetadata; using PerFrameMetadataKey = IComposerClient::PerFrameMetadataKey; using PerFrameMetadataBlob = IComposerClient::PerFrameMetadataBlob; using AidlTransform = ::aidl::android::hardware::graphics::common::Transform; +using aidl::android::hardware::graphics::common::Hdr; class Composer { public: @@ -140,7 +140,7 @@ public: virtual Error getDozeSupport(Display display, bool* outSupport) = 0; virtual Error hasDisplayIdleTimerCapability(Display display, bool* outSupport) = 0; - virtual Error getHdrCapabilities(Display display, std::vector* outTypes, + virtual Error getHdrCapabilities(Display display, std::vector* outHdrTypes, float* outMaxLuminance, float* outMaxAverageLuminance, float* outMinLuminance) = 0; diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp index e264570a87..d0126d05ff 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.cpp +++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp @@ -320,17 +320,17 @@ Error Display::getHdrCapabilities(HdrCapabilities* outCapabilities) const float maxLuminance = -1.0f; float maxAverageLuminance = -1.0f; float minLuminance = -1.0f; - std::vector types; - auto intError = mComposer.getHdrCapabilities(mId, &types, - &maxLuminance, &maxAverageLuminance, &minLuminance); + std::vector hdrTypes; + auto intError = mComposer.getHdrCapabilities(mId, &hdrTypes, &maxLuminance, + &maxAverageLuminance, &minLuminance); auto error = static_cast(intError); if (error != Error::NONE) { return error; } - *outCapabilities = HdrCapabilities(std::move(types), - maxLuminance, maxAverageLuminance, minLuminance); + *outCapabilities = + HdrCapabilities(std::move(hdrTypes), maxLuminance, maxAverageLuminance, minLuminance); return Error::NONE; } diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h index 82388289ed..537d5450ad 100644 --- a/services/surfaceflinger/DisplayHardware/Hal.h +++ b/services/surfaceflinger/DisplayHardware/Hal.h @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -39,7 +40,6 @@ using types::V1_0::Transform; using types::V1_1::RenderIntent; using types::V1_2::ColorMode; using types::V1_2::Dataspace; -using types::V1_2::Hdr; using types::V1_2::PixelFormat; using V2_1::Error; @@ -69,6 +69,7 @@ using PerFrameMetadataBlob = IComposerClient::PerFrameMetadataBlob; using PowerMode = IComposerClient::PowerMode; using Vsync = IComposerClient::Vsync; using VsyncPeriodChangeConstraints = IComposerClient::VsyncPeriodChangeConstraints; +using Hdr = aidl::android::hardware::graphics::common::Hdr; } // namespace hardware::graphics::composer::hal diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp index f8522e2ef0..b607df0fd3 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp @@ -496,13 +496,13 @@ Error HidlComposer::hasDisplayIdleTimerCapability(Display, bool*) { "OptionalFeature::KernelIdleTimer is not supported on HIDL"); } -Error HidlComposer::getHdrCapabilities(Display display, std::vector* outTypes, +Error HidlComposer::getHdrCapabilities(Display display, std::vector* outHdrTypes, float* outMaxLuminance, float* outMaxAverageLuminance, float* outMinLuminance) { Error error = kDefaultError; if (mClient_2_3) { mClient_2_3->getHdrCapabilities_2_3(display, - [&](const auto& tmpError, const auto& tmpTypes, + [&](const auto& tmpError, const auto& tmpHdrTypes, const auto& tmpMaxLuminance, const auto& tmpMaxAverageLuminance, const auto& tmpMinLuminance) { @@ -510,15 +510,15 @@ Error HidlComposer::getHdrCapabilities(Display display, std::vector* outTyp if (error != Error::NONE) { return; } + *outHdrTypes = translate(tmpHdrTypes); - *outTypes = tmpTypes; *outMaxLuminance = tmpMaxLuminance; *outMaxAverageLuminance = tmpMaxAverageLuminance; *outMinLuminance = tmpMinLuminance; }); } else { mClient->getHdrCapabilities(display, - [&](const auto& tmpError, const auto& tmpTypes, + [&](const auto& tmpError, const auto& tmpHdrTypes, const auto& tmpMaxLuminance, const auto& tmpMaxAverageLuminance, const auto& tmpMinLuminance) { @@ -526,11 +526,7 @@ Error HidlComposer::getHdrCapabilities(Display display, std::vector* outTyp if (error != Error::NONE) { return; } - - outTypes->clear(); - for (auto type : tmpTypes) { - outTypes->push_back(static_cast(type)); - } + *outHdrTypes = translate(tmpHdrTypes); *outMaxLuminance = tmpMaxLuminance; *outMaxAverageLuminance = tmpMaxAverageLuminance; diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h index 48b720c108..3602bbb24e 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h @@ -56,7 +56,6 @@ using types::V1_0::Transform; using types::V1_1::RenderIntent; using types::V1_2::ColorMode; using types::V1_2::Dataspace; -using types::V1_2::Hdr; using types::V1_2::PixelFormat; using V2_1::Config; @@ -209,7 +208,7 @@ public: Error getDozeSupport(Display display, bool* outSupport) override; Error hasDisplayIdleTimerCapability(Display display, bool* outSupport) override; - Error getHdrCapabilities(Display display, std::vector* outTypes, float* outMaxLuminance, + Error getHdrCapabilities(Display display, std::vector* outHdrTypes, float* outMaxLuminance, float* outMaxAverageLuminance, float* outMinLuminance) override; Error getOverlaySupport(aidl::android::hardware::graphics::composer3::OverlayProperties* outProperties) override; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 9af8ca91c6..bd3c0f12cc 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -198,6 +198,9 @@ namespace hal = android::hardware::graphics::composer::hal; namespace { +static constexpr int FOUR_K_WIDTH = 3840; +static constexpr int FOUR_K_HEIGHT = 2160; + // TODO(b/141333600): Consolidate with DisplayMode::Builder::getDefaultDensity. constexpr float FALLBACK_DENSITY = ACONFIGURATION_DENSITY_TV; @@ -245,6 +248,44 @@ bool getKernelIdleTimerSyspropConfig(DisplayId displayId) { return displaySupportKernelIdleTimer || sysprop::support_kernel_idle_timer(false); } +bool isAbove4k30(const ui::DisplayMode& outMode) { + using fps_approx_ops::operator>; + Fps refreshRate = Fps::fromValue(outMode.refreshRate); + return outMode.resolution.getWidth() >= FOUR_K_WIDTH && + outMode.resolution.getHeight() >= FOUR_K_HEIGHT && refreshRate > 30_Hz; +} + +void excludeDolbyVisionIf4k30Present(const std::vector& displayHdrTypes, + ui::DisplayMode& outMode) { + if (isAbove4k30(outMode) && + std::any_of(displayHdrTypes.begin(), displayHdrTypes.end(), + [](ui::Hdr type) { return type == ui::Hdr::DOLBY_VISION_4K30; })) { + for (ui::Hdr type : displayHdrTypes) { + if (type != ui::Hdr::DOLBY_VISION_4K30 && type != ui::Hdr::DOLBY_VISION) { + outMode.supportedHdrTypes.push_back(type); + } + } + } else { + for (ui::Hdr type : displayHdrTypes) { + if (type != ui::Hdr::DOLBY_VISION_4K30) { + outMode.supportedHdrTypes.push_back(type); + } + } + } +} + +HdrCapabilities filterOut4k30(const HdrCapabilities& displayHdrCapabilities) { + std::vector hdrTypes; + for (ui::Hdr type : displayHdrCapabilities.getSupportedHdrTypes()) { + if (type != ui::Hdr::DOLBY_VISION_4K30) { + hdrTypes.push_back(type); + } + } + return {hdrTypes, displayHdrCapabilities.getDesiredMaxLuminance(), + displayHdrCapabilities.getDesiredMaxAverageLuminance(), + displayHdrCapabilities.getDesiredMinLuminance()}; +} + } // namespace anonymous // --------------------------------------------------------------------------- @@ -1033,7 +1074,8 @@ status_t SurfaceFlinger::getDynamicDisplayInfo(const sp& displayToken, // We add an additional 1ms to allow for processing time and // differences between the ideal and actual refresh rate. outMode.presentationDeadline = period - outMode.sfVsyncOffset + 1000000; - + excludeDolbyVisionIf4k30Present(display->getHdrCapabilities().getSupportedHdrTypes(), + outMode); info->supportedDisplayModes.push_back(outMode); } @@ -1044,7 +1086,7 @@ status_t SurfaceFlinger::getDynamicDisplayInfo(const sp& displayToken, info->activeDisplayModeId = display->refreshRateSelector().getActiveMode().modePtr->getId().value(); info->activeColorMode = display->getCompositionDisplay()->getState().colorMode; - info->hdrCapabilities = display->getHdrCapabilities(); + info->hdrCapabilities = filterOut4k30(display->getHdrCapabilities()); info->autoLowLatencyModeSupported = getHwComposer().hasDisplayCapability(displayId, @@ -7355,6 +7397,10 @@ binder::Status SurfaceComposerAIDL::getDynamicDisplayInfo(const sp& dis outMode.sfVsyncOffset = mode.sfVsyncOffset; outMode.presentationDeadline = mode.presentationDeadline; outMode.group = mode.group; + std::transform(mode.supportedHdrTypes.begin(), mode.supportedHdrTypes.end(), + std::back_inserter(outMode.supportedHdrTypes), + [](const ui::Hdr& value) { return static_cast(value); }); + outInfo->supportedDisplayModes.push_back(outMode); } diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index dbfce78283..c0a6bdbe27 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -81,7 +81,6 @@ using types::V1_0::Transform; using types::V1_1::RenderIntent; using types::V1_2::ColorMode; using types::V1_2::Dataspace; -using types::V1_2::Hdr; using types::V1_2::PixelFormat; using V2_1::Config; diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index 01145772d5..8b0cd78732 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -111,6 +111,7 @@ cc_test { "SurfaceFlinger_SetPowerModeInternalTest.cpp", "SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp", "SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp", + "SurfaceFlinger_ExcludeDolbyVisionTest.cpp", "SchedulerTest.cpp", "SetFrameRateTest.cpp", "RefreshRateSelectorTest.cpp", diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp new file mode 100644 index 0000000000..11e734a416 --- /dev/null +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2020 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. + */ + +#undef LOG_TAG +#define LOG_TAG "LibSurfaceFlingerUnittests" + +#include "DisplayTransactionTestHelpers.h" + +namespace android { +namespace { + +using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector; + +class ExcludeDolbyVisionTest : public DisplayTransactionTest { +public: + void injectDisplayModes(std::vector displayModePtrs) { + DisplayModes modes; + for (DisplayModePtr displayMode : displayModePtrs) { + modes.try_emplace(displayMode->getId(), displayMode); + } + + mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this) + .setDisplayModes(std::move(modes), displayModePtrs[0]->getId()) + .inject(); + mDisplay->overrideHdrTypes(types); + } + +protected: + sp mDisplay; + + static constexpr DisplayModeId modeId1080p60{0}; + static constexpr DisplayModeId modeId4k30{1}; + static constexpr DisplayModeId modeId4k60{2}; + + static inline const DisplayModePtr mode1080p60 = + createDisplayMode(modeId1080p60, 60_Hz, 0, ui::Size(1920, 1080)); + static inline const DisplayModePtr mode4k30 = + createDisplayMode(modeId4k30, 30_Hz, 1, ui::Size(3840, 2160)); + static inline const DisplayModePtr mode4k30NonStandard = + createDisplayMode(modeId4k30, 30.1_Hz, 1, ui::Size(3840, 2160)); + static inline const DisplayModePtr mode4k60 = + createDisplayMode(modeId4k60, 60_Hz, 2, ui::Size(3840, 2160)); + + const std::vector types = {ui::Hdr::DOLBY_VISION, ui::Hdr::DOLBY_VISION_4K30, + ui::Hdr::HDR10_PLUS}; +}; + +TEST_F(ExcludeDolbyVisionTest, excludesDolbyVisionOnModesHigherThan4k30) { + injectDisplayModes({mode4k60}); + ui::DynamicDisplayInfo info; + mFlinger.getDynamicDisplayInfo(mDisplay->getDisplayToken().promote(), &info); + + std::vector displayModes = info.supportedDisplayModes; + + ASSERT_EQ(1, displayModes.size()); + ASSERT_TRUE(std::any_of(displayModes[0].supportedHdrTypes.begin(), + displayModes[0].supportedHdrTypes.end(), + [](ui::Hdr type) { return type == ui::Hdr::HDR10_PLUS; })); + ASSERT_TRUE(displayModes[0].supportedHdrTypes.size() == 1); +} + +TEST_F(ExcludeDolbyVisionTest, includesDolbyVisionOnModesLowerThanOrEqualTo4k30) { + injectDisplayModes({mode1080p60, mode4k30, mode4k30NonStandard}); + ui::DynamicDisplayInfo info; + mFlinger.getDynamicDisplayInfo(mDisplay->getDisplayToken().promote(), &info); + + std::vector displayModes = info.supportedDisplayModes; + + ASSERT_EQ(2, displayModes.size()); + for (size_t i = 0; i < displayModes.size(); i++) { + ASSERT_TRUE(std::any_of(displayModes[i].supportedHdrTypes.begin(), + displayModes[i].supportedHdrTypes.end(), + [](ui::Hdr type) { return type == ui::Hdr::HDR10_PLUS; })); + ASSERT_TRUE(std::any_of(displayModes[i].supportedHdrTypes.begin(), + displayModes[i].supportedHdrTypes.end(), + [](ui::Hdr type) { return type == ui::Hdr::DOLBY_VISION; })); + ASSERT_TRUE(displayModes[i].supportedHdrTypes.size() == 2); + } +} + +TEST_F(ExcludeDolbyVisionTest, 4k30IsNotReportedAsAValidHdrType) { + injectDisplayModes({mode4k60}); + ui::DynamicDisplayInfo info; + mFlinger.getDynamicDisplayInfo(mDisplay->getDisplayToken().promote(), &info); + + std::vector displayHdrTypes = info.hdrCapabilities.getSupportedHdrTypes(); + + ASSERT_EQ(2, displayHdrTypes.size()); + ASSERT_TRUE(std::any_of(displayHdrTypes.begin(), displayHdrTypes.end(), + [](ui::Hdr type) { return type == ui::Hdr::HDR10_PLUS; })); + ASSERT_TRUE(std::any_of(displayHdrTypes.begin(), displayHdrTypes.end(), + [](ui::Hdr type) { return type == ui::Hdr::DOLBY_VISION; })); +} + +} // namespace +} // namespace android diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index e29dd67c21..c8362ee036 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -30,6 +30,7 @@ #include #include +#include #include "DisplayDevice.h" #include "FakeVsyncConfiguration.h" #include "FrameTracer/FrameTracer.h" @@ -486,6 +487,11 @@ public: void updateLayerMetadataSnapshot() { mFlinger->updateLayerMetadataSnapshot(); } + void getDynamicDisplayInfo(const sp& displayToken, + ui::DynamicDisplayInfo* dynamicDisplayInfo) { + mFlinger->getDynamicDisplayInfo(displayToken, dynamicDisplayInfo); + } + /* ------------------------------------------------------------------------ * Read-only access to private data to assert post-conditions. */ @@ -643,7 +649,6 @@ public: // is much longer lived. auto display = std::make_unique(*composer, *mCapabilities, mHwcDisplayId, mHwcDisplayType); - display->mutableIsConnected() = true; display->setPowerMode(mPowerMode); flinger->mutableHwcDisplayData()[mDisplayId].hwcDisplay = std::move(display); @@ -652,7 +657,6 @@ public: .WillRepeatedly( DoAll(SetArgPointee<1>(std::vector{mActiveConfig}), Return(hal::Error::NONE))); - EXPECT_CALL(*composer, getDisplayAttribute(mHwcDisplayId, mActiveConfig, hal::Attribute::WIDTH, _)) .WillRepeatedly(DoAll(SetArgPointee<3>(mResolution.getWidth()), diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h index 5ee38ec621..836e3a47cc 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h @@ -32,7 +32,6 @@ using android::hardware::graphics::common::V1_0::Transform; using android::hardware::graphics::common::V1_1::RenderIntent; using android::hardware::graphics::common::V1_2::ColorMode; using android::hardware::graphics::common::V1_2::Dataspace; -using android::hardware::graphics::common::V1_2::Hdr; using android::hardware::graphics::common::V1_2::PixelFormat; using android::hardware::graphics::composer::V2_1::Config; -- GitLab From 2f61bdcda1ac3d90640e851bb6a427b796f582e2 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 2 Dec 2022 08:55:51 -0800 Subject: [PATCH 0570/1310] Stricter validation of motion events in dispatcher We are already validating incoming motion events, but for the events received from the bottom layers, currently there's no crash. Add a crash to ensure that the data that the dispatcher is getting is good. Ideally, this would be done directly in the constructor for NotifyMotionArgs, but that could be a separate refactor after we convert pointerProperties and PointerCoords to std::vector. Bug: 211379801 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: Id089db27245827de7c8f83b57dbf38adf820b5f8 --- .../dispatcher/InputDispatcher.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 0e95ee4bb4..4aac377b42 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -148,21 +148,23 @@ bool validateKeyEvent(int32_t action) { } bool isValidMotionAction(int32_t action, int32_t actionButton, int32_t pointerCount) { - switch (action & AMOTION_EVENT_ACTION_MASK) { + switch (MotionEvent::getActionMasked(action)) { case AMOTION_EVENT_ACTION_DOWN: case AMOTION_EVENT_ACTION_UP: - case AMOTION_EVENT_ACTION_CANCEL: + return pointerCount == 1; case AMOTION_EVENT_ACTION_MOVE: - case AMOTION_EVENT_ACTION_OUTSIDE: case AMOTION_EVENT_ACTION_HOVER_ENTER: case AMOTION_EVENT_ACTION_HOVER_MOVE: case AMOTION_EVENT_ACTION_HOVER_EXIT: + return pointerCount >= 1; + case AMOTION_EVENT_ACTION_CANCEL: + case AMOTION_EVENT_ACTION_OUTSIDE: case AMOTION_EVENT_ACTION_SCROLL: return true; case AMOTION_EVENT_ACTION_POINTER_DOWN: case AMOTION_EVENT_ACTION_POINTER_UP: { - int32_t index = getMotionEventActionPointerIndex(action); - return index >= 0 && index < pointerCount; + const int32_t index = MotionEvent::getActionIndex(action); + return index >= 0 && index < pointerCount && pointerCount > 1; } case AMOTION_EVENT_ACTION_BUTTON_PRESS: case AMOTION_EVENT_ACTION_BUTTON_RELEASE: @@ -4056,10 +4058,9 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) { args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION)); } } - if (!validateMotionEvent(args->action, args->actionButton, args->pointerCount, - args->pointerProperties)) { - return; - } + LOG_ALWAYS_FATAL_IF(!validateMotionEvent(args->action, args->actionButton, args->pointerCount, + args->pointerProperties), + "Invalid event: %s", args->dump().c_str()); uint32_t policyFlags = args->policyFlags; policyFlags |= POLICY_FLAG_TRUSTED; -- GitLab From e9349e7a626bc5b7a495f0f5169cab6d679b97f4 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 2 Dec 2022 11:39:20 -0800 Subject: [PATCH 0571/1310] Ensure returned event is non-null Currently, we aren't checking whether the returned event is null. This causes NPE during a test run when the implementation is bad. The NPE causes the entire test run to fail, rather than failing individual tests. This is inconvenient when doing TDD. In this change, add a matcher for downtime to simplify the code, and add null checks for cases where a matcher wouldn't be convenient. Bug: 211379801 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: Id0c3cf1908adee0d810c7e24f8507c14f689f999 --- .../tests/InputDispatcher_test.cpp | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index a5b5bb8cf8..41c174a1f7 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -110,6 +110,13 @@ MATCHER_P(WithMotionAction, action, "MotionEvent with specified action") { *result_listener << "expected action " << MotionEvent::actionToString(action) << ", but got " << MotionEvent::actionToString(arg.getAction()); } + if (action == AMOTION_EVENT_ACTION_DOWN) { + if (!matches) { + *result_listener << "; "; + } + *result_listener << "downTime should match eventTime for ACTION_DOWN events"; + matches &= arg.getDownTime() == arg.getEventTime(); + } if (action == AMOTION_EVENT_ACTION_CANCEL) { if (!matches) { *result_listener << "; "; @@ -120,6 +127,10 @@ MATCHER_P(WithMotionAction, action, "MotionEvent with specified action") { return matches; } +MATCHER_P(WithDownTime, downTime, "InputEvent with specified downTime") { + return arg.getDownTime() == downTime; +} + MATCHER_P(WithSource, source, "InputEvent with specified source") { *result_listener << "expected source " << inputEventSourceToString(source) << ", but got " << inputEventSourceToString(arg.getSource()); @@ -1210,6 +1221,7 @@ public: void consumeMotionOutsideWithZeroedCoords(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { InputEvent* event = consume(); + ASSERT_NE(nullptr, event); ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType()); const MotionEvent& motionEvent = static_cast(*event); EXPECT_EQ(AMOTION_EVENT_ACTION_OUTSIDE, motionEvent.getActionMasked()); @@ -2056,6 +2068,7 @@ TEST_F(InputDispatcherTest, SplitTouchesSendCorrectActionDownTime) { mDispatcher->waitForIdle(); InputEvent* inputEvent1 = window1->consume(); + ASSERT_NE(inputEvent1, nullptr); window2->assertNoEvents(); MotionEvent& motionEvent1 = static_cast(*inputEvent1); nsecs_t downTimeForWindow1 = motionEvent1.getDownTime(); @@ -2065,6 +2078,7 @@ TEST_F(InputDispatcherTest, SplitTouchesSendCorrectActionDownTime) { mDispatcher->notifyMotion(&(args = generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}}))); mDispatcher->waitForIdle(); InputEvent* inputEvent2 = window2->consume(); + ASSERT_NE(inputEvent2, nullptr); MotionEvent& motionEvent2 = static_cast(*inputEvent2); nsecs_t downTimeForWindow2 = motionEvent2.getDownTime(); ASSERT_NE(downTimeForWindow1, downTimeForWindow2); @@ -2074,17 +2088,13 @@ TEST_F(InputDispatcherTest, SplitTouchesSendCorrectActionDownTime) { mDispatcher->notifyMotion( &(args = generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{50, 50}, {151, 51}}))); mDispatcher->waitForIdle(); - InputEvent* inputEvent3 = window2->consume(); - MotionEvent& motionEvent3 = static_cast(*inputEvent3); - ASSERT_EQ(motionEvent3.getDownTime(), downTimeForWindow2); + window2->consumeMotionEvent(WithDownTime(downTimeForWindow2)); // Now add new touch down on the second window mDispatcher->notifyMotion( &(args = generateTouchArgs(POINTER_2_DOWN, {{50, 50}, {151, 51}, {150, 50}}))); mDispatcher->waitForIdle(); - InputEvent* inputEvent4 = window2->consume(); - MotionEvent& motionEvent4 = static_cast(*inputEvent4); - ASSERT_EQ(motionEvent4.getDownTime(), downTimeForWindow2); + window2->consumeMotionEvent(WithDownTime(downTimeForWindow2)); // TODO(b/232530217): do not send the unnecessary MOVE event and delete the next line window1->consumeMotionMove(); @@ -2094,16 +2104,12 @@ TEST_F(InputDispatcherTest, SplitTouchesSendCorrectActionDownTime) { mDispatcher->notifyMotion( &(args = generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{51, 51}, {151, 51}}))); mDispatcher->waitForIdle(); - InputEvent* inputEvent5 = window1->consume(); - MotionEvent& motionEvent5 = static_cast(*inputEvent5); - ASSERT_EQ(motionEvent5.getDownTime(), downTimeForWindow1); + window1->consumeMotionEvent(WithDownTime(downTimeForWindow1)); mDispatcher->notifyMotion(&( args = generateTouchArgs(POINTER_3_DOWN, {{51, 51}, {151, 51}, {150, 50}, {50, 50}}))); mDispatcher->waitForIdle(); - InputEvent* inputEvent6 = window1->consume(); - MotionEvent& motionEvent6 = static_cast(*inputEvent6); - ASSERT_EQ(motionEvent6.getDownTime(), downTimeForWindow1); + window1->consumeMotionEvent(WithDownTime(downTimeForWindow1)); } TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { @@ -4693,6 +4699,7 @@ protected: const MotionEvent& motionEvent = static_cast(*event); assertMotionAction(expectedAction, motionEvent.getAction()); + ASSERT_EQ(points.size(), motionEvent.getPointerCount()); for (size_t i = 0; i < points.size(); i++) { float expectedX = points[i].x; -- GitLab From 67434eb6a138eadad9e0c38f111841957ff7fa41 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Thu, 1 Dec 2022 17:48:12 -0800 Subject: [PATCH 0572/1310] SF: send the renderFrameRate with mode changed event Bug: 259740021 Test: atest FrameRateOverrideHostTest Test: atest libsurfaceflinger_unittest Change-Id: I3ef1b5c945826b37b9da739177c0b2fcd9444e08 --- libs/gui/SurfaceComposerClient.cpp | 1 + .../aidl/android/gui/DynamicDisplayInfo.aidl | 1 + libs/ui/include/ui/DynamicDisplayInfo.h | 1 + .../surfaceflinger/Scheduler/EventThread.cpp | 12 ++++----- .../surfaceflinger/Scheduler/EventThread.h | 5 ++-- .../surfaceflinger/Scheduler/Scheduler.cpp | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 25 +++++++++++-------- .../tests/unittests/EventThreadTest.cpp | 20 +++++++++------ .../SurfaceFlinger_DisplayModeSwitching.cpp | 6 +++-- .../tests/unittests/mock/MockEventThread.h | 2 +- 10 files changed, 44 insertions(+), 31 deletions(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index aaa2102a0d..325c294762 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2337,6 +2337,7 @@ status_t SurfaceComposerClient::getDynamicDisplayInfo(const sp& display } outInfo->activeDisplayModeId = ginfo.activeDisplayModeId; + outInfo->renderFrameRate = ginfo.renderFrameRate; outInfo->supportedColorModes.clear(); outInfo->supportedColorModes.reserve(ginfo.supportedColorModes.size()); diff --git a/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl b/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl index 57e6081e27..3114929e86 100644 --- a/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl +++ b/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl @@ -27,6 +27,7 @@ parcelable DynamicDisplayInfo { List supportedDisplayModes; int activeDisplayModeId; + float renderFrameRate; int[] supportedColorModes; int activeColorMode; diff --git a/libs/ui/include/ui/DynamicDisplayInfo.h b/libs/ui/include/ui/DynamicDisplayInfo.h index 8c9fe4c311..0b77754455 100644 --- a/libs/ui/include/ui/DynamicDisplayInfo.h +++ b/libs/ui/include/ui/DynamicDisplayInfo.h @@ -35,6 +35,7 @@ struct DynamicDisplayInfo { // we can't use size_t because it may have different width // in the client process. ui::DisplayModeId activeDisplayModeId; + float renderFrameRate; std::vector supportedColorModes; ui::ColorMode activeColorMode; diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index 1acf15a55c..008d8c4461 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -124,12 +124,12 @@ DisplayEventReceiver::Event makeVSync(PhysicalDisplayId displayId, nsecs_t times return event; } -DisplayEventReceiver::Event makeModeChanged(DisplayModePtr mode) { +DisplayEventReceiver::Event makeModeChanged(const scheduler::FrameRateMode& mode) { DisplayEventReceiver::Event event; - event.header = {DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE, mode->getPhysicalDisplayId(), - systemTime()}; - event.modeChange.modeId = mode->getId().value(); - event.modeChange.vsyncPeriod = mode->getVsyncPeriod(); + event.header = {DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE, + mode.modePtr->getPhysicalDisplayId(), systemTime()}; + event.modeChange.modeId = mode.modePtr->getId().value(); + event.modeChange.vsyncPeriod = mode.fps.getPeriodNsecs(); return event; } @@ -405,7 +405,7 @@ void EventThread::onHotplugReceived(PhysicalDisplayId displayId, bool connected) mCondition.notify_all(); } -void EventThread::onModeChanged(DisplayModePtr mode) { +void EventThread::onModeChanged(const scheduler::FrameRateMode& mode) { std::lock_guard lock(mMutex); mPendingEvents.push_back(makeModeChanged(mode)); diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h index 7a5a348db9..43c359833e 100644 --- a/services/surfaceflinger/Scheduler/EventThread.h +++ b/services/surfaceflinger/Scheduler/EventThread.h @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -134,7 +135,7 @@ public: virtual void onHotplugReceived(PhysicalDisplayId displayId, bool connected) = 0; // called when SF changes the active mode and apps needs to be notified about the change - virtual void onModeChanged(DisplayModePtr) = 0; + virtual void onModeChanged(const scheduler::FrameRateMode&) = 0; // called when SF updates the Frame Rate Override list virtual void onFrameRateOverridesChanged(PhysicalDisplayId displayId, @@ -185,7 +186,7 @@ public: void onHotplugReceived(PhysicalDisplayId displayId, bool connected) override; - void onModeChanged(DisplayModePtr) override; + void onModeChanged(const scheduler::FrameRateMode&) override; void onFrameRateOverridesChanged(PhysicalDisplayId displayId, std::vector overrides) override; diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 7f8f600d30..34f8df28e8 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -330,7 +330,7 @@ void Scheduler::onNonPrimaryDisplayModeChanged(ConnectionHandle handle, const Fr RETURN_IF_INVALID_HANDLE(handle); thread = mConnections[handle].thread.get(); } - thread->onModeChanged(mode.modePtr.get()); + thread->onModeChanged(mode); } size_t Scheduler::getEventThreadConnectionCount(ConnectionHandle handle) { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index bd3c0f12cc..cc4e0823cf 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1083,8 +1083,9 @@ status_t SurfaceFlinger::getDynamicDisplayInfo(const sp& displayToken, const PhysicalDisplayId displayId = snapshot.displayId(); - info->activeDisplayModeId = - display->refreshRateSelector().getActiveMode().modePtr->getId().value(); + const auto mode = display->refreshRateSelector().getActiveMode(); + info->activeDisplayModeId = mode.modePtr->getId().value(); + info->renderFrameRate = mode.fps.getValue(); info->activeColorMode = display->getCompositionDisplay()->getState().colorMode; info->hdrCapabilities = filterOut4k30(display->getHdrCapabilities()); @@ -1127,8 +1128,8 @@ void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request) return; } - const Fps renderFps = request.mode.fps; - const Fps displayFps = request.mode.modePtr->getFps(); + const auto mode = request.mode; + const bool emitEvent = request.emitEvent; switch (display->setDesiredActiveMode(DisplayDevice::ActiveModeInfo(std::move(request)))) { case DisplayDevice::DesiredActiveModeAction::InitiateDisplayModeSwitch: @@ -1136,21 +1137,22 @@ void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request) // Start receiving vsync samples now, so that we can detect a period // switch. - mScheduler->resyncToHardwareVsync(true, displayFps); + mScheduler->resyncToHardwareVsync(true, mode.modePtr->getFps()); // As we called to set period, we will call to onRefreshRateChangeCompleted once // VsyncController model is locked. modulateVsync(&VsyncModulator::onRefreshRateChangeInitiated); - updatePhaseConfiguration(renderFps); + updatePhaseConfiguration(mode.fps); mScheduler->setModeChangePending(true); break; case DisplayDevice::DesiredActiveModeAction::InitiateRenderRateSwitch: - mScheduler->setRenderRate(renderFps); - updatePhaseConfiguration(renderFps); - mRefreshRateStats->setRefreshRate(renderFps); + mScheduler->setRenderRate(mode.fps); + updatePhaseConfiguration(mode.fps); + mRefreshRateStats->setRefreshRate(mode.fps); + if (display->getPhysicalId() == mActiveDisplayId && emitEvent) { + mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, mode); + } - // TODO(b/259740021): send event to display manager about - // the render rate change break; case DisplayDevice::DesiredActiveModeAction::None: break; @@ -7405,6 +7407,7 @@ binder::Status SurfaceComposerAIDL::getDynamicDisplayInfo(const sp& dis } outInfo->activeDisplayModeId = info.activeDisplayModeId; + outInfo->renderFrameRate = info.renderFrameRate; outInfo->supportedColorModes.clear(); outInfo->supportedColorModes.reserve(info.supportedColorModes.size()); diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp index a5beaba286..dd87f9dadf 100644 --- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp +++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp @@ -633,9 +633,10 @@ TEST_F(EventThreadTest, postConfigChangedPrimary) { .setId(DisplayModeId(7)) .setVsyncPeriod(16666666) .build(); + const Fps fps = mode->getFps() / 2; - mThread->onModeChanged(mode); - expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 7, 16666666); + mThread->onModeChanged({fps, ftl::as_non_null(mode)}); + expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 7, fps.getPeriodNsecs()); } TEST_F(EventThreadTest, postConfigChangedExternal) { @@ -644,9 +645,10 @@ TEST_F(EventThreadTest, postConfigChangedExternal) { .setId(DisplayModeId(5)) .setVsyncPeriod(16666666) .build(); + const Fps fps = mode->getFps() / 2; - mThread->onModeChanged(mode); - expectConfigChangedEventReceivedByConnection(EXTERNAL_DISPLAY_ID, 5, 16666666); + mThread->onModeChanged({fps, ftl::as_non_null(mode)}); + expectConfigChangedEventReceivedByConnection(EXTERNAL_DISPLAY_ID, 5, fps.getPeriodNsecs()); } TEST_F(EventThreadTest, postConfigChangedPrimary64bit) { @@ -655,8 +657,9 @@ TEST_F(EventThreadTest, postConfigChangedPrimary64bit) { .setId(DisplayModeId(7)) .setVsyncPeriod(16666666) .build(); - mThread->onModeChanged(mode); - expectConfigChangedEventReceivedByConnection(DISPLAY_ID_64BIT, 7, 16666666); + const Fps fps = mode->getFps() / 2; + mThread->onModeChanged({fps, ftl::as_non_null(mode)}); + expectConfigChangedEventReceivedByConnection(DISPLAY_ID_64BIT, 7, fps.getPeriodNsecs()); } TEST_F(EventThreadTest, suppressConfigChanged) { @@ -669,9 +672,10 @@ TEST_F(EventThreadTest, suppressConfigChanged) { .setId(DisplayModeId(9)) .setVsyncPeriod(16666666) .build(); + const Fps fps = mode->getFps() / 2; - mThread->onModeChanged(mode); - expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 9, 16666666); + mThread->onModeChanged({fps, ftl::as_non_null(mode)}); + expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 9, fps.getPeriodNsecs()); auto args = suppressConnectionEventRecorder.waitForCall(); ASSERT_FALSE(args.has_value()); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp index b81693a0c2..074bf8cd1e 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp @@ -144,7 +144,8 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRe // Verify that the next commit will complete the mode change and send // a onModeChanged event to the framework. - EXPECT_CALL(*mAppEventThread, onModeChanged(kMode90)); + EXPECT_CALL(*mAppEventThread, + onModeChanged(scheduler::FrameRateMode{90_Hz, ftl::as_non_null(kMode90)})); mFlinger.commit(); Mock::VerifyAndClearExpectations(mAppEventThread); @@ -175,7 +176,8 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithoutRefres hal::HWConfigId(kModeId90.value()), _, _)) .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); - EXPECT_CALL(*mAppEventThread, onModeChanged(kMode90)); + EXPECT_CALL(*mAppEventThread, + onModeChanged(scheduler::FrameRateMode{90_Hz, ftl::as_non_null(kMode90)})); mFlinger.commit(); diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h index ded68069cb..f8567bd636 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h +++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h @@ -34,7 +34,7 @@ public: MOCK_METHOD0(onScreenReleased, void()); MOCK_METHOD0(onScreenAcquired, void()); MOCK_METHOD2(onHotplugReceived, void(PhysicalDisplayId, bool)); - MOCK_METHOD1(onModeChanged, void(DisplayModePtr)); + MOCK_METHOD1(onModeChanged, void(const scheduler::FrameRateMode &)); MOCK_METHOD2(onFrameRateOverridesChanged, void(PhysicalDisplayId, std::vector)); MOCK_CONST_METHOD1(dump, void(std::string&)); -- GitLab From e6512e1634051ac150c963230440ad6833ee02c0 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Mon, 28 Nov 2022 18:44:01 +0000 Subject: [PATCH 0573/1310] inputflinger_tests: Put `InputMapperTest` in its own file I would like to be able to put automated tests for the new `TouchpadEventMapper` in their own file, rather than InputReader_tests.cpp. To do this I'll need some of the test utilities in their own files, too. Bug: 251196347 Test: atest inputflinger_tests Change-Id: Ie524adb3431e4a0c59efe75be82abddacd676c21 --- services/inputflinger/tests/Android.bp | 1 + .../inputflinger/tests/InputMapperTest.cpp | 176 ++++++++++++++++ services/inputflinger/tests/InputMapperTest.h | 95 +++++++++ .../inputflinger/tests/InputReader_test.cpp | 193 +----------------- services/inputflinger/tests/TestConstants.h | 3 + 5 files changed, 276 insertions(+), 192 deletions(-) create mode 100644 services/inputflinger/tests/InputMapperTest.cpp create mode 100644 services/inputflinger/tests/InputMapperTest.h diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 2a8b359837..53d821f197 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -44,6 +44,7 @@ cc_test { "FakeInputReaderPolicy.cpp", "FakePointerController.cpp", "FocusResolver_test.cpp", + "InputMapperTest.cpp", "InputProcessor_test.cpp", "InputProcessorConverter_test.cpp", "InputDispatcher_test.cpp", diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp new file mode 100644 index 0000000000..3cd7c1b0f2 --- /dev/null +++ b/services/inputflinger/tests/InputMapperTest.cpp @@ -0,0 +1,176 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "InputMapperTest.h" + +#include +#include +#include + +namespace android { + +const char* InputMapperTest::DEVICE_NAME = "device"; +const char* InputMapperTest::DEVICE_LOCATION = "USB1"; +const ftl::Flags InputMapperTest::DEVICE_CLASSES = + ftl::Flags(0); // not needed for current tests + +void InputMapperTest::SetUp(ftl::Flags classes, int bus) { + mFakeEventHub = std::make_unique(); + mFakePolicy = sp::make(); + mFakeListener = std::make_unique(); + mReader = std::make_unique(mFakeEventHub, mFakePolicy, *mFakeListener); + mDevice = newDevice(DEVICE_ID, DEVICE_NAME, DEVICE_LOCATION, EVENTHUB_ID, classes, bus); + // Consume the device reset notification generated when adding a new device. + mFakeListener->assertNotifyDeviceResetWasCalled(); +} + +void InputMapperTest::SetUp() { + SetUp(DEVICE_CLASSES); +} + +void InputMapperTest::TearDown() { + mFakeListener.reset(); + mFakePolicy.clear(); +} + +void InputMapperTest::addConfigurationProperty(const char* key, const char* value) { + mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, key, value); +} + +std::list InputMapperTest::configureDevice(uint32_t changes) { + if (!changes || + (changes & + (InputReaderConfiguration::CHANGE_DISPLAY_INFO | + InputReaderConfiguration::CHANGE_POINTER_CAPTURE))) { + mReader->requestRefreshConfiguration(changes); + mReader->loopOnce(); + } + std::list out = + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), changes); + // Loop the reader to flush the input listener queue. + for (const NotifyArgs& args : out) { + mFakeListener->notify(args); + } + mReader->loopOnce(); + return out; +} + +std::shared_ptr InputMapperTest::newDevice(int32_t deviceId, const std::string& name, + const std::string& location, + int32_t eventHubId, + ftl::Flags classes, + int bus) { + InputDeviceIdentifier identifier; + identifier.name = name; + identifier.location = location; + identifier.bus = bus; + std::shared_ptr device = + std::make_shared(mReader->getContext(), deviceId, DEVICE_GENERATION, + identifier); + mReader->pushNextDevice(device); + mFakeEventHub->addDevice(eventHubId, name, classes, bus); + mReader->loopOnce(); + return device; +} + +void InputMapperTest::setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, + ui::Rotation orientation, + const std::string& uniqueId, + std::optional physicalPort, + ViewportType viewportType) { + mFakePolicy->addDisplayViewport(displayId, width, height, orientation, /* isActive= */ true, + uniqueId, physicalPort, viewportType); + configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); +} + +void InputMapperTest::clearViewports() { + mFakePolicy->clearViewports(); +} + +std::list InputMapperTest::process(InputMapper& mapper, nsecs_t when, nsecs_t readTime, + int32_t type, int32_t code, int32_t value) { + RawEvent event; + event.when = when; + event.readTime = readTime; + event.deviceId = mapper.getDeviceContext().getEventHubId(); + event.type = type; + event.code = code; + event.value = value; + std::list processArgList = mapper.process(&event); + for (const NotifyArgs& args : processArgList) { + mFakeListener->notify(args); + } + // Loop the reader to flush the input listener queue. + mReader->loopOnce(); + return processArgList; +} + +void InputMapperTest::resetMapper(InputMapper& mapper, nsecs_t when) { + const auto resetArgs = mapper.reset(when); + for (const auto args : resetArgs) { + mFakeListener->notify(args); + } + // Loop the reader to flush the input listener queue. + mReader->loopOnce(); +} + +std::list InputMapperTest::handleTimeout(InputMapper& mapper, nsecs_t when) { + std::list generatedArgs = mapper.timeoutExpired(when); + for (const NotifyArgs& args : generatedArgs) { + mFakeListener->notify(args); + } + // Loop the reader to flush the input listener queue. + mReader->loopOnce(); + return generatedArgs; +} + +void InputMapperTest::assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source, + float min, float max, float flat, float fuzz) { + const InputDeviceInfo::MotionRange* range = info.getMotionRange(axis, source); + ASSERT_TRUE(range != nullptr) << "Axis: " << axis << " Source: " << source; + ASSERT_EQ(axis, range->axis) << "Axis: " << axis << " Source: " << source; + ASSERT_EQ(source, range->source) << "Axis: " << axis << " Source: " << source; + ASSERT_NEAR(min, range->min, EPSILON) << "Axis: " << axis << " Source: " << source; + ASSERT_NEAR(max, range->max, EPSILON) << "Axis: " << axis << " Source: " << source; + ASSERT_NEAR(flat, range->flat, EPSILON) << "Axis: " << axis << " Source: " << source; + ASSERT_NEAR(fuzz, range->fuzz, EPSILON) << "Axis: " << axis << " Source: " << source; +} + +void InputMapperTest::assertPointerCoords(const PointerCoords& coords, float x, float y, + float pressure, float size, float touchMajor, + float touchMinor, float toolMajor, float toolMinor, + float orientation, float distance, + float scaledAxisEpsilon) { + ASSERT_NEAR(x, coords.getAxisValue(AMOTION_EVENT_AXIS_X), scaledAxisEpsilon); + ASSERT_NEAR(y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y), scaledAxisEpsilon); + ASSERT_NEAR(pressure, coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), EPSILON); + ASSERT_NEAR(size, coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE), EPSILON); + ASSERT_NEAR(touchMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), scaledAxisEpsilon); + ASSERT_NEAR(touchMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), scaledAxisEpsilon); + ASSERT_NEAR(toolMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), scaledAxisEpsilon); + ASSERT_NEAR(toolMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), scaledAxisEpsilon); + ASSERT_NEAR(orientation, coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION), EPSILON); + ASSERT_NEAR(distance, coords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE), EPSILON); +} + +void InputMapperTest::assertPosition(const FakePointerController& controller, float x, float y) { + float actualX, actualY; + controller.getPosition(&actualX, &actualY); + ASSERT_NEAR(x, actualX, 1); + ASSERT_NEAR(y, actualY, 1); +} + +} // namespace android diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h new file mode 100644 index 0000000000..b3401c37e2 --- /dev/null +++ b/services/inputflinger/tests/InputMapperTest.h @@ -0,0 +1,95 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +#include "FakeEventHub.h" +#include "FakeInputReaderPolicy.h" +#include "InstrumentedInputReader.h" +#include "TestConstants.h" +#include "TestInputListener.h" + +namespace android { + +class InputMapperTest : public testing::Test { +protected: + static const char* DEVICE_NAME; + static const char* DEVICE_LOCATION; + static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000; + static constexpr int32_t DEVICE_GENERATION = 2; + static constexpr int32_t DEVICE_CONTROLLER_NUMBER = 0; + static const ftl::Flags DEVICE_CLASSES; + static constexpr int32_t EVENTHUB_ID = 1; + + std::shared_ptr mFakeEventHub; + sp mFakePolicy; + std::unique_ptr mFakeListener; + std::unique_ptr mReader; + std::shared_ptr mDevice; + + virtual void SetUp(ftl::Flags classes, int bus = 0); + void SetUp() override; + void TearDown() override; + + void addConfigurationProperty(const char* key, const char* value); + std::list configureDevice(uint32_t changes); + std::shared_ptr newDevice(int32_t deviceId, const std::string& name, + const std::string& location, int32_t eventHubId, + ftl::Flags classes, int bus = 0); + template + T& addMapperAndConfigure(Args... args) { + T& mapper = mDevice->addMapper(EVENTHUB_ID, args...); + configureDevice(0); + std::list resetArgList = mDevice->reset(ARBITRARY_TIME); + resetArgList += mapper.reset(ARBITRARY_TIME); + // Loop the reader to flush the input listener queue. + for (const NotifyArgs& loopArgs : resetArgList) { + mFakeListener->notify(loopArgs); + } + mReader->loopOnce(); + return mapper; + } + + void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, + ui::Rotation orientation, const std::string& uniqueId, + std::optional physicalPort, + ViewportType viewportType); + void clearViewports(); + std::list process(InputMapper& mapper, nsecs_t when, nsecs_t readTime, int32_t type, + int32_t code, int32_t value); + void resetMapper(InputMapper& mapper, nsecs_t when); + + std::list handleTimeout(InputMapper& mapper, nsecs_t when); + + static void assertMotionRange(const InputDeviceInfo& info, int32_t axis, uint32_t source, + float min, float max, float flat, float fuzz); + static void assertPointerCoords(const PointerCoords& coords, float x, float y, float pressure, + float size, float touchMajor, float touchMinor, float toolMajor, + float toolMinor, float orientation, float distance, + float scaledAxisEpsilon = 1.f); + static void assertPosition(const FakePointerController& controller, float x, float y); +}; + +} // namespace android diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 58f1e1e035..eef56902c7 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -45,6 +45,7 @@ #include "FakeEventHub.h" #include "FakeInputReaderPolicy.h" #include "FakePointerController.h" +#include "InputMapperTest.h" #include "InstrumentedInputReader.h" #include "TestConstants.h" #include "android/hardware/input/InputDeviceCountryCode.h" @@ -92,9 +93,6 @@ static constexpr int32_t ACTION_POINTER_1_DOWN = static constexpr int32_t ACTION_POINTER_1_UP = AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); -// Error tolerance for floating point assertions. -static const float EPSILON = 0.001f; - // Minimum timestamp separation between subsequent input events from a Bluetooth device. static constexpr nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4); // Maximum smoothing time delta so that we don't generate events too far into the future. @@ -2507,195 +2505,6 @@ TEST_F(InputDeviceTest, GetBluetoothAddress) { ASSERT_EQ(DEVICE_BLUETOOTH_ADDRESS, *address); } -// --- InputMapperTest --- - -class InputMapperTest : public testing::Test { -protected: - static const char* DEVICE_NAME; - static const char* DEVICE_LOCATION; - static const int32_t DEVICE_ID; - static const int32_t DEVICE_GENERATION; - static const int32_t DEVICE_CONTROLLER_NUMBER; - static const ftl::Flags DEVICE_CLASSES; - static const int32_t EVENTHUB_ID; - - std::shared_ptr mFakeEventHub; - sp mFakePolicy; - std::unique_ptr mFakeListener; - std::unique_ptr mReader; - std::shared_ptr mDevice; - - virtual void SetUp(ftl::Flags classes, int bus = 0) { - mFakeEventHub = std::make_unique(); - mFakePolicy = sp::make(); - mFakeListener = std::make_unique(); - mReader = std::make_unique(mFakeEventHub, mFakePolicy, - *mFakeListener); - mDevice = newDevice(DEVICE_ID, DEVICE_NAME, DEVICE_LOCATION, EVENTHUB_ID, classes, bus); - // Consume the device reset notification generated when adding a new device. - mFakeListener->assertNotifyDeviceResetWasCalled(); - } - - void SetUp() override { - SetUp(DEVICE_CLASSES); - } - - void TearDown() override { - mFakeListener.reset(); - mFakePolicy.clear(); - } - - void addConfigurationProperty(const char* key, const char* value) { - mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, key, value); - } - - std::list configureDevice(uint32_t changes) { - if (!changes || - (changes & - (InputReaderConfiguration::CHANGE_DISPLAY_INFO | - InputReaderConfiguration::CHANGE_POINTER_CAPTURE))) { - mReader->requestRefreshConfiguration(changes); - mReader->loopOnce(); - } - std::list out = - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), changes); - // Loop the reader to flush the input listener queue. - for (const NotifyArgs& args : out) { - mFakeListener->notify(args); - } - mReader->loopOnce(); - return out; - } - - std::shared_ptr newDevice(int32_t deviceId, const std::string& name, - const std::string& location, int32_t eventHubId, - ftl::Flags classes, int bus = 0) { - InputDeviceIdentifier identifier; - identifier.name = name; - identifier.location = location; - identifier.bus = bus; - std::shared_ptr device = - std::make_shared(mReader->getContext(), deviceId, DEVICE_GENERATION, - identifier); - mReader->pushNextDevice(device); - mFakeEventHub->addDevice(eventHubId, name, classes, bus); - mReader->loopOnce(); - return device; - } - - template - T& addMapperAndConfigure(Args... args) { - T& mapper = mDevice->addMapper(EVENTHUB_ID, args...); - configureDevice(0); - std::list resetArgList = mDevice->reset(ARBITRARY_TIME); - resetArgList += mapper.reset(ARBITRARY_TIME); - // Loop the reader to flush the input listener queue. - for (const NotifyArgs& loopArgs : resetArgList) { - mFakeListener->notify(loopArgs); - } - mReader->loopOnce(); - return mapper; - } - - void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, - ui::Rotation orientation, const std::string& uniqueId, - std::optional physicalPort, - ViewportType viewportType) { - mFakePolicy->addDisplayViewport(displayId, width, height, orientation, true /*isActive*/, - uniqueId, physicalPort, viewportType); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); - } - - void clearViewports() { - mFakePolicy->clearViewports(); - } - - std::list process(InputMapper& mapper, nsecs_t when, nsecs_t readTime, int32_t type, - int32_t code, int32_t value) { - RawEvent event; - event.when = when; - event.readTime = readTime; - event.deviceId = mapper.getDeviceContext().getEventHubId(); - event.type = type; - event.code = code; - event.value = value; - std::list processArgList = mapper.process(&event); - for (const NotifyArgs& args : processArgList) { - mFakeListener->notify(args); - } - // Loop the reader to flush the input listener queue. - mReader->loopOnce(); - return processArgList; - } - - void resetMapper(InputMapper& mapper, nsecs_t when) { - const auto resetArgs = mapper.reset(when); - for (const auto& args : resetArgs) { - mFakeListener->notify(args); - } - // Loop the reader to flush the input listener queue. - mReader->loopOnce(); - } - - std::list handleTimeout(InputMapper& mapper, nsecs_t when) { - std::list generatedArgs = mapper.timeoutExpired(when); - for (const NotifyArgs& args : generatedArgs) { - mFakeListener->notify(args); - } - // Loop the reader to flush the input listener queue. - mReader->loopOnce(); - return generatedArgs; - } - - static void assertMotionRange(const InputDeviceInfo& info, - int32_t axis, uint32_t source, float min, float max, float flat, float fuzz) { - const InputDeviceInfo::MotionRange* range = info.getMotionRange(axis, source); - ASSERT_TRUE(range != nullptr) << "Axis: " << axis << " Source: " << source; - ASSERT_EQ(axis, range->axis) << "Axis: " << axis << " Source: " << source; - ASSERT_EQ(source, range->source) << "Axis: " << axis << " Source: " << source; - ASSERT_NEAR(min, range->min, EPSILON) << "Axis: " << axis << " Source: " << source; - ASSERT_NEAR(max, range->max, EPSILON) << "Axis: " << axis << " Source: " << source; - ASSERT_NEAR(flat, range->flat, EPSILON) << "Axis: " << axis << " Source: " << source; - ASSERT_NEAR(fuzz, range->fuzz, EPSILON) << "Axis: " << axis << " Source: " << source; - } - - static void assertPointerCoords(const PointerCoords& coords, float x, float y, float pressure, - float size, float touchMajor, float touchMinor, float toolMajor, - float toolMinor, float orientation, float distance, - float scaledAxisEpsilon = 1.f) { - ASSERT_NEAR(x, coords.getAxisValue(AMOTION_EVENT_AXIS_X), scaledAxisEpsilon); - ASSERT_NEAR(y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y), scaledAxisEpsilon); - ASSERT_NEAR(pressure, coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), EPSILON); - ASSERT_NEAR(size, coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE), EPSILON); - ASSERT_NEAR(touchMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), - scaledAxisEpsilon); - ASSERT_NEAR(touchMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), - scaledAxisEpsilon); - ASSERT_NEAR(toolMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), - scaledAxisEpsilon); - ASSERT_NEAR(toolMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), - scaledAxisEpsilon); - ASSERT_NEAR(orientation, coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION), EPSILON); - ASSERT_NEAR(distance, coords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE), EPSILON); - } - - static void assertPosition(const FakePointerController& controller, float x, float y) { - float actualX, actualY; - controller.getPosition(&actualX, &actualY); - ASSERT_NEAR(x, actualX, 1); - ASSERT_NEAR(y, actualY, 1); - } -}; - -const char* InputMapperTest::DEVICE_NAME = "device"; -const char* InputMapperTest::DEVICE_LOCATION = "USB1"; -const int32_t InputMapperTest::DEVICE_ID = END_RESERVED_ID + 1000; -const int32_t InputMapperTest::DEVICE_GENERATION = 2; -const int32_t InputMapperTest::DEVICE_CONTROLLER_NUMBER = 0; -const ftl::Flags InputMapperTest::DEVICE_CLASSES = - ftl::Flags(0); // not needed for current tests -const int32_t InputMapperTest::EVENTHUB_ID = 1; - // --- SwitchInputMapperTest --- class SwitchInputMapperTest : public InputMapperTest { diff --git a/services/inputflinger/tests/TestConstants.h b/services/inputflinger/tests/TestConstants.h index 8dc9d71392..27881f6f49 100644 --- a/services/inputflinger/tests/TestConstants.h +++ b/services/inputflinger/tests/TestConstants.h @@ -27,4 +27,7 @@ static constexpr std::chrono::duration WAIT_TIMEOUT = 100ms; static constexpr nsecs_t ARBITRARY_TIME = 1234; static constexpr nsecs_t READ_TIME = 4321; +// Error tolerance for floating point assertions. +static const float EPSILON = 0.001f; + } // namespace android -- GitLab From 29d2afd88d1f535c7a900ad6c0afdae16d908a14 Mon Sep 17 00:00:00 2001 From: Vladimir Komsiyski Date: Tue, 6 Dec 2022 13:24:10 +0100 Subject: [PATCH 0574/1310] Reference the runtime sensor range defined in the HAL from SensorService Fix: 259227294 Test: n/a Change-Id: I6b49fa23f89c24c3e868d553ae6afddaa0ed10c5 --- services/sensorservice/Android.bp | 2 +- services/sensorservice/SensorService.cpp | 9 ++++----- services/sensorservice/aidl/Android.bp | 2 +- services/sensorservice/aidl/fuzzer/Android.bp | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp index 0eeb820b87..11c56a8c56 100644 --- a/services/sensorservice/Android.bp +++ b/services/sensorservice/Android.bp @@ -76,7 +76,7 @@ cc_library { "libaidlcommonsupport", "android.hardware.sensors@1.0-convert", "android.hardware.sensors-V1-convert", - "android.hardware.sensors-V1-ndk", + "android.hardware.sensors-V2-ndk", ], generated_headers: ["framework-cppstream-protos"], diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp index 0c9fef5652..d341b71d3c 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include #include #include #include @@ -104,12 +105,10 @@ static const String16 sManageSensorsPermission("android.permission.MANAGE_SENSOR namespace { -// TODO(b/259227294): Move the sensor ranges to the HAL. int32_t nextRuntimeSensorHandle() { - static constexpr int32_t kRuntimeHandleBase = 0x5F000000; - static constexpr int32_t kRuntimeHandleEnd = 0x5FFFFFFF; - static int32_t nextHandle = kRuntimeHandleBase; - if (nextHandle == kRuntimeHandleEnd) { + using ::aidl::android::hardware::sensors::ISensors; + static int32_t nextHandle = ISensors::RUNTIME_SENSORS_HANDLE_BASE; + if (nextHandle == ISensors::RUNTIME_SENSORS_HANDLE_END) { return -1; } return nextHandle++; diff --git a/services/sensorservice/aidl/Android.bp b/services/sensorservice/aidl/Android.bp index 34d1de72f9..542fcaee8d 100644 --- a/services/sensorservice/aidl/Android.bp +++ b/services/sensorservice/aidl/Android.bp @@ -28,7 +28,7 @@ cc_library { "libbinder_ndk", "libsensor", "android.frameworks.sensorservice-V1-ndk", - "android.hardware.sensors-V1-ndk", + "android.hardware.sensors-V2-ndk", ], export_include_dirs: [ "include/", diff --git a/services/sensorservice/aidl/fuzzer/Android.bp b/services/sensorservice/aidl/fuzzer/Android.bp index 0d6e476e70..5301fe9931 100644 --- a/services/sensorservice/aidl/fuzzer/Android.bp +++ b/services/sensorservice/aidl/fuzzer/Android.bp @@ -18,7 +18,7 @@ cc_fuzz { "libpermission", "android.frameworks.sensorservice-V1-ndk", "android.hardware.sensors-V1-convert", - "android.hardware.sensors-V1-ndk", + "android.hardware.sensors-V2-ndk", "android.hardware.common-V2-ndk", "libsensor", "libfakeservicemanager", -- GitLab From cbba14c55c15ab3d41a4427fbb2406e6d0ade4b2 Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Mon, 10 Oct 2022 16:54:49 +0000 Subject: [PATCH 0575/1310] Add API to support key remappings on native side For adding support for modifier key remapping, we need to add native support save key remappings and send appropriate android key codes using KeyCharacterMap. Test: atest KeyboardLayoutChangeTest Bug: 252812993 Change-Id: Ib3efa866c6e6408dd11e97dfaf49feb92f48bc18 --- include/input/KeyCharacterMap.h | 20 ++- libs/input/KeyCharacterMap.cpp | 150 +++++++++++------- .../inputflinger/include/InputReaderBase.h | 3 + services/inputflinger/reader/EventHub.cpp | 30 +++- services/inputflinger/reader/InputDevice.cpp | 6 + services/inputflinger/reader/InputReader.cpp | 9 ++ .../inputflinger/reader/include/EventHub.h | 6 + .../inputflinger/reader/include/InputDevice.h | 6 + .../inputflinger/reader/include/InputReader.h | 2 + services/inputflinger/tests/FakeEventHub.cpp | 8 +- services/inputflinger/tests/FakeEventHub.h | 2 + .../inputflinger/tests/InputReader_test.cpp | 21 +++ .../tests/fuzzers/InputReaderFuzzer.cpp | 4 + .../tests/fuzzers/MapperHelpers.h | 1 + 14 files changed, 198 insertions(+), 70 deletions(-) diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h index dc928b806f..867a08955c 100644 --- a/include/input/KeyCharacterMap.h +++ b/include/input/KeyCharacterMap.h @@ -125,14 +125,21 @@ public: bool getEvents(int32_t deviceId, const char16_t* chars, size_t numChars, Vector& outEvents) const; + /* Maps an Android key code to another Android key code. This mapping is applied after scanCode + * and usageCodes are mapped to corresponding Android Keycode */ + void addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode); + /* Maps a scan code and usage code to a key code, in case this key map overrides * the mapping in some way. */ status_t mapKey(int32_t scanCode, int32_t usageCode, int32_t* outKeyCode) const; - /* Tries to find a replacement key code for a given key code and meta state - * in character map. */ - void tryRemapKey(int32_t scanCode, int32_t metaState, - int32_t* outKeyCode, int32_t* outMetaState) const; + /* Returns keycode after applying Android key code remapping defined in mKeyRemapping */ + int32_t applyKeyRemapping(int32_t fromKeyCode) const; + + /* Returns the pair after applying key behavior defined in the kcm file, + * that tries to find a replacement key code based on current meta state */ + std::pair applyKeyBehavior(int32_t keyCode, + int32_t metaState) const; #ifdef __linux__ /* Reads a key map from a parcel. */ @@ -227,8 +234,9 @@ private: std::string mLoadFileName; bool mLayoutOverlayApplied; - KeyedVector mKeysByScanCode; - KeyedVector mKeysByUsageCode; + std::map mKeyRemapping; + std::map mKeysByScanCode; + std::map mKeysByUsageCode; KeyCharacterMap(const std::string& filename); diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp index 422e6e09eb..fa5c41f657 100644 --- a/libs/input/KeyCharacterMap.cpp +++ b/libs/input/KeyCharacterMap.cpp @@ -43,7 +43,6 @@ // Enables debug output for mapping. #define DEBUG_MAPPING 0 - namespace android { static const char* WHITESPACE = " \t\r"; @@ -93,6 +92,7 @@ KeyCharacterMap::KeyCharacterMap(const KeyCharacterMap& other) : mType(other.mType), mLoadFileName(other.mLoadFileName), mLayoutOverlayApplied(other.mLayoutOverlayApplied), + mKeyRemapping(other.mKeyRemapping), mKeysByScanCode(other.mKeysByScanCode), mKeysByUsageCode(other.mKeysByUsageCode) { for (size_t i = 0; i < other.mKeys.size(); i++) { @@ -114,7 +114,7 @@ bool KeyCharacterMap::operator==(const KeyCharacterMap& other) const { if (mLayoutOverlayApplied != other.mLayoutOverlayApplied) { return false; } - if (mKeys.size() != other.mKeys.size() || + if (mKeys.size() != other.mKeys.size() || mKeyRemapping.size() != other.mKeyRemapping.size() || mKeysByScanCode.size() != other.mKeysByScanCode.size() || mKeysByUsageCode.size() != other.mKeysByUsageCode.size()) { return false; @@ -131,22 +131,9 @@ bool KeyCharacterMap::operator==(const KeyCharacterMap& other) const { } } - for (size_t i = 0; i < mKeysByScanCode.size(); i++) { - if (mKeysByScanCode.keyAt(i) != other.mKeysByScanCode.keyAt(i)) { - return false; - } - if (mKeysByScanCode.valueAt(i) != other.mKeysByScanCode.valueAt(i)) { - return false; - } - } - - for (size_t i = 0; i < mKeysByUsageCode.size(); i++) { - if (mKeysByUsageCode.keyAt(i) != other.mKeysByUsageCode.keyAt(i)) { - return false; - } - if (mKeysByUsageCode.valueAt(i) != other.mKeysByUsageCode.valueAt(i)) { - return false; - } + if (mKeyRemapping != other.mKeyRemapping || mKeysByScanCode != other.mKeysByScanCode || + mKeysByUsageCode != other.mKeysByUsageCode) { + return false; } return true; @@ -258,14 +245,12 @@ void KeyCharacterMap::combine(const KeyCharacterMap& overlay) { } } - for (size_t i = 0; i < overlay.mKeysByScanCode.size(); i++) { - mKeysByScanCode.replaceValueFor(overlay.mKeysByScanCode.keyAt(i), - overlay.mKeysByScanCode.valueAt(i)); + for (auto const& it : overlay.mKeysByScanCode) { + mKeysByScanCode.insert_or_assign(it.first, it.second); } - for (size_t i = 0; i < overlay.mKeysByUsageCode.size(); i++) { - mKeysByUsageCode.replaceValueFor(overlay.mKeysByUsageCode.keyAt(i), - overlay.mKeysByUsageCode.valueAt(i)); + for (auto const& it : overlay.mKeysByUsageCode) { + mKeysByUsageCode.insert_or_assign(it.first, it.second); } mLayoutOverlayApplied = true; } @@ -400,11 +385,26 @@ bool KeyCharacterMap::getEvents(int32_t deviceId, const char16_t* chars, size_t return true; } +void KeyCharacterMap::addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode) { + if (fromKeyCode == toKeyCode) { + mKeyRemapping.erase(fromKeyCode); +#if DEBUG_MAPPING + ALOGD("addKeyRemapping: Cleared remapping forKeyCode=%d ~ Result Successful.", fromKeyCode); +#endif + return; + } + mKeyRemapping.insert_or_assign(fromKeyCode, toKeyCode); +#if DEBUG_MAPPING + ALOGD("addKeyRemapping: fromKeyCode=%d, toKeyCode=%d ~ Result Successful.", fromKeyCode, + toKeyCode); +#endif +} + status_t KeyCharacterMap::mapKey(int32_t scanCode, int32_t usageCode, int32_t* outKeyCode) const { if (usageCode) { - ssize_t index = mKeysByUsageCode.indexOfKey(usageCode); - if (index >= 0) { - *outKeyCode = mKeysByUsageCode.valueAt(index); + const auto it = mKeysByUsageCode.find(usageCode); + if (it != mKeysByUsageCode.end()) { + *outKeyCode = it->second; #if DEBUG_MAPPING ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d.", scanCode, usageCode, *outKeyCode); @@ -413,9 +413,9 @@ status_t KeyCharacterMap::mapKey(int32_t scanCode, int32_t usageCode, int32_t* o } } if (scanCode) { - ssize_t index = mKeysByScanCode.indexOfKey(scanCode); - if (index >= 0) { - *outKeyCode = mKeysByScanCode.valueAt(index); + const auto it = mKeysByScanCode.find(scanCode); + if (it != mKeysByScanCode.end()) { + *outKeyCode = it->second; #if DEBUG_MAPPING ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d.", scanCode, usageCode, *outKeyCode); @@ -431,45 +431,59 @@ status_t KeyCharacterMap::mapKey(int32_t scanCode, int32_t usageCode, int32_t* o return NAME_NOT_FOUND; } -void KeyCharacterMap::tryRemapKey(int32_t keyCode, int32_t metaState, - int32_t *outKeyCode, int32_t *outMetaState) const { - *outKeyCode = keyCode; - *outMetaState = metaState; +int32_t KeyCharacterMap::applyKeyRemapping(int32_t fromKeyCode) const { + int32_t toKeyCode = fromKeyCode; - const Behavior* behavior = getKeyBehavior(keyCode, metaState); + const auto it = mKeyRemapping.find(fromKeyCode); + if (it != mKeyRemapping.end()) { + toKeyCode = it->second; + } +#if DEBUG_MAPPING + ALOGD("applyKeyRemapping: keyCode=%d ~ replacement keyCode=%d.", fromKeyCode, toKeyCode); +#endif + return toKeyCode; +} + +std::pair KeyCharacterMap::applyKeyBehavior(int32_t fromKeyCode, + int32_t fromMetaState) const { + int32_t toKeyCode = fromKeyCode; + int32_t toMetaState = fromMetaState; + + const Behavior* behavior = getKeyBehavior(fromKeyCode, fromMetaState); if (behavior != nullptr) { if (behavior->replacementKeyCode) { - *outKeyCode = behavior->replacementKeyCode; - int32_t newMetaState = metaState & ~behavior->metaState; + toKeyCode = behavior->replacementKeyCode; + toMetaState = fromMetaState & ~behavior->metaState; // Reset dependent meta states. if (behavior->metaState & AMETA_ALT_ON) { - newMetaState &= ~(AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON); + toMetaState &= ~(AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON); } if (behavior->metaState & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) { - newMetaState &= ~AMETA_ALT_ON; + toMetaState &= ~AMETA_ALT_ON; } if (behavior->metaState & AMETA_CTRL_ON) { - newMetaState &= ~(AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON); + toMetaState &= ~(AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON); } if (behavior->metaState & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) { - newMetaState &= ~AMETA_CTRL_ON; + toMetaState &= ~AMETA_CTRL_ON; } if (behavior->metaState & AMETA_SHIFT_ON) { - newMetaState &= ~(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON); + toMetaState &= ~(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON); } if (behavior->metaState & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) { - newMetaState &= ~AMETA_SHIFT_ON; + toMetaState &= ~AMETA_SHIFT_ON; } // ... and put universal bits back if needed - *outMetaState = normalizeMetaState(newMetaState); + toMetaState = normalizeMetaState(toMetaState); } } #if DEBUG_MAPPING - ALOGD("tryRemapKey: keyCode=%d, metaState=0x%08x ~ " - "replacement keyCode=%d, replacement metaState=0x%08x.", - keyCode, metaState, *outKeyCode, *outMetaState); + ALOGD("applyKeyBehavior: keyCode=%d, metaState=0x%08x ~ " + "replacement keyCode=%d, replacement metaState=0x%08x.", + fromKeyCode, fromMetaState, toKeyCode, toMetaState); #endif + return std::make_pair(toKeyCode, toMetaState); } bool KeyCharacterMap::getKey(int32_t keyCode, const Key** outKey) const { @@ -720,6 +734,18 @@ std::shared_ptr KeyCharacterMap::readFromParcel(Parcel* parcel) return nullptr; } } + size_t numKeyRemapping = parcel->readInt32(); + if (parcel->errorCheck()) { + return nullptr; + } + for (size_t i = 0; i < numKeyRemapping; i++) { + int32_t key = parcel->readInt32(); + int32_t value = parcel->readInt32(); + map->mKeyRemapping.insert_or_assign(key, value); + if (parcel->errorCheck()) { + return nullptr; + } + } size_t numKeysByScanCode = parcel->readInt32(); if (parcel->errorCheck()) { return nullptr; @@ -727,7 +753,7 @@ std::shared_ptr KeyCharacterMap::readFromParcel(Parcel* parcel) for (size_t i = 0; i < numKeysByScanCode; i++) { int32_t key = parcel->readInt32(); int32_t value = parcel->readInt32(); - map->mKeysByScanCode.add(key, value); + map->mKeysByScanCode.insert_or_assign(key, value); if (parcel->errorCheck()) { return nullptr; } @@ -739,7 +765,7 @@ std::shared_ptr KeyCharacterMap::readFromParcel(Parcel* parcel) for (size_t i = 0; i < numKeysByUsageCode; i++) { int32_t key = parcel->readInt32(); int32_t value = parcel->readInt32(); - map->mKeysByUsageCode.add(key, value); + map->mKeysByUsageCode.insert_or_assign(key, value); if (parcel->errorCheck()) { return nullptr; } @@ -773,17 +799,23 @@ void KeyCharacterMap::writeToParcel(Parcel* parcel) const { } parcel->writeInt32(0); } + size_t numKeyRemapping = mKeyRemapping.size(); + parcel->writeInt32(numKeyRemapping); + for (auto const& [fromAndroidKeyCode, toAndroidKeyCode] : mKeyRemapping) { + parcel->writeInt32(fromAndroidKeyCode); + parcel->writeInt32(toAndroidKeyCode); + } size_t numKeysByScanCode = mKeysByScanCode.size(); parcel->writeInt32(numKeysByScanCode); - for (size_t i = 0; i < numKeysByScanCode; i++) { - parcel->writeInt32(mKeysByScanCode.keyAt(i)); - parcel->writeInt32(mKeysByScanCode.valueAt(i)); + for (auto const& [fromScanCode, toAndroidKeyCode] : mKeysByScanCode) { + parcel->writeInt32(fromScanCode); + parcel->writeInt32(toAndroidKeyCode); } size_t numKeysByUsageCode = mKeysByUsageCode.size(); parcel->writeInt32(numKeysByUsageCode); - for (size_t i = 0; i < numKeysByUsageCode; i++) { - parcel->writeInt32(mKeysByUsageCode.keyAt(i)); - parcel->writeInt32(mKeysByUsageCode.valueAt(i)); + for (auto const& [fromUsageCode, toAndroidKeyCode] : mKeysByUsageCode) { + parcel->writeInt32(fromUsageCode); + parcel->writeInt32(toAndroidKeyCode); } } #endif // __linux__ @@ -950,9 +982,9 @@ status_t KeyCharacterMap::Parser::parseMapKey() { mapUsage ? "usage" : "scan code", codeToken.string()); return BAD_VALUE; } - KeyedVector& map = - mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode; - if (map.indexOfKey(code) >= 0) { + std::map& map = mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode; + const auto it = map.find(code); + if (it != map.end()) { ALOGE("%s: Duplicate entry for key %s '%s'.", mTokenizer->getLocation().string(), mapUsage ? "usage" : "scan code", codeToken.string()); return BAD_VALUE; @@ -971,7 +1003,7 @@ status_t KeyCharacterMap::Parser::parseMapKey() { ALOGD("Parsed map key %s: code=%d, keyCode=%d.", mapUsage ? "usage" : "scan code", code, keyCode); #endif - map.add(code, keyCode); + map.insert_or_assign(code, keyCode); return NO_ERROR; } diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index 6d6cefb0c0..b8a6dade34 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -88,6 +88,9 @@ public: virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) = 0; + virtual void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, + int32_t toKeyCode) const = 0; + virtual int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const = 0; /* Toggle Caps Lock */ diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index f2ea90c291..e26bc8c6dc 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -952,15 +952,19 @@ int32_t EventHub::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKey device->getKeyCharacterMap()->mapKey(scanCodes[0], 0 /*usageCode*/, &outKeyCode); switch (mapKeyRes) { case OK: - return outKeyCode; + break; case NAME_NOT_FOUND: // key character map doesn't re-map this scanCode, hence the keyCode remains the same - return locationKeyCode; + outKeyCode = locationKeyCode; + break; default: ALOGW("Failed to get key code for key location: Key character map returned error %s", statusToString(mapKeyRes).c_str()); - return AKEYCODE_UNKNOWN; + outKeyCode = AKEYCODE_UNKNOWN; + break; } + // Remap if there is a Key remapping added to the KCM and return the remapped key + return device->getKeyCharacterMap()->applyKeyRemapping(outKeyCode); } int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const { @@ -1023,6 +1027,18 @@ bool EventHub::markSupportedKeyCodes(int32_t deviceId, const std::vector kcm = device->getKeyCharacterMap(); + if (kcm) { + kcm->addKeyRemapping(fromKeyCode, toKeyCode); + } +} + status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState, int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const { std::scoped_lock _l(mLock); @@ -1048,7 +1064,13 @@ status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, if (status == NO_ERROR) { if (kcm) { - kcm->tryRemapKey(*outKeycode, metaState, outKeycode, outMetaState); + // Remap keys based on user-defined key remappings and key behavior defined in the + // corresponding kcm file + *outKeycode = kcm->applyKeyRemapping(*outKeycode); + + // Remap keys based on Key behavior defined in KCM file + std::tie(*outKeycode, *outMetaState) = + kcm->applyKeyBehavior(*outKeycode, metaState); } else { *outMetaState = metaState; } diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 150a8aac0d..e9a94b3a42 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -624,6 +624,12 @@ void InputDevice::updateMetaState(int32_t keyCode) { }); } +void InputDevice::addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode) { + for_each_subdevice([fromKeyCode, toKeyCode](auto& context) { + context.addKeyRemapping(fromKeyCode, toKeyCode); + }); +} + void InputDevice::bumpGeneration() { mGeneration = mContext->bumpGeneration(); } diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index f04a6469ea..57f679c022 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -650,6 +650,15 @@ bool InputReader::markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceM return result; } +void InputReader::addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const { + std::scoped_lock _l(mLock); + + InputDevice* device = findInputDeviceLocked(deviceId); + if (device != nullptr) { + device->addKeyRemapping(fromKeyCode, toKeyCode); + } +} + int32_t InputReader::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const { std::scoped_lock _l(mLock); diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index 42ca482a68..8a844b2088 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -262,6 +262,9 @@ public: virtual bool hasMscEvent(int32_t deviceId, int mscEvent) const = 0; + virtual void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, + int32_t toKeyCode) const = 0; + virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState, int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const = 0; @@ -460,6 +463,9 @@ public: bool hasMscEvent(int32_t deviceId, int mscEvent) const override final; + void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, + int32_t toKeyCode) const override final; + status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState, int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const override final; diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 439123bf1d..6fa21e5362 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -114,6 +114,8 @@ public: int32_t getMetaState(); void updateMetaState(int32_t keyCode); + void addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode); + void bumpGeneration(); [[nodiscard]] NotifyDeviceResetArgs notifyReset(nsecs_t when); @@ -278,6 +280,10 @@ public: inline bool hasMscEvent(int mscEvent) const { return mEventHub->hasMscEvent(mId, mscEvent); } + inline void addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode) const { + mEventHub->addKeyRemapping(mId, fromKeyCode, toKeyCode); + } + inline status_t mapKey(int32_t scanCode, int32_t usageCode, int32_t metaState, int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const { return mEventHub->mapKey(mId, scanCode, usageCode, metaState, outKeycode, outMetaState, diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h index 4f2503ad5c..e9c989a224 100644 --- a/services/inputflinger/reader/include/InputReader.h +++ b/services/inputflinger/reader/include/InputReader.h @@ -68,6 +68,8 @@ public: int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, int32_t keyCode) override; int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) override; + void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const override; + int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override; void toggleCapsLockState(int32_t deviceId) override; diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp index f6cf1cc304..289a7809cc 100644 --- a/services/inputflinger/tests/FakeEventHub.cpp +++ b/services/inputflinger/tests/FakeEventHub.cpp @@ -154,6 +154,11 @@ void FakeEventHub::addKeyCodeMapping(int32_t deviceId, int32_t fromKeyCode, int3 getDevice(deviceId)->keyCodeMapping.insert_or_assign(fromKeyCode, toKeyCode); } +void FakeEventHub::addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const { + Device* device = getDevice(deviceId); + device->keyRemapping.insert_or_assign(fromKeyCode, toKeyCode); +} + void FakeEventHub::addLed(int32_t deviceId, int32_t led, bool initialState) { getDevice(deviceId)->leds.add(led, initialState); } @@ -299,7 +304,8 @@ status_t FakeEventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageC const KeyInfo* key = getKey(device, scanCode, usageCode); if (key) { if (outKeycode) { - *outKeycode = key->keyCode; + auto it = device->keyRemapping.find(key->keyCode); + *outKeycode = it != device->keyRemapping.end() ? it->second : key->keyCode; } if (outFlags) { *outFlags = key->flags; diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h index 21cb2f1963..fb3c8596c7 100644 --- a/services/inputflinger/tests/FakeEventHub.h +++ b/services/inputflinger/tests/FakeEventHub.h @@ -59,6 +59,7 @@ class FakeEventHub : public EventHubInterface { KeyedVector absoluteAxisValue; KeyedVector keysByScanCode; KeyedVector keysByUsageCode; + std::unordered_map keyRemapping; KeyedVector leds; // fake mapping which would normally come from keyCharacterMap std::unordered_map keyCodeMapping; @@ -132,6 +133,7 @@ public: void addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t keyCode, uint32_t flags); void addKeyCodeMapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode); + void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const; void addVirtualKeyDefinition(int32_t deviceId, const VirtualKeyDefinition& definition); void addSensorAxis(int32_t deviceId, int32_t absCode, InputDeviceSensorType sensorType, diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index eef56902c7..4cc48f6fc3 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -2912,6 +2912,27 @@ TEST_F(KeyboardInputMapperTest, Process_SimpleKeyPress) { ASSERT_EQ(ARBITRARY_TIME, args.downTime); } +TEST_F(KeyboardInputMapperTest, Process_KeyRemapping) { + mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0); + mFakeEventHub->addKey(EVENTHUB_ID, KEY_B, 0, AKEYCODE_B, 0); + mFakeEventHub->addKeyRemapping(EVENTHUB_ID, AKEYCODE_A, AKEYCODE_B); + + KeyboardInputMapper& mapper = + addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); + + // Key down by scan code. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_A, 1); + NotifyKeyArgs args; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AKEYCODE_B, args.keyCode); + + // Key up by scan code. + process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_A, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); + ASSERT_EQ(AKEYCODE_B, args.keyCode); +} + /** * Ensure that the readTime is set to the time when the EV_KEY is received. */ diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp index 2eed9977be..057c15d6b8 100644 --- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp @@ -153,6 +153,10 @@ public: return reader->getLightPlayerId(deviceId, lightId); } + void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const { + reader->addKeyRemapping(deviceId, fromKeyCode, toKeyCode); + } + int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const { return reader->getKeyCodeForKeyLocation(deviceId, locationKeyCode); } diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index 445ed182fe..cd852d668c 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -211,6 +211,7 @@ public: int32_t getSwitchState(int32_t deviceId, int32_t sw) const override { return mFdp->ConsumeIntegral(); } + void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const override {} int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override { return mFdp->ConsumeIntegral(); } -- GitLab From e23e22d5d0af47ae56721254cb9d49ed75ed595a Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Tue, 6 Dec 2022 17:00:57 +0000 Subject: [PATCH 0576/1310] Add gestures library to whole_static_libs for input reader The next CL in this series causes linker errors for libinputreader_arc on aosp_bramble-userdebug that were previously hidden by compiler optimization. It seems that we need to explicitly include libchrome-gestures as a "whole" static library for libinputreader_static. Bug: 251196347 Test: lunch aosp_bramble-userdebug && m libinputreader_arc Change-Id: I8d9230af939ed2c2b6d922bcc4d5eaa12fb4a00d --- services/inputflinger/reader/Android.bp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index f5ac8de53d..c93443a025 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -116,6 +116,9 @@ cc_library_static { "libchrome-gestures_headers", "libinputreader_headers", ], + whole_static_libs: [ + "libchrome-gestures", + ], } cc_library_shared { -- GitLab From 898446255db4ca1ecd228cd6e746731f42bae832 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Fri, 2 Dec 2022 15:02:26 +0000 Subject: [PATCH 0577/1310] Use a sysprop to enable `TouchpadInputMapper` The new touchpad behaviour can now be toggled using the `setprop` command followed by a userspace reboot instead of having to change the const and rebuild. Bug: 251196347 Test: set the flag with setprop, check the gestures library logs appear Test: atest inputflinger_tests Change-Id: Ie4db00219e915fd059647d7541ef84e27e75bd86 --- services/inputflinger/reader/InputDevice.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 150a8aac0d..8d5377fc73 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -20,6 +20,9 @@ #include +#if defined(__ANDROID__) +#include +#endif #include #include "CursorInputMapper.h" @@ -209,9 +212,13 @@ void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) { } // Touchscreens and touchpad devices. - // TODO(b/251196347): replace this with a proper flag. - constexpr bool ENABLE_NEW_TOUCHPAD_STACK = false; - if (ENABLE_NEW_TOUCHPAD_STACK && classes.test(InputDeviceClass::TOUCHPAD) && + static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY = +#if defined(__ANDROID__) + sysprop::InputProperties::enable_touchpad_gestures_library().value_or(false); +#else + false; +#endif + if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) && classes.test(InputDeviceClass::TOUCH_MT)) { mappers.push_back(std::make_unique(*contextPtr)); } else if (classes.test(InputDeviceClass::TOUCH_MT)) { -- GitLab From 58120357b446182f8a2a047be49a9f35789c0d50 Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Tue, 6 Dec 2022 22:25:09 +0000 Subject: [PATCH 0578/1310] Add new KEYCODE_RECENT_APPS and corresponding mappings Test: manual Bug: 261621522 Change-Id: I282eb4b2b29a4790e287371ac9408120ff32997d --- include/android/keycodes.h | 2 ++ libs/input/InputEventLabels.cpp | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/include/android/keycodes.h b/include/android/keycodes.h index e5b5db2964..d4ba321057 100644 --- a/include/android/keycodes.h +++ b/include/android/keycodes.h @@ -829,6 +829,8 @@ enum { AKEYCODE_STYLUS_BUTTON_TERTIARY = 310, /** A button on the tail end of a stylus. */ AKEYCODE_STYLUS_BUTTON_TAIL = 311, + /** Key to open recent apps (a.k.a. Overview) */ + AKEYCODE_RECENT_APPS = 312, // NOTE: If you add a new keycode here you must also add it to several other files. // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list. diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp index b78fae3027..dd7cbb5ba9 100644 --- a/libs/input/InputEventLabels.cpp +++ b/libs/input/InputEventLabels.cpp @@ -339,7 +339,8 @@ namespace android { DEFINE_KEYCODE(STYLUS_BUTTON_PRIMARY), \ DEFINE_KEYCODE(STYLUS_BUTTON_SECONDARY), \ DEFINE_KEYCODE(STYLUS_BUTTON_TERTIARY), \ - DEFINE_KEYCODE(STYLUS_BUTTON_TAIL) + DEFINE_KEYCODE(STYLUS_BUTTON_TAIL), \ + DEFINE_KEYCODE(RECENT_APPS) // 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. -- GitLab From 04f8969172df975a30d6975a159bd2880a4003af Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 16 Nov 2022 23:21:05 +0000 Subject: [PATCH 0579/1310] SF: Introduce LayerHierarchy and LayerHierarchyBuilder LayerHierarchy allows us to navigate the layer tree in z-order, or depth first traversal. The hierarchy is created from a set of requested layer states and the structure itself does not contain additional layer states. This is in contrast to the existing model where the Layer class contained information about the hierarchy it belonged to. By breaking out of this model, we can make the layer class (now called RequestedLayerState) simpler and separate client states from the hierarchy. This also allows us to construct more flexible hierarchies that handles mirroring and relative parents more efficiently. When traversing the hierarchy, each node can be visited multiple times. For example, it could be visited as a child, a relative child, or a mirrored child. The class introduces the concept of variants to distinguish the traversal path. In the upcoming cl, this traversal will be used to compute the final composition state in z-order. Bug: 238781169 Test: presubmit Change-Id: I9902e521df2d47556e3a671e2134d5be8cd2a73f --- services/surfaceflinger/Android.bp | 1 + .../FrontEnd/LayerHierarchy.cpp | 472 ++++++++++++ .../surfaceflinger/FrontEnd/LayerHierarchy.h | 163 ++++ .../FrontEnd/LayerLifecycleManager.cpp | 62 +- .../FrontEnd/LayerLifecycleManager.h | 10 +- .../FrontEnd/RequestedLayerState.cpp | 19 +- .../FrontEnd/RequestedLayerState.h | 3 +- .../surfaceflinger/tests/unittests/Android.bp | 1 + .../tests/unittests/LayerHierarchyTest.cpp | 728 ++++++++++++++++++ 9 files changed, 1429 insertions(+), 30 deletions(-) create mode 100644 services/surfaceflinger/FrontEnd/LayerHierarchy.cpp create mode 100644 services/surfaceflinger/FrontEnd/LayerHierarchy.h create mode 100644 services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index b1bd705f19..9f7a687386 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -157,6 +157,7 @@ filegroup { "EventLog/EventLog.cpp", "FrontEnd/LayerCreationArgs.cpp", "FrontEnd/LayerHandle.cpp", + "FrontEnd/LayerHierarchy.cpp", "FrontEnd/LayerLifecycleManager.cpp", "FrontEnd/RequestedLayerState.cpp", "FrontEnd/TransactionHandler.cpp", diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp new file mode 100644 index 0000000000..db4e8af484 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp @@ -0,0 +1,472 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define ATRACE_TAG ATRACE_TAG_GRAPHICS +#undef LOG_TAG +#define LOG_TAG "LayerHierarchy" + +#include "LayerHierarchy.h" +#include "SwapErase.h" + +namespace android::surfaceflinger::frontend { + +namespace { +auto layerZCompare = [](const std::pair& lhs, + const std::pair& rhs) { + auto lhsLayer = lhs.first->getLayer(); + auto rhsLayer = rhs.first->getLayer(); + if (lhsLayer->layerStack != rhsLayer->layerStack) { + return lhsLayer->layerStack.id < rhsLayer->layerStack.id; + } + if (lhsLayer->z != rhsLayer->z) { + return lhsLayer->z < rhsLayer->z; + } + return lhsLayer->id < rhsLayer->id; +}; + +void insertSorted(std::vector>& vec, + std::pair value) { + auto it = std::upper_bound(vec.begin(), vec.end(), value, layerZCompare); + vec.insert(it, std::move(value)); +} +} // namespace + +LayerHierarchy::LayerHierarchy(RequestedLayerState* layer) : mLayer(layer) {} + +LayerHierarchy::LayerHierarchy(const LayerHierarchy& hierarchy, bool childrenOnly) { + mLayer = (childrenOnly) ? nullptr : hierarchy.mLayer; + mChildren = hierarchy.mChildren; +} + +void LayerHierarchy::traverse(const Visitor& visitor, + LayerHierarchy::TraversalPath& traversalPath) const { + if (mLayer) { + bool breakTraversal = !visitor(*this, traversalPath); + if (breakTraversal) { + return; + } + } + if (traversalPath.hasRelZLoop()) { + LOG_ALWAYS_FATAL("Found relative z loop layerId:%d", traversalPath.invalidRelativeRootId); + } + for (auto& [child, childVariant] : mChildren) { + ScopedAddToTraversalPath addChildToTraversalPath(traversalPath, child->mLayer->id, + childVariant); + child->traverse(visitor, traversalPath); + } +} + +void LayerHierarchy::traverseInZOrder(const Visitor& visitor, + LayerHierarchy::TraversalPath& traversalPath) const { + bool traverseThisLayer = (mLayer != nullptr); + for (auto it = mChildren.begin(); it < mChildren.end(); it++) { + auto& [child, childVariant] = *it; + if (traverseThisLayer && child->getLayer()->z >= 0) { + bool breakTraversal = !visitor(*this, traversalPath); + if (breakTraversal) { + return; + } + traverseThisLayer = false; + } + if (childVariant == LayerHierarchy::Variant::Detached) { + continue; + } + ScopedAddToTraversalPath addChildToTraversalPath(traversalPath, child->mLayer->id, + childVariant); + child->traverseInZOrder(visitor, traversalPath); + } + + if (traverseThisLayer) { + visitor(*this, traversalPath); + } +} + +void LayerHierarchy::addChild(LayerHierarchy* child, LayerHierarchy::Variant variant) { + insertSorted(mChildren, {child, variant}); +} + +void LayerHierarchy::removeChild(LayerHierarchy* child) { + auto it = std::find_if(mChildren.begin(), mChildren.end(), + [child](const std::pair& x) { + return x.first == child; + }); + if (it == mChildren.end()) { + LOG_ALWAYS_FATAL("Could not find child!"); + } + mChildren.erase(it); +} + +void LayerHierarchy::sortChildrenByZOrder() { + std::sort(mChildren.begin(), mChildren.end(), layerZCompare); +} + +void LayerHierarchy::updateChild(LayerHierarchy* hierarchy, LayerHierarchy::Variant variant) { + auto it = std::find_if(mChildren.begin(), mChildren.end(), + [hierarchy](std::pair& child) { + return child.first == hierarchy; + }); + if (it == mChildren.end()) { + LOG_ALWAYS_FATAL("Could not find child!"); + } else { + it->second = variant; + } +} + +const RequestedLayerState* LayerHierarchy::getLayer() const { + return mLayer; +} + +std::string LayerHierarchy::getDebugStringShort() const { + std::string debug = "LayerHierarchy{"; + debug += ((mLayer) ? mLayer->getDebugStringShort() : "root") + " "; + if (mChildren.empty()) { + debug += "no children"; + } else { + debug += std::to_string(mChildren.size()) + " children"; + } + return debug + "}"; +} + +std::string LayerHierarchy::getDebugString(const char* prefix) const { + std::string debug = prefix + getDebugStringShort(); + for (auto& [child, childVariant] : mChildren) { + std::string childPrefix = " " + std::string(prefix) + " " + std::to_string(childVariant); + debug += "\n" + child->getDebugString(childPrefix.c_str()); + } + return debug; +} + +bool LayerHierarchy::hasRelZLoop(uint32_t& outInvalidRelativeRoot) const { + outInvalidRelativeRoot = UNASSIGNED_LAYER_ID; + traverse([&outInvalidRelativeRoot](const LayerHierarchy&, + const LayerHierarchy::TraversalPath& traversalPath) -> bool { + if (traversalPath.hasRelZLoop()) { + outInvalidRelativeRoot = traversalPath.invalidRelativeRootId; + return false; + } + return true; + }); + return outInvalidRelativeRoot != UNASSIGNED_LAYER_ID; +} + +LayerHierarchyBuilder::LayerHierarchyBuilder( + const std::vector>& layers) { + mHierarchies.reserve(layers.size()); + mLayerIdToHierarchy.reserve(layers.size()); + for (auto& layer : layers) { + mHierarchies.emplace_back(std::make_unique(layer.get())); + mLayerIdToHierarchy[layer->id] = mHierarchies.back().get(); + } + for (const auto& layer : layers) { + onLayerAdded(layer.get()); + } + detachHierarchyFromRelativeParent(&mOffscreenRoot); +} + +void LayerHierarchyBuilder::attachToParent(LayerHierarchy* hierarchy) { + auto layer = hierarchy->mLayer; + LayerHierarchy::Variant type = layer->hasValidRelativeParent() + ? LayerHierarchy::Variant::Detached + : LayerHierarchy::Variant::Attached; + + LayerHierarchy* parent; + + if (layer->parentId != UNASSIGNED_LAYER_ID) { + parent = getHierarchyFromId(layer->parentId); + } else if (layer->canBeRoot) { + parent = &mRoot; + } else { + parent = &mOffscreenRoot; + } + parent->addChild(hierarchy, type); + hierarchy->mParent = parent; +} + +void LayerHierarchyBuilder::detachFromParent(LayerHierarchy* hierarchy) { + hierarchy->mParent->removeChild(hierarchy); + hierarchy->mParent = nullptr; +} + +void LayerHierarchyBuilder::attachToRelativeParent(LayerHierarchy* hierarchy) { + auto layer = hierarchy->mLayer; + if (!layer->hasValidRelativeParent() || hierarchy->mRelativeParent) { + return; + } + + if (layer->relativeParentId != UNASSIGNED_LAYER_ID) { + hierarchy->mRelativeParent = getHierarchyFromId(layer->relativeParentId); + } else { + hierarchy->mRelativeParent = &mOffscreenRoot; + } + hierarchy->mRelativeParent->addChild(hierarchy, LayerHierarchy::Variant::Relative); + hierarchy->mParent->updateChild(hierarchy, LayerHierarchy::Variant::Detached); +} + +void LayerHierarchyBuilder::detachFromRelativeParent(LayerHierarchy* hierarchy) { + if (hierarchy->mRelativeParent) { + hierarchy->mRelativeParent->removeChild(hierarchy); + } + hierarchy->mRelativeParent = nullptr; + hierarchy->mParent->updateChild(hierarchy, LayerHierarchy::Variant::Attached); +} + +void LayerHierarchyBuilder::attachHierarchyToRelativeParent(LayerHierarchy* root) { + if (root->mLayer) { + attachToRelativeParent(root); + } + for (auto& [child, childVariant] : root->mChildren) { + if (childVariant == LayerHierarchy::Variant::Detached || + childVariant == LayerHierarchy::Variant::Attached) { + attachHierarchyToRelativeParent(child); + } + } +} + +void LayerHierarchyBuilder::detachHierarchyFromRelativeParent(LayerHierarchy* root) { + if (root->mLayer) { + detachFromRelativeParent(root); + } + for (auto& [child, childVariant] : root->mChildren) { + if (childVariant == LayerHierarchy::Variant::Detached || + childVariant == LayerHierarchy::Variant::Attached) { + detachHierarchyFromRelativeParent(child); + } + } +} + +void LayerHierarchyBuilder::onLayerAdded(RequestedLayerState* layer) { + LayerHierarchy* hierarchy = getHierarchyFromId(layer->id); + attachToParent(hierarchy); + attachToRelativeParent(hierarchy); + + if (layer->mirrorId != UNASSIGNED_LAYER_ID) { + LayerHierarchy* mirror = getHierarchyFromId(layer->mirrorId); + hierarchy->addChild(mirror, LayerHierarchy::Variant::Mirror); + } +} + +void LayerHierarchyBuilder::onLayerDestroyed(RequestedLayerState* layer) { + LayerHierarchy* hierarchy = getHierarchyFromId(layer->id, /*crashOnFailure=*/false); + if (!hierarchy) { + // Layer was never part of the hierarchy if it was created and destroyed in the same + // transaction. + return; + } + // detach from parent + detachFromRelativeParent(hierarchy); + detachFromParent(hierarchy); + + // detach children + for (auto& [child, variant] : hierarchy->mChildren) { + if (variant == LayerHierarchy::Variant::Attached || + variant == LayerHierarchy::Variant::Detached) { + mOffscreenRoot.addChild(child, LayerHierarchy::Variant::Attached); + child->mParent = &mOffscreenRoot; + } else if (variant == LayerHierarchy::Variant::Relative) { + mOffscreenRoot.addChild(child, LayerHierarchy::Variant::Attached); + child->mRelativeParent = &mOffscreenRoot; + } + } + + swapErase(mHierarchies, [hierarchy](std::unique_ptr& layerHierarchy) { + return layerHierarchy.get() == hierarchy; + }); + mLayerIdToHierarchy.erase(layer->id); +} + +void LayerHierarchyBuilder::updateMirrorLayer(RequestedLayerState* layer) { + LayerHierarchy* hierarchy = getHierarchyFromId(layer->id); + auto it = hierarchy->mChildren.begin(); + while (it != hierarchy->mChildren.end()) { + if (it->second == LayerHierarchy::Variant::Mirror) { + hierarchy->mChildren.erase(it); + break; + } + it++; + } + + if (layer->mirrorId != UNASSIGNED_LAYER_ID) { + hierarchy->addChild(getHierarchyFromId(layer->mirrorId), LayerHierarchy::Variant::Mirror); + } +} + +void LayerHierarchyBuilder::update( + const std::vector>& layers, + const std::vector>& destroyedLayers) { + // rebuild map + for (auto& layer : layers) { + if (layer->changes.test(RequestedLayerState::Changes::Created)) { + mHierarchies.emplace_back(std::make_unique(layer.get())); + mLayerIdToHierarchy[layer->id] = mHierarchies.back().get(); + } + } + + for (auto& layer : layers) { + if (layer->changes.get() == 0) { + continue; + } + if (layer->changes.test(RequestedLayerState::Changes::Created)) { + onLayerAdded(layer.get()); + continue; + } + LayerHierarchy* hierarchy = getHierarchyFromId(layer->id); + if (layer->changes.test(RequestedLayerState::Changes::Parent)) { + detachFromParent(hierarchy); + attachToParent(hierarchy); + } + if (layer->changes.test(RequestedLayerState::Changes::RelativeParent)) { + detachFromRelativeParent(hierarchy); + attachToRelativeParent(hierarchy); + } + if (layer->changes.test(RequestedLayerState::Changes::Z)) { + hierarchy->mParent->sortChildrenByZOrder(); + if (hierarchy->mRelativeParent) { + hierarchy->mRelativeParent->sortChildrenByZOrder(); + } + } + if (layer->changes.test(RequestedLayerState::Changes::Mirror)) { + updateMirrorLayer(layer.get()); + } + } + + for (auto& layer : destroyedLayers) { + onLayerDestroyed(layer.get()); + } + // When moving from onscreen to offscreen and vice versa, we need to attach and detach + // from our relative parents. This walks down both trees to do so. We can optimize this + // further by tracking onscreen, offscreen state in LayerHierarchy. + detachHierarchyFromRelativeParent(&mOffscreenRoot); + attachHierarchyToRelativeParent(&mRoot); +} + +const LayerHierarchy& LayerHierarchyBuilder::getHierarchy() const { + return mRoot; +} + +const LayerHierarchy& LayerHierarchyBuilder::getOffscreenHierarchy() const { + return mOffscreenRoot; +} + +std::string LayerHierarchyBuilder::getDebugString(uint32_t layerId, uint32_t depth) const { + if (depth > 10) return "too deep, loop?"; + if (layerId == UNASSIGNED_LAYER_ID) return ""; + auto it = mLayerIdToHierarchy.find(layerId); + if (it == mLayerIdToHierarchy.end()) return "not found"; + + LayerHierarchy* hierarchy = it->second; + if (!hierarchy->mLayer) return "none"; + + std::string debug = + "[" + std::to_string(hierarchy->mLayer->id) + "] " + hierarchy->mLayer->name; + if (hierarchy->mRelativeParent) { + debug += " Relative:" + hierarchy->mRelativeParent->getDebugStringShort(); + } + if (hierarchy->mParent) { + debug += " Parent:" + hierarchy->mParent->getDebugStringShort(); + } + return debug; +} + +LayerHierarchy LayerHierarchyBuilder::getPartialHierarchy(uint32_t layerId, + bool childrenOnly) const { + auto it = mLayerIdToHierarchy.find(layerId); + if (it == mLayerIdToHierarchy.end()) return {nullptr}; + + LayerHierarchy hierarchy(*it->second, childrenOnly); + return hierarchy; +} + +LayerHierarchy* LayerHierarchyBuilder::getHierarchyFromId(uint32_t layerId, bool crashOnFailure) { + auto it = mLayerIdToHierarchy.find(layerId); + if (it == mLayerIdToHierarchy.end()) { + if (crashOnFailure) { + LOG_ALWAYS_FATAL("Could not find hierarchy for layer id %d", layerId); + } + return nullptr; + }; + + return it->second; +} + +LayerHierarchy::TraversalPath LayerHierarchy::TraversalPath::ROOT_TRAVERSAL_ID = + {.id = UNASSIGNED_LAYER_ID, .variant = LayerHierarchy::Attached}; + +std::string LayerHierarchy::TraversalPath::toString() const { + std::string debugString = "TraversalPath{.id = " + std::to_string(id); + + if (!mirrorRootIds.empty()) { + debugString += ", .mirrorRootIds="; + for (auto rootId : mirrorRootIds) { + debugString += std::to_string(rootId) + ","; + } + } + + if (!relativeRootIds.empty()) { + debugString += ", .relativeRootIds="; + for (auto rootId : relativeRootIds) { + debugString += std::to_string(rootId) + ","; + } + } + + if (hasRelZLoop()) { + debugString += ", hasRelZLoop=true invalidRelativeRootId="; + debugString += std::to_string(invalidRelativeRootId) + ","; + } + + debugString += "}"; + return debugString; +} + +// Helper class to update a passed in TraversalPath when visiting a child. When the object goes out +// of scope the TraversalPath is reset to its original state. +LayerHierarchy::ScopedAddToTraversalPath::ScopedAddToTraversalPath(TraversalPath& traversalPath, + uint32_t layerId, + LayerHierarchy::Variant variant) + : mTraversalPath(traversalPath), + mParentId(traversalPath.id), + mParentVariant(traversalPath.variant) { + // Update the traversal id with the child layer id and variant. Parent id and variant are + // stored to reset the id upon destruction. + traversalPath.id = layerId; + traversalPath.variant = variant; + if (variant == LayerHierarchy::Variant::Mirror) { + traversalPath.mirrorRootIds.emplace_back(layerId); + } + if (variant == LayerHierarchy::Variant::Relative) { + if (std::find(traversalPath.relativeRootIds.begin(), traversalPath.relativeRootIds.end(), + layerId) != traversalPath.relativeRootIds.end()) { + traversalPath.invalidRelativeRootId = layerId; + } + traversalPath.relativeRootIds.emplace_back(layerId); + } +} +LayerHierarchy::ScopedAddToTraversalPath::~ScopedAddToTraversalPath() { + // Reset the traversal id to its original parent state using the state that was saved in + // the constructor. + if (mTraversalPath.variant == LayerHierarchy::Variant::Mirror) { + mTraversalPath.mirrorRootIds.pop_back(); + } + if (mTraversalPath.variant == LayerHierarchy::Variant::Relative) { + mTraversalPath.relativeRootIds.pop_back(); + } + if (mTraversalPath.invalidRelativeRootId == mTraversalPath.id) { + mTraversalPath.invalidRelativeRootId = UNASSIGNED_LAYER_ID; + } + mTraversalPath.id = mParentId; + mTraversalPath.variant = mParentVariant; +} + +} // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h new file mode 100644 index 0000000000..f83a859de7 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h @@ -0,0 +1,163 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "FrontEnd/LayerCreationArgs.h" +#include "RequestedLayerState.h" +#include "ftl/small_vector.h" + +namespace android::surfaceflinger::frontend { +class LayerHierarchyBuilder; + +// LayerHierarchy allows us to navigate the layer hierarchy in z-order, or depth first traversal. +// The hierarchy is created from a set of RequestedLayerStates. The hierarchy itself does not +// contain additional states. Instead, it is a representation of RequestedLayerStates as a graph. +// +// Each node in the hierarchy can be visited by multiple parents (making this a graph). While +// traversing the hierarchy, a new concept called Variant can be used to understand the +// relationship of the layer to its parent. The following variants are possible: +// Attached - child of the parent +// Detached - child of the parent but currently relative parented to another layer +// Relative - relative child of the parent +// Mirror - mirrored from another layer +// +// By representing the hierarchy as a graph, we can represent mirrored layer hierarchies without +// cloning the layer requested state. The mirrored hierarchy and its corresponding +// RequestedLayerStates are kept in sync because the mirrored hierarchy does not clone any +// states. +class LayerHierarchy { +public: + enum Variant { + Attached, + Detached, + Relative, + Mirror, + }; + // Represents a unique path to a node. + struct TraversalPath { + uint32_t id; + LayerHierarchy::Variant variant; + // Mirrored layers can have a different geometry than their parents so we need to track + // the mirror roots in the traversal. + ftl::SmallVector mirrorRootIds; + // Relative layers can be visited twice, once by their parent and then once again by + // their relative parent. We keep track of the roots here to detect any loops in the + // hierarchy. If a relative root already exists in the list while building the + // TraversalPath, it means that somewhere in the hierarchy two layers are relatively + // parented to each other. + ftl::SmallVector relativeRootIds; + // First duplicate relative root id found. If this is a valid layer id that means we are + // in a loop. + uint32_t invalidRelativeRootId = UNASSIGNED_LAYER_ID; + bool hasRelZLoop() const { return invalidRelativeRootId != UNASSIGNED_LAYER_ID; } + bool isRelative() { return !relativeRootIds.empty(); } + + bool operator==(const TraversalPath& other) const { + return id == other.id && mirrorRootIds == other.mirrorRootIds; + } + std::string toString() const; + + static TraversalPath ROOT_TRAVERSAL_ID; + }; + + // Helper class to add nodes to an existing traversal id and removes the + // node when it goes out of scope. + class ScopedAddToTraversalPath { + public: + ScopedAddToTraversalPath(TraversalPath& traversalPath, uint32_t layerId, + LayerHierarchy::Variant variantArg); + ~ScopedAddToTraversalPath(); + + private: + TraversalPath& mTraversalPath; + uint32_t mParentId; + LayerHierarchy::Variant mParentVariant; + }; + LayerHierarchy(RequestedLayerState* layer); + + // Visitor function that provides the hierarchy node and a traversal id which uniquely + // identifies how was visited. The hierarchy contains a pointer to the RequestedLayerState. + // Return false to stop traversing down the hierarchy. + typedef std::function + Visitor; + + // Traverse the hierarchy and visit all child variants. + void traverse(const Visitor& visitor) const { + traverse(visitor, TraversalPath::ROOT_TRAVERSAL_ID); + } + + // Traverse the hierarchy in z-order, skipping children that have relative parents. + void traverseInZOrder(const Visitor& visitor) const { + traverseInZOrder(visitor, TraversalPath::ROOT_TRAVERSAL_ID); + } + + const RequestedLayerState* getLayer() const; + std::string getDebugString(const char* prefix = "") const; + std::string getDebugStringShort() const; + // Traverse the hierarchy and return true if loops are found. The outInvalidRelativeRoot + // will contain the first relative root that was visited twice in a traversal. + bool hasRelZLoop(uint32_t& outInvalidRelativeRoot) const; + std::vector> mChildren; + +private: + friend LayerHierarchyBuilder; + LayerHierarchy(const LayerHierarchy& hierarchy, bool childrenOnly); + void addChild(LayerHierarchy*, LayerHierarchy::Variant); + void removeChild(LayerHierarchy*); + void sortChildrenByZOrder(); + void updateChild(LayerHierarchy*, LayerHierarchy::Variant); + void traverseInZOrder(const Visitor& visitor, LayerHierarchy::TraversalPath& parent) const; + void traverse(const Visitor& visitor, LayerHierarchy::TraversalPath& parent) const; + + const RequestedLayerState* mLayer; + LayerHierarchy* mParent = nullptr; + LayerHierarchy* mRelativeParent = nullptr; +}; + +// Given a list of RequestedLayerState, this class will build a root hierarchy and an +// offscreen hierarchy. The builder also has an update method which can update an existing +// hierarchy from a list of RequestedLayerState and associated change flags. +class LayerHierarchyBuilder { +public: + LayerHierarchyBuilder(const std::vector>&); + void update(const std::vector>& layers, + const std::vector>& destroyedLayers); + LayerHierarchy getPartialHierarchy(uint32_t, bool childrenOnly) const; + const LayerHierarchy& getHierarchy() const; + const LayerHierarchy& getOffscreenHierarchy() const; + std::string getDebugString(uint32_t layerId, uint32_t depth = 0) const; + +private: + void onLayerAdded(RequestedLayerState* layer); + void attachToParent(LayerHierarchy*); + void detachFromParent(LayerHierarchy*); + void attachToRelativeParent(LayerHierarchy*); + void detachFromRelativeParent(LayerHierarchy*); + void attachHierarchyToRelativeParent(LayerHierarchy*); + void detachHierarchyFromRelativeParent(LayerHierarchy*); + + void onLayerDestroyed(RequestedLayerState* layer); + void updateMirrorLayer(RequestedLayerState* layer); + LayerHierarchy* getHierarchyFromId(uint32_t layerId, bool crashOnFailure = true); + std::unordered_map mLayerIdToHierarchy; + std::vector> mHierarchies; + LayerHierarchy mRoot{nullptr}; + LayerHierarchy mOffscreenRoot{nullptr}; +}; + +} // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp index 1108246e47..d869ab5451 100644 --- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp +++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp @@ -42,10 +42,10 @@ void LayerLifecycleManager::addLayers(std::vectorsecond.owner.getDebugString().c_str()); } - linkLayer(layer.parentId, layer.id); - linkLayer(layer.relativeParentId, layer.id); - linkLayer(layer.mirrorId, layer.id); - linkLayer(layer.touchCropId, layer.id); + layer.parentId = linkLayer(layer.parentId, layer.id); + layer.relativeParentId = linkLayer(layer.relativeParentId, layer.id); + layer.mirrorId = linkLayer(layer.mirrorId, layer.id); + layer.touchCropId = linkLayer(layer.touchCropId, layer.id); mLayers.emplace_back(std::move(newLayer)); } @@ -83,10 +83,10 @@ void LayerLifecycleManager::onHandlesDestroyed(const std::vector& dest RequestedLayerState& layer = it->second.owner; - unlinkLayer(layer.parentId, layer.id); - unlinkLayer(layer.relativeParentId, layer.id); - unlinkLayer(layer.mirrorId, layer.id); - unlinkLayer(layer.touchCropId, layer.id); + layer.parentId = unlinkLayer(layer.parentId, layer.id); + layer.relativeParentId = unlinkLayer(layer.relativeParentId, layer.id); + layer.mirrorId = unlinkLayer(layer.mirrorId, layer.id); + layer.touchCropId = unlinkLayer(layer.touchCropId, layer.id); auto& references = it->second.references; for (uint32_t linkedLayerId : references) { @@ -195,15 +195,15 @@ void LayerLifecycleManager::applyTransactions(const std::vectorparentId) { unlinkLayer(oldParentId, layer->id); - linkLayer(layer->parentId, layer->id); + layer->parentId = linkLayer(layer->parentId, layer->id); } if (oldRelativeParentId != layer->relativeParentId) { unlinkLayer(oldRelativeParentId, layer->id); - linkLayer(layer->relativeParentId, layer->id); + layer->relativeParentId = linkLayer(layer->relativeParentId, layer->id); } if (oldTouchCropId != layer->touchCropId) { unlinkLayer(oldTouchCropId, layer->id); - linkLayer(layer->touchCropId, layer->id); + layer->touchCropId = linkLayer(layer->touchCropId, layer->id); } mGlobalChanges |= layer->changes & @@ -283,26 +283,28 @@ std::vector* LayerLifecycleManager::getLinkedLayersFromId(uint32_t id) return &it->second.references; } -void LayerLifecycleManager::linkLayer(uint32_t layerId, uint32_t layerToLink) { - if (layerToLink && layerId != UNASSIGNED_LAYER_ID) { - std::vector* linkedLayers = getLinkedLayersFromId(layerId); - if (!linkedLayers) { - LOG_ALWAYS_FATAL("Could not find layer id %d to link %d", layerId, layerToLink); - return; - } - linkedLayers->emplace_back(layerToLink); +uint32_t LayerLifecycleManager::linkLayer(uint32_t layerId, uint32_t layerToLink) { + if (layerId == UNASSIGNED_LAYER_ID) { + return UNASSIGNED_LAYER_ID; } -} -void LayerLifecycleManager::unlinkLayer(uint32_t& inOutLayerId, uint32_t linkedLayer) { - uint32_t layerId = inOutLayerId; - inOutLayerId = UNASSIGNED_LAYER_ID; + std::vector* linkedLayers = getLinkedLayersFromId(layerId); + if (!linkedLayers) { + ALOGV("Could not find layer id %d to link %d. Parent is probably destroyed", layerId, + layerToLink); + return UNASSIGNED_LAYER_ID; + } + linkedLayers->emplace_back(layerToLink); + return layerId; +} +uint32_t LayerLifecycleManager::unlinkLayer(uint32_t layerId, uint32_t linkedLayer) { std::vector* linkedLayers = getLinkedLayersFromId(layerId); if (!linkedLayers) { - return; + return UNASSIGNED_LAYER_ID; } swapErase(*linkedLayers, linkedLayer); + return UNASSIGNED_LAYER_ID; } std::string LayerLifecycleManager::References::getDebugString() const { @@ -314,4 +316,16 @@ std::string LayerLifecycleManager::References::getDebugString() const { return debugInfo; } +void LayerLifecycleManager::fixRelativeZLoop(uint32_t relativeRootId) { + auto it = mIdToLayer.find(relativeRootId); + if (it == mIdToLayer.end()) { + return; + } + RequestedLayerState& layer = it->second.owner; + layer.relativeParentId = unlinkLayer(layer.relativeParentId, layer.id); + layer.changes |= + RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::RelativeParent; + mGlobalChanges |= RequestedLayerState::Changes::Hierarchy; +} + } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h index ad70d3f8e9..63a7afca4e 100644 --- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h +++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h @@ -42,6 +42,12 @@ public: void applyTransactions(const std::vector&); void onHandlesDestroyed(const std::vector&); + // Detaches the layer from its relative parent to prevent a loop in the + // layer hierarchy. This overrides the RequestedLayerState and leaves + // the system in an invalid state. This is always a client error that + // needs to be fixed but overriding the state allows us to fail gracefully. + void fixRelativeZLoop(uint32_t relativeRootId); + // Destroys RequestedLayerStates that are marked to be destroyed. Invokes all // ILifecycleListener callbacks and clears any change flags from previous state // updates. This function should be called outside the hot path since it's not @@ -72,8 +78,8 @@ private: RequestedLayerState* getLayerFromId(uint32_t); std::vector* getLinkedLayersFromId(uint32_t); - void linkLayer(uint32_t layerId, uint32_t layerToLink); - void unlinkLayer(uint32_t& inOutLayerId, uint32_t linkedLayer); + uint32_t linkLayer(uint32_t layerId, uint32_t layerToLink); + uint32_t unlinkLayer(uint32_t layerId, uint32_t linkedLayer); struct References { // Lifetime tied to mLayers diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index 45058d900f..e2a4ed16d8 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -40,7 +40,7 @@ uint32_t getLayerIdFromSurfaceControl(sp surfaceControl) { } std::string layerIdToString(uint32_t layerId) { - return layerId == UNASSIGNED_LAYER_ID ? std::to_string(layerId) : "none"; + return layerId == UNASSIGNED_LAYER_ID ? "none" : std::to_string(layerId); } } // namespace @@ -147,13 +147,17 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta static const mat4 identityMatrix = mat4(); hasColorTransform = colorTransform != identityMatrix; } - if (clientState.what & layer_state_t::eLayerChanged) { + if (clientState.what & (layer_state_t::eLayerChanged | layer_state_t::eRelativeLayerChanged)) { changes |= RequestedLayerState::Changes::Z; } if (clientState.what & layer_state_t::eReparent) { changes |= RequestedLayerState::Changes::Parent; parentId = getLayerIdFromSurfaceControl(clientState.parentSurfaceControlForChild); parentSurfaceControlForChild = nullptr; + // Once a layer has be reparented, it cannot be placed at the root. It sounds odd + // but thats the existing logic and until we make this behavior more explicit, we need + // to maintain this logic. + canBeRoot = false; } if (clientState.what & layer_state_t::eRelativeLayerChanged) { changes |= RequestedLayerState::Changes::RelativeParent; @@ -254,7 +258,7 @@ std::string RequestedLayerState::getDebugString() const { ",relativeParent=" + layerIdToString(relativeParentId) + ",isRelativeOf=" + std::to_string(isRelativeOf) + ",mirrorId=" + layerIdToString(mirrorId) + - ",handleAlive=" + std::to_string(handleAlive); + ",handleAlive=" + std::to_string(handleAlive) + ",z=" + std::to_string(z); } std::string RequestedLayerState::getDebugStringShort() const { @@ -355,4 +359,13 @@ Rect RequestedLayerState::reduce(const Rect& win, const Region& exclude) { return Region(win).subtract(exclude).getBounds(); } +// Returns true if the layer has a relative parent that is not its own parent. This is an input +// error from the client, and this check allows us to handle it gracefully. If both parentId and +// relativeParentId is unassigned then the layer does not have a valid relative parent. +// If the relative parentid is unassigned, the layer will be considered relative but won't be +// reachable. +bool RequestedLayerState::hasValidRelativeParent() const { + return isRelativeOf && parentId != relativeParentId; +} + } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h index 0ddf5e2895..6891fbc8e6 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h @@ -62,13 +62,14 @@ struct RequestedLayerState : layer_state_t { std::string getDebugString() const; std::string getDebugStringShort() const; aidl::android::hardware::graphics::composer3::Composition getCompositionType() const; + bool hasValidRelativeParent() const; // Layer serial number. This gives layers an explicit ordering, so we // have a stable sort order when their layer stack and Z-order are // the same. const uint32_t id; const std::string name; - const bool canBeRoot = false; + bool canBeRoot = false; const uint32_t layerCreationFlags; const uint32_t textureName; // The owner of the layer. If created from a non system process, it will be the calling uid. diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index 26c0d8e158..914c5f9c1b 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -92,6 +92,7 @@ cc_test { "LayerHistoryTest.cpp", "LayerInfoTest.cpp", "LayerMetadataTest.cpp", + "LayerHierarchyTest.cpp", "LayerLifecycleManagerTest.cpp", "LayerTest.cpp", "LayerTestUtils.cpp", diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp new file mode 100644 index 0000000000..8560902e4e --- /dev/null +++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp @@ -0,0 +1,728 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "FrontEnd/LayerHandle.h" +#include "FrontEnd/LayerHierarchy.h" +#include "FrontEnd/LayerLifecycleManager.h" +#include "Layer.h" +#include "gui/SurfaceComposerClient.h" + +#define UPDATE_AND_VERIFY(HIERARCHY) \ + ({ \ + SCOPED_TRACE(""); \ + updateAndVerify((HIERARCHY)); \ + }) + +namespace android::surfaceflinger::frontend { + +namespace { +LayerCreationArgs createArgs(uint32_t id, bool canBeRoot, wp parent, wp mirror) { + LayerCreationArgs args(nullptr, nullptr, "testlayer", 0, {}, std::make_optional(id)); + args.addToRoot = canBeRoot; + args.parentHandle = parent; + args.mirrorLayerHandle = mirror; + return args; +} +} // namespace + +// To run test: +/** + mp :libsurfaceflinger_unittest && adb sync; adb shell \ + /data/nativetest/libsurfaceflinger_unittest/libsurfaceflinger_unittest \ + --gtest_filter="LayerHierarchyTest.*" --gtest_repeat=100 \ + --gtest_shuffle \ + --gtest_brief=1 +*/ + +class LayerHierarchyTest : public testing::Test { +protected: + LayerHierarchyTest() { + // tree with 3 levels of children + // ROOT + // ├── 1 + // │ ├── 11 + // │ │ └── 111 + // │ ├── 12 + // │ │ ├── 121 + // │ │ └── 122 + // │ │ └── 1221 + // │ └── 13 + // └── 2 + + createRootLayer(1); + createRootLayer(2); + createLayer(11, 1); + createLayer(12, 1); + createLayer(13, 1); + createLayer(111, 11); + createLayer(121, 12); + createLayer(122, 12); + createLayer(1221, 122); + mLifecycleManager.commitChanges(); + } + std::vector getTraversalPath(const LayerHierarchy& hierarchy) const { + std::vector layerIds; + hierarchy.traverse([&layerIds = layerIds](const LayerHierarchy& hierarchy, + const LayerHierarchy::TraversalPath&) -> bool { + layerIds.emplace_back(hierarchy.getLayer()->id); + return true; + }); + return layerIds; + } + + std::vector getTraversalPathInZOrder(const LayerHierarchy& hierarchy) const { + std::vector layerIds; + hierarchy.traverseInZOrder( + [&layerIds = layerIds](const LayerHierarchy& hierarchy, + const LayerHierarchy::TraversalPath&) -> bool { + layerIds.emplace_back(hierarchy.getLayer()->id); + return true; + }); + return layerIds; + } + + void createRootLayer(uint32_t id) { + sp handle = sp::make(id); + mHandles[id] = handle; + std::vector> layers; + layers.emplace_back(std::make_unique( + createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/nullptr, /*mirror=*/nullptr))); + mLifecycleManager.addLayers(std::move(layers)); + } + + void createLayer(uint32_t id, uint32_t parentId) { + sp handle = sp::make(id); + mHandles[id] = handle; + std::vector> layers; + layers.emplace_back(std::make_unique( + createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/mHandles[parentId], + /*mirror=*/nullptr))); + mLifecycleManager.addLayers(std::move(layers)); + } + + void reparentLayer(uint32_t id, uint32_t newParentId) { + std::vector transactions; + transactions.emplace_back(); + transactions.back().states.push_back({}); + + if (newParentId == UNASSIGNED_LAYER_ID) { + transactions.back().states.front().state.parentSurfaceControlForChild = nullptr; + } else { + auto parentHandle = mHandles[newParentId]; + transactions.back().states.front().state.parentSurfaceControlForChild = + sp::make(SurfaceComposerClient::getDefault(), parentHandle, + static_cast(newParentId), "Test"); + } + transactions.back().states.front().state.what = layer_state_t::eReparent; + transactions.back().states.front().state.surface = mHandles[id]; + mLifecycleManager.applyTransactions(transactions); + } + + void reparentRelativeLayer(uint32_t id, uint32_t relativeParentId) { + std::vector transactions; + transactions.emplace_back(); + transactions.back().states.push_back({}); + + if (relativeParentId == UNASSIGNED_LAYER_ID) { + transactions.back().states.front().state.what = layer_state_t::eLayerChanged; + } else { + auto parentHandle = mHandles[relativeParentId]; + transactions.back().states.front().state.relativeLayerSurfaceControl = + sp::make(SurfaceComposerClient::getDefault(), parentHandle, + static_cast(relativeParentId), "test"); + transactions.back().states.front().state.what = layer_state_t::eRelativeLayerChanged; + } + transactions.back().states.front().state.surface = mHandles[id]; + mLifecycleManager.applyTransactions(transactions); + } + + void mirrorLayer(uint32_t id, uint32_t parent, uint32_t layerToMirror) { + auto parentHandle = (parent == UNASSIGNED_LAYER_ID) ? nullptr : mHandles[parent]; + auto mirrorHandle = + (layerToMirror == UNASSIGNED_LAYER_ID) ? nullptr : mHandles[layerToMirror]; + + sp handle = sp::make(id); + mHandles[id] = handle; + std::vector> layers; + layers.emplace_back(std::make_unique( + createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentHandle, + /*mirror=*/mHandles[layerToMirror]))); + mLifecycleManager.addLayers(std::move(layers)); + } + + void updateBackgroundColor(uint32_t id, half alpha) { + std::vector transactions; + transactions.emplace_back(); + transactions.back().states.push_back({}); + transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged; + transactions.back().states.front().state.bgColorAlpha = alpha; + transactions.back().states.front().state.surface = mHandles[id]; + mLifecycleManager.applyTransactions(transactions); + } + + void destroyLayerHandle(uint32_t id) { mLifecycleManager.onHandlesDestroyed({id}); } + + void updateAndVerify(LayerHierarchyBuilder& hierarchyBuilder) { + if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) { + hierarchyBuilder.update(mLifecycleManager.getLayers(), + mLifecycleManager.getDestroyedLayers()); + } + mLifecycleManager.commitChanges(); + + // rebuild layer hierarchy from scratch and verify that it matches the updated state. + LayerHierarchyBuilder newBuilder(mLifecycleManager.getLayers()); + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), + getTraversalPath(newBuilder.getHierarchy())); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), + getTraversalPathInZOrder(newBuilder.getHierarchy())); + EXPECT_FALSE( + mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)); + } + + void setZ(uint32_t id, int32_t z) { + std::vector transactions; + transactions.emplace_back(); + transactions.back().states.push_back({}); + + transactions.back().states.front().state.what = layer_state_t::eLayerChanged; + transactions.back().states.front().state.layerId = static_cast(id); + transactions.back().states.front().state.z = z; + mLifecycleManager.applyTransactions(transactions); + } + LayerLifecycleManager mLifecycleManager; + std::unordered_map> mHandles; +}; + +// reparenting tests +TEST_F(LayerHierarchyTest, addLayer) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + std::vector expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); + + createRootLayer(3); + createLayer(112, 11); + createLayer(12211, 1221); + UPDATE_AND_VERIFY(hierarchyBuilder); + expectedTraversalPath = {1, 11, 111, 112, 12, 121, 122, 1221, 12211, 13, 2, 3}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, reparentLayer) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + reparentLayer(2, 11); + reparentLayer(111, 12); + reparentLayer(1221, 1); + reparentLayer(1221, 13); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {1, 11, 2, 12, 111, 121, 122, 13, 1221}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, reparentLayerToNull) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + + reparentLayer(2, UNASSIGNED_LAYER_ID); + reparentLayer(11, UNASSIGNED_LAYER_ID); + reparentLayer(1221, 13); + reparentLayer(1221, UNASSIGNED_LAYER_ID); + + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {1, 12, 121, 122, 13}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {2, 11, 111, 1221}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, reparentLayerToNullAndDestroyHandles) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + reparentLayer(2, UNASSIGNED_LAYER_ID); + reparentLayer(11, UNASSIGNED_LAYER_ID); + reparentLayer(1221, UNASSIGNED_LAYER_ID); + + destroyLayerHandle(2); + destroyLayerHandle(11); + destroyLayerHandle(1221); + + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {1, 12, 121, 122, 13}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {111}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, destroyHandleThenDestroyParentLayer) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + destroyLayerHandle(111); + UPDATE_AND_VERIFY(hierarchyBuilder); + + // handle is destroyed but layer is kept alive and reachable by parent + std::vector expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); + + // destroy parent layer and the child gets destroyed + reparentLayer(11, UNASSIGNED_LAYER_ID); + destroyLayerHandle(11); + UPDATE_AND_VERIFY(hierarchyBuilder); + + expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, layerSurvivesTemporaryReparentToNull) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + reparentLayer(11, UNASSIGNED_LAYER_ID); + reparentLayer(11, 1); + + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +// offscreen tests +TEST_F(LayerHierarchyTest, layerMovesOnscreen) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + + reparentLayer(11, UNASSIGNED_LAYER_ID); + UPDATE_AND_VERIFY(hierarchyBuilder); + + reparentLayer(11, 1); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, addLayerToOffscreenParent) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + + reparentLayer(11, UNASSIGNED_LAYER_ID); + UPDATE_AND_VERIFY(hierarchyBuilder); + + createLayer(112, 11); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {11, 111, 112}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +// rel-z tests +TEST_F(LayerHierarchyTest, setRelativeParent) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + reparentRelativeLayer(11, 2); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 11, 111}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2, 11, 111}; + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, reparentFromRelativeParentWithSetLayer) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + reparentRelativeLayer(11, 2); + UPDATE_AND_VERIFY(hierarchyBuilder); + + reparentRelativeLayer(11, UNASSIGNED_LAYER_ID); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, reparentToRelativeParent) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + reparentRelativeLayer(11, 2); + UPDATE_AND_VERIFY(hierarchyBuilder); + + reparentLayer(11, 2); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2, 11, 111}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, setParentAsRelativeParent) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + reparentLayer(11, 2); + UPDATE_AND_VERIFY(hierarchyBuilder); + + reparentRelativeLayer(11, 2); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2, 11, 111}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, relativeChildMovesOffscreenIsNotTraversable) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + reparentRelativeLayer(11, 2); + UPDATE_AND_VERIFY(hierarchyBuilder); + + reparentLayer(2, UNASSIGNED_LAYER_ID); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {1, 12, 121, 122, 1221, 13}; + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {2, 11, 111}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +// mirror tests +TEST_F(LayerHierarchyTest, canTraverseMirrorLayer) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + + mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {1, 11, 111, 12, 121, 122, + 1221, 13, 14, 11, 111, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, canMirrorOffscreenLayer) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + + reparentLayer(11, UNASSIGNED_LAYER_ID); + mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 14, 11, 111, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {11, 111}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, newChildLayerIsUpdatedInMirrorHierarchy) { + mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11); + mLifecycleManager.commitChanges(); + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + + createLayer(1111, 111); + createLayer(112, 11); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {1, 11, 111, 1111, 112, 12, 121, 122, + 1221, 13, 14, 11, 111, 1111, 112, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +// mirror & relatives tests +TEST_F(LayerHierarchyTest, mirrorWithRelativeOutsideMirrorHierarchy) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + reparentRelativeLayer(111, 12); + mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11); + + // ROOT + // ├── 1 + // │ ├── 11 + // │ │ └── 111 + // │ ├── 12 + // │ │ ├── 121 + // │ │ ├── 122 + // │ │ │ └── 1221 + // │ │ â”” - 111 (relative) + // │ ├── 13 + // │ └── 14 + // │ â”” * 11 (mirroring) + // └── 2 + + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {1, 11, 111, 12, 111, 121, 122, + 1221, 13, 14, 11, 111, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + // 111 is not reachable in the mirror + expectedTraversalPath = {1, 11, 12, 111, 121, 122, 1221, 13, 14, 11, 2}; + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, mirrorWithRelativeInsideMirrorHierarchy) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + reparentRelativeLayer(1221, 12); + mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 12); + + // ROOT + // ├── 1 + // │ ├── 11 + // │ │ └── 111 + // │ ├── 12 + // │ │ ├── 121 + // │ │ ├── 122 + // │ │ │ └── 1221 + // │ │ â”” - 1221 (relative) + // │ ├── 13 + // │ └── 14 + // │ â”” * 12 (mirroring) + // └── 2 + + UPDATE_AND_VERIFY(hierarchyBuilder); + std::vector expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 1221, + 13, 14, 12, 121, 122, 1221, 1221, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + // relative layer 1221 is traversable in the mirrored hierarchy as well + expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 14, 12, 121, 122, 1221, 2}; + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, childMovesOffscreenWhenRelativeParentDies) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + + reparentRelativeLayer(11, 2); + reparentLayer(2, UNASSIGNED_LAYER_ID); + destroyLayerHandle(2); + + UPDATE_AND_VERIFY(hierarchyBuilder); + std::vector expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {1, 12, 121, 122, 1221, 13}; + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {11, 111}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); + + // remove relative parent so layer becomes onscreen again + reparentRelativeLayer(11, UNASSIGNED_LAYER_ID); + UPDATE_AND_VERIFY(hierarchyBuilder); + + expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, offscreenLayerCannotBeRelativeToOnscreenLayer) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + reparentRelativeLayer(1221, 2); + UPDATE_AND_VERIFY(hierarchyBuilder); + + // verify relz path + std::vector expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 1221}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {1, 11, 111, 12, 121, 122, 13, 2, 1221}; + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); + + // offscreen layer cannot be reached as a relative child + reparentLayer(12, UNASSIGNED_LAYER_ID); + UPDATE_AND_VERIFY(hierarchyBuilder); + + expectedTraversalPath = {1, 11, 111, 13, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {12, 121, 122, 1221}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); + + // layer when onscreen can be reached as a relative child again + reparentLayer(12, 1); + UPDATE_AND_VERIFY(hierarchyBuilder); + + expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 1221}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {1, 11, 111, 12, 121, 122, 13, 2, 1221}; + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, backgroundLayersAreBehindParentLayer) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + + updateBackgroundColor(1, 0.5); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {1, 1222, 11, 111, 12, 121, 122, 1221, 13, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {1222, 1, 11, 111, 12, 121, 122, 1221, 13, 2}; + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +// cycle tests +TEST_F(LayerHierarchyTest, ParentBecomesTheChild) { + // remove default hierarchy + mLifecycleManager = LayerLifecycleManager(); + createRootLayer(1); + createLayer(11, 1); + reparentLayer(1, 11); + mLifecycleManager.commitChanges(); + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + + std::vector expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, RelativeLoops) { + // remove default hierarchy + mLifecycleManager = LayerLifecycleManager(); + createRootLayer(1); + createRootLayer(2); + createLayer(11, 1); + reparentRelativeLayer(11, 2); + reparentRelativeLayer(2, 11); + mLifecycleManager.commitChanges(); + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + + // fix loop + uint32_t invalidRelativeRoot; + bool hasRelZLoop = hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot); + EXPECT_TRUE(hasRelZLoop); + mLifecycleManager.fixRelativeZLoop(invalidRelativeRoot); + hierarchyBuilder.update(mLifecycleManager.getLayers(), mLifecycleManager.getDestroyedLayers()); + EXPECT_EQ(invalidRelativeRoot, 11u); + EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot)); + + std::vector expectedTraversalPath = {1, 11, 2, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {1}; + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {11, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, IndirectRelativeLoops) { + // remove default hierarchy + mLifecycleManager = LayerLifecycleManager(); + createRootLayer(1); + createRootLayer(2); + createLayer(11, 1); + createLayer(111, 11); + createLayer(21, 2); + createLayer(22, 2); + createLayer(221, 22); + reparentRelativeLayer(22, 111); + reparentRelativeLayer(11, 221); + mLifecycleManager.commitChanges(); + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + + // fix loop + uint32_t invalidRelativeRoot; + bool hasRelZLoop = hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot); + EXPECT_TRUE(hasRelZLoop); + mLifecycleManager.fixRelativeZLoop(invalidRelativeRoot); + hierarchyBuilder.update(mLifecycleManager.getLayers(), mLifecycleManager.getDestroyedLayers()); + EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot)); + + std::vector expectedTraversalPath = {1, 11, 111, 22, 221, 2, 21, 22, 221}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {1, 2, 21}; + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {11, 111, 22, 221}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, ReparentRootLayerToNull) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + reparentLayer(1, UNASSIGNED_LAYER_ID); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, AddRemoveLayerInSameTransaction) { + // remove default hierarchy + mLifecycleManager = LayerLifecycleManager(); + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + createRootLayer(1); + destroyLayerHandle(1); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +// traversal path test +TEST_F(LayerHierarchyTest, traversalPathId) { + setZ(122, -1); + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + auto checkTraversalPathIdVisitor = + [](const LayerHierarchy& hierarchy, + const LayerHierarchy::TraversalPath& traversalPath) -> bool { + EXPECT_EQ(hierarchy.getLayer()->id, traversalPath.id); + return true; + }; + hierarchyBuilder.getHierarchy().traverse(checkTraversalPathIdVisitor); + hierarchyBuilder.getHierarchy().traverseInZOrder(checkTraversalPathIdVisitor); +} + +} // namespace android::surfaceflinger::frontend -- GitLab From b1a00841c9cf9de4d2c8637dd05b0d0c628f3cd2 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Tue, 6 Dec 2022 16:20:29 +0000 Subject: [PATCH 0580/1310] Move methods from TouchCursorInputMapperCommon.h into a cpp file These methods are currently all used by two other files, but I want to use one of them in TouchpadInputMapper.cpp. This means that I get compilation errors saying the others are unused. Moving the method definitions into a cpp file fixes this. Bug: b/251196347 Test: builds without errors Change-Id: Idc8b7bf2c8a1cb202e3122bcf5ff8552abbc9ca5 --- services/inputflinger/reader/Android.bp | 1 + .../mapper/TouchCursorInputMapperCommon.cpp | 127 ++++++++++++++++++ .../mapper/TouchCursorInputMapperCommon.h | 97 +------------ 3 files changed, 135 insertions(+), 90 deletions(-) create mode 100644 services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index c93443a025..a34cb4c6bd 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -50,6 +50,7 @@ filegroup { "mapper/SensorInputMapper.cpp", "mapper/SingleTouchInputMapper.cpp", "mapper/SwitchInputMapper.cpp", + "mapper/TouchCursorInputMapperCommon.cpp", "mapper/TouchInputMapper.cpp", "mapper/TouchpadInputMapper.cpp", "mapper/VibratorInputMapper.cpp", diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp new file mode 100644 index 0000000000..c12e95dfa0 --- /dev/null +++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "EventHub.h" +#include "InputListener.h" +#include "InputReaderContext.h" + +namespace android { + +namespace { + +[[nodiscard]] std::list synthesizeButtonKey( + InputReaderContext* context, int32_t action, nsecs_t when, nsecs_t readTime, + int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags, + int32_t lastButtonState, int32_t currentButtonState, int32_t buttonState, int32_t keyCode) { + std::list out; + if ((action == AKEY_EVENT_ACTION_DOWN && !(lastButtonState & buttonState) && + (currentButtonState & buttonState)) || + (action == AKEY_EVENT_ACTION_UP && (lastButtonState & buttonState) && + !(currentButtonState & buttonState))) { + out.push_back(NotifyKeyArgs(context->getNextId(), when, readTime, deviceId, source, + displayId, policyFlags, action, 0, keyCode, 0, + context->getGlobalMetaState(), when)); + } + return out; +} + +} // namespace + +ui::Rotation getInverseRotation(ui::Rotation orientation) { + switch (orientation) { + case ui::ROTATION_90: + return ui::ROTATION_270; + case ui::ROTATION_270: + return ui::ROTATION_90; + default: + return orientation; + } +} + +void rotateDelta(ui::Rotation orientation, float* deltaX, float* deltaY) { + float temp; + switch (orientation) { + case ui::ROTATION_90: + temp = *deltaX; + *deltaX = *deltaY; + *deltaY = -temp; + break; + + case ui::ROTATION_180: + *deltaX = -*deltaX; + *deltaY = -*deltaY; + break; + + case ui::ROTATION_270: + temp = *deltaX; + *deltaX = -*deltaY; + *deltaY = temp; + break; + + default: + break; + } +} + +bool isPointerDown(int32_t buttonState) { + return buttonState & + (AMOTION_EVENT_BUTTON_PRIMARY | AMOTION_EVENT_BUTTON_SECONDARY | + AMOTION_EVENT_BUTTON_TERTIARY); +} + +[[nodiscard]] std::list synthesizeButtonKeys( + InputReaderContext* context, int32_t action, nsecs_t when, nsecs_t readTime, + int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags, + int32_t lastButtonState, int32_t currentButtonState) { + std::list out; + out += synthesizeButtonKey(context, action, when, readTime, deviceId, source, displayId, + policyFlags, lastButtonState, currentButtonState, + AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK); + out += synthesizeButtonKey(context, action, when, readTime, deviceId, source, displayId, + policyFlags, lastButtonState, currentButtonState, + AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD); + return out; +} + +std::tuple applyBluetoothTimestampSmoothening( + const InputDeviceIdentifier& identifier, nsecs_t currentEventTime, nsecs_t readTime, + nsecs_t lastEventTime) { + if (identifier.bus != BUS_BLUETOOTH) { + return {currentEventTime, readTime}; + } + + // Assume the fastest rate at which a Bluetooth touch device can report input events is one + // every 4 milliseconds, or 250 Hz. Timestamps for successive events from a Bluetooth device + // will be separated by at least this amount. + constexpr static nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4); + // We define a maximum smoothing time delta so that we don't generate events too far into the + // future. + constexpr static nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32); + const nsecs_t smoothenedEventTime = + std::min(std::max(currentEventTime, lastEventTime + MIN_BLUETOOTH_TIMESTAMP_DELTA), + currentEventTime + MAX_BLUETOOTH_SMOOTHING_DELTA); + // If we are modifying the event time, treat this event as a synthetically generated event for + // latency tracking purposes and use the event time as the read time (zero read latency). + const nsecs_t smoothenedReadTime = + smoothenedEventTime != currentEventTime ? currentEventTime : readTime; + return {smoothenedEventTime, smoothenedReadTime}; +} + +} // namespace android diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h index 1c3ca975ac..3023e686c2 100644 --- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h +++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h @@ -26,81 +26,18 @@ namespace android { -// --- Static Definitions --- +ui::Rotation getInverseRotation(ui::Rotation orientation); -static ui::Rotation getInverseRotation(ui::Rotation orientation) { - switch (orientation) { - case ui::ROTATION_90: - return ui::ROTATION_270; - case ui::ROTATION_270: - return ui::ROTATION_90; - default: - return orientation; - } -} - -static void rotateDelta(ui::Rotation orientation, float* deltaX, float* deltaY) { - float temp; - switch (orientation) { - case ui::ROTATION_90: - temp = *deltaX; - *deltaX = *deltaY; - *deltaY = -temp; - break; - - case ui::ROTATION_180: - *deltaX = -*deltaX; - *deltaY = -*deltaY; - break; - - case ui::ROTATION_270: - temp = *deltaX; - *deltaX = -*deltaY; - *deltaY = temp; - break; - - default: - break; - } -} +void rotateDelta(ui::Rotation orientation, float* deltaX, float* deltaY); // Returns true if the pointer should be reported as being down given the specified // button states. This determines whether the event is reported as a touch event. -static bool isPointerDown(int32_t buttonState) { - return buttonState & - (AMOTION_EVENT_BUTTON_PRIMARY | AMOTION_EVENT_BUTTON_SECONDARY | - AMOTION_EVENT_BUTTON_TERTIARY); -} +bool isPointerDown(int32_t buttonState); -[[nodiscard]] static std::list synthesizeButtonKey( +[[nodiscard]] std::list synthesizeButtonKeys( InputReaderContext* context, int32_t action, nsecs_t when, nsecs_t readTime, int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags, - int32_t lastButtonState, int32_t currentButtonState, int32_t buttonState, int32_t keyCode) { - std::list out; - if ((action == AKEY_EVENT_ACTION_DOWN && !(lastButtonState & buttonState) && - (currentButtonState & buttonState)) || - (action == AKEY_EVENT_ACTION_UP && (lastButtonState & buttonState) && - !(currentButtonState & buttonState))) { - out.push_back(NotifyKeyArgs(context->getNextId(), when, readTime, deviceId, source, - displayId, policyFlags, action, 0, keyCode, 0, - context->getGlobalMetaState(), when)); - } - return out; -} - -[[nodiscard]] static std::list synthesizeButtonKeys( - InputReaderContext* context, int32_t action, nsecs_t when, nsecs_t readTime, - int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags, - int32_t lastButtonState, int32_t currentButtonState) { - std::list out; - out += synthesizeButtonKey(context, action, when, readTime, deviceId, source, displayId, - policyFlags, lastButtonState, currentButtonState, - AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK); - out += synthesizeButtonKey(context, action, when, readTime, deviceId, source, displayId, - policyFlags, lastButtonState, currentButtonState, - AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD); - return out; -} + int32_t lastButtonState, int32_t currentButtonState); // For devices connected over Bluetooth, although they may produce events at a consistent rate, // the events might end up reaching Android in a "batched" manner through the Bluetooth @@ -111,28 +48,8 @@ static bool isPointerDown(int32_t buttonState) { // coordinates result in extremely large instantaneous velocities, which can negatively impact // user experience. To avoid this, we augment the timestamps so that subsequent event timestamps // differ by at least a minimum delta value. -static std::tuple applyBluetoothTimestampSmoothening( +std::tuple applyBluetoothTimestampSmoothening( const InputDeviceIdentifier& identifier, nsecs_t currentEventTime, nsecs_t readTime, - nsecs_t lastEventTime) { - if (identifier.bus != BUS_BLUETOOTH) { - return {currentEventTime, readTime}; - } - - // Assume the fastest rate at which a Bluetooth touch device can report input events is one - // every 4 milliseconds, or 250 Hz. Timestamps for successive events from a Bluetooth device - // will be separated by at least this amount. - constexpr static nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4); - // We define a maximum smoothing time delta so that we don't generate events too far into the - // future. - constexpr static nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32); - const nsecs_t smoothenedEventTime = - std::min(std::max(currentEventTime, lastEventTime + MIN_BLUETOOTH_TIMESTAMP_DELTA), - currentEventTime + MAX_BLUETOOTH_SMOOTHING_DELTA); - // If we are modifying the event time, treat this event as a synthetically generated event for - // latency tracking purposes and use the event time as the read time (zero read latency). - const nsecs_t smoothenedReadTime = - smoothenedEventTime != currentEventTime ? currentEventTime : readTime; - return {smoothenedEventTime, smoothenedReadTime}; -} + nsecs_t lastEventTime); } // namespace android -- GitLab From 74235548729b615df5e85443ae3d4128fe041c79 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Thu, 24 Nov 2022 15:52:53 +0000 Subject: [PATCH 0581/1310] Convert touchpad gestures into pointer moves & clicks While the gestures library may return some events asynchronously (which will require some changes to InputReader to handle), most are returned during the call to PushHardwareState, allowing us to handle them easily. Here we handle mouse movement, clicks, and drags. I was going to send the BUTTON_PRESS events before the DOWN events, as we'd discussed changing as part of the click event consistency work, but doing so results in an input event injection failure from the dispatcher, so for now I've kept the order the same as for mice. Bug: 251196347 Test: connect Apple Magic Trackpad 2, move, click, and drag Test: atest inputflinger_tests Change-Id: I3a60ea6bd166bdb9b628f5a17d9e1b6e7341ba42 --- .../reader/mapper/TouchpadInputMapper.cpp | 201 +++++++++++++++++- .../reader/mapper/TouchpadInputMapper.h | 27 ++- 2 files changed, 219 insertions(+), 9 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 8c5bce7d13..de6e4b0193 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -16,8 +16,11 @@ #include "../Macros.h" -#include #include + +#include +#include +#include "TouchCursorInputMapperCommon.h" #include "TouchpadInputMapper.h" namespace android { @@ -75,9 +78,26 @@ HardwareProperties createHardwareProperties(const InputDeviceContext& context) { return props; } -void gestureInterpreterCallback(void* clientData, const struct Gesture* gesture) { - // TODO(b/251196347): turn the gesture into a NotifyArgs and dispatch it. - ALOGD("Gesture ready: %s", gesture->String().c_str()); +void gestureInterpreterCallback(void* clientData, const Gesture* gesture) { + TouchpadInputMapper* mapper = static_cast(clientData); + mapper->consumeGesture(gesture); +} + +uint32_t gesturesButtonToMotionEventButton(uint32_t gesturesButton) { + switch (gesturesButton) { + case GESTURES_BUTTON_LEFT: + return AMOTION_EVENT_BUTTON_PRIMARY; + case GESTURES_BUTTON_MIDDLE: + return AMOTION_EVENT_BUTTON_TERTIARY; + case GESTURES_BUTTON_RIGHT: + return AMOTION_EVENT_BUTTON_SECONDARY; + case GESTURES_BUTTON_BACK: + return AMOTION_EVENT_BUTTON_BACK; + case GESTURES_BUTTON_FORWARD: + return AMOTION_EVENT_BUTTON_FORWARD; + default: + return 0; + } } } // namespace @@ -85,10 +105,15 @@ void gestureInterpreterCallback(void* clientData, const struct Gesture* gesture) TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext) : InputMapper(deviceContext), mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter), + mPointerController(getContext()->getPointerController(getDeviceId())), mTouchButtonAccumulator(deviceContext) { mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD); mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext)); - mGestureInterpreter->SetCallback(gestureInterpreterCallback, nullptr); + // Even though we don't explicitly delete copy/move semantics, it's safe to + // give away a pointer to TouchpadInputMapper here because + // 1) mGestureInterpreter's lifecycle is determined by TouchpadInputMapper, and + // 2) TouchpadInputMapper is stored as a unique_ptr and not moved. + mGestureInterpreter->SetCallback(gestureInterpreterCallback, this); // TODO(b/251196347): set a property provider, so we can change gesture properties. // TODO(b/251196347): set a timer provider, so the library can use timers. @@ -103,6 +128,12 @@ TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext) mTouchButtonAccumulator.configure(); } +TouchpadInputMapper::~TouchpadInputMapper() { + if (mPointerController != nullptr) { + mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); + } +} + uint32_t TouchpadInputMapper::getSources() const { return AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD; } @@ -111,12 +142,15 @@ std::list TouchpadInputMapper::reset(nsecs_t when) { mCursorButtonAccumulator.reset(getDeviceContext()); mTouchButtonAccumulator.reset(); mMscTimestamp = 0; + + mButtonState = 0; return InputMapper::reset(when); } std::list TouchpadInputMapper::process(const RawEvent* rawEvent) { + std::list out = {}; if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { - sync(rawEvent->when); + out = sync(rawEvent->when, rawEvent->readTime); } if (rawEvent->type == EV_MSC && rawEvent->code == MSC_TIMESTAMP) { mMscTimestamp = rawEvent->value; @@ -124,10 +158,10 @@ std::list TouchpadInputMapper::process(const RawEvent* rawEvent) { mCursorButtonAccumulator.process(rawEvent); mMotionAccumulator.process(rawEvent); mTouchButtonAccumulator.process(rawEvent); - return {}; + return out; } -void TouchpadInputMapper::sync(nsecs_t when) { +std::list TouchpadInputMapper::sync(nsecs_t when, nsecs_t readTime) { HardwareState hwState; // The gestures library uses doubles to represent timestamps in seconds. hwState.timestamp = std::chrono::duration(std::chrono::nanoseconds(when)).count(); @@ -172,9 +206,160 @@ void TouchpadInputMapper::sync(nsecs_t when) { hwState.finger_cnt = fingers.size(); hwState.touch_cnt = mTouchButtonAccumulator.getTouchCount(); + mProcessing = true; mGestureInterpreter->PushHardwareState(&hwState); + mProcessing = false; + + std::list out = processGestures(when, readTime); + mMotionAccumulator.finishSync(); mMscTimestamp = 0; + return out; +} + +void TouchpadInputMapper::consumeGesture(const Gesture* gesture) { + ALOGD("Gesture ready: %s", gesture->String().c_str()); + if (!mProcessing) { + ALOGE("Received gesture outside of the normal processing flow; ignoring it."); + return; + } + mGesturesToProcess.push_back(*gesture); +} + +std::list TouchpadInputMapper::processGestures(nsecs_t when, nsecs_t readTime) { + std::list out = {}; + for (Gesture& gesture : mGesturesToProcess) { + switch (gesture.type) { + case kGestureTypeMove: + out.push_back(handleMove(when, readTime, gesture)); + break; + case kGestureTypeButtonsChange: + out += handleButtonsChange(when, readTime, gesture); + break; + default: + // TODO(b/251196347): handle more gesture types. + break; + } + } + mGesturesToProcess.clear(); + return out; +} + +NotifyArgs TouchpadInputMapper::handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture) { + PointerProperties props; + props.clear(); + props.id = 0; + props.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + + mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); + mPointerController->move(gesture.details.move.dx, gesture.details.move.dy); + mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); + float xCursorPosition, yCursorPosition; + mPointerController->getPosition(&xCursorPosition, &yCursorPosition); + + PointerCoords coords; + coords.clear(); + coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); + coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, gesture.details.move.dx); + coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, gesture.details.move.dy); + const bool down = isPointerDown(mButtonState); + coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f); + + const int32_t action = down ? AMOTION_EVENT_ACTION_MOVE : AMOTION_EVENT_ACTION_HOVER_MOVE; + return makeMotionArgs(when, readTime, action, /* actionButton= */ 0, mButtonState, + /* pointerCount= */ 1, &props, &coords, xCursorPosition, yCursorPosition); +} + +std::list TouchpadInputMapper::handleButtonsChange(nsecs_t when, nsecs_t readTime, + const Gesture& gesture) { + std::list out = {}; + + mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); + mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); + + PointerProperties props; + props.clear(); + props.id = 0; + props.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + + float xCursorPosition, yCursorPosition; + mPointerController->getPosition(&xCursorPosition, &yCursorPosition); + + PointerCoords coords; + coords.clear(); + coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); + coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0); + coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0); + const uint32_t buttonsPressed = gesture.details.buttons.down; + bool pointerDown = isPointerDown(mButtonState) || + buttonsPressed & + (GESTURES_BUTTON_LEFT | GESTURES_BUTTON_MIDDLE | GESTURES_BUTTON_RIGHT); + coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerDown ? 1.0f : 0.0f); + + uint32_t newButtonState = mButtonState; + std::list pressEvents = {}; + for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) { + if (buttonsPressed & button) { + uint32_t actionButton = gesturesButtonToMotionEventButton(button); + newButtonState |= actionButton; + pressEvents.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS, + actionButton, newButtonState, + /* pointerCount= */ 1, &props, &coords, + xCursorPosition, yCursorPosition)); + } + } + if (!isPointerDown(mButtonState) && isPointerDown(newButtonState)) { + mDownTime = when; + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, + /* actionButton= */ 0, newButtonState, /* pointerCount= */ 1, + &props, &coords, xCursorPosition, yCursorPosition)); + } + out.splice(out.end(), pressEvents); + + // The same button may be in both down and up in the same gesture, in which case we should treat + // it as having gone down and then up. So, we treat a single button change gesture as two state + // changes: a set of buttons going down, followed by a set of buttons going up. + mButtonState = newButtonState; + + const uint32_t buttonsReleased = gesture.details.buttons.up; + for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) { + if (buttonsReleased & button) { + uint32_t actionButton = gesturesButtonToMotionEventButton(button); + newButtonState &= ~actionButton; + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE, + actionButton, newButtonState, /* pointerCount= */ 1, + &props, &coords, xCursorPosition, yCursorPosition)); + } + } + if (isPointerDown(mButtonState) && !isPointerDown(newButtonState)) { + coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f); + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0, + newButtonState, /* pointerCount= */ 1, &props, &coords, + xCursorPosition, yCursorPosition)); + } + mButtonState = newButtonState; + return out; +} + +NotifyMotionArgs TouchpadInputMapper::makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, + int32_t actionButton, int32_t buttonState, + uint32_t pointerCount, + const PointerProperties* pointerProperties, + const PointerCoords* pointerCoords, + float xCursorPosition, float yCursorPosition) { + // TODO(b/260226362): consider what the appropriate source for these events is. + const uint32_t source = AINPUT_SOURCE_MOUSE; + + return NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), source, + mPointerController->getDisplayId(), /* policyFlags= */ 0, action, + /* actionButton= */ actionButton, /* flags= */ 0, + getContext()->getGlobalMetaState(), buttonState, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, + pointerProperties, pointerCoords, + /* xPrecision= */ 1.0f, /* yPrecision= */ 1.0f, xCursorPosition, + yCursorPosition, /* downTime= */ mDownTime, /* videoFrames= */ {}); } } // namespace android diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index 9d3a4b3e0d..fe6b1fe759 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -18,6 +18,8 @@ #include +#include + #include "EventHub.h" #include "InputDevice.h" #include "InputMapper.h" @@ -33,21 +35,44 @@ namespace android { class TouchpadInputMapper : public InputMapper { public: explicit TouchpadInputMapper(InputDeviceContext& deviceContext); + ~TouchpadInputMapper(); uint32_t getSources() const override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; + void consumeGesture(const Gesture* gesture); + private: - void sync(nsecs_t when); + [[nodiscard]] std::list sync(nsecs_t when, nsecs_t readTime); + [[nodiscard]] std::list processGestures(nsecs_t when, nsecs_t readTime); + NotifyArgs handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture); + [[nodiscard]] std::list handleButtonsChange(nsecs_t when, nsecs_t readTime, + const Gesture& gesture); + + NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, + int32_t actionButton, int32_t buttonState, + uint32_t pointerCount, + const PointerProperties* pointerProperties, + const PointerCoords* pointerCoords, float xCursorPosition, + float yCursorPosition); std::unique_ptr mGestureInterpreter; + std::shared_ptr mPointerController; CursorButtonAccumulator mCursorButtonAccumulator; MultiTouchMotionAccumulator mMotionAccumulator; TouchButtonAccumulator mTouchButtonAccumulator; int32_t mMscTimestamp = 0; + + bool mProcessing = false; + std::vector mGesturesToProcess; + + // The current button state according to the gestures library, but converted into MotionEvent + // button values (AMOTION_EVENT_BUTTON_...). + uint32_t mButtonState = 0; + nsecs_t mDownTime = 0; }; } // namespace android -- GitLab From d57302f9f69790c9d34fd3f97346c38e739eb765 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 8 Nov 2022 11:12:29 -0800 Subject: [PATCH 0582/1310] Track hovering pointers explicitly Before this CL, hovering window was tracked separately inside InputDispatcher. This window was getting updated in various places. Inconsistent motion streams, like HOVER_ENTER->DOWN->UP->HOVER_EXIT were possible. In this CL, we track hovering pointers inside TouchedWindow. At all times, the currently tracked pointer must always be in the touch state. The hovering pointer is removed when HOVER_EXIT is received. This CL also establishes the foundation for multi-device, multi-pointer streams, by storing hovering pointers inside TouchedWindow per-device. Eventually, we can look into separately creating touched targets from updating the touch state. This approach is partially used in this CL. TouchState is used to keep track of where the hovering pointer is currently. The 'addHoveringWindowsLocked' function returns the equivalent of InputTargets. Eventually, we can change this to return InputTargets. Bug: 211379801 Test: m inputflinger_tests && adb sync data && adb shell -t /data/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: I047926e53b846c96807aed45fb585e031e5b88b9 --- include/input/PrintTools.h | 6 + .../dispatcher/InputDispatcher.cpp | 164 +++++++++++------- .../inputflinger/dispatcher/InputDispatcher.h | 3 - .../inputflinger/dispatcher/TouchState.cpp | 55 ++++++ services/inputflinger/dispatcher/TouchState.h | 11 ++ .../inputflinger/dispatcher/TouchedWindow.cpp | 44 ++++- .../inputflinger/dispatcher/TouchedWindow.h | 12 ++ .../tests/InputDispatcher_test.cpp | 44 ++++- 8 files changed, 260 insertions(+), 79 deletions(-) diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h index e24344b3f1..a20544b5fd 100644 --- a/include/input/PrintTools.h +++ b/include/input/PrintTools.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include #include @@ -23,6 +24,11 @@ namespace android { +template +std::string bitsetToString(const std::bitset& bitset) { + return bitset.to_string(); +} + template inline std::string constToString(const T& v) { return std::to_string(v); diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 4aac377b42..4d3e6def00 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -554,6 +554,65 @@ std::optional getDownTime(const EventEntry& eventEntry) { return std::nullopt; } +/** + * Compare the old touch state to the new touch state, and generate the corresponding touched + * windows (== input targets). + * If a window had the hovering pointer, but now it doesn't, produce HOVER_EXIT for that window. + * If the pointer just entered the new window, produce HOVER_ENTER. + * For pointers remaining in the window, produce HOVER_MOVE. + */ +std::vector getHoveringWindowsLocked(const TouchState* oldState, + const TouchState& newTouchState, + const MotionEntry& entry) { + std::vector out; + const int32_t maskedAction = MotionEvent::getActionMasked(entry.action); + if (maskedAction != AMOTION_EVENT_ACTION_HOVER_ENTER && + maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE && + maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) { + // Not a hover event - don't need to do anything + return out; + } + + // We should consider all hovering pointers here. But for now, just use the first one + const int32_t pointerId = entry.pointerProperties[0].id; + + std::set> oldWindows; + if (oldState != nullptr) { + oldWindows = oldState->getWindowsWithHoveringPointer(entry.deviceId, pointerId); + } + + std::set> newWindows = + newTouchState.getWindowsWithHoveringPointer(entry.deviceId, pointerId); + + // If the pointer is no longer in the new window set, send HOVER_EXIT. + for (const sp& oldWindow : oldWindows) { + if (newWindows.find(oldWindow) == newWindows.end()) { + TouchedWindow touchedWindow; + touchedWindow.windowHandle = oldWindow; + touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_HOVER_EXIT; + touchedWindow.pointerIds.markBit(pointerId); + out.push_back(touchedWindow); + } + } + + for (const sp& newWindow : newWindows) { + TouchedWindow touchedWindow; + touchedWindow.windowHandle = newWindow; + if (oldWindows.find(newWindow) == oldWindows.end()) { + // Any windows that have this pointer now, and didn't have it before, should get + // HOVER_ENTER + touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_HOVER_ENTER; + } else { + // This pointer was already sent to the window. Use ACTION_HOVER_MOVE. + LOG_ALWAYS_FATAL_IF(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE); + touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_IS; + } + touchedWindow.pointerIds.markBit(pointerId); + out.push_back(touchedWindow); + } + return out; +} + } // namespace // --- InputDispatcher --- @@ -2083,8 +2142,6 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( // Update the touch state as needed based on the properties of the touch event. outInjectionResult = InputEventInjectionResult::PENDING; - sp newHoverWindowHandle(mLastHoverWindowHandle); - sp newTouchedWindowHandle; // Copy current touch state into tempTouchState. // This state will be used to update mTouchStatesByDisplay at the end of this function. @@ -2117,7 +2174,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( outInjectionResult = InputEventInjectionResult::FAILED; return touchedWindows; // wrong device } - tempTouchState.reset(); + tempTouchState.clearWindowsWithoutPointers(); tempTouchState.deviceId = entry.deviceId; tempTouchState.source = entry.source; isSplit = false; @@ -2130,14 +2187,21 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( return touchedWindows; // wrong device } + if (isHoverAction) { + // For hover actions, we will treat 'tempTouchState' as a new state, so let's erase + // all of the existing hovering pointers and recompute. + tempTouchState.clearHoveringPointers(); + } + if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) { /* Case 1: New splittable pointer going down, or need target for hover or scroll. */ const auto [x, y] = resolveTouchedPosition(entry); const int32_t pointerIndex = getMotionEventActionPointerIndex(action); const bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN; const bool isStylus = isPointerFromStylus(entry, pointerIndex); - newTouchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, - isStylus, isDown /*addOutsideTargets*/); + sp newTouchedWindowHandle = + findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, isStylus, + isDown /*addOutsideTargets*/); // Handle the case where we did not find a window. if (newTouchedWindowHandle == nullptr) { @@ -2172,15 +2236,6 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( isSplit = !isFromMouse; } - // Update hover state. - if (newTouchedWindowHandle != nullptr) { - if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) { - newHoverWindowHandle = nullptr; - } else if (isHoverAction) { - newHoverWindowHandle = newTouchedWindowHandle; - } - } - std::vector> newTouchedWindows = findTouchedSpyWindowsAtLocked(displayId, x, y, isStylus); if (newTouchedWindowHandle != nullptr) { @@ -2200,6 +2255,18 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( continue; } + if (isHoverAction) { + const int32_t pointerId = entry.pointerProperties[0].id; + if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) { + // Pointer left. Remove it + tempTouchState.removeHoveringPointer(entry.deviceId, pointerId); + } else { + // The "windowHandle" is the target of this hovering pointer. + tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, + pointerId); + } + } + // Set target flags. ftl::Flags targetFlags = InputTarget::Flags::DISPATCH_AS_IS; @@ -2219,7 +2286,9 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( // Update the temporary touch state. BitSet32 pointerIds; - pointerIds.markBit(entry.pointerProperties[pointerIndex].id); + if (!isHoverAction) { + pointerIds.markBit(entry.pointerProperties[pointerIndex].id); + } tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds, entry.eventTime); @@ -2255,7 +2324,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( const bool isStylus = isPointerFromStylus(entry, 0 /*pointerIndex*/); sp oldTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle(); - newTouchedWindowHandle = + sp newTouchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, isStylus); // Verify targeted injection. @@ -2326,36 +2395,11 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } // Update dispatching for hover enter and exit. - if (newHoverWindowHandle != mLastHoverWindowHandle) { - // Let the previous window know that the hover sequence is over, unless we already did - // it when dispatching it as is to newTouchedWindowHandle. - if (mLastHoverWindowHandle != nullptr && - (maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT || - mLastHoverWindowHandle != newTouchedWindowHandle)) { - if (DEBUG_HOVER) { - ALOGD("Sending hover exit event to window %s.", - mLastHoverWindowHandle->getName().c_str()); - } - tempTouchState.addOrUpdateWindow(mLastHoverWindowHandle, - InputTarget::Flags::DISPATCH_AS_HOVER_EXIT, - BitSet32(0)); - } - - // Let the new window know that the hover sequence is starting, unless we already did it - // when dispatching it as is to newTouchedWindowHandle. - if (newHoverWindowHandle != nullptr && - (maskedAction != AMOTION_EVENT_ACTION_HOVER_ENTER || - newHoverWindowHandle != newTouchedWindowHandle)) { - if (DEBUG_HOVER) { - ALOGD("Sending hover enter event to window %s.", - newHoverWindowHandle->getName().c_str()); - } - tempTouchState.addOrUpdateWindow(newHoverWindowHandle, - InputTarget::Flags::DISPATCH_AS_HOVER_ENTER, - BitSet32(0)); - } + { + std::vector hoveringWindows = + getHoveringWindowsLocked(oldState, tempTouchState, entry); + touchedWindows.insert(touchedWindows.end(), hoveringWindows.begin(), hoveringWindows.end()); } - // Ensure that we have at least one foreground window or at least one window that cannot be a // foreground target. If we only have windows that are not receiving foreground touches (e.g. we // only have windows getting ACTION_OUTSIDE), then drop the event, because there is no window @@ -2445,10 +2489,13 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } } - // Success! Output targets. - touchedWindows = tempTouchState.windows; - outInjectionResult = InputEventInjectionResult::SUCCEEDED; + // Success! Output targets for everything except hovers. + if (!isHoverAction) { + touchedWindows.insert(touchedWindows.end(), tempTouchState.windows.begin(), + tempTouchState.windows.end()); + } + outInjectionResult = InputEventInjectionResult::SUCCEEDED; // Drop the outside or hover touch windows since we will not care about them // in the next iteration. tempTouchState.filterNonAsIsTouchWindows(); @@ -2469,14 +2516,16 @@ Failed: "Conflicting pointer actions: Hover received while pointer was down."); *outConflictingPointerActions = true; } - tempTouchState.reset(); if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) { tempTouchState.deviceId = entry.deviceId; tempTouchState.source = entry.source; } - } else if (maskedAction == AMOTION_EVENT_ACTION_UP || - maskedAction == AMOTION_EVENT_ACTION_CANCEL) { + } else if (maskedAction == AMOTION_EVENT_ACTION_UP) { + // Pointer went up. + tempTouchState.removeTouchedPointer(entry.pointerProperties[0].id); + tempTouchState.clearWindowsWithoutPointers(); + } else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) { // All pointers up or canceled. tempTouchState.reset(); } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { @@ -2515,9 +2564,6 @@ Failed: mTouchStatesByDisplay.erase(displayId); } - // Update hover state. - mLastHoverWindowHandle = newHoverWindowHandle; - return touchedWindows; } @@ -4809,14 +4855,6 @@ void InputDispatcher::setInputWindowsLocked( updateWindowHandlesForDisplayLocked(windowInfoHandles, displayId); const std::vector>& windowHandles = getWindowHandlesLocked(displayId); - if (mLastHoverWindowHandle) { - const WindowInfo* lastHoverWindowInfo = mLastHoverWindowHandle->getInfo(); - if (lastHoverWindowInfo->displayId == displayId && - std::find(windowHandles.begin(), windowHandles.end(), mLastHoverWindowHandle) == - windowHandles.end()) { - mLastHoverWindowHandle = nullptr; - } - } std::optional changes = mFocusResolver.setInputWindows(displayId, windowHandles); @@ -5264,7 +5302,6 @@ void InputDispatcher::resetAndDropEverythingLocked(const char* reason) { mAnrTracker.clear(); mTouchStatesByDisplay.clear(); - mLastHoverWindowHandle.clear(); mReplacedKeys.clear(); } @@ -6454,7 +6491,6 @@ void InputDispatcher::cancelCurrentTouch() { synthesizeCancelationEventsForAllConnectionsLocked(options); mTouchStatesByDisplay.clear(); - mLastHoverWindowHandle.clear(); } // Wake up poll loop since there might be work to do. mLooper->wake(); diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 5efb39e0f2..3c7ddfa67f 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -530,9 +530,6 @@ private: // prevent unneeded wakeups. AnrTracker mAnrTracker GUARDED_BY(mLock); - // Contains the last window which received a hover event. - sp mLastHoverWindowHandle GUARDED_BY(mLock); - void cancelEventsForAnrLocked(const sp& connection) REQUIRES(mLock); // If a focused application changes, we should stop counting down the "no focused window" time, // because we will have no way of knowing when the previous application actually added a window. diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index c21af9e0b5..f120fc9919 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -31,10 +31,30 @@ void TouchState::reset() { *this = TouchState(); } +void TouchState::removeTouchedPointer(int32_t pointerId) { + for (TouchedWindow& touchedWindow : windows) { + touchedWindow.pointerIds.clearBit(pointerId); + } +} + +void TouchState::clearHoveringPointers() { + for (TouchedWindow& touchedWindow : windows) { + touchedWindow.clearHoveringPointers(); + } +} + +void TouchState::clearWindowsWithoutPointers() { + std::erase_if(windows, [](const TouchedWindow& w) { + return w.pointerIds.isEmpty() && !w.hasHoveringPointers(); + }); +} + void TouchState::addOrUpdateWindow(const sp& windowHandle, ftl::Flags targetFlags, BitSet32 pointerIds, std::optional eventTime) { for (TouchedWindow& touchedWindow : windows) { + // We do not compare windows by token here because two windows that share the same token + // may have a different transform if (touchedWindow.windowHandle == windowHandle) { touchedWindow.targetFlags |= targetFlags; if (targetFlags.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT)) { @@ -59,6 +79,21 @@ void TouchState::addOrUpdateWindow(const sp& windowHandle, windows.push_back(touchedWindow); } +void TouchState::addHoveringPointerToWindow(const sp& windowHandle, + int32_t hoveringDeviceId, int32_t hoveringPointerId) { + for (TouchedWindow& touchedWindow : windows) { + if (touchedWindow.windowHandle == windowHandle) { + touchedWindow.addHoveringPointer(hoveringDeviceId, hoveringPointerId); + return; + } + } + + TouchedWindow touchedWindow; + touchedWindow.windowHandle = windowHandle; + touchedWindow.addHoveringPointer(hoveringDeviceId, hoveringPointerId); + windows.push_back(touchedWindow); +} + void TouchState::removeWindowByToken(const sp& token) { for (size_t i = 0; i < windows.size(); i++) { if (windows[i].windowHandle->getToken() == token) { @@ -145,6 +180,26 @@ bool TouchState::isDown() const { [](const TouchedWindow& window) { return !window.pointerIds.isEmpty(); }); } +std::set> TouchState::getWindowsWithHoveringPointer(int32_t hoveringDeviceId, + int32_t pointerId) const { + std::set> out; + for (const TouchedWindow& window : windows) { + if (window.hasHoveringPointer(hoveringDeviceId, pointerId)) { + out.insert(window.windowHandle); + } + } + return out; +} + +void TouchState::removeHoveringPointer(int32_t hoveringDeviceId, int32_t hoveringPointerId) { + for (TouchedWindow& window : windows) { + window.removeHoveringPointer(hoveringDeviceId, hoveringPointerId); + } + std::erase_if(windows, [](const TouchedWindow& w) { + return w.pointerIds.isEmpty() && !w.hasHoveringPointers(); + }); +} + std::string TouchState::dump() const { std::string out; out += StringPrintf("deviceId=%d, source=%s\n", deviceId, diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index 77c1cdf50a..b75e6ef359 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -16,6 +16,7 @@ #pragma once +#include #include "TouchedWindow.h" namespace android { @@ -39,9 +40,16 @@ struct TouchState { TouchState& operator=(const TouchState&) = default; void reset(); + void clearWindowsWithoutPointers(); + + void removeTouchedPointer(int32_t pointerId); void addOrUpdateWindow(const sp& windowHandle, ftl::Flags targetFlags, BitSet32 pointerIds, std::optional eventTime = std::nullopt); + void addHoveringPointerToWindow(const sp& windowHandle, + int32_t deviceId, int32_t hoveringPointerId); + void removeHoveringPointer(int32_t deviceId, int32_t hoveringPointerId); + void clearHoveringPointers(); void removeWindowByToken(const sp& token); void filterNonAsIsTouchWindows(); @@ -56,6 +64,9 @@ struct TouchState { sp getWallpaperWindow() const; // Whether any of the windows are currently being touched bool isDown() const; + + std::set> getWindowsWithHoveringPointer( + int32_t deviceId, int32_t pointerId) const; std::string dump() const; }; diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp index af745988ad..3704edd575 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.cpp +++ b/services/inputflinger/dispatcher/TouchedWindow.cpp @@ -25,11 +25,49 @@ namespace android { namespace inputdispatcher { +bool TouchedWindow::hasHoveringPointers() const { + return !mHoveringPointerIdsByDevice.empty(); +} + +void TouchedWindow::clearHoveringPointers() { + mHoveringPointerIdsByDevice.clear(); +} + +bool TouchedWindow::hasHoveringPointer(int32_t deviceId, int32_t pointerId) const { + auto it = mHoveringPointerIdsByDevice.find(deviceId); + if (it == mHoveringPointerIdsByDevice.end()) { + return false; + } + return it->second.test(pointerId); +} + +void TouchedWindow::addHoveringPointer(int32_t deviceId, int32_t pointerId) { + const auto [it, _] = mHoveringPointerIdsByDevice.insert({deviceId, {}}); + it->second.set(pointerId); +} + +void TouchedWindow::removeHoveringPointer(int32_t deviceId, int32_t pointerId) { + const auto it = mHoveringPointerIdsByDevice.find(deviceId); + if (it == mHoveringPointerIdsByDevice.end()) { + return; + } + it->second.set(pointerId, false); + + if (it->second.none()) { + mHoveringPointerIdsByDevice.erase(deviceId); + } +} + std::string TouchedWindow::dump() const { - return StringPrintf("name='%s', pointerIds=0x%0x, " - "targetFlags=%s, firstDownTimeInTarget=%s\n", + std::string out; + std::string hoveringPointers = + dumpMap(mHoveringPointerIdsByDevice, constToString, bitsetToString); + out += StringPrintf("name='%s', pointerIds=0x%0x, targetFlags=%s, firstDownTimeInTarget=%s, " + "mHoveringPointerIdsByDevice=%s\n", windowHandle->getName().c_str(), pointerIds.value, - targetFlags.string().c_str(), toString(firstDownTimeInTarget).c_str()); + targetFlags.string().c_str(), toString(firstDownTimeInTarget).c_str(), + hoveringPointers.c_str()); + return out; } } // namespace inputdispatcher diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h index dd08323dd4..add6b6120e 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -17,7 +17,9 @@ #pragma once #include +#include #include +#include #include "InputTarget.h" namespace android { @@ -33,7 +35,17 @@ struct TouchedWindow { // Time at which the first action down occurred on this window. // NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE scenario. std::optional firstDownTimeInTarget; + + bool hasHoveringPointers() const; + + bool hasHoveringPointer(int32_t deviceId, int32_t pointerId) const; + void addHoveringPointer(int32_t deviceId, int32_t pointerId); + void removeHoveringPointer(int32_t deviceId, int32_t pointerId); + void clearHoveringPointers(); std::string dump() const; + +private: + std::map> mHoveringPointerIdsByDevice; }; } // namespace inputdispatcher diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 41c174a1f7..9881cd4c7c 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -2135,7 +2135,6 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .y(400)) .build())); windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); - windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); // Move cursor into left window ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -2148,7 +2147,6 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .build())); windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); - windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); // Inject a series of mouse events for a mouse click ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -2206,7 +2204,6 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .build())); windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); - windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); // No more events windowLeft->assertNoEvents(); @@ -2268,7 +2265,6 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { .y(400)) .build())); window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); - // Inject a series of mouse events for a mouse click ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, @@ -2325,9 +2321,39 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); } +/** + * Hover over a window, and then remove that window. Make sure that HOVER_EXIT for that event + * is generated. + */ +TEST_F(InputDispatcherTest, HoverExitIsSentToRemovedWindow) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 1200, 800)); + + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, + AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .x(300) + .y(400)) + .build())); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + + // Remove the window, but keep the channel. + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}}); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); +} + /** * Inject a mouse hover event followed by a tap from touchscreen. - * In the current implementation, the tap does not cause a HOVER_EXIT event. + * The tap causes a HOVER_EXIT event to be generated because the current event + * stream's source has been switched. */ TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) { std::shared_ptr application = std::make_shared(); @@ -2347,14 +2373,15 @@ TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) { ASSERT_NO_FATAL_FAILURE( window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithSource(AINPUT_SOURCE_MOUSE)))); - ASSERT_NO_FATAL_FAILURE( - window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithSource(AINPUT_SOURCE_MOUSE)))); // Tap on the window motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {{10, 10}}); mDispatcher->notifyMotion(&motionArgs); + ASSERT_NO_FATAL_FAILURE( + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithSource(AINPUT_SOURCE_MOUSE)))); + ASSERT_NO_FATAL_FAILURE( window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN)))); @@ -2393,7 +2420,6 @@ TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) { .y(600)) .build())); windowDefaultDisplay->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); - windowDefaultDisplay->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); // Remove all windows in secondary display and check that no event happens on window in // primary display. -- GitLab From 39d37cfb471c69f924e90f8e35afcff4ad4e6042 Mon Sep 17 00:00:00 2001 From: Sam Dubey Date: Wed, 7 Dec 2022 18:05:35 +0000 Subject: [PATCH 0583/1310] Revert "Track hovering pointers explicitly" Revert "Remove duplicate ACTION_HOVER_MOVE" Revert submission 20414906-refactor hovering pointers Reason for revert: Part of DM+Platinum monitor efforts likely Likely causing b/261731836. This won't be submitted if proven otherwise. Reverted Changes: I3665d8e68:Remove duplicate ACTION_HOVER_MOVE I047926e53:Track hovering pointers explicitly Change-Id: I64b682558071661d79dca953d449251f79ee7004 --- include/input/PrintTools.h | 6 - .../dispatcher/InputDispatcher.cpp | 164 +++++++----------- .../inputflinger/dispatcher/InputDispatcher.h | 3 + .../inputflinger/dispatcher/TouchState.cpp | 55 ------ services/inputflinger/dispatcher/TouchState.h | 11 -- .../inputflinger/dispatcher/TouchedWindow.cpp | 44 +---- .../inputflinger/dispatcher/TouchedWindow.h | 12 -- .../tests/InputDispatcher_test.cpp | 44 +---- 8 files changed, 79 insertions(+), 260 deletions(-) diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h index a20544b5fd..e24344b3f1 100644 --- a/include/input/PrintTools.h +++ b/include/input/PrintTools.h @@ -16,7 +16,6 @@ #pragma once -#include #include #include #include @@ -24,11 +23,6 @@ namespace android { -template -std::string bitsetToString(const std::bitset& bitset) { - return bitset.to_string(); -} - template inline std::string constToString(const T& v) { return std::to_string(v); diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 4d3e6def00..4aac377b42 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -554,65 +554,6 @@ std::optional getDownTime(const EventEntry& eventEntry) { return std::nullopt; } -/** - * Compare the old touch state to the new touch state, and generate the corresponding touched - * windows (== input targets). - * If a window had the hovering pointer, but now it doesn't, produce HOVER_EXIT for that window. - * If the pointer just entered the new window, produce HOVER_ENTER. - * For pointers remaining in the window, produce HOVER_MOVE. - */ -std::vector getHoveringWindowsLocked(const TouchState* oldState, - const TouchState& newTouchState, - const MotionEntry& entry) { - std::vector out; - const int32_t maskedAction = MotionEvent::getActionMasked(entry.action); - if (maskedAction != AMOTION_EVENT_ACTION_HOVER_ENTER && - maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE && - maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) { - // Not a hover event - don't need to do anything - return out; - } - - // We should consider all hovering pointers here. But for now, just use the first one - const int32_t pointerId = entry.pointerProperties[0].id; - - std::set> oldWindows; - if (oldState != nullptr) { - oldWindows = oldState->getWindowsWithHoveringPointer(entry.deviceId, pointerId); - } - - std::set> newWindows = - newTouchState.getWindowsWithHoveringPointer(entry.deviceId, pointerId); - - // If the pointer is no longer in the new window set, send HOVER_EXIT. - for (const sp& oldWindow : oldWindows) { - if (newWindows.find(oldWindow) == newWindows.end()) { - TouchedWindow touchedWindow; - touchedWindow.windowHandle = oldWindow; - touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_HOVER_EXIT; - touchedWindow.pointerIds.markBit(pointerId); - out.push_back(touchedWindow); - } - } - - for (const sp& newWindow : newWindows) { - TouchedWindow touchedWindow; - touchedWindow.windowHandle = newWindow; - if (oldWindows.find(newWindow) == oldWindows.end()) { - // Any windows that have this pointer now, and didn't have it before, should get - // HOVER_ENTER - touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_HOVER_ENTER; - } else { - // This pointer was already sent to the window. Use ACTION_HOVER_MOVE. - LOG_ALWAYS_FATAL_IF(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE); - touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_IS; - } - touchedWindow.pointerIds.markBit(pointerId); - out.push_back(touchedWindow); - } - return out; -} - } // namespace // --- InputDispatcher --- @@ -2142,6 +2083,8 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( // Update the touch state as needed based on the properties of the touch event. outInjectionResult = InputEventInjectionResult::PENDING; + sp newHoverWindowHandle(mLastHoverWindowHandle); + sp newTouchedWindowHandle; // Copy current touch state into tempTouchState. // This state will be used to update mTouchStatesByDisplay at the end of this function. @@ -2174,7 +2117,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( outInjectionResult = InputEventInjectionResult::FAILED; return touchedWindows; // wrong device } - tempTouchState.clearWindowsWithoutPointers(); + tempTouchState.reset(); tempTouchState.deviceId = entry.deviceId; tempTouchState.source = entry.source; isSplit = false; @@ -2187,21 +2130,14 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( return touchedWindows; // wrong device } - if (isHoverAction) { - // For hover actions, we will treat 'tempTouchState' as a new state, so let's erase - // all of the existing hovering pointers and recompute. - tempTouchState.clearHoveringPointers(); - } - if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) { /* Case 1: New splittable pointer going down, or need target for hover or scroll. */ const auto [x, y] = resolveTouchedPosition(entry); const int32_t pointerIndex = getMotionEventActionPointerIndex(action); const bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN; const bool isStylus = isPointerFromStylus(entry, pointerIndex); - sp newTouchedWindowHandle = - findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, isStylus, - isDown /*addOutsideTargets*/); + newTouchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, + isStylus, isDown /*addOutsideTargets*/); // Handle the case where we did not find a window. if (newTouchedWindowHandle == nullptr) { @@ -2236,6 +2172,15 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( isSplit = !isFromMouse; } + // Update hover state. + if (newTouchedWindowHandle != nullptr) { + if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) { + newHoverWindowHandle = nullptr; + } else if (isHoverAction) { + newHoverWindowHandle = newTouchedWindowHandle; + } + } + std::vector> newTouchedWindows = findTouchedSpyWindowsAtLocked(displayId, x, y, isStylus); if (newTouchedWindowHandle != nullptr) { @@ -2255,18 +2200,6 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( continue; } - if (isHoverAction) { - const int32_t pointerId = entry.pointerProperties[0].id; - if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) { - // Pointer left. Remove it - tempTouchState.removeHoveringPointer(entry.deviceId, pointerId); - } else { - // The "windowHandle" is the target of this hovering pointer. - tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, - pointerId); - } - } - // Set target flags. ftl::Flags targetFlags = InputTarget::Flags::DISPATCH_AS_IS; @@ -2286,9 +2219,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( // Update the temporary touch state. BitSet32 pointerIds; - if (!isHoverAction) { - pointerIds.markBit(entry.pointerProperties[pointerIndex].id); - } + pointerIds.markBit(entry.pointerProperties[pointerIndex].id); tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds, entry.eventTime); @@ -2324,7 +2255,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( const bool isStylus = isPointerFromStylus(entry, 0 /*pointerIndex*/); sp oldTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle(); - sp newTouchedWindowHandle = + newTouchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, isStylus); // Verify targeted injection. @@ -2395,11 +2326,36 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } // Update dispatching for hover enter and exit. - { - std::vector hoveringWindows = - getHoveringWindowsLocked(oldState, tempTouchState, entry); - touchedWindows.insert(touchedWindows.end(), hoveringWindows.begin(), hoveringWindows.end()); + if (newHoverWindowHandle != mLastHoverWindowHandle) { + // Let the previous window know that the hover sequence is over, unless we already did + // it when dispatching it as is to newTouchedWindowHandle. + if (mLastHoverWindowHandle != nullptr && + (maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT || + mLastHoverWindowHandle != newTouchedWindowHandle)) { + if (DEBUG_HOVER) { + ALOGD("Sending hover exit event to window %s.", + mLastHoverWindowHandle->getName().c_str()); + } + tempTouchState.addOrUpdateWindow(mLastHoverWindowHandle, + InputTarget::Flags::DISPATCH_AS_HOVER_EXIT, + BitSet32(0)); + } + + // Let the new window know that the hover sequence is starting, unless we already did it + // when dispatching it as is to newTouchedWindowHandle. + if (newHoverWindowHandle != nullptr && + (maskedAction != AMOTION_EVENT_ACTION_HOVER_ENTER || + newHoverWindowHandle != newTouchedWindowHandle)) { + if (DEBUG_HOVER) { + ALOGD("Sending hover enter event to window %s.", + newHoverWindowHandle->getName().c_str()); + } + tempTouchState.addOrUpdateWindow(newHoverWindowHandle, + InputTarget::Flags::DISPATCH_AS_HOVER_ENTER, + BitSet32(0)); + } } + // Ensure that we have at least one foreground window or at least one window that cannot be a // foreground target. If we only have windows that are not receiving foreground touches (e.g. we // only have windows getting ACTION_OUTSIDE), then drop the event, because there is no window @@ -2489,13 +2445,10 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } } - // Success! Output targets for everything except hovers. - if (!isHoverAction) { - touchedWindows.insert(touchedWindows.end(), tempTouchState.windows.begin(), - tempTouchState.windows.end()); - } - + // Success! Output targets. + touchedWindows = tempTouchState.windows; outInjectionResult = InputEventInjectionResult::SUCCEEDED; + // Drop the outside or hover touch windows since we will not care about them // in the next iteration. tempTouchState.filterNonAsIsTouchWindows(); @@ -2516,16 +2469,14 @@ Failed: "Conflicting pointer actions: Hover received while pointer was down."); *outConflictingPointerActions = true; } + tempTouchState.reset(); if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) { tempTouchState.deviceId = entry.deviceId; tempTouchState.source = entry.source; } - } else if (maskedAction == AMOTION_EVENT_ACTION_UP) { - // Pointer went up. - tempTouchState.removeTouchedPointer(entry.pointerProperties[0].id); - tempTouchState.clearWindowsWithoutPointers(); - } else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) { + } else if (maskedAction == AMOTION_EVENT_ACTION_UP || + maskedAction == AMOTION_EVENT_ACTION_CANCEL) { // All pointers up or canceled. tempTouchState.reset(); } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { @@ -2564,6 +2515,9 @@ Failed: mTouchStatesByDisplay.erase(displayId); } + // Update hover state. + mLastHoverWindowHandle = newHoverWindowHandle; + return touchedWindows; } @@ -4855,6 +4809,14 @@ void InputDispatcher::setInputWindowsLocked( updateWindowHandlesForDisplayLocked(windowInfoHandles, displayId); const std::vector>& windowHandles = getWindowHandlesLocked(displayId); + if (mLastHoverWindowHandle) { + const WindowInfo* lastHoverWindowInfo = mLastHoverWindowHandle->getInfo(); + if (lastHoverWindowInfo->displayId == displayId && + std::find(windowHandles.begin(), windowHandles.end(), mLastHoverWindowHandle) == + windowHandles.end()) { + mLastHoverWindowHandle = nullptr; + } + } std::optional changes = mFocusResolver.setInputWindows(displayId, windowHandles); @@ -5302,6 +5264,7 @@ void InputDispatcher::resetAndDropEverythingLocked(const char* reason) { mAnrTracker.clear(); mTouchStatesByDisplay.clear(); + mLastHoverWindowHandle.clear(); mReplacedKeys.clear(); } @@ -6491,6 +6454,7 @@ void InputDispatcher::cancelCurrentTouch() { synthesizeCancelationEventsForAllConnectionsLocked(options); mTouchStatesByDisplay.clear(); + mLastHoverWindowHandle.clear(); } // Wake up poll loop since there might be work to do. mLooper->wake(); diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 3c7ddfa67f..5efb39e0f2 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -530,6 +530,9 @@ private: // prevent unneeded wakeups. AnrTracker mAnrTracker GUARDED_BY(mLock); + // Contains the last window which received a hover event. + sp mLastHoverWindowHandle GUARDED_BY(mLock); + void cancelEventsForAnrLocked(const sp& connection) REQUIRES(mLock); // If a focused application changes, we should stop counting down the "no focused window" time, // because we will have no way of knowing when the previous application actually added a window. diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index f120fc9919..c21af9e0b5 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -31,30 +31,10 @@ void TouchState::reset() { *this = TouchState(); } -void TouchState::removeTouchedPointer(int32_t pointerId) { - for (TouchedWindow& touchedWindow : windows) { - touchedWindow.pointerIds.clearBit(pointerId); - } -} - -void TouchState::clearHoveringPointers() { - for (TouchedWindow& touchedWindow : windows) { - touchedWindow.clearHoveringPointers(); - } -} - -void TouchState::clearWindowsWithoutPointers() { - std::erase_if(windows, [](const TouchedWindow& w) { - return w.pointerIds.isEmpty() && !w.hasHoveringPointers(); - }); -} - void TouchState::addOrUpdateWindow(const sp& windowHandle, ftl::Flags targetFlags, BitSet32 pointerIds, std::optional eventTime) { for (TouchedWindow& touchedWindow : windows) { - // We do not compare windows by token here because two windows that share the same token - // may have a different transform if (touchedWindow.windowHandle == windowHandle) { touchedWindow.targetFlags |= targetFlags; if (targetFlags.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT)) { @@ -79,21 +59,6 @@ void TouchState::addOrUpdateWindow(const sp& windowHandle, windows.push_back(touchedWindow); } -void TouchState::addHoveringPointerToWindow(const sp& windowHandle, - int32_t hoveringDeviceId, int32_t hoveringPointerId) { - for (TouchedWindow& touchedWindow : windows) { - if (touchedWindow.windowHandle == windowHandle) { - touchedWindow.addHoveringPointer(hoveringDeviceId, hoveringPointerId); - return; - } - } - - TouchedWindow touchedWindow; - touchedWindow.windowHandle = windowHandle; - touchedWindow.addHoveringPointer(hoveringDeviceId, hoveringPointerId); - windows.push_back(touchedWindow); -} - void TouchState::removeWindowByToken(const sp& token) { for (size_t i = 0; i < windows.size(); i++) { if (windows[i].windowHandle->getToken() == token) { @@ -180,26 +145,6 @@ bool TouchState::isDown() const { [](const TouchedWindow& window) { return !window.pointerIds.isEmpty(); }); } -std::set> TouchState::getWindowsWithHoveringPointer(int32_t hoveringDeviceId, - int32_t pointerId) const { - std::set> out; - for (const TouchedWindow& window : windows) { - if (window.hasHoveringPointer(hoveringDeviceId, pointerId)) { - out.insert(window.windowHandle); - } - } - return out; -} - -void TouchState::removeHoveringPointer(int32_t hoveringDeviceId, int32_t hoveringPointerId) { - for (TouchedWindow& window : windows) { - window.removeHoveringPointer(hoveringDeviceId, hoveringPointerId); - } - std::erase_if(windows, [](const TouchedWindow& w) { - return w.pointerIds.isEmpty() && !w.hasHoveringPointers(); - }); -} - std::string TouchState::dump() const { std::string out; out += StringPrintf("deviceId=%d, source=%s\n", deviceId, diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index b75e6ef359..77c1cdf50a 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -16,7 +16,6 @@ #pragma once -#include #include "TouchedWindow.h" namespace android { @@ -40,16 +39,9 @@ struct TouchState { TouchState& operator=(const TouchState&) = default; void reset(); - void clearWindowsWithoutPointers(); - - void removeTouchedPointer(int32_t pointerId); void addOrUpdateWindow(const sp& windowHandle, ftl::Flags targetFlags, BitSet32 pointerIds, std::optional eventTime = std::nullopt); - void addHoveringPointerToWindow(const sp& windowHandle, - int32_t deviceId, int32_t hoveringPointerId); - void removeHoveringPointer(int32_t deviceId, int32_t hoveringPointerId); - void clearHoveringPointers(); void removeWindowByToken(const sp& token); void filterNonAsIsTouchWindows(); @@ -64,9 +56,6 @@ struct TouchState { sp getWallpaperWindow() const; // Whether any of the windows are currently being touched bool isDown() const; - - std::set> getWindowsWithHoveringPointer( - int32_t deviceId, int32_t pointerId) const; std::string dump() const; }; diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp index 3704edd575..af745988ad 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.cpp +++ b/services/inputflinger/dispatcher/TouchedWindow.cpp @@ -25,49 +25,11 @@ namespace android { namespace inputdispatcher { -bool TouchedWindow::hasHoveringPointers() const { - return !mHoveringPointerIdsByDevice.empty(); -} - -void TouchedWindow::clearHoveringPointers() { - mHoveringPointerIdsByDevice.clear(); -} - -bool TouchedWindow::hasHoveringPointer(int32_t deviceId, int32_t pointerId) const { - auto it = mHoveringPointerIdsByDevice.find(deviceId); - if (it == mHoveringPointerIdsByDevice.end()) { - return false; - } - return it->second.test(pointerId); -} - -void TouchedWindow::addHoveringPointer(int32_t deviceId, int32_t pointerId) { - const auto [it, _] = mHoveringPointerIdsByDevice.insert({deviceId, {}}); - it->second.set(pointerId); -} - -void TouchedWindow::removeHoveringPointer(int32_t deviceId, int32_t pointerId) { - const auto it = mHoveringPointerIdsByDevice.find(deviceId); - if (it == mHoveringPointerIdsByDevice.end()) { - return; - } - it->second.set(pointerId, false); - - if (it->second.none()) { - mHoveringPointerIdsByDevice.erase(deviceId); - } -} - std::string TouchedWindow::dump() const { - std::string out; - std::string hoveringPointers = - dumpMap(mHoveringPointerIdsByDevice, constToString, bitsetToString); - out += StringPrintf("name='%s', pointerIds=0x%0x, targetFlags=%s, firstDownTimeInTarget=%s, " - "mHoveringPointerIdsByDevice=%s\n", + return StringPrintf("name='%s', pointerIds=0x%0x, " + "targetFlags=%s, firstDownTimeInTarget=%s\n", windowHandle->getName().c_str(), pointerIds.value, - targetFlags.string().c_str(), toString(firstDownTimeInTarget).c_str(), - hoveringPointers.c_str()); - return out; + targetFlags.string().c_str(), toString(firstDownTimeInTarget).c_str()); } } // namespace inputdispatcher diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h index add6b6120e..dd08323dd4 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -17,9 +17,7 @@ #pragma once #include -#include #include -#include #include "InputTarget.h" namespace android { @@ -35,17 +33,7 @@ struct TouchedWindow { // Time at which the first action down occurred on this window. // NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE scenario. std::optional firstDownTimeInTarget; - - bool hasHoveringPointers() const; - - bool hasHoveringPointer(int32_t deviceId, int32_t pointerId) const; - void addHoveringPointer(int32_t deviceId, int32_t pointerId); - void removeHoveringPointer(int32_t deviceId, int32_t pointerId); - void clearHoveringPointers(); std::string dump() const; - -private: - std::map> mHoveringPointerIdsByDevice; }; } // namespace inputdispatcher diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 9881cd4c7c..41c174a1f7 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -2135,6 +2135,7 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .y(400)) .build())); windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); // Move cursor into left window ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -2147,6 +2148,7 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .build())); windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); // Inject a series of mouse events for a mouse click ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -2204,6 +2206,7 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .build())); windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); // No more events windowLeft->assertNoEvents(); @@ -2265,6 +2268,7 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { .y(400)) .build())); window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + // Inject a series of mouse events for a mouse click ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, @@ -2321,39 +2325,9 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); } -/** - * Hover over a window, and then remove that window. Make sure that HOVER_EXIT for that event - * is generated. - */ -TEST_F(InputDispatcherTest, HoverExitIsSentToRemovedWindow) { - std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); - window->setFrame(Rect(0, 0, 1200, 800)); - - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); - - mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); - - ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionEvent(mDispatcher, - MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, - AINPUT_SOURCE_MOUSE) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) - .build())); - window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); - - // Remove the window, but keep the channel. - mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}}); - window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); -} - /** * Inject a mouse hover event followed by a tap from touchscreen. - * The tap causes a HOVER_EXIT event to be generated because the current event - * stream's source has been switched. + * In the current implementation, the tap does not cause a HOVER_EXIT event. */ TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) { std::shared_ptr application = std::make_shared(); @@ -2373,15 +2347,14 @@ TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) { ASSERT_NO_FATAL_FAILURE( window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithSource(AINPUT_SOURCE_MOUSE)))); + ASSERT_NO_FATAL_FAILURE( + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithSource(AINPUT_SOURCE_MOUSE)))); // Tap on the window motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {{10, 10}}); mDispatcher->notifyMotion(&motionArgs); - ASSERT_NO_FATAL_FAILURE( - window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), - WithSource(AINPUT_SOURCE_MOUSE)))); - ASSERT_NO_FATAL_FAILURE( window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN)))); @@ -2420,6 +2393,7 @@ TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) { .y(600)) .build())); windowDefaultDisplay->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + windowDefaultDisplay->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); // Remove all windows in secondary display and check that no event happens on window in // primary display. -- GitLab From 6bb12824db3c540775b2b737331ed3f448a50e2e Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Wed, 5 Oct 2022 11:42:30 -0700 Subject: [PATCH 0584/1310] Add security check to getPhysicalDisplayToken binder function. - There is a possible way to take over the screen display and swap the display content due to a missing permission check. - Add a short-term fix for WCG checking failure because of new permission check added to SF::getPhysicalDisplayToken: change two function signatures (getStaticDisplayInfo and getDynamicDisplayInfo). - To make short-term fix workable, split getDynamicDisplayInfo binder call into two, one is to take display id, one is to take display token as old codes show to avoid huge modification on other callees. Bug: 248031255 Test: test using displaytoken app manually on the phone, test shell screenrecord during using displaytoken; atest android.hardware.camera2.cts.FastBasicsTest Change-Id: Id9d9012d4ede9c8330f0ce1096bcb78e51b7c5df --- libs/gui/LayerState.cpp | 21 ++ libs/gui/SurfaceComposerClient.cpp | 117 +++++----- .../aidl/android/gui/ISurfaceComposer.aidl | 6 +- libs/gui/fuzzer/libgui_fuzzer_utils.h | 6 +- libs/gui/include/gui/LayerState.h | 1 + libs/gui/include/gui/SurfaceComposerClient.h | 12 +- libs/gui/tests/Surface_test.cpp | 11 +- libs/nativedisplay/ADisplay.cpp | 23 +- services/surfaceflinger/SurfaceFlinger.cpp | 200 +++++++++++------- services/surfaceflinger/SurfaceFlinger.h | 21 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 18 +- .../surfaceflinger/tests/Credentials_test.cpp | 37 ++-- .../tests/DisplayConfigs_test.cpp | 6 +- .../SurfaceFlinger_ExcludeDolbyVisionTest.cpp | 6 +- .../tests/unittests/TestableSurfaceFlinger.h | 6 +- 15 files changed, 298 insertions(+), 193 deletions(-) diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 95962afda1..59b62fe58c 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -388,6 +388,27 @@ void DisplayState::merge(const DisplayState& other) { } } +void DisplayState::sanitize(int32_t permissions) { + if (what & DisplayState::eLayerStackChanged) { + if (!(permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER)) { + what &= ~DisplayState::eLayerStackChanged; + ALOGE("Stripped attempt to set eLayerStackChanged in sanitize"); + } + } + if (what & DisplayState::eDisplayProjectionChanged) { + if (!(permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER)) { + what &= ~DisplayState::eDisplayProjectionChanged; + ALOGE("Stripped attempt to set eDisplayProjectionChanged in sanitize"); + } + } + if (what & DisplayState::eSurfaceChanged) { + if (!(permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER)) { + what &= ~DisplayState::eSurfaceChanged; + ALOGE("Stripped attempt to set eSurfaceChanged in sanitize"); + } + } +} + void layer_state_t::sanitize(int32_t permissions) { // TODO: b/109894387 // diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 325c294762..abe5d35d0e 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2260,12 +2260,12 @@ status_t SurfaceComposerClient::getDisplayState(const sp& display, return statusTFromBinderStatus(status); } -status_t SurfaceComposerClient::getStaticDisplayInfo(const sp& display, +status_t SurfaceComposerClient::getStaticDisplayInfo(int64_t displayId, ui::StaticDisplayInfo* outInfo) { using Tag = android::gui::DeviceProductInfo::ManufactureOrModelDate::Tag; gui::StaticDisplayInfo ginfo; binder::Status status = - ComposerServiceAIDL::getComposerService()->getStaticDisplayInfo(display, &ginfo); + ComposerServiceAIDL::getComposerService()->getStaticDisplayInfo(displayId, &ginfo); if (status.isOk()) { // convert gui::StaticDisplayInfo to ui::StaticDisplayInfo outInfo->connectionType = static_cast(ginfo.connectionType); @@ -2309,56 +2309,74 @@ status_t SurfaceComposerClient::getStaticDisplayInfo(const sp& display, return statusTFromBinderStatus(status); } -status_t SurfaceComposerClient::getDynamicDisplayInfo(const sp& display, - ui::DynamicDisplayInfo* outInfo) { +void SurfaceComposerClient::getDynamicDisplayInfoInternal(gui::DynamicDisplayInfo& ginfo, + ui::DynamicDisplayInfo*& outInfo) { + // convert gui::DynamicDisplayInfo to ui::DynamicDisplayInfo + outInfo->supportedDisplayModes.clear(); + outInfo->supportedDisplayModes.reserve(ginfo.supportedDisplayModes.size()); + for (const auto& mode : ginfo.supportedDisplayModes) { + ui::DisplayMode outMode; + outMode.id = mode.id; + outMode.resolution.width = mode.resolution.width; + outMode.resolution.height = mode.resolution.height; + outMode.xDpi = mode.xDpi; + outMode.yDpi = mode.yDpi; + outMode.refreshRate = mode.refreshRate; + outMode.appVsyncOffset = mode.appVsyncOffset; + outMode.sfVsyncOffset = mode.sfVsyncOffset; + outMode.presentationDeadline = mode.presentationDeadline; + outMode.group = mode.group; + std::transform(mode.supportedHdrTypes.begin(), mode.supportedHdrTypes.end(), + std::back_inserter(outMode.supportedHdrTypes), + [](const int32_t& value) { return static_cast(value); }); + outInfo->supportedDisplayModes.push_back(outMode); + } + + outInfo->activeDisplayModeId = ginfo.activeDisplayModeId; + outInfo->renderFrameRate = ginfo.renderFrameRate; + + outInfo->supportedColorModes.clear(); + outInfo->supportedColorModes.reserve(ginfo.supportedColorModes.size()); + for (const auto& cmode : ginfo.supportedColorModes) { + outInfo->supportedColorModes.push_back(static_cast(cmode)); + } + + outInfo->activeColorMode = static_cast(ginfo.activeColorMode); + + std::vector types; + types.reserve(ginfo.hdrCapabilities.supportedHdrTypes.size()); + for (const auto& hdr : ginfo.hdrCapabilities.supportedHdrTypes) { + types.push_back(static_cast(hdr)); + } + outInfo->hdrCapabilities = HdrCapabilities(types, ginfo.hdrCapabilities.maxLuminance, + ginfo.hdrCapabilities.maxAverageLuminance, + ginfo.hdrCapabilities.minLuminance); + + outInfo->autoLowLatencyModeSupported = ginfo.autoLowLatencyModeSupported; + outInfo->gameContentTypeSupported = ginfo.gameContentTypeSupported; + outInfo->preferredBootDisplayMode = ginfo.preferredBootDisplayMode; +} + +status_t SurfaceComposerClient::getDynamicDisplayInfoFromId(int64_t displayId, + ui::DynamicDisplayInfo* outInfo) { gui::DynamicDisplayInfo ginfo; binder::Status status = - ComposerServiceAIDL::getComposerService()->getDynamicDisplayInfo(display, &ginfo); + ComposerServiceAIDL::getComposerService()->getDynamicDisplayInfoFromId(displayId, + &ginfo); if (status.isOk()) { - // convert gui::DynamicDisplayInfo to ui::DynamicDisplayInfo - outInfo->supportedDisplayModes.clear(); - outInfo->supportedDisplayModes.reserve(ginfo.supportedDisplayModes.size()); - for (const auto& mode : ginfo.supportedDisplayModes) { - ui::DisplayMode outMode; - outMode.id = mode.id; - outMode.resolution.width = mode.resolution.width; - outMode.resolution.height = mode.resolution.height; - outMode.xDpi = mode.xDpi; - outMode.yDpi = mode.yDpi; - outMode.refreshRate = mode.refreshRate; - outMode.appVsyncOffset = mode.appVsyncOffset; - outMode.sfVsyncOffset = mode.sfVsyncOffset; - outMode.presentationDeadline = mode.presentationDeadline; - outMode.group = mode.group; - std::transform(mode.supportedHdrTypes.begin(), mode.supportedHdrTypes.end(), - std::back_inserter(outMode.supportedHdrTypes), - [](const int32_t& value) { return static_cast(value); }); - outInfo->supportedDisplayModes.push_back(outMode); - } - - outInfo->activeDisplayModeId = ginfo.activeDisplayModeId; - outInfo->renderFrameRate = ginfo.renderFrameRate; - - outInfo->supportedColorModes.clear(); - outInfo->supportedColorModes.reserve(ginfo.supportedColorModes.size()); - for (const auto& cmode : ginfo.supportedColorModes) { - outInfo->supportedColorModes.push_back(static_cast(cmode)); - } - - outInfo->activeColorMode = static_cast(ginfo.activeColorMode); - - std::vector types; - types.reserve(ginfo.hdrCapabilities.supportedHdrTypes.size()); - for (const auto& hdr : ginfo.hdrCapabilities.supportedHdrTypes) { - types.push_back(static_cast(hdr)); - } - outInfo->hdrCapabilities = HdrCapabilities(types, ginfo.hdrCapabilities.maxLuminance, - ginfo.hdrCapabilities.maxAverageLuminance, - ginfo.hdrCapabilities.minLuminance); + getDynamicDisplayInfoInternal(ginfo, outInfo); + } + return statusTFromBinderStatus(status); +} - outInfo->autoLowLatencyModeSupported = ginfo.autoLowLatencyModeSupported; - outInfo->gameContentTypeSupported = ginfo.gameContentTypeSupported; - outInfo->preferredBootDisplayMode = ginfo.preferredBootDisplayMode; +status_t SurfaceComposerClient::getDynamicDisplayInfoFromToken(const sp& display, + ui::DynamicDisplayInfo* outInfo) { + gui::DynamicDisplayInfo ginfo; + binder::Status status = + ComposerServiceAIDL::getComposerService()->getDynamicDisplayInfoFromToken(display, + &ginfo); + if (status.isOk()) { + getDynamicDisplayInfoInternal(ginfo, outInfo); } return statusTFromBinderStatus(status); } @@ -2366,7 +2384,8 @@ status_t SurfaceComposerClient::getDynamicDisplayInfo(const sp& display status_t SurfaceComposerClient::getActiveDisplayMode(const sp& display, ui::DisplayMode* mode) { ui::DynamicDisplayInfo info; - status_t result = getDynamicDisplayInfo(display, &info); + + status_t result = getDynamicDisplayInfoFromToken(display, &info); if (result != NO_ERROR) { return result; } diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 40410fb59e..488a1486cc 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -126,12 +126,14 @@ interface ISurfaceComposer { /** * Gets immutable information about given physical display. */ - StaticDisplayInfo getStaticDisplayInfo(IBinder display); + StaticDisplayInfo getStaticDisplayInfo(long displayId); /** * Gets dynamic information about given physical display. */ - DynamicDisplayInfo getDynamicDisplayInfo(IBinder display); + DynamicDisplayInfo getDynamicDisplayInfoFromId(long displayId); + + DynamicDisplayInfo getDynamicDisplayInfoFromToken(IBinder display); DisplayPrimaries getDisplayNativePrimaries(IBinder display); diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h index 9d1ee8f65b..8810e4e83a 100644 --- a/libs/gui/fuzzer/libgui_fuzzer_utils.h +++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h @@ -79,9 +79,11 @@ public: (override)); MOCK_METHOD(binder::Status, getDisplayState, (const sp&, gui::DisplayState*), (override)); - MOCK_METHOD(binder::Status, getStaticDisplayInfo, (const sp&, gui::StaticDisplayInfo*), + MOCK_METHOD(binder::Status, getStaticDisplayInfo, (int64_t, gui::StaticDisplayInfo*), (override)); - MOCK_METHOD(binder::Status, getDynamicDisplayInfo, + MOCK_METHOD(binder::Status, getDynamicDisplayInfoFromId, (int64_t, gui::DynamicDisplayInfo*), + (override)); + MOCK_METHOD(binder::Status, getDynamicDisplayInfoFromToken, (const sp&, gui::DynamicDisplayInfo*), (override)); MOCK_METHOD(binder::Status, getDisplayNativePrimaries, (const sp&, gui::DisplayPrimaries*), (override)); diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 6ec6bd70db..45a84f6c66 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -359,6 +359,7 @@ struct DisplayState { DisplayState(); void merge(const DisplayState& other); + void sanitize(int32_t permissions); uint32_t what = 0; uint32_t flags = 0; diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 2038f1477a..df47002b3b 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -149,10 +149,10 @@ public: static status_t getDisplayState(const sp& display, ui::DisplayState*); // Get immutable information about given physical display. - static status_t getStaticDisplayInfo(const sp& display, ui::StaticDisplayInfo*); + static status_t getStaticDisplayInfo(int64_t, ui::StaticDisplayInfo*); - // Get dynamic information about given physical display. - static status_t getDynamicDisplayInfo(const sp& display, ui::DynamicDisplayInfo*); + // Get dynamic information about given physical display from display id + static status_t getDynamicDisplayInfoFromId(int64_t, ui::DynamicDisplayInfo*); // Shorthand for the active display mode from getDynamicDisplayInfo(). // TODO(b/180391891): Update clients to use getDynamicDisplayInfo and remove this function. @@ -714,6 +714,12 @@ protected: ReleaseCallbackThread mReleaseCallbackThread; private: + // Get dynamic information about given physical display from token + static status_t getDynamicDisplayInfoFromToken(const sp& display, + ui::DynamicDisplayInfo*); + + static void getDynamicDisplayInfoInternal(gui::DynamicDisplayInfo& ginfo, + ui::DynamicDisplayInfo*& outInfo); virtual void onFirstRef(); mutable Mutex mLock; diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 6d3b42515b..55242dfd39 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -782,13 +782,18 @@ public: return binder::Status::ok(); } - binder::Status getStaticDisplayInfo(const sp& /*display*/, + binder::Status getStaticDisplayInfo(int64_t /*displayId*/, gui::StaticDisplayInfo* /*outInfo*/) override { return binder::Status::ok(); } - binder::Status getDynamicDisplayInfo(const sp& /*display*/, - gui::DynamicDisplayInfo* /*outInfo*/) override { + binder::Status getDynamicDisplayInfoFromId(int64_t /*displayId*/, + gui::DynamicDisplayInfo* /*outInfo*/) override { + return binder::Status::ok(); + } + + binder::Status getDynamicDisplayInfoFromToken(const sp& /*display*/, + gui::DynamicDisplayInfo* /*outInfo*/) override { return binder::Status::ok(); } diff --git a/libs/nativedisplay/ADisplay.cpp b/libs/nativedisplay/ADisplay.cpp index 60328e48a6..bf0805b46c 100644 --- a/libs/nativedisplay/ADisplay.cpp +++ b/libs/nativedisplay/ADisplay.cpp @@ -117,15 +117,6 @@ using namespace android::display::impl; #define CHECK_NOT_NULL(name) \ LOG_ALWAYS_FATAL_IF(name == nullptr, "nullptr passed as " #name " argument"); -namespace { - -sp getToken(ADisplay* display) { - DisplayImpl* impl = reinterpret_cast(display); - return SurfaceComposerClient::getPhysicalDisplayToken(impl->id); -} - -} // namespace - namespace android { int ADisplay_acquirePhysicalDisplays(ADisplay*** outDisplays) { @@ -139,10 +130,9 @@ int ADisplay_acquirePhysicalDisplays(ADisplay*** outDisplays) { ui::DisplayConnectionType displayConnectionTypes[size]; int numModes = 0; for (int i = 0; i < size; ++i) { - const sp token = SurfaceComposerClient::getPhysicalDisplayToken(ids[i]); - ui::StaticDisplayInfo staticInfo; - if (const status_t status = SurfaceComposerClient::getStaticDisplayInfo(token, &staticInfo); + if (const status_t status = + SurfaceComposerClient::getStaticDisplayInfo(ids[i].value, &staticInfo); status != OK) { return status; } @@ -150,7 +140,7 @@ int ADisplay_acquirePhysicalDisplays(ADisplay*** outDisplays) { ui::DynamicDisplayInfo dynamicInfo; if (const status_t status = - SurfaceComposerClient::getDynamicDisplayInfo(token, &dynamicInfo); + SurfaceComposerClient::getDynamicDisplayInfoFromId(ids[i].value, &dynamicInfo); status != OK) { return status; } @@ -260,14 +250,15 @@ void ADisplay_getPreferredWideColorFormat(ADisplay* display, ADataSpace* outData int ADisplay_getCurrentConfig(ADisplay* display, ADisplayConfig** outConfig) { CHECK_NOT_NULL(display); - sp token = getToken(display); ui::DynamicDisplayInfo info; - if (const auto status = SurfaceComposerClient::getDynamicDisplayInfo(token, &info); + DisplayImpl* impl = reinterpret_cast(display); + + if (const auto status = + SurfaceComposerClient::getDynamicDisplayInfoFromId(impl->id.value, &info); status != OK) { return status; } - DisplayImpl* impl = reinterpret_cast(display); for (size_t i = 0; i < impl->numConfigs; i++) { auto* config = impl->configs + i; if (config->id == info.activeDisplayModeId) { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 36ff3ec5c2..6da6022a83 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -974,17 +974,14 @@ status_t SurfaceFlinger::getDisplayState(const sp& displayToken, ui::Di return NO_ERROR; } -status_t SurfaceFlinger::getStaticDisplayInfo(const sp& displayToken, - ui::StaticDisplayInfo* info) { - if (!displayToken || !info) { +status_t SurfaceFlinger::getStaticDisplayInfo(int64_t displayId, ui::StaticDisplayInfo* info) { + if (!info) { return BAD_VALUE; } Mutex::Autolock lock(mStateLock); - - const auto displayOpt = ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken)) - .transform(&ftl::to_mapped_ref) - .and_then(getDisplayDeviceAndSnapshot()); + const auto id = DisplayId::fromValue(static_cast(displayId)); + const auto displayOpt = mPhysicalDisplays.get(*id).and_then(getDisplayDeviceAndSnapshot()); if (!displayOpt) { return NAME_NOT_FOUND; @@ -1011,26 +1008,10 @@ status_t SurfaceFlinger::getStaticDisplayInfo(const sp& displayToken, return NO_ERROR; } -status_t SurfaceFlinger::getDynamicDisplayInfo(const sp& displayToken, - ui::DynamicDisplayInfo* info) { - if (!displayToken || !info) { - return BAD_VALUE; - } - - Mutex::Autolock lock(mStateLock); - - const auto displayOpt = ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken)) - .transform(&ftl::to_mapped_ref) - .and_then(getDisplayDeviceAndSnapshot()); - if (!displayOpt) { - return NAME_NOT_FOUND; - } - - const auto& [display, snapshotRef] = *displayOpt; - const auto& snapshot = snapshotRef.get(); - +void SurfaceFlinger::getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo*& info, + const sp& display, + const display::DisplaySnapshot& snapshot) { const auto& displayModes = snapshot.displayModes(); - info->supportedDisplayModes.clear(); info->supportedDisplayModes.reserve(displayModes.size()); @@ -1104,7 +1085,47 @@ status_t SurfaceFlinger::getDynamicDisplayInfo(const sp& displayToken, } } } +} + +status_t SurfaceFlinger::getDynamicDisplayInfoFromId(int64_t physicalDisplayId, + ui::DynamicDisplayInfo* info) { + if (!info) { + return BAD_VALUE; + } + + Mutex::Autolock lock(mStateLock); + + const auto id_ = + DisplayId::fromValue(static_cast(physicalDisplayId)); + const auto displayOpt = mPhysicalDisplays.get(*id_).and_then(getDisplayDeviceAndSnapshot()); + + if (!displayOpt) { + return NAME_NOT_FOUND; + } + + const auto& [display, snapshotRef] = *displayOpt; + getDynamicDisplayInfoInternal(info, display, snapshotRef.get()); + return NO_ERROR; +} + +status_t SurfaceFlinger::getDynamicDisplayInfoFromToken(const sp& displayToken, + ui::DynamicDisplayInfo* info) { + if (!displayToken || !info) { + return BAD_VALUE; + } + + Mutex::Autolock lock(mStateLock); + + const auto displayOpt = ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken)) + .transform(&ftl::to_mapped_ref) + .and_then(getDisplayDeviceAndSnapshot()); + + if (!displayOpt) { + return NAME_NOT_FOUND; + } + const auto& [display, snapshotRef] = *displayOpt; + getDynamicDisplayInfoInternal(info, display, snapshotRef.get()); return NO_ERROR; } @@ -4036,7 +4057,7 @@ status_t SurfaceFlinger::setTransactionState( bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelineInfo, std::vector& states, - const Vector& displays, uint32_t flags, + Vector& displays, uint32_t flags, const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime, bool isAutoTimestamp, const client_cache_t& uncacheBuffer, @@ -4045,7 +4066,8 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin const std::vector& listenerCallbacks, int originPid, int originUid, uint64_t transactionId) { uint32_t transactionFlags = 0; - for (const DisplayState& display : displays) { + for (DisplayState& display : displays) { + display.sanitize(permissions); transactionFlags |= setDisplayStateLocked(display); } @@ -7283,6 +7305,10 @@ binder::Status SurfaceComposerAIDL::getPhysicalDisplayIds(std::vector* binder::Status SurfaceComposerAIDL::getPhysicalDisplayToken(int64_t displayId, sp* outDisplay) { + status_t status = checkAccessPermission(); + if (status != OK) { + return binderStatusFromStatusT(status); + } const auto id = DisplayId::fromValue(static_cast(displayId)); *outDisplay = mFlinger->getPhysicalDisplayToken(*id); return binder::Status::ok(); @@ -7333,11 +7359,12 @@ binder::Status SurfaceComposerAIDL::getDisplayState(const sp& display, return binderStatusFromStatusT(status); } -binder::Status SurfaceComposerAIDL::getStaticDisplayInfo(const sp& display, +binder::Status SurfaceComposerAIDL::getStaticDisplayInfo(int64_t displayId, gui::StaticDisplayInfo* outInfo) { using Tag = gui::DeviceProductInfo::ManufactureOrModelDate::Tag; ui::StaticDisplayInfo info; - status_t status = mFlinger->getStaticDisplayInfo(display, &info); + + status_t status = mFlinger->getStaticDisplayInfo(displayId, &info); if (status == NO_ERROR) { // convert ui::StaticDisplayInfo to gui::StaticDisplayInfo outInfo->connectionType = static_cast(info.connectionType); @@ -7376,58 +7403,71 @@ binder::Status SurfaceComposerAIDL::getStaticDisplayInfo(const sp& disp return binderStatusFromStatusT(status); } -binder::Status SurfaceComposerAIDL::getDynamicDisplayInfo(const sp& display, - gui::DynamicDisplayInfo* outInfo) { +void SurfaceComposerAIDL::getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo& info, + gui::DynamicDisplayInfo*& outInfo) { + // convert ui::DynamicDisplayInfo to gui::DynamicDisplayInfo + outInfo->supportedDisplayModes.clear(); + outInfo->supportedDisplayModes.reserve(info.supportedDisplayModes.size()); + for (const auto& mode : info.supportedDisplayModes) { + gui::DisplayMode outMode; + outMode.id = mode.id; + outMode.resolution.width = mode.resolution.width; + outMode.resolution.height = mode.resolution.height; + outMode.xDpi = mode.xDpi; + outMode.yDpi = mode.yDpi; + outMode.refreshRate = mode.refreshRate; + outMode.appVsyncOffset = mode.appVsyncOffset; + outMode.sfVsyncOffset = mode.sfVsyncOffset; + outMode.presentationDeadline = mode.presentationDeadline; + outMode.group = mode.group; + std::transform(mode.supportedHdrTypes.begin(), mode.supportedHdrTypes.end(), + std::back_inserter(outMode.supportedHdrTypes), + [](const ui::Hdr& value) { return static_cast(value); }); + outInfo->supportedDisplayModes.push_back(outMode); + } + + outInfo->activeDisplayModeId = info.activeDisplayModeId; + outInfo->renderFrameRate = info.renderFrameRate; + + outInfo->supportedColorModes.clear(); + outInfo->supportedColorModes.reserve(info.supportedColorModes.size()); + for (const auto& cmode : info.supportedColorModes) { + outInfo->supportedColorModes.push_back(static_cast(cmode)); + } + + outInfo->activeColorMode = static_cast(info.activeColorMode); + + gui::HdrCapabilities& hdrCapabilities = outInfo->hdrCapabilities; + hdrCapabilities.supportedHdrTypes.clear(); + hdrCapabilities.supportedHdrTypes.reserve(info.hdrCapabilities.getSupportedHdrTypes().size()); + for (const auto& hdr : info.hdrCapabilities.getSupportedHdrTypes()) { + hdrCapabilities.supportedHdrTypes.push_back(static_cast(hdr)); + } + hdrCapabilities.maxLuminance = info.hdrCapabilities.getDesiredMaxLuminance(); + hdrCapabilities.maxAverageLuminance = info.hdrCapabilities.getDesiredMaxAverageLuminance(); + hdrCapabilities.minLuminance = info.hdrCapabilities.getDesiredMinLuminance(); + + outInfo->autoLowLatencyModeSupported = info.autoLowLatencyModeSupported; + outInfo->gameContentTypeSupported = info.gameContentTypeSupported; + outInfo->preferredBootDisplayMode = info.preferredBootDisplayMode; +} + +binder::Status SurfaceComposerAIDL::getDynamicDisplayInfoFromToken( + const sp& display, gui::DynamicDisplayInfo* outInfo) { + ui::DynamicDisplayInfo info; + status_t status = mFlinger->getDynamicDisplayInfoFromToken(display, &info); + if (status == NO_ERROR) { + getDynamicDisplayInfoInternal(info, outInfo); + } + return binderStatusFromStatusT(status); +} + +binder::Status SurfaceComposerAIDL::getDynamicDisplayInfoFromId(int64_t displayId, + gui::DynamicDisplayInfo* outInfo) { ui::DynamicDisplayInfo info; - status_t status = mFlinger->getDynamicDisplayInfo(display, &info); + status_t status = mFlinger->getDynamicDisplayInfoFromId(displayId, &info); if (status == NO_ERROR) { - // convert ui::DynamicDisplayInfo to gui::DynamicDisplayInfo - outInfo->supportedDisplayModes.clear(); - outInfo->supportedDisplayModes.reserve(info.supportedDisplayModes.size()); - for (const auto& mode : info.supportedDisplayModes) { - gui::DisplayMode outMode; - outMode.id = mode.id; - outMode.resolution.width = mode.resolution.width; - outMode.resolution.height = mode.resolution.height; - outMode.xDpi = mode.xDpi; - outMode.yDpi = mode.yDpi; - outMode.refreshRate = mode.refreshRate; - outMode.appVsyncOffset = mode.appVsyncOffset; - outMode.sfVsyncOffset = mode.sfVsyncOffset; - outMode.presentationDeadline = mode.presentationDeadline; - outMode.group = mode.group; - std::transform(mode.supportedHdrTypes.begin(), mode.supportedHdrTypes.end(), - std::back_inserter(outMode.supportedHdrTypes), - [](const ui::Hdr& value) { return static_cast(value); }); - - outInfo->supportedDisplayModes.push_back(outMode); - } - - outInfo->activeDisplayModeId = info.activeDisplayModeId; - outInfo->renderFrameRate = info.renderFrameRate; - - outInfo->supportedColorModes.clear(); - outInfo->supportedColorModes.reserve(info.supportedColorModes.size()); - for (const auto& cmode : info.supportedColorModes) { - outInfo->supportedColorModes.push_back(static_cast(cmode)); - } - - outInfo->activeColorMode = static_cast(info.activeColorMode); - - gui::HdrCapabilities& hdrCapabilities = outInfo->hdrCapabilities; - hdrCapabilities.supportedHdrTypes.clear(); - hdrCapabilities.supportedHdrTypes.reserve( - info.hdrCapabilities.getSupportedHdrTypes().size()); - for (const auto& hdr : info.hdrCapabilities.getSupportedHdrTypes()) { - hdrCapabilities.supportedHdrTypes.push_back(static_cast(hdr)); - } - hdrCapabilities.maxLuminance = info.hdrCapabilities.getDesiredMaxLuminance(); - hdrCapabilities.maxAverageLuminance = info.hdrCapabilities.getDesiredMaxAverageLuminance(); - hdrCapabilities.minLuminance = info.hdrCapabilities.getDesiredMinLuminance(); - - outInfo->autoLowLatencyModeSupported = info.autoLowLatencyModeSupported; - outInfo->gameContentTypeSupported = info.gameContentTypeSupported; - outInfo->preferredBootDisplayMode = info.preferredBootDisplayMode; + getDynamicDisplayInfoInternal(info, outInfo); } return binderStatusFromStatusT(status); } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 6ddcfbccf8..e265939e70 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -512,10 +512,13 @@ private: status_t getDisplayStats(const sp& displayToken, DisplayStatInfo* stats); status_t getDisplayState(const sp& displayToken, ui::DisplayState*) EXCLUDES(mStateLock); - status_t getStaticDisplayInfo(const sp& displayToken, ui::StaticDisplayInfo*) - EXCLUDES(mStateLock); - status_t getDynamicDisplayInfo(const sp& displayToken, ui::DynamicDisplayInfo*) + status_t getStaticDisplayInfo(int64_t displayId, ui::StaticDisplayInfo*) EXCLUDES(mStateLock); + status_t getDynamicDisplayInfoFromId(int64_t displayId, ui::DynamicDisplayInfo*) EXCLUDES(mStateLock); + status_t getDynamicDisplayInfoFromToken(const sp& displayToken, + ui::DynamicDisplayInfo*) EXCLUDES(mStateLock); + void getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo*&, const sp&, + const display::DisplaySnapshot&); status_t getDisplayNativePrimaries(const sp& displayToken, ui::DisplayPrimaries&); status_t setActiveColorMode(const sp& displayToken, ui::ColorMode colorMode); status_t getBootDisplayModeSupport(bool* outSupport) const; @@ -702,7 +705,7 @@ private: */ bool applyTransactionState(const FrameTimelineInfo& info, std::vector& state, - const Vector& displays, uint32_t flags, + Vector& displays, uint32_t flags, const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime, bool isAutoTimestamp, const client_cache_t& uncacheBuffer, const int64_t postTime, @@ -1401,10 +1404,12 @@ public: gui::DisplayStatInfo* outStatInfo) override; binder::Status getDisplayState(const sp& display, gui::DisplayState* outState) override; - binder::Status getStaticDisplayInfo(const sp& display, + binder::Status getStaticDisplayInfo(int64_t displayId, gui::StaticDisplayInfo* outInfo) override; - binder::Status getDynamicDisplayInfo(const sp& display, - gui::DynamicDisplayInfo* outInfo) override; + binder::Status getDynamicDisplayInfoFromId(int64_t displayId, + gui::DynamicDisplayInfo* outInfo) override; + binder::Status getDynamicDisplayInfoFromToken(const sp& display, + gui::DynamicDisplayInfo* outInfo) override; binder::Status getDisplayNativePrimaries(const sp& display, gui::DisplayPrimaries* outPrimaries) override; binder::Status setActiveColorMode(const sp& display, int colorMode) override; @@ -1489,6 +1494,8 @@ private: status_t checkAccessPermission(bool usePermissionCache = kUsePermissionCache); status_t checkControlDisplayBrightnessPermission(); status_t checkReadFrameBufferPermission(); + static void getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo& info, + gui::DynamicDisplayInfo*& outInfo); private: sp mFlinger; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index c0a6bdbe27..96844d2b18 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -491,14 +491,14 @@ public: mFlinger->getDisplayState(display, &displayState); } - void getStaticDisplayInfo(sp &display) { + void getStaticDisplayInfo(int64_t displayId) { ui::StaticDisplayInfo staticDisplayInfo; - mFlinger->getStaticDisplayInfo(display, &staticDisplayInfo); + mFlinger->getStaticDisplayInfo(displayId, &staticDisplayInfo); } - void getDynamicDisplayInfo(sp &display) { + void getDynamicDisplayInfo(int64_t displayId) { android::ui::DynamicDisplayInfo dynamicDisplayInfo; - mFlinger->getDynamicDisplayInfo(display, &dynamicDisplayInfo); + mFlinger->getDynamicDisplayInfoFromId(displayId, &dynamicDisplayInfo); } void getDisplayNativePrimaries(sp &display) { android::ui::DisplayPrimaries displayPrimaries; @@ -522,7 +522,7 @@ public: return ids.front(); } - sp fuzzBoot(FuzzedDataProvider *fdp) { + std::pair, int64_t> fuzzBoot(FuzzedDataProvider *fdp) { mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(fdp->ConsumeBool()); const sp client = sp::make(mFlinger); @@ -549,13 +549,13 @@ public: mFlinger->bootFinished(); - return display; + return {display, physicalDisplayId.value}; } void fuzzSurfaceFlinger(const uint8_t *data, size_t size) { FuzzedDataProvider mFdp(data, size); - sp display = fuzzBoot(&mFdp); + auto [display, displayId] = fuzzBoot(&mFdp); sp bufferProducer = sp::make(); @@ -563,8 +563,8 @@ public: getDisplayStats(display); getDisplayState(display); - getStaticDisplayInfo(display); - getDynamicDisplayInfo(display); + getStaticDisplayInfo(displayId); + getDynamicDisplayInfo(displayId); getDisplayNativePrimaries(display); mFlinger->setAutoLowLatencyMode(display, mFdp.ConsumeBool()); diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp index 16768441f0..4a45eb5586 100644 --- a/services/surfaceflinger/tests/Credentials_test.cpp +++ b/services/surfaceflinger/tests/Credentials_test.cpp @@ -83,6 +83,15 @@ protected: return SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); } + static std::optional getFirstDisplayId() { + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + if (ids.empty()) { + return std::nullopt; + } + + return ids.front().value; + } + void setupBackgroundSurface() { mDisplay = getFirstDisplayToken(); ASSERT_FALSE(mDisplay == nullptr); @@ -169,29 +178,25 @@ TEST_F(CredentialsTest, ClientInitTest) { TEST_F(CredentialsTest, GetBuiltInDisplayAccessTest) { std::function condition = [] { return getFirstDisplayToken() != nullptr; }; // Anyone can access display information. - ASSERT_NO_FATAL_FAILURE(checkWithPrivileges(condition, true, true)); + ASSERT_NO_FATAL_FAILURE(checkWithPrivileges(condition, true, false)); } TEST_F(CredentialsTest, AllowedGetterMethodsTest) { // The following methods are tested with a UID that is not root, graphics, // or system, to show that anyone can access them. UIDFaker f(AID_BIN); - const auto display = getFirstDisplayToken(); - ASSERT_TRUE(display != nullptr); - - ui::DisplayMode mode; - ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode)); - - Vector modes; + const auto id = getFirstDisplayId(); + ASSERT_TRUE(id); ui::DynamicDisplayInfo info; - ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info)); + ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfoFromId(*id, &info)); } TEST_F(CredentialsTest, GetDynamicDisplayInfoTest) { - const auto display = getFirstDisplayToken(); + const auto id = getFirstDisplayId(); + ASSERT_TRUE(id); std::function condition = [=]() { ui::DynamicDisplayInfo info; - return SurfaceComposerClient::getDynamicDisplayInfo(display, &info); + return SurfaceComposerClient::getDynamicDisplayInfoFromId(*id, &info); }; ASSERT_NO_FATAL_FAILURE(checkWithPrivileges(condition, NO_ERROR, NO_ERROR)); } @@ -335,8 +340,10 @@ TEST_F(CredentialsTest, IsWideColorDisplayBasicCorrectness) { status_t error = SurfaceComposerClient::isWideColorDisplay(display, &result); ASSERT_EQ(NO_ERROR, error); bool hasWideColorMode = false; + const auto id = getFirstDisplayId(); + ASSERT_TRUE(id); ui::DynamicDisplayInfo info; - SurfaceComposerClient::getDynamicDisplayInfo(display, &info); + SurfaceComposerClient::getDynamicDisplayInfoFromId(*id, &info); const auto& colorModes = info.supportedColorModes; for (ColorMode colorMode : colorModes) { switch (colorMode) { @@ -363,10 +370,10 @@ TEST_F(CredentialsTest, IsWideColorDisplayWithPrivileges) { } TEST_F(CredentialsTest, GetActiveColorModeBasicCorrectness) { - const auto display = getFirstDisplayToken(); - ASSERT_FALSE(display == nullptr); + const auto id = getFirstDisplayId(); + ASSERT_TRUE(id); ui::DynamicDisplayInfo info; - SurfaceComposerClient::getDynamicDisplayInfo(display, &info); + SurfaceComposerClient::getDynamicDisplayInfoFromId(*id, &info); ColorMode colorMode = info.activeColorMode; ASSERT_NE(static_cast(BAD_VALUE), colorMode); } diff --git a/services/surfaceflinger/tests/DisplayConfigs_test.cpp b/services/surfaceflinger/tests/DisplayConfigs_test.cpp index 10dae4636e..4be961bda1 100644 --- a/services/surfaceflinger/tests/DisplayConfigs_test.cpp +++ b/services/surfaceflinger/tests/DisplayConfigs_test.cpp @@ -45,6 +45,7 @@ protected: void SetUp() override { const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); ASSERT_FALSE(ids.empty()); + mDisplayId = ids.front().value; mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); status_t res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &mSpecs); ASSERT_EQ(res, NO_ERROR); @@ -58,11 +59,14 @@ protected: void testSetAllowGroupSwitching(bool allowGroupSwitching); sp mDisplayToken; + uint64_t mDisplayId; }; TEST_F(RefreshRateRangeTest, setAllConfigs) { ui::DynamicDisplayInfo info; - status_t res = SurfaceComposerClient::getDynamicDisplayInfo(mDisplayToken, &info); + status_t res = + SurfaceComposerClient::getDynamicDisplayInfoFromId(static_cast(mDisplayId), + &info); const auto& modes = info.supportedDisplayModes; ASSERT_EQ(res, NO_ERROR); ASSERT_GT(modes.size(), 0); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp index 11e734a416..0e149d2bfb 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp @@ -61,7 +61,7 @@ protected: TEST_F(ExcludeDolbyVisionTest, excludesDolbyVisionOnModesHigherThan4k30) { injectDisplayModes({mode4k60}); ui::DynamicDisplayInfo info; - mFlinger.getDynamicDisplayInfo(mDisplay->getDisplayToken().promote(), &info); + mFlinger.getDynamicDisplayInfoFromToken(mDisplay->getDisplayToken().promote(), &info); std::vector displayModes = info.supportedDisplayModes; @@ -75,7 +75,7 @@ TEST_F(ExcludeDolbyVisionTest, excludesDolbyVisionOnModesHigherThan4k30) { TEST_F(ExcludeDolbyVisionTest, includesDolbyVisionOnModesLowerThanOrEqualTo4k30) { injectDisplayModes({mode1080p60, mode4k30, mode4k30NonStandard}); ui::DynamicDisplayInfo info; - mFlinger.getDynamicDisplayInfo(mDisplay->getDisplayToken().promote(), &info); + mFlinger.getDynamicDisplayInfoFromToken(mDisplay->getDisplayToken().promote(), &info); std::vector displayModes = info.supportedDisplayModes; @@ -94,7 +94,7 @@ TEST_F(ExcludeDolbyVisionTest, includesDolbyVisionOnModesLowerThanOrEqualTo4k30) TEST_F(ExcludeDolbyVisionTest, 4k30IsNotReportedAsAValidHdrType) { injectDisplayModes({mode4k60}); ui::DynamicDisplayInfo info; - mFlinger.getDynamicDisplayInfo(mDisplay->getDisplayToken().promote(), &info); + mFlinger.getDynamicDisplayInfoFromToken(mDisplay->getDisplayToken().promote(), &info); std::vector displayHdrTypes = info.hdrCapabilities.getSupportedHdrTypes(); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 7d0b340cfa..2117084bbf 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -487,9 +487,9 @@ public: void updateLayerMetadataSnapshot() { mFlinger->updateLayerMetadataSnapshot(); } - void getDynamicDisplayInfo(const sp& displayToken, - ui::DynamicDisplayInfo* dynamicDisplayInfo) { - mFlinger->getDynamicDisplayInfo(displayToken, dynamicDisplayInfo); + void getDynamicDisplayInfoFromToken(const sp& displayToken, + ui::DynamicDisplayInfo* dynamicDisplayInfo) { + mFlinger->getDynamicDisplayInfoFromToken(displayToken, dynamicDisplayInfo); } /* ------------------------------------------------------------------------ -- GitLab From 544443888b64d414d709c694aecfa9fb5d943778 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Wed, 7 Dec 2022 20:57:49 +0000 Subject: [PATCH 0585/1310] libjpegrecoverymap: test API-0 Change-Id: I32bc24f9a72583479deffe2776b8b127b95bf239 Test: this is the test Bug: b/252835416 --- .../tests/recoverymap_test.cpp | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp index ade33a0e4f..c4129ad614 100644 --- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp +++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp @@ -93,6 +93,58 @@ TEST_F(RecoveryMapTest, build) { recovery_map.decodeJPEGR(nullptr, nullptr, nullptr, false); } +TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) { + int ret; + + // Load input files. + if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + mRawP010Image.width = RAW_P010_IMAGE_WIDTH; + mRawP010Image.height = RAW_P010_IMAGE_HEIGHT; + mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100; + + RecoveryMap recoveryMap; + + jpegr_compressed_struct jpegR; + jpegR.maxLength = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + ret = recoveryMap.encodeJPEGR( + &mRawP010Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, 90, nullptr); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + if (SAVE_ENCODING_RESULT) { + // Output image data to file + std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)jpegR.data, jpegR.length); + } + + jpegr_uncompressed_struct decodedJpegR; + int decodedJpegRSize = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * 4; + decodedJpegR.data = malloc(decodedJpegRSize); + ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + if (SAVE_DECODING_RESULT) { + // Output image data to file + std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize); + } + + free(jpegR.data); + free(decodedJpegR.data); +} + TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) { int ret; -- GitLab From 9faa446753815eed2e58afebad7fbd7b70dcb105 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 6 Dec 2022 11:45:04 -0800 Subject: [PATCH 0586/1310] Use sysprop flags on host Now that sysprop is available for host, we no longer need to guard the code with #ifdef. Bug: 261586828 Test: m Change-Id: I5b97415d2c52b465074e345a843e6d9e1f08171d --- services/inputflinger/reader/Android.bp | 2 +- services/inputflinger/reader/InputDevice.cpp | 6 ------ .../inputflinger/reader/mapper/MultiTouchInputMapper.cpp | 9 +-------- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index c93443a025..7f229ec664 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -73,6 +73,7 @@ cc_defaults { "libcutils", "libjsoncpp", "liblog", + "libPlatformProperties", "libstatslog", "libutils", ], @@ -89,7 +90,6 @@ cc_defaults { target: { android: { shared_libs: [ - "libPlatformProperties", "libinput", ], }, diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 8d5377fc73..c184b202b7 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -20,9 +20,7 @@ #include -#if defined(__ANDROID__) #include -#endif #include #include "CursorInputMapper.h" @@ -213,11 +211,7 @@ void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) { // Touchscreens and touchpad devices. static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY = -#if defined(__ANDROID__) sysprop::InputProperties::enable_touchpad_gestures_library().value_or(false); -#else - false; -#endif if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) && classes.test(InputDeviceClass::TOUCH_MT)) { mappers.push_back(std::make_unique(*contextPtr)); diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp index b193dffcfc..633efc6047 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp @@ -16,10 +16,8 @@ #include "../Macros.h" -#include "MultiTouchInputMapper.h" -#if defined(__ANDROID__) #include -#endif +#include "MultiTouchInputMapper.h" namespace android { @@ -218,12 +216,7 @@ bool MultiTouchInputMapper::hasStylus() const { bool MultiTouchInputMapper::shouldSimulateStylusWithTouch() const { static const bool SIMULATE_STYLUS_WITH_TOUCH = -#if defined(__ANDROID__) sysprop::InputProperties::simulate_stylus_with_touch().value_or(false); -#else - // Disable this developer feature where sysproperties are not available - false; -#endif return SIMULATE_STYLUS_WITH_TOUCH && mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN; } -- GitLab From 30aa4375d1a6e0045449ea2cdd43500f39b06d89 Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Mon, 3 Oct 2022 14:54:12 -0700 Subject: [PATCH 0587/1310] Fix data type error in AIDL utf8InCpp string should not be used to pass arbitrary data, as it drops invalid chars during utf8-utf16 conversion. byte[] is the correct type to use. Bug: 250401609 Test: atest libgui_test libsurfaceflinger_unittest SurfaceFlinger_test Change-Id: Ia1bf7e2cdc134a392d7b4edace9b2799a4c0d23b --- libs/gui/SurfaceComposerClient.cpp | 2 +- libs/gui/aidl/android/gui/PullAtomData.aidl | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 3 +- services/surfaceflinger/SurfaceFlinger.h | 2 +- .../surfaceflinger/TimeStats/TimeStats.cpp | 12 ++++---- services/surfaceflinger/TimeStats/TimeStats.h | 8 +++--- .../fuzzer/surfaceflinger_fuzzers_utils.h | 2 +- .../tests/unittests/TimeStatsTest.cpp | 28 ++++++++++++++----- .../tests/unittests/mock/MockTimeStats.h | 2 +- 9 files changed, 39 insertions(+), 22 deletions(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 325c294762..b86f4b5ef1 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2542,7 +2542,7 @@ status_t SurfaceComposerClient::onPullAtom(const int32_t atomId, std::string* ou gui::PullAtomData pad; binder::Status status = ComposerServiceAIDL::getComposerService()->onPullAtom(atomId, &pad); if (status.isOk()) { - outData->assign((const char*)pad.data.data(), pad.data.size()); + outData->assign(pad.data.begin(), pad.data.end()); *success = pad.success; } return statusTFromBinderStatus(status); diff --git a/libs/gui/aidl/android/gui/PullAtomData.aidl b/libs/gui/aidl/android/gui/PullAtomData.aidl index 14d33c6d0b..c307cef70e 100644 --- a/libs/gui/aidl/android/gui/PullAtomData.aidl +++ b/libs/gui/aidl/android/gui/PullAtomData.aidl @@ -18,6 +18,6 @@ package android.gui; /** @hide */ parcelable PullAtomData { - @utf8InCpp String data; + byte[] data; boolean success; } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 36ff3ec5c2..2b077f6794 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1558,7 +1558,8 @@ status_t SurfaceFlinger::overrideHdrTypes(const sp& displayToken, return NO_ERROR; } -status_t SurfaceFlinger::onPullAtom(const int32_t atomId, std::string* pulledData, bool* success) { +status_t SurfaceFlinger::onPullAtom(const int32_t atomId, std::vector* pulledData, + bool* success) { *success = mTimeStats->onPullAtom(atomId, pulledData); return NO_ERROR; } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 6ddcfbccf8..bff210ae0a 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -527,7 +527,7 @@ private: void setPowerMode(const sp& displayToken, int mode); status_t overrideHdrTypes(const sp& displayToken, const std::vector& hdrTypes); - status_t onPullAtom(const int32_t atomId, std::string* pulledData, bool* success); + status_t onPullAtom(const int32_t atomId, std::vector* pulledData, bool* success); status_t getLayerDebugInfo(std::vector* outLayers); status_t getColorManagement(bool* outGetColorManagement) const; status_t getCompositionPreference(ui::Dataspace* outDataspace, ui::PixelFormat* outPixelFormat, diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp index e860d88cd1..9368edd6ac 100644 --- a/services/surfaceflinger/TimeStats/TimeStats.cpp +++ b/services/surfaceflinger/TimeStats/TimeStats.cpp @@ -90,7 +90,7 @@ SurfaceflingerStatsLayerInfo_SetFrameRateVote frameRateVoteToProto( } } // namespace -bool TimeStats::populateGlobalAtom(std::string* pulledData) { +bool TimeStats::populateGlobalAtom(std::vector* pulledData) { std::lock_guard lock(mMutex); if (mTimeStats.statsStartLegacy == 0) { @@ -138,10 +138,11 @@ bool TimeStats::populateGlobalAtom(std::string* pulledData) { // Always clear data. clearGlobalLocked(); - return atomList.SerializeToString(pulledData); + pulledData->resize(atomList.ByteSizeLong()); + return atomList.SerializeToArray(pulledData->data(), atomList.ByteSizeLong()); } -bool TimeStats::populateLayerAtom(std::string* pulledData) { +bool TimeStats::populateLayerAtom(std::vector* pulledData) { std::lock_guard lock(mMutex); std::vector dumpStats; @@ -229,7 +230,8 @@ bool TimeStats::populateLayerAtom(std::string* pulledData) { // Always clear data. clearLayersLocked(); - return atomList.SerializeToString(pulledData); + pulledData->resize(atomList.ByteSizeLong()); + return atomList.SerializeToArray(pulledData->data(), atomList.ByteSizeLong()); } TimeStats::TimeStats() : TimeStats(std::nullopt, std::nullopt) {} @@ -245,7 +247,7 @@ TimeStats::TimeStats(std::optional maxPulledLayers, } } -bool TimeStats::onPullAtom(const int atomId, std::string* pulledData) { +bool TimeStats::onPullAtom(const int atomId, std::vector* pulledData) { bool success = false; if (atomId == 10062) { // SURFACEFLINGER_STATS_GLOBAL_INFO success = populateGlobalAtom(pulledData); diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h index 61d7c22a2a..1872d0e002 100644 --- a/services/surfaceflinger/TimeStats/TimeStats.h +++ b/services/surfaceflinger/TimeStats/TimeStats.h @@ -47,7 +47,7 @@ public: virtual ~TimeStats() = default; // Process a pull request from statsd. - virtual bool onPullAtom(const int atomId, std::string* pulledData) = 0; + virtual bool onPullAtom(const int atomId, std::vector* pulledData) = 0; virtual void parseArgs(bool asProto, const Vector& args, std::string& result) = 0; virtual bool isEnabled() = 0; @@ -244,7 +244,7 @@ public: TimeStats(std::optional maxPulledLayers, std::optional maxPulledHistogramBuckets); - bool onPullAtom(const int atomId, std::string* pulledData) override; + bool onPullAtom(const int atomId, std::vector* pulledData) override; void parseArgs(bool asProto, const Vector& args, std::string& result) override; bool isEnabled() override; std::string miniDump() override; @@ -292,8 +292,8 @@ public: static const size_t MAX_NUM_TIME_RECORDS = 64; private: - bool populateGlobalAtom(std::string* pulledData); - bool populateLayerAtom(std::string* pulledData); + bool populateGlobalAtom(std::vector* pulledData); + bool populateLayerAtom(std::vector* pulledData); bool recordReadyLocked(int32_t layerId, TimeRecord* timeRecord); void flushAvailableRecordsToStatsLocked(int32_t layerId, Fps displayRefreshRate, std::optional renderRate, SetFrameRateVote, diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index c0a6bdbe27..a15aecdc52 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -419,7 +419,7 @@ public: void onPullAtom(FuzzedDataProvider *fdp) { const int32_t atomId = fdp->ConsumeIntegral(); - std::string pulledData = fdp->ConsumeRandomLengthString().c_str(); + std::vector pulledData = fdp->ConsumeRemainingBytes(); bool success = fdp->ConsumeBool(); mFlinger->onPullAtom(atomId, &pulledData, &success); } diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp index 1dd4f254fb..0aaefe3b92 100644 --- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp +++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp @@ -1099,8 +1099,10 @@ TEST_F(TimeStatsTest, globalStatsCallback) { kGameMode, JankType::None, DISPLAY_DEADLINE_DELTA, DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA}); + std::vector pulledBytes; + EXPECT_TRUE(mTimeStats->onPullAtom(10062 /*SURFACEFLINGER_STATS_GLOBAL_INFO*/, &pulledBytes)); std::string pulledData; - EXPECT_TRUE(mTimeStats->onPullAtom(10062 /*SURFACEFLINGER_STATS_GLOBAL_INFO*/, &pulledData)); + pulledData.assign(pulledBytes.begin(), pulledBytes.end()); android::surfaceflinger::SurfaceflingerStatsGlobalInfoWrapper atomList; ASSERT_TRUE(atomList.ParseFromString(pulledData)); @@ -1234,8 +1236,10 @@ TEST_F(TimeStatsTest, layerStatsCallback_pullsAllAndClears) { GameMode::Standard, JankType::None, DISPLAY_DEADLINE_DELTA, DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA_3MS}); + std::vector pulledBytes; + EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledBytes)); std::string pulledData; - EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData)); + pulledData.assign(pulledBytes.begin(), pulledBytes.end()); SurfaceflingerStatsLayerInfoWrapper atomList; ASSERT_TRUE(atomList.ParseFromString(pulledData)); @@ -1322,8 +1326,10 @@ TEST_F(TimeStatsTest, layerStatsCallback_multipleGameModes) { insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 5, 4000000, {}, GameMode::Battery); insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 6, 5000000, {}, GameMode::Custom); + std::vector pulledBytes; + EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledBytes)); std::string pulledData; - EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData)); + pulledData.assign(pulledBytes.begin(), pulledBytes.end()); SurfaceflingerStatsLayerInfoWrapper atomList; ASSERT_TRUE(atomList.ParseFromString(pulledData)); @@ -1412,8 +1418,10 @@ TEST_F(TimeStatsTest, layerStatsCallback_pullsMultipleLayers) { insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_1, 1, 2000000); insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_1, 2, 3000000); + std::vector pulledBytes; + EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledBytes)); std::string pulledData; - EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData)); + pulledData.assign(pulledBytes.begin(), pulledBytes.end()); SurfaceflingerStatsLayerInfoWrapper atomList; ASSERT_TRUE(atomList.ParseFromString(pulledData)); @@ -1437,8 +1445,10 @@ TEST_F(TimeStatsTest, layerStatsCallback_pullsMultipleBuckets) { mTimeStats->setPresentFenceGlobal(std::make_shared(3000000)); mTimeStats->setPresentFenceGlobal(std::make_shared(5000000)); + std::vector pulledBytes; + EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledBytes)); std::string pulledData; - EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData)); + pulledData.assign(pulledBytes.begin(), pulledBytes.end()); SurfaceflingerStatsLayerInfoWrapper atomList; ASSERT_TRUE(atomList.ParseFromString(pulledData)); @@ -1456,8 +1466,10 @@ TEST_F(TimeStatsTest, layerStatsCallback_limitsHistogramBuckets) { insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 3, 4000000); insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 4, 5000000); + std::vector pulledBytes; + EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledBytes)); std::string pulledData; - EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData)); + pulledData.assign(pulledBytes.begin(), pulledBytes.end()); SurfaceflingerStatsLayerInfoWrapper atomList; ASSERT_TRUE(atomList.ParseFromString(pulledData)); @@ -1476,8 +1488,10 @@ TEST_F(TimeStatsTest, layerStatsCallback_limitsLayers) { insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_1, 2, 3000000); insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_1, 4, 5000000); + std::vector pulledBytes; + EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledBytes)); std::string pulledData; - EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData)); + pulledData.assign(pulledBytes.begin(), pulledBytes.end()); SurfaceflingerStatsLayerInfoWrapper atomList; ASSERT_TRUE(atomList.ParseFromString(pulledData)); diff --git a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h index 0dee800558..86fbadc0f2 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h +++ b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h @@ -27,7 +27,7 @@ public: TimeStats(); ~TimeStats() override; - MOCK_METHOD2(onPullAtom, bool(const int, std::string*)); + MOCK_METHOD2(onPullAtom, bool(const int, std::vector*)); MOCK_METHOD3(parseArgs, void(bool, const Vector&, std::string&)); MOCK_METHOD0(isEnabled, bool()); MOCK_METHOD0(miniDump, std::string()); -- GitLab From d1f18fa8bef0a319c6952040b9a7ad2a255c17c9 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Thu, 8 Dec 2022 14:28:02 +0000 Subject: [PATCH 0588/1310] Revert "Add asserts for new HAL axes in InputCommonConverter" Revert "Add GESTURE_{X,Y}_OFFSET axes to input HAL" Revert "Depend on V2 of the Input HAL from libservices" Revert submission 20187666-input-hal-gesture-offset Reason for revert: Causes build errors (https://android-build.googleplex.com/builds/submitted/9380741/sdk_finalization/latest/logs/build_error.log) Reverted Changes: I31e8b6975:Add GESTURE_{X,Y}_OFFSET axes to input HAL I2eb4924f3:Depend on V2 of the Input HAL from libservices I13d341fa0:Add asserts for new HAL axes in InputCommonConvert... Change-Id: Ibbef9e8fc3737e418133aef70a53e9d5609bc5f6 --- services/inputflinger/Android.bp | 2 +- services/inputflinger/InputCommonConverter.cpp | 5 +---- services/inputflinger/tests/fuzzers/Android.bp | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp index 8714b0397f..ddcd51f357 100644 --- a/services/inputflinger/Android.bp +++ b/services/inputflinger/Android.bp @@ -69,7 +69,7 @@ cc_defaults { name: "libinputflinger_defaults", srcs: [":libinputflinger_sources"], shared_libs: [ - "android.hardware.input.processor-V2-ndk", + "android.hardware.input.processor-V1-ndk", "libbase", "libbinder", "libbinder_ndk", diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp index 09f1c0f6f3..6db89d4759 100644 --- a/services/inputflinger/InputCommonConverter.cpp +++ b/services/inputflinger/InputCommonConverter.cpp @@ -263,10 +263,7 @@ static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_13) == common static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_14) == common::Axis::GENERIC_14); static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_15) == common::Axis::GENERIC_15); static_assert(static_cast(AMOTION_EVENT_AXIS_GENERIC_16) == common::Axis::GENERIC_16); -static_assert(static_cast(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET) == - common::Axis::GESTURE_X_OFFSET); -static_assert(static_cast(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET) == - common::Axis::GESTURE_Y_OFFSET); +// TODO(hcutts): add GESTURE_X_OFFSET and GESTURE_Y_OFFSET. // If you added a new axis, consider whether this should also be exposed as a HAL axis. Update the // static_assert below and add the new axis here, or leave a comment summarizing your decision. static_assert(static_cast(AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE) == diff --git a/services/inputflinger/tests/fuzzers/Android.bp b/services/inputflinger/tests/fuzzers/Android.bp index 4359a4b7fb..55c2db6c91 100644 --- a/services/inputflinger/tests/fuzzers/Android.bp +++ b/services/inputflinger/tests/fuzzers/Android.bp @@ -55,7 +55,7 @@ cc_defaults { ], shared_libs: [ "android.hardware.input.classifier@1.0", - "android.hardware.input.processor-V2-ndk", + "android.hardware.input.processor-V1-ndk", "libbase", "libbinder", "libcutils", -- GitLab From 636f5240a78cc824eaa081e919dbec73894b4390 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Wed, 7 Dec 2022 20:25:44 +0000 Subject: [PATCH 0589/1310] libjpegrecoverymap: add API-0 (only raw HDR input) and skeleton of tonemap Test: Bug: b/252835416 Change-Id: I09b0d54edbcf86f08b38aab7d4c73ea8f5ae3426 --- .../include/jpegrecoverymap/jpegrerrorcode.h | 1 + .../include/jpegrecoverymap/recoverymap.h | 37 ++++++++++ libs/jpegrecoverymap/recoverymap.cpp | 73 ++++++++++++++++++- .../tests/recoverymap_test.cpp | 1 + 4 files changed, 111 insertions(+), 1 deletion(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h index 9f53a5791a..6995762ea2 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h @@ -44,6 +44,7 @@ enum { ERROR_JPEGR_DECODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 2, ERROR_JPEGR_CALCULATION_ERROR = JPEGR_RUNTIME_ERROR_BASE - 3, ERROR_JPEGR_METADATA_ERROR = JPEGR_RUNTIME_ERROR_BASE - 4, + ERROR_JPEGR_TONEMAP_ERROR = JPEGR_RUNTIME_ERROR_BASE - 5, }; } // namespace android::recoverymap diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h index 55973034bb..74f9776be6 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -129,6 +129,28 @@ typedef struct jpegr_info_struct* jr_info_ptr; class RecoveryMap { public: /* + * Encode API-0 + * Compress JPEGR image from 10-bit HDR YUV. + * + * Tonemap the HDR input to a SDR image, generate recovery map from the HDR and SDR images, + * compress SDR YUV to 8-bit JPEG and append the recovery map to the end of the compressed + * JPEG. + * @param uncompressed_p010_image uncompressed HDR image in P010 color format + * @param hdr_tf transfer function of the HDR image + * @param dest destination of the compressed JPEGR image + * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is + * the highest quality + * @param exif pointer to the exif metadata. + * @return NO_ERROR if encoding succeeds, error code if error occurs. + */ + status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, + jpegr_transfer_function hdr_tf, + jr_compressed_ptr dest, + int quality, + jr_exif_ptr exif); + + /* + * Encode API-1 * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. * * Generate recovery map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append @@ -151,6 +173,7 @@ public: jr_exif_ptr exif); /* + * Encode API-2 * Compress JPEGR image from 10-bit HDR YUV, 8-bit SDR YUV and compressed 8-bit JPEG. * * This method requires HAL Hardware JPEG encoder. @@ -159,6 +182,8 @@ public: * compressed JPEG. HDR and SDR inputs must be the same resolution and color space. * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format + * Note: the SDR image must be the decoded version of the JPEG + * input * @param compressed_jpeg_image compressed 8-bit JPEG image * @param hdr_tf transfer function of the HDR image * @param dest destination of the compressed JPEGR image @@ -171,6 +196,7 @@ public: jr_compressed_ptr dest); /* + * Encode API-3 * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. * * This method requires HAL Hardware JPEG encoder. @@ -190,6 +216,7 @@ public: jr_compressed_ptr dest); /* + * Decode API * Decompress JPEGR image. * * The output JPEGR image is in RGBA_1010102 data format if decoding to HDR. @@ -356,6 +383,16 @@ private: * @return XMP metadata in type of string */ std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata); + + /* + * This method will tone map a HDR image to an SDR image. + * + * @param uncompressed_p010_image (input) uncompressed P010 image + * @param dest (output) tone mapping result as a YUV_420 image + * @return NO_ERROR if calculation succeeds, error code if error occurs. + */ + status_t toneMap(jr_uncompressed_ptr uncompressed_p010_image, + jr_uncompressed_ptr dest); }; } // namespace android::recoverymap diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index 4a209ec381..a744d157fd 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -96,6 +96,59 @@ status_t Write(jr_compressed_ptr destination, const void* source, size_t length, return NO_ERROR; } +/* Encode API-0 */ +status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, + jpegr_transfer_function hdr_tf, + jr_compressed_ptr dest, + int quality, + jr_exif_ptr /* exif */) { + if (uncompressed_p010_image == nullptr || dest == nullptr) { + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + if (quality < 0 || quality > 100) { + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + jpegr_metadata metadata; + metadata.version = kJpegrVersion; + metadata.transferFunction = hdr_tf; + if (hdr_tf == JPEGR_TF_PQ) { + metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata; + } + + jpegr_uncompressed_struct uncompressed_yuv_420_image; + JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image)); + + jpegr_uncompressed_struct map; + JPEGR_CHECK(generateRecoveryMap( + &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map)); + std::unique_ptr map_data; + map_data.reset(reinterpret_cast(map.data)); + + jpegr_compressed_struct compressed_map; + compressed_map.maxLength = map.width * map.height; + unique_ptr compressed_map_data = make_unique(compressed_map.maxLength); + compressed_map.data = compressed_map_data.get(); + JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); + + JpegEncoder jpeg_encoder; + // TODO: determine ICC data based on color gamut information + if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data, + uncompressed_yuv_420_image.width, + uncompressed_yuv_420_image.height, quality, nullptr, 0)) { + return ERROR_JPEGR_ENCODE_ERROR; + } + jpegr_compressed_struct jpeg; + jpeg.data = jpeg_encoder.getCompressedImagePtr(); + jpeg.length = jpeg_encoder.getCompressedImageSize(); + + JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest)); + + return NO_ERROR; +} + +/* Encode API-1 */ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, jpegr_transfer_function hdr_tf, @@ -152,6 +205,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return NO_ERROR; } +/* Encode API-2 */ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, jr_compressed_ptr compressed_jpeg_image, @@ -193,6 +247,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return NO_ERROR; } +/* Encode API-3 */ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_compressed_ptr compressed_jpeg_image, jpegr_transfer_function hdr_tf, @@ -262,7 +317,7 @@ status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, return NO_ERROR; } - +/* Decode API */ status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, jr_uncompressed_ptr dest, jr_exif_ptr exif, @@ -673,4 +728,20 @@ string RecoveryMap::generateXmp(int secondary_image_length, jpegr_metadata& meta return ss.str(); } +status_t RecoveryMap::toneMap(jr_uncompressed_ptr uncompressed_p010_image, + jr_uncompressed_ptr dest) { + if (uncompressed_p010_image == nullptr || dest == nullptr) { + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + dest->width = uncompressed_p010_image->width; + dest->height = uncompressed_p010_image->height; + unique_ptr dest_data = make_unique(dest->width * dest->height * 3 / 2); + dest->data = dest_data.get(); + + // TODO: Tone map algorighm here. + + return NO_ERROR; +} + } // namespace android::recoverymap diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp index b3cd37e7e8..01c24ff2b5 100644 --- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp +++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp @@ -37,6 +37,7 @@ void RecoveryMapTest::TearDown() {} TEST_F(RecoveryMapTest, build) { // Force all of the recovery map lib to be linked by calling all public functions. RecoveryMap recovery_map; + recovery_map.encodeJPEGR(nullptr, static_cast(0), nullptr, 0, nullptr); recovery_map.encodeJPEGR(nullptr, nullptr, static_cast(0), nullptr, 0, nullptr); recovery_map.encodeJPEGR(nullptr, nullptr, nullptr, static_cast(0), -- GitLab From 4cd01fb2eb68d4e6652e027fefd67f76c9d3697c Mon Sep 17 00:00:00 2001 From: Chris Forbes Date: Wed, 19 Oct 2022 11:35:16 +1300 Subject: [PATCH 0590/1310] Break QueuePresentKHR into more manageable pieces Instead of QueuePresentKHR doing everything, let's decompose to: QueuePresentKHR -> PresentOneSwapchain (considers just one swapchain in the list) -> SetSwapchainSurfaceDamage (does KHR_incremental_present) -> SetSwapchainFrameTimestamp (does GOOGLE_display_timing) As part of the decomposition, stop trying to be so clever about reusing allocations for KHR_incremental_present; it's not worth threading it through the list of swapchains. Also just use a vector; it's not worth using the app-provided allocator from the device scope. Note some issues with the multi-swapchain case in general. We should also refine the fd handling to be leak-free by construction; handling raw fds is asking for trouble. Bug: b/255376900 Change-Id: Iaa66b7b1bfd2be2a1fda578fcb8141607be1e360 --- vulkan/libvulkan/swapchain.cpp | 353 +++++++++++++++++---------------- 1 file changed, 181 insertions(+), 172 deletions(-) diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp index 475bc40d14..8ec5e9495f 100644 --- a/vulkan/libvulkan/swapchain.cpp +++ b/vulkan/libvulkan/swapchain.cpp @@ -243,6 +243,11 @@ enum { MAX_TIMING_INFOS = 10 }; // syncronous requests to Surface Flinger): enum { MIN_NUM_FRAMES_AGO = 5 }; +bool IsSharedPresentMode(VkPresentModeKHR mode) { + return mode == VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR || + mode == VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR; +} + struct Swapchain { Swapchain(Surface& surface_, uint32_t num_images_, @@ -254,9 +259,7 @@ struct Swapchain { pre_transform(pre_transform_), frame_timestamps_enabled(false), acquire_next_image_timeout(-1), - shared(present_mode == VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR || - present_mode == - VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR) { + shared(IsSharedPresentMode(present_mode)) { ANativeWindow* window = surface.window.get(); native_window_get_refresh_cycle_duration( window, @@ -1783,6 +1786,173 @@ static VkResult WorstPresentResult(VkResult a, VkResult b) { return a != VK_SUCCESS ? a : b; } +// KHR_incremental_present aspect of QueuePresentKHR +static void SetSwapchainSurfaceDamage(ANativeWindow *window, const VkPresentRegionKHR *pRegion) { + std::vector rects(pRegion->rectangleCount); + for (auto i = 0u; i < pRegion->rectangleCount; i++) { + auto const& rect = pRegion->pRectangles[i]; + if (rect.layer > 0) { + ALOGV("vkQueuePresentKHR ignoring invalid layer (%u); using layer 0 instead", + rect.layer); + } + + rects[i].left = rect.offset.x; + rects[i].bottom = rect.offset.y; + rects[i].right = rect.offset.x + rect.extent.width; + rects[i].top = rect.offset.y + rect.extent.height; + } + native_window_set_surface_damage(window, rects.data(), rects.size()); +} + +// GOOGLE_display_timing aspect of QueuePresentKHR +static void SetSwapchainFrameTimestamp(Swapchain &swapchain, const VkPresentTimeGOOGLE *pTime) { + ANativeWindow *window = swapchain.surface.window.get(); + + // We don't know whether the app will actually use GOOGLE_display_timing + // with a particular swapchain until QueuePresent; enable it on the BQ + // now if needed + if (!swapchain.frame_timestamps_enabled) { + ALOGV("Calling native_window_enable_frame_timestamps(true)"); + native_window_enable_frame_timestamps(window, true); + swapchain.frame_timestamps_enabled = true; + } + + // Record the nativeFrameId so it can be later correlated to + // this present. + uint64_t nativeFrameId = 0; + int err = native_window_get_next_frame_id( + window, &nativeFrameId); + if (err != android::OK) { + ALOGE("Failed to get next native frame ID."); + } + + // Add a new timing record with the user's presentID and + // the nativeFrameId. + swapchain.timing.emplace_back(pTime, nativeFrameId); + if (swapchain.timing.size() > MAX_TIMING_INFOS) { + swapchain.timing.erase( + swapchain.timing.begin(), + swapchain.timing.begin() + swapchain.timing.size() - MAX_TIMING_INFOS); + } + if (pTime->desiredPresentTime) { + ALOGV( + "Calling native_window_set_buffers_timestamp(%" PRId64 ")", + pTime->desiredPresentTime); + native_window_set_buffers_timestamp( + window, + static_cast(pTime->desiredPresentTime)); + } +} + +static VkResult PresentOneSwapchain( + VkQueue queue, + Swapchain& swapchain, + uint32_t imageIndex, + const VkPresentRegionKHR *pRegion, + const VkPresentTimeGOOGLE *pTime, + uint32_t waitSemaphoreCount, + const VkSemaphore *pWaitSemaphores) { + + VkDevice device = GetData(queue).driver_device; + const auto& dispatch = GetData(queue).driver; + + Swapchain::Image& img = swapchain.images[imageIndex]; + VkResult swapchain_result = VK_SUCCESS; + VkResult result; + int err; + + // XXX: long standing issue: QueueSignalReleaseImageANDROID consumes the + // wait semaphores, so this doesn't actually work for the multiple swapchain + // case. + int fence = -1; + result = dispatch.QueueSignalReleaseImageANDROID( + queue, waitSemaphoreCount, + pWaitSemaphores, img.image, &fence); + if (result != VK_SUCCESS) { + ALOGE("QueueSignalReleaseImageANDROID failed: %d", result); + swapchain_result = result; + } + if (img.release_fence >= 0) + close(img.release_fence); + img.release_fence = fence < 0 ? -1 : dup(fence); + + if (swapchain.surface.swapchain_handle == HandleFromSwapchain(&swapchain)) { + ANativeWindow* window = swapchain.surface.window.get(); + if (swapchain_result == VK_SUCCESS) { + + if (pRegion) { + SetSwapchainSurfaceDamage(window, pRegion); + } + if (pTime) { + SetSwapchainFrameTimestamp(swapchain, pTime); + } + + err = window->queueBuffer(window, img.buffer.get(), fence); + // queueBuffer always closes fence, even on error + if (err != android::OK) { + ALOGE("queueBuffer failed: %s (%d)", strerror(-err), err); + swapchain_result = WorstPresentResult( + swapchain_result, VK_ERROR_SURFACE_LOST_KHR); + } else { + if (img.dequeue_fence >= 0) { + close(img.dequeue_fence); + img.dequeue_fence = -1; + } + img.dequeued = false; + } + + // If the swapchain is in shared mode, immediately dequeue the + // buffer so it can be presented again without an intervening + // call to AcquireNextImageKHR. We expect to get the same buffer + // back from every call to dequeueBuffer in this mode. + if (swapchain.shared && swapchain_result == VK_SUCCESS) { + ANativeWindowBuffer* buffer; + int fence_fd; + err = window->dequeueBuffer(window, &buffer, &fence_fd); + if (err != android::OK) { + ALOGE("dequeueBuffer failed: %s (%d)", strerror(-err), err); + swapchain_result = WorstPresentResult(swapchain_result, + VK_ERROR_SURFACE_LOST_KHR); + } else if (img.buffer != buffer) { + ALOGE("got wrong image back for shared swapchain"); + swapchain_result = WorstPresentResult(swapchain_result, + VK_ERROR_SURFACE_LOST_KHR); + } else { + img.dequeue_fence = fence_fd; + img.dequeued = true; + } + } + } + if (swapchain_result != VK_SUCCESS) { + OrphanSwapchain(device, &swapchain); + } + // Android will only return VK_SUBOPTIMAL_KHR for vkQueuePresentKHR, + // and only when the window's transform/rotation changes. Extent + // changes will not cause VK_SUBOPTIMAL_KHR because of the + // application issues that were caused when the following transform + // change was added. + int window_transform_hint; + err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT, + &window_transform_hint); + if (err != android::OK) { + ALOGE("NATIVE_WINDOW_TRANSFORM_HINT query failed: %s (%d)", + strerror(-err), err); + swapchain_result = WorstPresentResult( + swapchain_result, VK_ERROR_SURFACE_LOST_KHR); + } + if (swapchain.pre_transform != window_transform_hint) { + swapchain_result = + WorstPresentResult(swapchain_result, VK_SUBOPTIMAL_KHR); + } + } else { + ReleaseSwapchainImage(device, swapchain.shared, nullptr, fence, + img, true); + swapchain_result = VK_ERROR_OUT_OF_DATE_KHR; + } + + return swapchain_result; +} + VKAPI_ATTR VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) { ATRACE_CALL(); @@ -1791,8 +1961,6 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) { "vkQueuePresentKHR: invalid VkPresentInfoKHR structure type %d", present_info->sType); - VkDevice device = GetData(queue).driver_device; - const auto& dispatch = GetData(queue).driver; VkResult final_result = VK_SUCCESS; // Look at the pNext chain for supported extension structs: @@ -1828,175 +1996,19 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) { (present_regions) ? present_regions->pRegions : nullptr; const VkPresentTimeGOOGLE* times = (present_times) ? present_times->pTimes : nullptr; - const VkAllocationCallbacks* allocator = &GetData(device).allocator; - android_native_rect_t* rects = nullptr; - uint32_t nrects = 0; for (uint32_t sc = 0; sc < present_info->swapchainCount; sc++) { Swapchain& swapchain = *SwapchainFromHandle(present_info->pSwapchains[sc]); - uint32_t image_idx = present_info->pImageIndices[sc]; - Swapchain::Image& img = swapchain.images[image_idx]; - const VkPresentRegionKHR* region = - (regions && !swapchain.mailbox_mode) ? ®ions[sc] : nullptr; - const VkPresentTimeGOOGLE* time = (times) ? ×[sc] : nullptr; - VkResult swapchain_result = VK_SUCCESS; - VkResult result; - int err; - - int fence = -1; - result = dispatch.QueueSignalReleaseImageANDROID( - queue, present_info->waitSemaphoreCount, - present_info->pWaitSemaphores, img.image, &fence); - if (result != VK_SUCCESS) { - ALOGE("QueueSignalReleaseImageANDROID failed: %d", result); - swapchain_result = result; - } - if (img.release_fence >= 0) - close(img.release_fence); - img.release_fence = fence < 0 ? -1 : dup(fence); - - if (swapchain.surface.swapchain_handle == - present_info->pSwapchains[sc]) { - ANativeWindow* window = swapchain.surface.window.get(); - if (swapchain_result == VK_SUCCESS) { - if (region) { - // Process the incremental-present hint for this swapchain: - uint32_t rcount = region->rectangleCount; - if (rcount > nrects) { - android_native_rect_t* new_rects = - static_cast( - allocator->pfnReallocation( - allocator->pUserData, rects, - sizeof(android_native_rect_t) * rcount, - alignof(android_native_rect_t), - VK_SYSTEM_ALLOCATION_SCOPE_COMMAND)); - if (new_rects) { - rects = new_rects; - nrects = rcount; - } else { - rcount = 0; // Ignore the hint for this swapchain - } - } - for (uint32_t r = 0; r < rcount; ++r) { - if (region->pRectangles[r].layer > 0) { - ALOGV( - "vkQueuePresentKHR ignoring invalid layer " - "(%u); using layer 0 instead", - region->pRectangles[r].layer); - } - int x = region->pRectangles[r].offset.x; - int y = region->pRectangles[r].offset.y; - int width = static_cast( - region->pRectangles[r].extent.width); - int height = static_cast( - region->pRectangles[r].extent.height); - android_native_rect_t* cur_rect = &rects[r]; - cur_rect->left = x; - cur_rect->top = y + height; - cur_rect->right = x + width; - cur_rect->bottom = y; - } - native_window_set_surface_damage(window, rects, rcount); - } - if (time) { - if (!swapchain.frame_timestamps_enabled) { - ALOGV( - "Calling " - "native_window_enable_frame_timestamps(true)"); - native_window_enable_frame_timestamps(window, true); - swapchain.frame_timestamps_enabled = true; - } - // Record the nativeFrameId so it can be later correlated to - // this present. - uint64_t nativeFrameId = 0; - err = native_window_get_next_frame_id( - window, &nativeFrameId); - if (err != android::OK) { - ALOGE("Failed to get next native frame ID."); - } - - // Add a new timing record with the user's presentID and - // the nativeFrameId. - swapchain.timing.emplace_back(time, nativeFrameId); - while (swapchain.timing.size() > MAX_TIMING_INFOS) { - swapchain.timing.erase(swapchain.timing.begin()); - } - if (time->desiredPresentTime) { - // Set the desiredPresentTime: - ALOGV( - "Calling " - "native_window_set_buffers_timestamp(%" PRId64 ")", - time->desiredPresentTime); - native_window_set_buffers_timestamp( - window, - static_cast(time->desiredPresentTime)); - } - } - - err = window->queueBuffer(window, img.buffer.get(), fence); - // queueBuffer always closes fence, even on error - if (err != android::OK) { - ALOGE("queueBuffer failed: %s (%d)", strerror(-err), err); - swapchain_result = WorstPresentResult( - swapchain_result, VK_ERROR_SURFACE_LOST_KHR); - } else { - if (img.dequeue_fence >= 0) { - close(img.dequeue_fence); - img.dequeue_fence = -1; - } - img.dequeued = false; - } - - // If the swapchain is in shared mode, immediately dequeue the - // buffer so it can be presented again without an intervening - // call to AcquireNextImageKHR. We expect to get the same buffer - // back from every call to dequeueBuffer in this mode. - if (swapchain.shared && swapchain_result == VK_SUCCESS) { - ANativeWindowBuffer* buffer; - int fence_fd; - err = window->dequeueBuffer(window, &buffer, &fence_fd); - if (err != android::OK) { - ALOGE("dequeueBuffer failed: %s (%d)", strerror(-err), err); - swapchain_result = WorstPresentResult(swapchain_result, - VK_ERROR_SURFACE_LOST_KHR); - } else if (img.buffer != buffer) { - ALOGE("got wrong image back for shared swapchain"); - swapchain_result = WorstPresentResult(swapchain_result, - VK_ERROR_SURFACE_LOST_KHR); - } else { - img.dequeue_fence = fence_fd; - img.dequeued = true; - } - } - } - if (swapchain_result != VK_SUCCESS) { - OrphanSwapchain(device, &swapchain); - } - // Android will only return VK_SUBOPTIMAL_KHR for vkQueuePresentKHR, - // and only when the window's transform/rotation changes. Extent - // changes will not cause VK_SUBOPTIMAL_KHR because of the - // application issues that were caused when the following transform - // change was added. - int window_transform_hint; - err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT, - &window_transform_hint); - if (err != android::OK) { - ALOGE("NATIVE_WINDOW_TRANSFORM_HINT query failed: %s (%d)", - strerror(-err), err); - swapchain_result = WorstPresentResult( - swapchain_result, VK_ERROR_SURFACE_LOST_KHR); - } - if (swapchain.pre_transform != window_transform_hint) { - swapchain_result = - WorstPresentResult(swapchain_result, VK_SUBOPTIMAL_KHR); - } - } else { - ReleaseSwapchainImage(device, swapchain.shared, nullptr, fence, - img, true); - swapchain_result = VK_ERROR_OUT_OF_DATE_KHR; - } + VkResult swapchain_result = PresentOneSwapchain( + queue, + swapchain, + present_info->pImageIndices[sc], + (regions && !swapchain.mailbox_mode) ? ®ions[sc] : nullptr, + times ? ×[sc] : nullptr, + present_info->waitSemaphoreCount, + present_info->pWaitSemaphores); if (present_info->pResults) present_info->pResults[sc] = swapchain_result; @@ -2004,9 +2016,6 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) { if (swapchain_result != final_result) final_result = WorstPresentResult(final_result, swapchain_result); } - if (rects) { - allocator->pfnFree(allocator->pUserData, rects); - } return final_result; } -- GitLab From 76554eb475edf45349b64845909ed0e740f640dc Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Fri, 9 Dec 2022 03:38:36 +0000 Subject: [PATCH 0591/1310] SF: Identify layers with buffers when dumping offscreen list Test: adb shell su root dumpsys SurfaceFlinger Bug: 238781169 Change-Id: I6b1d051fedc5883df814fe5edc8aaa766cbd06f8 --- services/surfaceflinger/Layer.cpp | 5 +++-- services/surfaceflinger/Layer.h | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index d53f0b167c..f4006a6a01 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -1472,8 +1472,9 @@ void Layer::getFrameStats(FrameStats* outStats) const { mFrameTracker.getStats(outStats); } -void Layer::dumpCallingUidPid(std::string& result) const { - StringAppendF(&result, "Layer %s (%s) ownerPid:%d ownerUid:%d\n", getName().c_str(), getType(), +void Layer::dumpOffscreenDebugInfo(std::string& result) const { + std::string hasBuffer = hasBufferOrSidebandStream() ? " (contains buffer)" : ""; + StringAppendF(&result, "Layer %s%s pid:%d uid:%d\n", getName().c_str(), hasBuffer.c_str(), mOwnerPid, mOwnerUid); } diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index f743896c03..bab721fe41 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -634,7 +634,7 @@ public: void miniDump(std::string& result, const DisplayDevice&) const; void dumpFrameStats(std::string& result) const; - void dumpCallingUidPid(std::string& result) const; + void dumpOffscreenDebugInfo(std::string& result) const; void clearFrameStats(); void logFrameStats(); void getFrameStats(FrameStats* outStats) const; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index fd03ba3ab7..0d5b4c6317 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5143,7 +5143,7 @@ void SurfaceFlinger::dumpOffscreenLayers(std::string& result) { std::string result; for (Layer* offscreenLayer : mOffscreenLayers) { offscreenLayer->traverse(LayerVector::StateSet::Drawing, - [&](Layer* layer) { layer->dumpCallingUidPid(result); }); + [&](Layer* layer) { layer->dumpOffscreenDebugInfo(result); }); } return result; }); -- GitLab From c50b4988e93872bfe023a4e099548f84bdfd998d Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Wed, 24 Nov 2021 12:34:52 -0800 Subject: [PATCH 0592/1310] Migrate ITransactionCompletedListener to AIDL This migrates the c++ interface to aidl. Bug: 225250470 Test: atest libsurfaceflinger_unittest libgui_test SurfaceFlinger_test Change-Id: I997e302ac8c6a23bedefaa5b8272677f3dce54df --- libs/binder/include/binder/IInterface.h | 1 - libs/gui/ISurfaceComposer.cpp | 1 + libs/gui/ITransactionCompletedListener.cpp | 71 +------------------ libs/gui/LayerState.cpp | 1 + libs/gui/SurfaceComposerClient.cpp | 33 ++++++--- .../gui/ITransactionCompletedListener.aidl | 31 ++++++++ libs/gui/aidl/android/gui/ListenerStats.aidl | 19 +++++ .../aidl/android/gui/ReleaseCallbackId.aidl | 19 +++++ libs/gui/include/gui/ISurfaceComposer.h | 3 +- libs/gui/include/gui/LayerState.h | 6 +- ...ionCompletedListener.h => ListenerStats.h} | 54 ++------------ libs/gui/include/gui/ReleaseCallbackId.h | 50 +++++++++++++ libs/gui/include/gui/SurfaceComposerClient.h | 25 +++++-- .../FrontEnd/TransactionHandler.cpp | 2 +- .../FrontEnd/TransactionHandler.h | 1 + services/surfaceflinger/Layer.cpp | 9 ++- services/surfaceflinger/SurfaceFlinger.cpp | 5 +- services/surfaceflinger/SurfaceFlinger.h | 5 +- .../TransactionCallbackInvoker.h | 17 ++++- 19 files changed, 207 insertions(+), 146 deletions(-) create mode 100644 libs/gui/aidl/android/gui/ITransactionCompletedListener.aidl create mode 100644 libs/gui/aidl/android/gui/ListenerStats.aidl create mode 100644 libs/gui/aidl/android/gui/ReleaseCallbackId.aidl rename libs/gui/include/gui/{ITransactionCompletedListener.h => ListenerStats.h} (82%) create mode 100644 libs/gui/include/gui/ReleaseCallbackId.h diff --git a/libs/binder/include/binder/IInterface.h b/libs/binder/include/binder/IInterface.h index dc572ac953..8cc8105ff9 100644 --- a/libs/binder/include/binder/IInterface.h +++ b/libs/binder/include/binder/IInterface.h @@ -230,7 +230,6 @@ constexpr const char* const kManualInterfaces[] = { "android.graphicsenv.IGpuService", "android.gui.IConsumerListener", "android.gui.IGraphicBufferConsumer", - "android.gui.ITransactionComposerListener", "android.gui.SensorEventConnection", "android.gui.SensorServer", "android.hardware.ICamera", diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index a77ca04943..a0e75ffe49 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -42,6 +42,7 @@ using namespace aidl::android::hardware::graphics; namespace android { +using gui::CallbackId; using gui::DisplayCaptureArgs; using gui::IDisplayEventConnection; using gui::IRegionSamplingListener; diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp index 2b25b614e9..23d7d500c8 100644 --- a/libs/gui/ITransactionCompletedListener.cpp +++ b/libs/gui/ITransactionCompletedListener.cpp @@ -21,22 +21,11 @@ #include #include -#include #include +#include #include -namespace android { - -namespace { // Anonymous - -enum class Tag : uint32_t { - ON_TRANSACTION_COMPLETED = IBinder::FIRST_CALL_TRANSACTION, - ON_RELEASE_BUFFER, - ON_TRANSACTION_QUEUE_STALLED, - LAST = ON_TRANSACTION_QUEUE_STALLED, -}; - -} // Anonymous namespace +namespace android::gui { status_t FrameEventHistoryStats::writeToParcel(Parcel* output) const { status_t err = output->writeUint64(frameNumber); @@ -274,60 +263,6 @@ ListenerStats ListenerStats::createEmpty( return listenerStats; } -class BpTransactionCompletedListener : public SafeBpInterface { -public: - explicit BpTransactionCompletedListener(const sp& impl) - : SafeBpInterface(impl, "BpTransactionCompletedListener") { - } - - ~BpTransactionCompletedListener() override; - - void onTransactionCompleted(ListenerStats stats) override { - callRemoteAsync(Tag::ON_TRANSACTION_COMPLETED, - stats); - } - - void onReleaseBuffer(ReleaseCallbackId callbackId, sp releaseFence, - uint32_t currentMaxAcquiredBufferCount) override { - callRemoteAsync(Tag::ON_RELEASE_BUFFER, callbackId, - releaseFence, - currentMaxAcquiredBufferCount); - } - - void onTransactionQueueStalled(const String8& reason) override { - callRemoteAsync< - decltype(&ITransactionCompletedListener:: - onTransactionQueueStalled)>(Tag::ON_TRANSACTION_QUEUE_STALLED, - reason); - } -}; - -// Out-of-line virtual method definitions to trigger vtable emission in this translation unit (see -// clang warning -Wweak-vtables) -BpTransactionCompletedListener::~BpTransactionCompletedListener() = default; - -IMPLEMENT_META_INTERFACE(TransactionCompletedListener, "android.gui.ITransactionComposerListener"); - -status_t BnTransactionCompletedListener::onTransact(uint32_t code, const Parcel& data, - Parcel* reply, uint32_t flags) { - if (code < IBinder::FIRST_CALL_TRANSACTION || code > static_cast(Tag::LAST)) { - return BBinder::onTransact(code, data, reply, flags); - } - auto tag = static_cast(code); - switch (tag) { - case Tag::ON_TRANSACTION_COMPLETED: - return callLocalAsync(data, reply, - &ITransactionCompletedListener::onTransactionCompleted); - case Tag::ON_RELEASE_BUFFER: - return callLocalAsync(data, reply, &ITransactionCompletedListener::onReleaseBuffer); - case Tag::ON_TRANSACTION_QUEUE_STALLED: - return callLocalAsync(data, reply, - &ITransactionCompletedListener::onTransactionQueueStalled); - } -} - ListenerCallbacks ListenerCallbacks::filter(CallbackId::Type type) const { std::vector filteredCallbackIds; for (const auto& callbackId : callbackIds) { @@ -366,4 +301,4 @@ status_t ReleaseCallbackId::readFromParcel(const Parcel* input) { const ReleaseCallbackId ReleaseCallbackId::INVALID_ID = ReleaseCallbackId(0, 0); -}; // namespace android +}; // namespace android::gui diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 59b62fe58c..0d1a69b898 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -51,6 +51,7 @@ namespace android { +using gui::CallbackId; using gui::FocusRequest; using gui::WindowInfoHandle; diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 7085e8a349..d741c99d01 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -314,7 +314,8 @@ void TransactionCompletedListener::addSurfaceControlToCallbacks( } } -void TransactionCompletedListener::onTransactionCompleted(ListenerStats listenerStats) { +binder::Status TransactionCompletedListener::onTransactionCompleted( + const ListenerStats& listenerStats) { std::unordered_map callbacksMap; std::multimap> jankListenersMap; { @@ -454,9 +455,10 @@ void TransactionCompletedListener::onTransactionCompleted(ListenerStats listener } } } + return binder::Status::ok(); } -void TransactionCompletedListener::onTransactionQueueStalled(const String8& reason) { +binder::Status TransactionCompletedListener::onTransactionQueueStalled(const std::string& reason) { std::unordered_map> callbackCopy; { std::scoped_lock lock(mMutex); @@ -465,6 +467,7 @@ void TransactionCompletedListener::onTransactionQueueStalled(const String8& reas for (auto const& it : callbackCopy) { it.second(reason.c_str()); } + return binder::Status::ok(); } void TransactionCompletedListener::addQueueStallListener( @@ -478,9 +481,12 @@ void TransactionCompletedListener::removeQueueStallListener(void* id) { mQueueStallListeners.erase(id); } -void TransactionCompletedListener::onReleaseBuffer(ReleaseCallbackId callbackId, - sp releaseFence, - uint32_t currentMaxAcquiredBufferCount) { +binder::Status TransactionCompletedListener::onReleaseBuffer( + const ReleaseCallbackId& callbackId, + const std::optional& releaseFenceFd, + int32_t currentMaxAcquiredBufferCount) { + sp releaseFence(releaseFenceFd ? new Fence(::dup(releaseFenceFd->get())) + : Fence::NO_FENCE); ReleaseBufferCallback callback; { std::scoped_lock lock(mMutex); @@ -489,13 +495,14 @@ void TransactionCompletedListener::onReleaseBuffer(ReleaseCallbackId callbackId, if (!callback) { ALOGE("Could not call release buffer callback, buffer not found %s", callbackId.to_string().c_str()); - return; + return binder::Status::fromExceptionCode(binder::Status::EX_ILLEGAL_ARGUMENT); } std::optional optionalMaxAcquiredBufferCount = - currentMaxAcquiredBufferCount == UINT_MAX + static_cast(currentMaxAcquiredBufferCount) == UINT_MAX ? std::nullopt : std::make_optional(currentMaxAcquiredBufferCount); callback(callbackId, releaseFence, optionalMaxAcquiredBufferCount); + return binder::Status::ok(); } ReleaseBufferCallback TransactionCompletedListener::popReleaseBufferCallbackLocked( @@ -825,7 +832,11 @@ void SurfaceComposerClient::Transaction::releaseBufferIfOverwriting(const layer_ ->mReleaseCallbackThread .addReleaseCallback(state.bufferData->generateReleaseCallbackId(), fence); } else { - listener->onReleaseBuffer(state.bufferData->generateReleaseCallbackId(), fence, UINT_MAX); + std::optional fenceFd; + if (fence != Fence::NO_FENCE) { + fenceFd = os::ParcelFileDescriptor(base::unique_fd(::dup(fence->get()))); + } + listener->onReleaseBuffer(state.bufferData->generateReleaseCallbackId(), fenceFd, UINT_MAX); } } @@ -2846,7 +2857,11 @@ void ReleaseCallbackThread::threadMain() { while (!callbackInfos.empty()) { auto [callbackId, releaseFence] = callbackInfos.front(); - listener->onReleaseBuffer(callbackId, std::move(releaseFence), UINT_MAX); + std::optional fenceFd; + if (releaseFence != Fence::NO_FENCE) { + fenceFd = os::ParcelFileDescriptor(base::unique_fd(::dup(releaseFence->get()))); + } + listener->onReleaseBuffer(callbackId, fenceFd, UINT_MAX); callbackInfos.pop(); } diff --git a/libs/gui/aidl/android/gui/ITransactionCompletedListener.aidl b/libs/gui/aidl/android/gui/ITransactionCompletedListener.aidl new file mode 100644 index 0000000000..dde4d38cba --- /dev/null +++ b/libs/gui/aidl/android/gui/ITransactionCompletedListener.aidl @@ -0,0 +1,31 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +import android.gui.ListenerStats; +import android.gui.ReleaseCallbackId; + +/** @hide */ +oneway interface ITransactionCompletedListener { + void onTransactionCompleted(in ListenerStats stats); + + void onReleaseBuffer(in ReleaseCallbackId callbackId, + in @nullable ParcelFileDescriptor releaseFenceFd, + int currentMaxAcquiredBufferCount); + + void onTransactionQueueStalled(@utf8InCpp String name); +} diff --git a/libs/gui/aidl/android/gui/ListenerStats.aidl b/libs/gui/aidl/android/gui/ListenerStats.aidl new file mode 100644 index 0000000000..63248b2bf3 --- /dev/null +++ b/libs/gui/aidl/android/gui/ListenerStats.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +parcelable ListenerStats cpp_header "gui/ListenerStats.h"; diff --git a/libs/gui/aidl/android/gui/ReleaseCallbackId.aidl b/libs/gui/aidl/android/gui/ReleaseCallbackId.aidl new file mode 100644 index 0000000000..c86de34de9 --- /dev/null +++ b/libs/gui/aidl/android/gui/ReleaseCallbackId.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +parcelable ReleaseCallbackId cpp_header "gui/ReleaseCallbackId.h"; diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index d517e99fda..d70a7f0f1b 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -23,11 +23,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include @@ -66,6 +66,7 @@ using gui::FrameTimelineInfo; using gui::IDisplayEventConnection; using gui::IRegionSamplingListener; using gui::IScreenCaptureListener; +using gui::ListenerCallbacks; using gui::SpHash; namespace gui { diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 45a84f6c66..c5fdf82d4f 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -21,10 +21,10 @@ #include #include +#include #include #include #include -#include #include #include @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,9 @@ class Parcel; using gui::ISurfaceComposerClient; using gui::LayerMetadata; +using gui::ITransactionCompletedListener; +using gui::ReleaseCallbackId; + struct client_cache_t { wp token = nullptr; uint64_t id; diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ListenerStats.h similarity index 82% rename from libs/gui/include/gui/ITransactionCompletedListener.h rename to libs/gui/include/gui/ListenerStats.h index 453e8f3ef5..3a12802146 100644 --- a/libs/gui/include/gui/ITransactionCompletedListener.h +++ b/libs/gui/include/gui/ListenerStats.h @@ -24,6 +24,8 @@ #include #include +#include + #include #include @@ -32,10 +34,7 @@ #include #include -namespace android { - -class ITransactionCompletedListener; -class ListenerCallbacks; +namespace android::gui { class CallbackId : public Parcelable { public: @@ -54,30 +53,6 @@ struct CallbackIdHash { std::size_t operator()(const CallbackId& key) const { return std::hash()(key.id); } }; -class ReleaseCallbackId : public Parcelable { -public: - static const ReleaseCallbackId INVALID_ID; - - uint64_t bufferId; - uint64_t framenumber; - ReleaseCallbackId() {} - ReleaseCallbackId(uint64_t bufferId, uint64_t framenumber) - : bufferId(bufferId), framenumber(framenumber) {} - status_t writeToParcel(Parcel* output) const override; - status_t readFromParcel(const Parcel* input) override; - - bool operator==(const ReleaseCallbackId& rhs) const { - return bufferId == rhs.bufferId && framenumber == rhs.framenumber; - } - bool operator!=(const ReleaseCallbackId& rhs) const { return !operator==(rhs); } - std::string to_string() const { - if (*this == INVALID_ID) return "INVALID_ID"; - - return "bufferId:" + std::to_string(bufferId) + - " framenumber:" + std::to_string(framenumber); - } -}; - struct ReleaseBufferCallbackIdHash { std::size_t operator()(const ReleaseCallbackId& key) const { return std::hash()(key.bufferId); @@ -186,27 +161,6 @@ public: std::vector transactionStats; }; -class ITransactionCompletedListener : public IInterface { -public: - DECLARE_META_INTERFACE(TransactionCompletedListener) - - virtual void onTransactionCompleted(ListenerStats stats) = 0; - - virtual void onReleaseBuffer(ReleaseCallbackId callbackId, sp releaseFence, - uint32_t currentMaxAcquiredBufferCount) = 0; - - virtual void onTransactionQueueStalled(const String8& name) = 0; -}; - -class BnTransactionCompletedListener : public SafeBnInterface { -public: - BnTransactionCompletedListener() - : SafeBnInterface("BnTransactionCompletedListener") {} - - status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, - uint32_t flags = 0) override; -}; - class ListenerCallbacks { public: ListenerCallbacks(const sp& listener, @@ -268,4 +222,4 @@ struct ListenerCallbacksHash { } }; -} // namespace android +} // namespace android::gui diff --git a/libs/gui/include/gui/ReleaseCallbackId.h b/libs/gui/include/gui/ReleaseCallbackId.h new file mode 100644 index 0000000000..142ee5a727 --- /dev/null +++ b/libs/gui/include/gui/ReleaseCallbackId.h @@ -0,0 +1,50 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include + +namespace android::gui { + +class ReleaseCallbackId : public Parcelable { +public: + static const ReleaseCallbackId INVALID_ID; + + uint64_t bufferId; + uint64_t framenumber; + ReleaseCallbackId() {} + ReleaseCallbackId(uint64_t bufferId, uint64_t framenumber) + : bufferId(bufferId), framenumber(framenumber) {} + status_t writeToParcel(Parcel* output) const override; + status_t readFromParcel(const Parcel* input) override; + + bool operator==(const ReleaseCallbackId& rhs) const { + return bufferId == rhs.bufferId && framenumber == rhs.framenumber; + } + bool operator!=(const ReleaseCallbackId& rhs) const { return !operator==(rhs); } + std::string to_string() const { + if (*this == INVALID_ID) return "INVALID_ID"; + + return "bufferId:" + std::to_string(bufferId) + + " framenumber:" + std::to_string(framenumber); + } +}; + +} // namespace android::gui diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index df47002b3b..96d3a23bec 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -42,10 +42,13 @@ #include +#include + #include #include -#include #include +#include +#include #include #include #include @@ -59,11 +62,21 @@ class IGraphicBufferProducer; class ITunnelModeEnabledListener; class Region; +using gui::BnTransactionCompletedListener; +using gui::CallbackId; +using gui::CallbackIdHash; using gui::DisplayCaptureArgs; +using gui::FrameEventHistoryStats; using gui::IRegionSamplingListener; using gui::ISurfaceComposerClient; +using gui::ITransactionCompletedListener; +using gui::JankData; using gui::LayerCaptureArgs; using gui::LayerMetadata; +using gui::ListenerStats; +using gui::ReleaseBufferCallbackIdHash; +using gui::ReleaseCallbackId; +using gui::SurfaceStats; struct SurfaceControlStats { SurfaceControlStats(const sp& sc, nsecs_t latchTime, @@ -825,17 +838,17 @@ public: void setReleaseBufferCallback(const ReleaseCallbackId&, ReleaseBufferCallback); // BnTransactionCompletedListener overrides - void onTransactionCompleted(ListenerStats stats) override; - void onReleaseBuffer(ReleaseCallbackId, sp releaseFence, - uint32_t currentMaxAcquiredBufferCount) override; + binder::Status onTransactionCompleted(const ListenerStats& stats) override; + binder::Status onReleaseBuffer(const ReleaseCallbackId& callbackId, + const std::optional& releaseFenceFd, + int32_t currentMaxAcquiredBufferCount) override; + binder::Status onTransactionQueueStalled(const std::string& reason) override; void removeReleaseBufferCallback(const ReleaseCallbackId& callbackId); // For Testing Only static void setInstance(const sp&); - void onTransactionQueueStalled(const String8& reason) override; - private: ReleaseBufferCallback popReleaseBufferCallbackLocked(const ReleaseCallbackId&); static sp sInstance; diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp index 8629671214..c2109b3aa5 100644 --- a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp +++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp @@ -177,7 +177,7 @@ void TransactionHandler::onTransactionQueueStalled(uint64_t transactionId, } mStalledTransactions.push_back(transactionId); - listener->onTransactionQueueStalled(String8(reason.c_str())); + listener->onTransactionQueueStalled(reason); } void TransactionHandler::removeFromStalledTransactions(uint64_t id) { diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h index a06b870549..475ff1b1dc 100644 --- a/services/surfaceflinger/FrontEnd/TransactionHandler.h +++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h @@ -29,6 +29,7 @@ namespace android { class TestableSurfaceFlinger; +using gui::IListenerHash; namespace surfaceflinger::frontend { class TransactionHandler { diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 0017af0476..56abc516fd 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -2604,9 +2604,12 @@ void Layer::callReleaseBufferCallback(const sp& l return; } ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64, getDebugName(), framenumber); - listener->onReleaseBuffer({buffer->getId(), framenumber}, - releaseFence ? releaseFence : Fence::NO_FENCE, - currentMaxAcquiredBufferCount); + std::optional fenceFd; + if (releaseFence) { + fenceFd = os::ParcelFileDescriptor(base::unique_fd(::dup(releaseFence->get()))); + } + listener->onReleaseBuffer({buffer->getId(), framenumber}, fenceFd, + static_cast(currentMaxAcquiredBufferCount)); } void Layer::onLayerDisplayed(ftl::SharedFuture futureFenceResult) { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index d4c4fb28f5..55644e2a41 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -7113,8 +7113,7 @@ std::shared_ptr SurfaceFlinger::getExternalTextur layerName, static_cast(mMaxRenderTargetSize)); ALOGD("%s", errorMessage.c_str()); if (bufferData.releaseBufferListener) { - bufferData.releaseBufferListener->onTransactionQueueStalled( - String8(errorMessage.c_str())); + bufferData.releaseBufferListener->onTransactionQueueStalled(errorMessage); } return nullptr; } @@ -7132,7 +7131,7 @@ std::shared_ptr SurfaceFlinger::getExternalTextur if (bufferData.releaseBufferListener) { bufferData.releaseBufferListener->onTransactionQueueStalled( - String8("Buffer processing hung due to full buffer cache")); + "Buffer processing hung due to full buffer cache"); } } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 3354b24bbb..8fa427843b 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -34,8 +35,8 @@ #include #include #include -#include #include + #include #include #include @@ -125,7 +126,9 @@ using frontend::TransactionHandler; using gui::CaptureArgs; using gui::DisplayCaptureArgs; using gui::IRegionSamplingListener; +using gui::ITransactionCompletedListener; using gui::LayerCaptureArgs; + using gui::ScreenCaptureResults; namespace frametimeline { diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h index 61ff9bce98..c09bcce067 100644 --- a/services/surfaceflinger/TransactionCallbackInvoker.h +++ b/services/surfaceflinger/TransactionCallbackInvoker.h @@ -26,14 +26,27 @@ #include #include +#include + #include -#include -#include +#include +#include +#include #include #include namespace android { +using gui::CallbackId; +using gui::FrameEventHistoryStats; +using gui::IListenerHash; +using gui::ITransactionCompletedListener; +using gui::JankData; +using gui::ListenerCallbacks; +using gui::ListenerStats; +using gui::ReleaseCallbackId; +using gui::TransactionStats; + class CallbackHandle : public RefBase { public: CallbackHandle(const sp& transactionListener, const std::vector& ids, -- GitLab From 439afadf859e15f01cb2781949d2402e69e4b4b7 Mon Sep 17 00:00:00 2001 From: Brian Lindahl Date: Mon, 14 Nov 2022 11:16:55 -0700 Subject: [PATCH 0593/1310] Push HWC cache slot generation down into CompositionEngine Stop caching buffers inside SurfaceFlinger and remove the tight coupling between SurfaceFlinger's ClientCache and the Hardware Composer cache. This allows a better seperation of responsibility, where buffer cache management is not split between HwcSlotGenerator and HwcBufferCache, but is instead solely handled by HwcBufferCache. Note that FramebufferSurface and VirtualDisplaySurface no longer use HwcBufferCache, but instead use their own cache slot management that is solely based on BufferQueue slot numbers. Also do minor refactoring in FramebufferSurface to simplify code. Bug: 258196272 Test: started and stopped multiple YouTube videos on adt4 and verified no change in graphic buffer usage Test: atest HwcBufferCacheTest Test: atest OutputPrepareTest Change-Id: Ica7955ab4bc70e3c70207390e36dff73a2fc4949 --- services/surfaceflinger/Android.bp | 1 - services/surfaceflinger/ClientCache.cpp | 8 +- services/surfaceflinger/ClientCache.h | 14 +- .../CompositionRefreshArgs.h | 3 + .../LayerFECompositionState.h | 1 - .../include/compositionengine/Output.h | 2 + .../include/compositionengine/OutputLayer.h | 5 + .../compositionengine/impl/HwcBufferCache.h | 86 ++++++-- .../include/compositionengine/impl/Output.h | 1 + .../compositionengine/impl/OutputLayer.h | 3 + .../include/compositionengine/mock/Output.h | 1 + .../compositionengine/mock/OutputLayer.h | 2 + .../CompositionEngine/src/HwcBufferCache.cpp | 87 +++++--- .../src/LayerFECompositionState.cpp | 1 - .../CompositionEngine/src/Output.cpp | 10 + .../CompositionEngine/src/OutputLayer.cpp | 44 ++-- .../tests/HwcBufferCacheTest.cpp | 196 ++++++++++++++---- .../tests/OutputLayerTest.cpp | 3 +- .../CompositionEngine/tests/OutputTest.cpp | 37 +++- .../DisplayHardware/FramebufferSurface.cpp | 46 ++-- .../DisplayHardware/FramebufferSurface.h | 14 +- .../DisplayHardware/VirtualDisplaySurface.cpp | 22 +- .../DisplayHardware/VirtualDisplaySurface.h | 8 +- .../FrontEnd/RequestedLayerState.cpp | 1 - .../FrontEnd/RequestedLayerState.h | 1 - services/surfaceflinger/HwcSlotGenerator.cpp | 106 ---------- services/surfaceflinger/HwcSlotGenerator.h | 58 ------ services/surfaceflinger/Layer.cpp | 12 +- services/surfaceflinger/Layer.h | 9 +- services/surfaceflinger/SurfaceFlinger.cpp | 14 +- services/surfaceflinger/SurfaceFlinger.h | 5 + services/surfaceflinger/TransactionState.h | 3 +- .../fuzzer/surfaceflinger_layer_fuzzer.cpp | 2 +- .../surfaceflinger/tests/unittests/Android.bp | 1 - .../tests/unittests/CachingTest.cpp | 97 --------- .../unittests/TransactionFrameTracerTest.cpp | 2 +- .../unittests/TransactionSurfaceFrameTest.cpp | 24 +-- 37 files changed, 461 insertions(+), 469 deletions(-) delete mode 100644 services/surfaceflinger/HwcSlotGenerator.cpp delete mode 100644 services/surfaceflinger/HwcSlotGenerator.h delete mode 100644 services/surfaceflinger/tests/unittests/CachingTest.cpp diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index b1bd705f19..fe473597b1 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -165,7 +165,6 @@ filegroup { "FrameTracer/FrameTracer.cpp", "FrameTracker.cpp", "HdrLayerInfoReporter.cpp", - "HwcSlotGenerator.cpp", "WindowInfosListenerInvoker.cpp", "Layer.cpp", "LayerFE.cpp", diff --git a/services/surfaceflinger/ClientCache.cpp b/services/surfaceflinger/ClientCache.cpp index 2bd8f324e1..09e41ffede 100644 --- a/services/surfaceflinger/ClientCache.cpp +++ b/services/surfaceflinger/ClientCache.cpp @@ -118,7 +118,8 @@ ClientCache::add(const client_cache_t& cacheId, const sp& buffer) Usage::READABLE)); } -void ClientCache::erase(const client_cache_t& cacheId) { +sp ClientCache::erase(const client_cache_t& cacheId) { + sp buffer; auto& [processToken, id] = cacheId; std::vector> pendingErase; { @@ -126,9 +127,11 @@ void ClientCache::erase(const client_cache_t& cacheId) { ClientCacheBuffer* buf = nullptr; if (!getBuffer(cacheId, &buf)) { ALOGE("failed to erase buffer, could not retrieve buffer"); - return; + return nullptr; } + buffer = buf->buffer->getBuffer(); + for (auto& recipient : buf->recipients) { sp erasedRecipient = recipient.promote(); if (erasedRecipient) { @@ -142,6 +145,7 @@ void ClientCache::erase(const client_cache_t& cacheId) { for (auto& recipient : pendingErase) { recipient->bufferErased(cacheId); } + return buffer; } std::shared_ptr ClientCache::get(const client_cache_t& cacheId) { diff --git a/services/surfaceflinger/ClientCache.h b/services/surfaceflinger/ClientCache.h index cdeac2bbc1..b56b252d9f 100644 --- a/services/surfaceflinger/ClientCache.h +++ b/services/surfaceflinger/ClientCache.h @@ -33,6 +33,17 @@ namespace android { +// This class manages a cache of buffer handles between SurfaceFlinger clients +// and the SurfaceFlinger process which optimizes away some of the cost of +// sending buffer handles across processes. +// +// Buffers are explicitly cached and uncached by the SurfaceFlinger client. When +// a buffer is uncached, it is not only purged from this cache, but the buffer +// ID is also passed down to CompositionEngine to purge it from a similar cache +// used between SurfaceFlinger and Composer HAL. The buffer ID used to purge +// both the SurfaceFlinger side of this other cache, as well as Composer HAL's +// side of the cache. +// class ClientCache : public Singleton { public: ClientCache(); @@ -41,7 +52,8 @@ public: base::expected, AddError> add( const client_cache_t& cacheId, const sp& buffer); - void erase(const client_cache_t& cacheId); + + sp erase(const client_cache_t& cacheId); std::shared_ptr get(const client_cache_t& cacheId); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h index f861fc97e4..415a04113c 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h @@ -52,6 +52,9 @@ struct CompositionRefreshArgs { // All the layers that have queued updates. Layers layersWithQueuedFrames; + // All graphic buffers that will no longer be used and should be removed from caches. + std::vector bufferIdsToUncache; + // Controls how the color mode is chosen for an output OutputColorSetting outputColorSetting{OutputColorSetting::kEnhanced}; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h index 974f7c6134..ad98e93232 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h @@ -163,7 +163,6 @@ struct LayerFECompositionState { // The buffer and related state sp buffer; - int bufferSlot{BufferQueue::INVALID_BUFFER_SLOT}; sp acquireFence = Fence::NO_FENCE; Region surfaceDamage; uint64_t frameNumber = 0; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h index 874b330c1c..bd43c897a0 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -272,6 +273,7 @@ protected: virtual void setDisplayColorProfile(std::unique_ptr) = 0; virtual void setRenderSurface(std::unique_ptr) = 0; + virtual void uncacheBuffers(const std::vector&) = 0; virtual void rebuildLayerStacks(const CompositionRefreshArgs&, LayerFESet&) = 0; virtual void collectVisibleLayers(const CompositionRefreshArgs&, CoverageState&) = 0; virtual void ensureOutputLayerIfVisible(sp&, CoverageState&) = 0; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h index 6d0c395b2f..4dbf8d2fce 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -81,6 +82,10 @@ public: // TODO(lpique): Make this protected once it is only internally called. virtual CompositionState& editState() = 0; + // Clear the cache entries for a set of buffers that SurfaceFlinger no + // longer cares about. + virtual void uncacheBuffers(const std::vector& bufferIdsToUncache) = 0; + // Recalculates the state of the output layer from the output-independent // layer. If includeGeometry is false, the geometry state can be skipped. // internalDisplayRotationFlags must be set to the rotation flags for the diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h index fd22aa3a2a..6e9ea6ffd8 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h @@ -17,7 +17,8 @@ #pragma once #include -#include +#include +#include // TODO(b/129481165): remove the #pragma below and fix conversion issues #pragma clang diagnostic push @@ -37,35 +38,76 @@ class GraphicBuffer; namespace compositionengine::impl { -// With HIDLized hwcomposer HAL, the HAL can maintain a buffer cache for each -// HWC display and layer. When updating a display target or a layer buffer, -// we have the option to send the buffer handle over or to request the HAL to -// retrieve it from its cache. The latter is cheaper since it eliminates the -// overhead to transfer the handle over the trasport layer, and the overhead -// for the HAL to clone and retain the handle. +// The buffer cache returns both a slot and the buffer that should be sent to HWC. In cases +// where the buffer is already cached, the buffer is a nullptr and will not be sent to HWC as +// an optimization. +struct HwcSlotAndBuffer { + uint32_t slot; + sp buffer; +}; + +// +// Manages the slot assignments for a buffers stored in Composer HAL's cache. +// +// Cache slots are an optimization when communicating buffer handles to Composer +// HAL. When updating a layer's buffer, we can either send a new buffer handle +// along with it's slot assignment or request the HAL to reuse a buffer handle +// that we've already sent by using the slot assignment. The latter is cheaper +// since it eliminates the overhead to transfer the buffer handle over IPC and +// the overhead for the HAL to clone the handle. // -// To be able to find out whether a buffer is already in the HAL's cache, we -// use HWComposerBufferCache to mirror the cache in SF. class HwcBufferCache { +private: + static const constexpr size_t kMaxLayerBufferCount = BufferQueue::NUM_BUFFER_SLOTS; + public: + // public for testing + // Override buffers don't use the normal cache slots because we don't want them to evict client + // buffers from the cache. We add an extra slot at the end for the override buffers. + static const constexpr size_t kOverrideBufferSlot = kMaxLayerBufferCount; + HwcBufferCache(); - // Given a buffer, return the HWC cache slot and - // buffer to be sent to HWC. + + // + // Given a buffer, return the HWC cache slot and buffer to send to HWC. + // + // If the buffer is already in the cache, the buffer is null to optimize away sending HWC the + // buffer handle. // - // outBuffer is set to buffer when buffer is not in the HWC cache; - // otherwise, outBuffer is set to nullptr. - void getHwcBuffer(int slot, const sp& buffer, uint32_t* outSlot, - sp* outBuffer); + HwcSlotAndBuffer getHwcSlotAndBuffer(const sp& buffer); + // + // Given a buffer, return the HWC cache slot and buffer to send to HWC. + // + // A special slot number is used for override buffers. + // + // If the buffer is already in the cache, the buffer is null to optimize away sending HWC the + // buffer handle. + // + HwcSlotAndBuffer getHwcSlotAndBufferForOverride(const sp& buffer); - // Special caching slot for the layer caching feature. - static const constexpr size_t FLATTENER_CACHING_SLOT = BufferQueue::NUM_BUFFER_SLOTS; + // + // When a client process discards a buffer, it needs to be purged from the HWC cache. + // + // Returns the slot number of the buffer, or UINT32_MAX if it wasn't found in the cache. + // + uint32_t uncache(uint64_t graphicBufferId); private: - // an array where the index corresponds to a slot and the value corresponds to a (counter, - // buffer) pair. "counter" is a unique value that indicates the last time this slot was updated - // or used and allows us to keep track of the least-recently used buffer. - static const constexpr size_t kMaxLayerBufferCount = BufferQueue::NUM_BUFFER_SLOTS + 1; - wp mBuffers[kMaxLayerBufferCount]; + uint32_t cache(const sp& buffer); + uint32_t getLeastRecentlyUsedSlot(); + + struct Cache { + sp buffer; + uint32_t slot; + // Cache entries are evicted according to least-recently-used when more than + // kMaxLayerBufferCount unique buffers have been sent to a layer. + uint64_t lruCounter; + }; + + std::unordered_map mCacheByBufferId; + sp mLastOverrideBuffer; + std::stack mFreeSlots; + uint64_t mLeastRecentlyUsedCounter; }; } // namespace compositionengine::impl diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h index 9ca5da95bb..1393e29cc2 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h @@ -82,6 +82,7 @@ public: void prepare(const CompositionRefreshArgs&, LayerFESet&) override; void present(const CompositionRefreshArgs&) override; + void uncacheBuffers(const std::vector& bufferIdsToUncache) override; void rebuildLayerStacks(const CompositionRefreshArgs&, LayerFESet&) override; void collectVisibleLayers(const CompositionRefreshArgs&, compositionengine::Output::CoverageState&) override; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h index 6d4abf9f47..f383392e55 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -44,6 +45,8 @@ public: void setHwcLayer(std::shared_ptr) override; + void uncacheBuffers(const std::vector& bufferIdsToUncache) override; + void updateCompositionState(bool includeGeometry, bool forceClientComposition, ui::Transform::RotationFlags) override; void writeStateToHWC(bool includeGeometry, bool skipLayer, uint32_t z, bool zIsOverridden, diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h index 7592cac582..18e6879bdb 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h @@ -82,6 +82,7 @@ public: MOCK_METHOD2(prepare, void(const compositionengine::CompositionRefreshArgs&, LayerFESet&)); MOCK_METHOD1(present, void(const compositionengine::CompositionRefreshArgs&)); + MOCK_METHOD1(uncacheBuffers, void(const std::vector&)); MOCK_METHOD2(rebuildLayerStacks, void(const compositionengine::CompositionRefreshArgs&, LayerFESet&)); MOCK_METHOD2(collectVisibleLayers, diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h index c22f1bf260..5fef63a510 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h @@ -35,6 +35,8 @@ public: MOCK_METHOD1(setHwcLayer, void(std::shared_ptr)); + MOCK_METHOD1(uncacheBuffers, void(const std::vector&)); + MOCK_CONST_METHOD0(getOutput, const compositionengine::Output&()); MOCK_CONST_METHOD0(getLayerFE, compositionengine::LayerFE&()); diff --git a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp index f95382d921..d64fd5707c 100644 --- a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp +++ b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp @@ -16,43 +16,80 @@ #include -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - #include #include -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" - namespace android::compositionengine::impl { HwcBufferCache::HwcBufferCache() { - std::fill(std::begin(mBuffers), std::end(mBuffers), wp(nullptr)); + for (uint32_t i = 0; i < kMaxLayerBufferCount; i++) { + mFreeSlots.push(i); + } } -void HwcBufferCache::getHwcBuffer(int slot, const sp& buffer, uint32_t* outSlot, - sp* outBuffer) { - // default is 0 - if (slot == BufferQueue::INVALID_BUFFER_SLOT || slot < 0 || - slot >= static_cast(kMaxLayerBufferCount)) { - *outSlot = 0; - } else { - *outSlot = static_cast(slot); +HwcSlotAndBuffer HwcBufferCache::getHwcSlotAndBuffer(const sp& buffer) { + // TODO(b/261930578): This is for unit tests which don't mock GraphicBuffers but instead send + // in nullptrs. + if (buffer == nullptr) { + return {0, nullptr}; + } + if (auto i = mCacheByBufferId.find(buffer->getId()); i != mCacheByBufferId.end()) { + Cache& cache = i->second; + // mark this cache slot as more recently used so it won't get evicted anytime soon + cache.lruCounter = mLeastRecentlyUsedCounter++; + return {cache.slot, nullptr}; } + return {cache(buffer), buffer}; +} - auto& currentBuffer = mBuffers[*outSlot]; - wp weakCopy(buffer); - if (currentBuffer == weakCopy) { - // already cached in HWC, skip sending the buffer - *outBuffer = nullptr; - } else { - *outBuffer = buffer; +HwcSlotAndBuffer HwcBufferCache::getHwcSlotAndBufferForOverride(const sp& buffer) { + if (buffer == mLastOverrideBuffer) { + return {kOverrideBufferSlot, nullptr}; + } + mLastOverrideBuffer = buffer; + return {kOverrideBufferSlot, buffer}; +} + +uint32_t HwcBufferCache::uncache(uint64_t bufferId) { + if (auto i = mCacheByBufferId.find(bufferId); i != mCacheByBufferId.end()) { + uint32_t slot = i->second.slot; + mCacheByBufferId.erase(i); + mFreeSlots.push(slot); + return slot; + } + if (mLastOverrideBuffer && bufferId == mLastOverrideBuffer->getId()) { + mLastOverrideBuffer = nullptr; + return kOverrideBufferSlot; + } + return UINT32_MAX; +} + +uint32_t HwcBufferCache::cache(const sp& buffer) { + Cache cache; + cache.slot = getLeastRecentlyUsedSlot(); + cache.lruCounter = mLeastRecentlyUsedCounter++; + cache.buffer = buffer; + mCacheByBufferId.emplace(buffer->getId(), cache); + return cache.slot; +} - // update cache - currentBuffer = buffer; +uint32_t HwcBufferCache::getLeastRecentlyUsedSlot() { + if (mFreeSlots.empty()) { + assert(!mCacheByBufferId.empty()); + // evict the least recently used cache entry + auto cacheToErase = mCacheByBufferId.begin(); + for (auto i = cacheToErase; i != mCacheByBufferId.end(); ++i) { + if (i->second.lruCounter < cacheToErase->second.lruCounter) { + cacheToErase = i; + } + } + uint32_t slot = cacheToErase->second.slot; + mCacheByBufferId.erase(cacheToErase); + mFreeSlots.push(slot); } + uint32_t slot = mFreeSlots.top(); + mFreeSlots.pop(); + return slot; } } // namespace android::compositionengine::impl diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp index 6631a2772c..a405c4df88 100644 --- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp +++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp @@ -106,7 +106,6 @@ void LayerFECompositionState::dump(std::string& out) const { dumpVal(out, "composition type", toString(compositionType), compositionType); out.append("\n buffer: "); - dumpVal(out, "slot", bufferSlot); dumpVal(out, "buffer", buffer.get()); out.append("\n "); diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 3ee8017d39..16ef812d04 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -428,6 +428,7 @@ void Output::prepare(const compositionengine::CompositionRefreshArgs& refreshArg ALOGV(__FUNCTION__); rebuildLayerStacks(refreshArgs, geomSnapshots); + uncacheBuffers(refreshArgs.bufferIdsToUncache); } void Output::present(const compositionengine::CompositionRefreshArgs& refreshArgs) { @@ -455,6 +456,15 @@ void Output::present(const compositionengine::CompositionRefreshArgs& refreshArg renderCachedSets(refreshArgs); } +void Output::uncacheBuffers(std::vector const& bufferIdsToUncache) { + if (bufferIdsToUncache.empty()) { + return; + } + for (auto outputLayer : getOutputLayersOrderedByZ()) { + outputLayer->uncacheBuffers(bufferIdsToUncache); + } +} + void Output::rebuildLayerStacks(const compositionengine::CompositionRefreshArgs& refreshArgs, LayerFESet& layerFESet) { ATRACE_CALL(); diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp index a39c527655..a7c24b628d 100644 --- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp +++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp @@ -610,6 +610,19 @@ void OutputLayer::writeSidebandStateToHWC(HWC2::Layer* hwcLayer, } } +void OutputLayer::uncacheBuffers(std::vector const& bufferIdsToUncache) { + auto& state = editState(); + // Skip doing this if there is no HWC interface + if (!state.hwc) { + return; + } + + for (auto bufferId : bufferIdsToUncache) { + state.hwc->hwcBufferCache.uncache(bufferId); + // TODO(b/258196272): send uncache requests to Composer HAL + } +} + void OutputLayer::writeBufferStateToHWC(HWC2::Layer* hwcLayer, const LayerFECompositionState& outputIndependentState, bool skipLayer) { @@ -622,27 +635,24 @@ void OutputLayer::writeBufferStateToHWC(HWC2::Layer* hwcLayer, to_string(error).c_str(), static_cast(error)); } - sp buffer = outputIndependentState.buffer; - sp acquireFence = outputIndependentState.acquireFence; - int slot = outputIndependentState.bufferSlot; + HwcSlotAndBuffer hwcSlotAndBuffer; + sp hwcFence; + // Override buffers use a special cache slot so that they don't evict client buffers. if (getState().overrideInfo.buffer != nullptr && !skipLayer) { - buffer = getState().overrideInfo.buffer->getBuffer(); - acquireFence = getState().overrideInfo.acquireFence; - slot = HwcBufferCache::FLATTENER_CACHING_SLOT; + hwcSlotAndBuffer = editState().hwc->hwcBufferCache.getHwcSlotAndBufferForOverride( + getState().overrideInfo.buffer->getBuffer()); + hwcFence = getState().overrideInfo.acquireFence; + } else { + hwcSlotAndBuffer = + editState().hwc->hwcBufferCache.getHwcSlotAndBuffer(outputIndependentState.buffer); + hwcFence = outputIndependentState.acquireFence; } - ALOGV("Writing buffer %p", buffer.get()); - - uint32_t hwcSlot = 0; - sp hwcBuffer; - // We need access to the output-dependent state for the buffer cache there, - // though otherwise the buffer is not output-dependent. - editState().hwc->hwcBufferCache.getHwcBuffer(slot, buffer, &hwcSlot, &hwcBuffer); - - if (auto error = hwcLayer->setBuffer(hwcSlot, hwcBuffer, acquireFence); + if (auto error = hwcLayer->setBuffer(hwcSlotAndBuffer.slot, hwcSlotAndBuffer.buffer, hwcFence); error != hal::Error::NONE) { - ALOGE("[%s] Failed to set buffer %p: %s (%d)", getLayerFE().getDebugName(), buffer->handle, - to_string(error).c_str(), static_cast(error)); + ALOGE("[%s] Failed to set buffer %p: %s (%d)", getLayerFE().getDebugName(), + hwcSlotAndBuffer.buffer->handle, to_string(error).c_str(), + static_cast(error)); } } diff --git a/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp b/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp index 7197780c17..cf72e8f8de 100644 --- a/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp @@ -22,66 +22,172 @@ namespace android::compositionengine { namespace { -class TestableHwcBufferCache : public impl::HwcBufferCache { -public: - void getHwcBuffer(int slot, const sp& buffer, uint32_t* outSlot, - sp* outBuffer) { - HwcBufferCache::getHwcBuffer(slot, buffer, outSlot, outBuffer); - } -}; +using impl::HwcBufferCache; +using impl::HwcSlotAndBuffer; class HwcBufferCacheTest : public testing::Test { public: ~HwcBufferCacheTest() override = default; - void testSlot(const int inSlot, const uint32_t expectedSlot) { - uint32_t outSlot; - sp outBuffer; - - // The first time, the output is the same as the input - mCache.getHwcBuffer(inSlot, mBuffer1, &outSlot, &outBuffer); - EXPECT_EQ(expectedSlot, outSlot); - EXPECT_EQ(mBuffer1, outBuffer); - - // The second time with the same buffer, the outBuffer is nullptr. - mCache.getHwcBuffer(inSlot, mBuffer1, &outSlot, &outBuffer); - EXPECT_EQ(expectedSlot, outSlot); - EXPECT_EQ(nullptr, outBuffer.get()); - - // With a new buffer, the outBuffer is the input. - mCache.getHwcBuffer(inSlot, mBuffer2, &outSlot, &outBuffer); - EXPECT_EQ(expectedSlot, outSlot); - EXPECT_EQ(mBuffer2, outBuffer); - - // Again, the second request with the same buffer sets outBuffer to nullptr. - mCache.getHwcBuffer(inSlot, mBuffer2, &outSlot, &outBuffer); - EXPECT_EQ(expectedSlot, outSlot); - EXPECT_EQ(nullptr, outBuffer.get()); - - // Setting a slot to use nullptr lookslike works, but note that - // the output values make it look like no new buffer is being set.... - mCache.getHwcBuffer(inSlot, sp(), &outSlot, &outBuffer); - EXPECT_EQ(expectedSlot, outSlot); - EXPECT_EQ(nullptr, outBuffer.get()); - } - - impl::HwcBufferCache mCache; sp mBuffer1 = sp::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u); sp mBuffer2 = sp::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u); }; -TEST_F(HwcBufferCacheTest, cacheWorksForSlotZero) { - testSlot(0, 0); +TEST_F(HwcBufferCacheTest, getHwcSlotAndBuffer_returnsUniqueSlotNumberForEachBuffer) { + HwcBufferCache cache; + sp outBuffer; + + HwcSlotAndBuffer slotAndBufferFor1 = cache.getHwcSlotAndBuffer(mBuffer1); + EXPECT_NE(slotAndBufferFor1.slot, UINT32_MAX); + EXPECT_EQ(slotAndBufferFor1.buffer, mBuffer1); + + HwcSlotAndBuffer slotAndBufferFor2 = cache.getHwcSlotAndBuffer(mBuffer2); + EXPECT_NE(slotAndBufferFor2.slot, slotAndBufferFor1.slot); + EXPECT_NE(slotAndBufferFor2.slot, UINT32_MAX); + EXPECT_EQ(slotAndBufferFor2.buffer, mBuffer2); +} + +TEST_F(HwcBufferCacheTest, getHwcSlotAndBuffer_whenCached_returnsSameSlotNumberAndNullBuffer) { + HwcBufferCache cache; + sp outBuffer; + + HwcSlotAndBuffer originalSlotAndBuffer = cache.getHwcSlotAndBuffer(mBuffer1); + EXPECT_NE(originalSlotAndBuffer.slot, UINT32_MAX); + EXPECT_EQ(originalSlotAndBuffer.buffer, mBuffer1); + + HwcSlotAndBuffer finalSlotAndBuffer = cache.getHwcSlotAndBuffer(mBuffer1); + EXPECT_EQ(finalSlotAndBuffer.slot, originalSlotAndBuffer.slot); + EXPECT_EQ(finalSlotAndBuffer.buffer, nullptr); +} + +TEST_F(HwcBufferCacheTest, getHwcSlotAndBuffer_whenSlotsFull_evictsOldestCachedBuffer) { + HwcBufferCache cache; + sp outBuffer; + + sp graphicBuffers[100]; + HwcSlotAndBuffer slotsAndBuffers[100]; + int finalCachedBufferIndex = 0; + for (int i = 0; i < 100; ++i) { + graphicBuffers[i] = sp::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u); + slotsAndBuffers[i] = cache.getHwcSlotAndBuffer(graphicBuffers[i]); + // we fill up the cache when the slot number for the first buffer is reused + if (i > 0 && slotsAndBuffers[i].slot == slotsAndBuffers[0].slot) { + finalCachedBufferIndex = i; + break; + } + } + ASSERT_GT(finalCachedBufferIndex, 1); + // the final cached buffer has the same slot value as the oldest buffer + EXPECT_EQ(slotsAndBuffers[finalCachedBufferIndex].slot, slotsAndBuffers[0].slot); + // the oldest buffer is no longer in the cache because it was evicted + EXPECT_EQ(cache.uncache(graphicBuffers[0]->getId()), UINT32_MAX); +} + +TEST_F(HwcBufferCacheTest, uncache_whenCached_returnsSlotNumber) { + HwcBufferCache cache; + sp outBuffer; + + HwcSlotAndBuffer slotAndBufferFor1 = cache.getHwcSlotAndBuffer(mBuffer1); + ASSERT_NE(slotAndBufferFor1.slot, UINT32_MAX); + + HwcSlotAndBuffer slotAndBufferFor2 = cache.getHwcSlotAndBuffer(mBuffer2); + ASSERT_NE(slotAndBufferFor2.slot, UINT32_MAX); + + // the 1st buffer should be found in the cache with a slot number + EXPECT_EQ(cache.uncache(mBuffer1->getId()), slotAndBufferFor1.slot); + // since the 1st buffer has been previously uncached, we should no longer receive a slot number + EXPECT_EQ(cache.uncache(mBuffer1->getId()), UINT32_MAX); + // the 2nd buffer should be still found in the cache with a slot number + EXPECT_EQ(cache.uncache(mBuffer2->getId()), slotAndBufferFor2.slot); + // since the 2nd buffer has been previously uncached, we should no longer receive a slot number + EXPECT_EQ(cache.uncache(mBuffer2->getId()), UINT32_MAX); } -TEST_F(HwcBufferCacheTest, cacheWorksForMaxSlot) { - testSlot(BufferQueue::NUM_BUFFER_SLOTS - 1, BufferQueue::NUM_BUFFER_SLOTS - 1); +TEST_F(HwcBufferCacheTest, uncache_whenUncached_returnsInvalidSlotNumber) { + HwcBufferCache cache; + sp outBuffer; + + HwcSlotAndBuffer slotAndBufferFor1 = cache.getHwcSlotAndBuffer(mBuffer1); + ASSERT_NE(slotAndBufferFor1.slot, UINT32_MAX); + + EXPECT_EQ(cache.uncache(mBuffer2->getId()), UINT32_MAX); +} + +TEST_F(HwcBufferCacheTest, getHwcSlotAndBufferForOverride_whenCached_returnsSameSlotAndNullBuffer) { + HwcBufferCache cache; + sp outBuffer; + + HwcSlotAndBuffer originalSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1); + EXPECT_NE(originalSlotAndBuffer.slot, UINT32_MAX); + EXPECT_EQ(originalSlotAndBuffer.buffer, mBuffer1); + + HwcSlotAndBuffer finalSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1); + EXPECT_EQ(finalSlotAndBuffer.slot, originalSlotAndBuffer.slot); + EXPECT_EQ(finalSlotAndBuffer.buffer, nullptr); +} + +TEST_F(HwcBufferCacheTest, getHwcSlotAndBufferForOverride_whenSlotsFull_returnsIndependentSlot) { + HwcBufferCache cache; + sp outBuffer; + + sp graphicBuffers[100]; + HwcSlotAndBuffer slotsAndBuffers[100]; + int finalCachedBufferIndex = -1; + for (int i = 0; i < 100; ++i) { + graphicBuffers[i] = sp::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u); + slotsAndBuffers[i] = cache.getHwcSlotAndBuffer(graphicBuffers[i]); + // we fill up the cache when the slot number for the first buffer is reused + if (i > 0 && slotsAndBuffers[i].slot == slotsAndBuffers[0].slot) { + finalCachedBufferIndex = i; + break; + } + } + // expect to have cached at least a few buffers before evicting + ASSERT_GT(finalCachedBufferIndex, 1); + + sp overrideBuffer = + sp::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u); + HwcSlotAndBuffer overrideSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(overrideBuffer); + // expect us to have a slot number + EXPECT_NE(overrideSlotAndBuffer.slot, UINT32_MAX); + // expect this to be the first time we cached the buffer + EXPECT_NE(overrideSlotAndBuffer.buffer, nullptr); + + // expect the slot number to not equal any other slot number, even after the slots have been + // exhausted, indicating that the override buffer slot is independent from the slots for + // non-override buffers + for (int i = 0; i < finalCachedBufferIndex; ++i) { + EXPECT_NE(overrideSlotAndBuffer.slot, slotsAndBuffers[i].slot); + } + // the override buffer is independently uncached from the oldest cached buffer + // expect to find the override buffer still in the override buffer slot + EXPECT_EQ(cache.uncache(overrideBuffer->getId()), overrideSlotAndBuffer.slot); + // expect that the first buffer was not evicted from the cache when the override buffer was + // cached + EXPECT_EQ(cache.uncache(graphicBuffers[1]->getId()), slotsAndBuffers[1].slot); +} + +TEST_F(HwcBufferCacheTest, uncache_whenOverrideCached_returnsSlotNumber) { + HwcBufferCache cache; + sp outBuffer; + + HwcSlotAndBuffer hwcSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1); + ASSERT_NE(hwcSlotAndBuffer.slot, UINT32_MAX); + + EXPECT_EQ(cache.uncache(mBuffer1->getId()), hwcSlotAndBuffer.slot); + EXPECT_EQ(cache.uncache(mBuffer1->getId()), UINT32_MAX); } -TEST_F(HwcBufferCacheTest, cacheMapsNegativeSlotToZero) { - testSlot(-123, 0); +TEST_F(HwcBufferCacheTest, uncache_whenOverrideUncached_returnsInvalidSlotNumber) { + HwcBufferCache cache; + sp outBuffer; + + HwcSlotAndBuffer hwcSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1); + ASSERT_NE(hwcSlotAndBuffer.slot, UINT32_MAX); + + EXPECT_EQ(cache.uncache(mBuffer2->getId()), UINT32_MAX); } } // namespace diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp index 0f7dce896c..03cf2927af 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp @@ -774,7 +774,7 @@ struct OutputLayerWriteStateToHWCTest : public OutputLayerTest { static constexpr ui::Dataspace kOverrideDataspace = static_cast(72); static constexpr int kSupportedPerFrameMetadata = 101; static constexpr int kExpectedHwcSlot = 0; - static constexpr int kOverrideHwcSlot = impl::HwcBufferCache::FLATTENER_CACHING_SLOT; + static constexpr int kOverrideHwcSlot = impl::HwcBufferCache::kOverrideBufferSlot; static constexpr bool kLayerGenericMetadata1Mandatory = true; static constexpr bool kLayerGenericMetadata2Mandatory = true; static constexpr float kWhitePointNits = 200.f; @@ -823,7 +823,6 @@ struct OutputLayerWriteStateToHWCTest : public OutputLayerTest { mLayerFEState.hdrMetadata = kHdrMetadata; mLayerFEState.sidebandStream = NativeHandle::create(kSidebandStreamHandle, false); mLayerFEState.buffer = kBuffer; - mLayerFEState.bufferSlot = BufferQueue::INVALID_BUFFER_SLOT; mLayerFEState.acquireFence = kFence; mOutputState.displayBrightnessNits = kDisplayBrightnessNits; diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index 21099876f4..bfd863b88a 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -1192,14 +1192,49 @@ struct OutputPrepareTest : public testing::Test { compositionengine::LayerFESet&)); }; + OutputPrepareTest() { + EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(2u)); + EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0)) + .WillRepeatedly(Return(&mLayer1.outputLayer)); + EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(1)) + .WillRepeatedly(Return(&mLayer2.outputLayer)); + + mRefreshArgs.layers.push_back(mLayer1.layerFE); + mRefreshArgs.layers.push_back(mLayer2.layerFE); + } + + struct Layer { + StrictMock outputLayer; + sp> layerFE = sp>::make(); + }; + StrictMock mOutput; CompositionRefreshArgs mRefreshArgs; LayerFESet mGeomSnapshots; + Layer mLayer1; + Layer mLayer2; }; -TEST_F(OutputPrepareTest, justInvokesRebuildLayerStacks) { +TEST_F(OutputPrepareTest, callsUncacheBuffersOnEachOutputLayerAndThenRebuildsLayerStacks) { InSequence seq; + + mRefreshArgs.bufferIdsToUncache = {1, 3, 5}; + + EXPECT_CALL(mOutput, rebuildLayerStacks(Ref(mRefreshArgs), Ref(mGeomSnapshots))); + EXPECT_CALL(mLayer1.outputLayer, uncacheBuffers(Ref(mRefreshArgs.bufferIdsToUncache))); + EXPECT_CALL(mLayer2.outputLayer, uncacheBuffers(Ref(mRefreshArgs.bufferIdsToUncache))); + + mOutput.prepare(mRefreshArgs, mGeomSnapshots); +} + +TEST_F(OutputPrepareTest, skipsUncacheBuffersIfEmptyAndThenRebuildsLayerStacks) { + InSequence seq; + + mRefreshArgs.bufferIdsToUncache = {}; + EXPECT_CALL(mOutput, rebuildLayerStacks(Ref(mRefreshArgs), Ref(mGeomSnapshots))); + EXPECT_CALL(mLayer1.outputLayer, uncacheBuffers(_)).Times(0); + EXPECT_CALL(mLayer2.outputLayer, uncacheBuffers(_)).Times(0); mOutput.prepare(mRefreshArgs, mGeomSnapshots); } diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp index eb14933a61..ce602a8ad9 100644 --- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp +++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp @@ -72,6 +72,10 @@ FramebufferSurface::FramebufferSurface(HWComposer& hwc, PhysicalDisplayId displa mConsumer->setDefaultBufferSize(limitedSize.width, limitedSize.height); mConsumer->setMaxAcquiredBufferCount( SurfaceFlinger::maxFrameBufferAcquiredBuffers - 1); + + for (size_t i = 0; i < sizeof(mHwcBufferIds) / sizeof(mHwcBufferIds[0]); ++i) { + mHwcBufferIds[i] = UINT64_MAX; + } } void FramebufferSurface::resizeBuffers(const ui::Size& newSize) { @@ -88,31 +92,16 @@ status_t FramebufferSurface::prepareFrame(CompositionType /*compositionType*/) { } status_t FramebufferSurface::advanceFrame() { - uint32_t slot = 0; - sp buf; - sp acquireFence(Fence::NO_FENCE); - Dataspace dataspace = Dataspace::UNKNOWN; - status_t result = nextBuffer(slot, buf, acquireFence, dataspace); - mDataSpace = dataspace; - if (result != NO_ERROR) { - ALOGE("error latching next FramebufferSurface buffer: %s (%d)", - strerror(-result), result); - } - return result; -} - -status_t FramebufferSurface::nextBuffer(uint32_t& outSlot, - sp& outBuffer, sp& outFence, - Dataspace& outDataspace) { Mutex::Autolock lock(mMutex); BufferItem item; status_t err = acquireBufferLocked(&item, 0); if (err == BufferQueue::NO_BUFFER_AVAILABLE) { - mHwcBufferCache.getHwcBuffer(mCurrentBufferSlot, mCurrentBuffer, &outSlot, &outBuffer); + mDataspace = Dataspace::UNKNOWN; return NO_ERROR; } else if (err != NO_ERROR) { ALOGE("error acquiring buffer: %s (%d)", strerror(-err), err); + mDataspace = Dataspace::UNKNOWN; return err; } @@ -133,13 +122,18 @@ status_t FramebufferSurface::nextBuffer(uint32_t& outSlot, mCurrentBufferSlot = item.mSlot; mCurrentBuffer = mSlots[mCurrentBufferSlot].mGraphicBuffer; mCurrentFence = item.mFence; + mDataspace = static_cast(item.mDataSpace); - outFence = item.mFence; - mHwcBufferCache.getHwcBuffer(mCurrentBufferSlot, mCurrentBuffer, &outSlot, &outBuffer); - outDataspace = static_cast(item.mDataSpace); - status_t result = mHwc.setClientTarget(mDisplayId, outSlot, outFence, outBuffer, outDataspace); + // assume HWC has previously seen the buffer in this slot + sp hwcBuffer = sp(nullptr); + if (mCurrentBuffer->getId() != mHwcBufferIds[mCurrentBufferSlot]) { + mHwcBufferIds[mCurrentBufferSlot] = mCurrentBuffer->getId(); + hwcBuffer = mCurrentBuffer; // HWC hasn't previously seen this buffer in this slot + } + status_t result = mHwc.setClientTarget(mDisplayId, mCurrentBufferSlot, mCurrentFence, hwcBuffer, + mDataspace); if (result != NO_ERROR) { - ALOGE("error posting framebuffer: %d", result); + ALOGE("error posting framebuffer: %s (%d)", strerror(-result), result); return result; } @@ -190,7 +184,7 @@ ui::Size FramebufferSurface::limitSizeInternal(const ui::Size& size, const ui::S limitedSize.width = maxSize.height * aspectRatio; wasLimited = true; } - ALOGI_IF(wasLimited, "framebuffer size has been limited to [%dx%d] from [%dx%d]", + ALOGI_IF(wasLimited, "Framebuffer size has been limited to [%dx%d] from [%dx%d]", limitedSize.width, limitedSize.height, size.width, size.height); return limitedSize; } @@ -198,9 +192,9 @@ ui::Size FramebufferSurface::limitSizeInternal(const ui::Size& size, const ui::S void FramebufferSurface::dumpAsString(String8& result) const { Mutex::Autolock lock(mMutex); result.append(" FramebufferSurface\n"); - result.appendFormat(" mDataSpace=%s (%d)\n", - dataspaceDetails(static_cast(mDataSpace)).c_str(), - mDataSpace); + result.appendFormat(" mDataspace=%s (%d)\n", + dataspaceDetails(static_cast(mDataspace)).c_str(), + mDataspace); ConsumerBase::dumpLocked(result, " "); } diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h index d41a856e68..0b863daf47 100644 --- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h +++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h @@ -21,7 +21,7 @@ #include #include -#include +#include #include #include #include @@ -69,12 +69,6 @@ private: virtual void dumpLocked(String8& result, const char* prefix) const; - // nextBuffer waits for and then latches the next buffer from the - // BufferQueue and releases the previously latched buffer to the - // BufferQueue. The new buffer is returned in the 'buffer' argument. - status_t nextBuffer(uint32_t& outSlot, sp& outBuffer, - sp& outFence, ui::Dataspace& outDataspace); - const PhysicalDisplayId mDisplayId; // Framebuffer size has a dimension limitation in pixels based on the graphics capabilities of @@ -91,7 +85,7 @@ private: // compositing. Otherwise it will display the dataspace of the buffer // use for compositing which can change as wide-color content is // on/off. - ui::Dataspace mDataSpace; + ui::Dataspace mDataspace; // mCurrentBuffer is the current buffer or nullptr to indicate that there is // no current buffer. @@ -103,7 +97,9 @@ private: // Hardware composer, owned by SurfaceFlinger. HWComposer& mHwc; - compositionengine::impl::HwcBufferCache mHwcBufferCache; + // Buffers that HWC has seen before, indexed by slot number. + // NOTE: The BufferQueue slot number is the same as the HWC slot number. + uint64_t mHwcBufferIds[BufferQueue::NUM_BUFFER_SLOTS]; // Previous buffer to release after getting an updated retire fence bool mHasPendingRelease; diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp index 3803a78670..d62075ec65 100644 --- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp +++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp @@ -103,6 +103,10 @@ VirtualDisplaySurface::VirtualDisplaySurface(HWComposer& hwc, VirtualDisplayId d sink->setAsyncMode(true); IGraphicBufferProducer::QueueBufferOutput output; mSource[SOURCE_SCRATCH]->connect(nullptr, NATIVE_WINDOW_API_EGL, false, &output); + + for (size_t i = 0; i < sizeof(mHwcBufferIds) / sizeof(mHwcBufferIds[0]); ++i) { + mHwcBufferIds[i] = UINT64_MAX; + } } VirtualDisplaySurface::~VirtualDisplaySurface() { @@ -197,9 +201,9 @@ status_t VirtualDisplaySurface::advanceFrame() { return NO_MEMORY; } - sp fbBuffer = mFbProducerSlot >= 0 ? - mProducerBuffers[mFbProducerSlot] : sp(nullptr); - sp outBuffer = mProducerBuffers[mOutputProducerSlot]; + sp const& fbBuffer = + mFbProducerSlot >= 0 ? mProducerBuffers[mFbProducerSlot] : sp(nullptr); + sp const& outBuffer = mProducerBuffers[mOutputProducerSlot]; VDS_LOGV("%s: fb=%d(%p) out=%d(%p)", __func__, mFbProducerSlot, fbBuffer.get(), mOutputProducerSlot, outBuffer.get()); @@ -211,12 +215,14 @@ status_t VirtualDisplaySurface::advanceFrame() { status_t result = NO_ERROR; if (fbBuffer != nullptr) { - uint32_t hwcSlot = 0; - sp hwcBuffer; - mHwcBufferCache.getHwcBuffer(mFbProducerSlot, fbBuffer, &hwcSlot, &hwcBuffer); - + // assume that HWC has previously seen the buffer in this slot + sp hwcBuffer = sp(nullptr); + if (fbBuffer->getId() != mHwcBufferIds[mFbProducerSlot]) { + mHwcBufferIds[mFbProducerSlot] = fbBuffer->getId(); + hwcBuffer = fbBuffer; // HWC hasn't previously seen this buffer in this slot + } // TODO: Correctly propagate the dataspace from GL composition - result = mHwc.setClientTarget(*halDisplayId, hwcSlot, mFbFence, hwcBuffer, + result = mHwc.setClientTarget(*halDisplayId, mFbProducerSlot, mFbFence, hwcBuffer, ui::Dataspace::UNKNOWN); } diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h index e21095aa88..be06e2bb10 100644 --- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h +++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h @@ -20,7 +20,7 @@ #include #include -#include +#include #include #include #include @@ -164,6 +164,10 @@ private: sp mSource[2]; // indexed by SOURCE_* uint32_t mDefaultOutputFormat; + // Buffers that HWC has seen before, indexed by HWC slot number. + // NOTE: The BufferQueue slot number is the same as the HWC slot number. + uint64_t mHwcBufferIds[BufferQueue::NUM_BUFFER_SLOTS]; + // // Inter-frame state // @@ -260,8 +264,6 @@ private: bool mMustRecompose = false; - compositionengine::impl::HwcBufferCache mHwcBufferCache; - bool mForceHwcCopy; }; diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index 45058d900f..9a3d750670 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -190,7 +190,6 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta if (clientState.what & layer_state_t::eBufferChanged) { externalTexture = resolvedComposerState.externalTexture; - hwcBufferSlot = resolvedComposerState.hwcBufferSlot; } if (clientState.what & layer_state_t::ePositionChanged) { diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h index 0ddf5e2895..7e5a79fe00 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h @@ -86,7 +86,6 @@ struct RequestedLayerState : layer_state_t { ui::Transform requestedTransform; std::shared_ptr acquireFenceTime; std::shared_ptr externalTexture; - int hwcBufferSlot = 0; // book keeping states bool handleAlive = true; diff --git a/services/surfaceflinger/HwcSlotGenerator.cpp b/services/surfaceflinger/HwcSlotGenerator.cpp deleted file mode 100644 index 939c35b8b3..0000000000 --- a/services/surfaceflinger/HwcSlotGenerator.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#undef LOG_TAG -#define LOG_TAG "HwcSlotGenerator" -#define ATRACE_TAG ATRACE_TAG_GRAPHICS - -#include - -#include "HwcSlotGenerator.h" - -namespace android { - -HwcSlotGenerator::HwcSlotGenerator() { - for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) { - mFreeHwcCacheSlots.push(i); - } -} - -void HwcSlotGenerator::bufferErased(const client_cache_t& clientCacheId) { - std::lock_guard lock(mMutex); - if (!clientCacheId.isValid()) { - ALOGE("invalid process, failed to erase buffer"); - return; - } - eraseBufferLocked(clientCacheId); -} - -int HwcSlotGenerator::getHwcCacheSlot(const client_cache_t& clientCacheId) { - std::lock_guard lock(mMutex); - auto itr = mCachedBuffers.find(clientCacheId); - if (itr == mCachedBuffers.end()) { - return addCachedBuffer(clientCacheId); - } - auto& [hwcCacheSlot, counter] = itr->second; - counter = mCounter++; - return hwcCacheSlot; -} - -int HwcSlotGenerator::addCachedBuffer(const client_cache_t& clientCacheId) REQUIRES(mMutex) { - if (!clientCacheId.isValid()) { - ALOGE("invalid process, returning invalid slot"); - return BufferQueue::INVALID_BUFFER_SLOT; - } - - ClientCache::getInstance().registerErasedRecipient(clientCacheId, - wp::fromExisting(this)); - - int hwcCacheSlot = getFreeHwcCacheSlot(); - mCachedBuffers[clientCacheId] = {hwcCacheSlot, mCounter++}; - return hwcCacheSlot; -} - -int HwcSlotGenerator::getFreeHwcCacheSlot() REQUIRES(mMutex) { - if (mFreeHwcCacheSlots.empty()) { - evictLeastRecentlyUsed(); - } - - int hwcCacheSlot = mFreeHwcCacheSlots.top(); - mFreeHwcCacheSlots.pop(); - return hwcCacheSlot; -} - -void HwcSlotGenerator::evictLeastRecentlyUsed() REQUIRES(mMutex) { - uint64_t minCounter = UINT_MAX; - client_cache_t minClientCacheId = {}; - for (const auto& [clientCacheId, slotCounter] : mCachedBuffers) { - const auto& [hwcCacheSlot, counter] = slotCounter; - if (counter < minCounter) { - minCounter = counter; - minClientCacheId = clientCacheId; - } - } - eraseBufferLocked(minClientCacheId); - - ClientCache::getInstance().unregisterErasedRecipient(minClientCacheId, - wp::fromExisting(this)); -} - -void HwcSlotGenerator::eraseBufferLocked(const client_cache_t& clientCacheId) REQUIRES(mMutex) { - auto itr = mCachedBuffers.find(clientCacheId); - if (itr == mCachedBuffers.end()) { - return; - } - auto& [hwcCacheSlot, counter] = itr->second; - - // TODO send to hwc cache and resources - - mFreeHwcCacheSlots.push(hwcCacheSlot); - mCachedBuffers.erase(clientCacheId); -} - -} // namespace android diff --git a/services/surfaceflinger/HwcSlotGenerator.h b/services/surfaceflinger/HwcSlotGenerator.h deleted file mode 100644 index 5a1b6d7d9d..0000000000 --- a/services/surfaceflinger/HwcSlotGenerator.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include - -#include "ClientCache.h" - -namespace android { - -class HwcSlotGenerator : public ClientCache::ErasedRecipient { -public: - HwcSlotGenerator(); - void bufferErased(const client_cache_t& clientCacheId); - int getHwcCacheSlot(const client_cache_t& clientCacheId); - -private: - friend class SlotGenerationTest; - int addCachedBuffer(const client_cache_t& clientCacheId) REQUIRES(mMutex); - int getFreeHwcCacheSlot() REQUIRES(mMutex); - void evictLeastRecentlyUsed() REQUIRES(mMutex); - void eraseBufferLocked(const client_cache_t& clientCacheId) REQUIRES(mMutex); - - struct CachedBufferHash { - std::size_t operator()(const client_cache_t& clientCacheId) const { - return std::hash{}(clientCacheId.id); - } - }; - - std::mutex mMutex; - - std::unordered_map, - CachedBufferHash> - mCachedBuffers GUARDED_BY(mMutex); - std::stack mFreeHwcCacheSlots GUARDED_BY(mMutex); - - // The cache increments this counter value when a slot is updated or used. - // Used to track the least recently-used buffer - uint64_t mCounter = 0; -}; -} // namespace android diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 0017af0476..b0ee850541 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -144,7 +144,6 @@ Layer::Layer(const LayerCreationArgs& args) mLayerCreationFlags(args.flags), mBorderEnabled(false), mTextureName(args.textureName), - mHwcSlotGenerator(sp::make()), mLayerFE(args.flinger->getFactory().createLayerFE(mName)) { ALOGV("Creating Layer %s", getDebugName()); @@ -573,9 +572,6 @@ void Layer::preparePerFrameBufferCompositionState() { } snapshot->buffer = getBuffer(); - snapshot->bufferSlot = (mBufferInfo.mBufferSlot == BufferQueue::INVALID_BUFFER_SLOT) - ? 0 - : mBufferInfo.mBufferSlot; snapshot->acquireFence = mBufferInfo.mFence; snapshot->frameNumber = mBufferInfo.mFrameNumber; snapshot->sidebandStreamHasFrame = false; @@ -2840,7 +2836,7 @@ bool Layer::setPosition(float x, float y) { bool Layer::setBuffer(std::shared_ptr& buffer, const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime, bool isAutoTimestamp, std::optional dequeueTime, - const FrameTimelineInfo& info, int hwcBufferSlot) { + const FrameTimelineInfo& info) { ATRACE_FORMAT("setBuffer %s - hasBuffer=%s", getDebugName(), (buffer ? "true" : "false")); if (!buffer) { return false; @@ -2886,7 +2882,6 @@ bool Layer::setBuffer(std::shared_ptr& buffer, mDrawingState.releaseBufferListener = bufferData.releaseBufferListener; mDrawingState.buffer = std::move(buffer); mDrawingState.clientCacheId = bufferData.cachedBuffer; - mDrawingState.hwcBufferSlot = hwcBufferSlot; mDrawingState.acquireFence = bufferData.flags.test(BufferData::BufferDataChange::fenceChanged) ? bufferData.acquireFence : Fence::NO_FENCE; @@ -3185,7 +3180,6 @@ void Layer::gatherBufferInfo() { mBufferInfo.mHdrMetadata = mDrawingState.hdrMetadata; mBufferInfo.mApi = mDrawingState.api; mBufferInfo.mTransformToDisplayInverse = mDrawingState.transformToDisplayInverse; - mBufferInfo.mBufferSlot = mDrawingState.hwcBufferSlot; } Rect Layer::computeBufferCrop(const State& s) { @@ -3968,10 +3962,6 @@ void Layer::updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMet } } -int Layer::getHwcCacheSlot(const client_cache_t& clientCacheId) { - return mHwcSlotGenerator->getHwcCacheSlot(clientCacheId); -} - LayerSnapshotGuard::LayerSnapshotGuard(Layer* layer) : mLayer(layer) { if (mLayer) { mLayer->mLayerFE->mSnapshot = std::move(mLayer->mSnapshot); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index f743896c03..866339b3ce 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -51,7 +51,6 @@ #include "Client.h" #include "DisplayHardware/HWComposer.h" #include "FrameTracker.h" -#include "HwcSlotGenerator.h" #include "LayerFE.h" #include "LayerVector.h" #include "Scheduler/LayerInfo.h" @@ -146,7 +145,6 @@ public: bool transformToDisplayInverse; Region transparentRegionHint; std::shared_ptr buffer; - int hwcBufferSlot; client_cache_t clientCacheId; sp acquireFence; std::shared_ptr acquireFenceTime; @@ -298,8 +296,7 @@ public: bool setBuffer(std::shared_ptr& /* buffer */, const BufferData& /* bufferData */, nsecs_t /* postTime */, nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/, - std::optional /* dequeueTime */, const FrameTimelineInfo& /*info*/, - int /* hwcBufferSlot */); + std::optional /* dequeueTime */, const FrameTimelineInfo& /*info*/); bool setDataspace(ui::Dataspace /*dataspace*/); bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/); bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/); @@ -500,7 +497,6 @@ public: std::shared_ptr mBuffer; uint64_t mFrameNumber; - int mBufferSlot{BufferQueue::INVALID_BUFFER_SLOT}; bool mFrameLatencyNeeded{false}; }; @@ -813,7 +809,6 @@ public: void updateMetadataSnapshot(const LayerMetadata& parentMetadata); void updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata, std::unordered_set& visited); - int getHwcCacheSlot(const client_cache_t& clientCacheId); protected: // For unit tests @@ -1124,8 +1119,6 @@ private: // not specify a destination frame. ui::Transform mRequestedTransform; - sp mHwcSlotGenerator; - sp mLayerFE; std::unique_ptr mSnapshot = std::make_unique(); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index bd3c0f12cc..a7b59a16ed 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -112,6 +112,7 @@ #include #include "BackgroundExecutor.h" #include "Client.h" +#include "ClientCache.h" #include "Colorizer.h" #include "Display/DisplayMap.h" #include "DisplayDevice.h" @@ -2255,6 +2256,8 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) }); } + refreshArgs.bufferIdsToUncache = std::move(mBufferIdsToUncache); + refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size()); for (auto layer : mLayersWithQueuedFrames) { if (auto layerFE = layer->getCompositionEngineLayerFE()) @@ -3999,10 +4002,6 @@ status_t SurfaceFlinger::setTransactionState( getExternalTextureFromBufferData(*resolvedState.state.bufferData, layerName.c_str(), transactionId); mBufferCountTracker.increment(resolvedState.state.surface->localBinder()); - if (layer) { - resolvedState.hwcBufferSlot = - layer->getHwcCacheSlot(resolvedState.state.bufferData->cachedBuffer); - } } } @@ -4078,7 +4077,10 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin } if (uncacheBuffer.isValid()) { - ClientCache::getInstance().erase(uncacheBuffer); + sp buffer = ClientCache::getInstance().erase(uncacheBuffer); + if (buffer != nullptr) { + mBufferIdsToUncache.push_back(buffer->getId()); + } } // If a synchronous transaction is explicitly requested without any changes, force a transaction @@ -4469,7 +4471,7 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime if (what & layer_state_t::eBufferChanged) { if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime, desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp, - frameTimelineInfo, composerState.hwcBufferSlot)) { + frameTimelineInfo)) { flags |= eTraversalNeeded; } } else if (frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index b9903a7768..e7ecf6c757 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -96,6 +96,7 @@ #include #include #include +#include #include #include "Client.h" @@ -1091,6 +1092,10 @@ private: std::atomic mUniqueTransactionId = 1; SortedVector> mLayersPendingRemoval; + // Buffers that have been discarded by clients and need to be evicted from per-layer caches so + // the graphics memory can be immediately freed. + std::vector mBufferIdsToUncache; + // global color transform states Daltonizer mDaltonizer; float mGlobalSaturationFactor = 1.0f; diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h index 7bde2c1e23..380301fc73 100644 --- a/services/surfaceflinger/TransactionState.h +++ b/services/surfaceflinger/TransactionState.h @@ -27,13 +27,12 @@ namespace android { -// Extends the client side composer state by resolving buffer cache ids. +// Extends the client side composer state by resolving buffer. class ResolvedComposerState : public ComposerState { public: ResolvedComposerState() = default; ResolvedComposerState(ComposerState&& source) { state = std::move(source.state); } std::shared_ptr externalTexture; - int hwcBufferSlot = 0; }; struct TransactionState { diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp index c5b3fa67ae..acfc1d4dc8 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp @@ -160,7 +160,7 @@ void LayerFuzzer::invokeBufferStateLayer() { layer->setBuffer(texture, {} /*bufferData*/, mFdp.ConsumeIntegral() /*postTime*/, mFdp.ConsumeIntegral() /*desiredTime*/, mFdp.ConsumeBool() /*isAutoTimestamp*/, - {mFdp.ConsumeIntegral()} /*dequeue*/, {} /*info*/, 0 /* hwcslot */); + {mFdp.ConsumeIntegral()} /*dequeue*/, {} /*info*/); LayerRenderArea layerArea(*(flinger.flinger()), layer, getFuzzedRect(), {mFdp.ConsumeIntegral(), diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index 8b0cd78732..96db43e59b 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -70,7 +70,6 @@ cc_test { ":libsurfaceflinger_sources", "libsurfaceflinger_unittest_main.cpp", "AidlPowerHalWrapperTest.cpp", - "CachingTest.cpp", "CompositionTest.cpp", "DispSyncSourceTest.cpp", "DisplayIdGeneratorTest.cpp", diff --git a/services/surfaceflinger/tests/unittests/CachingTest.cpp b/services/surfaceflinger/tests/unittests/CachingTest.cpp deleted file mode 100644 index c1cbbfb4ef..0000000000 --- a/services/surfaceflinger/tests/unittests/CachingTest.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2019 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. - */ - -#undef LOG_TAG -#define LOG_TAG "CachingTest" - -#include -#include -#include - -#include "HwcSlotGenerator.h" - -namespace android { - -class SlotGenerationTest : public testing::Test { -protected: - sp mHwcSlotGenerator = sp::make(); - sp mBuffer1 = - sp::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u); - sp mBuffer2 = - sp::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u); - sp mBuffer3 = - sp::make(10u, 10u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u); -}; - -TEST_F(SlotGenerationTest, getHwcCacheSlot_Invalid) { - sp binder = sp::make(); - // test getting invalid client_cache_id - client_cache_t id; - int slot = mHwcSlotGenerator->getHwcCacheSlot(id); - EXPECT_EQ(BufferQueue::INVALID_BUFFER_SLOT, slot); -} - -TEST_F(SlotGenerationTest, getHwcCacheSlot_Basic) { - sp binder = sp::make(); - client_cache_t id; - id.token = binder; - id.id = 0; - int slot = mHwcSlotGenerator->getHwcCacheSlot(id); - EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - 1, slot); - - client_cache_t idB; - idB.token = binder; - idB.id = 1; - slot = mHwcSlotGenerator->getHwcCacheSlot(idB); - EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - 2, slot); - - slot = mHwcSlotGenerator->getHwcCacheSlot(idB); - EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - 2, slot); - - slot = mHwcSlotGenerator->getHwcCacheSlot(id); - EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - 1, slot); -} - -TEST_F(SlotGenerationTest, getHwcCacheSlot_Reuse) { - sp binder = sp::make(); - std::vector ids; - uint32_t cacheId = 0; - // fill up cache - for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) { - client_cache_t id; - id.token = binder; - id.id = cacheId; - ids.push_back(id); - - int slot = mHwcSlotGenerator->getHwcCacheSlot(id); - EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - (i + 1), slot); - cacheId++; - } - for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) { - int slot = mHwcSlotGenerator->getHwcCacheSlot(ids[static_cast(i)]); - EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - (i + 1), slot); - } - - for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) { - client_cache_t id; - id.token = binder; - id.id = cacheId; - int slot = mHwcSlotGenerator->getHwcCacheSlot(id); - EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - (i + 1), slot); - cacheId++; - } -} -} // namespace android diff --git a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp index 09d002f6e8..1173d1c876 100644 --- a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp @@ -126,7 +126,7 @@ public: HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); layer->setBuffer(externalTexture, bufferData, postTime, /*desiredPresentTime*/ 30, false, - dequeueTime, FrameTimelineInfo{}, 0); + dequeueTime, FrameTimelineInfo{}); commitTransaction(layer.get()); nsecs_t latchTime = 25; diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp index 7dfbcc00d4..ae03db43a7 100644 --- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp @@ -131,7 +131,7 @@ public: FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo, 0); + layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo); acquireFence->signalForTest(12); commitTransaction(layer.get()); @@ -166,7 +166,7 @@ public: FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; - layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo, 0); + layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX; @@ -183,7 +183,7 @@ public: 2ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo, 0); + layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo); nsecs_t end = systemTime(); acquireFence2->signalForTest(12); @@ -229,7 +229,7 @@ public: 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo, 0); + layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo); acquireFence->signalForTest(12); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); @@ -264,7 +264,7 @@ public: FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo, 0); + layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); @@ -307,7 +307,7 @@ public: FrameTimelineInfo ftInfo3; ftInfo3.vsyncId = 3; ftInfo3.inputEventId = 0; - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo3, 0); + layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo3); EXPECT_EQ(2u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); const auto bufferSurfaceFrameTX = layer->mDrawingState.bufferSurfaceFrameTX; @@ -352,7 +352,7 @@ public: FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; - layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo, 0); + layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX; @@ -367,7 +367,7 @@ public: 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo, 0); + layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo); acquireFence2->signalForTest(12); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); @@ -404,7 +404,7 @@ public: FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; - layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo, 0); + layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); const auto droppedSurfaceFrame1 = layer->mDrawingState.bufferSurfaceFrameTX; @@ -424,7 +424,7 @@ public: FrameTimelineInfo ftInfoInv; ftInfoInv.vsyncId = FrameTimelineInfo::INVALID_VSYNC_ID; ftInfoInv.inputEventId = 0; - layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfoInv, 0); + layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfoInv); auto dropEndTime1 = systemTime(); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); @@ -445,7 +445,7 @@ public: FrameTimelineInfo ftInfo2; ftInfo2.vsyncId = 2; ftInfo2.inputEventId = 0; - layer->setBuffer(externalTexture3, bufferData, 10, 20, false, std::nullopt, ftInfo2, 0); + layer->setBuffer(externalTexture3, bufferData, 10, 20, false, std::nullopt, ftInfo2); auto dropEndTime2 = systemTime(); acquireFence3->signalForTest(12); @@ -494,7 +494,7 @@ public: FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo, 0); + layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo); FrameTimelineInfo ftInfo2; ftInfo2.vsyncId = 2; ftInfo2.inputEventId = 0; -- GitLab From 31afdda1ebe66096921088cfffcc3e1ebbe10c94 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Thu, 8 Dec 2022 19:15:15 -0500 Subject: [PATCH 0594/1310] SF: Activate display on boot and fold/unfold Revert to the behavior before I0d20ce7f7ab3f9e90ef1c72263b8166865486d62 by not activating a display when it transitions from PowerMode::OFF, as the resulting modeset seems to trigger long waits on some HWCs. Bug: 261768275 Test: Leader display ID is only logged during boot. Test: For foldables, leader display ID is also logged on fold/unfold. Change-Id: I9d2bdd688129880278da336bbc1633857ae29334 --- services/surfaceflinger/SurfaceFlinger.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 36ff3ec5c2..3e970fd95d 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4731,9 +4731,9 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: .value_or(false); const auto activeDisplay = getDisplayDeviceLocked(mActiveDisplayId); - const bool isActiveDisplayPoweredOn = activeDisplay && activeDisplay->isPoweredOn(); - ALOGW_IF(display != activeDisplay && isInternalDisplay && isActiveDisplayPoweredOn, + ALOGW_IF(display != activeDisplay && isInternalDisplay && activeDisplay && + activeDisplay->isPoweredOn(), "Trying to change power mode on inactive display without powering off active display"); display->setPowerMode(mode); @@ -4741,9 +4741,24 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: const auto refreshRate = display->refreshRateSelector().getActiveMode().modePtr->getFps(); if (!currentModeOpt || *currentModeOpt == hal::PowerMode::OFF) { // Turn on the display - if (isInternalDisplay && !isActiveDisplayPoweredOn) { + + // Activate the display (which involves a modeset to the active mode): + // 1) When the first (a.k.a. primary) display is powered on during boot. + // 2) When the inner or outer display of a foldable is powered on. This condition relies + // on the above DisplayDevice::setPowerMode. If `display` and `activeDisplay` are the + // same display, then the `activeDisplay->isPoweredOn()` below is true, such that the + // display is not activated every time it is powered on. + // + // TODO(b/255635821): Remove the concept of active display. + const bool activeDisplayChanged = + isInternalDisplay && (!activeDisplay || !activeDisplay->isPoweredOn()); + + static bool sPrimaryDisplay = true; + if (sPrimaryDisplay || activeDisplayChanged) { onActiveDisplayChangedLocked(activeDisplay, display); + sPrimaryDisplay = false; } + // Keep uclamp in a separate syscall and set it before changing to RT due to b/190237315. // We can merge the syscall later. if (SurfaceFlinger::setSchedAttr(true) != NO_ERROR) { -- GitLab From 4e04c3a31253304dbb92a25a8d0c9e3ad5b1fe31 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Fri, 9 Dec 2022 20:48:00 +0000 Subject: [PATCH 0595/1310] SurfaceComposerClient: Handle transaction apply sync timeouts We switched to using transaction complete callbacks to handle transaction#apply(/*sync*/=true). The callback object was destroyed after waiting for the callback. In the event of a timeout, the callback would access an invalid object. Fix this by using ref counted object to ensure the context remains valid. Fixes: 261679196 Test: atest SurfaceFlinger_test Change-Id: I4f840214672dd4051cb57b9551bf20802cc90890 --- libs/gui/SurfaceComposerClient.cpp | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 1e43700d06..6fe1fb2b35 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -971,14 +971,16 @@ void SurfaceComposerClient::Transaction::cacheBuffers() { class SyncCallback { public: - static void function(void* callbackContext, nsecs_t /* latchTime */, - const sp& /* presentFence */, - const std::vector& /* stats */) { - if (!callbackContext) { - ALOGE("failed to get callback context for SyncCallback"); - } - SyncCallback* helper = static_cast(callbackContext); - LOG_ALWAYS_FATAL_IF(sem_post(&helper->mSemaphore), "sem_post failed"); + static auto getCallback(std::shared_ptr& callbackContext) { + return [callbackContext](void* /* unused context */, nsecs_t /* latchTime */, + const sp& /* presentFence */, + const std::vector& /* stats */) { + if (!callbackContext) { + ALOGE("failed to get callback context for SyncCallback"); + return; + } + LOG_ALWAYS_FATAL_IF(sem_post(&callbackContext->mSemaphore), "sem_post failed"); + }; } ~SyncCallback() { if (mInitialized) { @@ -1013,10 +1015,11 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay return mStatus; } - SyncCallback syncCallback; + std::shared_ptr syncCallback = std::make_shared(); if (synchronous) { - syncCallback.init(); - addTransactionCommittedCallback(syncCallback.function, syncCallback.getContext()); + syncCallback->init(); + addTransactionCommittedCallback(SyncCallback::getCallback(syncCallback), + /*callbackContext=*/nullptr); } bool hasListenerCallbacks = !mListenerCallbacks.empty(); @@ -1092,7 +1095,7 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay clear(); if (synchronous) { - syncCallback.wait(); + syncCallback->wait(); } mStatus = NO_ERROR; -- GitLab From 4609a4a6e848001206f23cdf5963c9a335bbab16 Mon Sep 17 00:00:00 2001 From: Carlos Martinez Romero Date: Wed, 30 Nov 2022 00:38:11 +0000 Subject: [PATCH 0596/1310] Call bootFinished using JNI. This way we dont need to use FIRST_CALL_TRANSACTION which forces us to set bootFinished as the first method in the class. These files have also been formatted with clang. Test: Manually booted the device after changes. Bug: 221898546 Change-Id: Ifd04938a22da9b811c6c032c229c9b58e8c1b4c7 --- libs/gui/SurfaceComposerClient.cpp | 6 ++++++ libs/gui/aidl/android/gui/ISurfaceComposer.aidl | 2 -- libs/gui/include/gui/SurfaceComposerClient.h | 3 +++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 325c294762..9ab98e833e 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2144,6 +2144,12 @@ void SurfaceComposerClient::dispose() { mStatus = NO_INIT; } +status_t SurfaceComposerClient::bootFinished() { + sp sf(ComposerServiceAIDL::getComposerService()); + binder::Status status = sf->bootFinished(); + return statusTFromBinderStatus(status); +} + sp SurfaceComposerClient::createSurface(const String8& name, uint32_t w, uint32_t h, PixelFormat format, int32_t flags, const sp& parentHandle, diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 40410fb59e..f3e449b666 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -62,8 +62,6 @@ interface ISurfaceComposer { * Signal that we're done booting. * Requires ACCESS_SURFACE_FLINGER permission */ - // Note this must be the 1st method, so IBinder::FIRST_CALL_TRANSACTION - // is assigned, as it is called from Java by ActivityManagerService. void bootFinished(); /** diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 2038f1477a..a6d4010adc 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -145,6 +145,9 @@ public: status_t linkToComposerDeath(const sp& recipient, void* cookie = nullptr, uint32_t flags = 0); + // Notify the SurfaceComposerClient that the boot procedure has completed + static status_t bootFinished(); + // Get transactional state of given display. static status_t getDisplayState(const sp& display, ui::DisplayState*); -- GitLab From 705e5abc47fc6fe002de9dd54376b297f93329d5 Mon Sep 17 00:00:00 2001 From: Vladimir Komsiyski Date: Thu, 8 Dec 2022 17:29:14 +0100 Subject: [PATCH 0597/1310] Fix memory leak when sensor registration fails. Switching sp to std::shared_ptr. Fix: 261949012 Test: atest cts/tests/sensor Test: atest VirtualSensorTest Change-Id: I1276d35eb91bf54438271f603d36124af7fd4a4c --- .../sensorservice/SensorDirectConnection.cpp | 4 +- .../sensorservice/SensorEventConnection.cpp | 16 +-- services/sensorservice/SensorList.cpp | 14 +- services/sensorservice/SensorList.h | 18 +-- services/sensorservice/SensorService.cpp | 121 ++++++++++-------- services/sensorservice/SensorService.h | 11 +- 6 files changed, 94 insertions(+), 90 deletions(-) diff --git a/services/sensorservice/SensorDirectConnection.cpp b/services/sensorservice/SensorDirectConnection.cpp index 291c770692..4ac9651a7b 100644 --- a/services/sensorservice/SensorDirectConnection.cpp +++ b/services/sensorservice/SensorDirectConnection.cpp @@ -151,7 +151,7 @@ int32_t SensorService::SensorDirectConnection::configureChannel(int handle, int return PERMISSION_DENIED; } - sp si = mService->getSensorInterfaceFromHandle(handle); + std::shared_ptr si = mService->getSensorInterfaceFromHandle(handle); if (si == nullptr) { return NAME_NOT_FOUND; } @@ -228,7 +228,7 @@ void SensorService::SensorDirectConnection::capRates() { for (auto &i : existingConnections) { int handle = i.first; int rateLevel = i.second; - sp si = mService->getSensorInterfaceFromHandle(handle); + std::shared_ptr si = mService->getSensorInterfaceFromHandle(handle); if (si != nullptr) { const Sensor& s = si->getSensor(); if (mService->isSensorInCappedSet(s.getType()) && diff --git a/services/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp index f06f9472bd..82d0295a7b 100644 --- a/services/sensorservice/SensorEventConnection.cpp +++ b/services/sensorservice/SensorEventConnection.cpp @@ -160,7 +160,7 @@ void SensorService::SensorEventConnection::dump(util::ProtoOutputStream* proto) bool SensorService::SensorEventConnection::addSensor(int32_t handle) { Mutex::Autolock _l(mConnectionLock); - sp si = mService->getSensorInterfaceFromHandle(handle); + std::shared_ptr si = mService->getSensorInterfaceFromHandle(handle); if (si == nullptr || !mService->canAccessSensor(si->getSensor(), "Add to SensorEventConnection: ", mOpPackageName) || @@ -202,7 +202,7 @@ bool SensorService::SensorEventConnection::hasOneShotSensors() const { Mutex::Autolock _l(mConnectionLock); for (auto &it : mSensorInfo) { const int handle = it.first; - sp si = mService->getSensorInterfaceFromHandle(handle); + std::shared_ptr si = mService->getSensorInterfaceFromHandle(handle); if (si != nullptr && si->getSensor().getReportingMode() == AREPORTING_MODE_ONE_SHOT) { return true; } @@ -245,7 +245,7 @@ void SensorService::SensorEventConnection::updateLooperRegistrationLocked( if (mDataInjectionMode) looper_flags |= ALOOPER_EVENT_INPUT; for (auto& it : mSensorInfo) { const int handle = it.first; - sp si = mService->getSensorInterfaceFromHandle(handle); + std::shared_ptr si = mService->getSensorInterfaceFromHandle(handle); if (si != nullptr && si->getSensor().isWakeUpSensor()) { looper_flags |= ALOOPER_EVENT_INPUT; } @@ -555,7 +555,7 @@ void SensorService::SensorEventConnection::sendPendingFlushEventsLocked() { // flush complete events to be sent. for (auto& it : mSensorInfo) { const int handle = it.first; - sp si = mService->getSensorInterfaceFromHandle(handle); + std::shared_ptr si = mService->getSensorInterfaceFromHandle(handle); if (si == nullptr) { continue; } @@ -689,7 +689,7 @@ status_t SensorService::SensorEventConnection::enableDisable( if (enabled) { nsecs_t requestedSamplingPeriodNs = samplingPeriodNs; bool isSensorCapped = false; - sp si = mService->getSensorInterfaceFromHandle(handle); + std::shared_ptr si = mService->getSensorInterfaceFromHandle(handle); if (si != nullptr) { const Sensor& s = si->getSensor(); if (mService->isSensorInCappedSet(s.getType())) { @@ -729,7 +729,7 @@ status_t SensorService::SensorEventConnection::setEventRate(int handle, nsecs_t nsecs_t requestedSamplingPeriodNs = samplingPeriodNs; bool isSensorCapped = false; - sp si = mService->getSensorInterfaceFromHandle(handle); + std::shared_ptr si = mService->getSensorInterfaceFromHandle(handle); if (si != nullptr) { const Sensor& s = si->getSensor(); if (mService->isSensorInCappedSet(s.getType())) { @@ -852,7 +852,7 @@ int SensorService::SensorEventConnection::handleEvent(int fd, int events, void* } sensors_event_t sensor_event; memcpy(&sensor_event, buf, sizeof(sensors_event_t)); - sp si = + std::shared_ptr si = mService->getSensorInterfaceFromHandle(sensor_event.sensor); if (si == nullptr) { return 1; @@ -903,7 +903,7 @@ int SensorService::SensorEventConnection::computeMaxCacheSizeLocked() const { size_t fifoWakeUpSensors = 0; size_t fifoNonWakeUpSensors = 0; for (auto& it : mSensorInfo) { - sp si = mService->getSensorInterfaceFromHandle(it.first); + std::shared_ptr si = mService->getSensorInterfaceFromHandle(it.first); if (si == nullptr) { continue; } diff --git a/services/sensorservice/SensorList.cpp b/services/sensorservice/SensorList.cpp index 6d36b4789b..daff4d00dc 100644 --- a/services/sensorservice/SensorList.cpp +++ b/services/sensorservice/SensorList.cpp @@ -28,13 +28,13 @@ namespace SensorServiceUtil { const Sensor SensorList::mNonSensor = Sensor("unknown"); -bool SensorList::add( - int handle, SensorInterface* si, bool isForDebug, bool isVirtual, int deviceId) { +bool SensorList::add(int handle, std::shared_ptr si, bool isForDebug, + bool isVirtual, int deviceId) { std::lock_guard lk(mLock); if (handle == si->getSensor().getHandle() && mUsedHandle.insert(handle).second) { // will succeed as the mUsedHandle does not have this handle - mHandleMap.emplace(handle, Entry(si, isForDebug, isVirtual, deviceId)); + mHandleMap.emplace(handle, Entry(std::move(si), isForDebug, isVirtual, deviceId)); return true; } // handle exist already or handle mismatch @@ -63,12 +63,12 @@ String8 SensorList::getStringType(int handle) const { mNonSensor.getStringType()); } -sp SensorList::getInterface(int handle) const { - return getOne>( - handle, [] (const Entry& e) -> sp {return e.si;}, nullptr); +std::shared_ptr SensorList::getInterface(int handle) const { + return getOne>( + handle, [] (const Entry& e) -> std::shared_ptr {return e.si;}, + nullptr); } - bool SensorList::isNewHandle(int handle) const { std::lock_guard lk(mLock); return mUsedHandle.find(handle) == mUsedHandle.end(); diff --git a/services/sensorservice/SensorList.h b/services/sensorservice/SensorList.h index 79f6701922..ad5b21f370 100644 --- a/services/sensorservice/SensorList.h +++ b/services/sensorservice/SensorList.h @@ -37,22 +37,18 @@ namespace SensorServiceUtil { class SensorList : public Dumpable { public: struct Entry { - sp si; + std::shared_ptr si; const bool isForDebug; const bool isVirtual; const int deviceId; - Entry(SensorInterface* si_, bool debug_, bool virtual_, int deviceId_) : - si(si_), isForDebug(debug_), isVirtual(virtual_), deviceId(deviceId_) { + Entry(std::shared_ptr si_, bool debug_, bool virtual_, int deviceId_) : + si(std::move(si_)), isForDebug(debug_), isVirtual(virtual_), deviceId(deviceId_) { } }; - // After SensorInterface * is added into SensorList, it can be assumed that SensorList own the - // object it pointed to and the object should not be released elsewhere. - bool add(int handle, SensorInterface* si, bool isForDebug = false, bool isVirtual = false, - int deviceId = RuntimeSensor::DEFAULT_DEVICE_ID); - - // After a handle is removed, the object that SensorInterface * pointing to may get deleted if - // no more sp<> of the same object exist. + // SensorList owns the SensorInterface pointer. + bool add(int handle, std::shared_ptr si, bool isForDebug = false, + bool isVirtual = false, int deviceId = RuntimeSensor::DEFAULT_DEVICE_ID); bool remove(int handle); inline bool hasAnySensor() const { return mHandleMap.size() > 0;} @@ -67,7 +63,7 @@ public: String8 getName(int handle) const; String8 getStringType(int handle) const; - sp getInterface(int handle) const; + std::shared_ptr getInterface(int handle) const; bool isNewHandle(int handle) const; // Iterate through Sensor in sensor list and perform operation f on each Sensor object. diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp index 0c9fef5652..7124d35bde 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -183,15 +183,14 @@ int SensorService::registerRuntimeSensor( sensor_t runtimeSensor = sensor; // force the handle to be consistent runtimeSensor.handle = handle; - SensorInterface *si = new RuntimeSensor(runtimeSensor, std::move(runtimeSensorCallback)); + auto si = std::make_shared(runtimeSensor, std::move(runtimeSensorCallback)); Mutex::Autolock _l(mLock); - const Sensor& s = registerSensor(si, /* isDebug= */ false, /* isVirtual= */ false, deviceId); - - if (s.getHandle() != handle) { + if (!registerSensor(std::move(si), /* isDebug= */ false, /* isVirtual= */ false, deviceId)) { // The registration was unsuccessful. - return s.getHandle(); + return mSensors.getNonSensor().getHandle(); } + return handle; } @@ -319,11 +318,13 @@ void SensorService::onFirstRef() { } if (useThisSensor) { if (list[i].type == SENSOR_TYPE_PROXIMITY) { - SensorInterface* s = new ProximitySensor(list[i], *this); - registerSensor(s); - mProxSensorHandles.push_back(s->getSensor().getHandle()); + auto s = std::make_shared(list[i], *this); + const int handle = s->getSensor().getHandle(); + if (registerSensor(std::move(s))) { + mProxSensorHandles.push_back(handle); + } } else { - registerSensor(new HardwareSensor(list[i])); + registerSensor(std::make_shared(list[i])); } } } @@ -338,56 +339,63 @@ void SensorService::onFirstRef() { // available in the HAL bool needRotationVector = (virtualSensorsNeeds & (1<(), + /* isDebug= */ !needRotationVector); + registerVirtualSensor(std::make_shared(), + /* isDebug= */ !needRotationVector); // virtual debugging sensors are not for user - registerSensor( new CorrectedGyroSensor(list, count), true, true); - registerSensor( new GyroDriftSensor(), true, true); + registerVirtualSensor(std::make_shared(list, count), + /* isDebug= */ true); + registerVirtualSensor(std::make_shared(), /* isDebug= */ true); } if (hasAccel && (hasGyro || hasGyroUncalibrated)) { bool needGravitySensor = (virtualSensorsNeeds & (1<(list, count), + /* isDebug= */ !needGravitySensor); bool needLinearAcceleration = (virtualSensorsNeeds & (1<(list, count), + /* isDebug= */ !needLinearAcceleration); bool needGameRotationVector = (virtualSensorsNeeds & (1<(), + /* isDebug= */ !needGameRotationVector); } if (hasAccel && hasMag) { bool needGeoMagRotationVector = (virtualSensorsNeeds & (1<(), + /* isDebug= */ !needGeoMagRotationVector); } if (isAutomotive()) { if (hasAccel) { - registerSensor(new LimitedAxesImuSensor(list, count, SENSOR_TYPE_ACCELEROMETER), - /*isDebug=*/false, /*isVirtual=*/true); + registerVirtualSensor( + std::make_shared( + list, count, SENSOR_TYPE_ACCELEROMETER)); } if (hasGyro) { - registerSensor(new LimitedAxesImuSensor(list, count, SENSOR_TYPE_GYROSCOPE), - /*isDebug=*/false, /*isVirtual=*/true); + registerVirtualSensor( + std::make_shared( + list, count, SENSOR_TYPE_GYROSCOPE)); } if (hasAccelUncalibrated) { - registerSensor(new LimitedAxesImuSensor(list, count, - SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED), - /*isDebug=*/false, /*isVirtual=*/true); + registerVirtualSensor( + std::make_shared( + list, count, SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED)); } if (hasGyroUncalibrated) { - registerSensor(new LimitedAxesImuSensor(list, count, - SENSOR_TYPE_GYROSCOPE_UNCALIBRATED), - /*isDebug=*/false, /*isVirtual=*/true); + registerVirtualSensor( + std::make_shared( + list, count, SENSOR_TYPE_GYROSCOPE_UNCALIBRATED)); } } @@ -488,20 +496,21 @@ bool SensorService::hasSensorAccessLocked(uid_t uid, const String16& opPackageNa && isUidActive(uid) && !isOperationRestrictedLocked(opPackageName); } -const Sensor& SensorService::registerSensor(SensorInterface* s, bool isDebug, bool isVirtual, - int deviceId) { - int handle = s->getSensor().getHandle(); - int type = s->getSensor().getType(); - if (mSensors.add(handle, s, isDebug, isVirtual, deviceId)) { +bool SensorService::registerSensor(std::shared_ptr s, bool isDebug, bool isVirtual, + int deviceId) { + const int handle = s->getSensor().getHandle(); + const int type = s->getSensor().getType(); + if (mSensors.add(handle, std::move(s), isDebug, isVirtual, deviceId)) { mRecentEvent.emplace(handle, new SensorServiceUtil::RecentEventLogger(type)); - return s->getSensor(); + return true; } else { - return mSensors.getNonSensor(); + LOG_FATAL("Failed to register sensor with handle %d", handle); + return false; } } -const Sensor& SensorService::registerDynamicSensorLocked(SensorInterface* s, bool isDebug) { - return registerSensor(s, isDebug); +bool SensorService::registerDynamicSensorLocked(std::shared_ptr s, bool isDebug) { + return registerSensor(std::move(s), isDebug); } bool SensorService::unregisterDynamicSensorLocked(int handle) { @@ -515,8 +524,8 @@ bool SensorService::unregisterDynamicSensorLocked(int handle) { return ret; } -const Sensor& SensorService::registerVirtualSensor(SensorInterface* s, bool isDebug) { - return registerSensor(s, isDebug, true); +bool SensorService::registerVirtualSensor(std::shared_ptr s, bool isDebug) { + return registerSensor(std::move(s), isDebug, true); } SensorService::~SensorService() { @@ -611,8 +620,8 @@ status_t SensorService::dump(int fd, const Vector& args) { result.append("Recent Sensor events:\n"); for (auto&& i : mRecentEvent) { - sp s = mSensors.getInterface(i.first); - if (!i.second->isEmpty()) { + std::shared_ptr s = getSensorInterfaceFromHandle(i.first); + if (!i.second->isEmpty() && s != nullptr) { if (privileged || s->getSensor().getRequiredPermission().isEmpty()) { i.second->setFormat("normal"); } else { @@ -729,8 +738,8 @@ status_t SensorService::dumpProtoLocked(int fd, ConnectionSafeAutolock* connLock // Write SensorEventsProto token = proto.start(SENSOR_EVENTS); for (auto&& i : mRecentEvent) { - sp s = mSensors.getInterface(i.first); - if (!i.second->isEmpty()) { + std::shared_ptr s = getSensorInterfaceFromHandle(i.first); + if (!i.second->isEmpty() && s != nullptr) { i.second->setFormat(privileged || s->getSensor().getRequiredPermission().isEmpty() ? "normal" : "mask_data"); const uint64_t mToken = proto.start(service::SensorEventsProto::RECENT_EVENTS_LOGS); @@ -1020,7 +1029,7 @@ void SensorService::cleanupAutoDisabledSensorLocked(const sphasSensor(handle)) { - sp si = getSensorInterfaceFromHandle(handle); + std::shared_ptr si = getSensorInterfaceFromHandle(handle); // If this buffer has an event from a one_shot sensor and this connection is registered // for this particular one_shot sensor, try cleaning up the connection. if (si != nullptr && @@ -1105,7 +1114,7 @@ bool SensorService::threadLoop() { break; } sensors_event_t out; - sp si = mSensors.getInterface(handle); + std::shared_ptr si = getSensorInterfaceFromHandle(handle); if (si == nullptr) { ALOGE("handle %d is not an valid virtual sensor", handle); continue; @@ -1200,12 +1209,12 @@ bool SensorService::threadLoop() { // force the handle to be consistent s.handle = handle; - SensorInterface *si = new HardwareSensor(s, uuid); + auto si = std::make_shared(s, uuid); // This will release hold on dynamic sensor meta, so it should be called // after Sensor object is created. device.handleDynamicSensorConnection(handle, true /*connected*/); - registerDynamicSensorLocked(si); + registerDynamicSensorLocked(std::move(si)); } else { ALOGE("Handle %d has been used, cannot use again before reboot.", handle); } @@ -1332,7 +1341,7 @@ String8 SensorService::getSensorStringType(int handle) const { } bool SensorService::isVirtualSensor(int handle) const { - sp sensor = getSensorInterfaceFromHandle(handle); + std::shared_ptr sensor = getSensorInterfaceFromHandle(handle); return sensor != nullptr && sensor->isVirtual(); } @@ -1341,7 +1350,7 @@ bool SensorService::isWakeUpSensorEvent(const sensors_event_t& event) const { if (event.type == SENSOR_TYPE_META_DATA) { handle = event.meta_data.sensor; } - sp sensor = getSensorInterfaceFromHandle(handle); + std::shared_ptr sensor = getSensorInterfaceFromHandle(handle); return sensor != nullptr && sensor->getSensor().isWakeUpSensor(); } @@ -1741,7 +1750,7 @@ void SensorService::cleanupConnection(SensorEventConnection* c) { int handle = mActiveSensors.keyAt(i); if (c->hasSensor(handle)) { ALOGD_IF(DEBUG_CONNECTIONS, "%zu: disabling handle=0x%08x", i, handle); - sp sensor = getSensorInterfaceFromHandle(handle); + std::shared_ptr sensor = getSensorInterfaceFromHandle(handle); if (sensor != nullptr) { sensor->activate(c, false); } else { @@ -1855,7 +1864,7 @@ status_t SensorService::removeProximityActiveListener( return NAME_NOT_FOUND; } -sp SensorService::getSensorInterfaceFromHandle(int handle) const { +std::shared_ptr SensorService::getSensorInterfaceFromHandle(int handle) const { return mSensors.getInterface(handle); } @@ -1865,7 +1874,7 @@ status_t SensorService::enable(const sp& connection, if (mInitCheck != NO_ERROR) return mInitCheck; - sp sensor = getSensorInterfaceFromHandle(handle); + std::shared_ptr sensor = getSensorInterfaceFromHandle(handle); if (sensor == nullptr || !canAccessSensor(sensor->getSensor(), "Tried enabling", opPackageName)) { return BAD_VALUE; @@ -2011,7 +2020,7 @@ status_t SensorService::disable(const sp& connection, int Mutex::Autolock _l(mLock); status_t err = cleanupWithoutDisableLocked(connection, handle); if (err == NO_ERROR) { - sp sensor = getSensorInterfaceFromHandle(handle); + std::shared_ptr sensor = getSensorInterfaceFromHandle(handle); err = sensor != nullptr ? sensor->activate(connection.get(), false) : status_t(BAD_VALUE); } @@ -2057,7 +2066,7 @@ status_t SensorService::setEventRate(const sp& connection if (mInitCheck != NO_ERROR) return mInitCheck; - sp sensor = getSensorInterfaceFromHandle(handle); + std::shared_ptr sensor = getSensorInterfaceFromHandle(handle); if (sensor == nullptr || !canAccessSensor(sensor->getSensor(), "Tried configuring", opPackageName)) { return BAD_VALUE; @@ -2083,7 +2092,7 @@ status_t SensorService::flushSensor(const sp& connection, Mutex::Autolock _l(mLock); // Loop through all sensors for this connection and call flush on each of them. for (int handle : connection->getActiveSensorHandles()) { - sp sensor = getSensorInterfaceFromHandle(handle); + std::shared_ptr sensor = getSensorInterfaceFromHandle(handle); if (sensor == nullptr) { continue; } diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h index e4903987bc..78df501699 100644 --- a/services/sensorservice/SensorService.h +++ b/services/sensorservice/SensorService.h @@ -375,15 +375,14 @@ private: String8 getSensorName(int handle) const; String8 getSensorStringType(int handle) const; bool isVirtualSensor(int handle) const; - sp getSensorInterfaceFromHandle(int handle) const; + std::shared_ptr getSensorInterfaceFromHandle(int handle) const; bool isWakeUpSensor(int type) const; void recordLastValueLocked(sensors_event_t const* buffer, size_t count); static void sortEventBuffer(sensors_event_t* buffer, size_t count); - const Sensor& registerSensor(SensorInterface* sensor, bool isDebug = false, - bool isVirtual = false, - int deviceId = RuntimeSensor::DEFAULT_DEVICE_ID); - const Sensor& registerVirtualSensor(SensorInterface* sensor, bool isDebug = false); - const Sensor& registerDynamicSensorLocked(SensorInterface* sensor, bool isDebug = false); + bool registerSensor(std::shared_ptr sensor, bool isDebug = false, + bool isVirtual = false, int deviceId = RuntimeSensor::DEFAULT_DEVICE_ID); + bool registerVirtualSensor(std::shared_ptr sensor, bool isDebug = false); + bool registerDynamicSensorLocked(std::shared_ptr sensor, bool isDebug = false); bool unregisterDynamicSensorLocked(int handle); status_t cleanupWithoutDisable(const sp& connection, int handle); status_t cleanupWithoutDisableLocked(const sp& connection, int handle); -- GitLab From ad58a34e716896ca9d497ebc982cb65996d3410d Mon Sep 17 00:00:00 2001 From: Fyodor Kyslov Date: Mon, 12 Dec 2022 02:10:35 +0000 Subject: [PATCH 0598/1310] librecoverymap: Update XMP parser Updating XMP parser to support latest JPEGR XMP spec Bug: b/252835416 Test: manual Change-Id: I882cd3bebb7db8d3c7f7afa51cf70c0895c68b3d --- libs/jpegrecoverymap/recoverymaputils.cpp | 69 ++++++++++++++++++----- 1 file changed, 54 insertions(+), 15 deletions(-) diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/recoverymaputils.cpp index fe46cbad91..31ddb23abc 100644 --- a/libs/jpegrecoverymap/recoverymaputils.cpp +++ b/libs/jpegrecoverymap/recoverymaputils.cpp @@ -36,7 +36,7 @@ class XMPXmlHandler : public XmlHandler { public: XMPXmlHandler() : XmlHandler() { - rangeScalingFactorState = NotStrarted; + gContainerItemState = NotStrarted; } enum ParseState { @@ -48,11 +48,11 @@ public: virtual DataMatchResult StartElement(const XmlTokenContext& context) { string val; if (context.BuildTokenValue(&val)) { - if (!val.compare(rangeScalingFactorName)) { - rangeScalingFactorState = Started; + if (!val.compare(gContainerItemName)) { + gContainerItemState = Started; } else { - if (rangeScalingFactorState != Done) { - rangeScalingFactorState = NotStrarted; + if (gContainerItemState != Done) { + gContainerItemState = NotStrarted; } } } @@ -60,24 +60,45 @@ public: } virtual DataMatchResult FinishElement(const XmlTokenContext& context) { - if (rangeScalingFactorState == Started) { - rangeScalingFactorState = Done; + if (gContainerItemState == Started) { + gContainerItemState = Done; + lastAttributeName = ""; } return context.GetResult(); } - virtual DataMatchResult ElementContent(const XmlTokenContext& context) { + virtual DataMatchResult AttributeName(const XmlTokenContext& context) { string val; - if (rangeScalingFactorState == Started) { + if (gContainerItemState == Started) { if (context.BuildTokenValue(&val)) { - rangeScalingFactorStr.assign(val); + if (!val.compare(rangeScalingFactorAttrName)) { + lastAttributeName = rangeScalingFactorAttrName; + } else if (!val.compare(transferFunctionAttrName)) { + lastAttributeName = transferFunctionAttrName; + } else { + lastAttributeName = ""; + } + } + } + return context.GetResult(); + } + + virtual DataMatchResult AttributeValue(const XmlTokenContext& context) { + string val; + if (gContainerItemState == Started) { + if (context.BuildTokenValue(&val, true)) { + if (!lastAttributeName.compare(rangeScalingFactorAttrName)) { + rangeScalingFactorStr = val; + } else if (!lastAttributeName.compare(transferFunctionAttrName)) { + transferFunctionStr = val; + } } } return context.GetResult(); } bool getRangeScalingFactor(float* scaling_factor) { - if (rangeScalingFactorState == Done) { + if (gContainerItemState == Done) { stringstream ss(rangeScalingFactorStr); float val; if (ss >> val) { @@ -92,17 +113,35 @@ public: } bool getTransferFunction(jpegr_transfer_function* transfer_function) { - *transfer_function = JPEGR_TF_HLG; + if (gContainerItemState == Done) { + stringstream ss(transferFunctionStr); + int val; + if (ss >> val) { + *transfer_function = static_cast(val); + return true; + } else { + return false; + } + } else { + return false; + } return true; } private: - static const string rangeScalingFactorName; + static const string gContainerItemName; + static const string rangeScalingFactorAttrName; + static const string transferFunctionAttrName; string rangeScalingFactorStr; - ParseState rangeScalingFactorState; + string transferFunctionStr; + string lastAttributeName; + ParseState gContainerItemState; }; -const string XMPXmlHandler::rangeScalingFactorName = "GContainer:rangeScalingFactor"; +const string XMPXmlHandler::gContainerItemName = "GContainer:Item"; +const string XMPXmlHandler::rangeScalingFactorAttrName = "RecoveryMap:RangeScalingFactor"; +const string XMPXmlHandler::transferFunctionAttrName = "RecoveryMap:TransferFunction"; + bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) { -- GitLab From 70093fd524140907511e762650bfe0de9a1a9b09 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Mon, 12 Dec 2022 16:51:52 +0000 Subject: [PATCH 0599/1310] SF: log fatal if layer destroyed off main thread We're seeing occasional nullptr errors when LayerFE accesses its mSnapshot member during SurfaceFlinger::composite calls. One way this may happen is if a Layer is destroyed during SurfaceFlinger::composite, causing LayerFE to receive a null snapshot. Adding these logs should help to identify the root cause and prevent LayerFE::mSnapshot nullptr errors. Bug: 261627672 Test: presubmits Change-Id: I83b3fa304c65ae2dbe936f0f2bdc08045ed96d1f --- services/surfaceflinger/Layer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index b7abd95af8..1f159ae41a 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -216,6 +216,9 @@ void Layer::onFirstRef() { } Layer::~Layer() { + LOG_ALWAYS_FATAL_IF(std::this_thread::get_id() != mFlinger->mMainThreadId, + "Layer destructor called off the main thread."); + // The original layer and the clone layer share the same texture and buffer. Therefore, only // one of the layers, in this case the original layer, needs to handle the deletion. The // original layer and the clone should be removed at the same time so there shouldn't be any -- GitLab From b2901c9ec5b6e131623170b3ca3b9bd79bb25545 Mon Sep 17 00:00:00 2001 From: Serdar Kocdemir Date: Thu, 17 Nov 2022 00:39:05 +0000 Subject: [PATCH 0600/1310] Adding new Vulkan metrics to GPU Stats Following fields are added into GpuStatsAppInfo for tracking: bool createdGlesContext = false; bool createdVulkanDevice = false; bool createdVulkanSwapchain = false; uint32_t vulkanApiVersion = 0; uint64_t vulkanDeviceFeaturesEnabled = 0; std::vector vulkanInstanceExtensions = {}; std::vector vulkanDeviceExtensions = {}; Extensions are tracked as 32-bit hashes. setTargetStatsArray GPU service function added to provide an array of stat values, used for reporting list of extensions. Bug: b/244286661 Test: adb shell dumpsys gpu Test: atest GpuStatsTest Change-Id: I4ae4e3b687cd6274a9b4127a336dd0f91f5f9e39 --- libs/graphicsenv/GpuStatsInfo.cpp | 32 ++++++ libs/graphicsenv/GraphicsEnv.cpp | 60 +++++++++- libs/graphicsenv/IGpuService.cpp | 8 ++ .../include/graphicsenv/GpuStatsInfo.h | 17 +++ .../include/graphicsenv/GraphicsEnv.h | 9 ++ .../include/graphicsenv/IGpuService.h | 4 + opengl/libs/EGL/egl_platform_entries.cpp | 2 + services/gpuservice/GpuService.cpp | 6 + services/gpuservice/GpuService.h | 3 + services/gpuservice/gpustats/GpuStats.cpp | 91 ++++++++++++--- .../gpustats/include/gpustats/GpuStats.h | 5 +- .../tests/unittests/GpuStatsTest.cpp | 107 ++++++++++++++++++ vulkan/libvulkan/driver.cpp | 80 +++++++++++++ vulkan/libvulkan/swapchain.cpp | 4 + 14 files changed, 410 insertions(+), 18 deletions(-) diff --git a/libs/graphicsenv/GpuStatsInfo.cpp b/libs/graphicsenv/GpuStatsInfo.cpp index 858739c9dd..7b7421424d 100644 --- a/libs/graphicsenv/GpuStatsInfo.cpp +++ b/libs/graphicsenv/GpuStatsInfo.cpp @@ -89,6 +89,14 @@ status_t GpuStatsAppInfo::writeToParcel(Parcel* parcel) const { if ((status = parcel->writeBool(falsePrerotation)) != OK) return status; if ((status = parcel->writeBool(gles1InUse)) != OK) return status; if ((status = parcel->writeBool(angleInUse)) != OK) return status; + if ((status = parcel->writeBool(createdGlesContext)) != OK) return status; + if ((status = parcel->writeBool(createdVulkanDevice)) != OK) return status; + if ((status = parcel->writeBool(createdVulkanSwapchain)) != OK) return status; + if ((status = parcel->writeUint32(vulkanApiVersion)) != OK) return status; + if ((status = parcel->writeUint64(vulkanDeviceFeaturesEnabled)) != OK) return status; + if ((status = parcel->writeInt32Vector(vulkanInstanceExtensions)) != OK) return status; + if ((status = parcel->writeInt32Vector(vulkanDeviceExtensions)) != OK) return status; + return OK; } @@ -103,6 +111,14 @@ status_t GpuStatsAppInfo::readFromParcel(const Parcel* parcel) { if ((status = parcel->readBool(&falsePrerotation)) != OK) return status; if ((status = parcel->readBool(&gles1InUse)) != OK) return status; if ((status = parcel->readBool(&angleInUse)) != OK) return status; + if ((status = parcel->readBool(&createdGlesContext)) != OK) return status; + if ((status = parcel->readBool(&createdVulkanDevice)) != OK) return status; + if ((status = parcel->readBool(&createdVulkanSwapchain)) != OK) return status; + if ((status = parcel->readUint32(&vulkanApiVersion)) != OK) return status; + if ((status = parcel->readUint64(&vulkanDeviceFeaturesEnabled)) != OK) return status; + if ((status = parcel->readInt32Vector(&vulkanInstanceExtensions)) != OK) return status; + if ((status = parcel->readInt32Vector(&vulkanDeviceExtensions)) != OK) return status; + return OK; } @@ -114,6 +130,12 @@ std::string GpuStatsAppInfo::toString() const { StringAppendF(&result, "falsePrerotation = %d\n", falsePrerotation); StringAppendF(&result, "gles1InUse = %d\n", gles1InUse); StringAppendF(&result, "angleInUse = %d\n", angleInUse); + StringAppendF(&result, "createdGlesContext = %d\n", createdGlesContext); + StringAppendF(&result, "createdVulkanDevice = %d\n", createdVulkanDevice); + StringAppendF(&result, "createdVulkanSwapchain = %d\n", createdVulkanSwapchain); + StringAppendF(&result, "vulkanApiVersion = 0x%" PRIx32 "\n", vulkanApiVersion); + StringAppendF(&result, "vulkanDeviceFeaturesEnabled = 0x%" PRIx64 "\n", + vulkanDeviceFeaturesEnabled); result.append("glDriverLoadingTime:"); for (int32_t loadingTime : glDriverLoadingTime) { StringAppendF(&result, " %d", loadingTime); @@ -129,6 +151,16 @@ std::string GpuStatsAppInfo::toString() const { StringAppendF(&result, " %d", loadingTime); } result.append("\n"); + result.append("vulkanInstanceExtensions:"); + for (int32_t extension : vulkanInstanceExtensions) { + StringAppendF(&result, " 0x%x", extension); + } + result.append("\n"); + result.append("vulkanDeviceExtensions:"); + for (int32_t extension : vulkanDeviceExtensions) { + StringAppendF(&result, " 0x%x", extension); + } + result.append("\n"); return result; } diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp index 5f5f85a2ad..46dd62d3bf 100644 --- a/libs/graphicsenv/GraphicsEnv.cpp +++ b/libs/graphicsenv/GraphicsEnv.cpp @@ -259,6 +259,57 @@ void GraphicsEnv::setDriverLoaded(GpuStatsInfo::Api api, bool isDriverLoaded, sendGpuStatsLocked(api, isDriverLoaded, driverLoadingTime); } +// Hash function to calculate hash for null-terminated Vulkan extension names +// We store hash values of the extensions, rather than the actual names or +// indices to be able to support new extensions easily, avoid creating +// a table of 'known' extensions inside Android and reduce the runtime overhead. +static uint64_t calculateExtensionHash(const char* word) { + if (!word) { + return 0; + } + const size_t wordLen = strlen(word); + const uint32_t seed = 167; + uint64_t hash = 0; + for (size_t i = 0; i < wordLen; i++) { + hash = (hash * seed) + word[i]; + } + return hash; +} + +void GraphicsEnv::setVulkanInstanceExtensions(uint32_t enabledExtensionCount, + const char* const* ppEnabledExtensionNames) { + ATRACE_CALL(); + if (enabledExtensionCount == 0 || ppEnabledExtensionNames == nullptr) { + return; + } + + const uint32_t maxNumStats = android::GpuStatsAppInfo::MAX_NUM_EXTENSIONS; + uint64_t extensionHashes[maxNumStats]; + const uint32_t numStats = std::min(enabledExtensionCount, maxNumStats); + for(uint32_t i = 0; i < numStats; i++) { + extensionHashes[i] = calculateExtensionHash(ppEnabledExtensionNames[i]); + } + setTargetStatsArray(android::GpuStatsInfo::Stats::VULKAN_INSTANCE_EXTENSION, + extensionHashes, numStats); +} + +void GraphicsEnv::setVulkanDeviceExtensions(uint32_t enabledExtensionCount, + const char* const* ppEnabledExtensionNames) { + ATRACE_CALL(); + if (enabledExtensionCount == 0 || ppEnabledExtensionNames == nullptr) { + return; + } + + const uint32_t maxNumStats = android::GpuStatsAppInfo::MAX_NUM_EXTENSIONS; + uint64_t extensionHashes[maxNumStats]; + const uint32_t numStats = std::min(enabledExtensionCount, maxNumStats); + for(uint32_t i = 0; i < numStats; i++) { + extensionHashes[i] = calculateExtensionHash(ppEnabledExtensionNames[i]); + } + setTargetStatsArray(android::GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION, + extensionHashes, numStats); +} + static sp getGpuService() { static const sp binder = defaultServiceManager()->checkService(String16("gpu")); if (!binder) { @@ -276,6 +327,11 @@ bool GraphicsEnv::readyToSendGpuStatsLocked() { } void GraphicsEnv::setTargetStats(const GpuStatsInfo::Stats stats, const uint64_t value) { + return setTargetStatsArray(stats, &value, 1); +} + +void GraphicsEnv::setTargetStatsArray(const GpuStatsInfo::Stats stats, const uint64_t* values, + const uint32_t valueCount) { ATRACE_CALL(); std::lock_guard lock(mStatsLock); @@ -283,8 +339,8 @@ void GraphicsEnv::setTargetStats(const GpuStatsInfo::Stats stats, const uint64_t const sp gpuService = getGpuService(); if (gpuService) { - gpuService->setTargetStats(mGpuStats.appPackageName, mGpuStats.driverVersionCode, stats, - value); + gpuService->setTargetStatsArray(mGpuStats.appPackageName, mGpuStats.driverVersionCode, + stats, values, valueCount); } } diff --git a/libs/graphicsenv/IGpuService.cpp b/libs/graphicsenv/IGpuService.cpp index fa25c5516d..ceb52f71d8 100644 --- a/libs/graphicsenv/IGpuService.cpp +++ b/libs/graphicsenv/IGpuService.cpp @@ -61,6 +61,14 @@ public: remote()->transact(BnGpuService::SET_TARGET_STATS, data, &reply, IBinder::FLAG_ONEWAY); } + void setTargetStatsArray(const std::string& appPackageName, const uint64_t driverVersionCode, + const GpuStatsInfo::Stats stats, const uint64_t* values, + const uint32_t valueCount) override { + for (uint32_t i = 0; i < valueCount; i++) { + setTargetStats(appPackageName, driverVersionCode, stats, values[i]); + } + } + void setUpdatableDriverPath(const std::string& driverPath) override { Parcel data, reply; data.writeInterfaceToken(IGpuService::getInterfaceDescriptor()); diff --git a/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h b/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h index 5b513d2a79..47607a0ab9 100644 --- a/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h +++ b/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h @@ -58,6 +58,9 @@ public: */ class GpuStatsAppInfo : public Parcelable { public: + // This limits the worst case number of extensions to be tracked. + static const uint32_t MAX_NUM_EXTENSIONS = 100; + GpuStatsAppInfo() = default; GpuStatsAppInfo(const GpuStatsAppInfo&) = default; virtual ~GpuStatsAppInfo() = default; @@ -74,6 +77,13 @@ public: bool falsePrerotation = false; bool gles1InUse = false; bool angleInUse = false; + bool createdGlesContext = false; + bool createdVulkanDevice = false; + bool createdVulkanSwapchain = false; + uint32_t vulkanApiVersion = 0; + uint64_t vulkanDeviceFeaturesEnabled = 0; + std::vector vulkanInstanceExtensions = {}; + std::vector vulkanDeviceExtensions = {}; std::chrono::time_point lastAccessTime; }; @@ -101,6 +111,13 @@ public: CPU_VULKAN_IN_USE = 0, FALSE_PREROTATION = 1, GLES_1_IN_USE = 2, + CREATED_GLES_CONTEXT = 3, + CREATED_VULKAN_API_VERSION = 4, + CREATED_VULKAN_DEVICE = 5, + CREATED_VULKAN_SWAPCHAIN = 6, + VULKAN_DEVICE_FEATURES_ENABLED = 7, + VULKAN_INSTANCE_EXTENSION = 8, + VULKAN_DEVICE_EXTENSION = 9, }; GpuStatsInfo() = default; diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h index 73d3196948..b58a6d90fe 100644 --- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h +++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h @@ -71,10 +71,19 @@ public: const std::string& appPackageName, const int32_t vulkanVersion); // Set stats for target GpuStatsInfo::Stats type. void setTargetStats(const GpuStatsInfo::Stats stats, const uint64_t value = 0); + // Set array of stats for target GpuStatsInfo::Stats type. + void setTargetStatsArray(const GpuStatsInfo::Stats stats, const uint64_t* values, + const uint32_t valueCount); // Set which driver is intended to load. void setDriverToLoad(GpuStatsInfo::Driver driver); // Set which driver is actually loaded. void setDriverLoaded(GpuStatsInfo::Api api, bool isDriverLoaded, int64_t driverLoadingTime); + // Set which instance extensions are enabled for the app. + void setVulkanInstanceExtensions(uint32_t enabledExtensionCount, + const char* const* ppEnabledExtensionNames); + // Set which device extensions are enabled for the app. + void setVulkanDeviceExtensions(uint32_t enabledExtensionCount, + const char* const* ppEnabledExtensionNames); /* * Api for Vk/GL layer injection. Presently, drivers enable certain diff --git a/libs/graphicsenv/include/graphicsenv/IGpuService.h b/libs/graphicsenv/include/graphicsenv/IGpuService.h index 2d59fa0165..b708b0fec0 100644 --- a/libs/graphicsenv/include/graphicsenv/IGpuService.h +++ b/libs/graphicsenv/include/graphicsenv/IGpuService.h @@ -42,6 +42,10 @@ public: // set target stats. virtual void setTargetStats(const std::string& appPackageName, const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, const uint64_t value = 0) = 0; + virtual void setTargetStatsArray(const std::string& appPackageName, + const uint64_t driverVersionCode, + const GpuStatsInfo::Stats stats, const uint64_t* values, + const uint32_t valueCount) = 0; // setter and getter for updatable driver path. virtual void setUpdatableDriverPath(const std::string& driverPath) = 0; diff --git a/opengl/libs/EGL/egl_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp index 7619a5071c..0527c8a45a 100644 --- a/opengl/libs/EGL/egl_platform_entries.cpp +++ b/opengl/libs/EGL/egl_platform_entries.cpp @@ -937,6 +937,8 @@ EGLContext eglCreateContextImpl(EGLDisplay dpy, EGLConfig config, EGLContext sha android::GraphicsEnv::getInstance().setTargetStats( android::GpuStatsInfo::Stats::GLES_1_IN_USE); } + android::GraphicsEnv::getInstance().setTargetStats( + android::GpuStatsInfo::Stats::CREATED_GLES_CONTEXT); egl_context_t* c = new egl_context_t(dpy, context, config, cnx, version); return c; } diff --git a/services/gpuservice/GpuService.cpp b/services/gpuservice/GpuService.cpp index 7b9782f4e8..aaa8c18508 100644 --- a/services/gpuservice/GpuService.cpp +++ b/services/gpuservice/GpuService.cpp @@ -82,6 +82,12 @@ void GpuService::setTargetStats(const std::string& appPackageName, const uint64_ mGpuStats->insertTargetStats(appPackageName, driverVersionCode, stats, value); } +void GpuService::setTargetStatsArray(const std::string& appPackageName, + const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, + const uint64_t* values, const uint32_t valueCount) { + mGpuStats->insertTargetStatsArray(appPackageName, driverVersionCode, stats, values, valueCount); +} + void GpuService::setUpdatableDriverPath(const std::string& driverPath) { IPCThreadState* ipc = IPCThreadState::self(); const int pid = ipc->getCallingPid(); diff --git a/services/gpuservice/GpuService.h b/services/gpuservice/GpuService.h index d7313d165e..e7e0cba689 100644 --- a/services/gpuservice/GpuService.h +++ b/services/gpuservice/GpuService.h @@ -56,6 +56,9 @@ private: int64_t driverLoadingTime) override; void setTargetStats(const std::string& appPackageName, const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, const uint64_t value) override; + void setTargetStatsArray(const std::string& appPackageName, + const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, + const uint64_t* values, const uint32_t valueCount) override; void setUpdatableDriverPath(const std::string& driverPath) override; std::string getUpdatableDriverPath() override; diff --git a/services/gpuservice/gpustats/GpuStats.cpp b/services/gpuservice/gpustats/GpuStats.cpp index d033453dd7..f06a0457d3 100644 --- a/services/gpuservice/gpustats/GpuStats.cpp +++ b/services/gpuservice/gpustats/GpuStats.cpp @@ -175,29 +175,83 @@ void GpuStats::insertDriverStats(const std::string& driverPackageName, void GpuStats::insertTargetStats(const std::string& appPackageName, const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, - const uint64_t /*value*/) { + const uint64_t value) { + return insertTargetStatsArray(appPackageName, driverVersionCode, stats, &value, 1); +} + +void GpuStats::insertTargetStatsArray(const std::string& appPackageName, + const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, + const uint64_t* values, const uint32_t valueCount) { ATRACE_CALL(); const std::string appStatsKey = appPackageName + std::to_string(driverVersionCode); std::lock_guard lock(mLock); registerStatsdCallbacksIfNeeded(); - if (!mAppStats.count(appStatsKey)) { + + const auto foundApp = mAppStats.find(appStatsKey); + if (foundApp == mAppStats.end()) { return; } - switch (stats) { - case GpuStatsInfo::Stats::CPU_VULKAN_IN_USE: - mAppStats[appStatsKey].cpuVulkanInUse = true; - break; - case GpuStatsInfo::Stats::FALSE_PREROTATION: - mAppStats[appStatsKey].falsePrerotation = true; - break; - case GpuStatsInfo::Stats::GLES_1_IN_USE: - mAppStats[appStatsKey].gles1InUse = true; - break; - default: - break; + GpuStatsAppInfo& targetAppStats = foundApp->second; + + if (stats == GpuStatsInfo::Stats::VULKAN_INSTANCE_EXTENSION + || stats == GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION) { + // Handle extension arrays separately as we need to store a unique set of them + // in the stats vector. Storing in std::set<> is not efficient for serialization tasks. + std::vector& targetVec = + (stats == GpuStatsInfo::Stats::VULKAN_INSTANCE_EXTENSION) ? + targetAppStats.vulkanInstanceExtensions : + targetAppStats.vulkanDeviceExtensions; + const bool addAll = (targetVec.size() == 0); + targetVec.reserve(valueCount); + + // Add new extensions into the set + for(uint32_t i = 0; + (i < valueCount) && (targetVec.size() < GpuStatsAppInfo::MAX_NUM_EXTENSIONS); + i++) { + const int32_t extVal = int32_t(values[i] & 0xFFFFFFFF); + if (addAll + || std::find(targetVec.cbegin(), targetVec.cend(), extVal) == targetVec.cend()) { + targetVec.push_back(extVal); + } + } + } + else { + // Handle other type of stats info events + for(uint32_t i = 0; i < valueCount; i++) { + const uint64_t value = values[i]; + switch (stats) { + case GpuStatsInfo::Stats::CPU_VULKAN_IN_USE: + targetAppStats.cpuVulkanInUse = true; + break; + case GpuStatsInfo::Stats::FALSE_PREROTATION: + targetAppStats.falsePrerotation = true; + break; + case GpuStatsInfo::Stats::GLES_1_IN_USE: + targetAppStats.gles1InUse = true; + break; + case GpuStatsInfo::Stats::CREATED_GLES_CONTEXT: + targetAppStats.createdGlesContext = true; + break; + case GpuStatsInfo::Stats::CREATED_VULKAN_DEVICE: + targetAppStats.createdVulkanDevice = true; + break; + case GpuStatsInfo::Stats::CREATED_VULKAN_API_VERSION: + targetAppStats.vulkanApiVersion = uint32_t(value & 0xffffffff); + break; + case GpuStatsInfo::Stats::CREATED_VULKAN_SWAPCHAIN: + targetAppStats.createdVulkanSwapchain = true; + break; + case GpuStatsInfo::Stats::VULKAN_DEVICE_FEATURES_ENABLED: + // Merge all requested feature bits together for this app + targetAppStats.vulkanDeviceFeaturesEnabled |= value; + break; + default: + break; + } + } } } @@ -347,7 +401,14 @@ AStatsManager_PullAtomCallbackReturn GpuStats::pullAppInfoAtom(AStatsEventList* ele.second.cpuVulkanInUse, ele.second.falsePrerotation, ele.second.gles1InUse, - ele.second.angleInUse); + ele.second.angleInUse, + ele.second.createdGlesContext, + ele.second.createdVulkanDevice, + ele.second.createdVulkanSwapchain, + ele.second.vulkanApiVersion, + ele.second.vulkanDeviceFeaturesEnabled, + ele.second.vulkanInstanceExtensions, + ele.second.vulkanDeviceExtensions); } } diff --git a/services/gpuservice/gpustats/include/gpustats/GpuStats.h b/services/gpuservice/gpustats/include/gpustats/GpuStats.h index 2aba651af9..22c64dbc02 100644 --- a/services/gpuservice/gpustats/include/gpustats/GpuStats.h +++ b/services/gpuservice/gpustats/include/gpustats/GpuStats.h @@ -41,11 +41,14 @@ public: // Insert target stats into app stats or potentially global stats as well. void insertTargetStats(const std::string& appPackageName, const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, const uint64_t value); + void insertTargetStatsArray(const std::string& appPackageName, + const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, + const uint64_t* values, const uint32_t valueCount); // dumpsys interface void dump(const Vector& args, std::string* result); // This limits the worst case number of loading times tracked. - static const size_t MAX_NUM_LOADING_TIMES = 50; + static const size_t MAX_NUM_LOADING_TIMES = 16; // Below limits the memory usage of GpuStats to be less than 10KB. This is // the preferred number for statsd while maintaining nice data quality. static const size_t MAX_NUM_APP_RECORDS = 100; diff --git a/services/gpuservice/tests/unittests/GpuStatsTest.cpp b/services/gpuservice/tests/unittests/GpuStatsTest.cpp index 7ea22888f8..4ce533ff7c 100644 --- a/services/gpuservice/tests/unittests/GpuStatsTest.cpp +++ b/services/gpuservice/tests/unittests/GpuStatsTest.cpp @@ -52,6 +52,13 @@ using testing::HasSubstr; #define DRIVER_LOADING_TIME_2 789 #define DRIVER_LOADING_TIME_3 891 +constexpr uint64_t VULKAN_FEATURES_MASK = 0x600D; +constexpr uint32_t VULKAN_API_VERSION = 0x400000; +constexpr int32_t VULKAN_INSTANCE_EXTENSION_1 = 0x1234; +constexpr int32_t VULKAN_INSTANCE_EXTENSION_2 = 0x8765; +constexpr int32_t VULKAN_DEVICE_EXTENSION_1 = 0x9012; +constexpr int32_t VULKAN_DEVICE_EXTENSION_2 = 0x3456; + enum InputCommand : int32_t { DUMP_ALL = 0, DUMP_GLOBAL = 1, @@ -218,6 +225,24 @@ TEST_F(GpuStatsTest, canNotInsertTargetStatsBeforeProperSetup) { GpuStatsInfo::Stats::FALSE_PREROTATION, 0); mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, GpuStatsInfo::Stats::GLES_1_IN_USE, 0); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_GLES_CONTEXT, 0); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_VULKAN_API_VERSION, + VULKAN_API_VERSION); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_VULKAN_DEVICE, 0); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_VULKAN_SWAPCHAIN, 0); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_DEVICE_FEATURES_ENABLED, + VULKAN_FEATURES_MASK); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_INSTANCE_EXTENSION, + VULKAN_INSTANCE_EXTENSION_1); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION, + VULKAN_DEVICE_EXTENSION_1); EXPECT_TRUE(inputCommand(InputCommand::DUMP_APP).empty()); } @@ -233,10 +258,51 @@ TEST_F(GpuStatsTest, canInsertTargetStatsAfterProperSetup) { GpuStatsInfo::Stats::FALSE_PREROTATION, 0); mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, GpuStatsInfo::Stats::GLES_1_IN_USE, 0); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_GLES_CONTEXT, 0); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_VULKAN_API_VERSION, + VULKAN_API_VERSION); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_VULKAN_DEVICE, 0); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_VULKAN_SWAPCHAIN, 0); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_DEVICE_FEATURES_ENABLED, + VULKAN_FEATURES_MASK); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_INSTANCE_EXTENSION, + VULKAN_INSTANCE_EXTENSION_1); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_INSTANCE_EXTENSION, + VULKAN_INSTANCE_EXTENSION_2); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION, + VULKAN_DEVICE_EXTENSION_1); + mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION, + VULKAN_DEVICE_EXTENSION_2); EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("cpuVulkanInUse = 1")); EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("falsePrerotation = 1")); EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("gles1InUse = 1")); + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("createdGlesContext = 1")); + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("createdVulkanDevice = 1")); + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("createdVulkanSwapchain = 1")); + std::stringstream expectedResult; + expectedResult << "vulkanApiVersion = 0x" << std::hex << VULKAN_API_VERSION; + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); + expectedResult.str(""); + expectedResult << "vulkanDeviceFeaturesEnabled = 0x" << std::hex << VULKAN_FEATURES_MASK; + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); + expectedResult.str(""); + expectedResult << "vulkanInstanceExtensions: 0x" << std::hex << VULKAN_INSTANCE_EXTENSION_1 + << " 0x" << std::hex << VULKAN_INSTANCE_EXTENSION_2; + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); + expectedResult.str(""); + expectedResult << "vulkanDeviceExtensions: 0x" << std::hex << VULKAN_DEVICE_EXTENSION_1 + << " 0x" << std::hex << VULKAN_DEVICE_EXTENSION_2; + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); } // Verify we always have the most recently used apps in mAppStats, even when we fill it. @@ -260,11 +326,52 @@ TEST_F(GpuStatsTest, canInsertMoreThanMaxNumAppRecords) { GpuStatsInfo::Stats::FALSE_PREROTATION, 0); mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, GpuStatsInfo::Stats::GLES_1_IN_USE, 0); + mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_GLES_CONTEXT, 0); + mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_VULKAN_API_VERSION, + VULKAN_API_VERSION); + mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_VULKAN_DEVICE, 0); + mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::CREATED_VULKAN_SWAPCHAIN, 0); + mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_DEVICE_FEATURES_ENABLED, + VULKAN_FEATURES_MASK); + mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_INSTANCE_EXTENSION, + VULKAN_INSTANCE_EXTENSION_1); + mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_INSTANCE_EXTENSION, + VULKAN_INSTANCE_EXTENSION_2); + mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION, + VULKAN_DEVICE_EXTENSION_1); + mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, + GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION, + VULKAN_DEVICE_EXTENSION_2); EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(fullPkgName.c_str())); EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("cpuVulkanInUse = 1")); EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("falsePrerotation = 1")); EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("gles1InUse = 1")); + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("createdGlesContext = 1")); + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("createdVulkanDevice = 1")); + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("createdVulkanSwapchain = 1")); + std::stringstream expectedResult; + expectedResult << "vulkanApiVersion = 0x" << std::hex << VULKAN_API_VERSION; + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); + expectedResult.str(""); + expectedResult << "vulkanDeviceFeaturesEnabled = 0x" << std::hex << VULKAN_FEATURES_MASK; + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); + expectedResult.str(""); + expectedResult << "vulkanInstanceExtensions: 0x" << std::hex << VULKAN_INSTANCE_EXTENSION_1 + << " 0x" << std::hex << VULKAN_INSTANCE_EXTENSION_2; + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); + expectedResult.str(""); + expectedResult << "vulkanDeviceExtensions: 0x" << std::hex << VULKAN_DEVICE_EXTENSION_1 + << " 0x" << std::hex << VULKAN_DEVICE_EXTENSION_2; + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); } // mAppStats purges GpuStats::APP_RECORD_HEADROOM apps removed everytime it's filled up. diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp index 766451824a..15b637cf17 100644 --- a/vulkan/libvulkan/driver.cpp +++ b/vulkan/libvulkan/driver.cpp @@ -1178,6 +1178,27 @@ VkResult CreateInstance(const VkInstanceCreateInfo* pCreateInfo, return VK_ERROR_INCOMPATIBLE_DRIVER; } + // TODO(b/259516419) avoid getting stats from hwui + // const bool reportStats = (pCreateInfo->pApplicationInfo == nullptr ) + // || (strcmp("android framework", + // pCreateInfo->pApplicationInfo->pEngineName) != 0); + const bool reportStats = true; + if (reportStats) { + // Set stats for Vulkan api version requested with application info + if (pCreateInfo->pApplicationInfo) { + const uint32_t vulkanApiVersion = + pCreateInfo->pApplicationInfo->apiVersion; + android::GraphicsEnv::getInstance().setTargetStats( + android::GpuStatsInfo::Stats::CREATED_VULKAN_API_VERSION, + vulkanApiVersion); + } + + // Update stats for the extensions requested + android::GraphicsEnv::getInstance().setVulkanInstanceExtensions( + pCreateInfo->enabledExtensionCount, + pCreateInfo->ppEnabledExtensionNames); + } + *pInstance = instance; return VK_SUCCESS; @@ -1280,6 +1301,65 @@ VkResult CreateDevice(VkPhysicalDevice physicalDevice, *pDevice = dev; + // TODO(b/259516419) avoid getting stats from hwui + const bool reportStats = true; + if (reportStats) { + android::GraphicsEnv::getInstance().setTargetStats( + android::GpuStatsInfo::Stats::CREATED_VULKAN_DEVICE); + + // Set stats for creating a Vulkan device and report features in use + const VkPhysicalDeviceFeatures* pEnabledFeatures = + pCreateInfo->pEnabledFeatures; + if (!pEnabledFeatures) { + // Use features from the chained VkPhysicalDeviceFeatures2 + // structure, if given + const VkPhysicalDeviceFeatures2* features2 = + reinterpret_cast( + pCreateInfo->pNext); + while (features2 && + features2->sType != + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2) { + features2 = reinterpret_cast( + features2->pNext); + } + if (features2) { + pEnabledFeatures = &features2->features; + } + } + const VkBool32* pFeatures = + reinterpret_cast(pEnabledFeatures); + if (pFeatures) { + // VkPhysicalDeviceFeatures consists of VkBool32 values, go over all + // of them using pointer arithmetic here and save the features in a + // 64-bit bitfield + static_assert( + (sizeof(VkPhysicalDeviceFeatures) / sizeof(VkBool32)) <= 64, + "VkPhysicalDeviceFeatures has too many elements for bitfield " + "packing"); + static_assert( + (sizeof(VkPhysicalDeviceFeatures) % sizeof(VkBool32)) == 0, + "VkPhysicalDeviceFeatures has invalid size for bitfield " + "packing"); + const int numFeatures = + sizeof(VkPhysicalDeviceFeatures) / sizeof(VkBool32); + + uint64_t enableFeatureBits = 0; + for (int i = 0; i < numFeatures; i++) { + if (pFeatures[i] != VK_FALSE) { + enableFeatureBits |= (uint64_t(1) << i); + } + } + android::GraphicsEnv::getInstance().setTargetStats( + android::GpuStatsInfo::Stats::VULKAN_DEVICE_FEATURES_ENABLED, + enableFeatureBits); + } + + // Update stats for the extensions requested + android::GraphicsEnv::getInstance().setVulkanDeviceExtensions( + pCreateInfo->enabledExtensionCount, + pCreateInfo->ppEnabledExtensionNames); + } + return VK_SUCCESS; } diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp index 87b3a89cce..07b6dce26e 100644 --- a/vulkan/libvulkan/swapchain.cpp +++ b/vulkan/libvulkan/swapchain.cpp @@ -1533,6 +1533,10 @@ VkResult CreateSwapchainKHR(VkDevice device, android::GpuStatsInfo::Stats::FALSE_PREROTATION); } + // Set stats for creating a Vulkan swapchain + android::GraphicsEnv::getInstance().setTargetStats( + android::GpuStatsInfo::Stats::CREATED_VULKAN_SWAPCHAIN); + surface.swapchain_handle = HandleFromSwapchain(swapchain); *swapchain_handle = surface.swapchain_handle; return VK_SUCCESS; -- GitLab From 3812533636fe4d5327b99fb51198444912ab0e9f Mon Sep 17 00:00:00 2001 From: Nick Deakin Date: Mon, 12 Dec 2022 15:48:24 -0500 Subject: [PATCH 0601/1310] A couple recoverymap fixes. Fix decoding of P010 narrow range code points. Also fix scaling for input to OETF during application of the map. NOTE: tests pass, although there is still an expected failure for encoding against just P010 input (API-0), sine toneMap() isn't implemented yet. Bug: 252835416, 261479255 Test: libjpegrecoverymap_test passes Change-Id: Ibfd036c6c0cd55de7c5886e32fff69f461cf45d9 --- libs/jpegrecoverymap/recoverymap.cpp | 2 +- libs/jpegrecoverymap/recoverymapmath.cpp | 6 +++--- libs/jpegrecoverymap/tests/recoverymapmath_test.cpp | 7 +++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index c9ac92128f..74bb51266f 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -562,7 +562,7 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y); Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata->rangeScalingFactor); - Color rgb_gamma_hdr = hdrOetf(rgb_hdr); + Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->rangeScalingFactor); uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr); size_t pixel_idx = x + y * width; diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp index e838f43b58..9ed2949509 100644 --- a/libs/jpegrecoverymap/recoverymapmath.cpp +++ b/libs/jpegrecoverymap/recoverymapmath.cpp @@ -336,9 +336,9 @@ Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) { >> 6; // Conversions include taking narrow-range into account. - return {{{ static_cast(y_uint) / 940.0f, - (static_cast(u_uint) - 64.0f) / 940.0f - 0.5f, - (static_cast(v_uint) - 64.0f) / 940.0f - 0.5f }}}; + return {{{ (static_cast(y_uint) - 64.0f) / 876.0f, + (static_cast(u_uint) - 64.0f) / 896.0f - 0.5f, + (static_cast(v_uint) - 64.0f) / 896.0f - 0.5f }}}; } typedef Color (*getPixelFn)(jr_uncompressed_ptr, size_t, size_t); diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp index 169201c73b..f8dd49025e 100644 --- a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp +++ b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp @@ -36,9 +36,9 @@ public: } Color P010(uint16_t y, uint16_t u, uint16_t v) { - return {{{ static_cast(y) / 940.0f, - (static_cast(u) - 64.0f) / 940.0f - 0.5f, - (static_cast(v) - 64.0f) / 940.0f - 0.5f }}}; + return {{{ (static_cast(y) - 64.0f) / 876.0f, + (static_cast(u) - 64.0f) / 896.0f - 0.5f, + (static_cast(v) - 64.0f) / 896.0f - 0.5f }}}; } float Map(uint8_t e) { @@ -821,7 +821,6 @@ TEST_F(RecoveryMapMathTest, GenerateMapLuminancePq) { bt2100Luminance(RgbBlue()) * kPqMaxNits, LuminanceEpsilon()); } -//Color Recover(Color yuv_gamma, float recovery, float range_scaling_factor) { TEST_F(RecoveryMapMathTest, ApplyMap) { EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, 8.0f), RgbWhite() * 8.0f); -- GitLab From dc8452b8035de7a0573d19fb7c90d3abf685b6b1 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Wed, 23 Nov 2022 17:17:56 +0000 Subject: [PATCH 0602/1310] Update XMP metadata Bug: b/252835416 Change-Id: Iced2c5bf6b5e2dfd7139c43fad6b0bca0d1b2d1c Test: build, print generated xmp --- .../include/jpegrecoverymap/recoverymap.h | 46 +----- .../jpegrecoverymap/recoverymaputils.h | 50 ++++++- libs/jpegrecoverymap/recoverymap.cpp | 71 +-------- libs/jpegrecoverymap/recoverymaputils.cpp | 138 +++++++++++++++++- .../tests/recoverymap_test.cpp | 14 ++ 5 files changed, 207 insertions(+), 112 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h index 74f9776be6..fca78a0edb 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -30,8 +30,9 @@ typedef enum { // Transfer functions as defined for XMP metadata typedef enum { - JPEGR_TF_HLG = 0, - JPEGR_TF_PQ = 1, + JPEGR_TF_LINEAR = 0, + JPEGR_TF_HLG = 1, + JPEGR_TF_PQ = 2, } jpegr_transfer_function; struct jpegr_info_struct { @@ -343,47 +344,6 @@ private: jr_metadata_ptr metadata, jr_compressed_ptr dest); - /* - * This method generates XMP metadata. - * - * below is an example of the XMP metadata that this function generates where - * secondary_image_length = 1000 - * range_scaling_factor = 1.25 - * - * - * - * - * 1 - * 1.25 - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * @param secondary_image_length length of secondary image - * @param metadata JPEG/R metadata to encode as XMP - * @return XMP metadata in type of string - */ - std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata); - /* * This method will tone map a HDR image to an SDR image. * diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h index e35f2d72cb..e61d0c4cce 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h @@ -17,10 +17,11 @@ #ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H #define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H +#include #include +#include #include - namespace android::recoverymap { struct jpegr_metadata; @@ -35,6 +36,53 @@ struct jpegr_metadata; */ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata); +/* + * This method generates XMP metadata. + * + * below is an example of the XMP metadata that this function generates where + * secondary_image_length = 1000 + * range_scaling_factor = 1.25 + * + * + * + * + * 1 + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @param secondary_image_length length of secondary image + * @param metadata JPEG/R metadata to encode as XMP + * @return XMP metadata in type of string + */ +std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata); } #endif //ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H \ No newline at end of file diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index c9ac92128f..3d713cef80 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -21,7 +21,6 @@ #include #include -#include #include #include #include @@ -64,19 +63,6 @@ static const st2086_metadata kSt2086Metadata = { 1.0f, }; -/* - * Helper function used for generating XMP metadata. - * - * @param prefix The prefix part of the name. - * @param suffix The suffix part of the name. - * @return A name of the form "prefix:suffix". - */ -string Name(const string &prefix, const string &suffix) { - std::stringstream ss; - ss << prefix << ":" << suffix; - return ss.str(); -} - /* * Helper function used for writing data to destination. * @@ -447,6 +433,9 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 ColorTransformFn hdrInvOetf = nullptr; float hdr_white_nits = 0.0f; switch (metadata->transferFunction) { + case JPEGR_TF_LINEAR: + hdrInvOetf = identityConversion; + break; case JPEGR_TF_HLG: hdrInvOetf = hlgInvOetf; hdr_white_nits = kHlgMaxNits; @@ -544,6 +533,9 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ ColorTransformFn hdrOetf = nullptr; switch (metadata->transferFunction) { + case JPEGR_TF_LINEAR: + hdrOetf = identityConversion; + break; case JPEGR_TF_HLG: hdrOetf = hlgOetf; break; @@ -680,57 +672,6 @@ status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, return NO_ERROR; } -string RecoveryMap::generateXmp(int secondary_image_length, jpegr_metadata& metadata) { - const string kContainerPrefix = "GContainer"; - const string kContainerUri = "http://ns.google.com/photos/1.0/container/"; - const string kItemPrefix = "Item"; - const string kRecoveryMap = "RecoveryMap"; - const string kDirectory = "Directory"; - const string kImageJpeg = "image/jpeg"; - const string kItem = "Item"; - const string kLength = "Length"; - const string kMime = "Mime"; - const string kPrimary = "Primary"; - const string kSemantic = "Semantic"; - const string kVersion = "Version"; - - const string kConDir = Name(kContainerPrefix, kDirectory); - const string kContainerItem = Name(kContainerPrefix, kItem); - const string kItemLength = Name(kItemPrefix, kLength); - const string kItemMime = Name(kItemPrefix, kMime); - const string kItemSemantic = Name(kItemPrefix, kSemantic); - - const vector kConDirSeq({kConDir, string("rdf:Seq")}); - const vector kLiItem({string("rdf:li"), kContainerItem}); - - std::stringstream ss; - photos_editing_formats::image_io::XmlWriter writer(ss); - writer.StartWritingElement("x:xmpmeta"); - writer.WriteXmlns("x", "adobe:ns:meta/"); - writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2"); - writer.StartWritingElement("rdf:RDF"); - writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); - writer.StartWritingElement("rdf:Description"); - writer.WriteXmlns(kContainerPrefix, kContainerUri); - writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), metadata.version); - writer.WriteElementAndContent(Name(kContainerPrefix, "rangeScalingFactor"), - metadata.rangeScalingFactor); - // TODO: determine structure for hdr10 metadata - // TODO: write rest of metadata - writer.StartWritingElements(kConDirSeq); - size_t item_depth = writer.StartWritingElements(kLiItem); - writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary); - writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg); - writer.FinishWritingElementsToDepth(item_depth); - writer.StartWritingElements(kLiItem); - writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap); - writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg); - writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length); - writer.FinishWriting(); - - return ss.str(); -} - status_t RecoveryMap::toneMap(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr dest) { if (uncompressed_p010_image == nullptr || dest == nullptr) { diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/recoverymaputils.cpp index fe46cbad91..537f86fdc0 100644 --- a/libs/jpegrecoverymap/recoverymaputils.cpp +++ b/libs/jpegrecoverymap/recoverymaputils.cpp @@ -17,19 +17,29 @@ #include #include #include +#include #include #include #include #include -#include -#include - using namespace photos_editing_formats::image_io; using namespace std; namespace android::recoverymap { +/* + * Helper function used for generating XMP metadata. + * + * @param prefix The prefix part of the name. + * @param suffix The suffix part of the name. + * @return A name of the form "prefix:suffix". + */ +string Name(const string &prefix, const string &suffix) { + std::stringstream ss; + ss << prefix << ":" << suffix; + return ss.str(); +} // Extremely simple XML Handler - just searches for interesting elements class XMPXmlHandler : public XmlHandler { @@ -104,6 +114,36 @@ private: const string XMPXmlHandler::rangeScalingFactorName = "GContainer:rangeScalingFactor"; +const string kContainerPrefix = "GContainer"; +const string kContainerUri = "http://ns.google.com/photos/1.0/container/"; +const string kRecoveryMapUri = "http://ns.google.com/photos/1.0/recoverymap/"; +const string kItemPrefix = "Item"; +const string kRecoveryMap = "RecoveryMap"; +const string kDirectory = "Directory"; +const string kImageJpeg = "image/jpeg"; +const string kItem = "Item"; +const string kLength = "Length"; +const string kMime = "Mime"; +const string kPrimary = "Primary"; +const string kSemantic = "Semantic"; +const string kVersion = "Version"; +const string kHdr10Metadata = "HDR10Metadata"; +const string kSt2086Metadata = "ST2086Metadata"; +const string kSt2086Coordinate = "ST2086Coordinate"; +const string kSt2086CoordinateX = "ST2086CoordinateX"; +const string kSt2086CoordinateY = "ST2086CoordinateY"; +const string kSt2086Primary = "ST2086Primary"; +const int kSt2086PrimaryRed = 0; +const int kSt2086PrimaryGreen = 1; +const int kSt2086PrimaryBlue = 2; +const int kSt2086PrimaryWhite = 3; +const int kGContainerVersion = 1; + +const string kConDir = Name(kContainerPrefix, kDirectory); +const string kContainerItem = Name(kContainerPrefix, kItem); +const string kItemLength = Name(kItemPrefix, kLength); +const string kItemMime = Name(kItemPrefix, kMime); +const string kItemSemantic = Name(kItemPrefix, kSemantic); bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) { string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; @@ -150,4 +190,96 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta return true; } +string generateXmp(int secondary_image_length, jpegr_metadata& metadata) { + const vector kConDirSeq({kConDir, string("rdf:Seq")}); + const vector kLiItem({string("rdf:li"), kContainerItem}); + + std::stringstream ss; + photos_editing_formats::image_io::XmlWriter writer(ss); + writer.StartWritingElement("x:xmpmeta"); + writer.WriteXmlns("x", "adobe:ns:meta/"); + writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2"); + writer.StartWritingElement("rdf:RDF"); + writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); + writer.StartWritingElement("rdf:Description"); + writer.WriteXmlns(kContainerPrefix, kContainerUri); + writer.WriteXmlns(kRecoveryMap, kRecoveryMapUri); + writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), kGContainerVersion); + writer.StartWritingElements(kConDirSeq); + size_t item_depth = writer.StartWritingElements(kLiItem); + writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary); + writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg); + writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kVersion), metadata.version); + writer.WriteAttributeNameAndValue( + Name(kRecoveryMap, "RangeScalingFactor"), metadata.rangeScalingFactor); + writer.WriteAttributeNameAndValue( + Name(kRecoveryMap, "TransferFunction"), metadata.transferFunction); + if (metadata.transferFunction == JPEGR_TF_PQ) { + writer.StartWritingElement(Name(kRecoveryMap, kHdr10Metadata)); + writer.WriteAttributeNameAndValue( + Name(kRecoveryMap, "HDR10MaxFALL"), metadata.hdr10Metadata.maxFALL); + writer.WriteAttributeNameAndValue( + Name(kRecoveryMap, "HDR10MaxCLL"), metadata.hdr10Metadata.maxCLL); + writer.StartWritingElement(Name(kRecoveryMap, kSt2086Metadata)); + writer.WriteAttributeNameAndValue( + Name(kRecoveryMap, "ST2086MaxLuminance"), + metadata.hdr10Metadata.st2086Metadata.maxLuminance); + writer.WriteAttributeNameAndValue( + Name(kRecoveryMap, "ST2086MinLuminance"), + metadata.hdr10Metadata.st2086Metadata.minLuminance); + + // red + writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate)); + writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryRed); + writer.WriteAttributeNameAndValue( + Name(kRecoveryMap, kSt2086CoordinateX), + metadata.hdr10Metadata.st2086Metadata.redPrimary.x); + writer.WriteAttributeNameAndValue( + Name(kRecoveryMap, kSt2086CoordinateY), + metadata.hdr10Metadata.st2086Metadata.redPrimary.y); + writer.FinishWritingElement(); + + // green + writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate)); + writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryGreen); + writer.WriteAttributeNameAndValue( + Name(kRecoveryMap, kSt2086CoordinateX), + metadata.hdr10Metadata.st2086Metadata.greenPrimary.x); + writer.WriteAttributeNameAndValue( + Name(kRecoveryMap, kSt2086CoordinateY), + metadata.hdr10Metadata.st2086Metadata.greenPrimary.y); + writer.FinishWritingElement(); + + // blue + writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate)); + writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryBlue); + writer.WriteAttributeNameAndValue( + Name(kRecoveryMap, kSt2086CoordinateX), + metadata.hdr10Metadata.st2086Metadata.bluePrimary.x); + writer.WriteAttributeNameAndValue( + Name(kRecoveryMap, kSt2086CoordinateY), + metadata.hdr10Metadata.st2086Metadata.bluePrimary.y); + writer.FinishWritingElement(); + + // white + writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate)); + writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryWhite); + writer.WriteAttributeNameAndValue( + Name(kRecoveryMap, kSt2086CoordinateX), + metadata.hdr10Metadata.st2086Metadata.whitePoint.x); + writer.WriteAttributeNameAndValue( + Name(kRecoveryMap, kSt2086CoordinateY), + metadata.hdr10Metadata.st2086Metadata.whitePoint.y); + writer.FinishWritingElement(); + } + writer.FinishWritingElementsToDepth(item_depth); + writer.StartWritingElements(kLiItem); + writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap); + writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg); + writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length); + writer.FinishWriting(); + + return ss.str(); +} + } // namespace android::recoverymap \ No newline at end of file diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp index 0f96723189..6dea27f0a8 100644 --- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp +++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp @@ -15,6 +15,7 @@ */ #include +#include #include #include #include @@ -94,6 +95,19 @@ TEST_F(RecoveryMapTest, build) { recovery_map.decodeJPEGR(nullptr, nullptr, nullptr, false); } +TEST_F(RecoveryMapTest, writeXmpThenRead) { + jpegr_metadata metadata_expected; + metadata_expected.transferFunction = JPEGR_TF_HLG; + metadata_expected.rangeScalingFactor = 1.25; + int length_expected = 1000; + std::string xmp = generateXmp(1000, metadata_expected); + + jpegr_metadata metadata_read; + EXPECT_TRUE(getMetadataFromXMP(reinterpret_cast(xmp[0]), xmp.size(), &metadata_read)); + ASSERT_EQ(metadata_expected.transferFunction, metadata_read.transferFunction); + ASSERT_EQ(metadata_expected.rangeScalingFactor, metadata_read.rangeScalingFactor); + +} TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) { int ret; -- GitLab From e85e16ee39e40cb206724e74f15360452c0cb995 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Mon, 12 Dec 2022 12:51:18 -0500 Subject: [PATCH 0603/1310] Add the displayId to AidlComposer traces We already have traces for HwcPresentDisplay, HwcValidateDisplay, and HwcPresentOrValidateDisplay. Add the displayId so we can tell which display is being presented/validated. Bug: 241285473 Bug: 232888251 Test: manual (look at perfetto traces) Change-Id: Ib257efb929919411c13607dc59707ce3616d253f --- .../DisplayHardware/AidlComposerHal.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp index 800e36db48..806df871f6 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -578,13 +579,15 @@ Error AidlComposer::getReleaseFences(Display display, std::vector* outLay } Error AidlComposer::presentDisplay(Display display, int* outPresentFence) { - ATRACE_NAME("HwcPresentDisplay"); + const auto displayId = translate(display); + ATRACE_FORMAT("HwcPresentDisplay %" PRId64, displayId); + Error error = Error::NONE; mMutex.lock_shared(); auto writer = getWriter(display); auto reader = getReader(display); if (writer && reader) { - writer->get().presentDisplay(translate(display)); + writer->get().presentDisplay(displayId); error = execute(display); } else { error = Error::BAD_DISPLAY; @@ -595,7 +598,7 @@ Error AidlComposer::presentDisplay(Display display, int* outPresentFence) { return error; } - auto fence = reader->get().takePresentFence(translate(display)); + auto fence = reader->get().takePresentFence(displayId); mMutex.unlock_shared(); // take ownership *outPresentFence = fence.get(); @@ -707,8 +710,9 @@ Error AidlComposer::setClientTargetSlotCount(Display display) { Error AidlComposer::validateDisplay(Display display, nsecs_t expectedPresentTime, uint32_t* outNumTypes, uint32_t* outNumRequests) { - ATRACE_NAME("HwcValidateDisplay"); const auto displayId = translate(display); + ATRACE_FORMAT("HwcValidateDisplay %" PRId64, displayId); + Error error = Error::NONE; mMutex.lock_shared(); auto writer = getWriter(display); @@ -734,8 +738,9 @@ Error AidlComposer::validateDisplay(Display display, nsecs_t expectedPresentTime Error AidlComposer::presentOrValidateDisplay(Display display, nsecs_t expectedPresentTime, uint32_t* outNumTypes, uint32_t* outNumRequests, int* outPresentFence, uint32_t* state) { - ATRACE_NAME("HwcPresentOrValidateDisplay"); const auto displayId = translate(display); + ATRACE_FORMAT("HwcPresentOrValidateDisplay %" PRId64, displayId); + Error error = Error::NONE; mMutex.lock_shared(); auto writer = getWriter(display); -- GitLab From b255215a6fa06b333136b44465efe08cd238eee3 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Tue, 13 Dec 2022 17:18:09 +0000 Subject: [PATCH 0604/1310] Fix max touch count calculation in TouchpadInputMapper It seems I got confused between InputDeviceContext.hasKeyCode and hasScanCode. Bug: 251196347 Test: check that max_touch_cnt in the HardwareProperties is set correctly (e.g. with the tests from ag/20691427) Change-Id: Ief9144deb90360e1f12f35316b174a40de8c3db4 --- .../inputflinger/reader/mapper/TouchpadInputMapper.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index de6e4b0193..956a7aae54 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -28,11 +28,11 @@ namespace android { namespace { short getMaxTouchCount(const InputDeviceContext& context) { - if (context.hasKeyCode(BTN_TOOL_QUINTTAP)) return 5; - if (context.hasKeyCode(BTN_TOOL_QUADTAP)) return 4; - if (context.hasKeyCode(BTN_TOOL_TRIPLETAP)) return 3; - if (context.hasKeyCode(BTN_TOOL_DOUBLETAP)) return 2; - if (context.hasKeyCode(BTN_TOOL_FINGER)) return 1; + if (context.hasScanCode(BTN_TOOL_QUINTTAP)) return 5; + if (context.hasScanCode(BTN_TOOL_QUADTAP)) return 4; + if (context.hasScanCode(BTN_TOOL_TRIPLETAP)) return 3; + if (context.hasScanCode(BTN_TOOL_DOUBLETAP)) return 2; + if (context.hasScanCode(BTN_TOOL_FINGER)) return 1; return 0; } -- GitLab From 2d0a29dded24a09f11af4a060522bd2a3704a0dc Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Tue, 13 Dec 2022 14:49:48 -0500 Subject: [PATCH 0605/1310] SF: Disable flaky SetPowerModeInternalTest Bug: 262417075 Test: libsurfaceflinger_unittest Change-Id: I4a9a54037b4bf08a091d9c75fb8c45c1a7c8eb5e --- .../unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp index 0fb8e2bbaa..6e500593fa 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp @@ -484,7 +484,8 @@ TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOnToUnknownExternalDispla transitionDisplayCommon>(); } -TEST_F(SetPowerModeInternalTest, designatesLeaderDisplay) { +// TODO(b/262417075) +TEST_F(SetPowerModeInternalTest, DISABLED_designatesLeaderDisplay) { using Case = SimplePrimaryDisplayCase; // -------------------------------------------------------------------- -- GitLab From bf24157656926fc0b4a74f8188f0a300d75a4a3a Mon Sep 17 00:00:00 2001 From: Fyodor Kyslov Date: Tue, 13 Dec 2022 22:38:07 +0000 Subject: [PATCH 0606/1310] librecoverymap: Fix lifecycle of recovery map decoder This fixes the issue with lifecycle of recovery map decoder and makes decompressed data valid till the image is converted and copied to the destination Bug: b/252835416 Test: manual Change-Id: Ieaef889bdabe77997063ea50dc81699af3cfb2cd --- .../include/jpegrecoverymap/recoverymap.h | 10 ------ libs/jpegrecoverymap/recoverymap.cpp | 33 +++++++------------ 2 files changed, 12 insertions(+), 31 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h index fca78a0edb..50ccdff6f7 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -250,16 +250,6 @@ public: status_t getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info); private: - /* - * This method is called in the decoding pipeline. It will decode the recovery map. - * - * @param compressed_recovery_map compressed recovery map - * @param dest decoded recover map - * @return NO_ERROR if decoding succeeds, error code if error occurs. - */ - status_t decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map, - jr_uncompressed_ptr dest); - /* * This method is called in the encoding pipeline. It will encode the recovery map. * diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index 200ec99055..fafc319271 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -319,14 +319,24 @@ status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, jpegr_metadata metadata; JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map)); - jpegr_uncompressed_struct map; - JPEGR_CHECK(decompressRecoveryMap(&compressed_map, &map)); JpegDecoder jpeg_decoder; if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) { return ERROR_JPEGR_DECODE_ERROR; } + JpegDecoder recovery_map_decoder; + if (!recovery_map_decoder.decompressImage(compressed_map.data, + compressed_map.length)) { + return ERROR_JPEGR_DECODE_ERROR; + } + + jpegr_uncompressed_struct map; + map.data = recovery_map_decoder.getDecompressedImagePtr(); + map.width = recovery_map_decoder.getDecompressedImageWidth(); + map.height = recovery_map_decoder.getDecompressedImageHeight(); + + jpegr_uncompressed_struct uncompressed_yuv_420_image; uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr(); uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth(); @@ -349,25 +359,6 @@ status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, return NO_ERROR; } -status_t RecoveryMap::decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map, - jr_uncompressed_ptr dest) { - if (compressed_recovery_map == nullptr || dest == nullptr) { - return ERROR_JPEGR_INVALID_NULL_PTR; - } - - JpegDecoder jpeg_decoder; - if (!jpeg_decoder.decompressImage(compressed_recovery_map->data, - compressed_recovery_map->length)) { - return ERROR_JPEGR_DECODE_ERROR; - } - - dest->data = jpeg_decoder.getDecompressedImagePtr(); - dest->width = jpeg_decoder.getDecompressedImageWidth(); - dest->height = jpeg_decoder.getDecompressedImageHeight(); - - return NO_ERROR; -} - status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, jr_compressed_ptr dest) { if (uncompressed_recovery_map == nullptr || dest == nullptr) { -- GitLab From 0ff95c9b2f9d85cac2015aa4de5731c6d5e1a060 Mon Sep 17 00:00:00 2001 From: John Reck Date: Thu, 8 Dec 2022 11:45:29 -0500 Subject: [PATCH 0607/1310] Add IMapper 5 implementation Fixes: 205761028 Test: boots on CF w/ mapper4 removed Change-Id: I062ba3160fae972757669241fedcaf6ac3c6c12b --- libs/ui/Android.bp | 5 +- libs/ui/Gralloc2.cpp | 2 +- libs/ui/Gralloc3.cpp | 2 +- libs/ui/Gralloc4.cpp | 5 +- libs/ui/Gralloc5.cpp | 903 +++++++++++++++++++++++ libs/ui/GraphicBufferAllocator.cpp | 37 +- libs/ui/GraphicBufferMapper.cpp | 23 +- libs/ui/include/ui/Gralloc.h | 10 +- libs/ui/include/ui/Gralloc2.h | 4 +- libs/ui/include/ui/Gralloc3.h | 4 +- libs/ui/include/ui/Gralloc4.h | 4 +- libs/ui/include/ui/Gralloc5.h | 238 ++++++ libs/ui/include/ui/GraphicBufferMapper.h | 12 +- 13 files changed, 1203 insertions(+), 46 deletions(-) create mode 100644 libs/ui/Gralloc5.cpp create mode 100644 libs/ui/include/ui/Gralloc5.h diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp index d33dd34d4e..ec0ab4e464 100644 --- a/libs/ui/Android.bp +++ b/libs/ui/Android.bp @@ -48,7 +48,6 @@ cc_defaults { integer_overflow: true, misc_undefined: ["bounds"], }, - } cc_library_static { @@ -135,6 +134,7 @@ cc_library_shared { "Gralloc2.cpp", "Gralloc3.cpp", "Gralloc4.cpp", + "Gralloc5.cpp", "GraphicBuffer.cpp", "GraphicBufferAllocator.cpp", "GraphicBufferMapper.cpp", @@ -176,6 +176,7 @@ cc_library_shared { "libsync", "libutils", "liblog", + "libvndksupport", ], export_shared_lib_headers: [ @@ -214,6 +215,8 @@ cc_library_shared { "libnativewindow_headers", "libhardware_headers", "libui_headers", + "libimapper_stablec", + "libimapper_providerutils", ], export_static_lib_headers: [ diff --git a/libs/ui/Gralloc2.cpp b/libs/ui/Gralloc2.cpp index f23f10a1a9..e9b5decee8 100644 --- a/libs/ui/Gralloc2.cpp +++ b/libs/ui/Gralloc2.cpp @@ -161,7 +161,7 @@ status_t Gralloc2Mapper::createDescriptor(void* bufferDescriptorInfo, return static_cast((ret.isOk()) ? error : kTransactionError); } -status_t Gralloc2Mapper::importBuffer(const hardware::hidl_handle& rawHandle, +status_t Gralloc2Mapper::importBuffer(const native_handle_t* rawHandle, buffer_handle_t* outBufferHandle) const { Error error; auto ret = mMapper->importBuffer(rawHandle, diff --git a/libs/ui/Gralloc3.cpp b/libs/ui/Gralloc3.cpp index 15c60bcadf..474d381dbb 100644 --- a/libs/ui/Gralloc3.cpp +++ b/libs/ui/Gralloc3.cpp @@ -138,7 +138,7 @@ status_t Gralloc3Mapper::createDescriptor(void* bufferDescriptorInfo, return static_cast((ret.isOk()) ? error : kTransactionError); } -status_t Gralloc3Mapper::importBuffer(const hardware::hidl_handle& rawHandle, +status_t Gralloc3Mapper::importBuffer(const native_handle_t* rawHandle, buffer_handle_t* outBufferHandle) const { Error error; auto ret = mMapper->importBuffer(rawHandle, [&](const auto& tmpError, const auto& tmpBuffer) { diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp index f6ab7b2a5e..7459466d31 100644 --- a/libs/ui/Gralloc4.cpp +++ b/libs/ui/Gralloc4.cpp @@ -196,7 +196,7 @@ status_t Gralloc4Mapper::createDescriptor(void* bufferDescriptorInfo, return static_cast((ret.isOk()) ? error : kTransactionError); } -status_t Gralloc4Mapper::importBuffer(const hardware::hidl_handle& rawHandle, +status_t Gralloc4Mapper::importBuffer(const native_handle_t* rawHandle, buffer_handle_t* outBufferHandle) const { Error error; auto ret = mMapper->importBuffer(rawHandle, [&](const auto& tmpError, const auto& tmpBuffer) { @@ -1233,7 +1233,10 @@ status_t Gralloc4Allocator::allocate(std::string requestorName, uint32_t width, if (mAidlAllocator) { AllocationResult result; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" auto status = mAidlAllocator->allocate(descriptor, bufferCount, &result); +#pragma clang diagnostic pop // deprecation if (!status.isOk()) { error = status.getExceptionCode(); if (error == EX_SERVICE_SPECIFIC) { diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp new file mode 100644 index 0000000000..6f196b86d5 --- /dev/null +++ b/libs/ui/Gralloc5.cpp @@ -0,0 +1,903 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "Gralloc5" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace aidl::android::hardware::graphics::allocator; +using namespace aidl::android::hardware::graphics::common; +using namespace ::android::hardware::graphics::mapper; + +namespace android { + +static const auto kIAllocatorServiceName = IAllocator::descriptor + std::string("/default"); +static const auto kIAllocatorMinimumVersion = 2; + +// TODO(b/72323293, b/72703005): Remove these invalid bits from callers +static constexpr uint64_t kRemovedUsageBits = static_cast((1 << 10) | (1 << 13)); + +typedef AIMapper_Error (*AIMapper_loadIMapperFn)(AIMapper *_Nullable *_Nonnull outImplementation); + +struct Gralloc5 { + std::shared_ptr allocator; + AIMapper *mapper = nullptr; +}; + +static std::shared_ptr waitForAllocator() { + if (__builtin_available(android 31, *)) { + if (!AServiceManager_isDeclared(kIAllocatorServiceName.c_str())) { + return nullptr; + } + auto allocator = IAllocator::fromBinder( + ndk::SpAIBinder(AServiceManager_waitForService(kIAllocatorServiceName.c_str()))); + if (!allocator) { + ALOGE("AIDL IAllocator declared but failed to get service"); + return nullptr; + } + + int32_t version = 0; + if (!allocator->getInterfaceVersion(&version).isOk()) { + ALOGE("Failed to query interface version"); + return nullptr; + } + if (version < kIAllocatorMinimumVersion) { + return nullptr; + } + return allocator; + } else { + // TODO: LOG_ALWAYS_FATAL("libui is not backwards compatible"); + return nullptr; + } +} + +static void *loadIMapperLibrary() { + static void *imapperLibrary = []() -> void * { + auto allocator = waitForAllocator(); + std::string mapperSuffix; + auto status = allocator->getIMapperLibrarySuffix(&mapperSuffix); + if (!status.isOk()) { + ALOGE("Failed to get IMapper library suffix"); + return nullptr; + } + + std::string lib_name = "mapper." + mapperSuffix + ".so"; + void *so = android_load_sphal_library(lib_name.c_str(), RTLD_LOCAL | RTLD_NOW); + if (!so) { + ALOGE("Failed to load %s", lib_name.c_str()); + } + return so; + }(); + return imapperLibrary; +} + +static const Gralloc5 &getInstance() { + static Gralloc5 instance = []() { + auto allocator = waitForAllocator(); + if (!allocator) { + return Gralloc5{}; + } + void *so = loadIMapperLibrary(); + if (!so) { + return Gralloc5{}; + } + auto loadIMapper = (AIMapper_loadIMapperFn)dlsym(so, "AIMapper_loadIMapper"); + AIMapper *mapper = nullptr; + AIMapper_Error error = loadIMapper(&mapper); + if (error != AIMAPPER_ERROR_NONE) { + ALOGE("AIMapper_loadIMapper failed %d", error); + return Gralloc5{}; + } + return Gralloc5{std::move(allocator), mapper}; + }(); + return instance; +} + +template +static auto getStandardMetadata(AIMapper *mapper, buffer_handle_t bufferHandle) + -> decltype(StandardMetadata::value::decode(nullptr, 0)) { + using Value = typename StandardMetadata::value; + // TODO: Tune for common-case better + FatVector buffer; + int32_t sizeRequired = mapper->v5.getStandardMetadata(bufferHandle, static_cast(T), + buffer.data(), buffer.size()); + if (sizeRequired < 0) { + ALOGW_IF(-AIMAPPER_ERROR_UNSUPPORTED != sizeRequired, + "Unexpected error %d from valid getStandardMetadata call", -sizeRequired); + return std::nullopt; + } + if ((size_t)sizeRequired > buffer.size()) { + buffer.resize(sizeRequired); + sizeRequired = mapper->v5.getStandardMetadata(bufferHandle, static_cast(T), + buffer.data(), buffer.size()); + } + if (sizeRequired < 0 || (size_t)sizeRequired > buffer.size()) { + ALOGW("getStandardMetadata failed, received %d with buffer size %zd", sizeRequired, + buffer.size()); + // Generate a fail type + return std::nullopt; + } + return Value::decode(buffer.data(), sizeRequired); +} + +template +static AIMapper_Error setStandardMetadata(AIMapper *mapper, buffer_handle_t bufferHandle, + const typename StandardMetadata::value_type &value) { + using Value = typename StandardMetadata::value; + int32_t sizeRequired = Value::encode(value, nullptr, 0); + if (sizeRequired < 0) { + ALOGW("Failed to calculate required size"); + return static_cast(-sizeRequired); + } + FatVector buffer; + buffer.resize(sizeRequired); + sizeRequired = Value::encode(value, buffer.data(), buffer.size()); + if (sizeRequired < 0 || (size_t)sizeRequired > buffer.size()) { + ALOGW("Failed to encode with calculated size %d; buffer size %zd", sizeRequired, + buffer.size()); + return static_cast(-sizeRequired); + } + return mapper->v5.setStandardMetadata(bufferHandle, static_cast(T), buffer.data(), + sizeRequired); +} + +Gralloc5Allocator::Gralloc5Allocator(const Gralloc5Mapper &mapper) : mMapper(mapper) { + mAllocator = getInstance().allocator; +} + +bool Gralloc5Allocator::isLoaded() const { + return mAllocator != nullptr; +} + +static uint64_t getValidUsageBits() { + static const uint64_t validUsageBits = []() -> uint64_t { + uint64_t bits = 0; + for (const auto bit : ndk::enum_range{}) { + bits |= static_cast(bit); + } + return bits; + }(); + return validUsageBits | kRemovedUsageBits; +} + +static std::optional makeDescriptor(std::string requestorName, uint32_t width, + uint32_t height, PixelFormat format, + uint32_t layerCount, uint64_t usage) { + uint64_t validUsageBits = getValidUsageBits(); + if (usage & ~validUsageBits) { + ALOGE("buffer descriptor contains invalid usage bits 0x%" PRIx64, usage & ~validUsageBits); + return std::nullopt; + } + + BufferDescriptorInfo descriptorInfo{ + .width = static_cast(width), + .height = static_cast(height), + .layerCount = static_cast(layerCount), + .format = static_cast<::aidl::android::hardware::graphics::common::PixelFormat>(format), + .usage = static_cast(usage), + }; + auto nameLength = std::min(requestorName.length(), descriptorInfo.name.size() - 1); + memcpy(descriptorInfo.name.data(), requestorName.data(), nameLength); + requestorName.data()[nameLength] = 0; + return descriptorInfo; +} + +std::string Gralloc5Allocator::dumpDebugInfo(bool less) const { + return mMapper.dumpBuffers(less); +} + +status_t Gralloc5Allocator::allocate(std::string requestorName, uint32_t width, uint32_t height, + android::PixelFormat format, uint32_t layerCount, + uint64_t usage, uint32_t bufferCount, uint32_t *outStride, + buffer_handle_t *outBufferHandles, bool importBuffers) const { + auto descriptorInfo = makeDescriptor(requestorName, width, height, format, layerCount, usage); + if (!descriptorInfo) { + return BAD_VALUE; + } + + AllocationResult result; + auto status = mAllocator->allocate2(*descriptorInfo, bufferCount, &result); + if (!status.isOk()) { + auto error = status.getExceptionCode(); + if (error == EX_SERVICE_SPECIFIC) { + error = status.getServiceSpecificError(); + } + if (error == OK) { + error = UNKNOWN_ERROR; + } + return error; + } + + if (importBuffers) { + for (uint32_t i = 0; i < bufferCount; i++) { + auto handle = makeFromAidl(result.buffers[i]); + auto error = mMapper.importBuffer(handle, &outBufferHandles[i]); + native_handle_delete(handle); + if (error != NO_ERROR) { + for (uint32_t j = 0; j < i; j++) { + mMapper.freeBuffer(outBufferHandles[j]); + outBufferHandles[j] = nullptr; + } + return error; + } + } + } else { + for (uint32_t i = 0; i < bufferCount; i++) { + outBufferHandles[i] = dupFromAidl(result.buffers[i]); + if (!outBufferHandles[i]) { + for (uint32_t j = 0; j < i; j++) { + auto buffer = const_cast(outBufferHandles[j]); + native_handle_close(buffer); + native_handle_delete(buffer); + outBufferHandles[j] = nullptr; + } + return NO_MEMORY; + } + } + } + + *outStride = result.stride; + + // Release all the resources held by AllocationResult (specifically any remaining FDs) + result = {}; + // make sure the kernel driver sees BC_FREE_BUFFER and closes the fds now + // TODO: Re-enable this at some point if it's necessary. We can't do it now because libui + // is marked apex_available (b/214400477) and libbinder isn't (which of course is correct) + // IPCThreadState::self()->flushCommands(); + + return OK; +} + +void Gralloc5Mapper::preload() { + // TODO(b/261858155): Implement. We can't bounce off of IAllocator for this because zygote can't + // use binder. So when an alternate strategy of retrieving the library prefix is available, + // use that here. +} + +Gralloc5Mapper::Gralloc5Mapper() { + mMapper = getInstance().mapper; +} + +bool Gralloc5Mapper::isLoaded() const { + return mMapper != nullptr && mMapper->version >= AIMAPPER_VERSION_5; +} + +std::string Gralloc5Mapper::dumpBuffer(buffer_handle_t bufferHandle, bool less) const { + // TODO(b/261858392): Implement + (void)bufferHandle; + (void)less; + return {}; +} + +std::string Gralloc5Mapper::dumpBuffers(bool less) const { + // TODO(b/261858392): Implement + (void)less; + return {}; +} + +status_t Gralloc5Mapper::importBuffer(const native_handle_t *rawHandle, + buffer_handle_t *outBufferHandle) const { + return mMapper->v5.importBuffer(rawHandle, outBufferHandle); +} + +void Gralloc5Mapper::freeBuffer(buffer_handle_t bufferHandle) const { + mMapper->v5.freeBuffer(bufferHandle); +} + +status_t Gralloc5Mapper::validateBufferSize(buffer_handle_t bufferHandle, uint32_t width, + uint32_t height, PixelFormat format, + uint32_t layerCount, uint64_t usage, + uint32_t stride) const { + { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (width != value) { + ALOGW("Width didn't match, expected %d got %" PRId64, width, value.value_or(-1)); + return BAD_VALUE; + } + } + { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (height != value) { + ALOGW("Height didn't match, expected %d got %" PRId64, height, value.value_or(-1)); + return BAD_VALUE; + } + } + { + auto value = + getStandardMetadata(mMapper, + bufferHandle); + if (static_cast<::aidl::android::hardware::graphics::common::PixelFormat>(format) != + value) { + ALOGW("Format didn't match, expected %d got %s", format, + value.has_value() ? toString(*value).c_str() : ""); + return BAD_VALUE; + } + } + { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (layerCount != value) { + ALOGW("Layer count didn't match, expected %d got %" PRId64, layerCount, + value.value_or(-1)); + return BAD_VALUE; + } + } + { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (static_cast(usage) != value) { + ALOGW("Usage didn't match, expected %" PRIu64 " got %" PRId64, usage, + static_cast(value.value_or(BufferUsage::CPU_READ_NEVER))); + return BAD_VALUE; + } + } + { + (void)stride; + // TODO(b/261856851): Add StandardMetadataType::STRIDE && enable this + // auto value = getStandardMetadata(mMapper, + // bufferHandle); if (static_cast(usage) != value) { + // ALOGW("Layer count didn't match, expected %" PRIu64 " got %" PRId64, usage, + // static_cast(value.value_or(BufferUsage::CPU_READ_NEVER))); + // return BAD_VALUE; + // } + } + return OK; +} + +void Gralloc5Mapper::getTransportSize(buffer_handle_t bufferHandle, uint32_t *outNumFds, + uint32_t *outNumInts) const { + mMapper->v5.getTransportSize(bufferHandle, outNumFds, outNumInts); +} + +status_t Gralloc5Mapper::lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect &bounds, + int acquireFence, void **outData, int32_t *outBytesPerPixel, + int32_t *outBytesPerStride) const { + std::vector planeLayouts; + status_t err = getPlaneLayouts(bufferHandle, &planeLayouts); + + if (err == NO_ERROR && !planeLayouts.empty()) { + if (outBytesPerPixel) { + int32_t bitsPerPixel = planeLayouts.front().sampleIncrementInBits; + for (const auto &planeLayout : planeLayouts) { + if (bitsPerPixel != planeLayout.sampleIncrementInBits) { + bitsPerPixel = -1; + } + } + if (bitsPerPixel >= 0 && bitsPerPixel % 8 == 0) { + *outBytesPerPixel = bitsPerPixel / 8; + } else { + *outBytesPerPixel = -1; + } + } + if (outBytesPerStride) { + int32_t bytesPerStride = planeLayouts.front().strideInBytes; + for (const auto &planeLayout : planeLayouts) { + if (bytesPerStride != planeLayout.strideInBytes) { + bytesPerStride = -1; + } + } + if (bytesPerStride >= 0) { + *outBytesPerStride = bytesPerStride; + } else { + *outBytesPerStride = -1; + } + } + } + + auto status = mMapper->v5.lock(bufferHandle, usage, bounds, acquireFence, outData); + + ALOGW_IF(status != AIMAPPER_ERROR_NONE, "lock(%p, ...) failed: %d", bufferHandle, status); + return static_cast(status); +} + +status_t Gralloc5Mapper::lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect &bounds, + int acquireFence, android_ycbcr *outYcbcr) const { + if (!outYcbcr) { + return BAD_VALUE; + } + + // TODO(b/262279301): Change the return type of ::unlock to unique_fd instead of int so that + // ignoring the return value "just works" instead + auto unlock = [this](buffer_handle_t bufferHandle) { + int fence = this->unlock(bufferHandle); + if (fence != -1) { + ::close(fence); + } + }; + + std::vector planeLayouts; + status_t error = getPlaneLayouts(bufferHandle, &planeLayouts); + if (error != NO_ERROR) { + return error; + } + + void *data = nullptr; + error = lock(bufferHandle, usage, bounds, acquireFence, &data, nullptr, nullptr); + if (error != NO_ERROR) { + return error; + } + + android_ycbcr ycbcr; + + ycbcr.y = nullptr; + ycbcr.cb = nullptr; + ycbcr.cr = nullptr; + ycbcr.ystride = 0; + ycbcr.cstride = 0; + ycbcr.chroma_step = 0; + + for (const auto &planeLayout : planeLayouts) { + for (const auto &planeLayoutComponent : planeLayout.components) { + if (!gralloc4::isStandardPlaneLayoutComponentType(planeLayoutComponent.type)) { + continue; + } + + uint8_t *tmpData = static_cast(data) + planeLayout.offsetInBytes; + + // Note that `offsetInBits` may not be a multiple of 8 for packed formats (e.g. P010) + // but we still want to point to the start of the first byte. + tmpData += (planeLayoutComponent.offsetInBits / 8); + + uint64_t sampleIncrementInBytes; + + auto type = static_cast(planeLayoutComponent.type.value); + switch (type) { + case PlaneLayoutComponentType::Y: + if ((ycbcr.y != nullptr) || (planeLayout.sampleIncrementInBits % 8 != 0)) { + unlock(bufferHandle); + return BAD_VALUE; + } + ycbcr.y = tmpData; + ycbcr.ystride = planeLayout.strideInBytes; + break; + + case PlaneLayoutComponentType::CB: + case PlaneLayoutComponentType::CR: + if (planeLayout.sampleIncrementInBits % 8 != 0) { + unlock(bufferHandle); + return BAD_VALUE; + } + + sampleIncrementInBytes = planeLayout.sampleIncrementInBits / 8; + if ((sampleIncrementInBytes != 1) && (sampleIncrementInBytes != 2) && + (sampleIncrementInBytes != 4)) { + unlock(bufferHandle); + return BAD_VALUE; + } + + if (ycbcr.cstride == 0 && ycbcr.chroma_step == 0) { + ycbcr.cstride = planeLayout.strideInBytes; + ycbcr.chroma_step = sampleIncrementInBytes; + } else { + if ((static_cast(ycbcr.cstride) != planeLayout.strideInBytes) || + (ycbcr.chroma_step != sampleIncrementInBytes)) { + unlock(bufferHandle); + return BAD_VALUE; + } + } + + if (type == PlaneLayoutComponentType::CB) { + if (ycbcr.cb != nullptr) { + unlock(bufferHandle); + return BAD_VALUE; + } + ycbcr.cb = tmpData; + } else { + if (ycbcr.cr != nullptr) { + unlock(bufferHandle); + return BAD_VALUE; + } + ycbcr.cr = tmpData; + } + break; + default: + break; + }; + } + } + + *outYcbcr = ycbcr; + return OK; +} + +int Gralloc5Mapper::unlock(buffer_handle_t bufferHandle) const { + int fence = -1; + AIMapper_Error error = mMapper->v5.unlock(bufferHandle, &fence); + if (error != AIMAPPER_ERROR_NONE) { + ALOGW("unlock failed with error %d", error); + } + return fence; +} + +status_t Gralloc5Mapper::isSupported(uint32_t width, uint32_t height, PixelFormat format, + uint32_t layerCount, uint64_t usage, + bool *outSupported) const { + auto descriptorInfo = makeDescriptor("", width, height, format, layerCount, usage); + if (!descriptorInfo) { + *outSupported = false; + return OK; + } + auto status = getInstance().allocator->isSupported(*descriptorInfo, outSupported); + if (!status.isOk()) { + ALOGW("IAllocator::isSupported error %d (%s)", status.getStatus(), status.getMessage()); + *outSupported = false; + } + return OK; +} + +status_t Gralloc5Mapper::getBufferId(buffer_handle_t bufferHandle, uint64_t *outBufferId) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outBufferId = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getName(buffer_handle_t bufferHandle, std::string *outName) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outName = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getWidth(buffer_handle_t bufferHandle, uint64_t *outWidth) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outWidth = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getHeight(buffer_handle_t bufferHandle, uint64_t *outHeight) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outHeight = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getLayerCount(buffer_handle_t bufferHandle, + uint64_t *outLayerCount) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outLayerCount = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getPixelFormatRequested(buffer_handle_t bufferHandle, + ui::PixelFormat *outPixelFormatRequested) const { + auto value = getStandardMetadata(mMapper, + bufferHandle); + if (value.has_value()) { + *outPixelFormatRequested = static_cast(*value); + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getPixelFormatFourCC(buffer_handle_t bufferHandle, + uint32_t *outPixelFormatFourCC) const { + auto value = + getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outPixelFormatFourCC = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getPixelFormatModifier(buffer_handle_t bufferHandle, + uint64_t *outPixelFormatModifier) const { + auto value = + getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outPixelFormatModifier = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getUsage(buffer_handle_t bufferHandle, uint64_t *outUsage) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outUsage = static_cast(*value); + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getAllocationSize(buffer_handle_t bufferHandle, + uint64_t *outAllocationSize) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outAllocationSize = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getProtectedContent(buffer_handle_t bufferHandle, + uint64_t *outProtectedContent) const { + auto value = + getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outProtectedContent = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getCompression( + buffer_handle_t bufferHandle, + aidl::android::hardware::graphics::common::ExtendableType *outCompression) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outCompression = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getCompression(buffer_handle_t bufferHandle, + ui::Compression *outCompression) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (!value.has_value()) { + return UNKNOWN_TRANSACTION; + } + if (!gralloc4::isStandardCompression(*value)) { + return BAD_TYPE; + } + *outCompression = gralloc4::getStandardCompressionValue(*value); + return OK; +} + +status_t Gralloc5Mapper::getInterlaced( + buffer_handle_t bufferHandle, + aidl::android::hardware::graphics::common::ExtendableType *outInterlaced) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outInterlaced = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getInterlaced(buffer_handle_t bufferHandle, + ui::Interlaced *outInterlaced) const { + if (!outInterlaced) { + return BAD_VALUE; + } + ExtendableType interlaced; + status_t error = getInterlaced(bufferHandle, &interlaced); + if (error) { + return error; + } + if (!gralloc4::isStandardInterlaced(interlaced)) { + return BAD_TYPE; + } + *outInterlaced = gralloc4::getStandardInterlacedValue(interlaced); + return NO_ERROR; +} + +status_t Gralloc5Mapper::getChromaSiting( + buffer_handle_t bufferHandle, + aidl::android::hardware::graphics::common::ExtendableType *outChromaSiting) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outChromaSiting = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getChromaSiting(buffer_handle_t bufferHandle, + ui::ChromaSiting *outChromaSiting) const { + if (!outChromaSiting) { + return BAD_VALUE; + } + ExtendableType chromaSiting; + status_t error = getChromaSiting(bufferHandle, &chromaSiting); + if (error) { + return error; + } + if (!gralloc4::isStandardChromaSiting(chromaSiting)) { + return BAD_TYPE; + } + *outChromaSiting = gralloc4::getStandardChromaSitingValue(chromaSiting); + return NO_ERROR; +} + +status_t Gralloc5Mapper::getPlaneLayouts(buffer_handle_t bufferHandle, + std::vector *outPlaneLayouts) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outPlaneLayouts = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getDataspace(buffer_handle_t bufferHandle, + ui::Dataspace *outDataspace) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outDataspace = static_cast(*value); + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::setDataspace(buffer_handle_t bufferHandle, ui::Dataspace dataspace) const { + return setStandardMetadata(mMapper, bufferHandle, + static_cast(dataspace)); +} + +status_t Gralloc5Mapper::getBlendMode(buffer_handle_t bufferHandle, + ui::BlendMode *outBlendMode) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outBlendMode = static_cast(*value); + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getSmpte2086(buffer_handle_t bufferHandle, + std::optional *outSmpte2086) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outSmpte2086 = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::setSmpte2086(buffer_handle_t bufferHandle, + std::optional smpte2086) const { + return setStandardMetadata(mMapper, bufferHandle, smpte2086); +} + +status_t Gralloc5Mapper::getCta861_3(buffer_handle_t bufferHandle, + std::optional *outCta861_3) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outCta861_3 = *value; + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::setCta861_3(buffer_handle_t bufferHandle, + std::optional cta861_3) const { + return setStandardMetadata(mMapper, bufferHandle, cta861_3); +} + +status_t Gralloc5Mapper::getSmpte2094_40( + buffer_handle_t bufferHandle, std::optional> *outSmpte2094_40) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outSmpte2094_40 = std::move(*value); + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::setSmpte2094_40(buffer_handle_t bufferHandle, + std::optional> smpte2094_40) const { + return setStandardMetadata(mMapper, bufferHandle, + smpte2094_40); +} + +status_t Gralloc5Mapper::getSmpte2094_10( + buffer_handle_t bufferHandle, std::optional> *outSmpte2094_10) const { + auto value = getStandardMetadata(mMapper, bufferHandle); + if (value.has_value()) { + *outSmpte2094_10 = std::move(*value); + return OK; + } + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::setSmpte2094_10(buffer_handle_t bufferHandle, + std::optional> smpte2094_10) const { + return setStandardMetadata(mMapper, bufferHandle, + smpte2094_10); +} + +status_t Gralloc5Mapper::getDefaultPixelFormatFourCC(uint32_t, uint32_t, PixelFormat, uint32_t, + uint64_t, uint32_t *) const { + // TODO(b/261857910): Remove + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getDefaultPixelFormatModifier(uint32_t, uint32_t, PixelFormat, uint32_t, + uint64_t, uint64_t *) const { + // TODO(b/261857910): Remove + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getDefaultAllocationSize(uint32_t, uint32_t, PixelFormat, uint32_t, + uint64_t, uint64_t *) const { + // TODO(b/261857910): Remove + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getDefaultProtectedContent(uint32_t, uint32_t, PixelFormat, uint32_t, + uint64_t, uint64_t *) const { + // TODO(b/261857910): Remove + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getDefaultCompression( + uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t, + aidl::android::hardware::graphics::common::ExtendableType *) const { + // TODO(b/261857910): Remove + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getDefaultCompression(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t, + ui::Compression *) const { + // TODO(b/261857910): Remove + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getDefaultInterlaced( + uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t, + aidl::android::hardware::graphics::common::ExtendableType *) const { + // TODO(b/261857910): Remove + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getDefaultInterlaced(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t, + ui::Interlaced *) const { + // TODO(b/261857910): Remove + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getDefaultChromaSiting( + uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t, + aidl::android::hardware::graphics::common::ExtendableType *) const { + // TODO(b/261857910): Remove + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getDefaultChromaSiting(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t, + ui::ChromaSiting *) const { + // TODO(b/261857910): Remove + return UNKNOWN_TRANSACTION; +} + +status_t Gralloc5Mapper::getDefaultPlaneLayouts(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t, + std::vector *) const { + // TODO(b/261857910): Remove + return UNKNOWN_TRANSACTION; +} + +} // namespace android \ No newline at end of file diff --git a/libs/ui/GraphicBufferAllocator.cpp b/libs/ui/GraphicBufferAllocator.cpp index 3f958ba68f..c0abec23e0 100644 --- a/libs/ui/GraphicBufferAllocator.cpp +++ b/libs/ui/GraphicBufferAllocator.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include namespace android { @@ -48,23 +49,27 @@ KeyedVector GraphicBufferAllocator::sAllocList; GraphicBufferAllocator::GraphicBufferAllocator() : mMapper(GraphicBufferMapper::getInstance()) { - mAllocator = std::make_unique( - reinterpret_cast(mMapper.getGrallocMapper())); - if (mAllocator->isLoaded()) { - return; + switch (mMapper.getMapperVersion()) { + case GraphicBufferMapper::GRALLOC_5: + mAllocator = std::make_unique( + reinterpret_cast(mMapper.getGrallocMapper())); + break; + case GraphicBufferMapper::GRALLOC_4: + mAllocator = std::make_unique( + reinterpret_cast(mMapper.getGrallocMapper())); + break; + case GraphicBufferMapper::GRALLOC_3: + mAllocator = std::make_unique( + reinterpret_cast(mMapper.getGrallocMapper())); + break; + case GraphicBufferMapper::GRALLOC_2: + mAllocator = std::make_unique( + reinterpret_cast(mMapper.getGrallocMapper())); + break; } - mAllocator = std::make_unique( - reinterpret_cast(mMapper.getGrallocMapper())); - if (mAllocator->isLoaded()) { - return; - } - mAllocator = std::make_unique( - reinterpret_cast(mMapper.getGrallocMapper())); - if (mAllocator->isLoaded()) { - return; - } - - LOG_ALWAYS_FATAL("gralloc-allocator is missing"); + LOG_ALWAYS_FATAL_IF(!mAllocator->isLoaded(), + "Failed to load matching allocator for mapper version %d", + mMapper.getMapperVersion()); } GraphicBufferAllocator::~GraphicBufferAllocator() {} diff --git a/libs/ui/GraphicBufferMapper.cpp b/libs/ui/GraphicBufferMapper.cpp index a98e697eb1..6002a6d29e 100644 --- a/libs/ui/GraphicBufferMapper.cpp +++ b/libs/ui/GraphicBufferMapper.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -49,9 +50,15 @@ void GraphicBufferMapper::preloadHal() { Gralloc2Mapper::preload(); Gralloc3Mapper::preload(); Gralloc4Mapper::preload(); + Gralloc5Mapper::preload(); } GraphicBufferMapper::GraphicBufferMapper() { + mMapper = std::make_unique(); + if (mMapper->isLoaded()) { + mMapperVersion = Version::GRALLOC_5; + return; + } mMapper = std::make_unique(); if (mMapper->isLoaded()) { mMapperVersion = Version::GRALLOC_4; @@ -82,15 +89,14 @@ void GraphicBufferMapper::dumpBufferToSystemLog(buffer_handle_t bufferHandle, bo ALOGD("%s", s.c_str()); } -status_t GraphicBufferMapper::importBuffer(buffer_handle_t rawHandle, - uint32_t width, uint32_t height, uint32_t layerCount, - PixelFormat format, uint64_t usage, uint32_t stride, - buffer_handle_t* outHandle) -{ +status_t GraphicBufferMapper::importBuffer(const native_handle_t* rawHandle, uint32_t width, + uint32_t height, uint32_t layerCount, PixelFormat format, + uint64_t usage, uint32_t stride, + buffer_handle_t* outHandle) { ATRACE_CALL(); buffer_handle_t bufferHandle; - status_t error = mMapper->importBuffer(hardware::hidl_handle(rawHandle), &bufferHandle); + status_t error = mMapper->importBuffer(rawHandle, &bufferHandle); if (error != NO_ERROR) { ALOGW("importBuffer(%p) failed: %d", rawHandle, error); return error; @@ -109,6 +115,11 @@ status_t GraphicBufferMapper::importBuffer(buffer_handle_t rawHandle, return NO_ERROR; } +status_t GraphicBufferMapper::importBufferNoValidate(const native_handle_t* rawHandle, + buffer_handle_t* outHandle) { + return mMapper->importBuffer(rawHandle, outHandle); +} + void GraphicBufferMapper::getTransportSize(buffer_handle_t handle, uint32_t* outTransportNumFds, uint32_t* outTransportNumInts) { diff --git a/libs/ui/include/ui/Gralloc.h b/libs/ui/include/ui/Gralloc.h index 6101d4b43b..b494cbe4fa 100644 --- a/libs/ui/include/ui/Gralloc.h +++ b/libs/ui/include/ui/Gralloc.h @@ -39,14 +39,11 @@ public: return ""; } - virtual status_t createDescriptor(void* bufferDescriptorInfo, - void* outBufferDescriptor) const = 0; - // Import a buffer that is from another HAL, another process, or is // cloned. // // The returned handle must be freed with freeBuffer. - virtual status_t importBuffer(const hardware::hidl_handle& rawHandle, + virtual status_t importBuffer(const native_handle_t* rawHandle, buffer_handle_t* outBufferHandle) const = 0; virtual void freeBuffer(buffer_handle_t bufferHandle) const = 0; @@ -269,11 +266,6 @@ public: std::vector* /*outPlaneLayouts*/) const { return INVALID_OPERATION; } - - virtual std::vector - listSupportedMetadataTypes() const { - return {}; - } }; // A wrapper to IAllocator diff --git a/libs/ui/include/ui/Gralloc2.h b/libs/ui/include/ui/Gralloc2.h index f570c428b6..a7b6f49206 100644 --- a/libs/ui/include/ui/Gralloc2.h +++ b/libs/ui/include/ui/Gralloc2.h @@ -38,9 +38,9 @@ public: bool isLoaded() const override; - status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const override; + status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const; - status_t importBuffer(const hardware::hidl_handle& rawHandle, + status_t importBuffer(const native_handle_t* rawHandle, buffer_handle_t* outBufferHandle) const override; void freeBuffer(buffer_handle_t bufferHandle) const override; diff --git a/libs/ui/include/ui/Gralloc3.h b/libs/ui/include/ui/Gralloc3.h index 93a5077313..7367549964 100644 --- a/libs/ui/include/ui/Gralloc3.h +++ b/libs/ui/include/ui/Gralloc3.h @@ -37,9 +37,9 @@ public: bool isLoaded() const override; - status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const override; + status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const; - status_t importBuffer(const hardware::hidl_handle& rawHandle, + status_t importBuffer(const native_handle_t* rawHandle, buffer_handle_t* outBufferHandle) const override; void freeBuffer(buffer_handle_t bufferHandle) const override; diff --git a/libs/ui/include/ui/Gralloc4.h b/libs/ui/include/ui/Gralloc4.h index cf023c9bcc..6bc5ce5296 100644 --- a/libs/ui/include/ui/Gralloc4.h +++ b/libs/ui/include/ui/Gralloc4.h @@ -42,9 +42,9 @@ public: std::string dumpBuffer(buffer_handle_t bufferHandle, bool less = true) const override; std::string dumpBuffers(bool less = true) const; - status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const override; + status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const; - status_t importBuffer(const hardware::hidl_handle& rawHandle, + status_t importBuffer(const native_handle_t* rawHandle, buffer_handle_t* outBufferHandle) const override; void freeBuffer(buffer_handle_t bufferHandle) const override; diff --git a/libs/ui/include/ui/Gralloc5.h b/libs/ui/include/ui/Gralloc5.h new file mode 100644 index 0000000000..bc1016944a --- /dev/null +++ b/libs/ui/include/ui/Gralloc5.h @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +namespace android { + +class Gralloc5Mapper : public GrallocMapper { +public: +public: + static void preload(); + + Gralloc5Mapper(); + + [[nodiscard]] bool isLoaded() const override; + + [[nodiscard]] std::string dumpBuffer(buffer_handle_t bufferHandle, bool less) const override; + + [[nodiscard]] std::string dumpBuffers(bool less = true) const; + + [[nodiscard]] status_t importBuffer(const native_handle_t *rawHandle, + buffer_handle_t *outBufferHandle) const override; + + void freeBuffer(buffer_handle_t bufferHandle) const override; + + [[nodiscard]] status_t validateBufferSize(buffer_handle_t bufferHandle, uint32_t width, + uint32_t height, PixelFormat format, + uint32_t layerCount, uint64_t usage, + uint32_t stride) const override; + + void getTransportSize(buffer_handle_t bufferHandle, uint32_t *outNumFds, + uint32_t *outNumInts) const override; + + [[nodiscard]] status_t lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect &bounds, + int acquireFence, void **outData, int32_t *outBytesPerPixel, + int32_t *outBytesPerStride) const override; + + [[nodiscard]] status_t lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect &bounds, + int acquireFence, android_ycbcr *ycbcr) const override; + + [[nodiscard]] int unlock(buffer_handle_t bufferHandle) const override; + + [[nodiscard]] status_t isSupported(uint32_t width, uint32_t height, PixelFormat format, + uint32_t layerCount, uint64_t usage, + bool *outSupported) const override; + + [[nodiscard]] status_t getBufferId(buffer_handle_t bufferHandle, + uint64_t *outBufferId) const override; + + [[nodiscard]] status_t getName(buffer_handle_t bufferHandle, + std::string *outName) const override; + + [[nodiscard]] status_t getWidth(buffer_handle_t bufferHandle, + uint64_t *outWidth) const override; + + [[nodiscard]] status_t getHeight(buffer_handle_t bufferHandle, + uint64_t *outHeight) const override; + + [[nodiscard]] status_t getLayerCount(buffer_handle_t bufferHandle, + uint64_t *outLayerCount) const override; + + [[nodiscard]] status_t getPixelFormatRequested( + buffer_handle_t bufferHandle, ui::PixelFormat *outPixelFormatRequested) const override; + + [[nodiscard]] status_t getPixelFormatFourCC(buffer_handle_t bufferHandle, + uint32_t *outPixelFormatFourCC) const override; + + [[nodiscard]] status_t getPixelFormatModifier(buffer_handle_t bufferHandle, + uint64_t *outPixelFormatModifier) const override; + + [[nodiscard]] status_t getUsage(buffer_handle_t bufferHandle, + uint64_t *outUsage) const override; + + [[nodiscard]] status_t getAllocationSize(buffer_handle_t bufferHandle, + uint64_t *outAllocationSize) const override; + + [[nodiscard]] status_t getProtectedContent(buffer_handle_t bufferHandle, + uint64_t *outProtectedContent) const override; + + [[nodiscard]] status_t getCompression(buffer_handle_t bufferHandle, + aidl::android::hardware::graphics::common::ExtendableType + *outCompression) const override; + + [[nodiscard]] status_t getCompression(buffer_handle_t bufferHandle, + ui::Compression *outCompression) const override; + + [[nodiscard]] status_t getInterlaced(buffer_handle_t bufferHandle, + aidl::android::hardware::graphics::common::ExtendableType + *outInterlaced) const override; + + [[nodiscard]] status_t getInterlaced(buffer_handle_t bufferHandle, + ui::Interlaced *outInterlaced) const override; + + [[nodiscard]] status_t getChromaSiting(buffer_handle_t bufferHandle, + aidl::android::hardware::graphics::common::ExtendableType + *outChromaSiting) const override; + + [[nodiscard]] status_t getChromaSiting(buffer_handle_t bufferHandle, + ui::ChromaSiting *outChromaSiting) const override; + + [[nodiscard]] status_t getPlaneLayouts( + buffer_handle_t bufferHandle, + std::vector *outPlaneLayouts) const override; + + [[nodiscard]] status_t getDataspace(buffer_handle_t bufferHandle, + ui::Dataspace *outDataspace) const override; + + [[nodiscard]] status_t setDataspace(buffer_handle_t bufferHandle, + ui::Dataspace dataspace) const override; + + [[nodiscard]] status_t getBlendMode(buffer_handle_t bufferHandle, + ui::BlendMode *outBlendMode) const override; + + [[nodiscard]] status_t getSmpte2086(buffer_handle_t bufferHandle, + std::optional *outSmpte2086) const override; + + [[nodiscard]] status_t setSmpte2086(buffer_handle_t bufferHandle, + std::optional smpte2086) const override; + + [[nodiscard]] status_t getCta861_3(buffer_handle_t bufferHandle, + std::optional *outCta861_3) const override; + + [[nodiscard]] status_t setCta861_3(buffer_handle_t bufferHandle, + std::optional cta861_3) const override; + + [[nodiscard]] status_t getSmpte2094_40( + buffer_handle_t bufferHandle, + std::optional> *outSmpte2094_40) const override; + + [[nodiscard]] status_t setSmpte2094_40( + buffer_handle_t bufferHandle, + std::optional> smpte2094_40) const override; + + [[nodiscard]] status_t getSmpte2094_10( + buffer_handle_t bufferHandle, + std::optional> *outSmpte2094_10) const override; + + [[nodiscard]] status_t setSmpte2094_10( + buffer_handle_t bufferHandle, + std::optional> smpte2094_10) const override; + + [[nodiscard]] status_t getDefaultPixelFormatFourCC( + uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, + uint64_t usage, uint32_t *outPixelFormatFourCC) const override; + + [[nodiscard]] status_t getDefaultPixelFormatModifier( + uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, + uint64_t usage, uint64_t *outPixelFormatModifier) const override; + + [[nodiscard]] status_t getDefaultAllocationSize(uint32_t width, uint32_t height, + PixelFormat format, uint32_t layerCount, + uint64_t usage, + uint64_t *outAllocationSize) const override; + + [[nodiscard]] status_t getDefaultProtectedContent(uint32_t width, uint32_t height, + PixelFormat format, uint32_t layerCount, + uint64_t usage, + uint64_t *outProtectedContent) const override; + + [[nodiscard]] status_t getDefaultCompression( + uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, + uint64_t usage, + aidl::android::hardware::graphics::common::ExtendableType *outCompression) + const override; + + [[nodiscard]] status_t getDefaultCompression(uint32_t width, uint32_t height, + PixelFormat format, uint32_t layerCount, + uint64_t usage, + ui::Compression *outCompression) const override; + + [[nodiscard]] status_t getDefaultInterlaced( + uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, + uint64_t usage, + aidl::android::hardware::graphics::common::ExtendableType *outInterlaced) + const override; + + [[nodiscard]] status_t getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format, + uint32_t layerCount, uint64_t usage, + ui::Interlaced *outInterlaced) const override; + + [[nodiscard]] status_t getDefaultChromaSiting( + uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, + uint64_t usage, + aidl::android::hardware::graphics::common::ExtendableType *outChromaSiting) + const override; + + [[nodiscard]] status_t getDefaultChromaSiting(uint32_t width, uint32_t height, + PixelFormat format, uint32_t layerCount, + uint64_t usage, + ui::ChromaSiting *outChromaSiting) const override; + + [[nodiscard]] status_t getDefaultPlaneLayouts( + uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, + uint64_t usage, std::vector *outPlaneLayouts) const override; + +private: + void unlockBlocking(buffer_handle_t bufferHandle) const; + + AIMapper *mMapper = nullptr; +}; + +class Gralloc5Allocator : public GrallocAllocator { +public: + Gralloc5Allocator(const Gralloc5Mapper &mapper); + + [[nodiscard]] bool isLoaded() const override; + + [[nodiscard]] std::string dumpDebugInfo(bool less) const override; + + [[nodiscard]] status_t allocate(std::string requestorName, uint32_t width, uint32_t height, + PixelFormat format, uint32_t layerCount, uint64_t usage, + uint32_t bufferCount, uint32_t *outStride, + buffer_handle_t *outBufferHandles, + bool importBuffers) const override; + +private: + const Gralloc5Mapper &mMapper; + std::shared_ptr mAllocator; +}; + +} // namespace android diff --git a/libs/ui/include/ui/GraphicBufferMapper.h b/libs/ui/include/ui/GraphicBufferMapper.h index 507fa355a4..51c6e92f43 100644 --- a/libs/ui/include/ui/GraphicBufferMapper.h +++ b/libs/ui/include/ui/GraphicBufferMapper.h @@ -42,9 +42,10 @@ class GraphicBufferMapper : public Singleton { public: enum Version { - GRALLOC_2, + GRALLOC_2 = 2, GRALLOC_3, GRALLOC_4, + GRALLOC_5, }; static void preloadHal(); static inline GraphicBufferMapper& get() { return getInstance(); } @@ -54,10 +55,11 @@ public: // The imported outHandle must be freed with freeBuffer when no longer // needed. rawHandle is owned by the caller. - status_t importBuffer(buffer_handle_t rawHandle, - uint32_t width, uint32_t height, uint32_t layerCount, - PixelFormat format, uint64_t usage, uint32_t stride, - buffer_handle_t* outHandle); + status_t importBuffer(const native_handle_t* rawHandle, uint32_t width, uint32_t height, + uint32_t layerCount, PixelFormat format, uint64_t usage, uint32_t stride, + buffer_handle_t* outHandle); + + status_t importBufferNoValidate(const native_handle_t* rawHandle, buffer_handle_t* outHandle); status_t freeBuffer(buffer_handle_t handle); -- GitLab From fc378b0b8fc14b3367f493f6a6c88b1e8d61b9e1 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Fri, 2 Dec 2022 14:56:05 -0500 Subject: [PATCH 0608/1310] SF: Fix display mode transitions for multi-display SF does not yet support concurrent modeset on multiple displays, as the scheduler/modeset/VSYNC state machines are tied to the internal display that is powered on, a.k.a. the active display. When SF detects a change in the DM policy for a display, it initiates a transition to the new display mode, which includes syncing to its VSYNC. However, the per-display calls to setDesiredDisplayModeSpecs that start this process occur after the setPowerMode calls to turn off/on the old/ new active display, respectively. Before this CL, a change in policy on the now inactive (powered-off) display triggered a partial display mode transition (that would start resync but abort before HWC modeset), such that SF wound up internally inconsistent and out of sync with HWC. Fix this by deferring the applyRefreshRateSelectorPolicy of the inactive display until it becomes active. Later, in onActiveDisplayChangedLocked, the Scheduler::Policy is cleared by Scheduler::setLeaderDisplay, so ensure that Scheduler::getPreferredDisplayMode subsequently initializes Scheduler::Policy::modeOpt to the chosen mode for the newly active display. Otherwise, applyRefreshRateSelectorPolicy falls back to its default mode. Bug: 260092798 Test: No intermittent jank on outer/inner displays after fold/unfold. Test: DisplayModeSwitchingTest.multiDisplay Change-Id: Iebe1a6bb4749630333ef954955ac33807c95dd9f --- services/surfaceflinger/DisplayDevice.cpp | 5 +- services/surfaceflinger/DisplayDevice.h | 3 +- .../surfaceflinger/Scheduler/Scheduler.cpp | 18 +-- services/surfaceflinger/Scheduler/Scheduler.h | 4 +- services/surfaceflinger/SurfaceFlinger.cpp | 41 ++++-- services/surfaceflinger/SurfaceFlinger.h | 10 +- .../SurfaceFlinger_DisplayModeSwitching.cpp | 120 +++++++++++++++++- .../mock/DisplayHardware/MockDisplayMode.h | 20 +++ 8 files changed, 191 insertions(+), 30 deletions(-) diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 46b857b189..96ae77fbff 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -452,7 +452,8 @@ void DisplayDevice::animateRefreshRateOverlay() { } } -auto DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info) -> DesiredActiveModeAction { +auto DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info, bool force) + -> DesiredActiveModeAction { ATRACE_CALL(); LOG_ALWAYS_FATAL_IF(!info.modeOpt, "desired mode not provided"); @@ -473,7 +474,7 @@ auto DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info) -> DesiredA const auto& desiredMode = *info.modeOpt->modePtr; // Check if we are already at the desired mode - if (refreshRateSelector().getActiveMode().modePtr->getId() == desiredMode.getId()) { + if (!force && refreshRateSelector().getActiveMode().modePtr->getId() == desiredMode.getId()) { if (refreshRateSelector().getActiveMode() == info.modeOpt) { return DesiredActiveModeAction::None; } diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index 8f9b2a19a6..d757673176 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -210,7 +210,8 @@ public: InitiateDisplayModeSwitch, InitiateRenderRateSwitch }; - DesiredActiveModeAction setDesiredActiveMode(const ActiveModeInfo&) EXCLUDES(mActiveModeLock); + DesiredActiveModeAction setDesiredActiveMode(const ActiveModeInfo&, bool force = false) + EXCLUDES(mActiveModeLock); std::optional getDesiredActiveMode() const EXCLUDES(mActiveModeLock); void clearDesiredActiveModeState() EXCLUDES(mActiveModeLock); ActiveModeInfo getUpcomingActiveMode() const REQUIRES(kMainThreadContext) { diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 34f8df28e8..856fda0f6c 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -815,18 +815,18 @@ GlobalSignals Scheduler::makeGlobalSignals() const { .powerOnImminent = powerOnImminent}; } -ftl::Optional Scheduler::getPreferredDisplayMode() { +FrameRateMode Scheduler::getPreferredDisplayMode() { std::lock_guard lock(mPolicyLock); + const auto frameRateMode = + leaderSelectorPtr() + ->getRankedFrameRates(mPolicy.contentRequirements, makeGlobalSignals()) + .ranking.front() + .frameRateMode; + // Make sure the stored mode is up to date. - if (mPolicy.modeOpt) { - const auto ranking = - leaderSelectorPtr() - ->getRankedFrameRates(mPolicy.contentRequirements, makeGlobalSignals()) - .ranking; + mPolicy.modeOpt = frameRateMode; - mPolicy.modeOpt = ranking.front().frameRateMode; - } - return mPolicy.modeOpt; + return frameRateMode; } void Scheduler::onNewVsyncPeriodChangeTimeline(const hal::VsyncPeriodChangeTimeline& timeline) { diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index cf2ffb8cdd..f1894262ef 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -209,8 +209,8 @@ public: void dump(ConnectionHandle, std::string&) const; void dumpVsync(std::string&) const; - // Get the appropriate refresh for current conditions. - ftl::Optional getPreferredDisplayMode(); + // Returns the preferred refresh rate and frame rate for the leader display. + FrameRateMode getPreferredDisplayMode(); // Notifies the scheduler about a refresh rate timeline change. void onNewVsyncPeriodChangeTimeline(const hal::VsyncPeriodChangeTimeline& timeline); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index cdd604495e..9f4178e78d 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1141,7 +1141,7 @@ status_t SurfaceFlinger::getDisplayStats(const sp&, DisplayStatInfo* ou return NO_ERROR; } -void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request) { +void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request, bool force) { ATRACE_CALL(); auto display = getDisplayDeviceLocked(request.mode.modePtr->getPhysicalDisplayId()); @@ -1153,7 +1153,8 @@ void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request) const auto mode = request.mode; const bool emitEvent = request.emitEvent; - switch (display->setDesiredActiveMode(DisplayDevice::ActiveModeInfo(std::move(request)))) { + switch (display->setDesiredActiveMode(DisplayDevice::ActiveModeInfo(std::move(request)), + force)) { case DisplayDevice::DesiredActiveModeAction::InitiateDisplayModeSwitch: scheduleComposite(FrameHint::kNone); @@ -3467,10 +3468,21 @@ void SurfaceFlinger::requestDisplayModes(std::vectorgetPhysicalDisplayId()); + + const auto displayId = modePtr->getPhysicalDisplayId(); + const auto display = getDisplayDeviceLocked(displayId); if (!display) continue; + const bool isInternalDisplay = mPhysicalDisplays.get(displayId) + .transform(&PhysicalDisplay::isInternal) + .value_or(false); + + if (isInternalDisplay && displayId != mActiveDisplayId) { + ALOGV("%s(%s): Inactive display", __func__, to_string(displayId).c_str()); + continue; + } + if (display->refreshRateSelector().isModeAllowed(request.mode)) { setDesiredActiveMode(std::move(request)); } else { @@ -6730,7 +6742,7 @@ void SurfaceFlinger::traverseLayersInLayerStack(ui::LayerStack layerStack, const ftl::Optional SurfaceFlinger::getPreferredDisplayMode( PhysicalDisplayId displayId, DisplayModeId defaultModeId) const { if (const auto schedulerMode = mScheduler->getPreferredDisplayMode(); - schedulerMode && schedulerMode->modePtr->getPhysicalDisplayId() == displayId) { + schedulerMode.modePtr->getPhysicalDisplayId() == displayId) { return schedulerMode; } @@ -6765,12 +6777,24 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( case SetPolicyResult::Unchanged: return NO_ERROR; case SetPolicyResult::Changed: - return applyRefreshRateSelectorPolicy(displayId, selector); + break; } + + const bool isInternalDisplay = mPhysicalDisplays.get(displayId) + .transform(&PhysicalDisplay::isInternal) + .value_or(false); + + if (isInternalDisplay && displayId != mActiveDisplayId) { + // The policy will be be applied when the display becomes active. + ALOGV("%s(%s): Inactive display", __func__, to_string(displayId).c_str()); + return NO_ERROR; + } + + return applyRefreshRateSelectorPolicy(displayId, selector); } status_t SurfaceFlinger::applyRefreshRateSelectorPolicy( - PhysicalDisplayId displayId, const scheduler::RefreshRateSelector& selector) { + PhysicalDisplayId displayId, const scheduler::RefreshRateSelector& selector, bool force) { const scheduler::RefreshRateSelector::Policy currentPolicy = selector.getCurrentPolicy(); ALOGV("Setting desired display mode specs: %s", currentPolicy.toString().c_str()); @@ -6800,7 +6824,7 @@ status_t SurfaceFlinger::applyRefreshRateSelectorPolicy( return INVALID_OPERATION; } - setDesiredActiveMode({std::move(preferredMode), .emitEvent = true}); + setDesiredActiveMode({std::move(preferredMode), .emitEvent = true}, force); return NO_ERROR; } @@ -7104,7 +7128,8 @@ void SurfaceFlinger::onActiveDisplayChangedLocked(const sp& inact // case, its preferred mode has not been propagated to HWC (via setDesiredActiveMode). In either // case, the Scheduler's cachedModeChangedParams must be initialized to the newly active mode, // and the kernel idle timer of the newly active display must be toggled. - applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay->refreshRateSelector()); + constexpr bool kForce = true; + applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay->refreshRateSelector(), kForce); } status_t SurfaceFlinger::addWindowInfosListener( diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index c957b678e9..5f35c44ebb 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -649,7 +649,8 @@ private: // Show render rate with refresh rate overlay bool mRefreshRateOverlayRenderRate = false; - void setDesiredActiveMode(display::DisplayModeRequest&&) REQUIRES(mStateLock); + void setDesiredActiveMode(display::DisplayModeRequest&&, bool force = false) + REQUIRES(mStateLock); status_t setActiveModeFromBackdoor(const sp&, DisplayModeId); // Sets the active mode and a new refresh rate in SF. @@ -678,7 +679,8 @@ private: // TODO(b/241285191): Look up RefreshRateSelector on Scheduler to remove redundant parameter. status_t applyRefreshRateSelectorPolicy(PhysicalDisplayId, - const scheduler::RefreshRateSelector&) + const scheduler::RefreshRateSelector&, + bool force = false) REQUIRES(mStateLock, kMainThreadContext); void commitTransactions() EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); @@ -1187,7 +1189,9 @@ private: display::PhysicalDisplays mPhysicalDisplays GUARDED_BY(mStateLock); // The inner or outer display for foldables, assuming they have mutually exclusive power states. - PhysicalDisplayId mActiveDisplayId GUARDED_BY(mStateLock); + // Atomic because writes from onActiveDisplayChangedLocked are not always under mStateLock, but + // reads from ISchedulerCallback::requestDisplayModes may happen concurrently. + std::atomic mActiveDisplayId GUARDED_BY(mStateLock); struct { DisplayIdGenerator gpu; diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp index 074bf8cd1e..ad3bd353ed 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp @@ -14,12 +14,12 @@ * limitations under the License. */ -#include "mock/MockDisplayModeSpecs.h" -#include "mock/MockEventThread.h" #undef LOG_TAG #define LOG_TAG "LibSurfaceFlingerUnittests" #include "DisplayTransactionTestHelpers.h" +#include "mock/DisplayHardware/MockDisplayMode.h" +#include "mock/MockDisplayModeSpecs.h" #include #include @@ -42,8 +42,7 @@ public: PrimaryDisplayVariant::setupNativeWindowSurfaceCreationCallExpectations(this); PrimaryDisplayVariant::setupHwcGetActiveConfigCallExpectations(this); - DisplayModes modes = makeModes(kMode60, kMode90, kMode120, kMode90_4K); - auto selectorPtr = std::make_shared(modes, kModeId60); + auto selectorPtr = std::make_shared(kModes, kModeId60); setupScheduler(selectorPtr); @@ -51,7 +50,7 @@ public: mFlinger.configureAndCommit(); mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this) - .setDisplayModes(std::move(modes), kModeId60, std::move(selectorPtr)) + .setDisplayModes(kModes, kModeId60, std::move(selectorPtr)) .inject(); // isVsyncPeriodSwitchSupported should return true, otherwise the SF's HWC proxy @@ -78,6 +77,8 @@ protected: static constexpr ui::Size kResolution4K{3840, 2160}; static inline const DisplayModePtr kMode90_4K = createDisplayMode(kModeId90_4K, 90_Hz, 3, kResolution4K); + + static inline const DisplayModes kModes = makeModes(kMode60, kMode90, kMode120, kMode90_4K); }; void DisplayModeSwitchingTest::setupScheduler( @@ -283,5 +284,114 @@ TEST_F(DisplayModeSwitchingTest, changeResolution_OnActiveDisplay_WithoutRefresh ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90_4K); } +TEST_F(DisplayModeSwitchingTest, multiDisplay) { + ftl::FakeGuard guard(kMainThreadContext); + + constexpr HWDisplayId kInnerDisplayHwcId = PrimaryDisplayVariant::HWC_DISPLAY_ID; + constexpr HWDisplayId kOuterDisplayHwcId = kInnerDisplayHwcId + 1; + + constexpr PhysicalDisplayId kOuterDisplayId = PhysicalDisplayId::fromPort(254u); + + constexpr bool kIsPrimary = false; + TestableSurfaceFlinger::FakeHwcDisplayInjector(kOuterDisplayId, hal::DisplayType::PHYSICAL, + kIsPrimary) + .setHwcDisplayId(kOuterDisplayHwcId) + .inject(&mFlinger, mComposer); + + const auto outerDisplay = mFakeDisplayInjector.injectInternalDisplay( + [&](FakeDisplayDeviceInjector& injector) { + injector.setDisplayModes(mock::cloneForDisplay(kOuterDisplayId, kModes), + kModeId120); + }, + {.displayId = kOuterDisplayId, + .hwcDisplayId = kOuterDisplayHwcId, + .isPrimary = kIsPrimary}); + + const auto& innerDisplay = mDisplay; + + EXPECT_FALSE(innerDisplay->getDesiredActiveMode()); + EXPECT_FALSE(outerDisplay->getDesiredActiveMode()); + + EXPECT_EQ(innerDisplay->getActiveMode().modePtr->getId(), kModeId60); + EXPECT_EQ(outerDisplay->getActiveMode().modePtr->getId(), kModeId120); + + mFlinger.onActiveDisplayChanged(innerDisplay); + + EXPECT_EQ(NO_ERROR, + mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(), + mock::createDisplayModeSpecs(kModeId90.value(), + false, 0.f, 120.f))); + + EXPECT_EQ(NO_ERROR, + mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(), + mock::createDisplayModeSpecs(kModeId60.value(), + false, 0.f, 120.f))); + + // Transition on the inner display. + ASSERT_TRUE(innerDisplay->getDesiredActiveMode()); + EXPECT_EQ(innerDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId90); + + // No transition on the outer display. + EXPECT_FALSE(outerDisplay->getDesiredActiveMode()); + + const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; + EXPECT_CALL(*mComposer, + setActiveConfigWithConstraints(kInnerDisplayHwcId, + hal::HWConfigId(kModeId90.value()), _, _)) + .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); + + mFlinger.commit(); + + // Transition on the inner display. + ASSERT_TRUE(innerDisplay->getDesiredActiveMode()); + EXPECT_EQ(innerDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId90); + + // No transition on the outer display. + EXPECT_FALSE(outerDisplay->getDesiredActiveMode()); + + mFlinger.commit(); + + // Transition on the inner display. + EXPECT_FALSE(innerDisplay->getDesiredActiveMode()); + EXPECT_EQ(innerDisplay->getActiveMode().modePtr->getId(), kModeId90); + + // No transition on the outer display. + EXPECT_FALSE(outerDisplay->getDesiredActiveMode()); + EXPECT_EQ(outerDisplay->getActiveMode().modePtr->getId(), kModeId120); + + mFlinger.onActiveDisplayChanged(outerDisplay); + + // No transition on the inner display. + EXPECT_FALSE(innerDisplay->getDesiredActiveMode()); + + // Transition on the outer display. + ASSERT_TRUE(outerDisplay->getDesiredActiveMode()); + EXPECT_EQ(outerDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId60); + + EXPECT_CALL(*mComposer, + setActiveConfigWithConstraints(kOuterDisplayHwcId, + hal::HWConfigId(kModeId60.value()), _, _)) + .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); + + mFlinger.commit(); + + // No transition on the inner display. + EXPECT_FALSE(innerDisplay->getDesiredActiveMode()); + + // Transition on the outer display. + ASSERT_TRUE(outerDisplay->getDesiredActiveMode()); + EXPECT_EQ(outerDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId60); + + mFlinger.commit(); + + // No transition on the inner display. + EXPECT_FALSE(innerDisplay->getDesiredActiveMode()); + EXPECT_EQ(innerDisplay->getActiveMode().modePtr->getId(), kModeId90); + + // Transition on the outer display. + EXPECT_FALSE(outerDisplay->getDesiredActiveMode()); + EXPECT_EQ(outerDisplay->getActiveMode().modePtr->getId(), kModeId60); +} + } // namespace } // namespace android diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h index c78b6bdc18..3b36361657 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h @@ -38,4 +38,24 @@ inline DisplayModePtr createDisplayMode(PhysicalDisplayId displayId, DisplayMode return createDisplayMode(modeId, refreshRate, {}, {}, displayId); } +inline DisplayModePtr cloneForDisplay(PhysicalDisplayId displayId, const DisplayModePtr& modePtr) { + return DisplayMode::Builder(modePtr->getHwcId()) + .setId(modePtr->getId()) + .setPhysicalDisplayId(displayId) + .setVsyncPeriod(modePtr->getVsyncPeriod()) + .setGroup(modePtr->getGroup()) + .setResolution(modePtr->getResolution()) + .build(); +} + +inline DisplayModes cloneForDisplay(PhysicalDisplayId displayId, const DisplayModes& modes) { + DisplayModes clones; + + for (const auto& [id, modePtr] : modes) { + clones.try_emplace(id, cloneForDisplay(displayId, modePtr)); + } + + return clones; +} + } // namespace android::mock -- GitLab From cd7f6eb19a49c7803107021ac587c695dd90bd65 Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Tue, 6 Dec 2022 23:41:22 +0000 Subject: [PATCH 0609/1310] Make sendHint private in platform * Convert PerformanceHintManager sendHint to a private API * Have HWUI depend on private implementation Bug: b/261640114 Test: atest PerformanceHintNativeTestCases Test: atest FrameworksCoreTests:android.os.PerformanceHintManagerTest Change-Id: I9d13f1eaff856dc3d38de5f6c1b5cf92a75f6ec3 --- include/android/performance_hint.h | 41 ---------------------- include/private/performance_hint_private.h | 39 ++++++++++++++++++++ 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h index eed6b3339f..5fa47f64be 100644 --- a/include/android/performance_hint.h +++ b/include/android/performance_hint.h @@ -87,36 +87,6 @@ typedef struct APerformanceHintManager APerformanceHintManager; */ typedef struct APerformanceHintSession APerformanceHintSession; -/** - * Hints for the session used by {@link APerformanceHint_sendHint} to signal upcoming changes - * in the mode or workload. - */ -enum SessionHint { - /** - * This hint indicates a sudden increase in CPU workload intensity. It means - * that this hint session needs extra CPU resources immediately to meet the - * target duration for the current work cycle. - */ - CPU_LOAD_UP = 0, - /** - * This hint indicates a decrease in CPU workload intensity. It means that - * this hint session can reduce CPU resources and still meet the target duration. - */ - CPU_LOAD_DOWN = 1, - /* - * This hint indicates an upcoming CPU workload that is completely changed and - * unknown. It means that the hint session should reset CPU resources to a known - * baseline to prepare for an arbitrary load, and must wake up if inactive. - */ - CPU_LOAD_RESET = 2, - /* - * This hint indicates that the most recent CPU workload is resuming after a - * period of inactivity. It means that the hint session should allocate similar - * CPU resources to what was used previously, and must wake up if inactive. - */ - CPU_LOAD_RESUME = 3, -}; - /** * Acquire an instance of the performance hint manager. * @@ -189,17 +159,6 @@ int APerformanceHint_reportActualWorkDuration( void APerformanceHint_closeSession( APerformanceHintSession* session) __INTRODUCED_IN(__ANDROID_API_T__); -/** - * Sends performance hints to inform the hint session of changes in the workload. - * - * @param session The performance hint session instance to update. - * @param hint The hint to send to the session. - * @return 0 on success - * EPIPE if communication with the system service has failed. - */ -int APerformanceHint_sendHint( - APerformanceHintSession* session, int hint) __INTRODUCED_IN(__ANDROID_API_U__); - __END_DECLS #endif // ANDROID_NATIVE_PERFORMANCE_HINT_H diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h index f27f5f150f..1333bcc73b 100644 --- a/include/private/performance_hint_private.h +++ b/include/private/performance_hint_private.h @@ -24,6 +24,45 @@ __BEGIN_DECLS */ void APerformanceHint_setIHintManagerForTesting(void* iManager); +/** + * Hints for the session used to signal upcoming changes in the mode or workload. + */ +enum SessionHint { + /** + * This hint indicates a sudden increase in CPU workload intensity. It means + * that this hint session needs extra CPU resources immediately to meet the + * target duration for the current work cycle. + */ + CPU_LOAD_UP = 0, + /** + * This hint indicates a decrease in CPU workload intensity. It means that + * this hint session can reduce CPU resources and still meet the target duration. + */ + CPU_LOAD_DOWN = 1, + /* + * This hint indicates an upcoming CPU workload that is completely changed and + * unknown. It means that the hint session should reset CPU resources to a known + * baseline to prepare for an arbitrary load, and must wake up if inactive. + */ + CPU_LOAD_RESET = 2, + /* + * This hint indicates that the most recent CPU workload is resuming after a + * period of inactivity. It means that the hint session should allocate similar + * CPU resources to what was used previously, and must wake up if inactive. + */ + CPU_LOAD_RESUME = 3, +}; + +/** + * Sends performance hints to inform the hint session of changes in the workload. + * + * @param session The performance hint session instance to update. + * @param hint The hint to send to the session. + * @return 0 on success + * EPIPE if communication with the system service has failed. + */ +int APerformanceHint_sendHint(void* session, int hint); + __END_DECLS #endif // ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H -- GitLab From eed9d5db739e2342c4219a2ef23f77097b3581f7 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Wed, 14 Dec 2022 00:47:13 +0000 Subject: [PATCH 0610/1310] Treat extended range sRGB dataspaces as HDR Linear and non-linear extended sRGB support luminance values beyond the historic nominal white point of 80 nits defined for sRGB. To detect extended sRGB, we just check for the EXTENDED_RANGE bit. This also allows for custom extended range formats to be used to support HDR at the display level. This is useful for achieving HDR on devices that do not advertise fp16 support, which would otherwise be desired for non-video HDR formats as (a) having enough bits for "good enough" fidelity and (b) being a floating point format that allows for describing colors beyond the SDR max of 1.0. For instance, an application could use a 10-bit sRGB-encoded fixed point buffer to represent HDR as long as the rendered content is referred by a reasonable SDR luminance. Bug: 241001465 Test: Custom apk sending buffers directly to a SurfaceControl. Change-Id: I03ffa7f0a45633c9bb19e3ae7c17b189277ca06a --- libs/ui/include_types/ui/DataspaceUtils.h | 6 ++++-- libs/ui/tests/DataspaceUtils_test.cpp | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/libs/ui/include_types/ui/DataspaceUtils.h b/libs/ui/include_types/ui/DataspaceUtils.h index a461cb4e68..cd3116760b 100644 --- a/libs/ui/include_types/ui/DataspaceUtils.h +++ b/libs/ui/include_types/ui/DataspaceUtils.h @@ -22,8 +22,10 @@ namespace android { inline bool isHdrDataspace(ui::Dataspace dataspace) { const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK; + const auto range = dataspace & HAL_DATASPACE_RANGE_MASK; - return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG; + return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG || + range == HAL_DATASPACE_RANGE_EXTENDED; } -} // namespace android \ No newline at end of file +} // namespace android diff --git a/libs/ui/tests/DataspaceUtils_test.cpp b/libs/ui/tests/DataspaceUtils_test.cpp index 3e0967182b..ffe64382d0 100644 --- a/libs/ui/tests/DataspaceUtils_test.cpp +++ b/libs/ui/tests/DataspaceUtils_test.cpp @@ -29,12 +29,13 @@ TEST_F(DataspaceUtilsTest, isHdrDataspace) { EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_ITU_PQ)); EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_PQ)); EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_HLG)); + // The original formulation of scRGB indicates the same white points as that + // of sRGB, however scRGB may be used to implement HDR. + EXPECT_TRUE(isHdrDataspace(ui::Dataspace::V0_SCRGB_LINEAR)); + EXPECT_TRUE(isHdrDataspace(ui::Dataspace::V0_SCRGB)); EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SRGB_LINEAR)); - // scRGB defines a very wide gamut but not an expanded luminance range - EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SCRGB_LINEAR)); EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SRGB)); - EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SCRGB)); EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_JFIF)); EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_BT601_625)); EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_BT601_525)); -- GitLab From 205c6dda83a81150bea1357ce3d22b7a739b0a1f Mon Sep 17 00:00:00 2001 From: Theodore Dubois Date: Wed, 23 Nov 2022 15:06:33 -0800 Subject: [PATCH 0611/1310] Improve handling of hwcomposer with unsupported present fences An implementation such as HWC2OnFbAdapter always returns -1 for the present fence. FrameTimeline should not ignore invalid fences in mPendingPresentFences, since in this case every fence is invalid, resulting in resource leaks. Similarly, PresentLatencyTracker should not be used if HWC doesn't support present fences. Test: no logspam running on starnix (fuchsia), which uses framebuffer Test: booted emulator, no behavior change Test: booted panther, no behavior change Change-Id: I4a11edc443a1cd3a395fc8f97b93a5474d11fe31 --- services/surfaceflinger/FrameTimeline/FrameTimeline.cpp | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp index cd1ba70d84..27a099cd1f 100644 --- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp +++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp @@ -1175,7 +1175,7 @@ float FrameTimeline::computeFps(const std::unordered_set& layerIds) { std::optional FrameTimeline::getFirstSignalFenceIndex() const { for (size_t i = 0; i < mPendingPresentFences.size(); i++) { const auto& [fence, _] = mPendingPresentFences[i]; - if (fence && fence->isValid() && fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) { + if (fence && fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) { return i; } } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 6961f7cecf..609c2f58b4 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2430,7 +2430,9 @@ void SurfaceFlinger::postComposition() { const TimePoint compositeTime = TimePoint::fromNs(mCompositionEngine->getLastFrameRefreshTimestamp()); const Duration presentLatency = - mPresentLatencyTracker.trackPendingFrame(compositeTime, presentFenceTime); + !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE) + ? mPresentLatencyTracker.trackPendingFrame(compositeTime, presentFenceTime) + : Duration::zero(); const auto& schedule = mScheduler->getVsyncSchedule(); const TimePoint vsyncDeadline = schedule.vsyncDeadlineAfter(presentTime); -- GitLab From 273c18c3226262dcb8259d4788f6b1d7f0504de0 Mon Sep 17 00:00:00 2001 From: Rachel Lee Date: Tue, 13 Dec 2022 14:35:07 -0800 Subject: [PATCH 0612/1310] Move Choreographer to libgui. For Attached Choreographer, we want to store, access, and set attributes on a Choreographer from the SurfaceControl it is attached to. SurfaceControl is in libgui, so move Choreographer from libnativedisplay into libgui as well. The Android.bp dep chain: libandroid includes< libhwui < libnativedisplay < libgui Bug: 255838011 Test: atest ChoreographerNativeTest (no regression) Test: make and flash Test: presubmit Change-Id: Ie9f8cac93a888127ffa48d7061a467649e0ca694 --- libs/gui/Android.bp | 3 +++ libs/{nativedisplay => gui}/Choreographer.cpp | 2 +- .../include/nativedisplay => gui/include/gui}/Choreographer.h | 3 ++- libs/nativedisplay/AChoreographer.cpp | 2 +- libs/nativedisplay/Android.bp | 1 - 5 files changed, 7 insertions(+), 4 deletions(-) rename libs/{nativedisplay => gui}/Choreographer.cpp (99%) rename libs/{nativedisplay/include/nativedisplay => gui/include/gui}/Choreographer.h (98%) diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index a988e39de0..6c9c28a48f 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -192,6 +192,7 @@ cc_library_shared { "BitTube.cpp", "BLASTBufferQueue.cpp", "BufferItemConsumer.cpp", + "Choreographer.cpp", "CompositorTiming.cpp", "ConsumerBase.cpp", "CpuConsumer.cpp", @@ -234,6 +235,7 @@ cc_library_shared { export_header_lib_headers: [ "libgui_aidl_headers", + "jni_headers", ], aidl: { @@ -241,6 +243,7 @@ cc_library_shared { }, header_libs: [ + "jni_headers", "libdvr_headers", "libgui_aidl_headers", "libpdx_headers", diff --git a/libs/nativedisplay/Choreographer.cpp b/libs/gui/Choreographer.cpp similarity index 99% rename from libs/nativedisplay/Choreographer.cpp rename to libs/gui/Choreographer.cpp index 01e9f04d15..6b25b262c3 100644 --- a/libs/nativedisplay/Choreographer.cpp +++ b/libs/gui/Choreographer.cpp @@ -16,8 +16,8 @@ // #define LOG_NDEBUG 0 +#include #include -#include #undef LOG_TAG #define LOG_TAG "AChoreographer" diff --git a/libs/nativedisplay/include/nativedisplay/Choreographer.h b/libs/gui/include/gui/Choreographer.h similarity index 98% rename from libs/nativedisplay/include/nativedisplay/Choreographer.h rename to libs/gui/include/gui/Choreographer.h index bb63f291f9..89a7058dd6 100644 --- a/libs/nativedisplay/include/nativedisplay/Choreographer.h +++ b/libs/gui/include/gui/Choreographer.h @@ -16,8 +16,9 @@ #pragma once +#include #include -#include +#include #include #include diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp index e64165fef3..66a40f1278 100644 --- a/libs/nativedisplay/AChoreographer.cpp +++ b/libs/nativedisplay/AChoreographer.cpp @@ -16,8 +16,8 @@ #include #include +#include #include -#include #include #include #include diff --git a/libs/nativedisplay/Android.bp b/libs/nativedisplay/Android.bp index 70de33da12..8d8a2bc244 100644 --- a/libs/nativedisplay/Android.bp +++ b/libs/nativedisplay/Android.bp @@ -56,7 +56,6 @@ cc_library_shared { ":libgui_frame_event_aidl", "AChoreographer.cpp", "ADisplay.cpp", - "Choreographer.cpp", "surfacetexture/surface_texture.cpp", "surfacetexture/SurfaceTexture.cpp", "surfacetexture/ImageConsumer.cpp", -- GitLab From 7bc23bf22fb7e85c58a8439e3d178056ae976bdc Mon Sep 17 00:00:00 2001 From: Ambrus Weisz Date: Tue, 4 Oct 2022 13:13:07 +0000 Subject: [PATCH 0613/1310] Update TouchInputMapper to support new VirtualNavigationTouchpad. This change propagates type associations to InputDevice.cpp. The type associations are used there to define the device type for touch input mapper to use. This is required because of the virtual nature of the navigation touchpad the implementation can't rely on idc files. Design doc: http://go/virtual-touchpad Test: atest cts/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualNavigationTouchpadTest Test: atest frameworks/native/services/inputflinger/tests/InputReader_test.cpp Bug: 252767726 Change-Id: Ia4252460c16deb093d61b059e5030d902a4c9a2c --- .../inputflinger/include/InputReaderBase.h | 8 ++- services/inputflinger/reader/InputDevice.cpp | 13 +++++ .../inputflinger/reader/include/InputDevice.h | 7 +++ .../reader/mapper/TouchInputMapper.cpp | 57 +++++++++++-------- .../reader/mapper/TouchInputMapper.h | 2 + .../tests/FakeInputReaderPolicy.cpp | 5 ++ .../tests/FakeInputReaderPolicy.h | 1 + .../inputflinger/tests/InputReader_test.cpp | 11 ++++ 8 files changed, 78 insertions(+), 26 deletions(-) diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index b8a6dade34..3e4db43cbe 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -191,6 +191,9 @@ struct InputReaderConfiguration { // The set of disabled input devices (disabledDevices) has changed. CHANGE_ENABLED_STATE = 1 << 9, + // The device type has been updated. + CHANGE_DEVICE_TYPE = 1 << 10, + // All devices must be reopened. CHANGE_MUST_REOPEN = 1 << 31, }; @@ -212,6 +215,10 @@ struct InputReaderConfiguration { // Used to determine which DisplayViewport should be tied to which InputDevice. std::unordered_map uniqueIdAssociations; + // The associations between input device ports device types. + // This is used to determine which device type and source should be tied to which InputDevice. + std::unordered_map deviceTypeAssociations; + // The suggested display ID to show the cursor. int32_t defaultPointerDisplayId; @@ -326,7 +333,6 @@ struct InputReaderConfiguration { std::optional getDisplayViewportById(int32_t displayId) const; void setDisplayViewports(const std::vector& viewports); - void dump(std::string& dump) const; void dumpViewport(std::string& dump, const DisplayViewport& viewport) const; diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 11b5209b0f..6dfe5f52fa 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -56,6 +56,16 @@ InputDevice::InputDevice(InputReaderContext* context, int32_t id, int32_t genera InputDevice::~InputDevice() {} +template +std::optional getValueByKey(const std::unordered_map& map, K key) { + auto it = map.find(key); + std::optional value = std::nullopt; + if (it != map.end()) { + value = it->second; + } + return value; +} + bool InputDevice::isEnabled() { if (!hasEventHubDevices()) { return false; @@ -291,6 +301,9 @@ std::list InputDevice::configure(nsecs_t when, const InputReaderConf context.getConfiguration(&configuration); mConfiguration.addAll(&configuration); }); + + mAssociatedDeviceType = + getValueByKey(config->deviceTypeAssociations, mIdentifier.location); } if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS)) { diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 6fa21e5362..af59fe2aca 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -65,6 +65,9 @@ public: inline std::optional getAssociatedDisplayUniqueId() const { return mAssociatedDisplayUniqueId; } + inline std::optional getDeviceTypeAssociation() const { + return mAssociatedDeviceType; + } inline std::optional getAssociatedViewport() const { return mAssociatedViewport; } @@ -180,6 +183,7 @@ private: bool mIsExternal; std::optional mAssociatedDisplayPort; std::optional mAssociatedDisplayUniqueId; + std::optional mAssociatedDeviceType; std::optional mAssociatedViewport; bool mHasMic; bool mDropUntilNextSync; @@ -408,6 +412,9 @@ public: inline std::optional getAssociatedDisplayUniqueId() const { return mDevice.getAssociatedDisplayUniqueId(); } + inline std::optional getDeviceTypeAssociation() const { + return mDevice.getDeviceTypeAssociation(); + } inline std::optional getAssociatedViewport() const { return mDevice.getAssociatedViewport(); } diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index cefc44ef7a..160f9eb906 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -392,33 +392,10 @@ void TouchInputMapper::configureParameters() { } } - if (getDeviceContext().hasInputProperty(INPUT_PROP_DIRECT)) { - // The device is a touch screen. - mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN; - } else if (getDeviceContext().hasInputProperty(INPUT_PROP_POINTER)) { - // The device is a pointing device like a track pad. - mParameters.deviceType = Parameters::DeviceType::POINTER; - } else { - // The device is a touch pad of unknown purpose. - mParameters.deviceType = Parameters::DeviceType::POINTER; - } + configureDeviceType(); mParameters.hasButtonUnderPad = getDeviceContext().hasInputProperty(INPUT_PROP_BUTTONPAD); - std::string deviceTypeString; - if (getDeviceContext().getConfiguration().tryGetProperty("touch.deviceType", - deviceTypeString)) { - if (deviceTypeString == "touchScreen") { - mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN; - } else if (deviceTypeString == "touchNavigation") { - mParameters.deviceType = Parameters::DeviceType::TOUCH_NAVIGATION; - } else if (deviceTypeString == "pointer") { - mParameters.deviceType = Parameters::DeviceType::POINTER; - } else if (deviceTypeString != "default") { - ALOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.c_str()); - } - } - mParameters.orientationAware = mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN; getDeviceContext().getConfiguration().tryGetProperty("touch.orientationAware", mParameters.orientationAware); @@ -444,7 +421,9 @@ void TouchInputMapper::configureParameters() { mParameters.associatedDisplayIsExternal = false; if (mParameters.orientationAware || mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN || - mParameters.deviceType == Parameters::DeviceType::POINTER) { + mParameters.deviceType == Parameters::DeviceType::POINTER || + (mParameters.deviceType == Parameters::DeviceType::TOUCH_NAVIGATION && + getDeviceContext().getAssociatedViewport())) { mParameters.hasAssociatedDisplay = true; if (mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN) { mParameters.associatedDisplayIsExternal = getDeviceContext().isExternal(); @@ -473,6 +452,34 @@ void TouchInputMapper::configureParameters() { mParameters.enableForInactiveViewport); } +void TouchInputMapper::configureDeviceType() { + if (getDeviceContext().hasInputProperty(INPUT_PROP_DIRECT)) { + // The device is a touch screen. + mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN; + } else if (getDeviceContext().hasInputProperty(INPUT_PROP_POINTER)) { + // The device is a pointing device like a track pad. + mParameters.deviceType = Parameters::DeviceType::POINTER; + } else { + // The device is a touch pad of unknown purpose. + mParameters.deviceType = Parameters::DeviceType::POINTER; + } + + // Type association takes precedence over the device type found in the idc file. + std::string deviceTypeString = getDeviceContext().getDeviceTypeAssociation().value_or(""); + if (deviceTypeString.empty()) { + getDeviceContext().getConfiguration().tryGetProperty("touch.deviceType", deviceTypeString); + } + if (deviceTypeString == "touchScreen") { + mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN; + } else if (deviceTypeString == "touchNavigation") { + mParameters.deviceType = Parameters::DeviceType::TOUCH_NAVIGATION; + } else if (deviceTypeString == "pointer") { + mParameters.deviceType = Parameters::DeviceType::POINTER; + } else if (deviceTypeString != "default" && deviceTypeString != "") { + ALOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.c_str()); + } +} + void TouchInputMapper::dumpParameters(std::string& dump) { dump += INDENT3 "Parameters:\n"; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 34ba62515f..50a7ea387a 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -814,6 +814,8 @@ private: static void assignPointerIds(const RawState& last, RawState& current); void rotateAndScale(float& x, float& y) const; + + void configureDeviceType(); }; } // namespace android diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp index 3af4298434..dc7e581abd 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp +++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp @@ -126,6 +126,11 @@ void FakeInputReaderPolicy::addInputPortAssociation(const std::string& inputPort mConfig.portAssociations.insert({inputPort, displayPort}); } +void FakeInputReaderPolicy::addDeviceTypeAssociation(const std::string& inputPort, + const std::string& type) { + mConfig.deviceTypeAssociations.insert({inputPort, type}); +} + void FakeInputReaderPolicy::addInputUniqueIdAssociation(const std::string& inputUniqueId, const std::string& displayUniqueId) { mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId}); diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h index c16cda404e..faa9c01a98 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.h +++ b/services/inputflinger/tests/FakeInputReaderPolicy.h @@ -55,6 +55,7 @@ public: bool updateViewport(const DisplayViewport& viewport); void addExcludedDeviceName(const std::string& deviceName); void addInputPortAssociation(const std::string& inputPort, uint8_t displayPort); + void addDeviceTypeAssociation(const std::string& inputPort, const std::string& type); void addInputUniqueIdAssociation(const std::string& inputUniqueId, const std::string& displayUniqueId); void addDisabledDevice(int32_t deviceId); diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 4cc48f6fc3..3516092cef 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -6505,6 +6505,17 @@ TEST_F(SingleTouchInputMapperTest, ButtonIsReleasedOnTouchUp) { WithCoords(toDisplayX(100), toDisplayY(200)), WithButtonState(0)))); } +TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsSetToTouchNavigation_setsCorrectType) { + mFakePolicy->addDeviceTypeAssociation(DEVICE_LOCATION, "touchNavigation"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + SingleTouchInputMapper& mapper = addMapperAndConfigure(); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); + + ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mapper.getSources()); +} + // --- TouchDisplayProjectionTest --- class TouchDisplayProjectionTest : public SingleTouchInputMapperTest { -- GitLab From 8ef7e1440aedc4047d64178c6e97ddbc9b975f83 Mon Sep 17 00:00:00 2001 From: Nolan Scobie Date: Fri, 14 Oct 2022 10:52:43 -0400 Subject: [PATCH 0614/1310] Rework how RenderEngine and SurfaceFlinger include Skia This is in preparation for including Perfetto in Skia, see go/skia-perfetto-android (excerpt below) librenderengine includes Skia via whole_static_libs: ["libskia_renderengine"], which scoops everything out of libskia_renderengine and plops it into librenderengine, including any of Skia's dependencies. It seems that if another target (e.g. SurfaceFlinger) includes both Perfetto (on its own) and librenderengine (which would now transitively include Perfetto by way of Skia), there will be a conflict. Per b/169779783, a common workaround for this dependency conflict would be to switch from whole_static_libs to static_libs: ["libskia_renderengine"] in librenderengine (which won't include Skia's dependencies), and then separately include Skia's dependencies into librenderengine by using defaults: ["skia_renderengine_deps"]. This has the effect of not propagating them further. Then, downstream users of libskia_renderengine (e.g. SurfaceFlinger) that previously accessed Skia APIs directly from their inclusion of librenderengine (since librenderengine included Skia via whole_static_libs) would have to both pull in their own reference to Skia (via static_libs) and Skia's dependencies (via defaults). These two requirements have been simplified by the addition of cc_defaults: librenderengine_deps, which can be used to pull in both at the same time. Bug: 259248961 Test: presubmits (builds) Change-Id: I46750a0336a29d8d32ad2cb79a71c90461272fce --- libs/renderengine/Android.bp | 17 +++++++++++++++-- libs/renderengine/benchmark/Android.bp | 2 +- libs/renderengine/tests/Android.bp | 2 +- services/surfaceflinger/Android.bp | 2 +- .../surfaceflinger/CompositionEngine/Android.bp | 1 + .../surfaceflinger/Tracing/tools/Android.bp | 2 +- .../surfaceflinger/tests/unittests/Android.bp | 1 + 7 files changed, 21 insertions(+), 6 deletions(-) diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp index 04e24ed9ed..8d19c45527 100644 --- a/libs/renderengine/Android.bp +++ b/libs/renderengine/Android.bp @@ -111,9 +111,23 @@ filegroup { ], } +// Used to consolidate and simplify pulling Skia & Skia deps into targets that depend on +// librenderengine. This allows shared deps to be deduplicated (e.g. Perfetto), which doesn't seem +// possible if libskia_renderengine is just pulled into librenderengine via whole_static_libs. +cc_defaults { + name: "librenderengine_deps", + defaults: ["skia_renderengine_deps"], + static_libs: ["libskia_renderengine"], +} + +// Note: if compilation fails when adding librenderengine as a dependency, try adding +// librenderengine_deps to the defaults field of your dependent target. cc_library_static { name: "librenderengine", - defaults: ["librenderengine_defaults"], + defaults: [ + "librenderengine_defaults", + "librenderengine_deps", + ], double_loadable: true, cflags: [ "-fvisibility=hidden", @@ -132,7 +146,6 @@ cc_library_static { include_dirs: [ "external/skia/src/gpu", ], - whole_static_libs: ["libskia_renderengine"], lto: { thin: true, }, diff --git a/libs/renderengine/benchmark/Android.bp b/libs/renderengine/benchmark/Android.bp index afbe6cfa4d..55c34cd059 100644 --- a/libs/renderengine/benchmark/Android.bp +++ b/libs/renderengine/benchmark/Android.bp @@ -25,7 +25,7 @@ cc_benchmark { name: "librenderengine_bench", defaults: [ "android.hardware.graphics.composer3-ndk_shared", - "skia_deps", + "librenderengine_deps", "surfaceflinger_defaults", ], srcs: [ diff --git a/libs/renderengine/tests/Android.bp b/libs/renderengine/tests/Android.bp index 6f328d738c..50e166d2a7 100644 --- a/libs/renderengine/tests/Android.bp +++ b/libs/renderengine/tests/Android.bp @@ -25,7 +25,7 @@ cc_test { name: "librenderengine_test", defaults: [ "android.hardware.graphics.composer3-ndk_shared", - "skia_deps", + "librenderengine_deps", "surfaceflinger_defaults", ], test_suites: ["device-tests"], diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index b1bd705f19..ff2578cc64 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -26,8 +26,8 @@ cc_defaults { name: "libsurfaceflinger_defaults", defaults: [ "android.hardware.graphics.composer3-ndk_shared", + "librenderengine_deps", "surfaceflinger_defaults", - "skia_renderengine_deps", ], cflags: [ "-DLOG_TAG=\"SurfaceFlinger\"", diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp index 30d34a581b..f3a0186e3e 100644 --- a/services/surfaceflinger/CompositionEngine/Android.bp +++ b/services/surfaceflinger/CompositionEngine/Android.bp @@ -11,6 +11,7 @@ cc_defaults { name: "libcompositionengine_defaults", defaults: [ "android.hardware.graphics.composer3-ndk_shared", + "librenderengine_deps", "surfaceflinger_defaults", ], cflags: [ diff --git a/services/surfaceflinger/Tracing/tools/Android.bp b/services/surfaceflinger/Tracing/tools/Android.bp index e8fe734a8f..b6435a8a13 100644 --- a/services/surfaceflinger/Tracing/tools/Android.bp +++ b/services/surfaceflinger/Tracing/tools/Android.bp @@ -25,8 +25,8 @@ cc_binary { name: "layertracegenerator", defaults: [ "libsurfaceflinger_mocks_defaults", + "librenderengine_deps", "surfaceflinger_defaults", - "skia_renderengine_deps", ], srcs: [ ":libsurfaceflinger_sources", diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index 8b0cd78732..cd74547dc4 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -140,6 +140,7 @@ cc_defaults { defaults: [ "android.hardware.graphics.common-ndk_static", "android.hardware.graphics.composer3-ndk_static", + "librenderengine_deps", ], static_libs: [ "android.hardware.common-V2-ndk", -- GitLab From 47db1c715b7b8689f41e04034c5c35fd1e407675 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Tue, 13 Dec 2022 19:20:47 +0000 Subject: [PATCH 0615/1310] Extract HardwareState conversion into a class and test it Putting the conversion logic into its own class makes it more obvious which variables are used for HardwareState conversion, and also provides a clean public API to write tests against. Bug: 251196347 Test: m inputflinger_tests && \ $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests \ --gtest_filter='*HardwareStateConverterTest*' Test: atest inputflinger_tests Change-Id: I26771887b6b2eae46c9cec7190499da0016fdb1f --- services/inputflinger/reader/Android.bp | 2 + .../reader/mapper/TouchpadInputMapper.cpp | 88 +------ .../reader/mapper/TouchpadInputMapper.h | 13 +- .../gestures/HardwareStateConverter.cpp | 109 +++++++++ .../mapper/gestures/HardwareStateConverter.h | 58 +++++ services/inputflinger/tests/Android.bp | 1 + .../tests/HardwareStateConverter_test.cpp | 219 ++++++++++++++++++ 7 files changed, 405 insertions(+), 85 deletions(-) create mode 100644 services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp create mode 100644 services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h create mode 100644 services/inputflinger/tests/HardwareStateConverter_test.cpp diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index f37f0fa70e..1535df3205 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -29,6 +29,7 @@ cc_library_headers { "include", "mapper", "mapper/accumulator", + "mapper/gestures", ], } @@ -61,6 +62,7 @@ filegroup { "mapper/accumulator/SingleTouchMotionAccumulator.cpp", "mapper/accumulator/TouchButtonAccumulator.cpp", "mapper/gestures/GesturesLogging.cpp", + "mapper/gestures/HardwareStateConverter.cpp", ], } diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 956a7aae54..f535ab4bbb 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -16,8 +16,6 @@ #include "../Macros.h" -#include - #include #include #include "TouchCursorInputMapperCommon.h" @@ -106,7 +104,7 @@ TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext) : InputMapper(deviceContext), mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter), mPointerController(getContext()->getPointerController(getDeviceId())), - mTouchButtonAccumulator(deviceContext) { + mStateConverter(deviceContext) { mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD); mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext)); // Even though we don't explicitly delete copy/move semantics, it's safe to @@ -116,16 +114,6 @@ TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext) mGestureInterpreter->SetCallback(gestureInterpreterCallback, this); // TODO(b/251196347): set a property provider, so we can change gesture properties. // TODO(b/251196347): set a timer provider, so the library can use timers. - - RawAbsoluteAxisInfo slotAxisInfo; - getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo); - if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) { - ALOGW("Touchpad \"%s\" doesn't have a valid ABS_MT_SLOT axis, and probably won't work " - "properly.", - getDeviceName().c_str()); - } - mMotionAccumulator.configure(getDeviceContext(), slotAxisInfo.maxValue + 1, true); - mTouchButtonAccumulator.configure(); } TouchpadInputMapper::~TouchpadInputMapper() { @@ -139,82 +127,28 @@ uint32_t TouchpadInputMapper::getSources() const { } std::list TouchpadInputMapper::reset(nsecs_t when) { - mCursorButtonAccumulator.reset(getDeviceContext()); - mTouchButtonAccumulator.reset(); - mMscTimestamp = 0; + mStateConverter.reset(); mButtonState = 0; return InputMapper::reset(when); } std::list TouchpadInputMapper::process(const RawEvent* rawEvent) { - std::list out = {}; - if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { - out = sync(rawEvent->when, rawEvent->readTime); - } - if (rawEvent->type == EV_MSC && rawEvent->code == MSC_TIMESTAMP) { - mMscTimestamp = rawEvent->value; + std::optional state = mStateConverter.processRawEvent(rawEvent); + if (state) { + return sendHardwareState(rawEvent->when, rawEvent->readTime, *state); + } else { + return {}; } - mCursorButtonAccumulator.process(rawEvent); - mMotionAccumulator.process(rawEvent); - mTouchButtonAccumulator.process(rawEvent); - return out; } -std::list TouchpadInputMapper::sync(nsecs_t when, nsecs_t readTime) { - HardwareState hwState; - // The gestures library uses doubles to represent timestamps in seconds. - hwState.timestamp = std::chrono::duration(std::chrono::nanoseconds(when)).count(); - hwState.msc_timestamp = - std::chrono::duration(std::chrono::microseconds(mMscTimestamp)).count(); - - hwState.buttons_down = 0; - if (mCursorButtonAccumulator.isLeftPressed()) { - hwState.buttons_down |= GESTURES_BUTTON_LEFT; - } - if (mCursorButtonAccumulator.isMiddlePressed()) { - hwState.buttons_down |= GESTURES_BUTTON_MIDDLE; - } - if (mCursorButtonAccumulator.isRightPressed()) { - hwState.buttons_down |= GESTURES_BUTTON_RIGHT; - } - if (mCursorButtonAccumulator.isBackPressed() || mCursorButtonAccumulator.isSidePressed()) { - hwState.buttons_down |= GESTURES_BUTTON_BACK; - } - if (mCursorButtonAccumulator.isForwardPressed() || mCursorButtonAccumulator.isExtraPressed()) { - hwState.buttons_down |= GESTURES_BUTTON_FORWARD; - } - - std::vector fingers; - for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) { - MultiTouchMotionAccumulator::Slot slot = mMotionAccumulator.getSlot(i); - if (slot.isInUse()) { - FingerState& fingerState = fingers.emplace_back(); - fingerState = {}; - fingerState.touch_major = slot.getTouchMajor(); - fingerState.touch_minor = slot.getTouchMinor(); - fingerState.width_major = slot.getToolMajor(); - fingerState.width_minor = slot.getToolMinor(); - fingerState.pressure = slot.getPressure(); - fingerState.orientation = slot.getOrientation(); - fingerState.position_x = slot.getX(); - fingerState.position_y = slot.getY(); - fingerState.tracking_id = slot.getTrackingId(); - } - } - hwState.fingers = fingers.data(); - hwState.finger_cnt = fingers.size(); - hwState.touch_cnt = mTouchButtonAccumulator.getTouchCount(); - +std::list TouchpadInputMapper::sendHardwareState(nsecs_t when, nsecs_t readTime, + SelfContainedHardwareState schs) { mProcessing = true; - mGestureInterpreter->PushHardwareState(&hwState); + mGestureInterpreter->PushHardwareState(&schs.state); mProcessing = false; - std::list out = processGestures(when, readTime); - - mMotionAccumulator.finishSync(); - mMscTimestamp = 0; - return out; + return processGestures(when, readTime); } void TouchpadInputMapper::consumeGesture(const Gesture* gesture) { diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index fe6b1fe759..c6863f5994 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include @@ -24,9 +25,7 @@ #include "InputDevice.h" #include "InputMapper.h" #include "NotifyArgs.h" -#include "accumulator/CursorButtonAccumulator.h" -#include "accumulator/MultiTouchMotionAccumulator.h" -#include "accumulator/TouchButtonAccumulator.h" +#include "gestures/HardwareStateConverter.h" #include "include/gestures.h" @@ -44,7 +43,8 @@ public: void consumeGesture(const Gesture* gesture); private: - [[nodiscard]] std::list sync(nsecs_t when, nsecs_t readTime); + [[nodiscard]] std::list sendHardwareState(nsecs_t when, nsecs_t readTime, + SelfContainedHardwareState schs); [[nodiscard]] std::list processGestures(nsecs_t when, nsecs_t readTime); NotifyArgs handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture); [[nodiscard]] std::list handleButtonsChange(nsecs_t when, nsecs_t readTime, @@ -61,10 +61,7 @@ private: mGestureInterpreter; std::shared_ptr mPointerController; - CursorButtonAccumulator mCursorButtonAccumulator; - MultiTouchMotionAccumulator mMotionAccumulator; - TouchButtonAccumulator mTouchButtonAccumulator; - int32_t mMscTimestamp = 0; + HardwareStateConverter mStateConverter; bool mProcessing = false; std::vector mGesturesToProcess; diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp new file mode 100644 index 0000000000..49c13ca709 --- /dev/null +++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gestures/HardwareStateConverter.h" + +#include +#include + +#include + +namespace android { + +HardwareStateConverter::HardwareStateConverter(InputDeviceContext& deviceContext) + : mDeviceContext(deviceContext), mTouchButtonAccumulator(deviceContext) { + RawAbsoluteAxisInfo slotAxisInfo; + deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo); + if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) { + ALOGW("Touchpad \"%s\" doesn't have a valid ABS_MT_SLOT axis, and probably won't work " + "properly.", + deviceContext.getName().c_str()); + } + mMotionAccumulator.configure(deviceContext, slotAxisInfo.maxValue + 1, true); + mTouchButtonAccumulator.configure(); +} + +std::optional HardwareStateConverter::processRawEvent( + const RawEvent* rawEvent) { + std::optional out; + if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { + out = produceHardwareState(rawEvent->when); + mMotionAccumulator.finishSync(); + mMscTimestamp = 0; + } + if (rawEvent->type == EV_MSC && rawEvent->code == MSC_TIMESTAMP) { + mMscTimestamp = rawEvent->value; + } + mCursorButtonAccumulator.process(rawEvent); + mMotionAccumulator.process(rawEvent); + mTouchButtonAccumulator.process(rawEvent); + return out; +} + +SelfContainedHardwareState HardwareStateConverter::produceHardwareState(nsecs_t when) { + SelfContainedHardwareState schs; + // The gestures library uses doubles to represent timestamps in seconds. + schs.state.timestamp = std::chrono::duration(std::chrono::nanoseconds(when)).count(); + schs.state.msc_timestamp = + std::chrono::duration(std::chrono::microseconds(mMscTimestamp)).count(); + + schs.state.buttons_down = 0; + if (mCursorButtonAccumulator.isLeftPressed()) { + schs.state.buttons_down |= GESTURES_BUTTON_LEFT; + } + if (mCursorButtonAccumulator.isMiddlePressed()) { + schs.state.buttons_down |= GESTURES_BUTTON_MIDDLE; + } + if (mCursorButtonAccumulator.isRightPressed()) { + schs.state.buttons_down |= GESTURES_BUTTON_RIGHT; + } + if (mCursorButtonAccumulator.isBackPressed() || mCursorButtonAccumulator.isSidePressed()) { + schs.state.buttons_down |= GESTURES_BUTTON_BACK; + } + if (mCursorButtonAccumulator.isForwardPressed() || mCursorButtonAccumulator.isExtraPressed()) { + schs.state.buttons_down |= GESTURES_BUTTON_FORWARD; + } + + schs.fingers.clear(); + for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) { + MultiTouchMotionAccumulator::Slot slot = mMotionAccumulator.getSlot(i); + if (slot.isInUse()) { + FingerState& fingerState = schs.fingers.emplace_back(); + fingerState = {}; + fingerState.touch_major = slot.getTouchMajor(); + fingerState.touch_minor = slot.getTouchMinor(); + fingerState.width_major = slot.getToolMajor(); + fingerState.width_minor = slot.getToolMinor(); + fingerState.pressure = slot.getPressure(); + fingerState.orientation = slot.getOrientation(); + fingerState.position_x = slot.getX(); + fingerState.position_y = slot.getY(); + fingerState.tracking_id = slot.getTrackingId(); + } + } + schs.state.fingers = schs.fingers.data(); + schs.state.finger_cnt = schs.fingers.size(); + schs.state.touch_cnt = mTouchButtonAccumulator.getTouchCount(); + return schs; +} + +void HardwareStateConverter::reset() { + mCursorButtonAccumulator.reset(mDeviceContext); + mTouchButtonAccumulator.reset(); + mMscTimestamp = 0; +} + +} // namespace android diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h new file mode 100644 index 0000000000..fd63c05b1b --- /dev/null +++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h @@ -0,0 +1,58 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include + +#include "EventHub.h" +#include "InputDevice.h" +#include "accumulator/CursorButtonAccumulator.h" +#include "accumulator/MultiTouchMotionAccumulator.h" +#include "accumulator/TouchButtonAccumulator.h" + +#include "include/gestures.h" + +namespace android { + +// A HardwareState struct, but bundled with a vector to contain its FingerStates, so you don't have +// to worry about where that memory is allocated. +struct SelfContainedHardwareState { + HardwareState state; + std::vector fingers; +}; + +// Converts RawEvents into the HardwareState structs used by the gestures library. +class HardwareStateConverter { +public: + HardwareStateConverter(InputDeviceContext& deviceContext); + + std::optional processRawEvent(const RawEvent* event); + void reset(); + +private: + SelfContainedHardwareState produceHardwareState(nsecs_t when); + + InputDeviceContext& mDeviceContext; + CursorButtonAccumulator mCursorButtonAccumulator; + MultiTouchMotionAccumulator mMotionAccumulator; + TouchButtonAccumulator mTouchButtonAccumulator; + int32_t mMscTimestamp = 0; +}; + +} // namespace android diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 53d821f197..547a488b7d 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -44,6 +44,7 @@ cc_test { "FakeInputReaderPolicy.cpp", "FakePointerController.cpp", "FocusResolver_test.cpp", + "HardwareStateConverter_test.cpp", "InputMapperTest.cpp", "InputProcessor_test.cpp", "InputProcessorConverter_test.cpp", diff --git a/services/inputflinger/tests/HardwareStateConverter_test.cpp b/services/inputflinger/tests/HardwareStateConverter_test.cpp new file mode 100644 index 0000000000..79218816bd --- /dev/null +++ b/services/inputflinger/tests/HardwareStateConverter_test.cpp @@ -0,0 +1,219 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "FakeEventHub.h" +#include "FakeInputReaderPolicy.h" +#include "InstrumentedInputReader.h" +#include "TestConstants.h" +#include "TestInputListener.h" + +namespace android { + +class HardwareStateConverterTest : public testing::Test { +protected: + static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000; + static constexpr int32_t EVENTHUB_ID = 1; + + void SetUp() { + mFakeEventHub = std::make_unique(); + mFakePolicy = sp::make(); + mFakeListener = std::make_unique(); + mReader = std::make_unique(mFakeEventHub, mFakePolicy, + *mFakeListener); + mDevice = newDevice(); + + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, 7, 0, 0, 0); + } + + std::shared_ptr newDevice() { + InputDeviceIdentifier identifier; + identifier.name = "device"; + identifier.location = "USB1"; + identifier.bus = 0; + std::shared_ptr device = + std::make_shared(mReader->getContext(), DEVICE_ID, /* generation= */ 2, + identifier); + mReader->pushNextDevice(device); + mFakeEventHub->addDevice(EVENTHUB_ID, identifier.name, InputDeviceClass::TOUCHPAD, + identifier.bus); + mReader->loopOnce(); + return device; + } + + void processAxis(HardwareStateConverter& conv, nsecs_t when, int32_t type, int32_t code, + int32_t value) { + RawEvent event; + event.when = when; + event.readTime = READ_TIME; + event.deviceId = EVENTHUB_ID; + event.type = type; + event.code = code; + event.value = value; + std::optional schs = conv.processRawEvent(&event); + EXPECT_FALSE(schs.has_value()); + } + + std::optional processSync(HardwareStateConverter& conv, + nsecs_t when) { + RawEvent event; + event.when = when; + event.readTime = READ_TIME; + event.deviceId = EVENTHUB_ID; + event.type = EV_SYN; + event.code = SYN_REPORT; + event.value = 0; + return conv.processRawEvent(&event); + } + + std::shared_ptr mFakeEventHub; + sp mFakePolicy; + std::unique_ptr mFakeListener; + std::unique_ptr mReader; + std::shared_ptr mDevice; +}; + +TEST_F(HardwareStateConverterTest, OneFinger) { + const nsecs_t time = 1500000000; + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + HardwareStateConverter conv(deviceContext); + + processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123); + processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MAJOR, 5); + processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MINOR, 4); + processAxis(conv, time, EV_ABS, ABS_MT_PRESSURE, 42); + processAxis(conv, time, EV_ABS, ABS_MT_ORIENTATION, 2); + + processAxis(conv, time, EV_ABS, ABS_X, 50); + processAxis(conv, time, EV_ABS, ABS_Y, 100); + processAxis(conv, time, EV_ABS, ABS_PRESSURE, 42); + + processAxis(conv, time, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, time, EV_KEY, BTN_TOOL_FINGER, 1); + std::optional schs = processSync(conv, time); + + ASSERT_TRUE(schs.has_value()); + const HardwareState& state = schs->state; + EXPECT_NEAR(1.5, state.timestamp, EPSILON); + EXPECT_EQ(0, state.buttons_down); + EXPECT_EQ(1, state.touch_cnt); + + ASSERT_EQ(1, state.finger_cnt); + const FingerState& finger = state.fingers[0]; + EXPECT_EQ(123, finger.tracking_id); + EXPECT_NEAR(50, finger.position_x, EPSILON); + EXPECT_NEAR(100, finger.position_y, EPSILON); + EXPECT_NEAR(5, finger.touch_major, EPSILON); + EXPECT_NEAR(4, finger.touch_minor, EPSILON); + EXPECT_NEAR(42, finger.pressure, EPSILON); + EXPECT_NEAR(2, finger.orientation, EPSILON); + EXPECT_EQ(0u, finger.flags); + + EXPECT_EQ(0, state.rel_x); + EXPECT_EQ(0, state.rel_y); + EXPECT_EQ(0, state.rel_wheel); + EXPECT_EQ(0, state.rel_wheel_hi_res); + EXPECT_EQ(0, state.rel_hwheel); + EXPECT_NEAR(0.0, state.msc_timestamp, EPSILON); +} + +TEST_F(HardwareStateConverterTest, TwoFingers) { + const nsecs_t time = ARBITRARY_TIME; + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + HardwareStateConverter conv(deviceContext); + + processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123); + processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MAJOR, 5); + processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MINOR, 4); + processAxis(conv, time, EV_ABS, ABS_MT_PRESSURE, 42); + processAxis(conv, time, EV_ABS, ABS_MT_ORIENTATION, 2); + + processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 456); + processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, -20); + processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 40); + processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MAJOR, 8); + processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MINOR, 7); + processAxis(conv, time, EV_ABS, ABS_MT_PRESSURE, 21); + processAxis(conv, time, EV_ABS, ABS_MT_ORIENTATION, 1); + + processAxis(conv, time, EV_ABS, ABS_X, 50); + processAxis(conv, time, EV_ABS, ABS_Y, 100); + processAxis(conv, time, EV_ABS, ABS_PRESSURE, 42); + + processAxis(conv, time, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, time, EV_KEY, BTN_TOOL_DOUBLETAP, 1); + std::optional schs = processSync(conv, time); + + ASSERT_TRUE(schs.has_value()); + ASSERT_EQ(2, schs->state.finger_cnt); + const FingerState& finger1 = schs->state.fingers[0]; + EXPECT_EQ(123, finger1.tracking_id); + EXPECT_NEAR(50, finger1.position_x, EPSILON); + EXPECT_NEAR(100, finger1.position_y, EPSILON); + EXPECT_NEAR(5, finger1.touch_major, EPSILON); + EXPECT_NEAR(4, finger1.touch_minor, EPSILON); + EXPECT_NEAR(42, finger1.pressure, EPSILON); + EXPECT_NEAR(2, finger1.orientation, EPSILON); + EXPECT_EQ(0u, finger1.flags); + + const FingerState& finger2 = schs->state.fingers[1]; + EXPECT_EQ(456, finger2.tracking_id); + EXPECT_NEAR(-20, finger2.position_x, EPSILON); + EXPECT_NEAR(40, finger2.position_y, EPSILON); + EXPECT_NEAR(8, finger2.touch_major, EPSILON); + EXPECT_NEAR(7, finger2.touch_minor, EPSILON); + EXPECT_NEAR(21, finger2.pressure, EPSILON); + EXPECT_NEAR(1, finger2.orientation, EPSILON); + EXPECT_EQ(0u, finger2.flags); +} + +TEST_F(HardwareStateConverterTest, ButtonPressed) { + const nsecs_t time = ARBITRARY_TIME; + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + HardwareStateConverter conv(deviceContext); + + processAxis(conv, time, EV_KEY, BTN_LEFT, 1); + std::optional schs = processSync(conv, time); + + ASSERT_TRUE(schs.has_value()); + EXPECT_EQ(GESTURES_BUTTON_LEFT, schs->state.buttons_down); +} + +TEST_F(HardwareStateConverterTest, MscTimestamp) { + const nsecs_t time = ARBITRARY_TIME; + mFakeEventHub->setMscEvent(EVENTHUB_ID, MSC_TIMESTAMP); + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + HardwareStateConverter conv(deviceContext); + + processAxis(conv, time, EV_MSC, MSC_TIMESTAMP, 1200000); + std::optional schs = processSync(conv, time); + + ASSERT_TRUE(schs.has_value()); + EXPECT_NEAR(1.2, schs->state.msc_timestamp, EPSILON); +} + +} // namespace android -- GitLab From ce86cc3726bdcc66198e498c420ad39fd56e37d7 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Wed, 14 Dec 2022 20:36:33 +0000 Subject: [PATCH 0616/1310] Make assertPosition a method of FakePointerController This way it can be used in tests that aren't based on InputMapperTest. Bug: 251196347 Test: m inputflinger_tests && \ $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests \ --gtest_filter='*HardwareStateConverterTest*' Test: atest inputflinger_tests Change-Id: I39a7f08ac9b705d12e45acc3e3773b0046e80e5c --- .../inputflinger/tests/FakePointerController.cpp | 9 +++++++++ services/inputflinger/tests/FakePointerController.h | 2 ++ services/inputflinger/tests/InputMapperTest.cpp | 7 ------- services/inputflinger/tests/InputMapperTest.h | 1 - services/inputflinger/tests/InputReader_test.cpp | 12 ++++++------ 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp index 635366bff2..ab7879f1aa 100644 --- a/services/inputflinger/tests/FakePointerController.cpp +++ b/services/inputflinger/tests/FakePointerController.cpp @@ -16,6 +16,8 @@ #include "FakePointerController.h" +#include + namespace android { void FakePointerController::setBounds(float minX, float minY, float maxX, float maxY) { @@ -56,6 +58,13 @@ void FakePointerController::setDisplayViewport(const DisplayViewport& viewport) mDisplayId = viewport.displayId; } +void FakePointerController::assertPosition(float x, float y) { + float actualX, actualY; + getPosition(&actualX, &actualY); + ASSERT_NEAR(x, actualX, 1); + ASSERT_NEAR(y, actualY, 1); +} + bool FakePointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const { *outMinX = mMinX; diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h index f00870f13c..d10cbcd68b 100644 --- a/services/inputflinger/tests/FakePointerController.h +++ b/services/inputflinger/tests/FakePointerController.h @@ -38,6 +38,8 @@ public: int32_t getDisplayId() const override; void setDisplayViewport(const DisplayViewport& viewport) override; + void assertPosition(float x, float y); + private: bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override; void move(float deltaX, float deltaY) override; diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp index 3cd7c1b0f2..a02ef0548e 100644 --- a/services/inputflinger/tests/InputMapperTest.cpp +++ b/services/inputflinger/tests/InputMapperTest.cpp @@ -166,11 +166,4 @@ void InputMapperTest::assertPointerCoords(const PointerCoords& coords, float x, ASSERT_NEAR(distance, coords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE), EPSILON); } -void InputMapperTest::assertPosition(const FakePointerController& controller, float x, float y) { - float actualX, actualY; - controller.getPosition(&actualX, &actualY); - ASSERT_NEAR(x, actualX, 1); - ASSERT_NEAR(y, actualY, 1); -} - } // namespace android diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h index b3401c37e2..63ca44c402 100644 --- a/services/inputflinger/tests/InputMapperTest.h +++ b/services/inputflinger/tests/InputMapperTest.h @@ -89,7 +89,6 @@ protected: float size, float touchMajor, float touchMinor, float toolMajor, float toolMinor, float orientation, float distance, float scaledAxisEpsilon = 1.f); - static void assertPosition(const FakePointerController& controller, float x, float y); }; } // namespace android diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 4cc48f6fc3..78b70aa39f 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -4344,7 +4344,7 @@ TEST_F(CursorInputMapperTest, Process_WhenModeIsPointer_ShouldMoveThePointerArou ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], 110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f)); + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f)); } TEST_F(CursorInputMapperTest, Process_PointerCapture) { @@ -4372,7 +4372,7 @@ TEST_F(CursorInputMapperTest, Process_PointerCapture) { ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], 10.0f, 20.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 100.0f, 200.0f)); + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(100.0f, 200.0f)); // Button press. process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1); @@ -4411,7 +4411,7 @@ TEST_F(CursorInputMapperTest, Process_PointerCapture) { ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], 30.0f, 40.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 100.0f, 200.0f)); + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(100.0f, 200.0f)); // Disable pointer capture and check that the device generation got bumped // and events are generated the usual way. @@ -4431,7 +4431,7 @@ TEST_F(CursorInputMapperTest, Process_PointerCapture) { ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], 110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); - ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f)); + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f)); } /** @@ -4548,7 +4548,7 @@ TEST_F(CursorInputMapperTest, ConfigureDisplayId_NoAssociatedViewport) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(110.0f, 220.0f)))); - ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f)); + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f)); } TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) { @@ -4575,7 +4575,7 @@ TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(110.0f, 220.0f)))); - ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f)); + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f)); } TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPointerDisplay) { -- GitLab From 487c49b0b539d26fe51b90b55801e4b97492f642 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 2 Dec 2022 15:48:57 -0800 Subject: [PATCH 0617/1310] Test coordinates of ACTION_OUTSIDE events The ACTION_OUTSIDE events have valid coordinates when the window watching outside touches has the same owner as the first touched window. Add a test to document this behaviour. Bug: 211379801 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: I701089ca39999ad8548ca8b83f11ac29f75a8cce --- .../tests/InputDispatcher_test.cpp | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 41c174a1f7..d2ff097d18 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -56,6 +57,7 @@ static constexpr int32_t DEVICE_ID = 1; static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; static constexpr int32_t SECOND_DISPLAY_ID = 1; +static constexpr int32_t ACTION_OUTSIDE = AMOTION_EVENT_ACTION_OUTSIDE; static constexpr int32_t POINTER_1_DOWN = AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); static constexpr int32_t POINTER_2_DOWN = @@ -84,6 +86,7 @@ static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 1000ms; struct PointF { float x; float y; + auto operator<=>(const PointF&) const = default; }; /** @@ -137,6 +140,24 @@ MATCHER_P(WithSource, source, "InputEvent with specified source") { return arg.getSource() == source; } +MATCHER_P2(WithCoords, x, y, "MotionEvent with specified coordinates") { + if (arg.getPointerCount() != 1) { + *result_listener << "Expected 1 pointer, got " << arg.getPointerCount(); + return false; + } + return arg.getX(0 /*pointerIndex*/) == x && arg.getY(0 /*pointerIndex*/) == y; +} + +MATCHER_P(WithPointers, pointers, "MotionEvent with specified pointers") { + // Build a map for the received pointers, by pointer id + std::map actualPointers; + for (size_t pointerIndex = 0; pointerIndex < arg.getPointerCount(); pointerIndex++) { + const int32_t pointerId = arg.getPointerId(pointerIndex); + actualPointers[pointerId] = {arg.getX(pointerIndex), arg.getY(pointerIndex)}; + } + return pointers == actualPointers; +} + // --- FakeInputDispatcherPolicy --- class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface { @@ -2535,6 +2556,39 @@ TEST_F(InputDispatcherTest, InterceptKeyIfKeyUp) { window->consumeKeyUp(ADISPLAY_ID_DEFAULT); } +/** + * Two windows. First is a regular window. Second does not overlap with the first, and has + * WATCH_OUTSIDE_TOUCH. + * Both windows are owned by the same UID. + * Tap first window. Make sure that the second window receives ACTION_OUTSIDE with correct, non-zero + * coordinates. The coordinates are not zeroed out because both windows are owned by the same UID. + */ +TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinates) { + std::shared_ptr application = std::make_shared(); + sp window = sp::make(application, mDispatcher, + "First Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect{0, 0, 100, 100}); + + sp outsideWindow = + sp::make(application, mDispatcher, "Second Window", + ADISPLAY_ID_DEFAULT); + outsideWindow->setFrame(Rect{100, 100, 200, 200}); + outsideWindow->setWatchOutsideTouch(true); + // outsideWindow must be above 'window' to receive ACTION_OUTSIDE events when 'window' is tapped + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {outsideWindow, window}}}); + + // Tap on first window. + NotifyMotionArgs motionArgs = + generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {PointF{50, 50}}); + mDispatcher->notifyMotion(&motionArgs); + window->consumeMotionDown(); + // The coordinates of the tap in 'outsideWindow' are relative to its top left corner. + // Therefore, we should offset them by (100, 100) relative to the screen's top left corner. + outsideWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_OUTSIDE), WithCoords(-50, -50))); +} + /** * This test documents the behavior of WATCH_OUTSIDE_TOUCH. The window will get ACTION_OUTSIDE when * a another pointer causes ACTION_DOWN to be sent to another window for the first time. Only one @@ -2570,7 +2624,9 @@ TEST_F(InputDispatcherTest, ActionOutsideSentOnlyWhenAWindowIsTouched) { motionArgs = generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {PointF{-10, -10}, PointF{105, 105}}); mDispatcher->notifyMotion(&motionArgs); - window->consumeMotionOutside(); + const std::map expectedPointers{{0, PointF{-10, -10}}, {1, PointF{105, 105}}}; + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_OUTSIDE), WithPointers(expectedPointers))); secondWindow->consumeMotionDown(); thirdWindow->assertNoEvents(); -- GitLab From 7e3ed12f1ffc53282c0bbef044b38c4e67ac2e34 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Wed, 14 Dec 2022 22:05:01 +0000 Subject: [PATCH 0618/1310] libjpegrecoverymap: add unit tests for encode APIs Test: itself Bug: b/252835416 Change-Id: Id5bca17202b908cbb79c8ce1e1ad8da6c24bde69 --- .../tests/data/raw_yuv420_image.yuv420 | 1 + .../tests/recoverymap_test.cpp | 158 ++++++++++++++++-- 2 files changed, 147 insertions(+), 12 deletions(-) create mode 100644 libs/jpegrecoverymap/tests/data/raw_yuv420_image.yuv420 diff --git a/libs/jpegrecoverymap/tests/data/raw_yuv420_image.yuv420 b/libs/jpegrecoverymap/tests/data/raw_yuv420_image.yuv420 new file mode 100644 index 0000000000..c043da6423 --- /dev/null +++ b/libs/jpegrecoverymap/tests/data/raw_yuv420_image.yuv420 @@ -0,0 +1 @@ +ûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòñîçßÙØØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÑËÓÊÐÎÌÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÎÏÑÒÔ×ÙÙØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÉÅÑÔÕ±F""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÄÇÍÓ×××ÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÚËÅÆÑÆ0!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÒÙÛØÖ×ØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÎÏÍÐÔ&+########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÇËÔÙ×ØÜØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÅÎÓÐÉÏ% ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÊÇÊÓÖÔÖÜØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÒÐÊÆÊÕ$1%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÑËÎØÚÔÔÚØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÍÍÑÕÎ  ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÆÌÙÝØ×ÞØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÐÍÌÍÐÆ1#########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÍÈËÕÛØÖ×ØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÑÆÏÑÎÇ-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÏÌÏØÝÚØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎËÔÓÉÈ(%!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÊÉÎÖÛÚØÙØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌËÐÑËÏ%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÅÇËÑÖ×××ØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÑÌÅËÍØ"#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÒÔÕÕÖÖ×ÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÇÏÏÕËÃ#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôéêçßÙØØ×ØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÓÖÅÂÃá¿ãÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððõõïáØØÙØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎοÏÐÚÔÍÅÄÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòóëÝÔÖÙØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÓÑÁËÐÇÝÈÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÎÌËËÌÍÍÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÏÏÇäÜâæáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÌÌÍÏÏÏÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÒÒÊèßâäÞââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÌÍÎÏÎÊÆÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎËËÆèàâãÞââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÌÍÎÍÈ¿¸················································································································································································································································¹»»ãßâäàââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÌÍÎÎÇ»²³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³°±³áßáäâââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌËÌÐÑË¿´ºººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººº³³µãááããââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍËÌÐÒÌ¿´²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²¸¶¶åâàâãââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÌÎÏȺ®·················································································································································································································································´µäáàãåââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÓÍÒε·¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¸¸¶áÞáâÝââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÎÒÊÎ˳µ³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³¶··äâåæâââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÐÎÑÉÎ˵¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸´µ¶ãáääßââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÌÑËÑϸ»´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´¶·ãáââÜââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎËËÒÍÒε¶¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸µ··äâäåàââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÌÒËÏ˱²················································································································································································································································³³³ßÞâäáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÉÏ͸¼³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³¼º·àÝàãßââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÏÈÑÔÄÌÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÌÅêäåæâââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÉÔÊËÌÕÄ×ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎËÐÆÊÑÔÈËÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÊËÐÌÔÑËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÉÎÔÑÍÃÎÍÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÓËËÇÏÙÎÓÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÆÄÕÚ׺1 ########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÛËÊÃÆÏ+,########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÇÉÙÑÊÕ($$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÍÌÍÐÉ)#""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÓÔÎÃËç®ÕÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÃÎÚÑÏÑÅÚÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÊÐËÐÄÏÅÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎ×ÊÌÈÖÉâÌÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎËÉÖÑÖÃÑÀËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎËÒÆÊÊÕÞÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍËÓÊÏÒÄÎÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüýúôñóóðññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññòòòóóóóóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÜÙÕÑÎÍÍÎÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÑÐÕßäãáâããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããææàåßãûùûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùú÷òðóóðóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóòòòñññððòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÔÓÒÑÑÐÑÑÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÈÈÏÚáàßàßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßâãßçâæýûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûøú÷òñóôñññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññóóóòññððòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×ÖÔÒÐÍÌËÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÌÍÔàæåããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããßáÞæáäûøûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüùôòôôñððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððóóóóóóóóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÝÛ×ÓÐÎÍÍÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÊËÒÝãáÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞããÞåàâú÷ûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüýúóñòòîòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòñññòòóóôòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÛØÔÐÏÐÒÓÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÍÎÕàæåããââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââåäÞãßäýüûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûøñïñðíððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððïïðððñññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÙÖÑÌÉÈÈÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌËËÑÜäääåââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââßßÚàÜáûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûøúøóòõõóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòõõôóòñððòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×ÖÔÒÐÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÒÐÓÛàààâààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààæèäëåæûöûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûú÷÷ûýûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûýüúøöôòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÔÙáéîññðòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúýùùýÿüúûüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüùüùÿöñÿùûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòíóîÝÓÖÙÖÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÜÖÛìöóðóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòðõðßÖÙÜÚÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÜ×ÜíöóðòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòõîÞÕ×Ù×ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ×ÕÜëõóðòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòóóëÜÓÓÕÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÓÓÛéóòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòñëâÜÜÝÝÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÜÝãìòòññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòñïíìíïïññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññìíïòóñððòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòññóõõôõóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóôôòññññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòðñóôñïîòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòðññïîñóôòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûú÷ôðíòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââáãâàßâäåââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûûøõòðññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàââàÞßßÞááááááááááááááááááááááááááááááááááááááááááááááááááááááááûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûûúøöôóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââáâäæçæääââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûüûúùøùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââáâèñ÷÷ööúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûüüûûûýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââáàè÷ÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââáÞåõþüúü÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷ûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüûûúúúûûúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââãÞãôýú÷ùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüûûúùúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââæàåöÿýúýùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààáåìó÷ùùùûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüôöúýþþüûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúýþþþýüûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúùùúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþûûúúûüýþûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùüüûúúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýùùùùùùøøûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùøùúüýýýýûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúúúúûüúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùúùùùúúù÷ûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúúúúúûûüúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷øùúüýüûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùùùúúûûûüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷øúûûûûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûøùùúúûúúýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüýýüûùùúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûøùúúúúùøùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúùúûù÷÷úýûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùúûûúùöõóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóââââââââââââââââââââââââââââââââââââââââââââââââââââââââçèéèèìõûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüüú÷ôòññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññááááááááááááááááááááááááááááááááááááááááááááááááááááááááÜÞÞÝÞåñûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüüýüúöóñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââääãààèõÿûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]VUW]cd^Wkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‰‡†ˆŠ†}tkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\—”‘ކynkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒˆ…ƒƒxj_kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‰ˆˆ‹Œ…xmkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽ††‰‰{pkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\……………………………………………………………………………………………………………………………………………………‚‚„‡‡~nbkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹Œ€nakkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹Œ€nakkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\……………………………………………………………………………………………………………………………………………………‚‚„‡‡~nbkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽ††‰‰{pkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‰ˆˆ‹Œ…xmkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒˆ†ƒƒxj_kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\—”‘ކynkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‰‡†ˆŠ†}tkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]VUW]cd^Wkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}xtty|{x{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{`dd\ncbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzwvwz{xuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuy{_df]m`bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyuwy{{yur{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{rv]eg]l]bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyqv}~{vtsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxqu]fh]j[bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyypw~xuw|zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzy|agg[i[bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyqz€|tt~ŠqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqŠˆffdYi]bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyu|xot‡™²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²–mf`Vj`bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyw}vmtŒ¢¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨ªŸqf^Ukcbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyqy}vnu¢©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª¡_Ukdhbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyry}vov‹Ÿ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««±©g[mce_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyty|vqvˆ˜ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª°¬m`obc]bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvyzvsw„¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯žŸh_n`c`bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyxvuy€†ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttŠ]Zk_eebbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzywvwy|}yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyo~WXi]fhbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy|yvvyzywxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxn]^k\cfbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}yuvz{wszzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzu‰fdn[`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyzz{{yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwxxyyzz{yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwxxyyzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvwwxxyyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuvvwxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\[^`^[^fnllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllnf^[^`^[\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\_][_goiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiog_[]_]Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\W[]\[_hphhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhph_[\][W\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\VY\\[_hpkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkph_[\\YV\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\VZ\[Z^goooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog^Z[\ZV\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\X[][Y\dkmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmkd\Y[][X\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\[^_[XYahffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffhaYX[_^[\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\]_`\WX_e^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^e_XW\`_]\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ}‰Š‚‡zbYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYZ^`\VRTX\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcdeddcbbbbbbbbbbbbbbbbbbbbbbbba_aiu}}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ~‰Š€€…xaeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeY]`^YWZ^\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcccccbbccccccccccccccccccccccccb`cly‚„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ~‰‰~~ƒv_SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSVZ_^[Z_c\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccbbaaaabbbbbbbbbbbbbbbbbbbbbbbbbadn{†‰ˆƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‰ˆ||ƒv`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\RW\\YY^c\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbdcba`abb`````````````````````````_blz…ˆ‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ~ˆ‡{|„yd^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^VZ^\WVY^\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbdca`acegaaaaaaaaaaaaaaaaaaaaaaaaa`blx„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ~‡†{~‡j^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^dghc[VWZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbdcaacgknjjjjjjjjjjjjjjjjjjjjjjjjkiipzƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ}†…|€‹„q€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€yzypd[YZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbdbabekquyyyyyyyyyyyyyyyyyyyyyyyy{xvz‚†„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ|†…|Žˆvƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‰‰†zk`\]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcbabgnuz††††††††††††††††††††††††ˆƒƒ‰‹ˆ„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ|…‰{dVZcVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV________________________________________________________edb_`j{‰ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ~†ˆzdVX`ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddggggggggggggggggggggggggggggggggggggggggggggggggggggggggbba^_iz‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ††yeXW\TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTdddddddddddddddddddddddddddddddddddddddddddddddddddddddd_aa_`iy…ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„†„xh][]^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`bdcdkxƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ……‚zphddZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiigjllkpzƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†„}yurp††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††ruxwuw~…ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ƒ€€ƒƒ€}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ~…ƒ~‚‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ…‚€ƒˆ‹‰†~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~…‰ŒŠ„‚…‰ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ„†ˆŠ‹Œ~~~~~~~~~~~~~~~~~~~~~~~~~‚yvy~ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚€‚‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ‡ŠŒ‰„‚‡Œƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„}xtsrr††††††††††††††††††††††††††††††††††††††††††††††††††††††††~ƒ~~„Šƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†xohdcdZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZiiiiiiiiiiiiiiiiiiiiiiiicgihfhpxƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒˆ€th_[Z[^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\\\\\\\\\\\\\\\\\\\\\\\\X[][Z^hpƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒŠsd[WXZTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTddddddddddddddddddddddddacecafqzƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‹‚rcZX[^ddddddddddddddddddddddddddddddddddddddddddddddddddddddddggggggggggggggggggggggggfhifdit~ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒŒ‚rc[Z^aVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV________________________abb_]alvƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„ƒ~†…Ÿžœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHFEPDHD=vJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ>DL\-=EKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ||||||||||||||||||||||||||||||||||||||||||||||||||||||||z‡u~~{ššœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHFEPDIF@z‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†‡†ŠNRQRHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuq}luwu•–œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHFDPEKJE€††††††††††††††††††††††††††††††††††††††††††††††††††††††††š™“NLFEHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmyhrtt–˜œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHFDPELMJ†ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ}ACAAHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuq|ktwx›žœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHFDODLML‰ƒ„‚†KNMNHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxv€mvyzž¡œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHEOCJLKˆƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ’‘Œ‹KJEDHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvwmuwxœ œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENAGII†‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŽŒ‡…DB=DL\-=EKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHMGAAGKMKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKIe~••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••Ÿ’upnlioooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHLXBF\9?š‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘—”glof~loooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHDP>E\>F›••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••œ˜prqgzioooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH=I=DYCL“““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““—”wvsithoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHAJAFSDK~€~srqmpkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHJOIHLADbKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK^_jkmqmroooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHNPMIFACMHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHFIfhjtjuoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIKIDFIFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBEnllvdtoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH?AGGCKQGJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJFJwqnw_qoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH@SJ?MK?HBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBRKI^=RQJHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHARIANKCPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ=9;S4KMIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHBPHDQKH_GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGPML^7HHDHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHDNGHSILo‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†€y~GLE@HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGLEKTEN~ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ™•ŽŽPPIEHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHKJDMS>Mˆ‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ƒ‚†JMLNHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHNIBNQ8K‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰|€„FIJMHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHOHANP5I‘ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ’•’IFCFHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHKIGGJOUY‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡”ŽƒueVKEHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHKIGFHMRUJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJLKIGDA?>HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHKIGFFIMOKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK9:=@DGJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHKJGEEFHJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIITSSRQPPOHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHKIHFEFFGBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBPOMJHECBHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIIHHGGGGNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNABCEHJKLHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIIJJIIEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEFHKNQSUHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGHIJKKKKIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIJIHFDCA@HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‡‹‹Š–žžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžž–ŠŒ‹‡ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ†‰Œ‹ŠŽ—Ÿ››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››Ÿ—ŽŠ‹Œ‰†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ„‡‹ŠŠŽ˜ šššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššš ˜ŽŠŠŠ‡„ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‚†‰‰‰Ž˜ œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ ˜Ž‰‰‰†‚ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠƒ†‰‰ˆ–žŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸž–ˆ‰‰‡ƒŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ…ˆŠ‰‡‹“››“‹‡‰Šˆ…ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠˆŠŒ‰†ˆ˜••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••˜ˆ†‰ŒŠˆŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŒ‰†‡Ž••އ†‰ŒŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ˆˆ‡‡‡ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooomnpqrrqqllllllllllllllllllllllllroov……‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‰‰‰‰ˆˆŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooonoopppoonnnnnnnnnnnnnnnnnnnnnnnnommt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooopoonnmmmppppppppppppppppppppppppmjkr|ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹‹‹ŒŒŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠooooooooooooooooooooooooooooooooooooooooooooooooooooooooqpnllllmnnnnnnnnnnnnnnnnnnnnnnnnljjq{‚‚€ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ˆˆ‰Š‹‹ŒŒŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠooooooooooooooooooooooooooooooooooooooooooooooooooooooooqpnlkmnpmmmmmmmmmmmmmmmmmmmmmmmmpnns}ƒ‚€ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†††††††††††††††††††††††††††††††††††††††††††††††††††††††††…†‡ˆ‰Š‹ŒŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooorpmlmosurrrrrrrrrrrrrrrrrrrrrrrrxutx€„ƒ€ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„ƒƒ„†‡‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠooooooooooooooooooooooooooooooooooooooooooooooooooooooooqomlnsx{}}}}}}}}}}}}}}}}}}}}}}}}}z~„‡…ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒ„†‡‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠooooooooooooooooooooooooooooooooooooooooooooooooooooooooqommpu{†††††††††††††††††††††††††‚†ˆ…ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ†ˆŠŒŒŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠpppppppppppppppppppppppppppppppppppppppppppppppppppppppprolknu}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…ˆŠ‹Œ‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹ooooooooooooooooooooooooooooooooooooooooooooooooooooooooomjjnu}‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…‡‰Š‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹oooooooooooooooooooooooooooooooooooooooooooooooooooooooomkjjnu}‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚ƒ…†ˆ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠppppppppppppppppppppppppppppppppppppppppppppppppppppppppmlklpw~‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒƒ„…†‡‡ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuurqqruz‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„……††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzyy{~ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„„„……ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚ƒ„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„„„„„……………………………………………………………………………………………………………………………………………………‰ˆ‡……………ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚………………………………………………………………‚‡‹Š…ƒ†Šƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ~‚†…‚†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚ƒ„„……††††††††††††††††††††††††††††††††††††††††††††††††††††††††{{{{{{{{{{{{{{{{{{{{{{{{w|~zy}‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚ƒ„…†‡‡ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆuuuuuuuuuuuuuuuuuuuuuuuuquxvssx}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ„…‡ˆ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠppppppppppppppppppppppppmpsqmnu{ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ„†ˆŠ‹Œ‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹ooooooooooooooooooooooooknpnkmt|ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…‡‰‹Œ‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹oooooooooooooooooooooooolnomjmv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…‡‰ŒŽŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠpppppppppppppppppppppppplopmknwƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ \ No newline at end of file diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp index 6dea27f0a8..c3c6fd4e2b 100644 --- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp +++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp @@ -22,9 +22,11 @@ #include #define RAW_P010_IMAGE "/sdcard/Documents/raw_p010_image.p010" -#define RAW_P010_IMAGE_WIDTH 1280 -#define RAW_P010_IMAGE_HEIGHT 720 +#define RAW_YUV420_IMAGE "/sdcard/Documents/raw_yuv420_image.yuv420" #define JPEG_IMAGE "/sdcard/Documents/jpeg_image.jpg" +#define TEST_IMAGE_WIDTH 1280 +#define TEST_IMAGE_HEIGHT 720 +#define DEFAULT_JPEG_QUALITY 90 #define SAVE_ENCODING_RESULT true #define SAVE_DECODING_RESULT true @@ -40,6 +42,7 @@ protected: virtual void TearDown(); struct jpegr_uncompressed_struct mRawP010Image; + struct jpegr_uncompressed_struct mRawYuv420Image; struct jpegr_compressed_struct mJpegImage; }; @@ -49,6 +52,7 @@ RecoveryMapTest::~RecoveryMapTest() {} void RecoveryMapTest::SetUp() {} void RecoveryMapTest::TearDown() { free(mRawP010Image.data); + free(mRawYuv420Image.data); free(mJpegImage.data); } @@ -108,24 +112,153 @@ TEST_F(RecoveryMapTest, writeXmpThenRead) { ASSERT_EQ(metadata_expected.rangeScalingFactor, metadata_read.rangeScalingFactor); } -TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) { + +/* Test Encode API-0 and decode */ +// TODO: enable when tonemapper is ready. +//TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) { +// int ret; +// +// // Load input files. +// if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) { +// FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; +// } +// mRawP010Image.width = TEST_IMAGE_WIDTH; +// mRawP010Image.height = TEST_IMAGE_HEIGHT; +// mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100; +// +// RecoveryMap recoveryMap; +// +// jpegr_compressed_struct jpegR; +// jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); +// jpegR.data = malloc(jpegR.maxLength); +// ret = recoveryMap.encodeJPEGR( +// &mRawP010Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, 90, nullptr); +// if (ret != OK) { +// FAIL() << "Error code is " << ret; +// } +// if (SAVE_ENCODING_RESULT) { +// // Output image data to file +// std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr"; +// std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); +// if (!imageFile.is_open()) { +// ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); +// } +// imageFile.write((const char*)jpegR.data, jpegR.length); +// } +// +// jpegr_uncompressed_struct decodedJpegR; +// int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4; +// decodedJpegR.data = malloc(decodedJpegRSize); +// ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR); +// if (ret != OK) { +// FAIL() << "Error code is " << ret; +// } +// if (SAVE_DECODING_RESULT) { +// // Output image data to file +// std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10"; +// std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); +// if (!imageFile.is_open()) { +// ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); +// } +// imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize); +// } +// +// free(jpegR.data); +// free(decodedJpegR.data); +//} + +/* Test Encode API-1 and decode */ +TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrThenDecode) { int ret; // Load input files. if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) { FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; } - mRawP010Image.width = RAW_P010_IMAGE_WIDTH; - mRawP010Image.height = RAW_P010_IMAGE_HEIGHT; + mRawP010Image.width = TEST_IMAGE_WIDTH; + mRawP010Image.height = TEST_IMAGE_HEIGHT; mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100; + if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709; + + RecoveryMap recoveryMap; + + jpegr_compressed_struct jpegR; + jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + ret = recoveryMap.encodeJPEGR( + &mRawP010Image, &mRawYuv420Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + if (SAVE_ENCODING_RESULT) { + // Output image data to file + std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)jpegR.data, jpegR.length); + } + + jpegr_uncompressed_struct decodedJpegR; + int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4; + decodedJpegR.data = malloc(decodedJpegRSize); + ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + if (SAVE_DECODING_RESULT) { + // Output image data to file + std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize); + } + + free(jpegR.data); + free(decodedJpegR.data); +} + +/* Test Encode API-2 and decode */ +TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrAndJpegThenDecode) { + int ret; + + // Load input files. + if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + mRawP010Image.width = TEST_IMAGE_WIDTH; + mRawP010Image.height = TEST_IMAGE_HEIGHT; + mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100; + + if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709; + + if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) { + FAIL() << "Load file " << JPEG_IMAGE << " failed"; + } + mJpegImage.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709; + RecoveryMap recoveryMap; jpegr_compressed_struct jpegR; - jpegR.maxLength = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * sizeof(uint8_t); + jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); jpegR.data = malloc(jpegR.maxLength); ret = recoveryMap.encodeJPEGR( - &mRawP010Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, 90, nullptr); + &mRawP010Image, &mRawYuv420Image, &mJpegImage, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR); if (ret != OK) { FAIL() << "Error code is " << ret; } @@ -140,7 +273,7 @@ TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) { } jpegr_uncompressed_struct decodedJpegR; - int decodedJpegRSize = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * 4; + int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4; decodedJpegR.data = malloc(decodedJpegRSize); ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR); if (ret != OK) { @@ -160,6 +293,7 @@ TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) { free(decodedJpegR.data); } +/* Test Encode API-3 and decode */ TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) { int ret; @@ -167,8 +301,8 @@ TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) { if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) { FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; } - mRawP010Image.width = RAW_P010_IMAGE_WIDTH; - mRawP010Image.height = RAW_P010_IMAGE_HEIGHT; + mRawP010Image.width = TEST_IMAGE_WIDTH; + mRawP010Image.height = TEST_IMAGE_HEIGHT; mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100; if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) { @@ -179,7 +313,7 @@ TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) { RecoveryMap recoveryMap; jpegr_compressed_struct jpegR; - jpegR.maxLength = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * sizeof(uint8_t); + jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); jpegR.data = malloc(jpegR.maxLength); ret = recoveryMap.encodeJPEGR( &mRawP010Image, &mJpegImage, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR); @@ -197,7 +331,7 @@ TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) { } jpegr_uncompressed_struct decodedJpegR; - int decodedJpegRSize = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * 4; + int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4; decodedJpegR.data = malloc(decodedJpegRSize); ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR); if (ret != OK) { -- GitLab From 8b4cb55c654999f091bf176cf683514e7676d214 Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Wed, 14 Dec 2022 16:24:31 -0800 Subject: [PATCH 0619/1310] [SF] Add supportMixedColorSpace variables into OverlayProperties aidl interface. Bug: 242588489 Test: build and flash Change-Id: I34ae0280256615538fc9b9e662f0b04dfa22cb51 --- libs/gui/aidl/android/gui/OverlayProperties.aidl | 2 ++ services/surfaceflinger/SurfaceFlinger.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/libs/gui/aidl/android/gui/OverlayProperties.aidl b/libs/gui/aidl/android/gui/OverlayProperties.aidl index 75cea157aa..1af574655b 100644 --- a/libs/gui/aidl/android/gui/OverlayProperties.aidl +++ b/libs/gui/aidl/android/gui/OverlayProperties.aidl @@ -23,4 +23,6 @@ parcelable OverlayProperties { int[] dataspaces; } SupportedBufferCombinations[] combinations; + + boolean supportMixedColorSpaces; } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index fd03ba3ab7..d230dfdc08 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1414,6 +1414,7 @@ status_t SurfaceFlinger::getOverlaySupport(gui::OverlayProperties* outProperties outCombination.dataspaces = std::move(dataspaces); outProperties->combinations.emplace_back(outCombination); } + outProperties->supportMixedColorSpaces = aidlProperties.supportMixedColorSpaces; return NO_ERROR; } -- GitLab From c539dbb9812e30632ca2946ef6b1150127db0182 Mon Sep 17 00:00:00 2001 From: Arthur Hung Date: Thu, 8 Dec 2022 07:45:36 +0000 Subject: [PATCH 0620/1310] Fix wallpaper window multi-touch If the touched window has a wallpaper, then the touch event will be duplicated to the wallpaper window when the foreground window received a touch event. When the foreground supports split touch, the wallpaper should deliver the same events of foreground window. And when the foreground window is transferred (caused by slippery or transfer touch) to the new touched window, it has to check if the wallpaper window should deliver the cancel event or keep in current touch state. Bug: 240308355 Test: atest inputflinger_tests Change-Id: Ie53279f18838c459e528b24709aebf739290dc41 --- .../dispatcher/InputDispatcher.cpp | 196 +++++++++++++---- .../inputflinger/dispatcher/InputDispatcher.h | 20 +- .../tests/InputDispatcher_test.cpp | 198 ++++++++++++------ 3 files changed, 310 insertions(+), 104 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 4aac377b42..906bb1b0d9 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2060,6 +2060,12 @@ bool InputDispatcher::shouldSplitTouch(const TouchState& touchState, if (touchedWindow.windowHandle->getInfo()->supportsSplitTouch()) { continue; } + if (touchedWindow.windowHandle->getInfo()->inputConfig.test( + gui::WindowInfo::InputConfig::IS_WALLPAPER)) { + // Wallpaper window should not affect whether or not touch is split + continue; + } + // Eventually, touchedWindow will contain the deviceId of each pointer that's currently // being sent there. For now, use deviceId from touch state. if (entry.deviceId == touchState.deviceId && !touchedWindow.pointerIds.isEmpty()) { @@ -2223,6 +2229,32 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds, entry.eventTime); + + // If this is the pointer going down and the touched window has a wallpaper + // then also add the touched wallpaper windows so they are locked in for the duration + // of the touch gesture. + // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper + // engine only supports touch events. We would need to add a mechanism similar + // to View.onGenericMotionEvent to enable wallpapers to handle these events. + if (maskedAction == AMOTION_EVENT_ACTION_DOWN || + maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) { + if (targetFlags.test(InputTarget::Flags::FOREGROUND) && + windowHandle->getInfo()->inputConfig.test( + gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) { + sp wallpaper = findWallpaperWindowBelow(windowHandle); + if (wallpaper != nullptr) { + ftl::Flags wallpaperFlags = + InputTarget::Flags::WINDOW_IS_OBSCURED | + InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED | + InputTarget::Flags::DISPATCH_AS_IS; + if (isSplit) { + wallpaperFlags |= InputTarget::Flags::SPLIT; + } + tempTouchState.addOrUpdateWindow(wallpaper, wallpaperFlags, pointerIds, + entry.eventTime); + } + } + } } // If any existing window is pilfering pointers from newly added window, remove it @@ -2307,6 +2339,10 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( pointerIds.markBit(entry.pointerProperties[0].id); tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds, entry.eventTime); + + // Check if the wallpaper window should deliver the corresponding event. + slipWallpaperTouch(targetFlags, oldTouchedWindowHandle, newTouchedWindowHandle, + tempTouchState, pointerIds); } } @@ -2413,38 +2449,6 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } } - // If this is the first pointer going down and the touched window has a wallpaper - // then also add the touched wallpaper windows so they are locked in for the duration - // of the touch gesture. - // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper - // engine only supports touch events. We would need to add a mechanism similar - // to View.onGenericMotionEvent to enable wallpapers to handle these events. - if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { - sp foregroundWindowHandle = - tempTouchState.getFirstForegroundWindowHandle(); - if (foregroundWindowHandle && - foregroundWindowHandle->getInfo()->inputConfig.test( - WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) { - const std::vector>& windowHandles = - getWindowHandlesLocked(displayId); - for (const sp& windowHandle : windowHandles) { - const WindowInfo* info = windowHandle->getInfo(); - if (info->displayId == displayId && - windowHandle->getInfo()->inputConfig.test( - WindowInfo::InputConfig::IS_WALLPAPER)) { - BitSet32 pointerIds; - pointerIds.markBit(entry.pointerProperties[0].id); - tempTouchState.addOrUpdateWindow(windowHandle, - InputTarget::Flags::WINDOW_IS_OBSCURED | - InputTarget::Flags:: - WINDOW_IS_PARTIALLY_OBSCURED | - InputTarget::Flags::DISPATCH_AS_IS, - pointerIds, entry.eventTime); - } - } - } - } - // Success! Output targets. touchedWindows = tempTouchState.windows; outInjectionResult = InputEventInjectionResult::SUCCEEDED; @@ -3726,7 +3730,8 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( } void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( - const nsecs_t downTime, const sp& connection) { + const nsecs_t downTime, const sp& connection, + ftl::Flags targetFlags) { if (connection->status == Connection::Status::BROKEN) { return; } @@ -3752,7 +3757,7 @@ void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( target.globalScaleFactor = windowInfo->globalScaleFactor; } target.inputChannel = connection->inputChannel; - target.flags = InputTarget::Flags::DISPATCH_AS_IS; + target.flags = targetFlags; const bool wasEmpty = connection->outboundQueue.empty(); for (std::unique_ptr& downEventEntry : downEvents) { @@ -3787,6 +3792,16 @@ void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( } } +void InputDispatcher::synthesizeCancelationEventsForWindowLocked( + const sp& windowHandle, const CancelationOptions& options) { + if (windowHandle != nullptr) { + sp wallpaperConnection = getConnectionLocked(windowHandle->getToken()); + if (wallpaperConnection != nullptr) { + synthesizeCancelationEventsForConnectionLocked(wallpaperConnection, options); + } + } +} + std::unique_ptr InputDispatcher::splitMotionEvent( const MotionEntry& originalMotionEntry, BitSet32 pointerIds, nsecs_t splitDownTime) { ALOG_ASSERT(pointerIds.value != 0); @@ -4847,14 +4862,7 @@ void InputDispatcher::setInputWindowsLocked( touchedWindow.windowHandle->getInfo()->inputConfig.test( gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) { sp wallpaper = state.getWallpaperWindow(); - if (wallpaper != nullptr) { - sp wallpaperConnection = - getConnectionLocked(wallpaper->getToken()); - if (wallpaperConnection != nullptr) { - synthesizeCancelationEventsForConnectionLocked(wallpaperConnection, - options); - } - } + synthesizeCancelationEventsForWindowLocked(wallpaper, options); } } state.windows.erase(state.windows.begin() + i); @@ -5155,6 +5163,7 @@ bool InputDispatcher::transferTouchFocus(const sp& fromToken, const sp< // Erase old window. ftl::Flags oldTargetFlags = touchedWindow->targetFlags; BitSet32 pointerIds = touchedWindow->pointerIds; + sp fromWindowHandle = touchedWindow->windowHandle; state->removeWindowByToken(fromToken); // Add new window. @@ -5187,7 +5196,12 @@ bool InputDispatcher::transferTouchFocus(const sp& fromToken, const sp< options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, "transferring touch focus from this window to another window"); synthesizeCancelationEventsForConnectionLocked(fromConnection, options); - synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection); + synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection, + newTargetFlags); + + // Check if the wallpaper window should deliver the corresponding event. + transferWallpaperTouch(oldTargetFlags, newTargetFlags, fromWindowHandle, toWindowHandle, + *state, pointerIds); } } // release lock @@ -6465,4 +6479,100 @@ void InputDispatcher::setMonitorDispatchingTimeoutForTest(std::chrono::nanosecon mMonitorDispatchingTimeout = timeout; } +void InputDispatcher::slipWallpaperTouch(ftl::Flags targetFlags, + const sp& oldWindowHandle, + const sp& newWindowHandle, + TouchState& state, const BitSet32& pointerIds) { + const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test( + gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER); + const bool newHasWallpaper = targetFlags.test(InputTarget::Flags::FOREGROUND) && + newWindowHandle->getInfo()->inputConfig.test( + gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER); + const sp oldWallpaper = + oldHasWallpaper ? state.getWallpaperWindow() : nullptr; + const sp newWallpaper = + newHasWallpaper ? findWallpaperWindowBelow(newWindowHandle) : nullptr; + if (oldWallpaper == newWallpaper) { + return; + } + + if (oldWallpaper != nullptr) { + state.addOrUpdateWindow(oldWallpaper, InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT, + BitSet32(0)); + } + + if (newWallpaper != nullptr) { + state.addOrUpdateWindow(newWallpaper, + InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER | + InputTarget::Flags::WINDOW_IS_OBSCURED | + InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED, + pointerIds); + } +} + +void InputDispatcher::transferWallpaperTouch(ftl::Flags oldTargetFlags, + ftl::Flags newTargetFlags, + const sp fromWindowHandle, + const sp toWindowHandle, + TouchState& state, const BitSet32& pointerIds) { + const bool oldHasWallpaper = oldTargetFlags.test(InputTarget::Flags::FOREGROUND) && + fromWindowHandle->getInfo()->inputConfig.test( + gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER); + const bool newHasWallpaper = newTargetFlags.test(InputTarget::Flags::FOREGROUND) && + toWindowHandle->getInfo()->inputConfig.test( + gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER); + + const sp oldWallpaper = + oldHasWallpaper ? state.getWallpaperWindow() : nullptr; + const sp newWallpaper = + newHasWallpaper ? findWallpaperWindowBelow(toWindowHandle) : nullptr; + if (oldWallpaper == newWallpaper) { + return; + } + + if (oldWallpaper != nullptr) { + CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, + "transferring touch focus to another window"); + state.removeWindowByToken(oldWallpaper->getToken()); + synthesizeCancelationEventsForWindowLocked(oldWallpaper, options); + } + + if (newWallpaper != nullptr) { + nsecs_t downTimeInTarget = now(); + ftl::Flags wallpaperFlags = + oldTargetFlags & (InputTarget::Flags::SPLIT | InputTarget::Flags::DISPATCH_AS_IS); + wallpaperFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED | + InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED; + state.addOrUpdateWindow(newWallpaper, wallpaperFlags, pointerIds, downTimeInTarget); + sp wallpaperConnection = getConnectionLocked(newWallpaper->getToken()); + if (wallpaperConnection != nullptr) { + sp toConnection = getConnectionLocked(toWindowHandle->getToken()); + toConnection->inputState.mergePointerStateTo(wallpaperConnection->inputState); + synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, wallpaperConnection, + wallpaperFlags); + } + } +} + +sp InputDispatcher::findWallpaperWindowBelow( + const sp& windowHandle) const { + const std::vector>& windowHandles = + getWindowHandlesLocked(windowHandle->getInfo()->displayId); + bool foundWindow = false; + for (const sp& otherHandle : windowHandles) { + if (!foundWindow && otherHandle != windowHandle) { + continue; + } + if (windowHandle == otherHandle) { + foundWindow = true; + continue; + } + + if (otherHandle->getInfo()->inputConfig.test(WindowInfo::InputConfig::IS_WALLPAPER)) { + return otherHandle; + } + } + return nullptr; +} + } // namespace android::inputdispatcher diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 5efb39e0f2..a32ebd3757 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -628,9 +628,14 @@ private: REQUIRES(mLock); void synthesizePointerDownEventsForConnectionLocked(const nsecs_t downTime, - const sp& connection) + const sp& connection, + ftl::Flags targetFlags) REQUIRES(mLock); + void synthesizeCancelationEventsForWindowLocked( + const sp& windowHandle, + const CancelationOptions& options) REQUIRES(mLock); + // Splitting motion events across windows. When splitting motion event for a target, // splitDownTime refers to the time of first 'down' event on that particular target std::unique_ptr splitMotionEvent(const MotionEntry& originalMotionEntry, @@ -691,6 +696,19 @@ private: bool recentWindowsAreOwnedByLocked(int32_t pid, int32_t uid) REQUIRES(mLock); sp mReporter; + + void slipWallpaperTouch(ftl::Flags targetFlags, + const sp& oldWindowHandle, + const sp& newWindowHandle, + TouchState& state, const BitSet32& pointerIds) REQUIRES(mLock); + void transferWallpaperTouch(ftl::Flags oldTargetFlags, + ftl::Flags newTargetFlags, + const sp fromWindowHandle, + const sp toWindowHandle, + TouchState& state, const BitSet32& pointerIds) REQUIRES(mLock); + + sp findWallpaperWindowBelow( + const sp& windowHandle) const REQUIRES(mLock); }; } // namespace android::inputdispatcher diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 41c174a1f7..e3bc5168a1 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -62,6 +62,8 @@ static constexpr int32_t POINTER_2_DOWN = AMOTION_EVENT_ACTION_POINTER_DOWN | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); static constexpr int32_t POINTER_3_DOWN = AMOTION_EVENT_ACTION_POINTER_DOWN | (3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); +static constexpr int32_t POINTER_0_UP = + AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); static constexpr int32_t POINTER_1_UP = AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); @@ -81,6 +83,9 @@ static constexpr int32_t MONITOR_PID = 2001; static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 1000ms; +static constexpr int expectedWallpaperFlags = + AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; + struct PointF { float x; float y; @@ -1717,8 +1722,6 @@ TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCance sp wallpaperWindow = sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); wallpaperWindow->setIsWallpaper(true); - constexpr int expectedWallpaperFlags = - AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -1761,8 +1764,6 @@ TEST_F(InputDispatcherTest, WhenWallpaperDisappears_NoCrash) { sp wallpaperWindow = sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); wallpaperWindow->setIsWallpaper(true); - constexpr int expectedWallpaperFlags = - AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -1792,24 +1793,27 @@ TEST_F(InputDispatcherTest, WhenWallpaperDisappears_NoCrash) { foregroundWindow->consumeMotionCancel(); } +class ShouldSplitTouchFixture : public InputDispatcherTest, + public ::testing::WithParamInterface {}; +INSTANTIATE_TEST_SUITE_P(InputDispatcherTest, ShouldSplitTouchFixture, + ::testing::Values(true, false)); /** * A single window that receives touch (on top), and a wallpaper window underneath it. * The top window gets a multitouch gesture. * Ensure that wallpaper gets the same gesture. */ -TEST_F(InputDispatcherTest, WallpaperWindow_ReceivesMultiTouch) { +TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); - window->setDupTouchToWallpaper(true); + sp foregroundWindow = + sp::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); + foregroundWindow->setDupTouchToWallpaper(true); + foregroundWindow->setPreventSplitting(GetParam()); sp wallpaperWindow = sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); wallpaperWindow->setIsWallpaper(true); - constexpr int expectedWallpaperFlags = - AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; - mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, wallpaperWindow}}}); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}}); // Touch down on top window ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -1818,7 +1822,7 @@ TEST_F(InputDispatcherTest, WallpaperWindow_ReceivesMultiTouch) { << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; // Both top window and its wallpaper should receive the touch down - window->consumeMotionDown(); + foregroundWindow->consumeMotionDown(); wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); // Second finger down on the top window @@ -1837,11 +1841,34 @@ TEST_F(InputDispatcherTest, WallpaperWindow_ReceivesMultiTouch) { InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - window->consumeMotionPointerDown(1 /* pointerIndex */); + foregroundWindow->consumeMotionPointerDown(1 /* pointerIndex */); wallpaperWindow->consumeMotionPointerDown(1 /* pointerIndex */, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); - window->assertNoEvents(); - wallpaperWindow->assertNoEvents(); + + const MotionEvent secondFingerUpEvent = + MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) + .displayId(ADISPLAY_ID_DEFAULT) + .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) + .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(100) + .y(100)) + .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(150) + .y(150)) + .build(); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT, + InputEventInjectionSync::WAIT_FOR_RESULT)) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + foregroundWindow->consumeMotionPointerUp(0); + wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {100, 100})) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + foregroundWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); + wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); } /** @@ -1868,8 +1895,6 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); wallpaperWindow->setFrame(Rect(0, 0, 400, 200)); wallpaperWindow->setIsWallpaper(true); - constexpr int expectedWallpaperFlags = - AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; mDispatcher->setInputWindows( {{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow, wallpaperWindow}}}); @@ -1934,62 +1959,49 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { wallpaperWindow->assertNoEvents(); } -TEST_F(InputDispatcherTest, WallpaperWindowReceivesMultiTouch) { +/** + * Two windows: a window on the left with dup touch to wallpaper and window on the right without it. + * The touch slips to the right window. so left window and wallpaper should receive ACTION_CANCEL + * The right window should receive ACTION_DOWN. + */ +TEST_F(InputDispatcherTest, WallpaperWindowWhenSlippery) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); - window->setDupTouchToWallpaper(true); + sp leftWindow = + sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 200, 200)); + leftWindow->setDupTouchToWallpaper(true); + leftWindow->setSlippery(true); + + sp rightWindow = + sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightWindow->setFrame(Rect(200, 0, 400, 200)); sp wallpaperWindow = sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); wallpaperWindow->setIsWallpaper(true); - constexpr int expectedWallpaperFlags = - AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; - wallpaperWindow->setPreventSplitting(true); - mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, wallpaperWindow}}}); + mDispatcher->setInputWindows( + {{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow, wallpaperWindow}}}); + // Touch down on left window ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {50, 50})) + {100, 100})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + + // Both foreground window and its wallpaper should receive the touch down + leftWindow->consumeMotionDown(); wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); - const MotionEvent secondFingerDownEvent = - MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(ADISPLAY_ID_DEFAULT) - .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10)) - .build(); + // Move to right window, the left window should receive cancel. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, - InputEventInjectionSync::WAIT_FOR_RESULT)) - << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - - window->consumeMotionPointerDown(1); - wallpaperWindow->consumeMotionPointerDown(1, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); - - const MotionEvent secondFingerUpEvent = - MotionEventBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(ADISPLAY_ID_DEFAULT) - .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10)) - .build(); - ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT, - InputEventInjectionSync::WAIT_FOR_RESULT)) + injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {201, 100})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - window->consumeMotionPointerUp(1); - wallpaperWindow->consumeMotionPointerUp(1, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); - ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {50, 50})) - << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - window->consumeMotionUp(ADISPLAY_ID_DEFAULT); - wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + leftWindow->consumeMotionCancel(); + rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); + wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); } /** @@ -2802,21 +2814,26 @@ TEST_P(TransferTouchFixture, TransferTouch_OnePointer) { sp firstWindow = sp::make(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT); + firstWindow->setDupTouchToWallpaper(true); sp secondWindow = sp::make(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT); - + sp wallpaper = + sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); + wallpaper->setIsWallpaper(true); // Add the windows to the dispatcher - mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}}); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow, wallpaper}}}); // Send down to the first window NotifyMotionArgs downMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT); mDispatcher->notifyMotion(&downMotionArgs); + // Only the first window should get the down event firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); + wallpaper->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); // Transfer touch to the second window TransferFunction f = GetParam(); @@ -2825,6 +2842,7 @@ TEST_P(TransferTouchFixture, TransferTouch_OnePointer) { // The first window gets cancel and the second gets down firstWindow->consumeMotionCancel(); secondWindow->consumeMotionDown(); + wallpaper->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); // Send up event to the second window NotifyMotionArgs upMotionArgs = @@ -2834,6 +2852,7 @@ TEST_P(TransferTouchFixture, TransferTouch_OnePointer) { // The first window gets no events and the second gets up firstWindow->assertNoEvents(); secondWindow->consumeMotionUp(); + wallpaper->assertNoEvents(); } /** @@ -2957,6 +2976,65 @@ TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) { secondWindow->consumeMotionUp(); } +TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) { + std::shared_ptr application = std::make_shared(); + + // Create a couple of windows + sp firstWindow = + sp::make(application, mDispatcher, "First Window", + ADISPLAY_ID_DEFAULT); + firstWindow->setDupTouchToWallpaper(true); + sp secondWindow = + sp::make(application, mDispatcher, "Second Window", + ADISPLAY_ID_DEFAULT); + secondWindow->setDupTouchToWallpaper(true); + + sp wallpaper1 = + sp::make(application, mDispatcher, "Wallpaper1", ADISPLAY_ID_DEFAULT); + wallpaper1->setIsWallpaper(true); + + sp wallpaper2 = + sp::make(application, mDispatcher, "Wallpaper2", ADISPLAY_ID_DEFAULT); + wallpaper2->setIsWallpaper(true); + // Add the windows to the dispatcher + mDispatcher->setInputWindows( + {{ADISPLAY_ID_DEFAULT, {firstWindow, wallpaper1, secondWindow, wallpaper2}}}); + + // Send down to the first window + NotifyMotionArgs downMotionArgs = + generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT); + mDispatcher->notifyMotion(&downMotionArgs); + + // Only the first window should get the down event + firstWindow->consumeMotionDown(); + secondWindow->assertNoEvents(); + wallpaper1->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaper2->assertNoEvents(); + + // Transfer touch focus to the second window + TransferFunction f = GetParam(); + bool success = f(mDispatcher, firstWindow->getToken(), secondWindow->getToken()); + ASSERT_TRUE(success); + + // The first window gets cancel and the second gets down + firstWindow->consumeMotionCancel(); + secondWindow->consumeMotionDown(); + wallpaper1->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaper2->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + + // Send up event to the second window + NotifyMotionArgs upMotionArgs = + generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT); + mDispatcher->notifyMotion(&upMotionArgs); + // The first window gets no events and the second gets up + firstWindow->assertNoEvents(); + secondWindow->consumeMotionUp(); + wallpaper1->assertNoEvents(); + wallpaper2->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); +} + // For the cases of single pointer touch and two pointers non-split touch, the api's // 'transferTouch' and 'transferTouchFocus' are equivalent in behaviour. They only differ // for the case where there are multiple pointers split across several windows. -- GitLab From 4fb941a83bbefe0e9ee939de8412ce3767f9dee2 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Wed, 14 Dec 2022 19:14:04 +0000 Subject: [PATCH 0621/1310] Extract Gesture handling code and test it Similarly to the HardwareState logic, extracting this code makes things a bit clearer and easier to test. Bug: 251196347 Test: m inputflinger_tests && \ $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests \ --gtest_filter='*HardwareStateConverterTest*' Test: atest inputflinger_tests Change-Id: I6902e052fc5e193228aca4378587413b22889b26 --- services/inputflinger/reader/Android.bp | 1 + .../reader/mapper/TouchpadInputMapper.cpp | 153 +------------- .../reader/mapper/TouchpadInputMapper.h | 18 +- .../mapper/gestures/GestureConverter.cpp | 186 ++++++++++++++++ .../reader/mapper/gestures/GestureConverter.h | 65 ++++++ services/inputflinger/tests/Android.bp | 1 + .../tests/GestureConverter_test.cpp | 200 ++++++++++++++++++ .../tests/TestInputListenerMatchers.h | 14 ++ 8 files changed, 475 insertions(+), 163 deletions(-) create mode 100644 services/inputflinger/reader/mapper/gestures/GestureConverter.cpp create mode 100644 services/inputflinger/reader/mapper/gestures/GestureConverter.h create mode 100644 services/inputflinger/tests/GestureConverter_test.cpp diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index 1535df3205..f3b680bc29 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -61,6 +61,7 @@ filegroup { "mapper/accumulator/MultiTouchMotionAccumulator.cpp", "mapper/accumulator/SingleTouchMotionAccumulator.cpp", "mapper/accumulator/TouchButtonAccumulator.cpp", + "mapper/gestures/GestureConverter.cpp", "mapper/gestures/GesturesLogging.cpp", "mapper/gestures/HardwareStateConverter.cpp", ], diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index f535ab4bbb..c563dbaad0 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -17,6 +17,7 @@ #include "../Macros.h" #include +#include #include #include "TouchCursorInputMapperCommon.h" #include "TouchpadInputMapper.h" @@ -81,30 +82,14 @@ void gestureInterpreterCallback(void* clientData, const Gesture* gesture) { mapper->consumeGesture(gesture); } -uint32_t gesturesButtonToMotionEventButton(uint32_t gesturesButton) { - switch (gesturesButton) { - case GESTURES_BUTTON_LEFT: - return AMOTION_EVENT_BUTTON_PRIMARY; - case GESTURES_BUTTON_MIDDLE: - return AMOTION_EVENT_BUTTON_TERTIARY; - case GESTURES_BUTTON_RIGHT: - return AMOTION_EVENT_BUTTON_SECONDARY; - case GESTURES_BUTTON_BACK: - return AMOTION_EVENT_BUTTON_BACK; - case GESTURES_BUTTON_FORWARD: - return AMOTION_EVENT_BUTTON_FORWARD; - default: - return 0; - } -} - } // namespace TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext) : InputMapper(deviceContext), mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter), mPointerController(getContext()->getPointerController(getDeviceId())), - mStateConverter(deviceContext) { + mStateConverter(deviceContext), + mGestureConverter(*getContext(), getDeviceId()) { mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD); mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext)); // Even though we don't explicitly delete copy/move semantics, it's safe to @@ -128,8 +113,7 @@ uint32_t TouchpadInputMapper::getSources() const { std::list TouchpadInputMapper::reset(nsecs_t when) { mStateConverter.reset(); - - mButtonState = 0; + mGestureConverter.reset(); return InputMapper::reset(when); } @@ -163,137 +147,10 @@ void TouchpadInputMapper::consumeGesture(const Gesture* gesture) { std::list TouchpadInputMapper::processGestures(nsecs_t when, nsecs_t readTime) { std::list out = {}; for (Gesture& gesture : mGesturesToProcess) { - switch (gesture.type) { - case kGestureTypeMove: - out.push_back(handleMove(when, readTime, gesture)); - break; - case kGestureTypeButtonsChange: - out += handleButtonsChange(when, readTime, gesture); - break; - default: - // TODO(b/251196347): handle more gesture types. - break; - } + out += mGestureConverter.handleGesture(when, readTime, gesture); } mGesturesToProcess.clear(); return out; } -NotifyArgs TouchpadInputMapper::handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture) { - PointerProperties props; - props.clear(); - props.id = 0; - props.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; - - mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); - mPointerController->move(gesture.details.move.dx, gesture.details.move.dy); - mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); - float xCursorPosition, yCursorPosition; - mPointerController->getPosition(&xCursorPosition, &yCursorPosition); - - PointerCoords coords; - coords.clear(); - coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); - coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); - coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, gesture.details.move.dx); - coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, gesture.details.move.dy); - const bool down = isPointerDown(mButtonState); - coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f); - - const int32_t action = down ? AMOTION_EVENT_ACTION_MOVE : AMOTION_EVENT_ACTION_HOVER_MOVE; - return makeMotionArgs(when, readTime, action, /* actionButton= */ 0, mButtonState, - /* pointerCount= */ 1, &props, &coords, xCursorPosition, yCursorPosition); -} - -std::list TouchpadInputMapper::handleButtonsChange(nsecs_t when, nsecs_t readTime, - const Gesture& gesture) { - std::list out = {}; - - mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); - mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); - - PointerProperties props; - props.clear(); - props.id = 0; - props.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; - - float xCursorPosition, yCursorPosition; - mPointerController->getPosition(&xCursorPosition, &yCursorPosition); - - PointerCoords coords; - coords.clear(); - coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); - coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); - coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0); - coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0); - const uint32_t buttonsPressed = gesture.details.buttons.down; - bool pointerDown = isPointerDown(mButtonState) || - buttonsPressed & - (GESTURES_BUTTON_LEFT | GESTURES_BUTTON_MIDDLE | GESTURES_BUTTON_RIGHT); - coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerDown ? 1.0f : 0.0f); - - uint32_t newButtonState = mButtonState; - std::list pressEvents = {}; - for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) { - if (buttonsPressed & button) { - uint32_t actionButton = gesturesButtonToMotionEventButton(button); - newButtonState |= actionButton; - pressEvents.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS, - actionButton, newButtonState, - /* pointerCount= */ 1, &props, &coords, - xCursorPosition, yCursorPosition)); - } - } - if (!isPointerDown(mButtonState) && isPointerDown(newButtonState)) { - mDownTime = when; - out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, - /* actionButton= */ 0, newButtonState, /* pointerCount= */ 1, - &props, &coords, xCursorPosition, yCursorPosition)); - } - out.splice(out.end(), pressEvents); - - // The same button may be in both down and up in the same gesture, in which case we should treat - // it as having gone down and then up. So, we treat a single button change gesture as two state - // changes: a set of buttons going down, followed by a set of buttons going up. - mButtonState = newButtonState; - - const uint32_t buttonsReleased = gesture.details.buttons.up; - for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) { - if (buttonsReleased & button) { - uint32_t actionButton = gesturesButtonToMotionEventButton(button); - newButtonState &= ~actionButton; - out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE, - actionButton, newButtonState, /* pointerCount= */ 1, - &props, &coords, xCursorPosition, yCursorPosition)); - } - } - if (isPointerDown(mButtonState) && !isPointerDown(newButtonState)) { - coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f); - out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0, - newButtonState, /* pointerCount= */ 1, &props, &coords, - xCursorPosition, yCursorPosition)); - } - mButtonState = newButtonState; - return out; -} - -NotifyMotionArgs TouchpadInputMapper::makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, - int32_t actionButton, int32_t buttonState, - uint32_t pointerCount, - const PointerProperties* pointerProperties, - const PointerCoords* pointerCoords, - float xCursorPosition, float yCursorPosition) { - // TODO(b/260226362): consider what the appropriate source for these events is. - const uint32_t source = AINPUT_SOURCE_MOUSE; - - return NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), source, - mPointerController->getDisplayId(), /* policyFlags= */ 0, action, - /* actionButton= */ actionButton, /* flags= */ 0, - getContext()->getGlobalMetaState(), buttonState, - MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, - pointerProperties, pointerCoords, - /* xPrecision= */ 1.0f, /* yPrecision= */ 1.0f, xCursorPosition, - yCursorPosition, /* downTime= */ mDownTime, /* videoFrames= */ {}); -} - } // namespace android diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index c6863f5994..b3bc831baf 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include @@ -25,6 +26,7 @@ #include "InputDevice.h" #include "InputMapper.h" #include "NotifyArgs.h" +#include "gestures/GestureConverter.h" #include "gestures/HardwareStateConverter.h" #include "include/gestures.h" @@ -46,30 +48,16 @@ private: [[nodiscard]] std::list sendHardwareState(nsecs_t when, nsecs_t readTime, SelfContainedHardwareState schs); [[nodiscard]] std::list processGestures(nsecs_t when, nsecs_t readTime); - NotifyArgs handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture); - [[nodiscard]] std::list handleButtonsChange(nsecs_t when, nsecs_t readTime, - const Gesture& gesture); - - NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, - int32_t actionButton, int32_t buttonState, - uint32_t pointerCount, - const PointerProperties* pointerProperties, - const PointerCoords* pointerCoords, float xCursorPosition, - float yCursorPosition); std::unique_ptr mGestureInterpreter; std::shared_ptr mPointerController; HardwareStateConverter mStateConverter; + GestureConverter mGestureConverter; bool mProcessing = false; std::vector mGesturesToProcess; - - // The current button state according to the gestures library, but converted into MotionEvent - // button values (AMOTION_EVENT_BUTTON_...). - uint32_t mButtonState = 0; - nsecs_t mDownTime = 0; }; } // namespace android diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp new file mode 100644 index 0000000000..23216d35c5 --- /dev/null +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp @@ -0,0 +1,186 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gestures/GestureConverter.h" + +#include + +#include "TouchCursorInputMapperCommon.h" +#include "input/Input.h" + +namespace android { + +namespace { + +uint32_t gesturesButtonToMotionEventButton(uint32_t gesturesButton) { + switch (gesturesButton) { + case GESTURES_BUTTON_LEFT: + return AMOTION_EVENT_BUTTON_PRIMARY; + case GESTURES_BUTTON_MIDDLE: + return AMOTION_EVENT_BUTTON_TERTIARY; + case GESTURES_BUTTON_RIGHT: + return AMOTION_EVENT_BUTTON_SECONDARY; + case GESTURES_BUTTON_BACK: + return AMOTION_EVENT_BUTTON_BACK; + case GESTURES_BUTTON_FORWARD: + return AMOTION_EVENT_BUTTON_FORWARD; + default: + return 0; + } +} + +} // namespace + +GestureConverter::GestureConverter(InputReaderContext& readerContext, int32_t deviceId) + : mDeviceId(deviceId), + mReaderContext(readerContext), + mPointerController(readerContext.getPointerController(deviceId)) {} + +void GestureConverter::reset() { + mButtonState = 0; +} + +std::list GestureConverter::handleGesture(nsecs_t when, nsecs_t readTime, + const Gesture& gesture) { + switch (gesture.type) { + case kGestureTypeMove: + return {handleMove(when, readTime, gesture)}; + case kGestureTypeButtonsChange: + return handleButtonsChange(when, readTime, gesture); + default: + // TODO(b/251196347): handle more gesture types. + return {}; + } +} + +NotifyArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture) { + PointerProperties props; + props.clear(); + props.id = 0; + props.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + + mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); + mPointerController->move(gesture.details.move.dx, gesture.details.move.dy); + mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); + float xCursorPosition, yCursorPosition; + mPointerController->getPosition(&xCursorPosition, &yCursorPosition); + + PointerCoords coords; + coords.clear(); + coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); + coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, gesture.details.move.dx); + coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, gesture.details.move.dy); + const bool down = isPointerDown(mButtonState); + coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f); + + const int32_t action = down ? AMOTION_EVENT_ACTION_MOVE : AMOTION_EVENT_ACTION_HOVER_MOVE; + return makeMotionArgs(when, readTime, action, /* actionButton= */ 0, mButtonState, + /* pointerCount= */ 1, &props, &coords, xCursorPosition, yCursorPosition); +} + +std::list GestureConverter::handleButtonsChange(nsecs_t when, nsecs_t readTime, + const Gesture& gesture) { + std::list out = {}; + + mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); + mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); + + PointerProperties props; + props.clear(); + props.id = 0; + props.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + + float xCursorPosition, yCursorPosition; + mPointerController->getPosition(&xCursorPosition, &yCursorPosition); + + PointerCoords coords; + coords.clear(); + coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); + coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0); + coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0); + const uint32_t buttonsPressed = gesture.details.buttons.down; + bool pointerDown = isPointerDown(mButtonState) || + buttonsPressed & + (GESTURES_BUTTON_LEFT | GESTURES_BUTTON_MIDDLE | GESTURES_BUTTON_RIGHT); + coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerDown ? 1.0f : 0.0f); + + uint32_t newButtonState = mButtonState; + std::list pressEvents = {}; + for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) { + if (buttonsPressed & button) { + uint32_t actionButton = gesturesButtonToMotionEventButton(button); + newButtonState |= actionButton; + pressEvents.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS, + actionButton, newButtonState, + /* pointerCount= */ 1, &props, &coords, + xCursorPosition, yCursorPosition)); + } + } + if (!isPointerDown(mButtonState) && isPointerDown(newButtonState)) { + mDownTime = when; + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, + /* actionButton= */ 0, newButtonState, /* pointerCount= */ 1, + &props, &coords, xCursorPosition, yCursorPosition)); + } + out.splice(out.end(), pressEvents); + + // The same button may be in both down and up in the same gesture, in which case we should treat + // it as having gone down and then up. So, we treat a single button change gesture as two state + // changes: a set of buttons going down, followed by a set of buttons going up. + mButtonState = newButtonState; + + const uint32_t buttonsReleased = gesture.details.buttons.up; + for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) { + if (buttonsReleased & button) { + uint32_t actionButton = gesturesButtonToMotionEventButton(button); + newButtonState &= ~actionButton; + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE, + actionButton, newButtonState, /* pointerCount= */ 1, + &props, &coords, xCursorPosition, yCursorPosition)); + } + } + if (isPointerDown(mButtonState) && !isPointerDown(newButtonState)) { + coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f); + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0, + newButtonState, /* pointerCount= */ 1, &props, &coords, + xCursorPosition, yCursorPosition)); + } + mButtonState = newButtonState; + return out; +} + +NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, + int32_t actionButton, int32_t buttonState, + uint32_t pointerCount, + const PointerProperties* pointerProperties, + const PointerCoords* pointerCoords, + float xCursorPosition, float yCursorPosition) { + // TODO(b/260226362): consider what the appropriate source for these events is. + const uint32_t source = AINPUT_SOURCE_MOUSE; + + return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, source, + mPointerController->getDisplayId(), /* policyFlags= */ 0, action, + /* actionButton= */ actionButton, /* flags= */ 0, + mReaderContext.getGlobalMetaState(), buttonState, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, + pointerProperties, pointerCoords, + /* xPrecision= */ 1.0f, /* yPrecision= */ 1.0f, xCursorPosition, + yCursorPosition, /* downTime= */ mDownTime, /* videoFrames= */ {}); +} + +} // namespace android diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h new file mode 100644 index 0000000000..dc11f2476b --- /dev/null +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h @@ -0,0 +1,65 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#include + +#include "InputReaderContext.h" +#include "NotifyArgs.h" + +#include "include/gestures.h" + +namespace android { + +// Converts Gesture structs from the gestures library into NotifyArgs and the appropriate +// PointerController calls. +class GestureConverter { +public: + GestureConverter(InputReaderContext& readerContext, int32_t deviceId); + + void reset(); + + [[nodiscard]] std::list handleGesture(nsecs_t when, nsecs_t readTime, + const Gesture& gesture); + +private: + NotifyArgs handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture); + [[nodiscard]] std::list handleButtonsChange(nsecs_t when, nsecs_t readTime, + const Gesture& gesture); + + NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, + int32_t actionButton, int32_t buttonState, + uint32_t pointerCount, + const PointerProperties* pointerProperties, + const PointerCoords* pointerCoords, float xCursorPosition, + float yCursorPosition); + + const int32_t mDeviceId; + InputReaderContext& mReaderContext; + std::shared_ptr mPointerController; + + // The current button state according to the gestures library, but converted into MotionEvent + // button values (AMOTION_EVENT_BUTTON_...). + uint32_t mButtonState = 0; + nsecs_t mDownTime = 0; +}; + +} // namespace android diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 547a488b7d..58a5c31b78 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -44,6 +44,7 @@ cc_test { "FakeInputReaderPolicy.cpp", "FakePointerController.cpp", "FocusResolver_test.cpp", + "GestureConverter_test.cpp", "HardwareStateConverter_test.cpp", "InputMapperTest.cpp", "InputProcessor_test.cpp", diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp new file mode 100644 index 0000000000..91efd1a52d --- /dev/null +++ b/services/inputflinger/tests/GestureConverter_test.cpp @@ -0,0 +1,200 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include "FakeEventHub.h" +#include "FakeInputReaderPolicy.h" +#include "FakePointerController.h" +#include "InstrumentedInputReader.h" +#include "NotifyArgs.h" +#include "TestConstants.h" +#include "TestInputListener.h" +#include "TestInputListenerMatchers.h" +#include "include/gestures.h" + +namespace android { + +using testing::AllOf; + +class GestureConverterTest : public testing::Test { +protected: + static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000; + static constexpr stime_t ARBITRARY_GESTURE_TIME = 1.2; + static constexpr float POINTER_X = 100; + static constexpr float POINTER_Y = 200; + + void SetUp() { + mFakeEventHub = std::make_unique(); + mFakePolicy = sp::make(); + mFakeListener = std::make_unique(); + mReader = std::make_unique(mFakeEventHub, mFakePolicy, + *mFakeListener); + + mFakePointerController = std::make_shared(); + mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); + mFakePointerController->setPosition(POINTER_X, POINTER_Y); + mFakePolicy->setPointerController(mFakePointerController); + } + + std::shared_ptr mFakeEventHub; + sp mFakePolicy; + std::unique_ptr mFakeListener; + std::unique_ptr mReader; + std::shared_ptr mFakePointerController; +}; + +TEST_F(GestureConverterTest, Move) { + GestureConverter converter(*mReader->getContext(), DEVICE_ID); + + Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); + std::list args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); + ASSERT_EQ(1u, args.size()); + + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithButtonState(0), + WithPressure(0.0f))); + + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(95, 210)); +} + +TEST_F(GestureConverterTest, ButtonsChange) { + GestureConverter converter(*mReader->getContext(), DEVICE_ID); + + // Press left and right buttons at once + Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, + /* down= */ GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT, + /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false); + std::list args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture); + ASSERT_EQ(3u, args.size()); + + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | + AMOTION_EVENT_BUTTON_SECONDARY), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | + AMOTION_EVENT_BUTTON_SECONDARY), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + + // Then release the left button + Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, + /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT, + /* is_tap= */ false); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, leftUpGesture); + ASSERT_EQ(1u, args.size()); + + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + + // Finally release the right button + Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, + /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_RIGHT, + /* is_tap= */ false); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, rightUpGesture); + ASSERT_EQ(2u, args.size()); + + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); +} + +TEST_F(GestureConverterTest, DragWithButton) { + GestureConverter converter(*mReader->getContext(), DEVICE_ID); + + // Press the button + Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, + /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE, + /* is_tap= */ false); + std::list args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture); + ASSERT_EQ(2u, args.size()); + + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + + // Move + Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture); + ASSERT_EQ(1u, args.size()); + + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f))); + + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(95, 210)); + + // Release the button + Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, + /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT, + /* is_tap= */ false); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, upGesture); + ASSERT_EQ(2u, args.size()); + + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0), + WithCoords(POINTER_X - 5, POINTER_Y + 10), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0), + WithCoords(POINTER_X - 5, POINTER_Y + 10), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); +} + +} // namespace android diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h index 9db34227f5..e5a4b14f00 100644 --- a/services/inputflinger/tests/TestInputListenerMatchers.h +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -74,6 +74,14 @@ MATCHER_P2(WithCoords, x, y, "InputEvent with specified coords") { return argX == x && argY == y; } +MATCHER_P2(WithRelativeMotion, x, y, "InputEvent with specified relative motion") { + const auto argX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); + const auto argY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); + *result_listener << "expected relative motion (" << x << ", " << y << "), but got (" << argX + << ", " << argY << ")"; + return argX == x && argY == y; +} + MATCHER_P(WithPressure, pressure, "InputEvent with specified pressure") { const auto argPressure = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE); *result_listener << "expected pressure " << pressure << ", but got " << argPressure; @@ -97,6 +105,12 @@ MATCHER_P(WithButtonState, buttons, "InputEvent with specified button state") { return arg.buttonState == buttons; } +MATCHER_P(WithActionButton, actionButton, "InputEvent with specified action button") { + *result_listener << "expected action button " << actionButton << ", but got " + << arg.actionButton; + return arg.actionButton == actionButton; +} + MATCHER_P(WithEventTime, eventTime, "InputEvent with specified eventTime") { *result_listener << "expected event time " << eventTime << ", but got " << arg.eventTime; return arg.eventTime == eventTime; -- GitLab From 90553da7160f93b0dc280cf4283b9aa1f8ea8760 Mon Sep 17 00:00:00 2001 From: Brian Lindahl Date: Tue, 6 Dec 2022 13:36:30 -0700 Subject: [PATCH 0622/1310] Clear HWC layer buffer slots by assiging a placeholder buffer When buffers are discarded by SurfaceFlinger clients, the memory should be free'd immediately. This includes clearing references to them from within the implementation of Composer HAL, specifically in cache slots associated with the layer. Since there is no HAL API to clear the slot directly, prior to writing any other buffer to the layer, we set the layer's buffer multiple times to a placeholder buffer, using each of the slot numbers that need to be cleared. This replaces the reference to the client-discarded buffer inside Composer HAL with a reference to the placeholder buffer, causing the reference count to drop, allowing the buffer memory to be freed. Bug: 258196272 Test: atest OutputLayerUncacheBufferTest Change-Id: Id85482e8859490566f0eedbd8d8729c47a7349fb --- .../include/compositionengine/mock/LayerFE.h | 1 + .../CompositionEngine/src/HwcBufferCache.cpp | 2 +- .../CompositionEngine/src/OutputLayer.cpp | 8 +- .../CompositionEngine/tests/MockHWC2.h | 1 + .../tests/OutputLayerTest.cpp | 142 ++++++++++++------ .../DisplayHardware/AidlComposerHal.cpp | 25 +++ .../DisplayHardware/AidlComposerHal.h | 4 + .../DisplayHardware/ComposerHal.h | 1 + .../surfaceflinger/DisplayHardware/HWC2.cpp | 8 + .../surfaceflinger/DisplayHardware/HWC2.h | 2 + .../DisplayHardware/HidlComposerHal.cpp | 27 +++- .../DisplayHardware/HidlComposerHal.h | 4 + .../mock/DisplayHardware/MockComposer.h | 1 + 13 files changed, 176 insertions(+), 50 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h index 14922a4e0d..12e063b5d3 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h @@ -36,6 +36,7 @@ private: // sp>::make() friend class sp; friend class testing::StrictMock; + friend class testing::NiceMock; public: virtual ~LayerFE(); diff --git a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp index d64fd5707c..e2c0acfd81 100644 --- a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp +++ b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp @@ -22,7 +22,7 @@ namespace android::compositionengine::impl { HwcBufferCache::HwcBufferCache() { - for (uint32_t i = 0; i < kMaxLayerBufferCount; i++) { + for (uint32_t i = kMaxLayerBufferCount; i-- > 0;) { mFreeSlots.push(i); } } diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp index a7c24b628d..0ac2d434e8 100644 --- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp +++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp @@ -618,8 +618,12 @@ void OutputLayer::uncacheBuffers(std::vector const& bufferIdsToUncache } for (auto bufferId : bufferIdsToUncache) { - state.hwc->hwcBufferCache.uncache(bufferId); - // TODO(b/258196272): send uncache requests to Composer HAL + uint32_t slot = state.hwc->hwcBufferCache.uncache(bufferId); + if (slot != UINT32_MAX && state.hwc->hwcLayer) { + hal::Error error = state.hwc->hwcLayer->clearBufferSlot(slot); + ALOGE("[%s] Failed to clear buffer slot %d: %s (%d)", getLayerFE().getDebugName(), slot, + to_string(error).c_str(), static_cast(error)); + } } } diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h index d933b94da9..c7f9c69318 100644 --- a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h +++ b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h @@ -56,6 +56,7 @@ public: MOCK_METHOD3(setBuffer, Error(uint32_t, const android::sp&, const android::sp&)); + MOCK_METHOD1(clearBufferSlot, Error(uint32_t)); MOCK_METHOD1(setSurfaceDamage, Error(const android::Region&)); MOCK_METHOD1(setBlendMode, Error(hal::BlendMode)); MOCK_METHOD1(setColor, Error(aidl::android::hardware::graphics::composer3::Color)); diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp index 03cf2927af..f2128c9675 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp @@ -42,6 +42,8 @@ namespace hal = android::hardware::graphics::composer::hal; using testing::_; using testing::InSequence; +using testing::Mock; +using testing::NiceMock; using testing::Return; using testing::ReturnRef; using testing::StrictMock; @@ -82,13 +84,13 @@ ui::Rotation toRotation(uint32_t rotationFlag) { struct OutputLayerTest : public testing::Test { struct OutputLayer final : public impl::OutputLayer { - OutputLayer(const compositionengine::Output& output, sp layerFE) + OutputLayer(const compositionengine::Output& output, compositionengine::LayerFE& layerFE) : mOutput(output), mLayerFE(layerFE) {} ~OutputLayer() override = default; // compositionengine::OutputLayer overrides const compositionengine::Output& getOutput() const override { return mOutput; } - compositionengine::LayerFE& getLayerFE() const override { return *mLayerFE; } + compositionengine::LayerFE& getLayerFE() const override { return mLayerFE; } const impl::OutputLayerCompositionState& getState() const override { return mState; } impl::OutputLayerCompositionState& editState() override { return mState; } @@ -96,21 +98,22 @@ struct OutputLayerTest : public testing::Test { void dumpState(std::string& out) const override { mState.dump(out); } const compositionengine::Output& mOutput; - sp mLayerFE; + compositionengine::LayerFE& mLayerFE; impl::OutputLayerCompositionState mState; }; OutputLayerTest() { - EXPECT_CALL(*mLayerFE, getDebugName()).WillRepeatedly(Return("Test LayerFE")); - EXPECT_CALL(mOutput, getName()).WillRepeatedly(ReturnRef(kOutputName)); + ON_CALL(mLayerFE, getDebugName()).WillByDefault(Return("Test LayerFE")); + ON_CALL(mOutput, getName()).WillByDefault(ReturnRef(kOutputName)); - EXPECT_CALL(*mLayerFE, getCompositionState()).WillRepeatedly(Return(&mLayerFEState)); - EXPECT_CALL(mOutput, getState()).WillRepeatedly(ReturnRef(mOutputState)); + ON_CALL(mLayerFE, getCompositionState()).WillByDefault(Return(&mLayerFEState)); + ON_CALL(mOutput, getState()).WillByDefault(ReturnRef(mOutputState)); } - compositionengine::mock::Output mOutput; - sp> mLayerFE = - sp>::make(); + NiceMock mOutput; + sp> mLayerFE_ = + sp>::make(); + NiceMock& mLayerFE = *mLayerFE_; OutputLayer mOutputLayer{mOutput, mLayerFE}; LayerFECompositionState mLayerFEState; @@ -530,7 +533,7 @@ TEST_F(OutputLayerTest, struct OutputLayerPartialMockForUpdateCompositionState : public impl::OutputLayer { OutputLayerPartialMockForUpdateCompositionState(const compositionengine::Output& output, - sp layerFE) + compositionengine::LayerFE& layerFE) : mOutput(output), mLayerFE(layerFE) {} // Mock everything called by updateCompositionState to simplify testing it. MOCK_CONST_METHOD1(calculateOutputSourceCrop, FloatRect(uint32_t)); @@ -539,7 +542,7 @@ struct OutputLayerPartialMockForUpdateCompositionState : public impl::OutputLaye // compositionengine::OutputLayer overrides const compositionengine::Output& getOutput() const override { return mOutput; } - compositionengine::LayerFE& getLayerFE() const override { return *mLayerFE; } + compositionengine::LayerFE& getLayerFE() const override { return mLayerFE; } const impl::OutputLayerCompositionState& getState() const override { return mState; } impl::OutputLayerCompositionState& editState() override { return mState; } @@ -547,7 +550,7 @@ struct OutputLayerPartialMockForUpdateCompositionState : public impl::OutputLaye MOCK_CONST_METHOD1(dumpState, void(std::string&)); const compositionengine::Output& mOutput; - sp mLayerFE; + compositionengine::LayerFE& mLayerFE; impl::OutputLayerCompositionState mState; }; @@ -588,7 +591,7 @@ public: }; TEST_F(OutputLayerUpdateCompositionStateTest, doesNothingIfNoFECompositionState) { - EXPECT_CALL(*mLayerFE, getCompositionState()).WillOnce(Return(nullptr)); + EXPECT_CALL(mLayerFE, getCompositionState()).WillOnce(Return(nullptr)); mOutputLayer.updateCompositionState(true, false, ui::Transform::RotationFlags::ROT_90); } @@ -833,7 +836,6 @@ struct OutputLayerWriteStateToHWCTest : public OutputLayerTest { EXPECT_CALL(mDisplayColorProfile, getSupportedPerFrameMetadata()) .WillRepeatedly(Return(kSupportedPerFrameMetadata)); } - // Some tests may need to simulate unsupported HWC calls enum class SimulateUnsupported { None, ColorTransform }; @@ -952,7 +954,10 @@ const Region OutputLayerWriteStateToHWCTest::kOverrideSurfaceDamage{Rect{1026, 1 const HdrMetadata OutputLayerWriteStateToHWCTest::kHdrMetadata{{/* LightFlattenable */}, 1029}; native_handle_t* OutputLayerWriteStateToHWCTest::kSidebandStreamHandle = reinterpret_cast(1031); -const sp OutputLayerWriteStateToHWCTest::kBuffer; +const sp OutputLayerWriteStateToHWCTest::kBuffer = + sp::make(1, 2, PIXEL_FORMAT_RGBA_8888, + AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | + AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN); const sp OutputLayerWriteStateToHWCTest::kOverrideBuffer = sp::make(4, 5, PIXEL_FORMAT_RGBA_8888, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | @@ -968,7 +973,7 @@ const std::vector OutputLayerWriteStateToHWCTest::kLayerGenericMetadata {4, 5, 6, 7}}; TEST_F(OutputLayerWriteStateToHWCTest, doesNothingIfNoFECompositionState) { - EXPECT_CALL(*mLayerFE, getCompositionState()).WillOnce(Return(nullptr)); + EXPECT_CALL(mLayerFE, getCompositionState()).WillOnce(Return(nullptr)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); @@ -993,7 +998,7 @@ TEST_F(OutputLayerWriteStateToHWCTest, canSetAllState) { expectPerFrameCommonCalls(); expectNoSetCompositionTypeCall(); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); + EXPECT_CALL(mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); @@ -1018,7 +1023,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForSolidColor) { mLayerFEState.compositionType = Composition::SOLID_COLOR; expectPerFrameCommonCalls(); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); // Setting the composition type should happen before setting the color. We // check this in this test only by setting up an testing::InSeqeuence @@ -1038,8 +1042,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForSideband) { expectSetSidebandHandleCall(); expectSetCompositionTypeCall(Composition::SIDEBAND); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); - mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); } @@ -1051,8 +1053,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForCursor) { expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Composition::CURSOR); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); - mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); } @@ -1064,8 +1064,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForDevice) { expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Composition::DEVICE); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); - mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); } @@ -1079,8 +1077,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsNotSetIfUnchanged) { expectSetColorCall(); expectNoSetCompositionTypeCall(); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); - mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); } @@ -1119,8 +1115,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, allStateIncludesMetadataIfPresent) { expectGenericLayerMetadataCalls(); expectSetCompositionTypeCall(Composition::DEVICE); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); - mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); } @@ -1133,8 +1127,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, perFrameStateDoesNotIncludeMetadataIfPres expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Composition::DEVICE); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); - mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); } @@ -1149,7 +1141,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, overriddenSkipLayerDoesNotSendBuffer) { kOverrideSurfaceDamage, kOverrideLayerBrightness); expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Composition::DEVICE); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ true, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); @@ -1165,7 +1156,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, overriddenSkipLayerForSolidColorDoesNotSe kOverrideSurfaceDamage, kOverrideLayerBrightness); expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Composition::DEVICE); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ true, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); @@ -1181,7 +1171,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, includesOverrideInfoIfPresent) { kOverrideSurfaceDamage, kOverrideLayerBrightness); expectSetHdrMetadataAndBufferCalls(kOverrideHwcSlot, kOverrideBuffer, kOverrideFence); expectSetCompositionTypeCall(Composition::DEVICE); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); @@ -1197,7 +1186,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, includesOverrideInfoForSolidColorIfPresen kOverrideSurfaceDamage, kOverrideLayerBrightness); expectSetHdrMetadataAndBufferCalls(kOverrideHwcSlot, kOverrideBuffer, kOverrideFence); expectSetCompositionTypeCall(Composition::DEVICE); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); @@ -1212,7 +1200,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, previousOverriddenLayerSendsSurfaceDamage Region::INVALID_REGION); expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Composition::DEVICE); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); @@ -1229,7 +1216,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, previousSkipLayerSendsUpdatedDeviceCompos Region::INVALID_REGION); expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Composition::DEVICE); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); @@ -1247,22 +1233,20 @@ TEST_F(OutputLayerWriteStateToHWCTest, previousSkipLayerSendsUpdatedClientCompos Region::INVALID_REGION); expectSetHdrMetadataAndBufferCalls(); expectSetCompositionTypeCall(Composition::CLIENT); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); } TEST_F(OutputLayerWriteStateToHWCTest, peekThroughChangesBlendMode) { - auto peekThroughLayerFE = sp::make(); - OutputLayer peekThroughLayer{mOutput, peekThroughLayerFE}; + auto peekThroughLayerFE = sp>::make(); + OutputLayer peekThroughLayer{mOutput, *peekThroughLayerFE}; mOutputLayer.mState.overrideInfo.peekThroughLayer = &peekThroughLayer; expectGeometryCommonCalls(kDisplayFrame, kSourceCrop, kBufferTransform, Hwc2::IComposerClient::BlendMode::PREMULTIPLIED); expectPerFrameCommonCalls(); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); @@ -1280,7 +1264,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, isPeekingThroughSetsOverride) { TEST_F(OutputLayerWriteStateToHWCTest, zIsOverriddenSetsOverride) { expectGeometryCommonCalls(); expectPerFrameCommonCalls(); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, /*zIsOverridden*/ true, /*isPeekingThrough*/ @@ -1291,7 +1274,7 @@ TEST_F(OutputLayerWriteStateToHWCTest, zIsOverriddenSetsOverride) { TEST_F(OutputLayerWriteStateToHWCTest, roundedCornersForceClientComposition) { expectGeometryCommonCalls(); expectPerFrameCommonCalls(); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(true)); + EXPECT_CALL(mLayerFE, hasRoundedCorners()).WillOnce(Return(true)); expectSetCompositionTypeCall(Composition::CLIENT); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, @@ -1303,7 +1286,7 @@ TEST_F(OutputLayerWriteStateToHWCTest, roundedCornersPeekingThroughAllowsDeviceC expectGeometryCommonCalls(); expectPerFrameCommonCalls(); expectSetHdrMetadataAndBufferCalls(); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(true)); + EXPECT_CALL(mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(true)); expectSetCompositionTypeCall(Composition::DEVICE); mLayerFEState.compositionType = Composition::DEVICE; @@ -1322,7 +1305,6 @@ TEST_F(OutputLayerWriteStateToHWCTest, setBlockingRegion) { expectPerFrameCommonCalls(SimulateUnsupported::None, kDataspace, kOutputSpaceVisibleRegion, kSurfaceDamage, kLayerBrightness, blockingRegion); expectSetHdrMetadataAndBufferCalls(); - EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false)); expectSetCompositionTypeCall(Composition::DISPLAY_DECORATION); mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0, @@ -1330,6 +1312,74 @@ TEST_F(OutputLayerWriteStateToHWCTest, setBlockingRegion) { false); } +/* + * OutputLayer::uncacheBuffers + */ +struct OutputLayerUncacheBufferTest : public OutputLayerTest { + static const sp kBuffer1; + static const sp kBuffer2; + static const sp kFence; + + OutputLayerUncacheBufferTest() { + auto& outputLayerState = mOutputLayer.editState(); + outputLayerState.hwc = impl::OutputLayerCompositionState::Hwc(mHwcLayer_); + + mLayerFEState.compositionType = Composition::DEVICE; + mLayerFEState.acquireFence = kFence; + + ON_CALL(mOutput, getDisplayColorProfile()).WillByDefault(Return(&mDisplayColorProfile)); + } + + std::shared_ptr mHwcLayer_{std::make_shared>()}; + HWC2::mock::Layer& mHwcLayer = *mHwcLayer_; + NiceMock mDisplayColorProfile; +}; + +const sp OutputLayerUncacheBufferTest::kBuffer1 = + sp::make(1, 2, PIXEL_FORMAT_RGBA_8888, + AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | + AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN); +const sp OutputLayerUncacheBufferTest::kBuffer2 = + sp::make(2, 3, PIXEL_FORMAT_RGBA_8888, + AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | + AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN); +const sp OutputLayerUncacheBufferTest::kFence = sp::make(); + +TEST_F(OutputLayerUncacheBufferTest, canUncacheAndReuseSlot) { + // Buffer1 is stored in slot 0 + mLayerFEState.buffer = kBuffer1; + EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 0, kBuffer1, kFence)); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, + /*zIsOverridden*/ false, /*isPeekingThrough*/ false); + Mock::VerifyAndClearExpectations(&mHwcLayer); + + // Buffer2 is stored in slot 1 + mLayerFEState.buffer = kBuffer2; + EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 1, kBuffer2, kFence)); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, + /*zIsOverridden*/ false, /*isPeekingThrough*/ false); + Mock::VerifyAndClearExpectations(&mHwcLayer); + + // buffer slots are cleared in HWC + EXPECT_CALL(mHwcLayer, clearBufferSlot(/*slot*/ 0)); + EXPECT_CALL(mHwcLayer, clearBufferSlot(/*slot*/ 1)); + mOutputLayer.uncacheBuffers({kBuffer1->getId(), kBuffer2->getId()}); + Mock::VerifyAndClearExpectations(&mHwcLayer); + + // slot 1 is reused for Buffer1 + mLayerFEState.buffer = kBuffer1; + EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 1, kBuffer1, kFence)); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, + /*zIsOverridden*/ false, /*isPeekingThrough*/ false); + Mock::VerifyAndClearExpectations(&mHwcLayer); + + // slot 0 is reused for Buffer2 + mLayerFEState.buffer = kBuffer2; + EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 0, kBuffer2, kFence)); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, + /*zIsOverridden*/ false, /*isPeekingThrough*/ false); +} + /* * OutputLayer::writeCursorPositionToHWC() */ @@ -1358,7 +1408,7 @@ const Rect OutputLayerWriteCursorPositionToHWCTest::kDefaultDisplayViewport{0, 0 const Rect OutputLayerWriteCursorPositionToHWCTest::kDefaultCursorFrame{1, 2, 3, 4}; TEST_F(OutputLayerWriteCursorPositionToHWCTest, doesNothingIfNoFECompositionState) { - EXPECT_CALL(*mLayerFE, getCompositionState()).WillOnce(Return(nullptr)); + EXPECT_CALL(mLayerFE, getCompositionState()).WillOnce(Return(nullptr)); mOutputLayer.writeCursorPositionToHWC(); } diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp index 800e36db48..a93bd95e48 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp @@ -232,6 +232,17 @@ AidlComposer::AidlComposer(const std::string& serviceName) { addReader(translate(kSingleReaderKey)); + // TODO(b/262041682): When using new API to clear buffer slots, don't allocate this buffer. + mClearSlotBuffer = sp::make(1, 1, PIXEL_FORMAT_RGBX_8888, + GraphicBuffer::USAGE_HW_COMPOSER | + GraphicBuffer::USAGE_SW_READ_OFTEN | + GraphicBuffer::USAGE_SW_WRITE_OFTEN, + "AidlComposer"); + if (!mClearSlotBuffer || mClearSlotBuffer->initCheck() != ::android::OK) { + LOG_ALWAYS_FATAL("Failed to allocate a buffer for clearing layer buffer slots"); + return; + } + ALOGI("Loaded AIDL composer3 HAL service"); } @@ -809,6 +820,20 @@ Error AidlComposer::setLayerBuffer(Display display, Layer layer, uint32_t slot, return error; } +Error AidlComposer::clearLayerBufferSlot(Display display, Layer layer, uint32_t slot) { + Error error = Error::NONE; + mMutex.lock_shared(); + if (auto writer = getWriter(display)) { + writer->get().setLayerBufferWithNewCommand(translate(display), + translate(layer), slot, + mClearSlotBuffer->handle, /*fence*/ -1); + } else { + error = Error::BAD_DISPLAY; + } + mMutex.unlock_shared(); + return error; +} + Error AidlComposer::setLayerSurfaceDamage(Display display, Layer layer, const std::vector& damage) { Error error = Error::NONE; diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h index 2a043fd0db..5128648218 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h @@ -143,6 +143,7 @@ public: /* see setClientTarget for the purpose of slot */ Error setLayerBuffer(Display display, Layer layer, uint32_t slot, const sp& buffer, int acquireFence) override; + Error clearLayerBufferSlot(Display display, Layer layer, uint32_t slot) override; Error setLayerSurfaceDamage(Display display, Layer layer, const std::vector& damage) override; Error setLayerBlendMode(Display display, Layer layer, IComposerClient::BlendMode mode) override; @@ -280,6 +281,9 @@ private: // threading annotations. ftl::SharedMutex mMutex; + // Buffer slots for layers are cleared by setting the slot buffer to this buffer. + sp mClearSlotBuffer; + // Aidl interface using AidlIComposer = aidl::android::hardware::graphics::composer3::IComposer; using AidlIComposerClient = aidl::android::hardware::graphics::composer3::IComposerClient; diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h index ec23935dbd..821025c7b4 100644 --- a/services/surfaceflinger/DisplayHardware/ComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h @@ -179,6 +179,7 @@ public: /* see setClientTarget for the purpose of slot */ virtual Error setLayerBuffer(Display display, Layer layer, uint32_t slot, const sp& buffer, int acquireFence) = 0; + virtual Error clearLayerBufferSlot(Display display, Layer layer, uint32_t slot) = 0; virtual Error setLayerSurfaceDamage(Display display, Layer layer, const std::vector& damage) = 0; virtual Error setLayerBlendMode(Display display, Layer layer, diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp index d0126d05ff..b5f91a64f3 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.cpp +++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp @@ -717,6 +717,14 @@ Error Layer::setBuffer(uint32_t slot, const sp& buffer, return static_cast(intError); } +Error Layer::clearBufferSlot(uint32_t slot) { + if (CC_UNLIKELY(!mDisplay)) { + return Error::BAD_DISPLAY; + } + auto intError = mComposer.clearLayerBufferSlot(mDisplay->getId(), mId, slot); + return static_cast(intError); +} + Error Layer::setSurfaceDamage(const Region& damage) { if (CC_UNLIKELY(!mDisplay)) { diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h index 4971d19cc4..13edb136d5 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.h +++ b/services/surfaceflinger/DisplayHardware/HWC2.h @@ -310,6 +310,7 @@ public: [[nodiscard]] virtual hal::Error setBuffer(uint32_t slot, const android::sp& buffer, const android::sp& acquireFence) = 0; + [[nodiscard]] virtual hal::Error clearBufferSlot(uint32_t slot) = 0; [[nodiscard]] virtual hal::Error setSurfaceDamage(const android::Region& damage) = 0; [[nodiscard]] virtual hal::Error setBlendMode(hal::BlendMode mode) = 0; @@ -360,6 +361,7 @@ public: hal::Error setCursorPosition(int32_t x, int32_t y) override; hal::Error setBuffer(uint32_t slot, const android::sp& buffer, const android::sp& acquireFence) override; + hal::Error clearBufferSlot(uint32_t slot) override; hal::Error setSurfaceDamage(const android::Region& damage) override; hal::Error setBlendMode(hal::BlendMode mode) override; diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp index b607df0fd3..9a49d94dee 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp @@ -186,9 +186,22 @@ std::vector translate(const hidl_vec& in) { return out; } +sp allocateClearSlotBuffer() { + sp buffer = sp::make(1, 1, PIXEL_FORMAT_RGBX_8888, + GraphicBuffer::USAGE_HW_COMPOSER | + GraphicBuffer::USAGE_SW_READ_OFTEN | + GraphicBuffer::USAGE_SW_WRITE_OFTEN, + "HidlComposer"); + if (!buffer || buffer->initCheck() != ::android::OK) { + return nullptr; + } + return std::move(buffer); +} + } // anonymous namespace -HidlComposer::HidlComposer(const std::string& serviceName) : mWriter(kWriterInitialSize) { +HidlComposer::HidlComposer(const std::string& serviceName) + : mClearSlotBuffer(allocateClearSlotBuffer()), mWriter(kWriterInitialSize) { mComposer = V2_1::IComposer::getService(serviceName); if (mComposer == nullptr) { @@ -230,6 +243,11 @@ HidlComposer::HidlComposer(const std::string& serviceName) : mWriter(kWriterInit if (mClient == nullptr) { LOG_ALWAYS_FATAL("failed to create composer client"); } + + if (!mClearSlotBuffer) { + LOG_ALWAYS_FATAL("Failed to allocate a buffer for clearing layer buffer slots"); + return; + } } bool HidlComposer::isSupported(OptionalFeature feature) const { @@ -694,6 +712,13 @@ Error HidlComposer::setLayerBuffer(Display display, Layer layer, uint32_t slot, return Error::NONE; } +Error HidlComposer::clearLayerBufferSlot(Display display, Layer layer, uint32_t slot) { + mWriter.selectDisplay(display); + mWriter.selectLayer(layer); + mWriter.setLayerBuffer(slot, mClearSlotBuffer->handle, /*fence*/ -1); + return Error::NONE; +} + Error HidlComposer::setLayerSurfaceDamage(Display display, Layer layer, const std::vector& damage) { mWriter.selectDisplay(display); diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h index 3602bbb24e..dc115b259e 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h @@ -248,6 +248,7 @@ public: /* see setClientTarget for the purpose of slot */ Error setLayerBuffer(Display display, Layer layer, uint32_t slot, const sp& buffer, int acquireFence) override; + Error clearLayerBufferSlot(Display display, Layer layer, uint32_t slot) override; Error setLayerSurfaceDamage(Display display, Layer layer, const std::vector& damage) override; Error setLayerBlendMode(Display display, Layer layer, IComposerClient::BlendMode mode) override; @@ -362,6 +363,9 @@ private: sp mClient_2_3; sp mClient_2_4; + // Buffer slots for layers are cleared by setting the slot buffer to this buffer. + sp mClearSlotBuffer; + // 64KiB minus a small space for metadata such as read/write pointers static constexpr size_t kWriterInitialSize = 64 * 1024 / sizeof(uint32_t) - 16; // Max number of buffers that may be cached for a given layer diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h index 836e3a47cc..af3511d5fc 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h @@ -98,6 +98,7 @@ public: Error(Display, nsecs_t, uint32_t*, uint32_t*, int*, uint32_t*)); MOCK_METHOD4(setCursorPosition, Error(Display, Layer, int32_t, int32_t)); MOCK_METHOD5(setLayerBuffer, Error(Display, Layer, uint32_t, const sp&, int)); + MOCK_METHOD3(clearLayerBufferSlot, Error(Display, Layer, uint32_t)); MOCK_METHOD3(setLayerSurfaceDamage, Error(Display, Layer, const std::vector&)); MOCK_METHOD3(setLayerBlendMode, Error(Display, Layer, IComposerClient::BlendMode)); -- GitLab From fecb60640999c4758a8967e7d7960d4530198d17 Mon Sep 17 00:00:00 2001 From: Zixuan Qu Date: Sat, 12 Nov 2022 04:44:31 +0000 Subject: [PATCH 0623/1310] Gets keyboard layout info from user space when available. The layout info consists of a BCP 47 conformant language tag and a layout type such as "qwerty" or "azerty". They will be used to initialize the input device. This is currently used by uinput virtual keyboard to pass in layout information. Bug: 237537306 Test: atest inputflinger_tests Change-Id: Icfc30f1afb0f88dd704d1d598d62a300a032b0f5 --- include/input/InputDevice.h | 16 +++++++++++++++ libs/input/InputDevice.cpp | 5 +++++ .../inputflinger/include/InputReaderBase.h | 9 ++++++++- services/inputflinger/reader/InputDevice.cpp | 10 ---------- services/inputflinger/reader/Macros.h | 12 +++++++++++ .../inputflinger/reader/include/InputDevice.h | 2 ++ .../reader/mapper/KeyboardInputMapper.cpp | 16 +++++++++++++++ .../reader/mapper/KeyboardInputMapper.h | 1 + .../tests/FakeInputReaderPolicy.cpp | 5 +++++ .../tests/FakeInputReaderPolicy.h | 2 ++ .../inputflinger/tests/InputReader_test.cpp | 20 ++++++++++++++++++- 11 files changed, 86 insertions(+), 12 deletions(-) diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h index e911734407..5fa9fda64b 100644 --- a/include/input/InputDevice.h +++ b/include/input/InputDevice.h @@ -205,6 +205,16 @@ struct InputDeviceBatteryInfo { int32_t id; }; +struct KeyboardLayoutInfo { + explicit KeyboardLayoutInfo(std::string languageTag, std::string layoutType) + : languageTag(languageTag), layoutType(layoutType) {} + + // A BCP 47 conformant language tag such as "en-US". + std::string languageTag; + // The layout type such as QWERTY or AZERTY. + std::string layoutType; +}; + /* * Describes the characteristics and capabilities of an input device. */ @@ -256,6 +266,11 @@ public: void setKeyboardType(int32_t keyboardType); inline int32_t getKeyboardType() const { return mKeyboardType; } + void setKeyboardLayoutInfo(KeyboardLayoutInfo keyboardLayoutInfo); + inline const std::optional& getKeyboardLayoutInfo() const { + return mKeyboardLayoutInfo; + } + inline void setKeyCharacterMap(const std::shared_ptr value) { mKeyCharacterMap = value; } @@ -296,6 +311,7 @@ private: bool mIsExternal; bool mHasMic; hardware::input::InputDeviceCountryCode mCountryCode; + std::optional mKeyboardLayoutInfo; uint32_t mSources; int32_t mKeyboardType; std::shared_ptr mKeyCharacterMap; diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp index 4751a7de8b..fb6c590b6e 100644 --- a/libs/input/InputDevice.cpp +++ b/libs/input/InputDevice.cpp @@ -179,6 +179,7 @@ InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) mIsExternal(other.mIsExternal), mHasMic(other.mHasMic), mCountryCode(other.mCountryCode), + mKeyboardLayoutInfo(other.mKeyboardLayoutInfo), mSources(other.mSources), mKeyboardType(other.mKeyboardType), mKeyCharacterMap(other.mKeyCharacterMap), @@ -270,6 +271,10 @@ void InputDeviceInfo::setKeyboardType(int32_t keyboardType) { mKeyboardType = std::max(mKeyboardType, keyboardType); } +void InputDeviceInfo::setKeyboardLayoutInfo(KeyboardLayoutInfo layoutInfo) { + mKeyboardLayoutInfo = std::move(layoutInfo); +} + std::vector InputDeviceInfo::getSensors() { std::vector infos; infos.reserve(mSensors.size()); diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index 3e4db43cbe..d2c940ff47 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -194,6 +194,9 @@ struct InputReaderConfiguration { // The device type has been updated. CHANGE_DEVICE_TYPE = 1 << 10, + // The keyboard layout association has changed. + CHANGE_KEYBOARD_LAYOUT_ASSOCIATION = 1 << 11, + // All devices must be reopened. CHANGE_MUST_REOPEN = 1 << 31, }; @@ -211,7 +214,7 @@ struct InputReaderConfiguration { // Used to determine which DisplayViewport should be tied to which InputDevice. std::unordered_map portAssociations; - // The associations between input device names and display unique ids. + // The associations between input device physical port locations and display unique ids. // Used to determine which DisplayViewport should be tied to which InputDevice. std::unordered_map uniqueIdAssociations; @@ -219,6 +222,10 @@ struct InputReaderConfiguration { // This is used to determine which device type and source should be tied to which InputDevice. std::unordered_map deviceTypeAssociations; + // The map from the input device physical port location to the input device layout info. + // Can be used to determine the layout of the keyboard device. + std::unordered_map keyboardLayoutAssociations; + // The suggested display ID to show the cursor. int32_t defaultPointerDisplayId; diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 6dfe5f52fa..13f40ee8e4 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -56,16 +56,6 @@ InputDevice::InputDevice(InputReaderContext* context, int32_t id, int32_t genera InputDevice::~InputDevice() {} -template -std::optional getValueByKey(const std::unordered_map& map, K key) { - auto it = map.find(key); - std::optional value = std::nullopt; - if (it != map.end()) { - value = it->second; - } - return value; -} - bool InputDevice::isEnabled() { if (!hasEventHubDevices()) { return false; diff --git a/services/inputflinger/reader/Macros.h b/services/inputflinger/reader/Macros.h index e107d882b7..d2a7ced864 100644 --- a/services/inputflinger/reader/Macros.h +++ b/services/inputflinger/reader/Macros.h @@ -22,6 +22,8 @@ #include #include +#include + namespace android { /** * Log debug messages for each raw event received from the EventHub. @@ -113,4 +115,14 @@ static inline bool sourcesMatchMask(uint32_t sources, uint32_t sourceMask) { return (sources & sourceMask & ~AINPUT_SOURCE_CLASS_MASK) != 0; } +template +static inline std::optional getValueByKey(const std::unordered_map& map, K key) { + auto it = map.find(key); + std::optional value = std::nullopt; + if (it != map.end()) { + value = it->second; + } + return value; +} + } // namespace android diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index af59fe2aca..6179f05f6d 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -54,6 +54,7 @@ public: inline std::optional getBluetoothAddress() const { return mIdentifier.bluetoothAddress; } + inline const std::string getLocation() const { return mIdentifier.location; } inline ftl::Flags getClasses() const { return mClasses; } inline uint32_t getSources() const { return mSources; } inline bool hasEventHubDevices() const { return !mDevices.empty(); } @@ -405,6 +406,7 @@ public: inline const std::string getName() { return mDevice.getName(); } inline const std::string getDescriptor() { return mDevice.getDescriptor(); } + inline const std::string getLocation() { return mDevice.getLocation(); } inline bool isExternal() { return mDevice.isExternal(); } inline std::optional getAssociatedDisplayPort() const { return mDevice.getAssociatedDisplayPort(); diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index 44f0dfe3b6..6f01449083 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -120,6 +120,10 @@ void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo* info) { info->setKeyboardType(mKeyboardType); info->setKeyCharacterMap(getDeviceContext().getKeyCharacterMap()); + + if (mKeyboardLayoutInfo) { + info->setKeyboardLayoutInfo(*mKeyboardLayoutInfo); + } } void KeyboardInputMapper::dump(std::string& dump) { @@ -129,6 +133,12 @@ void KeyboardInputMapper::dump(std::string& dump) { dump += StringPrintf(INDENT3 "Orientation: %d\n", getOrientation()); dump += StringPrintf(INDENT3 "KeyDowns: %zu keys currently down\n", mKeyDowns.size()); dump += StringPrintf(INDENT3 "MetaState: 0x%0x\n", mMetaState); + dump += INDENT3 "KeyboardLayoutInfo: "; + if (mKeyboardLayoutInfo) { + dump += mKeyboardLayoutInfo->languageTag + ", " + mKeyboardLayoutInfo->layoutType + "\n"; + } else { + dump += "\n"; + } } std::optional KeyboardInputMapper::findViewport( @@ -158,6 +168,12 @@ std::list KeyboardInputMapper::configure(nsecs_t when, if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { mViewport = findViewport(config); } + + if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUT_ASSOCIATION)) { + mKeyboardLayoutInfo = + getValueByKey(config->keyboardLayoutAssociations, getDeviceContext().getLocation()); + } + return out; } diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index 0526fd89de..da5b8ee4f3 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -58,6 +58,7 @@ private: uint32_t mSource{}; int32_t mKeyboardType{}; + std::optional mKeyboardLayoutInfo; std::vector mKeyDowns{}; // keys that are down int32_t mMetaState{}; diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp index dc7e581abd..f7553563cf 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp +++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp @@ -136,6 +136,11 @@ void FakeInputReaderPolicy::addInputUniqueIdAssociation(const std::string& input mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId}); } +void FakeInputReaderPolicy::addKeyboardLayoutAssociation(const std::string& inputUniqueId, + const KeyboardLayoutInfo& layoutInfo) { + mConfig.keyboardLayoutAssociations.insert({inputUniqueId, layoutInfo}); +} + void FakeInputReaderPolicy::addDisabledDevice(int32_t deviceId) { mConfig.disabledDevices.insert(deviceId); } diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h index faa9c01a98..862ff0b21a 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.h +++ b/services/inputflinger/tests/FakeInputReaderPolicy.h @@ -58,6 +58,8 @@ public: void addDeviceTypeAssociation(const std::string& inputPort, const std::string& type); void addInputUniqueIdAssociation(const std::string& inputUniqueId, const std::string& displayUniqueId); + void addKeyboardLayoutAssociation(const std::string& inputUniqueId, + const KeyboardLayoutInfo& layoutInfo); void addDisabledDevice(int32_t deviceId); void removeDisabledDevice(int32_t deviceId); void setPointerController(std::shared_ptr controller); diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 3516092cef..5b0185fc8e 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -2763,7 +2763,7 @@ TEST_F(SensorInputMapperTest, ProcessGyroscopeSensor) { class KeyboardInputMapperTest : public InputMapperTest { protected: const std::string UNIQUE_ID = "local:0"; - + const KeyboardLayoutInfo DEVICE_KEYBOARD_LAYOUT_INFO = KeyboardLayoutInfo("en-US", "qwerty"); void prepareDisplay(ui::Rotation orientation); void testDPadKeyRotation(KeyboardInputMapper& mapper, int32_t originalScanCode, @@ -3582,6 +3582,24 @@ TEST_F(KeyboardInputMapperTest, Process_DisabledDevice) { ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED, args.flags); } +TEST_F(KeyboardInputMapperTest, Configure_AssignKeyboardLayoutInfo) { + mDevice->addMapper(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); + std::list unused = + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); + + mFakePolicy->addKeyboardLayoutAssociation(DEVICE_LOCATION, DEVICE_KEYBOARD_LAYOUT_INFO); + + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUT_ASSOCIATION); + + InputDeviceInfo deviceInfo = mDevice->getDeviceInfo(); + ASSERT_EQ(DEVICE_KEYBOARD_LAYOUT_INFO.languageTag, + deviceInfo.getKeyboardLayoutInfo()->languageTag); + ASSERT_EQ(DEVICE_KEYBOARD_LAYOUT_INFO.layoutType, + deviceInfo.getKeyboardLayoutInfo()->layoutType); +} + // --- KeyboardInputMapperTest_ExternalDevice --- class KeyboardInputMapperTest_ExternalDevice : public InputMapperTest { -- GitLab From ba9364aca10c6913d08884d8a9d5e5cbbe5a4e9e Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Thu, 15 Dec 2022 12:39:04 +0000 Subject: [PATCH 0624/1310] Use a const InputDeviceContext ref in HardwareStateConverter This involved changing a number of other references that could have been const but weren't, hence this being a separate CL from the original. Bug: 251196347 Test: build succeeds Test: m inputflinger_tests && \ $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests \ --gtest_filter='*HardwareStateConverterTest*' Change-Id: Id4ecad74c9be19bd26aac525f0ebf44536677f85 --- services/inputflinger/reader/include/InputDevice.h | 2 +- .../reader/mapper/accumulator/CursorButtonAccumulator.cpp | 2 +- .../reader/mapper/accumulator/CursorButtonAccumulator.h | 2 +- .../reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp | 4 ++-- .../reader/mapper/accumulator/MultiTouchMotionAccumulator.h | 3 ++- .../reader/mapper/accumulator/TouchButtonAccumulator.h | 4 ++-- .../reader/mapper/gestures/HardwareStateConverter.cpp | 2 +- .../reader/mapper/gestures/HardwareStateConverter.h | 4 ++-- 8 files changed, 12 insertions(+), 11 deletions(-) diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index af59fe2aca..3994cfd358 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -403,7 +403,7 @@ public: inline status_t enableDevice() { return mEventHub->enableDevice(mId); } inline status_t disableDevice() { return mEventHub->disableDevice(mId); } - inline const std::string getName() { return mDevice.getName(); } + inline const std::string getName() const { return mDevice.getName(); } inline const std::string getDescriptor() { return mDevice.getDescriptor(); } inline bool isExternal() { return mDevice.isExternal(); } inline std::optional getAssociatedDisplayPort() const { diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp index 2d7d73b4a3..153236c177 100644 --- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp @@ -25,7 +25,7 @@ CursorButtonAccumulator::CursorButtonAccumulator() { clearButtons(); } -void CursorButtonAccumulator::reset(InputDeviceContext& deviceContext) { +void CursorButtonAccumulator::reset(const InputDeviceContext& deviceContext) { mBtnLeft = deviceContext.isKeyPressed(BTN_LEFT); mBtnRight = deviceContext.isKeyPressed(BTN_RIGHT); mBtnMiddle = deviceContext.isKeyPressed(BTN_MIDDLE); diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h index 138060483d..6960644a1d 100644 --- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h @@ -27,7 +27,7 @@ struct RawEvent; class CursorButtonAccumulator { public: CursorButtonAccumulator(); - void reset(InputDeviceContext& deviceContext); + void reset(const InputDeviceContext& deviceContext); void process(const RawEvent* rawEvent); diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp index 8746729425..f6a42bdea0 100644 --- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp @@ -26,8 +26,8 @@ namespace android { MultiTouchMotionAccumulator::MultiTouchMotionAccumulator() : mCurrentSlot(-1), mUsingSlotsProtocol(false) {} -void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, size_t slotCount, - bool usingSlotsProtocol) { +void MultiTouchMotionAccumulator::configure(const InputDeviceContext& deviceContext, + size_t slotCount, bool usingSlotsProtocol) { mUsingSlotsProtocol = usingSlotsProtocol; mSlots = std::vector(slotCount); diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h index 62bc780df5..3c1a2a9e64 100644 --- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h @@ -72,7 +72,8 @@ public: MultiTouchMotionAccumulator(); - void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol); + void configure(const InputDeviceContext& deviceContext, size_t slotCount, + bool usingSlotsProtocol); void process(const RawEvent* rawEvent); void finishSync(); diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h index 2e70e2e50d..c2aa2adc0f 100644 --- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h @@ -27,7 +27,7 @@ struct RawEvent; /* Keeps track of the state of touch, stylus and tool buttons. */ class TouchButtonAccumulator { public: - explicit TouchButtonAccumulator(InputDeviceContext& deviceContext) + explicit TouchButtonAccumulator(const InputDeviceContext& deviceContext) : mDeviceContext(deviceContext){}; void configure(); @@ -65,7 +65,7 @@ private: HidUsageAccumulator mHidUsageAccumulator{}; - InputDeviceContext& mDeviceContext; + const InputDeviceContext& mDeviceContext; void processMappedKey(int32_t scanCode, bool down); }; diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp index 49c13ca709..2e175b8fae 100644 --- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp @@ -23,7 +23,7 @@ namespace android { -HardwareStateConverter::HardwareStateConverter(InputDeviceContext& deviceContext) +HardwareStateConverter::HardwareStateConverter(const InputDeviceContext& deviceContext) : mDeviceContext(deviceContext), mTouchButtonAccumulator(deviceContext) { RawAbsoluteAxisInfo slotAxisInfo; deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo); diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h index fd63c05b1b..88312993a0 100644 --- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h +++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h @@ -40,7 +40,7 @@ struct SelfContainedHardwareState { // Converts RawEvents into the HardwareState structs used by the gestures library. class HardwareStateConverter { public: - HardwareStateConverter(InputDeviceContext& deviceContext); + HardwareStateConverter(const InputDeviceContext& deviceContext); std::optional processRawEvent(const RawEvent* event); void reset(); @@ -48,7 +48,7 @@ public: private: SelfContainedHardwareState produceHardwareState(nsecs_t when); - InputDeviceContext& mDeviceContext; + const InputDeviceContext& mDeviceContext; CursorButtonAccumulator mCursorButtonAccumulator; MultiTouchMotionAccumulator mMotionAccumulator; TouchButtonAccumulator mTouchButtonAccumulator; -- GitLab From 42fd151161c73e5cefb9d1d2c4ccf469db0e1be7 Mon Sep 17 00:00:00 2001 From: Emilian Peev Date: Mon, 10 Oct 2022 16:23:31 -0700 Subject: [PATCH 0625/1310] PixelFormat: Add support for Jpeg/R Introduce support for a new Jpeg/R format that includes 10-bit recovery map. Bug: 241284696 Test: atest -c -d cts/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java#testJpegR Change-Id: I8dfcbea8cf555e58aa9eb1f8fbfcb441edb8596c --- libs/ui/PublicFormat.cpp | 19 +++++++++++++------ libs/ui/include/ui/PublicFormat.h | 1 + 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/libs/ui/PublicFormat.cpp b/libs/ui/PublicFormat.cpp index 78e82dab39..c9663edd7a 100644 --- a/libs/ui/PublicFormat.cpp +++ b/libs/ui/PublicFormat.cpp @@ -14,14 +14,15 @@ * limitations under the License. */ -#include // ui::Dataspace +#include "aidl/android/hardware/graphics/common/Dataspace.h" #include + // ---------------------------------------------------------------------------- namespace android { // ---------------------------------------------------------------------------- -using ui::Dataspace; +using ::aidl::android::hardware::graphics::common::Dataspace; int mapPublicFormatToHalFormat(PublicFormat f) { switch (f) { @@ -29,6 +30,7 @@ int mapPublicFormatToHalFormat(PublicFormat f) { case PublicFormat::DEPTH_POINT_CLOUD: case PublicFormat::DEPTH_JPEG: case PublicFormat::HEIC: + case PublicFormat::JPEG_R: return HAL_PIXEL_FORMAT_BLOB; case PublicFormat::DEPTH16: return HAL_PIXEL_FORMAT_Y16; @@ -47,7 +49,7 @@ android_dataspace mapPublicFormatToHalDataspace(PublicFormat f) { Dataspace dataspace; switch (f) { case PublicFormat::JPEG: - dataspace = Dataspace::V0_JFIF; + dataspace = Dataspace::JFIF; break; case PublicFormat::DEPTH_POINT_CLOUD: case PublicFormat::DEPTH16: @@ -64,7 +66,7 @@ android_dataspace mapPublicFormatToHalDataspace(PublicFormat f) { case PublicFormat::YUV_420_888: case PublicFormat::NV21: case PublicFormat::YV12: - dataspace = Dataspace::V0_JFIF; + dataspace = Dataspace::JFIF; break; case PublicFormat::DEPTH_JPEG: dataspace = Dataspace::DYNAMIC_DEPTH; @@ -72,6 +74,9 @@ android_dataspace mapPublicFormatToHalDataspace(PublicFormat f) { case PublicFormat::HEIC: dataspace = Dataspace::HEIF; break; + case PublicFormat::JPEG_R: + dataspace = Dataspace::JPEG_R; + break; default: // Most formats map to UNKNOWN dataspace = Dataspace::UNKNOWN; @@ -139,14 +144,16 @@ PublicFormat mapHalFormatDataspaceToPublicFormat(int format, android_dataspace d switch (ds) { case Dataspace::DEPTH: return PublicFormat::DEPTH_POINT_CLOUD; - case Dataspace::V0_JFIF: + case Dataspace::JFIF: return PublicFormat::JPEG; case Dataspace::HEIF: return PublicFormat::HEIC; default: if (dataSpace == static_cast(HAL_DATASPACE_DYNAMIC_DEPTH)) { return PublicFormat::DEPTH_JPEG; - } else { + } else if (dataSpace == static_cast(Dataspace::JPEG_R)) { + return PublicFormat::JPEG_R; + }else { // Assume otherwise-marked blobs are also JPEG return PublicFormat::JPEG; } diff --git a/libs/ui/include/ui/PublicFormat.h b/libs/ui/include/ui/PublicFormat.h index aa58805718..2248ccab0c 100644 --- a/libs/ui/include/ui/PublicFormat.h +++ b/libs/ui/include/ui/PublicFormat.h @@ -57,6 +57,7 @@ enum class PublicFormat { YCBCR_P010 = 0x36, DEPTH16 = 0x44363159, DEPTH_JPEG = 0x69656963, + JPEG_R = 0x1005, HEIC = 0x48454946, }; -- GitLab From 1bcf5292d51d432842d603c56fc9f519ee58687f Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Fri, 9 Dec 2022 04:08:57 +0000 Subject: [PATCH 0626/1310] SF: change the min supported frame rate to 1Hz Divided the long running tests into multiple tests, so these tests don't take as much time and can finish with the increased number of refresh rate ranges. Updated the tests in the next cl to check over the range of refresh rates from the start and end. Test: atest libsurfaceflinger_unittest BUG: 261520501 Change-Id: I7cd197c39d4482cedbf54e7ddf0d7450e808a289 --- .../Scheduler/RefreshRateSelector.h | 2 +- .../unittests/RefreshRateSelectorTest.cpp | 182 ++++++++++++++++-- 2 files changed, 168 insertions(+), 16 deletions(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h index 4f5842a67a..14d08f85e2 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h @@ -61,7 +61,7 @@ public: std::chrono::nanoseconds(800us).count(); // The lowest Render Frame Rate that will ever be selected - static constexpr Fps kMinSupportedFrameRate = 20_Hz; + static constexpr Fps kMinSupportedFrameRate = 1_Hz; class Policy { static constexpr int kAllowGroupSwitchingDefault = false; diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp index a3b3c4cf58..79d02dd512 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp @@ -141,6 +141,12 @@ protected: RefreshRateSelectorTest(); ~RefreshRateSelectorTest(); + // Represents the number of refresh rates possible + // from 1_Hz to 90_hz, including fractional rates. + static constexpr size_t kTotalRefreshRates120 = 120; + // Represents the number of refresh rates possible + // from 1_Hz to 120_hz, including fractional rates. + static constexpr size_t kTotalRefreshRates216 = 216; static constexpr DisplayModeId kModeId60{0}; static constexpr DisplayModeId kModeId90{1}; static constexpr DisplayModeId kModeId72{2}; @@ -1129,7 +1135,12 @@ TEST_P(RefreshRateSelectorTest, getMaxRefreshRatesByPolicy) { return {{90_Hz, kMode90}, {60_Hz, kMode60}, {45_Hz, kMode90}, {30_Hz, kMode30}}; } }(); - ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + + if (GetParam() == Config::FrameRateOverride::Enabled) { + ASSERT_EQ(kTotalRefreshRates120, refreshRates.size()); + } else { + ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + } for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode) @@ -1155,10 +1166,18 @@ TEST_P(RefreshRateSelectorTest, getMinRefreshRatesByPolicy) { case Config::FrameRateOverride::AppOverride: return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}}; case Config::FrameRateOverride::Enabled: - return {{30_Hz, kMode30}, {45_Hz, kMode90}, {60_Hz, kMode60}, {90_Hz, kMode90}}; + return {{1_Hz, kMode30}, + {1.011_Hz, kMode90}, + {1.016_Hz, kMode60}, + {1.022_Hz, kMode90}}; } }(); - ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + + if (GetParam() == Config::FrameRateOverride::Enabled) { + ASSERT_EQ(kTotalRefreshRates120, refreshRates.size()); + } else { + ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + } for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode) @@ -1250,7 +1269,12 @@ TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) { {30_Hz, kMode60}, {22.5_Hz, kMode90}, {20_Hz, kMode60}}; } }(); - ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + + if (GetParam() == Config::FrameRateOverride::Enabled) { + ASSERT_EQ(kTotalRefreshRates120, refreshRates.size()); + } else { + ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + } for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode) @@ -1264,7 +1288,11 @@ TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) { selector.getRankedRefreshRatesAsPair({}, {.powerOnImminent = true}); EXPECT_TRUE(signals.powerOnImminent); - ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + if (GetParam() == Config::FrameRateOverride::Enabled) { + ASSERT_EQ(kTotalRefreshRates120, refreshRates.size()); + } else { + ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + } for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode) @@ -1284,7 +1312,11 @@ TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) { selector.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = true}); EXPECT_TRUE(signals.powerOnImminent); - ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + if (GetParam() == Config::FrameRateOverride::Enabled) { + ASSERT_EQ(kTotalRefreshRates120, refreshRates.size()); + } else { + ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + } for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode) @@ -1309,7 +1341,12 @@ TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) { {30_Hz, kMode60}, {22.5_Hz, kMode90}, {20_Hz, kMode60}}; } }(); - ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + + if (GetParam() == Config::FrameRateOverride::Enabled) { + ASSERT_EQ(kTotalRefreshRates120, refreshRates.size()); + } else { + ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + } for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode) @@ -1562,7 +1599,11 @@ TEST_P(RefreshRateSelectorTest, testDisplayModeOrdering) { }(); auto actualRanking = selector.getRankedFrameRates(layers, {}).ranking; - ASSERT_EQ(expectedRanking.size(), actualRanking.size()); + if (GetParam() == Config::FrameRateOverride::Enabled) { + ASSERT_EQ(kTotalRefreshRates216, actualRanking.size()); + } else { + ASSERT_EQ(expectedRanking.size(), actualRanking.size()); + } for (size_t i = 0; i < expectedRanking.size(); ++i) { EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode) @@ -1604,7 +1645,11 @@ TEST_P(RefreshRateSelectorTest, testDisplayModeOrdering) { }(); actualRanking = selector.getRankedFrameRates(layers, {}).ranking; - ASSERT_EQ(expectedRanking.size(), actualRanking.size()); + if (GetParam() == Config::FrameRateOverride::Enabled) { + ASSERT_EQ(kTotalRefreshRates216, actualRanking.size()); + } else { + ASSERT_EQ(expectedRanking.size(), actualRanking.size()); + } for (size_t i = 0; i < expectedRanking.size(); ++i) { EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode) @@ -1644,7 +1689,11 @@ TEST_P(RefreshRateSelectorTest, testDisplayModeOrdering) { }(); actualRanking = selector.getRankedFrameRates(layers, {}).ranking; - ASSERT_EQ(expectedRanking.size(), actualRanking.size()); + if (GetParam() == Config::FrameRateOverride::Enabled) { + ASSERT_EQ(kTotalRefreshRates216, actualRanking.size()); + } else { + ASSERT_EQ(expectedRanking.size(), actualRanking.size()); + } for (size_t i = 0; i < expectedRanking.size(); ++i) { EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode) @@ -1687,7 +1736,11 @@ TEST_P(RefreshRateSelectorTest, testDisplayModeOrdering) { }(); actualRanking = selector.getRankedFrameRates(layers, {}).ranking; - ASSERT_EQ(expectedRanking.size(), actualRanking.size()); + if (GetParam() == Config::FrameRateOverride::Enabled) { + ASSERT_EQ(kTotalRefreshRates216, actualRanking.size()); + } else { + ASSERT_EQ(expectedRanking.size(), actualRanking.size()); + } for (size_t i = 0; i < expectedRanking.size(); ++i) { EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode) @@ -2317,7 +2370,8 @@ TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_FractionalRefreshRates_Exac } // b/190578904 -TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withCloseRefreshRates) { +TEST_P(RefreshRateSelectorTest, + getBestFrameRateMode_withCloseRefreshRates_LayerVoteType_Heuristic) { if (g_noSlowTests) { GTEST_SKIP(); } @@ -2346,8 +2400,101 @@ TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withCloseRefreshRates) { for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) { const auto refreshRate = Fps::fromValue(static_cast(fps)); testRefreshRate(refreshRate, LayerVoteType::Heuristic); + } +} +TEST_P(RefreshRateSelectorTest, + getBestFrameRateMode_withCloseRefreshRates_LayerVoteType_ExplicitDefault) { + if (g_noSlowTests) { + GTEST_SKIP(); + } + + const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue(); + constexpr int kMaxRefreshRate = 240; + + DisplayModes displayModes; + for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) { + const DisplayModeId modeId(fps); + displayModes.try_emplace(modeId, + createDisplayMode(modeId, + Fps::fromValue(static_cast(fps)))); + } + + const auto selector = createSelector(std::move(displayModes), DisplayModeId(kMinRefreshRate)); + + std::vector layers = {{.weight = 1.f}}; + const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) { + layers[0].desiredRefreshRate = fps; + layers[0].vote = vote; + EXPECT_EQ(fps.getIntValue(), selector.getBestFrameRateMode(layers)->getFps().getIntValue()) + << "Failed for " << ftl::enum_string(vote); + }; + + for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) { + const auto refreshRate = Fps::fromValue(static_cast(fps)); testRefreshRate(refreshRate, LayerVoteType::ExplicitDefault); + } +} +TEST_P(RefreshRateSelectorTest, + getBestFrameRateMode_withCloseRefreshRates_LayerVoteType_ExplicitExactOrMultiple) { + if (g_noSlowTests) { + GTEST_SKIP(); + } + + const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue(); + constexpr int kMaxRefreshRate = 240; + + DisplayModes displayModes; + for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) { + const DisplayModeId modeId(fps); + displayModes.try_emplace(modeId, + createDisplayMode(modeId, + Fps::fromValue(static_cast(fps)))); + } + + const auto selector = createSelector(std::move(displayModes), DisplayModeId(kMinRefreshRate)); + + std::vector layers = {{.weight = 1.f}}; + const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) { + layers[0].desiredRefreshRate = fps; + layers[0].vote = vote; + EXPECT_EQ(fps.getIntValue(), selector.getBestFrameRateMode(layers)->getFps().getIntValue()) + << "Failed for " << ftl::enum_string(vote); + }; + + for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) { + const auto refreshRate = Fps::fromValue(static_cast(fps)); testRefreshRate(refreshRate, LayerVoteType::ExplicitExactOrMultiple); + } +} +TEST_P(RefreshRateSelectorTest, + getBestFrameRateMode_withCloseRefreshRates_LayerVoteType_ExplicitExact) { + if (g_noSlowTests) { + GTEST_SKIP(); + } + + const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue(); + constexpr int kMaxRefreshRate = 240; + + DisplayModes displayModes; + for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) { + const DisplayModeId modeId(fps); + displayModes.try_emplace(modeId, + createDisplayMode(modeId, + Fps::fromValue(static_cast(fps)))); + } + + const auto selector = createSelector(std::move(displayModes), DisplayModeId(kMinRefreshRate)); + + std::vector layers = {{.weight = 1.f}}; + const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) { + layers[0].desiredRefreshRate = fps; + layers[0].vote = vote; + EXPECT_EQ(fps.getIntValue(), selector.getBestFrameRateMode(layers)->getFps().getIntValue()) + << "Failed for " << ftl::enum_string(vote); + }; + + for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) { + const auto refreshRate = Fps::fromValue(static_cast(fps)); testRefreshRate(refreshRate, LayerVoteType::ExplicitExact); } } @@ -2807,13 +2954,18 @@ TEST_P(RefreshRateSelectorTest, renderFrameRates) { {90_Hz, 90_Hz}, {120_Hz, 120_Hz}}; case Config::FrameRateOverride::Enabled: - return {{30_Hz, 30_Hz}, {36_Hz, 72_Hz}, {40_Hz, 120_Hz}, {45_Hz, 90_Hz}, - {60_Hz, 60_Hz}, {72_Hz, 72_Hz}, {90_Hz, 90_Hz}, {120_Hz, 120_Hz}}; + return {{1_Hz, 30_Hz}, {1.008_Hz, 120_Hz}, {1.011_Hz, 90_Hz}, + {1.014_Hz, 72_Hz}, {1.016_Hz, 60_Hz}, {1.022_Hz, 90_Hz}, + {1.0256_Hz, 120_Hz}, {1.028_Hz, 72_Hz}}; } }(); const auto& primaryRefreshRates = selector.getPrimaryFrameRates(); - ASSERT_EQ(expected.size(), primaryRefreshRates.size()); + if (GetParam() == Config::FrameRateOverride::Enabled) { + ASSERT_EQ(kTotalRefreshRates216, primaryRefreshRates.size()); + } else { + ASSERT_EQ(expected.size(), primaryRefreshRates.size()); + } for (size_t i = 0; i < expected.size(); i++) { const auto [expectedRenderRate, expectedRefreshRate] = expected[i]; -- GitLab From cff052d114fbbfb9219f552d33cca82e8d472f75 Mon Sep 17 00:00:00 2001 From: ramindani Date: Thu, 15 Dec 2022 15:38:39 -0800 Subject: [PATCH 0627/1310] SF: Update RefreshRateSelectorTest to check range of refresh rates. We have increased number of Refresh rates with the frame rate override being 1Hz and this test checks the total number of refresh rates possible in tests. And Tests a range of refresh rates from the start and end of the refresh rate range for validation. The whole range of refresh rates are high and will only increase going forward. So having a range check helps with reducing the hard coded number of refresh rates. Test: atest libsurfaceflinger_unittest && atest SurfaceFlinger_unittest && atest libcompositionengine_test && atest CtsGraphicsTestCases && go/wm-smoke BUG: 261520501 Change-Id: Iff0f2e28d2a80cfc46ccf993f1cb33affaee8440 --- .../unittests/RefreshRateSelectorTest.cpp | 420 ++++++++++++------ 1 file changed, 289 insertions(+), 131 deletions(-) diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp index 79d02dd512..c9fefe3dee 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp @@ -230,6 +230,12 @@ protected: config.enableFrameRateOverride = GetParam(); return TestableRefreshRateSelector(modes, activeModeId, config); } + + // TODO(b/262783137) Test the complete range of refresh rates + struct FpsBeginEnd { + std::vector> begin; + std::vector> end; + }; }; RefreshRateSelectorTest::RefreshRateSelectorTest() { @@ -1125,29 +1131,39 @@ TEST_P(RefreshRateSelectorTest, getMaxRefreshRatesByPolicy) { const auto refreshRates = selector.rankRefreshRates(selector.getActiveMode().getGroup(), RefreshRateOrder::Descending); - const auto expectedRefreshRates = []() -> std::vector { + const auto expectedRefreshRates = []() -> FpsBeginEnd { switch (GetParam()) { case Config::FrameRateOverride::Disabled: case Config::FrameRateOverride::AppOverrideNativeRefreshRates: case Config::FrameRateOverride::AppOverride: - return {{90_Hz, kMode90}, {60_Hz, kMode60}, {30_Hz, kMode30}}; + return {.begin{{90_Hz, 90_Hz}, {60_Hz, 60_Hz}, {30_Hz, 30_Hz}}}; case Config::FrameRateOverride::Enabled: - return {{90_Hz, kMode90}, {60_Hz, kMode60}, {45_Hz, kMode90}, {30_Hz, kMode30}}; + return {.begin{{90_Hz, 90_Hz}, {60_Hz, 60_Hz}, {45_Hz, 90_Hz}, {30_Hz, 30_Hz}}, + .end{{1.022_Hz, 90_Hz}, + {1.016_Hz, 60_Hz}, + {1.011_Hz, 90_Hz}, + {1_Hz, 30_Hz}}}; } }(); if (GetParam() == Config::FrameRateOverride::Enabled) { ASSERT_EQ(kTotalRefreshRates120, refreshRates.size()); } else { - ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + ASSERT_EQ(expectedRefreshRates.begin.size(), refreshRates.size()); } - for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { - EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode) - << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " (" - << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")" - << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " (" - << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")"; + for (size_t i = 0; i < expectedRefreshRates.begin.size(); i++) { + const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.begin[i]; + EXPECT_EQ(expectedRenderRate, refreshRates[i].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, refreshRates[i].frameRateMode.modePtr->getFps()); + } + + for (size_t i = 0; i < expectedRefreshRates.end.size(); i++) { + const size_t refreshRateIndex = refreshRates.size() - expectedRefreshRates.end.size() + i; + const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.end[i]; + EXPECT_EQ(expectedRenderRate, refreshRates[refreshRateIndex].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, + refreshRates[refreshRateIndex].frameRateMode.modePtr->getFps()); } } @@ -1159,32 +1175,39 @@ TEST_P(RefreshRateSelectorTest, getMinRefreshRatesByPolicy) { const auto refreshRates = selector.rankRefreshRates(selector.getActiveMode().getGroup(), RefreshRateOrder::Ascending); - const auto expectedRefreshRates = []() -> std::vector { + const auto expectedRefreshRates = []() -> FpsBeginEnd { switch (GetParam()) { case Config::FrameRateOverride::Disabled: case Config::FrameRateOverride::AppOverrideNativeRefreshRates: case Config::FrameRateOverride::AppOverride: - return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}}; + return {.begin{{30_Hz, 30_Hz}, {60_Hz, 60_Hz}, {90_Hz, 90_Hz}}}; case Config::FrameRateOverride::Enabled: - return {{1_Hz, kMode30}, - {1.011_Hz, kMode90}, - {1.016_Hz, kMode60}, - {1.022_Hz, kMode90}}; + return {.begin{{1_Hz, 30_Hz}, + {1.011_Hz, 90_Hz}, + {1.016_Hz, 60_Hz}, + {1.022_Hz, 90_Hz}}, + .end{{30_Hz, 30_Hz}, {45_Hz, 90_Hz}, {60_Hz, 60_Hz}, {90_Hz, 90_Hz}}}; } }(); if (GetParam() == Config::FrameRateOverride::Enabled) { ASSERT_EQ(kTotalRefreshRates120, refreshRates.size()); } else { - ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + ASSERT_EQ(expectedRefreshRates.begin.size(), refreshRates.size()); } - for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { - EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode) - << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " (" - << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")" - << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " (" - << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")"; + for (size_t i = 0; i < expectedRefreshRates.begin.size(); i++) { + const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.begin[i]; + EXPECT_EQ(expectedRenderRate, refreshRates[i].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, refreshRates[i].frameRateMode.modePtr->getFps()); + } + + for (size_t i = 0; i < expectedRefreshRates.end.size(); i++) { + const size_t refreshRateIndex = refreshRates.size() - expectedRefreshRates.end.size() + i; + const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.end[i]; + EXPECT_EQ(expectedRenderRate, refreshRates[refreshRateIndex].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, + refreshRates[refreshRateIndex].frameRateMode.modePtr->getFps()); } } @@ -1258,30 +1281,44 @@ TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) { auto [refreshRates, signals] = selector.getRankedFrameRates({}, {}); EXPECT_FALSE(signals.powerOnImminent); - auto expectedRefreshRates = []() -> std::vector { + auto expectedRefreshRates = []() -> FpsBeginEnd { switch (GetParam()) { case Config::FrameRateOverride::Disabled: case Config::FrameRateOverride::AppOverrideNativeRefreshRates: case Config::FrameRateOverride::AppOverride: - return {{90_Hz, kMode90}, {60_Hz, kMode60}}; + return {.begin{{90_Hz, 90_Hz}, {60_Hz, 60_Hz}}}; case Config::FrameRateOverride::Enabled: - return {{90_Hz, kMode90}, {60_Hz, kMode60}, {45_Hz, kMode90}, - {30_Hz, kMode60}, {22.5_Hz, kMode90}, {20_Hz, kMode60}}; + return {.begin{{90_Hz, 90_Hz}, + {60_Hz, 60_Hz}, + {45_Hz, 90_Hz}, + {30_Hz, 60_Hz}, + {22.5_Hz, 90_Hz}, + {20_Hz, 60_Hz}}, + .end{{1.022_Hz, 90_Hz}, + {1.016_Hz, 60_Hz}, + {1.011_Hz, 90_Hz}, + {1_Hz, 60_Hz}}}; } }(); if (GetParam() == Config::FrameRateOverride::Enabled) { ASSERT_EQ(kTotalRefreshRates120, refreshRates.size()); } else { - ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + ASSERT_EQ(expectedRefreshRates.begin.size(), refreshRates.size()); } - for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { - EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode) - << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " (" - << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")" - << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " (" - << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")"; + for (size_t i = 0; i < expectedRefreshRates.begin.size(); i++) { + const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.begin[i]; + EXPECT_EQ(expectedRenderRate, refreshRates[i].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, refreshRates[i].frameRateMode.modePtr->getFps()); + } + + for (size_t i = 0; i < expectedRefreshRates.end.size(); i++) { + const size_t refreshRateIndex = refreshRates.size() - expectedRefreshRates.end.size() + i; + const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.end[i]; + EXPECT_EQ(expectedRenderRate, refreshRates[refreshRateIndex].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, + refreshRates[refreshRateIndex].frameRateMode.modePtr->getFps()); } std::tie(refreshRates, signals) = @@ -1291,15 +1328,21 @@ TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) { if (GetParam() == Config::FrameRateOverride::Enabled) { ASSERT_EQ(kTotalRefreshRates120, refreshRates.size()); } else { - ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + ASSERT_EQ(expectedRefreshRates.begin.size(), refreshRates.size()); } - for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { - EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode) - << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " (" - << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")" - << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " (" - << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")"; + for (size_t i = 0; i < expectedRefreshRates.begin.size(); i++) { + const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.begin[i]; + EXPECT_EQ(expectedRenderRate, refreshRates[i].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, refreshRates[i].frameRateMode.modePtr->getFps()); + } + + for (size_t i = 0; i < expectedRefreshRates.end.size(); i++) { + const size_t refreshRateIndex = refreshRates.size() - expectedRefreshRates.end.size() + i; + const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.end[i]; + EXPECT_EQ(expectedRenderRate, refreshRates[refreshRateIndex].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, + refreshRates[refreshRateIndex].frameRateMode.modePtr->getFps()); } std::vector layers = {{.weight = 1.f}}; @@ -1315,45 +1358,65 @@ TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) { if (GetParam() == Config::FrameRateOverride::Enabled) { ASSERT_EQ(kTotalRefreshRates120, refreshRates.size()); } else { - ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + ASSERT_EQ(expectedRefreshRates.begin.size(), refreshRates.size()); } - for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { - EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode) - << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " (" - << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")" - << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " (" - << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")"; + for (size_t i = 0; i < expectedRefreshRates.begin.size(); i++) { + const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.begin[i]; + EXPECT_EQ(expectedRenderRate, refreshRates[i].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, refreshRates[i].frameRateMode.modePtr->getFps()); + } + + for (size_t i = 0; i < expectedRefreshRates.end.size(); i++) { + const size_t refreshRateIndex = refreshRates.size() - expectedRefreshRates.end.size() + i; + const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.end[i]; + EXPECT_EQ(expectedRenderRate, refreshRates[refreshRateIndex].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, + refreshRates[refreshRateIndex].frameRateMode.modePtr->getFps()); } std::tie(refreshRates, signals) = selector.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = false}); EXPECT_FALSE(signals.powerOnImminent); - expectedRefreshRates = []() -> std::vector { + expectedRefreshRates = []() -> FpsBeginEnd { switch (GetParam()) { case Config::FrameRateOverride::Disabled: case Config::FrameRateOverride::AppOverrideNativeRefreshRates: case Config::FrameRateOverride::AppOverride: - return {{60_Hz, kMode60}, {90_Hz, kMode90}}; + return {.begin{{60_Hz, 60_Hz}, {90_Hz, 90_Hz}}}; case Config::FrameRateOverride::Enabled: - return {{60_Hz, kMode60}, {90_Hz, kMode90}, {45_Hz, kMode90}, - {30_Hz, kMode60}, {22.5_Hz, kMode90}, {20_Hz, kMode60}}; + return {.begin{{60_Hz, 60_Hz}, + {90_Hz, 90_Hz}, + {45_Hz, 90_Hz}, + {30_Hz, 60_Hz}, + {22.5_Hz, 90_Hz}, + {20_Hz, 60_Hz}}, + .end{{1.034_Hz, 60_Hz}, + {1.046_Hz, 90_Hz}, + {1.052_Hz, 60_Hz}, + {1.058_Hz, 90_Hz}}}; } }(); if (GetParam() == Config::FrameRateOverride::Enabled) { ASSERT_EQ(kTotalRefreshRates120, refreshRates.size()); } else { - ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); + ASSERT_EQ(expectedRefreshRates.begin.size(), refreshRates.size()); } - for (size_t i = 0; i < expectedRefreshRates.size(); ++i) { - EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode) - << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " (" - << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")" - << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " (" - << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")"; + for (size_t i = 0; i < expectedRefreshRates.begin.size(); i++) { + const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.begin[i]; + EXPECT_EQ(expectedRenderRate, refreshRates[i].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, refreshRates[i].frameRateMode.modePtr->getFps()); + } + + for (size_t i = 0; i < expectedRefreshRates.end.size(); i++) { + const size_t refreshRateIndex = refreshRates.size() - expectedRefreshRates.end.size() + i; + const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.end[i]; + EXPECT_EQ(expectedRenderRate, refreshRates[refreshRateIndex].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, + refreshRates[refreshRateIndex].frameRateMode.modePtr->getFps()); } } @@ -1582,19 +1645,33 @@ TEST_P(RefreshRateSelectorTest, testDisplayModeOrdering) { lr5.name = "30Hz"; lr5.focused = true; - auto expectedRanking = []() -> std::vector { + auto expectedRanking = []() -> FpsBeginEnd { switch (GetParam()) { case Config::FrameRateOverride::Disabled: case Config::FrameRateOverride::AppOverrideNativeRefreshRates: case Config::FrameRateOverride::AppOverride: - return {{120_Hz, kMode120}, - {90_Hz, kMode90}, - {72_Hz, kMode72}, - {60_Hz, kMode60}, - {30_Hz, kMode30}}; + return {.begin{{120_Hz, 120_Hz}, + {90_Hz, 90_Hz}, + {72_Hz, 72_Hz}, + {60_Hz, 60_Hz}, + {30_Hz, 30_Hz}}}; case Config::FrameRateOverride::Enabled: - return {{120_Hz, kMode120}, {90_Hz, kMode90}, {72_Hz, kMode72}, {60_Hz, kMode60}, - {45_Hz, kMode90}, {40_Hz, kMode120}, {36_Hz, kMode72}, {30_Hz, kMode30}}; + return {.begin{{120_Hz, 120_Hz}, + {90_Hz, 90_Hz}, + {72_Hz, 72_Hz}, + {60_Hz, 60_Hz}, + {45_Hz, 90_Hz}, + {40_Hz, 120_Hz}, + {36_Hz, 72_Hz}, + {30_Hz, 30_Hz}}, + .end{{1.028_Hz, 72_Hz}, + {1.0256_Hz, 120_Hz}, + {1.022_Hz, 90_Hz}, + {1.016_Hz, 60_Hz}, + {1.014_Hz, 72_Hz}, + {1.011_Hz, 90_Hz}, + {1.008_Hz, 120_Hz}, + {1_Hz, 30_Hz}}}; } }(); @@ -1602,15 +1679,21 @@ TEST_P(RefreshRateSelectorTest, testDisplayModeOrdering) { if (GetParam() == Config::FrameRateOverride::Enabled) { ASSERT_EQ(kTotalRefreshRates216, actualRanking.size()); } else { - ASSERT_EQ(expectedRanking.size(), actualRanking.size()); + ASSERT_EQ(expectedRanking.begin.size(), actualRanking.size()); + } + + for (size_t i = 0; i < expectedRanking.begin.size(); i++) { + const auto [expectedRenderRate, expectedRefreshRate] = expectedRanking.begin[i]; + EXPECT_EQ(expectedRenderRate, actualRanking[i].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, actualRanking[i].frameRateMode.modePtr->getFps()); } - for (size_t i = 0; i < expectedRanking.size(); ++i) { - EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode) - << "Expected " << expectedRanking[i].fps.getIntValue() << " (" - << expectedRanking[i].modePtr->getFps().getIntValue() << ")" - << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " (" - << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")"; + for (size_t i = 0; i < expectedRanking.end.size(); i++) { + const size_t refreshRateIndex = actualRanking.size() - expectedRanking.end.size() + i; + const auto [expectedRenderRate, expectedRefreshRate] = expectedRanking.end[i]; + EXPECT_EQ(expectedRenderRate, actualRanking[refreshRateIndex].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, + actualRanking[refreshRateIndex].frameRateMode.modePtr->getFps()); } lr1.vote = LayerVoteType::Max; @@ -1628,19 +1711,33 @@ TEST_P(RefreshRateSelectorTest, testDisplayModeOrdering) { lr5.desiredRefreshRate = 120_Hz; lr5.name = "120Hz"; - expectedRanking = []() -> std::vector { + expectedRanking = []() -> FpsBeginEnd { switch (GetParam()) { case Config::FrameRateOverride::Disabled: case Config::FrameRateOverride::AppOverrideNativeRefreshRates: case Config::FrameRateOverride::AppOverride: - return {{120_Hz, kMode120}, - {90_Hz, kMode90}, - {72_Hz, kMode72}, - {60_Hz, kMode60}, - {30_Hz, kMode30}}; + return {.begin{{120_Hz, 120_Hz}, + {90_Hz, 90_Hz}, + {72_Hz, 72_Hz}, + {60_Hz, 60_Hz}, + {30_Hz, 30_Hz}}}; case Config::FrameRateOverride::Enabled: - return {{120_Hz, kMode120}, {90_Hz, kMode90}, {72_Hz, kMode72}, {60_Hz, kMode60}, - {45_Hz, kMode90}, {40_Hz, kMode120}, {36_Hz, kMode72}, {30_Hz, kMode30}}; + return {.begin{{120_Hz, 120_Hz}, + {90_Hz, 90_Hz}, + {72_Hz, 72_Hz}, + {60_Hz, 60_Hz}, + {45_Hz, 90_Hz}, + {40_Hz, 120_Hz}, + {36_Hz, 72_Hz}, + {30_Hz, 30_Hz}}, + .end{{1.028_Hz, 72_Hz}, + {1.0256_Hz, 120_Hz}, + {1.022_Hz, 90_Hz}, + {1.016_Hz, 60_Hz}, + {1.014_Hz, 72_Hz}, + {1.011_Hz, 90_Hz}, + {1.008_Hz, 120_Hz}, + {1_Hz, 30_Hz}}}; } }(); actualRanking = selector.getRankedFrameRates(layers, {}).ranking; @@ -1648,15 +1745,21 @@ TEST_P(RefreshRateSelectorTest, testDisplayModeOrdering) { if (GetParam() == Config::FrameRateOverride::Enabled) { ASSERT_EQ(kTotalRefreshRates216, actualRanking.size()); } else { - ASSERT_EQ(expectedRanking.size(), actualRanking.size()); + ASSERT_EQ(expectedRanking.begin.size(), actualRanking.size()); } - for (size_t i = 0; i < expectedRanking.size(); ++i) { - EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode) - << "Expected " << expectedRanking[i].fps.getIntValue() << " (" - << expectedRanking[i].modePtr->getFps().getIntValue() << ")" - << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " (" - << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")"; + for (size_t i = 0; i < expectedRanking.begin.size(); i++) { + const auto [expectedRenderRate, expectedRefreshRate] = expectedRanking.begin[i]; + EXPECT_EQ(expectedRenderRate, actualRanking[i].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, actualRanking[i].frameRateMode.modePtr->getFps()); + } + + for (size_t i = 0; i < expectedRanking.end.size(); i++) { + const size_t refreshRateIndex = actualRanking.size() - expectedRanking.end.size() + i; + const auto [expectedRenderRate, expectedRefreshRate] = expectedRanking.end[i]; + EXPECT_EQ(expectedRenderRate, actualRanking[refreshRateIndex].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, + actualRanking[refreshRateIndex].frameRateMode.modePtr->getFps()); } lr1.vote = LayerVoteType::Heuristic; @@ -1672,19 +1775,33 @@ TEST_P(RefreshRateSelectorTest, testDisplayModeOrdering) { lr5.desiredRefreshRate = 72_Hz; lr5.name = "72Hz"; - expectedRanking = []() -> std::vector { + expectedRanking = []() -> FpsBeginEnd { switch (GetParam()) { case Config::FrameRateOverride::Disabled: case Config::FrameRateOverride::AppOverrideNativeRefreshRates: case Config::FrameRateOverride::AppOverride: - return {{30_Hz, kMode30}, - {60_Hz, kMode60}, - {90_Hz, kMode90}, - {120_Hz, kMode120}, - {72_Hz, kMode72}}; + return {.begin{{30_Hz, 30_Hz}, + {60_Hz, 60_Hz}, + {90_Hz, 90_Hz}, + {120_Hz, 120_Hz}, + {72_Hz, 72_Hz}}}; case Config::FrameRateOverride::Enabled: - return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}, {120_Hz, kMode120}, - {45_Hz, kMode90}, {40_Hz, kMode120}, {72_Hz, kMode72}, {36_Hz, kMode72}}; + return {.begin{{30_Hz, 30_Hz}, + {60_Hz, 60_Hz}, + {90_Hz, 90_Hz}, + {120_Hz, 120_Hz}, + {45_Hz, 90_Hz}, + {40_Hz, 120_Hz}, + {72_Hz, 72_Hz}, + {36_Hz, 72_Hz}}, + .end{{1.074_Hz, 72_Hz}, + {1.081_Hz, 120_Hz}, + {1.084_Hz, 90_Hz}, + {1.00_Hz, 30_Hz}, + {1.008_Hz, 120_Hz}, + {1.011_Hz, 90_Hz}, + {1.014_Hz, 72_Hz}, + {1.016_Hz, 60_Hz}}}; } }(); actualRanking = selector.getRankedFrameRates(layers, {}).ranking; @@ -1692,15 +1809,21 @@ TEST_P(RefreshRateSelectorTest, testDisplayModeOrdering) { if (GetParam() == Config::FrameRateOverride::Enabled) { ASSERT_EQ(kTotalRefreshRates216, actualRanking.size()); } else { - ASSERT_EQ(expectedRanking.size(), actualRanking.size()); + ASSERT_EQ(expectedRanking.begin.size(), actualRanking.size()); + } + + for (size_t i = 0; i < expectedRanking.begin.size(); i++) { + const auto [expectedRenderRate, expectedRefreshRate] = expectedRanking.begin[i]; + EXPECT_EQ(expectedRenderRate, actualRanking[i].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, actualRanking[i].frameRateMode.modePtr->getFps()); } - for (size_t i = 0; i < expectedRanking.size(); ++i) { - EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode) - << "Expected " << expectedRanking[i].fps.getIntValue() << " (" - << expectedRanking[i].modePtr->getFps().getIntValue() << ")" - << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " (" - << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")"; + for (size_t i = 0; i < expectedRanking.end.size(); i++) { + const size_t refreshRateIndex = actualRanking.size() - expectedRanking.end.size() + i; + const auto [expectedRenderRate, expectedRefreshRate] = expectedRanking.end[i]; + EXPECT_EQ(expectedRenderRate, actualRanking[refreshRateIndex].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, + actualRanking[refreshRateIndex].frameRateMode.modePtr->getFps()); } lr1.desiredRefreshRate = 120_Hz; @@ -1719,19 +1842,26 @@ TEST_P(RefreshRateSelectorTest, testDisplayModeOrdering) { lr5.desiredRefreshRate = 120_Hz; lr5.name = "120Hz-2"; - expectedRanking = []() -> std::vector { + expectedRanking = []() -> FpsBeginEnd { switch (GetParam()) { case Config::FrameRateOverride::Disabled: case Config::FrameRateOverride::AppOverrideNativeRefreshRates: case Config::FrameRateOverride::AppOverride: - return {{90_Hz, kMode90}, - {60_Hz, kMode60}, - {120_Hz, kMode120}, - {72_Hz, kMode72}, - {30_Hz, kMode30}}; + return {.begin{{90_Hz, 90_Hz}, + {60_Hz, 60_Hz}, + {120_Hz, 120_Hz}, + {72_Hz, 72_Hz}, + {30_Hz, 30_Hz}}}; case Config::FrameRateOverride::Enabled: - return {{90_Hz, kMode90}, {60_Hz, kMode60}, {120_Hz, kMode120}, {72_Hz, kMode72}, - {45_Hz, kMode90}, {40_Hz, kMode120}, {36_Hz, kMode72}, {30_Hz, kMode30}}; + return {.begin{{90_Hz, 90_Hz}, + {60_Hz, 60_Hz}, + {120_Hz, 120_Hz}, + {72_Hz, 72_Hz}, + {45_Hz, 90_Hz}, + {40_Hz, 120_Hz}, + {36_Hz, 72_Hz}, + {30_Hz, 30_Hz}}, + .end{}}; } }(); actualRanking = selector.getRankedFrameRates(layers, {}).ranking; @@ -1739,15 +1869,21 @@ TEST_P(RefreshRateSelectorTest, testDisplayModeOrdering) { if (GetParam() == Config::FrameRateOverride::Enabled) { ASSERT_EQ(kTotalRefreshRates216, actualRanking.size()); } else { - ASSERT_EQ(expectedRanking.size(), actualRanking.size()); + ASSERT_EQ(expectedRanking.begin.size(), actualRanking.size()); } - for (size_t i = 0; i < expectedRanking.size(); ++i) { - EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode) - << "Expected " << expectedRanking[i].fps.getIntValue() << " (" - << expectedRanking[i].modePtr->getFps().getIntValue() << ")" - << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " (" - << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")"; + for (size_t i = 0; i < expectedRanking.begin.size(); i++) { + const auto [expectedRenderRate, expectedRefreshRate] = expectedRanking.begin[i]; + EXPECT_EQ(expectedRenderRate, actualRanking[i].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, actualRanking[i].frameRateMode.modePtr->getFps()); + } + + for (size_t i = 0; i < expectedRanking.end.size(); i++) { + const size_t refreshRateIndex = actualRanking.size() - expectedRanking.end.size() + i; + const auto [expectedRenderRate, expectedRefreshRate] = expectedRanking.end[i]; + EXPECT_EQ(expectedRenderRate, actualRanking[refreshRateIndex].frameRateMode.fps); + EXPECT_EQ(expectedRefreshRate, + actualRanking[refreshRateIndex].frameRateMode.modePtr->getFps()); } } @@ -2943,20 +3079,35 @@ TEST_P(RefreshRateSelectorTest, renderFrameRates) { auto selector = createSelector(kModes_30_60_72_90_120, kModeId120); // [renderRate, refreshRate] - const auto expected = []() -> std::vector> { + const auto expected = []() -> FpsBeginEnd { switch (GetParam()) { case Config::FrameRateOverride::Disabled: case Config::FrameRateOverride::AppOverrideNativeRefreshRates: case Config::FrameRateOverride::AppOverride: - return {{30_Hz, 30_Hz}, - {60_Hz, 60_Hz}, - {72_Hz, 72_Hz}, - {90_Hz, 90_Hz}, - {120_Hz, 120_Hz}}; + return {.begin{{30_Hz, 30_Hz}, + {60_Hz, 60_Hz}, + {72_Hz, 72_Hz}, + {90_Hz, 90_Hz}, + {120_Hz, 120_Hz}}}; case Config::FrameRateOverride::Enabled: - return {{1_Hz, 30_Hz}, {1.008_Hz, 120_Hz}, {1.011_Hz, 90_Hz}, - {1.014_Hz, 72_Hz}, {1.016_Hz, 60_Hz}, {1.022_Hz, 90_Hz}, - {1.0256_Hz, 120_Hz}, {1.028_Hz, 72_Hz}}; + return {.begin{{1_Hz, 30_Hz}, + {1.008_Hz, 120_Hz}, + {1.011_Hz, 90_Hz}, + {1.014_Hz, 72_Hz}, + {1.016_Hz, 60_Hz}, + {1.022_Hz, 90_Hz}, + {1.0256_Hz, 120_Hz}, + {1.028_Hz, 72_Hz}}, + .end{ + {30_Hz, 30_Hz}, + {36_Hz, 72_Hz}, + {40_Hz, 120_Hz}, + {45_Hz, 90_Hz}, + {60_Hz, 60_Hz}, + {72_Hz, 72_Hz}, + {90_Hz, 90_Hz}, + {120_Hz, 120_Hz}, + }}; } }(); @@ -2964,14 +3115,21 @@ TEST_P(RefreshRateSelectorTest, renderFrameRates) { if (GetParam() == Config::FrameRateOverride::Enabled) { ASSERT_EQ(kTotalRefreshRates216, primaryRefreshRates.size()); } else { - ASSERT_EQ(expected.size(), primaryRefreshRates.size()); + ASSERT_EQ(expected.begin.size(), primaryRefreshRates.size()); } - for (size_t i = 0; i < expected.size(); i++) { - const auto [expectedRenderRate, expectedRefreshRate] = expected[i]; + for (size_t i = 0; i < expected.begin.size(); i++) { + const auto [expectedRenderRate, expectedRefreshRate] = expected.begin[i]; EXPECT_EQ(expectedRenderRate, primaryRefreshRates[i].fps); EXPECT_EQ(expectedRefreshRate, primaryRefreshRates[i].modePtr->getFps()); } + + for (size_t i = 0; i < expected.end.size(); i++) { + const auto [expectedRenderRate, expectedRefreshRate] = expected.end[i]; + const size_t refreshRateIndex = primaryRefreshRates.size() - expected.end.size() + i; + EXPECT_EQ(expectedRenderRate, primaryRefreshRates[refreshRateIndex].fps); + EXPECT_EQ(expectedRefreshRate, primaryRefreshRates[refreshRateIndex].modePtr->getFps()); + } } TEST_P(RefreshRateSelectorTest, refreshRateIsCappedWithRenderFrameRate) { -- GitLab From 70991e3b006b7665d24c2202bc26caff9a9e78f4 Mon Sep 17 00:00:00 2001 From: ramindani Date: Thu, 15 Dec 2022 15:55:58 -0800 Subject: [PATCH 0628/1310] SF: Update RefreshRateSelectorTest to remove g_noSlowTests from some tests Divided the test into multiple tests in the previous CL. These tests should be able to execute on slow environment as well. Test: atest libsurfaceflinger_unittest && atest SurfaceFlinger_unittest && atest libcompositionengine_test && atest CtsGraphicsTestCases BUG: 261520501 Change-Id: Ie3ad977395250fdc3a3161d207071d7cfbc1a77d --- .../tests/unittests/RefreshRateSelectorTest.cpp | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp index c9fefe3dee..a78f42c89d 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp @@ -2508,10 +2508,6 @@ TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_FractionalRefreshRates_Exac // b/190578904 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withCloseRefreshRates_LayerVoteType_Heuristic) { - if (g_noSlowTests) { - GTEST_SKIP(); - } - const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue(); constexpr int kMaxRefreshRate = 240; @@ -2540,10 +2536,6 @@ TEST_P(RefreshRateSelectorTest, } TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withCloseRefreshRates_LayerVoteType_ExplicitDefault) { - if (g_noSlowTests) { - GTEST_SKIP(); - } - const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue(); constexpr int kMaxRefreshRate = 240; @@ -2572,10 +2564,6 @@ TEST_P(RefreshRateSelectorTest, } TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withCloseRefreshRates_LayerVoteType_ExplicitExactOrMultiple) { - if (g_noSlowTests) { - GTEST_SKIP(); - } - const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue(); constexpr int kMaxRefreshRate = 240; @@ -2604,10 +2592,6 @@ TEST_P(RefreshRateSelectorTest, } TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withCloseRefreshRates_LayerVoteType_ExplicitExact) { - if (g_noSlowTests) { - GTEST_SKIP(); - } - const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue(); constexpr int kMaxRefreshRate = 240; -- GitLab From ea31d4f8f95bbaedbe518f4a6e2a1bf9ea04b149 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 10 Nov 2022 20:48:01 +0000 Subject: [PATCH 0629/1310] TouchInputMapper: Use ui::Transform to "cook" raw input coordinates Instead of manually scaling and rotating the input device's raw coordinates to the un-rotated display's coordinate space, we use a transfomation matrix as a helper. This change also exposed some bugs, where the tests were enforcing incorrect behavior. Bug: 236798672 Test: atest inputflinger_tests Test: manual with touchscreen and stylus Change-Id: I15b5fa6696720736ff1a598f88a4bc7067644cba --- .../reader/mapper/TouchInputMapper.cpp | 98 +++++++++---------- .../reader/mapper/TouchInputMapper.h | 6 +- .../inputflinger/tests/InputReader_test.cpp | 9 +- 3 files changed, 53 insertions(+), 60 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 160f9eb906..7c566317bd 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -82,13 +82,13 @@ inline static int32_t signExtendNybble(int32_t value) { } static std::tuple getNaturalDisplayInfo( - const DisplayViewport& viewport, ui::Rotation naturalOrientation) { + const DisplayViewport& viewport) { ui::Size rotatedDisplaySize{viewport.deviceWidth, viewport.deviceHeight}; - if (naturalOrientation == ui::ROTATION_90 || naturalOrientation == ui::ROTATION_270) { + if (viewport.orientation == ui::ROTATION_90 || viewport.orientation == ui::ROTATION_270) { std::swap(rotatedDisplaySize.width, rotatedDisplaySize.height); } - ui::Transform rotate(ui::Transform::toRotationFlags(naturalOrientation), + ui::Transform rotate(ui::Transform::toRotationFlags(viewport.orientation), rotatedDisplaySize.width, rotatedDisplaySize.height); Rect physicalFrame{viewport.physicalLeft, viewport.physicalTop, viewport.physicalRight, @@ -224,6 +224,7 @@ void TouchInputMapper::dump(std::string& dump) { dumpDisplay(dump); dump += StringPrintf(INDENT3 "Translation and Scaling Factors:\n"); + mRawToDisplay.dump(dump, "RawToDisplay Transform:", INDENT4); dump += StringPrintf(INDENT4 "XScale: %0.3f\n", mXScale); dump += StringPrintf(INDENT4 "YScale: %0.3f\n", mYScale); dump += StringPrintf(INDENT4 "XPrecision: %0.3f\n", mXPrecision); @@ -855,6 +856,34 @@ void TouchInputMapper::initializeOrientedRanges() { } } +ui::Transform TouchInputMapper::computeInputTransform() const { + const ui::Size rawSize{mRawPointerAxes.getRawWidth(), mRawPointerAxes.getRawHeight()}; + + ui::Size rotatedRawSize = rawSize; + if (mInputDeviceOrientation == ui::ROTATION_270 || mInputDeviceOrientation == ui::ROTATION_90) { + std::swap(rotatedRawSize.width, rotatedRawSize.height); + } + + // Step 1: Undo the raw offset so that the raw coordinate space now starts at (0, 0). + ui::Transform undoRawOffset; + undoRawOffset.set(-mRawPointerAxes.x.minValue, -mRawPointerAxes.y.minValue); + + // Step 2: Rotate the raw coordinates to the expected orientation. + ui::Transform rotate; + // When rotating raw coordinates, the raw size will be used as an offset. + // Account for the extra unit added to the raw range when the raw size was calculated. + rotate.set(ui::Transform::toRotationFlags(-mInputDeviceOrientation), rotatedRawSize.width - 1, + rotatedRawSize.height - 1); + + // Step 3: Scale the raw coordinates to the display space. + ui::Transform scaleToDisplay; + const float xScale = static_cast(mDisplayBounds.width) / rotatedRawSize.width; + const float yScale = static_cast(mDisplayBounds.height) / rotatedRawSize.height; + scaleToDisplay.set(xScale, 0, 0, yScale); + + return (scaleToDisplay * (rotate * undoRawOffset)); +} + void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) { const DeviceMode oldDeviceMode = mDeviceMode; @@ -926,14 +955,7 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) if (mDeviceMode == DeviceMode::DIRECT || mDeviceMode == DeviceMode::POINTER) { const auto oldDisplayBounds = mDisplayBounds; - // Apply the inverse of the input device orientation so that the input device is - // configured in the same orientation as the viewport. The input device orientation will - // be re-applied by mInputDeviceOrientation. - const ui::Rotation naturalDeviceOrientation = - mViewport.orientation - mParameters.orientation; - - std::tie(mDisplayBounds, mPhysicalFrameInDisplay) = - getNaturalDisplayInfo(mViewport, naturalDeviceOrientation); + std::tie(mDisplayBounds, mPhysicalFrameInDisplay) = getNaturalDisplayInfo(mViewport); // InputReader works in the un-rotated display coordinate space, so we don't need to do // anything if the device is already orientation-aware. If the device is not @@ -950,10 +972,13 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) // Apply the input device orientation for the device. mInputDeviceOrientation = mInputDeviceOrientation + mParameters.orientation; + mRawToDisplay = computeInputTransform(); } else { mDisplayBounds = rawSize; mPhysicalFrameInDisplay = Rect{mDisplayBounds}; mInputDeviceOrientation = ui::ROTATION_0; + mRawToDisplay.reset(); + mRawToDisplay.set(-mRawPointerAxes.x.minValue, -mRawPointerAxes.y.minValue); } } @@ -2344,11 +2369,11 @@ void TouchInputMapper::cookPointerData() { break; } - // Adjust X,Y coords for device calibration + // Adjust X,Y coords for device calibration and convert to the natural display coordinates. + vec2 transformed = {in.x, in.y}; // TODO: Adjust coverage coords? - float xTransformed = in.x, yTransformed = in.y; - mAffineTransform.applyTo(xTransformed, yTransformed); - rotateAndScale(xTransformed, yTransformed); + mAffineTransform.applyTo(transformed.x /*byRef*/, transformed.y /*byRef*/); + transformed = mRawToDisplay.transform(transformed); // Adjust X, Y, and coverage coords for input device orientation. float left, top, right, bottom; @@ -2398,8 +2423,8 @@ void TouchInputMapper::cookPointerData() { // Write output coords. PointerCoords& out = mCurrentCookedState.cookedPointerData.pointerCoords[i]; out.clear(); - out.setAxisValue(AMOTION_EVENT_AXIS_X, xTransformed); - out.setAxisValue(AMOTION_EVENT_AXIS_Y, yTransformed); + out.setAxisValue(AMOTION_EVENT_AXIS_X, transformed.x); + out.setAxisValue(AMOTION_EVENT_AXIS_Y, transformed.y); out.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure); out.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size); out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, touchMajor); @@ -2422,8 +2447,8 @@ void TouchInputMapper::cookPointerData() { if (mSource == AINPUT_SOURCE_TOUCHPAD && mLastCookedState.cookedPointerData.hasPointerCoordsForId(id)) { const PointerCoords& p = mLastCookedState.cookedPointerData.pointerCoordsForId(id); - float dx = xTransformed - p.getAxisValue(AMOTION_EVENT_AXIS_X); - float dy = yTransformed - p.getAxisValue(AMOTION_EVENT_AXIS_Y); + float dx = transformed.x - p.getAxisValue(AMOTION_EVENT_AXIS_X); + float dy = transformed.y - p.getAxisValue(AMOTION_EVENT_AXIS_Y); out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, dx); out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, dy); } @@ -3796,41 +3821,6 @@ std::list TouchInputMapper::cancelTouch(nsecs_t when, nsecs_t readTi return out; } -// Transform input device coordinates to display panel coordinates. -void TouchInputMapper::rotateAndScale(float& x, float& y) const { - const float xScaled = float(x - mRawPointerAxes.x.minValue) * mXScale; - const float yScaled = float(y - mRawPointerAxes.y.minValue) * mYScale; - - const float xScaledMax = float(mRawPointerAxes.x.maxValue - x) * mXScale; - const float yScaledMax = float(mRawPointerAxes.y.maxValue - y) * mYScale; - - // Rotate to display coordinate. - // 0 - no swap and reverse. - // 90 - swap x/y and reverse y. - // 180 - reverse x, y. - // 270 - swap x/y and reverse x. - switch (mInputDeviceOrientation) { - case ui::ROTATION_0: - x = xScaled; - y = yScaled; - break; - case ui::ROTATION_90: - y = xScaledMax; - x = yScaled; - break; - case ui::ROTATION_180: - x = xScaledMax; - y = yScaledMax; - break; - case ui::ROTATION_270: - y = xScaled; - x = yScaledMax; - break; - default: - assert(false); - } -} - bool TouchInputMapper::isPointInsidePhysicalFrame(int32_t x, int32_t y) const { const float xScaled = (x - mRawPointerAxes.x.minValue) * mXScale; const float yScaled = (y - mRawPointerAxes.y.minValue) * mYScale; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 50a7ea387a..45a4962f41 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -419,6 +419,10 @@ private: // orientation, so it will depend on whether the device is orientation aware. ui::Rotation mInputDeviceOrientation; + // The transform that maps the input device's raw coordinate space to the un-rotated display's + // coordinate space. InputReader generates events in the un-rotated display's coordinate space. + ui::Transform mRawToDisplay; + // Translation and scaling factors, orientation-independent. float mXScale; float mXPrecision; @@ -813,7 +817,7 @@ private: static void assignPointerIds(const RawState& last, RawState& current); - void rotateAndScale(float& x, float& y) const; + ui::Transform computeInputTransform() const; void configureDeviceType(); }; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index db6bf45d0b..621bad7633 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -5535,7 +5535,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_RotatesMotion // Rotation 90. clearViewports(); prepareDisplay(ui::ROTATION_90); - processDown(mapper, toRawX(75), RAW_Y_MAX - toRawY(50) + RAW_Y_MIN); + processDown(mapper, toRotatedRawX(75), RAW_Y_MAX - toRotatedRawY(50) + RAW_Y_MIN); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); @@ -5563,7 +5563,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_RotatesMotion // Rotation 270. clearViewports(); prepareDisplay(ui::ROTATION_270); - processDown(mapper, RAW_X_MAX - toRawX(75) + RAW_X_MIN, toRawY(50)); + processDown(mapper, RAW_X_MAX - toRotatedRawX(75) + RAW_X_MIN, toRotatedRawY(50)); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); @@ -5700,7 +5700,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationSpecified_RotatesMotio // Orientation 90, Rotation 90. clearViewports(); prepareDisplay(ui::ROTATION_90); - processDown(mapper, toRotatedRawX(50), toRotatedRawY(75)); + processDown(mapper, toRawX(50), toRawY(75)); processSync(mapper); EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); @@ -5728,8 +5728,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationSpecified_RotatesMotio // Orientation 90, Rotation 270. clearViewports(); prepareDisplay(ui::ROTATION_270); - processDown(mapper, RAW_X_MAX - toRotatedRawX(50) + RAW_X_MIN, - RAW_Y_MAX - toRotatedRawY(75) + RAW_Y_MIN); + processDown(mapper, RAW_X_MAX - toRawX(50) + RAW_X_MIN, RAW_Y_MAX - toRawY(75) + RAW_Y_MIN); processSync(mapper); EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); -- GitLab From 675f25abd9341963827033a5cc4b7786a800a11d Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 10 Nov 2022 22:04:07 +0000 Subject: [PATCH 0630/1310] TouchInputMapper: Perform physical frame hit test in rotated display space The physical frame is specified in DisplayViewport in the rotated display space. The frame is not symmetric along the X or Y axes because the right and bottom edges are outside of the frame. For example, for a physical frame with bounds [left, top, right, bottom], any point with an x value of `right` or y value of `bottom` is outside the frame, whereas points in the frame could contain an x value of `left` and a y value of `top`. To address this asymmetry, we must perform any hit tests in the intended coordinate space, which in this case is that of the rotated display. This logic is tested again in following CLs. Bug: 236798672 Bug: 257118693 Test: atest inputflinger_tests Change-Id: I403a686c437aa53cb808910b296a7251e0e96321 --- .../reader/mapper/TouchInputMapper.cpp | 53 +++++++----------- .../reader/mapper/TouchInputMapper.h | 11 +++- .../inputflinger/tests/InputReader_test.cpp | 55 +++++++++++++++++++ 3 files changed, 84 insertions(+), 35 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 7c566317bd..66691f83c0 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -51,9 +51,8 @@ static std::string toString(const ui::Size& size) { return base::StringPrintf("%dx%d", size.width, size.height); } -static bool isPointInRect(const Rect& rect, int32_t x, int32_t y) { - // Consider all four sides as "inclusive". - return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom; +static bool isPointInRect(const Rect& rect, vec2 p) { + return p.x >= rect.left && p.x < rect.right && p.y >= rect.top && p.y < rect.bottom; } template @@ -81,29 +80,12 @@ inline static int32_t signExtendNybble(int32_t value) { return value >= 8 ? value - 16 : value; } -static std::tuple getNaturalDisplayInfo( - const DisplayViewport& viewport) { +static ui::Size getNaturalDisplaySize(const DisplayViewport& viewport) { ui::Size rotatedDisplaySize{viewport.deviceWidth, viewport.deviceHeight}; if (viewport.orientation == ui::ROTATION_90 || viewport.orientation == ui::ROTATION_270) { std::swap(rotatedDisplaySize.width, rotatedDisplaySize.height); } - - ui::Transform rotate(ui::Transform::toRotationFlags(viewport.orientation), - rotatedDisplaySize.width, rotatedDisplaySize.height); - - Rect physicalFrame{viewport.physicalLeft, viewport.physicalTop, viewport.physicalRight, - viewport.physicalBottom}; - physicalFrame = rotate.transform(physicalFrame); - - LOG_ALWAYS_FATAL_IF(!physicalFrame.isValid()); - if (physicalFrame.isEmpty()) { - ALOGE("Viewport is not set properly: %s", viewport.toString().c_str()); - physicalFrame.right = - physicalFrame.left + (physicalFrame.width() == 0 ? 1 : physicalFrame.width()); - physicalFrame.bottom = - physicalFrame.top + (physicalFrame.height() == 0 ? 1 : physicalFrame.height()); - } - return {rotatedDisplaySize, physicalFrame}; + return rotatedDisplaySize; } // --- RawPointerData --- @@ -856,7 +838,7 @@ void TouchInputMapper::initializeOrientedRanges() { } } -ui::Transform TouchInputMapper::computeInputTransform() const { +void TouchInputMapper::computeInputTransforms() { const ui::Size rawSize{mRawPointerAxes.getRawWidth(), mRawPointerAxes.getRawHeight()}; ui::Size rotatedRawSize = rawSize; @@ -881,7 +863,13 @@ ui::Transform TouchInputMapper::computeInputTransform() const { const float yScale = static_cast(mDisplayBounds.height) / rotatedRawSize.height; scaleToDisplay.set(xScale, 0, 0, yScale); - return (scaleToDisplay * (rotate * undoRawOffset)); + mRawToDisplay = (scaleToDisplay * (rotate * undoRawOffset)); + + // Calculate the transform that takes raw coordinates to the rotated display space. + ui::Transform displayToRotatedDisplay; + displayToRotatedDisplay.set(ui::Transform::toRotationFlags(-mViewport.orientation), + mViewport.deviceWidth, mViewport.deviceHeight); + mRawToRotatedDisplay = displayToRotatedDisplay * mRawToDisplay; } void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) { @@ -955,7 +943,9 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) if (mDeviceMode == DeviceMode::DIRECT || mDeviceMode == DeviceMode::POINTER) { const auto oldDisplayBounds = mDisplayBounds; - std::tie(mDisplayBounds, mPhysicalFrameInDisplay) = getNaturalDisplayInfo(mViewport); + mDisplayBounds = getNaturalDisplaySize(mViewport); + mPhysicalFrameInRotatedDisplay = {mViewport.physicalLeft, mViewport.physicalTop, + mViewport.physicalRight, mViewport.physicalBottom}; // InputReader works in the un-rotated display coordinate space, so we don't need to do // anything if the device is already orientation-aware. If the device is not @@ -972,13 +962,14 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) // Apply the input device orientation for the device. mInputDeviceOrientation = mInputDeviceOrientation + mParameters.orientation; - mRawToDisplay = computeInputTransform(); + computeInputTransforms(); } else { mDisplayBounds = rawSize; - mPhysicalFrameInDisplay = Rect{mDisplayBounds}; + mPhysicalFrameInRotatedDisplay = Rect{mDisplayBounds}; mInputDeviceOrientation = ui::ROTATION_0; mRawToDisplay.reset(); mRawToDisplay.set(-mRawPointerAxes.x.minValue, -mRawPointerAxes.y.minValue); + mRawToRotatedDisplay = mRawToDisplay; } } @@ -1061,7 +1052,8 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) void TouchInputMapper::dumpDisplay(std::string& dump) { dump += StringPrintf(INDENT3 "%s\n", mViewport.toString().c_str()); dump += StringPrintf(INDENT3 "DisplayBounds: %s\n", toString(mDisplayBounds).c_str()); - dump += StringPrintf(INDENT3 "PhysicalFrame: %s\n", toString(mPhysicalFrameInDisplay).c_str()); + dump += StringPrintf(INDENT3 "PhysicalFrameInRotatedDisplay: %s\n", + toString(mPhysicalFrameInRotatedDisplay).c_str()); dump += StringPrintf(INDENT3 "InputDeviceOrientation: %d\n", mInputDeviceOrientation); } @@ -3822,12 +3814,9 @@ std::list TouchInputMapper::cancelTouch(nsecs_t when, nsecs_t readTi } bool TouchInputMapper::isPointInsidePhysicalFrame(int32_t x, int32_t y) const { - const float xScaled = (x - mRawPointerAxes.x.minValue) * mXScale; - const float yScaled = (y - mRawPointerAxes.y.minValue) * mYScale; - return x >= mRawPointerAxes.x.minValue && x <= mRawPointerAxes.x.maxValue && y >= mRawPointerAxes.y.minValue && y <= mRawPointerAxes.y.maxValue && - isPointInRect(mPhysicalFrameInDisplay, xScaled, yScaled); + isPointInRect(mPhysicalFrameInRotatedDisplay, mRawToRotatedDisplay.transform(x, y)); } const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHit(int32_t x, int32_t y) { diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 45a4962f41..1a583c0dda 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -410,9 +410,9 @@ private: // Always starts at (0, 0). ui::Size mDisplayBounds{ui::kInvalidSize}; - // The physical frame is the rectangle in the natural display's coordinate space that maps to + // The physical frame is the rectangle in the rotated display's coordinate space that maps to // the logical display frame. - Rect mPhysicalFrameInDisplay{Rect::INVALID_RECT}; + Rect mPhysicalFrameInRotatedDisplay{Rect::INVALID_RECT}; // The orientation of the input device relative to that of the display panel. It specifies // the rotation of the input device coordinates required to produce the display panel @@ -423,6 +423,11 @@ private: // coordinate space. InputReader generates events in the un-rotated display's coordinate space. ui::Transform mRawToDisplay; + // The transform that maps the input device's raw coordinate space to the rotated display's + // coordinate space. This used to perform hit-testing of raw events with the physical frame in + // the rotated coordinate space. See mPhysicalFrameInRotatedDisplay. + ui::Transform mRawToRotatedDisplay; + // Translation and scaling factors, orientation-independent. float mXScale; float mXPrecision; @@ -817,7 +822,7 @@ private: static void assignPointerIds(const RawState& last, RawState& current); - ui::Transform computeInputTransform() const; + void computeInputTransforms(); void configureDeviceType(); }; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 621bad7633..e407638b0a 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -5740,6 +5740,61 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationSpecified_RotatesMotio EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); } +TEST_F(SingleTouchInputMapperTest, Process_IgnoresTouchesOutsidePhysicalFrame) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareButtons(); + prepareAxes(POSITION); + addConfigurationProperty("touch.orientationAware", "1"); + prepareDisplay(ui::ROTATION_0); + auto& mapper = addMapperAndConfigure(); + + // Set a physical frame in the display viewport. + auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); + viewport->physicalLeft = 20; + viewport->physicalTop = 600; + viewport->physicalRight = 30; + viewport->physicalBottom = 610; + mFakePolicy->updateViewport(*viewport); + configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + + // Start the touch. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 1); + processSync(mapper); + + // Expect all input starting outside the physical frame to be ignored. + const std::array outsidePoints = { + {{0, 0}, {19, 605}, {31, 605}, {25, 599}, {25, 611}, {DISPLAY_WIDTH, DISPLAY_HEIGHT}}}; + for (const auto& p : outsidePoints) { + processMove(mapper, toRawX(p.x), toRawY(p.y)); + processSync(mapper); + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + } + + // Move the touch into the physical frame. + processMove(mapper, toRawX(25), toRawY(605)); + processSync(mapper); + NotifyMotionArgs args; + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); + EXPECT_NEAR(25, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + EXPECT_NEAR(605, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + + // Once the touch down is reported, continue reporting input, even if it is outside the frame. + for (const auto& p : outsidePoints) { + processMove(mapper, toRawX(p.x), toRawY(p.y)); + processSync(mapper); + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + EXPECT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + EXPECT_NEAR(p.x, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1); + EXPECT_NEAR(p.y, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1); + } + + processUp(mapper); + processSync(mapper); + EXPECT_NO_FATAL_FAILURE( + mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP))); +} + TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); -- GitLab From e2e10b4a0143b4a90be2d6a52b8cc2757d18d4bf Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 17 Nov 2022 20:59:36 +0000 Subject: [PATCH 0631/1310] TouchInputMapper: Use ui::Transform to calculate orientation angles Rather than manually re-orienting the calculated angles for orientation and tilt, we use the transform to compute the oriented values for these non-planar axes. This approach is both less error-prone and less verbose. In this CL, we also transform the coverage rect from raw to display space using the computed transform. Bug: 236798672 Test: atest inputflinger_tests Change-Id: Ibfb6d2ab43e6fd7a63736ee7d9610c42be44affd --- include/input/Input.h | 7 ++ libs/input/Input.cpp | 38 +++--- .../reader/mapper/TouchInputMapper.cpp | 114 +++++------------- .../reader/mapper/TouchInputMapper.h | 8 +- 4 files changed, 58 insertions(+), 109 deletions(-) diff --git a/include/input/Input.h b/include/input/Input.h index 015efdd064..1a35196036 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -203,6 +203,13 @@ class Parcel; */ vec2 transformWithoutTranslation(const ui::Transform& transform, const vec2& xy); +/* + * Transform an angle on the x-y plane. An angle of 0 radians corresponds to "north" or + * pointing upwards in the negative Y direction, a positive angle points towards the right, and a + * negative angle points towards the left. + */ +float transformAngle(const ui::Transform& transform, float angleRadians); + const char* inputEventTypeToString(int32_t type); std::string inputEventSourceToString(int32_t source); diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index 9e8ebf30c5..d893cb99ba 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -46,25 +46,6 @@ namespace android { namespace { -float transformAngle(const ui::Transform& transform, float angleRadians) { - // Construct and transform a vector oriented at the specified clockwise angle from vertical. - // Coordinate system: down is increasing Y, right is increasing X. - float x = sinf(angleRadians); - float y = -cosf(angleRadians); - vec2 transformedPoint = transform.transform(x, y); - - // Determine how the origin is transformed by the matrix so that we - // can transform orientation vectors. - const vec2 origin = transform.transform(0, 0); - - transformedPoint.x -= origin.x; - transformedPoint.y -= origin.y; - - // Derive the transformed vector's clockwise angle from vertical. - // The return value of atan2f is in range [-pi, pi] which conforms to the orientation API. - return atan2f(transformedPoint.x, -transformedPoint.y); -} - bool shouldDisregardTransformation(uint32_t source) { // Do not apply any transformations to axes from joysticks, touchpads, or relative mice. return isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK) || @@ -172,6 +153,25 @@ vec2 transformWithoutTranslation(const ui::Transform& transform, const vec2& xy) return transformedXy - transformedOrigin; } +float transformAngle(const ui::Transform& transform, float angleRadians) { + // Construct and transform a vector oriented at the specified clockwise angle from vertical. + // Coordinate system: down is increasing Y, right is increasing X. + float x = sinf(angleRadians); + float y = -cosf(angleRadians); + vec2 transformedPoint = transform.transform(x, y); + + // Determine how the origin is transformed by the matrix so that we + // can transform orientation vectors. + const vec2 origin = transform.transform(0, 0); + + transformedPoint.x -= origin.x; + transformedPoint.y -= origin.y; + + // Derive the transformed vector's clockwise angle from vertical. + // The return value of atan2f is in range [-pi, pi] which conforms to the orientation API. + return atan2f(transformedPoint.x, -transformedPoint.y); +} + const char* inputEventTypeToString(int32_t type) { switch (type) { case AINPUT_EVENT_TYPE_KEY: { diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 66691f83c0..80e1a8900a 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -207,10 +207,9 @@ void TouchInputMapper::dump(std::string& dump) { dump += StringPrintf(INDENT3 "Translation and Scaling Factors:\n"); mRawToDisplay.dump(dump, "RawToDisplay Transform:", INDENT4); - dump += StringPrintf(INDENT4 "XScale: %0.3f\n", mXScale); - dump += StringPrintf(INDENT4 "YScale: %0.3f\n", mYScale); - dump += StringPrintf(INDENT4 "XPrecision: %0.3f\n", mXPrecision); - dump += StringPrintf(INDENT4 "YPrecision: %0.3f\n", mYPrecision); + mRawRotation.dump(dump, "RawRotation Transform:", INDENT4); + dump += StringPrintf(INDENT4 "OrientedXPrecision: %0.3f\n", mOrientedXPrecision); + dump += StringPrintf(INDENT4 "OrientedYPrecision: %0.3f\n", mOrientedYPrecision); dump += StringPrintf(INDENT4 "GeometricScale: %0.3f\n", mGeometricScale); dump += StringPrintf(INDENT4 "PressureScale: %0.3f\n", mPressureScale); dump += StringPrintf(INDENT4 "SizeScale: %0.3f\n", mSizeScale); @@ -670,10 +669,10 @@ void TouchInputMapper::initializeSizeRanges() { void TouchInputMapper::initializeOrientedRanges() { // Configure X and Y factors. - mXScale = float(mDisplayBounds.width) / mRawPointerAxes.getRawWidth(); - mYScale = float(mDisplayBounds.height) / mRawPointerAxes.getRawHeight(); - mXPrecision = 1.0f / mXScale; - mYPrecision = 1.0f / mYScale; + const float orientedScaleX = mRawToDisplay.getScaleX(); + const float orientedScaleY = mRawToDisplay.getScaleY(); + mOrientedXPrecision = 1.0f / orientedScaleX; + mOrientedYPrecision = 1.0f / orientedScaleY; mOrientedRanges.x.axis = AMOTION_EVENT_AXIS_X; mOrientedRanges.x.source = mSource; @@ -683,7 +682,7 @@ void TouchInputMapper::initializeOrientedRanges() { // Scale factor for terms that are not oriented in a particular axis. // If the pixels are square then xScale == yScale otherwise we fake it // by choosing an average. - mGeometricScale = avg(mXScale, mYScale); + mGeometricScale = avg(orientedScaleX, orientedScaleY); initializeSizeRanges(); @@ -800,40 +799,35 @@ void TouchInputMapper::initializeOrientedRanges() { // Compute oriented precision, scales and ranges. // Note that the maximum value reported is an inclusive maximum value so it is one // unit less than the total width or height of the display. + // TODO(b/20508709): Calculate the oriented ranges using the input device's raw frame. switch (mInputDeviceOrientation) { case ui::ROTATION_90: case ui::ROTATION_270: - mOrientedXPrecision = mYPrecision; - mOrientedYPrecision = mXPrecision; - mOrientedRanges.x.min = 0; mOrientedRanges.x.max = mDisplayBounds.height - 1; mOrientedRanges.x.flat = 0; mOrientedRanges.x.fuzz = 0; - mOrientedRanges.x.resolution = mRawPointerAxes.y.resolution * mYScale; + mOrientedRanges.x.resolution = mRawPointerAxes.y.resolution * mRawToDisplay.getScaleY(); mOrientedRanges.y.min = 0; mOrientedRanges.y.max = mDisplayBounds.width - 1; mOrientedRanges.y.flat = 0; mOrientedRanges.y.fuzz = 0; - mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mXScale; + mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mRawToDisplay.getScaleX(); break; default: - mOrientedXPrecision = mXPrecision; - mOrientedYPrecision = mYPrecision; - mOrientedRanges.x.min = 0; mOrientedRanges.x.max = mDisplayBounds.width - 1; mOrientedRanges.x.flat = 0; mOrientedRanges.x.fuzz = 0; - mOrientedRanges.x.resolution = mRawPointerAxes.x.resolution * mXScale; + mOrientedRanges.x.resolution = mRawPointerAxes.x.resolution * mRawToDisplay.getScaleX(); mOrientedRanges.y.min = 0; mOrientedRanges.y.max = mDisplayBounds.height - 1; mOrientedRanges.y.flat = 0; mOrientedRanges.y.fuzz = 0; - mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mYScale; + mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mRawToDisplay.getScaleY(); break; } } @@ -845,6 +839,8 @@ void TouchInputMapper::computeInputTransforms() { if (mInputDeviceOrientation == ui::ROTATION_270 || mInputDeviceOrientation == ui::ROTATION_90) { std::swap(rotatedRawSize.width, rotatedRawSize.height); } + const auto rotationFlags = ui::Transform::toRotationFlags(-mInputDeviceOrientation); + mRawRotation = ui::Transform{rotationFlags}; // Step 1: Undo the raw offset so that the raw coordinate space now starts at (0, 0). ui::Transform undoRawOffset; @@ -854,8 +850,7 @@ void TouchInputMapper::computeInputTransforms() { ui::Transform rotate; // When rotating raw coordinates, the raw size will be used as an offset. // Account for the extra unit added to the raw range when the raw size was calculated. - rotate.set(ui::Transform::toRotationFlags(-mInputDeviceOrientation), rotatedRawSize.width - 1, - rotatedRawSize.height - 1); + rotate.set(rotationFlags, rotatedRawSize.width - 1, rotatedRawSize.height - 1); // Step 3: Scale the raw coordinates to the display space. ui::Transform scaleToDisplay; @@ -2307,20 +2302,20 @@ void TouchInputMapper::cookPointerData() { if (mHaveTilt) { float tiltXAngle = (in.tiltX - mTiltXCenter) * mTiltXScale; float tiltYAngle = (in.tiltY - mTiltYCenter) * mTiltYScale; - orientation = atan2f(-sinf(tiltXAngle), sinf(tiltYAngle)); + orientation = transformAngle(mRawRotation, atan2f(-sinf(tiltXAngle), sinf(tiltYAngle))); tilt = acosf(cosf(tiltXAngle) * cosf(tiltYAngle)); } else { tilt = 0; switch (mCalibration.orientationCalibration) { case Calibration::OrientationCalibration::INTERPOLATED: - orientation = in.orientation * mOrientationScale; + orientation = transformAngle(mRawRotation, in.orientation * mOrientationScale); break; case Calibration::OrientationCalibration::VECTOR: { int32_t c1 = signExtendNybble((in.orientation & 0xf0) >> 4); int32_t c2 = signExtendNybble(in.orientation & 0x0f); if (c1 != 0 || c2 != 0) { - orientation = atan2f(c1, c2) * 0.5f; + orientation = transformAngle(mRawRotation, atan2f(c1, c2) * 0.5f); float confidence = hypotf(c1, c2); float scale = 1.0f + confidence / 16.0f; touchMajor *= scale; @@ -2348,18 +2343,14 @@ void TouchInputMapper::cookPointerData() { } // Coverage - int32_t rawLeft, rawTop, rawRight, rawBottom; - switch (mCalibration.coverageCalibration) { - case Calibration::CoverageCalibration::BOX: - rawLeft = (in.toolMinor & 0xffff0000) >> 16; - rawRight = in.toolMinor & 0x0000ffff; - rawBottom = in.toolMajor & 0x0000ffff; - rawTop = (in.toolMajor & 0xffff0000) >> 16; - break; - default: - rawLeft = rawTop = rawRight = rawBottom = 0; - break; + Rect rawCoverage{0, 0}; + if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::BOX) { + rawCoverage.left = (in.toolMinor & 0xffff0000) >> 16; + rawCoverage.right = in.toolMinor & 0x0000ffff; + rawCoverage.bottom = in.toolMajor & 0x0000ffff; + rawCoverage.top = (in.toolMajor & 0xffff0000) >> 16; } + const auto coverage = mRawToDisplay.transform(rawCoverage); // Adjust X,Y coords for device calibration and convert to the natural display coordinates. vec2 transformed = {in.x, in.y}; @@ -2367,51 +2358,6 @@ void TouchInputMapper::cookPointerData() { mAffineTransform.applyTo(transformed.x /*byRef*/, transformed.y /*byRef*/); transformed = mRawToDisplay.transform(transformed); - // Adjust X, Y, and coverage coords for input device orientation. - float left, top, right, bottom; - - switch (mInputDeviceOrientation) { - case ui::ROTATION_90: - left = float(rawTop - mRawPointerAxes.y.minValue) * mYScale; - right = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale; - bottom = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale; - top = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale; - orientation -= M_PI_2; - if (mOrientedRanges.orientation && orientation < mOrientedRanges.orientation->min) { - orientation += - (mOrientedRanges.orientation->max - mOrientedRanges.orientation->min); - } - break; - case ui::ROTATION_180: - left = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale; - right = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale; - bottom = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale; - top = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale; - orientation -= M_PI; - if (mOrientedRanges.orientation && orientation < mOrientedRanges.orientation->min) { - orientation += - (mOrientedRanges.orientation->max - mOrientedRanges.orientation->min); - } - break; - case ui::ROTATION_270: - left = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale; - right = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale; - bottom = float(rawRight - mRawPointerAxes.x.minValue) * mXScale; - top = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale; - orientation += M_PI_2; - if (mOrientedRanges.orientation && orientation > mOrientedRanges.orientation->max) { - orientation -= - (mOrientedRanges.orientation->max - mOrientedRanges.orientation->min); - } - break; - default: - left = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale; - right = float(rawRight - mRawPointerAxes.x.minValue) * mXScale; - bottom = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale; - top = float(rawTop - mRawPointerAxes.y.minValue) * mYScale; - break; - } - // Write output coords. PointerCoords& out = mCurrentCookedState.cookedPointerData.pointerCoords[i]; out.clear(); @@ -2425,10 +2371,10 @@ void TouchInputMapper::cookPointerData() { out.setAxisValue(AMOTION_EVENT_AXIS_TILT, tilt); out.setAxisValue(AMOTION_EVENT_AXIS_DISTANCE, distance); if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::BOX) { - out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_1, left); - out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_2, top); - out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_3, right); - out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_4, bottom); + out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_1, coverage.left); + out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_2, coverage.top); + out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_3, coverage.right); + out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_4, coverage.bottom); } else { out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor); out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor); diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 1a583c0dda..29b850ffd3 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -428,12 +428,8 @@ private: // the rotated coordinate space. See mPhysicalFrameInRotatedDisplay. ui::Transform mRawToRotatedDisplay; - // Translation and scaling factors, orientation-independent. - float mXScale; - float mXPrecision; - - float mYScale; - float mYPrecision; + // The transform used for non-planar raw axes, such as orientation and tilt. + ui::Transform mRawRotation; float mGeometricScale; -- GitLab From 64fd520f4ad9c34639389bfd8f0d50d78581f0d4 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 30 Nov 2022 19:45:11 +0000 Subject: [PATCH 0632/1310] TouchInputMapper: Remove the concept of coverage calibration Coverage calibration assumes the overloaded use of certain touch protocol axes, and was added as a workaround for a specific device. Since it is no longer being used, it can be removed. Bug: 260869755 Test: atest inputflinger_tests Change-Id: If7b0026a2c55672d6a3141cf29ce442e9e92dc75 --- .../reader/mapper/TouchInputMapper.cpp | 63 +------------------ .../reader/mapper/TouchInputMapper.h | 8 --- 2 files changed, 2 insertions(+), 69 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 80e1a8900a..9a7af40456 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -179,18 +179,6 @@ void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) { if (mCursorScrollAccumulator.haveRelativeHWheel()) { info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); } - if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::BOX) { - const InputDeviceInfo::MotionRange& x = mOrientedRanges.x; - const InputDeviceInfo::MotionRange& y = mOrientedRanges.y; - info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_1, mSource, x.min, x.max, x.flat, x.fuzz, - x.resolution); - info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_2, mSource, y.min, y.max, y.flat, y.fuzz, - y.resolution); - info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_3, mSource, x.min, x.max, x.flat, x.fuzz, - x.resolution); - info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_4, mSource, y.min, y.max, y.flat, y.fuzz, - y.resolution); - } info->setButtonUnderPad(mParameters.hasButtonUnderPad); info->setSupportsUsi(mParameters.supportsUsi); } @@ -1209,19 +1197,6 @@ void TouchInputMapper::parseCalibration() { if (in.tryGetProperty("touch.distance.scale", distanceScale)) { out.distanceScale = distanceScale; } - - out.coverageCalibration = Calibration::CoverageCalibration::DEFAULT; - std::string coverageCalibrationString; - if (in.tryGetProperty("touch.coverage.calibration", coverageCalibrationString)) { - if (coverageCalibrationString == "none") { - out.coverageCalibration = Calibration::CoverageCalibration::NONE; - } else if (coverageCalibrationString == "box") { - out.coverageCalibration = Calibration::CoverageCalibration::BOX; - } else if (coverageCalibrationString != "default") { - ALOGW("Invalid value for touch.coverage.calibration: '%s'", - coverageCalibrationString.c_str()); - } - } } void TouchInputMapper::resolveCalibration() { @@ -1260,11 +1235,6 @@ void TouchInputMapper::resolveCalibration() { } else { mCalibration.distanceCalibration = Calibration::DistanceCalibration::NONE; } - - // Coverage - if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::DEFAULT) { - mCalibration.coverageCalibration = Calibration::CoverageCalibration::NONE; - } } void TouchInputMapper::dumpCalibration(std::string& dump) { @@ -1335,17 +1305,6 @@ void TouchInputMapper::dumpCalibration(std::string& dump) { if (mCalibration.distanceScale) { dump += StringPrintf(INDENT4 "touch.distance.scale: %0.3f\n", *mCalibration.distanceScale); } - - switch (mCalibration.coverageCalibration) { - case Calibration::CoverageCalibration::NONE: - dump += INDENT4 "touch.coverage.calibration: none\n"; - break; - case Calibration::CoverageCalibration::BOX: - dump += INDENT4 "touch.coverage.calibration: box\n"; - break; - default: - ALOG_ASSERT(false); - } } void TouchInputMapper::dumpAffineTransformation(std::string& dump) { @@ -2342,19 +2301,8 @@ void TouchInputMapper::cookPointerData() { distance = 0; } - // Coverage - Rect rawCoverage{0, 0}; - if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::BOX) { - rawCoverage.left = (in.toolMinor & 0xffff0000) >> 16; - rawCoverage.right = in.toolMinor & 0x0000ffff; - rawCoverage.bottom = in.toolMajor & 0x0000ffff; - rawCoverage.top = (in.toolMajor & 0xffff0000) >> 16; - } - const auto coverage = mRawToDisplay.transform(rawCoverage); - // Adjust X,Y coords for device calibration and convert to the natural display coordinates. vec2 transformed = {in.x, in.y}; - // TODO: Adjust coverage coords? mAffineTransform.applyTo(transformed.x /*byRef*/, transformed.y /*byRef*/); transformed = mRawToDisplay.transform(transformed); @@ -2370,15 +2318,8 @@ void TouchInputMapper::cookPointerData() { out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, orientation); out.setAxisValue(AMOTION_EVENT_AXIS_TILT, tilt); out.setAxisValue(AMOTION_EVENT_AXIS_DISTANCE, distance); - if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::BOX) { - out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_1, coverage.left); - out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_2, coverage.top); - out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_3, coverage.right); - out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_4, coverage.bottom); - } else { - out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor); - out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor); - } + out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor); + out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor); // Write output relative fields if applicable. uint32_t id = in.id; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 29b850ffd3..6e35b460af 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -291,14 +291,6 @@ protected: DistanceCalibration distanceCalibration; std::optional distanceScale; - enum class CoverageCalibration { - DEFAULT, - NONE, - BOX, - }; - - CoverageCalibration coverageCalibration; - inline void applySizeScaleAndBias(float& outSize) const { if (sizeScale) { outSize *= *sizeScale; -- GitLab From f9c984f6cc6087cc29c61c463ffc449003f1f4fc Mon Sep 17 00:00:00 2001 From: Peiyong Lin Date: Fri, 11 Nov 2022 18:28:20 +0000 Subject: [PATCH 0633/1310] Add setThreads to APerformanceHint. Bug: b/244216750 Test: atest PerformanceHintManagerTest Change-Id: I98f94a47caf94a9f9027f14ec01f5da531a20d35 --- include/android/performance_hint.h | 17 +++++++++++++++++ include/private/performance_hint_private.h | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h index 5fa47f64be..4a5bd5ecfb 100644 --- a/include/android/performance_hint.h +++ b/include/android/performance_hint.h @@ -159,6 +159,23 @@ int APerformanceHint_reportActualWorkDuration( void APerformanceHint_closeSession( APerformanceHintSession* session) __INTRODUCED_IN(__ANDROID_API_T__); +/** + * Set a list of threads to the performance hint session. This operation will replace + * the current list of threads with the given list of threads. + * + * @param session The performance hint session instance for the threads. + * @param threadIds The list of threads to be associated with this session. They must be part of + * this app's thread group. + * @param size the size of the list of threadIds. + * @return 0 on success. + * EINVAL if the list of thread ids is empty or if any of the thread ids is not part of the thread group. + * EPIPE if communication with the system service has failed. + */ +int APerformanceHint_setThreads( + APerformanceHintSession* session, + const int32_t* threadIds, + size_t size) __INTRODUCED_IN(__ANDROID_API_U__); + __END_DECLS #endif // ANDROID_NATIVE_PERFORMANCE_HINT_H diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h index 1333bcc73b..eaf3b5e791 100644 --- a/include/private/performance_hint_private.h +++ b/include/private/performance_hint_private.h @@ -63,6 +63,12 @@ enum SessionHint { */ int APerformanceHint_sendHint(void* session, int hint); +/** + * Return the list of thread ids, this API should only be used for testing only. + */ +int APerformanceHint_getThreadIds(void* aPerformanceHintSession, + int32_t* const threadIds, size_t* const size); + __END_DECLS #endif // ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H -- GitLab From b158a5c543132eca535af220d96356c24810b5e8 Mon Sep 17 00:00:00 2001 From: Brian Lindahl Date: Thu, 15 Dec 2022 15:21:13 -0700 Subject: [PATCH 0634/1310] Maintain the active buffer when clearing buffer slots When an app discards graphic buffers, for example when a MediaCodec disconnects from a surface, those buffers will be uncached and removed from HWC buffer slots. The active buffer, however, should still remain active until the next buffer is queued up. Bug: 262037933 Bug: 258196272 Test: atest OutputLayerUncacheBufferTest Test: atest VtsHalGraphicsComposer3_TargetTest Test: atest VtsHalGraphicsComposerV2_2TargetTest Change-Id: I7c4eefb17e8bad694d698f9ad6d1d289f4af8d2c --- .../compositionengine/impl/HwcBufferCache.h | 2 +- .../impl/OutputLayerCompositionState.h | 3 ++ .../CompositionEngine/src/HwcBufferCache.cpp | 2 +- .../CompositionEngine/src/OutputLayer.cpp | 46 +++++++++++++------ .../tests/HwcBufferCacheTest.cpp | 14 +++--- .../CompositionEngine/tests/MockHWC2.h | 2 +- .../tests/OutputLayerTest.cpp | 10 ++-- .../DisplayHardware/AidlComposerHal.cpp | 28 +++++++++-- .../DisplayHardware/AidlComposerHal.h | 4 +- .../DisplayHardware/ComposerHal.h | 4 +- .../surfaceflinger/DisplayHardware/HWC2.cpp | 6 ++- .../surfaceflinger/DisplayHardware/HWC2.h | 6 ++- .../DisplayHardware/HidlComposerHal.cpp | 23 +++++++++- .../DisplayHardware/HidlComposerHal.h | 4 +- .../mock/DisplayHardware/MockComposer.h | 3 +- 15 files changed, 115 insertions(+), 42 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h index 6e9ea6ffd8..b6a4240b4b 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h @@ -83,7 +83,7 @@ public: // If the buffer is already in the cache, the buffer is null to optimize away sending HWC the // buffer handle. // - HwcSlotAndBuffer getHwcSlotAndBufferForOverride(const sp& buffer); + HwcSlotAndBuffer getOverrideHwcSlotAndBuffer(const sp& buffer); // // When a client process discards a buffer, it needs to be purged from the HWC cache. diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h index 2b383c1dfe..2a5bfaea6c 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h @@ -136,6 +136,9 @@ struct OutputLayerCompositionState { // cost of sending reused buffers to the HWC. HwcBufferCache hwcBufferCache; + // The previously-active buffer for this layer. + uint32_t activeBufferSlot; + // Set to true when overridden info has been sent to HW composer bool stateOverridden = false; diff --git a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp index e2c0acfd81..34ed214079 100644 --- a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp +++ b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp @@ -42,7 +42,7 @@ HwcSlotAndBuffer HwcBufferCache::getHwcSlotAndBuffer(const sp& bu return {cache(buffer), buffer}; } -HwcSlotAndBuffer HwcBufferCache::getHwcSlotAndBufferForOverride(const sp& buffer) { +HwcSlotAndBuffer HwcBufferCache::getOverrideHwcSlotAndBuffer(const sp& buffer) { if (buffer == mLastOverrideBuffer) { return {kOverrideBufferSlot, nullptr}; } diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp index 0ac2d434e8..766d7ea4c3 100644 --- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp +++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp @@ -610,21 +610,27 @@ void OutputLayer::writeSidebandStateToHWC(HWC2::Layer* hwcLayer, } } -void OutputLayer::uncacheBuffers(std::vector const& bufferIdsToUncache) { +void OutputLayer::uncacheBuffers(const std::vector& bufferIdsToUncache) { auto& state = editState(); // Skip doing this if there is no HWC interface if (!state.hwc) { return; } - for (auto bufferId : bufferIdsToUncache) { + std::vector slotsToClear; + for (uint64_t bufferId : bufferIdsToUncache) { uint32_t slot = state.hwc->hwcBufferCache.uncache(bufferId); - if (slot != UINT32_MAX && state.hwc->hwcLayer) { - hal::Error error = state.hwc->hwcLayer->clearBufferSlot(slot); - ALOGE("[%s] Failed to clear buffer slot %d: %s (%d)", getLayerFE().getDebugName(), slot, - to_string(error).c_str(), static_cast(error)); + if (slot != UINT32_MAX) { + slotsToClear.push_back(slot); } } + + hal::Error error = + state.hwc->hwcLayer->setBufferSlotsToClear(slotsToClear, state.hwc->activeBufferSlot); + if (error != hal::Error::NONE) { + ALOGE("[%s] Failed to clear buffer slots: %s (%d)", getLayerFE().getDebugName(), + to_string(error).c_str(), static_cast(error)); + } } void OutputLayer::writeBufferStateToHWC(HWC2::Layer* hwcLayer, @@ -641,15 +647,25 @@ void OutputLayer::writeBufferStateToHWC(HWC2::Layer* hwcLayer, HwcSlotAndBuffer hwcSlotAndBuffer; sp hwcFence; - // Override buffers use a special cache slot so that they don't evict client buffers. - if (getState().overrideInfo.buffer != nullptr && !skipLayer) { - hwcSlotAndBuffer = editState().hwc->hwcBufferCache.getHwcSlotAndBufferForOverride( - getState().overrideInfo.buffer->getBuffer()); - hwcFence = getState().overrideInfo.acquireFence; - } else { - hwcSlotAndBuffer = - editState().hwc->hwcBufferCache.getHwcSlotAndBuffer(outputIndependentState.buffer); - hwcFence = outputIndependentState.acquireFence; + { + // Editing the state only because we update the HWC buffer cache and active buffer. + auto& state = editState(); + // Override buffers use a special cache slot so that they don't evict client buffers. + if (state.overrideInfo.buffer != nullptr && !skipLayer) { + hwcSlotAndBuffer = state.hwc->hwcBufferCache.getOverrideHwcSlotAndBuffer( + state.overrideInfo.buffer->getBuffer()); + hwcFence = state.overrideInfo.acquireFence; + } else { + hwcSlotAndBuffer = + state.hwc->hwcBufferCache.getHwcSlotAndBuffer(outputIndependentState.buffer); + hwcFence = outputIndependentState.acquireFence; + } + + // Keep track of the active buffer slot, so we can restore it after clearing other buffer + // slots. + if (hwcSlotAndBuffer.buffer) { + state.hwc->activeBufferSlot = hwcSlotAndBuffer.slot; + } } if (auto error = hwcLayer->setBuffer(hwcSlotAndBuffer.slot, hwcSlotAndBuffer.buffer, hwcFence); diff --git a/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp b/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp index cf72e8f8de..c5fb5944fb 100644 --- a/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp @@ -115,20 +115,20 @@ TEST_F(HwcBufferCacheTest, uncache_whenUncached_returnsInvalidSlotNumber) { EXPECT_EQ(cache.uncache(mBuffer2->getId()), UINT32_MAX); } -TEST_F(HwcBufferCacheTest, getHwcSlotAndBufferForOverride_whenCached_returnsSameSlotAndNullBuffer) { +TEST_F(HwcBufferCacheTest, getOverrideHwcSlotAndBuffer_whenCached_returnsSameSlotAndNullBuffer) { HwcBufferCache cache; sp outBuffer; - HwcSlotAndBuffer originalSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1); + HwcSlotAndBuffer originalSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(mBuffer1); EXPECT_NE(originalSlotAndBuffer.slot, UINT32_MAX); EXPECT_EQ(originalSlotAndBuffer.buffer, mBuffer1); - HwcSlotAndBuffer finalSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1); + HwcSlotAndBuffer finalSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(mBuffer1); EXPECT_EQ(finalSlotAndBuffer.slot, originalSlotAndBuffer.slot); EXPECT_EQ(finalSlotAndBuffer.buffer, nullptr); } -TEST_F(HwcBufferCacheTest, getHwcSlotAndBufferForOverride_whenSlotsFull_returnsIndependentSlot) { +TEST_F(HwcBufferCacheTest, getOverrideHwcSlotAndBuffer_whenSlotsFull_returnsIndependentSlot) { HwcBufferCache cache; sp outBuffer; @@ -149,7 +149,7 @@ TEST_F(HwcBufferCacheTest, getHwcSlotAndBufferForOverride_whenSlotsFull_returnsI sp overrideBuffer = sp::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u); - HwcSlotAndBuffer overrideSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(overrideBuffer); + HwcSlotAndBuffer overrideSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(overrideBuffer); // expect us to have a slot number EXPECT_NE(overrideSlotAndBuffer.slot, UINT32_MAX); // expect this to be the first time we cached the buffer @@ -173,7 +173,7 @@ TEST_F(HwcBufferCacheTest, uncache_whenOverrideCached_returnsSlotNumber) { HwcBufferCache cache; sp outBuffer; - HwcSlotAndBuffer hwcSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1); + HwcSlotAndBuffer hwcSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(mBuffer1); ASSERT_NE(hwcSlotAndBuffer.slot, UINT32_MAX); EXPECT_EQ(cache.uncache(mBuffer1->getId()), hwcSlotAndBuffer.slot); @@ -184,7 +184,7 @@ TEST_F(HwcBufferCacheTest, uncache_whenOverrideUncached_returnsInvalidSlotNumber HwcBufferCache cache; sp outBuffer; - HwcSlotAndBuffer hwcSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1); + HwcSlotAndBuffer hwcSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(mBuffer1); ASSERT_NE(hwcSlotAndBuffer.slot, UINT32_MAX); EXPECT_EQ(cache.uncache(mBuffer2->getId()), UINT32_MAX); diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h index c7f9c69318..b0b1a02164 100644 --- a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h +++ b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h @@ -56,7 +56,7 @@ public: MOCK_METHOD3(setBuffer, Error(uint32_t, const android::sp&, const android::sp&)); - MOCK_METHOD1(clearBufferSlot, Error(uint32_t)); + MOCK_METHOD2(setBufferSlotsToClear, Error(const std::vector&, uint32_t)); MOCK_METHOD1(setSurfaceDamage, Error(const android::Region&)); MOCK_METHOD1(setBlendMode, Error(hal::BlendMode)); MOCK_METHOD1(setColor, Error(aidl::android::hardware::graphics::composer3::Color)); diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp index f2128c9675..0edc22681f 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp @@ -1361,19 +1361,21 @@ TEST_F(OutputLayerUncacheBufferTest, canUncacheAndReuseSlot) { Mock::VerifyAndClearExpectations(&mHwcLayer); // buffer slots are cleared in HWC - EXPECT_CALL(mHwcLayer, clearBufferSlot(/*slot*/ 0)); - EXPECT_CALL(mHwcLayer, clearBufferSlot(/*slot*/ 1)); + std::vector slotsToClear = {0, 1}; + EXPECT_CALL(mHwcLayer, + setBufferSlotsToClear(/*slotsToClear*/ slotsToClear, /*activeBufferSlot*/ 1)); mOutputLayer.uncacheBuffers({kBuffer1->getId(), kBuffer2->getId()}); Mock::VerifyAndClearExpectations(&mHwcLayer); - // slot 1 is reused for Buffer1 + // rather than allocating a new slot, the active buffer slot (slot 1) is reused first to free + // the memory as soon as possible mLayerFEState.buffer = kBuffer1; EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 1, kBuffer1, kFence)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); Mock::VerifyAndClearExpectations(&mHwcLayer); - // slot 0 is reused for Buffer2 + // rather than allocating a new slot, slot 0 is reused mLayerFEState.buffer = kBuffer2; EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 0, kBuffer2, kFence)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp index a93bd95e48..36da2c3415 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp @@ -820,13 +820,35 @@ Error AidlComposer::setLayerBuffer(Display display, Layer layer, uint32_t slot, return error; } -Error AidlComposer::clearLayerBufferSlot(Display display, Layer layer, uint32_t slot) { +Error AidlComposer::setLayerBufferSlotsToClear(Display display, Layer layer, + const std::vector& slotsToClear, + uint32_t activeBufferSlot) { + if (slotsToClear.empty()) { + return Error::NONE; + } + Error error = Error::NONE; mMutex.lock_shared(); if (auto writer = getWriter(display)) { + // Backwards compatible way of clearing buffer slots is tricky... + for (uint32_t slot : slotsToClear) { + // Don't clear the active buffer slot because we need to restore the active buffer + // after clearing the requested buffer slots with a placeholder buffer. + if (slot != activeBufferSlot) { + writer->get().setLayerBufferWithNewCommand(translate(display), + translate(layer), slot, + mClearSlotBuffer->handle, /*fence*/ -1); + } + } + // Since we clear buffers by setting them to a placeholder buffer, we want to make + // sure that the last setLayerBuffer command is sent with the currently active + // buffer, not the placeholder buffer, so that there is no perceptual change when + // buffers are discarded. writer->get().setLayerBufferWithNewCommand(translate(display), - translate(layer), slot, - mClearSlotBuffer->handle, /*fence*/ -1); + translate(layer), activeBufferSlot, + // The active buffer is still cached in + // its slot and doesn't need a fence. + /*buffer*/ nullptr, /*fence*/ -1); } else { error = Error::BAD_DISPLAY; } diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h index 5128648218..9a7ade7437 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h @@ -143,7 +143,9 @@ public: /* see setClientTarget for the purpose of slot */ Error setLayerBuffer(Display display, Layer layer, uint32_t slot, const sp& buffer, int acquireFence) override; - Error clearLayerBufferSlot(Display display, Layer layer, uint32_t slot) override; + Error setLayerBufferSlotsToClear(Display display, Layer layer, + const std::vector& slotsToClear, + uint32_t activeBufferSlot) override; Error setLayerSurfaceDamage(Display display, Layer layer, const std::vector& damage) override; Error setLayerBlendMode(Display display, Layer layer, IComposerClient::BlendMode mode) override; diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h index 821025c7b4..1c2b8b52d9 100644 --- a/services/surfaceflinger/DisplayHardware/ComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h @@ -179,7 +179,9 @@ public: /* see setClientTarget for the purpose of slot */ virtual Error setLayerBuffer(Display display, Layer layer, uint32_t slot, const sp& buffer, int acquireFence) = 0; - virtual Error clearLayerBufferSlot(Display display, Layer layer, uint32_t slot) = 0; + virtual Error setLayerBufferSlotsToClear(Display display, Layer layer, + const std::vector& slotsToClear, + uint32_t activeBufferSlot) = 0; virtual Error setLayerSurfaceDamage(Display display, Layer layer, const std::vector& damage) = 0; virtual Error setLayerBlendMode(Display display, Layer layer, diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp index b5f91a64f3..6738f00d86 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.cpp +++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp @@ -717,11 +717,13 @@ Error Layer::setBuffer(uint32_t slot, const sp& buffer, return static_cast(intError); } -Error Layer::clearBufferSlot(uint32_t slot) { +Error Layer::setBufferSlotsToClear(const std::vector& slotsToClear, + uint32_t activeBufferSlot) { if (CC_UNLIKELY(!mDisplay)) { return Error::BAD_DISPLAY; } - auto intError = mComposer.clearLayerBufferSlot(mDisplay->getId(), mId, slot); + auto intError = mComposer.setLayerBufferSlotsToClear(mDisplay->getId(), mId, slotsToClear, + activeBufferSlot); return static_cast(intError); } diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h index 13edb136d5..c1c7070a2d 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.h +++ b/services/surfaceflinger/DisplayHardware/HWC2.h @@ -310,7 +310,8 @@ public: [[nodiscard]] virtual hal::Error setBuffer(uint32_t slot, const android::sp& buffer, const android::sp& acquireFence) = 0; - [[nodiscard]] virtual hal::Error clearBufferSlot(uint32_t slot) = 0; + [[nodiscard]] virtual hal::Error setBufferSlotsToClear( + const std::vector& slotsToClear, uint32_t activeBufferSlot) = 0; [[nodiscard]] virtual hal::Error setSurfaceDamage(const android::Region& damage) = 0; [[nodiscard]] virtual hal::Error setBlendMode(hal::BlendMode mode) = 0; @@ -361,7 +362,8 @@ public: hal::Error setCursorPosition(int32_t x, int32_t y) override; hal::Error setBuffer(uint32_t slot, const android::sp& buffer, const android::sp& acquireFence) override; - hal::Error clearBufferSlot(uint32_t slot) override; + hal::Error setBufferSlotsToClear(const std::vector& slotsToClear, + uint32_t activeBufferSlot) override; hal::Error setSurfaceDamage(const android::Region& damage) override; hal::Error setBlendMode(hal::BlendMode mode) override; diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp index 9a49d94dee..c9e1e794ab 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp @@ -712,10 +712,29 @@ Error HidlComposer::setLayerBuffer(Display display, Layer layer, uint32_t slot, return Error::NONE; } -Error HidlComposer::clearLayerBufferSlot(Display display, Layer layer, uint32_t slot) { +Error HidlComposer::setLayerBufferSlotsToClear(Display display, Layer layer, + const std::vector& slotsToClear, + uint32_t activeBufferSlot) { + if (slotsToClear.empty()) { + return Error::NONE; + } + // Backwards compatible way of clearing buffer is to set the layer buffer with a placeholder + // buffer, using the slot that needs to cleared... tricky. + for (uint32_t slot : slotsToClear) { + // Don't clear the active buffer slot because we need to restore the active buffer after + // setting the requested buffer slots with a placeholder buffer. + if (slot != activeBufferSlot) { + mWriter.selectDisplay(display); + mWriter.selectLayer(layer); + mWriter.setLayerBuffer(slot, mClearSlotBuffer->handle, /*fence*/ -1); + } + } + // Since we clear buffers by setting them to a placeholder buffer, we want to make sure that the + // last setLayerBuffer command is sent with the currently active buffer, not the placeholder + // buffer, so that there is no perceptual change. mWriter.selectDisplay(display); mWriter.selectLayer(layer); - mWriter.setLayerBuffer(slot, mClearSlotBuffer->handle, /*fence*/ -1); + mWriter.setLayerBuffer(activeBufferSlot, /*buffer*/ nullptr, /*fence*/ -1); return Error::NONE; } diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h index dc115b259e..921add530a 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h @@ -248,7 +248,9 @@ public: /* see setClientTarget for the purpose of slot */ Error setLayerBuffer(Display display, Layer layer, uint32_t slot, const sp& buffer, int acquireFence) override; - Error clearLayerBufferSlot(Display display, Layer layer, uint32_t slot) override; + Error setLayerBufferSlotsToClear(Display display, Layer layer, + const std::vector& slotsToClear, + uint32_t activeBufferSlot) override; Error setLayerSurfaceDamage(Display display, Layer layer, const std::vector& damage) override; Error setLayerBlendMode(Display display, Layer layer, IComposerClient::BlendMode mode) override; diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h index af3511d5fc..2f16b7b29f 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h @@ -98,7 +98,8 @@ public: Error(Display, nsecs_t, uint32_t*, uint32_t*, int*, uint32_t*)); MOCK_METHOD4(setCursorPosition, Error(Display, Layer, int32_t, int32_t)); MOCK_METHOD5(setLayerBuffer, Error(Display, Layer, uint32_t, const sp&, int)); - MOCK_METHOD3(clearLayerBufferSlot, Error(Display, Layer, uint32_t)); + MOCK_METHOD4(setLayerBufferSlotsToClear, + Error(Display, Layer, const std::vector&, uint32_t)); MOCK_METHOD3(setLayerSurfaceDamage, Error(Display, Layer, const std::vector&)); MOCK_METHOD3(setLayerBlendMode, Error(Display, Layer, IComposerClient::BlendMode)); -- GitLab From eea5be04528879515546ccc54dd2b768ea04c572 Mon Sep 17 00:00:00 2001 From: Aditya Wazir Date: Mon, 31 Oct 2022 12:40:10 +0530 Subject: [PATCH 0635/1310] inputflinger_input_reader_fuzzer: Bug Fix Resolved OOB read getting triggered due to FuzzedDataProvider's shared_ptr which is used among different classes. Implemented ThreadSafe instance of FuzzedDataProvider as bug-fix. Test: ./inputflinger_input_reader_fuzzer clusterfuzz-testcase -minimized-inputflinger_input_reader_fuzzer-6227827124207616 Test: ./inputflinger_input_reader_fuzzer clusterfuzz-testcase -minimized-inputflinger_input_reader_fuzzer-5394273856782336 Bug: 253728999 Bug: 254590389 Change-Id: I42d53d1d7da18c8085d381c939bedf14fe18dc87 (cherry picked from commit 08aa517eee8e7cdf65bf0bd303239e25ed1d50e8) --- .../tests/fuzzers/CursorInputFuzzer.cpp | 6 +- .../tests/fuzzers/FuzzContainer.h | 5 +- .../tests/fuzzers/InputReaderFuzzer.cpp | 3 +- .../tests/fuzzers/KeyboardInputFuzzer.cpp | 6 +- .../tests/fuzzers/MapperHelpers.h | 19 ++- .../tests/fuzzers/MultiTouchInputFuzzer.cpp | 6 +- .../tests/fuzzers/SwitchInputFuzzer.cpp | 4 +- .../fuzzers/ThreadSafeFuzzedDataProvider.h | 136 ++++++++++++++++++ 8 files changed, 160 insertions(+), 25 deletions(-) create mode 100644 services/inputflinger/tests/fuzzers/ThreadSafeFuzzedDataProvider.h diff --git a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp index cc523e12cd..be85026301 100644 --- a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp @@ -16,11 +16,10 @@ #include #include -#include namespace android { -static void addProperty(FuzzContainer& fuzzer, std::shared_ptr fdp) { +static void addProperty(FuzzContainer& fuzzer, std::shared_ptr fdp) { // Pick a random property to set for the mapper to have set. fdp->PickValueInArray>( {[&]() -> void { fuzzer.addProperty("cursor.mode", "pointer"); }, @@ -35,7 +34,8 @@ static void addProperty(FuzzContainer& fuzzer, std::shared_ptr fdp = std::make_shared(data, size); + std::shared_ptr fdp = + std::make_shared(data, size); FuzzContainer fuzzer(fdp); CursorInputMapper& mapper = fuzzer.getMapper(); diff --git a/services/inputflinger/tests/fuzzers/FuzzContainer.h b/services/inputflinger/tests/fuzzers/FuzzContainer.h index 1e0764f10f..76d2bcd03d 100644 --- a/services/inputflinger/tests/fuzzers/FuzzContainer.h +++ b/services/inputflinger/tests/fuzzers/FuzzContainer.h @@ -20,7 +20,6 @@ #include #include #include -#include namespace android { @@ -31,10 +30,10 @@ class FuzzContainer { std::unique_ptr mFuzzContext; std::unique_ptr mFuzzDevice; InputReaderConfiguration mPolicyConfig; - std::shared_ptr mFdp; + std::shared_ptr mFdp; public: - FuzzContainer(std::shared_ptr fdp) : mFdp(fdp) { + FuzzContainer(std::shared_ptr fdp) : mFdp(fdp) { // Setup parameters. std::string deviceName = mFdp->ConsumeRandomLengthString(16); std::string deviceLocation = mFdp->ConsumeRandomLengthString(12); diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp index 2eed9977be..d100a57614 100644 --- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp @@ -166,7 +166,8 @@ private: }; extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { - std::shared_ptr fdp = std::make_shared(data, size); + std::shared_ptr fdp = + std::make_shared(data, size); FuzzInputListener fuzzListener; sp fuzzPolicy = sp::make(fdp); diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp index e880f55891..8e2d677866 100644 --- a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp @@ -16,13 +16,12 @@ #include #include -#include namespace android { const int32_t kMaxKeycodes = 100; -static void addProperty(FuzzContainer& fuzzer, std::shared_ptr fdp) { +static void addProperty(FuzzContainer& fuzzer, std::shared_ptr fdp) { // Pick a random property to set for the mapper to have set. fdp->PickValueInArray>( {[&]() -> void { fuzzer.addProperty("keyboard.orientationAware", "1"); }, @@ -41,7 +40,8 @@ static void addProperty(FuzzContainer& fuzzer, std::shared_ptr fdp = std::make_shared(data, size); + std::shared_ptr fdp = + std::make_shared(data, size); FuzzContainer fuzzer(fdp); KeyboardInputMapper& mapper = diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index 445ed182fe..81e4816723 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - #pragma once #include #include #include -#include +#include #include "android/hardware/input/InputDeviceCountryCode.h" using android::hardware::input::InputDeviceCountryCode; @@ -114,10 +113,10 @@ class FuzzEventHub : public EventHubInterface { InputDeviceIdentifier mIdentifier; std::vector mVideoFrames; PropertyMap mFuzzConfig; - std::shared_ptr mFdp; + std::shared_ptr mFdp; public: - FuzzEventHub(std::shared_ptr fdp) : mFdp(std::move(fdp)) {} + FuzzEventHub(std::shared_ptr fdp) : mFdp(std::move(fdp)) {} ~FuzzEventHub() {} void addProperty(std::string key, std::string value) { mFuzzConfig.addProperty(key, value); } @@ -263,10 +262,10 @@ public: }; class FuzzPointerController : public PointerControllerInterface { - std::shared_ptr mFdp; + std::shared_ptr mFdp; public: - FuzzPointerController(std::shared_ptr mFdp) : mFdp(mFdp) {} + FuzzPointerController(std::shared_ptr mFdp) : mFdp(mFdp) {} ~FuzzPointerController() {} bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override { return mFdp->ConsumeBool(); @@ -289,13 +288,13 @@ public: class FuzzInputReaderPolicy : public InputReaderPolicyInterface { TouchAffineTransformation mTransform; std::shared_ptr mPointerController; - std::shared_ptr mFdp; + std::shared_ptr mFdp; protected: ~FuzzInputReaderPolicy() {} public: - FuzzInputReaderPolicy(std::shared_ptr mFdp) : mFdp(mFdp) { + FuzzInputReaderPolicy(std::shared_ptr mFdp) : mFdp(mFdp) { mPointerController = std::make_shared(mFdp); } void getReaderConfiguration(InputReaderConfiguration* outConfig) override {} @@ -333,13 +332,13 @@ public: class FuzzInputReaderContext : public InputReaderContext { std::shared_ptr mEventHub; sp mPolicy; - std::shared_ptr mFdp; + std::shared_ptr mFdp; public: FuzzInputReaderContext(std::shared_ptr eventHub, const sp& policy, InputListenerInterface& listener, - std::shared_ptr mFdp) + std::shared_ptr mFdp) : mEventHub(eventHub), mPolicy(policy), mFdp(mFdp) {} ~FuzzInputReaderContext() {} void updateGlobalMetaState() override {} diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp index 99fd0831a9..011455b8a1 100644 --- a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp @@ -16,13 +16,12 @@ #include #include -#include namespace android { const int32_t kMaxKeycodes = 100; -static void addProperty(FuzzContainer& fuzzer, std::shared_ptr fdp) { +static void addProperty(FuzzContainer& fuzzer, std::shared_ptr fdp) { // Pick a random property to set for the mapper to have set. fdp->PickValueInArray>( {[&]() -> void { fuzzer.addProperty("touch.deviceType", "touchScreen"); }, @@ -58,7 +57,8 @@ static void addProperty(FuzzContainer& fuzzer, std::shared_ptr fdp = std::make_shared(data, size); + std::shared_ptr fdp = + std::make_shared(data, size); FuzzContainer fuzzer(fdp); MultiTouchInputMapper& mapper = fuzzer.getMapper(); diff --git a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp index 7416ce9d47..c4938f2aec 100644 --- a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp @@ -16,12 +16,12 @@ #include #include -#include namespace android { extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { - std::shared_ptr fdp = std::make_shared(data, size); + std::shared_ptr fdp = + std::make_shared(data, size); FuzzContainer fuzzer(fdp); SwitchInputMapper& mapper = fuzzer.getMapper(); diff --git a/services/inputflinger/tests/fuzzers/ThreadSafeFuzzedDataProvider.h b/services/inputflinger/tests/fuzzers/ThreadSafeFuzzedDataProvider.h new file mode 100644 index 0000000000..2f76f181cb --- /dev/null +++ b/services/inputflinger/tests/fuzzers/ThreadSafeFuzzedDataProvider.h @@ -0,0 +1,136 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +/** + * A thread-safe interface to the FuzzedDataProvider + */ +class ThreadSafeFuzzedDataProvider : FuzzedDataProvider { +private: + std::mutex mLock; + +public: + ThreadSafeFuzzedDataProvider(const uint8_t* data, size_t size) + : FuzzedDataProvider(data, size) {} + + template + std::vector ConsumeBytes(size_t num_bytes) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeBytes(num_bytes); + } + + template + std::vector ConsumeBytesWithTerminator(size_t num_bytes, T terminator) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeBytesWithTerminator(num_bytes, terminator); + } + + template + std::vector ConsumeRemainingBytes() { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeRemainingBytes(); + } + + std::string ConsumeBytesAsString(size_t num_bytes) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeBytesAsString(num_bytes); + } + + std::string ConsumeRandomLengthString(size_t max_length) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeRandomLengthString(max_length); + } + + std::string ConsumeRandomLengthString() { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeRandomLengthString(); + } + + std::string ConsumeRemainingBytesAsString() { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeRemainingBytesAsString(); + } + + template + T ConsumeIntegral() { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeIntegral(); + } + + template + T ConsumeIntegralInRange(T min, T max) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeIntegralInRange(min, max); + } + + template + T ConsumeFloatingPoint() { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeFloatingPoint(); + } + + template + T ConsumeFloatingPointInRange(T min, T max) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeFloatingPointInRange(min, max); + } + + template + T ConsumeProbability() { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeProbability(); + } + + bool ConsumeBool() { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeBool(); + } + + template + T ConsumeEnum() { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeEnum(); + } + + template + T PickValueInArray(const T (&array)[size]) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::PickValueInArray(array); + } + + template + T PickValueInArray(const std::array& array) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::PickValueInArray(array); + } + + template + T PickValueInArray(std::initializer_list list) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::PickValueInArray(list); + } + + size_t ConsumeData(void* destination, size_t num_bytes) { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::ConsumeData(destination, num_bytes); + } + + size_t remaining_bytes() { + std::scoped_lock _l(mLock); + return FuzzedDataProvider::remaining_bytes(); + } +}; -- GitLab From 0de2f79f50ad4f63dd234021e5e16e7a05cde7b2 Mon Sep 17 00:00:00 2001 From: Justin Chung Date: Mon, 19 Dec 2022 08:36:38 +0000 Subject: [PATCH 0636/1310] Delete invalid placeholder for LLOB We already have LLOB, so no need placeholder anymore Test: No test needed Change-Id: I8c3cb0c93fbe4c70b301fd9781571d7a1d5f970b --- libs/sensor/Sensor.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libs/sensor/Sensor.cpp b/libs/sensor/Sensor.cpp index ec0ced8663..fb895f59b9 100644 --- a/libs/sensor/Sensor.cpp +++ b/libs/sensor/Sensor.cpp @@ -264,10 +264,6 @@ Sensor::Sensor(struct sensor_t const& hwSensor, const uuid_t& uuid, int halVersi mStringType = SENSOR_STRING_TYPE_HEART_BEAT; mFlags |= SENSOR_FLAG_SPECIAL_REPORTING_MODE; break; - - // TODO: Placeholder for LLOB sensor type - - case SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED: mStringType = SENSOR_STRING_TYPE_ACCELEROMETER_UNCALIBRATED; mFlags |= SENSOR_FLAG_CONTINUOUS_MODE; -- GitLab From d18bc30fc49c0344fe119dc406a4ee80f830d20d Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Fri, 16 Dec 2022 20:55:24 +0000 Subject: [PATCH 0637/1310] Update EXIF Modify the EXIF package if it exists, or create a single-entry EXIF package (API-1). Extract / modify the first EXIF package (if it exists) or create a single-entry EXIF package from the primary image (API-2, API-3). Change the order of the EXIF and XMP package, new order: SOI EXIF XMP [other packages if they appears in the primary image] primary image without EXIF secondary image Test: recoverymap_test and check the encoded file with a hex editor Bug: b/252835416 Change-Id: I3d8c4a3fe3cff63f8b57277511aa801b9dd1b75d --- .../include/jpegrecoverymap/jpegdecoder.h | 37 ++- .../include/jpegrecoverymap/recoverymap.h | 2 + libs/jpegrecoverymap/jpegdecoder.cpp | 134 ++++++-- libs/jpegrecoverymap/recoverymap.cpp | 290 ++++++++++++++++-- 4 files changed, 407 insertions(+), 56 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h index 5c9c8b6ec6..39c79c9695 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h @@ -47,7 +47,7 @@ public: */ void* getDecompressedImagePtr(); /* - * Returns the decompressed raw image buffer size. This method must be called only after + * Returns the decompressed raw image buffer size. This mgit ethod must be called only after * calling decompressImage(). */ size_t getDecompressedImageSize(); @@ -67,14 +67,35 @@ public: void* getXMPPtr(); /* * Returns the decompressed XMP buffer size. This method must be called only after - * calling decompressImage(). + * calling decompressImage() or getCompressedImageParameters(). */ size_t getXMPSize(); - + /* + * Returns the EXIF data from the image. + */ + void* getEXIFPtr(); + /* + * Returns the decompressed EXIF buffer size. This method must be called only after + * calling decompressImage() or getCompressedImageParameters(). + */ + size_t getEXIFSize(); + /* + * Returns the position offset of EXIF package + * (4 bypes offset to FF sign, the byte after FF E1 XX XX ), + * or -1 if no EXIF exists. + */ + int getEXIFPos() { return mExifPos; } + /* + * Decompresses metadata of the image. + */ bool getCompressedImageParameters(const void* image, int length, - size_t* pWidth, size_t* pHeight, - std::vector* &iccData, - std::vector* &exifData); + size_t* pWidth, size_t* pHeight, + std::vector* &iccData, + std::vector* &exifData); + /* + * Extracts EXIF package and updates the EXIF position / length without decoding the image. + */ + bool extractEXIF(const void* image, int length); private: bool decode(const void* image, int length); @@ -89,10 +110,14 @@ private: std::vector mResultBuffer; // The buffer that holds XMP Data. std::vector mXMPBuffer; + // The buffer that holds EXIF Data. + std::vector mEXIFBuffer; // Resolution of the decompressed image. size_t mWidth; size_t mHeight; + // Position of EXIF package, default value is -1 which means no EXIF package appears. + size_t mExifPos; }; } /* namespace android */ diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h index 50ccdff6f7..905bf16c1a 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -325,12 +325,14 @@ private: * * @param compressed_jpeg_image compressed 8-bit JPEG image * @param compress_recovery_map compressed recover map + * @param exif EXIF package * @param metadata JPEG/R metadata to encode in XMP of the jpeg * @param dest compressed JPEGR image * @return NO_ERROR if calculation succeeds, error code if error occurs. */ status_t appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, jr_compressed_ptr compressed_recovery_map, + jr_exif_ptr exif, jr_metadata_ptr metadata, jr_compressed_ptr dest); diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoder.cpp index 0185e55e9e..c2a8f45dbd 100644 --- a/libs/jpegrecoverymap/jpegdecoder.cpp +++ b/libs/jpegrecoverymap/jpegdecoder.cpp @@ -26,8 +26,12 @@ using namespace std; namespace android::recoverymap { -const uint32_t kExifMarker = JPEG_APP0 + 1; -const uint32_t kICCMarker = JPEG_APP0 + 2; +const uint32_t kAPP0Marker = JPEG_APP0; // JFIF +const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP +const uint32_t kAPP2Marker = JPEG_APP0 + 2; // ICC + +const std::string kXmpNameSpace = "http://ns.adobe.com/xap/1.0/"; +const std::string kExifIdCode = "Exif"; struct jpegr_source_mgr : jpeg_source_mgr { jpegr_source_mgr(const uint8_t* ptr, int len); @@ -83,6 +87,7 @@ static void jpegrerror_exit(j_common_ptr cinfo) { } JpegDecoder::JpegDecoder() { + mExifPos = 0; } JpegDecoder::~JpegDecoder() { @@ -119,6 +124,13 @@ size_t JpegDecoder::getXMPSize() { return mXMPBuffer.size(); } +void* JpegDecoder::getEXIFPtr() { + return mEXIFBuffer.data(); +} + +size_t JpegDecoder::getEXIFSize() { + return mEXIFBuffer.size(); +} size_t JpegDecoder::getDecompressedImageWidth() { return mWidth; @@ -132,7 +144,6 @@ bool JpegDecoder::decode(const void* image, int length) { jpeg_decompress_struct cinfo; jpegr_source_mgr mgr(static_cast(image), length); jpegrerror_mgr myerr; - string nameSpace = "http://ns.adobe.com/xap/1.0/"; cinfo.err = jpeg_std_error(&myerr.pub); myerr.pub.error_exit = jpegrerror_exit; @@ -143,25 +154,58 @@ bool JpegDecoder::decode(const void* image, int length) { } jpeg_create_decompress(&cinfo); - jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF); + jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF); + jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); + jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); cinfo.src = &mgr; jpeg_read_header(&cinfo, TRUE); - // Save XMP Data - for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) { - if (marker->marker == kExifMarker) { - const unsigned int len = marker->data_length; - if (len > nameSpace.size() && - !strncmp(reinterpret_cast(marker->data), - nameSpace.c_str(), nameSpace.size())) { - mXMPBuffer.resize(len+1, 0); - memcpy(static_cast(mXMPBuffer.data()), marker->data, len); - break; - } + // Save XMP data and EXIF data. + // Here we only handle the first XMP / EXIF package. + // The parameter pos is used for capturing start offset of EXIF, which is hacky, but working... + // We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package), + // two bytes of package length which is stored in marker->original_length, and the real data + // which is stored in marker->data. The pos is adding up all previous package lengths ( + // 4 bytes marker and length, marker->original_length) before EXIF appears. Note that here we + // we are using marker->original_length instead of marker->data_length because in case the real + // package length is larger than the limitation, jpeg-turbo will only copy the data within the + // limitation (represented by data_length) and this may vary from original_length / real offset. + // A better solution is making jpeg_marker_struct holding the offset, but currently it doesn't. + bool exifAppears = false; + bool xmpAppears = false; + size_t pos = 2; // position after SOI + for (jpeg_marker_struct* marker = cinfo.marker_list; + marker && !(exifAppears && xmpAppears); + marker = marker->next) { + + pos += 4; + pos += marker->original_length; + + if (marker->marker != kAPP1Marker) { + continue; } - } + const unsigned int len = marker->data_length; + if (!xmpAppears && + len > kXmpNameSpace.size() && + !strncmp(reinterpret_cast(marker->data), + kXmpNameSpace.c_str(), + kXmpNameSpace.size())) { + mXMPBuffer.resize(len+1, 0); + memcpy(static_cast(mXMPBuffer.data()), marker->data, len); + xmpAppears = true; + } else if (!exifAppears && + len > kExifIdCode.size() && + !strncmp(reinterpret_cast(marker->data), + kExifIdCode.c_str(), + kExifIdCode.size())) { + mEXIFBuffer.resize(len, 0); + memcpy(static_cast(mEXIFBuffer.data()), marker->data, len); + exifAppears = true; + mExifPos = pos - marker->original_length; + } + } mWidth = cinfo.image_width; mHeight = cinfo.image_height; @@ -189,6 +233,60 @@ bool JpegDecoder::decode(const void* image, int length) { return true; } +// TODO (Fyodor/Dichen): merge this method with getCompressedImageParameters() since they have +// similar functionality. Yet Dichen is not familiar with who's calling +// getCompressedImageParameters(), looks like it's used by some pending CLs. +bool JpegDecoder::extractEXIF(const void* image, int length) { + jpeg_decompress_struct cinfo; + jpegr_source_mgr mgr(static_cast(image), length); + jpegrerror_mgr myerr; + + cinfo.err = jpeg_std_error(&myerr.pub); + myerr.pub.error_exit = jpegrerror_exit; + + if (setjmp(myerr.setjmp_buffer)) { + jpeg_destroy_decompress(&cinfo); + return false; + } + jpeg_create_decompress(&cinfo); + + jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF); + jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); + jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); + + cinfo.src = &mgr; + jpeg_read_header(&cinfo, TRUE); + + bool exifAppears = false; + size_t pos = 2; // position after SOI + for (jpeg_marker_struct* marker = cinfo.marker_list; + marker && !exifAppears; + marker = marker->next) { + + pos += 4; + pos += marker->original_length; + + if (marker->marker != kAPP1Marker) { + continue; + } + + const unsigned int len = marker->data_length; + if (!exifAppears && + len > kExifIdCode.size() && + !strncmp(reinterpret_cast(marker->data), + kExifIdCode.c_str(), + kExifIdCode.size())) { + mEXIFBuffer.resize(len, 0); + memcpy(static_cast(mEXIFBuffer.data()), marker->data, len); + exifAppears = true; + mExifPos = pos - marker->original_length; + } + } + + jpeg_destroy_decompress(&cinfo); + return true; +} + bool JpegDecoder::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, bool isSingleChannel) { if (isSingleChannel) { @@ -212,8 +310,8 @@ bool JpegDecoder::getCompressedImageParameters(const void* image, int length, } jpeg_create_decompress(&cinfo); - jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF); - jpeg_save_markers(&cinfo, kICCMarker, 0xFFFF); + jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); + jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); cinfo.src = &mgr; if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) { diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index fafc319271..53fa8ce9e3 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -82,12 +82,122 @@ status_t Write(jr_compressed_ptr destination, const void* source, size_t length, return NO_ERROR; } +status_t Write(jr_exif_ptr destination, const void* source, size_t length, int &position) { + memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length); + position += length; + return NO_ERROR; +} + +// If the EXIF package doesn't exist in the input JPEG, we'll create one with one entry +// where the length is represented by this value. +const size_t PSEUDO_EXIF_PACKAGE_LENGTH = 28; +// If the EXIF package exists in the input JPEG, we'll add an "JR" entry where the length is +// represented by this value. +const size_t EXIF_J_R_ENTRY_LENGTH = 12; + +/* + * Helper function + * Add J R entry to existing exif, or create a new one with J R entry if it's null. + * EXIF syntax / change: + * ori: + * FF E1 - APP1 + * 01 FC - size of APP1 (to be calculated) + * ----------------------------------------------------- + * 45 78 69 66 00 00 - Exif\0\0 "Exif header" + * 49 49 2A 00 - TIFF Header + * 08 00 00 00 - offset to the IFD (image file directory) + * 06 00 - 6 entries + * 00 01 - Width Tag + * 03 00 - 'Short' type + * 01 00 00 00 - one entry + * 00 05 00 00 - image with 0x500 + *-------------------------------------------------------------------------- + * new: + * FF E1 - APP1 + * 02 08 - new size, equals to old size + EXIF_J_R_ENTRY_LENGTH (12) + *----------------------------------------------------- + * 45 78 69 66 00 00 - Exif\0\0 "Exif header" + * 49 49 2A 00 - TIFF Header + * 08 00 00 00 - offset to the IFD (image file directory) + * 07 00 - +1 entry + * 4A 52 Custom ('J''R') Tag + * 07 00 - Unknown type + * 01 00 00 00 - one element + * 00 00 00 00 - empty data + * 00 01 - Width Tag + * 03 00 - 'Short' type + * 01 00 00 00 - one entry + * 00 05 00 00 - image with 0x500 + */ +status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest) { + if (exif == nullptr || exif->data == nullptr) { + uint8_t data[PSEUDO_EXIF_PACKAGE_LENGTH] = { + 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, + 0x49, 0x49, 0x2A, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, + 0x4A, 0x52, + 0x07, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; + int pos = 0; + Write(dest, data, PSEUDO_EXIF_PACKAGE_LENGTH, pos); + return NO_ERROR; + } + + int num_entry = 0; + uint8_t num_entry_low = 0; + uint8_t num_entry_high = 0; + bool use_big_endian = false; + if (reinterpret_cast(exif->data)[3] == 0x4949) { + num_entry_low = reinterpret_cast(exif->data)[14]; + num_entry_high = reinterpret_cast(exif->data)[15]; + } else if (reinterpret_cast(exif->data)[3] == 0x4d4d) { + use_big_endian = true; + num_entry_high = reinterpret_cast(exif->data)[14]; + num_entry_low = reinterpret_cast(exif->data)[15]; + } else { + return ERROR_JPEGR_METADATA_ERROR; + } + num_entry = (num_entry_high << 8) | num_entry_low; + num_entry += 1; + num_entry_low = num_entry & 0xff; + num_entry_high = (num_entry << 8) & 0xff; + + int pos = 0; + Write(dest, (uint8_t*)exif->data, 14, pos); + + if (use_big_endian) { + Write(dest, &num_entry_high, 1, pos); + Write(dest, &num_entry_low, 1, pos); + uint8_t data[EXIF_J_R_ENTRY_LENGTH] = { + 0x4A, 0x52, + 0x07, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; + Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos); + } else { + Write(dest, &num_entry_low, 1, pos); + Write(dest, &num_entry_high, 1, pos); + uint8_t data[EXIF_J_R_ENTRY_LENGTH] = { + 0x4A, 0x52, + 0x00, 0x07, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00}; + Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos); + } + + Write(dest, (uint8_t*)exif->data + 16, exif->length - 16, pos); + + return NO_ERROR; +} + /* Encode API-0 */ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpegr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality, - jr_exif_ptr /* exif */) { + jr_exif_ptr exif) { if (uncompressed_p010_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } @@ -129,7 +239,18 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpeg.data = jpeg_encoder.getCompressedImagePtr(); jpeg.length = jpeg_encoder.getCompressedImageSize(); - JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest)); + jpegr_exif_struct new_exif; + if (exif->data == nullptr) { + new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH; + } else { + new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH; + } + new_exif.data = new uint8_t[new_exif.length]; + std::unique_ptr new_exif_data; + new_exif_data.reset(reinterpret_cast(new_exif.data)); + JPEGR_CHECK(updateExif(exif, &new_exif)); + + JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest)); return NO_ERROR; } @@ -140,7 +261,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpegr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality, - jr_exif_ptr /* exif */) { + jr_exif_ptr exif) { if (uncompressed_p010_image == nullptr || uncompressed_yuv_420_image == nullptr || dest == nullptr) { @@ -186,7 +307,19 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpeg.data = jpeg_encoder.getCompressedImagePtr(); jpeg.length = jpeg_encoder.getCompressedImageSize(); - JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest)); + jpegr_exif_struct new_exif; + if (exif == nullptr || exif->data == nullptr) { + new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH; + } else { + new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH; + } + + new_exif.data = new uint8_t[new_exif.length]; + std::unique_ptr new_exif_data; + new_exif_data.reset(reinterpret_cast(new_exif.data)); + JPEGR_CHECK(updateExif(exif, &new_exif)); + + JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest)); return NO_ERROR; } @@ -228,7 +361,41 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, compressed_map.data = compressed_map_data.get(); JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); - JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest)); + // Extract EXIF from JPEG without decoding. + JpegDecoder jpeg_decoder; + if (!jpeg_decoder.extractEXIF(compressed_jpeg_image->data, compressed_jpeg_image->length)) { + return ERROR_JPEGR_DECODE_ERROR; + } + + jpegr_exif_struct exif; + exif.data = nullptr; + exif.length = 0; + // Delete EXIF package if it appears, and update exif. + if (jpeg_decoder.getEXIFPos() != 0) { + int new_length = compressed_jpeg_image->length - jpeg_decoder.getEXIFSize() - 4; + memcpy((uint8_t*)compressed_jpeg_image->data + jpeg_decoder.getEXIFPos() - 4, + (uint8_t*)compressed_jpeg_image->data + jpeg_decoder.getEXIFPos() + + jpeg_decoder.getEXIFSize(), + compressed_jpeg_image->length - jpeg_decoder.getEXIFPos() - jpeg_decoder.getEXIFSize()); + compressed_jpeg_image->length = new_length; + exif.data = jpeg_decoder.getEXIFPtr(); + exif.length = jpeg_decoder.getEXIFSize(); + } + + jpegr_exif_struct new_exif; + if (exif.data == nullptr) { + new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH; + } else { + new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH; + } + + new_exif.data = new uint8_t[new_exif.length]; + std::unique_ptr new_exif_data; + new_exif_data.reset(reinterpret_cast(new_exif.data)); + JPEGR_CHECK(updateExif(&exif, &new_exif)); + + JPEGR_CHECK(appendRecoveryMap( + compressed_jpeg_image, &compressed_map, &new_exif, &metadata, dest)); return NO_ERROR; } @@ -254,6 +421,32 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight(); uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut; + jpegr_exif_struct exif; + exif.data = nullptr; + exif.length = 0; + // Delete EXIF package if it appears, and update exif. + if (jpeg_decoder.getEXIFPos() != 0) { + int new_length = compressed_jpeg_image->length - jpeg_decoder.getEXIFSize() - 4; + memcpy((uint8_t*)compressed_jpeg_image->data + jpeg_decoder.getEXIFPos() - 4, + (uint8_t*)compressed_jpeg_image->data + jpeg_decoder.getEXIFPos() + + jpeg_decoder.getEXIFSize(), + compressed_jpeg_image->length - jpeg_decoder.getEXIFPos() - jpeg_decoder.getEXIFSize()); + compressed_jpeg_image->length = new_length; + exif.data = jpeg_decoder.getEXIFPtr(); + exif.length = jpeg_decoder.getEXIFSize(); + } + + jpegr_exif_struct new_exif; + if (exif.data == nullptr) { + new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH; + } else { + new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH; + } + new_exif.data = new uint8_t[new_exif.length]; + std::unique_ptr new_exif_data; + new_exif_data.reset(reinterpret_cast(new_exif.data)); + JPEGR_CHECK(updateExif(&exif, &new_exif)); + if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) { return ERROR_JPEGR_RESOLUTION_MISMATCH; @@ -278,7 +471,8 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, compressed_map.data = compressed_map_data.get(); JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); - JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest)); + JPEGR_CHECK(appendRecoveryMap( + compressed_jpeg_image, &compressed_map, &new_exif, &metadata, dest)); return NO_ERROR; } @@ -614,52 +808,84 @@ status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_imag return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest); } +// JPEG/R structure: +// SOI (ff d8) +// APP1 (ff e1) +// 2 bytes of length (2 + length of exif package) +// EXIF package (this includes the first two bytes representing the package length) +// APP1 (ff e1) +// 2 bytes of length (2 + 29 + length of xmp package) +// name space ("http://ns.adobe.com/xap/1.0/\0") +// xmp +// primary image (without the first two bytes (SOI) and without EXIF, may have other packages) +// secondary image (the recovery map) +// +// Metadata versions we are using: +// ECMA TR-98 for JFIF marker +// Exif 2.2 spec for EXIF marker +// Adobe XMP spec part 3 for XMP marker +// ICC v4.3 spec for ICC status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, jr_compressed_ptr compressed_recovery_map, + jr_exif_ptr exif, jr_metadata_ptr metadata, jr_compressed_ptr dest) { if (compressed_jpeg_image == nullptr || compressed_recovery_map == nullptr + || exif == nullptr || metadata == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } - const string xmp = generateXmp(compressed_recovery_map->length, *metadata); - const string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; - const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator - - // 2 bytes: APP1 sign (ff e1) - // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0", - // x bytes: length of xmp packet - - const int length = 3 + nameSpaceLength + xmp.size(); - const uint8_t lengthH = ((length >> 8) & 0xff); - const uint8_t lengthL = (length & 0xff); - int pos = 0; - // JPEG/R structure: - // SOI (ff d8) - // APP1 (ff e1) - // 2 bytes of length (2 + 29 + length of xmp packet) - // name space ("http://ns.adobe.com/xap/1.0/\0") - // xmp - // primary image (without the first two bytes, the SOI sign) - // secondary image (the recovery map) + // Write SOI JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos)); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); - JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos)); - JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos)); + + // Write EXIF + { + const int length = 2 + exif->length; + const uint8_t lengthH = ((length >> 8) & 0xff); + const uint8_t lengthL = (length & 0xff); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos)); + JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); + JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); + JPEGR_CHECK(Write(dest, exif->data, exif->length, pos)); + } + + // Prepare and write XMP + { + const string xmp = generateXmp(compressed_recovery_map->length, *metadata); + const string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; + const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator + // 2 bytes: representing the length of the package + // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0", + // x bytes: length of xmp packet + const int length = 3 + nameSpaceLength + xmp.size(); + const uint8_t lengthH = ((length >> 8) & 0xff); + const uint8_t lengthL = (length & 0xff); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos)); + JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); + JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); + JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos)); + JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos)); + } + + // Write primary image JPEGR_CHECK(Write(dest, (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos)); + + // Write secondary image JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos)); + + // Set back length dest->length = pos; + // Done! return NO_ERROR; } -- GitLab From dbf7e3aef69af260ce69e8d68a8b2569bae54a51 Mon Sep 17 00:00:00 2001 From: Brian Lindahl Date: Fri, 16 Dec 2022 11:43:39 -0700 Subject: [PATCH 0638/1310] Use new HAL API to clear buffer slots Bug: 262041682 Test: VtsHalGraphicsComposer3_TargetTest Change-Id: I08aa7e0f3831c37c600d9aa9dffb33acb8661347 --- .../DisplayHardware/AidlComposerHal.cpp | 67 ++++++++++++------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp index d061a99f7d..e372b72b33 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp @@ -233,15 +233,23 @@ AidlComposer::AidlComposer(const std::string& serviceName) { addReader(translate(kSingleReaderKey)); - // TODO(b/262041682): When using new API to clear buffer slots, don't allocate this buffer. - mClearSlotBuffer = sp::make(1, 1, PIXEL_FORMAT_RGBX_8888, - GraphicBuffer::USAGE_HW_COMPOSER | - GraphicBuffer::USAGE_SW_READ_OFTEN | - GraphicBuffer::USAGE_SW_WRITE_OFTEN, - "AidlComposer"); - if (!mClearSlotBuffer || mClearSlotBuffer->initCheck() != ::android::OK) { - LOG_ALWAYS_FATAL("Failed to allocate a buffer for clearing layer buffer slots"); - return; + // If unable to read interface version, then become backwards compatible. + int32_t version = 1; + const auto status = mAidlComposerClient->getInterfaceVersion(&version); + if (!status.isOk()) { + ALOGE("getInterfaceVersion for AidlComposer constructor failed %s", + status.getDescription().c_str()); + } + if (version == 1) { + mClearSlotBuffer = sp::make(1, 1, PIXEL_FORMAT_RGBX_8888, + GraphicBuffer::USAGE_HW_COMPOSER | + GraphicBuffer::USAGE_SW_READ_OFTEN | + GraphicBuffer::USAGE_SW_WRITE_OFTEN, + "AidlComposer"); + if (!mClearSlotBuffer || mClearSlotBuffer->initCheck() != ::android::OK) { + LOG_ALWAYS_FATAL("Failed to allocate a buffer for clearing layer buffer slots"); + return; + } } ALOGI("Loaded AIDL composer3 HAL service"); @@ -835,25 +843,32 @@ Error AidlComposer::setLayerBufferSlotsToClear(Display display, Layer layer, Error error = Error::NONE; mMutex.lock_shared(); if (auto writer = getWriter(display)) { - // Backwards compatible way of clearing buffer slots is tricky... - for (uint32_t slot : slotsToClear) { - // Don't clear the active buffer slot because we need to restore the active buffer - // after clearing the requested buffer slots with a placeholder buffer. - if (slot != activeBufferSlot) { - writer->get().setLayerBufferWithNewCommand(translate(display), - translate(layer), slot, - mClearSlotBuffer->handle, /*fence*/ -1); + // Backwards compatible way of clearing buffer is to set the layer buffer with a placeholder + // buffer, using the slot that needs to cleared... tricky. + if (mClearSlotBuffer == nullptr) { + writer->get().setLayerBufferSlotsToClear(translate(display), + translate(layer), slotsToClear); + } else { + for (uint32_t slot : slotsToClear) { + // Don't clear the active buffer slot because we need to restore the active buffer + // after clearing the requested buffer slots with a placeholder buffer. + if (slot != activeBufferSlot) { + writer->get().setLayerBufferWithNewCommand(translate(display), + translate(layer), slot, + mClearSlotBuffer->handle, + /*fence*/ -1); + } } + // Since we clear buffers by setting them to a placeholder buffer, we want to make + // sure that the last setLayerBuffer command is sent with the currently active + // buffer, not the placeholder buffer, so that there is no perceptual change when + // buffers are discarded. + writer->get().setLayerBufferWithNewCommand(translate(display), + translate(layer), activeBufferSlot, + // The active buffer is still cached in + // its slot and doesn't need a fence. + /*buffer*/ nullptr, /*fence*/ -1); } - // Since we clear buffers by setting them to a placeholder buffer, we want to make - // sure that the last setLayerBuffer command is sent with the currently active - // buffer, not the placeholder buffer, so that there is no perceptual change when - // buffers are discarded. - writer->get().setLayerBufferWithNewCommand(translate(display), - translate(layer), activeBufferSlot, - // The active buffer is still cached in - // its slot and doesn't need a fence. - /*buffer*/ nullptr, /*fence*/ -1); } else { error = Error::BAD_DISPLAY; } -- GitLab From b06a84aa9917bdfd48ffc71219e99a90b222bf29 Mon Sep 17 00:00:00 2001 From: Nolan Scobie Date: Mon, 19 Dec 2022 19:57:10 -0500 Subject: [PATCH 0639/1310] Add libgui_test to libs/gui/TEST_MAPPING postsubmit This should ideally be in presubmit, but from initial investigation it seems different subsets of this test suite are consistently failing across different devices. Putting this in (non-blocking) postsubmit should give us a better view of what's failing where. We can then fix/ignore tests as needed before moving this to presubmit. Docs for non-blocking vs. "blocking" (investigated) postsubmit: go/test-mapping#attributes Test: will evaluate non-blocking postsubmit failures after this lands Bug: 257123981 Change-Id: I95a7906eed0b901eb46ab0af6cca47ff5c0d7c45 --- libs/gui/TEST_MAPPING | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libs/gui/TEST_MAPPING b/libs/gui/TEST_MAPPING index 1c435304a8..941503548d 100644 --- a/libs/gui/TEST_MAPPING +++ b/libs/gui/TEST_MAPPING @@ -3,5 +3,11 @@ { "path": "frameworks/native/libs/nativewindow" } + ], + "postsubmit": [ + { + // TODO(257123981): move this to presubmit after dealing with existing breakages. + "name": "libgui_test" + } ] } -- GitLab From 2a66e5457bb43316e78f511d9e89013f63b6f200 Mon Sep 17 00:00:00 2001 From: Cody Northrop Date: Mon, 19 Dec 2022 15:32:11 -0700 Subject: [PATCH 0640/1310] OpenGL: Update test mapping to include EGL_test We need to get EGL tests running continuously in order to make modifications to blobcache. Skipping one failing test, will address or remove after we get the test running. Also add hasWideColorDisplay checks to two tests that require Display P3 gamut. Test: adb shell /data/nativetest64/EGL_test/EGL_test Bug: b/120714942 Bug: b/246966894 Change-Id: I985f871900ab72553bcf0e73987b843800fcac89 --- opengl/TEST_MAPPING | 3 +++ opengl/tests/EGLTest/Android.bp | 2 +- opengl/tests/EGLTest/EGL_test.cpp | 17 +++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/opengl/TEST_MAPPING b/opengl/TEST_MAPPING index d391dce2de..7c50a945fa 100644 --- a/opengl/TEST_MAPPING +++ b/opengl/TEST_MAPPING @@ -2,6 +2,9 @@ "presubmit": [ { "name": "CtsGpuToolsHostTestCases" + }, + { + "name": "EGL_test" } ] } diff --git a/opengl/tests/EGLTest/Android.bp b/opengl/tests/EGLTest/Android.bp index 51c937614f..d96a89564d 100644 --- a/opengl/tests/EGLTest/Android.bp +++ b/opengl/tests/EGLTest/Android.bp @@ -1,4 +1,3 @@ - package { // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import @@ -11,6 +10,7 @@ package { cc_test { name: "EGL_test", + test_suites: ["general-tests"], srcs: [ "egl_cache_test.cpp", diff --git a/opengl/tests/EGLTest/EGL_test.cpp b/opengl/tests/EGLTest/EGL_test.cpp index bbd786d155..cbe4ef9c40 100644 --- a/opengl/tests/EGLTest/EGL_test.cpp +++ b/opengl/tests/EGLTest/EGL_test.cpp @@ -343,6 +343,11 @@ TEST_F(EGLTest, EGLDisplayP3Passthrough) { } TEST_F(EGLTest, EGLDisplayP31010102) { + // This test has been failing since: + // libEGL: When driver doesn't understand P3, map sRGB-encoded P3 to sRGB + // https://android-review.git.corp.google.com/c/platform/frameworks/native/+/793504 + GTEST_SKIP() << "Skipping broken test. See b/120714942 and b/117104367"; + EGLint numConfigs; EGLConfig config; EGLBoolean success; @@ -866,6 +871,12 @@ TEST_F(EGLTest, EGLUnsupportedColorspaceFormatCombo) { EGLConfig config; EGLBoolean success; + if (!hasWideColorDisplay) { + // skip this test if device does not have wide-color display + RecordProperty("hasWideColorDisplay", false); + return; + } + const EGLint attrs[] = { // clang-format off EGL_SURFACE_TYPE, EGL_WINDOW_BIT, @@ -951,6 +962,12 @@ TEST_F(EGLTest, EGLCreateWindowFailAndSucceed) { TEST_F(EGLTest, EGLCreateWindowTwoColorspaces) { EGLConfig config; + if (!hasWideColorDisplay) { + // skip this test if device does not have wide-color display + RecordProperty("hasWideColorDisplay", false); + return; + } + ASSERT_NO_FATAL_FAILURE(get8BitConfig(config)); struct MockConsumer : public BnConsumerListener { -- GitLab From 26e617d8f0f25e14b9260f18af3c0942e046c5bc Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Wed, 21 Dec 2022 03:42:34 +0000 Subject: [PATCH 0641/1310] AudioManager definitions for playback activity monitoring format Add event for playback activity event for format update. Add keys for each attribute of the format being communicated Bug: 263300713 Test: atest android.media.audio.cts.AudioPlaybackConfigurationTest Change-Id: Ib1dcc597e32ce4c451f4a42ae89587e3be416542 --- include/audiomanager/AudioManager.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/audiomanager/AudioManager.h b/include/audiomanager/AudioManager.h index 6794fbfc34..43048dbff0 100644 --- a/include/audiomanager/AudioManager.h +++ b/include/audiomanager/AudioManager.h @@ -40,8 +40,16 @@ typedef enum { PLAYER_UPDATE_DEVICE_ID = 5, PLAYER_UPDATE_PORT_ID = 6, PLAYER_UPDATE_MUTED = 7, + PLAYER_UPDATE_FORMAT = 8, } player_state_t; +static constexpr char + kExtraPlayerEventSpatializedKey[] = "android.media.extra.PLAYER_EVENT_SPATIALIZED"; +static constexpr char + kExtraPlayerEventSampleRateKey[] = "android.media.extra.PLAYER_EVENT_SAMPLE_RATE"; +static constexpr char + kExtraPlayerEventChannelMaskKey[] = "android.media.extra.PLAYER_EVENT_CHANNEL_MASK"; + static constexpr char kExtraPlayerEventMuteKey[] = "android.media.extra.PLAYER_EVENT_MUTE"; enum { -- GitLab From 08c7bb1fdb33d6ad34ba1b2de3ac0579750456fe Mon Sep 17 00:00:00 2001 From: Chris Forbes Date: Fri, 9 Dec 2022 08:10:18 +1300 Subject: [PATCH 0642/1310] libvulkan: Implement EXT_swapchain_maintenance1 The loader now implements EXT_swapchain_maintenance1 and EXT_surface_maintenance1, which fix assorted issues with the original swapchain extension. Our implementation of EXT_swapchain_maintenance1 is conditional on the underlying driver having support for importing sync fds as external fences (which requires Vulkan 1.1 + support for VK_KHR_external_fence_fd). Bug: b/255376900 Change-Id: I72ce770a7296e4e97cccf13bad420aa5a1001a6a --- vulkan/libvulkan/driver.cpp | 56 ++- vulkan/libvulkan/driver_gen.cpp | 20 + vulkan/libvulkan/driver_gen.h | 4 + vulkan/libvulkan/swapchain.cpp | 624 ++++++++++++++++++++--------- vulkan/libvulkan/swapchain.h | 1 + vulkan/scripts/driver_generator.py | 6 + 6 files changed, 528 insertions(+), 183 deletions(-) diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp index 9ed992b230..b7e19d4296 100644 --- a/vulkan/libvulkan/driver.cpp +++ b/vulkan/libvulkan/driver.cpp @@ -636,6 +636,7 @@ void CreateInfoWrapper::FilterExtension(const char* name) { case ProcHook::EXT_swapchain_colorspace: case ProcHook::KHR_get_surface_capabilities2: case ProcHook::GOOGLE_surfaceless_query: + case ProcHook::EXT_surface_maintenance1: hook_extensions_.set(ext_bit); // return now as these extensions do not require HAL support return; @@ -657,9 +658,11 @@ void CreateInfoWrapper::FilterExtension(const char* name) { case ProcHook::KHR_shared_presentable_image: case ProcHook::KHR_swapchain: case ProcHook::EXT_hdr_metadata: + case ProcHook::EXT_swapchain_maintenance1: case ProcHook::ANDROID_external_memory_android_hardware_buffer: case ProcHook::ANDROID_native_buffer: case ProcHook::GOOGLE_display_timing: + case ProcHook::KHR_external_fence_fd: case ProcHook::EXTENSION_CORE_1_0: case ProcHook::EXTENSION_CORE_1_1: case ProcHook::EXTENSION_CORE_1_2: @@ -690,16 +693,22 @@ void CreateInfoWrapper::FilterExtension(const char* name) { ext_bit = ProcHook::ANDROID_native_buffer; break; case ProcHook::KHR_incremental_present: - case ProcHook::GOOGLE_display_timing: case ProcHook::KHR_shared_presentable_image: + case ProcHook::GOOGLE_display_timing: hook_extensions_.set(ext_bit); // return now as these extensions do not require HAL support return; + case ProcHook::EXT_swapchain_maintenance1: + // map VK_KHR_swapchain_maintenance1 to KHR_external_fence_fd + name = VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME; + ext_bit = ProcHook::KHR_external_fence_fd; + break; case ProcHook::EXT_hdr_metadata: case ProcHook::KHR_bind_memory2: hook_extensions_.set(ext_bit); break; case ProcHook::ANDROID_external_memory_android_hardware_buffer: + case ProcHook::KHR_external_fence_fd: case ProcHook::EXTENSION_UNKNOWN: // Extensions we don't need to do anything about at this level break; @@ -715,6 +724,7 @@ void CreateInfoWrapper::FilterExtension(const char* name) { case ProcHook::KHR_surface_protected_capabilities: case ProcHook::EXT_debug_report: case ProcHook::EXT_swapchain_colorspace: + case ProcHook::EXT_surface_maintenance1: case ProcHook::GOOGLE_surfaceless_query: case ProcHook::ANDROID_native_buffer: case ProcHook::EXTENSION_CORE_1_0: @@ -751,6 +761,8 @@ void CreateInfoWrapper::FilterExtension(const char* name) { if (ext_bit != ProcHook::EXTENSION_UNKNOWN) { if (ext_bit == ProcHook::ANDROID_native_buffer) hook_extensions_.set(ProcHook::KHR_swapchain); + if (ext_bit == ProcHook::KHR_external_fence_fd) + hook_extensions_.set(ProcHook::EXT_swapchain_maintenance1); hal_extensions_.set(ext_bit); } @@ -940,6 +952,9 @@ VkResult EnumerateInstanceExtensionProperties( VK_KHR_GET_SURFACE_CAPABILITIES_2_SPEC_VERSION}); loader_extensions.push_back({VK_GOOGLE_SURFACELESS_QUERY_EXTENSION_NAME, VK_GOOGLE_SURFACELESS_QUERY_SPEC_VERSION}); + loader_extensions.push_back({ + VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME, + VK_EXT_SURFACE_MAINTENANCE_1_SPEC_VERSION}); static const VkExtensionProperties loader_debug_report_extension = { VK_EXT_DEBUG_REPORT_EXTENSION_NAME, VK_EXT_DEBUG_REPORT_SPEC_VERSION, @@ -1072,6 +1087,33 @@ VkResult GetAndroidNativeBufferSpecVersion9Support( return result; } +bool CanSupportSwapchainMaintenance1Extension(VkPhysicalDevice physicalDevice) { + const auto& driver = GetData(physicalDevice).driver; + if (!driver.GetPhysicalDeviceExternalFenceProperties) + return false; + + // Requires support for external fences imported from sync fds. + // This is _almost_ universal on Android, but may be missing on + // some extremely old drivers, or on strange implementations like + // cuttlefish. + VkPhysicalDeviceExternalFenceInfo fenceInfo = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO, + nullptr, + VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT + }; + VkExternalFenceProperties fenceProperties = { + VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES, + nullptr, + 0, 0, 0 + }; + + GetPhysicalDeviceExternalFenceProperties(physicalDevice, &fenceInfo, &fenceProperties); + if (fenceProperties.externalFenceFeatures & VK_EXTERNAL_FENCE_FEATURE_IMPORTABLE_BIT) + return true; + + return false; +} + VkResult EnumerateDeviceExtensionProperties( VkPhysicalDevice physicalDevice, const char* pLayerName, @@ -1149,6 +1191,12 @@ VkResult EnumerateDeviceExtensionProperties( VK_EXT_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_SPEC_VERSION}); } + if (CanSupportSwapchainMaintenance1Extension(physicalDevice)) { + loader_extensions.push_back({ + VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME, + VK_EXT_SWAPCHAIN_MAINTENANCE_1_SPEC_VERSION}); + } + // enumerate our extensions first if (!pLayerName && pProperties) { uint32_t count = std::min( @@ -1644,6 +1692,12 @@ void GetPhysicalDeviceFeatures2(VkPhysicalDevice physicalDevice, imageCompressionControlSwapchainInChain = true; } break; + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT: { + auto smf = reinterpret_cast( + pFeats); + smf->swapchainMaintenance1 = true; + } break; + default: break; } diff --git a/vulkan/libvulkan/driver_gen.cpp b/vulkan/libvulkan/driver_gen.cpp index de98aa7e61..798af5c6a6 100644 --- a/vulkan/libvulkan/driver_gen.cpp +++ b/vulkan/libvulkan/driver_gen.cpp @@ -162,6 +162,15 @@ VKAPI_ATTR void checkedGetDeviceQueue2(VkDevice device, const VkDeviceQueueInfo2 } } +VKAPI_ATTR VkResult checkedReleaseSwapchainImagesEXT(VkDevice device, const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo) { + if (GetData(device).hook_extensions[ProcHook::EXT_swapchain_maintenance1]) { + return ReleaseSwapchainImagesEXT(device, pReleaseInfo); + } else { + Logger(device).Err(device, "VK_EXT_swapchain_maintenance1 not enabled. vkReleaseSwapchainImagesEXT not executed."); + return VK_SUCCESS; + } +} + // clang-format on const ProcHook g_proc_hooks[] = { @@ -544,6 +553,13 @@ const ProcHook g_proc_hooks[] = { reinterpret_cast(QueueSubmit), nullptr, }, + { + "vkReleaseSwapchainImagesEXT", + ProcHook::DEVICE, + ProcHook::EXT_swapchain_maintenance1, + reinterpret_cast(ReleaseSwapchainImagesEXT), + reinterpret_cast(checkedReleaseSwapchainImagesEXT), + }, { "vkSetHdrMetadataEXT", ProcHook::DEVICE, @@ -580,6 +596,8 @@ ProcHook::Extension GetProcHookExtension(const char* name) { if (strcmp(name, "VK_KHR_surface") == 0) return ProcHook::KHR_surface; if (strcmp(name, "VK_KHR_surface_protected_capabilities") == 0) return ProcHook::KHR_surface_protected_capabilities; if (strcmp(name, "VK_KHR_swapchain") == 0) return ProcHook::KHR_swapchain; + if (strcmp(name, "VK_EXT_swapchain_maintenance1") == 0) return ProcHook::EXT_swapchain_maintenance1; + if (strcmp(name, "VK_EXT_surface_maintenance1") == 0) return ProcHook::EXT_surface_maintenance1; if (strcmp(name, "VK_ANDROID_external_memory_android_hardware_buffer") == 0) return ProcHook::ANDROID_external_memory_android_hardware_buffer; if (strcmp(name, "VK_KHR_bind_memory2") == 0) return ProcHook::KHR_bind_memory2; if (strcmp(name, "VK_KHR_get_physical_device_properties2") == 0) return ProcHook::KHR_get_physical_device_properties2; @@ -587,6 +605,7 @@ ProcHook::Extension GetProcHookExtension(const char* name) { if (strcmp(name, "VK_KHR_external_memory_capabilities") == 0) return ProcHook::KHR_external_memory_capabilities; if (strcmp(name, "VK_KHR_external_semaphore_capabilities") == 0) return ProcHook::KHR_external_semaphore_capabilities; if (strcmp(name, "VK_KHR_external_fence_capabilities") == 0) return ProcHook::KHR_external_fence_capabilities; + if (strcmp(name, "VK_KHR_external_fence_fd") == 0) return ProcHook::KHR_external_fence_fd; // clang-format on return ProcHook::EXTENSION_UNKNOWN; } @@ -666,6 +685,7 @@ bool InitDriverTable(VkDevice dev, INIT_PROC(true, dev, CreateImage); INIT_PROC(true, dev, DestroyImage); INIT_PROC(true, dev, AllocateCommandBuffers); + INIT_PROC_EXT(KHR_external_fence_fd, true, dev, ImportFenceFdKHR); INIT_PROC(false, dev, BindImageMemory2); INIT_PROC_EXT(KHR_bind_memory2, true, dev, BindImageMemory2KHR); INIT_PROC(false, dev, GetDeviceQueue2); diff --git a/vulkan/libvulkan/driver_gen.h b/vulkan/libvulkan/driver_gen.h index 2f60086a27..31ba04ba1f 100644 --- a/vulkan/libvulkan/driver_gen.h +++ b/vulkan/libvulkan/driver_gen.h @@ -49,6 +49,8 @@ struct ProcHook { KHR_surface, KHR_surface_protected_capabilities, KHR_swapchain, + EXT_swapchain_maintenance1, + EXT_surface_maintenance1, ANDROID_external_memory_android_hardware_buffer, KHR_bind_memory2, KHR_get_physical_device_properties2, @@ -56,6 +58,7 @@ struct ProcHook { KHR_external_memory_capabilities, KHR_external_semaphore_capabilities, KHR_external_fence_capabilities, + KHR_external_fence_fd, EXTENSION_CORE_1_0, EXTENSION_CORE_1_1, @@ -118,6 +121,7 @@ struct DeviceDriverTable { PFN_vkCreateImage CreateImage; PFN_vkDestroyImage DestroyImage; PFN_vkAllocateCommandBuffers AllocateCommandBuffers; + PFN_vkImportFenceFdKHR ImportFenceFdKHR; PFN_vkBindImageMemory2 BindImageMemory2; PFN_vkBindImageMemory2KHR BindImageMemory2KHR; PFN_vkGetDeviceQueue2 GetDeviceQueue2; diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp index 1bf7abb758..0dac6ad1aa 100644 --- a/vulkan/libvulkan/swapchain.cpp +++ b/vulkan/libvulkan/swapchain.cpp @@ -291,6 +291,9 @@ struct Swapchain { release_fence(-1), dequeued(false) {} VkImage image; + // If the image is bound to memory, an sp to the underlying gralloc buffer. + // Otherwise, nullptr; the image will be bound to memory as part of + // AcquireNextImage. android::sp buffer; // The fence is only valid when the buffer is dequeued, and should be // -1 any other time. When valid, we own the fd, and must ensure it is @@ -652,100 +655,40 @@ VkResult GetPhysicalDeviceSurfaceCapabilitiesKHR( VkSurfaceCapabilitiesKHR* capabilities) { ATRACE_CALL(); - int err; - int width, height; - int transform_hint; - int max_buffer_count; - if (surface == VK_NULL_HANDLE) { - const InstanceData& instance_data = GetData(pdev); - ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query; - bool surfaceless_enabled = - instance_data.hook_extensions.test(surfaceless); - if (!surfaceless_enabled) { - // It is an error to pass a surface==VK_NULL_HANDLE unless the - // VK_GOOGLE_surfaceless_query extension is enabled - return VK_ERROR_SURFACE_LOST_KHR; - } - // Support for VK_GOOGLE_surfaceless_query. The primary purpose of this - // extension for this function is for - // VkSurfaceProtectedCapabilitiesKHR::supportsProtected. The following - // four values cannot be known without a surface. Default values will - // be supplied anyway, but cannot be relied upon. - width = 0xFFFFFFFF; - height = 0xFFFFFFFF; - transform_hint = VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR; - capabilities->minImageCount = 0xFFFFFFFF; - capabilities->maxImageCount = 0xFFFFFFFF; - } else { - ANativeWindow* window = SurfaceFromHandle(surface)->window.get(); + // Implement in terms of GetPhysicalDeviceSurfaceCapabilities2KHR - err = window->query(window, NATIVE_WINDOW_DEFAULT_WIDTH, &width); - if (err != android::OK) { - ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)", - strerror(-err), err); - return VK_ERROR_SURFACE_LOST_KHR; - } - err = window->query(window, NATIVE_WINDOW_DEFAULT_HEIGHT, &height); - if (err != android::OK) { - ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)", - strerror(-err), err); - return VK_ERROR_SURFACE_LOST_KHR; - } - - err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT, - &transform_hint); - if (err != android::OK) { - ALOGE("NATIVE_WINDOW_TRANSFORM_HINT query failed: %s (%d)", - strerror(-err), err); - return VK_ERROR_SURFACE_LOST_KHR; - } - - err = window->query(window, NATIVE_WINDOW_MAX_BUFFER_COUNT, - &max_buffer_count); - if (err != android::OK) { - ALOGE("NATIVE_WINDOW_MAX_BUFFER_COUNT query failed: %s (%d)", - strerror(-err), err); - return VK_ERROR_SURFACE_LOST_KHR; - } - capabilities->minImageCount = std::min(max_buffer_count, 3); - capabilities->maxImageCount = static_cast(max_buffer_count); - } - - capabilities->currentExtent = - VkExtent2D{static_cast(width), static_cast(height)}; + VkPhysicalDeviceSurfaceInfo2KHR info2 = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR, + nullptr, + surface + }; - // TODO(http://b/134182502): Figure out what the max extent should be. - capabilities->minImageExtent = VkExtent2D{1, 1}; - capabilities->maxImageExtent = VkExtent2D{4096, 4096}; + VkSurfaceCapabilities2KHR caps2 = { + VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR, + nullptr, + {}, + }; - if (capabilities->maxImageExtent.height < - capabilities->currentExtent.height) { - capabilities->maxImageExtent.height = - capabilities->currentExtent.height; - } + VkResult result = GetPhysicalDeviceSurfaceCapabilities2KHR(pdev, &info2, &caps2); + *capabilities = caps2.surfaceCapabilities; + return result; +} - if (capabilities->maxImageExtent.width < - capabilities->currentExtent.width) { - capabilities->maxImageExtent.width = capabilities->currentExtent.width; +// Does the call-twice and VK_INCOMPLETE handling for querying lists +// of things, where we already have the full set built in a vector. +template +VkResult CopyWithIncomplete(std::vector const& things, + T* callerPtr, uint32_t* callerCount) { + VkResult result = VK_SUCCESS; + if (callerPtr) { + if (things.size() > *callerCount) + result = VK_INCOMPLETE; + *callerCount = std::min(uint32_t(things.size()), *callerCount); + std::copy(things.begin(), things.begin() + *callerCount, callerPtr); + } else { + *callerCount = things.size(); } - - capabilities->maxImageArrayLayers = 1; - - capabilities->supportedTransforms = kSupportedTransforms; - capabilities->currentTransform = - TranslateNativeToVulkanTransform(transform_hint); - - // On Android, window composition is a WindowManager property, not something - // associated with the bufferqueue. It can't be changed from here. - capabilities->supportedCompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; - - capabilities->supportedUsageFlags = - VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | - VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | - VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT; - - return VK_SUCCESS; + return result; } VKAPI_ATTR @@ -862,21 +805,7 @@ VkResult GetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice pdev, // Android users. This includes the ANGLE team (a layered implementation of // OpenGL-ES). - VkResult result = VK_SUCCESS; - if (formats) { - uint32_t transfer_count = all_formats.size(); - if (transfer_count > *count) { - transfer_count = *count; - result = VK_INCOMPLETE; - } - std::copy(all_formats.begin(), all_formats.begin() + transfer_count, - formats); - *count = transfer_count; - } else { - *count = all_formats.size(); - } - - return result; + return CopyWithIncomplete(all_formats, formats, count); } VKAPI_ATTR @@ -886,19 +815,134 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR( VkSurfaceCapabilities2KHR* pSurfaceCapabilities) { ATRACE_CALL(); - VkResult result = GetPhysicalDeviceSurfaceCapabilitiesKHR( - physicalDevice, pSurfaceInfo->surface, - &pSurfaceCapabilities->surfaceCapabilities); + auto surface = pSurfaceInfo->surface; + auto capabilities = &pSurfaceCapabilities->surfaceCapabilities; + + VkSurfacePresentModeEXT const *pPresentMode = nullptr; + for (auto pNext = reinterpret_cast(pSurfaceInfo->pNext); + pNext; pNext = reinterpret_cast(pNext->pNext)) { + switch (pNext->sType) { + case VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_EXT: + pPresentMode = reinterpret_cast(pNext); + break; + + default: + break; + } + } - VkSurfaceCapabilities2KHR* caps = pSurfaceCapabilities; - while (caps->pNext) { - caps = reinterpret_cast(caps->pNext); + int err; + int width, height; + int transform_hint; + int max_buffer_count; + if (surface == VK_NULL_HANDLE) { + const InstanceData& instance_data = GetData(physicalDevice); + ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query; + bool surfaceless_enabled = + instance_data.hook_extensions.test(surfaceless); + if (!surfaceless_enabled) { + // It is an error to pass a surface==VK_NULL_HANDLE unless the + // VK_GOOGLE_surfaceless_query extension is enabled + return VK_ERROR_SURFACE_LOST_KHR; + } + // Support for VK_GOOGLE_surfaceless_query. The primary purpose of this + // extension for this function is for + // VkSurfaceProtectedCapabilitiesKHR::supportsProtected. The following + // four values cannot be known without a surface. Default values will + // be supplied anyway, but cannot be relied upon. + width = 0xFFFFFFFF; + height = 0xFFFFFFFF; + transform_hint = VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR; + capabilities->minImageCount = 0xFFFFFFFF; + capabilities->maxImageCount = 0xFFFFFFFF; + } else { + ANativeWindow* window = SurfaceFromHandle(surface)->window.get(); - switch (caps->sType) { + err = window->query(window, NATIVE_WINDOW_DEFAULT_WIDTH, &width); + if (err != android::OK) { + ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)", + strerror(-err), err); + return VK_ERROR_SURFACE_LOST_KHR; + } + err = window->query(window, NATIVE_WINDOW_DEFAULT_HEIGHT, &height); + if (err != android::OK) { + ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)", + strerror(-err), err); + return VK_ERROR_SURFACE_LOST_KHR; + } + + err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT, + &transform_hint); + if (err != android::OK) { + ALOGE("NATIVE_WINDOW_TRANSFORM_HINT query failed: %s (%d)", + strerror(-err), err); + return VK_ERROR_SURFACE_LOST_KHR; + } + + err = window->query(window, NATIVE_WINDOW_MAX_BUFFER_COUNT, + &max_buffer_count); + if (err != android::OK) { + ALOGE("NATIVE_WINDOW_MAX_BUFFER_COUNT query failed: %s (%d)", + strerror(-err), err); + return VK_ERROR_SURFACE_LOST_KHR; + } + + if (pPresentMode && IsSharedPresentMode(pPresentMode->presentMode)) { + capabilities->minImageCount = 1; + capabilities->maxImageCount = 1; + } else if (pPresentMode && pPresentMode->presentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + // TODO: use undequeued buffer requirement for more precise bound + capabilities->minImageCount = std::min(max_buffer_count, 4); + capabilities->maxImageCount = static_cast(max_buffer_count); + } else { + // TODO: if we're able to, provide better bounds on the number of buffers + // for other modes as well. + capabilities->minImageCount = std::min(max_buffer_count, 3); + capabilities->maxImageCount = static_cast(max_buffer_count); + } + } + + capabilities->currentExtent = + VkExtent2D{static_cast(width), static_cast(height)}; + + // TODO(http://b/134182502): Figure out what the max extent should be. + capabilities->minImageExtent = VkExtent2D{1, 1}; + capabilities->maxImageExtent = VkExtent2D{4096, 4096}; + + if (capabilities->maxImageExtent.height < + capabilities->currentExtent.height) { + capabilities->maxImageExtent.height = + capabilities->currentExtent.height; + } + + if (capabilities->maxImageExtent.width < + capabilities->currentExtent.width) { + capabilities->maxImageExtent.width = capabilities->currentExtent.width; + } + + capabilities->maxImageArrayLayers = 1; + + capabilities->supportedTransforms = kSupportedTransforms; + capabilities->currentTransform = + TranslateNativeToVulkanTransform(transform_hint); + + // On Android, window composition is a WindowManager property, not something + // associated with the bufferqueue. It can't be changed from here. + capabilities->supportedCompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; + + capabilities->supportedUsageFlags = + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT; + + for (auto pNext = reinterpret_cast(pSurfaceCapabilities->pNext); + pNext; pNext = reinterpret_cast(pNext->pNext)) { + + switch (pNext->sType) { case VK_STRUCTURE_TYPE_SHARED_PRESENT_SURFACE_CAPABILITIES_KHR: { VkSharedPresentSurfaceCapabilitiesKHR* shared_caps = - reinterpret_cast( - caps); + reinterpret_cast(pNext); // Claim same set of usage flags are supported for // shared present modes as for other modes. shared_caps->sharedPresentSupportedUsageFlags = @@ -908,17 +952,64 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR( case VK_STRUCTURE_TYPE_SURFACE_PROTECTED_CAPABILITIES_KHR: { VkSurfaceProtectedCapabilitiesKHR* protected_caps = - reinterpret_cast(caps); + reinterpret_cast(pNext); protected_caps->supportsProtected = VK_TRUE; } break; + case VK_STRUCTURE_TYPE_SURFACE_PRESENT_SCALING_CAPABILITIES_EXT: { + VkSurfacePresentScalingCapabilitiesEXT* scaling_caps = + reinterpret_cast(pNext); + // By default, Android stretches the buffer to fit the window, + // without preserving aspect ratio. Other modes are technically possible + // but consult with CoGS team before exposing them here! + scaling_caps->supportedPresentScaling = VK_PRESENT_SCALING_STRETCH_BIT_EXT; + + // Since we always scale, we don't support any gravity. + scaling_caps->supportedPresentGravityX = 0; + scaling_caps->supportedPresentGravityY = 0; + + // Scaled image limits are just the basic image limits + scaling_caps->minScaledImageExtent = capabilities->minImageExtent; + scaling_caps->maxScaledImageExtent = capabilities->maxImageExtent; + } break; + + case VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_COMPATIBILITY_EXT: { + VkSurfacePresentModeCompatibilityEXT* mode_caps = + reinterpret_cast(pNext); + + ALOG_ASSERT(pPresentMode, + "querying VkSurfacePresentModeCompatibilityEXT " + "requires VkSurfacePresentModeEXT to be provided"); + std::vector compatibleModes; + compatibleModes.push_back(pPresentMode->presentMode); + + switch (pPresentMode->presentMode) { + // Shared modes are both compatible with each other. + case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR: + compatibleModes.push_back(VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR); + break; + case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR: + compatibleModes.push_back(VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR); + break; + default: + // Other modes are only compatible with themselves. + // TODO: consider whether switching between FIFO and MAILBOX is reasonable + break; + } + + // Note: this does not generate VK_INCOMPLETE since we're nested inside + // a larger query and there would be no way to determine exactly where it came from. + CopyWithIncomplete(compatibleModes, mode_caps->pPresentModes, + &mode_caps->presentModeCount); + } break; + default: // Ignore all other extension structs break; } } - return result; + return VK_SUCCESS; } VKAPI_ATTR @@ -1077,18 +1168,7 @@ VkResult GetPhysicalDeviceSurfacePresentModesKHR(VkPhysicalDevice pdev, present_modes.push_back(VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR); } - uint32_t num_modes = uint32_t(present_modes.size()); - - VkResult result = VK_SUCCESS; - if (modes) { - if (*count < num_modes) - result = VK_INCOMPLETE; - *count = std::min(*count, num_modes); - std::copy_n(present_modes.data(), *count, modes); - } else { - *count = num_modes; - } - return result; + return CopyWithIncomplete(present_modes, modes, count); } VKAPI_ATTR @@ -1374,8 +1454,7 @@ VkResult CreateSwapchainKHR(VkDevice device, } VkSwapchainImageUsageFlagsANDROID swapchain_image_usage = 0; - if (create_info->presentMode == VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR || - create_info->presentMode == VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR) { + if (IsSharedPresentMode(create_info->presentMode)) { swapchain_image_usage |= VK_SWAPCHAIN_IMAGE_USAGE_SHARED_BIT_ANDROID; err = native_window_set_shared_buffer_mode(window, true); if (err != android::OK) { @@ -1525,8 +1604,6 @@ VkResult CreateSwapchainKHR(VkDevice device, Swapchain* swapchain = new (mem) Swapchain(surface, num_images, create_info->presentMode, TranslateVulkanToNativeTransform(create_info->preTransform)); - // -- Dequeue all buffers and create a VkImage for each -- - // Any failures during or after this must cancel the dequeued buffers. VkSwapchainImageCreateInfoANDROID swapchain_image_create = { #pragma clang diagnostic push @@ -1543,13 +1620,18 @@ VkResult CreateSwapchainKHR(VkDevice device, #pragma clang diagnostic pop .pNext = &swapchain_image_create, }; + VkImageCreateInfo image_create = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, - .pNext = &image_native_buffer, + .pNext = nullptr, .flags = createProtectedSwapchain ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u, .imageType = VK_IMAGE_TYPE_2D, .format = create_info->imageFormat, - .extent = {0, 0, 1}, + .extent = { + create_info->imageExtent.width, + create_info->imageExtent.height, + 1 + }, .mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT, @@ -1560,60 +1642,87 @@ VkResult CreateSwapchainKHR(VkDevice device, .pQueueFamilyIndices = create_info->pQueueFamilyIndices, }; - for (uint32_t i = 0; i < num_images; i++) { - Swapchain::Image& img = swapchain->images[i]; + // Note: don't do deferred allocation for shared present modes. There's only one buffer + // involved so very little benefit. + if ((create_info->flags & VK_SWAPCHAIN_CREATE_DEFERRED_MEMORY_ALLOCATION_BIT_EXT) && + !IsSharedPresentMode(create_info->presentMode)) { + // Don't want to touch the underlying gralloc buffers yet; + // instead just create unbound VkImages which will later be bound to memory inside + // AcquireNextImage. + VkImageSwapchainCreateInfoKHR image_swapchain_create = { + .sType = VK_STRUCTURE_TYPE_IMAGE_SWAPCHAIN_CREATE_INFO_KHR, + .pNext = nullptr, + .swapchain = HandleFromSwapchain(swapchain), + }; + image_create.pNext = &image_swapchain_create; - ANativeWindowBuffer* buffer; - err = window->dequeueBuffer(window, &buffer, &img.dequeue_fence); - if (err != android::OK) { - ALOGE("dequeueBuffer[%u] failed: %s (%d)", i, strerror(-err), err); - switch (-err) { - case ENOMEM: - result = VK_ERROR_OUT_OF_DEVICE_MEMORY; - break; - default: - result = VK_ERROR_SURFACE_LOST_KHR; - break; + for (uint32_t i = 0; i < num_images; i++) { + Swapchain::Image& img = swapchain->images[i]; + img.buffer = nullptr; + img.dequeued = false; + + result = dispatch.CreateImage(device, &image_create, nullptr, &img.image); + if (result != VK_SUCCESS) { + ALOGD("vkCreateImage w/ for deferred swapchain image failed: %u", result); + break; } - break; } - img.buffer = buffer; - img.dequeued = true; - - image_create.extent = - VkExtent3D{static_cast(img.buffer->width), - static_cast(img.buffer->height), - 1}; - image_native_buffer.handle = img.buffer->handle; - image_native_buffer.stride = img.buffer->stride; - image_native_buffer.format = img.buffer->format; - image_native_buffer.usage = int(img.buffer->usage); - android_convertGralloc0To1Usage(int(img.buffer->usage), - &image_native_buffer.usage2.producer, - &image_native_buffer.usage2.consumer); - image_native_buffer.usage3 = img.buffer->usage; - - ATRACE_BEGIN("CreateImage"); - result = - dispatch.CreateImage(device, &image_create, nullptr, &img.image); - ATRACE_END(); - if (result != VK_SUCCESS) { - ALOGD("vkCreateImage w/ native buffer failed: %u", result); - break; + } else { + // -- Dequeue all buffers and create a VkImage for each -- + // Any failures during or after this must cancel the dequeued buffers. + + for (uint32_t i = 0; i < num_images; i++) { + Swapchain::Image& img = swapchain->images[i]; + + ANativeWindowBuffer* buffer; + err = window->dequeueBuffer(window, &buffer, &img.dequeue_fence); + if (err != android::OK) { + ALOGE("dequeueBuffer[%u] failed: %s (%d)", i, strerror(-err), err); + switch (-err) { + case ENOMEM: + result = VK_ERROR_OUT_OF_DEVICE_MEMORY; + break; + default: + result = VK_ERROR_SURFACE_LOST_KHR; + break; + } + break; + } + img.buffer = buffer; + img.dequeued = true; + + image_native_buffer.handle = img.buffer->handle; + image_native_buffer.stride = img.buffer->stride; + image_native_buffer.format = img.buffer->format; + image_native_buffer.usage = int(img.buffer->usage); + android_convertGralloc0To1Usage(int(img.buffer->usage), + &image_native_buffer.usage2.producer, + &image_native_buffer.usage2.consumer); + image_native_buffer.usage3 = img.buffer->usage; + image_create.pNext = &image_native_buffer; + + ATRACE_BEGIN("CreateImage"); + result = + dispatch.CreateImage(device, &image_create, nullptr, &img.image); + ATRACE_END(); + if (result != VK_SUCCESS) { + ALOGD("vkCreateImage w/ native buffer failed: %u", result); + break; + } } - } - // -- Cancel all buffers, returning them to the queue -- - // If an error occurred before, also destroy the VkImage and release the - // buffer reference. Otherwise, we retain a strong reference to the buffer. - for (uint32_t i = 0; i < num_images; i++) { - Swapchain::Image& img = swapchain->images[i]; - if (img.dequeued) { - if (!swapchain->shared) { - window->cancelBuffer(window, img.buffer.get(), - img.dequeue_fence); - img.dequeue_fence = -1; - img.dequeued = false; + // -- Cancel all buffers, returning them to the queue -- + // If an error occurred before, also destroy the VkImage and release the + // buffer reference. Otherwise, we retain a strong reference to the buffer. + for (uint32_t i = 0; i < num_images; i++) { + Swapchain::Image& img = swapchain->images[i]; + if (img.dequeued) { + if (!swapchain->shared) { + window->cancelBuffer(window, img.buffer.get(), + img.dequeue_fence); + img.dequeue_fence = -1; + img.dequeued = false; + } } } } @@ -1736,6 +1845,64 @@ VkResult AcquireNextImageKHR(VkDevice device, break; } } + + // If this is a deferred alloc swapchain, this may be the first time we've + // seen a particular buffer. If so, there should be an empty slot. Find it, + // and bind the gralloc buffer to the VkImage for that slot. If there is no + // empty slot, then we dequeued an unexpected buffer. Non-deferred swapchains + // will also take this path, but will never have an empty slot since we + // populated them all upfront. + if (idx == swapchain.num_images) { + for (idx = 0; idx < swapchain.num_images; idx++) { + if (!swapchain.images[idx].buffer) { + // Note: this structure is technically required for + // Vulkan correctness, even though the driver is probably going + // to use everything from the VkNativeBufferANDROID below. + // This is kindof silly, but it's how we did the ANB + // side of VK_KHR_swapchain v69, so we're stuck with it unless + // we want to go tinkering with the ANB spec some more. + VkBindImageMemorySwapchainInfoKHR bimsi = { + .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_SWAPCHAIN_INFO_KHR, + .pNext = nullptr, + .swapchain = swapchain_handle, + .imageIndex = idx, + }; + VkNativeBufferANDROID nb = { + .sType = VK_STRUCTURE_TYPE_NATIVE_BUFFER_ANDROID, + .pNext = &bimsi, + .handle = buffer->handle, + .stride = buffer->stride, + .format = buffer->format, + .usage = int(buffer->usage), + }; + VkBindImageMemoryInfo bimi = { + .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO, + .pNext = &nb, + .image = swapchain.images[idx].image, + .memory = VK_NULL_HANDLE, + .memoryOffset = 0, + }; + result = GetData(device).driver.BindImageMemory2(device, 1, &bimi); + if (result != VK_SUCCESS) { + // This shouldn't really happen. If it does, something is probably + // unrecoverably wrong with the swapchain and its images. Cancel + // the buffer and declare the swapchain broken. + ALOGE("failed to do deferred gralloc buffer bind"); + window->cancelBuffer(window, buffer, fence_fd); + return VK_ERROR_OUT_OF_DATE_KHR; + } + + swapchain.images[idx].dequeued = true; + swapchain.images[idx].dequeue_fence = fence_fd; + swapchain.images[idx].buffer = buffer; + break; + } + } + } + + // The buffer doesn't match any slot. This shouldn't normally happen, but is + // possible if the bufferqueue is reconfigured behind libvulkan's back. If this + // happens, just declare the swapchain to be broken and the app will recreate it. if (idx == swapchain.num_images) { ALOGE("dequeueBuffer returned unrecognized buffer"); window->cancelBuffer(window, buffer, fence_fd); @@ -1861,12 +2028,32 @@ static void SetSwapchainFrameTimestamp(Swapchain &swapchain, const VkPresentTime } } +// EXT_swapchain_maintenance1 present mode change +static bool SetSwapchainPresentMode(ANativeWindow *window, VkPresentModeKHR mode) { + // There is no dynamic switching between non-shared present modes. + // All we support is switching between demand and continuous refresh. + if (!IsSharedPresentMode(mode)) + return true; + + int err = native_window_set_auto_refresh(window, + mode == VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR); + if (err != android::OK) { + ALOGE("native_window_set_auto_refresh() failed: %s (%d)", + strerror(-err), err); + return false; + } + + return true; +} + static VkResult PresentOneSwapchain( VkQueue queue, Swapchain& swapchain, uint32_t imageIndex, const VkPresentRegionKHR *pRegion, const VkPresentTimeGOOGLE *pTime, + VkFence presentFence, + const VkPresentModeKHR *pPresentMode, uint32_t waitSemaphoreCount, const VkSemaphore *pWaitSemaphores) { @@ -1897,12 +2084,33 @@ static VkResult PresentOneSwapchain( ANativeWindow* window = swapchain.surface.window.get(); if (swapchain_result == VK_SUCCESS) { + if (presentFence != VK_NULL_HANDLE) { + int fence_copy = fence < 0 ? -1 : dup(fence); + VkImportFenceFdInfoKHR iffi = { + VK_STRUCTURE_TYPE_IMPORT_FENCE_FD_INFO_KHR, + nullptr, + presentFence, + VK_FENCE_IMPORT_TEMPORARY_BIT, + VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT, + fence_copy, + }; + if (VK_SUCCESS != dispatch.ImportFenceFdKHR(device, &iffi) && fence_copy >= 0) { + // ImportFenceFdKHR takes ownership only if it succeeds + close(fence_copy); + } + } + if (pRegion) { SetSwapchainSurfaceDamage(window, pRegion); } if (pTime) { SetSwapchainFrameTimestamp(swapchain, pTime); } + if (pPresentMode) { + if (!SetSwapchainPresentMode(window, *pPresentMode)) + swapchain_result = WorstPresentResult(swapchain_result, + VK_ERROR_SURFACE_LOST_KHR); + } err = window->queueBuffer(window, img.buffer.get(), fence); // queueBuffer always closes fence, even on error @@ -1983,6 +2191,9 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) { // Look at the pNext chain for supported extension structs: const VkPresentRegionsKHR* present_regions = nullptr; const VkPresentTimesInfoGOOGLE* present_times = nullptr; + const VkSwapchainPresentFenceInfoEXT* present_fences = nullptr; + const VkSwapchainPresentModeInfoEXT* present_modes = nullptr; + const VkPresentRegionsKHR* next = reinterpret_cast(present_info->pNext); while (next) { @@ -1994,6 +2205,14 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) { present_times = reinterpret_cast(next); break; + case VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_FENCE_INFO_EXT: + present_fences = + reinterpret_cast(next); + break; + case VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_EXT: + present_modes = + reinterpret_cast(next); + break; default: ALOGV("QueuePresentKHR ignoring unrecognized pNext->sType = %x", next->sType); @@ -2009,6 +2228,15 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) { present_times->swapchainCount != present_info->swapchainCount, "VkPresentTimesInfoGOOGLE::swapchainCount != " "VkPresentInfo::swapchainCount"); + ALOGV_IF(present_fences && + present_fences->swapchainCount != present_info->swapchainCount, + "VkSwapchainPresentFenceInfoEXT::swapchainCount != " + "VkPresentInfo::swapchainCount"); + ALOGV_IF(present_modes && + present_modes->swapchainCount != present_info->swapchainCount, + "VkSwapchainPresentModeInfoEXT::swapchainCount != " + "VkPresentInfo::swapchainCount"); + const VkPresentRegionKHR* regions = (present_regions) ? present_regions->pRegions : nullptr; const VkPresentTimeGOOGLE* times = @@ -2024,6 +2252,8 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) { present_info->pImageIndices[sc], (regions && !swapchain.mailbox_mode) ? ®ions[sc] : nullptr, times ? ×[sc] : nullptr, + present_fences ? present_fences->pFences[sc] : VK_NULL_HANDLE, + present_modes ? &present_modes->pPresentModes[sc] : nullptr, present_info->waitSemaphoreCount, present_info->pWaitSemaphores); @@ -2248,5 +2478,35 @@ VkResult BindImageMemory2KHR(VkDevice device, out_bind_infos.empty() ? pBindInfos : out_bind_infos.data()); } +VKAPI_ATTR +VkResult ReleaseSwapchainImagesEXT(VkDevice /*device*/, + const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo) { + ATRACE_CALL(); + + Swapchain& swapchain = *SwapchainFromHandle(pReleaseInfo->swapchain); + ANativeWindow* window = swapchain.surface.window.get(); + + // If in shared present mode, don't actually release the image back to the BQ. + // Both sides share it forever. + if (swapchain.shared) + return VK_SUCCESS; + + for (uint32_t i = 0; i < pReleaseInfo->imageIndexCount; i++) { + Swapchain::Image& img = swapchain.images[pReleaseInfo->pImageIndices[i]]; + window->cancelBuffer(window, img.buffer.get(), img.dequeue_fence); + + // cancelBuffer has taken ownership of the dequeue fence + img.dequeue_fence = -1; + // if we're still holding a release fence, get rid of it now + if (img.release_fence >= 0) { + close(img.release_fence); + img.release_fence = -1; + } + img.dequeued = false; + } + + return VK_SUCCESS; +} + } // namespace driver } // namespace vulkan diff --git a/vulkan/libvulkan/swapchain.h b/vulkan/libvulkan/swapchain.h index 4912ef1a33..280fe9b5a3 100644 --- a/vulkan/libvulkan/swapchain.h +++ b/vulkan/libvulkan/swapchain.h @@ -46,6 +46,7 @@ VKAPI_ATTR VkResult GetPhysicalDeviceSurfaceCapabilities2KHR(VkPhysicalDevice ph VKAPI_ATTR VkResult GetPhysicalDeviceSurfaceFormats2KHR(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, uint32_t* pSurfaceFormatCount, VkSurfaceFormat2KHR* pSurfaceFormats); VKAPI_ATTR VkResult BindImageMemory2(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos); VKAPI_ATTR VkResult BindImageMemory2KHR(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos); +VKAPI_ATTR VkResult ReleaseSwapchainImagesEXT(VkDevice device, const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo); // clang-format on } // namespace driver diff --git a/vulkan/scripts/driver_generator.py b/vulkan/scripts/driver_generator.py index af56764a21..78b550c202 100644 --- a/vulkan/scripts/driver_generator.py +++ b/vulkan/scripts/driver_generator.py @@ -35,6 +35,8 @@ _INTERCEPTED_EXTENSIONS = [ 'VK_KHR_surface', 'VK_KHR_surface_protected_capabilities', 'VK_KHR_swapchain', + 'VK_EXT_swapchain_maintenance1', + 'VK_EXT_surface_maintenance1', ] # Extensions known to vulkan::driver level. @@ -46,6 +48,7 @@ _KNOWN_EXTENSIONS = _INTERCEPTED_EXTENSIONS + [ 'VK_KHR_external_memory_capabilities', 'VK_KHR_external_semaphore_capabilities', 'VK_KHR_external_fence_capabilities', + 'VK_KHR_external_fence_fd', ] # Functions needed at vulkan::driver level. @@ -112,6 +115,9 @@ _NEEDED_COMMANDS = [ # For promoted VK_KHR_external_fence_capabilities 'vkGetPhysicalDeviceExternalFenceProperties', 'vkGetPhysicalDeviceExternalFencePropertiesKHR', + + # VK_KHR_swapchain_maintenance1 requirement + 'vkImportFenceFdKHR', ] # Functions intercepted at vulkan::driver level. -- GitLab From 646ebf21377ff28f9604846351a1230f9cd5c1a9 Mon Sep 17 00:00:00 2001 From: Ian Elliott Date: Tue, 29 Nov 2022 13:58:20 -0700 Subject: [PATCH 0643/1310] Compile SkiaVkRenderEngine so that it can be enabled Test: manual + CI testing Bug: 236390072 Change-Id: I7c583e29de9c236281475cbbb0ed3fa4f796d86c --- libs/renderengine/Android.bp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp index 8d19c45527..b8fd1b2292 100644 --- a/libs/renderengine/Android.bp +++ b/libs/renderengine/Android.bp @@ -133,6 +133,8 @@ cc_library_static { "-fvisibility=hidden", "-Werror=format", "-Wno-unused-parameter", + // TODO: Investigate reducing pinned-memory usage (b/263377839) + "-DRE_SKIAVK", ], srcs: [ ":librenderengine_sources", -- GitLab From f5ab5ae62252e952748a04d8099f6d0adcbd76a4 Mon Sep 17 00:00:00 2001 From: Brian Lindahl Date: Wed, 21 Dec 2022 14:27:15 -0700 Subject: [PATCH 0644/1310] Stop using nullptr for GraphicBuffers in unit tests Bug: 258196272 Bug: 261930578 Fixes: 261930578 Test: presubmit Change-Id: I76ec7cd245fa23ebc4ffe8fcc9f09f25ca7b993f --- .../renderengine/mock/FakeExternalTexture.h | 6 ++-- .../CompositionEngine/src/HwcBufferCache.cpp | 5 --- .../Tracing/tools/LayerTraceGenerator.cpp | 35 +++++++++++++++---- .../tests/unittests/CompositionTest.cpp | 4 ++- .../unittests/DisplayTransactionTestHelpers.h | 4 ++- 5 files changed, 38 insertions(+), 16 deletions(-) diff --git a/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h b/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h index 974e0fddde..b95f011753 100644 --- a/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h +++ b/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h @@ -23,7 +23,9 @@ namespace renderengine { namespace mock { class FakeExternalTexture : public renderengine::ExternalTexture { - const sp mNullBuffer = nullptr; + const sp mEmptyBuffer = + sp::make(1u, 1u, PIXEL_FORMAT_RGBA_8888, + GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_OFTEN); uint32_t mWidth; uint32_t mHeight; uint64_t mId; @@ -34,7 +36,7 @@ public: FakeExternalTexture(uint32_t width, uint32_t height, uint64_t id, PixelFormat pixelFormat, uint64_t usage) : mWidth(width), mHeight(height), mId(id), mPixelFormat(pixelFormat), mUsage(usage) {} - const sp& getBuffer() const { return mNullBuffer; } + const sp& getBuffer() const { return mEmptyBuffer; } bool hasSameBuffer(const renderengine::ExternalTexture& other) const override { return getId() == other.getId(); } diff --git a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp index 34ed214079..f0105b2782 100644 --- a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp +++ b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp @@ -28,11 +28,6 @@ HwcBufferCache::HwcBufferCache() { } HwcSlotAndBuffer HwcBufferCache::getHwcSlotAndBuffer(const sp& buffer) { - // TODO(b/261930578): This is for unit tests which don't mock GraphicBuffers but instead send - // in nullptrs. - if (buffer == nullptr) { - return {0, nullptr}; - } if (auto i = mCacheByBufferId.find(buffer->getId()); i != mCacheByBufferId.end()) { Cache& cache = i->second; // mark this cache slot as more recently used so it won't get evicted anytime soon diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index f1a6c0e2fa..ab98dbfe2f 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -95,6 +94,30 @@ public: } }; +class FakeExternalTexture : public renderengine::ExternalTexture { + const sp mNullBuffer = nullptr; + uint32_t mWidth; + uint32_t mHeight; + uint64_t mId; + PixelFormat mPixelFormat; + uint64_t mUsage; + +public: + FakeExternalTexture(uint32_t width, uint32_t height, uint64_t id, PixelFormat pixelFormat, + uint64_t usage) + : mWidth(width), mHeight(height), mId(id), mPixelFormat(pixelFormat), mUsage(usage) {} + const sp& getBuffer() const { return mNullBuffer; } + bool hasSameBuffer(const renderengine::ExternalTexture& other) const override { + return getId() == other.getId(); + } + uint32_t getWidth() const override { return mWidth; } + uint32_t getHeight() const override { return mHeight; } + uint64_t getId() const override { return mId; } + PixelFormat getPixelFormat() const override { return mPixelFormat; } + uint64_t getUsage() const override { return mUsage; } + ~FakeExternalTexture() = default; +}; + class MockSurfaceFlinger : public SurfaceFlinger { public: MockSurfaceFlinger(Factory& factory) @@ -102,12 +125,10 @@ public: std::shared_ptr getExternalTextureFromBufferData( BufferData& bufferData, const char* /* layerName */, uint64_t /* transactionId */) override { - return std::make_shared(bufferData.getWidth(), - bufferData.getHeight(), - bufferData.getId(), - bufferData - .getPixelFormat(), - bufferData.getUsage()); + return std::make_shared(bufferData.getWidth(), bufferData.getHeight(), + bufferData.getId(), + bufferData.getPixelFormat(), + bufferData.getUsage()); }; // b/220017192 migrate from transact codes to ISurfaceComposer apis diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 06b9caa7cb..ba77600cda 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -182,7 +182,9 @@ public: sp mNativeWindow = sp::make(); std::vector> mAuxiliaryLayers; - sp mBuffer = sp::make(); + sp mBuffer = + sp::make(1u, 1u, PIXEL_FORMAT_RGBA_8888, + GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_OFTEN); ANativeWindowBuffer* mNativeWindowBuffer = mBuffer->getNativeBuffer(); Hwc2::mock::Composer* mComposer = nullptr; diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h index d58e644506..223f4db889 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h @@ -115,7 +115,9 @@ public: TestableSurfaceFlinger mFlinger; sp mNativeWindow = sp::make(); - sp mBuffer = sp::make(); + sp mBuffer = + sp::make(1u, 1u, PIXEL_FORMAT_RGBA_8888, + GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_OFTEN); Hwc2::mock::PowerAdvisor mPowerAdvisor; FakeDisplayInjector mFakeDisplayInjector{mFlinger, mPowerAdvisor, mNativeWindow}; -- GitLab From 3e1e1e6920cb5265e0ef15ba81d33a3625de086d Mon Sep 17 00:00:00 2001 From: Brian Lindahl Date: Wed, 21 Dec 2022 14:28:58 -0700 Subject: [PATCH 0645/1310] Uncache the active buffer slot last This allows the memory for the active buffer to be freed as soon as possible by purging it from HWC cache as soon as the next buffer is sent to the layer. Bug: 258196272 Test: atest OutputLayerUncacheBufferTest Change-Id: I96c24390de5757ac99b369119e9ba031afb0b042 --- .../impl/OutputLayerCompositionState.h | 1 + .../CompositionEngine/src/OutputLayer.cpp | 28 +++++++++---- .../tests/OutputLayerTest.cpp | 41 +++++++++++++------ 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h index 2a5bfaea6c..b86782f417 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h @@ -137,6 +137,7 @@ struct OutputLayerCompositionState { HwcBufferCache hwcBufferCache; // The previously-active buffer for this layer. + uint64_t activeBufferId; uint32_t activeBufferSlot; // Set to true when overridden info has been sent to HW composer diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp index 766d7ea4c3..60a2c83194 100644 --- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp +++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp @@ -617,13 +617,24 @@ void OutputLayer::uncacheBuffers(const std::vector& bufferIdsToUncache return; } + // Uncache the active buffer last so that it's the first buffer to be purged from the cache + // next time a buffer is sent to this layer. + bool uncacheActiveBuffer = false; + std::vector slotsToClear; for (uint64_t bufferId : bufferIdsToUncache) { - uint32_t slot = state.hwc->hwcBufferCache.uncache(bufferId); - if (slot != UINT32_MAX) { - slotsToClear.push_back(slot); + if (bufferId == state.hwc->activeBufferId) { + uncacheActiveBuffer = true; + } else { + uint32_t slot = state.hwc->hwcBufferCache.uncache(bufferId); + if (slot != UINT32_MAX) { + slotsToClear.push_back(slot); + } } } + if (uncacheActiveBuffer) { + slotsToClear.push_back(state.hwc->hwcBufferCache.uncache(state.hwc->activeBufferId)); + } hal::Error error = state.hwc->hwcLayer->setBufferSlotsToClear(slotsToClear, state.hwc->activeBufferSlot); @@ -655,17 +666,20 @@ void OutputLayer::writeBufferStateToHWC(HWC2::Layer* hwcLayer, hwcSlotAndBuffer = state.hwc->hwcBufferCache.getOverrideHwcSlotAndBuffer( state.overrideInfo.buffer->getBuffer()); hwcFence = state.overrideInfo.acquireFence; + // Keep track of the active buffer ID so when it's discarded we uncache it last so its + // slot will be used first, allowing the memory to be freed as soon as possible. + state.hwc->activeBufferId = state.overrideInfo.buffer->getBuffer()->getId(); } else { hwcSlotAndBuffer = state.hwc->hwcBufferCache.getHwcSlotAndBuffer(outputIndependentState.buffer); hwcFence = outputIndependentState.acquireFence; + // Keep track of the active buffer ID so when it's discarded we uncache it last so its + // slot will be used first, allowing the memory to be freed as soon as possible. + state.hwc->activeBufferId = outputIndependentState.buffer->getId(); } - // Keep track of the active buffer slot, so we can restore it after clearing other buffer // slots. - if (hwcSlotAndBuffer.buffer) { - state.hwc->activeBufferSlot = hwcSlotAndBuffer.slot; - } + state.hwc->activeBufferSlot = hwcSlotAndBuffer.slot; } if (auto error = hwcLayer->setBuffer(hwcSlotAndBuffer.slot, hwcSlotAndBuffer.buffer, hwcFence); diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp index 0edc22681f..9ad2edb983 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp @@ -1318,6 +1318,7 @@ TEST_F(OutputLayerWriteStateToHWCTest, setBlockingRegion) { struct OutputLayerUncacheBufferTest : public OutputLayerTest { static const sp kBuffer1; static const sp kBuffer2; + static const sp kBuffer3; static const sp kFence; OutputLayerUncacheBufferTest() { @@ -1343,6 +1344,10 @@ const sp OutputLayerUncacheBufferTest::kBuffer2 = sp::make(2, 3, PIXEL_FORMAT_RGBA_8888, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN); +const sp OutputLayerUncacheBufferTest::kBuffer3 = + sp::make(4, 5, PIXEL_FORMAT_RGBA_8888, + AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | + AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN); const sp OutputLayerUncacheBufferTest::kFence = sp::make(); TEST_F(OutputLayerUncacheBufferTest, canUncacheAndReuseSlot) { @@ -1360,26 +1365,38 @@ TEST_F(OutputLayerUncacheBufferTest, canUncacheAndReuseSlot) { /*zIsOverridden*/ false, /*isPeekingThrough*/ false); Mock::VerifyAndClearExpectations(&mHwcLayer); - // buffer slots are cleared in HWC - std::vector slotsToClear = {0, 1}; - EXPECT_CALL(mHwcLayer, - setBufferSlotsToClear(/*slotsToClear*/ slotsToClear, /*activeBufferSlot*/ 1)); - mOutputLayer.uncacheBuffers({kBuffer1->getId(), kBuffer2->getId()}); + // Buffer3 is stored in slot 2 + mLayerFEState.buffer = kBuffer3; + EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 2, kBuffer3, kFence)); + mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, + /*zIsOverridden*/ false, /*isPeekingThrough*/ false); Mock::VerifyAndClearExpectations(&mHwcLayer); - // rather than allocating a new slot, the active buffer slot (slot 1) is reused first to free - // the memory as soon as possible - mLayerFEState.buffer = kBuffer1; - EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 1, kBuffer1, kFence)); + // Buffer2 becomes the active buffer again (with a nullptr) and reuses slot 1 + mLayerFEState.buffer = kBuffer2; + sp nullBuffer = nullptr; + EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 1, nullBuffer, kFence)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); Mock::VerifyAndClearExpectations(&mHwcLayer); - // rather than allocating a new slot, slot 0 is reused - mLayerFEState.buffer = kBuffer2; - EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 0, kBuffer2, kFence)); + // Buffer slots are cleared + std::vector slotsToClear = {0, 2, 1}; // order doesn't matter + EXPECT_CALL(mHwcLayer, setBufferSlotsToClear(slotsToClear, /*activeBufferSlot*/ 1)); + // Uncache the active buffer in between other buffers to exercise correct algorithmic behavior. + mOutputLayer.uncacheBuffers({kBuffer1->getId(), kBuffer2->getId(), kBuffer3->getId()}); + Mock::VerifyAndClearExpectations(&mHwcLayer); + + // Buffer1 becomes active again, and rather than allocating a new slot, or re-using slot 0, + // the active buffer slot (slot 1 for Buffer2) is reused first, which allows HWC to free the + // memory for the active buffer. Note: slot 1 is different from the first and last buffer slot + // requested to be cleared in slotsToClear (slot 1), above, indicating that the algorithm + // correctly identifies the active buffer as the buffer in slot 1, despite ping-ponging. + mLayerFEState.buffer = kBuffer1; + EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 1, kBuffer1, kFence)); mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0, /*zIsOverridden*/ false, /*isPeekingThrough*/ false); + Mock::VerifyAndClearExpectations(&mHwcLayer); } /* -- GitLab From ac40c1e284453e10a7c2b6f2c6090fc4be322398 Mon Sep 17 00:00:00 2001 From: Ian Elliott Date: Wed, 21 Dec 2022 23:16:21 +0000 Subject: [PATCH 0646/1310] Revert "libvulkan: Implement EXT_swapchain_maintenance1" This reverts commit 08c7bb1fdb33d6ad34ba1b2de3ac0579750456fe. Reason for revert: Possibly the cause of b/263412882 (will revert and see if that clears up the problem) Change-Id: If217e36c67fcea8b5270dbf20b0c13c42d1da09a --- vulkan/libvulkan/driver.cpp | 56 +-- vulkan/libvulkan/driver_gen.cpp | 20 - vulkan/libvulkan/driver_gen.h | 4 - vulkan/libvulkan/swapchain.cpp | 624 +++++++++-------------------- vulkan/libvulkan/swapchain.h | 1 - vulkan/scripts/driver_generator.py | 6 - 6 files changed, 183 insertions(+), 528 deletions(-) diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp index b7e19d4296..9ed992b230 100644 --- a/vulkan/libvulkan/driver.cpp +++ b/vulkan/libvulkan/driver.cpp @@ -636,7 +636,6 @@ void CreateInfoWrapper::FilterExtension(const char* name) { case ProcHook::EXT_swapchain_colorspace: case ProcHook::KHR_get_surface_capabilities2: case ProcHook::GOOGLE_surfaceless_query: - case ProcHook::EXT_surface_maintenance1: hook_extensions_.set(ext_bit); // return now as these extensions do not require HAL support return; @@ -658,11 +657,9 @@ void CreateInfoWrapper::FilterExtension(const char* name) { case ProcHook::KHR_shared_presentable_image: case ProcHook::KHR_swapchain: case ProcHook::EXT_hdr_metadata: - case ProcHook::EXT_swapchain_maintenance1: case ProcHook::ANDROID_external_memory_android_hardware_buffer: case ProcHook::ANDROID_native_buffer: case ProcHook::GOOGLE_display_timing: - case ProcHook::KHR_external_fence_fd: case ProcHook::EXTENSION_CORE_1_0: case ProcHook::EXTENSION_CORE_1_1: case ProcHook::EXTENSION_CORE_1_2: @@ -693,22 +690,16 @@ void CreateInfoWrapper::FilterExtension(const char* name) { ext_bit = ProcHook::ANDROID_native_buffer; break; case ProcHook::KHR_incremental_present: - case ProcHook::KHR_shared_presentable_image: case ProcHook::GOOGLE_display_timing: + case ProcHook::KHR_shared_presentable_image: hook_extensions_.set(ext_bit); // return now as these extensions do not require HAL support return; - case ProcHook::EXT_swapchain_maintenance1: - // map VK_KHR_swapchain_maintenance1 to KHR_external_fence_fd - name = VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME; - ext_bit = ProcHook::KHR_external_fence_fd; - break; case ProcHook::EXT_hdr_metadata: case ProcHook::KHR_bind_memory2: hook_extensions_.set(ext_bit); break; case ProcHook::ANDROID_external_memory_android_hardware_buffer: - case ProcHook::KHR_external_fence_fd: case ProcHook::EXTENSION_UNKNOWN: // Extensions we don't need to do anything about at this level break; @@ -724,7 +715,6 @@ void CreateInfoWrapper::FilterExtension(const char* name) { case ProcHook::KHR_surface_protected_capabilities: case ProcHook::EXT_debug_report: case ProcHook::EXT_swapchain_colorspace: - case ProcHook::EXT_surface_maintenance1: case ProcHook::GOOGLE_surfaceless_query: case ProcHook::ANDROID_native_buffer: case ProcHook::EXTENSION_CORE_1_0: @@ -761,8 +751,6 @@ void CreateInfoWrapper::FilterExtension(const char* name) { if (ext_bit != ProcHook::EXTENSION_UNKNOWN) { if (ext_bit == ProcHook::ANDROID_native_buffer) hook_extensions_.set(ProcHook::KHR_swapchain); - if (ext_bit == ProcHook::KHR_external_fence_fd) - hook_extensions_.set(ProcHook::EXT_swapchain_maintenance1); hal_extensions_.set(ext_bit); } @@ -952,9 +940,6 @@ VkResult EnumerateInstanceExtensionProperties( VK_KHR_GET_SURFACE_CAPABILITIES_2_SPEC_VERSION}); loader_extensions.push_back({VK_GOOGLE_SURFACELESS_QUERY_EXTENSION_NAME, VK_GOOGLE_SURFACELESS_QUERY_SPEC_VERSION}); - loader_extensions.push_back({ - VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME, - VK_EXT_SURFACE_MAINTENANCE_1_SPEC_VERSION}); static const VkExtensionProperties loader_debug_report_extension = { VK_EXT_DEBUG_REPORT_EXTENSION_NAME, VK_EXT_DEBUG_REPORT_SPEC_VERSION, @@ -1087,33 +1072,6 @@ VkResult GetAndroidNativeBufferSpecVersion9Support( return result; } -bool CanSupportSwapchainMaintenance1Extension(VkPhysicalDevice physicalDevice) { - const auto& driver = GetData(physicalDevice).driver; - if (!driver.GetPhysicalDeviceExternalFenceProperties) - return false; - - // Requires support for external fences imported from sync fds. - // This is _almost_ universal on Android, but may be missing on - // some extremely old drivers, or on strange implementations like - // cuttlefish. - VkPhysicalDeviceExternalFenceInfo fenceInfo = { - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO, - nullptr, - VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT - }; - VkExternalFenceProperties fenceProperties = { - VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES, - nullptr, - 0, 0, 0 - }; - - GetPhysicalDeviceExternalFenceProperties(physicalDevice, &fenceInfo, &fenceProperties); - if (fenceProperties.externalFenceFeatures & VK_EXTERNAL_FENCE_FEATURE_IMPORTABLE_BIT) - return true; - - return false; -} - VkResult EnumerateDeviceExtensionProperties( VkPhysicalDevice physicalDevice, const char* pLayerName, @@ -1191,12 +1149,6 @@ VkResult EnumerateDeviceExtensionProperties( VK_EXT_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_SPEC_VERSION}); } - if (CanSupportSwapchainMaintenance1Extension(physicalDevice)) { - loader_extensions.push_back({ - VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME, - VK_EXT_SWAPCHAIN_MAINTENANCE_1_SPEC_VERSION}); - } - // enumerate our extensions first if (!pLayerName && pProperties) { uint32_t count = std::min( @@ -1692,12 +1644,6 @@ void GetPhysicalDeviceFeatures2(VkPhysicalDevice physicalDevice, imageCompressionControlSwapchainInChain = true; } break; - case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT: { - auto smf = reinterpret_cast( - pFeats); - smf->swapchainMaintenance1 = true; - } break; - default: break; } diff --git a/vulkan/libvulkan/driver_gen.cpp b/vulkan/libvulkan/driver_gen.cpp index 798af5c6a6..de98aa7e61 100644 --- a/vulkan/libvulkan/driver_gen.cpp +++ b/vulkan/libvulkan/driver_gen.cpp @@ -162,15 +162,6 @@ VKAPI_ATTR void checkedGetDeviceQueue2(VkDevice device, const VkDeviceQueueInfo2 } } -VKAPI_ATTR VkResult checkedReleaseSwapchainImagesEXT(VkDevice device, const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo) { - if (GetData(device).hook_extensions[ProcHook::EXT_swapchain_maintenance1]) { - return ReleaseSwapchainImagesEXT(device, pReleaseInfo); - } else { - Logger(device).Err(device, "VK_EXT_swapchain_maintenance1 not enabled. vkReleaseSwapchainImagesEXT not executed."); - return VK_SUCCESS; - } -} - // clang-format on const ProcHook g_proc_hooks[] = { @@ -553,13 +544,6 @@ const ProcHook g_proc_hooks[] = { reinterpret_cast(QueueSubmit), nullptr, }, - { - "vkReleaseSwapchainImagesEXT", - ProcHook::DEVICE, - ProcHook::EXT_swapchain_maintenance1, - reinterpret_cast(ReleaseSwapchainImagesEXT), - reinterpret_cast(checkedReleaseSwapchainImagesEXT), - }, { "vkSetHdrMetadataEXT", ProcHook::DEVICE, @@ -596,8 +580,6 @@ ProcHook::Extension GetProcHookExtension(const char* name) { if (strcmp(name, "VK_KHR_surface") == 0) return ProcHook::KHR_surface; if (strcmp(name, "VK_KHR_surface_protected_capabilities") == 0) return ProcHook::KHR_surface_protected_capabilities; if (strcmp(name, "VK_KHR_swapchain") == 0) return ProcHook::KHR_swapchain; - if (strcmp(name, "VK_EXT_swapchain_maintenance1") == 0) return ProcHook::EXT_swapchain_maintenance1; - if (strcmp(name, "VK_EXT_surface_maintenance1") == 0) return ProcHook::EXT_surface_maintenance1; if (strcmp(name, "VK_ANDROID_external_memory_android_hardware_buffer") == 0) return ProcHook::ANDROID_external_memory_android_hardware_buffer; if (strcmp(name, "VK_KHR_bind_memory2") == 0) return ProcHook::KHR_bind_memory2; if (strcmp(name, "VK_KHR_get_physical_device_properties2") == 0) return ProcHook::KHR_get_physical_device_properties2; @@ -605,7 +587,6 @@ ProcHook::Extension GetProcHookExtension(const char* name) { if (strcmp(name, "VK_KHR_external_memory_capabilities") == 0) return ProcHook::KHR_external_memory_capabilities; if (strcmp(name, "VK_KHR_external_semaphore_capabilities") == 0) return ProcHook::KHR_external_semaphore_capabilities; if (strcmp(name, "VK_KHR_external_fence_capabilities") == 0) return ProcHook::KHR_external_fence_capabilities; - if (strcmp(name, "VK_KHR_external_fence_fd") == 0) return ProcHook::KHR_external_fence_fd; // clang-format on return ProcHook::EXTENSION_UNKNOWN; } @@ -685,7 +666,6 @@ bool InitDriverTable(VkDevice dev, INIT_PROC(true, dev, CreateImage); INIT_PROC(true, dev, DestroyImage); INIT_PROC(true, dev, AllocateCommandBuffers); - INIT_PROC_EXT(KHR_external_fence_fd, true, dev, ImportFenceFdKHR); INIT_PROC(false, dev, BindImageMemory2); INIT_PROC_EXT(KHR_bind_memory2, true, dev, BindImageMemory2KHR); INIT_PROC(false, dev, GetDeviceQueue2); diff --git a/vulkan/libvulkan/driver_gen.h b/vulkan/libvulkan/driver_gen.h index 31ba04ba1f..2f60086a27 100644 --- a/vulkan/libvulkan/driver_gen.h +++ b/vulkan/libvulkan/driver_gen.h @@ -49,8 +49,6 @@ struct ProcHook { KHR_surface, KHR_surface_protected_capabilities, KHR_swapchain, - EXT_swapchain_maintenance1, - EXT_surface_maintenance1, ANDROID_external_memory_android_hardware_buffer, KHR_bind_memory2, KHR_get_physical_device_properties2, @@ -58,7 +56,6 @@ struct ProcHook { KHR_external_memory_capabilities, KHR_external_semaphore_capabilities, KHR_external_fence_capabilities, - KHR_external_fence_fd, EXTENSION_CORE_1_0, EXTENSION_CORE_1_1, @@ -121,7 +118,6 @@ struct DeviceDriverTable { PFN_vkCreateImage CreateImage; PFN_vkDestroyImage DestroyImage; PFN_vkAllocateCommandBuffers AllocateCommandBuffers; - PFN_vkImportFenceFdKHR ImportFenceFdKHR; PFN_vkBindImageMemory2 BindImageMemory2; PFN_vkBindImageMemory2KHR BindImageMemory2KHR; PFN_vkGetDeviceQueue2 GetDeviceQueue2; diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp index 0dac6ad1aa..1bf7abb758 100644 --- a/vulkan/libvulkan/swapchain.cpp +++ b/vulkan/libvulkan/swapchain.cpp @@ -291,9 +291,6 @@ struct Swapchain { release_fence(-1), dequeued(false) {} VkImage image; - // If the image is bound to memory, an sp to the underlying gralloc buffer. - // Otherwise, nullptr; the image will be bound to memory as part of - // AcquireNextImage. android::sp buffer; // The fence is only valid when the buffer is dequeued, and should be // -1 any other time. When valid, we own the fd, and must ensure it is @@ -655,40 +652,100 @@ VkResult GetPhysicalDeviceSurfaceCapabilitiesKHR( VkSurfaceCapabilitiesKHR* capabilities) { ATRACE_CALL(); - // Implement in terms of GetPhysicalDeviceSurfaceCapabilities2KHR + int err; + int width, height; + int transform_hint; + int max_buffer_count; + if (surface == VK_NULL_HANDLE) { + const InstanceData& instance_data = GetData(pdev); + ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query; + bool surfaceless_enabled = + instance_data.hook_extensions.test(surfaceless); + if (!surfaceless_enabled) { + // It is an error to pass a surface==VK_NULL_HANDLE unless the + // VK_GOOGLE_surfaceless_query extension is enabled + return VK_ERROR_SURFACE_LOST_KHR; + } + // Support for VK_GOOGLE_surfaceless_query. The primary purpose of this + // extension for this function is for + // VkSurfaceProtectedCapabilitiesKHR::supportsProtected. The following + // four values cannot be known without a surface. Default values will + // be supplied anyway, but cannot be relied upon. + width = 0xFFFFFFFF; + height = 0xFFFFFFFF; + transform_hint = VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR; + capabilities->minImageCount = 0xFFFFFFFF; + capabilities->maxImageCount = 0xFFFFFFFF; + } else { + ANativeWindow* window = SurfaceFromHandle(surface)->window.get(); - VkPhysicalDeviceSurfaceInfo2KHR info2 = { - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR, - nullptr, - surface - }; + err = window->query(window, NATIVE_WINDOW_DEFAULT_WIDTH, &width); + if (err != android::OK) { + ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)", + strerror(-err), err); + return VK_ERROR_SURFACE_LOST_KHR; + } + err = window->query(window, NATIVE_WINDOW_DEFAULT_HEIGHT, &height); + if (err != android::OK) { + ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)", + strerror(-err), err); + return VK_ERROR_SURFACE_LOST_KHR; + } - VkSurfaceCapabilities2KHR caps2 = { - VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR, - nullptr, - {}, - }; + err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT, + &transform_hint); + if (err != android::OK) { + ALOGE("NATIVE_WINDOW_TRANSFORM_HINT query failed: %s (%d)", + strerror(-err), err); + return VK_ERROR_SURFACE_LOST_KHR; + } - VkResult result = GetPhysicalDeviceSurfaceCapabilities2KHR(pdev, &info2, &caps2); - *capabilities = caps2.surfaceCapabilities; - return result; -} + err = window->query(window, NATIVE_WINDOW_MAX_BUFFER_COUNT, + &max_buffer_count); + if (err != android::OK) { + ALOGE("NATIVE_WINDOW_MAX_BUFFER_COUNT query failed: %s (%d)", + strerror(-err), err); + return VK_ERROR_SURFACE_LOST_KHR; + } + capabilities->minImageCount = std::min(max_buffer_count, 3); + capabilities->maxImageCount = static_cast(max_buffer_count); + } -// Does the call-twice and VK_INCOMPLETE handling for querying lists -// of things, where we already have the full set built in a vector. -template -VkResult CopyWithIncomplete(std::vector const& things, - T* callerPtr, uint32_t* callerCount) { - VkResult result = VK_SUCCESS; - if (callerPtr) { - if (things.size() > *callerCount) - result = VK_INCOMPLETE; - *callerCount = std::min(uint32_t(things.size()), *callerCount); - std::copy(things.begin(), things.begin() + *callerCount, callerPtr); - } else { - *callerCount = things.size(); + capabilities->currentExtent = + VkExtent2D{static_cast(width), static_cast(height)}; + + // TODO(http://b/134182502): Figure out what the max extent should be. + capabilities->minImageExtent = VkExtent2D{1, 1}; + capabilities->maxImageExtent = VkExtent2D{4096, 4096}; + + if (capabilities->maxImageExtent.height < + capabilities->currentExtent.height) { + capabilities->maxImageExtent.height = + capabilities->currentExtent.height; } - return result; + + if (capabilities->maxImageExtent.width < + capabilities->currentExtent.width) { + capabilities->maxImageExtent.width = capabilities->currentExtent.width; + } + + capabilities->maxImageArrayLayers = 1; + + capabilities->supportedTransforms = kSupportedTransforms; + capabilities->currentTransform = + TranslateNativeToVulkanTransform(transform_hint); + + // On Android, window composition is a WindowManager property, not something + // associated with the bufferqueue. It can't be changed from here. + capabilities->supportedCompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; + + capabilities->supportedUsageFlags = + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT; + + return VK_SUCCESS; } VKAPI_ATTR @@ -805,7 +862,21 @@ VkResult GetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice pdev, // Android users. This includes the ANGLE team (a layered implementation of // OpenGL-ES). - return CopyWithIncomplete(all_formats, formats, count); + VkResult result = VK_SUCCESS; + if (formats) { + uint32_t transfer_count = all_formats.size(); + if (transfer_count > *count) { + transfer_count = *count; + result = VK_INCOMPLETE; + } + std::copy(all_formats.begin(), all_formats.begin() + transfer_count, + formats); + *count = transfer_count; + } else { + *count = all_formats.size(); + } + + return result; } VKAPI_ATTR @@ -815,134 +886,19 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR( VkSurfaceCapabilities2KHR* pSurfaceCapabilities) { ATRACE_CALL(); - auto surface = pSurfaceInfo->surface; - auto capabilities = &pSurfaceCapabilities->surfaceCapabilities; - - VkSurfacePresentModeEXT const *pPresentMode = nullptr; - for (auto pNext = reinterpret_cast(pSurfaceInfo->pNext); - pNext; pNext = reinterpret_cast(pNext->pNext)) { - switch (pNext->sType) { - case VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_EXT: - pPresentMode = reinterpret_cast(pNext); - break; - - default: - break; - } - } + VkResult result = GetPhysicalDeviceSurfaceCapabilitiesKHR( + physicalDevice, pSurfaceInfo->surface, + &pSurfaceCapabilities->surfaceCapabilities); - int err; - int width, height; - int transform_hint; - int max_buffer_count; - if (surface == VK_NULL_HANDLE) { - const InstanceData& instance_data = GetData(physicalDevice); - ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query; - bool surfaceless_enabled = - instance_data.hook_extensions.test(surfaceless); - if (!surfaceless_enabled) { - // It is an error to pass a surface==VK_NULL_HANDLE unless the - // VK_GOOGLE_surfaceless_query extension is enabled - return VK_ERROR_SURFACE_LOST_KHR; - } - // Support for VK_GOOGLE_surfaceless_query. The primary purpose of this - // extension for this function is for - // VkSurfaceProtectedCapabilitiesKHR::supportsProtected. The following - // four values cannot be known without a surface. Default values will - // be supplied anyway, but cannot be relied upon. - width = 0xFFFFFFFF; - height = 0xFFFFFFFF; - transform_hint = VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR; - capabilities->minImageCount = 0xFFFFFFFF; - capabilities->maxImageCount = 0xFFFFFFFF; - } else { - ANativeWindow* window = SurfaceFromHandle(surface)->window.get(); + VkSurfaceCapabilities2KHR* caps = pSurfaceCapabilities; + while (caps->pNext) { + caps = reinterpret_cast(caps->pNext); - err = window->query(window, NATIVE_WINDOW_DEFAULT_WIDTH, &width); - if (err != android::OK) { - ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)", - strerror(-err), err); - return VK_ERROR_SURFACE_LOST_KHR; - } - err = window->query(window, NATIVE_WINDOW_DEFAULT_HEIGHT, &height); - if (err != android::OK) { - ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)", - strerror(-err), err); - return VK_ERROR_SURFACE_LOST_KHR; - } - - err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT, - &transform_hint); - if (err != android::OK) { - ALOGE("NATIVE_WINDOW_TRANSFORM_HINT query failed: %s (%d)", - strerror(-err), err); - return VK_ERROR_SURFACE_LOST_KHR; - } - - err = window->query(window, NATIVE_WINDOW_MAX_BUFFER_COUNT, - &max_buffer_count); - if (err != android::OK) { - ALOGE("NATIVE_WINDOW_MAX_BUFFER_COUNT query failed: %s (%d)", - strerror(-err), err); - return VK_ERROR_SURFACE_LOST_KHR; - } - - if (pPresentMode && IsSharedPresentMode(pPresentMode->presentMode)) { - capabilities->minImageCount = 1; - capabilities->maxImageCount = 1; - } else if (pPresentMode && pPresentMode->presentMode == VK_PRESENT_MODE_MAILBOX_KHR) { - // TODO: use undequeued buffer requirement for more precise bound - capabilities->minImageCount = std::min(max_buffer_count, 4); - capabilities->maxImageCount = static_cast(max_buffer_count); - } else { - // TODO: if we're able to, provide better bounds on the number of buffers - // for other modes as well. - capabilities->minImageCount = std::min(max_buffer_count, 3); - capabilities->maxImageCount = static_cast(max_buffer_count); - } - } - - capabilities->currentExtent = - VkExtent2D{static_cast(width), static_cast(height)}; - - // TODO(http://b/134182502): Figure out what the max extent should be. - capabilities->minImageExtent = VkExtent2D{1, 1}; - capabilities->maxImageExtent = VkExtent2D{4096, 4096}; - - if (capabilities->maxImageExtent.height < - capabilities->currentExtent.height) { - capabilities->maxImageExtent.height = - capabilities->currentExtent.height; - } - - if (capabilities->maxImageExtent.width < - capabilities->currentExtent.width) { - capabilities->maxImageExtent.width = capabilities->currentExtent.width; - } - - capabilities->maxImageArrayLayers = 1; - - capabilities->supportedTransforms = kSupportedTransforms; - capabilities->currentTransform = - TranslateNativeToVulkanTransform(transform_hint); - - // On Android, window composition is a WindowManager property, not something - // associated with the bufferqueue. It can't be changed from here. - capabilities->supportedCompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; - - capabilities->supportedUsageFlags = - VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | - VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | - VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT; - - for (auto pNext = reinterpret_cast(pSurfaceCapabilities->pNext); - pNext; pNext = reinterpret_cast(pNext->pNext)) { - - switch (pNext->sType) { + switch (caps->sType) { case VK_STRUCTURE_TYPE_SHARED_PRESENT_SURFACE_CAPABILITIES_KHR: { VkSharedPresentSurfaceCapabilitiesKHR* shared_caps = - reinterpret_cast(pNext); + reinterpret_cast( + caps); // Claim same set of usage flags are supported for // shared present modes as for other modes. shared_caps->sharedPresentSupportedUsageFlags = @@ -952,64 +908,17 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR( case VK_STRUCTURE_TYPE_SURFACE_PROTECTED_CAPABILITIES_KHR: { VkSurfaceProtectedCapabilitiesKHR* protected_caps = - reinterpret_cast(pNext); + reinterpret_cast(caps); protected_caps->supportsProtected = VK_TRUE; } break; - case VK_STRUCTURE_TYPE_SURFACE_PRESENT_SCALING_CAPABILITIES_EXT: { - VkSurfacePresentScalingCapabilitiesEXT* scaling_caps = - reinterpret_cast(pNext); - // By default, Android stretches the buffer to fit the window, - // without preserving aspect ratio. Other modes are technically possible - // but consult with CoGS team before exposing them here! - scaling_caps->supportedPresentScaling = VK_PRESENT_SCALING_STRETCH_BIT_EXT; - - // Since we always scale, we don't support any gravity. - scaling_caps->supportedPresentGravityX = 0; - scaling_caps->supportedPresentGravityY = 0; - - // Scaled image limits are just the basic image limits - scaling_caps->minScaledImageExtent = capabilities->minImageExtent; - scaling_caps->maxScaledImageExtent = capabilities->maxImageExtent; - } break; - - case VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_COMPATIBILITY_EXT: { - VkSurfacePresentModeCompatibilityEXT* mode_caps = - reinterpret_cast(pNext); - - ALOG_ASSERT(pPresentMode, - "querying VkSurfacePresentModeCompatibilityEXT " - "requires VkSurfacePresentModeEXT to be provided"); - std::vector compatibleModes; - compatibleModes.push_back(pPresentMode->presentMode); - - switch (pPresentMode->presentMode) { - // Shared modes are both compatible with each other. - case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR: - compatibleModes.push_back(VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR); - break; - case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR: - compatibleModes.push_back(VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR); - break; - default: - // Other modes are only compatible with themselves. - // TODO: consider whether switching between FIFO and MAILBOX is reasonable - break; - } - - // Note: this does not generate VK_INCOMPLETE since we're nested inside - // a larger query and there would be no way to determine exactly where it came from. - CopyWithIncomplete(compatibleModes, mode_caps->pPresentModes, - &mode_caps->presentModeCount); - } break; - default: // Ignore all other extension structs break; } } - return VK_SUCCESS; + return result; } VKAPI_ATTR @@ -1168,7 +1077,18 @@ VkResult GetPhysicalDeviceSurfacePresentModesKHR(VkPhysicalDevice pdev, present_modes.push_back(VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR); } - return CopyWithIncomplete(present_modes, modes, count); + uint32_t num_modes = uint32_t(present_modes.size()); + + VkResult result = VK_SUCCESS; + if (modes) { + if (*count < num_modes) + result = VK_INCOMPLETE; + *count = std::min(*count, num_modes); + std::copy_n(present_modes.data(), *count, modes); + } else { + *count = num_modes; + } + return result; } VKAPI_ATTR @@ -1454,7 +1374,8 @@ VkResult CreateSwapchainKHR(VkDevice device, } VkSwapchainImageUsageFlagsANDROID swapchain_image_usage = 0; - if (IsSharedPresentMode(create_info->presentMode)) { + if (create_info->presentMode == VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR || + create_info->presentMode == VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR) { swapchain_image_usage |= VK_SWAPCHAIN_IMAGE_USAGE_SHARED_BIT_ANDROID; err = native_window_set_shared_buffer_mode(window, true); if (err != android::OK) { @@ -1604,6 +1525,8 @@ VkResult CreateSwapchainKHR(VkDevice device, Swapchain* swapchain = new (mem) Swapchain(surface, num_images, create_info->presentMode, TranslateVulkanToNativeTransform(create_info->preTransform)); + // -- Dequeue all buffers and create a VkImage for each -- + // Any failures during or after this must cancel the dequeued buffers. VkSwapchainImageCreateInfoANDROID swapchain_image_create = { #pragma clang diagnostic push @@ -1620,18 +1543,13 @@ VkResult CreateSwapchainKHR(VkDevice device, #pragma clang diagnostic pop .pNext = &swapchain_image_create, }; - VkImageCreateInfo image_create = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, - .pNext = nullptr, + .pNext = &image_native_buffer, .flags = createProtectedSwapchain ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u, .imageType = VK_IMAGE_TYPE_2D, .format = create_info->imageFormat, - .extent = { - create_info->imageExtent.width, - create_info->imageExtent.height, - 1 - }, + .extent = {0, 0, 1}, .mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT, @@ -1642,87 +1560,60 @@ VkResult CreateSwapchainKHR(VkDevice device, .pQueueFamilyIndices = create_info->pQueueFamilyIndices, }; - // Note: don't do deferred allocation for shared present modes. There's only one buffer - // involved so very little benefit. - if ((create_info->flags & VK_SWAPCHAIN_CREATE_DEFERRED_MEMORY_ALLOCATION_BIT_EXT) && - !IsSharedPresentMode(create_info->presentMode)) { - // Don't want to touch the underlying gralloc buffers yet; - // instead just create unbound VkImages which will later be bound to memory inside - // AcquireNextImage. - VkImageSwapchainCreateInfoKHR image_swapchain_create = { - .sType = VK_STRUCTURE_TYPE_IMAGE_SWAPCHAIN_CREATE_INFO_KHR, - .pNext = nullptr, - .swapchain = HandleFromSwapchain(swapchain), - }; - image_create.pNext = &image_swapchain_create; - - for (uint32_t i = 0; i < num_images; i++) { - Swapchain::Image& img = swapchain->images[i]; - img.buffer = nullptr; - img.dequeued = false; + for (uint32_t i = 0; i < num_images; i++) { + Swapchain::Image& img = swapchain->images[i]; - result = dispatch.CreateImage(device, &image_create, nullptr, &img.image); - if (result != VK_SUCCESS) { - ALOGD("vkCreateImage w/ for deferred swapchain image failed: %u", result); - break; + ANativeWindowBuffer* buffer; + err = window->dequeueBuffer(window, &buffer, &img.dequeue_fence); + if (err != android::OK) { + ALOGE("dequeueBuffer[%u] failed: %s (%d)", i, strerror(-err), err); + switch (-err) { + case ENOMEM: + result = VK_ERROR_OUT_OF_DEVICE_MEMORY; + break; + default: + result = VK_ERROR_SURFACE_LOST_KHR; + break; } + break; } - } else { - // -- Dequeue all buffers and create a VkImage for each -- - // Any failures during or after this must cancel the dequeued buffers. - - for (uint32_t i = 0; i < num_images; i++) { - Swapchain::Image& img = swapchain->images[i]; - - ANativeWindowBuffer* buffer; - err = window->dequeueBuffer(window, &buffer, &img.dequeue_fence); - if (err != android::OK) { - ALOGE("dequeueBuffer[%u] failed: %s (%d)", i, strerror(-err), err); - switch (-err) { - case ENOMEM: - result = VK_ERROR_OUT_OF_DEVICE_MEMORY; - break; - default: - result = VK_ERROR_SURFACE_LOST_KHR; - break; - } - break; - } - img.buffer = buffer; - img.dequeued = true; - - image_native_buffer.handle = img.buffer->handle; - image_native_buffer.stride = img.buffer->stride; - image_native_buffer.format = img.buffer->format; - image_native_buffer.usage = int(img.buffer->usage); - android_convertGralloc0To1Usage(int(img.buffer->usage), - &image_native_buffer.usage2.producer, - &image_native_buffer.usage2.consumer); - image_native_buffer.usage3 = img.buffer->usage; - image_create.pNext = &image_native_buffer; - - ATRACE_BEGIN("CreateImage"); - result = - dispatch.CreateImage(device, &image_create, nullptr, &img.image); - ATRACE_END(); - if (result != VK_SUCCESS) { - ALOGD("vkCreateImage w/ native buffer failed: %u", result); - break; - } + img.buffer = buffer; + img.dequeued = true; + + image_create.extent = + VkExtent3D{static_cast(img.buffer->width), + static_cast(img.buffer->height), + 1}; + image_native_buffer.handle = img.buffer->handle; + image_native_buffer.stride = img.buffer->stride; + image_native_buffer.format = img.buffer->format; + image_native_buffer.usage = int(img.buffer->usage); + android_convertGralloc0To1Usage(int(img.buffer->usage), + &image_native_buffer.usage2.producer, + &image_native_buffer.usage2.consumer); + image_native_buffer.usage3 = img.buffer->usage; + + ATRACE_BEGIN("CreateImage"); + result = + dispatch.CreateImage(device, &image_create, nullptr, &img.image); + ATRACE_END(); + if (result != VK_SUCCESS) { + ALOGD("vkCreateImage w/ native buffer failed: %u", result); + break; } + } - // -- Cancel all buffers, returning them to the queue -- - // If an error occurred before, also destroy the VkImage and release the - // buffer reference. Otherwise, we retain a strong reference to the buffer. - for (uint32_t i = 0; i < num_images; i++) { - Swapchain::Image& img = swapchain->images[i]; - if (img.dequeued) { - if (!swapchain->shared) { - window->cancelBuffer(window, img.buffer.get(), - img.dequeue_fence); - img.dequeue_fence = -1; - img.dequeued = false; - } + // -- Cancel all buffers, returning them to the queue -- + // If an error occurred before, also destroy the VkImage and release the + // buffer reference. Otherwise, we retain a strong reference to the buffer. + for (uint32_t i = 0; i < num_images; i++) { + Swapchain::Image& img = swapchain->images[i]; + if (img.dequeued) { + if (!swapchain->shared) { + window->cancelBuffer(window, img.buffer.get(), + img.dequeue_fence); + img.dequeue_fence = -1; + img.dequeued = false; } } } @@ -1845,64 +1736,6 @@ VkResult AcquireNextImageKHR(VkDevice device, break; } } - - // If this is a deferred alloc swapchain, this may be the first time we've - // seen a particular buffer. If so, there should be an empty slot. Find it, - // and bind the gralloc buffer to the VkImage for that slot. If there is no - // empty slot, then we dequeued an unexpected buffer. Non-deferred swapchains - // will also take this path, but will never have an empty slot since we - // populated them all upfront. - if (idx == swapchain.num_images) { - for (idx = 0; idx < swapchain.num_images; idx++) { - if (!swapchain.images[idx].buffer) { - // Note: this structure is technically required for - // Vulkan correctness, even though the driver is probably going - // to use everything from the VkNativeBufferANDROID below. - // This is kindof silly, but it's how we did the ANB - // side of VK_KHR_swapchain v69, so we're stuck with it unless - // we want to go tinkering with the ANB spec some more. - VkBindImageMemorySwapchainInfoKHR bimsi = { - .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_SWAPCHAIN_INFO_KHR, - .pNext = nullptr, - .swapchain = swapchain_handle, - .imageIndex = idx, - }; - VkNativeBufferANDROID nb = { - .sType = VK_STRUCTURE_TYPE_NATIVE_BUFFER_ANDROID, - .pNext = &bimsi, - .handle = buffer->handle, - .stride = buffer->stride, - .format = buffer->format, - .usage = int(buffer->usage), - }; - VkBindImageMemoryInfo bimi = { - .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO, - .pNext = &nb, - .image = swapchain.images[idx].image, - .memory = VK_NULL_HANDLE, - .memoryOffset = 0, - }; - result = GetData(device).driver.BindImageMemory2(device, 1, &bimi); - if (result != VK_SUCCESS) { - // This shouldn't really happen. If it does, something is probably - // unrecoverably wrong with the swapchain and its images. Cancel - // the buffer and declare the swapchain broken. - ALOGE("failed to do deferred gralloc buffer bind"); - window->cancelBuffer(window, buffer, fence_fd); - return VK_ERROR_OUT_OF_DATE_KHR; - } - - swapchain.images[idx].dequeued = true; - swapchain.images[idx].dequeue_fence = fence_fd; - swapchain.images[idx].buffer = buffer; - break; - } - } - } - - // The buffer doesn't match any slot. This shouldn't normally happen, but is - // possible if the bufferqueue is reconfigured behind libvulkan's back. If this - // happens, just declare the swapchain to be broken and the app will recreate it. if (idx == swapchain.num_images) { ALOGE("dequeueBuffer returned unrecognized buffer"); window->cancelBuffer(window, buffer, fence_fd); @@ -2028,32 +1861,12 @@ static void SetSwapchainFrameTimestamp(Swapchain &swapchain, const VkPresentTime } } -// EXT_swapchain_maintenance1 present mode change -static bool SetSwapchainPresentMode(ANativeWindow *window, VkPresentModeKHR mode) { - // There is no dynamic switching between non-shared present modes. - // All we support is switching between demand and continuous refresh. - if (!IsSharedPresentMode(mode)) - return true; - - int err = native_window_set_auto_refresh(window, - mode == VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR); - if (err != android::OK) { - ALOGE("native_window_set_auto_refresh() failed: %s (%d)", - strerror(-err), err); - return false; - } - - return true; -} - static VkResult PresentOneSwapchain( VkQueue queue, Swapchain& swapchain, uint32_t imageIndex, const VkPresentRegionKHR *pRegion, const VkPresentTimeGOOGLE *pTime, - VkFence presentFence, - const VkPresentModeKHR *pPresentMode, uint32_t waitSemaphoreCount, const VkSemaphore *pWaitSemaphores) { @@ -2084,33 +1897,12 @@ static VkResult PresentOneSwapchain( ANativeWindow* window = swapchain.surface.window.get(); if (swapchain_result == VK_SUCCESS) { - if (presentFence != VK_NULL_HANDLE) { - int fence_copy = fence < 0 ? -1 : dup(fence); - VkImportFenceFdInfoKHR iffi = { - VK_STRUCTURE_TYPE_IMPORT_FENCE_FD_INFO_KHR, - nullptr, - presentFence, - VK_FENCE_IMPORT_TEMPORARY_BIT, - VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT, - fence_copy, - }; - if (VK_SUCCESS != dispatch.ImportFenceFdKHR(device, &iffi) && fence_copy >= 0) { - // ImportFenceFdKHR takes ownership only if it succeeds - close(fence_copy); - } - } - if (pRegion) { SetSwapchainSurfaceDamage(window, pRegion); } if (pTime) { SetSwapchainFrameTimestamp(swapchain, pTime); } - if (pPresentMode) { - if (!SetSwapchainPresentMode(window, *pPresentMode)) - swapchain_result = WorstPresentResult(swapchain_result, - VK_ERROR_SURFACE_LOST_KHR); - } err = window->queueBuffer(window, img.buffer.get(), fence); // queueBuffer always closes fence, even on error @@ -2191,9 +1983,6 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) { // Look at the pNext chain for supported extension structs: const VkPresentRegionsKHR* present_regions = nullptr; const VkPresentTimesInfoGOOGLE* present_times = nullptr; - const VkSwapchainPresentFenceInfoEXT* present_fences = nullptr; - const VkSwapchainPresentModeInfoEXT* present_modes = nullptr; - const VkPresentRegionsKHR* next = reinterpret_cast(present_info->pNext); while (next) { @@ -2205,14 +1994,6 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) { present_times = reinterpret_cast(next); break; - case VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_FENCE_INFO_EXT: - present_fences = - reinterpret_cast(next); - break; - case VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_EXT: - present_modes = - reinterpret_cast(next); - break; default: ALOGV("QueuePresentKHR ignoring unrecognized pNext->sType = %x", next->sType); @@ -2228,15 +2009,6 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) { present_times->swapchainCount != present_info->swapchainCount, "VkPresentTimesInfoGOOGLE::swapchainCount != " "VkPresentInfo::swapchainCount"); - ALOGV_IF(present_fences && - present_fences->swapchainCount != present_info->swapchainCount, - "VkSwapchainPresentFenceInfoEXT::swapchainCount != " - "VkPresentInfo::swapchainCount"); - ALOGV_IF(present_modes && - present_modes->swapchainCount != present_info->swapchainCount, - "VkSwapchainPresentModeInfoEXT::swapchainCount != " - "VkPresentInfo::swapchainCount"); - const VkPresentRegionKHR* regions = (present_regions) ? present_regions->pRegions : nullptr; const VkPresentTimeGOOGLE* times = @@ -2252,8 +2024,6 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) { present_info->pImageIndices[sc], (regions && !swapchain.mailbox_mode) ? ®ions[sc] : nullptr, times ? ×[sc] : nullptr, - present_fences ? present_fences->pFences[sc] : VK_NULL_HANDLE, - present_modes ? &present_modes->pPresentModes[sc] : nullptr, present_info->waitSemaphoreCount, present_info->pWaitSemaphores); @@ -2478,35 +2248,5 @@ VkResult BindImageMemory2KHR(VkDevice device, out_bind_infos.empty() ? pBindInfos : out_bind_infos.data()); } -VKAPI_ATTR -VkResult ReleaseSwapchainImagesEXT(VkDevice /*device*/, - const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo) { - ATRACE_CALL(); - - Swapchain& swapchain = *SwapchainFromHandle(pReleaseInfo->swapchain); - ANativeWindow* window = swapchain.surface.window.get(); - - // If in shared present mode, don't actually release the image back to the BQ. - // Both sides share it forever. - if (swapchain.shared) - return VK_SUCCESS; - - for (uint32_t i = 0; i < pReleaseInfo->imageIndexCount; i++) { - Swapchain::Image& img = swapchain.images[pReleaseInfo->pImageIndices[i]]; - window->cancelBuffer(window, img.buffer.get(), img.dequeue_fence); - - // cancelBuffer has taken ownership of the dequeue fence - img.dequeue_fence = -1; - // if we're still holding a release fence, get rid of it now - if (img.release_fence >= 0) { - close(img.release_fence); - img.release_fence = -1; - } - img.dequeued = false; - } - - return VK_SUCCESS; -} - } // namespace driver } // namespace vulkan diff --git a/vulkan/libvulkan/swapchain.h b/vulkan/libvulkan/swapchain.h index 280fe9b5a3..4912ef1a33 100644 --- a/vulkan/libvulkan/swapchain.h +++ b/vulkan/libvulkan/swapchain.h @@ -46,7 +46,6 @@ VKAPI_ATTR VkResult GetPhysicalDeviceSurfaceCapabilities2KHR(VkPhysicalDevice ph VKAPI_ATTR VkResult GetPhysicalDeviceSurfaceFormats2KHR(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, uint32_t* pSurfaceFormatCount, VkSurfaceFormat2KHR* pSurfaceFormats); VKAPI_ATTR VkResult BindImageMemory2(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos); VKAPI_ATTR VkResult BindImageMemory2KHR(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos); -VKAPI_ATTR VkResult ReleaseSwapchainImagesEXT(VkDevice device, const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo); // clang-format on } // namespace driver diff --git a/vulkan/scripts/driver_generator.py b/vulkan/scripts/driver_generator.py index 78b550c202..af56764a21 100644 --- a/vulkan/scripts/driver_generator.py +++ b/vulkan/scripts/driver_generator.py @@ -35,8 +35,6 @@ _INTERCEPTED_EXTENSIONS = [ 'VK_KHR_surface', 'VK_KHR_surface_protected_capabilities', 'VK_KHR_swapchain', - 'VK_EXT_swapchain_maintenance1', - 'VK_EXT_surface_maintenance1', ] # Extensions known to vulkan::driver level. @@ -48,7 +46,6 @@ _KNOWN_EXTENSIONS = _INTERCEPTED_EXTENSIONS + [ 'VK_KHR_external_memory_capabilities', 'VK_KHR_external_semaphore_capabilities', 'VK_KHR_external_fence_capabilities', - 'VK_KHR_external_fence_fd', ] # Functions needed at vulkan::driver level. @@ -115,9 +112,6 @@ _NEEDED_COMMANDS = [ # For promoted VK_KHR_external_fence_capabilities 'vkGetPhysicalDeviceExternalFenceProperties', 'vkGetPhysicalDeviceExternalFencePropertiesKHR', - - # VK_KHR_swapchain_maintenance1 requirement - 'vkImportFenceFdKHR', ] # Functions intercepted at vulkan::driver level. -- GitLab From 39147ceecf38b27875939e603ad45d9f1d6bd6ea Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 15 Nov 2022 12:13:04 -0800 Subject: [PATCH 0647/1310] Add native MotionPredictor The native MotionPredictor will call neural net code to create motion predictions for the provided gesture stream. In this CL, a simple linear extrapolation model is provided in order to test the API. To enable prediction: adb shell setprop persist.input.enable_motion_prediction true Bug: 167946763 Test: atest InputTests inputflinger_tests libinput_tests Change-Id: Id8a23b728aeb844288d5b8daae0829d61d4c1482 --- include/input/MotionPredictor.h | 79 +++++++++++ libs/input/Android.bp | 4 +- libs/input/MotionPredictor.cpp | 156 ++++++++++++++++++++++ libs/input/tests/Android.bp | 2 + libs/input/tests/MotionPredictor_test.cpp | 121 +++++++++++++++++ 5 files changed, 361 insertions(+), 1 deletion(-) create mode 100644 include/input/MotionPredictor.h create mode 100644 libs/input/MotionPredictor.cpp create mode 100644 libs/input/tests/MotionPredictor_test.cpp diff --git a/include/input/MotionPredictor.h b/include/input/MotionPredictor.h new file mode 100644 index 0000000000..6c07849c15 --- /dev/null +++ b/include/input/MotionPredictor.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include & values) { + LOG_ALWAYS_FATAL_IF(values.size() != mCount, + "Attempt to write %zu values to \"%s\" gesture property, which holds %zu.", + values.size(), mName.c_str(), mCount); + std::copy(values.begin(), values.end(), dataPointer); + if (mSetter != nullptr) { + mSetter(mHandlerData); + } +} diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.h b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h new file mode 100644 index 0000000000..4bebd469b2 --- /dev/null +++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h @@ -0,0 +1,97 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include "include/gestures.h" + +namespace android { + +// Struct containing functions that wrap PropertyProvider in a C-compatible interface. +extern const GesturesPropProvider gesturePropProvider; + +// Implementation of a gestures library property provider, which provides configuration parameters. +class PropertyProvider { +public: + bool hasProperty(const std::string name) const; + GesturesProp& getProperty(const std::string name); + + // Methods to be called by the gestures library: + GesturesProp* createIntArrayProperty(const std::string name, int* loc, size_t count, + const int* init); + GesturesProp* createBoolArrayProperty(const std::string name, GesturesPropBool* loc, + size_t count, const GesturesPropBool* init); + GesturesProp* createRealArrayProperty(const std::string name, double* loc, size_t count, + const double* init); + GesturesProp* createStringProperty(const std::string name, const char** loc, + const char* const init); + + void freeProperty(GesturesProp* prop); + +private: + std::map mProperties; +}; + +} // namespace android + +// Represents a single gesture property. +// +// Pointers to this struct will be used by the gestures library (though it can never deference +// them). The library's API requires this to be in the top-level namespace. +struct GesturesProp { +public: + template + GesturesProp(std::string name, T* dataPointer, size_t count, const T* initialValues); + GesturesProp(std::string name, const char** dataPointer, const char* const initialValue); + + std::string getName() const { return mName; } + + size_t getCount() const { return mCount; } + + void registerHandlers(void* handlerData, GesturesPropGetHandler getter, + GesturesPropSetHandler setter); + + std::vector getIntValues() const; + std::vector getBoolValues() const; + std::vector getRealValues() const; + std::string getStringValue() const; + + void setIntValues(const std::vector& values); + void setBoolValues(const std::vector& values); + void setRealValues(const std::vector& values); + // Setting string values isn't supported since we don't have a use case yet and the memory + // management adds additional complexity. + +private: + // Two type parameters are required for these methods, rather than one, due to the gestures + // library using its own bool type. + template + const std::vector getValues(U* dataPointer) const; + template + void setValues(T* dataPointer, const std::vector& values); + + std::string mName; + size_t mCount; + std::variant mDataPointer; + void* mHandlerData = nullptr; + GesturesPropGetHandler mGetter = nullptr; + GesturesPropSetHandler mSetter = nullptr; +}; diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 58a5c31b78..af40fed51d 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -55,6 +55,7 @@ cc_test { "LatencyTracker_test.cpp", "NotifyArgs_test.cpp", "PreferStylusOverTouch_test.cpp", + "PropertyProvider_test.cpp", "TestInputListener.cpp", "UinputDevice.cpp", "UnwantedInteractionBlocker_test.cpp", diff --git a/services/inputflinger/tests/PropertyProvider_test.cpp b/services/inputflinger/tests/PropertyProvider_test.cpp new file mode 100644 index 0000000000..42a6a9f4b9 --- /dev/null +++ b/services/inputflinger/tests/PropertyProvider_test.cpp @@ -0,0 +1,286 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "include/gestures.h" + +namespace android { + +using testing::ElementsAre; + +class PropertyProviderTest : public testing::Test { +protected: + PropertyProvider mProvider; +}; + +TEST_F(PropertyProviderTest, Int_Create) { + const size_t COUNT = 4; + int intData[COUNT] = {0, 0, 0, 0}; + int initialValues[COUNT] = {1, 2, 3, 4}; + gesturePropProvider.create_int_fn(&mProvider, "Some Integers", intData, COUNT, initialValues); + + ASSERT_TRUE(mProvider.hasProperty("Some Integers")); + GesturesProp& prop = mProvider.getProperty("Some Integers"); + EXPECT_EQ(prop.getName(), "Some Integers"); + EXPECT_EQ(prop.getCount(), COUNT); + EXPECT_THAT(intData, ElementsAre(1, 2, 3, 4)); +} + +TEST_F(PropertyProviderTest, Int_Get) { + const size_t COUNT = 4; + int intData[COUNT] = {0, 0, 0, 0}; + int initialValues[COUNT] = {9, 9, 9, 9}; + GesturesProp* propPtr = gesturePropProvider.create_int_fn(&mProvider, "Some Integers", intData, + COUNT, initialValues); + + // Get handlers are supposed to be called before the property's data is accessed, so they can + // update it if necessary. This getter updates the values, so that the ordering can be checked. + GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool { + int* array = static_cast(handlerData); + array[0] = 1; + array[1] = 2; + array[2] = 3; + array[3] = 4; + return true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ intData, + getter, nullptr); + + ASSERT_TRUE(mProvider.hasProperty("Some Integers")); + GesturesProp& prop = mProvider.getProperty("Some Integers"); + EXPECT_THAT(prop.getIntValues(), ElementsAre(1, 2, 3, 4)); +} + +TEST_F(PropertyProviderTest, Int_Set) { + const size_t COUNT = 4; + int intData[COUNT] = {0, 0, 0, 0}; + int initialValues[COUNT] = {9, 9, 9, 9}; + GesturesProp* propPtr = gesturePropProvider.create_int_fn(&mProvider, "Some Integers", intData, + COUNT, initialValues); + + struct SetterData { + bool setterCalled; + int* propertyData; + }; + SetterData setterData = {false, intData}; + GesturesPropSetHandler setter{[](void* handlerData) { + SetterData* data = static_cast(handlerData); + // Set handlers should be called after the property's data has changed, so check the data. + EXPECT_EQ(data->propertyData[0], 1); + EXPECT_EQ(data->propertyData[1], 2); + EXPECT_EQ(data->propertyData[2], 3); + EXPECT_EQ(data->propertyData[3], 4); + data->setterCalled = true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &setterData, + nullptr, setter); + + ASSERT_TRUE(mProvider.hasProperty("Some Integers")); + GesturesProp& prop = mProvider.getProperty("Some Integers"); + prop.setIntValues({1, 2, 3, 4}); + EXPECT_THAT(intData, ElementsAre(1, 2, 3, 4)); + EXPECT_TRUE(setterData.setterCalled); + EXPECT_THAT(prop.getIntValues(), ElementsAre(1, 2, 3, 4)); +} + +TEST_F(PropertyProviderTest, Bool_Create) { + const size_t COUNT = 3; + GesturesPropBool boolData[COUNT] = {false, false, false}; + GesturesPropBool initialValues[COUNT] = {true, false, false}; + gesturePropProvider.create_bool_fn(&mProvider, "Some Booleans", boolData, COUNT, initialValues); + + ASSERT_TRUE(mProvider.hasProperty("Some Booleans")); + GesturesProp& prop = mProvider.getProperty("Some Booleans"); + EXPECT_EQ(prop.getName(), "Some Booleans"); + EXPECT_EQ(prop.getCount(), COUNT); + EXPECT_THAT(boolData, ElementsAre(true, false, false)); +} + +TEST_F(PropertyProviderTest, Bool_Get) { + const size_t COUNT = 3; + GesturesPropBool boolData[COUNT] = {false, false, false}; + GesturesPropBool initialValues[COUNT] = {true, false, false}; + GesturesProp* propPtr = gesturePropProvider.create_bool_fn(&mProvider, "Some Booleans", + boolData, COUNT, initialValues); + + // Get handlers are supposed to be called before the property's data is accessed, so they can + // update it if necessary. This getter updates the values, so that the ordering can be checked. + GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool { + GesturesPropBool* array = static_cast(handlerData); + array[0] = false; + array[1] = true; + array[2] = true; + return true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ boolData, + getter, nullptr); + + ASSERT_TRUE(mProvider.hasProperty("Some Booleans")); + GesturesProp& prop = mProvider.getProperty("Some Booleans"); + EXPECT_THAT(prop.getBoolValues(), ElementsAre(false, true, true)); +} + +TEST_F(PropertyProviderTest, Bool_Set) { + const size_t COUNT = 3; + GesturesPropBool boolData[COUNT] = {false, false, false}; + GesturesPropBool initialValues[COUNT] = {true, false, false}; + GesturesProp* propPtr = gesturePropProvider.create_bool_fn(&mProvider, "Some Booleans", + boolData, COUNT, initialValues); + + struct SetterData { + bool setterCalled; + GesturesPropBool* propertyData; + }; + SetterData setterData = {false, boolData}; + GesturesPropSetHandler setter{[](void* handlerData) { + SetterData* data = static_cast(handlerData); + // Set handlers should be called after the property's data has changed, so check the data. + EXPECT_EQ(data->propertyData[0], false); + EXPECT_EQ(data->propertyData[1], true); + EXPECT_EQ(data->propertyData[2], true); + data->setterCalled = true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &setterData, + nullptr, setter); + + ASSERT_TRUE(mProvider.hasProperty("Some Booleans")); + GesturesProp& prop = mProvider.getProperty("Some Booleans"); + prop.setBoolValues({false, true, true}); + EXPECT_THAT(boolData, ElementsAre(false, true, true)); + EXPECT_TRUE(setterData.setterCalled); + EXPECT_THAT(prop.getBoolValues(), ElementsAre(false, true, true)); +} + +TEST_F(PropertyProviderTest, Real_Create) { + const size_t COUNT = 3; + double realData[COUNT] = {0.0, 0.0, 0.0}; + double initialValues[COUNT] = {3.14, 0.7, -5.0}; + gesturePropProvider.create_real_fn(&mProvider, "Some Reals", realData, COUNT, initialValues); + + ASSERT_TRUE(mProvider.hasProperty("Some Reals")); + GesturesProp& prop = mProvider.getProperty("Some Reals"); + EXPECT_EQ(prop.getName(), "Some Reals"); + EXPECT_EQ(prop.getCount(), COUNT); + EXPECT_THAT(realData, ElementsAre(3.14, 0.7, -5.0)); +} + +TEST_F(PropertyProviderTest, Real_Get) { + const size_t COUNT = 3; + double realData[COUNT] = {0.0, 0.0, 0.0}; + double initialValues[COUNT] = {-1.0, -1.0, -1.0}; + GesturesProp* propPtr = gesturePropProvider.create_real_fn(&mProvider, "Some Reals", realData, + COUNT, initialValues); + + // Get handlers are supposed to be called before the property's data is accessed, so they can + // update it if necessary. This getter updates the values, so that the ordering can be checked. + GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool { + double* array = static_cast(handlerData); + array[0] = 3.14; + array[1] = 0.7; + array[2] = -5.0; + return true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ realData, + getter, nullptr); + + ASSERT_TRUE(mProvider.hasProperty("Some Reals")); + GesturesProp& prop = mProvider.getProperty("Some Reals"); + EXPECT_THAT(prop.getRealValues(), ElementsAre(3.14, 0.7, -5.0)); +} + +TEST_F(PropertyProviderTest, Real_Set) { + const size_t COUNT = 3; + double realData[COUNT] = {0.0, 0.0, 0.0}; + double initialValues[COUNT] = {-1.0, -1.0, -1.0}; + GesturesProp* propPtr = gesturePropProvider.create_real_fn(&mProvider, "Some Reals", realData, + COUNT, initialValues); + + struct SetterData { + bool setterCalled; + double* propertyData; + }; + SetterData setterData = {false, realData}; + GesturesPropSetHandler setter{[](void* handlerData) { + SetterData* data = static_cast(handlerData); + // Set handlers should be called after the property's data has changed, so check the data. + EXPECT_EQ(data->propertyData[0], 3.14); + EXPECT_EQ(data->propertyData[1], 0.7); + EXPECT_EQ(data->propertyData[2], -5.0); + data->setterCalled = true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &setterData, + nullptr, setter); + + ASSERT_TRUE(mProvider.hasProperty("Some Reals")); + GesturesProp& prop = mProvider.getProperty("Some Reals"); + prop.setRealValues({3.14, 0.7, -5.0}); + EXPECT_THAT(realData, ElementsAre(3.14, 0.7, -5.0)); + EXPECT_TRUE(setterData.setterCalled); + EXPECT_THAT(prop.getRealValues(), ElementsAre(3.14, 0.7, -5.0)); +} + +TEST_F(PropertyProviderTest, String_Create) { + const char* str = nullptr; + std::string initialValue = "Foo"; + gesturePropProvider.create_string_fn(&mProvider, "A String", &str, initialValue.c_str()); + + ASSERT_TRUE(mProvider.hasProperty("A String")); + GesturesProp& prop = mProvider.getProperty("A String"); + EXPECT_EQ(prop.getName(), "A String"); + EXPECT_EQ(prop.getCount(), 1u); + EXPECT_STREQ(str, "Foo"); +} + +TEST_F(PropertyProviderTest, String_Get) { + const char* str = nullptr; + std::string initialValue = "Foo"; + GesturesProp* propPtr = gesturePropProvider.create_string_fn(&mProvider, "A String", &str, + initialValue.c_str()); + + // Get handlers are supposed to be called before the property's data is accessed, so they can + // update it if necessary. This getter updates the values, so that the ordering can be checked. + struct GetterData { + const char** strPtr; + std::string newValue; // Have to store the new value outside getter so it stays allocated. + }; + GetterData getterData = {&str, "Bar"}; + GesturesPropGetHandler getter{[](void* handlerData) -> GesturesPropBool { + GetterData* data = static_cast(handlerData); + *data->strPtr = data->newValue.c_str(); + return true; + }}; + gesturePropProvider.register_handlers_fn(&mProvider, propPtr, /* handler_data= */ &getterData, + getter, nullptr); + + ASSERT_TRUE(mProvider.hasProperty("A String")); + GesturesProp& prop = mProvider.getProperty("A String"); + EXPECT_EQ(prop.getStringValue(), "Bar"); +} + +TEST_F(PropertyProviderTest, Free) { + int intData = 0; + int initialValue = 42; + GesturesProp* propPtr = + gesturePropProvider.create_int_fn(&mProvider, "Foo", &intData, 1, &initialValue); + gesturePropProvider.free_fn(&mProvider, propPtr); + + EXPECT_FALSE(mProvider.hasProperty("Foo")); +} + +} // namespace android -- GitLab From ea73eaa7cb640654d6cd5b423196a7beeb781416 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Mon, 16 Jan 2023 17:55:46 +0000 Subject: [PATCH 0740/1310] TouchpadInputMapper: add dump method This dumps the state from the gesture converter, and a list of gesture properties, which is useful for debugging settings. Bug: 251196347 Test: run dumpsys input with touchpad connected, check output Change-Id: I036d0251b06489b645b883a239ff345a98448497 --- include/input/PrintTools.h | 26 +++++++++++++- services/inputflinger/reader/EventHub.cpp | 12 +++++++ .../inputflinger/reader/include/EventHub.h | 4 +++ .../reader/mapper/InputMapper.cpp | 11 +++--- .../reader/mapper/TouchpadInputMapper.cpp | 9 +++++ .../reader/mapper/TouchpadInputMapper.h | 2 ++ .../mapper/gestures/GestureConverter.cpp | 16 +++++++++ .../reader/mapper/gestures/GestureConverter.h | 2 ++ .../mapper/gestures/PropertyProvider.cpp | 35 +++++++++++++++++++ .../reader/mapper/gestures/PropertyProvider.h | 3 ++ 10 files changed, 113 insertions(+), 7 deletions(-) diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h index e24344b3f1..ce8717009e 100644 --- a/include/input/PrintTools.h +++ b/include/input/PrintTools.h @@ -20,6 +20,7 @@ #include #include #include +#include namespace android { @@ -28,6 +29,16 @@ inline std::string constToString(const T& v) { return std::to_string(v); } +template <> +inline std::string constToString(const bool& value) { + return value ? "true" : "false"; +} + +template <> +inline std::string constToString(const std::vector::reference& value) { + return value ? "true" : "false"; +} + inline std::string constToString(const std::string& s) { return s; } @@ -70,6 +81,19 @@ std::string dumpMap(const std::map& map, std::string (*keyToString)(const return out; } +/** + * Convert a vector to a string. The values of the vector should be of a type supported by + * constToString. + */ +template +std::string dumpVector(std::vector values) { + std::string dump = constToString(values[0]); + for (size_t i = 1; i < values.size(); i++) { + dump += ", " + constToString(values[i]); + } + return dump; +} + const char* toString(bool value); /** @@ -81,4 +105,4 @@ const char* toString(bool value); */ std::string addLinePrefix(std::string str, const std::string& prefix); -} // namespace android \ No newline at end of file +} // namespace android diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index 43b67cae8b..f7b38a1b9e 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -515,6 +515,18 @@ ftl::Flags getAbsAxisUsage(int32_t axis, return deviceClasses & InputDeviceClass::JOYSTICK; } +// --- RawAbsoluteAxisInfo --- + +std::ostream& operator<<(std::ostream& out, const RawAbsoluteAxisInfo& info) { + if (info.valid) { + out << "min=" << info.minValue << ", max=" << info.maxValue << ", flat=" << info.flat + << ", fuzz=" << info.fuzz << ", resolution=" << info.resolution; + } else { + out << "unknown range"; + } + return out; +} + // --- EventHub::Device --- EventHub::Device::Device(int fd, int32_t id, std::string path, InputDeviceIdentifier identifier, diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index a3ecf418bb..86acadb428 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include #include @@ -77,6 +79,8 @@ struct RawAbsoluteAxisInfo { inline void clear() { *this = RawAbsoluteAxisInfo(); } }; +std::ostream& operator<<(std::ostream& out, const RawAbsoluteAxisInfo& info); + /* * Input device classes. */ diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp index 8e3539c3a6..ba2ea998b5 100644 --- a/services/inputflinger/reader/mapper/InputMapper.cpp +++ b/services/inputflinger/reader/mapper/InputMapper.cpp @@ -18,6 +18,8 @@ #include "InputMapper.h" +#include + #include "InputDevice.h" #include "input/PrintTools.h" @@ -120,12 +122,9 @@ void InputMapper::bumpGeneration() { void InputMapper::dumpRawAbsoluteAxisInfo(std::string& dump, const RawAbsoluteAxisInfo& axis, const char* name) { - if (axis.valid) { - dump += StringPrintf(INDENT4 "%s: min=%d, max=%d, flat=%d, fuzz=%d, resolution=%d\n", name, - axis.minValue, axis.maxValue, axis.flat, axis.fuzz, axis.resolution); - } else { - dump += StringPrintf(INDENT4 "%s: unknown range\n", name); - } + std::stringstream out; + out << INDENT4 << name << ": " << axis << "\n"; + dump += out.str(); } void InputMapper::dumpStylusState(std::string& dump, const StylusState& state) { diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 89ba07ea9b..b6313a1049 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include "TouchCursorInputMapperCommon.h" @@ -124,6 +125,14 @@ uint32_t TouchpadInputMapper::getSources() const { return AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD; } +void TouchpadInputMapper::dump(std::string& dump) { + dump += INDENT2 "Touchpad Input Mapper:\n"; + dump += INDENT3 "Gesture converter:\n"; + dump += addLinePrefix(mGestureConverter.dump(), INDENT4); + dump += INDENT3 "Gesture properties:\n"; + dump += addLinePrefix(mPropertyProvider.dump(), INDENT4); +} + std::list TouchpadInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) { diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index 8ffc8a0b39..d693bcaf30 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -41,6 +41,8 @@ public: ~TouchpadInputMapper(); uint32_t getSources() const override; + void dump(std::string& dump) override; + [[nodiscard]] std::list configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) override; diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp index 11ffd286dd..561b1f819a 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp @@ -16,7 +16,11 @@ #include "gestures/GestureConverter.h" +#include + +#include #include +#include #include #include @@ -55,6 +59,18 @@ GestureConverter::GestureConverter(InputReaderContext& readerContext, deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mYAxisInfo); } +std::string GestureConverter::dump() const { + std::stringstream out; + out << "Orientation: " << ftl::enum_string(mOrientation) << "\n"; + out << "Axis info:\n"; + out << " X: " << mXAxisInfo << "\n"; + out << " Y: " << mYAxisInfo << "\n"; + out << StringPrintf("Button state: 0x%08x\n", mButtonState); + out << "Down time: " << mDownTime << "\n"; + out << "Current classification: " << ftl::enum_string(mCurrentClassification) << "\n"; + return out.str(); +} + void GestureConverter::reset() { mButtonState = 0; } diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h index 8e8e3d9de3..2ec5841fe0 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h @@ -40,6 +40,8 @@ public: GestureConverter(InputReaderContext& readerContext, const InputDeviceContext& deviceContext, int32_t deviceId); + std::string dump() const; + void setOrientation(ui::Rotation orientation) { mOrientation = orientation; } void reset(); diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp index 2e128540d8..cd18cd3e35 100644 --- a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp +++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp @@ -21,7 +21,9 @@ #include #include +#include #include +#include #include namespace android { @@ -74,6 +76,14 @@ GesturesProp& PropertyProvider::getProperty(const std::string name) { return mProperties.at(name); } +std::string PropertyProvider::dump() const { + std::string dump; + for (const auto& [name, property] : mProperties) { + dump += property.dump() + "\n"; + } + return dump; +} + GesturesProp* PropertyProvider::createIntArrayProperty(const std::string name, int* loc, size_t count, const int* init) { const auto [it, inserted] = @@ -124,6 +134,31 @@ GesturesProp::GesturesProp(std::string name, const char** dataPointer, *(std::get(mDataPointer)) = initialValue; } +std::string GesturesProp::dump() const { + using android::base::StringPrintf; + std::string type, values; + switch (mDataPointer.index()) { + case 0: + type = "integer"; + values = android::dumpVector(getIntValues()); + break; + case 1: + type = "boolean"; + values = android::dumpVector(getBoolValues()); + break; + case 2: + type = "string"; + values = getStringValue(); + break; + case 3: + type = "real"; + values = android::dumpVector(getRealValues()); + break; + } + std::string typeAndSize = mCount == 1 ? type : std::to_string(mCount) + " " + type + "s"; + return StringPrintf("%s (%s): %s", mName.c_str(), typeAndSize.c_str(), values.c_str()); +} + void GesturesProp::registerHandlers(void* handlerData, GesturesPropGetHandler getter, GesturesPropSetHandler setter) { mHandlerData = handlerData; diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.h b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h index 4bebd469b2..c21260f6c2 100644 --- a/services/inputflinger/reader/mapper/gestures/PropertyProvider.h +++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h @@ -33,6 +33,7 @@ class PropertyProvider { public: bool hasProperty(const std::string name) const; GesturesProp& getProperty(const std::string name); + std::string dump() const; // Methods to be called by the gestures library: GesturesProp* createIntArrayProperty(const std::string name, int* loc, size_t count, @@ -62,6 +63,8 @@ public: GesturesProp(std::string name, T* dataPointer, size_t count, const T* initialValues); GesturesProp(std::string name, const char** dataPointer, const char* const initialValue); + std::string dump() const; + std::string getName() const { return mName; } size_t getCount() const { return mCount; } -- GitLab From 13063aa7f1385410f230ca0079a4ce36fd1d6a92 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Fri, 20 Jan 2023 18:13:03 +0000 Subject: [PATCH 0741/1310] Revert "Disable new touchpad stack for Sony gamepads" Revert submission 21018993 Reason for revert: Causes a failure in DeviceBootTest (see b/266216942). Reverted changes: /q/submissionid:21018993 Change-Id: Iecea5aeb1c567e936bf5c7c793c5833a086664cb --- services/inputflinger/reader/InputDevice.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 90d5d01c16..6e78e82c7e 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -210,14 +210,8 @@ void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) { // Touchscreens and touchpad devices. static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY = sysprop::InputProperties::enable_touchpad_gestures_library().value_or(false); - // TODO(b/246587538): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) and DualSense - // (ce6) touchpads, or at least load this setting from the IDC file. - const InputDeviceIdentifier& identifier = getDeviceInfo().getIdentifier(); - const bool isSonyGamepadTouchpad = identifier.vendor == 0x054c && - (identifier.product == 0x05c4 || identifier.product == 0x09cc || - identifier.product == 0x0ce6); if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) && - classes.test(InputDeviceClass::TOUCH_MT) && !isSonyGamepadTouchpad) { + classes.test(InputDeviceClass::TOUCH_MT)) { mappers.push_back(std::make_unique(*contextPtr)); } else if (classes.test(InputDeviceClass::TOUCH_MT)) { mappers.push_back(std::make_unique(*contextPtr)); -- GitLab From e60fa698a2d69fcdcd4fb71eb1a0be0bef000507 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Fri, 20 Jan 2023 18:13:03 +0000 Subject: [PATCH 0742/1310] Revert "Enable new touchpad stack by default" Revert submission 21018993 Reason for revert: Causes a failure in DeviceBootTest (see b/266216942). Reverted changes: /q/submissionid:21018993 Change-Id: I9247187c69dca2051ac135908a0c83ac322fdff4 --- services/inputflinger/reader/InputDevice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 7031c8ba74..90d5d01c16 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -209,7 +209,7 @@ void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) { // Touchscreens and touchpad devices. static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY = - sysprop::InputProperties::enable_touchpad_gestures_library().value_or(true); + sysprop::InputProperties::enable_touchpad_gestures_library().value_or(false); // TODO(b/246587538): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) and DualSense // (ce6) touchpads, or at least load this setting from the IDC file. const InputDeviceIdentifier& identifier = getDeviceInfo().getIdentifier(); -- GitLab From 91dfc57ae2b1d18725e6a59111634abe5220d26d Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Thu, 19 Jan 2023 11:22:43 -0800 Subject: [PATCH 0743/1310] libjpegrecoverymap refactor: move EXIF stuffs into utils. Test: recoverymap_test Bug: b/264715926 Change-Id: Ie4c3632c03ac34867cb9e4f50fb782578ad8c8da --- .../jpegrecoverymap/recoverymaputils.h | 58 ++++++++ libs/jpegrecoverymap/recoverymap.cpp | 129 ------------------ libs/jpegrecoverymap/recoverymaputils.cpp | 87 +++++++++++- 3 files changed, 144 insertions(+), 130 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h index 164ea64628..f8ae30aade 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h @@ -17,6 +17,8 @@ #ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H #define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H +#include + #include #include #include @@ -26,6 +28,26 @@ namespace android::recoverymap { struct jpegr_metadata; +// If the EXIF package doesn't exist in the input JPEG, we'll create one with one entry +// where the length is represented by this value. +const size_t PSEUDO_EXIF_PACKAGE_LENGTH = 28; +// If the EXIF package exists in the input JPEG, we'll add an "JR" entry where the length is +// represented by this value. +const size_t EXIF_J_R_ENTRY_LENGTH = 12; + +/* + * Helper function used for writing data to destination. + * + * @param destination destination of the data to be written. + * @param source source of data being written. + * @param length length of the data to be written. + * @param position cursor in desitination where the data is to be written. + * @return status of succeed or error code. + */ +status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position); +status_t Write(jr_exif_ptr destination, const void* source, size_t length, int &position); + + /* * Parses XMP packet and fills metadata with data from XMP * @@ -83,6 +105,42 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta * @return XMP metadata in type of string */ std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata); + +/* + * Helper function + * Add J R entry to existing exif, or create a new one with J R entry if it's null. + * EXIF syntax / change: + * ori: + * FF E1 - APP1 + * 01 FC - size of APP1 (to be calculated) + * ----------------------------------------------------- + * 45 78 69 66 00 00 - Exif\0\0 "Exif header" + * 49 49 2A 00 - TIFF Header + * 08 00 00 00 - offset to the IFD (image file directory) + * 06 00 - 6 entries + * 00 01 - Width Tag + * 03 00 - 'Short' type + * 01 00 00 00 - one entry + * 00 05 00 00 - image with 0x500 + *-------------------------------------------------------------------------- + * new: + * FF E1 - APP1 + * 02 08 - new size, equals to old size + EXIF_J_R_ENTRY_LENGTH (12) + *----------------------------------------------------- + * 45 78 69 66 00 00 - Exif\0\0 "Exif header" + * 49 49 2A 00 - TIFF Header + * 08 00 00 00 - offset to the IFD (image file directory) + * 07 00 - +1 entry + * 4A 52 Custom ('J''R') Tag + * 07 00 - Unknown type + * 01 00 00 00 - one element + * 00 00 00 00 - empty data + * 00 01 - Width Tag + * 03 00 - 'Short' type + * 01 00 00 00 - one entry + * 00 05 00 00 - image with 0x500 + */ +status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest); } #endif //ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index eb557e5f17..22289de39c 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -93,135 +93,6 @@ int GetCPUCoreCount() { return cpuCoreCount; } -/* - * Helper function used for writing data to destination. - * - * @param destination destination of the data to be written. - * @param source source of data being written. - * @param length length of the data to be written. - * @param position cursor in desitination where the data is to be written. - * @return status of succeed or error code. - */ -status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) { - if (position + length > destination->maxLength) { - return ERROR_JPEGR_BUFFER_TOO_SMALL; - } - - memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length); - position += length; - return NO_ERROR; -} - -status_t Write(jr_exif_ptr destination, const void* source, size_t length, int &position) { - memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length); - position += length; - return NO_ERROR; -} - -// If the EXIF package doesn't exist in the input JPEG, we'll create one with one entry -// where the length is represented by this value. -const size_t PSEUDO_EXIF_PACKAGE_LENGTH = 28; -// If the EXIF package exists in the input JPEG, we'll add an "JR" entry where the length is -// represented by this value. -const size_t EXIF_J_R_ENTRY_LENGTH = 12; - -/* - * Helper function - * Add J R entry to existing exif, or create a new one with J R entry if it's null. - * EXIF syntax / change: - * ori: - * FF E1 - APP1 - * 01 FC - size of APP1 (to be calculated) - * ----------------------------------------------------- - * 45 78 69 66 00 00 - Exif\0\0 "Exif header" - * 49 49 2A 00 - TIFF Header - * 08 00 00 00 - offset to the IFD (image file directory) - * 06 00 - 6 entries - * 00 01 - Width Tag - * 03 00 - 'Short' type - * 01 00 00 00 - one entry - * 00 05 00 00 - image with 0x500 - *-------------------------------------------------------------------------- - * new: - * FF E1 - APP1 - * 02 08 - new size, equals to old size + EXIF_J_R_ENTRY_LENGTH (12) - *----------------------------------------------------- - * 45 78 69 66 00 00 - Exif\0\0 "Exif header" - * 49 49 2A 00 - TIFF Header - * 08 00 00 00 - offset to the IFD (image file directory) - * 07 00 - +1 entry - * 4A 52 Custom ('J''R') Tag - * 07 00 - Unknown type - * 01 00 00 00 - one element - * 00 00 00 00 - empty data - * 00 01 - Width Tag - * 03 00 - 'Short' type - * 01 00 00 00 - one entry - * 00 05 00 00 - image with 0x500 - */ -status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest) { - if (exif == nullptr || exif->data == nullptr) { - uint8_t data[PSEUDO_EXIF_PACKAGE_LENGTH] = { - 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, - 0x49, 0x49, 0x2A, 0x00, - 0x08, 0x00, 0x00, 0x00, - 0x01, 0x00, - 0x4A, 0x52, - 0x07, 0x00, - 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00}; - int pos = 0; - Write(dest, data, PSEUDO_EXIF_PACKAGE_LENGTH, pos); - return NO_ERROR; - } - - int num_entry = 0; - uint8_t num_entry_low = 0; - uint8_t num_entry_high = 0; - bool use_big_endian = false; - if (reinterpret_cast(exif->data)[3] == 0x4949) { - num_entry_low = reinterpret_cast(exif->data)[14]; - num_entry_high = reinterpret_cast(exif->data)[15]; - } else if (reinterpret_cast(exif->data)[3] == 0x4d4d) { - use_big_endian = true; - num_entry_high = reinterpret_cast(exif->data)[14]; - num_entry_low = reinterpret_cast(exif->data)[15]; - } else { - return ERROR_JPEGR_METADATA_ERROR; - } - num_entry = (num_entry_high << 8) | num_entry_low; - num_entry += 1; - num_entry_low = num_entry & 0xff; - num_entry_high = (num_entry << 8) & 0xff; - - int pos = 0; - Write(dest, (uint8_t*)exif->data, 14, pos); - - if (use_big_endian) { - Write(dest, &num_entry_high, 1, pos); - Write(dest, &num_entry_low, 1, pos); - uint8_t data[EXIF_J_R_ENTRY_LENGTH] = { - 0x4A, 0x52, - 0x07, 0x00, - 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00}; - Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos); - } else { - Write(dest, &num_entry_low, 1, pos); - Write(dest, &num_entry_high, 1, pos); - uint8_t data[EXIF_J_R_ENTRY_LENGTH] = { - 0x4A, 0x52, - 0x00, 0x07, - 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00}; - Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos); - } - - Write(dest, (uint8_t*)exif->data + 16, exif->length - 16, pos); - - return NO_ERROR; -} - /* * Helper function copies the JPEG image from without EXIF. * diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/recoverymaputils.cpp index 8997b4dee2..78edd27b09 100644 --- a/libs/jpegrecoverymap/recoverymaputils.cpp +++ b/libs/jpegrecoverymap/recoverymaputils.cpp @@ -15,7 +15,6 @@ */ #include -#include #include #include #include @@ -41,6 +40,25 @@ string Name(const string &prefix, const string &suffix) { return ss.str(); } +/* + * Helper function used for writing data to destination. + */ +status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) { + if (position + length > destination->maxLength) { + return ERROR_JPEGR_BUFFER_TOO_SMALL; + } + + memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length); + position += length; + return NO_ERROR; +} + +status_t Write(jr_exif_ptr destination, const void* source, size_t length, int &position) { + memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length); + position += length; + return NO_ERROR; +} + // Extremely simple XML Handler - just searches for interesting elements class XMPXmlHandler : public XmlHandler { public: @@ -324,4 +342,71 @@ string generateXmp(int secondary_image_length, jpegr_metadata& metadata) { return ss.str(); } +/* + * Helper function + * Add J R entry to existing exif, or create a new one with J R entry if it's null. + */ +status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest) { + if (exif == nullptr || exif->data == nullptr) { + uint8_t data[PSEUDO_EXIF_PACKAGE_LENGTH] = { + 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, + 0x49, 0x49, 0x2A, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, + 0x4A, 0x52, + 0x07, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; + int pos = 0; + Write(dest, data, PSEUDO_EXIF_PACKAGE_LENGTH, pos); + return NO_ERROR; + } + + int num_entry = 0; + uint8_t num_entry_low = 0; + uint8_t num_entry_high = 0; + bool use_big_endian = false; + if (reinterpret_cast(exif->data)[3] == 0x4949) { + num_entry_low = reinterpret_cast(exif->data)[14]; + num_entry_high = reinterpret_cast(exif->data)[15]; + } else if (reinterpret_cast(exif->data)[3] == 0x4d4d) { + use_big_endian = true; + num_entry_high = reinterpret_cast(exif->data)[14]; + num_entry_low = reinterpret_cast(exif->data)[15]; + } else { + return ERROR_JPEGR_METADATA_ERROR; + } + num_entry = (num_entry_high << 8) | num_entry_low; + num_entry += 1; + num_entry_low = num_entry & 0xff; + num_entry_high = (num_entry >> 8) & 0xff; + + int pos = 0; + Write(dest, (uint8_t*)exif->data, 14, pos); + + if (use_big_endian) { + Write(dest, &num_entry_high, 1, pos); + Write(dest, &num_entry_low, 1, pos); + uint8_t data[EXIF_J_R_ENTRY_LENGTH] = { + 0x4A, 0x52, + 0x00, 0x07, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00}; + Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos); + } else { + Write(dest, &num_entry_low, 1, pos); + Write(dest, &num_entry_high, 1, pos); + uint8_t data[EXIF_J_R_ENTRY_LENGTH] = { + 0x4A, 0x52, + 0x07, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; + Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos); + } + + Write(dest, (uint8_t*)exif->data + 16, exif->length - 16, pos); + + return NO_ERROR; +} + } // namespace android::recoverymap -- GitLab From 0daf5f8e9073c54438d1045d6da70d9bbd7110a0 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Thu, 19 Jan 2023 13:44:23 -0800 Subject: [PATCH 0744/1310] Update EXIF Some tags contain offsets, and need to be modified after an insertion of a "JR" tag Test: manual Bug: b/264715926 Change-Id: I273c43ca86ee2d089abeae84f65aa37dace1e4c4 --- .../jpegrecoverymap/recoverymaputils.h | 49 +++++++- libs/jpegrecoverymap/recoverymaputils.cpp | 118 +++++++++++++++++- 2 files changed, 162 insertions(+), 5 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h index f8ae30aade..8b2672fefa 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h @@ -107,7 +107,6 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata); /* - * Helper function * Add J R entry to existing exif, or create a new one with J R entry if it's null. * EXIF syntax / change: * ori: @@ -120,7 +119,7 @@ std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata); * 06 00 - 6 entries * 00 01 - Width Tag * 03 00 - 'Short' type - * 01 00 00 00 - one entry + * 01 00 00 00 - 1 component * 00 05 00 00 - image with 0x500 *-------------------------------------------------------------------------- * new: @@ -133,14 +132,56 @@ std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata); * 07 00 - +1 entry * 4A 52 Custom ('J''R') Tag * 07 00 - Unknown type - * 01 00 00 00 - one element + * 01 00 00 00 - 1 component * 00 00 00 00 - empty data * 00 01 - Width Tag * 03 00 - 'Short' type - * 01 00 00 00 - one entry + * 01 00 00 00 - 1 component * 00 05 00 00 - image with 0x500 */ status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest); + +/* + * Modify offsets in EXIF in place. + * + * Each tag has the following structure: + * + * 00 01 - Tag + * 03 00 - data format + * 01 00 00 00 - number of components + * 00 05 00 00 - value + * + * The value means offset if + * (1) num_of_components * bytes_per_component > 4 bytes, or + * (2) tag == 0x8769 (ExifOffset). + * In both cases, the method will add EXIF_J_R_ENTRY_LENGTH (12) to the offsets. + */ +void updateExifOffsets(jr_exif_ptr exif, int pos, bool use_big_endian); +void updateExifOffsets(jr_exif_ptr exif, int pos, int num_entry, bool use_big_endian); + +/* + * Read data from the target position and target length in bytes; + */ +int readValue(uint8_t* data, int pos, int length, bool use_big_endian); + +/* + * Returns the length of data format in bytes + * + * ---------------------------------------------------------------------------------------------- + * | value | 1 | 2 | 3 | 4 | + * | format | unsigned byte | ascii strings | unsigned short | unsigned long | + * | bytes/component | 1 | 1 | 2 | 4 | + * ---------------------------------------------------------------------------------------------- + * | value | 5 | 6 | 7 | 8 | + * | format |unsigned rational| signed byte | undefined | signed short | + * | bytes/component | 8 | 1 | 1 | 2 | + * ---------------------------------------------------------------------------------------------- + * | value | 9 | 10 | 11 | 12 | + * | format | signed long | signed rational | single float | double float | + * | bytes/component | 4 | 8 | 4 | 8 | + * ---------------------------------------------------------------------------------------------- + */ +int findFormatLengthInBytes(int data_format); } #endif //ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/recoverymaputils.cpp index 78edd27b09..d5ad9a51c4 100644 --- a/libs/jpegrecoverymap/recoverymaputils.cpp +++ b/libs/jpegrecoverymap/recoverymaputils.cpp @@ -22,6 +22,8 @@ #include #include +#include + using namespace photos_editing_formats::image_io; using namespace std; @@ -406,7 +408,121 @@ status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest) { Write(dest, (uint8_t*)exif->data + 16, exif->length - 16, pos); + updateExifOffsets(dest, + 28, // start from the second tag, skip the "JR" tag + num_entry - 1, + use_big_endian); + return NO_ERROR; } -} // namespace android::recoverymap +/* + * Helper function + * Modify offsets in EXIF in place. + */ +void updateExifOffsets(jr_exif_ptr exif, int pos, bool use_big_endian) { + int num_entry = readValue(reinterpret_cast(exif->data), pos, 2, use_big_endian); + updateExifOffsets(exif, pos + 2, num_entry, use_big_endian); +} + +void updateExifOffsets(jr_exif_ptr exif, int pos, int num_entry, bool use_big_endian) { + for (int i = 0; i < num_entry; pos += EXIF_J_R_ENTRY_LENGTH, i++) { + int tag = readValue(reinterpret_cast(exif->data), pos, 2, use_big_endian); + bool need_to_update_offset = false; + if (tag == 0x8769) { + need_to_update_offset = true; + int sub_ifd_offset = + readValue(reinterpret_cast(exif->data), pos + 8, 4, use_big_endian) + + 6 // "Exif\0\0"; + + EXIF_J_R_ENTRY_LENGTH; + updateExifOffsets(exif, sub_ifd_offset, use_big_endian); + } else { + int data_format = + readValue(reinterpret_cast(exif->data), pos + 2, 2, use_big_endian); + int num_of_components = + readValue(reinterpret_cast(exif->data), pos + 4, 4, use_big_endian); + int data_length = findFormatLengthInBytes(data_format) * num_of_components; + if (data_length > 4) { + need_to_update_offset = true; + } + } + + if (!need_to_update_offset) { + continue; + } + + int offset = readValue(reinterpret_cast(exif->data), pos + 8, 4, use_big_endian); + + offset += EXIF_J_R_ENTRY_LENGTH; + + if (use_big_endian) { + reinterpret_cast(exif->data)[pos + 11] = offset & 0xff; + reinterpret_cast(exif->data)[pos + 10] = (offset >> 8) & 0xff; + reinterpret_cast(exif->data)[pos + 9] = (offset >> 16) & 0xff; + reinterpret_cast(exif->data)[pos + 8] = (offset >> 24) & 0xff; + } else { + reinterpret_cast(exif->data)[pos + 8] = offset & 0xff; + reinterpret_cast(exif->data)[pos + 9] = (offset >> 8) & 0xff; + reinterpret_cast(exif->data)[pos + 10] = (offset >> 16) & 0xff; + reinterpret_cast(exif->data)[pos + 11] = (offset >> 24) & 0xff; + } + } +} + +/* + * Read data from the target position and target length in bytes; + */ +int readValue(uint8_t* data, int pos, int length, bool use_big_endian) { + if (length == 2) { + if (use_big_endian) { + return (data[pos] << 8) | data[pos + 1]; + } else { + return (data[pos + 1] << 8) | data[pos]; + } + } else if (length == 4) { + if (use_big_endian) { + return (data[pos] << 24) | (data[pos + 1] << 16) | (data[pos + 2] << 8) | data[pos + 3]; + } else { + return (data[pos + 3] << 24) | (data[pos + 2] << 16) | (data[pos + 1] << 8) | data[pos]; + } + } else { + // Not support for now. + ALOGE("Error in readValue(): pos=%d, length=%d", pos, length); + return -1; + } +} + +/* + * Helper function + * Returns the length of data format in bytes + */ +int findFormatLengthInBytes(int data_format) { + switch (data_format) { + case 1: // unsigned byte + case 2: // ascii strings + case 6: // signed byte + case 7: // undefined + return 1; + + case 3: // unsigned short + case 8: // signed short + return 2; + + case 4: // unsigned long + case 9: // signed long + case 11: // single float + return 4; + + case 5: // unsigned rational + case 10: // signed rational + case 12: // double float + return 8; + + default: + // should not hit here + ALOGE("Error in findFormatLengthInBytes(): data_format=%d", data_format); + return -1; + } +} + +} // namespace android::recoverymap \ No newline at end of file -- GitLab From b15ef13974e62fe2c93f6557222f9d8ed0b2cdb1 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Sat, 21 Jan 2023 00:29:14 +0000 Subject: [PATCH 0745/1310] Revert "SF: enable frame rate override " Revert submission 20885603-enable_frame_rate_override Reason for revert: b/264952266 Reverted changes: /q/submissionid:20885603-enable_frame_rate_override Change-Id: I2ca61c38b80ad9c4cf3ef10fb31ef0ac290b6250 --- services/surfaceflinger/SurfaceFlinger.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 99f220d0ee..3dd86aa62e 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2888,15 +2888,15 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( const auto enableFrameRateOverride = [&] { using Config = scheduler::RefreshRateSelector::Config; - if (!sysprop::enable_frame_rate_override(true)) { + if (!sysprop::enable_frame_rate_override(false)) { return Config::FrameRateOverride::Disabled; } - if (sysprop::frame_rate_override_for_native_rates(false)) { + if (sysprop::frame_rate_override_for_native_rates(true)) { return Config::FrameRateOverride::AppOverrideNativeRefreshRates; } - if (!sysprop::frame_rate_override_global(true)) { + if (!sysprop::frame_rate_override_global(false)) { return Config::FrameRateOverride::AppOverride; } -- GitLab From 27bcb5501b6c082a2a9ae05f957277cab2c95a1a Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Sat, 21 Jan 2023 04:34:22 +0000 Subject: [PATCH 0746/1310] SF: reset the idle timer if a frame is scheduled There is a case where the idle timer might expire while SF is active as it is not getting reset if SF was already scheduled from some other reason. Change-Id: If79b2df4f0fe56b70658a5495069a556d3fdfc79 Test: atest android.platform.test.scenario.sysui.bubble.ShowBubbleAndShowImeMicrobenchmark#testShowBubbleAndShowIme Bug: 264952266 --- services/surfaceflinger/SurfaceFlinger.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 40de4d675d..5c609d6929 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3843,6 +3843,10 @@ void SurfaceFlinger::setTransactionFlags(uint32_t mask, TransactionSchedule sche if (const bool scheduled = mTransactionFlags.fetch_or(mask) & mask; !scheduled) { scheduleCommit(frameHint); + } else if (frameHint == FrameHint::kActive) { + // Even if the next frame is already scheduled, we should reset the idle timer + // as a new activity just happened. + mScheduler->resetIdleTimer(); } } -- GitLab From 0e56ec0404b58dd0b0dd370cf6879e13c9c6d896 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 23 Jan 2023 09:01:12 +0000 Subject: [PATCH 0747/1310] Add input tests from FrameworksCoreTests to presubmit Bug: None Test: presubmit Change-Id: I4f23a8b588dc8f0fd61a9d09f0f912d51b458250 --- services/inputflinger/TEST_MAPPING | 1 + 1 file changed, 1 insertion(+) diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING index 3d7242e9ea..cacf30bd82 100644 --- a/services/inputflinger/TEST_MAPPING +++ b/services/inputflinger/TEST_MAPPING @@ -61,6 +61,7 @@ "name": "FrameworksCoreTests", "options": [ { + "include-filter": "android.hardware.input", "include-filter": "android.view.VerifiedKeyEventTest", "include-filter": "android.view.VerifiedMotionEventTest" } -- GitLab From d010b014dc42f55b5973c8329ab10dd69da92c77 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 18 Jan 2023 15:00:53 -0800 Subject: [PATCH 0748/1310] Improve debug prints in InputDispatcher It's useful sometimes to print out the events produced by the dispatcher. In this CL: - Switch (partially) to the C++-style prints from android-base - Add a way to print keyevents, motionevent into a stream - Add InputEventInjectionResult print Also, improve the debug prints for outgoing events. When an entry is getting dispatched, the dispatcher may modify its action, among other variables. With this CL, this will be observable in the logs. Bug: 211379801 Test: atest AccessibilityEndToEndTest Change-Id: I221161af7903ae4da77733265c67a426a3e5b557 --- include/input/Input.h | 2 ++ libs/input/Input.cpp | 22 +++++++++++++++++ .../android/os/InputEventInjectionResult.aidl | 3 +++ services/inputflinger/dispatcher/Entry.cpp | 24 +++++++++++++++++++ services/inputflinger/dispatcher/Entry.h | 2 ++ .../dispatcher/InputDispatcher.cpp | 18 ++++++++++++-- 6 files changed, 69 insertions(+), 2 deletions(-) diff --git a/include/input/Input.h b/include/input/Input.h index e281675a33..30b0d6a67a 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -550,6 +550,8 @@ protected: nsecs_t mEventTime; }; +std::ostream& operator<<(std::ostream& out, const KeyEvent& event); + /* * Motion events. */ diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index c7964393e0..c356c2e5e9 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -343,6 +343,28 @@ const char* KeyEvent::actionToString(int32_t action) { return "UNKNOWN"; } +std::ostream& operator<<(std::ostream& out, const KeyEvent& event) { + out << "KeyEvent { action=" << KeyEvent::actionToString(event.getAction()); + + out << ", keycode=" << event.getKeyCode() << "(" << KeyEvent::getLabel(event.getKeyCode()) + << ")"; + + if (event.getMetaState() != 0) { + out << ", metaState=" << event.getMetaState(); + } + + out << ", eventTime=" << event.getEventTime(); + out << ", downTime=" << event.getDownTime(); + out << ", flags=" << std::hex << event.getFlags() << std::dec; + out << ", repeatCount=" << event.getRepeatCount(); + out << ", deviceId=" << event.getDeviceId(); + out << ", source=" << inputEventSourceToString(event.getSource()); + out << ", displayId=" << event.getDisplayId(); + out << ", eventId=" << event.getId(); + out << "}"; + return out; +} + // --- PointerCoords --- float PointerCoords::getAxisValue(int32_t axis) const { diff --git a/libs/input/android/os/InputEventInjectionResult.aidl b/libs/input/android/os/InputEventInjectionResult.aidl index 3bc7068f3c..e80c2a52dc 100644 --- a/libs/input/android/os/InputEventInjectionResult.aidl +++ b/libs/input/android/os/InputEventInjectionResult.aidl @@ -37,4 +37,7 @@ enum InputEventInjectionResult { /* Injection failed due to a timeout. */ TIMED_OUT = 3, + + ftl_first=PENDING, + ftl_last=TIMED_OUT, } diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp index 7bbfb95b88..ce7c882f7d 100644 --- a/services/inputflinger/dispatcher/Entry.cpp +++ b/services/inputflinger/dispatcher/Entry.cpp @@ -331,4 +331,28 @@ uint32_t DispatchEntry::nextSeq() { return seq; } +std::ostream& operator<<(std::ostream& out, const DispatchEntry& entry) { + out << "DispatchEntry{resolvedAction="; + switch (entry.eventEntry->type) { + case EventEntry::Type::KEY: { + out << KeyEvent::actionToString(entry.resolvedAction); + break; + } + case EventEntry::Type::MOTION: { + out << MotionEvent::actionToString(entry.resolvedAction); + break; + } + default: { + out << ""; + break; + } + } + std::string transform; + entry.transform.dump(transform, "transform"); + out << ", resolvedFlags=" << entry.resolvedFlags + << ", targetFlags=" << entry.targetFlags.string() << ", transform=" << transform + << "} original =" << entry.eventEntry->getDescription(); + return out; +} + } // namespace android::inputdispatcher diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h index 3799814f67..8dc2a2a221 100644 --- a/services/inputflinger/dispatcher/Entry.h +++ b/services/inputflinger/dispatcher/Entry.h @@ -254,6 +254,8 @@ private: static uint32_t nextSeq(); }; +std::ostream& operator<<(std::ostream& out, const DispatchEntry& entry); + VerifiedKeyEvent verifiedKeyEventFromKeyEntry(const KeyEntry& entry); VerifiedMotionEvent verifiedMotionEventFromMotionEntry(const MotionEntry& entry, const ui::Transform& rawTransform); diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 37a451b3dd..204fff4566 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -20,6 +20,7 @@ #define LOG_NDEBUG 1 #include +#include #include #include #include @@ -3382,6 +3383,10 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, case EventEntry::Type::KEY: { const KeyEntry& keyEntry = static_cast(eventEntry); std::array hmac = getSignature(keyEntry, *dispatchEntry); + if (DEBUG_OUTBOUND_EVENT_DETAILS) { + LOG(DEBUG) << "Publishing " << *dispatchEntry << " to " + << connection->getInputChannelName(); + } // Publish the key event. status = connection->inputPublisher @@ -3397,6 +3402,10 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, } case EventEntry::Type::MOTION: { + if (DEBUG_OUTBOUND_EVENT_DETAILS) { + LOG(DEBUG) << "Publishing " << *dispatchEntry << " to " + << connection->getInputChannelName(); + } status = publishMotionEvent(*connection, *dispatchEntry); break; } @@ -4448,6 +4457,9 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev bool needWake = false; while (!injectedEntries.empty()) { + if (DEBUG_INJECTION) { + LOG(DEBUG) << "Injecting " << injectedEntries.front()->getDescription(); + } needWake |= enqueueInboundEventLocked(std::move(injectedEntries.front())); injectedEntries.pop(); } @@ -4510,7 +4522,8 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev } // release lock if (DEBUG_INJECTION) { - ALOGD("injectInputEvent - Finished with result %d.", injectionResult); + LOG(DEBUG) << "injectInputEvent - Finished with result " + << ftl::enum_string(injectionResult); } return injectionResult; @@ -4554,7 +4567,8 @@ void InputDispatcher::setInjectionResult(EventEntry& entry, InjectionState* injectionState = entry.injectionState; if (injectionState) { if (DEBUG_INJECTION) { - ALOGD("Setting input event injection result to %d.", injectionResult); + LOG(DEBUG) << "Setting input event injection result to " + << ftl::enum_string(injectionResult); } if (injectionState->injectionIsAsync && !(entry.policyFlags & POLICY_FLAG_FILTERED)) { -- GitLab From 75ce1ea078444100db9f9eef06a9ef35ad8a0446 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Thu, 19 Jan 2023 15:02:30 -0600 Subject: [PATCH 0749/1310] Cache and uncache buffers in the same transaction This removes a race condition where clients may attempt to cache a buffer before an associated uncache buffer request has completed, leading to full buffer cache errors in SurfaceFlinger. GLSurfaceViewTest#testPauseResumeWithoutDelay is failing on barbet and test logs show frequent `ClientCache::add - cache is full` messages. This CL fixes the "cache is full" error but does not fix the broken test. Bug: 264892858 Test: presubmits Test: GLSurfaceViewTest#testPauseResumeWithoutDelay on barbet Change-Id: I9a075054d11c819307cd837fcfbdc03d8faf5086 --- libs/gui/ISurfaceComposer.cpp | 23 ++++--- libs/gui/SurfaceComposerClient.cpp | 62 ++++++++++++++----- libs/gui/include/gui/ISurfaceComposer.h | 5 +- libs/gui/include/gui/SurfaceComposerClient.h | 1 + libs/gui/tests/Surface_test.cpp | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 49 ++++++++++----- services/surfaceflinger/SurfaceFlinger.h | 21 +++---- services/surfaceflinger/TransactionState.h | 6 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 15 ++--- .../tests/unittests/TestableSurfaceFlinger.h | 5 +- .../unittests/TransactionApplicationTest.cpp | 19 +++--- 11 files changed, 130 insertions(+), 78 deletions(-) diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index a77ca04943..cefb9a71d6 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -63,7 +63,8 @@ public: Vector& state, const Vector& displays, uint32_t flags, const sp& applyToken, const InputWindowCommands& commands, int64_t desiredPresentTime, - bool isAutoTimestamp, const client_cache_t& uncacheBuffer, + bool isAutoTimestamp, + const std::vector& uncacheBuffers, bool hasListenerCallbacks, const std::vector& listenerCallbacks, uint64_t transactionId) override { @@ -87,8 +88,11 @@ public: SAFE_PARCEL(commands.write, data); SAFE_PARCEL(data.writeInt64, desiredPresentTime); SAFE_PARCEL(data.writeBool, isAutoTimestamp); - SAFE_PARCEL(data.writeStrongBinder, uncacheBuffer.token.promote()); - SAFE_PARCEL(data.writeUint64, uncacheBuffer.id); + SAFE_PARCEL(data.writeUint32, static_cast(uncacheBuffers.size())); + for (const client_cache_t& uncacheBuffer : uncacheBuffers) { + SAFE_PARCEL(data.writeStrongBinder, uncacheBuffer.token.promote()); + SAFE_PARCEL(data.writeUint64, uncacheBuffer.id); + } SAFE_PARCEL(data.writeBool, hasListenerCallbacks); SAFE_PARCEL(data.writeVectorSize, listenerCallbacks); @@ -158,11 +162,14 @@ status_t BnSurfaceComposer::onTransact( SAFE_PARCEL(data.readInt64, &desiredPresentTime); SAFE_PARCEL(data.readBool, &isAutoTimestamp); - client_cache_t uncachedBuffer; + SAFE_PARCEL_READ_SIZE(data.readUint32, &count, data.dataSize()); + std::vector uncacheBuffers(count); sp tmpBinder; - SAFE_PARCEL(data.readNullableStrongBinder, &tmpBinder); - uncachedBuffer.token = tmpBinder; - SAFE_PARCEL(data.readUint64, &uncachedBuffer.id); + for (size_t i = 0; i < count; i++) { + SAFE_PARCEL(data.readNullableStrongBinder, &tmpBinder); + uncacheBuffers[i].token = tmpBinder; + SAFE_PARCEL(data.readUint64, &uncacheBuffers[i].id); + } bool hasListenerCallbacks = false; SAFE_PARCEL(data.readBool, &hasListenerCallbacks); @@ -182,7 +189,7 @@ status_t BnSurfaceComposer::onTransact( return setTransactionState(frameTimelineInfo, state, displays, stateFlags, applyToken, inputWindowCommands, desiredPresentTime, isAutoTimestamp, - uncachedBuffer, hasListenerCallbacks, listenerCallbacks, + uncacheBuffers, hasListenerCallbacks, listenerCallbacks, transactionId); } default: { diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 92125ead1f..21a7f7817a 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -565,11 +565,13 @@ public: return NO_ERROR; } - uint64_t cache(const sp& buffer) { + uint64_t cache(const sp& buffer, + std::optional& outUncacheBuffer) { std::lock_guard lock(mMutex); if (mBuffers.size() >= BUFFER_CACHE_MAX_SIZE) { - evictLeastRecentlyUsedBuffer(); + outUncacheBuffer = findLeastRecentlyUsedBuffer(); + mBuffers.erase(outUncacheBuffer->id); } buffer->addDeathCallback(removeDeadBufferCallback, nullptr); @@ -580,16 +582,13 @@ public: void uncache(uint64_t cacheId) { std::lock_guard lock(mMutex); - uncacheLocked(cacheId); - } - - void uncacheLocked(uint64_t cacheId) REQUIRES(mMutex) { - mBuffers.erase(cacheId); - SurfaceComposerClient::doUncacheBufferTransaction(cacheId); + if (mBuffers.erase(cacheId)) { + SurfaceComposerClient::doUncacheBufferTransaction(cacheId); + } } private: - void evictLeastRecentlyUsedBuffer() REQUIRES(mMutex) { + client_cache_t findLeastRecentlyUsedBuffer() REQUIRES(mMutex) { auto itr = mBuffers.begin(); uint64_t minCounter = itr->second; auto minBuffer = itr; @@ -603,7 +602,8 @@ private: } itr++; } - uncacheLocked(minBuffer->first); + + return {.token = getToken(), .id = minBuffer->first}; } uint64_t getCounter() REQUIRES(mMutex) { @@ -741,6 +741,18 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel InputWindowCommands inputWindowCommands; inputWindowCommands.read(*parcel); + count = static_cast(parcel->readUint32()); + if (count > parcel->dataSize()) { + return BAD_VALUE; + } + std::vector uncacheBuffers(count); + for (size_t i = 0; i < count; i++) { + sp tmpBinder; + SAFE_PARCEL(parcel->readStrongBinder, &tmpBinder); + uncacheBuffers[i].token = tmpBinder; + SAFE_PARCEL(parcel->readUint64, &uncacheBuffers[i].id); + } + // Parsing was successful. Update the object. mId = transactionId; mTransactionNestCount = transactionNestCount; @@ -755,6 +767,7 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel mComposerStates = composerStates; mInputWindowCommands = inputWindowCommands; mApplyToken = applyToken; + mUncacheBuffers = std::move(uncacheBuffers); return NO_ERROR; } @@ -806,6 +819,13 @@ status_t SurfaceComposerClient::Transaction::writeToParcel(Parcel* parcel) const } mInputWindowCommands.write(*parcel); + + SAFE_PARCEL(parcel->writeUint32, static_cast(mUncacheBuffers.size())); + for (const client_cache_t& uncacheBuffer : mUncacheBuffers) { + SAFE_PARCEL(parcel->writeStrongBinder, uncacheBuffer.token.promote()); + SAFE_PARCEL(parcel->writeUint64, uncacheBuffer.id); + } + return NO_ERROR; } @@ -873,6 +893,10 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::merge(Tr } } + for (const auto& cacheId : other.mUncacheBuffers) { + mUncacheBuffers.push_back(cacheId); + } + mInputWindowCommands.merge(other.mInputWindowCommands); mMayContainBuffer |= other.mMayContainBuffer; @@ -891,6 +915,7 @@ void SurfaceComposerClient::Transaction::clear() { mDisplayStates.clear(); mListenerCallbacks.clear(); mInputWindowCommands.clear(); + mUncacheBuffers.clear(); mMayContainBuffer = false; mTransactionNestCount = 0; mAnimation = false; @@ -913,10 +938,10 @@ void SurfaceComposerClient::doUncacheBufferTransaction(uint64_t cacheId) { uncacheBuffer.token = BufferCache::getInstance().getToken(); uncacheBuffer.id = cacheId; Vector composerStates; - status_t status = - sf->setTransactionState(FrameTimelineInfo{}, composerStates, {}, - ISurfaceComposer::eOneWay, Transaction::getDefaultApplyToken(), - {}, systemTime(), true, uncacheBuffer, false, {}, generateId()); + status_t status = sf->setTransactionState(FrameTimelineInfo{}, composerStates, {}, + ISurfaceComposer::eOneWay, + Transaction::getDefaultApplyToken(), {}, systemTime(), + true, {uncacheBuffer}, false, {}, generateId()); if (status != NO_ERROR) { ALOGE_AND_TRACE("SurfaceComposerClient::doUncacheBufferTransaction - %s", strerror(-status)); @@ -954,7 +979,11 @@ void SurfaceComposerClient::Transaction::cacheBuffers() { s->bufferData->buffer = nullptr; } else { // Cache-miss. Include the buffer and send the new cacheId. - cacheId = BufferCache::getInstance().cache(s->bufferData->buffer); + std::optional uncacheBuffer; + cacheId = BufferCache::getInstance().cache(s->bufferData->buffer, uncacheBuffer); + if (uncacheBuffer) { + mUncacheBuffers.push_back(*uncacheBuffer); + } } s->bufferData->flags |= BufferData::BufferDataChange::cachedBufferChanged; s->bufferData->cachedBuffer.token = BufferCache::getInstance().getToken(); @@ -1087,8 +1116,7 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay sp sf(ComposerService::getComposerService()); sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken, mInputWindowCommands, mDesiredPresentTime, mIsAutoTimestamp, - {} /*uncacheBuffer - only set in doUncacheBufferTransaction*/, - hasListenerCallbacks, listenerCallbacks, mId); + mUncacheBuffers, hasListenerCallbacks, listenerCallbacks, mId); mId = generateId(); // Clear the current states and flags diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index 045cc2a184..ae56f9fdb5 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -113,8 +113,9 @@ public: const FrameTimelineInfo& frameTimelineInfo, Vector& state, const Vector& displays, uint32_t flags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, - bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, - const std::vector& listenerCallbacks, uint64_t transactionId) = 0; + bool isAutoTimestamp, const std::vector& uncacheBuffer, + bool hasListenerCallbacks, const std::vector& listenerCallbacks, + uint64_t transactionId) = 0; }; // ---------------------------------------------------------------------------- diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 2458a40ce0..0e51dcf4d4 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -402,6 +402,7 @@ public: SortedVector mDisplayStates; std::unordered_map, CallbackInfo, TCLHash> mListenerCallbacks; + std::vector mUncacheBuffers; uint64_t mId; diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 30148042fb..32d60cd5bd 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -701,7 +701,7 @@ public: const sp& /*applyToken*/, const InputWindowCommands& /*inputWindowCommands*/, int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/, - const client_cache_t& /*cachedBuffer*/, + const std::vector& /*cachedBuffer*/, bool /*hasListenerCallbacks*/, const std::vector& /*listenerCallbacks*/, uint64_t /*transactionId*/) override { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 40de4d675d..d63e2812e2 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3973,7 +3973,7 @@ bool SurfaceFlinger::applyTransactions(std::vector& transactio transaction.displays, transaction.flags, transaction.inputWindowCommands, transaction.desiredPresentTime, transaction.isAutoTimestamp, - transaction.buffer, transaction.postTime, + std::move(transaction.uncacheBufferIds), transaction.postTime, transaction.permissions, transaction.hasListenerCallbacks, transaction.listenerCallbacks, transaction.originPid, transaction.originUid, transaction.id); @@ -4061,8 +4061,9 @@ status_t SurfaceFlinger::setTransactionState( const FrameTimelineInfo& frameTimelineInfo, Vector& states, const Vector& displays, uint32_t flags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, - bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, - const std::vector& listenerCallbacks, uint64_t transactionId) { + bool isAutoTimestamp, const std::vector& uncacheBuffers, + bool hasListenerCallbacks, const std::vector& listenerCallbacks, + uint64_t transactionId) { ATRACE_CALL(); uint32_t permissions = @@ -4096,6 +4097,15 @@ status_t SurfaceFlinger::setTransactionState( const int originPid = ipc->getCallingPid(); const int originUid = ipc->getCallingUid(); + std::vector uncacheBufferIds; + uncacheBufferIds.reserve(uncacheBuffers.size()); + for (const auto& uncacheBuffer : uncacheBuffers) { + sp buffer = ClientCache::getInstance().erase(uncacheBuffer); + if (buffer != nullptr) { + uncacheBufferIds.push_back(buffer->getId()); + } + } + std::vector resolvedStates; resolvedStates.reserve(states.size()); for (auto& state : states) { @@ -4113,14 +4123,22 @@ status_t SurfaceFlinger::setTransactionState( } } - TransactionState state{frameTimelineInfo, resolvedStates, - displays, flags, - applyToken, inputWindowCommands, - desiredPresentTime, isAutoTimestamp, - uncacheBuffer, postTime, - permissions, hasListenerCallbacks, - listenerCallbacks, originPid, - originUid, transactionId}; + TransactionState state{frameTimelineInfo, + resolvedStates, + displays, + flags, + applyToken, + inputWindowCommands, + desiredPresentTime, + isAutoTimestamp, + std::move(uncacheBufferIds), + postTime, + permissions, + hasListenerCallbacks, + listenerCallbacks, + originPid, + originUid, + transactionId}; if (mTransactionTracing) { mTransactionTracing->addQueuedTransaction(state); @@ -4144,7 +4162,7 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin Vector& displays, uint32_t flags, const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime, bool isAutoTimestamp, - const client_cache_t& uncacheBuffer, + const std::vector& uncacheBufferIds, const int64_t postTime, uint32_t permissions, bool hasListenerCallbacks, const std::vector& listenerCallbacks, @@ -4185,11 +4203,8 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin ALOGE("Only privileged callers are allowed to send input commands."); } - if (uncacheBuffer.isValid()) { - sp buffer = ClientCache::getInstance().erase(uncacheBuffer); - if (buffer != nullptr) { - mBufferIdsToUncache.push_back(buffer->getId()); - } + for (uint64_t uncacheBufferId : uncacheBufferIds) { + mBufferIdsToUncache.push_back(uncacheBufferId); } // If a synchronous transaction is explicitly requested without any changes, force a transaction diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 5457be81a5..9245399e0d 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -501,7 +501,8 @@ private: uint32_t flags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, - const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, + const std::vector& uncacheBuffers, + bool hasListenerCallbacks, const std::vector& listenerCallbacks, uint64_t transactionId) override; void bootFinished(); @@ -714,16 +715,14 @@ private: /* * Transactions */ - bool applyTransactionState(const FrameTimelineInfo& info, - std::vector& state, - Vector& displays, uint32_t flags, - const InputWindowCommands& inputWindowCommands, - const int64_t desiredPresentTime, bool isAutoTimestamp, - const client_cache_t& uncacheBuffer, const int64_t postTime, - uint32_t permissions, bool hasListenerCallbacks, - const std::vector& listenerCallbacks, - int originPid, int originUid, uint64_t transactionId) - REQUIRES(mStateLock); + bool applyTransactionState( + const FrameTimelineInfo& info, std::vector& state, + Vector& displays, uint32_t flags, + const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime, + bool isAutoTimestamp, const std::vector& uncacheBufferIds, + const int64_t postTime, uint32_t permissions, bool hasListenerCallbacks, + const std::vector& listenerCallbacks, int originPid, int originUid, + uint64_t transactionId) REQUIRES(mStateLock); // Flush pending transactions that were presented after desiredPresentTime. bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext); // Returns true if there is at least one transaction that needs to be flushed diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h index 366b09d68b..5025c4935c 100644 --- a/services/surfaceflinger/TransactionState.h +++ b/services/surfaceflinger/TransactionState.h @@ -43,7 +43,7 @@ struct TransactionState { const Vector& displayStates, uint32_t transactionFlags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, - const client_cache_t& uncacheBuffer, int64_t postTime, uint32_t permissions, + std::vector uncacheBufferIds, int64_t postTime, uint32_t permissions, bool hasListenerCallbacks, std::vector listenerCallbacks, int originPid, int originUid, uint64_t transactionId) : frameTimelineInfo(frameTimelineInfo), @@ -54,7 +54,7 @@ struct TransactionState { inputWindowCommands(inputWindowCommands), desiredPresentTime(desiredPresentTime), isAutoTimestamp(isAutoTimestamp), - buffer(uncacheBuffer), + uncacheBufferIds(std::move(uncacheBufferIds)), postTime(postTime), permissions(permissions), hasListenerCallbacks(hasListenerCallbacks), @@ -109,7 +109,7 @@ struct TransactionState { InputWindowCommands inputWindowCommands; int64_t desiredPresentTime; bool isAutoTimestamp; - client_cache_t buffer; + std::vector uncacheBufferIds; int64_t postTime; uint32_t permissions; bool hasListenerCallbacks; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 81ca659915..c22d78b86e 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -730,17 +730,18 @@ public: return mFlinger->mTransactionHandler.mPendingTransactionQueues; } - auto setTransactionState(const FrameTimelineInfo &frameTimelineInfo, - Vector &states, const Vector &displays, - uint32_t flags, const sp &applyToken, - const InputWindowCommands &inputWindowCommands, + auto setTransactionState(const FrameTimelineInfo& frameTimelineInfo, + Vector& states, const Vector& displays, + uint32_t flags, const sp& applyToken, + const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, - const client_cache_t &uncacheBuffer, bool hasListenerCallbacks, - std::vector &listenerCallbacks, + const std::vector& uncacheBuffers, + bool hasListenerCallbacks, + std::vector& listenerCallbacks, uint64_t transactionId) { return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken, inputWindowCommands, desiredPresentTime, - isAutoTimestamp, uncacheBuffer, hasListenerCallbacks, + isAutoTimestamp, uncacheBuffers, hasListenerCallbacks, listenerCallbacks, transactionId); } diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 584d52ca02..72e0c7be16 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -439,12 +439,13 @@ public: uint32_t flags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, - const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, + const std::vector& uncacheBuffers, + bool hasListenerCallbacks, std::vector& listenerCallbacks, uint64_t transactionId) { return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken, inputWindowCommands, desiredPresentTime, - isAutoTimestamp, uncacheBuffer, hasListenerCallbacks, + isAutoTimestamp, uncacheBuffers, hasListenerCallbacks, listenerCallbacks, transactionId); } diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index d84698f279..a28d1cd415 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -102,7 +102,7 @@ public: int64_t desiredPresentTime = 0; bool isAutoTimestamp = true; FrameTimelineInfo frameTimelineInfo; - client_cache_t uncacheBuffer; + std::vector uncacheBuffers; uint64_t id = static_cast(-1); static_assert(0xffffffffffffffff == static_cast(-1)); }; @@ -138,7 +138,7 @@ public: transaction.displays, transaction.flags, transaction.applyToken, transaction.inputWindowCommands, transaction.desiredPresentTime, transaction.isAutoTimestamp, - transaction.uncacheBuffer, mHasListenerCallbacks, mCallbacks, + transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks, transaction.id); // If transaction is synchronous, SF applyTransactionState should time out (5s) wating for @@ -165,7 +165,7 @@ public: transaction.displays, transaction.flags, transaction.applyToken, transaction.inputWindowCommands, transaction.desiredPresentTime, transaction.isAutoTimestamp, - transaction.uncacheBuffer, mHasListenerCallbacks, mCallbacks, + transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks, transaction.id); nsecs_t returnedTime = systemTime(); @@ -196,7 +196,7 @@ public: transactionA.displays, transactionA.flags, transactionA.applyToken, transactionA.inputWindowCommands, transactionA.desiredPresentTime, transactionA.isAutoTimestamp, - transactionA.uncacheBuffer, mHasListenerCallbacks, mCallbacks, + transactionA.uncacheBuffers, mHasListenerCallbacks, mCallbacks, transactionA.id); // This thread should not have been blocked by the above transaction @@ -211,7 +211,7 @@ public: transactionB.displays, transactionB.flags, transactionB.applyToken, transactionB.inputWindowCommands, transactionB.desiredPresentTime, transactionB.isAutoTimestamp, - transactionB.uncacheBuffer, mHasListenerCallbacks, mCallbacks, + transactionB.uncacheBuffers, mHasListenerCallbacks, mCallbacks, transactionB.id); // this thread should have been blocked by the above transaction @@ -243,7 +243,7 @@ TEST_F(TransactionApplicationTest, AddToPendingQueue) { mFlinger.setTransactionState(transactionA.frameTimelineInfo, transactionA.states, transactionA.displays, transactionA.flags, transactionA.applyToken, transactionA.inputWindowCommands, transactionA.desiredPresentTime, - transactionA.isAutoTimestamp, transactionA.uncacheBuffer, + transactionA.isAutoTimestamp, transactionA.uncacheBuffers, mHasListenerCallbacks, mCallbacks, transactionA.id); auto& transactionQueue = mFlinger.getTransactionQueue(); @@ -263,7 +263,7 @@ TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) { mFlinger.setTransactionState(transactionA.frameTimelineInfo, transactionA.states, transactionA.displays, transactionA.flags, transactionA.applyToken, transactionA.inputWindowCommands, transactionA.desiredPresentTime, - transactionA.isAutoTimestamp, transactionA.uncacheBuffer, + transactionA.isAutoTimestamp, transactionA.uncacheBuffers, mHasListenerCallbacks, mCallbacks, transactionA.id); auto& transactionQueue = mFlinger.getTransactionQueue(); @@ -277,7 +277,7 @@ TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) { mFlinger.setTransactionState(empty.frameTimelineInfo, empty.states, empty.displays, empty.flags, empty.applyToken, empty.inputWindowCommands, empty.desiredPresentTime, empty.isAutoTimestamp, - empty.uncacheBuffer, mHasListenerCallbacks, mCallbacks, empty.id); + empty.uncacheBuffers, mHasListenerCallbacks, mCallbacks, empty.id); // flush transaction queue should flush as desiredPresentTime has // passed @@ -374,8 +374,7 @@ public: transaction.applyToken, transaction.inputWindowCommands, transaction.desiredPresentTime, - transaction.isAutoTimestamp, - transaction.uncacheBuffer, systemTime(), 0, + transaction.isAutoTimestamp, {}, systemTime(), 0, mHasListenerCallbacks, mCallbacks, getpid(), static_cast(getuid()), transaction.id); mFlinger.setTransactionStateInternal(transactionState); -- GitLab From b124d519481341a759b2fef5b7139a83da36c728 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Mon, 23 Jan 2023 18:17:20 +0000 Subject: [PATCH 0750/1310] Revert^2 "SF: enable frame rate override " b15ef13974e62fe2c93f6557222f9d8ed0b2cdb1 Change-Id: I0f0dc8268013159113f7ba8413bbd60d893d368d --- services/surfaceflinger/SurfaceFlinger.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 3dd86aa62e..99f220d0ee 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2888,15 +2888,15 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( const auto enableFrameRateOverride = [&] { using Config = scheduler::RefreshRateSelector::Config; - if (!sysprop::enable_frame_rate_override(false)) { + if (!sysprop::enable_frame_rate_override(true)) { return Config::FrameRateOverride::Disabled; } - if (sysprop::frame_rate_override_for_native_rates(true)) { + if (sysprop::frame_rate_override_for_native_rates(false)) { return Config::FrameRateOverride::AppOverrideNativeRefreshRates; } - if (!sysprop::frame_rate_override_global(false)) { + if (!sysprop::frame_rate_override_global(true)) { return Config::FrameRateOverride::AppOverride; } -- GitLab From 2d8c79afe6dbbb6c9c04bed008a73b6438b14df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josep=20del=20R=C3=ADo?= Date: Mon, 23 Jan 2023 19:33:50 +0000 Subject: [PATCH 0751/1310] Revert "Use both mouse and touchpad sources for touchpads" This reverts commit 16a24cc68aecf8f5b29efb1ef9420b3060559446. Reason for revert: The change affects the accuracy and functionality of the original touchpad stack Change-Id: Idf3ffa805aec8f5e36e04b5a86e3337ae62e6a70 --- .../reader/mapper/TouchInputMapper.cpp | 2 -- services/inputflinger/tests/InputReader_test.cpp | 16 ++++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 16bc381a9c..615889ebe3 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -897,8 +897,6 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) mDeviceMode = DeviceMode::POINTER; if (hasStylus()) { mSource |= AINPUT_SOURCE_STYLUS; - } else { - mSource |= AINPUT_SOURCE_TOUCHPAD; } } else if (isTouchScreen()) { mSource = AINPUT_SOURCE_TOUCHSCREEN; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 076be9eb70..bc70584af8 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -5521,7 +5521,7 @@ TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndNot prepareAxes(POSITION); SingleTouchInputMapper& mapper = addMapperAndConfigure(); - ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); + ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); } TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchScreen_ReturnsTouchScreen) { @@ -8791,8 +8791,8 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) { prepareAxes(POSITION); MultiTouchInputMapper& mapper = addMapperAndConfigure(); - // Check source is a touchpad that would obtain the PointerController. - ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); + // Check source is mouse that would obtain the PointerController. + ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); NotifyMotionArgs motionArgs; processPosition(mapper, 100, 100); @@ -9795,11 +9795,11 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); - // A non captured touchpad should have a mouse and touchpad source. + // non captured touchpad should be a mouse source mFakePolicy->setPointerCapture(false); configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); + ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); } TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) { @@ -9858,10 +9858,10 @@ TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) { mFakePolicy->setPointerCapture(false); MultiTouchInputMapper& mapper = addMapperAndConfigure(); - // An uncaptured touchpad should be a pointer device, with additional touchpad source. - ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); + // uncaptured touchpad should be a pointer device + ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); - // A captured touchpad should just have a touchpad source. + // captured touchpad should be a touchpad device mFakePolicy->setPointerCapture(true); configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); -- GitLab From 076acace33a13f588d3e7450872e738bc1fa3f06 Mon Sep 17 00:00:00 2001 From: Chavi Weingarten Date: Thu, 19 Jan 2023 17:20:43 +0000 Subject: [PATCH 0752/1310] SurfaceFlinger: Add TrustedPresentationListener API TrustedPresentationListener is intended to allow the producer of a buffer layer to gain a trusted signal on whether and to what extent a layer is presented. A strawman design would be to provide the producer details on it's presentation (alpha, position, scale, final crop, covered region, etc). In the strawman design the client end would then decide itself whether each of these parameters were in an acceptable range. However providing the client feedback on it's per frame position would have a negative system health impact. Furthermore in some of the target use cases we can't even be sure the layer of interest is actively producing buffers and so there may be no existing callback to piggy-back on. Because of this we use a server side thresholding approach, where the client expresses some visibility threshold and a time stability constraint. See SurfaceComposerClient.h comment for details on these thresholds and their computation. Bug: 256993331 Test: LayerTrustedPresentationListener_test.cpp Change-Id: If4bef60dc6b22ce4959c353fa2a19b0994a00f5c --- libs/gui/ITransactionCompletedListener.cpp | 11 +- libs/gui/LayerState.cpp | 27 ++ libs/gui/SurfaceComposerClient.cpp | 80 ++++++ .../gui/TrustedPresentationThresholds.aidl | 24 ++ .../gui/ITransactionCompletedListener.h | 2 + libs/gui/include/gui/LayerState.h | 21 +- libs/gui/include/gui/SurfaceComposerClient.h | 72 ++++++ services/surfaceflinger/Layer.cpp | 104 ++++++++ services/surfaceflinger/Layer.h | 22 ++ .../surfaceflinger/Scheduler/MessageQueue.cpp | 4 + .../surfaceflinger/Scheduler/MessageQueue.h | 2 + services/surfaceflinger/Scheduler/Scheduler.h | 7 + services/surfaceflinger/SurfaceFlinger.cpp | 24 +- services/surfaceflinger/SurfaceFlinger.h | 6 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 2 +- services/surfaceflinger/tests/Android.bp | 1 + .../LayerTrustedPresentationListener_test.cpp | 241 ++++++++++++++++++ 17 files changed, 644 insertions(+), 6 deletions(-) create mode 100644 libs/gui/aidl/android/gui/TrustedPresentationThresholds.aidl create mode 100644 services/surfaceflinger/tests/LayerTrustedPresentationListener_test.cpp diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp index 2b25b614e9..985c54922d 100644 --- a/libs/gui/ITransactionCompletedListener.cpp +++ b/libs/gui/ITransactionCompletedListener.cpp @@ -33,7 +33,8 @@ enum class Tag : uint32_t { ON_TRANSACTION_COMPLETED = IBinder::FIRST_CALL_TRANSACTION, ON_RELEASE_BUFFER, ON_TRANSACTION_QUEUE_STALLED, - LAST = ON_TRANSACTION_QUEUE_STALLED, + ON_TRUSTED_PRESENTATION_CHANGED, + LAST = ON_TRUSTED_PRESENTATION_CHANGED, }; } // Anonymous namespace @@ -302,6 +303,11 @@ public: onTransactionQueueStalled)>(Tag::ON_TRANSACTION_QUEUE_STALLED, reason); } + + void onTrustedPresentationChanged(int id, bool inTrustedPresentationState) override { + callRemoteAsync( + Tag::ON_TRUSTED_PRESENTATION_CHANGED, id, inTrustedPresentationState); + } }; // Out-of-line virtual method definitions to trigger vtable emission in this translation unit (see @@ -325,6 +331,9 @@ status_t BnTransactionCompletedListener::onTransact(uint32_t code, const Parcel& case Tag::ON_TRANSACTION_QUEUE_STALLED: return callLocalAsync(data, reply, &ITransactionCompletedListener::onTransactionQueueStalled); + case Tag::ON_TRUSTED_PRESENTATION_CHANGED: + return callLocalAsync(data, reply, + &ITransactionCompletedListener::onTrustedPresentationChanged); } } diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 59b62fe58c..43acb16299 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -185,6 +185,8 @@ status_t layer_state_t::write(Parcel& output) const if (hasBufferData) { SAFE_PARCEL(output.writeParcelable, *bufferData); } + SAFE_PARCEL(output.writeParcelable, trustedPresentationThresholds); + SAFE_PARCEL(output.writeParcelable, trustedPresentationListener); return NO_ERROR; } @@ -315,6 +317,10 @@ status_t layer_state_t::read(const Parcel& input) } else { bufferData = nullptr; } + + SAFE_PARCEL(input.readParcelable, &trustedPresentationThresholds); + SAFE_PARCEL(input.readParcelable, &trustedPresentationListener); + return NO_ERROR; } @@ -553,6 +559,11 @@ void layer_state_t::merge(const layer_state_t& other) { what |= eBufferChanged; bufferData = other.bufferData; } + if (other.what & eTrustedPresentationInfoChanged) { + what |= eTrustedPresentationInfoChanged; + trustedPresentationListener = other.trustedPresentationListener; + trustedPresentationThresholds = other.trustedPresentationThresholds; + } if (other.what & eDataspaceChanged) { what |= eDataspaceChanged; dataspace = other.dataspace; @@ -998,4 +1009,20 @@ status_t BufferData::readFromParcel(const Parcel* input) { return NO_ERROR; } +status_t TrustedPresentationListener::writeToParcel(Parcel* parcel) const { + SAFE_PARCEL(parcel->writeStrongBinder, callbackInterface); + SAFE_PARCEL(parcel->writeInt32, callbackId); + return NO_ERROR; +} + +status_t TrustedPresentationListener::readFromParcel(const Parcel* parcel) { + sp tmpBinder = nullptr; + SAFE_PARCEL(parcel->readNullableStrongBinder, &tmpBinder); + if (tmpBinder) { + callbackInterface = checked_interface_cast(tmpBinder); + } + SAFE_PARCEL(parcel->readInt32, &callbackId); + return NO_ERROR; +} + }; // namespace android diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 92125ead1f..732a168daa 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -63,6 +64,7 @@ namespace android { using aidl::android::hardware::graphics::common::DisplayDecorationSupport; using gui::FocusRequest; using gui::IRegionSamplingListener; +using gui::TrustedPresentationThresholds; using gui::WindowInfo; using gui::WindowInfoHandle; using gui::WindowInfosListener; @@ -518,6 +520,45 @@ void TransactionCompletedListener::removeReleaseBufferCallback( } } +SurfaceComposerClient::PresentationCallbackRAII::PresentationCallbackRAII( + TransactionCompletedListener* tcl, int id) { + mTcl = tcl; + mId = id; +} + +SurfaceComposerClient::PresentationCallbackRAII::~PresentationCallbackRAII() { + mTcl->clearTrustedPresentationCallback(mId); +} + +sp +TransactionCompletedListener::addTrustedPresentationCallback(TrustedPresentationCallback tpc, + int id, void* context) { + std::scoped_lock lock(mMutex); + mTrustedPresentationCallbacks[id] = + std::tuple(tpc, context); + return new SurfaceComposerClient::PresentationCallbackRAII(this, id); +} + +void TransactionCompletedListener::clearTrustedPresentationCallback(int id) { + std::scoped_lock lock(mMutex); + mTrustedPresentationCallbacks.erase(id); +} + +void TransactionCompletedListener::onTrustedPresentationChanged(int id, + bool presentedWithinThresholds) { + TrustedPresentationCallback tpc; + void* context; + { + std::scoped_lock lock(mMutex); + auto it = mTrustedPresentationCallbacks.find(id); + if (it == mTrustedPresentationCallbacks.end()) { + return; + } + std::tie(tpc, context) = it->second; + } + tpc(context, presentedWithinThresholds); +} + // --------------------------------------------------------------------------- void removeDeadBufferCallback(void* /*context*/, uint64_t graphicBufferId); @@ -2098,6 +2139,45 @@ void SurfaceComposerClient::Transaction::clearFrameTimelineInfo(FrameTimelineInf t.startTimeNanos = 0; } +SurfaceComposerClient::Transaction& +SurfaceComposerClient::Transaction::setTrustedPresentationCallback( + const sp& sc, TrustedPresentationCallback cb, + const TrustedPresentationThresholds& thresholds, void* context, + sp& outCallbackRef) { + auto listener = TransactionCompletedListener::getInstance(); + outCallbackRef = listener->addTrustedPresentationCallback(cb, sc->getLayerId(), context); + + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + s->what |= layer_state_t::eTrustedPresentationInfoChanged; + s->trustedPresentationThresholds = thresholds; + s->trustedPresentationListener.callbackInterface = TransactionCompletedListener::getIInstance(); + s->trustedPresentationListener.callbackId = sc->getLayerId(); + + return *this; +} + +SurfaceComposerClient::Transaction& +SurfaceComposerClient::Transaction::clearTrustedPresentationCallback(const sp& sc) { + auto listener = TransactionCompletedListener::getInstance(); + listener->clearTrustedPresentationCallback(sc->getLayerId()); + + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + s->what |= layer_state_t::eTrustedPresentationInfoChanged; + s->trustedPresentationThresholds = TrustedPresentationThresholds(); + s->trustedPresentationListener.callbackInterface = nullptr; + s->trustedPresentationListener.callbackId = -1; + + return *this; +} + // --------------------------------------------------------------------------- SurfaceComposerClient::SurfaceComposerClient() : mStatus(NO_INIT) {} diff --git a/libs/gui/aidl/android/gui/TrustedPresentationThresholds.aidl b/libs/gui/aidl/android/gui/TrustedPresentationThresholds.aidl new file mode 100644 index 0000000000..1eea5b44a4 --- /dev/null +++ b/libs/gui/aidl/android/gui/TrustedPresentationThresholds.aidl @@ -0,0 +1,24 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +parcelable TrustedPresentationThresholds { + float minAlpha = -1.0f; + float minFractionRendered = -1.0f; + + int stabilityRequirementMs = 0; +} diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h index 453e8f3ef5..d593f56c3a 100644 --- a/libs/gui/include/gui/ITransactionCompletedListener.h +++ b/libs/gui/include/gui/ITransactionCompletedListener.h @@ -196,6 +196,8 @@ public: uint32_t currentMaxAcquiredBufferCount) = 0; virtual void onTransactionQueueStalled(const String8& name) = 0; + + virtual void onTrustedPresentationChanged(int id, bool inTrustedPresentationState) = 0; }; class BnTransactionCompletedListener : public SafeBnInterface { diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 45a84f6c66..17ed2d84fa 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -56,6 +57,8 @@ class Parcel; using gui::ISurfaceComposerClient; using gui::LayerMetadata; +using gui::TrustedPresentationThresholds; + struct client_cache_t { wp token = nullptr; uint64_t id; @@ -65,6 +68,19 @@ struct client_cache_t { bool isValid() const { return token != nullptr; } }; +class TrustedPresentationListener : public Parcelable { +public: + sp callbackInterface; + int callbackId = -1; + + void invoke(bool presentedWithinThresholds) { + callbackInterface->onTrustedPresentationChanged(callbackId, presentedWithinThresholds); + } + + status_t writeToParcel(Parcel* parcel) const; + status_t readFromParcel(const Parcel* parcel); +}; + class BufferData : public Parcelable { public: virtual ~BufferData() = default; @@ -148,7 +164,7 @@ struct layer_state_t { enum { ePositionChanged = 0x00000001, eLayerChanged = 0x00000002, - /* unused = 0x00000004, */ + eTrustedPresentationInfoChanged = 0x00000004, eAlphaChanged = 0x00000008, eMatrixChanged = 0x00000010, eTransparentRegionChanged = 0x00000020, @@ -339,6 +355,9 @@ struct layer_state_t { gui::DropInputMode dropInputMode; bool dimmingEnabled; + + TrustedPresentationThresholds trustedPresentationThresholds; + TrustedPresentationListener trustedPresentationListener; }; class ComposerState { diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 2458a40ce0..6c45b260df 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -58,6 +58,7 @@ class HdrCapabilities; class IGraphicBufferProducer; class ITunnelModeEnabledListener; class Region; +class TransactionCompletedListener; using gui::DisplayCaptureArgs; using gui::IRegionSamplingListener; @@ -106,6 +107,8 @@ using SurfaceStatsCallback = const sp& /*presentFence*/, const SurfaceStats& /*stats*/)>; +using TrustedPresentationCallback = std::function; + // --------------------------------------------------------------------------- class ReleaseCallbackThread { @@ -390,6 +393,13 @@ public: std::unordered_set, SCHash> surfaceControls; }; + struct PresentationCallbackRAII : public RefBase { + sp mTcl; + int mId; + PresentationCallbackRAII(TransactionCompletedListener* tcl, int id); + virtual ~PresentationCallbackRAII(); + }; + class Transaction : public Parcelable { private: static sp sApplyToken; @@ -569,6 +579,59 @@ public: Transaction& addTransactionCommittedCallback( TransactionCompletedCallbackTakesContext callback, void* callbackContext); + /** + * Set a callback to receive feedback about the presentation of a layer. + * When the layer is presented according to the passed in Thresholds, + * it is said to "enter the state", and receives the callback with true. + * When the conditions fall out of thresholds, it is then said to leave the + * state. + * + * There are a few simple thresholds: + * minAlpha: Lower bound on computed alpha + * minFractionRendered: Lower bounds on fraction of pixels that + * were rendered. + * stabilityThresholdMs: A time that alpha and fraction rendered + * must remain within bounds before we can "enter the state" + * + * The fraction of pixels rendered is a computation based on scale, crop + * and occlusion. The calculation may be somewhat counterintuitive, so we + * can work through an example. Imagine we have a layer with a 100x100 buffer + * which is occluded by (10x100) pixels on the left, and cropped by (100x10) pixels + * on the top. Furthermore imagine this layer is scaled by 0.9 in both dimensions. + * (c=crop,o=occluded,b=both,x=none + * b c c c + * o x x x + * o x x x + * o x x x + * + * We first start by computing fr=xscale*yscale=0.9*0.9=0.81, indicating + * that "81%" of the pixels were rendered. This corresponds to what was 100 + * pixels being displayed in 81 pixels. This is somewhat of an abuse of + * language, as the information of merged pixels isn't totally lost, but + * we err on the conservative side. + * + * We then repeat a similar process for the crop and covered regions and + * accumulate the results: fr = fr * (fractionNotCropped) * (fractionNotCovered) + * So for this example we would get 0.9*0.9*0.9*0.9=0.65... + * + * Notice that this is not completely accurate, as we have double counted + * the region marked as b. However we only wanted a "lower bound" and so it + * is ok to err in this direction. Selection of the threshold will ultimately + * be somewhat arbitrary, and so there are some somewhat arbitrary decisions in + * this API as well. + * + * The caller must keep "PresentationCallbackRAII" alive, or the callback + * in SurfaceComposerClient will be unregistered. + */ + Transaction& setTrustedPresentationCallback(const sp& sc, + TrustedPresentationCallback callback, + const TrustedPresentationThresholds& thresholds, + void* context, + sp& outCallbackOwner); + + // Clear local memory in SCC + Transaction& clearTrustedPresentationCallback(const sp& sc); + // ONLY FOR BLAST ADAPTER Transaction& notifyProducerDisconnect(const sp& sc); @@ -795,6 +858,9 @@ protected: std::multimap mSurfaceStatsListeners; std::unordered_map> mQueueStallListeners; + std::unordered_map> + mTrustedPresentationCallbacks; + public: static sp getInstance(); static sp getIInstance(); @@ -814,6 +880,10 @@ public: void addQueueStallListener(std::function stallListener, void* id); void removeQueueStallListener(void *id); + sp addTrustedPresentationCallback( + TrustedPresentationCallback tpc, int id, void* context); + void clearTrustedPresentationCallback(int id); + /* * Adds a jank listener to be informed about SurfaceFlinger's jank classification for a specific * surface. Jank classifications arrive as part of the transaction callbacks about previous @@ -844,6 +914,8 @@ public: void onTransactionQueueStalled(const String8& reason) override; + void onTrustedPresentationChanged(int id, bool presentedWithinThresholds) override; + private: ReleaseBufferCallback popReleaseBufferCallbackLocked(const ReleaseCallbackId&); static sp sInstance; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index de9ea0420c..6197e9bb47 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -250,6 +250,10 @@ Layer::~Layer() { if (mHadClonedChild) { mFlinger->mNumClones--; } + if (hasTrustedPresentationListener()) { + mFlinger->mNumTrustedPresentationListeners--; + updateTrustedPresentationState(nullptr, -1 /* time_in_ms */, true /* leaveState*/); + } } // --------------------------------------------------------------------------- @@ -279,6 +283,7 @@ void Layer::removeFromCurrentState() { mRemovedFromDrawingState = true; mFlinger->mScheduler->deregisterLayer(this); } + updateTrustedPresentationState(nullptr, -1 /* time_in_ms */, true /* leaveState*/); mFlinger->markLayerPendingRemovalLocked(sp::fromExisting(this)); } @@ -376,6 +381,92 @@ FloatRect Layer::getBounds(const Region& activeTransparentRegion) const { return reduce(mBounds, activeTransparentRegion); } +// No early returns. +void Layer::updateTrustedPresentationState(const DisplayDevice* display, int64_t time_in_ms, + bool leaveState) { + if (!hasTrustedPresentationListener()) { + return; + } + const bool lastState = mLastComputedTrustedPresentationState; + mLastComputedTrustedPresentationState = false; + + if (!leaveState) { + const auto outputLayer = findOutputLayerForDisplay(display); + if (outputLayer != nullptr) { + mLastComputedTrustedPresentationState = + computeTrustedPresentationState(mBounds, mSourceBounds, + outputLayer->getState().coveredRegion, + mScreenBounds, getCompositionState()->alpha, + getCompositionState()->geomLayerTransform, + mTrustedPresentationThresholds); + } + } + const bool newState = mLastComputedTrustedPresentationState; + if (lastState && !newState) { + // We were in the trusted presentation state, but now we left it, + // emit the callback if needed + if (mLastReportedTrustedPresentationState) { + mLastReportedTrustedPresentationState = false; + mTrustedPresentationListener.invoke(false); + } + // Reset the timer + mEnteredTrustedPresentationStateTime = -1; + } else if (!lastState && newState) { + // We were not in the trusted presentation state, but we entered it, begin the timer + // and make sure this gets called at least once more! + mEnteredTrustedPresentationStateTime = time_in_ms; + mFlinger->forceFutureUpdate(mTrustedPresentationThresholds.stabilityRequirementMs * 1.5); + } + + // Has the timer elapsed, but we are still in the state? Emit a callback if needed + if (!mLastReportedTrustedPresentationState && newState && + (time_in_ms - mEnteredTrustedPresentationStateTime > + mTrustedPresentationThresholds.stabilityRequirementMs)) { + mLastReportedTrustedPresentationState = true; + mTrustedPresentationListener.invoke(true); + } +} + +/** + * See SurfaceComposerClient.h: setTrustedPresentationCallback for discussion + * of how the parameters and thresholds are interpreted. The general spirit is + * to produce an upper bound on the amount of the buffer which was presented. + */ +bool Layer::computeTrustedPresentationState(const FloatRect& bounds, const FloatRect& sourceBounds, + const Region& coveredRegion, + const FloatRect& screenBounds, float alpha, + const ui::Transform& effectiveTransform, + const TrustedPresentationThresholds& thresholds) { + if (alpha < thresholds.minAlpha) { + return false; + } + if (sourceBounds.getWidth() == 0 || sourceBounds.getHeight() == 0) { + return false; + } + if (screenBounds.getWidth() == 0 || screenBounds.getHeight() == 0) { + return false; + } + + const float sx = effectiveTransform.dsdx(); + const float sy = effectiveTransform.dsdy(); + float fractionRendered = std::min(sx * sy, 1.0f); + + float boundsOverSourceW = bounds.getWidth() / (float)sourceBounds.getWidth(); + float boundsOverSourceH = bounds.getHeight() / (float)sourceBounds.getHeight(); + fractionRendered *= boundsOverSourceW * boundsOverSourceH; + + Rect coveredBounds = coveredRegion.bounds(); + fractionRendered *= (1 - + ((coveredBounds.width() / (float)screenBounds.getWidth()) * + coveredBounds.height() / (float)screenBounds.getHeight())); + + if (fractionRendered < thresholds.minFractionRendered) { + return false; + } + + return true; +} + void Layer::computeBounds(FloatRect parentBounds, ui::Transform parentTransform, float parentShadowRadius) { const State& s(getDrawingState()); @@ -3988,6 +4079,19 @@ LayerSnapshotGuard& LayerSnapshotGuard::operator=(LayerSnapshotGuard&& other) { return *this; } +void Layer::setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds, + TrustedPresentationListener const& listener) { + bool hadTrustedPresentationListener = hasTrustedPresentationListener(); + mTrustedPresentationListener = listener; + mTrustedPresentationThresholds = thresholds; + bool haveTrustedPresentationListener = hasTrustedPresentationListener(); + if (!hadTrustedPresentationListener && haveTrustedPresentationListener) { + mFlinger->mNumTrustedPresentationListeners++; + } else if (hadTrustedPresentationListener && !haveTrustedPresentationListener) { + mFlinger->mNumTrustedPresentationListeners--; + } +} + // --------------------------------------------------------------------------- std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate) { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 08a13a34eb..63894cd83d 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -527,6 +527,19 @@ public: uint32_t getTransactionFlags() const { return mTransactionFlags; } + static bool computeTrustedPresentationState(const FloatRect& bounds, + const FloatRect& sourceBounds, + const Region& coveredRegion, + const FloatRect& screenBounds, float, + const ui::Transform&, + const TrustedPresentationThresholds&); + void updateTrustedPresentationState(const DisplayDevice* display, int64_t time_in_ms, + bool leaveState); + + inline bool hasTrustedPresentationListener() { + return mTrustedPresentationListener.callbackInterface != nullptr; + } + // Sets the masked bits. void setTransactionFlags(uint32_t mask); @@ -728,6 +741,9 @@ public: std::shared_ptr createSurfaceFrameForBuffer( const FrameTimelineInfo& info, nsecs_t queueTime, std::string debugName); + void setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds, + TrustedPresentationListener const& listener); + // Creates a new handle each time, so we only expect // this to be called once. sp getHandle(); @@ -883,6 +899,12 @@ protected: // These are only accessed by the main thread or the tracing thread. State mDrawingState; + TrustedPresentationThresholds mTrustedPresentationThresholds; + TrustedPresentationListener mTrustedPresentationListener; + bool mLastComputedTrustedPresentationState = false; + bool mLastReportedTrustedPresentationState = false; + int64_t mEnteredTrustedPresentationStateTime = -1; + uint32_t mTransactionFlags{0}; // Updated in doTransaction, used to track the last sequence number we // committed. Currently this is really only used for updating visible diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp index e827c12f7c..9b044977be 100644 --- a/services/surfaceflinger/Scheduler/MessageQueue.cpp +++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp @@ -132,6 +132,10 @@ void MessageQueue::postMessage(sp&& handler) { mLooper->sendMessage(handler, Message()); } +void MessageQueue::postMessageDelayed(sp&& handler, nsecs_t uptimeDelay) { + mLooper->sendMessageDelayed(uptimeDelay, handler, Message()); +} + void MessageQueue::scheduleConfigure() { struct ConfigureHandler : MessageHandler { explicit ConfigureHandler(ICompositor& compositor) : compositor(compositor) {} diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h index 71f8645828..ad0ea72623 100644 --- a/services/surfaceflinger/Scheduler/MessageQueue.h +++ b/services/surfaceflinger/Scheduler/MessageQueue.h @@ -79,6 +79,7 @@ public: virtual void setDuration(std::chrono::nanoseconds workDuration) = 0; virtual void waitMessage() = 0; virtual void postMessage(sp&&) = 0; + virtual void postMessageDelayed(sp&&, nsecs_t uptimeDelay) = 0; virtual void scheduleConfigure() = 0; virtual void scheduleFrame() = 0; @@ -144,6 +145,7 @@ public: void waitMessage() override; void postMessage(sp&&) override; + void postMessageDelayed(sp&&, nsecs_t uptimeDelay) override; void scheduleConfigure() override; void scheduleFrame() override; diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 20221d1907..36280e3888 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -139,6 +139,13 @@ public: return std::move(future); } + template > + [[nodiscard]] std::future scheduleDelayed(F&& f, nsecs_t uptimeDelay) { + auto [task, future] = makeTask(std::move(f)); + postMessageDelayed(std::move(task), uptimeDelay); + return std::move(future); + } + ConnectionHandle createConnection(const char* connectionName, frametimeline::TokenManager*, std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 2d8b9c1672..41e305e986 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2439,7 +2439,7 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) scheduleComposite(FrameHint::kNone); } - postComposition(); + postComposition(presentTime); const bool prevFrameHadClientComposition = mHadClientComposition; @@ -2553,7 +2553,7 @@ ui::Rotation SurfaceFlinger::getPhysicalDisplayOrientation(DisplayId displayId, return ui::ROTATION_0; } -void SurfaceFlinger::postComposition() { +void SurfaceFlinger::postComposition(nsecs_t callTime) { ATRACE_CALL(); ALOGV(__func__); @@ -2718,6 +2718,17 @@ void SurfaceFlinger::postComposition() { } } + if (mNumTrustedPresentationListeners > 0) { + // We avoid any reverse traversal upwards so this shouldn't be too expensive + mDrawingState.traverse([&](Layer* layer) { + if (!layer->hasTrustedPresentationListener()) { + return; + } + layer->updateTrustedPresentationState(display, nanoseconds_to_milliseconds(callTime), + false); + }); + } + // Even though ATRACE_INT64 already checks if tracing is enabled, it doesn't prevent the // side-effect of getTotalSize(), so we check that again here if (ATRACE_ENABLED()) { @@ -4593,6 +4604,11 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime layer->setFrameTimelineVsyncForBufferlessTransaction(frameTimelineInfo, postTime); } + if (what & layer_state_t::eTrustedPresentationInfoChanged) { + layer->setTrustedPresentationInfo(s.trustedPresentationThresholds, + s.trustedPresentationListener); + } + if (layer->setTransactionCompletedListeners(callbackHandles)) flags |= eTraversalNeeded; // Do not put anything that updates layer state or modifies flags after // setTransactionCompletedListener @@ -8118,6 +8134,10 @@ status_t SurfaceComposerAIDL::checkReadFrameBufferPermission() { return OK; } +void SurfaceFlinger::forceFutureUpdate(int delayInMs) { + static_cast(mScheduler->scheduleDelayed([&]() { scheduleRepaint(); }, ms2ns(delayInMs))); +} + } // namespace android #if defined(__gl_h_) diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 33f0402094..b52dc82773 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -313,6 +313,8 @@ public: // TODO(b/246793311): Clean up a temporary property bool mIgnoreHwcPhysicalDisplayOrientation = false; + void forceFutureUpdate(int delayInMs); + protected: // We're reference counted, never destroy SurfaceFlinger directly virtual ~SurfaceFlinger(); @@ -926,7 +928,7 @@ private: /* * Compositing */ - void postComposition() REQUIRES(kMainThreadContext); + void postComposition(nsecs_t callTime) REQUIRES(kMainThreadContext); /* * Display management @@ -1274,6 +1276,8 @@ private: float mDimmingRatio = -1.f; std::unique_ptr mRenderEngine; + std::atomic mNumTrustedPresentationListeners = 0; + std::unique_ptr mCompositionEngine; // mMaxRenderTargetSize is only set once in init() so it doesn't need to be protected by // any mutex. diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 81ca659915..6b52a3ac13 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -599,7 +599,7 @@ public: mFlinger->commitTransactions(); mFlinger->flushTransactionQueues(getFuzzedVsyncId(mFdp)); - mFlinger->postComposition(); + mFlinger->postComposition(systemTime()); } mFlinger->setTransactionFlags(mFdp.ConsumeIntegral()); diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp index bd6367d672..de47330216 100644 --- a/services/surfaceflinger/tests/Android.bp +++ b/services/surfaceflinger/tests/Android.bp @@ -43,6 +43,7 @@ cc_test { "LayerRenderTypeTransaction_test.cpp", "LayerState_test.cpp", "LayerTransaction_test.cpp", + "LayerTrustedPresentationListener_test.cpp", "LayerTypeAndRenderTypeTransaction_test.cpp", "LayerTypeTransaction_test.cpp", "LayerUpdate_test.cpp", diff --git a/services/surfaceflinger/tests/LayerTrustedPresentationListener_test.cpp b/services/surfaceflinger/tests/LayerTrustedPresentationListener_test.cpp new file mode 100644 index 0000000000..a843fd17b1 --- /dev/null +++ b/services/surfaceflinger/tests/LayerTrustedPresentationListener_test.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// TODO(b/129481165): remove the #pragma below and fix conversion issues +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wconversion" + +#include +#include +#include +#include "TransactionTestHarnesses.h" + +namespace android { +struct PresentationCallbackHelper { + void callbackArrived(bool state) { + std::unique_lock l(mMutex); + mGotCallback = true; + mState = state; + mCondition.notify_all(); + } + bool awaitCallback() { + std::unique_lock l(mMutex); + mGotCallback = false; + mCondition.wait_for(l, 5000ms); + EXPECT_TRUE(mGotCallback); + return mState; + } + + bool mState; + bool mGotCallback; + std::mutex mMutex; + std::condition_variable mCondition; +}; + +TrustedPresentationThresholds thresh() { + TrustedPresentationThresholds thresholds; + thresholds.minAlpha = 1.0; + thresholds.minFractionRendered = 1.0; + thresholds.stabilityRequirementMs = 100; + return thresholds; +} + +class LayerTrustedPresentationListenerTest : public LayerTransactionTest { +public: + void SetUp() override { + LayerTransactionTest::SetUp(); + mainLayer = makeLayer(); + thresholds = thresh(); + } + + void TearDown() override { + LayerTransactionTest::TearDown(); + mCallback = nullptr; + t.reparent(mainLayer, nullptr).apply(); + mainLayer = nullptr; + } + + void thresholdsPrepared() { + t.show(mainLayer) + .setLayer(mainLayer, INT32_MAX) + .setTrustedPresentationCallback( + mainLayer, + [&](void* context, bool state) { + PresentationCallbackHelper* helper = + (PresentationCallbackHelper*)context; + helper->callbackArrived(state); + }, + thresholds, &pch, mCallback) + .setPosition(mainLayer, 100, 100) + .apply(); + } + + sp makeLayer() { + sp layer = + createLayer("test", 100, 100, ISurfaceComposerClient::eFXSurfaceBufferState, + mBlackBgSurface.get()); + fillBufferLayerColor(layer, Color::RED, 100, 100); + return layer; + } + sp mainLayer; + PresentationCallbackHelper pch; + SurfaceComposerClient::Transaction t; + TrustedPresentationThresholds thresholds; + sp mCallback; +}; + +// The layer is fully presented with the default test setup. +TEST_F(LayerTrustedPresentationListenerTest, callback_arrives) { + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); +} + +// A hidden layer can't be considered presented! +TEST_F(LayerTrustedPresentationListenerTest, hiding_layer_clears_state) { + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + t.hide(mainLayer).apply(); + EXPECT_FALSE(pch.awaitCallback()); +} + +// A fully obscured layer can't be considered presented! +TEST_F(LayerTrustedPresentationListenerTest, obscuring_clears_state) { + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + + auto otherLayer = makeLayer(); + t.show(otherLayer) + .setPosition(otherLayer, 100, 100) + .setLayer(otherLayer, INT32_MAX) + .setLayer(mainLayer, INT32_MAX - 1) + .apply(); + EXPECT_FALSE(pch.awaitCallback()); +} + +// Even if the layer obscuring us has an Alpha channel, we are still considered +// obscured. +TEST_F(LayerTrustedPresentationListenerTest, obscuring_with_transparency_clears_state) { + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + + auto otherLayer = makeLayer(); + t.show(otherLayer) + .setPosition(otherLayer, 100, 100) + .setLayer(otherLayer, INT32_MAX) + .setFlags(otherLayer, 0, layer_state_t::eLayerOpaque) + .setLayer(mainLayer, INT32_MAX - 1) + .apply(); + EXPECT_FALSE(pch.awaitCallback()); +} + +// We can't be presented if our alpha is below the threshold. +TEST_F(LayerTrustedPresentationListenerTest, alpha_below_threshold) { + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + t.setAlpha(mainLayer, 0.9).apply(); + EXPECT_FALSE(pch.awaitCallback()); + t.setAlpha(mainLayer, 1.0).apply(); + EXPECT_TRUE(pch.awaitCallback()); +} + +// Verify that the passed in threshold is actually respected! +TEST_F(LayerTrustedPresentationListenerTest, alpha_below_other_threshold) { + thresholds.minAlpha = 0.8; + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + t.setAlpha(mainLayer, 0.8).apply(); + EXPECT_FALSE(pch.awaitCallback()); + t.setAlpha(mainLayer, 0.9).apply(); + EXPECT_TRUE(pch.awaitCallback()); +} + +// (86*86)/(100*100) = 0.73...so a crop of 86x86 is below the threshold +// (87*87)/(100*100) = 0.76...so a crop of 87x87 is above the threshold! +TEST_F(LayerTrustedPresentationListenerTest, crop_below_threshold) { + thresholds.minFractionRendered = 0.75; + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + t.setCrop(mainLayer, Rect(0, 0, 86, 86)).apply(); + EXPECT_FALSE(pch.awaitCallback()); + t.setCrop(mainLayer, Rect(0, 0, 87, 87)).apply(); + EXPECT_TRUE(pch.awaitCallback()); +} + +TEST_F(LayerTrustedPresentationListenerTest, scale_below_threshold) { + thresholds.minFractionRendered = 0.64; + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + // 0.8 = sqrt(0.64) + t.setMatrix(mainLayer, 0.79, 0, 0, 0.79).apply(); + EXPECT_FALSE(pch.awaitCallback()); + t.setMatrix(mainLayer, 0.81, 0, 0, 0.81).apply(); + EXPECT_TRUE(pch.awaitCallback()); +} + +TEST_F(LayerTrustedPresentationListenerTest, obscuring_with_threshold_1) { + thresholds.minFractionRendered = 0.75; + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + + auto otherLayer = makeLayer(); + t.show(otherLayer) + .setPosition(otherLayer, 100, 100) + .setLayer(otherLayer, INT32_MAX) + .setLayer(mainLayer, INT32_MAX - 1) + .apply(); + EXPECT_FALSE(pch.awaitCallback()); + t.setMatrix(otherLayer, 0.49, 0, 0, 0.49).apply(); + EXPECT_TRUE(pch.awaitCallback()); + t.setMatrix(otherLayer, 0.51, 0, 0, 0.51).apply(); + EXPECT_FALSE(pch.awaitCallback()); +} + +TEST_F(LayerTrustedPresentationListenerTest, obscuring_with_threshold_2) { + thresholds.minFractionRendered = 0.9; + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + + auto otherLayer = makeLayer(); + t.show(otherLayer) + .setPosition(otherLayer, 100, 100) + .setLayer(otherLayer, INT32_MAX) + .setLayer(mainLayer, INT32_MAX - 1) + .apply(); + EXPECT_FALSE(pch.awaitCallback()); + t.setMatrix(otherLayer, 0.3, 0, 0, 0.3).apply(); + EXPECT_TRUE(pch.awaitCallback()); + t.setMatrix(otherLayer, 0.33, 0, 0, 0.33).apply(); + EXPECT_FALSE(pch.awaitCallback()); +} + +TEST_F(LayerTrustedPresentationListenerTest, obscuring_with_alpha) { + thresholds.minFractionRendered = 0.9; + thresholdsPrepared(); + EXPECT_TRUE(pch.awaitCallback()); + + auto otherLayer = makeLayer(); + t.show(otherLayer) + .setPosition(otherLayer, 100, 100) + .setLayer(otherLayer, INT32_MAX) + .setLayer(mainLayer, INT32_MAX - 1) + .setAlpha(otherLayer, 0.01) + .apply(); + EXPECT_FALSE(pch.awaitCallback()); + t.setAlpha(otherLayer, 0.0).apply(); + EXPECT_TRUE(pch.awaitCallback()); +} + +} // namespace android -- GitLab From 3f96592d3b985b258f488f5497dc02a7dc2eb5a8 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Mon, 23 Jan 2023 17:18:29 -0800 Subject: [PATCH 0753/1310] SF: Min layers should not choose a frame rate override Due to b/266444890, we need to limit the frame rate override usages for Min layers (such as infrequent layers) as it creates jank for layers that just started to animate due to the slow ramp up time. Bug: 266444890 Test: [v2/android-crystalball-eng/health/microbench/systemui/main/systemui-notification-1-jank-suite] android.platform.test.scenario.notification.ConversationNotificationMicrobenchmark#conversationNotificationTest Change-Id: Ic42e00a6920b19cc6e972a13c4df54f73a55e1d7 --- .../Scheduler/RefreshRateSelector.cpp | 13 +++++++++++-- .../tests/unittests/RefreshRateSelectorTest.cpp | 10 ++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index 30821d89b3..1d27cfcece 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -932,14 +932,22 @@ auto RefreshRateSelector::rankFrameRates(std::optional anchorGroupOpt, const char* const whence = __func__; std::deque ranking; const auto rankFrameRate = [&](const FrameRateMode& frameRateMode) REQUIRES(mLock) { + using fps_approx_ops::operator<; const auto& modePtr = frameRateMode.modePtr; if (anchorGroupOpt && modePtr->getGroup() != anchorGroupOpt) { return; } + const bool ascending = (refreshRateOrder == RefreshRateOrder::Ascending); + if (ascending && frameRateMode.fps < getMinRefreshRateByPolicyLocked()->getFps()) { + // TODO(b/266481656): Once this bug is fixed, we can remove this workaround and actually + // use a lower frame rate when we want Ascending frame rates. + return; + } + float score = calculateDistanceScoreFromMax(frameRateMode.fps); - const bool inverseScore = (refreshRateOrder == RefreshRateOrder::Ascending); - if (inverseScore) { + + if (ascending) { score = 1.0f / score; } if (preferredDisplayModeOpt) { @@ -951,6 +959,7 @@ auto RefreshRateSelector::rankFrameRates(std::optional anchorGroupOpt, constexpr float kNonPreferredModePenalty = 0.95f; score *= kNonPreferredModePenalty; } + ALOGV("%s(%s) %s (%s) scored %.2f", whence, ftl::enum_string(refreshRateOrder).c_str(), to_string(frameRateMode.fps).c_str(), to_string(modePtr->getFps()).c_str(), score); ranking.emplace_back(ScoredFrameRate{frameRateMode, score}); diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp index 06f45f9197..5fddda5b35 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp @@ -2973,5 +2973,15 @@ TEST_P(RefreshRateSelectorTest, SupportsLowPhysicalRefreshRates) { EXPECT_EQ(kMode1, selector.getMinRefreshRateByPolicy()); } +// TODO(b/266481656): Once this bug is fixed, we can remove this test +TEST_P(RefreshRateSelectorTest, noLowerFrameRateOnMinVote) { + auto selector = createSelector(kModes_60_90, kModeId60); + + std::vector layers = {{.weight = 1.f}}; + layers[0].name = "Test layer"; + layers[0].vote = LayerVoteType::Min; + EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers).frameRateMode); +} + } // namespace } // namespace android::scheduler -- GitLab From d6e409e4614b68901e6d81f036d49b162fbe9278 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Thu, 19 Jan 2023 16:07:31 -0800 Subject: [PATCH 0754/1310] SF: fix FrameTimelineInfo association to buffers Store a mapping between a frame number and the FrameTimelineInfo and apply the correct FrameTimelineInfo on the transaction. Test: TBD Bug: 255875655 Change-Id: I4206984be8e5a91c5dc15b74688575d97fbb5357 --- libs/gui/BLASTBufferQueue.cpp | 33 +++++++++++++++---- libs/gui/Surface.cpp | 6 ++-- libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp | 2 +- libs/gui/include/gui/BLASTBufferQueue.h | 4 +-- libs/gui/include/gui/Surface.h | 2 +- libs/nativewindow/include/system/window.h | 5 +-- 6 files changed, 37 insertions(+), 15 deletions(-) diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index 797d6aedcc..60603ba50a 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -587,9 +587,23 @@ status_t BLASTBufferQueue::acquireNextBufferLocked( t->setDesiredPresentTime(bufferItem.mTimestamp); } - if (!mNextFrameTimelineInfoQueue.empty()) { - t->setFrameTimelineInfo(mNextFrameTimelineInfoQueue.front()); - mNextFrameTimelineInfoQueue.pop(); + // Drop stale frame timeline infos + while (!mPendingFrameTimelines.empty() && + mPendingFrameTimelines.front().first < bufferItem.mFrameNumber) { + ATRACE_FORMAT_INSTANT("dropping stale frameNumber: %" PRIu64 " vsyncId: %" PRId64, + mPendingFrameTimelines.front().first, + mPendingFrameTimelines.front().second.vsyncId); + mPendingFrameTimelines.pop(); + } + + if (!mPendingFrameTimelines.empty() && + mPendingFrameTimelines.front().first == bufferItem.mFrameNumber) { + ATRACE_FORMAT_INSTANT("Transaction::setFrameTimelineInfo frameNumber: %" PRIu64 + " vsyncId: %" PRId64, + bufferItem.mFrameNumber, + mPendingFrameTimelines.front().second.vsyncId); + t->setFrameTimelineInfo(mPendingFrameTimelines.front().second); + mPendingFrameTimelines.pop(); } { @@ -653,6 +667,7 @@ void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) { { std::unique_lock _lock{mMutex}; BBQ_TRACE(); + const bool syncTransactionSet = mTransactionReadyCallback != nullptr; BQA_LOGV("onFrameAvailable-start syncTransactionSet=%s", boolToString(syncTransactionSet)); @@ -847,12 +862,13 @@ public: return mBbq->setFrameRate(frameRate, compatibility, changeFrameRateStrategy); } - status_t setFrameTimelineInfo(const FrameTimelineInfo& frameTimelineInfo) override { + status_t setFrameTimelineInfo(uint64_t frameNumber, + const FrameTimelineInfo& frameTimelineInfo) override { std::unique_lock _lock{mMutex}; if (mDestroyed) { return DEAD_OBJECT; } - return mBbq->setFrameTimelineInfo(frameTimelineInfo); + return mBbq->setFrameTimelineInfo(frameNumber, frameTimelineInfo); } void destroy() override { @@ -874,9 +890,12 @@ status_t BLASTBufferQueue::setFrameRate(float frameRate, int8_t compatibility, return t.setFrameRate(mSurfaceControl, frameRate, compatibility, shouldBeSeamless).apply(); } -status_t BLASTBufferQueue::setFrameTimelineInfo(const FrameTimelineInfo& frameTimelineInfo) { +status_t BLASTBufferQueue::setFrameTimelineInfo(uint64_t frameNumber, + const FrameTimelineInfo& frameTimelineInfo) { + ATRACE_FORMAT("%s(%s) frameNumber: %" PRIu64 " vsyncId: %" PRId64, __func__, mName.c_str(), + frameNumber, frameTimelineInfo.vsyncId); std::unique_lock _lock{mMutex}; - mNextFrameTimelineInfoQueue.push(frameTimelineInfo); + mPendingFrameTimelines.push({frameNumber, frameTimelineInfo}); return OK; } diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index edb18a86ee..b18bf5bdee 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -1863,6 +1863,7 @@ int Surface::dispatchGetLastQueuedBuffer2(va_list args) { int Surface::dispatchSetFrameTimelineInfo(va_list args) { ATRACE_CALL(); + auto frameNumber = static_cast(va_arg(args, uint64_t)); auto frameTimelineVsyncId = static_cast(va_arg(args, int64_t)); auto inputEventId = static_cast(va_arg(args, int32_t)); auto startTimeNanos = static_cast(va_arg(args, int64_t)); @@ -1872,7 +1873,7 @@ int Surface::dispatchSetFrameTimelineInfo(va_list args) { ftlInfo.vsyncId = frameTimelineVsyncId; ftlInfo.inputEventId = inputEventId; ftlInfo.startTimeNanos = startTimeNanos; - return setFrameTimelineInfo(ftlInfo); + return setFrameTimelineInfo(frameNumber, ftlInfo); } bool Surface::transformToDisplayInverse() const { @@ -2641,7 +2642,8 @@ void Surface::ProducerListenerProxy::onBuffersDiscarded(const std::vectorsetFrameRate(mFdp.ConsumeFloatingPoint(), mFdp.ConsumeIntegral(), mFdp.ConsumeBool() /*shouldBeSeamless*/); FrameTimelineInfo info; - queue->setFrameTimelineInfo(info); + queue->setFrameTimelineInfo(mFdp.ConsumeIntegral(), info); ManageResourceHandle handle(&mFdp); queue->setSidebandStream(handle.getStream()); diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h index 001d8e5233..c93ab86770 100644 --- a/libs/gui/include/gui/BLASTBufferQueue.h +++ b/libs/gui/include/gui/BLASTBufferQueue.h @@ -106,7 +106,7 @@ public: void update(const sp& surface, uint32_t width, uint32_t height, int32_t format); status_t setFrameRate(float frameRate, int8_t compatibility, bool shouldBeSeamless); - status_t setFrameTimelineInfo(const FrameTimelineInfo& info); + status_t setFrameTimelineInfo(uint64_t frameNumber, const FrameTimelineInfo& info); void setSidebandStream(const sp& stream); @@ -231,7 +231,7 @@ private: std::vector> mPendingTransactions GUARDED_BY(mMutex); - std::queue mNextFrameTimelineInfoQueue GUARDED_BY(mMutex); + std::queue> mPendingFrameTimelines GUARDED_BY(mMutex); // Tracks the last acquired frame number uint64_t mLastAcquiredFrameNumber GUARDED_BY(mMutex) = 0; diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h index b9ccdc9124..39a59e42aa 100644 --- a/libs/gui/include/gui/Surface.h +++ b/libs/gui/include/gui/Surface.h @@ -213,7 +213,7 @@ public: virtual status_t setFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy); - virtual status_t setFrameTimelineInfo(const FrameTimelineInfo& info); + virtual status_t setFrameTimelineInfo(uint64_t frameNumber, const FrameTimelineInfo& info); protected: virtual ~Surface(); diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h index c7745e6672..6c54635824 100644 --- a/libs/nativewindow/include/system/window.h +++ b/libs/nativewindow/include/system/window.h @@ -1067,11 +1067,12 @@ static inline int native_window_set_frame_rate(struct ANativeWindow* window, flo } static inline int native_window_set_frame_timeline_info(struct ANativeWindow* window, + uint64_t frameNumber, int64_t frameTimelineVsyncId, int32_t inputEventId, int64_t startTimeNanos) { - return window->perform(window, NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO, frameTimelineVsyncId, - inputEventId, startTimeNanos); + return window->perform(window, NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO, frameNumber, + frameTimelineVsyncId, inputEventId, startTimeNanos); } // ------------------------------------------------------------------------------------------------ -- GitLab From e92317088881340d13e8a2d54f82a256162e3714 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Thu, 19 Jan 2023 12:52:03 +0000 Subject: [PATCH 0755/1310] Disable new touchpad stack for Sony gamepads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The touchpads on the Sony DualShock 4 and DualSense gamepads do not work with the gestures library, for a reason that is currently unknown. This is tracked in b/246587538, but while we wait for that to be fixed just check for them by vendor and product ID, and fall back to the old stack. This shouldn't really be a few IDs hard-coded into `InputDevice` — it should be an input device configuration property, but since those are currently only loaded in the configuration stage that will require a larger change. This is a second attempt after ag/21019000 caused a hwasan failure, which we believe was due to calling getDeviceInfo() when not all mappers had been configured. Getting the device ID from contextPtr instead fixes this issue. Bug: 251196347, 246587538 Test: `setprop persist.input.touchpad.gestures_library.enabled true`, connect gamepads, do a pinch gesture, confirm that touch spots show on screen (which doesn't happen in the new stack) Test: `setprop persist.input.touchpad.gestures_library.enabled true`, run android.hardware.input.cts.tests.SonyDualshock4BluetoothTest Change-Id: Ie843a7552ae31db640a847a9a108a66f6363730e --- services/inputflinger/reader/InputDevice.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 9bd50f7754..bd41fa5361 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -210,8 +210,14 @@ void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) { // Touchscreens and touchpad devices. static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY = sysprop::InputProperties::enable_touchpad_gestures_library().value_or(false); + // TODO(b/246587538): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) and DualSense + // (ce6) touchpads, or at least load this setting from the IDC file. + const InputDeviceIdentifier& identifier = contextPtr->getDeviceIdentifier(); + const bool isSonyGamepadTouchpad = identifier.vendor == 0x054c && + (identifier.product == 0x05c4 || identifier.product == 0x09cc || + identifier.product == 0x0ce6); if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) && - classes.test(InputDeviceClass::TOUCH_MT)) { + classes.test(InputDeviceClass::TOUCH_MT) && !isSonyGamepadTouchpad) { mappers.push_back(std::make_unique(*contextPtr)); } else if (classes.test(InputDeviceClass::TOUCH_MT)) { mappers.push_back(std::make_unique(*contextPtr)); -- GitLab From 0026b4cfc864e6cbd32ef911235e39b31de54aba Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 10 Nov 2022 19:33:29 -0800 Subject: [PATCH 0756/1310] Return InputTargets instead of TouchedWindows Before this CL, TouchedWindows were getting returned from findTouchedWindowTargetsLocked, just to be converted immediately to InputTargets. However, TouchedWindows are actually an implementation detail of TouchState, and should not leak out from there. In this CL, the first step is taken to fix this abstraction by returning InputTargets where TouchedWindows are getting returned. This will allow: 1. Removal of goto from dispatcher 2. Diff-based approach for generating InputTargets from old and new touch state These updates will be attempted in follow-up CLs. Bug: 211379801 Test: m inputflinger_tests && adb sync data && adb shell -t /data/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: I8b93719994ed383ad0f9bb848d84805470d95db9 --- .../dispatcher/InputDispatcher.cpp | 151 ++++++++++-------- .../inputflinger/dispatcher/InputDispatcher.h | 11 +- .../inputflinger/dispatcher/TouchState.cpp | 18 ++- services/inputflinger/dispatcher/TouchState.h | 4 + 4 files changed, 111 insertions(+), 73 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 204fff4566..7974b2c0a7 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -617,6 +617,12 @@ std::vector getHoveringWindowsLocked(const TouchState* oldState, return out; } +template +std::vector& operator+=(std::vector& left, const std::vector& right) { + left.insert(left.end(), right.begin(), right.end()); + return left; +} + } // namespace // --- InputDispatcher --- @@ -1019,8 +1025,7 @@ bool InputDispatcher::shouldPruneInboundQueueLocked(const MotionEntry& motionEnt const auto [x, y] = resolveTouchedPosition(motionEntry); const bool isStylus = isPointerFromStylus(motionEntry, 0 /*pointerIndex*/); - sp touchedWindowHandle = - findTouchedWindowAtLocked(displayId, x, y, nullptr, isStylus); + auto [touchedWindowHandle, _] = findTouchedWindowAtLocked(displayId, x, y, isStylus); if (touchedWindowHandle != nullptr && touchedWindowHandle->getApplicationToken() != mAwaitedFocusedApplication->getApplicationToken()) { @@ -1143,15 +1148,11 @@ void InputDispatcher::addRecentEventLocked(std::shared_ptr entry) { } } -sp InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x, - int32_t y, TouchState* touchState, - bool isStylus, - bool addOutsideTargets, - bool ignoreDragWindow) const { - if (addOutsideTargets && touchState == nullptr) { - LOG_ALWAYS_FATAL("Must provide a valid touch state if adding outside targets"); - } +std::pair, std::vector> +InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x, int32_t y, bool isStylus, + bool ignoreDragWindow) const { // Traverse windows from front to back to find touched window. + std::vector outsideTargets; const auto& windowHandles = getWindowHandlesLocked(displayId); for (const sp& windowHandle : windowHandles) { if (ignoreDragWindow && haveSameToken(windowHandle, mDragState->dragWindow)) { @@ -1160,16 +1161,16 @@ sp InputDispatcher::findTouchedWindowAtLocked(int32_t displayI const WindowInfo& info = *windowHandle->getInfo(); if (!info.isSpy() && windowAcceptsTouchAt(info, displayId, x, y, isStylus)) { - return windowHandle; + return {windowHandle, outsideTargets}; } - if (addOutsideTargets && - info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) { - touchState->addOrUpdateWindow(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE, - BitSet32(0)); + if (info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) { + addWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE, + BitSet32(0), /*firstDownTimeInTarget=*/std::nullopt, + outsideTargets); } } - return nullptr; + return {nullptr, {}}; } std::vector> InputDispatcher::findTouchedSpyWindowsAtLocked( @@ -1757,15 +1758,12 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr< pilferPointersLocked(mDragState->dragWindow->getToken()); } - std::vector touchedWindows = + inputTargets = findTouchedWindowTargetsLocked(currentTime, *entry, &conflictingPointerActions, /*byref*/ injectionResult); - for (const TouchedWindow& touchedWindow : touchedWindows) { - LOG_ALWAYS_FATAL_IF(injectionResult != InputEventInjectionResult::SUCCEEDED, - "Shouldn't be adding window if the injection didn't succeed."); - addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, - touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget, - inputTargets); + if (injectionResult != InputEventInjectionResult::SUCCEEDED) { + // No events should be dispatched if the injection didn't succeed + inputTargets = {}; } } else { // Non touch event. (eg. trackball) @@ -2138,12 +2136,12 @@ bool InputDispatcher::shouldSplitTouch(const TouchState& touchState, return true; } -std::vector InputDispatcher::findTouchedWindowTargetsLocked( +std::vector InputDispatcher::findTouchedWindowTargetsLocked( nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions, InputEventInjectionResult& outInjectionResult) { ATRACE_CALL(); - std::vector touchedWindows; + std::vector targets; // For security reasons, we defer updating the touch state until we are sure that // event injection will be allowed. const int32_t displayId = entry.displayId; @@ -2170,8 +2168,12 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( const bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE || maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER || maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT); - const bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN || - maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction); + // A DOWN could be generated from POINTER_DOWN if the initial pointers did not land into any + // touchable windows. + const bool wasDown = oldState != nullptr && oldState->isDown(); + const bool isDown = (maskedAction == AMOTION_EVENT_ACTION_DOWN) || + (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN && !wasDown); + const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction; const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE); if (newGesture) { @@ -2182,7 +2184,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( displayId); // TODO: test multiple simultaneous input streams. outInjectionResult = InputEventInjectionResult::FAILED; - return touchedWindows; // wrong device + return targets; // wrong device } tempTouchState.clearWindowsWithoutPointers(); tempTouchState.deviceId = entry.deviceId; @@ -2194,7 +2196,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( displayId); // TODO: test multiple simultaneous input streams. outInjectionResult = InputEventInjectionResult::FAILED; - return touchedWindows; // wrong device + return targets; // wrong device } if (isHoverAction) { @@ -2207,12 +2209,15 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( /* Case 1: New splittable pointer going down, or need target for hover or scroll. */ const auto [x, y] = resolveTouchedPosition(entry); const int32_t pointerIndex = getMotionEventActionPointerIndex(action); - const bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN; + // Outside targets should be added upon first dispatched DOWN event. That means, this should + // be a pointer that would generate ACTION_DOWN, *and* touch should not already be down. const bool isStylus = isPointerFromStylus(entry, pointerIndex); - sp newTouchedWindowHandle = - findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, isStylus, - isDown /*addOutsideTargets*/); + auto [newTouchedWindowHandle, outsideTargets] = + findTouchedWindowAtLocked(displayId, x, y, isStylus); + if (isDown) { + targets += outsideTargets; + } // Handle the case where we did not find a window. if (newTouchedWindowHandle == nullptr) { ALOGD("No new touched window at (%" PRId32 ", %" PRId32 ") in display %" PRId32, x, y, @@ -2360,14 +2365,12 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( const bool isStylus = isPointerFromStylus(entry, 0 /*pointerIndex*/); sp oldTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle(); - sp newTouchedWindowHandle = - findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, isStylus); + auto [newTouchedWindowHandle, _] = findTouchedWindowAtLocked(displayId, x, y, isStylus); // Verify targeted injection. if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) { ALOGW("Dropping injected event: %s", (*err).c_str()); outInjectionResult = os::InputEventInjectionResult::TARGET_MISMATCH; - newTouchedWindowHandle = nullptr; goto Failed; } @@ -2385,9 +2388,15 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( newTouchedWindowHandle->getName().c_str(), displayId); } // Make a slippery exit from the old window. - tempTouchState.addOrUpdateWindow(oldTouchedWindowHandle, - InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT, - BitSet32(0)); + BitSet32 pointerIds; + const int32_t pointerId = entry.pointerProperties[0].id; + pointerIds.markBit(pointerId); + + const TouchedWindow& touchedWindow = + tempTouchState.getTouchedWindow(oldTouchedWindowHandle); + addWindowTargetLocked(oldTouchedWindowHandle, + InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT, pointerIds, + touchedWindow.firstDownTimeInTarget, targets); // Make a slippery entrance into the new window. if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) { @@ -2408,14 +2417,13 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( targetFlags |= InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED; } - BitSet32 pointerIds; - pointerIds.markBit(entry.pointerProperties[0].id); tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds, entry.eventTime); // Check if the wallpaper window should deliver the corresponding event. slipWallpaperTouch(targetFlags, oldTouchedWindowHandle, newTouchedWindowHandle, - tempTouchState, pointerIds); + tempTouchState, pointerId, targets); + tempTouchState.removeTouchedPointerFromWindow(pointerId, oldTouchedWindowHandle); } } @@ -2438,7 +2446,11 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( { std::vector hoveringWindows = getHoveringWindowsLocked(oldState, tempTouchState, entry); - touchedWindows.insert(touchedWindows.end(), hoveringWindows.begin(), hoveringWindows.end()); + for (const TouchedWindow& touchedWindow : hoveringWindows) { + addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, + touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget, + targets); + } } // Ensure that we have at least one foreground window or at least one window that cannot be a // foreground target. If we only have windows that are not receiving foreground touches (e.g. we @@ -2477,30 +2489,31 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } } - // Check whether windows listening for outside touches are owned by the same UID. If it is - // set the policy flag that we will not reveal coordinate information to this window. + // Check whether windows listening for outside touches are owned by the same UID. If the owner + // has a different UID, then we will not reveal coordinate information to this window. if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { sp foregroundWindowHandle = tempTouchState.getFirstForegroundWindowHandle(); if (foregroundWindowHandle) { const int32_t foregroundWindowUid = foregroundWindowHandle->getInfo()->ownerUid; - for (const TouchedWindow& touchedWindow : tempTouchState.windows) { - if (touchedWindow.targetFlags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) { - sp windowInfoHandle = touchedWindow.windowHandle; - if (windowInfoHandle->getInfo()->ownerUid != foregroundWindowUid) { - tempTouchState.addOrUpdateWindow(windowInfoHandle, - InputTarget::Flags::ZERO_COORDS, - BitSet32(0)); + for (InputTarget& target : targets) { + if (target.flags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) { + sp targetWindow = + getWindowHandleLocked(target.inputChannel->getConnectionToken()); + if (targetWindow->getInfo()->ownerUid != foregroundWindowUid) { + target.flags |= InputTarget::Flags::ZERO_COORDS; } } } } } - // Success! Output targets for everything except hovers. - if (!isHoverAction) { - touchedWindows.insert(touchedWindows.end(), tempTouchState.windows.begin(), - tempTouchState.windows.end()); + // Success! Output targets from the touch state. + tempTouchState.clearWindowsWithoutPointers(); + for (const TouchedWindow& touchedWindow : tempTouchState.windows) { + addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, + touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget, + targets); } outInjectionResult = InputEventInjectionResult::SUCCEEDED; @@ -2572,7 +2585,7 @@ Failed: mTouchStatesByDisplay.erase(displayId); } - return touchedWindows; + return targets; } void InputDispatcher::finishDragAndDrop(int32_t displayId, float x, float y) { @@ -2580,9 +2593,8 @@ void InputDispatcher::finishDragAndDrop(int32_t displayId, float x, float y) { // have an explicit reason to support it. constexpr bool isStylus = false; - const sp dropWindow = - findTouchedWindowAtLocked(displayId, x, y, nullptr /*touchState*/, isStylus, - false /*addOutsideTargets*/, true /*ignoreDragWindow*/); + auto [dropWindow, _] = + findTouchedWindowAtLocked(displayId, x, y, isStylus, true /*ignoreDragWindow*/); if (dropWindow) { vec2 local = dropWindow->getInfo()->transform.transform(x, y); sendDropWindowCommandLocked(dropWindow->getToken(), local.x, local.y); @@ -2638,10 +2650,8 @@ void InputDispatcher::addDragEventLocked(const MotionEntry& entry) { // until we have an explicit reason to support it. constexpr bool isStylus = false; - const sp hoverWindowHandle = - findTouchedWindowAtLocked(entry.displayId, x, y, nullptr /*touchState*/, - isStylus, false /*addOutsideTargets*/, - true /*ignoreDragWindow*/); + auto [hoverWindowHandle, _] = findTouchedWindowAtLocked(entry.displayId, x, y, isStylus, + true /*ignoreDragWindow*/); // enqueue drag exit if needed. if (hoverWindowHandle != mDragState->dragHoverWindowHandle && !haveSameToken(hoverWindowHandle, mDragState->dragHoverWindowHandle)) { @@ -6535,7 +6545,10 @@ void InputDispatcher::setMonitorDispatchingTimeoutForTest(std::chrono::nanosecon void InputDispatcher::slipWallpaperTouch(ftl::Flags targetFlags, const sp& oldWindowHandle, const sp& newWindowHandle, - TouchState& state, const BitSet32& pointerIds) { + TouchState& state, int32_t pointerId, + std::vector& targets) { + BitSet32 pointerIds; + pointerIds.markBit(pointerId); const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test( gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER); const bool newHasWallpaper = targetFlags.test(InputTarget::Flags::FOREGROUND) && @@ -6550,8 +6563,12 @@ void InputDispatcher::slipWallpaperTouch(ftl::Flags targetFl } if (oldWallpaper != nullptr) { - state.addOrUpdateWindow(oldWallpaper, InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT, - BitSet32(0)); + const TouchedWindow& oldTouchedWindow = state.getTouchedWindow(oldWallpaper); + addWindowTargetLocked(oldWallpaper, + oldTouchedWindow.targetFlags | + InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT, + pointerIds, oldTouchedWindow.firstDownTimeInTarget, targets); + state.removeTouchedPointerFromWindow(pointerId, oldWallpaper); } if (newWallpaper != nullptr) { diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 91ca2db466..81f8de8f95 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -237,9 +237,9 @@ private: // to transfer focus to a new application. std::shared_ptr mNextUnblockedEvent GUARDED_BY(mLock); - sp findTouchedWindowAtLocked( - int32_t displayId, int32_t x, int32_t y, TouchState* touchState, bool isStylus = false, - bool addOutsideTargets = false, bool ignoreDragWindow = false) const REQUIRES(mLock); + std::pair, std::vector> + findTouchedWindowAtLocked(int32_t displayId, int32_t x, int32_t y, bool isStylus = false, + bool ignoreDragWindow = false) const REQUIRES(mLock); std::vector> findTouchedSpyWindowsAtLocked( int32_t displayId, int32_t x, int32_t y, bool isStylus) const REQUIRES(mLock); @@ -543,7 +543,7 @@ private: sp findFocusedWindowTargetLocked( nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime, android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock); - std::vector findTouchedWindowTargetsLocked( + std::vector findTouchedWindowTargetsLocked( nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions, android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock); std::vector selectResponsiveMonitorsLocked( @@ -697,7 +697,8 @@ private: void slipWallpaperTouch(ftl::Flags targetFlags, const sp& oldWindowHandle, const sp& newWindowHandle, - TouchState& state, const BitSet32& pointerIds) REQUIRES(mLock); + TouchState& state, int32_t pointerId, std::vector& targets) + REQUIRES(mLock); void transferWallpaperTouch(ftl::Flags oldTargetFlags, ftl::Flags newTargetFlags, const sp fromWindowHandle, diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index f120fc9919..ad37d025ca 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -37,6 +37,16 @@ void TouchState::removeTouchedPointer(int32_t pointerId) { } } +void TouchState::removeTouchedPointerFromWindow( + int32_t pointerId, const sp& windowHandle) { + for (TouchedWindow& touchedWindow : windows) { + if (touchedWindow.windowHandle == windowHandle) { + touchedWindow.pointerIds.clearBit(pointerId); + return; + } + } +} + void TouchState::clearHoveringPointers() { for (TouchedWindow& touchedWindow : windows) { touchedWindow.clearHoveringPointers(); @@ -70,7 +80,6 @@ void TouchState::addOrUpdateWindow(const sp& windowHandle, return; } } - TouchedWindow touchedWindow; touchedWindow.windowHandle = windowHandle; touchedWindow.targetFlags = targetFlags; @@ -175,6 +184,13 @@ sp TouchState::getWallpaperWindow() const { return nullptr; } +const TouchedWindow& TouchState::getTouchedWindow(const sp& windowHandle) const { + auto it = std::find_if(windows.begin(), windows.end(), + [&](const TouchedWindow& w) { return w.windowHandle == windowHandle; }); + LOG_ALWAYS_FATAL_IF(it == windows.end(), "Could not find %s", windowHandle->getName().c_str()); + return *it; +} + bool TouchState::isDown() const { return std::any_of(windows.begin(), windows.end(), [](const TouchedWindow& window) { return !window.pointerIds.isEmpty(); }); diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index b75e6ef359..4025db8a08 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -43,6 +43,8 @@ struct TouchState { void clearWindowsWithoutPointers(); void removeTouchedPointer(int32_t pointerId); + void removeTouchedPointerFromWindow(int32_t pointerId, + const sp& windowHandle); void addOrUpdateWindow(const sp& windowHandle, ftl::Flags targetFlags, BitSet32 pointerIds, std::optional eventTime = std::nullopt); @@ -62,6 +64,8 @@ struct TouchState { sp getFirstForegroundWindowHandle() const; bool isSlippery() const; sp getWallpaperWindow() const; + const TouchedWindow& getTouchedWindow( + const sp& windowHandle) const; // Whether any of the windows are currently being touched bool isDown() const; -- GitLab From 5df3493d3cf633f8ac7447bc5474a0dfbc1a8359 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 23 Jan 2023 12:41:01 -0800 Subject: [PATCH 0757/1310] Validate axes and led labels correctly Before this CL, a number of checks for kl file validity were incorrect. Some of the APIs were supposed to return an invalid value, but instead were always returning a valid value, no matter what the input was. Correct these values by switching to std::optional. Bug: 266400536 Test: m libinput_tests && adb sync data && adb shell -t /data/nativetest64/libinput_tests/libinput_tests Change-Id: I4ef45f3249dca4f4f033fb85e9fecbc2ad1f1395 --- include/input/Input.h | 4 +- include/input/InputEventLabels.h | 12 +-- libs/input/Input.cpp | 4 +- libs/input/InputEventLabels.cpp | 22 ++--- libs/input/KeyCharacterMap.cpp | 24 +++--- libs/input/KeyLayoutMap.cpp | 106 ++++++++++++++---------- libs/input/tests/InputDevice_test.cpp | 14 ++++ libs/input/tests/data/bad_axis_label.kl | 17 ++++ libs/input/tests/data/bad_led_label.kl | 17 ++++ 9 files changed, 142 insertions(+), 78 deletions(-) create mode 100644 libs/input/tests/data/bad_axis_label.kl create mode 100644 libs/input/tests/data/bad_led_label.kl diff --git a/include/input/Input.h b/include/input/Input.h index 30b0d6a67a..7573282fc1 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -529,7 +529,7 @@ public: inline nsecs_t getEventTime() const { return mEventTime; } static const char* getLabel(int32_t keyCode); - static int32_t getKeyCodeFromLabel(const char* label); + static std::optional getKeyCodeFromLabel(const char* label); void initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId, std::array hmac, int32_t action, int32_t flags, int32_t keyCode, @@ -842,7 +842,7 @@ public: } static const char* getLabel(int32_t axis); - static int32_t getAxisFromLabel(const char* label); + static std::optional getAxisFromLabel(const char* label); static std::string actionToString(int32_t action); diff --git a/include/input/InputEventLabels.h b/include/input/InputEventLabels.h index b4374acdcc..4668fce116 100644 --- a/include/input/InputEventLabels.h +++ b/include/input/InputEventLabels.h @@ -35,22 +35,22 @@ struct InputEventLabel { class InputEventLookup { public: - static int lookupValueByLabel(const std::unordered_map& map, - const char* literal); + static std::optional lookupValueByLabel(const std::unordered_map& map, + const char* literal); static const char* lookupLabelByValue(const std::vector& vec, int value); - static int32_t getKeyCodeByLabel(const char* label); + static std::optional getKeyCodeByLabel(const char* label); static const char* getLabelByKeyCode(int32_t keyCode); - static uint32_t getKeyFlagByLabel(const char* label); + static std::optional getKeyFlagByLabel(const char* label); - static int32_t getAxisByLabel(const char* label); + static std::optional getAxisByLabel(const char* label); static const char* getAxisLabel(int32_t axisId); - static int32_t getLedByLabel(const char* label); + static std::optional getLedByLabel(const char* label); private: static const std::unordered_map KEYCODES; diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index c356c2e5e9..133b260a61 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -299,7 +299,7 @@ const char* KeyEvent::getLabel(int32_t keyCode) { return InputEventLookup::getLabelByKeyCode(keyCode); } -int32_t KeyEvent::getKeyCodeFromLabel(const char* label) { +std::optional KeyEvent::getKeyCodeFromLabel(const char* label) { return InputEventLookup::getKeyCodeByLabel(label); } @@ -891,7 +891,7 @@ const char* MotionEvent::getLabel(int32_t axis) { return InputEventLookup::getAxisLabel(axis); } -int32_t MotionEvent::getAxisFromLabel(const char* label) { +std::optional MotionEvent::getAxisFromLabel(const char* label) { return InputEventLookup::getAxisByLabel(label); } diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp index 7159e27b13..d97c1bb629 100644 --- a/libs/input/InputEventLabels.cpp +++ b/libs/input/InputEventLabels.cpp @@ -438,11 +438,11 @@ const std::unordered_map InputEventLookup::LEDS = {LEDS_SEQUEN const std::unordered_map InputEventLookup::FLAGS = {FLAGS_SEQUENCE}; -int InputEventLookup::lookupValueByLabel(const std::unordered_map& map, - const char* literal) { +std::optional InputEventLookup::lookupValueByLabel( + const std::unordered_map& map, const char* literal) { std::string str(literal); auto it = map.find(str); - return it != map.end() ? it->second : 0; + return it != map.end() ? std::make_optional(it->second) : std::nullopt; } const char* InputEventLookup::lookupLabelByValue(const std::vector& vec, @@ -453,8 +453,8 @@ const char* InputEventLookup::lookupLabelByValue(const std::vector InputEventLookup::getKeyCodeByLabel(const char* label) { + return lookupValueByLabel(KEYCODES, label); } const char* InputEventLookup::getLabelByKeyCode(int32_t keyCode) { @@ -464,20 +464,20 @@ const char* InputEventLookup::getLabelByKeyCode(int32_t keyCode) { return nullptr; } -uint32_t InputEventLookup::getKeyFlagByLabel(const char* label) { - return uint32_t(lookupValueByLabel(FLAGS, label)); +std::optional InputEventLookup::getKeyFlagByLabel(const char* label) { + return lookupValueByLabel(FLAGS, label); } -int32_t InputEventLookup::getAxisByLabel(const char* label) { - return int32_t(lookupValueByLabel(AXES, label)); +std::optional InputEventLookup::getAxisByLabel(const char* label) { + return lookupValueByLabel(AXES, label); } const char* InputEventLookup::getAxisLabel(int32_t axisId) { return lookupLabelByValue(AXES_NAMES, axisId); } -int32_t InputEventLookup::getLedByLabel(const char* label) { - return int32_t(lookupValueByLabel(LEDS, label)); +std::optional InputEventLookup::getLedByLabel(const char* label) { + return lookupValueByLabel(LEDS, label); } } // namespace android diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp index 6bfac40932..737bd15901 100644 --- a/libs/input/KeyCharacterMap.cpp +++ b/libs/input/KeyCharacterMap.cpp @@ -999,7 +999,7 @@ status_t KeyCharacterMap::Parser::parseMapKey() { mTokenizer->skipDelimiters(WHITESPACE); String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE); - int32_t keyCode = InputEventLookup::getKeyCodeByLabel(keyCodeToken.string()); + std::optional keyCode = InputEventLookup::getKeyCodeByLabel(keyCodeToken.string()); if (!keyCode) { ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(), keyCodeToken.string()); @@ -1010,19 +1010,19 @@ status_t KeyCharacterMap::Parser::parseMapKey() { ALOGD("Parsed map key %s: code=%d, keyCode=%d.", mapUsage ? "usage" : "scan code", code, keyCode); #endif - map.insert_or_assign(code, keyCode); + map.insert_or_assign(code, *keyCode); return NO_ERROR; } status_t KeyCharacterMap::Parser::parseKey() { String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE); - int32_t keyCode = InputEventLookup::getKeyCodeByLabel(keyCodeToken.string()); + std::optional keyCode = InputEventLookup::getKeyCodeByLabel(keyCodeToken.string()); if (!keyCode) { ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(), keyCodeToken.string()); return BAD_VALUE; } - if (mMap->mKeys.indexOfKey(keyCode) >= 0) { + if (mMap->mKeys.indexOfKey(*keyCode) >= 0) { ALOGE("%s: Duplicate entry for key code '%s'.", mTokenizer->getLocation().string(), keyCodeToken.string()); return BAD_VALUE; @@ -1036,11 +1036,9 @@ status_t KeyCharacterMap::Parser::parseKey() { return BAD_VALUE; } -#if DEBUG_PARSER - ALOGD("Parsed beginning of key: keyCode=%d.", keyCode); -#endif - mKeyCode = keyCode; - mMap->mKeys.add(keyCode, new Key()); + ALOGD_IF(DEBUG_PARSER, "Parsed beginning of key: keyCode=%d.", *keyCode); + mKeyCode = *keyCode; + mMap->mKeys.add(*keyCode, new Key()); mState = STATE_KEY; return NO_ERROR; } @@ -1136,7 +1134,7 @@ status_t KeyCharacterMap::Parser::parseKeyProperty() { } else if (token == "fallback") { mTokenizer->skipDelimiters(WHITESPACE); token = mTokenizer->nextToken(WHITESPACE); - int32_t keyCode = InputEventLookup::getKeyCodeByLabel(token.string()); + std::optional keyCode = InputEventLookup::getKeyCodeByLabel(token.string()); if (!keyCode) { ALOGE("%s: Invalid key code label for fallback behavior, got '%s'.", mTokenizer->getLocation().string(), @@ -1148,12 +1146,12 @@ status_t KeyCharacterMap::Parser::parseKeyProperty() { mTokenizer->getLocation().string()); return BAD_VALUE; } - behavior.fallbackKeyCode = keyCode; + behavior.fallbackKeyCode = *keyCode; haveFallback = true; } else if (token == "replace") { mTokenizer->skipDelimiters(WHITESPACE); token = mTokenizer->nextToken(WHITESPACE); - int32_t keyCode = InputEventLookup::getKeyCodeByLabel(token.string()); + std::optional keyCode = InputEventLookup::getKeyCodeByLabel(token.string()); if (!keyCode) { ALOGE("%s: Invalid key code label for replace, got '%s'.", mTokenizer->getLocation().string(), @@ -1170,7 +1168,7 @@ status_t KeyCharacterMap::Parser::parseKeyProperty() { mTokenizer->getLocation().string()); return BAD_VALUE; } - behavior.replacementKeyCode = keyCode; + behavior.replacementKeyCode = *keyCode; haveReplacement = true; } else { diff --git a/libs/input/KeyLayoutMap.cpp b/libs/input/KeyLayoutMap.cpp index 73710330d0..a2649f6f11 100644 --- a/libs/input/KeyLayoutMap.cpp +++ b/libs/input/KeyLayoutMap.cpp @@ -16,6 +16,7 @@ #define LOG_TAG "KeyLayoutMap" +#include #include #include #include @@ -54,6 +55,21 @@ const bool DEBUG_MAPPING = namespace android { namespace { +std::optional parseInt(const char* str) { + char* end; + errno = 0; + const int value = strtol(str, &end, 0); + if (end == str) { + LOG(ERROR) << "Could not parse " << str; + return {}; + } + if (errno == ERANGE) { + LOG(ERROR) << "Out of bounds: " << str; + return {}; + } + return value; +} + constexpr const char* WHITESPACE = " \t\r"; template @@ -336,16 +352,15 @@ status_t KeyLayoutMap::Parser::parseKey() { codeToken = mTokenizer->nextToken(WHITESPACE); } - char* end; - int32_t code = int32_t(strtol(codeToken.string(), &end, 0)); - if (*end) { + std::optional code = parseInt(codeToken.string()); + if (!code) { ALOGE("%s: Expected key %s number, got '%s'.", mTokenizer->getLocation().string(), mapUsage ? "usage" : "scan code", codeToken.string()); return BAD_VALUE; } std::unordered_map& map = mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode; - if (map.find(code) != map.end()) { + if (map.find(*code) != map.end()) { ALOGE("%s: Duplicate entry for key %s '%s'.", mTokenizer->getLocation().string(), mapUsage ? "usage" : "scan code", codeToken.string()); return BAD_VALUE; @@ -353,7 +368,7 @@ status_t KeyLayoutMap::Parser::parseKey() { mTokenizer->skipDelimiters(WHITESPACE); String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE); - int32_t keyCode = InputEventLookup::getKeyCodeByLabel(keyCodeToken.string()); + std::optional keyCode = InputEventLookup::getKeyCodeByLabel(keyCodeToken.string()); if (!keyCode) { ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(), keyCodeToken.string()); @@ -366,40 +381,39 @@ status_t KeyLayoutMap::Parser::parseKey() { if (mTokenizer->isEol() || mTokenizer->peekChar() == '#') break; String8 flagToken = mTokenizer->nextToken(WHITESPACE); - uint32_t flag = InputEventLookup::getKeyFlagByLabel(flagToken.string()); + std::optional flag = InputEventLookup::getKeyFlagByLabel(flagToken.string()); if (!flag) { ALOGE("%s: Expected key flag label, got '%s'.", mTokenizer->getLocation().string(), flagToken.string()); return BAD_VALUE; } - if (flags & flag) { + if (flags & *flag) { ALOGE("%s: Duplicate key flag '%s'.", mTokenizer->getLocation().string(), flagToken.string()); return BAD_VALUE; } - flags |= flag; + flags |= *flag; } ALOGD_IF(DEBUG_PARSER, "Parsed key %s: code=%d, keyCode=%d, flags=0x%08x.", - mapUsage ? "usage" : "scan code", code, keyCode, flags); + mapUsage ? "usage" : "scan code", *code, *keyCode, flags); Key key; - key.keyCode = keyCode; + key.keyCode = *keyCode; key.flags = flags; - map.insert({code, key}); + map.insert({*code, key}); return NO_ERROR; } status_t KeyLayoutMap::Parser::parseAxis() { String8 scanCodeToken = mTokenizer->nextToken(WHITESPACE); - char* end; - int32_t scanCode = int32_t(strtol(scanCodeToken.string(), &end, 0)); - if (*end) { + std::optional scanCode = parseInt(scanCodeToken.string()); + if (!scanCode) { ALOGE("%s: Expected axis scan code number, got '%s'.", mTokenizer->getLocation().string(), scanCodeToken.string()); return BAD_VALUE; } - if (mMap->mAxes.find(scanCode) != mMap->mAxes.end()) { + if (mMap->mAxes.find(*scanCode) != mMap->mAxes.end()) { ALOGE("%s: Duplicate entry for axis scan code '%s'.", mTokenizer->getLocation().string(), scanCodeToken.string()); return BAD_VALUE; @@ -414,48 +428,53 @@ status_t KeyLayoutMap::Parser::parseAxis() { mTokenizer->skipDelimiters(WHITESPACE); String8 axisToken = mTokenizer->nextToken(WHITESPACE); - axisInfo.axis = InputEventLookup::getAxisByLabel(axisToken.string()); - if (axisInfo.axis < 0) { + std::optional axis = InputEventLookup::getAxisByLabel(axisToken.string()); + if (!axis) { ALOGE("%s: Expected inverted axis label, got '%s'.", mTokenizer->getLocation().string(), axisToken.string()); return BAD_VALUE; } + axisInfo.axis = *axis; } else if (token == "split") { axisInfo.mode = AxisInfo::MODE_SPLIT; mTokenizer->skipDelimiters(WHITESPACE); String8 splitToken = mTokenizer->nextToken(WHITESPACE); - axisInfo.splitValue = int32_t(strtol(splitToken.string(), &end, 0)); - if (*end) { + std::optional splitValue = parseInt(splitToken.string()); + if (!splitValue) { ALOGE("%s: Expected split value, got '%s'.", mTokenizer->getLocation().string(), splitToken.string()); return BAD_VALUE; } + axisInfo.splitValue = *splitValue; mTokenizer->skipDelimiters(WHITESPACE); String8 lowAxisToken = mTokenizer->nextToken(WHITESPACE); - axisInfo.axis = InputEventLookup::getAxisByLabel(lowAxisToken.string()); - if (axisInfo.axis < 0) { + std::optional axis = InputEventLookup::getAxisByLabel(lowAxisToken.string()); + if (!axis) { ALOGE("%s: Expected low axis label, got '%s'.", mTokenizer->getLocation().string(), lowAxisToken.string()); return BAD_VALUE; } + axisInfo.axis = *axis; mTokenizer->skipDelimiters(WHITESPACE); String8 highAxisToken = mTokenizer->nextToken(WHITESPACE); - axisInfo.highAxis = InputEventLookup::getAxisByLabel(highAxisToken.string()); - if (axisInfo.highAxis < 0) { + std::optional highAxis = InputEventLookup::getAxisByLabel(highAxisToken.string()); + if (!highAxis) { ALOGE("%s: Expected high axis label, got '%s'.", mTokenizer->getLocation().string(), highAxisToken.string()); return BAD_VALUE; } + axisInfo.highAxis = *highAxis; } else { - axisInfo.axis = InputEventLookup::getAxisByLabel(token.string()); - if (axisInfo.axis < 0) { + std::optional axis = InputEventLookup::getAxisByLabel(token.string()); + if (!axis) { ALOGE("%s: Expected axis label, 'split' or 'invert', got '%s'.", mTokenizer->getLocation().string(), token.string()); return BAD_VALUE; } + axisInfo.axis = *axis; } for (;;) { @@ -467,12 +486,13 @@ status_t KeyLayoutMap::Parser::parseAxis() { if (keywordToken == "flat") { mTokenizer->skipDelimiters(WHITESPACE); String8 flatToken = mTokenizer->nextToken(WHITESPACE); - axisInfo.flatOverride = int32_t(strtol(flatToken.string(), &end, 0)); - if (*end) { + std::optional flatOverride = parseInt(flatToken.string()); + if (!flatOverride) { ALOGE("%s: Expected flat value, got '%s'.", mTokenizer->getLocation().string(), flatToken.string()); return BAD_VALUE; } + axisInfo.flatOverride = *flatOverride; } else { ALOGE("%s: Expected keyword 'flat', got '%s'.", mTokenizer->getLocation().string(), keywordToken.string()); @@ -483,9 +503,9 @@ status_t KeyLayoutMap::Parser::parseAxis() { ALOGD_IF(DEBUG_PARSER, "Parsed axis: scanCode=%d, mode=%d, axis=%d, highAxis=%d, " "splitValue=%d, flatOverride=%d.", - scanCode, axisInfo.mode, axisInfo.axis, axisInfo.highAxis, axisInfo.splitValue, + *scanCode, axisInfo.mode, axisInfo.axis, axisInfo.highAxis, axisInfo.splitValue, axisInfo.flatOverride); - mMap->mAxes.insert({scanCode, axisInfo}); + mMap->mAxes.insert({*scanCode, axisInfo}); return NO_ERROR; } @@ -497,9 +517,8 @@ status_t KeyLayoutMap::Parser::parseLed() { mTokenizer->skipDelimiters(WHITESPACE); codeToken = mTokenizer->nextToken(WHITESPACE); } - char* end; - int32_t code = int32_t(strtol(codeToken.string(), &end, 0)); - if (*end) { + std::optional code = parseInt(codeToken.string()); + if (!code) { ALOGE("%s: Expected led %s number, got '%s'.", mTokenizer->getLocation().string(), mapUsage ? "usage" : "scan code", codeToken.string()); return BAD_VALUE; @@ -507,7 +526,7 @@ status_t KeyLayoutMap::Parser::parseLed() { std::unordered_map& map = mapUsage ? mMap->mLedsByUsageCode : mMap->mLedsByScanCode; - if (map.find(code) != map.end()) { + if (map.find(*code) != map.end()) { ALOGE("%s: Duplicate entry for led %s '%s'.", mTokenizer->getLocation().string(), mapUsage ? "usage" : "scan code", codeToken.string()); return BAD_VALUE; @@ -515,19 +534,19 @@ status_t KeyLayoutMap::Parser::parseLed() { mTokenizer->skipDelimiters(WHITESPACE); String8 ledCodeToken = mTokenizer->nextToken(WHITESPACE); - int32_t ledCode = InputEventLookup::getLedByLabel(ledCodeToken.string()); - if (ledCode < 0) { + std::optional ledCode = InputEventLookup::getLedByLabel(ledCodeToken.string()); + if (!ledCode) { ALOGE("%s: Expected LED code label, got '%s'.", mTokenizer->getLocation().string(), ledCodeToken.string()); return BAD_VALUE; } ALOGD_IF(DEBUG_PARSER, "Parsed led %s: code=%d, ledCode=%d.", mapUsage ? "usage" : "scan code", - code, ledCode); + *code, *ledCode); Led led; - led.ledCode = ledCode; - map.insert({code, led}); + led.ledCode = *ledCode; + map.insert({*code, led}); return NO_ERROR; } @@ -565,16 +584,15 @@ static std::optional getSensorDataIndex(String8 token) { // sensor 0x05 GYROSCOPE Z status_t KeyLayoutMap::Parser::parseSensor() { String8 codeToken = mTokenizer->nextToken(WHITESPACE); - char* end; - int32_t code = int32_t(strtol(codeToken.string(), &end, 0)); - if (*end) { + std::optional code = parseInt(codeToken.string()); + if (!code) { ALOGE("%s: Expected sensor %s number, got '%s'.", mTokenizer->getLocation().string(), "abs code", codeToken.string()); return BAD_VALUE; } std::unordered_map& map = mMap->mSensorsByAbsCode; - if (map.find(code) != map.end()) { + if (map.find(*code) != map.end()) { ALOGE("%s: Duplicate entry for sensor %s '%s'.", mTokenizer->getLocation().string(), "abs code", codeToken.string()); return BAD_VALUE; @@ -599,13 +617,13 @@ status_t KeyLayoutMap::Parser::parseSensor() { } int32_t sensorDataIndex = indexOpt.value(); - ALOGD_IF(DEBUG_PARSER, "Parsed sensor: abs code=%d, sensorType=%s, sensorDataIndex=%d.", code, + ALOGD_IF(DEBUG_PARSER, "Parsed sensor: abs code=%d, sensorType=%s, sensorDataIndex=%d.", *code, ftl::enum_string(sensorType).c_str(), sensorDataIndex); Sensor sensor; sensor.sensorType = sensorType; sensor.sensorDataIndex = sensorDataIndex; - map.emplace(code, sensor); + map.emplace(*code, sensor); return NO_ERROR; } diff --git a/libs/input/tests/InputDevice_test.cpp b/libs/input/tests/InputDevice_test.cpp index 2344463241..ee961f0ffc 100644 --- a/libs/input/tests/InputDevice_test.cpp +++ b/libs/input/tests/InputDevice_test.cpp @@ -133,6 +133,20 @@ TEST_F(InputDeviceKeyMapTest, keyCharacteMapApplyMultipleOverlaysTest) { ASSERT_EQ(*mKeyMap.keyCharacterMap, *frenchOverlaidKeyCharacterMap); } +TEST_F(InputDeviceKeyMapTest, keyCharacteMapBadAxisLabel) { + std::string klPath = base::GetExecutableDirectory() + "/data/bad_axis_label.kl"; + + base::Result> ret = KeyLayoutMap::load(klPath); + ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath; +} + +TEST_F(InputDeviceKeyMapTest, keyCharacteMapBadLedLabel) { + std::string klPath = base::GetExecutableDirectory() + "/data/bad_led_label.kl"; + + base::Result> ret = KeyLayoutMap::load(klPath); + ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath; +} + TEST(InputDeviceKeyLayoutTest, DoesNotLoadWhenRequiredKernelConfigIsMissing) { #if !defined(__ANDROID__) GTEST_SKIP() << "Can't check kernel configs on host"; diff --git a/libs/input/tests/data/bad_axis_label.kl b/libs/input/tests/data/bad_axis_label.kl new file mode 100644 index 0000000000..689738077c --- /dev/null +++ b/libs/input/tests/data/bad_axis_label.kl @@ -0,0 +1,17 @@ +# Copyright (C) 2023 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This KL should not be loaded because the axis label is not valid + +axis 0 DEFINITELY_NOT_AXIS_LABEL diff --git a/libs/input/tests/data/bad_led_label.kl b/libs/input/tests/data/bad_led_label.kl new file mode 100644 index 0000000000..293c0d2af4 --- /dev/null +++ b/libs/input/tests/data/bad_led_label.kl @@ -0,0 +1,17 @@ +# Copyright (C) 2023 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This KL should not be loaded because the led label is invalid + +led 0 ABSOLUTELY_NOT_LED_LABEL -- GitLab From 8619eb316e97edcd6b3dade83910036dcdad7de2 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 1 Dec 2022 21:30:59 -0800 Subject: [PATCH 0758/1310] Remove goto from InputDispatcher The goto instructions inside findTouchedWindowTargetsLocked have made it very difficult to reason about this function. Remove the goto statements in this CL. Bug: 211379801 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: I9d645cefe6cd8d9a9a0816f36efd7a024a0447a7 --- .../dispatcher/InputDispatcher.cpp | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 7974b2c0a7..dc9f02ad5d 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -1761,10 +1761,8 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr< inputTargets = findTouchedWindowTargetsLocked(currentTime, *entry, &conflictingPointerActions, /*byref*/ injectionResult); - if (injectionResult != InputEventInjectionResult::SUCCEEDED) { - // No events should be dispatched if the injection didn't succeed - inputTargets = {}; - } + LOG_ALWAYS_FATAL_IF(injectionResult != InputEventInjectionResult::SUCCEEDED && + !inputTargets.empty()); } else { // Non touch event. (eg. trackball) sp focusedWindow = @@ -2184,7 +2182,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( displayId); // TODO: test multiple simultaneous input streams. outInjectionResult = InputEventInjectionResult::FAILED; - return targets; // wrong device + return {}; // wrong device } tempTouchState.clearWindowsWithoutPointers(); tempTouchState.deviceId = entry.deviceId; @@ -2196,7 +2194,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( displayId); // TODO: test multiple simultaneous input streams. outInjectionResult = InputEventInjectionResult::FAILED; - return targets; // wrong device + return {}; // wrong device } if (isHoverAction) { @@ -2231,7 +2229,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( ALOGW("Dropping injected touch event: %s", (*err).c_str()); outInjectionResult = os::InputEventInjectionResult::TARGET_MISMATCH; newTouchedWindowHandle = nullptr; - goto Failed; + return {}; } // Figure out whether splitting will be allowed for this window. @@ -2262,7 +2260,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( ALOGI("Dropping event because there is no touchable window at (%d, %d) on display %d.", x, y, displayId); outInjectionResult = InputEventInjectionResult::FAILED; - goto Failed; + return {}; } for (const sp& windowHandle : newTouchedWindows) { @@ -2353,7 +2351,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( "dropped the pointer down event in display %" PRId32 ": %s", displayId, entry.getDescription().c_str()); outInjectionResult = InputEventInjectionResult::FAILED; - goto Failed; + return {}; } addDragEventLocked(entry); @@ -2371,7 +2369,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) { ALOGW("Dropping injected event: %s", (*err).c_str()); outInjectionResult = os::InputEventInjectionResult::TARGET_MISMATCH; - goto Failed; + return {}; } // Drop touch events if requested by input feature @@ -2465,7 +2463,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( ALOGI("Dropping event because there is no touched window on display %d to receive it: %s", displayId, entry.getDescription().c_str()); outInjectionResult = InputEventInjectionResult::FAILED; - goto Failed; + return {}; } // Ensure that all touched windows are valid for injection. @@ -2485,7 +2483,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( "%d:%s", *entry.injectionState->targetUid, errs.c_str()); outInjectionResult = InputEventInjectionResult::TARGET_MISMATCH; - goto Failed; + return {}; } } @@ -2521,7 +2519,6 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( // in the next iteration. tempTouchState.filterNonAsIsTouchWindows(); -Failed: // Update final pieces of touch state if the injector had permission. if (switchedDevice) { if (DEBUG_FOCUS) { -- GitLab From 93ec6f2a1ec21591e68d3e0d815c829f3c81e99f Mon Sep 17 00:00:00 2001 From: Devin Moore Date: Tue, 24 Jan 2023 18:41:40 +0000 Subject: [PATCH 0759/1310] Don't store a pointer to internal SensorManager The underlying SensorManager::getInstanceForPackage already manages and caches the SensorManager instance. The fuzzers are treating this as a memory leak since it's never freed in sensorservice. Test: m Bug: 266474722 Change-Id: I0025cf9c2e51f96e134dcdfe13771b5289535ce5 --- services/sensorservice/aidl/SensorManager.cpp | 8 ++------ .../aidl/include/sensorserviceaidl/SensorManagerAidl.h | 2 -- services/sensorservice/hidl/SensorManager.cpp | 8 ++------ .../hidl/include/sensorservicehidl/SensorManager.h | 2 -- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/services/sensorservice/aidl/SensorManager.cpp b/services/sensorservice/aidl/SensorManager.cpp index 9b0334443b..b7aecdf1bd 100644 --- a/services/sensorservice/aidl/SensorManager.cpp +++ b/services/sensorservice/aidl/SensorManager.cpp @@ -185,12 +185,8 @@ ndk::ScopedAStatus SensorManagerAidl::getSensorList(std::vector* _ai } ::android::SensorManager& SensorManagerAidl::getInternalManager() { - std::lock_guard lock(mInternalManagerMutex); - if (mInternalManager == nullptr) { - mInternalManager = &::android::SensorManager::getInstanceForPackage( - String16(ISensorManager::descriptor)); - } - return *mInternalManager; + return ::android::SensorManager::getInstanceForPackage( + String16(ISensorManager::descriptor)); } /* One global looper for all event queues created from this SensorManager. */ diff --git a/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h b/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h index c77ee880dd..83496f6012 100644 --- a/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h +++ b/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h @@ -57,8 +57,6 @@ private: ::android::SensorManager& getInternalManager(); sp getLooper(); - std::mutex mInternalManagerMutex; - ::android::SensorManager* mInternalManager = nullptr; // does not own sp mLooper; volatile bool mStopThread; diff --git a/services/sensorservice/hidl/SensorManager.cpp b/services/sensorservice/hidl/SensorManager.cpp index 938060063f..f04712c534 100644 --- a/services/sensorservice/hidl/SensorManager.cpp +++ b/services/sensorservice/hidl/SensorManager.cpp @@ -193,12 +193,8 @@ sp SensorManager::getLooper() { } ::android::SensorManager& SensorManager::getInternalManager() { - std::lock_guard lock(mInternalManagerMutex); - if (mInternalManager == nullptr) { - mInternalManager = &::android::SensorManager::getInstanceForPackage( - String16(ISensorManager::descriptor)); - } - return *mInternalManager; + return ::android::SensorManager::getInstanceForPackage( + String16(ISensorManager::descriptor)); } Return SensorManager::createEventQueue( diff --git a/services/sensorservice/hidl/include/sensorservicehidl/SensorManager.h b/services/sensorservice/hidl/include/sensorservicehidl/SensorManager.h index 8d7a05b5e2..1b085acfca 100644 --- a/services/sensorservice/hidl/include/sensorservicehidl/SensorManager.h +++ b/services/sensorservice/hidl/include/sensorservicehidl/SensorManager.h @@ -58,8 +58,6 @@ private: ::android::SensorManager& getInternalManager(); sp getLooper(); - std::mutex mInternalManagerMutex; - ::android::SensorManager* mInternalManager = nullptr; // does not own sp mLooper; volatile bool mStopThread; -- GitLab From 933f8dedadb7772efd7f4d8c7afef7034e423d8a Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Fri, 20 Jan 2023 13:15:51 -0500 Subject: [PATCH 0760/1310] SF: Extract VsyncConfig to its own header ...such that VsyncConfiguration does not depend on VsyncModulator. Bug: 241285475 Bug: 241285191 Test: Build Change-Id: I05b66a4ed2e6721b10ae90bdc6af2e49c22f2a59 --- services/surfaceflinger/Layer.cpp | 2 +- services/surfaceflinger/Scheduler/Scheduler.h | 4 +- .../Scheduler/VsyncConfiguration.cpp | 12 ++--- .../Scheduler/VsyncConfiguration.h | 12 ++--- .../Scheduler/VsyncModulator.cpp | 10 ++-- .../surfaceflinger/Scheduler/VsyncModulator.h | 33 +----------- .../Scheduler/include/scheduler/Fps.h | 3 +- .../Scheduler/include/scheduler/VsyncConfig.h | 54 +++++++++++++++++++ services/surfaceflinger/SurfaceFlinger.cpp | 15 +++--- services/surfaceflinger/SurfaceFlinger.h | 3 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 21 +++++--- .../surfaceflinger_scheduler_fuzzer.cpp | 17 +++--- .../tests/unittests/VsyncModulatorTest.cpp | 19 +++---- 13 files changed, 113 insertions(+), 92 deletions(-) create mode 100644 services/surfaceflinger/Scheduler/include/scheduler/VsyncConfig.h diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index df76ed02c7..d3d52aeb2a 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -196,7 +196,7 @@ Layer::Layer(const LayerCreationArgs& args) mDrawingState.color.b = -1.0_hf; } - mFrameTracker.setDisplayRefreshPeriod(args.flinger->mScheduler->getLeaderVsyncPeriod()); + mFrameTracker.setDisplayRefreshPeriod(args.flinger->mScheduler->getLeaderVsyncPeriod().ns()); mOwnerUid = args.ownerUid; mOwnerPid = args.ownerPid; diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 36280e3888..a3e28b6ca4 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -239,8 +239,8 @@ public: // Retrieves the overridden refresh rate for a given uid. std::optional getFrameRateOverride(uid_t) const EXCLUDES(mDisplayLock); - nsecs_t getLeaderVsyncPeriod() const EXCLUDES(mDisplayLock) { - return leaderSelectorPtr()->getActiveMode().fps.getPeriodNsecs(); + Period getLeaderVsyncPeriod() const EXCLUDES(mDisplayLock) { + return leaderSelectorPtr()->getActiveMode().fps.getPeriod(); } // Returns the framerate of the layer with the given sequence ID diff --git a/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp b/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp index ff316515b6..6ae10f3d31 100644 --- a/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp +++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp @@ -42,12 +42,12 @@ namespace android::scheduler::impl { VsyncConfiguration::VsyncConfiguration(Fps currentFps) : mRefreshRateFps(currentFps) {} -PhaseOffsets::VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRate(Fps fps) const { +VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRate(Fps fps) const { std::lock_guard lock(mLock); return getConfigsForRefreshRateLocked(fps); } -PhaseOffsets::VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRateLocked(Fps fps) const { +VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRateLocked(Fps fps) const { if (const auto offsets = mOffsetsCache.get(fps)) { return offsets->get(); } @@ -134,7 +134,7 @@ PhaseOffsets::PhaseOffsets(Fps currentFps, nsecs_t vsyncPhaseOffsetNs, nsecs_t s mThresholdForNextVsync(thresholdForNextVsync), mHwcMinWorkDuration(hwcMinWorkDuration) {} -PhaseOffsets::VsyncConfigSet PhaseOffsets::constructOffsets(nsecs_t vsyncDuration) const { +VsyncConfigSet PhaseOffsets::constructOffsets(nsecs_t vsyncDuration) const { if (vsyncDuration < std::chrono::nanoseconds(15ms).count()) { return getHighFpsOffsets(vsyncDuration); } else { @@ -158,7 +158,7 @@ std::chrono::nanoseconds appOffsetToDuration(nsecs_t appOffset, nsecs_t sfOffset } } // namespace -PhaseOffsets::VsyncConfigSet PhaseOffsets::getDefaultOffsets(nsecs_t vsyncDuration) const { +VsyncConfigSet PhaseOffsets::getDefaultOffsets(nsecs_t vsyncDuration) const { const auto earlySfOffset = mEarlySfOffsetNs.value_or(mSfVSyncPhaseOffsetNs) < mThresholdForNextVsync @@ -196,7 +196,7 @@ PhaseOffsets::VsyncConfigSet PhaseOffsets::getDefaultOffsets(nsecs_t vsyncDurati }; } -PhaseOffsets::VsyncConfigSet PhaseOffsets::getHighFpsOffsets(nsecs_t vsyncDuration) const { +VsyncConfigSet PhaseOffsets::getHighFpsOffsets(nsecs_t vsyncDuration) const { const auto earlySfOffset = mHighFpsEarlySfOffsetNs.value_or(mHighFpsSfVSyncPhaseOffsetNs) < mThresholdForNextVsync ? mHighFpsEarlySfOffsetNs.value_or(mHighFpsSfVSyncPhaseOffsetNs) @@ -286,7 +286,7 @@ nsecs_t appDurationToOffset(std::chrono::nanoseconds appDuration, } } // namespace -WorkDuration::VsyncConfigSet WorkDuration::constructOffsets(nsecs_t vsyncDuration) const { +VsyncConfigSet WorkDuration::constructOffsets(nsecs_t vsyncDuration) const { const auto sfDurationFixup = [vsyncDuration](nsecs_t duration) { return duration == -1 ? std::chrono::nanoseconds(vsyncDuration) - 1ms : std::chrono::nanoseconds(duration); diff --git a/services/surfaceflinger/Scheduler/VsyncConfiguration.h b/services/surfaceflinger/Scheduler/VsyncConfiguration.h index 02ebd70272..a24e43f9d6 100644 --- a/services/surfaceflinger/Scheduler/VsyncConfiguration.h +++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.h @@ -20,12 +20,12 @@ #include #include +#include #include #include #include - -#include "VsyncModulator.h" +#include namespace android::scheduler { @@ -37,8 +37,6 @@ namespace android::scheduler { */ class VsyncConfiguration { public: - using VsyncConfigSet = VsyncModulator::VsyncConfigSet; - virtual ~VsyncConfiguration() = default; virtual VsyncConfigSet getCurrentConfigs() const = 0; virtual VsyncConfigSet getConfigsForRefreshRate(Fps fps) const = 0; @@ -85,7 +83,7 @@ public: void dump(std::string& result) const override; protected: - virtual VsyncConfiguration::VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const = 0; + virtual VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const = 0; VsyncConfigSet getConfigsForRefreshRateLocked(Fps fps) const REQUIRES(mLock); @@ -115,7 +113,7 @@ protected: nsecs_t hwcMinWorkDuration); private: - VsyncConfiguration::VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const override; + VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const override; VsyncConfigSet getDefaultOffsets(nsecs_t vsyncPeriod) const; VsyncConfigSet getHighFpsOffsets(nsecs_t vsyncPeriod) const; @@ -154,7 +152,7 @@ protected: nsecs_t hwcMinWorkDuration); private: - VsyncConfiguration::VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const override; + VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const override; const nsecs_t mSfDuration; const nsecs_t mAppDuration; diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.cpp b/services/surfaceflinger/Scheduler/VsyncModulator.cpp index 138d8d65f7..c9af4c27fe 100644 --- a/services/surfaceflinger/Scheduler/VsyncModulator.cpp +++ b/services/surfaceflinger/Scheduler/VsyncModulator.cpp @@ -40,7 +40,7 @@ VsyncModulator::VsyncModulator(const VsyncConfigSet& config, Now now) mNow(now), mTraceDetailedInfo(base::GetBoolProperty("debug.sf.vsync_trace_detailed_info", false)) {} -VsyncModulator::VsyncConfig VsyncModulator::setVsyncConfigSet(const VsyncConfigSet& config) { +VsyncConfig VsyncModulator::setVsyncConfigSet(const VsyncConfigSet& config) { std::lock_guard lock(mMutex); mVsyncConfigSet = config; return updateVsyncConfigLocked(); @@ -129,7 +129,7 @@ VsyncModulator::VsyncConfigOpt VsyncModulator::onDisplayRefresh(bool usedGpuComp return updateVsyncConfig(); } -VsyncModulator::VsyncConfig VsyncModulator::getVsyncConfig() const { +VsyncConfig VsyncModulator::getVsyncConfig() const { std::lock_guard lock(mMutex); return mVsyncConfig; } @@ -147,7 +147,7 @@ auto VsyncModulator::getNextVsyncConfigType() const -> VsyncConfigType { } } -const VsyncModulator::VsyncConfig& VsyncModulator::getNextVsyncConfig() const { +const VsyncConfig& VsyncModulator::getNextVsyncConfig() const { switch (getNextVsyncConfigType()) { case VsyncConfigType::Early: return mVsyncConfigSet.early; @@ -158,12 +158,12 @@ const VsyncModulator::VsyncConfig& VsyncModulator::getNextVsyncConfig() const { } } -VsyncModulator::VsyncConfig VsyncModulator::updateVsyncConfig() { +VsyncConfig VsyncModulator::updateVsyncConfig() { std::lock_guard lock(mMutex); return updateVsyncConfigLocked(); } -VsyncModulator::VsyncConfig VsyncModulator::updateVsyncConfigLocked() { +VsyncConfig VsyncModulator::updateVsyncConfigLocked() { const VsyncConfig& offsets = getNextVsyncConfig(); mVsyncConfig = offsets; diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.h b/services/surfaceflinger/Scheduler/VsyncModulator.h index 537cae1d7c..4b01ec3d52 100644 --- a/services/surfaceflinger/Scheduler/VsyncModulator.h +++ b/services/surfaceflinger/Scheduler/VsyncModulator.h @@ -25,6 +25,8 @@ #include #include +#include + #include "../WpHash.h" namespace android::scheduler { @@ -51,39 +53,8 @@ public: // This may keep early offsets for an extra frame, but avoids a race with transaction commit. static const std::chrono::nanoseconds MIN_EARLY_TRANSACTION_TIME; - // Phase offsets and work durations for SF and app deadlines from VSYNC. - struct VsyncConfig { - nsecs_t sfOffset; - nsecs_t appOffset; - std::chrono::nanoseconds sfWorkDuration; - std::chrono::nanoseconds appWorkDuration; - - bool operator==(const VsyncConfig& other) const { - return sfOffset == other.sfOffset && appOffset == other.appOffset && - sfWorkDuration == other.sfWorkDuration && - appWorkDuration == other.appWorkDuration; - } - - bool operator!=(const VsyncConfig& other) const { return !(*this == other); } - }; - using VsyncConfigOpt = std::optional; - struct VsyncConfigSet { - VsyncConfig early; // Used for early transactions, and during refresh rate change. - VsyncConfig earlyGpu; // Used during GPU composition. - VsyncConfig late; // Default. - std::chrono::nanoseconds hwcMinWorkDuration; // Used for calculating the - // earliest present time - - bool operator==(const VsyncConfigSet& other) const { - return early == other.early && earlyGpu == other.earlyGpu && late == other.late && - hwcMinWorkDuration == other.hwcMinWorkDuration; - } - - bool operator!=(const VsyncConfigSet& other) const { return !(*this == other); } - }; - using Clock = std::chrono::steady_clock; using TimePoint = Clock::time_point; using Now = TimePoint (*)(); diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h index 5522ff80c8..d6329e246c 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h @@ -23,7 +23,7 @@ #include #include -#include +#include namespace android { @@ -52,6 +52,7 @@ public: constexpr float getValue() const { return mFrequency; } int getIntValue() const { return static_cast(std::round(mFrequency)); } + constexpr Period getPeriod() const { return Period::fromNs(mPeriod); } constexpr nsecs_t getPeriodNsecs() const { return mPeriod; } private: diff --git a/services/surfaceflinger/Scheduler/include/scheduler/VsyncConfig.h b/services/surfaceflinger/Scheduler/include/scheduler/VsyncConfig.h new file mode 100644 index 0000000000..3b1985f56d --- /dev/null +++ b/services/surfaceflinger/Scheduler/include/scheduler/VsyncConfig.h @@ -0,0 +1,54 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include + +namespace android::scheduler { + +// Phase offsets and work durations for SF and app deadlines from VSYNC. +struct VsyncConfig { + nsecs_t sfOffset; + nsecs_t appOffset; + std::chrono::nanoseconds sfWorkDuration; + std::chrono::nanoseconds appWorkDuration; + + bool operator==(const VsyncConfig& other) const { + return sfOffset == other.sfOffset && appOffset == other.appOffset && + sfWorkDuration == other.sfWorkDuration && appWorkDuration == other.appWorkDuration; + } + + bool operator!=(const VsyncConfig& other) const { return !(*this == other); } +}; + +struct VsyncConfigSet { + VsyncConfig early; // Used for early transactions, and during refresh rate change. + VsyncConfig earlyGpu; // Used during GPU composition. + VsyncConfig late; // Default. + std::chrono::nanoseconds hwcMinWorkDuration; // Used for calculating the earliest present time. + + bool operator==(const VsyncConfigSet& other) const { + return early == other.early && earlyGpu == other.earlyGpu && late == other.late && + hwcMinWorkDuration == other.hwcMinWorkDuration; + } + + bool operator!=(const VsyncConfigSet& other) const { return !(*this == other); } +}; + +} // namespace android::scheduler diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index cb3c94f5b0..6a056d3839 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3652,17 +3652,16 @@ void SurfaceFlinger::initScheduler(const sp& display) { void SurfaceFlinger::updatePhaseConfiguration(const Fps& refreshRate) { mVsyncConfiguration->setRefreshRateFps(refreshRate); setVsyncConfig(mVsyncModulator->setVsyncConfigSet(mVsyncConfiguration->getCurrentConfigs()), - refreshRate.getPeriodNsecs()); + refreshRate.getPeriod()); } -void SurfaceFlinger::setVsyncConfig(const VsyncModulator::VsyncConfig& config, - nsecs_t vsyncPeriod) { +void SurfaceFlinger::setVsyncConfig(const scheduler::VsyncConfig& config, Period vsyncPeriod) { mScheduler->setDuration(mAppConnectionHandle, - /*workDuration=*/config.appWorkDuration, - /*readyDuration=*/config.sfWorkDuration); + /* workDuration */ config.appWorkDuration, + /* readyDuration */ config.sfWorkDuration); mScheduler->setDuration(mSfConnectionHandle, - /*workDuration=*/std::chrono::nanoseconds(vsyncPeriod), - /*readyDuration=*/config.sfWorkDuration); + /* workDuration */ vsyncPeriod, + /* readyDuration */ config.sfWorkDuration); mScheduler->setDuration(config.sfWorkDuration); } @@ -4026,7 +4025,7 @@ bool SurfaceFlinger::frameIsEarly(TimePoint expectedPresentTime, VsyncId vsyncId const auto predictedPresentTime = TimePoint::fromNs(prediction->presentTime); // The duration for which SF can delay a frame if it is considered early based on the - // VsyncModulator::VsyncConfig::appWorkDuration. + // VsyncConfig::appWorkDuration. if (constexpr std::chrono::nanoseconds kEarlyLatchMaxThreshold = 100ms; std::chrono::abs(predictedPresentTime - expectedPresentTime) >= kEarlyLatchMaxThreshold) { return false; diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 5e4015e728..a49c62387d 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -711,8 +711,7 @@ private: void initScheduler(const sp&) REQUIRES(kMainThreadContext, mStateLock); void updatePhaseConfiguration(const Fps&) REQUIRES(mStateLock); - void setVsyncConfig(const VsyncModulator::VsyncConfig&, nsecs_t vsyncPeriod); - + void setVsyncConfig(const scheduler::VsyncConfig&, Period vsyncPeriod); /* * Transactions diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 83e0c0ddc3..9773644282 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -157,6 +157,10 @@ inline TimePoint getFuzzedTimePoint(FuzzedDataProvider& fdp) { return TimePoint::fromNs(fdp.ConsumeIntegral()); } +inline Duration getFuzzedDuration(FuzzedDataProvider& fdp) { + return Duration::fromNs(fdp.ConsumeIntegral()); +} + inline FloatRect getFuzzedFloatRect(FuzzedDataProvider* fdp) { return FloatRect(fdp->ConsumeFloatingPoint() /*left*/, fdp->ConsumeFloatingPoint() /*right*/, @@ -196,9 +200,11 @@ struct FakePhaseOffsets : scheduler::VsyncConfiguration { static constexpr nsecs_t FAKE_PHASE_OFFSET_NS = 0; static constexpr auto FAKE_DURATION_OFFSET_NS = std::chrono::nanoseconds(0); - VsyncConfigSet getConfigsForRefreshRate(Fps) const override { return getCurrentConfigs(); } + scheduler::VsyncConfigSet getConfigsForRefreshRate(Fps) const override { + return getCurrentConfigs(); + } - VsyncConfigSet getCurrentConfigs() const override { + scheduler::VsyncConfigSet getCurrentConfigs() const override { return {{FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS, FAKE_DURATION_OFFSET_NS}, {FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS, @@ -510,11 +516,6 @@ public: mFlinger->getDesiredDisplayModeSpecs(display, &_); } - void setVsyncConfig(FuzzedDataProvider *fdp) { - const scheduler::VsyncModulator::VsyncConfig vsyncConfig{}; - mFlinger->setVsyncConfig(vsyncConfig, fdp->ConsumeIntegral()); - } - // TODO(b/248317436): extend to cover all displays for multi-display devices static std::optional getFirstDisplayId() { std::vector ids = SurfaceComposerClient::getPhysicalDisplayIds(); @@ -592,7 +593,11 @@ public: mFlinger->updateInputFlinger(); mFlinger->updateCursorAsync(); - setVsyncConfig(&mFdp); + mFlinger->setVsyncConfig({.sfOffset = mFdp.ConsumeIntegral(), + .appOffset = mFdp.ConsumeIntegral(), + .sfWorkDuration = getFuzzedDuration(mFdp), + .appWorkDuration = getFuzzedDuration(mFdp)}, + getFuzzedDuration(mFdp)); { ftl::FakeGuard guard(kMainThreadContext); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index 0af3efaa02..44805dba82 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -280,17 +280,14 @@ void SchedulerFuzzer::fuzzVSyncModulator() { }; using Schedule = scheduler::TransactionSchedule; using nanos = std::chrono::nanoseconds; - using VsyncModulator = scheduler::VsyncModulator; using FuzzImplVsyncModulator = scheduler::FuzzImplVsyncModulator; - const VsyncModulator::VsyncConfig early{SF_OFFSET_EARLY, APP_OFFSET_EARLY, - nanos(SF_DURATION_LATE), nanos(APP_DURATION_LATE)}; - const VsyncModulator::VsyncConfig earlyGpu{SF_OFFSET_EARLY_GPU, APP_OFFSET_EARLY_GPU, - nanos(SF_DURATION_EARLY), nanos(APP_DURATION_EARLY)}; - const VsyncModulator::VsyncConfig late{SF_OFFSET_LATE, APP_OFFSET_LATE, - nanos(SF_DURATION_EARLY_GPU), - nanos(APP_DURATION_EARLY_GPU)}; - const VsyncModulator::VsyncConfigSet offsets = {early, earlyGpu, late, - nanos(HWC_MIN_WORK_DURATION)}; + const scheduler::VsyncConfig early{SF_OFFSET_EARLY, APP_OFFSET_EARLY, nanos(SF_DURATION_LATE), + nanos(APP_DURATION_LATE)}; + const scheduler::VsyncConfig earlyGpu{SF_OFFSET_EARLY_GPU, APP_OFFSET_EARLY_GPU, + nanos(SF_DURATION_EARLY), nanos(APP_DURATION_EARLY)}; + const scheduler::VsyncConfig late{SF_OFFSET_LATE, APP_OFFSET_LATE, nanos(SF_DURATION_EARLY_GPU), + nanos(APP_DURATION_EARLY_GPU)}; + const scheduler::VsyncConfigSet offsets = {early, earlyGpu, late, nanos(HWC_MIN_WORK_DURATION)}; sp vSyncModulator = sp::make(offsets, scheduler::Now); (void)vSyncModulator->setVsyncConfigSet(offsets); diff --git a/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp b/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp index b5195826b1..8acbd6fa03 100644 --- a/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp @@ -57,17 +57,14 @@ protected: using Schedule = scheduler::TransactionSchedule; using nanos = std::chrono::nanoseconds; - const VsyncModulator::VsyncConfig kEarly{SF_OFFSET_EARLY, APP_OFFSET_EARLY, - nanos(SF_DURATION_LATE), nanos(APP_DURATION_LATE)}; - const VsyncModulator::VsyncConfig kEarlyGpu{SF_OFFSET_EARLY_GPU, APP_OFFSET_EARLY_GPU, - nanos(SF_DURATION_EARLY), - nanos(APP_DURATION_EARLY)}; - const VsyncModulator::VsyncConfig kLate{SF_OFFSET_LATE, APP_OFFSET_LATE, - nanos(SF_DURATION_EARLY_GPU), - nanos(APP_DURATION_EARLY_GPU)}; - - const VsyncModulator::VsyncConfigSet mOffsets = {kEarly, kEarlyGpu, kLate, - nanos(HWC_MIN_WORK_DURATION)}; + const VsyncConfig kEarly{SF_OFFSET_EARLY, APP_OFFSET_EARLY, nanos(SF_DURATION_LATE), + nanos(APP_DURATION_LATE)}; + const VsyncConfig kEarlyGpu{SF_OFFSET_EARLY_GPU, APP_OFFSET_EARLY_GPU, nanos(SF_DURATION_EARLY), + nanos(APP_DURATION_EARLY)}; + const VsyncConfig kLate{SF_OFFSET_LATE, APP_OFFSET_LATE, nanos(SF_DURATION_EARLY_GPU), + nanos(APP_DURATION_EARLY_GPU)}; + + const VsyncConfigSet mOffsets = {kEarly, kEarlyGpu, kLate, nanos(HWC_MIN_WORK_DURATION)}; sp mVsyncModulator = sp::make(mOffsets, Now); void SetUp() override { EXPECT_EQ(kLate, mVsyncModulator->setVsyncConfigSet(mOffsets)); } -- GitLab From 1c99a0018c4bbe2d4c55c166887eaf2be00e4a0c Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Fri, 20 Jan 2023 17:10:36 -0500 Subject: [PATCH 0761/1310] SF: Move ownership of VsyncModulator to Scheduler ...as a prerequisite for calculating the VSYNC deadline of the leader display (and deriving per-display state from it, e.g. present fences, traces) in the Scheduler. Bug: 241285475 Bug: 241285191 Test: Boot Change-Id: Ieea136616435464dd0756525f94441b8e82ad06a --- .../surfaceflinger/RegionSamplingThread.h | 1 - .../surfaceflinger/Scheduler/Scheduler.cpp | 46 ++++++++---- services/surfaceflinger/Scheduler/Scheduler.h | 34 ++++++++- .../surfaceflinger/Scheduler/VsyncModulator.h | 10 +-- .../include/scheduler/TransactionSchedule.h | 30 ++++++++ services/surfaceflinger/SurfaceFlinger.cpp | 73 +++++++++---------- services/surfaceflinger/SurfaceFlinger.h | 17 +---- .../fuzzer/surfaceflinger_fuzzers_utils.h | 28 ++++--- .../tests/unittests/TestableScheduler.h | 7 +- .../tests/unittests/TestableSurfaceFlinger.h | 11 +-- .../unittests/TransactionApplicationTest.cpp | 13 ++-- 11 files changed, 164 insertions(+), 106 deletions(-) create mode 100644 services/surfaceflinger/Scheduler/include/scheduler/TransactionSchedule.h diff --git a/services/surfaceflinger/RegionSamplingThread.h b/services/surfaceflinger/RegionSamplingThread.h index b62b15cb6d..e8c891e4bf 100644 --- a/services/surfaceflinger/RegionSamplingThread.h +++ b/services/surfaceflinger/RegionSamplingThread.h @@ -37,7 +37,6 @@ namespace android { class Layer; -class Scheduler; class SurfaceFlinger; struct SamplingOffsetCallback; diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index bc465ce932..1fc1519982 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -60,8 +60,12 @@ namespace android::scheduler { -Scheduler::Scheduler(ICompositor& compositor, ISchedulerCallback& callback, FeatureFlags features) - : impl::MessageQueue(compositor), mFeatures(features), mSchedulerCallback(callback) {} +Scheduler::Scheduler(ICompositor& compositor, ISchedulerCallback& callback, FeatureFlags features, + sp modulatorPtr) + : impl::MessageQueue(compositor), + mFeatures(features), + mVsyncModulator(std::move(modulatorPtr)), + mSchedulerCallback(callback) {} Scheduler::~Scheduler() { // MessageQueue depends on VsyncSchedule, so first destroy it. @@ -186,17 +190,19 @@ impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction( }; } -ConnectionHandle Scheduler::createConnection(const char* connectionName, - frametimeline::TokenManager* tokenManager, - std::chrono::nanoseconds workDuration, - std::chrono::nanoseconds readyDuration) { - auto throttleVsync = makeThrottleVsyncCallback(); - auto getVsyncPeriod = makeGetVsyncPeriodFunction(); - auto eventThread = - std::make_unique(connectionName, *mVsyncSchedule, tokenManager, - std::move(throttleVsync), std::move(getVsyncPeriod), - workDuration, readyDuration); - return createConnection(std::move(eventThread)); +ConnectionHandle Scheduler::createEventThread(Cycle cycle, + frametimeline::TokenManager* tokenManager, + std::chrono::nanoseconds workDuration, + std::chrono::nanoseconds readyDuration) { + auto eventThread = std::make_unique(cycle == Cycle::Render ? "app" : "appSf", + *mVsyncSchedule, tokenManager, + makeThrottleVsyncCallback(), + makeGetVsyncPeriodFunction(), + workDuration, readyDuration); + + auto& handle = cycle == Cycle::Render ? mAppConnectionHandle : mSfConnectionHandle; + handle = createConnection(std::move(eventThread)); + return handle; } ConnectionHandle Scheduler::createConnection(std::unique_ptr eventThread) { @@ -356,6 +362,20 @@ void Scheduler::setDuration(ConnectionHandle handle, std::chrono::nanoseconds wo thread->setDuration(workDuration, readyDuration); } +void Scheduler::setVsyncConfigSet(const VsyncConfigSet& configs, Period vsyncPeriod) { + setVsyncConfig(mVsyncModulator->setVsyncConfigSet(configs), vsyncPeriod); +} + +void Scheduler::setVsyncConfig(const VsyncConfig& config, Period vsyncPeriod) { + setDuration(mAppConnectionHandle, + /* workDuration */ config.appWorkDuration, + /* readyDuration */ config.sfWorkDuration); + setDuration(mSfConnectionHandle, + /* workDuration */ vsyncPeriod, + /* readyDuration */ config.sfWorkDuration); + setDuration(config.sfWorkDuration); +} + void Scheduler::enableHardwareVsync() { std::lock_guard lock(mHWVsyncLock); if (!mPrimaryHWVsyncEnabled && mHWVsyncAvailable) { diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index a3e28b6ca4..ef7d0cf5b1 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include "Display/DisplayMap.h" @@ -47,6 +48,7 @@ #include "OneShotTimer.h" #include "RefreshRateSelector.h" #include "Utils/Dumper.h" +#include "VsyncModulator.h" #include "VsyncSchedule.h" namespace android::scheduler { @@ -104,7 +106,7 @@ class Scheduler : android::impl::MessageQueue { using Impl = android::impl::MessageQueue; public: - Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags); + Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags, sp); virtual ~Scheduler(); void startTimers(); @@ -146,9 +148,14 @@ public: return std::move(future); } - ConnectionHandle createConnection(const char* connectionName, frametimeline::TokenManager*, - std::chrono::nanoseconds workDuration, - std::chrono::nanoseconds readyDuration); + enum class Cycle { + Render, // Surface rendering. + LastComposite // Ahead of display compositing by one refresh period. + }; + + ConnectionHandle createEventThread(Cycle, frametimeline::TokenManager*, + std::chrono::nanoseconds workDuration, + std::chrono::nanoseconds readyDuration); sp createDisplayEventConnection( ConnectionHandle, EventRegistrationFlags eventRegistration = {}); @@ -168,6 +175,18 @@ public: void setDuration(ConnectionHandle, std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration); + const VsyncModulator& vsyncModulator() const { return *mVsyncModulator; } + + template (VsyncModulator::*)(Args...)> + void modulateVsync(Handler handler, Args... args) { + if (const auto config = (*mVsyncModulator.*handler)(args...)) { + setVsyncConfig(*config, getLeaderVsyncPeriod()); + } + } + + void setVsyncConfigSet(const VsyncConfigSet&, Period vsyncPeriod); + // Sets the render rate for the scheduler to run at. void setRenderRate(Fps); @@ -270,6 +289,7 @@ private: void displayPowerTimerCallback(TimerState); void setVsyncPeriod(nsecs_t period); + void setVsyncConfig(const VsyncConfig&, Period vsyncPeriod); // Chooses a leader among the registered displays, unless `leaderIdOpt` is specified. The new // `mLeaderDisplayId` is never `std::nullopt`. @@ -330,6 +350,9 @@ private: mutable std::mutex mConnectionsLock; std::unordered_map mConnections GUARDED_BY(mConnectionsLock); + ConnectionHandle mAppConnectionHandle; + ConnectionHandle mSfConnectionHandle; + mutable std::mutex mHWVsyncLock; bool mPrimaryHWVsyncEnabled GUARDED_BY(mHWVsyncLock) = false; bool mHWVsyncAvailable GUARDED_BY(mHWVsyncLock) = false; @@ -339,6 +362,9 @@ private: const FeatureFlags mFeatures; std::optional mVsyncSchedule; + // Shifts the VSYNC phase during certain transactions and refresh rate changes. + const sp mVsyncModulator; + // Used to choose refresh rate if content detection is enabled. LayerHistory mLayerHistory; diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.h b/services/surfaceflinger/Scheduler/VsyncModulator.h index 4b01ec3d52..dc4dafd979 100644 --- a/services/surfaceflinger/Scheduler/VsyncModulator.h +++ b/services/surfaceflinger/Scheduler/VsyncModulator.h @@ -25,21 +25,13 @@ #include #include +#include #include #include "../WpHash.h" namespace android::scheduler { -// State machine controlled by transaction flags. VsyncModulator switches to early phase offsets -// when a transaction is flagged EarlyStart or Early, lasting until an EarlyEnd transaction or a -// fixed number of frames, respectively. -enum class TransactionSchedule { - Late, // Default. - EarlyStart, - EarlyEnd -}; - // Modulates VSYNC phase depending on transaction schedule and refresh rate changes. class VsyncModulator : public IBinder::DeathRecipient { public: diff --git a/services/surfaceflinger/Scheduler/include/scheduler/TransactionSchedule.h b/services/surfaceflinger/Scheduler/include/scheduler/TransactionSchedule.h new file mode 100644 index 0000000000..6fc44dde23 --- /dev/null +++ b/services/surfaceflinger/Scheduler/include/scheduler/TransactionSchedule.h @@ -0,0 +1,30 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +namespace android::scheduler { + +// State machine controlled by transaction flags. VsyncModulator switches to early phase offsets +// when a transaction is flagged EarlyStart or Early, lasting until an EarlyEnd transaction or a +// fixed number of frames, respectively. +enum class TransactionSchedule { + Late, // Default. + EarlyStart, + EarlyEnd +}; + +} // namespace android::scheduler diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 6a056d3839..be055b22fd 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -142,6 +142,7 @@ #include "Scheduler/LayerHistory.h" #include "Scheduler/Scheduler.h" #include "Scheduler/VsyncConfiguration.h" +#include "Scheduler/VsyncModulator.h" #include "ScreenCaptureOutput.h" #include "StartPropertySetThread.h" #include "SurfaceFlingerProperties.h" @@ -189,6 +190,7 @@ using gui::IWindowInfosListener; using gui::LayerMetadata; using gui::WindowInfo; using gui::aidl_utils::binderStatusFromStatusT; +using scheduler::VsyncModulator; using ui::Dataspace; using ui::DisplayPrimaries; using ui::RenderIntent; @@ -1163,7 +1165,7 @@ void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request, mScheduler->resyncToHardwareVsync(true, mode.modePtr->getFps()); // As we called to set period, we will call to onRefreshRateChangeCompleted once // VsyncController model is locked. - modulateVsync(&VsyncModulator::onRefreshRateChangeInitiated); + mScheduler->modulateVsync(&VsyncModulator::onRefreshRateChangeInitiated); updatePhaseConfiguration(mode.fps); mScheduler->setModeChangePending(true); @@ -2039,7 +2041,7 @@ void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t t bool periodFlushed = false; mScheduler->addResyncSample(timestamp, vsyncPeriod, &periodFlushed); if (periodFlushed) { - modulateVsync(&VsyncModulator::onRefreshRateChangeCompleted); + mScheduler->modulateVsync(&VsyncModulator::onRefreshRateChangeCompleted); } } @@ -2117,7 +2119,7 @@ TimePoint SurfaceFlinger::calculateExpectedPresentTime(TimePoint frameTime) cons const auto& schedule = mScheduler->getVsyncSchedule(); const TimePoint vsyncDeadline = schedule.vsyncDeadlineAfter(frameTime); - if (mVsyncModulator->getVsyncConfig().sfOffset > 0) { + if (mScheduler->vsyncModulator().getVsyncConfig().sfOffset > 0) { return vsyncDeadline; } @@ -2231,17 +2233,19 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe mPowerHintSessionEnabled = mPowerAdvisor->usePowerHintSession() && activeDisplay && activeDisplay->getPowerMode() == hal::PowerMode::ON; if (mPowerHintSessionEnabled) { - const auto& display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get(); - const Period vsyncPeriod = Period::fromNs(display->getActiveMode().fps.getPeriodNsecs()); mPowerAdvisor->setCommitStart(frameTime); mPowerAdvisor->setExpectedPresentTime(mExpectedPresentTime); // Frame delay is how long we should have minus how long we actually have. - const Duration idealSfWorkDuration = mVsyncModulator->getVsyncConfig().sfWorkDuration; + const Duration idealSfWorkDuration = + mScheduler->vsyncModulator().getVsyncConfig().sfWorkDuration; const Duration frameDelay = idealSfWorkDuration - (mExpectedPresentTime - frameTime); mPowerAdvisor->setFrameDelay(frameDelay); mPowerAdvisor->setTotalFrameTargetWorkDuration(idealSfWorkDuration); + + const auto& display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get(); + const Period vsyncPeriod = display->getActiveMode().fps.getPeriod(); mPowerAdvisor->setTargetWorkDuration(vsyncPeriod); // Send early hint here to make sure there's not another frame pending @@ -2468,7 +2472,7 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) // TODO: b/160583065 Enable skip validation when SF caches all client composition layers const bool usedGpuComposition = mHadClientComposition || mReusedClientComposition; - modulateVsync(&VsyncModulator::onDisplayRefresh, usedGpuComposition); + mScheduler->modulateVsync(&VsyncModulator::onDisplayRefresh, usedGpuComposition); mLayersWithQueuedFrames.clear(); if (mLayerTracingEnabled && mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) { @@ -2791,7 +2795,7 @@ void SurfaceFlinger::commitTransactions() { // so we can call commitTransactionsLocked unconditionally. // We clear the flags with mStateLock held to guarantee that // mCurrentState won't change until the transaction is committed. - modulateVsync(&VsyncModulator::onTransactionCommit); + mScheduler->modulateVsync(&VsyncModulator::onTransactionCommit); commitTransactionsLocked(clearTransactionFlags(eTransactionMask)); mDebugInTransaction = 0; @@ -3592,19 +3596,18 @@ void SurfaceFlinger::triggerOnFrameRateOverridesChanged() { } void SurfaceFlinger::initScheduler(const sp& display) { + using namespace scheduler; + LOG_ALWAYS_FATAL_IF(mScheduler); const auto activeMode = display->refreshRateSelector().getActiveMode(); const Fps activeRefreshRate = activeMode.fps; mRefreshRateStats = - std::make_unique(*mTimeStats, activeRefreshRate, - hal::PowerMode::OFF); + std::make_unique(*mTimeStats, activeRefreshRate, hal::PowerMode::OFF); mVsyncConfiguration = getFactory().createVsyncConfiguration(activeRefreshRate); - mVsyncModulator = sp::make(mVsyncConfiguration->getCurrentConfigs()); - using Feature = scheduler::Feature; - scheduler::FeatureFlags features; + FeatureFlags features; if (sysprop::use_content_detection_for_refresh_rate(false)) { features |= Feature::kContentDetection; @@ -3620,9 +3623,11 @@ void SurfaceFlinger::initScheduler(const sp& display) { features |= Feature::kKernelIdleTimer; } - mScheduler = std::make_unique(static_cast(*this), - static_cast(*this), - features); + auto modulatorPtr = sp::make(mVsyncConfiguration->getCurrentConfigs()); + + mScheduler = std::make_unique(static_cast(*this), + static_cast(*this), features, + std::move(modulatorPtr)); mScheduler->createVsyncSchedule(features); mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector()); @@ -3630,15 +3635,17 @@ void SurfaceFlinger::initScheduler(const sp& display) { mScheduler->startTimers(); const auto configs = mVsyncConfiguration->getCurrentConfigs(); - const nsecs_t vsyncPeriod = activeRefreshRate.getPeriodNsecs(); + mAppConnectionHandle = - mScheduler->createConnection("app", mFrameTimeline->getTokenManager(), - /*workDuration=*/configs.late.appWorkDuration, - /*readyDuration=*/configs.late.sfWorkDuration); + mScheduler->createEventThread(Scheduler::Cycle::Render, + mFrameTimeline->getTokenManager(), + /* workDuration */ configs.late.appWorkDuration, + /* readyDuration */ configs.late.sfWorkDuration); mSfConnectionHandle = - mScheduler->createConnection("appSf", mFrameTimeline->getTokenManager(), - /*workDuration=*/std::chrono::nanoseconds(vsyncPeriod), - /*readyDuration=*/configs.late.sfWorkDuration); + mScheduler->createEventThread(Scheduler::Cycle::LastComposite, + mFrameTimeline->getTokenManager(), + /* workDuration */ activeRefreshRate.getPeriod(), + /* readyDuration */ configs.late.sfWorkDuration); mScheduler->initVsync(mScheduler->getVsyncSchedule().getDispatch(), *mFrameTimeline->getTokenManager(), configs.late.sfWorkDuration); @@ -3649,20 +3656,10 @@ void SurfaceFlinger::initScheduler(const sp& display) { mFpsReporter = sp::make(*mFrameTimeline, *this); } -void SurfaceFlinger::updatePhaseConfiguration(const Fps& refreshRate) { +void SurfaceFlinger::updatePhaseConfiguration(Fps refreshRate) { mVsyncConfiguration->setRefreshRateFps(refreshRate); - setVsyncConfig(mVsyncModulator->setVsyncConfigSet(mVsyncConfiguration->getCurrentConfigs()), - refreshRate.getPeriod()); -} - -void SurfaceFlinger::setVsyncConfig(const scheduler::VsyncConfig& config, Period vsyncPeriod) { - mScheduler->setDuration(mAppConnectionHandle, - /* workDuration */ config.appWorkDuration, - /* readyDuration */ config.sfWorkDuration); - mScheduler->setDuration(mSfConnectionHandle, - /* workDuration */ vsyncPeriod, - /* readyDuration */ config.sfWorkDuration); - mScheduler->setDuration(config.sfWorkDuration); + mScheduler->setVsyncConfigSet(mVsyncConfiguration->getCurrentConfigs(), + refreshRate.getPeriod()); } void SurfaceFlinger::doCommitTransactions() { @@ -3861,7 +3858,7 @@ uint32_t SurfaceFlinger::clearTransactionFlags(uint32_t mask) { void SurfaceFlinger::setTransactionFlags(uint32_t mask, TransactionSchedule schedule, const sp& applyToken, FrameHint frameHint) { - modulateVsync(&VsyncModulator::setTransactionSchedule, schedule, applyToken); + mScheduler->modulateVsync(&VsyncModulator::setTransactionSchedule, schedule, applyToken); if (const bool scheduled = mTransactionFlags.fetch_or(mask) & mask; !scheduled) { scheduleCommit(frameHint); @@ -4066,7 +4063,7 @@ bool SurfaceFlinger::shouldLatchUnsignaled(const sp& layer, const layer_s // We don't want to latch unsignaled if are in early / client composition // as it leads to jank due to RenderEngine waiting for unsignaled buffer // or window animations being slow. - const auto isDefaultVsyncConfig = mVsyncModulator->isVsyncConfigDefault(); + const auto isDefaultVsyncConfig = mScheduler->vsyncModulator().isVsyncConfigDefault(); if (!isDefaultVsyncConfig) { ALOGV("%s: false (LatchUnsignaledConfig::AutoSingleLayer; !isDefaultVsyncConfig)", __func__); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index a49c62387d..3121a34f4c 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -56,6 +56,7 @@ #include #include #include +#include #include #include "Display/DisplayMap.h" @@ -73,7 +74,6 @@ #include "Scheduler/RefreshRateSelector.h" #include "Scheduler/RefreshRateStats.h" #include "Scheduler/Scheduler.h" -#include "Scheduler/VsyncModulator.h" #include "SurfaceFlingerFactory.h" #include "ThreadContext.h" #include "Tracing/LayerTracing.h" @@ -352,7 +352,6 @@ private: friend class TransactionApplicationTest; friend class TunnelModeEnabledReporterTest; - using VsyncModulator = scheduler::VsyncModulator; using TransactionSchedule = scheduler::TransactionSchedule; using TraverseLayersFunction = std::function; using RenderAreaFuture = ftl::Future>; @@ -470,14 +469,6 @@ private: return std::bind(dump, this, _1, _2, _3); } - template - void modulateVsync(Handler handler, Args... args) { - if (const auto config = (*mVsyncModulator.*handler)(args...)) { - setVsyncConfig(*config, mScheduler->getLeaderVsyncPeriod()); - } - } - // Maximum allowed number of display frames that can be set through backdoor static const int MAX_ALLOWED_DISPLAY_FRAMES = 2048; @@ -710,8 +701,7 @@ private: void updateCursorAsync(); void initScheduler(const sp&) REQUIRES(kMainThreadContext, mStateLock); - void updatePhaseConfiguration(const Fps&) REQUIRES(mStateLock); - void setVsyncConfig(const scheduler::VsyncConfig&, Period vsyncPeriod); + void updatePhaseConfiguration(Fps) REQUIRES(mStateLock); /* * Transactions @@ -1290,9 +1280,6 @@ private: // Stores phase offsets configured per refresh rate. std::unique_ptr mVsyncConfiguration; - // Optional to defer construction until PhaseConfiguration is created. - sp mVsyncModulator; - std::unique_ptr mRefreshRateStats; scheduler::PresentLatencyTracker mPresentLatencyTracker GUARDED_BY(kMainThreadContext); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 9773644282..cdffbb4724 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -224,16 +224,16 @@ namespace scheduler { class TestableScheduler : public Scheduler, private ICompositor { public: TestableScheduler(const std::shared_ptr& selectorPtr, - ISchedulerCallback& callback) + sp modulatorPtr, ISchedulerCallback& callback) : TestableScheduler(std::make_unique(), std::make_unique(), selectorPtr, - callback) {} + std::move(modulatorPtr), callback) {} TestableScheduler(std::unique_ptr controller, std::unique_ptr tracker, std::shared_ptr selectorPtr, - ISchedulerCallback& callback) - : Scheduler(*this, callback, Feature::kContentDetection) { + sp modulatorPtr, ISchedulerCallback& callback) + : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) { mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller))); const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId(); @@ -281,6 +281,8 @@ public: return Scheduler::onNonPrimaryDisplayModeChanged(handle, mode); } + using Scheduler::setVsyncConfig; + private: // ICompositor overrides: void configure() override {} @@ -593,11 +595,11 @@ public: mFlinger->updateInputFlinger(); mFlinger->updateCursorAsync(); - mFlinger->setVsyncConfig({.sfOffset = mFdp.ConsumeIntegral(), - .appOffset = mFdp.ConsumeIntegral(), - .sfWorkDuration = getFuzzedDuration(mFdp), - .appWorkDuration = getFuzzedDuration(mFdp)}, - getFuzzedDuration(mFdp)); + mutableScheduler().setVsyncConfig({.sfOffset = mFdp.ConsumeIntegral(), + .appOffset = mFdp.ConsumeIntegral(), + .sfWorkDuration = getFuzzedDuration(mFdp), + .appWorkDuration = getFuzzedDuration(mFdp)}, + getFuzzedDuration(mFdp)); { ftl::FakeGuard guard(kMainThreadContext); @@ -663,15 +665,17 @@ public: mRefreshRateSelector = std::make_shared(modes, kModeId60); const auto fps = mRefreshRateSelector->getActiveMode().modePtr->getFps(); mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps); - mFlinger->mVsyncModulator = sp::make( - mFlinger->mVsyncConfiguration->getCurrentConfigs()); + mFlinger->mRefreshRateStats = std::make_unique(*mFlinger->mTimeStats, fps, hal::PowerMode::OFF); + auto modulatorPtr = sp::make( + mFlinger->mVsyncConfiguration->getCurrentConfigs()); + mScheduler = new scheduler::TestableScheduler(std::move(vsyncController), std::move(vsyncTracker), mRefreshRateSelector, - *(callback ?: this)); + std::move(modulatorPtr), *(callback ?: this)); mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread)); mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread)); diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index 0f53eb69f3..0cbfa63354 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -37,12 +37,12 @@ public: TestableScheduler(RefreshRateSelectorPtr selectorPtr, ISchedulerCallback& callback) : TestableScheduler(std::make_unique(), std::make_unique(), std::move(selectorPtr), - callback) {} + /* modulatorPtr */ nullptr, callback) {} TestableScheduler(std::unique_ptr controller, std::unique_ptr tracker, RefreshRateSelectorPtr selectorPtr, - ISchedulerCallback& callback) - : Scheduler(*this, callback, Feature::kContentDetection) { + sp modulatorPtr, ISchedulerCallback& callback) + : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) { mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), std::make_unique(), std::move(controller))); @@ -99,6 +99,7 @@ public: Scheduler::setLeaderDisplay(displayId); } + auto& mutableVsyncModulator() { return *mVsyncModulator; } auto& mutableLayerHistory() { return mLayerHistory; } size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS { diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 72e0c7be16..51d20120cd 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -224,8 +224,6 @@ public: const auto fps = selectorPtr->getActiveMode().fps; mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps); - mFlinger->mVsyncModulator = sp::make( - mFlinger->mVsyncConfiguration->getCurrentConfigs()); mFlinger->mRefreshRateStats = std::make_unique(*mFlinger->mTimeStats, fps, @@ -238,16 +236,21 @@ public: ? static_cast(mNoOpSchedulerCallback) : static_cast(mSchedulerCallback); + auto modulatorPtr = sp::make( + mFlinger->mVsyncConfiguration->getCurrentConfigs()); + if (useNiceMock) { mScheduler = new testing::NiceMock(std::move(vsyncController), std::move(vsyncTracker), std::move(selectorPtr), + std::move(modulatorPtr), callback); } else { mScheduler = new scheduler::TestableScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(selectorPtr), callback); + std::move(selectorPtr), + std::move(modulatorPtr), callback); } mScheduler->initVsync(mScheduler->getVsyncSchedule().getDispatch(), *mTokenManager, 0ms); @@ -262,8 +265,6 @@ public: scheduler::TestableScheduler& mutableScheduler() { return *mScheduler; } scheduler::mock::SchedulerCallback& mockSchedulerCallback() { return mSchedulerCallback; } - auto& mutableVsyncModulator() { return mFlinger->mVsyncModulator; } - using CreateBufferQueueFunction = surfaceflinger::test::Factory::CreateBufferQueueFunction; void setCreateBufferQueueFunction(CreateBufferQueueFunction f) { mFactory.mCreateBufferQueue = f; diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index a28d1cd415..859f702fe7 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -228,6 +228,11 @@ public: EXPECT_EQ(0u, transactionQueue.size()); } + void modulateVsync() { + static_cast( + mFlinger.mutableScheduler().mutableVsyncModulator().onRefreshRateChangeInitiated()); + } + bool mHasListenerCallbacks = false; std::vector mCallbacks; int mTransactionNumber = 0; @@ -623,9 +628,7 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, DontLatchUnsignaledWhenEarlyOffset) { layer_state_t::eBufferChanged), }); - // Get VsyncModulator out of the default config - static_cast(mFlinger.mutableVsyncModulator()->onRefreshRateChangeInitiated()); - + modulateVsync(); setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); } @@ -985,9 +988,7 @@ TEST_F(LatchUnsignaledAlwaysTest, LatchUnsignaledWhenEarlyOffset) { layer_state_t::eBufferChanged), }); - // Get VsyncModulator out of the default config - static_cast(mFlinger.mutableVsyncModulator()->onRefreshRateChangeInitiated()); - + modulateVsync(); setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); } -- GitLab From cfb2d25e176dfd912580f0ebcd6820524f8b3c28 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Thu, 19 Jan 2023 04:44:02 +0000 Subject: [PATCH 0762/1310] SF: LayerSnapshotBuilder updates - Adds support for framerate, gamemode, transformhint & input - Fixes z-order traversal bug where a layer was visited twice. - Compat fix, a layer cannot be root once its parented Test: presubmit Bug: 238781169 Change-Id: Ic33aa09b2c41fadd4872ca481b41f86400629fc8 --- libs/gui/include/gui/LayerState.h | 18 +- services/surfaceflinger/DisplayDevice.cpp | 3 +- .../surfaceflinger/FrontEnd/DisplayInfo.h | 12 + .../FrontEnd/LayerHierarchy.cpp | 6 +- .../surfaceflinger/FrontEnd/LayerHierarchy.h | 4 +- services/surfaceflinger/FrontEnd/LayerLog.h | 27 ++ .../surfaceflinger/FrontEnd/LayerSnapshot.cpp | 52 +-- .../surfaceflinger/FrontEnd/LayerSnapshot.h | 11 + .../FrontEnd/LayerSnapshotBuilder.cpp | 382 +++++++++++++----- .../FrontEnd/LayerSnapshotBuilder.h | 45 ++- .../FrontEnd/RequestedLayerState.cpp | 67 ++- .../FrontEnd/RequestedLayerState.h | 6 + services/surfaceflinger/Layer.cpp | 5 +- .../tests/unittests/LayerHierarchyTest.cpp | 79 ++++ .../tests/unittests/LayerHierarchyTest.h | 12 + .../tests/unittests/LayerSnapshotTest.cpp | 65 ++- 16 files changed, 624 insertions(+), 170 deletions(-) create mode 100644 services/surfaceflinger/FrontEnd/LayerLog.h diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index ecde47fed5..0bd59e5143 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -232,9 +232,9 @@ struct layer_state_t { layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBackgroundColorChanged | layer_state_t::eBlurRegionsChanged | layer_state_t::eColorChanged | layer_state_t::eColorSpaceAgnosticChanged | layer_state_t::eColorTransformChanged | - layer_state_t::eCornerRadiusChanged | layer_state_t::eHdrMetadataChanged | - layer_state_t::eRenderBorderChanged | layer_state_t::eShadowRadiusChanged | - layer_state_t::eStretchChanged; + layer_state_t::eCornerRadiusChanged | layer_state_t::eDimmingEnabledChanged | + layer_state_t::eHdrMetadataChanged | layer_state_t::eRenderBorderChanged | + layer_state_t::eShadowRadiusChanged | layer_state_t::eStretchChanged; // Changes which invalidates the layer's visible region in CE. static constexpr uint64_t CONTENT_DIRTY = layer_state_t::CONTENT_CHANGES | @@ -245,7 +245,17 @@ struct layer_state_t { layer_state_t::HIERARCHY_CHANGES | layer_state_t::eAlphaChanged | layer_state_t::eColorTransformChanged | layer_state_t::eCornerRadiusChanged | layer_state_t::eFlagsChanged | layer_state_t::eLayerStackChanged | - layer_state_t::eTrustedOverlayChanged; + layer_state_t::eTrustedOverlayChanged | layer_state_t::eFrameRateChanged | + layer_state_t::eFixedTransformHintChanged; + + // Changes affecting data sent to input. + static constexpr uint64_t INPUT_CHANGES = layer_state_t::GEOMETRY_CHANGES | + layer_state_t::HIERARCHY_CHANGES | layer_state_t::eInputInfoChanged | + layer_state_t::eDropInputModeChanged | layer_state_t::eTrustedOverlayChanged; + + // Changes that affect the visible region on a display. + static constexpr uint64_t VISIBLE_REGION_CHANGES = + layer_state_t::GEOMETRY_CHANGES | layer_state_t::HIERARCHY_CHANGES; bool hasValidBuffer() const; void sanitize(int32_t permissions); diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 098207709a..d76faba354 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -166,7 +166,8 @@ auto DisplayDevice::getFrontEndInfo() const -> frontend::DisplayInfo { .receivesInput = receivesInput(), .isSecure = isSecure(), .isPrimary = isPrimary(), - .rotationFlags = ui::Transform::toRotationFlags(mOrientation)}; + .rotationFlags = ui::Transform::toRotationFlags(mOrientation), + .transformHint = getTransformHint()}; } void DisplayDevice::setPowerMode(hal::PowerMode mode) { diff --git a/services/surfaceflinger/FrontEnd/DisplayInfo.h b/services/surfaceflinger/FrontEnd/DisplayInfo.h index 0c7b24a2c4..6b9d7a2f5c 100644 --- a/services/surfaceflinger/FrontEnd/DisplayInfo.h +++ b/services/surfaceflinger/FrontEnd/DisplayInfo.h @@ -16,6 +16,8 @@ #pragma once +#include + #include namespace android::surfaceflinger::frontend { @@ -29,6 +31,16 @@ struct DisplayInfo { // TODO(b/238781169) can eliminate once sPrimaryDisplayRotationFlags is removed. bool isPrimary; ui::Transform::RotationFlags rotationFlags; + ui::Transform::RotationFlags transformHint; + std::string getDebugString() const { + std::stringstream debug; + debug << "DisplayInfo {displayId=" << info.displayId << " lw=" << info.logicalWidth + << " lh=" << info.logicalHeight << " transform={" << transform.dsdx() << " ," + << transform.dsdy() << " ," << transform.dtdx() << " ," << transform.dtdy() + << "} isSecure=" << isSecure << " isPrimary=" << isPrimary + << " rotationFlags=" << rotationFlags << " transformHint=" << transformHint << "}"; + return debug.str(); + } }; } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp index 514a642bdc..678d36b5cf 100644 --- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp @@ -28,8 +28,8 @@ auto layerZCompare = [](const std::pair& rhs) { auto lhsLayer = lhs.first->getLayer(); auto rhsLayer = rhs.first->getLayer(); - if (lhsLayer->layerStack != rhsLayer->layerStack) { - return lhsLayer->layerStack.id < rhsLayer->layerStack.id; + if (lhsLayer->layerStack.id != rhsLayer->layerStack.id) { + return lhsLayer->layerStack.id > rhsLayer->layerStack.id; } if (lhsLayer->z != rhsLayer->z) { return lhsLayer->z < rhsLayer->z; @@ -75,11 +75,11 @@ void LayerHierarchy::traverseInZOrder(const Visitor& visitor, for (auto it = mChildren.begin(); it < mChildren.end(); it++) { auto& [child, childVariant] = *it; if (traverseThisLayer && child->getLayer()->z >= 0) { + traverseThisLayer = false; bool breakTraversal = !visitor(*this, traversalPath); if (breakTraversal) { return; } - traverseThisLayer = false; } if (childVariant == LayerHierarchy::Variant::Detached) { continue; diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h index 8cdc24062b..ca8d301879 100644 --- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h @@ -41,11 +41,13 @@ class LayerHierarchyBuilder; // states. class LayerHierarchy { public: - enum Variant { + enum Variant : uint32_t { Attached, Detached, Relative, Mirror, + ftl_first = Attached, + ftl_last = Mirror, }; // Represents a unique path to a node. struct TraversalPath { diff --git a/services/surfaceflinger/FrontEnd/LayerLog.h b/services/surfaceflinger/FrontEnd/LayerLog.h new file mode 100644 index 0000000000..47e1e30c48 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/LayerLog.h @@ -0,0 +1,27 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +// Uncomment to trace layer updates for a single layer +// #define LOG_LAYER 1 + +#ifdef LOG_LAYER +#define LLOGV(LAYER_ID, x, ...) \ + ALOGV_IF(((LAYER_ID) == LOG_LAYER), "[%d] %s " x, LOG_LAYER, __func__, ##__VA_ARGS__); +#else +#define LLOGV(LAYER_ID, x, ...) ALOGV("[%d] %s " x, (LAYER_ID), __func__, ##__VA_ARGS__); +#endif diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp index d483a99824..3a0540c659 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp @@ -61,7 +61,7 @@ bool LayerSnapshot::isOpaqueFormat(PixelFormat format) { } bool LayerSnapshot::hasBufferOrSidebandStream() const { - return ((sidebandStream != nullptr) || (buffer != nullptr)); + return ((sidebandStream != nullptr) || (externalTexture != nullptr)); } bool LayerSnapshot::drawShadows() const { @@ -99,7 +99,7 @@ bool LayerSnapshot::isContentOpaque() const { // If the buffer has no alpha channel, then we are opaque if (hasBufferOrSidebandStream() && - isOpaqueFormat(buffer ? buffer->getPixelFormat() : PIXEL_FORMAT_NONE)) { + isOpaqueFormat(externalTexture ? externalTexture->getPixelFormat() : PIXEL_FORMAT_NONE)) { return true; } @@ -108,11 +108,7 @@ bool LayerSnapshot::isContentOpaque() const { } bool LayerSnapshot::isHiddenByPolicy() const { - if (CC_UNLIKELY(invalidTransform)) { - ALOGW("Hide layer %s because it has invalid transformation.", name.c_str()); - return true; - } - return isHiddenByPolicyFromParent || isHiddenByPolicyFromRelativeParent; + return invalidTransform || isHiddenByPolicyFromParent || isHiddenByPolicyFromRelativeParent; } bool LayerSnapshot::getIsVisible() const { @@ -128,19 +124,22 @@ bool LayerSnapshot::getIsVisible() const { } std::string LayerSnapshot::getIsVisibleReason() const { - if (!hasSomethingToDraw()) { - return "!hasSomethingToDraw"; - } - - if (isHiddenByPolicy()) { - return "isHiddenByPolicy"; - } - - if (color.a > 0.0f || hasBlur()) { - return ""; - } - - return "alpha = 0 and !hasBlur"; + // not visible + if (!hasSomethingToDraw()) return "!hasSomethingToDraw"; + if (invalidTransform) return "invalidTransform"; + if (isHiddenByPolicyFromParent) return "hidden by parent or layer flag"; + if (isHiddenByPolicyFromRelativeParent) return "hidden by relative parent"; + if (color.a == 0.0f && !hasBlur()) return "alpha = 0 and no blur"; + + // visible + std::stringstream reason; + if (sidebandStream != nullptr) reason << " sidebandStream"; + if (externalTexture != nullptr) reason << " buffer"; + if (fillsColor() || color.a > 0.0f) reason << " color{" << color << "}"; + if (drawShadows()) reason << " shadowSettings.length=" << shadowSettings.length; + if (backgroundBlurRadius > 0) reason << " backgroundBlurRadius=" << backgroundBlurRadius; + if (blurRegions.size() > 0) reason << " blurRegions.size()=" << blurRegions.size(); + return reason.str(); } bool LayerSnapshot::canReceiveInput() const { @@ -152,11 +151,16 @@ bool LayerSnapshot::isTransformValid(const ui::Transform& t) { return transformDet != 0 && !isinf(transformDet) && !isnan(transformDet); } +bool LayerSnapshot::hasInputInfo() const { + return inputInfo.token != nullptr || + inputInfo.inputConfig.test(gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL); +} + std::string LayerSnapshot::getDebugString() const { - return "Snapshot(" + base::StringPrintf("%p", this) + "){" + path.toString() + name + - " isHidden=" + std::to_string(isHiddenByPolicyFromParent) + - " isHiddenRelative=" + std::to_string(isHiddenByPolicyFromRelativeParent) + - " isVisible=" + std::to_string(isVisible) + " " + getIsVisibleReason() + "}"; + std::stringstream debug; + debug << "Snapshot{" << path.toString() << name << " isVisible=" << isVisible << " {" + << getIsVisibleReason() << "} changes=" << changes.string() << "}"; + return debug.str(); } } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h index d14bd3ae8b..4512ade977 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h @@ -20,6 +20,7 @@ #include #include "LayerHierarchy.h" #include "RequestedLayerState.h" +#include "Scheduler/LayerInfo.h" #include "android-base/stringprintf.h" namespace android::surfaceflinger::frontend { @@ -39,6 +40,10 @@ struct RoundedCornerState { } }; +struct ChildState { + bool hasValidFrameRate = false; +}; + // LayerSnapshot stores Layer state used by CompositionEngine and RenderEngine. Composition // Engine uses a pointer to LayerSnapshot (as LayerFECompositionState*) and the LayerSettings // passed to Render Engine are created using properties stored on this struct. @@ -59,6 +64,7 @@ struct LayerSnapshot : public compositionengine::LayerFECompositionState { bool layerOpaqueFlagSet; RoundedCornerState roundedCorner; FloatRect transformedBounds; + Rect transformedBoundsWithoutTransparentRegion; renderengine::ShadowSettings shadowSettings; bool premultipliedAlpha; bool isHdrY410; @@ -75,6 +81,10 @@ struct LayerSnapshot : public compositionengine::LayerFECompositionState { ui::Transform localTransform; gui::DropInputMode dropInputMode; bool isTrustedOverlay; + gui::GameMode gameMode; + scheduler::LayerInfo::FrameRate frameRate; + ui::Transform::RotationFlags fixedTransformHint; + ChildState childState; static bool isOpaqueFormat(PixelFormat format); static bool isTransformValid(const ui::Transform& t); @@ -91,6 +101,7 @@ struct LayerSnapshot : public compositionengine::LayerFECompositionState { bool isHiddenByPolicy() const; std::string getDebugString() const; std::string getIsVisibleReason() const; + bool hasInputInfo() const; }; } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index bff12d7b5a..40dffb9706 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -24,6 +24,8 @@ #include #include "DisplayHardware/HWC2.h" #include "DisplayHardware/Hal.h" +#include "LayerLog.h" +#include "TimeStats/TimeStats.h" #include "ftl/small_map.h" namespace android::surfaceflinger::frontend { @@ -250,6 +252,64 @@ auto getBlendMode(const LayerSnapshot& snapshot, const RequestedLayerState& requ return blendMode; } +void updateSurfaceDamage(const RequestedLayerState& requested, bool hasReadyFrame, + bool forceFullDamage, Region& outSurfaceDamageRegion) { + if (!hasReadyFrame) { + outSurfaceDamageRegion.clear(); + return; + } + if (forceFullDamage) { + outSurfaceDamageRegion = Region::INVALID_REGION; + } else { + outSurfaceDamageRegion = requested.surfaceDamageRegion; + } +} + +void updateVisibility(LayerSnapshot& snapshot) { + snapshot.isVisible = snapshot.getIsVisible(); + + // TODO(b/238781169) we are ignoring this compat for now, since we will have + // to remove any optimization based on visibility. + + // For compatibility reasons we let layers which can receive input + // receive input before they have actually submitted a buffer. Because + // of this we use canReceiveInput instead of isVisible to check the + // policy-visibility, ignoring the buffer state. However for layers with + // hasInputInfo()==false we can use the real visibility state. + // We are just using these layers for occlusion detection in + // InputDispatcher, and obviously if they aren't visible they can't occlude + // anything. + const bool visible = + (snapshot.inputInfo.token != nullptr) ? snapshot.canReceiveInput() : snapshot.isVisible; + snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE, !visible); +} + +bool needsInputInfo(const LayerSnapshot& snapshot, const RequestedLayerState& requested) { + if (requested.potentialCursor) { + return false; + } + + if (snapshot.inputInfo.token != nullptr) { + return true; + } + + if (snapshot.hasBufferOrSidebandStream()) { + return true; + } + + return requested.windowInfoHandle && + requested.windowInfoHandle->getInfo()->inputConfig.test( + gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL); +} + +void clearChanges(LayerSnapshot& snapshot) { + snapshot.changes.clear(); + snapshot.contentDirty = false; + snapshot.hasReadyFrame = false; + snapshot.sidebandStreamHasFrame = false; + snapshot.surfaceDamage.clear(); +} + } // namespace LayerSnapshot LayerSnapshotBuilder::getRootSnapshot() { @@ -274,6 +334,9 @@ LayerSnapshot LayerSnapshotBuilder::getRootSnapshot() { snapshot.inputInfo.touchOcclusionMode = gui::TouchOcclusionMode::BLOCK_UNTRUSTED; snapshot.dropInputMode = gui::DropInputMode::NONE; snapshot.isTrustedOverlay = false; + snapshot.gameMode = gui::GameMode::Unsupported; + snapshot.frameRate = {}; + snapshot.fixedTransformHint = ui::Transform::ROT_INVALID; return snapshot; } @@ -285,16 +348,15 @@ LayerSnapshotBuilder::LayerSnapshotBuilder(Args args) : LayerSnapshotBuilder() { } bool LayerSnapshotBuilder::tryFastUpdate(const Args& args) { - if (args.forceUpdate) { - // force update requested, so skip the fast path + if (args.forceUpdate || args.displayChanges) { + // force update requested, or we have display changes, so skip the fast path return false; } if (args.layerLifecycleManager.getGlobalChanges().get() == 0) { // there are no changes, so just clear the change flags from before. for (auto& snapshot : mSnapshots) { - snapshot->changes.clear(); - snapshot->contentDirty = false; + clearChanges(*snapshot); } return true; } @@ -320,14 +382,14 @@ bool LayerSnapshotBuilder::tryFastUpdate(const Args& args) { // Walk through the snapshots, clearing previous change flags and updating the snapshots // if needed. for (auto& snapshot : mSnapshots) { - snapshot->changes.clear(); - snapshot->contentDirty = false; + clearChanges(*snapshot); auto it = layersWithChanges.find(snapshot->path.id); if (it != layersWithChanges.end()) { ALOGV("%s fast path snapshot changes = %s", __func__, mRootSnapshot.changes.string().c_str()); LayerHierarchy::TraversalPath root = LayerHierarchy::TraversalPath::ROOT; - updateSnapshot(*snapshot, args, *it->second, mRootSnapshot, root); + updateSnapshot(*snapshot, args, *it->second, mRootSnapshot, root, + /*newSnapshot=*/false); } } return true; @@ -335,7 +397,6 @@ bool LayerSnapshotBuilder::tryFastUpdate(const Args& args) { void LayerSnapshotBuilder::updateSnapshots(const Args& args) { ATRACE_NAME("UpdateSnapshots"); - ALOGV("%s updateSnapshots force = %s", __func__, std::to_string(args.forceUpdate).c_str()); if (args.forceUpdate || args.displayChanges) { mRootSnapshot.geomLayerBounds = getMaxDisplayBounds(args.displays); } @@ -352,7 +413,7 @@ void LayerSnapshotBuilder::updateSnapshots(const Args& args) { } sortSnapshotsByZ(args); - mRootSnapshot.changes.clear(); + clearChanges(mRootSnapshot); // Destroy unreachable snapshots if (args.layerLifecycleManager.getDestroyedLayers().empty()) { @@ -372,6 +433,7 @@ void LayerSnapshotBuilder::updateSnapshots(const Args& args) { } mIdToSnapshot.erase(traversalPath); + mSnapshots.back()->globalZ = it->get()->globalZ; std::iter_swap(it, mSnapshots.end() - 1); mSnapshots.erase(mSnapshots.end() - 1); } @@ -384,12 +446,15 @@ void LayerSnapshotBuilder::update(const Args& args) { updateSnapshots(args); } -void LayerSnapshotBuilder::updateSnapshotsInHierarchy(const Args& args, - const LayerHierarchy& hierarchy, - LayerHierarchy::TraversalPath& traversalPath, - const LayerSnapshot& parentSnapshot) { +const LayerSnapshot& LayerSnapshotBuilder::updateSnapshotsInHierarchy( + const Args& args, const LayerHierarchy& hierarchy, + LayerHierarchy::TraversalPath& traversalPath, const LayerSnapshot& parentSnapshot) { const RequestedLayerState* layer = hierarchy.getLayer(); - LayerSnapshot* snapshot = getOrCreateSnapshot(traversalPath, *layer); + LayerSnapshot* snapshot = getSnapshot(traversalPath); + const bool newSnapshot = snapshot == nullptr; + if (newSnapshot) { + snapshot = createSnapshot(traversalPath, *layer); + } if (traversalPath.isRelative()) { bool parentIsRelative = traversalPath.variant == LayerHierarchy::Variant::Relative; updateRelativeState(*snapshot, parentSnapshot, parentIsRelative, args); @@ -397,23 +462,18 @@ void LayerSnapshotBuilder::updateSnapshotsInHierarchy(const Args& args, if (traversalPath.isAttached()) { resetRelativeState(*snapshot); } - updateSnapshot(*snapshot, args, *layer, parentSnapshot, traversalPath); - } - - // If layer is hidden by policy we can avoid update its children. If the visibility - // changed this update, then we still need to set the visibility on all the children. - if (snapshot->isHiddenByPolicy() && - (!snapshot->changes.any(RequestedLayerState::Changes::Visibility | - RequestedLayerState::Changes::Hierarchy))) { - return; + updateSnapshot(*snapshot, args, *layer, parentSnapshot, traversalPath, newSnapshot); } for (auto& [childHierarchy, variant] : hierarchy.mChildren) { LayerHierarchy::ScopedAddToTraversalPath addChildToPath(traversalPath, childHierarchy->getLayer()->id, variant); - updateSnapshotsInHierarchy(args, *childHierarchy, traversalPath, *snapshot); + const LayerSnapshot& childSnapshot = + updateSnapshotsInHierarchy(args, *childHierarchy, traversalPath, *snapshot); + updateChildState(*snapshot, childSnapshot, args); } + return *snapshot; } LayerSnapshot* LayerSnapshotBuilder::getSnapshot(uint32_t layerId) const { @@ -429,22 +489,17 @@ LayerSnapshot* LayerSnapshotBuilder::getSnapshot(const LayerHierarchy::Traversal return it == mIdToSnapshot.end() ? nullptr : it->second; } -LayerSnapshot* LayerSnapshotBuilder::getOrCreateSnapshot(const LayerHierarchy::TraversalPath& id, - const RequestedLayerState& layer) { - auto snapshot = getSnapshot(id); - if (snapshot) { - return snapshot; - } - +LayerSnapshot* LayerSnapshotBuilder::createSnapshot(const LayerHierarchy::TraversalPath& id, + const RequestedLayerState& layer) { mSnapshots.emplace_back(std::make_unique(layer, id)); - snapshot = mSnapshots.back().get(); + LayerSnapshot* snapshot = mSnapshots.back().get(); snapshot->globalZ = static_cast(mSnapshots.size()) - 1; mIdToSnapshot[id] = snapshot; return snapshot; } void LayerSnapshotBuilder::sortSnapshotsByZ(const Args& args) { - if (!args.forceUpdate && + if (!mResortSnapshots && !args.forceUpdate && !args.layerLifecycleManager.getGlobalChanges().any( RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Visibility)) { @@ -453,6 +508,8 @@ void LayerSnapshotBuilder::sortSnapshotsByZ(const Args& args) { return; } + mResortSnapshots = false; + size_t globalZ = 0; args.root.traverseInZOrder( [this, &globalZ](const LayerHierarchy&, @@ -467,7 +524,8 @@ void LayerSnapshotBuilder::sortSnapshotsByZ(const Args& args) { return false; } - if (snapshot->isVisible) { + if (snapshot->getIsVisible() || snapshot->hasInputInfo()) { + updateVisibility(*snapshot); size_t oldZ = snapshot->globalZ; size_t newZ = globalZ++; snapshot->globalZ = newZ; @@ -475,16 +533,17 @@ void LayerSnapshotBuilder::sortSnapshotsByZ(const Args& args) { return true; } mSnapshots[newZ]->globalZ = oldZ; + LLOGV(snapshot->sequence, "Made visible z=%zu -> %zu %s", oldZ, newZ, + snapshot->getDebugString().c_str()); std::iter_swap(mSnapshots.begin() + static_cast(oldZ), mSnapshots.begin() + static_cast(newZ)); } - return true; }); - + mNumInterestingSnapshots = (int)globalZ; while (globalZ < mSnapshots.size()) { mSnapshots[globalZ]->globalZ = globalZ; - mSnapshots[globalZ]->isVisible = false; + updateVisibility(*mSnapshots[globalZ]); globalZ++; } } @@ -493,7 +552,8 @@ void LayerSnapshotBuilder::updateRelativeState(LayerSnapshot& snapshot, const LayerSnapshot& parentSnapshot, bool parentIsRelative, const Args& args) { if (parentIsRelative) { - snapshot.isHiddenByPolicyFromRelativeParent = parentSnapshot.isHiddenByPolicyFromParent; + snapshot.isHiddenByPolicyFromRelativeParent = + parentSnapshot.isHiddenByPolicyFromParent || parentSnapshot.invalidTransform; if (args.includeMetadata) { snapshot.relativeLayerMetadata = parentSnapshot.layerMetadata; } @@ -507,6 +567,38 @@ void LayerSnapshotBuilder::updateRelativeState(LayerSnapshot& snapshot, snapshot.isVisible = snapshot.getIsVisible(); } +void LayerSnapshotBuilder::updateChildState(LayerSnapshot& snapshot, + const LayerSnapshot& childSnapshot, const Args& args) { + if (snapshot.childState.hasValidFrameRate) { + return; + } + if (args.forceUpdate || childSnapshot.changes.test(RequestedLayerState::Changes::FrameRate)) { + // We return whether this layer ot its children has a vote. We ignore ExactOrMultiple votes + // for the same reason we are allowing touch boost for those layers. See + // RefreshRateSelector::rankFrameRates for details. + using FrameRateCompatibility = scheduler::LayerInfo::FrameRateCompatibility; + const auto layerVotedWithDefaultCompatibility = childSnapshot.frameRate.rate.isValid() && + childSnapshot.frameRate.type == FrameRateCompatibility::Default; + const auto layerVotedWithNoVote = + childSnapshot.frameRate.type == FrameRateCompatibility::NoVote; + const auto layerVotedWithExactCompatibility = childSnapshot.frameRate.rate.isValid() && + childSnapshot.frameRate.type == FrameRateCompatibility::Exact; + + snapshot.childState.hasValidFrameRate |= layerVotedWithDefaultCompatibility || + layerVotedWithNoVote || layerVotedWithExactCompatibility; + + // If we don't have a valid frame rate, but the children do, we set this + // layer as NoVote to allow the children to control the refresh rate + if (!snapshot.frameRate.rate.isValid() && + snapshot.frameRate.type != FrameRateCompatibility::NoVote && + snapshot.childState.hasValidFrameRate) { + snapshot.frameRate = + scheduler::LayerInfo::FrameRate(Fps(), FrameRateCompatibility::NoVote); + snapshot.changes |= childSnapshot.changes & RequestedLayerState::Changes::FrameRate; + } + } +} + void LayerSnapshotBuilder::resetRelativeState(LayerSnapshot& snapshot) { snapshot.isHiddenByPolicyFromRelativeParent = false; snapshot.relativeLayerMetadata.mMap.clear(); @@ -523,27 +615,69 @@ uint32_t getDisplayRotationFlags( void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& args, const RequestedLayerState& requested, const LayerSnapshot& parentSnapshot, - const LayerHierarchy::TraversalPath& path) { + const LayerHierarchy::TraversalPath& path, + bool newSnapshot) { // Always update flags and visibility ftl::Flags parentChanges = parentSnapshot.changes & (RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Geometry | RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Metadata | RequestedLayerState::Changes::AffectsChildren); snapshot.changes = parentChanges | requested.changes; - snapshot.isHiddenByPolicyFromParent = - parentSnapshot.isHiddenByPolicyFromParent || requested.isHiddenByPolicy(); + snapshot.isHiddenByPolicyFromParent = parentSnapshot.isHiddenByPolicyFromParent || + parentSnapshot.invalidTransform || requested.isHiddenByPolicy(); snapshot.contentDirty = requested.what & layer_state_t::CONTENT_DIRTY; - if (snapshot.isHiddenByPolicyFromParent) { - snapshot.isVisible = false; - return; - } - + // TODO(b/238781169) scope down the changes to only buffer updates. + snapshot.hasReadyFrame = + (snapshot.contentDirty || requested.autoRefresh) && (requested.externalTexture); + // TODO(b/238781169) how is this used? ag/15523870 + snapshot.sidebandStreamHasFrame = false; + updateSurfaceDamage(requested, snapshot.hasReadyFrame, args.forceFullDamage, + snapshot.surfaceDamage); + + const bool forceUpdate = newSnapshot || args.forceUpdate || + snapshot.changes.any(RequestedLayerState::Changes::Visibility | + RequestedLayerState::Changes::Created); uint32_t displayRotationFlags = getDisplayRotationFlags(args.displays, snapshot.outputFilter.layerStack); - const bool forceUpdate = args.forceUpdate || - snapshot.changes.any(RequestedLayerState::Changes::Visibility | - RequestedLayerState::Changes::Created); + // always update the buffer regardless of visibility + if (forceUpdate || requested.what & layer_state_t::BUFFER_CHANGES) { + snapshot.acquireFence = + (requested.externalTexture && + requested.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged)) + ? requested.bufferData->acquireFence + : Fence::NO_FENCE; + snapshot.buffer = + requested.externalTexture ? requested.externalTexture->getBuffer() : nullptr; + snapshot.bufferSize = requested.getBufferSize(displayRotationFlags); + snapshot.geomBufferSize = snapshot.bufferSize; + snapshot.croppedBufferSize = requested.getCroppedBufferSize(snapshot.bufferSize); + snapshot.dataspace = requested.dataspace; + snapshot.externalTexture = requested.externalTexture; + snapshot.frameNumber = (requested.bufferData) ? requested.bufferData->frameNumber : 0; + snapshot.geomBufferTransform = requested.bufferTransform; + snapshot.geomBufferUsesDisplayInverseTransform = requested.transformToDisplayInverse; + snapshot.geomContentCrop = requested.getBufferCrop(); + snapshot.geomUsesSourceCrop = snapshot.hasBufferOrSidebandStream(); + snapshot.hasProtectedContent = requested.externalTexture && + requested.externalTexture->getUsage() & GRALLOC_USAGE_PROTECTED; + snapshot.isHdrY410 = requested.dataspace == ui::Dataspace::BT2020_ITU_PQ && + requested.api == NATIVE_WINDOW_API_MEDIA && + requested.bufferData->getPixelFormat() == HAL_PIXEL_FORMAT_RGBA_1010102; + snapshot.sidebandStream = requested.sidebandStream; + snapshot.transparentRegionHint = requested.transparentRegion; + snapshot.color.rgb = requested.getColor().rgb; + } + + if (snapshot.isHiddenByPolicyFromParent && !newSnapshot) { + if (forceUpdate || + snapshot.changes.any(RequestedLayerState::Changes::Hierarchy | + RequestedLayerState::Changes::Geometry | + RequestedLayerState::Changes::Input)) { + updateInput(snapshot, requested, parentSnapshot, path, args); + } + return; + } if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::AffectsChildren)) { // If root layer, use the layer stack otherwise get the parent's layer stack. @@ -567,6 +701,17 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a snapshot.colorTransform = requested.colorTransform; snapshot.colorTransformIsIdentity = !requested.hasColorTransform; } + snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE) + ? requested.gameMode + : parentSnapshot.gameMode; + snapshot.frameRate = (requested.requestedFrameRate.rate.isValid() || + (requested.requestedFrameRate.type == + scheduler::LayerInfo::FrameRateCompatibility::NoVote)) + ? requested.requestedFrameRate + : parentSnapshot.frameRate; + snapshot.fixedTransformHint = requested.fixedTransformHint != ui::Transform::ROT_INVALID + ? requested.fixedTransformHint + : parentSnapshot.fixedTransformHint; } if (forceUpdate || requested.changes.get() != 0) { @@ -576,35 +721,11 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a (requested.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque; } - if (forceUpdate || requested.what & layer_state_t::BUFFER_CHANGES) { - snapshot.acquireFence = - (requested.bufferData) ? requested.bufferData->acquireFence : Fence::NO_FENCE; - snapshot.buffer = - requested.externalTexture ? requested.externalTexture->getBuffer() : nullptr; - snapshot.bufferSize = requested.getBufferSize(displayRotationFlags); - snapshot.geomBufferSize = snapshot.bufferSize; - snapshot.croppedBufferSize = requested.getCroppedBufferSize(snapshot.bufferSize); - snapshot.dataspace = requested.dataspace; - snapshot.externalTexture = requested.externalTexture; - snapshot.frameNumber = (requested.bufferData) ? requested.bufferData->frameNumber : 0; - snapshot.geomBufferTransform = requested.bufferTransform; - snapshot.geomBufferUsesDisplayInverseTransform = requested.transformToDisplayInverse; - snapshot.geomContentCrop = requested.getBufferCrop(); - snapshot.geomUsesSourceCrop = snapshot.hasBufferOrSidebandStream(); - snapshot.hasProtectedContent = requested.externalTexture && - requested.externalTexture->getUsage() & GRALLOC_USAGE_PROTECTED; - snapshot.isHdrY410 = requested.dataspace == ui::Dataspace::BT2020_ITU_PQ && - requested.api == NATIVE_WINDOW_API_MEDIA && - requested.bufferData->getPixelFormat() == HAL_PIXEL_FORMAT_RGBA_1010102; - snapshot.sidebandStream = requested.sidebandStream; - snapshot.surfaceDamage = requested.surfaceDamageRegion; - snapshot.transparentRegionHint = requested.transparentRegion; - } - if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Content)) { snapshot.color.rgb = requested.getColor().rgb; snapshot.isColorspaceAgnostic = requested.colorSpaceAgnostic; - snapshot.backgroundBlurRadius = static_cast(requested.backgroundBlurRadius); + snapshot.backgroundBlurRadius = + args.supportsBlur ? static_cast(requested.backgroundBlurRadius) : 0; snapshot.blurRegions = requested.blurRegions; snapshot.hdrMetadata = requested.hdrMetadata; } @@ -620,12 +741,7 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a snapshot.changes.any(RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Geometry | RequestedLayerState::Changes::Input)) { - static frontend::DisplayInfo sDefaultInfo = {.isSecure = false}; - const std::optional displayInfo = - args.displays.get(snapshot.outputFilter.layerStack); - bool noValidDisplay = !displayInfo.has_value(); - updateInput(snapshot, requested, parentSnapshot, displayInfo.value_or(sDefaultInfo), - noValidDisplay, path); + updateInput(snapshot, requested, parentSnapshot, path, args); } // computed snapshot properties @@ -636,12 +752,14 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a } snapshot.forceClientComposition = snapshot.isHdrY410 || snapshot.shadowSettings.length > 0 || requested.blurRegions.size() > 0 || snapshot.stretchEffect.hasEffect(); - snapshot.isVisible = snapshot.getIsVisible(); snapshot.isOpaque = snapshot.isContentOpaque() && !snapshot.roundedCorner.hasRoundedCorners() && snapshot.color.a == 1.f; snapshot.blendMode = getBlendMode(snapshot, requested); - - ALOGV("%supdated [%d]%s changes parent:%s global:%s local:%s requested:%s %s from parent %s", + // TODO(b/238781169) pass this from flinger + // snapshot.fps; + // snapshot.metadata; + LLOGV(snapshot.sequence, + "%supdated [%d]%s changes parent:%s global:%s local:%s requested:%s %s from parent %s", args.forceUpdate ? "Force " : "", requested.id, requested.name.c_str(), parentSnapshot.changes.string().c_str(), snapshot.changes.string().c_str(), requested.changes.string().c_str(), std::to_string(requested.what).c_str(), @@ -694,12 +812,35 @@ void LayerSnapshotBuilder::updateLayerBounds(LayerSnapshot& snapshot, snapshot.localTransform = requested.getTransform(displayRotationFlags); snapshot.localTransformInverse = snapshot.localTransform.inverse(); snapshot.geomLayerTransform = parentSnapshot.geomLayerTransform * snapshot.localTransform; + const bool transformWasInvalid = snapshot.invalidTransform; snapshot.invalidTransform = !LayerSnapshot::isTransformValid(snapshot.geomLayerTransform); if (snapshot.invalidTransform) { - ALOGW("Resetting transform for %s because it has an invalid transformation.", - requested.getDebugStringShort().c_str()); + auto& t = snapshot.geomLayerTransform; + auto& requestedT = requested.requestedTransform; + std::string transformDebug = + base::StringPrintf(" transform={%f,%f,%f,%f} requestedTransform={%f,%f,%f,%f}", + t.dsdx(), t.dsdy(), t.dtdx(), t.dtdy(), requestedT.dsdx(), + requestedT.dsdy(), requestedT.dtdx(), requestedT.dtdy()); + std::string bufferDebug; + if (requested.externalTexture) { + auto unRotBuffer = requested.getUnrotatedBufferSize(displayRotationFlags); + auto& destFrame = requested.destinationFrame; + bufferDebug = base::StringPrintf(" buffer={%d,%d} displayRot=%d" + " destFrame={%d,%d,%d,%d} unRotBuffer={%d,%d}", + requested.externalTexture->getWidth(), + requested.externalTexture->getHeight(), + displayRotationFlags, destFrame.left, destFrame.top, + destFrame.right, destFrame.bottom, + unRotBuffer.getHeight(), unRotBuffer.getWidth()); + } + ALOGW("Resetting transform for %s because it is invalid.%s%s", + snapshot.getDebugString().c_str(), transformDebug.c_str(), bufferDebug.c_str()); snapshot.geomLayerTransform.reset(); } + if (transformWasInvalid != snapshot.invalidTransform) { + // If transform is invalid, the layer will be hidden. + mResortSnapshots = true; + } snapshot.geomInverseLayerTransform = snapshot.geomLayerTransform.inverse(); FloatRect parentBounds = parentSnapshot.geomLayerBounds; @@ -711,12 +852,19 @@ void LayerSnapshotBuilder::updateLayerBounds(LayerSnapshot& snapshot, } snapshot.geomLayerBounds = snapshot.geomLayerBounds.intersect(parentBounds); snapshot.transformedBounds = snapshot.geomLayerTransform.transform(snapshot.geomLayerBounds); + const Rect geomLayerBoundsWithoutTransparentRegion = + RequestedLayerState::reduce(Rect(snapshot.geomLayerBounds), + requested.transparentRegion); + snapshot.transformedBoundsWithoutTransparentRegion = + snapshot.geomLayerTransform.transform(geomLayerBoundsWithoutTransparentRegion); snapshot.parentTransform = parentSnapshot.geomLayerTransform; // Subtract the transparent region and snap to the bounds - Rect bounds = + const Rect bounds = RequestedLayerState::reduce(snapshot.croppedBufferSize, requested.transparentRegion); - snapshot.cursorFrame = snapshot.geomLayerTransform.transform(bounds); + if (requested.potentialCursor) { + snapshot.cursorFrame = snapshot.geomLayerTransform.transform(bounds); + } // TODO(b/238781169) use dest vs src snapshot.bufferNeedsFiltering = snapshot.externalTexture && @@ -749,15 +897,28 @@ void LayerSnapshotBuilder::updateShadows(LayerSnapshot& snapshot, void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, const RequestedLayerState& requested, const LayerSnapshot& parentSnapshot, - const frontend::DisplayInfo& displayInfo, - bool noValidDisplay, - const LayerHierarchy::TraversalPath& path) { + const LayerHierarchy::TraversalPath& path, + const Args& args) { + if (requested.windowInfoHandle) { + snapshot.inputInfo = *requested.windowInfoHandle->getInfo(); + } else { + snapshot.inputInfo = {}; + } snapshot.inputInfo.displayId = static_cast(snapshot.outputFilter.layerStack.id); - if (!requested.hasInputInfo()) { - snapshot.inputInfo.inputConfig = gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL; + + if (!needsInputInfo(snapshot, requested)) { return; } + static frontend::DisplayInfo sDefaultInfo = {.isSecure = false}; + const std::optional displayInfoOpt = + args.displays.get(snapshot.outputFilter.layerStack); + bool noValidDisplay = !displayInfoOpt.has_value(); + auto displayInfo = displayInfoOpt.value_or(sDefaultInfo); + + if (!requested.windowInfoHandle) { + snapshot.inputInfo.inputConfig = gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL; + } fillInputFrameInfo(snapshot.inputInfo, displayInfo.transform, snapshot); if (noValidDisplay) { @@ -766,17 +927,6 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::NOT_TOUCHABLE; } - // For compatibility reasons we let layers which can receive input - // receive input before they have actually submitted a buffer. Because - // of this we use canReceiveInput instead of isVisible to check the - // policy-visibility, ignoring the buffer state. However for layers with - // hasInputInfo()==false we can use the real visibility state. - // We are just using these layers for occlusion detection in - // InputDispatcher, and obviously if they aren't visible they can't occlude - // anything. - const bool visible = requested.hasInputInfo() ? snapshot.canReceiveInput() : snapshot.isVisible; - snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE, !visible); - snapshot.inputInfo.alpha = snapshot.color.a; snapshot.inputInfo.touchOcclusionMode = parentSnapshot.inputInfo.touchOcclusionMode; if (requested.dropInputMode == gui::DropInputMode::ALL || @@ -830,4 +980,28 @@ std::vector>& LayerSnapshotBuilder::getSnapshots( return mSnapshots; } +void LayerSnapshotBuilder::forEachVisibleSnapshot(const ConstVisitor& visitor) const { + for (int i = 0; i < mNumInterestingSnapshots; i++) { + LayerSnapshot& snapshot = *mSnapshots[(size_t)i]; + if (!snapshot.isVisible) continue; + visitor(snapshot); + } +} + +void LayerSnapshotBuilder::forEachVisibleSnapshot(const Visitor& visitor) { + for (int i = 0; i < mNumInterestingSnapshots; i++) { + std::unique_ptr& snapshot = mSnapshots.at((size_t)i); + if (!snapshot->isVisible) continue; + visitor(snapshot); + } +} + +void LayerSnapshotBuilder::forEachInputSnapshot(const ConstVisitor& visitor) const { + for (int i = mNumInterestingSnapshots - 1; i >= 0; i--) { + LayerSnapshot& snapshot = *mSnapshots[(size_t)i]; + if (!snapshot.hasInputInfo()) continue; + visitor(snapshot); + } +} + } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h index 33b250c793..abb7e668c3 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h @@ -44,6 +44,8 @@ public: // Set to true if there were display changes since last update. bool displayChanges = false; const renderengine::ShadowSettings& globalShadowSettings; + bool supportsBlur = true; + bool forceFullDamage = false; }; LayerSnapshotBuilder(); @@ -56,10 +58,22 @@ public: // change flags. void update(const Args&); std::vector>& getSnapshots(); + LayerSnapshot* getSnapshot(uint32_t layerId) const; + + typedef std::function ConstVisitor; + + // Visit each visible snapshot in z-order + void forEachVisibleSnapshot(const ConstVisitor& visitor) const; + + typedef std::function& snapshot)> Visitor; + // Visit each visible snapshot in z-order and move the snapshot if needed + void forEachVisibleSnapshot(const Visitor& visitor); + + // Visit each snapshot interesting to input reverse z-order + void forEachInputSnapshot(const ConstVisitor& visitor) const; private: friend class LayerSnapshotTest; - LayerSnapshot* getSnapshot(uint32_t layerId) const; LayerSnapshot* getSnapshot(const LayerHierarchy::TraversalPath& id) const; static LayerSnapshot getRootSnapshot(); @@ -69,28 +83,29 @@ private: void updateSnapshots(const Args& args); - void updateSnapshotsInHierarchy(const Args&, const LayerHierarchy& hierarchy, - LayerHierarchy::TraversalPath& traversalPath, - const LayerSnapshot& parentSnapshot); - void updateSnapshot(LayerSnapshot& snapshot, const Args& args, const RequestedLayerState&, - const LayerSnapshot& parentSnapshot, - const LayerHierarchy::TraversalPath& path); + const LayerSnapshot& updateSnapshotsInHierarchy(const Args&, const LayerHierarchy& hierarchy, + LayerHierarchy::TraversalPath& traversalPath, + const LayerSnapshot& parentSnapshot); + void updateSnapshot(LayerSnapshot&, const Args&, const RequestedLayerState&, + const LayerSnapshot& parentSnapshot, const LayerHierarchy::TraversalPath&, + bool newSnapshot); static void updateRelativeState(LayerSnapshot& snapshot, const LayerSnapshot& parentSnapshot, bool parentIsRelative, const Args& args); static void resetRelativeState(LayerSnapshot& snapshot); static void updateRoundedCorner(LayerSnapshot& snapshot, const RequestedLayerState& layerState, const LayerSnapshot& parentSnapshot); - static void updateLayerBounds(LayerSnapshot& snapshot, const RequestedLayerState& layerState, - const LayerSnapshot& parentSnapshot, - uint32_t displayRotationFlags); + void updateLayerBounds(LayerSnapshot& snapshot, const RequestedLayerState& layerState, + const LayerSnapshot& parentSnapshot, uint32_t displayRotationFlags); static void updateShadows(LayerSnapshot& snapshot, const RequestedLayerState& requested, const renderengine::ShadowSettings& globalShadowSettings); void updateInput(LayerSnapshot& snapshot, const RequestedLayerState& requested, - const LayerSnapshot& parentSnapshot, const frontend::DisplayInfo& displayInfo, - bool noValidDisplay, const LayerHierarchy::TraversalPath& path); + const LayerSnapshot& parentSnapshot, const LayerHierarchy::TraversalPath& path, + const Args& args); void sortSnapshotsByZ(const Args& args); - LayerSnapshot* getOrCreateSnapshot(const LayerHierarchy::TraversalPath& id, - const RequestedLayerState& layer); + LayerSnapshot* createSnapshot(const LayerHierarchy::TraversalPath& id, + const RequestedLayerState& layer); + void updateChildState(LayerSnapshot& snapshot, const LayerSnapshot& childSnapshot, + const Args& args); struct TraversalPathHash { std::size_t operator()(const LayerHierarchy::TraversalPath& key) const { @@ -105,6 +120,8 @@ private: mIdToSnapshot; std::vector> mSnapshots; LayerSnapshot mRootSnapshot; + bool mResortSnapshots = false; + int mNumInterestingSnapshots = 0; }; } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index dcc16e8db7..39bf07abd2 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -14,16 +14,18 @@ * limitations under the License. */ -#include "FrontEnd/LayerCreationArgs.h" #define ATRACE_TAG ATRACE_TAG_GRAPHICS #undef LOG_TAG #define LOG_TAG "RequestedLayerState" +#include #include #include #include "Layer.h" +#include "LayerCreationArgs.h" #include "LayerHandle.h" +#include "LayerLog.h" #include "RequestedLayerState.h" namespace android::surfaceflinger::frontend { @@ -47,7 +49,7 @@ std::string layerIdToString(uint32_t layerId) { RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args) : id(args.sequence), - name(args.name), + name(args.name + "#" + std::to_string(args.sequence)), canBeRoot(args.addToRoot), layerCreationFlags(args.flags), textureName(args.textureName), @@ -59,6 +61,9 @@ RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args) changes |= RequestedLayerState::Changes::Metadata; handleAlive = true; parentId = LayerHandle::getLayerId(args.parentHandle.promote()); + if (args.parentHandle != nullptr) { + canBeRoot = false; + } mirrorId = LayerHandle::getLayerId(args.mirrorLayerHandle.promote()); if (mirrorId != UNASSIGNED_LAYER_ID) { changes |= RequestedLayerState::Changes::Mirror; @@ -83,6 +88,7 @@ RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args) } else { color.rgb = {0.0_hf, 0.0_hf, 0.0_hf}; } + LLOGV(layerId, "Created %s flags=%d", getDebugString().c_str(), flags); color.a = 1.0f; crop.makeInvalid(); @@ -114,11 +120,14 @@ RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args) defaultFrameRateCompatibility = static_cast(scheduler::LayerInfo::FrameRateCompatibility::Default); dataspace = ui::Dataspace::V0_SRGB; + gameMode = gui::GameMode::Unsupported; + requestedFrameRate = {}; } void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerState) { - bool oldFlags = flags; - Rect oldBufferSize = getBufferSize(0); + const uint32_t oldFlags = flags; + const half oldAlpha = color.a; + const bool hadBufferOrSideStream = hasValidBuffer() || sidebandStream != nullptr; const layer_state_t& clientState = resolvedComposerState.state; uint64_t clientChanges = what | layer_state_t::diff(clientState); @@ -127,14 +136,28 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta if (clientState.what & layer_state_t::eFlagsChanged) { if ((oldFlags ^ flags) & layer_state_t::eLayerHidden) { - changes |= RequestedLayerState::Changes::Visibility; + changes |= RequestedLayerState::Changes::Visibility | + RequestedLayerState::Changes::VisibleRegion; } if ((oldFlags ^ flags) & layer_state_t::eIgnoreDestinationFrame) { changes |= RequestedLayerState::Changes::Geometry; } } - if (clientState.what & layer_state_t::eBufferChanged && oldBufferSize != getBufferSize(0)) { - changes |= RequestedLayerState::Changes::Geometry; + if (clientState.what & + (layer_state_t::eBufferChanged | layer_state_t::eSidebandStreamChanged)) { + const bool hasBufferOrSideStream = hasValidBuffer() || sidebandStream != nullptr; + if (hadBufferOrSideStream != hasBufferOrSideStream) { + changes |= RequestedLayerState::Changes::Geometry | + RequestedLayerState::Changes::VisibleRegion | + RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Input | + RequestedLayerState::Changes::Buffer; + } + } + if (what & (layer_state_t::eAlphaChanged)) { + if (oldAlpha == 0 || color.a == 0) { + changes |= RequestedLayerState::Changes::Visibility | + RequestedLayerState::Changes::VisibleRegion; + } } if (clientChanges & layer_state_t::HIERARCHY_CHANGES) changes |= RequestedLayerState::Changes::Hierarchy; @@ -144,7 +167,10 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta changes |= RequestedLayerState::Changes::Geometry; if (clientChanges & layer_state_t::AFFECTS_CHILDREN) changes |= RequestedLayerState::Changes::AffectsChildren; - + if (clientChanges & layer_state_t::INPUT_CHANGES) + changes |= RequestedLayerState::Changes::Input; + if (clientChanges & layer_state_t::VISIBLE_REGION_CHANGES) + changes |= RequestedLayerState::Changes::VisibleRegion; if (clientState.what & layer_state_t::eColorTransformChanged) { static const mat4 identityMatrix = mat4(); hasColorTransform = colorTransform != identityMatrix; @@ -183,7 +209,6 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta wp& touchableRegionCropHandle = windowInfoHandle->editInfo()->touchableRegionCropHandle; touchCropId = LayerHandle::getLayerId(touchableRegionCropHandle.promote()); - changes |= RequestedLayerState::Changes::Input; touchableRegionCropHandle.clear(); } if (clientState.what & layer_state_t::eStretchChanged) { @@ -205,6 +230,27 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta if (clientState.what & layer_state_t::eMatrixChanged) { requestedTransform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy); } + if (clientState.what & layer_state_t::eMetadataChanged) { + const int32_t requestedGameMode = + clientState.metadata.getInt32(gui::METADATA_GAME_MODE, -1); + if (requestedGameMode != -1) { + // The transaction will be received on the Task layer and needs to be applied to all + // child layers. + if (static_cast(gameMode) != requestedGameMode) { + gameMode = static_cast(requestedGameMode); + changes |= RequestedLayerState::Changes::AffectsChildren; + } + } + } + if (clientState.what & layer_state_t::eFrameRateChanged) { + const auto compatibility = + Layer::FrameRate::convertCompatibility(clientState.frameRateCompatibility); + const auto strategy = Layer::FrameRate::convertChangeFrameRateStrategy( + clientState.changeFrameRateStrategy); + requestedFrameRate = + Layer::FrameRate(Fps::fromValue(clientState.frameRate), compatibility, strategy); + changes |= RequestedLayerState::Changes::FrameRate; + } } ui::Size RequestedLayerState::getUnrotatedBufferSize(uint32_t displayRotationFlags) const { @@ -368,7 +414,8 @@ Rect RequestedLayerState::reduce(const Rect& win, const Region& exclude) { // If the relative parentid is unassigned, the layer will be considered relative but won't be // reachable. bool RequestedLayerState::hasValidRelativeParent() const { - return isRelativeOf && parentId != relativeParentId; + return isRelativeOf && + (parentId != relativeParentId || relativeParentId == UNASSIGNED_LAYER_ID); } bool RequestedLayerState::hasInputInfo() const { diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h index 95240d0682..3a16531d80 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h @@ -20,6 +20,7 @@ #include #include #include +#include "Scheduler/LayerInfo.h" #include "LayerCreationArgs.h" #include "TransactionState.h" @@ -48,6 +49,9 @@ struct RequestedLayerState : layer_state_t { Metadata = 1u << 10, Visibility = 1u << 11, AffectsChildren = 1u << 12, + FrameRate = 1u << 13, + VisibleRegion = 1u << 14, + Buffer = 1u << 15, }; static Rect reduce(const Rect& win, const Region& exclude); RequestedLayerState(const LayerCreationArgs&); @@ -91,6 +95,8 @@ struct RequestedLayerState : layer_state_t { ui::Transform requestedTransform; std::shared_ptr acquireFenceTime; std::shared_ptr externalTexture; + gui::GameMode gameMode; + scheduler::LayerInfo::FrameRate requestedFrameRate; // book keeping states bool handleAlive = true; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 0179d62f08..ba1d2c671b 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -475,7 +475,8 @@ void Layer::prepareBasicGeometryCompositionState() { snapshot->geomLayerTransform = getTransform(); snapshot->geomInverseLayerTransform = snapshot->geomLayerTransform.inverse(); snapshot->transparentRegionHint = getActiveTransparentRegion(drawingState); - snapshot->localTransformInverse = getActiveTransform(drawingState).inverse(); + snapshot->localTransform = getActiveTransform(drawingState); + snapshot->localTransformInverse = snapshot->localTransform.inverse(); snapshot->blendMode = static_cast(blendMode); snapshot->alpha = alpha; snapshot->backgroundBlurRadius = drawingState.backgroundBlurRadius; @@ -2595,6 +2596,8 @@ void Layer::cloneDrawingState(const Layer* from) { mDrawingState = from->mDrawingState; // Skip callback info since they are not applicable for cloned layers. mDrawingState.releaseBufferListener = nullptr; + // TODO (b/238781169) currently broken for mirror layers because we do not + // track release fences for mirror layers composed on other displays mDrawingState.callbackHandles = {}; } diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp index 33c94400d6..783df2821b 100644 --- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp @@ -562,4 +562,83 @@ TEST_F(LayerHierarchyTest, traversalPathId) { hierarchyBuilder.getHierarchy().traverseInZOrder(checkTraversalPathIdVisitor); } +TEST_F(LayerHierarchyTest, zorderRespectsLayerSequenceId) { + // remove default hierarchy + mLifecycleManager = LayerLifecycleManager(); + createRootLayer(1); + createRootLayer(2); + createRootLayer(4); + createRootLayer(5); + createLayer(11, 1); + createLayer(51, 5); + createLayer(53, 5); + + mLifecycleManager.commitChanges(); + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + UPDATE_AND_VERIFY(hierarchyBuilder); + std::vector expectedTraversalPath = {1, 11, 2, 4, 5, 51, 53}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); + + // A new layer is added with a smaller sequence id. Make sure its sorted correctly. While + // sequence ids are always incremented, this scenario can happen when a layer is reparented. + createRootLayer(3); + createLayer(52, 5); + + UPDATE_AND_VERIFY(hierarchyBuilder); + expectedTraversalPath = {1, 11, 2, 3, 4, 5, 51, 52, 53}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, zorderRespectsLayerZ) { + // remove default hierarchy + mLifecycleManager = LayerLifecycleManager(); + createRootLayer(1); + createLayer(11, 1); + createLayer(12, 1); + createLayer(13, 1); + setZ(11, -1); + setZ(12, 2); + setZ(13, 1); + + mLifecycleManager.commitChanges(); + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + UPDATE_AND_VERIFY(hierarchyBuilder); + std::vector expectedTraversalPath = {1, 11, 13, 12}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + + expectedTraversalPath = {11, 1, 13, 12}; + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + +TEST_F(LayerHierarchyTest, zorderRespectsLayerStack) { + // remove default hierarchy + mLifecycleManager = LayerLifecycleManager(); + createRootLayer(1); + createRootLayer(2); + createLayer(11, 1); + createLayer(21, 2); + setLayerStack(1, 20); + setLayerStack(2, 10); + + mLifecycleManager.commitChanges(); + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + UPDATE_AND_VERIFY(hierarchyBuilder); + std::vector expectedTraversalPath = {1, 11, 2, 21}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + + expectedTraversalPath = {1, 11, 2, 21}; + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h index 08727f2c17..1a822329bd 100644 --- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h +++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h @@ -245,6 +245,18 @@ protected: mLifecycleManager.applyTransactions(transactions); } + void setLayerStack(uint32_t id, int32_t layerStack) { + std::vector transactions; + transactions.emplace_back(); + transactions.back().states.push_back({}); + + transactions.back().states.front().state.what = layer_state_t::eLayerStackChanged; + transactions.back().states.front().state.surface = mHandles[id]; + transactions.back().states.front().state.layerId = static_cast(id); + transactions.back().states.front().state.layerStack = ui::LayerStack::fromValue(layerStack); + mLifecycleManager.applyTransactions(transactions); + } + LayerLifecycleManager mLifecycleManager; std::unordered_map> mHandles; }; diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index 2441c065c0..e124342edc 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -38,6 +38,9 @@ namespace android::surfaceflinger::frontend { +using ftl::Flags; +using namespace ftl::flag_operators; + // To run test: /** mp :libsurfaceflinger_unittest && adb sync; adb shell \ @@ -88,14 +91,11 @@ protected: ASSERT_TRUE(expectedBuilder.getSnapshots().size() > 0); ASSERT_TRUE(actualBuilder.getSnapshots().size() > 0); - std::vector>& snapshots = actualBuilder.getSnapshots(); std::vector actualVisibleLayerIdsInZOrder; - for (auto& snapshot : snapshots) { - if (!snapshot->isVisible) { - break; - } - actualVisibleLayerIdsInZOrder.push_back(snapshot->path.id); - } + actualBuilder.forEachVisibleSnapshot( + [&actualVisibleLayerIdsInZOrder](const LayerSnapshot& snapshot) { + actualVisibleLayerIdsInZOrder.push_back(snapshot.path.id); + }); EXPECT_EQ(expectedVisibleLayerIdsInZOrder, actualVisibleLayerIdsInZOrder); } @@ -103,7 +103,6 @@ protected: LayerHierarchyBuilder mHierarchyBuilder{{}}; LayerSnapshotBuilder mSnapshotBuilder; - std::unordered_map> mHandles; display::DisplayMap mFrontEndDisplayInfos; renderengine::ShadowSettings globalShadowSettings; static const std::vector STARTING_ZORDER; @@ -257,4 +256,54 @@ TEST_F(LayerSnapshotTest, FastPathSetsChangeFlagToContent) { EXPECT_EQ(getSnapshot(1)->changes, RequestedLayerState::Changes::Content); } +TEST_F(LayerSnapshotTest, GameMode) { + std::vector transactions; + transactions.emplace_back(); + transactions.back().states.push_back({}); + transactions.back().states.front().state.what = layer_state_t::eMetadataChanged; + transactions.back().states.front().state.metadata = LayerMetadata(); + transactions.back().states.front().state.metadata.setInt32(METADATA_GAME_MODE, 42); + transactions.back().states.front().state.surface = mHandles[1]; + transactions.back().states.front().state.layerId = static_cast(1); + mLifecycleManager.applyTransactions(transactions); + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + EXPECT_EQ(static_cast(getSnapshot(1)->gameMode), 42); + EXPECT_EQ(static_cast(getSnapshot(11)->gameMode), 42); +} + +TEST_F(LayerSnapshotTest, NoLayerVoteForParentWithChildVotes) { + // ROOT + // ├── 1 + // │ ├── 11 (frame rate set) + // │ │ └── 111 + // │ ├── 12 + // │ │ ├── 121 + // │ │ └── 122 + // │ │ └── 1221 + // │ └── 13 + // └── 2 + + std::vector transactions; + transactions.emplace_back(); + transactions.back().states.push_back({}); + transactions.back().states.front().state.what = layer_state_t::eFrameRateChanged; + transactions.back().states.front().state.frameRate = 90.0; + transactions.back().states.front().state.frameRateCompatibility = + ANATIVEWINDOW_FRAME_RATE_EXACT; + transactions.back().states.front().state.changeFrameRateStrategy = + ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS; + transactions.back().states.front().state.surface = mHandles[11]; + transactions.back().states.front().state.layerId = static_cast(11); + mLifecycleManager.applyTransactions(transactions); + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + + EXPECT_EQ(getSnapshot(11)->frameRate.rate.getIntValue(), 90); + EXPECT_EQ(getSnapshot(11)->frameRate.type, scheduler::LayerInfo::FrameRateCompatibility::Exact); + EXPECT_EQ(getSnapshot(111)->frameRate.rate.getIntValue(), 90); + EXPECT_EQ(getSnapshot(111)->frameRate.type, + scheduler::LayerInfo::FrameRateCompatibility::Exact); + EXPECT_EQ(getSnapshot(1)->frameRate.rate.getIntValue(), 0); + EXPECT_EQ(getSnapshot(1)->frameRate.type, scheduler::LayerInfo::FrameRateCompatibility::NoVote); +} + } // namespace android::surfaceflinger::frontend -- GitLab From 719f50602d24d9b180762ac8687d4b643f657566 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 7 Dec 2022 12:25:26 -0800 Subject: [PATCH 0763/1310] Remove filterNonTouchAsIs function Since we are no longer storing hover_exit windows and outside windows in touch state, we can remove this function. Bug: 211379801 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: I2083a4adb7c5b55656d1010d87557e99b774f462 --- .../dispatcher/InputDispatcher.cpp | 18 +++++++----------- .../inputflinger/dispatcher/TouchState.cpp | 14 -------------- 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index dc9f02ad5d..a97fda0440 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2441,15 +2441,14 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } // Update dispatching for hover enter and exit. - { - std::vector hoveringWindows = - getHoveringWindowsLocked(oldState, tempTouchState, entry); - for (const TouchedWindow& touchedWindow : hoveringWindows) { - addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, - touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget, - targets); - } + std::vector hoveringWindows = + getHoveringWindowsLocked(oldState, tempTouchState, entry); + for (const TouchedWindow& touchedWindow : hoveringWindows) { + addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, + touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget, + targets); } + // Ensure that we have at least one foreground window or at least one window that cannot be a // foreground target. If we only have windows that are not receiving foreground touches (e.g. we // only have windows getting ACTION_OUTSIDE), then drop the event, because there is no window @@ -2515,9 +2514,6 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } outInjectionResult = InputEventInjectionResult::SUCCEEDED; - // Drop the outside or hover touch windows since we will not care about them - // in the next iteration. - tempTouchState.filterNonAsIsTouchWindows(); // Update final pieces of touch state if the injector had permission. if (switchedDevice) { diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index ad37d025ca..6cfb915e3e 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -112,20 +112,6 @@ void TouchState::removeWindowByToken(const sp& token) { } } -void TouchState::filterNonAsIsTouchWindows() { - for (size_t i = 0; i < windows.size();) { - TouchedWindow& window = windows[i]; - if (window.targetFlags.any(InputTarget::Flags::DISPATCH_AS_IS | - InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER)) { - window.targetFlags.clear(InputTarget::DISPATCH_MASK); - window.targetFlags |= InputTarget::Flags::DISPATCH_AS_IS; - i += 1; - } else { - windows.erase(windows.begin() + i); - } - } -} - void TouchState::cancelPointersForWindowsExcept(const BitSet32 pointerIds, const sp& token) { if (pointerIds.isEmpty()) return; -- GitLab From 42ad319696887c4021d34a96479085f06bd41fc3 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 24 Jan 2023 19:17:59 -0800 Subject: [PATCH 0764/1310] SF: Fix a race between layer creation and apply transaction Between commitCreatedLayers and applyTransactions in the main thread, the client could create a new layer and queue a transaction. This will mean a layer transaction can be applied before the layer can be committed. Fix this by flushing the transactions to be applied before committing any new layers. Test: presubmit Fixes: b/262336014 Change-Id: Id2848af4fbb7afe4e6a20a48f6a8a13f322e1cd7 --- services/surfaceflinger/SurfaceFlinger.cpp | 25 +++++++++++++++------- services/surfaceflinger/SurfaceFlinger.h | 6 +++++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index e8dc7d495e..fbb2dee33b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2250,9 +2250,17 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe bool needsTraversal = false; if (clearTransactionFlags(eTransactionFlushNeeded)) { + // Locking: + // 1. to prevent onHandleDestroyed from being called while the state lock is held, + // we must keep a copy of the transactions (specifically the composer + // states) around outside the scope of the lock. + // 2. Transactions and created layers do not share a lock. To prevent applying + // transactions with layers still in the createdLayer queue, flush the transactions + // before committing the created layers. + std::vector transactions = mTransactionHandler.flushTransactions(); needsTraversal |= commitMirrorDisplays(vsyncId); needsTraversal |= commitCreatedLayers(vsyncId); - needsTraversal |= flushTransactionQueues(vsyncId); + needsTraversal |= applyTransactions(transactions, vsyncId); } const bool shouldCommit = @@ -3948,19 +3956,20 @@ void SurfaceFlinger::addTransactionReadyFilters() { std::bind(&SurfaceFlinger::transactionReadyBufferCheck, this, std::placeholders::_1)); } +// For tests only bool SurfaceFlinger::flushTransactionQueues(VsyncId vsyncId) { - // to prevent onHandleDestroyed from being called while the lock is held, - // we must keep a copy of the transactions (specifically the composer - // states) around outside the scope of the lock std::vector transactions = mTransactionHandler.flushTransactions(); - { - Mutex::Autolock _l(mStateLock); - return applyTransactions(transactions, vsyncId); - } + return applyTransactions(transactions, vsyncId); } bool SurfaceFlinger::applyTransactions(std::vector& transactions, VsyncId vsyncId) { + Mutex::Autolock _l(mStateLock); + return applyTransactionsLocked(transactions, vsyncId); +} + +bool SurfaceFlinger::applyTransactionsLocked(std::vector& transactions, + VsyncId vsyncId) { bool needsTraversal = false; // Now apply all transactions. for (auto& transaction : transactions) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 5457be81a5..fa2a6f4708 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -725,7 +725,11 @@ private: int originPid, int originUid, uint64_t transactionId) REQUIRES(mStateLock); // Flush pending transactions that were presented after desiredPresentTime. + // For test only bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext); + + bool applyTransactions(std::vector&, VsyncId) REQUIRES(kMainThreadContext); + // Returns true if there is at least one transaction that needs to be flushed bool transactionFlushNeeded(); void addTransactionReadyFilters(); @@ -756,7 +760,7 @@ private: static LatchUnsignaledConfig getLatchUnsignaledConfig(); bool shouldLatchUnsignaled(const sp& layer, const layer_state_t&, size_t numStates, bool firstTransaction) const; - bool applyTransactions(std::vector& transactions, VsyncId) + bool applyTransactionsLocked(std::vector& transactions, VsyncId) REQUIRES(mStateLock); uint32_t setDisplayStateLocked(const DisplayState& s) REQUIRES(mStateLock); uint32_t addInputWindowCommands(const InputWindowCommands& inputWindowCommands) -- GitLab From a692783e596bcdba847fcf65cc53fccd978d975d Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Wed, 25 Jan 2023 20:06:17 +0000 Subject: [PATCH 0765/1310] Revert^2 "Enable new touchpad stack by default" This reverts commit e60fa698a2d69fcdcd4fb71eb1a0be0bef000507. Reason for revert: the issue behind the original revert (caused by the previous CL in the chain) has now been resolved. Bug: 251196347, 264996455 Test: presubmit Change-Id: I4471d1e6bdd910b06b50a24d7999e2228983b4c5 --- services/inputflinger/reader/InputDevice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 90d5d01c16..7031c8ba74 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -209,7 +209,7 @@ void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) { // Touchscreens and touchpad devices. static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY = - sysprop::InputProperties::enable_touchpad_gestures_library().value_or(false); + sysprop::InputProperties::enable_touchpad_gestures_library().value_or(true); // TODO(b/246587538): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) and DualSense // (ce6) touchpads, or at least load this setting from the IDC file. const InputDeviceIdentifier& identifier = getDeviceInfo().getIdentifier(); -- GitLab From 6879659d589a17d362f91bba4bec5737504a75e7 Mon Sep 17 00:00:00 2001 From: John Reck Date: Wed, 25 Jan 2023 13:47:12 -0500 Subject: [PATCH 0766/1310] Add extended brightness range impl Bug: 241001465 Test: manual currently, flagged off Change-Id: I707281b9acaf6ea218f1d9ce888fc7cdbf1cf7c9 --- libs/gui/LayerState.cpp | 14 ++++++ libs/gui/SurfaceComposerClient.cpp | 15 ++++++ libs/gui/include/gui/LayerState.h | 8 +++- libs/gui/include/gui/SurfaceComposerClient.h | 2 + .../LayerFECompositionState.h | 3 ++ .../src/LayerFECompositionState.cpp | 4 ++ .../CompositionEngine/src/OutputLayer.cpp | 15 ++++-- .../FrontEnd/LayerSnapshotBuilder.cpp | 2 + .../FrontEnd/RequestedLayerState.cpp | 2 + .../surfaceflinger/HdrLayerInfoReporter.h | 25 ++++++++++ services/surfaceflinger/Layer.cpp | 27 ++++++++++- services/surfaceflinger/Layer.h | 8 +++- services/surfaceflinger/SurfaceFlinger.cpp | 48 ++++++++++++------- services/surfaceflinger/SurfaceFlinger.h | 8 ++-- 14 files changed, 153 insertions(+), 28 deletions(-) diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 43acb16299..8372363185 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -187,6 +187,8 @@ status_t layer_state_t::write(Parcel& output) const } SAFE_PARCEL(output.writeParcelable, trustedPresentationThresholds); SAFE_PARCEL(output.writeParcelable, trustedPresentationListener); + SAFE_PARCEL(output.writeFloat, currentSdrHdrRatio); + SAFE_PARCEL(output.writeFloat, desiredSdrHdrRatio); return NO_ERROR; } @@ -321,6 +323,11 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(input.readParcelable, &trustedPresentationThresholds); SAFE_PARCEL(input.readParcelable, &trustedPresentationListener); + SAFE_PARCEL(input.readFloat, &tmpFloat); + currentSdrHdrRatio = tmpFloat; + SAFE_PARCEL(input.readFloat, &tmpFloat); + desiredSdrHdrRatio = tmpFloat; + return NO_ERROR; } @@ -568,6 +575,11 @@ void layer_state_t::merge(const layer_state_t& other) { what |= eDataspaceChanged; dataspace = other.dataspace; } + if (other.what & eExtendedRangeBrightnessChanged) { + what |= eExtendedRangeBrightnessChanged; + desiredSdrHdrRatio = other.desiredSdrHdrRatio; + currentSdrHdrRatio = other.currentSdrHdrRatio; + } if (other.what & eHdrMetadataChanged) { what |= eHdrMetadataChanged; hdrMetadata = other.hdrMetadata; @@ -714,6 +726,8 @@ uint64_t layer_state_t::diff(const layer_state_t& other) const { CHECK_DIFF(diff, eCropChanged, other, crop); if (other.what & eBufferChanged) diff |= eBufferChanged; CHECK_DIFF(diff, eDataspaceChanged, other, dataspace); + CHECK_DIFF2(diff, eExtendedRangeBrightnessChanged, other, currentSdrHdrRatio, + desiredSdrHdrRatio); CHECK_DIFF(diff, eHdrMetadataChanged, other, hdrMetadata); if (other.what & eSurfaceDamageRegionChanged && (!surfaceDamageRegion.hasSameRects(other.surfaceDamageRegion))) { diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 5bdeae8a1f..cf9828b2f8 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -1673,6 +1673,21 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDatas return *this; } +SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setExtendedRangeBrightness( + const sp& sc, float currentBufferRatio, float desiredRatio) { + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + s->what |= layer_state_t::eExtendedRangeBrightnessChanged; + s->currentSdrHdrRatio = currentBufferRatio; + s->desiredSdrHdrRatio = desiredRatio; + + registerSurfaceControlForCallback(sc); + return *this; +} + SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setHdrMetadata( const sp& sc, const HdrMetadata& hdrMetadata) { layer_state_t* s = getLayerState(sc); diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 70c9daf42b..b8bee72cec 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -209,7 +209,8 @@ struct layer_state_t { eAutoRefreshChanged = 0x1000'00000000, eStretchChanged = 0x2000'00000000, eTrustedOverlayChanged = 0x4000'00000000, - eDropInputModeChanged = 0x8000'00000000 + eDropInputModeChanged = 0x8000'00000000, + eExtendedRangeBrightnessChanged = 0x10000'00000000 }; layer_state_t(); @@ -240,7 +241,8 @@ struct layer_state_t { layer_state_t::eBufferTransformChanged | layer_state_t::eDataspaceChanged | layer_state_t::eSidebandStreamChanged | layer_state_t::eSurfaceDamageRegionChanged | layer_state_t::eTransformToDisplayInverseChanged | - layer_state_t::eTransparentRegionChanged; + layer_state_t::eTransparentRegionChanged | + layer_state_t::eExtendedRangeBrightnessChanged; // Content updates. static constexpr uint64_t CONTENT_CHANGES = layer_state_t::BUFFER_CHANGES | @@ -385,6 +387,8 @@ struct layer_state_t { gui::DropInputMode dropInputMode; bool dimmingEnabled; + float currentSdrHdrRatio = 1.f; + float desiredSdrHdrRatio = 1.f; TrustedPresentationThresholds trustedPresentationThresholds; TrustedPresentationListener trustedPresentationListener; diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 439e06049b..c5f59c8e3d 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -559,6 +559,8 @@ public: Transaction& setBufferHasBarrier(const sp& sc, uint64_t barrierFrameNumber); Transaction& setDataspace(const sp& sc, ui::Dataspace dataspace); + Transaction& setExtendedRangeBrightness(const sp& sc, + float currentBufferRatio, float desiredRatio); Transaction& setHdrMetadata(const sp& sc, const HdrMetadata& hdrMetadata); Transaction& setSurfaceDamageRegion(const sp& sc, const Region& surfaceDamageRegion); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h index ad98e93232..5bb249719e 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h @@ -209,6 +209,9 @@ struct LayerFECompositionState { // The dimming flag bool dimmingEnabled{true}; + float currentSdrHdrRatio = 1.f; + float desiredSdrHdrRatio = 1.f; + virtual ~LayerFECompositionState(); // Debugging diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp index a405c4df88..531b65963d 100644 --- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp +++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp @@ -121,6 +121,10 @@ void LayerFECompositionState::dump(std::string& out) const { dumpVal(out, "dataspace", toString(dataspace), dataspace); dumpVal(out, "hdr metadata types", hdrMetadata.validTypes); dumpVal(out, "dimming enabled", dimmingEnabled); + if (currentSdrHdrRatio > 1.01f || desiredSdrHdrRatio > 1.01f) { + dumpVal(out, "current sdr/hdr ratio", currentSdrHdrRatio); + dumpVal(out, "desired sdr/hdr ratio", desiredSdrHdrRatio); + } dumpVal(out, "colorTransform", colorTransform); out.append("\n"); diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp index 60a2c83194..6b69ce7941 100644 --- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp +++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp @@ -340,10 +340,17 @@ void OutputLayer::updateCompositionState( state.dimmingRatio = 1.f; state.whitePointNits = getOutput().getState().displayBrightnessNits; } else { - state.dimmingRatio = std::clamp(getOutput().getState().sdrWhitePointNits / - getOutput().getState().displayBrightnessNits, - 0.f, 1.f); - state.whitePointNits = getOutput().getState().sdrWhitePointNits; + float layerBrightnessNits = getOutput().getState().sdrWhitePointNits; + // RANGE_EXTENDED can "self-promote" to HDR, but is still rendered for a particular + // range that we may need to re-adjust to the current display conditions + if ((state.dataspace & HAL_DATASPACE_RANGE_MASK) == HAL_DATASPACE_RANGE_EXTENDED && + layerFEState->currentSdrHdrRatio > 1.01f) { + layerBrightnessNits *= layerFEState->currentSdrHdrRatio; + } + state.dimmingRatio = + std::clamp(layerBrightnessNits / getOutput().getState().displayBrightnessNits, 0.f, + 1.f); + state.whitePointNits = layerBrightnessNits; } // These are evaluated every frame as they can potentially change at any diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index 40dffb9706..cc265916f1 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -667,6 +667,8 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a snapshot.sidebandStream = requested.sidebandStream; snapshot.transparentRegionHint = requested.transparentRegion; snapshot.color.rgb = requested.getColor().rgb; + snapshot.currentSdrHdrRatio = requested.currentSdrHdrRatio; + snapshot.desiredSdrHdrRatio = requested.desiredSdrHdrRatio; } if (snapshot.isHiddenByPolicyFromParent && !newSnapshot) { diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index 39bf07abd2..b7fa4f0aa2 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -96,6 +96,8 @@ RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args) layerStack = ui::DEFAULT_LAYER_STACK; transformToDisplayInverse = false; dataspace = ui::Dataspace::UNKNOWN; + desiredSdrHdrRatio = 1.f; + currentSdrHdrRatio = 1.f; dataspaceRequested = false; hdrMetadata.validTypes = 0; surfaceDamageRegion = Region::INVALID_REGION; diff --git a/services/surfaceflinger/HdrLayerInfoReporter.h b/services/surfaceflinger/HdrLayerInfoReporter.h index 4ada2b6372..9b70c164de 100644 --- a/services/surfaceflinger/HdrLayerInfoReporter.h +++ b/services/surfaceflinger/HdrLayerInfoReporter.h @@ -33,6 +33,17 @@ public: int32_t maxW = 0; int32_t maxH = 0; int32_t flags = 0; + // Counter-intuitively a value of "1" means "as much as you can give me" due to "1" being + // the default value for all layers, so any HDR layer with a value of 1.f means no + // reduced maximum has been requested + // TODO: Should the max desired ratio have a better meaning for HLG/PQ so this can be + // eliminated? If we assume an SDR white point of even just 100 nits for those content + // then HLG could have a meaningful max ratio of 10.f and PQ of 100.f instead of needing + // to treat 1.f as "uncapped" + // With peak display brightnesses exceeding 1,000 nits currently, HLG's request could + // actually be satisfied in some ambient conditions such that limiting that max for that + // content in theory makes sense + float maxDesiredSdrHdrRatio = 0.f; bool operator==(const HdrLayerInfo& other) const { return numberOfHdrLayers == other.numberOfHdrLayers && maxW == other.maxW && @@ -40,6 +51,20 @@ public: } bool operator!=(const HdrLayerInfo& other) const { return !(*this == other); } + + void mergeDesiredRatio(float update) { + if (maxDesiredSdrHdrRatio == 0.f) { + // If nothing is set, take the incoming value + maxDesiredSdrHdrRatio = update; + } else if (update == 1.f) { + // If the request is to "go to max", then take it regardless + maxDesiredSdrHdrRatio = 1.f; + } else if (maxDesiredSdrHdrRatio != 1.f) { + // If we're not currently asked to "go to max", then take the max + // of the incoming requests + maxDesiredSdrHdrRatio = std::max(maxDesiredSdrHdrRatio, update); + } + } }; HdrLayerInfoReporter() = default; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 539f2fe353..953cdff237 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -622,6 +622,8 @@ void Layer::preparePerFrameCompositionState() { snapshot->surfaceDamage = surfaceDamageRegion; snapshot->hasProtectedContent = isProtected(); snapshot->dimmingEnabled = isDimmingEnabled(); + snapshot->currentSdrHdrRatio = getCurrentSdrHdrRatio(); + snapshot->desiredSdrHdrRatio = getDesiredSdrHdrRatio(); const bool usesRoundedCorners = hasRoundedCorners(); @@ -3041,6 +3043,17 @@ bool Layer::setDataspace(ui::Dataspace dataspace) { return true; } +bool Layer::setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio) { + if (mDrawingState.currentSdrHdrRatio == currentBufferRatio && + mDrawingState.desiredSdrHdrRatio == desiredRatio) + return false; + mDrawingState.currentSdrHdrRatio = currentBufferRatio; + mDrawingState.desiredSdrHdrRatio = desiredRatio; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + bool Layer::setHdrMetadata(const HdrMetadata& hdrMetadata) { if (mDrawingState.hdrMetadata == hdrMetadata) return false; mDrawingState.hdrMetadata = hdrMetadata; @@ -3272,7 +3285,11 @@ void Layer::gatherBufferInfo() { auto lastDataspace = mBufferInfo.mDataspace; mBufferInfo.mDataspace = translateDataspace(mDrawingState.dataspace); if (lastDataspace != mBufferInfo.mDataspace) { - mFlinger->mSomeDataspaceChanged = true; + mFlinger->mHdrLayerInfoChanged = true; + } + if (mBufferInfo.mDesiredSdrHdrRatio != mDrawingState.desiredSdrHdrRatio) { + mBufferInfo.mDesiredSdrHdrRatio = mDrawingState.desiredSdrHdrRatio; + mFlinger->mHdrLayerInfoChanged = true; } mBufferInfo.mCrop = computeBufferCrop(mDrawingState); mBufferInfo.mScaleMode = NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW; @@ -3563,6 +3580,14 @@ bool Layer::simpleBufferUpdate(const layer_state_t& s) const { } } + if (s.what & layer_state_t::eExtendedRangeBrightnessChanged) { + if (mDrawingState.currentSdrHdrRatio != s.currentSdrHdrRatio || + mDrawingState.desiredSdrHdrRatio != s.desiredSdrHdrRatio) { + ALOGV("%s: false [eDimmingEnabledChanged changed]", __func__); + return false; + } + } + ALOGV("%s: true", __func__); return true; } diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 7631f5d963..3384e4af2d 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -223,6 +223,8 @@ public: gui::DropInputMode dropInputMode; bool autoRefresh = false; bool dimmingEnabled = true; + float currentSdrHdrRatio = 1.f; + float desiredSdrHdrRatio = 1.f; }; explicit Layer(const LayerCreationArgs& args); @@ -289,7 +291,9 @@ public: virtual mat4 getColorTransform() const; virtual bool hasColorTransform() const; virtual bool isColorSpaceAgnostic() const { return mDrawingState.colorSpaceAgnostic; } - virtual bool isDimmingEnabled() const { return getDrawingState().dimmingEnabled; }; + virtual bool isDimmingEnabled() const { return getDrawingState().dimmingEnabled; } + float getDesiredSdrHdrRatio() const { return getDrawingState().desiredSdrHdrRatio; } + float getCurrentSdrHdrRatio() const { return getDrawingState().currentSdrHdrRatio; } bool setTransform(uint32_t /*transform*/); bool setTransformToDisplayInverse(bool /*transformToDisplayInverse*/); @@ -298,6 +302,7 @@ public: nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/, std::optional /* dequeueTime */, const FrameTimelineInfo& /*info*/); bool setDataspace(ui::Dataspace /*dataspace*/); + bool setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio); bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/); bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/); bool setApi(int32_t /*api*/); @@ -499,6 +504,7 @@ public: uint64_t mFrameNumber; bool mFrameLatencyNeeded{false}; + float mDesiredSdrHdrRatio = 1.f; }; BufferInfo mBufferInfo; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index cb3c94f5b0..546aceb74f 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2476,7 +2476,7 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) mLayerTracing.notify(mVisibleRegionsDirty, frameTime.ns(), vsyncId.value); } - mVisibleRegionsWereDirtyThisFrame = mVisibleRegionsDirty; // Cache value for use in post-comp + if (mVisibleRegionsDirty) mHdrLayerInfoChanged = true; mVisibleRegionsDirty = false; if (mCompositionEngine->needsAnotherUpdate()) { @@ -2504,21 +2504,32 @@ void SurfaceFlinger::updateLayerGeometry() { } bool SurfaceFlinger::isHdrLayer(Layer* layer) const { - // Treat all layers as non-HDR if: - // 1. They do not have a valid HDR dataspace. Currently we treat those as PQ or HLG. and - // 2. The layer is allowed to be dimmed. WindowManager may disable dimming in order to - // keep animations invoking SDR screenshots of HDR layers seamless. Treat such tagged - // layers as HDR so that DisplayManagerService does not try to change the screen brightness - if (!isHdrDataspace(layer->getDataSpace()) && layer->isDimmingEnabled()) { - return false; - } + // Even though the camera layer may be using an HDR transfer function or otherwise be "HDR" + // the device may need to avoid boosting the brightness as a result of these layers to + // reduce power consumption during camera recording if (mIgnoreHdrCameraLayers) { auto buffer = layer->getBuffer(); if (buffer && (buffer->getUsage() & GRALLOC_USAGE_HW_CAMERA_WRITE) != 0) { return false; } } - return true; + if (isHdrDataspace(layer->getDataSpace())) { + return true; + } + // If the layer is not allowed to be dimmed, treat it as HDR. WindowManager may disable + // dimming in order to keep animations invoking SDR screenshots of HDR layers seamless. + // Treat such tagged layers as HDR so that DisplayManagerService does not try to change + // the screen brightness + if (!layer->isDimmingEnabled()) { + return true; + } + // RANGE_EXTENDED layers may identify themselves as being "HDR" via a desired sdr/hdr ratio + if ((layer->getDataSpace() & (int32_t)Dataspace::RANGE_MASK) == + (int32_t)Dataspace::RANGE_EXTENDED && + layer->getDesiredSdrHdrRatio() > 1.01f) { + return true; + } + return false; } ui::Rotation SurfaceFlinger::getPhysicalDisplayOrientation(DisplayId displayId, @@ -2637,7 +2648,7 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { mAddingHDRLayerInfoListener = false; } - if (haveNewListeners || mSomeDataspaceChanged || mVisibleRegionsWereDirtyThisFrame) { + if (haveNewListeners || mHdrLayerInfoChanged) { for (auto& [compositionDisplay, listener] : hdrInfoListeners) { HdrLayerInfoReporter::HdrLayerInfo info; int32_t maxArea = 0; @@ -2649,6 +2660,7 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { const auto* outputLayer = compositionDisplay->getOutputLayerForLayer(layerFe); if (outputLayer) { + info.mergeDesiredRatio(layer->getDesiredSdrHdrRatio()); info.numberOfHdrLayers++; const auto displayFrame = outputLayer->getState().displayFrame; const int32_t area = displayFrame.width() * displayFrame.height(); @@ -2665,8 +2677,7 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { } } - mSomeDataspaceChanged = false; - mVisibleRegionsWereDirtyThisFrame = false; + mHdrLayerInfoChanged = false; mTransactionCallbackInvoker.addPresentFence(std::move(presentFence)); mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */); @@ -4492,9 +4503,6 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime if (what & layer_state_t::eDataspaceChanged) { if (layer->setDataspace(s.dataspace)) flags |= eTraversalNeeded; } - if (what & layer_state_t::eHdrMetadataChanged) { - if (layer->setHdrMetadata(s.hdrMetadata)) flags |= eTraversalNeeded; - } if (what & layer_state_t::eSurfaceDamageRegionChanged) { if (layer->setSurfaceDamageRegion(s.surfaceDamageRegion)) flags |= eTraversalNeeded; } @@ -4568,6 +4576,14 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime if (what & layer_state_t::eDimmingEnabledChanged) { if (layer->setDimmingEnabled(s.dimmingEnabled)) flags |= eTraversalNeeded; } + if (what & layer_state_t::eExtendedRangeBrightnessChanged) { + if (layer->setExtendedRangeBrightness(s.currentSdrHdrRatio, s.desiredSdrHdrRatio)) { + flags |= eTraversalNeeded; + } + } + if (what & layer_state_t::eHdrMetadataChanged) { + if (layer->setHdrMetadata(s.hdrMetadata)) flags |= eTraversalNeeded; + } if (what & layer_state_t::eTrustedOverlayChanged) { if (layer->setTrustedOverlay(s.isTrustedOverlay)) { flags |= eTraversalNeeded; diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 5e4015e728..c107d02c03 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1140,9 +1140,8 @@ private: State mDrawingState{LayerVector::StateSet::Drawing}; bool mVisibleRegionsDirty = false; - // VisibleRegions dirty is already cleared by postComp, but we need to track it to prevent - // extra work in the HDR layer info listener. - bool mVisibleRegionsWereDirtyThisFrame = false; + bool mHdrLayerInfoChanged = false; + // Used to ensure we omit a callback when HDR layer info listener is newly added but the // scene hasn't changed bool mAddingHDRLayerInfoListener = false; @@ -1153,7 +1152,6 @@ private: // TODO: Also move visibleRegions over to a boolean system. bool mUpdateInputInfo = false; bool mSomeChildrenChanged; - bool mSomeDataspaceChanged = false; bool mForceTransactionDisplayChange = false; // Set if LayerMetadata has changed since the last LayerMetadata snapshot. @@ -1342,6 +1340,7 @@ private: std::unordered_map> mHdrLayerInfoListeners GUARDED_BY(mStateLock); + mutable std::mutex mCreatedLayersLock; struct LayerCreatedState { LayerCreatedState(const wp& layer, const wp parent, bool addToRoot) @@ -1497,6 +1496,7 @@ public: binder::Status removeHdrLayerInfoListener( const sp& displayToken, const sp& listener) override; + binder::Status notifyPowerBoost(int boostId) override; binder::Status setGlobalShadowSettings(const gui::Color& ambientColor, const gui::Color& spotColor, float lightPosY, -- GitLab From c1041d4f1476a5663702f4a8b9312cfd224a8c10 Mon Sep 17 00:00:00 2001 From: Peiyong Lin Date: Thu, 26 Jan 2023 00:51:33 +0000 Subject: [PATCH 0767/1310] Use pid_t instead of not int32_t. Bug: b/266595015 Test: atest PerformanceHintManagerTest Change-Id: I33755bb30cceb9953db293092e417a1a14288226 --- include/android/performance_hint.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h index 4a5bd5ecfb..b494f897d5 100644 --- a/include/android/performance_hint.h +++ b/include/android/performance_hint.h @@ -37,6 +37,7 @@ #include #include +#include __BEGIN_DECLS @@ -173,7 +174,7 @@ void APerformanceHint_closeSession( */ int APerformanceHint_setThreads( APerformanceHintSession* session, - const int32_t* threadIds, + const pid_t* threadIds, size_t size) __INTRODUCED_IN(__ANDROID_API_U__); __END_DECLS -- GitLab From afd1fda06f1aa13788477557b35912c1e1abf862 Mon Sep 17 00:00:00 2001 From: Kriti Dang Date: Tue, 17 Jan 2023 13:33:15 +0100 Subject: [PATCH 0768/1310] Add getHdrOutputConversionSupport to SurfaceComposerClient. Bug: 251165759 Test: atest HdrConversionTest Change-Id: I284158f6a630f6c337d36347803bc95536cb3ea0 --- libs/gui/SurfaceComposerClient.cpp | 6 ++++++ libs/gui/include/gui/SurfaceComposerClient.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 5bdeae8a1f..47339fe450 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2607,6 +2607,12 @@ status_t SurfaceComposerClient::setHdrConversionStrategy( return statusTFromBinderStatus(status); } +status_t SurfaceComposerClient::getHdrOutputConversionSupport(bool* isSupported) { + binder::Status status = + ComposerServiceAIDL::getComposerService()->getHdrOutputConversionSupport(isSupported); + return statusTFromBinderStatus(status); +} + status_t SurfaceComposerClient::setOverrideFrameRate(uid_t uid, float frameRate) { binder::Status status = ComposerServiceAIDL::getComposerService()->setOverrideFrameRate(uid, frameRate); diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 439e06049b..27353fbc9f 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -194,6 +194,8 @@ public: static status_t getHdrConversionCapabilities(std::vector*); // Sets the HDR conversion strategy for the device static status_t setHdrConversionStrategy(gui::HdrConversionStrategy hdrConversionStrategy); + // Returns whether HDR conversion is supported by the device. + static status_t getHdrOutputConversionSupport(bool* isSupported); // Sets the frame rate of a particular app (uid). This is currently called // by GameManager. -- GitLab From 86f5df6f4c1a840f2b6e5c9ca8a42c747adf1b4f Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Thu, 26 Jan 2023 11:40:03 +0000 Subject: [PATCH 0769/1310] Copy InputDeviceIdentifier instead of taking a reference This line appears to be causing hwasan failures in the lab (see bug). While a const& should extend the lifetime of the thing it's referring to, that doesn't seem to be happening in this case, possibly because getDeviceIdentifier isn't a simple getter. InputDeviceIdentifier isn't a particularly large struct, and we intend to replace this code with an IDC property anyway, so just take a copy of it. Bug: 266534706, 251196347 Test: atest inputflinger_tests Test: connect Sony gamepads, check old stack is used Test: connect Apple Magic Trackpad 2, check new stack is used Change-Id: I98f09f8226bc5e42082b07f9795eccc6de890879 --- services/inputflinger/reader/InputDevice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index bd41fa5361..aba33bcfac 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -212,7 +212,7 @@ void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) { sysprop::InputProperties::enable_touchpad_gestures_library().value_or(false); // TODO(b/246587538): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) and DualSense // (ce6) touchpads, or at least load this setting from the IDC file. - const InputDeviceIdentifier& identifier = contextPtr->getDeviceIdentifier(); + const InputDeviceIdentifier identifier = contextPtr->getDeviceIdentifier(); const bool isSonyGamepadTouchpad = identifier.vendor == 0x054c && (identifier.product == 0x05c4 || identifier.product == 0x09cc || identifier.product == 0x0ce6); -- GitLab From a44686c4d1f1102b631d23cd2840cbc7aff8e501 Mon Sep 17 00:00:00 2001 From: Gavin Corkery Date: Wed, 23 Nov 2022 18:16:51 +0000 Subject: [PATCH 0770/1310] Add retrieveBugreport dumpstate implementation Adds the native logic necessary for handling the DEFER_CONSENT flag when calling startBugreport, and handling the retrieveBugreport API. When calling startBugreport with the DEFER_CONSENT flag set, the consent flow will not be invoked. Instead, the bugreport files will be stored in the bugreport internal directories and will only be passed to the caller's file descriptors when the same caller invokes retrieveBugreport at a later time. Test: atest CtsRootBugreportTestCases Test: atest dumpstate_test Bug: 245328405 Change-Id: I3a46482e173c6084ebda96f08ec60a31953d5f94 --- cmds/dumpstate/DumpstateService.cpp | 42 +++++++++++ cmds/dumpstate/DumpstateService.h | 7 ++ .../binder/android/os/IDumpstate.aidl | 23 +++++- .../binder/android/os/IDumpstateListener.aidl | 7 +- cmds/dumpstate/dumpstate.cpp | 71 ++++++++++++++++--- cmds/dumpstate/dumpstate.h | 17 ++++- cmds/dumpstate/tests/dumpstate_smoke_test.cpp | 2 +- cmds/dumpstate/tests/dumpstate_test.cpp | 16 ++++- 8 files changed, 172 insertions(+), 13 deletions(-) diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp index 42e9e0f1a7..a7bc018ff6 100644 --- a/cmds/dumpstate/DumpstateService.cpp +++ b/cmds/dumpstate/DumpstateService.cpp @@ -58,6 +58,13 @@ static binder::Status exception(uint32_t code, const std::string& msg, exit(0); } +[[noreturn]] static void* dumpstate_thread_retrieve(void* data) { + std::unique_ptr ds_info(static_cast(data)); + ds_info->ds->Retrieve(ds_info->calling_uid, ds_info->calling_package); + MYLOGD("Finished retrieving a bugreport. Exiting.\n"); + exit(0); +} + [[noreturn]] static void signalErrorAndExit(sp listener, int error_code) { listener->onError(error_code); exit(0); @@ -192,6 +199,41 @@ binder::Status DumpstateService::cancelBugreport(int32_t calling_uid, return binder::Status::ok(); } +binder::Status DumpstateService::retrieveBugreport( + int32_t calling_uid, const std::string& calling_package, + android::base::unique_fd bugreport_fd, + const std::string& bugreport_file, + const sp& listener) { + + ds_ = &(Dumpstate::GetInstance()); + DumpstateInfo* ds_info = new DumpstateInfo(); + ds_info->ds = ds_; + ds_info->calling_uid = calling_uid; + ds_info->calling_package = calling_package; + ds_->listener_ = listener; + std::unique_ptr options = std::make_unique(); + // Use a /dev/null FD when initializing options since none is provided. + android::base::unique_fd devnull_fd( + TEMP_FAILURE_RETRY(open("/dev/null", O_WRONLY | O_CLOEXEC))); + + options->Initialize(Dumpstate::BugreportMode::BUGREPORT_DEFAULT, + 0, bugreport_fd, devnull_fd, false); + + if (bugreport_fd.get() == -1) { + MYLOGE("Invalid filedescriptor"); + signalErrorAndExit(listener, IDumpstateListener::BUGREPORT_ERROR_INVALID_INPUT); + } + ds_->SetOptions(std::move(options)); + ds_->path_ = bugreport_file; + pthread_t thread; + status_t err = pthread_create(&thread, nullptr, dumpstate_thread_retrieve, ds_info); + if (err != 0) { + MYLOGE("Could not create a thread"); + signalErrorAndExit(listener, IDumpstateListener::BUGREPORT_ERROR_RUNTIME_ERROR); + } + return binder::Status::ok(); +} + status_t DumpstateService::dump(int fd, const Vector&) { std::lock_guard lock(lock_); if (ds_ == nullptr) { diff --git a/cmds/dumpstate/DumpstateService.h b/cmds/dumpstate/DumpstateService.h index 997999c805..dd7331932c 100644 --- a/cmds/dumpstate/DumpstateService.h +++ b/cmds/dumpstate/DumpstateService.h @@ -46,6 +46,13 @@ class DumpstateService : public BinderService, public BnDumpst int bugreport_flags, const sp& listener, bool is_screenshot_requested) override; + binder::Status retrieveBugreport(int32_t calling_uid, + const std::string& calling_package, + android::base::unique_fd bugreport_fd, + const std::string& bugreport_file, + const sp& listener) + override; + binder::Status cancelBugreport(int32_t calling_uid, const std::string& calling_package) override; diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl index d4323af3cf..0dc8f5ad64 100644 --- a/cmds/dumpstate/binder/android/os/IDumpstate.aidl +++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl @@ -50,7 +50,10 @@ interface IDumpstate { const int BUGREPORT_MODE_DEFAULT = 6; // Use pre-dumped data. - const int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 1; + const int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 0x1; + + // Defer user consent. + const int BUGREPORT_FLAG_DEFER_CONSENT = 0x2; /** * Speculatively pre-dumps UI data for a bugreport request that might come later. @@ -100,4 +103,22 @@ interface IDumpstate { * @param callingPackage package of the original application that requested the cancellation. */ void cancelBugreport(int callingUid, @utf8InCpp String callingPackage); + + /** + * Retrieves a previously generated bugreport. + * + *

The caller must have previously generated a bugreport using + * {@link #startBugreport} with the {@link BUGREPORT_FLAG_DEFER_CONSENT} + * flag set. + * + * @param callingUid UID of the original application that requested the report. + * @param callingPackage package of the original application that requested the report. + * @param bugreportFd the file to which the zipped bugreport should be written + * @param bugreportFile the path of the bugreport file + * @param listener callback for updates; optional + */ + void retrieveBugreport(int callingUid, @utf8InCpp String callingPackage, + FileDescriptor bugreportFd, + @utf8InCpp String bugreportFile, + IDumpstateListener listener); } diff --git a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl index 50c1624dc2..e8891d3236 100644 --- a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl +++ b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl @@ -50,6 +50,9 @@ interface IDumpstateListener { /* There is currently a bugreport running. The caller should try again later. */ const int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS = 5; + /* There is no bugreport to retrieve for the given caller. */ + const int BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE = 6; + /** * Called on an error condition with one of the error codes listed above. */ @@ -57,8 +60,10 @@ interface IDumpstateListener { /** * Called when taking bugreport finishes successfully. + * + * @param bugreportFile The location of the bugreport file */ - oneway void onFinished(); + oneway void onFinished(@utf8InCpp String bugreportFile); /** * Called when screenshot is taken. diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp index d77b45800d..83ae2d2b0a 100644 --- a/cmds/dumpstate/dumpstate.cpp +++ b/cmds/dumpstate/dumpstate.cpp @@ -2825,6 +2825,7 @@ void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode, const android::base::unique_fd& screenshot_fd_in, bool is_screenshot_requested) { this->use_predumped_ui_data = bugreport_flags & BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA; + this->is_consent_deferred = bugreport_flags & BugreportFlag::BUGREPORT_FLAG_DEFER_CONSENT; // Duplicate the fds because the passed in fds don't outlive the binder transaction. bugreport_fd.reset(fcntl(bugreport_fd_in.get(), F_DUPFD_CLOEXEC, 0)); screenshot_fd.reset(fcntl(screenshot_fd_in.get(), F_DUPFD_CLOEXEC, 0)); @@ -2907,10 +2908,64 @@ void Dumpstate::Initialize() { Dumpstate::RunStatus Dumpstate::Run(int32_t calling_uid, const std::string& calling_package) { Dumpstate::RunStatus status = RunInternal(calling_uid, calling_package); - if (listener_ != nullptr) { + HandleRunStatus(status); + return status; +} + +Dumpstate::RunStatus Dumpstate::Retrieve(int32_t calling_uid, const std::string& calling_package) { + Dumpstate::RunStatus status = RetrieveInternal(calling_uid, calling_package); + HandleRunStatus(status); + return status; +} + +Dumpstate::RunStatus Dumpstate::RetrieveInternal(int32_t calling_uid, + const std::string& calling_package) { + consent_callback_ = new ConsentCallback(); + const String16 incidentcompanion("incidentcompanion"); + sp ics( + defaultServiceManager()->checkService(incidentcompanion)); + android::String16 package(calling_package.c_str()); + if (ics != nullptr) { + MYLOGD("Checking user consent via incidentcompanion service\n"); + android::interface_cast(ics)->authorizeReport( + calling_uid, package, String16(), String16(), + 0x1 /* FLAG_CONFIRMATION_DIALOG */, consent_callback_.get()); + } else { + MYLOGD( + "Unable to check user consent; incidentcompanion service unavailable\n"); + return RunStatus::USER_CONSENT_TIMED_OUT; + } + UserConsentResult consent_result = consent_callback_->getResult(); + int timeout_ms = 30 * 1000; + while (consent_result == UserConsentResult::UNAVAILABLE && + consent_callback_->getElapsedTimeMs() < timeout_ms) { + sleep(1); + consent_result = consent_callback_->getResult(); + } + if (consent_result == UserConsentResult::DENIED) { + return RunStatus::USER_CONSENT_DENIED; + } + if (consent_result == UserConsentResult::UNAVAILABLE) { + MYLOGD("Canceling user consent request via incidentcompanion service\n"); + android::interface_cast(ics)->cancelAuthorization( + consent_callback_.get()); + return RunStatus::USER_CONSENT_TIMED_OUT; + } + + bool copy_succeeded = + android::os::CopyFileToFd(path_, options_->bugreport_fd.get()); + if (copy_succeeded) { + android::os::UnlinkAndLogOnError(path_); + } + return copy_succeeded ? Dumpstate::RunStatus::OK + : Dumpstate::RunStatus::ERROR; +} + +void Dumpstate::HandleRunStatus(Dumpstate::RunStatus status) { + if (listener_ != nullptr) { switch (status) { case Dumpstate::RunStatus::OK: - listener_->onFinished(); + listener_->onFinished(path_.c_str()); break; case Dumpstate::RunStatus::HELP: break; @@ -2928,9 +2983,7 @@ Dumpstate::RunStatus Dumpstate::Run(int32_t calling_uid, const std::string& call break; } } - return status; } - void Dumpstate::Cancel() { CleanupTmpFiles(); android::os::UnlinkAndLogOnError(log_path_); @@ -3181,7 +3234,7 @@ Dumpstate::RunStatus Dumpstate::RunInternal(int32_t calling_uid, // Share the final file with the caller if the user has consented or Shell is the caller. Dumpstate::RunStatus status = Dumpstate::RunStatus::OK; - if (CalledByApi()) { + if (CalledByApi() && !options_->is_consent_deferred) { status = CopyBugreportIfUserConsented(calling_uid); if (status != Dumpstate::RunStatus::OK && status != Dumpstate::RunStatus::USER_CONSENT_TIMED_OUT) { @@ -3326,9 +3379,11 @@ void Dumpstate::onUiIntensiveBugreportDumpsFinished(int32_t calling_uid) { } void Dumpstate::MaybeCheckUserConsent(int32_t calling_uid, const std::string& calling_package) { - if (multiuser_get_app_id(calling_uid) == AID_SHELL || !CalledByApi()) { - // No need to get consent for shell triggered dumpstates, or not through - // bugreporting API (i.e. no fd to copy back). + if (multiuser_get_app_id(calling_uid) == AID_SHELL || + !CalledByApi() || options_->is_consent_deferred) { + // No need to get consent for shell triggered dumpstates, or not + // through bugreporting API (i.e. no fd to copy back), or when consent + // is deferred. return; } consent_callback_ = new ConsentCallback(); diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h index 9f894b5079..8a31c314d9 100644 --- a/cmds/dumpstate/dumpstate.h +++ b/cmds/dumpstate/dumpstate.h @@ -207,7 +207,9 @@ class Dumpstate { // The flags used to customize bugreport requests. enum BugreportFlag { BUGREPORT_USE_PREDUMPED_UI_DATA = - android::os::IDumpstate::BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA + android::os::IDumpstate::BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA, + BUGREPORT_FLAG_DEFER_CONSENT = + android::os::IDumpstate::BUGREPORT_FLAG_DEFER_CONSENT }; static android::os::dumpstate::CommandOptions DEFAULT_DUMPSYS; @@ -353,6 +355,15 @@ class Dumpstate { */ RunStatus Run(int32_t calling_uid, const std::string& calling_package); + /* + * Entry point for retrieving a previous-generated bugreport. + * + * Initialize() dumpstate before calling this method. + */ + RunStatus Retrieve(int32_t calling_uid, const std::string& calling_package); + + + RunStatus ParseCommandlineAndRun(int argc, char* argv[]); /* Deletes in-progress files */ @@ -396,6 +407,7 @@ class Dumpstate { bool progress_updates_to_socket = false; bool do_screenshot = false; bool is_screenshot_copied = false; + bool is_consent_deferred = false; bool is_remote_mode = false; bool show_header_only = false; bool telephony_only = false; @@ -548,6 +560,7 @@ class Dumpstate { private: RunStatus RunInternal(int32_t calling_uid, const std::string& calling_package); + RunStatus RetrieveInternal(int32_t calling_uid, const std::string& calling_package); RunStatus DumpstateDefaultAfterCritical(); RunStatus dumpstate(); @@ -572,6 +585,8 @@ class Dumpstate { RunStatus HandleUserConsentDenied(); + void HandleRunStatus(RunStatus status); + // Copies bugreport artifacts over to the caller's directories provided there is user consent or // called by Shell. RunStatus CopyBugreportIfUserConsented(int32_t calling_uid); diff --git a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp index b091c8e297..ccf64fe54e 100644 --- a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp +++ b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp @@ -160,7 +160,7 @@ class DumpstateListener : public BnDumpstateListener { return binder::Status::ok(); } - binder::Status onFinished() override { + binder::Status onFinished([[maybe_unused]] const std::string& bugreport_file) override { std::lock_guard lock(lock_); is_finished_ = true; dprintf(out_fd_, "\rFinished"); diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp index 1ffcafa544..87f92544c0 100644 --- a/cmds/dumpstate/tests/dumpstate_test.cpp +++ b/cmds/dumpstate/tests/dumpstate_test.cpp @@ -71,7 +71,7 @@ class DumpstateListenerMock : public IDumpstateListener { public: MOCK_METHOD1(onProgress, binder::Status(int32_t progress)); MOCK_METHOD1(onError, binder::Status(int32_t error_code)); - MOCK_METHOD0(onFinished, binder::Status()); + MOCK_METHOD1(onFinished, binder::Status(const std::string& bugreport_file)); MOCK_METHOD1(onScreenshotTaken, binder::Status(bool success)); MOCK_METHOD0(onUiIntensiveBugreportDumpsFinished, binder::Status()); @@ -486,6 +486,20 @@ TEST_F(DumpOptionsTest, ValidateOptionsRemoteMode) { EXPECT_TRUE(options_.ValidateOptions()); } +TEST_F(DumpOptionsTest, InitializeBugreportFlags) { + int flags = Dumpstate::BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA | + Dumpstate::BugreportFlag::BUGREPORT_FLAG_DEFER_CONSENT; + options_.Initialize( + Dumpstate::BugreportMode::BUGREPORT_FULL, flags, fd, fd, true); + EXPECT_TRUE(options_.is_consent_deferred); + EXPECT_TRUE(options_.use_predumped_ui_data); + + options_.Initialize( + Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true); + EXPECT_FALSE(options_.is_consent_deferred); + EXPECT_FALSE(options_.use_predumped_ui_data); +} + class DumpstateTest : public DumpstateBaseTest { public: void SetUp() { -- GitLab From a546ba8dfb64ef493c06dae1402fb02b024f8262 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Fri, 13 Jan 2023 17:21:00 +0000 Subject: [PATCH 0771/1310] Add plumbing from settings to gesture properties Because we don't have the Android curve parameters yet (or the code to put them into the custom curve gesture properties), the fifteen different speed settings get mapped onto the five ChromeOS curves built-in to the gestures library Bug: 251196347, 247080509, 265798483 Test: Use settings command in ADB shell to change values Change-Id: I414d6e63c56d8e42c5848ca4aa150510956c70e3 --- services/inputflinger/InputReaderBase.cpp | 3 +++ .../inputflinger/include/InputReaderBase.h | 23 ++++++++++++++++++- .../reader/mapper/TouchpadInputMapper.cpp | 12 ++++++++++ .../mapper/gestures/GestureConverter.cpp | 8 +++---- .../tests/GestureConverter_test.cpp | 12 +++++----- 5 files changed, 47 insertions(+), 11 deletions(-) diff --git a/services/inputflinger/InputReaderBase.cpp b/services/inputflinger/InputReaderBase.cpp index a864cf8202..2450235ec1 100644 --- a/services/inputflinger/InputReaderBase.cpp +++ b/services/inputflinger/InputReaderBase.cpp @@ -73,6 +73,9 @@ std::string InputReaderConfiguration::changesToString(uint32_t changes) { if (changes & CHANGE_ENABLED_STATE) { result += "ENABLED_STATE | "; } + if (changes & CHANGE_TOUCHPAD_SETTINGS) { + result += "TOUCHPAD_SETTINGS | "; + } if (changes & CHANGE_MUST_REOPEN) { result += "MUST_REOPEN | "; } diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index d55ab28ff6..2173117fed 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -161,7 +161,7 @@ public: struct InputReaderConfiguration { // Describes changes that have occurred. enum { - // The pointer speed changed. + // The mouse pointer speed changed. CHANGE_POINTER_SPEED = 1 << 0, // The pointer gesture control changed. @@ -200,6 +200,9 @@ struct InputReaderConfiguration { // The stylus button reporting configurations has changed. CHANGE_STYLUS_BUTTON_REPORTING = 1 << 12, + // The touchpad settings changed. + CHANGE_TOUCHPAD_SETTINGS = 1 << 13, + // All devices must be reopened. CHANGE_MUST_REOPEN = 1 << 31, }; @@ -309,6 +312,20 @@ struct InputReaderConfiguration { // The latest request to enable or disable Pointer Capture. PointerCaptureRequest pointerCaptureRequest; + // The touchpad pointer speed, as a number from -7 (slowest) to 7 (fastest). + int32_t touchpadPointerSpeed; + + // True to invert the touchpad scrolling direction, so that moving two fingers downwards on the + // touchpad scrolls the content upwards. + bool touchpadNaturalScrollingEnabled; + + // True to enable tap-to-click on touchpads. + bool touchpadTapToClickEnabled; + + // True to enable a zone on the right-hand side of touchpads where clicks will be turned into + // context (a.k.a. "right") clicks. + bool touchpadRightClickZoneEnabled; + // The set of currently disabled input devices. std::set disabledDevices; @@ -337,6 +354,10 @@ struct InputReaderConfiguration { pointerGestureZoomSpeedRatio(0.3f), showTouches(false), pointerCaptureRequest(), + touchpadPointerSpeed(0), + touchpadNaturalScrollingEnabled(true), + touchpadTapToClickEnabled(true), + touchpadRightClickZoneEnabled(false), stylusButtonMotionEventsEnabled(true) {} static std::string changesToString(uint32_t changes); diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index b6313a1049..9f32311e14 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -146,6 +146,18 @@ std::list TouchpadInputMapper::configure(nsecs_t when, } mGestureConverter.setOrientation(orientation); } + if (!changes || (changes & InputReaderConfiguration::CHANGE_TOUCHPAD_SETTINGS)) { + // TODO(b/265798483): load an Android-specific acceleration curve instead of mapping to one + // of five ChromeOS curves. + const int pointerSensitivity = (config->touchpadPointerSpeed + 7) / 3 + 1; + mPropertyProvider.getProperty("Pointer Sensitivity").setIntValues({pointerSensitivity}); + mPropertyProvider.getProperty("Invert Scrolling") + .setBoolValues({config->touchpadNaturalScrollingEnabled}); + mPropertyProvider.getProperty("Tap Enable") + .setBoolValues({config->touchpadTapToClickEnabled}); + mPropertyProvider.getProperty("Button Right Click Zone Enable") + .setBoolValues({config->touchpadRightClickZoneEnabled}); + } return {}; } diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp index 561b1f819a..d636d4458f 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp @@ -219,11 +219,11 @@ std::list GestureConverter::handleScroll(nsecs_t when, nsecs_t readT float deltaY = gesture.details.scroll.dy; rotateDelta(mOrientation, &deltaX, &deltaY); - coords.setAxisValue(AMOTION_EVENT_AXIS_X, coords.getAxisValue(AMOTION_EVENT_AXIS_X) - deltaX); - coords.setAxisValue(AMOTION_EVENT_AXIS_Y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y) - deltaY); + coords.setAxisValue(AMOTION_EVENT_AXIS_X, coords.getAxisValue(AMOTION_EVENT_AXIS_X) + deltaX); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y) + deltaY); // TODO(b/262876643): set AXIS_GESTURE_{X,Y}_OFFSET. - coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, gesture.details.scroll.dx); - coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, gesture.details.scroll.dy); + coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, -gesture.details.scroll.dx); + coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, -gesture.details.scroll.dy); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1, mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp index 36a39bb2c6..9c624ba560 100644 --- a/services/inputflinger/tests/GestureConverter_test.cpp +++ b/services/inputflinger/tests/GestureConverter_test.cpp @@ -244,7 +244,7 @@ TEST_F(GestureConverterTest, Scroll) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 10); + Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); std::list args = converter.handleGesture(downTime, READ_TIME, startGesture); ASSERT_EQ(2u, args.size()); @@ -261,7 +261,7 @@ TEST_F(GestureConverterTest, Scroll) { WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); - Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 5); + Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); ASSERT_EQ(1u, args.size()); ASSERT_THAT(std::get(args.front()), @@ -289,7 +289,7 @@ TEST_F(GestureConverterTest, Scroll_Rotated) { GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setOrientation(ui::ROTATION_90); - Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 10); + Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); std::list args = converter.handleGesture(downTime, READ_TIME, startGesture); ASSERT_EQ(2u, args.size()); @@ -306,7 +306,7 @@ TEST_F(GestureConverterTest, Scroll_Rotated) { WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); - Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 5); + Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); ASSERT_EQ(1u, args.size()); ASSERT_THAT(std::get(args.front()), @@ -332,10 +332,10 @@ TEST_F(GestureConverterTest, Scroll_ClearsClassificationAndOffsetsAfterGesture) InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 10); + Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); std::list args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); - Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 5); + Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, -- GitLab From 7aa0eb74f5d3ae176cc4b3d51ce69512aae6926f Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 24 Jan 2023 03:59:27 +0000 Subject: [PATCH 0772/1310] SF: Change screenshot code to use snapshots This prepares us for frontend changes which do not rely on the Layer object. Instead we change the traversal function to return a z-ordered list of snapshots. This cl also changes some of the logic to check the snapshot instead of the layer drawing state. Bug: 238781169 Test: presubmit Test: manually test hdr listeners Change-Id: If508f9380fdef0414bbf448ece767be3e0bba9cf --- .../surfaceflinger/RegionSamplingThread.cpp | 3 +- services/surfaceflinger/RenderArea.h | 15 +++ services/surfaceflinger/SurfaceFlinger.cpp | 121 ++++++++---------- services/surfaceflinger/SurfaceFlinger.h | 12 +- .../tests/unittests/CompositionTest.cpp | 4 +- .../tests/unittests/TestableSurfaceFlinger.h | 2 +- 6 files changed, 84 insertions(+), 73 deletions(-) diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp index 6e64e0a13b..839500f8a0 100644 --- a/services/surfaceflinger/RegionSamplingThread.cpp +++ b/services/surfaceflinger/RegionSamplingThread.cpp @@ -344,12 +344,13 @@ void RegionSamplingThread::captureSample() { renderengine::impl::ExternalTexture::Usage:: WRITEABLE); } + auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); constexpr bool kRegionSampling = true; constexpr bool kGrayscale = false; if (const auto fenceResult = - mFlinger.captureScreenCommon(std::move(renderAreaFuture), traverseLayers, buffer, + mFlinger.captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, buffer, kRegionSampling, kGrayscale, nullptr) .get(); fenceResult.ok()) { diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h index 387364c03a..3c20e3b1e2 100644 --- a/services/surfaceflinger/RenderArea.h +++ b/services/surfaceflinger/RenderArea.h @@ -34,6 +34,21 @@ public: mRotationFlags(rotation), mLayerStackSpaceRect(layerStackRect) {} + static std::function>>()> fromTraverseLayersLambda( + std::function traverseLayers) { + return [traverseLayers = std::move(traverseLayers)]() { + std::vector>> layers; + traverseLayers([&](Layer* layer) { + // Layer::prepareClientComposition uses the layer's snapshot to populate the + // resulting LayerSettings. Calling Layer::updateSnapshot ensures that LayerSettings + // are generated with the layer's current buffer and geometry. + layer->updateSnapshot(true /* updateGeometry */); + layers.emplace_back(layer, layer->copyCompositionEngineLayerFE()); + }); + return layers; + }; + } + virtual ~RenderArea() = default; // Invoke drawLayers to render layers into the render area. diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 1577404d23..bdc57c904b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -130,6 +130,7 @@ #include "FrameTracer/FrameTracer.h" #include "FrontEnd/LayerCreationArgs.h" #include "FrontEnd/LayerHandle.h" +#include "FrontEnd/LayerSnapshot.h" #include "HdrLayerInfoReporter.h" #include "Layer.h" #include "LayerProtoHelper.h" @@ -2515,30 +2516,30 @@ void SurfaceFlinger::updateLayerGeometry() { mLayersPendingRefresh.clear(); } -bool SurfaceFlinger::isHdrLayer(Layer* layer) const { +bool SurfaceFlinger::isHdrLayer(const frontend::LayerSnapshot& snapshot) const { // Even though the camera layer may be using an HDR transfer function or otherwise be "HDR" // the device may need to avoid boosting the brightness as a result of these layers to // reduce power consumption during camera recording if (mIgnoreHdrCameraLayers) { - auto buffer = layer->getBuffer(); - if (buffer && (buffer->getUsage() & GRALLOC_USAGE_HW_CAMERA_WRITE) != 0) { + if (snapshot.externalTexture && + (snapshot.externalTexture->getUsage() & GRALLOC_USAGE_HW_CAMERA_WRITE) != 0) { return false; } } - if (isHdrDataspace(layer->getDataSpace())) { + if (isHdrDataspace(snapshot.dataspace)) { return true; } // If the layer is not allowed to be dimmed, treat it as HDR. WindowManager may disable // dimming in order to keep animations invoking SDR screenshots of HDR layers seamless. // Treat such tagged layers as HDR so that DisplayManagerService does not try to change // the screen brightness - if (!layer->isDimmingEnabled()) { + if (!snapshot.dimmingEnabled) { return true; } // RANGE_EXTENDED layers may identify themselves as being "HDR" via a desired sdr/hdr ratio - if ((layer->getDataSpace() & (int32_t)Dataspace::RANGE_MASK) == + if ((snapshot.dataspace & (int32_t)Dataspace::RANGE_MASK) == (int32_t)Dataspace::RANGE_EXTENDED && - layer->getDesiredSdrHdrRatio() > 1.01f) { + snapshot.desiredSdrHdrRatio > 1.01f) { return true; } return false; @@ -2666,13 +2667,14 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { int32_t maxArea = 0; mDrawingState.traverse([&, compositionDisplay = compositionDisplay](Layer* layer) { const auto layerFe = layer->getCompositionEngineLayerFE(); - if (layer->isVisible() && - compositionDisplay->includesLayer(layer->getOutputFilter())) { - if (isHdrLayer(layer)) { + const frontend::LayerSnapshot& snapshot = *layer->getLayerSnapshot(); + if (snapshot.isVisible && + compositionDisplay->includesLayer(snapshot.outputFilter)) { + if (isHdrLayer(snapshot)) { const auto* outputLayer = compositionDisplay->getOutputLayerForLayer(layerFe); if (outputLayer) { - info.mergeDesiredRatio(layer->getDesiredSdrHdrRatio()); + info.mergeDesiredRatio(snapshot.desiredSdrHdrRatio); info.numberOfHdrLayers++; const auto displayFrame = outputLayer->getState().displayFrame; const int32_t area = displayFrame.width() * displayFrame.height(); @@ -6418,8 +6420,9 @@ status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, auto traverseLayers = [this, args, layerStack](const LayerVector::Visitor& visitor) { traverseLayersInLayerStack(layerStack, args.uid, visitor); }; + auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); - auto future = captureScreenCommon(std::move(renderAreaFuture), traverseLayers, reqSize, + auto future = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize, args.pixelFormat, args.allowProtected, args.grayscale, captureListener); return fenceStatus(future.get()); @@ -6454,6 +6457,7 @@ status_t SurfaceFlinger::captureDisplay(DisplayId displayId, auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) { traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, visitor); }; + auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); if (captureListener == nullptr) { ALOGE("capture screen must provide a capture listener callback"); @@ -6463,7 +6467,7 @@ status_t SurfaceFlinger::captureDisplay(DisplayId displayId, constexpr bool kAllowProtected = false; constexpr bool kGrayscale = false; - auto future = captureScreenCommon(std::move(renderAreaFuture), traverseLayers, size, + auto future = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, size, ui::PixelFormat::RGBA_8888, kAllowProtected, kGrayscale, captureListener); return fenceStatus(future.get()); @@ -6481,7 +6485,7 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, ui::Size reqSize; sp parent; Rect crop(args.sourceCrop); - std::unordered_set, SpHash> excludeLayers; + std::unordered_set excludeLayerIds; ui::Dataspace dataspace; // Call this before holding mStateLock to avoid any deadlocking. @@ -6521,9 +6525,9 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, reqSize = ui::Size(crop.width() * args.frameScaleX, crop.height() * args.frameScaleY); for (const auto& handle : args.excludeHandles) { - sp excludeLayer = LayerHandle::getLayer(handle); - if (excludeLayer != nullptr) { - excludeLayers.emplace(excludeLayer); + uint32_t excludeLayer = LayerHandle::getLayerId(handle); + if (excludeLayer != UNASSIGNED_LAYER_ID) { + excludeLayerIds.emplace(excludeLayer); } else { ALOGW("Invalid layer handle passed as excludeLayer to captureLayers"); return NAME_NOT_FOUND; @@ -6552,7 +6556,7 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, args.captureSecureLayers); }); - auto traverseLayers = [parent, args, excludeLayers](const LayerVector::Visitor& visitor) { + auto traverseLayers = [parent, args, excludeLayerIds](const LayerVector::Visitor& visitor) { parent->traverseChildrenInZOrder(LayerVector::StateSet::Drawing, [&](Layer* layer) { if (!layer->isVisible()) { return; @@ -6564,7 +6568,7 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, auto p = sp::fromExisting(layer); while (p != nullptr) { - if (excludeLayers.count(p) != 0) { + if (excludeLayerIds.count(p->sequence) != 0) { return; } p = p->getParent(); @@ -6573,20 +6577,21 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, visitor(layer); }); }; + auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); if (captureListener == nullptr) { ALOGE("capture screen must provide a capture listener callback"); return BAD_VALUE; } - auto future = captureScreenCommon(std::move(renderAreaFuture), traverseLayers, reqSize, + auto future = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize, args.pixelFormat, args.allowProtected, args.grayscale, captureListener); return fenceStatus(future.get()); } ftl::SharedFuture SurfaceFlinger::captureScreenCommon( - RenderAreaFuture renderAreaFuture, TraverseLayersFunction traverseLayers, + RenderAreaFuture renderAreaFuture, GetLayerSnapshotsFunction getLayerSnapshots, ui::Size bufferSize, ui::PixelFormat reqPixelFormat, bool allowProtected, bool grayscale, const sp& captureListener) { ATRACE_CALL(); @@ -6605,15 +6610,18 @@ ftl::SharedFuture SurfaceFlinger::captureScreenCommon( const bool supportsProtected = getRenderEngine().supportsProtectedContent(); bool hasProtectedLayer = false; if (allowProtected && supportsProtected) { - auto future = mScheduler->schedule([=]() { - bool protectedLayerFound = false; - traverseLayers([&](Layer* layer) { - protectedLayerFound = - protectedLayerFound || (layer->isVisible() && layer->isProtected()); - }); - return protectedLayerFound; - }); - hasProtectedLayer = future.get(); + hasProtectedLayer = mScheduler + ->schedule([=]() { + bool protectedLayerFound = false; + auto layers = getLayerSnapshots(); + for (auto& [layer, layerFe] : layers) { + protectedLayerFound |= + (layerFe->mSnapshot->isVisible && + layerFe->mSnapshot->hasProtectedContent); + } + return protectedLayerFound; + }) + .get(); } const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER | @@ -6638,12 +6646,12 @@ ftl::SharedFuture SurfaceFlinger::captureScreenCommon( renderengine::impl::ExternalTexture>(buffer, getRenderEngine(), renderengine::impl::ExternalTexture::Usage:: WRITEABLE); - return captureScreenCommon(std::move(renderAreaFuture), traverseLayers, texture, + return captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, texture, false /* regionSampling */, grayscale, captureListener); } ftl::SharedFuture SurfaceFlinger::captureScreenCommon( - RenderAreaFuture renderAreaFuture, TraverseLayersFunction traverseLayers, + RenderAreaFuture renderAreaFuture, GetLayerSnapshotsFunction getLayerSnapshots, const std::shared_ptr& buffer, bool regionSampling, bool grayscale, const sp& captureListener) { ATRACE_CALL(); @@ -6666,7 +6674,7 @@ ftl::SharedFuture SurfaceFlinger::captureScreenCommon( ftl::SharedFuture renderFuture; renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) { - renderFuture = renderScreenImpl(renderArea, traverseLayers, buffer, + renderFuture = renderScreenImpl(renderArea, getLayerSnapshots, buffer, canCaptureBlackoutContent, regionSampling, grayscale, captureResults); }); @@ -6694,18 +6702,26 @@ ftl::SharedFuture SurfaceFlinger::captureScreenCommon( } ftl::SharedFuture SurfaceFlinger::renderScreenImpl( - std::shared_ptr renderArea, TraverseLayersFunction traverseLayers, + std::shared_ptr renderArea, GetLayerSnapshotsFunction getLayerSnapshots, const std::shared_ptr& buffer, bool canCaptureBlackoutContent, bool regionSampling, bool grayscale, ScreenCaptureResults& captureResults) { ATRACE_CALL(); - size_t layerCount = 0; - traverseLayers([&](Layer* layer) { - layerCount++; - captureResults.capturedSecureLayers = - captureResults.capturedSecureLayers || (layer->isVisible() && layer->isSecure()); - }); + const auto& display = renderArea->getDisplayDevice(); + const auto& transform = renderArea->getTransform(); + std::unordered_set filterForScreenshot; + auto layers = getLayerSnapshots(); + for (auto& [layer, layerFE] : layers) { + frontend::LayerSnapshot* snapshot = layerFE->mSnapshot.get(); + captureResults.capturedSecureLayers |= (snapshot->isVisible && snapshot->isSecure); + captureResults.capturedHdrLayers |= isHdrLayer(*snapshot); + layerFE->mSnapshot->geomLayerTransform = + renderArea->getTransform() * layerFE->mSnapshot->geomLayerTransform; + if (layer->needsFilteringForScreenshots(display.get(), transform)) { + filterForScreenshot.insert(layerFE.get()); + } + } // We allow the system server to take screenshots of secure layers for // use in situations like the Screen-rotation animation and place @@ -6739,31 +6755,6 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( } captureResults.capturedDataspace = dataspace; - const auto transform = renderArea->getTransform(); - const auto display = renderArea->getDisplayDevice(); - - std::vector>> layers; - layers.reserve(layerCount); - std::unordered_set filterForScreenshot; - traverseLayers([&](Layer* layer) { - captureResults.capturedHdrLayers |= isHdrLayer(layer); - // Layer::prepareClientComposition uses the layer's snapshot to populate the resulting - // LayerSettings. Calling Layer::updateSnapshot ensures that LayerSettings are - // generated with the layer's current buffer and geometry. - layer->updateSnapshot(true /* updateGeometry */); - - layers.emplace_back(layer, layer->copyCompositionEngineLayerFE()); - - sp& layerFE = layers.back().second; - - layerFE->mSnapshot->geomLayerTransform = - renderArea->getTransform() * layerFE->mSnapshot->geomLayerTransform; - - if (layer->needsFilteringForScreenshots(display.get(), transform)) { - filterForScreenshot.insert(layerFE.get()); - } - }); - ui::LayerStack layerStack{ui::DEFAULT_LAYER_STACK}; if (!layers.empty()) { const sp& layerFE = layers.back().second; diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 6f092ed995..207dfe2e96 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -69,6 +69,7 @@ #include "FlagManager.h" #include "FrontEnd/DisplayInfo.h" #include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/LayerSnapshot.h" #include "FrontEnd/TransactionHandler.h" #include "LayerVector.h" #include "Scheduler/RefreshRateSelector.h" @@ -346,6 +347,7 @@ private: friend class LayerRenderArea; friend class LayerTracing; friend class SurfaceComposerAIDL; + friend class DisplayRenderArea; // For unit tests friend class TestableSurfaceFlinger; @@ -353,7 +355,7 @@ private: friend class TunnelModeEnabledReporterTest; using TransactionSchedule = scheduler::TransactionSchedule; - using TraverseLayersFunction = std::function; + using GetLayerSnapshotsFunction = std::function>>()>; using RenderAreaFuture = ftl::Future>; using DumpArgs = Vector; using Dumper = std::function; @@ -787,16 +789,16 @@ private: // Boot animation, on/off animations and screen capture void startBootAnim(); - ftl::SharedFuture captureScreenCommon(RenderAreaFuture, TraverseLayersFunction, + ftl::SharedFuture captureScreenCommon(RenderAreaFuture, GetLayerSnapshotsFunction, ui::Size bufferSize, ui::PixelFormat, bool allowProtected, bool grayscale, const sp&); ftl::SharedFuture captureScreenCommon( - RenderAreaFuture, TraverseLayersFunction, + RenderAreaFuture, GetLayerSnapshotsFunction, const std::shared_ptr&, bool regionSampling, bool grayscale, const sp&); ftl::SharedFuture renderScreenImpl( - std::shared_ptr, TraverseLayersFunction, + std::shared_ptr, GetLayerSnapshotsFunction, const std::shared_ptr&, bool canCaptureBlackoutContent, bool regionSampling, bool grayscale, ScreenCaptureResults&) EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); @@ -1085,7 +1087,7 @@ private: void updateInternalDisplayVsyncLocked(const sp& activeDisplay) REQUIRES(mStateLock, kMainThreadContext); - bool isHdrLayer(Layer* layer) const; + bool isHdrLayer(const frontend::LayerSnapshot& snapshot) const; ui::Rotation getPhysicalDisplayOrientation(DisplayId, bool isPrimary) const REQUIRES(mStateLock); diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index ba77600cda..0416e93f1e 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -238,6 +238,8 @@ void CompositionTest::captureScreenComposition() { CaptureArgs::UNSET_UID, visitor); }; + auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + const uint32_t usage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE; mCaptureScreenBuffer = @@ -246,7 +248,7 @@ void CompositionTest::captureScreenComposition() { HAL_PIXEL_FORMAT_RGBA_8888, 1, usage); - auto future = mFlinger.renderScreenImpl(std::move(renderArea), traverseLayers, + auto future = mFlinger.renderScreenImpl(std::move(renderArea), getLayerSnapshots, mCaptureScreenBuffer, forSystem, regionSampling); ASSERT_TRUE(future.valid()); const auto fenceResult = future.get(); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 51d20120cd..68c738fc9d 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -407,7 +407,7 @@ public: } auto renderScreenImpl(std::shared_ptr renderArea, - SurfaceFlinger::TraverseLayersFunction traverseLayers, + SurfaceFlinger::GetLayerSnapshotsFunction traverseLayers, const std::shared_ptr& buffer, bool forSystem, bool regionSampling) { ScreenCaptureResults captureResults; -- GitLab From 8f953ab5a1deea03a6abb4019008bac8c3ea2561 Mon Sep 17 00:00:00 2001 From: Philip Quinn Date: Tue, 6 Dec 2022 15:37:07 -0800 Subject: [PATCH 0773/1310] Add TFLite model for motion prediction. This model generates probabilistic motion predictions based on a sequence of relative input movements. The input movements are converted into polar coordinates (distance and angle) based on an axis that follows the current path. This ensures that the orientation of the device and of the inputs do not affect the predictions. The orientation of the input device is also transformed to be relative to the path axis. The test cases verifying model efficacy are consolidated into CTS. Bug: 167946763 Test: atest libinput_tests PiperOrigin-RevId: 492068340 Change-Id: Icd8d90bd5a7ce79c699bfdb6367a4cbd8130441a --- data/etc/input/Android.bp | 14 + data/etc/input/motion_predictor_model.fb | Bin 0 -> 34080 bytes include/input/MotionPredictor.h | 35 +- include/input/TfLiteMotionPredictor.h | 147 ++++++++ libs/input/Android.bp | 13 +- libs/input/MotionPredictor.cpp | 244 ++++++++----- libs/input/TfLiteMotionPredictor.cpp | 338 ++++++++++++++++++ libs/input/tests/Android.bp | 14 +- libs/input/tests/MotionPredictor_test.cpp | 136 ++++--- .../tests/TfLiteMotionPredictor_test.cpp | 179 ++++++++++ 10 files changed, 970 insertions(+), 150 deletions(-) create mode 100644 data/etc/input/Android.bp create mode 100644 data/etc/input/motion_predictor_model.fb create mode 100644 include/input/TfLiteMotionPredictor.h create mode 100644 libs/input/TfLiteMotionPredictor.cpp create mode 100644 libs/input/tests/TfLiteMotionPredictor_test.cpp diff --git a/data/etc/input/Android.bp b/data/etc/input/Android.bp new file mode 100644 index 0000000000..90f3c6b49a --- /dev/null +++ b/data/etc/input/Android.bp @@ -0,0 +1,14 @@ +package { + default_applicable_licenses: ["frameworks_native_license"], +} + +filegroup { + name: "motion_predictor_model.fb", + srcs: ["motion_predictor_model.fb"], +} + +prebuilt_etc { + name: "motion_predictor_model_prebuilt", + filename_from_src: true, + src: "motion_predictor_model.fb", +} diff --git a/data/etc/input/motion_predictor_model.fb b/data/etc/input/motion_predictor_model.fb new file mode 100644 index 0000000000000000000000000000000000000000..10b3c8b1143cf51be9a11b3f12fd54c1458ad398 GIT binary patch literal 34080 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 literal 0 HcmV?d00001 diff --git a/include/input/MotionPredictor.h b/include/input/MotionPredictor.h index 045e61bea2..3fae4e6b68 100644 --- a/include/input/MotionPredictor.h +++ b/include/input/MotionPredictor.h @@ -16,9 +16,15 @@ #pragma once +#include +#include +#include +#include + #include #include #include +#include namespace android { @@ -28,48 +34,51 @@ static inline bool isMotionPredictionEnabled() { /** * Given a set of MotionEvents for the current gesture, predict the motion. The returned MotionEvent - * contains a set of samples in the future, up to "presentation time + offset". + * contains a set of samples in the future. * * The typical usage is like this: * * MotionPredictor predictor(offset = MY_OFFSET); - * predictor.setExpectedPresentationTimeNanos(NEXT_PRESENT_TIME); * predictor.record(DOWN_MOTION_EVENT); * predictor.record(MOVE_MOTION_EVENT); - * prediction = predictor.predict(); - * - * The presentation time should be set some time before calling .predict(). It could be set before - * or after the recorded motion events. Must be done on every frame. + * prediction = predictor.predict(futureTime); * - * The resulting motion event will have eventTime <= (NEXT_PRESENT_TIME + MY_OFFSET). It might - * contain historical data, which are additional samples from the latest recorded MotionEvent's - * eventTime to the NEXT_PRESENT_TIME + MY_OFFSET. + * The resulting motion event will have eventTime <= (futureTime + MY_OFFSET). It might contain + * historical data, which are additional samples from the latest recorded MotionEvent's eventTime + * to the futureTime + MY_OFFSET. * * The offset is used to provide additional flexibility to the caller, in case the default present * time (typically provided by the choreographer) does not account for some delays, or to simply - * reduce the aggressiveness of the prediction. Offset can be both positive and negative. + * reduce the aggressiveness of the prediction. Offset can be positive or negative. */ class MotionPredictor { public: /** * Parameters: * predictionTimestampOffsetNanos: additional, constant shift to apply to the target - * presentation time. The prediction will target the time t=(presentationTime + + * prediction time. The prediction will target the time t=(prediction time + * predictionTimestampOffsetNanos). * + * modelPath: filesystem path to a TfLiteMotionPredictorModel flatbuffer, or nullptr to use the + * default model path. + * * checkEnableMotionPredition: the function to check whether the prediction should run. Used to * provide an additional way of turning prediction on and off. Can be toggled at runtime. */ - MotionPredictor(nsecs_t predictionTimestampOffsetNanos, + MotionPredictor(nsecs_t predictionTimestampOffsetNanos, const char* modelPath = nullptr, std::function checkEnableMotionPrediction = isMotionPredictionEnabled); void record(const MotionEvent& event); std::vector> predict(nsecs_t timestamp); bool isPredictionAvailable(int32_t deviceId, int32_t source); private: - std::vector mEvents; const nsecs_t mPredictionTimestampOffsetNanos; const std::function mCheckMotionPredictionEnabled; + + std::unique_ptr mModel; + // Buffers/events for each device seen by record(). + std::unordered_map mDeviceBuffers; + std::unordered_map mLastEvents; }; } // namespace android diff --git a/include/input/TfLiteMotionPredictor.h b/include/input/TfLiteMotionPredictor.h new file mode 100644 index 0000000000..ff0f51c7d9 --- /dev/null +++ b/include/input/TfLiteMotionPredictor.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace android { + +struct TfLiteMotionPredictorSample { + // The untransformed AMOTION_EVENT_AXIS_X and AMOTION_EVENT_AXIS_Y of the sample. + struct Point { + float x; + float y; + } position; + // The AMOTION_EVENT_AXIS_PRESSURE, _TILT, and _ORIENTATION. + float pressure; + float tilt; + float orientation; +}; + +inline TfLiteMotionPredictorSample::Point operator-(const TfLiteMotionPredictorSample::Point& lhs, + const TfLiteMotionPredictorSample::Point& rhs) { + return {.x = lhs.x - rhs.x, .y = lhs.y - rhs.y}; +} + +class TfLiteMotionPredictorModel; + +// Buffer storage for a TfLiteMotionPredictorModel. +class TfLiteMotionPredictorBuffers { +public: + // Creates buffer storage for a model with the given input length. + TfLiteMotionPredictorBuffers(size_t inputLength); + + // Adds a motion sample to the buffers. + void pushSample(int64_t timestamp, TfLiteMotionPredictorSample sample); + + // Returns true if the buffers are complete enough to generate a prediction. + bool isReady() const { + // Predictions can't be applied unless there are at least two points to determine + // the direction to apply them in. + return mAxisFrom && mAxisTo; + } + + // Resets all buffers to their initial state. + void reset(); + + // Copies the buffers to those of a model for prediction. + void copyTo(TfLiteMotionPredictorModel& model) const; + + // Returns the current axis of the buffer's samples. Only valid if isReady(). + TfLiteMotionPredictorSample axisFrom() const { return *mAxisFrom; } + TfLiteMotionPredictorSample axisTo() const { return *mAxisTo; } + + // Returns the timestamp of the last sample. + int64_t lastTimestamp() const { return mTimestamp; } + +private: + int64_t mTimestamp = 0; + + std::vector mInputR; + std::vector mInputPhi; + std::vector mInputPressure; + std::vector mInputTilt; + std::vector mInputOrientation; + + // The samples defining the current polar axis. + std::optional mAxisFrom; + std::optional mAxisTo; +}; + +// A TFLite model for generating motion predictions. +class TfLiteMotionPredictorModel { +public: + // Creates a model from an encoded Flatbuffer model. + static std::unique_ptr create(const char* modelPath); + + // Returns the length of the model's input buffers. + size_t inputLength() const; + + // Executes the model. + // Returns true if the model successfully executed and the output tensors can be read. + bool invoke(); + + // Returns mutable buffers to the input tensors of inputLength() elements. + std::span inputR(); + std::span inputPhi(); + std::span inputPressure(); + std::span inputOrientation(); + std::span inputTilt(); + + // Returns immutable buffers to the output tensors of identical length. Only valid after a + // successful call to invoke(). + std::span outputR() const; + std::span outputPhi() const; + std::span outputPressure() const; + +private: + explicit TfLiteMotionPredictorModel(std::string model); + + void allocateTensors(); + void attachInputTensors(); + void attachOutputTensors(); + + TfLiteTensor* mInputR = nullptr; + TfLiteTensor* mInputPhi = nullptr; + TfLiteTensor* mInputPressure = nullptr; + TfLiteTensor* mInputTilt = nullptr; + TfLiteTensor* mInputOrientation = nullptr; + + const TfLiteTensor* mOutputR = nullptr; + const TfLiteTensor* mOutputPhi = nullptr; + const TfLiteTensor* mOutputPressure = nullptr; + + std::string mFlatBuffer; + std::unique_ptr mErrorReporter; + std::unique_ptr mModel; + std::unique_ptr mInterpreter; + tflite::SignatureRunner* mRunner = nullptr; +}; + +} // namespace android diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 8f41cc1872..83392ec793 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -41,6 +41,7 @@ cc_library { "-Wall", "-Wextra", "-Werror", + "-Wno-unused-parameter", ], srcs: [ "Input.cpp", @@ -52,13 +53,18 @@ cc_library { "MotionPredictor.cpp", "PrintTools.cpp", "PropertyMap.cpp", + "TfLiteMotionPredictor.cpp", "TouchVideoFrame.cpp", "VelocityControl.cpp", "VelocityTracker.cpp", "VirtualKeyMap.cpp", ], - header_libs: ["jni_headers"], + header_libs: [ + "flatbuffer_headers", + "jni_headers", + "tensorflow_headers", + ], export_header_lib_headers: ["jni_headers"], shared_libs: [ @@ -67,6 +73,7 @@ cc_library { "liblog", "libPlatformProperties", "libvintf", + "libtflite", ], static_libs: [ @@ -103,6 +110,10 @@ cc_library { sanitize: { misc_undefined: ["integer"], }, + + required: [ + "motion_predictor_model_prebuilt", + ], }, host: { shared: { diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp index 0fa0f1229c..0f889e8128 100644 --- a/libs/input/MotionPredictor.cpp +++ b/libs/input/MotionPredictor.cpp @@ -18,118 +18,188 @@ #include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace android { +namespace { + +const char DEFAULT_MODEL_PATH[] = "/system/etc/motion_predictor_model.fb"; +const int64_t PREDICTION_INTERVAL_NANOS = + 12500000 / 3; // TODO(b/266747937): Get this from the model. + /** * Log debug messages about predictions. * Enable this via "adb shell setprop log.tag.MotionPredictor DEBUG" */ -static bool isDebug() { +bool isDebug() { return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO); } -namespace android { +// Converts a prediction of some polar (r, phi) to Cartesian (x, y) when applied to an axis. +TfLiteMotionPredictorSample::Point convertPrediction( + const TfLiteMotionPredictorSample::Point& axisFrom, + const TfLiteMotionPredictorSample::Point& axisTo, float r, float phi) { + const TfLiteMotionPredictorSample::Point axis = axisTo - axisFrom; + const float axis_phi = std::atan2(axis.y, axis.x); + const float x_delta = r * std::cos(axis_phi + phi); + const float y_delta = r * std::sin(axis_phi + phi); + return {.x = axisTo.x + x_delta, .y = axisTo.y + y_delta}; +} + +} // namespace // --- MotionPredictor --- -MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos, +MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos, const char* modelPath, std::function checkMotionPredictionEnabled) : mPredictionTimestampOffsetNanos(predictionTimestampOffsetNanos), - mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)) {} + mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)), + mModel(TfLiteMotionPredictorModel::create(modelPath == nullptr ? DEFAULT_MODEL_PATH + : modelPath)) {} void MotionPredictor::record(const MotionEvent& event) { - mEvents.push_back({}); - mEvents.back().copyFrom(&event, /*keepHistory=*/true); - if (mEvents.size() > 2) { - // Just need 2 samples in order to extrapolate - mEvents.erase(mEvents.begin()); + if (!isPredictionAvailable(event.getDeviceId(), event.getSource())) { + ALOGE("Prediction not supported for device %d's %s source", event.getDeviceId(), + inputEventSourceToString(event.getSource()).c_str()); + return; } -} -/** - * This is an example implementation that should be replaced with the actual prediction. - * The returned MotionEvent should be similar to the incoming MotionEvent, except for the - * fields that are predicted: - * - * 1) event.getEventTime - * 2) event.getPointerCoords - * - * The returned event should not contain any of the real, existing data. It should only - * contain the predicted samples. - */ -std::vector> MotionPredictor::predict(nsecs_t timestamp) { - if (mEvents.size() < 2) { - return {}; + TfLiteMotionPredictorBuffers& buffers = + mDeviceBuffers.try_emplace(event.getDeviceId(), mModel->inputLength()).first->second; + + const int32_t action = event.getActionMasked(); + if (action == AMOTION_EVENT_ACTION_UP) { + ALOGD_IF(isDebug(), "End of event stream"); + buffers.reset(); + return; + } else if (action != AMOTION_EVENT_ACTION_DOWN && action != AMOTION_EVENT_ACTION_MOVE) { + ALOGD_IF(isDebug(), "Skipping unsupported %s action", + MotionEvent::actionToString(action).c_str()); + return; } - const MotionEvent& event = mEvents.back(); - if (!isPredictionAvailable(event.getDeviceId(), event.getSource())) { - return {}; + if (event.getPointerCount() != 1) { + ALOGD_IF(isDebug(), "Prediction not supported for multiple pointers"); + return; } - std::unique_ptr prediction = std::make_unique(); - std::vector futureCoords; - const nsecs_t futureTime = timestamp + mPredictionTimestampOffsetNanos; - const nsecs_t currentTime = event.getEventTime(); - const MotionEvent& previous = mEvents.rbegin()[1]; - const nsecs_t oldTime = previous.getEventTime(); - if (currentTime == oldTime) { - // This can happen if it's an ACTION_POINTER_DOWN event, for example. - return {}; // prevent division by zero. + const int32_t toolType = event.getPointerProperties(0)->toolType; + if (toolType != AMOTION_EVENT_TOOL_TYPE_STYLUS) { + ALOGD_IF(isDebug(), "Prediction not supported for non-stylus tool: %s", + motionToolTypeToString(toolType)); + return; } - for (size_t i = 0; i < event.getPointerCount(); i++) { - const int32_t pointerId = event.getPointerId(i); - const PointerCoords* currentPointerCoords = event.getRawPointerCoords(i); - const float currentX = currentPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_X); - const float currentY = currentPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_Y); - - PointerCoords coords; - coords.clear(); - - ssize_t index = previous.findPointerIndex(pointerId); - if (index >= 0) { - // We have old data for this pointer. Compute the prediction. - const PointerCoords* oldPointerCoords = previous.getRawPointerCoords(index); - const float oldX = oldPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_X); - const float oldY = oldPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_Y); - - // Let's do a linear interpolation while waiting for a real model - const float scale = - static_cast(futureTime - currentTime) / (currentTime - oldTime); - const float futureX = currentX + (currentX - oldX) * scale; - const float futureY = currentY + (currentY - oldY) * scale; - - coords.setAxisValue(AMOTION_EVENT_AXIS_X, futureX); - coords.setAxisValue(AMOTION_EVENT_AXIS_Y, futureY); - ALOGD_IF(isDebug(), - "Prediction by %.1f ms, (%.1f, %.1f), (%.1f, %.1f) --> (%.1f, %.1f)", - (futureTime - event.getEventTime()) * 1E-6, oldX, oldY, currentX, currentY, - futureX, futureY); + for (size_t i = 0; i <= event.getHistorySize(); ++i) { + if (event.isResampled(0, i)) { + continue; } - - futureCoords.push_back(coords); + const PointerCoords* coords = event.getHistoricalRawPointerCoords(0, i); + buffers.pushSample(event.getHistoricalEventTime(i), + { + .position.x = coords->getAxisValue(AMOTION_EVENT_AXIS_X), + .position.y = coords->getAxisValue(AMOTION_EVENT_AXIS_Y), + .pressure = event.getHistoricalPressure(0, i), + .tilt = event.getHistoricalAxisValue(AMOTION_EVENT_AXIS_TILT, 0, + i), + .orientation = event.getHistoricalOrientation(0, i), + }); } - /** - * The process of adding samples is different for the first and subsequent samples: - * 1. Add the first sample via 'initialize' as below - * 2. Add subsequent samples via 'addSample' - */ - prediction->initialize(event.getId(), event.getDeviceId(), event.getSource(), - event.getDisplayId(), event.getHmac(), event.getAction(), - event.getActionButton(), event.getFlags(), event.getEdgeFlags(), - event.getMetaState(), event.getButtonState(), event.getClassification(), - event.getTransform(), event.getXPrecision(), event.getYPrecision(), - event.getRawXCursorPosition(), event.getRawYCursorPosition(), - event.getRawTransform(), event.getDownTime(), futureTime, - event.getPointerCount(), event.getPointerProperties(), - futureCoords.data()); - - // To add more predicted samples, use 'addSample': - prediction->addSample(futureTime + 1, futureCoords.data()); - - std::vector> out; - out.push_back(std::move(prediction)); - return out; + mLastEvents.try_emplace(event.getDeviceId()) + .first->second.copyFrom(&event, /*keepHistory=*/false); +} + +std::vector> MotionPredictor::predict(nsecs_t timestamp) { + std::vector> predictions; + + for (const auto& [deviceId, buffer] : mDeviceBuffers) { + if (!buffer.isReady()) { + continue; + } + + buffer.copyTo(*mModel); + LOG_ALWAYS_FATAL_IF(!mModel->invoke()); + + // Read out the predictions. + const std::span predictedR = mModel->outputR(); + const std::span predictedPhi = mModel->outputPhi(); + const std::span predictedPressure = mModel->outputPressure(); + + TfLiteMotionPredictorSample::Point axisFrom = buffer.axisFrom().position; + TfLiteMotionPredictorSample::Point axisTo = buffer.axisTo().position; + + if (isDebug()) { + ALOGD("deviceId: %d", deviceId); + ALOGD("axisFrom: %f, %f", axisFrom.x, axisFrom.y); + ALOGD("axisTo: %f, %f", axisTo.x, axisTo.y); + ALOGD("mInputR: %s", base::Join(mModel->inputR(), ", ").c_str()); + ALOGD("mInputPhi: %s", base::Join(mModel->inputPhi(), ", ").c_str()); + ALOGD("mInputPressure: %s", base::Join(mModel->inputPressure(), ", ").c_str()); + ALOGD("mInputTilt: %s", base::Join(mModel->inputTilt(), ", ").c_str()); + ALOGD("mInputOrientation: %s", base::Join(mModel->inputOrientation(), ", ").c_str()); + ALOGD("predictedR: %s", base::Join(predictedR, ", ").c_str()); + ALOGD("predictedPhi: %s", base::Join(predictedPhi, ", ").c_str()); + ALOGD("predictedPressure: %s", base::Join(predictedPressure, ", ").c_str()); + } + + const MotionEvent& event = mLastEvents[deviceId]; + bool hasPredictions = false; + std::unique_ptr prediction = std::make_unique(); + int64_t predictionTime = buffer.lastTimestamp(); + const int64_t futureTime = timestamp + mPredictionTimestampOffsetNanos; + + for (int i = 0; i < predictedR.size() && predictionTime <= futureTime; ++i) { + const TfLiteMotionPredictorSample::Point point = + convertPrediction(axisFrom, axisTo, predictedR[i], predictedPhi[i]); + // TODO(b/266747654): Stop predictions if confidence is < some threshold. + + ALOGD_IF(isDebug(), "prediction %d: %f, %f", i, point.x, point.y); + PointerCoords coords; + coords.clear(); + coords.setAxisValue(AMOTION_EVENT_AXIS_X, point.x); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, point.y); + // TODO(b/266747654): Stop predictions if predicted pressure is < some threshold. + coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, predictedPressure[i]); + + predictionTime += PREDICTION_INTERVAL_NANOS; + if (i == 0) { + hasPredictions = true; + prediction->initialize(InputEvent::nextId(), event.getDeviceId(), event.getSource(), + event.getDisplayId(), INVALID_HMAC, + AMOTION_EVENT_ACTION_MOVE, event.getActionButton(), + event.getFlags(), event.getEdgeFlags(), event.getMetaState(), + event.getButtonState(), event.getClassification(), + event.getTransform(), event.getXPrecision(), + event.getYPrecision(), event.getRawXCursorPosition(), + event.getRawYCursorPosition(), event.getRawTransform(), + event.getDownTime(), predictionTime, event.getPointerCount(), + event.getPointerProperties(), &coords); + } else { + prediction->addSample(predictionTime, &coords); + } + + axisFrom = axisTo; + axisTo = point; + } + // TODO(b/266747511): Interpolate to futureTime? + if (hasPredictions) { + predictions.push_back(std::move(prediction)); + } + } + return predictions; } bool MotionPredictor::isPredictionAvailable(int32_t /*deviceId*/, int32_t source) { diff --git a/libs/input/TfLiteMotionPredictor.cpp b/libs/input/TfLiteMotionPredictor.cpp new file mode 100644 index 0000000000..1a337adf13 --- /dev/null +++ b/libs/input/TfLiteMotionPredictor.cpp @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "TfLiteMotionPredictor" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ATRACE_TAG ATRACE_TAG_INPUT +#include +#include + +#include "tensorflow/lite/core/api/error_reporter.h" +#include "tensorflow/lite/interpreter.h" +#include "tensorflow/lite/kernels/register.h" +#include "tensorflow/lite/model.h" + +namespace android { +namespace { + +constexpr char SIGNATURE_KEY[] = "serving_default"; + +// Input tensor names. +constexpr char INPUT_R[] = "r"; +constexpr char INPUT_PHI[] = "phi"; +constexpr char INPUT_PRESSURE[] = "pressure"; +constexpr char INPUT_TILT[] = "tilt"; +constexpr char INPUT_ORIENTATION[] = "orientation"; + +// Output tensor names. +constexpr char OUTPUT_R[] = "r"; +constexpr char OUTPUT_PHI[] = "phi"; +constexpr char OUTPUT_PRESSURE[] = "pressure"; + +// A TFLite ErrorReporter that logs to logcat. +class LoggingErrorReporter : public tflite::ErrorReporter { +public: + int Report(const char* format, va_list args) override { + return LOG_PRI_VA(ANDROID_LOG_ERROR, LOG_TAG, format, args); + } +}; + +// Searches a runner for an input tensor. +TfLiteTensor* findInputTensor(const char* name, tflite::SignatureRunner* runner) { + TfLiteTensor* tensor = runner->input_tensor(name); + LOG_ALWAYS_FATAL_IF(!tensor, "Failed to find input tensor '%s'", name); + return tensor; +} + +// Searches a runner for an output tensor. +const TfLiteTensor* findOutputTensor(const char* name, tflite::SignatureRunner* runner) { + const TfLiteTensor* tensor = runner->output_tensor(name); + LOG_ALWAYS_FATAL_IF(!tensor, "Failed to find output tensor '%s'", name); + return tensor; +} + +// Returns the buffer for a tensor of type T. +template +std::span getTensorBuffer(typename std::conditional::value, const TfLiteTensor*, + TfLiteTensor*>::type tensor) { + LOG_ALWAYS_FATAL_IF(!tensor); + + const TfLiteType type = tflite::typeToTfLiteType::type>(); + LOG_ALWAYS_FATAL_IF(tensor->type != type, "Unexpected type for '%s' tensor: %s (expected %s)", + tensor->name, TfLiteTypeGetName(tensor->type), TfLiteTypeGetName(type)); + + LOG_ALWAYS_FATAL_IF(!tensor->data.data); + return {reinterpret_cast(tensor->data.data), + static_cast::index_type>(tensor->bytes / sizeof(T))}; +} + +// Verifies that a tensor exists and has an underlying buffer of type T. +template +void checkTensor(const TfLiteTensor* tensor) { + LOG_ALWAYS_FATAL_IF(!tensor); + + const auto buffer = getTensorBuffer(tensor); + LOG_ALWAYS_FATAL_IF(buffer.empty(), "No buffer for tensor '%s'", tensor->name); +} + +} // namespace + +TfLiteMotionPredictorBuffers::TfLiteMotionPredictorBuffers(size_t inputLength) { + LOG_ALWAYS_FATAL_IF(inputLength == 0, "Buffer input size must be greater than 0"); + mInputR.resize(inputLength); + mInputPhi.resize(inputLength); + mInputPressure.resize(inputLength); + mInputTilt.resize(inputLength); + mInputOrientation.resize(inputLength); +} + +void TfLiteMotionPredictorBuffers::reset() { + std::fill(mInputR.begin(), mInputR.end(), 0); + std::fill(mInputPhi.begin(), mInputPhi.end(), 0); + std::fill(mInputPressure.begin(), mInputPressure.end(), 0); + std::fill(mInputTilt.begin(), mInputTilt.end(), 0); + std::fill(mInputOrientation.begin(), mInputOrientation.end(), 0); + mAxisFrom.reset(); + mAxisTo.reset(); +} + +void TfLiteMotionPredictorBuffers::copyTo(TfLiteMotionPredictorModel& model) const { + LOG_ALWAYS_FATAL_IF(mInputR.size() != model.inputLength(), + "Buffer length %zu doesn't match model input length %zu", mInputR.size(), + model.inputLength()); + LOG_ALWAYS_FATAL_IF(!isReady(), "Buffers are incomplete"); + + std::copy(mInputR.begin(), mInputR.end(), model.inputR().begin()); + std::copy(mInputPhi.begin(), mInputPhi.end(), model.inputPhi().begin()); + std::copy(mInputPressure.begin(), mInputPressure.end(), model.inputPressure().begin()); + std::copy(mInputTilt.begin(), mInputTilt.end(), model.inputTilt().begin()); + std::copy(mInputOrientation.begin(), mInputOrientation.end(), model.inputOrientation().begin()); +} + +void TfLiteMotionPredictorBuffers::pushSample(int64_t timestamp, + const TfLiteMotionPredictorSample sample) { + // Convert the sample (x, y) into polar (r, φ) based on a reference axis + // from the preceding two points (mAxisFrom/mAxisTo). + + mTimestamp = timestamp; + + if (!mAxisTo) { // First point. + mAxisTo = sample; + return; + } + + // Vector from the last point to the current sample point. + const TfLiteMotionPredictorSample::Point v = sample.position - mAxisTo->position; + + const float r = std::hypot(v.x, v.y); + float phi = 0; + float orientation = 0; + + // Ignore the sample if there is no movement. These samples can occur when there's change to a + // property other than the coordinates and pollute the input to the model. + if (r == 0) { + return; + } + + if (!mAxisFrom) { // Second point. + // We can only determine the distance from the first point, and not any + // angle. However, if the second point forms an axis, the orientation can + // be transformed relative to that axis. + const float axisPhi = std::atan2(v.y, v.x); + // A MotionEvent's orientation is measured clockwise from the vertical + // axis, but axisPhi is measured counter-clockwise from the horizontal + // axis. + orientation = M_PI_2 - sample.orientation - axisPhi; + } else { + const TfLiteMotionPredictorSample::Point axis = mAxisTo->position - mAxisFrom->position; + const float axisPhi = std::atan2(axis.y, axis.x); + phi = std::atan2(v.y, v.x) - axisPhi; + + if (std::hypot(axis.x, axis.y) > 0) { + // See note above. + orientation = M_PI_2 - sample.orientation - axisPhi; + } + } + + // Update the axis for the next point. + mAxisFrom = mAxisTo; + mAxisTo = sample; + + // Push the current sample onto the end of the input buffers. + mInputR.erase(mInputR.begin()); + mInputPhi.erase(mInputPhi.begin()); + mInputPressure.erase(mInputPressure.begin()); + mInputTilt.erase(mInputTilt.begin()); + mInputOrientation.erase(mInputOrientation.begin()); + + mInputR.push_back(r); + mInputPhi.push_back(phi); + mInputPressure.push_back(sample.pressure); + mInputTilt.push_back(sample.tilt); + mInputOrientation.push_back(orientation); +} + +std::unique_ptr TfLiteMotionPredictorModel::create( + const char* modelPath) { + std::ifstream f(modelPath, std::ios::binary); + LOG_ALWAYS_FATAL_IF(!f, "Could not read model from %s", modelPath); + + std::string data; + data.assign(std::istreambuf_iterator(f), std::istreambuf_iterator()); + + return std::unique_ptr( + new TfLiteMotionPredictorModel(std::move(data))); +} + +TfLiteMotionPredictorModel::TfLiteMotionPredictorModel(std::string model) + : mFlatBuffer(std::move(model)) { + mErrorReporter = std::make_unique(); + mModel = tflite::FlatBufferModel::VerifyAndBuildFromBuffer(mFlatBuffer.data(), + mFlatBuffer.length(), + /*extra_verifier=*/nullptr, + mErrorReporter.get()); + LOG_ALWAYS_FATAL_IF(!mModel); + + tflite::ops::builtin::BuiltinOpResolver resolver; + tflite::InterpreterBuilder builder(*mModel, resolver); + + if (builder(&mInterpreter) != kTfLiteOk || !mInterpreter) { + LOG_ALWAYS_FATAL("Failed to build interpreter"); + } + + mRunner = mInterpreter->GetSignatureRunner(SIGNATURE_KEY); + LOG_ALWAYS_FATAL_IF(!mRunner, "Failed to find runner for signature '%s'", SIGNATURE_KEY); + + allocateTensors(); +} + +void TfLiteMotionPredictorModel::allocateTensors() { + if (mRunner->AllocateTensors() != kTfLiteOk) { + LOG_ALWAYS_FATAL("Failed to allocate tensors"); + } + + attachInputTensors(); + attachOutputTensors(); + + checkTensor(mInputR); + checkTensor(mInputPhi); + checkTensor(mInputPressure); + checkTensor(mInputTilt); + checkTensor(mInputOrientation); + checkTensor(mOutputR); + checkTensor(mOutputPhi); + checkTensor(mOutputPressure); + + const auto checkInputTensorSize = [this](const TfLiteTensor* tensor) { + const size_t size = getTensorBuffer(tensor).size(); + LOG_ALWAYS_FATAL_IF(size != inputLength(), + "Tensor '%s' length %zu does not match input length %zu", tensor->name, + size, inputLength()); + }; + + checkInputTensorSize(mInputR); + checkInputTensorSize(mInputPhi); + checkInputTensorSize(mInputPressure); + checkInputTensorSize(mInputTilt); + checkInputTensorSize(mInputOrientation); +} + +void TfLiteMotionPredictorModel::attachInputTensors() { + mInputR = findInputTensor(INPUT_R, mRunner); + mInputPhi = findInputTensor(INPUT_PHI, mRunner); + mInputPressure = findInputTensor(INPUT_PRESSURE, mRunner); + mInputTilt = findInputTensor(INPUT_TILT, mRunner); + mInputOrientation = findInputTensor(INPUT_ORIENTATION, mRunner); +} + +void TfLiteMotionPredictorModel::attachOutputTensors() { + mOutputR = findOutputTensor(OUTPUT_R, mRunner); + mOutputPhi = findOutputTensor(OUTPUT_PHI, mRunner); + mOutputPressure = findOutputTensor(OUTPUT_PRESSURE, mRunner); +} + +bool TfLiteMotionPredictorModel::invoke() { + ATRACE_BEGIN("TfLiteMotionPredictorModel::invoke"); + TfLiteStatus result = mRunner->Invoke(); + ATRACE_END(); + + if (result != kTfLiteOk) { + return false; + } + + // Invoke() might reallocate tensors, so they need to be reattached. + attachInputTensors(); + attachOutputTensors(); + + if (outputR().size() != outputPhi().size() || outputR().size() != outputPressure().size()) { + LOG_ALWAYS_FATAL("Output size mismatch: (r: %zu, phi: %zu, pressure: %zu)", + outputR().size(), outputPhi().size(), outputPressure().size()); + } + + return true; +} + +size_t TfLiteMotionPredictorModel::inputLength() const { + return getTensorBuffer(mInputR).size(); +} + +std::span TfLiteMotionPredictorModel::inputR() { + return getTensorBuffer(mInputR); +} + +std::span TfLiteMotionPredictorModel::inputPhi() { + return getTensorBuffer(mInputPhi); +} + +std::span TfLiteMotionPredictorModel::inputPressure() { + return getTensorBuffer(mInputPressure); +} + +std::span TfLiteMotionPredictorModel::inputTilt() { + return getTensorBuffer(mInputTilt); +} + +std::span TfLiteMotionPredictorModel::inputOrientation() { + return getTensorBuffer(mInputOrientation); +} + +std::span TfLiteMotionPredictorModel::outputR() const { + return getTensorBuffer(mOutputR); +} + +std::span TfLiteMotionPredictorModel::outputPhi() const { + return getTensorBuffer(mOutputPhi); +} + +std::span TfLiteMotionPredictorModel::outputPressure() const { + return getTensorBuffer(mOutputPressure); +} + +} // namespace android diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index e2c08604ae..916a8f2b2a 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -10,6 +10,7 @@ package { cc_test { name: "libinput_tests", + cpp_std: "c++20", host_supported: true, srcs: [ "IdGenerator_test.cpp", @@ -18,12 +19,18 @@ cc_test { "InputEvent_test.cpp", "InputPublisherAndConsumer_test.cpp", "MotionPredictor_test.cpp", + "TfLiteMotionPredictor_test.cpp", "TouchResampling_test.cpp", "TouchVideoFrame_test.cpp", "VelocityTracker_test.cpp", "VerifiedInputEvent_test.cpp", ], + header_libs: [ + "flatbuffer_headers", + "tensorflow_headers", + ], static_libs: [ + "libgmock", "libgui_window_info_static", "libinput", "libui-types", @@ -32,6 +39,7 @@ cc_test { "-Wall", "-Wextra", "-Werror", + "-Wno-unused-parameter", ], shared_libs: [ "libbase", @@ -39,10 +47,14 @@ cc_test { "libcutils", "liblog", "libPlatformProperties", + "libtflite", "libutils", "libvintf", ], - data: ["data/*"], + data: [ + "data/*", + ":motion_predictor_model.fb", + ], test_options: { unit_test: true, }, diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp index d2b59a1ac6..ce87c8617e 100644 --- a/libs/input/tests/MotionPredictor_test.cpp +++ b/libs/input/tests/MotionPredictor_test.cpp @@ -14,17 +14,36 @@ * limitations under the License. */ +#include + +#include #include #include #include #include +using namespace std::literals::chrono_literals; + namespace android { +using ::testing::IsEmpty; +using ::testing::SizeIs; +using ::testing::UnorderedElementsAre; + +const char MODEL_PATH[] = +#if defined(__ANDROID__) + "/system/etc/motion_predictor_model.fb"; +#else + "motion_predictor_model.fb"; +#endif + constexpr int32_t DOWN = AMOTION_EVENT_ACTION_DOWN; constexpr int32_t MOVE = AMOTION_EVENT_ACTION_MOVE; +constexpr int32_t UP = AMOTION_EVENT_ACTION_UP; +constexpr nsecs_t NSEC_PER_MSEC = 1'000'000; -static MotionEvent getMotionEvent(int32_t action, float x, float y, nsecs_t eventTime) { +static MotionEvent getMotionEvent(int32_t action, float x, float y, + std::chrono::nanoseconds eventTime, int32_t deviceId = 0) { MotionEvent event; constexpr size_t pointerCount = 1; std::vector pointerProperties; @@ -33,6 +52,7 @@ static MotionEvent getMotionEvent(int32_t action, float x, float y, nsecs_t even PointerProperties properties; properties.clear(); properties.id = i; + properties.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; pointerProperties.push_back(properties); PointerCoords coords; coords.clear(); @@ -42,73 +62,93 @@ static MotionEvent getMotionEvent(int32_t action, float x, float y, nsecs_t even } ui::Transform identityTransform; - event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_STYLUS, - ADISPLAY_ID_DEFAULT, {0}, action, /*actionButton=*/0, /*flags=*/0, - AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0, - MotionClassification::NONE, identityTransform, /*xPrecision=*/0.1, + event.initialize(InputEvent::nextId(), deviceId, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT, {0}, + action, /*actionButton=*/0, /*flags=*/0, AMOTION_EVENT_EDGE_FLAG_NONE, + AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, identityTransform, + /*xPrecision=*/0.1, /*yPrecision=*/0.2, /*xCursorPosition=*/280, /*yCursorPosition=*/540, - identityTransform, /*downTime=*/100, eventTime, pointerCount, + identityTransform, /*downTime=*/100, eventTime.count(), pointerCount, pointerProperties.data(), pointerCoords.data()); return event; } -/** - * A linear motion should be predicted to be linear in the future - */ -TEST(MotionPredictorTest, LinearPrediction) { - MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, +TEST(MotionPredictorTest, IsPredictionAvailable) { + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH, []() { return true /*enable prediction*/; }); - - predictor.record(getMotionEvent(DOWN, 0, 1, 0)); - predictor.record(getMotionEvent(MOVE, 1, 3, 10)); - predictor.record(getMotionEvent(MOVE, 2, 5, 20)); - predictor.record(getMotionEvent(MOVE, 3, 7, 30)); - std::vector> predicted = predictor.predict(40); - ASSERT_EQ(1u, predicted.size()); - ASSERT_EQ(predicted[0]->getX(0), 4); - ASSERT_EQ(predicted[0]->getY(0), 9); + ASSERT_TRUE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_STYLUS)); + ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN)); } -/** - * A still motion should be predicted to remain still - */ -TEST(MotionPredictorTest, StationaryPrediction) { - MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, +TEST(MotionPredictorTest, Offset) { + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/1, MODEL_PATH, []() { return true /*enable prediction*/; }); - - predictor.record(getMotionEvent(DOWN, 0, 1, 0)); - predictor.record(getMotionEvent(MOVE, 0, 1, 10)); - predictor.record(getMotionEvent(MOVE, 0, 1, 20)); - predictor.record(getMotionEvent(MOVE, 0, 1, 30)); - std::vector> predicted = predictor.predict(40); + predictor.record(getMotionEvent(DOWN, 0, 1, 30ms)); + predictor.record(getMotionEvent(MOVE, 0, 2, 35ms)); + std::vector> predicted = predictor.predict(40 * NSEC_PER_MSEC); ASSERT_EQ(1u, predicted.size()); - ASSERT_EQ(predicted[0]->getX(0), 0); - ASSERT_EQ(predicted[0]->getY(0), 1); + ASSERT_GE(predicted[0]->getEventTime(), 41); } -TEST(MotionPredictorTest, IsPredictionAvailable) { - MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, +TEST(MotionPredictorTest, FollowsGesture) { + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH, []() { return true /*enable prediction*/; }); - ASSERT_TRUE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_STYLUS)); - ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN)); + + // MOVE without a DOWN is ignored. + predictor.record(getMotionEvent(MOVE, 1, 3, 10ms)); + EXPECT_THAT(predictor.predict(20 * NSEC_PER_MSEC), IsEmpty()); + + predictor.record(getMotionEvent(DOWN, 2, 5, 20ms)); + predictor.record(getMotionEvent(MOVE, 2, 7, 30ms)); + predictor.record(getMotionEvent(MOVE, 3, 9, 40ms)); + EXPECT_THAT(predictor.predict(50 * NSEC_PER_MSEC), SizeIs(1)); + + predictor.record(getMotionEvent(UP, 4, 11, 50ms)); + EXPECT_THAT(predictor.predict(20 * NSEC_PER_MSEC), IsEmpty()); } -TEST(MotionPredictorTest, Offset) { - MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/1, +TEST(MotionPredictorTest, MultipleDevicesTracked) { + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH, []() { return true /*enable prediction*/; }); - predictor.record(getMotionEvent(DOWN, 0, 1, 30)); - predictor.record(getMotionEvent(MOVE, 0, 1, 35)); - std::vector> predicted = predictor.predict(40); - ASSERT_EQ(1u, predicted.size()); - ASSERT_GE(predicted[0]->getEventTime(), 41); + + predictor.record(getMotionEvent(DOWN, 1, 3, 0ms, /*deviceId=*/0)); + predictor.record(getMotionEvent(MOVE, 1, 3, 10ms, /*deviceId=*/0)); + predictor.record(getMotionEvent(MOVE, 2, 5, 20ms, /*deviceId=*/0)); + predictor.record(getMotionEvent(MOVE, 3, 7, 30ms, /*deviceId=*/0)); + + predictor.record(getMotionEvent(DOWN, 100, 300, 0ms, /*deviceId=*/1)); + predictor.record(getMotionEvent(MOVE, 100, 300, 10ms, /*deviceId=*/1)); + predictor.record(getMotionEvent(MOVE, 200, 500, 20ms, /*deviceId=*/1)); + predictor.record(getMotionEvent(MOVE, 300, 700, 30ms, /*deviceId=*/1)); + + { + std::vector> predicted = predictor.predict(40 * NSEC_PER_MSEC); + ASSERT_EQ(2u, predicted.size()); + + // Order of the returned vector is not guaranteed. + std::vector seenDeviceIds; + for (const auto& prediction : predicted) { + seenDeviceIds.push_back(prediction->getDeviceId()); + } + EXPECT_THAT(seenDeviceIds, UnorderedElementsAre(0, 1)); + } + + // End the gesture for device 0. + predictor.record(getMotionEvent(UP, 4, 9, 40ms, /*deviceId=*/0)); + predictor.record(getMotionEvent(MOVE, 400, 900, 40ms, /*deviceId=*/1)); + + { + std::vector> predicted = predictor.predict(40 * NSEC_PER_MSEC); + ASSERT_EQ(1u, predicted.size()); + ASSERT_EQ(predicted[0]->getDeviceId(), 1); + } } TEST(MotionPredictorTest, FlagDisablesPrediction) { - MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, + MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0, MODEL_PATH, []() { return false /*disable prediction*/; }); - predictor.record(getMotionEvent(DOWN, 0, 1, 30)); - predictor.record(getMotionEvent(MOVE, 0, 1, 35)); - std::vector> predicted = predictor.predict(40); + predictor.record(getMotionEvent(DOWN, 0, 1, 30ms)); + predictor.record(getMotionEvent(MOVE, 0, 1, 35ms)); + std::vector> predicted = predictor.predict(40 * NSEC_PER_MSEC); ASSERT_EQ(0u, predicted.size()); ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_STYLUS)); ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN)); diff --git a/libs/input/tests/TfLiteMotionPredictor_test.cpp b/libs/input/tests/TfLiteMotionPredictor_test.cpp new file mode 100644 index 0000000000..454f2aaac4 --- /dev/null +++ b/libs/input/tests/TfLiteMotionPredictor_test.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace android { +namespace { + +using ::testing::Each; +using ::testing::ElementsAre; +using ::testing::FloatNear; + +std::string getModelPath() { +#if defined(__ANDROID__) + return "/system/etc/motion_predictor_model.fb"; +#else + return base::GetExecutableDirectory() + "/motion_predictor_model.fb"; +#endif +} + +TEST(TfLiteMotionPredictorTest, BuffersReadiness) { + TfLiteMotionPredictorBuffers buffers(/*inputLength=*/5); + ASSERT_FALSE(buffers.isReady()); + + buffers.pushSample(/*timestamp=*/0, {.position = {.x = 100, .y = 100}}); + ASSERT_FALSE(buffers.isReady()); + + buffers.pushSample(/*timestamp=*/1, {.position = {.x = 100, .y = 100}}); + ASSERT_FALSE(buffers.isReady()); + + // Two samples with distinct positions are required. + buffers.pushSample(/*timestamp=*/2, {.position = {.x = 100, .y = 110}}); + ASSERT_TRUE(buffers.isReady()); + + buffers.reset(); + ASSERT_FALSE(buffers.isReady()); +} + +TEST(TfLiteMotionPredictorTest, BuffersRecentData) { + TfLiteMotionPredictorBuffers buffers(/*inputLength=*/5); + + buffers.pushSample(/*timestamp=*/1, {.position = {.x = 100, .y = 200}}); + ASSERT_EQ(buffers.lastTimestamp(), 1); + + buffers.pushSample(/*timestamp=*/2, {.position = {.x = 150, .y = 250}}); + ASSERT_EQ(buffers.lastTimestamp(), 2); + ASSERT_TRUE(buffers.isReady()); + ASSERT_EQ(buffers.axisFrom().position.x, 100); + ASSERT_EQ(buffers.axisFrom().position.y, 200); + ASSERT_EQ(buffers.axisTo().position.x, 150); + ASSERT_EQ(buffers.axisTo().position.y, 250); + + // Position doesn't change, so neither do the axes. + buffers.pushSample(/*timestamp=*/3, {.position = {.x = 150, .y = 250}}); + ASSERT_EQ(buffers.lastTimestamp(), 3); + ASSERT_TRUE(buffers.isReady()); + ASSERT_EQ(buffers.axisFrom().position.x, 100); + ASSERT_EQ(buffers.axisFrom().position.y, 200); + ASSERT_EQ(buffers.axisTo().position.x, 150); + ASSERT_EQ(buffers.axisTo().position.y, 250); + + buffers.pushSample(/*timestamp=*/4, {.position = {.x = 180, .y = 280}}); + ASSERT_EQ(buffers.lastTimestamp(), 4); + ASSERT_TRUE(buffers.isReady()); + ASSERT_EQ(buffers.axisFrom().position.x, 150); + ASSERT_EQ(buffers.axisFrom().position.y, 250); + ASSERT_EQ(buffers.axisTo().position.x, 180); + ASSERT_EQ(buffers.axisTo().position.y, 280); +} + +TEST(TfLiteMotionPredictorTest, BuffersCopyTo) { + std::unique_ptr model = + TfLiteMotionPredictorModel::create(getModelPath().c_str()); + TfLiteMotionPredictorBuffers buffers(model->inputLength()); + + buffers.pushSample(/*timestamp=*/1, + {.position = {.x = 10, .y = 10}, + .pressure = 0, + .orientation = 0, + .tilt = 0.2}); + buffers.pushSample(/*timestamp=*/2, + {.position = {.x = 10, .y = 50}, + .pressure = 0.4, + .orientation = M_PI / 4, + .tilt = 0.3}); + buffers.pushSample(/*timestamp=*/3, + {.position = {.x = 30, .y = 50}, + .pressure = 0.5, + .orientation = -M_PI / 4, + .tilt = 0.4}); + buffers.pushSample(/*timestamp=*/3, + {.position = {.x = 30, .y = 60}, + .pressure = 0, + .orientation = 0, + .tilt = 0.5}); + buffers.copyTo(*model); + + const int zeroPadding = model->inputLength() - 3; + ASSERT_GE(zeroPadding, 0); + + EXPECT_THAT(model->inputR().subspan(0, zeroPadding), Each(0)); + EXPECT_THAT(model->inputPhi().subspan(0, zeroPadding), Each(0)); + EXPECT_THAT(model->inputPressure().subspan(0, zeroPadding), Each(0)); + EXPECT_THAT(model->inputTilt().subspan(0, zeroPadding), Each(0)); + EXPECT_THAT(model->inputOrientation().subspan(0, zeroPadding), Each(0)); + + EXPECT_THAT(model->inputR().subspan(zeroPadding), ElementsAre(40, 20, 10)); + EXPECT_THAT(model->inputPhi().subspan(zeroPadding), ElementsAre(0, -M_PI / 2, M_PI / 2)); + EXPECT_THAT(model->inputPressure().subspan(zeroPadding), ElementsAre(0.4, 0.5, 0)); + EXPECT_THAT(model->inputTilt().subspan(zeroPadding), ElementsAre(0.3, 0.4, 0.5)); + EXPECT_THAT(model->inputOrientation().subspan(zeroPadding), + ElementsAre(FloatNear(-M_PI / 4, 1e-5), FloatNear(M_PI / 4, 1e-5), + FloatNear(M_PI / 2, 1e-5))); +} + +TEST(TfLiteMotionPredictorTest, ModelInputOutputLength) { + std::unique_ptr model = + TfLiteMotionPredictorModel::create(getModelPath().c_str()); + ASSERT_GT(model->inputLength(), 0u); + + const int inputLength = model->inputLength(); + ASSERT_EQ(inputLength, model->inputR().size()); + ASSERT_EQ(inputLength, model->inputPhi().size()); + ASSERT_EQ(inputLength, model->inputPressure().size()); + ASSERT_EQ(inputLength, model->inputOrientation().size()); + ASSERT_EQ(inputLength, model->inputTilt().size()); + + ASSERT_TRUE(model->invoke()); + + ASSERT_EQ(model->outputR().size(), model->outputPhi().size()); + ASSERT_EQ(model->outputR().size(), model->outputPressure().size()); +} + +TEST(TfLiteMotionPredictorTest, ModelOutput) { + std::unique_ptr model = + TfLiteMotionPredictorModel::create(getModelPath().c_str()); + TfLiteMotionPredictorBuffers buffers(model->inputLength()); + + buffers.pushSample(/*timestamp=*/1, {.position = {.x = 100, .y = 200}, .pressure = 0.2}); + buffers.pushSample(/*timestamp=*/2, {.position = {.x = 150, .y = 250}, .pressure = 0.4}); + buffers.pushSample(/*timestamp=*/3, {.position = {.x = 180, .y = 280}, .pressure = 0.6}); + buffers.copyTo(*model); + + ASSERT_TRUE(model->invoke()); + + // The actual model output is implementation-defined, but it should at least be non-zero and + // non-NaN. + const auto is_valid = [](float value) { return !isnan(value) && value != 0; }; + ASSERT_TRUE(std::all_of(model->outputR().begin(), model->outputR().end(), is_valid)); + ASSERT_TRUE(std::all_of(model->outputPhi().begin(), model->outputPhi().end(), is_valid)); + ASSERT_TRUE( + std::all_of(model->outputPressure().begin(), model->outputPressure().end(), is_valid)); +} + +} // namespace +} // namespace android -- GitLab From 57a8ab471ea5557ac9c04d38a934c0b2630f0b51 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Thu, 26 Jan 2023 15:28:19 -0800 Subject: [PATCH 0774/1310] SF: don't trace SF timelines when there is no layer update Fixes: 239101875 Test: manual Change-Id: I024f6facff5d74a500fb5d917bd97ab5d1c3eba3 --- .../FrameTimeline/FrameTimeline.cpp | 6 ++++ .../tests/unittests/FrameTimelineTest.cpp | 36 +++++++++++++------ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp index 27a099cd1f..925f111dc6 100644 --- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp +++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp @@ -1095,6 +1095,12 @@ void FrameTimeline::DisplayFrame::traceActuals(pid_t surfaceFlingerPid, } void FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const { + if (mSurfaceFrames.empty()) { + // We don't want to trace display frames without any surface frames updates as this cannot + // be janky + return; + } + if (mToken == FrameTimelineInfo::INVALID_VSYNC_ID) { // DisplayFrame should not have an invalid token. ALOGE("Cannot trace DisplayFrame with invalid token"); diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp index f47ac6dc43..abd77894af 100644 --- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp +++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp @@ -44,6 +44,17 @@ using ProtoPredictionType = perfetto::protos::FrameTimelineEvent_PredictionType; namespace android::frametimeline { +static const std::string sLayerNameOne = "layer1"; +static const std::string sLayerNameTwo = "layer2"; + +constexpr const uid_t sUidOne = 0; +constexpr pid_t sPidOne = 10; +constexpr pid_t sPidTwo = 20; +constexpr int32_t sInputEventId = 5; +constexpr int32_t sLayerIdOne = 1; +constexpr int32_t sLayerIdTwo = 2; +constexpr GameMode sGameMode = GameMode::Unsupported; + class FrameTimelineTest : public testing::Test { public: FrameTimelineTest() { @@ -106,6 +117,14 @@ public: return packets; } + void addEmptySurfaceFrame() { + auto surfaceFrame = + mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ false, sGameMode); + mFrameTimeline->addSurfaceFrame(std::move(surfaceFrame)); + } + void addEmptyDisplayFrame() { auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); // Trigger a flushPresentFence by calling setSfPresent for the next frame @@ -168,17 +187,6 @@ public: kStartThreshold}; }; -static const std::string sLayerNameOne = "layer1"; -static const std::string sLayerNameTwo = "layer2"; - -constexpr const uid_t sUidOne = 0; -constexpr pid_t sPidOne = 10; -constexpr pid_t sPidTwo = 20; -constexpr int32_t sInputEventId = 5; -constexpr int32_t sLayerIdOne = 1; -constexpr int32_t sLayerIdTwo = 2; -constexpr GameMode sGameMode = GameMode::Unsupported; - TEST_F(FrameTimelineTest, tokenManagerRemovesStalePredictions) { int64_t token1 = mTokenManager->generateTokenForPredictions({0, 0, 0}); EXPECT_EQ(getPredictions().size(), 1u); @@ -1054,6 +1062,9 @@ TEST_F(FrameTimelineTest, traceDisplayFrame_emitsValidTracePacket) { auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); tracingSession->StartBlocking(); + + // Add an empty surface frame so that display frame would get traced. + addEmptySurfaceFrame(); int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({10, 30, 30}); // Set up the display frame @@ -1135,6 +1146,9 @@ TEST_F(FrameTimelineTest, traceDisplayFrame_predictionExpiredDoesNotTraceExpecte // Flush the token so that it would expire flushTokens(); + // Add an empty surface frame so that display frame would get traced. + addEmptySurfaceFrame(); + // Set up the display frame mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, Fps::fromPeriodNsecs(11)); mFrameTimeline->setSfPresent(26, presentFence1); -- GitLab From f886dec0ab696a011b433b215206b5f243121789 Mon Sep 17 00:00:00 2001 From: Sam Dubey Date: Fri, 27 Jan 2023 13:28:19 +0000 Subject: [PATCH 0775/1310] Revert "Remove filterNonTouchAsIs function" This reverts commit 719f50602d24d9b180762ac8687d4b643f657566. Reason for revert: Testing breakages in b/266911763 Change-Id: Id03395f06fc1fca7a290978b18ebbda3b234124d --- .../dispatcher/InputDispatcher.cpp | 18 +++++++++++------- .../inputflinger/dispatcher/TouchState.cpp | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index a97fda0440..dc9f02ad5d 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2441,14 +2441,15 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } // Update dispatching for hover enter and exit. - std::vector hoveringWindows = - getHoveringWindowsLocked(oldState, tempTouchState, entry); - for (const TouchedWindow& touchedWindow : hoveringWindows) { - addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, - touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget, - targets); + { + std::vector hoveringWindows = + getHoveringWindowsLocked(oldState, tempTouchState, entry); + for (const TouchedWindow& touchedWindow : hoveringWindows) { + addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, + touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget, + targets); + } } - // Ensure that we have at least one foreground window or at least one window that cannot be a // foreground target. If we only have windows that are not receiving foreground touches (e.g. we // only have windows getting ACTION_OUTSIDE), then drop the event, because there is no window @@ -2514,6 +2515,9 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } outInjectionResult = InputEventInjectionResult::SUCCEEDED; + // Drop the outside or hover touch windows since we will not care about them + // in the next iteration. + tempTouchState.filterNonAsIsTouchWindows(); // Update final pieces of touch state if the injector had permission. if (switchedDevice) { diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index 6cfb915e3e..ad37d025ca 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -112,6 +112,20 @@ void TouchState::removeWindowByToken(const sp& token) { } } +void TouchState::filterNonAsIsTouchWindows() { + for (size_t i = 0; i < windows.size();) { + TouchedWindow& window = windows[i]; + if (window.targetFlags.any(InputTarget::Flags::DISPATCH_AS_IS | + InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER)) { + window.targetFlags.clear(InputTarget::DISPATCH_MASK); + window.targetFlags |= InputTarget::Flags::DISPATCH_AS_IS; + i += 1; + } else { + windows.erase(windows.begin() + i); + } + } +} + void TouchState::cancelPointersForWindowsExcept(const BitSet32 pointerIds, const sp& token) { if (pointerIds.isEmpty()) return; -- GitLab From 50ff1299491bfbe256724dda6377d0ed05f21fc9 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Fri, 27 Jan 2023 18:03:43 +0000 Subject: [PATCH 0776/1310] Revert "Update EXIF" This reverts: commit 0daf5f8e9073c54438d1045d6da70d9bbd7110a0, commit Ie4c3632c03ac34867cb9e4f50fb782578ad8c8da, commit I3d8c4a3fe3cff63f8b57277511aa801b9dd1b75d. Reason for revert: POR has changed bug: b/264715926 test: libjpegrecoverymap_test Change-Id: I88d2c2c3cabb76bddf23622a695b01c21381b34a --- .../include/jpegrecoverymap/jpegdecoder.h | 6 +- .../include/jpegrecoverymap/recoverymap.h | 12 +- .../jpegrecoverymap/recoverymaputils.h | 78 ------- libs/jpegrecoverymap/jpegdecoder.cpp | 54 ----- libs/jpegrecoverymap/recoverymap.cpp | 118 ++--------- libs/jpegrecoverymap/recoverymaputils.cpp | 191 +----------------- 6 files changed, 23 insertions(+), 436 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h index d0de48ff87..419b63d1de 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h @@ -47,7 +47,7 @@ public: */ void* getDecompressedImagePtr(); /* - * Returns the decompressed raw image buffer size. This mgit ethod must be called only after + * Returns the decompressed raw image buffer size. This method must be called only after * calling decompressImage(). */ size_t getDecompressedImageSize(); @@ -92,10 +92,6 @@ public: size_t* pWidth, size_t* pHeight, std::vector* iccData, std::vector* exifData); - /* - * Extracts EXIF package and updates the EXIF position / length without decoding the image. - */ - bool extractEXIF(const void* image, int length); private: bool decode(const void* image, int length, bool decodeToRGBA); diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h index ae15d247bd..696be1beb7 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -321,13 +321,17 @@ private: jr_compressed_ptr dest); /* - * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image - * and the compressed recovery map as input, and update the XMP metadata with the end of JPEG - * marker, and append the compressed gian map after the JPEG. + * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image, + * the compressed recovery map and optionally the exif package as inputs, and generate the XMP + * metadata, and finally append everything in the order of: + * SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, recovery map + * Note that EXIF package is only available for encoding API-0 and API-1. For encoding API-2 and + * API-3 this parameter is null, but the primary image in JPEG/R may still have EXIF as long as + * the input JPEG has EXIF. * * @param compressed_jpeg_image compressed 8-bit JPEG image * @param compress_recovery_map compressed recover map - * @param exif EXIF package + * @param (nullable) exif EXIF package * @param metadata JPEG/R metadata to encode in XMP of the jpeg * @param dest compressed JPEGR image * @return NO_ERROR if calculation succeeds, error code if error occurs. diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h index 8b2672fefa..c36a363a3c 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h @@ -45,7 +45,6 @@ const size_t EXIF_J_R_ENTRY_LENGTH = 12; * @return status of succeed or error code. */ status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position); -status_t Write(jr_exif_ptr destination, const void* source, size_t length, int &position); /* @@ -105,83 +104,6 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta * @return XMP metadata in type of string */ std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata); - -/* - * Add J R entry to existing exif, or create a new one with J R entry if it's null. - * EXIF syntax / change: - * ori: - * FF E1 - APP1 - * 01 FC - size of APP1 (to be calculated) - * ----------------------------------------------------- - * 45 78 69 66 00 00 - Exif\0\0 "Exif header" - * 49 49 2A 00 - TIFF Header - * 08 00 00 00 - offset to the IFD (image file directory) - * 06 00 - 6 entries - * 00 01 - Width Tag - * 03 00 - 'Short' type - * 01 00 00 00 - 1 component - * 00 05 00 00 - image with 0x500 - *-------------------------------------------------------------------------- - * new: - * FF E1 - APP1 - * 02 08 - new size, equals to old size + EXIF_J_R_ENTRY_LENGTH (12) - *----------------------------------------------------- - * 45 78 69 66 00 00 - Exif\0\0 "Exif header" - * 49 49 2A 00 - TIFF Header - * 08 00 00 00 - offset to the IFD (image file directory) - * 07 00 - +1 entry - * 4A 52 Custom ('J''R') Tag - * 07 00 - Unknown type - * 01 00 00 00 - 1 component - * 00 00 00 00 - empty data - * 00 01 - Width Tag - * 03 00 - 'Short' type - * 01 00 00 00 - 1 component - * 00 05 00 00 - image with 0x500 - */ -status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest); - -/* - * Modify offsets in EXIF in place. - * - * Each tag has the following structure: - * - * 00 01 - Tag - * 03 00 - data format - * 01 00 00 00 - number of components - * 00 05 00 00 - value - * - * The value means offset if - * (1) num_of_components * bytes_per_component > 4 bytes, or - * (2) tag == 0x8769 (ExifOffset). - * In both cases, the method will add EXIF_J_R_ENTRY_LENGTH (12) to the offsets. - */ -void updateExifOffsets(jr_exif_ptr exif, int pos, bool use_big_endian); -void updateExifOffsets(jr_exif_ptr exif, int pos, int num_entry, bool use_big_endian); - -/* - * Read data from the target position and target length in bytes; - */ -int readValue(uint8_t* data, int pos, int length, bool use_big_endian); - -/* - * Returns the length of data format in bytes - * - * ---------------------------------------------------------------------------------------------- - * | value | 1 | 2 | 3 | 4 | - * | format | unsigned byte | ascii strings | unsigned short | unsigned long | - * | bytes/component | 1 | 1 | 2 | 4 | - * ---------------------------------------------------------------------------------------------- - * | value | 5 | 6 | 7 | 8 | - * | format |unsigned rational| signed byte | undefined | signed short | - * | bytes/component | 8 | 1 | 1 | 2 | - * ---------------------------------------------------------------------------------------------- - * | value | 9 | 10 | 11 | 12 | - * | format | signed long | signed rational | single float | double float | - * | bytes/component | 4 | 8 | 4 | 8 | - * ---------------------------------------------------------------------------------------------- - */ -int findFormatLengthInBytes(int data_format); } #endif //ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoder.cpp index 6fbc6b0118..1bf609a54c 100644 --- a/libs/jpegrecoverymap/jpegdecoder.cpp +++ b/libs/jpegrecoverymap/jpegdecoder.cpp @@ -248,60 +248,6 @@ bool JpegDecoder::decode(const void* image, int length, bool decodeToRGBA) { return true; } -// TODO (Fyodor/Dichen): merge this method with getCompressedImageParameters() since they have -// similar functionality. Yet Dichen is not familiar with who's calling -// getCompressedImageParameters(), looks like it's used by some pending CLs. -bool JpegDecoder::extractEXIF(const void* image, int length) { - jpeg_decompress_struct cinfo; - jpegr_source_mgr mgr(static_cast(image), length); - jpegrerror_mgr myerr; - - cinfo.err = jpeg_std_error(&myerr.pub); - myerr.pub.error_exit = jpegrerror_exit; - - if (setjmp(myerr.setjmp_buffer)) { - jpeg_destroy_decompress(&cinfo); - return false; - } - jpeg_create_decompress(&cinfo); - - jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF); - jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); - jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); - - cinfo.src = &mgr; - jpeg_read_header(&cinfo, TRUE); - - bool exifAppears = false; - size_t pos = 2; // position after SOI - for (jpeg_marker_struct* marker = cinfo.marker_list; - marker && !exifAppears; - marker = marker->next) { - - pos += 4; - pos += marker->original_length; - - if (marker->marker != kAPP1Marker) { - continue; - } - - const unsigned int len = marker->data_length; - if (!exifAppears && - len > kExifIdCode.size() && - !strncmp(reinterpret_cast(marker->data), - kExifIdCode.c_str(), - kExifIdCode.size())) { - mEXIFBuffer.resize(len, 0); - memcpy(static_cast(mEXIFBuffer.data()), marker->data, len); - exifAppears = true; - mExifPos = pos - marker->original_length; - } - } - - jpeg_destroy_decompress(&cinfo); - return true; -} - bool JpegDecoder::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, bool isSingleChannel) { if (isSingleChannel) { diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index 22289de39c..30aa846bf8 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -175,18 +175,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpeg.data = jpeg_encoder.getCompressedImagePtr(); jpeg.length = jpeg_encoder.getCompressedImageSize(); - jpegr_exif_struct new_exif; - if (exif == nullptr || exif->data == nullptr) { - new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH; - } else { - new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH; - } - new_exif.data = new uint8_t[new_exif.length]; - std::unique_ptr new_exif_data; - new_exif_data.reset(reinterpret_cast(new_exif.data)); - JPEGR_CHECK(updateExif(exif, &new_exif)); - - JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest)); + JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, exif, &metadata, dest)); return NO_ERROR; } @@ -250,19 +239,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpeg.data = jpeg_encoder.getCompressedImagePtr(); jpeg.length = jpeg_encoder.getCompressedImageSize(); - jpegr_exif_struct new_exif; - if (exif == nullptr || exif->data == nullptr) { - new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH; - } else { - new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH; - } - - new_exif.data = new uint8_t[new_exif.length]; - std::unique_ptr new_exif_data; - new_exif_data.reset(reinterpret_cast(new_exif.data)); - JPEGR_CHECK(updateExif(exif, &new_exif)); - - JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest)); + JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, exif, &metadata, dest)); return NO_ERROR; } @@ -311,47 +288,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, compressed_map.data = compressed_map_data.get(); JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); - // Extract EXIF from JPEG without decoding. - JpegDecoder jpeg_decoder; - if (!jpeg_decoder.extractEXIF(compressed_jpeg_image->data, compressed_jpeg_image->length)) { - return ERROR_JPEGR_DECODE_ERROR; - } - - // Update exif. - jpegr_exif_struct exif; - exif.data = nullptr; - exif.length = 0; - jpegr_compressed_struct new_jpeg_image; - new_jpeg_image.data = nullptr; - new_jpeg_image.length = 0; - if (jpeg_decoder.getEXIFPos() != 0) { - copyJpegWithoutExif(&new_jpeg_image, - compressed_jpeg_image, - jpeg_decoder.getEXIFPos(), - jpeg_decoder.getEXIFSize()); - exif.data = jpeg_decoder.getEXIFPtr(); - exif.length = jpeg_decoder.getEXIFSize(); - } - - jpegr_exif_struct new_exif; - if (exif.data == nullptr) { - new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH; - } else { - new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH; - } - - new_exif.data = new uint8_t[new_exif.length]; - std::unique_ptr new_exif_data; - new_exif_data.reset(reinterpret_cast(new_exif.data)); - JPEGR_CHECK(updateExif(&exif, &new_exif)); - - JPEGR_CHECK(appendRecoveryMap( - new_jpeg_image.data == nullptr ? compressed_jpeg_image : &new_jpeg_image, - &compressed_map, &new_exif, &metadata, dest)); - - if (new_jpeg_image.data != nullptr) { - free(new_jpeg_image.data); - } + JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest)); return NO_ERROR; } @@ -384,33 +321,6 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight(); uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut; - // Update exif. - jpegr_exif_struct exif; - exif.data = nullptr; - exif.length = 0; - jpegr_compressed_struct new_jpeg_image; - new_jpeg_image.data = nullptr; - new_jpeg_image.length = 0; - if (jpeg_decoder.getEXIFPos() != 0) { - copyJpegWithoutExif(&new_jpeg_image, - compressed_jpeg_image, - jpeg_decoder.getEXIFPos(), - jpeg_decoder.getEXIFSize()); - exif.data = jpeg_decoder.getEXIFPtr(); - exif.length = jpeg_decoder.getEXIFSize(); - } - - jpegr_exif_struct new_exif; - if (exif.data == nullptr) { - new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH; - } else { - new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH; - } - new_exif.data = new uint8_t[new_exif.length]; - std::unique_ptr new_exif_data; - new_exif_data.reset(reinterpret_cast(new_exif.data)); - JPEGR_CHECK(updateExif(&exif, &new_exif)); - if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) { return ERROR_JPEGR_RESOLUTION_MISMATCH; @@ -435,13 +345,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, compressed_map.data = compressed_map_data.get(); JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); - JPEGR_CHECK(appendRecoveryMap( - new_jpeg_image.data == nullptr ? compressed_jpeg_image : &new_jpeg_image, - &compressed_map, &new_exif, &metadata, dest)); - - if (new_jpeg_image.data != nullptr) { - free(new_jpeg_image.data); - } + JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest)); return NO_ERROR; } @@ -967,15 +871,20 @@ status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_imag // JPEG/R structure: // SOI (ff d8) +// +// (Optional, only if EXIF package is from outside) // APP1 (ff e1) // 2 bytes of length (2 + length of exif package) // EXIF package (this includes the first two bytes representing the package length) -// APP1 (ff e1) +// +// (Required, XMP package) APP1 (ff e1) // 2 bytes of length (2 + 29 + length of xmp package) // name space ("http://ns.adobe.com/xap/1.0/\0") // xmp -// primary image (without the first two bytes (SOI) and without EXIF, may have other packages) -// secondary image (the recovery map) +// +// (Required) primary image (without the first two bytes (SOI), may have other packages) +// +// (Required) secondary image (the recovery map) // // Metadata versions we are using: // ECMA TR-98 for JFIF marker @@ -989,7 +898,6 @@ status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, jr_compressed_ptr dest) { if (compressed_jpeg_image == nullptr || compressed_recovery_map == nullptr - || exif == nullptr || metadata == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; @@ -1002,7 +910,7 @@ status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos)); // Write EXIF - { + if (exif != nullptr) { const int length = 2 + exif->length; const uint8_t lengthH = ((length >> 8) & 0xff); const uint8_t lengthL = (length & 0xff); diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/recoverymaputils.cpp index d5ad9a51c4..1617b8b97a 100644 --- a/libs/jpegrecoverymap/recoverymaputils.cpp +++ b/libs/jpegrecoverymap/recoverymaputils.cpp @@ -22,8 +22,6 @@ #include #include -#include - using namespace photos_editing_formats::image_io; using namespace std; @@ -55,12 +53,6 @@ status_t Write(jr_compressed_ptr destination, const void* source, size_t length, return NO_ERROR; } -status_t Write(jr_exif_ptr destination, const void* source, size_t length, int &position) { - memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length); - position += length; - return NO_ERROR; -} - // Extremely simple XML Handler - just searches for interesting elements class XMPXmlHandler : public XmlHandler { public: @@ -344,185 +336,4 @@ string generateXmp(int secondary_image_length, jpegr_metadata& metadata) { return ss.str(); } -/* - * Helper function - * Add J R entry to existing exif, or create a new one with J R entry if it's null. - */ -status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest) { - if (exif == nullptr || exif->data == nullptr) { - uint8_t data[PSEUDO_EXIF_PACKAGE_LENGTH] = { - 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, - 0x49, 0x49, 0x2A, 0x00, - 0x08, 0x00, 0x00, 0x00, - 0x01, 0x00, - 0x4A, 0x52, - 0x07, 0x00, - 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00}; - int pos = 0; - Write(dest, data, PSEUDO_EXIF_PACKAGE_LENGTH, pos); - return NO_ERROR; - } - - int num_entry = 0; - uint8_t num_entry_low = 0; - uint8_t num_entry_high = 0; - bool use_big_endian = false; - if (reinterpret_cast(exif->data)[3] == 0x4949) { - num_entry_low = reinterpret_cast(exif->data)[14]; - num_entry_high = reinterpret_cast(exif->data)[15]; - } else if (reinterpret_cast(exif->data)[3] == 0x4d4d) { - use_big_endian = true; - num_entry_high = reinterpret_cast(exif->data)[14]; - num_entry_low = reinterpret_cast(exif->data)[15]; - } else { - return ERROR_JPEGR_METADATA_ERROR; - } - num_entry = (num_entry_high << 8) | num_entry_low; - num_entry += 1; - num_entry_low = num_entry & 0xff; - num_entry_high = (num_entry >> 8) & 0xff; - - int pos = 0; - Write(dest, (uint8_t*)exif->data, 14, pos); - - if (use_big_endian) { - Write(dest, &num_entry_high, 1, pos); - Write(dest, &num_entry_low, 1, pos); - uint8_t data[EXIF_J_R_ENTRY_LENGTH] = { - 0x4A, 0x52, - 0x00, 0x07, - 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00}; - Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos); - } else { - Write(dest, &num_entry_low, 1, pos); - Write(dest, &num_entry_high, 1, pos); - uint8_t data[EXIF_J_R_ENTRY_LENGTH] = { - 0x4A, 0x52, - 0x07, 0x00, - 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00}; - Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos); - } - - Write(dest, (uint8_t*)exif->data + 16, exif->length - 16, pos); - - updateExifOffsets(dest, - 28, // start from the second tag, skip the "JR" tag - num_entry - 1, - use_big_endian); - - return NO_ERROR; -} - -/* - * Helper function - * Modify offsets in EXIF in place. - */ -void updateExifOffsets(jr_exif_ptr exif, int pos, bool use_big_endian) { - int num_entry = readValue(reinterpret_cast(exif->data), pos, 2, use_big_endian); - updateExifOffsets(exif, pos + 2, num_entry, use_big_endian); -} - -void updateExifOffsets(jr_exif_ptr exif, int pos, int num_entry, bool use_big_endian) { - for (int i = 0; i < num_entry; pos += EXIF_J_R_ENTRY_LENGTH, i++) { - int tag = readValue(reinterpret_cast(exif->data), pos, 2, use_big_endian); - bool need_to_update_offset = false; - if (tag == 0x8769) { - need_to_update_offset = true; - int sub_ifd_offset = - readValue(reinterpret_cast(exif->data), pos + 8, 4, use_big_endian) - + 6 // "Exif\0\0"; - + EXIF_J_R_ENTRY_LENGTH; - updateExifOffsets(exif, sub_ifd_offset, use_big_endian); - } else { - int data_format = - readValue(reinterpret_cast(exif->data), pos + 2, 2, use_big_endian); - int num_of_components = - readValue(reinterpret_cast(exif->data), pos + 4, 4, use_big_endian); - int data_length = findFormatLengthInBytes(data_format) * num_of_components; - if (data_length > 4) { - need_to_update_offset = true; - } - } - - if (!need_to_update_offset) { - continue; - } - - int offset = readValue(reinterpret_cast(exif->data), pos + 8, 4, use_big_endian); - - offset += EXIF_J_R_ENTRY_LENGTH; - - if (use_big_endian) { - reinterpret_cast(exif->data)[pos + 11] = offset & 0xff; - reinterpret_cast(exif->data)[pos + 10] = (offset >> 8) & 0xff; - reinterpret_cast(exif->data)[pos + 9] = (offset >> 16) & 0xff; - reinterpret_cast(exif->data)[pos + 8] = (offset >> 24) & 0xff; - } else { - reinterpret_cast(exif->data)[pos + 8] = offset & 0xff; - reinterpret_cast(exif->data)[pos + 9] = (offset >> 8) & 0xff; - reinterpret_cast(exif->data)[pos + 10] = (offset >> 16) & 0xff; - reinterpret_cast(exif->data)[pos + 11] = (offset >> 24) & 0xff; - } - } -} - -/* - * Read data from the target position and target length in bytes; - */ -int readValue(uint8_t* data, int pos, int length, bool use_big_endian) { - if (length == 2) { - if (use_big_endian) { - return (data[pos] << 8) | data[pos + 1]; - } else { - return (data[pos + 1] << 8) | data[pos]; - } - } else if (length == 4) { - if (use_big_endian) { - return (data[pos] << 24) | (data[pos + 1] << 16) | (data[pos + 2] << 8) | data[pos + 3]; - } else { - return (data[pos + 3] << 24) | (data[pos + 2] << 16) | (data[pos + 1] << 8) | data[pos]; - } - } else { - // Not support for now. - ALOGE("Error in readValue(): pos=%d, length=%d", pos, length); - return -1; - } -} - -/* - * Helper function - * Returns the length of data format in bytes - */ -int findFormatLengthInBytes(int data_format) { - switch (data_format) { - case 1: // unsigned byte - case 2: // ascii strings - case 6: // signed byte - case 7: // undefined - return 1; - - case 3: // unsigned short - case 8: // signed short - return 2; - - case 4: // unsigned long - case 9: // signed long - case 11: // single float - return 4; - - case 5: // unsigned rational - case 10: // signed rational - case 12: // double float - return 8; - - default: - // should not hit here - ALOGE("Error in findFormatLengthInBytes(): data_format=%d", data_format); - return -1; - } -} - -} // namespace android::recoverymap \ No newline at end of file +} // namespace android::recoverymap -- GitLab From a9c43763ce7d4ce6a48331acc0092711ed11afef Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Fri, 27 Jan 2023 19:10:25 +0000 Subject: [PATCH 0777/1310] SF: Support display mirroring with new frontend Bug: 238781169 Test: presubmit Change-Id: I569822bd1dd70e2b0b660042329abb27f263dc66 --- .../FrontEnd/LayerCreationArgs.h | 3 + .../FrontEnd/LayerHierarchy.cpp | 14 ++-- .../FrontEnd/LayerLifecycleManager.cpp | 76 +++++++++++++++++-- .../FrontEnd/LayerLifecycleManager.h | 5 ++ .../surfaceflinger/FrontEnd/LayerSnapshot.cpp | 5 ++ .../surfaceflinger/FrontEnd/LayerSnapshot.h | 1 + .../FrontEnd/LayerSnapshotBuilder.cpp | 4 + .../FrontEnd/RequestedLayerState.cpp | 19 ++++- .../FrontEnd/RequestedLayerState.h | 4 +- services/surfaceflinger/FrontEnd/SwapErase.h | 5 +- .../tests/unittests/LayerHierarchyTest.cpp | 65 ++++++++++++++++ .../tests/unittests/LayerHierarchyTest.h | 17 +++++ .../tests/unittests/LayerSnapshotTest.cpp | 23 ++++++ 13 files changed, 224 insertions(+), 17 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h index 7b5a157871..9d2aaab23d 100644 --- a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h +++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h @@ -18,10 +18,12 @@ #include #include +#include #include #include #include #include + constexpr uint32_t UNASSIGNED_LAYER_ID = std::numeric_limits::max(); namespace android { @@ -51,6 +53,7 @@ struct LayerCreationArgs { bool addToRoot = true; wp parentHandle = nullptr; wp mirrorLayerHandle = nullptr; + ui::LayerStack layerStackToMirror = ui::INVALID_LAYER_STACK; }; } // namespace android::surfaceflinger diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp index 678d36b5cf..a4fac1cf5c 100644 --- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp @@ -252,8 +252,8 @@ void LayerHierarchyBuilder::onLayerAdded(RequestedLayerState* layer) { attachToParent(hierarchy); attachToRelativeParent(hierarchy); - if (layer->mirrorId != UNASSIGNED_LAYER_ID) { - LayerHierarchy* mirror = getHierarchyFromId(layer->mirrorId); + for (uint32_t mirrorId : layer->mirrorIds) { + LayerHierarchy* mirror = getHierarchyFromId(mirrorId); hierarchy->addChild(mirror, LayerHierarchy::Variant::Mirror); } } @@ -292,14 +292,14 @@ void LayerHierarchyBuilder::updateMirrorLayer(RequestedLayerState* layer) { auto it = hierarchy->mChildren.begin(); while (it != hierarchy->mChildren.end()) { if (it->second == LayerHierarchy::Variant::Mirror) { - hierarchy->mChildren.erase(it); - break; + it = hierarchy->mChildren.erase(it); + } else { + it++; } - it++; } - if (layer->mirrorId != UNASSIGNED_LAYER_ID) { - hierarchy->addChild(getHierarchyFromId(layer->mirrorId), LayerHierarchy::Variant::Mirror); + for (uint32_t mirrorId : layer->mirrorIds) { + hierarchy->addChild(getHierarchyFromId(mirrorId), LayerHierarchy::Variant::Mirror); } } diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp index 5514c06c84..547a852b9e 100644 --- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp +++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp @@ -44,9 +44,28 @@ void LayerLifecycleManager::addLayers(std::vectorisRoot() && rootLayer->layerStack == layer.layerStackToMirror) { + layer.mirrorIds.emplace_back(rootLayer->id); + linkLayer(rootLayer->id, layer.id); + } + } + } else { + // Check if we are mirroring a single layer, and if so add it to the list of children + // to be mirrored. + layer.layerIdToMirror = linkLayer(layer.layerIdToMirror, layer.id); + if (layer.layerIdToMirror != UNASSIGNED_LAYER_ID) { + layer.mirrorIds.emplace_back(layer.layerIdToMirror); + } + } layer.touchCropId = linkLayer(layer.touchCropId, layer.id); - + if (layer.isRoot()) { + updateDisplayMirrorLayers(layer); + } mLayers.emplace_back(std::move(newLayer)); } } @@ -85,7 +104,14 @@ void LayerLifecycleManager::onHandlesDestroyed(const std::vector& dest layer.parentId = unlinkLayer(layer.parentId, layer.id); layer.relativeParentId = unlinkLayer(layer.relativeParentId, layer.id); - layer.mirrorId = unlinkLayer(layer.mirrorId, layer.id); + if (layer.layerStackToMirror != ui::INVALID_LAYER_STACK) { + layer.mirrorIds = unlinkLayers(layer.mirrorIds, layer.id); + swapErase(mDisplayMirroringLayers, layer.id); + } else { + layer.layerIdToMirror = unlinkLayer(layer.layerIdToMirror, layer.id); + layer.mirrorIds.clear(); + } + layer.touchCropId = unlinkLayer(layer.touchCropId, layer.id); auto& references = it->second.references; @@ -106,8 +132,8 @@ void LayerLifecycleManager::onHandlesDestroyed(const std::vector& dest if (linkedLayer->relativeParentId == layer.id) { linkedLayer->relativeParentId = UNASSIGNED_LAYER_ID; } - if (linkedLayer->mirrorId == layer.id) { - linkedLayer->mirrorId = UNASSIGNED_LAYER_ID; + if (swapErase(linkedLayer->mirrorIds, layer.id)) { + linkedLayer->changes |= RequestedLayerState::Changes::Mirror; } if (linkedLayer->touchCropId == layer.id) { linkedLayer->touchCropId = UNASSIGNED_LAYER_ID; @@ -200,6 +226,12 @@ void LayerLifecycleManager::applyTransactions(const std::vectorparentId) { unlinkLayer(oldParentId, layer->id); layer->parentId = linkLayer(layer->parentId, layer->id); + if (oldParentId == UNASSIGNED_LAYER_ID) { + updateDisplayMirrorLayers(*layer); + } + } + if (layer->what & layer_state_t::eLayerStackChanged && layer->isRoot()) { + updateDisplayMirrorLayers(*layer); } if (oldRelativeParentId != layer->relativeParentId) { unlinkLayer(oldRelativeParentId, layer->id); @@ -308,6 +340,14 @@ uint32_t LayerLifecycleManager::unlinkLayer(uint32_t layerId, uint32_t linkedLay return UNASSIGNED_LAYER_ID; } +std::vector LayerLifecycleManager::unlinkLayers(const std::vector& layerIds, + uint32_t linkedLayer) { + for (uint32_t layerId : layerIds) { + unlinkLayer(layerId, linkedLayer); + } + return {}; +} + std::string LayerLifecycleManager::References::getDebugString() const { std::string debugInfo = owner.name + "[" + std::to_string(owner.id) + "] refs:"; std::for_each(references.begin(), references.end(), @@ -329,4 +369,30 @@ void LayerLifecycleManager::fixRelativeZLoop(uint32_t relativeRootId) { mGlobalChanges |= RequestedLayerState::Changes::Hierarchy; } +// Some layers mirror the entire display stack. Since we don't have a single root layer per display +// we have to track all these layers and update what they mirror when the list of root layers +// on a display changes. This function walks through the list of display mirroring layers +// and updates its list of layers that its mirroring. This function should be called when a new +// root layer is added, removed or moved to another display. +void LayerLifecycleManager::updateDisplayMirrorLayers(RequestedLayerState& rootLayer) { + for (uint32_t mirrorLayerId : mDisplayMirroringLayers) { + RequestedLayerState* mirrorLayer = getLayerFromId(mirrorLayerId); + bool canBeMirrored = + rootLayer.isRoot() && rootLayer.layerStack == mirrorLayer->layerStackToMirror; + bool currentlyMirrored = + std::find(mirrorLayer->mirrorIds.begin(), mirrorLayer->mirrorIds.end(), + rootLayer.id) != mirrorLayer->mirrorIds.end(); + + if (canBeMirrored && !currentlyMirrored) { + mirrorLayer->mirrorIds.emplace_back(rootLayer.id); + linkLayer(rootLayer.id, mirrorLayer->id); + mirrorLayer->changes |= RequestedLayerState::Changes::Mirror; + } else if (!canBeMirrored && currentlyMirrored) { + swapErase(mirrorLayer->mirrorIds, rootLayer.id); + unlinkLayer(rootLayer.id, mirrorLayer->id); + mirrorLayer->changes |= RequestedLayerState::Changes::Mirror; + } + } +} + } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h index 63a7afca4e..25d27ee373 100644 --- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h +++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h @@ -80,6 +80,9 @@ private: std::vector* getLinkedLayersFromId(uint32_t); uint32_t linkLayer(uint32_t layerId, uint32_t layerToLink); uint32_t unlinkLayer(uint32_t layerId, uint32_t linkedLayer); + std::vector unlinkLayers(const std::vector& layerIds, uint32_t linkedLayer); + + void updateDisplayMirrorLayers(RequestedLayerState& rootLayer); struct References { // Lifetime tied to mLayers @@ -90,6 +93,8 @@ private: std::unordered_map mIdToLayer; // Listeners are invoked once changes are committed. std::vector> mListeners; + // Layers that mirror a display stack (see updateDisplayMirrorLayers) + std::vector mDisplayMirroringLayers; // Aggregation of changes since last commit. ftl::Flags mGlobalChanges; diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp index 3a0540c659..4e695653e7 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp @@ -112,6 +112,10 @@ bool LayerSnapshot::isHiddenByPolicy() const { } bool LayerSnapshot::getIsVisible() const { + if (handleSkipScreenshotFlag & outputFilter.toInternalDisplay) { + return false; + } + if (!hasSomethingToDraw()) { return false; } @@ -125,6 +129,7 @@ bool LayerSnapshot::getIsVisible() const { std::string LayerSnapshot::getIsVisibleReason() const { // not visible + if (handleSkipScreenshotFlag & outputFilter.toInternalDisplay) return "eLayerSkipScreenshot"; if (!hasSomethingToDraw()) return "!hasSomethingToDraw"; if (invalidTransform) return "invalidTransform"; if (isHiddenByPolicyFromParent) return "hidden by parent or layer flag"; diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h index 4512ade977..159410f3ea 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h @@ -84,6 +84,7 @@ struct LayerSnapshot : public compositionengine::LayerFECompositionState { gui::GameMode gameMode; scheduler::LayerInfo::FrameRate frameRate; ui::Transform::RotationFlags fixedTransformHint; + bool handleSkipScreenshotFlag = false; ChildState childState; static bool isOpaqueFormat(PixelFormat format); diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index cc265916f1..d0ffe613cc 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -714,6 +714,10 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a snapshot.fixedTransformHint = requested.fixedTransformHint != ui::Transform::ROT_INVALID ? requested.fixedTransformHint : parentSnapshot.fixedTransformHint; + // Display mirrors are always placed in a VirtualDisplay so we never want to capture layers + // marked as skip capture + snapshot.handleSkipScreenshotFlag = parentSnapshot.handleSkipScreenshotFlag || + (requested.layerStackToMirror != ui::INVALID_LAYER_STACK); } if (forceUpdate || requested.changes.get() != 0) { diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index b7fa4f0aa2..d63b126452 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -45,6 +45,16 @@ std::string layerIdToString(uint32_t layerId) { return layerId == UNASSIGNED_LAYER_ID ? "none" : std::to_string(layerId); } +std::string layerIdsToString(const std::vector& layerIds) { + std::stringstream stream; + stream << "{"; + for (auto layerId : layerIds) { + stream << layerId << ","; + } + stream << "}"; + return stream.str(); +} + } // namespace RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args) @@ -64,8 +74,11 @@ RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args) if (args.parentHandle != nullptr) { canBeRoot = false; } - mirrorId = LayerHandle::getLayerId(args.mirrorLayerHandle.promote()); - if (mirrorId != UNASSIGNED_LAYER_ID) { + layerIdToMirror = LayerHandle::getLayerId(args.mirrorLayerHandle.promote()); + if (layerIdToMirror != UNASSIGNED_LAYER_ID) { + changes |= RequestedLayerState::Changes::Mirror; + } else if (args.layerStackToMirror != ui::INVALID_LAYER_STACK) { + layerStackToMirror = args.layerStackToMirror; changes |= RequestedLayerState::Changes::Mirror; } @@ -309,7 +322,7 @@ std::string RequestedLayerState::getDebugString() const { return "[" + std::to_string(id) + "]" + name + ",parent=" + layerIdToString(parentId) + ",relativeParent=" + layerIdToString(relativeParentId) + ",isRelativeOf=" + std::to_string(isRelativeOf) + - ",mirrorId=" + layerIdToString(mirrorId) + + ",mirrorIds=" + layerIdsToString(mirrorIds) + ",handleAlive=" + std::to_string(handleAlive) + ",z=" + std::to_string(z); } diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h index 3a16531d80..6317b95709 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h @@ -97,13 +97,15 @@ struct RequestedLayerState : layer_state_t { std::shared_ptr externalTexture; gui::GameMode gameMode; scheduler::LayerInfo::FrameRate requestedFrameRate; + ui::LayerStack layerStackToMirror = ui::INVALID_LAYER_STACK; + uint32_t layerIdToMirror = UNASSIGNED_LAYER_ID; // book keeping states bool handleAlive = true; bool isRelativeOf = false; uint32_t parentId = UNASSIGNED_LAYER_ID; uint32_t relativeParentId = UNASSIGNED_LAYER_ID; - uint32_t mirrorId = UNASSIGNED_LAYER_ID; + std::vector mirrorIds{}; uint32_t touchCropId = UNASSIGNED_LAYER_ID; uint32_t bgColorLayerId = UNASSIGNED_LAYER_ID; ftl::Flags changes; diff --git a/services/surfaceflinger/FrontEnd/SwapErase.h b/services/surfaceflinger/FrontEnd/SwapErase.h index f672f998d9..0061c53e62 100644 --- a/services/surfaceflinger/FrontEnd/SwapErase.h +++ b/services/surfaceflinger/FrontEnd/SwapErase.h @@ -23,12 +23,15 @@ namespace android::surfaceflinger::frontend { // remove an element from a vector that avoids relocating all the elements after the one // that is erased. template -void swapErase(std::vector& vec, const T& value) { +bool swapErase(std::vector& vec, const T& value) { + bool found = false; auto it = std::find(vec.begin(), vec.end(), value); if (it != vec.end()) { std::iter_swap(it, vec.end() - 1); vec.erase(vec.end() - 1); + found = true; } + return found; } // Similar to swapErase(std::vector& vec, const T& value) but erases the first element diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp index 783df2821b..763426a9f8 100644 --- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp @@ -641,4 +641,69 @@ TEST_F(LayerHierarchyTest, zorderRespectsLayerStack) { EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); } +TEST_F(LayerHierarchyTest, canMirrorDisplay) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot); + createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0)); + setLayerStack(3, 1); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expected = {3, 1, 11, 111, 12, 121, 122, 1221, 13, 2, + 1, 11, 111, 12, 121, 122, 1221, 13, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected); + expected = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected); +} + +TEST_F(LayerHierarchyTest, mirrorNonExistingDisplay) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot); + createDisplayMirrorLayer(3, ui::LayerStack::fromValue(5)); + setLayerStack(3, 1); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expected = {3, 1, 11, 111, 12, 121, 122, 1221, 13, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected); + expected = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected); +} + +TEST_F(LayerHierarchyTest, newRootLayerIsMirrored) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot); + createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0)); + setLayerStack(3, 1); + UPDATE_AND_VERIFY(hierarchyBuilder); + + createRootLayer(4); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expected = {3, 1, 11, 111, 12, 121, 122, 1221, 13, 2, 4, + 1, 11, 111, 12, 121, 122, 1221, 13, 2, 4}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected); + expected = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected); +} + +TEST_F(LayerHierarchyTest, removedRootLayerIsNoLongerMirrored) { + LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); + setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot); + createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0)); + setLayerStack(3, 1); + UPDATE_AND_VERIFY(hierarchyBuilder); + + reparentLayer(1, UNASSIGNED_LAYER_ID); + destroyLayerHandle(1); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expected = {3, 2, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected); + expected = {11, 111, 12, 121, 122, 1221, 13}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected); +} + } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h index 1a822329bd..852cb91b2c 100644 --- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h +++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h @@ -60,6 +60,14 @@ protected: return args; } + LayerCreationArgs createDisplayMirrorArgs(uint32_t id, ui::LayerStack layerStack) { + LayerCreationArgs args(nullptr, nullptr, "testlayer", 0, {}, std::make_optional(id)); + args.addToRoot = true; + args.parentHandle.clear(); + args.layerStackToMirror = layerStack; + return args; + } + std::vector getTraversalPath(const LayerHierarchy& hierarchy) const { std::vector layerIds; hierarchy.traverse([&layerIds = layerIds](const LayerHierarchy& hierarchy, @@ -90,6 +98,15 @@ protected: mLifecycleManager.addLayers(std::move(layers)); } + void createDisplayMirrorLayer(uint32_t id, ui::LayerStack layerStack) { + sp handle = sp::make(id); + mHandles[id] = handle; + std::vector> layers; + layers.emplace_back(std::make_unique( + createDisplayMirrorArgs(/*id=*/id, layerStack))); + mLifecycleManager.addLayers(std::move(layers)); + } + virtual void createLayer(uint32_t id, uint32_t parentId) { sp handle = sp::make(id); mHandles[id] = handle; diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index e124342edc..aa6a14ed17 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -306,4 +306,27 @@ TEST_F(LayerSnapshotTest, NoLayerVoteForParentWithChildVotes) { EXPECT_EQ(getSnapshot(1)->frameRate.type, scheduler::LayerInfo::FrameRateCompatibility::NoVote); } +// Display Mirroring Tests +// tree with 3 levels of children +// ROOT (DISPLAY 0) +// ├── 1 +// │ ├── 11 +// │ │ └── 111 +// │ ├── 12 (has skip screenshot flag) +// │ │ ├── 121 +// │ │ └── 122 +// │ │ └── 1221 +// │ └── 13 +// └── 2 +// ROOT (DISPLAY 1) +// └── 3 (mirrors display 0) +TEST_F(LayerSnapshotTest, displayMirrorRespects) { + setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot); + createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0)); + setLayerStack(3, 1); + + std::vector expected = {3, 1, 11, 111, 13, 2, 1, 11, 111, 12, 121, 122, 1221, 13, 2}; + UPDATE_AND_VERIFY(mSnapshotBuilder, expected); +} + } // namespace android::surfaceflinger::frontend -- GitLab From 3886b07d585688859138d9e632bdd5438206ea12 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Wed, 25 Jan 2023 14:32:23 -0600 Subject: [PATCH 0778/1310] SF: Add TextureFilteringTest Bug: 238348307 Test: atest TextureFilteringTest Change-Id: I2aa3ac70845019b5b12a01fce93ee9446e0ac9a2 --- libs/gui/include/gui/DisplayCaptureArgs.h | 1 + services/surfaceflinger/tests/Android.bp | 1 + .../tests/TextureFiltering_test.cpp | 227 ++++++++++++++++++ 3 files changed, 229 insertions(+) create mode 100644 services/surfaceflinger/tests/TextureFiltering_test.cpp diff --git a/libs/gui/include/gui/DisplayCaptureArgs.h b/libs/gui/include/gui/DisplayCaptureArgs.h index ec884cfa8c..c826c17d2c 100644 --- a/libs/gui/include/gui/DisplayCaptureArgs.h +++ b/libs/gui/include/gui/DisplayCaptureArgs.h @@ -24,6 +24,7 @@ #include #include #include +#include namespace android::gui { diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp index de47330216..6d12aa7d4e 100644 --- a/services/surfaceflinger/tests/Android.bp +++ b/services/surfaceflinger/tests/Android.bp @@ -57,6 +57,7 @@ cc_test { "SetFrameRateOverride_test.cpp", "SetGeometry_test.cpp", "Stress_test.cpp", + "TextureFiltering_test.cpp", "VirtualDisplay_test.cpp", "WindowInfosListener_test.cpp", ], diff --git a/services/surfaceflinger/tests/TextureFiltering_test.cpp b/services/surfaceflinger/tests/TextureFiltering_test.cpp new file mode 100644 index 0000000000..e9b1fbb354 --- /dev/null +++ b/services/surfaceflinger/tests/TextureFiltering_test.cpp @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include "LayerTransactionTest.h" + +namespace android { + +bool operator==(const Color& left, const Color& right) { + return left.a == right.a && left.r == right.r && left.g == right.g && left.b == right.b; +} + +class TextureFilteringTest : public LayerTransactionTest { +protected: + virtual void SetUp() { + LayerTransactionTest::SetUp(); + + mParent = createLayer("test-parent", 100, 100, + gui::ISurfaceComposerClient::eFXSurfaceContainer); + mLayer = createLayer("test-child", 100, 100, + gui::ISurfaceComposerClient::eFXSurfaceBufferState, mParent.get()); + sp buffer = + sp::make(static_cast(100), static_cast(100), + PIXEL_FORMAT_RGBA_8888, 1u, + BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN | + BufferUsage::COMPOSER_OVERLAY | + BufferUsage::GPU_TEXTURE, + "test"); + TransactionUtils::fillGraphicBufferColor(buffer, Rect{0, 0, 50, 100}, Color::RED); + TransactionUtils::fillGraphicBufferColor(buffer, Rect{50, 0, 100, 100}, Color::BLUE); + Transaction() + .setBuffer(mLayer, buffer) + .setDataspace(mLayer, ui::Dataspace::V0_SRGB) + .setLayer(mLayer, INT32_MAX) + .apply(); + } + + virtual void TearDown() { LayerTransactionTest::TearDown(); } + + void expectFiltered(Rect redRect, Rect blueRect) { + // Check that at least some of the pixels in the red rectangle aren't solid red + int redPixels = 0; + for (int x = redRect.left; x < redRect.right; x++) { + for (int y = redRect.top; y < redRect.bottom; y++) { + redPixels += mCapture->getPixelColor(static_cast(x), + static_cast(y)) == Color::RED; + } + } + ASSERT_LT(redPixels, redRect.getWidth() * redRect.getHeight()); + + // Check that at least some of the pixels in the blue rectangle aren't solid blue + int bluePixels = 0; + for (int x = blueRect.left; x < blueRect.right; x++) { + for (int y = blueRect.top; y < blueRect.bottom; y++) { + bluePixels += mCapture->getPixelColor(static_cast(x), + static_cast(y)) == Color::BLUE; + } + } + ASSERT_LT(bluePixels, blueRect.getWidth() * blueRect.getHeight()); + } + + sp mParent; + sp mLayer; + std::unique_ptr mCapture; +}; + +TEST_F(TextureFilteringTest, NoFiltering) { + gui::DisplayCaptureArgs captureArgs; + captureArgs.displayToken = mDisplay; + captureArgs.width = 100; + captureArgs.height = 100; + captureArgs.sourceCrop = Rect{100, 100}; + ScreenCapture::captureDisplay(&mCapture, captureArgs); + + mCapture->expectColor(Rect{0, 0, 50, 100}, Color::RED); + mCapture->expectColor(Rect{50, 0, 100, 100}, Color::BLUE); +} + +TEST_F(TextureFilteringTest, BufferCropNoFiltering) { + Transaction().setBufferCrop(mLayer, Rect{0, 0, 100, 100}).apply(); + + gui::DisplayCaptureArgs captureArgs; + captureArgs.displayToken = mDisplay; + captureArgs.width = 100; + captureArgs.height = 100; + captureArgs.sourceCrop = Rect{0, 0, 100, 100}; + ScreenCapture::captureDisplay(&mCapture, captureArgs); + + mCapture->expectColor(Rect{0, 0, 50, 100}, Color::RED); + mCapture->expectColor(Rect{50, 0, 100, 100}, Color::BLUE); +} + +// Expect filtering because the buffer is stretched to the layer's bounds. +TEST_F(TextureFilteringTest, BufferCropIsFiltered) { + Transaction().setBufferCrop(mLayer, Rect{25, 25, 75, 75}).apply(); + + gui::DisplayCaptureArgs captureArgs; + captureArgs.displayToken = mDisplay; + captureArgs.width = 100; + captureArgs.height = 100; + captureArgs.sourceCrop = Rect{0, 0, 100, 100}; + ScreenCapture::captureDisplay(&mCapture, captureArgs); + + expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100}); +} + +// Expect filtering because the output source crop is stretched to the output buffer's size. +TEST_F(TextureFilteringTest, OutputSourceCropIsFiltered) { + gui::DisplayCaptureArgs captureArgs; + captureArgs.displayToken = mDisplay; + captureArgs.width = 100; + captureArgs.height = 100; + captureArgs.sourceCrop = Rect{25, 25, 75, 75}; + ScreenCapture::captureDisplay(&mCapture, captureArgs); + + expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100}); +} + +// Expect filtering because the layer crop and output source crop are stretched to the output +// buffer's size. +TEST_F(TextureFilteringTest, LayerCropOutputSourceCropIsFiltered) { + Transaction().setCrop(mLayer, Rect{25, 25, 75, 75}).apply(); + + gui::DisplayCaptureArgs captureArgs; + captureArgs.displayToken = mDisplay; + captureArgs.width = 100; + captureArgs.height = 100; + captureArgs.sourceCrop = Rect{25, 25, 75, 75}; + ScreenCapture::captureDisplay(&mCapture, captureArgs); + + expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100}); +} + +// Expect filtering because the layer is scaled up. +TEST_F(TextureFilteringTest, LayerCaptureWithScalingIsFiltered) { + LayerCaptureArgs captureArgs; + captureArgs.layerHandle = mLayer->getHandle(); + captureArgs.frameScaleX = 2; + captureArgs.frameScaleY = 2; + ScreenCapture::captureLayers(&mCapture, captureArgs); + + expectFiltered({0, 0, 100, 200}, {100, 0, 200, 200}); +} + +// Expect no filtering because the output buffer's size matches the source crop. +TEST_F(TextureFilteringTest, LayerCaptureOutputSourceCropNoFiltering) { + LayerCaptureArgs captureArgs; + captureArgs.layerHandle = mLayer->getHandle(); + captureArgs.sourceCrop = Rect{25, 25, 75, 75}; + ScreenCapture::captureLayers(&mCapture, captureArgs); + + mCapture->expectColor(Rect{0, 0, 25, 50}, Color::RED); + mCapture->expectColor(Rect{25, 0, 50, 50}, Color::BLUE); +} + +// Expect no filtering because the output buffer's size matches the source crop (with a cropped +// layer). +TEST_F(TextureFilteringTest, LayerCaptureWithCropNoFiltering) { + Transaction().setCrop(mLayer, Rect{10, 10, 90, 90}).apply(); + + LayerCaptureArgs captureArgs; + captureArgs.layerHandle = mLayer->getHandle(); + captureArgs.sourceCrop = Rect{25, 25, 75, 75}; + ScreenCapture::captureLayers(&mCapture, captureArgs); + + mCapture->expectColor(Rect{0, 0, 25, 50}, Color::RED); + mCapture->expectColor(Rect{25, 0, 50, 50}, Color::BLUE); +} + +// Expect no filtering because the output source crop and output buffer are the same size. +TEST_F(TextureFilteringTest, OutputSourceCropDisplayFrameMatchNoFiltering) { + // Transaction().setCrop(mLayer, Rect{25, 25, 75, 75}).apply(); + + gui::DisplayCaptureArgs captureArgs; + captureArgs.displayToken = mDisplay; + captureArgs.width = 50; + captureArgs.height = 50; + captureArgs.sourceCrop = Rect{25, 25, 75, 75}; + ScreenCapture::captureDisplay(&mCapture, captureArgs); + + mCapture->expectColor(Rect{0, 0, 25, 50}, Color::RED); + mCapture->expectColor(Rect{25, 0, 50, 50}, Color::BLUE); +} + +// Expect no filtering because the layer crop shouldn't scale the layer. +TEST_F(TextureFilteringTest, LayerCropDisplayFrameMatchNoFiltering) { + Transaction().setCrop(mLayer, Rect{25, 25, 75, 75}).apply(); + + gui::DisplayCaptureArgs captureArgs; + captureArgs.displayToken = mDisplay; + ScreenCapture::captureDisplay(&mCapture, captureArgs); + + mCapture->expectColor(Rect{25, 25, 50, 75}, Color::RED); + mCapture->expectColor(Rect{50, 25, 75, 75}, Color::BLUE); +} + +// Expect no filtering because the parent layer crop shouldn't scale the layer. +TEST_F(TextureFilteringTest, ParentCropNoFiltering) { + Transaction().setCrop(mParent, Rect{25, 25, 75, 75}).apply(); + + gui::DisplayCaptureArgs captureArgs; + captureArgs.displayToken = mDisplay; + ScreenCapture::captureDisplay(&mCapture, captureArgs); + + mCapture->expectColor(Rect{25, 25, 50, 75}, Color::RED); + mCapture->expectColor(Rect{50, 25, 75, 75}, Color::BLUE); +} + +} // namespace android -- GitLab From a5992df3c82b16ede6d9cdacb76c84df4dcf05f3 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Fri, 27 Jan 2023 21:10:57 -0800 Subject: [PATCH 0779/1310] SF: small fix to 3f96592d3b985b258f488f5497dc02a7dc2eb5a8 Addressing a bug with that commit when the render frame rate range is smaller than the display refresh rate. Test: atest FrameRateOverrideHostTest Bug: 266444890 Change-Id: Ie51ade1c31e6929ba3c0ed47f1583fdb8edb1941 --- .../Scheduler/RefreshRateSelector.cpp | 25 +++++++++++++++++-- .../unittests/RefreshRateSelectorTest.cpp | 23 +++++++++++++++-- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index 1d27cfcece..c5b3e14047 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -929,17 +929,38 @@ auto RefreshRateSelector::rankFrameRates(std::optional anchorGroupOpt, RefreshRateOrder refreshRateOrder, std::optional preferredDisplayModeOpt) const -> FrameRateRanking { + using fps_approx_ops::operator<; const char* const whence = __func__; + + // find the highest frame rate for each display mode + ftl::SmallMap maxRenderRateForMode; + const bool ascending = (refreshRateOrder == RefreshRateOrder::Ascending); + if (ascending) { + // TODO(b/266481656): Once this bug is fixed, we can remove this workaround and actually + // use a lower frame rate when we want Ascending frame rates. + for (const auto& frameRateMode : mPrimaryFrameRates) { + if (anchorGroupOpt && frameRateMode.modePtr->getGroup() != anchorGroupOpt) { + continue; + } + + const auto [iter, _] = maxRenderRateForMode.try_emplace(frameRateMode.modePtr->getId(), + frameRateMode.fps); + if (iter->second < frameRateMode.fps) { + iter->second = frameRateMode.fps; + } + } + } + std::deque ranking; const auto rankFrameRate = [&](const FrameRateMode& frameRateMode) REQUIRES(mLock) { - using fps_approx_ops::operator<; const auto& modePtr = frameRateMode.modePtr; if (anchorGroupOpt && modePtr->getGroup() != anchorGroupOpt) { return; } const bool ascending = (refreshRateOrder == RefreshRateOrder::Ascending); - if (ascending && frameRateMode.fps < getMinRefreshRateByPolicyLocked()->getFps()) { + const auto id = frameRateMode.modePtr->getId(); + if (ascending && frameRateMode.fps < *maxRenderRateForMode.get(id)) { // TODO(b/266481656): Once this bug is fixed, we can remove this workaround and actually // use a lower frame rate when we want Ascending frame rates. return; diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp index 5fddda5b35..f4d052d359 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp @@ -1165,7 +1165,7 @@ TEST_P(RefreshRateSelectorTest, getMinRefreshRatesByPolicy) { case Config::FrameRateOverride::AppOverride: return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}}; case Config::FrameRateOverride::Enabled: - return {{30_Hz, kMode30}, {45_Hz, kMode90}, {60_Hz, kMode60}, {90_Hz, kMode90}}; + return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}}; } }(); ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); @@ -1197,7 +1197,7 @@ TEST_P(RefreshRateSelectorTest, getMinRefreshRatesByPolicyOutsideTheGroup) { case Config::FrameRateOverride::AppOverride: return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}}; case Config::FrameRateOverride::Enabled: - return {{30_Hz, kMode30}, {45_Hz, kMode90}, {60_Hz, kMode60}, {90_Hz, kMode90}}; + return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}}; } }(); ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size()); @@ -2983,5 +2983,24 @@ TEST_P(RefreshRateSelectorTest, noLowerFrameRateOnMinVote) { EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers).frameRateMode); } +TEST_P(RefreshRateSelectorTest, frameRateIsCappedByPolicy) { + if (GetParam() != Config::FrameRateOverride::Enabled) { + return; + } + + auto selector = createSelector(kModes_60_90, kModeId60); + + constexpr FpsRanges kCappedAt30 = {{60_Hz, 90_Hz}, {30_Hz, 30_Hz}}; + + EXPECT_EQ(SetPolicyResult::Changed, + selector.setDisplayManagerPolicy( + {DisplayModeId(kModeId60), kCappedAt30, kCappedAt30})); + + std::vector layers = {{.weight = 1.f}}; + layers[0].name = "Test layer"; + layers[0].vote = LayerVoteType::Min; + EXPECT_FRAME_RATE_MODE(kMode60, 30_Hz, selector.getBestScoredFrameRate(layers).frameRateMode); +} + } // namespace } // namespace android::scheduler -- GitLab From 1ae72f16f429213c8c7f6c76f693c4fc356399f2 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Sun, 29 Jan 2023 12:55:30 -0800 Subject: [PATCH 0780/1310] Add FLAG_CANCELED for all ACTION_CANCEL events We are supposed to set FLAG_CANCELED for all ACTION_CANCEL events, but before this CL, some events didn't have this flag set. This became apparent when some of the tests were converted to use the new matcher syntax for consuming the events. In this CL, add the missing flag. Bug: 266705421 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: Ic17ff1e8d1480c4cbd5c812d9ce0a4ce14627b5e --- .../dispatcher/InputDispatcher.cpp | 3 + .../inputflinger/dispatcher/InputState.cpp | 17 +++-- .../tests/InputDispatcher_test.cpp | 63 ++++++++++++++----- 3 files changed, 63 insertions(+), 20 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index dc9f02ad5d..5d9ee5f61e 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -3165,6 +3165,9 @@ void InputDispatcher::enqueueDispatchEntryLocked(const sp& connectio } dispatchEntry->resolvedFlags = motionEntry.flags; + if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_CANCEL) { + dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_CANCELED; + } if (dispatchEntry->targetFlags.test(InputTarget::Flags::WINDOW_IS_OBSCURED)) { dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; } diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp index 563868d10e..93419a15d5 100644 --- a/services/inputflinger/dispatcher/InputState.cpp +++ b/services/inputflinger/dispatcher/InputState.cpp @@ -289,13 +289,16 @@ std::vector> InputState::synthesizeCancelationEvents if (options.pointerIds == std::nullopt) { const int32_t action = memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT : AMOTION_EVENT_ACTION_CANCEL; + int32_t flags = memento.flags; + if (action == AMOTION_EVENT_ACTION_CANCEL) { + flags |= AMOTION_EVENT_FLAG_CANCELED; + } events.push_back( std::make_unique(mIdGenerator.nextId(), currentTime, memento.deviceId, memento.source, memento.displayId, memento.policyFlags, - action, 0 /*actionButton*/, memento.flags, - AMETA_NONE, 0 /*buttonState*/, - MotionClassification::NONE, + action, 0 /*actionButton*/, flags, AMETA_NONE, + 0 /*buttonState*/, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision, memento.yPrecision, memento.xCursorPosition, @@ -388,11 +391,15 @@ std::vector> InputState::synthesizeCancelationEvent if (canceledPointerIndices.size() == memento.pointerCount) { const int32_t action = memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT : AMOTION_EVENT_ACTION_CANCEL; + int32_t flags = memento.flags; + if (action == AMOTION_EVENT_ACTION_CANCEL) { + flags |= AMOTION_EVENT_FLAG_CANCELED; + } events.push_back( std::make_unique(mIdGenerator.nextId(), currentTime, memento.deviceId, memento.source, memento.displayId, memento.policyFlags, action, 0 /*actionButton*/, - memento.flags, AMETA_NONE, 0 /*buttonState*/, + flags, AMETA_NONE, 0 /*buttonState*/, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision, memento.yPrecision, memento.xCursorPosition, @@ -400,7 +407,7 @@ std::vector> InputState::synthesizeCancelationEvent memento.pointerCount, memento.pointerProperties, memento.pointerCoords)); } else { - // If we aren't canceling all pointers, we need to generated ACTION_POINTER_UP with + // If we aren't canceling all pointers, we need to generate ACTION_POINTER_UP with // FLAG_CANCELED for each of the canceled pointers. For each event, we must remove the // previously canceled pointers from PointerProperties and PointerCoords, and update // pointerCount appropriately. For convenience, sort the canceled pointer indices so that we diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 3abe43a69f..dfe3d16e75 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -58,6 +58,7 @@ static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; static constexpr int32_t SECOND_DISPLAY_ID = 1; static constexpr int32_t ACTION_OUTSIDE = AMOTION_EVENT_ACTION_OUTSIDE; +static constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL; static constexpr int32_t POINTER_1_DOWN = AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); static constexpr int32_t POINTER_2_DOWN = @@ -139,12 +140,20 @@ MATCHER_P(WithDownTime, downTime, "InputEvent with specified downTime") { return arg.getDownTime() == downTime; } +MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") { + return arg.getDisplayId() == displayId; +} + MATCHER_P(WithSource, source, "InputEvent with specified source") { *result_listener << "expected source " << inputEventSourceToString(source) << ", but got " << inputEventSourceToString(arg.getSource()); return arg.getSource() == source; } +MATCHER_P(WithFlags, flags, "InputEvent with specified flags") { + return arg.getFlags() == flags; +} + MATCHER_P2(WithCoords, x, y, "MotionEvent with specified coordinates") { if (arg.getPointerCount() != 1) { *result_listener << "Expected 1 pointer, got " << arg.getPointerCount(); @@ -944,6 +953,28 @@ public: } } + MotionEvent* consumeMotion() { + InputEvent* event = consume(); + + if (event == nullptr) { + ADD_FAILURE() << mName << ": expected a MotionEvent, but didn't get one."; + return nullptr; + } + + if (event->getType() != AINPUT_EVENT_TYPE_MOTION) { + ADD_FAILURE() << mName << " expected a MotionEvent, got " + << inputEventTypeToString(event->getType()) << " event"; + return nullptr; + } + return static_cast(event); + } + + void consumeMotionEvent(const ::testing::Matcher& matcher) { + MotionEvent* motionEvent = consumeMotion(); + ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event, but expected " << matcher; + ASSERT_THAT(*motionEvent, matcher); + } + void consumeFocusEvent(bool hasFocus, bool inTouchMode) { InputEvent* event = consume(); ASSERT_NE(nullptr, event) << mName.c_str() @@ -1196,14 +1227,14 @@ public: void consumeMotionCancel(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { - consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, expectedDisplayId, - expectedFlags); + consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(expectedDisplayId), + WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED))); } void consumeMotionMove(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { - consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE, expectedDisplayId, - expectedFlags); + consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithDisplayId(expectedDisplayId), WithFlags(expectedFlags))); } void consumeMotionDown(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, @@ -2546,8 +2577,8 @@ TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsMotionStream) { // on the app side. NotifyDeviceResetArgs args(10 /*id*/, 20 /*eventTime*/, DEVICE_ID); mDispatcher->notifyDeviceReset(&args); - window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, ADISPLAY_ID_DEFAULT, - 0 /*expectedFlags*/); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); } TEST_F(InputDispatcherTest, InterceptKeyByPolicy) { @@ -3544,8 +3575,10 @@ public: } void consumeMotionCancel(int32_t expectedDisplayId, int32_t expectedFlags = 0) { - mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, - expectedDisplayId, expectedFlags); + mInputReceiver->consumeMotionEvent( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL), + WithDisplayId(expectedDisplayId), + WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED))); } void consumeMotionPointerDown(int32_t pointerIdx) { @@ -5109,8 +5142,8 @@ TEST_F(InputDispatcherSingleWindowAnr, OnPointerDown_BasicAnr) { mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow); mWindow->finishEvent(*sequenceNum); - mWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, - ADISPLAY_ID_DEFAULT, 0 /*flags*/); + mWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid()); } @@ -5277,8 +5310,8 @@ TEST_F(InputDispatcherSingleWindowAnr, SpyWindowAnr) { mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, spy); spy->finishEvent(*sequenceNum); - spy->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, ADISPLAY_ID_DEFAULT, - 0 /*flags*/); + spy->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertNotifyWindowResponsiveWasCalled(spy->getToken(), mWindow->getPid()); } @@ -5400,8 +5433,8 @@ TEST_F(InputDispatcherSingleWindowAnr, Policy_DoesNotGetDuplicateAnr) { mFakePolicy->assertNotifyAnrWasNotCalled(); // When the ANR happened, dispatcher should abort the current event stream via ACTION_CANCEL mWindow->consumeMotionDown(); - mWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, - ADISPLAY_ID_DEFAULT, 0 /*flags*/); + mWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); mWindow->assertNoEvents(); mDispatcher->waitForIdle(); mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid()); @@ -7768,7 +7801,7 @@ TEST_F(InputDispatcherPilferPointersTest, ContinuesToReceiveGestureAfterPilfer) ASSERT_EQ(InputEventInjectionResult::FAILED, injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) - << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + << "Inject motion event should return InputEventInjectionResult::FAILED"; spy->assertNoEvents(); window->assertNoEvents(); -- GitLab From 63f127947bcb0275d2c75fdb25fe7ba8f30fecbc Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Sat, 21 Jan 2023 16:58:22 -0500 Subject: [PATCH 0781/1310] SF: Bundle ftl::Flags for CompositionCoverage ...as they will later be passed to Scheduler via ICompositor. For now, extract ICompositor to its own header, alongside CompositionCoverage. Bug: 241285475 Bug: 241285191 Test: Frame misses are still traced. Change-Id: I1562893c9357a02aa42516e775700065b9b17e4b --- .../surfaceflinger/Scheduler/MessageQueue.cpp | 4 +- .../surfaceflinger/Scheduler/MessageQueue.h | 10 +-- .../surfaceflinger/Scheduler/Scheduler.cpp | 2 + .../scheduler/interface/CompositionCoverage.h | 37 +++++++++++ .../include/scheduler/interface/ICompositor.h | 43 +++++++++++++ services/surfaceflinger/SurfaceFlinger.cpp | 64 +++++++++++-------- services/surfaceflinger/SurfaceFlinger.h | 28 ++------ .../tests/unittests/TestableScheduler.h | 3 +- 8 files changed, 131 insertions(+), 60 deletions(-) create mode 100644 services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h create mode 100644 services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp index 9b044977be..dec8f59ee9 100644 --- a/services/surfaceflinger/Scheduler/MessageQueue.cpp +++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp @@ -17,12 +17,12 @@ #define ATRACE_TAG ATRACE_TAG_GRAPHICS #include - +#include #include #include #include -#include +#include #include "EventThread.h" #include "FrameTimeline.h" diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h index ad0ea72623..0d59337950 100644 --- a/services/surfaceflinger/Scheduler/MessageQueue.h +++ b/services/surfaceflinger/Scheduler/MessageQueue.h @@ -37,15 +37,7 @@ namespace android { -struct ICompositor { - virtual void configure() = 0; - virtual bool commit(TimePoint frameTime, VsyncId, TimePoint expectedVsyncTime) = 0; - virtual void composite(TimePoint frameTime, VsyncId) = 0; - virtual void sample() = 0; - -protected: - ~ICompositor() = default; -}; +struct ICompositor; template class Task : public MessageHandler { diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 1fc1519982..1e97f352d1 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -34,6 +34,8 @@ #include #include +#include + #include #include #include diff --git a/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h b/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h new file mode 100644 index 0000000000..3d0f1a9d33 --- /dev/null +++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h @@ -0,0 +1,37 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include + +namespace android { + +// Whether composition was covered by HWC and/or GPU. +enum class CompositionCoverage : std::uint8_t { + Hwc = 1 << 0, + + // Mutually exclusive: The composition either used the GPU, or reused a buffer that had been + // composited on the GPU. + Gpu = 1 << 1, + GpuReuse = 1 << 2, +}; + +using CompositionCoverageFlags = ftl::Flags; + +} // namespace android diff --git a/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h new file mode 100644 index 0000000000..cc419259ef --- /dev/null +++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h @@ -0,0 +1,43 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +namespace android { + +struct ICompositor { + // Configures physical displays, processing hotplug and/or mode setting via the Composer HAL. + virtual void configure() = 0; + + // Commits transactions for layers and displays. Returns whether any state has been invalidated, + // i.e. whether a frame should be composited for each display. + virtual bool commit(TimePoint frameTime, VsyncId, TimePoint expectedVsyncTime) = 0; + + // Composites a frame for each display. CompositionEngine performs GPU and/or HAL composition + // via RenderEngine and the Composer HAL, respectively. + virtual void composite(TimePoint frameTime, VsyncId) = 0; + + // Samples the composited frame via RegionSamplingThread. + virtual void sample() = 0; + +protected: + ~ICompositor() = default; +}; + +} // namespace android diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 7d0dc93cf9..0dc8b05993 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -408,15 +408,8 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI mDebugFlashDelay = base::GetUintProperty("debug.sf.showupdates"s, 0u); - // DDMS debugging deprecated (b/120782499) - property_get("debug.sf.ddms", value, "0"); - int debugDdms = atoi(value); - ALOGI_IF(debugDdms, "DDMS debugging not supported"); - - property_get("debug.sf.enable_gl_backpressure", value, "1"); - mPropagateBackpressureClientComposition = atoi(value); - ALOGI_IF(mPropagateBackpressureClientComposition, - "Enabling backpressure propagation for Client Composition"); + mBackpressureGpuComposition = base::GetBoolProperty("debug.sf.enable_gl_backpressure"s, true); + ALOGI_IF(mBackpressureGpuComposition, "Enabling backpressure for GPU composition"); property_get("ro.surface_flinger.supports_background_blur", value, "0"); bool supportsBlurs = atoi(value); @@ -2155,11 +2148,11 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe const Period vsyncPeriod = mScheduler->getVsyncSchedule().period(); const FenceTimePtr& previousPresentFence = getPreviousPresentFence(frameTime, vsyncPeriod); - // When Backpressure propagation is enabled we want to give a small grace period + // When backpressure propagation is enabled, we want to give a small grace period of 1ms // for the present fence to fire instead of just giving up on this frame to handle cases // where present fence is just about to get signaled. - const int graceTimeForPresentFenceMs = - (mPropagateBackpressureClientComposition || !mHadClientComposition) ? 1 : 0; + const int graceTimeForPresentFenceMs = static_cast( + mBackpressureGpuComposition || !mCompositionCoverage.test(CompositionCoverage::Gpu)); // Pending frames may trigger backpressure propagation. const TracedOrdinal framePending = {"PrevFramePending", @@ -2182,9 +2175,14 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe (lastScheduledPresentTime.ns() < previousPresentTime - frameMissedSlop))}; const TracedOrdinal hwcFrameMissed = {"PrevHwcFrameMissed", - mHadDeviceComposition && frameMissed}; + frameMissed && + mCompositionCoverage.test( + CompositionCoverage::Hwc)}; + const TracedOrdinal gpuFrameMissed = {"PrevGpuFrameMissed", - mHadClientComposition && frameMissed}; + frameMissed && + mCompositionCoverage.test( + CompositionCoverage::Gpu)}; if (frameMissed) { mFrameMissedCount++; @@ -2222,7 +2220,7 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe } if (framePending) { - if ((hwcFrameMissed && !gpuFrameMissed) || mPropagateBackpressureClientComposition) { + if (mBackpressureGpuComposition || (hwcFrameMissed && !gpuFrameMissed)) { scheduleCommit(FrameHint::kNone); return false; } @@ -2459,29 +2457,43 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) postComposition(presentTime); - const bool prevFrameHadClientComposition = mHadClientComposition; + const bool hadGpuComposited = mCompositionCoverage.test(CompositionCoverage::Gpu); + mCompositionCoverage.clear(); - mHadClientComposition = mHadDeviceComposition = mReusedClientComposition = false; TimeStats::ClientCompositionRecord clientCompositionRecord; for (const auto& [_, display] : displays) { const auto& state = display->getCompositionDisplay()->getState(); - mHadClientComposition |= state.usesClientComposition && !state.reusedClientComposition; - mHadDeviceComposition |= state.usesDeviceComposition; - mReusedClientComposition |= state.reusedClientComposition; + + if (state.usesDeviceComposition) { + mCompositionCoverage |= CompositionCoverage::Hwc; + } + + if (state.reusedClientComposition) { + mCompositionCoverage |= CompositionCoverage::GpuReuse; + } else if (state.usesClientComposition) { + mCompositionCoverage |= CompositionCoverage::Gpu; + } + clientCompositionRecord.predicted |= (state.strategyPrediction != CompositionStrategyPredictionState::DISABLED); clientCompositionRecord.predictionSucceeded |= (state.strategyPrediction == CompositionStrategyPredictionState::SUCCESS); } - clientCompositionRecord.hadClientComposition = mHadClientComposition; - clientCompositionRecord.reused = mReusedClientComposition; - clientCompositionRecord.changed = prevFrameHadClientComposition != mHadClientComposition; + const bool hasGpuComposited = mCompositionCoverage.test(CompositionCoverage::Gpu); + + clientCompositionRecord.hadClientComposition = hasGpuComposited; + clientCompositionRecord.reused = mCompositionCoverage.test(CompositionCoverage::GpuReuse); + clientCompositionRecord.changed = hadGpuComposited != hasGpuComposited; + mTimeStats->pushCompositionStrategyState(clientCompositionRecord); - // TODO: b/160583065 Enable skip validation when SF caches all client composition layers - const bool usedGpuComposition = mHadClientComposition || mReusedClientComposition; - mScheduler->modulateVsync(&VsyncModulator::onDisplayRefresh, usedGpuComposition); + using namespace ftl::flag_operators; + + // TODO(b/160583065): Enable skip validation when SF caches all client composition layers. + const bool hasGpuUseOrReuse = + mCompositionCoverage.any(CompositionCoverage::Gpu | CompositionCoverage::GpuReuse); + mScheduler->modulateVsync(&VsyncModulator::onDisplayRefresh, hasGpuUseOrReuse); mLayersWithQueuedFrames.clear(); if (mLayerTracingEnabled && mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 207dfe2e96..ed09c3f2a8 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -57,6 +57,8 @@ #include #include #include +#include +#include #include #include "Display/DisplayMap.h" @@ -606,19 +608,9 @@ private: void onComposerHalVsyncIdle(hal::HWDisplayId) override; // ICompositor overrides: - - // Configures physical displays, processing hotplug and/or mode setting via the Composer HAL. void configure() override; - - // Commits transactions for layers and displays. Returns whether any state has been invalidated, - // i.e. whether a frame should be composited for each display. bool commit(TimePoint frameTime, VsyncId, TimePoint expectedVsyncTime) override; - - // Composites a frame for each display. CompositionEngine performs GPU and/or HAL composition - // via RenderEngine and the Composer HAL, respectively. void composite(TimePoint frameTime, VsyncId) override; - - // Samples the composited frame via RegionSamplingThread. void sample() override; // ISchedulerCallback overrides: @@ -1158,17 +1150,6 @@ private: // Tracks layers that need to update a display's dirty region. std::vector> mLayersPendingRefresh; - // True if in the previous frame at least one layer was composed via the GPU. - bool mHadClientComposition = false; - // True if in the previous frame at least one layer was composed via HW Composer. - // Note that it is possible for a frame to be composed via both client and device - // composition, for example in the case of overlays. - bool mHadDeviceComposition = false; - // True if in the previous frame, the client composition was skipped by reusing the buffer - // used in a previous composition. This can happed if the client composition requests - // did not change. - bool mReusedClientComposition = false; - BootStage mBootStage = BootStage::BOOTLOADER; struct HotplugEvent { @@ -1204,7 +1185,7 @@ private: std::atomic_bool mForceFullDamage = false; bool mLayerCachingEnabled = false; - bool mPropagateBackpressureClientComposition = false; + bool mBackpressureGpuComposition = false; LayerTracing mLayerTracing{*this}; bool mLayerTracingEnabled = false; @@ -1268,6 +1249,9 @@ private: std::atomic mNumTrustedPresentationListeners = 0; std::unique_ptr mCompositionEngine; + + CompositionCoverageFlags mCompositionCoverage; + // mMaxRenderTargetSize is only set once in init() so it doesn't need to be protected by // any mutex. size_t mMaxRenderTargetSize{1}; diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index 0cbfa63354..74885d8a78 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -16,11 +16,12 @@ #pragma once -#include #include #include #include +#include + #include "Scheduler/EventThread.h" #include "Scheduler/LayerHistory.h" #include "Scheduler/Scheduler.h" -- GitLab From 060f82bea87826bb126de5d20c79dfb4b2261bbd Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 27 Jan 2023 06:39:14 -0800 Subject: [PATCH 0782/1310] Keep track of pilfered pointer explicitly The pointers that are getting pilfered were not being tracked explicitly before. The behaviour relied on the window going away in order to reset the "isPilfering" value to "false". Now, however, some windows are getting persisted longer, because they might receive a hover gesture that doesn't end (like a mouse that someone stops using). Therefore, we need to explicitly keep track of which pointers are being pilfered. This is also in line with our eventual goal of explicit per-window pointer management. In the failing case, the windows was considered to be "pilfering". Since the gesture monitors were receiving all of the pointers, the pointers would no longer get dispatched to any windows underneath. With this CL, we maintain the existing intended behaviours of pilfering, while also expanding the support for multiple pilfered pointers. In the added test case, there is a mouse interaction first, which persists the gesture monitor window. Next, we pilfer a touch event, which sets the "isPilfering" flag to ON. And finally, we inject the new touch gesture. This gesture was (incorrectly) getting only sent to the gesture monitor, because it kept maintaining the "isPilfering" status. Bug: 266705421 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests --gtest_filter="*MouseAndTouchWithSpyWindows*" Change-Id: I6b72dc3e4de0e0ef09ccc469df0f703762c76fa6 --- .../dispatcher/InputDispatcher.cpp | 29 ++-- .../inputflinger/dispatcher/TouchState.cpp | 41 +++++- services/inputflinger/dispatcher/TouchState.h | 2 +- .../inputflinger/dispatcher/TouchedWindow.cpp | 4 +- .../inputflinger/dispatcher/TouchedWindow.h | 3 +- .../tests/InputDispatcher_test.cpp | 127 +++++++++++++++++- 6 files changed, 187 insertions(+), 19 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 5d9ee5f61e..0f3dc5c972 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2333,14 +2333,22 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } } - // If any existing window is pilfering pointers from newly added window, remove it - BitSet32 canceledPointers = BitSet32(0); - for (const TouchedWindow& window : tempTouchState.windows) { - if (window.isPilferingPointers) { - canceledPointers |= window.pointerIds; + // If a window is already pilfering some pointers, give it this new pointer as well and + // make it pilfering. This will prevent other non-spy windows from getting this pointer, + // which is a specific behaviour that we want. + const int32_t pointerId = entry.pointerProperties[pointerIndex].id; + for (TouchedWindow& touchedWindow : tempTouchState.windows) { + if (touchedWindow.pointerIds.hasBit(pointerId) && + touchedWindow.pilferedPointerIds.count() > 0) { + // This window is already pilfering some pointers, and this new pointer is also + // going to it. Therefore, take over this pointer and don't give it to anyone + // else. + touchedWindow.pilferedPointerIds.set(pointerId); } } - tempTouchState.cancelPointersForNonPilferingWindows(canceledPointers); + + // Restrict all pilfered pointers to the pilfering windows. + tempTouchState.cancelPointersForNonPilferingWindows(); } else { /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */ @@ -3942,8 +3950,8 @@ std::unique_ptr InputDispatcher::splitMotionEvent( if (action == AMOTION_EVENT_ACTION_DOWN) { LOG_ALWAYS_FATAL_IF(splitDownTime != originalMotionEntry.eventTime, "Split motion event has mismatching downTime and eventTime for " - "ACTION_DOWN, motionEntry=%s, splitDownTime=%" PRId64 "ms", - originalMotionEntry.getDescription().c_str(), ns2ms(splitDownTime)); + "ACTION_DOWN, motionEntry=%s, splitDownTime=%" PRId64, + originalMotionEntry.getDescription().c_str(), splitDownTime); } int32_t newId = mIdGenerator.nextId(); @@ -5778,7 +5786,10 @@ status_t InputDispatcher::pilferPointersLocked(const sp& token) { // Prevent the gesture from being sent to any other windows. // This only blocks relevant pointers to be sent to other windows - window.isPilferingPointers = true; + for (BitSet32 idBits(window.pointerIds); !idBits.isEmpty();) { + uint32_t id = idBits.clearFirstMarkedBit(); + window.pilferedPointerIds.set(id); + } state.cancelPointersForWindowsExcept(window.pointerIds, token); return OK; diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index ad37d025ca..e9c6ad5f0a 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -34,6 +34,7 @@ void TouchState::reset() { void TouchState::removeTouchedPointer(int32_t pointerId) { for (TouchedWindow& touchedWindow : windows) { touchedWindow.pointerIds.clearBit(pointerId); + touchedWindow.pilferedPointerIds.reset(pointerId); } } @@ -42,6 +43,7 @@ void TouchState::removeTouchedPointerFromWindow( for (TouchedWindow& touchedWindow : windows) { if (touchedWindow.windowHandle == windowHandle) { touchedWindow.pointerIds.clearBit(pointerId); + touchedWindow.pilferedPointerIds.reset(pointerId); return; } } @@ -137,11 +139,40 @@ void TouchState::cancelPointersForWindowsExcept(const BitSet32 pointerIds, std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.isEmpty(); }); } -void TouchState::cancelPointersForNonPilferingWindows(const BitSet32 pointerIds) { - if (pointerIds.isEmpty()) return; - std::for_each(windows.begin(), windows.end(), [&pointerIds](TouchedWindow& w) { - if (!w.isPilferingPointers) { - w.pointerIds &= BitSet32(~pointerIds.value); +/** + * For any pointer that's being pilfered, remove it from all of the other windows that currently + * aren't pilfering it. For example, if we determined that pointer 1 is going to both window A and + * window B, but window A is currently pilfering pointer 1, then pointer 1 should not go to window + * B. + */ +void TouchState::cancelPointersForNonPilferingWindows() { + // First, find all pointers that are being pilfered, across all windows + std::bitset allPilferedPointerIds; + std::for_each(windows.begin(), windows.end(), [&allPilferedPointerIds](const TouchedWindow& w) { + allPilferedPointerIds |= w.pilferedPointerIds; + }); + + // Optimization: most of the time, pilfering does not occur + if (allPilferedPointerIds.none()) return; + + // Now, remove all pointers from every window that's being pilfered by other windows. + // For example, if window A is pilfering pointer 1 (only), and window B is pilfering pointer 2 + // (only), the remove pointer 2 from window A and pointer 1 from window B. Usually, the set of + // pilfered pointers will be disjoint across all windows, but there's no reason to cause that + // limitation here. + std::for_each(windows.begin(), windows.end(), [&allPilferedPointerIds](TouchedWindow& w) { + std::bitset pilferedByOtherWindows = + w.pilferedPointerIds ^ allPilferedPointerIds; + // TODO(b/211379801) : convert pointerIds to use std::bitset, which would allow us to + // replace the loop below with a bitwise operation. Currently, the XOR operation above is + // redundant, but is done to make the code more explicit / easier to convert later. + for (std::size_t i = 0; i < pilferedByOtherWindows.size(); i++) { + if (pilferedByOtherWindows.test(i) && !w.pilferedPointerIds.test(i)) { + // Pointer is pilfered by other windows, but not by this one! Remove it from here. + // We could call 'removeTouchedPointerFromWindow' here, but it's faster to directly + // manipulate it. + w.pointerIds.clearBit(i); + } } }); std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.isEmpty(); }); diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index 4025db8a08..0092f1d8a0 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -59,7 +59,7 @@ struct TouchState { void cancelPointersForWindowsExcept(const BitSet32 pointerIds, const sp& token); // Cancel pointers for current set of non-pilfering windows i.e. windows with isPilferingWindow // set to false. - void cancelPointersForNonPilferingWindows(const BitSet32 pointerIds); + void cancelPointersForNonPilferingWindows(); sp getFirstForegroundWindowHandle() const; bool isSlippery() const; diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp index 3704edd575..562067e393 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.cpp +++ b/services/inputflinger/dispatcher/TouchedWindow.cpp @@ -63,10 +63,10 @@ std::string TouchedWindow::dump() const { std::string hoveringPointers = dumpMap(mHoveringPointerIdsByDevice, constToString, bitsetToString); out += StringPrintf("name='%s', pointerIds=0x%0x, targetFlags=%s, firstDownTimeInTarget=%s, " - "mHoveringPointerIdsByDevice=%s\n", + "mHoveringPointerIdsByDevice=%s, pilferedPointerIds=%s\n", windowHandle->getName().c_str(), pointerIds.value, targetFlags.string().c_str(), toString(firstDownTimeInTarget).c_str(), - hoveringPointers.c_str()); + hoveringPointers.c_str(), bitsetToString(pilferedPointerIds).c_str()); return out; } diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h index add6b6120e..af7530471f 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -31,7 +31,8 @@ struct TouchedWindow { sp windowHandle; ftl::Flags targetFlags; BitSet32 pointerIds; - bool isPilferingPointers = false; + // The pointer ids of the pointers that this window is currently pilfering + std::bitset pilferedPointerIds; // Time at which the first action down occurred on this window. // NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE scenario. std::optional firstDownTimeInTarget; diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index dfe3d16e75..c04227d16c 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -52,6 +52,7 @@ static constexpr nsecs_t ARBITRARY_TIME = 1234; // An arbitrary device id. static constexpr int32_t DEVICE_ID = 1; +static constexpr int32_t SECOND_DEVICE_ID = 2; // An arbitrary display id. static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; @@ -1471,6 +1472,11 @@ public: mEventTime = systemTime(SYSTEM_TIME_MONOTONIC); } + MotionEventBuilder& deviceId(int32_t deviceId) { + mDeviceId = deviceId; + return *this; + } + MotionEventBuilder& eventTime(nsecs_t eventTime) { mEventTime = eventTime; return *this; @@ -1529,7 +1535,7 @@ public: MotionEvent event; ui::Transform identityTransform; - event.initialize(InputEvent::nextId(), DEVICE_ID, mSource, mDisplayId, INVALID_HMAC, + event.initialize(InputEvent::nextId(), mDeviceId, mSource, mDisplayId, INVALID_HMAC, mAction, mActionButton, mFlags, /* edgeFlags */ 0, AMETA_NONE, mButtonState, MotionClassification::NONE, identityTransform, /* xPrecision */ 0, /* yPrecision */ 0, mRawXCursorPosition, @@ -1541,6 +1547,7 @@ public: private: int32_t mAction; + int32_t mDeviceId = DEVICE_ID; int32_t mSource; nsecs_t mEventTime; int32_t mDisplayId{ADISPLAY_ID_DEFAULT}; @@ -2308,6 +2315,124 @@ TEST_F(InputDispatcherTest, HoverWithSpyWindows) { spyWindow->assertNoEvents(); } +TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) { + std::shared_ptr application = std::make_shared(); + + sp spyWindow = + sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 600, 800)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 600, 800)); + + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}}); + + // Send mouse cursor to the window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, + AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .x(100) + .y(100)) + .build())); + + // Move mouse cursor + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, + AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .x(110) + .y(110)) + .build())); + + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithSource(AINPUT_SOURCE_MOUSE))); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithSource(AINPUT_SOURCE_MOUSE))); + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithSource(AINPUT_SOURCE_MOUSE))); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithSource(AINPUT_SOURCE_MOUSE))); + // Touch down on the window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(200) + .y(200)) + .build())); + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithSource(AINPUT_SOURCE_MOUSE))); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithSource(AINPUT_SOURCE_MOUSE))); + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + // pilfer the motion, retaining the gesture on the spy window. + EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken())); + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + // Touch UP on the window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(200) + .y(200)) + .build())); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + // Previously, a touch was pilfered. However, that gesture was just finished. Now, we are going + // to send a new gesture. It should again go to both windows (spy and the window below), just + // like the first gesture did, before pilfering. The window configuration has not changed. + + // One more tap - DOWN + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(250) + .y(250)) + .build())); + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + // Touch UP on the window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(250) + .y(250)) + .build())); + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithSource(AINPUT_SOURCE_TOUCHSCREEN))); + + window->assertNoEvents(); + spyWindow->assertNoEvents(); +} + // This test is different from the test above that HOVER_ENTER and HOVER_EXIT events are injected // directly in this test. TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { -- GitLab From 1768cb03cf1ea51ec18f2514f8afa086b6314ba5 Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Tue, 11 Oct 2022 11:10:34 -0700 Subject: [PATCH 0783/1310] Variable refresh rate for virtual display Allow virtual display to request a refresh rate, so it can be different from VSYNC frequencies. SurfaceFlinger drops frames for the corresponding virtual display based on the refresh rate. Bug: 241286579 Test: atest libgui_test libsurfaceflinger_unittest SurfaceFlinger_test Change-Id: I4fba0e553618bb4c7333514b16206ae4277acf72 --- libs/gui/SurfaceComposerClient.cpp | 6 +++-- .../aidl/android/gui/ISurfaceComposer.aidl | 14 ++++++++++- libs/gui/fuzzer/libgui_fuzzer_utils.h | 2 +- libs/gui/include/gui/SurfaceComposerClient.h | 3 ++- libs/gui/tests/Surface_test.cpp | 1 + services/surfaceflinger/DisplayDevice.cpp | 18 +++++++++++++++ services/surfaceflinger/DisplayDevice.h | 19 +++++++++++++++ .../surfaceflinger/Scheduler/Scheduler.cpp | 4 ++++ services/surfaceflinger/Scheduler/Scheduler.h | 7 ++++++ services/surfaceflinger/SurfaceFlinger.cpp | 23 ++++++++++++++++--- services/surfaceflinger/SurfaceFlinger.h | 5 ++-- 11 files changed, 92 insertions(+), 10 deletions(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index cf9828b2f8..9092f5fe67 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -1182,12 +1182,14 @@ void SurfaceComposerClient::Transaction::setDefaultApplyToken(sp applyT } // --------------------------------------------------------------------------- -sp SurfaceComposerClient::createDisplay(const String8& displayName, bool secure) { +sp SurfaceComposerClient::createDisplay(const String8& displayName, bool secure, + float requestedRefereshRate) { sp display = nullptr; binder::Status status = ComposerServiceAIDL::getComposerService()->createDisplay(std::string( displayName.string()), - secure, &display); + secure, requestedRefereshRate, + &display); return status.isOk() ? display : nullptr; } diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index c08a7c67ae..597749acc4 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -79,9 +79,21 @@ interface ISurfaceComposer { /** * Create a virtual display + * + * displayName + * The name of the virtual display + * secure + * Whether this virtual display is secure + * requestedRefreshRate + * The refresh rate, frames per second, to request on the virtual display. + * This is just a request, the actual rate may be adjusted to align well + * with physical displays running concurrently. If 0 is specified, the + * virtual display is refreshed at the physical display refresh rate. + * * requires ACCESS_SURFACE_FLINGER permission. */ - @nullable IBinder createDisplay(@utf8InCpp String displayName, boolean secure); + @nullable IBinder createDisplay(@utf8InCpp String displayName, boolean secure, + float requestedRefreshRate); /** * Destroy a virtual display diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h index 685bd9290d..14a0e39813 100644 --- a/libs/gui/fuzzer/libgui_fuzzer_utils.h +++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h @@ -67,7 +67,7 @@ public: sp*), (override)); MOCK_METHOD(binder::Status, createConnection, (sp*), (override)); - MOCK_METHOD(binder::Status, createDisplay, (const std::string&, bool, sp*), + MOCK_METHOD(binder::Status, createDisplay, (const std::string&, bool, float, sp*), (override)); MOCK_METHOD(binder::Status, destroyDisplay, (const sp&), (override)); MOCK_METHOD(binder::Status, getPhysicalDisplayIds, (std::vector*), (override)); diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index c5f59c8e3d..45f4dbe2be 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -355,7 +355,8 @@ public: sp mirrorDisplay(DisplayId displayId); //! Create a virtual display - static sp createDisplay(const String8& displayName, bool secure); + static sp createDisplay(const String8& displayName, bool secure, + float requestedRefereshRate = 0); //! Destroy a virtual display static void destroyDisplay(const sp& display); diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 32d60cd5bd..9b2bf7ff31 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -736,6 +736,7 @@ public: } binder::Status createDisplay(const std::string& /*displayName*/, bool /*secure*/, + float /*requestedRefreshRate*/, sp* /*outDisplay*/) override { return binder::Status::ok(); } diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index c61f7d8e55..ba8b6cf86f 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -71,6 +71,7 @@ DisplayDevice::DisplayDevice(DisplayDeviceCreationArgs& args) mRenderFrameRateFPSTrace("RenderRateFPS -" + to_string(getId())), mPhysicalOrientation(args.physicalOrientation), mIsPrimary(args.isPrimary), + mRequestedRefreshRate(args.requestedRefreshRate), mRefreshRateSelector(std::move(args.refreshRateSelector)) { mCompositionDisplay->editState().isSecure = args.isSecure; mCompositionDisplay->createRenderSurface( @@ -513,6 +514,23 @@ void DisplayDevice::clearDesiredActiveModeState() { mDesiredActiveModeChanged = false; } +void DisplayDevice::adjustRefreshRate(Fps leaderDisplayRefreshRate) { + using fps_approx_ops::operator==; + if (mRequestedRefreshRate == 0_Hz) { + return; + } + + using fps_approx_ops::operator>; + if (mRequestedRefreshRate > leaderDisplayRefreshRate) { + mAdjustedRefreshRate = leaderDisplayRefreshRate; + return; + } + + unsigned divisor = static_cast( + std::round(leaderDisplayRefreshRate.getValue() / mRequestedRefreshRate.getValue())); + mAdjustedRefreshRate = leaderDisplayRefreshRate / divisor; +} + std::atomic DisplayDeviceState::sNextSequenceId(1); } // namespace android diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index 370bd66b9e..59f7a306c7 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -245,6 +245,12 @@ public: nsecs_t getVsyncPeriodFromHWC() const; + Fps getAdjustedRefreshRate() const { return mAdjustedRefreshRate; } + + // Round the requested refresh rate to match a divisor of the leader + // display's refresh rate. Only supported for virtual displays. + void adjustRefreshRate(Fps leaderDisplayRefreshRate); + // release HWC resources (if any) for removable displays void disconnect(); @@ -279,6 +285,15 @@ private: uint32_t mFlags = 0; + // Requested refresh rate in fps, supported only for virtual displays. + // when this value is non zero, SurfaceFlinger will try to drop frames + // for virtual displays to match this requested refresh rate. + const Fps mRequestedRefreshRate; + + // Adjusted refresh rate, rounded to match a divisor of the leader + // display's refresh rate. Only supported for virtual displays. + Fps mAdjustedRefreshRate = 0_Hz; + std::vector mOverrideHdrTypes; std::shared_ptr mRefreshRateSelector; @@ -316,6 +331,8 @@ struct DisplayDeviceState { uint32_t height = 0; std::string displayName; bool isSecure = false; + // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only + Fps requestedRefreshRate; private: static std::atomic sNextSequenceId; @@ -345,6 +362,8 @@ struct DisplayDeviceCreationArgs { std::optional initialPowerMode; bool isPrimary{false}; DisplayModeId activeModeId; + // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only + Fps requestedRefreshRate; }; } // namespace android diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 1fc1519982..fa95685955 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -166,6 +166,10 @@ bool Scheduler::isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const return mVsyncSchedule->getTracker().isVSyncInPhase(expectedVsyncTimestamp.ns(), *frameRate); } +bool Scheduler::isVsyncInPhase(TimePoint timePoint, const Fps frameRate) const { + return mVsyncSchedule->getTracker().isVSyncInPhase(timePoint.ns(), frameRate); +} + impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() const { return [this](nsecs_t expectedVsyncTimestamp, uid_t uid) { return !isVsyncValid(TimePoint::fromNs(expectedVsyncTimestamp), uid); diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index ef7d0cf5b1..e8224481c6 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -231,6 +231,9 @@ public: // for a given uid bool isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const; + // Checks if a vsync timestamp is in phase for a frame rate + bool isVsyncInPhase(TimePoint timePoint, const Fps frameRate) const; + void dump(utils::Dumper&) const; void dump(ConnectionHandle, std::string&) const; void dumpVsync(std::string&) const; @@ -262,6 +265,10 @@ public: return leaderSelectorPtr()->getActiveMode().fps.getPeriod(); } + Fps getLeaderRefreshRate() const EXCLUDES(mDisplayLock) { + return leaderSelectorPtr()->getActiveMode().fps; + } + // Returns the framerate of the layer with the given sequence ID float getLayerFramerate(nsecs_t now, int32_t id) const { return mLayerHistory.getLayerFramerate(now, id); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 7d0dc93cf9..fe4ae02a72 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -512,7 +512,8 @@ void SurfaceFlinger::run() { mScheduler->run(); } -sp SurfaceFlinger::createDisplay(const String8& displayName, bool secure) { +sp SurfaceFlinger::createDisplay(const String8& displayName, bool secure, + float requestedRefreshRate) { // onTransact already checks for some permissions, but adding an additional check here. // This is to ensure that only system and graphics can request to create a secure // display. Secure displays can show secure content so we add an additional restriction on it. @@ -543,6 +544,7 @@ sp SurfaceFlinger::createDisplay(const String8& displayName, bool secur DisplayDeviceState state; state.isSecure = secure; state.displayName = displayName; + state.requestedRefreshRate = Fps::fromValue(requestedRefreshRate); mCurrentState.displays.add(token, state); return token; } @@ -2343,7 +2345,15 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) refreshArgs.outputs.reserve(displays.size()); std::vector displayIds; for (const auto& [_, display] : displays) { - refreshArgs.outputs.push_back(display->getCompositionDisplay()); + bool dropFrame = false; + if (display->isVirtual()) { + Fps refreshRate = display->getAdjustedRefreshRate(); + using fps_approx_ops::operator>; + dropFrame = (refreshRate > 0_Hz) && !mScheduler->isVsyncInPhase(frameTime, refreshRate); + } + if (!dropFrame) { + refreshArgs.outputs.push_back(display->getCompositionDisplay()); + } displayIds.push_back(display->getId()); } mPowerAdvisor->setDisplays(displayIds); @@ -3082,6 +3092,8 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( creationArgs.initialPowerMode = state.isVirtual() ? std::make_optional(hal::PowerMode::ON) : std::nullopt; + creationArgs.requestedRefreshRate = state.requestedRefreshRate; + sp display = getFactory().createDisplayDevice(creationArgs); nativeWindowSurface->preallocateBuffers(); @@ -3198,6 +3210,10 @@ void SurfaceFlinger::processDisplayAdded(const wp& displayToken, dispatchDisplayHotplugEvent(displayId, true); } + if (display->isVirtual()) { + display->adjustRefreshRate(mScheduler->getLeaderRefreshRate()); + } + mDisplays.try_emplace(displayToken, std::move(display)); } @@ -7448,13 +7464,14 @@ binder::Status SurfaceComposerAIDL::createConnection(sp* outDisplay) { status_t status = checkAccessPermission(); if (status != OK) { return binderStatusFromStatusT(status); } String8 displayName8 = String8::format("%s", displayName.c_str()); - *outDisplay = mFlinger->createDisplay(displayName8, secure); + *outDisplay = mFlinger->createDisplay(displayName8, secure, requestedRefreshRate); return binder::Status::ok(); } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 207dfe2e96..170af48e19 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -483,7 +483,8 @@ private: EXCLUDES(mStateLock); // Implements ISurfaceComposer - sp createDisplay(const String8& displayName, bool secure); + sp createDisplay(const String8& displayName, bool secure, + float requestedRefreshRate = 0); void destroyDisplay(const sp& displayToken); std::vector getPhysicalDisplayIds() const EXCLUDES(mStateLock) { Mutex::Autolock lock(mStateLock); @@ -1403,7 +1404,7 @@ public: sp* outConnection) override; binder::Status createConnection(sp* outClient) override; binder::Status createDisplay(const std::string& displayName, bool secure, - sp* outDisplay) override; + float requestedRefreshRate, sp* outDisplay) override; binder::Status destroyDisplay(const sp& display) override; binder::Status getPhysicalDisplayIds(std::vector* outDisplayIds) override; binder::Status getPhysicalDisplayToken(int64_t displayId, sp* outDisplay) override; -- GitLab From e0431e406172a2589d1013daa43c46208ae68720 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Sat, 28 Jan 2023 17:01:39 -0800 Subject: [PATCH 0784/1310] Only send events to windows with pointers Due to the recent refactor, some of the windows with only hovering pointers are getting persisted inside TouchState. This is the case for the hovering pointers from mouse, for example. Mouse usually never leaves the screen, so it's always inside some window. However, we are still currently iterating over TouchedWindows inside the TouchState in order to determine which targets should receive the current entry. That means that the windows that are hovered over will always be there, which is not something that we want. It would cause the events to go to windows that are not directly getting touched. To avoid this, make sure that the pointers are indeed supposed to be going to the current window. In a future refactor, we will store the pointers per-device, as well. The added test reproduces a condition where we crash due to mismatching downtimes. There are few issues that it exposes. 1) We currently use hover events for setting the downtime of a window, which we shouldn't do. 2) If a window is not receiving the events from the current device, it shouldn't be in the dispatching list. So we should not be sending the events there to begin with. Bug: 266455987 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests --gtest_filter="*HoverFromLeftToRightAndTap*" Change-Id: Ic0d2a1ed6d053e18077bc7216b1ce02e88017b4a --- .../dispatcher/InputDispatcher.cpp | 16 ++- .../inputflinger/dispatcher/TouchState.cpp | 6 +- services/inputflinger/dispatcher/TouchState.h | 2 +- .../inputflinger/dispatcher/TouchedWindow.cpp | 4 + .../inputflinger/dispatcher/TouchedWindow.h | 1 + .../tests/InputDispatcher_test.cpp | 126 +++++++++++++++++- 6 files changed, 147 insertions(+), 8 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 0f3dc5c972..44c133c9a1 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2303,8 +2303,13 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( pointerIds.markBit(entry.pointerProperties[pointerIndex].id); } + const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN || + maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN; + tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds, - entry.eventTime); + isDownOrPointerDown + ? std::make_optional(entry.eventTime) + : std::nullopt); // If this is the pointer going down and the touched window has a wallpaper // then also add the touched wallpaper windows so they are locked in for the duration @@ -2312,8 +2317,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper // engine only supports touch events. We would need to add a mechanism similar // to View.onGenericMotionEvent to enable wallpapers to handle these events. - if (maskedAction == AMOTION_EVENT_ACTION_DOWN || - maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) { + if (isDownOrPointerDown) { if (targetFlags.test(InputTarget::Flags::FOREGROUND) && windowHandle->getInfo()->inputConfig.test( gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) { @@ -2517,6 +2521,12 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( // Success! Output targets from the touch state. tempTouchState.clearWindowsWithoutPointers(); for (const TouchedWindow& touchedWindow : tempTouchState.windows) { + if (touchedWindow.pointerIds.isEmpty() && + !touchedWindow.hasHoveringPointers(entry.deviceId)) { + // Windows with hovering pointers are getting persisted inside TouchState. + // Do not send this event to those windows. + continue; + } addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget, targets); diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index e9c6ad5f0a..c257ee540d 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -63,7 +63,7 @@ void TouchState::clearWindowsWithoutPointers() { void TouchState::addOrUpdateWindow(const sp& windowHandle, ftl::Flags targetFlags, BitSet32 pointerIds, - std::optional eventTime) { + std::optional firstDownTimeInTarget) { for (TouchedWindow& touchedWindow : windows) { // We do not compare windows by token here because two windows that share the same token // may have a different transform @@ -77,7 +77,7 @@ void TouchState::addOrUpdateWindow(const sp& windowHandle, // the window. touchedWindow.pointerIds.value |= pointerIds.value; if (!touchedWindow.firstDownTimeInTarget.has_value()) { - touchedWindow.firstDownTimeInTarget = eventTime; + touchedWindow.firstDownTimeInTarget = firstDownTimeInTarget; } return; } @@ -86,7 +86,7 @@ void TouchState::addOrUpdateWindow(const sp& windowHandle, touchedWindow.windowHandle = windowHandle; touchedWindow.targetFlags = targetFlags; touchedWindow.pointerIds = pointerIds; - touchedWindow.firstDownTimeInTarget = eventTime; + touchedWindow.firstDownTimeInTarget = firstDownTimeInTarget; windows.push_back(touchedWindow); } diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index 0092f1d8a0..f1409d6d42 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -47,7 +47,7 @@ struct TouchState { const sp& windowHandle); void addOrUpdateWindow(const sp& windowHandle, ftl::Flags targetFlags, BitSet32 pointerIds, - std::optional eventTime = std::nullopt); + std::optional firstDownTimeInTarget = std::nullopt); void addHoveringPointerToWindow(const sp& windowHandle, int32_t deviceId, int32_t hoveringPointerId); void removeHoveringPointer(int32_t deviceId, int32_t hoveringPointerId); diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp index 562067e393..99e1c86f38 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.cpp +++ b/services/inputflinger/dispatcher/TouchedWindow.cpp @@ -29,6 +29,10 @@ bool TouchedWindow::hasHoveringPointers() const { return !mHoveringPointerIdsByDevice.empty(); } +bool TouchedWindow::hasHoveringPointers(int32_t deviceId) const { + return mHoveringPointerIdsByDevice.find(deviceId) != mHoveringPointerIdsByDevice.end(); +} + void TouchedWindow::clearHoveringPointers() { mHoveringPointerIdsByDevice.clear(); } diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h index af7530471f..4ec33ac3ef 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -38,6 +38,7 @@ struct TouchedWindow { std::optional firstDownTimeInTarget; bool hasHoveringPointers() const; + bool hasHoveringPointers(int32_t deviceId) const; bool hasHoveringPointer(int32_t deviceId, int32_t pointerId) const; void addHoveringPointer(int32_t deviceId, int32_t pointerId); diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index c04227d16c..20a1977813 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -1470,6 +1470,7 @@ public: mAction = action; mSource = source; mEventTime = systemTime(SYSTEM_TIME_MONOTONIC); + mDownTime = mEventTime; } MotionEventBuilder& deviceId(int32_t deviceId) { @@ -1477,6 +1478,11 @@ public: return *this; } + MotionEventBuilder& downTime(nsecs_t downTime) { + mDownTime = downTime; + return *this; + } + MotionEventBuilder& eventTime(nsecs_t eventTime) { mEventTime = eventTime; return *this; @@ -1539,7 +1545,7 @@ public: mAction, mActionButton, mFlags, /* edgeFlags */ 0, AMETA_NONE, mButtonState, MotionClassification::NONE, identityTransform, /* xPrecision */ 0, /* yPrecision */ 0, mRawXCursorPosition, - mRawYCursorPosition, identityTransform, mEventTime, mEventTime, + mRawYCursorPosition, identityTransform, mDownTime, mEventTime, mPointers.size(), pointerProperties.data(), pointerCoords.data()); return event; @@ -1549,6 +1555,7 @@ private: int32_t mAction; int32_t mDeviceId = DEVICE_ID; int32_t mSource; + nsecs_t mDownTime; nsecs_t mEventTime; int32_t mDisplayId{ADISPLAY_ID_DEFAULT}; int32_t mActionButton{0}; @@ -2063,6 +2070,123 @@ TEST_F(InputDispatcherTest, WallpaperWindowWhenSlippery) { wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); } +/** + * Two windows: a window on the left and a window on the right. + * Mouse is hovered from the right window into the left window. + * Next, we tap on the left window, where the cursor was last seen. + * The second tap is done onto the right window. + * The mouse and tap are from two different devices. + * We technically don't need to set the downtime / eventtime for these events, but setting these + * explicitly helps during debugging. + * This test reproduces a crash where there is a mismatch between the downTime and eventTime. + * In the buggy implementation, a tap on the right window would cause a crash. + */ +TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { + std::shared_ptr application = std::make_shared(); + sp leftWindow = + sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 200, 200)); + + sp rightWindow = + sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightWindow->setFrame(Rect(200, 0, 400, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow}}}); + // All times need to start at the current time, otherwise the dispatcher will drop the events as + // stale. + const nsecs_t baseTime = systemTime(SYSTEM_TIME_MONOTONIC); + const int32_t mouseDeviceId = 6; + const int32_t touchDeviceId = 4; + // Move the cursor from right + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, + AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .downTime(baseTime + 10) + .eventTime(baseTime + 20) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .x(300) + .y(100)) + .build())); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + + // .. to the left window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, + AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .downTime(baseTime + 10) + .eventTime(baseTime + 30) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .x(110) + .y(100)) + .build())); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + // Now tap the left window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .downTime(baseTime + 40) + .eventTime(baseTime + 40) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(100) + .y(100)) + .build())); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + + // release tap + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .downTime(baseTime + 40) + .eventTime(baseTime + 50) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(100) + .y(100)) + .build())); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP)); + + // Tap the window on the right + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .downTime(baseTime + 60) + .eventTime(baseTime + 60) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(300) + .y(100)) + .build())); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + + // release tap + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .downTime(baseTime + 60) + .eventTime(baseTime + 70) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(300) + .y(100)) + .build())); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP)); + + // No more events + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + /** * On the display, have a single window, and also an area where there's no window. * First pointer touches the "no window" area of the screen. Second pointer touches the window. -- GitLab From 781d72590f31f774ca707154229bbfcfac9f7c97 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Mon, 30 Jan 2023 18:16:01 +0000 Subject: [PATCH 0785/1310] SF: Use snapshots for TrustedPresentationListener updates Also pass the correct display to the compute function. Bug: 238781169 Test: presubmit Change-Id: I465f275fa1247ff98b4d578a26416d8c100170de --- .../surfaceflinger/FrontEnd/LayerSnapshot.cpp | 7 +++++++ .../surfaceflinger/FrontEnd/LayerSnapshot.h | 1 + services/surfaceflinger/Layer.cpp | 18 ++++++++++-------- services/surfaceflinger/Layer.h | 3 ++- services/surfaceflinger/SurfaceFlinger.cpp | 14 ++++++++++++-- 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp index 4e695653e7..dbb7fbf462 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp @@ -168,4 +168,11 @@ std::string LayerSnapshot::getDebugString() const { return debug.str(); } +FloatRect LayerSnapshot::sourceBounds() const { + if (!externalTexture) { + return geomLayerBounds; + } + return geomBufferSize.toFloatRect(); +} + } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h index 159410f3ea..7141f0a712 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h @@ -103,6 +103,7 @@ struct LayerSnapshot : public compositionengine::LayerFECompositionState { std::string getDebugString() const; std::string getIsVisibleReason() const; bool hasInputInfo() const; + FloatRect sourceBounds() const; }; } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 7a4b3375a1..8cf25af7ab 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -254,7 +254,7 @@ Layer::~Layer() { } if (hasTrustedPresentationListener()) { mFlinger->mNumTrustedPresentationListeners--; - updateTrustedPresentationState(nullptr, -1 /* time_in_ms */, true /* leaveState*/); + updateTrustedPresentationState(nullptr, nullptr, -1 /* time_in_ms */, true /* leaveState*/); } } @@ -285,7 +285,7 @@ void Layer::removeFromCurrentState() { mRemovedFromDrawingState = true; mFlinger->mScheduler->deregisterLayer(this); } - updateTrustedPresentationState(nullptr, -1 /* time_in_ms */, true /* leaveState*/); + updateTrustedPresentationState(nullptr, nullptr, -1 /* time_in_ms */, true /* leaveState*/); mFlinger->markLayerPendingRemovalLocked(sp::fromExisting(this)); } @@ -384,8 +384,9 @@ FloatRect Layer::getBounds(const Region& activeTransparentRegion) const { } // No early returns. -void Layer::updateTrustedPresentationState(const DisplayDevice* display, int64_t time_in_ms, - bool leaveState) { +void Layer::updateTrustedPresentationState(const DisplayDevice* display, + const frontend::LayerSnapshot* snapshot, + int64_t time_in_ms, bool leaveState) { if (!hasTrustedPresentationListener()) { return; } @@ -394,12 +395,13 @@ void Layer::updateTrustedPresentationState(const DisplayDevice* display, int64_t if (!leaveState) { const auto outputLayer = findOutputLayerForDisplay(display); - if (outputLayer != nullptr) { + if (outputLayer != nullptr && snapshot != nullptr) { mLastComputedTrustedPresentationState = - computeTrustedPresentationState(mBounds, mSourceBounds, + computeTrustedPresentationState(snapshot->geomLayerBounds, + snapshot->sourceBounds(), outputLayer->getState().coveredRegion, - mScreenBounds, getCompositionState()->alpha, - getCompositionState()->geomLayerTransform, + snapshot->transformedBounds, snapshot->alpha, + snapshot->geomLayerTransform, mTrustedPresentationThresholds); } } diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 3384e4af2d..234b26583d 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -539,7 +539,8 @@ public: const FloatRect& screenBounds, float, const ui::Transform&, const TrustedPresentationThresholds&); - void updateTrustedPresentationState(const DisplayDevice* display, int64_t time_in_ms, + void updateTrustedPresentationState(const DisplayDevice* display, + const frontend::LayerSnapshot* snapshot, int64_t time_in_ms, bool leaveState); inline bool hasTrustedPresentationListener() { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 7d0dc93cf9..bcc24da413 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2750,13 +2750,23 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { } if (mNumTrustedPresentationListeners > 0) { + display::DisplayMap layerStackToDisplay; + { + Mutex::Autolock lock(mStateLock); + for (const auto& [token, display] : mDisplays) { + layerStackToDisplay.emplace_or_replace(display->getLayerStack(), display.get()); + } + } + // We avoid any reverse traversal upwards so this shouldn't be too expensive mDrawingState.traverse([&](Layer* layer) { if (!layer->hasTrustedPresentationListener()) { return; } - layer->updateTrustedPresentationState(display, nanoseconds_to_milliseconds(callTime), - false); + const auto display = + layerStackToDisplay.get(layer->getLayerSnapshot()->outputFilter.layerStack); + layer->updateTrustedPresentationState(display->get(), layer->getLayerSnapshot(), + nanoseconds_to_milliseconds(callTime), false); }); } -- GitLab From da21f422c2de37bca9addcdace3badc329be24b7 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Mon, 30 Jan 2023 20:17:56 -0500 Subject: [PATCH 0786/1310] Scheduler: Prevent removing the last display unregisterDisplay calls promoteLeaderDisplay, which assumes that there is at least one RefreshRateSelector remaining. Other Scheduler code also assumes that there will be at least one display, which is necessary for there to be a leader. In SchedulerTest, remove the final call to unregisterDisplay, so we don't put Scheduler in a bad state. Remove hasRefreshRateSelectors(), since this condition must be true. Bug: 241285191 Test: libsurfaceflinger_unittest Change-Id: I827f20acd6a105d9f99e1ca9a7bfc59e633ab33f --- services/surfaceflinger/Scheduler/Scheduler.cpp | 5 +++++ services/surfaceflinger/tests/unittests/SchedulerTest.cpp | 3 --- services/surfaceflinger/tests/unittests/TestableScheduler.h | 2 -- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 1e97f352d1..b8b3f51285 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -127,6 +127,11 @@ void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { std::scoped_lock lock(mDisplayLock); mRefreshRateSelectors.erase(displayId); + // Do not allow removing the final display. Code in the scheduler expects + // there to be at least one display. (This may be relaxed in the future with + // headless virtual display.) + LOG_ALWAYS_FATAL_IF(mRefreshRateSelectors.empty(), "Cannot unregister all displays!"); + promoteLeaderDisplay(); } diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 3ee53c94c2..4b15385fa8 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -301,9 +301,6 @@ TEST_F(SchedulerTest, chooseDisplayModesSingleDisplay) { choice = modeChoices.get(kDisplayId1); ASSERT_TRUE(choice); EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, globalSignals)); - - mScheduler->unregisterDisplay(kDisplayId1); - EXPECT_FALSE(mScheduler->hasRefreshRateSelectors()); } TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index 74885d8a78..6cf61416c0 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -79,8 +79,6 @@ public: return mRefreshRateSelectors; } - bool hasRefreshRateSelectors() const { return !refreshRateSelectors().empty(); } - void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) { ftl::FakeGuard guard(kMainThreadContext); Scheduler::registerDisplay(displayId, std::move(selectorPtr)); -- GitLab From 6438a192effc1cb2905cd9c178108b779d78ac6b Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Sun, 29 Jan 2023 07:51:15 +0000 Subject: [PATCH 0787/1310] jpegr: encode ICC bug: b/264715926, b/263609305 test: recoverymap_test.cpp, check the ICC package in the encoded jpeg/r image in a hex editor Change-Id: I099eb9201c26fce046b7ca8ea4b5e7e8443adcb3 --- libs/jpegrecoverymap/Android.bp | 2 + .../include/jpegrecoverymap/recoverymap.h | 1 + .../jpegrecoverymap/recoverymaputils.h | 7 ---- libs/jpegrecoverymap/recoverymap.cpp | 37 ++++++++++++++++--- libs/jpegrecoverymap/tests/Android.bp | 8 ++-- 5 files changed, 37 insertions(+), 18 deletions(-) diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp index 01318dc80a..2c4b3bff77 100644 --- a/libs/jpegrecoverymap/Android.bp +++ b/libs/jpegrecoverymap/Android.bp @@ -41,6 +41,8 @@ cc_library { "libjpegdecoder", "liblog", ], + + static_libs: ["libskia"], } cc_library { diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h index 696be1beb7..1a4b679a73 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -34,6 +34,7 @@ typedef enum { JPEGR_TF_LINEAR = 0, JPEGR_TF_HLG = 1, JPEGR_TF_PQ = 2, + JPEGR_TF_SRGB = 3, } jpegr_transfer_function; struct jpegr_info_struct { diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h index c36a363a3c..8696851155 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h @@ -28,13 +28,6 @@ namespace android::recoverymap { struct jpegr_metadata; -// If the EXIF package doesn't exist in the input JPEG, we'll create one with one entry -// where the length is represented by this value. -const size_t PSEUDO_EXIF_PACKAGE_LENGTH = 28; -// If the EXIF package exists in the input JPEG, we'll add an "JR" entry where the length is -// represented by this value. -const size_t EXIF_J_R_ENTRY_LENGTH = 12; - /* * Helper function used for writing data to destination. * diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index 30aa846bf8..7ca6094d20 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -26,7 +26,10 @@ #include #include #include +#include "SkColorSpace.h" +#include "SkICC.h" +#include #include #include #include @@ -93,6 +96,20 @@ int GetCPUCoreCount() { return cpuCoreCount; } +static const map jrGamut_to_skGamut { + {JPEGR_COLORGAMUT_BT709, SkNamedGamut::kSRGB}, + {JPEGR_COLORGAMUT_P3, SkNamedGamut::kDisplayP3}, + {JPEGR_COLORGAMUT_BT2100, SkNamedGamut::kRec2020}, +}; + +static const map< + recoverymap::jpegr_transfer_function, skcms_TransferFunction> jrTransFunc_to_skTransFunc { + {JPEGR_TF_SRGB, SkNamedTransferFn::kSRGB}, + {JPEGR_TF_LINEAR, SkNamedTransferFn::kLinear}, + {JPEGR_TF_HLG, SkNamedTransferFn::kHLG}, + {JPEGR_TF_PQ, SkNamedTransferFn::kPQ}, +}; + /* * Helper function copies the JPEG image from without EXIF. * @@ -164,11 +181,15 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, compressed_map.data = compressed_map_data.get(); JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); + sk_sp icc = SkWriteICCProfile( + jrTransFunc_to_skTransFunc.at(JPEGR_TF_SRGB), + jrGamut_to_skGamut.at(uncompressed_yuv_420_image.colorGamut)); + JpegEncoder jpeg_encoder; - // TODO: determine ICC data based on color gamut information if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data, uncompressed_yuv_420_image.width, - uncompressed_yuv_420_image.height, quality, nullptr, 0)) { + uncompressed_yuv_420_image.height, quality, + icc.get()->data(), icc.get()->size())) { return ERROR_JPEGR_ENCODE_ERROR; } jpegr_compressed_struct jpeg; @@ -228,11 +249,15 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, compressed_map.data = compressed_map_data.get(); JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); + sk_sp icc = SkWriteICCProfile( + jrTransFunc_to_skTransFunc.at(JPEGR_TF_SRGB), + jrGamut_to_skGamut.at(uncompressed_yuv_420_image->colorGamut)); + JpegEncoder jpeg_encoder; - // TODO: determine ICC data based on color gamut information if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data, uncompressed_yuv_420_image->width, - uncompressed_yuv_420_image->height, quality, nullptr, 0)) { + uncompressed_yuv_420_image->height, quality, + icc.get()->data(), icc.get()->size())) { return ERROR_JPEGR_ENCODE_ERROR; } jpegr_compressed_struct jpeg; @@ -574,7 +599,7 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 #endif hdr_white_nits = kPqMaxNits; break; - case JPEGR_TF_UNSPECIFIED: + default: // Should be impossible to hit after input validation. return ERROR_JPEGR_INVALID_TRANS_FUNC; } @@ -750,7 +775,7 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ hdrOetf = pqOetf; #endif break; - case JPEGR_TF_UNSPECIFIED: + default: // Should be impossible to hit after input validation. hdrOetf = identityConversion; } diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp index 39445f81ab..cad273e437 100644 --- a/libs/jpegrecoverymap/tests/Android.bp +++ b/libs/jpegrecoverymap/tests/Android.bp @@ -30,15 +30,13 @@ cc_test { ], shared_libs: [ "libjpeg", + "libjpegrecoverymap", "libimage_io", "liblog", ], static_libs: [ "libgmock", "libgtest", - "libjpegdecoder", - "libjpegencoder", - "libjpegrecoverymap", ], } @@ -50,10 +48,10 @@ cc_test { ], shared_libs: [ "libjpeg", + "libjpegencoder", "liblog", ], static_libs: [ - "libjpegencoder", "libgtest", ], } @@ -66,10 +64,10 @@ cc_test { ], shared_libs: [ "libjpeg", + "libjpegdecoder", "liblog", ], static_libs: [ - "libjpegdecoder", "libgtest", ], } -- GitLab From 334762394eea028a940b7ba1e05909b54841c03e Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Mon, 30 Jan 2023 19:57:29 +0000 Subject: [PATCH 0788/1310] inputflinger: make parameter comment style consistent The Google C++ style guide [0] suggests that parameter name comments should be of the form `/*paramName=*/value`. This potentially allows tooling to check that the parameter names are correct, too. [0]: https://google.github.io/styleguide/cppguide.html#Function_Argument_Comments Bug: none Test: host-side inputflinger_tests Change-Id: I89625844bcd19d27c43b4f14f1f207ade39a4718 --- services/inputflinger/InputProcessor.cpp | 2 +- .../benchmarks/InputDispatcher_benchmarks.cpp | 8 +- .../dispatcher/InputDispatcher.cpp | 34 +- .../inputflinger/dispatcher/InputState.cpp | 34 +- .../dispatcher/LatencyTracker.cpp | 2 +- services/inputflinger/reader/EventHub.cpp | 6 +- .../controller/PeripheralController.cpp | 11 +- .../reader/mapper/JoystickInputMapper.cpp | 2 +- .../reader/mapper/KeyboardInputMapper.cpp | 2 +- .../reader/mapper/MultiTouchInputMapper.cpp | 4 +- .../reader/mapper/SensorInputMapper.cpp | 20 +- .../reader/mapper/SwitchInputMapper.cpp | 2 +- .../reader/mapper/TouchInputMapper.cpp | 14 +- .../accumulator/TouchButtonAccumulator.cpp | 2 +- .../inputflinger/tests/FocusResolver_test.cpp | 44 +-- .../tests/InputDispatcher_test.cpp | 370 ++++++++---------- .../tests/InputProcessorConverter_test.cpp | 14 +- .../tests/InputProcessor_test.cpp | 32 +- .../inputflinger/tests/InputReader_test.cpp | 122 +++--- .../tests/LatencyTracker_test.cpp | 50 +-- .../tests/PreferStylusOverTouch_test.cpp | 142 +++---- .../tests/UnwantedInteractionBlocker_test.cpp | 94 ++--- .../tests/fuzzers/InputClassifierFuzzer.cpp | 55 +-- .../tests/fuzzers/InputReaderFuzzer.cpp | 8 +- 24 files changed, 519 insertions(+), 555 deletions(-) diff --git a/services/inputflinger/InputProcessor.cpp b/services/inputflinger/InputProcessor.cpp index 02d62bffb2..a98b383037 100644 --- a/services/inputflinger/InputProcessor.cpp +++ b/services/inputflinger/InputProcessor.cpp @@ -402,7 +402,7 @@ void InputProcessor::setMotionClassifierEnabled(bool enabled) { { // acquire lock std::scoped_lock threadLock(mLock); mHalDeathRecipient = - std::make_unique(onBinderDied, this /*cookie*/); + std::make_unique(onBinderDied, /*cookie=*/this); mHalDeathRecipient->linkToDeath(service->asBinder().get()); setMotionClassifierLocked(MotionClassifier::create(std::move(service))); } // release lock diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp index 3d4dd7eabb..f03c837f56 100644 --- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp +++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp @@ -141,7 +141,7 @@ public: ALOGE("Waited too long for consumer to produce an event, giving up"); break; } - result = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, + result = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event); } if (result != OK) { @@ -308,13 +308,13 @@ static void benchmarkInjectMotion(benchmark::State& state) { for (auto _ : state) { MotionEvent event = generateMotionEvent(); // Send ACTION_DOWN - dispatcher.injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + dispatcher.injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, INJECT_EVENT_TIMEOUT, POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER); // Send ACTION_UP event.setAction(AMOTION_EVENT_ACTION_UP); - dispatcher.injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + dispatcher.injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, INJECT_EVENT_TIMEOUT, POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER); @@ -344,7 +344,7 @@ static void benchmarkOnWindowInfosChanged(benchmark::State& state) { for (auto _ : state) { dispatcher.onWindowInfosChanged(windowInfos, displayInfos); - dispatcher.onWindowInfosChanged({} /*windowInfos*/, {} /*displayInfos*/); + dispatcher.onWindowInfosChanged(/*windowInfos=*/{}, /*displayInfos=*/{}); } dispatcher.stop(); } diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 44c133c9a1..143d25ce44 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -670,8 +670,7 @@ InputDispatcher::~InputDispatcher() { while (!mConnectionsByToken.empty()) { sp connection = mConnectionsByToken.begin()->second; - removeInputChannelLocked(connection->inputChannel->getConnectionToken(), - false /* notify */); + removeInputChannelLocked(connection->inputChannel->getConnectionToken(), /*notify=*/false); } } @@ -1023,7 +1022,7 @@ bool InputDispatcher::shouldPruneInboundQueueLocked(const MotionEntry& motionEnt if (isPointerDownEvent && mAwaitedFocusedApplication != nullptr) { const int32_t displayId = motionEntry.displayId; const auto [x, y] = resolveTouchedPosition(motionEntry); - const bool isStylus = isPointerFromStylus(motionEntry, 0 /*pointerIndex*/); + const bool isStylus = isPointerFromStylus(motionEntry, /*pointerIndex=*/0); auto [touchedWindowHandle, _] = findTouchedWindowAtLocked(displayId, x, y, isStylus); if (touchedWindowHandle != nullptr && @@ -2372,7 +2371,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( if (maskedAction == AMOTION_EVENT_ACTION_MOVE && entry.pointerCount == 1 && tempTouchState.isSlippery()) { const auto [x, y] = resolveTouchedPosition(entry); - const bool isStylus = isPointerFromStylus(entry, 0 /*pointerIndex*/); + const bool isStylus = isPointerFromStylus(entry, /*pointerIndex=*/0); sp oldTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle(); auto [newTouchedWindowHandle, _] = findTouchedWindowAtLocked(displayId, x, y, isStylus); @@ -2609,7 +2608,7 @@ void InputDispatcher::finishDragAndDrop(int32_t displayId, float x, float y) { constexpr bool isStylus = false; auto [dropWindow, _] = - findTouchedWindowAtLocked(displayId, x, y, isStylus, true /*ignoreDragWindow*/); + findTouchedWindowAtLocked(displayId, x, y, isStylus, /*ignoreDragWindow=*/true); if (dropWindow) { vec2 local = dropWindow->getInfo()->transform.transform(x, y); sendDropWindowCommandLocked(dropWindow->getToken(), local.x, local.y); @@ -2666,19 +2665,19 @@ void InputDispatcher::addDragEventLocked(const MotionEntry& entry) { constexpr bool isStylus = false; auto [hoverWindowHandle, _] = findTouchedWindowAtLocked(entry.displayId, x, y, isStylus, - true /*ignoreDragWindow*/); + /*ignoreDragWindow=*/true); // enqueue drag exit if needed. if (hoverWindowHandle != mDragState->dragHoverWindowHandle && !haveSameToken(hoverWindowHandle, mDragState->dragHoverWindowHandle)) { if (mDragState->dragHoverWindowHandle != nullptr) { - enqueueDragEventLocked(mDragState->dragHoverWindowHandle, true /*isExiting*/, x, + enqueueDragEventLocked(mDragState->dragHoverWindowHandle, /*isExiting=*/true, x, y); } mDragState->dragHoverWindowHandle = hoverWindowHandle; } // enqueue drag location if needed. if (hoverWindowHandle != nullptr) { - enqueueDragEventLocked(hoverWindowHandle, false /*isExiting*/, x, y); + enqueueDragEventLocked(hoverWindowHandle, /*isExiting=*/false, x, y); } break; } @@ -3356,8 +3355,7 @@ status_t InputDispatcher::publishMotionEvent(Connection& connection, // Don't apply window scale here since we don't want scale to affect raw // coordinates. The scale will be sent back to the client and applied // later when requesting relative coordinates. - scaledCoords[i].scale(globalScaleFactor, 1 /* windowXScale */, - 1 /* windowYScale */); + scaledCoords[i].scale(globalScaleFactor, /*windowXScale=*/1, /*windowYScale=*/1); } usingCoords = scaledCoords; } @@ -3493,7 +3491,7 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, "event to it, status=%s(%d)", connection->getInputChannelName().c_str(), statusToString(status).c_str(), status); - abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/); + abortBrokenDispatchCycleLocked(currentTime, connection, /*notify=*/true); } else { // Pipe is full and we are waiting for the app to finish process some events // before sending more events to it. @@ -3508,7 +3506,7 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, "status=%s(%d)", connection->getInputChannelName().c_str(), statusToString(status).c_str(), status); - abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/); + abortBrokenDispatchCycleLocked(currentTime, connection, /*notify=*/true); } return; } @@ -4245,7 +4243,7 @@ void InputDispatcher::notifySensor(const NotifySensorArgs* args) { // Just enqueue a new sensor event. std::unique_ptr newEntry = std::make_unique(args->id, args->eventTime, args->deviceId, - args->source, 0 /* policyFlags*/, args->hwTimestamp, + args->source, /* policyFlags=*/0, args->hwTimestamp, args->sensorType, args->accuracy, args->accuracyChanged, args->values); @@ -5639,7 +5637,7 @@ Result> InputDispatcher::createInputChannel(const const sp& token = serverChannel->getConnectionToken(); int fd = serverChannel->getFd(); sp connection = - sp::make(std::move(serverChannel), false /*monitor*/, mIdGenerator); + sp::make(std::move(serverChannel), /*monitor=*/false, mIdGenerator); if (mConnectionsByToken.find(token) != mConnectionsByToken.end()) { ALOGE("Created a new connection, but the token %p is already known", token.get()); @@ -5677,7 +5675,7 @@ Result> InputDispatcher::createInputMonitor(int32_ } sp connection = - sp::make(serverChannel, true /*monitor*/, mIdGenerator); + sp::make(serverChannel, /*monitor=*/true, mIdGenerator); const sp& token = serverChannel->getConnectionToken(); const int fd = serverChannel->getFd(); @@ -5703,7 +5701,7 @@ status_t InputDispatcher::removeInputChannel(const sp& connectionToken) { // acquire lock std::scoped_lock _l(mLock); - status_t status = removeInputChannelLocked(connectionToken, false /*notify*/); + status_t status = removeInputChannelLocked(connectionToken, /*notify=*/false); if (status) { return status; } @@ -6411,11 +6409,11 @@ void InputDispatcher::onFocusChangedLocked(const FocusResolver::FocusChanges& ch CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, "focus left window"); synthesizeCancelationEventsForInputChannelLocked(focusedInputChannel, options); - enqueueFocusEventLocked(changes.oldFocus, false /*hasFocus*/, changes.reason); + enqueueFocusEventLocked(changes.oldFocus, /*hasFocus=*/false, changes.reason); } } if (changes.newFocus) { - enqueueFocusEventLocked(changes.newFocus, true /*hasFocus*/, changes.reason); + enqueueFocusEventLocked(changes.newFocus, /*hasFocus=*/true, changes.reason); } // If a window has pointer capture, then it must have focus. We need to ensure that this diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp index 93419a15d5..de8bfd5ffe 100644 --- a/services/inputflinger/dispatcher/InputState.cpp +++ b/services/inputflinger/dispatcher/InputState.cpp @@ -97,7 +97,7 @@ bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t f switch (actionMasked) { case AMOTION_EVENT_ACTION_UP: case AMOTION_EVENT_ACTION_CANCEL: { - ssize_t index = findMotionMemento(entry, false /*hovering*/); + ssize_t index = findMotionMemento(entry, /*hovering=*/false); if (index >= 0) { mMotionMementos.erase(mMotionMementos.begin() + index); return true; @@ -111,11 +111,11 @@ bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t f } case AMOTION_EVENT_ACTION_DOWN: { - ssize_t index = findMotionMemento(entry, false /*hovering*/); + ssize_t index = findMotionMemento(entry, /*hovering=*/false); if (index >= 0) { mMotionMementos.erase(mMotionMementos.begin() + index); } - addMotionMemento(entry, flags, false /*hovering*/); + addMotionMemento(entry, flags, /*hovering=*/false); return true; } @@ -129,7 +129,7 @@ bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t f return true; } - ssize_t index = findMotionMemento(entry, false /*hovering*/); + ssize_t index = findMotionMemento(entry, /*hovering=*/false); if (entry.source & AINPUT_SOURCE_CLASS_JOYSTICK) { // Joysticks can send MOVE events without a corresponding DOWN or UP. Since all @@ -145,7 +145,7 @@ bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t f memento.setPointers(entry); } } else if (!entry.pointerCoords[0].isEmpty()) { - addMotionMemento(entry, flags, false /*hovering*/); + addMotionMemento(entry, flags, /*hovering=*/false); } // Joysticks and trackballs can send MOVE events without corresponding DOWN or UP. @@ -168,7 +168,7 @@ bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t f } case AMOTION_EVENT_ACTION_HOVER_EXIT: { - ssize_t index = findMotionMemento(entry, true /*hovering*/); + ssize_t index = findMotionMemento(entry, /*hovering=*/true); if (index >= 0) { mMotionMementos.erase(mMotionMementos.begin() + index); return true; @@ -183,11 +183,11 @@ bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t f case AMOTION_EVENT_ACTION_HOVER_ENTER: case AMOTION_EVENT_ACTION_HOVER_MOVE: { - ssize_t index = findMotionMemento(entry, true /*hovering*/); + ssize_t index = findMotionMemento(entry, /*hovering=*/true); if (index >= 0) { mMotionMementos.erase(mMotionMementos.begin() + index); } - addMotionMemento(entry, flags, true /*hovering*/); + addMotionMemento(entry, flags, /*hovering=*/true); return true; } @@ -280,7 +280,7 @@ std::vector> InputState::synthesizeCancelationEvents memento.policyFlags, AKEY_EVENT_ACTION_UP, memento.flags | AKEY_EVENT_FLAG_CANCELED, memento.keyCode, memento.scanCode, memento.metaState, - 0 /*repeatCount*/, memento.downTime)); + /*repeatCount=*/0, memento.downTime)); } } @@ -297,8 +297,8 @@ std::vector> InputState::synthesizeCancelationEvents std::make_unique(mIdGenerator.nextId(), currentTime, memento.deviceId, memento.source, memento.displayId, memento.policyFlags, - action, 0 /*actionButton*/, flags, AMETA_NONE, - 0 /*buttonState*/, MotionClassification::NONE, + action, /*actionButton=*/0, flags, AMETA_NONE, + /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision, memento.yPrecision, memento.xCursorPosition, @@ -359,8 +359,8 @@ std::vector> InputState::synthesizePointerDownEvents std::make_unique(mIdGenerator.nextId(), currentTime, memento.deviceId, memento.source, memento.displayId, memento.policyFlags, action, - 0 /*actionButton*/, memento.flags, AMETA_NONE, - 0 /*buttonState*/, MotionClassification::NONE, + /*actionButton=*/0, memento.flags, AMETA_NONE, + /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision, memento.yPrecision, memento.xCursorPosition, memento.yCursorPosition, memento.downTime, @@ -398,8 +398,8 @@ std::vector> InputState::synthesizeCancelationEvent events.push_back( std::make_unique(mIdGenerator.nextId(), currentTime, memento.deviceId, memento.source, memento.displayId, - memento.policyFlags, action, 0 /*actionButton*/, - flags, AMETA_NONE, 0 /*buttonState*/, + memento.policyFlags, action, /*actionButton=*/0, + flags, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision, memento.yPrecision, memento.xCursorPosition, @@ -425,9 +425,9 @@ std::vector> InputState::synthesizeCancelationEvent std::make_unique(mIdGenerator.nextId(), currentTime, memento.deviceId, memento.source, memento.displayId, memento.policyFlags, action, - 0 /*actionButton*/, + /*actionButton=*/0, memento.flags | AMOTION_EVENT_FLAG_CANCELED, - AMETA_NONE, 0 /*buttonState*/, + AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision, memento.yPrecision, memento.xCursorPosition, diff --git a/services/inputflinger/dispatcher/LatencyTracker.cpp b/services/inputflinger/dispatcher/LatencyTracker.cpp index 52f189c5c5..2dd7d3f529 100644 --- a/services/inputflinger/dispatcher/LatencyTracker.cpp +++ b/services/inputflinger/dispatcher/LatencyTracker.cpp @@ -148,7 +148,7 @@ void LatencyTracker::trackGraphicsLatency( void LatencyTracker::reportAndPruneMatureRecords(nsecs_t newEventTime) { while (!mEventTimes.empty()) { const auto& [oldestEventTime, oldestInputEventId] = *mEventTimes.begin(); - if (isMatureEvent(oldestEventTime, newEventTime /*now*/)) { + if (isMatureEvent(oldestEventTime, /*now=*/newEventTime)) { // Report and drop this event const auto it = mTimelines.find(oldestInputEventId); LOG_ALWAYS_FATAL_IF(it == mTimelines.end(), diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index f7b38a1b9e..d3d8021645 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -220,7 +220,7 @@ static bool isV4lTouchNode(std::string name) { * directly from /dev. */ static bool isV4lScanningEnabled() { - return property_get_bool("ro.input.video_enabled", true /* default_value */); + return property_get_bool("ro.input.video_enabled", /*default_value=*/true); } static nsecs_t processEventTimestamp(const struct input_event& event) { @@ -1006,7 +1006,7 @@ int32_t EventHub::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKey } int32_t outKeyCode; status_t mapKeyRes = - device->getKeyCharacterMap()->mapKey(scanCodes[0], 0 /*usageCode*/, &outKeyCode); + device->getKeyCharacterMap()->mapKey(scanCodes[0], /*usageCode=*/0, &outKeyCode); switch (mapKeyRes) { case OK: break; @@ -2544,7 +2544,7 @@ void EventHub::createVirtualKeyboardLocked() { std::unique_ptr device = std::make_unique(-1, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, "", - identifier, nullptr /*associatedDevice*/); + identifier, /*associatedDevice=*/nullptr); device->classes = InputDeviceClass::KEYBOARD | InputDeviceClass::ALPHAKEY | InputDeviceClass::DPAD | InputDeviceClass::VIRTUAL; device->loadKeyMapLocked(); diff --git a/services/inputflinger/reader/controller/PeripheralController.cpp b/services/inputflinger/reader/controller/PeripheralController.cpp index cedbacb046..a380b5eadf 100644 --- a/services/inputflinger/reader/controller/PeripheralController.cpp +++ b/services/inputflinger/reader/controller/PeripheralController.cpp @@ -152,7 +152,7 @@ std::optional PeripheralController::MonoLight::getLightColor() { return std::nullopt; } - return toArgb(brightness.value(), 0 /* red */, 0 /* green */, 0 /* blue */); + return toArgb(brightness.value(), /*red=*/0, /*green=*/0, /*blue=*/0); } std::optional PeripheralController::RgbLight::getLightColor() { @@ -197,13 +197,12 @@ std::optional PeripheralController::MultiColorLight::getLightColor() { } std::unordered_map intensities = ret.value(); // Get red, green, blue colors - int32_t color = toArgb(0 /* brightness */, intensities.at(LightColor::RED) /* red */, - intensities.at(LightColor::GREEN) /* green */, - intensities.at(LightColor::BLUE) /* blue */); + int32_t color = toArgb(/*brightness=*/0, intensities.at(LightColor::RED), + intensities.at(LightColor::GREEN), intensities.at(LightColor::BLUE)); // Get brightness std::optional brightness = getRawLightBrightness(rawId); if (brightness.has_value()) { - return toArgb(brightness.value() /* A */, 0, 0, 0) | color; + return toArgb(/*brightness=*/brightness.value(), 0, 0, 0) | color; } return std::nullopt; } @@ -273,7 +272,7 @@ void PeripheralController::populateDeviceInfo(InputDeviceInfo* deviceInfo) { for (const auto& [lightId, light] : mLights) { // Input device light doesn't support ordinal, always pass 1. InputDeviceLightInfo lightInfo(light->name, light->id, light->type, light->capabilityFlags, - 1 /* ordinal */); + /*ordinal=*/1); deviceInfo->addLightInfo(lightInfo); } } diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp index 929bf18ffe..d7f8b17490 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp @@ -301,7 +301,7 @@ std::list JoystickInputMapper::process(const RawEvent* rawEvent) { case EV_SYN: switch (rawEvent->code) { case SYN_REPORT: - out += sync(rawEvent->when, rawEvent->readTime, false /*force*/); + out += sync(rawEvent->when, rawEvent->readTime, /*force=*/false); break; } break; diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index d147d60f4e..0361bc670a 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -440,7 +440,7 @@ std::list KeyboardInputMapper::cancelAllDownKeys(nsecs_t when) { for (size_t i = 0; i < n; i++) { out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, systemTime(SYSTEM_TIME_MONOTONIC), getDeviceId(), mSource, - getDisplayId(), 0 /*policyFlags*/, AKEY_EVENT_ACTION_UP, + getDisplayId(), /*policyFlags=*/0, AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED, mKeyDowns[i].keyCode, mKeyDowns[i].scanCode, AMETA_NONE, mKeyDowns[i].downTime)); diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp index 633efc6047..33e72c7c6f 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp @@ -202,10 +202,10 @@ void MultiTouchInputMapper::configureRawPointerAxes() { slotCount = MAX_SLOTS; } mMultiTouchMotionAccumulator.configure(getDeviceContext(), slotCount, - true /*usingSlotsProtocol*/); + /*usingSlotsProtocol=*/true); } else { mMultiTouchMotionAccumulator.configure(getDeviceContext(), MAX_POINTERS, - false /*usingSlotsProtocol*/); + /*usingSlotsProtocol=*/false); } } diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp index d81022f6e5..f797bc9c0b 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp @@ -208,10 +208,10 @@ SensorInputMapper::Sensor SensorInputMapper::createSensor(InputDeviceSensorType InputDeviceSensorInfo sensorInfo(identifier.name, std::to_string(identifier.vendor), identifier.version, sensorType, InputDeviceSensorAccuracy::ACCURACY_HIGH, - axis.max /* maxRange */, axis.scale /* resolution */, - 0.0f /* power */, 0 /* minDelay */, - 0 /* fifoReservedEventCount */, 0 /* fifoMaxEventCount */, - ftl::enum_string(sensorType), 0 /* maxDelay */, 0 /* flags */, + /*maxRange=*/axis.max, /*resolution=*/axis.scale, + /*power=*/0.0f, /*minDelay=*/0, + /*fifoReservedEventCount=*/0, /*fifoMaxEventCount=*/0, + ftl::enum_string(sensorType), /*maxDelay=*/0, /*flags=*/0, getDeviceId()); std::string prefix = "sensor." + ftl::enum_string(sensorType); @@ -277,7 +277,7 @@ std::list SensorInputMapper::process(const RawEvent* rawEvent) { Axis& axis = pair.second; axis.currentValue = axis.newValue; } - out += sync(rawEvent->when, false /*force*/); + out += sync(rawEvent->when, /*force=*/false); break; } break; @@ -344,7 +344,7 @@ bool SensorInputMapper::enableSensor(InputDeviceSensorType sensorType, maxBatchReportLatency.count()); } - if (!setSensorEnabled(sensorType, true /* enabled */)) { + if (!setSensorEnabled(sensorType, /*enabled=*/true)) { return false; } @@ -367,7 +367,7 @@ void SensorInputMapper::disableSensor(InputDeviceSensorType sensorType) { ALOGD("Disable Sensor %s", ftl::enum_string(sensorType).c_str()); } - if (!setSensorEnabled(sensorType, false /* enabled */)) { + if (!setSensorEnabled(sensorType, /*enabled=*/false)) { return; } @@ -413,9 +413,9 @@ std::list SensorInputMapper::sync(nsecs_t when, bool force) { out.push_back(NotifySensorArgs(getContext()->getNextId(), when, getDeviceId(), AINPUT_SOURCE_SENSOR, sensorType, sensor.sensorInfo.accuracy, - sensor.accuracy != - sensor.sensorInfo.accuracy /* accuracyChanged */, - timestamp /* hwTimestamp */, values)); + /*accuracyChanged=*/sensor.accuracy != + sensor.sensorInfo.accuracy, + /*hwTimestamp=*/timestamp, values)); sensor.lastSampleTimeNs = timestamp; sensor.accuracy = sensor.sensorInfo.accuracy; } diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp index c9101cadc6..c4564a4380 100644 --- a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp @@ -59,7 +59,7 @@ std::list SwitchInputMapper::sync(nsecs_t when) { std::list out; if (mUpdatedSwitchMask) { uint32_t updatedSwitchValues = mSwitchValues & mUpdatedSwitchMask; - out.push_back(NotifySwitchArgs(getContext()->getNextId(), when, 0 /*policyFlags*/, + out.push_back(NotifySwitchArgs(getContext()->getNextId(), when, /*policyFlags=*/0, updatedSwitchValues, mUpdatedSwitchMask)); mUpdatedSwitchMask = 0; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 0c57628d80..d415854e15 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -1455,7 +1455,7 @@ std::list TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { next.rawPointerData.hoveringIdBits.value); } - out += processRawTouches(false /*timeout*/); + out += processRawTouches(/*timeout=*/false); return out; } @@ -1749,11 +1749,11 @@ std::list TouchInputMapper::timeoutExpired(nsecs_t when) { if (mPointerUsage == PointerUsage::GESTURES) { // Since this is a synthetic event, we can consider its latency to be zero const nsecs_t readTime = when; - out += dispatchPointerGestures(when, readTime, 0 /*policyFlags*/, true /*isTimeout*/); + out += dispatchPointerGestures(when, readTime, /*policyFlags=*/0, /*isTimeout=*/true); } } else if (mDeviceMode == DeviceMode::DIRECT) { if (mExternalStylusFusionTimeout <= when) { - out += processRawTouches(true /*timeout*/); + out += processRawTouches(/*timeout=*/true); } else if (mExternalStylusFusionTimeout != LLONG_MAX) { getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout); } @@ -1772,7 +1772,7 @@ std::list TouchInputMapper::updateExternalStylusState(const StylusSt // - Only the button state, which is not reported through a specific pointer, has changed. // Go ahead and dispatch now that we have fresh stylus data. mExternalStylusDataPending = true; - out += processRawTouches(false /*timeout*/); + out += processRawTouches(/*timeout=*/false); } return out; } @@ -2373,7 +2373,7 @@ std::list TouchInputMapper::dispatchPointerUsage(nsecs_t when, nsecs switch (mPointerUsage) { case PointerUsage::GESTURES: - out += dispatchPointerGestures(when, readTime, policyFlags, false /*isTimeout*/); + out += dispatchPointerGestures(when, readTime, policyFlags, /*isTimeout=*/false); break; case PointerUsage::STYLUS: out += dispatchPointerStylus(when, readTime, policyFlags); @@ -3710,8 +3710,8 @@ NotifyMotionArgs TouchInputMapper::dispatchMotion( std::list TouchInputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) { std::list out; - out += abortPointerUsage(when, readTime, 0 /*policyFlags*/); - out += abortTouches(when, readTime, 0 /* policyFlags*/); + out += abortPointerUsage(when, readTime, /*policyFlags=*/0); + out += abortTouches(when, readTime, /* policyFlags=*/0); return out; } diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp index 66017024eb..6b84f32db2 100644 --- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp @@ -115,7 +115,7 @@ void TouchButtonAccumulator::processMappedKey(int32_t scanCode, bool down) { int32_t keyCode, metaState; uint32_t flags; if (mDeviceContext.mapKey(scanCode, mHidUsageAccumulator.consumeCurrentHidUsage(), - 0 /*metaState*/, &keyCode, &metaState, &flags) != OK) { + /*metaState=*/0, &keyCode, &metaState, &flags) != OK) { return; } switch (keyCode) { diff --git a/services/inputflinger/tests/FocusResolver_test.cpp b/services/inputflinger/tests/FocusResolver_test.cpp index 5d5cf9c108..ccdb37afb0 100644 --- a/services/inputflinger/tests/FocusResolver_test.cpp +++ b/services/inputflinger/tests/FocusResolver_test.cpp @@ -55,11 +55,11 @@ TEST(FocusResolverTest, SetFocusedWindow) { sp unfocusableWindowToken = sp::make(); std::vector> windows; windows.push_back(sp::make("Focusable", focusableWindowToken, - true /* focusable */, true /* visible */)); + /*focusable=*/true, /*visible=*/true)); windows.push_back(sp::make("Invisible", invisibleWindowToken, - true /* focusable */, false /* visible */)); + /*focusable=*/true, /*visible=*/false)); windows.push_back(sp::make("unfocusable", unfocusableWindowToken, - false /* focusable */, true /* visible */)); + /*focusable=*/false, /*visible=*/true)); // focusable window can get focused FocusRequest request; @@ -88,7 +88,7 @@ TEST(FocusResolverTest, RemoveFocusFromFocusedWindow) { sp focusableWindowToken = sp::make(); std::vector> windows; windows.push_back(sp::make("Focusable", focusableWindowToken, - true /* focusable */, true /* visible */)); + /*focusable=*/true, /*visible=*/true)); FocusRequest request; request.displayId = 42; @@ -114,19 +114,19 @@ TEST(FocusResolverTest, SetFocusedMirroredWindow) { sp unfocusableWindowToken = sp::make(); std::vector> windows; windows.push_back(sp::make("Mirror1", focusableWindowToken, - true /* focusable */, true /* visible */)); + /*focusable=*/true, /*visible=*/true)); windows.push_back(sp::make("Mirror1", focusableWindowToken, - true /* focusable */, true /* visible */)); + /*focusable=*/true, /*visible=*/true)); windows.push_back(sp::make("Mirror2Visible", invisibleWindowToken, - true /* focusable */, true /* visible */)); + /*focusable=*/true, /*visible=*/true)); windows.push_back(sp::make("Mirror2Invisible", invisibleWindowToken, - true /* focusable */, false /* visible */)); + /*focusable=*/true, /*visible=*/false)); windows.push_back(sp::make("Mirror3Focusable", unfocusableWindowToken, - true /* focusable */, true /* visible */)); + /*focusable=*/true, /*visible=*/true)); windows.push_back(sp::make("Mirror3Unfocusable", unfocusableWindowToken, - false /* focusable */, true /* visible */)); + /*focusable=*/false, /*visible=*/true)); // mirrored window can get focused FocusRequest request; @@ -152,8 +152,8 @@ TEST(FocusResolverTest, SetInputWindows) { sp focusableWindowToken = sp::make(); std::vector> windows; sp window = - sp::make("Focusable", focusableWindowToken, true /* focusable */, - true /* visible */); + sp::make("Focusable", focusableWindowToken, /*focusable=*/true, + /*visible=*/true); windows.push_back(window); // focusable window can get focused @@ -176,8 +176,8 @@ TEST(FocusResolverTest, FocusRequestsCanBePending) { std::vector> windows; sp invisibleWindow = - sp::make("Invisible", invisibleWindowToken, true /* focusable */, - false /* visible */); + sp::make("Invisible", invisibleWindowToken, /*focusable=*/true, + /*visible=*/false); windows.push_back(invisibleWindow); // invisible window cannot get focused @@ -200,8 +200,8 @@ TEST(FocusResolverTest, FocusRequestsArePersistent) { std::vector> windows; sp window = - sp::make("Test Window", windowToken, false /* focusable */, - true /* visible */); + sp::make("Test Window", windowToken, /*focusable=*/false, + /*visible=*/true); windows.push_back(window); // non-focusable window cannot get focused @@ -242,13 +242,13 @@ TEST(FocusResolverTest, ConditionalFocusRequestsAreNotPersistent) { std::vector> windows; sp hostWindow = - sp::make("Host Window", hostWindowToken, true /* focusable */, - true /* visible */); + sp::make("Host Window", hostWindowToken, /*focusable=*/true, + /*visible=*/true); windows.push_back(hostWindow); sp embeddedWindowToken = sp::make(); sp embeddedWindow = - sp::make("Embedded Window", embeddedWindowToken, true /* focusable */, - true /* visible */); + sp::make("Embedded Window", embeddedWindowToken, /*focusable=*/true, + /*visible=*/true); windows.push_back(embeddedWindow); FocusRequest request; @@ -293,8 +293,8 @@ TEST(FocusResolverTest, FocusRequestsAreClearedWhenWindowIsRemoved) { std::vector> windows; sp window = - sp::make("Test Window", windowToken, true /* focusable */, - true /* visible */); + sp::make("Test Window", windowToken, /*focusable=*/true, + /*visible=*/true); windows.push_back(window); FocusRequest request; diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 20a1977813..b1b6e058e4 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -160,7 +160,7 @@ MATCHER_P2(WithCoords, x, y, "MotionEvent with specified coordinates") { *result_listener << "Expected 1 pointer, got " << arg.getPointerCount(); return false; } - return arg.getX(0 /*pointerIndex*/) == x && arg.getY(0 /*pointerIndex*/) == y; + return arg.getX(/*pointerIndex=*/0) == x && arg.getY(/*pointerIndex=*/0) == y; } MATCHER_P(WithPointers, pointers, "MotionEvent with specified pointers") { @@ -546,7 +546,7 @@ private: /** We simply reconstruct NotifySwitchArgs in policy because InputDispatcher is * essentially a passthrough for notifySwitch. */ - mLastNotifySwitch = NotifySwitchArgs(1 /*id*/, when, policyFlags, switchValues, switchMask); + mLastNotifySwitch = NotifySwitchArgs(/*id=*/1, when, policyFlags, switchValues, switchMask); } void pokeUserActivity(nsecs_t, int32_t, int32_t) override {} @@ -635,7 +635,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) { /*action*/ -1, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject key events with undefined action."; @@ -644,7 +644,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) { INVALID_HMAC, AKEY_EVENT_ACTION_MULTIPLE, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject key events with ACTION_MULTIPLE."; } @@ -674,7 +674,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with undefined action."; @@ -686,7 +686,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with pointer down index too large."; @@ -698,7 +698,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { identityTransform, ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with pointer down index too small."; @@ -710,7 +710,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with pointer up index too large."; @@ -722,7 +722,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { identityTransform, ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with pointer up index too small."; @@ -734,7 +734,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 0, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with 0 pointers."; @@ -745,7 +745,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ MAX_POINTERS + 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with more than MAX_POINTERS pointers."; @@ -758,7 +758,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with pointer ids less than 0."; @@ -770,7 +770,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 1, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with pointer ids greater than MAX_POINTER_ID."; @@ -784,7 +784,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, /*pointerCount*/ 2, pointerProperties, pointerCoords); ASSERT_EQ(InputEventInjectionResult::FAILED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) << "Should reject motion events with duplicate pointer ids."; } @@ -793,7 +793,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { TEST_F(InputDispatcherTest, NotifyConfigurationChanged_CallsPolicy) { constexpr nsecs_t eventTime = 20; - NotifyConfigurationChangedArgs args(10 /*id*/, eventTime); + NotifyConfigurationChangedArgs args(/*id=*/10, eventTime); mDispatcher->notifyConfigurationChanged(&args); ASSERT_TRUE(mDispatcher->waitForIdle()); @@ -801,8 +801,8 @@ TEST_F(InputDispatcherTest, NotifyConfigurationChanged_CallsPolicy) { } TEST_F(InputDispatcherTest, NotifySwitch_CallsPolicy) { - NotifySwitchArgs args(10 /*id*/, 20 /*eventTime*/, 0 /*policyFlags*/, 1 /*switchValues*/, - 2 /*switchMask*/); + NotifySwitchArgs args(/*id=*/10, /*eventTime=*/20, /*policyFlags=*/0, /*switchValues=*/1, + /*switchMask=*/2); mDispatcher->notifySwitch(&args); // InputDispatcher adds POLICY_FLAG_TRUSTED because the event went through InputListener @@ -863,7 +863,7 @@ public: std::chrono::time_point start = std::chrono::steady_clock::now(); status_t status = WOULD_BLOCK; while (status == WOULD_BLOCK) { - status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, + status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event); std::chrono::duration elapsed = std::chrono::steady_clock::now() - start; if (elapsed > 100ms) { @@ -1407,8 +1407,8 @@ static InputEventInjectionResult injectKey( // Define a valid key down event. event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, displayId, - INVALID_HMAC, action, /* flags */ 0, AKEYCODE_A, KEY_A, AMETA_NONE, - repeatCount, currentTime, currentTime); + INVALID_HMAC, action, /*flags=*/0, AKEYCODE_A, KEY_A, AMETA_NONE, repeatCount, + currentTime, currentTime); if (!allowKeyRepeat) { policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT; @@ -1419,7 +1419,7 @@ static InputEventInjectionResult injectKey( static InputEventInjectionResult injectKeyDown(const std::unique_ptr& dispatcher, int32_t displayId = ADISPLAY_ID_NONE) { - return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /* repeatCount */ 0, displayId); + return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, displayId); } // Inject a down event that has key repeat disabled. This allows InputDispatcher to idle without @@ -1427,14 +1427,14 @@ static InputEventInjectionResult injectKeyDown(const std::unique_ptr& dispatcher, int32_t displayId = ADISPLAY_ID_NONE) { - return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /* repeatCount */ 0, displayId, + return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, displayId, InputEventInjectionSync::WAIT_FOR_RESULT, INJECT_EVENT_TIMEOUT, - /* allowKeyRepeat */ false); + /*allowKeyRepeat=*/false); } static InputEventInjectionResult injectKeyUp(const std::unique_ptr& dispatcher, int32_t displayId = ADISPLAY_ID_NONE) { - return injectKey(dispatcher, AKEY_EVENT_ACTION_UP, /* repeatCount */ 0, displayId); + return injectKey(dispatcher, AKEY_EVENT_ACTION_UP, /*repeatCount=*/0, displayId); } class PointerBuilder { @@ -1590,7 +1590,7 @@ static InputEventInjectionResult injectMotionEvent( .eventTime(eventTime) .rawXCursorPosition(cursorPosition.x) .rawYCursorPosition(cursorPosition.y) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER) .x(position.x) .y(position.y)) .build(); @@ -1615,7 +1615,7 @@ static InputEventInjectionResult injectMotionUp(const std::unique_ptrconsumeMotionPointerDown(1 /* pointerIndex */); - wallpaperWindow->consumeMotionPointerDown(1 /* pointerIndex */, ADISPLAY_ID_DEFAULT, + foregroundWindow->consumeMotionPointerDown(/*pointerIndex=*/1); + wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); const MotionEvent secondFingerUpEvent = MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(100) - .y(100)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(150) - .y(150)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT, @@ -1979,12 +1971,8 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(100) - .y(100)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(300) - .y(100)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -1994,7 +1982,7 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { leftWindow->consumeMotionMove(); // Since the touch is split, right window gets ACTION_DOWN rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); - wallpaperWindow->consumeMotionPointerDown(1 /* pointerIndex */, ADISPLAY_ID_DEFAULT, + wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); // Now, leftWindow, which received the first finger, disappears. @@ -2008,12 +1996,8 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { const MotionEvent secondFingerMoveEvent = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(100) - .y(100)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(310) - .y(110)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(310).y(110)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT, @@ -2801,7 +2785,7 @@ TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsKeyStream) { // When device reset happens, that key stream should be terminated with FLAG_CANCELED // on the app side. - NotifyDeviceResetArgs args(10 /*id*/, 20 /*eventTime*/, DEVICE_ID); + NotifyDeviceResetArgs args(/*id=*/10, /*eventTime=*/20, DEVICE_ID); mDispatcher->notifyDeviceReset(&args); window->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT, AKEY_EVENT_FLAG_CANCELED); @@ -2824,7 +2808,7 @@ TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsMotionStream) { // When device reset happens, that motion stream should be terminated with ACTION_CANCEL // on the app side. - NotifyDeviceResetArgs args(10 /*id*/, 20 /*eventTime*/, DEVICE_ID); + NotifyDeviceResetArgs args(/*id=*/10, /*eventTime=*/20, DEVICE_ID); mDispatcher->notifyDeviceReset(&args); window->consumeMotionEvent( AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); @@ -3006,10 +2990,8 @@ TEST_F(InputDispatcherTest, NonSplitTouchableWindowReceivesMultiTouch) { MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(-30) - .y(-50)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(-30).y(-50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -3126,7 +3108,7 @@ TEST_F(InputDispatcherDisplayProjectionTest, InjectionWithTransformInLogicalDisp MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER) .x(untransformedPoint.x) .y(untransformedPoint.y)) .build(); @@ -3410,7 +3392,7 @@ INSTANTIATE_TEST_SUITE_P(TransferFunctionTests, TransferTouchFixture, [&](const std::unique_ptr& dispatcher, sp from, sp to) { return dispatcher->transferTouchFocus(from, to, - false /*isDragAndDrop*/); + /*isDragAndDrop=*/false); })); TEST_F(InputDispatcherTest, TransferTouchFocus_TwoPointersSplitTouch) { @@ -3783,7 +3765,7 @@ TEST_F(InputDispatcherTest, SendTimeline_DoesNotCrashDispatcher) { graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 2; graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 3; - window->sendTimeline(1 /*inputEventId*/, graphicsTimeline); + window->sendTimeline(/*inputEventId=*/1, graphicsTimeline); window->assertNoEvents(); mDispatcher->waitForIdle(); } @@ -3834,7 +3816,7 @@ public: int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, action, ADISPLAY_ID_DEFAULT, - 0 /*expectedFlags*/); + /*expectedFlags=*/0); } MotionEvent* consumeMotion() { @@ -4001,7 +3983,7 @@ TEST_F(InputDispatcherTest, TestMoveEvent) { mDispatcher->notifyMotion(&motionArgs); window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE, ADISPLAY_ID_DEFAULT, - 0 /*expectedFlags*/); + /*expectedFlags=*/0); } /** @@ -4022,35 +4004,35 @@ TEST_F(InputDispatcherTest, TouchModeState_IsSentToApps) { SCOPED_TRACE("Check default value of touch mode"); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); setFocusedWindow(window); - window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); SCOPED_TRACE("Remove the window to trigger focus loss"); window->setFocusable(false); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); - window->consumeFocusEvent(false /*hasFocus*/, true /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/false, /*inTouchMode=*/true); SCOPED_TRACE("Disable touch mode"); mDispatcher->setInTouchMode(false, windowInfo.ownerPid, windowInfo.ownerUid, - true /*hasPermission*/, ADISPLAY_ID_DEFAULT); + /*hasPermission=*/true, ADISPLAY_ID_DEFAULT); window->consumeTouchModeEvent(false); window->setFocusable(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); setFocusedWindow(window); - window->consumeFocusEvent(true /*hasFocus*/, false /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/false); SCOPED_TRACE("Remove the window to trigger focus loss"); window->setFocusable(false); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); - window->consumeFocusEvent(false /*hasFocus*/, false /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/false, /*inTouchMode=*/false); SCOPED_TRACE("Enable touch mode again"); mDispatcher->setInTouchMode(true, windowInfo.ownerPid, windowInfo.ownerUid, - true /*hasPermission*/, ADISPLAY_ID_DEFAULT); + /*hasPermission=*/true, ADISPLAY_ID_DEFAULT); window->consumeTouchModeEvent(true); window->setFocusable(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); setFocusedWindow(window); - window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); window->assertNoEvents(); } @@ -4066,7 +4048,7 @@ TEST_F(InputDispatcherTest, VerifyInputEvent_KeyEvent) { mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); setFocusedWindow(window); - window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN); mDispatcher->notifyKey(&keyArgs); @@ -4327,8 +4309,8 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_DeferInvisibleWindow) { // Injected key goes to pending queue. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, - ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE)); + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::NONE)); // Window does not get focus event or key down. window->assertNoEvents(); @@ -4485,23 +4467,23 @@ protected: // Window should receive key down event. mWindow->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT, - 0 /*expectedFlags*/); + /*expectedFlags=*/0); } }; TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_ReceivesKeyRepeat) { - sendAndConsumeKeyDown(1 /* deviceId */); + sendAndConsumeKeyDown(/*deviceId=*/1); for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) { expectKeyRepeatOnce(repeatCount); } } TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_ReceivesKeyRepeatFromTwoDevices) { - sendAndConsumeKeyDown(1 /* deviceId */); + sendAndConsumeKeyDown(/*deviceId=*/1); for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) { expectKeyRepeatOnce(repeatCount); } - sendAndConsumeKeyDown(2 /* deviceId */); + sendAndConsumeKeyDown(/*deviceId=*/2); /* repeatCount will start from 1 for deviceId 2 */ for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) { expectKeyRepeatOnce(repeatCount); @@ -4509,42 +4491,42 @@ TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_ReceivesKeyRepeatFromTwoDevic } TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_StopsKeyRepeatAfterUp) { - sendAndConsumeKeyDown(1 /* deviceId */); - expectKeyRepeatOnce(1 /*repeatCount*/); - sendAndConsumeKeyUp(1 /* deviceId */); + sendAndConsumeKeyDown(/*deviceId=*/1); + expectKeyRepeatOnce(/*repeatCount=*/1); + sendAndConsumeKeyUp(/*deviceId=*/1); mWindow->assertNoEvents(); } TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_KeyRepeatAfterStaleDeviceKeyUp) { - sendAndConsumeKeyDown(1 /* deviceId */); - expectKeyRepeatOnce(1 /*repeatCount*/); - sendAndConsumeKeyDown(2 /* deviceId */); - expectKeyRepeatOnce(1 /*repeatCount*/); + sendAndConsumeKeyDown(/*deviceId=*/1); + expectKeyRepeatOnce(/*repeatCount=*/1); + sendAndConsumeKeyDown(/*deviceId=*/2); + expectKeyRepeatOnce(/*repeatCount=*/1); // Stale key up from device 1. - sendAndConsumeKeyUp(1 /* deviceId */); + sendAndConsumeKeyUp(/*deviceId=*/1); // Device 2 is still down, keep repeating - expectKeyRepeatOnce(2 /*repeatCount*/); - expectKeyRepeatOnce(3 /*repeatCount*/); + expectKeyRepeatOnce(/*repeatCount=*/2); + expectKeyRepeatOnce(/*repeatCount=*/3); // Device 2 key up - sendAndConsumeKeyUp(2 /* deviceId */); + sendAndConsumeKeyUp(/*deviceId=*/2); mWindow->assertNoEvents(); } TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_KeyRepeatStopsAfterRepeatingKeyUp) { - sendAndConsumeKeyDown(1 /* deviceId */); - expectKeyRepeatOnce(1 /*repeatCount*/); - sendAndConsumeKeyDown(2 /* deviceId */); - expectKeyRepeatOnce(1 /*repeatCount*/); + sendAndConsumeKeyDown(/*deviceId=*/1); + expectKeyRepeatOnce(/*repeatCount=*/1); + sendAndConsumeKeyDown(/*deviceId=*/2); + expectKeyRepeatOnce(/*repeatCount=*/1); // Device 2 which holds the key repeating goes up, expect the repeating to stop. - sendAndConsumeKeyUp(2 /* deviceId */); + sendAndConsumeKeyUp(/*deviceId=*/2); // Device 1 still holds key down, but the repeating was already stopped mWindow->assertNoEvents(); } TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_StopsKeyRepeatAfterDisableInputDevice) { sendAndConsumeKeyDown(DEVICE_ID); - expectKeyRepeatOnce(1 /*repeatCount*/); - NotifyDeviceResetArgs args(10 /*id*/, 20 /*eventTime*/, DEVICE_ID); + expectKeyRepeatOnce(/*repeatCount=*/1); + NotifyDeviceResetArgs args(/*id=*/10, /*eventTime=*/20, DEVICE_ID); mDispatcher->notifyDeviceReset(&args); mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT, AKEY_EVENT_FLAG_CANCELED | AKEY_EVENT_FLAG_LONG_PRESS); @@ -4552,7 +4534,7 @@ TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_StopsKeyRepeatAfterDisableInp } TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseEventIdFromInputDispatcher) { - sendAndConsumeKeyDown(1 /* deviceId */); + sendAndConsumeKeyDown(/*deviceId=*/1); for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) { InputEvent* repeatEvent = mWindow->consume(); ASSERT_NE(nullptr, repeatEvent) << "Didn't receive event with repeat count " << repeatCount; @@ -4562,7 +4544,7 @@ TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseEventIdFrom } TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseUniqueEventId) { - sendAndConsumeKeyDown(1 /* deviceId */); + sendAndConsumeKeyDown(/*deviceId=*/1); std::unordered_set idSet; for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) { @@ -4908,11 +4890,11 @@ protected: const nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC); event.initialize(InputEvent::nextId(), injectedDeviceId, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, 0, AKEYCODE_A, - KEY_A, AMETA_NONE, 0 /*repeatCount*/, eventTime, eventTime); + KEY_A, AMETA_NONE, /*repeatCount=*/0, eventTime, eventTime); const int32_t additionalPolicyFlags = POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_DISABLE_KEY_REPEAT; ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, policyFlags | additionalPolicyFlags)); @@ -4947,7 +4929,7 @@ protected: const int32_t additionalPolicyFlags = POLICY_FLAG_PASS_TO_USER; ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - mDispatcher->injectInputEvent(&event, {} /*targetUid*/, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, policyFlags | additionalPolicyFlags)); @@ -4967,26 +4949,26 @@ TEST_F(InputFilterInjectionPolicyTest, TrustedFilteredEvents_KeepOriginalDeviceI // Must have POLICY_FLAG_FILTERED here to indicate that the event has gone through the input // filter. Without it, the event will no different from a regularly injected event, and the // injected device id will be overwritten. - testInjectedKey(POLICY_FLAG_FILTERED, 3 /*injectedDeviceId*/, 3 /*resolvedDeviceId*/, - 0 /*flags*/); + testInjectedKey(POLICY_FLAG_FILTERED, /*injectedDeviceId=*/3, /*resolvedDeviceId=*/3, + /*flags=*/0); } TEST_F(InputFilterInjectionPolicyTest, KeyEventsInjectedFromAccessibility_HaveAccessibilityFlag) { testInjectedKey(POLICY_FLAG_FILTERED | POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY, - 3 /*injectedDeviceId*/, 3 /*resolvedDeviceId*/, + /*injectedDeviceId=*/3, /*resolvedDeviceId=*/3, AKEY_EVENT_FLAG_IS_ACCESSIBILITY_EVENT); } TEST_F(InputFilterInjectionPolicyTest, MotionEventsInjectedFromAccessibility_HaveAccessibilityFlag) { testInjectedMotion(POLICY_FLAG_FILTERED | POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY, - 3 /*injectedDeviceId*/, 3 /*resolvedDeviceId*/, + /*injectedDeviceId=*/3, /*resolvedDeviceId=*/3, AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT); } TEST_F(InputFilterInjectionPolicyTest, RegularInjectedEvents_ReceiveVirtualDeviceId) { - testInjectedKey(0 /*policyFlags*/, 3 /*injectedDeviceId*/, - VIRTUAL_KEYBOARD_ID /*resolvedDeviceId*/, 0 /*flags*/); + testInjectedKey(/*policyFlags=*/0, /*injectedDeviceId=*/3, + /*resolvedDeviceId=*/VIRTUAL_KEYBOARD_ID, /*flags=*/0); } class InputDispatcherOnPointerDownOutsideFocus : public InputDispatcherTest { @@ -5085,7 +5067,7 @@ TEST_F(InputDispatcherOnPointerDownOutsideFocus, NoFocusChangeFlag) { const MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(20).y(20)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(20).y(20)) .addFlag(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, event)) @@ -5362,9 +5344,9 @@ TEST_F(InputDispatcherSingleWindowAnr, WhenFocusedApplicationChanges_NoAnr) { mWindow->consumeFocusEvent(false); InputEventInjectionResult result = - injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /*repeatCount*/, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::NONE, 10ms /*injectionTimeout*/, - false /* allowKeyRepeat */); + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::NONE, /*injectionTimeout=*/10ms, + /*allowKeyRepeat=*/false); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result); // Key will not go to window because we have no focused window. // The 'no focused window' ANR timer should start instead. @@ -5426,8 +5408,8 @@ TEST_F(InputDispatcherSingleWindowAnr, FocusedApplication_NoFocusedWindow) { // We specify the injection timeout to be smaller than the application timeout, to ensure that // injection times out (instead of failing). const InputEventInjectionResult result = - injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, false /* allowKeyRepeat */); + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, /*allowKeyRepeat=*/false); ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result); const std::chrono::duration timeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT); mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(timeout, mApplication); @@ -5450,12 +5432,12 @@ TEST_F(InputDispatcherSingleWindowAnr, StaleKeyEventDoesNotAnr) { // Define a valid key down event that is stale (too old). event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, /* flags */ 0, AKEYCODE_A, KEY_A, - AMETA_NONE, 1 /*repeatCount*/, eventTime, eventTime); + AMETA_NONE, /*repeatCount=*/1, eventTime, eventTime); const int32_t policyFlags = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER; InputEventInjectionResult result = - mDispatcher->injectInputEvent(&event, {} /* targetUid */, + mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::WAIT_FOR_RESULT, INJECT_EVENT_TIMEOUT, policyFlags); ASSERT_EQ(InputEventInjectionResult::FAILED, result) @@ -5477,8 +5459,8 @@ TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DoesNotSendDuplicateAnr) // We specify the injection timeout to be smaller than the application timeout, to ensure that // injection times out (instead of failing). const InputEventInjectionResult result = - injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, false /* allowKeyRepeat */); + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, /*allowKeyRepeat=*/false); ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result); const std::chrono::duration appTimeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT); @@ -5501,7 +5483,7 @@ TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DropsFocusedEvents) { // Once a focused event arrives, we get an ANR for this application const InputEventInjectionResult result = - injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT, + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, InputEventInjectionSync::WAIT_FOR_RESULT, 10ms); ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result); @@ -5717,7 +5699,7 @@ TEST_F(InputDispatcherSingleWindowAnr, Key_StaysPendingWhileMotionIsProcessed) { // we will receive INJECTION_TIMED_OUT as the result. InputEventInjectionResult result = - injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT, + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, InputEventInjectionSync::WAIT_FOR_RESULT, 10ms); ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result); // Key will not be sent to the window, yet, because the window is still processing events @@ -5752,8 +5734,8 @@ TEST_F(InputDispatcherSingleWindowAnr, // Don't finish the events yet, and send a key // Injection is async, so it will succeed ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, - ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE)); + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::NONE)); // At this point, key is still pending, and should not be sent to the application yet. std::optional keySequenceNum = mWindow->receiveEvent(); ASSERT_FALSE(keySequenceNum); @@ -5835,7 +5817,7 @@ TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsive) { << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; mFocusedWindow->consumeMotionDown(); mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE, - ADISPLAY_ID_DEFAULT, 0 /*flags*/); + ADISPLAY_ID_DEFAULT, /*flags=*/0); // We consumed all events, so no ANR ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertNotifyAnrWasNotCalled(); @@ -5909,7 +5891,7 @@ TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsiveWithSameTimeout TEST_F(InputDispatcherMultiWindowAnr, DuringAnr_SecondTapIsIgnored) { tapOnFocusedWindow(); mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE, - ADISPLAY_ID_DEFAULT, 0 /*flags*/); + ADISPLAY_ID_DEFAULT, /*flags=*/0); // Receive the events, but don't respond std::optional downEventSequenceNum = mFocusedWindow->receiveEvent(); // ACTION_DOWN ASSERT_TRUE(downEventSequenceNum); @@ -5998,8 +5980,8 @@ TEST_F(InputDispatcherMultiWindowAnr, PendingKey_GoesToNewlyFocusedWindow) { // window even if motions are still being processed. InputEventInjectionResult result = - injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /*repeatCount*/, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::NONE, 10ms /*injectionTimeout*/); + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::NONE, /*injectionTimeout=*/10ms); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result); // Key will not be sent to the window, yet, because the window is still processing events // and the key remains pending, waiting for the touch events to be processed @@ -6038,7 +6020,7 @@ TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) { ADISPLAY_ID_DEFAULT, {FOCUSED_WINDOW_LOCATION}); mDispatcher->notifyMotion(&motionArgs); mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE, - ADISPLAY_ID_DEFAULT, 0 /*flags*/); + ADISPLAY_ID_DEFAULT, /*flags=*/0); // Touch Window 2 motionArgs = generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, @@ -6100,9 +6082,9 @@ TEST_F(InputDispatcherMultiWindowAnr, FocusedWindowWithoutSetFocusedApplication_ // 'focusedApplication' will get blamed if this timer completes. // Key will not be sent anywhere because we have no focused window. It will remain pending. InputEventInjectionResult result = - injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /*repeatCount*/, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::NONE, 10ms /*injectionTimeout*/, - false /* allowKeyRepeat */); + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::NONE, /*injectionTimeout=*/10ms, + /*allowKeyRepeat=*/false); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result); // Wait until dispatcher starts the "no focused window" timer. If we don't wait here, @@ -6152,7 +6134,7 @@ class InputDispatcherMultiWindowOcclusionTests : public InputDispatcherTest { mNoInputWindow = sp::make(mApplication, mDispatcher, "Window without input channel", ADISPLAY_ID_DEFAULT, - std::make_optional>(nullptr) /*token*/); + /*token=*/std::make_optional>(nullptr)); mNoInputWindow->setNoInputChannel(true); mNoInputWindow->setFrame(Rect(0, 0, 100, 100)); // It's perfectly valid for this window to not have an associated input channel @@ -6342,8 +6324,8 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, DeferFocusWhenInvisible) { // Injected key goes to pending queue. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, - ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE)); + injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::NONE)); mMirror->setVisible(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mMirror}}}); @@ -6991,7 +6973,7 @@ protected: // Transfer touch focus to the drag window bool transferred = mDispatcher->transferTouchFocus(mWindow->getToken(), mDragWindow->getToken(), - true /* isDragDrop */); + /*isDragDrop=*/true); if (transferred) { mWindow->consumeMotionCancel(); mDragWindow->consumeMotionDown(); @@ -7047,8 +7029,8 @@ TEST_F(InputDispatcherDragTests, DragEnterAndPointerDownPilfersPointers) { const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(60).y(60)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(60).y(60)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -7191,14 +7173,14 @@ TEST_F(InputDispatcherDragTests, NoDragAndDropWhenMultiFingers) { MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(75).y(50)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(75).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeMotionPointerDown(1 /* pointerIndex */); + mWindow->consumeMotionPointerDown(/*pointerIndex=*/1); // Should not perform drag and drop when window has multi fingers. ASSERT_FALSE(startDrag(false)); @@ -7218,9 +7200,8 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) { MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer( - PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) + .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -7235,9 +7216,8 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) { const MotionEvent secondFingerMoveEvent = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer( - PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) + .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT, @@ -7250,9 +7230,8 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) { const MotionEvent secondFingerUpEvent = MotionEventBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer( - PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) + .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT, @@ -7282,7 +7261,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenMultiDisplays) { .y(100)) .build())); windowInSecondary->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN, - SECOND_DISPLAY_ID, 0 /* expectedFlag */); + SECOND_DISPLAY_ID, /*expectedFlag=*/0); // Update window again. mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {windowInSecondary}}}); @@ -7375,7 +7354,7 @@ TEST_F(InputDispatcherDropInputFeatureTest, WindowDropsInput) { window->setFocusable(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); setFocusedWindow(window); - window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); // With the flag set, window should not get any input NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); @@ -7421,7 +7400,7 @@ TEST_F(InputDispatcherDropInputFeatureTest, ObscuredWindowDropsInput) { window->setFocusable(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}}); setFocusedWindow(window); - window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); // With the flag set, window should not get any input NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); @@ -7467,7 +7446,7 @@ TEST_F(InputDispatcherDropInputFeatureTest, UnobscuredWindowGetsInput) { window->setFocusable(true); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}}); setFocusedWindow(window); - window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); // With the flag set, window should not get any input NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); @@ -7526,8 +7505,7 @@ protected: // Set main display initial touch mode to InputDispatcher::kDefaultInTouchMode. if (mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, WINDOW_PID, - WINDOW_UID, true /* hasPermission */, - ADISPLAY_ID_DEFAULT)) { + WINDOW_UID, /*hasPermission=*/true, ADISPLAY_ID_DEFAULT)) { mWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode); mSecondWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode); mThirdWindow->assertNoEvents(); @@ -7535,7 +7513,7 @@ protected: // Set secondary display initial touch mode to InputDispatcher::kDefaultInTouchMode. if (mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, SECONDARY_WINDOW_PID, - SECONDARY_WINDOW_UID, true /* hasPermission */, + SECONDARY_WINDOW_UID, /*hasPermission=*/true, SECOND_DISPLAY_ID)) { mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); @@ -7557,7 +7535,7 @@ TEST_F(InputDispatcherTouchModeChangedTests, FocusedWindowCanChangeTouchMode) { const WindowInfo& windowInfo = *mWindow->getInfo(); changeAndVerifyTouchModeInMainDisplayOnly(!InputDispatcher::kDefaultInTouchMode, windowInfo.ownerPid, windowInfo.ownerUid, - false /* hasPermission */); + /* hasPermission=*/false); } TEST_F(InputDispatcherTouchModeChangedTests, NonFocusedWindowOwnerCannotChangeTouchMode) { @@ -7566,7 +7544,7 @@ TEST_F(InputDispatcherTouchModeChangedTests, NonFocusedWindowOwnerCannotChangeTo int32_t ownerUid = windowInfo.ownerUid; mWindow->setOwnerInfo(/* pid */ -1, /* uid */ -1); ASSERT_FALSE(mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, ownerPid, - ownerUid, false /*hasPermission*/, + ownerUid, /*hasPermission=*/false, ADISPLAY_ID_DEFAULT)); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); @@ -7578,14 +7556,14 @@ TEST_F(InputDispatcherTouchModeChangedTests, NonWindowOwnerMayChangeTouchModeOnP int32_t ownerUid = windowInfo.ownerUid; mWindow->setOwnerInfo(/* pid */ -1, /* uid */ -1); changeAndVerifyTouchModeInMainDisplayOnly(!InputDispatcher::kDefaultInTouchMode, ownerPid, - ownerUid, true /*hasPermission*/); + ownerUid, /*hasPermission=*/true); } TEST_F(InputDispatcherTouchModeChangedTests, EventIsNotGeneratedIfNotChangingTouchMode) { const WindowInfo& windowInfo = *mWindow->getInfo(); ASSERT_FALSE(mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, windowInfo.ownerPid, windowInfo.ownerUid, - true /*hasPermission*/, ADISPLAY_ID_DEFAULT)); + /*hasPermission=*/true, ADISPLAY_ID_DEFAULT)); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); } @@ -7594,7 +7572,7 @@ TEST_F(InputDispatcherTouchModeChangedTests, ChangeTouchOnSecondaryDisplayOnly) const WindowInfo& windowInfo = *mThirdWindow->getInfo(); ASSERT_TRUE(mDispatcher->setInTouchMode(!InputDispatcher::kDefaultInTouchMode, windowInfo.ownerPid, windowInfo.ownerUid, - true /*hasPermission*/, SECOND_DISPLAY_ID)); + /*hasPermission=*/true, SECOND_DISPLAY_ID)); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); mThirdWindow->consumeTouchModeEvent(!InputDispatcher::kDefaultInTouchMode); @@ -7614,7 +7592,7 @@ TEST_F(InputDispatcherTouchModeChangedTests, CanChangeTouchModeWhenOwningLastInt const WindowInfo& windowInfo = *mWindow->getInfo(); ASSERT_TRUE(mDispatcher->setInTouchMode(!InputDispatcher::kDefaultInTouchMode, windowInfo.ownerPid, windowInfo.ownerUid, - false /*hasPermission*/, ADISPLAY_ID_DEFAULT)); + /*hasPermission=*/false, ADISPLAY_ID_DEFAULT)); } class InputDispatcherSpyWindowTest : public InputDispatcherTest { @@ -7826,16 +7804,15 @@ TEST_F(InputDispatcherSpyWindowTest, ReceivesMultiplePointers) { const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer( - PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; windowRight->consumeMotionDown(); - spy->consumeMotionPointerDown(1 /*pointerIndex*/); + spy->consumeMotionPointerDown(/*pointerIndex=*/1); } /** @@ -7859,15 +7836,14 @@ TEST_F(InputDispatcherSpyWindowTest, ReceivesSecondPointerAsDown) { const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer( - PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - window->consumeMotionPointerDown(1 /*pointerIndex*/); + window->consumeMotionPointerDown(/*pointerIndex=*/1); spyRight->consumeMotionDown(); } @@ -7899,10 +7875,8 @@ TEST_F(InputDispatcherSpyWindowTest, SplitIfNoForegroundWindowTouched) { MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(100) - .y(200)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(200)) + .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -7910,7 +7884,7 @@ TEST_F(InputDispatcherSpyWindowTest, SplitIfNoForegroundWindowTouched) { << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; window->consumeMotionDown(ADISPLAY_ID_DEFAULT); - spy->consumeMotionPointerDown(1 /* pointerIndex */); + spy->consumeMotionPointerDown(/*pointerIndex=*/1); } /** @@ -8024,28 +7998,24 @@ TEST_F(InputDispatcherPilferPointersTest, ContinuesToReceiveGestureAfterPilfer) MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(100) - .y(200)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(200)) + .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - spy->consumeMotionPointerDown(1 /*pointerIndex*/); + spy->consumeMotionPointerDown(/*pointerIndex=*/1); // Third finger goes down outside all windows, so injection should fail. const MotionEvent thirdFingerDownEvent = MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(100) - .y(200)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer(PointerBuilder(/* id */ 2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(-5).y(-5)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(200)) + .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(-5).y(-5)) .build(); ASSERT_EQ(InputEventInjectionResult::FAILED, injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -8079,10 +8049,8 @@ TEST_F(InputDispatcherPilferPointersTest, PartiallyPilferRequiredPointers) { MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(150) - .y(150)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150)) + .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -8096,11 +8064,9 @@ TEST_F(InputDispatcherPilferPointersTest, PartiallyPilferRequiredPointers) { MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(150) - .y(150)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10)) - .pointer(PointerBuilder(/* id */ 2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150)) + .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10)) + .pointer(PointerBuilder(/*id=*/2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -8144,8 +8110,8 @@ TEST_F(InputDispatcherPilferPointersTest, PilferAllRequiredPointers) { MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10)) + .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -8191,10 +8157,8 @@ TEST_F(InputDispatcherPilferPointersTest, CanReceivePointersAfterPilfer) { MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10)) - .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(150) - .y(150)) + .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10)) + .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -8233,7 +8197,7 @@ public: mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}}); setFocusedWindow(window); - window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/); + window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); return {std::move(overlay), std::move(window)}; } @@ -8357,9 +8321,9 @@ struct User { } InputEventInjectionResult injectTargetedKey(int32_t action) const { - return inputdispatcher::injectKey(mDispatcher, action, 0 /* repeatCount*/, ADISPLAY_ID_NONE, + return inputdispatcher::injectKey(mDispatcher, action, /*repeatCount=*/0, ADISPLAY_ID_NONE, InputEventInjectionSync::WAIT_FOR_RESULT, - INJECT_EVENT_TIMEOUT, false /*allowKeyRepeat*/, {mUid}, + INJECT_EVENT_TIMEOUT, /*allowKeyRepeat=*/false, {mUid}, mPolicyFlags); } diff --git a/services/inputflinger/tests/InputProcessorConverter_test.cpp b/services/inputflinger/tests/InputProcessorConverter_test.cpp index 040c8da309..161a24ff70 100644 --- a/services/inputflinger/tests/InputProcessorConverter_test.cpp +++ b/services/inputflinger/tests/InputProcessorConverter_test.cpp @@ -38,15 +38,15 @@ static NotifyMotionArgs generateBasicMotionArgs() { coords.setAxisValue(AMOTION_EVENT_AXIS_Y, 2); coords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.5); static constexpr nsecs_t downTime = 2; - NotifyMotionArgs motionArgs(1 /*sequenceNum*/, downTime /*eventTime*/, 2 /*readTime*/, - 3 /*deviceId*/, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT, - 4 /*policyFlags*/, AMOTION_EVENT_ACTION_DOWN, 0 /*actionButton*/, - 0 /*flags*/, AMETA_NONE, 0 /*buttonState*/, + NotifyMotionArgs motionArgs(/*sequenceNum=*/1, /*eventTime=*/downTime, /*readTime=*/2, + /*deviceId=*/3, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT, + /*policyFlags=*/4, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0, + /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, - 1 /*pointerCount*/, &properties, &coords, 0 /*xPrecision*/, - 0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION, + /*pointerCount=*/1, &properties, &coords, /*xPrecision=*/0, + /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, - {} /*videoFrames*/); + /*videoFrames=*/{}); return motionArgs; } diff --git a/services/inputflinger/tests/InputProcessor_test.cpp b/services/inputflinger/tests/InputProcessor_test.cpp index 380001c83a..b6deed8aae 100644 --- a/services/inputflinger/tests/InputProcessor_test.cpp +++ b/services/inputflinger/tests/InputProcessor_test.cpp @@ -44,15 +44,15 @@ static NotifyMotionArgs generateBasicMotionArgs() { coords.setAxisValue(AMOTION_EVENT_AXIS_X, 1); coords.setAxisValue(AMOTION_EVENT_AXIS_Y, 1); static constexpr nsecs_t downTime = 2; - NotifyMotionArgs motionArgs(1 /*sequenceNum*/, downTime /*eventTime*/, 2 /*readTime*/, - 3 /*deviceId*/, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT, - 4 /*policyFlags*/, AMOTION_EVENT_ACTION_DOWN, 0 /*actionButton*/, - 0 /*flags*/, AMETA_NONE, 0 /*buttonState*/, + NotifyMotionArgs motionArgs(/*sequenceNum=*/1, /*eventTime=*/downTime, /*readTime=*/2, + /*deviceId=*/3, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT, + /*policyFlags=*/4, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0, + /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, - 1 /*pointerCount*/, &properties, &coords, 0 /*xPrecision*/, - 0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION, + /*pointerCount=*/1, &properties, &coords, /*xPrecision=*/0, + /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, - {} /*videoFrames*/); + /*videoFrames=*/{}); return motionArgs; } @@ -70,7 +70,7 @@ protected: */ TEST_F(InputProcessorTest, SendToNextStage_NotifyConfigurationChangedArgs) { // Create a basic configuration change and send to processor - NotifyConfigurationChangedArgs args(1 /*sequenceNum*/, 2 /*eventTime*/); + NotifyConfigurationChangedArgs args(/*sequenceNum=*/1, /*eventTime=*/2); mProcessor->notifyConfigurationChanged(&args); NotifyConfigurationChangedArgs outArgs; @@ -80,10 +80,10 @@ TEST_F(InputProcessorTest, SendToNextStage_NotifyConfigurationChangedArgs) { TEST_F(InputProcessorTest, SendToNextStage_NotifyKeyArgs) { // Create a basic key event and send to processor - NotifyKeyArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 21 /*readTime*/, 3 /*deviceId*/, - AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, 0 /*policyFlags*/, - AKEY_EVENT_ACTION_DOWN, 4 /*flags*/, AKEYCODE_HOME, 5 /*scanCode*/, - AMETA_NONE, 6 /*downTime*/); + NotifyKeyArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*readTime=*/21, /*deviceId=*/3, + AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, /*policyFlags=*/0, + AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5, + AMETA_NONE, /*downTime=*/6); mProcessor->notifyKey(&args); NotifyKeyArgs outArgs; @@ -108,8 +108,8 @@ TEST_F(InputProcessorTest, SendToNextStage_NotifyMotionArgs) { * Expect that the event is received by the next input stage, unmodified. */ TEST_F(InputProcessorTest, SendToNextStage_NotifySwitchArgs) { - NotifySwitchArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 3 /*policyFlags*/, 4 /*switchValues*/, - 5 /*switchMask*/); + NotifySwitchArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*policyFlags=*/3, + /*switchValues=*/4, /*switchMask=*/5); mProcessor->notifySwitch(&args); NotifySwitchArgs outArgs; @@ -122,7 +122,7 @@ TEST_F(InputProcessorTest, SendToNextStage_NotifySwitchArgs) { * Expect that the event is received by the next input stage, unmodified. */ TEST_F(InputProcessorTest, SendToNextStage_NotifyDeviceResetArgs) { - NotifyDeviceResetArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 3 /*deviceId*/); + NotifyDeviceResetArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*deviceId=*/3); mProcessor->notifyDeviceReset(&args); NotifyDeviceResetArgs outArgs; @@ -252,7 +252,7 @@ TEST_F(MotionClassifierTest, Reset_DoesNotCrash) { * Make sure MotionClassifier does not crash when a device is reset. */ TEST_F(MotionClassifierTest, DeviceReset_DoesNotCrash) { - NotifyDeviceResetArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 3 /*deviceId*/); + NotifyDeviceResetArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*deviceId=*/3); ASSERT_NO_FATAL_FAILURE(mMotionClassifier->reset(args)); } diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index feda191401..e1c54e9914 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -367,7 +367,7 @@ TEST_F(InputReaderPolicyTest, Viewports_GetCleared) { // Add an internal viewport, then clear it mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, - true /*isActive*/, uniqueId, NO_PORT, ViewportType::INTERNAL); + /*isActive=*/true, uniqueId, NO_PORT, ViewportType::INTERNAL); // Check matching by uniqueId internalViewport = mFakePolicy->getDisplayViewportByUniqueId(uniqueId); @@ -397,19 +397,19 @@ TEST_F(InputReaderPolicyTest, Viewports_GetByType) { // Add an internal viewport mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, - true /*isActive*/, internalUniqueId, NO_PORT, + /*isActive=*/true, internalUniqueId, NO_PORT, ViewportType::INTERNAL); // Add an external viewport mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, - true /*isActive*/, externalUniqueId, NO_PORT, + /*isActive=*/true, externalUniqueId, NO_PORT, ViewportType::EXTERNAL); // Add an virtual viewport mFakePolicy->addDisplayViewport(virtualDisplayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, - ui::ROTATION_0, true /*isActive*/, virtualUniqueId1, NO_PORT, + ui::ROTATION_0, /*isActive=*/true, virtualUniqueId1, NO_PORT, ViewportType::VIRTUAL); // Add another virtual viewport mFakePolicy->addDisplayViewport(virtualDisplayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, - ui::ROTATION_0, true /*isActive*/, virtualUniqueId2, NO_PORT, + ui::ROTATION_0, /*isActive=*/true, virtualUniqueId2, NO_PORT, ViewportType::VIRTUAL); // Check matching by type for internal @@ -459,10 +459,10 @@ TEST_F(InputReaderPolicyTest, Viewports_TwoOfSameType) { mFakePolicy->clearViewports(); // Add a viewport mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, - true /*isActive*/, uniqueId1, NO_PORT, type); + /*isActive=*/true, uniqueId1, NO_PORT, type); // Add another viewport mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, - true /*isActive*/, uniqueId2, NO_PORT, type); + /*isActive=*/true, uniqueId2, NO_PORT, type); // Check that correct display viewport was returned by comparing the display IDs. std::optional viewport1 = @@ -502,10 +502,10 @@ TEST_F(InputReaderPolicyTest, Viewports_ByTypeReturnsDefaultForInternal) { // Add the default display first and ensure it gets returned. mFakePolicy->clearViewports(); mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT, - ui::ROTATION_0, true /*isActive*/, uniqueId1, NO_PORT, + ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT, ViewportType::INTERNAL); mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, - ui::ROTATION_0, true /*isActive*/, uniqueId2, NO_PORT, + ui::ROTATION_0, /*isActive=*/true, uniqueId2, NO_PORT, ViewportType::INTERNAL); std::optional viewport = @@ -517,10 +517,10 @@ TEST_F(InputReaderPolicyTest, Viewports_ByTypeReturnsDefaultForInternal) { // Add the default display second to make sure order doesn't matter. mFakePolicy->clearViewports(); mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, - ui::ROTATION_0, true /*isActive*/, uniqueId2, NO_PORT, + ui::ROTATION_0, /*isActive=*/true, uniqueId2, NO_PORT, ViewportType::INTERNAL); mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT, - ui::ROTATION_0, true /*isActive*/, uniqueId1, NO_PORT, + ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT, ViewportType::INTERNAL); viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); @@ -545,10 +545,10 @@ TEST_F(InputReaderPolicyTest, Viewports_GetByPort) { mFakePolicy->clearViewports(); // Add a viewport that's associated with some display port that's not of interest. mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, - true /*isActive*/, uniqueId1, hdmi3, type); + /*isActive=*/true, uniqueId1, hdmi3, type); // Add another viewport, connected to HDMI1 port mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, - true /*isActive*/, uniqueId2, hdmi1, type); + /*isActive=*/true, uniqueId2, hdmi1, type); // Check that correct display viewport was returned by comparing the display ports. std::optional hdmi1Viewport = mFakePolicy->getDisplayViewportByPort(hdmi1); @@ -1002,9 +1002,9 @@ TEST_F(InputReaderTest, Device_CanDispatchToDisplay) { // Add default and second display. mFakePolicy->clearViewports(); mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, - true /*isActive*/, "local:0", NO_PORT, ViewportType::INTERNAL); + /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL); mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - ui::ROTATION_0, true /*isActive*/, "local:1", hdmi1, + ui::ROTATION_0, /*isActive=*/true, "local:1", hdmi1, ViewportType::EXTERNAL); mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO); mReader->loopOnce(); @@ -1255,15 +1255,15 @@ TEST_F(InputReaderTest, LightGetColor) { .maxBrightness = 255, .flags = InputLightClass::BRIGHTNESS, .path = ""}; - mFakeEventHub->addRawLightInfo(1 /* rawId */, std::move(info)); - mFakeEventHub->fakeLightBrightness(1 /* rawId */, 0x55); + mFakeEventHub->addRawLightInfo(/*rawId=*/1, std::move(info)); + mFakeEventHub->fakeLightBrightness(/*rawId=*/1, 0x55); ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); - ASSERT_TRUE(controller.setLightColor(1 /* lightId */, LIGHT_BRIGHTNESS)); - ASSERT_EQ(controller.getLightColor(1 /* lightId */), LIGHT_BRIGHTNESS); - ASSERT_TRUE(mReader->setLightColor(deviceId, 1 /* lightId */, LIGHT_BRIGHTNESS)); - ASSERT_EQ(mReader->getLightColor(deviceId, 1 /* lightId */), LIGHT_BRIGHTNESS); + ASSERT_TRUE(controller.setLightColor(/*lightId=*/1, LIGHT_BRIGHTNESS)); + ASSERT_EQ(controller.getLightColor(/*lightId=*/1), LIGHT_BRIGHTNESS); + ASSERT_TRUE(mReader->setLightColor(deviceId, /*lightId=*/1, LIGHT_BRIGHTNESS)); + ASSERT_EQ(mReader->getLightColor(deviceId, /*lightId=*/1), LIGHT_BRIGHTNESS); } // --- InputReaderIntegrationTest --- @@ -1288,8 +1288,8 @@ protected: mFakePolicy = sp::make(); mFakePointerController = std::make_shared(); mFakePolicy->setPointerController(mFakePointerController); - mTestListener = std::make_unique(2000ms /*eventHappenedTimeout*/, - 30ms /*eventDidNotHappenTimeout*/); + mTestListener = std::make_unique(/*eventHappenedTimeout=*/2000ms, + /*eventDidNotHappenTimeout=*/30ms); mReader = std::make_unique(std::make_shared(), mFakePolicy, *mTestListener); @@ -1326,7 +1326,7 @@ TEST_F(InputReaderIntegrationTest, TestInvalidDevice) { // An invalid input device that is only used for this test. class InvalidUinputDevice : public UinputDevice { public: - InvalidUinputDevice() : UinputDevice("Invalid Device", 99 /*productId*/) {} + InvalidUinputDevice() : UinputDevice("Invalid Device", /*productId=*/99) {} private: void configureDevice(int fd, uinput_user_dev* device) override {} @@ -1478,7 +1478,7 @@ protected: ui::Rotation orientation, const std::string& uniqueId, std::optional physicalPort, ViewportType viewportType) { - mFakePolicy->addDisplayViewport(displayId, width, height, orientation, true /*isActive*/, + mFakePolicy->addDisplayViewport(displayId, width, height, orientation, /*isActive=*/true, uniqueId, physicalPort, viewportType); mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO); } @@ -2452,7 +2452,7 @@ TEST_F(InputDeviceTest, Configure_AssignsDisplayPort) { // Prepare displays. mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - ui::ROTATION_0, true /*isActive*/, UNIQUE_ID, hdmi, + ui::ROTATION_0, /*isActive=*/true, UNIQUE_ID, hdmi, ViewportType::INTERNAL); unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), InputReaderConfiguration::CHANGE_DISPLAY_INFO); @@ -2528,8 +2528,8 @@ TEST_F(InputDeviceTest, DumpDoesNotCrash) { constexpr int32_t TEST_EVENTHUB_ID = 10; mFakeEventHub->addDevice(TEST_EVENTHUB_ID, "Test EventHub device", InputDeviceClass::BATTERY); - InputDevice device(mReader->getContext(), 1 /*id*/, 2 /*generation*/, {} /*identifier*/); - device.addEventHubDevice(TEST_EVENTHUB_ID, true /*populateMappers*/); + InputDevice device(mReader->getContext(), /*id=*/1, /*generation=*/2, /*identifier=*/{}); + device.addEventHubDevice(TEST_EVENTHUB_ID, /*populateMappers=*/true); device.removeEventHubDevice(TEST_EVENTHUB_ID); std::string dumpStr, eventHubDevStr; device.dump(dumpStr, eventHubDevStr); @@ -2609,12 +2609,12 @@ TEST_F(VibratorInputMapperTest, Vibrate) { VibrationElement pattern(2); VibrationSequence sequence(2); pattern.duration = std::chrono::milliseconds(200); - pattern.channels = {{0 /* vibratorId */, DEFAULT_AMPLITUDE / 2}, - {1 /* vibratorId */, DEFAULT_AMPLITUDE}}; + pattern.channels = {{/*vibratorId=*/0, DEFAULT_AMPLITUDE / 2}, + {/*vibratorId=*/1, DEFAULT_AMPLITUDE}}; sequence.addElement(pattern); pattern.duration = std::chrono::milliseconds(500); - pattern.channels = {{0 /* vibratorId */, DEFAULT_AMPLITUDE / 4}, - {1 /* vibratorId */, DEFAULT_AMPLITUDE}}; + pattern.channels = {{/*vibratorId=*/0, DEFAULT_AMPLITUDE / 4}, + {/*vibratorId=*/1, DEFAULT_AMPLITUDE}}; sequence.addElement(pattern); std::vector timings = {0, 1}; @@ -2622,7 +2622,7 @@ TEST_F(VibratorInputMapperTest, Vibrate) { ASSERT_FALSE(mapper.isVibrating()); // Start vibrating - std::list out = mapper.vibrate(sequence, -1 /* repeat */, VIBRATION_TOKEN); + std::list out = mapper.vibrate(sequence, /*repeat=*/-1, VIBRATION_TOKEN); ASSERT_TRUE(mapper.isVibrating()); // Verify vibrator state listener was notified. mReader->loopOnce(); @@ -2981,12 +2981,12 @@ TEST_F(KeyboardInputMapperTest, Process_SendsReadTime) { NotifyKeyArgs args; // Key down - process(mapper, ARBITRARY_TIME, 12 /*readTime*/, EV_KEY, KEY_HOME, 1); + process(mapper, ARBITRARY_TIME, /*readTime=*/12, EV_KEY, KEY_HOME, 1); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); ASSERT_EQ(12, args.readTime); // Key up - process(mapper, ARBITRARY_TIME, 15 /*readTime*/, EV_KEY, KEY_HOME, 1); + process(mapper, ARBITRARY_TIME, /*readTime=*/15, EV_KEY, KEY_HOME, 1); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); ASSERT_EQ(15, args.readTime); } @@ -3369,7 +3369,7 @@ TEST_F(KeyboardInputMapperTest, Configure_AssignsDisplayPort) { AINPUT_KEYBOARD_TYPE_ALPHABETIC); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - 0 /*changes*/); + /*changes=*/0); unused += device2->reset(ARBITRARY_TIME); // Prepared displays and associated info. @@ -3479,7 +3479,7 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleAfterReattach) { AINPUT_KEYBOARD_TYPE_ALPHABETIC); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - 0 /*changes*/); + /*changes=*/0); unused += device2->reset(ARBITRARY_TIME); ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_CAPSL)); @@ -3540,7 +3540,7 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleInMultiDevices) { AINPUT_KEYBOARD_TYPE_ALPHABETIC); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - 0 /*changes*/); + /*changes=*/0); unused += device2->reset(ARBITRARY_TIME); // Initial metastate is AMETA_NONE. @@ -4508,8 +4508,8 @@ TEST_F(CursorInputMapperTest, Process_PointerCapture) { */ TEST_F(CursorInputMapperTest, PointerCaptureDisablesVelocityProcessing) { addConfigurationProperty("cursor.mode", "pointer"); - const VelocityControlParameters testParams(5.f /*scale*/, 0.f /*low threshold*/, - 100.f /*high threshold*/, 10.f /*acceleration*/); + const VelocityControlParameters testParams(/*scale=*/5.f, /*low threshold=*/0.f, + /*high threshold=*/100.f, /*acceleration=*/10.f); mFakePolicy->setVelocityControlParams(testParams); CursorInputMapper& mapper = addMapperAndConfigure(); @@ -8886,18 +8886,18 @@ TEST_F(MultiTouchInputMapperTest, Process_SendsReadTime) { MultiTouchInputMapper& mapper = addMapperAndConfigure(); prepareDisplay(ui::ROTATION_0); - process(mapper, 10, 11 /*readTime*/, EV_ABS, ABS_MT_TRACKING_ID, 1); - process(mapper, 15, 16 /*readTime*/, EV_ABS, ABS_MT_POSITION_X, 100); - process(mapper, 20, 21 /*readTime*/, EV_ABS, ABS_MT_POSITION_Y, 100); - process(mapper, 25, 26 /*readTime*/, EV_SYN, SYN_REPORT, 0); + process(mapper, 10, /*readTime=*/11, EV_ABS, ABS_MT_TRACKING_ID, 1); + process(mapper, 15, /*readTime=*/16, EV_ABS, ABS_MT_POSITION_X, 100); + process(mapper, 20, /*readTime=*/21, EV_ABS, ABS_MT_POSITION_Y, 100); + process(mapper, 25, /*readTime=*/26, EV_SYN, SYN_REPORT, 0); NotifyMotionArgs args; ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); ASSERT_EQ(26, args.readTime); - process(mapper, 30, 31 /*readTime*/, EV_ABS, ABS_MT_POSITION_X, 110); - process(mapper, 30, 32 /*readTime*/, EV_ABS, ABS_MT_POSITION_Y, 220); - process(mapper, 30, 33 /*readTime*/, EV_SYN, SYN_REPORT, 0); + process(mapper, 30, /*readTime=*/31, EV_ABS, ABS_MT_POSITION_X, 110); + process(mapper, 30, /*readTime=*/32, EV_ABS, ABS_MT_POSITION_Y, 220); + process(mapper, 30, /*readTime=*/33, EV_SYN, SYN_REPORT, 0); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); ASSERT_EQ(33, args.readTime); @@ -8911,7 +8911,7 @@ TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreDropped) { addConfigurationProperty("touch.deviceType", "touchScreen"); // Don't set touch.enableForInactiveViewport to verify the default behavior. mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, - false /*isActive*/, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); + /*isActive=*/false, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); prepareAxes(POSITION); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -8931,7 +8931,7 @@ TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreProcessed) { addConfigurationProperty("touch.deviceType", "touchScreen"); addConfigurationProperty("touch.enableForInactiveViewport", "1"); mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, - false /*isActive*/, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); + /*isActive=*/false, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); prepareAxes(POSITION); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -8948,7 +8948,7 @@ TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_AbortTouches) { addConfigurationProperty("touch.deviceType", "touchScreen"); addConfigurationProperty("touch.enableForInactiveViewport", "0"); mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, - true /*isActive*/, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); + /*isActive=*/true, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); std::optional optionalDisplayViewport = mFakePolicy->getDisplayViewportByUniqueId(UNIQUE_ID); ASSERT_TRUE(optionalDisplayViewport.has_value()); @@ -9011,14 +9011,14 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { ftl::Flags(0)); mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, - 0 /*flat*/, 0 /*fuzz*/); + /*flat=*/0, /*fuzz=*/0); mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_POSITION_Y, RAW_Y_MIN, RAW_Y_MAX, - 0 /*flat*/, 0 /*fuzz*/); + /*flat=*/0, /*fuzz=*/0); mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_TRACKING_ID, RAW_ID_MIN, RAW_ID_MAX, - 0 /*flat*/, 0 /*fuzz*/); + /*flat=*/0, /*fuzz=*/0); mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_SLOT, RAW_SLOT_MIN, RAW_SLOT_MAX, - 0 /*flat*/, 0 /*fuzz*/); - mFakeEventHub->setAbsoluteAxisValue(SECOND_EVENTHUB_ID, ABS_MT_SLOT, 0 /*value*/); + /*flat=*/0, /*fuzz=*/0); + mFakeEventHub->setAbsoluteAxisValue(SECOND_EVENTHUB_ID, ABS_MT_SLOT, /*value=*/0); mFakeEventHub->addConfigurationProperty(SECOND_EVENTHUB_ID, String8("touch.deviceType"), String8("touchScreen")); @@ -9026,7 +9026,7 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { MultiTouchInputMapper& mapper2 = device2->addMapper(SECOND_EVENTHUB_ID); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - 0 /*changes*/); + /*changes=*/0); unused += device2->reset(ARBITRARY_TIME); // Setup PointerController. @@ -10108,7 +10108,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthSwipe) { // The min freeform gesture width is 25units/mm x 30mm = 750 // which is greater than fraction of the diagnal length of the touchpad (349). // Thus, MaxSwipWidth is 750. - preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/); + preparePointerMode(/*xResolution=*/25, /*yResolution=*/25); MultiTouchInputMapper& mapper = addMapperAndConfigure(); NotifyMotionArgs motionArgs; @@ -10168,7 +10168,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthLowResolutionSwipe) // The min freeform gesture width is 5units/mm x 30mm = 150 // which is greater than fraction of the diagnal length of the touchpad (349). // Thus, MaxSwipWidth is the fraction of the diagnal length, 349. - preparePointerMode(5 /*xResolution*/, 5 /*yResolution*/); + preparePointerMode(/*xResolution=*/5, /*yResolution=*/5); MultiTouchInputMapper& mapper = addMapperAndConfigure(); NotifyMotionArgs motionArgs; @@ -10224,7 +10224,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthLowResolutionSwipe) * freeform gestures after two fingers start to move downwards. */ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthFreeform) { - preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/); + preparePointerMode(/*xResolution=*/25, /*yResolution=*/25); MultiTouchInputMapper& mapper = addMapperAndConfigure(); NotifyMotionArgs motionArgs; @@ -10319,7 +10319,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthFreeform) { } TEST_F(MultiTouchPointerModeTest, TwoFingerSwipeOffsets) { - preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/); + preparePointerMode(/*xResolution=*/25, /*yResolution=*/25); MultiTouchInputMapper& mapper = addMapperAndConfigure(); NotifyMotionArgs motionArgs; @@ -10365,7 +10365,7 @@ TEST_F(MultiTouchPointerModeTest, TwoFingerSwipeOffsets) { } TEST_F(MultiTouchPointerModeTest, WhenViewportActiveStatusChanged_PointerGestureIsReset) { - preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/); + preparePointerMode(/*xResolution=*/25, /*yResolution=*/25); mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); MultiTouchInputMapper& mapper = addMapperAndConfigure(); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); diff --git a/services/inputflinger/tests/LatencyTracker_test.cpp b/services/inputflinger/tests/LatencyTracker_test.cpp index 1f4b2486bd..fa149dba05 100644 --- a/services/inputflinger/tests/LatencyTracker_test.cpp +++ b/services/inputflinger/tests/LatencyTracker_test.cpp @@ -36,10 +36,10 @@ const std::chrono::duration ANR_TIMEOUT = std::chrono::milliseconds( InputEventTimeline getTestTimeline() { InputEventTimeline t( - /*isDown*/ true, - /*eventTime*/ 2, - /*readTime*/ 3); - ConnectionTimeline expectedCT(/*deliveryTime*/ 6, /* consumeTime*/ 7, /*finishTime*/ 8); + /*isDown=*/true, + /*eventTime=*/2, + /*readTime=*/3); + ConnectionTimeline expectedCT(/*deliveryTime=*/6, /*consumeTime=*/7, /*finishTime=*/8); std::array graphicsTimeline; graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 9; graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 10; @@ -87,7 +87,8 @@ private: void LatencyTrackerTest::triggerEventReporting(nsecs_t lastEventTime) { const nsecs_t triggerEventTime = lastEventTime + std::chrono::nanoseconds(ANR_TIMEOUT).count() + 1; - mTracker->trackListener(1 /*inputEventId*/, true /*isDown*/, triggerEventTime, 3 /*readTime*/); + mTracker->trackListener(/*inputEventId=*/1, /*isDown=*/true, triggerEventTime, + /*readTime=*/3); } void LatencyTrackerTest::assertReceivedTimeline(const InputEventTimeline& timeline) { @@ -136,8 +137,9 @@ void LatencyTrackerTest::assertReceivedTimelines(const std::vectortrackListener(1 /*inputEventId*/, false /*isDown*/, 2 /*eventTime*/, 3 /*readTime*/); - triggerEventReporting(2 /*eventTime*/); + mTracker->trackListener(/*inputEventId=*/1, /*isDown=*/false, /*eventTime=*/2, + /*readTime=*/3); + triggerEventReporting(/*eventTime=*/2); assertReceivedTimeline(InputEventTimeline{false, 2, 3}); } @@ -145,9 +147,9 @@ TEST_F(LatencyTrackerTest, TrackListener_DoesNotTriggerReporting) { * A single call to trackFinishedEvent should not cause a timeline to be reported. */ TEST_F(LatencyTrackerTest, TrackFinishedEvent_DoesNotTriggerReporting) { - mTracker->trackFinishedEvent(1 /*inputEventId*/, connection1, 2 /*deliveryTime*/, - 3 /*consumeTime*/, 4 /*finishTime*/); - triggerEventReporting(4 /*eventTime*/); + mTracker->trackFinishedEvent(/*inputEventId=*/1, connection1, /*deliveryTime=*/2, + /*consumeTime=*/3, /*finishTime=*/4); + triggerEventReporting(/*eventTime=*/4); assertReceivedTimelines({}); } @@ -158,8 +160,8 @@ TEST_F(LatencyTrackerTest, TrackGraphicsLatency_DoesNotTriggerReporting) { std::array graphicsTimeline; graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 2; graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 3; - mTracker->trackGraphicsLatency(1 /*inputEventId*/, connection2, graphicsTimeline); - triggerEventReporting(3 /*eventTime*/); + mTracker->trackGraphicsLatency(/*inputEventId=*/1, connection2, graphicsTimeline); + triggerEventReporting(/*eventTime=*/3); assertReceivedTimelines({}); } @@ -189,10 +191,10 @@ TEST_F(LatencyTrackerTest, WhenDuplicateEventsAreReported_DoesNotCrash) { // In the following 2 calls to trackListener, the inputEventId's are the same, but event times // are different. - mTracker->trackListener(inputEventId, isDown, 1 /*eventTime*/, readTime); - mTracker->trackListener(inputEventId, isDown, 2 /*eventTime*/, readTime); + mTracker->trackListener(inputEventId, isDown, /*eventTime=*/1, readTime); + mTracker->trackListener(inputEventId, isDown, /*eventTime=*/2, readTime); - triggerEventReporting(2 /*eventTime*/); + triggerEventReporting(/*eventTime=*/2); // Since we sent duplicate input events, the tracker should just delete all of them, because it // does not have enough information to properly track them. assertReceivedTimelines({}); @@ -215,13 +217,13 @@ TEST_F(LatencyTrackerTest, MultipleEvents_AreReportedConsistently) { constexpr int32_t inputEventId2 = 10; InputEventTimeline timeline2( - /*isDown*/ false, - /*eventTime*/ 20, - /*readTime*/ 30); + /*isDown=*/false, + /*eventTime=*/20, + /*readTime=*/30); timeline2.connectionTimelines.emplace(connection2, - ConnectionTimeline(/*deliveryTime*/ 60, - /*consumeTime*/ 70, - /*finishTime*/ 80)); + ConnectionTimeline(/*deliveryTime=*/60, + /*consumeTime=*/70, + /*finishTime=*/80)); ConnectionTimeline& connectionTimeline2 = timeline2.connectionTimelines.begin()->second; std::array graphicsTimeline2; graphicsTimeline2[GraphicsTimeline::GPU_COMPLETED_TIME] = 90; @@ -258,15 +260,15 @@ TEST_F(LatencyTrackerTest, IncompleteEvents_AreHandledConsistently) { const sp& token = timeline.connectionTimelines.begin()->first; for (size_t i = 1; i <= 100; i++) { - mTracker->trackListener(i /*inputEventId*/, timeline.isDown, timeline.eventTime, + mTracker->trackListener(/*inputEventId=*/i, timeline.isDown, timeline.eventTime, timeline.readTime); expectedTimelines.push_back( InputEventTimeline{timeline.isDown, timeline.eventTime, timeline.readTime}); } // Now, complete the first event that was sent. - mTracker->trackFinishedEvent(1 /*inputEventId*/, token, expectedCT.deliveryTime, + mTracker->trackFinishedEvent(/*inputEventId=*/1, token, expectedCT.deliveryTime, expectedCT.consumeTime, expectedCT.finishTime); - mTracker->trackGraphicsLatency(1 /*inputEventId*/, token, expectedCT.graphicsTimeline); + mTracker->trackGraphicsLatency(/*inputEventId=*/1, token, expectedCT.graphicsTimeline); expectedTimelines[0].connectionTimelines.emplace(token, std::move(expectedCT)); triggerEventReporting(timeline.eventTime); diff --git a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp index 7265362a7c..9014dfb48b 100644 --- a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp +++ b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp @@ -64,13 +64,13 @@ static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, } // Define a valid motion event. - NotifyMotionArgs args(/* id */ 0, eventTime, 0 /*readTime*/, deviceId, source, 0 /*displayId*/, + NotifyMotionArgs args(/*id=*/0, eventTime, /*readTime=*/0, deviceId, source, /*displayId=*/0, POLICY_FLAG_PASS_TO_USER, action, /* actionButton */ 0, - /* flags */ 0, AMETA_NONE, /* buttonState */ 0, - MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, - pointerProperties, pointerCoords, /* xPrecision */ 0, /* yPrecision */ 0, + /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties, + pointerCoords, /*xPrecision=*/0, /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION, - AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, /* videoFrames */ {}); + AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, /*videoFrames=*/{}); return args; } @@ -109,26 +109,26 @@ private: TEST_F(PreferStylusOverTouchTest, TouchGestureIsNotBlocked) { NotifyMotionArgs args; - args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2}}, TOUCHSCREEN); assertNotBlocked(args); - args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 3}}, TOUCHSCREEN); assertNotBlocked(args); - args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{1, 3}}, TOUCHSCREEN); assertNotBlocked(args); } TEST_F(PreferStylusOverTouchTest, StylusGestureIsNotBlocked) { NotifyMotionArgs args; - args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, STYLUS); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2}}, STYLUS); assertNotBlocked(args); - args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, STYLUS); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 3}}, STYLUS); assertNotBlocked(args); - args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, STYLUS); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{1, 3}}, STYLUS); assertNotBlocked(args); } @@ -139,24 +139,24 @@ TEST_F(PreferStylusOverTouchTest, StylusGestureIsNotBlocked) { TEST_F(PreferStylusOverTouchTest, TouchIsCanceledWhenStylusGoesDown) { NotifyMotionArgs args; - args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2}}, TOUCHSCREEN); assertNotBlocked(args); - args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 3}}, TOUCHSCREEN); assertNotBlocked(args); - args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); + args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 30}}, STYLUS); NotifyMotionArgs cancelArgs = - generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, CANCEL, {{1, 3}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, CANCEL, {{1, 3}}, TOUCHSCREEN); cancelArgs.flags |= AMOTION_EVENT_FLAG_CANCELED; assertResponse(args, {cancelArgs, args}); // Both stylus and touch events continue. Stylus should be not blocked, and touch should be // blocked - args = generateMotionArgs(3 /*downTime*/, 4 /*eventTime*/, MOVE, {{10, 31}}, STYLUS); + args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/4, MOVE, {{10, 31}}, STYLUS); assertNotBlocked(args); - args = generateMotionArgs(0 /*downTime*/, 5 /*eventTime*/, MOVE, {{1, 4}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/5, MOVE, {{1, 4}}, TOUCHSCREEN); assertDropped(args); } @@ -166,17 +166,17 @@ TEST_F(PreferStylusOverTouchTest, TouchIsCanceledWhenStylusGoesDown) { TEST_F(PreferStylusOverTouchTest, StylusDownAfterTouch) { NotifyMotionArgs args; - args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2}}, TOUCHSCREEN); assertNotBlocked(args); - args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 3}}, TOUCHSCREEN); assertNotBlocked(args); - args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{1, 3}}, TOUCHSCREEN); assertNotBlocked(args); // Stylus goes down - args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); + args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 30}}, STYLUS); assertNotBlocked(args); } @@ -189,21 +189,21 @@ TEST_F(PreferStylusOverTouchTest, NewTouchIsBlockedWhenStylusIsDown) { constexpr nsecs_t stylusDownTime = 0; constexpr nsecs_t touchDownTime = 1; - args = generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); + args = generateMotionArgs(stylusDownTime, /*eventTime=*/0, DOWN, {{10, 30}}, STYLUS); assertNotBlocked(args); - args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + args = generateMotionArgs(touchDownTime, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN); assertDropped(args); // Stylus should continue to work - args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, MOVE, {{10, 31}}, STYLUS); + args = generateMotionArgs(stylusDownTime, /*eventTime=*/2, MOVE, {{10, 31}}, STYLUS); assertNotBlocked(args); // Touch should continue to be blocked - args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(touchDownTime, /*eventTime=*/1, MOVE, {{1, 3}}, TOUCHSCREEN); assertDropped(args); - args = generateMotionArgs(0 /*downTime*/, 5 /*eventTime*/, MOVE, {{1, 4}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/5, MOVE, {{1, 4}}, TOUCHSCREEN); assertDropped(args); } @@ -217,23 +217,23 @@ TEST_F(PreferStylusOverTouchTest, NewTouchWorksAfterStylusIsLifted) { constexpr nsecs_t touchDownTime = 4; // Stylus goes down and up - args = generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); + args = generateMotionArgs(stylusDownTime, /*eventTime=*/0, DOWN, {{10, 30}}, STYLUS); assertNotBlocked(args); - args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, MOVE, {{10, 31}}, STYLUS); + args = generateMotionArgs(stylusDownTime, /*eventTime=*/2, MOVE, {{10, 31}}, STYLUS); assertNotBlocked(args); - args = generateMotionArgs(stylusDownTime, 3 /*eventTime*/, UP, {{10, 31}}, STYLUS); + args = generateMotionArgs(stylusDownTime, /*eventTime=*/3, UP, {{10, 31}}, STYLUS); assertNotBlocked(args); // New touch goes down. It should not be blocked args = generateMotionArgs(touchDownTime, touchDownTime, DOWN, {{1, 2}}, TOUCHSCREEN); assertNotBlocked(args); - args = generateMotionArgs(touchDownTime, 5 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(touchDownTime, /*eventTime=*/5, MOVE, {{1, 3}}, TOUCHSCREEN); assertNotBlocked(args); - args = generateMotionArgs(touchDownTime, 6 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(touchDownTime, /*eventTime=*/6, UP, {{1, 3}}, TOUCHSCREEN); assertNotBlocked(args); } @@ -246,25 +246,25 @@ TEST_F(PreferStylusOverTouchTest, AfterStylusIsLiftedCurrentTouchIsBlocked) { constexpr nsecs_t stylusDownTime = 0; constexpr nsecs_t touchDownTime = 1; - assertNotBlocked(generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS)); + assertNotBlocked(generateMotionArgs(stylusDownTime, /*eventTime=*/0, DOWN, {{10, 30}}, STYLUS)); - args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + args = generateMotionArgs(touchDownTime, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN); assertDropped(args); // Lift the stylus - args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, UP, {{10, 30}}, STYLUS); + args = generateMotionArgs(stylusDownTime, /*eventTime=*/2, UP, {{10, 30}}, STYLUS); assertNotBlocked(args); // Touch should continue to be blocked - args = generateMotionArgs(touchDownTime, 3 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(touchDownTime, /*eventTime=*/3, MOVE, {{1, 3}}, TOUCHSCREEN); assertDropped(args); - args = generateMotionArgs(touchDownTime, 4 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN); + args = generateMotionArgs(touchDownTime, /*eventTime=*/4, UP, {{1, 3}}, TOUCHSCREEN); assertDropped(args); // New touch should go through, though. constexpr nsecs_t newTouchDownTime = 5; - args = generateMotionArgs(newTouchDownTime, 5 /*eventTime*/, DOWN, {{10, 20}}, TOUCHSCREEN); + args = generateMotionArgs(newTouchDownTime, /*eventTime=*/5, DOWN, {{10, 20}}, TOUCHSCREEN); assertNotBlocked(args); } @@ -276,20 +276,20 @@ TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchPointersAreIgnored) { NotifyMotionArgs args; // Event from a stylus device, but with finger tool type - args = generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, STYLUS); + args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{1, 2}}, STYLUS); // Keep source stylus, but make the tool type touch args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; assertNotBlocked(args); // Second pointer (stylus pointer) goes down, from the same device - args = generateMotionArgs(1 /*downTime*/, 2 /*eventTime*/, POINTER_1_DOWN, {{1, 2}, {10, 20}}, + args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/2, POINTER_1_DOWN, {{1, 2}, {10, 20}}, STYLUS); // Keep source stylus, but make the tool type touch args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; assertNotBlocked(args); // Second pointer (stylus pointer) goes down, from the same device - args = generateMotionArgs(1 /*downTime*/, 3 /*eventTime*/, MOVE, {{2, 3}, {11, 21}}, STYLUS); + args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/3, MOVE, {{2, 3}, {11, 21}}, STYLUS); // Keep source stylus, but make the tool type touch args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; assertNotBlocked(args); @@ -300,16 +300,16 @@ TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchPointersAreIgnored) { */ TEST_F(PreferStylusOverTouchTest, TouchFromTwoDevicesAndStylus) { NotifyMotionArgs touch1Down = - generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN); assertNotBlocked(touch1Down); NotifyMotionArgs touch2Down = - generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{3, 4}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/2, /*eventTime=*/2, DOWN, {{3, 4}}, TOUCHSCREEN); touch2Down.deviceId = SECOND_TOUCH_DEVICE_ID; assertNotBlocked(touch2Down); NotifyMotionArgs stylusDown = - generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); + generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 30}}, STYLUS); NotifyMotionArgs cancelArgs1 = touch1Down; cancelArgs1.action = CANCEL; cancelArgs1.flags |= AMOTION_EVENT_FLAG_CANCELED; @@ -328,12 +328,12 @@ TEST_F(PreferStylusOverTouchTest, TouchFromTwoDevicesAndStylus) { TEST_F(PreferStylusOverTouchTest, AllTouchMustLiftAfterCanceledByStylus) { // First device touches down NotifyMotionArgs touch1Down = - generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN); assertNotBlocked(touch1Down); // Stylus goes down - touch should be canceled NotifyMotionArgs stylusDown = - generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{10, 30}}, STYLUS); + generateMotionArgs(/*downTime=*/2, /*eventTime=*/2, DOWN, {{10, 30}}, STYLUS); NotifyMotionArgs cancelArgs1 = touch1Down; cancelArgs1.action = CANCEL; cancelArgs1.flags |= AMOTION_EVENT_FLAG_CANCELED; @@ -341,44 +341,44 @@ TEST_F(PreferStylusOverTouchTest, AllTouchMustLiftAfterCanceledByStylus) { // Stylus goes up NotifyMotionArgs stylusUp = - generateMotionArgs(2 /*downTime*/, 3 /*eventTime*/, UP, {{10, 30}}, STYLUS); + generateMotionArgs(/*downTime=*/2, /*eventTime=*/3, UP, {{10, 30}}, STYLUS); assertNotBlocked(stylusUp); // Touch from the first device remains blocked NotifyMotionArgs touch1Move = - generateMotionArgs(1 /*downTime*/, 4 /*eventTime*/, MOVE, {{2, 3}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/1, /*eventTime=*/4, MOVE, {{2, 3}}, TOUCHSCREEN); assertDropped(touch1Move); // Second touch goes down. It should not be blocked because stylus has already lifted. NotifyMotionArgs touch2Down = - generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{31, 32}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/5, /*eventTime=*/5, DOWN, {{31, 32}}, TOUCHSCREEN); touch2Down.deviceId = SECOND_TOUCH_DEVICE_ID; assertNotBlocked(touch2Down); // First device is lifted up. It's already been canceled, so the UP event should be dropped. NotifyMotionArgs touch1Up = - generateMotionArgs(1 /*downTime*/, 6 /*eventTime*/, UP, {{2, 3}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/1, /*eventTime=*/6, UP, {{2, 3}}, TOUCHSCREEN); assertDropped(touch1Up); // Touch from second device touch should continue to work NotifyMotionArgs touch2Move = - generateMotionArgs(5 /*downTime*/, 7 /*eventTime*/, MOVE, {{32, 33}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/5, /*eventTime=*/7, MOVE, {{32, 33}}, TOUCHSCREEN); touch2Move.deviceId = SECOND_TOUCH_DEVICE_ID; assertNotBlocked(touch2Move); // Second touch lifts up NotifyMotionArgs touch2Up = - generateMotionArgs(5 /*downTime*/, 8 /*eventTime*/, UP, {{32, 33}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/5, /*eventTime=*/8, UP, {{32, 33}}, TOUCHSCREEN); touch2Up.deviceId = SECOND_TOUCH_DEVICE_ID; assertNotBlocked(touch2Up); // Now that all touch has been lifted, new touch from either first or second device should work NotifyMotionArgs touch3Down = - generateMotionArgs(9 /*downTime*/, 9 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/9, /*eventTime=*/9, DOWN, {{1, 2}}, TOUCHSCREEN); assertNotBlocked(touch3Down); NotifyMotionArgs touch4Down = - generateMotionArgs(10 /*downTime*/, 10 /*eventTime*/, DOWN, {{100, 200}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/10, /*eventTime=*/10, DOWN, {{100, 200}}, TOUCHSCREEN); touch4Down.deviceId = SECOND_TOUCH_DEVICE_ID; assertNotBlocked(touch4Down); } @@ -403,27 +403,27 @@ TEST_F(PreferStylusOverTouchTest, AllTouchMustLiftAfterCanceledByStylus) { TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchDeviceIsCanceledAtFirst) { // Touch from device 1 goes down NotifyMotionArgs touchDown = - generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{1, 2}}, TOUCHSCREEN); touchDown.source = STYLUS; assertNotBlocked(touchDown); // Stylus from device 2 goes down. Touch should be canceled. NotifyMotionArgs args = - generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{10, 20}}, STYLUS); + generateMotionArgs(/*downTime=*/2, /*eventTime=*/2, DOWN, {{10, 20}}, STYLUS); NotifyMotionArgs cancelTouchArgs = touchDown; cancelTouchArgs.action = CANCEL; cancelTouchArgs.flags |= AMOTION_EVENT_FLAG_CANCELED; assertResponse(args, {cancelTouchArgs, args}); // Introduce a stylus pointer into the device 1 stream. It should be ignored. - args = generateMotionArgs(1 /*downTime*/, 3 /*eventTime*/, POINTER_1_DOWN, {{1, 2}, {3, 4}}, + args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/3, POINTER_1_DOWN, {{1, 2}, {3, 4}}, TOUCHSCREEN); args.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; args.source = STYLUS; assertDropped(args); // Lift up touch from the mixed touch/stylus device - args = generateMotionArgs(1 /*downTime*/, 4 /*eventTime*/, CANCEL, {{1, 2}, {3, 4}}, + args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/4, CANCEL, {{1, 2}, {3, 4}}, TOUCHSCREEN); args.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; args.source = STYLUS; @@ -431,19 +431,19 @@ TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchDeviceIsCanceledAtFirst) { // Stylus from device 2 is still down. Since the device 1 is now identified as a mixed // touch/stylus device, its events should go through, even if they are touch. - args = generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{21, 22}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/5, /*eventTime=*/5, DOWN, {{21, 22}}, TOUCHSCREEN); touchDown.source = STYLUS; assertResponse(args, {args}); // Reconfigure such that only the stylus device remains InputDeviceInfo stylusDevice; - stylusDevice.initialize(STYLUS_DEVICE_ID, 1 /*generation*/, 1 /*controllerNumber*/, - {} /*identifier*/, "stylus device", false /*external*/, - false /*hasMic*/, ADISPLAY_ID_NONE); + stylusDevice.initialize(STYLUS_DEVICE_ID, /*generation=*/1, /*controllerNumber=*/1, + /*identifier=*/{}, "stylus device", /*external=*/false, + /*hasMic=*/false, ADISPLAY_ID_NONE); notifyInputDevicesChanged({stylusDevice}); // The touchscreen device was removed, so we no longer remember anything about it. We should // again start blocking touch events from it. - args = generateMotionArgs(6 /*downTime*/, 6 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/6, /*eventTime=*/6, DOWN, {{1, 2}}, TOUCHSCREEN); args.source = STYLUS; assertDropped(args); } @@ -456,41 +456,41 @@ TEST_F(PreferStylusOverTouchTest, TouchIsBlockedWhenTwoStyliAreUsed) { NotifyMotionArgs args; // First stylus is down - assertNotBlocked(generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS)); + assertNotBlocked(generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{10, 30}}, STYLUS)); // Second stylus is down - args = generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{20, 40}}, STYLUS); + args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{20, 40}}, STYLUS); args.deviceId = SECOND_STYLUS_DEVICE_ID; assertNotBlocked(args); // Touch goes down. It should be ignored. - args = generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/2, /*eventTime=*/2, DOWN, {{1, 2}}, TOUCHSCREEN); assertDropped(args); // Lift the first stylus - args = generateMotionArgs(0 /*downTime*/, 3 /*eventTime*/, UP, {{10, 30}}, STYLUS); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/3, UP, {{10, 30}}, STYLUS); assertNotBlocked(args); // Touch should continue to be blocked - args = generateMotionArgs(2 /*downTime*/, 4 /*eventTime*/, UP, {{1, 2}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/2, /*eventTime=*/4, UP, {{1, 2}}, TOUCHSCREEN); assertDropped(args); // New touch should be blocked because second stylus is still down - args = generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{5, 6}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/5, /*eventTime=*/5, DOWN, {{5, 6}}, TOUCHSCREEN); assertDropped(args); // Second stylus goes up - args = generateMotionArgs(1 /*downTime*/, 6 /*eventTime*/, UP, {{20, 40}}, STYLUS); + args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/6, UP, {{20, 40}}, STYLUS); args.deviceId = SECOND_STYLUS_DEVICE_ID; assertNotBlocked(args); // Current touch gesture should continue to be blocked // Touch should continue to be blocked - args = generateMotionArgs(5 /*downTime*/, 7 /*eventTime*/, UP, {{5, 6}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/5, /*eventTime=*/7, UP, {{5, 6}}, TOUCHSCREEN); assertDropped(args); // Now that all styli were lifted, new touch should go through - args = generateMotionArgs(8 /*downTime*/, 8 /*eventTime*/, DOWN, {{7, 8}}, TOUCHSCREEN); + args = generateMotionArgs(/*downTime=*/8, /*eventTime=*/8, DOWN, {{7, 8}}, TOUCHSCREEN); assertNotBlocked(args); } diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp index e12f88ef40..3f749b1fed 100644 --- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp +++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp @@ -88,8 +88,8 @@ static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, } // Define a valid motion event. - NotifyMotionArgs args(/* id */ 0, eventTime, 0 /*readTime*/, DEVICE_ID, - AINPUT_SOURCE_TOUCHSCREEN, 0 /*displayId*/, POLICY_FLAG_PASS_TO_USER, + NotifyMotionArgs args(/* id */ 0, eventTime, /*readTime=*/0, DEVICE_ID, + AINPUT_SOURCE_TOUCHSCREEN, /*displayId=*/0, POLICY_FLAG_PASS_TO_USER, action, /* actionButton */ 0, /* flags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, @@ -409,7 +409,7 @@ protected: void SetUp() override { mBlocker = std::make_unique(mTestListener, - /*enablePalmRejection*/ true); + /*enablePalmRejection=*/true); } }; @@ -419,7 +419,7 @@ protected: */ TEST_F(UnwantedInteractionBlockerTest, ConfigurationChangedIsPassedToNextListener) { // Create a basic configuration change and send to blocker - NotifyConfigurationChangedArgs args(1 /*sequenceNum*/, 2 /*eventTime*/); + NotifyConfigurationChangedArgs args(/*sequenceNum=*/1, /*eventTime=*/2); mBlocker->notifyConfigurationChanged(&args); NotifyConfigurationChangedArgs outArgs; @@ -433,10 +433,10 @@ TEST_F(UnwantedInteractionBlockerTest, ConfigurationChangedIsPassedToNextListene */ TEST_F(UnwantedInteractionBlockerTest, KeyIsPassedToNextListener) { // Create a basic key event and send to blocker - NotifyKeyArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 21 /*readTime*/, 3 /*deviceId*/, - AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, 0 /*policyFlags*/, - AKEY_EVENT_ACTION_DOWN, 4 /*flags*/, AKEYCODE_HOME, 5 /*scanCode*/, - AMETA_NONE, 6 /*downTime*/); + NotifyKeyArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*readTime=*/21, /*deviceId=*/3, + AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, /*policyFlags=*/0, + AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5, + AMETA_NONE, /*downTime=*/6); mBlocker->notifyKey(&args); NotifyKeyArgs outArgs; @@ -451,7 +451,7 @@ TEST_F(UnwantedInteractionBlockerTest, KeyIsPassedToNextListener) { */ TEST_F(UnwantedInteractionBlockerTest, DownEventIsPassedToNextListener) { NotifyMotionArgs motionArgs = - generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}}); + generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); mBlocker->notifyMotion(&motionArgs); NotifyMotionArgs args; ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyMotionWasCalled(&args)); @@ -463,8 +463,8 @@ TEST_F(UnwantedInteractionBlockerTest, DownEventIsPassedToNextListener) { * Expect that the event is received by the next input stage, unmodified. */ TEST_F(UnwantedInteractionBlockerTest, SwitchIsPassedToNextListener) { - NotifySwitchArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 3 /*policyFlags*/, 4 /*switchValues*/, - 5 /*switchMask*/); + NotifySwitchArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*policyFlags=*/3, + /*switchValues=*/4, /*switchMask=*/5); mBlocker->notifySwitch(&args); NotifySwitchArgs outArgs; @@ -477,7 +477,7 @@ TEST_F(UnwantedInteractionBlockerTest, SwitchIsPassedToNextListener) { * Expect that the event is received by the next input stage, unmodified. */ TEST_F(UnwantedInteractionBlockerTest, DeviceResetIsPassedToNextListener) { - NotifyDeviceResetArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, DEVICE_ID); + NotifyDeviceResetArgs args(/*sequenceNum=*/1, /*eventTime=*/2, DEVICE_ID); mBlocker->notifyDeviceReset(&args); NotifyDeviceResetArgs outArgs; @@ -494,19 +494,19 @@ TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenResetHappens) { NotifyMotionArgs args; mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); mBlocker->notifyMotion( - &(args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}}))); + &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}}))); mBlocker->notifyMotion( - &(args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, MOVE, {{4, 5, 6}}))); - NotifyDeviceResetArgs resetArgs(1 /*sequenceNum*/, 3 /*eventTime*/, DEVICE_ID); + &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}}))); + NotifyDeviceResetArgs resetArgs(/*sequenceNum=*/1, /*eventTime=*/3, DEVICE_ID); mBlocker->notifyDeviceReset(&resetArgs); // Start a new gesture with a DOWN event, even though the previous event stream was incomplete. mBlocker->notifyMotion( - &(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/, DOWN, {{7, 8, 9}}))); + &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, DOWN, {{7, 8, 9}}))); } TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenStylusSourceWithFingerToolIsReceived) { mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); - NotifyMotionArgs args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}}); + NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}}); args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; args.source = AINPUT_SOURCE_STYLUS; mBlocker->notifyMotion(&args); @@ -520,9 +520,9 @@ TEST_F(UnwantedInteractionBlockerTest, NoResetIfDeviceInfoChanges) { NotifyMotionArgs args; mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); mBlocker->notifyMotion( - &(args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}}))); + &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}}))); mBlocker->notifyMotion( - &(args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, MOVE, {{4, 5, 6}}))); + &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}}))); // Now pretend the device changed, even though nothing is different for DEVICE_ID in practice. mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); @@ -530,7 +530,7 @@ TEST_F(UnwantedInteractionBlockerTest, NoResetIfDeviceInfoChanges) { // The MOVE event continues the gesture that started before 'devices changed', so it should not // cause a crash. mBlocker->notifyMotion( - &(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/, MOVE, {{7, 8, 9}}))); + &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, MOVE, {{7, 8, 9}}))); } /** @@ -539,23 +539,23 @@ TEST_F(UnwantedInteractionBlockerTest, NoResetIfDeviceInfoChanges) { TEST_F(UnwantedInteractionBlockerTest, StylusAfterTouchWorks) { NotifyMotionArgs args; mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); - args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}}); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); mBlocker->notifyMotion(&args); - args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{4, 5, 6}}); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{4, 5, 6}}); mBlocker->notifyMotion(&args); - args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{4, 5, 6}}); + args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{4, 5, 6}}); mBlocker->notifyMotion(&args); // Now touch down stylus - args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 20, 30}}); + args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 20, 30}}); args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; args.source |= AINPUT_SOURCE_STYLUS; mBlocker->notifyMotion(&args); - args = generateMotionArgs(3 /*downTime*/, 4 /*eventTime*/, MOVE, {{40, 50, 60}}); + args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/4, MOVE, {{40, 50, 60}}); args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; args.source |= AINPUT_SOURCE_STYLUS; mBlocker->notifyMotion(&args); - args = generateMotionArgs(3 /*downTime*/, 5 /*eventTime*/, UP, {{40, 50, 60}}); + args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/5, UP, {{40, 50, 60}}); args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; args.source |= AINPUT_SOURCE_STYLUS; mBlocker->notifyMotion(&args); @@ -569,15 +569,15 @@ TEST_F(UnwantedInteractionBlockerTest, StylusAfterTouchWorks) { */ TEST_F(UnwantedInteractionBlockerTest, DumpCanBeAccessedOnAnotherThread) { mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); - NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}}); + NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); mBlocker->notifyMotion(&args1); std::thread dumpThread([this]() { std::string dump; mBlocker->dump(dump); }); - NotifyMotionArgs args2 = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{4, 5, 6}}); + NotifyMotionArgs args2 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{4, 5, 6}}); mBlocker->notifyMotion(&args2); - NotifyMotionArgs args3 = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{4, 5, 6}}); + NotifyMotionArgs args3 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{4, 5, 6}}); mBlocker->notifyMotion(&args3); dumpThread.join(); } @@ -589,19 +589,19 @@ TEST_F(UnwantedInteractionBlockerTest, DumpCanBeAccessedOnAnotherThread) { TEST_F(UnwantedInteractionBlockerTest, HeuristicFilterWorks) { mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); // Small touch down - NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}}); + NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); mBlocker->notifyMotion(&args1); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN)); // Large touch oval on the next move NotifyMotionArgs args2 = - generateMotionArgs(0 /*downTime*/, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}}); + generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}}); mBlocker->notifyMotion(&args2); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE)); // Lift up the touch to force the model to decide on whether it's a palm NotifyMotionArgs args3 = - generateMotionArgs(0 /*downTime*/, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}); + generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}); mBlocker->notifyMotion(&args3); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(CANCEL)); } @@ -616,14 +616,14 @@ TEST_F(UnwantedInteractionBlockerTest, StylusIsNotBlocked) { InputDeviceInfo info = generateTestDeviceInfo(); info.addSource(AINPUT_SOURCE_STYLUS); mBlocker->notifyInputDevicesChanged({info}); - NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}}); + NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); args1.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; mBlocker->notifyMotion(&args1); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN)); // Move the stylus, setting large TOUCH_MAJOR/TOUCH_MINOR dimensions NotifyMotionArgs args2 = - generateMotionArgs(0 /*downTime*/, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}}); + generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}}); args2.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; mBlocker->notifyMotion(&args2); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE)); @@ -631,7 +631,7 @@ TEST_F(UnwantedInteractionBlockerTest, StylusIsNotBlocked) { // Lift up the stylus. If it were a touch event, this would force the model to decide on whether // it's a palm. NotifyMotionArgs args3 = - generateMotionArgs(0 /*downTime*/, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}); + generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}); args3.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; mBlocker->notifyMotion(&args3); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(UP)); @@ -648,26 +648,26 @@ TEST_F(UnwantedInteractionBlockerTest, TouchIsBlockedWhenMixedWithStylus) { mBlocker->notifyInputDevicesChanged({info}); // Touch down - NotifyMotionArgs args1 = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}}); + NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); mBlocker->notifyMotion(&args1); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN)); // Stylus pointer down - NotifyMotionArgs args2 = generateMotionArgs(0 /*downTime*/, RESAMPLE_PERIOD, POINTER_1_DOWN, + NotifyMotionArgs args2 = generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, POINTER_1_DOWN, {{1, 2, 3}, {10, 20, 30}}); args2.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; mBlocker->notifyMotion(&args2); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(POINTER_1_DOWN)); // Large touch oval on the next finger move - NotifyMotionArgs args3 = generateMotionArgs(0 /*downTime*/, 2 * RESAMPLE_PERIOD, MOVE, + NotifyMotionArgs args3 = generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, MOVE, {{1, 2, 300}, {11, 21, 30}}); args3.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; mBlocker->notifyMotion(&args3); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE)); // Lift up the finger pointer. It should be canceled due to the heuristic filter. - NotifyMotionArgs args4 = generateMotionArgs(0 /*downTime*/, 3 * RESAMPLE_PERIOD, POINTER_0_UP, + NotifyMotionArgs args4 = generateMotionArgs(/*downTime=*/0, 3 * RESAMPLE_PERIOD, POINTER_0_UP, {{1, 2, 300}, {11, 21, 30}}); args4.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; mBlocker->notifyMotion(&args4); @@ -675,7 +675,7 @@ TEST_F(UnwantedInteractionBlockerTest, TouchIsBlockedWhenMixedWithStylus) { AllOf(WithMotionAction(POINTER_0_UP), WithFlags(FLAG_CANCELED))); NotifyMotionArgs args5 = - generateMotionArgs(0 /*downTime*/, 4 * RESAMPLE_PERIOD, MOVE, {{12, 22, 30}}); + generateMotionArgs(/*downTime=*/0, 4 * RESAMPLE_PERIOD, MOVE, {{12, 22, 30}}); args5.pointerProperties[0].id = args4.pointerProperties[1].id; args5.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; mBlocker->notifyMotion(&args5); @@ -683,7 +683,7 @@ TEST_F(UnwantedInteractionBlockerTest, TouchIsBlockedWhenMixedWithStylus) { // Lift up the stylus pointer NotifyMotionArgs args6 = - generateMotionArgs(0 /*downTime*/, 5 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}); + generateMotionArgs(/*downTime=*/0, 5 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}); args6.pointerProperties[0].id = args4.pointerProperties[1].id; args6.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; mBlocker->notifyMotion(&args6); @@ -701,15 +701,15 @@ TEST_F(UnwantedInteractionBlockerTestDeathTest, InconsistentEventAfterResetCause NotifyMotionArgs args; mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); mBlocker->notifyMotion( - &(args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}}))); + &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}}))); mBlocker->notifyMotion( - &(args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, MOVE, {{4, 5, 6}}))); - NotifyDeviceResetArgs resetArgs(1 /*sequenceNum*/, 3 /*eventTime*/, DEVICE_ID); + &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}}))); + NotifyDeviceResetArgs resetArgs(/*sequenceNum=*/1, /*eventTime=*/3, DEVICE_ID); mBlocker->notifyDeviceReset(&resetArgs); // Sending MOVE without a DOWN -> should crash! ASSERT_DEATH( { - mBlocker->notifyMotion(&(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/, + mBlocker->notifyMotion(&(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, MOVE, {{7, 8, 9}}))); }, "Could not find slot"); @@ -720,7 +720,7 @@ TEST_F(UnwantedInteractionBlockerTestDeathTest, InconsistentEventAfterResetCause */ TEST_F(UnwantedInteractionBlockerTestDeathTest, WhenMoveWithoutDownCausesACrash) { ScopedSilentDeath _silentDeath; - NotifyMotionArgs args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 2, 3}}); + NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 2, 3}}); mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); ASSERT_DEATH({ mBlocker->notifyMotion(&args); }, "Could not find slot"); } @@ -741,7 +741,7 @@ TEST_F(PalmRejectorTestDeathTest, InconsistentEventCausesACrash) { ScopedSilentDeath _silentDeath; constexpr nsecs_t downTime = 0; NotifyMotionArgs args = - generateMotionArgs(downTime, 2 /*eventTime*/, MOVE, {{1406.0, 650.0, 52.0}}); + generateMotionArgs(downTime, /*eventTime=*/2, MOVE, {{1406.0, 650.0, 52.0}}); ASSERT_DEATH({ mPalmRejector->processMotion(args); }, "Could not find slot"); } diff --git a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp index c407cffdbf..2909129126 100644 --- a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp @@ -37,22 +37,22 @@ NotifyMotionArgs generateFuzzedMotionArgs(FuzzedDataProvider &fdp) { const nsecs_t downTime = 2; const nsecs_t readTime = downTime + fdp.ConsumeIntegralInRange(0, 1E8); - NotifyMotionArgs motionArgs(fdp.ConsumeIntegral() /*sequenceNum*/, - downTime /*eventTime*/, readTime, - fdp.ConsumeIntegral() /*deviceId*/, AINPUT_SOURCE_ANY, + NotifyMotionArgs motionArgs(/*sequenceNum=*/fdp.ConsumeIntegral(), + /*eventTime=*/downTime, readTime, + /*deviceId=*/fdp.ConsumeIntegral(), AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT, - fdp.ConsumeIntegral() /*policyFlags*/, + /*policyFlags=*/fdp.ConsumeIntegral(), AMOTION_EVENT_ACTION_DOWN, - fdp.ConsumeIntegral() /*actionButton*/, - fdp.ConsumeIntegral() /*flags*/, AMETA_NONE, - fdp.ConsumeIntegral() /*buttonState*/, + /*actionButton=*/fdp.ConsumeIntegral(), + /*flags=*/fdp.ConsumeIntegral(), AMETA_NONE, + /*buttonState=*/fdp.ConsumeIntegral(), MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, - 1 /*pointerCount*/, &properties, &coords, - fdp.ConsumeFloatingPoint() /*xPrecision*/, - fdp.ConsumeFloatingPoint() /*yPrecision*/, + /*pointerCount=*/1, &properties, &coords, + /*xPrecision=*/fdp.ConsumeFloatingPoint(), + /*yPrecision=*/fdp.ConsumeFloatingPoint(), AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, - {} /*videoFrames*/); + /*videoFrames=*/{}); return motionArgs; } @@ -68,8 +68,8 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { [&]() -> void { // SendToNextStage_NotifyConfigurationChangedArgs NotifyConfigurationChangedArgs - args(fdp.ConsumeIntegral() /*sequenceNum*/, - fdp.ConsumeIntegral() /*eventTime*/); + args(/*sequenceNum=*/fdp.ConsumeIntegral(), + /*eventTime=*/fdp.ConsumeIntegral()); mClassifier->notifyConfigurationChanged(&args); }, [&]() -> void { @@ -77,15 +77,15 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { const nsecs_t eventTime = fdp.ConsumeIntegral(); const nsecs_t readTime = eventTime + fdp.ConsumeIntegralInRange(0, 1E8); - NotifyKeyArgs keyArgs(fdp.ConsumeIntegral() /*sequenceNum*/, + NotifyKeyArgs keyArgs(/*sequenceNum=*/fdp.ConsumeIntegral(), eventTime, readTime, - fdp.ConsumeIntegral() /*deviceId*/, + /*deviceId=*/fdp.ConsumeIntegral(), AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, - fdp.ConsumeIntegral() /*policyFlags*/, + /*policyFlags=*/fdp.ConsumeIntegral(), AKEY_EVENT_ACTION_DOWN, - fdp.ConsumeIntegral() /*flags*/, AKEYCODE_HOME, - fdp.ConsumeIntegral() /*scanCode*/, AMETA_NONE, - fdp.ConsumeIntegral() /*downTime*/); + /*flags=*/fdp.ConsumeIntegral(), AKEYCODE_HOME, + /*scanCode=*/fdp.ConsumeIntegral(), AMETA_NONE, + /*downTime=*/fdp.ConsumeIntegral()); mClassifier->notifyKey(&keyArgs); }, @@ -96,19 +96,20 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { }, [&]() -> void { // SendToNextStage_NotifySwitchArgs - NotifySwitchArgs switchArgs(fdp.ConsumeIntegral() /*sequenceNum*/, - fdp.ConsumeIntegral() /*eventTime*/, - fdp.ConsumeIntegral() /*policyFlags*/, - fdp.ConsumeIntegral() /*switchValues*/, - fdp.ConsumeIntegral() /*switchMask*/); + NotifySwitchArgs switchArgs(/*sequenceNum=*/fdp.ConsumeIntegral(), + /*eventTime=*/fdp.ConsumeIntegral(), + /*policyFlags=*/fdp.ConsumeIntegral(), + /*switchValues=*/fdp.ConsumeIntegral(), + /*switchMask=*/fdp.ConsumeIntegral()); mClassifier->notifySwitch(&switchArgs); }, [&]() -> void { // SendToNextStage_NotifyDeviceResetArgs - NotifyDeviceResetArgs resetArgs(fdp.ConsumeIntegral() /*sequenceNum*/, - fdp.ConsumeIntegral() /*eventTime*/, - fdp.ConsumeIntegral() /*deviceId*/); + NotifyDeviceResetArgs resetArgs( + /*sequenceNum=*/fdp.ConsumeIntegral(), + /*eventTime=*/fdp.ConsumeIntegral(), + /*deviceId=*/fdp.ConsumeIntegral()); mClassifier->notifyDeviceReset(&resetArgs); }, diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp index a80839cf9b..20242b1c15 100644 --- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp @@ -182,13 +182,13 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { VibrationSequence pattern(patternCount); for (size_t i = 0; i < patternCount; ++i) { VibrationElement element(i); - element.addChannel(fdp->ConsumeIntegral() /* vibratorId */, - fdp->ConsumeIntegral() /* amplitude */); + element.addChannel(/*vibratorId=*/fdp->ConsumeIntegral(), + /*amplitude=*/fdp->ConsumeIntegral()); pattern.addElement(element); } reader->vibrate(fdp->ConsumeIntegral(), pattern, - fdp->ConsumeIntegral() /*repeat*/, - fdp->ConsumeIntegral() /*token*/); + /*repeat=*/fdp->ConsumeIntegral(), + /*token=*/fdp->ConsumeIntegral()); reader->start(); // Loop through mapper operations until randomness is exhausted. -- GitLab From 278a88f603fbdbc46275dc67463b796a22e71353 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Fri, 27 Jan 2023 16:52:40 -0600 Subject: [PATCH 0789/1310] SF: clean up texture filtering logic * Removes redundant logic for determining whether or not SurfaceFlinger should request texture filtering. * Fixes an issue where ScreenCaptureOutput was using incorrect values for its layer stack projection space. Bug: 238348307 Test: atest TextureFilteringTest Change-Id: I4ece87321ee73d10b86a2c01d53717d337c11926 --- .../include/compositionengine/impl/Output.h | 5 +- .../CompositionEngine/src/Output.cpp | 6 +- services/surfaceflinger/DisplayDevice.cpp | 4 - services/surfaceflinger/DisplayDevice.h | 1 - services/surfaceflinger/DisplayRenderArea.cpp | 30 +------ services/surfaceflinger/DisplayRenderArea.h | 6 +- .../surfaceflinger/FrontEnd/LayerSnapshot.h | 1 - .../FrontEnd/LayerSnapshotBuilder.cpp | 11 --- services/surfaceflinger/Layer.cpp | 82 ------------------- services/surfaceflinger/Layer.h | 4 - services/surfaceflinger/LayerFE.cpp | 9 +- services/surfaceflinger/LayerRenderArea.cpp | 28 +------ services/surfaceflinger/LayerRenderArea.h | 8 +- services/surfaceflinger/RenderArea.h | 19 +---- .../surfaceflinger/ScreenCaptureOutput.cpp | 29 ++----- services/surfaceflinger/ScreenCaptureOutput.h | 5 -- services/surfaceflinger/SurfaceFlinger.cpp | 18 +--- .../fuzzer/surfaceflinger_layer_fuzzer.cpp | 2 +- 18 files changed, 30 insertions(+), 238 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h index 8ec77c075b..229a657236 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h @@ -136,9 +136,8 @@ protected: compositionengine::Output::FrameFences presentAndGetFrameFences() override; virtual renderengine::DisplaySettings generateClientCompositionDisplaySettings() const; std::vector generateClientCompositionRequests( - bool supportsProtectedContent, ui::Dataspace outputDataspace, - std::vector &outLayerFEs) override; - virtual bool layerNeedsFiltering(const OutputLayer*) const; + bool supportsProtectedContent, ui::Dataspace outputDataspace, + std::vector& outLayerFEs) override; void appendRegionFlashRequests(const Region&, std::vector&) override; void setExpensiveRenderingExpected(bool enabled) override; void setHintSessionGpuFence(std::unique_ptr&& gpuFence) override; diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 3ec681668b..403385ea8c 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -1438,7 +1438,7 @@ std::vector Output::generateClientCompositionRequests( Enabled); compositionengine::LayerFE::ClientCompositionTargetSettings targetSettings{.clip = clip, - .needsFiltering = layerNeedsFiltering(layer) || + .needsFiltering = layer->needsFiltering() || outputState.needsFiltering, .isSecure = outputState.isSecure, .supportsProtectedContent = supportsProtectedContent, @@ -1469,10 +1469,6 @@ std::vector Output::generateClientCompositionRequests( return clientCompositionLayers; } -bool Output::layerNeedsFiltering(const compositionengine::OutputLayer* layer) const { - return layer->needsFiltering(); -} - void Output::appendRegionFlashRequests( const Region& flashRegion, std::vector& clientCompositionLayers) { if (flashRegion.isEmpty()) { diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index c61f7d8e55..d0e0c4f8cf 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -347,10 +347,6 @@ const Region& DisplayDevice::getUndefinedRegion() const { return mCompositionDisplay->getState().undefinedRegion; } -bool DisplayDevice::needsFiltering() const { - return mCompositionDisplay->getState().needsFiltering; -} - ui::LayerStack DisplayDevice::getLayerStack() const { return mCompositionDisplay->getState().layerFilter.layerStack; } diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index 370bd66b9e..e48e8130e1 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -115,7 +115,6 @@ public: const ui::Transform& getTransform() const; const Rect& getLayerStackSpaceRect() const; const Rect& getOrientedDisplaySpaceRect() const; - bool needsFiltering() const; ui::LayerStack getLayerStack() const; bool receivesInput() const { return mFlags & eReceivesInput; } diff --git a/services/surfaceflinger/DisplayRenderArea.cpp b/services/surfaceflinger/DisplayRenderArea.cpp index 20486e0aa3..8f39e26e0f 100644 --- a/services/surfaceflinger/DisplayRenderArea.cpp +++ b/services/surfaceflinger/DisplayRenderArea.cpp @@ -48,8 +48,8 @@ std::unique_ptr DisplayRenderArea::create(wp di DisplayRenderArea::DisplayRenderArea(sp display, const Rect& sourceCrop, ui::Size reqSize, ui::Dataspace reqDataSpace, bool useIdentityTransform, bool allowSecureLayers) - : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, display->getLayerStackSpaceRect(), - allowSecureLayers, applyDeviceOrientation(useIdentityTransform, *display)), + : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, allowSecureLayers, + applyDeviceOrientation(useIdentityTransform, *display)), mDisplay(std::move(display)), mSourceCrop(sourceCrop) {} @@ -57,18 +57,6 @@ const ui::Transform& DisplayRenderArea::getTransform() const { return mTransform; } -Rect DisplayRenderArea::getBounds() const { - return mDisplay->getBounds(); -} - -int DisplayRenderArea::getHeight() const { - return mDisplay->getHeight(); -} - -int DisplayRenderArea::getWidth() const { - return mDisplay->getWidth(); -} - bool DisplayRenderArea::isSecure() const { return mAllowSecureLayers && mDisplay->isSecure(); } @@ -77,18 +65,6 @@ sp DisplayRenderArea::getDisplayDevice() const { return mDisplay; } -bool DisplayRenderArea::needsFiltering() const { - // check if the projection from the logical render area - // to the physical render area requires filtering - const Rect& sourceCrop = getSourceCrop(); - int width = sourceCrop.width(); - int height = sourceCrop.height(); - if (getRotationFlags() & ui::Transform::ROT_90) { - std::swap(width, height); - } - return width != getReqWidth() || height != getReqHeight(); -} - Rect DisplayRenderArea::getSourceCrop() const { // use the projected display viewport by default. if (mSourceCrop.isEmpty()) { @@ -107,4 +83,4 @@ Rect DisplayRenderArea::getSourceCrop() const { return rotation.transform(mSourceCrop); } -} // namespace android \ No newline at end of file +} // namespace android diff --git a/services/surfaceflinger/DisplayRenderArea.h b/services/surfaceflinger/DisplayRenderArea.h index 3478fc16c4..ce5410a90d 100644 --- a/services/surfaceflinger/DisplayRenderArea.h +++ b/services/surfaceflinger/DisplayRenderArea.h @@ -33,12 +33,8 @@ public: bool allowSecureLayers = true); const ui::Transform& getTransform() const override; - Rect getBounds() const override; - int getHeight() const override; - int getWidth() const override; bool isSecure() const override; sp getDisplayDevice() const override; - bool needsFiltering() const override; Rect getSourceCrop() const override; private: @@ -50,4 +46,4 @@ private: const ui::Transform mTransform; }; -} // namespace android \ No newline at end of file +} // namespace android diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h index 159410f3ea..d173db3ec5 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h @@ -68,7 +68,6 @@ struct LayerSnapshot : public compositionengine::LayerFECompositionState { renderengine::ShadowSettings shadowSettings; bool premultipliedAlpha; bool isHdrY410; - bool bufferNeedsFiltering; ui::Transform parentTransform; Rect bufferSize; Rect croppedBufferSize; diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index d0ffe613cc..6490476396 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -237,12 +237,6 @@ void handleDropInputMode(LayerSnapshot& snapshot, const LayerSnapshot& parentSna } } -bool getBufferNeedsFiltering(const LayerSnapshot& snapshot, const ui::Size& unrotatedBufferSize) { - const int32_t layerWidth = static_cast(snapshot.geomLayerBounds.getWidth()); - const int32_t layerHeight = static_cast(snapshot.geomLayerBounds.getHeight()); - return layerWidth != unrotatedBufferSize.width || layerHeight != unrotatedBufferSize.height; -} - auto getBlendMode(const LayerSnapshot& snapshot, const RequestedLayerState& requested) { auto blendMode = Hwc2::IComposerClient::BlendMode::NONE; if (snapshot.alpha != 1.0f || !snapshot.isContentOpaque()) { @@ -871,11 +865,6 @@ void LayerSnapshotBuilder::updateLayerBounds(LayerSnapshot& snapshot, if (requested.potentialCursor) { snapshot.cursorFrame = snapshot.geomLayerTransform.transform(bounds); } - - // TODO(b/238781169) use dest vs src - snapshot.bufferNeedsFiltering = snapshot.externalTexture && - getBufferNeedsFiltering(snapshot, - requested.getUnrotatedBufferSize(displayRotationFlags)); } void LayerSnapshotBuilder::updateShadows(LayerSnapshot& snapshot, diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 7a4b3375a1..c58ee2bdb2 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -3319,39 +3319,6 @@ sp Layer::createClone() { return layer; } -bool Layer::bufferNeedsFiltering() const { - const State& s(getDrawingState()); - if (!s.buffer) { - return false; - } - - int32_t bufferWidth = static_cast(s.buffer->getWidth()); - int32_t bufferHeight = static_cast(s.buffer->getHeight()); - - // Undo any transformations on the buffer and return the result. - if (s.bufferTransform & ui::Transform::ROT_90) { - std::swap(bufferWidth, bufferHeight); - } - - if (s.transformToDisplayInverse) { - uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags(); - if (invTransform & ui::Transform::ROT_90) { - std::swap(bufferWidth, bufferHeight); - } - } - - const Rect layerSize{getBounds()}; - int32_t layerWidth = layerSize.getWidth(); - int32_t layerHeight = layerSize.getHeight(); - - // Align the layer orientation with the buffer before comparism - if (mTransformHint & ui::Transform::ROT_90) { - std::swap(layerWidth, layerHeight); - } - - return layerWidth != bufferWidth || layerHeight != bufferHeight; -} - void Layer::decrementPendingBufferCount() { int32_t pendingBuffers = --mPendingBufferTransactions; tracePendingBufferCount(pendingBuffers); @@ -3821,54 +3788,6 @@ bool Layer::isProtected() const { (mBufferInfo.mBuffer->getUsage() & GRALLOC_USAGE_PROTECTED); } -bool Layer::needsFiltering(const DisplayDevice* display) const { - if (!hasBufferOrSidebandStream()) { - return false; - } - const auto outputLayer = findOutputLayerForDisplay(display); - if (outputLayer == nullptr) { - return false; - } - - // We need filtering if the sourceCrop rectangle size does not match the - // displayframe rectangle size (not a 1:1 render) - const auto& compositionState = outputLayer->getState(); - const auto displayFrame = compositionState.displayFrame; - const auto sourceCrop = compositionState.sourceCrop; - return sourceCrop.getHeight() != displayFrame.getHeight() || - sourceCrop.getWidth() != displayFrame.getWidth(); -} - -bool Layer::needsFilteringForScreenshots(const DisplayDevice* display, - const ui::Transform& inverseParentTransform) const { - if (!hasBufferOrSidebandStream()) { - return false; - } - const auto outputLayer = findOutputLayerForDisplay(display); - if (outputLayer == nullptr) { - return false; - } - - // We need filtering if the sourceCrop rectangle size does not match the - // viewport rectangle size (not a 1:1 render) - const auto& compositionState = outputLayer->getState(); - const ui::Transform& displayTransform = display->getTransform(); - const ui::Transform inverseTransform = inverseParentTransform * displayTransform.inverse(); - // Undo the transformation of the displayFrame so that we're back into - // layer-stack space. - const Rect frame = inverseTransform.transform(compositionState.displayFrame); - const FloatRect sourceCrop = compositionState.sourceCrop; - - int32_t frameHeight = frame.getHeight(); - int32_t frameWidth = frame.getWidth(); - // If the display transform had a rotational component then undo the - // rotation so that the orientation matches the source crop. - if (displayTransform.getOrientation() & ui::Transform::ROT_90) { - std::swap(frameHeight, frameWidth); - } - return sourceCrop.getHeight() != frameHeight || sourceCrop.getWidth() != frameWidth; -} - void Layer::latchAndReleaseBuffer() { if (hasReadyFrame()) { bool ignored = false; @@ -4014,7 +3933,6 @@ void Layer::updateSnapshot(bool updateGeometry) { snapshot->layerOpaqueFlagSet = (mDrawingState.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque; snapshot->isHdrY410 = isHdrY410(); - snapshot->bufferNeedsFiltering = bufferNeedsFiltering(); sp p = mDrawingParent.promote(); if (p != nullptr) { snapshot->parentTransform = p->getTransform(); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 3384e4af2d..6ba772f2f0 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -1038,10 +1038,6 @@ private: bool willPresentCurrentTransaction() const; - // Returns true if the transformed buffer size does not match the layer size and we need - // to apply filtering. - bool bufferNeedsFiltering() const; - void callReleaseBufferCallback(const sp& listener, const sp& buffer, uint64_t framenumber, const sp& releaseFence, diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp index c31a2e3cfc..b9c8b78f55 100644 --- a/services/surfaceflinger/LayerFE.cpp +++ b/services/surfaceflinger/LayerFE.cpp @@ -249,14 +249,11 @@ void LayerFE::prepareBufferStateClientComposition( layerSettings.frameNumber = mSnapshot->frameNumber; layerSettings.bufferId = mSnapshot->externalTexture->getId(); - const bool useFiltering = targetSettings.needsFiltering || - mSnapshot->geomLayerTransform.needsBilinearFiltering() || - mSnapshot->bufferNeedsFiltering; - // Query the texture matrix given our current filtering mode. float textureMatrix[16]; getDrawingTransformMatrix(layerSettings.source.buffer.buffer, mSnapshot->geomContentCrop, - mSnapshot->geomBufferTransform, useFiltering, textureMatrix); + mSnapshot->geomBufferTransform, targetSettings.needsFiltering, + textureMatrix); if (mSnapshot->geomBufferUsesDisplayInverseTransform) { /* @@ -306,7 +303,7 @@ void LayerFE::prepareBufferStateClientComposition( mat4::translate(vec4(translateX, translateY, 0.f, 1.f)) * mat4::scale(vec4(scaleWidth, scaleHeight, 1.0f, 1.0f)); - layerSettings.source.buffer.useTextureFiltering = useFiltering; + layerSettings.source.buffer.useTextureFiltering = targetSettings.needsFiltering; layerSettings.source.buffer.textureTransform = mat4(static_cast(textureMatrix)) * tr; diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp index 554fae401e..2b4375b0fa 100644 --- a/services/surfaceflinger/LayerRenderArea.cpp +++ b/services/surfaceflinger/LayerRenderArea.cpp @@ -39,8 +39,8 @@ void reparentForDrawing(const sp& oldParent, const sp& newParent, LayerRenderArea::LayerRenderArea(SurfaceFlinger& flinger, sp layer, const Rect& crop, ui::Size reqSize, ui::Dataspace reqDataSpace, bool childrenOnly, - const Rect& layerStackRect, bool allowSecureLayers) - : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, layerStackRect, allowSecureLayers), + bool allowSecureLayers) + : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, allowSecureLayers), mLayer(std::move(layer)), mCrop(crop), mFlinger(flinger), @@ -50,33 +50,17 @@ const ui::Transform& LayerRenderArea::getTransform() const { return mTransform; } -Rect LayerRenderArea::getBounds() const { - return mLayer->getBufferSize(mLayer->getDrawingState()); -} - -int LayerRenderArea::getHeight() const { - return mLayer->getBufferSize(mLayer->getDrawingState()).getHeight(); -} - -int LayerRenderArea::getWidth() const { - return mLayer->getBufferSize(mLayer->getDrawingState()).getWidth(); -} - bool LayerRenderArea::isSecure() const { return mAllowSecureLayers; } -bool LayerRenderArea::needsFiltering() const { - return mNeedsFiltering; -} - sp LayerRenderArea::getDisplayDevice() const { return nullptr; } Rect LayerRenderArea::getSourceCrop() const { if (mCrop.isEmpty()) { - return getBounds(); + return mLayer->getBufferSize(mLayer->getDrawingState()); } else { return mCrop; } @@ -85,10 +69,6 @@ Rect LayerRenderArea::getSourceCrop() const { void LayerRenderArea::render(std::function drawLayers) { using namespace std::string_literals; - const Rect sourceCrop = getSourceCrop(); - // no need to check rotation because there is none - mNeedsFiltering = sourceCrop.width() != getReqWidth() || sourceCrop.height() != getReqHeight(); - // If layer is offscreen, update mirroring info if it exists if (mLayer->isRemovedFromCurrentState()) { mLayer->traverse(LayerVector::StateSet::Drawing, @@ -116,7 +96,7 @@ void LayerRenderArea::render(std::function drawLayers) { LayerMetadata()}); { Mutex::Autolock _l(mFlinger.mStateLock); - reparentForDrawing(mLayer, screenshotParentLayer, sourceCrop); + reparentForDrawing(mLayer, screenshotParentLayer, getSourceCrop()); } drawLayers(); { diff --git a/services/surfaceflinger/LayerRenderArea.h b/services/surfaceflinger/LayerRenderArea.h index 41273e01c1..322dbd1bf4 100644 --- a/services/surfaceflinger/LayerRenderArea.h +++ b/services/surfaceflinger/LayerRenderArea.h @@ -33,15 +33,10 @@ class SurfaceFlinger; class LayerRenderArea : public RenderArea { public: LayerRenderArea(SurfaceFlinger& flinger, sp layer, const Rect& crop, ui::Size reqSize, - ui::Dataspace reqDataSpace, bool childrenOnly, const Rect& layerStackRect, - bool allowSecureLayers); + ui::Dataspace reqDataSpace, bool childrenOnly, bool allowSecureLayers); const ui::Transform& getTransform() const override; - Rect getBounds() const override; - int getHeight() const override; - int getWidth() const override; bool isSecure() const override; - bool needsFiltering() const override; sp getDisplayDevice() const override; Rect getSourceCrop() const override; @@ -53,7 +48,6 @@ private: const Rect mCrop; ui::Transform mTransform; - bool mNeedsFiltering = false; SurfaceFlinger& mFlinger; const bool mChildrenOnly; diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h index 3c20e3b1e2..910fce043c 100644 --- a/services/surfaceflinger/RenderArea.h +++ b/services/surfaceflinger/RenderArea.h @@ -25,14 +25,12 @@ public: static float getCaptureFillValue(CaptureFill captureFill); RenderArea(ui::Size reqSize, CaptureFill captureFill, ui::Dataspace reqDataSpace, - const Rect& layerStackRect, bool allowSecureLayers = false, - RotationFlags rotation = ui::Transform::ROT_0) + bool allowSecureLayers = false, RotationFlags rotation = ui::Transform::ROT_0) : mAllowSecureLayers(allowSecureLayers), mReqSize(reqSize), mReqDataSpace(reqDataSpace), mCaptureFill(captureFill), - mRotationFlags(rotation), - mLayerStackSpaceRect(layerStackRect) {} + mRotationFlags(rotation) {} static std::function>>()> fromTraverseLayersLambda( std::function traverseLayers) { @@ -58,20 +56,10 @@ public: // blacked out / skipped when rendered to an insecure render area. virtual bool isSecure() const = 0; - // Returns true if the otherwise disabled layer filtering should be - // enabled when rendering to this render area. - virtual bool needsFiltering() const = 0; - // Returns the transform to be applied on layers to transform them into // the logical render area. virtual const ui::Transform& getTransform() const = 0; - // Returns the size of the logical render area. Layers are clipped to the - // logical render area. - virtual int getWidth() const = 0; - virtual int getHeight() const = 0; - virtual Rect getBounds() const = 0; - // Returns the source crop of the render area. The source crop defines // how layers are projected from the logical render area onto the physical // render area. It can be larger than the logical render area. It can @@ -98,9 +86,6 @@ public: virtual sp getDisplayDevice() const = 0; - // Returns the source display viewport. - const Rect& getLayerStackSpaceRect() const { return mLayerStackSpaceRect; } - // If this is a LayerRenderArea, return the root layer of the // capture operation. virtual sp getParentLayer() const { return nullptr; } diff --git a/services/surfaceflinger/ScreenCaptureOutput.cpp b/services/surfaceflinger/ScreenCaptureOutput.cpp index 6d195b9f7b..a1d5cd7379 100644 --- a/services/surfaceflinger/ScreenCaptureOutput.cpp +++ b/services/surfaceflinger/ScreenCaptureOutput.cpp @@ -27,11 +27,8 @@ namespace android { std::shared_ptr createScreenCaptureOutput(ScreenCaptureOutputArgs args) { std::shared_ptr output = compositionengine::impl::createOutputTemplated< ScreenCaptureOutput, compositionengine::CompositionEngine, const RenderArea&, - std::unordered_set, const compositionengine::Output::ColorProfile&, bool>(args.compositionEngine, args.renderArea, - std::move( - args.filterForScreenshot), args.colorProfile, args.regionSampling); output->editState().isSecure = args.renderArea.isSecure(); @@ -45,12 +42,11 @@ std::shared_ptr createScreenCaptureOutput(ScreenCaptureOutp .setHasWideColorGamut(true) .Build())); - ui::Rotation orientation = ui::Transform::toRotation(args.renderArea.getRotationFlags()); - Rect orientedDisplaySpaceRect{args.renderArea.getReqWidth(), args.renderArea.getReqHeight()}; - output->setProjection(orientation, args.renderArea.getLayerStackSpaceRect(), - orientedDisplaySpaceRect); - - Rect sourceCrop = args.renderArea.getSourceCrop(); + const Rect& sourceCrop = args.renderArea.getSourceCrop(); + const ui::Rotation orientation = ui::Transform::toRotation(args.renderArea.getRotationFlags()); + const Rect orientedDisplaySpaceRect{args.renderArea.getReqWidth(), + args.renderArea.getReqHeight()}; + output->setProjection(orientation, sourceCrop, orientedDisplaySpaceRect); output->setDisplaySize({sourceCrop.getWidth(), sourceCrop.getHeight()}); { @@ -64,13 +60,9 @@ std::shared_ptr createScreenCaptureOutput(ScreenCaptureOutp } ScreenCaptureOutput::ScreenCaptureOutput( - const RenderArea& renderArea, - std::unordered_set filterForScreenshot, - const compositionengine::Output::ColorProfile& colorProfile, bool regionSampling) - : mRenderArea(renderArea), - mFilterForScreenshot(std::move(filterForScreenshot)), - mColorProfile(colorProfile), - mRegionSampling(regionSampling) {} + const RenderArea& renderArea, const compositionengine::Output::ColorProfile& colorProfile, + bool regionSampling) + : mRenderArea(renderArea), mColorProfile(colorProfile), mRegionSampling(regionSampling) {} void ScreenCaptureOutput::updateColorProfile(const compositionengine::CompositionRefreshArgs&) { auto& outputState = editState(); @@ -115,9 +107,4 @@ ScreenCaptureOutput::generateClientCompositionRequests( return clientCompositionLayers; } -bool ScreenCaptureOutput::layerNeedsFiltering(const compositionengine::OutputLayer* layer) const { - return mRenderArea.needsFiltering() || - mFilterForScreenshot.find(&layer->getLayerFE()) != mFilterForScreenshot.end(); -} - } // namespace android diff --git a/services/surfaceflinger/ScreenCaptureOutput.h b/services/surfaceflinger/ScreenCaptureOutput.h index 5dffc1d530..4e5a0ccfd2 100644 --- a/services/surfaceflinger/ScreenCaptureOutput.h +++ b/services/surfaceflinger/ScreenCaptureOutput.h @@ -33,7 +33,6 @@ struct ScreenCaptureOutputArgs { std::shared_ptr buffer; float sdrWhitePointNits; float displayBrightnessNits; - std::unordered_set filterForScreenshot; bool regionSampling; }; @@ -44,7 +43,6 @@ struct ScreenCaptureOutputArgs { class ScreenCaptureOutput : public compositionengine::impl::Output { public: ScreenCaptureOutput(const RenderArea& renderArea, - std::unordered_set filterForScreenshot, const compositionengine::Output::ColorProfile& colorProfile, bool regionSampling); @@ -54,15 +52,12 @@ public: bool supportsProtectedContent, ui::Dataspace outputDataspace, std::vector& outLayerFEs) override; - bool layerNeedsFiltering(const compositionengine::OutputLayer*) const override; - protected: bool getSkipColorTransform() const override { return false; } renderengine::DisplaySettings generateClientCompositionDisplaySettings() const override; private: const RenderArea& mRenderArea; - const std::unordered_set mFilterForScreenshot; const compositionengine::Output::ColorProfile& mColorProfile; const bool mRegionSampling; }; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 0dc8b05993..ac61e94e01 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -6559,13 +6559,10 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, return BAD_VALUE; } - Rect layerStackSpaceRect(crop.left, crop.top, crop.left + reqSize.width, - crop.top + reqSize.height); bool childrenOnly = args.childrenOnly; RenderAreaFuture renderAreaFuture = ftl::defer([=]() -> std::unique_ptr { return std::make_unique(*this, parent, crop, reqSize, dataspace, - childrenOnly, layerStackSpaceRect, - args.captureSecureLayers); + childrenOnly, args.captureSecureLayers); }); auto traverseLayers = [parent, args, excludeLayerIds](const LayerVector::Visitor& visitor) { @@ -6720,9 +6717,6 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( ScreenCaptureResults& captureResults) { ATRACE_CALL(); - const auto& display = renderArea->getDisplayDevice(); - const auto& transform = renderArea->getTransform(); - std::unordered_set filterForScreenshot; auto layers = getLayerSnapshots(); for (auto& [layer, layerFE] : layers) { frontend::LayerSnapshot* snapshot = layerFE->mSnapshot.get(); @@ -6730,9 +6724,6 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( captureResults.capturedHdrLayers |= isHdrLayer(*snapshot); layerFE->mSnapshot->geomLayerTransform = renderArea->getTransform() * layerFE->mSnapshot->geomLayerTransform; - if (layer->needsFilteringForScreenshots(display.get(), transform)) { - filterForScreenshot.insert(layerFE.get()); - } } // We allow the system server to take screenshots of secure layers for @@ -6783,9 +6774,9 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( }; auto present = [this, buffer = std::move(buffer), dataspace, sdrWhitePointNits, - displayBrightnessNits, filterForScreenshot = std::move(filterForScreenshot), - grayscale, layerFEs = copyLayerFEs(), layerStack, regionSampling, - renderArea = std::move(renderArea), renderIntent]() -> FenceResult { + displayBrightnessNits, grayscale, layerFEs = copyLayerFEs(), layerStack, + regionSampling, renderArea = std::move(renderArea), + renderIntent]() -> FenceResult { std::unique_ptr compositionEngine = mFactory.createCompositionEngine(); compositionEngine->setRenderEngine(mRenderEngine.get()); @@ -6801,7 +6792,6 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( .buffer = std::move(buffer), .sdrWhitePointNits = sdrWhitePointNits, .displayBrightnessNits = displayBrightnessNits, - .filterForScreenshot = std::move(filterForScreenshot), .regionSampling = regionSampling}); const float colorSaturation = grayscale ? 0 : 1; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp index acfc1d4dc8..11719c435e 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp @@ -166,7 +166,7 @@ void LayerFuzzer::invokeBufferStateLayer() { {mFdp.ConsumeIntegral(), mFdp.ConsumeIntegral()} /*reqSize*/, mFdp.PickValueInArray(kDataspaces), mFdp.ConsumeBool(), - getFuzzedRect(), mFdp.ConsumeBool()); + mFdp.ConsumeBool()); layerArea.render([]() {} /*drawLayers*/); if (!ownsHandle) { -- GitLab From f7b491388ec27d4490df3478a1ae17c5a9bf6f62 Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Tue, 31 Jan 2023 21:21:12 +0000 Subject: [PATCH 0790/1310] Use "SessionHint" enum in ndk API Change NDK to use "SessionHint" enum consistently across definitions. Bug: 266596626 Test: manual Change-Id: I6ad095655b2dc05daf95cb5e7f506b3725915596 --- include/private/performance_hint_private.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h index eaf3b5e791..d50c5f846e 100644 --- a/include/private/performance_hint_private.h +++ b/include/private/performance_hint_private.h @@ -17,6 +17,8 @@ #ifndef ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H #define ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H +#include + __BEGIN_DECLS /** @@ -27,7 +29,7 @@ void APerformanceHint_setIHintManagerForTesting(void* iManager); /** * Hints for the session used to signal upcoming changes in the mode or workload. */ -enum SessionHint { +enum SessionHint: int32_t { /** * This hint indicates a sudden increase in CPU workload intensity. It means * that this hint session needs extra CPU resources immediately to meet the @@ -61,7 +63,7 @@ enum SessionHint { * @return 0 on success * EPIPE if communication with the system service has failed. */ -int APerformanceHint_sendHint(void* session, int hint); +int APerformanceHint_sendHint(void* session, SessionHint hint); /** * Return the list of thread ids, this API should only be used for testing only. -- GitLab From 392291386dd80f88f55870aaa6fbec83eea45d06 Mon Sep 17 00:00:00 2001 From: Hongwei Wang Date: Tue, 24 Jan 2023 15:09:59 -0800 Subject: [PATCH 0791/1310] Include WMShell protolog in bug-report When generating the bug-report, dumpstate will now issue a "save-for-bugreport" command to the WMShell and WMShell flashes the proto logging file to /data/misc/wmtrace if it's enabled. /data/misc/wmtrace is included in the final bug-report. Bug: 251513116 Test: adb bugreport Ignore-AOSP-First: resolve merge conflict on master first Change-Id: I2f48511868a871a290ca38126c4a4c3016169db4 --- cmds/dumpstate/dumpstate.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp index d77b45800d..c06a714b6a 100644 --- a/cmds/dumpstate/dumpstate.cpp +++ b/cmds/dumpstate/dumpstate.cpp @@ -3270,6 +3270,15 @@ void Dumpstate::MaybeSnapshotUiTraces() { return; } + // Include the proto logging from WMShell. + RunCommand( + // Empty name because it's not intended to be classified as a bugreport section. + // Actual logging files can be found as "/data/misc/wmtrace/shell_log.winscope" + // in the bugreport. + "", {"dumpsys", "activity", "service", "SystemUIService", + "WMShell", "protolog", "save-for-bugreport"}, + CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build()); + // Currently WindowManagerService and InputMethodManagerSerivice support WinScope protocol. for (const auto& service : {"input_method", "window"}) { RunCommand( -- GitLab From 63cec8b16e906bc6c316b8c62cb68c4edb404a65 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Mon, 30 Jan 2023 17:44:22 +0000 Subject: [PATCH 0792/1310] jpegr: remove unused functions bug: b/264715926 Change-Id: I504d2e9bb76ea9c7ad5e5c16af9da7ea6baedb65 --- libs/jpegrecoverymap/recoverymap.cpp | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index 7ca6094d20..e06bd24cfa 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -110,31 +110,6 @@ static const map< {JPEGR_TF_PQ, SkNamedTransferFn::kPQ}, }; -/* - * Helper function copies the JPEG image from without EXIF. - * - * @param dest destination of the data to be written. - * @param source source of data being written. - * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos(). - * (4 bypes offset to FF sign, the byte after FF E1 XX XX ). - * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize(). - */ -void copyJpegWithoutExif(jr_compressed_ptr dest, - jr_compressed_ptr source, - size_t exif_pos, - size_t exif_size) { - memcpy(dest, source, sizeof(jpegr_compressed_struct)); - - const size_t exif_offset = 4; //exif_pos has 4 bypes offset to the FF sign - dest->length = source->length - exif_size - exif_offset; - dest->data = malloc(dest->length); - - memcpy(dest->data, source->data, exif_pos - exif_offset); - memcpy((uint8_t*)dest->data + exif_pos - exif_offset, - (uint8_t*)source->data + exif_pos + exif_size, - source->length - exif_pos - exif_size); -} - /* Encode API-0 */ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpegr_transfer_function hdr_tf, -- GitLab From 31d41415101ff3483ce1cc5a9c2ef322490a05bd Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Fri, 18 Nov 2022 16:42:53 -0500 Subject: [PATCH 0793/1310] Create a VsyncSchedule per display In order to determine the vsync offsets between displays, keep track of a VsyncSchedule for each display. Store the VsyncSchedules in a SmallMap. Update getVsyncSchedule with a parameter to choose the display. The default parameter uses the leader's display, which is what current external callers want. Update VsyncDispatches when the leader changes, so that they are always listening to the leader. Enable and disable vsync callbacks per display. Earlier attempts to turn them on and off together could leave a secondary display on a bad schedule. Move state and logic for enabling/disabling the callbacks into VsyncSchedule. Add a method for resyncing all displays at once. Use std::shared_ptrs for VsyncDispatches. This prevents lifetime issues if a VsyncSchedule gets removed while its VsyncDispatch is still in use. Same for VsyncTracker, which is referenced by VsyncDispatch. When the leader VsyncSchedule changes, call cancel on VsyncCallbackRegistrations and replace them with new ones using the new VsyncDispatches. If a callback was scheduled, schedule a new one. Update VsyncSchedule's members' traces so that there is a separate track for each display. Move SF's record of the last HWC Vsync states into VsyncSchedule, so it sits with other related logic. Remove the pending HWC Vsync state, which did not affect behavior. For refresh rate changes, modulate vsync config based on the leader display. When switching leaders, force a period transition to ensure that a potential refresh rate change is completed. Bug: 255601557 Bug: 256196556 Bug: 241285473 Bug: 241286146 Test: libsurfaceflinger_unittest Test: manual (look at perfetto traces) Change-Id: If60218e85292c786b9fa70ecb33ee374d3a385e0 --- .../CompositionEngine/tests/MockHWComposer.h | 2 +- .../DisplayHardware/HWComposer.cpp | 22 +- .../DisplayHardware/HWComposer.h | 7 +- .../surfaceflinger/Scheduler/EventThread.cpp | 66 +-- .../surfaceflinger/Scheduler/EventThread.h | 31 +- .../Scheduler/ISchedulerCallback.h | 37 ++ .../surfaceflinger/Scheduler/MessageQueue.cpp | 22 +- .../surfaceflinger/Scheduler/MessageQueue.h | 9 +- .../surfaceflinger/Scheduler/OneShotTimer.h | 4 +- .../surfaceflinger/Scheduler/Scheduler.cpp | 272 ++++++----- services/surfaceflinger/Scheduler/Scheduler.h | 117 +++-- .../surfaceflinger/Scheduler/VSyncDispatch.h | 5 +- .../Scheduler/VSyncDispatchTimerQueue.cpp | 28 +- .../Scheduler/VSyncDispatchTimerQueue.h | 8 +- .../Scheduler/VSyncPredictor.cpp | 21 +- .../surfaceflinger/Scheduler/VSyncPredictor.h | 7 +- .../surfaceflinger/Scheduler/VSyncReactor.cpp | 26 +- .../surfaceflinger/Scheduler/VSyncReactor.h | 8 +- .../Scheduler/VsyncController.h | 3 +- .../Scheduler/VsyncSchedule.cpp | 80 +++- .../surfaceflinger/Scheduler/VsyncSchedule.h | 66 ++- services/surfaceflinger/SurfaceFlinger.cpp | 171 +++---- services/surfaceflinger/SurfaceFlinger.h | 22 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 20 +- .../surfaceflinger_scheduler_fuzzer.cpp | 42 +- .../tests/unittests/CompositionTest.cpp | 2 +- .../unittests/DisplayTransactionTest.cpp | 4 +- .../unittests/DisplayTransactionTestHelpers.h | 2 - .../tests/unittests/EventThreadTest.cpp | 57 ++- .../tests/unittests/FpsReporterTest.cpp | 2 +- .../FrameRateSelectionPriorityTest.cpp | 2 +- .../tests/unittests/GameModeTest.cpp | 2 +- .../tests/unittests/HWComposerTest.cpp | 28 ++ .../tests/unittests/LayerTestUtils.cpp | 2 +- .../tests/unittests/MessageQueueTest.cpp | 18 +- .../tests/unittests/SchedulerTest.cpp | 5 +- .../SurfaceFlinger_DisplayModeSwitching.cpp | 2 +- ...urfaceFlinger_OnInitializeDisplaysTest.cpp | 5 +- .../SurfaceFlinger_PowerHintTest.cpp | 2 +- ...urfaceFlinger_SetPowerModeInternalTest.cpp | 49 +- ...linger_UpdateLayerMetadataSnapshotTest.cpp | 2 +- .../tests/unittests/TestableScheduler.h | 40 +- .../tests/unittests/TestableSurfaceFlinger.h | 4 +- .../unittests/TransactionApplicationTest.cpp | 5 +- .../unittests/TransactionFrameTracerTest.cpp | 2 +- .../unittests/TransactionSurfaceFrameTest.cpp | 2 +- .../TunnelModeEnabledReporterTest.cpp | 2 +- .../unittests/VSyncDispatchRealtimeTest.cpp | 28 +- .../unittests/VSyncDispatchTimerQueueTest.cpp | 432 +++++++++--------- .../tests/unittests/VSyncPredictorTest.cpp | 5 +- .../tests/unittests/VSyncReactorTest.cpp | 45 +- .../tests/unittests/mock/MockEventThread.h | 42 +- .../unittests/mock/MockSchedulerCallback.h | 6 +- .../unittests/mock/MockVsyncController.h | 8 +- 54 files changed, 1134 insertions(+), 767 deletions(-) create mode 100644 services/surfaceflinger/Scheduler/ISchedulerCallback.h diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h index 6199a5ae40..933f6168c9 100644 --- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h +++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h @@ -93,7 +93,7 @@ public: MOCK_METHOD2(onHotplug, std::optional(hal::HWDisplayId, hal::Connection)); MOCK_CONST_METHOD0(updatesDeviceProductInfoOnHotplugReconnect, bool()); - MOCK_METHOD2(onVsync, bool(hal::HWDisplayId, int64_t)); + MOCK_METHOD(std::optional, onVsync, (hal::HWDisplayId, int64_t)); MOCK_METHOD2(setVsyncEnabled, void(PhysicalDisplayId, hal::Vsync)); MOCK_CONST_METHOD1(isConnected, bool(PhysicalDisplayId)); MOCK_CONST_METHOD1(getModes, std::vector(PhysicalDisplayId)); diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp index 7dde6b4e44..8e74716efa 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -148,16 +149,17 @@ bool HWComposer::updatesDeviceProductInfoOnHotplugReconnect() const { return mUpdateDeviceProductInfoOnHotplugReconnect; } -bool HWComposer::onVsync(hal::HWDisplayId hwcDisplayId, nsecs_t timestamp) { - const auto displayId = toPhysicalDisplayId(hwcDisplayId); - if (!displayId) { +std::optional HWComposer::onVsync(hal::HWDisplayId hwcDisplayId, + nsecs_t timestamp) { + const auto displayIdOpt = toPhysicalDisplayId(hwcDisplayId); + if (!displayIdOpt) { LOG_HWC_DISPLAY_ERROR(hwcDisplayId, "Invalid HWC display"); - return false; + return {}; } - RETURN_IF_INVALID_DISPLAY(*displayId, false); + RETURN_IF_INVALID_DISPLAY(*displayIdOpt, {}); - auto& displayData = mDisplayData[*displayId]; + auto& displayData = mDisplayData[*displayIdOpt]; { // There have been reports of HWCs that signal several vsync events @@ -166,18 +168,18 @@ bool HWComposer::onVsync(hal::HWDisplayId hwcDisplayId, nsecs_t timestamp) { // out here so they don't cause havoc downstream. if (timestamp == displayData.lastPresentTimestamp) { ALOGW("Ignoring duplicate VSYNC event from HWC for display %s (t=%" PRId64 ")", - to_string(*displayId).c_str(), timestamp); - return false; + to_string(*displayIdOpt).c_str(), timestamp); + return {}; } displayData.lastPresentTimestamp = timestamp; } - const auto tag = "HW_VSYNC_" + to_string(*displayId); + const ftl::Concat tag("HW_VSYNC_", displayIdOpt->value); ATRACE_INT(tag.c_str(), displayData.vsyncTraceToggle); displayData.vsyncTraceToggle = !displayData.vsyncTraceToggle; - return true; + return displayIdOpt; } size_t HWComposer::getMaxVirtualDisplayCount() const { diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h index f6155d24ad..acebfb2362 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.h +++ b/services/surfaceflinger/DisplayHardware/HWComposer.h @@ -221,7 +221,10 @@ public: // TODO(b/157555476): Remove when the framework has proper support for headless mode virtual bool updatesDeviceProductInfoOnHotplugReconnect() const = 0; - virtual bool onVsync(hal::HWDisplayId, nsecs_t timestamp) = 0; + // Called when a vsync happens. If the vsync is valid, returns the + // corresponding PhysicalDisplayId. Otherwise returns nullopt. + virtual std::optional onVsync(hal::HWDisplayId, nsecs_t timestamp) = 0; + virtual void setVsyncEnabled(PhysicalDisplayId, hal::Vsync enabled) = 0; virtual bool isConnected(PhysicalDisplayId) const = 0; @@ -402,7 +405,7 @@ public: bool updatesDeviceProductInfoOnHotplugReconnect() const override; - bool onVsync(hal::HWDisplayId, nsecs_t timestamp) override; + std::optional onVsync(hal::HWDisplayId, nsecs_t timestamp) override; void setVsyncEnabled(PhysicalDisplayId, hal::Vsync enabled) override; bool isConnected(PhysicalDisplayId) const override; diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index a902a8ebde..76e9416fec 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -238,29 +238,19 @@ EventThread::~EventThread() = default; namespace impl { -EventThread::EventThread(const char* name, scheduler::VsyncSchedule& vsyncSchedule, +EventThread::EventThread(const char* name, std::shared_ptr vsyncSchedule, + IEventThreadCallback& eventThreadCallback, android::frametimeline::TokenManager* tokenManager, - ThrottleVsyncCallback throttleVsyncCallback, - GetVsyncPeriodFunction getVsyncPeriodFunction, std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration) : mThreadName(name), mVsyncTracer(base::StringPrintf("VSYNC-%s", name), 0), mWorkDuration(base::StringPrintf("VsyncWorkDuration-%s", name), workDuration), mReadyDuration(readyDuration), - mVsyncSchedule(vsyncSchedule), - mVsyncRegistration( - vsyncSchedule.getDispatch(), - [this](nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) { - onVsync(vsyncTime, wakeupTime, readyTime); - }, - name), + mVsyncSchedule(std::move(vsyncSchedule)), + mVsyncRegistration(mVsyncSchedule->getDispatch(), createDispatchCallback(), name), mTokenManager(tokenManager), - mThrottleVsyncCallback(std::move(throttleVsyncCallback)), - mGetVsyncPeriodFunction(std::move(getVsyncPeriodFunction)) { - LOG_ALWAYS_FATAL_IF(getVsyncPeriodFunction == nullptr, - "getVsyncPeriodFunction must not be null"); - + mEventThreadCallback(eventThreadCallback) { mThread = std::thread([this]() NO_THREAD_SAFETY_ANALYSIS { std::unique_lock lock(mMutex); threadMain(lock); @@ -371,16 +361,16 @@ VsyncEventData EventThread::getLatestVsyncEventData( } VsyncEventData vsyncEventData; - nsecs_t frameInterval = mGetVsyncPeriodFunction(connection->mOwnerUid); - vsyncEventData.frameInterval = frameInterval; + const Fps frameInterval = mEventThreadCallback.getLeaderRenderFrameRate(connection->mOwnerUid); + vsyncEventData.frameInterval = frameInterval.getPeriodNsecs(); const auto [presentTime, deadline] = [&]() -> std::pair { std::lock_guard lock(mMutex); - const auto vsyncTime = mVsyncSchedule.getTracker().nextAnticipatedVSyncTimeFrom( + const auto vsyncTime = mVsyncSchedule->getTracker().nextAnticipatedVSyncTimeFrom( systemTime() + mWorkDuration.get().count() + mReadyDuration.count()); return {vsyncTime, vsyncTime - mReadyDuration.count()}; }(); - generateFrameTimeline(vsyncEventData, frameInterval, systemTime(SYSTEM_TIME_MONOTONIC), - presentTime, deadline); + generateFrameTimeline(vsyncEventData, frameInterval.getPeriodNsecs(), + systemTime(SYSTEM_TIME_MONOTONIC), presentTime, deadline); return vsyncEventData; } @@ -541,9 +531,11 @@ void EventThread::threadMain(std::unique_lock& lock) { bool EventThread::shouldConsumeEvent(const DisplayEventReceiver::Event& event, const sp& connection) const { const auto throttleVsync = [&] { - return mThrottleVsyncCallback && - mThrottleVsyncCallback(event.vsync.vsyncData.preferredExpectedPresentationTime(), - connection->mOwnerUid); + const auto& vsyncData = event.vsync.vsyncData; + const auto expectedPresentTime = + TimePoint::fromNs(vsyncData.preferredExpectedPresentationTime()); + return !mEventThreadCallback.isVsyncTargetForUid(expectedPresentTime, + connection->mOwnerUid); }; switch (event.header.type) { @@ -631,9 +623,11 @@ void EventThread::dispatchEvent(const DisplayEventReceiver::Event& event, for (const auto& consumer : consumers) { DisplayEventReceiver::Event copy = event; if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) { - const int64_t frameInterval = mGetVsyncPeriodFunction(consumer->mOwnerUid); - copy.vsync.vsyncData.frameInterval = frameInterval; - generateFrameTimeline(copy.vsync.vsyncData, frameInterval, copy.header.timestamp, + const Fps frameInterval = + mEventThreadCallback.getLeaderRenderFrameRate(consumer->mOwnerUid); + copy.vsync.vsyncData.frameInterval = frameInterval.getPeriodNsecs(); + generateFrameTimeline(copy.vsync.vsyncData, frameInterval.getPeriodNsecs(), + copy.header.timestamp, event.vsync.vsyncData.preferredExpectedPresentationTime(), event.vsync.vsyncData.preferredDeadlineTimestamp()); } @@ -699,6 +693,26 @@ const char* EventThread::toCString(State state) { } } +void EventThread::onNewVsyncSchedule(std::shared_ptr schedule) { + std::lock_guard lock(mMutex); + const bool reschedule = mVsyncRegistration.cancel() == scheduler::CancelResult::Cancelled; + mVsyncSchedule = std::move(schedule); + mVsyncRegistration = + scheduler::VSyncCallbackRegistration(mVsyncSchedule->getDispatch(), + createDispatchCallback(), mThreadName); + if (reschedule) { + mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(), + .readyDuration = mReadyDuration.count(), + .earliestVsync = mLastVsyncCallbackTime.ns()}); + } +} + +scheduler::VSyncDispatch::Callback EventThread::createDispatchCallback() { + return [this](nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) { + onVsync(vsyncTime, wakeupTime, readyTime); + }; +} + } // namespace impl } // namespace android diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h index ab9085e44a..b86553bebe 100644 --- a/services/surfaceflinger/Scheduler/EventThread.h +++ b/services/surfaceflinger/Scheduler/EventThread.h @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -67,6 +68,15 @@ enum class VSyncRequest { // Subsequent values are periods. }; +class IEventThreadCallback { +public: + virtual ~IEventThreadCallback() = default; + + virtual bool isVsyncTargetForUid(TimePoint expectedVsyncTime, uid_t uid) const = 0; + + virtual Fps getLeaderRenderFrameRate(uid_t uid) const = 0; +}; + class EventThreadConnection : public gui::BnDisplayEventConnection { public: EventThreadConnection(EventThread*, uid_t callingUid, ResyncCallback, @@ -133,18 +143,17 @@ public: // Retrieves the number of event connections tracked by this EventThread. virtual size_t getEventThreadConnectionCount() = 0; + + virtual void onNewVsyncSchedule(std::shared_ptr) = 0; }; namespace impl { class EventThread : public android::EventThread { public: - using ThrottleVsyncCallback = std::function; - using GetVsyncPeriodFunction = std::function; - - EventThread(const char* name, scheduler::VsyncSchedule&, frametimeline::TokenManager*, - ThrottleVsyncCallback, GetVsyncPeriodFunction, - std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration); + EventThread(const char* name, std::shared_ptr, IEventThreadCallback&, + frametimeline::TokenManager*, std::chrono::nanoseconds workDuration, + std::chrono::nanoseconds readyDuration); ~EventThread(); sp createEventConnection( @@ -176,6 +185,8 @@ public: size_t getEventThreadConnectionCount() override; + void onNewVsyncSchedule(std::shared_ptr) override; + private: friend EventThreadTest; @@ -199,17 +210,19 @@ private: nsecs_t timestamp, nsecs_t preferredExpectedPresentationTime, nsecs_t preferredDeadlineTimestamp) const; + scheduler::VSyncDispatch::Callback createDispatchCallback(); + const char* const mThreadName; TracedOrdinal mVsyncTracer; TracedOrdinal mWorkDuration GUARDED_BY(mMutex); std::chrono::nanoseconds mReadyDuration GUARDED_BY(mMutex); - scheduler::VsyncSchedule& mVsyncSchedule; + std::shared_ptr mVsyncSchedule; TimePoint mLastVsyncCallbackTime GUARDED_BY(mMutex) = TimePoint::now(); scheduler::VSyncCallbackRegistration mVsyncRegistration GUARDED_BY(mMutex); frametimeline::TokenManager* const mTokenManager; - const ThrottleVsyncCallback mThrottleVsyncCallback; - const GetVsyncPeriodFunction mGetVsyncPeriodFunction; + // mEventThreadCallback will outlive the EventThread. + IEventThreadCallback& mEventThreadCallback; std::thread mThread; mutable std::mutex mMutex; diff --git a/services/surfaceflinger/Scheduler/ISchedulerCallback.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h new file mode 100644 index 0000000000..92c2189244 --- /dev/null +++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.h @@ -0,0 +1,37 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include + +#include "Display/DisplayModeRequest.h" + +namespace android::scheduler { + +struct ISchedulerCallback { + virtual void setVsyncEnabled(PhysicalDisplayId, bool) = 0; + virtual void requestDisplayModes(std::vector) = 0; + virtual void kernelTimerChanged(bool expired) = 0; + virtual void triggerOnFrameRateOverridesChanged() = 0; + +protected: + ~ISchedulerCallback() = default; +}; + +} // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp index dec8f59ee9..925f739534 100644 --- a/services/surfaceflinger/Scheduler/MessageQueue.cpp +++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp @@ -75,19 +75,37 @@ void MessageQueue::vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, ns mHandler->dispatchFrame(vsyncId, expectedVsyncTime); } -void MessageQueue::initVsync(scheduler::VSyncDispatch& dispatch, +void MessageQueue::initVsync(std::shared_ptr dispatch, frametimeline::TokenManager& tokenManager, std::chrono::nanoseconds workDuration) { std::lock_guard lock(mVsync.mutex); mVsync.workDuration = workDuration; mVsync.tokenManager = &tokenManager; + updateVsyncRegistrationLocked(std::move(dispatch)); +} + +void MessageQueue::updateVsyncRegistration(std::shared_ptr dispatch) { + std::lock_guard lock(mVsync.mutex); + updateVsyncRegistrationLocked(std::move(dispatch)); +} + +void MessageQueue::updateVsyncRegistrationLocked( + std::shared_ptr dispatch) { + const bool reschedule = mVsync.registration && + mVsync.registration->cancel() == scheduler::CancelResult::Cancelled; mVsync.registration = std::make_unique< - scheduler::VSyncCallbackRegistration>(dispatch, + scheduler::VSyncCallbackRegistration>(std::move(dispatch), std::bind(&MessageQueue::vsyncCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), "sf"); + if (reschedule) { + mVsync.scheduledFrameTime = + mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(), + .readyDuration = 0, + .earliestVsync = mVsync.lastCallbackTime.ns()}); + } } void MessageQueue::destroyVsync() { diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h index 0d59337950..ecb237d128 100644 --- a/services/surfaceflinger/Scheduler/MessageQueue.h +++ b/services/surfaceflinger/Scheduler/MessageQueue.h @@ -65,7 +65,7 @@ class MessageQueue { public: virtual ~MessageQueue() = default; - virtual void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&, + virtual void initVsync(std::shared_ptr, frametimeline::TokenManager&, std::chrono::nanoseconds workDuration) = 0; virtual void destroyVsync() = 0; virtual void setDuration(std::chrono::nanoseconds workDuration) = 0; @@ -106,6 +106,8 @@ protected: void vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, nsecs_t readyTime); + void updateVsyncRegistration(std::shared_ptr) EXCLUDES(mVsync.mutex); + private: virtual void onFrameSignal(ICompositor&, VsyncId, TimePoint expectedVsyncTime) = 0; @@ -127,10 +129,13 @@ private: Vsync mVsync; + void updateVsyncRegistrationLocked(std::shared_ptr) + REQUIRES(mVsync.mutex); + public: explicit MessageQueue(ICompositor&); - void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&, + void initVsync(std::shared_ptr, frametimeline::TokenManager&, std::chrono::nanoseconds workDuration) override; void destroyVsync() override; void setDuration(std::chrono::nanoseconds workDuration) override; diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.h b/services/surfaceflinger/Scheduler/OneShotTimer.h index f95646c48f..02e8719c08 100644 --- a/services/surfaceflinger/Scheduler/OneShotTimer.h +++ b/services/surfaceflinger/Scheduler/OneShotTimer.h @@ -40,7 +40,7 @@ public: OneShotTimer(std::string name, const Interval& interval, const ResetCallback& resetCallback, const TimeoutCallback& timeoutCallback, - std::unique_ptr clock = std::make_unique()); + std::unique_ptr clock = std::make_unique()); ~OneShotTimer(); Duration interval() const { return mInterval; } @@ -82,7 +82,7 @@ private: std::thread mThread; // Clock object for the timer. Mocked in unit tests. - std::unique_ptr mClock; + std::unique_ptr mClock; // Semaphore to keep mThread synchronized. sem_t mSemaphore; diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 33c98ff6d5..e6f46655fc 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -113,10 +113,18 @@ void Scheduler::setLeaderDisplay(std::optional leaderIdOpt) { } void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) { + registerDisplayInternal(displayId, std::move(selectorPtr), + std::make_shared(displayId, mFeatures)); +} + +void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId, + RefreshRateSelectorPtr selectorPtr, + std::shared_ptr vsyncSchedule) { demoteLeaderDisplay(); std::scoped_lock lock(mDisplayLock); mRefreshRateSelectors.emplace_or_replace(displayId, std::move(selectorPtr)); + mVsyncSchedules.emplace_or_replace(displayId, std::move(vsyncSchedule)); promoteLeaderDisplay(); } @@ -126,6 +134,7 @@ void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { std::scoped_lock lock(mDisplayLock); mRefreshRateSelectors.erase(displayId); + mVsyncSchedules.erase(displayId); // Do not allow removing the final display. Code in the scheduler expects // there to be at least one display. (This may be relaxed in the future with @@ -153,52 +162,49 @@ void Scheduler::onFrameSignal(ICompositor& compositor, VsyncId vsyncId, compositor.sample(); } -void Scheduler::createVsyncSchedule(FeatureFlags features) { - mVsyncSchedule.emplace(features); +std::optional Scheduler::getFrameRateOverride(uid_t uid) const { + std::scoped_lock lock(mDisplayLock); + return getFrameRateOverrideLocked(uid); } -std::optional Scheduler::getFrameRateOverride(uid_t uid) const { +std::optional Scheduler::getFrameRateOverrideLocked(uid_t uid) const { const bool supportsFrameRateOverrideByContent = - leaderSelectorPtr()->supportsAppFrameRateOverrideByContent(); + leaderSelectorPtrLocked()->supportsAppFrameRateOverrideByContent(); return mFrameRateOverrideMappings .getFrameRateOverrideForUid(uid, supportsFrameRateOverrideByContent); } -bool Scheduler::isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const { +bool Scheduler::isVsyncTargetForUid(TimePoint expectedVsyncTime, uid_t uid) const { const auto frameRate = getFrameRateOverride(uid); if (!frameRate.has_value()) { return true; } - return mVsyncSchedule->getTracker().isVSyncInPhase(expectedVsyncTimestamp.ns(), *frameRate); + return isVsyncInPhase(expectedVsyncTime, *frameRate); } -bool Scheduler::isVsyncInPhase(TimePoint timePoint, const Fps frameRate) const { - return mVsyncSchedule->getTracker().isVSyncInPhase(timePoint.ns(), frameRate); +bool Scheduler::isVsyncInPhase(TimePoint expectedVsyncTime, Fps frameRate) const { + return getVsyncSchedule()->getTracker().isVSyncInPhase(expectedVsyncTime.ns(), frameRate); } -impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() const { - return [this](nsecs_t expectedVsyncTimestamp, uid_t uid) { - return !isVsyncValid(TimePoint::fromNs(expectedVsyncTimestamp), uid); - }; -} +Fps Scheduler::getLeaderRenderFrameRate(uid_t uid) const { + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + auto vsyncSchedule = getVsyncScheduleLocked(); -impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const { - return [this](uid_t uid) { - const Fps refreshRate = leaderSelectorPtr()->getActiveMode().fps; - const nsecs_t currentPeriod = mVsyncSchedule->period().ns() ?: refreshRate.getPeriodNsecs(); + const Fps refreshRate = leaderSelectorPtrLocked()->getActiveMode().fps; + const nsecs_t currentPeriod = vsyncSchedule->period().ns() ?: refreshRate.getPeriodNsecs(); - const auto frameRate = getFrameRateOverride(uid); - if (!frameRate.has_value()) { - return currentPeriod; - } + const auto frameRate = getFrameRateOverrideLocked(uid); + if (!frameRate.has_value()) { + return Fps::fromPeriodNsecs(currentPeriod); + } - const auto divisor = RefreshRateSelector::getFrameRateDivisor(refreshRate, *frameRate); - if (divisor <= 1) { - return currentPeriod; - } - return currentPeriod * divisor; - }; + const auto divisor = RefreshRateSelector::getFrameRateDivisor(refreshRate, *frameRate); + if (divisor <= 1) { + return Fps::fromPeriodNsecs(currentPeriod); + } + return Fps::fromPeriodNsecs(currentPeriod * divisor); } ConnectionHandle Scheduler::createEventThread(Cycle cycle, @@ -206,9 +212,7 @@ ConnectionHandle Scheduler::createEventThread(Cycle cycle, std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration) { auto eventThread = std::make_unique(cycle == Cycle::Render ? "app" : "appSf", - *mVsyncSchedule, tokenManager, - makeThrottleVsyncCallback(), - makeGetVsyncPeriodFunction(), + getVsyncSchedule(), *this, tokenManager, workDuration, readyDuration); auto& handle = cycle == Cycle::Render ? mAppConnectionHandle : mSfConnectionHandle; @@ -265,7 +269,6 @@ void Scheduler::onScreenAcquired(ConnectionHandle handle) { thread = mConnections[handle].thread.get(); } thread->onScreenAcquired(); - mScreenAcquired = true; } void Scheduler::onScreenReleased(ConnectionHandle handle) { @@ -276,7 +279,6 @@ void Scheduler::onScreenReleased(ConnectionHandle handle) { thread = mConnections[handle].thread.get(); } thread->onScreenReleased(); - mScreenAcquired = false; } void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId) { @@ -387,48 +389,57 @@ void Scheduler::setVsyncConfig(const VsyncConfig& config, Period vsyncPeriod) { setDuration(config.sfWorkDuration); } -void Scheduler::enableHardwareVsync() { - std::lock_guard lock(mHWVsyncLock); - if (!mPrimaryHWVsyncEnabled && mHWVsyncAvailable) { - mVsyncSchedule->getTracker().resetModel(); - mSchedulerCallback.setVsyncEnabled(true); - mPrimaryHWVsyncEnabled = true; - } +void Scheduler::enableHardwareVsync(PhysicalDisplayId id) { + auto schedule = getVsyncSchedule(id); + schedule->enableHardwareVsync(mSchedulerCallback); } -void Scheduler::disableHardwareVsync(bool makeUnavailable) { - std::lock_guard lock(mHWVsyncLock); - if (mPrimaryHWVsyncEnabled) { - mSchedulerCallback.setVsyncEnabled(false); - mPrimaryHWVsyncEnabled = false; - } - if (makeUnavailable) { - mHWVsyncAvailable = false; +void Scheduler::disableHardwareVsync(PhysicalDisplayId id, bool disallow) { + auto schedule = getVsyncSchedule(id); + schedule->disableHardwareVsync(mSchedulerCallback, disallow); +} + +void Scheduler::resyncAllToHardwareVsync(bool allowToEnable) { + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + + for (const auto& [id, _] : mRefreshRateSelectors) { + resyncToHardwareVsyncLocked(id, allowToEnable); } } -void Scheduler::resyncToHardwareVsync(bool makeAvailable, Fps refreshRate) { - { - std::lock_guard lock(mHWVsyncLock); - if (makeAvailable) { - mHWVsyncAvailable = makeAvailable; - } else if (!mHWVsyncAvailable) { - // Hardware vsync is not currently available, so abort the resync - // attempt for now - return; - } +void Scheduler::resyncToHardwareVsyncLocked(PhysicalDisplayId id, bool allowToEnable, + std::optional refreshRate) { + if (!refreshRate) { + auto selectorPtr = mRefreshRateSelectors.get(id); + LOG_ALWAYS_FATAL_IF(!selectorPtr); + refreshRate = selectorPtr->get()->getActiveMode().modePtr->getFps(); + } + auto schedule = getVsyncScheduleLocked(id); + if (allowToEnable) { + schedule->allowHardwareVsync(); + } else if (!schedule->isHardwareVsyncAllowed()) { + // Hardware vsync is not currently allowed, so abort the resync + // attempt for now. + return; } - setVsyncPeriod(refreshRate.getPeriodNsecs()); + setVsyncPeriod(schedule, refreshRate->getPeriodNsecs(), false /* force */); } -void Scheduler::setRenderRate(Fps renderFrameRate) { - const auto mode = leaderSelectorPtr()->getActiveMode(); +void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate) { + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + + auto selectorPtr = mRefreshRateSelectors.get(id); + LOG_ALWAYS_FATAL_IF(!selectorPtr); + const auto mode = selectorPtr->get()->getActiveMode(); using fps_approx_ops::operator!=; LOG_ALWAYS_FATAL_IF(renderFrameRate != mode.fps, - "Mismatch in render frame rates. Selector: %s, Scheduler: %s", - to_string(mode.fps).c_str(), to_string(renderFrameRate).c_str()); + "Mismatch in render frame rates. Selector: %s, Scheduler: %s, Display: " + "%" PRIu64, + to_string(mode.fps).c_str(), to_string(renderFrameRate).c_str(), id.value); ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(), to_string(mode.modePtr->getFps()).c_str()); @@ -437,7 +448,7 @@ void Scheduler::setRenderRate(Fps renderFrameRate) { LOG_ALWAYS_FATAL_IF(divisor == 0, "%s <> %s -- not divisors", to_string(mode.fps).c_str(), to_string(mode.fps).c_str()); - mVsyncSchedule->getTracker().setDivisor(static_cast(divisor)); + getVsyncScheduleLocked(id)->getTracker().setDivisor(static_cast(divisor)); } void Scheduler::resync() { @@ -447,49 +458,43 @@ void Scheduler::resync() { const nsecs_t last = mLastResyncTime.exchange(now); if (now - last > kIgnoreDelay) { - const auto refreshRate = leaderSelectorPtr()->getActiveMode().modePtr->getFps(); - resyncToHardwareVsync(false, refreshRate); + resyncAllToHardwareVsync(false /* allowToEnable */); } } -void Scheduler::setVsyncPeriod(nsecs_t period) { +void Scheduler::setVsyncPeriod(const std::shared_ptr& schedule, nsecs_t period, + bool force) { + ALOGD("Scheduler::setVsyncPeriod"); if (period <= 0) return; - std::lock_guard lock(mHWVsyncLock); - mVsyncSchedule->getController().startPeriodTransition(period); - - if (!mPrimaryHWVsyncEnabled) { - mVsyncSchedule->getTracker().resetModel(); - mSchedulerCallback.setVsyncEnabled(true); - mPrimaryHWVsyncEnabled = true; - } + // TODO (b/266712910):The old code held mHWVsyncLock before calling + // startPeriodTransition. Move these into a new method on VsyncSchedule that + // encapsulates this behavior there and allows holding the lock the whole + // time. + schedule->getController().startPeriodTransition(period, force); + schedule->enableHardwareVsync(mSchedulerCallback); } -void Scheduler::addResyncSample(nsecs_t timestamp, std::optional hwcVsyncPeriod, - bool* periodFlushed) { - bool needsHwVsync = false; - *periodFlushed = false; - { // Scope for the lock - std::lock_guard lock(mHWVsyncLock); - if (mPrimaryHWVsyncEnabled) { - needsHwVsync = - mVsyncSchedule->getController().addHwVsyncTimestamp(timestamp, hwcVsyncPeriod, - periodFlushed); - } - } - - if (needsHwVsync) { - enableHardwareVsync(); +bool Scheduler::addResyncSample(PhysicalDisplayId id, nsecs_t timestamp, + std::optional hwcVsyncPeriod) { + bool periodFlushed = false; + auto schedule = getVsyncSchedule(id); + if (schedule->getController().addHwVsyncTimestamp(timestamp, hwcVsyncPeriod, &periodFlushed)) { + schedule->enableHardwareVsync(mSchedulerCallback); } else { - disableHardwareVsync(false); + schedule->disableHardwareVsync(mSchedulerCallback, false /* disallow */); } + + return periodFlushed; } -void Scheduler::addPresentFence(std::shared_ptr fence) { - if (mVsyncSchedule->getController().addPresentFence(std::move(fence))) { - enableHardwareVsync(); +void Scheduler::addPresentFence(PhysicalDisplayId id, std::shared_ptr fence) { + auto schedule = getVsyncSchedule(id); + const bool needMoreSignals = schedule->getController().addPresentFence(std::move(fence)); + if (needMoreSignals) { + schedule->enableHardwareVsync(mSchedulerCallback); } else { - disableHardwareVsync(false); + schedule->disableHardwareVsync(mSchedulerCallback, false /* disallow */); } } @@ -541,12 +546,22 @@ void Scheduler::onTouchHint() { } } -void Scheduler::setDisplayPowerMode(hal::PowerMode powerMode) { - { +void Scheduler::setDisplayPowerMode(PhysicalDisplayId id, hal::PowerMode powerMode) { + const bool isLeader = [this, id]() REQUIRES(kMainThreadContext) { + ftl::FakeGuard guard(mDisplayLock); + return id == mLeaderDisplayId; + }(); + if (isLeader) { + // TODO (b/255657128): This needs to be handled per display. std::lock_guard lock(mPolicyLock); mPolicy.displayPowerMode = powerMode; } - mVsyncSchedule->getController().setDisplayPowerMode(powerMode); + { + std::scoped_lock lock(mDisplayLock); + auto vsyncSchedule = getVsyncScheduleLocked(id); + vsyncSchedule->getController().setDisplayPowerMode(powerMode); + } + if (!isLeader) return; if (mDisplayPowerTimer) { mDisplayPowerTimer->reset(); @@ -557,6 +572,24 @@ void Scheduler::setDisplayPowerMode(hal::PowerMode powerMode) { mLayerHistory.clear(); } +std::shared_ptr Scheduler::getVsyncSchedule( + std::optional idOpt) const { + std::scoped_lock lock(mDisplayLock); + return getVsyncScheduleLocked(idOpt); +} + +std::shared_ptr Scheduler::getVsyncScheduleLocked( + std::optional idOpt) const { + ftl::FakeGuard guard(kMainThreadContext); + if (!idOpt) { + LOG_ALWAYS_FATAL_IF(!mLeaderDisplayId, "Missing a leader!"); + idOpt = mLeaderDisplayId; + } + auto scheduleOpt = mVsyncSchedules.get(*idOpt); + LOG_ALWAYS_FATAL_IF(!scheduleOpt); + return std::const_pointer_cast(scheduleOpt->get()); +} + void Scheduler::kernelIdleTimerCallback(TimerState state) { ATRACE_INT("ExpiredKernelIdleTimer", static_cast(state)); @@ -571,12 +604,17 @@ void Scheduler::kernelIdleTimerCallback(TimerState state) { // If we're not in performance mode then the kernel timer shouldn't do // anything, as the refresh rate during DPU power collapse will be the // same. - resyncToHardwareVsync(true /* makeAvailable */, refreshRate); + resyncAllToHardwareVsync(true /* allowToEnable */); } else if (state == TimerState::Expired && refreshRate <= FPS_THRESHOLD_FOR_KERNEL_TIMER) { // Disable HW VSYNC if the timer expired, as we don't need it enabled if // we're not pushing frames, and if we're in PERFORMANCE mode then we'll // need to update the VsyncController model anyway. - disableHardwareVsync(false /* makeUnavailable */); + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + constexpr bool disallow = false; + for (auto& [_, schedule] : mVsyncSchedules) { + schedule->disableHardwareVsync(mSchedulerCallback, disallow); + } } mSchedulerCallback.kernelTimerChanged(state == TimerState::Expired); @@ -630,19 +668,23 @@ void Scheduler::dump(utils::Dumper& dumper) const { mFrameRateOverrideMappings.dump(dumper); dumper.eol(); - - { - utils::Dumper::Section section(dumper, "Hardware VSYNC"sv); - - std::lock_guard lock(mHWVsyncLock); - dumper.dump("screenAcquired"sv, mScreenAcquired.load()); - dumper.dump("hwVsyncAvailable"sv, mHWVsyncAvailable); - dumper.dump("hwVsyncEnabled"sv, mPrimaryHWVsyncEnabled); - } } void Scheduler::dumpVsync(std::string& out) const { - mVsyncSchedule->dump(out); + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + if (mLeaderDisplayId) { + base::StringAppendF(&out, "VsyncSchedule for leader %s:\n", + to_string(*mLeaderDisplayId).c_str()); + getVsyncScheduleLocked()->dump(out); + } + for (auto& [id, vsyncSchedule] : mVsyncSchedules) { + if (id == mLeaderDisplayId) { + continue; + } + base::StringAppendF(&out, "VsyncSchedule for follower %s:\n", to_string(id).c_str()); + vsyncSchedule->dump(out); + } } bool Scheduler::updateFrameRateOverrides(GlobalSignals consideredSignals, Fps displayRefreshRate) { @@ -662,6 +704,7 @@ void Scheduler::promoteLeaderDisplay(std::optional leaderIdOp mLeaderDisplayId = leaderIdOpt.value_or(mRefreshRateSelectors.begin()->first); ALOGI("Display %s is the leader", to_string(*mLeaderDisplayId).c_str()); + auto vsyncSchedule = getVsyncScheduleLocked(*mLeaderDisplayId); if (const auto leaderPtr = leaderSelectorPtrLocked()) { leaderPtr->setIdleTimerCallbacks( {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); }, @@ -671,6 +714,17 @@ void Scheduler::promoteLeaderDisplay(std::optional leaderIdOp [this] { kernelIdleTimerCallback(TimerState::Expired); }}}); leaderPtr->startIdleTimer(); + + const Fps refreshRate = leaderPtr->getActiveMode().modePtr->getFps(); + setVsyncPeriod(vsyncSchedule, refreshRate.getPeriodNsecs(), true /* force */); + } + + updateVsyncRegistration(vsyncSchedule->getDispatch()); + { + std::lock_guard lock(mConnectionsLock); + for (auto& [_, connection] : mConnections) { + connection.thread->onNewVsyncSchedule(vsyncSchedule); + } } } diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index e8224481c6..8dc2def113 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -43,6 +43,7 @@ #include "Display/DisplayModeRequest.h" #include "EventThread.h" #include "FrameRateOverrideMappings.h" +#include "ISchedulerCallback.h" #include "LayerHistory.h" #include "MessageQueue.h" #include "OneShotTimer.h" @@ -92,17 +93,7 @@ namespace scheduler { using GlobalSignals = RefreshRateSelector::GlobalSignals; -struct ISchedulerCallback { - virtual void setVsyncEnabled(bool) = 0; - virtual void requestDisplayModes(std::vector) = 0; - virtual void kernelTimerChanged(bool expired) = 0; - virtual void triggerOnFrameRateOverridesChanged() = 0; - -protected: - ~ISchedulerCallback() = default; -}; - -class Scheduler : android::impl::MessageQueue { +class Scheduler : android::impl::MessageQueue, public IEventThreadCallback { using Impl = android::impl::MessageQueue; public: @@ -123,8 +114,6 @@ public: void run(); - void createVsyncSchedule(FeatureFlags); - using Impl::initVsync; using Impl::getScheduledFrameTime; @@ -177,9 +166,21 @@ public: const VsyncModulator& vsyncModulator() const { return *mVsyncModulator; } + // In some cases, we should only modulate for the leader display. In those + // cases, the caller should pass in the relevant display, and the method + // will no-op if it's not the leader. Other cases are not specific to a + // display. template (VsyncModulator::*)(Args...)> - void modulateVsync(Handler handler, Args... args) { + void modulateVsync(std::optional id, Handler handler, Args... args) { + if (id) { + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + if (id != mLeaderDisplayId) { + return; + } + } + if (const auto config = (*mVsyncModulator.*handler)(args...)) { setVsyncConfig(*config, getLeaderVsyncPeriod()); } @@ -188,24 +189,32 @@ public: void setVsyncConfigSet(const VsyncConfigSet&, Period vsyncPeriod); // Sets the render rate for the scheduler to run at. - void setRenderRate(Fps); + void setRenderRate(PhysicalDisplayId, Fps); - void enableHardwareVsync(); - void disableHardwareVsync(bool makeUnavailable); + void enableHardwareVsync(PhysicalDisplayId); + void disableHardwareVsync(PhysicalDisplayId, bool makeUnavailable); // Resyncs the scheduler to hardware vsync. - // If makeAvailable is true, then hardware vsync will be turned on. + // If allowToEnable is true, then hardware vsync will be turned on. // Otherwise, if hardware vsync is not already enabled then this method will // no-op. - void resyncToHardwareVsync(bool makeAvailable, Fps refreshRate); + // If refreshRate is nullopt, use the existing refresh rate of the display. + void resyncToHardwareVsync(PhysicalDisplayId id, bool allowToEnable, + std::optional refreshRate = std::nullopt) + EXCLUDES(mDisplayLock) { + std::scoped_lock lock(mDisplayLock); + ftl::FakeGuard guard(kMainThreadContext); + resyncToHardwareVsyncLocked(id, allowToEnable, refreshRate); + } void resync() EXCLUDES(mDisplayLock); void forceNextResync() { mLastResyncTime = 0; } - // Passes a vsync sample to VsyncController. periodFlushed will be true if - // VsyncController detected that the vsync period changed, and false otherwise. - void addResyncSample(nsecs_t timestamp, std::optional hwcVsyncPeriod, - bool* periodFlushed); - void addPresentFence(std::shared_ptr); + // Passes a vsync sample to VsyncController. Returns true if + // VsyncController detected that the vsync period changed and false + // otherwise. + bool addResyncSample(PhysicalDisplayId, nsecs_t timestamp, + std::optional hwcVsyncPeriod); + void addPresentFence(PhysicalDisplayId, std::shared_ptr) EXCLUDES(mDisplayLock); // Layers are registered on creation, and unregistered when the weak reference expires. void registerLayer(Layer*); @@ -223,20 +232,22 @@ public: // Indicates that touch interaction is taking place. void onTouchHint(); - void setDisplayPowerMode(hal::PowerMode powerMode); + void setDisplayPowerMode(PhysicalDisplayId, hal::PowerMode powerMode) + REQUIRES(kMainThreadContext); - VsyncSchedule& getVsyncSchedule() { return *mVsyncSchedule; } - - // Returns true if a given vsync timestamp is considered valid vsync - // for a given uid - bool isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const; + std::shared_ptr getVsyncSchedule( + std::optional idOpt = std::nullopt) const EXCLUDES(mDisplayLock); + std::shared_ptr getVsyncSchedule( + std::optional idOpt = std::nullopt) EXCLUDES(mDisplayLock) { + return std::const_pointer_cast( + static_cast(this)->getVsyncSchedule(idOpt)); + } - // Checks if a vsync timestamp is in phase for a frame rate - bool isVsyncInPhase(TimePoint timePoint, const Fps frameRate) const; + bool isVsyncInPhase(TimePoint expectedVsyncTime, Fps frameRate) const; void dump(utils::Dumper&) const; void dump(ConnectionHandle, std::string&) const; - void dumpVsync(std::string&) const; + void dumpVsync(std::string&) const EXCLUDES(mDisplayLock); // Returns the preferred refresh rate and frame rate for the leader display. FrameRateMode getPreferredDisplayMode(); @@ -274,6 +285,10 @@ public: return mLayerHistory.getLayerFramerate(now, id); } + // IEventThreadCallback overrides: + bool isVsyncTargetForUid(TimePoint expectedVsyncTime, uid_t uid) const override; + Fps getLeaderRenderFrameRate(uid_t uid) const override; + private: friend class TestableScheduler; @@ -295,7 +310,12 @@ private: void touchTimerCallback(TimerState); void displayPowerTimerCallback(TimerState); - void setVsyncPeriod(nsecs_t period); + void resyncToHardwareVsyncLocked(PhysicalDisplayId, bool allowToEnable, + std::optional refreshRate = std::nullopt) + REQUIRES(kMainThreadContext, mDisplayLock); + void resyncAllToHardwareVsync(bool allowToEnable) EXCLUDES(mDisplayLock); + void setVsyncPeriod(const std::shared_ptr&, nsecs_t period, bool force) + REQUIRES(mDisplayLock); void setVsyncConfig(const VsyncConfig&, Period vsyncPeriod); // Chooses a leader among the registered displays, unless `leaderIdOpt` is specified. The new @@ -307,6 +327,12 @@ private: // caller on the main thread to avoid deadlock, since the timer thread locks it before exit. void demoteLeaderDisplay() REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock, mPolicyLock); + void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr, + std::shared_ptr) REQUIRES(kMainThreadContext) + EXCLUDES(mDisplayLock); + + std::optional getFrameRateOverrideLocked(uid_t) const REQUIRES(mDisplayLock); + struct Policy; // Sets the S state of the policy to the T value under mPolicyLock, and chooses a display mode @@ -344,9 +370,6 @@ private: void dispatchCachedReportedMode() REQUIRES(mPolicyLock) EXCLUDES(mDisplayLock); - android::impl::EventThread::ThrottleVsyncCallback makeThrottleVsyncCallback() const; - android::impl::EventThread::GetVsyncPeriodFunction makeGetVsyncPeriodFunction() const; - // Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection. struct Connection { sp connection; @@ -360,14 +383,9 @@ private: ConnectionHandle mAppConnectionHandle; ConnectionHandle mSfConnectionHandle; - mutable std::mutex mHWVsyncLock; - bool mPrimaryHWVsyncEnabled GUARDED_BY(mHWVsyncLock) = false; - bool mHWVsyncAvailable GUARDED_BY(mHWVsyncLock) = false; - std::atomic mLastResyncTime = 0; const FeatureFlags mFeatures; - std::optional mVsyncSchedule; // Shifts the VSYNC phase during certain transactions and refresh rate changes. const sp mVsyncModulator; @@ -392,6 +410,10 @@ private: display::PhysicalDisplayMap mRefreshRateSelectors GUARDED_BY(mDisplayLock) GUARDED_BY(kMainThreadContext); + // TODO (b/266715559): Store in the same map as mRefreshRateSelectors. + display::PhysicalDisplayMap> mVsyncSchedules + GUARDED_BY(mDisplayLock) GUARDED_BY(kMainThreadContext); + ftl::Optional mLeaderDisplayId GUARDED_BY(mDisplayLock) GUARDED_BY(kMainThreadContext); @@ -411,6 +433,14 @@ private: .value_or(std::cref(noLeader)); } + std::shared_ptr getVsyncScheduleLocked( + std::optional idOpt = std::nullopt) const REQUIRES(mDisplayLock); + std::shared_ptr getVsyncScheduleLocked( + std::optional idOpt = std::nullopt) REQUIRES(mDisplayLock) { + return std::const_pointer_cast( + static_cast(this)->getVsyncScheduleLocked(idOpt)); + } + struct Policy { // Policy for choosing the display mode. LayerHistory::Summary contentRequirements; @@ -437,9 +467,6 @@ private: static constexpr std::chrono::nanoseconds MAX_VSYNC_APPLIED_TIME = 200ms; FrameRateOverrideMappings mFrameRateOverrideMappings; - - // Keeps track of whether the screen is acquired for debug - std::atomic mScreenAcquired = false; }; } // namespace scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncDispatch.h b/services/surfaceflinger/Scheduler/VSyncDispatch.h index 95201314a4..77875e3b4d 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatch.h +++ b/services/surfaceflinger/Scheduler/VSyncDispatch.h @@ -161,7 +161,8 @@ protected: */ class VSyncCallbackRegistration { public: - VSyncCallbackRegistration(VSyncDispatch&, VSyncDispatch::Callback, std::string callbackName); + VSyncCallbackRegistration(std::shared_ptr, VSyncDispatch::Callback, + std::string callbackName); ~VSyncCallbackRegistration(); VSyncCallbackRegistration(VSyncCallbackRegistration&&); @@ -177,7 +178,7 @@ public: CancelResult cancel(); private: - std::reference_wrapper mDispatch; + std::shared_ptr mDispatch; VSyncDispatch::CallbackToken mToken; bool mValidToken; }; diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp index 73d52cf986..26389eb8cc 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp @@ -215,10 +215,10 @@ void VSyncDispatchTimerQueueEntry::dump(std::string& result) const { } VSyncDispatchTimerQueue::VSyncDispatchTimerQueue(std::unique_ptr tk, - VSyncTracker& tracker, nsecs_t timerSlack, - nsecs_t minVsyncDistance) + VsyncSchedule::TrackerPtr tracker, + nsecs_t timerSlack, nsecs_t minVsyncDistance) : mTimeKeeper(std::move(tk)), - mTracker(tracker), + mTracker(std::move(tracker)), mTimerSlack(timerSlack), mMinVsyncDistance(minVsyncDistance) {} @@ -255,7 +255,7 @@ void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( } if (it != skipUpdateIt) { - callback->update(mTracker, now); + callback->update(*mTracker, now); } auto const wakeupTime = *callback->wakeupTime(); if (!min || *min > wakeupTime) { @@ -365,10 +365,10 @@ ScheduleResult VSyncDispatchTimerQueue::scheduleLocked(CallbackToken token, auto const rearmImminent = now > mIntendedWakeupTime; if (CC_UNLIKELY(rearmImminent)) { callback->addPendingWorkloadUpdate(scheduleTiming); - return getExpectedCallbackTime(mTracker, now, scheduleTiming); + return getExpectedCallbackTime(*mTracker, now, scheduleTiming); } - const ScheduleResult result = callback->schedule(scheduleTiming, mTracker, now); + const ScheduleResult result = callback->schedule(scheduleTiming, *mTracker, now); if (!result.has_value()) { return {}; } @@ -434,15 +434,15 @@ void VSyncDispatchTimerQueue::dump(std::string& result) const { } } -VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncDispatch& dispatch, +VSyncCallbackRegistration::VSyncCallbackRegistration(std::shared_ptr dispatch, VSyncDispatch::Callback callback, std::string callbackName) - : mDispatch(dispatch), - mToken(dispatch.registerCallback(std::move(callback), std::move(callbackName))), + : mDispatch(std::move(dispatch)), + mToken(mDispatch->registerCallback(std::move(callback), std::move(callbackName))), mValidToken(true) {} VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncCallbackRegistration&& other) - : mDispatch(other.mDispatch), + : mDispatch(std::move(other.mDispatch)), mToken(std::move(other.mToken)), mValidToken(std::move(other.mValidToken)) { other.mValidToken = false; @@ -457,28 +457,28 @@ VSyncCallbackRegistration& VSyncCallbackRegistration::operator=(VSyncCallbackReg } VSyncCallbackRegistration::~VSyncCallbackRegistration() { - if (mValidToken) mDispatch.get().unregisterCallback(mToken); + if (mValidToken) mDispatch->unregisterCallback(mToken); } ScheduleResult VSyncCallbackRegistration::schedule(VSyncDispatch::ScheduleTiming scheduleTiming) { if (!mValidToken) { return std::nullopt; } - return mDispatch.get().schedule(mToken, scheduleTiming); + return mDispatch->schedule(mToken, scheduleTiming); } ScheduleResult VSyncCallbackRegistration::update(VSyncDispatch::ScheduleTiming scheduleTiming) { if (!mValidToken) { return std::nullopt; } - return mDispatch.get().update(mToken, scheduleTiming); + return mDispatch->update(mToken, scheduleTiming); } CancelResult VSyncCallbackRegistration::cancel() { if (!mValidToken) { return CancelResult::Error; } - return mDispatch.get().cancel(mToken); + return mDispatch->cancel(mToken); } } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h index c3af136d66..6499d69969 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h @@ -26,11 +26,11 @@ #include #include "VSyncDispatch.h" +#include "VsyncSchedule.h" namespace android::scheduler { class TimeKeeper; -class VSyncTracker; // VSyncDispatchTimerQueueEntry is a helper class representing internal state for each entry in // VSyncDispatchTimerQueue hoisted to public for unit testing. @@ -120,8 +120,8 @@ public: // should be grouped into one wakeup. // \param[in] minVsyncDistance The minimum distance between two vsync estimates before the // vsyncs are considered the same vsync event. - VSyncDispatchTimerQueue(std::unique_ptr, VSyncTracker&, nsecs_t timerSlack, - nsecs_t minVsyncDistance); + VSyncDispatchTimerQueue(std::unique_ptr, VsyncSchedule::TrackerPtr, + nsecs_t timerSlack, nsecs_t minVsyncDistance); ~VSyncDispatchTimerQueue(); CallbackToken registerCallback(Callback, std::string callbackName) final; @@ -148,7 +148,7 @@ private: static constexpr nsecs_t kInvalidTime = std::numeric_limits::max(); std::unique_ptr const mTimeKeeper; - VSyncTracker& mTracker; + VsyncSchedule::TrackerPtr mTracker; nsecs_t const mTimerSlack; nsecs_t const mMinVsyncDistance; diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 02e12fd942..a3b8a5619d 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -40,14 +41,16 @@ namespace android::scheduler { using base::StringAppendF; +using base::StringPrintf; static auto constexpr kMaxPercent = 100u; VSyncPredictor::~VSyncPredictor() = default; -VSyncPredictor::VSyncPredictor(nsecs_t idealPeriod, size_t historySize, +VSyncPredictor::VSyncPredictor(std::string name, nsecs_t idealPeriod, size_t historySize, size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent) - : mTraceOn(property_get_bool("debug.sf.vsp_trace", false)), + : mName(name), + mTraceOn(property_get_bool("debug.sf.vsp_trace", false)), kHistorySize(historySize), kMinimumSamplesForPrediction(minimumSamplesForPrediction), kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)), @@ -57,12 +60,14 @@ VSyncPredictor::VSyncPredictor(nsecs_t idealPeriod, size_t historySize, inline void VSyncPredictor::traceInt64If(const char* name, int64_t value) const { if (CC_UNLIKELY(mTraceOn)) { - ATRACE_INT64(name, value); + traceInt64(name, value); } } inline void VSyncPredictor::traceInt64(const char* name, int64_t value) const { - ATRACE_INT64(name, value); + // TODO (b/266817103): Pass in PhysicalDisplayId and use ftl::Concat to + // avoid unnecessary allocations. + ATRACE_INT64(StringPrintf("%s %s", name, mName.c_str()).c_str(), value); } inline size_t VSyncPredictor::next(size_t i) const { @@ -214,8 +219,8 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { it->second = {anticipatedPeriod, intercept}; - ALOGV("model update ts: %" PRId64 " slope: %" PRId64 " intercept: %" PRId64, timestamp, - anticipatedPeriod, intercept); + ALOGV("model update ts %s: %" PRId64 " slope: %" PRId64 " intercept: %" PRId64, mName.c_str(), + timestamp, anticipatedPeriod, intercept); return true; } @@ -327,7 +332,7 @@ bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) c } void VSyncPredictor::setDivisor(unsigned divisor) { - ALOGV("%s: %d", __func__, divisor); + ALOGV("%s %s: %d", __func__, mName.c_str(), divisor); std::lock_guard lock(mMutex); mDivisor = divisor; } @@ -343,7 +348,7 @@ VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const { } void VSyncPredictor::setPeriod(nsecs_t period) { - ATRACE_CALL(); + ATRACE_FORMAT("%s %s", __func__, mName.c_str()); traceInt64("VSP-setPeriod", period); std::lock_guard lock(mMutex); diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index 305cdb0d56..1ded54f768 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -29,14 +29,15 @@ namespace android::scheduler { class VSyncPredictor : public VSyncTracker { public: /* + * \param [in] name The name of the display this corresponds to. * \param [in] idealPeriod The initial ideal period to use. * \param [in] historySize The internal amount of entries to store in the model. * \param [in] minimumSamplesForPrediction The minimum number of samples to collect before * predicting. \param [in] outlierTolerancePercent a number 0 to 100 that will be used to filter * samples that fall outlierTolerancePercent from an anticipated vsync event. */ - VSyncPredictor(nsecs_t idealPeriod, size_t historySize, size_t minimumSamplesForPrediction, - uint32_t outlierTolerancePercent); + VSyncPredictor(std::string name, nsecs_t idealPeriod, size_t historySize, + size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent); ~VSyncPredictor(); bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex); @@ -76,6 +77,8 @@ private: VSyncPredictor& operator=(VSyncPredictor const&) = delete; void clearTimestamps() REQUIRES(mMutex); + const std::string mName; + inline void traceInt64If(const char* name, int64_t value) const; inline void traceInt64(const char* name, int64_t value) const; bool const mTraceOn; diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp index b5f212e085..a831f6624d 100644 --- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -32,6 +33,7 @@ namespace android::scheduler { using base::StringAppendF; +using base::StringPrintf; VsyncController::~VsyncController() = default; @@ -39,12 +41,12 @@ nsecs_t SystemClock::now() const { return systemTime(SYSTEM_TIME_MONOTONIC); } -VSyncReactor::VSyncReactor(std::unique_ptr clock, VSyncTracker& tracker, +VSyncReactor::VSyncReactor(std::string name, std::unique_ptr clock, VSyncTracker& tracker, size_t pendingFenceLimit, bool supportKernelIdleTimer) - : mClock(std::move(clock)), + : mName(name), + mClock(std::move(clock)), mTracker(tracker), mPendingLimit(pendingFenceLimit), - // TODO(adyabr): change mSupportKernelIdleTimer when the active display changes mSupportKernelIdleTimer(supportKernelIdleTimer) {} VSyncReactor::~VSyncReactor() = default; @@ -114,7 +116,7 @@ void VSyncReactor::updateIgnorePresentFencesInternal() { } void VSyncReactor::startPeriodTransitionInternal(nsecs_t newPeriod) { - ATRACE_CALL(); + ATRACE_FORMAT("%s %s", __func__, mName.c_str()); mPeriodConfirmationInProgress = true; mPeriodTransitioningTo = newPeriod; mMoreSamplesNeeded = true; @@ -122,18 +124,20 @@ void VSyncReactor::startPeriodTransitionInternal(nsecs_t newPeriod) { } void VSyncReactor::endPeriodTransition() { - ATRACE_CALL(); + ATRACE_FORMAT("%s %s", __func__, mName.c_str()); mPeriodTransitioningTo.reset(); mPeriodConfirmationInProgress = false; mLastHwVsync.reset(); } -void VSyncReactor::startPeriodTransition(nsecs_t period) { - ATRACE_INT64("VSR-startPeriodTransition", period); +void VSyncReactor::startPeriodTransition(nsecs_t period, bool force) { + // TODO (b/266817103): Pass in PhysicalDisplayId and use ftl::Concat to + // avoid unnecessary allocations. + ATRACE_INT64(StringPrintf("VSR-startPeriodTransition %s", mName.c_str()).c_str(), period); std::lock_guard lock(mMutex); mLastHwVsync.reset(); - if (!mSupportKernelIdleTimer && period == mTracker.currentPeriod()) { + if (!mSupportKernelIdleTimer && period == mTracker.currentPeriod() && !force) { endPeriodTransition(); setIgnorePresentFencesInternal(false); mMoreSamplesNeeded = false; @@ -181,7 +185,7 @@ bool VSyncReactor::addHwVsyncTimestamp(nsecs_t timestamp, std::optional std::lock_guard lock(mMutex); if (periodConfirmed(timestamp, hwcVsyncPeriod)) { - ATRACE_NAME("VSR: period confirmed"); + ATRACE_FORMAT("VSR %s: period confirmed", mName.c_str()); if (mPeriodTransitioningTo) { mTracker.setPeriod(*mPeriodTransitioningTo); *periodFlushed = true; @@ -195,12 +199,12 @@ bool VSyncReactor::addHwVsyncTimestamp(nsecs_t timestamp, std::optional endPeriodTransition(); mMoreSamplesNeeded = mTracker.needsMoreSamples(); } else if (mPeriodConfirmationInProgress) { - ATRACE_NAME("VSR: still confirming period"); + ATRACE_FORMAT("VSR %s: still confirming period", mName.c_str()); mLastHwVsync = timestamp; mMoreSamplesNeeded = true; *periodFlushed = false; } else { - ATRACE_NAME("VSR: adding sample"); + ATRACE_FORMAT("VSR %s: adding sample", mName.c_str()); *periodFlushed = false; mTracker.addVsyncTimestamp(timestamp); mMoreSamplesNeeded = mTracker.needsMoreSamples(); diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.h b/services/surfaceflinger/Scheduler/VSyncReactor.h index 4501487392..fd9ca42f28 100644 --- a/services/surfaceflinger/Scheduler/VSyncReactor.h +++ b/services/surfaceflinger/Scheduler/VSyncReactor.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -37,14 +38,14 @@ class VSyncTracker; // TODO (b/145217110): consider renaming. class VSyncReactor : public VsyncController { public: - VSyncReactor(std::unique_ptr clock, VSyncTracker& tracker, size_t pendingFenceLimit, - bool supportKernelIdleTimer); + VSyncReactor(std::string name, std::unique_ptr clock, VSyncTracker& tracker, + size_t pendingFenceLimit, bool supportKernelIdleTimer); ~VSyncReactor(); bool addPresentFence(std::shared_ptr) final; void setIgnorePresentFences(bool ignore) final; - void startPeriodTransition(nsecs_t period) final; + void startPeriodTransition(nsecs_t period, bool force) final; bool addHwVsyncTimestamp(nsecs_t timestamp, std::optional hwcVsyncPeriod, bool* periodFlushed) final; @@ -61,6 +62,7 @@ private: bool periodConfirmed(nsecs_t vsync_timestamp, std::optional hwcVsyncPeriod) REQUIRES(mMutex); + const std::string mName; std::unique_ptr const mClock; VSyncTracker& mTracker; size_t const mPendingLimit; diff --git a/services/surfaceflinger/Scheduler/VsyncController.h b/services/surfaceflinger/Scheduler/VsyncController.h index 726a420649..917789934a 100644 --- a/services/surfaceflinger/Scheduler/VsyncController.h +++ b/services/surfaceflinger/Scheduler/VsyncController.h @@ -63,8 +63,9 @@ public: * itself. The controller will end the period transition internally. * * \param [in] period The period that the system is changing into. + * \param [in] force True to recalibrate even if period matches the existing period. */ - virtual void startPeriodTransition(nsecs_t period) = 0; + virtual void startPeriodTransition(nsecs_t period, bool force) = 0; /* * Tells the tracker to stop using present fences to get a vsync signal. diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp index 95bc31f239..951c1eca25 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp @@ -21,6 +21,9 @@ #include "VsyncSchedule.h" +#include "ISchedulerCallback.h" +#include "Scheduler.h" +#include "Utils/Dumper.h" #include "VSyncDispatchTimerQueue.h" #include "VSyncPredictor.h" #include "VSyncReactor.h" @@ -39,8 +42,8 @@ class VsyncSchedule::PredictedVsyncTracer { } public: - explicit PredictedVsyncTracer(VsyncDispatch& dispatch) - : mRegistration(dispatch, makeVsyncCallback(), __func__) { + explicit PredictedVsyncTracer(std::shared_ptr dispatch) + : mRegistration(std::move(dispatch), makeVsyncCallback(), __func__) { schedule(); } @@ -51,21 +54,23 @@ private: VSyncCallbackRegistration mRegistration; }; -VsyncSchedule::VsyncSchedule(FeatureFlags features) - : mTracker(createTracker()), - mDispatch(createDispatch(*mTracker)), - mController(createController(*mTracker, features)) { +VsyncSchedule::VsyncSchedule(PhysicalDisplayId id, FeatureFlags features) + : mId(id), + mTracker(createTracker(id)), + mDispatch(createDispatch(mTracker)), + mController(createController(id, *mTracker, features)) { if (features.test(Feature::kTracePredictedVsync)) { - mTracer = std::make_unique(*mDispatch); + mTracer = std::make_unique(mDispatch); } } -VsyncSchedule::VsyncSchedule(TrackerPtr tracker, DispatchPtr dispatch, ControllerPtr controller) - : mTracker(std::move(tracker)), +VsyncSchedule::VsyncSchedule(PhysicalDisplayId id, TrackerPtr tracker, DispatchPtr dispatch, + ControllerPtr controller) + : mId(id), + mTracker(std::move(tracker)), mDispatch(std::move(dispatch)), mController(std::move(controller)) {} -VsyncSchedule::VsyncSchedule(VsyncSchedule&&) = default; VsyncSchedule::~VsyncSchedule() = default; Period VsyncSchedule::period() const { @@ -77,6 +82,13 @@ TimePoint VsyncSchedule::vsyncDeadlineAfter(TimePoint timePoint) const { } void VsyncSchedule::dump(std::string& out) const { + utils::Dumper dumper(out); + { + std::lock_guard lock(mHwVsyncLock); + dumper.dump("hwVsyncState", ftl::enum_string(mHwVsyncState)); + dumper.dump("lastHwVsyncState", ftl::enum_string(mLastHwVsyncState)); + } + out.append("VsyncController:\n"); mController->dump(out); @@ -84,40 +96,72 @@ void VsyncSchedule::dump(std::string& out) const { mDispatch->dump(out); } -VsyncSchedule::TrackerPtr VsyncSchedule::createTracker() { +VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(PhysicalDisplayId id) { // TODO(b/144707443): Tune constants. constexpr nsecs_t kInitialPeriod = (60_Hz).getPeriodNsecs(); constexpr size_t kHistorySize = 20; constexpr size_t kMinSamplesForPrediction = 6; constexpr uint32_t kDiscardOutlierPercent = 20; - return std::make_unique(kInitialPeriod, kHistorySize, kMinSamplesForPrediction, - kDiscardOutlierPercent); + return std::make_unique(to_string(id), kInitialPeriod, kHistorySize, + kMinSamplesForPrediction, kDiscardOutlierPercent); } -VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(VsyncTracker& tracker) { +VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(TrackerPtr tracker) { using namespace std::chrono_literals; // TODO(b/144707443): Tune constants. constexpr std::chrono::nanoseconds kGroupDispatchWithin = 500us; constexpr std::chrono::nanoseconds kSnapToSameVsyncWithin = 3ms; - return std::make_unique(std::make_unique(), tracker, + return std::make_unique(std::make_unique(), std::move(tracker), kGroupDispatchWithin.count(), kSnapToSameVsyncWithin.count()); } -VsyncSchedule::ControllerPtr VsyncSchedule::createController(VsyncTracker& tracker, +VsyncSchedule::ControllerPtr VsyncSchedule::createController(PhysicalDisplayId id, + VsyncTracker& tracker, FeatureFlags features) { // TODO(b/144707443): Tune constants. constexpr size_t kMaxPendingFences = 20; const bool hasKernelIdleTimer = features.test(Feature::kKernelIdleTimer); - auto reactor = std::make_unique(std::make_unique(), tracker, - kMaxPendingFences, hasKernelIdleTimer); + auto reactor = std::make_unique(to_string(id), std::make_unique(), + tracker, kMaxPendingFences, hasKernelIdleTimer); reactor->setIgnorePresentFences(!features.test(Feature::kPresentFences)); return reactor; } +void VsyncSchedule::enableHardwareVsync(ISchedulerCallback& callback) { + std::lock_guard lock(mHwVsyncLock); + if (mHwVsyncState == HwVsyncState::Disabled) { + getTracker().resetModel(); + callback.setVsyncEnabled(mId, true); + mHwVsyncState = HwVsyncState::Enabled; + mLastHwVsyncState = HwVsyncState::Enabled; + } +} + +void VsyncSchedule::disableHardwareVsync(ISchedulerCallback& callback, bool disallow) { + std::lock_guard lock(mHwVsyncLock); + if (mHwVsyncState == HwVsyncState::Enabled) { + callback.setVsyncEnabled(mId, false); + mLastHwVsyncState = HwVsyncState::Disabled; + } + mHwVsyncState = disallow ? HwVsyncState::Disallowed : HwVsyncState::Disabled; +} + +bool VsyncSchedule::isHardwareVsyncAllowed() const { + std::lock_guard lock(mHwVsyncLock); + return mHwVsyncState != HwVsyncState::Disallowed; +} + +void VsyncSchedule::allowHardwareVsync() { + std::lock_guard lock(mHwVsyncLock); + if (mHwVsyncState == HwVsyncState::Disallowed) { + mHwVsyncState = HwVsyncState::Disabled; + } +} + } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h index 173b1d00cf..ffb7ad5b42 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.h +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h @@ -19,8 +19,10 @@ #include #include +#include #include #include +#include namespace android { class EventThreadTest; @@ -32,6 +34,8 @@ class SchedulerFuzzer; namespace android::scheduler { +struct ISchedulerCallback; + // TODO(b/185535769): Rename classes, and remove aliases. class VSyncDispatch; class VSyncTracker; @@ -43,8 +47,7 @@ using VsyncTracker = VSyncTracker; // Schedule that synchronizes to hardware VSYNC of a physical display. class VsyncSchedule { public: - explicit VsyncSchedule(FeatureFlags); - VsyncSchedule(VsyncSchedule&&); + VsyncSchedule(PhysicalDisplayId, FeatureFlags); ~VsyncSchedule(); Period period() const; @@ -55,30 +58,71 @@ public: VsyncTracker& getTracker() { return *mTracker; } VsyncController& getController() { return *mController; } + // TODO(b/185535769): Once these are hidden behind the API, they may no + // longer need to be shared_ptrs. + using DispatchPtr = std::shared_ptr; + using TrackerPtr = std::shared_ptr; + // TODO(b/185535769): Remove once VsyncSchedule owns all registrations. - VsyncDispatch& getDispatch() { return *mDispatch; } + DispatchPtr getDispatch() { return mDispatch; } void dump(std::string&) const; + // Turn on hardware vsyncs, unless mHwVsyncState is Disallowed, in which + // case this call is ignored. + void enableHardwareVsync(ISchedulerCallback&) EXCLUDES(mHwVsyncLock); + + // Disable hardware vsyncs. If `disallow` is true, future calls to + // enableHardwareVsync are ineffective until allowHardwareVsync is called. + void disableHardwareVsync(ISchedulerCallback&, bool disallow) EXCLUDES(mHwVsyncLock); + + // Restore the ability to enable hardware vsync. + void allowHardwareVsync() EXCLUDES(mHwVsyncLock); + + // If true, enableHardwareVsync can enable hardware vsync (if not already + // enabled). If false, enableHardwareVsync does nothing. + bool isHardwareVsyncAllowed() const EXCLUDES(mHwVsyncLock); + +protected: + using ControllerPtr = std::unique_ptr; + + // For tests. + VsyncSchedule(PhysicalDisplayId, TrackerPtr, DispatchPtr, ControllerPtr); + private: friend class TestableScheduler; friend class android::EventThreadTest; friend class android::fuzz::SchedulerFuzzer; - using TrackerPtr = std::unique_ptr; - using DispatchPtr = std::unique_ptr; - using ControllerPtr = std::unique_ptr; + static TrackerPtr createTracker(PhysicalDisplayId); + static DispatchPtr createDispatch(TrackerPtr); + static ControllerPtr createController(PhysicalDisplayId, VsyncTracker&, FeatureFlags); - // For tests. - VsyncSchedule(TrackerPtr, DispatchPtr, ControllerPtr); + mutable std::mutex mHwVsyncLock; + enum class HwVsyncState { + // Hardware vsyncs are currently enabled. + Enabled, - static TrackerPtr createTracker(); - static DispatchPtr createDispatch(VsyncTracker&); - static ControllerPtr createController(VsyncTracker&, FeatureFlags); + // Hardware vsyncs are currently disabled. They can be enabled by a call + // to `enableHardwareVsync`. + Disabled, + + // Hardware vsyncs are not currently allowed (e.g. because the display + // is off). + Disallowed, + + ftl_last = Disallowed, + }; + HwVsyncState mHwVsyncState GUARDED_BY(mHwVsyncLock) = HwVsyncState::Disallowed; + + // The last state, which may be the current state, or the state prior to setting to Disallowed. + HwVsyncState mLastHwVsyncState GUARDED_BY(mHwVsyncLock) = HwVsyncState::Disabled; class PredictedVsyncTracer; using TracerPtr = std::unique_ptr; + const PhysicalDisplayId mId; + // Effectively const except in move constructor. TrackerPtr mTracker; DispatchPtr mDispatch; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 43483e475b..6020aba4fd 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1124,21 +1124,33 @@ status_t SurfaceFlinger::getDynamicDisplayInfoFromToken(const sp& displ return NO_ERROR; } -status_t SurfaceFlinger::getDisplayStats(const sp&, DisplayStatInfo* outStats) { +status_t SurfaceFlinger::getDisplayStats(const sp& displayToken, + DisplayStatInfo* outStats) { if (!outStats) { return BAD_VALUE; } - const auto& schedule = mScheduler->getVsyncSchedule(); - outStats->vsyncTime = schedule.vsyncDeadlineAfter(TimePoint::now()).ns(); - outStats->vsyncPeriod = schedule.period().ns(); + std::optional displayIdOpt; + { + Mutex::Autolock lock(mStateLock); + displayIdOpt = getPhysicalDisplayIdLocked(displayToken); + } + + if (!displayIdOpt) { + ALOGE("%s: Invalid physical display token %p", __func__, displayToken.get()); + return NAME_NOT_FOUND; + } + const auto schedule = mScheduler->getVsyncSchedule(displayIdOpt); + outStats->vsyncTime = schedule->vsyncDeadlineAfter(TimePoint::now()).ns(); + outStats->vsyncPeriod = schedule->period().ns(); return NO_ERROR; } void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request, bool force) { ATRACE_CALL(); - auto display = getDisplayDeviceLocked(request.mode.modePtr->getPhysicalDisplayId()); + const auto displayId = request.mode.modePtr->getPhysicalDisplayId(); + const auto display = getDisplayDeviceLocked(displayId); if (!display) { ALOGW("%s: display is no longer valid", __func__); return; @@ -1151,23 +1163,25 @@ void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request, force)) { case DisplayDevice::DesiredActiveModeAction::InitiateDisplayModeSwitch: // Set the render rate as setDesiredActiveMode updated it. - mScheduler->setRenderRate(display->refreshRateSelector().getActiveMode().fps); + mScheduler->setRenderRate(displayId, + display->refreshRateSelector().getActiveMode().fps); // Schedule a new frame to initiate the display mode switch. scheduleComposite(FrameHint::kNone); // Start receiving vsync samples now, so that we can detect a period // switch. - mScheduler->resyncToHardwareVsync(true, mode.modePtr->getFps()); + mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, + mode.modePtr->getFps()); + // As we called to set period, we will call to onRefreshRateChangeCompleted once // VsyncController model is locked. - mScheduler->modulateVsync(&VsyncModulator::onRefreshRateChangeInitiated); - + mScheduler->modulateVsync(displayId, &VsyncModulator::onRefreshRateChangeInitiated); updatePhaseConfiguration(mode.fps); mScheduler->setModeChangePending(true); break; case DisplayDevice::DesiredActiveModeAction::InitiateRenderRateSwitch: - mScheduler->setRenderRate(mode.fps); + mScheduler->setRenderRate(displayId, mode.fps); updatePhaseConfiguration(mode.fps); mRefreshRateStats->setRefreshRate(mode.fps); if (display->getPhysicalId() == mActiveDisplayId && emitEvent) { @@ -1283,11 +1297,14 @@ void SurfaceFlinger::clearDesiredActiveModeState(const sp& displa } void SurfaceFlinger::desiredActiveModeChangeDone(const sp& display) { - const auto displayFps = display->getDesiredActiveMode()->modeOpt->modePtr->getFps(); - const auto renderFps = display->getDesiredActiveMode()->modeOpt->fps; + const auto desiredActiveMode = display->getDesiredActiveMode(); + const auto& modeOpt = desiredActiveMode->modeOpt; + const auto displayId = modeOpt->modePtr->getPhysicalDisplayId(); + const auto displayFps = modeOpt->modePtr->getFps(); + const auto renderFps = modeOpt->fps; clearDesiredActiveModeState(display); - mScheduler->resyncToHardwareVsync(true, displayFps); - mScheduler->setRenderRate(renderFps); + mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, displayFps); + mScheduler->setRenderRate(displayId, renderFps); updatePhaseConfiguration(renderFps); } @@ -2023,21 +2040,11 @@ void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t t ATRACE_FORMAT("onComposerHalVsync%s", tracePeriod.c_str()); Mutex::Autolock lock(mStateLock); - - if (!getHwComposer().onVsync(hwcDisplayId, timestamp)) { - return; - } - - if (const auto displayId = getHwComposer().toPhysicalDisplayId(hwcDisplayId); - displayId != mActiveDisplayId) { - // For now, we don't do anything with non active display vsyncs. - return; - } - - bool periodFlushed = false; - mScheduler->addResyncSample(timestamp, vsyncPeriod, &periodFlushed); - if (periodFlushed) { - mScheduler->modulateVsync(&VsyncModulator::onRefreshRateChangeCompleted); + if (const auto displayIdOpt = getHwComposer().onVsync(hwcDisplayId, timestamp)) { + if (mScheduler->addResyncSample(*displayIdOpt, timestamp, vsyncPeriod)) { + // period flushed + mScheduler->modulateVsync(displayIdOpt, &VsyncModulator::onRefreshRateChangeCompleted); + } } } @@ -2078,16 +2085,15 @@ void SurfaceFlinger::onComposerHalVsyncIdle(hal::HWDisplayId) { mScheduler->forceNextResync(); } -void SurfaceFlinger::setVsyncEnabled(bool enabled) { - ATRACE_CALL(); +void SurfaceFlinger::setVsyncEnabled(PhysicalDisplayId id, bool enabled) { + const char* const whence = __func__; + ATRACE_FORMAT("%s (%d) for %" PRIu64, whence, enabled, id.value); // On main thread to avoid race conditions with display power state. static_cast(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) { - mHWCVsyncPendingState = enabled ? hal::Vsync::ENABLE : hal::Vsync::DISABLE; - - if (const auto display = getDefaultDisplayDeviceLocked(); - display && display->isPoweredOn()) { - setHWCVsyncEnabled(display->getPhysicalId(), mHWCVsyncPendingState); + ATRACE_FORMAT("%s (%d) for %" PRIu64 " (main thread)", whence, enabled, id.value); + if (const auto display = getDisplayDeviceLocked(id); display && display->isPoweredOn()) { + setHWCVsyncEnabled(id, enabled); } })); } @@ -2114,13 +2120,13 @@ bool SurfaceFlinger::isFencePending(const FenceTimePtr& fence, int graceTimeMs) TimePoint SurfaceFlinger::calculateExpectedPresentTime(TimePoint frameTime) const { const auto& schedule = mScheduler->getVsyncSchedule(); - const TimePoint vsyncDeadline = schedule.vsyncDeadlineAfter(frameTime); + const TimePoint vsyncDeadline = schedule->vsyncDeadlineAfter(frameTime); if (mScheduler->vsyncModulator().getVsyncConfig().sfOffset > 0) { return vsyncDeadline; } // Inflate the expected present time if we're targeting the next vsync. - return vsyncDeadline + schedule.period(); + return vsyncDeadline + schedule->period(); } void SurfaceFlinger::configure() FTL_FAKE_GUARD(kMainThreadContext) { @@ -2147,7 +2153,7 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe ticks(mExpectedPresentTime - TimePoint::now()), mExpectedPresentTime == expectedVsyncTime ? "" : " (adjusted)"); - const Period vsyncPeriod = mScheduler->getVsyncSchedule().period(); + const Period vsyncPeriod = mScheduler->getVsyncSchedule()->period(); const FenceTimePtr& previousPresentFence = getPreviousPresentFence(frameTime, vsyncPeriod); // When backpressure propagation is enabled, we want to give a small grace period of 1ms @@ -2417,7 +2423,7 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) refreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::milliseconds(mDebugFlashDelay); } - const auto prevVsyncTime = mExpectedPresentTime - mScheduler->getVsyncSchedule().period(); + const auto prevVsyncTime = mExpectedPresentTime - mScheduler->getVsyncSchedule()->period(); const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration; refreshArgs.earliestPresentTime = prevVsyncTime - hwcMinWorkDuration; @@ -2503,7 +2509,7 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) // TODO(b/160583065): Enable skip validation when SF caches all client composition layers. const bool hasGpuUseOrReuse = mCompositionCoverage.any(CompositionCoverage::Gpu | CompositionCoverage::GpuReuse); - mScheduler->modulateVsync(&VsyncModulator::onDisplayRefresh, hasGpuUseOrReuse); + mScheduler->modulateVsync({}, &VsyncModulator::onDisplayRefresh, hasGpuUseOrReuse); mLayersWithQueuedFrames.clear(); if (mLayerTracingEnabled && mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) { @@ -2645,9 +2651,9 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { ? mPresentLatencyTracker.trackPendingFrame(compositeTime, presentFenceTime) : Duration::zero(); - const auto& schedule = mScheduler->getVsyncSchedule(); - const TimePoint vsyncDeadline = schedule.vsyncDeadlineAfter(presentTime); - const Period vsyncPeriod = schedule.period(); + const auto schedule = mScheduler->getVsyncSchedule(); + const TimePoint vsyncDeadline = schedule->vsyncDeadlineAfter(presentTime); + const Period vsyncPeriod = schedule->period(); const nsecs_t vsyncPhase = mVsyncConfiguration->getCurrentConfigs().late.sfOffset; const CompositorTiming compositorTiming(vsyncDeadline.ns(), vsyncPeriod.ns(), vsyncPhase, @@ -2722,15 +2728,19 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { mTimeStats->incrementTotalFrames(); mTimeStats->setPresentFenceGlobal(presentFenceTime); - const bool isInternalDisplay = display && - FTL_FAKE_GUARD(mStateLock, mPhysicalDisplays) - .get(display->getPhysicalId()) - .transform(&PhysicalDisplay::isInternal) - .value_or(false); - - if (isInternalDisplay && display && display->getPowerMode() == hal::PowerMode::ON && - presentFenceTime->isValid()) { - mScheduler->addPresentFence(std::move(presentFenceTime)); + { + ftl::FakeGuard guard(mStateLock); + for (const auto& [id, physicalDisplay] : mPhysicalDisplays) { + if (auto displayDevice = getDisplayDeviceLocked(id); + displayDevice && displayDevice->isPoweredOn() && physicalDisplay.isInternal()) { + auto presentFenceTimeI = display && display->getPhysicalId() == id + ? std::move(presentFenceTime) + : std::make_shared(getHwComposer().getPresentFence(id)); + if (presentFenceTimeI->isValid()) { + mScheduler->addPresentFence(id, std::move(presentFenceTimeI)); + } + } + } } const bool isDisplayConnected = @@ -2738,7 +2748,7 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { if (!hasSyncFramework) { if (isDisplayConnected && display->isPoweredOn()) { - mScheduler->enableHardwareVsync(); + mScheduler->enableHardwareVsync(display->getPhysicalId()); } } @@ -2848,7 +2858,7 @@ void SurfaceFlinger::commitTransactions() { // so we can call commitTransactionsLocked unconditionally. // We clear the flags with mStateLock held to guarantee that // mCurrentState won't change until the transaction is committed. - mScheduler->modulateVsync(&VsyncModulator::onTransactionCommit); + mScheduler->modulateVsync({}, &VsyncModulator::onTransactionCommit); commitTransactionsLocked(clearTransactionFlags(eTransactionMask)); mDebugInTransaction = 0; @@ -3687,10 +3697,9 @@ void SurfaceFlinger::initScheduler(const sp& display) { mScheduler = std::make_unique(static_cast(*this), static_cast(*this), features, std::move(modulatorPtr)); - mScheduler->createVsyncSchedule(features); mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector()); - setVsyncEnabled(false); + setVsyncEnabled(display->getPhysicalId(), false); mScheduler->startTimers(); const auto configs = mVsyncConfiguration->getCurrentConfigs(); @@ -3706,7 +3715,7 @@ void SurfaceFlinger::initScheduler(const sp& display) { /* workDuration */ activeRefreshRate.getPeriod(), /* readyDuration */ configs.late.sfWorkDuration); - mScheduler->initVsync(mScheduler->getVsyncSchedule().getDispatch(), + mScheduler->initVsync(mScheduler->getVsyncSchedule()->getDispatch(), *mFrameTimeline->getTokenManager(), configs.late.sfWorkDuration); mRegionSamplingThread = @@ -3917,7 +3926,7 @@ uint32_t SurfaceFlinger::clearTransactionFlags(uint32_t mask) { void SurfaceFlinger::setTransactionFlags(uint32_t mask, TransactionSchedule schedule, const sp& applyToken, FrameHint frameHint) { - mScheduler->modulateVsync(&VsyncModulator::setTransactionSchedule, schedule, applyToken); + mScheduler->modulateVsync({}, &VsyncModulator::setTransactionSchedule, schedule, applyToken); if (const bool scheduled = mTransactionFlags.fetch_or(mask) & mask; !scheduled) { scheduleCommit(frameHint); @@ -3944,7 +3953,7 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyTimelin return TransactionReadiness::NotReady; } - if (!mScheduler->isVsyncValid(mExpectedPresentTime, transaction.originUid)) { + if (!mScheduler->isVsyncTargetForUid(mExpectedPresentTime, transaction.originUid)) { ATRACE_NAME("!isVsyncValid"); return TransactionReadiness::NotReady; } @@ -4088,7 +4097,7 @@ bool SurfaceFlinger::frameIsEarly(TimePoint expectedPresentTime, VsyncId vsyncId return false; } - const Duration earlyLatchVsyncThreshold = mScheduler->getVsyncSchedule().period() / 2; + const Duration earlyLatchVsyncThreshold = mScheduler->getVsyncSchedule()->period() / 2; return predictedPresentTime >= expectedPresentTime && predictedPresentTime - expectedPresentTime >= earlyLatchVsyncThreshold; @@ -4985,10 +4994,11 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: ALOGW("Couldn't set SCHED_FIFO on display on: %s\n", strerror(errno)); } getHwComposer().setPowerMode(displayId, mode); - if (isActiveDisplay && mode != hal::PowerMode::DOZE_SUSPEND) { - setHWCVsyncEnabled(displayId, mHWCVsyncPendingState); - mScheduler->onScreenAcquired(mAppConnectionHandle); - mScheduler->resyncToHardwareVsync(true, refreshRate); + if (mode != hal::PowerMode::DOZE_SUSPEND) { + if (isActiveDisplay) { + mScheduler->onScreenAcquired(mAppConnectionHandle); + } + mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, refreshRate); } mVisibleRegionsDirty = true; @@ -5001,33 +5011,34 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: if (SurfaceFlinger::setSchedAttr(false) != NO_ERROR) { ALOGW("Couldn't set uclamp.min on display off: %s\n", strerror(errno)); } - if (isActiveDisplay && *currentModeOpt != hal::PowerMode::DOZE_SUSPEND) { - mScheduler->disableHardwareVsync(true); - mScheduler->onScreenReleased(mAppConnectionHandle); + if (*currentModeOpt != hal::PowerMode::DOZE_SUSPEND) { + mScheduler->disableHardwareVsync(displayId, true); + if (isActiveDisplay) { + mScheduler->onScreenReleased(mAppConnectionHandle); + } } - // Make sure HWVsync is disabled before turning off the display - setHWCVsyncEnabled(displayId, hal::Vsync::DISABLE); - getHwComposer().setPowerMode(displayId, mode); mVisibleRegionsDirty = true; // from this point on, SF will stop drawing on this display } else if (mode == hal::PowerMode::DOZE || mode == hal::PowerMode::ON) { // Update display while dozing getHwComposer().setPowerMode(displayId, mode); - if (isActiveDisplay && *currentModeOpt == hal::PowerMode::DOZE_SUSPEND) { + if (*currentModeOpt == hal::PowerMode::DOZE_SUSPEND) { + if (isActiveDisplay) { + mScheduler->onScreenAcquired(mAppConnectionHandle); + } ALOGI("Force repainting for DOZE_SUSPEND -> DOZE or ON."); mVisibleRegionsDirty = true; scheduleRepaint(); - mScheduler->onScreenAcquired(mAppConnectionHandle); - mScheduler->resyncToHardwareVsync(true, refreshRate); + mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, refreshRate); } } else if (mode == hal::PowerMode::DOZE_SUSPEND) { // Leave display going to doze if (isActiveDisplay) { - mScheduler->disableHardwareVsync(true); mScheduler->onScreenReleased(mAppConnectionHandle); } + mScheduler->disableHardwareVsync(displayId, true); getHwComposer().setPowerMode(displayId, mode); } else { ALOGE("Attempting to set unknown power mode: %d\n", mode); @@ -5037,8 +5048,8 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: if (isActiveDisplay) { mTimeStats->setPowerMode(mode); mRefreshRateStats->setPowerMode(mode); - mScheduler->setDisplayPowerMode(mode); } + mScheduler->setDisplayPowerMode(displayId, mode); ALOGD("Finished setting power mode %d on display %s", mode, to_string(displayId).c_str()); } @@ -5216,14 +5227,6 @@ void SurfaceFlinger::dumpScheduler(std::string& result) const { mScheduler->dump(dumper); - // TODO(b/241286146): Move to Scheduler. - { - utils::Dumper::Indent indent(dumper); - dumper.dump("lastHwcVsyncState"sv, mLastHWCVsyncState); - dumper.dump("pendingHwcVsyncState"sv, mHWCVsyncPendingState); - } - dumper.eol(); - // TODO(b/241285876): Move to DisplayModeController. dumper.dump("debugDisplayModeSetByBackdoor"sv, mDebugDisplayModeSetByBackdoor); dumper.eol(); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 5b9bfd80f9..1eb1fdaa48 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -74,6 +74,7 @@ #include "FrontEnd/LayerSnapshot.h" #include "FrontEnd/TransactionHandler.h" #include "LayerVector.h" +#include "Scheduler/ISchedulerCallback.h" #include "Scheduler/RefreshRateSelector.h" #include "Scheduler/RefreshRateStats.h" #include "Scheduler/Scheduler.h" @@ -618,7 +619,7 @@ private: // Toggles hardware VSYNC by calling into HWC. // TODO(b/241286146): Rename for self-explanatory API. - void setVsyncEnabled(bool) override; + void setVsyncEnabled(PhysicalDisplayId, bool) override; void requestDisplayModes(std::vector) override; void kernelTimerChanged(bool expired) override; void triggerOnFrameRateOverridesChanged() override; @@ -953,9 +954,9 @@ private: */ nsecs_t getVsyncPeriodFromHWC() const REQUIRES(mStateLock); - void setHWCVsyncEnabled(PhysicalDisplayId id, hal::Vsync enabled) { - mLastHWCVsyncState = enabled; - getHwComposer().setVsyncEnabled(id, enabled); + void setHWCVsyncEnabled(PhysicalDisplayId id, bool enabled) { + hal::Vsync halState = enabled ? hal::Vsync::ENABLE : hal::Vsync::DISABLE; + getHwComposer().setVsyncEnabled(id, halState); } using FenceTimePtr = std::shared_ptr; @@ -1090,7 +1091,15 @@ private: pid_t mPid; std::future mRenderEnginePrimeCacheFuture; - // access must be protected by mStateLock + // mStateLock has conventions related to the current thread, because only + // the main thread should modify variables protected by mStateLock. + // - read access from a non-main thread must lock mStateLock, since the main + // thread may modify these variables. + // - write access from a non-main thread is not permitted. + // - read access from the main thread can use an ftl::FakeGuard, since other + // threads must not modify these variables. + // - write access from the main thread must lock mStateLock, since another + // thread may be reading these variables. mutable Mutex mStateLock; State mCurrentState{LayerVector::StateSet::Current}; std::atomic mTransactionFlags = 0; @@ -1281,9 +1290,6 @@ private: TimePoint mScheduledPresentTime GUARDED_BY(kMainThreadContext); TimePoint mExpectedPresentTime GUARDED_BY(kMainThreadContext); - hal::Vsync mHWCVsyncPendingState = hal::Vsync::DISABLE; - hal::Vsync mLastHWCVsyncState = hal::Vsync::DISABLE; - // below flags are set by main thread only bool mSetActiveModePending = false; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index cdffbb4724..5303db314c 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -226,27 +226,27 @@ public: TestableScheduler(const std::shared_ptr& selectorPtr, sp modulatorPtr, ISchedulerCallback& callback) : TestableScheduler(std::make_unique(), - std::make_unique(), selectorPtr, + std::make_shared(), selectorPtr, std::move(modulatorPtr), callback) {} TestableScheduler(std::unique_ptr controller, - std::unique_ptr tracker, + VsyncSchedule::TrackerPtr tracker, std::shared_ptr selectorPtr, sp modulatorPtr, ISchedulerCallback& callback) : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) { - mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller))); - const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId(); registerDisplay(displayId, std::move(selectorPtr)); + mVsyncSchedules.emplace_or_replace(displayId, + std::shared_ptr( + new VsyncSchedule(displayId, std::move(tracker), + nullptr, + std::move(controller)))); } ConnectionHandle createConnection(std::unique_ptr eventThread) { return Scheduler::createConnection(std::move(eventThread)); } - auto &mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; } - auto &mutableHWVsyncAvailable() { return mHWVsyncAvailable; } - auto &mutableLayerHistory() { return mLayerHistory; } auto refreshRateSelector() { return leaderSelectorPtr(); } @@ -649,10 +649,10 @@ public: // The ISchedulerCallback argument can be nullptr for a no-op implementation. void setupScheduler(std::unique_ptr vsyncController, - std::unique_ptr vsyncTracker, + std::shared_ptr vsyncTracker, std::unique_ptr appEventThread, std::unique_ptr sfEventThread, - scheduler::ISchedulerCallback *callback = nullptr, + scheduler::ISchedulerCallback* callback = nullptr, bool hasMultipleModes = false) { constexpr DisplayModeId kModeId60{0}; DisplayModes modes = makeModes(mock::createDisplayMode(kModeId60, 60_Hz)); @@ -791,7 +791,7 @@ public: } private: - void setVsyncEnabled(bool) override {} + void setVsyncEnabled(PhysicalDisplayId, bool) override {} void requestDisplayModes(std::vector) override {} void kernelTimerChanged(bool) override {} void triggerOnFrameRateOverridesChanged() override {} diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index 44805dba82..b7b42ab18d 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -47,19 +47,23 @@ constexpr PowerMode kPowerModes[] = {PowerMode::ON, PowerMode::DOZE, PowerMode:: PowerMode::DOZE_SUSPEND, PowerMode::ON_SUSPEND}; constexpr uint16_t kRandomStringLength = 256; -constexpr std::chrono::duration kSyncPeriod(16ms); - template void dump(T* component, FuzzedDataProvider* fdp) { std::string res = fdp->ConsumeRandomLengthString(kRandomStringLength); component->dump(res); } -class SchedulerFuzzer { +class SchedulerFuzzer : public IEventThreadCallback { public: SchedulerFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; void process(); + // IEventThreadCallback overrides. + bool isVsyncTargetForUid(TimePoint /* expectedVsyncTime */, uid_t) const override { + return true; + } + Fps getLeaderRenderFrameRate(uid_t) const override { return 60_Hz; } + private: void fuzzRefreshRateSelection(); void fuzzRefreshRateSelector(); @@ -76,7 +80,7 @@ private: FuzzedDataProvider mFdp; - std::optional mVsyncSchedule; + std::shared_ptr mVsyncSchedule; }; PhysicalDisplayId SchedulerFuzzer::getPhysicalDisplayId() { @@ -90,12 +94,12 @@ PhysicalDisplayId SchedulerFuzzer::getPhysicalDisplayId() { } void SchedulerFuzzer::fuzzEventThread() { - mVsyncSchedule.emplace(scheduler::VsyncSchedule(std::make_unique(), - std::make_unique(), - nullptr)); - const auto getVsyncPeriod = [](uid_t /* uid */) { return kSyncPeriod.count(); }; + mVsyncSchedule = std::shared_ptr( + new scheduler::VsyncSchedule(getPhysicalDisplayId(), + std::make_shared(), + std::make_shared(), nullptr)); std::unique_ptr thread = std::make_unique< - android::impl::EventThread>("fuzzer", *mVsyncSchedule, nullptr, nullptr, getVsyncPeriod, + android::impl::EventThread>("fuzzer", mVsyncSchedule, *this, nullptr /* TokenManager */, (std::chrono::nanoseconds)mFdp.ConsumeIntegral(), (std::chrono::nanoseconds)mFdp.ConsumeIntegral()); @@ -132,7 +136,7 @@ void SchedulerFuzzer::fuzzCallbackToken(scheduler::VSyncDispatchTimerQueue* disp } void SchedulerFuzzer::fuzzVSyncDispatchTimerQueue() { - FuzzImplVSyncTracker stubTracker{mFdp.ConsumeIntegral()}; + auto stubTracker = std::make_shared(mFdp.ConsumeIntegral()); scheduler::VSyncDispatchTimerQueue mDispatch{std::make_unique(), stubTracker, mFdp.ConsumeIntegral() /*dispatchGroupThreshold*/, @@ -145,17 +149,17 @@ void SchedulerFuzzer::fuzzVSyncDispatchTimerQueue() { scheduler::VSyncDispatchTimerQueueEntry entry( "fuzz", [](auto, auto, auto) {}, mFdp.ConsumeIntegral() /*vSyncMoveThreshold*/); - entry.update(stubTracker, 0); + entry.update(*stubTracker, 0); entry.schedule({.workDuration = mFdp.ConsumeIntegral(), .readyDuration = mFdp.ConsumeIntegral(), .earliestVsync = mFdp.ConsumeIntegral()}, - stubTracker, 0); + *stubTracker, 0); entry.disarm(); entry.ensureNotRunning(); entry.schedule({.workDuration = mFdp.ConsumeIntegral(), .readyDuration = mFdp.ConsumeIntegral(), .earliestVsync = mFdp.ConsumeIntegral()}, - stubTracker, 0); + *stubTracker, 0); auto const wakeup = entry.wakeupTime(); auto const ready = entry.readyTime(); entry.callback(entry.executing(), *wakeup, *ready); @@ -169,8 +173,8 @@ void SchedulerFuzzer::fuzzVSyncPredictor() { uint16_t now = mFdp.ConsumeIntegral(); uint16_t historySize = mFdp.ConsumeIntegralInRange(1, UINT16_MAX); uint16_t minimumSamplesForPrediction = mFdp.ConsumeIntegralInRange(1, UINT16_MAX); - scheduler::VSyncPredictor tracker{mFdp.ConsumeIntegral() /*period*/, historySize, - minimumSamplesForPrediction, + scheduler::VSyncPredictor tracker{"predictor", mFdp.ConsumeIntegral() /*period*/, + historySize, minimumSamplesForPrediction, mFdp.ConsumeIntegral() /*outlierTolerancePercent*/}; uint16_t period = mFdp.ConsumeIntegral(); tracker.setPeriod(period); @@ -242,13 +246,15 @@ void SchedulerFuzzer::fuzzLayerHistory() { void SchedulerFuzzer::fuzzVSyncReactor() { std::shared_ptr vSyncTracker = std::make_shared(); - scheduler::VSyncReactor reactor(std::make_unique( + scheduler::VSyncReactor reactor("fuzzer_reactor", + std::make_unique( std::make_shared()), *vSyncTracker, mFdp.ConsumeIntegral() /*pendingLimit*/, false); - reactor.startPeriodTransition(mFdp.ConsumeIntegral()); - bool periodFlushed = mFdp.ConsumeBool(); + reactor.startPeriodTransition(mFdp.ConsumeIntegral(), mFdp.ConsumeBool()); + bool periodFlushed = false; // Value does not matter, since this is an out + // param from addHwVsyncTimestamp. reactor.addHwVsyncTimestamp(0, std::nullopt, &periodFlushed); reactor.addHwVsyncTimestamp(mFdp.ConsumeIntegral() /*newPeriod*/, std::nullopt, &periodFlushed); diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 0416e93f1e..419c818e0e 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -139,7 +139,7 @@ public: ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); + auto vsyncTracker = std::make_shared(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp index e0b508aa73..214b02888f 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp @@ -74,8 +74,8 @@ void DisplayTransactionTest::injectMockScheduler() { mock::EventThread::kCallingUid, ResyncCallback()))); - mFlinger.setupScheduler(std::unique_ptr(mVsyncController), - std::unique_ptr(mVSyncTracker), + mFlinger.setupScheduler(std::make_unique(), + std::make_shared(), std::unique_ptr(mEventThread), std::unique_ptr(mSFEventThread), TestableSurfaceFlinger::SchedulerCallbackImpl::kMock); diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h index 223f4db889..c9245d6d97 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h @@ -128,8 +128,6 @@ public: renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine(); Hwc2::mock::Composer* mComposer = nullptr; - mock::VsyncController* mVsyncController = new mock::VsyncController; - mock::VSyncTracker* mVSyncTracker = new mock::VSyncTracker; mock::EventThread* mEventThread = new mock::EventThread; mock::EventThread* mSFEventThread = new mock::EventThread; diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp index b3aba377ee..5cecb8eb19 100644 --- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp +++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp @@ -52,11 +52,9 @@ constexpr PhysicalDisplayId EXTERNAL_DISPLAY_ID = PhysicalDisplayId::fromPort(22 constexpr PhysicalDisplayId DISPLAY_ID_64BIT = PhysicalDisplayId::fromEdid(0xffu, 0xffffu, 0xffff'ffffu); -constexpr std::chrono::duration VSYNC_PERIOD(16ms); - } // namespace -class EventThreadTest : public testing::Test { +class EventThreadTest : public testing::Test, public IEventThreadCallback { protected: static constexpr std::chrono::nanoseconds kWorkDuration = 0ms; static constexpr std::chrono::nanoseconds kReadyDuration = 3ms; @@ -97,7 +95,7 @@ protected: void expectConfigChangedEventReceivedByConnection(PhysicalDisplayId expectedDisplayId, int32_t expectedConfigId, nsecs_t expectedVsyncPeriod); - void expectThrottleVsyncReceived(nsecs_t expectedTimestamp, uid_t); + void expectThrottleVsyncReceived(TimePoint expectedTimestamp, uid_t); void expectUidFrameRateMappingEventReceivedByConnection(PhysicalDisplayId expectedDisplayId, std::vector); @@ -106,6 +104,14 @@ protected: mThread->onVsync(expectedPresentationTime, timestamp, deadlineTimestamp); } + // IEventThreadCallback overrides. + bool isVsyncTargetForUid(TimePoint expectedVsyncTime, uid_t uid) const override { + mThrottleVsyncCallRecorder.getInvocable()(expectedVsyncTime, uid); + return uid != mThrottledConnectionUid; + } + + Fps getLeaderRenderFrameRate(uid_t uid) const override { return 60_Hz; } + AsyncCallRecorderWithCannedReturn< scheduler::ScheduleResult (*)(scheduler::VSyncDispatch::CallbackToken, scheduler::VSyncDispatch::ScheduleTiming)> @@ -121,11 +127,11 @@ protected: AsyncCallRecorder mVSyncCallbackUnregisterRecorder; AsyncCallRecorder mResyncCallRecorder; - AsyncCallRecorder mThrottleVsyncCallRecorder; + mutable AsyncCallRecorder mThrottleVsyncCallRecorder; ConnectionEventRecorder mConnectionEventCallRecorder{0}; ConnectionEventRecorder mThrottledConnectionEventCallRecorder{0}; - std::optional mVsyncSchedule; + std::shared_ptr mVsyncSchedule; std::unique_ptr mThread; sp mConnection; sp mThrottledConnection; @@ -140,12 +146,12 @@ EventThreadTest::EventThreadTest() { ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - mVsyncSchedule.emplace(scheduler::VsyncSchedule(std::make_unique(), - std::make_unique(), - nullptr)); - - mock::VSyncDispatch& mockDispatch = - *static_cast(&mVsyncSchedule->getDispatch()); + auto mockDispatchPtr = std::make_shared(); + mVsyncSchedule = std::shared_ptr( + new scheduler::VsyncSchedule(INTERNAL_DISPLAY_ID, + std::make_shared(), mockDispatchPtr, + nullptr)); + mock::VSyncDispatch& mockDispatch = *mockDispatchPtr; EXPECT_CALL(mockDispatch, registerCallback(_, _)) .WillRepeatedly(Invoke(mVSyncCallbackRegisterRecorder.getInvocable())); EXPECT_CALL(mockDispatch, schedule(_, _)) @@ -180,19 +186,10 @@ EventThreadTest::~EventThreadTest() { } void EventThreadTest::createThread() { - const auto throttleVsync = [&](nsecs_t expectedVsyncTimestamp, uid_t uid) { - mThrottleVsyncCallRecorder.getInvocable()(expectedVsyncTimestamp, uid); - return (uid == mThrottledConnectionUid); - }; - const auto getVsyncPeriod = [](uid_t uid) { - return VSYNC_PERIOD.count(); - }; - mTokenManager = std::make_unique(); mThread = - std::make_unique(/*std::move(source), */ "EventThreadTest", - *mVsyncSchedule, mTokenManager.get(), throttleVsync, - getVsyncPeriod, kWorkDuration, kReadyDuration); + std::make_unique("EventThreadTest", mVsyncSchedule, *this, + mTokenManager.get(), kWorkDuration, kReadyDuration); // EventThread should register itself as VSyncSource callback. EXPECT_TRUE(mVSyncCallbackRegisterRecorder.waitForCall().has_value()); @@ -225,7 +222,7 @@ void EventThreadTest::expectVSyncSetDurationCallReceived( EXPECT_EQ(expectedReadyDuration.count(), std::get<1>(args.value()).readyDuration); } -void EventThreadTest::expectThrottleVsyncReceived(nsecs_t expectedTimestamp, uid_t uid) { +void EventThreadTest::expectThrottleVsyncReceived(TimePoint expectedTimestamp, uid_t uid) { auto args = mThrottleVsyncCallRecorder.waitForCall(); ASSERT_TRUE(args.has_value()); EXPECT_EQ(expectedTimestamp, std::get<0>(args.value())); @@ -376,7 +373,7 @@ TEST_F(EventThreadTest, requestNextVsyncPostsASingleVSyncEventToTheConnection) { // Use the received callback to signal a first vsync event. // The throttler should receive the event, as well as the connection. onVSyncEvent(123, 456, 789); - expectThrottleVsyncReceived(456, mConnectionUid); + expectThrottleVsyncReceived(TimePoint::fromNs(456), mConnectionUid); expectVsyncEventReceivedByConnection(123, 1u); // EventThread is requesting one more callback due to VsyncRequest::SingleSuppressCallback @@ -494,17 +491,17 @@ TEST_F(EventThreadTest, setVsyncRateOnePostsAllEventsToThatConnection) { // Send a vsync event. EventThread should then make a call to the // throttler, and the connection. onVSyncEvent(123, 456, 789); - expectThrottleVsyncReceived(456, mConnectionUid); + expectThrottleVsyncReceived(TimePoint::fromNs(456), mConnectionUid); expectVsyncEventReceivedByConnection(123, 1u); // A second event should go to the same places. onVSyncEvent(456, 123, 0); - expectThrottleVsyncReceived(123, mConnectionUid); + expectThrottleVsyncReceived(TimePoint::fromNs(123), mConnectionUid); expectVsyncEventReceivedByConnection(456, 2u); // A third event should go to the same places. onVSyncEvent(789, 777, 111); - expectThrottleVsyncReceived(777, mConnectionUid); + expectThrottleVsyncReceived(TimePoint::fromNs(777), mConnectionUid); expectVsyncEventReceivedByConnection(789, 3u); } @@ -743,7 +740,7 @@ TEST_F(EventThreadTest, requestNextVsyncWithThrottleVsyncDoesntPostVSync) { // Use the received callback to signal a first vsync event. // The throttler should receive the event, but not the connection. onVSyncEvent(123, 456, 789); - expectThrottleVsyncReceived(456, mThrottledConnectionUid); + expectThrottleVsyncReceived(TimePoint::fromNs(456), mThrottledConnectionUid); mThrottledConnectionEventCallRecorder.waitForUnexpectedCall(); expectVSyncCallbackScheduleReceived(true); @@ -751,7 +748,7 @@ TEST_F(EventThreadTest, requestNextVsyncWithThrottleVsyncDoesntPostVSync) { // The throttler should receive the event, but the connection should // not as it was only interested in the first. onVSyncEvent(456, 123, 0); - expectThrottleVsyncReceived(123, mThrottledConnectionUid); + expectThrottleVsyncReceived(TimePoint::fromNs(123), mThrottledConnectionUid); EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value()); expectVSyncCallbackScheduleReceived(true); diff --git a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp index 1cd9e49051..248061cc30 100644 --- a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp +++ b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp @@ -137,7 +137,7 @@ void FpsReporterTest::setupScheduler() { ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); + auto vsyncTracker = std::make_shared(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp index ac63a0edbd..ff7c2c98ba 100644 --- a/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp +++ b/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp @@ -125,7 +125,7 @@ void RefreshRateSelectionTest::setupScheduler() { ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); + auto vsyncTracker = std::make_shared(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/GameModeTest.cpp b/services/surfaceflinger/tests/unittests/GameModeTest.cpp index 29aa7171ba..ddf871b5ce 100644 --- a/services/surfaceflinger/tests/unittests/GameModeTest.cpp +++ b/services/surfaceflinger/tests/unittests/GameModeTest.cpp @@ -76,7 +76,7 @@ public: ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); + auto vsyncTracker = std::make_shared(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp index afbc57add8..9534f3b548 100644 --- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp +++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp @@ -118,6 +118,34 @@ TEST_F(HWComposerTest, getActiveMode) { } } +TEST_F(HWComposerTest, onVsync) { + constexpr hal::HWDisplayId kHwcDisplayId = 1; + expectHotplugConnect(kHwcDisplayId); + + const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED); + ASSERT_TRUE(info); + + const auto physicalDisplayId = info->id; + + // Deliberately chosen not to match DisplayData.lastPresentTimestamp's + // initial value. + constexpr nsecs_t kTimestamp = 1; + auto displayIdOpt = mHwc.onVsync(kHwcDisplayId, kTimestamp); + ASSERT_TRUE(displayIdOpt); + EXPECT_EQ(physicalDisplayId, displayIdOpt); + + // Attempt to send the same time stamp again. + displayIdOpt = mHwc.onVsync(kHwcDisplayId, kTimestamp); + EXPECT_FALSE(displayIdOpt); +} + +TEST_F(HWComposerTest, onVsyncInvalid) { + constexpr hal::HWDisplayId kInvalidHwcDisplayId = 2; + constexpr nsecs_t kTimestamp = 1; + const auto displayIdOpt = mHwc.onVsync(kInvalidHwcDisplayId, kTimestamp); + EXPECT_FALSE(displayIdOpt); +} + struct MockHWC2ComposerCallback final : StrictMock { MOCK_METHOD2(onComposerHalHotplug, void(hal::HWDisplayId, hal::Connection)); MOCK_METHOD1(onComposerHalRefresh, void(hal::HWDisplayId)); diff --git a/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp b/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp index ee42e19c34..23506b13e9 100644 --- a/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp +++ b/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp @@ -64,7 +64,7 @@ void BaseLayerTest::setupScheduler() { ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); + auto vsyncTracker = std::make_shared(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp index 7aa5201f2f..8f1b450b06 100644 --- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp +++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp @@ -67,12 +67,12 @@ struct MockTokenManager : frametimeline::TokenManager { struct MessageQueueTest : testing::Test { void SetUp() override { - EXPECT_CALL(mVSyncDispatch, registerCallback(_, "sf")).WillOnce(Return(mCallbackToken)); + EXPECT_CALL(*mVSyncDispatch, registerCallback(_, "sf")).WillOnce(Return(mCallbackToken)); EXPECT_NO_FATAL_FAILURE(mEventQueue.initVsync(mVSyncDispatch, mTokenManager, kDuration)); - EXPECT_CALL(mVSyncDispatch, unregisterCallback(mCallbackToken)).Times(1); + EXPECT_CALL(*mVSyncDispatch, unregisterCallback(mCallbackToken)).Times(1); } - mock::VSyncDispatch mVSyncDispatch; + std::shared_ptr mVSyncDispatch = std::make_shared(); MockTokenManager mTokenManager; TestableMessageQueue mEventQueue; @@ -90,7 +90,7 @@ TEST_F(MessageQueueTest, commit) { .earliestVsync = 0}; EXPECT_FALSE(mEventQueue.getScheduledFrameTime()); - EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); + EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); ASSERT_TRUE(mEventQueue.getScheduledFrameTime()); @@ -103,13 +103,13 @@ TEST_F(MessageQueueTest, commitTwice) { .readyDuration = 0, .earliestVsync = 0}; - EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); + EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); ASSERT_TRUE(mEventQueue.getScheduledFrameTime()); EXPECT_EQ(1234, mEventQueue.getScheduledFrameTime()->time_since_epoch().count()); - EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(4567)); + EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(4567)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); ASSERT_TRUE(mEventQueue.getScheduledFrameTime()); @@ -122,7 +122,7 @@ TEST_F(MessageQueueTest, commitTwiceWithCallback) { .readyDuration = 0, .earliestVsync = 0}; - EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); + EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); ASSERT_TRUE(mEventQueue.getScheduledFrameTime()); @@ -149,7 +149,7 @@ TEST_F(MessageQueueTest, commitTwiceWithCallback) { .readyDuration = 0, .earliestVsync = kPresentTime.ns()}; - EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timingAfterCallback)).WillOnce(Return(0)); + EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timingAfterCallback)).WillOnce(Return(0)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); } @@ -161,7 +161,7 @@ TEST_F(MessageQueueTest, commitWithDurationChange) { .readyDuration = 0, .earliestVsync = 0}; - EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(0)); + EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(0)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); } diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 4b15385fa8..f0dd06d758 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include #include #include @@ -176,7 +177,7 @@ TEST_F(SchedulerTest, chooseRefreshRateForContentIsNoopWhenModeSwitchingIsNotSup ASSERT_EQ(0u, mScheduler->getNumActiveLayers()); constexpr hal::PowerMode kPowerModeOn = hal::PowerMode::ON; - mScheduler->setDisplayPowerMode(kPowerModeOn); + FTL_FAKE_GUARD(kMainThreadContext, mScheduler->setDisplayPowerMode(kDisplayId1, kPowerModeOn)); constexpr uint32_t kDisplayArea = 999'999; mScheduler->onActiveDisplayAreaChanged(kDisplayArea); @@ -248,7 +249,7 @@ TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) { mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer); constexpr hal::PowerMode kPowerModeOn = hal::PowerMode::ON; - mScheduler->setDisplayPowerMode(kPowerModeOn); + FTL_FAKE_GUARD(kMainThreadContext, mScheduler->setDisplayPowerMode(kDisplayId1, kPowerModeOn)); constexpr uint32_t kDisplayArea = 999'999; mScheduler->onActiveDisplayAreaChanged(kDisplayArea); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp index ad3bd353ed..31f948fd68 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp @@ -100,7 +100,7 @@ void DisplayModeSwitchingTest::setupScheduler( ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); + auto vsyncTracker = std::make_shared(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp index f553a23f3c..98644aa89d 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp @@ -44,7 +44,10 @@ TEST_F(OnInitializeDisplaysTest, onInitializeDisplaysSetsUpPrimaryDisplay) { // We expect a scheduled commit for the display transaction. EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1); - EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); + EXPECT_CALL(static_cast( + mFlinger.scheduler()->getVsyncSchedule()->getTracker()), + nextAnticipatedVSyncTimeFrom(_)) + .WillRepeatedly(Return(0)); // -------------------------------------------------------------------- // Invocation diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp index 622717f290..2a0f2efb75 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp @@ -116,7 +116,7 @@ void SurfaceFlingerPowerHintTest::setupScheduler() { ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); + auto vsyncTracker = std::make_shared(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp index ab732ed485..80ad22cebe 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp @@ -61,7 +61,7 @@ struct DozeNotSupportedVariant { struct EventThreadBaseSupportedVariant { static void setupVsyncAndEventThreadNoCallExpectations(DisplayTransactionTest* test) { // The callback should not be notified to toggle VSYNC. - EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_)).Times(0); + EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_, _)).Times(0); // The event thread should not be notified. EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(0); @@ -71,24 +71,28 @@ struct EventThreadBaseSupportedVariant { struct EventThreadNotSupportedVariant : public EventThreadBaseSupportedVariant { static void setupAcquireAndEnableVsyncCallExpectations(DisplayTransactionTest* test) { - // These calls are only expected for the primary display. + // The callback should be notified to enable VSYNC. + EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_, true)).Times(1); - // Instead expect no calls. - setupVsyncAndEventThreadNoCallExpectations(test); + // The event thread should not be notified. + EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(0); + EXPECT_CALL(*test->mEventThread, onScreenAcquired()).Times(0); } static void setupReleaseAndDisableVsyncCallExpectations(DisplayTransactionTest* test) { - // These calls are only expected for the primary display. + // The callback should be notified to disable VSYNC. + EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_, false)).Times(1); - // Instead expect no calls. - setupVsyncAndEventThreadNoCallExpectations(test); + // The event thread should not be notified. + EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(0); + EXPECT_CALL(*test->mEventThread, onScreenAcquired()).Times(0); } }; struct EventThreadIsSupportedVariant : public EventThreadBaseSupportedVariant { static void setupAcquireAndEnableVsyncCallExpectations(DisplayTransactionTest* test) { // The callback should be notified to enable VSYNC. - EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(true)).Times(1); + EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_, true)).Times(1); // The event thread should be notified that the screen was acquired. EXPECT_CALL(*test->mEventThread, onScreenAcquired()).Times(1); @@ -96,7 +100,7 @@ struct EventThreadIsSupportedVariant : public EventThreadBaseSupportedVariant { static void setupReleaseAndDisableVsyncCallExpectations(DisplayTransactionTest* test) { // The callback should be notified to disable VSYNC. - EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(false)).Times(1); + EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_, false)).Times(1); // The event thread should not be notified that the screen was released. EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(1); @@ -105,8 +109,12 @@ struct EventThreadIsSupportedVariant : public EventThreadBaseSupportedVariant { struct DispSyncIsSupportedVariant { static void setupResetModelCallExpectations(DisplayTransactionTest* test) { - EXPECT_CALL(*test->mVsyncController, startPeriodTransition(DEFAULT_VSYNC_PERIOD)).Times(1); - EXPECT_CALL(*test->mVSyncTracker, resetModel()).Times(1); + auto vsyncSchedule = test->mFlinger.scheduler()->getVsyncSchedule(); + EXPECT_CALL(static_cast(vsyncSchedule->getController()), + startPeriodTransition(DEFAULT_VSYNC_PERIOD, false)) + .Times(1); + EXPECT_CALL(static_cast(vsyncSchedule->getTracker()), resetModel()) + .Times(1); } }; @@ -262,8 +270,9 @@ struct DisplayPowerCase { return display; } - static void setInitialPrimaryHWVsyncEnabled(DisplayTransactionTest* test, bool enabled) { - test->mFlinger.scheduler()->mutablePrimaryHWVsyncEnabled() = enabled; + static void setInitialHwVsyncEnabled(DisplayTransactionTest* test, PhysicalDisplayId id, + bool enabled) { + test->mFlinger.scheduler()->setInitialHwVsyncEnabled(id, enabled); } static void setupRepaintEverythingCallExpectations(DisplayTransactionTest* test) { @@ -300,6 +309,11 @@ using PrimaryDisplayPowerCase = // A sample configuration for the external display. // In addition to not having event thread support, we emulate not having doze // support. +// FIXME (b/267483230): ExternalDisplay supports the features tracked in +// DispSyncIsSupportedVariant, but is the follower, so the +// expectations set by DispSyncIsSupportedVariant don't match (wrong schedule). +// We need a way to retrieve the proper DisplayId from +// setupResetModelCallExpectations (or pass it in). template using ExternalDisplayPowerCase = DisplayPowerCase, @@ -329,9 +343,12 @@ void SetPowerModeInternalTest::transitionDisplayCommon() { Case::Doze::setupComposerCallExpectations(this); auto display = Case::injectDisplayWithInitialPowerMode(this, Case::Transition::INITIAL_POWER_MODE); - Case::setInitialPrimaryHWVsyncEnabled(this, - PowerModeInitialVSyncEnabled< - Case::Transition::INITIAL_POWER_MODE>::value); + auto displayId = display->getId(); + if (auto physicalDisplayId = PhysicalDisplayId::tryCast(displayId)) { + Case::setInitialHwVsyncEnabled(this, *physicalDisplayId, + PowerModeInitialVSyncEnabled< + Case::Transition::INITIAL_POWER_MODE>::value); + } // -------------------------------------------------------------------- // Call Expectations diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp index fed6a1ae56..7e1458806b 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp @@ -34,7 +34,7 @@ protected: ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); + auto vsyncTracker = std::make_shared(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index 6cf61416c0..c360f934f8 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -37,19 +37,16 @@ class TestableScheduler : public Scheduler, private ICompositor { public: TestableScheduler(RefreshRateSelectorPtr selectorPtr, ISchedulerCallback& callback) : TestableScheduler(std::make_unique(), - std::make_unique(), std::move(selectorPtr), + std::make_shared(), std::move(selectorPtr), /* modulatorPtr */ nullptr, callback) {} TestableScheduler(std::unique_ptr controller, - std::unique_ptr tracker, RefreshRateSelectorPtr selectorPtr, + std::shared_ptr tracker, RefreshRateSelectorPtr selectorPtr, sp modulatorPtr, ISchedulerCallback& callback) : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) { - mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), - std::make_unique(), - std::move(controller))); - const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId(); - registerDisplay(displayId, std::move(selectorPtr)); + registerDisplay(displayId, std::move(selectorPtr), std::move(controller), + std::move(tracker)); ON_CALL(*this, postMessage).WillByDefault([](sp&& handler) { // Execute task to prevent broken promise exception on destruction. @@ -66,13 +63,6 @@ public: return Scheduler::createConnection(std::move(eventThread)); } - /* ------------------------------------------------------------------------ - * Read-write access to private data to set up preconditions and assert - * post-conditions. - */ - auto& mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; } - auto& mutableHWVsyncAvailable() { return mHWVsyncAvailable; } - auto refreshRateSelector() { return leaderSelectorPtr(); } const auto& refreshRateSelectors() const NO_THREAD_SAFETY_ANALYSIS { @@ -80,8 +70,21 @@ public: } void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) { + registerDisplay(displayId, std::move(selectorPtr), + std::make_unique(), + std::make_shared()); + } + + void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr, + std::unique_ptr controller, + std::shared_ptr tracker) { ftl::FakeGuard guard(kMainThreadContext); - Scheduler::registerDisplay(displayId, std::move(selectorPtr)); + Scheduler::registerDisplayInternal(displayId, std::move(selectorPtr), + std::shared_ptr( + new VsyncSchedule(displayId, std::move(tracker), + std::make_shared< + mock::VSyncDispatch>(), + std::move(controller)))); } void unregisterDisplay(PhysicalDisplayId displayId) { @@ -157,6 +160,13 @@ public: Scheduler::onNonPrimaryDisplayModeChanged(handle, mode); } + void setInitialHwVsyncEnabled(PhysicalDisplayId id, bool enabled) { + auto schedule = getVsyncSchedule(id); + std::lock_guard lock(schedule->mHwVsyncLock); + schedule->mHwVsyncState = enabled ? VsyncSchedule::HwVsyncState::Enabled + : VsyncSchedule::HwVsyncState::Disabled; + } + private: // ICompositor overrides: void configure() override {} diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 68c738fc9d..63b79a4436 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -201,7 +201,7 @@ public: std::variant; void setupScheduler(std::unique_ptr vsyncController, - std::unique_ptr vsyncTracker, + std::shared_ptr vsyncTracker, std::unique_ptr appEventThread, std::unique_ptr sfEventThread, SchedulerCallbackImpl callbackImpl = SchedulerCallbackImpl::kNoOp, @@ -253,7 +253,7 @@ public: std::move(modulatorPtr), callback); } - mScheduler->initVsync(mScheduler->getVsyncSchedule().getDispatch(), *mTokenManager, 0ms); + mScheduler->initVsync(mScheduler->getVsyncSchedule()->getDispatch(), *mTokenManager, 0ms); mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread)); mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread)); diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index 859f702fe7..a9a617bfa1 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -83,15 +83,14 @@ public: mFlinger.setupComposer(std::make_unique()); mFlinger.setupScheduler(std::unique_ptr(mVsyncController), - std::unique_ptr(mVSyncTracker), - std::move(eventThread), std::move(sfEventThread)); + mVSyncTracker, std::move(eventThread), std::move(sfEventThread)); mFlinger.flinger()->addTransactionReadyFilters(); } TestableSurfaceFlinger mFlinger; mock::VsyncController* mVsyncController = new mock::VsyncController(); - mock::VSyncTracker* mVSyncTracker = new mock::VSyncTracker(); + std::shared_ptr mVSyncTracker = std::make_shared(); struct TransactionInfo { Vector states; diff --git a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp index 1173d1c876..b228bcbbd2 100644 --- a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp @@ -85,7 +85,7 @@ public: ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); + auto vsyncTracker = std::make_shared(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp index ae03db43a7..bfebecd2e2 100644 --- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp @@ -84,7 +84,7 @@ public: ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); + auto vsyncTracker = std::make_shared(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp index da87f1db17..aa33716ed8 100644 --- a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp +++ b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp @@ -117,7 +117,7 @@ void TunnelModeEnabledReporterTest::setupScheduler() { ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); + auto vsyncTracker = std::make_shared(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp index 47c2deef51..fcd2f562d7 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp @@ -109,7 +109,8 @@ struct VSyncDispatchRealtimeTest : testing::Test { class RepeatingCallbackReceiver { public: - RepeatingCallbackReceiver(VSyncDispatch& dispatch, nsecs_t workload, nsecs_t readyDuration) + RepeatingCallbackReceiver(std::shared_ptr dispatch, nsecs_t workload, + nsecs_t readyDuration) : mWorkload(workload), mReadyDuration(readyDuration), mCallback( @@ -166,9 +167,10 @@ private: }; TEST_F(VSyncDispatchRealtimeTest, triple_alarm) { - FixedRateIdealStubTracker tracker; - VSyncDispatchTimerQueue dispatch(std::make_unique(), tracker, mDispatchGroupThreshold, - mVsyncMoveThreshold); + auto tracker = std::make_shared(); + auto dispatch = + std::make_shared(std::make_unique(), tracker, + mDispatchGroupThreshold, mVsyncMoveThreshold); static size_t constexpr num_clients = 3; std::array @@ -195,14 +197,15 @@ TEST_F(VSyncDispatchRealtimeTest, triple_alarm) { // starts at 333hz, slides down to 43hz TEST_F(VSyncDispatchRealtimeTest, vascillating_vrr) { auto next_vsync_interval = toNs(3ms); - VRRStubTracker tracker(next_vsync_interval); - VSyncDispatchTimerQueue dispatch(std::make_unique(), tracker, mDispatchGroupThreshold, - mVsyncMoveThreshold); + auto tracker = std::make_shared(next_vsync_interval); + auto dispatch = + std::make_shared(std::make_unique(), tracker, + mDispatchGroupThreshold, mVsyncMoveThreshold); RepeatingCallbackReceiver cb_receiver(dispatch, toNs(1ms), toNs(5ms)); auto const on_each_frame = [&](nsecs_t last_known) { - tracker.set_interval(next_vsync_interval += toNs(1ms), last_known); + tracker->set_interval(next_vsync_interval += toNs(1ms), last_known); }; std::thread eventThread([&] { cb_receiver.repeatedly_schedule(mIterations, on_each_frame); }); @@ -213,9 +216,10 @@ TEST_F(VSyncDispatchRealtimeTest, vascillating_vrr) { // starts at 333hz, jumps to 200hz at frame 10 TEST_F(VSyncDispatchRealtimeTest, fixed_jump) { - VRRStubTracker tracker(toNs(3ms)); - VSyncDispatchTimerQueue dispatch(std::make_unique(), tracker, mDispatchGroupThreshold, - mVsyncMoveThreshold); + auto tracker = std::make_shared(toNs(3ms)); + auto dispatch = + std::make_shared(std::make_unique(), tracker, + mDispatchGroupThreshold, mVsyncMoveThreshold); RepeatingCallbackReceiver cb_receiver(dispatch, toNs(1ms), toNs(5ms)); @@ -223,7 +227,7 @@ TEST_F(VSyncDispatchRealtimeTest, fixed_jump) { auto constexpr jump_frame_at = 10u; auto const on_each_frame = [&](nsecs_t last_known) { if (jump_frame_counter++ == jump_frame_at) { - tracker.set_interval(toNs(5ms), last_known); + tracker->set_interval(toNs(5ms), last_known); } }; std::thread eventThread([&] { cb_receiver.repeatedly_schedule(mIterations, on_each_frame); }); diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp index 14a2860378..82daffd28d 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp @@ -116,13 +116,14 @@ private: class CountingCallback { public: - CountingCallback(VSyncDispatch& dispatch) - : mDispatch(dispatch), - mToken(dispatch.registerCallback(std::bind(&CountingCallback::counter, this, - std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - "test")) {} - ~CountingCallback() { mDispatch.unregisterCallback(mToken); } + CountingCallback(std::shared_ptr dispatch) + : mDispatch(std::move(dispatch)), + mToken(mDispatch->registerCallback(std::bind(&CountingCallback::counter, this, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3), + "test")) {} + ~CountingCallback() { mDispatch->unregisterCallback(mToken); } operator VSyncDispatch::CallbackToken() const { return mToken; } @@ -132,7 +133,7 @@ public: mReadyTime.push_back(readyTime); } - VSyncDispatch& mDispatch; + std::shared_ptr mDispatch; VSyncDispatch::CallbackToken mToken; std::vector mCalls; std::vector mWakeupTime; @@ -141,12 +142,12 @@ public: class PausingCallback { public: - PausingCallback(VSyncDispatch& dispatch, std::chrono::milliseconds pauseAmount) - : mDispatch(dispatch), - mToken(dispatch.registerCallback(std::bind(&PausingCallback::pause, this, - std::placeholders::_1, - std::placeholders::_2), - "test")), + PausingCallback(std::shared_ptr dispatch, std::chrono::milliseconds pauseAmount) + : mDispatch(std::move(dispatch)), + mToken(mDispatch->registerCallback(std::bind(&PausingCallback::pause, this, + std::placeholders::_1, + std::placeholders::_2), + "test")), mRegistered(true), mPauseAmount(pauseAmount) {} ~PausingCallback() { unregister(); } @@ -181,12 +182,12 @@ public: void unregister() { if (mRegistered) { - mDispatch.unregisterCallback(mToken); + mDispatch->unregisterCallback(mToken); mRegistered = false; } } - VSyncDispatch& mDispatch; + std::shared_ptr mDispatch; VSyncDispatch::CallbackToken mToken; bool mRegistered = true; @@ -231,22 +232,26 @@ protected: static nsecs_t constexpr mDispatchGroupThreshold = 5; nsecs_t const mPeriod = 1000; nsecs_t const mVsyncMoveThreshold = 300; - NiceMock mStubTracker{mPeriod}; - VSyncDispatchTimerQueue mDispatch{createTimeKeeper(), mStubTracker, mDispatchGroupThreshold, - mVsyncMoveThreshold}; + std::shared_ptr> mStubTracker = + std::make_shared>(mPeriod); + std::shared_ptr mDispatch = + std::make_shared(createTimeKeeper(), mStubTracker, + mDispatchGroupThreshold, mVsyncMoveThreshold); }; TEST_F(VSyncDispatchTimerQueueTest, unregistersSetAlarmOnDestruction) { EXPECT_CALL(mMockClock, alarmAt(_, 900)); EXPECT_CALL(mMockClock, alarmCancel()); { - VSyncDispatchTimerQueue mDispatch{createTimeKeeper(), mStubTracker, mDispatchGroupThreshold, - mVsyncMoveThreshold}; + std::shared_ptr mDispatch = + std::make_shared(createTimeKeeper(), mStubTracker, + mDispatchGroupThreshold, + mVsyncMoveThreshold); CountingCallback cb(mDispatch); - const auto result = mDispatch.schedule(cb, - {.workDuration = 100, - .readyDuration = 0, - .earliestVsync = 1000}); + const auto result = mDispatch->schedule(cb, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); } @@ -257,10 +262,10 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFuture) { EXPECT_CALL(mMockClock, alarmAt(_, 900)); CountingCallback cb(mDispatch); - const auto result = mDispatch.schedule(cb, - {.workDuration = 100, - .readyDuration = 0, - .earliestVsync = intended}); + const auto result = mDispatch->schedule(cb, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = intended}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); @@ -277,14 +282,15 @@ TEST_F(VSyncDispatchTimerQueueTest, updateAlarmSettingFuture) { EXPECT_CALL(mMockClock, alarmAt(_, 700)).InSequence(seq); CountingCallback cb(mDispatch); - auto result = mDispatch.schedule(cb, - {.workDuration = 100, - .readyDuration = 0, - .earliestVsync = intended}); + auto result = mDispatch->schedule(cb, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = intended}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); - result = mDispatch.update(cb, + result = + mDispatch->update(cb, {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(700, *result); @@ -302,17 +308,17 @@ TEST_F(VSyncDispatchTimerQueueTest, updateDoesntSchedule) { CountingCallback cb(mDispatch); const auto result = - mDispatch.update(cb, - {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended}); + mDispatch->update(cb, + {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended}); EXPECT_FALSE(result.has_value()); } TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFutureWithAdjustmentToTrueVsync) { - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000)).WillOnce(Return(1150)); + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)).WillOnce(Return(1150)); EXPECT_CALL(mMockClock, alarmAt(_, 1050)); CountingCallback cb(mDispatch); - mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); + mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); advanceToNextCallback(); ASSERT_THAT(cb.mCalls.size(), Eq(1)); @@ -323,15 +329,15 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingAdjustmentPast) { auto const now = 234; mMockClock.advanceBy(234); auto const workDuration = 10 * mPeriod; - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(now + workDuration)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(now + workDuration)) .WillOnce(Return(mPeriod * 11)); EXPECT_CALL(mMockClock, alarmAt(_, mPeriod)); CountingCallback cb(mDispatch); - const auto result = mDispatch.schedule(cb, - {.workDuration = workDuration, - .readyDuration = 0, - .earliestVsync = mPeriod}); + const auto result = mDispatch->schedule(cb, + {.workDuration = workDuration, + .readyDuration = 0, + .earliestVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod, *result); } @@ -341,12 +347,13 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancel) { EXPECT_CALL(mMockClock, alarmCancel()); CountingCallback cb(mDispatch); - const auto result = - mDispatch.schedule(cb, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); + const auto result = mDispatch->schedule(cb, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod - 100, *result); - EXPECT_EQ(mDispatch.cancel(cb), CancelResult::Cancelled); + EXPECT_EQ(mDispatch->cancel(cb), CancelResult::Cancelled); } TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLate) { @@ -354,13 +361,14 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLate) { EXPECT_CALL(mMockClock, alarmCancel()); CountingCallback cb(mDispatch); - const auto result = - mDispatch.schedule(cb, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); + const auto result = mDispatch->schedule(cb, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod - 100, *result); mMockClock.advanceBy(950); - EXPECT_EQ(mDispatch.cancel(cb), CancelResult::TooLate); + EXPECT_EQ(mDispatch->cancel(cb), CancelResult::TooLate); } TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLateWhenRunning) { @@ -368,15 +376,16 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLateWhenRunning) { EXPECT_CALL(mMockClock, alarmCancel()); PausingCallback cb(mDispatch, std::chrono::duration_cast(1s)); - const auto result = - mDispatch.schedule(cb, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); + const auto result = mDispatch->schedule(cb, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod - 100, *result); std::thread pausingThread([&] { mMockClock.advanceToNextCallback(); }); EXPECT_TRUE(cb.waitForPause()); - EXPECT_EQ(mDispatch.cancel(cb), CancelResult::TooLate); + EXPECT_EQ(mDispatch->cancel(cb), CancelResult::TooLate); cb.unpause(); pausingThread.join(); } @@ -389,9 +398,10 @@ TEST_F(VSyncDispatchTimerQueueTest, unregisterSynchronizes) { PausingCallback cb(mDispatch, 50ms); cb.stashResource(resource); - const auto result = - mDispatch.schedule(cb, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); + const auto result = mDispatch->schedule(cb, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod - 100, *result); @@ -408,7 +418,7 @@ TEST_F(VSyncDispatchTimerQueueTest, unregisterSynchronizes) { } TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) { - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)) .Times(4) .WillOnce(Return(1055)) .WillOnce(Return(1063)) @@ -423,8 +433,8 @@ TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch.schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); - mDispatch.schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod}); + mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); + mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod}); advanceToNextCallback(); advanceToNextCallback(); @@ -436,7 +446,7 @@ TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) { } TEST_F(VSyncDispatchTimerQueueTest, noCloseCallbacksAfterPeriodChange) { - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_)) .Times(4) .WillOnce(Return(1000)) .WillOnce(Return(2000)) @@ -450,21 +460,21 @@ TEST_F(VSyncDispatchTimerQueueTest, noCloseCallbacksAfterPeriodChange) { CountingCallback cb(mDispatch); - mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 0}); + mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 0}); advanceToNextCallback(); ASSERT_THAT(cb.mCalls.size(), Eq(1)); EXPECT_THAT(cb.mCalls[0], Eq(1000)); - mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); ASSERT_THAT(cb.mCalls.size(), Eq(2)); EXPECT_THAT(cb.mCalls[1], Eq(2000)); - mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); advanceToNextCallback(); @@ -473,7 +483,7 @@ TEST_F(VSyncDispatchTimerQueueTest, noCloseCallbacksAfterPeriodChange) { } TEST_F(VSyncDispatchTimerQueueTest, rearmsFaroutTimeoutWhenCancellingCloseOne) { - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_)) .Times(4) .WillOnce(Return(10000)) .WillOnce(Return(1000)) @@ -488,10 +498,10 @@ TEST_F(VSyncDispatchTimerQueueTest, rearmsFaroutTimeoutWhenCancellingCloseOne) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch.schedule(cb0, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod * 10}); - mDispatch.schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod}); - mDispatch.cancel(cb1); + mDispatch->schedule(cb0, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod * 10}); + mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod}); + mDispatch->cancel(cb1); } TEST_F(VSyncDispatchTimerQueueTest, noUnnecessaryRearmsWhenRescheduling) { @@ -502,9 +512,9 @@ TEST_F(VSyncDispatchTimerQueueTest, noUnnecessaryRearmsWhenRescheduling) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb1, {.workDuration = 300, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 300, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); } @@ -517,9 +527,9 @@ TEST_F(VSyncDispatchTimerQueueTest, necessaryRearmsWhenModifying) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); } @@ -537,10 +547,10 @@ TEST_F(VSyncDispatchTimerQueueTest, modifyIntoGroup) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb1, - {.workDuration = closeOffset, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, + {.workDuration = closeOffset, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); ASSERT_THAT(cb0.mCalls.size(), Eq(1)); @@ -548,9 +558,11 @@ TEST_F(VSyncDispatchTimerQueueTest, modifyIntoGroup) { ASSERT_THAT(cb1.mCalls.size(), Eq(1)); EXPECT_THAT(cb1.mCalls[0], Eq(mPeriod)); - mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 2000}); - mDispatch.schedule(cb1, - {.workDuration = notCloseOffset, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch->schedule(cb1, + {.workDuration = notCloseOffset, + .readyDuration = 0, + .earliestVsync = 2000}); advanceToNextCallback(); ASSERT_THAT(cb1.mCalls.size(), Eq(2)); EXPECT_THAT(cb1.mCalls[1], Eq(2000)); @@ -570,32 +582,32 @@ TEST_F(VSyncDispatchTimerQueueTest, rearmsWhenEndingAndDoesntCancel) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch.schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); - EXPECT_EQ(mDispatch.cancel(cb0), CancelResult::Cancelled); + EXPECT_EQ(mDispatch->cancel(cb0), CancelResult::Cancelled); } TEST_F(VSyncDispatchTimerQueueTest, setAlarmCallsAtCorrectTimeWithChangingVsync) { - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_)) .Times(3) .WillOnce(Return(950)) .WillOnce(Return(1975)) .WillOnce(Return(2950)); CountingCallback cb(mDispatch); - mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 920}); + mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 920}); mMockClock.advanceBy(850); EXPECT_THAT(cb.mCalls.size(), Eq(1)); - mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1900}); + mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1900}); mMockClock.advanceBy(900); EXPECT_THAT(cb.mCalls.size(), Eq(1)); mMockClock.advanceBy(125); EXPECT_THAT(cb.mCalls.size(), Eq(2)); - mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2900}); + mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2900}); mMockClock.advanceBy(975); EXPECT_THAT(cb.mCalls.size(), Eq(3)); } @@ -606,48 +618,48 @@ TEST_F(VSyncDispatchTimerQueueTest, callbackReentrancy) { EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq); VSyncDispatch::CallbackToken tmp; - tmp = mDispatch.registerCallback( + tmp = mDispatch->registerCallback( [&](auto, auto, auto) { - mDispatch.schedule(tmp, - {.workDuration = 100, - .readyDuration = 0, - .earliestVsync = 2000}); + mDispatch->schedule(tmp, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = 2000}); }, "o.o"); - mDispatch.schedule(tmp, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(tmp, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); } TEST_F(VSyncDispatchTimerQueueTest, callbackReentrantWithPastWakeup) { VSyncDispatch::CallbackToken tmp; std::optional lastTarget; - tmp = mDispatch.registerCallback( + tmp = mDispatch->registerCallback( [&](auto timestamp, auto, auto) { auto result = - mDispatch.schedule(tmp, - {.workDuration = 400, - .readyDuration = 0, - .earliestVsync = timestamp - mVsyncMoveThreshold}); - EXPECT_TRUE(result.has_value()); - EXPECT_EQ(mPeriod + timestamp - 400, *result); - result = mDispatch.schedule(tmp, + mDispatch->schedule(tmp, {.workDuration = 400, .readyDuration = 0, - .earliestVsync = timestamp}); + .earliestVsync = timestamp - mVsyncMoveThreshold}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod + timestamp - 400, *result); - result = mDispatch.schedule(tmp, - {.workDuration = 400, - .readyDuration = 0, - .earliestVsync = timestamp + mVsyncMoveThreshold}); + result = mDispatch->schedule(tmp, + {.workDuration = 400, + .readyDuration = 0, + .earliestVsync = timestamp}); + EXPECT_TRUE(result.has_value()); + EXPECT_EQ(mPeriod + timestamp - 400, *result); + result = mDispatch->schedule(tmp, + {.workDuration = 400, + .readyDuration = 0, + .earliestVsync = timestamp + mVsyncMoveThreshold}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod + timestamp - 400, *result); lastTarget = timestamp; }, "oo"); - mDispatch.schedule(tmp, {.workDuration = 999, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(tmp, {.workDuration = 999, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); EXPECT_THAT(lastTarget, Eq(1000)); @@ -663,16 +675,16 @@ TEST_F(VSyncDispatchTimerQueueTest, modificationsAroundVsyncTime) { EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq); CountingCallback cb(mDispatch); - mDispatch.schedule(cb, {.workDuration = 0, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, {.workDuration = 0, .readyDuration = 0, .earliestVsync = 1000}); mMockClock.advanceBy(750); - mDispatch.schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); - mDispatch.schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 2000}); mMockClock.advanceBy(800); - mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); } TEST_F(VSyncDispatchTimerQueueTest, lateModifications) { @@ -685,12 +697,12 @@ TEST_F(VSyncDispatchTimerQueueTest, lateModifications) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch.schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb1, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); - mDispatch.schedule(cb0, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 2000}); - mDispatch.schedule(cb1, {.workDuration = 150, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch->schedule(cb1, {.workDuration = 150, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); advanceToNextCallback(); @@ -702,8 +714,8 @@ TEST_F(VSyncDispatchTimerQueueTest, doesntCancelPriorValidTimerForFutureMod) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch.schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 20000}); + mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 20000}); } TEST_F(VSyncDispatchTimerQueueTest, setsTimerAfterCancellation) { @@ -713,29 +725,30 @@ TEST_F(VSyncDispatchTimerQueueTest, setsTimerAfterCancellation) { EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq); CountingCallback cb0(mDispatch); - mDispatch.schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.cancel(cb0); - mDispatch.schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->cancel(cb0); + mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); } TEST_F(VSyncDispatchTimerQueueTest, makingUpIdsError) { VSyncDispatch::CallbackToken token(100); - EXPECT_FALSE(mDispatch - .schedule(token, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}) - .has_value()); - EXPECT_THAT(mDispatch.cancel(token), Eq(CancelResult::Error)); + EXPECT_FALSE( + mDispatch + ->schedule(token, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}) + .has_value()); + EXPECT_THAT(mDispatch->cancel(token), Eq(CancelResult::Error)); } TEST_F(VSyncDispatchTimerQueueTest, canMoveCallbackBackwardsInTime) { CountingCallback cb0(mDispatch); auto result = - mDispatch.schedule(cb0, - {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, + {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(500, *result); - result = mDispatch.schedule(cb0, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + result = mDispatch->schedule(cb0, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); } @@ -745,14 +758,14 @@ TEST_F(VSyncDispatchTimerQueueTest, doesNotMoveCallbackBackwardsAndSkipASchedule EXPECT_CALL(mMockClock, alarmAt(_, 500)); CountingCallback cb(mDispatch); auto result = - mDispatch.schedule(cb, - {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, + {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(500, *result); mMockClock.advanceBy(400); - result = mDispatch.schedule(cb, - {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000}); + result = mDispatch->schedule(cb, + {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1200, *result); advanceToNextCallback(); @@ -760,19 +773,19 @@ TEST_F(VSyncDispatchTimerQueueTest, doesNotMoveCallbackBackwardsAndSkipASchedule } TEST_F(VSyncDispatchTimerQueueTest, targetOffsetMovingBackALittleCanStillSchedule) { - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)) .Times(2) .WillOnce(Return(1000)) .WillOnce(Return(1002)); CountingCallback cb(mDispatch); auto result = - mDispatch.schedule(cb, - {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, + {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(500, *result); mMockClock.advanceBy(400); - result = mDispatch.schedule(cb, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + result = mDispatch->schedule(cb, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(602, *result); } @@ -780,13 +793,13 @@ TEST_F(VSyncDispatchTimerQueueTest, targetOffsetMovingBackALittleCanStillSchedul TEST_F(VSyncDispatchTimerQueueTest, canScheduleNegativeOffsetAgainstDifferentPeriods) { CountingCallback cb0(mDispatch); auto result = - mDispatch.schedule(cb0, - {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, + {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(500, *result); advanceToNextCallback(); - result = mDispatch.schedule(cb0, - {.workDuration = 1100, .readyDuration = 0, .earliestVsync = 2000}); + result = mDispatch->schedule(cb0, + {.workDuration = 1100, .readyDuration = 0, .earliestVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); } @@ -797,13 +810,13 @@ TEST_F(VSyncDispatchTimerQueueTest, canScheduleLargeNegativeOffset) { EXPECT_CALL(mMockClock, alarmAt(_, 1100)).InSequence(seq); CountingCallback cb0(mDispatch); auto result = - mDispatch.schedule(cb0, - {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb0, + {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(500, *result); advanceToNextCallback(); - result = mDispatch.schedule(cb0, - {.workDuration = 1900, .readyDuration = 0, .earliestVsync = 2000}); + result = mDispatch->schedule(cb0, + {.workDuration = 1900, .readyDuration = 0, .earliestVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1100, *result); } @@ -813,13 +826,13 @@ TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesNotAffectSchedulingState) CountingCallback cb(mDispatch); auto result = - mDispatch.schedule(cb, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); - result = mDispatch.schedule(cb, - {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000}); + result = mDispatch->schedule(cb, + {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); @@ -865,16 +878,16 @@ TEST_F(VSyncDispatchTimerQueueTest, skipsSchedulingIfTimerReschedulingIsImminent CountingCallback cb2(mDispatch); auto result = - mDispatch.schedule(cb1, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); mMockClock.setLag(100); mMockClock.advanceBy(620); - result = mDispatch.schedule(cb2, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); + result = mDispatch->schedule(cb2, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1900, *result); mMockClock.advanceBy(80); @@ -893,16 +906,16 @@ TEST_F(VSyncDispatchTimerQueueTest, skipsSchedulingIfTimerReschedulingIsImminent CountingCallback cb(mDispatch); auto result = - mDispatch.schedule(cb, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); mMockClock.setLag(100); mMockClock.advanceBy(620); - result = mDispatch.schedule(cb, - {.workDuration = 370, .readyDuration = 0, .earliestVsync = 2000}); + result = mDispatch->schedule(cb, + {.workDuration = 370, .readyDuration = 0, .earliestVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1630, *result); mMockClock.advanceBy(80); @@ -919,19 +932,19 @@ TEST_F(VSyncDispatchTimerQueueTest, skipsRearmingWhenNotNextScheduled) { CountingCallback cb2(mDispatch); auto result = - mDispatch.schedule(cb1, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); - result = mDispatch.schedule(cb2, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); + result = mDispatch->schedule(cb2, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1900, *result); mMockClock.setLag(100); mMockClock.advanceBy(620); - EXPECT_EQ(mDispatch.cancel(cb2), CancelResult::Cancelled); + EXPECT_EQ(mDispatch->cancel(cb2), CancelResult::Cancelled); mMockClock.advanceBy(80); @@ -948,19 +961,19 @@ TEST_F(VSyncDispatchTimerQueueTest, rearmsWhenCancelledAndIsNextScheduled) { CountingCallback cb2(mDispatch); auto result = - mDispatch.schedule(cb1, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); - result = mDispatch.schedule(cb2, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); + result = mDispatch->schedule(cb2, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1900, *result); mMockClock.setLag(100); mMockClock.advanceBy(620); - EXPECT_EQ(mDispatch.cancel(cb1), CancelResult::Cancelled); + EXPECT_EQ(mDispatch->cancel(cb1), CancelResult::Cancelled); EXPECT_THAT(cb1.mCalls.size(), Eq(0)); EXPECT_THAT(cb2.mCalls.size(), Eq(0)); @@ -975,21 +988,21 @@ TEST_F(VSyncDispatchTimerQueueTest, laggedTimerGroupsCallbacksWithinLag) { CountingCallback cb2(mDispatch); Sequence seq; - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)) .InSequence(seq) .WillOnce(Return(1000)); EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq); - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)) .InSequence(seq) .WillOnce(Return(1000)); auto result = - mDispatch.schedule(cb1, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb1, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); - result = mDispatch.schedule(cb2, - {.workDuration = 390, .readyDuration = 0, .earliestVsync = 1000}); + result = mDispatch->schedule(cb2, + {.workDuration = 390, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(610, *result); @@ -1011,10 +1024,10 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFutureWithReadyDuration) { EXPECT_CALL(mMockClock, alarmAt(_, 900)); CountingCallback cb(mDispatch); - const auto result = mDispatch.schedule(cb, - {.workDuration = 70, - .readyDuration = 30, - .earliestVsync = intended}); + const auto result = mDispatch->schedule(cb, + {.workDuration = 70, + .readyDuration = 30, + .earliestVsync = intended}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); advanceToNextCallback(); @@ -1033,8 +1046,8 @@ TEST_F(VSyncDispatchTimerQueueTest, updatesVsyncTimeForCloseWakeupTime) { CountingCallback cb(mDispatch); - mDispatch.schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch.schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); @@ -1052,7 +1065,8 @@ class VSyncDispatchTimerQueueEntryTest : public testing::Test { protected: nsecs_t const mPeriod = 1000; nsecs_t const mVsyncMoveThreshold = 200; - NiceMock mStubTracker{mPeriod}; + std::shared_ptr> mStubTracker = + std::make_shared>(mPeriod); }; TEST_F(VSyncDispatchTimerQueueEntryTest, stateAfterInitialization) { @@ -1070,7 +1084,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, stateScheduling) { EXPECT_FALSE(entry.wakeupTime()); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); auto const wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); @@ -1084,7 +1098,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, stateSchedulingReallyLongWakeupLatency) auto const duration = 500; auto const now = 8750; - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(now + duration)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(now + duration)) .Times(1) .WillOnce(Return(10000)); VSyncDispatchTimerQueueEntry entry( @@ -1092,7 +1106,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, stateSchedulingReallyLongWakeupLatency) EXPECT_FALSE(entry.wakeupTime()); EXPECT_TRUE(entry.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 994}, - mStubTracker, now) + *mStubTracker.get(), now) .has_value()); auto const wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); @@ -1115,7 +1129,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, runCallback) { mVsyncMoveThreshold); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); auto const wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); @@ -1137,7 +1151,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, runCallback) { } TEST_F(VSyncDispatchTimerQueueEntryTest, updateCallback) { - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_)) .Times(2) .WillOnce(Return(1000)) .WillOnce(Return(1020)); @@ -1146,17 +1160,17 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, updateCallback) { "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); EXPECT_FALSE(entry.wakeupTime()); - entry.update(mStubTracker, 0); + entry.update(*mStubTracker.get(), 0); EXPECT_FALSE(entry.wakeupTime()); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); auto wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); EXPECT_THAT(wakeup, Eq(900)); - entry.update(mStubTracker, 0); + entry.update(*mStubTracker.get(), 0); wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); EXPECT_THAT(*wakeup, Eq(920)); @@ -1166,9 +1180,9 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, skipsUpdateIfJustScheduled) { VSyncDispatchTimerQueueEntry entry( "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); - entry.update(mStubTracker, 0); + entry.update(*mStubTracker.get(), 0); auto const wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); @@ -1179,24 +1193,24 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, willSnapToNextTargettableVSync) { VSyncDispatchTimerQueueEntry entry( "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); entry.executing(); // 1000 is executing // had 1000 not been executing, this could have been scheduled for time 800. EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); EXPECT_THAT(*entry.wakeupTime(), Eq(1800)); EXPECT_THAT(*entry.readyTime(), Eq(2000)); EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); EXPECT_THAT(*entry.wakeupTime(), Eq(1950)); EXPECT_THAT(*entry.readyTime(), Eq(2000)); EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 1001}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); EXPECT_THAT(*entry.wakeupTime(), Eq(1800)); EXPECT_THAT(*entry.readyTime(), Eq(2000)); @@ -1208,24 +1222,24 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); Sequence seq; - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(500)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500)) .InSequence(seq) .WillOnce(Return(1000)); - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(500)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500)) .InSequence(seq) .WillOnce(Return(1000)); - EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold)) + EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold)) .InSequence(seq) .WillOnce(Return(2000)); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); entry.executing(); // 1000 is executing EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); } @@ -1233,16 +1247,16 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, reportsScheduledIfStillTime) { VSyncDispatchTimerQueueEntry entry( "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); EXPECT_TRUE(entry.schedule({.workDuration = 1200, .readyDuration = 0, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); } @@ -1255,7 +1269,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, storesPendingUpdatesUntilUpdate) { entry.addPendingWorkloadUpdate( {.workDuration = effectualOffset, .readyDuration = 0, .earliestVsync = 400}); EXPECT_TRUE(entry.hasPendingWorkloadUpdate()); - entry.update(mStubTracker, 0); + entry.update(*mStubTracker.get(), 0); EXPECT_FALSE(entry.hasPendingWorkloadUpdate()); EXPECT_THAT(*entry.wakeupTime(), Eq(mPeriod - effectualOffset)); } @@ -1276,7 +1290,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, runCallbackWithReadyDuration) { mVsyncMoveThreshold); EXPECT_TRUE(entry.schedule({.workDuration = 70, .readyDuration = 30, .earliestVsync = 500}, - mStubTracker, 0) + *mStubTracker.get(), 0) .has_value()); auto const wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index 3095e8aa9a..7947a5e97a 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -55,7 +55,7 @@ struct VSyncPredictorTest : testing::Test { static constexpr size_t kOutlierTolerancePercent = 25; static constexpr nsecs_t mMaxRoundingError = 100; - VSyncPredictor tracker{mPeriod, kHistorySize, kMinimumSamplesForPrediction, + VSyncPredictor tracker{"tracker", mPeriod, kHistorySize, kMinimumSamplesForPrediction, kOutlierTolerancePercent}; }; @@ -376,7 +376,8 @@ TEST_F(VSyncPredictorTest, doesNotPredictBeforeTimePointWithHigherIntercept) { // See b/151146131 TEST_F(VSyncPredictorTest, hasEnoughPrecision) { - VSyncPredictor tracker{mPeriod, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent}; + VSyncPredictor tracker{"tracker", mPeriod, 20, kMinimumSamplesForPrediction, + kOutlierTolerancePercent}; std::vector const simulatedVsyncs{840873348817, 840890049444, 840906762675, 840923581635, 840940161584, 840956868096, 840973702473, 840990256277, 841007116851, diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp index 1fb2709f8d..a2de1360eb 100644 --- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp @@ -96,8 +96,8 @@ protected: VSyncReactorTest() : mMockTracker(std::make_shared>()), mMockClock(std::make_shared>()), - mReactor(std::make_unique(mMockClock), *mMockTracker, kPendingLimit, - false /* supportKernelIdleTimer */) { + mReactor("reactor", std::make_unique(mMockClock), *mMockTracker, + kPendingLimit, false /* supportKernelIdleTimer */) { ON_CALL(*mMockClock, now()).WillByDefault(Return(mFakeNow)); ON_CALL(*mMockTracker, currentPeriod()).WillByDefault(Return(period)); } @@ -192,7 +192,7 @@ TEST_F(VSyncReactorTest, ignoresProperlyAfterAPeriodConfirmation) { mReactor.setIgnorePresentFences(true); nsecs_t const newPeriod = 5000; - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(0, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); @@ -205,7 +205,7 @@ TEST_F(VSyncReactorTest, ignoresProperlyAfterAPeriodConfirmation) { TEST_F(VSyncReactorTest, setPeriodCalledOnceConfirmedChange) { nsecs_t const newPeriod = 5000; EXPECT_CALL(*mMockTracker, setPeriod(_)).Times(0); - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); bool periodFlushed = true; EXPECT_TRUE(mReactor.addHwVsyncTimestamp(10000, std::nullopt, &periodFlushed)); @@ -224,7 +224,7 @@ TEST_F(VSyncReactorTest, setPeriodCalledOnceConfirmedChange) { TEST_F(VSyncReactorTest, changingPeriodBackAbortsConfirmationProcess) { nsecs_t sampleTime = 0; nsecs_t const newPeriod = 5000; - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); bool periodFlushed = true; EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); @@ -232,7 +232,7 @@ TEST_F(VSyncReactorTest, changingPeriodBackAbortsConfirmationProcess) { EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); - mReactor.startPeriodTransition(period); + mReactor.startPeriodTransition(period, false); EXPECT_FALSE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); } @@ -242,13 +242,13 @@ TEST_F(VSyncReactorTest, changingToAThirdPeriodWillWaitForLastPeriod) { nsecs_t const secondPeriod = 5000; nsecs_t const thirdPeriod = 2000; - mReactor.startPeriodTransition(secondPeriod); + mReactor.startPeriodTransition(secondPeriod, false); bool periodFlushed = true; EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); - mReactor.startPeriodTransition(thirdPeriod); + mReactor.startPeriodTransition(thirdPeriod, false); EXPECT_TRUE( mReactor.addHwVsyncTimestamp(sampleTime += secondPeriod, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); @@ -289,14 +289,14 @@ TEST_F(VSyncReactorTest, reportedBadTimestampFromPredictorWillReactivateHwVSyncP TEST_F(VSyncReactorTest, presentFenceAdditionDoesNotInterruptConfirmationProcess) { nsecs_t const newPeriod = 5000; - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); EXPECT_TRUE(mReactor.addPresentFence(generateSignalledFenceWithTime(0))); } TEST_F(VSyncReactorTest, setPeriodCalledFirstTwoEventsNewPeriod) { nsecs_t const newPeriod = 5000; EXPECT_CALL(*mMockTracker, setPeriod(_)).Times(0); - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); bool periodFlushed = true; EXPECT_TRUE(mReactor.addHwVsyncTimestamp(5000, std::nullopt, &periodFlushed)); @@ -321,7 +321,7 @@ TEST_F(VSyncReactorTest, addResyncSamplePeriodChanges) { bool periodFlushed = false; nsecs_t const newPeriod = 4000; - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); auto time = 0; auto constexpr numTimestampSubmissions = 10; @@ -346,7 +346,7 @@ TEST_F(VSyncReactorTest, addHwVsyncTimestampDozePreempt) { bool periodFlushed = false; nsecs_t const newPeriod = 4000; - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); auto time = 0; // If the power mode is not DOZE or DOZE_SUSPEND, it is still collecting timestamps. @@ -363,7 +363,7 @@ TEST_F(VSyncReactorTest, addPresentFenceWhileAwaitingPeriodConfirmationRequestsH auto time = 0; bool periodFlushed = false; nsecs_t const newPeriod = 4000; - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); time += period; mReactor.addHwVsyncTimestamp(time, std::nullopt, &periodFlushed); @@ -379,7 +379,7 @@ TEST_F(VSyncReactorTest, hwVsyncIsRequestedForTracker) { auto time = 0; bool periodFlushed = false; nsecs_t const newPeriod = 4000; - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); static auto constexpr numSamplesWithNewPeriod = 4; Sequence seq; @@ -406,7 +406,7 @@ TEST_F(VSyncReactorTest, hwVsyncturnsOffOnConfirmationWhenTrackerDoesntRequest) auto time = 0; bool periodFlushed = false; nsecs_t const newPeriod = 4000; - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); Sequence seq; EXPECT_CALL(*mMockTracker, needsMoreSamples()) @@ -426,7 +426,7 @@ TEST_F(VSyncReactorTest, hwVsyncIsRequestedForTrackerMultiplePeriodChanges) { nsecs_t const newPeriod1 = 4000; nsecs_t const newPeriod2 = 7000; - mReactor.startPeriodTransition(newPeriod1); + mReactor.startPeriodTransition(newPeriod1, false); Sequence seq; EXPECT_CALL(*mMockTracker, needsMoreSamples()) @@ -445,7 +445,7 @@ TEST_F(VSyncReactorTest, hwVsyncIsRequestedForTrackerMultiplePeriodChanges) { EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod1, std::nullopt, &periodFlushed)); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod1, std::nullopt, &periodFlushed)); - mReactor.startPeriodTransition(newPeriod2); + mReactor.startPeriodTransition(newPeriod2, false); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod1, std::nullopt, &periodFlushed)); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod2, std::nullopt, &periodFlushed)); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod2, std::nullopt, &periodFlushed)); @@ -458,7 +458,7 @@ TEST_F(VSyncReactorTest, periodChangeWithGivenVsyncPeriod) { mReactor.setIgnorePresentFences(true); nsecs_t const newPeriod = 5000; - mReactor.startPeriodTransition(newPeriod); + mReactor.startPeriodTransition(newPeriod, false); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(0, 0, &periodFlushed)); EXPECT_FALSE(periodFlushed); @@ -472,8 +472,9 @@ TEST_F(VSyncReactorTest, periodChangeWithGivenVsyncPeriod) { TEST_F(VSyncReactorTest, periodIsMeasuredIfIgnoringComposer) { // Create a reactor which supports the kernel idle timer - auto idleReactor = VSyncReactor(std::make_unique(mMockClock), *mMockTracker, - kPendingLimit, true /* supportKernelIdleTimer */); + auto idleReactor = + VSyncReactor("reactor", std::make_unique(mMockClock), *mMockTracker, + kPendingLimit, true /* supportKernelIdleTimer */); bool periodFlushed = true; EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(4); @@ -481,7 +482,7 @@ TEST_F(VSyncReactorTest, periodIsMeasuredIfIgnoringComposer) { // First, set the same period, which should only be confirmed when we receive two // matching callbacks - idleReactor.startPeriodTransition(10000); + idleReactor.startPeriodTransition(10000, false); EXPECT_TRUE(idleReactor.addHwVsyncTimestamp(0, 0, &periodFlushed)); EXPECT_FALSE(periodFlushed); // Correct period but incorrect timestamp delta @@ -494,7 +495,7 @@ TEST_F(VSyncReactorTest, periodIsMeasuredIfIgnoringComposer) { // Then, set a new period, which should be confirmed as soon as we receive a callback // reporting the new period nsecs_t const newPeriod = 5000; - idleReactor.startPeriodTransition(newPeriod); + idleReactor.startPeriodTransition(newPeriod, false); // Incorrect timestamp delta and period EXPECT_TRUE(idleReactor.addHwVsyncTimestamp(20000, 10000, &periodFlushed)); EXPECT_FALSE(periodFlushed); diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h index f8567bd636..3a6068ae88 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h +++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h @@ -29,27 +29,29 @@ public: EventThread(); ~EventThread() override; - MOCK_CONST_METHOD2(createEventConnection, - sp(ResyncCallback, EventRegistrationFlags)); - MOCK_METHOD0(onScreenReleased, void()); - MOCK_METHOD0(onScreenAcquired, void()); - MOCK_METHOD2(onHotplugReceived, void(PhysicalDisplayId, bool)); - MOCK_METHOD1(onModeChanged, void(const scheduler::FrameRateMode &)); - MOCK_METHOD2(onFrameRateOverridesChanged, - void(PhysicalDisplayId, std::vector)); - MOCK_CONST_METHOD1(dump, void(std::string&)); - MOCK_METHOD2(setDuration, - void(std::chrono::nanoseconds workDuration, - std::chrono::nanoseconds readyDuration)); - MOCK_METHOD1(registerDisplayEventConnection, - status_t(const sp &)); - MOCK_METHOD2(setVsyncRate, void(uint32_t, const sp &)); - MOCK_METHOD1(requestNextVsync, void(const sp &)); + MOCK_METHOD(sp, createEventConnection, + (ResyncCallback, EventRegistrationFlags), (const, override)); + MOCK_METHOD(void, onScreenReleased, (), (override)); + MOCK_METHOD(void, onScreenAcquired, (), (override)); + MOCK_METHOD(void, onHotplugReceived, (PhysicalDisplayId, bool), (override)); + MOCK_METHOD(void, onModeChanged, (const scheduler::FrameRateMode&), (override)); + MOCK_METHOD(void, onFrameRateOverridesChanged, + (PhysicalDisplayId, std::vector), (override)); + MOCK_METHOD(void, dump, (std::string&), (const, override)); + MOCK_METHOD(void, setDuration, + (std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration), + (override)); + MOCK_METHOD(status_t, registerDisplayEventConnection, + (const sp&), (override)); + MOCK_METHOD(void, setVsyncRate, (uint32_t, const sp&), + (override)); + MOCK_METHOD(void, requestNextVsync, (const sp&), (override)); MOCK_METHOD(VsyncEventData, getLatestVsyncEventData, - (const sp &), (const)); - MOCK_METHOD1(requestLatestConfig, void(const sp &)); - MOCK_METHOD1(pauseVsyncCallback, void(bool)); - MOCK_METHOD0(getEventThreadConnectionCount, size_t()); + (const sp&), (const, override)); + MOCK_METHOD(void, requestLatestConfig, (const sp&)); + MOCK_METHOD(void, pauseVsyncCallback, (bool)); + MOCK_METHOD(size_t, getEventThreadConnectionCount, (), (override)); + MOCK_METHOD(void, onNewVsyncSchedule, (std::shared_ptr), (override)); }; } // namespace android::mock diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h index 7d4b159e5e..a8eca2192f 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h +++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h @@ -18,19 +18,19 @@ #include -#include "Scheduler/Scheduler.h" +#include "Scheduler/ISchedulerCallback.h" namespace android::scheduler::mock { struct SchedulerCallback final : ISchedulerCallback { - MOCK_METHOD(void, setVsyncEnabled, (bool), (override)); + MOCK_METHOD(void, setVsyncEnabled, (PhysicalDisplayId, bool), (override)); MOCK_METHOD(void, requestDisplayModes, (std::vector), (override)); MOCK_METHOD(void, kernelTimerChanged, (bool), (override)); MOCK_METHOD(void, triggerOnFrameRateOverridesChanged, (), (override)); }; struct NoOpSchedulerCallback final : ISchedulerCallback { - void setVsyncEnabled(bool) override {} + void setVsyncEnabled(PhysicalDisplayId, bool) override {} void requestDisplayModes(std::vector) override {} void kernelTimerChanged(bool) override {} void triggerOnFrameRateOverridesChanged() override {} diff --git a/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h b/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h index 4ef91dacb2..69ec60acd4 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h +++ b/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h @@ -28,12 +28,12 @@ public: ~VsyncController() override; MOCK_METHOD(bool, addPresentFence, (std::shared_ptr), (override)); - MOCK_METHOD3(addHwVsyncTimestamp, bool(nsecs_t, std::optional, bool*)); - MOCK_METHOD1(startPeriodTransition, void(nsecs_t)); - MOCK_METHOD1(setIgnorePresentFences, void(bool)); + MOCK_METHOD(bool, addHwVsyncTimestamp, (nsecs_t, std::optional, bool*), (override)); + MOCK_METHOD(void, startPeriodTransition, (nsecs_t, bool), (override)); + MOCK_METHOD(void, setIgnorePresentFences, (bool), (override)); MOCK_METHOD(void, setDisplayPowerMode, (hal::PowerMode), (override)); - MOCK_CONST_METHOD1(dump, void(std::string&)); + MOCK_METHOD(void, dump, (std::string&), (const, override)); }; } // namespace android::mock -- GitLab From e4f00c5bcb34270be79b6021ffe1e0a6e880e6eb Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 31 Jan 2023 20:20:18 -0800 Subject: [PATCH 0794/1310] SF: Set eTransactionFlushNeeded after queuing a transaction Fixes a possible race where the transaction may not be queued in the binder thread before the main thread wakes up to flush the queues. Bug: 251712581 Test: scroll on 90hz device and check all transactions are applied Change-Id: I1dbfb2368179d1a101e821e821d1374cd2842965 --- services/surfaceflinger/SurfaceFlinger.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 6020aba4fd..85c7f9f937 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3921,14 +3921,18 @@ uint32_t SurfaceFlinger::getTransactionFlags() const { } uint32_t SurfaceFlinger::clearTransactionFlags(uint32_t mask) { - return mTransactionFlags.fetch_and(~mask) & mask; + uint32_t transactionFlags = mTransactionFlags.fetch_and(~mask); + ATRACE_INT("mTransactionFlags", transactionFlags); + return transactionFlags & mask; } void SurfaceFlinger::setTransactionFlags(uint32_t mask, TransactionSchedule schedule, const sp& applyToken, FrameHint frameHint) { mScheduler->modulateVsync({}, &VsyncModulator::setTransactionSchedule, schedule, applyToken); + uint32_t transactionFlags = mTransactionFlags.fetch_or(mask); + ATRACE_INT("mTransactionFlags", transactionFlags); - if (const bool scheduled = mTransactionFlags.fetch_or(mask) & mask; !scheduled) { + if (const bool scheduled = transactionFlags & mask; !scheduled) { scheduleCommit(frameHint); } else if (frameHint == FrameHint::kActive) { // Even if the next frame is already scheduled, we should reset the idle timer @@ -4243,9 +4247,8 @@ status_t SurfaceFlinger::setTransactionState( }(state.flags); const auto frameHint = state.isFrameActive() ? FrameHint::kActive : FrameHint::kNone; - setTransactionFlags(eTransactionFlushNeeded, schedule, state.applyToken, frameHint); mTransactionHandler.queueTransaction(std::move(state)); - + setTransactionFlags(eTransactionFlushNeeded, schedule, applyToken, frameHint); return NO_ERROR; } -- GitLab From fad494c0cf1c7c600965e5c65df3cc7a1667784e Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 1 Feb 2023 19:52:54 +0000 Subject: [PATCH 0795/1310] SF: Fix TrustedPresentationListener npe Fixes: 267348876 Test: atest TrustedPresentationCallbackTest Change-Id: Ib4c63ab2fdfc6c2dc67821a5e0ae06fff4e3dc40 --- services/surfaceflinger/SurfaceFlinger.cpp | 29 ++++++++++++---------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 6020aba4fd..638302b7b3 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2614,12 +2614,13 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { ATRACE_CALL(); ALOGV(__func__); - const auto* display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get(); + const auto* defaultDisplay = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get(); std::shared_ptr glCompositionDoneFenceTime; - if (display && display->getCompositionDisplay()->getState().usesClientComposition) { + if (defaultDisplay && + defaultDisplay->getCompositionDisplay()->getState().usesClientComposition) { glCompositionDoneFenceTime = - std::make_shared(display->getCompositionDisplay() + std::make_shared(defaultDisplay->getCompositionDisplay() ->getRenderSurface() ->getClientTargetAcquireFence()); } else { @@ -2628,8 +2629,9 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { mPreviousPresentFences[1] = mPreviousPresentFences[0]; - auto presentFence = - display ? getHwComposer().getPresentFence(display->getPhysicalId()) : Fence::NO_FENCE; + auto presentFence = defaultDisplay + ? getHwComposer().getPresentFence(defaultDisplay->getPhysicalId()) + : Fence::NO_FENCE; auto presentFenceTime = std::make_shared(presentFence); mPreviousPresentFences[0] = {presentFence, presentFenceTime}; @@ -2660,7 +2662,7 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { presentLatency.ns()); for (const auto& layer: mLayersWithQueuedFrames) { - layer->onPostComposition(display, glCompositionDoneFenceTime, presentFenceTime, + layer->onPostComposition(defaultDisplay, glCompositionDoneFenceTime, presentFenceTime, compositorTiming); layer->releasePendingBuffer(presentTime.ns()); } @@ -2733,7 +2735,7 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { for (const auto& [id, physicalDisplay] : mPhysicalDisplays) { if (auto displayDevice = getDisplayDeviceLocked(id); displayDevice && displayDevice->isPoweredOn() && physicalDisplay.isInternal()) { - auto presentFenceTimeI = display && display->getPhysicalId() == id + auto presentFenceTimeI = defaultDisplay && defaultDisplay->getPhysicalId() == id ? std::move(presentFenceTime) : std::make_shared(getHwComposer().getPresentFence(id)); if (presentFenceTimeI->isValid()) { @@ -2744,11 +2746,11 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { } const bool isDisplayConnected = - display && getHwComposer().isConnected(display->getPhysicalId()); + defaultDisplay && getHwComposer().isConnected(defaultDisplay->getPhysicalId()); if (!hasSyncFramework) { - if (isDisplayConnected && display->isPoweredOn()) { - mScheduler->enableHardwareVsync(display->getPhysicalId()); + if (isDisplayConnected && defaultDisplay->isPoweredOn()) { + mScheduler->enableHardwareVsync(defaultDisplay->getPhysicalId()); } } @@ -2756,7 +2758,7 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { const size_t appConnections = mScheduler->getEventThreadConnectionCount(mAppConnectionHandle); mTimeStats->recordDisplayEventConnectionCount(sfConnections + appConnections); - if (isDisplayConnected && !display->isPoweredOn()) { + if (isDisplayConnected && !defaultDisplay->isPoweredOn()) { getRenderEngine().cleanupPostRender(); return; } @@ -2795,9 +2797,10 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { if (!layer->hasTrustedPresentationListener()) { return; } - const auto display = + const std::optional displayOpt = layerStackToDisplay.get(layer->getLayerSnapshot()->outputFilter.layerStack); - layer->updateTrustedPresentationState(display->get(), layer->getLayerSnapshot(), + const DisplayDevice* display = displayOpt.value_or(nullptr); + layer->updateTrustedPresentationState(display, layer->getLayerSnapshot(), nanoseconds_to_milliseconds(callTime), false); }); } -- GitLab From b9e1cbe595b9c8d212e37e223eea0b66a2b642b0 Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Wed, 1 Feb 2023 22:44:45 +0000 Subject: [PATCH 0796/1310] servicemanager: more restrictions for isolated app Can never be too careful. Bug: 267381133 Test: servicemanager_test Change-Id: I56a5143978a25a5acf2116652dfd0b53be5c97ab --- cmds/servicemanager/ServiceManager.cpp | 26 ++++++++++++++++++-------- cmds/servicemanager/test_sm.cpp | 16 ++++++++++++++++ 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp index cc038ae0d4..d5645a3961 100644 --- a/cmds/servicemanager/ServiceManager.cpp +++ b/cmds/servicemanager/ServiceManager.cpp @@ -39,6 +39,11 @@ using ::android::internal::Stability; namespace android { +bool is_multiuser_uid_isolated(uid_t uid) { + uid_t appid = multiuser_get_app_id(uid); + return appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END; +} + #ifndef VENDORSERVICEMANAGER struct ManifestWithDescription { @@ -273,13 +278,8 @@ sp ServiceManager::tryGetService(const std::string& name, bool startIfN if (auto it = mNameToService.find(name); it != mNameToService.end()) { service = &(it->second); - if (!service->allowIsolated) { - uid_t appid = multiuser_get_app_id(ctx.uid); - bool isIsolated = appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END; - - if (isIsolated) { - return nullptr; - } + if (!service->allowIsolated && is_multiuser_uid_isolated(ctx.uid)) { + return nullptr; } out = service->binder; } @@ -425,7 +425,17 @@ Status ServiceManager::registerForNotifications( auto ctx = mAccess->getCallingContext(); if (!mAccess->canFind(ctx, name)) { - return Status::fromExceptionCode(Status::EX_SECURITY); + return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux"); + } + + // note - we could allow isolated apps to get notifications if we + // keep track of isolated callbacks and non-isolated callbacks, but + // this is done since isolated apps shouldn't access lazy services + // so we should be able to use different APIs to keep things simple. + // Here, we disallow everything, because the service might not be + // registered yet. + if (is_multiuser_uid_isolated(ctx.uid)) { + return Status::fromExceptionCode(Status::EX_SECURITY, "isolated app"); } if (!isValidServiceName(name)) { diff --git a/cmds/servicemanager/test_sm.cpp b/cmds/servicemanager/test_sm.cpp index 0fd8d8ee2a..cae32e3bc3 100644 --- a/cmds/servicemanager/test_sm.cpp +++ b/cmds/servicemanager/test_sm.cpp @@ -383,6 +383,22 @@ TEST(ServiceNotifications, NoPermissionsRegister) { sp cb = sp::make(); + EXPECT_EQ(sm->registerForNotifications("foofoo", cb).exceptionCode(), Status::EX_SECURITY); +} + +TEST(GetService, IsolatedCantRegister) { + std::unique_ptr access = std::make_unique>(); + + EXPECT_CALL(*access, getCallingContext()) + .WillOnce(Return(Access::CallingContext{ + .uid = AID_ISOLATED_START, + })); + EXPECT_CALL(*access, canFind(_, _)).WillOnce(Return(true)); + + sp sm = sp::make(std::move(access)); + + sp cb = sp::make(); + EXPECT_EQ(sm->registerForNotifications("foofoo", cb).exceptionCode(), Status::EX_SECURITY); } -- GitLab From d691322f979c8b76c54c30a15bfe40200d61d6e1 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 1 Feb 2023 22:56:21 +0000 Subject: [PATCH 0797/1310] SF: Introduce new frontend logic Changes to allow creating layer snapshots using the new and legacy frontend logic. Switching the logic is controlled by debug flags. By default SF will continue to use the legacy logic so there should be no functional changes with this cl. Bug: 238781169 Test: presubmit Change-Id: I33a4de1b8e93fbfe02ac1787d57d6e07a8c3ef26 --- .../FrontEnd/LayerSnapshotBuilder.cpp | 21 +- .../FrontEnd/LayerSnapshotBuilder.h | 7 +- services/surfaceflinger/Layer.cpp | 56 +- services/surfaceflinger/Layer.h | 39 +- services/surfaceflinger/LayerRenderArea.cpp | 9 +- services/surfaceflinger/SurfaceFlinger.cpp | 620 ++++++++++++++---- services/surfaceflinger/SurfaceFlinger.h | 75 ++- .../fuzzer/surfaceflinger_layer_fuzzer.cpp | 2 +- 8 files changed, 633 insertions(+), 196 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index 6490476396..3ed24b2740 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -391,7 +391,9 @@ bool LayerSnapshotBuilder::tryFastUpdate(const Args& args) { void LayerSnapshotBuilder::updateSnapshots(const Args& args) { ATRACE_NAME("UpdateSnapshots"); - if (args.forceUpdate || args.displayChanges) { + if (args.parentCrop) { + mRootSnapshot.geomLayerBounds = *args.parentCrop; + } else if (args.forceUpdate || args.displayChanges) { mRootSnapshot.geomLayerBounds = getMaxDisplayBounds(args.displays); } if (args.displayChanges) { @@ -618,7 +620,8 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a RequestedLayerState::Changes::AffectsChildren); snapshot.changes = parentChanges | requested.changes; snapshot.isHiddenByPolicyFromParent = parentSnapshot.isHiddenByPolicyFromParent || - parentSnapshot.invalidTransform || requested.isHiddenByPolicy(); + parentSnapshot.invalidTransform || requested.isHiddenByPolicy() || + (args.excludeLayerIds.find(path.id) != args.excludeLayerIds.end()); snapshot.contentDirty = requested.what & layer_state_t::CONTENT_DIRTY; // TODO(b/238781169) scope down the changes to only buffer updates. snapshot.hasReadyFrame = @@ -983,6 +986,20 @@ void LayerSnapshotBuilder::forEachVisibleSnapshot(const ConstVisitor& visitor) c } } +// Visit each visible snapshot in z-order +void LayerSnapshotBuilder::forEachVisibleSnapshot(const ConstVisitor& visitor, + const LayerHierarchy& root) const { + root.traverseInZOrder( + [this, visitor](const LayerHierarchy&, + const LayerHierarchy::TraversalPath& traversalPath) -> bool { + LayerSnapshot* snapshot = getSnapshot(traversalPath); + if (snapshot && snapshot->isVisible) { + visitor(*snapshot); + } + return true; + }); +} + void LayerSnapshotBuilder::forEachVisibleSnapshot(const Visitor& visitor) { for (int i = 0; i < mNumInterestingSnapshots; i++) { std::unique_ptr& snapshot = mSnapshots.at((size_t)i); diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h index abb7e668c3..f4544fd62f 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h @@ -36,7 +36,7 @@ namespace android::surfaceflinger::frontend { class LayerSnapshotBuilder { public: struct Args { - const LayerHierarchy& root; + LayerHierarchy root; const LayerLifecycleManager& layerLifecycleManager; bool forceUpdate = false; bool includeMetadata = false; @@ -46,6 +46,8 @@ public: const renderengine::ShadowSettings& globalShadowSettings; bool supportsBlur = true; bool forceFullDamage = false; + std::optional parentCrop = std::nullopt; + std::unordered_set excludeLayerIds; }; LayerSnapshotBuilder(); @@ -65,6 +67,9 @@ public: // Visit each visible snapshot in z-order void forEachVisibleSnapshot(const ConstVisitor& visitor) const; + // Visit each visible snapshot in z-order + void forEachVisibleSnapshot(const ConstVisitor& visitor, const LayerHierarchy& root) const; + typedef std::function& snapshot)> Visitor; // Visit each visible snapshot in z-order and move the snapshot if needed void forEachVisibleSnapshot(const Visitor& visitor); diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index b6b9965407..31ee91ea02 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -146,7 +146,7 @@ Layer::Layer(const LayerCreationArgs& args) mLayerCreationFlags(args.flags), mBorderEnabled(false), mTextureName(args.textureName), - mLayerFE(args.flinger->getFactory().createLayerFE(mName)) { + mLegacyLayerFE(args.flinger->getFactory().createLayerFE(mName)) { ALOGV("Creating Layer %s", getDebugName()); uint32_t layerFlags = 0; @@ -3098,15 +3098,14 @@ bool Layer::setSidebandStream(const sp& sidebandStream) { return true; } -bool Layer::setTransactionCompletedListeners(const std::vector>& handles) { +bool Layer::setTransactionCompletedListeners(const std::vector>& handles, + bool willPresent) { // If there is no handle, we will not send a callback so reset mReleasePreviousBuffer and return if (handles.empty()) { mReleasePreviousBuffer = false; return false; } - const bool willPresent = willPresentCurrentTransaction(); - for (const auto& handle : handles) { // If this transaction set a buffer on this layer, release its previous buffer handle->releasePreviousBuffer = mReleasePreviousBuffer; @@ -3180,11 +3179,10 @@ bool Layer::fenceHasSignaled() const { return fenceSignaled; } -bool Layer::onPreComposition(nsecs_t refreshStartTime) { +void Layer::onPreComposition(nsecs_t refreshStartTime) { for (const auto& handle : mDrawingState.callbackHandles) { handle->refreshStartTime = refreshStartTime; } - return hasReadyFrame(); } void Layer::setAutoRefresh(bool autoRefresh) { @@ -3570,7 +3568,7 @@ bool Layer::isHdrY410() const { sp Layer::getCompositionEngineLayerFE() const { // There's no need to get a CE Layer if the layer isn't going to draw anything. - return hasSomethingToDraw() ? mLayerFE : nullptr; + return hasSomethingToDraw() ? mLegacyLayerFE : nullptr; } const LayerSnapshot* Layer::getLayerSnapshot() const { @@ -3581,16 +3579,36 @@ LayerSnapshot* Layer::editLayerSnapshot() { return mSnapshot.get(); } +std::unique_ptr Layer::stealLayerSnapshot() { + return std::move(mSnapshot); +} + +void Layer::updateLayerSnapshot(std::unique_ptr snapshot) { + mSnapshot = std::move(snapshot); +} + const compositionengine::LayerFECompositionState* Layer::getCompositionState() const { return mSnapshot.get(); } sp Layer::copyCompositionEngineLayerFE() const { - auto result = mFlinger->getFactory().createLayerFE(mLayerFE->getDebugName()); + auto result = mFlinger->getFactory().createLayerFE(mName); result->mSnapshot = std::make_unique(*mSnapshot); return result; } +sp Layer::getCompositionEngineLayerFE( + const frontend::LayerHierarchy::TraversalPath& path) { + for (auto& [p, layerFE] : mLayerFEs) { + if (p == path) { + return layerFE; + } + } + auto layerFE = mFlinger->getFactory().createLayerFE(mName); + mLayerFEs.emplace_back(path, layerFE); + return layerFE; +} + void Layer::useSurfaceDamage() { if (mFlinger->mForceFullDamage) { surfaceDamageRegion = Region::INVALID_REGION; @@ -3986,28 +4004,6 @@ void Layer::updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMet } } -LayerSnapshotGuard::LayerSnapshotGuard(Layer* layer) : mLayer(layer) { - if (mLayer) { - mLayer->mLayerFE->mSnapshot = std::move(mLayer->mSnapshot); - } -} - -LayerSnapshotGuard::~LayerSnapshotGuard() { - if (mLayer) { - mLayer->mSnapshot = std::move(mLayer->mLayerFE->mSnapshot); - } -} - -LayerSnapshotGuard::LayerSnapshotGuard(LayerSnapshotGuard&& other) : mLayer(other.mLayer) { - other.mLayer = nullptr; -} - -LayerSnapshotGuard& LayerSnapshotGuard::operator=(LayerSnapshotGuard&& other) { - mLayer = other.mLayer; - other.mLayer = nullptr; - return *this; -} - void Layer::setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds, TrustedPresentationListener const& listener) { bool hadTrustedPresentationListener = hasTrustedPresentationListener(); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index f858224281..3d4f03f6ee 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -307,7 +307,8 @@ public: bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/); bool setApi(int32_t /*api*/); bool setSidebandStream(const sp& /*sidebandStream*/); - bool setTransactionCompletedListeners(const std::vector>& /*handles*/); + bool setTransactionCompletedListeners(const std::vector>& /*handles*/, + bool willPresent); virtual bool setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace); virtual bool setColorSpaceAgnostic(const bool agnostic); virtual bool setDimmingEnabled(const bool dimmingEnabled); @@ -328,9 +329,12 @@ public: virtual sp getCompositionEngineLayerFE() const; virtual sp copyCompositionEngineLayerFE() const; + sp getCompositionEngineLayerFE(const frontend::LayerHierarchy::TraversalPath&); const frontend::LayerSnapshot* getLayerSnapshot() const; frontend::LayerSnapshot* editLayerSnapshot(); + std::unique_ptr stealLayerSnapshot(); + void updateLayerSnapshot(std::unique_ptr snapshot); // If we have received a new buffer this frame, we will pass its surface // damage down to hardware composer. Otherwise, we must send a region with @@ -512,7 +516,7 @@ public: // implements compositionengine::LayerFE const compositionengine::LayerFECompositionState* getCompositionState() const; bool fenceHasSignaled() const; - bool onPreComposition(nsecs_t refreshStartTime); + void onPreComposition(nsecs_t refreshStartTime); void onLayerDisplayed(ftl::SharedFuture); void setWasClientComposed(const sp& fence) { @@ -832,6 +836,7 @@ public: void updateMetadataSnapshot(const LayerMetadata& parentMetadata); void updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata, std::unordered_set& visited); + bool willPresentCurrentTransaction() const; protected: // For unit tests @@ -1037,8 +1042,6 @@ private: // Crop that applies to the buffer Rect computeBufferCrop(const State& s); - bool willPresentCurrentTransaction() const; - void callReleaseBufferCallback(const sp& listener, const sp& buffer, uint64_t framenumber, const sp& releaseFence, @@ -1146,34 +1149,10 @@ private: // not specify a destination frame. ui::Transform mRequestedTransform; - sp mLayerFE; + sp mLegacyLayerFE; + std::vector>> mLayerFEs; std::unique_ptr mSnapshot = std::make_unique(); - - friend class LayerSnapshotGuard; -}; - -// LayerSnapshotGuard manages the movement of LayerSnapshot between a Layer and its corresponding -// LayerFE. This class must be used whenever LayerFEs are passed to CompositionEngine. Instances of -// LayerSnapshotGuard should only be constructed on the main thread and should not be moved outside -// the main thread. -// -// Moving the snapshot instead of sharing common state prevents use of LayerFE outside the main -// thread by making errors obvious (i.e. use outside the main thread results in SEGFAULTs due to -// nullptr dereference). -class LayerSnapshotGuard { -public: - LayerSnapshotGuard(Layer* layer) REQUIRES(kMainThreadContext); - ~LayerSnapshotGuard() REQUIRES(kMainThreadContext); - - LayerSnapshotGuard(const LayerSnapshotGuard&) = delete; - LayerSnapshotGuard& operator=(const LayerSnapshotGuard&) = delete; - - LayerSnapshotGuard(LayerSnapshotGuard&& other) REQUIRES(kMainThreadContext); - LayerSnapshotGuard& operator=(LayerSnapshotGuard&& other) REQUIRES(kMainThreadContext); - -private: - Layer* mLayer; }; std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate); diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp index 2b4375b0fa..03a7f226ed 100644 --- a/services/surfaceflinger/LayerRenderArea.cpp +++ b/services/surfaceflinger/LayerRenderArea.cpp @@ -69,6 +69,14 @@ Rect LayerRenderArea::getSourceCrop() const { void LayerRenderArea::render(std::function drawLayers) { using namespace std::string_literals; + if (!mChildrenOnly) { + mTransform = mLayer->getTransform().inverse(); + } + + if (mFlinger.mLayerLifecycleManagerEnabled) { + drawLayers(); + return; + } // If layer is offscreen, update mirroring info if it exists if (mLayer->isRemovedFromCurrentState()) { mLayer->traverse(LayerVector::StateSet::Drawing, @@ -78,7 +86,6 @@ void LayerRenderArea::render(std::function drawLayers) { } if (!mChildrenOnly) { - mTransform = mLayer->getTransform().inverse(); // If the layer is offscreen, compute bounds since we don't compute bounds for offscreen // layers in a regular cycles. if (mLayer->isRemovedFromCurrentState()) { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 6020aba4fd..c10ef94dbb 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -173,6 +173,8 @@ using namespace std::string_view_literals; using namespace hardware::configstore; using namespace hardware::configstore::V1_0; using namespace sysprop; +using ftl::Flags; +using namespace ftl::flag_operators; using aidl::android::hardware::graphics::common::DisplayDecorationSupport; using aidl::android::hardware::graphics::composer3::Capability; @@ -470,6 +472,10 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI mPowerHintSessionMode = {.late = base::GetBoolProperty("debug.sf.send_late_power_session_hint"s, true), .early = base::GetBoolProperty("debug.sf.send_early_power_session_hint"s, false)}; + mLayerLifecycleManagerEnabled = + base::GetBoolProperty("debug.sf.enable_layer_lifecycle_manager"s, false); + mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled || + base::GetBoolProperty("debug.sf.enable_legacy_frontend"s, true); } LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() { @@ -2136,6 +2142,110 @@ void SurfaceFlinger::configure() FTL_FAKE_GUARD(kMainThreadContext) { } } +bool SurfaceFlinger::updateLayerSnapshotsLegacy(VsyncId vsyncId, LifecycleUpdate& update, + bool transactionsFlushed, + bool& outTransactionsAreEmpty) { + bool needsTraversal = false; + if (transactionsFlushed) { + needsTraversal |= commitMirrorDisplays(vsyncId); + needsTraversal |= commitCreatedLayers(vsyncId, update.layerCreatedStates); + needsTraversal |= applyTransactions(update.transactions, vsyncId); + } + outTransactionsAreEmpty = !needsTraversal; + const bool shouldCommit = (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal; + if (shouldCommit) { + commitTransactions(); + } + + bool mustComposite = latchBuffers() || shouldCommit; + updateLayerGeometry(); + return mustComposite; +} + +bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, LifecycleUpdate& update, + bool transactionsFlushed, bool& outTransactionsAreEmpty) { + using Changes = frontend::RequestedLayerState::Changes; + ATRACE_NAME("updateLayerSnapshots"); + { + mLayerLifecycleManager.addLayers(std::move(update.newLayers)); + mLayerLifecycleManager.applyTransactions(update.transactions); + mLayerLifecycleManager.onHandlesDestroyed(update.destroyedHandles); + for (auto& legacyLayer : update.layerCreatedStates) { + sp layer = legacyLayer.layer.promote(); + if (layer) { + mLegacyLayers[layer->sequence] = layer; + } + } + } + if (mLayerLifecycleManager.getGlobalChanges().test(Changes::Hierarchy)) { + ATRACE_NAME("LayerHierarchyBuilder:update"); + mLayerHierarchyBuilder.update(mLayerLifecycleManager.getLayers(), + mLayerLifecycleManager.getDestroyedLayers()); + } + + applyAndCommitDisplayTransactionStates(update.transactions); + + { + ATRACE_NAME("LayerSnapshotBuilder:update"); + frontend::LayerSnapshotBuilder::Args args{.root = mLayerHierarchyBuilder.getHierarchy(), + .layerLifecycleManager = mLayerLifecycleManager, + .displays = mFrontEndDisplayInfos, + .displayChanges = mFrontEndDisplayInfosChanged, + .globalShadowSettings = + mDrawingState.globalShadowSettings, + .supportsBlur = mSupportsBlur, + .forceFullDamage = mForceFullDamage}; + mLayerSnapshotBuilder.update(args); + } + + if (mLayerLifecycleManager.getGlobalChanges().any(Changes::Geometry | Changes::Input | + Changes::Hierarchy)) { + mUpdateInputInfo = true; + } + if (mLayerLifecycleManager.getGlobalChanges().any(Changes::VisibleRegion | Changes::Hierarchy | + Changes::Visibility)) { + mVisibleRegionsDirty = true; + } + outTransactionsAreEmpty = mLayerLifecycleManager.getGlobalChanges().get() == 0; + const bool mustComposite = mLayerLifecycleManager.getGlobalChanges().get() != 0; + { + ATRACE_NAME("LLM:commitChanges"); + mLayerLifecycleManager.commitChanges(); + } + + if (!mLegacyFrontEndEnabled) { + ATRACE_NAME("DisplayCallbackAndStatsUpdates"); + applyTransactions(update.transactions, vsyncId); + + bool newDataLatched = false; + for (auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) { + if (!snapshot->changes.test(Changes::Buffer)) continue; + auto it = mLegacyLayers.find(snapshot->sequence); + LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s", + snapshot->getDebugString().c_str()); + mLayersWithQueuedFrames.emplace(it->second); + newDataLatched = true; + if (!snapshot->isVisible) break; + + Region visibleReg; + visibleReg.set(snapshot->transformedBoundsWithoutTransparentRegion); + invalidateLayerStack(snapshot->outputFilter, visibleReg); + } + + for (auto& destroyedLayer : mLayerLifecycleManager.getDestroyedLayers()) { + mLegacyLayers.erase(destroyedLayer->id); + } + + // enter boot animation on first buffer latch + if (CC_UNLIKELY(mBootStage == BootStage::BOOTLOADER && newDataLatched)) { + ALOGI("Enter boot animation"); + mBootStage = BootStage::BOOTANIMATION; + } + commitTransactions(); + } + return mustComposite; +} + bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expectedVsyncTime) FTL_FAKE_GUARD(kMainThreadContext) { // The expectedVsyncTime, which was predicted when this frame was scheduled, is normally in the @@ -2275,45 +2385,34 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe mFrameTimeline->setSfWakeUp(vsyncId.value, frameTime.ns(), Fps::fromPeriodNsecs(vsyncPeriod.ns())); - bool needsTraversal = false; - if (clearTransactionFlags(eTransactionFlushNeeded)) { - // Locking: - // 1. to prevent onHandleDestroyed from being called while the state lock is held, - // we must keep a copy of the transactions (specifically the composer - // states) around outside the scope of the lock. - // 2. Transactions and created layers do not share a lock. To prevent applying - // transactions with layers still in the createdLayer queue, flush the transactions - // before committing the created layers. - std::vector transactions = mTransactionHandler.flushTransactions(); - needsTraversal |= commitMirrorDisplays(vsyncId); - needsTraversal |= commitCreatedLayers(vsyncId); - needsTraversal |= applyTransactions(transactions, vsyncId); + const bool flushTransactions = clearTransactionFlags(eTransactionFlushNeeded); + LifecycleUpdate updates; + if (flushTransactions) { + updates = flushLifecycleUpdates(); } - - const bool shouldCommit = - (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal; - if (shouldCommit) { - commitTransactions(); + bool transactionsAreEmpty; + if (mLegacyFrontEndEnabled) { + mustComposite |= updateLayerSnapshotsLegacy(vsyncId, updates, flushTransactions, + transactionsAreEmpty); + } + if (mLayerLifecycleManagerEnabled) { + mustComposite |= + updateLayerSnapshots(vsyncId, updates, flushTransactions, transactionsAreEmpty); } if (transactionFlushNeeded()) { setTransactionFlags(eTransactionFlushNeeded); } - mustComposite |= shouldCommit; - mustComposite |= latchBuffers(); - // This has to be called after latchBuffers because we want to include the layers that have // been latched in the commit callback - if (!needsTraversal) { + if (transactionsAreEmpty) { // Invoke empty transaction callbacks early. mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */); } else { // Invoke OnCommit callbacks. mTransactionCallbackInvoker.sendCallbacks(true /* onCommitOnly */); } - - updateLayerGeometry(); } // Layers need to get updated (in the previous line) before we can use them for @@ -2400,15 +2499,6 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty; refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty; - std::vector layers; - - mDrawingState.traverseInZOrder([&refreshArgs, &layers](Layer* layer) { - if (auto layerFE = layer->getCompositionEngineLayerFE()) { - layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame); - refreshArgs.layers.push_back(layerFE); - layers.push_back(layer); - } - }); refreshArgs.internalDisplayRotationFlags = DisplayDevice::getPrimaryDisplayRotationFlags(); if (CC_UNLIKELY(mDrawingState.colorMatrixChanged)) { @@ -2435,17 +2525,13 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) // the scheduler. const auto presentTime = systemTime(); - { - std::vector layerSnapshotGuards; - for (Layer* layer : layers) { - layerSnapshotGuards.emplace_back(layer); - } - mCompositionEngine->present(refreshArgs); - } + std::vector> layers = + moveSnapshotsToCompositionArgs(refreshArgs, /*cursorOnly=*/false, vsyncId.value); + mCompositionEngine->present(refreshArgs); + moveSnapshotsFromCompositionArgs(refreshArgs, layers); - for (auto& layer : layers) { - CompositionResult compositionResult{ - layer->getCompositionEngineLayerFE()->stealCompositionResult()}; + for (auto [layer, layerFE] : layers) { + CompositionResult compositionResult{layerFE->stealCompositionResult()}; layer->onPreComposition(compositionResult.refreshStartTime); for (auto releaseFence : compositionResult.releaseFences) { layer->onLayerDisplayed(releaseFence); @@ -2539,7 +2625,7 @@ void SurfaceFlinger::updateLayerGeometry() { for (auto& layer : mLayersPendingRefresh) { Region visibleReg; visibleReg.set(layer->getScreenBounds()); - invalidateLayerStack(layer, visibleReg); + invalidateLayerStack(layer->getOutputFilter(), visibleReg); } mLayersPendingRefresh.clear(); } @@ -3397,7 +3483,8 @@ void SurfaceFlinger::processDisplayChangesLocked() { void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { // Commit display transactions. const bool displayTransactionNeeded = transactionFlags & eDisplayTransactionNeeded; - if (displayTransactionNeeded) { + mFrontEndDisplayInfosChanged = displayTransactionNeeded; + if (displayTransactionNeeded && !mLayerLifecycleManagerEnabled) { processDisplayChangesLocked(); mFrontEndDisplayInfos.clear(); for (const auto& [_, display] : mDisplays) { @@ -3488,7 +3575,7 @@ void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { // this layer is not visible anymore Region visibleReg; visibleReg.set(layer->getScreenBounds()); - invalidateLayerStack(sp::fromExisting(layer), visibleReg); + invalidateLayerStack(layer->getOutputFilter(), visibleReg); } }); } @@ -3576,16 +3663,23 @@ void SurfaceFlinger::buildWindowInfos(std::vector& outWindowInfos, outWindowInfos.reserve(sNumWindowInfos); sNumWindowInfos = 0; - mDrawingState.traverseInReverseZOrder([&](Layer* layer) { - if (!layer->needsInputInfo()) return; - - const auto opt = mFrontEndDisplayInfos.get(layer->getLayerStack()) - .transform([](const frontend::DisplayInfo& info) { - return Layer::InputDisplayArgs{&info.transform, info.isSecure}; - }); + if (mLayerLifecycleManagerEnabled) { + mLayerSnapshotBuilder.forEachInputSnapshot( + [&outWindowInfos](const frontend::LayerSnapshot& snapshot) { + outWindowInfos.push_back(snapshot.inputInfo); + }); + } else { + mDrawingState.traverseInReverseZOrder([&](Layer* layer) { + if (!layer->needsInputInfo()) return; + const auto opt = + mFrontEndDisplayInfos.get(layer->getLayerStack()) + .transform([](const frontend::DisplayInfo& info) { + return Layer::InputDisplayArgs{&info.transform, info.isSecure}; + }); - outWindowInfos.push_back(layer->fillInputInfo(opt.value_or(Layer::InputDisplayArgs{}))); - }); + outWindowInfos.push_back(layer->fillInputInfo(opt.value_or(Layer::InputDisplayArgs{}))); + }); + } sNumWindowInfos = outWindowInfos.size(); @@ -3602,17 +3696,9 @@ void SurfaceFlinger::updateCursorAsync() { refreshArgs.outputs.push_back(display->getCompositionDisplay()); } } - - std::vector layerSnapshotGuards; - mDrawingState.traverse([&layerSnapshotGuards](Layer* layer) { - if (layer->getLayerSnapshot()->compositionType == - aidl::android::hardware::graphics::composer3::Composition::CURSOR) { - layer->updateSnapshot(false /* updateGeometry */); - layerSnapshotGuards.emplace_back(layer); - } - }); - + auto layers = moveSnapshotsToCompositionArgs(refreshArgs, /*cursorOnly=*/true, 0); mCompositionEngine->updateCursorAsync(refreshArgs); + moveSnapshotsFromCompositionArgs(refreshArgs, layers); } void SurfaceFlinger::requestDisplayModes(std::vector modeRequests) { @@ -3787,10 +3873,10 @@ void SurfaceFlinger::commitOffscreenLayers() { } } -void SurfaceFlinger::invalidateLayerStack(const sp& layer, const Region& dirty) { +void SurfaceFlinger::invalidateLayerStack(const ui::LayerFilter& layerFilter, const Region& dirty) { for (const auto& [token, displayDevice] : FTL_FAKE_GUARD(mStateLock, mDisplays)) { auto display = displayDevice->getCompositionDisplay(); - if (display->includesLayer(layer->getOutputFilter())) { + if (display->includesLayer(layerFilter)) { display->editState().dirtyRegion.orSelf(dirty); } } @@ -3910,6 +3996,7 @@ status_t SurfaceFlinger::addClientLayer(const LayerCreationArgs& args, const sp< { std::scoped_lock lock(mCreatedLayersLock); mCreatedLayers.emplace_back(layer, parent, args.addToRoot); + mNewLayers.emplace_back(std::make_unique(args)); } setTransactionFlags(eTransactionNeeded); @@ -4260,9 +4347,11 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin const std::vector& listenerCallbacks, int originPid, int originUid, uint64_t transactionId) { uint32_t transactionFlags = 0; - for (DisplayState& display : displays) { - display.sanitize(permissions); - transactionFlags |= setDisplayStateLocked(display); + if (!mLayerLifecycleManagerEnabled) { + for (DisplayState& display : displays) { + display.sanitize(permissions); + transactionFlags |= setDisplayStateLocked(display); + } } // start and end registration for listeners w/ no surface so they can get their callback. Note @@ -4274,9 +4363,16 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin uint32_t clientStateFlags = 0; for (auto& resolvedState : states) { - clientStateFlags |= - setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime, - isAutoTimestamp, postTime, permissions, transactionId); + if (mLegacyFrontEndEnabled) { + clientStateFlags |= + setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime, + isAutoTimestamp, postTime, permissions, transactionId); + + } else /*mLayerLifecycleManagerEnabled*/ { + clientStateFlags |= updateLayerCallbacksAndStats(frameTimelineInfo, resolvedState, + desiredPresentTime, isAutoTimestamp, + postTime, permissions, transactionId); + } if ((flags & eAnimation) && resolvedState.state.surface) { if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) { using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType; @@ -4309,8 +4405,8 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin bool needsTraversal = false; if (transactionFlags) { - // We are on the main thread, we are about to preform a traversal. Clear the traversal bit - // so we don't have to wake up again next frame to preform an unnecessary traversal. + // We are on the main thread, we are about to perform a traversal. Clear the traversal bit + // so we don't have to wake up again next frame to perform an unnecessary traversal. if (transactionFlags & eTraversalNeeded) { transactionFlags = transactionFlags & (~eTraversalNeeded); needsTraversal = true; @@ -4323,6 +4419,42 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin return needsTraversal; } +bool SurfaceFlinger::applyAndCommitDisplayTransactionStates( + std::vector& transactions) { + Mutex::Autolock _l(mStateLock); + bool needsTraversal = false; + uint32_t transactionFlags = 0; + for (auto& transaction : transactions) { + for (DisplayState& display : transaction.displays) { + display.sanitize(transaction.permissions); + transactionFlags |= setDisplayStateLocked(display); + } + } + + if (transactionFlags) { + // We are on the main thread, we are about to perform a traversal. Clear the traversal bit + // so we don't have to wake up again next frame to perform an unnecessary traversal. + if (transactionFlags & eTraversalNeeded) { + transactionFlags = transactionFlags & (~eTraversalNeeded); + needsTraversal = true; + } + if (transactionFlags) { + setTransactionFlags(transactionFlags); + } + } + + mFrontEndDisplayInfosChanged = mTransactionFlags & eDisplayTransactionNeeded; + if (mFrontEndDisplayInfosChanged && !mLegacyFrontEndEnabled) { + processDisplayChangesLocked(); + mFrontEndDisplayInfos.clear(); + for (const auto& [_, display] : mDisplays) { + mFrontEndDisplayInfos.try_emplace(display->getLayerStack(), display->getFrontEndInfo()); + } + } + + return needsTraversal; +} + uint32_t SurfaceFlinger::setDisplayStateLocked(const DisplayState& s) { const ssize_t index = mCurrentState.displays.indexOfKey(s.token); if (index < 0) return 0; @@ -4704,7 +4836,11 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime s.trustedPresentationListener); } - if (layer->setTransactionCompletedListeners(callbackHandles)) flags |= eTraversalNeeded; + if (layer->setTransactionCompletedListeners(callbackHandles, + layer->willPresentCurrentTransaction())) { + flags |= eTraversalNeeded; + } + // Do not put anything that updates layer state or modifies flags after // setTransactionCompletedListener @@ -4717,6 +4853,94 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime return flags; } +uint32_t SurfaceFlinger::updateLayerCallbacksAndStats(const FrameTimelineInfo& frameTimelineInfo, + ResolvedComposerState& composerState, + int64_t desiredPresentTime, + bool isAutoTimestamp, int64_t postTime, + uint32_t permissions, + uint64_t transactionId) { + layer_state_t& s = composerState.state; + s.sanitize(permissions); + const nsecs_t latchTime = systemTime(); + bool unused; + + std::vector filteredListeners; + for (auto& listener : s.listeners) { + // Starts a registration but separates the callback ids according to callback type. This + // allows the callback invoker to send on latch callbacks earlier. + // note that startRegistration will not re-register if the listener has + // already be registered for a prior surface control + + ListenerCallbacks onCommitCallbacks = listener.filter(CallbackId::Type::ON_COMMIT); + if (!onCommitCallbacks.callbackIds.empty()) { + filteredListeners.push_back(onCommitCallbacks); + } + + ListenerCallbacks onCompleteCallbacks = listener.filter(CallbackId::Type::ON_COMPLETE); + if (!onCompleteCallbacks.callbackIds.empty()) { + filteredListeners.push_back(onCompleteCallbacks); + } + } + + const uint64_t what = s.what; + uint32_t flags = 0; + sp layer = nullptr; + if (s.surface) { + layer = LayerHandle::getLayer(s.surface); + } else { + // The client may provide us a null handle. Treat it as if the layer was removed. + ALOGW("Attempt to set client state with a null layer handle"); + } + if (layer == nullptr) { + for (auto& [listener, callbackIds] : s.listeners) { + mTransactionCallbackInvoker.registerUnpresentedCallbackHandle( + sp::make(listener, callbackIds, s.surface)); + } + return 0; + } + if (what & layer_state_t::eProducerDisconnect) { + layer->onDisconnect(); + } + std::optional dequeueBufferTimestamp; + if (what & layer_state_t::eMetadataChanged) { + dequeueBufferTimestamp = s.metadata.getInt64(gui::METADATA_DEQUEUE_TIME); + } + + std::vector> callbackHandles; + if ((what & layer_state_t::eHasListenerCallbacksChanged) && (!filteredListeners.empty())) { + for (auto& [listener, callbackIds] : filteredListeners) { + callbackHandles.emplace_back( + sp::make(listener, callbackIds, s.surface)); + } + } + if (what & layer_state_t::eSidebandStreamChanged) { + if (layer->setSidebandStream(s.sidebandStream)) flags |= eTraversalNeeded; + } + if (what & layer_state_t::eBufferChanged) { + if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime, + desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp, + frameTimelineInfo)) { + layer->latchBuffer(unused, latchTime); + flags |= eTraversalNeeded; + } + mLayersWithQueuedFrames.emplace(layer); + } else if (frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) { + layer->setFrameTimelineVsyncForBufferlessTransaction(frameTimelineInfo, postTime); + } + + if (what & layer_state_t::eTrustedPresentationInfoChanged) { + layer->setTrustedPresentationInfo(s.trustedPresentationThresholds, + s.trustedPresentationListener); + } + + const auto& snapshot = mLayerSnapshotBuilder.getSnapshot(layer->getSequence()); + bool willPresentCurrentTransaction = + snapshot && (snapshot->hasReadyFrame || snapshot->sidebandStreamHasFrame); + if (layer->setTransactionCompletedListeners(callbackHandles, willPresentCurrentTransaction)) + flags |= eTraversalNeeded; + return flags; +} + uint32_t SurfaceFlinger::addInputWindowCommands(const InputWindowCommands& inputWindowCommands) { bool hasChanges = mInputWindowCommands.merge(inputWindowCommands); return hasChanges ? eTraversalNeeded : 0; @@ -4785,6 +5009,7 @@ status_t SurfaceFlinger::mirrorDisplay(DisplayId displayId, const LayerCreationA LayerCreationArgs mirrorArgs(args); mirrorArgs.flags |= ISurfaceComposerClient::eNoColorFill; mirrorArgs.addToRoot = true; + mirrorArgs.layerStackToMirror = layerStack; result = createEffectLayer(mirrorArgs, &outResult.handle, &rootMirrorLayer); outResult.layerId = rootMirrorLayer->sequence; outResult.layerName = String16(rootMirrorLayer->getDebugName()); @@ -4887,7 +5112,12 @@ void SurfaceFlinger::markLayerPendingRemovalLocked(const sp& layer) { setTransactionFlags(eTransactionNeeded); } -void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp& layer, uint32_t /* layerId */) { +void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp& layer, uint32_t layerId) { + { + std::scoped_lock lock(mCreatedLayersLock); + mDestroyedHandles.emplace_back(layerId); + } + Mutex::Autolock lock(mStateLock); markLayerPendingRemovalLocked(layer); mBufferCountTracker.remove(handle); @@ -4895,6 +5125,8 @@ void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp& layer, uint32 if (mTransactionTracing) { mTransactionTracing->onHandleRemoved(handle); } + + setTransactionFlags(eTransactionFlushNeeded); } void SurfaceFlinger::onInitializeDisplays() { @@ -6458,10 +6690,15 @@ status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, args.useIdentityTransform, args.captureSecureLayers); }); - auto traverseLayers = [this, args, layerStack](const LayerVector::Visitor& visitor) { - traverseLayersInLayerStack(layerStack, args.uid, visitor); - }; - auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + GetLayerSnapshotsFunction getLayerSnapshots; + if (mLayerLifecycleManagerEnabled) { + getLayerSnapshots = getLayerSnapshotsForScreenshots(layerStack, args.uid); + } else { + auto traverseLayers = [this, args, layerStack](const LayerVector::Visitor& visitor) { + traverseLayersInLayerStack(layerStack, args.uid, visitor); + }; + getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + } auto future = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize, args.pixelFormat, args.allowProtected, args.grayscale, @@ -6495,10 +6732,15 @@ status_t SurfaceFlinger::captureDisplay(DisplayId displayId, false /* captureSecureLayers */); }); - auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) { - traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, visitor); - }; - auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + GetLayerSnapshotsFunction getLayerSnapshots; + if (mLayerLifecycleManagerEnabled) { + getLayerSnapshots = getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID); + } else { + auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) { + traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, visitor); + }; + getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + } if (captureListener == nullptr) { ALOGE("capture screen must provide a capture listener callback"); @@ -6593,29 +6835,37 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, return std::make_unique(*this, parent, crop, reqSize, dataspace, childrenOnly, args.captureSecureLayers); }); - - auto traverseLayers = [parent, args, excludeLayerIds](const LayerVector::Visitor& visitor) { - parent->traverseChildrenInZOrder(LayerVector::StateSet::Drawing, [&](Layer* layer) { - if (!layer->isVisible()) { - return; - } else if (args.childrenOnly && layer == parent.get()) { - return; - } else if (args.uid != CaptureArgs::UNSET_UID && args.uid != layer->getOwnerUid()) { - return; - } - - auto p = sp::fromExisting(layer); - while (p != nullptr) { - if (excludeLayerIds.count(p->sequence) != 0) { + GetLayerSnapshotsFunction getLayerSnapshots; + if (mLayerLifecycleManagerEnabled) { + FloatRect parentCrop = crop.isEmpty() ? FloatRect(0, 0, reqSize.width, reqSize.height) + : crop.toFloatRect(); + getLayerSnapshots = getLayerSnapshotsForScreenshots(parent->sequence, args.uid, + std::move(excludeLayerIds), + args.childrenOnly, parentCrop); + } else { + auto traverseLayers = [parent, args, excludeLayerIds](const LayerVector::Visitor& visitor) { + parent->traverseChildrenInZOrder(LayerVector::StateSet::Drawing, [&](Layer* layer) { + if (!layer->isVisible()) { + return; + } else if (args.childrenOnly && layer == parent.get()) { + return; + } else if (args.uid != CaptureArgs::UNSET_UID && args.uid != layer->getOwnerUid()) { return; } - p = p->getParent(); - } - visitor(layer); - }); - }; - auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + auto p = sp::fromExisting(layer); + while (p != nullptr) { + if (excludeLayerIds.count(p->sequence) != 0) { + return; + } + p = p->getParent(); + } + + visitor(layer); + }); + }; + getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + } if (captureListener == nullptr) { ALOGE("capture screen must provide a capture listener callback"); @@ -7397,24 +7647,18 @@ bool SurfaceFlinger::commitMirrorDisplays(VsyncId vsyncId) { return true; } -bool SurfaceFlinger::commitCreatedLayers(VsyncId vsyncId) { - std::vector createdLayers; - { - std::scoped_lock lock(mCreatedLayersLock); - createdLayers = std::move(mCreatedLayers); - mCreatedLayers.clear(); - if (createdLayers.size() == 0) { - return false; - } +bool SurfaceFlinger::commitCreatedLayers(VsyncId vsyncId, + std::vector& createdLayers) { + if (createdLayers.size() == 0) { + return false; } Mutex::Autolock _l(mStateLock); for (const auto& createdLayer : createdLayers) { handleLayerCreatedLocked(createdLayer, vsyncId); } - createdLayers.clear(); mLayersAdded = true; - return true; + return mLayersAdded; } void SurfaceFlinger::updateLayerMetadataSnapshot() { @@ -7442,6 +7686,150 @@ void SurfaceFlinger::updateLayerMetadataSnapshot() { }); } +void SurfaceFlinger::moveSnapshotsFromCompositionArgs( + compositionengine::CompositionRefreshArgs& refreshArgs, + std::vector>& layers) { + if (mLayerLifecycleManagerEnabled) { + std::vector>& snapshots = + mLayerSnapshotBuilder.getSnapshots(); + for (auto [_, layerFE] : layers) { + auto i = layerFE->mSnapshot->globalZ; + snapshots[i] = std::move(layerFE->mSnapshot); + } + } + if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) { + for (auto [layer, layerFE] : layers) { + layer->updateLayerSnapshot(std::move(layerFE->mSnapshot)); + } + } +} + +std::vector> SurfaceFlinger::moveSnapshotsToCompositionArgs( + compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly, int64_t vsyncId) { + std::vector> layers; + if (mLayerLifecycleManagerEnabled) { + mLayerSnapshotBuilder.forEachVisibleSnapshot( + [&](std::unique_ptr& snapshot) { + if (cursorOnly && + snapshot->compositionType != + aidl::android::hardware::graphics::composer3::Composition::CURSOR) { + return; + } + + if (!snapshot->hasSomethingToDraw()) { + return; + } + + auto it = mLegacyLayers.find(snapshot->sequence); + LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), + "Couldnt find layer object for %s", + snapshot->getDebugString().c_str()); + auto& legacyLayer = it->second; + sp layerFE = legacyLayer->getCompositionEngineLayerFE(snapshot->path); + layerFE->mSnapshot = std::move(snapshot); + refreshArgs.layers.push_back(layerFE); + layers.emplace_back(legacyLayer.get(), layerFE.get()); + }); + } + if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) { + mDrawingState.traverseInZOrder([&refreshArgs, cursorOnly, &layers](Layer* layer) { + if (auto layerFE = layer->getCompositionEngineLayerFE()) { + if (cursorOnly && + layer->getLayerSnapshot()->compositionType != + aidl::android::hardware::graphics::composer3::Composition::CURSOR) + return; + layer->updateSnapshot(/* refreshArgs.updatingGeometryThisFrame */ true); + layerFE->mSnapshot = layer->stealLayerSnapshot(); + refreshArgs.layers.push_back(layerFE); + layers.emplace_back(layer, layerFE.get()); + } + }); + } + + return layers; +} + +std::function>>()> +SurfaceFlinger::getLayerSnapshotsForScreenshots(std::optional layerStack, + uint32_t uid) { + return [this, layerStack, uid]() { + std::vector>> layers; + for (auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) { + if (layerStack && snapshot->outputFilter.layerStack != *layerStack) { + continue; + } + if (uid != CaptureArgs::UNSET_UID && snapshot->inputInfo.ownerUid != uid) { + continue; + } + if (!snapshot->isVisible || !snapshot->hasSomethingToDraw()) { + continue; + } + + auto it = mLegacyLayers.find(snapshot->sequence); + LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s", + snapshot->getDebugString().c_str()); + auto& legacyLayer = it->second; + sp layerFE = getFactory().createLayerFE(legacyLayer->getName()); + layerFE->mSnapshot = std::make_unique(*snapshot); + layers.emplace_back(legacyLayer.get(), std::move(layerFE)); + } + + return layers; + }; +} + +std::function>>()> +SurfaceFlinger::getLayerSnapshotsForScreenshots(uint32_t rootLayerId, uint32_t uid, + std::unordered_set excludeLayerIds, + bool childrenOnly, const FloatRect& parentCrop) { + return [this, excludeLayerIds = std::move(excludeLayerIds), uid, rootLayerId, childrenOnly, + parentCrop]() { + frontend::LayerSnapshotBuilder::Args + args{.root = mLayerHierarchyBuilder.getPartialHierarchy(rootLayerId, childrenOnly), + .layerLifecycleManager = mLayerLifecycleManager, + .displays = mFrontEndDisplayInfos, + .displayChanges = true, + .globalShadowSettings = mDrawingState.globalShadowSettings, + .supportsBlur = mSupportsBlur, + .forceFullDamage = mForceFullDamage, + .parentCrop = {parentCrop}, + .excludeLayerIds = std::move(excludeLayerIds)}; + mLayerSnapshotBuilder.update(args); + + auto getLayerSnapshotsFn = getLayerSnapshotsForScreenshots({}, uid); + std::vector>> layers = getLayerSnapshotsFn(); + args.root = mLayerHierarchyBuilder.getHierarchy(); + args.parentCrop.reset(); + args.excludeLayerIds.clear(); + mLayerSnapshotBuilder.update(args); + return layers; + }; +} + +SurfaceFlinger::LifecycleUpdate SurfaceFlinger::flushLifecycleUpdates() { + LifecycleUpdate update; + ATRACE_NAME("TransactionHandler:flushTransactions"); + // Locking: + // 1. to prevent onHandleDestroyed from being called while the state lock is held, + // we must keep a copy of the transactions (specifically the composer + // states) around outside the scope of the lock. + // 2. Transactions and created layers do not share a lock. To prevent applying + // transactions with layers still in the createdLayer queue, flush the transactions + // before committing the created layers. + update.transactions = mTransactionHandler.flushTransactions(); + { + // TODO(b/238781169) lockless queue this and keep order. + std::scoped_lock lock(mCreatedLayersLock); + update.layerCreatedStates = std::move(mCreatedLayers); + mCreatedLayers.clear(); + update.newLayers = std::move(mNewLayers); + mNewLayers.clear(); + update.destroyedHandles = std::move(mDestroyedHandles); + mDestroyedHandles.clear(); + } + return update; +} + // gui::ISurfaceComposer binder::Status SurfaceComposerAIDL::bootFinished() { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 1eb1fdaa48..09891bebab 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -71,7 +71,9 @@ #include "FlagManager.h" #include "FrontEnd/DisplayInfo.h" #include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/LayerLifecycleManager.h" #include "FrontEnd/LayerSnapshot.h" +#include "FrontEnd/LayerSnapshotBuilder.h" #include "FrontEnd/TransactionHandler.h" #include "LayerVector.h" #include "Scheduler/ISchedulerCallback.h" @@ -450,6 +452,26 @@ private: FINISHED, }; + struct LayerCreatedState { + LayerCreatedState(const wp& layer, const wp& parent, bool addToRoot) + : layer(layer), initialParent(parent), addToRoot(addToRoot) {} + wp layer; + // Indicates the initial parent of the created layer, only used for creating layer in + // SurfaceFlinger. If nullptr, it may add the created layer into the current root layers. + wp initialParent; + // Indicates whether the layer getting created should be added at root if there's no parent + // and has permission ACCESS_SURFACE_FLINGER. If set to false and no parent, the layer will + // be added offscreen. + bool addToRoot; + }; + + struct LifecycleUpdate { + std::vector transactions; + std::vector layerCreatedStates; + std::vector> newLayers; + std::vector destroyedHandles; + }; + template >* = nullptr> static Dumper dumper(F&& dump) { using namespace std::placeholders; @@ -688,6 +710,17 @@ private: void updateLayerGeometry(); void updateLayerMetadataSnapshot(); + std::vector> moveSnapshotsToCompositionArgs( + compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly, + int64_t vsyncId); + void moveSnapshotsFromCompositionArgs(compositionengine::CompositionRefreshArgs& refreshArgs, + std::vector>& layers); + bool updateLayerSnapshotsLegacy(VsyncId vsyncId, LifecycleUpdate& update, + bool transactionsFlushed, bool& out) + REQUIRES(kMainThreadContext); + bool updateLayerSnapshots(VsyncId vsyncId, LifecycleUpdate& update, bool transactionsFlushed, + bool& out) REQUIRES(kMainThreadContext); + LifecycleUpdate flushLifecycleUpdates() REQUIRES(kMainThreadContext); void updateInputFlinger(); void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext); @@ -715,6 +748,8 @@ private: bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext); bool applyTransactions(std::vector&, VsyncId) REQUIRES(kMainThreadContext); + bool applyAndCommitDisplayTransactionStates(std::vector& transactions) + REQUIRES(kMainThreadContext); // Returns true if there is at least one transaction that needs to be flushed bool transactionFlushNeeded(); @@ -730,7 +765,10 @@ private: int64_t desiredPresentTime, bool isAutoTimestamp, int64_t postTime, uint32_t permissions, uint64_t transactionId) REQUIRES(mStateLock); - + uint32_t updateLayerCallbacksAndStats(const FrameTimelineInfo&, ResolvedComposerState&, + int64_t desiredPresentTime, bool isAutoTimestamp, + int64_t postTime, uint32_t permissions, + uint64_t transactionId) REQUIRES(mStateLock); uint32_t getTransactionFlags() const; // Sets the masked bits, and schedules a commit if needed. @@ -888,7 +926,7 @@ private: // mark a region of a layer stack dirty. this updates the dirty // region of all screens presenting this layer stack. - void invalidateLayerStack(const sp& layer, const Region& dirty); + void invalidateLayerStack(const ui::LayerFilter& layerFilter, const Region& dirty); ui::LayerFilter makeLayerFilterForDisplay(DisplayId displayId, ui::LayerStack layerStack) REQUIRES(mStateLock) { @@ -1154,6 +1192,7 @@ private: // Set if LayerMetadata has changed since the last LayerMetadata snapshot. bool mLayerMetadataSnapshotNeeded = false; + // TODO(b/238781169) validate these on composition // Tracks layers that have pending frames which are candidates for being // latched. std::unordered_set, SpHash> mLayersWithQueuedFrames; @@ -1325,23 +1364,11 @@ private: GUARDED_BY(mStateLock); mutable std::mutex mCreatedLayersLock; - struct LayerCreatedState { - LayerCreatedState(const wp& layer, const wp parent, bool addToRoot) - : layer(layer), initialParent(parent), addToRoot(addToRoot) {} - wp layer; - // Indicates the initial parent of the created layer, only used for creating layer in - // SurfaceFlinger. If nullptr, it may add the created layer into the current root layers. - wp initialParent; - // Indicates whether the layer getting created should be added at root if there's no parent - // and has permission ACCESS_SURFACE_FLINGER. If set to false and no parent, the layer will - // be added offscreen. - bool addToRoot; - }; // A temporay pool that store the created layers and will be added to current state in main // thread. std::vector mCreatedLayers GUARDED_BY(mCreatedLayersLock); - bool commitCreatedLayers(VsyncId); + bool commitCreatedLayers(VsyncId, std::vector& createdLayers); void handleLayerCreatedLocked(const LayerCreatedState&, VsyncId) REQUIRES(mStateLock); mutable std::mutex mMirrorDisplayLock; @@ -1363,6 +1390,11 @@ private: return hasDisplay( [](const auto& display) { return display.isRefreshRateOverlayEnabled(); }); } + std::function>>()> getLayerSnapshotsForScreenshots( + std::optional layerStack, uint32_t uid); + std::function>>()> getLayerSnapshotsForScreenshots( + uint32_t rootLayerId, uint32_t uid, std::unordered_set excludeLayerIds, + bool childrenOnly, const FloatRect& parentCrop); const sp mWindowInfosListenerInvoker; @@ -1375,6 +1407,18 @@ private: bool mPowerHintSessionEnabled; + bool mLayerLifecycleManagerEnabled = false; + bool mLegacyFrontEndEnabled = true; + + frontend::LayerLifecycleManager mLayerLifecycleManager; + frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}}; + frontend::LayerSnapshotBuilder mLayerSnapshotBuilder; + + std::vector mDestroyedHandles; + std::vector> mNewLayers; + // These classes do not store any client state but help with managing transaction callbacks + // and stats. + std::unordered_map> mLegacyLayers; struct { bool late = false; bool early = false; @@ -1382,6 +1426,7 @@ private: TransactionHandler mTransactionHandler; display::DisplayMap mFrontEndDisplayInfos; + bool mFrontEndDisplayInfosChanged = false; }; class SurfaceComposerAIDL : public gui::BnSurfaceComposer { diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp index 11719c435e..c088e7b40c 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp @@ -148,7 +148,7 @@ void LayerFuzzer::invokeBufferStateLayer() { layer->fenceHasSignaled(); layer->onPreComposition(mFdp.ConsumeIntegral()); const std::vector> callbacks; - layer->setTransactionCompletedListeners(callbacks); + layer->setTransactionCompletedListeners(callbacks, mFdp.ConsumeBool()); std::shared_ptr texture = std::make_shared< renderengine::mock::FakeExternalTexture>(mFdp.ConsumeIntegral(), -- GitLab From 2248f52cd8102e5bc71c5c1dca7a85e90fea1276 Mon Sep 17 00:00:00 2001 From: Rachel Lee Date: Fri, 27 Jan 2023 16:45:23 -0800 Subject: [PATCH 0798/1310] Framework code for Attached Choreographer. Allows a direct association of a Layer (via LayerHistory) to attached choreographer. The EventThread checks whether the vsync is in phase for a choreographer. Bug: 255838011 Test: atest AttachedChoreographerNativeTest Change-Id: I9cb35bced5e6d4509609ad7698ab2902a31d5b98 --- libs/gui/Choreographer.cpp | 5 ++-- libs/gui/DisplayEventDispatcher.cpp | 5 ++-- libs/gui/DisplayEventReceiver.cpp | 11 ++++++--- libs/gui/SurfaceControl.cpp | 22 +++++++++++++++++- .../aidl/android/gui/ISurfaceComposer.aidl | 8 ++++++- libs/gui/fuzzer/libgui_fuzzer_utils.h | 2 +- libs/gui/include/gui/Choreographer.h | 3 ++- libs/gui/include/gui/DisplayEventDispatcher.h | 3 ++- libs/gui/include/gui/DisplayEventReceiver.h | 3 ++- libs/gui/include/gui/SurfaceControl.h | 5 ++++ libs/gui/tests/Surface_test.cpp | 1 + .../surfaceflinger/Scheduler/EventThread.cpp | 6 +++++ .../surfaceflinger/Scheduler/EventThread.h | 3 +++ .../surfaceflinger/Scheduler/LayerHistory.cpp | 23 +++++++++++++++++++ .../surfaceflinger/Scheduler/LayerHistory.h | 9 ++++++++ .../surfaceflinger/Scheduler/Scheduler.cpp | 15 ++++++++---- services/surfaceflinger/Scheduler/Scheduler.h | 6 +++-- services/surfaceflinger/SurfaceFlinger.cpp | 9 ++++---- services/surfaceflinger/SurfaceFlinger.h | 4 +++- 19 files changed, 119 insertions(+), 24 deletions(-) diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp index 6b25b262c3..99bf6badee 100644 --- a/libs/gui/Choreographer.cpp +++ b/libs/gui/Choreographer.cpp @@ -101,8 +101,9 @@ Choreographer* Choreographer::getForThread() { return gChoreographer; } -Choreographer::Choreographer(const sp& looper) - : DisplayEventDispatcher(looper, gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp), +Choreographer::Choreographer(const sp& looper, const sp& layerHandle) + : DisplayEventDispatcher(looper, gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp, {}, + layerHandle), mLooper(looper), mThreadId(std::this_thread::get_id()) { std::lock_guard _l(gChoreographers.lock); diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp index 501e69ade5..8a883770d8 100644 --- a/libs/gui/DisplayEventDispatcher.cpp +++ b/libs/gui/DisplayEventDispatcher.cpp @@ -37,9 +37,10 @@ static constexpr nsecs_t WAITING_FOR_VSYNC_TIMEOUT = ms2ns(300); DisplayEventDispatcher::DisplayEventDispatcher(const sp& looper, gui::ISurfaceComposer::VsyncSource vsyncSource, - EventRegistrationFlags eventRegistration) + EventRegistrationFlags eventRegistration, + const sp& layerHandle) : mLooper(looper), - mReceiver(vsyncSource, eventRegistration), + mReceiver(vsyncSource, eventRegistration, layerHandle), mWaitingForVsync(false), mLastVsyncCount(0), mLastScheduleVsyncTime(0) { diff --git a/libs/gui/DisplayEventReceiver.cpp b/libs/gui/DisplayEventReceiver.cpp index c52fb6b7c3..6849a95d1e 100644 --- a/libs/gui/DisplayEventReceiver.cpp +++ b/libs/gui/DisplayEventReceiver.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#define LOG_TAG "DisplayEventReceiver" + #include #include @@ -32,7 +34,8 @@ namespace android { // --------------------------------------------------------------------------- DisplayEventReceiver::DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource vsyncSource, - EventRegistrationFlags eventRegistration) { + EventRegistrationFlags eventRegistration, + const sp& layerHandle) { sp sf(ComposerServiceAIDL::getComposerService()); if (sf != nullptr) { mEventConnection = nullptr; @@ -41,8 +44,8 @@ DisplayEventReceiver::DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource vs static_cast< gui::ISurfaceComposer::EventRegistration>( eventRegistration.get()), - &mEventConnection); - if (mEventConnection != nullptr) { + layerHandle, &mEventConnection); + if (status.isOk() && mEventConnection != nullptr) { mDataChannel = std::make_unique(); status = mEventConnection->stealReceiveChannel(mDataChannel.get()); if (!status.isOk()) { @@ -51,6 +54,8 @@ DisplayEventReceiver::DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource vs mDataChannel.reset(); mEventConnection.clear(); } + } else { + ALOGE("DisplayEventConnection creation failed: status=%s", status.toString8().c_str()); } } } diff --git a/libs/gui/SurfaceControl.cpp b/libs/gui/SurfaceControl.cpp index 7aee882422..c5f9c38ca3 100644 --- a/libs/gui/SurfaceControl.cpp +++ b/libs/gui/SurfaceControl.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -34,8 +35,9 @@ #include #include -#include #include +#include +#include #include #include #include @@ -191,6 +193,24 @@ const std::string& SurfaceControl::getName() const { return mName; } +std::shared_ptr SurfaceControl::getChoreographer() { + if (mChoreographer) { + return mChoreographer; + } + sp looper = Looper::getForThread(); + if (!looper.get()) { + ALOGE("%s: No looper prepared for thread", __func__); + return nullptr; + } + mChoreographer = std::make_shared(looper, getHandle()); + status_t result = mChoreographer->initialize(); + if (result != OK) { + ALOGE("Failed to initialize choreographer"); + mChoreographer = nullptr; + } + return mChoreographer; +} + sp SurfaceControl::getIGraphicBufferProducer() { getSurface(); diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 597749acc4..981214212b 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -68,9 +68,15 @@ interface ISurfaceComposer { /** * Create a display event connection + * + * layerHandle + * Optional binder handle representing a Layer in SF to associate the new + * DisplayEventConnection with. This handle can be found inside a surface control after + * surface creation, see ISurfaceComposerClient::createSurface. Set to null if no layer + * association should be made. */ @nullable IDisplayEventConnection createDisplayEventConnection(VsyncSource vsyncSource, - EventRegistration eventRegistration); + EventRegistration eventRegistration, @nullable IBinder layerHandle); /** * Create a connection with SurfaceFlinger. diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h index 14a0e39813..f01c2a9e8e 100644 --- a/libs/gui/fuzzer/libgui_fuzzer_utils.h +++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h @@ -64,7 +64,7 @@ public: MOCK_METHOD(binder::Status, bootFinished, (), (override)); MOCK_METHOD(binder::Status, createDisplayEventConnection, (gui::ISurfaceComposer::VsyncSource, gui::ISurfaceComposer::EventRegistration, - sp*), + const sp& /*layerHandle*/, sp*), (override)); MOCK_METHOD(binder::Status, createConnection, (sp*), (override)); MOCK_METHOD(binder::Status, createDisplay, (const std::string&, bool, float, sp*), diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h index 89a7058dd6..1df9b11432 100644 --- a/libs/gui/include/gui/Choreographer.h +++ b/libs/gui/include/gui/Choreographer.h @@ -73,7 +73,8 @@ public: }; static Context gChoreographers; - explicit Choreographer(const sp& looper) EXCLUDES(gChoreographers.lock); + explicit Choreographer(const sp& looper, const sp& layerHandle = nullptr) + EXCLUDES(gChoreographers.lock); void postFrameCallbackDelayed(AChoreographer_frameCallback cb, AChoreographer_frameCallback64 cb64, AChoreographer_vsyncCallback vsyncCallback, void* data, diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h index bf3a07b6b5..140efa6d97 100644 --- a/libs/gui/include/gui/DisplayEventDispatcher.h +++ b/libs/gui/include/gui/DisplayEventDispatcher.h @@ -26,7 +26,8 @@ public: explicit DisplayEventDispatcher(const sp& looper, gui::ISurfaceComposer::VsyncSource vsyncSource = gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp, - EventRegistrationFlags eventRegistration = {}); + EventRegistrationFlags eventRegistration = {}, + const sp& layerHandle = nullptr); status_t initialize(); void dispose(); diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h index 0f4907fcb9..7fd6c35c5e 100644 --- a/libs/gui/include/gui/DisplayEventReceiver.h +++ b/libs/gui/include/gui/DisplayEventReceiver.h @@ -119,7 +119,8 @@ public: */ explicit DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource vsyncSource = gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp, - EventRegistrationFlags eventRegistration = {}); + EventRegistrationFlags eventRegistration = {}, + const sp& layerHandle = nullptr); /* * ~DisplayEventReceiver severs the connection with SurfaceFlinger, new events diff --git a/libs/gui/include/gui/SurfaceControl.h b/libs/gui/include/gui/SurfaceControl.h index 1d4fc7f06d..344b957ba7 100644 --- a/libs/gui/include/gui/SurfaceControl.h +++ b/libs/gui/include/gui/SurfaceControl.h @@ -36,6 +36,7 @@ namespace android { // --------------------------------------------------------------------------- +class Choreographer; class IGraphicBufferProducer; class Surface; class SurfaceComposerClient; @@ -80,6 +81,9 @@ public: int32_t getLayerId() const; const std::string& getName() const; + // TODO(b/267195698): Consider renaming. + std::shared_ptr getChoreographer(); + sp getIGraphicBufferProducer(); status_t clearLayerFrameStats() const; @@ -130,6 +134,7 @@ private: PixelFormat mFormat = PIXEL_FORMAT_NONE; uint32_t mCreateFlags = 0; uint64_t mFallbackFrameNumber = 100; + std::shared_ptr mChoreographer; }; }; // namespace android diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 9b2bf7ff31..babc197ae9 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -725,6 +725,7 @@ public: binder::Status createDisplayEventConnection( VsyncSource /*vsyncSource*/, EventRegistration /*eventRegistration*/, + const sp& /*layerHandle*/, sp* outConnection) override { *outConnection = nullptr; return binder::Status::ok(); diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index 76e9416fec..5e79a5c13e 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -532,6 +532,12 @@ bool EventThread::shouldConsumeEvent(const DisplayEventReceiver::Event& event, const sp& connection) const { const auto throttleVsync = [&] { const auto& vsyncData = event.vsync.vsyncData; + if (connection->frameRate.isValid()) { + return !mVsyncSchedule->getTracker() + .isVSyncInPhase(vsyncData.preferredExpectedPresentationTime(), + connection->frameRate); + } + const auto expectedPresentTime = TimePoint::fromNs(vsyncData.preferredExpectedPresentationTime()); return !mEventThreadCallback.isVsyncTargetForUid(expectedPresentTime, diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h index b86553bebe..aa27091908 100644 --- a/services/surfaceflinger/Scheduler/EventThread.h +++ b/services/surfaceflinger/Scheduler/EventThread.h @@ -97,6 +97,9 @@ public: const uid_t mOwnerUid; const EventRegistrationFlags mEventRegistration; + /** The frame rate set to the attached choreographer. */ + Fps frameRate; + private: virtual void onFirstRef(); EventThread* const mEventThread; diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp index 55fa402fa6..e853833bb9 100644 --- a/services/surfaceflinger/Scheduler/LayerHistory.cpp +++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp @@ -32,6 +32,7 @@ #include #include "../Layer.h" +#include "EventThread.h" #include "LayerInfo.h" namespace android::scheduler { @@ -140,6 +141,22 @@ void LayerHistory::record(Layer* layer, nsecs_t presentTime, nsecs_t now, info->setLastPresentTime(presentTime, now, updateType, mModeChangePending, layerProps); + // Set frame rate to attached choreographer. + // TODO(b/260898223): Change to use layer hierarchy and handle frame rate vote. + if (updateType == LayerUpdateType::SetFrameRate) { + auto range = mAttachedChoreographers.equal_range(id); + auto it = range.first; + while (it != range.second) { + sp choreographerConnection = it->second.promote(); + if (choreographerConnection) { + choreographerConnection->frameRate = layer->getFrameRateForLayerTree().rate; + it++; + } else { + it = mAttachedChoreographers.erase(it); + } + } + } + // Activate layer if inactive. if (found == LayerStatus::LayerInInactiveMap) { mActiveLayerInfos.insert( @@ -294,6 +311,12 @@ float LayerHistory::getLayerFramerate(nsecs_t now, int32_t id) const { return 0.f; } +void LayerHistory::attachChoreographer(int32_t layerId, + const sp& choreographerConnection) { + std::lock_guard lock(mLock); + mAttachedChoreographers.insert({layerId, wp(choreographerConnection)}); +} + auto LayerHistory::findLayer(int32_t id) -> std::pair { // the layer could be in either the active or inactive map, try both auto it = mActiveLayerInfos.find(id); diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h index 5022906ff9..68e7030feb 100644 --- a/services/surfaceflinger/Scheduler/LayerHistory.h +++ b/services/surfaceflinger/Scheduler/LayerHistory.h @@ -27,6 +27,8 @@ #include #include +#include "EventThread.h" + #include "RefreshRateSelector.h" namespace android { @@ -80,6 +82,9 @@ public: // return the frames per second of the layer with the given sequence id. float getLayerFramerate(nsecs_t now, int32_t id) const; + void attachChoreographer(int32_t layerId, + const sp& choreographerConnection); + private: friend class LayerHistoryTest; friend class TestableScheduler; @@ -117,6 +122,10 @@ private: LayerInfos mActiveLayerInfos GUARDED_BY(mLock); LayerInfos mInactiveLayerInfos GUARDED_BY(mLock); + // Map keyed by layer ID (sequence) to choreographer connections. + std::unordered_multimap> mAttachedChoreographers + GUARDED_BY(mLock); + uint32_t mDisplayArea = 0; // Whether to emit systrace output and debug logs. diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index e6f46655fc..eed57ef4f1 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -47,6 +47,7 @@ #include "Display/DisplayMap.h" #include "EventThread.h" #include "FrameRateOverrideMappings.h" +#include "FrontEnd/LayerHandle.h" #include "OneShotTimer.h" #include "SurfaceFlingerProperties.h" #include "VSyncPredictor.h" @@ -232,15 +233,21 @@ ConnectionHandle Scheduler::createConnection(std::unique_ptr eventT } sp Scheduler::createConnectionInternal( - EventThread* eventThread, EventRegistrationFlags eventRegistration) { - return eventThread->createEventConnection([&] { resync(); }, eventRegistration); + EventThread* eventThread, EventRegistrationFlags eventRegistration, + const sp& layerHandle) { + int32_t layerId = static_cast(LayerHandle::getLayerId(layerHandle)); + auto connection = eventThread->createEventConnection([&] { resync(); }, eventRegistration); + mLayerHistory.attachChoreographer(layerId, connection); + return connection; } sp Scheduler::createDisplayEventConnection( - ConnectionHandle handle, EventRegistrationFlags eventRegistration) { + ConnectionHandle handle, EventRegistrationFlags eventRegistration, + const sp& layerHandle) { std::lock_guard lock(mConnectionsLock); RETURN_IF_INVALID_HANDLE(handle, nullptr); - return createConnectionInternal(mConnections[handle].thread.get(), eventRegistration); + return createConnectionInternal(mConnections[handle].thread.get(), eventRegistration, + layerHandle); } sp Scheduler::getEventConnection(ConnectionHandle handle) { diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 8dc2def113..8c8fc21e09 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -147,7 +147,8 @@ public: std::chrono::nanoseconds readyDuration); sp createDisplayEventConnection( - ConnectionHandle, EventRegistrationFlags eventRegistration = {}); + ConnectionHandle, EventRegistrationFlags eventRegistration = {}, + const sp& layerHandle = nullptr); sp getEventConnection(ConnectionHandle); @@ -302,7 +303,8 @@ private: // Create a connection on the given EventThread. ConnectionHandle createConnection(std::unique_ptr); sp createConnectionInternal( - EventThread*, EventRegistrationFlags eventRegistration = {}); + EventThread*, EventRegistrationFlags eventRegistration = {}, + const sp& layerHandle = nullptr); // Update feature state machine to given state when corresponding timer resets or expires. void kernelIdleTimerCallback(TimerState) EXCLUDES(mDisplayLock); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 6020aba4fd..4b3f9a1f7a 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1988,13 +1988,14 @@ status_t SurfaceFlinger::getDisplayDecorationSupport( // ---------------------------------------------------------------------------- sp SurfaceFlinger::createDisplayEventConnection( - gui::ISurfaceComposer::VsyncSource vsyncSource, EventRegistrationFlags eventRegistration) { + gui::ISurfaceComposer::VsyncSource vsyncSource, EventRegistrationFlags eventRegistration, + const sp& layerHandle) { const auto& handle = vsyncSource == gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger ? mSfConnectionHandle : mAppConnectionHandle; - return mScheduler->createDisplayEventConnection(handle, eventRegistration); + return mScheduler->createDisplayEventConnection(handle, eventRegistration, layerHandle); } void SurfaceFlinger::scheduleCommit(FrameHint hint) { @@ -7455,9 +7456,9 @@ binder::Status SurfaceComposerAIDL::bootFinished() { binder::Status SurfaceComposerAIDL::createDisplayEventConnection( VsyncSource vsyncSource, EventRegistration eventRegistration, - sp* outConnection) { + const sp& layerHandle, sp* outConnection) { sp conn = - mFlinger->createDisplayEventConnection(vsyncSource, eventRegistration); + mFlinger->createDisplayEventConnection(vsyncSource, eventRegistration, layerHandle); if (conn == nullptr) { *outConnection = nullptr; return binderStatusFromStatusT(BAD_VALUE); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 1eb1fdaa48..e7d4f77c23 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -509,7 +509,8 @@ private: sp createDisplayEventConnection( gui::ISurfaceComposer::VsyncSource vsyncSource = gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp, - EventRegistrationFlags eventRegistration = {}); + EventRegistrationFlags eventRegistration = {}, + const sp& layerHandle = nullptr); status_t captureDisplay(const DisplayCaptureArgs&, const sp&); status_t captureDisplay(DisplayId, const sp&); @@ -1391,6 +1392,7 @@ public: binder::Status bootFinished() override; binder::Status createDisplayEventConnection( VsyncSource vsyncSource, EventRegistration eventRegistration, + const sp& layerHandle, sp* outConnection) override; binder::Status createConnection(sp* outClient) override; binder::Status createDisplay(const std::string& displayName, bool secure, -- GitLab From c0d38fcbffee52ea08bc5e6c38e1fb1d5bbaee2c Mon Sep 17 00:00:00 2001 From: Rachel Lee Date: Fri, 6 Jan 2023 14:46:10 -0800 Subject: [PATCH 0799/1310] Attached Choreographer API from SurfaceControl. The API to get a native attached choreographer from ASurfaceControl. Bug: 255838011 Test: atest GraphicsTest Test: atest ChoreographerNativeTest (test no regression) Change-Id: I5db99d8888fd2adbc4a8fc2ce9a7c07f4de146ee --- include/private/surface_control_private.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/include/private/surface_control_private.h b/include/private/surface_control_private.h index 7e6c51587d..138926e55b 100644 --- a/include/private/surface_control_private.h +++ b/include/private/surface_control_private.h @@ -19,6 +19,8 @@ #include +#include + __BEGIN_DECLS struct ASurfaceControl; @@ -55,6 +57,13 @@ void ASurfaceControl_registerSurfaceStatsListener(ASurfaceControl* control, int3 void ASurfaceControl_unregisterSurfaceStatsListener(void* context, ASurfaceControl_SurfaceStatsListener func); +/** + * Gets the attached AChoreographer instance from the given \c surfaceControl. If there is no + * choreographer associated with the surface control, then a new instance of choreographer is + * created. The new choreographer is associated with the current thread's Looper. + */ +AChoreographer* ASurfaceControl_getChoreographer(ASurfaceControl* surfaceControl); + /** * Returns the timestamp of when the buffer was acquired for a specific frame with frame number * obtained from ASurfaceControlStats_getFrameNumber. -- GitLab From 481ff8dbaa3770b629901c6fc35764a18df4c176 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 1 Feb 2023 14:14:05 -0800 Subject: [PATCH 0800/1310] Add HoverTest and VelocityTracker test to presubmit Input affects tests inside CtsViewTestCases that depend on event injection. Some of these are HoverTest and TooltipTest. Changes in libinput would also affect VelocityTrackerTest. Add both to presubmit Bug: 211379801 Test: none Change-Id: I8474764abcd397fb4ed2b7f92c0e5e4aff19c93a --- services/inputflinger/TEST_MAPPING | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING index cacf30bd82..2a3dba7415 100644 --- a/services/inputflinger/TEST_MAPPING +++ b/services/inputflinger/TEST_MAPPING @@ -42,9 +42,11 @@ "options": [ { "include-filter": "android.view.cts.input", + "include-filter": "android.view.cts.HoverTest", "include-filter": "android.view.cts.MotionEventTest", "include-filter": "android.view.cts.PointerCaptureTest", "include-filter": "android.view.cts.TooltipTest", + "include-filter": "android.view.cts.VelocityTrackerTest", "include-filter": "android.view.cts.VerifyInputEventTest" } ] @@ -131,9 +133,12 @@ "name": "CtsViewTestCases", "options": [ { + "include-filter": "android.view.cts.input", + "include-filter": "android.view.cts.HoverTest", "include-filter": "android.view.cts.MotionEventTest", "include-filter": "android.view.cts.PointerCaptureTest", "include-filter": "android.view.cts.TooltipTest", + "include-filter": "android.view.cts.VelocityTrackerTest", "include-filter": "android.view.cts.VerifyInputEventTest" } ] -- GitLab From b81ab20164c2d12e0f3d5f734edcf1147b4a12d4 Mon Sep 17 00:00:00 2001 From: Cody Northrop Date: Tue, 10 Jan 2023 15:33:18 -0700 Subject: [PATCH 0801/1310] EGL: Refactor multifile blobcache After more testing, it became apparent that we need to avoid accessing the filesystem to have decent speed. This CL does that by: * Moving the multifile cache to a class * Tracking all entries in a bookkeeping system * Remove use of STL for read/write * Keep recent entries in a hot cache * Deferring file writes to a worker thread * Allowing devices to opt in via config For more data and details on the design philosophy: go/improving-multifile-blobcache-speed Also added a new sequence of tests that bypass EGL and directly invoke the mulfifile cache. Test: pubg_mobile_launch ANGLE trace Test: /data/nativetest64/EGL_test/EGL_test Test: /data/nativetest64/libEGL_test/libEGL_test Bug: b/266725576 Change-Id: Ia19147522a6f68f05fbe47c1545e4c9d69e513c6 --- opengl/libs/Android.bp | 7 +- opengl/libs/EGL/MultifileBlobCache.cpp | 668 ++++++++++++++++++++ opengl/libs/EGL/MultifileBlobCache.h | 164 +++++ opengl/libs/EGL/MultifileBlobCache_test.cpp | 200 ++++++ opengl/libs/EGL/egl_cache.cpp | 105 +-- opengl/libs/EGL/egl_cache.h | 21 +- opengl/libs/EGL/egl_cache_multifile.cpp | 343 ---------- opengl/libs/EGL/egl_cache_multifile.h | 36 -- opengl/tests/EGLTest/egl_cache_test.cpp | 52 +- 9 files changed, 1150 insertions(+), 446 deletions(-) create mode 100644 opengl/libs/EGL/MultifileBlobCache.cpp create mode 100644 opengl/libs/EGL/MultifileBlobCache.h create mode 100644 opengl/libs/EGL/MultifileBlobCache_test.cpp delete mode 100644 opengl/libs/EGL/egl_cache_multifile.cpp delete mode 100644 opengl/libs/EGL/egl_cache_multifile.h diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp index 750338bd84..49e1cbafb4 100644 --- a/opengl/libs/Android.bp +++ b/opengl/libs/Android.bp @@ -144,6 +144,7 @@ cc_library_static { srcs: [ "EGL/BlobCache.cpp", "EGL/FileBlobCache.cpp", + "EGL/MultifileBlobCache.cpp", ], export_include_dirs: ["EGL"], } @@ -160,7 +161,6 @@ cc_library_shared { srcs: [ "EGL/egl_tls.cpp", "EGL/egl_cache.cpp", - "EGL/egl_cache_multifile.cpp", "EGL/egl_display.cpp", "EGL/egl_object.cpp", "EGL/egl_layers.cpp", @@ -205,6 +205,11 @@ cc_test { srcs: [ "EGL/BlobCache.cpp", "EGL/BlobCache_test.cpp", + "EGL/MultifileBlobCache.cpp", + "EGL/MultifileBlobCache_test.cpp", + ], + shared_libs: [ + "libutils", ], } diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp new file mode 100644 index 0000000000..48b184b100 --- /dev/null +++ b/opengl/libs/EGL/MultifileBlobCache.cpp @@ -0,0 +1,668 @@ +/* + ** Copyright 2022, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +// #define LOG_NDEBUG 0 + +#include "MultifileBlobCache.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +using namespace std::literals; + +namespace { + +// Open the file and determine the size of the value it contains +size_t getValueSizeFromFile(int fd, const std::string& entryPath) { + // Read the beginning of the file to get header + android::MultifileHeader header; + size_t result = read(fd, static_cast(&header), sizeof(android::MultifileHeader)); + if (result != sizeof(android::MultifileHeader)) { + ALOGE("Error reading MultifileHeader from cache entry (%s): %s", entryPath.c_str(), + std::strerror(errno)); + return 0; + } + + return header.valueSize; +} + +// Helper function to close entries or free them +void freeHotCacheEntry(android::MultifileHotCache& entry) { + if (entry.entryFd != -1) { + // If we have an fd, then this entry was added to hot cache via INIT or GET + // We need to unmap and close the entry + munmap(entry.entryBuffer, entry.entrySize); + close(entry.entryFd); + } else { + // Otherwise, this was added to hot cache during SET, so it was never mapped + // and fd was only on the deferred thread. + delete[] entry.entryBuffer; + } +} + +} // namespace + +namespace android { + +MultifileBlobCache::MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize, + const std::string& baseDir) + : mInitialized(false), + mMaxTotalSize(maxTotalSize), + mTotalCacheSize(0), + mHotCacheLimit(maxHotCacheSize), + mHotCacheSize(0), + mWorkerThreadIdle(true) { + if (baseDir.empty()) { + return; + } + + // Establish the name of our multifile directory + mMultifileDirName = baseDir + ".multifile"; + + // Set a limit for max key and value, ensuring at least one entry can always fit in hot cache + mMaxKeySize = mHotCacheLimit / 4; + mMaxValueSize = mHotCacheLimit / 2; + + // Initialize our cache with the contents of the directory + mTotalCacheSize = 0; + + // See if the dir exists, and initialize using its contents + struct stat st; + if (stat(mMultifileDirName.c_str(), &st) == 0) { + // Read all the files and gather details, then preload their contents + DIR* dir; + struct dirent* entry; + if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) { + while ((entry = readdir(dir)) != nullptr) { + if (entry->d_name == "."s || entry->d_name == ".."s) { + continue; + } + + std::string entryName = entry->d_name; + std::string fullPath = mMultifileDirName + "/" + entryName; + + // The filename is the same as the entryHash + uint32_t entryHash = static_cast(strtoul(entry->d_name, nullptr, 10)); + + // Look up the details of the file + struct stat st; + if (stat(fullPath.c_str(), &st) != 0) { + ALOGE("Failed to stat %s", fullPath.c_str()); + return; + } + + // Open the file so we can read its header + int fd = open(fullPath.c_str(), O_RDONLY); + if (fd == -1) { + ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(), + std::strerror(errno)); + return; + } + + // Look up the details we track about each file + size_t valueSize = getValueSizeFromFile(fd, fullPath); + size_t fileSize = st.st_size; + time_t accessTime = st.st_atime; + + // If the cache entry is damaged or no good, remove it + // TODO: Perform any other checks + if (valueSize <= 0 || fileSize <= 0 || accessTime <= 0) { + if (remove(fullPath.c_str()) != 0) { + ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno)); + } + continue; + } + + // Track details for rapid lookup later + trackEntry(entryHash, valueSize, fileSize, accessTime); + + // Track the total size + increaseTotalCacheSize(fileSize); + + // Preload the entry for fast retrieval + if ((mHotCacheSize + fileSize) < mHotCacheLimit) { + // Memory map the file + uint8_t* mappedEntry = reinterpret_cast( + mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0)); + if (mappedEntry == MAP_FAILED) { + ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); + } + + ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for " + "entryHash %u", + fd, mappedEntry, entryHash); + + // Track the details of the preload so they can be retrieved later + if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) { + ALOGE("INIT Failed to add %u to hot cache", entryHash); + munmap(mappedEntry, fileSize); + close(fd); + return; + } + } else { + close(fd); + } + } + closedir(dir); + } else { + ALOGE("Unable to open filename: %s", mMultifileDirName.c_str()); + } + } else { + // If the multifile directory does not exist, create it and start from scratch + if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) { + ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno); + } + } + + mTaskThread = std::thread(&MultifileBlobCache::processTasks, this); + + mInitialized = true; +} + +MultifileBlobCache::~MultifileBlobCache() { + // Inform the worker thread we're done + ALOGV("DESCTRUCTOR: Shutting down worker thread"); + DeferredTask task(TaskCommand::Exit); + queueTask(std::move(task)); + + // Wait for it to complete + ALOGV("DESCTRUCTOR: Waiting for worker thread to complete"); + waitForWorkComplete(); + mTaskThread.join(); +} + +// Set will add the entry to hot cache and start a deferred process to write it to disk +void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const void* value, + EGLsizeiANDROID valueSize) { + if (!mInitialized) { + return; + } + + // Ensure key and value are under their limits + if (keySize > mMaxKeySize || valueSize > mMaxValueSize) { + ALOGV("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize, + valueSize, mMaxValueSize); + return; + } + + // Generate a hash of the key and use it to track this entry + uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast(key), keySize); + + size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize; + + // If we're going to be over the cache limit, kick off a trim to clear space + if (getTotalSize() + fileSize > mMaxTotalSize) { + ALOGV("SET: Cache is full, calling trimCache to clear space"); + trimCache(mMaxTotalSize); + } + + ALOGV("SET: Add %u to cache", entryHash); + + uint8_t* buffer = new uint8_t[fileSize]; + + // Write the key and value after the header + android::MultifileHeader header = {keySize, valueSize}; + memcpy(static_cast(buffer), static_cast(&header), + sizeof(android::MultifileHeader)); + memcpy(static_cast(buffer + sizeof(MultifileHeader)), static_cast(key), + keySize); + memcpy(static_cast(buffer + sizeof(MultifileHeader) + keySize), + static_cast(value), valueSize); + + std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash); + + // Track the size and access time for quick recall + trackEntry(entryHash, valueSize, fileSize, time(0)); + + // Update the overall cache size + increaseTotalCacheSize(fileSize); + + // Keep the entry in hot cache for quick retrieval + ALOGV("SET: Adding %u to hot cache.", entryHash); + + // Sending -1 as the fd indicates we don't have an fd for this + if (!addToHotCache(entryHash, -1, buffer, fileSize)) { + ALOGE("GET: Failed to add %u to hot cache", entryHash); + return; + } + + // Track that we're creating a pending write for this entry + // Include the buffer to handle the case when multiple writes are pending for an entry + mDeferredWrites.insert(std::make_pair(entryHash, buffer)); + + // Create deferred task to write to storage + ALOGV("SET: Adding task to queue."); + DeferredTask task(TaskCommand::WriteToDisk); + task.initWriteToDisk(fullPath, buffer, fileSize); + queueTask(std::move(task)); +} + +// Get will check the hot cache, then load it from disk if needed +EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize, void* value, + EGLsizeiANDROID valueSize) { + if (!mInitialized) { + return 0; + } + + // Ensure key and value are under their limits + if (keySize > mMaxKeySize || valueSize > mMaxValueSize) { + ALOGV("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize, + valueSize, mMaxValueSize); + return 0; + } + + // Generate a hash of the key and use it to track this entry + uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast(key), keySize); + + // See if we have this file + if (!contains(entryHash)) { + ALOGV("GET: Cache MISS - cache does not contain entry: %u", entryHash); + return 0; + } + + // Look up the data for this entry + MultifileEntryStats entryStats = getEntryStats(entryHash); + + size_t cachedValueSize = entryStats.valueSize; + if (cachedValueSize > valueSize) { + ALOGV("GET: Cache MISS - valueSize not large enough (%lu) for entry %u, returning required" + "size (%zu)", + valueSize, entryHash, cachedValueSize); + return cachedValueSize; + } + + // We have the file and have enough room to write it out, return the entry + ALOGV("GET: Cache HIT - cache contains entry: %u", entryHash); + + // Look up the size of the file + size_t fileSize = entryStats.fileSize; + if (keySize > fileSize) { + ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified " + "file", + keySize, fileSize); + return 0; + } + + std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash); + + // Open the hashed filename path + uint8_t* cacheEntry = 0; + + // Check hot cache + if (mHotCache.find(entryHash) != mHotCache.end()) { + ALOGV("GET: HotCache HIT for entry %u", entryHash); + cacheEntry = mHotCache[entryHash].entryBuffer; + } else { + ALOGV("GET: HotCache MISS for entry: %u", entryHash); + + if (mDeferredWrites.find(entryHash) != mDeferredWrites.end()) { + // Wait for writes to complete if there is an outstanding write for this entry + ALOGV("GET: Waiting for write to complete for %u", entryHash); + waitForWorkComplete(); + } + + // Open the entry file + int fd = open(fullPath.c_str(), O_RDONLY); + if (fd == -1) { + ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(), + std::strerror(errno)); + return 0; + } + + // Memory map the file + cacheEntry = + reinterpret_cast(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0)); + if (cacheEntry == MAP_FAILED) { + ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); + close(fd); + return 0; + } + + ALOGV("GET: Adding %u to hot cache", entryHash); + if (!addToHotCache(entryHash, fd, cacheEntry, fileSize)) { + ALOGE("GET: Failed to add %u to hot cache", entryHash); + return 0; + } + + cacheEntry = mHotCache[entryHash].entryBuffer; + } + + // Ensure the header matches + MultifileHeader* header = reinterpret_cast(cacheEntry); + if (header->keySize != keySize || header->valueSize != valueSize) { + ALOGW("Mismatch on keySize(%ld vs. cached %ld) or valueSize(%ld vs. cached %ld) compared " + "to cache header values for fullPath: %s", + keySize, header->keySize, valueSize, header->valueSize, fullPath.c_str()); + removeFromHotCache(entryHash); + return 0; + } + + // Compare the incoming key with our stored version (the beginning of the entry) + uint8_t* cachedKey = cacheEntry + sizeof(MultifileHeader); + int compare = memcmp(cachedKey, key, keySize); + if (compare != 0) { + ALOGW("Cached key and new key do not match! This is a hash collision or modified file"); + removeFromHotCache(entryHash); + return 0; + } + + // Remaining entry following the key is the value + uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader)); + memcpy(value, cachedValue, cachedValueSize); + + return cachedValueSize; +} + +void MultifileBlobCache::finish() { + // Wait for all deferred writes to complete + ALOGV("FINISH: Waiting for work to complete."); + waitForWorkComplete(); + + // Close all entries in the hot cache + for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) { + uint32_t entryHash = hotCacheIter->first; + MultifileHotCache entry = hotCacheIter->second; + + ALOGV("FINISH: Closing hot cache entry for %u", entryHash); + freeHotCacheEntry(entry); + + mHotCache.erase(hotCacheIter++); + } +} + +void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize, + time_t accessTime) { + mEntries.insert(entryHash); + mEntryStats[entryHash] = {valueSize, fileSize, accessTime}; +} + +bool MultifileBlobCache::contains(uint32_t hashEntry) const { + return mEntries.find(hashEntry) != mEntries.end(); +} + +MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) { + return mEntryStats[entryHash]; +} + +void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) { + mTotalCacheSize += fileSize; +} + +void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) { + mTotalCacheSize -= fileSize; +} + +bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer, + size_t newEntrySize) { + ALOGV("HOTCACHE(ADD): Adding %u to hot cache", newEntryHash); + + // Clear space if we need to + if ((mHotCacheSize + newEntrySize) > mHotCacheLimit) { + ALOGV("HOTCACHE(ADD): mHotCacheSize (%zu) + newEntrySize (%zu) is to big for " + "mHotCacheLimit " + "(%zu), freeing up space for %u", + mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash); + + // Wait for all the files to complete writing so our hot cache is accurate + waitForWorkComplete(); + + // Free up old entries until under the limit + for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) { + uint32_t oldEntryHash = hotCacheIter->first; + MultifileHotCache oldEntry = hotCacheIter->second; + + // Move our iterator before deleting the entry + hotCacheIter++; + if (!removeFromHotCache(oldEntryHash)) { + ALOGE("HOTCACHE(ADD): Unable to remove entry %u", oldEntryHash); + return false; + } + + // Clear at least half the hot cache + if ((mHotCacheSize + newEntrySize) <= mHotCacheLimit / 2) { + ALOGV("HOTCACHE(ADD): Freed enough space for %zu", mHotCacheSize); + break; + } + } + } + + // Track it + mHotCache[newEntryHash] = {newFd, newEntryBuffer, newEntrySize}; + mHotCacheSize += newEntrySize; + + ALOGV("HOTCACHE(ADD): New hot cache size: %zu", mHotCacheSize); + + return true; +} + +bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) { + if (mHotCache.find(entryHash) != mHotCache.end()) { + ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash); + + // Wait for all the files to complete writing so our hot cache is accurate + waitForWorkComplete(); + + ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash); + MultifileHotCache entry = mHotCache[entryHash]; + freeHotCacheEntry(entry); + + // Delete the entry from our tracking + mHotCacheSize -= entry.entrySize; + size_t count = mHotCache.erase(entryHash); + + return true; + } + + return false; +} + +bool MultifileBlobCache::applyLRU(size_t cacheLimit) { + // Walk through our map of sorted last access times and remove files until under the limit + for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) { + uint32_t entryHash = cacheEntryIter->first; + + ALOGV("LRU: Removing entryHash %u", entryHash); + + // Track the overall size + MultifileEntryStats entryStats = getEntryStats(entryHash); + decreaseTotalCacheSize(entryStats.fileSize); + + // Remove it from hot cache if present + removeFromHotCache(entryHash); + + // Remove it from the system + std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash); + if (remove(entryPath.c_str()) != 0) { + ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno)); + return false; + } + + // Increment the iterator before clearing the entry + cacheEntryIter++; + + // Delete the entry from our tracking + size_t count = mEntryStats.erase(entryHash); + if (count != 1) { + ALOGE("LRU: Failed to remove entryHash (%u) from mEntryStats", entryHash); + return false; + } + + // See if it has been reduced enough + size_t totalCacheSize = getTotalSize(); + if (totalCacheSize <= cacheLimit) { + // Success + ALOGV("LRU: Reduced cache to %zu", totalCacheSize); + return true; + } + } + + ALOGV("LRU: Cache is emptry"); + return false; +} + +// When removing files, what fraction of the overall limit should be reached when removing files +// A divisor of two will decrease the cache to 50%, four to 25% and so on +constexpr uint32_t kCacheLimitDivisor = 2; + +// Calculate the cache size and remove old entries until under the limit +void MultifileBlobCache::trimCache(size_t cacheByteLimit) { + // Start with the value provided by egl_cache + size_t limit = cacheByteLimit; + + // Wait for all deferred writes to complete + waitForWorkComplete(); + + size_t size = getTotalSize(); + + // If size is larger than the threshold, remove files using LRU + if (size > limit) { + ALOGV("TRIM: Multifile cache size is larger than %zu, removing old entries", + cacheByteLimit); + if (!applyLRU(limit / kCacheLimitDivisor)) { + ALOGE("Error when clearing multifile shader cache"); + return; + } + } +} + +// This function performs a task. It only knows how to write files to disk, +// but it could be expanded if needed. +void MultifileBlobCache::processTask(DeferredTask& task) { + switch (task.getTaskCommand()) { + case TaskCommand::Exit: { + ALOGV("DEFERRED: Shutting down"); + return; + } + case TaskCommand::WriteToDisk: { + uint32_t entryHash = task.getEntryHash(); + std::string& fullPath = task.getFullPath(); + uint8_t* buffer = task.getBuffer(); + size_t bufferSize = task.getBufferSize(); + + // Create the file or reset it if already present, read+write for user only + int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd == -1) { + ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s", + fullPath.c_str(), std::strerror(errno)); + return; + } + + ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str()); + + ssize_t result = write(fd, buffer, bufferSize); + if (result != bufferSize) { + ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(), + std::strerror(errno)); + return; + } + + ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str()); + close(fd); + + // Erase the entry from mDeferredWrites + // Since there could be multiple outstanding writes for an entry, find the matching one + typedef std::multimap::iterator entryIter; + std::pair iterPair = mDeferredWrites.equal_range(entryHash); + for (entryIter it = iterPair.first; it != iterPair.second; ++it) { + if (it->second == buffer) { + ALOGV("DEFERRED: Marking write complete for %u at %p", it->first, it->second); + mDeferredWrites.erase(it); + break; + } + } + + return; + } + default: { + ALOGE("DEFERRED: Unhandled task type"); + return; + } + } +} + +// This function will wait until tasks arrive, then execute them +// If the exit command is submitted, the loop will terminate +void MultifileBlobCache::processTasksImpl(bool* exitThread) { + while (true) { + std::unique_lock lock(mWorkerMutex); + if (mTasks.empty()) { + ALOGV("WORKER: No tasks available, waiting"); + mWorkerThreadIdle = true; + mWorkerIdleCondition.notify_all(); + // Only wake if notified and command queue is not empty + mWorkAvailableCondition.wait(lock, [this] { return !mTasks.empty(); }); + } + + ALOGV("WORKER: Task available, waking up."); + mWorkerThreadIdle = false; + DeferredTask task = std::move(mTasks.front()); + mTasks.pop(); + + if (task.getTaskCommand() == TaskCommand::Exit) { + ALOGV("WORKER: Exiting work loop."); + *exitThread = true; + mWorkerThreadIdle = true; + mWorkerIdleCondition.notify_one(); + return; + } + + lock.unlock(); + processTask(task); + } +} + +// Process tasks until the exit task is submitted +void MultifileBlobCache::processTasks() { + while (true) { + bool exitThread = false; + processTasksImpl(&exitThread); + if (exitThread) { + break; + } + } +} + +// Add a task to the queue to be processed by the worker thread +void MultifileBlobCache::queueTask(DeferredTask&& task) { + std::lock_guard queueLock(mWorkerMutex); + mTasks.emplace(std::move(task)); + mWorkAvailableCondition.notify_one(); +} + +// Wait until all tasks have been completed +void MultifileBlobCache::waitForWorkComplete() { + std::unique_lock lock(mWorkerMutex); + mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); }); +} + +}; // namespace android \ No newline at end of file diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h new file mode 100644 index 0000000000..dcdfe47c6f --- /dev/null +++ b/opengl/libs/EGL/MultifileBlobCache.h @@ -0,0 +1,164 @@ +/* + ** Copyright 2022, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +#ifndef ANDROID_MULTIFILE_BLOB_CACHE_H +#define ANDROID_MULTIFILE_BLOB_CACHE_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace android { + +struct MultifileHeader { + EGLsizeiANDROID keySize; + EGLsizeiANDROID valueSize; +}; + +struct MultifileEntryStats { + EGLsizeiANDROID valueSize; + size_t fileSize; + time_t accessTime; +}; + +struct MultifileHotCache { + int entryFd; + uint8_t* entryBuffer; + size_t entrySize; +}; + +enum class TaskCommand { + Invalid = 0, + WriteToDisk, + Exit, +}; + +class DeferredTask { +public: + DeferredTask(TaskCommand command) : mCommand(command) {} + + TaskCommand getTaskCommand() { return mCommand; } + + void initWriteToDisk(std::string fullPath, uint8_t* buffer, size_t bufferSize) { + mCommand = TaskCommand::WriteToDisk; + mFullPath = fullPath; + mBuffer = buffer; + mBufferSize = bufferSize; + } + + uint32_t getEntryHash() { return mEntryHash; } + std::string& getFullPath() { return mFullPath; } + uint8_t* getBuffer() { return mBuffer; } + size_t getBufferSize() { return mBufferSize; }; + +private: + TaskCommand mCommand; + + // Parameters for WriteToDisk + uint32_t mEntryHash; + std::string mFullPath; + uint8_t* mBuffer; + size_t mBufferSize; +}; + +class MultifileBlobCache { +public: + MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize, const std::string& baseDir); + ~MultifileBlobCache(); + + void set(const void* key, EGLsizeiANDROID keySize, const void* value, + EGLsizeiANDROID valueSize); + EGLsizeiANDROID get(const void* key, EGLsizeiANDROID keySize, void* value, + EGLsizeiANDROID valueSize); + + void finish(); + + size_t getTotalSize() const { return mTotalCacheSize; } + void trimCache(size_t cacheByteLimit); + +private: + void trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize, + time_t accessTime); + bool contains(uint32_t entryHash) const; + bool removeEntry(uint32_t entryHash); + MultifileEntryStats getEntryStats(uint32_t entryHash); + + size_t getFileSize(uint32_t entryHash); + size_t getValueSize(uint32_t entryHash); + + void increaseTotalCacheSize(size_t fileSize); + void decreaseTotalCacheSize(size_t fileSize); + + bool addToHotCache(uint32_t entryHash, int fd, uint8_t* entryBufer, size_t entrySize); + bool removeFromHotCache(uint32_t entryHash); + + bool applyLRU(size_t cacheLimit); + + bool mInitialized; + std::string mMultifileDirName; + + std::unordered_set mEntries; + std::unordered_map mEntryStats; + std::unordered_map mHotCache; + + size_t mMaxKeySize; + size_t mMaxValueSize; + size_t mMaxTotalSize; + size_t mTotalCacheSize; + size_t mHotCacheLimit; + size_t mHotCacheEntryLimit; + size_t mHotCacheSize; + + // Below are the components used to allow a deferred write + + // Track whether we have pending writes for an entry + std::multimap mDeferredWrites; + + // Functions to work through tasks in the queue + void processTasks(); + void processTasksImpl(bool* exitThread); + void processTask(DeferredTask& task); + + // Used by main thread to create work for the worker thread + void queueTask(DeferredTask&& task); + + // Used by main thread to wait for worker thread to complete all outstanding work. + void waitForWorkComplete(); + + std::thread mTaskThread; + std::queue mTasks; + std::mutex mWorkerMutex; + + // This condition will block the worker thread until a task is queued + std::condition_variable mWorkAvailableCondition; + + // This condition will block the main thread while the worker thread still has tasks + std::condition_variable mWorkerIdleCondition; + + // This bool will track whether all tasks have been completed + bool mWorkerThreadIdle; +}; + +}; // namespace android + +#endif // ANDROID_MULTIFILE_BLOB_CACHE_H diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp new file mode 100644 index 0000000000..1a55a4fcdd --- /dev/null +++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp @@ -0,0 +1,200 @@ +/* + ** Copyright 2023, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +#include "MultifileBlobCache.h" + +#include +#include +#include +#include + +#include + +namespace android { + +template +using sp = std::shared_ptr; + +constexpr size_t kMaxTotalSize = 32 * 1024; +constexpr size_t kMaxPreloadSize = 8 * 1024; + +constexpr size_t kMaxKeySize = kMaxPreloadSize / 4; +constexpr size_t kMaxValueSize = kMaxPreloadSize / 2; + +class MultifileBlobCacheTest : public ::testing::Test { +protected: + virtual void SetUp() { + mTempFile.reset(new TemporaryFile()); + mMBC.reset(new MultifileBlobCache(kMaxTotalSize, kMaxPreloadSize, &mTempFile->path[0])); + } + + virtual void TearDown() { mMBC.reset(); } + + std::unique_ptr mTempFile; + std::unique_ptr mMBC; +}; + +TEST_F(MultifileBlobCacheTest, CacheSingleValueSucceeds) { + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, CacheTwoValuesSucceeds) { + unsigned char buf[2] = {0xee, 0xee}; + mMBC->set("ab", 2, "cd", 2); + mMBC->set("ef", 2, "gh", 2); + ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); + ASSERT_EQ('c', buf[0]); + ASSERT_EQ('d', buf[1]); + ASSERT_EQ(size_t(2), mMBC->get("ef", 2, buf, 2)); + ASSERT_EQ('g', buf[0]); + ASSERT_EQ('h', buf[1]); +} + +TEST_F(MultifileBlobCacheTest, GetSetTwiceSucceeds) { + unsigned char buf[2] = {0xee, 0xee}; + mMBC->set("ab", 2, "cd", 2); + ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); + ASSERT_EQ('c', buf[0]); + ASSERT_EQ('d', buf[1]); + // Use the same key, but different value + mMBC->set("ab", 2, "ef", 2); + ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); +} + +TEST_F(MultifileBlobCacheTest, GetOnlyWritesInsideBounds) { + unsigned char buf[6] = {0xee, 0xee, 0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf + 1, 4)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ('e', buf[1]); + ASSERT_EQ('f', buf[2]); + ASSERT_EQ('g', buf[3]); + ASSERT_EQ('h', buf[4]); + ASSERT_EQ(0xee, buf[5]); +} + +TEST_F(MultifileBlobCacheTest, GetOnlyWritesIfBufferIsLargeEnough) { + unsigned char buf[3] = {0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 3)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ(0xee, buf[1]); + ASSERT_EQ(0xee, buf[2]); +} + +TEST_F(MultifileBlobCacheTest, GetDoesntAccessNullBuffer) { + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, nullptr, 0)); +} + +TEST_F(MultifileBlobCacheTest, MultipleSetsCacheLatestValue) { + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + mMBC->set("abcd", 4, "ijkl", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('i', buf[0]); + ASSERT_EQ('j', buf[1]); + ASSERT_EQ('k', buf[2]); + ASSERT_EQ('l', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, SecondSetKeepsFirstValueIfTooLarge) { + unsigned char buf[kMaxValueSize + 1] = {0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + mMBC->set("abcd", 4, buf, kMaxValueSize + 1); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, DoesntCacheIfKeyIsTooBig) { + char key[kMaxKeySize + 1]; + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + for (int i = 0; i < kMaxKeySize + 1; i++) { + key[i] = 'a'; + } + mMBC->set(key, kMaxKeySize + 1, "bbbb", 4); + ASSERT_EQ(size_t(0), mMBC->get(key, kMaxKeySize + 1, buf, 4)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ(0xee, buf[1]); + ASSERT_EQ(0xee, buf[2]); + ASSERT_EQ(0xee, buf[3]); +} + +TEST_F(MultifileBlobCacheTest, DoesntCacheIfValueIsTooBig) { + char buf[kMaxValueSize + 1]; + for (int i = 0; i < kMaxValueSize + 1; i++) { + buf[i] = 'b'; + } + mMBC->set("abcd", 4, buf, kMaxValueSize + 1); + for (int i = 0; i < kMaxValueSize + 1; i++) { + buf[i] = 0xee; + } + ASSERT_EQ(size_t(0), mMBC->get("abcd", 4, buf, kMaxValueSize + 1)); + for (int i = 0; i < kMaxValueSize + 1; i++) { + SCOPED_TRACE(i); + ASSERT_EQ(0xee, buf[i]); + } +} + +TEST_F(MultifileBlobCacheTest, CacheMaxKeySizeSucceeds) { + char key[kMaxKeySize]; + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + for (int i = 0; i < kMaxKeySize; i++) { + key[i] = 'a'; + } + mMBC->set(key, kMaxKeySize, "wxyz", 4); + ASSERT_EQ(size_t(4), mMBC->get(key, kMaxKeySize, buf, 4)); + ASSERT_EQ('w', buf[0]); + ASSERT_EQ('x', buf[1]); + ASSERT_EQ('y', buf[2]); + ASSERT_EQ('z', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, CacheMaxValueSizeSucceeds) { + char buf[kMaxValueSize]; + for (int i = 0; i < kMaxValueSize; i++) { + buf[i] = 'b'; + } + mMBC->set("abcd", 4, buf, kMaxValueSize); + for (int i = 0; i < kMaxValueSize; i++) { + buf[i] = 0xee; + } + mMBC->get("abcd", 4, buf, kMaxValueSize); + for (int i = 0; i < kMaxValueSize; i++) { + SCOPED_TRACE(i); + ASSERT_EQ('b', buf[i]); + } +} + +TEST_F(MultifileBlobCacheTest, CacheMinKeyAndValueSizeSucceeds) { + unsigned char buf[1] = {0xee}; + mMBC->set("x", 1, "y", 1); + ASSERT_EQ(size_t(1), mMBC->get("x", 1, buf, 1)); + ASSERT_EQ('y', buf[0]); +} + +} // namespace android diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp index 1e8a34863d..b00ee33374 100644 --- a/opengl/libs/EGL/egl_cache.cpp +++ b/opengl/libs/EGL/egl_cache.cpp @@ -14,6 +14,8 @@ ** limitations under the License. */ +// #define LOG_NDEBUG 0 + #include "egl_cache.h" #include @@ -25,22 +27,19 @@ #include #include "../egl_impl.h" -#include "egl_cache_multifile.h" #include "egl_display.h" // Monolithic cache size limits. -static const size_t maxKeySize = 12 * 1024; -static const size_t maxValueSize = 64 * 1024; -static const size_t maxTotalSize = 32 * 1024 * 1024; +static const size_t kMaxMonolithicKeySize = 12 * 1024; +static const size_t kMaxMonolithicValueSize = 64 * 1024; +static const size_t kMaxMonolithicTotalSize = 2 * 1024 * 1024; // The time in seconds to wait before saving newly inserted monolithic cache entries. -static const unsigned int deferredSaveDelay = 4; - -// Multifile cache size limit -constexpr size_t kMultifileCacheByteLimit = 64 * 1024 * 1024; +static const unsigned int kDeferredMonolithicSaveDelay = 4; -// Delay before cleaning up multifile cache entries -static const unsigned int deferredMultifileCleanupDelaySeconds = 1; +// Multifile cache size limits +constexpr uint32_t kMultifileHotCacheLimit = 8 * 1024 * 1024; +constexpr uint32_t kMultifileCacheByteLimit = 32 * 1024 * 1024; namespace android { @@ -68,10 +67,7 @@ static EGLsizeiANDROID getBlob(const void* key, EGLsizeiANDROID keySize, void* v // egl_cache_t definition // egl_cache_t::egl_cache_t() - : mInitialized(false), - mMultifileMode(false), - mCacheByteLimit(maxTotalSize), - mMultifileCleanupPending(false) {} + : mInitialized(false), mMultifileMode(false), mCacheByteLimit(kMaxMonolithicTotalSize) {} egl_cache_t::~egl_cache_t() {} @@ -85,7 +81,7 @@ void egl_cache_t::initialize(egl_display_t* display) { std::lock_guard lock(mMutex); egl_connection_t* const cnx = &gEGLImpl; - if (cnx->dso && cnx->major >= 0 && cnx->minor >= 0) { + if (display && cnx->dso && cnx->major >= 0 && cnx->minor >= 0) { const char* exts = display->disp.queryString.extensions; size_t bcExtLen = strlen(BC_EXT_STR); size_t extsLen = strlen(exts); @@ -114,14 +110,36 @@ void egl_cache_t::initialize(egl_display_t* display) { } } - // Allow forcing monolithic cache for debug purposes - if (base::GetProperty("debug.egl.blobcache.multifilemode", "") == "false") { - ALOGD("Forcing monolithic cache due to debug.egl.blobcache.multifilemode == \"false\""); + // Check the device config to decide whether multifile should be used + if (base::GetBoolProperty("ro.egl.blobcache.multifile", false)) { + mMultifileMode = true; + ALOGV("Using multifile EGL blobcache"); + } + + // Allow forcing the mode for debug purposes + std::string mode = base::GetProperty("debug.egl.blobcache.multifile", ""); + if (mode == "true") { + ALOGV("Forcing multifile cache due to debug.egl.blobcache.multifile == %s", mode.c_str()); + mMultifileMode = true; + } else if (mode == "false") { + ALOGV("Forcing monolithic cache due to debug.egl.blobcache.multifile == %s", mode.c_str()); mMultifileMode = false; } if (mMultifileMode) { - mCacheByteLimit = kMultifileCacheByteLimit; + mCacheByteLimit = static_cast( + base::GetUintProperty("ro.egl.blobcache.multifile_limit", + kMultifileCacheByteLimit)); + + // Check for a debug value + int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.multifile_limit", -1); + if (debugCacheSize >= 0) { + ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.multifile_limit", + mCacheByteLimit, debugCacheSize); + mCacheByteLimit = debugCacheSize; + } + + ALOGV("Using multifile EGL blobcache limit of %zu bytes", mCacheByteLimit); } mInitialized = true; @@ -133,10 +151,10 @@ void egl_cache_t::terminate() { mBlobCache->writeToFile(); } mBlobCache = nullptr; - if (mMultifileMode) { - checkMultifileCacheSize(mCacheByteLimit); + if (mMultifileBlobCache) { + mMultifileBlobCache->finish(); } - mMultifileMode = false; + mMultifileBlobCache = nullptr; mInitialized = false; } @@ -151,20 +169,8 @@ void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void* if (mInitialized) { if (mMultifileMode) { - setBlobMultifile(key, keySize, value, valueSize, mFilename); - - if (!mMultifileCleanupPending) { - mMultifileCleanupPending = true; - // Kick off a thread to cull cache files below limit - std::thread deferredMultifileCleanupThread([this]() { - sleep(deferredMultifileCleanupDelaySeconds); - std::lock_guard lock(mMutex); - // Check the size of cache and remove entries to stay under limit - checkMultifileCacheSize(mCacheByteLimit); - mMultifileCleanupPending = false; - }); - deferredMultifileCleanupThread.detach(); - } + MultifileBlobCache* mbc = getMultifileBlobCacheLocked(); + mbc->set(key, keySize, value, valueSize); } else { BlobCache* bc = getBlobCacheLocked(); bc->set(key, keySize, value, valueSize); @@ -172,7 +178,7 @@ void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void* if (!mSavePending) { mSavePending = true; std::thread deferredSaveThread([this]() { - sleep(deferredSaveDelay); + sleep(kDeferredMonolithicSaveDelay); std::lock_guard lock(mMutex); if (mInitialized && mBlobCache) { mBlobCache->writeToFile(); @@ -196,15 +202,21 @@ EGLsizeiANDROID egl_cache_t::getBlob(const void* key, EGLsizeiANDROID keySize, v if (mInitialized) { if (mMultifileMode) { - return getBlobMultifile(key, keySize, value, valueSize, mFilename); + MultifileBlobCache* mbc = getMultifileBlobCacheLocked(); + return mbc->get(key, keySize, value, valueSize); } else { BlobCache* bc = getBlobCacheLocked(); return bc->get(key, keySize, value, valueSize); } } + return 0; } +void egl_cache_t::setCacheMode(EGLCacheMode cacheMode) { + mMultifileMode = (cacheMode == EGLCacheMode::Multifile); +} + void egl_cache_t::setCacheFilename(const char* filename) { std::lock_guard lock(mMutex); mFilename = filename; @@ -216,7 +228,7 @@ void egl_cache_t::setCacheLimit(int64_t cacheByteLimit) { if (!mMultifileMode) { // If we're not in multifile mode, ensure the cache limit is only being lowered, // not increasing above the hard coded platform limit - if (cacheByteLimit > maxTotalSize) { + if (cacheByteLimit > kMaxMonolithicTotalSize) { return; } } @@ -226,8 +238,8 @@ void egl_cache_t::setCacheLimit(int64_t cacheByteLimit) { size_t egl_cache_t::getCacheSize() { std::lock_guard lock(mMutex); - if (mMultifileMode) { - return getMultifileCacheSize(); + if (mMultifileBlobCache) { + return mMultifileBlobCache->getTotalSize(); } if (mBlobCache) { return mBlobCache->getSize(); @@ -237,9 +249,18 @@ size_t egl_cache_t::getCacheSize() { BlobCache* egl_cache_t::getBlobCacheLocked() { if (mBlobCache == nullptr) { - mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, mCacheByteLimit, mFilename)); + mBlobCache.reset(new FileBlobCache(kMaxMonolithicKeySize, kMaxMonolithicValueSize, + mCacheByteLimit, mFilename)); } return mBlobCache.get(); } +MultifileBlobCache* egl_cache_t::getMultifileBlobCacheLocked() { + if (mMultifileBlobCache == nullptr) { + mMultifileBlobCache.reset( + new MultifileBlobCache(mCacheByteLimit, kMultifileHotCacheLimit, mFilename)); + } + return mMultifileBlobCache.get(); +} + }; // namespace android diff --git a/opengl/libs/EGL/egl_cache.h b/opengl/libs/EGL/egl_cache.h index 2dcd803324..1399368dd8 100644 --- a/opengl/libs/EGL/egl_cache.h +++ b/opengl/libs/EGL/egl_cache.h @@ -25,6 +25,7 @@ #include #include "FileBlobCache.h" +#include "MultifileBlobCache.h" namespace android { @@ -32,6 +33,11 @@ class egl_display_t; class EGLAPI egl_cache_t { public: + enum class EGLCacheMode { + Monolithic, + Multifile, + }; + // get returns a pointer to the singleton egl_cache_t object. This // singleton object will never be destroyed. static egl_cache_t* get(); @@ -64,6 +70,9 @@ public: // cache contents from one program invocation to another. void setCacheFilename(const char* filename); + // Allow setting monolithic or multifile modes + void setCacheMode(EGLCacheMode cacheMode); + // Allow the fixed cache limit to be overridden void setCacheLimit(int64_t cacheByteLimit); @@ -85,6 +94,9 @@ private: // possible. BlobCache* getBlobCacheLocked(); + // Get or create the multifile blobcache + MultifileBlobCache* getMultifileBlobCacheLocked(); + // mInitialized indicates whether the egl_cache_t is in the initialized // state. It is initialized to false at construction time, and gets set to // true when initialize is called. It is set back to false when terminate @@ -98,6 +110,9 @@ private: // first time it's needed. std::unique_ptr mBlobCache; + // The multifile version of blobcache allowing larger contents to be stored + std::unique_ptr mMultifileBlobCache; + // mFilename is the name of the file for storing cache contents in between // program invocations. It is initialized to an empty string at // construction time, and can be set with the setCacheFilename method. An @@ -123,11 +138,7 @@ private: bool mMultifileMode; // Cache limit - int64_t mCacheByteLimit; - - // Whether we've kicked off a side thread that will check the multifile - // cache size and remove entries if needed. - bool mMultifileCleanupPending; + size_t mCacheByteLimit; }; }; // namespace android diff --git a/opengl/libs/EGL/egl_cache_multifile.cpp b/opengl/libs/EGL/egl_cache_multifile.cpp deleted file mode 100644 index 48e557f190..0000000000 --- a/opengl/libs/EGL/egl_cache_multifile.cpp +++ /dev/null @@ -1,343 +0,0 @@ -/* - ** Copyright 2022, The Android Open Source Project - ** - ** Licensed under the Apache License, Version 2.0 (the "License"); - ** you may not use this file except in compliance with the License. - ** You may obtain a copy of the License at - ** - ** http://www.apache.org/licenses/LICENSE-2.0 - ** - ** Unless required by applicable law or agreed to in writing, software - ** distributed under the License is distributed on an "AS IS" BASIS, - ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ** See the License for the specific language governing permissions and - ** limitations under the License. - */ - -// #define LOG_NDEBUG 0 - -#include "egl_cache_multifile.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -static std::string multifileDirName = ""; - -using namespace std::literals; - -namespace { - -// Create a directory for tracking multiple files -void setupMultifile(const std::string& baseDir) { - // If we've already set up the multifile dir in this base directory, we're done - if (!multifileDirName.empty() && multifileDirName.find(baseDir) != std::string::npos) { - return; - } - - // Otherwise, create it - multifileDirName = baseDir + ".multifile"; - if (mkdir(multifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) { - ALOGW("Unable to create directory (%s), errno (%i)", multifileDirName.c_str(), errno); - } -} - -// Create a filename that is based on the hash of the key -std::string getCacheEntryFilename(const void* key, EGLsizeiANDROID keySize, - const std::string& baseDir) { - // Hash the key into a string - std::stringstream keyName; - keyName << android::JenkinsHashMixBytes(0, static_cast(key), keySize); - - // Build a filename using dir and hash - return baseDir + "/" + keyName.str(); -} - -// Determine file age based on stat modification time -// Newer files have a higher age (time since epoch) -time_t getFileAge(const std::string& filePath) { - struct stat st; - if (stat(filePath.c_str(), &st) == 0) { - ALOGD("getFileAge returning %" PRId64 " for file age", static_cast(st.st_mtime)); - return st.st_mtime; - } else { - ALOGW("Failed to stat %s", filePath.c_str()); - return 0; - } -} - -size_t getFileSize(const std::string& filePath) { - struct stat st; - if (stat(filePath.c_str(), &st) != 0) { - ALOGE("Unable to stat %s", filePath.c_str()); - return 0; - } - return st.st_size; -} - -// Walk through directory entries and track age and size -// Then iterate through the entries, oldest first, and remove them until under the limit. -// This will need to be updated if we move to a multilevel cache dir. -bool applyLRU(size_t cacheLimit) { - // Build a multimap of files indexed by age. - // They will be automatically sorted smallest (oldest) to largest (newest) - std::multimap agesToFiles; - - // Map files to sizes - std::unordered_map filesToSizes; - - size_t totalCacheSize = 0; - - DIR* dir; - struct dirent* entry; - if ((dir = opendir(multifileDirName.c_str())) != nullptr) { - while ((entry = readdir(dir)) != nullptr) { - if (entry->d_name == "."s || entry->d_name == ".."s) { - continue; - } - - // Look up each file age - std::string fullPath = multifileDirName + "/" + entry->d_name; - time_t fileAge = getFileAge(fullPath); - - // Track the files, sorted by age - agesToFiles.insert(std::make_pair(fileAge, fullPath)); - - // Also track the size so we know how much room we have freed - size_t fileSize = getFileSize(fullPath); - filesToSizes[fullPath] = fileSize; - totalCacheSize += fileSize; - } - closedir(dir); - } else { - ALOGE("Unable to open filename: %s", multifileDirName.c_str()); - return false; - } - - if (totalCacheSize <= cacheLimit) { - // If LRU was called on a sufficiently small cache, no need to remove anything - return true; - } - - // Walk through the map of files until we're under the cache size - for (const auto& cacheEntryIter : agesToFiles) { - time_t entryAge = cacheEntryIter.first; - const std::string entryPath = cacheEntryIter.second; - - ALOGD("Removing %s with age %ld", entryPath.c_str(), entryAge); - if (std::remove(entryPath.c_str()) != 0) { - ALOGE("Error removing %s: %s", entryPath.c_str(), std::strerror(errno)); - return false; - } - - totalCacheSize -= filesToSizes[entryPath]; - if (totalCacheSize <= cacheLimit) { - // Success - ALOGV("Reduced cache to %zu", totalCacheSize); - return true; - } else { - ALOGD("Cache size is still too large (%zu), removing more files", totalCacheSize); - } - } - - // Should never reach this return - return false; -} - -} // namespace - -namespace android { - -void setBlobMultifile(const void* key, EGLsizeiANDROID keySize, const void* value, - EGLsizeiANDROID valueSize, const std::string& baseDir) { - if (baseDir.empty()) { - return; - } - - setupMultifile(baseDir); - std::string filename = getCacheEntryFilename(key, keySize, multifileDirName); - - ALOGD("Attempting to open filename for set: %s", filename.c_str()); - std::ofstream outfile(filename, std::ofstream::binary); - if (outfile.fail()) { - ALOGW("Unable to open filename: %s", filename.c_str()); - return; - } - - // First write the key - outfile.write(static_cast(key), keySize); - if (outfile.bad()) { - ALOGW("Unable to write key to filename: %s", filename.c_str()); - outfile.close(); - return; - } - ALOGD("Wrote %i bytes to out file for key", static_cast(outfile.tellp())); - - // Then write the value - outfile.write(static_cast(value), valueSize); - if (outfile.bad()) { - ALOGW("Unable to write value to filename: %s", filename.c_str()); - outfile.close(); - return; - } - ALOGD("Wrote %i bytes to out file for full entry", static_cast(outfile.tellp())); - - outfile.close(); -} - -EGLsizeiANDROID getBlobMultifile(const void* key, EGLsizeiANDROID keySize, void* value, - EGLsizeiANDROID valueSize, const std::string& baseDir) { - if (baseDir.empty()) { - return 0; - } - - setupMultifile(baseDir); - std::string filename = getCacheEntryFilename(key, keySize, multifileDirName); - - // Open the hashed filename path - ALOGD("Attempting to open filename for get: %s", filename.c_str()); - int fd = open(filename.c_str(), O_RDONLY); - - // File doesn't exist, this is a MISS, return zero bytes read - if (fd == -1) { - ALOGD("Cache MISS - failed to open filename: %s, error: %s", filename.c_str(), - std::strerror(errno)); - return 0; - } - - ALOGD("Cache HIT - opened filename: %s", filename.c_str()); - - // Get the size of the file - size_t entrySize = getFileSize(filename); - if (keySize > entrySize) { - ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified " - "file", - keySize, entrySize); - close(fd); - return 0; - } - - // Memory map the file - uint8_t* cacheEntry = - reinterpret_cast(mmap(nullptr, entrySize, PROT_READ, MAP_PRIVATE, fd, 0)); - if (cacheEntry == MAP_FAILED) { - ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); - close(fd); - return 0; - } - - // Compare the incoming key with our stored version (the beginning of the entry) - int compare = memcmp(cacheEntry, key, keySize); - if (compare != 0) { - ALOGW("Cached key and new key do not match! This is a hash collision or modified file"); - munmap(cacheEntry, entrySize); - close(fd); - return 0; - } - - // Keys matched, so remaining cache is value size - size_t cachedValueSize = entrySize - keySize; - - // Return actual value size if valueSize is not large enough - if (cachedValueSize > valueSize) { - ALOGD("Skipping file read, not enough room provided (valueSize): %lu, " - "returning required space as %zu", - valueSize, cachedValueSize); - munmap(cacheEntry, entrySize); - close(fd); - return cachedValueSize; - } - - // Remaining entry following the key is the value - uint8_t* cachedValue = cacheEntry + keySize; - memcpy(value, cachedValue, cachedValueSize); - munmap(cacheEntry, entrySize); - close(fd); - - ALOGD("Read %zu bytes from %s", cachedValueSize, filename.c_str()); - return cachedValueSize; -} - -// Walk through the files in our flat directory, checking the size of each one. -// Return the total size of normal files in the directory. -// This will need to be updated if we move to a multilevel cache dir. -size_t getMultifileCacheSize() { - if (multifileDirName.empty()) { - return 0; - } - - DIR* dir; - struct dirent* entry; - size_t size = 0; - - ALOGD("Using %s as the multifile cache dir ", multifileDirName.c_str()); - - if ((dir = opendir(multifileDirName.c_str())) != nullptr) { - while ((entry = readdir(dir)) != nullptr) { - if (entry->d_name == "."s || entry->d_name == ".."s) { - continue; - } - - // Add up the size of all files in the dir - std::string fullPath = multifileDirName + "/" + entry->d_name; - size += getFileSize(fullPath); - } - closedir(dir); - } else { - ALOGW("Unable to open filename: %s", multifileDirName.c_str()); - return 0; - } - - return size; -} - -// When removing files, what fraction of the overall limit should be reached when removing files -// A divisor of two will decrease the cache to 50%, four to 25% and so on -constexpr uint32_t kCacheLimitDivisor = 2; - -// Calculate the cache size and remove old entries until under the limit -void checkMultifileCacheSize(size_t cacheByteLimit) { - // Start with the value provided by egl_cache - size_t limit = cacheByteLimit; - - // Check for a debug value - int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.bytelimit", -1); - if (debugCacheSize >= 0) { - ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.bytelimit", limit, - debugCacheSize); - limit = debugCacheSize; - } - - // Tally up the initial amount of cache in use - size_t size = getMultifileCacheSize(); - ALOGD("Multifile cache dir size: %zu", size); - - // If size is larger than the threshold, remove files using LRU - if (size > limit) { - ALOGV("Multifile cache size is larger than %zu, removing old entries", cacheByteLimit); - if (!applyLRU(limit / kCacheLimitDivisor)) { - ALOGE("Error when clearing multifile shader cache"); - return; - } - } - ALOGD("Multifile cache size after reduction: %zu", getMultifileCacheSize()); -} - -}; // namespace android \ No newline at end of file diff --git a/opengl/libs/EGL/egl_cache_multifile.h b/opengl/libs/EGL/egl_cache_multifile.h deleted file mode 100644 index ee5fe8108d..0000000000 --- a/opengl/libs/EGL/egl_cache_multifile.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - ** Copyright 2022, The Android Open Source Project - ** - ** Licensed under the Apache License, Version 2.0 (the "License"); - ** you may not use this file except in compliance with the License. - ** You may obtain a copy of the License at - ** - ** http://www.apache.org/licenses/LICENSE-2.0 - ** - ** Unless required by applicable law or agreed to in writing, software - ** distributed under the License is distributed on an "AS IS" BASIS, - ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ** See the License for the specific language governing permissions and - ** limitations under the License. - */ - -#ifndef ANDROID_EGL_CACHE_MULTIFILE_H -#define ANDROID_EGL_CACHE_MULTIFILE_H - -#include -#include - -#include - -namespace android { - -void setBlobMultifile(const void* key, EGLsizeiANDROID keySize, const void* value, - EGLsizeiANDROID valueSize, const std::string& baseDir); -EGLsizeiANDROID getBlobMultifile(const void* key, EGLsizeiANDROID keySize, void* value, - EGLsizeiANDROID valueSize, const std::string& baseDir); -size_t getMultifileCacheSize(); -void checkMultifileCacheSize(size_t cacheByteLimit); - -}; // namespace android - -#endif // ANDROID_EGL_CACHE_MULTIFILE_H diff --git a/opengl/tests/EGLTest/egl_cache_test.cpp b/opengl/tests/EGLTest/egl_cache_test.cpp index 265bec492e..32e408c1cd 100644 --- a/opengl/tests/EGLTest/egl_cache_test.cpp +++ b/opengl/tests/EGLTest/egl_cache_test.cpp @@ -24,7 +24,7 @@ #include #include "egl_cache.h" -#include "egl_cache_multifile.h" +#include "MultifileBlobCache.h" #include "egl_display.h" #include @@ -33,12 +33,16 @@ using namespace std::literals; namespace android { -class EGLCacheTest : public ::testing::Test { +class EGLCacheTest : public ::testing::TestWithParam { protected: virtual void SetUp() { - mCache = egl_cache_t::get(); + // Terminate to clean up any previous cache in this process + mCache->terminate(); + mTempFile.reset(new TemporaryFile()); mCache->setCacheFilename(&mTempFile->path[0]); + mCache->setCacheLimit(1024); + mCache->setCacheMode(mCacheMode); } virtual void TearDown() { @@ -49,11 +53,12 @@ protected: std::string getCachefileName(); - egl_cache_t* mCache; + egl_cache_t* mCache = egl_cache_t::get(); std::unique_ptr mTempFile; + egl_cache_t::EGLCacheMode mCacheMode = GetParam(); }; -TEST_F(EGLCacheTest, UninitializedCacheAlwaysMisses) { +TEST_P(EGLCacheTest, UninitializedCacheAlwaysMisses) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->setBlob("abcd", 4, "efgh", 4); ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4)); @@ -63,7 +68,7 @@ TEST_F(EGLCacheTest, UninitializedCacheAlwaysMisses) { ASSERT_EQ(0xee, buf[3]); } -TEST_F(EGLCacheTest, InitializedCacheAlwaysHits) { +TEST_P(EGLCacheTest, InitializedCacheAlwaysHits) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -74,7 +79,7 @@ TEST_F(EGLCacheTest, InitializedCacheAlwaysHits) { ASSERT_EQ('h', buf[3]); } -TEST_F(EGLCacheTest, TerminatedCacheAlwaysMisses) { +TEST_P(EGLCacheTest, TerminatedCacheAlwaysMisses) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -86,7 +91,7 @@ TEST_F(EGLCacheTest, TerminatedCacheAlwaysMisses) { ASSERT_EQ(0xee, buf[3]); } -TEST_F(EGLCacheTest, ReinitializedCacheContainsValues) { +TEST_P(EGLCacheTest, ReinitializedCacheContainsValues) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -101,12 +106,12 @@ TEST_F(EGLCacheTest, ReinitializedCacheContainsValues) { std::string EGLCacheTest::getCachefileName() { // Return the monolithic filename unless we find the multifile dir - std::string cachefileName = &mTempFile->path[0]; - std::string multifileDirName = cachefileName + ".multifile"; + std::string cachePath = &mTempFile->path[0]; + std::string multifileDirName = cachePath + ".multifile"; + std::string cachefileName = ""; struct stat info; if (stat(multifileDirName.c_str(), &info) == 0) { - // Ensure we only have one file to manage int realFileCount = 0; @@ -121,6 +126,8 @@ std::string EGLCacheTest::getCachefileName() { cachefileName = multifileDirName + "/" + entry->d_name; realFileCount++; } + } else { + printf("Unable to open %s, error: %s\n", multifileDirName.c_str(), std::strerror(errno)); } if (realFileCount != 1) { @@ -128,14 +135,18 @@ std::string EGLCacheTest::getCachefileName() { // violates test assumptions cachefileName = ""; } + } else { + printf("Unable to stat %s, error: %s\n", multifileDirName.c_str(), std::strerror(errno)); } return cachefileName; } -TEST_F(EGLCacheTest, ModifiedCacheMisses) { - // Turn this back on if multifile becomes the default - GTEST_SKIP() << "Skipping test designed for multifile, see b/263574392 and b/246966894"; +TEST_P(EGLCacheTest, ModifiedCacheMisses) { + // Skip if not in multifile mode + if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) { + GTEST_SKIP() << "Skipping test designed for multifile"; + } uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); @@ -147,13 +158,13 @@ TEST_F(EGLCacheTest, ModifiedCacheMisses) { ASSERT_EQ('g', buf[2]); ASSERT_EQ('h', buf[3]); + // Ensure the cache file is written to disk + mCache->terminate(); + // Depending on the cache mode, the file will be in different locations std::string cachefileName = getCachefileName(); ASSERT_TRUE(cachefileName.length() > 0); - // Ensure the cache file is written to disk - mCache->terminate(); - // Stomp on the beginning of the cache file, breaking the key match const long stomp = 0xbadf00d; FILE *file = fopen(cachefileName.c_str(), "w"); @@ -164,14 +175,15 @@ TEST_F(EGLCacheTest, ModifiedCacheMisses) { // Ensure no cache hit mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); uint8_t buf2[4] = { 0xee, 0xee, 0xee, 0xee }; - ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf2, 4)); + // getBlob may return junk for required size, but should not return a cache hit + mCache->getBlob("abcd", 4, buf2, 4); ASSERT_EQ(0xee, buf2[0]); ASSERT_EQ(0xee, buf2[1]); ASSERT_EQ(0xee, buf2[2]); ASSERT_EQ(0xee, buf2[3]); } -TEST_F(EGLCacheTest, TerminatedCacheBelowCacheLimit) { +TEST_P(EGLCacheTest, TerminatedCacheBelowCacheLimit) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); @@ -204,4 +216,6 @@ TEST_F(EGLCacheTest, TerminatedCacheBelowCacheLimit) { ASSERT_LE(mCache->getCacheSize(), 4); } +INSTANTIATE_TEST_CASE_P(MonolithicCacheTests, EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Monolithic)); +INSTANTIATE_TEST_CASE_P(MultifileCacheTests, EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Multifile)); } -- GitLab From 032c7dcf744f4c2c16755b4c34e0cc24abe54a7d Mon Sep 17 00:00:00 2001 From: Seunghwan Choi Date: Thu, 12 Jan 2023 16:03:47 +0900 Subject: [PATCH 0802/1310] [scribe] show stylus hover icon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Show a pointer when the stylus is hovering on the display. Currently, pointers are only used for ‘pointer’ touch devices such as trackpad and they are not used for ‘direct’ touch devices such as touchscreen or stylus. As internal stylus devices are supposed to be ‘direct’, add an implementation for direct touch devices. Test: Manual Test Bug: b/215436642 Change-Id: I0c57c40c496246b35b155a679959e12edb75d1e6 --- .../reader/mapper/TouchInputMapper.cpp | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index d415854e15..9003e58595 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -980,7 +980,7 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) // Create pointer controller if needed, and keep it around if Pointer Capture is enabled to // preserve the cursor position. if (mDeviceMode == DeviceMode::POINTER || - (mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches) || + (mDeviceMode == DeviceMode::DIRECT && (mConfig.showTouches || hasStylus())) || (mParameters.deviceType == Parameters::DeviceType::POINTER && mConfig.pointerCaptureRequest.enable)) { if (mPointerController == nullptr) { @@ -3650,6 +3650,14 @@ std::list TouchInputMapper::abortPointerSimple(nsecs_t when, nsecs_t return out; } +static bool isStylusEvent(uint32_t source, int32_t action, const PointerProperties* properties) { + if (!isFromSource(source, AINPUT_SOURCE_STYLUS)) { + return false; + } + const auto actionIndex = action >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + return isStylusToolType(properties[actionIndex].toolType); +} + NotifyMotionArgs TouchInputMapper::dispatchMotion( nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action, int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState, @@ -3691,6 +3699,25 @@ NotifyMotionArgs TouchInputMapper::dispatchMotion( ALOG_ASSERT(false); } } + const bool isStylus = isStylusEvent(source, action, pointerProperties); + if (isStylus) { + switch (action & AMOTION_EVENT_ACTION_MASK) { + case AMOTION_EVENT_ACTION_HOVER_ENTER: + case AMOTION_EVENT_ACTION_HOVER_MOVE: + mPointerController->setPresentation( + PointerControllerInterface::Presentation::POINTER); + mPointerController + ->setPosition(mCurrentCookedState.cookedPointerData.pointerCoords[0].getX(), + mCurrentCookedState.cookedPointerData.pointerCoords[0] + .getY()); + mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); + break; + case AMOTION_EVENT_ACTION_HOVER_EXIT: + mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); + break; + } + } + float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; if (mDeviceMode == DeviceMode::POINTER) { -- GitLab From 75789cd72bc516f3f9680a3f3c31a1e0015c1c98 Mon Sep 17 00:00:00 2001 From: Seunghwan Choi Date: Fri, 13 Jan 2023 20:31:59 +0900 Subject: [PATCH 0803/1310] Separate default pointer for mouse and stylus (native part) Let PointerController (MouseCursorController) know the source of the current event, either mouse or stylus. MouseCursorController will show the proper default pointer for the active source, if the requested pointer type is TYPE_NOT_SPECIFIED. Test: Manual Test Bug: b/215436642 Change-Id: I91f702db661846fc7ad857f71656903c8aa4334a --- include/input/Input.h | 1 + services/inputflinger/include/PointerControllerInterface.h | 4 +++- services/inputflinger/reader/mapper/TouchInputMapper.cpp | 7 ++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/include/input/Input.h b/include/input/Input.h index 7573282fc1..608519b70e 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -1113,6 +1113,7 @@ public: enum class PointerIconStyle : int32_t { TYPE_CUSTOM = -1, TYPE_NULL = 0, + TYPE_NOT_SPECIFIED = 1, TYPE_ARROW = 1000, TYPE_CONTEXT_MENU = 1001, TYPE_HAND = 1002, diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h index 7e0c1c77eb..9dbdd5ad0f 100644 --- a/services/inputflinger/include/PointerControllerInterface.h +++ b/services/inputflinger/include/PointerControllerInterface.h @@ -79,8 +79,10 @@ public: POINTER, // Show spots and a spot anchor in place of the mouse pointer. SPOT, + // Show the stylus hover pointer. + STYLUS_HOVER, - ftl_last = SPOT, + ftl_last = STYLUS_HOVER, }; /* Sets the mode of the pointer controller. */ diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 9003e58595..dc27622b3c 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -3699,13 +3699,14 @@ NotifyMotionArgs TouchInputMapper::dispatchMotion( ALOG_ASSERT(false); } } - const bool isStylus = isStylusEvent(source, action, pointerProperties); - if (isStylus) { + const bool isDirectStylus = + mDeviceMode == DeviceMode::DIRECT && isStylusEvent(source, action, pointerProperties); + if (isDirectStylus) { switch (action & AMOTION_EVENT_ACTION_MASK) { case AMOTION_EVENT_ACTION_HOVER_ENTER: case AMOTION_EVENT_ACTION_HOVER_MOVE: mPointerController->setPresentation( - PointerControllerInterface::Presentation::POINTER); + PointerControllerInterface::Presentation::STYLUS_HOVER); mPointerController ->setPosition(mCurrentCookedState.cookedPointerData.pointerCoords[0].getX(), mCurrentCookedState.cookedPointerData.pointerCoords[0] -- GitLab From 2de48e444c13ef4b81ad55931b72594b6a9c56ff Mon Sep 17 00:00:00 2001 From: Seunghwan Choi Date: Tue, 17 Jan 2023 20:45:15 +0900 Subject: [PATCH 0804/1310] check config for showing a stylus pointer (native part) On InputReader, get the configuration from config.xml to decide whether to show a stylus pointer. Test: Manual Test Bug: b/215436642 Change-Id: I3b42690cccf3ab3ada798c73b3114181fbe881c1 --- .../inputflinger/include/InputReaderBase.h | 6 +++- .../reader/mapper/TouchInputMapper.cpp | 28 ++++++++++++------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index 2173117fed..841c914d60 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -333,6 +333,9 @@ struct InputReaderConfiguration { // stylus button state changes are reported through motion events. bool stylusButtonMotionEventsEnabled; + // True if a pointer icon should be shown for direct stylus pointers. + bool stylusPointerIconEnabled; + InputReaderConfiguration() : virtualKeyQuietTime(0), pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f, @@ -358,7 +361,8 @@ struct InputReaderConfiguration { touchpadNaturalScrollingEnabled(true), touchpadTapToClickEnabled(true), touchpadRightClickZoneEnabled(false), - stylusButtonMotionEventsEnabled(true) {} + stylusButtonMotionEventsEnabled(true), + stylusPointerIconEnabled(false) {} static std::string changesToString(uint32_t changes); diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index dc27622b3c..9b2cf029da 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -977,12 +977,18 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) mOrientedRanges.clear(); } - // Create pointer controller if needed, and keep it around if Pointer Capture is enabled to - // preserve the cursor position. - if (mDeviceMode == DeviceMode::POINTER || - (mDeviceMode == DeviceMode::DIRECT && (mConfig.showTouches || hasStylus())) || - (mParameters.deviceType == Parameters::DeviceType::POINTER && - mConfig.pointerCaptureRequest.enable)) { + // Create and preserve the pointer controller in the following cases: + const bool isPointerControllerNeeded = + // - when the device is in pointer mode, to show the mouse cursor; + (mDeviceMode == DeviceMode::POINTER) || + // - when pointer capture is enabled, to preserve the mouse cursor position; + (mParameters.deviceType == Parameters::DeviceType::POINTER && + mConfig.pointerCaptureRequest.enable) || + // - when we should be showing touches; + (mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches) || + // - when we should be showing a pointer icon for direct styluses. + (mDeviceMode == DeviceMode::DIRECT && mConfig.stylusPointerIconEnabled && hasStylus()); + if (isPointerControllerNeeded) { if (mPointerController == nullptr) { mPointerController = getContext()->getPointerController(getDeviceId()); } @@ -3699,9 +3705,12 @@ NotifyMotionArgs TouchInputMapper::dispatchMotion( ALOG_ASSERT(false); } } - const bool isDirectStylus = - mDeviceMode == DeviceMode::DIRECT && isStylusEvent(source, action, pointerProperties); - if (isDirectStylus) { + + const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE); + const bool showDirectStylusPointer = mConfig.stylusPointerIconEnabled && + mDeviceMode == DeviceMode::DIRECT && isStylusEvent(source, action, pointerProperties) && + displayId != ADISPLAY_ID_NONE && displayId == mPointerController->getDisplayId(); + if (showDirectStylusPointer) { switch (action & AMOTION_EVENT_ACTION_MASK) { case AMOTION_EVENT_ACTION_HOVER_ENTER: case AMOTION_EVENT_ACTION_HOVER_MOVE: @@ -3724,7 +3733,6 @@ NotifyMotionArgs TouchInputMapper::dispatchMotion( if (mDeviceMode == DeviceMode::POINTER) { mPointerController->getPosition(&xCursorPosition, &yCursorPosition); } - const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE); const int32_t deviceId = getDeviceId(); std::vector frames = getDeviceContext().getVideoFrames(); std::for_each(frames.begin(), frames.end(), -- GitLab From 356026cfba3ffaadbb767e4353aa1b6f0004877c Mon Sep 17 00:00:00 2001 From: Seunghwan Choi Date: Wed, 1 Feb 2023 14:37:25 +0900 Subject: [PATCH 0805/1310] Add testcase for StylusPointer add testcases for styluspointer Test: atest inputflinger_tests Bug: b/215436642 Change-Id: I898f2af2ba81ef4048281baf105fcbba6269ec47 --- .../reader/mapper/TouchInputMapper.cpp | 3 +- .../tests/FakeInputReaderPolicy.cpp | 4 + .../tests/FakeInputReaderPolicy.h | 1 + .../tests/FakePointerController.cpp | 11 +++ .../tests/FakePointerController.h | 6 +- .../inputflinger/tests/InputReader_test.cpp | 98 +++++++++++++++++++ 6 files changed, 120 insertions(+), 3 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 9b2cf029da..b789156a7c 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -3709,7 +3709,8 @@ NotifyMotionArgs TouchInputMapper::dispatchMotion( const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE); const bool showDirectStylusPointer = mConfig.stylusPointerIconEnabled && mDeviceMode == DeviceMode::DIRECT && isStylusEvent(source, action, pointerProperties) && - displayId != ADISPLAY_ID_NONE && displayId == mPointerController->getDisplayId(); + mPointerController && displayId != ADISPLAY_ID_NONE && + displayId == mPointerController->getDisplayId(); if (showDirectStylusPointer) { switch (action & AMOTION_EVENT_ACTION_MASK) { case AMOTION_EVENT_ACTION_HOVER_ENTER: diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp index bb8a30e5ba..30c1719c35 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp +++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp @@ -205,6 +205,10 @@ void FakeInputReaderPolicy::setStylusButtonMotionEventsEnabled(bool enabled) { mConfig.stylusButtonMotionEventsEnabled = enabled; } +void FakeInputReaderPolicy::setStylusPointerIconEnabled(bool enabled) { + mConfig.stylusPointerIconEnabled = enabled; +} + void FakeInputReaderPolicy::getReaderConfiguration(InputReaderConfiguration* outConfig) { *outConfig = mConfig; } diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h index 9ec3217d3e..28ac505284 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.h +++ b/services/inputflinger/tests/FakeInputReaderPolicy.h @@ -76,6 +76,7 @@ public: float getPointerGestureZoomSpeedRatio(); void setVelocityControlParams(const VelocityControlParameters& params); void setStylusButtonMotionEventsEnabled(bool enabled); + void setStylusPointerIconEnabled(bool enabled); private: void getReaderConfiguration(InputReaderConfiguration* outConfig) override; diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp index ab7879f1aa..28dad95d47 100644 --- a/services/inputflinger/tests/FakePointerController.cpp +++ b/services/inputflinger/tests/FakePointerController.cpp @@ -65,6 +65,10 @@ void FakePointerController::assertPosition(float x, float y) { ASSERT_NEAR(y, actualY, 1); } +bool FakePointerController::isPointerShown() { + return mIsPointerShown; +} + bool FakePointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const { *outMinX = mMinX; @@ -83,6 +87,13 @@ void FakePointerController::move(float deltaX, float deltaY) { if (mY > mMaxY) mY = mMaxY; } +void FakePointerController::fade(Transition) { + mIsPointerShown = false; +} +void FakePointerController::unfade(Transition) { + mIsPointerShown = true; +} + void FakePointerController::setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits, int32_t displayId) { std::vector newSpots; diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h index d10cbcd68b..dd56e65c01 100644 --- a/services/inputflinger/tests/FakePointerController.h +++ b/services/inputflinger/tests/FakePointerController.h @@ -39,12 +39,13 @@ public: void setDisplayViewport(const DisplayViewport& viewport) override; void assertPosition(float x, float y); + bool isPointerShown(); private: bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override; void move(float deltaX, float deltaY) override; - void fade(Transition) override {} - void unfade(Transition) override {} + void fade(Transition) override; + void unfade(Transition) override; void setPresentation(Presentation) override {} void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits, int32_t displayId) override; @@ -55,6 +56,7 @@ private: float mX{0}, mY{0}; int32_t mButtonState{0}; int32_t mDisplayId{ADISPLAY_ID_DEFAULT}; + bool mIsPointerShown{false}; std::map> mSpotsByDisplay; }; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index e1c54e9914..1b04375a56 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -6678,6 +6678,52 @@ TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsSetToTouchNavigation_setsCorr ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mapper.getSources()); } +TEST_F(SingleTouchInputMapperTest, Process_WhenConfigEnabled_ShouldShowDirectStylusPointer) { + std::shared_ptr fakePointerController = + std::make_shared(); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); + mFakePolicy->setPointerController(fakePointerController); + mFakePolicy->setStylusPointerIconEnabled(true); + SingleTouchInputMapper& mapper = addMapperAndConfigure(); + + processKey(mapper, BTN_TOOL_PEN, 1); + processMove(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithPointerCoords(0, toDisplayX(100), toDisplayY(200))))); + ASSERT_TRUE(fakePointerController->isPointerShown()); + ASSERT_NO_FATAL_FAILURE( + fakePointerController->assertPosition(toDisplayX(100), toDisplayY(200))); +} + +TEST_F(SingleTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirectStylusPointer) { + std::shared_ptr fakePointerController = + std::make_shared(); + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); + mFakePolicy->setPointerController(fakePointerController); + mFakePolicy->setStylusPointerIconEnabled(false); + SingleTouchInputMapper& mapper = addMapperAndConfigure(); + + processKey(mapper, BTN_TOOL_PEN, 1); + processMove(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithPointerCoords(0, toDisplayX(100), toDisplayY(200))))); + ASSERT_FALSE(fakePointerController->isPointerShown()); +} + // --- TouchDisplayProjectionTest --- class TouchDisplayProjectionTest : public SingleTouchInputMapperTest { @@ -9757,6 +9803,58 @@ TEST_F(MultiTouchInputMapperTest, StylusSourceIsAddedDynamicallyFromToolType) { WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); } +TEST_F(MultiTouchInputMapperTest, Process_WhenConfigEnabled_ShouldShowDirectStylusPointer) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareAxes(POSITION | ID | SLOT | TOOL_TYPE | PRESSURE); + // Add BTN_TOOL_PEN to statically show stylus support, since using ABS_MT_TOOL_TYPE can only + // indicate stylus presence dynamically. + mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); + std::shared_ptr fakePointerController = + std::make_shared(); + mFakePolicy->setPointerController(fakePointerController); + mFakePolicy->setStylusPointerIconEnabled(true); + MultiTouchInputMapper& mapper = addMapperAndConfigure(); + + processId(mapper, FIRST_TRACKING_ID); + processPressure(mapper, RAW_PRESSURE_MIN); + processPosition(mapper, 100, 200); + processToolType(mapper, MT_TOOL_PEN); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithPointerCoords(0, toDisplayX(100), toDisplayY(200))))); + ASSERT_TRUE(fakePointerController->isPointerShown()); + ASSERT_NO_FATAL_FAILURE( + fakePointerController->assertPosition(toDisplayX(100), toDisplayY(200))); +} + +TEST_F(MultiTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirectStylusPointer) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareAxes(POSITION | ID | SLOT | TOOL_TYPE | PRESSURE); + // Add BTN_TOOL_PEN to statically show stylus support, since using ABS_MT_TOOL_TYPE can only + // indicate stylus presence dynamically. + mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); + std::shared_ptr fakePointerController = + std::make_shared(); + mFakePolicy->setPointerController(fakePointerController); + mFakePolicy->setStylusPointerIconEnabled(false); + MultiTouchInputMapper& mapper = addMapperAndConfigure(); + + processId(mapper, FIRST_TRACKING_ID); + processPressure(mapper, RAW_PRESSURE_MIN); + processPosition(mapper, 100, 200); + processToolType(mapper, MT_TOOL_PEN); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithPointerCoords(0, toDisplayX(100), toDisplayY(200))))); + ASSERT_FALSE(fakePointerController->isPointerShown()); +} + // --- MultiTouchInputMapperTest_ExternalDevice --- class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest { -- GitLab From 62be1e9ca8dcce5eb32d7f3a5b4ef7a7f25420a5 Mon Sep 17 00:00:00 2001 From: Nick Deakin Date: Thu, 2 Feb 2023 16:09:53 -0500 Subject: [PATCH 0806/1310] jpegrecoverymap: fix linkage for tests. Ensure that the actual lib code in your repo is tested when running the tests by updating linkage from shared to static. This way one won't accidentally be testing whatever is on their system imagr rather than their working tree. Bug: 264715926 Test: tests pass Change-Id: I7cc4db6a59c42f72e90fcfb39c668c4776592b5d --- libs/jpegrecoverymap/tests/Android.bp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp index cad273e437..e381caf025 100644 --- a/libs/jpegrecoverymap/tests/Android.bp +++ b/libs/jpegrecoverymap/tests/Android.bp @@ -29,14 +29,17 @@ cc_test { "recoverymapmath_test.cpp", ], shared_libs: [ - "libjpeg", - "libjpegrecoverymap", "libimage_io", + "libjpeg", "liblog", ], static_libs: [ "libgmock", "libgtest", + "libjpegdecoder", + "libjpegencoder", + "libjpegrecoverymap", + "libskia", ], } @@ -48,11 +51,11 @@ cc_test { ], shared_libs: [ "libjpeg", - "libjpegencoder", "liblog", ], static_libs: [ "libgtest", + "libjpegencoder", ], } @@ -64,10 +67,10 @@ cc_test { ], shared_libs: [ "libjpeg", - "libjpegdecoder", "liblog", ], static_libs: [ "libgtest", + "libjpegdecoder", ], } -- GitLab From 0175906bd2f309b326566bf62e7f6a72b659d50a Mon Sep 17 00:00:00 2001 From: Nick Deakin Date: Thu, 2 Feb 2023 18:21:43 -0500 Subject: [PATCH 0807/1310] jpegrecoverymap: Update XMP to match spec. Remove TransferFunction and PQ metadata from XMP, since they are no longer needed. Update RangeScalingFactor to MaxContentBoost. Also update GContainer prefix to Container and correct Item to be its own prefix, in order to properly conform to GContainer. In order to still provide a decode flow, default to HLG. Bug: 264715926 Test: tests pass Change-Id: I6a94a74666381637c4a7ad301de05cf562c53265 --- .../include/jpegrecoverymap/recoverymap.h | 50 +----- .../jpegrecoverymap/recoverymaputils.h | 33 ++-- libs/jpegrecoverymap/recoverymap.cpp | 78 ++------- libs/jpegrecoverymap/recoverymaputils.cpp | 157 ++++-------------- .../tests/recoverymap_test.cpp | 6 +- .../tests/recoverymapmath_test.cpp | 4 +- 6 files changed, 75 insertions(+), 253 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h index 1a4b679a73..aee6602aa4 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -21,6 +21,7 @@ namespace android::recoverymap { +// Color gamuts for image data typedef enum { JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_BT709, @@ -28,7 +29,7 @@ typedef enum { JPEGR_COLORGAMUT_BT2100, } jpegr_color_gamut; -// Transfer functions as defined for XMP metadata +// Transfer functions for image data typedef enum { JPEGR_TF_UNSPECIFIED = -1, JPEGR_TF_LINEAR = 0, @@ -82,45 +83,11 @@ struct jpegr_exif_struct { int length; }; -struct chromaticity_coord { - float x; - float y; -}; - - -struct st2086_metadata { - // xy chromaticity coordinate of the red primary of the mastering display - chromaticity_coord redPrimary; - // xy chromaticity coordinate of the green primary of the mastering display - chromaticity_coord greenPrimary; - // xy chromaticity coordinate of the blue primary of the mastering display - chromaticity_coord bluePrimary; - // xy chromaticity coordinate of the white point of the mastering display - chromaticity_coord whitePoint; - // Maximum luminance in nits of the mastering display - uint32_t maxLuminance; - // Minimum luminance in nits of the mastering display - float minLuminance; -}; - -struct hdr10_metadata { - // Mastering display color volume - st2086_metadata st2086Metadata; - // Max frame average light level in nits - float maxFALL; - // Max content light level in nits - float maxCLL; -}; - struct jpegr_metadata { // JPEG/R version uint32_t version; - // Range scaling factor for the map - float rangeScalingFactor; - // The transfer function for decoding the HDR representation of the image - jpegr_transfer_function transferFunction; - // HDR10 metadata, only applicable for transferFunction of JPEGR_TF_PQ - hdr10_metadata hdr10Metadata; + // Max Content Boost for the map + float maxContentBoost; }; typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr; @@ -270,14 +237,14 @@ private: * * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format * @param uncompressed_p010_image uncompressed HDR image in P010 color format + * @param hdr_tf transfer function of the HDR image * @param dest recovery map; caller responsible for memory of data - * @param metadata metadata provides the transfer function for the HDR - * image; range_scaling_factor and hdr10 FALL and CLL will - * be updated. + * @param metadata max_content_boost is filled in * @return NO_ERROR if calculation succeeds, error code if error occurs. */ status_t generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, jr_uncompressed_ptr uncompressed_p010_image, + jpegr_transfer_function hdr_tf, jr_metadata_ptr metadata, jr_uncompressed_ptr dest); @@ -285,8 +252,7 @@ private: * This method is called in the decoding pipeline. It will take the uncompressed (decoded) * 8-bit yuv image, the uncompressed (decoded) recovery map, and extracted JPEG/R metadata as * input, and calculate the 10-bit recovered image. The recovered output image is the same - * color gamut as the SDR image, with the transfer function specified in the JPEG/R metadata, - * and is in RGBA1010102 data format. + * color gamut as the SDR image, with HLG transfer function, and is in RGBA1010102 data format. * * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format * @param uncompressed_recovery_map uncompressed recovery map diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h index 8696851155..de29a339ed 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h @@ -55,7 +55,7 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta * * below is an example of the XMP metadata that this function generates where * secondary_image_length = 1000 - * range_scaling_factor = 1.25 + * max_content_boost = 8.0 * * * - * 1 - * + * * * - * - * + * * * - * + * * * - * + * * * * diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index e06bd24cfa..7fdcf7847b 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -72,16 +72,6 @@ static const size_t kJpegBlock = 8; // JPEG compress quality (0 ~ 100) for recovery map static const int kMapCompressQuality = 85; -// TODO: fill in st2086 metadata -static const st2086_metadata kSt2086Metadata = { - {0.0f, 0.0f}, - {0.0f, 0.0f}, - {0.0f, 0.0f}, - {0.0f, 0.0f}, - 0, - 1.0f, -}; - #define CONFIG_MULTITHREAD 1 int GetCPUCoreCount() { int cpuCoreCount = 1; @@ -133,10 +123,6 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpegr_metadata metadata; metadata.version = kJpegrVersion; - metadata.transferFunction = hdr_tf; - if (hdr_tf == JPEGR_TF_PQ) { - metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata; - } jpegr_uncompressed_struct uncompressed_yuv_420_image; unique_ptr uncompressed_yuv_420_image_data = make_unique( @@ -146,7 +132,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpegr_uncompressed_struct map; JPEGR_CHECK(generateRecoveryMap( - &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map)); + &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map)); std::unique_ptr map_data; map_data.reset(reinterpret_cast(map.data)); @@ -207,14 +193,10 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpegr_metadata metadata; metadata.version = kJpegrVersion; - metadata.transferFunction = hdr_tf; - if (hdr_tf == JPEGR_TF_PQ) { - metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata; - } jpegr_uncompressed_struct map; JPEGR_CHECK(generateRecoveryMap( - uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map)); + uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map)); std::unique_ptr map_data; map_data.reset(reinterpret_cast(map.data)); @@ -271,14 +253,10 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpegr_metadata metadata; metadata.version = kJpegrVersion; - metadata.transferFunction = hdr_tf; - if (hdr_tf == JPEGR_TF_PQ) { - metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata; - } jpegr_uncompressed_struct map; JPEGR_CHECK(generateRecoveryMap( - uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map)); + uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map)); std::unique_ptr map_data; map_data.reset(reinterpret_cast(map.data)); @@ -328,14 +306,10 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpegr_metadata metadata; metadata.version = kJpegrVersion; - metadata.transferFunction = hdr_tf; - if (hdr_tf == JPEGR_TF_PQ) { - metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata; - } jpegr_uncompressed_struct map; JPEGR_CHECK(generateRecoveryMap( - &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map)); + &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map)); std::unique_ptr map_data; map_data.reset(reinterpret_cast(map.data)); @@ -437,7 +411,6 @@ status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recov return ERROR_JPEGR_INVALID_NULL_PTR; } - // TODO: should we have ICC data for the map? JpegEncoder jpeg_encoder; if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data, uncompressed_recovery_map->width, @@ -518,6 +491,7 @@ void JobQueue::reset() { status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, jr_uncompressed_ptr uncompressed_p010_image, + jpegr_transfer_function hdr_tf, jr_metadata_ptr metadata, jr_uncompressed_ptr dest) { if (uncompressed_yuv_420_image == nullptr @@ -554,7 +528,7 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 ColorTransformFn hdrInvOetf = nullptr; float hdr_white_nits = 0.0f; - switch (metadata->transferFunction) { + switch (hdr_tf) { case JPEGR_TF_LINEAR: hdrInvOetf = identityConversion; break; @@ -658,7 +632,7 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 size_t pixel_idx = x + y * dest->width; reinterpret_cast(dest->data)[pixel_idx] = - encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor); + encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->maxContentBoost); } } } @@ -681,11 +655,7 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 workers.clear(); hdr_y_nits_avg /= image_width * image_height; - metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits; - if (metadata->transferFunction == JPEGR_TF_PQ) { - metadata->hdr10Metadata.maxFALL = hdr_y_nits_avg; - metadata->hdr10Metadata.maxCLL = hdr_y_nits_max; - } + metadata->maxContentBoost = hdr_y_nits_max / kSdrWhiteNits; // generate map jobQueue.reset(); @@ -721,39 +691,21 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ dest->width = uncompressed_yuv_420_image->width; dest->height = uncompressed_yuv_420_image->height; ShepardsIDW idwTable(kMapDimensionScaleFactor); - RecoveryLUT recoveryLUT(metadata->rangeScalingFactor); + RecoveryLUT recoveryLUT(metadata->maxContentBoost); JobQueue jobQueue; std::function applyRecMap = [uncompressed_yuv_420_image, uncompressed_recovery_map, metadata, dest, &jobQueue, &idwTable, &recoveryLUT]() -> void { - const float hdr_ratio = metadata->rangeScalingFactor; + const float hdr_ratio = metadata->maxContentBoost; size_t width = uncompressed_yuv_420_image->width; size_t height = uncompressed_yuv_420_image->height; - ColorTransformFn hdrOetf = nullptr; - switch (metadata->transferFunction) { - case JPEGR_TF_LINEAR: - hdrOetf = identityConversion; - break; - case JPEGR_TF_HLG: #if USE_HLG_OETF_LUT - hdrOetf = hlgOetfLUT; + ColorTransformFn hdrOetf = hlgOetfLUT; #else - hdrOetf = hlgOetf; + ColorTransformFn hdrOetf = hlgOetf; #endif - break; - case JPEGR_TF_PQ: -#if USE_PQ_OETF_LUT - hdrOetf = pqOetfLUT; -#else - hdrOetf = pqOetf; -#endif - break; - default: - // Should be impossible to hit after input validation. - hdrOetf = identityConversion; - } size_t rowStart, rowEnd; while (jobQueue.dequeueJob(rowStart, rowEnd)) { @@ -783,7 +735,7 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ #else Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio); #endif - Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->rangeScalingFactor); + Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->maxContentBoost); uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr); size_t pixel_idx = x + y * width; @@ -811,8 +763,8 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ } status_t RecoveryMap::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image, - jr_compressed_ptr primary_image, - jr_compressed_ptr recovery_map) { + jr_compressed_ptr primary_image, + jr_compressed_ptr recovery_map) { if (compressed_jpegr_image == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/recoverymaputils.cpp index 1617b8b97a..40956bdaba 100644 --- a/libs/jpegrecoverymap/recoverymaputils.cpp +++ b/libs/jpegrecoverymap/recoverymaputils.cpp @@ -93,10 +93,8 @@ public: string val; if (gContainerItemState == Started) { if (context.BuildTokenValue(&val)) { - if (!val.compare(rangeScalingFactorAttrName)) { - lastAttributeName = rangeScalingFactorAttrName; - } else if (!val.compare(transferFunctionAttrName)) { - lastAttributeName = transferFunctionAttrName; + if (!val.compare(maxContentBoostAttrName)) { + lastAttributeName = maxContentBoostAttrName; } else { lastAttributeName = ""; } @@ -109,22 +107,20 @@ public: string val; if (gContainerItemState == Started) { if (context.BuildTokenValue(&val, true)) { - if (!lastAttributeName.compare(rangeScalingFactorAttrName)) { - rangeScalingFactorStr = val; - } else if (!lastAttributeName.compare(transferFunctionAttrName)) { - transferFunctionStr = val; + if (!lastAttributeName.compare(maxContentBoostAttrName)) { + maxContentBoostStr = val; } } } return context.GetResult(); } - bool getRangeScalingFactor(float* scaling_factor) { + bool getMaxContentBoost(float* max_content_boost) { if (gContainerItemState == Done) { - stringstream ss(rangeScalingFactorStr); + stringstream ss(maxContentBoostStr); float val; if (ss >> val) { - *scaling_factor = val; + *max_content_boost = val; return true; } else { return false; @@ -134,84 +130,49 @@ public: } } - bool getTransferFunction(jpegr_transfer_function* transfer_function) { - if (gContainerItemState == Done) { - stringstream ss(transferFunctionStr); - int val; - if (ss >> val) { - *transfer_function = static_cast(val); - return true; - } else { - return false; - } - } else { - return false; - } - return true; - } - private: static const string gContainerItemName; - static const string rangeScalingFactorAttrName; - static const string transferFunctionAttrName; - string rangeScalingFactorStr; - string transferFunctionStr; + static const string maxContentBoostAttrName; + string maxContentBoostStr; string lastAttributeName; ParseState gContainerItemState; }; // GContainer XMP constants - URI and namespace prefix const string kContainerUri = "http://ns.google.com/photos/1.0/container/"; -const string kContainerPrefix = "GContainer"; +const string kContainerPrefix = "Container"; // GContainer XMP constants - element and attribute names const string kConDirectory = Name(kContainerPrefix, "Directory"); const string kConItem = Name(kContainerPrefix, "Item"); -const string kConItemLength = Name(kContainerPrefix, "ItemLength"); -const string kConItemMime = Name(kContainerPrefix, "ItemMime"); -const string kConItemSemantic = Name(kContainerPrefix, "ItemSemantic"); -const string kConVersion = Name(kContainerPrefix, "Version"); -// GContainer XMP constants - element and attribute values +// GContainer XMP constants - names for XMP handlers +const string XMPXmlHandler::gContainerItemName = kConItem; + +// Item XMP constants - URI and namespace prefix +const string kItemUri = "http://ns.google.com/photos/1.0/container/item/"; +const string kItemPrefix = "Item"; + +// Item XMP constants - element and attribute names +const string kItemLength = Name(kItemPrefix, "Length"); +const string kItemMime = Name(kItemPrefix, "Mime"); +const string kItemSemantic = Name(kItemPrefix, "Semantic"); + +// Item XMP constants - element and attribute values const string kSemanticPrimary = "Primary"; const string kSemanticRecoveryMap = "RecoveryMap"; const string kMimeImageJpeg = "image/jpeg"; -const int kGContainerVersion = 1; - -// GContainer XMP constants - names for XMP handlers -const string XMPXmlHandler::gContainerItemName = kConItem; - // RecoveryMap XMP constants - URI and namespace prefix const string kRecoveryMapUri = "http://ns.google.com/photos/1.0/recoverymap/"; const string kRecoveryMapPrefix = "RecoveryMap"; // RecoveryMap XMP constants - element and attribute names -const string kMapRangeScalingFactor = Name(kRecoveryMapPrefix, "RangeScalingFactor"); -const string kMapTransferFunction = Name(kRecoveryMapPrefix, "TransferFunction"); -const string kMapVersion = Name(kRecoveryMapPrefix, "Version"); - -const string kMapHdr10Metadata = Name(kRecoveryMapPrefix, "HDR10Metadata"); -const string kMapHdr10MaxFall = Name(kRecoveryMapPrefix, "HDR10MaxFALL"); -const string kMapHdr10MaxCll = Name(kRecoveryMapPrefix, "HDR10MaxCLL"); - -const string kMapSt2086Metadata = Name(kRecoveryMapPrefix, "ST2086Metadata"); -const string kMapSt2086MaxLum = Name(kRecoveryMapPrefix, "ST2086MaxLuminance"); -const string kMapSt2086MinLum = Name(kRecoveryMapPrefix, "ST2086MinLuminance"); -const string kMapSt2086Primary = Name(kRecoveryMapPrefix, "ST2086Primary"); -const string kMapSt2086Coordinate = Name(kRecoveryMapPrefix, "ST2086Coordinate"); -const string kMapSt2086CoordinateX = Name(kRecoveryMapPrefix, "ST2086CoordinateX"); -const string kMapSt2086CoordinateY = Name(kRecoveryMapPrefix, "ST2086CoordinateY"); - -// RecoveryMap XMP constants - element and attribute values -const int kSt2086PrimaryRed = 0; -const int kSt2086PrimaryGreen = 1; -const int kSt2086PrimaryBlue = 2; -const int kSt2086PrimaryWhite = 3; +const string kMapMaxContentBoost = Name(kRecoveryMapPrefix, "MaxContentBoost"); +const string kMapVersion = Name(kRecoveryMapPrefix, "Version"); // RecoveryMap XMP constants - names for XMP handlers -const string XMPXmlHandler::rangeScalingFactorAttrName = kMapRangeScalingFactor; -const string XMPXmlHandler::transferFunctionAttrName = kMapTransferFunction; +const string XMPXmlHandler::maxContentBoostAttrName = kMapMaxContentBoost; bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) { string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; @@ -248,13 +209,10 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta return false; } - if (!handler.getRangeScalingFactor(&metadata->rangeScalingFactor)) { + if (!handler.getMaxContentBoost(&metadata->maxContentBoost)) { return false; } - if (!handler.getTransferFunction(&metadata->transferFunction)) { - return false; - } return true; } @@ -271,66 +229,19 @@ string generateXmp(int secondary_image_length, jpegr_metadata& metadata) { writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); writer.StartWritingElement("rdf:Description"); writer.WriteXmlns(kContainerPrefix, kContainerUri); + writer.WriteXmlns(kItemPrefix, kItemUri); writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri); - writer.WriteElementAndContent(kConVersion, kGContainerVersion); writer.StartWritingElements(kConDirSeq); size_t item_depth = writer.StartWritingElements(kLiItem); - writer.WriteAttributeNameAndValue(kConItemSemantic, kSemanticPrimary); - writer.WriteAttributeNameAndValue(kConItemMime, kMimeImageJpeg); + writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary); + writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg); writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); - writer.WriteAttributeNameAndValue(kMapRangeScalingFactor, metadata.rangeScalingFactor); - writer.WriteAttributeNameAndValue(kMapTransferFunction, metadata.transferFunction); - if (metadata.transferFunction == JPEGR_TF_PQ) { - writer.StartWritingElement(kMapHdr10Metadata); - writer.WriteAttributeNameAndValue(kMapHdr10MaxFall, metadata.hdr10Metadata.maxFALL); - writer.WriteAttributeNameAndValue(kMapHdr10MaxCll, metadata.hdr10Metadata.maxCLL); - writer.StartWritingElement(kMapSt2086Metadata); - writer.WriteAttributeNameAndValue( - kMapSt2086MaxLum, metadata.hdr10Metadata.st2086Metadata.maxLuminance); - writer.WriteAttributeNameAndValue( - kMapSt2086MinLum, metadata.hdr10Metadata.st2086Metadata.minLuminance); - - // red - writer.StartWritingElement(kMapSt2086Coordinate); - writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryRed); - writer.WriteAttributeNameAndValue( - kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.redPrimary.x); - writer.WriteAttributeNameAndValue( - kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.redPrimary.y); - writer.FinishWritingElement(); - - // green - writer.StartWritingElement(kMapSt2086Coordinate); - writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryGreen); - writer.WriteAttributeNameAndValue( - kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.greenPrimary.x); - writer.WriteAttributeNameAndValue( - kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.greenPrimary.y); - writer.FinishWritingElement(); - - // blue - writer.StartWritingElement(kMapSt2086Coordinate); - writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryBlue); - writer.WriteAttributeNameAndValue( - kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.bluePrimary.x); - writer.WriteAttributeNameAndValue( - kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.bluePrimary.y); - writer.FinishWritingElement(); - - // white - writer.StartWritingElement(kMapSt2086Coordinate); - writer.WriteAttributeNameAndValue(kMapSt2086Primary, kSt2086PrimaryWhite); - writer.WriteAttributeNameAndValue( - kMapSt2086CoordinateX, metadata.hdr10Metadata.st2086Metadata.whitePoint.x); - writer.WriteAttributeNameAndValue( - kMapSt2086CoordinateY, metadata.hdr10Metadata.st2086Metadata.whitePoint.y); - writer.FinishWritingElement(); - } + writer.WriteAttributeNameAndValue(kMapMaxContentBoost, metadata.maxContentBoost); writer.FinishWritingElementsToDepth(item_depth); writer.StartWritingElements(kLiItem); - writer.WriteAttributeNameAndValue(kConItemSemantic, kSemanticRecoveryMap); - writer.WriteAttributeNameAndValue(kConItemMime, kMimeImageJpeg); - writer.WriteAttributeNameAndValue(kConItemLength, secondary_image_length); + writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticRecoveryMap); + writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg); + writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length); writer.FinishWriting(); return ss.str(); diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp index dfab76a2c9..3e9a76d47a 100644 --- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp +++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp @@ -103,8 +103,7 @@ TEST_F(RecoveryMapTest, build) { TEST_F(RecoveryMapTest, writeXmpThenRead) { jpegr_metadata metadata_expected; - metadata_expected.transferFunction = JPEGR_TF_HLG; - metadata_expected.rangeScalingFactor = 1.25; + metadata_expected.maxContentBoost = 1.25; int length_expected = 1000; const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator @@ -120,8 +119,7 @@ TEST_F(RecoveryMapTest, writeXmpThenRead) { jpegr_metadata metadata_read; EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read)); - ASSERT_EQ(metadata_expected.transferFunction, metadata_read.transferFunction); - ASSERT_EQ(metadata_expected.rangeScalingFactor, metadata_read.rangeScalingFactor); + ASSERT_EQ(metadata_expected.maxContentBoost, metadata_read.maxContentBoost); } /* Test Encode API-0 and decode */ diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp index 1d522d1860..2eec95f01b 100644 --- a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp +++ b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp @@ -88,10 +88,10 @@ public: return luminance_scaled * scale_factor; } - Color Recover(Color yuv_gamma, float recovery, float range_scaling_factor) { + Color Recover(Color yuv_gamma, float recovery, float max_content_boost) { Color rgb_gamma = srgbYuvToRgb(yuv_gamma); Color rgb = srgbInvOetf(rgb_gamma); - return applyRecovery(rgb, recovery, range_scaling_factor); + return applyRecovery(rgb, recovery, max_content_boost); } jpegr_uncompressed_struct Yuv420Image() { -- GitLab From 0c8b723827e997bb21c1b11919b84ea6905066b4 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Fri, 27 Jan 2023 21:12:16 -0800 Subject: [PATCH 0808/1310] SF: cleanup frame rate override sysprops Bug: 260875485 Test: atest FrameRateOverrideHostTest Change-Id: Ic2b01c4e0db348274e34b3e7ddecfc60414892dc --- services/surfaceflinger/SurfaceFlinger.cpp | 21 ++++-------------- .../SurfaceFlingerProperties.cpp | 8 ------- .../surfaceflinger/SurfaceFlingerProperties.h | 4 ---- .../sysprop/SurfaceFlingerProperties.sysprop | 22 ------------------- .../api/SurfaceFlingerProperties-current.txt | 8 ------- 5 files changed, 4 insertions(+), 59 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index a0c3eb0e26..927a9b7675 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3139,23 +3139,10 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( const auto [kernelIdleTimerController, idleTimerTimeoutMs] = getKernelIdleTimerProperties(compositionDisplay->getId()); - const auto enableFrameRateOverride = [&] { - using Config = scheduler::RefreshRateSelector::Config; - if (!sysprop::enable_frame_rate_override(true)) { - return Config::FrameRateOverride::Disabled; - } - - if (sysprop::frame_rate_override_for_native_rates(false)) { - return Config::FrameRateOverride::AppOverrideNativeRefreshRates; - } - - if (!sysprop::frame_rate_override_global(true)) { - return Config::FrameRateOverride::AppOverride; - } - - return Config::FrameRateOverride::Enabled; - }(); - + using Config = scheduler::RefreshRateSelector::Config; + const auto enableFrameRateOverride = sysprop::enable_frame_rate_override(true) + ? Config::FrameRateOverride::Enabled + : Config::FrameRateOverride::Disabled; scheduler::RefreshRateSelector::Config config = {.enableFrameRateOverride = enableFrameRateOverride, .frameRateMultipleThreshold = diff --git a/services/surfaceflinger/SurfaceFlingerProperties.cpp b/services/surfaceflinger/SurfaceFlingerProperties.cpp index 5b7303090d..20fa091730 100644 --- a/services/surfaceflinger/SurfaceFlingerProperties.cpp +++ b/services/surfaceflinger/SurfaceFlingerProperties.cpp @@ -367,14 +367,6 @@ bool enable_frame_rate_override(bool defaultValue) { return SurfaceFlingerProperties::enable_frame_rate_override().value_or(defaultValue); } -bool frame_rate_override_for_native_rates(bool defaultValue) { - return SurfaceFlingerProperties::frame_rate_override_for_native_rates().value_or(defaultValue); -} - -bool frame_rate_override_global(bool defaultValue) { - return SurfaceFlingerProperties::frame_rate_override_global().value_or(defaultValue); -} - bool enable_layer_caching(bool defaultValue) { return SurfaceFlingerProperties::enable_layer_caching().value_or(defaultValue); } diff --git a/services/surfaceflinger/SurfaceFlingerProperties.h b/services/surfaceflinger/SurfaceFlingerProperties.h index 09629cf93e..080feee686 100644 --- a/services/surfaceflinger/SurfaceFlingerProperties.h +++ b/services/surfaceflinger/SurfaceFlingerProperties.h @@ -96,10 +96,6 @@ bool update_device_product_info_on_hotplug_reconnect(bool defaultValue); bool enable_frame_rate_override(bool defaultValue); -bool frame_rate_override_for_native_rates(bool defaultValue); - -bool frame_rate_override_global(bool defaultValue); - bool enable_layer_caching(bool defaultValue); bool enable_sdr_dimming(bool defaultValue); diff --git a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop index 8540c3dcfc..bcbe21a483 100644 --- a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop +++ b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop @@ -445,28 +445,6 @@ prop { prop_name: "ro.surface_flinger.enable_frame_rate_override" } -# Limits the frame rate override feature (enable_frame_rate_override) to override the refresh rate -# to native display refresh rates only. Before introducing this flag, native display refresh rates -# was the default behaviour. With this flag we can control which behaviour we want explicitly. -# This flag is introduced as a fail-safe mechanism and planned to be defaulted to false. -prop { - api_name: "frame_rate_override_for_native_rates" - type: Boolean - scope: Public - access: Readonly - prop_name: "ro.surface_flinger.frame_rate_override_for_native_rates" -} - -# Enables the frame rate override feature (enable_frame_rate_override) to -# override the frame rate globally instead of only for individual apps. -prop { - api_name: "frame_rate_override_global" - type: Boolean - scope: Public - access: Readonly - prop_name: "ro.surface_flinger.frame_rate_override_global" -} - # Enables Layer Caching prop { api_name: "enable_layer_caching" diff --git a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt index 93381333a8..348a462038 100644 --- a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt +++ b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt @@ -60,14 +60,6 @@ props { api_name: "force_hwc_copy_for_virtual_displays" prop_name: "ro.surface_flinger.force_hwc_copy_for_virtual_displays" } - prop { - api_name: "frame_rate_override_for_native_rates" - prop_name: "ro.surface_flinger.frame_rate_override_for_native_rates" - } - prop { - api_name: "frame_rate_override_global" - prop_name: "ro.surface_flinger.frame_rate_override_global" - } prop { api_name: "has_HDR_display" prop_name: "ro.surface_flinger.has_HDR_display" -- GitLab From 24b4a395bcd8c5d0b3cae582e0c19f7cee9f74b1 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Thu, 2 Feb 2023 22:54:01 +0000 Subject: [PATCH 0809/1310] JPEG/R: fix a recovery map calculation bug bug: b/264715926, b/264715926 Change-Id: Ibd86bb22c6cef3211b161139716a78a6f26db2c0 --- libs/jpegrecoverymap/recoverymap.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index e06bd24cfa..d2b6268614 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -637,9 +637,11 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 metadata, dest, hdrInvOetf, hdrGamutConversionFn, luminanceFn, hdr_white_nits, &jobQueue]() -> void { size_t rowStart, rowEnd; + size_t dest_map_width = uncompressed_yuv_420_image->width / kMapDimensionScaleFactor; + size_t dest_map_stride = dest->width; while (jobQueue.dequeueJob(rowStart, rowEnd)) { for (size_t y = rowStart; y < rowEnd; ++y) { - for (size_t x = 0; x < dest->width; ++x) { + for (size_t x = 0; x < dest_map_width; ++x) { Color sdr_yuv_gamma = sampleYuv420(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y); Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma); @@ -656,7 +658,7 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 hdr_rgb = hdrGamutConversionFn(hdr_rgb); float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits; - size_t pixel_idx = x + y * dest->width; + size_t pixel_idx = x + y * dest_map_stride; reinterpret_cast(dest->data)[pixel_idx] = encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor); } -- GitLab From b81edf95e53784d8153c7c4999ccbc1ee653be63 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 3 Feb 2023 18:24:34 +0000 Subject: [PATCH 0810/1310] Revert "EGL: Refactor multifile blobcache" Revert submission 21108066-blobcache_multifile_20230125 Reason for revert: b/267777424 Reverted changes: /q/submissionid:21108066-blobcache_multifile_20230125 Change-Id: Ieb94d531a12b29b61958d3d5c601be544e429d79 --- opengl/libs/Android.bp | 7 +- opengl/libs/EGL/MultifileBlobCache.cpp | 668 -------------------- opengl/libs/EGL/MultifileBlobCache.h | 164 ----- opengl/libs/EGL/MultifileBlobCache_test.cpp | 200 ------ opengl/libs/EGL/egl_cache.cpp | 105 ++- opengl/libs/EGL/egl_cache.h | 21 +- opengl/libs/EGL/egl_cache_multifile.cpp | 343 ++++++++++ opengl/libs/EGL/egl_cache_multifile.h | 36 ++ opengl/tests/EGLTest/egl_cache_test.cpp | 52 +- 9 files changed, 446 insertions(+), 1150 deletions(-) delete mode 100644 opengl/libs/EGL/MultifileBlobCache.cpp delete mode 100644 opengl/libs/EGL/MultifileBlobCache.h delete mode 100644 opengl/libs/EGL/MultifileBlobCache_test.cpp create mode 100644 opengl/libs/EGL/egl_cache_multifile.cpp create mode 100644 opengl/libs/EGL/egl_cache_multifile.h diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp index 49e1cbafb4..750338bd84 100644 --- a/opengl/libs/Android.bp +++ b/opengl/libs/Android.bp @@ -144,7 +144,6 @@ cc_library_static { srcs: [ "EGL/BlobCache.cpp", "EGL/FileBlobCache.cpp", - "EGL/MultifileBlobCache.cpp", ], export_include_dirs: ["EGL"], } @@ -161,6 +160,7 @@ cc_library_shared { srcs: [ "EGL/egl_tls.cpp", "EGL/egl_cache.cpp", + "EGL/egl_cache_multifile.cpp", "EGL/egl_display.cpp", "EGL/egl_object.cpp", "EGL/egl_layers.cpp", @@ -205,11 +205,6 @@ cc_test { srcs: [ "EGL/BlobCache.cpp", "EGL/BlobCache_test.cpp", - "EGL/MultifileBlobCache.cpp", - "EGL/MultifileBlobCache_test.cpp", - ], - shared_libs: [ - "libutils", ], } diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp deleted file mode 100644 index 48b184b100..0000000000 --- a/opengl/libs/EGL/MultifileBlobCache.cpp +++ /dev/null @@ -1,668 +0,0 @@ -/* - ** Copyright 2022, The Android Open Source Project - ** - ** Licensed under the Apache License, Version 2.0 (the "License"); - ** you may not use this file except in compliance with the License. - ** You may obtain a copy of the License at - ** - ** http://www.apache.org/licenses/LICENSE-2.0 - ** - ** Unless required by applicable law or agreed to in writing, software - ** distributed under the License is distributed on an "AS IS" BASIS, - ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ** See the License for the specific language governing permissions and - ** limitations under the License. - */ - -// #define LOG_NDEBUG 0 - -#include "MultifileBlobCache.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -using namespace std::literals; - -namespace { - -// Open the file and determine the size of the value it contains -size_t getValueSizeFromFile(int fd, const std::string& entryPath) { - // Read the beginning of the file to get header - android::MultifileHeader header; - size_t result = read(fd, static_cast(&header), sizeof(android::MultifileHeader)); - if (result != sizeof(android::MultifileHeader)) { - ALOGE("Error reading MultifileHeader from cache entry (%s): %s", entryPath.c_str(), - std::strerror(errno)); - return 0; - } - - return header.valueSize; -} - -// Helper function to close entries or free them -void freeHotCacheEntry(android::MultifileHotCache& entry) { - if (entry.entryFd != -1) { - // If we have an fd, then this entry was added to hot cache via INIT or GET - // We need to unmap and close the entry - munmap(entry.entryBuffer, entry.entrySize); - close(entry.entryFd); - } else { - // Otherwise, this was added to hot cache during SET, so it was never mapped - // and fd was only on the deferred thread. - delete[] entry.entryBuffer; - } -} - -} // namespace - -namespace android { - -MultifileBlobCache::MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize, - const std::string& baseDir) - : mInitialized(false), - mMaxTotalSize(maxTotalSize), - mTotalCacheSize(0), - mHotCacheLimit(maxHotCacheSize), - mHotCacheSize(0), - mWorkerThreadIdle(true) { - if (baseDir.empty()) { - return; - } - - // Establish the name of our multifile directory - mMultifileDirName = baseDir + ".multifile"; - - // Set a limit for max key and value, ensuring at least one entry can always fit in hot cache - mMaxKeySize = mHotCacheLimit / 4; - mMaxValueSize = mHotCacheLimit / 2; - - // Initialize our cache with the contents of the directory - mTotalCacheSize = 0; - - // See if the dir exists, and initialize using its contents - struct stat st; - if (stat(mMultifileDirName.c_str(), &st) == 0) { - // Read all the files and gather details, then preload their contents - DIR* dir; - struct dirent* entry; - if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) { - while ((entry = readdir(dir)) != nullptr) { - if (entry->d_name == "."s || entry->d_name == ".."s) { - continue; - } - - std::string entryName = entry->d_name; - std::string fullPath = mMultifileDirName + "/" + entryName; - - // The filename is the same as the entryHash - uint32_t entryHash = static_cast(strtoul(entry->d_name, nullptr, 10)); - - // Look up the details of the file - struct stat st; - if (stat(fullPath.c_str(), &st) != 0) { - ALOGE("Failed to stat %s", fullPath.c_str()); - return; - } - - // Open the file so we can read its header - int fd = open(fullPath.c_str(), O_RDONLY); - if (fd == -1) { - ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(), - std::strerror(errno)); - return; - } - - // Look up the details we track about each file - size_t valueSize = getValueSizeFromFile(fd, fullPath); - size_t fileSize = st.st_size; - time_t accessTime = st.st_atime; - - // If the cache entry is damaged or no good, remove it - // TODO: Perform any other checks - if (valueSize <= 0 || fileSize <= 0 || accessTime <= 0) { - if (remove(fullPath.c_str()) != 0) { - ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno)); - } - continue; - } - - // Track details for rapid lookup later - trackEntry(entryHash, valueSize, fileSize, accessTime); - - // Track the total size - increaseTotalCacheSize(fileSize); - - // Preload the entry for fast retrieval - if ((mHotCacheSize + fileSize) < mHotCacheLimit) { - // Memory map the file - uint8_t* mappedEntry = reinterpret_cast( - mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0)); - if (mappedEntry == MAP_FAILED) { - ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); - } - - ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for " - "entryHash %u", - fd, mappedEntry, entryHash); - - // Track the details of the preload so they can be retrieved later - if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) { - ALOGE("INIT Failed to add %u to hot cache", entryHash); - munmap(mappedEntry, fileSize); - close(fd); - return; - } - } else { - close(fd); - } - } - closedir(dir); - } else { - ALOGE("Unable to open filename: %s", mMultifileDirName.c_str()); - } - } else { - // If the multifile directory does not exist, create it and start from scratch - if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) { - ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno); - } - } - - mTaskThread = std::thread(&MultifileBlobCache::processTasks, this); - - mInitialized = true; -} - -MultifileBlobCache::~MultifileBlobCache() { - // Inform the worker thread we're done - ALOGV("DESCTRUCTOR: Shutting down worker thread"); - DeferredTask task(TaskCommand::Exit); - queueTask(std::move(task)); - - // Wait for it to complete - ALOGV("DESCTRUCTOR: Waiting for worker thread to complete"); - waitForWorkComplete(); - mTaskThread.join(); -} - -// Set will add the entry to hot cache and start a deferred process to write it to disk -void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const void* value, - EGLsizeiANDROID valueSize) { - if (!mInitialized) { - return; - } - - // Ensure key and value are under their limits - if (keySize > mMaxKeySize || valueSize > mMaxValueSize) { - ALOGV("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize, - valueSize, mMaxValueSize); - return; - } - - // Generate a hash of the key and use it to track this entry - uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast(key), keySize); - - size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize; - - // If we're going to be over the cache limit, kick off a trim to clear space - if (getTotalSize() + fileSize > mMaxTotalSize) { - ALOGV("SET: Cache is full, calling trimCache to clear space"); - trimCache(mMaxTotalSize); - } - - ALOGV("SET: Add %u to cache", entryHash); - - uint8_t* buffer = new uint8_t[fileSize]; - - // Write the key and value after the header - android::MultifileHeader header = {keySize, valueSize}; - memcpy(static_cast(buffer), static_cast(&header), - sizeof(android::MultifileHeader)); - memcpy(static_cast(buffer + sizeof(MultifileHeader)), static_cast(key), - keySize); - memcpy(static_cast(buffer + sizeof(MultifileHeader) + keySize), - static_cast(value), valueSize); - - std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash); - - // Track the size and access time for quick recall - trackEntry(entryHash, valueSize, fileSize, time(0)); - - // Update the overall cache size - increaseTotalCacheSize(fileSize); - - // Keep the entry in hot cache for quick retrieval - ALOGV("SET: Adding %u to hot cache.", entryHash); - - // Sending -1 as the fd indicates we don't have an fd for this - if (!addToHotCache(entryHash, -1, buffer, fileSize)) { - ALOGE("GET: Failed to add %u to hot cache", entryHash); - return; - } - - // Track that we're creating a pending write for this entry - // Include the buffer to handle the case when multiple writes are pending for an entry - mDeferredWrites.insert(std::make_pair(entryHash, buffer)); - - // Create deferred task to write to storage - ALOGV("SET: Adding task to queue."); - DeferredTask task(TaskCommand::WriteToDisk); - task.initWriteToDisk(fullPath, buffer, fileSize); - queueTask(std::move(task)); -} - -// Get will check the hot cache, then load it from disk if needed -EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize, void* value, - EGLsizeiANDROID valueSize) { - if (!mInitialized) { - return 0; - } - - // Ensure key and value are under their limits - if (keySize > mMaxKeySize || valueSize > mMaxValueSize) { - ALOGV("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize, - valueSize, mMaxValueSize); - return 0; - } - - // Generate a hash of the key and use it to track this entry - uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast(key), keySize); - - // See if we have this file - if (!contains(entryHash)) { - ALOGV("GET: Cache MISS - cache does not contain entry: %u", entryHash); - return 0; - } - - // Look up the data for this entry - MultifileEntryStats entryStats = getEntryStats(entryHash); - - size_t cachedValueSize = entryStats.valueSize; - if (cachedValueSize > valueSize) { - ALOGV("GET: Cache MISS - valueSize not large enough (%lu) for entry %u, returning required" - "size (%zu)", - valueSize, entryHash, cachedValueSize); - return cachedValueSize; - } - - // We have the file and have enough room to write it out, return the entry - ALOGV("GET: Cache HIT - cache contains entry: %u", entryHash); - - // Look up the size of the file - size_t fileSize = entryStats.fileSize; - if (keySize > fileSize) { - ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified " - "file", - keySize, fileSize); - return 0; - } - - std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash); - - // Open the hashed filename path - uint8_t* cacheEntry = 0; - - // Check hot cache - if (mHotCache.find(entryHash) != mHotCache.end()) { - ALOGV("GET: HotCache HIT for entry %u", entryHash); - cacheEntry = mHotCache[entryHash].entryBuffer; - } else { - ALOGV("GET: HotCache MISS for entry: %u", entryHash); - - if (mDeferredWrites.find(entryHash) != mDeferredWrites.end()) { - // Wait for writes to complete if there is an outstanding write for this entry - ALOGV("GET: Waiting for write to complete for %u", entryHash); - waitForWorkComplete(); - } - - // Open the entry file - int fd = open(fullPath.c_str(), O_RDONLY); - if (fd == -1) { - ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(), - std::strerror(errno)); - return 0; - } - - // Memory map the file - cacheEntry = - reinterpret_cast(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0)); - if (cacheEntry == MAP_FAILED) { - ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); - close(fd); - return 0; - } - - ALOGV("GET: Adding %u to hot cache", entryHash); - if (!addToHotCache(entryHash, fd, cacheEntry, fileSize)) { - ALOGE("GET: Failed to add %u to hot cache", entryHash); - return 0; - } - - cacheEntry = mHotCache[entryHash].entryBuffer; - } - - // Ensure the header matches - MultifileHeader* header = reinterpret_cast(cacheEntry); - if (header->keySize != keySize || header->valueSize != valueSize) { - ALOGW("Mismatch on keySize(%ld vs. cached %ld) or valueSize(%ld vs. cached %ld) compared " - "to cache header values for fullPath: %s", - keySize, header->keySize, valueSize, header->valueSize, fullPath.c_str()); - removeFromHotCache(entryHash); - return 0; - } - - // Compare the incoming key with our stored version (the beginning of the entry) - uint8_t* cachedKey = cacheEntry + sizeof(MultifileHeader); - int compare = memcmp(cachedKey, key, keySize); - if (compare != 0) { - ALOGW("Cached key and new key do not match! This is a hash collision or modified file"); - removeFromHotCache(entryHash); - return 0; - } - - // Remaining entry following the key is the value - uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader)); - memcpy(value, cachedValue, cachedValueSize); - - return cachedValueSize; -} - -void MultifileBlobCache::finish() { - // Wait for all deferred writes to complete - ALOGV("FINISH: Waiting for work to complete."); - waitForWorkComplete(); - - // Close all entries in the hot cache - for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) { - uint32_t entryHash = hotCacheIter->first; - MultifileHotCache entry = hotCacheIter->second; - - ALOGV("FINISH: Closing hot cache entry for %u", entryHash); - freeHotCacheEntry(entry); - - mHotCache.erase(hotCacheIter++); - } -} - -void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize, - time_t accessTime) { - mEntries.insert(entryHash); - mEntryStats[entryHash] = {valueSize, fileSize, accessTime}; -} - -bool MultifileBlobCache::contains(uint32_t hashEntry) const { - return mEntries.find(hashEntry) != mEntries.end(); -} - -MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) { - return mEntryStats[entryHash]; -} - -void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) { - mTotalCacheSize += fileSize; -} - -void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) { - mTotalCacheSize -= fileSize; -} - -bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer, - size_t newEntrySize) { - ALOGV("HOTCACHE(ADD): Adding %u to hot cache", newEntryHash); - - // Clear space if we need to - if ((mHotCacheSize + newEntrySize) > mHotCacheLimit) { - ALOGV("HOTCACHE(ADD): mHotCacheSize (%zu) + newEntrySize (%zu) is to big for " - "mHotCacheLimit " - "(%zu), freeing up space for %u", - mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash); - - // Wait for all the files to complete writing so our hot cache is accurate - waitForWorkComplete(); - - // Free up old entries until under the limit - for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) { - uint32_t oldEntryHash = hotCacheIter->first; - MultifileHotCache oldEntry = hotCacheIter->second; - - // Move our iterator before deleting the entry - hotCacheIter++; - if (!removeFromHotCache(oldEntryHash)) { - ALOGE("HOTCACHE(ADD): Unable to remove entry %u", oldEntryHash); - return false; - } - - // Clear at least half the hot cache - if ((mHotCacheSize + newEntrySize) <= mHotCacheLimit / 2) { - ALOGV("HOTCACHE(ADD): Freed enough space for %zu", mHotCacheSize); - break; - } - } - } - - // Track it - mHotCache[newEntryHash] = {newFd, newEntryBuffer, newEntrySize}; - mHotCacheSize += newEntrySize; - - ALOGV("HOTCACHE(ADD): New hot cache size: %zu", mHotCacheSize); - - return true; -} - -bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) { - if (mHotCache.find(entryHash) != mHotCache.end()) { - ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash); - - // Wait for all the files to complete writing so our hot cache is accurate - waitForWorkComplete(); - - ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash); - MultifileHotCache entry = mHotCache[entryHash]; - freeHotCacheEntry(entry); - - // Delete the entry from our tracking - mHotCacheSize -= entry.entrySize; - size_t count = mHotCache.erase(entryHash); - - return true; - } - - return false; -} - -bool MultifileBlobCache::applyLRU(size_t cacheLimit) { - // Walk through our map of sorted last access times and remove files until under the limit - for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) { - uint32_t entryHash = cacheEntryIter->first; - - ALOGV("LRU: Removing entryHash %u", entryHash); - - // Track the overall size - MultifileEntryStats entryStats = getEntryStats(entryHash); - decreaseTotalCacheSize(entryStats.fileSize); - - // Remove it from hot cache if present - removeFromHotCache(entryHash); - - // Remove it from the system - std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash); - if (remove(entryPath.c_str()) != 0) { - ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno)); - return false; - } - - // Increment the iterator before clearing the entry - cacheEntryIter++; - - // Delete the entry from our tracking - size_t count = mEntryStats.erase(entryHash); - if (count != 1) { - ALOGE("LRU: Failed to remove entryHash (%u) from mEntryStats", entryHash); - return false; - } - - // See if it has been reduced enough - size_t totalCacheSize = getTotalSize(); - if (totalCacheSize <= cacheLimit) { - // Success - ALOGV("LRU: Reduced cache to %zu", totalCacheSize); - return true; - } - } - - ALOGV("LRU: Cache is emptry"); - return false; -} - -// When removing files, what fraction of the overall limit should be reached when removing files -// A divisor of two will decrease the cache to 50%, four to 25% and so on -constexpr uint32_t kCacheLimitDivisor = 2; - -// Calculate the cache size and remove old entries until under the limit -void MultifileBlobCache::trimCache(size_t cacheByteLimit) { - // Start with the value provided by egl_cache - size_t limit = cacheByteLimit; - - // Wait for all deferred writes to complete - waitForWorkComplete(); - - size_t size = getTotalSize(); - - // If size is larger than the threshold, remove files using LRU - if (size > limit) { - ALOGV("TRIM: Multifile cache size is larger than %zu, removing old entries", - cacheByteLimit); - if (!applyLRU(limit / kCacheLimitDivisor)) { - ALOGE("Error when clearing multifile shader cache"); - return; - } - } -} - -// This function performs a task. It only knows how to write files to disk, -// but it could be expanded if needed. -void MultifileBlobCache::processTask(DeferredTask& task) { - switch (task.getTaskCommand()) { - case TaskCommand::Exit: { - ALOGV("DEFERRED: Shutting down"); - return; - } - case TaskCommand::WriteToDisk: { - uint32_t entryHash = task.getEntryHash(); - std::string& fullPath = task.getFullPath(); - uint8_t* buffer = task.getBuffer(); - size_t bufferSize = task.getBufferSize(); - - // Create the file or reset it if already present, read+write for user only - int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); - if (fd == -1) { - ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s", - fullPath.c_str(), std::strerror(errno)); - return; - } - - ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str()); - - ssize_t result = write(fd, buffer, bufferSize); - if (result != bufferSize) { - ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(), - std::strerror(errno)); - return; - } - - ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str()); - close(fd); - - // Erase the entry from mDeferredWrites - // Since there could be multiple outstanding writes for an entry, find the matching one - typedef std::multimap::iterator entryIter; - std::pair iterPair = mDeferredWrites.equal_range(entryHash); - for (entryIter it = iterPair.first; it != iterPair.second; ++it) { - if (it->second == buffer) { - ALOGV("DEFERRED: Marking write complete for %u at %p", it->first, it->second); - mDeferredWrites.erase(it); - break; - } - } - - return; - } - default: { - ALOGE("DEFERRED: Unhandled task type"); - return; - } - } -} - -// This function will wait until tasks arrive, then execute them -// If the exit command is submitted, the loop will terminate -void MultifileBlobCache::processTasksImpl(bool* exitThread) { - while (true) { - std::unique_lock lock(mWorkerMutex); - if (mTasks.empty()) { - ALOGV("WORKER: No tasks available, waiting"); - mWorkerThreadIdle = true; - mWorkerIdleCondition.notify_all(); - // Only wake if notified and command queue is not empty - mWorkAvailableCondition.wait(lock, [this] { return !mTasks.empty(); }); - } - - ALOGV("WORKER: Task available, waking up."); - mWorkerThreadIdle = false; - DeferredTask task = std::move(mTasks.front()); - mTasks.pop(); - - if (task.getTaskCommand() == TaskCommand::Exit) { - ALOGV("WORKER: Exiting work loop."); - *exitThread = true; - mWorkerThreadIdle = true; - mWorkerIdleCondition.notify_one(); - return; - } - - lock.unlock(); - processTask(task); - } -} - -// Process tasks until the exit task is submitted -void MultifileBlobCache::processTasks() { - while (true) { - bool exitThread = false; - processTasksImpl(&exitThread); - if (exitThread) { - break; - } - } -} - -// Add a task to the queue to be processed by the worker thread -void MultifileBlobCache::queueTask(DeferredTask&& task) { - std::lock_guard queueLock(mWorkerMutex); - mTasks.emplace(std::move(task)); - mWorkAvailableCondition.notify_one(); -} - -// Wait until all tasks have been completed -void MultifileBlobCache::waitForWorkComplete() { - std::unique_lock lock(mWorkerMutex); - mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); }); -} - -}; // namespace android \ No newline at end of file diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h deleted file mode 100644 index dcdfe47c6f..0000000000 --- a/opengl/libs/EGL/MultifileBlobCache.h +++ /dev/null @@ -1,164 +0,0 @@ -/* - ** Copyright 2022, The Android Open Source Project - ** - ** Licensed under the Apache License, Version 2.0 (the "License"); - ** you may not use this file except in compliance with the License. - ** You may obtain a copy of the License at - ** - ** http://www.apache.org/licenses/LICENSE-2.0 - ** - ** Unless required by applicable law or agreed to in writing, software - ** distributed under the License is distributed on an "AS IS" BASIS, - ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ** See the License for the specific language governing permissions and - ** limitations under the License. - */ - -#ifndef ANDROID_MULTIFILE_BLOB_CACHE_H -#define ANDROID_MULTIFILE_BLOB_CACHE_H - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace android { - -struct MultifileHeader { - EGLsizeiANDROID keySize; - EGLsizeiANDROID valueSize; -}; - -struct MultifileEntryStats { - EGLsizeiANDROID valueSize; - size_t fileSize; - time_t accessTime; -}; - -struct MultifileHotCache { - int entryFd; - uint8_t* entryBuffer; - size_t entrySize; -}; - -enum class TaskCommand { - Invalid = 0, - WriteToDisk, - Exit, -}; - -class DeferredTask { -public: - DeferredTask(TaskCommand command) : mCommand(command) {} - - TaskCommand getTaskCommand() { return mCommand; } - - void initWriteToDisk(std::string fullPath, uint8_t* buffer, size_t bufferSize) { - mCommand = TaskCommand::WriteToDisk; - mFullPath = fullPath; - mBuffer = buffer; - mBufferSize = bufferSize; - } - - uint32_t getEntryHash() { return mEntryHash; } - std::string& getFullPath() { return mFullPath; } - uint8_t* getBuffer() { return mBuffer; } - size_t getBufferSize() { return mBufferSize; }; - -private: - TaskCommand mCommand; - - // Parameters for WriteToDisk - uint32_t mEntryHash; - std::string mFullPath; - uint8_t* mBuffer; - size_t mBufferSize; -}; - -class MultifileBlobCache { -public: - MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize, const std::string& baseDir); - ~MultifileBlobCache(); - - void set(const void* key, EGLsizeiANDROID keySize, const void* value, - EGLsizeiANDROID valueSize); - EGLsizeiANDROID get(const void* key, EGLsizeiANDROID keySize, void* value, - EGLsizeiANDROID valueSize); - - void finish(); - - size_t getTotalSize() const { return mTotalCacheSize; } - void trimCache(size_t cacheByteLimit); - -private: - void trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize, - time_t accessTime); - bool contains(uint32_t entryHash) const; - bool removeEntry(uint32_t entryHash); - MultifileEntryStats getEntryStats(uint32_t entryHash); - - size_t getFileSize(uint32_t entryHash); - size_t getValueSize(uint32_t entryHash); - - void increaseTotalCacheSize(size_t fileSize); - void decreaseTotalCacheSize(size_t fileSize); - - bool addToHotCache(uint32_t entryHash, int fd, uint8_t* entryBufer, size_t entrySize); - bool removeFromHotCache(uint32_t entryHash); - - bool applyLRU(size_t cacheLimit); - - bool mInitialized; - std::string mMultifileDirName; - - std::unordered_set mEntries; - std::unordered_map mEntryStats; - std::unordered_map mHotCache; - - size_t mMaxKeySize; - size_t mMaxValueSize; - size_t mMaxTotalSize; - size_t mTotalCacheSize; - size_t mHotCacheLimit; - size_t mHotCacheEntryLimit; - size_t mHotCacheSize; - - // Below are the components used to allow a deferred write - - // Track whether we have pending writes for an entry - std::multimap mDeferredWrites; - - // Functions to work through tasks in the queue - void processTasks(); - void processTasksImpl(bool* exitThread); - void processTask(DeferredTask& task); - - // Used by main thread to create work for the worker thread - void queueTask(DeferredTask&& task); - - // Used by main thread to wait for worker thread to complete all outstanding work. - void waitForWorkComplete(); - - std::thread mTaskThread; - std::queue mTasks; - std::mutex mWorkerMutex; - - // This condition will block the worker thread until a task is queued - std::condition_variable mWorkAvailableCondition; - - // This condition will block the main thread while the worker thread still has tasks - std::condition_variable mWorkerIdleCondition; - - // This bool will track whether all tasks have been completed - bool mWorkerThreadIdle; -}; - -}; // namespace android - -#endif // ANDROID_MULTIFILE_BLOB_CACHE_H diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp deleted file mode 100644 index 1a55a4fcdd..0000000000 --- a/opengl/libs/EGL/MultifileBlobCache_test.cpp +++ /dev/null @@ -1,200 +0,0 @@ -/* - ** Copyright 2023, The Android Open Source Project - ** - ** Licensed under the Apache License, Version 2.0 (the "License"); - ** you may not use this file except in compliance with the License. - ** You may obtain a copy of the License at - ** - ** http://www.apache.org/licenses/LICENSE-2.0 - ** - ** Unless required by applicable law or agreed to in writing, software - ** distributed under the License is distributed on an "AS IS" BASIS, - ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ** See the License for the specific language governing permissions and - ** limitations under the License. - */ - -#include "MultifileBlobCache.h" - -#include -#include -#include -#include - -#include - -namespace android { - -template -using sp = std::shared_ptr; - -constexpr size_t kMaxTotalSize = 32 * 1024; -constexpr size_t kMaxPreloadSize = 8 * 1024; - -constexpr size_t kMaxKeySize = kMaxPreloadSize / 4; -constexpr size_t kMaxValueSize = kMaxPreloadSize / 2; - -class MultifileBlobCacheTest : public ::testing::Test { -protected: - virtual void SetUp() { - mTempFile.reset(new TemporaryFile()); - mMBC.reset(new MultifileBlobCache(kMaxTotalSize, kMaxPreloadSize, &mTempFile->path[0])); - } - - virtual void TearDown() { mMBC.reset(); } - - std::unique_ptr mTempFile; - std::unique_ptr mMBC; -}; - -TEST_F(MultifileBlobCacheTest, CacheSingleValueSucceeds) { - unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; - mMBC->set("abcd", 4, "efgh", 4); - ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); - ASSERT_EQ('e', buf[0]); - ASSERT_EQ('f', buf[1]); - ASSERT_EQ('g', buf[2]); - ASSERT_EQ('h', buf[3]); -} - -TEST_F(MultifileBlobCacheTest, CacheTwoValuesSucceeds) { - unsigned char buf[2] = {0xee, 0xee}; - mMBC->set("ab", 2, "cd", 2); - mMBC->set("ef", 2, "gh", 2); - ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); - ASSERT_EQ('c', buf[0]); - ASSERT_EQ('d', buf[1]); - ASSERT_EQ(size_t(2), mMBC->get("ef", 2, buf, 2)); - ASSERT_EQ('g', buf[0]); - ASSERT_EQ('h', buf[1]); -} - -TEST_F(MultifileBlobCacheTest, GetSetTwiceSucceeds) { - unsigned char buf[2] = {0xee, 0xee}; - mMBC->set("ab", 2, "cd", 2); - ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); - ASSERT_EQ('c', buf[0]); - ASSERT_EQ('d', buf[1]); - // Use the same key, but different value - mMBC->set("ab", 2, "ef", 2); - ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); - ASSERT_EQ('e', buf[0]); - ASSERT_EQ('f', buf[1]); -} - -TEST_F(MultifileBlobCacheTest, GetOnlyWritesInsideBounds) { - unsigned char buf[6] = {0xee, 0xee, 0xee, 0xee, 0xee, 0xee}; - mMBC->set("abcd", 4, "efgh", 4); - ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf + 1, 4)); - ASSERT_EQ(0xee, buf[0]); - ASSERT_EQ('e', buf[1]); - ASSERT_EQ('f', buf[2]); - ASSERT_EQ('g', buf[3]); - ASSERT_EQ('h', buf[4]); - ASSERT_EQ(0xee, buf[5]); -} - -TEST_F(MultifileBlobCacheTest, GetOnlyWritesIfBufferIsLargeEnough) { - unsigned char buf[3] = {0xee, 0xee, 0xee}; - mMBC->set("abcd", 4, "efgh", 4); - ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 3)); - ASSERT_EQ(0xee, buf[0]); - ASSERT_EQ(0xee, buf[1]); - ASSERT_EQ(0xee, buf[2]); -} - -TEST_F(MultifileBlobCacheTest, GetDoesntAccessNullBuffer) { - mMBC->set("abcd", 4, "efgh", 4); - ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, nullptr, 0)); -} - -TEST_F(MultifileBlobCacheTest, MultipleSetsCacheLatestValue) { - unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; - mMBC->set("abcd", 4, "efgh", 4); - mMBC->set("abcd", 4, "ijkl", 4); - ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); - ASSERT_EQ('i', buf[0]); - ASSERT_EQ('j', buf[1]); - ASSERT_EQ('k', buf[2]); - ASSERT_EQ('l', buf[3]); -} - -TEST_F(MultifileBlobCacheTest, SecondSetKeepsFirstValueIfTooLarge) { - unsigned char buf[kMaxValueSize + 1] = {0xee, 0xee, 0xee, 0xee}; - mMBC->set("abcd", 4, "efgh", 4); - mMBC->set("abcd", 4, buf, kMaxValueSize + 1); - ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); - ASSERT_EQ('e', buf[0]); - ASSERT_EQ('f', buf[1]); - ASSERT_EQ('g', buf[2]); - ASSERT_EQ('h', buf[3]); -} - -TEST_F(MultifileBlobCacheTest, DoesntCacheIfKeyIsTooBig) { - char key[kMaxKeySize + 1]; - unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; - for (int i = 0; i < kMaxKeySize + 1; i++) { - key[i] = 'a'; - } - mMBC->set(key, kMaxKeySize + 1, "bbbb", 4); - ASSERT_EQ(size_t(0), mMBC->get(key, kMaxKeySize + 1, buf, 4)); - ASSERT_EQ(0xee, buf[0]); - ASSERT_EQ(0xee, buf[1]); - ASSERT_EQ(0xee, buf[2]); - ASSERT_EQ(0xee, buf[3]); -} - -TEST_F(MultifileBlobCacheTest, DoesntCacheIfValueIsTooBig) { - char buf[kMaxValueSize + 1]; - for (int i = 0; i < kMaxValueSize + 1; i++) { - buf[i] = 'b'; - } - mMBC->set("abcd", 4, buf, kMaxValueSize + 1); - for (int i = 0; i < kMaxValueSize + 1; i++) { - buf[i] = 0xee; - } - ASSERT_EQ(size_t(0), mMBC->get("abcd", 4, buf, kMaxValueSize + 1)); - for (int i = 0; i < kMaxValueSize + 1; i++) { - SCOPED_TRACE(i); - ASSERT_EQ(0xee, buf[i]); - } -} - -TEST_F(MultifileBlobCacheTest, CacheMaxKeySizeSucceeds) { - char key[kMaxKeySize]; - unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; - for (int i = 0; i < kMaxKeySize; i++) { - key[i] = 'a'; - } - mMBC->set(key, kMaxKeySize, "wxyz", 4); - ASSERT_EQ(size_t(4), mMBC->get(key, kMaxKeySize, buf, 4)); - ASSERT_EQ('w', buf[0]); - ASSERT_EQ('x', buf[1]); - ASSERT_EQ('y', buf[2]); - ASSERT_EQ('z', buf[3]); -} - -TEST_F(MultifileBlobCacheTest, CacheMaxValueSizeSucceeds) { - char buf[kMaxValueSize]; - for (int i = 0; i < kMaxValueSize; i++) { - buf[i] = 'b'; - } - mMBC->set("abcd", 4, buf, kMaxValueSize); - for (int i = 0; i < kMaxValueSize; i++) { - buf[i] = 0xee; - } - mMBC->get("abcd", 4, buf, kMaxValueSize); - for (int i = 0; i < kMaxValueSize; i++) { - SCOPED_TRACE(i); - ASSERT_EQ('b', buf[i]); - } -} - -TEST_F(MultifileBlobCacheTest, CacheMinKeyAndValueSizeSucceeds) { - unsigned char buf[1] = {0xee}; - mMBC->set("x", 1, "y", 1); - ASSERT_EQ(size_t(1), mMBC->get("x", 1, buf, 1)); - ASSERT_EQ('y', buf[0]); -} - -} // namespace android diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp index b00ee33374..1e8a34863d 100644 --- a/opengl/libs/EGL/egl_cache.cpp +++ b/opengl/libs/EGL/egl_cache.cpp @@ -14,8 +14,6 @@ ** limitations under the License. */ -// #define LOG_NDEBUG 0 - #include "egl_cache.h" #include @@ -27,19 +25,22 @@ #include #include "../egl_impl.h" +#include "egl_cache_multifile.h" #include "egl_display.h" // Monolithic cache size limits. -static const size_t kMaxMonolithicKeySize = 12 * 1024; -static const size_t kMaxMonolithicValueSize = 64 * 1024; -static const size_t kMaxMonolithicTotalSize = 2 * 1024 * 1024; +static const size_t maxKeySize = 12 * 1024; +static const size_t maxValueSize = 64 * 1024; +static const size_t maxTotalSize = 32 * 1024 * 1024; // The time in seconds to wait before saving newly inserted monolithic cache entries. -static const unsigned int kDeferredMonolithicSaveDelay = 4; +static const unsigned int deferredSaveDelay = 4; + +// Multifile cache size limit +constexpr size_t kMultifileCacheByteLimit = 64 * 1024 * 1024; -// Multifile cache size limits -constexpr uint32_t kMultifileHotCacheLimit = 8 * 1024 * 1024; -constexpr uint32_t kMultifileCacheByteLimit = 32 * 1024 * 1024; +// Delay before cleaning up multifile cache entries +static const unsigned int deferredMultifileCleanupDelaySeconds = 1; namespace android { @@ -67,7 +68,10 @@ static EGLsizeiANDROID getBlob(const void* key, EGLsizeiANDROID keySize, void* v // egl_cache_t definition // egl_cache_t::egl_cache_t() - : mInitialized(false), mMultifileMode(false), mCacheByteLimit(kMaxMonolithicTotalSize) {} + : mInitialized(false), + mMultifileMode(false), + mCacheByteLimit(maxTotalSize), + mMultifileCleanupPending(false) {} egl_cache_t::~egl_cache_t() {} @@ -81,7 +85,7 @@ void egl_cache_t::initialize(egl_display_t* display) { std::lock_guard lock(mMutex); egl_connection_t* const cnx = &gEGLImpl; - if (display && cnx->dso && cnx->major >= 0 && cnx->minor >= 0) { + if (cnx->dso && cnx->major >= 0 && cnx->minor >= 0) { const char* exts = display->disp.queryString.extensions; size_t bcExtLen = strlen(BC_EXT_STR); size_t extsLen = strlen(exts); @@ -110,36 +114,14 @@ void egl_cache_t::initialize(egl_display_t* display) { } } - // Check the device config to decide whether multifile should be used - if (base::GetBoolProperty("ro.egl.blobcache.multifile", false)) { - mMultifileMode = true; - ALOGV("Using multifile EGL blobcache"); - } - - // Allow forcing the mode for debug purposes - std::string mode = base::GetProperty("debug.egl.blobcache.multifile", ""); - if (mode == "true") { - ALOGV("Forcing multifile cache due to debug.egl.blobcache.multifile == %s", mode.c_str()); - mMultifileMode = true; - } else if (mode == "false") { - ALOGV("Forcing monolithic cache due to debug.egl.blobcache.multifile == %s", mode.c_str()); + // Allow forcing monolithic cache for debug purposes + if (base::GetProperty("debug.egl.blobcache.multifilemode", "") == "false") { + ALOGD("Forcing monolithic cache due to debug.egl.blobcache.multifilemode == \"false\""); mMultifileMode = false; } if (mMultifileMode) { - mCacheByteLimit = static_cast( - base::GetUintProperty("ro.egl.blobcache.multifile_limit", - kMultifileCacheByteLimit)); - - // Check for a debug value - int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.multifile_limit", -1); - if (debugCacheSize >= 0) { - ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.multifile_limit", - mCacheByteLimit, debugCacheSize); - mCacheByteLimit = debugCacheSize; - } - - ALOGV("Using multifile EGL blobcache limit of %zu bytes", mCacheByteLimit); + mCacheByteLimit = kMultifileCacheByteLimit; } mInitialized = true; @@ -151,10 +133,10 @@ void egl_cache_t::terminate() { mBlobCache->writeToFile(); } mBlobCache = nullptr; - if (mMultifileBlobCache) { - mMultifileBlobCache->finish(); + if (mMultifileMode) { + checkMultifileCacheSize(mCacheByteLimit); } - mMultifileBlobCache = nullptr; + mMultifileMode = false; mInitialized = false; } @@ -169,8 +151,20 @@ void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void* if (mInitialized) { if (mMultifileMode) { - MultifileBlobCache* mbc = getMultifileBlobCacheLocked(); - mbc->set(key, keySize, value, valueSize); + setBlobMultifile(key, keySize, value, valueSize, mFilename); + + if (!mMultifileCleanupPending) { + mMultifileCleanupPending = true; + // Kick off a thread to cull cache files below limit + std::thread deferredMultifileCleanupThread([this]() { + sleep(deferredMultifileCleanupDelaySeconds); + std::lock_guard lock(mMutex); + // Check the size of cache and remove entries to stay under limit + checkMultifileCacheSize(mCacheByteLimit); + mMultifileCleanupPending = false; + }); + deferredMultifileCleanupThread.detach(); + } } else { BlobCache* bc = getBlobCacheLocked(); bc->set(key, keySize, value, valueSize); @@ -178,7 +172,7 @@ void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void* if (!mSavePending) { mSavePending = true; std::thread deferredSaveThread([this]() { - sleep(kDeferredMonolithicSaveDelay); + sleep(deferredSaveDelay); std::lock_guard lock(mMutex); if (mInitialized && mBlobCache) { mBlobCache->writeToFile(); @@ -202,21 +196,15 @@ EGLsizeiANDROID egl_cache_t::getBlob(const void* key, EGLsizeiANDROID keySize, v if (mInitialized) { if (mMultifileMode) { - MultifileBlobCache* mbc = getMultifileBlobCacheLocked(); - return mbc->get(key, keySize, value, valueSize); + return getBlobMultifile(key, keySize, value, valueSize, mFilename); } else { BlobCache* bc = getBlobCacheLocked(); return bc->get(key, keySize, value, valueSize); } } - return 0; } -void egl_cache_t::setCacheMode(EGLCacheMode cacheMode) { - mMultifileMode = (cacheMode == EGLCacheMode::Multifile); -} - void egl_cache_t::setCacheFilename(const char* filename) { std::lock_guard lock(mMutex); mFilename = filename; @@ -228,7 +216,7 @@ void egl_cache_t::setCacheLimit(int64_t cacheByteLimit) { if (!mMultifileMode) { // If we're not in multifile mode, ensure the cache limit is only being lowered, // not increasing above the hard coded platform limit - if (cacheByteLimit > kMaxMonolithicTotalSize) { + if (cacheByteLimit > maxTotalSize) { return; } } @@ -238,8 +226,8 @@ void egl_cache_t::setCacheLimit(int64_t cacheByteLimit) { size_t egl_cache_t::getCacheSize() { std::lock_guard lock(mMutex); - if (mMultifileBlobCache) { - return mMultifileBlobCache->getTotalSize(); + if (mMultifileMode) { + return getMultifileCacheSize(); } if (mBlobCache) { return mBlobCache->getSize(); @@ -249,18 +237,9 @@ size_t egl_cache_t::getCacheSize() { BlobCache* egl_cache_t::getBlobCacheLocked() { if (mBlobCache == nullptr) { - mBlobCache.reset(new FileBlobCache(kMaxMonolithicKeySize, kMaxMonolithicValueSize, - mCacheByteLimit, mFilename)); + mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, mCacheByteLimit, mFilename)); } return mBlobCache.get(); } -MultifileBlobCache* egl_cache_t::getMultifileBlobCacheLocked() { - if (mMultifileBlobCache == nullptr) { - mMultifileBlobCache.reset( - new MultifileBlobCache(mCacheByteLimit, kMultifileHotCacheLimit, mFilename)); - } - return mMultifileBlobCache.get(); -} - }; // namespace android diff --git a/opengl/libs/EGL/egl_cache.h b/opengl/libs/EGL/egl_cache.h index 1399368dd8..2dcd803324 100644 --- a/opengl/libs/EGL/egl_cache.h +++ b/opengl/libs/EGL/egl_cache.h @@ -25,7 +25,6 @@ #include #include "FileBlobCache.h" -#include "MultifileBlobCache.h" namespace android { @@ -33,11 +32,6 @@ class egl_display_t; class EGLAPI egl_cache_t { public: - enum class EGLCacheMode { - Monolithic, - Multifile, - }; - // get returns a pointer to the singleton egl_cache_t object. This // singleton object will never be destroyed. static egl_cache_t* get(); @@ -70,9 +64,6 @@ public: // cache contents from one program invocation to another. void setCacheFilename(const char* filename); - // Allow setting monolithic or multifile modes - void setCacheMode(EGLCacheMode cacheMode); - // Allow the fixed cache limit to be overridden void setCacheLimit(int64_t cacheByteLimit); @@ -94,9 +85,6 @@ private: // possible. BlobCache* getBlobCacheLocked(); - // Get or create the multifile blobcache - MultifileBlobCache* getMultifileBlobCacheLocked(); - // mInitialized indicates whether the egl_cache_t is in the initialized // state. It is initialized to false at construction time, and gets set to // true when initialize is called. It is set back to false when terminate @@ -110,9 +98,6 @@ private: // first time it's needed. std::unique_ptr mBlobCache; - // The multifile version of blobcache allowing larger contents to be stored - std::unique_ptr mMultifileBlobCache; - // mFilename is the name of the file for storing cache contents in between // program invocations. It is initialized to an empty string at // construction time, and can be set with the setCacheFilename method. An @@ -138,7 +123,11 @@ private: bool mMultifileMode; // Cache limit - size_t mCacheByteLimit; + int64_t mCacheByteLimit; + + // Whether we've kicked off a side thread that will check the multifile + // cache size and remove entries if needed. + bool mMultifileCleanupPending; }; }; // namespace android diff --git a/opengl/libs/EGL/egl_cache_multifile.cpp b/opengl/libs/EGL/egl_cache_multifile.cpp new file mode 100644 index 0000000000..48e557f190 --- /dev/null +++ b/opengl/libs/EGL/egl_cache_multifile.cpp @@ -0,0 +1,343 @@ +/* + ** Copyright 2022, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +// #define LOG_NDEBUG 0 + +#include "egl_cache_multifile.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static std::string multifileDirName = ""; + +using namespace std::literals; + +namespace { + +// Create a directory for tracking multiple files +void setupMultifile(const std::string& baseDir) { + // If we've already set up the multifile dir in this base directory, we're done + if (!multifileDirName.empty() && multifileDirName.find(baseDir) != std::string::npos) { + return; + } + + // Otherwise, create it + multifileDirName = baseDir + ".multifile"; + if (mkdir(multifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) { + ALOGW("Unable to create directory (%s), errno (%i)", multifileDirName.c_str(), errno); + } +} + +// Create a filename that is based on the hash of the key +std::string getCacheEntryFilename(const void* key, EGLsizeiANDROID keySize, + const std::string& baseDir) { + // Hash the key into a string + std::stringstream keyName; + keyName << android::JenkinsHashMixBytes(0, static_cast(key), keySize); + + // Build a filename using dir and hash + return baseDir + "/" + keyName.str(); +} + +// Determine file age based on stat modification time +// Newer files have a higher age (time since epoch) +time_t getFileAge(const std::string& filePath) { + struct stat st; + if (stat(filePath.c_str(), &st) == 0) { + ALOGD("getFileAge returning %" PRId64 " for file age", static_cast(st.st_mtime)); + return st.st_mtime; + } else { + ALOGW("Failed to stat %s", filePath.c_str()); + return 0; + } +} + +size_t getFileSize(const std::string& filePath) { + struct stat st; + if (stat(filePath.c_str(), &st) != 0) { + ALOGE("Unable to stat %s", filePath.c_str()); + return 0; + } + return st.st_size; +} + +// Walk through directory entries and track age and size +// Then iterate through the entries, oldest first, and remove them until under the limit. +// This will need to be updated if we move to a multilevel cache dir. +bool applyLRU(size_t cacheLimit) { + // Build a multimap of files indexed by age. + // They will be automatically sorted smallest (oldest) to largest (newest) + std::multimap agesToFiles; + + // Map files to sizes + std::unordered_map filesToSizes; + + size_t totalCacheSize = 0; + + DIR* dir; + struct dirent* entry; + if ((dir = opendir(multifileDirName.c_str())) != nullptr) { + while ((entry = readdir(dir)) != nullptr) { + if (entry->d_name == "."s || entry->d_name == ".."s) { + continue; + } + + // Look up each file age + std::string fullPath = multifileDirName + "/" + entry->d_name; + time_t fileAge = getFileAge(fullPath); + + // Track the files, sorted by age + agesToFiles.insert(std::make_pair(fileAge, fullPath)); + + // Also track the size so we know how much room we have freed + size_t fileSize = getFileSize(fullPath); + filesToSizes[fullPath] = fileSize; + totalCacheSize += fileSize; + } + closedir(dir); + } else { + ALOGE("Unable to open filename: %s", multifileDirName.c_str()); + return false; + } + + if (totalCacheSize <= cacheLimit) { + // If LRU was called on a sufficiently small cache, no need to remove anything + return true; + } + + // Walk through the map of files until we're under the cache size + for (const auto& cacheEntryIter : agesToFiles) { + time_t entryAge = cacheEntryIter.first; + const std::string entryPath = cacheEntryIter.second; + + ALOGD("Removing %s with age %ld", entryPath.c_str(), entryAge); + if (std::remove(entryPath.c_str()) != 0) { + ALOGE("Error removing %s: %s", entryPath.c_str(), std::strerror(errno)); + return false; + } + + totalCacheSize -= filesToSizes[entryPath]; + if (totalCacheSize <= cacheLimit) { + // Success + ALOGV("Reduced cache to %zu", totalCacheSize); + return true; + } else { + ALOGD("Cache size is still too large (%zu), removing more files", totalCacheSize); + } + } + + // Should never reach this return + return false; +} + +} // namespace + +namespace android { + +void setBlobMultifile(const void* key, EGLsizeiANDROID keySize, const void* value, + EGLsizeiANDROID valueSize, const std::string& baseDir) { + if (baseDir.empty()) { + return; + } + + setupMultifile(baseDir); + std::string filename = getCacheEntryFilename(key, keySize, multifileDirName); + + ALOGD("Attempting to open filename for set: %s", filename.c_str()); + std::ofstream outfile(filename, std::ofstream::binary); + if (outfile.fail()) { + ALOGW("Unable to open filename: %s", filename.c_str()); + return; + } + + // First write the key + outfile.write(static_cast(key), keySize); + if (outfile.bad()) { + ALOGW("Unable to write key to filename: %s", filename.c_str()); + outfile.close(); + return; + } + ALOGD("Wrote %i bytes to out file for key", static_cast(outfile.tellp())); + + // Then write the value + outfile.write(static_cast(value), valueSize); + if (outfile.bad()) { + ALOGW("Unable to write value to filename: %s", filename.c_str()); + outfile.close(); + return; + } + ALOGD("Wrote %i bytes to out file for full entry", static_cast(outfile.tellp())); + + outfile.close(); +} + +EGLsizeiANDROID getBlobMultifile(const void* key, EGLsizeiANDROID keySize, void* value, + EGLsizeiANDROID valueSize, const std::string& baseDir) { + if (baseDir.empty()) { + return 0; + } + + setupMultifile(baseDir); + std::string filename = getCacheEntryFilename(key, keySize, multifileDirName); + + // Open the hashed filename path + ALOGD("Attempting to open filename for get: %s", filename.c_str()); + int fd = open(filename.c_str(), O_RDONLY); + + // File doesn't exist, this is a MISS, return zero bytes read + if (fd == -1) { + ALOGD("Cache MISS - failed to open filename: %s, error: %s", filename.c_str(), + std::strerror(errno)); + return 0; + } + + ALOGD("Cache HIT - opened filename: %s", filename.c_str()); + + // Get the size of the file + size_t entrySize = getFileSize(filename); + if (keySize > entrySize) { + ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified " + "file", + keySize, entrySize); + close(fd); + return 0; + } + + // Memory map the file + uint8_t* cacheEntry = + reinterpret_cast(mmap(nullptr, entrySize, PROT_READ, MAP_PRIVATE, fd, 0)); + if (cacheEntry == MAP_FAILED) { + ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); + close(fd); + return 0; + } + + // Compare the incoming key with our stored version (the beginning of the entry) + int compare = memcmp(cacheEntry, key, keySize); + if (compare != 0) { + ALOGW("Cached key and new key do not match! This is a hash collision or modified file"); + munmap(cacheEntry, entrySize); + close(fd); + return 0; + } + + // Keys matched, so remaining cache is value size + size_t cachedValueSize = entrySize - keySize; + + // Return actual value size if valueSize is not large enough + if (cachedValueSize > valueSize) { + ALOGD("Skipping file read, not enough room provided (valueSize): %lu, " + "returning required space as %zu", + valueSize, cachedValueSize); + munmap(cacheEntry, entrySize); + close(fd); + return cachedValueSize; + } + + // Remaining entry following the key is the value + uint8_t* cachedValue = cacheEntry + keySize; + memcpy(value, cachedValue, cachedValueSize); + munmap(cacheEntry, entrySize); + close(fd); + + ALOGD("Read %zu bytes from %s", cachedValueSize, filename.c_str()); + return cachedValueSize; +} + +// Walk through the files in our flat directory, checking the size of each one. +// Return the total size of normal files in the directory. +// This will need to be updated if we move to a multilevel cache dir. +size_t getMultifileCacheSize() { + if (multifileDirName.empty()) { + return 0; + } + + DIR* dir; + struct dirent* entry; + size_t size = 0; + + ALOGD("Using %s as the multifile cache dir ", multifileDirName.c_str()); + + if ((dir = opendir(multifileDirName.c_str())) != nullptr) { + while ((entry = readdir(dir)) != nullptr) { + if (entry->d_name == "."s || entry->d_name == ".."s) { + continue; + } + + // Add up the size of all files in the dir + std::string fullPath = multifileDirName + "/" + entry->d_name; + size += getFileSize(fullPath); + } + closedir(dir); + } else { + ALOGW("Unable to open filename: %s", multifileDirName.c_str()); + return 0; + } + + return size; +} + +// When removing files, what fraction of the overall limit should be reached when removing files +// A divisor of two will decrease the cache to 50%, four to 25% and so on +constexpr uint32_t kCacheLimitDivisor = 2; + +// Calculate the cache size and remove old entries until under the limit +void checkMultifileCacheSize(size_t cacheByteLimit) { + // Start with the value provided by egl_cache + size_t limit = cacheByteLimit; + + // Check for a debug value + int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.bytelimit", -1); + if (debugCacheSize >= 0) { + ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.bytelimit", limit, + debugCacheSize); + limit = debugCacheSize; + } + + // Tally up the initial amount of cache in use + size_t size = getMultifileCacheSize(); + ALOGD("Multifile cache dir size: %zu", size); + + // If size is larger than the threshold, remove files using LRU + if (size > limit) { + ALOGV("Multifile cache size is larger than %zu, removing old entries", cacheByteLimit); + if (!applyLRU(limit / kCacheLimitDivisor)) { + ALOGE("Error when clearing multifile shader cache"); + return; + } + } + ALOGD("Multifile cache size after reduction: %zu", getMultifileCacheSize()); +} + +}; // namespace android \ No newline at end of file diff --git a/opengl/libs/EGL/egl_cache_multifile.h b/opengl/libs/EGL/egl_cache_multifile.h new file mode 100644 index 0000000000..ee5fe8108d --- /dev/null +++ b/opengl/libs/EGL/egl_cache_multifile.h @@ -0,0 +1,36 @@ +/* + ** Copyright 2022, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +#ifndef ANDROID_EGL_CACHE_MULTIFILE_H +#define ANDROID_EGL_CACHE_MULTIFILE_H + +#include +#include + +#include + +namespace android { + +void setBlobMultifile(const void* key, EGLsizeiANDROID keySize, const void* value, + EGLsizeiANDROID valueSize, const std::string& baseDir); +EGLsizeiANDROID getBlobMultifile(const void* key, EGLsizeiANDROID keySize, void* value, + EGLsizeiANDROID valueSize, const std::string& baseDir); +size_t getMultifileCacheSize(); +void checkMultifileCacheSize(size_t cacheByteLimit); + +}; // namespace android + +#endif // ANDROID_EGL_CACHE_MULTIFILE_H diff --git a/opengl/tests/EGLTest/egl_cache_test.cpp b/opengl/tests/EGLTest/egl_cache_test.cpp index 32e408c1cd..265bec492e 100644 --- a/opengl/tests/EGLTest/egl_cache_test.cpp +++ b/opengl/tests/EGLTest/egl_cache_test.cpp @@ -24,7 +24,7 @@ #include #include "egl_cache.h" -#include "MultifileBlobCache.h" +#include "egl_cache_multifile.h" #include "egl_display.h" #include @@ -33,16 +33,12 @@ using namespace std::literals; namespace android { -class EGLCacheTest : public ::testing::TestWithParam { +class EGLCacheTest : public ::testing::Test { protected: virtual void SetUp() { - // Terminate to clean up any previous cache in this process - mCache->terminate(); - + mCache = egl_cache_t::get(); mTempFile.reset(new TemporaryFile()); mCache->setCacheFilename(&mTempFile->path[0]); - mCache->setCacheLimit(1024); - mCache->setCacheMode(mCacheMode); } virtual void TearDown() { @@ -53,12 +49,11 @@ protected: std::string getCachefileName(); - egl_cache_t* mCache = egl_cache_t::get(); + egl_cache_t* mCache; std::unique_ptr mTempFile; - egl_cache_t::EGLCacheMode mCacheMode = GetParam(); }; -TEST_P(EGLCacheTest, UninitializedCacheAlwaysMisses) { +TEST_F(EGLCacheTest, UninitializedCacheAlwaysMisses) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->setBlob("abcd", 4, "efgh", 4); ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4)); @@ -68,7 +63,7 @@ TEST_P(EGLCacheTest, UninitializedCacheAlwaysMisses) { ASSERT_EQ(0xee, buf[3]); } -TEST_P(EGLCacheTest, InitializedCacheAlwaysHits) { +TEST_F(EGLCacheTest, InitializedCacheAlwaysHits) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -79,7 +74,7 @@ TEST_P(EGLCacheTest, InitializedCacheAlwaysHits) { ASSERT_EQ('h', buf[3]); } -TEST_P(EGLCacheTest, TerminatedCacheAlwaysMisses) { +TEST_F(EGLCacheTest, TerminatedCacheAlwaysMisses) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -91,7 +86,7 @@ TEST_P(EGLCacheTest, TerminatedCacheAlwaysMisses) { ASSERT_EQ(0xee, buf[3]); } -TEST_P(EGLCacheTest, ReinitializedCacheContainsValues) { +TEST_F(EGLCacheTest, ReinitializedCacheContainsValues) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -106,12 +101,12 @@ TEST_P(EGLCacheTest, ReinitializedCacheContainsValues) { std::string EGLCacheTest::getCachefileName() { // Return the monolithic filename unless we find the multifile dir - std::string cachePath = &mTempFile->path[0]; - std::string multifileDirName = cachePath + ".multifile"; - std::string cachefileName = ""; + std::string cachefileName = &mTempFile->path[0]; + std::string multifileDirName = cachefileName + ".multifile"; struct stat info; if (stat(multifileDirName.c_str(), &info) == 0) { + // Ensure we only have one file to manage int realFileCount = 0; @@ -126,8 +121,6 @@ std::string EGLCacheTest::getCachefileName() { cachefileName = multifileDirName + "/" + entry->d_name; realFileCount++; } - } else { - printf("Unable to open %s, error: %s\n", multifileDirName.c_str(), std::strerror(errno)); } if (realFileCount != 1) { @@ -135,18 +128,14 @@ std::string EGLCacheTest::getCachefileName() { // violates test assumptions cachefileName = ""; } - } else { - printf("Unable to stat %s, error: %s\n", multifileDirName.c_str(), std::strerror(errno)); } return cachefileName; } -TEST_P(EGLCacheTest, ModifiedCacheMisses) { - // Skip if not in multifile mode - if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) { - GTEST_SKIP() << "Skipping test designed for multifile"; - } +TEST_F(EGLCacheTest, ModifiedCacheMisses) { + // Turn this back on if multifile becomes the default + GTEST_SKIP() << "Skipping test designed for multifile, see b/263574392 and b/246966894"; uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); @@ -158,13 +147,13 @@ TEST_P(EGLCacheTest, ModifiedCacheMisses) { ASSERT_EQ('g', buf[2]); ASSERT_EQ('h', buf[3]); - // Ensure the cache file is written to disk - mCache->terminate(); - // Depending on the cache mode, the file will be in different locations std::string cachefileName = getCachefileName(); ASSERT_TRUE(cachefileName.length() > 0); + // Ensure the cache file is written to disk + mCache->terminate(); + // Stomp on the beginning of the cache file, breaking the key match const long stomp = 0xbadf00d; FILE *file = fopen(cachefileName.c_str(), "w"); @@ -175,15 +164,14 @@ TEST_P(EGLCacheTest, ModifiedCacheMisses) { // Ensure no cache hit mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); uint8_t buf2[4] = { 0xee, 0xee, 0xee, 0xee }; - // getBlob may return junk for required size, but should not return a cache hit - mCache->getBlob("abcd", 4, buf2, 4); + ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf2, 4)); ASSERT_EQ(0xee, buf2[0]); ASSERT_EQ(0xee, buf2[1]); ASSERT_EQ(0xee, buf2[2]); ASSERT_EQ(0xee, buf2[3]); } -TEST_P(EGLCacheTest, TerminatedCacheBelowCacheLimit) { +TEST_F(EGLCacheTest, TerminatedCacheBelowCacheLimit) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); @@ -216,6 +204,4 @@ TEST_P(EGLCacheTest, TerminatedCacheBelowCacheLimit) { ASSERT_LE(mCache->getCacheSize(), 4); } -INSTANTIATE_TEST_CASE_P(MonolithicCacheTests, EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Monolithic)); -INSTANTIATE_TEST_CASE_P(MultifileCacheTests, EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Multifile)); } -- GitLab From 8a878356682afb9801816785ab7ade6f68384108 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 30 Jan 2023 14:05:01 -0800 Subject: [PATCH 0811/1310] Use std::bitset for tracking pointers in the dispatcher Convert pointerIds to std::bitset. This will allow us to do bitwise operations with other fields that already use std::bitset. Bug: 211379801 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: I0347b5e171a0597fc88ba628f40dcf6cd59ea093 --- .../dispatcher/CancelationOptions.h | 5 +- .../dispatcher/InputDispatcher.cpp | 85 ++++++++++--------- .../inputflinger/dispatcher/InputDispatcher.h | 10 ++- .../inputflinger/dispatcher/InputState.cpp | 5 +- services/inputflinger/dispatcher/InputState.h | 4 +- .../inputflinger/dispatcher/InputTarget.cpp | 23 ++--- .../inputflinger/dispatcher/InputTarget.h | 5 +- .../inputflinger/dispatcher/TouchState.cpp | 31 +++---- services/inputflinger/dispatcher/TouchState.h | 7 +- .../inputflinger/dispatcher/TouchedWindow.cpp | 4 +- .../inputflinger/dispatcher/TouchedWindow.h | 6 +- 11 files changed, 105 insertions(+), 80 deletions(-) diff --git a/services/inputflinger/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h index 512cb6e692..48f9f2b78a 100644 --- a/services/inputflinger/dispatcher/CancelationOptions.h +++ b/services/inputflinger/dispatcher/CancelationOptions.h @@ -16,7 +16,8 @@ #pragma once -#include +#include +#include #include namespace android { @@ -47,7 +48,7 @@ struct CancelationOptions { std::optional displayId = std::nullopt; // The specific pointers to cancel, or nullopt to cancel all pointer events - std::optional pointerIds = std::nullopt; + std::optional> pointerIds = std::nullopt; CancelationOptions(Mode mode, const char* reason) : mode(mode), reason(reason) {} }; diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 143d25ce44..079b80dad6 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -190,7 +190,7 @@ bool validateMotionEvent(int32_t action, int32_t actionButton, size_t pointerCou pointerCount, MAX_POINTERS); return false; } - BitSet32 pointerIdBits; + std::bitset pointerIdBits; for (size_t i = 0; i < pointerCount; i++) { int32_t id = pointerProperties[i].id; if (id < 0 || id > MAX_POINTER_ID) { @@ -198,11 +198,11 @@ bool validateMotionEvent(int32_t action, int32_t actionButton, size_t pointerCou MAX_POINTER_ID); return false; } - if (pointerIdBits.hasBit(id)) { + if (pointerIdBits.test(id)) { ALOGE("Motion event has duplicate pointer id %d", id); return false; } - pointerIdBits.markBit(id); + pointerIdBits.set(id); } return true; } @@ -292,6 +292,17 @@ bool haveSameApplicationToken(const WindowInfo* first, const WindowInfo* second) first->applicationInfo.token == second->applicationInfo.token; } +template +size_t firstMarkedBit(T set) { + // TODO: replace with std::countr_zero from when that's available + LOG_ALWAYS_FATAL_IF(set.none()); + size_t i = 0; + while (!set.test(i)) { + i++; + } + return i; +} + std::unique_ptr createDispatchEntry( const InputTarget& inputTarget, std::shared_ptr eventEntry, ftl::Flags inputTargetFlags) { @@ -312,7 +323,7 @@ std::unique_ptr createDispatchEntry( // as long as all other pointers are normalized to the same value and the final DispatchEntry // uses the transform for the normalized pointer. const ui::Transform& firstPointerTransform = - inputTarget.pointerTransforms[inputTarget.pointerIds.firstMarkedBit()]; + inputTarget.pointerTransforms[firstMarkedBit(inputTarget.pointerIds)]; ui::Transform inverseFirstTransform = firstPointerTransform.inverse(); // Iterate through all pointers in the event to normalize against the first. @@ -591,7 +602,7 @@ std::vector getHoveringWindowsLocked(const TouchState* oldState, TouchedWindow touchedWindow; touchedWindow.windowHandle = oldWindow; touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_HOVER_EXIT; - touchedWindow.pointerIds.markBit(pointerId); + touchedWindow.pointerIds.set(pointerId); out.push_back(touchedWindow); } } @@ -608,7 +619,7 @@ std::vector getHoveringWindowsLocked(const TouchState* oldState, LOG_ALWAYS_FATAL_IF(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE); touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_IS; } - touchedWindow.pointerIds.markBit(pointerId); + touchedWindow.pointerIds.set(pointerId); if (canReceiveForegroundTouches(*newWindow->getInfo())) { touchedWindow.targetFlags |= InputTarget::Flags::FOREGROUND; } @@ -1165,7 +1176,7 @@ InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x, int32_t if (info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) { addWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE, - BitSet32(0), /*firstDownTimeInTarget=*/std::nullopt, + /*pointerIds=*/{}, /*firstDownTimeInTarget=*/std::nullopt, outsideTargets); } } @@ -1662,7 +1673,7 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr inputTargets; addWindowTargetLocked(focusedWindow, InputTarget::Flags::FOREGROUND | InputTarget::Flags::DISPATCH_AS_IS, - BitSet32(0), getDownTime(*entry), inputTargets); + /*pointerIds=*/{}, getDownTime(*entry), inputTargets); // Add monitor channels from event's or focused display. addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry)); @@ -1771,7 +1782,7 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr< addWindowTargetLocked(focusedWindow, InputTarget::Flags::FOREGROUND | InputTarget::Flags::DISPATCH_AS_IS, - BitSet32(0), getDownTime(*entry), inputTargets); + /*pointerIds=*/{}, getDownTime(*entry), inputTargets); } } if (injectionResult == InputEventInjectionResult::PENDING) { @@ -2126,7 +2137,7 @@ bool InputDispatcher::shouldSplitTouch(const TouchState& touchState, // Eventually, touchedWindow will contain the deviceId of each pointer that's currently // being sent there. For now, use deviceId from touch state. - if (entry.deviceId == touchState.deviceId && !touchedWindow.pointerIds.isEmpty()) { + if (entry.deviceId == touchState.deviceId && touchedWindow.pointerIds.any()) { return false; } } @@ -2297,9 +2308,9 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } // Update the temporary touch state. - BitSet32 pointerIds; + std::bitset pointerIds; if (!isHoverAction) { - pointerIds.markBit(entry.pointerProperties[pointerIndex].id); + pointerIds.set(entry.pointerProperties[pointerIndex].id); } const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN || @@ -2341,7 +2352,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( // which is a specific behaviour that we want. const int32_t pointerId = entry.pointerProperties[pointerIndex].id; for (TouchedWindow& touchedWindow : tempTouchState.windows) { - if (touchedWindow.pointerIds.hasBit(pointerId) && + if (touchedWindow.pointerIds.test(pointerId) && touchedWindow.pilferedPointerIds.count() > 0) { // This window is already pilfering some pointers, and this new pointer is also // going to it. Therefore, take over this pointer and don't give it to anyone @@ -2397,9 +2408,9 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( newTouchedWindowHandle->getName().c_str(), displayId); } // Make a slippery exit from the old window. - BitSet32 pointerIds; + std::bitset pointerIds; const int32_t pointerId = entry.pointerProperties[0].id; - pointerIds.markBit(pointerId); + pointerIds.set(pointerId); const TouchedWindow& touchedWindow = tempTouchState.getTouchedWindow(oldTouchedWindowHandle); @@ -2446,7 +2457,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) { continue; } - touchedWindow.pointerIds.markBit(entry.pointerProperties[pointerIndex].id); + touchedWindow.pointerIds.set(entry.pointerProperties[pointerIndex].id); } } } @@ -2520,8 +2531,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( // Success! Output targets from the touch state. tempTouchState.clearWindowsWithoutPointers(); for (const TouchedWindow& touchedWindow : tempTouchState.windows) { - if (touchedWindow.pointerIds.isEmpty() && - !touchedWindow.hasHoveringPointers(entry.deviceId)) { + if (touchedWindow.pointerIds.none() && !touchedWindow.hasHoveringPointers(entry.deviceId)) { // Windows with hovering pointers are getting persisted inside TouchState. // Do not send this event to those windows. continue; @@ -2576,8 +2586,8 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( for (size_t i = 0; i < tempTouchState.windows.size();) { TouchedWindow& touchedWindow = tempTouchState.windows[i]; - touchedWindow.pointerIds.clearBit(pointerId); - if (touchedWindow.pointerIds.isEmpty()) { + touchedWindow.pointerIds.reset(pointerId); + if (touchedWindow.pointerIds.none()) { tempTouchState.windows.erase(tempTouchState.windows.begin() + i); continue; } @@ -2702,7 +2712,7 @@ void InputDispatcher::addDragEventLocked(const MotionEntry& entry) { void InputDispatcher::addWindowTargetLocked(const sp& windowHandle, ftl::Flags targetFlags, - BitSet32 pointerIds, + std::bitset pointerIds, std::optional firstDownTimeInTarget, std::vector& inputTargets) const { std::vector::iterator it = @@ -3012,9 +3022,9 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, } if (DEBUG_DISPATCH_CYCLE) { ALOGD("channel '%s' ~ prepareDispatchCycle - flags=%s, " - "globalScaleFactor=%f, pointerIds=0x%x %s", + "globalScaleFactor=%f, pointerIds=%s %s", connection->getInputChannelName().c_str(), inputTarget.flags.string().c_str(), - inputTarget.globalScaleFactor, inputTarget.pointerIds.value, + inputTarget.globalScaleFactor, bitsetToString(inputTarget.pointerIds).c_str(), inputTarget.getPointerInfoString().c_str()); } @@ -3887,8 +3897,9 @@ void InputDispatcher::synthesizeCancelationEventsForWindowLocked( } std::unique_ptr InputDispatcher::splitMotionEvent( - const MotionEntry& originalMotionEntry, BitSet32 pointerIds, nsecs_t splitDownTime) { - ALOG_ASSERT(pointerIds.value != 0); + const MotionEntry& originalMotionEntry, std::bitset pointerIds, + nsecs_t splitDownTime) { + ALOG_ASSERT(pointerIds.any()); uint32_t splitPointerIndexMap[MAX_POINTERS]; PointerProperties splitPointerProperties[MAX_POINTERS]; @@ -3902,7 +3913,7 @@ std::unique_ptr InputDispatcher::splitMotionEvent( const PointerProperties& pointerProperties = originalMotionEntry.pointerProperties[originalPointerIndex]; uint32_t pointerId = uint32_t(pointerProperties.id); - if (pointerIds.hasBit(pointerId)) { + if (pointerIds.test(pointerId)) { splitPointerIndexMap[splitPointerCount] = originalPointerIndex; splitPointerProperties[splitPointerCount].copyFrom(pointerProperties); splitPointerCoords[splitPointerCount].copyFrom( @@ -3918,7 +3929,7 @@ std::unique_ptr InputDispatcher::splitMotionEvent( // or ACTION_POINTER_DOWN events that caused us to decide to split the pointers // in this way. ALOGW("Dropping split motion event because the pointer count is %d but " - "we expected there to be %d pointers. This probably means we received " + "we expected there to be %zu pointers. This probably means we received " "a broken sequence of pointer ids from the input device.", splitPointerCount, pointerIds.count()); return nullptr; @@ -3932,7 +3943,7 @@ std::unique_ptr InputDispatcher::splitMotionEvent( const PointerProperties& pointerProperties = originalMotionEntry.pointerProperties[originalPointerIndex]; uint32_t pointerId = uint32_t(pointerProperties.id); - if (pointerIds.hasBit(pointerId)) { + if (pointerIds.test(pointerId)) { if (pointerIds.count() == 1) { // The first/last pointer went down/up. action = maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN @@ -5243,7 +5254,7 @@ bool InputDispatcher::transferTouchFocus(const sp& fromToken, const sp< // Erase old window. ftl::Flags oldTargetFlags = touchedWindow->targetFlags; - BitSet32 pointerIds = touchedWindow->pointerIds; + std::bitset pointerIds = touchedWindow->pointerIds; sp fromWindowHandle = touchedWindow->windowHandle; state->removeWindowByToken(fromToken); @@ -5264,7 +5275,7 @@ bool InputDispatcher::transferTouchFocus(const sp& fromToken, const sp< return false; } // Track the pointer id for drag window and generate the drag state. - const int32_t id = pointerIds.firstMarkedBit(); + const size_t id = firstMarkedBit(pointerIds); mDragState = std::make_unique(toWindowHandle, id); } @@ -5764,7 +5775,7 @@ status_t InputDispatcher::pilferPointersLocked(const sp& token) { } auto [statePtr, windowPtr, displayId] = findTouchStateWindowAndDisplayLocked(token); - if (statePtr == nullptr || windowPtr == nullptr || windowPtr->pointerIds.isEmpty()) { + if (statePtr == nullptr || windowPtr == nullptr || windowPtr->pointerIds.none()) { ALOGW("Attempted to pilfer points from a channel without any on-going pointer streams." " Ignoring."); return BAD_VALUE; @@ -5794,10 +5805,7 @@ status_t InputDispatcher::pilferPointersLocked(const sp& token) { // Prevent the gesture from being sent to any other windows. // This only blocks relevant pointers to be sent to other windows - for (BitSet32 idBits(window.pointerIds); !idBits.isEmpty();) { - uint32_t id = idBits.clearFirstMarkedBit(); - window.pilferedPointerIds.set(id); - } + window.pilferedPointerIds |= window.pointerIds; state.cancelPointersForWindowsExcept(window.pointerIds, token); return OK; @@ -6566,8 +6574,8 @@ void InputDispatcher::slipWallpaperTouch(ftl::Flags targetFl const sp& newWindowHandle, TouchState& state, int32_t pointerId, std::vector& targets) { - BitSet32 pointerIds; - pointerIds.markBit(pointerId); + std::bitset pointerIds; + pointerIds.set(pointerId); const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test( gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER); const bool newHasWallpaper = targetFlags.test(InputTarget::Flags::FOREGROUND) && @@ -6603,7 +6611,8 @@ void InputDispatcher::transferWallpaperTouch(ftl::Flags oldT ftl::Flags newTargetFlags, const sp fromWindowHandle, const sp toWindowHandle, - TouchState& state, const BitSet32& pointerIds) { + TouchState& state, + std::bitset pointerIds) { const bool oldHasWallpaper = oldTargetFlags.test(InputTarget::Flags::FOREGROUND) && fromWindowHandle->getInfo()->inputConfig.test( gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER); diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 81f8de8f95..b94858b6d0 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -550,7 +551,8 @@ private: const std::vector& gestureMonitors) const REQUIRES(mLock); void addWindowTargetLocked(const sp& windowHandle, - ftl::Flags targetFlags, BitSet32 pointerIds, + ftl::Flags targetFlags, + std::bitset pointerIds, std::optional firstDownTimeInTarget, std::vector& inputTargets) const REQUIRES(mLock); void addGlobalMonitoringTargetsLocked(std::vector& inputTargets, int32_t displayId) @@ -636,7 +638,8 @@ private: // Splitting motion events across windows. When splitting motion event for a target, // splitDownTime refers to the time of first 'down' event on that particular target std::unique_ptr splitMotionEvent(const MotionEntry& originalMotionEntry, - BitSet32 pointerIds, nsecs_t splitDownTime); + std::bitset pointerIds, + nsecs_t splitDownTime); // Reset and drop everything the dispatcher is doing. void resetAndDropEverythingLocked(const char* reason) REQUIRES(mLock); @@ -703,7 +706,8 @@ private: ftl::Flags newTargetFlags, const sp fromWindowHandle, const sp toWindowHandle, - TouchState& state, const BitSet32& pointerIds) REQUIRES(mLock); + TouchState& state, std::bitset pointerIds) + REQUIRES(mLock); sp findWallpaperWindowBelow( const sp& windowHandle) const REQUIRES(mLock); diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp index de8bfd5ffe..ad5a7fde07 100644 --- a/services/inputflinger/dispatcher/InputState.cpp +++ b/services/inputflinger/dispatcher/InputState.cpp @@ -374,7 +374,8 @@ std::vector> InputState::synthesizePointerDownEvents } std::vector> InputState::synthesizeCancelationEventsForPointers( - const MotionMemento& memento, const BitSet32 pointerIds, nsecs_t currentTime) { + const MotionMemento& memento, std::bitset pointerIds, + nsecs_t currentTime) { std::vector> events; std::vector canceledPointerIndices; std::vector pointerProperties(MAX_POINTERS); @@ -383,7 +384,7 @@ std::vector> InputState::synthesizeCancelationEvent uint32_t pointerId = uint32_t(memento.pointerProperties[pointerIdx].id); pointerProperties[pointerIdx].copyFrom(memento.pointerProperties[pointerIdx]); pointerCoords[pointerIdx].copyFrom(memento.pointerCoords[pointerIdx]); - if (pointerIds.hasBit(pointerId)) { + if (pointerIds.test(pointerId)) { canceledPointerIndices.push_back(pointerIdx); } } diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h index 6ab48c9273..42d8cc6af3 100644 --- a/services/inputflinger/dispatcher/InputState.h +++ b/services/inputflinger/dispatcher/InputState.h @@ -20,6 +20,7 @@ #include "Entry.h" #include +#include namespace android { namespace inputdispatcher { @@ -128,7 +129,8 @@ private: // Synthesizes pointer cancel events for a particular set of pointers. std::vector> synthesizeCancelationEventsForPointers( - const MotionMemento& memento, const BitSet32 pointerIds, nsecs_t currentTime); + const MotionMemento& memento, std::bitset pointerIds, + nsecs_t currentTime); }; } // namespace inputdispatcher diff --git a/services/inputflinger/dispatcher/InputTarget.cpp b/services/inputflinger/dispatcher/InputTarget.cpp index 2f39480555..fc8b785aa5 100644 --- a/services/inputflinger/dispatcher/InputTarget.cpp +++ b/services/inputflinger/dispatcher/InputTarget.cpp @@ -24,31 +24,34 @@ using android::base::StringPrintf; namespace android::inputdispatcher { -void InputTarget::addPointers(BitSet32 newPointerIds, const ui::Transform& transform) { +void InputTarget::addPointers(std::bitset newPointerIds, + const ui::Transform& transform) { // The pointerIds can be empty, but still a valid InputTarget. This can happen when there is no // valid pointer property from the input event. - if (newPointerIds.isEmpty()) { + if (newPointerIds.none()) { setDefaultPointerTransform(transform); return; } // Ensure that the new set of pointers doesn't overlap with the current set of pointers. - ALOG_ASSERT((pointerIds & newPointerIds) == 0); + LOG_ALWAYS_FATAL_IF((pointerIds & newPointerIds).any()); pointerIds |= newPointerIds; - while (!newPointerIds.isEmpty()) { - int32_t pointerId = newPointerIds.clearFirstMarkedBit(); - pointerTransforms[pointerId] = transform; + for (size_t i = 0; i < newPointerIds.size(); i++) { + if (!newPointerIds.test(i)) { + continue; + } + pointerTransforms[i] = transform; } } void InputTarget::setDefaultPointerTransform(const ui::Transform& transform) { - pointerIds.clear(); + pointerIds.reset(); pointerTransforms[0] = transform; } bool InputTarget::useDefaultPointerTransform() const { - return pointerIds.isEmpty(); + return pointerIds.none(); } const ui::Transform& InputTarget::getDefaultPointerTransform() const { @@ -63,8 +66,8 @@ std::string InputTarget::getPointerInfoString() const { return out; } - for (uint32_t i = pointerIds.firstMarkedBit(); i <= pointerIds.lastMarkedBit(); i++) { - if (!pointerIds.hasBit(i)) { + for (uint32_t i = 0; i < pointerIds.size(); i++) { + if (!pointerIds.test(i)) { continue; } diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h index 61b07feb3a..7b12f81c4e 100644 --- a/services/inputflinger/dispatcher/InputTarget.h +++ b/services/inputflinger/dispatcher/InputTarget.h @@ -21,6 +21,7 @@ #include #include #include +#include namespace android::inputdispatcher { @@ -105,7 +106,7 @@ struct InputTarget { // The subset of pointer ids to include in motion events dispatched to this input target // if FLAG_SPLIT is set. - BitSet32 pointerIds; + std::bitset pointerIds; // Event time for the first motion event (ACTION_DOWN) dispatched to this input target if // FLAG_SPLIT is set. std::optional firstDownTimeInTarget; @@ -113,7 +114,7 @@ struct InputTarget { // Transform per pointerId. ui::Transform pointerTransforms[MAX_POINTERS]; - void addPointers(BitSet32 pointerIds, const ui::Transform& transform); + void addPointers(std::bitset pointerIds, const ui::Transform& transform); void setDefaultPointerTransform(const ui::Transform& transform); /** diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index c257ee540d..acfd0a24a8 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -33,7 +33,7 @@ void TouchState::reset() { void TouchState::removeTouchedPointer(int32_t pointerId) { for (TouchedWindow& touchedWindow : windows) { - touchedWindow.pointerIds.clearBit(pointerId); + touchedWindow.pointerIds.reset(pointerId); touchedWindow.pilferedPointerIds.reset(pointerId); } } @@ -42,7 +42,7 @@ void TouchState::removeTouchedPointerFromWindow( int32_t pointerId, const sp& windowHandle) { for (TouchedWindow& touchedWindow : windows) { if (touchedWindow.windowHandle == windowHandle) { - touchedWindow.pointerIds.clearBit(pointerId); + touchedWindow.pointerIds.reset(pointerId); touchedWindow.pilferedPointerIds.reset(pointerId); return; } @@ -57,12 +57,13 @@ void TouchState::clearHoveringPointers() { void TouchState::clearWindowsWithoutPointers() { std::erase_if(windows, [](const TouchedWindow& w) { - return w.pointerIds.isEmpty() && !w.hasHoveringPointers(); + return w.pointerIds.none() && !w.hasHoveringPointers(); }); } void TouchState::addOrUpdateWindow(const sp& windowHandle, - ftl::Flags targetFlags, BitSet32 pointerIds, + ftl::Flags targetFlags, + std::bitset pointerIds, std::optional firstDownTimeInTarget) { for (TouchedWindow& touchedWindow : windows) { // We do not compare windows by token here because two windows that share the same token @@ -75,7 +76,7 @@ void TouchState::addOrUpdateWindow(const sp& windowHandle, // For cases like hover enter/exit or DISPATCH_AS_OUTSIDE a touch window might not have // downTime set initially. Need to update existing window when an pointer is down for // the window. - touchedWindow.pointerIds.value |= pointerIds.value; + touchedWindow.pointerIds |= pointerIds; if (!touchedWindow.firstDownTimeInTarget.has_value()) { touchedWindow.firstDownTimeInTarget = firstDownTimeInTarget; } @@ -128,15 +129,15 @@ void TouchState::filterNonAsIsTouchWindows() { } } -void TouchState::cancelPointersForWindowsExcept(const BitSet32 pointerIds, +void TouchState::cancelPointersForWindowsExcept(std::bitset pointerIds, const sp& token) { - if (pointerIds.isEmpty()) return; + if (pointerIds.none()) return; std::for_each(windows.begin(), windows.end(), [&pointerIds, &token](TouchedWindow& w) { if (w.windowHandle->getToken() != token) { - w.pointerIds &= BitSet32(~pointerIds.value); + w.pointerIds &= ~pointerIds; } }); - std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.isEmpty(); }); + std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.none(); }); } /** @@ -147,7 +148,7 @@ void TouchState::cancelPointersForWindowsExcept(const BitSet32 pointerIds, */ void TouchState::cancelPointersForNonPilferingWindows() { // First, find all pointers that are being pilfered, across all windows - std::bitset allPilferedPointerIds; + std::bitset allPilferedPointerIds; std::for_each(windows.begin(), windows.end(), [&allPilferedPointerIds](const TouchedWindow& w) { allPilferedPointerIds |= w.pilferedPointerIds; }); @@ -161,7 +162,7 @@ void TouchState::cancelPointersForNonPilferingWindows() { // pilfered pointers will be disjoint across all windows, but there's no reason to cause that // limitation here. std::for_each(windows.begin(), windows.end(), [&allPilferedPointerIds](TouchedWindow& w) { - std::bitset pilferedByOtherWindows = + std::bitset pilferedByOtherWindows = w.pilferedPointerIds ^ allPilferedPointerIds; // TODO(b/211379801) : convert pointerIds to use std::bitset, which would allow us to // replace the loop below with a bitwise operation. Currently, the XOR operation above is @@ -171,11 +172,11 @@ void TouchState::cancelPointersForNonPilferingWindows() { // Pointer is pilfered by other windows, but not by this one! Remove it from here. // We could call 'removeTouchedPointerFromWindow' here, but it's faster to directly // manipulate it. - w.pointerIds.clearBit(i); + w.pointerIds.reset(i); } } }); - std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.isEmpty(); }); + std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.none(); }); } sp TouchState::getFirstForegroundWindowHandle() const { @@ -224,7 +225,7 @@ const TouchedWindow& TouchState::getTouchedWindow(const sp& wi bool TouchState::isDown() const { return std::any_of(windows.begin(), windows.end(), - [](const TouchedWindow& window) { return !window.pointerIds.isEmpty(); }); + [](const TouchedWindow& window) { return window.pointerIds.any(); }); } std::set> TouchState::getWindowsWithHoveringPointer(int32_t hoveringDeviceId, @@ -243,7 +244,7 @@ void TouchState::removeHoveringPointer(int32_t hoveringDeviceId, int32_t hoverin window.removeHoveringPointer(hoveringDeviceId, hoveringPointerId); } std::erase_if(windows, [](const TouchedWindow& w) { - return w.pointerIds.isEmpty() && !w.hasHoveringPointers(); + return w.pointerIds.none() && !w.hasHoveringPointers(); }); } diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index f1409d6d42..6e965d8c96 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include "TouchedWindow.h" @@ -46,7 +47,8 @@ struct TouchState { void removeTouchedPointerFromWindow(int32_t pointerId, const sp& windowHandle); void addOrUpdateWindow(const sp& windowHandle, - ftl::Flags targetFlags, BitSet32 pointerIds, + ftl::Flags targetFlags, + std::bitset pointerIds, std::optional firstDownTimeInTarget = std::nullopt); void addHoveringPointerToWindow(const sp& windowHandle, int32_t deviceId, int32_t hoveringPointerId); @@ -56,7 +58,8 @@ struct TouchState { void filterNonAsIsTouchWindows(); // Cancel pointers for current set of windows except the window with particular binder token. - void cancelPointersForWindowsExcept(const BitSet32 pointerIds, const sp& token); + void cancelPointersForWindowsExcept(std::bitset pointerIds, + const sp& token); // Cancel pointers for current set of non-pilfering windows i.e. windows with isPilferingWindow // set to false. void cancelPointersForNonPilferingWindows(); diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp index 99e1c86f38..92f62b5932 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.cpp +++ b/services/inputflinger/dispatcher/TouchedWindow.cpp @@ -66,9 +66,9 @@ std::string TouchedWindow::dump() const { std::string out; std::string hoveringPointers = dumpMap(mHoveringPointerIdsByDevice, constToString, bitsetToString); - out += StringPrintf("name='%s', pointerIds=0x%0x, targetFlags=%s, firstDownTimeInTarget=%s, " + out += StringPrintf("name='%s', pointerIds=%s, targetFlags=%s, firstDownTimeInTarget=%s, " "mHoveringPointerIdsByDevice=%s, pilferedPointerIds=%s\n", - windowHandle->getName().c_str(), pointerIds.value, + windowHandle->getName().c_str(), bitsetToString(pointerIds).c_str(), targetFlags.string().c_str(), toString(firstDownTimeInTarget).c_str(), hoveringPointers.c_str(), bitsetToString(pilferedPointerIds).c_str()); return out; diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h index 4ec33ac3ef..e59e78106b 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -30,9 +30,9 @@ namespace inputdispatcher { struct TouchedWindow { sp windowHandle; ftl::Flags targetFlags; - BitSet32 pointerIds; + std::bitset pointerIds; // The pointer ids of the pointers that this window is currently pilfering - std::bitset pilferedPointerIds; + std::bitset pilferedPointerIds; // Time at which the first action down occurred on this window. // NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE scenario. std::optional firstDownTimeInTarget; @@ -47,7 +47,7 @@ struct TouchedWindow { std::string dump() const; private: - std::map> mHoveringPointerIdsByDevice; + std::map> mHoveringPointerIdsByDevice; }; } // namespace inputdispatcher -- GitLab From c9576892d1e1137d392548e00e0a63c18a176d8e Mon Sep 17 00:00:00 2001 From: Cody Northrop Date: Fri, 3 Feb 2023 14:00:16 -0700 Subject: [PATCH 0812/1310] Reland "EGL: Refactor multifile blobcache" This reverts commit b81edf95e53784d8153c7c4999ccbc1ee653be63 Previous revert was due to selinux denials causing test failures. Temporarily addressed in aosp/2420100 This also incorporates additional post-submit feedback on ag/21091488 Test: pubg_mobile_launch ANGLE trace Test: /data/nativetest64/EGL_test/EGL_test Test: /data/nativetest64/libEGL_test/libEGL_test Bug: b/266725576 Change-Id: I5631301c69605f25cbca52ca8ef096294c2e1bcf --- opengl/libs/Android.bp | 7 +- opengl/libs/EGL/MultifileBlobCache.cpp | 670 ++++++++++++++++++++ opengl/libs/EGL/MultifileBlobCache.h | 167 +++++ opengl/libs/EGL/MultifileBlobCache_test.cpp | 200 ++++++ opengl/libs/EGL/egl_cache.cpp | 105 +-- opengl/libs/EGL/egl_cache.h | 21 +- opengl/libs/EGL/egl_cache_multifile.cpp | 343 ---------- opengl/libs/EGL/egl_cache_multifile.h | 36 -- opengl/tests/EGLTest/egl_cache_test.cpp | 56 +- 9 files changed, 1159 insertions(+), 446 deletions(-) create mode 100644 opengl/libs/EGL/MultifileBlobCache.cpp create mode 100644 opengl/libs/EGL/MultifileBlobCache.h create mode 100644 opengl/libs/EGL/MultifileBlobCache_test.cpp delete mode 100644 opengl/libs/EGL/egl_cache_multifile.cpp delete mode 100644 opengl/libs/EGL/egl_cache_multifile.h diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp index 750338bd84..49e1cbafb4 100644 --- a/opengl/libs/Android.bp +++ b/opengl/libs/Android.bp @@ -144,6 +144,7 @@ cc_library_static { srcs: [ "EGL/BlobCache.cpp", "EGL/FileBlobCache.cpp", + "EGL/MultifileBlobCache.cpp", ], export_include_dirs: ["EGL"], } @@ -160,7 +161,6 @@ cc_library_shared { srcs: [ "EGL/egl_tls.cpp", "EGL/egl_cache.cpp", - "EGL/egl_cache_multifile.cpp", "EGL/egl_display.cpp", "EGL/egl_object.cpp", "EGL/egl_layers.cpp", @@ -205,6 +205,11 @@ cc_test { srcs: [ "EGL/BlobCache.cpp", "EGL/BlobCache_test.cpp", + "EGL/MultifileBlobCache.cpp", + "EGL/MultifileBlobCache_test.cpp", + ], + shared_libs: [ + "libutils", ], } diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp new file mode 100644 index 0000000000..304f9072e4 --- /dev/null +++ b/opengl/libs/EGL/MultifileBlobCache.cpp @@ -0,0 +1,670 @@ +/* + ** Copyright 2022, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +// #define LOG_NDEBUG 0 + +#include "MultifileBlobCache.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +using namespace std::literals; + +namespace { + +// Open the file and determine the size of the value it contains +size_t getValueSizeFromFile(int fd, const std::string& entryPath) { + // Read the beginning of the file to get header + android::MultifileHeader header; + size_t result = read(fd, static_cast(&header), sizeof(android::MultifileHeader)); + if (result != sizeof(android::MultifileHeader)) { + ALOGE("Error reading MultifileHeader from cache entry (%s): %s", entryPath.c_str(), + std::strerror(errno)); + return 0; + } + + return header.valueSize; +} + +// Helper function to close entries or free them +void freeHotCacheEntry(android::MultifileHotCache& entry) { + if (entry.entryFd != -1) { + // If we have an fd, then this entry was added to hot cache via INIT or GET + // We need to unmap and close the entry + munmap(entry.entryBuffer, entry.entrySize); + close(entry.entryFd); + } else { + // Otherwise, this was added to hot cache during SET, so it was never mapped + // and fd was only on the deferred thread. + delete[] entry.entryBuffer; + } +} + +} // namespace + +namespace android { + +MultifileBlobCache::MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize, + const std::string& baseDir) + : mInitialized(false), + mMaxTotalSize(maxTotalSize), + mTotalCacheSize(0), + mHotCacheLimit(maxHotCacheSize), + mHotCacheSize(0), + mWorkerThreadIdle(true) { + if (baseDir.empty()) { + return; + } + + // Establish the name of our multifile directory + mMultifileDirName = baseDir + ".multifile"; + + // Set a limit for max key and value, ensuring at least one entry can always fit in hot cache + mMaxKeySize = mHotCacheLimit / 4; + mMaxValueSize = mHotCacheLimit / 2; + + // Initialize our cache with the contents of the directory + mTotalCacheSize = 0; + + // See if the dir exists, and initialize using its contents + struct stat st; + if (stat(mMultifileDirName.c_str(), &st) == 0) { + // Read all the files and gather details, then preload their contents + DIR* dir; + struct dirent* entry; + if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) { + while ((entry = readdir(dir)) != nullptr) { + if (entry->d_name == "."s || entry->d_name == ".."s) { + continue; + } + + std::string entryName = entry->d_name; + std::string fullPath = mMultifileDirName + "/" + entryName; + + // The filename is the same as the entryHash + uint32_t entryHash = static_cast(strtoul(entry->d_name, nullptr, 10)); + + // Look up the details of the file + struct stat st; + if (stat(fullPath.c_str(), &st) != 0) { + ALOGE("Failed to stat %s", fullPath.c_str()); + return; + } + + // Open the file so we can read its header + int fd = open(fullPath.c_str(), O_RDONLY); + if (fd == -1) { + ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(), + std::strerror(errno)); + return; + } + + // Look up the details we track about each file + size_t valueSize = getValueSizeFromFile(fd, fullPath); + + // If the cache entry is damaged or no good, remove it + // TODO: Perform any other checks + if (valueSize <= 0 || st.st_size <= 0 || st.st_atime <= 0) { + if (remove(fullPath.c_str()) != 0) { + ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno)); + } + continue; + } + + // Note: Converting from off_t (signed) to size_t (unsigned) + size_t fileSize = static_cast(st.st_size); + time_t accessTime = st.st_atime; + + // Track details for rapid lookup later + trackEntry(entryHash, valueSize, fileSize, accessTime); + + // Track the total size + increaseTotalCacheSize(fileSize); + + // Preload the entry for fast retrieval + if ((mHotCacheSize + fileSize) < mHotCacheLimit) { + // Memory map the file + uint8_t* mappedEntry = reinterpret_cast( + mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0)); + if (mappedEntry == MAP_FAILED) { + ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); + } + + ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for " + "entryHash %u", + fd, mappedEntry, entryHash); + + // Track the details of the preload so they can be retrieved later + if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) { + ALOGE("INIT Failed to add %u to hot cache", entryHash); + munmap(mappedEntry, fileSize); + close(fd); + return; + } + } else { + close(fd); + } + } + closedir(dir); + } else { + ALOGE("Unable to open filename: %s", mMultifileDirName.c_str()); + } + } else { + // If the multifile directory does not exist, create it and start from scratch + if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) { + ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno); + } + } + + mTaskThread = std::thread(&MultifileBlobCache::processTasks, this); + + mInitialized = true; +} + +MultifileBlobCache::~MultifileBlobCache() { + // Inform the worker thread we're done + ALOGV("DESCTRUCTOR: Shutting down worker thread"); + DeferredTask task(TaskCommand::Exit); + queueTask(std::move(task)); + + // Wait for it to complete + ALOGV("DESCTRUCTOR: Waiting for worker thread to complete"); + waitForWorkComplete(); + mTaskThread.join(); +} + +// Set will add the entry to hot cache and start a deferred process to write it to disk +void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const void* value, + EGLsizeiANDROID valueSize) { + if (!mInitialized) { + return; + } + + // Ensure key and value are under their limits + if (keySize > mMaxKeySize || valueSize > mMaxValueSize) { + ALOGV("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize, + valueSize, mMaxValueSize); + return; + } + + // Generate a hash of the key and use it to track this entry + uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast(key), keySize); + + size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize; + + // If we're going to be over the cache limit, kick off a trim to clear space + if (getTotalSize() + fileSize > mMaxTotalSize) { + ALOGV("SET: Cache is full, calling trimCache to clear space"); + trimCache(mMaxTotalSize); + } + + ALOGV("SET: Add %u to cache", entryHash); + + uint8_t* buffer = new uint8_t[fileSize]; + + // Write the key and value after the header + android::MultifileHeader header = {keySize, valueSize}; + memcpy(static_cast(buffer), static_cast(&header), + sizeof(android::MultifileHeader)); + memcpy(static_cast(buffer + sizeof(MultifileHeader)), static_cast(key), + keySize); + memcpy(static_cast(buffer + sizeof(MultifileHeader) + keySize), + static_cast(value), valueSize); + + std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash); + + // Track the size and access time for quick recall + trackEntry(entryHash, valueSize, fileSize, time(0)); + + // Update the overall cache size + increaseTotalCacheSize(fileSize); + + // Keep the entry in hot cache for quick retrieval + ALOGV("SET: Adding %u to hot cache.", entryHash); + + // Sending -1 as the fd indicates we don't have an fd for this + if (!addToHotCache(entryHash, -1, buffer, fileSize)) { + ALOGE("GET: Failed to add %u to hot cache", entryHash); + return; + } + + // Track that we're creating a pending write for this entry + // Include the buffer to handle the case when multiple writes are pending for an entry + mDeferredWrites.insert(std::make_pair(entryHash, buffer)); + + // Create deferred task to write to storage + ALOGV("SET: Adding task to queue."); + DeferredTask task(TaskCommand::WriteToDisk); + task.initWriteToDisk(entryHash, fullPath, buffer, fileSize); + queueTask(std::move(task)); +} + +// Get will check the hot cache, then load it from disk if needed +EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize, void* value, + EGLsizeiANDROID valueSize) { + if (!mInitialized) { + return 0; + } + + // Ensure key and value are under their limits + if (keySize > mMaxKeySize || valueSize > mMaxValueSize) { + ALOGV("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize, + valueSize, mMaxValueSize); + return 0; + } + + // Generate a hash of the key and use it to track this entry + uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast(key), keySize); + + // See if we have this file + if (!contains(entryHash)) { + ALOGV("GET: Cache MISS - cache does not contain entry: %u", entryHash); + return 0; + } + + // Look up the data for this entry + MultifileEntryStats entryStats = getEntryStats(entryHash); + + size_t cachedValueSize = entryStats.valueSize; + if (cachedValueSize > valueSize) { + ALOGV("GET: Cache MISS - valueSize not large enough (%lu) for entry %u, returning required" + "size (%zu)", + valueSize, entryHash, cachedValueSize); + return cachedValueSize; + } + + // We have the file and have enough room to write it out, return the entry + ALOGV("GET: Cache HIT - cache contains entry: %u", entryHash); + + // Look up the size of the file + size_t fileSize = entryStats.fileSize; + if (keySize > fileSize) { + ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified " + "file", + keySize, fileSize); + return 0; + } + + std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash); + + // Open the hashed filename path + uint8_t* cacheEntry = 0; + + // Check hot cache + if (mHotCache.find(entryHash) != mHotCache.end()) { + ALOGV("GET: HotCache HIT for entry %u", entryHash); + cacheEntry = mHotCache[entryHash].entryBuffer; + } else { + ALOGV("GET: HotCache MISS for entry: %u", entryHash); + + if (mDeferredWrites.find(entryHash) != mDeferredWrites.end()) { + // Wait for writes to complete if there is an outstanding write for this entry + ALOGV("GET: Waiting for write to complete for %u", entryHash); + waitForWorkComplete(); + } + + // Open the entry file + int fd = open(fullPath.c_str(), O_RDONLY); + if (fd == -1) { + ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(), + std::strerror(errno)); + return 0; + } + + // Memory map the file + cacheEntry = + reinterpret_cast(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0)); + if (cacheEntry == MAP_FAILED) { + ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); + close(fd); + return 0; + } + + ALOGV("GET: Adding %u to hot cache", entryHash); + if (!addToHotCache(entryHash, fd, cacheEntry, fileSize)) { + ALOGE("GET: Failed to add %u to hot cache", entryHash); + return 0; + } + + cacheEntry = mHotCache[entryHash].entryBuffer; + } + + // Ensure the header matches + MultifileHeader* header = reinterpret_cast(cacheEntry); + if (header->keySize != keySize || header->valueSize != valueSize) { + ALOGW("Mismatch on keySize(%ld vs. cached %ld) or valueSize(%ld vs. cached %ld) compared " + "to cache header values for fullPath: %s", + keySize, header->keySize, valueSize, header->valueSize, fullPath.c_str()); + removeFromHotCache(entryHash); + return 0; + } + + // Compare the incoming key with our stored version (the beginning of the entry) + uint8_t* cachedKey = cacheEntry + sizeof(MultifileHeader); + int compare = memcmp(cachedKey, key, keySize); + if (compare != 0) { + ALOGW("Cached key and new key do not match! This is a hash collision or modified file"); + removeFromHotCache(entryHash); + return 0; + } + + // Remaining entry following the key is the value + uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader)); + memcpy(value, cachedValue, cachedValueSize); + + return cachedValueSize; +} + +void MultifileBlobCache::finish() { + // Wait for all deferred writes to complete + ALOGV("FINISH: Waiting for work to complete."); + waitForWorkComplete(); + + // Close all entries in the hot cache + for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) { + uint32_t entryHash = hotCacheIter->first; + MultifileHotCache entry = hotCacheIter->second; + + ALOGV("FINISH: Closing hot cache entry for %u", entryHash); + freeHotCacheEntry(entry); + + mHotCache.erase(hotCacheIter++); + } +} + +void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize, + time_t accessTime) { + mEntries.insert(entryHash); + mEntryStats[entryHash] = {valueSize, fileSize, accessTime}; +} + +bool MultifileBlobCache::contains(uint32_t hashEntry) const { + return mEntries.find(hashEntry) != mEntries.end(); +} + +MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) { + return mEntryStats[entryHash]; +} + +void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) { + mTotalCacheSize += fileSize; +} + +void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) { + mTotalCacheSize -= fileSize; +} + +bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer, + size_t newEntrySize) { + ALOGV("HOTCACHE(ADD): Adding %u to hot cache", newEntryHash); + + // Clear space if we need to + if ((mHotCacheSize + newEntrySize) > mHotCacheLimit) { + ALOGV("HOTCACHE(ADD): mHotCacheSize (%zu) + newEntrySize (%zu) is to big for " + "mHotCacheLimit " + "(%zu), freeing up space for %u", + mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash); + + // Wait for all the files to complete writing so our hot cache is accurate + waitForWorkComplete(); + + // Free up old entries until under the limit + for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) { + uint32_t oldEntryHash = hotCacheIter->first; + MultifileHotCache oldEntry = hotCacheIter->second; + + // Move our iterator before deleting the entry + hotCacheIter++; + if (!removeFromHotCache(oldEntryHash)) { + ALOGE("HOTCACHE(ADD): Unable to remove entry %u", oldEntryHash); + return false; + } + + // Clear at least half the hot cache + if ((mHotCacheSize + newEntrySize) <= mHotCacheLimit / 2) { + ALOGV("HOTCACHE(ADD): Freed enough space for %zu", mHotCacheSize); + break; + } + } + } + + // Track it + mHotCache[newEntryHash] = {newFd, newEntryBuffer, newEntrySize}; + mHotCacheSize += newEntrySize; + + ALOGV("HOTCACHE(ADD): New hot cache size: %zu", mHotCacheSize); + + return true; +} + +bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) { + if (mHotCache.find(entryHash) != mHotCache.end()) { + ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash); + + // Wait for all the files to complete writing so our hot cache is accurate + waitForWorkComplete(); + + ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash); + MultifileHotCache entry = mHotCache[entryHash]; + freeHotCacheEntry(entry); + + // Delete the entry from our tracking + mHotCacheSize -= entry.entrySize; + mHotCache.erase(entryHash); + + return true; + } + + return false; +} + +bool MultifileBlobCache::applyLRU(size_t cacheLimit) { + // Walk through our map of sorted last access times and remove files until under the limit + for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) { + uint32_t entryHash = cacheEntryIter->first; + + ALOGV("LRU: Removing entryHash %u", entryHash); + + // Track the overall size + MultifileEntryStats entryStats = getEntryStats(entryHash); + decreaseTotalCacheSize(entryStats.fileSize); + + // Remove it from hot cache if present + removeFromHotCache(entryHash); + + // Remove it from the system + std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash); + if (remove(entryPath.c_str()) != 0) { + ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno)); + return false; + } + + // Increment the iterator before clearing the entry + cacheEntryIter++; + + // Delete the entry from our tracking + size_t count = mEntryStats.erase(entryHash); + if (count != 1) { + ALOGE("LRU: Failed to remove entryHash (%u) from mEntryStats", entryHash); + return false; + } + + // See if it has been reduced enough + size_t totalCacheSize = getTotalSize(); + if (totalCacheSize <= cacheLimit) { + // Success + ALOGV("LRU: Reduced cache to %zu", totalCacheSize); + return true; + } + } + + ALOGV("LRU: Cache is emptry"); + return false; +} + +// When removing files, what fraction of the overall limit should be reached when removing files +// A divisor of two will decrease the cache to 50%, four to 25% and so on +constexpr uint32_t kCacheLimitDivisor = 2; + +// Calculate the cache size and remove old entries until under the limit +void MultifileBlobCache::trimCache(size_t cacheByteLimit) { + // Start with the value provided by egl_cache + size_t limit = cacheByteLimit; + + // Wait for all deferred writes to complete + waitForWorkComplete(); + + size_t size = getTotalSize(); + + // If size is larger than the threshold, remove files using LRU + if (size > limit) { + ALOGV("TRIM: Multifile cache size is larger than %zu, removing old entries", + cacheByteLimit); + if (!applyLRU(limit / kCacheLimitDivisor)) { + ALOGE("Error when clearing multifile shader cache"); + return; + } + } +} + +// This function performs a task. It only knows how to write files to disk, +// but it could be expanded if needed. +void MultifileBlobCache::processTask(DeferredTask& task) { + switch (task.getTaskCommand()) { + case TaskCommand::Exit: { + ALOGV("DEFERRED: Shutting down"); + return; + } + case TaskCommand::WriteToDisk: { + uint32_t entryHash = task.getEntryHash(); + std::string& fullPath = task.getFullPath(); + uint8_t* buffer = task.getBuffer(); + size_t bufferSize = task.getBufferSize(); + + // Create the file or reset it if already present, read+write for user only + int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd == -1) { + ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s", + fullPath.c_str(), std::strerror(errno)); + return; + } + + ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str()); + + ssize_t result = write(fd, buffer, bufferSize); + if (result != bufferSize) { + ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(), + std::strerror(errno)); + return; + } + + ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str()); + close(fd); + + // Erase the entry from mDeferredWrites + // Since there could be multiple outstanding writes for an entry, find the matching one + typedef std::multimap::iterator entryIter; + std::pair iterPair = mDeferredWrites.equal_range(entryHash); + for (entryIter it = iterPair.first; it != iterPair.second; ++it) { + if (it->second == buffer) { + ALOGV("DEFERRED: Marking write complete for %u at %p", it->first, it->second); + mDeferredWrites.erase(it); + break; + } + } + + return; + } + default: { + ALOGE("DEFERRED: Unhandled task type"); + return; + } + } +} + +// This function will wait until tasks arrive, then execute them +// If the exit command is submitted, the loop will terminate +void MultifileBlobCache::processTasksImpl(bool* exitThread) { + while (true) { + std::unique_lock lock(mWorkerMutex); + if (mTasks.empty()) { + ALOGV("WORKER: No tasks available, waiting"); + mWorkerThreadIdle = true; + mWorkerIdleCondition.notify_all(); + // Only wake if notified and command queue is not empty + mWorkAvailableCondition.wait(lock, [this] { return !mTasks.empty(); }); + } + + ALOGV("WORKER: Task available, waking up."); + mWorkerThreadIdle = false; + DeferredTask task = std::move(mTasks.front()); + mTasks.pop(); + + if (task.getTaskCommand() == TaskCommand::Exit) { + ALOGV("WORKER: Exiting work loop."); + *exitThread = true; + mWorkerThreadIdle = true; + mWorkerIdleCondition.notify_one(); + return; + } + + lock.unlock(); + processTask(task); + } +} + +// Process tasks until the exit task is submitted +void MultifileBlobCache::processTasks() { + while (true) { + bool exitThread = false; + processTasksImpl(&exitThread); + if (exitThread) { + break; + } + } +} + +// Add a task to the queue to be processed by the worker thread +void MultifileBlobCache::queueTask(DeferredTask&& task) { + std::lock_guard queueLock(mWorkerMutex); + mTasks.emplace(std::move(task)); + mWorkAvailableCondition.notify_one(); +} + +// Wait until all tasks have been completed +void MultifileBlobCache::waitForWorkComplete() { + std::unique_lock lock(mWorkerMutex); + mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); }); +} + +}; // namespace android \ No newline at end of file diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h new file mode 100644 index 0000000000..c11dfb72da --- /dev/null +++ b/opengl/libs/EGL/MultifileBlobCache.h @@ -0,0 +1,167 @@ +/* + ** Copyright 2022, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +#ifndef ANDROID_MULTIFILE_BLOB_CACHE_H +#define ANDROID_MULTIFILE_BLOB_CACHE_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace android { + +struct MultifileHeader { + EGLsizeiANDROID keySize; + EGLsizeiANDROID valueSize; +}; + +struct MultifileEntryStats { + EGLsizeiANDROID valueSize; + size_t fileSize; + time_t accessTime; +}; + +struct MultifileHotCache { + int entryFd; + uint8_t* entryBuffer; + size_t entrySize; +}; + +enum class TaskCommand { + Invalid = 0, + WriteToDisk, + Exit, +}; + +class DeferredTask { +public: + DeferredTask(TaskCommand command) + : mCommand(command), mEntryHash(0), mBuffer(nullptr), mBufferSize(0) {} + + TaskCommand getTaskCommand() { return mCommand; } + + void initWriteToDisk(uint32_t entryHash, std::string fullPath, uint8_t* buffer, + size_t bufferSize) { + mCommand = TaskCommand::WriteToDisk; + mEntryHash = entryHash; + mFullPath = std::move(fullPath); + mBuffer = buffer; + mBufferSize = bufferSize; + } + + uint32_t getEntryHash() { return mEntryHash; } + std::string& getFullPath() { return mFullPath; } + uint8_t* getBuffer() { return mBuffer; } + size_t getBufferSize() { return mBufferSize; }; + +private: + TaskCommand mCommand; + + // Parameters for WriteToDisk + uint32_t mEntryHash; + std::string mFullPath; + uint8_t* mBuffer; + size_t mBufferSize; +}; + +class MultifileBlobCache { +public: + MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize, const std::string& baseDir); + ~MultifileBlobCache(); + + void set(const void* key, EGLsizeiANDROID keySize, const void* value, + EGLsizeiANDROID valueSize); + EGLsizeiANDROID get(const void* key, EGLsizeiANDROID keySize, void* value, + EGLsizeiANDROID valueSize); + + void finish(); + + size_t getTotalSize() const { return mTotalCacheSize; } + void trimCache(size_t cacheByteLimit); + +private: + void trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize, + time_t accessTime); + bool contains(uint32_t entryHash) const; + bool removeEntry(uint32_t entryHash); + MultifileEntryStats getEntryStats(uint32_t entryHash); + + size_t getFileSize(uint32_t entryHash); + size_t getValueSize(uint32_t entryHash); + + void increaseTotalCacheSize(size_t fileSize); + void decreaseTotalCacheSize(size_t fileSize); + + bool addToHotCache(uint32_t entryHash, int fd, uint8_t* entryBufer, size_t entrySize); + bool removeFromHotCache(uint32_t entryHash); + + bool applyLRU(size_t cacheLimit); + + bool mInitialized; + std::string mMultifileDirName; + + std::unordered_set mEntries; + std::unordered_map mEntryStats; + std::unordered_map mHotCache; + + size_t mMaxKeySize; + size_t mMaxValueSize; + size_t mMaxTotalSize; + size_t mTotalCacheSize; + size_t mHotCacheLimit; + size_t mHotCacheEntryLimit; + size_t mHotCacheSize; + + // Below are the components used for deferred writes + + // Track whether we have pending writes for an entry + std::multimap mDeferredWrites; + + // Functions to work through tasks in the queue + void processTasks(); + void processTasksImpl(bool* exitThread); + void processTask(DeferredTask& task); + + // Used by main thread to create work for the worker thread + void queueTask(DeferredTask&& task); + + // Used by main thread to wait for worker thread to complete all outstanding work. + void waitForWorkComplete(); + + std::thread mTaskThread; + std::queue mTasks; + std::mutex mWorkerMutex; + + // This condition will block the worker thread until a task is queued + std::condition_variable mWorkAvailableCondition; + + // This condition will block the main thread while the worker thread still has tasks + std::condition_variable mWorkerIdleCondition; + + // This bool will track whether all tasks have been completed + bool mWorkerThreadIdle; +}; + +}; // namespace android + +#endif // ANDROID_MULTIFILE_BLOB_CACHE_H diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp new file mode 100644 index 0000000000..1a55a4fcdd --- /dev/null +++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp @@ -0,0 +1,200 @@ +/* + ** Copyright 2023, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +#include "MultifileBlobCache.h" + +#include +#include +#include +#include + +#include + +namespace android { + +template +using sp = std::shared_ptr; + +constexpr size_t kMaxTotalSize = 32 * 1024; +constexpr size_t kMaxPreloadSize = 8 * 1024; + +constexpr size_t kMaxKeySize = kMaxPreloadSize / 4; +constexpr size_t kMaxValueSize = kMaxPreloadSize / 2; + +class MultifileBlobCacheTest : public ::testing::Test { +protected: + virtual void SetUp() { + mTempFile.reset(new TemporaryFile()); + mMBC.reset(new MultifileBlobCache(kMaxTotalSize, kMaxPreloadSize, &mTempFile->path[0])); + } + + virtual void TearDown() { mMBC.reset(); } + + std::unique_ptr mTempFile; + std::unique_ptr mMBC; +}; + +TEST_F(MultifileBlobCacheTest, CacheSingleValueSucceeds) { + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, CacheTwoValuesSucceeds) { + unsigned char buf[2] = {0xee, 0xee}; + mMBC->set("ab", 2, "cd", 2); + mMBC->set("ef", 2, "gh", 2); + ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); + ASSERT_EQ('c', buf[0]); + ASSERT_EQ('d', buf[1]); + ASSERT_EQ(size_t(2), mMBC->get("ef", 2, buf, 2)); + ASSERT_EQ('g', buf[0]); + ASSERT_EQ('h', buf[1]); +} + +TEST_F(MultifileBlobCacheTest, GetSetTwiceSucceeds) { + unsigned char buf[2] = {0xee, 0xee}; + mMBC->set("ab", 2, "cd", 2); + ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); + ASSERT_EQ('c', buf[0]); + ASSERT_EQ('d', buf[1]); + // Use the same key, but different value + mMBC->set("ab", 2, "ef", 2); + ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); +} + +TEST_F(MultifileBlobCacheTest, GetOnlyWritesInsideBounds) { + unsigned char buf[6] = {0xee, 0xee, 0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf + 1, 4)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ('e', buf[1]); + ASSERT_EQ('f', buf[2]); + ASSERT_EQ('g', buf[3]); + ASSERT_EQ('h', buf[4]); + ASSERT_EQ(0xee, buf[5]); +} + +TEST_F(MultifileBlobCacheTest, GetOnlyWritesIfBufferIsLargeEnough) { + unsigned char buf[3] = {0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 3)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ(0xee, buf[1]); + ASSERT_EQ(0xee, buf[2]); +} + +TEST_F(MultifileBlobCacheTest, GetDoesntAccessNullBuffer) { + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, nullptr, 0)); +} + +TEST_F(MultifileBlobCacheTest, MultipleSetsCacheLatestValue) { + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + mMBC->set("abcd", 4, "ijkl", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('i', buf[0]); + ASSERT_EQ('j', buf[1]); + ASSERT_EQ('k', buf[2]); + ASSERT_EQ('l', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, SecondSetKeepsFirstValueIfTooLarge) { + unsigned char buf[kMaxValueSize + 1] = {0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + mMBC->set("abcd", 4, buf, kMaxValueSize + 1); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, DoesntCacheIfKeyIsTooBig) { + char key[kMaxKeySize + 1]; + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + for (int i = 0; i < kMaxKeySize + 1; i++) { + key[i] = 'a'; + } + mMBC->set(key, kMaxKeySize + 1, "bbbb", 4); + ASSERT_EQ(size_t(0), mMBC->get(key, kMaxKeySize + 1, buf, 4)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ(0xee, buf[1]); + ASSERT_EQ(0xee, buf[2]); + ASSERT_EQ(0xee, buf[3]); +} + +TEST_F(MultifileBlobCacheTest, DoesntCacheIfValueIsTooBig) { + char buf[kMaxValueSize + 1]; + for (int i = 0; i < kMaxValueSize + 1; i++) { + buf[i] = 'b'; + } + mMBC->set("abcd", 4, buf, kMaxValueSize + 1); + for (int i = 0; i < kMaxValueSize + 1; i++) { + buf[i] = 0xee; + } + ASSERT_EQ(size_t(0), mMBC->get("abcd", 4, buf, kMaxValueSize + 1)); + for (int i = 0; i < kMaxValueSize + 1; i++) { + SCOPED_TRACE(i); + ASSERT_EQ(0xee, buf[i]); + } +} + +TEST_F(MultifileBlobCacheTest, CacheMaxKeySizeSucceeds) { + char key[kMaxKeySize]; + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + for (int i = 0; i < kMaxKeySize; i++) { + key[i] = 'a'; + } + mMBC->set(key, kMaxKeySize, "wxyz", 4); + ASSERT_EQ(size_t(4), mMBC->get(key, kMaxKeySize, buf, 4)); + ASSERT_EQ('w', buf[0]); + ASSERT_EQ('x', buf[1]); + ASSERT_EQ('y', buf[2]); + ASSERT_EQ('z', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, CacheMaxValueSizeSucceeds) { + char buf[kMaxValueSize]; + for (int i = 0; i < kMaxValueSize; i++) { + buf[i] = 'b'; + } + mMBC->set("abcd", 4, buf, kMaxValueSize); + for (int i = 0; i < kMaxValueSize; i++) { + buf[i] = 0xee; + } + mMBC->get("abcd", 4, buf, kMaxValueSize); + for (int i = 0; i < kMaxValueSize; i++) { + SCOPED_TRACE(i); + ASSERT_EQ('b', buf[i]); + } +} + +TEST_F(MultifileBlobCacheTest, CacheMinKeyAndValueSizeSucceeds) { + unsigned char buf[1] = {0xee}; + mMBC->set("x", 1, "y", 1); + ASSERT_EQ(size_t(1), mMBC->get("x", 1, buf, 1)); + ASSERT_EQ('y', buf[0]); +} + +} // namespace android diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp index 1e8a34863d..b00ee33374 100644 --- a/opengl/libs/EGL/egl_cache.cpp +++ b/opengl/libs/EGL/egl_cache.cpp @@ -14,6 +14,8 @@ ** limitations under the License. */ +// #define LOG_NDEBUG 0 + #include "egl_cache.h" #include @@ -25,22 +27,19 @@ #include #include "../egl_impl.h" -#include "egl_cache_multifile.h" #include "egl_display.h" // Monolithic cache size limits. -static const size_t maxKeySize = 12 * 1024; -static const size_t maxValueSize = 64 * 1024; -static const size_t maxTotalSize = 32 * 1024 * 1024; +static const size_t kMaxMonolithicKeySize = 12 * 1024; +static const size_t kMaxMonolithicValueSize = 64 * 1024; +static const size_t kMaxMonolithicTotalSize = 2 * 1024 * 1024; // The time in seconds to wait before saving newly inserted monolithic cache entries. -static const unsigned int deferredSaveDelay = 4; - -// Multifile cache size limit -constexpr size_t kMultifileCacheByteLimit = 64 * 1024 * 1024; +static const unsigned int kDeferredMonolithicSaveDelay = 4; -// Delay before cleaning up multifile cache entries -static const unsigned int deferredMultifileCleanupDelaySeconds = 1; +// Multifile cache size limits +constexpr uint32_t kMultifileHotCacheLimit = 8 * 1024 * 1024; +constexpr uint32_t kMultifileCacheByteLimit = 32 * 1024 * 1024; namespace android { @@ -68,10 +67,7 @@ static EGLsizeiANDROID getBlob(const void* key, EGLsizeiANDROID keySize, void* v // egl_cache_t definition // egl_cache_t::egl_cache_t() - : mInitialized(false), - mMultifileMode(false), - mCacheByteLimit(maxTotalSize), - mMultifileCleanupPending(false) {} + : mInitialized(false), mMultifileMode(false), mCacheByteLimit(kMaxMonolithicTotalSize) {} egl_cache_t::~egl_cache_t() {} @@ -85,7 +81,7 @@ void egl_cache_t::initialize(egl_display_t* display) { std::lock_guard lock(mMutex); egl_connection_t* const cnx = &gEGLImpl; - if (cnx->dso && cnx->major >= 0 && cnx->minor >= 0) { + if (display && cnx->dso && cnx->major >= 0 && cnx->minor >= 0) { const char* exts = display->disp.queryString.extensions; size_t bcExtLen = strlen(BC_EXT_STR); size_t extsLen = strlen(exts); @@ -114,14 +110,36 @@ void egl_cache_t::initialize(egl_display_t* display) { } } - // Allow forcing monolithic cache for debug purposes - if (base::GetProperty("debug.egl.blobcache.multifilemode", "") == "false") { - ALOGD("Forcing monolithic cache due to debug.egl.blobcache.multifilemode == \"false\""); + // Check the device config to decide whether multifile should be used + if (base::GetBoolProperty("ro.egl.blobcache.multifile", false)) { + mMultifileMode = true; + ALOGV("Using multifile EGL blobcache"); + } + + // Allow forcing the mode for debug purposes + std::string mode = base::GetProperty("debug.egl.blobcache.multifile", ""); + if (mode == "true") { + ALOGV("Forcing multifile cache due to debug.egl.blobcache.multifile == %s", mode.c_str()); + mMultifileMode = true; + } else if (mode == "false") { + ALOGV("Forcing monolithic cache due to debug.egl.blobcache.multifile == %s", mode.c_str()); mMultifileMode = false; } if (mMultifileMode) { - mCacheByteLimit = kMultifileCacheByteLimit; + mCacheByteLimit = static_cast( + base::GetUintProperty("ro.egl.blobcache.multifile_limit", + kMultifileCacheByteLimit)); + + // Check for a debug value + int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.multifile_limit", -1); + if (debugCacheSize >= 0) { + ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.multifile_limit", + mCacheByteLimit, debugCacheSize); + mCacheByteLimit = debugCacheSize; + } + + ALOGV("Using multifile EGL blobcache limit of %zu bytes", mCacheByteLimit); } mInitialized = true; @@ -133,10 +151,10 @@ void egl_cache_t::terminate() { mBlobCache->writeToFile(); } mBlobCache = nullptr; - if (mMultifileMode) { - checkMultifileCacheSize(mCacheByteLimit); + if (mMultifileBlobCache) { + mMultifileBlobCache->finish(); } - mMultifileMode = false; + mMultifileBlobCache = nullptr; mInitialized = false; } @@ -151,20 +169,8 @@ void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void* if (mInitialized) { if (mMultifileMode) { - setBlobMultifile(key, keySize, value, valueSize, mFilename); - - if (!mMultifileCleanupPending) { - mMultifileCleanupPending = true; - // Kick off a thread to cull cache files below limit - std::thread deferredMultifileCleanupThread([this]() { - sleep(deferredMultifileCleanupDelaySeconds); - std::lock_guard lock(mMutex); - // Check the size of cache and remove entries to stay under limit - checkMultifileCacheSize(mCacheByteLimit); - mMultifileCleanupPending = false; - }); - deferredMultifileCleanupThread.detach(); - } + MultifileBlobCache* mbc = getMultifileBlobCacheLocked(); + mbc->set(key, keySize, value, valueSize); } else { BlobCache* bc = getBlobCacheLocked(); bc->set(key, keySize, value, valueSize); @@ -172,7 +178,7 @@ void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void* if (!mSavePending) { mSavePending = true; std::thread deferredSaveThread([this]() { - sleep(deferredSaveDelay); + sleep(kDeferredMonolithicSaveDelay); std::lock_guard lock(mMutex); if (mInitialized && mBlobCache) { mBlobCache->writeToFile(); @@ -196,15 +202,21 @@ EGLsizeiANDROID egl_cache_t::getBlob(const void* key, EGLsizeiANDROID keySize, v if (mInitialized) { if (mMultifileMode) { - return getBlobMultifile(key, keySize, value, valueSize, mFilename); + MultifileBlobCache* mbc = getMultifileBlobCacheLocked(); + return mbc->get(key, keySize, value, valueSize); } else { BlobCache* bc = getBlobCacheLocked(); return bc->get(key, keySize, value, valueSize); } } + return 0; } +void egl_cache_t::setCacheMode(EGLCacheMode cacheMode) { + mMultifileMode = (cacheMode == EGLCacheMode::Multifile); +} + void egl_cache_t::setCacheFilename(const char* filename) { std::lock_guard lock(mMutex); mFilename = filename; @@ -216,7 +228,7 @@ void egl_cache_t::setCacheLimit(int64_t cacheByteLimit) { if (!mMultifileMode) { // If we're not in multifile mode, ensure the cache limit is only being lowered, // not increasing above the hard coded platform limit - if (cacheByteLimit > maxTotalSize) { + if (cacheByteLimit > kMaxMonolithicTotalSize) { return; } } @@ -226,8 +238,8 @@ void egl_cache_t::setCacheLimit(int64_t cacheByteLimit) { size_t egl_cache_t::getCacheSize() { std::lock_guard lock(mMutex); - if (mMultifileMode) { - return getMultifileCacheSize(); + if (mMultifileBlobCache) { + return mMultifileBlobCache->getTotalSize(); } if (mBlobCache) { return mBlobCache->getSize(); @@ -237,9 +249,18 @@ size_t egl_cache_t::getCacheSize() { BlobCache* egl_cache_t::getBlobCacheLocked() { if (mBlobCache == nullptr) { - mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, mCacheByteLimit, mFilename)); + mBlobCache.reset(new FileBlobCache(kMaxMonolithicKeySize, kMaxMonolithicValueSize, + mCacheByteLimit, mFilename)); } return mBlobCache.get(); } +MultifileBlobCache* egl_cache_t::getMultifileBlobCacheLocked() { + if (mMultifileBlobCache == nullptr) { + mMultifileBlobCache.reset( + new MultifileBlobCache(mCacheByteLimit, kMultifileHotCacheLimit, mFilename)); + } + return mMultifileBlobCache.get(); +} + }; // namespace android diff --git a/opengl/libs/EGL/egl_cache.h b/opengl/libs/EGL/egl_cache.h index 2dcd803324..1399368dd8 100644 --- a/opengl/libs/EGL/egl_cache.h +++ b/opengl/libs/EGL/egl_cache.h @@ -25,6 +25,7 @@ #include #include "FileBlobCache.h" +#include "MultifileBlobCache.h" namespace android { @@ -32,6 +33,11 @@ class egl_display_t; class EGLAPI egl_cache_t { public: + enum class EGLCacheMode { + Monolithic, + Multifile, + }; + // get returns a pointer to the singleton egl_cache_t object. This // singleton object will never be destroyed. static egl_cache_t* get(); @@ -64,6 +70,9 @@ public: // cache contents from one program invocation to another. void setCacheFilename(const char* filename); + // Allow setting monolithic or multifile modes + void setCacheMode(EGLCacheMode cacheMode); + // Allow the fixed cache limit to be overridden void setCacheLimit(int64_t cacheByteLimit); @@ -85,6 +94,9 @@ private: // possible. BlobCache* getBlobCacheLocked(); + // Get or create the multifile blobcache + MultifileBlobCache* getMultifileBlobCacheLocked(); + // mInitialized indicates whether the egl_cache_t is in the initialized // state. It is initialized to false at construction time, and gets set to // true when initialize is called. It is set back to false when terminate @@ -98,6 +110,9 @@ private: // first time it's needed. std::unique_ptr mBlobCache; + // The multifile version of blobcache allowing larger contents to be stored + std::unique_ptr mMultifileBlobCache; + // mFilename is the name of the file for storing cache contents in between // program invocations. It is initialized to an empty string at // construction time, and can be set with the setCacheFilename method. An @@ -123,11 +138,7 @@ private: bool mMultifileMode; // Cache limit - int64_t mCacheByteLimit; - - // Whether we've kicked off a side thread that will check the multifile - // cache size and remove entries if needed. - bool mMultifileCleanupPending; + size_t mCacheByteLimit; }; }; // namespace android diff --git a/opengl/libs/EGL/egl_cache_multifile.cpp b/opengl/libs/EGL/egl_cache_multifile.cpp deleted file mode 100644 index 48e557f190..0000000000 --- a/opengl/libs/EGL/egl_cache_multifile.cpp +++ /dev/null @@ -1,343 +0,0 @@ -/* - ** Copyright 2022, The Android Open Source Project - ** - ** Licensed under the Apache License, Version 2.0 (the "License"); - ** you may not use this file except in compliance with the License. - ** You may obtain a copy of the License at - ** - ** http://www.apache.org/licenses/LICENSE-2.0 - ** - ** Unless required by applicable law or agreed to in writing, software - ** distributed under the License is distributed on an "AS IS" BASIS, - ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ** See the License for the specific language governing permissions and - ** limitations under the License. - */ - -// #define LOG_NDEBUG 0 - -#include "egl_cache_multifile.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -static std::string multifileDirName = ""; - -using namespace std::literals; - -namespace { - -// Create a directory for tracking multiple files -void setupMultifile(const std::string& baseDir) { - // If we've already set up the multifile dir in this base directory, we're done - if (!multifileDirName.empty() && multifileDirName.find(baseDir) != std::string::npos) { - return; - } - - // Otherwise, create it - multifileDirName = baseDir + ".multifile"; - if (mkdir(multifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) { - ALOGW("Unable to create directory (%s), errno (%i)", multifileDirName.c_str(), errno); - } -} - -// Create a filename that is based on the hash of the key -std::string getCacheEntryFilename(const void* key, EGLsizeiANDROID keySize, - const std::string& baseDir) { - // Hash the key into a string - std::stringstream keyName; - keyName << android::JenkinsHashMixBytes(0, static_cast(key), keySize); - - // Build a filename using dir and hash - return baseDir + "/" + keyName.str(); -} - -// Determine file age based on stat modification time -// Newer files have a higher age (time since epoch) -time_t getFileAge(const std::string& filePath) { - struct stat st; - if (stat(filePath.c_str(), &st) == 0) { - ALOGD("getFileAge returning %" PRId64 " for file age", static_cast(st.st_mtime)); - return st.st_mtime; - } else { - ALOGW("Failed to stat %s", filePath.c_str()); - return 0; - } -} - -size_t getFileSize(const std::string& filePath) { - struct stat st; - if (stat(filePath.c_str(), &st) != 0) { - ALOGE("Unable to stat %s", filePath.c_str()); - return 0; - } - return st.st_size; -} - -// Walk through directory entries and track age and size -// Then iterate through the entries, oldest first, and remove them until under the limit. -// This will need to be updated if we move to a multilevel cache dir. -bool applyLRU(size_t cacheLimit) { - // Build a multimap of files indexed by age. - // They will be automatically sorted smallest (oldest) to largest (newest) - std::multimap agesToFiles; - - // Map files to sizes - std::unordered_map filesToSizes; - - size_t totalCacheSize = 0; - - DIR* dir; - struct dirent* entry; - if ((dir = opendir(multifileDirName.c_str())) != nullptr) { - while ((entry = readdir(dir)) != nullptr) { - if (entry->d_name == "."s || entry->d_name == ".."s) { - continue; - } - - // Look up each file age - std::string fullPath = multifileDirName + "/" + entry->d_name; - time_t fileAge = getFileAge(fullPath); - - // Track the files, sorted by age - agesToFiles.insert(std::make_pair(fileAge, fullPath)); - - // Also track the size so we know how much room we have freed - size_t fileSize = getFileSize(fullPath); - filesToSizes[fullPath] = fileSize; - totalCacheSize += fileSize; - } - closedir(dir); - } else { - ALOGE("Unable to open filename: %s", multifileDirName.c_str()); - return false; - } - - if (totalCacheSize <= cacheLimit) { - // If LRU was called on a sufficiently small cache, no need to remove anything - return true; - } - - // Walk through the map of files until we're under the cache size - for (const auto& cacheEntryIter : agesToFiles) { - time_t entryAge = cacheEntryIter.first; - const std::string entryPath = cacheEntryIter.second; - - ALOGD("Removing %s with age %ld", entryPath.c_str(), entryAge); - if (std::remove(entryPath.c_str()) != 0) { - ALOGE("Error removing %s: %s", entryPath.c_str(), std::strerror(errno)); - return false; - } - - totalCacheSize -= filesToSizes[entryPath]; - if (totalCacheSize <= cacheLimit) { - // Success - ALOGV("Reduced cache to %zu", totalCacheSize); - return true; - } else { - ALOGD("Cache size is still too large (%zu), removing more files", totalCacheSize); - } - } - - // Should never reach this return - return false; -} - -} // namespace - -namespace android { - -void setBlobMultifile(const void* key, EGLsizeiANDROID keySize, const void* value, - EGLsizeiANDROID valueSize, const std::string& baseDir) { - if (baseDir.empty()) { - return; - } - - setupMultifile(baseDir); - std::string filename = getCacheEntryFilename(key, keySize, multifileDirName); - - ALOGD("Attempting to open filename for set: %s", filename.c_str()); - std::ofstream outfile(filename, std::ofstream::binary); - if (outfile.fail()) { - ALOGW("Unable to open filename: %s", filename.c_str()); - return; - } - - // First write the key - outfile.write(static_cast(key), keySize); - if (outfile.bad()) { - ALOGW("Unable to write key to filename: %s", filename.c_str()); - outfile.close(); - return; - } - ALOGD("Wrote %i bytes to out file for key", static_cast(outfile.tellp())); - - // Then write the value - outfile.write(static_cast(value), valueSize); - if (outfile.bad()) { - ALOGW("Unable to write value to filename: %s", filename.c_str()); - outfile.close(); - return; - } - ALOGD("Wrote %i bytes to out file for full entry", static_cast(outfile.tellp())); - - outfile.close(); -} - -EGLsizeiANDROID getBlobMultifile(const void* key, EGLsizeiANDROID keySize, void* value, - EGLsizeiANDROID valueSize, const std::string& baseDir) { - if (baseDir.empty()) { - return 0; - } - - setupMultifile(baseDir); - std::string filename = getCacheEntryFilename(key, keySize, multifileDirName); - - // Open the hashed filename path - ALOGD("Attempting to open filename for get: %s", filename.c_str()); - int fd = open(filename.c_str(), O_RDONLY); - - // File doesn't exist, this is a MISS, return zero bytes read - if (fd == -1) { - ALOGD("Cache MISS - failed to open filename: %s, error: %s", filename.c_str(), - std::strerror(errno)); - return 0; - } - - ALOGD("Cache HIT - opened filename: %s", filename.c_str()); - - // Get the size of the file - size_t entrySize = getFileSize(filename); - if (keySize > entrySize) { - ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified " - "file", - keySize, entrySize); - close(fd); - return 0; - } - - // Memory map the file - uint8_t* cacheEntry = - reinterpret_cast(mmap(nullptr, entrySize, PROT_READ, MAP_PRIVATE, fd, 0)); - if (cacheEntry == MAP_FAILED) { - ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); - close(fd); - return 0; - } - - // Compare the incoming key with our stored version (the beginning of the entry) - int compare = memcmp(cacheEntry, key, keySize); - if (compare != 0) { - ALOGW("Cached key and new key do not match! This is a hash collision or modified file"); - munmap(cacheEntry, entrySize); - close(fd); - return 0; - } - - // Keys matched, so remaining cache is value size - size_t cachedValueSize = entrySize - keySize; - - // Return actual value size if valueSize is not large enough - if (cachedValueSize > valueSize) { - ALOGD("Skipping file read, not enough room provided (valueSize): %lu, " - "returning required space as %zu", - valueSize, cachedValueSize); - munmap(cacheEntry, entrySize); - close(fd); - return cachedValueSize; - } - - // Remaining entry following the key is the value - uint8_t* cachedValue = cacheEntry + keySize; - memcpy(value, cachedValue, cachedValueSize); - munmap(cacheEntry, entrySize); - close(fd); - - ALOGD("Read %zu bytes from %s", cachedValueSize, filename.c_str()); - return cachedValueSize; -} - -// Walk through the files in our flat directory, checking the size of each one. -// Return the total size of normal files in the directory. -// This will need to be updated if we move to a multilevel cache dir. -size_t getMultifileCacheSize() { - if (multifileDirName.empty()) { - return 0; - } - - DIR* dir; - struct dirent* entry; - size_t size = 0; - - ALOGD("Using %s as the multifile cache dir ", multifileDirName.c_str()); - - if ((dir = opendir(multifileDirName.c_str())) != nullptr) { - while ((entry = readdir(dir)) != nullptr) { - if (entry->d_name == "."s || entry->d_name == ".."s) { - continue; - } - - // Add up the size of all files in the dir - std::string fullPath = multifileDirName + "/" + entry->d_name; - size += getFileSize(fullPath); - } - closedir(dir); - } else { - ALOGW("Unable to open filename: %s", multifileDirName.c_str()); - return 0; - } - - return size; -} - -// When removing files, what fraction of the overall limit should be reached when removing files -// A divisor of two will decrease the cache to 50%, four to 25% and so on -constexpr uint32_t kCacheLimitDivisor = 2; - -// Calculate the cache size and remove old entries until under the limit -void checkMultifileCacheSize(size_t cacheByteLimit) { - // Start with the value provided by egl_cache - size_t limit = cacheByteLimit; - - // Check for a debug value - int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.bytelimit", -1); - if (debugCacheSize >= 0) { - ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.bytelimit", limit, - debugCacheSize); - limit = debugCacheSize; - } - - // Tally up the initial amount of cache in use - size_t size = getMultifileCacheSize(); - ALOGD("Multifile cache dir size: %zu", size); - - // If size is larger than the threshold, remove files using LRU - if (size > limit) { - ALOGV("Multifile cache size is larger than %zu, removing old entries", cacheByteLimit); - if (!applyLRU(limit / kCacheLimitDivisor)) { - ALOGE("Error when clearing multifile shader cache"); - return; - } - } - ALOGD("Multifile cache size after reduction: %zu", getMultifileCacheSize()); -} - -}; // namespace android \ No newline at end of file diff --git a/opengl/libs/EGL/egl_cache_multifile.h b/opengl/libs/EGL/egl_cache_multifile.h deleted file mode 100644 index ee5fe8108d..0000000000 --- a/opengl/libs/EGL/egl_cache_multifile.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - ** Copyright 2022, The Android Open Source Project - ** - ** Licensed under the Apache License, Version 2.0 (the "License"); - ** you may not use this file except in compliance with the License. - ** You may obtain a copy of the License at - ** - ** http://www.apache.org/licenses/LICENSE-2.0 - ** - ** Unless required by applicable law or agreed to in writing, software - ** distributed under the License is distributed on an "AS IS" BASIS, - ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ** See the License for the specific language governing permissions and - ** limitations under the License. - */ - -#ifndef ANDROID_EGL_CACHE_MULTIFILE_H -#define ANDROID_EGL_CACHE_MULTIFILE_H - -#include -#include - -#include - -namespace android { - -void setBlobMultifile(const void* key, EGLsizeiANDROID keySize, const void* value, - EGLsizeiANDROID valueSize, const std::string& baseDir); -EGLsizeiANDROID getBlobMultifile(const void* key, EGLsizeiANDROID keySize, void* value, - EGLsizeiANDROID valueSize, const std::string& baseDir); -size_t getMultifileCacheSize(); -void checkMultifileCacheSize(size_t cacheByteLimit); - -}; // namespace android - -#endif // ANDROID_EGL_CACHE_MULTIFILE_H diff --git a/opengl/tests/EGLTest/egl_cache_test.cpp b/opengl/tests/EGLTest/egl_cache_test.cpp index 265bec492e..2b3e3a46af 100644 --- a/opengl/tests/EGLTest/egl_cache_test.cpp +++ b/opengl/tests/EGLTest/egl_cache_test.cpp @@ -24,7 +24,7 @@ #include #include "egl_cache.h" -#include "egl_cache_multifile.h" +#include "MultifileBlobCache.h" #include "egl_display.h" #include @@ -33,12 +33,16 @@ using namespace std::literals; namespace android { -class EGLCacheTest : public ::testing::Test { +class EGLCacheTest : public ::testing::TestWithParam { protected: virtual void SetUp() { - mCache = egl_cache_t::get(); + // Terminate to clean up any previous cache in this process + mCache->terminate(); + mTempFile.reset(new TemporaryFile()); mCache->setCacheFilename(&mTempFile->path[0]); + mCache->setCacheLimit(1024); + mCache->setCacheMode(mCacheMode); } virtual void TearDown() { @@ -49,11 +53,12 @@ protected: std::string getCachefileName(); - egl_cache_t* mCache; + egl_cache_t* mCache = egl_cache_t::get(); std::unique_ptr mTempFile; + egl_cache_t::EGLCacheMode mCacheMode = GetParam(); }; -TEST_F(EGLCacheTest, UninitializedCacheAlwaysMisses) { +TEST_P(EGLCacheTest, UninitializedCacheAlwaysMisses) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->setBlob("abcd", 4, "efgh", 4); ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4)); @@ -63,7 +68,7 @@ TEST_F(EGLCacheTest, UninitializedCacheAlwaysMisses) { ASSERT_EQ(0xee, buf[3]); } -TEST_F(EGLCacheTest, InitializedCacheAlwaysHits) { +TEST_P(EGLCacheTest, InitializedCacheAlwaysHits) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -74,7 +79,7 @@ TEST_F(EGLCacheTest, InitializedCacheAlwaysHits) { ASSERT_EQ('h', buf[3]); } -TEST_F(EGLCacheTest, TerminatedCacheAlwaysMisses) { +TEST_P(EGLCacheTest, TerminatedCacheAlwaysMisses) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -86,7 +91,7 @@ TEST_F(EGLCacheTest, TerminatedCacheAlwaysMisses) { ASSERT_EQ(0xee, buf[3]); } -TEST_F(EGLCacheTest, ReinitializedCacheContainsValues) { +TEST_P(EGLCacheTest, ReinitializedCacheContainsValues) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -101,12 +106,12 @@ TEST_F(EGLCacheTest, ReinitializedCacheContainsValues) { std::string EGLCacheTest::getCachefileName() { // Return the monolithic filename unless we find the multifile dir - std::string cachefileName = &mTempFile->path[0]; - std::string multifileDirName = cachefileName + ".multifile"; + std::string cachePath = &mTempFile->path[0]; + std::string multifileDirName = cachePath + ".multifile"; + std::string cachefileName = ""; struct stat info; if (stat(multifileDirName.c_str(), &info) == 0) { - // Ensure we only have one file to manage int realFileCount = 0; @@ -121,6 +126,9 @@ std::string EGLCacheTest::getCachefileName() { cachefileName = multifileDirName + "/" + entry->d_name; realFileCount++; } + } else { + printf("Unable to open %s, error: %s\n", + multifileDirName.c_str(), std::strerror(errno)); } if (realFileCount != 1) { @@ -128,14 +136,19 @@ std::string EGLCacheTest::getCachefileName() { // violates test assumptions cachefileName = ""; } + } else { + printf("Unable to stat %s, error: %s\n", + multifileDirName.c_str(), std::strerror(errno)); } return cachefileName; } -TEST_F(EGLCacheTest, ModifiedCacheMisses) { - // Turn this back on if multifile becomes the default - GTEST_SKIP() << "Skipping test designed for multifile, see b/263574392 and b/246966894"; +TEST_P(EGLCacheTest, ModifiedCacheMisses) { + // Skip if not in multifile mode + if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) { + GTEST_SKIP() << "Skipping test designed for multifile"; + } uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); @@ -147,13 +160,13 @@ TEST_F(EGLCacheTest, ModifiedCacheMisses) { ASSERT_EQ('g', buf[2]); ASSERT_EQ('h', buf[3]); + // Ensure the cache file is written to disk + mCache->terminate(); + // Depending on the cache mode, the file will be in different locations std::string cachefileName = getCachefileName(); ASSERT_TRUE(cachefileName.length() > 0); - // Ensure the cache file is written to disk - mCache->terminate(); - // Stomp on the beginning of the cache file, breaking the key match const long stomp = 0xbadf00d; FILE *file = fopen(cachefileName.c_str(), "w"); @@ -164,14 +177,15 @@ TEST_F(EGLCacheTest, ModifiedCacheMisses) { // Ensure no cache hit mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); uint8_t buf2[4] = { 0xee, 0xee, 0xee, 0xee }; - ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf2, 4)); + // getBlob may return junk for required size, but should not return a cache hit + mCache->getBlob("abcd", 4, buf2, 4); ASSERT_EQ(0xee, buf2[0]); ASSERT_EQ(0xee, buf2[1]); ASSERT_EQ(0xee, buf2[2]); ASSERT_EQ(0xee, buf2[3]); } -TEST_F(EGLCacheTest, TerminatedCacheBelowCacheLimit) { +TEST_P(EGLCacheTest, TerminatedCacheBelowCacheLimit) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); @@ -204,4 +218,8 @@ TEST_F(EGLCacheTest, TerminatedCacheBelowCacheLimit) { ASSERT_LE(mCache->getCacheSize(), 4); } +INSTANTIATE_TEST_CASE_P(MonolithicCacheTests, + EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Monolithic)); +INSTANTIATE_TEST_CASE_P(MultifileCacheTests, + EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Multifile)); } -- GitLab From f244f10ce4f47d1c423eb09a668ff57af9d13900 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Mon, 6 Feb 2023 11:05:02 -0500 Subject: [PATCH 0813/1310] Switch SchedulerClock to std::chrono::steady_clock According to https://en.cppreference.com/w/cpp/chrono/high_resolution_clock, we should prefer steady_clock or system_clock, depending on our needs. We already assert that it is_steady, and we use values of this type as if they *are* from steady_clock. Specifically, android::scheduler::TimePoint, used throughout SurfaceFlinger, inherits from SchedulerClock::time_point. When computing the earliestPresentTime, we use TimePoints (aka a subclass of high_resolution_clock::time_point), but then we assign the result to CompositionRefreshArgs.earliestPresentTime, which is a steady_clock::time_point. Make this consistent by using the recommended type throughout. Test: m Change-Id: I10a3ba4829a60dfc83256cb9fea3736a6c8a0f94 --- services/surfaceflinger/Scheduler/include/scheduler/Time.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Time.h b/services/surfaceflinger/Scheduler/include/scheduler/Time.h index bd4e3c27b3..ba1459a562 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/Time.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/Time.h @@ -26,7 +26,7 @@ namespace android { namespace scheduler { // TODO(b/185535769): Pull Clock.h to libscheduler to reuse this. -using SchedulerClock = std::chrono::high_resolution_clock; +using SchedulerClock = std::chrono::steady_clock; static_assert(SchedulerClock::is_steady); } // namespace scheduler -- GitLab From 758eb56f6259f3263a36abdb0777f16447fb4815 Mon Sep 17 00:00:00 2001 From: LiZhihong Date: Thu, 3 Nov 2022 15:28:29 +0800 Subject: [PATCH 0814/1310] Add send ACTION_BUTTON_PRESS and ACTION_BUTTON_RELEASE in touchpad L/R MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Touchpad L/R only upload ACTION_DOWN and ACTION_UP, but mouse L/R upload ACTION_DOWNã€ACTION_BUTTON_PRESS〠ACTION_BUTTON_RELEASE and ACTION_UP, there are different. It may cause the app to fail to handle touchpad L/R press behavior. Test: manual Bug: 237355203 Signed-off-by: LiZhihong Change-Id: I0934ae237e89eb65f3e9a34b2c12e5467052d43c --- .../reader/mapper/TouchInputMapper.cpp | 62 ++++++++++++++++++- .../reader/mapper/TouchInputMapper.h | 8 +++ .../inputflinger/tests/InputReader_test.cpp | 20 ++++++ 3 files changed, 88 insertions(+), 2 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index b789156a7c..31fdac9ffc 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -2156,6 +2156,53 @@ std::list TouchInputMapper::dispatchButtonPress(nsecs_t when, nsecs_ return out; } +std::list TouchInputMapper::dispatchGestureButtonRelease(nsecs_t when, + uint32_t policyFlags, + BitSet32 idBits, + nsecs_t readTime) { + std::list out; + BitSet32 releasedButtons(mLastCookedState.buttonState & ~mCurrentCookedState.buttonState); + const int32_t metaState = getContext()->getGlobalMetaState(); + int32_t buttonState = mLastCookedState.buttonState; + + while (!releasedButtons.isEmpty()) { + int32_t actionButton = BitSet32::valueForBit(releasedButtons.clearFirstMarkedBit()); + buttonState &= ~actionButton; + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0, + metaState, buttonState, 0, + mPointerGesture.lastGestureProperties, + mPointerGesture.lastGestureCoords, + mPointerGesture.lastGestureIdToIndex, idBits, -1, + mOrientedXPrecision, mOrientedYPrecision, + mPointerGesture.downTime, MotionClassification::NONE)); + } + return out; +} + +std::list TouchInputMapper::dispatchGestureButtonPress(nsecs_t when, + uint32_t policyFlags, + BitSet32 idBits, + nsecs_t readTime) { + std::list out; + BitSet32 pressedButtons(mCurrentCookedState.buttonState & ~mLastCookedState.buttonState); + const int32_t metaState = getContext()->getGlobalMetaState(); + int32_t buttonState = mLastCookedState.buttonState; + + while (!pressedButtons.isEmpty()) { + int32_t actionButton = BitSet32::valueForBit(pressedButtons.clearFirstMarkedBit()); + buttonState |= actionButton; + out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, + AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0, metaState, + buttonState, 0, mPointerGesture.currentGestureProperties, + mPointerGesture.currentGestureCoords, + mPointerGesture.currentGestureIdToIndex, idBits, -1, + mOrientedXPrecision, mOrientedYPrecision, + mPointerGesture.downTime, MotionClassification::NONE)); + } + return out; +} + const BitSet32& TouchInputMapper::findActiveIdBits(const CookedPointerData& cookedPointerData) { if (!cookedPointerData.touchingIdBits.isEmpty()) { return cookedPointerData.touchingIdBits; @@ -2540,8 +2587,13 @@ std::list TouchInputMapper::dispatchPointerGestures(nsecs_t when, ns dispatchedGestureIdBits.value & ~mPointerGesture.currentGestureIdBits.value; } while (!upGestureIdBits.isEmpty()) { - uint32_t id = upGestureIdBits.clearFirstMarkedBit(); - + if (((mLastCookedState.buttonState & AMOTION_EVENT_BUTTON_PRIMARY) != 0 || + (mLastCookedState.buttonState & AMOTION_EVENT_BUTTON_SECONDARY) != 0) && + mPointerGesture.lastGestureMode == PointerGesture::Mode::BUTTON_CLICK_OR_DRAG) { + out += dispatchGestureButtonRelease(when, policyFlags, dispatchedGestureIdBits, + readTime); + } + const uint32_t id = upGestureIdBits.clearFirstMarkedBit(); out.push_back(dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_UP, 0, flags, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, @@ -2586,6 +2638,12 @@ std::list TouchInputMapper::dispatchPointerGestures(nsecs_t when, ns mPointerGesture.currentGestureIdToIndex, dispatchedGestureIdBits, id, 0, 0, mPointerGesture.downTime, classification)); + if (((buttonState & AMOTION_EVENT_BUTTON_PRIMARY) != 0 || + (buttonState & AMOTION_EVENT_BUTTON_SECONDARY) != 0) && + mPointerGesture.currentGestureMode == PointerGesture::Mode::BUTTON_CLICK_OR_DRAG) { + out += dispatchGestureButtonPress(when, policyFlags, dispatchedGestureIdBits, + readTime); + } } } diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 87deb39e20..7b464efccb 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -735,6 +735,14 @@ private: uint32_t policyFlags); [[nodiscard]] std::list dispatchButtonPress(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); + [[nodiscard]] std::list dispatchGestureButtonPress(nsecs_t when, + uint32_t policyFlags, + BitSet32 idBits, + nsecs_t readTime); + [[nodiscard]] std::list dispatchGestureButtonRelease(nsecs_t when, + uint32_t policyFlags, + BitSet32 idBits, + nsecs_t readTime); const BitSet32& findActiveIdBits(const CookedPointerData& cookedPointerData); void cookPointerData(); [[nodiscard]] std::list abortTouches(nsecs_t when, nsecs_t readTime, diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 1b04375a56..fe7af80b92 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -10084,6 +10084,26 @@ TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) { ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], 100 * scale, 100 * scale, 0, 0, 0, 0, 0, 0, 0, 0)); + + // BUTTON DOWN + processKey(mapper, BTN_LEFT, 1); + processSync(mapper); + + // touchinputmapper design sends a move before button press + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action); + + // BUTTON UP + processKey(mapper, BTN_LEFT, 0); + processSync(mapper); + + // touchinputmapper design sends a move after button release + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); } TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) { -- GitLab From 9b8926eda5f03ab378fffb49d3c757ecd882c613 Mon Sep 17 00:00:00 2001 From: Philip Quinn Date: Tue, 31 Jan 2023 14:50:02 -0800 Subject: [PATCH 0815/1310] Add a simple ring buffer and use it for holding TFLite model inputs. Bug: 167946763 Test: atest libinput_tests Change-Id: I7e50d38ed0c593aebc5fdc6af4b25868505d48bc --- include/input/RingBuffer.h | 293 ++++++++++++++++++++++++++ include/input/TfLiteMotionPredictor.h | 13 +- libs/input/TfLiteMotionPredictor.cpp | 28 +-- libs/input/tests/Android.bp | 1 + libs/input/tests/RingBuffer_test.cpp | 208 ++++++++++++++++++ 5 files changed, 520 insertions(+), 23 deletions(-) create mode 100644 include/input/RingBuffer.h create mode 100644 libs/input/tests/RingBuffer_test.cpp diff --git a/include/input/RingBuffer.h b/include/input/RingBuffer.h new file mode 100644 index 0000000000..67984b7c80 --- /dev/null +++ b/include/input/RingBuffer.h @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace android { + +// A fixed-size ring buffer of elements. +// +// Elements can only be removed from the front/back or added to the front/back, but with O(1) +// performance. Elements from the opposing side are evicted when new elements are pushed onto a full +// buffer. +template +class RingBuffer { +public: + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + + template + class Iterator; + using iterator = Iterator; + using const_iterator = Iterator; + + // Creates an empty ring buffer that can hold some capacity. + explicit RingBuffer(size_type capacity) + : mBuffer(std::allocator().allocate(capacity)), mCapacity(capacity) {} + + // Creates a full ring buffer holding a fixed number of elements initialised to some value. + explicit RingBuffer(size_type count, const_reference value) : RingBuffer(count) { + while (count) { + pushBack(value); + --count; + } + } + + RingBuffer(const RingBuffer& other) : RingBuffer(other.capacity()) { + for (const auto& element : other) { + pushBack(element); + } + } + + RingBuffer(RingBuffer&& other) noexcept { *this = std::move(other); } + + ~RingBuffer() { + if (mBuffer) { + clear(); + std::allocator().deallocate(mBuffer, mCapacity); + } + } + + RingBuffer& operator=(const RingBuffer& other) { return *this = RingBuffer(other); } + + RingBuffer& operator=(RingBuffer&& other) noexcept { + if (this == &other) { + return *this; + } + if (mBuffer) { + clear(); + std::allocator().deallocate(mBuffer, mCapacity); + } + mBuffer = std::move(other.mBuffer); + mCapacity = other.mCapacity; + mBegin = other.mBegin; + mSize = other.mSize; + other.mBuffer = nullptr; + other.mCapacity = 0; + other.mBegin = 0; + other.mSize = 0; + return *this; + } + + iterator begin() { return {*this, 0}; } + const_iterator begin() const { return {*this, 0}; } + iterator end() { return {*this, mSize}; } + const_iterator end() const { return {*this, mSize}; } + + reference operator[](size_type i) { return mBuffer[bufferIndex(i)]; } + const_reference operator[](size_type i) const { return mBuffer[bufferIndex(i)]; } + + // Removes all elements from the buffer. + void clear() { + std::destroy(begin(), end()); + mSize = 0; + } + + // Removes and returns the first element from the buffer. + value_type popFront() { + value_type element = mBuffer[mBegin]; + std::destroy_at(std::addressof(mBuffer[mBegin])); + mBegin = next(mBegin); + --mSize; + return element; + } + + // Removes and returns the last element from the buffer. + value_type popBack() { + size_type backIndex = bufferIndex(mSize - 1); + value_type element = mBuffer[backIndex]; + std::destroy_at(std::addressof(mBuffer[backIndex])); + --mSize; + return element; + } + + // Adds an element to the front of the buffer. + void pushFront(const value_type& element) { pushFront(value_type(element)); } + void pushFront(value_type&& element) { + mBegin = previous(mBegin); + if (size() == capacity()) { + mBuffer[mBegin] = std::forward(element); + } else { + // The space at mBuffer[mBegin] is uninitialised. + // TODO: Use std::construct_at when it becomes available. + new (std::addressof(mBuffer[mBegin])) value_type(std::forward(element)); + ++mSize; + } + } + + // Adds an element to the back of the buffer. + void pushBack(const value_type& element) { pushBack(value_type(element)); } + void pushBack(value_type&& element) { + if (size() == capacity()) { + mBuffer[mBegin] = std::forward(element); + mBegin = next(mBegin); + } else { + // The space at mBuffer[...] is uninitialised. + // TODO: Use std::construct_at when it becomes available. + new (std::addressof(mBuffer[bufferIndex(mSize)])) + value_type(std::forward(element)); + ++mSize; + } + } + + bool empty() const { return mSize == 0; } + size_type capacity() const { return mCapacity; } + size_type size() const { return mSize; } + + void swap(RingBuffer& other) noexcept { + using std::swap; + swap(mBuffer, other.mBuffer); + swap(mCapacity, other.mCapacity); + swap(mBegin, other.mBegin); + swap(mSize, other.mSize); + } + + friend void swap(RingBuffer& lhs, RingBuffer& rhs) noexcept { lhs.swap(rhs); } + + template + class Iterator { + private: + using ContainerType = std::conditional_t, const RingBuffer, RingBuffer>; + + public: + using iterator_category = std::random_access_iterator_tag; + using size_type = ContainerType::size_type; + using difference_type = ContainerType::difference_type; + using value_type = std::remove_cv_t; + using pointer = U*; + using reference = U&; + + Iterator(ContainerType& container, size_type index) + : mContainer(container), mIndex(index) {} + + Iterator(const Iterator&) = default; + Iterator& operator=(const Iterator&) = default; + + Iterator& operator++() { + ++mIndex; + return *this; + } + + Iterator operator++(int) { + Iterator iterator(*this); + ++(*this); + return iterator; + } + + Iterator& operator--() { + --mIndex; + return *this; + } + + Iterator operator--(int) { + Iterator iterator(*this); + --(*this); + return iterator; + } + + Iterator& operator+=(difference_type n) { + mIndex += n; + return *this; + } + + Iterator operator+(difference_type n) { + Iterator iterator(*this); + return iterator += n; + } + + Iterator& operator-=(difference_type n) { return *this += -n; } + + Iterator operator-(difference_type n) { + Iterator iterator(*this); + return iterator -= n; + } + + difference_type operator-(const Iterator& other) { return mIndex - other.mIndex; } + + bool operator==(const Iterator& rhs) const { return mIndex == rhs.mIndex; } + + bool operator!=(const Iterator& rhs) const { return !(*this == rhs); } + + friend auto operator<=>(const Iterator& lhs, const Iterator& rhs) { + return lhs.mIndex <=> rhs.mIndex; + } + + reference operator[](difference_type n) { return *(*this + n); } + + reference operator*() const { return mContainer[mIndex]; } + pointer operator->() const { return std::addressof(mContainer[mIndex]); } + + private: + ContainerType& mContainer; + size_type mIndex = 0; + }; + +private: + // Returns the index of the next element in mBuffer. + size_type next(size_type index) const { + if (index == capacity() - 1) { + return 0; + } else { + return index + 1; + } + } + + // Returns the index of the previous element in mBuffer. + size_type previous(size_type index) const { + if (index == 0) { + return capacity() - 1; + } else { + return index - 1; + } + } + + // Converts the index of an element in [0, size()] to its corresponding index in mBuffer. + size_type bufferIndex(size_type elementIndex) const { + CHECK_LE(elementIndex, size()); + size_type index = mBegin + elementIndex; + if (index >= capacity()) { + index -= capacity(); + } + CHECK_LT(index, capacity()) + << android::base::StringPrintf("Invalid index calculated for element (%zu) " + "in buffer of size %zu", + elementIndex, size()); + return index; + } + + pointer mBuffer = nullptr; + size_type mCapacity = 0; // Total capacity of mBuffer. + size_type mBegin = 0; // Index of the first initialised element in mBuffer. + size_type mSize = 0; // Total number of initialised elements. +}; + +} // namespace android diff --git a/include/input/TfLiteMotionPredictor.h b/include/input/TfLiteMotionPredictor.h index ff0f51c7d9..704349c2eb 100644 --- a/include/input/TfLiteMotionPredictor.h +++ b/include/input/TfLiteMotionPredictor.h @@ -23,7 +23,8 @@ #include #include #include -#include + +#include #include #include @@ -83,11 +84,11 @@ public: private: int64_t mTimestamp = 0; - std::vector mInputR; - std::vector mInputPhi; - std::vector mInputPressure; - std::vector mInputTilt; - std::vector mInputOrientation; + RingBuffer mInputR; + RingBuffer mInputPhi; + RingBuffer mInputPressure; + RingBuffer mInputTilt; + RingBuffer mInputOrientation; // The samples defining the current polar axis. std::optional mAxisFrom; diff --git a/libs/input/TfLiteMotionPredictor.cpp b/libs/input/TfLiteMotionPredictor.cpp index 1a337adf13..40653d36f7 100644 --- a/libs/input/TfLiteMotionPredictor.cpp +++ b/libs/input/TfLiteMotionPredictor.cpp @@ -104,13 +104,13 @@ void checkTensor(const TfLiteTensor* tensor) { } // namespace -TfLiteMotionPredictorBuffers::TfLiteMotionPredictorBuffers(size_t inputLength) { +TfLiteMotionPredictorBuffers::TfLiteMotionPredictorBuffers(size_t inputLength) + : mInputR(inputLength, 0), + mInputPhi(inputLength, 0), + mInputPressure(inputLength, 0), + mInputTilt(inputLength, 0), + mInputOrientation(inputLength, 0) { LOG_ALWAYS_FATAL_IF(inputLength == 0, "Buffer input size must be greater than 0"); - mInputR.resize(inputLength); - mInputPhi.resize(inputLength); - mInputPressure.resize(inputLength); - mInputTilt.resize(inputLength); - mInputOrientation.resize(inputLength); } void TfLiteMotionPredictorBuffers::reset() { @@ -186,17 +186,11 @@ void TfLiteMotionPredictorBuffers::pushSample(int64_t timestamp, mAxisTo = sample; // Push the current sample onto the end of the input buffers. - mInputR.erase(mInputR.begin()); - mInputPhi.erase(mInputPhi.begin()); - mInputPressure.erase(mInputPressure.begin()); - mInputTilt.erase(mInputTilt.begin()); - mInputOrientation.erase(mInputOrientation.begin()); - - mInputR.push_back(r); - mInputPhi.push_back(phi); - mInputPressure.push_back(sample.pressure); - mInputTilt.push_back(sample.tilt); - mInputOrientation.push_back(orientation); + mInputR.pushBack(r); + mInputPhi.pushBack(phi); + mInputPressure.pushBack(sample.pressure); + mInputTilt.pushBack(sample.tilt); + mInputOrientation.pushBack(orientation); } std::unique_ptr TfLiteMotionPredictorModel::create( diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index 916a8f2b2a..f07164c87f 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -19,6 +19,7 @@ cc_test { "InputEvent_test.cpp", "InputPublisherAndConsumer_test.cpp", "MotionPredictor_test.cpp", + "RingBuffer_test.cpp", "TfLiteMotionPredictor_test.cpp", "TouchResampling_test.cpp", "TouchVideoFrame_test.cpp", diff --git a/libs/input/tests/RingBuffer_test.cpp b/libs/input/tests/RingBuffer_test.cpp new file mode 100644 index 0000000000..8a6ef4c21b --- /dev/null +++ b/libs/input/tests/RingBuffer_test.cpp @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include +#include +#include + +namespace android { +namespace { + +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::IsEmpty; +using ::testing::Not; +using ::testing::SizeIs; + +TEST(RingBufferTest, PushPop) { + RingBuffer buffer(/*capacity=*/3); + + buffer.pushBack(1); + buffer.pushBack(2); + buffer.pushBack(3); + EXPECT_THAT(buffer, ElementsAre(1, 2, 3)); + + buffer.pushBack(4); + EXPECT_THAT(buffer, ElementsAre(2, 3, 4)); + + buffer.pushFront(1); + EXPECT_THAT(buffer, ElementsAre(1, 2, 3)); + + EXPECT_EQ(1, buffer.popFront()); + EXPECT_THAT(buffer, ElementsAre(2, 3)); + + buffer.pushBack(4); + EXPECT_THAT(buffer, ElementsAre(2, 3, 4)); + + buffer.pushBack(5); + EXPECT_THAT(buffer, ElementsAre(3, 4, 5)); + + EXPECT_EQ(5, buffer.popBack()); + EXPECT_THAT(buffer, ElementsAre(3, 4)); + + EXPECT_EQ(4, buffer.popBack()); + EXPECT_THAT(buffer, ElementsAre(3)); + + EXPECT_EQ(3, buffer.popBack()); + EXPECT_THAT(buffer, ElementsAre()); + + buffer.pushBack(1); + EXPECT_THAT(buffer, ElementsAre(1)); + + EXPECT_EQ(1, buffer.popFront()); + EXPECT_THAT(buffer, ElementsAre()); +} + +TEST(RingBufferTest, ObjectType) { + RingBuffer> buffer(/*capacity=*/2); + buffer.pushBack(std::make_unique(1)); + buffer.pushBack(std::make_unique(2)); + buffer.pushBack(std::make_unique(3)); + + EXPECT_EQ(2, *buffer[0]); + EXPECT_EQ(3, *buffer[1]); +} + +TEST(RingBufferTest, ConstructConstantValue) { + RingBuffer buffer(/*count=*/3, /*value=*/10); + EXPECT_THAT(buffer, ElementsAre(10, 10, 10)); + EXPECT_EQ(3u, buffer.capacity()); +} + +TEST(RingBufferTest, Assignment) { + RingBuffer a(/*capacity=*/2); + a.pushBack(1); + a.pushBack(2); + + RingBuffer b(/*capacity=*/3); + b.pushBack(10); + b.pushBack(20); + b.pushBack(30); + + std::swap(a, b); + EXPECT_THAT(a, ElementsAre(10, 20, 30)); + EXPECT_THAT(b, ElementsAre(1, 2)); + + a = b; + EXPECT_THAT(a, ElementsAreArray(b)); + + RingBuffer c(b); + EXPECT_THAT(c, ElementsAreArray(b)); + + RingBuffer d(std::move(b)); + EXPECT_EQ(0u, b.capacity()); + EXPECT_THAT(b, ElementsAre()); + EXPECT_THAT(d, ElementsAre(1, 2)); + + b = std::move(d); + EXPECT_THAT(b, ElementsAre(1, 2)); + EXPECT_THAT(d, ElementsAre()); + EXPECT_EQ(0u, d.capacity()); +} + +TEST(RingBufferTest, Subscripting) { + RingBuffer buffer(/*capacity=*/2); + buffer.pushBack(1); + EXPECT_EQ(1, buffer[0]); + + buffer.pushFront(0); + EXPECT_EQ(0, buffer[0]); + EXPECT_EQ(1, buffer[1]); + + buffer.pushFront(-1); + EXPECT_EQ(-1, buffer[0]); + EXPECT_EQ(0, buffer[1]); +} + +TEST(RingBufferTest, Iterator) { + RingBuffer buffer(/*capacity=*/3); + buffer.pushFront(2); + buffer.pushBack(3); + + auto begin = buffer.begin(); + auto end = buffer.end(); + + EXPECT_NE(begin, end); + EXPECT_LE(begin, end); + EXPECT_GT(end, begin); + EXPECT_EQ(end, begin + 2); + EXPECT_EQ(begin, end - 2); + + EXPECT_EQ(2, end - begin); + EXPECT_EQ(1, end - (begin + 1)); + + EXPECT_EQ(2, *begin); + ++begin; + EXPECT_EQ(3, *begin); + --begin; + EXPECT_EQ(2, *begin); + begin += 1; + EXPECT_EQ(3, *begin); + begin += -1; + EXPECT_EQ(2, *begin); + begin -= -1; + EXPECT_EQ(3, *begin); +} + +TEST(RingBufferTest, Clear) { + RingBuffer buffer(/*capacity=*/2); + EXPECT_THAT(buffer, ElementsAre()); + + buffer.pushBack(1); + EXPECT_THAT(buffer, ElementsAre(1)); + + buffer.clear(); + EXPECT_THAT(buffer, ElementsAre()); + EXPECT_THAT(buffer, SizeIs(0)); + EXPECT_THAT(buffer, IsEmpty()); + + buffer.pushFront(1); + EXPECT_THAT(buffer, ElementsAre(1)); +} + +TEST(RingBufferTest, SizeAndIsEmpty) { + RingBuffer buffer(/*capacity=*/2); + EXPECT_THAT(buffer, SizeIs(0)); + EXPECT_THAT(buffer, IsEmpty()); + + buffer.pushBack(1); + EXPECT_THAT(buffer, SizeIs(1)); + EXPECT_THAT(buffer, Not(IsEmpty())); + + buffer.pushBack(2); + EXPECT_THAT(buffer, SizeIs(2)); + EXPECT_THAT(buffer, Not(IsEmpty())); + + buffer.pushBack(3); + EXPECT_THAT(buffer, SizeIs(2)); + EXPECT_THAT(buffer, Not(IsEmpty())); + + buffer.popFront(); + EXPECT_THAT(buffer, SizeIs(1)); + EXPECT_THAT(buffer, Not(IsEmpty())); + + buffer.popBack(); + EXPECT_THAT(buffer, SizeIs(0)); + EXPECT_THAT(buffer, IsEmpty()); +} + +} // namespace +} // namespace android -- GitLab From b4b484aca143cf86d91ed86824d9e5081e0ddbf0 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Fri, 20 Jan 2023 10:00:18 -0800 Subject: [PATCH 0816/1310] BBQ: Check if we have already acquired max num of buffers When the IGBP is disconnected, the BQ state is reset but the BBQ state is not. Without the max acquire check in BBQ, we will continue to acquire buffers. This may cause issues in some stress tests which continuously disconnect and reconnect while queuing buffers. The buffers will only be released once they are released by SF. This can cause some systems to run out of memory. Ideal fix would be to track and free the buffers once the BQ is disconnected but that will be more risky for QPR. Test: atest GLSurfaceViewTest#testPauseResumeWithoutDelay Bug: 263340543 Change-Id: I77b34f6151a9d1b461649c575be98df1f00f2464 --- libs/gui/BLASTBufferQueue.cpp | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index 5d12463f88..422002dfdc 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -485,11 +485,19 @@ void BLASTBufferQueue::releaseBuffer(const ReleaseCallbackId& callbackId, status_t BLASTBufferQueue::acquireNextBufferLocked( const std::optional transaction) { - // If the next transaction is set, we want to guarantee the our acquire will not fail, so don't - // include the extra buffer when checking if we can acquire the next buffer. + // Check if we have frames available and we have not acquired the maximum number of buffers. + // Even with this check, the consumer can fail to acquire an additional buffer if the consumer + // has already acquired (mMaxAcquiredBuffers + 1) and the new buffer is not droppable. In this + // case mBufferItemConsumer->acquireBuffer will return with NO_BUFFER_AVAILABLE. if (mNumFrameAvailable == 0) { - BQA_LOGV("Can't process next buffer. No available frames"); - return NOT_ENOUGH_DATA; + BQA_LOGV("Can't acquire next buffer. No available frames"); + return BufferQueue::NO_BUFFER_AVAILABLE; + } + + if (mNumAcquired >= (mMaxAcquiredBuffers + 2)) { + BQA_LOGV("Can't acquire next buffer. Already acquired max frames %d max:%d + 2", + mNumAcquired, mMaxAcquiredBuffers); + return BufferQueue::NO_BUFFER_AVAILABLE; } if (mSurfaceControl == nullptr) { @@ -662,12 +670,12 @@ void BLASTBufferQueue::acquireAndReleaseBuffer() { void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) { std::function prevCallback = nullptr; SurfaceComposerClient::Transaction* prevTransaction = nullptr; - bool waitForTransactionCallback = !mSyncedFrameNumbers.empty(); { std::unique_lock _lock{mMutex}; BBQ_TRACE(); + bool waitForTransactionCallback = !mSyncedFrameNumbers.empty(); const bool syncTransactionSet = mTransactionReadyCallback != nullptr; BQA_LOGV("onFrameAvailable-start syncTransactionSet=%s", boolToString(syncTransactionSet)); @@ -696,6 +704,15 @@ void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) { // flush out the shadow queue acquireAndReleaseBuffer(); } + } else { + // Make sure the frame available count is 0 before proceeding with a sync to ensure + // the correct frame is used for the sync. The only way mNumFrameAvailable would be + // greater than 0 is if we already ran out of buffers previously. This means we + // need to flush the buffers before proceeding with the sync. + while (mNumFrameAvailable > 0) { + BQA_LOGD("waiting until no queued buffers"); + mCallbackCV.wait(_lock); + } } } @@ -711,6 +728,11 @@ void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) { item.mFrameNumber, boolToString(syncTransactionSet)); if (syncTransactionSet) { + // Add to mSyncedFrameNumbers before waiting in case any buffers are released + // while waiting for a free buffer. The release and commit callback will try to + // acquire buffers if there are any available, but we don't want it to acquire + // in the case where a sync transaction wants the buffer. + mSyncedFrameNumbers.emplace(item.mFrameNumber); // If there's no available buffer and we're in a sync transaction, we need to wait // instead of returning since we guarantee a buffer will be acquired for the sync. while (acquireNextBufferLocked(mSyncTransaction) == BufferQueue::NO_BUFFER_AVAILABLE) { @@ -723,7 +745,6 @@ void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) { incStrong((void*)transactionCommittedCallbackThunk); mSyncTransaction->addTransactionCommittedCallback(transactionCommittedCallbackThunk, static_cast(this)); - mSyncedFrameNumbers.emplace(item.mFrameNumber); if (mAcquireSingleBuffer) { prevCallback = mTransactionReadyCallback; prevTransaction = mSyncTransaction; -- GitLab From e0237bbe066bb27eb88b4c5d6a5485cf9c2dd45d Mon Sep 17 00:00:00 2001 From: Chavi Weingarten Date: Mon, 6 Feb 2023 21:48:32 +0000 Subject: [PATCH 0817/1310] Add thread safety check to libgui build Fix places that had errors to ensure locks are correctly held in libgui Test: Builds Bug: 268091723 Change-Id: I8a94edeb649608ff66d25a482026a81074466305 --- libs/gui/Android.bp | 4 ++ libs/gui/BLASTBufferQueue.cpp | 58 ++++++++++---------- libs/gui/SurfaceComposerClient.cpp | 3 + libs/gui/include/gui/BLASTBufferQueue.h | 22 ++++---- libs/gui/include/gui/SurfaceComposerClient.h | 2 +- 5 files changed, 49 insertions(+), 40 deletions(-) diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index 6c9c28a48f..21900a073a 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -254,6 +254,10 @@ cc_library_shared { lto: { thin: true, }, + + cflags: [ + "-Wthread-safety", + ], } // Used by media codec services exclusively as a static lib for diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index 60603ba50a..de64ea8476 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -35,6 +35,7 @@ #include #include +#include #include using namespace std::chrono_literals; @@ -63,6 +64,10 @@ namespace android { ATRACE_FORMAT("%s - %s(f:%u,a:%u)" x, __FUNCTION__, mName.c_str(), mNumFrameAvailable, \ mNumAcquired, ##__VA_ARGS__) +#define UNIQUE_LOCK_WITH_ASSERTION(mutex) \ + std::unique_lock _lock{mutex}; \ + base::ScopedLockAssertion assumeLocked(mutex); + void BLASTBufferItemConsumer::onDisconnect() { Mutex::Autolock lock(mMutex); mPreviouslyConnected = mCurrentlyConnected; @@ -207,7 +212,7 @@ void BLASTBufferQueue::update(const sp& surface, uint32_t width, int32_t format) { LOG_ALWAYS_FATAL_IF(surface == nullptr, "BLASTBufferQueue: mSurfaceControl must not be NULL"); - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; if (mFormat != format) { mFormat = format; mBufferItemConsumer->setDefaultBufferFormat(convertBufferFormat(format)); @@ -277,7 +282,7 @@ void BLASTBufferQueue::transactionCommittedCallback(nsecs_t /*latchTime*/, const sp& /*presentFence*/, const std::vector& stats) { { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; BBQ_TRACE(); BQA_LOGV("transactionCommittedCallback"); if (!mSurfaceControlsWithPendingCallback.empty()) { @@ -325,7 +330,7 @@ static void transactionCallbackThunk(void* context, nsecs_t latchTime, void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp& /*presentFence*/, const std::vector& stats) { { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; BBQ_TRACE(); BQA_LOGV("transactionCallback"); @@ -406,9 +411,8 @@ void BLASTBufferQueue::flushShadowQueue() { void BLASTBufferQueue::releaseBufferCallback( const ReleaseCallbackId& id, const sp& releaseFence, std::optional currentMaxAcquiredBufferCount) { + std::lock_guard _lock{mMutex}; BBQ_TRACE(); - - std::unique_lock _lock{mMutex}; releaseBufferCallbackLocked(id, releaseFence, currentMaxAcquiredBufferCount, false /* fakeRelease */); } @@ -423,10 +427,8 @@ void BLASTBufferQueue::releaseBufferCallbackLocked( // to the buffer queue. This will prevent higher latency when we are running // on a lower refresh rate than the max supported. We only do that for EGL // clients as others don't care about latency - const bool isEGL = [&] { - const auto it = mSubmitted.find(id); - return it != mSubmitted.end() && it->second.mApi == NATIVE_WINDOW_API_EGL; - }(); + const auto it = mSubmitted.find(id); + const bool isEGL = it != mSubmitted.end() && it->second.mApi == NATIVE_WINDOW_API_EGL; if (currentMaxAcquiredBufferCount) { mCurrentMaxAcquiredBufferCount = *currentMaxAcquiredBufferCount; @@ -607,7 +609,7 @@ status_t BLASTBufferQueue::acquireNextBufferLocked( } { - std::unique_lock _lock{mTimestampMutex}; + std::lock_guard _lock{mTimestampMutex}; auto dequeueTime = mDequeueTimestamps.find(buffer->getId()); if (dequeueTime != mDequeueTimestamps.end()) { Parcel p; @@ -662,11 +664,11 @@ void BLASTBufferQueue::acquireAndReleaseBuffer() { void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) { std::function prevCallback = nullptr; SurfaceComposerClient::Transaction* prevTransaction = nullptr; - bool waitForTransactionCallback = !mSyncedFrameNumbers.empty(); { - std::unique_lock _lock{mMutex}; + UNIQUE_LOCK_WITH_ASSERTION(mMutex); BBQ_TRACE(); + bool waitForTransactionCallback = !mSyncedFrameNumbers.empty(); const bool syncTransactionSet = mTransactionReadyCallback != nullptr; BQA_LOGV("onFrameAvailable-start syncTransactionSet=%s", boolToString(syncTransactionSet)); @@ -745,25 +747,24 @@ void BLASTBufferQueue::onFrameReplaced(const BufferItem& item) { } void BLASTBufferQueue::onFrameDequeued(const uint64_t bufferId) { - std::unique_lock _lock{mTimestampMutex}; + std::lock_guard _lock{mTimestampMutex}; mDequeueTimestamps[bufferId] = systemTime(); }; void BLASTBufferQueue::onFrameCancelled(const uint64_t bufferId) { - std::unique_lock _lock{mTimestampMutex}; + std::lock_guard _lock{mTimestampMutex}; mDequeueTimestamps.erase(bufferId); }; void BLASTBufferQueue::syncNextTransaction( std::function callback, bool acquireSingleBuffer) { - BBQ_TRACE(); - std::function prevCallback = nullptr; SurfaceComposerClient::Transaction* prevTransaction = nullptr; { std::lock_guard _lock{mMutex}; + BBQ_TRACE(); // We're about to overwrite the previous call so we should invoke that callback // immediately. if (mTransactionReadyCallback) { @@ -829,8 +830,8 @@ bool BLASTBufferQueue::rejectBuffer(const BufferItem& item) { class BBQSurface : public Surface { private: std::mutex mMutex; - sp mBbq; - bool mDestroyed = false; + sp mBbq GUARDED_BY(mMutex); + bool mDestroyed GUARDED_BY(mMutex) = false; public: BBQSurface(const sp& igbp, bool controlledByApp, @@ -851,7 +852,7 @@ public: status_t setFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy) override { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; if (mDestroyed) { return DEAD_OBJECT; } @@ -864,7 +865,7 @@ public: status_t setFrameTimelineInfo(uint64_t frameNumber, const FrameTimelineInfo& frameTimelineInfo) override { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; if (mDestroyed) { return DEAD_OBJECT; } @@ -874,7 +875,7 @@ public: void destroy() override { Surface::destroy(); - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; mDestroyed = true; mBbq = nullptr; } @@ -884,7 +885,7 @@ public: // no timing issues. status_t BLASTBufferQueue::setFrameRate(float frameRate, int8_t compatibility, bool shouldBeSeamless) { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; SurfaceComposerClient::Transaction t; return t.setFrameRate(mSurfaceControl, frameRate, compatibility, shouldBeSeamless).apply(); @@ -894,20 +895,20 @@ status_t BLASTBufferQueue::setFrameTimelineInfo(uint64_t frameNumber, const FrameTimelineInfo& frameTimelineInfo) { ATRACE_FORMAT("%s(%s) frameNumber: %" PRIu64 " vsyncId: %" PRId64, __func__, mName.c_str(), frameNumber, frameTimelineInfo.vsyncId); - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; mPendingFrameTimelines.push({frameNumber, frameTimelineInfo}); return OK; } void BLASTBufferQueue::setSidebandStream(const sp& stream) { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; SurfaceComposerClient::Transaction t; t.setSidebandStream(mSurfaceControl, stream).apply(); } sp BLASTBufferQueue::getSurface(bool includeSurfaceControlHandle) { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; sp scHandle = nullptr; if (includeSurfaceControlHandle && mSurfaceControl) { scHandle = mSurfaceControl->getHandle(); @@ -1098,6 +1099,7 @@ PixelFormat BLASTBufferQueue::convertBufferFormat(PixelFormat& format) { } uint32_t BLASTBufferQueue::getLastTransformHint() const { + std::lock_guard _lock{mMutex}; if (mSurfaceControl != nullptr) { return mSurfaceControl->getTransformHint(); } else { @@ -1106,18 +1108,18 @@ uint32_t BLASTBufferQueue::getLastTransformHint() const { } uint64_t BLASTBufferQueue::getLastAcquiredFrameNum() { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; return mLastAcquiredFrameNumber; } bool BLASTBufferQueue::isSameSurfaceControl(const sp& surfaceControl) const { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; return SurfaceControl::isSameSurface(mSurfaceControl, surfaceControl); } void BLASTBufferQueue::setTransactionHangCallback( std::function callback) { - std::unique_lock _lock{mMutex}; + std::lock_guard _lock{mMutex}; mTransactionHangCallback = callback; } diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 9092f5fe67..ccd225cd62 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -53,6 +53,7 @@ #include #include +#include #include #include @@ -2988,6 +2989,7 @@ void ReleaseCallbackThread::threadMain() { while (true) { { std::unique_lock lock(mMutex); + base::ScopedLockAssertion assumeLocked(mMutex); callbackInfos = std::move(mCallbackInfos); mCallbackInfos = {}; } @@ -3000,6 +3002,7 @@ void ReleaseCallbackThread::threadMain() { { std::unique_lock lock(mMutex); + base::ScopedLockAssertion assumeLocked(mMutex); if (mCallbackInfos.size() == 0) { mReleaseCallbackPending.wait(lock); } diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h index c93ab86770..8d07162f1b 100644 --- a/libs/gui/include/gui/BLASTBufferQueue.h +++ b/libs/gui/include/gui/BLASTBufferQueue.h @@ -44,23 +44,23 @@ public: mCurrentlyConnected(false), mPreviouslyConnected(false) {} - void onDisconnect() override; + void onDisconnect() override EXCLUDES(mMutex); void addAndGetFrameTimestamps(const NewFrameEventsEntry* newTimestamps, - FrameEventHistoryDelta* outDelta) override REQUIRES(mMutex); + FrameEventHistoryDelta* outDelta) override EXCLUDES(mMutex); void updateFrameTimestamps(uint64_t frameNumber, nsecs_t refreshStartTime, const sp& gpuCompositionDoneFence, const sp& presentFence, const sp& prevReleaseFence, CompositorTiming compositorTiming, nsecs_t latchTime, - nsecs_t dequeueReadyTime) REQUIRES(mMutex); - void getConnectionEvents(uint64_t frameNumber, bool* needsDisconnect); + nsecs_t dequeueReadyTime) EXCLUDES(mMutex); + void getConnectionEvents(uint64_t frameNumber, bool* needsDisconnect) EXCLUDES(mMutex); protected: - void onSidebandStreamChanged() override REQUIRES(mMutex); + void onSidebandStreamChanged() override EXCLUDES(mMutex); private: const wp mBLASTBufferQueue; - uint64_t mCurrentFrameNumber = 0; + uint64_t mCurrentFrameNumber GUARDED_BY(mMutex) = 0; Mutex mMutex; ConsumerFrameEventHistory mFrameEventHistory GUARDED_BY(mMutex); @@ -94,7 +94,7 @@ public: std::optional currentMaxAcquiredBufferCount); void releaseBufferCallbackLocked(const ReleaseCallbackId& id, const sp& releaseFence, std::optional currentMaxAcquiredBufferCount, - bool fakeRelease); + bool fakeRelease) REQUIRES(mMutex); void syncNextTransaction(std::function callback, bool acquireSingleBuffer = true); void stopContinuousSyncTransaction(); @@ -150,7 +150,7 @@ private: // mNumAcquired (buffers that queued to SF) mPendingRelease.size() (buffers that are held by // blast). This counter is read by android studio profiler. std::string mQueuedBufferTrace; - sp mSurfaceControl; + sp mSurfaceControl GUARDED_BY(mMutex); mutable std::mutex mMutex; std::condition_variable mCallbackCV; @@ -252,7 +252,7 @@ private: // callback for them. std::queue> mSurfaceControlsWithPendingCallback GUARDED_BY(mMutex); - uint32_t mCurrentMaxAcquiredBufferCount; + uint32_t mCurrentMaxAcquiredBufferCount GUARDED_BY(mMutex); // Flag to determine if syncTransaction should only acquire a single buffer and then clear or // continue to acquire buffers until explicitly cleared @@ -276,8 +276,8 @@ private: // need to set this flag, notably only in the case where we are transitioning from a previous // transaction applied by us (one way, may not yet have reached server) and an upcoming // transaction that will be applied by some sync consumer. - bool mAppliedLastTransaction = false; - uint64_t mLastAppliedFrameNumber = 0; + bool mAppliedLastTransaction GUARDED_BY(mMutex) = false; + uint64_t mLastAppliedFrameNumber GUARDED_BY(mMutex) = 0; std::function mTransactionHangCallback; diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 45f4dbe2be..e3470c59b2 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -921,7 +921,7 @@ public: void onTrustedPresentationChanged(int id, bool presentedWithinThresholds) override; private: - ReleaseBufferCallback popReleaseBufferCallbackLocked(const ReleaseCallbackId&); + ReleaseBufferCallback popReleaseBufferCallbackLocked(const ReleaseCallbackId&) REQUIRES(mMutex); static sp sInstance; }; -- GitLab From db16a2be4054d0dd7d940f4fa0a2ced0575fd2e7 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Mon, 6 Feb 2023 17:50:05 -0500 Subject: [PATCH 0818/1310] Revert "Create a VsyncSchedule per display" This reverts commit 31d41415101ff3483ce1cc5a9c2ef322490a05bd. Conflicts: services/surfaceflinger/Scheduler/EventThread.cpp services/surfaceflinger/SurfaceFlinger.cpp Bug: 267562341 Test: ARC Regression Dashboard Change-Id: I0757a7df540fad316b2db42e4c77f1c73bc49420 --- .../CompositionEngine/tests/MockHWComposer.h | 2 +- .../DisplayHardware/HWComposer.cpp | 22 +- .../DisplayHardware/HWComposer.h | 7 +- .../surfaceflinger/Scheduler/EventThread.cpp | 67 ++- .../surfaceflinger/Scheduler/EventThread.h | 31 +- .../Scheduler/ISchedulerCallback.h | 37 -- .../surfaceflinger/Scheduler/MessageQueue.cpp | 22 +- .../surfaceflinger/Scheduler/MessageQueue.h | 9 +- .../surfaceflinger/Scheduler/OneShotTimer.h | 4 +- .../surfaceflinger/Scheduler/Scheduler.cpp | 272 +++++------ services/surfaceflinger/Scheduler/Scheduler.h | 117 ++--- .../surfaceflinger/Scheduler/VSyncDispatch.h | 5 +- .../Scheduler/VSyncDispatchTimerQueue.cpp | 28 +- .../Scheduler/VSyncDispatchTimerQueue.h | 8 +- .../Scheduler/VSyncPredictor.cpp | 21 +- .../surfaceflinger/Scheduler/VSyncPredictor.h | 7 +- .../surfaceflinger/Scheduler/VSyncReactor.cpp | 26 +- .../surfaceflinger/Scheduler/VSyncReactor.h | 8 +- .../Scheduler/VsyncController.h | 3 +- .../Scheduler/VsyncSchedule.cpp | 80 +--- .../surfaceflinger/Scheduler/VsyncSchedule.h | 66 +-- services/surfaceflinger/SurfaceFlinger.cpp | 171 ++++--- services/surfaceflinger/SurfaceFlinger.h | 22 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 20 +- .../surfaceflinger_scheduler_fuzzer.cpp | 42 +- .../tests/unittests/CompositionTest.cpp | 2 +- .../unittests/DisplayTransactionTest.cpp | 4 +- .../unittests/DisplayTransactionTestHelpers.h | 2 + .../tests/unittests/EventThreadTest.cpp | 57 +-- .../tests/unittests/FpsReporterTest.cpp | 2 +- .../FrameRateSelectionPriorityTest.cpp | 2 +- .../tests/unittests/GameModeTest.cpp | 2 +- .../tests/unittests/HWComposerTest.cpp | 28 -- .../tests/unittests/LayerTestUtils.cpp | 2 +- .../tests/unittests/MessageQueueTest.cpp | 18 +- .../tests/unittests/SchedulerTest.cpp | 5 +- .../SurfaceFlinger_DisplayModeSwitching.cpp | 2 +- ...urfaceFlinger_OnInitializeDisplaysTest.cpp | 5 +- .../SurfaceFlinger_PowerHintTest.cpp | 2 +- ...urfaceFlinger_SetPowerModeInternalTest.cpp | 49 +- ...linger_UpdateLayerMetadataSnapshotTest.cpp | 2 +- .../tests/unittests/TestableScheduler.h | 40 +- .../tests/unittests/TestableSurfaceFlinger.h | 4 +- .../unittests/TransactionApplicationTest.cpp | 5 +- .../unittests/TransactionFrameTracerTest.cpp | 2 +- .../unittests/TransactionSurfaceFrameTest.cpp | 2 +- .../TunnelModeEnabledReporterTest.cpp | 2 +- .../unittests/VSyncDispatchRealtimeTest.cpp | 28 +- .../unittests/VSyncDispatchTimerQueueTest.cpp | 432 +++++++++--------- .../tests/unittests/VSyncPredictorTest.cpp | 5 +- .../tests/unittests/VSyncReactorTest.cpp | 45 +- .../tests/unittests/mock/MockEventThread.h | 42 +- .../unittests/mock/MockSchedulerCallback.h | 6 +- .../unittests/mock/MockVsyncController.h | 8 +- 54 files changed, 768 insertions(+), 1134 deletions(-) delete mode 100644 services/surfaceflinger/Scheduler/ISchedulerCallback.h diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h index 933f6168c9..6199a5ae40 100644 --- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h +++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h @@ -93,7 +93,7 @@ public: MOCK_METHOD2(onHotplug, std::optional(hal::HWDisplayId, hal::Connection)); MOCK_CONST_METHOD0(updatesDeviceProductInfoOnHotplugReconnect, bool()); - MOCK_METHOD(std::optional, onVsync, (hal::HWDisplayId, int64_t)); + MOCK_METHOD2(onVsync, bool(hal::HWDisplayId, int64_t)); MOCK_METHOD2(setVsyncEnabled, void(PhysicalDisplayId, hal::Vsync)); MOCK_CONST_METHOD1(isConnected, bool(PhysicalDisplayId)); MOCK_CONST_METHOD1(getModes, std::vector(PhysicalDisplayId)); diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp index 8e74716efa..7dde6b4e44 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -149,17 +148,16 @@ bool HWComposer::updatesDeviceProductInfoOnHotplugReconnect() const { return mUpdateDeviceProductInfoOnHotplugReconnect; } -std::optional HWComposer::onVsync(hal::HWDisplayId hwcDisplayId, - nsecs_t timestamp) { - const auto displayIdOpt = toPhysicalDisplayId(hwcDisplayId); - if (!displayIdOpt) { +bool HWComposer::onVsync(hal::HWDisplayId hwcDisplayId, nsecs_t timestamp) { + const auto displayId = toPhysicalDisplayId(hwcDisplayId); + if (!displayId) { LOG_HWC_DISPLAY_ERROR(hwcDisplayId, "Invalid HWC display"); - return {}; + return false; } - RETURN_IF_INVALID_DISPLAY(*displayIdOpt, {}); + RETURN_IF_INVALID_DISPLAY(*displayId, false); - auto& displayData = mDisplayData[*displayIdOpt]; + auto& displayData = mDisplayData[*displayId]; { // There have been reports of HWCs that signal several vsync events @@ -168,18 +166,18 @@ std::optional HWComposer::onVsync(hal::HWDisplayId hwcDisplay // out here so they don't cause havoc downstream. if (timestamp == displayData.lastPresentTimestamp) { ALOGW("Ignoring duplicate VSYNC event from HWC for display %s (t=%" PRId64 ")", - to_string(*displayIdOpt).c_str(), timestamp); - return {}; + to_string(*displayId).c_str(), timestamp); + return false; } displayData.lastPresentTimestamp = timestamp; } - const ftl::Concat tag("HW_VSYNC_", displayIdOpt->value); + const auto tag = "HW_VSYNC_" + to_string(*displayId); ATRACE_INT(tag.c_str(), displayData.vsyncTraceToggle); displayData.vsyncTraceToggle = !displayData.vsyncTraceToggle; - return displayIdOpt; + return true; } size_t HWComposer::getMaxVirtualDisplayCount() const { diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h index acebfb2362..f6155d24ad 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.h +++ b/services/surfaceflinger/DisplayHardware/HWComposer.h @@ -221,10 +221,7 @@ public: // TODO(b/157555476): Remove when the framework has proper support for headless mode virtual bool updatesDeviceProductInfoOnHotplugReconnect() const = 0; - // Called when a vsync happens. If the vsync is valid, returns the - // corresponding PhysicalDisplayId. Otherwise returns nullopt. - virtual std::optional onVsync(hal::HWDisplayId, nsecs_t timestamp) = 0; - + virtual bool onVsync(hal::HWDisplayId, nsecs_t timestamp) = 0; virtual void setVsyncEnabled(PhysicalDisplayId, hal::Vsync enabled) = 0; virtual bool isConnected(PhysicalDisplayId) const = 0; @@ -405,7 +402,7 @@ public: bool updatesDeviceProductInfoOnHotplugReconnect() const override; - std::optional onVsync(hal::HWDisplayId, nsecs_t timestamp) override; + bool onVsync(hal::HWDisplayId, nsecs_t timestamp) override; void setVsyncEnabled(PhysicalDisplayId, hal::Vsync enabled) override; bool isConnected(PhysicalDisplayId) const override; diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index 5e79a5c13e..eb6d7e4cfe 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -238,19 +238,29 @@ EventThread::~EventThread() = default; namespace impl { -EventThread::EventThread(const char* name, std::shared_ptr vsyncSchedule, - IEventThreadCallback& eventThreadCallback, +EventThread::EventThread(const char* name, scheduler::VsyncSchedule& vsyncSchedule, android::frametimeline::TokenManager* tokenManager, + ThrottleVsyncCallback throttleVsyncCallback, + GetVsyncPeriodFunction getVsyncPeriodFunction, std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration) : mThreadName(name), mVsyncTracer(base::StringPrintf("VSYNC-%s", name), 0), mWorkDuration(base::StringPrintf("VsyncWorkDuration-%s", name), workDuration), mReadyDuration(readyDuration), - mVsyncSchedule(std::move(vsyncSchedule)), - mVsyncRegistration(mVsyncSchedule->getDispatch(), createDispatchCallback(), name), + mVsyncSchedule(vsyncSchedule), + mVsyncRegistration( + vsyncSchedule.getDispatch(), + [this](nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) { + onVsync(vsyncTime, wakeupTime, readyTime); + }, + name), mTokenManager(tokenManager), - mEventThreadCallback(eventThreadCallback) { + mThrottleVsyncCallback(std::move(throttleVsyncCallback)), + mGetVsyncPeriodFunction(std::move(getVsyncPeriodFunction)) { + LOG_ALWAYS_FATAL_IF(getVsyncPeriodFunction == nullptr, + "getVsyncPeriodFunction must not be null"); + mThread = std::thread([this]() NO_THREAD_SAFETY_ANALYSIS { std::unique_lock lock(mMutex); threadMain(lock); @@ -361,16 +371,16 @@ VsyncEventData EventThread::getLatestVsyncEventData( } VsyncEventData vsyncEventData; - const Fps frameInterval = mEventThreadCallback.getLeaderRenderFrameRate(connection->mOwnerUid); - vsyncEventData.frameInterval = frameInterval.getPeriodNsecs(); + nsecs_t frameInterval = mGetVsyncPeriodFunction(connection->mOwnerUid); + vsyncEventData.frameInterval = frameInterval; const auto [presentTime, deadline] = [&]() -> std::pair { std::lock_guard lock(mMutex); - const auto vsyncTime = mVsyncSchedule->getTracker().nextAnticipatedVSyncTimeFrom( + const auto vsyncTime = mVsyncSchedule.getTracker().nextAnticipatedVSyncTimeFrom( systemTime() + mWorkDuration.get().count() + mReadyDuration.count()); return {vsyncTime, vsyncTime - mReadyDuration.count()}; }(); - generateFrameTimeline(vsyncEventData, frameInterval.getPeriodNsecs(), - systemTime(SYSTEM_TIME_MONOTONIC), presentTime, deadline); + generateFrameTimeline(vsyncEventData, frameInterval, systemTime(SYSTEM_TIME_MONOTONIC), + presentTime, deadline); return vsyncEventData; } @@ -533,15 +543,14 @@ bool EventThread::shouldConsumeEvent(const DisplayEventReceiver::Event& event, const auto throttleVsync = [&] { const auto& vsyncData = event.vsync.vsyncData; if (connection->frameRate.isValid()) { - return !mVsyncSchedule->getTracker() + return !mVsyncSchedule.getTracker() .isVSyncInPhase(vsyncData.preferredExpectedPresentationTime(), connection->frameRate); } - const auto expectedPresentTime = - TimePoint::fromNs(vsyncData.preferredExpectedPresentationTime()); - return !mEventThreadCallback.isVsyncTargetForUid(expectedPresentTime, - connection->mOwnerUid); + return mThrottleVsyncCallback && + mThrottleVsyncCallback(event.vsync.vsyncData.preferredExpectedPresentationTime(), + connection->mOwnerUid); }; switch (event.header.type) { @@ -629,11 +638,9 @@ void EventThread::dispatchEvent(const DisplayEventReceiver::Event& event, for (const auto& consumer : consumers) { DisplayEventReceiver::Event copy = event; if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) { - const Fps frameInterval = - mEventThreadCallback.getLeaderRenderFrameRate(consumer->mOwnerUid); - copy.vsync.vsyncData.frameInterval = frameInterval.getPeriodNsecs(); - generateFrameTimeline(copy.vsync.vsyncData, frameInterval.getPeriodNsecs(), - copy.header.timestamp, + const int64_t frameInterval = mGetVsyncPeriodFunction(consumer->mOwnerUid); + copy.vsync.vsyncData.frameInterval = frameInterval; + generateFrameTimeline(copy.vsync.vsyncData, frameInterval, copy.header.timestamp, event.vsync.vsyncData.preferredExpectedPresentationTime(), event.vsync.vsyncData.preferredDeadlineTimestamp()); } @@ -699,26 +706,6 @@ const char* EventThread::toCString(State state) { } } -void EventThread::onNewVsyncSchedule(std::shared_ptr schedule) { - std::lock_guard lock(mMutex); - const bool reschedule = mVsyncRegistration.cancel() == scheduler::CancelResult::Cancelled; - mVsyncSchedule = std::move(schedule); - mVsyncRegistration = - scheduler::VSyncCallbackRegistration(mVsyncSchedule->getDispatch(), - createDispatchCallback(), mThreadName); - if (reschedule) { - mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(), - .readyDuration = mReadyDuration.count(), - .earliestVsync = mLastVsyncCallbackTime.ns()}); - } -} - -scheduler::VSyncDispatch::Callback EventThread::createDispatchCallback() { - return [this](nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) { - onVsync(vsyncTime, wakeupTime, readyTime); - }; -} - } // namespace impl } // namespace android diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h index aa27091908..347dc4afd5 100644 --- a/services/surfaceflinger/Scheduler/EventThread.h +++ b/services/surfaceflinger/Scheduler/EventThread.h @@ -23,7 +23,6 @@ #include #include -#include #include #include #include @@ -68,15 +67,6 @@ enum class VSyncRequest { // Subsequent values are periods. }; -class IEventThreadCallback { -public: - virtual ~IEventThreadCallback() = default; - - virtual bool isVsyncTargetForUid(TimePoint expectedVsyncTime, uid_t uid) const = 0; - - virtual Fps getLeaderRenderFrameRate(uid_t uid) const = 0; -}; - class EventThreadConnection : public gui::BnDisplayEventConnection { public: EventThreadConnection(EventThread*, uid_t callingUid, ResyncCallback, @@ -146,17 +136,18 @@ public: // Retrieves the number of event connections tracked by this EventThread. virtual size_t getEventThreadConnectionCount() = 0; - - virtual void onNewVsyncSchedule(std::shared_ptr) = 0; }; namespace impl { class EventThread : public android::EventThread { public: - EventThread(const char* name, std::shared_ptr, IEventThreadCallback&, - frametimeline::TokenManager*, std::chrono::nanoseconds workDuration, - std::chrono::nanoseconds readyDuration); + using ThrottleVsyncCallback = std::function; + using GetVsyncPeriodFunction = std::function; + + EventThread(const char* name, scheduler::VsyncSchedule&, frametimeline::TokenManager*, + ThrottleVsyncCallback, GetVsyncPeriodFunction, + std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration); ~EventThread(); sp createEventConnection( @@ -188,8 +179,6 @@ public: size_t getEventThreadConnectionCount() override; - void onNewVsyncSchedule(std::shared_ptr) override; - private: friend EventThreadTest; @@ -213,19 +202,17 @@ private: nsecs_t timestamp, nsecs_t preferredExpectedPresentationTime, nsecs_t preferredDeadlineTimestamp) const; - scheduler::VSyncDispatch::Callback createDispatchCallback(); - const char* const mThreadName; TracedOrdinal mVsyncTracer; TracedOrdinal mWorkDuration GUARDED_BY(mMutex); std::chrono::nanoseconds mReadyDuration GUARDED_BY(mMutex); - std::shared_ptr mVsyncSchedule; + scheduler::VsyncSchedule& mVsyncSchedule; TimePoint mLastVsyncCallbackTime GUARDED_BY(mMutex) = TimePoint::now(); scheduler::VSyncCallbackRegistration mVsyncRegistration GUARDED_BY(mMutex); frametimeline::TokenManager* const mTokenManager; - // mEventThreadCallback will outlive the EventThread. - IEventThreadCallback& mEventThreadCallback; + const ThrottleVsyncCallback mThrottleVsyncCallback; + const GetVsyncPeriodFunction mGetVsyncPeriodFunction; std::thread mThread; mutable std::mutex mMutex; diff --git a/services/surfaceflinger/Scheduler/ISchedulerCallback.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h deleted file mode 100644 index 92c2189244..0000000000 --- a/services/surfaceflinger/Scheduler/ISchedulerCallback.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -#include - -#include "Display/DisplayModeRequest.h" - -namespace android::scheduler { - -struct ISchedulerCallback { - virtual void setVsyncEnabled(PhysicalDisplayId, bool) = 0; - virtual void requestDisplayModes(std::vector) = 0; - virtual void kernelTimerChanged(bool expired) = 0; - virtual void triggerOnFrameRateOverridesChanged() = 0; - -protected: - ~ISchedulerCallback() = default; -}; - -} // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp index 925f739534..dec8f59ee9 100644 --- a/services/surfaceflinger/Scheduler/MessageQueue.cpp +++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp @@ -75,37 +75,19 @@ void MessageQueue::vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, ns mHandler->dispatchFrame(vsyncId, expectedVsyncTime); } -void MessageQueue::initVsync(std::shared_ptr dispatch, +void MessageQueue::initVsync(scheduler::VSyncDispatch& dispatch, frametimeline::TokenManager& tokenManager, std::chrono::nanoseconds workDuration) { std::lock_guard lock(mVsync.mutex); mVsync.workDuration = workDuration; mVsync.tokenManager = &tokenManager; - updateVsyncRegistrationLocked(std::move(dispatch)); -} - -void MessageQueue::updateVsyncRegistration(std::shared_ptr dispatch) { - std::lock_guard lock(mVsync.mutex); - updateVsyncRegistrationLocked(std::move(dispatch)); -} - -void MessageQueue::updateVsyncRegistrationLocked( - std::shared_ptr dispatch) { - const bool reschedule = mVsync.registration && - mVsync.registration->cancel() == scheduler::CancelResult::Cancelled; mVsync.registration = std::make_unique< - scheduler::VSyncCallbackRegistration>(std::move(dispatch), + scheduler::VSyncCallbackRegistration>(dispatch, std::bind(&MessageQueue::vsyncCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), "sf"); - if (reschedule) { - mVsync.scheduledFrameTime = - mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(), - .readyDuration = 0, - .earliestVsync = mVsync.lastCallbackTime.ns()}); - } } void MessageQueue::destroyVsync() { diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h index ecb237d128..0d59337950 100644 --- a/services/surfaceflinger/Scheduler/MessageQueue.h +++ b/services/surfaceflinger/Scheduler/MessageQueue.h @@ -65,7 +65,7 @@ class MessageQueue { public: virtual ~MessageQueue() = default; - virtual void initVsync(std::shared_ptr, frametimeline::TokenManager&, + virtual void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&, std::chrono::nanoseconds workDuration) = 0; virtual void destroyVsync() = 0; virtual void setDuration(std::chrono::nanoseconds workDuration) = 0; @@ -106,8 +106,6 @@ protected: void vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, nsecs_t readyTime); - void updateVsyncRegistration(std::shared_ptr) EXCLUDES(mVsync.mutex); - private: virtual void onFrameSignal(ICompositor&, VsyncId, TimePoint expectedVsyncTime) = 0; @@ -129,13 +127,10 @@ private: Vsync mVsync; - void updateVsyncRegistrationLocked(std::shared_ptr) - REQUIRES(mVsync.mutex); - public: explicit MessageQueue(ICompositor&); - void initVsync(std::shared_ptr, frametimeline::TokenManager&, + void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&, std::chrono::nanoseconds workDuration) override; void destroyVsync() override; void setDuration(std::chrono::nanoseconds workDuration) override; diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.h b/services/surfaceflinger/Scheduler/OneShotTimer.h index 02e8719c08..f95646c48f 100644 --- a/services/surfaceflinger/Scheduler/OneShotTimer.h +++ b/services/surfaceflinger/Scheduler/OneShotTimer.h @@ -40,7 +40,7 @@ public: OneShotTimer(std::string name, const Interval& interval, const ResetCallback& resetCallback, const TimeoutCallback& timeoutCallback, - std::unique_ptr clock = std::make_unique()); + std::unique_ptr clock = std::make_unique()); ~OneShotTimer(); Duration interval() const { return mInterval; } @@ -82,7 +82,7 @@ private: std::thread mThread; // Clock object for the timer. Mocked in unit tests. - std::unique_ptr mClock; + std::unique_ptr mClock; // Semaphore to keep mThread synchronized. sem_t mSemaphore; diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index eed57ef4f1..c314b5c215 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -114,18 +114,10 @@ void Scheduler::setLeaderDisplay(std::optional leaderIdOpt) { } void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) { - registerDisplayInternal(displayId, std::move(selectorPtr), - std::make_shared(displayId, mFeatures)); -} - -void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId, - RefreshRateSelectorPtr selectorPtr, - std::shared_ptr vsyncSchedule) { demoteLeaderDisplay(); std::scoped_lock lock(mDisplayLock); mRefreshRateSelectors.emplace_or_replace(displayId, std::move(selectorPtr)); - mVsyncSchedules.emplace_or_replace(displayId, std::move(vsyncSchedule)); promoteLeaderDisplay(); } @@ -135,7 +127,6 @@ void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { std::scoped_lock lock(mDisplayLock); mRefreshRateSelectors.erase(displayId); - mVsyncSchedules.erase(displayId); // Do not allow removing the final display. Code in the scheduler expects // there to be at least one display. (This may be relaxed in the future with @@ -163,49 +154,52 @@ void Scheduler::onFrameSignal(ICompositor& compositor, VsyncId vsyncId, compositor.sample(); } -std::optional Scheduler::getFrameRateOverride(uid_t uid) const { - std::scoped_lock lock(mDisplayLock); - return getFrameRateOverrideLocked(uid); +void Scheduler::createVsyncSchedule(FeatureFlags features) { + mVsyncSchedule.emplace(features); } -std::optional Scheduler::getFrameRateOverrideLocked(uid_t uid) const { +std::optional Scheduler::getFrameRateOverride(uid_t uid) const { const bool supportsFrameRateOverrideByContent = - leaderSelectorPtrLocked()->supportsAppFrameRateOverrideByContent(); + leaderSelectorPtr()->supportsAppFrameRateOverrideByContent(); return mFrameRateOverrideMappings .getFrameRateOverrideForUid(uid, supportsFrameRateOverrideByContent); } -bool Scheduler::isVsyncTargetForUid(TimePoint expectedVsyncTime, uid_t uid) const { +bool Scheduler::isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const { const auto frameRate = getFrameRateOverride(uid); if (!frameRate.has_value()) { return true; } - return isVsyncInPhase(expectedVsyncTime, *frameRate); + return mVsyncSchedule->getTracker().isVSyncInPhase(expectedVsyncTimestamp.ns(), *frameRate); } -bool Scheduler::isVsyncInPhase(TimePoint expectedVsyncTime, Fps frameRate) const { - return getVsyncSchedule()->getTracker().isVSyncInPhase(expectedVsyncTime.ns(), frameRate); +bool Scheduler::isVsyncInPhase(TimePoint timePoint, const Fps frameRate) const { + return mVsyncSchedule->getTracker().isVSyncInPhase(timePoint.ns(), frameRate); } -Fps Scheduler::getLeaderRenderFrameRate(uid_t uid) const { - std::scoped_lock lock(mDisplayLock); - ftl::FakeGuard guard(kMainThreadContext); - auto vsyncSchedule = getVsyncScheduleLocked(); +impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() const { + return [this](nsecs_t expectedVsyncTimestamp, uid_t uid) { + return !isVsyncValid(TimePoint::fromNs(expectedVsyncTimestamp), uid); + }; +} - const Fps refreshRate = leaderSelectorPtrLocked()->getActiveMode().fps; - const nsecs_t currentPeriod = vsyncSchedule->period().ns() ?: refreshRate.getPeriodNsecs(); +impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const { + return [this](uid_t uid) { + const Fps refreshRate = leaderSelectorPtr()->getActiveMode().fps; + const nsecs_t currentPeriod = mVsyncSchedule->period().ns() ?: refreshRate.getPeriodNsecs(); - const auto frameRate = getFrameRateOverrideLocked(uid); - if (!frameRate.has_value()) { - return Fps::fromPeriodNsecs(currentPeriod); - } + const auto frameRate = getFrameRateOverride(uid); + if (!frameRate.has_value()) { + return currentPeriod; + } - const auto divisor = RefreshRateSelector::getFrameRateDivisor(refreshRate, *frameRate); - if (divisor <= 1) { - return Fps::fromPeriodNsecs(currentPeriod); - } - return Fps::fromPeriodNsecs(currentPeriod * divisor); + const auto divisor = RefreshRateSelector::getFrameRateDivisor(refreshRate, *frameRate); + if (divisor <= 1) { + return currentPeriod; + } + return currentPeriod * divisor; + }; } ConnectionHandle Scheduler::createEventThread(Cycle cycle, @@ -213,7 +207,9 @@ ConnectionHandle Scheduler::createEventThread(Cycle cycle, std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration) { auto eventThread = std::make_unique(cycle == Cycle::Render ? "app" : "appSf", - getVsyncSchedule(), *this, tokenManager, + *mVsyncSchedule, tokenManager, + makeThrottleVsyncCallback(), + makeGetVsyncPeriodFunction(), workDuration, readyDuration); auto& handle = cycle == Cycle::Render ? mAppConnectionHandle : mSfConnectionHandle; @@ -276,6 +272,7 @@ void Scheduler::onScreenAcquired(ConnectionHandle handle) { thread = mConnections[handle].thread.get(); } thread->onScreenAcquired(); + mScreenAcquired = true; } void Scheduler::onScreenReleased(ConnectionHandle handle) { @@ -286,6 +283,7 @@ void Scheduler::onScreenReleased(ConnectionHandle handle) { thread = mConnections[handle].thread.get(); } thread->onScreenReleased(); + mScreenAcquired = false; } void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId) { @@ -396,57 +394,48 @@ void Scheduler::setVsyncConfig(const VsyncConfig& config, Period vsyncPeriod) { setDuration(config.sfWorkDuration); } -void Scheduler::enableHardwareVsync(PhysicalDisplayId id) { - auto schedule = getVsyncSchedule(id); - schedule->enableHardwareVsync(mSchedulerCallback); -} - -void Scheduler::disableHardwareVsync(PhysicalDisplayId id, bool disallow) { - auto schedule = getVsyncSchedule(id); - schedule->disableHardwareVsync(mSchedulerCallback, disallow); +void Scheduler::enableHardwareVsync() { + std::lock_guard lock(mHWVsyncLock); + if (!mPrimaryHWVsyncEnabled && mHWVsyncAvailable) { + mVsyncSchedule->getTracker().resetModel(); + mSchedulerCallback.setVsyncEnabled(true); + mPrimaryHWVsyncEnabled = true; + } } -void Scheduler::resyncAllToHardwareVsync(bool allowToEnable) { - std::scoped_lock lock(mDisplayLock); - ftl::FakeGuard guard(kMainThreadContext); - - for (const auto& [id, _] : mRefreshRateSelectors) { - resyncToHardwareVsyncLocked(id, allowToEnable); +void Scheduler::disableHardwareVsync(bool makeUnavailable) { + std::lock_guard lock(mHWVsyncLock); + if (mPrimaryHWVsyncEnabled) { + mSchedulerCallback.setVsyncEnabled(false); + mPrimaryHWVsyncEnabled = false; + } + if (makeUnavailable) { + mHWVsyncAvailable = false; } } -void Scheduler::resyncToHardwareVsyncLocked(PhysicalDisplayId id, bool allowToEnable, - std::optional refreshRate) { - if (!refreshRate) { - auto selectorPtr = mRefreshRateSelectors.get(id); - LOG_ALWAYS_FATAL_IF(!selectorPtr); - refreshRate = selectorPtr->get()->getActiveMode().modePtr->getFps(); - } - auto schedule = getVsyncScheduleLocked(id); - if (allowToEnable) { - schedule->allowHardwareVsync(); - } else if (!schedule->isHardwareVsyncAllowed()) { - // Hardware vsync is not currently allowed, so abort the resync - // attempt for now. - return; +void Scheduler::resyncToHardwareVsync(bool makeAvailable, Fps refreshRate) { + { + std::lock_guard lock(mHWVsyncLock); + if (makeAvailable) { + mHWVsyncAvailable = makeAvailable; + } else if (!mHWVsyncAvailable) { + // Hardware vsync is not currently available, so abort the resync + // attempt for now + return; + } } - setVsyncPeriod(schedule, refreshRate->getPeriodNsecs(), false /* force */); + setVsyncPeriod(refreshRate.getPeriodNsecs()); } -void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate) { - std::scoped_lock lock(mDisplayLock); - ftl::FakeGuard guard(kMainThreadContext); - - auto selectorPtr = mRefreshRateSelectors.get(id); - LOG_ALWAYS_FATAL_IF(!selectorPtr); - const auto mode = selectorPtr->get()->getActiveMode(); +void Scheduler::setRenderRate(Fps renderFrameRate) { + const auto mode = leaderSelectorPtr()->getActiveMode(); using fps_approx_ops::operator!=; LOG_ALWAYS_FATAL_IF(renderFrameRate != mode.fps, - "Mismatch in render frame rates. Selector: %s, Scheduler: %s, Display: " - "%" PRIu64, - to_string(mode.fps).c_str(), to_string(renderFrameRate).c_str(), id.value); + "Mismatch in render frame rates. Selector: %s, Scheduler: %s", + to_string(mode.fps).c_str(), to_string(renderFrameRate).c_str()); ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(), to_string(mode.modePtr->getFps()).c_str()); @@ -455,7 +444,7 @@ void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate) { LOG_ALWAYS_FATAL_IF(divisor == 0, "%s <> %s -- not divisors", to_string(mode.fps).c_str(), to_string(mode.fps).c_str()); - getVsyncScheduleLocked(id)->getTracker().setDivisor(static_cast(divisor)); + mVsyncSchedule->getTracker().setDivisor(static_cast(divisor)); } void Scheduler::resync() { @@ -465,43 +454,49 @@ void Scheduler::resync() { const nsecs_t last = mLastResyncTime.exchange(now); if (now - last > kIgnoreDelay) { - resyncAllToHardwareVsync(false /* allowToEnable */); + const auto refreshRate = leaderSelectorPtr()->getActiveMode().modePtr->getFps(); + resyncToHardwareVsync(false, refreshRate); } } -void Scheduler::setVsyncPeriod(const std::shared_ptr& schedule, nsecs_t period, - bool force) { - ALOGD("Scheduler::setVsyncPeriod"); +void Scheduler::setVsyncPeriod(nsecs_t period) { if (period <= 0) return; - // TODO (b/266712910):The old code held mHWVsyncLock before calling - // startPeriodTransition. Move these into a new method on VsyncSchedule that - // encapsulates this behavior there and allows holding the lock the whole - // time. - schedule->getController().startPeriodTransition(period, force); - schedule->enableHardwareVsync(mSchedulerCallback); + std::lock_guard lock(mHWVsyncLock); + mVsyncSchedule->getController().startPeriodTransition(period); + + if (!mPrimaryHWVsyncEnabled) { + mVsyncSchedule->getTracker().resetModel(); + mSchedulerCallback.setVsyncEnabled(true); + mPrimaryHWVsyncEnabled = true; + } } -bool Scheduler::addResyncSample(PhysicalDisplayId id, nsecs_t timestamp, - std::optional hwcVsyncPeriod) { - bool periodFlushed = false; - auto schedule = getVsyncSchedule(id); - if (schedule->getController().addHwVsyncTimestamp(timestamp, hwcVsyncPeriod, &periodFlushed)) { - schedule->enableHardwareVsync(mSchedulerCallback); - } else { - schedule->disableHardwareVsync(mSchedulerCallback, false /* disallow */); +void Scheduler::addResyncSample(nsecs_t timestamp, std::optional hwcVsyncPeriod, + bool* periodFlushed) { + bool needsHwVsync = false; + *periodFlushed = false; + { // Scope for the lock + std::lock_guard lock(mHWVsyncLock); + if (mPrimaryHWVsyncEnabled) { + needsHwVsync = + mVsyncSchedule->getController().addHwVsyncTimestamp(timestamp, hwcVsyncPeriod, + periodFlushed); + } } - return periodFlushed; + if (needsHwVsync) { + enableHardwareVsync(); + } else { + disableHardwareVsync(false); + } } -void Scheduler::addPresentFence(PhysicalDisplayId id, std::shared_ptr fence) { - auto schedule = getVsyncSchedule(id); - const bool needMoreSignals = schedule->getController().addPresentFence(std::move(fence)); - if (needMoreSignals) { - schedule->enableHardwareVsync(mSchedulerCallback); +void Scheduler::addPresentFence(std::shared_ptr fence) { + if (mVsyncSchedule->getController().addPresentFence(std::move(fence))) { + enableHardwareVsync(); } else { - schedule->disableHardwareVsync(mSchedulerCallback, false /* disallow */); + disableHardwareVsync(false); } } @@ -553,22 +548,12 @@ void Scheduler::onTouchHint() { } } -void Scheduler::setDisplayPowerMode(PhysicalDisplayId id, hal::PowerMode powerMode) { - const bool isLeader = [this, id]() REQUIRES(kMainThreadContext) { - ftl::FakeGuard guard(mDisplayLock); - return id == mLeaderDisplayId; - }(); - if (isLeader) { - // TODO (b/255657128): This needs to be handled per display. +void Scheduler::setDisplayPowerMode(hal::PowerMode powerMode) { + { std::lock_guard lock(mPolicyLock); mPolicy.displayPowerMode = powerMode; } - { - std::scoped_lock lock(mDisplayLock); - auto vsyncSchedule = getVsyncScheduleLocked(id); - vsyncSchedule->getController().setDisplayPowerMode(powerMode); - } - if (!isLeader) return; + mVsyncSchedule->getController().setDisplayPowerMode(powerMode); if (mDisplayPowerTimer) { mDisplayPowerTimer->reset(); @@ -579,24 +564,6 @@ void Scheduler::setDisplayPowerMode(PhysicalDisplayId id, hal::PowerMode powerMo mLayerHistory.clear(); } -std::shared_ptr Scheduler::getVsyncSchedule( - std::optional idOpt) const { - std::scoped_lock lock(mDisplayLock); - return getVsyncScheduleLocked(idOpt); -} - -std::shared_ptr Scheduler::getVsyncScheduleLocked( - std::optional idOpt) const { - ftl::FakeGuard guard(kMainThreadContext); - if (!idOpt) { - LOG_ALWAYS_FATAL_IF(!mLeaderDisplayId, "Missing a leader!"); - idOpt = mLeaderDisplayId; - } - auto scheduleOpt = mVsyncSchedules.get(*idOpt); - LOG_ALWAYS_FATAL_IF(!scheduleOpt); - return std::const_pointer_cast(scheduleOpt->get()); -} - void Scheduler::kernelIdleTimerCallback(TimerState state) { ATRACE_INT("ExpiredKernelIdleTimer", static_cast(state)); @@ -611,17 +578,12 @@ void Scheduler::kernelIdleTimerCallback(TimerState state) { // If we're not in performance mode then the kernel timer shouldn't do // anything, as the refresh rate during DPU power collapse will be the // same. - resyncAllToHardwareVsync(true /* allowToEnable */); + resyncToHardwareVsync(true /* makeAvailable */, refreshRate); } else if (state == TimerState::Expired && refreshRate <= FPS_THRESHOLD_FOR_KERNEL_TIMER) { // Disable HW VSYNC if the timer expired, as we don't need it enabled if // we're not pushing frames, and if we're in PERFORMANCE mode then we'll // need to update the VsyncController model anyway. - std::scoped_lock lock(mDisplayLock); - ftl::FakeGuard guard(kMainThreadContext); - constexpr bool disallow = false; - for (auto& [_, schedule] : mVsyncSchedules) { - schedule->disableHardwareVsync(mSchedulerCallback, disallow); - } + disableHardwareVsync(false /* makeUnavailable */); } mSchedulerCallback.kernelTimerChanged(state == TimerState::Expired); @@ -675,23 +637,19 @@ void Scheduler::dump(utils::Dumper& dumper) const { mFrameRateOverrideMappings.dump(dumper); dumper.eol(); + + { + utils::Dumper::Section section(dumper, "Hardware VSYNC"sv); + + std::lock_guard lock(mHWVsyncLock); + dumper.dump("screenAcquired"sv, mScreenAcquired.load()); + dumper.dump("hwVsyncAvailable"sv, mHWVsyncAvailable); + dumper.dump("hwVsyncEnabled"sv, mPrimaryHWVsyncEnabled); + } } void Scheduler::dumpVsync(std::string& out) const { - std::scoped_lock lock(mDisplayLock); - ftl::FakeGuard guard(kMainThreadContext); - if (mLeaderDisplayId) { - base::StringAppendF(&out, "VsyncSchedule for leader %s:\n", - to_string(*mLeaderDisplayId).c_str()); - getVsyncScheduleLocked()->dump(out); - } - for (auto& [id, vsyncSchedule] : mVsyncSchedules) { - if (id == mLeaderDisplayId) { - continue; - } - base::StringAppendF(&out, "VsyncSchedule for follower %s:\n", to_string(id).c_str()); - vsyncSchedule->dump(out); - } + mVsyncSchedule->dump(out); } bool Scheduler::updateFrameRateOverrides(GlobalSignals consideredSignals, Fps displayRefreshRate) { @@ -711,7 +669,6 @@ void Scheduler::promoteLeaderDisplay(std::optional leaderIdOp mLeaderDisplayId = leaderIdOpt.value_or(mRefreshRateSelectors.begin()->first); ALOGI("Display %s is the leader", to_string(*mLeaderDisplayId).c_str()); - auto vsyncSchedule = getVsyncScheduleLocked(*mLeaderDisplayId); if (const auto leaderPtr = leaderSelectorPtrLocked()) { leaderPtr->setIdleTimerCallbacks( {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); }, @@ -721,17 +678,6 @@ void Scheduler::promoteLeaderDisplay(std::optional leaderIdOp [this] { kernelIdleTimerCallback(TimerState::Expired); }}}); leaderPtr->startIdleTimer(); - - const Fps refreshRate = leaderPtr->getActiveMode().modePtr->getFps(); - setVsyncPeriod(vsyncSchedule, refreshRate.getPeriodNsecs(), true /* force */); - } - - updateVsyncRegistration(vsyncSchedule->getDispatch()); - { - std::lock_guard lock(mConnectionsLock); - for (auto& [_, connection] : mConnections) { - connection.thread->onNewVsyncSchedule(vsyncSchedule); - } } } diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 8c8fc21e09..796c785088 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -43,7 +43,6 @@ #include "Display/DisplayModeRequest.h" #include "EventThread.h" #include "FrameRateOverrideMappings.h" -#include "ISchedulerCallback.h" #include "LayerHistory.h" #include "MessageQueue.h" #include "OneShotTimer.h" @@ -93,7 +92,17 @@ namespace scheduler { using GlobalSignals = RefreshRateSelector::GlobalSignals; -class Scheduler : android::impl::MessageQueue, public IEventThreadCallback { +struct ISchedulerCallback { + virtual void setVsyncEnabled(bool) = 0; + virtual void requestDisplayModes(std::vector) = 0; + virtual void kernelTimerChanged(bool expired) = 0; + virtual void triggerOnFrameRateOverridesChanged() = 0; + +protected: + ~ISchedulerCallback() = default; +}; + +class Scheduler : android::impl::MessageQueue { using Impl = android::impl::MessageQueue; public: @@ -114,6 +123,8 @@ public: void run(); + void createVsyncSchedule(FeatureFlags); + using Impl::initVsync; using Impl::getScheduledFrameTime; @@ -167,21 +178,9 @@ public: const VsyncModulator& vsyncModulator() const { return *mVsyncModulator; } - // In some cases, we should only modulate for the leader display. In those - // cases, the caller should pass in the relevant display, and the method - // will no-op if it's not the leader. Other cases are not specific to a - // display. template (VsyncModulator::*)(Args...)> - void modulateVsync(std::optional id, Handler handler, Args... args) { - if (id) { - std::scoped_lock lock(mDisplayLock); - ftl::FakeGuard guard(kMainThreadContext); - if (id != mLeaderDisplayId) { - return; - } - } - + void modulateVsync(Handler handler, Args... args) { if (const auto config = (*mVsyncModulator.*handler)(args...)) { setVsyncConfig(*config, getLeaderVsyncPeriod()); } @@ -190,32 +189,24 @@ public: void setVsyncConfigSet(const VsyncConfigSet&, Period vsyncPeriod); // Sets the render rate for the scheduler to run at. - void setRenderRate(PhysicalDisplayId, Fps); + void setRenderRate(Fps); - void enableHardwareVsync(PhysicalDisplayId); - void disableHardwareVsync(PhysicalDisplayId, bool makeUnavailable); + void enableHardwareVsync(); + void disableHardwareVsync(bool makeUnavailable); // Resyncs the scheduler to hardware vsync. - // If allowToEnable is true, then hardware vsync will be turned on. + // If makeAvailable is true, then hardware vsync will be turned on. // Otherwise, if hardware vsync is not already enabled then this method will // no-op. - // If refreshRate is nullopt, use the existing refresh rate of the display. - void resyncToHardwareVsync(PhysicalDisplayId id, bool allowToEnable, - std::optional refreshRate = std::nullopt) - EXCLUDES(mDisplayLock) { - std::scoped_lock lock(mDisplayLock); - ftl::FakeGuard guard(kMainThreadContext); - resyncToHardwareVsyncLocked(id, allowToEnable, refreshRate); - } + void resyncToHardwareVsync(bool makeAvailable, Fps refreshRate); void resync() EXCLUDES(mDisplayLock); void forceNextResync() { mLastResyncTime = 0; } - // Passes a vsync sample to VsyncController. Returns true if - // VsyncController detected that the vsync period changed and false - // otherwise. - bool addResyncSample(PhysicalDisplayId, nsecs_t timestamp, - std::optional hwcVsyncPeriod); - void addPresentFence(PhysicalDisplayId, std::shared_ptr) EXCLUDES(mDisplayLock); + // Passes a vsync sample to VsyncController. periodFlushed will be true if + // VsyncController detected that the vsync period changed, and false otherwise. + void addResyncSample(nsecs_t timestamp, std::optional hwcVsyncPeriod, + bool* periodFlushed); + void addPresentFence(std::shared_ptr); // Layers are registered on creation, and unregistered when the weak reference expires. void registerLayer(Layer*); @@ -233,22 +224,20 @@ public: // Indicates that touch interaction is taking place. void onTouchHint(); - void setDisplayPowerMode(PhysicalDisplayId, hal::PowerMode powerMode) - REQUIRES(kMainThreadContext); + void setDisplayPowerMode(hal::PowerMode powerMode); - std::shared_ptr getVsyncSchedule( - std::optional idOpt = std::nullopt) const EXCLUDES(mDisplayLock); - std::shared_ptr getVsyncSchedule( - std::optional idOpt = std::nullopt) EXCLUDES(mDisplayLock) { - return std::const_pointer_cast( - static_cast(this)->getVsyncSchedule(idOpt)); - } + VsyncSchedule& getVsyncSchedule() { return *mVsyncSchedule; } + + // Returns true if a given vsync timestamp is considered valid vsync + // for a given uid + bool isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const; - bool isVsyncInPhase(TimePoint expectedVsyncTime, Fps frameRate) const; + // Checks if a vsync timestamp is in phase for a frame rate + bool isVsyncInPhase(TimePoint timePoint, const Fps frameRate) const; void dump(utils::Dumper&) const; void dump(ConnectionHandle, std::string&) const; - void dumpVsync(std::string&) const EXCLUDES(mDisplayLock); + void dumpVsync(std::string&) const; // Returns the preferred refresh rate and frame rate for the leader display. FrameRateMode getPreferredDisplayMode(); @@ -286,10 +275,6 @@ public: return mLayerHistory.getLayerFramerate(now, id); } - // IEventThreadCallback overrides: - bool isVsyncTargetForUid(TimePoint expectedVsyncTime, uid_t uid) const override; - Fps getLeaderRenderFrameRate(uid_t uid) const override; - private: friend class TestableScheduler; @@ -312,12 +297,7 @@ private: void touchTimerCallback(TimerState); void displayPowerTimerCallback(TimerState); - void resyncToHardwareVsyncLocked(PhysicalDisplayId, bool allowToEnable, - std::optional refreshRate = std::nullopt) - REQUIRES(kMainThreadContext, mDisplayLock); - void resyncAllToHardwareVsync(bool allowToEnable) EXCLUDES(mDisplayLock); - void setVsyncPeriod(const std::shared_ptr&, nsecs_t period, bool force) - REQUIRES(mDisplayLock); + void setVsyncPeriod(nsecs_t period); void setVsyncConfig(const VsyncConfig&, Period vsyncPeriod); // Chooses a leader among the registered displays, unless `leaderIdOpt` is specified. The new @@ -329,12 +309,6 @@ private: // caller on the main thread to avoid deadlock, since the timer thread locks it before exit. void demoteLeaderDisplay() REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock, mPolicyLock); - void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr, - std::shared_ptr) REQUIRES(kMainThreadContext) - EXCLUDES(mDisplayLock); - - std::optional getFrameRateOverrideLocked(uid_t) const REQUIRES(mDisplayLock); - struct Policy; // Sets the S state of the policy to the T value under mPolicyLock, and chooses a display mode @@ -372,6 +346,9 @@ private: void dispatchCachedReportedMode() REQUIRES(mPolicyLock) EXCLUDES(mDisplayLock); + android::impl::EventThread::ThrottleVsyncCallback makeThrottleVsyncCallback() const; + android::impl::EventThread::GetVsyncPeriodFunction makeGetVsyncPeriodFunction() const; + // Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection. struct Connection { sp connection; @@ -385,9 +362,14 @@ private: ConnectionHandle mAppConnectionHandle; ConnectionHandle mSfConnectionHandle; + mutable std::mutex mHWVsyncLock; + bool mPrimaryHWVsyncEnabled GUARDED_BY(mHWVsyncLock) = false; + bool mHWVsyncAvailable GUARDED_BY(mHWVsyncLock) = false; + std::atomic mLastResyncTime = 0; const FeatureFlags mFeatures; + std::optional mVsyncSchedule; // Shifts the VSYNC phase during certain transactions and refresh rate changes. const sp mVsyncModulator; @@ -412,10 +394,6 @@ private: display::PhysicalDisplayMap mRefreshRateSelectors GUARDED_BY(mDisplayLock) GUARDED_BY(kMainThreadContext); - // TODO (b/266715559): Store in the same map as mRefreshRateSelectors. - display::PhysicalDisplayMap> mVsyncSchedules - GUARDED_BY(mDisplayLock) GUARDED_BY(kMainThreadContext); - ftl::Optional mLeaderDisplayId GUARDED_BY(mDisplayLock) GUARDED_BY(kMainThreadContext); @@ -435,14 +413,6 @@ private: .value_or(std::cref(noLeader)); } - std::shared_ptr getVsyncScheduleLocked( - std::optional idOpt = std::nullopt) const REQUIRES(mDisplayLock); - std::shared_ptr getVsyncScheduleLocked( - std::optional idOpt = std::nullopt) REQUIRES(mDisplayLock) { - return std::const_pointer_cast( - static_cast(this)->getVsyncScheduleLocked(idOpt)); - } - struct Policy { // Policy for choosing the display mode. LayerHistory::Summary contentRequirements; @@ -469,6 +439,9 @@ private: static constexpr std::chrono::nanoseconds MAX_VSYNC_APPLIED_TIME = 200ms; FrameRateOverrideMappings mFrameRateOverrideMappings; + + // Keeps track of whether the screen is acquired for debug + std::atomic mScreenAcquired = false; }; } // namespace scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncDispatch.h b/services/surfaceflinger/Scheduler/VSyncDispatch.h index 77875e3b4d..95201314a4 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatch.h +++ b/services/surfaceflinger/Scheduler/VSyncDispatch.h @@ -161,8 +161,7 @@ protected: */ class VSyncCallbackRegistration { public: - VSyncCallbackRegistration(std::shared_ptr, VSyncDispatch::Callback, - std::string callbackName); + VSyncCallbackRegistration(VSyncDispatch&, VSyncDispatch::Callback, std::string callbackName); ~VSyncCallbackRegistration(); VSyncCallbackRegistration(VSyncCallbackRegistration&&); @@ -178,7 +177,7 @@ public: CancelResult cancel(); private: - std::shared_ptr mDispatch; + std::reference_wrapper mDispatch; VSyncDispatch::CallbackToken mToken; bool mValidToken; }; diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp index 26389eb8cc..73d52cf986 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp @@ -215,10 +215,10 @@ void VSyncDispatchTimerQueueEntry::dump(std::string& result) const { } VSyncDispatchTimerQueue::VSyncDispatchTimerQueue(std::unique_ptr tk, - VsyncSchedule::TrackerPtr tracker, - nsecs_t timerSlack, nsecs_t minVsyncDistance) + VSyncTracker& tracker, nsecs_t timerSlack, + nsecs_t minVsyncDistance) : mTimeKeeper(std::move(tk)), - mTracker(std::move(tracker)), + mTracker(tracker), mTimerSlack(timerSlack), mMinVsyncDistance(minVsyncDistance) {} @@ -255,7 +255,7 @@ void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( } if (it != skipUpdateIt) { - callback->update(*mTracker, now); + callback->update(mTracker, now); } auto const wakeupTime = *callback->wakeupTime(); if (!min || *min > wakeupTime) { @@ -365,10 +365,10 @@ ScheduleResult VSyncDispatchTimerQueue::scheduleLocked(CallbackToken token, auto const rearmImminent = now > mIntendedWakeupTime; if (CC_UNLIKELY(rearmImminent)) { callback->addPendingWorkloadUpdate(scheduleTiming); - return getExpectedCallbackTime(*mTracker, now, scheduleTiming); + return getExpectedCallbackTime(mTracker, now, scheduleTiming); } - const ScheduleResult result = callback->schedule(scheduleTiming, *mTracker, now); + const ScheduleResult result = callback->schedule(scheduleTiming, mTracker, now); if (!result.has_value()) { return {}; } @@ -434,15 +434,15 @@ void VSyncDispatchTimerQueue::dump(std::string& result) const { } } -VSyncCallbackRegistration::VSyncCallbackRegistration(std::shared_ptr dispatch, +VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncDispatch& dispatch, VSyncDispatch::Callback callback, std::string callbackName) - : mDispatch(std::move(dispatch)), - mToken(mDispatch->registerCallback(std::move(callback), std::move(callbackName))), + : mDispatch(dispatch), + mToken(dispatch.registerCallback(std::move(callback), std::move(callbackName))), mValidToken(true) {} VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncCallbackRegistration&& other) - : mDispatch(std::move(other.mDispatch)), + : mDispatch(other.mDispatch), mToken(std::move(other.mToken)), mValidToken(std::move(other.mValidToken)) { other.mValidToken = false; @@ -457,28 +457,28 @@ VSyncCallbackRegistration& VSyncCallbackRegistration::operator=(VSyncCallbackReg } VSyncCallbackRegistration::~VSyncCallbackRegistration() { - if (mValidToken) mDispatch->unregisterCallback(mToken); + if (mValidToken) mDispatch.get().unregisterCallback(mToken); } ScheduleResult VSyncCallbackRegistration::schedule(VSyncDispatch::ScheduleTiming scheduleTiming) { if (!mValidToken) { return std::nullopt; } - return mDispatch->schedule(mToken, scheduleTiming); + return mDispatch.get().schedule(mToken, scheduleTiming); } ScheduleResult VSyncCallbackRegistration::update(VSyncDispatch::ScheduleTiming scheduleTiming) { if (!mValidToken) { return std::nullopt; } - return mDispatch->update(mToken, scheduleTiming); + return mDispatch.get().update(mToken, scheduleTiming); } CancelResult VSyncCallbackRegistration::cancel() { if (!mValidToken) { return CancelResult::Error; } - return mDispatch->cancel(mToken); + return mDispatch.get().cancel(mToken); } } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h index 6499d69969..c3af136d66 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h @@ -26,11 +26,11 @@ #include #include "VSyncDispatch.h" -#include "VsyncSchedule.h" namespace android::scheduler { class TimeKeeper; +class VSyncTracker; // VSyncDispatchTimerQueueEntry is a helper class representing internal state for each entry in // VSyncDispatchTimerQueue hoisted to public for unit testing. @@ -120,8 +120,8 @@ public: // should be grouped into one wakeup. // \param[in] minVsyncDistance The minimum distance between two vsync estimates before the // vsyncs are considered the same vsync event. - VSyncDispatchTimerQueue(std::unique_ptr, VsyncSchedule::TrackerPtr, - nsecs_t timerSlack, nsecs_t minVsyncDistance); + VSyncDispatchTimerQueue(std::unique_ptr, VSyncTracker&, nsecs_t timerSlack, + nsecs_t minVsyncDistance); ~VSyncDispatchTimerQueue(); CallbackToken registerCallback(Callback, std::string callbackName) final; @@ -148,7 +148,7 @@ private: static constexpr nsecs_t kInvalidTime = std::numeric_limits::max(); std::unique_ptr const mTimeKeeper; - VsyncSchedule::TrackerPtr mTracker; + VSyncTracker& mTracker; nsecs_t const mTimerSlack; nsecs_t const mMinVsyncDistance; diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index a3b8a5619d..02e12fd942 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -31,7 +31,6 @@ #include #include #include -#include #include #include @@ -41,16 +40,14 @@ namespace android::scheduler { using base::StringAppendF; -using base::StringPrintf; static auto constexpr kMaxPercent = 100u; VSyncPredictor::~VSyncPredictor() = default; -VSyncPredictor::VSyncPredictor(std::string name, nsecs_t idealPeriod, size_t historySize, +VSyncPredictor::VSyncPredictor(nsecs_t idealPeriod, size_t historySize, size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent) - : mName(name), - mTraceOn(property_get_bool("debug.sf.vsp_trace", false)), + : mTraceOn(property_get_bool("debug.sf.vsp_trace", false)), kHistorySize(historySize), kMinimumSamplesForPrediction(minimumSamplesForPrediction), kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)), @@ -60,14 +57,12 @@ VSyncPredictor::VSyncPredictor(std::string name, nsecs_t idealPeriod, size_t his inline void VSyncPredictor::traceInt64If(const char* name, int64_t value) const { if (CC_UNLIKELY(mTraceOn)) { - traceInt64(name, value); + ATRACE_INT64(name, value); } } inline void VSyncPredictor::traceInt64(const char* name, int64_t value) const { - // TODO (b/266817103): Pass in PhysicalDisplayId and use ftl::Concat to - // avoid unnecessary allocations. - ATRACE_INT64(StringPrintf("%s %s", name, mName.c_str()).c_str(), value); + ATRACE_INT64(name, value); } inline size_t VSyncPredictor::next(size_t i) const { @@ -219,8 +214,8 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { it->second = {anticipatedPeriod, intercept}; - ALOGV("model update ts %s: %" PRId64 " slope: %" PRId64 " intercept: %" PRId64, mName.c_str(), - timestamp, anticipatedPeriod, intercept); + ALOGV("model update ts: %" PRId64 " slope: %" PRId64 " intercept: %" PRId64, timestamp, + anticipatedPeriod, intercept); return true; } @@ -332,7 +327,7 @@ bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) c } void VSyncPredictor::setDivisor(unsigned divisor) { - ALOGV("%s %s: %d", __func__, mName.c_str(), divisor); + ALOGV("%s: %d", __func__, divisor); std::lock_guard lock(mMutex); mDivisor = divisor; } @@ -348,7 +343,7 @@ VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const { } void VSyncPredictor::setPeriod(nsecs_t period) { - ATRACE_FORMAT("%s %s", __func__, mName.c_str()); + ATRACE_CALL(); traceInt64("VSP-setPeriod", period); std::lock_guard lock(mMutex); diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index 1ded54f768..305cdb0d56 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -29,15 +29,14 @@ namespace android::scheduler { class VSyncPredictor : public VSyncTracker { public: /* - * \param [in] name The name of the display this corresponds to. * \param [in] idealPeriod The initial ideal period to use. * \param [in] historySize The internal amount of entries to store in the model. * \param [in] minimumSamplesForPrediction The minimum number of samples to collect before * predicting. \param [in] outlierTolerancePercent a number 0 to 100 that will be used to filter * samples that fall outlierTolerancePercent from an anticipated vsync event. */ - VSyncPredictor(std::string name, nsecs_t idealPeriod, size_t historySize, - size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent); + VSyncPredictor(nsecs_t idealPeriod, size_t historySize, size_t minimumSamplesForPrediction, + uint32_t outlierTolerancePercent); ~VSyncPredictor(); bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex); @@ -77,8 +76,6 @@ private: VSyncPredictor& operator=(VSyncPredictor const&) = delete; void clearTimestamps() REQUIRES(mMutex); - const std::string mName; - inline void traceInt64If(const char* name, int64_t value) const; inline void traceInt64(const char* name, int64_t value) const; bool const mTraceOn; diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp index a831f6624d..b5f212e085 100644 --- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp @@ -21,7 +21,6 @@ #include #include -#include #include #include @@ -33,7 +32,6 @@ namespace android::scheduler { using base::StringAppendF; -using base::StringPrintf; VsyncController::~VsyncController() = default; @@ -41,12 +39,12 @@ nsecs_t SystemClock::now() const { return systemTime(SYSTEM_TIME_MONOTONIC); } -VSyncReactor::VSyncReactor(std::string name, std::unique_ptr clock, VSyncTracker& tracker, +VSyncReactor::VSyncReactor(std::unique_ptr clock, VSyncTracker& tracker, size_t pendingFenceLimit, bool supportKernelIdleTimer) - : mName(name), - mClock(std::move(clock)), + : mClock(std::move(clock)), mTracker(tracker), mPendingLimit(pendingFenceLimit), + // TODO(adyabr): change mSupportKernelIdleTimer when the active display changes mSupportKernelIdleTimer(supportKernelIdleTimer) {} VSyncReactor::~VSyncReactor() = default; @@ -116,7 +114,7 @@ void VSyncReactor::updateIgnorePresentFencesInternal() { } void VSyncReactor::startPeriodTransitionInternal(nsecs_t newPeriod) { - ATRACE_FORMAT("%s %s", __func__, mName.c_str()); + ATRACE_CALL(); mPeriodConfirmationInProgress = true; mPeriodTransitioningTo = newPeriod; mMoreSamplesNeeded = true; @@ -124,20 +122,18 @@ void VSyncReactor::startPeriodTransitionInternal(nsecs_t newPeriod) { } void VSyncReactor::endPeriodTransition() { - ATRACE_FORMAT("%s %s", __func__, mName.c_str()); + ATRACE_CALL(); mPeriodTransitioningTo.reset(); mPeriodConfirmationInProgress = false; mLastHwVsync.reset(); } -void VSyncReactor::startPeriodTransition(nsecs_t period, bool force) { - // TODO (b/266817103): Pass in PhysicalDisplayId and use ftl::Concat to - // avoid unnecessary allocations. - ATRACE_INT64(StringPrintf("VSR-startPeriodTransition %s", mName.c_str()).c_str(), period); +void VSyncReactor::startPeriodTransition(nsecs_t period) { + ATRACE_INT64("VSR-startPeriodTransition", period); std::lock_guard lock(mMutex); mLastHwVsync.reset(); - if (!mSupportKernelIdleTimer && period == mTracker.currentPeriod() && !force) { + if (!mSupportKernelIdleTimer && period == mTracker.currentPeriod()) { endPeriodTransition(); setIgnorePresentFencesInternal(false); mMoreSamplesNeeded = false; @@ -185,7 +181,7 @@ bool VSyncReactor::addHwVsyncTimestamp(nsecs_t timestamp, std::optional std::lock_guard lock(mMutex); if (periodConfirmed(timestamp, hwcVsyncPeriod)) { - ATRACE_FORMAT("VSR %s: period confirmed", mName.c_str()); + ATRACE_NAME("VSR: period confirmed"); if (mPeriodTransitioningTo) { mTracker.setPeriod(*mPeriodTransitioningTo); *periodFlushed = true; @@ -199,12 +195,12 @@ bool VSyncReactor::addHwVsyncTimestamp(nsecs_t timestamp, std::optional endPeriodTransition(); mMoreSamplesNeeded = mTracker.needsMoreSamples(); } else if (mPeriodConfirmationInProgress) { - ATRACE_FORMAT("VSR %s: still confirming period", mName.c_str()); + ATRACE_NAME("VSR: still confirming period"); mLastHwVsync = timestamp; mMoreSamplesNeeded = true; *periodFlushed = false; } else { - ATRACE_FORMAT("VSR %s: adding sample", mName.c_str()); + ATRACE_NAME("VSR: adding sample"); *periodFlushed = false; mTracker.addVsyncTimestamp(timestamp); mMoreSamplesNeeded = mTracker.needsMoreSamples(); diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.h b/services/surfaceflinger/Scheduler/VSyncReactor.h index fd9ca42f28..4501487392 100644 --- a/services/surfaceflinger/Scheduler/VSyncReactor.h +++ b/services/surfaceflinger/Scheduler/VSyncReactor.h @@ -22,7 +22,6 @@ #include #include -#include #include #include @@ -38,14 +37,14 @@ class VSyncTracker; // TODO (b/145217110): consider renaming. class VSyncReactor : public VsyncController { public: - VSyncReactor(std::string name, std::unique_ptr clock, VSyncTracker& tracker, - size_t pendingFenceLimit, bool supportKernelIdleTimer); + VSyncReactor(std::unique_ptr clock, VSyncTracker& tracker, size_t pendingFenceLimit, + bool supportKernelIdleTimer); ~VSyncReactor(); bool addPresentFence(std::shared_ptr) final; void setIgnorePresentFences(bool ignore) final; - void startPeriodTransition(nsecs_t period, bool force) final; + void startPeriodTransition(nsecs_t period) final; bool addHwVsyncTimestamp(nsecs_t timestamp, std::optional hwcVsyncPeriod, bool* periodFlushed) final; @@ -62,7 +61,6 @@ private: bool periodConfirmed(nsecs_t vsync_timestamp, std::optional hwcVsyncPeriod) REQUIRES(mMutex); - const std::string mName; std::unique_ptr const mClock; VSyncTracker& mTracker; size_t const mPendingLimit; diff --git a/services/surfaceflinger/Scheduler/VsyncController.h b/services/surfaceflinger/Scheduler/VsyncController.h index 917789934a..726a420649 100644 --- a/services/surfaceflinger/Scheduler/VsyncController.h +++ b/services/surfaceflinger/Scheduler/VsyncController.h @@ -63,9 +63,8 @@ public: * itself. The controller will end the period transition internally. * * \param [in] period The period that the system is changing into. - * \param [in] force True to recalibrate even if period matches the existing period. */ - virtual void startPeriodTransition(nsecs_t period, bool force) = 0; + virtual void startPeriodTransition(nsecs_t period) = 0; /* * Tells the tracker to stop using present fences to get a vsync signal. diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp index 951c1eca25..95bc31f239 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp @@ -21,9 +21,6 @@ #include "VsyncSchedule.h" -#include "ISchedulerCallback.h" -#include "Scheduler.h" -#include "Utils/Dumper.h" #include "VSyncDispatchTimerQueue.h" #include "VSyncPredictor.h" #include "VSyncReactor.h" @@ -42,8 +39,8 @@ class VsyncSchedule::PredictedVsyncTracer { } public: - explicit PredictedVsyncTracer(std::shared_ptr dispatch) - : mRegistration(std::move(dispatch), makeVsyncCallback(), __func__) { + explicit PredictedVsyncTracer(VsyncDispatch& dispatch) + : mRegistration(dispatch, makeVsyncCallback(), __func__) { schedule(); } @@ -54,23 +51,21 @@ private: VSyncCallbackRegistration mRegistration; }; -VsyncSchedule::VsyncSchedule(PhysicalDisplayId id, FeatureFlags features) - : mId(id), - mTracker(createTracker(id)), - mDispatch(createDispatch(mTracker)), - mController(createController(id, *mTracker, features)) { +VsyncSchedule::VsyncSchedule(FeatureFlags features) + : mTracker(createTracker()), + mDispatch(createDispatch(*mTracker)), + mController(createController(*mTracker, features)) { if (features.test(Feature::kTracePredictedVsync)) { - mTracer = std::make_unique(mDispatch); + mTracer = std::make_unique(*mDispatch); } } -VsyncSchedule::VsyncSchedule(PhysicalDisplayId id, TrackerPtr tracker, DispatchPtr dispatch, - ControllerPtr controller) - : mId(id), - mTracker(std::move(tracker)), +VsyncSchedule::VsyncSchedule(TrackerPtr tracker, DispatchPtr dispatch, ControllerPtr controller) + : mTracker(std::move(tracker)), mDispatch(std::move(dispatch)), mController(std::move(controller)) {} +VsyncSchedule::VsyncSchedule(VsyncSchedule&&) = default; VsyncSchedule::~VsyncSchedule() = default; Period VsyncSchedule::period() const { @@ -82,13 +77,6 @@ TimePoint VsyncSchedule::vsyncDeadlineAfter(TimePoint timePoint) const { } void VsyncSchedule::dump(std::string& out) const { - utils::Dumper dumper(out); - { - std::lock_guard lock(mHwVsyncLock); - dumper.dump("hwVsyncState", ftl::enum_string(mHwVsyncState)); - dumper.dump("lastHwVsyncState", ftl::enum_string(mLastHwVsyncState)); - } - out.append("VsyncController:\n"); mController->dump(out); @@ -96,72 +84,40 @@ void VsyncSchedule::dump(std::string& out) const { mDispatch->dump(out); } -VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(PhysicalDisplayId id) { +VsyncSchedule::TrackerPtr VsyncSchedule::createTracker() { // TODO(b/144707443): Tune constants. constexpr nsecs_t kInitialPeriod = (60_Hz).getPeriodNsecs(); constexpr size_t kHistorySize = 20; constexpr size_t kMinSamplesForPrediction = 6; constexpr uint32_t kDiscardOutlierPercent = 20; - return std::make_unique(to_string(id), kInitialPeriod, kHistorySize, - kMinSamplesForPrediction, kDiscardOutlierPercent); + return std::make_unique(kInitialPeriod, kHistorySize, kMinSamplesForPrediction, + kDiscardOutlierPercent); } -VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(TrackerPtr tracker) { +VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(VsyncTracker& tracker) { using namespace std::chrono_literals; // TODO(b/144707443): Tune constants. constexpr std::chrono::nanoseconds kGroupDispatchWithin = 500us; constexpr std::chrono::nanoseconds kSnapToSameVsyncWithin = 3ms; - return std::make_unique(std::make_unique(), std::move(tracker), + return std::make_unique(std::make_unique(), tracker, kGroupDispatchWithin.count(), kSnapToSameVsyncWithin.count()); } -VsyncSchedule::ControllerPtr VsyncSchedule::createController(PhysicalDisplayId id, - VsyncTracker& tracker, +VsyncSchedule::ControllerPtr VsyncSchedule::createController(VsyncTracker& tracker, FeatureFlags features) { // TODO(b/144707443): Tune constants. constexpr size_t kMaxPendingFences = 20; const bool hasKernelIdleTimer = features.test(Feature::kKernelIdleTimer); - auto reactor = std::make_unique(to_string(id), std::make_unique(), - tracker, kMaxPendingFences, hasKernelIdleTimer); + auto reactor = std::make_unique(std::make_unique(), tracker, + kMaxPendingFences, hasKernelIdleTimer); reactor->setIgnorePresentFences(!features.test(Feature::kPresentFences)); return reactor; } -void VsyncSchedule::enableHardwareVsync(ISchedulerCallback& callback) { - std::lock_guard lock(mHwVsyncLock); - if (mHwVsyncState == HwVsyncState::Disabled) { - getTracker().resetModel(); - callback.setVsyncEnabled(mId, true); - mHwVsyncState = HwVsyncState::Enabled; - mLastHwVsyncState = HwVsyncState::Enabled; - } -} - -void VsyncSchedule::disableHardwareVsync(ISchedulerCallback& callback, bool disallow) { - std::lock_guard lock(mHwVsyncLock); - if (mHwVsyncState == HwVsyncState::Enabled) { - callback.setVsyncEnabled(mId, false); - mLastHwVsyncState = HwVsyncState::Disabled; - } - mHwVsyncState = disallow ? HwVsyncState::Disallowed : HwVsyncState::Disabled; -} - -bool VsyncSchedule::isHardwareVsyncAllowed() const { - std::lock_guard lock(mHwVsyncLock); - return mHwVsyncState != HwVsyncState::Disallowed; -} - -void VsyncSchedule::allowHardwareVsync() { - std::lock_guard lock(mHwVsyncLock); - if (mHwVsyncState == HwVsyncState::Disallowed) { - mHwVsyncState = HwVsyncState::Disabled; - } -} - } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h index ffb7ad5b42..173b1d00cf 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.h +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h @@ -19,10 +19,8 @@ #include #include -#include #include #include -#include namespace android { class EventThreadTest; @@ -34,8 +32,6 @@ class SchedulerFuzzer; namespace android::scheduler { -struct ISchedulerCallback; - // TODO(b/185535769): Rename classes, and remove aliases. class VSyncDispatch; class VSyncTracker; @@ -47,7 +43,8 @@ using VsyncTracker = VSyncTracker; // Schedule that synchronizes to hardware VSYNC of a physical display. class VsyncSchedule { public: - VsyncSchedule(PhysicalDisplayId, FeatureFlags); + explicit VsyncSchedule(FeatureFlags); + VsyncSchedule(VsyncSchedule&&); ~VsyncSchedule(); Period period() const; @@ -58,71 +55,30 @@ public: VsyncTracker& getTracker() { return *mTracker; } VsyncController& getController() { return *mController; } - // TODO(b/185535769): Once these are hidden behind the API, they may no - // longer need to be shared_ptrs. - using DispatchPtr = std::shared_ptr; - using TrackerPtr = std::shared_ptr; - // TODO(b/185535769): Remove once VsyncSchedule owns all registrations. - DispatchPtr getDispatch() { return mDispatch; } + VsyncDispatch& getDispatch() { return *mDispatch; } void dump(std::string&) const; - // Turn on hardware vsyncs, unless mHwVsyncState is Disallowed, in which - // case this call is ignored. - void enableHardwareVsync(ISchedulerCallback&) EXCLUDES(mHwVsyncLock); - - // Disable hardware vsyncs. If `disallow` is true, future calls to - // enableHardwareVsync are ineffective until allowHardwareVsync is called. - void disableHardwareVsync(ISchedulerCallback&, bool disallow) EXCLUDES(mHwVsyncLock); - - // Restore the ability to enable hardware vsync. - void allowHardwareVsync() EXCLUDES(mHwVsyncLock); - - // If true, enableHardwareVsync can enable hardware vsync (if not already - // enabled). If false, enableHardwareVsync does nothing. - bool isHardwareVsyncAllowed() const EXCLUDES(mHwVsyncLock); - -protected: - using ControllerPtr = std::unique_ptr; - - // For tests. - VsyncSchedule(PhysicalDisplayId, TrackerPtr, DispatchPtr, ControllerPtr); - private: friend class TestableScheduler; friend class android::EventThreadTest; friend class android::fuzz::SchedulerFuzzer; - static TrackerPtr createTracker(PhysicalDisplayId); - static DispatchPtr createDispatch(TrackerPtr); - static ControllerPtr createController(PhysicalDisplayId, VsyncTracker&, FeatureFlags); - - mutable std::mutex mHwVsyncLock; - enum class HwVsyncState { - // Hardware vsyncs are currently enabled. - Enabled, - - // Hardware vsyncs are currently disabled. They can be enabled by a call - // to `enableHardwareVsync`. - Disabled, - - // Hardware vsyncs are not currently allowed (e.g. because the display - // is off). - Disallowed, + using TrackerPtr = std::unique_ptr; + using DispatchPtr = std::unique_ptr; + using ControllerPtr = std::unique_ptr; - ftl_last = Disallowed, - }; - HwVsyncState mHwVsyncState GUARDED_BY(mHwVsyncLock) = HwVsyncState::Disallowed; + // For tests. + VsyncSchedule(TrackerPtr, DispatchPtr, ControllerPtr); - // The last state, which may be the current state, or the state prior to setting to Disallowed. - HwVsyncState mLastHwVsyncState GUARDED_BY(mHwVsyncLock) = HwVsyncState::Disabled; + static TrackerPtr createTracker(); + static DispatchPtr createDispatch(VsyncTracker&); + static ControllerPtr createController(VsyncTracker&, FeatureFlags); class PredictedVsyncTracer; using TracerPtr = std::unique_ptr; - const PhysicalDisplayId mId; - // Effectively const except in move constructor. TrackerPtr mTracker; DispatchPtr mDispatch; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index a0c3eb0e26..169e101b2f 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1130,33 +1130,21 @@ status_t SurfaceFlinger::getDynamicDisplayInfoFromToken(const sp& displ return NO_ERROR; } -status_t SurfaceFlinger::getDisplayStats(const sp& displayToken, - DisplayStatInfo* outStats) { +status_t SurfaceFlinger::getDisplayStats(const sp&, DisplayStatInfo* outStats) { if (!outStats) { return BAD_VALUE; } - std::optional displayIdOpt; - { - Mutex::Autolock lock(mStateLock); - displayIdOpt = getPhysicalDisplayIdLocked(displayToken); - } - - if (!displayIdOpt) { - ALOGE("%s: Invalid physical display token %p", __func__, displayToken.get()); - return NAME_NOT_FOUND; - } - const auto schedule = mScheduler->getVsyncSchedule(displayIdOpt); - outStats->vsyncTime = schedule->vsyncDeadlineAfter(TimePoint::now()).ns(); - outStats->vsyncPeriod = schedule->period().ns(); + const auto& schedule = mScheduler->getVsyncSchedule(); + outStats->vsyncTime = schedule.vsyncDeadlineAfter(TimePoint::now()).ns(); + outStats->vsyncPeriod = schedule.period().ns(); return NO_ERROR; } void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request, bool force) { ATRACE_CALL(); - const auto displayId = request.mode.modePtr->getPhysicalDisplayId(); - const auto display = getDisplayDeviceLocked(displayId); + auto display = getDisplayDeviceLocked(request.mode.modePtr->getPhysicalDisplayId()); if (!display) { ALOGW("%s: display is no longer valid", __func__); return; @@ -1169,25 +1157,23 @@ void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request, force)) { case DisplayDevice::DesiredActiveModeAction::InitiateDisplayModeSwitch: // Set the render rate as setDesiredActiveMode updated it. - mScheduler->setRenderRate(displayId, - display->refreshRateSelector().getActiveMode().fps); + mScheduler->setRenderRate(display->refreshRateSelector().getActiveMode().fps); // Schedule a new frame to initiate the display mode switch. scheduleComposite(FrameHint::kNone); // Start receiving vsync samples now, so that we can detect a period // switch. - mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, - mode.modePtr->getFps()); - + mScheduler->resyncToHardwareVsync(true, mode.modePtr->getFps()); // As we called to set period, we will call to onRefreshRateChangeCompleted once // VsyncController model is locked. - mScheduler->modulateVsync(displayId, &VsyncModulator::onRefreshRateChangeInitiated); + mScheduler->modulateVsync(&VsyncModulator::onRefreshRateChangeInitiated); + updatePhaseConfiguration(mode.fps); mScheduler->setModeChangePending(true); break; case DisplayDevice::DesiredActiveModeAction::InitiateRenderRateSwitch: - mScheduler->setRenderRate(displayId, mode.fps); + mScheduler->setRenderRate(mode.fps); updatePhaseConfiguration(mode.fps); mRefreshRateStats->setRefreshRate(mode.fps); if (display->getPhysicalId() == mActiveDisplayId && emitEvent) { @@ -1303,14 +1289,11 @@ void SurfaceFlinger::clearDesiredActiveModeState(const sp& displa } void SurfaceFlinger::desiredActiveModeChangeDone(const sp& display) { - const auto desiredActiveMode = display->getDesiredActiveMode(); - const auto& modeOpt = desiredActiveMode->modeOpt; - const auto displayId = modeOpt->modePtr->getPhysicalDisplayId(); - const auto displayFps = modeOpt->modePtr->getFps(); - const auto renderFps = modeOpt->fps; + const auto displayFps = display->getDesiredActiveMode()->modeOpt->modePtr->getFps(); + const auto renderFps = display->getDesiredActiveMode()->modeOpt->fps; clearDesiredActiveModeState(display); - mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, displayFps); - mScheduler->setRenderRate(displayId, renderFps); + mScheduler->resyncToHardwareVsync(true, displayFps); + mScheduler->setRenderRate(renderFps); updatePhaseConfiguration(renderFps); } @@ -2047,11 +2030,21 @@ void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t t ATRACE_FORMAT("onComposerHalVsync%s", tracePeriod.c_str()); Mutex::Autolock lock(mStateLock); - if (const auto displayIdOpt = getHwComposer().onVsync(hwcDisplayId, timestamp)) { - if (mScheduler->addResyncSample(*displayIdOpt, timestamp, vsyncPeriod)) { - // period flushed - mScheduler->modulateVsync(displayIdOpt, &VsyncModulator::onRefreshRateChangeCompleted); - } + + if (!getHwComposer().onVsync(hwcDisplayId, timestamp)) { + return; + } + + if (const auto displayId = getHwComposer().toPhysicalDisplayId(hwcDisplayId); + displayId != mActiveDisplayId) { + // For now, we don't do anything with non active display vsyncs. + return; + } + + bool periodFlushed = false; + mScheduler->addResyncSample(timestamp, vsyncPeriod, &periodFlushed); + if (periodFlushed) { + mScheduler->modulateVsync(&VsyncModulator::onRefreshRateChangeCompleted); } } @@ -2092,15 +2085,16 @@ void SurfaceFlinger::onComposerHalVsyncIdle(hal::HWDisplayId) { mScheduler->forceNextResync(); } -void SurfaceFlinger::setVsyncEnabled(PhysicalDisplayId id, bool enabled) { - const char* const whence = __func__; - ATRACE_FORMAT("%s (%d) for %" PRIu64, whence, enabled, id.value); +void SurfaceFlinger::setVsyncEnabled(bool enabled) { + ATRACE_CALL(); // On main thread to avoid race conditions with display power state. static_cast(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) { - ATRACE_FORMAT("%s (%d) for %" PRIu64 " (main thread)", whence, enabled, id.value); - if (const auto display = getDisplayDeviceLocked(id); display && display->isPoweredOn()) { - setHWCVsyncEnabled(id, enabled); + mHWCVsyncPendingState = enabled ? hal::Vsync::ENABLE : hal::Vsync::DISABLE; + + if (const auto display = getDefaultDisplayDeviceLocked(); + display && display->isPoweredOn()) { + setHWCVsyncEnabled(display->getPhysicalId(), mHWCVsyncPendingState); } })); } @@ -2127,13 +2121,13 @@ bool SurfaceFlinger::isFencePending(const FenceTimePtr& fence, int graceTimeMs) TimePoint SurfaceFlinger::calculateExpectedPresentTime(TimePoint frameTime) const { const auto& schedule = mScheduler->getVsyncSchedule(); - const TimePoint vsyncDeadline = schedule->vsyncDeadlineAfter(frameTime); + const TimePoint vsyncDeadline = schedule.vsyncDeadlineAfter(frameTime); if (mScheduler->vsyncModulator().getVsyncConfig().sfOffset > 0) { return vsyncDeadline; } // Inflate the expected present time if we're targeting the next vsync. - return vsyncDeadline + schedule->period(); + return vsyncDeadline + schedule.period(); } void SurfaceFlinger::configure() FTL_FAKE_GUARD(kMainThreadContext) { @@ -2264,7 +2258,7 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe ticks(mExpectedPresentTime - TimePoint::now()), mExpectedPresentTime == expectedVsyncTime ? "" : " (adjusted)"); - const Period vsyncPeriod = mScheduler->getVsyncSchedule()->period(); + const Period vsyncPeriod = mScheduler->getVsyncSchedule().period(); const FenceTimePtr& previousPresentFence = getPreviousPresentFence(frameTime, vsyncPeriod); // When backpressure propagation is enabled, we want to give a small grace period of 1ms @@ -2514,7 +2508,7 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) refreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::milliseconds(mDebugFlashDelay); } - const auto prevVsyncTime = mExpectedPresentTime - mScheduler->getVsyncSchedule()->period(); + const auto prevVsyncTime = mExpectedPresentTime - mScheduler->getVsyncSchedule().period(); const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration; refreshArgs.earliestPresentTime = prevVsyncTime - hwcMinWorkDuration; @@ -2596,7 +2590,7 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) // TODO(b/160583065): Enable skip validation when SF caches all client composition layers. const bool hasGpuUseOrReuse = mCompositionCoverage.any(CompositionCoverage::Gpu | CompositionCoverage::GpuReuse); - mScheduler->modulateVsync({}, &VsyncModulator::onDisplayRefresh, hasGpuUseOrReuse); + mScheduler->modulateVsync(&VsyncModulator::onDisplayRefresh, hasGpuUseOrReuse); mLayersWithQueuedFrames.clear(); if (mLayerTracingEnabled && mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) { @@ -2740,9 +2734,9 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { ? mPresentLatencyTracker.trackPendingFrame(compositeTime, presentFenceTime) : Duration::zero(); - const auto schedule = mScheduler->getVsyncSchedule(); - const TimePoint vsyncDeadline = schedule->vsyncDeadlineAfter(presentTime); - const Period vsyncPeriod = schedule->period(); + const auto& schedule = mScheduler->getVsyncSchedule(); + const TimePoint vsyncDeadline = schedule.vsyncDeadlineAfter(presentTime); + const Period vsyncPeriod = schedule.period(); const nsecs_t vsyncPhase = mVsyncConfiguration->getCurrentConfigs().late.sfOffset; const CompositorTiming compositorTiming(vsyncDeadline.ns(), vsyncPeriod.ns(), vsyncPhase, @@ -2817,19 +2811,15 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { mTimeStats->incrementTotalFrames(); mTimeStats->setPresentFenceGlobal(presentFenceTime); - { - ftl::FakeGuard guard(mStateLock); - for (const auto& [id, physicalDisplay] : mPhysicalDisplays) { - if (auto displayDevice = getDisplayDeviceLocked(id); - displayDevice && displayDevice->isPoweredOn() && physicalDisplay.isInternal()) { - auto presentFenceTimeI = defaultDisplay && defaultDisplay->getPhysicalId() == id - ? std::move(presentFenceTime) - : std::make_shared(getHwComposer().getPresentFence(id)); - if (presentFenceTimeI->isValid()) { - mScheduler->addPresentFence(id, std::move(presentFenceTimeI)); - } - } - } + const bool isInternalDisplay = defaultDisplay && + FTL_FAKE_GUARD(mStateLock, mPhysicalDisplays) + .get(defaultDisplay->getPhysicalId()) + .transform(&PhysicalDisplay::isInternal) + .value_or(false); + + if (isInternalDisplay && defaultDisplay && defaultDisplay->getPowerMode() == hal::PowerMode::ON && + presentFenceTime->isValid()) { + mScheduler->addPresentFence(std::move(presentFenceTime)); } const bool isDisplayConnected = @@ -2837,7 +2827,7 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { if (!hasSyncFramework) { if (isDisplayConnected && defaultDisplay->isPoweredOn()) { - mScheduler->enableHardwareVsync(defaultDisplay->getPhysicalId()); + mScheduler->enableHardwareVsync(); } } @@ -2948,7 +2938,7 @@ void SurfaceFlinger::commitTransactions() { // so we can call commitTransactionsLocked unconditionally. // We clear the flags with mStateLock held to guarantee that // mCurrentState won't change until the transaction is committed. - mScheduler->modulateVsync({}, &VsyncModulator::onTransactionCommit); + mScheduler->modulateVsync(&VsyncModulator::onTransactionCommit); commitTransactionsLocked(clearTransactionFlags(eTransactionMask)); mDebugInTransaction = 0; @@ -3787,9 +3777,10 @@ void SurfaceFlinger::initScheduler(const sp& display) { mScheduler = std::make_unique(static_cast(*this), static_cast(*this), features, std::move(modulatorPtr)); + mScheduler->createVsyncSchedule(features); mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector()); - setVsyncEnabled(display->getPhysicalId(), false); + setVsyncEnabled(false); mScheduler->startTimers(); const auto configs = mVsyncConfiguration->getCurrentConfigs(); @@ -3805,7 +3796,7 @@ void SurfaceFlinger::initScheduler(const sp& display) { /* workDuration */ activeRefreshRate.getPeriod(), /* readyDuration */ configs.late.sfWorkDuration); - mScheduler->initVsync(mScheduler->getVsyncSchedule()->getDispatch(), + mScheduler->initVsync(mScheduler->getVsyncSchedule().getDispatch(), *mFrameTimeline->getTokenManager(), configs.late.sfWorkDuration); mRegionSamplingThread = @@ -4019,7 +4010,7 @@ uint32_t SurfaceFlinger::clearTransactionFlags(uint32_t mask) { void SurfaceFlinger::setTransactionFlags(uint32_t mask, TransactionSchedule schedule, const sp& applyToken, FrameHint frameHint) { - mScheduler->modulateVsync({}, &VsyncModulator::setTransactionSchedule, schedule, applyToken); + mScheduler->modulateVsync(&VsyncModulator::setTransactionSchedule, schedule, applyToken); uint32_t transactionFlags = mTransactionFlags.fetch_or(mask); ATRACE_INT("mTransactionFlags", transactionFlags); @@ -4048,7 +4039,7 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyTimelin return TransactionReadiness::NotReady; } - if (!mScheduler->isVsyncTargetForUid(mExpectedPresentTime, transaction.originUid)) { + if (!mScheduler->isVsyncValid(mExpectedPresentTime, transaction.originUid)) { ATRACE_NAME("!isVsyncValid"); return TransactionReadiness::NotReady; } @@ -4192,7 +4183,7 @@ bool SurfaceFlinger::frameIsEarly(TimePoint expectedPresentTime, VsyncId vsyncId return false; } - const Duration earlyLatchVsyncThreshold = mScheduler->getVsyncSchedule()->period() / 2; + const Duration earlyLatchVsyncThreshold = mScheduler->getVsyncSchedule().period() / 2; return predictedPresentTime >= expectedPresentTime && predictedPresentTime - expectedPresentTime >= earlyLatchVsyncThreshold; @@ -5233,11 +5224,10 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: ALOGW("Couldn't set SCHED_FIFO on display on: %s\n", strerror(errno)); } getHwComposer().setPowerMode(displayId, mode); - if (mode != hal::PowerMode::DOZE_SUSPEND) { - if (isActiveDisplay) { - mScheduler->onScreenAcquired(mAppConnectionHandle); - } - mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, refreshRate); + if (isActiveDisplay && mode != hal::PowerMode::DOZE_SUSPEND) { + setHWCVsyncEnabled(displayId, mHWCVsyncPendingState); + mScheduler->onScreenAcquired(mAppConnectionHandle); + mScheduler->resyncToHardwareVsync(true, refreshRate); } mVisibleRegionsDirty = true; @@ -5250,34 +5240,33 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: if (SurfaceFlinger::setSchedAttr(false) != NO_ERROR) { ALOGW("Couldn't set uclamp.min on display off: %s\n", strerror(errno)); } - if (*currentModeOpt != hal::PowerMode::DOZE_SUSPEND) { - mScheduler->disableHardwareVsync(displayId, true); - if (isActiveDisplay) { - mScheduler->onScreenReleased(mAppConnectionHandle); - } + if (isActiveDisplay && *currentModeOpt != hal::PowerMode::DOZE_SUSPEND) { + mScheduler->disableHardwareVsync(true); + mScheduler->onScreenReleased(mAppConnectionHandle); } + // Make sure HWVsync is disabled before turning off the display + setHWCVsyncEnabled(displayId, hal::Vsync::DISABLE); + getHwComposer().setPowerMode(displayId, mode); mVisibleRegionsDirty = true; // from this point on, SF will stop drawing on this display } else if (mode == hal::PowerMode::DOZE || mode == hal::PowerMode::ON) { // Update display while dozing getHwComposer().setPowerMode(displayId, mode); - if (*currentModeOpt == hal::PowerMode::DOZE_SUSPEND) { - if (isActiveDisplay) { - mScheduler->onScreenAcquired(mAppConnectionHandle); - } + if (isActiveDisplay && *currentModeOpt == hal::PowerMode::DOZE_SUSPEND) { ALOGI("Force repainting for DOZE_SUSPEND -> DOZE or ON."); mVisibleRegionsDirty = true; scheduleRepaint(); - mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, refreshRate); + mScheduler->onScreenAcquired(mAppConnectionHandle); + mScheduler->resyncToHardwareVsync(true, refreshRate); } } else if (mode == hal::PowerMode::DOZE_SUSPEND) { // Leave display going to doze if (isActiveDisplay) { + mScheduler->disableHardwareVsync(true); mScheduler->onScreenReleased(mAppConnectionHandle); } - mScheduler->disableHardwareVsync(displayId, true); getHwComposer().setPowerMode(displayId, mode); } else { ALOGE("Attempting to set unknown power mode: %d\n", mode); @@ -5287,8 +5276,8 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: if (isActiveDisplay) { mTimeStats->setPowerMode(mode); mRefreshRateStats->setPowerMode(mode); + mScheduler->setDisplayPowerMode(mode); } - mScheduler->setDisplayPowerMode(displayId, mode); ALOGD("Finished setting power mode %d on display %s", mode, to_string(displayId).c_str()); } @@ -5466,6 +5455,14 @@ void SurfaceFlinger::dumpScheduler(std::string& result) const { mScheduler->dump(dumper); + // TODO(b/241286146): Move to Scheduler. + { + utils::Dumper::Indent indent(dumper); + dumper.dump("lastHwcVsyncState"sv, mLastHWCVsyncState); + dumper.dump("pendingHwcVsyncState"sv, mHWCVsyncPendingState); + } + dumper.eol(); + // TODO(b/241285876): Move to DisplayModeController. dumper.dump("debugDisplayModeSetByBackdoor"sv, mDebugDisplayModeSetByBackdoor); dumper.eol(); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 0bd15dc241..5b8038b173 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -76,7 +76,6 @@ #include "FrontEnd/LayerSnapshotBuilder.h" #include "FrontEnd/TransactionHandler.h" #include "LayerVector.h" -#include "Scheduler/ISchedulerCallback.h" #include "Scheduler/RefreshRateSelector.h" #include "Scheduler/RefreshRateStats.h" #include "Scheduler/Scheduler.h" @@ -642,7 +641,7 @@ private: // Toggles hardware VSYNC by calling into HWC. // TODO(b/241286146): Rename for self-explanatory API. - void setVsyncEnabled(PhysicalDisplayId, bool) override; + void setVsyncEnabled(bool) override; void requestDisplayModes(std::vector) override; void kernelTimerChanged(bool expired) override; void triggerOnFrameRateOverridesChanged() override; @@ -993,9 +992,9 @@ private: */ nsecs_t getVsyncPeriodFromHWC() const REQUIRES(mStateLock); - void setHWCVsyncEnabled(PhysicalDisplayId id, bool enabled) { - hal::Vsync halState = enabled ? hal::Vsync::ENABLE : hal::Vsync::DISABLE; - getHwComposer().setVsyncEnabled(id, halState); + void setHWCVsyncEnabled(PhysicalDisplayId id, hal::Vsync enabled) { + mLastHWCVsyncState = enabled; + getHwComposer().setVsyncEnabled(id, enabled); } using FenceTimePtr = std::shared_ptr; @@ -1130,15 +1129,7 @@ private: pid_t mPid; std::future mRenderEnginePrimeCacheFuture; - // mStateLock has conventions related to the current thread, because only - // the main thread should modify variables protected by mStateLock. - // - read access from a non-main thread must lock mStateLock, since the main - // thread may modify these variables. - // - write access from a non-main thread is not permitted. - // - read access from the main thread can use an ftl::FakeGuard, since other - // threads must not modify these variables. - // - write access from the main thread must lock mStateLock, since another - // thread may be reading these variables. + // access must be protected by mStateLock mutable Mutex mStateLock; State mCurrentState{LayerVector::StateSet::Current}; std::atomic mTransactionFlags = 0; @@ -1330,6 +1321,9 @@ private: TimePoint mScheduledPresentTime GUARDED_BY(kMainThreadContext); TimePoint mExpectedPresentTime GUARDED_BY(kMainThreadContext); + hal::Vsync mHWCVsyncPendingState = hal::Vsync::DISABLE; + hal::Vsync mLastHWCVsyncState = hal::Vsync::DISABLE; + // below flags are set by main thread only bool mSetActiveModePending = false; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 5303db314c..cdffbb4724 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -226,27 +226,27 @@ public: TestableScheduler(const std::shared_ptr& selectorPtr, sp modulatorPtr, ISchedulerCallback& callback) : TestableScheduler(std::make_unique(), - std::make_shared(), selectorPtr, + std::make_unique(), selectorPtr, std::move(modulatorPtr), callback) {} TestableScheduler(std::unique_ptr controller, - VsyncSchedule::TrackerPtr tracker, + std::unique_ptr tracker, std::shared_ptr selectorPtr, sp modulatorPtr, ISchedulerCallback& callback) : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) { + mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller))); + const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId(); registerDisplay(displayId, std::move(selectorPtr)); - mVsyncSchedules.emplace_or_replace(displayId, - std::shared_ptr( - new VsyncSchedule(displayId, std::move(tracker), - nullptr, - std::move(controller)))); } ConnectionHandle createConnection(std::unique_ptr eventThread) { return Scheduler::createConnection(std::move(eventThread)); } + auto &mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; } + auto &mutableHWVsyncAvailable() { return mHWVsyncAvailable; } + auto &mutableLayerHistory() { return mLayerHistory; } auto refreshRateSelector() { return leaderSelectorPtr(); } @@ -649,10 +649,10 @@ public: // The ISchedulerCallback argument can be nullptr for a no-op implementation. void setupScheduler(std::unique_ptr vsyncController, - std::shared_ptr vsyncTracker, + std::unique_ptr vsyncTracker, std::unique_ptr appEventThread, std::unique_ptr sfEventThread, - scheduler::ISchedulerCallback* callback = nullptr, + scheduler::ISchedulerCallback *callback = nullptr, bool hasMultipleModes = false) { constexpr DisplayModeId kModeId60{0}; DisplayModes modes = makeModes(mock::createDisplayMode(kModeId60, 60_Hz)); @@ -791,7 +791,7 @@ public: } private: - void setVsyncEnabled(PhysicalDisplayId, bool) override {} + void setVsyncEnabled(bool) override {} void requestDisplayModes(std::vector) override {} void kernelTimerChanged(bool) override {} void triggerOnFrameRateOverridesChanged() override {} diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index b7b42ab18d..44805dba82 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -47,23 +47,19 @@ constexpr PowerMode kPowerModes[] = {PowerMode::ON, PowerMode::DOZE, PowerMode:: PowerMode::DOZE_SUSPEND, PowerMode::ON_SUSPEND}; constexpr uint16_t kRandomStringLength = 256; +constexpr std::chrono::duration kSyncPeriod(16ms); + template void dump(T* component, FuzzedDataProvider* fdp) { std::string res = fdp->ConsumeRandomLengthString(kRandomStringLength); component->dump(res); } -class SchedulerFuzzer : public IEventThreadCallback { +class SchedulerFuzzer { public: SchedulerFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; void process(); - // IEventThreadCallback overrides. - bool isVsyncTargetForUid(TimePoint /* expectedVsyncTime */, uid_t) const override { - return true; - } - Fps getLeaderRenderFrameRate(uid_t) const override { return 60_Hz; } - private: void fuzzRefreshRateSelection(); void fuzzRefreshRateSelector(); @@ -80,7 +76,7 @@ private: FuzzedDataProvider mFdp; - std::shared_ptr mVsyncSchedule; + std::optional mVsyncSchedule; }; PhysicalDisplayId SchedulerFuzzer::getPhysicalDisplayId() { @@ -94,12 +90,12 @@ PhysicalDisplayId SchedulerFuzzer::getPhysicalDisplayId() { } void SchedulerFuzzer::fuzzEventThread() { - mVsyncSchedule = std::shared_ptr( - new scheduler::VsyncSchedule(getPhysicalDisplayId(), - std::make_shared(), - std::make_shared(), nullptr)); + mVsyncSchedule.emplace(scheduler::VsyncSchedule(std::make_unique(), + std::make_unique(), + nullptr)); + const auto getVsyncPeriod = [](uid_t /* uid */) { return kSyncPeriod.count(); }; std::unique_ptr thread = std::make_unique< - android::impl::EventThread>("fuzzer", mVsyncSchedule, *this, nullptr /* TokenManager */, + android::impl::EventThread>("fuzzer", *mVsyncSchedule, nullptr, nullptr, getVsyncPeriod, (std::chrono::nanoseconds)mFdp.ConsumeIntegral(), (std::chrono::nanoseconds)mFdp.ConsumeIntegral()); @@ -136,7 +132,7 @@ void SchedulerFuzzer::fuzzCallbackToken(scheduler::VSyncDispatchTimerQueue* disp } void SchedulerFuzzer::fuzzVSyncDispatchTimerQueue() { - auto stubTracker = std::make_shared(mFdp.ConsumeIntegral()); + FuzzImplVSyncTracker stubTracker{mFdp.ConsumeIntegral()}; scheduler::VSyncDispatchTimerQueue mDispatch{std::make_unique(), stubTracker, mFdp.ConsumeIntegral() /*dispatchGroupThreshold*/, @@ -149,17 +145,17 @@ void SchedulerFuzzer::fuzzVSyncDispatchTimerQueue() { scheduler::VSyncDispatchTimerQueueEntry entry( "fuzz", [](auto, auto, auto) {}, mFdp.ConsumeIntegral() /*vSyncMoveThreshold*/); - entry.update(*stubTracker, 0); + entry.update(stubTracker, 0); entry.schedule({.workDuration = mFdp.ConsumeIntegral(), .readyDuration = mFdp.ConsumeIntegral(), .earliestVsync = mFdp.ConsumeIntegral()}, - *stubTracker, 0); + stubTracker, 0); entry.disarm(); entry.ensureNotRunning(); entry.schedule({.workDuration = mFdp.ConsumeIntegral(), .readyDuration = mFdp.ConsumeIntegral(), .earliestVsync = mFdp.ConsumeIntegral()}, - *stubTracker, 0); + stubTracker, 0); auto const wakeup = entry.wakeupTime(); auto const ready = entry.readyTime(); entry.callback(entry.executing(), *wakeup, *ready); @@ -173,8 +169,8 @@ void SchedulerFuzzer::fuzzVSyncPredictor() { uint16_t now = mFdp.ConsumeIntegral(); uint16_t historySize = mFdp.ConsumeIntegralInRange(1, UINT16_MAX); uint16_t minimumSamplesForPrediction = mFdp.ConsumeIntegralInRange(1, UINT16_MAX); - scheduler::VSyncPredictor tracker{"predictor", mFdp.ConsumeIntegral() /*period*/, - historySize, minimumSamplesForPrediction, + scheduler::VSyncPredictor tracker{mFdp.ConsumeIntegral() /*period*/, historySize, + minimumSamplesForPrediction, mFdp.ConsumeIntegral() /*outlierTolerancePercent*/}; uint16_t period = mFdp.ConsumeIntegral(); tracker.setPeriod(period); @@ -246,15 +242,13 @@ void SchedulerFuzzer::fuzzLayerHistory() { void SchedulerFuzzer::fuzzVSyncReactor() { std::shared_ptr vSyncTracker = std::make_shared(); - scheduler::VSyncReactor reactor("fuzzer_reactor", - std::make_unique( + scheduler::VSyncReactor reactor(std::make_unique( std::make_shared()), *vSyncTracker, mFdp.ConsumeIntegral() /*pendingLimit*/, false); - reactor.startPeriodTransition(mFdp.ConsumeIntegral(), mFdp.ConsumeBool()); - bool periodFlushed = false; // Value does not matter, since this is an out - // param from addHwVsyncTimestamp. + reactor.startPeriodTransition(mFdp.ConsumeIntegral()); + bool periodFlushed = mFdp.ConsumeBool(); reactor.addHwVsyncTimestamp(0, std::nullopt, &periodFlushed); reactor.addHwVsyncTimestamp(mFdp.ConsumeIntegral() /*newPeriod*/, std::nullopt, &periodFlushed); diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 419c818e0e..0416e93f1e 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -139,7 +139,7 @@ public: ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_shared(); + auto vsyncTracker = std::make_unique(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp index 214b02888f..e0b508aa73 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp @@ -74,8 +74,8 @@ void DisplayTransactionTest::injectMockScheduler() { mock::EventThread::kCallingUid, ResyncCallback()))); - mFlinger.setupScheduler(std::make_unique(), - std::make_shared(), + mFlinger.setupScheduler(std::unique_ptr(mVsyncController), + std::unique_ptr(mVSyncTracker), std::unique_ptr(mEventThread), std::unique_ptr(mSFEventThread), TestableSurfaceFlinger::SchedulerCallbackImpl::kMock); diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h index c9245d6d97..223f4db889 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h @@ -128,6 +128,8 @@ public: renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine(); Hwc2::mock::Composer* mComposer = nullptr; + mock::VsyncController* mVsyncController = new mock::VsyncController; + mock::VSyncTracker* mVSyncTracker = new mock::VSyncTracker; mock::EventThread* mEventThread = new mock::EventThread; mock::EventThread* mSFEventThread = new mock::EventThread; diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp index 5cecb8eb19..b3aba377ee 100644 --- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp +++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp @@ -52,9 +52,11 @@ constexpr PhysicalDisplayId EXTERNAL_DISPLAY_ID = PhysicalDisplayId::fromPort(22 constexpr PhysicalDisplayId DISPLAY_ID_64BIT = PhysicalDisplayId::fromEdid(0xffu, 0xffffu, 0xffff'ffffu); +constexpr std::chrono::duration VSYNC_PERIOD(16ms); + } // namespace -class EventThreadTest : public testing::Test, public IEventThreadCallback { +class EventThreadTest : public testing::Test { protected: static constexpr std::chrono::nanoseconds kWorkDuration = 0ms; static constexpr std::chrono::nanoseconds kReadyDuration = 3ms; @@ -95,7 +97,7 @@ protected: void expectConfigChangedEventReceivedByConnection(PhysicalDisplayId expectedDisplayId, int32_t expectedConfigId, nsecs_t expectedVsyncPeriod); - void expectThrottleVsyncReceived(TimePoint expectedTimestamp, uid_t); + void expectThrottleVsyncReceived(nsecs_t expectedTimestamp, uid_t); void expectUidFrameRateMappingEventReceivedByConnection(PhysicalDisplayId expectedDisplayId, std::vector); @@ -104,14 +106,6 @@ protected: mThread->onVsync(expectedPresentationTime, timestamp, deadlineTimestamp); } - // IEventThreadCallback overrides. - bool isVsyncTargetForUid(TimePoint expectedVsyncTime, uid_t uid) const override { - mThrottleVsyncCallRecorder.getInvocable()(expectedVsyncTime, uid); - return uid != mThrottledConnectionUid; - } - - Fps getLeaderRenderFrameRate(uid_t uid) const override { return 60_Hz; } - AsyncCallRecorderWithCannedReturn< scheduler::ScheduleResult (*)(scheduler::VSyncDispatch::CallbackToken, scheduler::VSyncDispatch::ScheduleTiming)> @@ -127,11 +121,11 @@ protected: AsyncCallRecorder mVSyncCallbackUnregisterRecorder; AsyncCallRecorder mResyncCallRecorder; - mutable AsyncCallRecorder mThrottleVsyncCallRecorder; + AsyncCallRecorder mThrottleVsyncCallRecorder; ConnectionEventRecorder mConnectionEventCallRecorder{0}; ConnectionEventRecorder mThrottledConnectionEventCallRecorder{0}; - std::shared_ptr mVsyncSchedule; + std::optional mVsyncSchedule; std::unique_ptr mThread; sp mConnection; sp mThrottledConnection; @@ -146,12 +140,12 @@ EventThreadTest::EventThreadTest() { ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - auto mockDispatchPtr = std::make_shared(); - mVsyncSchedule = std::shared_ptr( - new scheduler::VsyncSchedule(INTERNAL_DISPLAY_ID, - std::make_shared(), mockDispatchPtr, - nullptr)); - mock::VSyncDispatch& mockDispatch = *mockDispatchPtr; + mVsyncSchedule.emplace(scheduler::VsyncSchedule(std::make_unique(), + std::make_unique(), + nullptr)); + + mock::VSyncDispatch& mockDispatch = + *static_cast(&mVsyncSchedule->getDispatch()); EXPECT_CALL(mockDispatch, registerCallback(_, _)) .WillRepeatedly(Invoke(mVSyncCallbackRegisterRecorder.getInvocable())); EXPECT_CALL(mockDispatch, schedule(_, _)) @@ -186,10 +180,19 @@ EventThreadTest::~EventThreadTest() { } void EventThreadTest::createThread() { + const auto throttleVsync = [&](nsecs_t expectedVsyncTimestamp, uid_t uid) { + mThrottleVsyncCallRecorder.getInvocable()(expectedVsyncTimestamp, uid); + return (uid == mThrottledConnectionUid); + }; + const auto getVsyncPeriod = [](uid_t uid) { + return VSYNC_PERIOD.count(); + }; + mTokenManager = std::make_unique(); mThread = - std::make_unique("EventThreadTest", mVsyncSchedule, *this, - mTokenManager.get(), kWorkDuration, kReadyDuration); + std::make_unique(/*std::move(source), */ "EventThreadTest", + *mVsyncSchedule, mTokenManager.get(), throttleVsync, + getVsyncPeriod, kWorkDuration, kReadyDuration); // EventThread should register itself as VSyncSource callback. EXPECT_TRUE(mVSyncCallbackRegisterRecorder.waitForCall().has_value()); @@ -222,7 +225,7 @@ void EventThreadTest::expectVSyncSetDurationCallReceived( EXPECT_EQ(expectedReadyDuration.count(), std::get<1>(args.value()).readyDuration); } -void EventThreadTest::expectThrottleVsyncReceived(TimePoint expectedTimestamp, uid_t uid) { +void EventThreadTest::expectThrottleVsyncReceived(nsecs_t expectedTimestamp, uid_t uid) { auto args = mThrottleVsyncCallRecorder.waitForCall(); ASSERT_TRUE(args.has_value()); EXPECT_EQ(expectedTimestamp, std::get<0>(args.value())); @@ -373,7 +376,7 @@ TEST_F(EventThreadTest, requestNextVsyncPostsASingleVSyncEventToTheConnection) { // Use the received callback to signal a first vsync event. // The throttler should receive the event, as well as the connection. onVSyncEvent(123, 456, 789); - expectThrottleVsyncReceived(TimePoint::fromNs(456), mConnectionUid); + expectThrottleVsyncReceived(456, mConnectionUid); expectVsyncEventReceivedByConnection(123, 1u); // EventThread is requesting one more callback due to VsyncRequest::SingleSuppressCallback @@ -491,17 +494,17 @@ TEST_F(EventThreadTest, setVsyncRateOnePostsAllEventsToThatConnection) { // Send a vsync event. EventThread should then make a call to the // throttler, and the connection. onVSyncEvent(123, 456, 789); - expectThrottleVsyncReceived(TimePoint::fromNs(456), mConnectionUid); + expectThrottleVsyncReceived(456, mConnectionUid); expectVsyncEventReceivedByConnection(123, 1u); // A second event should go to the same places. onVSyncEvent(456, 123, 0); - expectThrottleVsyncReceived(TimePoint::fromNs(123), mConnectionUid); + expectThrottleVsyncReceived(123, mConnectionUid); expectVsyncEventReceivedByConnection(456, 2u); // A third event should go to the same places. onVSyncEvent(789, 777, 111); - expectThrottleVsyncReceived(TimePoint::fromNs(777), mConnectionUid); + expectThrottleVsyncReceived(777, mConnectionUid); expectVsyncEventReceivedByConnection(789, 3u); } @@ -740,7 +743,7 @@ TEST_F(EventThreadTest, requestNextVsyncWithThrottleVsyncDoesntPostVSync) { // Use the received callback to signal a first vsync event. // The throttler should receive the event, but not the connection. onVSyncEvent(123, 456, 789); - expectThrottleVsyncReceived(TimePoint::fromNs(456), mThrottledConnectionUid); + expectThrottleVsyncReceived(456, mThrottledConnectionUid); mThrottledConnectionEventCallRecorder.waitForUnexpectedCall(); expectVSyncCallbackScheduleReceived(true); @@ -748,7 +751,7 @@ TEST_F(EventThreadTest, requestNextVsyncWithThrottleVsyncDoesntPostVSync) { // The throttler should receive the event, but the connection should // not as it was only interested in the first. onVSyncEvent(456, 123, 0); - expectThrottleVsyncReceived(TimePoint::fromNs(123), mThrottledConnectionUid); + expectThrottleVsyncReceived(123, mThrottledConnectionUid); EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value()); expectVSyncCallbackScheduleReceived(true); diff --git a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp index 248061cc30..1cd9e49051 100644 --- a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp +++ b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp @@ -137,7 +137,7 @@ void FpsReporterTest::setupScheduler() { ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_shared(); + auto vsyncTracker = std::make_unique(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp index ff7c2c98ba..ac63a0edbd 100644 --- a/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp +++ b/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp @@ -125,7 +125,7 @@ void RefreshRateSelectionTest::setupScheduler() { ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_shared(); + auto vsyncTracker = std::make_unique(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/GameModeTest.cpp b/services/surfaceflinger/tests/unittests/GameModeTest.cpp index ddf871b5ce..29aa7171ba 100644 --- a/services/surfaceflinger/tests/unittests/GameModeTest.cpp +++ b/services/surfaceflinger/tests/unittests/GameModeTest.cpp @@ -76,7 +76,7 @@ public: ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_shared(); + auto vsyncTracker = std::make_unique(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp index 9534f3b548..afbc57add8 100644 --- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp +++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp @@ -118,34 +118,6 @@ TEST_F(HWComposerTest, getActiveMode) { } } -TEST_F(HWComposerTest, onVsync) { - constexpr hal::HWDisplayId kHwcDisplayId = 1; - expectHotplugConnect(kHwcDisplayId); - - const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED); - ASSERT_TRUE(info); - - const auto physicalDisplayId = info->id; - - // Deliberately chosen not to match DisplayData.lastPresentTimestamp's - // initial value. - constexpr nsecs_t kTimestamp = 1; - auto displayIdOpt = mHwc.onVsync(kHwcDisplayId, kTimestamp); - ASSERT_TRUE(displayIdOpt); - EXPECT_EQ(physicalDisplayId, displayIdOpt); - - // Attempt to send the same time stamp again. - displayIdOpt = mHwc.onVsync(kHwcDisplayId, kTimestamp); - EXPECT_FALSE(displayIdOpt); -} - -TEST_F(HWComposerTest, onVsyncInvalid) { - constexpr hal::HWDisplayId kInvalidHwcDisplayId = 2; - constexpr nsecs_t kTimestamp = 1; - const auto displayIdOpt = mHwc.onVsync(kInvalidHwcDisplayId, kTimestamp); - EXPECT_FALSE(displayIdOpt); -} - struct MockHWC2ComposerCallback final : StrictMock { MOCK_METHOD2(onComposerHalHotplug, void(hal::HWDisplayId, hal::Connection)); MOCK_METHOD1(onComposerHalRefresh, void(hal::HWDisplayId)); diff --git a/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp b/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp index 23506b13e9..ee42e19c34 100644 --- a/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp +++ b/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp @@ -64,7 +64,7 @@ void BaseLayerTest::setupScheduler() { ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_shared(); + auto vsyncTracker = std::make_unique(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp index 8f1b450b06..7aa5201f2f 100644 --- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp +++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp @@ -67,12 +67,12 @@ struct MockTokenManager : frametimeline::TokenManager { struct MessageQueueTest : testing::Test { void SetUp() override { - EXPECT_CALL(*mVSyncDispatch, registerCallback(_, "sf")).WillOnce(Return(mCallbackToken)); + EXPECT_CALL(mVSyncDispatch, registerCallback(_, "sf")).WillOnce(Return(mCallbackToken)); EXPECT_NO_FATAL_FAILURE(mEventQueue.initVsync(mVSyncDispatch, mTokenManager, kDuration)); - EXPECT_CALL(*mVSyncDispatch, unregisterCallback(mCallbackToken)).Times(1); + EXPECT_CALL(mVSyncDispatch, unregisterCallback(mCallbackToken)).Times(1); } - std::shared_ptr mVSyncDispatch = std::make_shared(); + mock::VSyncDispatch mVSyncDispatch; MockTokenManager mTokenManager; TestableMessageQueue mEventQueue; @@ -90,7 +90,7 @@ TEST_F(MessageQueueTest, commit) { .earliestVsync = 0}; EXPECT_FALSE(mEventQueue.getScheduledFrameTime()); - EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); + EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); ASSERT_TRUE(mEventQueue.getScheduledFrameTime()); @@ -103,13 +103,13 @@ TEST_F(MessageQueueTest, commitTwice) { .readyDuration = 0, .earliestVsync = 0}; - EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); + EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); ASSERT_TRUE(mEventQueue.getScheduledFrameTime()); EXPECT_EQ(1234, mEventQueue.getScheduledFrameTime()->time_since_epoch().count()); - EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(4567)); + EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(4567)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); ASSERT_TRUE(mEventQueue.getScheduledFrameTime()); @@ -122,7 +122,7 @@ TEST_F(MessageQueueTest, commitTwiceWithCallback) { .readyDuration = 0, .earliestVsync = 0}; - EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); + EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); ASSERT_TRUE(mEventQueue.getScheduledFrameTime()); @@ -149,7 +149,7 @@ TEST_F(MessageQueueTest, commitTwiceWithCallback) { .readyDuration = 0, .earliestVsync = kPresentTime.ns()}; - EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timingAfterCallback)).WillOnce(Return(0)); + EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timingAfterCallback)).WillOnce(Return(0)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); } @@ -161,7 +161,7 @@ TEST_F(MessageQueueTest, commitWithDurationChange) { .readyDuration = 0, .earliestVsync = 0}; - EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(0)); + EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(0)); EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame()); } diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index f0dd06d758..4b15385fa8 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -14,7 +14,6 @@ * limitations under the License. */ -#include #include #include #include @@ -177,7 +176,7 @@ TEST_F(SchedulerTest, chooseRefreshRateForContentIsNoopWhenModeSwitchingIsNotSup ASSERT_EQ(0u, mScheduler->getNumActiveLayers()); constexpr hal::PowerMode kPowerModeOn = hal::PowerMode::ON; - FTL_FAKE_GUARD(kMainThreadContext, mScheduler->setDisplayPowerMode(kDisplayId1, kPowerModeOn)); + mScheduler->setDisplayPowerMode(kPowerModeOn); constexpr uint32_t kDisplayArea = 999'999; mScheduler->onActiveDisplayAreaChanged(kDisplayArea); @@ -249,7 +248,7 @@ TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) { mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer); constexpr hal::PowerMode kPowerModeOn = hal::PowerMode::ON; - FTL_FAKE_GUARD(kMainThreadContext, mScheduler->setDisplayPowerMode(kDisplayId1, kPowerModeOn)); + mScheduler->setDisplayPowerMode(kPowerModeOn); constexpr uint32_t kDisplayArea = 999'999; mScheduler->onActiveDisplayAreaChanged(kDisplayArea); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp index 31f948fd68..ad3bd353ed 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp @@ -100,7 +100,7 @@ void DisplayModeSwitchingTest::setupScheduler( ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_shared(); + auto vsyncTracker = std::make_unique(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp index 98644aa89d..f553a23f3c 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp @@ -44,10 +44,7 @@ TEST_F(OnInitializeDisplaysTest, onInitializeDisplaysSetsUpPrimaryDisplay) { // We expect a scheduled commit for the display transaction. EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1); - EXPECT_CALL(static_cast( - mFlinger.scheduler()->getVsyncSchedule()->getTracker()), - nextAnticipatedVSyncTimeFrom(_)) - .WillRepeatedly(Return(0)); + EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); // -------------------------------------------------------------------- // Invocation diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp index 2a0f2efb75..622717f290 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp @@ -116,7 +116,7 @@ void SurfaceFlingerPowerHintTest::setupScheduler() { ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_shared(); + auto vsyncTracker = std::make_unique(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp index 80ad22cebe..ab732ed485 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp @@ -61,7 +61,7 @@ struct DozeNotSupportedVariant { struct EventThreadBaseSupportedVariant { static void setupVsyncAndEventThreadNoCallExpectations(DisplayTransactionTest* test) { // The callback should not be notified to toggle VSYNC. - EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_, _)).Times(0); + EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_)).Times(0); // The event thread should not be notified. EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(0); @@ -71,28 +71,24 @@ struct EventThreadBaseSupportedVariant { struct EventThreadNotSupportedVariant : public EventThreadBaseSupportedVariant { static void setupAcquireAndEnableVsyncCallExpectations(DisplayTransactionTest* test) { - // The callback should be notified to enable VSYNC. - EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_, true)).Times(1); + // These calls are only expected for the primary display. - // The event thread should not be notified. - EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(0); - EXPECT_CALL(*test->mEventThread, onScreenAcquired()).Times(0); + // Instead expect no calls. + setupVsyncAndEventThreadNoCallExpectations(test); } static void setupReleaseAndDisableVsyncCallExpectations(DisplayTransactionTest* test) { - // The callback should be notified to disable VSYNC. - EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_, false)).Times(1); + // These calls are only expected for the primary display. - // The event thread should not be notified. - EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(0); - EXPECT_CALL(*test->mEventThread, onScreenAcquired()).Times(0); + // Instead expect no calls. + setupVsyncAndEventThreadNoCallExpectations(test); } }; struct EventThreadIsSupportedVariant : public EventThreadBaseSupportedVariant { static void setupAcquireAndEnableVsyncCallExpectations(DisplayTransactionTest* test) { // The callback should be notified to enable VSYNC. - EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_, true)).Times(1); + EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(true)).Times(1); // The event thread should be notified that the screen was acquired. EXPECT_CALL(*test->mEventThread, onScreenAcquired()).Times(1); @@ -100,7 +96,7 @@ struct EventThreadIsSupportedVariant : public EventThreadBaseSupportedVariant { static void setupReleaseAndDisableVsyncCallExpectations(DisplayTransactionTest* test) { // The callback should be notified to disable VSYNC. - EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_, false)).Times(1); + EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(false)).Times(1); // The event thread should not be notified that the screen was released. EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(1); @@ -109,12 +105,8 @@ struct EventThreadIsSupportedVariant : public EventThreadBaseSupportedVariant { struct DispSyncIsSupportedVariant { static void setupResetModelCallExpectations(DisplayTransactionTest* test) { - auto vsyncSchedule = test->mFlinger.scheduler()->getVsyncSchedule(); - EXPECT_CALL(static_cast(vsyncSchedule->getController()), - startPeriodTransition(DEFAULT_VSYNC_PERIOD, false)) - .Times(1); - EXPECT_CALL(static_cast(vsyncSchedule->getTracker()), resetModel()) - .Times(1); + EXPECT_CALL(*test->mVsyncController, startPeriodTransition(DEFAULT_VSYNC_PERIOD)).Times(1); + EXPECT_CALL(*test->mVSyncTracker, resetModel()).Times(1); } }; @@ -270,9 +262,8 @@ struct DisplayPowerCase { return display; } - static void setInitialHwVsyncEnabled(DisplayTransactionTest* test, PhysicalDisplayId id, - bool enabled) { - test->mFlinger.scheduler()->setInitialHwVsyncEnabled(id, enabled); + static void setInitialPrimaryHWVsyncEnabled(DisplayTransactionTest* test, bool enabled) { + test->mFlinger.scheduler()->mutablePrimaryHWVsyncEnabled() = enabled; } static void setupRepaintEverythingCallExpectations(DisplayTransactionTest* test) { @@ -309,11 +300,6 @@ using PrimaryDisplayPowerCase = // A sample configuration for the external display. // In addition to not having event thread support, we emulate not having doze // support. -// FIXME (b/267483230): ExternalDisplay supports the features tracked in -// DispSyncIsSupportedVariant, but is the follower, so the -// expectations set by DispSyncIsSupportedVariant don't match (wrong schedule). -// We need a way to retrieve the proper DisplayId from -// setupResetModelCallExpectations (or pass it in). template using ExternalDisplayPowerCase = DisplayPowerCase, @@ -343,12 +329,9 @@ void SetPowerModeInternalTest::transitionDisplayCommon() { Case::Doze::setupComposerCallExpectations(this); auto display = Case::injectDisplayWithInitialPowerMode(this, Case::Transition::INITIAL_POWER_MODE); - auto displayId = display->getId(); - if (auto physicalDisplayId = PhysicalDisplayId::tryCast(displayId)) { - Case::setInitialHwVsyncEnabled(this, *physicalDisplayId, - PowerModeInitialVSyncEnabled< - Case::Transition::INITIAL_POWER_MODE>::value); - } + Case::setInitialPrimaryHWVsyncEnabled(this, + PowerModeInitialVSyncEnabled< + Case::Transition::INITIAL_POWER_MODE>::value); // -------------------------------------------------------------------- // Call Expectations diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp index 7e1458806b..fed6a1ae56 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp @@ -34,7 +34,7 @@ protected: ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_shared(); + auto vsyncTracker = std::make_unique(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index c360f934f8..6cf61416c0 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -37,16 +37,19 @@ class TestableScheduler : public Scheduler, private ICompositor { public: TestableScheduler(RefreshRateSelectorPtr selectorPtr, ISchedulerCallback& callback) : TestableScheduler(std::make_unique(), - std::make_shared(), std::move(selectorPtr), + std::make_unique(), std::move(selectorPtr), /* modulatorPtr */ nullptr, callback) {} TestableScheduler(std::unique_ptr controller, - std::shared_ptr tracker, RefreshRateSelectorPtr selectorPtr, + std::unique_ptr tracker, RefreshRateSelectorPtr selectorPtr, sp modulatorPtr, ISchedulerCallback& callback) : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) { + mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), + std::make_unique(), + std::move(controller))); + const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId(); - registerDisplay(displayId, std::move(selectorPtr), std::move(controller), - std::move(tracker)); + registerDisplay(displayId, std::move(selectorPtr)); ON_CALL(*this, postMessage).WillByDefault([](sp&& handler) { // Execute task to prevent broken promise exception on destruction. @@ -63,6 +66,13 @@ public: return Scheduler::createConnection(std::move(eventThread)); } + /* ------------------------------------------------------------------------ + * Read-write access to private data to set up preconditions and assert + * post-conditions. + */ + auto& mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; } + auto& mutableHWVsyncAvailable() { return mHWVsyncAvailable; } + auto refreshRateSelector() { return leaderSelectorPtr(); } const auto& refreshRateSelectors() const NO_THREAD_SAFETY_ANALYSIS { @@ -70,21 +80,8 @@ public: } void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) { - registerDisplay(displayId, std::move(selectorPtr), - std::make_unique(), - std::make_shared()); - } - - void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr, - std::unique_ptr controller, - std::shared_ptr tracker) { ftl::FakeGuard guard(kMainThreadContext); - Scheduler::registerDisplayInternal(displayId, std::move(selectorPtr), - std::shared_ptr( - new VsyncSchedule(displayId, std::move(tracker), - std::make_shared< - mock::VSyncDispatch>(), - std::move(controller)))); + Scheduler::registerDisplay(displayId, std::move(selectorPtr)); } void unregisterDisplay(PhysicalDisplayId displayId) { @@ -160,13 +157,6 @@ public: Scheduler::onNonPrimaryDisplayModeChanged(handle, mode); } - void setInitialHwVsyncEnabled(PhysicalDisplayId id, bool enabled) { - auto schedule = getVsyncSchedule(id); - std::lock_guard lock(schedule->mHwVsyncLock); - schedule->mHwVsyncState = enabled ? VsyncSchedule::HwVsyncState::Enabled - : VsyncSchedule::HwVsyncState::Disabled; - } - private: // ICompositor overrides: void configure() override {} diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 63b79a4436..68c738fc9d 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -201,7 +201,7 @@ public: std::variant; void setupScheduler(std::unique_ptr vsyncController, - std::shared_ptr vsyncTracker, + std::unique_ptr vsyncTracker, std::unique_ptr appEventThread, std::unique_ptr sfEventThread, SchedulerCallbackImpl callbackImpl = SchedulerCallbackImpl::kNoOp, @@ -253,7 +253,7 @@ public: std::move(modulatorPtr), callback); } - mScheduler->initVsync(mScheduler->getVsyncSchedule()->getDispatch(), *mTokenManager, 0ms); + mScheduler->initVsync(mScheduler->getVsyncSchedule().getDispatch(), *mTokenManager, 0ms); mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread)); mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread)); diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index a9a617bfa1..859f702fe7 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -83,14 +83,15 @@ public: mFlinger.setupComposer(std::make_unique()); mFlinger.setupScheduler(std::unique_ptr(mVsyncController), - mVSyncTracker, std::move(eventThread), std::move(sfEventThread)); + std::unique_ptr(mVSyncTracker), + std::move(eventThread), std::move(sfEventThread)); mFlinger.flinger()->addTransactionReadyFilters(); } TestableSurfaceFlinger mFlinger; mock::VsyncController* mVsyncController = new mock::VsyncController(); - std::shared_ptr mVSyncTracker = std::make_shared(); + mock::VSyncTracker* mVSyncTracker = new mock::VSyncTracker(); struct TransactionInfo { Vector states; diff --git a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp index b228bcbbd2..1173d1c876 100644 --- a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp @@ -85,7 +85,7 @@ public: ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_shared(); + auto vsyncTracker = std::make_unique(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp index bfebecd2e2..ae03db43a7 100644 --- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp @@ -84,7 +84,7 @@ public: ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_shared(); + auto vsyncTracker = std::make_unique(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp index aa33716ed8..da87f1db17 100644 --- a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp +++ b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp @@ -117,7 +117,7 @@ void TunnelModeEnabledReporterTest::setupScheduler() { ResyncCallback()))); auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_shared(); + auto vsyncTracker = std::make_unique(); EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); EXPECT_CALL(*vsyncTracker, currentPeriod()) diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp index fcd2f562d7..47c2deef51 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp @@ -109,8 +109,7 @@ struct VSyncDispatchRealtimeTest : testing::Test { class RepeatingCallbackReceiver { public: - RepeatingCallbackReceiver(std::shared_ptr dispatch, nsecs_t workload, - nsecs_t readyDuration) + RepeatingCallbackReceiver(VSyncDispatch& dispatch, nsecs_t workload, nsecs_t readyDuration) : mWorkload(workload), mReadyDuration(readyDuration), mCallback( @@ -167,10 +166,9 @@ private: }; TEST_F(VSyncDispatchRealtimeTest, triple_alarm) { - auto tracker = std::make_shared(); - auto dispatch = - std::make_shared(std::make_unique(), tracker, - mDispatchGroupThreshold, mVsyncMoveThreshold); + FixedRateIdealStubTracker tracker; + VSyncDispatchTimerQueue dispatch(std::make_unique(), tracker, mDispatchGroupThreshold, + mVsyncMoveThreshold); static size_t constexpr num_clients = 3; std::array @@ -197,15 +195,14 @@ TEST_F(VSyncDispatchRealtimeTest, triple_alarm) { // starts at 333hz, slides down to 43hz TEST_F(VSyncDispatchRealtimeTest, vascillating_vrr) { auto next_vsync_interval = toNs(3ms); - auto tracker = std::make_shared(next_vsync_interval); - auto dispatch = - std::make_shared(std::make_unique(), tracker, - mDispatchGroupThreshold, mVsyncMoveThreshold); + VRRStubTracker tracker(next_vsync_interval); + VSyncDispatchTimerQueue dispatch(std::make_unique(), tracker, mDispatchGroupThreshold, + mVsyncMoveThreshold); RepeatingCallbackReceiver cb_receiver(dispatch, toNs(1ms), toNs(5ms)); auto const on_each_frame = [&](nsecs_t last_known) { - tracker->set_interval(next_vsync_interval += toNs(1ms), last_known); + tracker.set_interval(next_vsync_interval += toNs(1ms), last_known); }; std::thread eventThread([&] { cb_receiver.repeatedly_schedule(mIterations, on_each_frame); }); @@ -216,10 +213,9 @@ TEST_F(VSyncDispatchRealtimeTest, vascillating_vrr) { // starts at 333hz, jumps to 200hz at frame 10 TEST_F(VSyncDispatchRealtimeTest, fixed_jump) { - auto tracker = std::make_shared(toNs(3ms)); - auto dispatch = - std::make_shared(std::make_unique(), tracker, - mDispatchGroupThreshold, mVsyncMoveThreshold); + VRRStubTracker tracker(toNs(3ms)); + VSyncDispatchTimerQueue dispatch(std::make_unique(), tracker, mDispatchGroupThreshold, + mVsyncMoveThreshold); RepeatingCallbackReceiver cb_receiver(dispatch, toNs(1ms), toNs(5ms)); @@ -227,7 +223,7 @@ TEST_F(VSyncDispatchRealtimeTest, fixed_jump) { auto constexpr jump_frame_at = 10u; auto const on_each_frame = [&](nsecs_t last_known) { if (jump_frame_counter++ == jump_frame_at) { - tracker->set_interval(toNs(5ms), last_known); + tracker.set_interval(toNs(5ms), last_known); } }; std::thread eventThread([&] { cb_receiver.repeatedly_schedule(mIterations, on_each_frame); }); diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp index 82daffd28d..14a2860378 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp @@ -116,14 +116,13 @@ private: class CountingCallback { public: - CountingCallback(std::shared_ptr dispatch) - : mDispatch(std::move(dispatch)), - mToken(mDispatch->registerCallback(std::bind(&CountingCallback::counter, this, - std::placeholders::_1, - std::placeholders::_2, - std::placeholders::_3), - "test")) {} - ~CountingCallback() { mDispatch->unregisterCallback(mToken); } + CountingCallback(VSyncDispatch& dispatch) + : mDispatch(dispatch), + mToken(dispatch.registerCallback(std::bind(&CountingCallback::counter, this, + std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3), + "test")) {} + ~CountingCallback() { mDispatch.unregisterCallback(mToken); } operator VSyncDispatch::CallbackToken() const { return mToken; } @@ -133,7 +132,7 @@ public: mReadyTime.push_back(readyTime); } - std::shared_ptr mDispatch; + VSyncDispatch& mDispatch; VSyncDispatch::CallbackToken mToken; std::vector mCalls; std::vector mWakeupTime; @@ -142,12 +141,12 @@ public: class PausingCallback { public: - PausingCallback(std::shared_ptr dispatch, std::chrono::milliseconds pauseAmount) - : mDispatch(std::move(dispatch)), - mToken(mDispatch->registerCallback(std::bind(&PausingCallback::pause, this, - std::placeholders::_1, - std::placeholders::_2), - "test")), + PausingCallback(VSyncDispatch& dispatch, std::chrono::milliseconds pauseAmount) + : mDispatch(dispatch), + mToken(dispatch.registerCallback(std::bind(&PausingCallback::pause, this, + std::placeholders::_1, + std::placeholders::_2), + "test")), mRegistered(true), mPauseAmount(pauseAmount) {} ~PausingCallback() { unregister(); } @@ -182,12 +181,12 @@ public: void unregister() { if (mRegistered) { - mDispatch->unregisterCallback(mToken); + mDispatch.unregisterCallback(mToken); mRegistered = false; } } - std::shared_ptr mDispatch; + VSyncDispatch& mDispatch; VSyncDispatch::CallbackToken mToken; bool mRegistered = true; @@ -232,26 +231,22 @@ protected: static nsecs_t constexpr mDispatchGroupThreshold = 5; nsecs_t const mPeriod = 1000; nsecs_t const mVsyncMoveThreshold = 300; - std::shared_ptr> mStubTracker = - std::make_shared>(mPeriod); - std::shared_ptr mDispatch = - std::make_shared(createTimeKeeper(), mStubTracker, - mDispatchGroupThreshold, mVsyncMoveThreshold); + NiceMock mStubTracker{mPeriod}; + VSyncDispatchTimerQueue mDispatch{createTimeKeeper(), mStubTracker, mDispatchGroupThreshold, + mVsyncMoveThreshold}; }; TEST_F(VSyncDispatchTimerQueueTest, unregistersSetAlarmOnDestruction) { EXPECT_CALL(mMockClock, alarmAt(_, 900)); EXPECT_CALL(mMockClock, alarmCancel()); { - std::shared_ptr mDispatch = - std::make_shared(createTimeKeeper(), mStubTracker, - mDispatchGroupThreshold, - mVsyncMoveThreshold); + VSyncDispatchTimerQueue mDispatch{createTimeKeeper(), mStubTracker, mDispatchGroupThreshold, + mVsyncMoveThreshold}; CountingCallback cb(mDispatch); - const auto result = mDispatch->schedule(cb, - {.workDuration = 100, - .readyDuration = 0, - .earliestVsync = 1000}); + const auto result = mDispatch.schedule(cb, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); } @@ -262,10 +257,10 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFuture) { EXPECT_CALL(mMockClock, alarmAt(_, 900)); CountingCallback cb(mDispatch); - const auto result = mDispatch->schedule(cb, - {.workDuration = 100, - .readyDuration = 0, - .earliestVsync = intended}); + const auto result = mDispatch.schedule(cb, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = intended}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); @@ -282,15 +277,14 @@ TEST_F(VSyncDispatchTimerQueueTest, updateAlarmSettingFuture) { EXPECT_CALL(mMockClock, alarmAt(_, 700)).InSequence(seq); CountingCallback cb(mDispatch); - auto result = mDispatch->schedule(cb, - {.workDuration = 100, - .readyDuration = 0, - .earliestVsync = intended}); + auto result = mDispatch.schedule(cb, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = intended}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); - result = - mDispatch->update(cb, + result = mDispatch.update(cb, {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(700, *result); @@ -308,17 +302,17 @@ TEST_F(VSyncDispatchTimerQueueTest, updateDoesntSchedule) { CountingCallback cb(mDispatch); const auto result = - mDispatch->update(cb, - {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended}); + mDispatch.update(cb, + {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended}); EXPECT_FALSE(result.has_value()); } TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFutureWithAdjustmentToTrueVsync) { - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)).WillOnce(Return(1150)); + EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000)).WillOnce(Return(1150)); EXPECT_CALL(mMockClock, alarmAt(_, 1050)); CountingCallback cb(mDispatch); - mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); + mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); advanceToNextCallback(); ASSERT_THAT(cb.mCalls.size(), Eq(1)); @@ -329,15 +323,15 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingAdjustmentPast) { auto const now = 234; mMockClock.advanceBy(234); auto const workDuration = 10 * mPeriod; - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(now + workDuration)) + EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(now + workDuration)) .WillOnce(Return(mPeriod * 11)); EXPECT_CALL(mMockClock, alarmAt(_, mPeriod)); CountingCallback cb(mDispatch); - const auto result = mDispatch->schedule(cb, - {.workDuration = workDuration, - .readyDuration = 0, - .earliestVsync = mPeriod}); + const auto result = mDispatch.schedule(cb, + {.workDuration = workDuration, + .readyDuration = 0, + .earliestVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod, *result); } @@ -347,13 +341,12 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancel) { EXPECT_CALL(mMockClock, alarmCancel()); CountingCallback cb(mDispatch); - const auto result = mDispatch->schedule(cb, - {.workDuration = 100, - .readyDuration = 0, - .earliestVsync = mPeriod}); + const auto result = + mDispatch.schedule(cb, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod - 100, *result); - EXPECT_EQ(mDispatch->cancel(cb), CancelResult::Cancelled); + EXPECT_EQ(mDispatch.cancel(cb), CancelResult::Cancelled); } TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLate) { @@ -361,14 +354,13 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLate) { EXPECT_CALL(mMockClock, alarmCancel()); CountingCallback cb(mDispatch); - const auto result = mDispatch->schedule(cb, - {.workDuration = 100, - .readyDuration = 0, - .earliestVsync = mPeriod}); + const auto result = + mDispatch.schedule(cb, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod - 100, *result); mMockClock.advanceBy(950); - EXPECT_EQ(mDispatch->cancel(cb), CancelResult::TooLate); + EXPECT_EQ(mDispatch.cancel(cb), CancelResult::TooLate); } TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLateWhenRunning) { @@ -376,16 +368,15 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLateWhenRunning) { EXPECT_CALL(mMockClock, alarmCancel()); PausingCallback cb(mDispatch, std::chrono::duration_cast(1s)); - const auto result = mDispatch->schedule(cb, - {.workDuration = 100, - .readyDuration = 0, - .earliestVsync = mPeriod}); + const auto result = + mDispatch.schedule(cb, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod - 100, *result); std::thread pausingThread([&] { mMockClock.advanceToNextCallback(); }); EXPECT_TRUE(cb.waitForPause()); - EXPECT_EQ(mDispatch->cancel(cb), CancelResult::TooLate); + EXPECT_EQ(mDispatch.cancel(cb), CancelResult::TooLate); cb.unpause(); pausingThread.join(); } @@ -398,10 +389,9 @@ TEST_F(VSyncDispatchTimerQueueTest, unregisterSynchronizes) { PausingCallback cb(mDispatch, 50ms); cb.stashResource(resource); - const auto result = mDispatch->schedule(cb, - {.workDuration = 100, - .readyDuration = 0, - .earliestVsync = mPeriod}); + const auto result = + mDispatch.schedule(cb, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod - 100, *result); @@ -418,7 +408,7 @@ TEST_F(VSyncDispatchTimerQueueTest, unregisterSynchronizes) { } TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) { - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)) + EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000)) .Times(4) .WillOnce(Return(1055)) .WillOnce(Return(1063)) @@ -433,8 +423,8 @@ TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); - mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod}); + mDispatch.schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod}); + mDispatch.schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod}); advanceToNextCallback(); advanceToNextCallback(); @@ -446,7 +436,7 @@ TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) { } TEST_F(VSyncDispatchTimerQueueTest, noCloseCallbacksAfterPeriodChange) { - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_)) + EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_)) .Times(4) .WillOnce(Return(1000)) .WillOnce(Return(2000)) @@ -460,21 +450,21 @@ TEST_F(VSyncDispatchTimerQueueTest, noCloseCallbacksAfterPeriodChange) { CountingCallback cb(mDispatch); - mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 0}); + mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 0}); advanceToNextCallback(); ASSERT_THAT(cb.mCalls.size(), Eq(1)); EXPECT_THAT(cb.mCalls[0], Eq(1000)); - mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); ASSERT_THAT(cb.mCalls.size(), Eq(2)); EXPECT_THAT(cb.mCalls[1], Eq(2000)); - mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); advanceToNextCallback(); @@ -483,7 +473,7 @@ TEST_F(VSyncDispatchTimerQueueTest, noCloseCallbacksAfterPeriodChange) { } TEST_F(VSyncDispatchTimerQueueTest, rearmsFaroutTimeoutWhenCancellingCloseOne) { - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_)) + EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_)) .Times(4) .WillOnce(Return(10000)) .WillOnce(Return(1000)) @@ -498,10 +488,10 @@ TEST_F(VSyncDispatchTimerQueueTest, rearmsFaroutTimeoutWhenCancellingCloseOne) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch->schedule(cb0, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod * 10}); - mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod}); - mDispatch->cancel(cb1); + mDispatch.schedule(cb0, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod * 10}); + mDispatch.schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod}); + mDispatch.cancel(cb1); } TEST_F(VSyncDispatchTimerQueueTest, noUnnecessaryRearmsWhenRescheduling) { @@ -512,9 +502,9 @@ TEST_F(VSyncDispatchTimerQueueTest, noUnnecessaryRearmsWhenRescheduling) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch->schedule(cb1, {.workDuration = 300, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb1, {.workDuration = 300, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); } @@ -527,9 +517,9 @@ TEST_F(VSyncDispatchTimerQueueTest, necessaryRearmsWhenModifying) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); } @@ -547,10 +537,10 @@ TEST_F(VSyncDispatchTimerQueueTest, modifyIntoGroup) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch->schedule(cb1, - {.workDuration = closeOffset, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb1, + {.workDuration = closeOffset, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); ASSERT_THAT(cb0.mCalls.size(), Eq(1)); @@ -558,11 +548,9 @@ TEST_F(VSyncDispatchTimerQueueTest, modifyIntoGroup) { ASSERT_THAT(cb1.mCalls.size(), Eq(1)); EXPECT_THAT(cb1.mCalls[0], Eq(mPeriod)); - mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 2000}); - mDispatch->schedule(cb1, - {.workDuration = notCloseOffset, - .readyDuration = 0, - .earliestVsync = 2000}); + mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch.schedule(cb1, + {.workDuration = notCloseOffset, .readyDuration = 0, .earliestVsync = 2000}); advanceToNextCallback(); ASSERT_THAT(cb1.mCalls.size(), Eq(2)); EXPECT_THAT(cb1.mCalls[1], Eq(2000)); @@ -582,32 +570,32 @@ TEST_F(VSyncDispatchTimerQueueTest, rearmsWhenEndingAndDoesntCancel) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); - EXPECT_EQ(mDispatch->cancel(cb0), CancelResult::Cancelled); + EXPECT_EQ(mDispatch.cancel(cb0), CancelResult::Cancelled); } TEST_F(VSyncDispatchTimerQueueTest, setAlarmCallsAtCorrectTimeWithChangingVsync) { - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_)) + EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_)) .Times(3) .WillOnce(Return(950)) .WillOnce(Return(1975)) .WillOnce(Return(2950)); CountingCallback cb(mDispatch); - mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 920}); + mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 920}); mMockClock.advanceBy(850); EXPECT_THAT(cb.mCalls.size(), Eq(1)); - mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1900}); + mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1900}); mMockClock.advanceBy(900); EXPECT_THAT(cb.mCalls.size(), Eq(1)); mMockClock.advanceBy(125); EXPECT_THAT(cb.mCalls.size(), Eq(2)); - mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2900}); + mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2900}); mMockClock.advanceBy(975); EXPECT_THAT(cb.mCalls.size(), Eq(3)); } @@ -618,48 +606,48 @@ TEST_F(VSyncDispatchTimerQueueTest, callbackReentrancy) { EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq); VSyncDispatch::CallbackToken tmp; - tmp = mDispatch->registerCallback( + tmp = mDispatch.registerCallback( [&](auto, auto, auto) { - mDispatch->schedule(tmp, - {.workDuration = 100, - .readyDuration = 0, - .earliestVsync = 2000}); + mDispatch.schedule(tmp, + {.workDuration = 100, + .readyDuration = 0, + .earliestVsync = 2000}); }, "o.o"); - mDispatch->schedule(tmp, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(tmp, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); } TEST_F(VSyncDispatchTimerQueueTest, callbackReentrantWithPastWakeup) { VSyncDispatch::CallbackToken tmp; std::optional lastTarget; - tmp = mDispatch->registerCallback( + tmp = mDispatch.registerCallback( [&](auto timestamp, auto, auto) { auto result = - mDispatch->schedule(tmp, - {.workDuration = 400, - .readyDuration = 0, - .earliestVsync = timestamp - mVsyncMoveThreshold}); + mDispatch.schedule(tmp, + {.workDuration = 400, + .readyDuration = 0, + .earliestVsync = timestamp - mVsyncMoveThreshold}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod + timestamp - 400, *result); - result = mDispatch->schedule(tmp, - {.workDuration = 400, - .readyDuration = 0, - .earliestVsync = timestamp}); + result = mDispatch.schedule(tmp, + {.workDuration = 400, + .readyDuration = 0, + .earliestVsync = timestamp}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod + timestamp - 400, *result); - result = mDispatch->schedule(tmp, - {.workDuration = 400, - .readyDuration = 0, - .earliestVsync = timestamp + mVsyncMoveThreshold}); + result = mDispatch.schedule(tmp, + {.workDuration = 400, + .readyDuration = 0, + .earliestVsync = timestamp + mVsyncMoveThreshold}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(mPeriod + timestamp - 400, *result); lastTarget = timestamp; }, "oo"); - mDispatch->schedule(tmp, {.workDuration = 999, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(tmp, {.workDuration = 999, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); EXPECT_THAT(lastTarget, Eq(1000)); @@ -675,16 +663,16 @@ TEST_F(VSyncDispatchTimerQueueTest, modificationsAroundVsyncTime) { EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq); CountingCallback cb(mDispatch); - mDispatch->schedule(cb, {.workDuration = 0, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb, {.workDuration = 0, .readyDuration = 0, .earliestVsync = 1000}); mMockClock.advanceBy(750); - mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); - mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch.schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 2000}); mMockClock.advanceBy(800); - mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); } TEST_F(VSyncDispatchTimerQueueTest, lateModifications) { @@ -697,12 +685,12 @@ TEST_F(VSyncDispatchTimerQueueTest, lateModifications) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch->schedule(cb1, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb1, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); - mDispatch->schedule(cb0, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 2000}); - mDispatch->schedule(cb1, {.workDuration = 150, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb0, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 2000}); + mDispatch.schedule(cb1, {.workDuration = 150, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); advanceToNextCallback(); @@ -714,8 +702,8 @@ TEST_F(VSyncDispatchTimerQueueTest, doesntCancelPriorValidTimerForFutureMod) { CountingCallback cb0(mDispatch); CountingCallback cb1(mDispatch); - mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 20000}); + mDispatch.schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 20000}); } TEST_F(VSyncDispatchTimerQueueTest, setsTimerAfterCancellation) { @@ -725,30 +713,29 @@ TEST_F(VSyncDispatchTimerQueueTest, setsTimerAfterCancellation) { EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq); CountingCallback cb0(mDispatch); - mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch->cancel(cb0); - mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.cancel(cb0); + mDispatch.schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); } TEST_F(VSyncDispatchTimerQueueTest, makingUpIdsError) { VSyncDispatch::CallbackToken token(100); - EXPECT_FALSE( - mDispatch - ->schedule(token, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}) - .has_value()); - EXPECT_THAT(mDispatch->cancel(token), Eq(CancelResult::Error)); + EXPECT_FALSE(mDispatch + .schedule(token, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}) + .has_value()); + EXPECT_THAT(mDispatch.cancel(token), Eq(CancelResult::Error)); } TEST_F(VSyncDispatchTimerQueueTest, canMoveCallbackBackwardsInTime) { CountingCallback cb0(mDispatch); auto result = - mDispatch->schedule(cb0, - {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb0, + {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(500, *result); - result = mDispatch->schedule(cb0, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); + result = mDispatch.schedule(cb0, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); } @@ -758,14 +745,14 @@ TEST_F(VSyncDispatchTimerQueueTest, doesNotMoveCallbackBackwardsAndSkipASchedule EXPECT_CALL(mMockClock, alarmAt(_, 500)); CountingCallback cb(mDispatch); auto result = - mDispatch->schedule(cb, - {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb, + {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(500, *result); mMockClock.advanceBy(400); - result = mDispatch->schedule(cb, - {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000}); + result = mDispatch.schedule(cb, + {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1200, *result); advanceToNextCallback(); @@ -773,19 +760,19 @@ TEST_F(VSyncDispatchTimerQueueTest, doesNotMoveCallbackBackwardsAndSkipASchedule } TEST_F(VSyncDispatchTimerQueueTest, targetOffsetMovingBackALittleCanStillSchedule) { - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)) + EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000)) .Times(2) .WillOnce(Return(1000)) .WillOnce(Return(1002)); CountingCallback cb(mDispatch); auto result = - mDispatch->schedule(cb, - {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb, + {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(500, *result); mMockClock.advanceBy(400); - result = mDispatch->schedule(cb, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + result = mDispatch.schedule(cb, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(602, *result); } @@ -793,13 +780,13 @@ TEST_F(VSyncDispatchTimerQueueTest, targetOffsetMovingBackALittleCanStillSchedul TEST_F(VSyncDispatchTimerQueueTest, canScheduleNegativeOffsetAgainstDifferentPeriods) { CountingCallback cb0(mDispatch); auto result = - mDispatch->schedule(cb0, - {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb0, + {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(500, *result); advanceToNextCallback(); - result = mDispatch->schedule(cb0, - {.workDuration = 1100, .readyDuration = 0, .earliestVsync = 2000}); + result = mDispatch.schedule(cb0, + {.workDuration = 1100, .readyDuration = 0, .earliestVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); } @@ -810,13 +797,13 @@ TEST_F(VSyncDispatchTimerQueueTest, canScheduleLargeNegativeOffset) { EXPECT_CALL(mMockClock, alarmAt(_, 1100)).InSequence(seq); CountingCallback cb0(mDispatch); auto result = - mDispatch->schedule(cb0, - {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb0, + {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(500, *result); advanceToNextCallback(); - result = mDispatch->schedule(cb0, - {.workDuration = 1900, .readyDuration = 0, .earliestVsync = 2000}); + result = mDispatch.schedule(cb0, + {.workDuration = 1900, .readyDuration = 0, .earliestVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1100, *result); } @@ -826,13 +813,13 @@ TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesNotAffectSchedulingState) CountingCallback cb(mDispatch); auto result = - mDispatch->schedule(cb, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); - result = mDispatch->schedule(cb, - {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000}); + result = mDispatch.schedule(cb, + {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); @@ -878,16 +865,16 @@ TEST_F(VSyncDispatchTimerQueueTest, skipsSchedulingIfTimerReschedulingIsImminent CountingCallback cb2(mDispatch); auto result = - mDispatch->schedule(cb1, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb1, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); mMockClock.setLag(100); mMockClock.advanceBy(620); - result = mDispatch->schedule(cb2, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); + result = mDispatch.schedule(cb2, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1900, *result); mMockClock.advanceBy(80); @@ -906,16 +893,16 @@ TEST_F(VSyncDispatchTimerQueueTest, skipsSchedulingIfTimerReschedulingIsImminent CountingCallback cb(mDispatch); auto result = - mDispatch->schedule(cb, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); mMockClock.setLag(100); mMockClock.advanceBy(620); - result = mDispatch->schedule(cb, - {.workDuration = 370, .readyDuration = 0, .earliestVsync = 2000}); + result = mDispatch.schedule(cb, + {.workDuration = 370, .readyDuration = 0, .earliestVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1630, *result); mMockClock.advanceBy(80); @@ -932,19 +919,19 @@ TEST_F(VSyncDispatchTimerQueueTest, skipsRearmingWhenNotNextScheduled) { CountingCallback cb2(mDispatch); auto result = - mDispatch->schedule(cb1, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb1, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); - result = mDispatch->schedule(cb2, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); + result = mDispatch.schedule(cb2, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1900, *result); mMockClock.setLag(100); mMockClock.advanceBy(620); - EXPECT_EQ(mDispatch->cancel(cb2), CancelResult::Cancelled); + EXPECT_EQ(mDispatch.cancel(cb2), CancelResult::Cancelled); mMockClock.advanceBy(80); @@ -961,19 +948,19 @@ TEST_F(VSyncDispatchTimerQueueTest, rearmsWhenCancelledAndIsNextScheduled) { CountingCallback cb2(mDispatch); auto result = - mDispatch->schedule(cb1, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb1, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); - result = mDispatch->schedule(cb2, - {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); + result = mDispatch.schedule(cb2, + {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(1900, *result); mMockClock.setLag(100); mMockClock.advanceBy(620); - EXPECT_EQ(mDispatch->cancel(cb1), CancelResult::Cancelled); + EXPECT_EQ(mDispatch.cancel(cb1), CancelResult::Cancelled); EXPECT_THAT(cb1.mCalls.size(), Eq(0)); EXPECT_THAT(cb2.mCalls.size(), Eq(0)); @@ -988,21 +975,21 @@ TEST_F(VSyncDispatchTimerQueueTest, laggedTimerGroupsCallbacksWithinLag) { CountingCallback cb2(mDispatch); Sequence seq; - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)) + EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000)) .InSequence(seq) .WillOnce(Return(1000)); EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq); - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)) + EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000)) .InSequence(seq) .WillOnce(Return(1000)); auto result = - mDispatch->schedule(cb1, - {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb1, + {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(600, *result); - result = mDispatch->schedule(cb2, - {.workDuration = 390, .readyDuration = 0, .earliestVsync = 1000}); + result = mDispatch.schedule(cb2, + {.workDuration = 390, .readyDuration = 0, .earliestVsync = 1000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(610, *result); @@ -1024,10 +1011,10 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFutureWithReadyDuration) { EXPECT_CALL(mMockClock, alarmAt(_, 900)); CountingCallback cb(mDispatch); - const auto result = mDispatch->schedule(cb, - {.workDuration = 70, - .readyDuration = 30, - .earliestVsync = intended}); + const auto result = mDispatch.schedule(cb, + {.workDuration = 70, + .readyDuration = 30, + .earliestVsync = intended}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(900, *result); advanceToNextCallback(); @@ -1046,8 +1033,8 @@ TEST_F(VSyncDispatchTimerQueueTest, updatesVsyncTimeForCloseWakeupTime) { CountingCallback cb(mDispatch); - mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); - mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000}); + mDispatch.schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000}); advanceToNextCallback(); @@ -1065,8 +1052,7 @@ class VSyncDispatchTimerQueueEntryTest : public testing::Test { protected: nsecs_t const mPeriod = 1000; nsecs_t const mVsyncMoveThreshold = 200; - std::shared_ptr> mStubTracker = - std::make_shared>(mPeriod); + NiceMock mStubTracker{mPeriod}; }; TEST_F(VSyncDispatchTimerQueueEntryTest, stateAfterInitialization) { @@ -1084,7 +1070,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, stateScheduling) { EXPECT_FALSE(entry.wakeupTime()); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - *mStubTracker.get(), 0) + mStubTracker, 0) .has_value()); auto const wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); @@ -1098,7 +1084,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, stateSchedulingReallyLongWakeupLatency) auto const duration = 500; auto const now = 8750; - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(now + duration)) + EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(now + duration)) .Times(1) .WillOnce(Return(10000)); VSyncDispatchTimerQueueEntry entry( @@ -1106,7 +1092,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, stateSchedulingReallyLongWakeupLatency) EXPECT_FALSE(entry.wakeupTime()); EXPECT_TRUE(entry.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 994}, - *mStubTracker.get(), now) + mStubTracker, now) .has_value()); auto const wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); @@ -1129,7 +1115,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, runCallback) { mVsyncMoveThreshold); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - *mStubTracker.get(), 0) + mStubTracker, 0) .has_value()); auto const wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); @@ -1151,7 +1137,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, runCallback) { } TEST_F(VSyncDispatchTimerQueueEntryTest, updateCallback) { - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_)) + EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_)) .Times(2) .WillOnce(Return(1000)) .WillOnce(Return(1020)); @@ -1160,17 +1146,17 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, updateCallback) { "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); EXPECT_FALSE(entry.wakeupTime()); - entry.update(*mStubTracker.get(), 0); + entry.update(mStubTracker, 0); EXPECT_FALSE(entry.wakeupTime()); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - *mStubTracker.get(), 0) + mStubTracker, 0) .has_value()); auto wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); EXPECT_THAT(wakeup, Eq(900)); - entry.update(*mStubTracker.get(), 0); + entry.update(mStubTracker, 0); wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); EXPECT_THAT(*wakeup, Eq(920)); @@ -1180,9 +1166,9 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, skipsUpdateIfJustScheduled) { VSyncDispatchTimerQueueEntry entry( "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - *mStubTracker.get(), 0) + mStubTracker, 0) .has_value()); - entry.update(*mStubTracker.get(), 0); + entry.update(mStubTracker, 0); auto const wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); @@ -1193,24 +1179,24 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, willSnapToNextTargettableVSync) { VSyncDispatchTimerQueueEntry entry( "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - *mStubTracker.get(), 0) + mStubTracker, 0) .has_value()); entry.executing(); // 1000 is executing // had 1000 not been executing, this could have been scheduled for time 800. EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500}, - *mStubTracker.get(), 0) + mStubTracker, 0) .has_value()); EXPECT_THAT(*entry.wakeupTime(), Eq(1800)); EXPECT_THAT(*entry.readyTime(), Eq(2000)); EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500}, - *mStubTracker.get(), 0) + mStubTracker, 0) .has_value()); EXPECT_THAT(*entry.wakeupTime(), Eq(1950)); EXPECT_THAT(*entry.readyTime(), Eq(2000)); EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 1001}, - *mStubTracker.get(), 0) + mStubTracker, 0) .has_value()); EXPECT_THAT(*entry.wakeupTime(), Eq(1800)); EXPECT_THAT(*entry.readyTime(), Eq(2000)); @@ -1222,24 +1208,24 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); Sequence seq; - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500)) + EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(500)) .InSequence(seq) .WillOnce(Return(1000)); - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500)) + EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(500)) .InSequence(seq) .WillOnce(Return(1000)); - EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold)) + EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold)) .InSequence(seq) .WillOnce(Return(2000)); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - *mStubTracker.get(), 0) + mStubTracker, 0) .has_value()); entry.executing(); // 1000 is executing EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500}, - *mStubTracker.get(), 0) + mStubTracker, 0) .has_value()); } @@ -1247,16 +1233,16 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, reportsScheduledIfStillTime) { VSyncDispatchTimerQueueEntry entry( "test", [](auto, auto, auto) {}, mVsyncMoveThreshold); EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500}, - *mStubTracker.get(), 0) + mStubTracker, 0) .has_value()); EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500}, - *mStubTracker.get(), 0) + mStubTracker, 0) .has_value()); EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500}, - *mStubTracker.get(), 0) + mStubTracker, 0) .has_value()); EXPECT_TRUE(entry.schedule({.workDuration = 1200, .readyDuration = 0, .earliestVsync = 500}, - *mStubTracker.get(), 0) + mStubTracker, 0) .has_value()); } @@ -1269,7 +1255,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, storesPendingUpdatesUntilUpdate) { entry.addPendingWorkloadUpdate( {.workDuration = effectualOffset, .readyDuration = 0, .earliestVsync = 400}); EXPECT_TRUE(entry.hasPendingWorkloadUpdate()); - entry.update(*mStubTracker.get(), 0); + entry.update(mStubTracker, 0); EXPECT_FALSE(entry.hasPendingWorkloadUpdate()); EXPECT_THAT(*entry.wakeupTime(), Eq(mPeriod - effectualOffset)); } @@ -1290,7 +1276,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, runCallbackWithReadyDuration) { mVsyncMoveThreshold); EXPECT_TRUE(entry.schedule({.workDuration = 70, .readyDuration = 30, .earliestVsync = 500}, - *mStubTracker.get(), 0) + mStubTracker, 0) .has_value()); auto const wakeup = entry.wakeupTime(); ASSERT_TRUE(wakeup); diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index 7947a5e97a..3095e8aa9a 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -55,7 +55,7 @@ struct VSyncPredictorTest : testing::Test { static constexpr size_t kOutlierTolerancePercent = 25; static constexpr nsecs_t mMaxRoundingError = 100; - VSyncPredictor tracker{"tracker", mPeriod, kHistorySize, kMinimumSamplesForPrediction, + VSyncPredictor tracker{mPeriod, kHistorySize, kMinimumSamplesForPrediction, kOutlierTolerancePercent}; }; @@ -376,8 +376,7 @@ TEST_F(VSyncPredictorTest, doesNotPredictBeforeTimePointWithHigherIntercept) { // See b/151146131 TEST_F(VSyncPredictorTest, hasEnoughPrecision) { - VSyncPredictor tracker{"tracker", mPeriod, 20, kMinimumSamplesForPrediction, - kOutlierTolerancePercent}; + VSyncPredictor tracker{mPeriod, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent}; std::vector const simulatedVsyncs{840873348817, 840890049444, 840906762675, 840923581635, 840940161584, 840956868096, 840973702473, 840990256277, 841007116851, diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp index a2de1360eb..1fb2709f8d 100644 --- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp @@ -96,8 +96,8 @@ protected: VSyncReactorTest() : mMockTracker(std::make_shared>()), mMockClock(std::make_shared>()), - mReactor("reactor", std::make_unique(mMockClock), *mMockTracker, - kPendingLimit, false /* supportKernelIdleTimer */) { + mReactor(std::make_unique(mMockClock), *mMockTracker, kPendingLimit, + false /* supportKernelIdleTimer */) { ON_CALL(*mMockClock, now()).WillByDefault(Return(mFakeNow)); ON_CALL(*mMockTracker, currentPeriod()).WillByDefault(Return(period)); } @@ -192,7 +192,7 @@ TEST_F(VSyncReactorTest, ignoresProperlyAfterAPeriodConfirmation) { mReactor.setIgnorePresentFences(true); nsecs_t const newPeriod = 5000; - mReactor.startPeriodTransition(newPeriod, false); + mReactor.startPeriodTransition(newPeriod); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(0, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); @@ -205,7 +205,7 @@ TEST_F(VSyncReactorTest, ignoresProperlyAfterAPeriodConfirmation) { TEST_F(VSyncReactorTest, setPeriodCalledOnceConfirmedChange) { nsecs_t const newPeriod = 5000; EXPECT_CALL(*mMockTracker, setPeriod(_)).Times(0); - mReactor.startPeriodTransition(newPeriod, false); + mReactor.startPeriodTransition(newPeriod); bool periodFlushed = true; EXPECT_TRUE(mReactor.addHwVsyncTimestamp(10000, std::nullopt, &periodFlushed)); @@ -224,7 +224,7 @@ TEST_F(VSyncReactorTest, setPeriodCalledOnceConfirmedChange) { TEST_F(VSyncReactorTest, changingPeriodBackAbortsConfirmationProcess) { nsecs_t sampleTime = 0; nsecs_t const newPeriod = 5000; - mReactor.startPeriodTransition(newPeriod, false); + mReactor.startPeriodTransition(newPeriod); bool periodFlushed = true; EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); @@ -232,7 +232,7 @@ TEST_F(VSyncReactorTest, changingPeriodBackAbortsConfirmationProcess) { EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); - mReactor.startPeriodTransition(period, false); + mReactor.startPeriodTransition(period); EXPECT_FALSE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); } @@ -242,13 +242,13 @@ TEST_F(VSyncReactorTest, changingToAThirdPeriodWillWaitForLastPeriod) { nsecs_t const secondPeriod = 5000; nsecs_t const thirdPeriod = 2000; - mReactor.startPeriodTransition(secondPeriod, false); + mReactor.startPeriodTransition(secondPeriod); bool periodFlushed = true; EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); - mReactor.startPeriodTransition(thirdPeriod, false); + mReactor.startPeriodTransition(thirdPeriod); EXPECT_TRUE( mReactor.addHwVsyncTimestamp(sampleTime += secondPeriod, std::nullopt, &periodFlushed)); EXPECT_FALSE(periodFlushed); @@ -289,14 +289,14 @@ TEST_F(VSyncReactorTest, reportedBadTimestampFromPredictorWillReactivateHwVSyncP TEST_F(VSyncReactorTest, presentFenceAdditionDoesNotInterruptConfirmationProcess) { nsecs_t const newPeriod = 5000; - mReactor.startPeriodTransition(newPeriod, false); + mReactor.startPeriodTransition(newPeriod); EXPECT_TRUE(mReactor.addPresentFence(generateSignalledFenceWithTime(0))); } TEST_F(VSyncReactorTest, setPeriodCalledFirstTwoEventsNewPeriod) { nsecs_t const newPeriod = 5000; EXPECT_CALL(*mMockTracker, setPeriod(_)).Times(0); - mReactor.startPeriodTransition(newPeriod, false); + mReactor.startPeriodTransition(newPeriod); bool periodFlushed = true; EXPECT_TRUE(mReactor.addHwVsyncTimestamp(5000, std::nullopt, &periodFlushed)); @@ -321,7 +321,7 @@ TEST_F(VSyncReactorTest, addResyncSamplePeriodChanges) { bool periodFlushed = false; nsecs_t const newPeriod = 4000; - mReactor.startPeriodTransition(newPeriod, false); + mReactor.startPeriodTransition(newPeriod); auto time = 0; auto constexpr numTimestampSubmissions = 10; @@ -346,7 +346,7 @@ TEST_F(VSyncReactorTest, addHwVsyncTimestampDozePreempt) { bool periodFlushed = false; nsecs_t const newPeriod = 4000; - mReactor.startPeriodTransition(newPeriod, false); + mReactor.startPeriodTransition(newPeriod); auto time = 0; // If the power mode is not DOZE or DOZE_SUSPEND, it is still collecting timestamps. @@ -363,7 +363,7 @@ TEST_F(VSyncReactorTest, addPresentFenceWhileAwaitingPeriodConfirmationRequestsH auto time = 0; bool periodFlushed = false; nsecs_t const newPeriod = 4000; - mReactor.startPeriodTransition(newPeriod, false); + mReactor.startPeriodTransition(newPeriod); time += period; mReactor.addHwVsyncTimestamp(time, std::nullopt, &periodFlushed); @@ -379,7 +379,7 @@ TEST_F(VSyncReactorTest, hwVsyncIsRequestedForTracker) { auto time = 0; bool periodFlushed = false; nsecs_t const newPeriod = 4000; - mReactor.startPeriodTransition(newPeriod, false); + mReactor.startPeriodTransition(newPeriod); static auto constexpr numSamplesWithNewPeriod = 4; Sequence seq; @@ -406,7 +406,7 @@ TEST_F(VSyncReactorTest, hwVsyncturnsOffOnConfirmationWhenTrackerDoesntRequest) auto time = 0; bool periodFlushed = false; nsecs_t const newPeriod = 4000; - mReactor.startPeriodTransition(newPeriod, false); + mReactor.startPeriodTransition(newPeriod); Sequence seq; EXPECT_CALL(*mMockTracker, needsMoreSamples()) @@ -426,7 +426,7 @@ TEST_F(VSyncReactorTest, hwVsyncIsRequestedForTrackerMultiplePeriodChanges) { nsecs_t const newPeriod1 = 4000; nsecs_t const newPeriod2 = 7000; - mReactor.startPeriodTransition(newPeriod1, false); + mReactor.startPeriodTransition(newPeriod1); Sequence seq; EXPECT_CALL(*mMockTracker, needsMoreSamples()) @@ -445,7 +445,7 @@ TEST_F(VSyncReactorTest, hwVsyncIsRequestedForTrackerMultiplePeriodChanges) { EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod1, std::nullopt, &periodFlushed)); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod1, std::nullopt, &periodFlushed)); - mReactor.startPeriodTransition(newPeriod2, false); + mReactor.startPeriodTransition(newPeriod2); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod1, std::nullopt, &periodFlushed)); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod2, std::nullopt, &periodFlushed)); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod2, std::nullopt, &periodFlushed)); @@ -458,7 +458,7 @@ TEST_F(VSyncReactorTest, periodChangeWithGivenVsyncPeriod) { mReactor.setIgnorePresentFences(true); nsecs_t const newPeriod = 5000; - mReactor.startPeriodTransition(newPeriod, false); + mReactor.startPeriodTransition(newPeriod); EXPECT_TRUE(mReactor.addHwVsyncTimestamp(0, 0, &periodFlushed)); EXPECT_FALSE(periodFlushed); @@ -472,9 +472,8 @@ TEST_F(VSyncReactorTest, periodChangeWithGivenVsyncPeriod) { TEST_F(VSyncReactorTest, periodIsMeasuredIfIgnoringComposer) { // Create a reactor which supports the kernel idle timer - auto idleReactor = - VSyncReactor("reactor", std::make_unique(mMockClock), *mMockTracker, - kPendingLimit, true /* supportKernelIdleTimer */); + auto idleReactor = VSyncReactor(std::make_unique(mMockClock), *mMockTracker, + kPendingLimit, true /* supportKernelIdleTimer */); bool periodFlushed = true; EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(4); @@ -482,7 +481,7 @@ TEST_F(VSyncReactorTest, periodIsMeasuredIfIgnoringComposer) { // First, set the same period, which should only be confirmed when we receive two // matching callbacks - idleReactor.startPeriodTransition(10000, false); + idleReactor.startPeriodTransition(10000); EXPECT_TRUE(idleReactor.addHwVsyncTimestamp(0, 0, &periodFlushed)); EXPECT_FALSE(periodFlushed); // Correct period but incorrect timestamp delta @@ -495,7 +494,7 @@ TEST_F(VSyncReactorTest, periodIsMeasuredIfIgnoringComposer) { // Then, set a new period, which should be confirmed as soon as we receive a callback // reporting the new period nsecs_t const newPeriod = 5000; - idleReactor.startPeriodTransition(newPeriod, false); + idleReactor.startPeriodTransition(newPeriod); // Incorrect timestamp delta and period EXPECT_TRUE(idleReactor.addHwVsyncTimestamp(20000, 10000, &periodFlushed)); EXPECT_FALSE(periodFlushed); diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h index 3a6068ae88..f8567bd636 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h +++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h @@ -29,29 +29,27 @@ public: EventThread(); ~EventThread() override; - MOCK_METHOD(sp, createEventConnection, - (ResyncCallback, EventRegistrationFlags), (const, override)); - MOCK_METHOD(void, onScreenReleased, (), (override)); - MOCK_METHOD(void, onScreenAcquired, (), (override)); - MOCK_METHOD(void, onHotplugReceived, (PhysicalDisplayId, bool), (override)); - MOCK_METHOD(void, onModeChanged, (const scheduler::FrameRateMode&), (override)); - MOCK_METHOD(void, onFrameRateOverridesChanged, - (PhysicalDisplayId, std::vector), (override)); - MOCK_METHOD(void, dump, (std::string&), (const, override)); - MOCK_METHOD(void, setDuration, - (std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration), - (override)); - MOCK_METHOD(status_t, registerDisplayEventConnection, - (const sp&), (override)); - MOCK_METHOD(void, setVsyncRate, (uint32_t, const sp&), - (override)); - MOCK_METHOD(void, requestNextVsync, (const sp&), (override)); + MOCK_CONST_METHOD2(createEventConnection, + sp(ResyncCallback, EventRegistrationFlags)); + MOCK_METHOD0(onScreenReleased, void()); + MOCK_METHOD0(onScreenAcquired, void()); + MOCK_METHOD2(onHotplugReceived, void(PhysicalDisplayId, bool)); + MOCK_METHOD1(onModeChanged, void(const scheduler::FrameRateMode &)); + MOCK_METHOD2(onFrameRateOverridesChanged, + void(PhysicalDisplayId, std::vector)); + MOCK_CONST_METHOD1(dump, void(std::string&)); + MOCK_METHOD2(setDuration, + void(std::chrono::nanoseconds workDuration, + std::chrono::nanoseconds readyDuration)); + MOCK_METHOD1(registerDisplayEventConnection, + status_t(const sp &)); + MOCK_METHOD2(setVsyncRate, void(uint32_t, const sp &)); + MOCK_METHOD1(requestNextVsync, void(const sp &)); MOCK_METHOD(VsyncEventData, getLatestVsyncEventData, - (const sp&), (const, override)); - MOCK_METHOD(void, requestLatestConfig, (const sp&)); - MOCK_METHOD(void, pauseVsyncCallback, (bool)); - MOCK_METHOD(size_t, getEventThreadConnectionCount, (), (override)); - MOCK_METHOD(void, onNewVsyncSchedule, (std::shared_ptr), (override)); + (const sp &), (const)); + MOCK_METHOD1(requestLatestConfig, void(const sp &)); + MOCK_METHOD1(pauseVsyncCallback, void(bool)); + MOCK_METHOD0(getEventThreadConnectionCount, size_t()); }; } // namespace android::mock diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h index a8eca2192f..7d4b159e5e 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h +++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h @@ -18,19 +18,19 @@ #include -#include "Scheduler/ISchedulerCallback.h" +#include "Scheduler/Scheduler.h" namespace android::scheduler::mock { struct SchedulerCallback final : ISchedulerCallback { - MOCK_METHOD(void, setVsyncEnabled, (PhysicalDisplayId, bool), (override)); + MOCK_METHOD(void, setVsyncEnabled, (bool), (override)); MOCK_METHOD(void, requestDisplayModes, (std::vector), (override)); MOCK_METHOD(void, kernelTimerChanged, (bool), (override)); MOCK_METHOD(void, triggerOnFrameRateOverridesChanged, (), (override)); }; struct NoOpSchedulerCallback final : ISchedulerCallback { - void setVsyncEnabled(PhysicalDisplayId, bool) override {} + void setVsyncEnabled(bool) override {} void requestDisplayModes(std::vector) override {} void kernelTimerChanged(bool) override {} void triggerOnFrameRateOverridesChanged() override {} diff --git a/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h b/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h index 69ec60acd4..4ef91dacb2 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h +++ b/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h @@ -28,12 +28,12 @@ public: ~VsyncController() override; MOCK_METHOD(bool, addPresentFence, (std::shared_ptr), (override)); - MOCK_METHOD(bool, addHwVsyncTimestamp, (nsecs_t, std::optional, bool*), (override)); - MOCK_METHOD(void, startPeriodTransition, (nsecs_t, bool), (override)); - MOCK_METHOD(void, setIgnorePresentFences, (bool), (override)); + MOCK_METHOD3(addHwVsyncTimestamp, bool(nsecs_t, std::optional, bool*)); + MOCK_METHOD1(startPeriodTransition, void(nsecs_t)); + MOCK_METHOD1(setIgnorePresentFences, void(bool)); MOCK_METHOD(void, setDisplayPowerMode, (hal::PowerMode), (override)); - MOCK_METHOD(void, dump, (std::string&), (const, override)); + MOCK_CONST_METHOD1(dump, void(std::string&)); }; } // namespace android::mock -- GitLab From 63a0fd1944c0fe541e6cc3af16cc1119ac7d1065 Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Tue, 7 Feb 2023 02:10:58 +0000 Subject: [PATCH 0819/1310] Revert "Use "SessionHint" enum in ndk API" Revert submission 21161765-sessionhint_api Reason for revert: this broke the main-finalization-1 build... somehow. see b/268111957 Reverted changes: /q/submissionid:21161765-sessionhint_api Change-Id: I2a8b6609729bba06fc9409f4583886915c82577c --- include/private/performance_hint_private.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h index d50c5f846e..eaf3b5e791 100644 --- a/include/private/performance_hint_private.h +++ b/include/private/performance_hint_private.h @@ -17,8 +17,6 @@ #ifndef ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H #define ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H -#include - __BEGIN_DECLS /** @@ -29,7 +27,7 @@ void APerformanceHint_setIHintManagerForTesting(void* iManager); /** * Hints for the session used to signal upcoming changes in the mode or workload. */ -enum SessionHint: int32_t { +enum SessionHint { /** * This hint indicates a sudden increase in CPU workload intensity. It means * that this hint session needs extra CPU resources immediately to meet the @@ -63,7 +61,7 @@ enum SessionHint: int32_t { * @return 0 on success * EPIPE if communication with the system service has failed. */ -int APerformanceHint_sendHint(void* session, SessionHint hint); +int APerformanceHint_sendHint(void* session, int hint); /** * Return the list of thread ids, this API should only be used for testing only. -- GitLab From 959a7ff7868d0df34ed76a5af2cc67ec55495789 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Tue, 7 Feb 2023 11:24:25 -0500 Subject: [PATCH 0820/1310] Make onVsync return a display id The caller of this method then turns around and asks HWComposer for the display id, so we might as well return the id in the first place. Use an optional so it can return the equivalent of false in the old version. Use ftl::Concat to save memory allocations. Factored out from If60218e85292c786b9fa70ecb33ee374d3a385e0 after it was reverted. Bug: 255601557 Bug: 256196556 Test: atest libsurfaceflinger_unittest:HWComposerTest Change-Id: I21be76e20776d8a3f49e5bd295a0042de9e2dde9 --- .../CompositionEngine/tests/MockHWComposer.h | 2 +- .../DisplayHardware/HWComposer.cpp | 28 ++++++++++--------- .../DisplayHardware/HWComposer.h | 7 +++-- services/surfaceflinger/SurfaceFlinger.cpp | 10 ++----- .../tests/unittests/HWComposerTest.cpp | 28 +++++++++++++++++++ 5 files changed, 52 insertions(+), 23 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h index 6199a5ae40..933f6168c9 100644 --- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h +++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h @@ -93,7 +93,7 @@ public: MOCK_METHOD2(onHotplug, std::optional(hal::HWDisplayId, hal::Connection)); MOCK_CONST_METHOD0(updatesDeviceProductInfoOnHotplugReconnect, bool()); - MOCK_METHOD2(onVsync, bool(hal::HWDisplayId, int64_t)); + MOCK_METHOD(std::optional, onVsync, (hal::HWDisplayId, int64_t)); MOCK_METHOD2(setVsyncEnabled, void(PhysicalDisplayId, hal::Vsync)); MOCK_CONST_METHOD1(isConnected, bool(PhysicalDisplayId)); MOCK_CONST_METHOD1(getModes, std::vector(PhysicalDisplayId)); diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp index 7dde6b4e44..6d940797e2 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -148,16 +149,17 @@ bool HWComposer::updatesDeviceProductInfoOnHotplugReconnect() const { return mUpdateDeviceProductInfoOnHotplugReconnect; } -bool HWComposer::onVsync(hal::HWDisplayId hwcDisplayId, nsecs_t timestamp) { - const auto displayId = toPhysicalDisplayId(hwcDisplayId); - if (!displayId) { +std::optional HWComposer::onVsync(hal::HWDisplayId hwcDisplayId, + nsecs_t timestamp) { + const auto displayIdOpt = toPhysicalDisplayId(hwcDisplayId); + if (!displayIdOpt) { LOG_HWC_DISPLAY_ERROR(hwcDisplayId, "Invalid HWC display"); - return false; + return {}; } - RETURN_IF_INVALID_DISPLAY(*displayId, false); + RETURN_IF_INVALID_DISPLAY(*displayIdOpt, {}); - auto& displayData = mDisplayData[*displayId]; + auto& displayData = mDisplayData[*displayIdOpt]; { // There have been reports of HWCs that signal several vsync events @@ -166,18 +168,18 @@ bool HWComposer::onVsync(hal::HWDisplayId hwcDisplayId, nsecs_t timestamp) { // out here so they don't cause havoc downstream. if (timestamp == displayData.lastPresentTimestamp) { ALOGW("Ignoring duplicate VSYNC event from HWC for display %s (t=%" PRId64 ")", - to_string(*displayId).c_str(), timestamp); - return false; + to_string(*displayIdOpt).c_str(), timestamp); + return {}; } displayData.lastPresentTimestamp = timestamp; } - const auto tag = "HW_VSYNC_" + to_string(*displayId); - ATRACE_INT(tag.c_str(), displayData.vsyncTraceToggle); + ATRACE_INT(ftl::Concat("HW_VSYNC_", displayIdOpt->value).c_str(), + displayData.vsyncTraceToggle); displayData.vsyncTraceToggle = !displayData.vsyncTraceToggle; - return true; + return displayIdOpt; } size_t HWComposer::getMaxVirtualDisplayCount() const { @@ -375,8 +377,8 @@ void HWComposer::setVsyncEnabled(PhysicalDisplayId displayId, hal::Vsync enabled displayData.vsyncEnabled = enabled; - const auto tag = "HW_VSYNC_ON_" + to_string(displayId); - ATRACE_INT(tag.c_str(), enabled == hal::Vsync::ENABLE ? 1 : 0); + ATRACE_INT(ftl::Concat("HW_VSYNC_ON_", displayId.value).c_str(), + enabled == hal::Vsync::ENABLE ? 1 : 0); } status_t HWComposer::setClientTarget(HalDisplayId displayId, uint32_t slot, diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h index f6155d24ad..acebfb2362 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.h +++ b/services/surfaceflinger/DisplayHardware/HWComposer.h @@ -221,7 +221,10 @@ public: // TODO(b/157555476): Remove when the framework has proper support for headless mode virtual bool updatesDeviceProductInfoOnHotplugReconnect() const = 0; - virtual bool onVsync(hal::HWDisplayId, nsecs_t timestamp) = 0; + // Called when a vsync happens. If the vsync is valid, returns the + // corresponding PhysicalDisplayId. Otherwise returns nullopt. + virtual std::optional onVsync(hal::HWDisplayId, nsecs_t timestamp) = 0; + virtual void setVsyncEnabled(PhysicalDisplayId, hal::Vsync enabled) = 0; virtual bool isConnected(PhysicalDisplayId) const = 0; @@ -402,7 +405,7 @@ public: bool updatesDeviceProductInfoOnHotplugReconnect() const override; - bool onVsync(hal::HWDisplayId, nsecs_t timestamp) override; + std::optional onVsync(hal::HWDisplayId, nsecs_t timestamp) override; void setVsyncEnabled(PhysicalDisplayId, hal::Vsync enabled) override; bool isConnected(PhysicalDisplayId) const override; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 169e101b2f..0b8d362151 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2031,13 +2031,9 @@ void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t t Mutex::Autolock lock(mStateLock); - if (!getHwComposer().onVsync(hwcDisplayId, timestamp)) { - return; - } - - if (const auto displayId = getHwComposer().toPhysicalDisplayId(hwcDisplayId); - displayId != mActiveDisplayId) { - // For now, we don't do anything with non active display vsyncs. + if (const auto displayIdOpt = getHwComposer().onVsync(hwcDisplayId, timestamp); + displayIdOpt != mActiveDisplayId) { + // Ignore VSYNC for invalid/inactive displays. return; } diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp index afbc57add8..9534f3b548 100644 --- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp +++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp @@ -118,6 +118,34 @@ TEST_F(HWComposerTest, getActiveMode) { } } +TEST_F(HWComposerTest, onVsync) { + constexpr hal::HWDisplayId kHwcDisplayId = 1; + expectHotplugConnect(kHwcDisplayId); + + const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED); + ASSERT_TRUE(info); + + const auto physicalDisplayId = info->id; + + // Deliberately chosen not to match DisplayData.lastPresentTimestamp's + // initial value. + constexpr nsecs_t kTimestamp = 1; + auto displayIdOpt = mHwc.onVsync(kHwcDisplayId, kTimestamp); + ASSERT_TRUE(displayIdOpt); + EXPECT_EQ(physicalDisplayId, displayIdOpt); + + // Attempt to send the same time stamp again. + displayIdOpt = mHwc.onVsync(kHwcDisplayId, kTimestamp); + EXPECT_FALSE(displayIdOpt); +} + +TEST_F(HWComposerTest, onVsyncInvalid) { + constexpr hal::HWDisplayId kInvalidHwcDisplayId = 2; + constexpr nsecs_t kTimestamp = 1; + const auto displayIdOpt = mHwc.onVsync(kInvalidHwcDisplayId, kTimestamp); + EXPECT_FALSE(displayIdOpt); +} + struct MockHWC2ComposerCallback final : StrictMock { MOCK_METHOD2(onComposerHalHotplug, void(hal::HWDisplayId, hal::Connection)); MOCK_METHOD1(onComposerHalRefresh, void(hal::HWDisplayId)); -- GitLab From 9af4e1151f010670d4daa3beff4e915e9a0288ca Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 6 Feb 2023 10:49:07 -0800 Subject: [PATCH 0821/1310] Use bitwise operator to remove pointers After we converted to std::bitset, we can now use a bitwise operator to remove pointers instead of doing a loop. Bug: 211379801 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: Ib95f7c67a9d4ee493fa913381953b092a692669c --- services/inputflinger/dispatcher/TouchState.cpp | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index acfd0a24a8..6d63991230 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -164,17 +164,7 @@ void TouchState::cancelPointersForNonPilferingWindows() { std::for_each(windows.begin(), windows.end(), [&allPilferedPointerIds](TouchedWindow& w) { std::bitset pilferedByOtherWindows = w.pilferedPointerIds ^ allPilferedPointerIds; - // TODO(b/211379801) : convert pointerIds to use std::bitset, which would allow us to - // replace the loop below with a bitwise operation. Currently, the XOR operation above is - // redundant, but is done to make the code more explicit / easier to convert later. - for (std::size_t i = 0; i < pilferedByOtherWindows.size(); i++) { - if (pilferedByOtherWindows.test(i) && !w.pilferedPointerIds.test(i)) { - // Pointer is pilfered by other windows, but not by this one! Remove it from here. - // We could call 'removeTouchedPointerFromWindow' here, but it's faster to directly - // manipulate it. - w.pointerIds.reset(i); - } - } + w.pointerIds &= ~pilferedByOtherWindows; }); std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.none(); }); } -- GitLab From 4c2bed7832d5ecf97d53329eec98404f94a410a0 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Tue, 7 Feb 2023 11:51:02 -0500 Subject: [PATCH 0822/1310] Add DisplayId to DesiredActiveModeChanged trace This way we know which display it corresponds to. Bug: 241285473 Test: perfetto trace Change-Id: Ic943bec4b6085eda97dd9bf1c97288021bdc3c14 --- services/surfaceflinger/DisplayDevice.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index 6b5d1d7f07..b86d9bec39 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -300,8 +301,8 @@ private: mutable std::mutex mActiveModeLock; ActiveModeInfo mDesiredActiveMode GUARDED_BY(mActiveModeLock); - TracedOrdinal mDesiredActiveModeChanged - GUARDED_BY(mActiveModeLock) = {"DesiredActiveModeChanged", false}; + TracedOrdinal mDesiredActiveModeChanged GUARDED_BY(mActiveModeLock) = + {ftl::Concat("DesiredActiveModeChanged-", getId().value).c_str(), false}; ActiveModeInfo mUpcomingActiveMode GUARDED_BY(kMainThreadContext); }; -- GitLab From ce14101c144b66bf8ec63b8669b51e7717f72897 Mon Sep 17 00:00:00 2001 From: Chavi Weingarten Date: Tue, 7 Feb 2023 20:11:24 +0000 Subject: [PATCH 0823/1310] Merge conflict didn't pick up changes. Change-Id: I195ebf30a87b7135a6cf3f9012947cfb794c14df --- libs/gui/BLASTBufferQueue.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index d5431916cc..66c0041965 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -678,7 +678,6 @@ void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) { BBQ_TRACE(); bool waitForTransactionCallback = !mSyncedFrameNumbers.empty(); - bool waitForTransactionCallback = !mSyncedFrameNumbers.empty(); const bool syncTransactionSet = mTransactionReadyCallback != nullptr; BQA_LOGV("onFrameAvailable-start syncTransactionSet=%s", boolToString(syncTransactionSet)); -- GitLab From cb46180366f29e0ef1b605f8fae89834ea44a4b2 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Tue, 7 Feb 2023 15:31:33 -0500 Subject: [PATCH 0824/1310] Remove redundant traces This information is already logged, and it's confusing in the trace. Bug: NA Test: m Change-Id: I4f9fc27e0b411a06a3d4439495b19ed41f3f7fd4 --- services/surfaceflinger/Scheduler/RefreshRateSelector.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index c5b3e14047..f6fe468dd6 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -238,7 +238,6 @@ struct RefreshRateSelector::RefreshRateScoreComparator { std::string name = to_string(frameRateMode); ALOGV("%s sorting scores %.2f", name.c_str(), overallScore); - ATRACE_INT(name.c_str(), static_cast(std::round(overallScore * 100))); if (!ScoredFrameRate::scoresEqual(overallScore, rhs.overallScore)) { return overallScore > rhs.overallScore; -- GitLab From f2bf196d1cb876a97104045c37887d537e3a4ef9 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Tue, 7 Feb 2023 16:03:01 -0500 Subject: [PATCH 0825/1310] Remove unnecessary mScreenAcquired This variable is only used for debugging, and did not have any effect on behavior. It's also not quite the right information in a multi-display world. Factored out of If60218e85292c786b9fa70ecb33ee374d3a385e0. Making this change simplifies further refactoring in re-landing that CL. Bug: 255601557 Bug: 256196556 Bug: 241286146 Test: m Change-Id: I062002245db8fd817e145a3aaf197bb874b00636 --- services/surfaceflinger/Scheduler/Scheduler.cpp | 3 --- services/surfaceflinger/Scheduler/Scheduler.h | 3 --- 2 files changed, 6 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index c314b5c215..5fc250bd9f 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -272,7 +272,6 @@ void Scheduler::onScreenAcquired(ConnectionHandle handle) { thread = mConnections[handle].thread.get(); } thread->onScreenAcquired(); - mScreenAcquired = true; } void Scheduler::onScreenReleased(ConnectionHandle handle) { @@ -283,7 +282,6 @@ void Scheduler::onScreenReleased(ConnectionHandle handle) { thread = mConnections[handle].thread.get(); } thread->onScreenReleased(); - mScreenAcquired = false; } void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId) { @@ -642,7 +640,6 @@ void Scheduler::dump(utils::Dumper& dumper) const { utils::Dumper::Section section(dumper, "Hardware VSYNC"sv); std::lock_guard lock(mHWVsyncLock); - dumper.dump("screenAcquired"sv, mScreenAcquired.load()); dumper.dump("hwVsyncAvailable"sv, mHWVsyncAvailable); dumper.dump("hwVsyncEnabled"sv, mPrimaryHWVsyncEnabled); } diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 796c785088..dae9546c78 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -439,9 +439,6 @@ private: static constexpr std::chrono::nanoseconds MAX_VSYNC_APPLIED_TIME = 200ms; FrameRateOverrideMappings mFrameRateOverrideMappings; - - // Keeps track of whether the screen is acquired for debug - std::atomic mScreenAcquired = false; }; } // namespace scheduler -- GitLab From b39918f8a4360e32692aa6353a9b20416e3b7ad3 Mon Sep 17 00:00:00 2001 From: Pascal Muetschard Date: Fri, 27 Jan 2023 11:36:11 +0100 Subject: [PATCH 0826/1310] Adds a flush jank data layer state update type. A transaction can be sent to SurfaceFlinger with a layer state change of this type to wake up SurfaceFlinger and have it perform the layer state update logic, without updating anything, but triggering any side-effects, especially jank data processing. Bug: 235178314 Bug: 221393601 Bug: 225105422 Test: atest SurfaceFlinger_test Change-Id: Idf458c96cbe8f54224ebde6f517c08b9a5c48a06 --- libs/gui/LayerState.cpp | 3 +++ libs/gui/SurfaceComposerClient.cpp | 13 +++++++++++++ libs/gui/include/gui/LayerState.h | 2 +- libs/gui/include/gui/SurfaceComposerClient.h | 2 ++ services/surfaceflinger/SurfaceFlinger.cpp | 4 ++++ 5 files changed, 23 insertions(+), 1 deletion(-) diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 8372363185..7772a65dae 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -684,6 +684,9 @@ void layer_state_t::merge(const layer_state_t& other) { what |= eDimmingEnabledChanged; dimmingEnabled = other.dimmingEnabled; } + if (other.what & eFlushJankData) { + what |= eFlushJankData; + } 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, diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 9092f5fe67..044fb4b080 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -1180,6 +1180,19 @@ sp SurfaceComposerClient::Transaction::getDefaultApplyToken() { void SurfaceComposerClient::Transaction::setDefaultApplyToken(sp applyToken) { sApplyToken = applyToken; } + +status_t SurfaceComposerClient::Transaction::sendSurfaceFlushJankDataTransaction( + const sp& sc) { + Transaction t; + layer_state_t* s = t.getLayerState(sc); + if (!s) { + return BAD_INDEX; + } + + s->what |= layer_state_t::eFlushJankData; + t.registerSurfaceControlForCallback(sc); + return t.apply(/*sync=*/false, /* oneWay=*/true); +} // --------------------------------------------------------------------------- sp SurfaceComposerClient::createDisplay(const String8& displayName, bool secure, diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index b8bee72cec..03a2582589 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -170,7 +170,7 @@ struct layer_state_t { eTransparentRegionChanged = 0x00000020, eFlagsChanged = 0x00000040, eLayerStackChanged = 0x00000080, - /* unused = 0x00000100, */ + eFlushJankData = 0x00000100, /* unused = 0x00000200, */ eDimmingEnabledChanged = 0x00000400, eShadowRadiusChanged = 0x00000800, diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 45f4dbe2be..8ae2b3ccc0 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -742,6 +742,8 @@ public: static sp getDefaultApplyToken(); static void setDefaultApplyToken(sp applyToken); + + static status_t sendSurfaceFlushJankDataTransaction(const sp& sc); }; status_t clearLayerFrameStats(const sp& token) const; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index a0c3eb0e26..2f5f93c16b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4843,6 +4843,10 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime s.trustedPresentationListener); } + if (what & layer_state_t::eFlushJankData) { + // Do nothing. Processing the transaction completed listeners currently cause the flush. + } + if (layer->setTransactionCompletedListeners(callbackHandles, layer->willPresentCurrentTransaction())) { flags |= eTraversalNeeded; -- GitLab From 6464e46baf8ee4c448f2b364d210e6c48a559eaa Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 6 Feb 2023 18:57:59 -0800 Subject: [PATCH 0827/1310] Clear downTime when all pointers have lifted Due to a recent change, the TouchWindows are getting persisted inside TouchState. As a result, we need to keep a consistent state inside them. Before this CL, the downTime of the TouchedWindow would get set with the first pointer, but it would never reset. We used to rely on the fact that the TouchedWindow would go away. Now that is no longer the case. In this CL, the downTime is cleared when all pointers leave the window. The downTime can be set again when a new pointer appears. This fixes one of the crashes due to the mismatching downTimes. To reproduce the failure, we need to: 1) Find a way to persist a window This can be done by injecting a mouse pointer into the window, and keep the mouse there 2) Establish a downTime This can be done by tapping on the window 3) Send another DOWN event while splitting the pointers. Normally, a DOWN event would not go through the "splitting" path, and therefore the crash would be avoided. The splitting path can be triggered if we first touch another window, and then touch the first one. The split touch feature would cause an ACTION_DOWN to get generated, which would then trigger the 'split' case. Bug: 266455987 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests --gtest_filter="*HoverTapAndSplitTouch*" Change-Id: I2340b5ab70a3659f52fc38fa4d9e2c9edfe1a768 --- .../inputflinger/dispatcher/TouchState.cpp | 6 +- .../inputflinger/dispatcher/TouchedWindow.cpp | 8 ++ .../inputflinger/dispatcher/TouchedWindow.h | 1 + .../tests/InputDispatcher_test.cpp | 96 +++++++++++++++++++ 4 files changed, 107 insertions(+), 4 deletions(-) diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index 6d63991230..425847183e 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -33,8 +33,7 @@ void TouchState::reset() { void TouchState::removeTouchedPointer(int32_t pointerId) { for (TouchedWindow& touchedWindow : windows) { - touchedWindow.pointerIds.reset(pointerId); - touchedWindow.pilferedPointerIds.reset(pointerId); + touchedWindow.removeTouchingPointer(pointerId); } } @@ -42,8 +41,7 @@ void TouchState::removeTouchedPointerFromWindow( int32_t pointerId, const sp& windowHandle) { for (TouchedWindow& touchedWindow : windows) { if (touchedWindow.windowHandle == windowHandle) { - touchedWindow.pointerIds.reset(pointerId); - touchedWindow.pilferedPointerIds.reset(pointerId); + touchedWindow.removeTouchingPointer(pointerId); return; } } diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp index 92f62b5932..99c4769712 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.cpp +++ b/services/inputflinger/dispatcher/TouchedWindow.cpp @@ -50,6 +50,14 @@ void TouchedWindow::addHoveringPointer(int32_t deviceId, int32_t pointerId) { it->second.set(pointerId); } +void TouchedWindow::removeTouchingPointer(int32_t pointerId) { + pointerIds.reset(pointerId); + pilferedPointerIds.reset(pointerId); + if (pointerIds.none()) { + firstDownTimeInTarget.reset(); + } +} + void TouchedWindow::removeHoveringPointer(int32_t deviceId, int32_t pointerId) { const auto it = mHoveringPointerIdsByDevice.find(deviceId); if (it == mHoveringPointerIdsByDevice.end()) { diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h index e59e78106b..aa2e9dd677 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -43,6 +43,7 @@ struct TouchedWindow { bool hasHoveringPointer(int32_t deviceId, int32_t pointerId) const; void addHoveringPointer(int32_t deviceId, int32_t pointerId); void removeHoveringPointer(int32_t deviceId, int32_t pointerId); + void removeTouchingPointer(int32_t pointerId); void clearHoveringPointers(); std::string dump() const; diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index b1b6e058e4..a2fe911e1c 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -2171,6 +2171,102 @@ TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { rightWindow->assertNoEvents(); } +/** + * This test is similar to the test above, but the sequence of injected events is different. + * + * Two windows: a window on the left and a window on the right. + * Mouse is hovered over the left window. + * Next, we tap on the left window, where the cursor was last seen. + * + * After that, we inject one finger down onto the right window, and then a second finger down onto + * the left window. + * The touch is split, so this last gesture should cause 2 ACTION_DOWN events, one in the right + * window (first), and then another on the left window (second). + * This test reproduces a crash where there is a mismatch between the downTime and eventTime. + * In the buggy implementation, second finger down on the left window would cause a crash. + */ +TEST_F(InputDispatcherTest, HoverTapAndSplitTouch) { + std::shared_ptr application = std::make_shared(); + sp leftWindow = + sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 200, 200)); + + sp rightWindow = + sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightWindow->setFrame(Rect(200, 0, 400, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow}}}); + + const int32_t mouseDeviceId = 6; + const int32_t touchDeviceId = 4; + // Hover over the left window. Keep the cursor there. + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, + AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .x(50) + .y(50)) + .build())); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + + // Tap on left window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(100) + .y(100)) + .build())); + + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(100) + .y(100)) + .build())); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP)); + + // First finger down on right window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(300) + .y(100)) + .build())); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + + // Second finger down on the left window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(300) + .y(100)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(100) + .y(100)) + .build())); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_MOVE)); + + // No more events + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + /** * On the display, have a single window, and also an area where there's no window. * First pointer touches the "no window" area of the screen. Second pointer touches the window. -- GitLab From 437354181694798af24038dd65455eeab06a329e Mon Sep 17 00:00:00 2001 From: Cody Northrop Date: Wed, 8 Feb 2023 16:19:05 +0000 Subject: [PATCH 0828/1310] Revert "Reland "EGL: Refactor multifile blobcache"" Revert submission 21292947-reland_blobcache_20230203 Reason for revert: Suspected cause of boot time regression Reverted changes: /q/submissionid:21292947-reland_blobcache_20230203 Bug: b/266725576 Bug: b/268248090 Change-Id: Ic70ffa65d8d194b00106853d8c05b9cc2992e02d --- opengl/libs/Android.bp | 7 +- opengl/libs/EGL/MultifileBlobCache.cpp | 670 -------------------- opengl/libs/EGL/MultifileBlobCache.h | 167 ----- opengl/libs/EGL/MultifileBlobCache_test.cpp | 200 ------ opengl/libs/EGL/egl_cache.cpp | 105 ++- opengl/libs/EGL/egl_cache.h | 21 +- opengl/libs/EGL/egl_cache_multifile.cpp | 343 ++++++++++ opengl/libs/EGL/egl_cache_multifile.h | 36 ++ opengl/tests/EGLTest/egl_cache_test.cpp | 56 +- 9 files changed, 446 insertions(+), 1159 deletions(-) delete mode 100644 opengl/libs/EGL/MultifileBlobCache.cpp delete mode 100644 opengl/libs/EGL/MultifileBlobCache.h delete mode 100644 opengl/libs/EGL/MultifileBlobCache_test.cpp create mode 100644 opengl/libs/EGL/egl_cache_multifile.cpp create mode 100644 opengl/libs/EGL/egl_cache_multifile.h diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp index 49e1cbafb4..750338bd84 100644 --- a/opengl/libs/Android.bp +++ b/opengl/libs/Android.bp @@ -144,7 +144,6 @@ cc_library_static { srcs: [ "EGL/BlobCache.cpp", "EGL/FileBlobCache.cpp", - "EGL/MultifileBlobCache.cpp", ], export_include_dirs: ["EGL"], } @@ -161,6 +160,7 @@ cc_library_shared { srcs: [ "EGL/egl_tls.cpp", "EGL/egl_cache.cpp", + "EGL/egl_cache_multifile.cpp", "EGL/egl_display.cpp", "EGL/egl_object.cpp", "EGL/egl_layers.cpp", @@ -205,11 +205,6 @@ cc_test { srcs: [ "EGL/BlobCache.cpp", "EGL/BlobCache_test.cpp", - "EGL/MultifileBlobCache.cpp", - "EGL/MultifileBlobCache_test.cpp", - ], - shared_libs: [ - "libutils", ], } diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp deleted file mode 100644 index 304f9072e4..0000000000 --- a/opengl/libs/EGL/MultifileBlobCache.cpp +++ /dev/null @@ -1,670 +0,0 @@ -/* - ** Copyright 2022, The Android Open Source Project - ** - ** Licensed under the Apache License, Version 2.0 (the "License"); - ** you may not use this file except in compliance with the License. - ** You may obtain a copy of the License at - ** - ** http://www.apache.org/licenses/LICENSE-2.0 - ** - ** Unless required by applicable law or agreed to in writing, software - ** distributed under the License is distributed on an "AS IS" BASIS, - ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ** See the License for the specific language governing permissions and - ** limitations under the License. - */ - -// #define LOG_NDEBUG 0 - -#include "MultifileBlobCache.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -using namespace std::literals; - -namespace { - -// Open the file and determine the size of the value it contains -size_t getValueSizeFromFile(int fd, const std::string& entryPath) { - // Read the beginning of the file to get header - android::MultifileHeader header; - size_t result = read(fd, static_cast(&header), sizeof(android::MultifileHeader)); - if (result != sizeof(android::MultifileHeader)) { - ALOGE("Error reading MultifileHeader from cache entry (%s): %s", entryPath.c_str(), - std::strerror(errno)); - return 0; - } - - return header.valueSize; -} - -// Helper function to close entries or free them -void freeHotCacheEntry(android::MultifileHotCache& entry) { - if (entry.entryFd != -1) { - // If we have an fd, then this entry was added to hot cache via INIT or GET - // We need to unmap and close the entry - munmap(entry.entryBuffer, entry.entrySize); - close(entry.entryFd); - } else { - // Otherwise, this was added to hot cache during SET, so it was never mapped - // and fd was only on the deferred thread. - delete[] entry.entryBuffer; - } -} - -} // namespace - -namespace android { - -MultifileBlobCache::MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize, - const std::string& baseDir) - : mInitialized(false), - mMaxTotalSize(maxTotalSize), - mTotalCacheSize(0), - mHotCacheLimit(maxHotCacheSize), - mHotCacheSize(0), - mWorkerThreadIdle(true) { - if (baseDir.empty()) { - return; - } - - // Establish the name of our multifile directory - mMultifileDirName = baseDir + ".multifile"; - - // Set a limit for max key and value, ensuring at least one entry can always fit in hot cache - mMaxKeySize = mHotCacheLimit / 4; - mMaxValueSize = mHotCacheLimit / 2; - - // Initialize our cache with the contents of the directory - mTotalCacheSize = 0; - - // See if the dir exists, and initialize using its contents - struct stat st; - if (stat(mMultifileDirName.c_str(), &st) == 0) { - // Read all the files and gather details, then preload their contents - DIR* dir; - struct dirent* entry; - if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) { - while ((entry = readdir(dir)) != nullptr) { - if (entry->d_name == "."s || entry->d_name == ".."s) { - continue; - } - - std::string entryName = entry->d_name; - std::string fullPath = mMultifileDirName + "/" + entryName; - - // The filename is the same as the entryHash - uint32_t entryHash = static_cast(strtoul(entry->d_name, nullptr, 10)); - - // Look up the details of the file - struct stat st; - if (stat(fullPath.c_str(), &st) != 0) { - ALOGE("Failed to stat %s", fullPath.c_str()); - return; - } - - // Open the file so we can read its header - int fd = open(fullPath.c_str(), O_RDONLY); - if (fd == -1) { - ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(), - std::strerror(errno)); - return; - } - - // Look up the details we track about each file - size_t valueSize = getValueSizeFromFile(fd, fullPath); - - // If the cache entry is damaged or no good, remove it - // TODO: Perform any other checks - if (valueSize <= 0 || st.st_size <= 0 || st.st_atime <= 0) { - if (remove(fullPath.c_str()) != 0) { - ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno)); - } - continue; - } - - // Note: Converting from off_t (signed) to size_t (unsigned) - size_t fileSize = static_cast(st.st_size); - time_t accessTime = st.st_atime; - - // Track details for rapid lookup later - trackEntry(entryHash, valueSize, fileSize, accessTime); - - // Track the total size - increaseTotalCacheSize(fileSize); - - // Preload the entry for fast retrieval - if ((mHotCacheSize + fileSize) < mHotCacheLimit) { - // Memory map the file - uint8_t* mappedEntry = reinterpret_cast( - mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0)); - if (mappedEntry == MAP_FAILED) { - ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); - } - - ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for " - "entryHash %u", - fd, mappedEntry, entryHash); - - // Track the details of the preload so they can be retrieved later - if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) { - ALOGE("INIT Failed to add %u to hot cache", entryHash); - munmap(mappedEntry, fileSize); - close(fd); - return; - } - } else { - close(fd); - } - } - closedir(dir); - } else { - ALOGE("Unable to open filename: %s", mMultifileDirName.c_str()); - } - } else { - // If the multifile directory does not exist, create it and start from scratch - if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) { - ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno); - } - } - - mTaskThread = std::thread(&MultifileBlobCache::processTasks, this); - - mInitialized = true; -} - -MultifileBlobCache::~MultifileBlobCache() { - // Inform the worker thread we're done - ALOGV("DESCTRUCTOR: Shutting down worker thread"); - DeferredTask task(TaskCommand::Exit); - queueTask(std::move(task)); - - // Wait for it to complete - ALOGV("DESCTRUCTOR: Waiting for worker thread to complete"); - waitForWorkComplete(); - mTaskThread.join(); -} - -// Set will add the entry to hot cache and start a deferred process to write it to disk -void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const void* value, - EGLsizeiANDROID valueSize) { - if (!mInitialized) { - return; - } - - // Ensure key and value are under their limits - if (keySize > mMaxKeySize || valueSize > mMaxValueSize) { - ALOGV("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize, - valueSize, mMaxValueSize); - return; - } - - // Generate a hash of the key and use it to track this entry - uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast(key), keySize); - - size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize; - - // If we're going to be over the cache limit, kick off a trim to clear space - if (getTotalSize() + fileSize > mMaxTotalSize) { - ALOGV("SET: Cache is full, calling trimCache to clear space"); - trimCache(mMaxTotalSize); - } - - ALOGV("SET: Add %u to cache", entryHash); - - uint8_t* buffer = new uint8_t[fileSize]; - - // Write the key and value after the header - android::MultifileHeader header = {keySize, valueSize}; - memcpy(static_cast(buffer), static_cast(&header), - sizeof(android::MultifileHeader)); - memcpy(static_cast(buffer + sizeof(MultifileHeader)), static_cast(key), - keySize); - memcpy(static_cast(buffer + sizeof(MultifileHeader) + keySize), - static_cast(value), valueSize); - - std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash); - - // Track the size and access time for quick recall - trackEntry(entryHash, valueSize, fileSize, time(0)); - - // Update the overall cache size - increaseTotalCacheSize(fileSize); - - // Keep the entry in hot cache for quick retrieval - ALOGV("SET: Adding %u to hot cache.", entryHash); - - // Sending -1 as the fd indicates we don't have an fd for this - if (!addToHotCache(entryHash, -1, buffer, fileSize)) { - ALOGE("GET: Failed to add %u to hot cache", entryHash); - return; - } - - // Track that we're creating a pending write for this entry - // Include the buffer to handle the case when multiple writes are pending for an entry - mDeferredWrites.insert(std::make_pair(entryHash, buffer)); - - // Create deferred task to write to storage - ALOGV("SET: Adding task to queue."); - DeferredTask task(TaskCommand::WriteToDisk); - task.initWriteToDisk(entryHash, fullPath, buffer, fileSize); - queueTask(std::move(task)); -} - -// Get will check the hot cache, then load it from disk if needed -EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize, void* value, - EGLsizeiANDROID valueSize) { - if (!mInitialized) { - return 0; - } - - // Ensure key and value are under their limits - if (keySize > mMaxKeySize || valueSize > mMaxValueSize) { - ALOGV("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize, - valueSize, mMaxValueSize); - return 0; - } - - // Generate a hash of the key and use it to track this entry - uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast(key), keySize); - - // See if we have this file - if (!contains(entryHash)) { - ALOGV("GET: Cache MISS - cache does not contain entry: %u", entryHash); - return 0; - } - - // Look up the data for this entry - MultifileEntryStats entryStats = getEntryStats(entryHash); - - size_t cachedValueSize = entryStats.valueSize; - if (cachedValueSize > valueSize) { - ALOGV("GET: Cache MISS - valueSize not large enough (%lu) for entry %u, returning required" - "size (%zu)", - valueSize, entryHash, cachedValueSize); - return cachedValueSize; - } - - // We have the file and have enough room to write it out, return the entry - ALOGV("GET: Cache HIT - cache contains entry: %u", entryHash); - - // Look up the size of the file - size_t fileSize = entryStats.fileSize; - if (keySize > fileSize) { - ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified " - "file", - keySize, fileSize); - return 0; - } - - std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash); - - // Open the hashed filename path - uint8_t* cacheEntry = 0; - - // Check hot cache - if (mHotCache.find(entryHash) != mHotCache.end()) { - ALOGV("GET: HotCache HIT for entry %u", entryHash); - cacheEntry = mHotCache[entryHash].entryBuffer; - } else { - ALOGV("GET: HotCache MISS for entry: %u", entryHash); - - if (mDeferredWrites.find(entryHash) != mDeferredWrites.end()) { - // Wait for writes to complete if there is an outstanding write for this entry - ALOGV("GET: Waiting for write to complete for %u", entryHash); - waitForWorkComplete(); - } - - // Open the entry file - int fd = open(fullPath.c_str(), O_RDONLY); - if (fd == -1) { - ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(), - std::strerror(errno)); - return 0; - } - - // Memory map the file - cacheEntry = - reinterpret_cast(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0)); - if (cacheEntry == MAP_FAILED) { - ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); - close(fd); - return 0; - } - - ALOGV("GET: Adding %u to hot cache", entryHash); - if (!addToHotCache(entryHash, fd, cacheEntry, fileSize)) { - ALOGE("GET: Failed to add %u to hot cache", entryHash); - return 0; - } - - cacheEntry = mHotCache[entryHash].entryBuffer; - } - - // Ensure the header matches - MultifileHeader* header = reinterpret_cast(cacheEntry); - if (header->keySize != keySize || header->valueSize != valueSize) { - ALOGW("Mismatch on keySize(%ld vs. cached %ld) or valueSize(%ld vs. cached %ld) compared " - "to cache header values for fullPath: %s", - keySize, header->keySize, valueSize, header->valueSize, fullPath.c_str()); - removeFromHotCache(entryHash); - return 0; - } - - // Compare the incoming key with our stored version (the beginning of the entry) - uint8_t* cachedKey = cacheEntry + sizeof(MultifileHeader); - int compare = memcmp(cachedKey, key, keySize); - if (compare != 0) { - ALOGW("Cached key and new key do not match! This is a hash collision or modified file"); - removeFromHotCache(entryHash); - return 0; - } - - // Remaining entry following the key is the value - uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader)); - memcpy(value, cachedValue, cachedValueSize); - - return cachedValueSize; -} - -void MultifileBlobCache::finish() { - // Wait for all deferred writes to complete - ALOGV("FINISH: Waiting for work to complete."); - waitForWorkComplete(); - - // Close all entries in the hot cache - for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) { - uint32_t entryHash = hotCacheIter->first; - MultifileHotCache entry = hotCacheIter->second; - - ALOGV("FINISH: Closing hot cache entry for %u", entryHash); - freeHotCacheEntry(entry); - - mHotCache.erase(hotCacheIter++); - } -} - -void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize, - time_t accessTime) { - mEntries.insert(entryHash); - mEntryStats[entryHash] = {valueSize, fileSize, accessTime}; -} - -bool MultifileBlobCache::contains(uint32_t hashEntry) const { - return mEntries.find(hashEntry) != mEntries.end(); -} - -MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) { - return mEntryStats[entryHash]; -} - -void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) { - mTotalCacheSize += fileSize; -} - -void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) { - mTotalCacheSize -= fileSize; -} - -bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer, - size_t newEntrySize) { - ALOGV("HOTCACHE(ADD): Adding %u to hot cache", newEntryHash); - - // Clear space if we need to - if ((mHotCacheSize + newEntrySize) > mHotCacheLimit) { - ALOGV("HOTCACHE(ADD): mHotCacheSize (%zu) + newEntrySize (%zu) is to big for " - "mHotCacheLimit " - "(%zu), freeing up space for %u", - mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash); - - // Wait for all the files to complete writing so our hot cache is accurate - waitForWorkComplete(); - - // Free up old entries until under the limit - for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) { - uint32_t oldEntryHash = hotCacheIter->first; - MultifileHotCache oldEntry = hotCacheIter->second; - - // Move our iterator before deleting the entry - hotCacheIter++; - if (!removeFromHotCache(oldEntryHash)) { - ALOGE("HOTCACHE(ADD): Unable to remove entry %u", oldEntryHash); - return false; - } - - // Clear at least half the hot cache - if ((mHotCacheSize + newEntrySize) <= mHotCacheLimit / 2) { - ALOGV("HOTCACHE(ADD): Freed enough space for %zu", mHotCacheSize); - break; - } - } - } - - // Track it - mHotCache[newEntryHash] = {newFd, newEntryBuffer, newEntrySize}; - mHotCacheSize += newEntrySize; - - ALOGV("HOTCACHE(ADD): New hot cache size: %zu", mHotCacheSize); - - return true; -} - -bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) { - if (mHotCache.find(entryHash) != mHotCache.end()) { - ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash); - - // Wait for all the files to complete writing so our hot cache is accurate - waitForWorkComplete(); - - ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash); - MultifileHotCache entry = mHotCache[entryHash]; - freeHotCacheEntry(entry); - - // Delete the entry from our tracking - mHotCacheSize -= entry.entrySize; - mHotCache.erase(entryHash); - - return true; - } - - return false; -} - -bool MultifileBlobCache::applyLRU(size_t cacheLimit) { - // Walk through our map of sorted last access times and remove files until under the limit - for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) { - uint32_t entryHash = cacheEntryIter->first; - - ALOGV("LRU: Removing entryHash %u", entryHash); - - // Track the overall size - MultifileEntryStats entryStats = getEntryStats(entryHash); - decreaseTotalCacheSize(entryStats.fileSize); - - // Remove it from hot cache if present - removeFromHotCache(entryHash); - - // Remove it from the system - std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash); - if (remove(entryPath.c_str()) != 0) { - ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno)); - return false; - } - - // Increment the iterator before clearing the entry - cacheEntryIter++; - - // Delete the entry from our tracking - size_t count = mEntryStats.erase(entryHash); - if (count != 1) { - ALOGE("LRU: Failed to remove entryHash (%u) from mEntryStats", entryHash); - return false; - } - - // See if it has been reduced enough - size_t totalCacheSize = getTotalSize(); - if (totalCacheSize <= cacheLimit) { - // Success - ALOGV("LRU: Reduced cache to %zu", totalCacheSize); - return true; - } - } - - ALOGV("LRU: Cache is emptry"); - return false; -} - -// When removing files, what fraction of the overall limit should be reached when removing files -// A divisor of two will decrease the cache to 50%, four to 25% and so on -constexpr uint32_t kCacheLimitDivisor = 2; - -// Calculate the cache size and remove old entries until under the limit -void MultifileBlobCache::trimCache(size_t cacheByteLimit) { - // Start with the value provided by egl_cache - size_t limit = cacheByteLimit; - - // Wait for all deferred writes to complete - waitForWorkComplete(); - - size_t size = getTotalSize(); - - // If size is larger than the threshold, remove files using LRU - if (size > limit) { - ALOGV("TRIM: Multifile cache size is larger than %zu, removing old entries", - cacheByteLimit); - if (!applyLRU(limit / kCacheLimitDivisor)) { - ALOGE("Error when clearing multifile shader cache"); - return; - } - } -} - -// This function performs a task. It only knows how to write files to disk, -// but it could be expanded if needed. -void MultifileBlobCache::processTask(DeferredTask& task) { - switch (task.getTaskCommand()) { - case TaskCommand::Exit: { - ALOGV("DEFERRED: Shutting down"); - return; - } - case TaskCommand::WriteToDisk: { - uint32_t entryHash = task.getEntryHash(); - std::string& fullPath = task.getFullPath(); - uint8_t* buffer = task.getBuffer(); - size_t bufferSize = task.getBufferSize(); - - // Create the file or reset it if already present, read+write for user only - int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); - if (fd == -1) { - ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s", - fullPath.c_str(), std::strerror(errno)); - return; - } - - ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str()); - - ssize_t result = write(fd, buffer, bufferSize); - if (result != bufferSize) { - ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(), - std::strerror(errno)); - return; - } - - ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str()); - close(fd); - - // Erase the entry from mDeferredWrites - // Since there could be multiple outstanding writes for an entry, find the matching one - typedef std::multimap::iterator entryIter; - std::pair iterPair = mDeferredWrites.equal_range(entryHash); - for (entryIter it = iterPair.first; it != iterPair.second; ++it) { - if (it->second == buffer) { - ALOGV("DEFERRED: Marking write complete for %u at %p", it->first, it->second); - mDeferredWrites.erase(it); - break; - } - } - - return; - } - default: { - ALOGE("DEFERRED: Unhandled task type"); - return; - } - } -} - -// This function will wait until tasks arrive, then execute them -// If the exit command is submitted, the loop will terminate -void MultifileBlobCache::processTasksImpl(bool* exitThread) { - while (true) { - std::unique_lock lock(mWorkerMutex); - if (mTasks.empty()) { - ALOGV("WORKER: No tasks available, waiting"); - mWorkerThreadIdle = true; - mWorkerIdleCondition.notify_all(); - // Only wake if notified and command queue is not empty - mWorkAvailableCondition.wait(lock, [this] { return !mTasks.empty(); }); - } - - ALOGV("WORKER: Task available, waking up."); - mWorkerThreadIdle = false; - DeferredTask task = std::move(mTasks.front()); - mTasks.pop(); - - if (task.getTaskCommand() == TaskCommand::Exit) { - ALOGV("WORKER: Exiting work loop."); - *exitThread = true; - mWorkerThreadIdle = true; - mWorkerIdleCondition.notify_one(); - return; - } - - lock.unlock(); - processTask(task); - } -} - -// Process tasks until the exit task is submitted -void MultifileBlobCache::processTasks() { - while (true) { - bool exitThread = false; - processTasksImpl(&exitThread); - if (exitThread) { - break; - } - } -} - -// Add a task to the queue to be processed by the worker thread -void MultifileBlobCache::queueTask(DeferredTask&& task) { - std::lock_guard queueLock(mWorkerMutex); - mTasks.emplace(std::move(task)); - mWorkAvailableCondition.notify_one(); -} - -// Wait until all tasks have been completed -void MultifileBlobCache::waitForWorkComplete() { - std::unique_lock lock(mWorkerMutex); - mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); }); -} - -}; // namespace android \ No newline at end of file diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h deleted file mode 100644 index c11dfb72da..0000000000 --- a/opengl/libs/EGL/MultifileBlobCache.h +++ /dev/null @@ -1,167 +0,0 @@ -/* - ** Copyright 2022, The Android Open Source Project - ** - ** Licensed under the Apache License, Version 2.0 (the "License"); - ** you may not use this file except in compliance with the License. - ** You may obtain a copy of the License at - ** - ** http://www.apache.org/licenses/LICENSE-2.0 - ** - ** Unless required by applicable law or agreed to in writing, software - ** distributed under the License is distributed on an "AS IS" BASIS, - ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ** See the License for the specific language governing permissions and - ** limitations under the License. - */ - -#ifndef ANDROID_MULTIFILE_BLOB_CACHE_H -#define ANDROID_MULTIFILE_BLOB_CACHE_H - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace android { - -struct MultifileHeader { - EGLsizeiANDROID keySize; - EGLsizeiANDROID valueSize; -}; - -struct MultifileEntryStats { - EGLsizeiANDROID valueSize; - size_t fileSize; - time_t accessTime; -}; - -struct MultifileHotCache { - int entryFd; - uint8_t* entryBuffer; - size_t entrySize; -}; - -enum class TaskCommand { - Invalid = 0, - WriteToDisk, - Exit, -}; - -class DeferredTask { -public: - DeferredTask(TaskCommand command) - : mCommand(command), mEntryHash(0), mBuffer(nullptr), mBufferSize(0) {} - - TaskCommand getTaskCommand() { return mCommand; } - - void initWriteToDisk(uint32_t entryHash, std::string fullPath, uint8_t* buffer, - size_t bufferSize) { - mCommand = TaskCommand::WriteToDisk; - mEntryHash = entryHash; - mFullPath = std::move(fullPath); - mBuffer = buffer; - mBufferSize = bufferSize; - } - - uint32_t getEntryHash() { return mEntryHash; } - std::string& getFullPath() { return mFullPath; } - uint8_t* getBuffer() { return mBuffer; } - size_t getBufferSize() { return mBufferSize; }; - -private: - TaskCommand mCommand; - - // Parameters for WriteToDisk - uint32_t mEntryHash; - std::string mFullPath; - uint8_t* mBuffer; - size_t mBufferSize; -}; - -class MultifileBlobCache { -public: - MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize, const std::string& baseDir); - ~MultifileBlobCache(); - - void set(const void* key, EGLsizeiANDROID keySize, const void* value, - EGLsizeiANDROID valueSize); - EGLsizeiANDROID get(const void* key, EGLsizeiANDROID keySize, void* value, - EGLsizeiANDROID valueSize); - - void finish(); - - size_t getTotalSize() const { return mTotalCacheSize; } - void trimCache(size_t cacheByteLimit); - -private: - void trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize, - time_t accessTime); - bool contains(uint32_t entryHash) const; - bool removeEntry(uint32_t entryHash); - MultifileEntryStats getEntryStats(uint32_t entryHash); - - size_t getFileSize(uint32_t entryHash); - size_t getValueSize(uint32_t entryHash); - - void increaseTotalCacheSize(size_t fileSize); - void decreaseTotalCacheSize(size_t fileSize); - - bool addToHotCache(uint32_t entryHash, int fd, uint8_t* entryBufer, size_t entrySize); - bool removeFromHotCache(uint32_t entryHash); - - bool applyLRU(size_t cacheLimit); - - bool mInitialized; - std::string mMultifileDirName; - - std::unordered_set mEntries; - std::unordered_map mEntryStats; - std::unordered_map mHotCache; - - size_t mMaxKeySize; - size_t mMaxValueSize; - size_t mMaxTotalSize; - size_t mTotalCacheSize; - size_t mHotCacheLimit; - size_t mHotCacheEntryLimit; - size_t mHotCacheSize; - - // Below are the components used for deferred writes - - // Track whether we have pending writes for an entry - std::multimap mDeferredWrites; - - // Functions to work through tasks in the queue - void processTasks(); - void processTasksImpl(bool* exitThread); - void processTask(DeferredTask& task); - - // Used by main thread to create work for the worker thread - void queueTask(DeferredTask&& task); - - // Used by main thread to wait for worker thread to complete all outstanding work. - void waitForWorkComplete(); - - std::thread mTaskThread; - std::queue mTasks; - std::mutex mWorkerMutex; - - // This condition will block the worker thread until a task is queued - std::condition_variable mWorkAvailableCondition; - - // This condition will block the main thread while the worker thread still has tasks - std::condition_variable mWorkerIdleCondition; - - // This bool will track whether all tasks have been completed - bool mWorkerThreadIdle; -}; - -}; // namespace android - -#endif // ANDROID_MULTIFILE_BLOB_CACHE_H diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp deleted file mode 100644 index 1a55a4fcdd..0000000000 --- a/opengl/libs/EGL/MultifileBlobCache_test.cpp +++ /dev/null @@ -1,200 +0,0 @@ -/* - ** Copyright 2023, The Android Open Source Project - ** - ** Licensed under the Apache License, Version 2.0 (the "License"); - ** you may not use this file except in compliance with the License. - ** You may obtain a copy of the License at - ** - ** http://www.apache.org/licenses/LICENSE-2.0 - ** - ** Unless required by applicable law or agreed to in writing, software - ** distributed under the License is distributed on an "AS IS" BASIS, - ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ** See the License for the specific language governing permissions and - ** limitations under the License. - */ - -#include "MultifileBlobCache.h" - -#include -#include -#include -#include - -#include - -namespace android { - -template -using sp = std::shared_ptr; - -constexpr size_t kMaxTotalSize = 32 * 1024; -constexpr size_t kMaxPreloadSize = 8 * 1024; - -constexpr size_t kMaxKeySize = kMaxPreloadSize / 4; -constexpr size_t kMaxValueSize = kMaxPreloadSize / 2; - -class MultifileBlobCacheTest : public ::testing::Test { -protected: - virtual void SetUp() { - mTempFile.reset(new TemporaryFile()); - mMBC.reset(new MultifileBlobCache(kMaxTotalSize, kMaxPreloadSize, &mTempFile->path[0])); - } - - virtual void TearDown() { mMBC.reset(); } - - std::unique_ptr mTempFile; - std::unique_ptr mMBC; -}; - -TEST_F(MultifileBlobCacheTest, CacheSingleValueSucceeds) { - unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; - mMBC->set("abcd", 4, "efgh", 4); - ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); - ASSERT_EQ('e', buf[0]); - ASSERT_EQ('f', buf[1]); - ASSERT_EQ('g', buf[2]); - ASSERT_EQ('h', buf[3]); -} - -TEST_F(MultifileBlobCacheTest, CacheTwoValuesSucceeds) { - unsigned char buf[2] = {0xee, 0xee}; - mMBC->set("ab", 2, "cd", 2); - mMBC->set("ef", 2, "gh", 2); - ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); - ASSERT_EQ('c', buf[0]); - ASSERT_EQ('d', buf[1]); - ASSERT_EQ(size_t(2), mMBC->get("ef", 2, buf, 2)); - ASSERT_EQ('g', buf[0]); - ASSERT_EQ('h', buf[1]); -} - -TEST_F(MultifileBlobCacheTest, GetSetTwiceSucceeds) { - unsigned char buf[2] = {0xee, 0xee}; - mMBC->set("ab", 2, "cd", 2); - ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); - ASSERT_EQ('c', buf[0]); - ASSERT_EQ('d', buf[1]); - // Use the same key, but different value - mMBC->set("ab", 2, "ef", 2); - ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); - ASSERT_EQ('e', buf[0]); - ASSERT_EQ('f', buf[1]); -} - -TEST_F(MultifileBlobCacheTest, GetOnlyWritesInsideBounds) { - unsigned char buf[6] = {0xee, 0xee, 0xee, 0xee, 0xee, 0xee}; - mMBC->set("abcd", 4, "efgh", 4); - ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf + 1, 4)); - ASSERT_EQ(0xee, buf[0]); - ASSERT_EQ('e', buf[1]); - ASSERT_EQ('f', buf[2]); - ASSERT_EQ('g', buf[3]); - ASSERT_EQ('h', buf[4]); - ASSERT_EQ(0xee, buf[5]); -} - -TEST_F(MultifileBlobCacheTest, GetOnlyWritesIfBufferIsLargeEnough) { - unsigned char buf[3] = {0xee, 0xee, 0xee}; - mMBC->set("abcd", 4, "efgh", 4); - ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 3)); - ASSERT_EQ(0xee, buf[0]); - ASSERT_EQ(0xee, buf[1]); - ASSERT_EQ(0xee, buf[2]); -} - -TEST_F(MultifileBlobCacheTest, GetDoesntAccessNullBuffer) { - mMBC->set("abcd", 4, "efgh", 4); - ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, nullptr, 0)); -} - -TEST_F(MultifileBlobCacheTest, MultipleSetsCacheLatestValue) { - unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; - mMBC->set("abcd", 4, "efgh", 4); - mMBC->set("abcd", 4, "ijkl", 4); - ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); - ASSERT_EQ('i', buf[0]); - ASSERT_EQ('j', buf[1]); - ASSERT_EQ('k', buf[2]); - ASSERT_EQ('l', buf[3]); -} - -TEST_F(MultifileBlobCacheTest, SecondSetKeepsFirstValueIfTooLarge) { - unsigned char buf[kMaxValueSize + 1] = {0xee, 0xee, 0xee, 0xee}; - mMBC->set("abcd", 4, "efgh", 4); - mMBC->set("abcd", 4, buf, kMaxValueSize + 1); - ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); - ASSERT_EQ('e', buf[0]); - ASSERT_EQ('f', buf[1]); - ASSERT_EQ('g', buf[2]); - ASSERT_EQ('h', buf[3]); -} - -TEST_F(MultifileBlobCacheTest, DoesntCacheIfKeyIsTooBig) { - char key[kMaxKeySize + 1]; - unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; - for (int i = 0; i < kMaxKeySize + 1; i++) { - key[i] = 'a'; - } - mMBC->set(key, kMaxKeySize + 1, "bbbb", 4); - ASSERT_EQ(size_t(0), mMBC->get(key, kMaxKeySize + 1, buf, 4)); - ASSERT_EQ(0xee, buf[0]); - ASSERT_EQ(0xee, buf[1]); - ASSERT_EQ(0xee, buf[2]); - ASSERT_EQ(0xee, buf[3]); -} - -TEST_F(MultifileBlobCacheTest, DoesntCacheIfValueIsTooBig) { - char buf[kMaxValueSize + 1]; - for (int i = 0; i < kMaxValueSize + 1; i++) { - buf[i] = 'b'; - } - mMBC->set("abcd", 4, buf, kMaxValueSize + 1); - for (int i = 0; i < kMaxValueSize + 1; i++) { - buf[i] = 0xee; - } - ASSERT_EQ(size_t(0), mMBC->get("abcd", 4, buf, kMaxValueSize + 1)); - for (int i = 0; i < kMaxValueSize + 1; i++) { - SCOPED_TRACE(i); - ASSERT_EQ(0xee, buf[i]); - } -} - -TEST_F(MultifileBlobCacheTest, CacheMaxKeySizeSucceeds) { - char key[kMaxKeySize]; - unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; - for (int i = 0; i < kMaxKeySize; i++) { - key[i] = 'a'; - } - mMBC->set(key, kMaxKeySize, "wxyz", 4); - ASSERT_EQ(size_t(4), mMBC->get(key, kMaxKeySize, buf, 4)); - ASSERT_EQ('w', buf[0]); - ASSERT_EQ('x', buf[1]); - ASSERT_EQ('y', buf[2]); - ASSERT_EQ('z', buf[3]); -} - -TEST_F(MultifileBlobCacheTest, CacheMaxValueSizeSucceeds) { - char buf[kMaxValueSize]; - for (int i = 0; i < kMaxValueSize; i++) { - buf[i] = 'b'; - } - mMBC->set("abcd", 4, buf, kMaxValueSize); - for (int i = 0; i < kMaxValueSize; i++) { - buf[i] = 0xee; - } - mMBC->get("abcd", 4, buf, kMaxValueSize); - for (int i = 0; i < kMaxValueSize; i++) { - SCOPED_TRACE(i); - ASSERT_EQ('b', buf[i]); - } -} - -TEST_F(MultifileBlobCacheTest, CacheMinKeyAndValueSizeSucceeds) { - unsigned char buf[1] = {0xee}; - mMBC->set("x", 1, "y", 1); - ASSERT_EQ(size_t(1), mMBC->get("x", 1, buf, 1)); - ASSERT_EQ('y', buf[0]); -} - -} // namespace android diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp index b00ee33374..1e8a34863d 100644 --- a/opengl/libs/EGL/egl_cache.cpp +++ b/opengl/libs/EGL/egl_cache.cpp @@ -14,8 +14,6 @@ ** limitations under the License. */ -// #define LOG_NDEBUG 0 - #include "egl_cache.h" #include @@ -27,19 +25,22 @@ #include #include "../egl_impl.h" +#include "egl_cache_multifile.h" #include "egl_display.h" // Monolithic cache size limits. -static const size_t kMaxMonolithicKeySize = 12 * 1024; -static const size_t kMaxMonolithicValueSize = 64 * 1024; -static const size_t kMaxMonolithicTotalSize = 2 * 1024 * 1024; +static const size_t maxKeySize = 12 * 1024; +static const size_t maxValueSize = 64 * 1024; +static const size_t maxTotalSize = 32 * 1024 * 1024; // The time in seconds to wait before saving newly inserted monolithic cache entries. -static const unsigned int kDeferredMonolithicSaveDelay = 4; +static const unsigned int deferredSaveDelay = 4; + +// Multifile cache size limit +constexpr size_t kMultifileCacheByteLimit = 64 * 1024 * 1024; -// Multifile cache size limits -constexpr uint32_t kMultifileHotCacheLimit = 8 * 1024 * 1024; -constexpr uint32_t kMultifileCacheByteLimit = 32 * 1024 * 1024; +// Delay before cleaning up multifile cache entries +static const unsigned int deferredMultifileCleanupDelaySeconds = 1; namespace android { @@ -67,7 +68,10 @@ static EGLsizeiANDROID getBlob(const void* key, EGLsizeiANDROID keySize, void* v // egl_cache_t definition // egl_cache_t::egl_cache_t() - : mInitialized(false), mMultifileMode(false), mCacheByteLimit(kMaxMonolithicTotalSize) {} + : mInitialized(false), + mMultifileMode(false), + mCacheByteLimit(maxTotalSize), + mMultifileCleanupPending(false) {} egl_cache_t::~egl_cache_t() {} @@ -81,7 +85,7 @@ void egl_cache_t::initialize(egl_display_t* display) { std::lock_guard lock(mMutex); egl_connection_t* const cnx = &gEGLImpl; - if (display && cnx->dso && cnx->major >= 0 && cnx->minor >= 0) { + if (cnx->dso && cnx->major >= 0 && cnx->minor >= 0) { const char* exts = display->disp.queryString.extensions; size_t bcExtLen = strlen(BC_EXT_STR); size_t extsLen = strlen(exts); @@ -110,36 +114,14 @@ void egl_cache_t::initialize(egl_display_t* display) { } } - // Check the device config to decide whether multifile should be used - if (base::GetBoolProperty("ro.egl.blobcache.multifile", false)) { - mMultifileMode = true; - ALOGV("Using multifile EGL blobcache"); - } - - // Allow forcing the mode for debug purposes - std::string mode = base::GetProperty("debug.egl.blobcache.multifile", ""); - if (mode == "true") { - ALOGV("Forcing multifile cache due to debug.egl.blobcache.multifile == %s", mode.c_str()); - mMultifileMode = true; - } else if (mode == "false") { - ALOGV("Forcing monolithic cache due to debug.egl.blobcache.multifile == %s", mode.c_str()); + // Allow forcing monolithic cache for debug purposes + if (base::GetProperty("debug.egl.blobcache.multifilemode", "") == "false") { + ALOGD("Forcing monolithic cache due to debug.egl.blobcache.multifilemode == \"false\""); mMultifileMode = false; } if (mMultifileMode) { - mCacheByteLimit = static_cast( - base::GetUintProperty("ro.egl.blobcache.multifile_limit", - kMultifileCacheByteLimit)); - - // Check for a debug value - int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.multifile_limit", -1); - if (debugCacheSize >= 0) { - ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.multifile_limit", - mCacheByteLimit, debugCacheSize); - mCacheByteLimit = debugCacheSize; - } - - ALOGV("Using multifile EGL blobcache limit of %zu bytes", mCacheByteLimit); + mCacheByteLimit = kMultifileCacheByteLimit; } mInitialized = true; @@ -151,10 +133,10 @@ void egl_cache_t::terminate() { mBlobCache->writeToFile(); } mBlobCache = nullptr; - if (mMultifileBlobCache) { - mMultifileBlobCache->finish(); + if (mMultifileMode) { + checkMultifileCacheSize(mCacheByteLimit); } - mMultifileBlobCache = nullptr; + mMultifileMode = false; mInitialized = false; } @@ -169,8 +151,20 @@ void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void* if (mInitialized) { if (mMultifileMode) { - MultifileBlobCache* mbc = getMultifileBlobCacheLocked(); - mbc->set(key, keySize, value, valueSize); + setBlobMultifile(key, keySize, value, valueSize, mFilename); + + if (!mMultifileCleanupPending) { + mMultifileCleanupPending = true; + // Kick off a thread to cull cache files below limit + std::thread deferredMultifileCleanupThread([this]() { + sleep(deferredMultifileCleanupDelaySeconds); + std::lock_guard lock(mMutex); + // Check the size of cache and remove entries to stay under limit + checkMultifileCacheSize(mCacheByteLimit); + mMultifileCleanupPending = false; + }); + deferredMultifileCleanupThread.detach(); + } } else { BlobCache* bc = getBlobCacheLocked(); bc->set(key, keySize, value, valueSize); @@ -178,7 +172,7 @@ void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void* if (!mSavePending) { mSavePending = true; std::thread deferredSaveThread([this]() { - sleep(kDeferredMonolithicSaveDelay); + sleep(deferredSaveDelay); std::lock_guard lock(mMutex); if (mInitialized && mBlobCache) { mBlobCache->writeToFile(); @@ -202,21 +196,15 @@ EGLsizeiANDROID egl_cache_t::getBlob(const void* key, EGLsizeiANDROID keySize, v if (mInitialized) { if (mMultifileMode) { - MultifileBlobCache* mbc = getMultifileBlobCacheLocked(); - return mbc->get(key, keySize, value, valueSize); + return getBlobMultifile(key, keySize, value, valueSize, mFilename); } else { BlobCache* bc = getBlobCacheLocked(); return bc->get(key, keySize, value, valueSize); } } - return 0; } -void egl_cache_t::setCacheMode(EGLCacheMode cacheMode) { - mMultifileMode = (cacheMode == EGLCacheMode::Multifile); -} - void egl_cache_t::setCacheFilename(const char* filename) { std::lock_guard lock(mMutex); mFilename = filename; @@ -228,7 +216,7 @@ void egl_cache_t::setCacheLimit(int64_t cacheByteLimit) { if (!mMultifileMode) { // If we're not in multifile mode, ensure the cache limit is only being lowered, // not increasing above the hard coded platform limit - if (cacheByteLimit > kMaxMonolithicTotalSize) { + if (cacheByteLimit > maxTotalSize) { return; } } @@ -238,8 +226,8 @@ void egl_cache_t::setCacheLimit(int64_t cacheByteLimit) { size_t egl_cache_t::getCacheSize() { std::lock_guard lock(mMutex); - if (mMultifileBlobCache) { - return mMultifileBlobCache->getTotalSize(); + if (mMultifileMode) { + return getMultifileCacheSize(); } if (mBlobCache) { return mBlobCache->getSize(); @@ -249,18 +237,9 @@ size_t egl_cache_t::getCacheSize() { BlobCache* egl_cache_t::getBlobCacheLocked() { if (mBlobCache == nullptr) { - mBlobCache.reset(new FileBlobCache(kMaxMonolithicKeySize, kMaxMonolithicValueSize, - mCacheByteLimit, mFilename)); + mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, mCacheByteLimit, mFilename)); } return mBlobCache.get(); } -MultifileBlobCache* egl_cache_t::getMultifileBlobCacheLocked() { - if (mMultifileBlobCache == nullptr) { - mMultifileBlobCache.reset( - new MultifileBlobCache(mCacheByteLimit, kMultifileHotCacheLimit, mFilename)); - } - return mMultifileBlobCache.get(); -} - }; // namespace android diff --git a/opengl/libs/EGL/egl_cache.h b/opengl/libs/EGL/egl_cache.h index 1399368dd8..2dcd803324 100644 --- a/opengl/libs/EGL/egl_cache.h +++ b/opengl/libs/EGL/egl_cache.h @@ -25,7 +25,6 @@ #include #include "FileBlobCache.h" -#include "MultifileBlobCache.h" namespace android { @@ -33,11 +32,6 @@ class egl_display_t; class EGLAPI egl_cache_t { public: - enum class EGLCacheMode { - Monolithic, - Multifile, - }; - // get returns a pointer to the singleton egl_cache_t object. This // singleton object will never be destroyed. static egl_cache_t* get(); @@ -70,9 +64,6 @@ public: // cache contents from one program invocation to another. void setCacheFilename(const char* filename); - // Allow setting monolithic or multifile modes - void setCacheMode(EGLCacheMode cacheMode); - // Allow the fixed cache limit to be overridden void setCacheLimit(int64_t cacheByteLimit); @@ -94,9 +85,6 @@ private: // possible. BlobCache* getBlobCacheLocked(); - // Get or create the multifile blobcache - MultifileBlobCache* getMultifileBlobCacheLocked(); - // mInitialized indicates whether the egl_cache_t is in the initialized // state. It is initialized to false at construction time, and gets set to // true when initialize is called. It is set back to false when terminate @@ -110,9 +98,6 @@ private: // first time it's needed. std::unique_ptr mBlobCache; - // The multifile version of blobcache allowing larger contents to be stored - std::unique_ptr mMultifileBlobCache; - // mFilename is the name of the file for storing cache contents in between // program invocations. It is initialized to an empty string at // construction time, and can be set with the setCacheFilename method. An @@ -138,7 +123,11 @@ private: bool mMultifileMode; // Cache limit - size_t mCacheByteLimit; + int64_t mCacheByteLimit; + + // Whether we've kicked off a side thread that will check the multifile + // cache size and remove entries if needed. + bool mMultifileCleanupPending; }; }; // namespace android diff --git a/opengl/libs/EGL/egl_cache_multifile.cpp b/opengl/libs/EGL/egl_cache_multifile.cpp new file mode 100644 index 0000000000..48e557f190 --- /dev/null +++ b/opengl/libs/EGL/egl_cache_multifile.cpp @@ -0,0 +1,343 @@ +/* + ** Copyright 2022, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +// #define LOG_NDEBUG 0 + +#include "egl_cache_multifile.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static std::string multifileDirName = ""; + +using namespace std::literals; + +namespace { + +// Create a directory for tracking multiple files +void setupMultifile(const std::string& baseDir) { + // If we've already set up the multifile dir in this base directory, we're done + if (!multifileDirName.empty() && multifileDirName.find(baseDir) != std::string::npos) { + return; + } + + // Otherwise, create it + multifileDirName = baseDir + ".multifile"; + if (mkdir(multifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) { + ALOGW("Unable to create directory (%s), errno (%i)", multifileDirName.c_str(), errno); + } +} + +// Create a filename that is based on the hash of the key +std::string getCacheEntryFilename(const void* key, EGLsizeiANDROID keySize, + const std::string& baseDir) { + // Hash the key into a string + std::stringstream keyName; + keyName << android::JenkinsHashMixBytes(0, static_cast(key), keySize); + + // Build a filename using dir and hash + return baseDir + "/" + keyName.str(); +} + +// Determine file age based on stat modification time +// Newer files have a higher age (time since epoch) +time_t getFileAge(const std::string& filePath) { + struct stat st; + if (stat(filePath.c_str(), &st) == 0) { + ALOGD("getFileAge returning %" PRId64 " for file age", static_cast(st.st_mtime)); + return st.st_mtime; + } else { + ALOGW("Failed to stat %s", filePath.c_str()); + return 0; + } +} + +size_t getFileSize(const std::string& filePath) { + struct stat st; + if (stat(filePath.c_str(), &st) != 0) { + ALOGE("Unable to stat %s", filePath.c_str()); + return 0; + } + return st.st_size; +} + +// Walk through directory entries and track age and size +// Then iterate through the entries, oldest first, and remove them until under the limit. +// This will need to be updated if we move to a multilevel cache dir. +bool applyLRU(size_t cacheLimit) { + // Build a multimap of files indexed by age. + // They will be automatically sorted smallest (oldest) to largest (newest) + std::multimap agesToFiles; + + // Map files to sizes + std::unordered_map filesToSizes; + + size_t totalCacheSize = 0; + + DIR* dir; + struct dirent* entry; + if ((dir = opendir(multifileDirName.c_str())) != nullptr) { + while ((entry = readdir(dir)) != nullptr) { + if (entry->d_name == "."s || entry->d_name == ".."s) { + continue; + } + + // Look up each file age + std::string fullPath = multifileDirName + "/" + entry->d_name; + time_t fileAge = getFileAge(fullPath); + + // Track the files, sorted by age + agesToFiles.insert(std::make_pair(fileAge, fullPath)); + + // Also track the size so we know how much room we have freed + size_t fileSize = getFileSize(fullPath); + filesToSizes[fullPath] = fileSize; + totalCacheSize += fileSize; + } + closedir(dir); + } else { + ALOGE("Unable to open filename: %s", multifileDirName.c_str()); + return false; + } + + if (totalCacheSize <= cacheLimit) { + // If LRU was called on a sufficiently small cache, no need to remove anything + return true; + } + + // Walk through the map of files until we're under the cache size + for (const auto& cacheEntryIter : agesToFiles) { + time_t entryAge = cacheEntryIter.first; + const std::string entryPath = cacheEntryIter.second; + + ALOGD("Removing %s with age %ld", entryPath.c_str(), entryAge); + if (std::remove(entryPath.c_str()) != 0) { + ALOGE("Error removing %s: %s", entryPath.c_str(), std::strerror(errno)); + return false; + } + + totalCacheSize -= filesToSizes[entryPath]; + if (totalCacheSize <= cacheLimit) { + // Success + ALOGV("Reduced cache to %zu", totalCacheSize); + return true; + } else { + ALOGD("Cache size is still too large (%zu), removing more files", totalCacheSize); + } + } + + // Should never reach this return + return false; +} + +} // namespace + +namespace android { + +void setBlobMultifile(const void* key, EGLsizeiANDROID keySize, const void* value, + EGLsizeiANDROID valueSize, const std::string& baseDir) { + if (baseDir.empty()) { + return; + } + + setupMultifile(baseDir); + std::string filename = getCacheEntryFilename(key, keySize, multifileDirName); + + ALOGD("Attempting to open filename for set: %s", filename.c_str()); + std::ofstream outfile(filename, std::ofstream::binary); + if (outfile.fail()) { + ALOGW("Unable to open filename: %s", filename.c_str()); + return; + } + + // First write the key + outfile.write(static_cast(key), keySize); + if (outfile.bad()) { + ALOGW("Unable to write key to filename: %s", filename.c_str()); + outfile.close(); + return; + } + ALOGD("Wrote %i bytes to out file for key", static_cast(outfile.tellp())); + + // Then write the value + outfile.write(static_cast(value), valueSize); + if (outfile.bad()) { + ALOGW("Unable to write value to filename: %s", filename.c_str()); + outfile.close(); + return; + } + ALOGD("Wrote %i bytes to out file for full entry", static_cast(outfile.tellp())); + + outfile.close(); +} + +EGLsizeiANDROID getBlobMultifile(const void* key, EGLsizeiANDROID keySize, void* value, + EGLsizeiANDROID valueSize, const std::string& baseDir) { + if (baseDir.empty()) { + return 0; + } + + setupMultifile(baseDir); + std::string filename = getCacheEntryFilename(key, keySize, multifileDirName); + + // Open the hashed filename path + ALOGD("Attempting to open filename for get: %s", filename.c_str()); + int fd = open(filename.c_str(), O_RDONLY); + + // File doesn't exist, this is a MISS, return zero bytes read + if (fd == -1) { + ALOGD("Cache MISS - failed to open filename: %s, error: %s", filename.c_str(), + std::strerror(errno)); + return 0; + } + + ALOGD("Cache HIT - opened filename: %s", filename.c_str()); + + // Get the size of the file + size_t entrySize = getFileSize(filename); + if (keySize > entrySize) { + ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified " + "file", + keySize, entrySize); + close(fd); + return 0; + } + + // Memory map the file + uint8_t* cacheEntry = + reinterpret_cast(mmap(nullptr, entrySize, PROT_READ, MAP_PRIVATE, fd, 0)); + if (cacheEntry == MAP_FAILED) { + ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); + close(fd); + return 0; + } + + // Compare the incoming key with our stored version (the beginning of the entry) + int compare = memcmp(cacheEntry, key, keySize); + if (compare != 0) { + ALOGW("Cached key and new key do not match! This is a hash collision or modified file"); + munmap(cacheEntry, entrySize); + close(fd); + return 0; + } + + // Keys matched, so remaining cache is value size + size_t cachedValueSize = entrySize - keySize; + + // Return actual value size if valueSize is not large enough + if (cachedValueSize > valueSize) { + ALOGD("Skipping file read, not enough room provided (valueSize): %lu, " + "returning required space as %zu", + valueSize, cachedValueSize); + munmap(cacheEntry, entrySize); + close(fd); + return cachedValueSize; + } + + // Remaining entry following the key is the value + uint8_t* cachedValue = cacheEntry + keySize; + memcpy(value, cachedValue, cachedValueSize); + munmap(cacheEntry, entrySize); + close(fd); + + ALOGD("Read %zu bytes from %s", cachedValueSize, filename.c_str()); + return cachedValueSize; +} + +// Walk through the files in our flat directory, checking the size of each one. +// Return the total size of normal files in the directory. +// This will need to be updated if we move to a multilevel cache dir. +size_t getMultifileCacheSize() { + if (multifileDirName.empty()) { + return 0; + } + + DIR* dir; + struct dirent* entry; + size_t size = 0; + + ALOGD("Using %s as the multifile cache dir ", multifileDirName.c_str()); + + if ((dir = opendir(multifileDirName.c_str())) != nullptr) { + while ((entry = readdir(dir)) != nullptr) { + if (entry->d_name == "."s || entry->d_name == ".."s) { + continue; + } + + // Add up the size of all files in the dir + std::string fullPath = multifileDirName + "/" + entry->d_name; + size += getFileSize(fullPath); + } + closedir(dir); + } else { + ALOGW("Unable to open filename: %s", multifileDirName.c_str()); + return 0; + } + + return size; +} + +// When removing files, what fraction of the overall limit should be reached when removing files +// A divisor of two will decrease the cache to 50%, four to 25% and so on +constexpr uint32_t kCacheLimitDivisor = 2; + +// Calculate the cache size and remove old entries until under the limit +void checkMultifileCacheSize(size_t cacheByteLimit) { + // Start with the value provided by egl_cache + size_t limit = cacheByteLimit; + + // Check for a debug value + int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.bytelimit", -1); + if (debugCacheSize >= 0) { + ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.bytelimit", limit, + debugCacheSize); + limit = debugCacheSize; + } + + // Tally up the initial amount of cache in use + size_t size = getMultifileCacheSize(); + ALOGD("Multifile cache dir size: %zu", size); + + // If size is larger than the threshold, remove files using LRU + if (size > limit) { + ALOGV("Multifile cache size is larger than %zu, removing old entries", cacheByteLimit); + if (!applyLRU(limit / kCacheLimitDivisor)) { + ALOGE("Error when clearing multifile shader cache"); + return; + } + } + ALOGD("Multifile cache size after reduction: %zu", getMultifileCacheSize()); +} + +}; // namespace android \ No newline at end of file diff --git a/opengl/libs/EGL/egl_cache_multifile.h b/opengl/libs/EGL/egl_cache_multifile.h new file mode 100644 index 0000000000..ee5fe8108d --- /dev/null +++ b/opengl/libs/EGL/egl_cache_multifile.h @@ -0,0 +1,36 @@ +/* + ** Copyright 2022, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +#ifndef ANDROID_EGL_CACHE_MULTIFILE_H +#define ANDROID_EGL_CACHE_MULTIFILE_H + +#include +#include + +#include + +namespace android { + +void setBlobMultifile(const void* key, EGLsizeiANDROID keySize, const void* value, + EGLsizeiANDROID valueSize, const std::string& baseDir); +EGLsizeiANDROID getBlobMultifile(const void* key, EGLsizeiANDROID keySize, void* value, + EGLsizeiANDROID valueSize, const std::string& baseDir); +size_t getMultifileCacheSize(); +void checkMultifileCacheSize(size_t cacheByteLimit); + +}; // namespace android + +#endif // ANDROID_EGL_CACHE_MULTIFILE_H diff --git a/opengl/tests/EGLTest/egl_cache_test.cpp b/opengl/tests/EGLTest/egl_cache_test.cpp index 2b3e3a46af..265bec492e 100644 --- a/opengl/tests/EGLTest/egl_cache_test.cpp +++ b/opengl/tests/EGLTest/egl_cache_test.cpp @@ -24,7 +24,7 @@ #include #include "egl_cache.h" -#include "MultifileBlobCache.h" +#include "egl_cache_multifile.h" #include "egl_display.h" #include @@ -33,16 +33,12 @@ using namespace std::literals; namespace android { -class EGLCacheTest : public ::testing::TestWithParam { +class EGLCacheTest : public ::testing::Test { protected: virtual void SetUp() { - // Terminate to clean up any previous cache in this process - mCache->terminate(); - + mCache = egl_cache_t::get(); mTempFile.reset(new TemporaryFile()); mCache->setCacheFilename(&mTempFile->path[0]); - mCache->setCacheLimit(1024); - mCache->setCacheMode(mCacheMode); } virtual void TearDown() { @@ -53,12 +49,11 @@ protected: std::string getCachefileName(); - egl_cache_t* mCache = egl_cache_t::get(); + egl_cache_t* mCache; std::unique_ptr mTempFile; - egl_cache_t::EGLCacheMode mCacheMode = GetParam(); }; -TEST_P(EGLCacheTest, UninitializedCacheAlwaysMisses) { +TEST_F(EGLCacheTest, UninitializedCacheAlwaysMisses) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->setBlob("abcd", 4, "efgh", 4); ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4)); @@ -68,7 +63,7 @@ TEST_P(EGLCacheTest, UninitializedCacheAlwaysMisses) { ASSERT_EQ(0xee, buf[3]); } -TEST_P(EGLCacheTest, InitializedCacheAlwaysHits) { +TEST_F(EGLCacheTest, InitializedCacheAlwaysHits) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -79,7 +74,7 @@ TEST_P(EGLCacheTest, InitializedCacheAlwaysHits) { ASSERT_EQ('h', buf[3]); } -TEST_P(EGLCacheTest, TerminatedCacheAlwaysMisses) { +TEST_F(EGLCacheTest, TerminatedCacheAlwaysMisses) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -91,7 +86,7 @@ TEST_P(EGLCacheTest, TerminatedCacheAlwaysMisses) { ASSERT_EQ(0xee, buf[3]); } -TEST_P(EGLCacheTest, ReinitializedCacheContainsValues) { +TEST_F(EGLCacheTest, ReinitializedCacheContainsValues) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -106,12 +101,12 @@ TEST_P(EGLCacheTest, ReinitializedCacheContainsValues) { std::string EGLCacheTest::getCachefileName() { // Return the monolithic filename unless we find the multifile dir - std::string cachePath = &mTempFile->path[0]; - std::string multifileDirName = cachePath + ".multifile"; - std::string cachefileName = ""; + std::string cachefileName = &mTempFile->path[0]; + std::string multifileDirName = cachefileName + ".multifile"; struct stat info; if (stat(multifileDirName.c_str(), &info) == 0) { + // Ensure we only have one file to manage int realFileCount = 0; @@ -126,9 +121,6 @@ std::string EGLCacheTest::getCachefileName() { cachefileName = multifileDirName + "/" + entry->d_name; realFileCount++; } - } else { - printf("Unable to open %s, error: %s\n", - multifileDirName.c_str(), std::strerror(errno)); } if (realFileCount != 1) { @@ -136,19 +128,14 @@ std::string EGLCacheTest::getCachefileName() { // violates test assumptions cachefileName = ""; } - } else { - printf("Unable to stat %s, error: %s\n", - multifileDirName.c_str(), std::strerror(errno)); } return cachefileName; } -TEST_P(EGLCacheTest, ModifiedCacheMisses) { - // Skip if not in multifile mode - if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) { - GTEST_SKIP() << "Skipping test designed for multifile"; - } +TEST_F(EGLCacheTest, ModifiedCacheMisses) { + // Turn this back on if multifile becomes the default + GTEST_SKIP() << "Skipping test designed for multifile, see b/263574392 and b/246966894"; uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); @@ -160,13 +147,13 @@ TEST_P(EGLCacheTest, ModifiedCacheMisses) { ASSERT_EQ('g', buf[2]); ASSERT_EQ('h', buf[3]); - // Ensure the cache file is written to disk - mCache->terminate(); - // Depending on the cache mode, the file will be in different locations std::string cachefileName = getCachefileName(); ASSERT_TRUE(cachefileName.length() > 0); + // Ensure the cache file is written to disk + mCache->terminate(); + // Stomp on the beginning of the cache file, breaking the key match const long stomp = 0xbadf00d; FILE *file = fopen(cachefileName.c_str(), "w"); @@ -177,15 +164,14 @@ TEST_P(EGLCacheTest, ModifiedCacheMisses) { // Ensure no cache hit mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); uint8_t buf2[4] = { 0xee, 0xee, 0xee, 0xee }; - // getBlob may return junk for required size, but should not return a cache hit - mCache->getBlob("abcd", 4, buf2, 4); + ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf2, 4)); ASSERT_EQ(0xee, buf2[0]); ASSERT_EQ(0xee, buf2[1]); ASSERT_EQ(0xee, buf2[2]); ASSERT_EQ(0xee, buf2[3]); } -TEST_P(EGLCacheTest, TerminatedCacheBelowCacheLimit) { +TEST_F(EGLCacheTest, TerminatedCacheBelowCacheLimit) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); @@ -218,8 +204,4 @@ TEST_P(EGLCacheTest, TerminatedCacheBelowCacheLimit) { ASSERT_LE(mCache->getCacheSize(), 4); } -INSTANTIATE_TEST_CASE_P(MonolithicCacheTests, - EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Monolithic)); -INSTANTIATE_TEST_CASE_P(MultifileCacheTests, - EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Multifile)); } -- GitLab From 7b6768b5a83b2e0a7ccf0595b102bb7f70c0d4b1 Mon Sep 17 00:00:00 2001 From: Charlie Wang Date: Fri, 3 Feb 2023 12:33:16 -0800 Subject: [PATCH 0829/1310] Add addService to pass in allowIsolated flag. This provides a way for services to add themselves with the allowIsolated flag set. Bug: 266943251. Test: Service can be added with allowIsolated set to true. Change-Id: I22d518ec8fabd942a7a22ab5baa0b76384ab066c --- .../include_platform/android/binder_manager.h | 16 ++++++++++++++++ libs/binder/ndk/libbinder_ndk.map.txt | 1 + libs/binder/ndk/service_manager.cpp | 13 +++++++++++++ 3 files changed, 30 insertions(+) diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h index ad4188f499..86d5ed27b8 100644 --- a/libs/binder/ndk/include_platform/android/binder_manager.h +++ b/libs/binder/ndk/include_platform/android/binder_manager.h @@ -37,6 +37,22 @@ __BEGIN_DECLS __attribute__((warn_unused_result)) binder_exception_t AServiceManager_addService( AIBinder* binder, const char* instance) __INTRODUCED_IN(29); +/** + * This registers the service with the default service manager under this instance name. This does + * not take ownership of binder. + * + * WARNING: when using this API across an APEX boundary, do not use with unstable + * AIDL services. TODO(b/139325195) + * + * \param binder object to register globally with the service manager. + * \param instance identifier of the service. This will be used to lookup the service. + * \param allowIsolated allows if this service can be isolated. + * + * \return EX_NONE on success. + */ +__attribute__((warn_unused_result)) binder_exception_t AServiceManager_addServiceWithAllowIsolated( + AIBinder* binder, const char* instance, bool allowIsolated) __INTRODUCED_IN(34); + /** * Gets a binder object with this specific instance name. Will return nullptr immediately if the * service is not available This also implicitly calls AIBinder_incStrong (so the caller of this diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt index 54e46287a9..5f2f617946 100644 --- a/libs/binder/ndk/libbinder_ndk.map.txt +++ b/libs/binder/ndk/libbinder_ndk.map.txt @@ -163,6 +163,7 @@ LIBBINDER_NDK34 { # introduced=UpsideDownCake LIBBINDER_NDK_PLATFORM { global: AParcel_getAllowFds; + AServiceManager_addServiceWithAllowIsolated; extern "C++" { AIBinder_fromPlatformBinder*; AIBinder_toPlatformBinder*; diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp index e107c83d14..2763ddb622 100644 --- a/libs/binder/ndk/service_manager.cpp +++ b/libs/binder/ndk/service_manager.cpp @@ -41,6 +41,19 @@ binder_exception_t AServiceManager_addService(AIBinder* binder, const char* inst status_t exception = sm->addService(String16(instance), binder->getBinder()); return PruneException(exception); } + +binder_exception_t AServiceManager_addServiceWithAllowIsolated(AIBinder* binder, + const char* instance, + bool allowIsolated) { + if (binder == nullptr || instance == nullptr) { + return EX_ILLEGAL_ARGUMENT; + } + + sp sm = defaultServiceManager(); + status_t exception = sm->addService(String16(instance), binder->getBinder(), allowIsolated); + return PruneException(exception); +} + AIBinder* AServiceManager_checkService(const char* instance) { if (instance == nullptr) { return nullptr; -- GitLab From 6cca6c2345b2ab508d0f9cbfbb230d26263c5d68 Mon Sep 17 00:00:00 2001 From: Cody Northrop Date: Wed, 8 Feb 2023 20:23:13 -0700 Subject: [PATCH 0830/1310] Reland "EGL: Refactor multifile blobcache" This reverts commit 437354181694798af24038dd65455eeab06a329e. Previous CL was reverted due to boot time regressions. Main change from previous CL is removal of thread.join() if the thread was not initialized. This was happening at boot because a number of processes try to use the cache without providing a location for the cache. In that case, both old and new cache will not initialize. Test: Boot (quickly) Test: pubg_mobile_launch ANGLE trace Test: /data/nativetest64/EGL_test/EGL_test Test: /data/nativetest64/libEGL_test/libEGL_test Bug: b/266725576 Change-Id: Ib9add1c8342278a46c2e629c919719f973f8c612 --- opengl/libs/Android.bp | 7 +- opengl/libs/EGL/MultifileBlobCache.cpp | 689 ++++++++++++++++++++ opengl/libs/EGL/MultifileBlobCache.h | 167 +++++ opengl/libs/EGL/MultifileBlobCache_test.cpp | 200 ++++++ opengl/libs/EGL/egl_cache.cpp | 105 +-- opengl/libs/EGL/egl_cache.h | 21 +- opengl/libs/EGL/egl_cache_multifile.cpp | 343 ---------- opengl/libs/EGL/egl_cache_multifile.h | 36 - opengl/tests/EGLTest/egl_cache_test.cpp | 56 +- 9 files changed, 1178 insertions(+), 446 deletions(-) create mode 100644 opengl/libs/EGL/MultifileBlobCache.cpp create mode 100644 opengl/libs/EGL/MultifileBlobCache.h create mode 100644 opengl/libs/EGL/MultifileBlobCache_test.cpp delete mode 100644 opengl/libs/EGL/egl_cache_multifile.cpp delete mode 100644 opengl/libs/EGL/egl_cache_multifile.h diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp index 750338bd84..49e1cbafb4 100644 --- a/opengl/libs/Android.bp +++ b/opengl/libs/Android.bp @@ -144,6 +144,7 @@ cc_library_static { srcs: [ "EGL/BlobCache.cpp", "EGL/FileBlobCache.cpp", + "EGL/MultifileBlobCache.cpp", ], export_include_dirs: ["EGL"], } @@ -160,7 +161,6 @@ cc_library_shared { srcs: [ "EGL/egl_tls.cpp", "EGL/egl_cache.cpp", - "EGL/egl_cache_multifile.cpp", "EGL/egl_display.cpp", "EGL/egl_object.cpp", "EGL/egl_layers.cpp", @@ -205,6 +205,11 @@ cc_test { srcs: [ "EGL/BlobCache.cpp", "EGL/BlobCache_test.cpp", + "EGL/MultifileBlobCache.cpp", + "EGL/MultifileBlobCache_test.cpp", + ], + shared_libs: [ + "libutils", ], } diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp new file mode 100644 index 0000000000..99af299f8d --- /dev/null +++ b/opengl/libs/EGL/MultifileBlobCache.cpp @@ -0,0 +1,689 @@ +/* + ** Copyright 2022, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +// #define LOG_NDEBUG 0 + +#include "MultifileBlobCache.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +using namespace std::literals; + +namespace { + +// Open the file and determine the size of the value it contains +size_t getValueSizeFromFile(int fd, const std::string& entryPath) { + // Read the beginning of the file to get header + android::MultifileHeader header; + size_t result = read(fd, static_cast(&header), sizeof(android::MultifileHeader)); + if (result != sizeof(android::MultifileHeader)) { + ALOGE("Error reading MultifileHeader from cache entry (%s): %s", entryPath.c_str(), + std::strerror(errno)); + return 0; + } + + return header.valueSize; +} + +// Helper function to close entries or free them +void freeHotCacheEntry(android::MultifileHotCache& entry) { + if (entry.entryFd != -1) { + // If we have an fd, then this entry was added to hot cache via INIT or GET + // We need to unmap and close the entry + munmap(entry.entryBuffer, entry.entrySize); + close(entry.entryFd); + } else { + // Otherwise, this was added to hot cache during SET, so it was never mapped + // and fd was only on the deferred thread. + delete[] entry.entryBuffer; + } +} + +} // namespace + +namespace android { + +MultifileBlobCache::MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize, + const std::string& baseDir) + : mInitialized(false), + mMaxTotalSize(maxTotalSize), + mTotalCacheSize(0), + mHotCacheLimit(maxHotCacheSize), + mHotCacheSize(0), + mWorkerThreadIdle(true) { + if (baseDir.empty()) { + ALOGV("INIT: no baseDir provided in MultifileBlobCache constructor, returning early."); + return; + } + + // Establish the name of our multifile directory + mMultifileDirName = baseDir + ".multifile"; + + // Set a limit for max key and value, ensuring at least one entry can always fit in hot cache + mMaxKeySize = mHotCacheLimit / 4; + mMaxValueSize = mHotCacheLimit / 2; + + ALOGV("INIT: Initializing multifile blobcache with maxKeySize=%zu and maxValueSize=%zu", + mMaxKeySize, mMaxValueSize); + + // Initialize our cache with the contents of the directory + mTotalCacheSize = 0; + + // Create the worker thread + mTaskThread = std::thread(&MultifileBlobCache::processTasks, this); + + // See if the dir exists, and initialize using its contents + struct stat st; + if (stat(mMultifileDirName.c_str(), &st) == 0) { + // Read all the files and gather details, then preload their contents + DIR* dir; + struct dirent* entry; + if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) { + while ((entry = readdir(dir)) != nullptr) { + if (entry->d_name == "."s || entry->d_name == ".."s) { + continue; + } + + std::string entryName = entry->d_name; + std::string fullPath = mMultifileDirName + "/" + entryName; + + // The filename is the same as the entryHash + uint32_t entryHash = static_cast(strtoul(entry->d_name, nullptr, 10)); + + ALOGV("INIT: Checking entry %u", entryHash); + + // Look up the details of the file + struct stat st; + if (stat(fullPath.c_str(), &st) != 0) { + ALOGE("Failed to stat %s", fullPath.c_str()); + return; + } + + // Open the file so we can read its header + int fd = open(fullPath.c_str(), O_RDONLY); + if (fd == -1) { + ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(), + std::strerror(errno)); + return; + } + + // Look up the details we track about each file + size_t valueSize = getValueSizeFromFile(fd, fullPath); + + // If the cache entry is damaged or no good, remove it + // TODO: Perform any other checks + if (valueSize <= 0 || st.st_size <= 0 || st.st_atime <= 0) { + ALOGV("INIT: Entry %u has a problem! Removing.", entryHash); + if (remove(fullPath.c_str()) != 0) { + ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno)); + } + continue; + } + + ALOGV("INIT: Entry %u is good, tracking it now.", entryHash); + + // Note: Converting from off_t (signed) to size_t (unsigned) + size_t fileSize = static_cast(st.st_size); + time_t accessTime = st.st_atime; + + // Track details for rapid lookup later + trackEntry(entryHash, valueSize, fileSize, accessTime); + + // Track the total size + increaseTotalCacheSize(fileSize); + + // Preload the entry for fast retrieval + if ((mHotCacheSize + fileSize) < mHotCacheLimit) { + // Memory map the file + uint8_t* mappedEntry = reinterpret_cast( + mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0)); + if (mappedEntry == MAP_FAILED) { + ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); + } + + ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for " + "entryHash %u", + fd, mappedEntry, entryHash); + + // Track the details of the preload so they can be retrieved later + if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) { + ALOGE("INIT Failed to add %u to hot cache", entryHash); + munmap(mappedEntry, fileSize); + close(fd); + return; + } + } else { + close(fd); + } + } + closedir(dir); + } else { + ALOGE("Unable to open filename: %s", mMultifileDirName.c_str()); + } + } else { + // If the multifile directory does not exist, create it and start from scratch + if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) { + ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno); + } + } + + mInitialized = true; +} + +MultifileBlobCache::~MultifileBlobCache() { + if (!mInitialized) { + return; + } + + // Inform the worker thread we're done + ALOGV("DESCTRUCTOR: Shutting down worker thread"); + DeferredTask task(TaskCommand::Exit); + queueTask(std::move(task)); + + // Wait for it to complete + ALOGV("DESCTRUCTOR: Waiting for worker thread to complete"); + waitForWorkComplete(); + if (mTaskThread.joinable()) { + mTaskThread.join(); + } +} + +// Set will add the entry to hot cache and start a deferred process to write it to disk +void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const void* value, + EGLsizeiANDROID valueSize) { + if (!mInitialized) { + return; + } + + // Ensure key and value are under their limits + if (keySize > mMaxKeySize || valueSize > mMaxValueSize) { + ALOGV("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize, + valueSize, mMaxValueSize); + return; + } + + // Generate a hash of the key and use it to track this entry + uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast(key), keySize); + + size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize; + + // If we're going to be over the cache limit, kick off a trim to clear space + if (getTotalSize() + fileSize > mMaxTotalSize) { + ALOGV("SET: Cache is full, calling trimCache to clear space"); + trimCache(mMaxTotalSize); + } + + ALOGV("SET: Add %u to cache", entryHash); + + uint8_t* buffer = new uint8_t[fileSize]; + + // Write the key and value after the header + android::MultifileHeader header = {keySize, valueSize}; + memcpy(static_cast(buffer), static_cast(&header), + sizeof(android::MultifileHeader)); + memcpy(static_cast(buffer + sizeof(MultifileHeader)), static_cast(key), + keySize); + memcpy(static_cast(buffer + sizeof(MultifileHeader) + keySize), + static_cast(value), valueSize); + + std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash); + + // Track the size and access time for quick recall + trackEntry(entryHash, valueSize, fileSize, time(0)); + + // Update the overall cache size + increaseTotalCacheSize(fileSize); + + // Keep the entry in hot cache for quick retrieval + ALOGV("SET: Adding %u to hot cache.", entryHash); + + // Sending -1 as the fd indicates we don't have an fd for this + if (!addToHotCache(entryHash, -1, buffer, fileSize)) { + ALOGE("GET: Failed to add %u to hot cache", entryHash); + return; + } + + // Track that we're creating a pending write for this entry + // Include the buffer to handle the case when multiple writes are pending for an entry + mDeferredWrites.insert(std::make_pair(entryHash, buffer)); + + // Create deferred task to write to storage + ALOGV("SET: Adding task to queue."); + DeferredTask task(TaskCommand::WriteToDisk); + task.initWriteToDisk(entryHash, fullPath, buffer, fileSize); + queueTask(std::move(task)); +} + +// Get will check the hot cache, then load it from disk if needed +EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize, void* value, + EGLsizeiANDROID valueSize) { + if (!mInitialized) { + return 0; + } + + // Ensure key and value are under their limits + if (keySize > mMaxKeySize || valueSize > mMaxValueSize) { + ALOGV("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize, + valueSize, mMaxValueSize); + return 0; + } + + // Generate a hash of the key and use it to track this entry + uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast(key), keySize); + + // See if we have this file + if (!contains(entryHash)) { + ALOGV("GET: Cache MISS - cache does not contain entry: %u", entryHash); + return 0; + } + + // Look up the data for this entry + MultifileEntryStats entryStats = getEntryStats(entryHash); + + size_t cachedValueSize = entryStats.valueSize; + if (cachedValueSize > valueSize) { + ALOGV("GET: Cache MISS - valueSize not large enough (%lu) for entry %u, returning required" + "size (%zu)", + valueSize, entryHash, cachedValueSize); + return cachedValueSize; + } + + // We have the file and have enough room to write it out, return the entry + ALOGV("GET: Cache HIT - cache contains entry: %u", entryHash); + + // Look up the size of the file + size_t fileSize = entryStats.fileSize; + if (keySize > fileSize) { + ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified " + "file", + keySize, fileSize); + return 0; + } + + std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash); + + // Open the hashed filename path + uint8_t* cacheEntry = 0; + + // Check hot cache + if (mHotCache.find(entryHash) != mHotCache.end()) { + ALOGV("GET: HotCache HIT for entry %u", entryHash); + cacheEntry = mHotCache[entryHash].entryBuffer; + } else { + ALOGV("GET: HotCache MISS for entry: %u", entryHash); + + if (mDeferredWrites.find(entryHash) != mDeferredWrites.end()) { + // Wait for writes to complete if there is an outstanding write for this entry + ALOGV("GET: Waiting for write to complete for %u", entryHash); + waitForWorkComplete(); + } + + // Open the entry file + int fd = open(fullPath.c_str(), O_RDONLY); + if (fd == -1) { + ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(), + std::strerror(errno)); + return 0; + } + + // Memory map the file + cacheEntry = + reinterpret_cast(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0)); + if (cacheEntry == MAP_FAILED) { + ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); + close(fd); + return 0; + } + + ALOGV("GET: Adding %u to hot cache", entryHash); + if (!addToHotCache(entryHash, fd, cacheEntry, fileSize)) { + ALOGE("GET: Failed to add %u to hot cache", entryHash); + return 0; + } + + cacheEntry = mHotCache[entryHash].entryBuffer; + } + + // Ensure the header matches + MultifileHeader* header = reinterpret_cast(cacheEntry); + if (header->keySize != keySize || header->valueSize != valueSize) { + ALOGW("Mismatch on keySize(%ld vs. cached %ld) or valueSize(%ld vs. cached %ld) compared " + "to cache header values for fullPath: %s", + keySize, header->keySize, valueSize, header->valueSize, fullPath.c_str()); + removeFromHotCache(entryHash); + return 0; + } + + // Compare the incoming key with our stored version (the beginning of the entry) + uint8_t* cachedKey = cacheEntry + sizeof(MultifileHeader); + int compare = memcmp(cachedKey, key, keySize); + if (compare != 0) { + ALOGW("Cached key and new key do not match! This is a hash collision or modified file"); + removeFromHotCache(entryHash); + return 0; + } + + // Remaining entry following the key is the value + uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader)); + memcpy(value, cachedValue, cachedValueSize); + + return cachedValueSize; +} + +void MultifileBlobCache::finish() { + if (!mInitialized) { + return; + } + + // Wait for all deferred writes to complete + ALOGV("FINISH: Waiting for work to complete."); + waitForWorkComplete(); + + // Close all entries in the hot cache + for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) { + uint32_t entryHash = hotCacheIter->first; + MultifileHotCache entry = hotCacheIter->second; + + ALOGV("FINISH: Closing hot cache entry for %u", entryHash); + freeHotCacheEntry(entry); + + mHotCache.erase(hotCacheIter++); + } +} + +void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize, + time_t accessTime) { + mEntries.insert(entryHash); + mEntryStats[entryHash] = {valueSize, fileSize, accessTime}; +} + +bool MultifileBlobCache::contains(uint32_t hashEntry) const { + return mEntries.find(hashEntry) != mEntries.end(); +} + +MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) { + return mEntryStats[entryHash]; +} + +void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) { + mTotalCacheSize += fileSize; +} + +void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) { + mTotalCacheSize -= fileSize; +} + +bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer, + size_t newEntrySize) { + ALOGV("HOTCACHE(ADD): Adding %u to hot cache", newEntryHash); + + // Clear space if we need to + if ((mHotCacheSize + newEntrySize) > mHotCacheLimit) { + ALOGV("HOTCACHE(ADD): mHotCacheSize (%zu) + newEntrySize (%zu) is to big for " + "mHotCacheLimit " + "(%zu), freeing up space for %u", + mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash); + + // Wait for all the files to complete writing so our hot cache is accurate + waitForWorkComplete(); + + // Free up old entries until under the limit + for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) { + uint32_t oldEntryHash = hotCacheIter->first; + MultifileHotCache oldEntry = hotCacheIter->second; + + // Move our iterator before deleting the entry + hotCacheIter++; + if (!removeFromHotCache(oldEntryHash)) { + ALOGE("HOTCACHE(ADD): Unable to remove entry %u", oldEntryHash); + return false; + } + + // Clear at least half the hot cache + if ((mHotCacheSize + newEntrySize) <= mHotCacheLimit / 2) { + ALOGV("HOTCACHE(ADD): Freed enough space for %zu", mHotCacheSize); + break; + } + } + } + + // Track it + mHotCache[newEntryHash] = {newFd, newEntryBuffer, newEntrySize}; + mHotCacheSize += newEntrySize; + + ALOGV("HOTCACHE(ADD): New hot cache size: %zu", mHotCacheSize); + + return true; +} + +bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) { + if (mHotCache.find(entryHash) != mHotCache.end()) { + ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash); + + // Wait for all the files to complete writing so our hot cache is accurate + waitForWorkComplete(); + + ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash); + MultifileHotCache entry = mHotCache[entryHash]; + freeHotCacheEntry(entry); + + // Delete the entry from our tracking + mHotCacheSize -= entry.entrySize; + mHotCache.erase(entryHash); + + return true; + } + + return false; +} + +bool MultifileBlobCache::applyLRU(size_t cacheLimit) { + // Walk through our map of sorted last access times and remove files until under the limit + for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) { + uint32_t entryHash = cacheEntryIter->first; + + ALOGV("LRU: Removing entryHash %u", entryHash); + + // Track the overall size + MultifileEntryStats entryStats = getEntryStats(entryHash); + decreaseTotalCacheSize(entryStats.fileSize); + + // Remove it from hot cache if present + removeFromHotCache(entryHash); + + // Remove it from the system + std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash); + if (remove(entryPath.c_str()) != 0) { + ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno)); + return false; + } + + // Increment the iterator before clearing the entry + cacheEntryIter++; + + // Delete the entry from our tracking + size_t count = mEntryStats.erase(entryHash); + if (count != 1) { + ALOGE("LRU: Failed to remove entryHash (%u) from mEntryStats", entryHash); + return false; + } + + // See if it has been reduced enough + size_t totalCacheSize = getTotalSize(); + if (totalCacheSize <= cacheLimit) { + // Success + ALOGV("LRU: Reduced cache to %zu", totalCacheSize); + return true; + } + } + + ALOGV("LRU: Cache is emptry"); + return false; +} + +// When removing files, what fraction of the overall limit should be reached when removing files +// A divisor of two will decrease the cache to 50%, four to 25% and so on +constexpr uint32_t kCacheLimitDivisor = 2; + +// Calculate the cache size and remove old entries until under the limit +void MultifileBlobCache::trimCache(size_t cacheByteLimit) { + // Start with the value provided by egl_cache + size_t limit = cacheByteLimit; + + // Wait for all deferred writes to complete + waitForWorkComplete(); + + size_t size = getTotalSize(); + + // If size is larger than the threshold, remove files using LRU + if (size > limit) { + ALOGV("TRIM: Multifile cache size is larger than %zu, removing old entries", + cacheByteLimit); + if (!applyLRU(limit / kCacheLimitDivisor)) { + ALOGE("Error when clearing multifile shader cache"); + return; + } + } +} + +// This function performs a task. It only knows how to write files to disk, +// but it could be expanded if needed. +void MultifileBlobCache::processTask(DeferredTask& task) { + switch (task.getTaskCommand()) { + case TaskCommand::Exit: { + ALOGV("DEFERRED: Shutting down"); + return; + } + case TaskCommand::WriteToDisk: { + uint32_t entryHash = task.getEntryHash(); + std::string& fullPath = task.getFullPath(); + uint8_t* buffer = task.getBuffer(); + size_t bufferSize = task.getBufferSize(); + + // Create the file or reset it if already present, read+write for user only + int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd == -1) { + ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s", + fullPath.c_str(), std::strerror(errno)); + return; + } + + ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str()); + + ssize_t result = write(fd, buffer, bufferSize); + if (result != bufferSize) { + ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(), + std::strerror(errno)); + return; + } + + ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str()); + close(fd); + + // Erase the entry from mDeferredWrites + // Since there could be multiple outstanding writes for an entry, find the matching one + typedef std::multimap::iterator entryIter; + std::pair iterPair = mDeferredWrites.equal_range(entryHash); + for (entryIter it = iterPair.first; it != iterPair.second; ++it) { + if (it->second == buffer) { + ALOGV("DEFERRED: Marking write complete for %u at %p", it->first, it->second); + mDeferredWrites.erase(it); + break; + } + } + + return; + } + default: { + ALOGE("DEFERRED: Unhandled task type"); + return; + } + } +} + +// This function will wait until tasks arrive, then execute them +// If the exit command is submitted, the loop will terminate +void MultifileBlobCache::processTasksImpl(bool* exitThread) { + while (true) { + std::unique_lock lock(mWorkerMutex); + if (mTasks.empty()) { + ALOGV("WORKER: No tasks available, waiting"); + mWorkerThreadIdle = true; + mWorkerIdleCondition.notify_all(); + // Only wake if notified and command queue is not empty + mWorkAvailableCondition.wait(lock, [this] { return !mTasks.empty(); }); + } + + ALOGV("WORKER: Task available, waking up."); + mWorkerThreadIdle = false; + DeferredTask task = std::move(mTasks.front()); + mTasks.pop(); + + if (task.getTaskCommand() == TaskCommand::Exit) { + ALOGV("WORKER: Exiting work loop."); + *exitThread = true; + mWorkerThreadIdle = true; + mWorkerIdleCondition.notify_one(); + return; + } + + lock.unlock(); + processTask(task); + } +} + +// Process tasks until the exit task is submitted +void MultifileBlobCache::processTasks() { + while (true) { + bool exitThread = false; + processTasksImpl(&exitThread); + if (exitThread) { + break; + } + } +} + +// Add a task to the queue to be processed by the worker thread +void MultifileBlobCache::queueTask(DeferredTask&& task) { + std::lock_guard queueLock(mWorkerMutex); + mTasks.emplace(std::move(task)); + mWorkAvailableCondition.notify_one(); +} + +// Wait until all tasks have been completed +void MultifileBlobCache::waitForWorkComplete() { + std::unique_lock lock(mWorkerMutex); + mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); }); +} + +}; // namespace android \ No newline at end of file diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h new file mode 100644 index 0000000000..c0cc9dc2a9 --- /dev/null +++ b/opengl/libs/EGL/MultifileBlobCache.h @@ -0,0 +1,167 @@ +/* + ** Copyright 2022, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +#ifndef ANDROID_MULTIFILE_BLOB_CACHE_H +#define ANDROID_MULTIFILE_BLOB_CACHE_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace android { + +struct MultifileHeader { + EGLsizeiANDROID keySize; + EGLsizeiANDROID valueSize; +}; + +struct MultifileEntryStats { + EGLsizeiANDROID valueSize; + size_t fileSize; + time_t accessTime; +}; + +struct MultifileHotCache { + int entryFd; + uint8_t* entryBuffer; + size_t entrySize; +}; + +enum class TaskCommand { + Invalid = 0, + WriteToDisk, + Exit, +}; + +class DeferredTask { +public: + DeferredTask(TaskCommand command) + : mCommand(command), mEntryHash(0), mBuffer(nullptr), mBufferSize(0) {} + + TaskCommand getTaskCommand() { return mCommand; } + + void initWriteToDisk(uint32_t entryHash, std::string fullPath, uint8_t* buffer, + size_t bufferSize) { + mCommand = TaskCommand::WriteToDisk; + mEntryHash = entryHash; + mFullPath = std::move(fullPath); + mBuffer = buffer; + mBufferSize = bufferSize; + } + + uint32_t getEntryHash() { return mEntryHash; } + std::string& getFullPath() { return mFullPath; } + uint8_t* getBuffer() { return mBuffer; } + size_t getBufferSize() { return mBufferSize; }; + +private: + TaskCommand mCommand; + + // Parameters for WriteToDisk + uint32_t mEntryHash; + std::string mFullPath; + uint8_t* mBuffer; + size_t mBufferSize; +}; + +class MultifileBlobCache { +public: + MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize, const std::string& baseDir); + ~MultifileBlobCache(); + + void set(const void* key, EGLsizeiANDROID keySize, const void* value, + EGLsizeiANDROID valueSize); + EGLsizeiANDROID get(const void* key, EGLsizeiANDROID keySize, void* value, + EGLsizeiANDROID valueSize); + + void finish(); + + size_t getTotalSize() const { return mTotalCacheSize; } + +private: + void trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize, + time_t accessTime); + bool contains(uint32_t entryHash) const; + bool removeEntry(uint32_t entryHash); + MultifileEntryStats getEntryStats(uint32_t entryHash); + + size_t getFileSize(uint32_t entryHash); + size_t getValueSize(uint32_t entryHash); + + void increaseTotalCacheSize(size_t fileSize); + void decreaseTotalCacheSize(size_t fileSize); + + bool addToHotCache(uint32_t entryHash, int fd, uint8_t* entryBufer, size_t entrySize); + bool removeFromHotCache(uint32_t entryHash); + + void trimCache(size_t cacheByteLimit); + bool applyLRU(size_t cacheLimit); + + bool mInitialized; + std::string mMultifileDirName; + + std::unordered_set mEntries; + std::unordered_map mEntryStats; + std::unordered_map mHotCache; + + size_t mMaxKeySize; + size_t mMaxValueSize; + size_t mMaxTotalSize; + size_t mTotalCacheSize; + size_t mHotCacheLimit; + size_t mHotCacheEntryLimit; + size_t mHotCacheSize; + + // Below are the components used for deferred writes + + // Track whether we have pending writes for an entry + std::multimap mDeferredWrites; + + // Functions to work through tasks in the queue + void processTasks(); + void processTasksImpl(bool* exitThread); + void processTask(DeferredTask& task); + + // Used by main thread to create work for the worker thread + void queueTask(DeferredTask&& task); + + // Used by main thread to wait for worker thread to complete all outstanding work. + void waitForWorkComplete(); + + std::thread mTaskThread; + std::queue mTasks; + std::mutex mWorkerMutex; + + // This condition will block the worker thread until a task is queued + std::condition_variable mWorkAvailableCondition; + + // This condition will block the main thread while the worker thread still has tasks + std::condition_variable mWorkerIdleCondition; + + // This bool will track whether all tasks have been completed + bool mWorkerThreadIdle; +}; + +}; // namespace android + +#endif // ANDROID_MULTIFILE_BLOB_CACHE_H diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp new file mode 100644 index 0000000000..1a55a4fcdd --- /dev/null +++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp @@ -0,0 +1,200 @@ +/* + ** Copyright 2023, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +#include "MultifileBlobCache.h" + +#include +#include +#include +#include + +#include + +namespace android { + +template +using sp = std::shared_ptr; + +constexpr size_t kMaxTotalSize = 32 * 1024; +constexpr size_t kMaxPreloadSize = 8 * 1024; + +constexpr size_t kMaxKeySize = kMaxPreloadSize / 4; +constexpr size_t kMaxValueSize = kMaxPreloadSize / 2; + +class MultifileBlobCacheTest : public ::testing::Test { +protected: + virtual void SetUp() { + mTempFile.reset(new TemporaryFile()); + mMBC.reset(new MultifileBlobCache(kMaxTotalSize, kMaxPreloadSize, &mTempFile->path[0])); + } + + virtual void TearDown() { mMBC.reset(); } + + std::unique_ptr mTempFile; + std::unique_ptr mMBC; +}; + +TEST_F(MultifileBlobCacheTest, CacheSingleValueSucceeds) { + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, CacheTwoValuesSucceeds) { + unsigned char buf[2] = {0xee, 0xee}; + mMBC->set("ab", 2, "cd", 2); + mMBC->set("ef", 2, "gh", 2); + ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); + ASSERT_EQ('c', buf[0]); + ASSERT_EQ('d', buf[1]); + ASSERT_EQ(size_t(2), mMBC->get("ef", 2, buf, 2)); + ASSERT_EQ('g', buf[0]); + ASSERT_EQ('h', buf[1]); +} + +TEST_F(MultifileBlobCacheTest, GetSetTwiceSucceeds) { + unsigned char buf[2] = {0xee, 0xee}; + mMBC->set("ab", 2, "cd", 2); + ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); + ASSERT_EQ('c', buf[0]); + ASSERT_EQ('d', buf[1]); + // Use the same key, but different value + mMBC->set("ab", 2, "ef", 2); + ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); +} + +TEST_F(MultifileBlobCacheTest, GetOnlyWritesInsideBounds) { + unsigned char buf[6] = {0xee, 0xee, 0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf + 1, 4)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ('e', buf[1]); + ASSERT_EQ('f', buf[2]); + ASSERT_EQ('g', buf[3]); + ASSERT_EQ('h', buf[4]); + ASSERT_EQ(0xee, buf[5]); +} + +TEST_F(MultifileBlobCacheTest, GetOnlyWritesIfBufferIsLargeEnough) { + unsigned char buf[3] = {0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 3)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ(0xee, buf[1]); + ASSERT_EQ(0xee, buf[2]); +} + +TEST_F(MultifileBlobCacheTest, GetDoesntAccessNullBuffer) { + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, nullptr, 0)); +} + +TEST_F(MultifileBlobCacheTest, MultipleSetsCacheLatestValue) { + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + mMBC->set("abcd", 4, "ijkl", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('i', buf[0]); + ASSERT_EQ('j', buf[1]); + ASSERT_EQ('k', buf[2]); + ASSERT_EQ('l', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, SecondSetKeepsFirstValueIfTooLarge) { + unsigned char buf[kMaxValueSize + 1] = {0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + mMBC->set("abcd", 4, buf, kMaxValueSize + 1); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, DoesntCacheIfKeyIsTooBig) { + char key[kMaxKeySize + 1]; + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + for (int i = 0; i < kMaxKeySize + 1; i++) { + key[i] = 'a'; + } + mMBC->set(key, kMaxKeySize + 1, "bbbb", 4); + ASSERT_EQ(size_t(0), mMBC->get(key, kMaxKeySize + 1, buf, 4)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ(0xee, buf[1]); + ASSERT_EQ(0xee, buf[2]); + ASSERT_EQ(0xee, buf[3]); +} + +TEST_F(MultifileBlobCacheTest, DoesntCacheIfValueIsTooBig) { + char buf[kMaxValueSize + 1]; + for (int i = 0; i < kMaxValueSize + 1; i++) { + buf[i] = 'b'; + } + mMBC->set("abcd", 4, buf, kMaxValueSize + 1); + for (int i = 0; i < kMaxValueSize + 1; i++) { + buf[i] = 0xee; + } + ASSERT_EQ(size_t(0), mMBC->get("abcd", 4, buf, kMaxValueSize + 1)); + for (int i = 0; i < kMaxValueSize + 1; i++) { + SCOPED_TRACE(i); + ASSERT_EQ(0xee, buf[i]); + } +} + +TEST_F(MultifileBlobCacheTest, CacheMaxKeySizeSucceeds) { + char key[kMaxKeySize]; + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + for (int i = 0; i < kMaxKeySize; i++) { + key[i] = 'a'; + } + mMBC->set(key, kMaxKeySize, "wxyz", 4); + ASSERT_EQ(size_t(4), mMBC->get(key, kMaxKeySize, buf, 4)); + ASSERT_EQ('w', buf[0]); + ASSERT_EQ('x', buf[1]); + ASSERT_EQ('y', buf[2]); + ASSERT_EQ('z', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, CacheMaxValueSizeSucceeds) { + char buf[kMaxValueSize]; + for (int i = 0; i < kMaxValueSize; i++) { + buf[i] = 'b'; + } + mMBC->set("abcd", 4, buf, kMaxValueSize); + for (int i = 0; i < kMaxValueSize; i++) { + buf[i] = 0xee; + } + mMBC->get("abcd", 4, buf, kMaxValueSize); + for (int i = 0; i < kMaxValueSize; i++) { + SCOPED_TRACE(i); + ASSERT_EQ('b', buf[i]); + } +} + +TEST_F(MultifileBlobCacheTest, CacheMinKeyAndValueSizeSucceeds) { + unsigned char buf[1] = {0xee}; + mMBC->set("x", 1, "y", 1); + ASSERT_EQ(size_t(1), mMBC->get("x", 1, buf, 1)); + ASSERT_EQ('y', buf[0]); +} + +} // namespace android diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp index 1e8a34863d..b00ee33374 100644 --- a/opengl/libs/EGL/egl_cache.cpp +++ b/opengl/libs/EGL/egl_cache.cpp @@ -14,6 +14,8 @@ ** limitations under the License. */ +// #define LOG_NDEBUG 0 + #include "egl_cache.h" #include @@ -25,22 +27,19 @@ #include #include "../egl_impl.h" -#include "egl_cache_multifile.h" #include "egl_display.h" // Monolithic cache size limits. -static const size_t maxKeySize = 12 * 1024; -static const size_t maxValueSize = 64 * 1024; -static const size_t maxTotalSize = 32 * 1024 * 1024; +static const size_t kMaxMonolithicKeySize = 12 * 1024; +static const size_t kMaxMonolithicValueSize = 64 * 1024; +static const size_t kMaxMonolithicTotalSize = 2 * 1024 * 1024; // The time in seconds to wait before saving newly inserted monolithic cache entries. -static const unsigned int deferredSaveDelay = 4; - -// Multifile cache size limit -constexpr size_t kMultifileCacheByteLimit = 64 * 1024 * 1024; +static const unsigned int kDeferredMonolithicSaveDelay = 4; -// Delay before cleaning up multifile cache entries -static const unsigned int deferredMultifileCleanupDelaySeconds = 1; +// Multifile cache size limits +constexpr uint32_t kMultifileHotCacheLimit = 8 * 1024 * 1024; +constexpr uint32_t kMultifileCacheByteLimit = 32 * 1024 * 1024; namespace android { @@ -68,10 +67,7 @@ static EGLsizeiANDROID getBlob(const void* key, EGLsizeiANDROID keySize, void* v // egl_cache_t definition // egl_cache_t::egl_cache_t() - : mInitialized(false), - mMultifileMode(false), - mCacheByteLimit(maxTotalSize), - mMultifileCleanupPending(false) {} + : mInitialized(false), mMultifileMode(false), mCacheByteLimit(kMaxMonolithicTotalSize) {} egl_cache_t::~egl_cache_t() {} @@ -85,7 +81,7 @@ void egl_cache_t::initialize(egl_display_t* display) { std::lock_guard lock(mMutex); egl_connection_t* const cnx = &gEGLImpl; - if (cnx->dso && cnx->major >= 0 && cnx->minor >= 0) { + if (display && cnx->dso && cnx->major >= 0 && cnx->minor >= 0) { const char* exts = display->disp.queryString.extensions; size_t bcExtLen = strlen(BC_EXT_STR); size_t extsLen = strlen(exts); @@ -114,14 +110,36 @@ void egl_cache_t::initialize(egl_display_t* display) { } } - // Allow forcing monolithic cache for debug purposes - if (base::GetProperty("debug.egl.blobcache.multifilemode", "") == "false") { - ALOGD("Forcing monolithic cache due to debug.egl.blobcache.multifilemode == \"false\""); + // Check the device config to decide whether multifile should be used + if (base::GetBoolProperty("ro.egl.blobcache.multifile", false)) { + mMultifileMode = true; + ALOGV("Using multifile EGL blobcache"); + } + + // Allow forcing the mode for debug purposes + std::string mode = base::GetProperty("debug.egl.blobcache.multifile", ""); + if (mode == "true") { + ALOGV("Forcing multifile cache due to debug.egl.blobcache.multifile == %s", mode.c_str()); + mMultifileMode = true; + } else if (mode == "false") { + ALOGV("Forcing monolithic cache due to debug.egl.blobcache.multifile == %s", mode.c_str()); mMultifileMode = false; } if (mMultifileMode) { - mCacheByteLimit = kMultifileCacheByteLimit; + mCacheByteLimit = static_cast( + base::GetUintProperty("ro.egl.blobcache.multifile_limit", + kMultifileCacheByteLimit)); + + // Check for a debug value + int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.multifile_limit", -1); + if (debugCacheSize >= 0) { + ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.multifile_limit", + mCacheByteLimit, debugCacheSize); + mCacheByteLimit = debugCacheSize; + } + + ALOGV("Using multifile EGL blobcache limit of %zu bytes", mCacheByteLimit); } mInitialized = true; @@ -133,10 +151,10 @@ void egl_cache_t::terminate() { mBlobCache->writeToFile(); } mBlobCache = nullptr; - if (mMultifileMode) { - checkMultifileCacheSize(mCacheByteLimit); + if (mMultifileBlobCache) { + mMultifileBlobCache->finish(); } - mMultifileMode = false; + mMultifileBlobCache = nullptr; mInitialized = false; } @@ -151,20 +169,8 @@ void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void* if (mInitialized) { if (mMultifileMode) { - setBlobMultifile(key, keySize, value, valueSize, mFilename); - - if (!mMultifileCleanupPending) { - mMultifileCleanupPending = true; - // Kick off a thread to cull cache files below limit - std::thread deferredMultifileCleanupThread([this]() { - sleep(deferredMultifileCleanupDelaySeconds); - std::lock_guard lock(mMutex); - // Check the size of cache and remove entries to stay under limit - checkMultifileCacheSize(mCacheByteLimit); - mMultifileCleanupPending = false; - }); - deferredMultifileCleanupThread.detach(); - } + MultifileBlobCache* mbc = getMultifileBlobCacheLocked(); + mbc->set(key, keySize, value, valueSize); } else { BlobCache* bc = getBlobCacheLocked(); bc->set(key, keySize, value, valueSize); @@ -172,7 +178,7 @@ void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void* if (!mSavePending) { mSavePending = true; std::thread deferredSaveThread([this]() { - sleep(deferredSaveDelay); + sleep(kDeferredMonolithicSaveDelay); std::lock_guard lock(mMutex); if (mInitialized && mBlobCache) { mBlobCache->writeToFile(); @@ -196,15 +202,21 @@ EGLsizeiANDROID egl_cache_t::getBlob(const void* key, EGLsizeiANDROID keySize, v if (mInitialized) { if (mMultifileMode) { - return getBlobMultifile(key, keySize, value, valueSize, mFilename); + MultifileBlobCache* mbc = getMultifileBlobCacheLocked(); + return mbc->get(key, keySize, value, valueSize); } else { BlobCache* bc = getBlobCacheLocked(); return bc->get(key, keySize, value, valueSize); } } + return 0; } +void egl_cache_t::setCacheMode(EGLCacheMode cacheMode) { + mMultifileMode = (cacheMode == EGLCacheMode::Multifile); +} + void egl_cache_t::setCacheFilename(const char* filename) { std::lock_guard lock(mMutex); mFilename = filename; @@ -216,7 +228,7 @@ void egl_cache_t::setCacheLimit(int64_t cacheByteLimit) { if (!mMultifileMode) { // If we're not in multifile mode, ensure the cache limit is only being lowered, // not increasing above the hard coded platform limit - if (cacheByteLimit > maxTotalSize) { + if (cacheByteLimit > kMaxMonolithicTotalSize) { return; } } @@ -226,8 +238,8 @@ void egl_cache_t::setCacheLimit(int64_t cacheByteLimit) { size_t egl_cache_t::getCacheSize() { std::lock_guard lock(mMutex); - if (mMultifileMode) { - return getMultifileCacheSize(); + if (mMultifileBlobCache) { + return mMultifileBlobCache->getTotalSize(); } if (mBlobCache) { return mBlobCache->getSize(); @@ -237,9 +249,18 @@ size_t egl_cache_t::getCacheSize() { BlobCache* egl_cache_t::getBlobCacheLocked() { if (mBlobCache == nullptr) { - mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, mCacheByteLimit, mFilename)); + mBlobCache.reset(new FileBlobCache(kMaxMonolithicKeySize, kMaxMonolithicValueSize, + mCacheByteLimit, mFilename)); } return mBlobCache.get(); } +MultifileBlobCache* egl_cache_t::getMultifileBlobCacheLocked() { + if (mMultifileBlobCache == nullptr) { + mMultifileBlobCache.reset( + new MultifileBlobCache(mCacheByteLimit, kMultifileHotCacheLimit, mFilename)); + } + return mMultifileBlobCache.get(); +} + }; // namespace android diff --git a/opengl/libs/EGL/egl_cache.h b/opengl/libs/EGL/egl_cache.h index 2dcd803324..1399368dd8 100644 --- a/opengl/libs/EGL/egl_cache.h +++ b/opengl/libs/EGL/egl_cache.h @@ -25,6 +25,7 @@ #include #include "FileBlobCache.h" +#include "MultifileBlobCache.h" namespace android { @@ -32,6 +33,11 @@ class egl_display_t; class EGLAPI egl_cache_t { public: + enum class EGLCacheMode { + Monolithic, + Multifile, + }; + // get returns a pointer to the singleton egl_cache_t object. This // singleton object will never be destroyed. static egl_cache_t* get(); @@ -64,6 +70,9 @@ public: // cache contents from one program invocation to another. void setCacheFilename(const char* filename); + // Allow setting monolithic or multifile modes + void setCacheMode(EGLCacheMode cacheMode); + // Allow the fixed cache limit to be overridden void setCacheLimit(int64_t cacheByteLimit); @@ -85,6 +94,9 @@ private: // possible. BlobCache* getBlobCacheLocked(); + // Get or create the multifile blobcache + MultifileBlobCache* getMultifileBlobCacheLocked(); + // mInitialized indicates whether the egl_cache_t is in the initialized // state. It is initialized to false at construction time, and gets set to // true when initialize is called. It is set back to false when terminate @@ -98,6 +110,9 @@ private: // first time it's needed. std::unique_ptr mBlobCache; + // The multifile version of blobcache allowing larger contents to be stored + std::unique_ptr mMultifileBlobCache; + // mFilename is the name of the file for storing cache contents in between // program invocations. It is initialized to an empty string at // construction time, and can be set with the setCacheFilename method. An @@ -123,11 +138,7 @@ private: bool mMultifileMode; // Cache limit - int64_t mCacheByteLimit; - - // Whether we've kicked off a side thread that will check the multifile - // cache size and remove entries if needed. - bool mMultifileCleanupPending; + size_t mCacheByteLimit; }; }; // namespace android diff --git a/opengl/libs/EGL/egl_cache_multifile.cpp b/opengl/libs/EGL/egl_cache_multifile.cpp deleted file mode 100644 index 48e557f190..0000000000 --- a/opengl/libs/EGL/egl_cache_multifile.cpp +++ /dev/null @@ -1,343 +0,0 @@ -/* - ** Copyright 2022, The Android Open Source Project - ** - ** Licensed under the Apache License, Version 2.0 (the "License"); - ** you may not use this file except in compliance with the License. - ** You may obtain a copy of the License at - ** - ** http://www.apache.org/licenses/LICENSE-2.0 - ** - ** Unless required by applicable law or agreed to in writing, software - ** distributed under the License is distributed on an "AS IS" BASIS, - ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ** See the License for the specific language governing permissions and - ** limitations under the License. - */ - -// #define LOG_NDEBUG 0 - -#include "egl_cache_multifile.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -static std::string multifileDirName = ""; - -using namespace std::literals; - -namespace { - -// Create a directory for tracking multiple files -void setupMultifile(const std::string& baseDir) { - // If we've already set up the multifile dir in this base directory, we're done - if (!multifileDirName.empty() && multifileDirName.find(baseDir) != std::string::npos) { - return; - } - - // Otherwise, create it - multifileDirName = baseDir + ".multifile"; - if (mkdir(multifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) { - ALOGW("Unable to create directory (%s), errno (%i)", multifileDirName.c_str(), errno); - } -} - -// Create a filename that is based on the hash of the key -std::string getCacheEntryFilename(const void* key, EGLsizeiANDROID keySize, - const std::string& baseDir) { - // Hash the key into a string - std::stringstream keyName; - keyName << android::JenkinsHashMixBytes(0, static_cast(key), keySize); - - // Build a filename using dir and hash - return baseDir + "/" + keyName.str(); -} - -// Determine file age based on stat modification time -// Newer files have a higher age (time since epoch) -time_t getFileAge(const std::string& filePath) { - struct stat st; - if (stat(filePath.c_str(), &st) == 0) { - ALOGD("getFileAge returning %" PRId64 " for file age", static_cast(st.st_mtime)); - return st.st_mtime; - } else { - ALOGW("Failed to stat %s", filePath.c_str()); - return 0; - } -} - -size_t getFileSize(const std::string& filePath) { - struct stat st; - if (stat(filePath.c_str(), &st) != 0) { - ALOGE("Unable to stat %s", filePath.c_str()); - return 0; - } - return st.st_size; -} - -// Walk through directory entries and track age and size -// Then iterate through the entries, oldest first, and remove them until under the limit. -// This will need to be updated if we move to a multilevel cache dir. -bool applyLRU(size_t cacheLimit) { - // Build a multimap of files indexed by age. - // They will be automatically sorted smallest (oldest) to largest (newest) - std::multimap agesToFiles; - - // Map files to sizes - std::unordered_map filesToSizes; - - size_t totalCacheSize = 0; - - DIR* dir; - struct dirent* entry; - if ((dir = opendir(multifileDirName.c_str())) != nullptr) { - while ((entry = readdir(dir)) != nullptr) { - if (entry->d_name == "."s || entry->d_name == ".."s) { - continue; - } - - // Look up each file age - std::string fullPath = multifileDirName + "/" + entry->d_name; - time_t fileAge = getFileAge(fullPath); - - // Track the files, sorted by age - agesToFiles.insert(std::make_pair(fileAge, fullPath)); - - // Also track the size so we know how much room we have freed - size_t fileSize = getFileSize(fullPath); - filesToSizes[fullPath] = fileSize; - totalCacheSize += fileSize; - } - closedir(dir); - } else { - ALOGE("Unable to open filename: %s", multifileDirName.c_str()); - return false; - } - - if (totalCacheSize <= cacheLimit) { - // If LRU was called on a sufficiently small cache, no need to remove anything - return true; - } - - // Walk through the map of files until we're under the cache size - for (const auto& cacheEntryIter : agesToFiles) { - time_t entryAge = cacheEntryIter.first; - const std::string entryPath = cacheEntryIter.second; - - ALOGD("Removing %s with age %ld", entryPath.c_str(), entryAge); - if (std::remove(entryPath.c_str()) != 0) { - ALOGE("Error removing %s: %s", entryPath.c_str(), std::strerror(errno)); - return false; - } - - totalCacheSize -= filesToSizes[entryPath]; - if (totalCacheSize <= cacheLimit) { - // Success - ALOGV("Reduced cache to %zu", totalCacheSize); - return true; - } else { - ALOGD("Cache size is still too large (%zu), removing more files", totalCacheSize); - } - } - - // Should never reach this return - return false; -} - -} // namespace - -namespace android { - -void setBlobMultifile(const void* key, EGLsizeiANDROID keySize, const void* value, - EGLsizeiANDROID valueSize, const std::string& baseDir) { - if (baseDir.empty()) { - return; - } - - setupMultifile(baseDir); - std::string filename = getCacheEntryFilename(key, keySize, multifileDirName); - - ALOGD("Attempting to open filename for set: %s", filename.c_str()); - std::ofstream outfile(filename, std::ofstream::binary); - if (outfile.fail()) { - ALOGW("Unable to open filename: %s", filename.c_str()); - return; - } - - // First write the key - outfile.write(static_cast(key), keySize); - if (outfile.bad()) { - ALOGW("Unable to write key to filename: %s", filename.c_str()); - outfile.close(); - return; - } - ALOGD("Wrote %i bytes to out file for key", static_cast(outfile.tellp())); - - // Then write the value - outfile.write(static_cast(value), valueSize); - if (outfile.bad()) { - ALOGW("Unable to write value to filename: %s", filename.c_str()); - outfile.close(); - return; - } - ALOGD("Wrote %i bytes to out file for full entry", static_cast(outfile.tellp())); - - outfile.close(); -} - -EGLsizeiANDROID getBlobMultifile(const void* key, EGLsizeiANDROID keySize, void* value, - EGLsizeiANDROID valueSize, const std::string& baseDir) { - if (baseDir.empty()) { - return 0; - } - - setupMultifile(baseDir); - std::string filename = getCacheEntryFilename(key, keySize, multifileDirName); - - // Open the hashed filename path - ALOGD("Attempting to open filename for get: %s", filename.c_str()); - int fd = open(filename.c_str(), O_RDONLY); - - // File doesn't exist, this is a MISS, return zero bytes read - if (fd == -1) { - ALOGD("Cache MISS - failed to open filename: %s, error: %s", filename.c_str(), - std::strerror(errno)); - return 0; - } - - ALOGD("Cache HIT - opened filename: %s", filename.c_str()); - - // Get the size of the file - size_t entrySize = getFileSize(filename); - if (keySize > entrySize) { - ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified " - "file", - keySize, entrySize); - close(fd); - return 0; - } - - // Memory map the file - uint8_t* cacheEntry = - reinterpret_cast(mmap(nullptr, entrySize, PROT_READ, MAP_PRIVATE, fd, 0)); - if (cacheEntry == MAP_FAILED) { - ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); - close(fd); - return 0; - } - - // Compare the incoming key with our stored version (the beginning of the entry) - int compare = memcmp(cacheEntry, key, keySize); - if (compare != 0) { - ALOGW("Cached key and new key do not match! This is a hash collision or modified file"); - munmap(cacheEntry, entrySize); - close(fd); - return 0; - } - - // Keys matched, so remaining cache is value size - size_t cachedValueSize = entrySize - keySize; - - // Return actual value size if valueSize is not large enough - if (cachedValueSize > valueSize) { - ALOGD("Skipping file read, not enough room provided (valueSize): %lu, " - "returning required space as %zu", - valueSize, cachedValueSize); - munmap(cacheEntry, entrySize); - close(fd); - return cachedValueSize; - } - - // Remaining entry following the key is the value - uint8_t* cachedValue = cacheEntry + keySize; - memcpy(value, cachedValue, cachedValueSize); - munmap(cacheEntry, entrySize); - close(fd); - - ALOGD("Read %zu bytes from %s", cachedValueSize, filename.c_str()); - return cachedValueSize; -} - -// Walk through the files in our flat directory, checking the size of each one. -// Return the total size of normal files in the directory. -// This will need to be updated if we move to a multilevel cache dir. -size_t getMultifileCacheSize() { - if (multifileDirName.empty()) { - return 0; - } - - DIR* dir; - struct dirent* entry; - size_t size = 0; - - ALOGD("Using %s as the multifile cache dir ", multifileDirName.c_str()); - - if ((dir = opendir(multifileDirName.c_str())) != nullptr) { - while ((entry = readdir(dir)) != nullptr) { - if (entry->d_name == "."s || entry->d_name == ".."s) { - continue; - } - - // Add up the size of all files in the dir - std::string fullPath = multifileDirName + "/" + entry->d_name; - size += getFileSize(fullPath); - } - closedir(dir); - } else { - ALOGW("Unable to open filename: %s", multifileDirName.c_str()); - return 0; - } - - return size; -} - -// When removing files, what fraction of the overall limit should be reached when removing files -// A divisor of two will decrease the cache to 50%, four to 25% and so on -constexpr uint32_t kCacheLimitDivisor = 2; - -// Calculate the cache size and remove old entries until under the limit -void checkMultifileCacheSize(size_t cacheByteLimit) { - // Start with the value provided by egl_cache - size_t limit = cacheByteLimit; - - // Check for a debug value - int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.bytelimit", -1); - if (debugCacheSize >= 0) { - ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.bytelimit", limit, - debugCacheSize); - limit = debugCacheSize; - } - - // Tally up the initial amount of cache in use - size_t size = getMultifileCacheSize(); - ALOGD("Multifile cache dir size: %zu", size); - - // If size is larger than the threshold, remove files using LRU - if (size > limit) { - ALOGV("Multifile cache size is larger than %zu, removing old entries", cacheByteLimit); - if (!applyLRU(limit / kCacheLimitDivisor)) { - ALOGE("Error when clearing multifile shader cache"); - return; - } - } - ALOGD("Multifile cache size after reduction: %zu", getMultifileCacheSize()); -} - -}; // namespace android \ No newline at end of file diff --git a/opengl/libs/EGL/egl_cache_multifile.h b/opengl/libs/EGL/egl_cache_multifile.h deleted file mode 100644 index ee5fe8108d..0000000000 --- a/opengl/libs/EGL/egl_cache_multifile.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - ** Copyright 2022, The Android Open Source Project - ** - ** Licensed under the Apache License, Version 2.0 (the "License"); - ** you may not use this file except in compliance with the License. - ** You may obtain a copy of the License at - ** - ** http://www.apache.org/licenses/LICENSE-2.0 - ** - ** Unless required by applicable law or agreed to in writing, software - ** distributed under the License is distributed on an "AS IS" BASIS, - ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ** See the License for the specific language governing permissions and - ** limitations under the License. - */ - -#ifndef ANDROID_EGL_CACHE_MULTIFILE_H -#define ANDROID_EGL_CACHE_MULTIFILE_H - -#include -#include - -#include - -namespace android { - -void setBlobMultifile(const void* key, EGLsizeiANDROID keySize, const void* value, - EGLsizeiANDROID valueSize, const std::string& baseDir); -EGLsizeiANDROID getBlobMultifile(const void* key, EGLsizeiANDROID keySize, void* value, - EGLsizeiANDROID valueSize, const std::string& baseDir); -size_t getMultifileCacheSize(); -void checkMultifileCacheSize(size_t cacheByteLimit); - -}; // namespace android - -#endif // ANDROID_EGL_CACHE_MULTIFILE_H diff --git a/opengl/tests/EGLTest/egl_cache_test.cpp b/opengl/tests/EGLTest/egl_cache_test.cpp index 265bec492e..2b3e3a46af 100644 --- a/opengl/tests/EGLTest/egl_cache_test.cpp +++ b/opengl/tests/EGLTest/egl_cache_test.cpp @@ -24,7 +24,7 @@ #include #include "egl_cache.h" -#include "egl_cache_multifile.h" +#include "MultifileBlobCache.h" #include "egl_display.h" #include @@ -33,12 +33,16 @@ using namespace std::literals; namespace android { -class EGLCacheTest : public ::testing::Test { +class EGLCacheTest : public ::testing::TestWithParam { protected: virtual void SetUp() { - mCache = egl_cache_t::get(); + // Terminate to clean up any previous cache in this process + mCache->terminate(); + mTempFile.reset(new TemporaryFile()); mCache->setCacheFilename(&mTempFile->path[0]); + mCache->setCacheLimit(1024); + mCache->setCacheMode(mCacheMode); } virtual void TearDown() { @@ -49,11 +53,12 @@ protected: std::string getCachefileName(); - egl_cache_t* mCache; + egl_cache_t* mCache = egl_cache_t::get(); std::unique_ptr mTempFile; + egl_cache_t::EGLCacheMode mCacheMode = GetParam(); }; -TEST_F(EGLCacheTest, UninitializedCacheAlwaysMisses) { +TEST_P(EGLCacheTest, UninitializedCacheAlwaysMisses) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->setBlob("abcd", 4, "efgh", 4); ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4)); @@ -63,7 +68,7 @@ TEST_F(EGLCacheTest, UninitializedCacheAlwaysMisses) { ASSERT_EQ(0xee, buf[3]); } -TEST_F(EGLCacheTest, InitializedCacheAlwaysHits) { +TEST_P(EGLCacheTest, InitializedCacheAlwaysHits) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -74,7 +79,7 @@ TEST_F(EGLCacheTest, InitializedCacheAlwaysHits) { ASSERT_EQ('h', buf[3]); } -TEST_F(EGLCacheTest, TerminatedCacheAlwaysMisses) { +TEST_P(EGLCacheTest, TerminatedCacheAlwaysMisses) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -86,7 +91,7 @@ TEST_F(EGLCacheTest, TerminatedCacheAlwaysMisses) { ASSERT_EQ(0xee, buf[3]); } -TEST_F(EGLCacheTest, ReinitializedCacheContainsValues) { +TEST_P(EGLCacheTest, ReinitializedCacheContainsValues) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -101,12 +106,12 @@ TEST_F(EGLCacheTest, ReinitializedCacheContainsValues) { std::string EGLCacheTest::getCachefileName() { // Return the monolithic filename unless we find the multifile dir - std::string cachefileName = &mTempFile->path[0]; - std::string multifileDirName = cachefileName + ".multifile"; + std::string cachePath = &mTempFile->path[0]; + std::string multifileDirName = cachePath + ".multifile"; + std::string cachefileName = ""; struct stat info; if (stat(multifileDirName.c_str(), &info) == 0) { - // Ensure we only have one file to manage int realFileCount = 0; @@ -121,6 +126,9 @@ std::string EGLCacheTest::getCachefileName() { cachefileName = multifileDirName + "/" + entry->d_name; realFileCount++; } + } else { + printf("Unable to open %s, error: %s\n", + multifileDirName.c_str(), std::strerror(errno)); } if (realFileCount != 1) { @@ -128,14 +136,19 @@ std::string EGLCacheTest::getCachefileName() { // violates test assumptions cachefileName = ""; } + } else { + printf("Unable to stat %s, error: %s\n", + multifileDirName.c_str(), std::strerror(errno)); } return cachefileName; } -TEST_F(EGLCacheTest, ModifiedCacheMisses) { - // Turn this back on if multifile becomes the default - GTEST_SKIP() << "Skipping test designed for multifile, see b/263574392 and b/246966894"; +TEST_P(EGLCacheTest, ModifiedCacheMisses) { + // Skip if not in multifile mode + if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) { + GTEST_SKIP() << "Skipping test designed for multifile"; + } uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); @@ -147,13 +160,13 @@ TEST_F(EGLCacheTest, ModifiedCacheMisses) { ASSERT_EQ('g', buf[2]); ASSERT_EQ('h', buf[3]); + // Ensure the cache file is written to disk + mCache->terminate(); + // Depending on the cache mode, the file will be in different locations std::string cachefileName = getCachefileName(); ASSERT_TRUE(cachefileName.length() > 0); - // Ensure the cache file is written to disk - mCache->terminate(); - // Stomp on the beginning of the cache file, breaking the key match const long stomp = 0xbadf00d; FILE *file = fopen(cachefileName.c_str(), "w"); @@ -164,14 +177,15 @@ TEST_F(EGLCacheTest, ModifiedCacheMisses) { // Ensure no cache hit mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); uint8_t buf2[4] = { 0xee, 0xee, 0xee, 0xee }; - ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf2, 4)); + // getBlob may return junk for required size, but should not return a cache hit + mCache->getBlob("abcd", 4, buf2, 4); ASSERT_EQ(0xee, buf2[0]); ASSERT_EQ(0xee, buf2[1]); ASSERT_EQ(0xee, buf2[2]); ASSERT_EQ(0xee, buf2[3]); } -TEST_F(EGLCacheTest, TerminatedCacheBelowCacheLimit) { +TEST_P(EGLCacheTest, TerminatedCacheBelowCacheLimit) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); @@ -204,4 +218,8 @@ TEST_F(EGLCacheTest, TerminatedCacheBelowCacheLimit) { ASSERT_LE(mCache->getCacheSize(), 4); } +INSTANTIATE_TEST_CASE_P(MonolithicCacheTests, + EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Monolithic)); +INSTANTIATE_TEST_CASE_P(MultifileCacheTests, + EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Multifile)); } -- GitLab From f54181b23d93de894becc43e9e50d954be8c0241 Mon Sep 17 00:00:00 2001 From: Pascal Muetschard Date: Tue, 13 Dec 2022 14:53:25 +0100 Subject: [PATCH 0831/1310] Add a flag to callbacks whether to include jank data. For transaction on-complete callbacks, SurfaceFlinger will now only send the JankData if the flag is set. If the flag is not set on all callbacks on a surface in a transaction, any pending jank data for that surface is now dropped. The client ensures that the flag is set on any callback on surfaces for which a jank listener is registered. Thus, the data now dropped by SurfaceFlinger would have been dropped on the client side anyways. Bug: 235178314 Bug: 221393601 Test: atest SurfaceFlinger_test Change-Id: I176d6962f09b587b39c5d106c48949571151f3bd --- libs/gui/ITransactionCompletedListener.cpp | 20 ++++++++- libs/gui/SurfaceComposerClient.cpp | 5 +++ .../gui/ITransactionCompletedListener.h | 9 +++- services/surfaceflinger/Layer.cpp | 45 ++++++++++++++----- services/surfaceflinger/Layer.h | 5 +++ 5 files changed, 70 insertions(+), 14 deletions(-) diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp index 985c54922d..ffe79a3a03 100644 --- a/libs/gui/ITransactionCompletedListener.cpp +++ b/libs/gui/ITransactionCompletedListener.cpp @@ -39,6 +39,12 @@ enum class Tag : uint32_t { } // Anonymous namespace +namespace { // Anonymous + +constexpr int32_t kSerializedCallbackTypeOnCompelteWithJankData = 2; + +} // Anonymous namespace + status_t FrameEventHistoryStats::writeToParcel(Parcel* output) const { status_t err = output->writeUint64(frameNumber); if (err != NO_ERROR) return err; @@ -349,7 +355,11 @@ ListenerCallbacks ListenerCallbacks::filter(CallbackId::Type type) const { status_t CallbackId::writeToParcel(Parcel* output) const { SAFE_PARCEL(output->writeInt64, id); - SAFE_PARCEL(output->writeInt32, static_cast(type)); + if (type == Type::ON_COMPLETE && includeJankData) { + SAFE_PARCEL(output->writeInt32, kSerializedCallbackTypeOnCompelteWithJankData); + } else { + SAFE_PARCEL(output->writeInt32, static_cast(type)); + } return NO_ERROR; } @@ -357,7 +367,13 @@ status_t CallbackId::readFromParcel(const Parcel* input) { SAFE_PARCEL(input->readInt64, &id); int32_t typeAsInt; SAFE_PARCEL(input->readInt32, &typeAsInt); - type = static_cast(typeAsInt); + if (typeAsInt == kSerializedCallbackTypeOnCompelteWithJankData) { + type = Type::ON_COMPLETE; + includeJankData = true; + } else { + type = static_cast(typeAsInt); + includeJankData = false; + } return NO_ERROR; } diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 7fc67886c0..2a6636ea3c 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -257,6 +257,11 @@ CallbackId TransactionCompletedListener::addCallbackFunction( for (const auto& surfaceControl : surfaceControls) { callbackSurfaceControls[surfaceControl->getHandle()] = surfaceControl; + + if (callbackType == CallbackId::Type::ON_COMPLETE && + mJankListeners.count(surfaceControl->getLayerId()) != 0) { + callbackId.includeJankData = true; + } } return callbackId; diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h index d593f56c3a..39bcb4a56c 100644 --- a/libs/gui/include/gui/ITransactionCompletedListener.h +++ b/libs/gui/include/gui/ITransactionCompletedListener.h @@ -40,10 +40,15 @@ class ListenerCallbacks; class CallbackId : public Parcelable { public: int64_t id; - enum class Type : int32_t { ON_COMPLETE, ON_COMMIT } type; + enum class Type : int32_t { + ON_COMPLETE = 0, + ON_COMMIT = 1, + /*reserved for serialization = 2*/ + } type; + bool includeJankData; // Only respected for ON_COMPLETE callbacks. CallbackId() {} - CallbackId(int64_t id, Type type) : id(id), type(type) {} + CallbackId(int64_t id, Type type) : id(id), type(type), includeJankData(false) {} status_t writeToParcel(Parcel* output) const override; status_t readFromParcel(const Parcel* input) override; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 31ee91ea02..98240d0f67 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -734,6 +734,40 @@ bool Layer::isSecure() const { return (p != nullptr) ? p->isSecure() : false; } +void Layer::transferAvailableJankData(const std::deque>& handles, + std::vector& jankData) { + if (mPendingJankClassifications.empty() || + !mPendingJankClassifications.front()->getJankType()) { + return; + } + + bool includeJankData = false; + for (const auto& handle : handles) { + for (const auto& cb : handle->callbackIds) { + if (cb.includeJankData) { + includeJankData = true; + break; + } + } + + if (includeJankData) { + jankData.reserve(mPendingJankClassifications.size()); + break; + } + } + + while (!mPendingJankClassifications.empty() && + mPendingJankClassifications.front()->getJankType()) { + if (includeJankData) { + std::shared_ptr surfaceFrame = + mPendingJankClassifications.front(); + jankData.emplace_back( + JankData(surfaceFrame->getToken(), surfaceFrame->getJankType().value())); + } + mPendingJankClassifications.pop_front(); + } +} + // ---------------------------------------------------------------------------- // transaction // ---------------------------------------------------------------------------- @@ -2798,16 +2832,7 @@ void Layer::releasePendingBuffer(nsecs_t dequeueReadyTime) { } std::vector jankData; - jankData.reserve(mPendingJankClassifications.size()); - while (!mPendingJankClassifications.empty() && - mPendingJankClassifications.front()->getJankType()) { - std::shared_ptr surfaceFrame = - mPendingJankClassifications.front(); - mPendingJankClassifications.pop_front(); - jankData.emplace_back( - JankData(surfaceFrame->getToken(), surfaceFrame->getJankType().value())); - } - + transferAvailableJankData(mDrawingState.callbackHandles, jankData); mFlinger->getTransactionCallbackInvoker().addCallbackHandles(mDrawingState.callbackHandles, jankData); mDrawingState.callbackHandles = {}; diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 3d4f03f6ee..07c01d8e17 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -1064,6 +1064,11 @@ private: void updateChildrenSnapshots(bool updateGeometry); + // Fills the provided vector with the currently available JankData and removes the processed + // JankData from the pending list. + void transferAvailableJankData(const std::deque>& handles, + std::vector& jankData); + // Cached properties computed from drawing state // Effective transform taking into account parent transforms and any parent scaling, which is // a transform from the current layer coordinate space to display(screen) coordinate space. -- GitLab From 81cef293e8d1200655796177125931474ff0d125 Mon Sep 17 00:00:00 2001 From: Pascal Muetschard Date: Mon, 6 Feb 2023 12:18:52 +0100 Subject: [PATCH 0832/1310] Send available JankData in all transaction completion callbacks. This, effectively, adds JankData to the callbacks also for layers that will not present in the current transaction. Bug: 235178314 Bug: 221393601 Test: atest SurfaceFlinger_test Change-Id: Ia6afaa31ad98a71cb6d6dc146f1094b857ab92b2 --- services/surfaceflinger/Layer.cpp | 13 +++++++++++-- services/surfaceflinger/SurfaceFlinger.cpp | 12 ++++++++---- .../surfaceflinger/TransactionCallbackInvoker.cpp | 5 ----- .../surfaceflinger/TransactionCallbackInvoker.h | 3 --- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 98240d0f67..66c2fb658f 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -3131,6 +3131,7 @@ bool Layer::setTransactionCompletedListeners(const std::vector> remainingHandles; for (const auto& handle : handles) { // If this transaction set a buffer on this layer, release its previous buffer handle->releasePreviousBuffer = mReleasePreviousBuffer; @@ -3145,11 +3146,19 @@ bool Layer::setTransactionCompletedListeners(const std::vectorgetTransactionCallbackInvoker().registerUnpresentedCallbackHandle(handle); + // Queue this handle to be notified below. + remainingHandles.push_back(handle); } } + if (!remainingHandles.empty()) { + // Notify the transaction completed threads these handles are done. These are only the + // handles that were not added to the mDrawingState, which will be notified later. + std::vector jankData; + transferAvailableJankData(remainingHandles, jankData); + mFlinger->getTransactionCallbackInvoker().addCallbackHandles(remainingHandles, jankData); + } + mReleasePreviousBuffer = false; mCallbackHandleAcquireTimeOrFence = -1; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index f23278042e..68ab776400 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4552,8 +4552,10 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime } if (layer == nullptr) { for (auto& [listener, callbackIds] : s.listeners) { - mTransactionCallbackInvoker.registerUnpresentedCallbackHandle( - sp::make(listener, callbackIds, s.surface)); + mTransactionCallbackInvoker.addCallbackHandle(sp::make(listener, + callbackIds, + s.surface), + std::vector()); } return 0; } @@ -4891,8 +4893,10 @@ uint32_t SurfaceFlinger::updateLayerCallbacksAndStats(const FrameTimelineInfo& f } if (layer == nullptr) { for (auto& [listener, callbackIds] : s.listeners) { - mTransactionCallbackInvoker.registerUnpresentedCallbackHandle( - sp::make(listener, callbackIds, s.surface)); + mTransactionCallbackInvoker.addCallbackHandle(sp::make(listener, + callbackIds, + s.surface), + std::vector()); } return 0; } diff --git a/services/surfaceflinger/TransactionCallbackInvoker.cpp b/services/surfaceflinger/TransactionCallbackInvoker.cpp index 3da98d4247..3587a726cd 100644 --- a/services/surfaceflinger/TransactionCallbackInvoker.cpp +++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp @@ -92,11 +92,6 @@ status_t TransactionCallbackInvoker::addCallbackHandles( return NO_ERROR; } -status_t TransactionCallbackInvoker::registerUnpresentedCallbackHandle( - const sp& handle) { - return addCallbackHandle(handle, std::vector()); -} - status_t TransactionCallbackInvoker::findOrCreateTransactionStats( const sp& listener, const std::vector& callbackIds, TransactionStats** outTransactionStats) { diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h index 61ff9bce98..3074795f62 100644 --- a/services/surfaceflinger/TransactionCallbackInvoker.h +++ b/services/surfaceflinger/TransactionCallbackInvoker.h @@ -66,9 +66,6 @@ public: status_t addOnCommitCallbackHandles(const std::deque>& handles, std::deque>& outRemainingHandles); - // Adds the Transaction CallbackHandle from a layer that does not need to be relatched and - // presented this frame. - status_t registerUnpresentedCallbackHandle(const sp& handle); void addEmptyTransaction(const ListenerCallbacks& listenerCallbacks); void addPresentFence(sp); -- GitLab From 29247e59380c9d0fb8d47c65a91778b2da5ef8d6 Mon Sep 17 00:00:00 2001 From: Pascal Muetschard Date: Tue, 8 Nov 2022 11:33:10 +0100 Subject: [PATCH 0833/1310] Ensure a callback is registered for all surfaces for jank data. If there is a jank listener registered for a surface, the client will now ensure that a completion callback is added to the transaction for that surface with the jank data flag set. Bug: 235178314 Bug: 221393601 Test: atest SurfaceFlinger_test Change-Id: I77504c05d396fb9d9d91514d320ecd4e3dc7c4e2 --- libs/gui/SurfaceComposerClient.cpp | 33 +++++++++++++++----- libs/gui/include/gui/SurfaceComposerClient.h | 10 ++++-- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 2a6636ea3c..a34593825a 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -82,6 +82,8 @@ std::atomic idCounter = 0; int64_t generateId() { return (((int64_t)getpid()) << 32) | ++idCounter; } + +void emptyCallback(nsecs_t, const sp&, const std::vector&) {} } // namespace ComposerService::ComposerService() @@ -249,6 +251,14 @@ CallbackId TransactionCompletedListener::addCallbackFunction( surfaceControls, CallbackId::Type callbackType) { std::lock_guard lock(mMutex); + return addCallbackFunctionLocked(callbackFunction, surfaceControls, callbackType); +} + +CallbackId TransactionCompletedListener::addCallbackFunctionLocked( + const TransactionCompletedCallback& callbackFunction, + const std::unordered_set, SurfaceComposerClient::SCHash>& + surfaceControls, + CallbackId::Type callbackType) { startListeningLocked(); CallbackId callbackId(getNextIdLocked(), callbackType); @@ -310,15 +320,26 @@ void TransactionCompletedListener::removeSurfaceStatsListener(void* context, voi } void TransactionCompletedListener::addSurfaceControlToCallbacks( - const sp& surfaceControl, - const std::unordered_set& callbackIds) { + SurfaceComposerClient::CallbackInfo& callbackInfo, + const sp& surfaceControl) { std::lock_guard lock(mMutex); - for (auto callbackId : callbackIds) { + bool includingJankData = false; + for (auto callbackId : callbackInfo.callbackIds) { mCallbacks[callbackId].surfaceControls.emplace(std::piecewise_construct, std::forward_as_tuple( surfaceControl->getHandle()), std::forward_as_tuple(surfaceControl)); + includingJankData = includingJankData || callbackId.includeJankData; + } + + // If no registered callback is requesting jank data, but there is a jank listener registered + // on the new surface control, add a synthetic callback that requests the jank data. + if (!includingJankData && mJankListeners.count(surfaceControl->getLayerId()) != 0) { + CallbackId callbackId = + addCallbackFunctionLocked(&emptyCallback, callbackInfo.surfaceControls, + CallbackId::Type::ON_COMPLETE); + callbackInfo.callbackIds.emplace(callbackId); } } @@ -935,8 +956,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::merge(Tr // register all surface controls for all callbackIds for this listener that is merging for (const auto& surfaceControl : currentProcessCallbackInfo.surfaceControls) { TransactionCompletedListener::getInstance() - ->addSurfaceControlToCallbacks(surfaceControl, - currentProcessCallbackInfo.callbackIds); + ->addSurfaceControlToCallbacks(currentProcessCallbackInfo, surfaceControl); } } @@ -1272,8 +1292,7 @@ void SurfaceComposerClient::Transaction::registerSurfaceControlForCallback( auto& callbackInfo = mListenerCallbacks[TransactionCompletedListener::getIInstance()]; callbackInfo.surfaceControls.insert(sc); - TransactionCompletedListener::getInstance() - ->addSurfaceControlToCallbacks(sc, callbackInfo.callbackIds); + TransactionCompletedListener::getInstance()->addSurfaceControlToCallbacks(callbackInfo, sc); } SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setPosition( diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 49196d6f71..809ea5a30f 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -878,10 +878,14 @@ public: const std::unordered_set, SurfaceComposerClient::SCHash>& surfaceControls, CallbackId::Type callbackType); + CallbackId addCallbackFunctionLocked( + const TransactionCompletedCallback& callbackFunction, + const std::unordered_set, SurfaceComposerClient::SCHash>& + surfaceControls, + CallbackId::Type callbackType) REQUIRES(mMutex); - void addSurfaceControlToCallbacks( - const sp& surfaceControl, - const std::unordered_set& callbackIds); + void addSurfaceControlToCallbacks(SurfaceComposerClient::CallbackInfo& callbackInfo, + const sp& surfaceControl); void addQueueStallListener(std::function stallListener, void* id); void removeQueueStallListener(void *id); -- GitLab From 5bf25d98693abcdb247e47f950fe661459b348ad Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 8 Feb 2023 15:43:38 -0800 Subject: [PATCH 0834/1310] Add POLICY_FLAG_PASS_TO_USER to complete the gesture Before an input event is processed by the dispatcher, the policy has the chance to intercept it. The policy sets POLICY_FLAG_PASS_TO_USER if it wants the event to be processed by the dispatcher, and does not set this flag if it wants the event to get dropped. If the device becomes non-interactive, the policy would stop sending this flag. This could happen in the middle of the gesture. To account for this, check whether there's an active stream inside the dispatcher, and finish the gesture if so. This allows the policy implementation to remain simple. If we wanted the policy to be consistent (either the whole gesture is sent to the app, or the whole gesture isn't), it would complicate the policy implementation and require it to keep state. Dispatcher already keeps the state, so we fix this in the dispatcher. Bug: 267614813 Test: atest inputflinger_tests:InputDispatcherTest#TwoPointerCancel Change-Id: I8d162c658f4f53a76d32d5e5a1de8ac3722e277d --- .../dispatcher/InputDispatcher.cpp | 11 + .../tests/InputDispatcher_test.cpp | 193 ++++++++++++++++++ 2 files changed, 204 insertions(+) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 079b80dad6..d14a781473 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -4185,6 +4185,17 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) { bool needWake = false; { // acquire lock mLock.lock(); + if (!(policyFlags & POLICY_FLAG_PASS_TO_USER)) { + // Set the flag anyway if we already have an ongoing gesture. That would allow us to + // complete the processing of the current stroke. + const auto touchStateIt = mTouchStatesByDisplay.find(args->displayId); + if (touchStateIt != mTouchStatesByDisplay.end()) { + const TouchState& touchState = touchStateIt->second; + if (touchState.deviceId == args->deviceId && touchState.isDown()) { + policyFlags |= POLICY_FLAG_PASS_TO_USER; + } + } + } if (shouldSendMotionToInputFilterLocked(args)) { ui::Transform displayTransform; diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index b1b6e058e4..f8d425639b 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -1567,6 +1567,113 @@ private: std::vector mPointers; }; +class MotionArgsBuilder { +public: + MotionArgsBuilder(int32_t action, int32_t source) { + mAction = action; + mSource = source; + mEventTime = systemTime(SYSTEM_TIME_MONOTONIC); + mDownTime = mEventTime; + } + + MotionArgsBuilder& deviceId(int32_t deviceId) { + mDeviceId = deviceId; + return *this; + } + + MotionArgsBuilder& downTime(nsecs_t downTime) { + mDownTime = downTime; + return *this; + } + + MotionArgsBuilder& eventTime(nsecs_t eventTime) { + mEventTime = eventTime; + return *this; + } + + MotionArgsBuilder& displayId(int32_t displayId) { + mDisplayId = displayId; + return *this; + } + + MotionArgsBuilder& policyFlags(int32_t policyFlags) { + mPolicyFlags = policyFlags; + return *this; + } + + MotionArgsBuilder& actionButton(int32_t actionButton) { + mActionButton = actionButton; + return *this; + } + + MotionArgsBuilder& buttonState(int32_t buttonState) { + mButtonState = buttonState; + return *this; + } + + MotionArgsBuilder& rawXCursorPosition(float rawXCursorPosition) { + mRawXCursorPosition = rawXCursorPosition; + return *this; + } + + MotionArgsBuilder& rawYCursorPosition(float rawYCursorPosition) { + mRawYCursorPosition = rawYCursorPosition; + return *this; + } + + MotionArgsBuilder& pointer(PointerBuilder pointer) { + mPointers.push_back(pointer); + return *this; + } + + MotionArgsBuilder& addFlag(uint32_t flags) { + mFlags |= flags; + return *this; + } + + NotifyMotionArgs build() { + std::vector pointerProperties; + std::vector pointerCoords; + for (const PointerBuilder& pointer : mPointers) { + pointerProperties.push_back(pointer.buildProperties()); + pointerCoords.push_back(pointer.buildCoords()); + } + + // Set mouse cursor position for the most common cases to avoid boilerplate. + if (mSource == AINPUT_SOURCE_MOUSE && + !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) && + mPointers.size() == 1) { + mRawXCursorPosition = pointerCoords[0].getX(); + mRawYCursorPosition = pointerCoords[0].getY(); + } + + NotifyMotionArgs args(InputEvent::nextId(), mEventTime, /*readTime=*/mEventTime, mDeviceId, + mSource, mDisplayId, mPolicyFlags, mAction, mActionButton, mFlags, + AMETA_NONE, mButtonState, MotionClassification::NONE, /*edgeFlags=*/0, + mPointers.size(), pointerProperties.data(), pointerCoords.data(), + /*xPrecision=*/0, /*yPrecision=*/0, mRawXCursorPosition, + mRawYCursorPosition, mDownTime, /*videoFrames=*/{}); + + return args; + } + +private: + int32_t mAction; + int32_t mDeviceId = DEVICE_ID; + int32_t mSource; + nsecs_t mDownTime; + nsecs_t mEventTime; + int32_t mDisplayId{ADISPLAY_ID_DEFAULT}; + int32_t mPolicyFlags = DEFAULT_POLICY_FLAGS; + int32_t mActionButton{0}; + int32_t mButtonState{0}; + int32_t mFlags{0}; + float mRawXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION}; + float mRawYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION}; + + std::vector mPointers; +}; + static InputEventInjectionResult injectMotionEvent( const std::unique_ptr& dispatcher, const MotionEvent& event, std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT, @@ -2054,6 +2161,92 @@ TEST_F(InputDispatcherTest, WallpaperWindowWhenSlippery) { wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); } +/** + * The policy typically sets POLICY_FLAG_PASS_TO_USER to the events. But when the display is not + * interactive, it might stop sending this flag. + * In this test, we check that if the policy stops sending this flag mid-gesture, we still ensure + * to have a consistent input stream. + * + * Test procedure: + * DOWN -> POINTER_DOWN -> (stop sending POLICY_FLAG_PASS_TO_USER) -> CANCEL. + * DOWN (new gesture). + * + * In the bad implementation, we could potentially drop the CANCEL event, and get an inconsistent + * state in the dispatcher. This would cause the final DOWN event to not be delivered to the app. + * + * We technically just need a single window here, but we are using two windows (spy on top and a + * regular window below) to emulate the actual situation where it happens on the device. + */ +TEST_F(InputDispatcherTest, TwoPointerCancelInconsistentPolicy) { + std::shared_ptr application = std::make_shared(); + sp spyWindow = + sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 200, 200)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}}); + const int32_t touchDeviceId = 4; + NotifyMotionArgs args; + + // Two pointers down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .policyFlags(DEFAULT_POLICY_FLAGS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .build())); + + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .policyFlags(DEFAULT_POLICY_FLAGS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(120).y(120)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + spyWindow->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + + // Cancel the current gesture. Send the cancel without the default policy flags. + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(AMOTION_EVENT_ACTION_CANCEL, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .policyFlags(0) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(120).y(120)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)); + + // We don't need to reset the device to reproduce the issue, but the reset event typically + // follows, so we keep it here to model the actual listener behaviour more closely. + NotifyDeviceResetArgs resetArgs; + resetArgs.id = 1; // arbitrary id + resetArgs.eventTime = systemTime(SYSTEM_TIME_MONOTONIC); + resetArgs.deviceId = touchDeviceId; + mDispatcher->notifyDeviceReset(&resetArgs); + + // Start new gesture + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .policyFlags(DEFAULT_POLICY_FLAGS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + + // No more events + spyWindow->assertNoEvents(); + window->assertNoEvents(); +} + /** * Two windows: a window on the left and a window on the right. * Mouse is hovered from the right window into the left window. -- GitLab From acb18f43f492ea00f733b6bdaee3f3def8f9c1ce Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Thu, 9 Feb 2023 18:27:02 +0000 Subject: [PATCH 0835/1310] Revert "SF: Introduce new frontend logic" This reverts commit d691322f979c8b76c54c30a15bfe40200d61d6e1. Reason for revert: b/267736365 Change-Id: I2625d645303549c38057c46afea59a25fd044199 --- .../FrontEnd/LayerSnapshotBuilder.cpp | 21 +- .../FrontEnd/LayerSnapshotBuilder.h | 7 +- services/surfaceflinger/Layer.cpp | 56 +- services/surfaceflinger/Layer.h | 39 +- services/surfaceflinger/LayerRenderArea.cpp | 9 +- services/surfaceflinger/SurfaceFlinger.cpp | 619 ++++-------------- services/surfaceflinger/SurfaceFlinger.h | 75 +-- .../fuzzer/surfaceflinger_layer_fuzzer.cpp | 2 +- 8 files changed, 196 insertions(+), 632 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index 3ed24b2740..6490476396 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -391,9 +391,7 @@ bool LayerSnapshotBuilder::tryFastUpdate(const Args& args) { void LayerSnapshotBuilder::updateSnapshots(const Args& args) { ATRACE_NAME("UpdateSnapshots"); - if (args.parentCrop) { - mRootSnapshot.geomLayerBounds = *args.parentCrop; - } else if (args.forceUpdate || args.displayChanges) { + if (args.forceUpdate || args.displayChanges) { mRootSnapshot.geomLayerBounds = getMaxDisplayBounds(args.displays); } if (args.displayChanges) { @@ -620,8 +618,7 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a RequestedLayerState::Changes::AffectsChildren); snapshot.changes = parentChanges | requested.changes; snapshot.isHiddenByPolicyFromParent = parentSnapshot.isHiddenByPolicyFromParent || - parentSnapshot.invalidTransform || requested.isHiddenByPolicy() || - (args.excludeLayerIds.find(path.id) != args.excludeLayerIds.end()); + parentSnapshot.invalidTransform || requested.isHiddenByPolicy(); snapshot.contentDirty = requested.what & layer_state_t::CONTENT_DIRTY; // TODO(b/238781169) scope down the changes to only buffer updates. snapshot.hasReadyFrame = @@ -986,20 +983,6 @@ void LayerSnapshotBuilder::forEachVisibleSnapshot(const ConstVisitor& visitor) c } } -// Visit each visible snapshot in z-order -void LayerSnapshotBuilder::forEachVisibleSnapshot(const ConstVisitor& visitor, - const LayerHierarchy& root) const { - root.traverseInZOrder( - [this, visitor](const LayerHierarchy&, - const LayerHierarchy::TraversalPath& traversalPath) -> bool { - LayerSnapshot* snapshot = getSnapshot(traversalPath); - if (snapshot && snapshot->isVisible) { - visitor(*snapshot); - } - return true; - }); -} - void LayerSnapshotBuilder::forEachVisibleSnapshot(const Visitor& visitor) { for (int i = 0; i < mNumInterestingSnapshots; i++) { std::unique_ptr& snapshot = mSnapshots.at((size_t)i); diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h index f4544fd62f..abb7e668c3 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h @@ -36,7 +36,7 @@ namespace android::surfaceflinger::frontend { class LayerSnapshotBuilder { public: struct Args { - LayerHierarchy root; + const LayerHierarchy& root; const LayerLifecycleManager& layerLifecycleManager; bool forceUpdate = false; bool includeMetadata = false; @@ -46,8 +46,6 @@ public: const renderengine::ShadowSettings& globalShadowSettings; bool supportsBlur = true; bool forceFullDamage = false; - std::optional parentCrop = std::nullopt; - std::unordered_set excludeLayerIds; }; LayerSnapshotBuilder(); @@ -67,9 +65,6 @@ public: // Visit each visible snapshot in z-order void forEachVisibleSnapshot(const ConstVisitor& visitor) const; - // Visit each visible snapshot in z-order - void forEachVisibleSnapshot(const ConstVisitor& visitor, const LayerHierarchy& root) const; - typedef std::function& snapshot)> Visitor; // Visit each visible snapshot in z-order and move the snapshot if needed void forEachVisibleSnapshot(const Visitor& visitor); diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 66c2fb658f..62e31b9f79 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -146,7 +146,7 @@ Layer::Layer(const LayerCreationArgs& args) mLayerCreationFlags(args.flags), mBorderEnabled(false), mTextureName(args.textureName), - mLegacyLayerFE(args.flinger->getFactory().createLayerFE(mName)) { + mLayerFE(args.flinger->getFactory().createLayerFE(mName)) { ALOGV("Creating Layer %s", getDebugName()); uint32_t layerFlags = 0; @@ -3123,14 +3123,15 @@ bool Layer::setSidebandStream(const sp& sidebandStream) { return true; } -bool Layer::setTransactionCompletedListeners(const std::vector>& handles, - bool willPresent) { +bool Layer::setTransactionCompletedListeners(const std::vector>& handles) { // If there is no handle, we will not send a callback so reset mReleasePreviousBuffer and return if (handles.empty()) { mReleasePreviousBuffer = false; return false; } + const bool willPresent = willPresentCurrentTransaction(); + std::deque> remainingHandles; for (const auto& handle : handles) { // If this transaction set a buffer on this layer, release its previous buffer @@ -3213,10 +3214,11 @@ bool Layer::fenceHasSignaled() const { return fenceSignaled; } -void Layer::onPreComposition(nsecs_t refreshStartTime) { +bool Layer::onPreComposition(nsecs_t refreshStartTime) { for (const auto& handle : mDrawingState.callbackHandles) { handle->refreshStartTime = refreshStartTime; } + return hasReadyFrame(); } void Layer::setAutoRefresh(bool autoRefresh) { @@ -3602,7 +3604,7 @@ bool Layer::isHdrY410() const { sp Layer::getCompositionEngineLayerFE() const { // There's no need to get a CE Layer if the layer isn't going to draw anything. - return hasSomethingToDraw() ? mLegacyLayerFE : nullptr; + return hasSomethingToDraw() ? mLayerFE : nullptr; } const LayerSnapshot* Layer::getLayerSnapshot() const { @@ -3613,36 +3615,16 @@ LayerSnapshot* Layer::editLayerSnapshot() { return mSnapshot.get(); } -std::unique_ptr Layer::stealLayerSnapshot() { - return std::move(mSnapshot); -} - -void Layer::updateLayerSnapshot(std::unique_ptr snapshot) { - mSnapshot = std::move(snapshot); -} - const compositionengine::LayerFECompositionState* Layer::getCompositionState() const { return mSnapshot.get(); } sp Layer::copyCompositionEngineLayerFE() const { - auto result = mFlinger->getFactory().createLayerFE(mName); + auto result = mFlinger->getFactory().createLayerFE(mLayerFE->getDebugName()); result->mSnapshot = std::make_unique(*mSnapshot); return result; } -sp Layer::getCompositionEngineLayerFE( - const frontend::LayerHierarchy::TraversalPath& path) { - for (auto& [p, layerFE] : mLayerFEs) { - if (p == path) { - return layerFE; - } - } - auto layerFE = mFlinger->getFactory().createLayerFE(mName); - mLayerFEs.emplace_back(path, layerFE); - return layerFE; -} - void Layer::useSurfaceDamage() { if (mFlinger->mForceFullDamage) { surfaceDamageRegion = Region::INVALID_REGION; @@ -4038,6 +4020,28 @@ void Layer::updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMet } } +LayerSnapshotGuard::LayerSnapshotGuard(Layer* layer) : mLayer(layer) { + if (mLayer) { + mLayer->mLayerFE->mSnapshot = std::move(mLayer->mSnapshot); + } +} + +LayerSnapshotGuard::~LayerSnapshotGuard() { + if (mLayer) { + mLayer->mSnapshot = std::move(mLayer->mLayerFE->mSnapshot); + } +} + +LayerSnapshotGuard::LayerSnapshotGuard(LayerSnapshotGuard&& other) : mLayer(other.mLayer) { + other.mLayer = nullptr; +} + +LayerSnapshotGuard& LayerSnapshotGuard::operator=(LayerSnapshotGuard&& other) { + mLayer = other.mLayer; + other.mLayer = nullptr; + return *this; +} + void Layer::setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds, TrustedPresentationListener const& listener) { bool hadTrustedPresentationListener = hasTrustedPresentationListener(); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 07c01d8e17..429dfb0c6f 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -307,8 +307,7 @@ public: bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/); bool setApi(int32_t /*api*/); bool setSidebandStream(const sp& /*sidebandStream*/); - bool setTransactionCompletedListeners(const std::vector>& /*handles*/, - bool willPresent); + bool setTransactionCompletedListeners(const std::vector>& /*handles*/); virtual bool setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace); virtual bool setColorSpaceAgnostic(const bool agnostic); virtual bool setDimmingEnabled(const bool dimmingEnabled); @@ -329,12 +328,9 @@ public: virtual sp getCompositionEngineLayerFE() const; virtual sp copyCompositionEngineLayerFE() const; - sp getCompositionEngineLayerFE(const frontend::LayerHierarchy::TraversalPath&); const frontend::LayerSnapshot* getLayerSnapshot() const; frontend::LayerSnapshot* editLayerSnapshot(); - std::unique_ptr stealLayerSnapshot(); - void updateLayerSnapshot(std::unique_ptr snapshot); // If we have received a new buffer this frame, we will pass its surface // damage down to hardware composer. Otherwise, we must send a region with @@ -516,7 +512,7 @@ public: // implements compositionengine::LayerFE const compositionengine::LayerFECompositionState* getCompositionState() const; bool fenceHasSignaled() const; - void onPreComposition(nsecs_t refreshStartTime); + bool onPreComposition(nsecs_t refreshStartTime); void onLayerDisplayed(ftl::SharedFuture); void setWasClientComposed(const sp& fence) { @@ -836,7 +832,6 @@ public: void updateMetadataSnapshot(const LayerMetadata& parentMetadata); void updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata, std::unordered_set& visited); - bool willPresentCurrentTransaction() const; protected: // For unit tests @@ -1042,6 +1037,8 @@ private: // Crop that applies to the buffer Rect computeBufferCrop(const State& s); + bool willPresentCurrentTransaction() const; + void callReleaseBufferCallback(const sp& listener, const sp& buffer, uint64_t framenumber, const sp& releaseFence, @@ -1154,10 +1151,34 @@ private: // not specify a destination frame. ui::Transform mRequestedTransform; - sp mLegacyLayerFE; - std::vector>> mLayerFEs; + sp mLayerFE; std::unique_ptr mSnapshot = std::make_unique(); + + friend class LayerSnapshotGuard; +}; + +// LayerSnapshotGuard manages the movement of LayerSnapshot between a Layer and its corresponding +// LayerFE. This class must be used whenever LayerFEs are passed to CompositionEngine. Instances of +// LayerSnapshotGuard should only be constructed on the main thread and should not be moved outside +// the main thread. +// +// Moving the snapshot instead of sharing common state prevents use of LayerFE outside the main +// thread by making errors obvious (i.e. use outside the main thread results in SEGFAULTs due to +// nullptr dereference). +class LayerSnapshotGuard { +public: + LayerSnapshotGuard(Layer* layer) REQUIRES(kMainThreadContext); + ~LayerSnapshotGuard() REQUIRES(kMainThreadContext); + + LayerSnapshotGuard(const LayerSnapshotGuard&) = delete; + LayerSnapshotGuard& operator=(const LayerSnapshotGuard&) = delete; + + LayerSnapshotGuard(LayerSnapshotGuard&& other) REQUIRES(kMainThreadContext); + LayerSnapshotGuard& operator=(LayerSnapshotGuard&& other) REQUIRES(kMainThreadContext); + +private: + Layer* mLayer; }; std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate); diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp index 03a7f226ed..2b4375b0fa 100644 --- a/services/surfaceflinger/LayerRenderArea.cpp +++ b/services/surfaceflinger/LayerRenderArea.cpp @@ -69,14 +69,6 @@ Rect LayerRenderArea::getSourceCrop() const { void LayerRenderArea::render(std::function drawLayers) { using namespace std::string_literals; - if (!mChildrenOnly) { - mTransform = mLayer->getTransform().inverse(); - } - - if (mFlinger.mLayerLifecycleManagerEnabled) { - drawLayers(); - return; - } // If layer is offscreen, update mirroring info if it exists if (mLayer->isRemovedFromCurrentState()) { mLayer->traverse(LayerVector::StateSet::Drawing, @@ -86,6 +78,7 @@ void LayerRenderArea::render(std::function drawLayers) { } if (!mChildrenOnly) { + mTransform = mLayer->getTransform().inverse(); // If the layer is offscreen, compute bounds since we don't compute bounds for offscreen // layers in a regular cycles. if (mLayer->isRemovedFromCurrentState()) { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 68ab776400..e3649ec4c1 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -173,8 +173,6 @@ using namespace std::string_view_literals; using namespace hardware::configstore; using namespace hardware::configstore::V1_0; using namespace sysprop; -using ftl::Flags; -using namespace ftl::flag_operators; using aidl::android::hardware::graphics::common::DisplayDecorationSupport; using aidl::android::hardware::graphics::composer3::Capability; @@ -472,10 +470,6 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI mPowerHintSessionMode = {.late = base::GetBoolProperty("debug.sf.send_late_power_session_hint"s, true), .early = base::GetBoolProperty("debug.sf.send_early_power_session_hint"s, false)}; - mLayerLifecycleManagerEnabled = - base::GetBoolProperty("debug.sf.enable_layer_lifecycle_manager"s, false); - mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled || - base::GetBoolProperty("debug.sf.enable_legacy_frontend"s, true); } LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() { @@ -2133,110 +2127,6 @@ void SurfaceFlinger::configure() FTL_FAKE_GUARD(kMainThreadContext) { } } -bool SurfaceFlinger::updateLayerSnapshotsLegacy(VsyncId vsyncId, LifecycleUpdate& update, - bool transactionsFlushed, - bool& outTransactionsAreEmpty) { - bool needsTraversal = false; - if (transactionsFlushed) { - needsTraversal |= commitMirrorDisplays(vsyncId); - needsTraversal |= commitCreatedLayers(vsyncId, update.layerCreatedStates); - needsTraversal |= applyTransactions(update.transactions, vsyncId); - } - outTransactionsAreEmpty = !needsTraversal; - const bool shouldCommit = (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal; - if (shouldCommit) { - commitTransactions(); - } - - bool mustComposite = latchBuffers() || shouldCommit; - updateLayerGeometry(); - return mustComposite; -} - -bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, LifecycleUpdate& update, - bool transactionsFlushed, bool& outTransactionsAreEmpty) { - using Changes = frontend::RequestedLayerState::Changes; - ATRACE_NAME("updateLayerSnapshots"); - { - mLayerLifecycleManager.addLayers(std::move(update.newLayers)); - mLayerLifecycleManager.applyTransactions(update.transactions); - mLayerLifecycleManager.onHandlesDestroyed(update.destroyedHandles); - for (auto& legacyLayer : update.layerCreatedStates) { - sp layer = legacyLayer.layer.promote(); - if (layer) { - mLegacyLayers[layer->sequence] = layer; - } - } - } - if (mLayerLifecycleManager.getGlobalChanges().test(Changes::Hierarchy)) { - ATRACE_NAME("LayerHierarchyBuilder:update"); - mLayerHierarchyBuilder.update(mLayerLifecycleManager.getLayers(), - mLayerLifecycleManager.getDestroyedLayers()); - } - - applyAndCommitDisplayTransactionStates(update.transactions); - - { - ATRACE_NAME("LayerSnapshotBuilder:update"); - frontend::LayerSnapshotBuilder::Args args{.root = mLayerHierarchyBuilder.getHierarchy(), - .layerLifecycleManager = mLayerLifecycleManager, - .displays = mFrontEndDisplayInfos, - .displayChanges = mFrontEndDisplayInfosChanged, - .globalShadowSettings = - mDrawingState.globalShadowSettings, - .supportsBlur = mSupportsBlur, - .forceFullDamage = mForceFullDamage}; - mLayerSnapshotBuilder.update(args); - } - - if (mLayerLifecycleManager.getGlobalChanges().any(Changes::Geometry | Changes::Input | - Changes::Hierarchy)) { - mUpdateInputInfo = true; - } - if (mLayerLifecycleManager.getGlobalChanges().any(Changes::VisibleRegion | Changes::Hierarchy | - Changes::Visibility)) { - mVisibleRegionsDirty = true; - } - outTransactionsAreEmpty = mLayerLifecycleManager.getGlobalChanges().get() == 0; - const bool mustComposite = mLayerLifecycleManager.getGlobalChanges().get() != 0; - { - ATRACE_NAME("LLM:commitChanges"); - mLayerLifecycleManager.commitChanges(); - } - - if (!mLegacyFrontEndEnabled) { - ATRACE_NAME("DisplayCallbackAndStatsUpdates"); - applyTransactions(update.transactions, vsyncId); - - bool newDataLatched = false; - for (auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) { - if (!snapshot->changes.test(Changes::Buffer)) continue; - auto it = mLegacyLayers.find(snapshot->sequence); - LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s", - snapshot->getDebugString().c_str()); - mLayersWithQueuedFrames.emplace(it->second); - newDataLatched = true; - if (!snapshot->isVisible) break; - - Region visibleReg; - visibleReg.set(snapshot->transformedBoundsWithoutTransparentRegion); - invalidateLayerStack(snapshot->outputFilter, visibleReg); - } - - for (auto& destroyedLayer : mLayerLifecycleManager.getDestroyedLayers()) { - mLegacyLayers.erase(destroyedLayer->id); - } - - // enter boot animation on first buffer latch - if (CC_UNLIKELY(mBootStage == BootStage::BOOTLOADER && newDataLatched)) { - ALOGI("Enter boot animation"); - mBootStage = BootStage::BOOTANIMATION; - } - commitTransactions(); - } - return mustComposite; -} - bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expectedVsyncTime) FTL_FAKE_GUARD(kMainThreadContext) { // The expectedVsyncTime, which was predicted when this frame was scheduled, is normally in the @@ -2376,34 +2266,45 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe mFrameTimeline->setSfWakeUp(vsyncId.value, frameTime.ns(), Fps::fromPeriodNsecs(vsyncPeriod.ns())); - const bool flushTransactions = clearTransactionFlags(eTransactionFlushNeeded); - LifecycleUpdate updates; - if (flushTransactions) { - updates = flushLifecycleUpdates(); - } - bool transactionsAreEmpty; - if (mLegacyFrontEndEnabled) { - mustComposite |= updateLayerSnapshotsLegacy(vsyncId, updates, flushTransactions, - transactionsAreEmpty); + bool needsTraversal = false; + if (clearTransactionFlags(eTransactionFlushNeeded)) { + // Locking: + // 1. to prevent onHandleDestroyed from being called while the state lock is held, + // we must keep a copy of the transactions (specifically the composer + // states) around outside the scope of the lock. + // 2. Transactions and created layers do not share a lock. To prevent applying + // transactions with layers still in the createdLayer queue, flush the transactions + // before committing the created layers. + std::vector transactions = mTransactionHandler.flushTransactions(); + needsTraversal |= commitMirrorDisplays(vsyncId); + needsTraversal |= commitCreatedLayers(vsyncId); + needsTraversal |= applyTransactions(transactions, vsyncId); } - if (mLayerLifecycleManagerEnabled) { - mustComposite |= - updateLayerSnapshots(vsyncId, updates, flushTransactions, transactionsAreEmpty); + + const bool shouldCommit = + (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal; + if (shouldCommit) { + commitTransactions(); } if (transactionFlushNeeded()) { setTransactionFlags(eTransactionFlushNeeded); } + mustComposite |= shouldCommit; + mustComposite |= latchBuffers(); + // This has to be called after latchBuffers because we want to include the layers that have // been latched in the commit callback - if (transactionsAreEmpty) { + if (!needsTraversal) { // Invoke empty transaction callbacks early. mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */); } else { // Invoke OnCommit callbacks. mTransactionCallbackInvoker.sendCallbacks(true /* onCommitOnly */); } + + updateLayerGeometry(); } // Layers need to get updated (in the previous line) before we can use them for @@ -2490,6 +2391,15 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty; refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty; + std::vector layers; + + mDrawingState.traverseInZOrder([&refreshArgs, &layers](Layer* layer) { + if (auto layerFE = layer->getCompositionEngineLayerFE()) { + layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame); + refreshArgs.layers.push_back(layerFE); + layers.push_back(layer); + } + }); refreshArgs.internalDisplayRotationFlags = DisplayDevice::getPrimaryDisplayRotationFlags(); if (CC_UNLIKELY(mDrawingState.colorMatrixChanged)) { @@ -2516,13 +2426,17 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) // the scheduler. const auto presentTime = systemTime(); - std::vector> layers = - moveSnapshotsToCompositionArgs(refreshArgs, /*cursorOnly=*/false, vsyncId.value); - mCompositionEngine->present(refreshArgs); - moveSnapshotsFromCompositionArgs(refreshArgs, layers); + { + std::vector layerSnapshotGuards; + for (Layer* layer : layers) { + layerSnapshotGuards.emplace_back(layer); + } + mCompositionEngine->present(refreshArgs); + } - for (auto [layer, layerFE] : layers) { - CompositionResult compositionResult{layerFE->stealCompositionResult()}; + for (auto& layer : layers) { + CompositionResult compositionResult{ + layer->getCompositionEngineLayerFE()->stealCompositionResult()}; layer->onPreComposition(compositionResult.refreshStartTime); for (auto releaseFence : compositionResult.releaseFences) { layer->onLayerDisplayed(releaseFence); @@ -2616,7 +2530,7 @@ void SurfaceFlinger::updateLayerGeometry() { for (auto& layer : mLayersPendingRefresh) { Region visibleReg; visibleReg.set(layer->getScreenBounds()); - invalidateLayerStack(layer->getOutputFilter(), visibleReg); + invalidateLayerStack(layer, visibleReg); } mLayersPendingRefresh.clear(); } @@ -3473,8 +3387,7 @@ void SurfaceFlinger::processDisplayChangesLocked() { void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { // Commit display transactions. const bool displayTransactionNeeded = transactionFlags & eDisplayTransactionNeeded; - mFrontEndDisplayInfosChanged = displayTransactionNeeded; - if (displayTransactionNeeded && !mLayerLifecycleManagerEnabled) { + if (displayTransactionNeeded) { processDisplayChangesLocked(); mFrontEndDisplayInfos.clear(); for (const auto& [_, display] : mDisplays) { @@ -3565,7 +3478,7 @@ void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { // this layer is not visible anymore Region visibleReg; visibleReg.set(layer->getScreenBounds()); - invalidateLayerStack(layer->getOutputFilter(), visibleReg); + invalidateLayerStack(sp::fromExisting(layer), visibleReg); } }); } @@ -3653,23 +3566,16 @@ void SurfaceFlinger::buildWindowInfos(std::vector& outWindowInfos, outWindowInfos.reserve(sNumWindowInfos); sNumWindowInfos = 0; - if (mLayerLifecycleManagerEnabled) { - mLayerSnapshotBuilder.forEachInputSnapshot( - [&outWindowInfos](const frontend::LayerSnapshot& snapshot) { - outWindowInfos.push_back(snapshot.inputInfo); - }); - } else { - mDrawingState.traverseInReverseZOrder([&](Layer* layer) { - if (!layer->needsInputInfo()) return; - const auto opt = - mFrontEndDisplayInfos.get(layer->getLayerStack()) - .transform([](const frontend::DisplayInfo& info) { - return Layer::InputDisplayArgs{&info.transform, info.isSecure}; - }); + mDrawingState.traverseInReverseZOrder([&](Layer* layer) { + if (!layer->needsInputInfo()) return; - outWindowInfos.push_back(layer->fillInputInfo(opt.value_or(Layer::InputDisplayArgs{}))); - }); - } + const auto opt = mFrontEndDisplayInfos.get(layer->getLayerStack()) + .transform([](const frontend::DisplayInfo& info) { + return Layer::InputDisplayArgs{&info.transform, info.isSecure}; + }); + + outWindowInfos.push_back(layer->fillInputInfo(opt.value_or(Layer::InputDisplayArgs{}))); + }); sNumWindowInfos = outWindowInfos.size(); @@ -3686,9 +3592,17 @@ void SurfaceFlinger::updateCursorAsync() { refreshArgs.outputs.push_back(display->getCompositionDisplay()); } } - auto layers = moveSnapshotsToCompositionArgs(refreshArgs, /*cursorOnly=*/true, 0); + + std::vector layerSnapshotGuards; + mDrawingState.traverse([&layerSnapshotGuards](Layer* layer) { + if (layer->getLayerSnapshot()->compositionType == + aidl::android::hardware::graphics::composer3::Composition::CURSOR) { + layer->updateSnapshot(false /* updateGeometry */); + layerSnapshotGuards.emplace_back(layer); + } + }); + mCompositionEngine->updateCursorAsync(refreshArgs); - moveSnapshotsFromCompositionArgs(refreshArgs, layers); } void SurfaceFlinger::requestDisplayModes(std::vector modeRequests) { @@ -3864,10 +3778,10 @@ void SurfaceFlinger::commitOffscreenLayers() { } } -void SurfaceFlinger::invalidateLayerStack(const ui::LayerFilter& layerFilter, const Region& dirty) { +void SurfaceFlinger::invalidateLayerStack(const sp& layer, const Region& dirty) { for (const auto& [token, displayDevice] : FTL_FAKE_GUARD(mStateLock, mDisplays)) { auto display = displayDevice->getCompositionDisplay(); - if (display->includesLayer(layerFilter)) { + if (display->includesLayer(layer->getOutputFilter())) { display->editState().dirtyRegion.orSelf(dirty); } } @@ -3987,7 +3901,6 @@ status_t SurfaceFlinger::addClientLayer(const LayerCreationArgs& args, const sp< { std::scoped_lock lock(mCreatedLayersLock); mCreatedLayers.emplace_back(layer, parent, args.addToRoot); - mNewLayers.emplace_back(std::make_unique(args)); } setTransactionFlags(eTransactionNeeded); @@ -4341,11 +4254,9 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin const std::vector& listenerCallbacks, int originPid, int originUid, uint64_t transactionId) { uint32_t transactionFlags = 0; - if (!mLayerLifecycleManagerEnabled) { - for (DisplayState& display : displays) { - display.sanitize(permissions); - transactionFlags |= setDisplayStateLocked(display); - } + for (DisplayState& display : displays) { + display.sanitize(permissions); + transactionFlags |= setDisplayStateLocked(display); } // start and end registration for listeners w/ no surface so they can get their callback. Note @@ -4357,16 +4268,9 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin uint32_t clientStateFlags = 0; for (auto& resolvedState : states) { - if (mLegacyFrontEndEnabled) { - clientStateFlags |= - setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime, - isAutoTimestamp, postTime, permissions, transactionId); - - } else /*mLayerLifecycleManagerEnabled*/ { - clientStateFlags |= updateLayerCallbacksAndStats(frameTimelineInfo, resolvedState, - desiredPresentTime, isAutoTimestamp, - postTime, permissions, transactionId); - } + clientStateFlags |= + setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime, + isAutoTimestamp, postTime, permissions, transactionId); if ((flags & eAnimation) && resolvedState.state.surface) { if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) { using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType; @@ -4399,35 +4303,8 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin bool needsTraversal = false; if (transactionFlags) { - // We are on the main thread, we are about to perform a traversal. Clear the traversal bit - // so we don't have to wake up again next frame to perform an unnecessary traversal. - if (transactionFlags & eTraversalNeeded) { - transactionFlags = transactionFlags & (~eTraversalNeeded); - needsTraversal = true; - } - if (transactionFlags) { - setTransactionFlags(transactionFlags); - } - } - - return needsTraversal; -} - -bool SurfaceFlinger::applyAndCommitDisplayTransactionStates( - std::vector& transactions) { - Mutex::Autolock _l(mStateLock); - bool needsTraversal = false; - uint32_t transactionFlags = 0; - for (auto& transaction : transactions) { - for (DisplayState& display : transaction.displays) { - display.sanitize(transaction.permissions); - transactionFlags |= setDisplayStateLocked(display); - } - } - - if (transactionFlags) { - // We are on the main thread, we are about to perform a traversal. Clear the traversal bit - // so we don't have to wake up again next frame to perform an unnecessary traversal. + // We are on the main thread, we are about to preform a traversal. Clear the traversal bit + // so we don't have to wake up again next frame to preform an unnecessary traversal. if (transactionFlags & eTraversalNeeded) { transactionFlags = transactionFlags & (~eTraversalNeeded); needsTraversal = true; @@ -4437,15 +4314,6 @@ bool SurfaceFlinger::applyAndCommitDisplayTransactionStates( } } - mFrontEndDisplayInfosChanged = mTransactionFlags & eDisplayTransactionNeeded; - if (mFrontEndDisplayInfosChanged && !mLegacyFrontEndEnabled) { - processDisplayChangesLocked(); - mFrontEndDisplayInfos.clear(); - for (const auto& [_, display] : mDisplays) { - mFrontEndDisplayInfos.try_emplace(display->getLayerStack(), display->getFrontEndInfo()); - } - } - return needsTraversal; } @@ -4836,8 +4704,7 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime // Do nothing. Processing the transaction completed listeners currently cause the flush. } - if (layer->setTransactionCompletedListeners(callbackHandles, - layer->willPresentCurrentTransaction())) { + if (layer->setTransactionCompletedListeners(callbackHandles)) { flags |= eTraversalNeeded; } @@ -4853,96 +4720,6 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime return flags; } -uint32_t SurfaceFlinger::updateLayerCallbacksAndStats(const FrameTimelineInfo& frameTimelineInfo, - ResolvedComposerState& composerState, - int64_t desiredPresentTime, - bool isAutoTimestamp, int64_t postTime, - uint32_t permissions, - uint64_t transactionId) { - layer_state_t& s = composerState.state; - s.sanitize(permissions); - const nsecs_t latchTime = systemTime(); - bool unused; - - std::vector filteredListeners; - for (auto& listener : s.listeners) { - // Starts a registration but separates the callback ids according to callback type. This - // allows the callback invoker to send on latch callbacks earlier. - // note that startRegistration will not re-register if the listener has - // already be registered for a prior surface control - - ListenerCallbacks onCommitCallbacks = listener.filter(CallbackId::Type::ON_COMMIT); - if (!onCommitCallbacks.callbackIds.empty()) { - filteredListeners.push_back(onCommitCallbacks); - } - - ListenerCallbacks onCompleteCallbacks = listener.filter(CallbackId::Type::ON_COMPLETE); - if (!onCompleteCallbacks.callbackIds.empty()) { - filteredListeners.push_back(onCompleteCallbacks); - } - } - - const uint64_t what = s.what; - uint32_t flags = 0; - sp layer = nullptr; - if (s.surface) { - layer = LayerHandle::getLayer(s.surface); - } else { - // The client may provide us a null handle. Treat it as if the layer was removed. - ALOGW("Attempt to set client state with a null layer handle"); - } - if (layer == nullptr) { - for (auto& [listener, callbackIds] : s.listeners) { - mTransactionCallbackInvoker.addCallbackHandle(sp::make(listener, - callbackIds, - s.surface), - std::vector()); - } - return 0; - } - if (what & layer_state_t::eProducerDisconnect) { - layer->onDisconnect(); - } - std::optional dequeueBufferTimestamp; - if (what & layer_state_t::eMetadataChanged) { - dequeueBufferTimestamp = s.metadata.getInt64(gui::METADATA_DEQUEUE_TIME); - } - - std::vector> callbackHandles; - if ((what & layer_state_t::eHasListenerCallbacksChanged) && (!filteredListeners.empty())) { - for (auto& [listener, callbackIds] : filteredListeners) { - callbackHandles.emplace_back( - sp::make(listener, callbackIds, s.surface)); - } - } - if (what & layer_state_t::eSidebandStreamChanged) { - if (layer->setSidebandStream(s.sidebandStream)) flags |= eTraversalNeeded; - } - if (what & layer_state_t::eBufferChanged) { - if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime, - desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp, - frameTimelineInfo)) { - layer->latchBuffer(unused, latchTime); - flags |= eTraversalNeeded; - } - mLayersWithQueuedFrames.emplace(layer); - } else if (frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) { - layer->setFrameTimelineVsyncForBufferlessTransaction(frameTimelineInfo, postTime); - } - - if (what & layer_state_t::eTrustedPresentationInfoChanged) { - layer->setTrustedPresentationInfo(s.trustedPresentationThresholds, - s.trustedPresentationListener); - } - - const auto& snapshot = mLayerSnapshotBuilder.getSnapshot(layer->getSequence()); - bool willPresentCurrentTransaction = - snapshot && (snapshot->hasReadyFrame || snapshot->sidebandStreamHasFrame); - if (layer->setTransactionCompletedListeners(callbackHandles, willPresentCurrentTransaction)) - flags |= eTraversalNeeded; - return flags; -} - uint32_t SurfaceFlinger::addInputWindowCommands(const InputWindowCommands& inputWindowCommands) { bool hasChanges = mInputWindowCommands.merge(inputWindowCommands); return hasChanges ? eTraversalNeeded : 0; @@ -5011,7 +4788,6 @@ status_t SurfaceFlinger::mirrorDisplay(DisplayId displayId, const LayerCreationA LayerCreationArgs mirrorArgs(args); mirrorArgs.flags |= ISurfaceComposerClient::eNoColorFill; mirrorArgs.addToRoot = true; - mirrorArgs.layerStackToMirror = layerStack; result = createEffectLayer(mirrorArgs, &outResult.handle, &rootMirrorLayer); outResult.layerId = rootMirrorLayer->sequence; outResult.layerName = String16(rootMirrorLayer->getDebugName()); @@ -5114,12 +4890,7 @@ void SurfaceFlinger::markLayerPendingRemovalLocked(const sp& layer) { setTransactionFlags(eTransactionNeeded); } -void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp& layer, uint32_t layerId) { - { - std::scoped_lock lock(mCreatedLayersLock); - mDestroyedHandles.emplace_back(layerId); - } - +void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp& layer, uint32_t /* layerId */) { Mutex::Autolock lock(mStateLock); markLayerPendingRemovalLocked(layer); mBufferCountTracker.remove(handle); @@ -5127,8 +4898,6 @@ void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp& layer, uint32 if (mTransactionTracing) { mTransactionTracing->onHandleRemoved(handle); } - - setTransactionFlags(eTransactionFlushNeeded); } void SurfaceFlinger::onInitializeDisplays() { @@ -6698,15 +6467,10 @@ status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, args.useIdentityTransform, args.captureSecureLayers); }); - GetLayerSnapshotsFunction getLayerSnapshots; - if (mLayerLifecycleManagerEnabled) { - getLayerSnapshots = getLayerSnapshotsForScreenshots(layerStack, args.uid); - } else { - auto traverseLayers = [this, args, layerStack](const LayerVector::Visitor& visitor) { - traverseLayersInLayerStack(layerStack, args.uid, visitor); - }; - getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); - } + auto traverseLayers = [this, args, layerStack](const LayerVector::Visitor& visitor) { + traverseLayersInLayerStack(layerStack, args.uid, visitor); + }; + auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); auto future = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize, args.pixelFormat, args.allowProtected, args.grayscale, @@ -6740,15 +6504,10 @@ status_t SurfaceFlinger::captureDisplay(DisplayId displayId, false /* captureSecureLayers */); }); - GetLayerSnapshotsFunction getLayerSnapshots; - if (mLayerLifecycleManagerEnabled) { - getLayerSnapshots = getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID); - } else { - auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) { - traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, visitor); - }; - getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); - } + auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) { + traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, visitor); + }; + auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); if (captureListener == nullptr) { ALOGE("capture screen must provide a capture listener callback"); @@ -6843,37 +6602,29 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, return std::make_unique(*this, parent, crop, reqSize, dataspace, childrenOnly, args.captureSecureLayers); }); - GetLayerSnapshotsFunction getLayerSnapshots; - if (mLayerLifecycleManagerEnabled) { - FloatRect parentCrop = crop.isEmpty() ? FloatRect(0, 0, reqSize.width, reqSize.height) - : crop.toFloatRect(); - getLayerSnapshots = getLayerSnapshotsForScreenshots(parent->sequence, args.uid, - std::move(excludeLayerIds), - args.childrenOnly, parentCrop); - } else { - auto traverseLayers = [parent, args, excludeLayerIds](const LayerVector::Visitor& visitor) { - parent->traverseChildrenInZOrder(LayerVector::StateSet::Drawing, [&](Layer* layer) { - if (!layer->isVisible()) { - return; - } else if (args.childrenOnly && layer == parent.get()) { - return; - } else if (args.uid != CaptureArgs::UNSET_UID && args.uid != layer->getOwnerUid()) { - return; - } - auto p = sp::fromExisting(layer); - while (p != nullptr) { - if (excludeLayerIds.count(p->sequence) != 0) { - return; - } - p = p->getParent(); + auto traverseLayers = [parent, args, excludeLayerIds](const LayerVector::Visitor& visitor) { + parent->traverseChildrenInZOrder(LayerVector::StateSet::Drawing, [&](Layer* layer) { + if (!layer->isVisible()) { + return; + } else if (args.childrenOnly && layer == parent.get()) { + return; + } else if (args.uid != CaptureArgs::UNSET_UID && args.uid != layer->getOwnerUid()) { + return; + } + + auto p = sp::fromExisting(layer); + while (p != nullptr) { + if (excludeLayerIds.count(p->sequence) != 0) { + return; } + p = p->getParent(); + } - visitor(layer); - }); - }; - getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); - } + visitor(layer); + }); + }; + auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); if (captureListener == nullptr) { ALOGE("capture screen must provide a capture listener callback"); @@ -7655,18 +7406,24 @@ bool SurfaceFlinger::commitMirrorDisplays(VsyncId vsyncId) { return true; } -bool SurfaceFlinger::commitCreatedLayers(VsyncId vsyncId, - std::vector& createdLayers) { - if (createdLayers.size() == 0) { - return false; +bool SurfaceFlinger::commitCreatedLayers(VsyncId vsyncId) { + std::vector createdLayers; + { + std::scoped_lock lock(mCreatedLayersLock); + createdLayers = std::move(mCreatedLayers); + mCreatedLayers.clear(); + if (createdLayers.size() == 0) { + return false; + } } Mutex::Autolock _l(mStateLock); for (const auto& createdLayer : createdLayers) { handleLayerCreatedLocked(createdLayer, vsyncId); } + createdLayers.clear(); mLayersAdded = true; - return mLayersAdded; + return true; } void SurfaceFlinger::updateLayerMetadataSnapshot() { @@ -7694,150 +7451,6 @@ void SurfaceFlinger::updateLayerMetadataSnapshot() { }); } -void SurfaceFlinger::moveSnapshotsFromCompositionArgs( - compositionengine::CompositionRefreshArgs& refreshArgs, - std::vector>& layers) { - if (mLayerLifecycleManagerEnabled) { - std::vector>& snapshots = - mLayerSnapshotBuilder.getSnapshots(); - for (auto [_, layerFE] : layers) { - auto i = layerFE->mSnapshot->globalZ; - snapshots[i] = std::move(layerFE->mSnapshot); - } - } - if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) { - for (auto [layer, layerFE] : layers) { - layer->updateLayerSnapshot(std::move(layerFE->mSnapshot)); - } - } -} - -std::vector> SurfaceFlinger::moveSnapshotsToCompositionArgs( - compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly, int64_t vsyncId) { - std::vector> layers; - if (mLayerLifecycleManagerEnabled) { - mLayerSnapshotBuilder.forEachVisibleSnapshot( - [&](std::unique_ptr& snapshot) { - if (cursorOnly && - snapshot->compositionType != - aidl::android::hardware::graphics::composer3::Composition::CURSOR) { - return; - } - - if (!snapshot->hasSomethingToDraw()) { - return; - } - - auto it = mLegacyLayers.find(snapshot->sequence); - LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), - "Couldnt find layer object for %s", - snapshot->getDebugString().c_str()); - auto& legacyLayer = it->second; - sp layerFE = legacyLayer->getCompositionEngineLayerFE(snapshot->path); - layerFE->mSnapshot = std::move(snapshot); - refreshArgs.layers.push_back(layerFE); - layers.emplace_back(legacyLayer.get(), layerFE.get()); - }); - } - if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) { - mDrawingState.traverseInZOrder([&refreshArgs, cursorOnly, &layers](Layer* layer) { - if (auto layerFE = layer->getCompositionEngineLayerFE()) { - if (cursorOnly && - layer->getLayerSnapshot()->compositionType != - aidl::android::hardware::graphics::composer3::Composition::CURSOR) - return; - layer->updateSnapshot(/* refreshArgs.updatingGeometryThisFrame */ true); - layerFE->mSnapshot = layer->stealLayerSnapshot(); - refreshArgs.layers.push_back(layerFE); - layers.emplace_back(layer, layerFE.get()); - } - }); - } - - return layers; -} - -std::function>>()> -SurfaceFlinger::getLayerSnapshotsForScreenshots(std::optional layerStack, - uint32_t uid) { - return [this, layerStack, uid]() { - std::vector>> layers; - for (auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) { - if (layerStack && snapshot->outputFilter.layerStack != *layerStack) { - continue; - } - if (uid != CaptureArgs::UNSET_UID && snapshot->inputInfo.ownerUid != uid) { - continue; - } - if (!snapshot->isVisible || !snapshot->hasSomethingToDraw()) { - continue; - } - - auto it = mLegacyLayers.find(snapshot->sequence); - LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s", - snapshot->getDebugString().c_str()); - auto& legacyLayer = it->second; - sp layerFE = getFactory().createLayerFE(legacyLayer->getName()); - layerFE->mSnapshot = std::make_unique(*snapshot); - layers.emplace_back(legacyLayer.get(), std::move(layerFE)); - } - - return layers; - }; -} - -std::function>>()> -SurfaceFlinger::getLayerSnapshotsForScreenshots(uint32_t rootLayerId, uint32_t uid, - std::unordered_set excludeLayerIds, - bool childrenOnly, const FloatRect& parentCrop) { - return [this, excludeLayerIds = std::move(excludeLayerIds), uid, rootLayerId, childrenOnly, - parentCrop]() { - frontend::LayerSnapshotBuilder::Args - args{.root = mLayerHierarchyBuilder.getPartialHierarchy(rootLayerId, childrenOnly), - .layerLifecycleManager = mLayerLifecycleManager, - .displays = mFrontEndDisplayInfos, - .displayChanges = true, - .globalShadowSettings = mDrawingState.globalShadowSettings, - .supportsBlur = mSupportsBlur, - .forceFullDamage = mForceFullDamage, - .parentCrop = {parentCrop}, - .excludeLayerIds = std::move(excludeLayerIds)}; - mLayerSnapshotBuilder.update(args); - - auto getLayerSnapshotsFn = getLayerSnapshotsForScreenshots({}, uid); - std::vector>> layers = getLayerSnapshotsFn(); - args.root = mLayerHierarchyBuilder.getHierarchy(); - args.parentCrop.reset(); - args.excludeLayerIds.clear(); - mLayerSnapshotBuilder.update(args); - return layers; - }; -} - -SurfaceFlinger::LifecycleUpdate SurfaceFlinger::flushLifecycleUpdates() { - LifecycleUpdate update; - ATRACE_NAME("TransactionHandler:flushTransactions"); - // Locking: - // 1. to prevent onHandleDestroyed from being called while the state lock is held, - // we must keep a copy of the transactions (specifically the composer - // states) around outside the scope of the lock. - // 2. Transactions and created layers do not share a lock. To prevent applying - // transactions with layers still in the createdLayer queue, flush the transactions - // before committing the created layers. - update.transactions = mTransactionHandler.flushTransactions(); - { - // TODO(b/238781169) lockless queue this and keep order. - std::scoped_lock lock(mCreatedLayersLock); - update.layerCreatedStates = std::move(mCreatedLayers); - mCreatedLayers.clear(); - update.newLayers = std::move(mNewLayers); - mNewLayers.clear(); - update.destroyedHandles = std::move(mDestroyedHandles); - mDestroyedHandles.clear(); - } - return update; -} - // gui::ISurfaceComposer binder::Status SurfaceComposerAIDL::bootFinished() { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 5b8038b173..97469f4253 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -71,9 +71,7 @@ #include "FlagManager.h" #include "FrontEnd/DisplayInfo.h" #include "FrontEnd/LayerCreationArgs.h" -#include "FrontEnd/LayerLifecycleManager.h" #include "FrontEnd/LayerSnapshot.h" -#include "FrontEnd/LayerSnapshotBuilder.h" #include "FrontEnd/TransactionHandler.h" #include "LayerVector.h" #include "Scheduler/RefreshRateSelector.h" @@ -451,26 +449,6 @@ private: FINISHED, }; - struct LayerCreatedState { - LayerCreatedState(const wp& layer, const wp& parent, bool addToRoot) - : layer(layer), initialParent(parent), addToRoot(addToRoot) {} - wp layer; - // Indicates the initial parent of the created layer, only used for creating layer in - // SurfaceFlinger. If nullptr, it may add the created layer into the current root layers. - wp initialParent; - // Indicates whether the layer getting created should be added at root if there's no parent - // and has permission ACCESS_SURFACE_FLINGER. If set to false and no parent, the layer will - // be added offscreen. - bool addToRoot; - }; - - struct LifecycleUpdate { - std::vector transactions; - std::vector layerCreatedStates; - std::vector> newLayers; - std::vector destroyedHandles; - }; - template >* = nullptr> static Dumper dumper(F&& dump) { using namespace std::placeholders; @@ -710,17 +688,6 @@ private: void updateLayerGeometry(); void updateLayerMetadataSnapshot(); - std::vector> moveSnapshotsToCompositionArgs( - compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly, - int64_t vsyncId); - void moveSnapshotsFromCompositionArgs(compositionengine::CompositionRefreshArgs& refreshArgs, - std::vector>& layers); - bool updateLayerSnapshotsLegacy(VsyncId vsyncId, LifecycleUpdate& update, - bool transactionsFlushed, bool& out) - REQUIRES(kMainThreadContext); - bool updateLayerSnapshots(VsyncId vsyncId, LifecycleUpdate& update, bool transactionsFlushed, - bool& out) REQUIRES(kMainThreadContext); - LifecycleUpdate flushLifecycleUpdates() REQUIRES(kMainThreadContext); void updateInputFlinger(); void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext); @@ -748,8 +715,6 @@ private: bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext); bool applyTransactions(std::vector&, VsyncId) REQUIRES(kMainThreadContext); - bool applyAndCommitDisplayTransactionStates(std::vector& transactions) - REQUIRES(kMainThreadContext); // Returns true if there is at least one transaction that needs to be flushed bool transactionFlushNeeded(); @@ -765,10 +730,7 @@ private: int64_t desiredPresentTime, bool isAutoTimestamp, int64_t postTime, uint32_t permissions, uint64_t transactionId) REQUIRES(mStateLock); - uint32_t updateLayerCallbacksAndStats(const FrameTimelineInfo&, ResolvedComposerState&, - int64_t desiredPresentTime, bool isAutoTimestamp, - int64_t postTime, uint32_t permissions, - uint64_t transactionId) REQUIRES(mStateLock); + uint32_t getTransactionFlags() const; // Sets the masked bits, and schedules a commit if needed. @@ -926,7 +888,7 @@ private: // mark a region of a layer stack dirty. this updates the dirty // region of all screens presenting this layer stack. - void invalidateLayerStack(const ui::LayerFilter& layerFilter, const Region& dirty); + void invalidateLayerStack(const sp& layer, const Region& dirty); ui::LayerFilter makeLayerFilterForDisplay(DisplayId displayId, ui::LayerStack layerStack) REQUIRES(mStateLock) { @@ -1184,7 +1146,6 @@ private: // Set if LayerMetadata has changed since the last LayerMetadata snapshot. bool mLayerMetadataSnapshotNeeded = false; - // TODO(b/238781169) validate these on composition // Tracks layers that have pending frames which are candidates for being // latched. std::unordered_set, SpHash> mLayersWithQueuedFrames; @@ -1359,11 +1320,23 @@ private: GUARDED_BY(mStateLock); mutable std::mutex mCreatedLayersLock; + struct LayerCreatedState { + LayerCreatedState(const wp& layer, const wp parent, bool addToRoot) + : layer(layer), initialParent(parent), addToRoot(addToRoot) {} + wp layer; + // Indicates the initial parent of the created layer, only used for creating layer in + // SurfaceFlinger. If nullptr, it may add the created layer into the current root layers. + wp initialParent; + // Indicates whether the layer getting created should be added at root if there's no parent + // and has permission ACCESS_SURFACE_FLINGER. If set to false and no parent, the layer will + // be added offscreen. + bool addToRoot; + }; // A temporay pool that store the created layers and will be added to current state in main // thread. std::vector mCreatedLayers GUARDED_BY(mCreatedLayersLock); - bool commitCreatedLayers(VsyncId, std::vector& createdLayers); + bool commitCreatedLayers(VsyncId); void handleLayerCreatedLocked(const LayerCreatedState&, VsyncId) REQUIRES(mStateLock); mutable std::mutex mMirrorDisplayLock; @@ -1385,11 +1358,6 @@ private: return hasDisplay( [](const auto& display) { return display.isRefreshRateOverlayEnabled(); }); } - std::function>>()> getLayerSnapshotsForScreenshots( - std::optional layerStack, uint32_t uid); - std::function>>()> getLayerSnapshotsForScreenshots( - uint32_t rootLayerId, uint32_t uid, std::unordered_set excludeLayerIds, - bool childrenOnly, const FloatRect& parentCrop); const sp mWindowInfosListenerInvoker; @@ -1402,18 +1370,6 @@ private: bool mPowerHintSessionEnabled; - bool mLayerLifecycleManagerEnabled = false; - bool mLegacyFrontEndEnabled = true; - - frontend::LayerLifecycleManager mLayerLifecycleManager; - frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}}; - frontend::LayerSnapshotBuilder mLayerSnapshotBuilder; - - std::vector mDestroyedHandles; - std::vector> mNewLayers; - // These classes do not store any client state but help with managing transaction callbacks - // and stats. - std::unordered_map> mLegacyLayers; struct { bool late = false; bool early = false; @@ -1421,7 +1377,6 @@ private: TransactionHandler mTransactionHandler; display::DisplayMap mFrontEndDisplayInfos; - bool mFrontEndDisplayInfosChanged = false; }; class SurfaceComposerAIDL : public gui::BnSurfaceComposer { diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp index c088e7b40c..11719c435e 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp @@ -148,7 +148,7 @@ void LayerFuzzer::invokeBufferStateLayer() { layer->fenceHasSignaled(); layer->onPreComposition(mFdp.ConsumeIntegral()); const std::vector> callbacks; - layer->setTransactionCompletedListeners(callbacks, mFdp.ConsumeBool()); + layer->setTransactionCompletedListeners(callbacks); std::shared_ptr texture = std::make_shared< renderengine::mock::FakeExternalTexture>(mFdp.ConsumeIntegral(), -- GitLab From eb489f69db265f6cb1aab63130750d0c12c99d94 Mon Sep 17 00:00:00 2001 From: liulijun Date: Mon, 17 Oct 2022 22:02:14 +0800 Subject: [PATCH 0836/1310] Add producerId so we know when the BBQ producer has been changed. If BBQ producer changes but the SC remains the same, the frame numbers for the SC will get reset. This causes issues if there's a barrier layer set because the barrier is waiting for a particular frame number before applying the transaction. Since the frame numbers have been reset, the barrier will be greater than the incoming frame numbers. The change adds a producerId to the buffer being sent so it can check if the producerId is older than what's currently set on the Layer. If there's a barriers set from the old producer, the buffer can be released and not applied and will stop SF from waiting indefinitely. Bug: 251971691 Test: Builds, hard to repro Signed-off-by: Liu Lijun Change-Id: If37171de4693a73f36f8de43e29c129b352eb55f --- libs/gui/BLASTBufferQueue.cpp | 14 ++++--- libs/gui/LayerState.cpp | 2 + libs/gui/SurfaceComposerClient.cpp | 3 +- libs/gui/include/gui/BLASTBufferQueue.h | 5 +++ libs/gui/include/gui/LayerState.h | 1 + libs/gui/include/gui/SurfaceComposerClient.h | 2 +- .../FrontEnd/TransactionHandler.h | 2 +- services/surfaceflinger/Layer.cpp | 18 ++++----- services/surfaceflinger/Layer.h | 10 +++++ services/surfaceflinger/SurfaceFlinger.cpp | 40 +++++++++++++------ services/surfaceflinger/TransactionState.h | 22 +++++++--- .../tests/ReleaseBufferCallback_test.cpp | 26 ++++++------ 12 files changed, 95 insertions(+), 50 deletions(-) diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index 66c0041965..9d82c143f5 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -20,6 +20,7 @@ #define ATRACE_TAG ATRACE_TAG_GRAPHICS //#define LOG_NDEBUG 0 +#include #include #include #include @@ -157,11 +158,11 @@ BLASTBufferQueue::BLASTBufferQueue(const std::string& name, bool updateDestinati GraphicBuffer::USAGE_HW_COMPOSER | GraphicBuffer::USAGE_HW_TEXTURE, 1, false, this); - static int32_t id = 0; - mName = name + "#" + std::to_string(id); - auto consumerName = mName + "(BLAST Consumer)" + std::to_string(id); - mQueuedBufferTrace = "QueuedBuffer - " + mName + "BLAST#" + std::to_string(id); - id++; + static std::atomic nextId = 0; + mProducerId = nextId++; + mName = name + "#" + std::to_string(mProducerId); + auto consumerName = mName + "(BLAST Consumer)" + std::to_string(mProducerId); + mQueuedBufferTrace = "QueuedBuffer - " + mName + "BLAST#" + std::to_string(mProducerId); mBufferItemConsumer->setName(String8(consumerName.c_str())); mBufferItemConsumer->setFrameAvailableListener(this); @@ -572,7 +573,8 @@ status_t BLASTBufferQueue::acquireNextBufferLocked( std::bind(releaseBufferCallbackThunk, wp(this) /* callbackContext */, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); sp fence = bufferItem.mFence ? new Fence(bufferItem.mFence->dup()) : Fence::NO_FENCE; - t->setBuffer(mSurfaceControl, buffer, fence, bufferItem.mFrameNumber, releaseBufferCallback); + t->setBuffer(mSurfaceControl, buffer, fence, bufferItem.mFrameNumber, mProducerId, + releaseBufferCallback); t->setDataspace(mSurfaceControl, static_cast(bufferItem.mDataSpace)); t->setHdrMetadata(mSurfaceControl, bufferItem.mHdrMetadata); t->setSurfaceDamageRegion(mSurfaceControl, bufferItem.mSurfaceDamage); diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 7772a65dae..a6276e500c 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -984,6 +984,7 @@ status_t BufferData::writeToParcel(Parcel* output) const { SAFE_PARCEL(output->writeUint64, cachedBuffer.id); SAFE_PARCEL(output->writeBool, hasBarrier); SAFE_PARCEL(output->writeUint64, barrierFrameNumber); + SAFE_PARCEL(output->writeUint32, producerId); return NO_ERROR; } @@ -1022,6 +1023,7 @@ status_t BufferData::readFromParcel(const Parcel* input) { SAFE_PARCEL(input->readBool, &hasBarrier); SAFE_PARCEL(input->readUint64, &barrierFrameNumber); + SAFE_PARCEL(input->readUint32, &producerId); return NO_ERROR; } diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index a34593825a..5088604396 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -1632,7 +1632,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffe SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffer( const sp& sc, const sp& buffer, const std::optional>& fence, const std::optional& optFrameNumber, - ReleaseBufferCallback callback) { + uint32_t producerId, ReleaseBufferCallback callback) { layer_state_t* s = getLayerState(sc); if (!s) { mStatus = BAD_INDEX; @@ -1651,6 +1651,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffe bufferData->buffer = buffer; uint64_t frameNumber = sc->resolveFrameNumber(optFrameNumber); bufferData->frameNumber = frameNumber; + bufferData->producerId = producerId; bufferData->flags |= BufferData::BufferDataChange::frameNumberChanged; if (fence) { bufferData->acquireFence = *fence; diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h index 8d07162f1b..b9e06473bf 100644 --- a/libs/gui/include/gui/BLASTBufferQueue.h +++ b/libs/gui/include/gui/BLASTBufferQueue.h @@ -162,6 +162,11 @@ private: int32_t mNumFrameAvailable GUARDED_BY(mMutex) = 0; int32_t mNumAcquired GUARDED_BY(mMutex) = 0; + // A value used to identify if a producer has been changed for the same SurfaceControl. + // This is needed to know when the frame number has been reset to make sure we don't + // latch stale buffers and that we don't wait on barriers from an old producer. + uint32_t mProducerId = 0; + // 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 mSubmitted diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 03a2582589..ddaf473855 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -111,6 +111,7 @@ public: uint64_t frameNumber = 0; bool hasBarrier = false; uint64_t barrierFrameNumber = 0; + uint32_t producerId = 0; // Listens to when the buffer is safe to be released. This is used for blast // layers only. The callback includes a release fence as well as the graphic diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 809ea5a30f..ffd5af100d 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -536,7 +536,7 @@ public: Transaction& setBuffer(const sp& sc, const sp& buffer, const std::optional>& fence = std::nullopt, const std::optional& frameNumber = std::nullopt, - ReleaseBufferCallback callback = nullptr); + uint32_t producerId = 0, ReleaseBufferCallback callback = nullptr); std::shared_ptr getAndClearBuffer(const sp& sc); /** diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h index a06b870549..7fc825eba3 100644 --- a/services/surfaceflinger/FrontEnd/TransactionHandler.h +++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h @@ -34,7 +34,7 @@ namespace surfaceflinger::frontend { class TransactionHandler { public: struct TransactionFlushState { - const TransactionState* transaction; + TransactionState* transaction; bool firstTransaction = true; nsecs_t queueProcessTime = 0; // Layer handles that have transactions with buffers that are ready to be applied. diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 66c2fb658f..8c484f0039 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -228,9 +228,7 @@ Layer::~Layer() { if (mBufferInfo.mBuffer != nullptr) { callReleaseBufferCallback(mDrawingState.releaseBufferListener, mBufferInfo.mBuffer->getBuffer(), mBufferInfo.mFrameNumber, - mBufferInfo.mFence, - mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate( - mOwnerUid)); + mBufferInfo.mFence); } if (!isClone()) { // The original layer and the clone layer share the same texture. Therefore, only one of @@ -2732,12 +2730,13 @@ void Layer::cloneDrawingState(const Layer* from) { void Layer::callReleaseBufferCallback(const sp& listener, const sp& buffer, uint64_t framenumber, - const sp& releaseFence, - uint32_t currentMaxAcquiredBufferCount) { + const sp& releaseFence) { if (!listener) { return; } ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64, getDebugName(), framenumber); + uint32_t currentMaxAcquiredBufferCount = + mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mOwnerUid); listener->onReleaseBuffer({buffer->getId(), framenumber}, releaseFence ? releaseFence : Fence::NO_FENCE, currentMaxAcquiredBufferCount); @@ -2988,9 +2987,7 @@ bool Layer::setBuffer(std::shared_ptr& buffer, // call any release buffer callbacks if set. callReleaseBufferCallback(mDrawingState.releaseBufferListener, mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber, - mDrawingState.acquireFence, - mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate( - mOwnerUid)); + mDrawingState.acquireFence); decrementPendingBufferCount(); if (mDrawingState.bufferSurfaceFrameTX != nullptr && mDrawingState.bufferSurfaceFrameTX->getPresentState() != PresentState::Presented) { @@ -3000,13 +2997,12 @@ bool Layer::setBuffer(std::shared_ptr& buffer, } else if (EARLY_RELEASE_ENABLED && mLastClientCompositionFence != nullptr) { callReleaseBufferCallback(mDrawingState.releaseBufferListener, mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber, - mLastClientCompositionFence, - mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate( - mOwnerUid)); + mLastClientCompositionFence); mLastClientCompositionFence = nullptr; } } + mDrawingState.producerId = bufferData.producerId; mDrawingState.frameNumber = frameNumber; mDrawingState.releaseBufferListener = bufferData.releaseBufferListener; mDrawingState.buffer = std::move(buffer); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 07c01d8e17..bf8cc7e36b 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -141,6 +141,8 @@ public: uint64_t frameNumber; ui::Transform transform; + + uint32_t producerId = 0; uint32_t bufferTransform; bool transformToDisplayInverse; Region transparentRegionHint; @@ -838,6 +840,10 @@ public: std::unordered_set& visited); bool willPresentCurrentTransaction() const; + void callReleaseBufferCallback(const sp& listener, + const sp& buffer, uint64_t framenumber, + const sp& releaseFence); + protected: // For unit tests friend class TestableSurfaceFlinger; @@ -1047,6 +1053,10 @@ private: const sp& releaseFence, uint32_t currentMaxAcquiredBufferCount); + // Returns true if the transformed buffer size does not match the layer size and we need + // to apply filtering. + bool bufferNeedsFiltering() const; + // Returns true if there is a valid color to fill. bool fillsColor() const; // Returns true if this layer has a blur value. diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 68ab776400..e74656e55c 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4058,16 +4058,30 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC sp layer = LayerHandle::getLayer(s.surface); const auto& transaction = *flushState.transaction; // check for barrier frames - if (s.bufferData->hasBarrier && - ((layer->getDrawingState().frameNumber) < s.bufferData->barrierFrameNumber)) { - const bool willApplyBarrierFrame = - flushState.bufferLayersReadyToPresent.contains(s.surface.get()) && - (flushState.bufferLayersReadyToPresent.get(s.surface.get()) >= - s.bufferData->barrierFrameNumber); - if (!willApplyBarrierFrame) { - ATRACE_NAME("NotReadyBarrier"); - ready = TransactionReadiness::NotReadyBarrier; - return false; + if (s.bufferData->hasBarrier) { + // The current producerId is already a newer producer than the buffer that has a + // barrier. This means the incoming buffer is older and we can release it here. We + // don't wait on the barrier since we know that's stale information. + if (layer->getDrawingState().producerId > s.bufferData->producerId) { + layer->callReleaseBufferCallback(s.bufferData->releaseBufferListener, + s.bufferData->buffer, s.bufferData->frameNumber, + s.bufferData->acquireFence); + // Delete the entire state at this point and not just release the buffer because + // everything associated with the Layer in this Transaction is now out of date. + ATRACE_NAME("DeleteStaleBuffer"); + return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL; + } + + if (layer->getDrawingState().frameNumber < s.bufferData->barrierFrameNumber) { + const bool willApplyBarrierFrame = + flushState.bufferLayersReadyToPresent.contains(s.surface.get()) && + ((flushState.bufferLayersReadyToPresent.get(s.surface.get()) >= + s.bufferData->barrierFrameNumber)); + if (!willApplyBarrierFrame) { + ATRACE_NAME("NotReadyBarrier"); + ready = TransactionReadiness::NotReadyBarrier; + return TraverseBuffersReturnValues::STOP_TRAVERSAL; + } } } @@ -4078,7 +4092,7 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC if (layer->backpressureEnabled() && hasPendingBuffer && transaction.isAutoTimestamp) { ATRACE_NAME("hasPendingBuffer"); ready = TransactionReadiness::NotReady; - return false; + return TraverseBuffersReturnValues::STOP_TRAVERSAL; } // check fence status @@ -4105,14 +4119,14 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC "Buffer processing hung up due to stuck " "fence. Indicates GPU hang"); } - return false; + return TraverseBuffersReturnValues::STOP_TRAVERSAL; } ready = enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer ? TransactionReadiness::ReadyUnsignaledSingle : TransactionReadiness::ReadyUnsignaled; } - return true; + return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL; }); ATRACE_INT("TransactionReadiness", static_cast(ready)); return ready; diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h index 5025c4935c..6c5a8b213d 100644 --- a/services/surfaceflinger/TransactionState.h +++ b/services/surfaceflinger/TransactionState.h @@ -27,6 +27,12 @@ namespace android { +enum TraverseBuffersReturnValues { + CONTINUE_TRAVERSAL, + STOP_TRAVERSAL, + DELETE_AND_CONTINUE_TRAVERSAL, +}; + // Extends the client side composer state by resolving buffer. class ResolvedComposerState : public ComposerState { public: @@ -75,12 +81,18 @@ struct TransactionState { } template - void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) const { - for (const auto& state : states) { - if (state.state.hasBufferChanges() && state.state.hasValidBuffer() && - state.state.surface) { - if (!visitor(state.state)) return; + void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) { + for (auto state = states.begin(); state != states.end();) { + if (state->state.hasBufferChanges() && state->state.hasValidBuffer() && + state->state.surface) { + int result = visitor(state->state); + if (result == STOP_TRAVERSAL) return; + if (result == DELETE_AND_CONTINUE_TRAVERSAL) { + state = states.erase(state); + continue; + } } + state++; } } diff --git a/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp b/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp index 16076eaac9..c23fb9bd23 100644 --- a/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp +++ b/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp @@ -85,7 +85,8 @@ public: sp fence, CallbackHelper& callback, const ReleaseCallbackId& id, ReleaseBufferCallbackHelper& releaseCallback) { Transaction t; - t.setBuffer(layer, buffer, fence, id.framenumber, releaseCallback.getCallback()); + t.setBuffer(layer, buffer, fence, id.framenumber, 0 /* producerId */, + releaseCallback.getCallback()); t.addTransactionCompletedCallback(callback.function, callback.getContext()); t.apply(); } @@ -301,7 +302,7 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_FrameDropping) { Transaction t; t.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); t.addTransactionCompletedCallback(transactionCallback.function, transactionCallback.getContext()); t.setDesiredPresentTime(time); @@ -317,7 +318,7 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_FrameDropping) { sp secondBuffer = getBuffer(); ReleaseCallbackId secondBufferCallbackId(secondBuffer->getId(), generateFrameNumber()); t.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); t.addTransactionCompletedCallback(transactionCallback.function, transactionCallback.getContext()); t.setDesiredPresentTime(time); @@ -362,7 +363,7 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_Merge_Different_Processes) { Transaction transaction1; transaction1.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); transaction1.addTransactionCompletedCallback(callback1.function, callback1.getContext()); // Set a different TransactionCompletedListener to mimic a second process @@ -397,14 +398,14 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_SetBuffer_OverwriteBuffers) { // Create transaction with a buffer. Transaction transaction; transaction.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); sp secondBuffer = getBuffer(); ReleaseCallbackId secondBufferCallbackId(secondBuffer->getId(), generateFrameNumber()); // Call setBuffer on the same transaction with a different buffer. transaction.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); ASSERT_NO_FATAL_FAILURE(waitForReleaseBufferCallback(*releaseCallback, firstBufferCallbackId)); } @@ -419,7 +420,7 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_Merge_Transactions_OverwriteBuffers) // Create transaction with a buffer. Transaction transaction1; transaction1.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); sp secondBuffer = getBuffer(); ReleaseCallbackId secondBufferCallbackId(secondBuffer->getId(), generateFrameNumber()); @@ -427,7 +428,7 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_Merge_Transactions_OverwriteBuffers) // Create a second transaction with a new buffer for the same layer. Transaction transaction2; transaction2.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); // merge transaction1 into transaction2 so ensure we get a proper buffer release callback. transaction1.merge(std::move(transaction2)); @@ -450,7 +451,7 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_MergeBuffers_Different_Processes) { Transaction transaction1; transaction1.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); // Sent a second buffer to allow the first buffer to get released. sp secondBuffer = getBuffer(); @@ -458,7 +459,7 @@ TEST_F(ReleaseBufferCallbackTest, DISABLED_MergeBuffers_Different_Processes) { Transaction transaction2; transaction2.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); // Set a different TransactionCompletedListener to mimic a second process TransactionCompletedListener::setInstance(secondCompletedListener); @@ -479,10 +480,11 @@ TEST_F(ReleaseBufferCallbackTest, SetBuffer_OverwriteBuffersWithNull) { // Create transaction with a buffer. Transaction transaction; transaction.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber, - releaseCallback->getCallback()); + 0 /* producerId */, releaseCallback->getCallback()); // Call setBuffer on the same transaction with a null buffer. - transaction.setBuffer(layer, nullptr, std::nullopt, 0, releaseCallback->getCallback()); + transaction.setBuffer(layer, nullptr, std::nullopt, 0, 0 /* producerId */, + releaseCallback->getCallback()); ASSERT_NO_FATAL_FAILURE(waitForReleaseBufferCallback(*releaseCallback, firstBufferCallbackId)); } -- GitLab From 1088bbf9882d778079eaa7465c1dec2b08848f1a Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Thu, 9 Feb 2023 21:45:08 +0000 Subject: [PATCH 0837/1310] Revert "Cache and uncache buffers in the same transaction" This reverts commit 75ce1ea078444100db9f9eef06a9ef35ad8a0446. Reason for revert: b/266481887 Change-Id: I147947d55672c8f14d9bbe51df54eadfc43edeb2 --- libs/gui/ISurfaceComposer.cpp | 23 +++---- libs/gui/SurfaceComposerClient.cpp | 62 +++++-------------- libs/gui/include/gui/ISurfaceComposer.h | 5 +- libs/gui/include/gui/SurfaceComposerClient.h | 1 - libs/gui/tests/Surface_test.cpp | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 49 +++++---------- services/surfaceflinger/SurfaceFlinger.h | 21 ++++--- services/surfaceflinger/TransactionState.h | 6 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 15 +++-- .../tests/unittests/TestableSurfaceFlinger.h | 5 +- .../unittests/TransactionApplicationTest.cpp | 19 +++--- 11 files changed, 78 insertions(+), 130 deletions(-) diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index cefb9a71d6..a77ca04943 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -63,8 +63,7 @@ public: Vector& state, const Vector& displays, uint32_t flags, const sp& applyToken, const InputWindowCommands& commands, int64_t desiredPresentTime, - bool isAutoTimestamp, - const std::vector& uncacheBuffers, + bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, const std::vector& listenerCallbacks, uint64_t transactionId) override { @@ -88,11 +87,8 @@ public: SAFE_PARCEL(commands.write, data); SAFE_PARCEL(data.writeInt64, desiredPresentTime); SAFE_PARCEL(data.writeBool, isAutoTimestamp); - SAFE_PARCEL(data.writeUint32, static_cast(uncacheBuffers.size())); - for (const client_cache_t& uncacheBuffer : uncacheBuffers) { - SAFE_PARCEL(data.writeStrongBinder, uncacheBuffer.token.promote()); - SAFE_PARCEL(data.writeUint64, uncacheBuffer.id); - } + SAFE_PARCEL(data.writeStrongBinder, uncacheBuffer.token.promote()); + SAFE_PARCEL(data.writeUint64, uncacheBuffer.id); SAFE_PARCEL(data.writeBool, hasListenerCallbacks); SAFE_PARCEL(data.writeVectorSize, listenerCallbacks); @@ -162,14 +158,11 @@ status_t BnSurfaceComposer::onTransact( SAFE_PARCEL(data.readInt64, &desiredPresentTime); SAFE_PARCEL(data.readBool, &isAutoTimestamp); - SAFE_PARCEL_READ_SIZE(data.readUint32, &count, data.dataSize()); - std::vector uncacheBuffers(count); + client_cache_t uncachedBuffer; sp tmpBinder; - for (size_t i = 0; i < count; i++) { - SAFE_PARCEL(data.readNullableStrongBinder, &tmpBinder); - uncacheBuffers[i].token = tmpBinder; - SAFE_PARCEL(data.readUint64, &uncacheBuffers[i].id); - } + SAFE_PARCEL(data.readNullableStrongBinder, &tmpBinder); + uncachedBuffer.token = tmpBinder; + SAFE_PARCEL(data.readUint64, &uncachedBuffer.id); bool hasListenerCallbacks = false; SAFE_PARCEL(data.readBool, &hasListenerCallbacks); @@ -189,7 +182,7 @@ status_t BnSurfaceComposer::onTransact( return setTransactionState(frameTimelineInfo, state, displays, stateFlags, applyToken, inputWindowCommands, desiredPresentTime, isAutoTimestamp, - uncacheBuffers, hasListenerCallbacks, listenerCallbacks, + uncachedBuffer, hasListenerCallbacks, listenerCallbacks, transactionId); } default: { diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 21a7f7817a..92125ead1f 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -565,13 +565,11 @@ public: return NO_ERROR; } - uint64_t cache(const sp& buffer, - std::optional& outUncacheBuffer) { + uint64_t cache(const sp& buffer) { std::lock_guard lock(mMutex); if (mBuffers.size() >= BUFFER_CACHE_MAX_SIZE) { - outUncacheBuffer = findLeastRecentlyUsedBuffer(); - mBuffers.erase(outUncacheBuffer->id); + evictLeastRecentlyUsedBuffer(); } buffer->addDeathCallback(removeDeadBufferCallback, nullptr); @@ -582,13 +580,16 @@ public: void uncache(uint64_t cacheId) { std::lock_guard lock(mMutex); - if (mBuffers.erase(cacheId)) { - SurfaceComposerClient::doUncacheBufferTransaction(cacheId); - } + uncacheLocked(cacheId); + } + + void uncacheLocked(uint64_t cacheId) REQUIRES(mMutex) { + mBuffers.erase(cacheId); + SurfaceComposerClient::doUncacheBufferTransaction(cacheId); } private: - client_cache_t findLeastRecentlyUsedBuffer() REQUIRES(mMutex) { + void evictLeastRecentlyUsedBuffer() REQUIRES(mMutex) { auto itr = mBuffers.begin(); uint64_t minCounter = itr->second; auto minBuffer = itr; @@ -602,8 +603,7 @@ private: } itr++; } - - return {.token = getToken(), .id = minBuffer->first}; + uncacheLocked(minBuffer->first); } uint64_t getCounter() REQUIRES(mMutex) { @@ -741,18 +741,6 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel InputWindowCommands inputWindowCommands; inputWindowCommands.read(*parcel); - count = static_cast(parcel->readUint32()); - if (count > parcel->dataSize()) { - return BAD_VALUE; - } - std::vector uncacheBuffers(count); - for (size_t i = 0; i < count; i++) { - sp tmpBinder; - SAFE_PARCEL(parcel->readStrongBinder, &tmpBinder); - uncacheBuffers[i].token = tmpBinder; - SAFE_PARCEL(parcel->readUint64, &uncacheBuffers[i].id); - } - // Parsing was successful. Update the object. mId = transactionId; mTransactionNestCount = transactionNestCount; @@ -767,7 +755,6 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel mComposerStates = composerStates; mInputWindowCommands = inputWindowCommands; mApplyToken = applyToken; - mUncacheBuffers = std::move(uncacheBuffers); return NO_ERROR; } @@ -819,13 +806,6 @@ status_t SurfaceComposerClient::Transaction::writeToParcel(Parcel* parcel) const } mInputWindowCommands.write(*parcel); - - SAFE_PARCEL(parcel->writeUint32, static_cast(mUncacheBuffers.size())); - for (const client_cache_t& uncacheBuffer : mUncacheBuffers) { - SAFE_PARCEL(parcel->writeStrongBinder, uncacheBuffer.token.promote()); - SAFE_PARCEL(parcel->writeUint64, uncacheBuffer.id); - } - return NO_ERROR; } @@ -893,10 +873,6 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::merge(Tr } } - for (const auto& cacheId : other.mUncacheBuffers) { - mUncacheBuffers.push_back(cacheId); - } - mInputWindowCommands.merge(other.mInputWindowCommands); mMayContainBuffer |= other.mMayContainBuffer; @@ -915,7 +891,6 @@ void SurfaceComposerClient::Transaction::clear() { mDisplayStates.clear(); mListenerCallbacks.clear(); mInputWindowCommands.clear(); - mUncacheBuffers.clear(); mMayContainBuffer = false; mTransactionNestCount = 0; mAnimation = false; @@ -938,10 +913,10 @@ void SurfaceComposerClient::doUncacheBufferTransaction(uint64_t cacheId) { uncacheBuffer.token = BufferCache::getInstance().getToken(); uncacheBuffer.id = cacheId; Vector composerStates; - status_t status = sf->setTransactionState(FrameTimelineInfo{}, composerStates, {}, - ISurfaceComposer::eOneWay, - Transaction::getDefaultApplyToken(), {}, systemTime(), - true, {uncacheBuffer}, false, {}, generateId()); + status_t status = + sf->setTransactionState(FrameTimelineInfo{}, composerStates, {}, + ISurfaceComposer::eOneWay, Transaction::getDefaultApplyToken(), + {}, systemTime(), true, uncacheBuffer, false, {}, generateId()); if (status != NO_ERROR) { ALOGE_AND_TRACE("SurfaceComposerClient::doUncacheBufferTransaction - %s", strerror(-status)); @@ -979,11 +954,7 @@ void SurfaceComposerClient::Transaction::cacheBuffers() { s->bufferData->buffer = nullptr; } else { // Cache-miss. Include the buffer and send the new cacheId. - std::optional uncacheBuffer; - cacheId = BufferCache::getInstance().cache(s->bufferData->buffer, uncacheBuffer); - if (uncacheBuffer) { - mUncacheBuffers.push_back(*uncacheBuffer); - } + cacheId = BufferCache::getInstance().cache(s->bufferData->buffer); } s->bufferData->flags |= BufferData::BufferDataChange::cachedBufferChanged; s->bufferData->cachedBuffer.token = BufferCache::getInstance().getToken(); @@ -1116,7 +1087,8 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay sp sf(ComposerService::getComposerService()); sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken, mInputWindowCommands, mDesiredPresentTime, mIsAutoTimestamp, - mUncacheBuffers, hasListenerCallbacks, listenerCallbacks, mId); + {} /*uncacheBuffer - only set in doUncacheBufferTransaction*/, + hasListenerCallbacks, listenerCallbacks, mId); mId = generateId(); // Clear the current states and flags diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index ae56f9fdb5..045cc2a184 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -113,9 +113,8 @@ public: const FrameTimelineInfo& frameTimelineInfo, Vector& state, const Vector& displays, uint32_t flags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, - bool isAutoTimestamp, const std::vector& uncacheBuffer, - bool hasListenerCallbacks, const std::vector& listenerCallbacks, - uint64_t transactionId) = 0; + bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, + const std::vector& listenerCallbacks, uint64_t transactionId) = 0; }; // ---------------------------------------------------------------------------- diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 0e51dcf4d4..2458a40ce0 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -402,7 +402,6 @@ public: SortedVector mDisplayStates; std::unordered_map, CallbackInfo, TCLHash> mListenerCallbacks; - std::vector mUncacheBuffers; uint64_t mId; diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 32d60cd5bd..30148042fb 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -701,7 +701,7 @@ public: const sp& /*applyToken*/, const InputWindowCommands& /*inputWindowCommands*/, int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/, - const std::vector& /*cachedBuffer*/, + const client_cache_t& /*cachedBuffer*/, bool /*hasListenerCallbacks*/, const std::vector& /*listenerCallbacks*/, uint64_t /*transactionId*/) override { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index d63e2812e2..40de4d675d 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3973,7 +3973,7 @@ bool SurfaceFlinger::applyTransactions(std::vector& transactio transaction.displays, transaction.flags, transaction.inputWindowCommands, transaction.desiredPresentTime, transaction.isAutoTimestamp, - std::move(transaction.uncacheBufferIds), transaction.postTime, + transaction.buffer, transaction.postTime, transaction.permissions, transaction.hasListenerCallbacks, transaction.listenerCallbacks, transaction.originPid, transaction.originUid, transaction.id); @@ -4061,9 +4061,8 @@ status_t SurfaceFlinger::setTransactionState( const FrameTimelineInfo& frameTimelineInfo, Vector& states, const Vector& displays, uint32_t flags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, - bool isAutoTimestamp, const std::vector& uncacheBuffers, - bool hasListenerCallbacks, const std::vector& listenerCallbacks, - uint64_t transactionId) { + bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, + const std::vector& listenerCallbacks, uint64_t transactionId) { ATRACE_CALL(); uint32_t permissions = @@ -4097,15 +4096,6 @@ status_t SurfaceFlinger::setTransactionState( const int originPid = ipc->getCallingPid(); const int originUid = ipc->getCallingUid(); - std::vector uncacheBufferIds; - uncacheBufferIds.reserve(uncacheBuffers.size()); - for (const auto& uncacheBuffer : uncacheBuffers) { - sp buffer = ClientCache::getInstance().erase(uncacheBuffer); - if (buffer != nullptr) { - uncacheBufferIds.push_back(buffer->getId()); - } - } - std::vector resolvedStates; resolvedStates.reserve(states.size()); for (auto& state : states) { @@ -4123,22 +4113,14 @@ status_t SurfaceFlinger::setTransactionState( } } - TransactionState state{frameTimelineInfo, - resolvedStates, - displays, - flags, - applyToken, - inputWindowCommands, - desiredPresentTime, - isAutoTimestamp, - std::move(uncacheBufferIds), - postTime, - permissions, - hasListenerCallbacks, - listenerCallbacks, - originPid, - originUid, - transactionId}; + TransactionState state{frameTimelineInfo, resolvedStates, + displays, flags, + applyToken, inputWindowCommands, + desiredPresentTime, isAutoTimestamp, + uncacheBuffer, postTime, + permissions, hasListenerCallbacks, + listenerCallbacks, originPid, + originUid, transactionId}; if (mTransactionTracing) { mTransactionTracing->addQueuedTransaction(state); @@ -4162,7 +4144,7 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin Vector& displays, uint32_t flags, const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime, bool isAutoTimestamp, - const std::vector& uncacheBufferIds, + const client_cache_t& uncacheBuffer, const int64_t postTime, uint32_t permissions, bool hasListenerCallbacks, const std::vector& listenerCallbacks, @@ -4203,8 +4185,11 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin ALOGE("Only privileged callers are allowed to send input commands."); } - for (uint64_t uncacheBufferId : uncacheBufferIds) { - mBufferIdsToUncache.push_back(uncacheBufferId); + if (uncacheBuffer.isValid()) { + sp buffer = ClientCache::getInstance().erase(uncacheBuffer); + if (buffer != nullptr) { + mBufferIdsToUncache.push_back(buffer->getId()); + } } // If a synchronous transaction is explicitly requested without any changes, force a transaction diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 9245399e0d..5457be81a5 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -501,8 +501,7 @@ private: uint32_t flags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, - const std::vector& uncacheBuffers, - bool hasListenerCallbacks, + const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, const std::vector& listenerCallbacks, uint64_t transactionId) override; void bootFinished(); @@ -715,14 +714,16 @@ private: /* * Transactions */ - bool applyTransactionState( - const FrameTimelineInfo& info, std::vector& state, - Vector& displays, uint32_t flags, - const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime, - bool isAutoTimestamp, const std::vector& uncacheBufferIds, - const int64_t postTime, uint32_t permissions, bool hasListenerCallbacks, - const std::vector& listenerCallbacks, int originPid, int originUid, - uint64_t transactionId) REQUIRES(mStateLock); + bool applyTransactionState(const FrameTimelineInfo& info, + std::vector& state, + Vector& displays, uint32_t flags, + const InputWindowCommands& inputWindowCommands, + const int64_t desiredPresentTime, bool isAutoTimestamp, + const client_cache_t& uncacheBuffer, const int64_t postTime, + uint32_t permissions, bool hasListenerCallbacks, + const std::vector& listenerCallbacks, + int originPid, int originUid, uint64_t transactionId) + REQUIRES(mStateLock); // Flush pending transactions that were presented after desiredPresentTime. bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext); // Returns true if there is at least one transaction that needs to be flushed diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h index 5025c4935c..366b09d68b 100644 --- a/services/surfaceflinger/TransactionState.h +++ b/services/surfaceflinger/TransactionState.h @@ -43,7 +43,7 @@ struct TransactionState { const Vector& displayStates, uint32_t transactionFlags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, - std::vector uncacheBufferIds, int64_t postTime, uint32_t permissions, + const client_cache_t& uncacheBuffer, int64_t postTime, uint32_t permissions, bool hasListenerCallbacks, std::vector listenerCallbacks, int originPid, int originUid, uint64_t transactionId) : frameTimelineInfo(frameTimelineInfo), @@ -54,7 +54,7 @@ struct TransactionState { inputWindowCommands(inputWindowCommands), desiredPresentTime(desiredPresentTime), isAutoTimestamp(isAutoTimestamp), - uncacheBufferIds(std::move(uncacheBufferIds)), + buffer(uncacheBuffer), postTime(postTime), permissions(permissions), hasListenerCallbacks(hasListenerCallbacks), @@ -109,7 +109,7 @@ struct TransactionState { InputWindowCommands inputWindowCommands; int64_t desiredPresentTime; bool isAutoTimestamp; - std::vector uncacheBufferIds; + client_cache_t buffer; int64_t postTime; uint32_t permissions; bool hasListenerCallbacks; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index c22d78b86e..81ca659915 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -730,18 +730,17 @@ public: return mFlinger->mTransactionHandler.mPendingTransactionQueues; } - auto setTransactionState(const FrameTimelineInfo& frameTimelineInfo, - Vector& states, const Vector& displays, - uint32_t flags, const sp& applyToken, - const InputWindowCommands& inputWindowCommands, + auto setTransactionState(const FrameTimelineInfo &frameTimelineInfo, + Vector &states, const Vector &displays, + uint32_t flags, const sp &applyToken, + const InputWindowCommands &inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, - const std::vector& uncacheBuffers, - bool hasListenerCallbacks, - std::vector& listenerCallbacks, + const client_cache_t &uncacheBuffer, bool hasListenerCallbacks, + std::vector &listenerCallbacks, uint64_t transactionId) { return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken, inputWindowCommands, desiredPresentTime, - isAutoTimestamp, uncacheBuffers, hasListenerCallbacks, + isAutoTimestamp, uncacheBuffer, hasListenerCallbacks, listenerCallbacks, transactionId); } diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 72e0c7be16..584d52ca02 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -439,13 +439,12 @@ public: uint32_t flags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, - const std::vector& uncacheBuffers, - bool hasListenerCallbacks, + const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, std::vector& listenerCallbacks, uint64_t transactionId) { return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken, inputWindowCommands, desiredPresentTime, - isAutoTimestamp, uncacheBuffers, hasListenerCallbacks, + isAutoTimestamp, uncacheBuffer, hasListenerCallbacks, listenerCallbacks, transactionId); } diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index a28d1cd415..d84698f279 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -102,7 +102,7 @@ public: int64_t desiredPresentTime = 0; bool isAutoTimestamp = true; FrameTimelineInfo frameTimelineInfo; - std::vector uncacheBuffers; + client_cache_t uncacheBuffer; uint64_t id = static_cast(-1); static_assert(0xffffffffffffffff == static_cast(-1)); }; @@ -138,7 +138,7 @@ public: transaction.displays, transaction.flags, transaction.applyToken, transaction.inputWindowCommands, transaction.desiredPresentTime, transaction.isAutoTimestamp, - transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks, + transaction.uncacheBuffer, mHasListenerCallbacks, mCallbacks, transaction.id); // If transaction is synchronous, SF applyTransactionState should time out (5s) wating for @@ -165,7 +165,7 @@ public: transaction.displays, transaction.flags, transaction.applyToken, transaction.inputWindowCommands, transaction.desiredPresentTime, transaction.isAutoTimestamp, - transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks, + transaction.uncacheBuffer, mHasListenerCallbacks, mCallbacks, transaction.id); nsecs_t returnedTime = systemTime(); @@ -196,7 +196,7 @@ public: transactionA.displays, transactionA.flags, transactionA.applyToken, transactionA.inputWindowCommands, transactionA.desiredPresentTime, transactionA.isAutoTimestamp, - transactionA.uncacheBuffers, mHasListenerCallbacks, mCallbacks, + transactionA.uncacheBuffer, mHasListenerCallbacks, mCallbacks, transactionA.id); // This thread should not have been blocked by the above transaction @@ -211,7 +211,7 @@ public: transactionB.displays, transactionB.flags, transactionB.applyToken, transactionB.inputWindowCommands, transactionB.desiredPresentTime, transactionB.isAutoTimestamp, - transactionB.uncacheBuffers, mHasListenerCallbacks, mCallbacks, + transactionB.uncacheBuffer, mHasListenerCallbacks, mCallbacks, transactionB.id); // this thread should have been blocked by the above transaction @@ -243,7 +243,7 @@ TEST_F(TransactionApplicationTest, AddToPendingQueue) { mFlinger.setTransactionState(transactionA.frameTimelineInfo, transactionA.states, transactionA.displays, transactionA.flags, transactionA.applyToken, transactionA.inputWindowCommands, transactionA.desiredPresentTime, - transactionA.isAutoTimestamp, transactionA.uncacheBuffers, + transactionA.isAutoTimestamp, transactionA.uncacheBuffer, mHasListenerCallbacks, mCallbacks, transactionA.id); auto& transactionQueue = mFlinger.getTransactionQueue(); @@ -263,7 +263,7 @@ TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) { mFlinger.setTransactionState(transactionA.frameTimelineInfo, transactionA.states, transactionA.displays, transactionA.flags, transactionA.applyToken, transactionA.inputWindowCommands, transactionA.desiredPresentTime, - transactionA.isAutoTimestamp, transactionA.uncacheBuffers, + transactionA.isAutoTimestamp, transactionA.uncacheBuffer, mHasListenerCallbacks, mCallbacks, transactionA.id); auto& transactionQueue = mFlinger.getTransactionQueue(); @@ -277,7 +277,7 @@ TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) { mFlinger.setTransactionState(empty.frameTimelineInfo, empty.states, empty.displays, empty.flags, empty.applyToken, empty.inputWindowCommands, empty.desiredPresentTime, empty.isAutoTimestamp, - empty.uncacheBuffers, mHasListenerCallbacks, mCallbacks, empty.id); + empty.uncacheBuffer, mHasListenerCallbacks, mCallbacks, empty.id); // flush transaction queue should flush as desiredPresentTime has // passed @@ -374,7 +374,8 @@ public: transaction.applyToken, transaction.inputWindowCommands, transaction.desiredPresentTime, - transaction.isAutoTimestamp, {}, systemTime(), 0, + transaction.isAutoTimestamp, + transaction.uncacheBuffer, systemTime(), 0, mHasListenerCallbacks, mCallbacks, getpid(), static_cast(getuid()), transaction.id); mFlinger.setTransactionStateInternal(transactionState); -- GitLab From b84158f023804fd31796d7970a86716f28e160dc Mon Sep 17 00:00:00 2001 From: Devin Moore Date: Thu, 9 Feb 2023 21:59:37 +0000 Subject: [PATCH 0838/1310] Don't throw an error when token is 0 The HIDL implementation uses the 0 as an "OK" value in the Result type, so we shouldn't be throwing an exception in this case for AIDL. Test: na Bug: 268129769 Change-Id: I2c8d37f289a7006cdd8d7be5ac87911e691517dd --- services/sensorservice/aidl/DirectReportChannel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/sensorservice/aidl/DirectReportChannel.cpp b/services/sensorservice/aidl/DirectReportChannel.cpp index cab53c17f5..9ef4ca8d08 100644 --- a/services/sensorservice/aidl/DirectReportChannel.cpp +++ b/services/sensorservice/aidl/DirectReportChannel.cpp @@ -32,7 +32,7 @@ ndk::ScopedAStatus DirectReportChannel::configure( int32_t sensorHandle, ::aidl::android::hardware::sensors::ISensors::RateLevel rate, int32_t* _aidl_return) { int token = mManager.configureDirectChannel(mId, sensorHandle, static_cast(rate)); - if (token <= 0) { + if (token < 0) { return ndk::ScopedAStatus::fromServiceSpecificError(token); } *_aidl_return = token; -- GitLab From 12bfe6b7c3aabdb1ab522d26a56520d70fb1a4d2 Mon Sep 17 00:00:00 2001 From: ramindani Date: Fri, 3 Feb 2023 13:29:19 -0800 Subject: [PATCH 0839/1310] [SF] Adds Refresh Rate Indicator HWC callback handlers Adds onRefreshRateChangedDebug handling Implementation details will follow in the upcoming CLS. Test: Device boots BUG: 202734676 Change-Id: I7e4c1f43712661627de02199ba900d520fa24dc3 --- services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp | 6 ++++++ services/surfaceflinger/DisplayHardware/HWC2.h | 4 ++++ services/surfaceflinger/SurfaceFlinger.cpp | 4 ++++ services/surfaceflinger/SurfaceFlinger.h | 3 +++ .../fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h | 2 ++ services/surfaceflinger/tests/unittests/HWComposerTest.cpp | 2 ++ 6 files changed, 21 insertions(+) diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp index 9470552e41..ba9aed8786 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp @@ -208,6 +208,12 @@ public: return ::ndk::ScopedAStatus::ok(); } + ::ndk::ScopedAStatus onRefreshRateChangedDebug( + const RefreshRateChangedDebugData& refreshRateChangedDebugData) override { + mCallback.onRefreshRateChangedDebug(refreshRateChangedDebugData); + return ::ndk::ScopedAStatus::ok(); + } + private: HWC2::ComposerCallback& mCallback; }; diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h index c1c7070a2d..23dd3e5016 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.h +++ b/services/surfaceflinger/DisplayHardware/HWC2.h @@ -44,6 +44,7 @@ #include #include #include +#include namespace android { @@ -63,6 +64,8 @@ class Layer; namespace hal = android::hardware::graphics::composer::hal; +using aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData; + // Implement this interface to receive hardware composer events. // // These callback functions will generally be called on a hwbinder thread, but @@ -77,6 +80,7 @@ struct ComposerCallback { const hal::VsyncPeriodChangeTimeline&) = 0; virtual void onComposerHalSeamlessPossible(hal::HWDisplayId) = 0; virtual void onComposerHalVsyncIdle(hal::HWDisplayId) = 0; + virtual void onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) = 0; protected: ~ComposerCallback() = default; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 169e101b2f..37175acb06 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2085,6 +2085,10 @@ void SurfaceFlinger::onComposerHalVsyncIdle(hal::HWDisplayId) { mScheduler->forceNextResync(); } +void SurfaceFlinger::onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) { + // TODO(b/202734676) update refresh rate value on the RefreshRateOverlay +} + void SurfaceFlinger::setVsyncEnabled(bool enabled) { ATRACE_CALL(); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 5b8038b173..335fb2993a 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -104,6 +104,7 @@ #include #include +#include #include "Client.h" using namespace android::surfaceflinger; @@ -127,6 +128,7 @@ class FrameTracer; class ScreenCapturer; class WindowInfosListenerInvoker; +using ::aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData; using frontend::TransactionHandler; using gui::CaptureArgs; using gui::DisplayCaptureArgs; @@ -630,6 +632,7 @@ private: const hal::VsyncPeriodChangeTimeline&) override; void onComposerHalSeamlessPossible(hal::HWDisplayId) override; void onComposerHalVsyncIdle(hal::HWDisplayId) override; + void onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) override; // ICompositor overrides: void configure() override; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h index 6a6e3db733..1a951b34ac 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h @@ -41,6 +41,7 @@ class SurfaceComposerClient; namespace android::hardware::graphics::composer::hal { +using aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData; using ::android::hardware::Return; using ::android::hardware::Void; using ::android::HWC2::ComposerCallback; @@ -99,6 +100,7 @@ struct TestHWC2ComposerCallback : public HWC2::ComposerCallback { void onComposerHalVsyncPeriodTimingChanged(HWDisplayId, const VsyncPeriodChangeTimeline&) {} void onComposerHalSeamlessPossible(HWDisplayId) {} void onComposerHalVsyncIdle(HWDisplayId) {} + void onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) {} }; } // namespace android::hardware::graphics::composer::hal diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp index afbc57add8..d77e9c7aa7 100644 --- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp +++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp @@ -52,6 +52,7 @@ namespace aidl = aidl::android::hardware::graphics::composer3; using Hwc2::Config; +using ::aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData; using ::testing::_; using ::testing::DoAll; using ::testing::ElementsAreArray; @@ -127,6 +128,7 @@ struct MockHWC2ComposerCallback final : StrictMock { void(hal::HWDisplayId, const hal::VsyncPeriodChangeTimeline&)); MOCK_METHOD1(onComposerHalSeamlessPossible, void(hal::HWDisplayId)); MOCK_METHOD1(onComposerHalVsyncIdle, void(hal::HWDisplayId)); + MOCK_METHOD(void, onRefreshRateChangedDebug, (const RefreshRateChangedDebugData&), (override)); }; struct HWComposerSetCallbackTest : HWComposerTest { -- GitLab From c1945cb782af21970f097805383ca723021f400f Mon Sep 17 00:00:00 2001 From: ramindani Date: Fri, 3 Feb 2023 13:28:15 -0800 Subject: [PATCH 0840/1310] Adds Composition REFRESH_RATE_INDICATOR Test: Device boots BUG: 202734676 Change-Id: Ibe1943e9ee8eb6b3f9968c097095ff10c609f479 --- services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp | 2 ++ .../CompositionEngine/src/planner/Predictor.cpp | 4 ++++ services/surfaceflinger/DisplayHardware/Hal.h | 2 ++ 3 files changed, 8 insertions(+) diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp index 6b69ce7941..1b86cd376a 100644 --- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp +++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp @@ -585,6 +585,7 @@ void OutputLayer::writeOutputIndependentPerFrameStateToHWC( case Composition::CURSOR: case Composition::DEVICE: case Composition::DISPLAY_DECORATION: + case Composition::REFRESH_RATE_INDICATOR: writeBufferStateToHWC(hwcLayer, outputIndependentState, skipLayer); break; case Composition::INVALID: @@ -780,6 +781,7 @@ void OutputLayer::detectDisallowedCompositionTypeChange(Composition from, Compos case Composition::CURSOR: case Composition::SIDEBAND: case Composition::DISPLAY_DECORATION: + case Composition::REFRESH_RATE_INDICATOR: result = (to == Composition::CLIENT || to == Composition::DEVICE); break; } diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp index 2fc029fdc6..6064126099 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp @@ -151,6 +151,10 @@ std::string to_string(const Plan& plan) { // A for "Alpha", since the decoration is an alpha layer. result.append("A"); break; + case aidl::android::hardware::graphics::composer3::Composition::REFRESH_RATE_INDICATOR: + // R for "Refresh", since the layer is Refresh rate overlay. + result.append("R"); + break; } } return result; diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h index 537d5450ad..bf3089f040 100644 --- a/services/surfaceflinger/DisplayHardware/Hal.h +++ b/services/surfaceflinger/DisplayHardware/Hal.h @@ -113,6 +113,8 @@ inline std::string to_string( return "Sideband"; case aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION: return "DisplayDecoration"; + case aidl::android::hardware::graphics::composer3::Composition::REFRESH_RATE_INDICATOR: + return "RefreshRateIndicator"; default: return "Unknown"; } -- GitLab From 9775c6fb8b3d5496147e6e4a7ce0b20d51e344f2 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Thu, 9 Feb 2023 23:28:10 +0000 Subject: [PATCH 0841/1310] Run all SurfaceFlinger_test in presubmit Bug: 242240488 Test: presubmit Change-Id: I163dcc38de8892269eaf45cca5b7903944b786da --- TEST_MAPPING | 106 +-------------------------------------------------- 1 file changed, 2 insertions(+), 104 deletions(-) diff --git a/TEST_MAPPING b/TEST_MAPPING index f54f13216f..cd8f3cdcc2 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -4,58 +4,7 @@ "name": "SurfaceFlinger_test", "options": [ { - "include-filter": "*CredentialsTest.*" - }, - { - "include-filter": "*SurfaceFlingerStress.*" - }, - { - "include-filter": "*SurfaceInterceptorTest.*" - }, - { - "include-filter": "*LayerTransactionTest.*" - }, - { - "include-filter": "*LayerTypeTransactionTest.*" - }, - { - "include-filter": "*LayerUpdateTest.*" - }, - { - "include-filter": "*GeometryLatchingTest.*" - }, - { - "include-filter": "*CropLatchingTest.*" - }, - { - "include-filter": "*ChildLayerTest.*" - }, - { - "include-filter": "*ScreenCaptureTest.*" - }, - { - "include-filter": "*ScreenCaptureChildOnlyTest.*" - }, - { - "include-filter": "*DereferenceSurfaceControlTest.*" - }, - { - "include-filter": "*BoundlessLayerTest.*" - }, - { - "include-filter": "*MultiDisplayLayerBoundsTest.*" - }, - { - "include-filter": "*InvalidHandleTest.*" - }, - { - "include-filter": "*VirtualDisplayTest.*" - }, - { - "include-filter": "*RelativeZTest.*" - }, - { - "include-filter": "*RefreshRateOverlayTest.*" + "include-filter": "*" }, { "exclude-filter": "*ChildLayerTest#ChildrenSurviveParentDestruction" @@ -76,58 +25,7 @@ "name": "SurfaceFlinger_test", "options": [ { - "include-filter": "*CredentialsTest.*" - }, - { - "include-filter": "*SurfaceFlingerStress.*" - }, - { - "include-filter": "*SurfaceInterceptorTest.*" - }, - { - "include-filter": "*LayerTransactionTest.*" - }, - { - "include-filter": "*LayerTypeTransactionTest.*" - }, - { - "include-filter": "*LayerUpdateTest.*" - }, - { - "include-filter": "*GeometryLatchingTest.*" - }, - { - "include-filter": "*CropLatchingTest.*" - }, - { - "include-filter": "*ChildLayerTest.*" - }, - { - "include-filter": "*ScreenCaptureTest.*" - }, - { - "include-filter": "*ScreenCaptureChildOnlyTest.*" - }, - { - "include-filter": "*DereferenceSurfaceControlTest.*" - }, - { - "include-filter": "*BoundlessLayerTest.*" - }, - { - "include-filter": "*MultiDisplayLayerBoundsTest.*" - }, - { - "include-filter": "*InvalidHandleTest.*" - }, - { - "include-filter": "*VirtualDisplayTest.*" - }, - { - "include-filter": "*RelativeZTest.*" - }, - { - "include-filter": "*RefreshRateOverlayTest.*" + "include-filter": "*" } ] } -- GitLab From 2e3e443fbf797e4e0488f1951c2cb6a8b065cae8 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 9 Feb 2023 18:34:11 -0800 Subject: [PATCH 0842/1310] Ignore hover events while pointers are already down If pointers are already down from another device, finish the current gesture and ignore events from other devices. When multi-device support is enabled, we can remove this restriction. Bug: 268538505 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: I6419b6d118760d285457bc9e5127e055f21ecf7f --- .../dispatcher/CancelationOptions.h | 1 + .../dispatcher/InputDispatcher.cpp | 9 ++- .../tests/InputDispatcher_test.cpp | 80 +++++++++++++++++++ 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/services/inputflinger/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h index 48f9f2b78a..83e6a60602 100644 --- a/services/inputflinger/dispatcher/CancelationOptions.h +++ b/services/inputflinger/dispatcher/CancelationOptions.h @@ -30,6 +30,7 @@ struct CancelationOptions { CANCEL_POINTER_EVENTS = 1, CANCEL_NON_POINTER_EVENTS = 2, CANCEL_FALLBACK_EVENTS = 3, + ftl_last = CANCEL_FALLBACK_EVENTS, }; // The criterion to use to determine which events should be canceled. diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 079b80dad6..fd24249f9c 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2185,8 +2185,9 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE); if (newGesture) { - bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN; - if (switchedDevice && tempTouchState.isDown() && !down && !isHoverAction) { + // If pointers are already down, let's finish the current gesture and ignore the new events + // from another device. + if (switchedDevice && wasDown) { ALOGI("Dropping event because a pointer for a different device is already down " "in display %" PRId32, displayId); @@ -3761,9 +3762,9 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( } if (DEBUG_OUTBOUND_EVENT_DETAILS) { ALOGD("channel '%s' ~ Synthesized %zu cancelation events to bring channel back in sync " - "with reality: %s, mode=%d.", + "with reality: %s, mode=%s.", connection->getInputChannelName().c_str(), cancelationEvents.size(), options.reason, - options.mode); + ftl::enum_string(options.mode).c_str()); } std::string reason = std::string("reason=").append(options.reason); diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index a2fe911e1c..2035fa1de2 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -2267,6 +2267,86 @@ TEST_F(InputDispatcherTest, HoverTapAndSplitTouch) { rightWindow->assertNoEvents(); } +/** + * Start hovering with a stylus device, and then tap with a touch device. Ensure no crash occurs. + * While the touch is down, new hover events from the stylus device should be ignored. After the + * touch is gone, stylus hovering should start working again. + */ +TEST_F(InputDispatcherTest, StylusHoverAndTouchTap) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + const int32_t stylusDeviceId = 5; + const int32_t touchDeviceId = 4; + // Start hovering with stylus + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, + AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS) + .x(50) + .y(50)) + .build())); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + + // Finger down on the window + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(100) + .y(100)) + .build())); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + + // Try to continue hovering with stylus. Since we are already down, injection should fail + ASSERT_EQ(InputEventInjectionResult::FAILED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, + AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS) + .x(50) + .y(50)) + .build())); + // No event should be sent. This event should be ignored because a pointer from another device + // is already down. + + // Lift up the finger + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(100) + .y(100)) + .build())); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP)); + + // Now that the touch is gone, stylus hovering should start working again + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, + AINPUT_SOURCE_STYLUS) + .deviceId(stylusDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS) + .x(50) + .y(50)) + .build())); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + // No more events + window->assertNoEvents(); +} + /** * On the display, have a single window, and also an area where there's no window. * First pointer touches the "no window" area of the screen. Second pointer touches the window. -- GitLab From da6a448e2dfbfa7f13ce243e9273ceb9bcda4388 Mon Sep 17 00:00:00 2001 From: Philip Quinn Date: Tue, 7 Feb 2023 10:09:57 -0800 Subject: [PATCH 0843/1310] Replace shared libtflite dependency with static library. This allows us to only include the ops required to run the model and have the linker strip the rest out, reducing memory overhead. Bug: 267050081 Test: atest libinput_tests Change-Id: I4055a0c8971ed4308ccfa425ab5e5ba560deb58c --- include/input/TfLiteMotionPredictor.h | 2 ++ libs/input/Android.bp | 6 +++++- libs/input/TfLiteMotionPredictor.cpp | 19 ++++++++++++++++--- libs/input/tests/Android.bp | 2 +- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/include/input/TfLiteMotionPredictor.h b/include/input/TfLiteMotionPredictor.h index 704349c2eb..6e9afc314b 100644 --- a/include/input/TfLiteMotionPredictor.h +++ b/include/input/TfLiteMotionPredictor.h @@ -101,6 +101,8 @@ public: // Creates a model from an encoded Flatbuffer model. static std::unique_ptr create(const char* modelPath); + ~TfLiteMotionPredictorModel(); + // Returns the length of the model's input buffers. size_t inputLength() const; diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 83392ec793..fd4fc16deb 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -73,11 +73,15 @@ cc_library { "liblog", "libPlatformProperties", "libvintf", - "libtflite", + ], + + ldflags: [ + "-Wl,--exclude-libs=libtflite_static.a", ], static_libs: [ "libui-types", + "libtflite_static", ], export_static_lib_headers: [ diff --git a/libs/input/TfLiteMotionPredictor.cpp b/libs/input/TfLiteMotionPredictor.cpp index 40653d36f7..fbb7106540 100644 --- a/libs/input/TfLiteMotionPredictor.cpp +++ b/libs/input/TfLiteMotionPredictor.cpp @@ -35,9 +35,11 @@ #include #include "tensorflow/lite/core/api/error_reporter.h" +#include "tensorflow/lite/core/api/op_resolver.h" #include "tensorflow/lite/interpreter.h" -#include "tensorflow/lite/kernels/register.h" +#include "tensorflow/lite/kernels/builtin_op_kernels.h" #include "tensorflow/lite/model.h" +#include "tensorflow/lite/mutable_op_resolver.h" namespace android { namespace { @@ -102,6 +104,15 @@ void checkTensor(const TfLiteTensor* tensor) { LOG_ALWAYS_FATAL_IF(buffer.empty(), "No buffer for tensor '%s'", tensor->name); } +std::unique_ptr createOpResolver() { + auto resolver = std::make_unique(); + resolver->AddBuiltin(::tflite::BuiltinOperator_CONCATENATION, + ::tflite::ops::builtin::Register_CONCATENATION()); + resolver->AddBuiltin(::tflite::BuiltinOperator_FULLY_CONNECTED, + ::tflite::ops::builtin::Register_FULLY_CONNECTED()); + return resolver; +} + } // namespace TfLiteMotionPredictorBuffers::TfLiteMotionPredictorBuffers(size_t inputLength) @@ -214,8 +225,8 @@ TfLiteMotionPredictorModel::TfLiteMotionPredictorModel(std::string model) mErrorReporter.get()); LOG_ALWAYS_FATAL_IF(!mModel); - tflite::ops::builtin::BuiltinOpResolver resolver; - tflite::InterpreterBuilder builder(*mModel, resolver); + auto resolver = createOpResolver(); + tflite::InterpreterBuilder builder(*mModel, *resolver); if (builder(&mInterpreter) != kTfLiteOk || !mInterpreter) { LOG_ALWAYS_FATAL("Failed to build interpreter"); @@ -227,6 +238,8 @@ TfLiteMotionPredictorModel::TfLiteMotionPredictorModel(std::string model) allocateTensors(); } +TfLiteMotionPredictorModel::~TfLiteMotionPredictorModel() {} + void TfLiteMotionPredictorModel::allocateTensors() { if (mRunner->AllocateTensors() != kTfLiteOk) { LOG_ALWAYS_FATAL("Failed to allocate tensors"); diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index f07164c87f..37faf91936 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -34,6 +34,7 @@ cc_test { "libgmock", "libgui_window_info_static", "libinput", + "libtflite_static", "libui-types", ], cflags: [ @@ -48,7 +49,6 @@ cc_test { "libcutils", "liblog", "libPlatformProperties", - "libtflite", "libutils", "libvintf", ], -- GitLab From cb3229aaf2233ebb917d967a6e73d48cce1a1480 Mon Sep 17 00:00:00 2001 From: Philip Quinn Date: Wed, 8 Feb 2023 22:50:59 -0800 Subject: [PATCH 0844/1310] Use mmap to read TFLite model. The buffers in the model file are used directly by TFLite, and so a small memory saving can be achieved by backing those memory pages with the file itself. Bug: 267050081 Test: atest libinput_tests Change-Id: I743a3c94477d4bb778b6e0c4b4890a44f4e19aa4 --- include/input/TfLiteMotionPredictor.h | 6 ++-- libs/input/TfLiteMotionPredictor.cpp | 41 +++++++++++++++++++-------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/include/input/TfLiteMotionPredictor.h b/include/input/TfLiteMotionPredictor.h index 6e9afc314b..54e2851a7a 100644 --- a/include/input/TfLiteMotionPredictor.h +++ b/include/input/TfLiteMotionPredictor.h @@ -22,8 +22,8 @@ #include #include #include -#include +#include #include #include @@ -124,7 +124,7 @@ public: std::span outputPressure() const; private: - explicit TfLiteMotionPredictorModel(std::string model); + explicit TfLiteMotionPredictorModel(std::unique_ptr model); void allocateTensors(); void attachInputTensors(); @@ -140,7 +140,7 @@ private: const TfLiteTensor* mOutputPhi = nullptr; const TfLiteTensor* mOutputPressure = nullptr; - std::string mFlatBuffer; + std::unique_ptr mFlatBuffer; std::unique_ptr mErrorReporter; std::unique_ptr mModel; std::unique_ptr mInterpreter; diff --git a/libs/input/TfLiteMotionPredictor.cpp b/libs/input/TfLiteMotionPredictor.cpp index fbb7106540..10510d675c 100644 --- a/libs/input/TfLiteMotionPredictor.cpp +++ b/libs/input/TfLiteMotionPredictor.cpp @@ -17,19 +17,21 @@ #define LOG_TAG "TfLiteMotionPredictor" #include +#include +#include +#include + #include #include #include #include -#include -#include -#include #include #include -#include #include #include +#include +#include #define ATRACE_TAG ATRACE_TAG_INPUT #include #include @@ -206,21 +208,36 @@ void TfLiteMotionPredictorBuffers::pushSample(int64_t timestamp, std::unique_ptr TfLiteMotionPredictorModel::create( const char* modelPath) { - std::ifstream f(modelPath, std::ios::binary); - LOG_ALWAYS_FATAL_IF(!f, "Could not read model from %s", modelPath); + const int fd = open(modelPath, O_RDONLY); + if (fd == -1) { + PLOG(FATAL) << "Could not read model from " << modelPath; + } + + const off_t fdSize = lseek(fd, 0, SEEK_END); + if (fdSize == -1) { + PLOG(FATAL) << "Failed to determine file size"; + } - std::string data; - data.assign(std::istreambuf_iterator(f), std::istreambuf_iterator()); + std::unique_ptr modelBuffer = + android::base::MappedFile::FromFd(fd, /*offset=*/0, fdSize, PROT_READ); + if (!modelBuffer) { + PLOG(FATAL) << "Failed to mmap model"; + } + if (close(fd) == -1) { + PLOG(FATAL) << "Failed to close model fd"; + } return std::unique_ptr( - new TfLiteMotionPredictorModel(std::move(data))); + new TfLiteMotionPredictorModel(std::move(modelBuffer))); } -TfLiteMotionPredictorModel::TfLiteMotionPredictorModel(std::string model) +TfLiteMotionPredictorModel::TfLiteMotionPredictorModel( + std::unique_ptr model) : mFlatBuffer(std::move(model)) { + CHECK(mFlatBuffer); mErrorReporter = std::make_unique(); - mModel = tflite::FlatBufferModel::VerifyAndBuildFromBuffer(mFlatBuffer.data(), - mFlatBuffer.length(), + mModel = tflite::FlatBufferModel::VerifyAndBuildFromBuffer(mFlatBuffer->data(), + mFlatBuffer->size(), /*extra_verifier=*/nullptr, mErrorReporter.get()); LOG_ALWAYS_FATAL_IF(!mModel); -- GitLab From 545da0e1040dabe238140c2ccdced5b3d60b0366 Mon Sep 17 00:00:00 2001 From: Chavi Weingarten Date: Thu, 9 Feb 2023 14:55:57 +0000 Subject: [PATCH 0845/1310] Some fixes to TrustedPresentationListener 1. Ignore display overlays since we don't want screen decorations included in the occlusion 2. Handle occluded region as separate Rects to ensure that disconnected Rects in a Region are not considered occluding in the disconnected area. Test: LayerTrustedPresentationListenerTest Bug: 256993331 Change-Id: Ib0a4b850e2aafb42e206b8728fcc9b6013171f3f --- .../CompositionRefreshArgs.h | 2 + .../LayerFECompositionState.h | 2 + .../include/compositionengine/Output.h | 4 ++ .../impl/OutputLayerCompositionState.h | 8 +++- .../CompositionEngine/src/Output.cpp | 22 +++++++++ services/surfaceflinger/Layer.cpp | 38 ++++++++++----- services/surfaceflinger/SurfaceFlinger.cpp | 1 + .../LayerTrustedPresentationListener_test.cpp | 46 +++++++++++++++++++ 8 files changed, 110 insertions(+), 13 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h index 4777f13598..a8322d8572 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h @@ -97,6 +97,8 @@ struct CompositionRefreshArgs { std::optional scheduledFrameTime; std::vector borderInfoList; + + bool hasTrustedPresentationListener = false; }; } // namespace android::compositionengine diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h index 5bb249719e..32684fc3b7 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h @@ -212,6 +212,8 @@ struct LayerFECompositionState { float currentSdrHdrRatio = 1.f; float desiredSdrHdrRatio = 1.f; + bool isInternalDisplayOverlay = false; + virtual ~LayerFECompositionState(); // Debugging diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h index 52ebd9ebec..a3d86398cf 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h @@ -153,6 +153,10 @@ public: Region aboveOpaqueLayers; // The region of the output which should be considered dirty Region dirtyRegion; + // The region of the output which is covered by layers, excluding display overlays. This + // only has a value if there's something needing it, like when a TrustedPresentationListener + // is set + std::optional aboveCoveredLayersExcludingOverlays; }; virtual ~Output(); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h index b86782f417..7b0af3a248 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h @@ -63,9 +63,15 @@ struct OutputLayerCompositionState { // The portion of the layer that is not obscured and is also opaque Region visibleNonTransparentRegion; - // The portion of the layer that is obscured by opaque layers on top + // The portion of the layer that is obscured by all layers on top. This includes transparent and + // opaque. Region coveredRegion; + // The portion of the layer that is obscured by all layers on top excluding display overlays. + // This only has a value if there's something needing it, like when a + // TrustedPresentationListener is set. + std::optional coveredRegionExcludingDisplayOverlays; + // The visibleRegion transformed to output space Region outputSpaceVisibleRegion; diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 403385ea8c..7d9431605d 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include "renderengine/ExternalTexture.h" @@ -480,6 +481,9 @@ void Output::rebuildLayerStacks(const compositionengine::CompositionRefreshArgs& // Process the layers to determine visibility and coverage compositionengine::Output::CoverageState coverage{layerFESet}; + coverage.aboveCoveredLayersExcludingOverlays = refreshArgs.hasTrustedPresentationListener + ? std::make_optional() + : std::nullopt; collectVisibleLayers(refreshArgs, coverage); // Compute the resulting coverage for this output, and store it for later @@ -534,6 +538,9 @@ void Output::ensureOutputLayerIfVisible(sp& layerFE, return; } + bool computeAboveCoveredExcludingOverlays = + coverage.aboveCoveredLayersExcludingOverlays && !layerFEState->isInternalDisplayOverlay; + /* * opaqueRegion: area of a surface that is fully opaque. */ @@ -575,6 +582,11 @@ void Output::ensureOutputLayerIfVisible(sp& layerFE, */ Region shadowRegion; + /** + * covered region above excluding internal display overlay layers + */ + std::optional coveredRegionExcludingDisplayOverlays = std::nullopt; + const ui::Transform& tr = layerFEState->geomLayerTransform; // Get the visible region @@ -647,6 +659,12 @@ void Output::ensureOutputLayerIfVisible(sp& layerFE, // Update accumAboveCoveredLayers for next (lower) layer coverage.aboveCoveredLayers.orSelf(visibleRegion); + if (CC_UNLIKELY(computeAboveCoveredExcludingOverlays)) { + coveredRegionExcludingDisplayOverlays = + coverage.aboveCoveredLayersExcludingOverlays->intersect(visibleRegion); + coverage.aboveCoveredLayersExcludingOverlays->orSelf(visibleRegion); + } + // subtract the opaque region covered by the layers above us visibleRegion.subtractSelf(coverage.aboveOpaqueLayers); @@ -733,6 +751,10 @@ void Output::ensureOutputLayerIfVisible(sp& layerFE, ? outputState.transform.transform( transparentRegion.intersect(outputState.layerStackSpace.getContent())) : Region(); + if (CC_UNLIKELY(computeAboveCoveredExcludingOverlays)) { + outputLayerState.coveredRegionExcludingDisplayOverlays = + std::move(coveredRegionExcludingDisplayOverlays); + } } void Output::setReleasedLayers(const compositionengine::CompositionRefreshArgs&) { diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 66c2fb658f..0a27bddb58 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -395,14 +395,22 @@ void Layer::updateTrustedPresentationState(const DisplayDevice* display, if (!leaveState) { const auto outputLayer = findOutputLayerForDisplay(display); - if (outputLayer != nullptr && snapshot != nullptr) { - mLastComputedTrustedPresentationState = - computeTrustedPresentationState(snapshot->geomLayerBounds, - snapshot->sourceBounds(), - outputLayer->getState().coveredRegion, - snapshot->transformedBounds, snapshot->alpha, - snapshot->geomLayerTransform, - mTrustedPresentationThresholds); + if (outputLayer != nullptr) { + if (outputLayer->getState().coveredRegionExcludingDisplayOverlays) { + Region coveredRegion = + *outputLayer->getState().coveredRegionExcludingDisplayOverlays; + mLastComputedTrustedPresentationState = + computeTrustedPresentationState(snapshot->geomLayerBounds, + snapshot->sourceBounds(), coveredRegion, + snapshot->transformedBounds, + snapshot->alpha, + snapshot->geomLayerTransform, + mTrustedPresentationThresholds); + } else { + ALOGE("CoveredRegionExcludingDisplayOverlays was not set for %s. Don't compute " + "TrustedPresentationState", + getDebugName()); + } } } const bool newState = mLastComputedTrustedPresentationState; @@ -459,10 +467,15 @@ bool Layer::computeTrustedPresentationState(const FloatRect& bounds, const Float float boundsOverSourceH = bounds.getHeight() / (float)sourceBounds.getHeight(); fractionRendered *= boundsOverSourceW * boundsOverSourceH; - Rect coveredBounds = coveredRegion.bounds(); - fractionRendered *= (1 - - ((coveredBounds.width() / (float)screenBounds.getWidth()) * - coveredBounds.height() / (float)screenBounds.getHeight())); + Region tJunctionFreeRegion = Region::createTJunctionFreeRegion(coveredRegion); + // Compute the size of all the rects since they may be disconnected. + float coveredSize = 0; + for (auto rect = tJunctionFreeRegion.begin(); rect < tJunctionFreeRegion.end(); rect++) { + float size = rect->width() * rect->height(); + coveredSize += size; + } + + fractionRendered *= (1 - (coveredSize / (screenBounds.getWidth() * screenBounds.getHeight()))); if (fractionRendered < thresholds.minFractionRendered) { return false; @@ -3996,6 +4009,7 @@ void Layer::updateSnapshot(bool updateGeometry) { snapshot->bufferSize = getBufferSize(mDrawingState); snapshot->externalTexture = mBufferInfo.mBuffer; snapshot->hasReadyFrame = hasReadyFrame(); + snapshot->isInternalDisplayOverlay = isInternalDisplayOverlay(); preparePerFrameCompositionState(); } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 68ab776400..39b942c3cb 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2511,6 +2511,7 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) refreshArgs.previousPresentFence = mPreviousPresentFences[0].fenceTime; refreshArgs.scheduledFrameTime = mScheduler->getScheduledFrameTime(); refreshArgs.expectedPresentTime = mExpectedPresentTime.ns(); + refreshArgs.hasTrustedPresentationListener = mNumTrustedPresentationListeners > 0; // Store the present time just before calling to the composition engine so we could notify // the scheduler. diff --git a/services/surfaceflinger/tests/LayerTrustedPresentationListener_test.cpp b/services/surfaceflinger/tests/LayerTrustedPresentationListener_test.cpp index a843fd17b1..2be8d3b013 100644 --- a/services/surfaceflinger/tests/LayerTrustedPresentationListener_test.cpp +++ b/services/surfaceflinger/tests/LayerTrustedPresentationListener_test.cpp @@ -238,4 +238,50 @@ TEST_F(LayerTrustedPresentationListenerTest, obscuring_with_alpha) { EXPECT_TRUE(pch.awaitCallback()); } +TEST_F(LayerTrustedPresentationListenerTest, obscuring_with_display_overlay) { + auto otherLayer = makeLayer(); + t.show(otherLayer) + .setPosition(otherLayer, 100, 100) + .setLayer(otherLayer, INT32_MAX) + .setFlags(otherLayer, layer_state_t::eLayerSkipScreenshot, + layer_state_t::eLayerSkipScreenshot) + .setLayer(mainLayer, INT32_MAX - 1) + .show(mainLayer) + .setPosition(mainLayer, 100, 100) + .setTrustedPresentationCallback( + mainLayer, + [&](void* context, bool state) { + PresentationCallbackHelper* helper = (PresentationCallbackHelper*)context; + helper->callbackArrived(state); + }, + thresholds, &pch, mCallback) + .apply(); + EXPECT_TRUE(pch.awaitCallback()); +} + +TEST_F(LayerTrustedPresentationListenerTest, obscuring_with_non_overlapping_bounds) { + thresholds.minFractionRendered = 0.5; + auto otherLayer1 = makeLayer(); + auto otherLayer2 = makeLayer(); + t.show(otherLayer1) + .show(otherLayer2) + .setPosition(otherLayer1, 100, 25) + .setLayer(otherLayer1, INT32_MAX) + .setPosition(otherLayer2, 100, 175) + .setLayer(otherLayer2, INT32_MAX) + .setLayer(mainLayer, INT32_MAX - 1) + .show(mainLayer) + .setPosition(mainLayer, 100, 100) + .setTrustedPresentationCallback( + mainLayer, + [&](void* context, bool state) { + PresentationCallbackHelper* helper = (PresentationCallbackHelper*)context; + helper->callbackArrived(state); + }, + thresholds, &pch, mCallback) + .apply(); + + EXPECT_TRUE(pch.awaitCallback()); +} + } // namespace android -- GitLab From c275df4a90960863e30d028c7ea5d163635b134f Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Tue, 7 Feb 2023 16:40:21 -0500 Subject: [PATCH 0846/1310] Move HwVsyncState into VsyncSchedule In preparation for having a separate schedule per display, pull state which is logically for a single display into VsyncSchedule. This should be a pure refactor. Consolidate mPrimaryHWVsyncEnabled and mHWVsyncAvailable (now in VsyncSchedule) into a single enum which better captures the possible states. Move SF's record of the pending HWC VSYNC state into the same place, so it sits with other related logic. Remove the last HWC VSYNC state, which did not affect behavior. Move ISchedulerCallback into its own file, so that VsyncSchedule.cpp doesn't need to depend on Scheduler.h. Rename variables `makeUnavailable` and `makeAvailable` for clarity. Make addResyncSample return a boolean, instead of void with an out parameter. Remove VsyncSchedule's move constructor. Now that it has a mutex, it is no longer thread-safe. We don't actually need to move it anyway. Replace std::optional with std::unique_ptr for owning a VsyncSchedule. Factored out of If60218e85292c786b9fa70ecb33ee374d3a385e0. Relevant differences from that patch: - There is still only one VsyncSchedule. - When SF turns off a display, we still disable vsync on that display. In If60218e85292c786b9fa70ecb33ee374d3a385e0, this was redundant with the call to disableHardwareVsync shortly before it, but right now we only call disableHardwareVsync for the active display. - Hold the VSYNC lock for more time in resyncToHaredwareVsync. This better matches the original behavior. - Remove mLastHwVsyncState. - Recreate the code for storing and setting the pending VSYNC state. - Inline setVsyncPeriod at its only call-site. Bug: 255601557 Bug: 256196556 Bug: 241286146 Fixes: 266712910 Test: libsurfaceflinger_unittest Change-Id: I54a1304a3428968134cc707b24d5b325927c31df --- .../Scheduler/ISchedulerCallback.h | 35 ++++++++ .../surfaceflinger/Scheduler/Scheduler.cpp | 80 +++-------------- services/surfaceflinger/Scheduler/Scheduler.h | 32 ++----- .../Scheduler/VsyncSchedule.cpp | 86 +++++++++++++++++-- .../surfaceflinger/Scheduler/VsyncSchedule.h | 67 +++++++++++++-- services/surfaceflinger/SurfaceFlinger.cpp | 23 ++--- services/surfaceflinger/SurfaceFlinger.h | 10 +-- .../fuzzer/surfaceflinger_fuzzers_utils.h | 6 +- .../surfaceflinger_scheduler_fuzzer.cpp | 8 +- .../tests/unittests/EventThreadTest.cpp | 8 +- ...urfaceFlinger_SetPowerModeInternalTest.cpp | 10 +-- .../tests/unittests/TestableScheduler.h | 19 ++-- 12 files changed, 235 insertions(+), 149 deletions(-) create mode 100644 services/surfaceflinger/Scheduler/ISchedulerCallback.h diff --git a/services/surfaceflinger/Scheduler/ISchedulerCallback.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h new file mode 100644 index 0000000000..c4de7497ac --- /dev/null +++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.h @@ -0,0 +1,35 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "Display/DisplayModeRequest.h" + +namespace android::scheduler { + +struct ISchedulerCallback { + virtual void setVsyncEnabled(bool) = 0; + virtual void requestDisplayModes(std::vector) = 0; + virtual void kernelTimerChanged(bool expired) = 0; + virtual void triggerOnFrameRateOverridesChanged() = 0; + +protected: + ~ISchedulerCallback() = default; +}; + +} // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 5fc250bd9f..d1f6e87119 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -155,7 +155,7 @@ void Scheduler::onFrameSignal(ICompositor& compositor, VsyncId vsyncId, } void Scheduler::createVsyncSchedule(FeatureFlags features) { - mVsyncSchedule.emplace(features); + mVsyncSchedule = std::make_unique(features); } std::optional Scheduler::getFrameRateOverride(uid_t uid) const { @@ -393,38 +393,17 @@ void Scheduler::setVsyncConfig(const VsyncConfig& config, Period vsyncPeriod) { } void Scheduler::enableHardwareVsync() { - std::lock_guard lock(mHWVsyncLock); - if (!mPrimaryHWVsyncEnabled && mHWVsyncAvailable) { - mVsyncSchedule->getTracker().resetModel(); - mSchedulerCallback.setVsyncEnabled(true); - mPrimaryHWVsyncEnabled = true; - } + mVsyncSchedule->enableHardwareVsync(mSchedulerCallback); } -void Scheduler::disableHardwareVsync(bool makeUnavailable) { - std::lock_guard lock(mHWVsyncLock); - if (mPrimaryHWVsyncEnabled) { - mSchedulerCallback.setVsyncEnabled(false); - mPrimaryHWVsyncEnabled = false; - } - if (makeUnavailable) { - mHWVsyncAvailable = false; - } +void Scheduler::disableHardwareVsync(bool disallow) { + mVsyncSchedule->disableHardwareVsync(mSchedulerCallback, disallow); } -void Scheduler::resyncToHardwareVsync(bool makeAvailable, Fps refreshRate) { - { - std::lock_guard lock(mHWVsyncLock); - if (makeAvailable) { - mHWVsyncAvailable = makeAvailable; - } else if (!mHWVsyncAvailable) { - // Hardware vsync is not currently available, so abort the resync - // attempt for now - return; - } +void Scheduler::resyncToHardwareVsync(bool allowToEnable, Fps refreshRate) { + if (mVsyncSchedule->isHardwareVsyncAllowed(allowToEnable) && refreshRate.isValid()) { + mVsyncSchedule->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod()); } - - setVsyncPeriod(refreshRate.getPeriodNsecs()); } void Scheduler::setRenderRate(Fps renderFrameRate) { @@ -457,37 +436,12 @@ void Scheduler::resync() { } } -void Scheduler::setVsyncPeriod(nsecs_t period) { - if (period <= 0) return; - - std::lock_guard lock(mHWVsyncLock); - mVsyncSchedule->getController().startPeriodTransition(period); - - if (!mPrimaryHWVsyncEnabled) { - mVsyncSchedule->getTracker().resetModel(); - mSchedulerCallback.setVsyncEnabled(true); - mPrimaryHWVsyncEnabled = true; - } -} - -void Scheduler::addResyncSample(nsecs_t timestamp, std::optional hwcVsyncPeriod, - bool* periodFlushed) { - bool needsHwVsync = false; - *periodFlushed = false; - { // Scope for the lock - std::lock_guard lock(mHWVsyncLock); - if (mPrimaryHWVsyncEnabled) { - needsHwVsync = - mVsyncSchedule->getController().addHwVsyncTimestamp(timestamp, hwcVsyncPeriod, - periodFlushed); - } - } - - if (needsHwVsync) { - enableHardwareVsync(); - } else { - disableHardwareVsync(false); - } +bool Scheduler::addResyncSample(nsecs_t timestamp, std::optional hwcVsyncPeriodIn) { + const auto hwcVsyncPeriod = ftl::Optional(hwcVsyncPeriodIn).transform([](nsecs_t nanos) { + return Period::fromNs(nanos); + }); + return mVsyncSchedule->addResyncSample(mSchedulerCallback, TimePoint::fromNs(timestamp), + hwcVsyncPeriod); } void Scheduler::addPresentFence(std::shared_ptr fence) { @@ -635,14 +589,6 @@ void Scheduler::dump(utils::Dumper& dumper) const { mFrameRateOverrideMappings.dump(dumper); dumper.eol(); - - { - utils::Dumper::Section section(dumper, "Hardware VSYNC"sv); - - std::lock_guard lock(mHWVsyncLock); - dumper.dump("hwVsyncAvailable"sv, mHWVsyncAvailable); - dumper.dump("hwVsyncEnabled"sv, mPrimaryHWVsyncEnabled); - } } void Scheduler::dumpVsync(std::string& out) const { diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index dae9546c78..a340919a65 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -43,6 +43,7 @@ #include "Display/DisplayModeRequest.h" #include "EventThread.h" #include "FrameRateOverrideMappings.h" +#include "ISchedulerCallback.h" #include "LayerHistory.h" #include "MessageQueue.h" #include "OneShotTimer.h" @@ -92,16 +93,6 @@ namespace scheduler { using GlobalSignals = RefreshRateSelector::GlobalSignals; -struct ISchedulerCallback { - virtual void setVsyncEnabled(bool) = 0; - virtual void requestDisplayModes(std::vector) = 0; - virtual void kernelTimerChanged(bool expired) = 0; - virtual void triggerOnFrameRateOverridesChanged() = 0; - -protected: - ~ISchedulerCallback() = default; -}; - class Scheduler : android::impl::MessageQueue { using Impl = android::impl::MessageQueue; @@ -192,20 +183,20 @@ public: void setRenderRate(Fps); void enableHardwareVsync(); - void disableHardwareVsync(bool makeUnavailable); + void disableHardwareVsync(bool disallow); // Resyncs the scheduler to hardware vsync. - // If makeAvailable is true, then hardware vsync will be turned on. + // If allowToEnable is true, then hardware vsync will be turned on. // Otherwise, if hardware vsync is not already enabled then this method will // no-op. - void resyncToHardwareVsync(bool makeAvailable, Fps refreshRate); + void resyncToHardwareVsync(bool allowToEnable, Fps refreshRate); void resync() EXCLUDES(mDisplayLock); void forceNextResync() { mLastResyncTime = 0; } - // Passes a vsync sample to VsyncController. periodFlushed will be true if - // VsyncController detected that the vsync period changed, and false otherwise. - void addResyncSample(nsecs_t timestamp, std::optional hwcVsyncPeriod, - bool* periodFlushed); + // Passes a vsync sample to VsyncController. Returns true if + // VsyncController detected that the vsync period changed and false + // otherwise. + bool addResyncSample(nsecs_t timestamp, std::optional hwcVsyncPeriod); void addPresentFence(std::shared_ptr); // Layers are registered on creation, and unregistered when the weak reference expires. @@ -297,7 +288,6 @@ private: void touchTimerCallback(TimerState); void displayPowerTimerCallback(TimerState); - void setVsyncPeriod(nsecs_t period); void setVsyncConfig(const VsyncConfig&, Period vsyncPeriod); // Chooses a leader among the registered displays, unless `leaderIdOpt` is specified. The new @@ -362,14 +352,10 @@ private: ConnectionHandle mAppConnectionHandle; ConnectionHandle mSfConnectionHandle; - mutable std::mutex mHWVsyncLock; - bool mPrimaryHWVsyncEnabled GUARDED_BY(mHWVsyncLock) = false; - bool mHWVsyncAvailable GUARDED_BY(mHWVsyncLock) = false; - std::atomic mLastResyncTime = 0; const FeatureFlags mFeatures; - std::optional mVsyncSchedule; + std::unique_ptr mVsyncSchedule; // Shifts the VSYNC phase during certain transactions and refresh rate changes. const sp mVsyncModulator; diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp index 95bc31f239..524555681b 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp @@ -16,11 +16,14 @@ #define ATRACE_TAG ATRACE_TAG_GRAPHICS +#include #include #include #include "VsyncSchedule.h" +#include "ISchedulerCallback.h" +#include "Utils/Dumper.h" #include "VSyncDispatchTimerQueue.h" #include "VSyncPredictor.h" #include "VSyncReactor.h" @@ -54,18 +57,16 @@ private: VsyncSchedule::VsyncSchedule(FeatureFlags features) : mTracker(createTracker()), mDispatch(createDispatch(*mTracker)), - mController(createController(*mTracker, features)) { - if (features.test(Feature::kTracePredictedVsync)) { - mTracer = std::make_unique(*mDispatch); - } -} + mController(createController(*mTracker, features)), + mTracer(features.test(Feature::kTracePredictedVsync) + ? std::make_unique(*mDispatch) + : nullptr) {} VsyncSchedule::VsyncSchedule(TrackerPtr tracker, DispatchPtr dispatch, ControllerPtr controller) : mTracker(std::move(tracker)), mDispatch(std::move(dispatch)), mController(std::move(controller)) {} -VsyncSchedule::VsyncSchedule(VsyncSchedule&&) = default; VsyncSchedule::~VsyncSchedule() = default; Period VsyncSchedule::period() const { @@ -77,6 +78,16 @@ TimePoint VsyncSchedule::vsyncDeadlineAfter(TimePoint timePoint) const { } void VsyncSchedule::dump(std::string& out) const { + utils::Dumper dumper(out); + { + std::lock_guard lock(mHwVsyncLock); + dumper.dump("hwVsyncState", ftl::enum_string(mHwVsyncState)); + + ftl::FakeGuard guard(kMainThreadContext); + dumper.dump("pendingHwVsyncState", ftl::enum_string(mPendingHwVsyncState)); + dumper.eol(); + } + out.append("VsyncController:\n"); mController->dump(out); @@ -120,4 +131,67 @@ VsyncSchedule::ControllerPtr VsyncSchedule::createController(VsyncTracker& track return reactor; } +void VsyncSchedule::startPeriodTransition(ISchedulerCallback& callback, Period period) { + std::lock_guard lock(mHwVsyncLock); + mController->startPeriodTransition(period.ns()); + enableHardwareVsyncLocked(callback); +} + +bool VsyncSchedule::addResyncSample(ISchedulerCallback& callback, TimePoint timestamp, + ftl::Optional hwcVsyncPeriod) { + bool needsHwVsync = false; + bool periodFlushed = false; + { + std::lock_guard lock(mHwVsyncLock); + if (mHwVsyncState == HwVsyncState::Enabled) { + needsHwVsync = mController->addHwVsyncTimestamp(timestamp.ns(), + hwcVsyncPeriod.transform(&Period::ns), + &periodFlushed); + } + } + if (needsHwVsync) { + enableHardwareVsync(callback); + } else { + disableHardwareVsync(callback, false /* disallow */); + } + return periodFlushed; +} + +void VsyncSchedule::enableHardwareVsync(ISchedulerCallback& callback) { + std::lock_guard lock(mHwVsyncLock); + enableHardwareVsyncLocked(callback); +} + +void VsyncSchedule::enableHardwareVsyncLocked(ISchedulerCallback& callback) { + if (mHwVsyncState == HwVsyncState::Disabled) { + getTracker().resetModel(); + callback.setVsyncEnabled(true); + mHwVsyncState = HwVsyncState::Enabled; + } +} + +void VsyncSchedule::disableHardwareVsync(ISchedulerCallback& callback, bool disallow) { + std::lock_guard lock(mHwVsyncLock); + if (mHwVsyncState == HwVsyncState::Enabled) { + callback.setVsyncEnabled(false); + } + mHwVsyncState = disallow ? HwVsyncState::Disallowed : HwVsyncState::Disabled; +} + +bool VsyncSchedule::isHardwareVsyncAllowed(bool makeAllowed) { + std::lock_guard lock(mHwVsyncLock); + if (makeAllowed && mHwVsyncState == HwVsyncState::Disallowed) { + mHwVsyncState = HwVsyncState::Disabled; + } + return mHwVsyncState != HwVsyncState::Disallowed; +} + +void VsyncSchedule::setPendingHardwareVsyncState(bool enabled) { + mPendingHwVsyncState = enabled ? HwVsyncState::Enabled : HwVsyncState::Disabled; +} + +bool VsyncSchedule::getPendingHardwareVsyncState() const { + return mPendingHwVsyncState == HwVsyncState::Enabled; +} + } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h index 173b1d00cf..d88f1d1f0b 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.h +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h @@ -19,6 +19,9 @@ #include #include +#include +#include +#include #include #include @@ -32,6 +35,8 @@ class SchedulerFuzzer; namespace android::scheduler { +struct ISchedulerCallback; + // TODO(b/185535769): Rename classes, and remove aliases. class VSyncDispatch; class VSyncTracker; @@ -44,12 +49,24 @@ using VsyncTracker = VSyncTracker; class VsyncSchedule { public: explicit VsyncSchedule(FeatureFlags); - VsyncSchedule(VsyncSchedule&&); ~VsyncSchedule(); Period period() const; TimePoint vsyncDeadlineAfter(TimePoint) const; + // Inform the schedule that the period is changing and the schedule needs to recalibrate + // itself. The schedule will end the period transition internally. This will + // enable hardware VSYNCs in order to calibrate. + // + // \param [in] period The period that the system is changing into. + void startPeriodTransition(ISchedulerCallback&, Period period); + + // Pass a VSYNC sample to VsyncController. Return true if + // VsyncController detected that the VSYNC period changed. Enable or disable + // hardware VSYNCs depending on whether more samples are needed. + bool addResyncSample(ISchedulerCallback&, TimePoint timestamp, + ftl::Optional hwcVsyncPeriod); + // TODO(b/185535769): Hide behind API. const VsyncTracker& getTracker() const { return *mTracker; } VsyncTracker& getTracker() { return *mTracker; } @@ -60,6 +77,22 @@ public: void dump(std::string&) const; + // Turn on hardware VSYNCs, unless mHwVsyncState is Disallowed, in which + // case this call is ignored. + void enableHardwareVsync(ISchedulerCallback&) EXCLUDES(mHwVsyncLock); + + // Disable hardware VSYNCs. If `disallow` is true, future calls to + // enableHardwareVsync are ineffective until allowHardwareVsync is called. + void disableHardwareVsync(ISchedulerCallback&, bool disallow) EXCLUDES(mHwVsyncLock); + + // If true, enableHardwareVsync can enable hardware VSYNC (if not already + // enabled). If false, enableHardwareVsync does nothing. + bool isHardwareVsyncAllowed(bool makeAllowed) EXCLUDES(mHwVsyncLock); + + void setPendingHardwareVsyncState(bool enabled) REQUIRES(kMainThreadContext); + + bool getPendingHardwareVsyncState() const REQUIRES(kMainThreadContext); + private: friend class TestableScheduler; friend class android::EventThreadTest; @@ -76,14 +109,36 @@ private: static DispatchPtr createDispatch(VsyncTracker&); static ControllerPtr createController(VsyncTracker&, FeatureFlags); + void enableHardwareVsyncLocked(ISchedulerCallback&) REQUIRES(mHwVsyncLock); + + mutable std::mutex mHwVsyncLock; + enum class HwVsyncState { + // Hardware VSYNCs are currently enabled. + Enabled, + + // Hardware VSYNCs are currently disabled. They can be enabled by a call + // to `enableHardwareVsync`. + Disabled, + + // Hardware VSYNCs are not currently allowed (e.g. because the display + // is off). + Disallowed, + + ftl_last = Disallowed, + }; + HwVsyncState mHwVsyncState GUARDED_BY(mHwVsyncLock) = HwVsyncState::Disallowed; + + // Pending state, in case an attempt is made to set the state while the + // device is off. + HwVsyncState mPendingHwVsyncState GUARDED_BY(kMainThreadContext) = HwVsyncState::Disabled; + class PredictedVsyncTracer; using TracerPtr = std::unique_ptr; - // Effectively const except in move constructor. - TrackerPtr mTracker; - DispatchPtr mDispatch; - ControllerPtr mController; - TracerPtr mTracer; + const TrackerPtr mTracker; + const DispatchPtr mDispatch; + const ControllerPtr mController; + const TracerPtr mTracer; }; } // namespace android::scheduler diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index c7c91ce2fa..830537f359 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2031,8 +2031,7 @@ void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t t return; } - bool periodFlushed = false; - mScheduler->addResyncSample(timestamp, vsyncPeriod, &periodFlushed); + const bool periodFlushed = mScheduler->addResyncSample(timestamp, vsyncPeriod); if (periodFlushed) { mScheduler->modulateVsync(&VsyncModulator::onRefreshRateChangeCompleted); } @@ -2080,11 +2079,14 @@ void SurfaceFlinger::setVsyncEnabled(bool enabled) { // On main thread to avoid race conditions with display power state. static_cast(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) { - mHWCVsyncPendingState = enabled ? hal::Vsync::ENABLE : hal::Vsync::DISABLE; + { + ftl::FakeGuard guard(kMainThreadContext); + mScheduler->getVsyncSchedule().setPendingHardwareVsyncState(enabled); + } if (const auto display = getDefaultDisplayDeviceLocked(); display && display->isPoweredOn()) { - setHWCVsyncEnabled(display->getPhysicalId(), mHWCVsyncPendingState); + setHWCVsyncEnabled(display->getPhysicalId(), enabled); } })); } @@ -5012,7 +5014,8 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: } getHwComposer().setPowerMode(displayId, mode); if (isActiveDisplay && mode != hal::PowerMode::DOZE_SUSPEND) { - setHWCVsyncEnabled(displayId, mHWCVsyncPendingState); + setHWCVsyncEnabled(displayId, + mScheduler->getVsyncSchedule().getPendingHardwareVsyncState()); mScheduler->onScreenAcquired(mAppConnectionHandle); mScheduler->resyncToHardwareVsync(true, refreshRate); } @@ -5033,7 +5036,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: } // Make sure HWVsync is disabled before turning off the display - setHWCVsyncEnabled(displayId, hal::Vsync::DISABLE); + setHWCVsyncEnabled(displayId, false); getHwComposer().setPowerMode(displayId, mode); mVisibleRegionsDirty = true; @@ -5242,14 +5245,6 @@ void SurfaceFlinger::dumpScheduler(std::string& result) const { mScheduler->dump(dumper); - // TODO(b/241286146): Move to Scheduler. - { - utils::Dumper::Indent indent(dumper); - dumper.dump("lastHwcVsyncState"sv, mLastHWCVsyncState); - dumper.dump("pendingHwcVsyncState"sv, mHWCVsyncPendingState); - } - dumper.eol(); - // TODO(b/241285876): Move to DisplayModeController. dumper.dump("debugDisplayModeSetByBackdoor"sv, mDebugDisplayModeSetByBackdoor); dumper.eol(); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 97469f4253..493dc44cb3 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -74,6 +74,7 @@ #include "FrontEnd/LayerSnapshot.h" #include "FrontEnd/TransactionHandler.h" #include "LayerVector.h" +#include "Scheduler/ISchedulerCallback.h" #include "Scheduler/RefreshRateSelector.h" #include "Scheduler/RefreshRateStats.h" #include "Scheduler/Scheduler.h" @@ -954,9 +955,9 @@ private: */ nsecs_t getVsyncPeriodFromHWC() const REQUIRES(mStateLock); - void setHWCVsyncEnabled(PhysicalDisplayId id, hal::Vsync enabled) { - mLastHWCVsyncState = enabled; - getHwComposer().setVsyncEnabled(id, enabled); + void setHWCVsyncEnabled(PhysicalDisplayId id, bool enabled) { + hal::Vsync halState = enabled ? hal::Vsync::ENABLE : hal::Vsync::DISABLE; + getHwComposer().setVsyncEnabled(id, halState); } using FenceTimePtr = std::shared_ptr; @@ -1282,9 +1283,6 @@ private: TimePoint mScheduledPresentTime GUARDED_BY(kMainThreadContext); TimePoint mExpectedPresentTime GUARDED_BY(kMainThreadContext); - hal::Vsync mHWCVsyncPendingState = hal::Vsync::DISABLE; - hal::Vsync mLastHWCVsyncState = hal::Vsync::DISABLE; - // below flags are set by main thread only bool mSetActiveModePending = false; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index cdffbb4724..609fd33a3f 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -234,7 +234,8 @@ public: std::shared_ptr selectorPtr, sp modulatorPtr, ISchedulerCallback& callback) : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) { - mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller))); + mVsyncSchedule = std::unique_ptr( + new VsyncSchedule(std::move(tracker), nullptr, std::move(controller))); const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId(); registerDisplay(displayId, std::move(selectorPtr)); @@ -244,9 +245,6 @@ public: return Scheduler::createConnection(std::move(eventThread)); } - auto &mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; } - auto &mutableHWVsyncAvailable() { return mHWVsyncAvailable; } - auto &mutableLayerHistory() { return mLayerHistory; } auto refreshRateSelector() { return leaderSelectorPtr(); } diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index 44805dba82..61fb29a964 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -76,7 +76,7 @@ private: FuzzedDataProvider mFdp; - std::optional mVsyncSchedule; + std::unique_ptr mVsyncSchedule; }; PhysicalDisplayId SchedulerFuzzer::getPhysicalDisplayId() { @@ -90,9 +90,9 @@ PhysicalDisplayId SchedulerFuzzer::getPhysicalDisplayId() { } void SchedulerFuzzer::fuzzEventThread() { - mVsyncSchedule.emplace(scheduler::VsyncSchedule(std::make_unique(), - std::make_unique(), - nullptr)); + mVsyncSchedule = std::unique_ptr( + new scheduler::VsyncSchedule(std::make_unique(), + std::make_unique(), nullptr)); const auto getVsyncPeriod = [](uid_t /* uid */) { return kSyncPeriod.count(); }; std::unique_ptr thread = std::make_unique< android::impl::EventThread>("fuzzer", *mVsyncSchedule, nullptr, nullptr, getVsyncPeriod, diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp index b3aba377ee..f6bcadc586 100644 --- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp +++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp @@ -125,7 +125,7 @@ protected: ConnectionEventRecorder mConnectionEventCallRecorder{0}; ConnectionEventRecorder mThrottledConnectionEventCallRecorder{0}; - std::optional mVsyncSchedule; + std::unique_ptr mVsyncSchedule; std::unique_ptr mThread; sp mConnection; sp mThrottledConnection; @@ -140,9 +140,9 @@ EventThreadTest::EventThreadTest() { ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - mVsyncSchedule.emplace(scheduler::VsyncSchedule(std::make_unique(), - std::make_unique(), - nullptr)); + mVsyncSchedule = std::unique_ptr( + new scheduler::VsyncSchedule(std::make_unique(), + std::make_unique(), nullptr)); mock::VSyncDispatch& mockDispatch = *static_cast(&mVsyncSchedule->getDispatch()); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp index ab732ed485..88ddb0f45b 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp @@ -262,8 +262,8 @@ struct DisplayPowerCase { return display; } - static void setInitialPrimaryHWVsyncEnabled(DisplayTransactionTest* test, bool enabled) { - test->mFlinger.scheduler()->mutablePrimaryHWVsyncEnabled() = enabled; + static void setInitialHwVsyncEnabled(DisplayTransactionTest* test, bool enabled) { + test->mFlinger.scheduler()->setInitialHwVsyncEnabled(enabled); } static void setupRepaintEverythingCallExpectations(DisplayTransactionTest* test) { @@ -329,9 +329,9 @@ void SetPowerModeInternalTest::transitionDisplayCommon() { Case::Doze::setupComposerCallExpectations(this); auto display = Case::injectDisplayWithInitialPowerMode(this, Case::Transition::INITIAL_POWER_MODE); - Case::setInitialPrimaryHWVsyncEnabled(this, - PowerModeInitialVSyncEnabled< - Case::Transition::INITIAL_POWER_MODE>::value); + Case::setInitialHwVsyncEnabled(this, + PowerModeInitialVSyncEnabled< + Case::Transition::INITIAL_POWER_MODE>::value); // -------------------------------------------------------------------- // Call Expectations diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index 6cf61416c0..bd3f3cae98 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -44,9 +44,9 @@ public: std::unique_ptr tracker, RefreshRateSelectorPtr selectorPtr, sp modulatorPtr, ISchedulerCallback& callback) : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr)) { - mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), - std::make_unique(), - std::move(controller))); + mVsyncSchedule = std::unique_ptr( + new VsyncSchedule(std::move(tracker), std::make_unique(), + std::move(controller))); const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId(); registerDisplay(displayId, std::move(selectorPtr)); @@ -66,13 +66,6 @@ public: return Scheduler::createConnection(std::move(eventThread)); } - /* ------------------------------------------------------------------------ - * Read-write access to private data to set up preconditions and assert - * post-conditions. - */ - auto& mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; } - auto& mutableHWVsyncAvailable() { return mHWVsyncAvailable; } - auto refreshRateSelector() { return leaderSelectorPtr(); } const auto& refreshRateSelectors() const NO_THREAD_SAFETY_ANALYSIS { @@ -157,6 +150,12 @@ public: Scheduler::onNonPrimaryDisplayModeChanged(handle, mode); } + void setInitialHwVsyncEnabled(bool enabled) { + std::lock_guard lock(mVsyncSchedule->mHwVsyncLock); + mVsyncSchedule->mHwVsyncState = enabled ? VsyncSchedule::HwVsyncState::Enabled + : VsyncSchedule::HwVsyncState::Disabled; + } + private: // ICompositor overrides: void configure() override {} -- GitLab From bd66e62069a5495ee8ba8b9ff8c35d3a2075a06d Mon Sep 17 00:00:00 2001 From: Philip Quinn Date: Fri, 10 Feb 2023 11:45:01 -0800 Subject: [PATCH 0847/1310] Postpone loading the TFLite model until a supported event is recorded. Bug: 267050081 Test: atest libinput_tests Change-Id: I09666da123a58786e8a6d47d4c29a475e92f2bbf --- include/input/MotionPredictor.h | 2 ++ libs/input/MotionPredictor.cpp | 11 ++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/include/input/MotionPredictor.h b/include/input/MotionPredictor.h index 3fae4e6b68..68ebf75fc6 100644 --- a/include/input/MotionPredictor.h +++ b/include/input/MotionPredictor.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -73,6 +74,7 @@ public: private: const nsecs_t mPredictionTimestampOffsetNanos; + const std::string mModelPath; const std::function mCheckMotionPredictionEnabled; std::unique_ptr mModel; diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp index 0f889e8128..7d11ef2575 100644 --- a/libs/input/MotionPredictor.cpp +++ b/libs/input/MotionPredictor.cpp @@ -65,9 +65,8 @@ TfLiteMotionPredictorSample::Point convertPrediction( MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos, const char* modelPath, std::function checkMotionPredictionEnabled) : mPredictionTimestampOffsetNanos(predictionTimestampOffsetNanos), - mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)), - mModel(TfLiteMotionPredictorModel::create(modelPath == nullptr ? DEFAULT_MODEL_PATH - : modelPath)) {} + mModelPath(modelPath == nullptr ? DEFAULT_MODEL_PATH : modelPath), + mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)) {} void MotionPredictor::record(const MotionEvent& event) { if (!isPredictionAvailable(event.getDeviceId(), event.getSource())) { @@ -76,6 +75,11 @@ void MotionPredictor::record(const MotionEvent& event) { return; } + // Initialise the model now that it's likely to be used. + if (!mModel) { + mModel = TfLiteMotionPredictorModel::create(mModelPath.c_str()); + } + TfLiteMotionPredictorBuffers& buffers = mDeviceBuffers.try_emplace(event.getDeviceId(), mModel->inputLength()).first->second; @@ -130,6 +134,7 @@ std::vector> MotionPredictor::predict(nsecs_t times continue; } + LOG_ALWAYS_FATAL_IF(!mModel); buffer.copyTo(*mModel); LOG_ALWAYS_FATAL_IF(!mModel->invoke()); -- GitLab From 02dd059d9460c178eb188c9ca61ceb8c98db6b48 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Fri, 10 Feb 2023 20:16:57 +0000 Subject: [PATCH 0848/1310] JPEG/R refactory: rename jpegencoder and jpegdecoder Rename those to ***helper Bug: 264715926 Test: build Change-Id: Ibcfd8554538a711b264f3d4227a9037302437af1 --- libs/jpegrecoverymap/Android.bp | 4 +-- .../{jpegdecoder.h => jpegdecoderhelper.h} | 13 ++++--- .../{jpegencoder.h => jpegencoderhelper.h} | 12 +++---- ...{jpegdecoder.cpp => jpegdecoderhelper.cpp} | 36 +++++++++---------- ...{jpegencoder.cpp => jpegencoderhelper.cpp} | 36 +++++++++---------- libs/jpegrecoverymap/recoverymap.cpp | 20 +++++------ libs/jpegrecoverymap/tests/Android.bp | 8 ++--- ...er_test.cpp => jpegdecoderhelper_test.cpp} | 26 +++++++------- ...er_test.cpp => jpegencoderhelper_test.cpp} | 30 ++++++++-------- 9 files changed, 92 insertions(+), 93 deletions(-) rename libs/jpegrecoverymap/include/jpegrecoverymap/{jpegdecoder.h => jpegdecoderhelper.h} (94%) rename libs/jpegrecoverymap/include/jpegrecoverymap/{jpegencoder.h => jpegencoderhelper.h} (93%) rename libs/jpegrecoverymap/{jpegdecoder.cpp => jpegdecoderhelper.cpp} (91%) rename libs/jpegrecoverymap/{jpegencoder.cpp => jpegencoderhelper.cpp} (87%) rename libs/jpegrecoverymap/tests/{jpegdecoder_test.cpp => jpegdecoderhelper_test.cpp} (79%) rename libs/jpegrecoverymap/tests/{jpegencoder_test.cpp => jpegencoderhelper_test.cpp} (83%) diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp index 2c4b3bff77..ee112e802c 100644 --- a/libs/jpegrecoverymap/Android.bp +++ b/libs/jpegrecoverymap/Android.bp @@ -57,7 +57,7 @@ cc_library { export_include_dirs: ["include"], srcs: [ - "jpegencoder.cpp", + "jpegencoderhelper.cpp", ], } @@ -73,6 +73,6 @@ cc_library { export_include_dirs: ["include"], srcs: [ - "jpegdecoder.cpp", + "jpegdecoderhelper.cpp", ], } diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h similarity index 94% rename from libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h rename to libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h index 419b63d1de..485869da25 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h @@ -1,4 +1,3 @@ - /* * Copyright 2022 The Android Open Source Project * @@ -15,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H -#define ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H +#ifndef ANDROID_JPEGRECOVERYMAP_JPEGDECODERHELPER_H +#define ANDROID_JPEGRECOVERYMAP_JPEGDECODERHELPER_H // We must include cstdio before jpeglib.h. It is a requirement of libjpeg. #include @@ -31,10 +30,10 @@ namespace android::recoverymap { * Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format. * This class is not thread-safe. */ -class JpegDecoder { +class JpegDecoderHelper { public: - JpegDecoder(); - ~JpegDecoder(); + JpegDecoderHelper(); + ~JpegDecoderHelper(); /* * Decompresses JPEG image to raw image (YUV420planer, grey-scale or RGBA) format. After * calling this method, call getDecompressedImage() to get the image. @@ -118,4 +117,4 @@ private: }; } /* namespace android */ -#endif // ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H +#endif // ANDROID_JPEGRECOVERYMAP_JPEGDECODERHELPER_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h similarity index 93% rename from libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h rename to libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h index 61aeb8ace7..f087b55b6f 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H -#define ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H +#ifndef ANDROID_JPEGRECOVERYMAP_JPEGENCODERHELPER_H +#define ANDROID_JPEGRECOVERYMAP_JPEGENCODERHELPER_H // We must include cstdio before jpeglib.h. It is a requirement of libjpeg. #include @@ -34,10 +34,10 @@ namespace android::recoverymap { * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format. * This class is not thread-safe. */ -class JpegEncoder { +class JpegEncoderHelper { public: - JpegEncoder(); - ~JpegEncoder(); + JpegEncoderHelper(); + ~JpegEncoderHelper(); /* * Compresses YUV420Planer image to JPEG format. After calling this method, call @@ -92,4 +92,4 @@ private: } /* namespace android */ -#endif // ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H +#endif // ANDROID_JPEGRECOVERYMAP_JPEGENCODERHELPER_H diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoderhelper.cpp similarity index 91% rename from libs/jpegrecoverymap/jpegdecoder.cpp rename to libs/jpegrecoverymap/jpegdecoderhelper.cpp index 1bf609a54c..0754ec9dd6 100644 --- a/libs/jpegrecoverymap/jpegdecoder.cpp +++ b/libs/jpegrecoverymap/jpegdecoderhelper.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include @@ -90,14 +90,14 @@ static void jpegrerror_exit(j_common_ptr cinfo) { longjmp(err->setjmp_buffer, 1); } -JpegDecoder::JpegDecoder() { +JpegDecoderHelper::JpegDecoderHelper() { mExifPos = 0; } -JpegDecoder::~JpegDecoder() { +JpegDecoderHelper::~JpegDecoderHelper() { } -bool JpegDecoder::decompressImage(const void* image, int length, bool decodeToRGBA) { +bool JpegDecoderHelper::decompressImage(const void* image, int length, bool decodeToRGBA) { if (image == nullptr || length <= 0) { ALOGE("Image size can not be handled: %d", length); return false; @@ -112,39 +112,39 @@ bool JpegDecoder::decompressImage(const void* image, int length, bool decodeToRG return true; } -void* JpegDecoder::getDecompressedImagePtr() { +void* JpegDecoderHelper::getDecompressedImagePtr() { return mResultBuffer.data(); } -size_t JpegDecoder::getDecompressedImageSize() { +size_t JpegDecoderHelper::getDecompressedImageSize() { return mResultBuffer.size(); } -void* JpegDecoder::getXMPPtr() { +void* JpegDecoderHelper::getXMPPtr() { return mXMPBuffer.data(); } -size_t JpegDecoder::getXMPSize() { +size_t JpegDecoderHelper::getXMPSize() { return mXMPBuffer.size(); } -void* JpegDecoder::getEXIFPtr() { +void* JpegDecoderHelper::getEXIFPtr() { return mEXIFBuffer.data(); } -size_t JpegDecoder::getEXIFSize() { +size_t JpegDecoderHelper::getEXIFSize() { return mEXIFBuffer.size(); } -size_t JpegDecoder::getDecompressedImageWidth() { +size_t JpegDecoderHelper::getDecompressedImageWidth() { return mWidth; } -size_t JpegDecoder::getDecompressedImageHeight() { +size_t JpegDecoderHelper::getDecompressedImageHeight() { return mHeight; } -bool JpegDecoder::decode(const void* image, int length, bool decodeToRGBA) { +bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) { jpeg_decompress_struct cinfo; jpegr_source_mgr mgr(static_cast(image), length); jpegrerror_mgr myerr; @@ -248,7 +248,7 @@ bool JpegDecoder::decode(const void* image, int length, bool decodeToRGBA) { return true; } -bool JpegDecoder::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, +bool JpegDecoderHelper::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, bool isSingleChannel) { if (isSingleChannel) { return decompressSingleChannel(cinfo, dest); @@ -259,7 +259,7 @@ bool JpegDecoder::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, return decompressYUV(cinfo, dest); } -bool JpegDecoder::getCompressedImageParameters(const void* image, int length, +bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int length, size_t *pWidth, size_t *pHeight, std::vector *iccData , std::vector *exifData) { jpeg_decompress_struct cinfo; @@ -326,7 +326,7 @@ bool JpegDecoder::getCompressedImageParameters(const void* image, int length, return true; } -bool JpegDecoder::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) { +bool JpegDecoderHelper::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) { JSAMPLE* decodeDst = (JSAMPLE*) dest; uint32_t lines = 0; // TODO: use batches for more effectiveness @@ -341,7 +341,7 @@ bool JpegDecoder::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* d return lines == cinfo->image_height; } -bool JpegDecoder::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) { +bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) { JSAMPROW y[kCompressBatchSize]; JSAMPROW cb[kCompressBatchSize / 2]; @@ -386,7 +386,7 @@ bool JpegDecoder::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* de return true; } -bool JpegDecoder::decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest) { +bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest) { JSAMPROW y[kCompressBatchSize]; JSAMPARRAY planes[1] {y}; diff --git a/libs/jpegrecoverymap/jpegencoder.cpp b/libs/jpegrecoverymap/jpegencoderhelper.cpp similarity index 87% rename from libs/jpegrecoverymap/jpegencoder.cpp rename to libs/jpegrecoverymap/jpegencoderhelper.cpp index 627dcdf6ee..54b184d2ef 100644 --- a/libs/jpegrecoverymap/jpegencoder.cpp +++ b/libs/jpegrecoverymap/jpegencoderhelper.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include @@ -22,20 +22,20 @@ namespace android::recoverymap { -// The destination manager that can access |mResultBuffer| in JpegEncoder. +// The destination manager that can access |mResultBuffer| in JpegEncoderHelper. struct destination_mgr { public: struct jpeg_destination_mgr mgr; - JpegEncoder* encoder; + JpegEncoderHelper* encoder; }; -JpegEncoder::JpegEncoder() { +JpegEncoderHelper::JpegEncoderHelper() { } -JpegEncoder::~JpegEncoder() { +JpegEncoderHelper::~JpegEncoderHelper() { } -bool JpegEncoder::compressImage(const void* image, int width, int height, int quality, +bool JpegEncoderHelper::compressImage(const void* image, int width, int height, int quality, const void* iccBuffer, unsigned int iccSize, bool isSingleChannel) { if (width % 8 != 0 || height % 2 != 0) { @@ -52,15 +52,15 @@ bool JpegEncoder::compressImage(const void* image, int width, int height, int qu return true; } -void* JpegEncoder::getCompressedImagePtr() { +void* JpegEncoderHelper::getCompressedImagePtr() { return mResultBuffer.data(); } -size_t JpegEncoder::getCompressedImageSize() { +size_t JpegEncoderHelper::getCompressedImageSize() { return mResultBuffer.size(); } -void JpegEncoder::initDestination(j_compress_ptr cinfo) { +void JpegEncoderHelper::initDestination(j_compress_ptr cinfo) { destination_mgr* dest = reinterpret_cast(cinfo->dest); std::vector& buffer = dest->encoder->mResultBuffer; buffer.resize(kBlockSize); @@ -68,7 +68,7 @@ void JpegEncoder::initDestination(j_compress_ptr cinfo) { dest->mgr.free_in_buffer = buffer.size(); } -boolean JpegEncoder::emptyOutputBuffer(j_compress_ptr cinfo) { +boolean JpegEncoderHelper::emptyOutputBuffer(j_compress_ptr cinfo) { destination_mgr* dest = reinterpret_cast(cinfo->dest); std::vector& buffer = dest->encoder->mResultBuffer; size_t oldsize = buffer.size(); @@ -78,13 +78,13 @@ boolean JpegEncoder::emptyOutputBuffer(j_compress_ptr cinfo) { return true; } -void JpegEncoder::terminateDestination(j_compress_ptr cinfo) { +void JpegEncoderHelper::terminateDestination(j_compress_ptr cinfo) { destination_mgr* dest = reinterpret_cast(cinfo->dest); std::vector& buffer = dest->encoder->mResultBuffer; buffer.resize(buffer.size() - dest->mgr.free_in_buffer); } -void JpegEncoder::outputErrorMessage(j_common_ptr cinfo) { +void JpegEncoderHelper::outputErrorMessage(j_common_ptr cinfo) { char buffer[JMSG_LENGTH_MAX]; /* Create the message */ @@ -92,7 +92,7 @@ void JpegEncoder::outputErrorMessage(j_common_ptr cinfo) { ALOGE("%s\n", buffer); } -bool JpegEncoder::encode(const void* image, int width, int height, int jpegQuality, +bool JpegEncoderHelper::encode(const void* image, int width, int height, int jpegQuality, const void* iccBuffer, unsigned int iccSize, bool isSingleChannel) { jpeg_compress_struct cinfo; jpeg_error_mgr jerr; @@ -118,7 +118,7 @@ bool JpegEncoder::encode(const void* image, int width, int height, int jpegQuali return true; } -void JpegEncoder::setJpegDestination(jpeg_compress_struct* cinfo) { +void JpegEncoderHelper::setJpegDestination(jpeg_compress_struct* cinfo) { destination_mgr* dest = static_cast((*cinfo->mem->alloc_small) ( (j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(destination_mgr))); dest->encoder = this; @@ -128,7 +128,7 @@ void JpegEncoder::setJpegDestination(jpeg_compress_struct* cinfo) { cinfo->dest = reinterpret_cast(dest); } -void JpegEncoder::setJpegCompressStruct(int width, int height, int quality, +void JpegEncoderHelper::setJpegCompressStruct(int width, int height, int quality, jpeg_compress_struct* cinfo, bool isSingleChannel) { cinfo->image_width = width; cinfo->image_height = height; @@ -158,7 +158,7 @@ void JpegEncoder::setJpegCompressStruct(int width, int height, int quality, } } -bool JpegEncoder::compress( +bool JpegEncoderHelper::compress( jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel) { if (isSingleChannel) { return compressSingleChannel(cinfo, image); @@ -166,7 +166,7 @@ bool JpegEncoder::compress( return compressYuv(cinfo, image); } -bool JpegEncoder::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) { +bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) { JSAMPROW y[kCompressBatchSize]; JSAMPROW cb[kCompressBatchSize / 2]; JSAMPROW cr[kCompressBatchSize / 2]; @@ -210,7 +210,7 @@ bool JpegEncoder::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) { return true; } -bool JpegEncoder::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image) { +bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image) { JSAMPROW y[kCompressBatchSize]; JSAMPARRAY planes[1] {y}; diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index 8b8c2e7fe8..218c43017b 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -15,8 +15,8 @@ */ #include -#include -#include +#include +#include #include #include @@ -146,7 +146,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jrTransFunc_to_skTransFunc.at(JPEGR_TF_SRGB), jrGamut_to_skGamut.at(uncompressed_yuv_420_image.colorGamut)); - JpegEncoder jpeg_encoder; + JpegEncoderHelper jpeg_encoder; if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data, uncompressed_yuv_420_image.width, uncompressed_yuv_420_image.height, quality, @@ -210,7 +210,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jrTransFunc_to_skTransFunc.at(JPEGR_TF_SRGB), jrGamut_to_skGamut.at(uncompressed_yuv_420_image->colorGamut)); - JpegEncoder jpeg_encoder; + JpegEncoderHelper jpeg_encoder; if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data, uncompressed_yuv_420_image->width, uncompressed_yuv_420_image->height, quality, @@ -289,7 +289,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return ERROR_JPEGR_INVALID_INPUT_TYPE; } - JpegDecoder jpeg_decoder; + JpegDecoderHelper jpeg_decoder; if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) { return ERROR_JPEGR_DECODE_ERROR; } @@ -334,7 +334,7 @@ status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, JPEGR_CHECK(extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, &primary_image, &recovery_map)); - JpegDecoder jpeg_decoder; + JpegDecoderHelper jpeg_decoder; if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length, &jpegr_info->width, &jpegr_info->height, jpegr_info->iccData, jpegr_info->exifData)) { @@ -356,7 +356,7 @@ status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, (void) exif; if (request_sdr) { - JpegDecoder jpeg_decoder; + JpegDecoderHelper jpeg_decoder; if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length, true)) { return ERROR_JPEGR_DECODE_ERROR; @@ -376,12 +376,12 @@ status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, jpegr_metadata metadata; JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map)); - JpegDecoder jpeg_decoder; + JpegDecoderHelper jpeg_decoder; if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) { return ERROR_JPEGR_DECODE_ERROR; } - JpegDecoder recovery_map_decoder; + JpegDecoderHelper recovery_map_decoder; if (!recovery_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) { return ERROR_JPEGR_DECODE_ERROR; } @@ -411,7 +411,7 @@ status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recov return ERROR_JPEGR_INVALID_NULL_PTR; } - JpegEncoder jpeg_encoder; + JpegEncoderHelper jpeg_encoder; if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data, uncompressed_recovery_map->width, uncompressed_recovery_map->height, diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp index e381caf025..e416db9e2d 100644 --- a/libs/jpegrecoverymap/tests/Android.bp +++ b/libs/jpegrecoverymap/tests/Android.bp @@ -44,10 +44,10 @@ cc_test { } cc_test { - name: "libjpegencoder_test", + name: "libjpegencoderhelper_test", test_suites: ["device-tests"], srcs: [ - "jpegencoder_test.cpp", + "jpegencoderhelper_test.cpp", ], shared_libs: [ "libjpeg", @@ -60,10 +60,10 @@ cc_test { } cc_test { - name: "libjpegdecoder_test", + name: "libjpegdecoderhelper_test", test_suites: ["device-tests"], srcs: [ - "jpegdecoder_test.cpp", + "jpegdecoderhelper_test.cpp", ], shared_libs: [ "libjpeg", diff --git a/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp b/libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp similarity index 79% rename from libs/jpegrecoverymap/tests/jpegdecoder_test.cpp rename to libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp index 8e013517fb..278bf3b847 100644 --- a/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp +++ b/libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include #include @@ -27,14 +27,14 @@ namespace android::recoverymap { #define GREY_IMAGE "/sdcard/Documents/minnie-320x240-y.jpg" #define GREY_IMAGE_SIZE 20193 -class JpegDecoderTest : public testing::Test { +class JpegDecoderHelperTest : public testing::Test { public: struct Image { std::unique_ptr buffer; size_t size; }; - JpegDecoderTest(); - ~JpegDecoderTest(); + JpegDecoderHelperTest(); + ~JpegDecoderHelperTest(); protected: virtual void SetUp(); virtual void TearDown(); @@ -42,9 +42,9 @@ protected: Image mYuvImage, mGreyImage; }; -JpegDecoderTest::JpegDecoderTest() {} +JpegDecoderHelperTest::JpegDecoderHelperTest() {} -JpegDecoderTest::~JpegDecoderTest() {} +JpegDecoderHelperTest::~JpegDecoderHelperTest() {} static size_t getFileSize(int fd) { struct stat st; @@ -55,7 +55,7 @@ static size_t getFileSize(int fd) { return st.st_size; // bytes } -static bool loadFile(const char filename[], JpegDecoderTest::Image* result) { +static bool loadFile(const char filename[], JpegDecoderHelperTest::Image* result) { int fd = open(filename, O_CLOEXEC); if (fd < 0) { return false; @@ -74,7 +74,7 @@ static bool loadFile(const char filename[], JpegDecoderTest::Image* result) { return true; } -void JpegDecoderTest::SetUp() { +void JpegDecoderHelperTest::SetUp() { if (!loadFile(YUV_IMAGE, &mYuvImage)) { FAIL() << "Load file " << YUV_IMAGE << " failed"; } @@ -85,16 +85,16 @@ void JpegDecoderTest::SetUp() { mGreyImage.size = GREY_IMAGE_SIZE; } -void JpegDecoderTest::TearDown() {} +void JpegDecoderHelperTest::TearDown() {} -TEST_F(JpegDecoderTest, decodeYuvImage) { - JpegDecoder decoder; +TEST_F(JpegDecoderHelperTest, decodeYuvImage) { + JpegDecoderHelper decoder; EXPECT_TRUE(decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size)); ASSERT_GT(decoder.getDecompressedImageSize(), static_cast(0)); } -TEST_F(JpegDecoderTest, decodeGreyImage) { - JpegDecoder decoder; +TEST_F(JpegDecoderHelperTest, decodeGreyImage) { + JpegDecoderHelper decoder; EXPECT_TRUE(decoder.decompressImage(mGreyImage.buffer.get(), mGreyImage.size)); ASSERT_GT(decoder.getDecompressedImageSize(), static_cast(0)); } diff --git a/libs/jpegrecoverymap/tests/jpegencoder_test.cpp b/libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp similarity index 83% rename from libs/jpegrecoverymap/tests/jpegencoder_test.cpp rename to libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp index 4cd2a5ef8c..532688d06f 100644 --- a/libs/jpegrecoverymap/tests/jpegencoder_test.cpp +++ b/libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include #include @@ -33,15 +33,15 @@ namespace android::recoverymap { #define INVALID_SIZE_IMAGE_HEIGHT 240 #define JPEG_QUALITY 90 -class JpegEncoderTest : public testing::Test { +class JpegEncoderHelperTest : public testing::Test { public: struct Image { std::unique_ptr buffer; size_t width; size_t height; }; - JpegEncoderTest(); - ~JpegEncoderTest(); + JpegEncoderHelperTest(); + ~JpegEncoderHelperTest(); protected: virtual void SetUp(); virtual void TearDown(); @@ -49,9 +49,9 @@ protected: Image mValidImage, mInvalidSizeImage, mSingleChannelImage; }; -JpegEncoderTest::JpegEncoderTest() {} +JpegEncoderHelperTest::JpegEncoderHelperTest() {} -JpegEncoderTest::~JpegEncoderTest() {} +JpegEncoderHelperTest::~JpegEncoderHelperTest() {} static size_t getFileSize(int fd) { struct stat st; @@ -62,7 +62,7 @@ static size_t getFileSize(int fd) { return st.st_size; // bytes } -static bool loadFile(const char filename[], JpegEncoderTest::Image* result) { +static bool loadFile(const char filename[], JpegEncoderHelperTest::Image* result) { int fd = open(filename, O_CLOEXEC); if (fd < 0) { return false; @@ -81,7 +81,7 @@ static bool loadFile(const char filename[], JpegEncoderTest::Image* result) { return true; } -void JpegEncoderTest::SetUp() { +void JpegEncoderHelperTest::SetUp() { if (!loadFile(VALID_IMAGE, &mValidImage)) { FAIL() << "Load file " << VALID_IMAGE << " failed"; } @@ -99,23 +99,23 @@ void JpegEncoderTest::SetUp() { mSingleChannelImage.height = SINGLE_CHANNEL_IMAGE_HEIGHT; } -void JpegEncoderTest::TearDown() {} +void JpegEncoderHelperTest::TearDown() {} -TEST_F(JpegEncoderTest, validImage) { - JpegEncoder encoder; +TEST_F(JpegEncoderHelperTest, validImage) { + JpegEncoderHelper encoder; EXPECT_TRUE(encoder.compressImage(mValidImage.buffer.get(), mValidImage.width, mValidImage.height, JPEG_QUALITY, NULL, 0)); ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); } -TEST_F(JpegEncoderTest, invalidSizeImage) { - JpegEncoder encoder; +TEST_F(JpegEncoderHelperTest, invalidSizeImage) { + JpegEncoderHelper encoder; EXPECT_FALSE(encoder.compressImage(mInvalidSizeImage.buffer.get(), mInvalidSizeImage.width, mInvalidSizeImage.height, JPEG_QUALITY, NULL, 0)); } -TEST_F(JpegEncoderTest, singleChannelImage) { - JpegEncoder encoder; +TEST_F(JpegEncoderHelperTest, singleChannelImage) { + JpegEncoderHelper encoder; EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), mSingleChannelImage.width, mSingleChannelImage.height, JPEG_QUALITY, NULL, 0, true)); ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); -- GitLab From 3af0ec085d28bb2a67525b07e3664d852386ba27 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Fri, 10 Feb 2023 04:13:48 +0000 Subject: [PATCH 0849/1310] SF: Introduce new frontend logic Re-landing changes with perf regression fix. We were updating the geometry every frame. Changes to allow creating layer snapshots using the new and legacy frontend logic. Switching the logic is controlled by debug flags. By default SF will continue to use the legacy logic so there should be no functional changes with this cl. Bug: 238781169 Test: presubmit Change-Id: Ied235a8f0c860f368afc39ba3998f381e21774d7 --- .../FrontEnd/LayerSnapshotBuilder.cpp | 21 +- .../FrontEnd/LayerSnapshotBuilder.h | 7 +- services/surfaceflinger/Layer.cpp | 56 +- services/surfaceflinger/Layer.h | 39 +- services/surfaceflinger/LayerRenderArea.cpp | 9 +- services/surfaceflinger/SurfaceFlinger.cpp | 619 ++++++++++++++---- services/surfaceflinger/SurfaceFlinger.h | 75 ++- .../fuzzer/surfaceflinger_layer_fuzzer.cpp | 2 +- 8 files changed, 632 insertions(+), 196 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index 6490476396..3ed24b2740 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -391,7 +391,9 @@ bool LayerSnapshotBuilder::tryFastUpdate(const Args& args) { void LayerSnapshotBuilder::updateSnapshots(const Args& args) { ATRACE_NAME("UpdateSnapshots"); - if (args.forceUpdate || args.displayChanges) { + if (args.parentCrop) { + mRootSnapshot.geomLayerBounds = *args.parentCrop; + } else if (args.forceUpdate || args.displayChanges) { mRootSnapshot.geomLayerBounds = getMaxDisplayBounds(args.displays); } if (args.displayChanges) { @@ -618,7 +620,8 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a RequestedLayerState::Changes::AffectsChildren); snapshot.changes = parentChanges | requested.changes; snapshot.isHiddenByPolicyFromParent = parentSnapshot.isHiddenByPolicyFromParent || - parentSnapshot.invalidTransform || requested.isHiddenByPolicy(); + parentSnapshot.invalidTransform || requested.isHiddenByPolicy() || + (args.excludeLayerIds.find(path.id) != args.excludeLayerIds.end()); snapshot.contentDirty = requested.what & layer_state_t::CONTENT_DIRTY; // TODO(b/238781169) scope down the changes to only buffer updates. snapshot.hasReadyFrame = @@ -983,6 +986,20 @@ void LayerSnapshotBuilder::forEachVisibleSnapshot(const ConstVisitor& visitor) c } } +// Visit each visible snapshot in z-order +void LayerSnapshotBuilder::forEachVisibleSnapshot(const ConstVisitor& visitor, + const LayerHierarchy& root) const { + root.traverseInZOrder( + [this, visitor](const LayerHierarchy&, + const LayerHierarchy::TraversalPath& traversalPath) -> bool { + LayerSnapshot* snapshot = getSnapshot(traversalPath); + if (snapshot && snapshot->isVisible) { + visitor(*snapshot); + } + return true; + }); +} + void LayerSnapshotBuilder::forEachVisibleSnapshot(const Visitor& visitor) { for (int i = 0; i < mNumInterestingSnapshots; i++) { std::unique_ptr& snapshot = mSnapshots.at((size_t)i); diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h index abb7e668c3..f4544fd62f 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h @@ -36,7 +36,7 @@ namespace android::surfaceflinger::frontend { class LayerSnapshotBuilder { public: struct Args { - const LayerHierarchy& root; + LayerHierarchy root; const LayerLifecycleManager& layerLifecycleManager; bool forceUpdate = false; bool includeMetadata = false; @@ -46,6 +46,8 @@ public: const renderengine::ShadowSettings& globalShadowSettings; bool supportsBlur = true; bool forceFullDamage = false; + std::optional parentCrop = std::nullopt; + std::unordered_set excludeLayerIds; }; LayerSnapshotBuilder(); @@ -65,6 +67,9 @@ public: // Visit each visible snapshot in z-order void forEachVisibleSnapshot(const ConstVisitor& visitor) const; + // Visit each visible snapshot in z-order + void forEachVisibleSnapshot(const ConstVisitor& visitor, const LayerHierarchy& root) const; + typedef std::function& snapshot)> Visitor; // Visit each visible snapshot in z-order and move the snapshot if needed void forEachVisibleSnapshot(const Visitor& visitor); diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index d1b5fcc0d8..8c484f0039 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -146,7 +146,7 @@ Layer::Layer(const LayerCreationArgs& args) mLayerCreationFlags(args.flags), mBorderEnabled(false), mTextureName(args.textureName), - mLayerFE(args.flinger->getFactory().createLayerFE(mName)) { + mLegacyLayerFE(args.flinger->getFactory().createLayerFE(mName)) { ALOGV("Creating Layer %s", getDebugName()); uint32_t layerFlags = 0; @@ -3119,15 +3119,14 @@ bool Layer::setSidebandStream(const sp& sidebandStream) { return true; } -bool Layer::setTransactionCompletedListeners(const std::vector>& handles) { +bool Layer::setTransactionCompletedListeners(const std::vector>& handles, + bool willPresent) { // If there is no handle, we will not send a callback so reset mReleasePreviousBuffer and return if (handles.empty()) { mReleasePreviousBuffer = false; return false; } - const bool willPresent = willPresentCurrentTransaction(); - std::deque> remainingHandles; for (const auto& handle : handles) { // If this transaction set a buffer on this layer, release its previous buffer @@ -3210,11 +3209,10 @@ bool Layer::fenceHasSignaled() const { return fenceSignaled; } -bool Layer::onPreComposition(nsecs_t refreshStartTime) { +void Layer::onPreComposition(nsecs_t refreshStartTime) { for (const auto& handle : mDrawingState.callbackHandles) { handle->refreshStartTime = refreshStartTime; } - return hasReadyFrame(); } void Layer::setAutoRefresh(bool autoRefresh) { @@ -3600,7 +3598,7 @@ bool Layer::isHdrY410() const { sp Layer::getCompositionEngineLayerFE() const { // There's no need to get a CE Layer if the layer isn't going to draw anything. - return hasSomethingToDraw() ? mLayerFE : nullptr; + return hasSomethingToDraw() ? mLegacyLayerFE : nullptr; } const LayerSnapshot* Layer::getLayerSnapshot() const { @@ -3611,16 +3609,36 @@ LayerSnapshot* Layer::editLayerSnapshot() { return mSnapshot.get(); } +std::unique_ptr Layer::stealLayerSnapshot() { + return std::move(mSnapshot); +} + +void Layer::updateLayerSnapshot(std::unique_ptr snapshot) { + mSnapshot = std::move(snapshot); +} + const compositionengine::LayerFECompositionState* Layer::getCompositionState() const { return mSnapshot.get(); } sp Layer::copyCompositionEngineLayerFE() const { - auto result = mFlinger->getFactory().createLayerFE(mLayerFE->getDebugName()); + auto result = mFlinger->getFactory().createLayerFE(mName); result->mSnapshot = std::make_unique(*mSnapshot); return result; } +sp Layer::getCompositionEngineLayerFE( + const frontend::LayerHierarchy::TraversalPath& path) { + for (auto& [p, layerFE] : mLayerFEs) { + if (p == path) { + return layerFE; + } + } + auto layerFE = mFlinger->getFactory().createLayerFE(mName); + mLayerFEs.emplace_back(path, layerFE); + return layerFE; +} + void Layer::useSurfaceDamage() { if (mFlinger->mForceFullDamage) { surfaceDamageRegion = Region::INVALID_REGION; @@ -4016,28 +4034,6 @@ void Layer::updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMet } } -LayerSnapshotGuard::LayerSnapshotGuard(Layer* layer) : mLayer(layer) { - if (mLayer) { - mLayer->mLayerFE->mSnapshot = std::move(mLayer->mSnapshot); - } -} - -LayerSnapshotGuard::~LayerSnapshotGuard() { - if (mLayer) { - mLayer->mSnapshot = std::move(mLayer->mLayerFE->mSnapshot); - } -} - -LayerSnapshotGuard::LayerSnapshotGuard(LayerSnapshotGuard&& other) : mLayer(other.mLayer) { - other.mLayer = nullptr; -} - -LayerSnapshotGuard& LayerSnapshotGuard::operator=(LayerSnapshotGuard&& other) { - mLayer = other.mLayer; - other.mLayer = nullptr; - return *this; -} - void Layer::setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds, TrustedPresentationListener const& listener) { bool hadTrustedPresentationListener = hasTrustedPresentationListener(); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index e2bae23ca5..bf8cc7e36b 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -309,7 +309,8 @@ public: bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/); bool setApi(int32_t /*api*/); bool setSidebandStream(const sp& /*sidebandStream*/); - bool setTransactionCompletedListeners(const std::vector>& /*handles*/); + bool setTransactionCompletedListeners(const std::vector>& /*handles*/, + bool willPresent); virtual bool setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace); virtual bool setColorSpaceAgnostic(const bool agnostic); virtual bool setDimmingEnabled(const bool dimmingEnabled); @@ -330,9 +331,12 @@ public: virtual sp getCompositionEngineLayerFE() const; virtual sp copyCompositionEngineLayerFE() const; + sp getCompositionEngineLayerFE(const frontend::LayerHierarchy::TraversalPath&); const frontend::LayerSnapshot* getLayerSnapshot() const; frontend::LayerSnapshot* editLayerSnapshot(); + std::unique_ptr stealLayerSnapshot(); + void updateLayerSnapshot(std::unique_ptr snapshot); // If we have received a new buffer this frame, we will pass its surface // damage down to hardware composer. Otherwise, we must send a region with @@ -514,7 +518,7 @@ public: // implements compositionengine::LayerFE const compositionengine::LayerFECompositionState* getCompositionState() const; bool fenceHasSignaled() const; - bool onPreComposition(nsecs_t refreshStartTime); + void onPreComposition(nsecs_t refreshStartTime); void onLayerDisplayed(ftl::SharedFuture); void setWasClientComposed(const sp& fence) { @@ -834,6 +838,7 @@ public: void updateMetadataSnapshot(const LayerMetadata& parentMetadata); void updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata, std::unordered_set& visited); + bool willPresentCurrentTransaction() const; void callReleaseBufferCallback(const sp& listener, const sp& buffer, uint64_t framenumber, @@ -1043,8 +1048,6 @@ private: // Crop that applies to the buffer Rect computeBufferCrop(const State& s); - bool willPresentCurrentTransaction() const; - void callReleaseBufferCallback(const sp& listener, const sp& buffer, uint64_t framenumber, const sp& releaseFence, @@ -1161,34 +1164,10 @@ private: // not specify a destination frame. ui::Transform mRequestedTransform; - sp mLayerFE; + sp mLegacyLayerFE; + std::vector>> mLayerFEs; std::unique_ptr mSnapshot = std::make_unique(); - - friend class LayerSnapshotGuard; -}; - -// LayerSnapshotGuard manages the movement of LayerSnapshot between a Layer and its corresponding -// LayerFE. This class must be used whenever LayerFEs are passed to CompositionEngine. Instances of -// LayerSnapshotGuard should only be constructed on the main thread and should not be moved outside -// the main thread. -// -// Moving the snapshot instead of sharing common state prevents use of LayerFE outside the main -// thread by making errors obvious (i.e. use outside the main thread results in SEGFAULTs due to -// nullptr dereference). -class LayerSnapshotGuard { -public: - LayerSnapshotGuard(Layer* layer) REQUIRES(kMainThreadContext); - ~LayerSnapshotGuard() REQUIRES(kMainThreadContext); - - LayerSnapshotGuard(const LayerSnapshotGuard&) = delete; - LayerSnapshotGuard& operator=(const LayerSnapshotGuard&) = delete; - - LayerSnapshotGuard(LayerSnapshotGuard&& other) REQUIRES(kMainThreadContext); - LayerSnapshotGuard& operator=(LayerSnapshotGuard&& other) REQUIRES(kMainThreadContext); - -private: - Layer* mLayer; }; std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate); diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp index 2b4375b0fa..03a7f226ed 100644 --- a/services/surfaceflinger/LayerRenderArea.cpp +++ b/services/surfaceflinger/LayerRenderArea.cpp @@ -69,6 +69,14 @@ Rect LayerRenderArea::getSourceCrop() const { void LayerRenderArea::render(std::function drawLayers) { using namespace std::string_literals; + if (!mChildrenOnly) { + mTransform = mLayer->getTransform().inverse(); + } + + if (mFlinger.mLayerLifecycleManagerEnabled) { + drawLayers(); + return; + } // If layer is offscreen, update mirroring info if it exists if (mLayer->isRemovedFromCurrentState()) { mLayer->traverse(LayerVector::StateSet::Drawing, @@ -78,7 +86,6 @@ void LayerRenderArea::render(std::function drawLayers) { } if (!mChildrenOnly) { - mTransform = mLayer->getTransform().inverse(); // If the layer is offscreen, compute bounds since we don't compute bounds for offscreen // layers in a regular cycles. if (mLayer->isRemovedFromCurrentState()) { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index c7c91ce2fa..c88757a978 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -173,6 +173,8 @@ using namespace std::string_view_literals; using namespace hardware::configstore; using namespace hardware::configstore::V1_0; using namespace sysprop; +using ftl::Flags; +using namespace ftl::flag_operators; using aidl::android::hardware::graphics::common::DisplayDecorationSupport; using aidl::android::hardware::graphics::composer3::Capability; @@ -470,6 +472,10 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI mPowerHintSessionMode = {.late = base::GetBoolProperty("debug.sf.send_late_power_session_hint"s, true), .early = base::GetBoolProperty("debug.sf.send_early_power_session_hint"s, false)}; + mLayerLifecycleManagerEnabled = + base::GetBoolProperty("debug.sf.enable_layer_lifecycle_manager"s, false); + mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled || + base::GetBoolProperty("debug.sf.enable_legacy_frontend"s, true); } LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() { @@ -2127,6 +2133,110 @@ void SurfaceFlinger::configure() FTL_FAKE_GUARD(kMainThreadContext) { } } +bool SurfaceFlinger::updateLayerSnapshotsLegacy(VsyncId vsyncId, LifecycleUpdate& update, + bool transactionsFlushed, + bool& outTransactionsAreEmpty) { + bool needsTraversal = false; + if (transactionsFlushed) { + needsTraversal |= commitMirrorDisplays(vsyncId); + needsTraversal |= commitCreatedLayers(vsyncId, update.layerCreatedStates); + needsTraversal |= applyTransactions(update.transactions, vsyncId); + } + outTransactionsAreEmpty = !needsTraversal; + const bool shouldCommit = (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal; + if (shouldCommit) { + commitTransactions(); + } + + bool mustComposite = latchBuffers() || shouldCommit; + updateLayerGeometry(); + return mustComposite; +} + +bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, LifecycleUpdate& update, + bool transactionsFlushed, bool& outTransactionsAreEmpty) { + using Changes = frontend::RequestedLayerState::Changes; + ATRACE_NAME("updateLayerSnapshots"); + { + mLayerLifecycleManager.addLayers(std::move(update.newLayers)); + mLayerLifecycleManager.applyTransactions(update.transactions); + mLayerLifecycleManager.onHandlesDestroyed(update.destroyedHandles); + for (auto& legacyLayer : update.layerCreatedStates) { + sp layer = legacyLayer.layer.promote(); + if (layer) { + mLegacyLayers[layer->sequence] = layer; + } + } + } + if (mLayerLifecycleManager.getGlobalChanges().test(Changes::Hierarchy)) { + ATRACE_NAME("LayerHierarchyBuilder:update"); + mLayerHierarchyBuilder.update(mLayerLifecycleManager.getLayers(), + mLayerLifecycleManager.getDestroyedLayers()); + } + + applyAndCommitDisplayTransactionStates(update.transactions); + + { + ATRACE_NAME("LayerSnapshotBuilder:update"); + frontend::LayerSnapshotBuilder::Args args{.root = mLayerHierarchyBuilder.getHierarchy(), + .layerLifecycleManager = mLayerLifecycleManager, + .displays = mFrontEndDisplayInfos, + .displayChanges = mFrontEndDisplayInfosChanged, + .globalShadowSettings = + mDrawingState.globalShadowSettings, + .supportsBlur = mSupportsBlur, + .forceFullDamage = mForceFullDamage}; + mLayerSnapshotBuilder.update(args); + } + + if (mLayerLifecycleManager.getGlobalChanges().any(Changes::Geometry | Changes::Input | + Changes::Hierarchy)) { + mUpdateInputInfo = true; + } + if (mLayerLifecycleManager.getGlobalChanges().any(Changes::VisibleRegion | Changes::Hierarchy | + Changes::Visibility)) { + mVisibleRegionsDirty = true; + } + outTransactionsAreEmpty = mLayerLifecycleManager.getGlobalChanges().get() == 0; + const bool mustComposite = mLayerLifecycleManager.getGlobalChanges().get() != 0; + { + ATRACE_NAME("LLM:commitChanges"); + mLayerLifecycleManager.commitChanges(); + } + + if (!mLegacyFrontEndEnabled) { + ATRACE_NAME("DisplayCallbackAndStatsUpdates"); + applyTransactions(update.transactions, vsyncId); + + bool newDataLatched = false; + for (auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) { + if (!snapshot->changes.test(Changes::Buffer)) continue; + auto it = mLegacyLayers.find(snapshot->sequence); + LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s", + snapshot->getDebugString().c_str()); + mLayersWithQueuedFrames.emplace(it->second); + newDataLatched = true; + if (!snapshot->isVisible) break; + + Region visibleReg; + visibleReg.set(snapshot->transformedBoundsWithoutTransparentRegion); + invalidateLayerStack(snapshot->outputFilter, visibleReg); + } + + for (auto& destroyedLayer : mLayerLifecycleManager.getDestroyedLayers()) { + mLegacyLayers.erase(destroyedLayer->id); + } + + // enter boot animation on first buffer latch + if (CC_UNLIKELY(mBootStage == BootStage::BOOTLOADER && newDataLatched)) { + ALOGI("Enter boot animation"); + mBootStage = BootStage::BOOTANIMATION; + } + commitTransactions(); + } + return mustComposite; +} + bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expectedVsyncTime) FTL_FAKE_GUARD(kMainThreadContext) { // The expectedVsyncTime, which was predicted when this frame was scheduled, is normally in the @@ -2266,45 +2376,34 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe mFrameTimeline->setSfWakeUp(vsyncId.value, frameTime.ns(), Fps::fromPeriodNsecs(vsyncPeriod.ns())); - bool needsTraversal = false; - if (clearTransactionFlags(eTransactionFlushNeeded)) { - // Locking: - // 1. to prevent onHandleDestroyed from being called while the state lock is held, - // we must keep a copy of the transactions (specifically the composer - // states) around outside the scope of the lock. - // 2. Transactions and created layers do not share a lock. To prevent applying - // transactions with layers still in the createdLayer queue, flush the transactions - // before committing the created layers. - std::vector transactions = mTransactionHandler.flushTransactions(); - needsTraversal |= commitMirrorDisplays(vsyncId); - needsTraversal |= commitCreatedLayers(vsyncId); - needsTraversal |= applyTransactions(transactions, vsyncId); + const bool flushTransactions = clearTransactionFlags(eTransactionFlushNeeded); + LifecycleUpdate updates; + if (flushTransactions) { + updates = flushLifecycleUpdates(); } - - const bool shouldCommit = - (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal; - if (shouldCommit) { - commitTransactions(); + bool transactionsAreEmpty; + if (mLegacyFrontEndEnabled) { + mustComposite |= updateLayerSnapshotsLegacy(vsyncId, updates, flushTransactions, + transactionsAreEmpty); + } + if (mLayerLifecycleManagerEnabled) { + mustComposite |= + updateLayerSnapshots(vsyncId, updates, flushTransactions, transactionsAreEmpty); } if (transactionFlushNeeded()) { setTransactionFlags(eTransactionFlushNeeded); } - mustComposite |= shouldCommit; - mustComposite |= latchBuffers(); - // This has to be called after latchBuffers because we want to include the layers that have // been latched in the commit callback - if (!needsTraversal) { + if (transactionsAreEmpty) { // Invoke empty transaction callbacks early. mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */); } else { // Invoke OnCommit callbacks. mTransactionCallbackInvoker.sendCallbacks(true /* onCommitOnly */); } - - updateLayerGeometry(); } // Layers need to get updated (in the previous line) before we can use them for @@ -2391,15 +2490,6 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty; refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty; - std::vector layers; - - mDrawingState.traverseInZOrder([&refreshArgs, &layers](Layer* layer) { - if (auto layerFE = layer->getCompositionEngineLayerFE()) { - layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame); - refreshArgs.layers.push_back(layerFE); - layers.push_back(layer); - } - }); refreshArgs.internalDisplayRotationFlags = DisplayDevice::getPrimaryDisplayRotationFlags(); if (CC_UNLIKELY(mDrawingState.colorMatrixChanged)) { @@ -2426,17 +2516,13 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) // the scheduler. const auto presentTime = systemTime(); - { - std::vector layerSnapshotGuards; - for (Layer* layer : layers) { - layerSnapshotGuards.emplace_back(layer); - } - mCompositionEngine->present(refreshArgs); - } + std::vector> layers = + moveSnapshotsToCompositionArgs(refreshArgs, /*cursorOnly=*/false, vsyncId.value); + mCompositionEngine->present(refreshArgs); + moveSnapshotsFromCompositionArgs(refreshArgs, layers); - for (auto& layer : layers) { - CompositionResult compositionResult{ - layer->getCompositionEngineLayerFE()->stealCompositionResult()}; + for (auto [layer, layerFE] : layers) { + CompositionResult compositionResult{layerFE->stealCompositionResult()}; layer->onPreComposition(compositionResult.refreshStartTime); for (auto releaseFence : compositionResult.releaseFences) { layer->onLayerDisplayed(releaseFence); @@ -2530,7 +2616,7 @@ void SurfaceFlinger::updateLayerGeometry() { for (auto& layer : mLayersPendingRefresh) { Region visibleReg; visibleReg.set(layer->getScreenBounds()); - invalidateLayerStack(layer, visibleReg); + invalidateLayerStack(layer->getOutputFilter(), visibleReg); } mLayersPendingRefresh.clear(); } @@ -3387,7 +3473,8 @@ void SurfaceFlinger::processDisplayChangesLocked() { void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { // Commit display transactions. const bool displayTransactionNeeded = transactionFlags & eDisplayTransactionNeeded; - if (displayTransactionNeeded) { + mFrontEndDisplayInfosChanged = displayTransactionNeeded; + if (displayTransactionNeeded && !mLayerLifecycleManagerEnabled) { processDisplayChangesLocked(); mFrontEndDisplayInfos.clear(); for (const auto& [_, display] : mDisplays) { @@ -3478,7 +3565,7 @@ void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { // this layer is not visible anymore Region visibleReg; visibleReg.set(layer->getScreenBounds()); - invalidateLayerStack(sp::fromExisting(layer), visibleReg); + invalidateLayerStack(layer->getOutputFilter(), visibleReg); } }); } @@ -3566,16 +3653,23 @@ void SurfaceFlinger::buildWindowInfos(std::vector& outWindowInfos, outWindowInfos.reserve(sNumWindowInfos); sNumWindowInfos = 0; - mDrawingState.traverseInReverseZOrder([&](Layer* layer) { - if (!layer->needsInputInfo()) return; - - const auto opt = mFrontEndDisplayInfos.get(layer->getLayerStack()) - .transform([](const frontend::DisplayInfo& info) { - return Layer::InputDisplayArgs{&info.transform, info.isSecure}; - }); + if (mLayerLifecycleManagerEnabled) { + mLayerSnapshotBuilder.forEachInputSnapshot( + [&outWindowInfos](const frontend::LayerSnapshot& snapshot) { + outWindowInfos.push_back(snapshot.inputInfo); + }); + } else { + mDrawingState.traverseInReverseZOrder([&](Layer* layer) { + if (!layer->needsInputInfo()) return; + const auto opt = + mFrontEndDisplayInfos.get(layer->getLayerStack()) + .transform([](const frontend::DisplayInfo& info) { + return Layer::InputDisplayArgs{&info.transform, info.isSecure}; + }); - outWindowInfos.push_back(layer->fillInputInfo(opt.value_or(Layer::InputDisplayArgs{}))); - }); + outWindowInfos.push_back(layer->fillInputInfo(opt.value_or(Layer::InputDisplayArgs{}))); + }); + } sNumWindowInfos = outWindowInfos.size(); @@ -3592,17 +3686,9 @@ void SurfaceFlinger::updateCursorAsync() { refreshArgs.outputs.push_back(display->getCompositionDisplay()); } } - - std::vector layerSnapshotGuards; - mDrawingState.traverse([&layerSnapshotGuards](Layer* layer) { - if (layer->getLayerSnapshot()->compositionType == - aidl::android::hardware::graphics::composer3::Composition::CURSOR) { - layer->updateSnapshot(false /* updateGeometry */); - layerSnapshotGuards.emplace_back(layer); - } - }); - + auto layers = moveSnapshotsToCompositionArgs(refreshArgs, /*cursorOnly=*/true, 0); mCompositionEngine->updateCursorAsync(refreshArgs); + moveSnapshotsFromCompositionArgs(refreshArgs, layers); } void SurfaceFlinger::requestDisplayModes(std::vector modeRequests) { @@ -3778,10 +3864,10 @@ void SurfaceFlinger::commitOffscreenLayers() { } } -void SurfaceFlinger::invalidateLayerStack(const sp& layer, const Region& dirty) { +void SurfaceFlinger::invalidateLayerStack(const ui::LayerFilter& layerFilter, const Region& dirty) { for (const auto& [token, displayDevice] : FTL_FAKE_GUARD(mStateLock, mDisplays)) { auto display = displayDevice->getCompositionDisplay(); - if (display->includesLayer(layer->getOutputFilter())) { + if (display->includesLayer(layerFilter)) { display->editState().dirtyRegion.orSelf(dirty); } } @@ -3901,6 +3987,7 @@ status_t SurfaceFlinger::addClientLayer(const LayerCreationArgs& args, const sp< { std::scoped_lock lock(mCreatedLayersLock); mCreatedLayers.emplace_back(layer, parent, args.addToRoot); + mNewLayers.emplace_back(std::make_unique(args)); } setTransactionFlags(eTransactionNeeded); @@ -4268,9 +4355,11 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin const std::vector& listenerCallbacks, int originPid, int originUid, uint64_t transactionId) { uint32_t transactionFlags = 0; - for (DisplayState& display : displays) { - display.sanitize(permissions); - transactionFlags |= setDisplayStateLocked(display); + if (!mLayerLifecycleManagerEnabled) { + for (DisplayState& display : displays) { + display.sanitize(permissions); + transactionFlags |= setDisplayStateLocked(display); + } } // start and end registration for listeners w/ no surface so they can get their callback. Note @@ -4282,9 +4371,16 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin uint32_t clientStateFlags = 0; for (auto& resolvedState : states) { - clientStateFlags |= - setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime, - isAutoTimestamp, postTime, permissions, transactionId); + if (mLegacyFrontEndEnabled) { + clientStateFlags |= + setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime, + isAutoTimestamp, postTime, permissions, transactionId); + + } else /*mLayerLifecycleManagerEnabled*/ { + clientStateFlags |= updateLayerCallbacksAndStats(frameTimelineInfo, resolvedState, + desiredPresentTime, isAutoTimestamp, + postTime, permissions, transactionId); + } if ((flags & eAnimation) && resolvedState.state.surface) { if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) { using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType; @@ -4317,8 +4413,35 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin bool needsTraversal = false; if (transactionFlags) { - // We are on the main thread, we are about to preform a traversal. Clear the traversal bit - // so we don't have to wake up again next frame to preform an unnecessary traversal. + // We are on the main thread, we are about to perform a traversal. Clear the traversal bit + // so we don't have to wake up again next frame to perform an unnecessary traversal. + if (transactionFlags & eTraversalNeeded) { + transactionFlags = transactionFlags & (~eTraversalNeeded); + needsTraversal = true; + } + if (transactionFlags) { + setTransactionFlags(transactionFlags); + } + } + + return needsTraversal; +} + +bool SurfaceFlinger::applyAndCommitDisplayTransactionStates( + std::vector& transactions) { + Mutex::Autolock _l(mStateLock); + bool needsTraversal = false; + uint32_t transactionFlags = 0; + for (auto& transaction : transactions) { + for (DisplayState& display : transaction.displays) { + display.sanitize(transaction.permissions); + transactionFlags |= setDisplayStateLocked(display); + } + } + + if (transactionFlags) { + // We are on the main thread, we are about to perform a traversal. Clear the traversal bit + // so we don't have to wake up again next frame to perform an unnecessary traversal. if (transactionFlags & eTraversalNeeded) { transactionFlags = transactionFlags & (~eTraversalNeeded); needsTraversal = true; @@ -4328,6 +4451,15 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin } } + mFrontEndDisplayInfosChanged = mTransactionFlags & eDisplayTransactionNeeded; + if (mFrontEndDisplayInfosChanged && !mLegacyFrontEndEnabled) { + processDisplayChangesLocked(); + mFrontEndDisplayInfos.clear(); + for (const auto& [_, display] : mDisplays) { + mFrontEndDisplayInfos.try_emplace(display->getLayerStack(), display->getFrontEndInfo()); + } + } + return needsTraversal; } @@ -4718,7 +4850,8 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime // Do nothing. Processing the transaction completed listeners currently cause the flush. } - if (layer->setTransactionCompletedListeners(callbackHandles)) { + if (layer->setTransactionCompletedListeners(callbackHandles, + layer->willPresentCurrentTransaction())) { flags |= eTraversalNeeded; } @@ -4734,6 +4867,96 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime return flags; } +uint32_t SurfaceFlinger::updateLayerCallbacksAndStats(const FrameTimelineInfo& frameTimelineInfo, + ResolvedComposerState& composerState, + int64_t desiredPresentTime, + bool isAutoTimestamp, int64_t postTime, + uint32_t permissions, + uint64_t transactionId) { + layer_state_t& s = composerState.state; + s.sanitize(permissions); + const nsecs_t latchTime = systemTime(); + bool unused; + + std::vector filteredListeners; + for (auto& listener : s.listeners) { + // Starts a registration but separates the callback ids according to callback type. This + // allows the callback invoker to send on latch callbacks earlier. + // note that startRegistration will not re-register if the listener has + // already be registered for a prior surface control + + ListenerCallbacks onCommitCallbacks = listener.filter(CallbackId::Type::ON_COMMIT); + if (!onCommitCallbacks.callbackIds.empty()) { + filteredListeners.push_back(onCommitCallbacks); + } + + ListenerCallbacks onCompleteCallbacks = listener.filter(CallbackId::Type::ON_COMPLETE); + if (!onCompleteCallbacks.callbackIds.empty()) { + filteredListeners.push_back(onCompleteCallbacks); + } + } + + const uint64_t what = s.what; + uint32_t flags = 0; + sp layer = nullptr; + if (s.surface) { + layer = LayerHandle::getLayer(s.surface); + } else { + // The client may provide us a null handle. Treat it as if the layer was removed. + ALOGW("Attempt to set client state with a null layer handle"); + } + if (layer == nullptr) { + for (auto& [listener, callbackIds] : s.listeners) { + mTransactionCallbackInvoker.addCallbackHandle(sp::make(listener, + callbackIds, + s.surface), + std::vector()); + } + return 0; + } + if (what & layer_state_t::eProducerDisconnect) { + layer->onDisconnect(); + } + std::optional dequeueBufferTimestamp; + if (what & layer_state_t::eMetadataChanged) { + dequeueBufferTimestamp = s.metadata.getInt64(gui::METADATA_DEQUEUE_TIME); + } + + std::vector> callbackHandles; + if ((what & layer_state_t::eHasListenerCallbacksChanged) && (!filteredListeners.empty())) { + for (auto& [listener, callbackIds] : filteredListeners) { + callbackHandles.emplace_back( + sp::make(listener, callbackIds, s.surface)); + } + } + if (what & layer_state_t::eSidebandStreamChanged) { + if (layer->setSidebandStream(s.sidebandStream)) flags |= eTraversalNeeded; + } + if (what & layer_state_t::eBufferChanged) { + if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime, + desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp, + frameTimelineInfo)) { + layer->latchBuffer(unused, latchTime); + flags |= eTraversalNeeded; + } + mLayersWithQueuedFrames.emplace(layer); + } else if (frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) { + layer->setFrameTimelineVsyncForBufferlessTransaction(frameTimelineInfo, postTime); + } + + if (what & layer_state_t::eTrustedPresentationInfoChanged) { + layer->setTrustedPresentationInfo(s.trustedPresentationThresholds, + s.trustedPresentationListener); + } + + const auto& snapshot = mLayerSnapshotBuilder.getSnapshot(layer->getSequence()); + bool willPresentCurrentTransaction = + snapshot && (snapshot->hasReadyFrame || snapshot->sidebandStreamHasFrame); + if (layer->setTransactionCompletedListeners(callbackHandles, willPresentCurrentTransaction)) + flags |= eTraversalNeeded; + return flags; +} + uint32_t SurfaceFlinger::addInputWindowCommands(const InputWindowCommands& inputWindowCommands) { bool hasChanges = mInputWindowCommands.merge(inputWindowCommands); return hasChanges ? eTraversalNeeded : 0; @@ -4802,6 +5025,7 @@ status_t SurfaceFlinger::mirrorDisplay(DisplayId displayId, const LayerCreationA LayerCreationArgs mirrorArgs(args); mirrorArgs.flags |= ISurfaceComposerClient::eNoColorFill; mirrorArgs.addToRoot = true; + mirrorArgs.layerStackToMirror = layerStack; result = createEffectLayer(mirrorArgs, &outResult.handle, &rootMirrorLayer); outResult.layerId = rootMirrorLayer->sequence; outResult.layerName = String16(rootMirrorLayer->getDebugName()); @@ -4904,7 +5128,12 @@ void SurfaceFlinger::markLayerPendingRemovalLocked(const sp& layer) { setTransactionFlags(eTransactionNeeded); } -void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp& layer, uint32_t /* layerId */) { +void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp& layer, uint32_t layerId) { + { + std::scoped_lock lock(mCreatedLayersLock); + mDestroyedHandles.emplace_back(layerId); + } + Mutex::Autolock lock(mStateLock); markLayerPendingRemovalLocked(layer); mBufferCountTracker.remove(handle); @@ -4912,6 +5141,8 @@ void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp& layer, uint32 if (mTransactionTracing) { mTransactionTracing->onHandleRemoved(handle); } + + setTransactionFlags(eTransactionFlushNeeded); } void SurfaceFlinger::onInitializeDisplays() { @@ -6481,10 +6712,15 @@ status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, args.useIdentityTransform, args.captureSecureLayers); }); - auto traverseLayers = [this, args, layerStack](const LayerVector::Visitor& visitor) { - traverseLayersInLayerStack(layerStack, args.uid, visitor); - }; - auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + GetLayerSnapshotsFunction getLayerSnapshots; + if (mLayerLifecycleManagerEnabled) { + getLayerSnapshots = getLayerSnapshotsForScreenshots(layerStack, args.uid); + } else { + auto traverseLayers = [this, args, layerStack](const LayerVector::Visitor& visitor) { + traverseLayersInLayerStack(layerStack, args.uid, visitor); + }; + getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + } auto future = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize, args.pixelFormat, args.allowProtected, args.grayscale, @@ -6518,10 +6754,15 @@ status_t SurfaceFlinger::captureDisplay(DisplayId displayId, false /* captureSecureLayers */); }); - auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) { - traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, visitor); - }; - auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + GetLayerSnapshotsFunction getLayerSnapshots; + if (mLayerLifecycleManagerEnabled) { + getLayerSnapshots = getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID); + } else { + auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) { + traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, visitor); + }; + getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + } if (captureListener == nullptr) { ALOGE("capture screen must provide a capture listener callback"); @@ -6616,29 +6857,37 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, return std::make_unique(*this, parent, crop, reqSize, dataspace, childrenOnly, args.captureSecureLayers); }); - - auto traverseLayers = [parent, args, excludeLayerIds](const LayerVector::Visitor& visitor) { - parent->traverseChildrenInZOrder(LayerVector::StateSet::Drawing, [&](Layer* layer) { - if (!layer->isVisible()) { - return; - } else if (args.childrenOnly && layer == parent.get()) { - return; - } else if (args.uid != CaptureArgs::UNSET_UID && args.uid != layer->getOwnerUid()) { - return; - } - - auto p = sp::fromExisting(layer); - while (p != nullptr) { - if (excludeLayerIds.count(p->sequence) != 0) { + GetLayerSnapshotsFunction getLayerSnapshots; + if (mLayerLifecycleManagerEnabled) { + FloatRect parentCrop = crop.isEmpty() ? FloatRect(0, 0, reqSize.width, reqSize.height) + : crop.toFloatRect(); + getLayerSnapshots = getLayerSnapshotsForScreenshots(parent->sequence, args.uid, + std::move(excludeLayerIds), + args.childrenOnly, parentCrop); + } else { + auto traverseLayers = [parent, args, excludeLayerIds](const LayerVector::Visitor& visitor) { + parent->traverseChildrenInZOrder(LayerVector::StateSet::Drawing, [&](Layer* layer) { + if (!layer->isVisible()) { + return; + } else if (args.childrenOnly && layer == parent.get()) { + return; + } else if (args.uid != CaptureArgs::UNSET_UID && args.uid != layer->getOwnerUid()) { return; } - p = p->getParent(); - } - visitor(layer); - }); - }; - auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + auto p = sp::fromExisting(layer); + while (p != nullptr) { + if (excludeLayerIds.count(p->sequence) != 0) { + return; + } + p = p->getParent(); + } + + visitor(layer); + }); + }; + getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + } if (captureListener == nullptr) { ALOGE("capture screen must provide a capture listener callback"); @@ -7420,24 +7669,18 @@ bool SurfaceFlinger::commitMirrorDisplays(VsyncId vsyncId) { return true; } -bool SurfaceFlinger::commitCreatedLayers(VsyncId vsyncId) { - std::vector createdLayers; - { - std::scoped_lock lock(mCreatedLayersLock); - createdLayers = std::move(mCreatedLayers); - mCreatedLayers.clear(); - if (createdLayers.size() == 0) { - return false; - } +bool SurfaceFlinger::commitCreatedLayers(VsyncId vsyncId, + std::vector& createdLayers) { + if (createdLayers.size() == 0) { + return false; } Mutex::Autolock _l(mStateLock); for (const auto& createdLayer : createdLayers) { handleLayerCreatedLocked(createdLayer, vsyncId); } - createdLayers.clear(); mLayersAdded = true; - return true; + return mLayersAdded; } void SurfaceFlinger::updateLayerMetadataSnapshot() { @@ -7465,6 +7708,150 @@ void SurfaceFlinger::updateLayerMetadataSnapshot() { }); } +void SurfaceFlinger::moveSnapshotsFromCompositionArgs( + compositionengine::CompositionRefreshArgs& refreshArgs, + std::vector>& layers) { + if (mLayerLifecycleManagerEnabled) { + std::vector>& snapshots = + mLayerSnapshotBuilder.getSnapshots(); + for (auto [_, layerFE] : layers) { + auto i = layerFE->mSnapshot->globalZ; + snapshots[i] = std::move(layerFE->mSnapshot); + } + } + if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) { + for (auto [layer, layerFE] : layers) { + layer->updateLayerSnapshot(std::move(layerFE->mSnapshot)); + } + } +} + +std::vector> SurfaceFlinger::moveSnapshotsToCompositionArgs( + compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly, int64_t vsyncId) { + std::vector> layers; + if (mLayerLifecycleManagerEnabled) { + mLayerSnapshotBuilder.forEachVisibleSnapshot( + [&](std::unique_ptr& snapshot) { + if (cursorOnly && + snapshot->compositionType != + aidl::android::hardware::graphics::composer3::Composition::CURSOR) { + return; + } + + if (!snapshot->hasSomethingToDraw()) { + return; + } + + auto it = mLegacyLayers.find(snapshot->sequence); + LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), + "Couldnt find layer object for %s", + snapshot->getDebugString().c_str()); + auto& legacyLayer = it->second; + sp layerFE = legacyLayer->getCompositionEngineLayerFE(snapshot->path); + layerFE->mSnapshot = std::move(snapshot); + refreshArgs.layers.push_back(layerFE); + layers.emplace_back(legacyLayer.get(), layerFE.get()); + }); + } + if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) { + mDrawingState.traverseInZOrder([&refreshArgs, cursorOnly, &layers](Layer* layer) { + if (const auto& layerFE = layer->getCompositionEngineLayerFE()) { + if (cursorOnly && + layer->getLayerSnapshot()->compositionType != + aidl::android::hardware::graphics::composer3::Composition::CURSOR) + return; + layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame); + layerFE->mSnapshot = layer->stealLayerSnapshot(); + refreshArgs.layers.push_back(layerFE); + layers.emplace_back(layer, layerFE.get()); + } + }); + } + + return layers; +} + +std::function>>()> +SurfaceFlinger::getLayerSnapshotsForScreenshots(std::optional layerStack, + uint32_t uid) { + return [this, layerStack, uid]() { + std::vector>> layers; + for (auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) { + if (layerStack && snapshot->outputFilter.layerStack != *layerStack) { + continue; + } + if (uid != CaptureArgs::UNSET_UID && snapshot->inputInfo.ownerUid != uid) { + continue; + } + if (!snapshot->isVisible || !snapshot->hasSomethingToDraw()) { + continue; + } + + auto it = mLegacyLayers.find(snapshot->sequence); + LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s", + snapshot->getDebugString().c_str()); + auto& legacyLayer = it->second; + sp layerFE = getFactory().createLayerFE(legacyLayer->getName()); + layerFE->mSnapshot = std::make_unique(*snapshot); + layers.emplace_back(legacyLayer.get(), std::move(layerFE)); + } + + return layers; + }; +} + +std::function>>()> +SurfaceFlinger::getLayerSnapshotsForScreenshots(uint32_t rootLayerId, uint32_t uid, + std::unordered_set excludeLayerIds, + bool childrenOnly, const FloatRect& parentCrop) { + return [this, excludeLayerIds = std::move(excludeLayerIds), uid, rootLayerId, childrenOnly, + parentCrop]() { + frontend::LayerSnapshotBuilder::Args + args{.root = mLayerHierarchyBuilder.getPartialHierarchy(rootLayerId, childrenOnly), + .layerLifecycleManager = mLayerLifecycleManager, + .displays = mFrontEndDisplayInfos, + .displayChanges = true, + .globalShadowSettings = mDrawingState.globalShadowSettings, + .supportsBlur = mSupportsBlur, + .forceFullDamage = mForceFullDamage, + .parentCrop = {parentCrop}, + .excludeLayerIds = std::move(excludeLayerIds)}; + mLayerSnapshotBuilder.update(args); + + auto getLayerSnapshotsFn = getLayerSnapshotsForScreenshots({}, uid); + std::vector>> layers = getLayerSnapshotsFn(); + args.root = mLayerHierarchyBuilder.getHierarchy(); + args.parentCrop.reset(); + args.excludeLayerIds.clear(); + mLayerSnapshotBuilder.update(args); + return layers; + }; +} + +SurfaceFlinger::LifecycleUpdate SurfaceFlinger::flushLifecycleUpdates() { + LifecycleUpdate update; + ATRACE_NAME("TransactionHandler:flushTransactions"); + // Locking: + // 1. to prevent onHandleDestroyed from being called while the state lock is held, + // we must keep a copy of the transactions (specifically the composer + // states) around outside the scope of the lock. + // 2. Transactions and created layers do not share a lock. To prevent applying + // transactions with layers still in the createdLayer queue, flush the transactions + // before committing the created layers. + update.transactions = mTransactionHandler.flushTransactions(); + { + // TODO(b/238781169) lockless queue this and keep order. + std::scoped_lock lock(mCreatedLayersLock); + update.layerCreatedStates = std::move(mCreatedLayers); + mCreatedLayers.clear(); + update.newLayers = std::move(mNewLayers); + mNewLayers.clear(); + update.destroyedHandles = std::move(mDestroyedHandles); + mDestroyedHandles.clear(); + } + return update; +} + // gui::ISurfaceComposer binder::Status SurfaceComposerAIDL::bootFinished() { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 97469f4253..5b8038b173 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -71,7 +71,9 @@ #include "FlagManager.h" #include "FrontEnd/DisplayInfo.h" #include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/LayerLifecycleManager.h" #include "FrontEnd/LayerSnapshot.h" +#include "FrontEnd/LayerSnapshotBuilder.h" #include "FrontEnd/TransactionHandler.h" #include "LayerVector.h" #include "Scheduler/RefreshRateSelector.h" @@ -449,6 +451,26 @@ private: FINISHED, }; + struct LayerCreatedState { + LayerCreatedState(const wp& layer, const wp& parent, bool addToRoot) + : layer(layer), initialParent(parent), addToRoot(addToRoot) {} + wp layer; + // Indicates the initial parent of the created layer, only used for creating layer in + // SurfaceFlinger. If nullptr, it may add the created layer into the current root layers. + wp initialParent; + // Indicates whether the layer getting created should be added at root if there's no parent + // and has permission ACCESS_SURFACE_FLINGER. If set to false and no parent, the layer will + // be added offscreen. + bool addToRoot; + }; + + struct LifecycleUpdate { + std::vector transactions; + std::vector layerCreatedStates; + std::vector> newLayers; + std::vector destroyedHandles; + }; + template >* = nullptr> static Dumper dumper(F&& dump) { using namespace std::placeholders; @@ -688,6 +710,17 @@ private: void updateLayerGeometry(); void updateLayerMetadataSnapshot(); + std::vector> moveSnapshotsToCompositionArgs( + compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly, + int64_t vsyncId); + void moveSnapshotsFromCompositionArgs(compositionengine::CompositionRefreshArgs& refreshArgs, + std::vector>& layers); + bool updateLayerSnapshotsLegacy(VsyncId vsyncId, LifecycleUpdate& update, + bool transactionsFlushed, bool& out) + REQUIRES(kMainThreadContext); + bool updateLayerSnapshots(VsyncId vsyncId, LifecycleUpdate& update, bool transactionsFlushed, + bool& out) REQUIRES(kMainThreadContext); + LifecycleUpdate flushLifecycleUpdates() REQUIRES(kMainThreadContext); void updateInputFlinger(); void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext); @@ -715,6 +748,8 @@ private: bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext); bool applyTransactions(std::vector&, VsyncId) REQUIRES(kMainThreadContext); + bool applyAndCommitDisplayTransactionStates(std::vector& transactions) + REQUIRES(kMainThreadContext); // Returns true if there is at least one transaction that needs to be flushed bool transactionFlushNeeded(); @@ -730,7 +765,10 @@ private: int64_t desiredPresentTime, bool isAutoTimestamp, int64_t postTime, uint32_t permissions, uint64_t transactionId) REQUIRES(mStateLock); - + uint32_t updateLayerCallbacksAndStats(const FrameTimelineInfo&, ResolvedComposerState&, + int64_t desiredPresentTime, bool isAutoTimestamp, + int64_t postTime, uint32_t permissions, + uint64_t transactionId) REQUIRES(mStateLock); uint32_t getTransactionFlags() const; // Sets the masked bits, and schedules a commit if needed. @@ -888,7 +926,7 @@ private: // mark a region of a layer stack dirty. this updates the dirty // region of all screens presenting this layer stack. - void invalidateLayerStack(const sp& layer, const Region& dirty); + void invalidateLayerStack(const ui::LayerFilter& layerFilter, const Region& dirty); ui::LayerFilter makeLayerFilterForDisplay(DisplayId displayId, ui::LayerStack layerStack) REQUIRES(mStateLock) { @@ -1146,6 +1184,7 @@ private: // Set if LayerMetadata has changed since the last LayerMetadata snapshot. bool mLayerMetadataSnapshotNeeded = false; + // TODO(b/238781169) validate these on composition // Tracks layers that have pending frames which are candidates for being // latched. std::unordered_set, SpHash> mLayersWithQueuedFrames; @@ -1320,23 +1359,11 @@ private: GUARDED_BY(mStateLock); mutable std::mutex mCreatedLayersLock; - struct LayerCreatedState { - LayerCreatedState(const wp& layer, const wp parent, bool addToRoot) - : layer(layer), initialParent(parent), addToRoot(addToRoot) {} - wp layer; - // Indicates the initial parent of the created layer, only used for creating layer in - // SurfaceFlinger. If nullptr, it may add the created layer into the current root layers. - wp initialParent; - // Indicates whether the layer getting created should be added at root if there's no parent - // and has permission ACCESS_SURFACE_FLINGER. If set to false and no parent, the layer will - // be added offscreen. - bool addToRoot; - }; // A temporay pool that store the created layers and will be added to current state in main // thread. std::vector mCreatedLayers GUARDED_BY(mCreatedLayersLock); - bool commitCreatedLayers(VsyncId); + bool commitCreatedLayers(VsyncId, std::vector& createdLayers); void handleLayerCreatedLocked(const LayerCreatedState&, VsyncId) REQUIRES(mStateLock); mutable std::mutex mMirrorDisplayLock; @@ -1358,6 +1385,11 @@ private: return hasDisplay( [](const auto& display) { return display.isRefreshRateOverlayEnabled(); }); } + std::function>>()> getLayerSnapshotsForScreenshots( + std::optional layerStack, uint32_t uid); + std::function>>()> getLayerSnapshotsForScreenshots( + uint32_t rootLayerId, uint32_t uid, std::unordered_set excludeLayerIds, + bool childrenOnly, const FloatRect& parentCrop); const sp mWindowInfosListenerInvoker; @@ -1370,6 +1402,18 @@ private: bool mPowerHintSessionEnabled; + bool mLayerLifecycleManagerEnabled = false; + bool mLegacyFrontEndEnabled = true; + + frontend::LayerLifecycleManager mLayerLifecycleManager; + frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}}; + frontend::LayerSnapshotBuilder mLayerSnapshotBuilder; + + std::vector mDestroyedHandles; + std::vector> mNewLayers; + // These classes do not store any client state but help with managing transaction callbacks + // and stats. + std::unordered_map> mLegacyLayers; struct { bool late = false; bool early = false; @@ -1377,6 +1421,7 @@ private: TransactionHandler mTransactionHandler; display::DisplayMap mFrontEndDisplayInfos; + bool mFrontEndDisplayInfosChanged = false; }; class SurfaceComposerAIDL : public gui::BnSurfaceComposer { diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp index 11719c435e..c088e7b40c 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp @@ -148,7 +148,7 @@ void LayerFuzzer::invokeBufferStateLayer() { layer->fenceHasSignaled(); layer->onPreComposition(mFdp.ConsumeIntegral()); const std::vector> callbacks; - layer->setTransactionCompletedListeners(callbacks); + layer->setTransactionCompletedListeners(callbacks, mFdp.ConsumeBool()); std::shared_ptr texture = std::make_shared< renderengine::mock::FakeExternalTexture>(mFdp.ConsumeIntegral(), -- GitLab From 9243bba2e7b770100ea76757ec9843d14cc7d31c Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Fri, 10 Feb 2023 15:31:14 -0800 Subject: [PATCH 0850/1310] SF: add traces for isVsyncValid Add more debug information for isVsyncValid Bug: 267780202 Test: manual Change-Id: I84bc750cf490a4d04a5d86915367da4bf6681f8c --- services/surfaceflinger/Scheduler/Scheduler.cpp | 3 ++- services/surfaceflinger/Scheduler/VSyncPredictor.cpp | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 5fc250bd9f..ad803f934d 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -28,10 +28,10 @@ #include #include #include +#include #include #include #include -#include #include #include @@ -171,6 +171,7 @@ bool Scheduler::isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const return true; } + ATRACE_FORMAT("%s uid: %d frameRate: %s", __func__, uid, to_string(*frameRate).c_str()); return mVsyncSchedule->getTracker().isVSyncInPhase(expectedVsyncTimestamp.ns(), *frameRate); } diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 02e12fd942..f8cb3230af 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -31,8 +31,8 @@ #include #include #include +#include #include -#include #include "RefreshRateSelector.h" #include "VSyncPredictor.h" @@ -282,6 +282,13 @@ bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const { } bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const { + const TimePoint now = TimePoint::now(); + const auto getTimePointIn = [](TimePoint now, nsecs_t timePoint) -> float { + return ticks(TimePoint::fromNs(timePoint) - now); + }; + ATRACE_FORMAT("%s timePoint in: %.2f divisor: %zu", __func__, getTimePointIn(now, timePoint), + divisor); + struct VsyncError { nsecs_t vsyncTimestamp; float error; @@ -304,6 +311,7 @@ bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) c if (knownTimestampIter == mRateDivisorKnownTimestampMap.end()) { const auto vsync = nextAnticipatedVSyncTimeFromLocked(justBeforeTimePoint); mRateDivisorKnownTimestampMap[dividedPeriod] = vsync; + ATRACE_FORMAT_INSTANT("(first) knownVsync in: %.2f", getTimePointIn(now, vsync)); return true; } @@ -323,6 +331,8 @@ bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) c const auto minVsyncError = std::min_element(vsyncs.begin(), vsyncs.end()); mRateDivisorKnownTimestampMap[dividedPeriod] = minVsyncError->vsyncTimestamp; + ATRACE_FORMAT_INSTANT("knownVsync in: %.2f", + getTimePointIn(now, minVsyncError->vsyncTimestamp)); return std::abs(minVsyncError->vsyncTimestamp - timePoint) < period / 2; } -- GitLab From fafbe05de337cc2330a9e56697e4e2502c494d8c Mon Sep 17 00:00:00 2001 From: Vladimir Komsiyski Date: Fri, 10 Feb 2023 10:23:59 +0100 Subject: [PATCH 0851/1310] Refactor the native RuntimeSensor API. Return a status from the virtual sensor callback indicating whether the callback invocation was successful. Bug: 266042170 Test: atest CtsSensorTestCases Change-Id: I001019c1915dc33db26e84444314d162a801fb9a --- services/sensorservice/SensorInterface.cpp | 11 +++++++---- services/sensorservice/SensorInterface.h | 10 +++++----- services/sensorservice/SensorService.cpp | 17 +++++++++-------- services/sensorservice/SensorService.h | 9 +++++---- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/services/sensorservice/SensorInterface.cpp b/services/sensorservice/SensorInterface.cpp index 398cdf9a0e..e9c8335963 100644 --- a/services/sensorservice/SensorInterface.cpp +++ b/services/sensorservice/SensorInterface.cpp @@ -87,14 +87,15 @@ VirtualSensor::VirtualSensor() : // --------------------------------------------------------------------------- -RuntimeSensor::RuntimeSensor(const sensor_t& sensor, sp callback) +RuntimeSensor::RuntimeSensor(const sensor_t& sensor, sp callback) : BaseSensor(sensor), mCallback(std::move(callback)) { } status_t RuntimeSensor::activate(void*, bool enabled) { if (enabled != mEnabled) { mEnabled = enabled; - mCallback->onStateChanged(mEnabled, mSamplingPeriodNs, mBatchReportLatencyNs); + return mCallback->onConfigurationChanged(mSensor.getHandle(), mEnabled, mSamplingPeriodNs, + mBatchReportLatencyNs); } return OK; } @@ -105,7 +106,8 @@ status_t RuntimeSensor::batch(void*, int, int, int64_t samplingPeriodNs, mSamplingPeriodNs = samplingPeriodNs; mBatchReportLatencyNs = maxBatchReportLatencyNs; if (mEnabled) { - mCallback->onStateChanged(mEnabled, mSamplingPeriodNs, mBatchReportLatencyNs); + return mCallback->onConfigurationChanged(mSensor.getHandle(), mEnabled, + mSamplingPeriodNs, mBatchReportLatencyNs); } } return OK; @@ -115,7 +117,8 @@ status_t RuntimeSensor::setDelay(void*, int, int64_t ns) { if (mSamplingPeriodNs != ns) { mSamplingPeriodNs = ns; if (mEnabled) { - mCallback->onStateChanged(mEnabled, mSamplingPeriodNs, mBatchReportLatencyNs); + return mCallback->onConfigurationChanged(mSensor.getHandle(), mEnabled, + mSamplingPeriodNs, mBatchReportLatencyNs); } } return OK; diff --git a/services/sensorservice/SensorInterface.h b/services/sensorservice/SensorInterface.h index 5ee5e1224a..c446d61f89 100644 --- a/services/sensorservice/SensorInterface.h +++ b/services/sensorservice/SensorInterface.h @@ -108,12 +108,12 @@ class RuntimeSensor : public BaseSensor { public: static constexpr int DEFAULT_DEVICE_ID = 0; - class StateChangeCallback : public virtual RefBase { + class SensorCallback : public virtual RefBase { public: - virtual void onStateChanged(bool enabled, int64_t samplingPeriodNs, - int64_t batchReportLatencyNs) = 0; + virtual status_t onConfigurationChanged(int handle, bool enabled, int64_t samplingPeriodNs, + int64_t batchReportLatencyNs) = 0; }; - RuntimeSensor(const sensor_t& sensor, sp callback); + RuntimeSensor(const sensor_t& sensor, sp callback); virtual status_t activate(void* ident, bool enabled) override; virtual status_t batch(void* ident, int handle, int flags, int64_t samplingPeriodNs, int64_t maxBatchReportLatencyNs) override; @@ -125,7 +125,7 @@ private: bool mEnabled = false; int64_t mSamplingPeriodNs = 0; int64_t mBatchReportLatencyNs = 0; - sp mCallback; + sp mCallback; }; // --------------------------------------------------------------------------- diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp index 5c98614f1a..3a0329cd09 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -116,16 +116,17 @@ int32_t nextRuntimeSensorHandle() { return nextHandle++; } -class RuntimeSensorCallbackProxy : public RuntimeSensor::StateChangeCallback { +class RuntimeSensorCallbackProxy : public RuntimeSensor::SensorCallback { public: - RuntimeSensorCallbackProxy(sp callback) + RuntimeSensorCallbackProxy(sp callback) : mCallback(std::move(callback)) {} - void onStateChanged(bool enabled, int64_t samplingPeriodNs, - int64_t batchReportLatencyNs) override { - mCallback->onStateChanged(enabled, samplingPeriodNs, batchReportLatencyNs); + status_t onConfigurationChanged(int handle, bool enabled, int64_t samplingPeriodNs, + int64_t batchReportLatencyNs) override { + return mCallback->onConfigurationChanged(handle, enabled, samplingPeriodNs, + batchReportLatencyNs); } private: - sp mCallback; + sp mCallback; }; } // namespace @@ -166,7 +167,7 @@ SensorService::SensorService() } int SensorService::registerRuntimeSensor( - const sensor_t& sensor, int deviceId, sp callback) { + const sensor_t& sensor, int deviceId, sp callback) { int handle = 0; while (handle == 0 || !mSensors.isNewHandle(handle)) { handle = nextRuntimeSensorHandle(); @@ -179,7 +180,7 @@ int SensorService::registerRuntimeSensor( ALOGI("Registering runtime sensor handle 0x%x, type %d, name %s", handle, sensor.type, sensor.name); - sp runtimeSensorCallback( + sp runtimeSensorCallback( new RuntimeSensorCallbackProxy(std::move(callback))); sensor_t runtimeSensor = sensor; // force the handle to be consistent diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h index 0798279b6b..3f6a895b52 100644 --- a/services/sensorservice/SensorService.h +++ b/services/sensorservice/SensorService.h @@ -147,12 +147,13 @@ public: virtual void onProximityActive(bool isActive) = 0; }; - class RuntimeSensorStateChangeCallback : public virtual RefBase { + class RuntimeSensorCallback : public virtual RefBase { public: // Note that the callback is invoked from an async thread and can interact with the // SensorService directly. - virtual void onStateChanged(bool enabled, int64_t samplingPeriodNanos, - int64_t batchReportLatencyNanos) = 0; + virtual status_t onConfigurationChanged(int handle, bool enabled, + int64_t samplingPeriodNanos, + int64_t batchReportLatencyNanos) = 0; }; static char const* getServiceName() ANDROID_API { return "sensorservice"; } @@ -182,7 +183,7 @@ public: status_t removeProximityActiveListener(const sp& callback) ANDROID_API; int registerRuntimeSensor(const sensor_t& sensor, int deviceId, - sp callback) ANDROID_API; + sp callback) ANDROID_API; status_t unregisterRuntimeSensor(int handle) ANDROID_API; status_t sendRuntimeSensorEvent(const sensors_event_t& event) ANDROID_API; -- GitLab From 39dfc94bb3c7e3645514bbc5ff8b9eba86951390 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Wed, 8 Feb 2023 16:27:05 -0500 Subject: [PATCH 0852/1310] SF: Avoid a few nullable sp params Bug: 255635821 Test: Build Change-Id: I6c84b89d8b532eca581530931a8b876c37ca3189 --- services/surfaceflinger/SurfaceFlinger.cpp | 34 +++++++++---------- services/surfaceflinger/SurfaceFlinger.h | 10 +++--- .../SurfaceFlinger_DisplayModeSwitching.cpp | 12 +++---- .../tests/unittests/TestableSurfaceFlinger.h | 2 +- 4 files changed, 28 insertions(+), 30 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 257045d2f5..a375233095 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3304,7 +3304,7 @@ void SurfaceFlinger::processDisplayChanged(const wp& displayToken, // TODO(b/175678251) Call a listener instead. if (currentState.physical->hwcDisplayId == getHwComposer().getPrimaryHwcDisplayId()) { - updateInternalDisplayVsyncLocked(display); + updateActiveDisplayVsyncLocked(*display); } } return; @@ -3332,15 +3332,15 @@ void SurfaceFlinger::processDisplayChanged(const wp& displayToken, display->setDisplaySize(currentState.width, currentState.height); if (display->getId() == mActiveDisplayId) { - onActiveDisplaySizeChanged(display); + onActiveDisplaySizeChanged(*display); } } } } -void SurfaceFlinger::updateInternalDisplayVsyncLocked(const sp& activeDisplay) { +void SurfaceFlinger::updateActiveDisplayVsyncLocked(const DisplayDevice& activeDisplay) { mVsyncConfiguration->reset(); - const Fps refreshRate = activeDisplay->getActiveMode().fps; + const Fps refreshRate = activeDisplay.getActiveMode().fps; updatePhaseConfiguration(refreshRate); mRefreshRateStats->setRefreshRate(refreshRate); } @@ -4985,7 +4985,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: static bool sPrimaryDisplay = true; if (sPrimaryDisplay || activeDisplayChanged) { - onActiveDisplayChangedLocked(activeDisplay, display); + onActiveDisplayChangedLocked(activeDisplay.get(), *display); sPrimaryDisplay = false; } @@ -7267,35 +7267,35 @@ void SurfaceFlinger::sample() { mRegionSamplingThread->onCompositionComplete(mScheduler->getScheduledFrameTime()); } -void SurfaceFlinger::onActiveDisplaySizeChanged(const sp& activeDisplay) { - mScheduler->onActiveDisplayAreaChanged(activeDisplay->getWidth() * activeDisplay->getHeight()); - getRenderEngine().onActiveDisplaySizeChanged(activeDisplay->getSize()); +void SurfaceFlinger::onActiveDisplaySizeChanged(const DisplayDevice& activeDisplay) { + mScheduler->onActiveDisplayAreaChanged(activeDisplay.getWidth() * activeDisplay.getHeight()); + getRenderEngine().onActiveDisplaySizeChanged(activeDisplay.getSize()); } -void SurfaceFlinger::onActiveDisplayChangedLocked(const sp& inactiveDisplay, - const sp& activeDisplay) { +void SurfaceFlinger::onActiveDisplayChangedLocked(const DisplayDevice* inactiveDisplayPtr, + const DisplayDevice& activeDisplay) { ATRACE_CALL(); - if (inactiveDisplay) { - inactiveDisplay->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false); + if (inactiveDisplayPtr) { + inactiveDisplayPtr->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false); } - mActiveDisplayId = activeDisplay->getPhysicalId(); - activeDisplay->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true); + mActiveDisplayId = activeDisplay.getPhysicalId(); + activeDisplay.getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true); - updateInternalDisplayVsyncLocked(activeDisplay); + updateActiveDisplayVsyncLocked(activeDisplay); mScheduler->setModeChangePending(false); mScheduler->setLeaderDisplay(mActiveDisplayId); onActiveDisplaySizeChanged(activeDisplay); - mActiveDisplayTransformHint = activeDisplay->getTransformHint(); + mActiveDisplayTransformHint = activeDisplay.getTransformHint(); // The policy of the new active/leader display may have changed while it was inactive. In that // case, its preferred mode has not been propagated to HWC (via setDesiredActiveMode). In either // case, the Scheduler's cachedModeChangedParams must be initialized to the newly active mode, // and the kernel idle timer of the newly active display must be toggled. constexpr bool kForce = true; - applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay->refreshRateSelector(), kForce); + applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay.refreshRateSelector(), kForce); } status_t SurfaceFlinger::addWindowInfosListener( diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 4a27f3cd44..46a5f70bd2 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1008,13 +1008,11 @@ private: VirtualDisplayId acquireVirtualDisplay(ui::Size, ui::PixelFormat) REQUIRES(mStateLock); void releaseVirtualDisplay(VirtualDisplayId); - // TODO(b/255635821): Replace pointers with references. `inactiveDisplay` is only ever `nullptr` - // in tests, and `activeDisplay` must not be `nullptr` as a precondition. - void onActiveDisplayChangedLocked(const sp& inactiveDisplay, - const sp& activeDisplay) + void onActiveDisplayChangedLocked(const DisplayDevice* inactiveDisplayPtr, + const DisplayDevice& activeDisplay) REQUIRES(mStateLock, kMainThreadContext); - void onActiveDisplaySizeChanged(const sp&); + void onActiveDisplaySizeChanged(const DisplayDevice&); /* * Debugging & dumpsys @@ -1080,7 +1078,7 @@ private: std::chrono::nanoseconds presentLatency); int getMaxAcquiredBufferCountForRefreshRate(Fps refreshRate) const; - void updateInternalDisplayVsyncLocked(const sp& activeDisplay) + void updateActiveDisplayVsyncLocked(const DisplayDevice& activeDisplay) REQUIRES(mStateLock, kMainThreadContext); bool isHdrLayer(const frontend::LayerSnapshot& snapshot) const; diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp index ad3bd353ed..43af595ca7 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp @@ -119,7 +119,7 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRe ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); - mFlinger.onActiveDisplayChanged(mDisplay); + mFlinger.onActiveDisplayChanged(*mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), mock::createDisplayModeSpecs(kModeId90.value(), false, 0, @@ -159,7 +159,7 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithoutRefres ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); - mFlinger.onActiveDisplayChanged(mDisplay); + mFlinger.onActiveDisplayChanged(*mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), mock::createDisplayModeSpecs(kModeId90.value(), true, 0, @@ -195,7 +195,7 @@ TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) { ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); - mFlinger.onActiveDisplayChanged(mDisplay); + mFlinger.onActiveDisplayChanged(*mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), mock::createDisplayModeSpecs(kModeId90.value(), false, 0, @@ -238,7 +238,7 @@ TEST_F(DisplayModeSwitchingTest, changeResolution_OnActiveDisplay_WithoutRefresh ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); - mFlinger.onActiveDisplayChanged(mDisplay); + mFlinger.onActiveDisplayChanged(*mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), mock::createDisplayModeSpecs(kModeId90_4K.value(), false, 0, @@ -315,7 +315,7 @@ TEST_F(DisplayModeSwitchingTest, multiDisplay) { EXPECT_EQ(innerDisplay->getActiveMode().modePtr->getId(), kModeId60); EXPECT_EQ(outerDisplay->getActiveMode().modePtr->getId(), kModeId120); - mFlinger.onActiveDisplayChanged(innerDisplay); + mFlinger.onActiveDisplayChanged(*innerDisplay); EXPECT_EQ(NO_ERROR, mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(), @@ -359,7 +359,7 @@ TEST_F(DisplayModeSwitchingTest, multiDisplay) { EXPECT_FALSE(outerDisplay->getDesiredActiveMode()); EXPECT_EQ(outerDisplay->getActiveMode().modePtr->getId(), kModeId120); - mFlinger.onActiveDisplayChanged(outerDisplay); + mFlinger.onActiveDisplayChanged(*outerDisplay); // No transition on the inner display. EXPECT_FALSE(innerDisplay->getDesiredActiveMode()); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index e03f6cccc5..282c87eb8a 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -473,7 +473,7 @@ public: return mFlinger->setDesiredDisplayModeSpecs(displayToken, specs); } - void onActiveDisplayChanged(const sp& activeDisplay) { + void onActiveDisplayChanged(const DisplayDevice& activeDisplay) { Mutex::Autolock lock(mFlinger->mStateLock); ftl::FakeGuard guard(kMainThreadContext); mFlinger->onActiveDisplayChangedLocked(nullptr, activeDisplay); -- GitLab From a39e32fd91bdeccdcbdae5b3c03992cf770db70d Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Wed, 8 Feb 2023 16:40:13 -0500 Subject: [PATCH 0853/1310] SF: Activate boot display earlier Remove setPowerModeInternal's special case for the boot display, whose global state broke tests unless they happened to run in the convenient order. Bug: 255635821 Bug: 262417075 Test: SetPowerModeInternalTest Change-Id: Id6800c30ef58e78615bf9abaaad116d8b1195ca2 --- services/surfaceflinger/SurfaceFlinger.cpp | 20 +++++++------------ ...urfaceFlinger_SetPowerModeInternalTest.cpp | 12 ++++------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index a375233095..90a73a1b89 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -865,7 +865,7 @@ void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { // initialize our drawing state mDrawingState = mCurrentState; - // set initial conditions (e.g. unblank default device) + onActiveDisplayChangedLocked(nullptr, *display); initializeDisplays(); mPowerAdvisor->init(); @@ -4972,21 +4972,15 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: if (!currentModeOpt || *currentModeOpt == hal::PowerMode::OFF) { // Turn on the display - // Activate the display (which involves a modeset to the active mode): - // 1) When the first (a.k.a. primary) display is powered on during boot. - // 2) When the inner or outer display of a foldable is powered on. This condition relies - // on the above DisplayDevice::setPowerMode. If `display` and `activeDisplay` are the - // same display, then the `activeDisplay->isPoweredOn()` below is true, such that the - // display is not activated every time it is powered on. + // Activate the display (which involves a modeset to the active mode) when the inner or + // outer display of a foldable is powered on. This condition relies on the above + // DisplayDevice::setPowerMode. If `display` and `activeDisplay` are the same display, + // then the `activeDisplay->isPoweredOn()` below is true, such that the display is not + // activated every time it is powered on. // // TODO(b/255635821): Remove the concept of active display. - const bool activeDisplayChanged = - isInternalDisplay && (!activeDisplay || !activeDisplay->isPoweredOn()); - - static bool sPrimaryDisplay = true; - if (sPrimaryDisplay || activeDisplayChanged) { + if (isInternalDisplay && (!activeDisplay || !activeDisplay->isPoweredOn())) { onActiveDisplayChangedLocked(activeDisplay.get(), *display); - sPrimaryDisplay = false; } // Keep uclamp in a separate syscall and set it before changing to RT due to b/190237315. diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp index 88ddb0f45b..a0aaa684f9 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp @@ -404,13 +404,11 @@ TEST_F(SetPowerModeInternalTest, setPowerModeInternalDoesNothingIfVirtualDisplay EXPECT_EQ(PowerMode::ON, display.mutableDisplayDevice()->getPowerMode()); } -// TODO(b/262417075) -TEST_F(SetPowerModeInternalTest, DISABLED_transitionsDisplayFromOffToOnPrimaryDisplay) { +TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOffToOnPrimaryDisplay) { transitionDisplayCommon>(); } -// TODO(b/262417075) -TEST_F(SetPowerModeInternalTest, DISABLED_transitionsDisplayFromOffToDozeSuspendPrimaryDisplay) { +TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOffToDozeSuspendPrimaryDisplay) { transitionDisplayCommon>(); } @@ -446,13 +444,11 @@ TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOnToUnknownPrimaryDisplay transitionDisplayCommon>(); } -// TODO(b/262417075) -TEST_F(SetPowerModeInternalTest, DISABLED_transitionsDisplayFromOffToOnExternalDisplay) { +TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOffToOnExternalDisplay) { transitionDisplayCommon>(); } -// TODO(b/262417075) -TEST_F(SetPowerModeInternalTest, DISABLED_transitionsDisplayFromOffToDozeSuspendExternalDisplay) { +TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOffToDozeSuspendExternalDisplay) { transitionDisplayCommon>(); } -- GitLab From d19e576fef9a1bd99a2118bb7edb6e2549ddde49 Mon Sep 17 00:00:00 2001 From: Nick Deakin Date: Fri, 10 Feb 2023 15:39:08 -0500 Subject: [PATCH 0854/1310] jpegrecoverymap: Add min boost to metadata. This change updates recovery map metadata from specifying only max boost (with implied min boost of its inverse) to specifying both min and max boost explicitly. Places where this matters are also updated to handle calculating, encoding, extracting, and applying boost appropriately. Updated tests and added new tests for relevant new edge cases. Also add benchmark test. To make this nice, make generateRecoveryMap() and applyRecoveryMap() functions protected instead of private in the RecoveryMap class. Current results on Pixel 7 Pro for 12MP are: * generate map: ~150ms * apply map: ~95ms This is a bit slower than before for generate (~110ms) because we now also sample the SDR image when determining the metadata's boost values. Also fix a bug in the applyRecoveryLUT test where we weren't actually iterating over the LUT and instead only tested the first entry, and another bug with LUTs in general where we were incrementing values by 1/number of entries when we should actuall increment by 1/(number-1). Bug: 264715926 Test: tests pass Change-Id: I687eb9c2f788e9220a14311497a129956b2077fd --- .../include/jpegrecoverymap/recoverymap.h | 28 +- .../include/jpegrecoverymap/recoverymapmath.h | 36 +- libs/jpegrecoverymap/recoverymap.cpp | 51 +-- libs/jpegrecoverymap/recoverymapmath.cpp | 72 ++-- .../tests/recoverymap_test.cpp | 150 +++++++- .../tests/recoverymapmath_test.cpp | 332 ++++++++++++------ 6 files changed, 463 insertions(+), 206 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h index aee6602aa4..1fd129b991 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -88,6 +88,8 @@ struct jpegr_metadata { uint32_t version; // Max Content Boost for the map float maxContentBoost; + // Min Content Boost for the map + float minContentBoost; }; typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr; @@ -219,16 +221,9 @@ public: */ status_t getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info); -private: - /* - * This method is called in the encoding pipeline. It will encode the recovery map. - * - * @param uncompressed_recovery_map uncompressed recovery map - * @param dest encoded recover map - * @return NO_ERROR if encoding succeeds, error code if error occurs. - */ - status_t compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, - jr_compressed_ptr dest); + +protected: + // Following functions protected instead of private for testing. /* * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and @@ -239,7 +234,7 @@ private: * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param hdr_tf transfer function of the HDR image * @param dest recovery map; caller responsible for memory of data - * @param metadata max_content_boost is filled in + * @param metadata minContentBoost and maxContentBoost are filled in * @return NO_ERROR if calculation succeeds, error code if error occurs. */ status_t generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, @@ -265,6 +260,17 @@ private: jr_metadata_ptr metadata, jr_uncompressed_ptr dest); +private: + /* + * This method is called in the encoding pipeline. It will encode the recovery map. + * + * @param uncompressed_recovery_map uncompressed recovery map + * @param dest encoded recover map + * @return NO_ERROR if encoding succeeds, error code if error occurs. + */ + status_t compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, + jr_compressed_ptr dest); + /* * This methoud is called to separate primary image and recovery map image from JPEGR * diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h index 0695bb74ac..6eed08afb4 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h @@ -118,11 +118,12 @@ inline Color operator/(const Color& lhs, const float rhs) { constexpr size_t kRecoveryFactorPrecision = 10; constexpr size_t kRecoveryFactorNumEntries = 1 << kRecoveryFactorPrecision; struct RecoveryLUT { - RecoveryLUT(float hdrRatio) { - float increment = 2.0 / kRecoveryFactorNumEntries; - float value = -1.0f; - for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++, value += increment) { - mRecoveryTable[idx] = pow(hdrRatio, value); + RecoveryLUT(jr_metadata_ptr metadata) { + for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kRecoveryFactorNumEntries - 1); + float logBoost = log2(metadata->minContentBoost) * (1.0f - value) + + log2(metadata->maxContentBoost) * value; + mRecoveryTable[idx] = exp2(logBoost); } } @@ -130,10 +131,10 @@ struct RecoveryLUT { } float getRecoveryFactor(float recovery) { - uint32_t value = static_cast(((recovery + 1.0f) / 2.0f) * kRecoveryFactorNumEntries); + uint32_t idx = static_cast(recovery * (kRecoveryFactorNumEntries - 1)); //TODO() : Remove once conversion modules have appropriate clamping in place - value = CLIP3(value, 0, kRecoveryFactorNumEntries - 1); - return mRecoveryTable[value]; + idx = CLIP3(idx, 0, kRecoveryFactorNumEntries - 1); + return mRecoveryTable[idx]; } private: @@ -219,6 +220,9 @@ Color srgbInvOetf(Color e_gamma); float srgbInvOetfLUT(float e_gamma); Color srgbInvOetfLUT(Color e_gamma); +constexpr size_t kSrgbInvOETFPrecision = 10; +constexpr size_t kSrgbInvOETFNumEntries = 1 << kSrgbInvOETFPrecision; + //////////////////////////////////////////////////////////////////////////////// // Display-P3 transformations @@ -260,6 +264,9 @@ Color hlgOetf(Color e); float hlgOetfLUT(float e); Color hlgOetfLUT(Color e); +constexpr size_t kHlgOETFPrecision = 10; +constexpr size_t kHlgOETFNumEntries = 1 << kHlgOETFPrecision; + /* * Convert from HLG to scene luminance. * @@ -270,6 +277,9 @@ Color hlgInvOetf(Color e_gamma); float hlgInvOetfLUT(float e_gamma); Color hlgInvOetfLUT(Color e_gamma); +constexpr size_t kHlgInvOETFPrecision = 10; +constexpr size_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision; + /* * Convert from scene luminance to PQ. * @@ -280,6 +290,9 @@ Color pqOetf(Color e); float pqOetfLUT(float e); Color pqOetfLUT(Color e); +constexpr size_t kPqOETFPrecision = 10; +constexpr size_t kPqOETFNumEntries = 1 << kPqOETFPrecision; + /* * Convert from PQ to scene luminance in nits. * @@ -290,6 +303,9 @@ Color pqInvOetf(Color e_gamma); float pqInvOetfLUT(float e_gamma); Color pqInvOetfLUT(Color e_gamma); +constexpr size_t kPqInvOETFPrecision = 10; +constexpr size_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision; + //////////////////////////////////////////////////////////////////////////////// // Color space conversions @@ -326,13 +342,13 @@ ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gam * Calculate the 8-bit unsigned integer recovery value for the given SDR and HDR * luminances in linear space, and the hdr ratio to encode against. */ -uint8_t encodeRecovery(float y_sdr, float y_hdr, float hdr_ratio); +uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata); /* * Calculates the linear luminance in nits after applying the given recovery * value, with the given hdr ratio, to the given sdr input in the range [0, 1]. */ -Color applyRecovery(Color e, float recovery, float hdr_ratio); +Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata); Color applyRecoveryLUT(Color e, float recovery, RecoveryLUT& recoveryLUT); /* diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index 218c43017b..349223bb6b 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -573,19 +573,20 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 } std::mutex mutex; - float hdr_y_nits_max = 0.0f; - double hdr_y_nits_avg = 0.0f; + float max_gain = 0.0f; + float min_gain = 1.0f; const int threads = std::clamp(GetCPUCoreCount(), 1, 4); size_t rowStep = threads == 1 ? image_height : kJobSzInRows; JobQueue jobQueue; - std::function computeMetadata = [uncompressed_p010_image, hdrInvOetf, - hdrGamutConversionFn, luminanceFn, hdr_white_nits, - threads, &mutex, &hdr_y_nits_avg, - &hdr_y_nits_max, &jobQueue]() -> void { + std::function computeMetadata = [uncompressed_p010_image, uncompressed_yuv_420_image, + hdrInvOetf, hdrGamutConversionFn, luminanceFn, + hdr_white_nits, threads, &mutex, &max_gain, &min_gain, + &jobQueue]() -> void { size_t rowStart, rowEnd; - float hdr_y_nits_max_th = 0.0f; - double hdr_y_nits_avg_th = 0.0f; + float max_gain_th = 0.0f; + float min_gain_th = 1.0f; + while (jobQueue.dequeueJob(rowStart, rowEnd)) { for (size_t y = rowStart; y < rowEnd; ++y) { for (size_t x = 0; x < uncompressed_p010_image->width; ++x) { @@ -595,16 +596,25 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 hdr_rgb = hdrGamutConversionFn(hdr_rgb); float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits; - hdr_y_nits_avg_th += hdr_y_nits; - if (hdr_y_nits > hdr_y_nits_max_th) { - hdr_y_nits_max_th = hdr_y_nits; - } + Color sdr_yuv_gamma = + getYuv420Pixel(uncompressed_yuv_420_image, x, y); + Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma); +#if USE_SRGB_INVOETF_LUT + Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma); +#else + Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma); +#endif + float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits; + + float gain = hdr_y_nits / sdr_y_nits; + max_gain_th = std::max(max_gain_th, gain); + min_gain_th = std::min(min_gain_th, gain); } } } std::unique_lock lock{mutex}; - hdr_y_nits_avg += hdr_y_nits_avg_th; - hdr_y_nits_max = std::max(hdr_y_nits_max, hdr_y_nits_max_th); + max_gain = std::max(max_gain, max_gain_th); + min_gain = std::min(min_gain, min_gain_th); }; std::function generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image, @@ -634,7 +644,7 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 size_t pixel_idx = x + y * dest_map_stride; reinterpret_cast(dest->data)[pixel_idx] = - encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->maxContentBoost); + encodeRecovery(sdr_y_nits, hdr_y_nits, metadata); } } } @@ -655,9 +665,9 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 computeMetadata(); std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); }); workers.clear(); - hdr_y_nits_avg /= image_width * image_height; - metadata->maxContentBoost = hdr_y_nits_max / kSdrWhiteNits; + metadata->maxContentBoost = max_gain; + metadata->minContentBoost = min_gain; // generate map jobQueue.reset(); @@ -693,7 +703,7 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ dest->width = uncompressed_yuv_420_image->width; dest->height = uncompressed_yuv_420_image->height; ShepardsIDW idwTable(kMapDimensionScaleFactor); - RecoveryLUT recoveryLUT(metadata->maxContentBoost); + RecoveryLUT recoveryLUT(metadata); JobQueue jobQueue; std::function applyRecMap = [uncompressed_yuv_420_image, uncompressed_recovery_map, @@ -729,13 +739,12 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ if (map_scale_factor != floorf(map_scale_factor)) { recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y); } else { - recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y, - idwTable); + recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y, idwTable); } #if USE_APPLY_RECOVERY_LUT Color rgb_hdr = applyRecoveryLUT(rgb_sdr, recovery, recoveryLUT); #else - Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio); + Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata); #endif Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->maxContentBoost); uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr); diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp index 4f21ac6372..9c89c8afd4 100644 --- a/libs/jpegrecoverymap/recoverymapmath.cpp +++ b/libs/jpegrecoverymap/recoverymapmath.cpp @@ -20,65 +20,46 @@ namespace android::recoverymap { -constexpr size_t kPqOETFPrecision = 10; -constexpr size_t kPqOETFNumEntries = 1 << kPqOETFPrecision; - static const std::vector kPqOETF = [] { std::vector result; - float increment = 1.0 / kPqOETFNumEntries; - float value = 0.0f; - for (int idx = 0; idx < kPqOETFNumEntries; idx++, value += increment) { + for (int idx = 0; idx < kPqOETFNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kPqOETFNumEntries - 1); result.push_back(pqOetf(value)); } return result; }(); -constexpr size_t kPqInvOETFPrecision = 10; -constexpr size_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision; - static const std::vector kPqInvOETF = [] { std::vector result; - float increment = 1.0 / kPqInvOETFNumEntries; - float value = 0.0f; - for (int idx = 0; idx < kPqInvOETFNumEntries; idx++, value += increment) { + for (int idx = 0; idx < kPqInvOETFNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kPqInvOETFNumEntries - 1); result.push_back(pqInvOetf(value)); } return result; }(); -constexpr size_t kHlgOETFPrecision = 10; -constexpr size_t kHlgOETFNumEntries = 1 << kHlgOETFPrecision; - static const std::vector kHlgOETF = [] { std::vector result; - float increment = 1.0 / kHlgOETFNumEntries; - float value = 0.0f; - for (int idx = 0; idx < kHlgOETFNumEntries; idx++, value += increment) { + for (int idx = 0; idx < kHlgOETFNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kHlgOETFNumEntries - 1); result.push_back(hlgOetf(value)); } return result; }(); -constexpr size_t kHlgInvOETFPrecision = 10; -constexpr size_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision; - static const std::vector kHlgInvOETF = [] { std::vector result; - float increment = 1.0 / kHlgInvOETFNumEntries; - float value = 0.0f; - for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++, value += increment) { + for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kHlgInvOETFNumEntries - 1); result.push_back(hlgInvOetf(value)); } return result; }(); -constexpr size_t kSRGBInvOETFPrecision = 10; -constexpr size_t kSRGBInvOETFNumEntries = 1 << kSRGBInvOETFPrecision; -static const std::vector kSRGBInvOETF = [] { +static const std::vector kSrgbInvOETF = [] { std::vector result; - float increment = 1.0 / kSRGBInvOETFNumEntries; - float value = 0.0f; - for (int idx = 0; idx < kSRGBInvOETFNumEntries; idx++, value += increment) { + for (int idx = 0; idx < kSrgbInvOETFNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kSrgbInvOETFNumEntries - 1); result.push_back(srgbInvOetf(value)); } return result; @@ -182,10 +163,10 @@ Color srgbInvOetf(Color e_gamma) { // See IEC 61966-2-1, Equations F.5 and F.6. float srgbInvOetfLUT(float e_gamma) { - uint32_t value = static_cast(e_gamma * kSRGBInvOETFNumEntries); + uint32_t value = static_cast(e_gamma * kSrgbInvOETFNumEntries); //TODO() : Remove once conversion modules have appropriate clamping in place - value = CLIP3(value, 0, kSRGBInvOETFNumEntries - 1); - return kSRGBInvOETF[value]; + value = CLIP3(value, 0, kSrgbInvOETFNumEntries - 1); + return kSrgbInvOETF[value]; } Color srgbInvOetfLUT(Color e_gamma) { @@ -461,21 +442,24 @@ ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gam //////////////////////////////////////////////////////////////////////////////// // Recovery map calculations - -uint8_t encodeRecovery(float y_sdr, float y_hdr, float hdr_ratio) { +uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata) { float gain = 1.0f; if (y_sdr > 0.0f) { gain = y_hdr / y_sdr; } - if (gain < (1.0f / hdr_ratio)) gain = 1.0f / hdr_ratio; - if (gain > hdr_ratio) gain = hdr_ratio; + if (gain < metadata->minContentBoost) gain = metadata->minContentBoost; + if (gain > metadata->maxContentBoost) gain = metadata->maxContentBoost; - return static_cast(log2(gain) / log2(hdr_ratio) * 127.5f + 127.5f); + return static_cast((log2(gain) - log2(metadata->minContentBoost)) + / (log2(metadata->maxContentBoost) - log2(metadata->minContentBoost)) + * 255.0f); } -Color applyRecovery(Color e, float recovery, float hdr_ratio) { - float recoveryFactor = pow(hdr_ratio, recovery); +Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata) { + float logBoost = log2(metadata->minContentBoost) * (1.0f - recovery) + + log2(metadata->maxContentBoost) * recovery; + float recoveryFactor = exp2(logBoost); return e * recoveryFactor; } @@ -550,7 +534,7 @@ static size_t clamp(const size_t& val, const size_t& low, const size_t& high) { } static float mapUintToFloat(uint8_t map_uint) { - return (static_cast(map_uint) - 127.5f) / 127.5f; + return static_cast(map_uint) / 255.0f; } static float pythDistance(float x_diff, float y_diff) { @@ -558,9 +542,9 @@ static float pythDistance(float x_diff, float y_diff) { } // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following. -float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y) { - float x_map = static_cast(x) / static_cast(map_scale_factor); - float y_map = static_cast(y) / static_cast(map_scale_factor); +float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y) { + float x_map = static_cast(x) / map_scale_factor; + float y_map = static_cast(y) / map_scale_factor; size_t x_lower = static_cast(floor(x_map)); size_t x_upper = x_lower + 1; diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp index 3e9a76d47a..1b73d94057 100644 --- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp +++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #define RAW_P010_IMAGE "/sdcard/Documents/raw_p010_image.p010" @@ -35,27 +36,24 @@ namespace android::recoverymap { -class RecoveryMapTest : public testing::Test { -public: - RecoveryMapTest(); - ~RecoveryMapTest(); -protected: - virtual void SetUp(); - virtual void TearDown(); - - struct jpegr_uncompressed_struct mRawP010Image; - struct jpegr_uncompressed_struct mRawYuv420Image; - struct jpegr_compressed_struct mJpegImage; +struct Timer { + struct timeval StartingTime; + struct timeval EndingTime; + struct timeval ElapsedMicroseconds; }; -RecoveryMapTest::RecoveryMapTest() {} -RecoveryMapTest::~RecoveryMapTest() {} +void timerStart(Timer *t) { + gettimeofday(&t->StartingTime, nullptr); +} -void RecoveryMapTest::SetUp() {} -void RecoveryMapTest::TearDown() { - free(mRawP010Image.data); - free(mRawYuv420Image.data); - free(mJpegImage.data); +void timerStop(Timer *t) { + gettimeofday(&t->EndingTime, nullptr); +} + +int64_t elapsedTime(Timer *t) { + t->ElapsedMicroseconds.tv_sec = t->EndingTime.tv_sec - t->StartingTime.tv_sec; + t->ElapsedMicroseconds.tv_usec = t->EndingTime.tv_usec - t->StartingTime.tv_usec; + return t->ElapsedMicroseconds.tv_sec * 1000000 + t->ElapsedMicroseconds.tv_usec; } static size_t getFileSize(int fd) { @@ -89,6 +87,80 @@ static bool loadFile(const char filename[], void*& result, int* fileLength) { return true; } +class RecoveryMapTest : public testing::Test { +public: + RecoveryMapTest(); + ~RecoveryMapTest(); + +protected: + virtual void SetUp(); + virtual void TearDown(); + + struct jpegr_uncompressed_struct mRawP010Image; + struct jpegr_uncompressed_struct mRawYuv420Image; + struct jpegr_compressed_struct mJpegImage; +}; + +RecoveryMapTest::RecoveryMapTest() {} +RecoveryMapTest::~RecoveryMapTest() {} + +void RecoveryMapTest::SetUp() {} +void RecoveryMapTest::TearDown() { + free(mRawP010Image.data); + free(mRawYuv420Image.data); + free(mJpegImage.data); +} + +class RecoveryMapBenchmark : public RecoveryMap { +public: + void BenchmarkGenerateRecoveryMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image, + jr_metadata_ptr metadata, jr_uncompressed_ptr map); + void BenchmarkApplyRecoveryMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map, + jr_metadata_ptr metadata, jr_uncompressed_ptr dest); +private: + const int kProfileCount = 10; +}; + +void RecoveryMapBenchmark::BenchmarkGenerateRecoveryMap(jr_uncompressed_ptr yuv420Image, + jr_uncompressed_ptr p010Image, + jr_metadata_ptr metadata, + jr_uncompressed_ptr map) { + ASSERT_EQ(yuv420Image->width, p010Image->width); + ASSERT_EQ(yuv420Image->height, p010Image->height); + + Timer genRecMapTime; + + timerStart(&genRecMapTime); + for (auto i = 0; i < kProfileCount; i++) { + ASSERT_EQ(OK, generateRecoveryMap( + yuv420Image, p010Image, jpegr_transfer_function::JPEGR_TF_HLG, metadata, map)); + if (i != kProfileCount - 1) delete[] static_cast(map->data); + } + timerStop(&genRecMapTime); + + ALOGE("Generate Recovery Map:- Res = %i x %i, time = %f ms", + yuv420Image->width, yuv420Image->height, + elapsedTime(&genRecMapTime) / (kProfileCount * 1000.f)); + +} + +void RecoveryMapBenchmark::BenchmarkApplyRecoveryMap(jr_uncompressed_ptr yuv420Image, + jr_uncompressed_ptr map, + jr_metadata_ptr metadata, + jr_uncompressed_ptr dest) { + Timer applyRecMapTime; + + timerStart(&applyRecMapTime); + for (auto i = 0; i < kProfileCount; i++) { + ASSERT_EQ(OK, applyRecoveryMap(yuv420Image, map, metadata, dest)); + } + timerStop(&applyRecMapTime); + + ALOGE("Apply Recovery Map:- Res = %i x %i, time = %f ms", + yuv420Image->width, yuv420Image->height, + elapsedTime(&applyRecMapTime) / (kProfileCount * 1000.f)); +} + TEST_F(RecoveryMapTest, build) { // Force all of the recovery map lib to be linked by calling all public functions. RecoveryMap recovery_map; @@ -382,4 +454,46 @@ TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) { free(decodedJpegR.data); } +TEST_F(RecoveryMapTest, ProfileRecoveryMapFuncs) { + const size_t kWidth = TEST_IMAGE_WIDTH; + const size_t kHeight = TEST_IMAGE_HEIGHT; + + // Load input files. + if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + mRawP010Image.width = kWidth; + mRawP010Image.height = kHeight; + mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100; + + if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + mRawYuv420Image.width = kWidth; + mRawYuv420Image.height = kHeight; + mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709; + + RecoveryMapBenchmark benchmark; + + jpegr_metadata metadata = { .version = 1, + .maxContentBoost = 8.0f, + .minContentBoost = 1.0f / 8.0f }; + + jpegr_uncompressed_struct map = { .data = NULL, + .width = 0, + .height = 0, + .colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED }; + + benchmark.BenchmarkGenerateRecoveryMap(&mRawYuv420Image, &mRawP010Image, &metadata, &map); + + const int dstSize = mRawYuv420Image.width * mRawYuv420Image.height * 4; + auto bufferDst = std::make_unique(dstSize); + jpegr_uncompressed_struct dest = { .data = bufferDst.get(), + .width = 0, + .height = 0, + .colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED }; + + benchmark.BenchmarkApplyRecoveryMap(&mRawYuv420Image, &map, &metadata, &dest); +} + } // namespace android::recoverymap diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp index 2eec95f01b..80a9596e3c 100644 --- a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp +++ b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp @@ -42,7 +42,7 @@ public: } float Map(uint8_t e) { - return (static_cast(e) - 127.5f) / 127.5f; + return static_cast(e) / 255.0f; } Color ColorMin(Color e1, Color e2) { @@ -88,10 +88,10 @@ public: return luminance_scaled * scale_factor; } - Color Recover(Color yuv_gamma, float recovery, float max_content_boost) { + Color Recover(Color yuv_gamma, float recovery, jr_metadata_ptr metadata) { Color rgb_gamma = srgbYuvToRgb(yuv_gamma); Color rgb = srgbInvOetf(rgb_gamma); - return applyRecovery(rgb, recovery, max_content_boost); + return applyRecovery(rgb, recovery, metadata); } jpegr_uncompressed_struct Yuv420Image() { @@ -518,59 +518,95 @@ TEST_F(RecoveryMapMathTest, PqInvOetf) { } TEST_F(RecoveryMapMathTest, PqInvOetfLUT) { - float increment = 1.0 / 1024.0; - float value = 0.0f; - for (int idx = 0; idx < 1024; idx++, value += increment) { + for (int idx = 0; idx < kPqInvOETFNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kPqInvOETFNumEntries - 1); EXPECT_FLOAT_EQ(pqInvOetf(value), pqInvOetfLUT(value)); } } TEST_F(RecoveryMapMathTest, HlgInvOetfLUT) { - float increment = 1.0 / 1024.0; - float value = 0.0f; - for (int idx = 0; idx < 1024; idx++, value += increment) { + for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kHlgInvOETFNumEntries - 1); EXPECT_FLOAT_EQ(hlgInvOetf(value), hlgInvOetfLUT(value)); } } TEST_F(RecoveryMapMathTest, pqOetfLUT) { - float increment = 1.0 / 1024.0; - float value = 0.0f; - for (int idx = 0; idx < 1024; idx++, value += increment) { + for (int idx = 0; idx < kPqOETFNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kPqOETFNumEntries - 1); EXPECT_FLOAT_EQ(pqOetf(value), pqOetfLUT(value)); } } TEST_F(RecoveryMapMathTest, hlgOetfLUT) { - float increment = 1.0 / 1024.0; - float value = 0.0f; - for (int idx = 0; idx < 1024; idx++, value += increment) { + for (int idx = 0; idx < kHlgOETFNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kHlgOETFNumEntries - 1); EXPECT_FLOAT_EQ(hlgOetf(value), hlgOetfLUT(value)); } } TEST_F(RecoveryMapMathTest, srgbInvOetfLUT) { - float increment = 1.0 / 1024.0; - float value = 0.0f; - for (int idx = 0; idx < 1024; idx++, value += increment) { + for (int idx = 0; idx < kSrgbInvOETFNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kSrgbInvOETFNumEntries - 1); EXPECT_FLOAT_EQ(srgbInvOetf(value), srgbInvOetfLUT(value)); } } TEST_F(RecoveryMapMathTest, applyRecoveryLUT) { - float increment = 2.0 / kRecoveryFactorNumEntries; - for (float hdrRatio = 1.0f; hdrRatio <= 10.0f; hdrRatio += 1.0f) { - RecoveryLUT recoveryLUT(hdrRatio); - for (float value = -1.0f; value <= -1.0f; value += increment) { - EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, hdrRatio), + for (int boost = 1; boost <= 10; boost++) { + jpegr_metadata metadata = { .maxContentBoost = static_cast(boost), + .minContentBoost = 1.0f / static_cast(boost) }; + RecoveryLUT recoveryLUT(&metadata); + for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kRecoveryFactorNumEntries - 1); + EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata), applyRecoveryLUT(RgbBlack(), value, recoveryLUT)); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, hdrRatio), + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, &metadata), applyRecoveryLUT(RgbWhite(), value, recoveryLUT)); - EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, hdrRatio), + EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, &metadata), applyRecoveryLUT(RgbRed(), value, recoveryLUT)); - EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, hdrRatio), + EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, &metadata), applyRecoveryLUT(RgbGreen(), value, recoveryLUT)); - EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, hdrRatio), + EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata), + applyRecoveryLUT(RgbBlue(), value, recoveryLUT)); + } + } + + for (int boost = 1; boost <= 10; boost++) { + jpegr_metadata metadata = { .maxContentBoost = static_cast(boost), + .minContentBoost = 1.0f }; + RecoveryLUT recoveryLUT(&metadata); + for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kRecoveryFactorNumEntries - 1); + EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata), + applyRecoveryLUT(RgbBlack(), value, recoveryLUT)); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, &metadata), + applyRecoveryLUT(RgbWhite(), value, recoveryLUT)); + EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, &metadata), + applyRecoveryLUT(RgbRed(), value, recoveryLUT)); + EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, &metadata), + applyRecoveryLUT(RgbGreen(), value, recoveryLUT)); + EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata), + applyRecoveryLUT(RgbBlue(), value, recoveryLUT)); + } + } + + for (int boost = 1; boost <= 10; boost++) { + jpegr_metadata metadata = { .maxContentBoost = static_cast(boost), + .minContentBoost = 1.0f / pow(static_cast(boost), + 1.0f / 3.0f) }; + RecoveryLUT recoveryLUT(&metadata); + for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kRecoveryFactorNumEntries - 1); + EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata), + applyRecoveryLUT(RgbBlack(), value, recoveryLUT)); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, &metadata), + applyRecoveryLUT(RgbWhite(), value, recoveryLUT)); + EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, &metadata), + applyRecoveryLUT(RgbRed(), value, recoveryLUT)); + EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, &metadata), + applyRecoveryLUT(RgbGreen(), value, recoveryLUT)); + EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata), applyRecoveryLUT(RgbBlue(), value, recoveryLUT)); } } @@ -623,60 +659,121 @@ TEST_F(RecoveryMapMathTest, ColorConversionLookup) { } TEST_F(RecoveryMapMathTest, EncodeRecovery) { - EXPECT_EQ(encodeRecovery(0.0f, 0.0f, 4.0f), 127); - EXPECT_EQ(encodeRecovery(0.0f, 1.0f, 4.0f), 127); - EXPECT_EQ(encodeRecovery(1.0f, 0.0f, 4.0f), 0); - EXPECT_EQ(encodeRecovery(0.5f, 0.0f, 4.0f), 0); - - EXPECT_EQ(encodeRecovery(1.0f, 1.0f, 4.0f), 127); - EXPECT_EQ(encodeRecovery(1.0f, 4.0f, 4.0f), 255); - EXPECT_EQ(encodeRecovery(1.0f, 5.0f, 4.0f), 255); - EXPECT_EQ(encodeRecovery(4.0f, 1.0f, 4.0f), 0); - EXPECT_EQ(encodeRecovery(4.0f, 0.5f, 4.0f), 0); - EXPECT_EQ(encodeRecovery(1.0f, 2.0f, 4.0f), 191); - EXPECT_EQ(encodeRecovery(2.0f, 1.0f, 4.0f), 63); - - EXPECT_EQ(encodeRecovery(1.0f, 2.0f, 2.0f), 255); - EXPECT_EQ(encodeRecovery(2.0f, 1.0f, 2.0f), 0); - EXPECT_EQ(encodeRecovery(1.0f, 1.41421f, 2.0f), 191); - EXPECT_EQ(encodeRecovery(1.41421f, 1.0f, 2.0f), 63); - - EXPECT_EQ(encodeRecovery(1.0f, 8.0f, 8.0f), 255); - EXPECT_EQ(encodeRecovery(8.0f, 1.0f, 8.0f), 0); - EXPECT_EQ(encodeRecovery(1.0f, 2.82843f, 8.0f), 191); - EXPECT_EQ(encodeRecovery(2.82843f, 1.0f, 8.0f), 63); + jpegr_metadata metadata = { .maxContentBoost = 4.0f, + .minContentBoost = 1.0f / 4.0f }; + + EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 127); + EXPECT_EQ(encodeRecovery(0.0f, 1.0f, &metadata), 127); + EXPECT_EQ(encodeRecovery(1.0f, 0.0f, &metadata), 0); + EXPECT_EQ(encodeRecovery(0.5f, 0.0f, &metadata), 0); + + EXPECT_EQ(encodeRecovery(1.0f, 1.0f, &metadata), 127); + EXPECT_EQ(encodeRecovery(1.0f, 4.0f, &metadata), 255); + EXPECT_EQ(encodeRecovery(1.0f, 5.0f, &metadata), 255); + EXPECT_EQ(encodeRecovery(4.0f, 1.0f, &metadata), 0); + EXPECT_EQ(encodeRecovery(4.0f, 0.5f, &metadata), 0); + EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 191); + EXPECT_EQ(encodeRecovery(2.0f, 1.0f, &metadata), 63); + + metadata.maxContentBoost = 2.0f; + metadata.minContentBoost = 1.0f / 2.0f; + + EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 255); + EXPECT_EQ(encodeRecovery(2.0f, 1.0f, &metadata), 0); + EXPECT_EQ(encodeRecovery(1.0f, 1.41421f, &metadata), 191); + EXPECT_EQ(encodeRecovery(1.41421f, 1.0f, &metadata), 63); + + metadata.maxContentBoost = 8.0f; + metadata.minContentBoost = 1.0f / 8.0f; + + EXPECT_EQ(encodeRecovery(1.0f, 8.0f, &metadata), 255); + EXPECT_EQ(encodeRecovery(8.0f, 1.0f, &metadata), 0); + EXPECT_EQ(encodeRecovery(1.0f, 2.82843f, &metadata), 191); + EXPECT_EQ(encodeRecovery(2.82843f, 1.0f, &metadata), 63); + + metadata.maxContentBoost = 8.0f; + metadata.minContentBoost = 1.0f; + + EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 0); + EXPECT_EQ(encodeRecovery(1.0f, 0.0f, &metadata), 0); + + EXPECT_EQ(encodeRecovery(1.0f, 1.0f, &metadata), 0); + EXPECT_EQ(encodeRecovery(1.0f, 8.0f, &metadata), 255); + EXPECT_EQ(encodeRecovery(1.0f, 4.0f, &metadata), 170); + EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 85); + + metadata.maxContentBoost = 8.0f; + metadata.minContentBoost = 0.5f; + + EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 63); + EXPECT_EQ(encodeRecovery(1.0f, 0.0f, &metadata), 0); + + EXPECT_EQ(encodeRecovery(1.0f, 1.0f, &metadata), 63); + EXPECT_EQ(encodeRecovery(1.0f, 8.0f, &metadata), 255); + EXPECT_EQ(encodeRecovery(1.0f, 4.0f, &metadata), 191); + EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 127); + EXPECT_EQ(encodeRecovery(1.0f, 0.7071f, &metadata), 31); + EXPECT_EQ(encodeRecovery(1.0f, 0.5f, &metadata), 0); } TEST_F(RecoveryMapMathTest, ApplyRecovery) { - EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), -1.0f, 4.0f), RgbBlack()); - EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.0f, 4.0f), RgbBlack()); - EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 1.0f, 4.0f), RgbBlack()); - - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -1.0f, 4.0f), RgbWhite() / 4.0f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -0.5f, 4.0f), RgbWhite() / 2.0f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, 4.0f), RgbWhite()); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, 4.0f), RgbWhite() * 2.0f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, 4.0f), RgbWhite() * 4.0f); - - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -1.0f, 2.0f), RgbWhite() / 2.0f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -0.5f, 2.0f), RgbWhite() / 1.41421f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, 2.0f), RgbWhite()); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, 2.0f), RgbWhite() * 1.41421f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, 2.0f), RgbWhite() * 2.0f); - - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -1.0f, 8.0f), RgbWhite() / 8.0f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -0.5f, 8.0f), RgbWhite() / 2.82843f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, 8.0f), RgbWhite()); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, 8.0f), RgbWhite() * 2.82843f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, 8.0f), RgbWhite() * 8.0f); + jpegr_metadata metadata = { .maxContentBoost = 4.0f, + .minContentBoost = 1.0f / 4.0f }; + + EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.0f, &metadata), RgbBlack()); + EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.5f, &metadata), RgbBlack()); + EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 1.0f, &metadata), RgbBlack()); + + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 4.0f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.0f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite()); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.0f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 4.0f); + + metadata.maxContentBoost = 2.0f; + metadata.minContentBoost = 1.0f / 2.0f; + + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite() / 1.41421f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite()); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 1.41421f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 2.0f); + + metadata.maxContentBoost = 8.0f; + metadata.minContentBoost = 1.0f / 8.0f; + + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 8.0f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.82843f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite()); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.82843f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); + + metadata.maxContentBoost = 8.0f; + metadata.minContentBoost = 1.0f; + + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite()); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); + + metadata.maxContentBoost = 8.0f; + metadata.minContentBoost = 0.5f; + + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite()); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite() * 2.0f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 4.0f); + EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); Color e = {{{ 0.0f, 0.5f, 1.0f }}}; - - EXPECT_RGB_NEAR(applyRecovery(e, -1.0f, 4.0f), e / 4.0f); - EXPECT_RGB_NEAR(applyRecovery(e, -0.5f, 4.0f), e / 2.0f); - EXPECT_RGB_NEAR(applyRecovery(e, 0.0f, 4.0f), e); - EXPECT_RGB_NEAR(applyRecovery(e, 0.5f, 4.0f), e * 2.0f); - EXPECT_RGB_NEAR(applyRecovery(e, 1.0f, 4.0f), e * 4.0f); + metadata.maxContentBoost = 4.0f; + metadata.minContentBoost = 1.0f / 4.0f; + + EXPECT_RGB_NEAR(applyRecovery(e, 0.0f, &metadata), e / 4.0f); + EXPECT_RGB_NEAR(applyRecovery(e, 0.25f, &metadata), e / 2.0f); + EXPECT_RGB_NEAR(applyRecovery(e, 0.5f, &metadata), e); + EXPECT_RGB_NEAR(applyRecovery(e, 0.75f, &metadata), e * 2.0f); + EXPECT_RGB_NEAR(applyRecovery(e, 1.0f, &metadata), e * 4.0f); } TEST_F(RecoveryMapMathTest, GetYuv420Pixel) { @@ -785,8 +882,10 @@ TEST_F(RecoveryMapMathTest, SampleMap) { // Instead of reimplementing the sampling algorithm, confirm that the // sample output is within the range of the min and max of the nearest // points. - EXPECT_THAT(sampleMap(&image, kMapScaleFactor, x, y, idwTable), + EXPECT_THAT(sampleMap(&image, kMapScaleFactor, x, y), testing::AllOf(testing::Ge(min), testing::Le(max))); + EXPECT_EQ(sampleMap(&image, kMapScaleFactor, x, y, idwTable), + sampleMap(&image, kMapScaleFactor, x, y)); } } } @@ -882,60 +981,89 @@ TEST_F(RecoveryMapMathTest, GenerateMapLuminancePq) { } TEST_F(RecoveryMapMathTest, ApplyMap) { - EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, 8.0f), + jpegr_metadata metadata = { .maxContentBoost = 8.0f, + .minContentBoost = 1.0f / 8.0f }; + + EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); - EXPECT_RGB_EQ(Recover(YuvBlack(), 1.0f, 8.0f), + EXPECT_RGB_EQ(Recover(YuvBlack(), 1.0f, &metadata), RgbBlack()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 1.0f, 8.0f), + EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 1.0f, &metadata), RgbRed() * 8.0f); - EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 1.0f, 8.0f), + EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 1.0f, &metadata), RgbGreen() * 8.0f); - EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 1.0f, 8.0f), + EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 1.0f, &metadata), RgbBlue() * 8.0f); - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, 8.0f), + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75f, &metadata), RgbWhite() * sqrt(8.0f)); - EXPECT_RGB_EQ(Recover(YuvBlack(), 0.5f, 8.0f), + EXPECT_RGB_EQ(Recover(YuvBlack(), 0.75f, &metadata), RgbBlack()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.5f, 8.0f), + EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.75f, &metadata), RgbRed() * sqrt(8.0f)); - EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.5f, 8.0f), + EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.75f, &metadata), RgbGreen() * sqrt(8.0f)); - EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.5f, 8.0f), + EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.75f, &metadata), RgbBlue() * sqrt(8.0f)); - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, 8.0f), + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata), RgbWhite()); - EXPECT_RGB_EQ(Recover(YuvBlack(), 0.0f, 8.0f), + EXPECT_RGB_EQ(Recover(YuvBlack(), 0.5f, &metadata), RgbBlack()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.0f, 8.0f), + EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.5f, &metadata), RgbRed()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, 8.0f), + EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.5f, &metadata), RgbGreen()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.0f, 8.0f), + EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.5f, &metadata), RgbBlue()); - EXPECT_RGB_EQ(Recover(YuvWhite(), -0.5f, 8.0f), + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata), RgbWhite() / sqrt(8.0f)); - EXPECT_RGB_EQ(Recover(YuvBlack(), -0.5f, 8.0f), + EXPECT_RGB_EQ(Recover(YuvBlack(), 0.25f, &metadata), RgbBlack()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), -0.5f, 8.0f), + EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.25f, &metadata), RgbRed() / sqrt(8.0f)); - EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), -0.5f, 8.0f), + EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.25f, &metadata), RgbGreen() / sqrt(8.0f)); - EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), -0.5f, 8.0f), + EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.25f, &metadata), RgbBlue() / sqrt(8.0f)); - EXPECT_RGB_EQ(Recover(YuvWhite(), -1.0f, 8.0f), + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), RgbWhite() / 8.0f); - EXPECT_RGB_EQ(Recover(YuvBlack(), -1.0f, 8.0f), + EXPECT_RGB_EQ(Recover(YuvBlack(), 0.0f, &metadata), RgbBlack()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), -1.0f, 8.0f), + EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.0f, &metadata), RgbRed() / 8.0f); - EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), -1.0f, 8.0f), + EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, &metadata), RgbGreen() / 8.0f); - EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), -1.0f, 8.0f), + EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.0f, &metadata), RgbBlue() / 8.0f); + + metadata.maxContentBoost = 8.0f; + metadata.minContentBoost = 1.0f; + + EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), + RgbWhite() * 8.0f); + EXPECT_RGB_EQ(Recover(YuvWhite(), 2.0f / 3.0f, &metadata), + RgbWhite() * 4.0f); + EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f / 3.0f, &metadata), + RgbWhite() * 2.0f); + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), + RgbWhite()); + + metadata.maxContentBoost = 8.0f; + metadata.minContentBoost = 0.5f;; + + EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), + RgbWhite() * 8.0f); + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75, &metadata), + RgbWhite() * 4.0f); + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata), + RgbWhite() * 2.0f); + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata), + RgbWhite()); + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), + RgbWhite() / 2.0f); } } // namespace android::recoverymap -- GitLab From 7fe69edeb671e90ea8a7a84e7f3ec181283b0dcd Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Mon, 13 Feb 2023 10:13:26 -0800 Subject: [PATCH 0855/1310] SF: Pass latch time for bufferless surface frames Latch time is used to classify jank type as BufferStuffing. This jank classification does not count as missed frames. Layers without buffers do not pass in a latch time to the frametimeline logic. Fix this inconsistency so we do not incorrectly report missed frames for missed layer updates due to buffer stuffing. Test: check perfetto traces and see leashes are also classified as buffer stuffing Fixes: 266666415 Change-Id: Ie211aa3bd5821f6052cf84a62a2e245132a19d90 --- services/surfaceflinger/Layer.cpp | 9 +++++---- services/surfaceflinger/Layer.h | 4 ++-- services/surfaceflinger/SurfaceFlinger.cpp | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 62e31b9f79..f2a8d44c10 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -772,7 +772,7 @@ void Layer::transferAvailableJankData(const std::deque>& hand // transaction // ---------------------------------------------------------------------------- -uint32_t Layer::doTransaction(uint32_t flags) { +uint32_t Layer::doTransaction(uint32_t flags, nsecs_t latchTime) { ATRACE_CALL(); // TODO: This is unfortunate. @@ -800,23 +800,24 @@ uint32_t Layer::doTransaction(uint32_t flags) { mFlinger->mUpdateInputInfo = true; } - commitTransaction(mDrawingState); + commitTransaction(mDrawingState, latchTime); return flags; } -void Layer::commitTransaction(State&) { +void Layer::commitTransaction(State&, nsecs_t currentLatchTime) { // Set the present state for all bufferlessSurfaceFramesTX to Presented. The // bufferSurfaceFrameTX will be presented in latchBuffer. for (auto& [token, surfaceFrame] : mDrawingState.bufferlessSurfaceFramesTX) { if (surfaceFrame->getPresentState() != PresentState::Presented) { // With applyPendingStates, we could end up having presented surfaceframes from previous // states - surfaceFrame->setPresentState(PresentState::Presented); + surfaceFrame->setPresentState(PresentState::Presented, mLastLatchTime); mFlinger->mFrameTimeline->addSurfaceFrame(surfaceFrame); } } mDrawingState.bufferlessSurfaceFramesTX.clear(); + mLastLatchTime = currentLatchTime; } uint32_t Layer::clearTransactionFlags(uint32_t mask) { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 429dfb0c6f..8424d7da9b 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -614,7 +614,7 @@ public: * doTransaction - process the transaction. This is a good place to figure * out which attributes of the surface have changed. */ - virtual uint32_t doTransaction(uint32_t transactionFlags); + virtual uint32_t doTransaction(uint32_t transactionFlags, nsecs_t currentLatchTime); /* * Remove relative z for the layer if its relative parent is not part of the @@ -846,7 +846,7 @@ protected: void preparePerFrameCompositionState(); void preparePerFrameBufferCompositionState(); void preparePerFrameEffectsCompositionState(); - virtual void commitTransaction(State& stateToCommit); + virtual void commitTransaction(State& stateToCommit, nsecs_t currentLatchTime = 0); void gatherBufferInfo(); void onSurfaceFrameCreated(const std::shared_ptr&); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index e3649ec4c1..f223de61fe 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3771,7 +3771,7 @@ void SurfaceFlinger::commitOffscreenLayers() { for (Layer* offscreenLayer : mOffscreenLayers) { offscreenLayer->traverse(LayerVector::StateSet::Drawing, [](Layer* layer) { if (layer->clearTransactionFlags(eTransactionNeeded)) { - layer->doTransaction(0); + layer->doTransaction(0, 0); layer->commitChildList(); } }); @@ -3807,7 +3807,7 @@ bool SurfaceFlinger::latchBuffers() { // second frame. But layer 0's second frame could be waiting on display. mDrawingState.traverse([&](Layer* layer) { if (layer->clearTransactionFlags(eTransactionNeeded) || mForceTransactionDisplayChange) { - const uint32_t flags = layer->doTransaction(0); + const uint32_t flags = layer->doTransaction(0, latchTime); if (flags & Layer::eVisibleRegion) { mVisibleRegionsDirty = true; } -- GitLab From 6c6dd3b2ae7ae677f0448624cf8f6aeafdaffbf5 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Mon, 13 Feb 2023 22:53:06 +0000 Subject: [PATCH 0856/1310] Cache and uncache buffers in the same transaction This removes a race condition where clients may attempt to cache a buffer before an associated uncache buffer request has completed, leading to full buffer cache errors in SurfaceFlinger. GLSurfaceViewTest#testPauseResumeWithoutDelay is failing on barbet and test logs show frequent `ClientCache::add - cache is full` messages. This CL fixes the "cache is full" error but does not fix the broken test. This reverts commit 1088bbf9882d778079eaa7465c1dec2b08848f1a. Reason for revert: b/269141832 Change-Id: I4ba8cf18a367ae6ca94992aabd695d8984fea76f --- libs/gui/ISurfaceComposer.cpp | 23 ++++--- libs/gui/SurfaceComposerClient.cpp | 62 ++++++++++++++----- libs/gui/include/gui/ISurfaceComposer.h | 5 +- libs/gui/include/gui/SurfaceComposerClient.h | 1 + libs/gui/tests/Surface_test.cpp | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 49 ++++++++++----- services/surfaceflinger/SurfaceFlinger.h | 21 +++---- services/surfaceflinger/TransactionState.h | 6 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 15 ++--- .../tests/unittests/TestableSurfaceFlinger.h | 5 +- .../unittests/TransactionApplicationTest.cpp | 19 +++--- 11 files changed, 130 insertions(+), 78 deletions(-) diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index a77ca04943..cefb9a71d6 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -63,7 +63,8 @@ public: Vector& state, const Vector& displays, uint32_t flags, const sp& applyToken, const InputWindowCommands& commands, int64_t desiredPresentTime, - bool isAutoTimestamp, const client_cache_t& uncacheBuffer, + bool isAutoTimestamp, + const std::vector& uncacheBuffers, bool hasListenerCallbacks, const std::vector& listenerCallbacks, uint64_t transactionId) override { @@ -87,8 +88,11 @@ public: SAFE_PARCEL(commands.write, data); SAFE_PARCEL(data.writeInt64, desiredPresentTime); SAFE_PARCEL(data.writeBool, isAutoTimestamp); - SAFE_PARCEL(data.writeStrongBinder, uncacheBuffer.token.promote()); - SAFE_PARCEL(data.writeUint64, uncacheBuffer.id); + SAFE_PARCEL(data.writeUint32, static_cast(uncacheBuffers.size())); + for (const client_cache_t& uncacheBuffer : uncacheBuffers) { + SAFE_PARCEL(data.writeStrongBinder, uncacheBuffer.token.promote()); + SAFE_PARCEL(data.writeUint64, uncacheBuffer.id); + } SAFE_PARCEL(data.writeBool, hasListenerCallbacks); SAFE_PARCEL(data.writeVectorSize, listenerCallbacks); @@ -158,11 +162,14 @@ status_t BnSurfaceComposer::onTransact( SAFE_PARCEL(data.readInt64, &desiredPresentTime); SAFE_PARCEL(data.readBool, &isAutoTimestamp); - client_cache_t uncachedBuffer; + SAFE_PARCEL_READ_SIZE(data.readUint32, &count, data.dataSize()); + std::vector uncacheBuffers(count); sp tmpBinder; - SAFE_PARCEL(data.readNullableStrongBinder, &tmpBinder); - uncachedBuffer.token = tmpBinder; - SAFE_PARCEL(data.readUint64, &uncachedBuffer.id); + for (size_t i = 0; i < count; i++) { + SAFE_PARCEL(data.readNullableStrongBinder, &tmpBinder); + uncacheBuffers[i].token = tmpBinder; + SAFE_PARCEL(data.readUint64, &uncacheBuffers[i].id); + } bool hasListenerCallbacks = false; SAFE_PARCEL(data.readBool, &hasListenerCallbacks); @@ -182,7 +189,7 @@ status_t BnSurfaceComposer::onTransact( return setTransactionState(frameTimelineInfo, state, displays, stateFlags, applyToken, inputWindowCommands, desiredPresentTime, isAutoTimestamp, - uncachedBuffer, hasListenerCallbacks, listenerCallbacks, + uncacheBuffers, hasListenerCallbacks, listenerCallbacks, transactionId); } default: { diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 92125ead1f..21a7f7817a 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -565,11 +565,13 @@ public: return NO_ERROR; } - uint64_t cache(const sp& buffer) { + uint64_t cache(const sp& buffer, + std::optional& outUncacheBuffer) { std::lock_guard lock(mMutex); if (mBuffers.size() >= BUFFER_CACHE_MAX_SIZE) { - evictLeastRecentlyUsedBuffer(); + outUncacheBuffer = findLeastRecentlyUsedBuffer(); + mBuffers.erase(outUncacheBuffer->id); } buffer->addDeathCallback(removeDeadBufferCallback, nullptr); @@ -580,16 +582,13 @@ public: void uncache(uint64_t cacheId) { std::lock_guard lock(mMutex); - uncacheLocked(cacheId); - } - - void uncacheLocked(uint64_t cacheId) REQUIRES(mMutex) { - mBuffers.erase(cacheId); - SurfaceComposerClient::doUncacheBufferTransaction(cacheId); + if (mBuffers.erase(cacheId)) { + SurfaceComposerClient::doUncacheBufferTransaction(cacheId); + } } private: - void evictLeastRecentlyUsedBuffer() REQUIRES(mMutex) { + client_cache_t findLeastRecentlyUsedBuffer() REQUIRES(mMutex) { auto itr = mBuffers.begin(); uint64_t minCounter = itr->second; auto minBuffer = itr; @@ -603,7 +602,8 @@ private: } itr++; } - uncacheLocked(minBuffer->first); + + return {.token = getToken(), .id = minBuffer->first}; } uint64_t getCounter() REQUIRES(mMutex) { @@ -741,6 +741,18 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel InputWindowCommands inputWindowCommands; inputWindowCommands.read(*parcel); + count = static_cast(parcel->readUint32()); + if (count > parcel->dataSize()) { + return BAD_VALUE; + } + std::vector uncacheBuffers(count); + for (size_t i = 0; i < count; i++) { + sp tmpBinder; + SAFE_PARCEL(parcel->readStrongBinder, &tmpBinder); + uncacheBuffers[i].token = tmpBinder; + SAFE_PARCEL(parcel->readUint64, &uncacheBuffers[i].id); + } + // Parsing was successful. Update the object. mId = transactionId; mTransactionNestCount = transactionNestCount; @@ -755,6 +767,7 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel mComposerStates = composerStates; mInputWindowCommands = inputWindowCommands; mApplyToken = applyToken; + mUncacheBuffers = std::move(uncacheBuffers); return NO_ERROR; } @@ -806,6 +819,13 @@ status_t SurfaceComposerClient::Transaction::writeToParcel(Parcel* parcel) const } mInputWindowCommands.write(*parcel); + + SAFE_PARCEL(parcel->writeUint32, static_cast(mUncacheBuffers.size())); + for (const client_cache_t& uncacheBuffer : mUncacheBuffers) { + SAFE_PARCEL(parcel->writeStrongBinder, uncacheBuffer.token.promote()); + SAFE_PARCEL(parcel->writeUint64, uncacheBuffer.id); + } + return NO_ERROR; } @@ -873,6 +893,10 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::merge(Tr } } + for (const auto& cacheId : other.mUncacheBuffers) { + mUncacheBuffers.push_back(cacheId); + } + mInputWindowCommands.merge(other.mInputWindowCommands); mMayContainBuffer |= other.mMayContainBuffer; @@ -891,6 +915,7 @@ void SurfaceComposerClient::Transaction::clear() { mDisplayStates.clear(); mListenerCallbacks.clear(); mInputWindowCommands.clear(); + mUncacheBuffers.clear(); mMayContainBuffer = false; mTransactionNestCount = 0; mAnimation = false; @@ -913,10 +938,10 @@ void SurfaceComposerClient::doUncacheBufferTransaction(uint64_t cacheId) { uncacheBuffer.token = BufferCache::getInstance().getToken(); uncacheBuffer.id = cacheId; Vector composerStates; - status_t status = - sf->setTransactionState(FrameTimelineInfo{}, composerStates, {}, - ISurfaceComposer::eOneWay, Transaction::getDefaultApplyToken(), - {}, systemTime(), true, uncacheBuffer, false, {}, generateId()); + status_t status = sf->setTransactionState(FrameTimelineInfo{}, composerStates, {}, + ISurfaceComposer::eOneWay, + Transaction::getDefaultApplyToken(), {}, systemTime(), + true, {uncacheBuffer}, false, {}, generateId()); if (status != NO_ERROR) { ALOGE_AND_TRACE("SurfaceComposerClient::doUncacheBufferTransaction - %s", strerror(-status)); @@ -954,7 +979,11 @@ void SurfaceComposerClient::Transaction::cacheBuffers() { s->bufferData->buffer = nullptr; } else { // Cache-miss. Include the buffer and send the new cacheId. - cacheId = BufferCache::getInstance().cache(s->bufferData->buffer); + std::optional uncacheBuffer; + cacheId = BufferCache::getInstance().cache(s->bufferData->buffer, uncacheBuffer); + if (uncacheBuffer) { + mUncacheBuffers.push_back(*uncacheBuffer); + } } s->bufferData->flags |= BufferData::BufferDataChange::cachedBufferChanged; s->bufferData->cachedBuffer.token = BufferCache::getInstance().getToken(); @@ -1087,8 +1116,7 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay sp sf(ComposerService::getComposerService()); sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken, mInputWindowCommands, mDesiredPresentTime, mIsAutoTimestamp, - {} /*uncacheBuffer - only set in doUncacheBufferTransaction*/, - hasListenerCallbacks, listenerCallbacks, mId); + mUncacheBuffers, hasListenerCallbacks, listenerCallbacks, mId); mId = generateId(); // Clear the current states and flags diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index 045cc2a184..ae56f9fdb5 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -113,8 +113,9 @@ public: const FrameTimelineInfo& frameTimelineInfo, Vector& state, const Vector& displays, uint32_t flags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, - bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, - const std::vector& listenerCallbacks, uint64_t transactionId) = 0; + bool isAutoTimestamp, const std::vector& uncacheBuffer, + bool hasListenerCallbacks, const std::vector& listenerCallbacks, + uint64_t transactionId) = 0; }; // ---------------------------------------------------------------------------- diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 2458a40ce0..0e51dcf4d4 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -402,6 +402,7 @@ public: SortedVector mDisplayStates; std::unordered_map, CallbackInfo, TCLHash> mListenerCallbacks; + std::vector mUncacheBuffers; uint64_t mId; diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 30148042fb..32d60cd5bd 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -701,7 +701,7 @@ public: const sp& /*applyToken*/, const InputWindowCommands& /*inputWindowCommands*/, int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/, - const client_cache_t& /*cachedBuffer*/, + const std::vector& /*cachedBuffer*/, bool /*hasListenerCallbacks*/, const std::vector& /*listenerCallbacks*/, uint64_t /*transactionId*/) override { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 40de4d675d..d63e2812e2 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3973,7 +3973,7 @@ bool SurfaceFlinger::applyTransactions(std::vector& transactio transaction.displays, transaction.flags, transaction.inputWindowCommands, transaction.desiredPresentTime, transaction.isAutoTimestamp, - transaction.buffer, transaction.postTime, + std::move(transaction.uncacheBufferIds), transaction.postTime, transaction.permissions, transaction.hasListenerCallbacks, transaction.listenerCallbacks, transaction.originPid, transaction.originUid, transaction.id); @@ -4061,8 +4061,9 @@ status_t SurfaceFlinger::setTransactionState( const FrameTimelineInfo& frameTimelineInfo, Vector& states, const Vector& displays, uint32_t flags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, - bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, - const std::vector& listenerCallbacks, uint64_t transactionId) { + bool isAutoTimestamp, const std::vector& uncacheBuffers, + bool hasListenerCallbacks, const std::vector& listenerCallbacks, + uint64_t transactionId) { ATRACE_CALL(); uint32_t permissions = @@ -4096,6 +4097,15 @@ status_t SurfaceFlinger::setTransactionState( const int originPid = ipc->getCallingPid(); const int originUid = ipc->getCallingUid(); + std::vector uncacheBufferIds; + uncacheBufferIds.reserve(uncacheBuffers.size()); + for (const auto& uncacheBuffer : uncacheBuffers) { + sp buffer = ClientCache::getInstance().erase(uncacheBuffer); + if (buffer != nullptr) { + uncacheBufferIds.push_back(buffer->getId()); + } + } + std::vector resolvedStates; resolvedStates.reserve(states.size()); for (auto& state : states) { @@ -4113,14 +4123,22 @@ status_t SurfaceFlinger::setTransactionState( } } - TransactionState state{frameTimelineInfo, resolvedStates, - displays, flags, - applyToken, inputWindowCommands, - desiredPresentTime, isAutoTimestamp, - uncacheBuffer, postTime, - permissions, hasListenerCallbacks, - listenerCallbacks, originPid, - originUid, transactionId}; + TransactionState state{frameTimelineInfo, + resolvedStates, + displays, + flags, + applyToken, + inputWindowCommands, + desiredPresentTime, + isAutoTimestamp, + std::move(uncacheBufferIds), + postTime, + permissions, + hasListenerCallbacks, + listenerCallbacks, + originPid, + originUid, + transactionId}; if (mTransactionTracing) { mTransactionTracing->addQueuedTransaction(state); @@ -4144,7 +4162,7 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin Vector& displays, uint32_t flags, const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime, bool isAutoTimestamp, - const client_cache_t& uncacheBuffer, + const std::vector& uncacheBufferIds, const int64_t postTime, uint32_t permissions, bool hasListenerCallbacks, const std::vector& listenerCallbacks, @@ -4185,11 +4203,8 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin ALOGE("Only privileged callers are allowed to send input commands."); } - if (uncacheBuffer.isValid()) { - sp buffer = ClientCache::getInstance().erase(uncacheBuffer); - if (buffer != nullptr) { - mBufferIdsToUncache.push_back(buffer->getId()); - } + for (uint64_t uncacheBufferId : uncacheBufferIds) { + mBufferIdsToUncache.push_back(uncacheBufferId); } // If a synchronous transaction is explicitly requested without any changes, force a transaction diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 5457be81a5..9245399e0d 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -501,7 +501,8 @@ private: uint32_t flags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, - const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, + const std::vector& uncacheBuffers, + bool hasListenerCallbacks, const std::vector& listenerCallbacks, uint64_t transactionId) override; void bootFinished(); @@ -714,16 +715,14 @@ private: /* * Transactions */ - bool applyTransactionState(const FrameTimelineInfo& info, - std::vector& state, - Vector& displays, uint32_t flags, - const InputWindowCommands& inputWindowCommands, - const int64_t desiredPresentTime, bool isAutoTimestamp, - const client_cache_t& uncacheBuffer, const int64_t postTime, - uint32_t permissions, bool hasListenerCallbacks, - const std::vector& listenerCallbacks, - int originPid, int originUid, uint64_t transactionId) - REQUIRES(mStateLock); + bool applyTransactionState( + const FrameTimelineInfo& info, std::vector& state, + Vector& displays, uint32_t flags, + const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime, + bool isAutoTimestamp, const std::vector& uncacheBufferIds, + const int64_t postTime, uint32_t permissions, bool hasListenerCallbacks, + const std::vector& listenerCallbacks, int originPid, int originUid, + uint64_t transactionId) REQUIRES(mStateLock); // Flush pending transactions that were presented after desiredPresentTime. bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext); // Returns true if there is at least one transaction that needs to be flushed diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h index 366b09d68b..5025c4935c 100644 --- a/services/surfaceflinger/TransactionState.h +++ b/services/surfaceflinger/TransactionState.h @@ -43,7 +43,7 @@ struct TransactionState { const Vector& displayStates, uint32_t transactionFlags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, - const client_cache_t& uncacheBuffer, int64_t postTime, uint32_t permissions, + std::vector uncacheBufferIds, int64_t postTime, uint32_t permissions, bool hasListenerCallbacks, std::vector listenerCallbacks, int originPid, int originUid, uint64_t transactionId) : frameTimelineInfo(frameTimelineInfo), @@ -54,7 +54,7 @@ struct TransactionState { inputWindowCommands(inputWindowCommands), desiredPresentTime(desiredPresentTime), isAutoTimestamp(isAutoTimestamp), - buffer(uncacheBuffer), + uncacheBufferIds(std::move(uncacheBufferIds)), postTime(postTime), permissions(permissions), hasListenerCallbacks(hasListenerCallbacks), @@ -109,7 +109,7 @@ struct TransactionState { InputWindowCommands inputWindowCommands; int64_t desiredPresentTime; bool isAutoTimestamp; - client_cache_t buffer; + std::vector uncacheBufferIds; int64_t postTime; uint32_t permissions; bool hasListenerCallbacks; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 81ca659915..c22d78b86e 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -730,17 +730,18 @@ public: return mFlinger->mTransactionHandler.mPendingTransactionQueues; } - auto setTransactionState(const FrameTimelineInfo &frameTimelineInfo, - Vector &states, const Vector &displays, - uint32_t flags, const sp &applyToken, - const InputWindowCommands &inputWindowCommands, + auto setTransactionState(const FrameTimelineInfo& frameTimelineInfo, + Vector& states, const Vector& displays, + uint32_t flags, const sp& applyToken, + const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, - const client_cache_t &uncacheBuffer, bool hasListenerCallbacks, - std::vector &listenerCallbacks, + const std::vector& uncacheBuffers, + bool hasListenerCallbacks, + std::vector& listenerCallbacks, uint64_t transactionId) { return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken, inputWindowCommands, desiredPresentTime, - isAutoTimestamp, uncacheBuffer, hasListenerCallbacks, + isAutoTimestamp, uncacheBuffers, hasListenerCallbacks, listenerCallbacks, transactionId); } diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 584d52ca02..72e0c7be16 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -439,12 +439,13 @@ public: uint32_t flags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, - const client_cache_t& uncacheBuffer, bool hasListenerCallbacks, + const std::vector& uncacheBuffers, + bool hasListenerCallbacks, std::vector& listenerCallbacks, uint64_t transactionId) { return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken, inputWindowCommands, desiredPresentTime, - isAutoTimestamp, uncacheBuffer, hasListenerCallbacks, + isAutoTimestamp, uncacheBuffers, hasListenerCallbacks, listenerCallbacks, transactionId); } diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index d84698f279..a28d1cd415 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -102,7 +102,7 @@ public: int64_t desiredPresentTime = 0; bool isAutoTimestamp = true; FrameTimelineInfo frameTimelineInfo; - client_cache_t uncacheBuffer; + std::vector uncacheBuffers; uint64_t id = static_cast(-1); static_assert(0xffffffffffffffff == static_cast(-1)); }; @@ -138,7 +138,7 @@ public: transaction.displays, transaction.flags, transaction.applyToken, transaction.inputWindowCommands, transaction.desiredPresentTime, transaction.isAutoTimestamp, - transaction.uncacheBuffer, mHasListenerCallbacks, mCallbacks, + transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks, transaction.id); // If transaction is synchronous, SF applyTransactionState should time out (5s) wating for @@ -165,7 +165,7 @@ public: transaction.displays, transaction.flags, transaction.applyToken, transaction.inputWindowCommands, transaction.desiredPresentTime, transaction.isAutoTimestamp, - transaction.uncacheBuffer, mHasListenerCallbacks, mCallbacks, + transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks, transaction.id); nsecs_t returnedTime = systemTime(); @@ -196,7 +196,7 @@ public: transactionA.displays, transactionA.flags, transactionA.applyToken, transactionA.inputWindowCommands, transactionA.desiredPresentTime, transactionA.isAutoTimestamp, - transactionA.uncacheBuffer, mHasListenerCallbacks, mCallbacks, + transactionA.uncacheBuffers, mHasListenerCallbacks, mCallbacks, transactionA.id); // This thread should not have been blocked by the above transaction @@ -211,7 +211,7 @@ public: transactionB.displays, transactionB.flags, transactionB.applyToken, transactionB.inputWindowCommands, transactionB.desiredPresentTime, transactionB.isAutoTimestamp, - transactionB.uncacheBuffer, mHasListenerCallbacks, mCallbacks, + transactionB.uncacheBuffers, mHasListenerCallbacks, mCallbacks, transactionB.id); // this thread should have been blocked by the above transaction @@ -243,7 +243,7 @@ TEST_F(TransactionApplicationTest, AddToPendingQueue) { mFlinger.setTransactionState(transactionA.frameTimelineInfo, transactionA.states, transactionA.displays, transactionA.flags, transactionA.applyToken, transactionA.inputWindowCommands, transactionA.desiredPresentTime, - transactionA.isAutoTimestamp, transactionA.uncacheBuffer, + transactionA.isAutoTimestamp, transactionA.uncacheBuffers, mHasListenerCallbacks, mCallbacks, transactionA.id); auto& transactionQueue = mFlinger.getTransactionQueue(); @@ -263,7 +263,7 @@ TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) { mFlinger.setTransactionState(transactionA.frameTimelineInfo, transactionA.states, transactionA.displays, transactionA.flags, transactionA.applyToken, transactionA.inputWindowCommands, transactionA.desiredPresentTime, - transactionA.isAutoTimestamp, transactionA.uncacheBuffer, + transactionA.isAutoTimestamp, transactionA.uncacheBuffers, mHasListenerCallbacks, mCallbacks, transactionA.id); auto& transactionQueue = mFlinger.getTransactionQueue(); @@ -277,7 +277,7 @@ TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) { mFlinger.setTransactionState(empty.frameTimelineInfo, empty.states, empty.displays, empty.flags, empty.applyToken, empty.inputWindowCommands, empty.desiredPresentTime, empty.isAutoTimestamp, - empty.uncacheBuffer, mHasListenerCallbacks, mCallbacks, empty.id); + empty.uncacheBuffers, mHasListenerCallbacks, mCallbacks, empty.id); // flush transaction queue should flush as desiredPresentTime has // passed @@ -374,8 +374,7 @@ public: transaction.applyToken, transaction.inputWindowCommands, transaction.desiredPresentTime, - transaction.isAutoTimestamp, - transaction.uncacheBuffer, systemTime(), 0, + transaction.isAutoTimestamp, {}, systemTime(), 0, mHasListenerCallbacks, mCallbacks, getpid(), static_cast(getuid()), transaction.id); mFlinger.setTransactionStateInternal(transactionState); -- GitLab From 761b52d1d4d3fc75eb4dcd66766c8c465ea96099 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Fri, 10 Feb 2023 22:38:38 +0000 Subject: [PATCH 0857/1310] JpegR refactor: rename recoverymap to jpegr Bug: 264715926 Test: build Change-Id: I6fd866979bcb608084293a16c59f5ce150dbff12 --- libs/jpegrecoverymap/Android.bp | 4 +- .../{recoverymap.h => jpegr.h} | 13 ++- .../{recoverymaputils.h => jpegrutils.h} | 8 +- .../include/jpegrecoverymap/recoverymapmath.h | 2 +- .../{recoverymap.cpp => jpegr.cpp} | 30 +++---- .../{recoverymaputils.cpp => jpegrutils.cpp} | 2 +- libs/jpegrecoverymap/tests/Android.bp | 2 +- .../{recoverymap_test.cpp => jpegr_test.cpp} | 82 +++++++++---------- 8 files changed, 70 insertions(+), 73 deletions(-) rename libs/jpegrecoverymap/include/jpegrecoverymap/{recoverymap.h => jpegr.h} (97%) rename libs/jpegrecoverymap/include/jpegrecoverymap/{recoverymaputils.h => jpegrutils.h} (93%) rename libs/jpegrecoverymap/{recoverymap.cpp => jpegr.cpp} (96%) rename libs/jpegrecoverymap/{recoverymaputils.cpp => jpegrutils.cpp} (99%) rename libs/jpegrecoverymap/tests/{recoverymap_test.cpp => jpegr_test.cpp} (88%) diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp index ee112e802c..78d1912d24 100644 --- a/libs/jpegrecoverymap/Android.bp +++ b/libs/jpegrecoverymap/Android.bp @@ -29,9 +29,9 @@ cc_library { local_include_dirs: ["include"], srcs: [ - "recoverymap.cpp", + "jpegr.cpp", "recoverymapmath.cpp", - "recoverymaputils.cpp", + "jpegrutils.cpp", ], shared_libs: [ diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h similarity index 97% rename from libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h rename to libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h index 1fd129b991..9f8801e1bb 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H -#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H +#ifndef ANDROID_JPEGRECOVERYMAP_JPEGR_H +#define ANDROID_JPEGRECOVERYMAP_JPEGR_H #include "jpegrerrorcode.h" @@ -98,7 +98,7 @@ typedef struct jpegr_exif_struct* jr_exif_ptr; typedef struct jpegr_metadata* jr_metadata_ptr; typedef struct jpegr_info_struct* jr_info_ptr; -class RecoveryMap { +class JpegR { public: /* * Encode API-0 @@ -221,10 +221,7 @@ public: */ status_t getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info); - protected: - // Following functions protected instead of private for testing. - /* * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and * 10-bit yuv images as input, and calculate the uncompressed recovery map. The input images @@ -234,7 +231,7 @@ protected: * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param hdr_tf transfer function of the HDR image * @param dest recovery map; caller responsible for memory of data - * @param metadata minContentBoost and maxContentBoost are filled in + * @param metadata max_content_boost is filled in * @return NO_ERROR if calculation succeeds, error code if error occurs. */ status_t generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, @@ -328,4 +325,4 @@ private: } // namespace android::recoverymap -#endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H +#endif // ANDROID_JPEGRECOVERYMAP_JPEGR_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h similarity index 93% rename from libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h rename to libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h index de29a339ed..73867f9552 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h @@ -14,10 +14,10 @@ * limitations under the License. */ -#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H -#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H +#ifndef ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H +#define ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H -#include +#include #include #include @@ -94,4 +94,4 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata); } -#endif //ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H +#endif //ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h index 6eed08afb4..71b18b9094 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h @@ -20,7 +20,7 @@ #include #include -#include +#include namespace android::recoverymap { diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/jpegr.cpp similarity index 96% rename from libs/jpegrecoverymap/recoverymap.cpp rename to libs/jpegrecoverymap/jpegr.cpp index 349223bb6b..fbd511a600 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/jpegr.cpp @@ -14,11 +14,11 @@ * limitations under the License. */ -#include +#include #include #include #include -#include +#include #include #include @@ -101,7 +101,7 @@ static const map< }; /* Encode API-0 */ -status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, +status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpegr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality, @@ -163,7 +163,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, } /* Encode API-1 */ -status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, +status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, jpegr_transfer_function hdr_tf, jr_compressed_ptr dest, @@ -227,7 +227,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, } /* Encode API-2 */ -status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, +status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, jr_compressed_ptr compressed_jpeg_image, jpegr_transfer_function hdr_tf, @@ -272,7 +272,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, } /* Encode API-3 */ -status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, +status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_compressed_ptr compressed_jpeg_image, jpegr_transfer_function hdr_tf, jr_compressed_ptr dest) { @@ -324,7 +324,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return NO_ERROR; } -status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, +status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info) { if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; @@ -345,7 +345,7 @@ status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, } /* Decode API */ -status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, +status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, jr_uncompressed_ptr dest, jr_exif_ptr exif, bool request_sdr) { @@ -405,7 +405,7 @@ status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, return NO_ERROR; } -status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, +status_t JpegR::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, jr_compressed_ptr dest) { if (uncompressed_recovery_map == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; @@ -489,7 +489,7 @@ void JobQueue::reset() { mQueuedAllJobs = false; } -status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, +status_t JpegR::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, jr_uncompressed_ptr uncompressed_p010_image, jpegr_transfer_function hdr_tf, jr_metadata_ptr metadata, @@ -689,7 +689,7 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4 return NO_ERROR; } -status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, +status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, jr_uncompressed_ptr uncompressed_recovery_map, jr_metadata_ptr metadata, jr_uncompressed_ptr dest) { @@ -773,7 +773,7 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ return NO_ERROR; } -status_t RecoveryMap::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image, +status_t JpegR::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image, jr_compressed_ptr primary_image, jr_compressed_ptr recovery_map) { if (compressed_jpegr_image == nullptr) { @@ -823,7 +823,7 @@ status_t RecoveryMap::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compre } -status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image, +status_t JpegR::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image, jr_compressed_ptr dest) { if (compressed_jpegr_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; @@ -854,7 +854,7 @@ status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_imag // Exif 2.2 spec for EXIF marker // Adobe XMP spec part 3 for XMP marker // ICC v4.3 spec for ICC -status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, +status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, jr_compressed_ptr compressed_recovery_map, jr_exif_ptr exif, jr_metadata_ptr metadata, @@ -917,7 +917,7 @@ status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, return NO_ERROR; } -status_t RecoveryMap::toneMap(jr_uncompressed_ptr src, +status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) { if (src == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/jpegrutils.cpp similarity index 99% rename from libs/jpegrecoverymap/recoverymaputils.cpp rename to libs/jpegrecoverymap/jpegrutils.cpp index 40956bdaba..e5323c7b85 100644 --- a/libs/jpegrecoverymap/recoverymaputils.cpp +++ b/libs/jpegrecoverymap/jpegrutils.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include #include #include diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp index e416db9e2d..5a4edb2a25 100644 --- a/libs/jpegrecoverymap/tests/Android.bp +++ b/libs/jpegrecoverymap/tests/Android.bp @@ -25,7 +25,7 @@ cc_test { name: "libjpegrecoverymap_test", test_suites: ["device-tests"], srcs: [ - "recoverymap_test.cpp", + "jpegr_test.cpp", "recoverymapmath_test.cpp", ], shared_libs: [ diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/jpegr_test.cpp similarity index 88% rename from libs/jpegrecoverymap/tests/recoverymap_test.cpp rename to libs/jpegrecoverymap/tests/jpegr_test.cpp index 1b73d94057..9a0bd3b37c 100644 --- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp +++ b/libs/jpegrecoverymap/tests/jpegr_test.cpp @@ -14,9 +14,9 @@ * limitations under the License. */ -#include +#include +#include #include -#include #include #include #include @@ -87,10 +87,10 @@ static bool loadFile(const char filename[], void*& result, int* fileLength) { return true; } -class RecoveryMapTest : public testing::Test { +class JpegRTest : public testing::Test { public: - RecoveryMapTest(); - ~RecoveryMapTest(); + JpegRTest(); + ~JpegRTest(); protected: virtual void SetUp(); @@ -101,17 +101,17 @@ protected: struct jpegr_compressed_struct mJpegImage; }; -RecoveryMapTest::RecoveryMapTest() {} -RecoveryMapTest::~RecoveryMapTest() {} +JpegRTest::JpegRTest() {} +JpegRTest::~JpegRTest() {} -void RecoveryMapTest::SetUp() {} -void RecoveryMapTest::TearDown() { +void JpegRTest::SetUp() {} +void JpegRTest::TearDown() { free(mRawP010Image.data); free(mRawYuv420Image.data); free(mJpegImage.data); } -class RecoveryMapBenchmark : public RecoveryMap { +class JpegRBenchmark : public JpegR { public: void BenchmarkGenerateRecoveryMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image, jr_metadata_ptr metadata, jr_uncompressed_ptr map); @@ -121,7 +121,7 @@ private: const int kProfileCount = 10; }; -void RecoveryMapBenchmark::BenchmarkGenerateRecoveryMap(jr_uncompressed_ptr yuv420Image, +void JpegRBenchmark::BenchmarkGenerateRecoveryMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image, jr_metadata_ptr metadata, jr_uncompressed_ptr map) { @@ -144,7 +144,7 @@ void RecoveryMapBenchmark::BenchmarkGenerateRecoveryMap(jr_uncompressed_ptr yuv4 } -void RecoveryMapBenchmark::BenchmarkApplyRecoveryMap(jr_uncompressed_ptr yuv420Image, +void JpegRBenchmark::BenchmarkApplyRecoveryMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map, jr_metadata_ptr metadata, jr_uncompressed_ptr dest) { @@ -161,19 +161,19 @@ void RecoveryMapBenchmark::BenchmarkApplyRecoveryMap(jr_uncompressed_ptr yuv420I elapsedTime(&applyRecMapTime) / (kProfileCount * 1000.f)); } -TEST_F(RecoveryMapTest, build) { +TEST_F(JpegRTest, build) { // Force all of the recovery map lib to be linked by calling all public functions. - RecoveryMap recovery_map; - recovery_map.encodeJPEGR(nullptr, static_cast(0), nullptr, 0, nullptr); - recovery_map.encodeJPEGR(nullptr, nullptr, static_cast(0), - nullptr, 0, nullptr); - recovery_map.encodeJPEGR(nullptr, nullptr, nullptr, static_cast(0), - nullptr); - recovery_map.encodeJPEGR(nullptr, nullptr, static_cast(0), nullptr); - recovery_map.decodeJPEGR(nullptr, nullptr, nullptr, false); + JpegR jpegRCodec; + jpegRCodec.encodeJPEGR(nullptr, static_cast(0), nullptr, 0, nullptr); + jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast(0), + nullptr, 0, nullptr); + jpegRCodec.encodeJPEGR(nullptr, nullptr, nullptr, static_cast(0), + nullptr); + jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast(0), nullptr); + jpegRCodec.decodeJPEGR(nullptr, nullptr, nullptr, false); } -TEST_F(RecoveryMapTest, writeXmpThenRead) { +TEST_F(JpegRTest, writeXmpThenRead) { jpegr_metadata metadata_expected; metadata_expected.maxContentBoost = 1.25; int length_expected = 1000; @@ -195,7 +195,7 @@ TEST_F(RecoveryMapTest, writeXmpThenRead) { } /* Test Encode API-0 and decode */ -TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) { +TEST_F(JpegRTest, encodeFromP010ThenDecode) { int ret; // Load input files. @@ -206,12 +206,12 @@ TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) { mRawP010Image.height = TEST_IMAGE_HEIGHT; mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100; - RecoveryMap recoveryMap; + JpegR jpegRCodec; jpegr_compressed_struct jpegR; jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); jpegR.data = malloc(jpegR.maxLength); - ret = recoveryMap.encodeJPEGR( + ret = jpegRCodec.encodeJPEGR( &mRawP010Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY, nullptr); if (ret != OK) { FAIL() << "Error code is " << ret; @@ -229,7 +229,7 @@ TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) { jpegr_uncompressed_struct decodedJpegR; int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4; decodedJpegR.data = malloc(decodedJpegRSize); - ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR); + ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR); if (ret != OK) { FAIL() << "Error code is " << ret; } @@ -248,7 +248,7 @@ TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) { } /* Test Encode API-1 and decode */ -TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrThenDecode) { +TEST_F(JpegRTest, encodeFromRawHdrAndSdrThenDecode) { int ret; // Load input files. @@ -266,12 +266,12 @@ TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrThenDecode) { mRawYuv420Image.height = TEST_IMAGE_HEIGHT; mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709; - RecoveryMap recoveryMap; + JpegR jpegRCodec; jpegr_compressed_struct jpegR; jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); jpegR.data = malloc(jpegR.maxLength); - ret = recoveryMap.encodeJPEGR( + ret = jpegRCodec.encodeJPEGR( &mRawP010Image, &mRawYuv420Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY, nullptr); if (ret != OK) { @@ -290,7 +290,7 @@ TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrThenDecode) { jpegr_uncompressed_struct decodedJpegR; int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4; decodedJpegR.data = malloc(decodedJpegRSize); - ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR); + ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR); if (ret != OK) { FAIL() << "Error code is " << ret; } @@ -309,7 +309,7 @@ TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrThenDecode) { } /* Test Encode API-2 and decode */ -TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrAndJpegThenDecode) { +TEST_F(JpegRTest, encodeFromRawHdrAndSdrAndJpegThenDecode) { int ret; // Load input files. @@ -332,12 +332,12 @@ TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrAndJpegThenDecode) { } mJpegImage.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709; - RecoveryMap recoveryMap; + JpegR jpegRCodec; jpegr_compressed_struct jpegR; jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); jpegR.data = malloc(jpegR.maxLength); - ret = recoveryMap.encodeJPEGR( + ret = jpegRCodec.encodeJPEGR( &mRawP010Image, &mRawYuv420Image, &mJpegImage, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR); if (ret != OK) { FAIL() << "Error code is " << ret; @@ -355,7 +355,7 @@ TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrAndJpegThenDecode) { jpegr_uncompressed_struct decodedJpegR; int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4; decodedJpegR.data = malloc(decodedJpegRSize); - ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR); + ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR); if (ret != OK) { FAIL() << "Error code is " << ret; } @@ -374,7 +374,7 @@ TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrAndJpegThenDecode) { } /* Test Encode API-3 and decode */ -TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) { +TEST_F(JpegRTest, encodeFromJpegThenDecode) { int ret; // Load input files. @@ -413,12 +413,12 @@ TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) { } mJpegImage.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709; - RecoveryMap recoveryMap; + JpegR jpegRCodec; jpegr_compressed_struct jpegR; jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); jpegR.data = malloc(jpegR.maxLength); - ret = recoveryMap.encodeJPEGR( + ret = jpegRCodec.encodeJPEGR( &mRawP010Image, &mJpegImage, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR); if (ret != OK) { FAIL() << "Error code is " << ret; @@ -436,7 +436,7 @@ TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) { jpegr_uncompressed_struct decodedJpegR; int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4; decodedJpegR.data = malloc(decodedJpegRSize); - ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR); + ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR); if (ret != OK) { FAIL() << "Error code is " << ret; } @@ -454,7 +454,7 @@ TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) { free(decodedJpegR.data); } -TEST_F(RecoveryMapTest, ProfileRecoveryMapFuncs) { +TEST_F(JpegRTest, ProfileRecoveryMapFuncs) { const size_t kWidth = TEST_IMAGE_WIDTH; const size_t kHeight = TEST_IMAGE_HEIGHT; @@ -473,7 +473,7 @@ TEST_F(RecoveryMapTest, ProfileRecoveryMapFuncs) { mRawYuv420Image.height = kHeight; mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709; - RecoveryMapBenchmark benchmark; + JpegRBenchmark benchmark; jpegr_metadata metadata = { .version = 1, .maxContentBoost = 8.0f, @@ -496,4 +496,4 @@ TEST_F(RecoveryMapTest, ProfileRecoveryMapFuncs) { benchmark.BenchmarkApplyRecoveryMap(&mRawYuv420Image, &map, &metadata, &dest); } -} // namespace android::recoverymap +} // namespace android::recoverymap \ No newline at end of file -- GitLab From feec2c60b201ea4ff65244c6d65f08acc9796a9f Mon Sep 17 00:00:00 2001 From: John Reck Date: Mon, 13 Feb 2023 10:19:08 -0500 Subject: [PATCH 0858/1310] Add ASurfaceTransaction_setExtendedRangeBrightness Bug: 241001465 Test: make / silkfx demo Change-Id: I043474aeda46a65ac93c124d854d71e1a8c082c9 --- include/android/surface_control.h | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/include/android/surface_control.h b/include/android/surface_control.h index f76e73d3cf..79c59f28fb 100644 --- a/include/android/surface_control.h +++ b/include/android/surface_control.h @@ -520,6 +520,45 @@ void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* transactio struct AHdrMetadata_cta861_3* metadata) __INTRODUCED_IN(29); +/** + * Sets the desired extended range brightness for the layer. This only applies for layers whose + * dataspace has RANGE_EXTENDED set on it. + * + * @param surface_control The layer whose extended range brightness is being specified + * @param currentBufferRatio The current hdr/sdr ratio of the current buffer as represented as + * peakHdrBrightnessInNits / targetSdrWhitePointInNits. For example if the + * buffer was rendered with a target SDR whitepoint of 100nits and a max + * display brightness of 200nits, this should be set to 2.0f. + * + * Default value is 1.0f. + * + * Transfer functions that encode their own brightness ranges, such as + * HLG or PQ, should also set this to 1.0f and instead communicate + * extended content brightness information via metadata such as CTA861_3 + * or SMPTE2086. + * + * Must be finite && >= 1.0f + * + * @param desiredRatio The desired hdr/sdr ratio as represented as peakHdrBrightnessInNits / + * targetSdrWhitePointInNits. This can be used to communicate the max desired + * brightness range. This is similar to the "max luminance" value in other + * HDR metadata formats, but represented as a ratio of the target SDR whitepoint + * to the max display brightness. The system may not be able to, or may choose + * not to, deliver the requested range. + * + * If unspecified, the system will attempt to provide the best range it can + * for the given ambient conditions & device state. However, voluntarily + * reducing the requested range can help improve battery life as well as can + * improve quality by ensuring greater bit depth is allocated to the luminance + * range in use. + * + * Must be finite && >= 1.0f + */ +void ASurfaceTransaction_setExtendedRangeBrightness(ASurfaceTransaction* transaction, + ASurfaceControl* surface_control, + float currentBufferRatio, + float desiredRatio) __INTRODUCED_IN(__ANDROID_API_U__); + /** * Same as ASurfaceTransaction_setFrameRateWithChangeStrategy(transaction, surface_control, * frameRate, compatibility, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS). -- GitLab From b2ed830b7ab522f06d34a0fde93094b9648a7166 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Fri, 10 Feb 2023 23:05:04 +0000 Subject: [PATCH 0859/1310] jpegr refactor: rename namespace Bug: 264715926 Test: build Change-Id: Ie60ae4f9362de50fe7d7d93790922333bc26a238 --- .../include/jpegrecoverymap/jpegdecoderhelper.h | 4 ++-- .../include/jpegrecoverymap/jpegencoderhelper.h | 4 ++-- libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h | 4 ++-- .../include/jpegrecoverymap/jpegrerrorcode.h | 4 ++-- .../jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h | 4 ++-- .../include/jpegrecoverymap/recoverymapmath.h | 4 ++-- libs/jpegrecoverymap/jpegdecoderhelper.cpp | 4 ++-- libs/jpegrecoverymap/jpegencoderhelper.cpp | 4 ++-- libs/jpegrecoverymap/jpegr.cpp | 9 +++++---- libs/jpegrecoverymap/jpegrutils.cpp | 4 ++-- libs/jpegrecoverymap/recoverymapmath.cpp | 4 ++-- libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp | 4 ++-- libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp | 4 ++-- libs/jpegrecoverymap/tests/jpegr_test.cpp | 2 +- libs/jpegrecoverymap/tests/recoverymapmath_test.cpp | 4 ++-- 15 files changed, 32 insertions(+), 31 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h index 485869da25..874823709c 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h @@ -25,7 +25,7 @@ extern "C" { } #include #include -namespace android::recoverymap { +namespace android::jpegrecoverymap { /* * Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format. * This class is not thread-safe. @@ -115,6 +115,6 @@ private: // Position of EXIF package, default value is -1 which means no EXIF package appears. size_t mExifPos; }; -} /* namespace android */ +} /* namespace android::jpegrecoverymap */ #endif // ANDROID_JPEGRECOVERYMAP_JPEGDECODERHELPER_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h index f087b55b6f..8b82b2b00a 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h @@ -28,7 +28,7 @@ extern "C" { #include #include -namespace android::recoverymap { +namespace android::jpegrecoverymap { /* * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format. @@ -90,6 +90,6 @@ private: std::vector mResultBuffer; }; -} /* namespace android */ +} /* namespace android::jpegrecoverymap */ #endif // ANDROID_JPEGRECOVERYMAP_JPEGENCODERHELPER_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h index 9f8801e1bb..5455ba61d6 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h @@ -19,7 +19,7 @@ #include "jpegrerrorcode.h" -namespace android::recoverymap { +namespace android::jpegrecoverymap { // Color gamuts for image data typedef enum { @@ -323,6 +323,6 @@ private: jr_uncompressed_ptr dest); }; -} // namespace android::recoverymap +} // namespace android::jpegrecoverymap #endif // ANDROID_JPEGRECOVERYMAP_JPEGR_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h index 699c0d3ca1..f73034338b 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h @@ -16,7 +16,7 @@ #include -namespace android::recoverymap { +namespace android::jpegrecoverymap { enum { // status_t map for errors in the media framework @@ -48,4 +48,4 @@ enum { ERROR_JPEGR_TONEMAP_ERROR = JPEGR_RUNTIME_ERROR_BASE - 5, }; -} // namespace android::recoverymap +} // namespace android::jpegrecoverymap diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h index 73867f9552..3a0f67de19 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h @@ -24,7 +24,7 @@ #include #include -namespace android::recoverymap { +namespace android::jpegrecoverymap { struct jpegr_metadata; @@ -92,6 +92,6 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta * @return XMP metadata in type of string */ std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata); -} +} // namespace android::jpegrecoverymap #endif //ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h index 71b18b9094..c12cee9f19 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h @@ -22,7 +22,7 @@ #include -namespace android::recoverymap { +namespace android::jpegrecoverymap { #define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x) @@ -392,6 +392,6 @@ float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size */ uint32_t colorToRgba1010102(Color e_gamma); -} // namespace android::recoverymap +} // namespace android::jpegrecoverymap #endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H diff --git a/libs/jpegrecoverymap/jpegdecoderhelper.cpp b/libs/jpegrecoverymap/jpegdecoderhelper.cpp index 0754ec9dd6..d36bbf8165 100644 --- a/libs/jpegrecoverymap/jpegdecoderhelper.cpp +++ b/libs/jpegrecoverymap/jpegdecoderhelper.cpp @@ -24,7 +24,7 @@ using namespace std; -namespace android::recoverymap { +namespace android::jpegrecoverymap { const uint32_t kAPP0Marker = JPEG_APP0; // JFIF const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP @@ -413,4 +413,4 @@ bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo, c return true; } -} // namespace android +} // namespace jpegrecoverymap diff --git a/libs/jpegrecoverymap/jpegencoderhelper.cpp b/libs/jpegrecoverymap/jpegencoderhelper.cpp index 54b184d2ef..586cd346e4 100644 --- a/libs/jpegrecoverymap/jpegencoderhelper.cpp +++ b/libs/jpegrecoverymap/jpegencoderhelper.cpp @@ -20,7 +20,7 @@ #include -namespace android::recoverymap { +namespace android::jpegrecoverymap { // The destination manager that can access |mResultBuffer| in JpegEncoderHelper. struct destination_mgr { @@ -236,4 +236,4 @@ bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const return true; } -} // namespace android +} // namespace jpegrecoverymap diff --git a/libs/jpegrecoverymap/jpegr.cpp b/libs/jpegrecoverymap/jpegr.cpp index fbd511a600..bd8874ed5d 100644 --- a/libs/jpegrecoverymap/jpegr.cpp +++ b/libs/jpegrecoverymap/jpegr.cpp @@ -43,7 +43,7 @@ using namespace std; using namespace photos_editing_formats::image_io; -namespace android::recoverymap { +namespace android::jpegrecoverymap { #define USE_SRGB_INVOETF_LUT 1 #define USE_HLG_OETF_LUT 1 @@ -86,14 +86,15 @@ int GetCPUCoreCount() { return cpuCoreCount; } -static const map jrGamut_to_skGamut { +static const map jrGamut_to_skGamut { {JPEGR_COLORGAMUT_BT709, SkNamedGamut::kSRGB}, {JPEGR_COLORGAMUT_P3, SkNamedGamut::kDisplayP3}, {JPEGR_COLORGAMUT_BT2100, SkNamedGamut::kRec2020}, }; static const map< - recoverymap::jpegr_transfer_function, skcms_TransferFunction> jrTransFunc_to_skTransFunc { + jpegrecoverymap::jpegr_transfer_function, + skcms_TransferFunction> jrTransFunc_to_skTransFunc { {JPEGR_TF_SRGB, SkNamedTransferFn::kSRGB}, {JPEGR_TF_LINEAR, SkNamedTransferFn::kLinear}, {JPEGR_TF_HLG, SkNamedTransferFn::kHLG}, @@ -954,4 +955,4 @@ status_t JpegR::toneMap(jr_uncompressed_ptr src, return NO_ERROR; } -} // namespace android::recoverymap +} // namespace android::jpegrecoverymap diff --git a/libs/jpegrecoverymap/jpegrutils.cpp b/libs/jpegrecoverymap/jpegrutils.cpp index e5323c7b85..bcca91af35 100644 --- a/libs/jpegrecoverymap/jpegrutils.cpp +++ b/libs/jpegrecoverymap/jpegrutils.cpp @@ -25,7 +25,7 @@ using namespace photos_editing_formats::image_io; using namespace std; -namespace android::recoverymap { +namespace android::jpegrecoverymap { /* * Helper function used for generating XMP metadata. @@ -247,4 +247,4 @@ string generateXmp(int secondary_image_length, jpegr_metadata& metadata) { return ss.str(); } -} // namespace android::recoverymap +} // namespace android::jpegrecoverymap diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp index 9c89c8afd4..7812e18b23 100644 --- a/libs/jpegrecoverymap/recoverymapmath.cpp +++ b/libs/jpegrecoverymap/recoverymapmath.cpp @@ -18,7 +18,7 @@ #include #include -namespace android::recoverymap { +namespace android::jpegrecoverymap { static const std::vector kPqOETF = [] { std::vector result; @@ -631,4 +631,4 @@ uint32_t colorToRgba1010102(Color e_gamma) { | (0x3 << 30); // Set alpha to 1.0 } -} // namespace android::recoverymap +} // namespace android::jpegrecoverymap diff --git a/libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp b/libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp index 278bf3b847..2f32a5685b 100644 --- a/libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp +++ b/libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp @@ -20,7 +20,7 @@ #include -namespace android::recoverymap { +namespace android::jpegrecoverymap { #define YUV_IMAGE "/sdcard/Documents/minnie-320x240-yuv.jpg" #define YUV_IMAGE_SIZE 20193 @@ -99,4 +99,4 @@ TEST_F(JpegDecoderHelperTest, decodeGreyImage) { ASSERT_GT(decoder.getDecompressedImageSize(), static_cast(0)); } -} \ No newline at end of file +} // namespace android::jpegrecoverymap \ No newline at end of file diff --git a/libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp b/libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp index 532688d06f..095ac2fbf6 100644 --- a/libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp +++ b/libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp @@ -20,7 +20,7 @@ #include -namespace android::recoverymap { +namespace android::jpegrecoverymap { #define VALID_IMAGE "/sdcard/Documents/minnie-320x240.yu12" #define VALID_IMAGE_WIDTH 320 @@ -121,5 +121,5 @@ TEST_F(JpegEncoderHelperTest, singleChannelImage) { ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); } -} +} // namespace android::jpegrecoverymap diff --git a/libs/jpegrecoverymap/tests/jpegr_test.cpp b/libs/jpegrecoverymap/tests/jpegr_test.cpp index 9a0bd3b37c..c0347e39c7 100644 --- a/libs/jpegrecoverymap/tests/jpegr_test.cpp +++ b/libs/jpegrecoverymap/tests/jpegr_test.cpp @@ -34,7 +34,7 @@ #define SAVE_DECODING_RESULT true #define SAVE_INPUT_RGBA true -namespace android::recoverymap { +namespace android::jpegrecoverymap { struct Timer { struct timeval StartingTime; diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp index 80a9596e3c..6c61ff13d7 100644 --- a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp +++ b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp @@ -19,7 +19,7 @@ #include #include -namespace android::recoverymap { +namespace android::jpegrecoverymap { class RecoveryMapMathTest : public testing::Test { public: @@ -1066,4 +1066,4 @@ TEST_F(RecoveryMapMathTest, ApplyMap) { RgbWhite() / 2.0f); } -} // namespace android::recoverymap +} // namespace android::jpegrecoverymap -- GitLab From 8244a8b367e137256cd429349972d5cbb57eaca6 Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Fri, 4 Nov 2022 20:36:52 +0000 Subject: [PATCH 0860/1310] libbinder_ndk: ScopedAStatus ostream<< For developer convenience. Bug: N/A Test: binderVendorDoubleLoadTest Change-Id: I3ca6c67a30682d53dc4767695f19436e5ef992c3 (cherry picked from commit 9afdc0f1449f89f6c50c39f0004c33aa2456df3c) Merged-In: I3ca6c67a30682d53dc4767695f19436e5ef992c3 --- libs/binder/ndk/include_cpp/android/binder_auto_utils.h | 9 +++++++-- libs/binder/ndk/tests/binderVendorDoubleLoadTest.cpp | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/libs/binder/ndk/include_cpp/android/binder_auto_utils.h b/libs/binder/ndk/include_cpp/android/binder_auto_utils.h index 7ea9be797b..885048a3d3 100644 --- a/libs/binder/ndk/include_cpp/android/binder_auto_utils.h +++ b/libs/binder/ndk/include_cpp/android/binder_auto_utils.h @@ -30,11 +30,11 @@ #include #include #include - #include - #include + #include +#include #include namespace ndk { @@ -315,6 +315,11 @@ class ScopedAStatus : public impl::ScopedAResourceRepeatString("foo", &outString); EXPECT_EQ(STATUS_OK, AStatus_getExceptionCode(status.get())) - << serviceName << " " << status.getDescription(); + << serviceName << " " << status; EXPECT_EQ("foo", outString) << serviceName; } } -- GitLab From d432bb5f03c20b3439cdfce963038667fc9c2a26 Mon Sep 17 00:00:00 2001 From: Kriti Dang Date: Thu, 9 Feb 2023 18:21:04 +0100 Subject: [PATCH 0861/1310] Update setHdrConversionStrategy to return the preferredHdrOutput type Bug: 268336382 Test: atest libsurfaceflinger_unittest Test: atest libgui_test Change-Id: I35108601a1b675f62fc02894ffee4f4e46bb892f --- libs/gui/SurfaceComposerClient.cpp | 8 +++--- .../aidl/android/gui/ISurfaceComposer.aidl | 4 ++- libs/gui/fuzzer/libgui_fuzzer_utils.h | 4 +-- libs/gui/include/gui/SurfaceComposerClient.h | 6 +++-- libs/gui/tests/Surface_test.cpp | 3 ++- .../CompositionEngine/tests/MockHWComposer.h | 5 ++-- .../DisplayHardware/AidlComposerHal.cpp | 6 +++-- .../DisplayHardware/AidlComposerHal.h | 2 +- .../DisplayHardware/ComposerHal.h | 2 +- .../DisplayHardware/HWComposer.cpp | 8 ++++-- .../DisplayHardware/HWComposer.h | 7 +++-- .../DisplayHardware/HidlComposerHal.cpp | 2 +- .../DisplayHardware/HidlComposerHal.h | 4 +-- services/surfaceflinger/SurfaceFlinger.cpp | 26 ++++++++++++++----- services/surfaceflinger/SurfaceFlinger.h | 7 ++--- .../mock/DisplayHardware/MockComposer.h | 5 ++-- 16 files changed, 66 insertions(+), 33 deletions(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index f51c402f29..68f008357c 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2629,9 +2629,11 @@ status_t SurfaceComposerClient::getHdrConversionCapabilities( } status_t SurfaceComposerClient::setHdrConversionStrategy( - gui::HdrConversionStrategy hdrConversionStrategy) { - binder::Status status = ComposerServiceAIDL::getComposerService()->setHdrConversionStrategy( - hdrConversionStrategy); + gui::HdrConversionStrategy hdrConversionStrategy, ui::Hdr* outPreferredHdrOutputType) { + int hdrType; + binder::Status status = ComposerServiceAIDL::getComposerService() + ->setHdrConversionStrategy(hdrConversionStrategy, &hdrType); + *outPreferredHdrOutputType = static_cast(hdrType); return statusTFromBinderStatus(status); } diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 981214212b..aa58e2e580 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -191,10 +191,12 @@ interface ISurfaceComposer { /** * Sets the HDR conversion strategy of the device. + * Returns the preferred HDR output type of the device, in case when HdrConversionStrategy has + * autoAllowedHdrTypes set. Returns Hdr::INVALID in other cases. * * Requires the ACCESS_SURFACE_FLINGER permission. */ - void setHdrConversionStrategy(in HdrConversionStrategy hdrConversionStrategy); + int setHdrConversionStrategy(in HdrConversionStrategy hdrConversionStrategy); /** * Gets whether HDR output conversion operations are supported on the device. diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h index f01c2a9e8e..8c003d8ad4 100644 --- a/libs/gui/fuzzer/libgui_fuzzer_utils.h +++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h @@ -93,8 +93,8 @@ public: MOCK_METHOD(binder::Status, getBootDisplayModeSupport, (bool*), (override)); MOCK_METHOD(binder::Status, getHdrConversionCapabilities, (std::vector*), (override)); - MOCK_METHOD(binder::Status, setHdrConversionStrategy, (const gui::HdrConversionStrategy&), - (override)); + MOCK_METHOD(binder::Status, setHdrConversionStrategy, + (const gui::HdrConversionStrategy&, int32_t*), (override)); MOCK_METHOD(binder::Status, getHdrOutputConversionSupport, (bool*), (override)); MOCK_METHOD(binder::Status, setAutoLowLatencyMode, (const sp&, bool), (override)); MOCK_METHOD(binder::Status, setGameContentType, (const sp&, bool), (override)); diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 63c0505a7b..763c240e31 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -192,8 +192,10 @@ public: // Gets the HDR conversion capabilities of the device static status_t getHdrConversionCapabilities(std::vector*); - // Sets the HDR conversion strategy for the device - static status_t setHdrConversionStrategy(gui::HdrConversionStrategy hdrConversionStrategy); + // Sets the HDR conversion strategy for the device. in case when HdrConversionStrategy has + // autoAllowedHdrTypes set. Returns Hdr::INVALID in other cases. + static status_t setHdrConversionStrategy(gui::HdrConversionStrategy hdrConversionStrategy, + ui::Hdr* outPreferredHdrOutputType); // Returns whether HDR conversion is supported by the device. static status_t getHdrOutputConversionSupport(bool* isSupported); diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index be1ef8e069..e00cdff739 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -827,7 +827,8 @@ public: } binder::Status setHdrConversionStrategy( - const gui::HdrConversionStrategy& /*hdrConversionStrategy*/) override { + const gui::HdrConversionStrategy& /*hdrConversionStrategy*/, + int32_t* /*outPreferredHdrOutputType*/) override { return binder::Status::ok(); } diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h index 933f6168c9..8555fd6d87 100644 --- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h +++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h @@ -115,8 +115,9 @@ public: MOCK_CONST_METHOD0( getHdrConversionCapabilities, std::vector()); - MOCK_METHOD1(setHdrConversionStrategy, - status_t(aidl::android::hardware::graphics::common::HdrConversionStrategy)); + MOCK_METHOD2(setHdrConversionStrategy, + status_t(aidl::android::hardware::graphics::common::HdrConversionStrategy, + aidl::android::hardware::graphics::common::Hdr*)); MOCK_METHOD2(setAutoLowLatencyMode, status_t(PhysicalDisplayId, bool)); MOCK_METHOD(status_t, getSupportedContentTypes, (PhysicalDisplayId, std::vector*), (const, override)); diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp index ba9aed8786..4194a7e4f6 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp @@ -1417,8 +1417,10 @@ Error AidlComposer::getHdrConversionCapabilities( return Error::NONE; } -Error AidlComposer::setHdrConversionStrategy(AidlHdrConversionStrategy hdrConversionStrategy) { - const auto status = mAidlComposerClient->setHdrConversionStrategy(hdrConversionStrategy); +Error AidlComposer::setHdrConversionStrategy(AidlHdrConversionStrategy hdrConversionStrategy, + Hdr* outPreferredHdrOutputType) { + const auto status = mAidlComposerClient->setHdrConversionStrategy(hdrConversionStrategy, + outPreferredHdrOutputType); if (!status.isOk()) { ALOGE("setHdrConversionStrategy failed %s", status.getDescription().c_str()); return static_cast(status.getServiceSpecificError()); diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h index a5ddf7457f..d163ff2d4a 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h @@ -238,7 +238,7 @@ public: void onHotplugConnect(Display) override; void onHotplugDisconnect(Display) override; Error getHdrConversionCapabilities(std::vector*) override; - Error setHdrConversionStrategy(HdrConversionStrategy) override; + Error setHdrConversionStrategy(HdrConversionStrategy, Hdr*) override; private: // Many public functions above simply write a command into the command diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h index 82b677e7d4..9b9b7fdbff 100644 --- a/services/surfaceflinger/DisplayHardware/ComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h @@ -293,7 +293,7 @@ public: virtual Error getHdrConversionCapabilities( std::vector<::aidl::android::hardware::graphics::common::HdrConversionCapability>*) = 0; virtual Error setHdrConversionStrategy( - ::aidl::android::hardware::graphics::common::HdrConversionStrategy) = 0; + ::aidl::android::hardware::graphics::common::HdrConversionStrategy, Hdr*) = 0; }; } // namespace Hwc2 diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp index 6d940797e2..470bf76792 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -796,10 +796,14 @@ std::vector HWComposer::getHdrConversionCapabilities() return mHdrConversionCapabilities; } -status_t HWComposer::setHdrConversionStrategy(HdrConversionStrategy hdrConversionStrategy) { - const auto error = mComposer->setHdrConversionStrategy(hdrConversionStrategy); +status_t HWComposer::setHdrConversionStrategy( + HdrConversionStrategy hdrConversionStrategy, + aidl::android::hardware::graphics::common::Hdr* outPreferredHdrOutputType) { + const auto error = + mComposer->setHdrConversionStrategy(hdrConversionStrategy, outPreferredHdrOutputType); if (error != hal::Error::NONE) { ALOGE("Error in setting HDR conversion strategy %s", to_string(error).c_str()); + return INVALID_OPERATION; } return NO_ERROR; } diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h index acebfb2362..95568eba4c 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.h +++ b/services/surfaceflinger/DisplayHardware/HWComposer.h @@ -44,6 +44,7 @@ #include "Hal.h" #include +#include #include #include #include @@ -294,7 +295,8 @@ public: virtual std::vector getHdrConversionCapabilities() const = 0; virtual status_t setHdrConversionStrategy( - aidl::android::hardware::graphics::common::HdrConversionStrategy) = 0; + aidl::android::hardware::graphics::common::HdrConversionStrategy, + aidl::android::hardware::graphics::common::Hdr*) = 0; }; static inline bool operator==(const android::HWComposer::DeviceRequestedChanges& lhs, @@ -449,7 +451,8 @@ public: std::vector getHdrConversionCapabilities() const override; status_t setHdrConversionStrategy( - aidl::android::hardware::graphics::common::HdrConversionStrategy) override; + aidl::android::hardware::graphics::common::HdrConversionStrategy, + aidl::android::hardware::graphics::common::Hdr*) override; // for debugging ---------------------------------------------------------- void dump(std::string& out) const override; diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp index 6fdb2d79f3..9bc62b6b04 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp @@ -1354,7 +1354,7 @@ Error HidlComposer::getHdrConversionCapabilities(std::vector*) override; - Error setHdrConversionStrategy( - aidl::android::hardware::graphics::common::HdrConversionStrategy) override; + Error setHdrConversionStrategy(aidl::android::hardware::graphics::common::HdrConversionStrategy, + Hdr*) override; private: class CommandWriter : public CommandWriterBase { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index d818b8e359..a8afe6c807 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1580,7 +1580,8 @@ status_t SurfaceFlinger::getHdrConversionCapabilities( } status_t SurfaceFlinger::setHdrConversionStrategy( - const gui::HdrConversionStrategy& hdrConversionStrategy) { + const gui::HdrConversionStrategy& hdrConversionStrategy, + int32_t* outPreferredHdrOutputType) { bool hdrOutputConversionSupport; getHdrOutputConversionSupport(&hdrOutputConversionSupport); if (hdrOutputConversionSupport == false) { @@ -1592,11 +1593,16 @@ status_t SurfaceFlinger::setHdrConversionStrategy( aidl::android::hardware::graphics::common::HdrConversionStrategy; using GuiHdrConversionStrategyTag = gui::HdrConversionStrategy::Tag; AidlHdrConversionStrategy aidlConversionStrategy; + status_t status; + aidl::android::hardware::graphics::common::Hdr aidlPreferredHdrOutputType; switch (hdrConversionStrategy.getTag()) { case GuiHdrConversionStrategyTag::passthrough: { aidlConversionStrategy.set( hdrConversionStrategy.get()); - return getHwComposer().setHdrConversionStrategy(aidlConversionStrategy); + status = getHwComposer().setHdrConversionStrategy(aidlConversionStrategy, + &aidlPreferredHdrOutputType); + *outPreferredHdrOutputType = static_cast(aidlPreferredHdrOutputType); + return status; } case GuiHdrConversionStrategyTag::autoAllowedHdrTypes: { auto autoHdrTypes = @@ -1609,7 +1615,10 @@ status_t SurfaceFlinger::setHdrConversionStrategy( } aidlConversionStrategy.set( aidlAutoHdrTypes); - return getHwComposer().setHdrConversionStrategy(aidlConversionStrategy); + status = getHwComposer().setHdrConversionStrategy(aidlConversionStrategy, + &aidlPreferredHdrOutputType); + *outPreferredHdrOutputType = static_cast(aidlPreferredHdrOutputType); + return status; } case GuiHdrConversionStrategyTag::forceHdrConversion: { auto forceHdrConversion = @@ -1618,7 +1627,10 @@ status_t SurfaceFlinger::setHdrConversionStrategy( aidlConversionStrategy.set( static_cast( forceHdrConversion)); - return getHwComposer().setHdrConversionStrategy(aidlConversionStrategy); + status = getHwComposer().setHdrConversionStrategy(aidlConversionStrategy, + &aidlPreferredHdrOutputType); + *outPreferredHdrOutputType = static_cast(aidlPreferredHdrOutputType); + return status; } } }); @@ -8148,10 +8160,12 @@ binder::Status SurfaceComposerAIDL::getHdrConversionCapabilities( } binder::Status SurfaceComposerAIDL::setHdrConversionStrategy( - const gui::HdrConversionStrategy& hdrConversionStrategy) { + const gui::HdrConversionStrategy& hdrConversionStrategy, + int32_t* outPreferredHdrOutputType) { status_t status = checkAccessPermission(); if (status == OK) { - status = mFlinger->setHdrConversionStrategy(hdrConversionStrategy); + status = mFlinger->setHdrConversionStrategy(hdrConversionStrategy, + outPreferredHdrOutputType); } return binderStatusFromStatusT(status); } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 4171d39f94..bed1241197 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -557,7 +557,8 @@ private: status_t clearBootDisplayMode(const sp& displayToken); status_t getHdrConversionCapabilities( std::vector* hdrConversionCapaabilities) const; - status_t setHdrConversionStrategy(const gui::HdrConversionStrategy& hdrConversionStrategy); + status_t setHdrConversionStrategy(const gui::HdrConversionStrategy& hdrConversionStrategy, + int32_t*); status_t getHdrOutputConversionSupport(bool* outSupport) const; void setAutoLowLatencyMode(const sp& displayToken, bool on); void setGameContentType(const sp& displayToken, bool on); @@ -1462,8 +1463,8 @@ public: binder::Status getOverlaySupport(gui::OverlayProperties* outProperties) override; binder::Status getHdrConversionCapabilities( std::vector*) override; - binder::Status setHdrConversionStrategy( - const gui::HdrConversionStrategy& hdrConversionStrategy) override; + binder::Status setHdrConversionStrategy(const gui::HdrConversionStrategy& hdrConversionStrategy, + int32_t*) override; binder::Status getHdrOutputConversionSupport(bool* outSupport) override; binder::Status setAutoLowLatencyMode(const sp& display, bool on) override; binder::Status setGameContentType(const sp& display, bool on) override; diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h index 5e29fe7539..f28b8d8a7e 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h @@ -148,8 +148,9 @@ public: MOCK_METHOD1(getHdrConversionCapabilities, Error(std::vector< aidl::android::hardware::graphics::common::HdrConversionCapability>*)); - MOCK_METHOD1(setHdrConversionStrategy, - Error(aidl::android::hardware::graphics::common::HdrConversionStrategy)); + MOCK_METHOD2(setHdrConversionStrategy, + Error(aidl::android::hardware::graphics::common::HdrConversionStrategy, + aidl::android::hardware::graphics::common::Hdr*)); MOCK_METHOD2(getSupportedContentTypes, V2_4::Error(Display, std::vector*)); MOCK_METHOD2(setContentType, V2_4::Error(Display, IComposerClient::ContentType)); -- GitLab From fd7f9c4b65505e44784cf4f2cf970086b6089582 Mon Sep 17 00:00:00 2001 From: Kevin Lubick Date: Tue, 14 Feb 2023 19:35:34 +0000 Subject: [PATCH 0862/1310] Add missing Skia includes in jpegr code We are cleaning up Skia includes in https://skia-review.googlesource.com/c/skia/+/642658 and these changes are needed to land that. Change-Id: I48d5eecddc7ea9254d794e358fc78e9319b25a6f --- libs/jpegrecoverymap/jpegr.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/jpegrecoverymap/jpegr.cpp b/libs/jpegrecoverymap/jpegr.cpp index bd8874ed5d..08be4be4e8 100644 --- a/libs/jpegrecoverymap/jpegr.cpp +++ b/libs/jpegrecoverymap/jpegr.cpp @@ -27,7 +27,9 @@ #include #include #include "SkColorSpace.h" +#include "SkData.h" #include "SkICC.h" +#include "SkRefCnt.h" #include #include -- GitLab From 0d4b41c0a7ec7a5c246a08a3313e31b9940958da Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Tue, 14 Feb 2023 23:28:40 +0000 Subject: [PATCH 0863/1310] SF: minor cosmetic fix Change-Id: I8b8b26015afd92290bc7d80a54d3caa3cf1440d9 Test: presubmit --- services/surfaceflinger/SurfaceFlinger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 927a9b7675..05c04ee6a9 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3143,7 +3143,7 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( const auto enableFrameRateOverride = sysprop::enable_frame_rate_override(true) ? Config::FrameRateOverride::Enabled : Config::FrameRateOverride::Disabled; - scheduler::RefreshRateSelector::Config config = + Config config = {.enableFrameRateOverride = enableFrameRateOverride, .frameRateMultipleThreshold = base::GetIntProperty("debug.sf.frame_rate_multiple_threshold", 0), -- GitLab From 5cd1a28647c3310833b23efc4125be7d7bc423d4 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 15 Feb 2023 03:49:36 +0000 Subject: [PATCH 0864/1310] Add more view and widget tests to input presubmit These tests deal with injection and use other input features like touch mode handling. Add them to input presubmit to find breakages ahead of time. Bug: 266687971 Bug: 266382436 Bug: 266532396 Change-Id: I8693405c046c391211eeb3dc93bb15c6fa3a9289 Test: none --- services/inputflinger/TEST_MAPPING | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING index 2a3dba7415..495334e99a 100644 --- a/services/inputflinger/TEST_MAPPING +++ b/services/inputflinger/TEST_MAPPING @@ -46,8 +46,11 @@ "include-filter": "android.view.cts.MotionEventTest", "include-filter": "android.view.cts.PointerCaptureTest", "include-filter": "android.view.cts.TooltipTest", + "include-filter": "android.view.cts.TouchDelegateTest", "include-filter": "android.view.cts.VelocityTrackerTest", - "include-filter": "android.view.cts.VerifyInputEventTest" + "include-filter": "android.view.cts.VerifyInputEventTest", + "include-filter": "android.view.cts.ViewTest", + "include-filter": "android.view.cts.ViewUnbufferedTest" } ] }, @@ -55,7 +58,8 @@ "name": "CtsWidgetTestCases", "options": [ { - "include-filter": "android.widget.cts.NumberPickerTest" + "include-filter": "android.widget.cts.NumberPickerTest", + "include-filter": "android.widget.cts.SeekBarTest" } ] }, @@ -138,8 +142,11 @@ "include-filter": "android.view.cts.MotionEventTest", "include-filter": "android.view.cts.PointerCaptureTest", "include-filter": "android.view.cts.TooltipTest", + "include-filter": "android.view.cts.TouchDelegateTest", "include-filter": "android.view.cts.VelocityTrackerTest", - "include-filter": "android.view.cts.VerifyInputEventTest" + "include-filter": "android.view.cts.VerifyInputEventTest", + "include-filter": "android.view.cts.ViewTest", + "include-filter": "android.view.cts.ViewUnbufferedTest" } ] }, @@ -147,7 +154,8 @@ "name": "CtsWidgetTestCases", "options": [ { - "include-filter": "android.widget.cts.NumberPickerTest" + "include-filter": "android.widget.cts.NumberPickerTest", + "include-filter": "android.widget.cts.SeekBarTest" } ] }, -- GitLab From 3f89a3ee4780a6b4674b255848c04bbea9f07a16 Mon Sep 17 00:00:00 2001 From: Nick Deakin Date: Tue, 14 Feb 2023 20:35:42 -0500 Subject: [PATCH 0865/1310] libjpegrecoverymap: XMP updates. Actually write out MinContentBoost in the XMP. Also move XMP for RecoveryMap:* elements into the secondary image Item, to match updates to the ghdr spec. Update XMP test for min content boost. Bug: 264715926 Test: tests pass Change-Id: I8e32755ef3852511ee2e89949fcb7a3cd61d0507 --- .../include/jpegrecoverymap/jpegrutils.h | 12 ++++--- libs/jpegrecoverymap/jpegrutils.cpp | 32 +++++++++++++++++-- libs/jpegrecoverymap/tests/jpegr_test.cpp | 4 ++- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h index 3a0f67de19..581806c54e 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h @@ -56,6 +56,7 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta * below is an example of the XMP metadata that this function generates where * secondary_image_length = 1000 * max_content_boost = 8.0 + * min_content_boost = 0.5 * * * * + * Item:Semantic="Primary" + * Item:Mime="image/jpeg"/> * * * + * Item:Length="1000" + * RecoveryMap:Version="1" + * RecoveryMap:MaxContentBoost="8.0" + * RecoveryMap:MinContentBoost="0.5"/> * * * diff --git a/libs/jpegrecoverymap/jpegrutils.cpp b/libs/jpegrecoverymap/jpegrutils.cpp index bcca91af35..49526c800f 100644 --- a/libs/jpegrecoverymap/jpegrutils.cpp +++ b/libs/jpegrecoverymap/jpegrutils.cpp @@ -95,6 +95,8 @@ public: if (context.BuildTokenValue(&val)) { if (!val.compare(maxContentBoostAttrName)) { lastAttributeName = maxContentBoostAttrName; + } else if (!val.compare(minContentBoostAttrName)) { + lastAttributeName = minContentBoostAttrName; } else { lastAttributeName = ""; } @@ -109,6 +111,8 @@ public: if (context.BuildTokenValue(&val, true)) { if (!lastAttributeName.compare(maxContentBoostAttrName)) { maxContentBoostStr = val; + } else if (!lastAttributeName.compare(minContentBoostAttrName)) { + minContentBoostStr = val; } } } @@ -130,10 +134,27 @@ public: } } + bool getMinContentBoost(float* min_content_boost) { + if (gContainerItemState == Done) { + stringstream ss(minContentBoostStr); + float val; + if (ss >> val) { + *min_content_boost = val; + return true; + } else { + return false; + } + } else { + return false; + } + } + private: static const string gContainerItemName; static const string maxContentBoostAttrName; string maxContentBoostStr; + static const string minContentBoostAttrName; + string minContentBoostStr; string lastAttributeName; ParseState gContainerItemState; }; @@ -169,10 +190,12 @@ const string kRecoveryMapPrefix = "RecoveryMap"; // RecoveryMap XMP constants - element and attribute names const string kMapMaxContentBoost = Name(kRecoveryMapPrefix, "MaxContentBoost"); +const string kMapMinContentBoost = Name(kRecoveryMapPrefix, "MinContentBoost"); const string kMapVersion = Name(kRecoveryMapPrefix, "Version"); // RecoveryMap XMP constants - names for XMP handlers const string XMPXmlHandler::maxContentBoostAttrName = kMapMaxContentBoost; +const string XMPXmlHandler::minContentBoostAttrName = kMapMinContentBoost; bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) { string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; @@ -213,6 +236,10 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta return false; } + if (!handler.getMinContentBoost(&metadata->minContentBoost)) { + return false; + } + return true; } @@ -235,13 +262,14 @@ string generateXmp(int secondary_image_length, jpegr_metadata& metadata) { size_t item_depth = writer.StartWritingElements(kLiItem); writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary); writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg); - writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); - writer.WriteAttributeNameAndValue(kMapMaxContentBoost, metadata.maxContentBoost); writer.FinishWritingElementsToDepth(item_depth); writer.StartWritingElements(kLiItem); writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticRecoveryMap); writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg); writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length); + writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); + writer.WriteAttributeNameAndValue(kMapMaxContentBoost, metadata.maxContentBoost); + writer.WriteAttributeNameAndValue(kMapMinContentBoost, metadata.minContentBoost); writer.FinishWriting(); return ss.str(); diff --git a/libs/jpegrecoverymap/tests/jpegr_test.cpp b/libs/jpegrecoverymap/tests/jpegr_test.cpp index c0347e39c7..7a3133d10c 100644 --- a/libs/jpegrecoverymap/tests/jpegr_test.cpp +++ b/libs/jpegrecoverymap/tests/jpegr_test.cpp @@ -176,6 +176,7 @@ TEST_F(JpegRTest, build) { TEST_F(JpegRTest, writeXmpThenRead) { jpegr_metadata metadata_expected; metadata_expected.maxContentBoost = 1.25; + metadata_expected.minContentBoost = 0.75; int length_expected = 1000; const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator @@ -192,6 +193,7 @@ TEST_F(JpegRTest, writeXmpThenRead) { jpegr_metadata metadata_read; EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read)); ASSERT_EQ(metadata_expected.maxContentBoost, metadata_read.maxContentBoost); + ASSERT_EQ(metadata_expected.minContentBoost, metadata_read.minContentBoost); } /* Test Encode API-0 and decode */ @@ -496,4 +498,4 @@ TEST_F(JpegRTest, ProfileRecoveryMapFuncs) { benchmark.BenchmarkApplyRecoveryMap(&mRawYuv420Image, &map, &metadata, &dest); } -} // namespace android::recoverymap \ No newline at end of file +} // namespace android::recoverymap -- GitLab From a22152938dae023cb4305d495ff9eecbe76bc343 Mon Sep 17 00:00:00 2001 From: Nick Deakin Date: Tue, 14 Feb 2023 21:40:06 -0500 Subject: [PATCH 0866/1310] libjpegrecoverymap: Update min/max boost determination. Fix min/max content boost to 1 / 10 for HLG and 1 / 100 for PQ. Determining these values based on both input images can be problematic due to jpeg encoding artifacts. Additionally, min of 1 seems justified since attentuation during map application has not come up in a meaningful way in practice with HLG and PQ input. This also has the nice benefit of improving encode performance significantly; generate map numbers for 12MP on Pixel 7 Pro are now around 45ms, down from ~150ms! Bug: 264715926 Test: tests pass Change-Id: Ia016b68de5900cb263d383b1ae64f7c39cc499cd --- libs/jpegrecoverymap/jpegr.cpp | 64 +++------------------------------- 1 file changed, 4 insertions(+), 60 deletions(-) diff --git a/libs/jpegrecoverymap/jpegr.cpp b/libs/jpegrecoverymap/jpegr.cpp index bd8874ed5d..bc353936a0 100644 --- a/libs/jpegrecoverymap/jpegr.cpp +++ b/libs/jpegrecoverymap/jpegr.cpp @@ -554,6 +554,9 @@ status_t JpegR::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ima return ERROR_JPEGR_INVALID_TRANS_FUNC; } + metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits; + metadata->minContentBoost = 1.0f; + ColorTransformFn hdrGamutConversionFn = getHdrConversionFn( uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut); @@ -574,50 +577,10 @@ status_t JpegR::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ima } std::mutex mutex; - float max_gain = 0.0f; - float min_gain = 1.0f; const int threads = std::clamp(GetCPUCoreCount(), 1, 4); size_t rowStep = threads == 1 ? image_height : kJobSzInRows; JobQueue jobQueue; - std::function computeMetadata = [uncompressed_p010_image, uncompressed_yuv_420_image, - hdrInvOetf, hdrGamutConversionFn, luminanceFn, - hdr_white_nits, threads, &mutex, &max_gain, &min_gain, - &jobQueue]() -> void { - size_t rowStart, rowEnd; - float max_gain_th = 0.0f; - float min_gain_th = 1.0f; - - while (jobQueue.dequeueJob(rowStart, rowEnd)) { - for (size_t y = rowStart; y < rowEnd; ++y) { - for (size_t x = 0; x < uncompressed_p010_image->width; ++x) { - Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y); - Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma); - Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma); - hdr_rgb = hdrGamutConversionFn(hdr_rgb); - float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits; - - Color sdr_yuv_gamma = - getYuv420Pixel(uncompressed_yuv_420_image, x, y); - Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma); -#if USE_SRGB_INVOETF_LUT - Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma); -#else - Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma); -#endif - float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits; - - float gain = hdr_y_nits / sdr_y_nits; - max_gain_th = std::max(max_gain_th, gain); - min_gain_th = std::min(min_gain_th, gain); - } - } - } - std::unique_lock lock{mutex}; - max_gain = std::max(max_gain, max_gain_th); - min_gain = std::min(min_gain, min_gain_th); - }; - std::function generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image, metadata, dest, hdrInvOetf, hdrGamutConversionFn, luminanceFn, hdr_white_nits, &jobQueue]() -> void { @@ -651,27 +614,8 @@ status_t JpegR::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ima } }; - std::vector workers; - for (int th = 0; th < threads - 1; th++) { - workers.push_back(std::thread(computeMetadata)); - } - - // compute metadata - for (size_t rowStart = 0; rowStart < image_height;) { - size_t rowEnd = std::min(rowStart + rowStep, image_height); - jobQueue.enqueueJob(rowStart, rowEnd); - rowStart = rowEnd; - } - jobQueue.markQueueForEnd(); - computeMetadata(); - std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); }); - workers.clear(); - - metadata->maxContentBoost = max_gain; - metadata->minContentBoost = min_gain; - // generate map - jobQueue.reset(); + std::vector workers; for (int th = 0; th < threads - 1; th++) { workers.push_back(std::thread(generateMap)); } -- GitLab From f34a813b3241efe96cfd1ac228ef29f406845062 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Mon, 13 Feb 2023 20:49:48 -0800 Subject: [PATCH 0867/1310] SF: vsync divisors should use the same reference point Fix an issue where vsync divisors use different reference points to classify whether a vsync is in phase or not. This caused an issue where 30fps and 60fps might picked a difference reference point, causing a 30fps vsync to be out of phase with a 60fps vsync. Test: SF unit tests Bug: 267780202 Change-Id: I95939670f2850e90978a816d319a59f7c6fa7ba5 --- .../Scheduler/VSyncPredictor.cpp | 70 +++++++------------ .../surfaceflinger/Scheduler/VSyncPredictor.h | 26 +++---- .../tests/unittests/VSyncPredictorTest.cpp | 47 ++++++++++++- 3 files changed, 86 insertions(+), 57 deletions(-) diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index f8cb3230af..5a5afd8fa6 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -219,6 +219,17 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { return true; } +auto VSyncPredictor::getVsyncSequenceLocked(nsecs_t timestamp) const -> VsyncSequence { + const auto vsync = nextAnticipatedVSyncTimeFromLocked(timestamp); + if (!mLastVsyncSequence) return {vsync, 0}; + + const auto [slope, _] = getVSyncPredictionModelLocked(); + const auto [lastVsyncTime, lastVsyncSequence] = *mLastVsyncSequence; + const auto vsyncSequence = lastVsyncSequence + + static_cast(std::round((vsync - lastVsyncTime) / static_cast(slope))); + return {vsync, vsyncSequence}; +} + nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const { auto const [slope, intercept] = getVSyncPredictionModelLocked(); @@ -258,12 +269,17 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) co nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const { std::lock_guard lock(mMutex); - // TODO(b/246164114): This implementation is not efficient at all. Refactor. - nsecs_t nextVsync = nextAnticipatedVSyncTimeFromLocked(timePoint); - while (!isVSyncInPhaseLocked(nextVsync, mDivisor)) { - nextVsync = nextAnticipatedVSyncTimeFromLocked(nextVsync + 1); + // update the mLastVsyncSequence for reference point + mLastVsyncSequence = getVsyncSequenceLocked(timePoint); + + const auto mod = mLastVsyncSequence->seq % mDivisor; + if (mod == 0) { + return mLastVsyncSequence->vsyncTime; } - return nextVsync; + + auto const [slope, intercept] = getVSyncPredictionModelLocked(); + const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * (mDivisor - mod); + return nextAnticipatedVSyncTimeFromLocked(approximateNextVsync - slope / 2); } /* @@ -289,51 +305,16 @@ bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) c ATRACE_FORMAT("%s timePoint in: %.2f divisor: %zu", __func__, getTimePointIn(now, timePoint), divisor); - struct VsyncError { - nsecs_t vsyncTimestamp; - float error; - - bool operator<(const VsyncError& other) const { return error < other.error; } - }; - if (divisor <= 1 || timePoint == 0) { return true; } const nsecs_t period = mRateMap[mIdealPeriod].slope; const nsecs_t justBeforeTimePoint = timePoint - period / 2; - const nsecs_t dividedPeriod = mIdealPeriod / divisor; - - // If this is the first time we have asked about this divisor with the - // current vsync period, it is considered in phase and we store the closest - // vsync timestamp - const auto knownTimestampIter = mRateDivisorKnownTimestampMap.find(dividedPeriod); - if (knownTimestampIter == mRateDivisorKnownTimestampMap.end()) { - const auto vsync = nextAnticipatedVSyncTimeFromLocked(justBeforeTimePoint); - mRateDivisorKnownTimestampMap[dividedPeriod] = vsync; - ATRACE_FORMAT_INSTANT("(first) knownVsync in: %.2f", getTimePointIn(now, vsync)); - return true; - } - - // Find the next N vsync timestamp where N is the divisor. - // One of these vsyncs will be in phase. We return the one which is - // the most aligned with the last known in phase vsync - std::vector vsyncs(static_cast(divisor)); - const nsecs_t knownVsync = knownTimestampIter->second; - nsecs_t point = justBeforeTimePoint; - for (size_t i = 0; i < divisor; i++) { - const nsecs_t vsync = nextAnticipatedVSyncTimeFromLocked(point); - const auto numPeriods = static_cast(vsync - knownVsync) / (period * divisor); - const auto error = std::abs(std::round(numPeriods) - numPeriods); - vsyncs[i] = {vsync, error}; - point = vsync + 1; - } - - const auto minVsyncError = std::min_element(vsyncs.begin(), vsyncs.end()); - mRateDivisorKnownTimestampMap[dividedPeriod] = minVsyncError->vsyncTimestamp; - ATRACE_FORMAT_INSTANT("knownVsync in: %.2f", - getTimePointIn(now, minVsyncError->vsyncTimestamp)); - return std::abs(minVsyncError->vsyncTimestamp - timePoint) < period / 2; + const auto vsyncSequence = getVsyncSequenceLocked(justBeforeTimePoint); + ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64, + getTimePointIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq); + return vsyncSequence.seq % divisor == 0; } void VSyncPredictor::setDivisor(unsigned divisor) { @@ -382,6 +363,7 @@ void VSyncPredictor::clearTimestamps() { mTimestamps.clear(); mLastTimestampIndex = 0; } + mLastVsyncSequence.reset(); } bool VSyncPredictor::needsMoreSamples() const { diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index 305cdb0d56..d0e3098c5e 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -78,35 +78,37 @@ private: inline void traceInt64If(const char* name, int64_t value) const; inline void traceInt64(const char* name, int64_t value) const; - bool const mTraceOn; - - size_t const kHistorySize; - size_t const kMinimumSamplesForPrediction; - size_t const kOutlierTolerancePercent; - std::mutex mutable mMutex; size_t next(size_t i) const REQUIRES(mMutex); bool validate(nsecs_t timestamp) const REQUIRES(mMutex); - Model getVSyncPredictionModelLocked() const REQUIRES(mMutex); - nsecs_t nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const REQUIRES(mMutex); - bool isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const REQUIRES(mMutex); + struct VsyncSequence { + nsecs_t vsyncTime; + int64_t seq; + }; + VsyncSequence getVsyncSequenceLocked(nsecs_t timestamp) const REQUIRES(mMutex); + + bool const mTraceOn; + size_t const kHistorySize; + size_t const kMinimumSamplesForPrediction; + size_t const kOutlierTolerancePercent; + std::mutex mutable mMutex; + nsecs_t mIdealPeriod GUARDED_BY(mMutex); std::optional mKnownTimestamp GUARDED_BY(mMutex); // Map between ideal vsync period and the calculated model std::unordered_map mutable mRateMap GUARDED_BY(mMutex); - // Map between the divided vsync period and the last known vsync timestamp - std::unordered_map mutable mRateDivisorKnownTimestampMap GUARDED_BY(mMutex); - size_t mLastTimestampIndex GUARDED_BY(mMutex) = 0; std::vector mTimestamps GUARDED_BY(mMutex); unsigned mDivisor GUARDED_BY(mMutex) = 1; + + mutable std::optional mLastVsyncSequence GUARDED_BY(mMutex); }; } // namespace android::scheduler diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index 3095e8aa9a..48d39cf3f5 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -468,7 +468,7 @@ TEST_F(VSyncPredictorTest, isVSyncInPhase) { const auto maxPeriods = 15; for (int divisor = 1; divisor < maxDivisor; divisor++) { for (int i = 0; i < maxPeriods; i++) { - const bool expectedInPhase = (i % divisor) == 0; + const bool expectedInPhase = ((kMinimumSamplesForPrediction - 1 + i) % divisor) == 0; EXPECT_THAT(expectedInPhase, tracker.isVSyncInPhase(mNow + i * mPeriod - bias, Fps::fromPeriodNsecs(divisor * mPeriod))) @@ -478,6 +478,28 @@ TEST_F(VSyncPredictorTest, isVSyncInPhase) { } } +TEST_F(VSyncPredictorTest, isVSyncInPhaseForDivisors) { + auto last = mNow; + for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); + mNow += mPeriod; + last = mNow; + tracker.addVsyncTimestamp(mNow); + } + + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod)); + + EXPECT_TRUE(tracker.isVSyncInPhase(mNow + 1 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 2))); + EXPECT_FALSE(tracker.isVSyncInPhase(mNow + 2 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 2))); + EXPECT_TRUE(tracker.isVSyncInPhase(mNow + 3 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 2))); + + EXPECT_FALSE(tracker.isVSyncInPhase(mNow + 5 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 4))); + EXPECT_TRUE(tracker.isVSyncInPhase(mNow + 3 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 4))); + EXPECT_FALSE(tracker.isVSyncInPhase(mNow + 4 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 4))); + EXPECT_FALSE(tracker.isVSyncInPhase(mNow + 6 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 4))); + EXPECT_TRUE(tracker.isVSyncInPhase(mNow + 7 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 4))); +} + TEST_F(VSyncPredictorTest, inconsistentVsyncValueIsFlushedEventually) { EXPECT_TRUE(tracker.addVsyncTimestamp(600)); EXPECT_TRUE(tracker.needsMoreSamples()); @@ -552,6 +574,29 @@ TEST_F(VSyncPredictorTest, setDivisorIsRespected) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 7 * mPeriod)); } +TEST_F(VSyncPredictorTest, setDivisorOfDivosorIsInPhase) { + auto last = mNow; + for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); + mNow += mPeriod; + last = mNow; + tracker.addVsyncTimestamp(mNow); + } + + tracker.setDivisor(4); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 3 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 7 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 11 * mPeriod)); + + tracker.setDivisor(2); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 3 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 5 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5 * mPeriod), Eq(mNow + 7 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 9 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 9 * mPeriod), Eq(mNow + 11 * mPeriod)); +} + } // namespace android::scheduler // TODO(b/129481165): remove the #pragma below and fix conversion issues -- GitLab From 80a5a70894964b3b9b148fa192a9eaa8c2771be0 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Sat, 11 Feb 2023 01:21:51 +0000 Subject: [PATCH 0868/1310] SF: More frontend fixes - Crop mirrored layers with the mirror root. Previously we were cropping the layers with the original layer and not the clone. - Explicitly make unreachable nodes invisible. Fixes an issue where we accessed stale snapshots that were no longer reachable. - Update buffer geometry (inverseTransformDisplay) when display state changes. - Blur fixes. Test: presubmit Bug: 238781169 Change-Id: I6f88f2456c3fd15c9d819ec2272aee639badcd19 --- .../FrontEnd/LayerHierarchy.cpp | 44 ++++++++++--------- .../surfaceflinger/FrontEnd/LayerHierarchy.h | 31 ++++++++++--- .../FrontEnd/LayerLifecycleManager.cpp | 4 +- .../FrontEnd/LayerSnapshotBuilder.cpp | 29 +++++++----- .../FrontEnd/LayerSnapshotBuilder.h | 4 +- .../FrontEnd/RequestedLayerState.cpp | 22 +++++++--- .../FrontEnd/RequestedLayerState.h | 1 + 7 files changed, 89 insertions(+), 46 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp index a4fac1cf5c..afe557e440 100644 --- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp @@ -19,6 +19,7 @@ #define LOG_TAG "LayerHierarchy" #include "LayerHierarchy.h" +#include "LayerLog.h" #include "SwapErase.h" namespace android::surfaceflinger::frontend { @@ -259,6 +260,7 @@ void LayerHierarchyBuilder::onLayerAdded(RequestedLayerState* layer) { } void LayerHierarchyBuilder::onLayerDestroyed(RequestedLayerState* layer) { + LLOGV(layer->id, ""); LayerHierarchy* hierarchy = getHierarchyFromId(layer->id, /*crashOnFailure=*/false); if (!hierarchy) { // Layer was never part of the hierarchy if it was created and destroyed in the same @@ -408,29 +410,32 @@ std::string LayerHierarchy::TraversalPath::toString() const { if (id == UNASSIGNED_LAYER_ID) { return "TraversalPath{ROOT}"; } - std::string debugString = "TraversalPath{.id = " + std::to_string(id); + std::stringstream ss; + ss << "TraversalPath{.id = " << id; - if (!mirrorRootIds.empty()) { - debugString += ", .mirrorRootIds="; - for (auto rootId : mirrorRootIds) { - debugString += std::to_string(rootId) + ","; - } + if (mirrorRootId != UNASSIGNED_LAYER_ID) { + ss << ", .mirrorRootId=" << mirrorRootId; } if (!relativeRootIds.empty()) { - debugString += ", .relativeRootIds="; + ss << ", .relativeRootIds="; for (auto rootId : relativeRootIds) { - debugString += std::to_string(rootId) + ","; + ss << rootId << ","; } } if (hasRelZLoop()) { - debugString += ", hasRelZLoop=true invalidRelativeRootId="; - debugString += std::to_string(invalidRelativeRootId) + ","; + ss << "hasRelZLoop=true invalidRelativeRootId=" << invalidRelativeRootId << ","; } + ss << "}"; + return ss.str(); +} - debugString += "}"; - return debugString; +LayerHierarchy::TraversalPath LayerHierarchy::TraversalPath::getMirrorRoot() const { + LOG_ALWAYS_FATAL_IF(!isClone(), "Cannot get mirror root of a non cloned node"); + TraversalPath mirrorRootPath = *this; + mirrorRootPath.id = mirrorRootId; + return mirrorRootPath; } // Helper class to update a passed in TraversalPath when visiting a child. When the object goes out @@ -438,16 +443,13 @@ std::string LayerHierarchy::TraversalPath::toString() const { LayerHierarchy::ScopedAddToTraversalPath::ScopedAddToTraversalPath(TraversalPath& traversalPath, uint32_t layerId, LayerHierarchy::Variant variant) - : mTraversalPath(traversalPath), - mParentId(traversalPath.id), - mParentVariant(traversalPath.variant), - mParentDetached(traversalPath.detached) { + : mTraversalPath(traversalPath), mParentPath(traversalPath) { // Update the traversal id with the child layer id and variant. Parent id and variant are // stored to reset the id upon destruction. traversalPath.id = layerId; traversalPath.variant = variant; if (variant == LayerHierarchy::Variant::Mirror) { - traversalPath.mirrorRootIds.emplace_back(layerId); + traversalPath.mirrorRootId = layerId; } else if (variant == LayerHierarchy::Variant::Relative) { if (std::find(traversalPath.relativeRootIds.begin(), traversalPath.relativeRootIds.end(), layerId) != traversalPath.relativeRootIds.end()) { @@ -462,16 +464,16 @@ LayerHierarchy::ScopedAddToTraversalPath::~ScopedAddToTraversalPath() { // Reset the traversal id to its original parent state using the state that was saved in // the constructor. if (mTraversalPath.variant == LayerHierarchy::Variant::Mirror) { - mTraversalPath.mirrorRootIds.pop_back(); + mTraversalPath.mirrorRootId = mParentPath.mirrorRootId; } else if (mTraversalPath.variant == LayerHierarchy::Variant::Relative) { mTraversalPath.relativeRootIds.pop_back(); } if (mTraversalPath.invalidRelativeRootId == mTraversalPath.id) { mTraversalPath.invalidRelativeRootId = UNASSIGNED_LAYER_ID; } - mTraversalPath.id = mParentId; - mTraversalPath.variant = mParentVariant; - mTraversalPath.detached = mParentDetached; + mTraversalPath.id = mParentPath.id; + mTraversalPath.variant = mParentPath.variant; + mTraversalPath.detached = mParentPath.detached; } } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h index ca8d301879..2ab897b35d 100644 --- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h @@ -50,12 +50,32 @@ public: ftl_last = Mirror, }; // Represents a unique path to a node. + // The layer hierarchy is represented as a graph. Each node can be visited by multiple parents. + // This allows us to represent mirroring in an efficient way. See the example below: + // root + // ├─ A {Traversal path id = 1} + // ├─ B {Traversal path id = 2} + // │ ├─ C {Traversal path id = 3} + // │ ├─ D {Traversal path id = 4} + // │ └─ E {Traversal path id = 5} + // ├─ F (Mirrors B) {Traversal path id = 6} + // └─ G (Mirrors F) {Traversal path id = 7} + // + // C, D and E can be traversed via B or via F then B or via G then F then B. + // Depending on how the node is reached, its properties such as geometry or visibility might be + // different. And we can uniquely identify the node by keeping track of the nodes leading up to + // it. But to be more efficient we only need to track the nodes id and the top mirror root path. + // So C for example, would have the following unique traversal paths: + // - {Traversal path id = 3} + // - {Traversal path id = 3, mirrorRootId = 6} + // - {Traversal path id = 3, mirrorRootId = 7} + struct TraversalPath { uint32_t id; LayerHierarchy::Variant variant; // Mirrored layers can have a different geometry than their parents so we need to track // the mirror roots in the traversal. - ftl::SmallVector mirrorRootIds; + uint32_t mirrorRootId = UNASSIGNED_LAYER_ID; // Relative layers can be visited twice, once by their parent and then once again by // their relative parent. We keep track of the roots here to detect any loops in the // hierarchy. If a relative root already exists in the list while building the @@ -73,10 +93,11 @@ public: // Returns true if the node or its parents are not Detached. bool isAttached() const { return !detached; } // Returns true if the node is a clone. - bool isClone() const { return !mirrorRootIds.empty(); } + bool isClone() const { return mirrorRootId != UNASSIGNED_LAYER_ID; } + TraversalPath getMirrorRoot() const; bool operator==(const TraversalPath& other) const { - return id == other.id && mirrorRootIds == other.mirrorRootIds; + return id == other.id && mirrorRootId == other.mirrorRootId; } std::string toString() const; @@ -93,9 +114,7 @@ public: private: TraversalPath& mTraversalPath; - uint32_t mParentId; - LayerHierarchy::Variant mParentVariant; - bool mParentDetached; + TraversalPath mParentPath; }; LayerHierarchy(RequestedLayerState* layer); diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp index 547a852b9e..66197be44a 100644 --- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp +++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp @@ -22,6 +22,7 @@ #include "LayerLifecycleManager.h" #include "Layer.h" // temporarily needed for LayerHandle #include "LayerHandle.h" +#include "LayerLog.h" #include "SwapErase.h" namespace android::surfaceflinger::frontend { @@ -36,6 +37,7 @@ void LayerLifecycleManager::addLayers(std::vector& dest while (it != mLayers.end()) { RequestedLayerState* layer = it->get(); if (layer->changes.test(RequestedLayerState::Changes::Destroyed)) { - ALOGV("%s destroyed layer %s", __func__, layer->getDebugStringShort().c_str()); + LLOGV(layer->id, "destroyed layer %s", layer->getDebugStringShort().c_str()); std::iter_swap(it, mLayers.end() - 1); mDestroyedLayers.emplace_back(std::move(mLayers.back())); if (it == mLayers.end() - 1) { diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index 3ed24b2740..c9aeb24cf5 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -259,8 +259,8 @@ void updateSurfaceDamage(const RequestedLayerState& requested, bool hasReadyFram } } -void updateVisibility(LayerSnapshot& snapshot) { - snapshot.isVisible = snapshot.getIsVisible(); +void updateVisibility(LayerSnapshot& snapshot, bool visible) { + snapshot.isVisible = visible; // TODO(b/238781169) we are ignoring this compat for now, since we will have // to remove any optimization based on visibility. @@ -273,9 +273,9 @@ void updateVisibility(LayerSnapshot& snapshot) { // We are just using these layers for occlusion detection in // InputDispatcher, and obviously if they aren't visible they can't occlude // anything. - const bool visible = + const bool visibleForInput = (snapshot.inputInfo.token != nullptr) ? snapshot.canReceiveInput() : snapshot.isVisible; - snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE, !visible); + snapshot.inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE, !visibleForInput); } bool needsInputInfo(const LayerSnapshot& snapshot, const RequestedLayerState& requested) { @@ -521,7 +521,7 @@ void LayerSnapshotBuilder::sortSnapshotsByZ(const Args& args) { } if (snapshot->getIsVisible() || snapshot->hasInputInfo()) { - updateVisibility(*snapshot); + updateVisibility(*snapshot, snapshot->getIsVisible()); size_t oldZ = snapshot->globalZ; size_t newZ = globalZ++; snapshot->globalZ = newZ; @@ -539,7 +539,8 @@ void LayerSnapshotBuilder::sortSnapshotsByZ(const Args& args) { mNumInterestingSnapshots = (int)globalZ; while (globalZ < mSnapshots.size()) { mSnapshots[globalZ]->globalZ = globalZ; - updateVisibility(*mSnapshots[globalZ]); + /* mark unreachable snapshots as explicitly invisible */ + updateVisibility(*mSnapshots[globalZ], false); globalZ++; } } @@ -634,11 +635,15 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a const bool forceUpdate = newSnapshot || args.forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Created); + snapshot.outputFilter.layerStack = requested.parentId != UNASSIGNED_LAYER_ID + ? parentSnapshot.outputFilter.layerStack + : requested.layerStack; + uint32_t displayRotationFlags = getDisplayRotationFlags(args.displays, snapshot.outputFilter.layerStack); // always update the buffer regardless of visibility - if (forceUpdate || requested.what & layer_state_t::BUFFER_CHANGES) { + if (forceUpdate || requested.what & layer_state_t::BUFFER_CHANGES || args.displayChanges) { snapshot.acquireFence = (requested.externalTexture && requested.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged)) @@ -727,9 +732,13 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Content)) { snapshot.color.rgb = requested.getColor().rgb; snapshot.isColorspaceAgnostic = requested.colorSpaceAgnostic; - snapshot.backgroundBlurRadius = - args.supportsBlur ? static_cast(requested.backgroundBlurRadius) : 0; + snapshot.backgroundBlurRadius = args.supportsBlur + ? static_cast(parentSnapshot.color.a * (float)requested.backgroundBlurRadius) + : 0; snapshot.blurRegions = requested.blurRegions; + for (auto& region : snapshot.blurRegions) { + region.alpha = region.alpha * snapshot.color.a; + } snapshot.hdrMetadata = requested.hdrMetadata; } @@ -965,7 +974,7 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, // touches from going outside the cloned area. if (path.isClone()) { snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::CLONE; - auto clonedRootSnapshot = getSnapshot(path.mirrorRootIds.back()); + auto clonedRootSnapshot = getSnapshot(path.getMirrorRoot()); if (clonedRootSnapshot) { const Rect rect = displayInfo.transform.transform(Rect{clonedRootSnapshot->transformedBounds}); diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h index f4544fd62f..0902ab8067 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h @@ -115,8 +115,8 @@ private: struct TraversalPathHash { std::size_t operator()(const LayerHierarchy::TraversalPath& key) const { uint32_t hashCode = key.id * 31; - for (auto mirrorRoot : key.mirrorRootIds) { - hashCode += mirrorRoot * 31; + if (key.mirrorRootId != UNASSIGNED_LAYER_ID) { + hashCode += key.mirrorRootId * 31; } return std::hash{}(hashCode); } diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index d63b126452..28340844a3 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -144,7 +144,7 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta const half oldAlpha = color.a; const bool hadBufferOrSideStream = hasValidBuffer() || sidebandStream != nullptr; const layer_state_t& clientState = resolvedComposerState.state; - + const bool hadBlur = hasBlur(); uint64_t clientChanges = what | layer_state_t::diff(clientState); layer_state_t::merge(clientState); what = clientChanges; @@ -174,6 +174,12 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta RequestedLayerState::Changes::VisibleRegion; } } + if (what & (layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBlurRegionsChanged)) { + if (hadBlur != hasBlur()) { + changes |= RequestedLayerState::Changes::Visibility | + RequestedLayerState::Changes::VisibleRegion; + } + } if (clientChanges & layer_state_t::HIERARCHY_CHANGES) changes |= RequestedLayerState::Changes::Hierarchy; if (clientChanges & layer_state_t::CONTENT_CHANGES) @@ -319,11 +325,11 @@ ui::Transform RequestedLayerState::getTransform(uint32_t displayRotationFlags) c } std::string RequestedLayerState::getDebugString() const { - return "[" + std::to_string(id) + "]" + name + ",parent=" + layerIdToString(parentId) + - ",relativeParent=" + layerIdToString(relativeParentId) + - ",isRelativeOf=" + std::to_string(isRelativeOf) + - ",mirrorIds=" + layerIdsToString(mirrorIds) + - ",handleAlive=" + std::to_string(handleAlive) + ",z=" + std::to_string(z); + std::stringstream debug; + debug << "RequestedLayerState{" << name << " parent=" << layerIdToString(parentId) + << " relativeParent=" << layerIdToString(relativeParentId) + << " mirrorId=" << layerIdsToString(mirrorIds) << " handle=" << handleAlive << " z=" << z; + return debug.str(); } std::string RequestedLayerState::getDebugStringShort() const { @@ -442,4 +448,8 @@ bool RequestedLayerState::hasInputInfo() const { windowInfo->inputConfig.test(gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL); } +bool RequestedLayerState::hasBlur() const { + return backgroundBlurRadius > 0 || blurRegions.size() > 0; +} + } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h index 6317b95709..6840b25a5b 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h @@ -71,6 +71,7 @@ struct RequestedLayerState : layer_state_t { aidl::android::hardware::graphics::composer3::Composition getCompositionType() const; bool hasValidRelativeParent() const; bool hasInputInfo() const; + bool hasBlur() const; // Layer serial number. This gives layers an explicit ordering, so we // have a stable sort order when their layer stack and Z-order are -- GitLab From 894a6d48c1c6c5cd12074b0ef2f7b1bde01db5c5 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Tue, 14 Feb 2023 10:55:43 -0800 Subject: [PATCH 0869/1310] SF: pass a render rate to VsyncTracker instead of a divisor passing a divisor to VsyncTracker might result in the wrong frame rate when the vsync period is changing. The Divisor was calculated in VsyncReactor which calculates it based on the new period, and passed to VsyncTracker which might still be learning the new period, hence using the old period. Bug: 267780202 Test: SF unit tests Change-Id: Ibb632d4ae6621a6ac0c0123e78f4c4d75699bd9e --- .../surfaceflinger/Scheduler/Scheduler.cpp | 6 +--- .../Scheduler/VSyncPredictor.cpp | 25 +++++++++---- .../surfaceflinger/Scheduler/VSyncPredictor.h | 4 +-- .../surfaceflinger/Scheduler/VSyncTracker.h | 9 ++--- .../fuzzer/surfaceflinger_scheduler_fuzzer.h | 2 +- .../unittests/VSyncDispatchRealtimeTest.cpp | 4 +-- .../unittests/VSyncDispatchTimerQueueTest.cpp | 2 +- .../tests/unittests/VSyncPredictorTest.cpp | 36 ++++++++++++++++--- .../tests/unittests/VSyncReactorTest.cpp | 2 +- .../tests/unittests/mock/MockVSyncTracker.h | 2 +- 10 files changed, 64 insertions(+), 28 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 17cdff9518..dab01ba48d 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -418,11 +418,7 @@ void Scheduler::setRenderRate(Fps renderFrameRate) { ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(), to_string(mode.modePtr->getFps()).c_str()); - const auto divisor = RefreshRateSelector::getFrameRateDivisor(mode.modePtr->getFps(), mode.fps); - LOG_ALWAYS_FATAL_IF(divisor == 0, "%s <> %s -- not divisors", to_string(mode.fps).c_str(), - to_string(mode.fps).c_str()); - - mVsyncSchedule->getTracker().setDivisor(static_cast(divisor)); + mVsyncSchedule->getTracker().setRenderRate(renderFrameRate); } void Scheduler::resync() { diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 5a5afd8fa6..de7b338a21 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -272,13 +272,26 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const { // update the mLastVsyncSequence for reference point mLastVsyncSequence = getVsyncSequenceLocked(timePoint); - const auto mod = mLastVsyncSequence->seq % mDivisor; - if (mod == 0) { + const auto renderRatePhase = [&]() REQUIRES(mMutex) -> int { + if (!mRenderRate) return 0; + + const auto divisor = + RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), + *mRenderRate); + if (divisor <= 1) return 0; + + const int mod = mLastVsyncSequence->seq % divisor; + if (mod == 0) return 0; + + return divisor - mod; + }(); + + if (renderRatePhase == 0) { return mLastVsyncSequence->vsyncTime; } auto const [slope, intercept] = getVSyncPredictionModelLocked(); - const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * (mDivisor - mod); + const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase; return nextAnticipatedVSyncTimeFromLocked(approximateNextVsync - slope / 2); } @@ -317,10 +330,10 @@ bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) c return vsyncSequence.seq % divisor == 0; } -void VSyncPredictor::setDivisor(unsigned divisor) { - ALOGV("%s: %d", __func__, divisor); +void VSyncPredictor::setRenderRate(Fps fps) { + ALOGV("%s: %s", __func__, to_string(fps).c_str()); std::lock_guard lock(mMutex); - mDivisor = divisor; + mRenderRate = fps; } VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const { diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index d0e3098c5e..cd5d9ef755 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -67,7 +67,7 @@ public: bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex); - void setDivisor(unsigned divisor) final EXCLUDES(mMutex); + void setRenderRate(Fps) final EXCLUDES(mMutex); void dump(std::string& result) const final EXCLUDES(mMutex); @@ -106,7 +106,7 @@ private: size_t mLastTimestampIndex GUARDED_BY(mMutex) = 0; std::vector mTimestamps GUARDED_BY(mMutex); - unsigned mDivisor GUARDED_BY(mMutex) = 1; + std::optional mRenderRate GUARDED_BY(mMutex); mutable std::optional mLastVsyncSequence GUARDED_BY(mMutex); }; diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h index 8d1629faae..bc0e3bcbb2 100644 --- a/services/surfaceflinger/Scheduler/VSyncTracker.h +++ b/services/surfaceflinger/Scheduler/VSyncTracker.h @@ -80,15 +80,16 @@ public: virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0; /* - * Sets a divisor on the rate (which is a multiplier of the period). + * Sets a render rate on the tracker. If the render rate is not a divisor + * of the period, the render rate is ignored until the period changes. * The tracker will continue to track the vsync timeline and expect it * to match the current period, however, nextAnticipatedVSyncTimeFrom will - * return vsyncs according to the divisor set. Setting a divisor is useful + * return vsyncs according to the render rate set. Setting a render rate is useful * when a display is running at 120Hz but the render frame rate is 60Hz. * - * \param [in] divisor The rate divisor the tracker should operate at. + * \param [in] Fps The render rate the tracker should operate at. */ - virtual void setDivisor(unsigned divisor) = 0; + virtual void setRenderRate(Fps) = 0; virtual void dump(std::string& result) const = 0; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h index 713b71042a..e6be9a8b21 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h @@ -100,7 +100,7 @@ public: return true; } - void setDivisor(unsigned) override {} + void setRenderRate(Fps) override {} nsecs_t nextVSyncTime(nsecs_t timePoint) const { if (timePoint % mPeriod == 0) { diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp index 47c2deef51..29088349f1 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp @@ -54,7 +54,7 @@ public: void resetModel() final {} bool needsMoreSamples() const final { return false; } bool isVSyncInPhase(nsecs_t, Fps) const final { return false; } - void setDivisor(unsigned) final {} + void setRenderRate(Fps) final {} void dump(std::string&) const final {} private: @@ -92,7 +92,7 @@ public: void resetModel() final {} bool needsMoreSamples() const final { return false; } bool isVSyncInPhase(nsecs_t, Fps) const final { return false; } - void setDivisor(unsigned) final {} + void setRenderRate(Fps) final {} void dump(std::string&) const final {} private: diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp index 14a2860378..f143b49e5e 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp @@ -55,7 +55,7 @@ public: MOCK_METHOD0(resetModel, void()); MOCK_CONST_METHOD0(needsMoreSamples, bool()); MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps)); - MOCK_METHOD(void, setDivisor, (unsigned), (override)); + MOCK_METHOD(void, setRenderRate, (Fps), (override)); MOCK_CONST_METHOD1(dump, void(std::string&)); nsecs_t nextVSyncTime(nsecs_t timePoint) const { diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index 48d39cf3f5..db531bf194 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -554,7 +554,7 @@ TEST_F(VSyncPredictorTest, robustToDuplicateTimestamps_60hzRealTraceData) { EXPECT_THAT(intercept, IsCloseTo(expectedIntercept, mMaxRoundingError)); } -TEST_F(VSyncPredictorTest, setDivisorIsRespected) { +TEST_F(VSyncPredictorTest, setRenderRateIsRespected) { auto last = mNow; for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); @@ -563,7 +563,7 @@ TEST_F(VSyncPredictorTest, setDivisorIsRespected) { tracker.addVsyncTimestamp(mNow); } - tracker.setDivisor(3); + tracker.setRenderRate(Fps::fromPeriodNsecs(3 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod)); @@ -574,7 +574,7 @@ TEST_F(VSyncPredictorTest, setDivisorIsRespected) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 7 * mPeriod)); } -TEST_F(VSyncPredictorTest, setDivisorOfDivosorIsInPhase) { +TEST_F(VSyncPredictorTest, setRenderRateOfDivisorIsInPhase) { auto last = mNow; for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); @@ -583,18 +583,44 @@ TEST_F(VSyncPredictorTest, setDivisorOfDivosorIsInPhase) { tracker.addVsyncTimestamp(mNow); } - tracker.setDivisor(4); + const auto refreshRate = Fps::fromPeriodNsecs(mPeriod); + + tracker.setRenderRate(refreshRate / 4); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 3 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 7 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 11 * mPeriod)); - tracker.setDivisor(2); + tracker.setRenderRate(refreshRate / 2); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 3 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 5 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5 * mPeriod), Eq(mNow + 7 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 9 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 9 * mPeriod), Eq(mNow + 11 * mPeriod)); + + tracker.setRenderRate(refreshRate / 6); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 7 * mPeriod)); +} + +TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) { + auto last = mNow; + for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); + mNow += mPeriod; + last = mNow; + tracker.addVsyncTimestamp(mNow); + } + + tracker.setRenderRate(Fps::fromPeriodNsecs(3.5f * mPeriod)); + + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1100), Eq(mNow + 2 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 2100), Eq(mNow + 3 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3100), Eq(mNow + 4 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 4100), Eq(mNow + 5 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod)); } } // namespace android::scheduler diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp index 1fb2709f8d..fb8d989d44 100644 --- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp @@ -50,7 +50,7 @@ public: MOCK_METHOD0(resetModel, void()); MOCK_CONST_METHOD0(needsMoreSamples, bool()); MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps)); - MOCK_METHOD(void, setDivisor, (unsigned), (override)); + MOCK_METHOD(void, setRenderRate, (Fps), (override)); MOCK_CONST_METHOD1(dump, void(std::string&)); }; diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h index 6893154259..dcf25e18a8 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h +++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h @@ -34,7 +34,7 @@ public: MOCK_METHOD0(resetModel, void()); MOCK_CONST_METHOD0(needsMoreSamples, bool()); MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps)); - MOCK_METHOD(void, setDivisor, (unsigned), (override)); + MOCK_METHOD(void, setRenderRate, (Fps), (override)); MOCK_CONST_METHOD1(dump, void(std::string&)); }; -- GitLab From ea1262381219d577bf5772cff2f474eebe8ce6d4 Mon Sep 17 00:00:00 2001 From: AleX Pelosi Date: Fri, 17 Feb 2023 01:00:19 +0000 Subject: [PATCH 0870/1310] batteryservice: add BATTERY_PROP_STATE_OF_HEALTH Bug: 251425963 Test: m Change-Id: I5b251b7c375ab423c93382cb51ec87b11e4f4a5a Signed-off-by: AleX Pelosi --- services/batteryservice/include/batteryservice/BatteryService.h | 1 + 1 file changed, 1 insertion(+) diff --git a/services/batteryservice/include/batteryservice/BatteryService.h b/services/batteryservice/include/batteryservice/BatteryService.h index a2e4115bd6..bf6189d7af 100644 --- a/services/batteryservice/include/batteryservice/BatteryService.h +++ b/services/batteryservice/include/batteryservice/BatteryService.h @@ -37,6 +37,7 @@ enum { BATTERY_PROP_CHARGING_POLICY = 7, // equals BATTERY_PROPERTY_CHARGING_POLICY BATTERY_PROP_MANUFACTURING_DATE = 8, // equals BATTERY_PROPERTY_MANUFACTURING_DATE BATTERY_PROP_FIRST_USAGE_DATE = 9, // equals BATTERY_PROPERTY_FIRST_USAGE_DATE + BATTERY_PROP_STATE_OF_HEALTH = 10, // equals BATTERY_PROPERTY_STATE_OF_HEALTH }; struct BatteryProperties { -- GitLab From 328a831c113c88541d637a9a0eafd329d72ac14b Mon Sep 17 00:00:00 2001 From: Chavi Weingarten Date: Thu, 26 Jan 2023 21:17:52 +0000 Subject: [PATCH 0871/1310] Clear TrustedPresentationListener when a new listener is set If a TrustedPresentationListener is overwritten for a Layer, the last trusted presentation states need to be cleared. It also needs to make sure composite is called after setting a TrustedPresentationListener since the layer may already be in the presented state and there's nothing new to compose. We need to make sure the presentation state is computed at least once after setting the TrustedPresentationListener Test: TrustedPresentationCallbackTest Bug: 256993331 Change-Id: I5cd344e9609d96c961fc70093ea705ee48f46817 --- services/surfaceflinger/Layer.cpp | 12 +++++++++++- services/surfaceflinger/Layer.h | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 12 ++++++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 084d9b93db..427fec26dd 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -4049,7 +4049,7 @@ void Layer::updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMet } } -void Layer::setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds, +bool Layer::setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds, TrustedPresentationListener const& listener) { bool hadTrustedPresentationListener = hasTrustedPresentationListener(); mTrustedPresentationListener = listener; @@ -4060,6 +4060,16 @@ void Layer::setTrustedPresentationInfo(TrustedPresentationThresholds const& thre } else if (hadTrustedPresentationListener && !haveTrustedPresentationListener) { mFlinger->mNumTrustedPresentationListeners--; } + + // Reset trusted presentation states to ensure we start the time again. + mEnteredTrustedPresentationStateTime = -1; + mLastReportedTrustedPresentationState = false; + mLastComputedTrustedPresentationState = false; + + // If there's a new trusted presentation listener, the code needs to go through the composite + // path to ensure it recomutes the current state and invokes the TrustedPresentationListener if + // we're already in the requested state. + return haveTrustedPresentationListener; } // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 2955dafa0f..59d50bf0b1 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -754,7 +754,7 @@ public: std::shared_ptr createSurfaceFrameForBuffer( const FrameTimelineInfo& info, nsecs_t queueTime, std::string debugName); - void setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds, + bool setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds, TrustedPresentationListener const& listener); // Creates a new handle each time, so we only expect diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 5f95859fd3..6f0589460c 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4848,8 +4848,10 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime } if (what & layer_state_t::eTrustedPresentationInfoChanged) { - layer->setTrustedPresentationInfo(s.trustedPresentationThresholds, - s.trustedPresentationListener); + if (layer->setTrustedPresentationInfo(s.trustedPresentationThresholds, + s.trustedPresentationListener)) { + flags |= eTraversalNeeded; + } } if (what & layer_state_t::eFlushJankData) { @@ -4951,8 +4953,10 @@ uint32_t SurfaceFlinger::updateLayerCallbacksAndStats(const FrameTimelineInfo& f } if (what & layer_state_t::eTrustedPresentationInfoChanged) { - layer->setTrustedPresentationInfo(s.trustedPresentationThresholds, - s.trustedPresentationListener); + if (layer->setTrustedPresentationInfo(s.trustedPresentationThresholds, + s.trustedPresentationListener)) { + flags |= eTraversalNeeded; + } } const auto& snapshot = mLayerSnapshotBuilder.getSnapshot(layer->getSequence()); -- GitLab From 0311fdd6d2d5eca00e84bd95b1d1a296171ca038 Mon Sep 17 00:00:00 2001 From: Adam Wright Date: Thu, 16 Feb 2023 19:05:45 +0000 Subject: [PATCH 0872/1310] Revert "SF: pass a render rate to VsyncTracker instead of a divisor" Revert submission 21422016 Reason for revert: DM P0 (b/269561042) Reverted changes: /q/submissionid:21422016 Bug: 269561042 Change-Id: I118dd75ea379673d649d3c75960f18ef45b3749f (cherry picked from commit 3d21d3afa1056efacf702b74449f9e2e7b5701f1) Merged-In: I118dd75ea379673d649d3c75960f18ef45b3749f --- .../surfaceflinger/Scheduler/Scheduler.cpp | 6 +++- .../Scheduler/VSyncPredictor.cpp | 25 ++++--------- .../surfaceflinger/Scheduler/VSyncPredictor.h | 4 +-- .../surfaceflinger/Scheduler/VSyncTracker.h | 9 +++-- .../fuzzer/surfaceflinger_scheduler_fuzzer.h | 2 +- .../unittests/VSyncDispatchRealtimeTest.cpp | 4 +-- .../unittests/VSyncDispatchTimerQueueTest.cpp | 2 +- .../tests/unittests/VSyncPredictorTest.cpp | 36 +++---------------- .../tests/unittests/VSyncReactorTest.cpp | 2 +- .../tests/unittests/mock/MockVSyncTracker.h | 2 +- 10 files changed, 28 insertions(+), 64 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index dab01ba48d..17cdff9518 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -418,7 +418,11 @@ void Scheduler::setRenderRate(Fps renderFrameRate) { ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(), to_string(mode.modePtr->getFps()).c_str()); - mVsyncSchedule->getTracker().setRenderRate(renderFrameRate); + const auto divisor = RefreshRateSelector::getFrameRateDivisor(mode.modePtr->getFps(), mode.fps); + LOG_ALWAYS_FATAL_IF(divisor == 0, "%s <> %s -- not divisors", to_string(mode.fps).c_str(), + to_string(mode.fps).c_str()); + + mVsyncSchedule->getTracker().setDivisor(static_cast(divisor)); } void Scheduler::resync() { diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index de7b338a21..5a5afd8fa6 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -272,26 +272,13 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const { // update the mLastVsyncSequence for reference point mLastVsyncSequence = getVsyncSequenceLocked(timePoint); - const auto renderRatePhase = [&]() REQUIRES(mMutex) -> int { - if (!mRenderRate) return 0; - - const auto divisor = - RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), - *mRenderRate); - if (divisor <= 1) return 0; - - const int mod = mLastVsyncSequence->seq % divisor; - if (mod == 0) return 0; - - return divisor - mod; - }(); - - if (renderRatePhase == 0) { + const auto mod = mLastVsyncSequence->seq % mDivisor; + if (mod == 0) { return mLastVsyncSequence->vsyncTime; } auto const [slope, intercept] = getVSyncPredictionModelLocked(); - const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase; + const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * (mDivisor - mod); return nextAnticipatedVSyncTimeFromLocked(approximateNextVsync - slope / 2); } @@ -330,10 +317,10 @@ bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) c return vsyncSequence.seq % divisor == 0; } -void VSyncPredictor::setRenderRate(Fps fps) { - ALOGV("%s: %s", __func__, to_string(fps).c_str()); +void VSyncPredictor::setDivisor(unsigned divisor) { + ALOGV("%s: %d", __func__, divisor); std::lock_guard lock(mMutex); - mRenderRate = fps; + mDivisor = divisor; } VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const { diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index cd5d9ef755..d0e3098c5e 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -67,7 +67,7 @@ public: bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex); - void setRenderRate(Fps) final EXCLUDES(mMutex); + void setDivisor(unsigned divisor) final EXCLUDES(mMutex); void dump(std::string& result) const final EXCLUDES(mMutex); @@ -106,7 +106,7 @@ private: size_t mLastTimestampIndex GUARDED_BY(mMutex) = 0; std::vector mTimestamps GUARDED_BY(mMutex); - std::optional mRenderRate GUARDED_BY(mMutex); + unsigned mDivisor GUARDED_BY(mMutex) = 1; mutable std::optional mLastVsyncSequence GUARDED_BY(mMutex); }; diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h index bc0e3bcbb2..8d1629faae 100644 --- a/services/surfaceflinger/Scheduler/VSyncTracker.h +++ b/services/surfaceflinger/Scheduler/VSyncTracker.h @@ -80,16 +80,15 @@ public: virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0; /* - * Sets a render rate on the tracker. If the render rate is not a divisor - * of the period, the render rate is ignored until the period changes. + * Sets a divisor on the rate (which is a multiplier of the period). * The tracker will continue to track the vsync timeline and expect it * to match the current period, however, nextAnticipatedVSyncTimeFrom will - * return vsyncs according to the render rate set. Setting a render rate is useful + * return vsyncs according to the divisor set. Setting a divisor is useful * when a display is running at 120Hz but the render frame rate is 60Hz. * - * \param [in] Fps The render rate the tracker should operate at. + * \param [in] divisor The rate divisor the tracker should operate at. */ - virtual void setRenderRate(Fps) = 0; + virtual void setDivisor(unsigned divisor) = 0; virtual void dump(std::string& result) const = 0; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h index e6be9a8b21..713b71042a 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h @@ -100,7 +100,7 @@ public: return true; } - void setRenderRate(Fps) override {} + void setDivisor(unsigned) override {} nsecs_t nextVSyncTime(nsecs_t timePoint) const { if (timePoint % mPeriod == 0) { diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp index 29088349f1..47c2deef51 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp @@ -54,7 +54,7 @@ public: void resetModel() final {} bool needsMoreSamples() const final { return false; } bool isVSyncInPhase(nsecs_t, Fps) const final { return false; } - void setRenderRate(Fps) final {} + void setDivisor(unsigned) final {} void dump(std::string&) const final {} private: @@ -92,7 +92,7 @@ public: void resetModel() final {} bool needsMoreSamples() const final { return false; } bool isVSyncInPhase(nsecs_t, Fps) const final { return false; } - void setRenderRate(Fps) final {} + void setDivisor(unsigned) final {} void dump(std::string&) const final {} private: diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp index f143b49e5e..14a2860378 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp @@ -55,7 +55,7 @@ public: MOCK_METHOD0(resetModel, void()); MOCK_CONST_METHOD0(needsMoreSamples, bool()); MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps)); - MOCK_METHOD(void, setRenderRate, (Fps), (override)); + MOCK_METHOD(void, setDivisor, (unsigned), (override)); MOCK_CONST_METHOD1(dump, void(std::string&)); nsecs_t nextVSyncTime(nsecs_t timePoint) const { diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index db531bf194..48d39cf3f5 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -554,7 +554,7 @@ TEST_F(VSyncPredictorTest, robustToDuplicateTimestamps_60hzRealTraceData) { EXPECT_THAT(intercept, IsCloseTo(expectedIntercept, mMaxRoundingError)); } -TEST_F(VSyncPredictorTest, setRenderRateIsRespected) { +TEST_F(VSyncPredictorTest, setDivisorIsRespected) { auto last = mNow; for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); @@ -563,7 +563,7 @@ TEST_F(VSyncPredictorTest, setRenderRateIsRespected) { tracker.addVsyncTimestamp(mNow); } - tracker.setRenderRate(Fps::fromPeriodNsecs(3 * mPeriod)); + tracker.setDivisor(3); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod)); @@ -574,7 +574,7 @@ TEST_F(VSyncPredictorTest, setRenderRateIsRespected) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 7 * mPeriod)); } -TEST_F(VSyncPredictorTest, setRenderRateOfDivisorIsInPhase) { +TEST_F(VSyncPredictorTest, setDivisorOfDivosorIsInPhase) { auto last = mNow; for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); @@ -583,44 +583,18 @@ TEST_F(VSyncPredictorTest, setRenderRateOfDivisorIsInPhase) { tracker.addVsyncTimestamp(mNow); } - const auto refreshRate = Fps::fromPeriodNsecs(mPeriod); - - tracker.setRenderRate(refreshRate / 4); + tracker.setDivisor(4); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 3 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 7 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 11 * mPeriod)); - tracker.setRenderRate(refreshRate / 2); + tracker.setDivisor(2); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 3 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 5 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5 * mPeriod), Eq(mNow + 7 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 9 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 9 * mPeriod), Eq(mNow + 11 * mPeriod)); - - tracker.setRenderRate(refreshRate / 6); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 7 * mPeriod)); -} - -TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) { - auto last = mNow; - for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); - mNow += mPeriod; - last = mNow; - tracker.addVsyncTimestamp(mNow); - } - - tracker.setRenderRate(Fps::fromPeriodNsecs(3.5f * mPeriod)); - - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1100), Eq(mNow + 2 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 2100), Eq(mNow + 3 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3100), Eq(mNow + 4 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 4100), Eq(mNow + 5 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod)); } } // namespace android::scheduler diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp index fb8d989d44..1fb2709f8d 100644 --- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp @@ -50,7 +50,7 @@ public: MOCK_METHOD0(resetModel, void()); MOCK_CONST_METHOD0(needsMoreSamples, bool()); MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps)); - MOCK_METHOD(void, setRenderRate, (Fps), (override)); + MOCK_METHOD(void, setDivisor, (unsigned), (override)); MOCK_CONST_METHOD1(dump, void(std::string&)); }; diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h index dcf25e18a8..6893154259 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h +++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h @@ -34,7 +34,7 @@ public: MOCK_METHOD0(resetModel, void()); MOCK_CONST_METHOD0(needsMoreSamples, bool()); MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps)); - MOCK_METHOD(void, setRenderRate, (Fps), (override)); + MOCK_METHOD(void, setDivisor, (unsigned), (override)); MOCK_CONST_METHOD1(dump, void(std::string&)); }; -- GitLab From fdc049c5f4e104b8005d80cb33088183963f8517 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Fri, 17 Feb 2023 14:52:05 +0000 Subject: [PATCH 0873/1310] Revert "Revert "SF: pass a render rate to VsyncTracker instead of a divisor"" This reverts commit 3d21d3afa1056efacf702b74449f9e2e7b5701f1. Reason for revert: https://b.corp.google.com/issues/269561042#comment14 Bug: 269561042 Change-Id: Iae8d0e62be2eafb4278b64ffcb1524ece211a00c --- .../surfaceflinger/Scheduler/Scheduler.cpp | 6 +--- .../Scheduler/VSyncPredictor.cpp | 25 +++++++++---- .../surfaceflinger/Scheduler/VSyncPredictor.h | 4 +-- .../surfaceflinger/Scheduler/VSyncTracker.h | 9 ++--- .../fuzzer/surfaceflinger_scheduler_fuzzer.h | 2 +- .../unittests/VSyncDispatchRealtimeTest.cpp | 4 +-- .../unittests/VSyncDispatchTimerQueueTest.cpp | 2 +- .../tests/unittests/VSyncPredictorTest.cpp | 36 ++++++++++++++++--- .../tests/unittests/VSyncReactorTest.cpp | 2 +- .../tests/unittests/mock/MockVSyncTracker.h | 2 +- 10 files changed, 64 insertions(+), 28 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 17cdff9518..dab01ba48d 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -418,11 +418,7 @@ void Scheduler::setRenderRate(Fps renderFrameRate) { ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(), to_string(mode.modePtr->getFps()).c_str()); - const auto divisor = RefreshRateSelector::getFrameRateDivisor(mode.modePtr->getFps(), mode.fps); - LOG_ALWAYS_FATAL_IF(divisor == 0, "%s <> %s -- not divisors", to_string(mode.fps).c_str(), - to_string(mode.fps).c_str()); - - mVsyncSchedule->getTracker().setDivisor(static_cast(divisor)); + mVsyncSchedule->getTracker().setRenderRate(renderFrameRate); } void Scheduler::resync() { diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 5a5afd8fa6..de7b338a21 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -272,13 +272,26 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const { // update the mLastVsyncSequence for reference point mLastVsyncSequence = getVsyncSequenceLocked(timePoint); - const auto mod = mLastVsyncSequence->seq % mDivisor; - if (mod == 0) { + const auto renderRatePhase = [&]() REQUIRES(mMutex) -> int { + if (!mRenderRate) return 0; + + const auto divisor = + RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), + *mRenderRate); + if (divisor <= 1) return 0; + + const int mod = mLastVsyncSequence->seq % divisor; + if (mod == 0) return 0; + + return divisor - mod; + }(); + + if (renderRatePhase == 0) { return mLastVsyncSequence->vsyncTime; } auto const [slope, intercept] = getVSyncPredictionModelLocked(); - const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * (mDivisor - mod); + const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase; return nextAnticipatedVSyncTimeFromLocked(approximateNextVsync - slope / 2); } @@ -317,10 +330,10 @@ bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) c return vsyncSequence.seq % divisor == 0; } -void VSyncPredictor::setDivisor(unsigned divisor) { - ALOGV("%s: %d", __func__, divisor); +void VSyncPredictor::setRenderRate(Fps fps) { + ALOGV("%s: %s", __func__, to_string(fps).c_str()); std::lock_guard lock(mMutex); - mDivisor = divisor; + mRenderRate = fps; } VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const { diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index d0e3098c5e..cd5d9ef755 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -67,7 +67,7 @@ public: bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex); - void setDivisor(unsigned divisor) final EXCLUDES(mMutex); + void setRenderRate(Fps) final EXCLUDES(mMutex); void dump(std::string& result) const final EXCLUDES(mMutex); @@ -106,7 +106,7 @@ private: size_t mLastTimestampIndex GUARDED_BY(mMutex) = 0; std::vector mTimestamps GUARDED_BY(mMutex); - unsigned mDivisor GUARDED_BY(mMutex) = 1; + std::optional mRenderRate GUARDED_BY(mMutex); mutable std::optional mLastVsyncSequence GUARDED_BY(mMutex); }; diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h index 8d1629faae..bc0e3bcbb2 100644 --- a/services/surfaceflinger/Scheduler/VSyncTracker.h +++ b/services/surfaceflinger/Scheduler/VSyncTracker.h @@ -80,15 +80,16 @@ public: virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0; /* - * Sets a divisor on the rate (which is a multiplier of the period). + * Sets a render rate on the tracker. If the render rate is not a divisor + * of the period, the render rate is ignored until the period changes. * The tracker will continue to track the vsync timeline and expect it * to match the current period, however, nextAnticipatedVSyncTimeFrom will - * return vsyncs according to the divisor set. Setting a divisor is useful + * return vsyncs according to the render rate set. Setting a render rate is useful * when a display is running at 120Hz but the render frame rate is 60Hz. * - * \param [in] divisor The rate divisor the tracker should operate at. + * \param [in] Fps The render rate the tracker should operate at. */ - virtual void setDivisor(unsigned divisor) = 0; + virtual void setRenderRate(Fps) = 0; virtual void dump(std::string& result) const = 0; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h index 713b71042a..e6be9a8b21 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h @@ -100,7 +100,7 @@ public: return true; } - void setDivisor(unsigned) override {} + void setRenderRate(Fps) override {} nsecs_t nextVSyncTime(nsecs_t timePoint) const { if (timePoint % mPeriod == 0) { diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp index 47c2deef51..29088349f1 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp @@ -54,7 +54,7 @@ public: void resetModel() final {} bool needsMoreSamples() const final { return false; } bool isVSyncInPhase(nsecs_t, Fps) const final { return false; } - void setDivisor(unsigned) final {} + void setRenderRate(Fps) final {} void dump(std::string&) const final {} private: @@ -92,7 +92,7 @@ public: void resetModel() final {} bool needsMoreSamples() const final { return false; } bool isVSyncInPhase(nsecs_t, Fps) const final { return false; } - void setDivisor(unsigned) final {} + void setRenderRate(Fps) final {} void dump(std::string&) const final {} private: diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp index 14a2860378..f143b49e5e 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp @@ -55,7 +55,7 @@ public: MOCK_METHOD0(resetModel, void()); MOCK_CONST_METHOD0(needsMoreSamples, bool()); MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps)); - MOCK_METHOD(void, setDivisor, (unsigned), (override)); + MOCK_METHOD(void, setRenderRate, (Fps), (override)); MOCK_CONST_METHOD1(dump, void(std::string&)); nsecs_t nextVSyncTime(nsecs_t timePoint) const { diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index 48d39cf3f5..db531bf194 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -554,7 +554,7 @@ TEST_F(VSyncPredictorTest, robustToDuplicateTimestamps_60hzRealTraceData) { EXPECT_THAT(intercept, IsCloseTo(expectedIntercept, mMaxRoundingError)); } -TEST_F(VSyncPredictorTest, setDivisorIsRespected) { +TEST_F(VSyncPredictorTest, setRenderRateIsRespected) { auto last = mNow; for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); @@ -563,7 +563,7 @@ TEST_F(VSyncPredictorTest, setDivisorIsRespected) { tracker.addVsyncTimestamp(mNow); } - tracker.setDivisor(3); + tracker.setRenderRate(Fps::fromPeriodNsecs(3 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod)); @@ -574,7 +574,7 @@ TEST_F(VSyncPredictorTest, setDivisorIsRespected) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 7 * mPeriod)); } -TEST_F(VSyncPredictorTest, setDivisorOfDivosorIsInPhase) { +TEST_F(VSyncPredictorTest, setRenderRateOfDivisorIsInPhase) { auto last = mNow; for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); @@ -583,18 +583,44 @@ TEST_F(VSyncPredictorTest, setDivisorOfDivosorIsInPhase) { tracker.addVsyncTimestamp(mNow); } - tracker.setDivisor(4); + const auto refreshRate = Fps::fromPeriodNsecs(mPeriod); + + tracker.setRenderRate(refreshRate / 4); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 3 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 7 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 11 * mPeriod)); - tracker.setDivisor(2); + tracker.setRenderRate(refreshRate / 2); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 3 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 5 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5 * mPeriod), Eq(mNow + 7 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 9 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 9 * mPeriod), Eq(mNow + 11 * mPeriod)); + + tracker.setRenderRate(refreshRate / 6); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 7 * mPeriod)); +} + +TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) { + auto last = mNow; + for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); + mNow += mPeriod; + last = mNow; + tracker.addVsyncTimestamp(mNow); + } + + tracker.setRenderRate(Fps::fromPeriodNsecs(3.5f * mPeriod)); + + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1100), Eq(mNow + 2 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 2100), Eq(mNow + 3 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3100), Eq(mNow + 4 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 4100), Eq(mNow + 5 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod)); } } // namespace android::scheduler diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp index 1fb2709f8d..fb8d989d44 100644 --- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp @@ -50,7 +50,7 @@ public: MOCK_METHOD0(resetModel, void()); MOCK_CONST_METHOD0(needsMoreSamples, bool()); MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps)); - MOCK_METHOD(void, setDivisor, (unsigned), (override)); + MOCK_METHOD(void, setRenderRate, (Fps), (override)); MOCK_CONST_METHOD1(dump, void(std::string&)); }; diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h index 6893154259..dcf25e18a8 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h +++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h @@ -34,7 +34,7 @@ public: MOCK_METHOD0(resetModel, void()); MOCK_CONST_METHOD0(needsMoreSamples, bool()); MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps)); - MOCK_METHOD(void, setDivisor, (unsigned), (override)); + MOCK_METHOD(void, setRenderRate, (Fps), (override)); MOCK_CONST_METHOD1(dump, void(std::string&)); }; -- GitLab From e8320c861050c2f6af9e4142d567222e9b717b81 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Thu, 16 Feb 2023 16:46:18 -0800 Subject: [PATCH 0874/1310] SF: do not clear mLastVsyncSequence on VSyncPredictor::clearTimestamps Clearing it looses the information about the last reference point. Even if that point is incorrect, it is still better than nothing, and the logic will self correct itself once the model gets more timestamps. Test: atest FrameRateOverrideHostTest Bug: 269561042 Change-Id: Ic0a1a173b5f99820e93e31a0dba11ee2b71cb6f0 --- services/surfaceflinger/Scheduler/VSyncPredictor.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index de7b338a21..fdeb310662 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -376,7 +376,6 @@ void VSyncPredictor::clearTimestamps() { mTimestamps.clear(); mLastTimestampIndex = 0; } - mLastVsyncSequence.reset(); } bool VSyncPredictor::needsMoreSamples() const { -- GitLab From 332e6baf75b1b8c5d086d241dcceed43ecabc14c Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 25 Jan 2023 17:16:19 -0800 Subject: [PATCH 0875/1310] SF: Pass the release fence to the owning layer instead of the clone Cloned layers do not have references to the callback handle so we were dropping the release fence from clone layers presented on other displays. Test: presubmit Fixes: 244427869 Change-Id: I0e202a84357c60d769a531cfadd748f0346aaa65 --- services/surfaceflinger/Layer.h | 9 +++++---- services/surfaceflinger/SurfaceFlinger.cpp | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 2955dafa0f..11cb8b2e35 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -838,6 +838,11 @@ public: void updateMetadataSnapshot(const LayerMetadata& parentMetadata); void updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata, std::unordered_set& visited); + sp getClonedFrom() const { + return mClonedFrom != nullptr ? mClonedFrom.promote() : nullptr; + } + bool isClone() { return mClonedFrom != nullptr; } + bool willPresentCurrentTransaction() const; void callReleaseBufferCallback(const sp& listener, @@ -861,10 +866,6 @@ protected: void gatherBufferInfo(); void onSurfaceFrameCreated(const std::shared_ptr&); - sp getClonedFrom() const { - return mClonedFrom != nullptr ? mClonedFrom.promote() : nullptr; - } - bool isClone() { return mClonedFrom != nullptr; } bool isClonedFromAlive() { return getClonedFrom() != nullptr; } void cloneDrawingState(const Layer* from); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 5f95859fd3..1393eb878f 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2544,7 +2544,9 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) CompositionResult compositionResult{layerFE->stealCompositionResult()}; layer->onPreComposition(compositionResult.refreshStartTime); for (auto releaseFence : compositionResult.releaseFences) { - layer->onLayerDisplayed(releaseFence); + Layer* clonedFrom = layer->getClonedFrom().get(); + auto owningLayer = clonedFrom ? clonedFrom : layer; + owningLayer->onLayerDisplayed(releaseFence); } if (compositionResult.lastClientCompositionFence) { layer->setWasClientComposed(compositionResult.lastClientCompositionFence); -- GitLab From aee9a6254992f5ff3d86d2e47893be99de1f8446 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Sat, 11 Feb 2023 14:24:19 -0500 Subject: [PATCH 0876/1310] SF: Deduplicate mock scheduler setup in tests Extract TestableSurfaceFlinger::setupMockScheduler, and add an option to customize the PhysicalDisplayId of the injected RefreshRateSelector (and its display modes), so that tests can later inject a DisplayDevice with a matching ID. Remove the DisplayModesVariant with two display modes, as the few tests that used it are unrelated to scheduling and will later be decoupled entirely. Bug: 259436835 Bug: 241285191 Test: libsurfaceflinger_unittest Change-Id: Ie58b6947201a1911342e7bcd5ea64dd8702aaa57 Merged-In: Ie58b6947201a1911342e7bcd5ea64dd8702aaa57 (cherry picked from commit fb281c53240c3e2420a20cf0ea071198e963ed3d) --- .../Tracing/tools/LayerTraceGenerator.cpp | 47 +++++----- .../tests/unittests/CompositionTest.cpp | 33 +------ .../unittests/DisplayTransactionTest.cpp | 14 +-- .../unittests/DisplayTransactionTestHelpers.h | 4 +- .../tests/unittests/FpsReporterTest.cpp | 33 +------ .../FrameRateSelectionPriorityTest.cpp | 38 +-------- .../tests/unittests/GameModeTest.cpp | 33 +------ .../tests/unittests/LayerTestUtils.cpp | 40 +-------- .../tests/unittests/LayerTestUtils.h | 4 +- .../SurfaceFlinger_DisplayModeSwitching.cpp | 4 +- .../SurfaceFlinger_PowerHintTest.cpp | 36 +------- ...linger_UpdateLayerMetadataSnapshotTest.cpp | 32 +------ .../tests/unittests/TestableSurfaceFlinger.h | 85 +++++++++++++------ .../unittests/TransactionApplicationTest.cpp | 37 +------- .../unittests/TransactionFrameTracerTest.cpp | 33 +------ .../unittests/TransactionSurfaceFrameTest.cpp | 33 +------ .../TunnelModeEnabledReporterTest.cpp | 34 +------- 17 files changed, 110 insertions(+), 430 deletions(-) diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index ab98dbfe2f..31f4723915 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -191,29 +191,20 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, return false; } - Factory mFactory; - sp flinger = sp::make(mFactory); - TestableSurfaceFlinger mFlinger(flinger); - mFlinger.setupRenderEngine( + Factory factory; + sp flingerPtr = sp::make(factory); + TestableSurfaceFlinger flinger(flingerPtr); + flinger.setupRenderEngine( std::make_unique>()); - mock::VsyncController* mVsyncController = new testing::NiceMock(); - mock::VSyncTracker* mVSyncTracker = new testing::NiceMock(); - mock::EventThread* mEventThread = new testing::NiceMock(); - mock::EventThread* mSFEventThread = new testing::NiceMock(); - mFlinger.setupScheduler(std::unique_ptr(mVsyncController), - std::unique_ptr(mVSyncTracker), - std::unique_ptr(mEventThread), - std::unique_ptr(mSFEventThread), - TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp, - TestableSurfaceFlinger::kOneDisplayMode, true /* useNiceMock */); - - Hwc2::mock::Composer* mComposer = new testing::NiceMock(); - mFlinger.setupComposer(std::unique_ptr(mComposer)); - mFlinger.mutableMaxRenderTargetSize() = 16384; - - flinger->setLayerTracingFlags(LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS); - flinger->setLayerTraceSize(512 * 1024); // 512MB buffer size - flinger->startLayerTracing(traceFile.entry(0).elapsed_realtime_nanos()); + flinger.setupMockScheduler({.useNiceMock = true}); + + Hwc2::mock::Composer* composerPtr = new testing::NiceMock(); + flinger.setupComposer(std::unique_ptr(composerPtr)); + flinger.mutableMaxRenderTargetSize() = 16384; + + flingerPtr->setLayerTracingFlags(LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS); + flingerPtr->setLayerTraceSize(512 * 1024); // 512MB buffer size + flingerPtr->startLayerTracing(traceFile.entry(0).elapsed_realtime_nanos()); std::unique_ptr mapper = std::make_unique(); TraceGenFlingerDataMapper* dataMapper = mapper.get(); @@ -234,7 +225,7 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, parser.fromProto(entry.added_layers(j), tracingArgs); gui::CreateSurfaceResult outResult; - LayerCreationArgs args(mFlinger.flinger(), nullptr /* client */, tracingArgs.name, + LayerCreationArgs args(flinger.flinger(), nullptr /* client */, tracingArgs.name, tracingArgs.flags, LayerMetadata(), std::make_optional(tracingArgs.layerId)); @@ -247,10 +238,10 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, } else if (tracingArgs.parentId != -1) { parentHandle = dataMapper->getLayerHandle(tracingArgs.parentId); } - mFlinger.createLayer(args, parentHandle, outResult); + flinger.createLayer(args, parentHandle, outResult); } else { sp mirrorFromHandle = dataMapper->getLayerHandle(tracingArgs.mirrorFromId); - mFlinger.mirrorLayer(args, mirrorFromHandle, outResult); + flinger.mirrorLayer(args, mirrorFromHandle, outResult); } LOG_ALWAYS_FATAL_IF(outResult.layerId != tracingArgs.layerId, "Could not create layer expected:%d actual:%d", tracingArgs.layerId, @@ -261,19 +252,19 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, for (int j = 0; j < entry.transactions_size(); j++) { // apply transactions TransactionState transaction = parser.fromProto(entry.transactions(j)); - mFlinger.setTransactionStateInternal(transaction); + flinger.setTransactionStateInternal(transaction); } const auto frameTime = TimePoint::fromNs(entry.elapsed_realtime_nanos()); const auto vsyncId = VsyncId{entry.vsync_id()}; - mFlinger.commit(frameTime, vsyncId); + flinger.commit(frameTime, vsyncId); for (int j = 0; j < entry.removed_layer_handles_size(); j++) { dataMapper->mLayerHandles.erase(entry.removed_layer_handles(j)); } } - flinger->stopLayerTracing(outputLayersTracePath); + flingerPtr->stopLayerTracing(outputLayersTracePath); ALOGD("End of generating trace file. File written to %s", outputLayersTracePath); dataMapper->mLayerHandles.clear(); return true; diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 0416e93f1e..17ef6ff5e1 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -99,7 +99,7 @@ public: ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - setupScheduler(); + mFlinger.setupMockScheduler({.displayId = DEFAULT_DISPLAY_ID}); EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_WIDTH, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_WIDTH), Return(0))); @@ -122,36 +122,6 @@ public: ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); } - void setupScheduler() { - auto eventThread = std::make_unique(); - auto sfEventThread = std::make_unique(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread), - TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp, - TestableSurfaceFlinger::kTwoDisplayModes); - } - void setupForceGeometryDirty() { // TODO: This requires the visible region and other related // state to be set, and is problematic for BufferLayers since they are @@ -176,7 +146,6 @@ public: bool mDisplayOff = false; TestableSurfaceFlinger mFlinger; sp mDisplay; - sp mExternalDisplay; sp mDisplaySurface = sp::make(); sp mNativeWindow = sp::make(); diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp index e0b508aa73..52dc695a93 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp @@ -29,9 +29,7 @@ using testing::SetArgPointee; using android::hardware::graphics::composer::hal::HWDisplayId; -using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector; - -DisplayTransactionTest::DisplayTransactionTest() { +DisplayTransactionTest::DisplayTransactionTest(bool withMockScheduler) { const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); @@ -48,7 +46,10 @@ DisplayTransactionTest::DisplayTransactionTest() { return nullptr; }); - injectMockScheduler(); + if (withMockScheduler) { + injectMockScheduler(PhysicalDisplayId::fromPort(0)); + } + mFlinger.setupRenderEngine(std::unique_ptr(mRenderEngine)); injectMockComposer(0); @@ -61,7 +62,9 @@ DisplayTransactionTest::~DisplayTransactionTest() { mFlinger.resetScheduler(nullptr); } -void DisplayTransactionTest::injectMockScheduler() { +void DisplayTransactionTest::injectMockScheduler(PhysicalDisplayId displayId) { + LOG_ALWAYS_FATAL_IF(mFlinger.scheduler()); + EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_)); EXPECT_CALL(*mEventThread, createEventConnection(_, _)) .WillOnce(Return(sp::make(mEventThread, @@ -78,6 +81,7 @@ void DisplayTransactionTest::injectMockScheduler() { std::unique_ptr(mVSyncTracker), std::unique_ptr(mEventThread), std::unique_ptr(mSFEventThread), + TestableSurfaceFlinger::DefaultDisplayMode{displayId}, TestableSurfaceFlinger::SchedulerCallbackImpl::kMock); } diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h index 223f4db889..66aa2041de 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h @@ -85,7 +85,7 @@ public: // -------------------------------------------------------------------- // Mock/Fake injection - void injectMockScheduler(); + void injectMockScheduler(PhysicalDisplayId); void injectMockComposer(int virtualDisplayCount); void injectFakeBufferQueueFactory(); void injectFakeNativeWindowSurfaceFactory(); @@ -139,7 +139,7 @@ public: surfaceflinger::mock::NativeWindowSurface* mNativeWindowSurface = nullptr; protected: - DisplayTransactionTest(); + DisplayTransactionTest(bool withMockScheduler = true); }; constexpr int32_t DEFAULT_VSYNC_PERIOD = 16'666'667; diff --git a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp index 1cd9e49051..f695b096a7 100644 --- a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp +++ b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp @@ -29,9 +29,7 @@ #include "TestableSurfaceFlinger.h" #include "fake/FakeClock.h" #include "mock/DisplayHardware/MockComposer.h" -#include "mock/MockEventThread.h" #include "mock/MockFrameTimeline.h" -#include "mock/MockVsyncController.h" namespace android { @@ -47,7 +45,6 @@ using testing::UnorderedElementsAre; using android::Hwc2::IComposer; using android::Hwc2::IComposerClient; -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; using gui::LayerMetadata; struct TestableFpsListener : public gui::BnFpsListener { @@ -77,7 +74,6 @@ protected: static constexpr uint32_t LAYER_FLAGS = 0; static constexpr int32_t PRIORITY_UNSET = -1; - void setupScheduler(); sp createBufferStateLayer(LayerMetadata metadata); TestableSurfaceFlinger mFlinger; @@ -102,7 +98,7 @@ FpsReporterTest::FpsReporterTest() { ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - setupScheduler(); + mFlinger.setupMockScheduler(); mFlinger.setupComposer(std::make_unique()); mFpsListener = sp::make(); @@ -120,33 +116,6 @@ sp FpsReporterTest::createBufferStateLayer(LayerMetadata metadata = {}) { return sp::make(args); } -void FpsReporterTest::setupScheduler() { - auto eventThread = std::make_unique(); - auto sfEventThread = std::make_unique(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread)); -} - namespace { TEST_F(FpsReporterTest, callsListeners) { diff --git a/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp index ac63a0edbd..1c9aee7443 100644 --- a/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp +++ b/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp @@ -24,8 +24,6 @@ #include "Layer.h" #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockComposer.h" -#include "mock/MockEventThread.h" -#include "mock/MockVsyncController.h" namespace android { @@ -38,8 +36,6 @@ using testing::SetArgPointee; using android::Hwc2::IComposer; using android::Hwc2::IComposerClient; -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; - /** * This class covers all the test that are related to refresh rate selection. */ @@ -56,7 +52,6 @@ protected: static constexpr uint32_t LAYER_FLAGS = 0; static constexpr int32_t PRIORITY_UNSET = -1; - void setupScheduler(); sp createBufferStateLayer(); sp createEffectLayer(); @@ -76,7 +71,7 @@ RefreshRateSelectionTest::RefreshRateSelectionTest() { ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - setupScheduler(); + mFlinger.setupMockScheduler(); mFlinger.setupComposer(std::make_unique()); } @@ -108,37 +103,8 @@ void RefreshRateSelectionTest::commitTransaction(Layer* layer) { layer->commitTransaction(c); } -void RefreshRateSelectionTest::setupScheduler() { - auto eventThread = std::make_unique(); - auto sfEventThread = std::make_unique(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread)); -} - namespace { -/* ------------------------------------------------------------------------ - * Test cases - */ + TEST_F(RefreshRateSelectionTest, testPriorityOnBufferStateLayers) { mParent = createBufferStateLayer(); mChild = createBufferStateLayer(); diff --git a/services/surfaceflinger/tests/unittests/GameModeTest.cpp b/services/surfaceflinger/tests/unittests/GameModeTest.cpp index 29aa7171ba..1b5c6e70f8 100644 --- a/services/surfaceflinger/tests/unittests/GameModeTest.cpp +++ b/services/surfaceflinger/tests/unittests/GameModeTest.cpp @@ -25,15 +25,13 @@ #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockComposer.h" -#include "mock/MockEventThread.h" -#include "mock/MockVsyncController.h" namespace android { using testing::_; using testing::Mock; using testing::Return; -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; + using gui::GameMode; using gui::LayerMetadata; @@ -43,7 +41,7 @@ public: const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - setupScheduler(); + mFlinger.setupMockScheduler(); setupComposer(); } @@ -59,33 +57,6 @@ public: return sp::make(args); } - void setupScheduler() { - auto eventThread = std::make_unique(); - auto sfEventThread = std::make_unique(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread)); - } - void setupComposer() { mComposer = new Hwc2::mock::Composer(); mFlinger.setupComposer(std::unique_ptr(mComposer)); diff --git a/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp b/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp index ee42e19c34..803e807d7b 100644 --- a/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp +++ b/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,8 @@ #include "LayerTestUtils.h" -#include "mock/MockEventThread.h" - namespace android { -using testing::_; -using testing::Return; - -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; - sp BufferStateLayerFactory::createLayer(TestableSurfaceFlinger& flinger) { sp client; LayerCreationArgs args(flinger.flinger(), client, "buffer-state-layer", LAYER_FLAGS, @@ -44,36 +37,7 @@ std::string PrintToStringParamName( } BaseLayerTest::BaseLayerTest() { - setupScheduler(); -} - -void BaseLayerTest::setupScheduler() { - auto eventThread = std::make_unique(); - auto sfEventThread = std::make_unique(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread), - TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp, - TestableSurfaceFlinger::kTwoDisplayModes); + mFlinger.setupMockScheduler(); } } // namespace android diff --git a/services/surfaceflinger/tests/unittests/LayerTestUtils.h b/services/surfaceflinger/tests/unittests/LayerTestUtils.h index ab446fafeb..0773d9081e 100644 --- a/services/surfaceflinger/tests/unittests/LayerTestUtils.h +++ b/services/surfaceflinger/tests/unittests/LayerTestUtils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,8 +63,6 @@ class BaseLayerTest : public ::testing::TestWithParam(mRenderEngine)); @@ -99,36 +95,6 @@ void SurfaceFlingerPowerHintTest::SetUp() { .inject(); } -void SurfaceFlingerPowerHintTest::setupScheduler() { - auto eventThread = std::make_unique(); - auto sfEventThread = std::make_unique(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread), - TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp, - TestableSurfaceFlinger::kTwoDisplayModes); -} - TEST_F(SurfaceFlingerPowerHintTest, sendDurationsIncludingHwcWaitTime) { ON_CALL(*mPowerAdvisor, usePowerHintSession()).WillByDefault(Return(true)); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp index fed6a1ae56..0e5f1ea789 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp @@ -3,47 +3,17 @@ #include #include "TestableSurfaceFlinger.h" -#include "mock/MockEventThread.h" -#include "mock/MockVsyncController.h" namespace android { using testing::_; using testing::Return; -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; class SurfaceFlingerUpdateLayerMetadataSnapshotTest : public testing::Test { public: - SurfaceFlingerUpdateLayerMetadataSnapshotTest() { setupScheduler(); } + SurfaceFlingerUpdateLayerMetadataSnapshotTest() { mFlinger.setupMockScheduler(); } protected: - void setupScheduler() { - auto eventThread = std::make_unique(); - auto sfEventThread = std::make_unique(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread)); - } - sp createLayer(const char* name, LayerMetadata& inOutlayerMetadata) { LayerCreationArgs args = LayerCreationArgs{mFlinger.flinger(), nullptr, name, 0, inOutlayerMetadata}; diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 89a661f108..dbde3b16d2 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -47,14 +48,12 @@ #include "TestableScheduler.h" #include "mock/DisplayHardware/MockComposer.h" #include "mock/DisplayHardware/MockDisplayMode.h" +#include "mock/MockEventThread.h" #include "mock/MockFrameTimeline.h" #include "mock/MockFrameTracer.h" #include "mock/MockSchedulerCallback.h" namespace android { - -class EventThread; - namespace renderengine { class RenderEngine; @@ -151,6 +150,11 @@ public: CreateCompositionEngineFunction mCreateCompositionEngine; }; +struct MockSchedulerOptions { + PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(0); + bool useNiceMock = false; +}; + } // namespace surfaceflinger::test class TestableSurfaceFlinger { @@ -189,38 +193,31 @@ public: enum class SchedulerCallbackImpl { kNoOp, kMock }; - static constexpr struct OneDisplayMode { - } kOneDisplayMode; - - static constexpr struct TwoDisplayModes { - } kTwoDisplayModes; + struct DefaultDisplayMode { + // The ID of the injected RefreshRateSelector and its default display mode. + PhysicalDisplayId displayId; + }; - using RefreshRateSelectorPtr = std::shared_ptr; + using RefreshRateSelectorPtr = scheduler::Scheduler::RefreshRateSelectorPtr; - using DisplayModesVariant = - std::variant; + using DisplayModesVariant = std::variant; void setupScheduler(std::unique_ptr vsyncController, std::unique_ptr vsyncTracker, std::unique_ptr appEventThread, std::unique_ptr sfEventThread, + DisplayModesVariant modesVariant, SchedulerCallbackImpl callbackImpl = SchedulerCallbackImpl::kNoOp, - DisplayModesVariant modesVariant = kOneDisplayMode, bool useNiceMock = false) { - RefreshRateSelectorPtr selectorPtr; - if (std::holds_alternative(modesVariant)) { - selectorPtr = std::move(std::get(modesVariant)); - } else { - constexpr DisplayModeId kModeId60{0}; - DisplayModes modes = makeModes(mock::createDisplayMode(kModeId60, 60_Hz)); - - if (std::holds_alternative(modesVariant)) { - constexpr DisplayModeId kModeId90{1}; - modes.try_emplace(kModeId90, mock::createDisplayMode(kModeId90, 90_Hz)); - } - - selectorPtr = std::make_shared(modes, kModeId60); - } + RefreshRateSelectorPtr selectorPtr = ftl::match( + modesVariant, + [](DefaultDisplayMode arg) { + constexpr DisplayModeId kModeId60{0}; + return std::make_shared( + makeModes(mock::createDisplayMode(arg.displayId, kModeId60, 60_Hz)), + kModeId60); + }, + [](RefreshRateSelectorPtr selectorPtr) { return selectorPtr; }); const auto fps = selectorPtr->getActiveMode().fps; mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps); @@ -260,6 +257,37 @@ public: resetScheduler(mScheduler); } + void setupMockScheduler(test::MockSchedulerOptions options = {}) { + using testing::_; + using testing::Return; + + auto eventThread = makeMock(options.useNiceMock); + auto sfEventThread = makeMock(options.useNiceMock); + + EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); + EXPECT_CALL(*eventThread, createEventConnection(_, _)) + .WillOnce(Return(sp::make(eventThread.get(), + mock::EventThread::kCallingUid, + ResyncCallback()))); + + EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); + EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) + .WillOnce(Return(sp::make(sfEventThread.get(), + mock::EventThread::kCallingUid, + ResyncCallback()))); + + auto vsyncController = makeMock(options.useNiceMock); + auto vsyncTracker = makeMock(options.useNiceMock); + + EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); + EXPECT_CALL(*vsyncTracker, currentPeriod()) + .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); + EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); + setupScheduler(std::move(vsyncController), std::move(vsyncTracker), std::move(eventThread), + std::move(sfEventThread), DefaultDisplayMode{options.displayId}, + SchedulerCallbackImpl::kNoOp, options.useNiceMock); + } + void resetScheduler(scheduler::Scheduler* scheduler) { mFlinger->mScheduler.reset(scheduler); } scheduler::TestableScheduler& mutableScheduler() { return *mScheduler; } @@ -911,6 +939,11 @@ public: }; private: + template + static std::unique_ptr makeMock(bool useNiceMock) { + return useNiceMock ? std::make_unique>() : std::make_unique(); + } + static constexpr VsyncId kVsyncId{123}; surfaceflinger::test::Factory mFactory; diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index 859f702fe7..c78148faa9 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -33,15 +33,12 @@ #include "FrontEnd/TransactionHandler.h" #include "TestableSurfaceFlinger.h" #include "TransactionState.h" -#include "mock/MockEventThread.h" -#include "mock/MockVsyncController.h" namespace android { using testing::_; using testing::Return; -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; using frontend::TransactionHandler; constexpr nsecs_t TRANSACTION_TIMEOUT = s2ns(5); @@ -52,7 +49,9 @@ public: ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - setupScheduler(); + mFlinger.setupComposer(std::make_unique()); + mFlinger.setupMockScheduler(); + mFlinger.flinger()->addTransactionReadyFilters(); } ~TransactionApplicationTest() { @@ -61,38 +60,8 @@ public: ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); } - void setupScheduler() { - auto eventThread = std::make_unique(); - auto sfEventThread = std::make_unique(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*mVSyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - - mFlinger.setupComposer(std::make_unique()); - mFlinger.setupScheduler(std::unique_ptr(mVsyncController), - std::unique_ptr(mVSyncTracker), - std::move(eventThread), std::move(sfEventThread)); - mFlinger.flinger()->addTransactionReadyFilters(); - } - TestableSurfaceFlinger mFlinger; - mock::VsyncController* mVsyncController = new mock::VsyncController(); - mock::VSyncTracker* mVSyncTracker = new mock::VSyncTracker(); - struct TransactionInfo { Vector states; Vector displays; diff --git a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp index 1173d1c876..764d19be0b 100644 --- a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp @@ -28,15 +28,13 @@ #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockComposer.h" -#include "mock/MockEventThread.h" -#include "mock/MockVsyncController.h" namespace android { using testing::_; using testing::Mock; using testing::Return; -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; + using PresentState = frametimeline::SurfaceFrame::PresentState; class TransactionFrameTracerTest : public testing::Test { @@ -45,7 +43,7 @@ public: const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - setupScheduler(); + mFlinger.setupMockScheduler(); mFlinger.setupComposer(std::make_unique()); mFlinger.setupRenderEngine(std::unique_ptr(mRenderEngine)); } @@ -68,33 +66,6 @@ public: layer->commitTransaction(c); } - void setupScheduler() { - auto eventThread = std::make_unique(); - auto sfEventThread = std::make_unique(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread)); - } - TestableSurfaceFlinger mFlinger; renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine(); diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp index ae03db43a7..e2c64917dc 100644 --- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp @@ -28,15 +28,13 @@ #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockComposer.h" -#include "mock/MockEventThread.h" -#include "mock/MockVsyncController.h" namespace android { using testing::_; using testing::Mock; using testing::Return; -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; + using PresentState = frametimeline::SurfaceFrame::PresentState; class TransactionSurfaceFrameTest : public testing::Test { @@ -45,7 +43,7 @@ public: const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - setupScheduler(); + mFlinger.setupMockScheduler(); mFlinger.setupComposer(std::make_unique()); mFlinger.setupRenderEngine(std::unique_ptr(mRenderEngine)); } @@ -67,33 +65,6 @@ public: layer->commitTransaction(c); } - void setupScheduler() { - auto eventThread = std::make_unique(); - auto sfEventThread = std::make_unique(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread)); - } - TestableSurfaceFlinger mFlinger; renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine(); diff --git a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp index da87f1db17..108151ec65 100644 --- a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp +++ b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp @@ -25,7 +25,6 @@ #include "TestableSurfaceFlinger.h" #include "TunnelModeEnabledReporter.h" #include "mock/DisplayHardware/MockComposer.h" -#include "mock/MockEventThread.h" namespace android { @@ -36,8 +35,6 @@ using testing::Return; using android::Hwc2::IComposer; using android::Hwc2::IComposerClient; -using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; - constexpr int DEFAULT_SIDEBAND_STREAM = 51; struct TestableTunnelModeEnabledListener : public gui::BnTunnelModeEnabledListener { @@ -61,8 +58,6 @@ protected: static constexpr uint32_t HEIGHT = 100; static constexpr uint32_t LAYER_FLAGS = 0; - void setupScheduler(); - void setupComposer(uint32_t virtualDisplayCount); sp createBufferStateLayer(LayerMetadata metadata); TestableSurfaceFlinger mFlinger; @@ -80,7 +75,7 @@ TunnelModeEnabledReporterTest::TunnelModeEnabledReporterTest() { ::testing::UnitTest::GetInstance()->current_test_info(); ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - setupScheduler(); + mFlinger.setupMockScheduler(); mFlinger.setupComposer(std::make_unique()); mFlinger.flinger()->mTunnelModeEnabledReporter = mTunnelModeEnabledReporter; mTunnelModeEnabledReporter->dispatchTunnelModeEnabled(false); @@ -100,33 +95,6 @@ sp TunnelModeEnabledReporterTest::createBufferStateLayer(LayerMetadata me return sp::make(args); } -void TunnelModeEnabledReporterTest::setupScheduler() { - auto eventThread = std::make_unique(); - auto sfEventThread = std::make_unique(); - - EXPECT_CALL(*eventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*eventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(eventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_)); - EXPECT_CALL(*sfEventThread, createEventConnection(_, _)) - .WillOnce(Return(sp::make(sfEventThread.get(), - mock::EventThread::kCallingUid, - ResyncCallback()))); - - auto vsyncController = std::make_unique(); - auto vsyncTracker = std::make_unique(); - - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - EXPECT_CALL(*vsyncTracker, currentPeriod()) - .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)); - EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0)); - mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker), - std::move(eventThread), std::move(sfEventThread)); -} - namespace { TEST_F(TunnelModeEnabledReporterTest, callsAddedListeners) { -- GitLab From e80bd31e90a85f00290096759f3351fa29ca6f38 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Sat, 11 Feb 2023 16:23:34 -0500 Subject: [PATCH 0877/1310] SF: Remove unused property of test DisplayVariant Bug: 241285876 Test: m libsurfaceflinger_unittest Change-Id: If5f4a7b44812a6a9b2fcc5ba423de14596b717f2 Merged-In: If5f4a7b44812a6a9b2fcc5ba423de14596b717f2 (cherry picked from commit 36cface18f0eb2d5fe4d479110695ef053a0f402) --- .../unittests/DisplayTransactionTestHelpers.h | 69 ++++++++----------- 1 file changed, 27 insertions(+), 42 deletions(-) diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h index 66aa2041de..23a8bd1274 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h @@ -158,7 +158,6 @@ constexpr int POWER_MODE_LEET = 1337; // An out of range power mode value #define BOOL_SUBSTITUTE(TYPENAME) enum class TYPENAME : bool { FALSE = false, TRUE = true }; BOOL_SUBSTITUTE(Async); -BOOL_SUBSTITUTE(Critical); BOOL_SUBSTITUTE(Primary); BOOL_SUBSTITUTE(Secure); BOOL_SUBSTITUTE(Virtual); @@ -238,8 +237,8 @@ struct HwcDisplayIdGetter> { // 1) PhysicalDisplayIdType<...> for generated ID of physical display backed by HWC. // 2) HalVirtualDisplayIdType<...> for hard-coded ID of virtual display backed by HWC. // 3) GpuVirtualDisplayIdType for virtual display without HWC backing. -template +template struct DisplayVariant { using DISPLAY_ID = DisplayIdGetter; using CONNECTION_TYPE = DisplayConnectionTypeGetter; @@ -255,9 +254,6 @@ struct DisplayVariant { static constexpr Virtual VIRTUAL = IsPhysicalDisplayId{} ? Virtual::FALSE : Virtual::TRUE; - // When creating native window surfaces for the framebuffer, whether those should be critical - static constexpr Critical CRITICAL = critical; - // When creating native window surfaces for the framebuffer, whether those should be async static constexpr Async ASYNC = async; @@ -486,17 +482,16 @@ constexpr uint32_t GRALLOC_USAGE_PHYSICAL_DISPLAY = constexpr int PHYSICAL_DISPLAY_FLAGS = 0x1; -template +template struct PhysicalDisplayVariant - : DisplayVariant, width, height, critical, - Async::FALSE, Secure::TRUE, PhysicalDisplay::PRIMARY, - GRALLOC_USAGE_PHYSICAL_DISPLAY, PHYSICAL_DISPLAY_FLAGS>, - HwcDisplayVariant< - PhysicalDisplay::HWC_DISPLAY_ID, DisplayType::PHYSICAL, - DisplayVariant, width, height, critical, - Async::FALSE, Secure::TRUE, PhysicalDisplay::PRIMARY, - GRALLOC_USAGE_PHYSICAL_DISPLAY, PHYSICAL_DISPLAY_FLAGS>, - PhysicalDisplay> {}; + : DisplayVariant, width, height, Async::FALSE, + Secure::TRUE, PhysicalDisplay::PRIMARY, GRALLOC_USAGE_PHYSICAL_DISPLAY, + PHYSICAL_DISPLAY_FLAGS>, + HwcDisplayVariant, width, height, + Async::FALSE, Secure::TRUE, PhysicalDisplay::PRIMARY, + GRALLOC_USAGE_PHYSICAL_DISPLAY, PHYSICAL_DISPLAY_FLAGS>, + PhysicalDisplay> {}; template struct PrimaryDisplay { @@ -525,15 +520,9 @@ struct TertiaryDisplay { static constexpr auto GET_IDENTIFICATION_DATA = getExternalEdid; }; -// A primary display is a physical display that is critical -using PrimaryDisplayVariant = - PhysicalDisplayVariant, 3840, 2160, Critical::TRUE>; - -// An external display is physical display that is not critical. -using ExternalDisplayVariant = - PhysicalDisplayVariant, 1920, 1280, Critical::FALSE>; - -using TertiaryDisplayVariant = PhysicalDisplayVariant; +using PrimaryDisplayVariant = PhysicalDisplayVariant, 3840, 2160>; +using ExternalDisplayVariant = PhysicalDisplayVariant, 1920, 1280>; +using TertiaryDisplayVariant = PhysicalDisplayVariant; // A virtual display not supported by the HWC. constexpr uint32_t GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY = 0; @@ -542,12 +531,11 @@ constexpr int VIRTUAL_DISPLAY_FLAGS = 0x0; template struct NonHwcVirtualDisplayVariant - : DisplayVariant { - using Base = DisplayVariant; + : DisplayVariant { + using Base = DisplayVariant; static void injectHwcDisplay(DisplayTransactionTest*) {} @@ -589,17 +577,14 @@ constexpr uint32_t GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY = GRALLOC_USAGE_HW_COMPOSER template struct HwcVirtualDisplayVariant - : DisplayVariant, width, height, Critical::FALSE, Async::TRUE, - secure, Primary::FALSE, GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY, - VIRTUAL_DISPLAY_FLAGS>, - HwcDisplayVariant< - HWC_VIRTUAL_DISPLAY_HWC_DISPLAY_ID, DisplayType::VIRTUAL, - DisplayVariant, width, height, Critical::FALSE, - Async::TRUE, secure, Primary::FALSE, - GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY, VIRTUAL_DISPLAY_FLAGS>> { - using Base = DisplayVariant, width, height, Critical::FALSE, - Async::TRUE, secure, Primary::FALSE, GRALLOC_USAGE_HW_COMPOSER, - VIRTUAL_DISPLAY_FLAGS>; + : DisplayVariant, width, height, Async::TRUE, secure, + Primary::FALSE, GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY, VIRTUAL_DISPLAY_FLAGS>, + HwcDisplayVariant, width, height, Async::TRUE, + secure, Primary::FALSE, GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY, + VIRTUAL_DISPLAY_FLAGS>> { + using Base = DisplayVariant, width, height, Async::TRUE, secure, + Primary::FALSE, GRALLOC_USAGE_HW_COMPOSER, VIRTUAL_DISPLAY_FLAGS>; using Self = HwcVirtualDisplayVariant; static std::shared_ptr injectCompositionDisplay( -- GitLab From f4af03ed9592dae0f2355dfff92c3c75c1b980d4 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Sat, 11 Feb 2023 00:25:24 +0000 Subject: [PATCH 0878/1310] Add CachingHint into SurfaceFlinger Some system layers may need to be excluded from surfaceflinger caching for a variety of reasons, including power or image quality considerations. This provides a mechanism for those system layers to never be cached on the GPU. Bug: 259311918 Test: libcompositionengine_test Test: run test app and inspect planner state Change-Id: I35418ad5a41cb2546e3b27b0af290bf3c31a0180 --- libs/gui/LayerState.cpp | 10 ++++ libs/gui/SurfaceComposerClient.cpp | 14 ++++++ libs/gui/aidl/android/gui/CachingHint.aidl | 30 ++++++++++++ libs/gui/include/gui/ISurfaceComposer.h | 1 + libs/gui/include/gui/LayerState.h | 7 ++- libs/gui/include/gui/SurfaceComposerClient.h | 1 + .../LayerFECompositionState.h | 2 + .../impl/planner/CachedSet.h | 3 ++ .../impl/planner/LayerState.h | 19 +++++-- .../src/LayerFECompositionState.cpp | 1 + .../src/planner/CachedSet.cpp | 12 +++++ .../src/planner/Flattener.cpp | 4 +- .../tests/planner/CachedSetTest.cpp | 14 ++++++ .../tests/planner/FlattenerTest.cpp | 49 +++++++++++++++++++ .../tests/planner/LayerStateTest.cpp | 39 +++++++++++++++ .../FrontEnd/LayerSnapshotBuilder.cpp | 1 + .../FrontEnd/RequestedLayerState.cpp | 1 + services/surfaceflinger/Layer.cpp | 17 ++++++- services/surfaceflinger/Layer.h | 3 ++ services/surfaceflinger/LayerFE.h | 1 + services/surfaceflinger/SurfaceFlinger.cpp | 5 ++ 21 files changed, 225 insertions(+), 9 deletions(-) create mode 100644 libs/gui/aidl/android/gui/CachingHint.aidl diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index a6276e500c..f6bba16899 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -189,6 +189,7 @@ status_t layer_state_t::write(Parcel& output) const SAFE_PARCEL(output.writeParcelable, trustedPresentationListener); SAFE_PARCEL(output.writeFloat, currentSdrHdrRatio); SAFE_PARCEL(output.writeFloat, desiredSdrHdrRatio); + SAFE_PARCEL(output.writeInt32, static_cast(cachingHint)) return NO_ERROR; } @@ -328,6 +329,10 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(input.readFloat, &tmpFloat); desiredSdrHdrRatio = tmpFloat; + int32_t tmpInt32; + SAFE_PARCEL(input.readInt32, &tmpInt32); + cachingHint = static_cast(tmpInt32); + return NO_ERROR; } @@ -580,6 +585,10 @@ void layer_state_t::merge(const layer_state_t& other) { desiredSdrHdrRatio = other.desiredSdrHdrRatio; currentSdrHdrRatio = other.currentSdrHdrRatio; } + if (other.what & eCachingHintChanged) { + what |= eCachingHintChanged; + cachingHint = other.cachingHint; + } if (other.what & eHdrMetadataChanged) { what |= eHdrMetadataChanged; hdrMetadata = other.hdrMetadata; @@ -731,6 +740,7 @@ uint64_t layer_state_t::diff(const layer_state_t& other) const { CHECK_DIFF(diff, eDataspaceChanged, other, dataspace); CHECK_DIFF2(diff, eExtendedRangeBrightnessChanged, other, currentSdrHdrRatio, desiredSdrHdrRatio); + CHECK_DIFF(diff, eCachingHintChanged, other, cachingHint); CHECK_DIFF(diff, eHdrMetadataChanged, other, hdrMetadata); if (other.what & eSurfaceDamageRegionChanged && (!surfaceDamageRegion.hasSameRects(other.surfaceDamageRegion))) { diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index c5089173bd..001d475823 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -1729,6 +1729,20 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setExten return *this; } +SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setCachingHint( + const sp& sc, gui::CachingHint cachingHint) { + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + s->what |= layer_state_t::eCachingHintChanged; + s->cachingHint = cachingHint; + + registerSurfaceControlForCallback(sc); + return *this; +} + SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setHdrMetadata( const sp& sc, const HdrMetadata& hdrMetadata) { layer_state_t* s = getLayerState(sc); diff --git a/libs/gui/aidl/android/gui/CachingHint.aidl b/libs/gui/aidl/android/gui/CachingHint.aidl new file mode 100644 index 0000000000..b35c79547f --- /dev/null +++ b/libs/gui/aidl/android/gui/CachingHint.aidl @@ -0,0 +1,30 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +/* + * Hint for configuring caching behavior for a layer + * @hide + */ +@Backing(type="int") +enum CachingHint { + // Caching is disabled. A layer may explicitly disable caching for + // improving image quality for some scenes. + Disabled = 0, + // Caching is enabled. A layer is cacheable by default. + Enabled = 1 +} diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index ae56f9fdb5..1e67225a4e 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include #include diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index ddaf473855..da144bd191 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -172,7 +172,7 @@ struct layer_state_t { eFlagsChanged = 0x00000040, eLayerStackChanged = 0x00000080, eFlushJankData = 0x00000100, - /* unused = 0x00000200, */ + eCachingHintChanged = 0x00000200, eDimmingEnabledChanged = 0x00000400, eShadowRadiusChanged = 0x00000800, eRenderBorderChanged = 0x00001000, @@ -211,7 +211,8 @@ struct layer_state_t { eStretchChanged = 0x2000'00000000, eTrustedOverlayChanged = 0x4000'00000000, eDropInputModeChanged = 0x8000'00000000, - eExtendedRangeBrightnessChanged = 0x10000'00000000 + eExtendedRangeBrightnessChanged = 0x10000'00000000, + }; layer_state_t(); @@ -391,6 +392,8 @@ struct layer_state_t { float currentSdrHdrRatio = 1.f; float desiredSdrHdrRatio = 1.f; + gui::CachingHint cachingHint = gui::CachingHint::Enabled; + TrustedPresentationThresholds trustedPresentationThresholds; TrustedPresentationListener trustedPresentationListener; }; diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 44e78ec0c4..d431b4381a 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -566,6 +566,7 @@ public: Transaction& setDataspace(const sp& sc, ui::Dataspace dataspace); Transaction& setExtendedRangeBrightness(const sp& sc, float currentBufferRatio, float desiredRatio); + Transaction& setCachingHint(const sp& sc, gui::CachingHint cachingHint); Transaction& setHdrMetadata(const sp& sc, const HdrMetadata& hdrMetadata); Transaction& setSurfaceDamageRegion(const sp& sc, const Region& surfaceDamageRegion); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h index 32684fc3b7..0b4d20c660 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h @@ -18,6 +18,7 @@ #include +#include #include #include #include @@ -213,6 +214,7 @@ struct LayerFECompositionState { float desiredSdrHdrRatio = 1.f; bool isInternalDisplayOverlay = false; + gui::CachingHint cachingHint = gui::CachingHint::Enabled; virtual ~LayerFECompositionState(); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h index 24a7744542..d26ca9dd00 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h @@ -149,6 +149,9 @@ public: bool hasSolidColorLayers() const; + // True if any layer in this cached set has CachingHint::Disabled + bool cachingHintExcludesLayers() const; + private: const NonBufferHash mFingerprint; std::chrono::steady_clock::time_point mLastUpdate = std::chrono::steady_clock::now(); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h index e309442220..d5c488e40e 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h @@ -73,6 +73,7 @@ enum class LayerStateField : uint32_t { BackgroundBlurRadius = 1u << 17, BlurRegions = 1u << 18, HasProtectedContent = 1u << 19, + CachingHint = 1u << 20, }; // clang-format on @@ -250,6 +251,8 @@ public: bool isProtected() const { return mIsProtected.get(); } + gui::CachingHint getCachingHint() const { return mCachingHint.get(); } + bool hasSolidColorCompositionType() const { return getOutputLayer()->getLayerFE().getCompositionState()->compositionType == aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR; @@ -487,7 +490,15 @@ private: return layer->getLayerFE().getCompositionState()->hasProtectedContent; }}; - static const constexpr size_t kNumNonUniqueFields = 18; + OutputLayerState + mCachingHint{[](auto layer) { + return layer->getLayerFE().getCompositionState()->cachingHint; + }, + [](const gui::CachingHint& cachingHint) { + return std::vector{toString(cachingHint)}; + }}; + + static const constexpr size_t kNumNonUniqueFields = 19; std::array getNonUniqueFields() { std::array constFields = @@ -501,13 +512,11 @@ private: } std::array getNonUniqueFields() const { - return { - &mDisplayFrame, &mSourceCrop, &mBufferTransform, &mBlendMode, + return {&mDisplayFrame, &mSourceCrop, &mBufferTransform, &mBlendMode, &mAlpha, &mLayerMetadata, &mVisibleRegion, &mOutputDataspace, &mPixelFormat, &mColorTransform, &mCompositionType, &mSidebandStream, &mBuffer, &mSolidColor, &mBackgroundBlurRadius, &mBlurRegions, - &mFrameNumber, &mIsProtected, - }; + &mFrameNumber, &mIsProtected, &mCachingHint}; } }; diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp index 531b65963d..615d04b460 100644 --- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp +++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp @@ -126,6 +126,7 @@ void LayerFECompositionState::dump(std::string& out) const { dumpVal(out, "desired sdr/hdr ratio", desiredSdrHdrRatio); } dumpVal(out, "colorTransform", colorTransform); + dumpVal(out, "caching hint", toString(cachingHint)); out.append("\n"); } diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp index ed9a88d3c4..a00ce57e29 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp @@ -393,6 +393,18 @@ bool CachedSet::hasSolidColorLayers() const { }); } +bool CachedSet::cachingHintExcludesLayers() const { + const bool shouldExcludeLayers = + std::any_of(mLayers.cbegin(), mLayers.cend(), [](const Layer& layer) { + return layer.getState()->getCachingHint() == gui::CachingHint::Disabled; + }); + + LOG_ALWAYS_FATAL_IF(shouldExcludeLayers && getLayerCount() > 1, + "CachedSet is invalid: should be excluded but contains %zu layers", + getLayerCount()); + return shouldExcludeLayers; +} + void CachedSet::dump(std::string& result) const { const auto now = std::chrono::steady_clock::now(); diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp index 9175dd01a1..13b6307aea 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp @@ -413,6 +413,7 @@ std::vector Flattener::findCandidateRuns(time_point now) const { for (auto currentSet = mLayers.cbegin(); currentSet != mLayers.cend(); ++currentSet) { bool layerIsInactive = now - currentSet->getLastUpdate() > mTunables.mActiveLayerTimeout; const bool layerHasBlur = currentSet->hasBlurBehind(); + const bool layerDeniedFromCaching = currentSet->cachingHintExcludesLayers(); // Layers should also be considered inactive whenever their framerate is lower than 1fps. if (!layerIsInactive && currentSet->getLayerCount() == kNumLayersFpsConsideration) { @@ -424,7 +425,8 @@ std::vector Flattener::findCandidateRuns(time_point now) const { } } - if (layerIsInactive && (firstLayer || runHasFirstLayer || !layerHasBlur) && + if (!layerDeniedFromCaching && layerIsInactive && + (firstLayer || runHasFirstLayer || !layerHasBlur) && !currentSet->hasUnsupportedDataspace()) { if (isPartOfRun) { builder.increment(); diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp index d5d688e705..ca5ba69e8e 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp @@ -577,6 +577,20 @@ TEST_F(CachedSetTest, rendersWithOffsetFramebufferContent) { cachedSet.append(CachedSet(layer3)); } +TEST_F(CachedSetTest, cachingHintIncludesLayersByDefault) { + CachedSet cachedSet(*mTestLayers[0]->cachedSetLayer.get()); + EXPECT_FALSE(cachedSet.cachingHintExcludesLayers()); +} + +TEST_F(CachedSetTest, cachingHintExcludesLayersWhenDisabled) { + CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get(); + mTestLayers[0]->layerFECompositionState.cachingHint = gui::CachingHint::Disabled; + mTestLayers[0]->layerState->update(&mTestLayers[0]->outputLayer); + + CachedSet cachedSet(layer1); + EXPECT_TRUE(cachedSet.cachingHintExcludesLayers()); +} + TEST_F(CachedSetTest, holePunch_requiresBuffer) { CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get(); auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState; diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp index 86cfee6f0a..778a0a8c93 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp @@ -1112,6 +1112,55 @@ TEST_F(FlattenerRenderSchedulingTest, flattenLayers_renderCachedSets_defersUpToM true); } +TEST_F(FlattenerTest, flattenLayers_skipsLayersDisabledFromCaching) { + auto& layerState1 = mTestLayers[0]->layerState; + const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; + + auto& layerState2 = mTestLayers[1]->layerState; + const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer; + + // The third layer has a CachingHint that prevents caching from running + auto& layerState3 = mTestLayers[2]->layerState; + const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer; + mTestLayers[2]->layerFECompositionState.cachingHint = gui::CachingHint::Disabled; + mTestLayers[2]->layerState->update(&mTestLayers[2]->outputLayer); + + const std::vector layers = { + layerState1.get(), + layerState2.get(), + layerState3.get(), + }; + + initializeFlattener(layers); + + mTime += 200ms; + initializeOverrideBuffer(layers); + EXPECT_EQ(getNonBufferHash(layers), + mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); + + // This will render a CachedSet. + EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)) + .WillOnce(Return(ByMove(ftl::yield(Fence::NO_FENCE)))); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); + + // We've rendered a CachedSet, but we haven't merged it in. + EXPECT_EQ(nullptr, overrideBuffer1); + EXPECT_EQ(nullptr, overrideBuffer2); + EXPECT_EQ(nullptr, overrideBuffer3); + + // This time we merge the CachedSet in, so we have a new hash, and we should + // only have two sets. + EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0); + initializeOverrideBuffer(layers); + EXPECT_NE(getNonBufferHash(layers), + mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime)); + mFlattener->renderCachedSets(mOutputState, std::nullopt, true); + + EXPECT_NE(nullptr, overrideBuffer1); + EXPECT_EQ(overrideBuffer1, overrideBuffer2); + EXPECT_EQ(nullptr, overrideBuffer3); +} + TEST_F(FlattenerTest, flattenLayers_skipsBT601_625) { auto& layerState1 = mTestLayers[0]->layerState; const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer; diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp index 47b682059f..044917ead9 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp @@ -994,6 +994,45 @@ TEST_F(LayerStateTest, hasBlurBehind_withBlurRegion_returnsTrue) { EXPECT_TRUE(mLayerState->hasBlurBehind()); } +TEST_F(LayerStateTest, updateCachingHint) { + OutputLayerCompositionState outputLayerCompositionState; + LayerFECompositionState layerFECompositionState; + layerFECompositionState.cachingHint = gui::CachingHint::Enabled; + setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, + layerFECompositionState); + mLayerState = std::make_unique(&mOutputLayer); + + mock::OutputLayer newOutputLayer; + sp newLayerFE = sp::make(); + LayerFECompositionState layerFECompositionStateTwo; + layerFECompositionStateTwo.cachingHint = gui::CachingHint::Disabled; + setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState, + layerFECompositionStateTwo); + ftl::Flags updates = mLayerState->update(&newOutputLayer); + EXPECT_EQ(ftl::Flags(LayerStateField::CachingHint), updates); +} + +TEST_F(LayerStateTest, compareCachingHint) { + OutputLayerCompositionState outputLayerCompositionState; + LayerFECompositionState layerFECompositionState; + layerFECompositionState.cachingHint = gui::CachingHint::Enabled; + setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState, + layerFECompositionState); + mLayerState = std::make_unique(&mOutputLayer); + mock::OutputLayer newOutputLayer; + sp newLayerFE = sp::make(); + LayerFECompositionState layerFECompositionStateTwo; + layerFECompositionStateTwo.cachingHint = gui::CachingHint::Disabled; + setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState, + layerFECompositionStateTwo); + auto otherLayerState = std::make_unique(&newOutputLayer); + + verifyNonUniqueDifferingFields(*mLayerState, *otherLayerState, LayerStateField::CachingHint); + + EXPECT_TRUE(mLayerState->compare(*otherLayerState)); + EXPECT_TRUE(otherLayerState->compare(*mLayerState)); +} + TEST_F(LayerStateTest, dumpDoesNotCrash) { OutputLayerCompositionState outputLayerCompositionState; LayerFECompositionState layerFECompositionState; diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index c9aeb24cf5..d740350cc5 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -727,6 +727,7 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a snapshot.dimmingEnabled = requested.dimmingEnabled; snapshot.layerOpaqueFlagSet = (requested.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque; + snapshot.cachingHint = requested.cachingHint; } if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Content)) { diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index 28340844a3..09523d3ea3 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -137,6 +137,7 @@ RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args) dataspace = ui::Dataspace::V0_SRGB; gameMode = gui::GameMode::Unsupported; requestedFrameRate = {}; + cachingHint = gui::CachingHint::Enabled; } void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerState) { diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 084d9b93db..dac09169c8 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -566,6 +566,7 @@ void Layer::prepareBasicGeometryCompositionState() { : Hwc2::IComposerClient::BlendMode::COVERAGE; } + // Please keep in sync with LayerSnapshotBuilder auto* snapshot = editLayerSnapshot(); snapshot->outputFilter = getOutputFilter(); snapshot->isVisible = isVisible(); @@ -592,6 +593,7 @@ void Layer::prepareGeometryCompositionState() { const auto& drawingState{getDrawingState()}; auto* snapshot = editLayerSnapshot(); + // Please keep in sync with LayerSnapshotBuilder snapshot->geomBufferSize = getBufferSize(drawingState); snapshot->geomContentCrop = getBufferCrop(); snapshot->geomCrop = getCrop(drawingState); @@ -624,6 +626,7 @@ void Layer::prepareGeometryCompositionState() { void Layer::preparePerFrameCompositionState() { const auto& drawingState{getDrawingState()}; + // Please keep in sync with LayerSnapshotBuilder auto* snapshot = editLayerSnapshot(); snapshot->forceClientComposition = false; @@ -637,6 +640,7 @@ void Layer::preparePerFrameCompositionState() { snapshot->dimmingEnabled = isDimmingEnabled(); snapshot->currentSdrHdrRatio = getCurrentSdrHdrRatio(); snapshot->desiredSdrHdrRatio = getDesiredSdrHdrRatio(); + snapshot->cachingHint = getCachingHint(); const bool usesRoundedCorners = hasRoundedCorners(); @@ -666,8 +670,9 @@ void Layer::preparePerFrameCompositionState() { } void Layer::preparePerFrameBufferCompositionState() { - // Sideband layers + // Please keep in sync with LayerSnapshotBuilder auto* snapshot = editLayerSnapshot(); + // Sideband layers if (snapshot->sidebandStream.get() && !snapshot->sidebandStreamHasFrame) { snapshot->compositionType = aidl::android::hardware::graphics::composer3::Composition::SIDEBAND; @@ -690,6 +695,7 @@ void Layer::preparePerFrameBufferCompositionState() { } void Layer::preparePerFrameEffectsCompositionState() { + // Please keep in sync with LayerSnapshotBuilder auto* snapshot = editLayerSnapshot(); snapshot->color = getColor(); snapshot->compositionType = @@ -698,6 +704,7 @@ void Layer::preparePerFrameEffectsCompositionState() { void Layer::prepareCursorCompositionState() { const State& drawingState{getDrawingState()}; + // Please keep in sync with LayerSnapshotBuilder auto* snapshot = editLayerSnapshot(); // Apply the layer's transform, followed by the display's global transform @@ -3091,6 +3098,14 @@ bool Layer::setExtendedRangeBrightness(float currentBufferRatio, float desiredRa return true; } +bool Layer::setCachingHint(gui::CachingHint cachingHint) { + if (mDrawingState.cachingHint == cachingHint) return false; + mDrawingState.cachingHint = cachingHint; + mDrawingState.modified = true; + setTransactionFlags(eTransactionNeeded); + return true; +} + bool Layer::setHdrMetadata(const HdrMetadata& hdrMetadata) { if (mDrawingState.hdrMetadata == hdrMetadata) return false; mDrawingState.hdrMetadata = hdrMetadata; diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 2955dafa0f..7d40774f83 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -227,6 +227,7 @@ public: bool dimmingEnabled = true; float currentSdrHdrRatio = 1.f; float desiredSdrHdrRatio = 1.f; + gui::CachingHint cachingHint = gui::CachingHint::Enabled; }; explicit Layer(const LayerCreationArgs& args); @@ -296,6 +297,7 @@ public: virtual bool isDimmingEnabled() const { return getDrawingState().dimmingEnabled; } float getDesiredSdrHdrRatio() const { return getDrawingState().desiredSdrHdrRatio; } float getCurrentSdrHdrRatio() const { return getDrawingState().currentSdrHdrRatio; } + gui::CachingHint getCachingHint() const { return getDrawingState().cachingHint; } bool setTransform(uint32_t /*transform*/); bool setTransformToDisplayInverse(bool /*transformToDisplayInverse*/); @@ -305,6 +307,7 @@ public: std::optional /* dequeueTime */, const FrameTimelineInfo& /*info*/); bool setDataspace(ui::Dataspace /*dataspace*/); bool setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio); + bool setCachingHint(gui::CachingHint cachingHint); bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/); bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/); bool setApi(int32_t /*api*/); diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h index 01da0199a2..c23bd31d1a 100644 --- a/services/surfaceflinger/LayerFE.h +++ b/services/surfaceflinger/LayerFE.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include "FrontEnd/LayerSnapshot.h" #include "compositionengine/LayerFE.h" diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 5f95859fd3..5c8579cb4d 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4783,6 +4783,11 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime flags |= eTraversalNeeded; } } + if (what & layer_state_t::eCachingHintChanged) { + if (layer->setCachingHint(s.cachingHint)) { + flags |= eTraversalNeeded; + } + } if (what & layer_state_t::eHdrMetadataChanged) { if (layer->setHdrMetadata(s.hdrMetadata)) flags |= eTraversalNeeded; } -- GitLab From c68c61aefed358d4dc63b442ff4dd449d2c66b8f Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Thu, 9 Feb 2023 17:35:36 +0000 Subject: [PATCH 0879/1310] Pass the max desired hdr/sdr ratio to DisplayManager SurfaceFlinger already computes this, but DisplayManager needs to use this for apps that don't want as large of a ratio than the system supports. Bug: 267350616 Test: SilkFX test app Change-Id: I03b6b458e220a9fdc30eb137a7455101d5a8f51a --- .../aidl/android/gui/IHdrLayerInfoListener.aidl | 6 ++++-- .../surfaceflinger/HdrLayerInfoReporter.cpp | 3 ++- services/surfaceflinger/HdrLayerInfoReporter.h | 17 ++++------------- services/surfaceflinger/SurfaceFlinger.cpp | 7 ++++++- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/libs/gui/aidl/android/gui/IHdrLayerInfoListener.aidl b/libs/gui/aidl/android/gui/IHdrLayerInfoListener.aidl index fc809c4c88..e8c36ee001 100644 --- a/libs/gui/aidl/android/gui/IHdrLayerInfoListener.aidl +++ b/libs/gui/aidl/android/gui/IHdrLayerInfoListener.aidl @@ -19,7 +19,9 @@ package android.gui; /** @hide */ oneway interface IHdrLayerInfoListener { // Callback with the total number of HDR layers, the dimensions of the largest layer, - // and a placeholder flags + // a placeholder flags, and the max desired HDR/SDR ratio. The max desired HDR/SDR + // ratio may be positive infinity to indicate an unbounded ratio. // TODO (b/182312559): Define the flags (likely need an indicator that a UDFPS layer is present) - void onHdrLayerInfoChanged(int numberOfHdrLayers, int maxW, int maxH, int flags); + void onHdrLayerInfoChanged(int numberOfHdrLayers, int maxW, int maxH, + int flags, float maxDesiredHdrSdrRatio); } \ No newline at end of file diff --git a/services/surfaceflinger/HdrLayerInfoReporter.cpp b/services/surfaceflinger/HdrLayerInfoReporter.cpp index c88554ee52..9eefbe463d 100644 --- a/services/surfaceflinger/HdrLayerInfoReporter.cpp +++ b/services/surfaceflinger/HdrLayerInfoReporter.cpp @@ -40,7 +40,8 @@ void HdrLayerInfoReporter::dispatchHdrLayerInfo(const HdrLayerInfo& info) { for (const auto& listener : toInvoke) { ATRACE_NAME("invoking onHdrLayerInfoChanged"); - listener->onHdrLayerInfoChanged(info.numberOfHdrLayers, info.maxW, info.maxH, info.flags); + listener->onHdrLayerInfoChanged(info.numberOfHdrLayers, info.maxW, info.maxH, info.flags, + info.maxDesiredHdrSdrRatio); } } diff --git a/services/surfaceflinger/HdrLayerInfoReporter.h b/services/surfaceflinger/HdrLayerInfoReporter.h index 9b70c164de..bf7c7753d2 100644 --- a/services/surfaceflinger/HdrLayerInfoReporter.h +++ b/services/surfaceflinger/HdrLayerInfoReporter.h @@ -43,27 +43,18 @@ public: // With peak display brightnesses exceeding 1,000 nits currently, HLG's request could // actually be satisfied in some ambient conditions such that limiting that max for that // content in theory makes sense - float maxDesiredSdrHdrRatio = 0.f; + float maxDesiredHdrSdrRatio = 0.f; bool operator==(const HdrLayerInfo& other) const { return numberOfHdrLayers == other.numberOfHdrLayers && maxW == other.maxW && - maxH == other.maxH && flags == other.flags; + maxH == other.maxH && flags == other.flags && + maxDesiredHdrSdrRatio == other.maxDesiredHdrSdrRatio; } bool operator!=(const HdrLayerInfo& other) const { return !(*this == other); } void mergeDesiredRatio(float update) { - if (maxDesiredSdrHdrRatio == 0.f) { - // If nothing is set, take the incoming value - maxDesiredSdrHdrRatio = update; - } else if (update == 1.f) { - // If the request is to "go to max", then take it regardless - maxDesiredSdrHdrRatio = 1.f; - } else if (maxDesiredSdrHdrRatio != 1.f) { - // If we're not currently asked to "go to max", then take the max - // of the incoming requests - maxDesiredSdrHdrRatio = std::max(maxDesiredSdrHdrRatio, update); - } + maxDesiredHdrSdrRatio = std::max(maxDesiredHdrSdrRatio, update); } }; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 5f95859fd3..b67188b1e0 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2800,7 +2800,12 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { const auto* outputLayer = compositionDisplay->getOutputLayerForLayer(layerFe); if (outputLayer) { - info.mergeDesiredRatio(snapshot.desiredSdrHdrRatio); + // TODO(b/267350616): Rename SdrHdrRatio -> HdrSdrRatio + // everywhere + const float desiredHdrSdrRatio = snapshot.desiredSdrHdrRatio <= 1.f + ? std::numeric_limits::infinity() + : snapshot.desiredSdrHdrRatio; + info.mergeDesiredRatio(desiredHdrSdrRatio); info.numberOfHdrLayers++; const auto displayFrame = outputLayer->getState().displayFrame; const int32_t area = displayFrame.width() * displayFrame.height(); -- GitLab From dfa3abbb6207b1b7c1c741349a9e110fae46b437 Mon Sep 17 00:00:00 2001 From: Jack Wu Date: Sun, 19 Feb 2023 01:30:54 +0800 Subject: [PATCH 0880/1310] Revert "batteryservice: add BATTERY_PROP_STATE_OF_HEALTH" This reverts commit ea1262381219d577bf5772cff2f474eebe8ce6d4. Reason for revert: merge to aosp first Bug: 251425963 Test: m Change-Id: Ia5a020861425849e347d850dda8ba9793709e632 Signed-off-by: Jack Wu --- services/batteryservice/include/batteryservice/BatteryService.h | 1 - 1 file changed, 1 deletion(-) diff --git a/services/batteryservice/include/batteryservice/BatteryService.h b/services/batteryservice/include/batteryservice/BatteryService.h index bf6189d7af..a2e4115bd6 100644 --- a/services/batteryservice/include/batteryservice/BatteryService.h +++ b/services/batteryservice/include/batteryservice/BatteryService.h @@ -37,7 +37,6 @@ enum { BATTERY_PROP_CHARGING_POLICY = 7, // equals BATTERY_PROPERTY_CHARGING_POLICY BATTERY_PROP_MANUFACTURING_DATE = 8, // equals BATTERY_PROPERTY_MANUFACTURING_DATE BATTERY_PROP_FIRST_USAGE_DATE = 9, // equals BATTERY_PROPERTY_FIRST_USAGE_DATE - BATTERY_PROP_STATE_OF_HEALTH = 10, // equals BATTERY_PROPERTY_STATE_OF_HEALTH }; struct BatteryProperties { -- GitLab From 2b1037b454951031c1c1fac7bca2252190d42646 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Sat, 11 Feb 2023 16:45:36 -0500 Subject: [PATCH 0881/1310] SF: Clean up FakeDisplayDeviceInjector TODO Split the double-duty setDisplayModes API. Bug: 241285876 Test: libsurfaceflinger_unittest Change-Id: Id52a824d22c9a89b650a9c50d0093b95ecfd9123 Merged-In: Id52a824d22c9a89b650a9c50d0093b95ecfd9123 (cherry picked from commit aa73cf5ade78f45dac0ba6feb03f0501b7b80ead) --- .../Scheduler/RefreshRateSelector.h | 3 ++- .../SurfaceFlinger_DisplayModeSwitching.cpp | 2 +- .../tests/unittests/TestableSurfaceFlinger.h | 16 ++++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h index 4f5842a67a..5052e6e257 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h @@ -32,7 +32,6 @@ #include #include "DisplayHardware/DisplayMode.h" -#include "DisplayHardware/HWComposer.h" #include "Scheduler/OneShotTimer.h" #include "Scheduler/StrongTyping.h" #include "ThreadContext.h" @@ -297,6 +296,8 @@ public: RefreshRateSelector(const RefreshRateSelector&) = delete; RefreshRateSelector& operator=(const RefreshRateSelector&) = delete; + const DisplayModes& displayModes() const { return mDisplayModes; } + // Returns whether switching modes (refresh rate or resolution) is possible. // TODO(b/158780872): Consider HAL support, and skip frame rate detection if the modes only // differ in resolution. Once Config::FrameRateOverride::Enabled becomes the default, diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp index f436a58238..019502f0fd 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp @@ -50,7 +50,7 @@ public: mFlinger.configureAndCommit(); mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this) - .setDisplayModes(kModes, kModeId60, std::move(selectorPtr)) + .setRefreshRateSelector(std::move(selectorPtr)) .inject(); // isVsyncPeriodSwitchSupported should return true, otherwise the SF's HWC proxy diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index dbde3b16d2..8703359906 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -801,16 +801,16 @@ public: return mFlinger.mutableDisplays().get(mDisplayToken)->get(); } - // If `selectorPtr` is nullptr, the injector creates RefreshRateSelector from the `modes`. - // Otherwise, it uses `selectorPtr`, which the caller must create using the same `modes`. - // - // TODO(b/182939859): Once `modes` can be retrieved from RefreshRateSelector, remove - // the `selectorPtr` parameter in favor of an alternative setRefreshRateSelector API. - auto& setDisplayModes( - DisplayModes modes, DisplayModeId activeModeId, - std::shared_ptr selectorPtr = nullptr) { + auto& setDisplayModes(DisplayModes modes, DisplayModeId activeModeId) { mDisplayModes = std::move(modes); mCreationArgs.activeModeId = activeModeId; + mCreationArgs.refreshRateSelector = nullptr; + return *this; + } + + auto& setRefreshRateSelector(RefreshRateSelectorPtr selectorPtr) { + mDisplayModes = selectorPtr->displayModes(); + mCreationArgs.activeModeId = selectorPtr->getActiveMode().modePtr->getId(); mCreationArgs.refreshRateSelector = std::move(selectorPtr); return *this; } -- GitLab From 06e5db05b0fbb9c6852846f0844f2c1b42edba27 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Sat, 11 Feb 2023 16:55:47 -0500 Subject: [PATCH 0882/1310] SF: Rewrite test for leader display designation Bug: 259436835 Fixes: 262417075 Test: MultiDisplayLeaderTest.foldable Change-Id: Icfe2bf1dedcfbdd09f98caf6db24c6b121afbf1c --- .../surfaceflinger/tests/unittests/Android.bp | 1 + .../unittests/DisplayTransactionTestHelpers.h | 21 +++-- .../SurfaceFlinger_MultiDisplayLeaderTest.cpp | 81 +++++++++++++++++++ ...urfaceFlinger_SetPowerModeInternalTest.cpp | 33 -------- 4 files changed, 98 insertions(+), 38 deletions(-) create mode 100644 services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayLeaderTest.cpp diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index 87c3c65a74..a5b508acea 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -105,6 +105,7 @@ cc_test { "SurfaceFlinger_DisplayTransactionCommitTest.cpp", "SurfaceFlinger_GetDisplayNativePrimariesTest.cpp", "SurfaceFlinger_HotplugTest.cpp", + "SurfaceFlinger_MultiDisplayLeaderTest.cpp", "SurfaceFlinger_NotifyPowerBoostTest.cpp", "SurfaceFlinger_OnInitializeDisplaysTest.cpp", "SurfaceFlinger_PowerHintTest.cpp", diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h index 23a8bd1274..57d1d9c4d2 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h @@ -503,14 +503,16 @@ struct PrimaryDisplay { static constexpr auto GET_IDENTIFICATION_DATA = getInternalEdid; }; -template -struct ExternalDisplay { - static constexpr auto CONNECTION_TYPE = ui::DisplayConnectionType::External; +template +struct SecondaryDisplay { + static constexpr auto CONNECTION_TYPE = connectionType; static constexpr Primary PRIMARY = Primary::FALSE; static constexpr uint8_t PORT = 254; static constexpr HWDisplayId HWC_DISPLAY_ID = 1002; static constexpr bool HAS_IDENTIFICATION_DATA = hasIdentificationData; - static constexpr auto GET_IDENTIFICATION_DATA = getExternalEdid; + static constexpr auto GET_IDENTIFICATION_DATA = + connectionType == ui::DisplayConnectionType::Internal ? getInternalEdid + : getExternalEdid; }; struct TertiaryDisplay { @@ -521,7 +523,16 @@ struct TertiaryDisplay { }; using PrimaryDisplayVariant = PhysicalDisplayVariant, 3840, 2160>; -using ExternalDisplayVariant = PhysicalDisplayVariant, 1920, 1280>; + +using InnerDisplayVariant = PhysicalDisplayVariant, 1840, 2208>; +using OuterDisplayVariant = + PhysicalDisplayVariant, 1080, + 2092>; + +using ExternalDisplayVariant = + PhysicalDisplayVariant, 1920, + 1280>; + using TertiaryDisplayVariant = PhysicalDisplayVariant; // A virtual display not supported by the HWC. diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayLeaderTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayLeaderTest.cpp new file mode 100644 index 0000000000..9c5894306c --- /dev/null +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayLeaderTest.cpp @@ -0,0 +1,81 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "LibSurfaceFlingerUnittests" + +#include "DisplayTransactionTestHelpers.h" + +#include +#include + +namespace android { +namespace { + +struct MultiDisplayLeaderTest : DisplayTransactionTest { + static constexpr bool kWithMockScheduler = false; + MultiDisplayLeaderTest() : DisplayTransactionTest(kWithMockScheduler) {} +}; + +TEST_F(MultiDisplayLeaderTest, foldable) { + injectMockScheduler(InnerDisplayVariant::DISPLAY_ID::get()); + + // Inject inner and outer displays with uninitialized power modes. + sp innerDisplay, outerDisplay; + constexpr bool kInitPowerMode = false; + { + InnerDisplayVariant::injectHwcDisplay(this); + auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this); + injector.setPowerMode(std::nullopt); + injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector()); + innerDisplay = injector.inject(); + } + { + OuterDisplayVariant::injectHwcDisplay(this); + auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this); + injector.setPowerMode(std::nullopt); + outerDisplay = injector.inject(); + } + + // When the device boots, the inner display should be the leader. + ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), innerDisplay->getPhysicalId()); + + // ...and should still be after powering on. + mFlinger.setPowerModeInternal(innerDisplay, PowerMode::ON); + ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), innerDisplay->getPhysicalId()); + + // The outer display should become the leader after folding. + mFlinger.setPowerModeInternal(innerDisplay, PowerMode::OFF); + mFlinger.setPowerModeInternal(outerDisplay, PowerMode::ON); + ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), outerDisplay->getPhysicalId()); + + // The inner display should become the leader after unfolding. + mFlinger.setPowerModeInternal(outerDisplay, PowerMode::OFF); + mFlinger.setPowerModeInternal(innerDisplay, PowerMode::ON); + ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), innerDisplay->getPhysicalId()); + + // The inner display should stay the leader if both are powered on. + // TODO(b/256196556): The leader should depend on the displays' VSYNC phases. + mFlinger.setPowerModeInternal(outerDisplay, PowerMode::ON); + ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), innerDisplay->getPhysicalId()); + + // The outer display should become the leader if designated. + mFlinger.scheduler()->setLeaderDisplay(outerDisplay->getPhysicalId()); + ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), outerDisplay->getPhysicalId()); +} + +} // namespace +} // namespace android diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp index a0aaa684f9..e5f7e652ad 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp @@ -484,38 +484,5 @@ TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOnToUnknownExternalDispla transitionDisplayCommon>(); } -// TODO(b/262417075) -TEST_F(SetPowerModeInternalTest, DISABLED_designatesLeaderDisplay) { - using Case = SimplePrimaryDisplayCase; - - // -------------------------------------------------------------------- - // Preconditions - - // Inject a primary display with uninitialized power mode. - constexpr bool kInitPowerMode = false; - Case::Display::injectHwcDisplay(this); - auto injector = Case::Display::makeFakeExistingDisplayInjector(this); - injector.setPowerMode(std::nullopt); - const auto display = injector.inject(); - - // -------------------------------------------------------------------- - // Invocation - - // FakeDisplayDeviceInjector registers the display with Scheduler, so it has already been - // designated as the leader. Set an arbitrary leader to verify that `setPowerModeInternal` - // designates a leader regardless of any preceding `Scheduler::registerDisplay` call(s). - constexpr PhysicalDisplayId kPlaceholderId = PhysicalDisplayId::fromPort(42); - ASSERT_NE(display->getPhysicalId(), kPlaceholderId); - mFlinger.scheduler()->setLeaderDisplay(kPlaceholderId); - - mFlinger.setPowerModeInternal(display, PowerMode::ON); - - // -------------------------------------------------------------------- - // Postconditions - - // The primary display should be designated as the leader. - EXPECT_EQ(mFlinger.scheduler()->leaderDisplayId(), display->getPhysicalId()); -} - } // namespace } // namespace android -- GitLab From e99b98ccbe273a4c664ff6cb07e74a25b163edcd Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Thu, 2 Feb 2023 12:37:23 -0500 Subject: [PATCH 0883/1310] SF: Fix synthetic VSYNC after first powering on After onActiveDisplayChangedLocked, isActiveDisplay was stale, so the first powering on of a newly active display did not enable and resync to hardware VSYNC, i.e. EventThread stayed in SyntheticVsync. Also, merge EventThread APIs to enable/disable synthetic VSYNC. Fixes: 264700488 Bug: 255635821 Test: dumpsys SurfaceFlinger --events Change-Id: Ibd4099bb856adc11d887f3b7b9432fbc4cb2e33d --- .../surfaceflinger/Scheduler/EventThread.cpp | 16 +---- .../surfaceflinger/Scheduler/EventThread.h | 13 +--- .../surfaceflinger/Scheduler/Scheduler.cpp | 16 ++--- services/surfaceflinger/Scheduler/Scheduler.h | 4 +- services/surfaceflinger/SurfaceFlinger.cpp | 19 +++--- .../surfaceflinger_scheduler_fuzzer.cpp | 3 +- .../tests/unittests/SchedulerTest.cpp | 12 ---- ...urfaceFlinger_SetPowerModeInternalTest.cpp | 59 ++++++++----------- .../tests/unittests/TestableScheduler.h | 1 + .../tests/unittests/TestableSurfaceFlinger.h | 5 +- .../tests/unittests/mock/MockEventThread.h | 3 +- 11 files changed, 51 insertions(+), 100 deletions(-) diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index eb6d7e4cfe..b78b92b698 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -384,23 +384,13 @@ VsyncEventData EventThread::getLatestVsyncEventData( return vsyncEventData; } -void EventThread::onScreenReleased() { +void EventThread::enableSyntheticVsync(bool enable) { std::lock_guard lock(mMutex); - if (!mVSyncState || mVSyncState->synthetic) { + if (!mVSyncState || mVSyncState->synthetic == enable) { return; } - mVSyncState->synthetic = true; - mCondition.notify_all(); -} - -void EventThread::onScreenAcquired() { - std::lock_guard lock(mMutex); - if (!mVSyncState || !mVSyncState->synthetic) { - return; - } - - mVSyncState->synthetic = false; + mVSyncState->synthetic = enable; mCondition.notify_all(); } diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h index 347dc4afd5..5037860482 100644 --- a/services/surfaceflinger/Scheduler/EventThread.h +++ b/services/surfaceflinger/Scheduler/EventThread.h @@ -106,11 +106,8 @@ public: virtual sp createEventConnection( ResyncCallback, EventRegistrationFlags eventRegistration = {}) const = 0; - // called before the screen is turned off from main thread - virtual void onScreenReleased() = 0; - - // called after the screen is turned on from main thread - virtual void onScreenAcquired() = 0; + // Feed clients with fake VSYNC, e.g. while the display is off. + virtual void enableSyntheticVsync(bool) = 0; virtual void onHotplugReceived(PhysicalDisplayId displayId, bool connected) = 0; @@ -159,11 +156,7 @@ public: VsyncEventData getLatestVsyncEventData( const sp& connection) const override; - // called before the screen is turned off from main thread - void onScreenReleased() override; - - // called after the screen is turned on from main thread - void onScreenAcquired() override; + void enableSyntheticVsync(bool) override; void onHotplugReceived(PhysicalDisplayId displayId, bool connected) override; diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index dab01ba48d..0daabe2197 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -265,24 +265,16 @@ void Scheduler::onHotplugReceived(ConnectionHandle handle, PhysicalDisplayId dis thread->onHotplugReceived(displayId, connected); } -void Scheduler::onScreenAcquired(ConnectionHandle handle) { +void Scheduler::enableSyntheticVsync(bool enable) { + // TODO(b/241285945): Remove connection handles. + const ConnectionHandle handle = mAppConnectionHandle; android::EventThread* thread; { std::lock_guard lock(mConnectionsLock); RETURN_IF_INVALID_HANDLE(handle); thread = mConnections[handle].thread.get(); } - thread->onScreenAcquired(); -} - -void Scheduler::onScreenReleased(ConnectionHandle handle) { - android::EventThread* thread; - { - std::lock_guard lock(mConnectionsLock); - RETURN_IF_INVALID_HANDLE(handle); - thread = mConnections[handle].thread.get(); - } - thread->onScreenReleased(); + thread->enableSyntheticVsync(enable); } void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId) { diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index a340919a65..67f4daa643 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -157,8 +157,8 @@ public: void onHotplugReceived(ConnectionHandle, PhysicalDisplayId, bool connected); void onPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&) EXCLUDES(mPolicyLock); void onNonPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&); - void onScreenAcquired(ConnectionHandle); - void onScreenReleased(ConnectionHandle); + + void enableSyntheticVsync(bool = true) REQUIRES(kMainThreadContext); void onFrameRateOverridesChanged(ConnectionHandle, PhysicalDisplayId) EXCLUDES(mConnectionsLock); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 5c8579cb4d..51dc02c9c4 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5210,7 +5210,6 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: return; } - const bool isActiveDisplay = displayId == mActiveDisplayId; const bool isInternalDisplay = mPhysicalDisplays.get(displayId) .transform(&PhysicalDisplay::isInternal) .value_or(false); @@ -5247,10 +5246,10 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: ALOGW("Couldn't set SCHED_FIFO on display on: %s\n", strerror(errno)); } getHwComposer().setPowerMode(displayId, mode); - if (isActiveDisplay && mode != hal::PowerMode::DOZE_SUSPEND) { + if (displayId == mActiveDisplayId && mode != hal::PowerMode::DOZE_SUSPEND) { setHWCVsyncEnabled(displayId, mScheduler->getVsyncSchedule().getPendingHardwareVsyncState()); - mScheduler->onScreenAcquired(mAppConnectionHandle); + mScheduler->enableSyntheticVsync(false); mScheduler->resyncToHardwareVsync(true, refreshRate); } @@ -5264,9 +5263,9 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: if (SurfaceFlinger::setSchedAttr(false) != NO_ERROR) { ALOGW("Couldn't set uclamp.min on display off: %s\n", strerror(errno)); } - if (isActiveDisplay && *currentModeOpt != hal::PowerMode::DOZE_SUSPEND) { + if (displayId == mActiveDisplayId && *currentModeOpt != hal::PowerMode::DOZE_SUSPEND) { mScheduler->disableHardwareVsync(true); - mScheduler->onScreenReleased(mAppConnectionHandle); + mScheduler->enableSyntheticVsync(); } // Make sure HWVsync is disabled before turning off the display @@ -5278,18 +5277,18 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: } else if (mode == hal::PowerMode::DOZE || mode == hal::PowerMode::ON) { // Update display while dozing getHwComposer().setPowerMode(displayId, mode); - if (isActiveDisplay && *currentModeOpt == hal::PowerMode::DOZE_SUSPEND) { + if (displayId == mActiveDisplayId && *currentModeOpt == hal::PowerMode::DOZE_SUSPEND) { ALOGI("Force repainting for DOZE_SUSPEND -> DOZE or ON."); mVisibleRegionsDirty = true; scheduleRepaint(); - mScheduler->onScreenAcquired(mAppConnectionHandle); + mScheduler->enableSyntheticVsync(false); mScheduler->resyncToHardwareVsync(true, refreshRate); } } else if (mode == hal::PowerMode::DOZE_SUSPEND) { // Leave display going to doze - if (isActiveDisplay) { + if (displayId == mActiveDisplayId) { mScheduler->disableHardwareVsync(true); - mScheduler->onScreenReleased(mAppConnectionHandle); + mScheduler->enableSyntheticVsync(); } getHwComposer().setPowerMode(displayId, mode); } else { @@ -5297,7 +5296,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: getHwComposer().setPowerMode(displayId, mode); } - if (isActiveDisplay) { + if (displayId == mActiveDisplayId) { mTimeStats->setPowerMode(mode); mRefreshRateStats->setPowerMode(mode); mScheduler->setDisplayPowerMode(mode); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index 61fb29a964..80486a2b09 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -109,8 +109,7 @@ void SchedulerFuzzer::fuzzEventThread() { thread->setDuration((std::chrono::nanoseconds)mFdp.ConsumeIntegral(), (std::chrono::nanoseconds)mFdp.ConsumeIntegral()); thread->registerDisplayEventConnection(connection); - thread->onScreenAcquired(); - thread->onScreenReleased(); + thread->enableSyntheticVsync(mFdp.ConsumeBool()); dump(thread.get(), &mFdp); } diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 4b15385fa8..422fa1ca7e 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -122,12 +122,6 @@ TEST_F(SchedulerTest, invalidConnectionHandle) { EXPECT_CALL(*mEventThread, onHotplugReceived(_, _)).Times(0); mScheduler->onHotplugReceived(handle, kDisplayId1, false); - EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(0); - mScheduler->onScreenAcquired(handle); - - EXPECT_CALL(*mEventThread, onScreenReleased()).Times(0); - mScheduler->onScreenReleased(handle); - std::string output; EXPECT_CALL(*mEventThread, dump(_)).Times(0); mScheduler->dump(handle, output); @@ -147,12 +141,6 @@ TEST_F(SchedulerTest, validConnectionHandle) { EXPECT_CALL(*mEventThread, onHotplugReceived(kDisplayId1, false)).Times(1); mScheduler->onHotplugReceived(mConnectionHandle, kDisplayId1, false); - EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(1); - mScheduler->onScreenAcquired(mConnectionHandle); - - EXPECT_CALL(*mEventThread, onScreenReleased()).Times(1); - mScheduler->onScreenReleased(mConnectionHandle); - std::string output("dump"); EXPECT_CALL(*mEventThread, dump(output)).Times(1); mScheduler->dump(mConnectionHandle, output); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp index e5f7e652ad..17b4714988 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp @@ -59,47 +59,34 @@ struct DozeNotSupportedVariant { }; struct EventThreadBaseSupportedVariant { - static void setupVsyncAndEventThreadNoCallExpectations(DisplayTransactionTest* test) { - // The callback should not be notified to toggle VSYNC. + static void setupVsyncNoCallExpectations(DisplayTransactionTest* test) { + // Expect no change to hardware nor synthetic VSYNC. EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_)).Times(0); - - // The event thread should not be notified. - EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(0); - EXPECT_CALL(*test->mEventThread, onScreenAcquired()).Times(0); + EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(_)).Times(0); } }; struct EventThreadNotSupportedVariant : public EventThreadBaseSupportedVariant { - static void setupAcquireAndEnableVsyncCallExpectations(DisplayTransactionTest* test) { - // These calls are only expected for the primary display. - - // Instead expect no calls. - setupVsyncAndEventThreadNoCallExpectations(test); + static void setupEnableVsyncCallExpectations(DisplayTransactionTest* test) { + setupVsyncNoCallExpectations(test); } - static void setupReleaseAndDisableVsyncCallExpectations(DisplayTransactionTest* test) { - // These calls are only expected for the primary display. - - // Instead expect no calls. - setupVsyncAndEventThreadNoCallExpectations(test); + static void setupDisableVsyncCallExpectations(DisplayTransactionTest* test) { + setupVsyncNoCallExpectations(test); } }; struct EventThreadIsSupportedVariant : public EventThreadBaseSupportedVariant { - static void setupAcquireAndEnableVsyncCallExpectations(DisplayTransactionTest* test) { - // The callback should be notified to enable VSYNC. + static void setupEnableVsyncCallExpectations(DisplayTransactionTest* test) { + // Expect to enable hardware VSYNC and disable synthetic VSYNC. EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(true)).Times(1); - - // The event thread should be notified that the screen was acquired. - EXPECT_CALL(*test->mEventThread, onScreenAcquired()).Times(1); + EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(false)).Times(1); } - static void setupReleaseAndDisableVsyncCallExpectations(DisplayTransactionTest* test) { - // The callback should be notified to disable VSYNC. + static void setupDisableVsyncCallExpectations(DisplayTransactionTest* test) { + // Expect to disable hardware VSYNC and enable synthetic VSYNC. EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(false)).Times(1); - - // The event thread should not be notified that the screen was released. - EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(1); + EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(true)).Times(1); } }; @@ -133,7 +120,7 @@ struct TransitionOffToOnVariant : public TransitionVariantCommon static void setupCallExpectations(DisplayTransactionTest* test) { Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::ON); - Case::EventThread::setupAcquireAndEnableVsyncCallExpectations(test); + Case::EventThread::setupEnableVsyncCallExpectations(test); Case::DispSync::setupResetModelCallExpectations(test); Case::setupRepaintEverythingCallExpectations(test); } @@ -148,7 +135,7 @@ struct TransitionOffToDozeSuspendVariant template static void setupCallExpectations(DisplayTransactionTest* test) { Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE_SUSPEND); - Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test); + Case::EventThread::setupVsyncNoCallExpectations(test); Case::setupRepaintEverythingCallExpectations(test); } @@ -160,7 +147,7 @@ struct TransitionOffToDozeSuspendVariant struct TransitionOnToOffVariant : public TransitionVariantCommon { template static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupReleaseAndDisableVsyncCallExpectations(test); + Case::EventThread::setupDisableVsyncCallExpectations(test); Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::OFF); } @@ -173,7 +160,7 @@ struct TransitionDozeSuspendToOffVariant : public TransitionVariantCommon { template static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test); + Case::EventThread::setupVsyncNoCallExpectations(test); Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::OFF); } @@ -185,7 +172,7 @@ struct TransitionDozeSuspendToOffVariant struct TransitionOnToDozeVariant : public TransitionVariantCommon { template static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test); + Case::EventThread::setupVsyncNoCallExpectations(test); Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE); } }; @@ -194,7 +181,7 @@ struct TransitionDozeSuspendToDozeVariant : public TransitionVariantCommon { template static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupAcquireAndEnableVsyncCallExpectations(test); + Case::EventThread::setupEnableVsyncCallExpectations(test); Case::DispSync::setupResetModelCallExpectations(test); Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE); } @@ -203,7 +190,7 @@ struct TransitionDozeSuspendToDozeVariant struct TransitionDozeToOnVariant : public TransitionVariantCommon { template static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test); + Case::EventThread::setupVsyncNoCallExpectations(test); Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::ON); } }; @@ -212,7 +199,7 @@ struct TransitionDozeSuspendToOnVariant : public TransitionVariantCommon { template static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupAcquireAndEnableVsyncCallExpectations(test); + Case::EventThread::setupEnableVsyncCallExpectations(test); Case::DispSync::setupResetModelCallExpectations(test); Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::ON); } @@ -222,7 +209,7 @@ struct TransitionOnToDozeSuspendVariant : public TransitionVariantCommon { template static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupReleaseAndDisableVsyncCallExpectations(test); + Case::EventThread::setupDisableVsyncCallExpectations(test); Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE_SUSPEND); } }; @@ -231,7 +218,7 @@ struct TransitionOnToUnknownVariant : public TransitionVariantCommon(POWER_MODE_LEET)> { template static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test); + Case::EventThread::setupVsyncNoCallExpectations(test); Case::setupNoComposerPowerModeCallExpectations(test); } }; diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index bd3f3cae98..48c5d48c98 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -91,6 +91,7 @@ public: Scheduler::setLeaderDisplay(displayId); } + auto& mutableAppConnectionHandle() { return mAppConnectionHandle; } auto& mutableVsyncModulator() { return *mVsyncModulator; } auto& mutableLayerHistory() { return mLayerHistory; } diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 8703359906..448803817a 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -252,7 +252,10 @@ public: mScheduler->initVsync(mScheduler->getVsyncSchedule().getDispatch(), *mTokenManager, 0ms); - mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread)); + mScheduler->mutableAppConnectionHandle() = + mScheduler->createConnection(std::move(appEventThread)); + + mFlinger->mAppConnectionHandle = mScheduler->mutableAppConnectionHandle(); mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread)); resetScheduler(mScheduler); } diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h index f8567bd636..8026a7accd 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h +++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h @@ -31,8 +31,7 @@ public: MOCK_CONST_METHOD2(createEventConnection, sp(ResyncCallback, EventRegistrationFlags)); - MOCK_METHOD0(onScreenReleased, void()); - MOCK_METHOD0(onScreenAcquired, void()); + MOCK_METHOD(void, enableSyntheticVsync, (bool), (override)); MOCK_METHOD2(onHotplugReceived, void(PhysicalDisplayId, bool)); MOCK_METHOD1(onModeChanged, void(const scheduler::FrameRateMode &)); MOCK_METHOD2(onFrameRateOverridesChanged, -- GitLab From dd0635d5169003c1119f78853289135cbfc71121 Mon Sep 17 00:00:00 2001 From: Zixuan Qu Date: Mon, 6 Feb 2023 04:52:38 +0000 Subject: [PATCH 0884/1310] Add virtual input device native classes. Add VirtualInputDevice base class and a set of subclasses for each device type. Each virtual input device wraps a fd representing the uinput device and a set of write...event() methods. Most logic are moved from InputController JNI code: see ag/21294055 for reference. Test: atest VirtualInputTest VirtualMouseTest VirtualKeyboardTest VirtualTouchscreenTest VirtualDpadTest Bug: 267515782 Change-Id: Ie3a580acc890ac5af7461f012e05eb9ed3709a5f Merged-In: Ie3a580acc890ac5af7461f012e05eb9ed3709a5f --- include/input/VirtualInputDevice.h | 97 ++++++++ libs/input/Android.bp | 3 + libs/input/VirtualInputDevice.cpp | 378 +++++++++++++++++++++++++++++ 3 files changed, 478 insertions(+) create mode 100644 include/input/VirtualInputDevice.h create mode 100644 libs/input/VirtualInputDevice.cpp diff --git a/include/input/VirtualInputDevice.h b/include/input/VirtualInputDevice.h new file mode 100644 index 0000000000..13ffb581b4 --- /dev/null +++ b/include/input/VirtualInputDevice.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +namespace android { + +enum class UinputAction { + RELEASE = 0, + PRESS = 1, + MOVE = 2, + CANCEL = 3, +}; + +class VirtualInputDevice { +public: + VirtualInputDevice(android::base::unique_fd fd); + virtual ~VirtualInputDevice(); + +protected: + const android::base::unique_fd mFd; + bool writeInputEvent(uint16_t type, uint16_t code, int32_t value); + bool writeEvKeyEvent(int32_t androidCode, int32_t androidAction, + const std::map& evKeyCodeMapping, + const std::map& actionMapping); +}; + +class VirtualKeyboard : public VirtualInputDevice { +public: + static const std::map KEY_CODE_MAPPING; + // Expose to share with VirtualDpad. + static const std::map KEY_ACTION_MAPPING; + VirtualKeyboard(android::base::unique_fd fd); + virtual ~VirtualKeyboard() override; + bool writeKeyEvent(int32_t androidKeyCode, int32_t androidAction); +}; + +class VirtualDpad : public VirtualInputDevice { +public: + static const std::map DPAD_KEY_CODE_MAPPING; + VirtualDpad(android::base::unique_fd fd); + virtual ~VirtualDpad() override; + bool writeDpadKeyEvent(int32_t androidKeyCode, int32_t androidAction); +}; + +class VirtualMouse : public VirtualInputDevice { +public: + VirtualMouse(android::base::unique_fd fd); + virtual ~VirtualMouse() override; + bool writeButtonEvent(int32_t androidButtonCode, int32_t androidAction); + // TODO(b/259554911): changing float parameters to int32_t. + bool writeRelativeEvent(float relativeX, float relativeY); + bool writeScrollEvent(float xAxisMovement, float yAxisMovement); + +private: + static const std::map BUTTON_ACTION_MAPPING; + static const std::map BUTTON_CODE_MAPPING; +}; + +class VirtualTouchscreen : public VirtualInputDevice { +public: + VirtualTouchscreen(android::base::unique_fd fd); + virtual ~VirtualTouchscreen() override; + // TODO(b/259554911): changing float parameters to int32_t. + bool writeTouchEvent(int32_t pointerId, int32_t toolType, int32_t action, float locationX, + float locationY, float pressure, float majorAxisSize); + +private: + static const std::map TOUCH_ACTION_MAPPING; + static const std::map TOOL_TYPE_MAPPING; + + /* The set of active touch pointers on this device. + * We only allow pointer id to go up to MAX_POINTERS because the maximum slots of virtual + * touchscreen is set up with MAX_POINTERS. Note that in other cases Android allows pointer id + * to go up to MAX_POINTERS_ID. + */ + std::bitset mActivePointers{}; + bool isValidPointerId(int32_t pointerId, UinputAction uinputAction); + bool handleTouchDown(int32_t pointerId); + bool handleTouchUp(int32_t pointerId); +}; +} // namespace android diff --git a/libs/input/Android.bp b/libs/input/Android.bp index fd4fc16deb..3809b6dd81 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -57,6 +57,7 @@ cc_library { "TouchVideoFrame.cpp", "VelocityControl.cpp", "VelocityTracker.cpp", + "VirtualInputDevice.cpp", "VirtualKeyMap.cpp", ], @@ -124,6 +125,8 @@ cc_library { enabled: false, }, include_dirs: [ + "bionic/libc/kernel/android/uapi/", + "bionic/libc/kernel/uapi", "frameworks/native/libs/arect/include", ], }, diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp new file mode 100644 index 0000000000..3c1f2b6b56 --- /dev/null +++ b/libs/input/VirtualInputDevice.cpp @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "VirtualInputDevice" + +#include +#include +#include +#include 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 diff --git a/services/surfaceflinger/tests/tracing/testdata/transactions_trace_boot.winscope b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_boot.winscope index 8356ae799275ef3a4d43b9f97093e68d65dcbd84..8d03df47359893f4e2542b4bc82d68a4d251ae7e 100644 GIT binary patch literal 354133 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 literal 25740 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 INz|=J0lTFazW@LL diff --git a/services/surfaceflinger/tests/tracing/testdata/transactions_trace_nodisplayfound.winscope b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_nodisplayfound.winscope index cd62ab8ce08615a1fa97d2ff760e87ee3a0e583f..022861c7ea8e60a7fc1acd4dac4fbc75996e1817 100644 GIT binary patch literal 383550 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 diff --git a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp index b6427c0ffb..3dea189ca0 100644 --- a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp @@ -19,6 +19,7 @@ #include // std::numeric_limits #include +#include "LayerProtoHelper.h" #include "Tracing/TransactionProtoParser.h" @@ -27,7 +28,6 @@ using namespace android::surfaceflinger; namespace android { TEST(TransactionProtoParserTest, parse) { - const sp layerHandle = sp::make(); const sp displayHandle = sp::make(); TransactionState t1; t1.originPid = 1; @@ -37,7 +37,6 @@ TEST(TransactionProtoParserTest, parse) { t1.postTime = 5; layer_state_t layer; - layer.layerId = 6; layer.what = std::numeric_limits::max(); layer.what &= ~static_cast(layer_state_t::eBufferChanged); layer.x = 7; @@ -48,10 +47,9 @@ TEST(TransactionProtoParserTest, parse) { for (uint32_t i = 0; i < layerCount; i++) { ResolvedComposerState s; if (i == 1) { - layer.parentSurfaceControlForChild = - sp::make(SurfaceComposerClient::getDefault(), layerHandle, 42, - "#42"); + s.parentId = 42; } + s.layerId = 6 + i; s.state = layer; t1.states.emplace_back(s); } @@ -72,18 +70,10 @@ TEST(TransactionProtoParserTest, parse) { class TestMapper : public TransactionProtoParser::FlingerDataMapper { public: - sp layerHandle; sp displayHandle; - TestMapper(sp layerHandle, sp displayHandle) - : layerHandle(layerHandle), displayHandle(displayHandle) {} + TestMapper(sp displayHandle) : displayHandle(displayHandle) {} - sp getLayerHandle(int32_t id) const override { - return (id == 42) ? layerHandle : nullptr; - } - int64_t getLayerId(const sp& handle) const override { - return (handle == layerHandle) ? 42 : -1; - } sp getDisplayHandle(int32_t id) const { return (id == 43) ? displayHandle : nullptr; } @@ -92,7 +82,7 @@ TEST(TransactionProtoParserTest, parse) { } }; - TransactionProtoParser parser(std::make_unique(layerHandle, displayHandle)); + TransactionProtoParser parser(std::make_unique(displayHandle)); proto::TransactionState proto = parser.toProto(t1); TransactionState t2 = parser.fromProto(proto); @@ -105,8 +95,8 @@ TEST(TransactionProtoParserTest, parse) { ASSERT_EQ(t1.states.size(), t2.states.size()); ASSERT_EQ(t1.states[0].state.x, t2.states[0].state.x); ASSERT_EQ(t1.states[0].state.matrix.dsdx, t2.states[0].state.matrix.dsdx); - ASSERT_EQ(t1.states[1].state.parentSurfaceControlForChild->getHandle(), - t2.states[1].state.parentSurfaceControlForChild->getHandle()); + ASSERT_EQ(t1.states[1].layerId, t2.states[1].layerId); + ASSERT_EQ(t1.states[1].parentId, t2.states[1].parentId); ASSERT_EQ(t1.displays.size(), t2.displays.size()); ASSERT_EQ(t1.displays[1].width, t2.displays[1].width); diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp index 482c3a8e50..74541ace0b 100644 --- a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp @@ -18,7 +18,11 @@ #include #include +#include +#include "Client.h" +#include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/Update.h" #include "Tracing/RingBuffer.h" #include "Tracing/TransactionTracing.h" @@ -42,14 +46,15 @@ protected: } void queueAndCommitTransaction(int64_t vsyncId) { + frontend::Update update; TransactionState transaction; transaction.id = static_cast(vsyncId * 3); transaction.originUid = 1; transaction.originPid = 2; mTracing.addQueuedTransaction(transaction); std::vector transactions; - transactions.emplace_back(transaction); - mTracing.addCommittedTransactions(transactions, vsyncId); + update.transactions.emplace_back(transaction); + mTracing.addCommittedTransactions(vsyncId, 0, update, {}, false); flush(vsyncId); } @@ -57,13 +62,25 @@ protected: const std::vector& expectedTransactions, int64_t expectedVsyncId) { EXPECT_EQ(actualProto.vsync_id(), expectedVsyncId); - EXPECT_EQ(actualProto.transactions().size(), + ASSERT_EQ(actualProto.transactions().size(), static_cast(expectedTransactions.size())); for (uint32_t i = 0; i < expectedTransactions.size(); i++) { EXPECT_EQ(actualProto.transactions(static_cast(i)).pid(), expectedTransactions[i].originPid); } } + + LayerCreationArgs getLayerCreationArgs(uint32_t layerId, uint32_t parentId, + uint32_t layerIdToMirror, uint32_t flags, + bool addToRoot) { + LayerCreationArgs args; + args.sequence = layerId; + args.parentId = parentId; + args.layerIdToMirror = layerIdToMirror; + args.flags = flags; + args.addToRoot = addToRoot; + return args; + } }; TEST_F(TransactionTracingTest, addTransactions) { @@ -79,57 +96,59 @@ TEST_F(TransactionTracingTest, addTransactions) { // Split incoming transactions into two and commit them in reverse order to test out of order // commits. - std::vector firstTransactionSet = - std::vector(transactions.begin() + 50, transactions.end()); int64_t firstTransactionSetVsyncId = 42; - mTracing.addCommittedTransactions(firstTransactionSet, firstTransactionSetVsyncId); + frontend::Update firstUpdate; + firstUpdate.transactions = + std::vector(transactions.begin() + 50, transactions.end()); + mTracing.addCommittedTransactions(firstTransactionSetVsyncId, 0, firstUpdate, {}, false); int64_t secondTransactionSetVsyncId = 43; - std::vector secondTransactionSet = + frontend::Update secondUpdate; + secondUpdate.transactions = std::vector(transactions.begin(), transactions.begin() + 50); - mTracing.addCommittedTransactions(secondTransactionSet, secondTransactionSetVsyncId); + mTracing.addCommittedTransactions(secondTransactionSetVsyncId, 0, secondUpdate, {}, false); flush(secondTransactionSetVsyncId); proto::TransactionTraceFile proto = writeToProto(); - EXPECT_EQ(proto.entry().size(), 2); - verifyEntry(proto.entry(0), firstTransactionSet, firstTransactionSetVsyncId); - verifyEntry(proto.entry(1), secondTransactionSet, secondTransactionSetVsyncId); + ASSERT_EQ(proto.entry().size(), 2); + verifyEntry(proto.entry(0), firstUpdate.transactions, firstTransactionSetVsyncId); + verifyEntry(proto.entry(1), secondUpdate.transactions, secondTransactionSetVsyncId); } class TransactionTracingLayerHandlingTest : public TransactionTracingTest { protected: void SetUp() override { - // add layers mTracing.setBufferSize(SMALL_BUFFER_SIZE); - const sp fakeLayerHandle = sp::make(); - mTracing.onLayerAdded(fakeLayerHandle->localBinder(), mParentLayerId, "parent", - 123 /* flags */, -1 /* parentId */); - const sp fakeChildLayerHandle = sp::make(); - mTracing.onLayerAdded(fakeChildLayerHandle->localBinder(), mChildLayerId, "child", - 456 /* flags */, mParentLayerId); - - // add some layer transaction + + // add layers and add some layer transaction { + frontend::Update update; + update.layerCreationArgs.emplace_back(std::move( + getLayerCreationArgs(mParentLayerId, /*parentId=*/UNASSIGNED_LAYER_ID, + /*layerIdToMirror=*/UNASSIGNED_LAYER_ID, /*flags=*/123, + /*addToRoot=*/true))); + update.layerCreationArgs.emplace_back(std::move( + getLayerCreationArgs(mChildLayerId, mParentLayerId, + /*layerIdToMirror=*/UNASSIGNED_LAYER_ID, /*flags=*/456, + /*addToRoot=*/true))); TransactionState transaction; transaction.id = 50; ResolvedComposerState layerState; - layerState.state.surface = fakeLayerHandle; + layerState.layerId = mParentLayerId; layerState.state.what = layer_state_t::eLayerChanged; layerState.state.z = 42; transaction.states.emplace_back(layerState); ResolvedComposerState childState; - childState.state.surface = fakeChildLayerHandle; + childState.layerId = mChildLayerId; childState.state.what = layer_state_t::eLayerChanged; childState.state.z = 43; transaction.states.emplace_back(childState); mTracing.addQueuedTransaction(transaction); - std::vector transactions; - transactions.emplace_back(transaction); + update.transactions.emplace_back(transaction); VSYNC_ID_FIRST_LAYER_CHANGE = ++mVsyncId; - mTracing.onLayerAddedToDrawingState(mParentLayerId, VSYNC_ID_FIRST_LAYER_CHANGE); - mTracing.onLayerAddedToDrawingState(mChildLayerId, VSYNC_ID_FIRST_LAYER_CHANGE); - mTracing.addCommittedTransactions(transactions, VSYNC_ID_FIRST_LAYER_CHANGE); + mTracing.addCommittedTransactions(VSYNC_ID_FIRST_LAYER_CHANGE, 0, update, {}, false); + flush(VSYNC_ID_FIRST_LAYER_CHANGE); } @@ -139,17 +158,17 @@ protected: TransactionState transaction; transaction.id = 51; ResolvedComposerState layerState; - layerState.state.surface = fakeLayerHandle; + layerState.layerId = mParentLayerId; layerState.state.what = layer_state_t::eLayerChanged | layer_state_t::ePositionChanged; layerState.state.z = 41; layerState.state.x = 22; transaction.states.emplace_back(layerState); mTracing.addQueuedTransaction(transaction); - std::vector transactions; - transactions.emplace_back(transaction); + frontend::Update update; + update.transactions.emplace_back(transaction); VSYNC_ID_SECOND_LAYER_CHANGE = ++mVsyncId; - mTracing.addCommittedTransactions(transactions, VSYNC_ID_SECOND_LAYER_CHANGE); + mTracing.addCommittedTransactions(VSYNC_ID_SECOND_LAYER_CHANGE, 0, update, {}, false); flush(VSYNC_ID_SECOND_LAYER_CHANGE); } @@ -163,8 +182,8 @@ protected: queueAndCommitTransaction(++mVsyncId); } - int mParentLayerId = 1; - int mChildLayerId = 2; + uint32_t mParentLayerId = 1; + uint32_t mChildLayerId = 2; int64_t mVsyncId = 0; int64_t VSYNC_ID_FIRST_LAYER_CHANGE; int64_t VSYNC_ID_SECOND_LAYER_CHANGE; @@ -232,42 +251,42 @@ TEST_F(TransactionTracingLayerHandlingTest, startingStateSurvivesBufferFlush) { class TransactionTracingMirrorLayerTest : public TransactionTracingTest { protected: void SetUp() override { - // add layers mTracing.setBufferSize(SMALL_BUFFER_SIZE); - const sp fakeLayerHandle = sp::make(); - mTracing.onLayerAdded(fakeLayerHandle->localBinder(), mLayerId, "Test Layer", - 123 /* flags */, -1 /* parentId */); - const sp fakeMirrorLayerHandle = sp::make(); - mTracing.onMirrorLayerAdded(fakeMirrorLayerHandle->localBinder(), mMirrorLayerId, "Mirror", - mLayerId); - mTracing.onLayerAddedToDrawingState(mLayerId, mVsyncId); - mTracing.onLayerAddedToDrawingState(mMirrorLayerId, mVsyncId); - - // add some layer transaction + + // add layers and some layer transaction { + frontend::Update update; + update.layerCreationArgs.emplace_back( + getLayerCreationArgs(mLayerId, /*parentId=*/UNASSIGNED_LAYER_ID, + /*layerIdToMirror=*/UNASSIGNED_LAYER_ID, /*flags=*/123, + /*addToRoot=*/true)); + update.layerCreationArgs.emplace_back( + getLayerCreationArgs(mMirrorLayerId, UNASSIGNED_LAYER_ID, + /*layerIdToMirror=*/mLayerId, /*flags=*/0, + /*addToRoot=*/false)); + TransactionState transaction; transaction.id = 50; ResolvedComposerState layerState; - layerState.state.surface = fakeLayerHandle; + layerState.layerId = mLayerId; layerState.state.what = layer_state_t::eLayerChanged; layerState.state.z = 42; transaction.states.emplace_back(layerState); ResolvedComposerState mirrorState; - mirrorState.state.surface = fakeMirrorLayerHandle; + mirrorState.layerId = mMirrorLayerId; mirrorState.state.what = layer_state_t::eLayerChanged; mirrorState.state.z = 43; transaction.states.emplace_back(mirrorState); mTracing.addQueuedTransaction(transaction); - std::vector transactions; - transactions.emplace_back(transaction); - mTracing.addCommittedTransactions(transactions, mVsyncId); + update.transactions.emplace_back(transaction); + mTracing.addCommittedTransactions(mVsyncId, 0, update, {}, false); flush(mVsyncId); } } - int mLayerId = 5; - int mMirrorLayerId = 55; + uint32_t mLayerId = 5; + uint32_t mMirrorLayerId = 55; int64_t mVsyncId = 0; int64_t VSYNC_ID_FIRST_LAYER_CHANGE; int64_t VSYNC_ID_SECOND_LAYER_CHANGE; -- GitLab From 00e029d8cfd1e5487d8c8997ec852ffd54252a57 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 9 Mar 2023 20:11:09 +0000 Subject: [PATCH 0994/1310] MotionEvent: Round coordinates to a precision of 0.001 When tests inject input events at a location on the screen, the dispatched event is expected to have the same coordinates. However, on scaled devices, this may not always be the case due to precision losses incurred through floating point arithmetics. For example, it was possible for an injected event with a coordinate of 1.0 to end up with a value of 0.9997. To combat this issue, we will round transformed axis values that are leaving a MotionEvent to a precision of 0.001. After this CL, even if the injection process results in precision losses, they should be overcome by rounding, assuming injection does not require greater precision. This will solve the issue where input injected to an inclusive edge of the View bounds was not getting dispatched to the View due to precision losses. Bug: 264978231 Bug: 260965930 Test: atest libinput_tests Test: atest inputflinger_tests Test: atest HoverTest (with screen size override) Change-Id: I81062597058361a1218e6873d34b9b0d2fbfad96 --- include/input/Input.h | 2 + libs/input/Input.cpp | 21 +- libs/input/tests/InputEvent_test.cpp | 328 +++++++++++------- .../tests/InputPublisherAndConsumer_test.cpp | 20 +- .../tests/InputDispatcher_test.cpp | 8 +- 5 files changed, 230 insertions(+), 149 deletions(-) diff --git a/include/input/Input.h b/include/input/Input.h index 608519b70e..e8af5f7d46 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -855,6 +855,8 @@ public: const PointerCoords&); static PointerCoords calculateTransformedCoords(uint32_t source, const ui::Transform&, const PointerCoords&); + // The rounding precision for transformed motion events. + static constexpr float ROUNDING_PRECISION = 0.001f; protected: int32_t mAction; diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index 133b260a61..53b22cb883 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -151,10 +151,23 @@ int32_t IdGenerator::nextId() const { // --- InputEvent --- +// Due to precision limitations when working with floating points, transforming - namely +// scaling - floating points can lead to minute errors. We round transformed values to approximately +// three decimal places so that values like 0.99997 show up as 1.0. +inline float roundTransformedCoords(float val) { + // Use a power to two to approximate three decimal places to potentially reduce some cycles. + // This should be at least as precise as MotionEvent::ROUNDING_PRECISION. + return std::round(val * 1024.f) / 1024.f; +} + +inline vec2 roundTransformedCoords(vec2 p) { + return {roundTransformedCoords(p.x), roundTransformedCoords(p.y)}; +} + vec2 transformWithoutTranslation(const ui::Transform& transform, const vec2& xy) { const vec2 transformedXy = transform.transform(xy); const vec2 transformedOrigin = transform.transform(0, 0); - return transformedXy - transformedOrigin; + return roundTransformedCoords(transformedXy - transformedOrigin); } float transformAngle(const ui::Transform& transform, float angleRadians) { @@ -606,12 +619,12 @@ std::optional MotionEvent::getSurfaceRotation() const { float MotionEvent::getXCursorPosition() const { vec2 vals = mTransform.transform(getRawXCursorPosition(), getRawYCursorPosition()); - return vals.x; + return roundTransformedCoords(vals.x); } float MotionEvent::getYCursorPosition() const { vec2 vals = mTransform.transform(getRawXCursorPosition(), getRawYCursorPosition()); - return vals.y; + return roundTransformedCoords(vals.y); } void MotionEvent::setCursorPosition(float x, float y) { @@ -933,7 +946,7 @@ std::string MotionEvent::actionToString(int32_t action) { static inline vec2 calculateTransformedXYUnchecked(uint32_t source, const ui::Transform& transform, const vec2& xy) { return shouldDisregardOffset(source) ? transformWithoutTranslation(transform, xy) - : transform.transform(xy); + : roundTransformedCoords(transform.transform(xy)); } vec2 MotionEvent::calculateTransformedXY(uint32_t source, const ui::Transform& transform, diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp index 8a6e983bb5..81c23df97a 100644 --- a/libs/input/tests/InputEvent_test.cpp +++ b/libs/input/tests/InputEvent_test.cpp @@ -29,6 +29,8 @@ namespace android { // Default display id. static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; +static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION; + class BaseTest : public testing::Test { protected: static constexpr std::array HMAC = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, @@ -235,102 +237,110 @@ protected: static constexpr float RAW_X_OFFSET = 12; static constexpr float RAW_Y_OFFSET = -41.1; + void SetUp() override; + int32_t mId; ui::Transform mTransform; ui::Transform mRawTransform; + PointerProperties mPointerProperties[2]; + struct Sample { + PointerCoords pointerCoords[2]; + }; + std::array mSamples{}; void initializeEventWithHistory(MotionEvent* event); void assertEqualsEventWithHistory(const MotionEvent* event); }; -void MotionEventTest::initializeEventWithHistory(MotionEvent* event) { +void MotionEventTest::SetUp() { mId = InputEvent::nextId(); mTransform.set({X_SCALE, 0, X_OFFSET, 0, Y_SCALE, Y_OFFSET, 0, 0, 1}); mRawTransform.set({RAW_X_SCALE, 0, RAW_X_OFFSET, 0, RAW_Y_SCALE, RAW_Y_OFFSET, 0, 0, 1}); - PointerProperties pointerProperties[2]; - pointerProperties[0].clear(); - pointerProperties[0].id = 1; - pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; - pointerProperties[1].clear(); - pointerProperties[1].id = 2; - pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; - - PointerCoords pointerCoords[2]; - pointerCoords[0].clear(); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 10); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 11); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 12); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 13); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 14); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 15); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 16); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 17); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 18); - pointerCoords[0].isResampled = true; - pointerCoords[1].clear(); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 20); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 21); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 22); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 23); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 24); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 25); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 26); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 27); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 28); + mPointerProperties[0].clear(); + mPointerProperties[0].id = 1; + mPointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + mPointerProperties[1].clear(); + mPointerProperties[1].id = 2; + mPointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + + mSamples[0].pointerCoords[0].clear(); + mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 10); + mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 11); + mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 12); + mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 13); + mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 14); + mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 15); + mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 16); + mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 17); + mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 18); + mSamples[0].pointerCoords[0].isResampled = true; + mSamples[0].pointerCoords[1].clear(); + mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 20); + mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 21); + mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 22); + mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 23); + mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 24); + mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 25); + mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 26); + mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 27); + mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 28); + + mSamples[1].pointerCoords[0].clear(); + mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 110); + mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 111); + mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 112); + mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 113); + mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 114); + mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 115); + mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 116); + mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 117); + mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 118); + mSamples[1].pointerCoords[0].isResampled = true; + mSamples[1].pointerCoords[1].clear(); + mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 120); + mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 121); + mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 122); + mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 123); + mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 124); + mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 125); + mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 126); + mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 127); + mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 128); + mSamples[1].pointerCoords[1].isResampled = true; + + mSamples[2].pointerCoords[0].clear(); + mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 210); + mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 211); + mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 212); + mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 213); + mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 214); + mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 215); + mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 216); + mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 217); + mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 218); + mSamples[2].pointerCoords[1].clear(); + mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 220); + mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 221); + mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 222); + mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 223); + mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 224); + mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 225); + mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 226); + mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 227); + mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 228); +} + +void MotionEventTest::initializeEventWithHistory(MotionEvent* event) { event->initialize(mId, 2, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, HMAC, AMOTION_EVENT_ACTION_MOVE, 0, AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED, AMOTION_EVENT_EDGE_FLAG_TOP, AMETA_ALT_ON, AMOTION_EVENT_BUTTON_PRIMARY, MotionClassification::NONE, mTransform, 2.0f, 2.1f, AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, mRawTransform, ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME, 2, - pointerProperties, pointerCoords); - - pointerCoords[0].clear(); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 110); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 111); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 112); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 113); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 114); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 115); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 116); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 117); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 118); - pointerCoords[0].isResampled = true; - pointerCoords[1].clear(); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 120); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 121); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 122); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 123); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 124); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 125); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 126); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 127); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 128); - pointerCoords[1].isResampled = true; - event->addSample(ARBITRARY_EVENT_TIME + 1, pointerCoords); - - pointerCoords[0].clear(); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 210); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 211); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 212); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 213); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 214); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 215); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 216); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 217); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 218); - pointerCoords[1].clear(); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 220); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 221); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 222); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 223); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 224); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 225); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 226); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 227); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 228); - event->addSample(ARBITRARY_EVENT_TIME + 2, pointerCoords); + mPointerProperties, mSamples[0].pointerCoords); + event->addSample(ARBITRARY_EVENT_TIME + 1, mSamples[1].pointerCoords); + event->addSample(ARBITRARY_EVENT_TIME + 2, mSamples[2].pointerCoords); } void MotionEventTest::assertEqualsEventWithHistory(const MotionEvent* event) { @@ -367,51 +377,65 @@ void MotionEventTest::assertEqualsEventWithHistory(const MotionEvent* event) { ASSERT_EQ(ARBITRARY_EVENT_TIME + 1, event->getHistoricalEventTime(1)); ASSERT_EQ(ARBITRARY_EVENT_TIME + 2, event->getEventTime()); - ASSERT_EQ(11, event->getHistoricalRawPointerCoords(0, 0)->getAxisValue(AMOTION_EVENT_AXIS_Y)); - ASSERT_EQ(21, event->getHistoricalRawPointerCoords(1, 0)->getAxisValue(AMOTION_EVENT_AXIS_Y)); - ASSERT_EQ(111, event->getHistoricalRawPointerCoords(0, 1)->getAxisValue(AMOTION_EVENT_AXIS_Y)); - ASSERT_EQ(121, event->getHistoricalRawPointerCoords(1, 1)->getAxisValue(AMOTION_EVENT_AXIS_Y)); - ASSERT_EQ(211, event->getRawPointerCoords(0)->getAxisValue(AMOTION_EVENT_AXIS_Y)); - ASSERT_EQ(221, event->getRawPointerCoords(1)->getAxisValue(AMOTION_EVENT_AXIS_Y)); - - ASSERT_EQ(RAW_Y_OFFSET + 11 * RAW_Y_SCALE, - event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 0)); - ASSERT_EQ(RAW_Y_OFFSET + 21 * RAW_Y_SCALE, - event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 0)); - ASSERT_EQ(RAW_Y_OFFSET + 111 * RAW_Y_SCALE, - event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 1)); - ASSERT_EQ(RAW_Y_OFFSET + 121 * RAW_Y_SCALE, - event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 1)); - ASSERT_EQ(RAW_Y_OFFSET + 211 * RAW_Y_SCALE, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 0)); - ASSERT_EQ(RAW_Y_OFFSET + 221 * RAW_Y_SCALE, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 1)); - - ASSERT_EQ(RAW_X_OFFSET + 10 * RAW_X_SCALE, event->getHistoricalRawX(0, 0)); - ASSERT_EQ(RAW_X_OFFSET + 20 * RAW_X_SCALE, event->getHistoricalRawX(1, 0)); - ASSERT_EQ(RAW_X_OFFSET + 110 * RAW_X_SCALE, event->getHistoricalRawX(0, 1)); - ASSERT_EQ(RAW_X_OFFSET + 120 * RAW_X_SCALE, event->getHistoricalRawX(1, 1)); - ASSERT_EQ(RAW_X_OFFSET + 210 * RAW_X_SCALE, event->getRawX(0)); - ASSERT_EQ(RAW_X_OFFSET + 220 * RAW_X_SCALE, event->getRawX(1)); - - ASSERT_EQ(RAW_Y_OFFSET + 11 * RAW_Y_SCALE, event->getHistoricalRawY(0, 0)); - ASSERT_EQ(RAW_Y_OFFSET + 21 * RAW_Y_SCALE, event->getHistoricalRawY(1, 0)); - ASSERT_EQ(RAW_Y_OFFSET + 111 * RAW_Y_SCALE, event->getHistoricalRawY(0, 1)); - ASSERT_EQ(RAW_Y_OFFSET + 121 * RAW_Y_SCALE, event->getHistoricalRawY(1, 1)); - ASSERT_EQ(RAW_Y_OFFSET + 211 * RAW_Y_SCALE, event->getRawY(0)); - ASSERT_EQ(RAW_Y_OFFSET + 221 * RAW_Y_SCALE, event->getRawY(1)); - - ASSERT_EQ(X_OFFSET + 10 * X_SCALE, event->getHistoricalX(0, 0)); - ASSERT_EQ(X_OFFSET + 20 * X_SCALE, event->getHistoricalX(1, 0)); - ASSERT_EQ(X_OFFSET + 110 * X_SCALE, event->getHistoricalX(0, 1)); - ASSERT_EQ(X_OFFSET + 120 * X_SCALE, event->getHistoricalX(1, 1)); - ASSERT_EQ(X_OFFSET + 210 * X_SCALE, event->getX(0)); - ASSERT_EQ(X_OFFSET + 220 * X_SCALE, event->getX(1)); - - ASSERT_EQ(Y_OFFSET + 11 * Y_SCALE, event->getHistoricalY(0, 0)); - ASSERT_EQ(Y_OFFSET + 21 * Y_SCALE, event->getHistoricalY(1, 0)); - ASSERT_EQ(Y_OFFSET + 111 * Y_SCALE, event->getHistoricalY(0, 1)); - ASSERT_EQ(Y_OFFSET + 121 * Y_SCALE, event->getHistoricalY(1, 1)); - ASSERT_EQ(Y_OFFSET + 211 * Y_SCALE, event->getY(0)); - ASSERT_EQ(Y_OFFSET + 221 * Y_SCALE, event->getY(1)); + // Ensure the underlying PointerCoords are identical. + for (int sampleIdx = 0; sampleIdx < 3; sampleIdx++) { + for (int pointerIdx = 0; pointerIdx < 2; pointerIdx++) { + ASSERT_EQ(mSamples[sampleIdx].pointerCoords[pointerIdx], + event->getSamplePointerCoords()[sampleIdx * 2 + pointerIdx]); + } + } + + ASSERT_NEAR(11, event->getHistoricalRawPointerCoords(0, 0)->getAxisValue(AMOTION_EVENT_AXIS_Y), + EPSILON); + ASSERT_NEAR(21, event->getHistoricalRawPointerCoords(1, 0)->getAxisValue(AMOTION_EVENT_AXIS_Y), + EPSILON); + ASSERT_NEAR(111, event->getHistoricalRawPointerCoords(0, 1)->getAxisValue(AMOTION_EVENT_AXIS_Y), + EPSILON); + ASSERT_NEAR(121, event->getHistoricalRawPointerCoords(1, 1)->getAxisValue(AMOTION_EVENT_AXIS_Y), + EPSILON); + ASSERT_NEAR(211, event->getRawPointerCoords(0)->getAxisValue(AMOTION_EVENT_AXIS_Y), EPSILON); + ASSERT_NEAR(221, event->getRawPointerCoords(1)->getAxisValue(AMOTION_EVENT_AXIS_Y), EPSILON); + + ASSERT_NEAR(RAW_Y_OFFSET + 11 * RAW_Y_SCALE, + event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 0), EPSILON); + ASSERT_NEAR(RAW_Y_OFFSET + 21 * RAW_Y_SCALE, + event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 0), EPSILON); + ASSERT_NEAR(RAW_Y_OFFSET + 111 * RAW_Y_SCALE, + event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 1), EPSILON); + ASSERT_NEAR(RAW_Y_OFFSET + 121 * RAW_Y_SCALE, + event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 1), EPSILON); + ASSERT_NEAR(RAW_Y_OFFSET + 211 * RAW_Y_SCALE, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 0), + EPSILON); + ASSERT_NEAR(RAW_Y_OFFSET + 221 * RAW_Y_SCALE, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 1), + EPSILON); + + ASSERT_NEAR(RAW_X_OFFSET + 10 * RAW_X_SCALE, event->getHistoricalRawX(0, 0), EPSILON); + ASSERT_NEAR(RAW_X_OFFSET + 20 * RAW_X_SCALE, event->getHistoricalRawX(1, 0), EPSILON); + ASSERT_NEAR(RAW_X_OFFSET + 110 * RAW_X_SCALE, event->getHistoricalRawX(0, 1), EPSILON); + ASSERT_NEAR(RAW_X_OFFSET + 120 * RAW_X_SCALE, event->getHistoricalRawX(1, 1), EPSILON); + ASSERT_NEAR(RAW_X_OFFSET + 210 * RAW_X_SCALE, event->getRawX(0), EPSILON); + ASSERT_NEAR(RAW_X_OFFSET + 220 * RAW_X_SCALE, event->getRawX(1), EPSILON); + + ASSERT_NEAR(RAW_Y_OFFSET + 11 * RAW_Y_SCALE, event->getHistoricalRawY(0, 0), EPSILON); + ASSERT_NEAR(RAW_Y_OFFSET + 21 * RAW_Y_SCALE, event->getHistoricalRawY(1, 0), EPSILON); + ASSERT_NEAR(RAW_Y_OFFSET + 111 * RAW_Y_SCALE, event->getHistoricalRawY(0, 1), EPSILON); + ASSERT_NEAR(RAW_Y_OFFSET + 121 * RAW_Y_SCALE, event->getHistoricalRawY(1, 1), EPSILON); + ASSERT_NEAR(RAW_Y_OFFSET + 211 * RAW_Y_SCALE, event->getRawY(0), EPSILON); + ASSERT_NEAR(RAW_Y_OFFSET + 221 * RAW_Y_SCALE, event->getRawY(1), EPSILON); + + ASSERT_NEAR(X_OFFSET + 10 * X_SCALE, event->getHistoricalX(0, 0), EPSILON); + ASSERT_NEAR(X_OFFSET + 20 * X_SCALE, event->getHistoricalX(1, 0), EPSILON); + ASSERT_NEAR(X_OFFSET + 110 * X_SCALE, event->getHistoricalX(0, 1), EPSILON); + ASSERT_NEAR(X_OFFSET + 120 * X_SCALE, event->getHistoricalX(1, 1), EPSILON); + ASSERT_NEAR(X_OFFSET + 210 * X_SCALE, event->getX(0), EPSILON); + ASSERT_NEAR(X_OFFSET + 220 * X_SCALE, event->getX(1), EPSILON); + + ASSERT_NEAR(Y_OFFSET + 11 * Y_SCALE, event->getHistoricalY(0, 0), EPSILON); + ASSERT_NEAR(Y_OFFSET + 21 * Y_SCALE, event->getHistoricalY(1, 0), EPSILON); + ASSERT_NEAR(Y_OFFSET + 111 * Y_SCALE, event->getHistoricalY(0, 1), EPSILON); + ASSERT_NEAR(Y_OFFSET + 121 * Y_SCALE, event->getHistoricalY(1, 1), EPSILON); + ASSERT_NEAR(Y_OFFSET + 211 * Y_SCALE, event->getY(0), EPSILON); + ASSERT_NEAR(Y_OFFSET + 221 * Y_SCALE, event->getY(1), EPSILON); ASSERT_EQ(12, event->getHistoricalPressure(0, 0)); ASSERT_EQ(22, event->getHistoricalPressure(1, 0)); @@ -550,10 +574,10 @@ TEST_F(MotionEventTest, Scale) { ASSERT_EQ(X_OFFSET * 2, event.getXOffset()); ASSERT_EQ(Y_OFFSET * 2, event.getYOffset()); - ASSERT_EQ((RAW_X_OFFSET + 210 * RAW_X_SCALE) * 2, event.getRawX(0)); - ASSERT_EQ((RAW_Y_OFFSET + 211 * RAW_Y_SCALE) * 2, event.getRawY(0)); - ASSERT_EQ((X_OFFSET + 210 * X_SCALE) * 2, event.getX(0)); - ASSERT_EQ((Y_OFFSET + 211 * Y_SCALE) * 2, event.getY(0)); + ASSERT_NEAR((RAW_X_OFFSET + 210 * RAW_X_SCALE) * 2, event.getRawX(0), EPSILON); + ASSERT_NEAR((RAW_Y_OFFSET + 211 * RAW_Y_SCALE) * 2, event.getRawY(0), EPSILON); + ASSERT_NEAR((X_OFFSET + 210 * X_SCALE) * 2, event.getX(0), EPSILON); + ASSERT_NEAR((Y_OFFSET + 211 * Y_SCALE) * 2, event.getY(0), EPSILON); ASSERT_EQ(212, event.getPressure(0)); ASSERT_EQ(213, event.getSize(0)); ASSERT_EQ(214 * 2, event.getTouchMajor(0)); @@ -791,18 +815,18 @@ TEST_F(MotionEventTest, AxesAreCorrectlyTransformed) { // The x and y axes should have the window transform applied. const auto newPoint = transform.transform(60, 100); - ASSERT_EQ(newPoint.x, event.getX(0)); - ASSERT_EQ(newPoint.y, event.getY(0)); + ASSERT_NEAR(newPoint.x, event.getX(0), EPSILON); + ASSERT_NEAR(newPoint.y, event.getY(0), EPSILON); // The raw values should have the display transform applied. const auto raw = rawTransform.transform(60, 100); - ASSERT_EQ(raw.x, event.getRawX(0)); - ASSERT_EQ(raw.y, event.getRawY(0)); + ASSERT_NEAR(raw.x, event.getRawX(0), EPSILON); + ASSERT_NEAR(raw.y, event.getRawY(0), EPSILON); // Relative values should have the window transform applied without any translation. const auto rel = transformWithoutTranslation(transform, 42, 96); - ASSERT_EQ(rel.x, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0)); - ASSERT_EQ(rel.y, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0)); + ASSERT_NEAR(rel.x, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0), EPSILON); + ASSERT_NEAR(rel.y, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0), EPSILON); } TEST_F(MotionEventTest, Initialize_SetsClassification) { @@ -869,4 +893,42 @@ TEST_F(MotionEventTest, SetCursorPosition) { ASSERT_EQ(4, event.getYCursorPosition()); } +TEST_F(MotionEventTest, CoordinatesAreRoundedAppropriately) { + // These are specifically integral values, since we are testing for rounding. + const vec2 EXPECTED{400.f, 700.f}; + + // Pick a transform such that transforming the point with its inverse and bringing that + // back to the original coordinate space results in a non-zero error amount due to the + // nature of floating point arithmetics. This can happen when the display is scaled. + // For example, the 'adb shell wm size' command can be used to set an override for the + // logical display size, which could result in the display being scaled. + constexpr float scale = 720.f / 1080.f; + ui::Transform transform; + transform.set(scale, 0, 0, scale); + ASSERT_NE(EXPECTED, transform.transform(transform.inverse().transform(EXPECTED))); + + // Store the inverse-transformed values in the motion event. + const vec2 rawCoords = transform.inverse().transform(EXPECTED); + PointerCoords pc{}; + pc.setAxisValue(AMOTION_EVENT_AXIS_X, rawCoords.x); + pc.setAxisValue(AMOTION_EVENT_AXIS_Y, rawCoords.y); + PointerProperties pp{}; + MotionEvent event; + event.initialize(InputEvent::nextId(), 2, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, HMAC, + AMOTION_EVENT_ACTION_MOVE, 0, AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED, + AMOTION_EVENT_EDGE_FLAG_TOP, AMETA_ALT_ON, AMOTION_EVENT_BUTTON_PRIMARY, + MotionClassification::NONE, transform, 2.0f, 2.1f, rawCoords.x, rawCoords.y, + transform, ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME, 1, &pp, &pc); + + // When using the getters from the MotionEvent to obtain the coordinates, the transformed + // values should be rounded by an appropriate amount so that they now precisely equal the + // original coordinates. + ASSERT_EQ(EXPECTED.x, event.getX(0)); + ASSERT_EQ(EXPECTED.y, event.getY(0)); + ASSERT_EQ(EXPECTED.x, event.getRawX(0)); + ASSERT_EQ(EXPECTED.y, event.getRawY(0)); + ASSERT_EQ(EXPECTED.x, event.getXCursorPosition()); + ASSERT_EQ(EXPECTED.y, event.getYCursorPosition()); +} + } // namespace android diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp index 70e4fda662..57606e8bdc 100644 --- a/libs/input/tests/InputPublisherAndConsumer_test.cpp +++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp @@ -25,6 +25,8 @@ using android::base::Result; namespace android { +constexpr static float EPSILON = MotionEvent::ROUNDING_PRECISION; + class InputPublisherAndConsumerTest : public testing::Test { protected: std::shared_ptr mServerChannel, mClientChannel; @@ -226,10 +228,10 @@ void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() { EXPECT_EQ(yOffset, motionEvent->getYOffset()); EXPECT_EQ(xPrecision, motionEvent->getXPrecision()); EXPECT_EQ(yPrecision, motionEvent->getYPrecision()); - EXPECT_EQ(xCursorPosition, motionEvent->getRawXCursorPosition()); - EXPECT_EQ(yCursorPosition, motionEvent->getRawYCursorPosition()); - EXPECT_EQ(xCursorPosition * xScale + xOffset, motionEvent->getXCursorPosition()); - EXPECT_EQ(yCursorPosition * yScale + yOffset, motionEvent->getYCursorPosition()); + EXPECT_NEAR(xCursorPosition, motionEvent->getRawXCursorPosition(), EPSILON); + EXPECT_NEAR(yCursorPosition, motionEvent->getRawYCursorPosition(), EPSILON); + EXPECT_NEAR(xCursorPosition * xScale + xOffset, motionEvent->getXCursorPosition(), EPSILON); + EXPECT_NEAR(yCursorPosition * yScale + yOffset, motionEvent->getYCursorPosition(), EPSILON); EXPECT_EQ(rawTransform, motionEvent->getRawTransform()); EXPECT_EQ(downTime, motionEvent->getDownTime()); EXPECT_EQ(eventTime, motionEvent->getEventTime()); @@ -242,10 +244,12 @@ void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() { EXPECT_EQ(pointerProperties[i].toolType, motionEvent->getToolType(i)); const auto& pc = pointerCoords[i]; - EXPECT_EQ(pc.getX() * rawXScale + rawXOffset, motionEvent->getRawX(i)); - EXPECT_EQ(pc.getY() * rawYScale + rawYOffset, motionEvent->getRawY(i)); - EXPECT_EQ(pc.getX() * xScale + xOffset, motionEvent->getX(i)); - EXPECT_EQ(pc.getY() * yScale + yOffset, motionEvent->getY(i)); + EXPECT_EQ(pc, motionEvent->getSamplePointerCoords()[i]); + + EXPECT_NEAR(pc.getX() * rawXScale + rawXOffset, motionEvent->getRawX(i), EPSILON); + EXPECT_NEAR(pc.getY() * rawYScale + rawYOffset, motionEvent->getRawY(i), EPSILON); + EXPECT_NEAR(pc.getX() * xScale + xOffset, motionEvent->getX(i), EPSILON); + EXPECT_NEAR(pc.getY() * yScale + yOffset, motionEvent->getY(i), EPSILON); EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), motionEvent->getPressure(i)); EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_SIZE), motionEvent->getSize(i)); EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), motionEvent->getTouchMajor(i)); diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index e4ba24153d..3586a5e3b1 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -230,10 +230,10 @@ public: const auto& motionEvent = static_cast(event); EXPECT_EQ(motionEvent.getEventTime(), args.eventTime); EXPECT_EQ(motionEvent.getAction(), args.action); - EXPECT_EQ(motionEvent.getX(0), point.x); - EXPECT_EQ(motionEvent.getY(0), point.y); - EXPECT_EQ(motionEvent.getRawX(0), point.x); - EXPECT_EQ(motionEvent.getRawY(0), point.y); + EXPECT_NEAR(motionEvent.getX(0), point.x, MotionEvent::ROUNDING_PRECISION); + EXPECT_NEAR(motionEvent.getY(0), point.y, MotionEvent::ROUNDING_PRECISION); + EXPECT_NEAR(motionEvent.getRawX(0), point.x, MotionEvent::ROUNDING_PRECISION); + EXPECT_NEAR(motionEvent.getRawY(0), point.y, MotionEvent::ROUNDING_PRECISION); }); } -- GitLab From f707520f76ef556549edf9546cea513041e1ff2b Mon Sep 17 00:00:00 2001 From: ramindani Date: Fri, 10 Mar 2023 00:24:34 +0000 Subject: [PATCH 0995/1310] SF: Select the highest render rate for the min refresh rate when order requested is RefreshRateOrder::Ascending BUG: 268157826 Test: RefreshRateSelectorTest Change-Id: Id75aeaf93d9ece40395253e18a8d6860aa52a7a1 --- .../surfaceflinger/Scheduler/RefreshRateSelector.cpp | 10 ++++++++-- .../tests/unittests/RefreshRateSelectorTest.cpp | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index f6fe468dd6..d35e6d8476 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -958,7 +958,7 @@ auto RefreshRateSelector::rankFrameRates(std::optional anchorGroupOpt, } const bool ascending = (refreshRateOrder == RefreshRateOrder::Ascending); - const auto id = frameRateMode.modePtr->getId(); + const auto id = modePtr->getId(); if (ascending && frameRateMode.fps < *maxRenderRateForMode.get(id)) { // TODO(b/266481656): Once this bug is fixed, we can remove this workaround and actually // use a lower frame rate when we want Ascending frame rates. @@ -970,14 +970,20 @@ auto RefreshRateSelector::rankFrameRates(std::optional anchorGroupOpt, if (ascending) { score = 1.0f / score; } + + constexpr float kScore = std::numeric_limits::max(); if (preferredDisplayModeOpt) { if (*preferredDisplayModeOpt == modePtr->getId()) { - constexpr float kScore = std::numeric_limits::max(); ranking.emplace_front(ScoredFrameRate{frameRateMode, kScore}); return; } constexpr float kNonPreferredModePenalty = 0.95f; score *= kNonPreferredModePenalty; + } else if (ascending && id == getMinRefreshRateByPolicyLocked()->getId()) { + // TODO(b/266481656): Once this bug is fixed, we can remove this workaround + // and actually use a lower frame rate when we want Ascending frame rates. + ranking.emplace_front(ScoredFrameRate{frameRateMode, kScore}); + return; } ALOGV("%s(%s) %s (%s) scored %.2f", whence, ftl::enum_string(refreshRateOrder).c_str(), diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp index f4d052d359..b169931000 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp @@ -2981,6 +2981,12 @@ TEST_P(RefreshRateSelectorTest, noLowerFrameRateOnMinVote) { layers[0].name = "Test layer"; layers[0].vote = LayerVoteType::Min; EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers).frameRateMode); + + constexpr FpsRanges kCappedAt60 = {{30_Hz, 90_Hz}, {30_Hz, 60_Hz}}; + EXPECT_EQ(SetPolicyResult::Changed, + selector.setDisplayManagerPolicy( + {DisplayModeId(kModeId60), kCappedAt60, kCappedAt60})); + EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers).frameRateMode); } TEST_P(RefreshRateSelectorTest, frameRateIsCappedByPolicy) { -- GitLab From 191eeb8f65c9d16620847b4f40c81d8f55b59bc0 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Sat, 11 Mar 2023 10:16:07 -0800 Subject: [PATCH 0996/1310] [LayerTraceGenerator] Ignore unknown handles Ignore unknown handles when iteroping with legacy front end. In the old world, we would create child layers which are not necessary with the new front end. This means we will get notified for handle changes that dont exist in the new front end. Test: presubmit Bug: 255901752 Change-Id: I669bcd7d635db6fa86cd3a64fc260178c2a85580 --- services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp | 6 ++++-- services/surfaceflinger/FrontEnd/LayerLifecycleManager.h | 5 ++++- .../surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp index fe42422f6f..3706225228 100644 --- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp +++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp @@ -71,12 +71,14 @@ void LayerLifecycleManager::addLayers(std::vector& destroyedHandles) { +void LayerLifecycleManager::onHandlesDestroyed(const std::vector& destroyedHandles, + bool ignoreUnknownHandles) { std::vector layersToBeDestroyed; for (const auto& layerId : destroyedHandles) { auto it = mIdToLayer.find(layerId); if (it == mIdToLayer.end()) { - LOG_ALWAYS_FATAL("%s Layerid not found %d", __func__, layerId); + LOG_ALWAYS_FATAL_IF(!ignoreUnknownHandles, "%s Layerid not found %d", __func__, + layerId); continue; } RequestedLayerState& layer = it->second.owner; diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h index 25d27ee373..3d9a74c843 100644 --- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h +++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h @@ -40,7 +40,10 @@ public: // External state changes should be updated in the following order: void addLayers(std::vector>); void applyTransactions(const std::vector&); - void onHandlesDestroyed(const std::vector&); + // Ignore unknown handles when iteroping with legacy front end. In the old world, we + // would create child layers which are not necessary with the new front end. This means + // we will get notified for handle changes that don't exist in the new front end. + void onHandlesDestroyed(const std::vector&, bool ignoreUnknownHandles = false); // Detaches the layer from its relative parent to prevent a loop in the // layer hierarchy. This overrides the RequestedLayerState and leaves diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index 915a689fbd..0a7101ccce 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -105,7 +105,7 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, // apply updates lifecycleManager.addLayers(std::move(addedLayers)); lifecycleManager.applyTransactions(transactions); - lifecycleManager.onHandlesDestroyed(destroyedHandles); + lifecycleManager.onHandlesDestroyed(destroyedHandles, /*ignoreUnknownHandles=*/true); if (lifecycleManager.getGlobalChanges().test( frontend::RequestedLayerState::Changes::Hierarchy)) { -- GitLab From 39872bc1b63e955e376f30d2d2012cb6c8becdc9 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Sat, 11 Mar 2023 12:48:31 -0800 Subject: [PATCH 0997/1310] [LayerTraceGenerator] fix mirrors and rel-z data Now that we are using the new front end to generate layers trace, we need to make a few fixes so we can generate data that's parsable by winscope. This means use a unique id instead of the layer id to identify layers. Test: presubmit Fixes: 255901752 Change-Id: Ib30f7d8e2a5575803c455f947a1fd671095ea7e0 --- .../surfaceflinger/FrontEnd/LayerHierarchy.h | 10 ++ .../surfaceflinger/FrontEnd/LayerSnapshot.cpp | 11 +- .../FrontEnd/LayerSnapshotBuilder.h | 14 +-- services/surfaceflinger/LayerProtoHelper.cpp | 111 +++++++++++++----- services/surfaceflinger/LayerProtoHelper.h | 41 ++++++- services/surfaceflinger/SurfaceFlinger.cpp | 14 +-- .../Tracing/tools/LayerTraceGenerator.cpp | 25 +--- .../surfaceflinger/layerproto/layers.proto | 2 + .../tracing/TransactionTraceTestSuite.cpp | 20 +++- 9 files changed, 160 insertions(+), 88 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h index 3dd89badff..b25b731356 100644 --- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h @@ -104,6 +104,16 @@ public: static const TraversalPath ROOT; }; + struct TraversalPathHash { + std::size_t operator()(const LayerHierarchy::TraversalPath& key) const { + uint32_t hashCode = key.id * 31; + if (key.mirrorRootId != UNASSIGNED_LAYER_ID) { + hashCode += key.mirrorRootId * 31; + } + return std::hash{}(hashCode); + } + }; + // Helper class to add nodes to an existing traversal id and removes the // node when it goes out of scope. class ScopedAddToTraversalPath { diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp index 8a450933f0..5e7c25946b 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp @@ -28,9 +28,14 @@ LayerSnapshot::LayerSnapshot(const RequestedLayerState& state, const LayerHierarchy::TraversalPath& path) : path(path) { static uint32_t sUniqueSequenceId = 0; - // Provide a unique id for clones otherwise keeping using the sequence id. - // The seq id can still be useful for debugging if its available. - uniqueSequence = (path.isClone()) ? sUniqueSequenceId++ : state.id; + // Provide a unique id for all snapshots. + // A front end layer can generate multiple snapshots if its mirrored. + // Additionally, if the layer is not reachable, we may choose to destroy + // and recreate the snapshot in which case the unique sequence id will + // change. The consumer shouldn't tie any lifetimes to this unique id but + // register a LayerLifecycleManager::ILifecycleListener or get a list of + // destroyed layers from LayerLifecycleManager. + uniqueSequence = sUniqueSequenceId++; sequence = static_cast(state.id); name = state.name; textureName = state.textureName; diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h index 3997a0ad83..7b1ff2710b 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h @@ -68,6 +68,7 @@ public: void update(const Args&); std::vector>& getSnapshots(); LayerSnapshot* getSnapshot(uint32_t layerId) const; + LayerSnapshot* getSnapshot(const LayerHierarchy::TraversalPath& id) const; typedef std::function ConstVisitor; @@ -86,7 +87,6 @@ public: private: friend class LayerSnapshotTest; - LayerSnapshot* getSnapshot(const LayerHierarchy::TraversalPath& id) const; static LayerSnapshot getRootSnapshot(); // return true if we were able to successfully update the snapshots via @@ -120,16 +120,8 @@ private: void updateChildState(LayerSnapshot& snapshot, const LayerSnapshot& childSnapshot, const Args& args); - struct TraversalPathHash { - std::size_t operator()(const LayerHierarchy::TraversalPath& key) const { - uint32_t hashCode = key.id * 31; - if (key.mirrorRootId != UNASSIGNED_LAYER_ID) { - hashCode += key.mirrorRootId * 31; - } - return std::hash{}(hashCode); - } - }; - std::unordered_map + std::unordered_map mIdToSnapshot; std::vector> mSnapshots; LayerSnapshot mRootSnapshot; diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp index 5c91b9181b..b5ae1a7f5a 100644 --- a/services/surfaceflinger/LayerProtoHelper.cpp +++ b/services/surfaceflinger/LayerProtoHelper.cpp @@ -15,6 +15,8 @@ */ // TODO(b/129481165): remove the #pragma below and fix conversion issues +#include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/LayerSnapshot.h" #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wconversion" #pragma clang diagnostic ignored "-Wextra" @@ -248,47 +250,88 @@ void LayerProtoHelper::readFromProto(const BlurRegion& proto, android::BlurRegio outRegion.bottom = proto.bottom(); } -void LayerProtoHelper::writeHierarchyToProto( - LayersProto& outLayersProto, const frontend::LayerHierarchy& root, - const frontend::LayerSnapshotBuilder& snapshotBuilder, - const std::unordered_map>& legacyLayers, uint32_t traceFlags) { - using Variant = frontend::LayerHierarchy::Variant; - frontend::LayerSnapshot defaultSnapshot; - - LayerProto* layerProto = outLayersProto.add_layers(); - const frontend::RequestedLayerState& layer = *root.getLayer(); - frontend::LayerSnapshot* snapshot = snapshotBuilder.getSnapshot(layer.id); +LayersProto LayerProtoFromSnapshotGenerator::generate(const frontend::LayerHierarchy& root) { + mLayersProto.clear_layers(); + std::unordered_set stackIdsToSkip; + if ((mTraceFlags & LayerTracing::TRACE_VIRTUAL_DISPLAYS) == 0) { + for (const auto& [layerStack, displayInfo] : mDisplayInfos) { + if (displayInfo.isVirtual) { + stackIdsToSkip.insert(layerStack.id); + } + } + } - if (!snapshot) { - defaultSnapshot.uniqueSequence = layer.id; - snapshot = &defaultSnapshot; + frontend::LayerHierarchy::TraversalPath path = frontend::LayerHierarchy::TraversalPath::ROOT; + for (auto& [child, variant] : root.mChildren) { + if (variant != frontend::LayerHierarchy::Variant::Attached || + stackIdsToSkip.find(child->getLayer()->layerStack.id) != stackIdsToSkip.end()) { + continue; + } + frontend::LayerHierarchy::ScopedAddToTraversalPath addChildToPath(path, + child->getLayer()->id, + variant); + LayerProtoFromSnapshotGenerator::writeHierarchyToProto(*child, path); } - writeSnapshotToProto(layerProto, layer, *snapshot, traceFlags); - for (const auto& [child, variant] : root.mChildren) { - if (variant == Variant::Attached || variant == Variant::Detached) { - layerProto->add_children(child->getLayer()->id); - } else if (variant == Variant::Relative) { - layerProto->add_relatives(child->getLayer()->id); + + // fill in relative and parent info + for (int i = 0; i < mLayersProto.layers_size(); i++) { + auto layerProto = mLayersProto.mutable_layers()->Mutable(i); + auto it = mChildToRelativeParent.find(layerProto->id()); + if (it == mChildToRelativeParent.end()) { + layerProto->set_z_order_relative_of(-1); + } else { + layerProto->set_z_order_relative_of(it->second); + } + it = mChildToParent.find(layerProto->id()); + if (it == mChildToParent.end()) { + layerProto->set_parent(-1); + } else { + layerProto->set_parent(it->second); } } - auto parent = root.getParent(); - if (parent && parent->getLayer()) { - layerProto->set_parent(parent->getLayer()->id); + mDefaultSnapshots.clear(); + mChildToRelativeParent.clear(); + return std::move(mLayersProto); +} + +frontend::LayerSnapshot* LayerProtoFromSnapshotGenerator::getSnapshot( + frontend::LayerHierarchy::TraversalPath& path, const frontend::RequestedLayerState& layer) { + frontend::LayerSnapshot* snapshot = mSnapshotBuilder.getSnapshot(path); + if (snapshot) { + return snapshot; } else { - layerProto->set_parent(-1); + mDefaultSnapshots[path] = frontend::LayerSnapshot(layer, path); + return &mDefaultSnapshots[path]; } +} - auto relativeParent = root.getRelativeParent(); - if (relativeParent && relativeParent->getLayer()) { - layerProto->set_z_order_relative_of(relativeParent->getLayer()->id); - } else { - layerProto->set_z_order_relative_of(-1); +void LayerProtoFromSnapshotGenerator::writeHierarchyToProto( + const frontend::LayerHierarchy& root, frontend::LayerHierarchy::TraversalPath& path) { + using Variant = frontend::LayerHierarchy::Variant; + LayerProto* layerProto = mLayersProto.add_layers(); + const frontend::RequestedLayerState& layer = *root.getLayer(); + frontend::LayerSnapshot* snapshot = getSnapshot(path, layer); + LayerProtoHelper::writeSnapshotToProto(layerProto, layer, *snapshot, mTraceFlags); + + for (const auto& [child, variant] : root.mChildren) { + frontend::LayerHierarchy::ScopedAddToTraversalPath addChildToPath(path, + child->getLayer()->id, + variant); + frontend::LayerSnapshot* childSnapshot = getSnapshot(path, layer); + if (variant == Variant::Attached || variant == Variant::Detached || + variant == Variant::Mirror) { + mChildToParent[childSnapshot->uniqueSequence] = snapshot->uniqueSequence; + layerProto->add_children(childSnapshot->uniqueSequence); + } else if (variant == Variant::Relative) { + mChildToRelativeParent[childSnapshot->uniqueSequence] = snapshot->uniqueSequence; + layerProto->add_relatives(childSnapshot->uniqueSequence); + } } - if (traceFlags & LayerTracing::TRACE_COMPOSITION) { - auto it = legacyLayers.find(layer.id); - if (it != legacyLayers.end()) { + if (mTraceFlags & LayerTracing::TRACE_COMPOSITION) { + auto it = mLegacyLayers.find(layer.id); + if (it != mLegacyLayers.end()) { it->second->writeCompositionStateToProto(layerProto); } } @@ -298,7 +341,10 @@ void LayerProtoHelper::writeHierarchyToProto( if (variant == Variant::Detached) { continue; } - writeHierarchyToProto(outLayersProto, *child, snapshotBuilder, legacyLayers, traceFlags); + frontend::LayerHierarchy::ScopedAddToTraversalPath addChildToPath(path, + child->getLayer()->id, + variant); + writeHierarchyToProto(*child, path); } } @@ -345,6 +391,7 @@ void LayerProtoHelper::writeSnapshotToProto(LayerProto* layerInfo, layerInfo->set_shadow_radius(snapshot.shadowRadius); layerInfo->set_id(snapshot.uniqueSequence); + layerInfo->set_original_id(snapshot.sequence); layerInfo->set_name(requestedState.name); layerInfo->set_type("Layer"); diff --git a/services/surfaceflinger/LayerProtoHelper.h b/services/surfaceflinger/LayerProtoHelper.h index 38d73f613f..b84a49b27c 100644 --- a/services/surfaceflinger/LayerProtoHelper.h +++ b/services/surfaceflinger/LayerProtoHelper.h @@ -25,6 +25,9 @@ #include #include #include +#include +#include "FrontEnd/LayerHierarchy.h" +#include "FrontEnd/LayerSnapshot.h" namespace android { namespace surfaceflinger { @@ -58,11 +61,6 @@ public: static void readFromProto(const ColorTransformProto& colorTransformProto, mat4& matrix); static void writeToProto(const android::BlurRegion region, BlurRegion*); static void readFromProto(const BlurRegion& proto, android::BlurRegion& outRegion); - static void writeHierarchyToProto(LayersProto& layersProto, - const frontend::LayerHierarchy& root, - const frontend::LayerSnapshotBuilder& snapshotBuilder, - const std::unordered_map>& mLegacyLayers, - uint32_t traceFlags); static void writeSnapshotToProto(LayerProto* outProto, const frontend::RequestedLayerState& requestedState, const frontend::LayerSnapshot& snapshot, uint32_t traceFlags); @@ -70,5 +68,38 @@ public: const display::DisplayMap& displayInfos); }; +class LayerProtoFromSnapshotGenerator { +public: + LayerProtoFromSnapshotGenerator( + const frontend::LayerSnapshotBuilder& snapshotBuilder, + const display::DisplayMap& displayInfos, + const std::unordered_map>& legacyLayers, uint32_t traceFlags) + : mSnapshotBuilder(snapshotBuilder), + mLegacyLayers(legacyLayers), + mDisplayInfos(displayInfos), + mTraceFlags(traceFlags) {} + LayersProto generate(const frontend::LayerHierarchy& root); + +private: + void writeHierarchyToProto(const frontend::LayerHierarchy& root, + frontend::LayerHierarchy::TraversalPath& path); + frontend::LayerSnapshot* getSnapshot(frontend::LayerHierarchy::TraversalPath& path, + const frontend::RequestedLayerState& layer); + + const frontend::LayerSnapshotBuilder& mSnapshotBuilder; + const std::unordered_map>& mLegacyLayers; + const display::DisplayMap& mDisplayInfos; + uint32_t mTraceFlags; + LayersProto mLayersProto; + // winscope expects all the layers, so provide a snapshot even if it not currently drawing + std::unordered_map + mDefaultSnapshots; + std::unordered_map + mChildToRelativeParent; + std::unordered_map + mChildToParent; +}; + } // namespace surfaceflinger } // namespace android diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index e47a147ed8..01a7df398b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5786,17 +5786,9 @@ LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const { return layersProto; } - const frontend::LayerHierarchy& root = mLayerHierarchyBuilder.getHierarchy(); - LayersProto layersProto; - for (auto& [child, variant] : root.mChildren) { - if (variant != frontend::LayerHierarchy::Variant::Attached || - stackIdsToSkip.find(child->getLayer()->layerStack.id) != stackIdsToSkip.end()) { - continue; - } - LayerProtoHelper::writeHierarchyToProto(layersProto, *child, mLayerSnapshotBuilder, - mLegacyLayers, traceFlags); - } - return layersProto; + return LayerProtoFromSnapshotGenerator(mLayerSnapshotBuilder, mFrontEndDisplayInfos, {}, + traceFlags) + .generate(mLayerHierarchyBuilder.getHierarchy()); } google::protobuf::RepeatedPtrField SurfaceFlinger::dumpDisplayProto() const { diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index 0a7101ccce..2418cf22e5 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -134,29 +134,10 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, lifecycleManager.getGlobalChanges().string().c_str()); lifecycleManager.commitChanges(); - // write layers trace - auto tracingFlags = LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS; - std::unordered_set stackIdsToSkip; - if ((tracingFlags & LayerTracing::TRACE_VIRTUAL_DISPLAYS) == 0) { - for (const auto& displayInfo : displayInfos) { - if (displayInfo.second.isVirtual) { - stackIdsToSkip.insert(displayInfo.first.id); - } - } - } - - const frontend::LayerHierarchy& root = hierarchyBuilder.getHierarchy(); - - LayersProto layersProto; - for (auto& [child, variant] : root.mChildren) { - if (variant != frontend::LayerHierarchy::Variant::Attached || - stackIdsToSkip.find(child->getLayer()->layerStack.id) != stackIdsToSkip.end()) { - continue; - } - LayerProtoHelper::writeHierarchyToProto(layersProto, *child, snapshotBuilder, {}, - tracingFlags); - } + LayersProto layersProto = LayerProtoFromSnapshotGenerator(snapshotBuilder, displayInfos, {}, + layerTracing.getFlags()) + .generate(hierarchyBuilder.getHierarchy()); auto displayProtos = LayerProtoHelper::writeDisplayInfoToProto(displayInfos); layerTracing.notify(visibleRegionsDirty, entry.elapsed_realtime_nanos(), entry.vsync_id(), &layersProto, {}, &displayProtos); diff --git a/services/surfaceflinger/layerproto/layers.proto b/services/surfaceflinger/layerproto/layers.proto index 3598308338..e9add2e1a4 100644 --- a/services/surfaceflinger/layerproto/layers.proto +++ b/services/surfaceflinger/layerproto/layers.proto @@ -138,6 +138,8 @@ message LayerProto { float requested_corner_radius = 56; RectProto destination_frame = 57; + + uint32 original_id = 58; } message PositionProto { diff --git a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp index 5f9214c548..7355c35131 100644 --- a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp +++ b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -84,9 +85,9 @@ protected: std::vector TransactionTraceTestSuite::sTransactionTraces{}; struct LayerInfo { - int id; + int32_t id; std::string name; - int parent; + int32_t parent; int z; uint64_t curr_frame; float x; @@ -143,16 +144,27 @@ TEST_P(TransactionTraceTestSuite, validateEndState) { expectedLayers.reserve(static_cast(expectedLastEntry.layers().layers_size())); for (int i = 0; i < expectedLastEntry.layers().layers_size(); i++) { auto layer = expectedLastEntry.layers().layers(i); - expectedLayers.push_back(getLayerInfoFromProto(layer)); + LayerInfo layerInfo = getLayerInfoFromProto(layer); + expectedLayers.push_back(layerInfo); } std::sort(expectedLayers.begin(), expectedLayers.end(), compareById); + std::unordered_map snapshotIdToLayerId; std::vector actualLayers; actualLayers.reserve(static_cast(actualLastEntry.layers().layers_size())); for (int i = 0; i < actualLastEntry.layers().layers_size(); i++) { auto layer = actualLastEntry.layers().layers(i); - actualLayers.push_back(getLayerInfoFromProto(layer)); + LayerInfo layerInfo = getLayerInfoFromProto(layer); + snapshotIdToLayerId[layerInfo.id] = static_cast(layer.original_id()); + actualLayers.push_back(layerInfo); + } + + for (auto& layer : actualLayers) { + layer.id = snapshotIdToLayerId[layer.id]; + auto it = snapshotIdToLayerId.find(layer.parent); + layer.parent = it == snapshotIdToLayerId.end() ? -1 : it->second; } + std::sort(actualLayers.begin(), actualLayers.end(), compareById); size_t i = 0; -- GitLab From 2b67ff1bda2323353b52c2762f0f0296b68c04bf Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Mon, 13 Mar 2023 11:32:06 +0000 Subject: [PATCH 0998/1310] Revert^2 "Support touchpad gesture properties in IDC files" a53cb97af2b4cbb247094d6be93583712dbee3c4 Change-Id: I5463f938665212362b8780e1c646a4b2bf8ad10a --- include/input/PropertyMap.h | 7 ++ libs/input/Android.bp | 1 + libs/input/PropertyMap.cpp | 29 +++++++ .../reader/mapper/TouchpadInputMapper.cpp | 6 ++ .../mapper/gestures/PropertyProvider.cpp | 76 +++++++++++++++++++ .../reader/mapper/gestures/PropertyProvider.h | 6 ++ .../tests/PropertyProvider_test.cpp | 65 ++++++++++++++++ services/inputflinger/tests/TestConstants.h | 4 + 8 files changed, 194 insertions(+) diff --git a/include/input/PropertyMap.h b/include/input/PropertyMap.h index 28e4816afe..18ce16df36 100644 --- a/include/input/PropertyMap.h +++ b/include/input/PropertyMap.h @@ -18,7 +18,10 @@ #include #include + +#include #include +#include namespace android { @@ -57,6 +60,9 @@ public: */ void addProperty(const std::string& key, const std::string& value); + /* Returns a set of all property keys starting with the given prefix. */ + std::unordered_set getKeysWithPrefix(const std::string& prefix) const; + /* Gets the value of a property and parses it. * Returns true and sets outValue if the key was found and its value was parsed successfully. * Otherwise returns false and does not modify outValue. (Also logs a warning.) @@ -65,6 +71,7 @@ public: bool tryGetProperty(const std::string& key, bool& outValue) const; bool tryGetProperty(const std::string& key, int32_t& outValue) const; bool tryGetProperty(const std::string& key, float& outValue) const; + bool tryGetProperty(const std::string& key, double& outValue) const; /* Adds all values from the specified property map. */ void addAll(const PropertyMap* map); diff --git a/libs/input/Android.bp b/libs/input/Android.bp index f38dd98428..869458c407 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -167,6 +167,7 @@ cc_library { cc_defaults { name: "libinput_fuzz_defaults", + cpp_std: "c++20", host_supported: true, shared_libs: [ "libutils", diff --git a/libs/input/PropertyMap.cpp b/libs/input/PropertyMap.cpp index ed9ac9fc72..9a4f10b21d 100644 --- a/libs/input/PropertyMap.cpp +++ b/libs/input/PropertyMap.cpp @@ -16,6 +16,8 @@ #define LOG_TAG "PropertyMap" +#include + #include #include @@ -44,6 +46,16 @@ void PropertyMap::addProperty(const std::string& key, const std::string& value) mProperties.emplace(key, value); } +std::unordered_set PropertyMap::getKeysWithPrefix(const std::string& prefix) const { + std::unordered_set keys; + for (const auto& [key, _] : mProperties) { + if (key.starts_with(prefix)) { + keys.insert(key); + } + } + return keys; +} + bool PropertyMap::hasProperty(const std::string& key) const { return mProperties.find(key) != mProperties.end(); } @@ -102,6 +114,23 @@ bool PropertyMap::tryGetProperty(const std::string& key, float& outValue) const return true; } +bool PropertyMap::tryGetProperty(const std::string& key, double& outValue) const { + std::string stringValue; + if (!tryGetProperty(key, stringValue) || stringValue.length() == 0) { + return false; + } + + char* end; + double value = strtod(stringValue.c_str(), &end); + if (*end != '\0') { + ALOGW("Property key '%s' has invalid value '%s'. Expected a double.", key.c_str(), + stringValue.c_str()); + return false; + } + outValue = value; + return true; +} + void PropertyMap::addAll(const PropertyMap* map) { for (const auto& [key, value] : map->mProperties) { mProperties.emplace(key, value); diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index d3af402153..330976719d 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -216,6 +217,11 @@ void TouchpadInputMapper::dump(std::string& dump) { std::list TouchpadInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) { + if (!changes) { + // First time configuration + mPropertyProvider.loadPropertiesFromIdcFile(getDeviceContext().getConfiguration()); + } + if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { std::optional displayId = mPointerController->getDisplayId(); ui::Rotation orientation = ui::ROTATION_0; diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp index 089f45a4e6..3d883389c9 100644 --- a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp +++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp @@ -84,6 +84,29 @@ std::string PropertyProvider::dump() const { return dump; } +void PropertyProvider::loadPropertiesFromIdcFile(const PropertyMap& idcProperties) { + // For compatibility with the configuration file syntax, gesture property names in IDC files are + // prefixed with "gestureProp." and have spaces replaced by underscores. So, for example, the + // configuration key "gestureProp.Palm_Width" refers to the "Palm Width" property. + const std::string gesturePropPrefix = "gestureProp."; + for (const std::string key : idcProperties.getKeysWithPrefix(gesturePropPrefix)) { + std::string propertyName = key.substr(gesturePropPrefix.length()); + for (size_t i = 0; i < propertyName.length(); i++) { + if (propertyName[i] == '_') { + propertyName[i] = ' '; + } + } + + auto it = mProperties.find(propertyName); + if (it != mProperties.end()) { + it->second.trySetFromIdcProperty(idcProperties, key); + } else { + ALOGE("Gesture property \"%s\" specified in IDC file does not exist for this device.", + propertyName.c_str()); + } + } +} + GesturesProp* PropertyProvider::createIntArrayProperty(const std::string& name, int* loc, size_t count, const int* init) { const auto [it, inserted] = @@ -211,6 +234,59 @@ void GesturesProp::setRealValues(const std::vector& values) { setValues(std::get(mDataPointer), values); } +namespace { + +// Helper to std::visit with lambdas. +template +struct Visitor : V... {}; +// explicit deduction guide (not needed as of C++20) +template +Visitor(V...) -> Visitor; + +} // namespace + +void GesturesProp::trySetFromIdcProperty(const android::PropertyMap& idcProperties, + const std::string& propertyName) { + if (mCount != 1) { + ALOGE("Gesture property \"%s\" is an array, and so cannot be set in an IDC file.", + mName.c_str()); + return; + } + bool parsedSuccessfully = false; + Visitor setVisitor{ + [&](int*) { + int32_t value; + parsedSuccessfully = idcProperties.tryGetProperty(propertyName, value); + if (parsedSuccessfully) { + setIntValues({value}); + } + }, + [&](GesturesPropBool*) { + bool value; + parsedSuccessfully = idcProperties.tryGetProperty(propertyName, value); + if (parsedSuccessfully) { + setBoolValues({value}); + } + }, + [&](double*) { + double value; + parsedSuccessfully = idcProperties.tryGetProperty(propertyName, value); + if (parsedSuccessfully) { + setRealValues({value}); + } + }, + [&](const char**) { + ALOGE("Gesture property \"%s\" is a string, and so cannot be set in an IDC file.", + mName.c_str()); + }, + }; + std::visit(setVisitor, mDataPointer); + + ALOGE_IF(!parsedSuccessfully, "Gesture property \"%s\" could set due to a type mismatch.", + mName.c_str()); + return; +} + template const std::vector GesturesProp::getValues(U* dataPointer) const { if (mGetter != nullptr) { diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.h b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h index 50451a3929..c7e0858c6d 100644 --- a/services/inputflinger/reader/mapper/gestures/PropertyProvider.h +++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.h @@ -22,6 +22,7 @@ #include #include "include/gestures.h" +#include "input/PropertyMap.h" namespace android { @@ -35,6 +36,8 @@ public: GesturesProp& getProperty(const std::string& name); std::string dump() const; + void loadPropertiesFromIdcFile(const PropertyMap& idcProperties); + // Methods to be called by the gestures library: GesturesProp* createIntArrayProperty(const std::string& name, int* loc, size_t count, const int* init); @@ -83,6 +86,9 @@ public: // Setting string values isn't supported since we don't have a use case yet and the memory // management adds additional complexity. + void trySetFromIdcProperty(const android::PropertyMap& idcProperties, + const std::string& propertyName); + private: // Two type parameters are required for these methods, rather than one, due to the gestures // library using its own bool type. diff --git a/services/inputflinger/tests/PropertyProvider_test.cpp b/services/inputflinger/tests/PropertyProvider_test.cpp index 42a6a9f4b9..8a40e78b89 100644 --- a/services/inputflinger/tests/PropertyProvider_test.cpp +++ b/services/inputflinger/tests/PropertyProvider_test.cpp @@ -18,6 +18,7 @@ #include #include +#include "TestConstants.h" #include "include/gestures.h" namespace android { @@ -283,4 +284,68 @@ TEST_F(PropertyProviderTest, Free) { EXPECT_FALSE(mProvider.hasProperty("Foo")); } +class PropertyProviderIdcLoadingTest : public testing::Test { +protected: + void SetUp() override { + int initialInt = 0; + GesturesPropBool initialBool = false; + double initialReal = 0.0; + gesturePropProvider.create_int_fn(&mProvider, "An Integer", &mIntData, 1, &initialInt); + gesturePropProvider.create_bool_fn(&mProvider, "A Boolean", &mBoolData, 1, &initialBool); + gesturePropProvider.create_real_fn(&mProvider, "A Real", &mRealData, 1, &initialReal); + } + + PropertyProvider mProvider; + + int mIntData; + GesturesPropBool mBoolData; + double mRealData; +}; + +TEST_F(PropertyProviderIdcLoadingTest, AllCorrect) { + PropertyMap idcProps; + idcProps.addProperty("gestureProp.An_Integer", "42"); + idcProps.addProperty("gestureProp.A_Boolean", "1"); + idcProps.addProperty("gestureProp.A_Real", "3.14159"); + + mProvider.loadPropertiesFromIdcFile(idcProps); + EXPECT_THAT(mProvider.getProperty("An Integer").getIntValues(), ElementsAre(42)); + EXPECT_THAT(mProvider.getProperty("A Boolean").getBoolValues(), ElementsAre(true)); + EXPECT_NEAR(mProvider.getProperty("A Real").getRealValues()[0], 3.14159, EPSILON); +} + +TEST_F(PropertyProviderIdcLoadingTest, InvalidPropsIgnored) { + int intArrayData[2]; + int initialInts[2] = {0, 1}; + gesturePropProvider.create_int_fn(&mProvider, "Two Integers", intArrayData, 2, initialInts); + + PropertyMap idcProps; + // Wrong type + idcProps.addProperty("gestureProp.An_Integer", "37.25"); + // Wrong size + idcProps.addProperty("gestureProp.Two_Integers", "42"); + // Doesn't exist + idcProps.addProperty("gestureProp.Some_Nonexistent_Property", "1"); + // A valid assignment that should still be applied despite the others being invalid + idcProps.addProperty("gestureProp.A_Real", "3.14159"); + + mProvider.loadPropertiesFromIdcFile(idcProps); + EXPECT_THAT(mProvider.getProperty("An Integer").getIntValues(), ElementsAre(0)); + EXPECT_THAT(mProvider.getProperty("Two Integers").getIntValues(), ElementsAre(0, 1)); + EXPECT_FALSE(mProvider.hasProperty("Some Nonexistent Property")); + EXPECT_NEAR(mProvider.getProperty("A Real").getRealValues()[0], 3.14159, EPSILON); +} + +TEST_F(PropertyProviderIdcLoadingTest, FunkyName) { + int data; + int initialData = 0; + gesturePropProvider.create_int_fn(&mProvider, " I lOvE sNAKes ", &data, 1, &initialData); + + PropertyMap idcProps; + idcProps.addProperty("gestureProp.__I_lOvE_sNAKes_", "42"); + + mProvider.loadPropertiesFromIdcFile(idcProps); + EXPECT_THAT(mProvider.getProperty(" I lOvE sNAKes ").getIntValues(), ElementsAre(42)); +} + } // namespace android diff --git a/services/inputflinger/tests/TestConstants.h b/services/inputflinger/tests/TestConstants.h index 27881f6f49..ad48b0fbe0 100644 --- a/services/inputflinger/tests/TestConstants.h +++ b/services/inputflinger/tests/TestConstants.h @@ -16,6 +16,10 @@ #pragma once +#include + +#include + namespace android { using std::chrono_literals::operator""ms; -- GitLab From 82c791cdbb1101cf2c8b19be537368f28e2b1a0a Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Fri, 10 Mar 2023 17:15:07 +0000 Subject: [PATCH 0999/1310] libs/input: make parameter comment style consistent The Google C++ style guide [0] suggests that parameter name comments should be of the form `/*paramName=*/value`. This potentially allows tooling to check that the parameter names are correct, too. [0]: https://google.github.io/styleguide/cppguide.html#Function_Argument_Comments Bug: 245989146 Test: atest inputflinger_tests Change-Id: Id5207b5a621c9a1ac0a46a8b2ff2bbf9aa43e726 --- libs/input/TouchVideoFrame.cpp | 4 +- libs/input/VelocityTracker.cpp | 12 ++-- libs/input/tests/InputEvent_test.cpp | 28 ++++----- .../tests/InputPublisherAndConsumer_test.cpp | 12 ++-- libs/input/tests/TouchResampling_test.cpp | 14 ++--- libs/input/tests/VelocityTracker_test.cpp | 58 +++++++++---------- libs/input/tests/VerifiedInputEvent_test.cpp | 18 +++--- 7 files changed, 73 insertions(+), 73 deletions(-) diff --git a/libs/input/TouchVideoFrame.cpp b/libs/input/TouchVideoFrame.cpp index c9393f4451..6d7d5614c6 100644 --- a/libs/input/TouchVideoFrame.cpp +++ b/libs/input/TouchVideoFrame.cpp @@ -43,13 +43,13 @@ const struct timeval& TouchVideoFrame::getTimestamp() const { return mTimestamp; void TouchVideoFrame::rotate(ui::Rotation orientation) { switch (orientation) { case ui::ROTATION_90: - rotateQuarterTurn(false /*clockwise*/); + rotateQuarterTurn(/*clockwise=*/false); break; case ui::ROTATION_180: rotate180(); break; case ui::ROTATION_270: - rotateQuarterTurn(true /*clockwise*/); + rotateQuarterTurn(/*clockwise=*/true); break; case ui::ROTATION_0: // No need to rotate if there's no rotation. diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp index 3632914b93..8551e5fa1c 100644 --- a/libs/input/VelocityTracker.cpp +++ b/libs/input/VelocityTracker.cpp @@ -157,10 +157,10 @@ void VelocityTracker::configureStrategy(int32_t axis) { std::unique_ptr createdStrategy; if (mOverrideStrategy != VelocityTracker::Strategy::DEFAULT) { - createdStrategy = createStrategy(mOverrideStrategy, isDifferentialAxis /* deltaValues */); + createdStrategy = createStrategy(mOverrideStrategy, /*deltaValues=*/isDifferentialAxis); } else { createdStrategy = createStrategy(DEFAULT_STRATEGY_BY_AXIS.at(axis), - isDifferentialAxis /* deltaValues */); + /*deltaValues=*/isDifferentialAxis); } LOG_ALWAYS_FATAL_IF(createdStrategy == nullptr, @@ -495,7 +495,7 @@ static bool solveLeastSquares(const std::vector& x, const std::vector& x, const std::vector& x, const std::vectorconsume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event); + status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event); ASSERT_EQ(OK, status) << "consumer consume should return OK"; @@ -199,7 +199,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() { uint32_t consumeSeq; InputEvent* event; - status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event); + status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event); ASSERT_EQ(OK, status) << "consumer consume should return OK"; @@ -290,7 +290,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeFocusEvent() { uint32_t consumeSeq; InputEvent* event; - status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event); + status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event); ASSERT_EQ(OK, status) << "consumer consume should return OK"; ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event"; @@ -331,7 +331,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeCaptureEvent() { uint32_t consumeSeq; InputEvent* event; - status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event); + status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event); ASSERT_EQ(OK, status) << "consumer consume should return OK"; ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event"; @@ -373,7 +373,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeDragEvent() { uint32_t consumeSeq; InputEvent* event; - status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event); + status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event); ASSERT_EQ(OK, status) << "consumer consume should return OK"; ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event"; @@ -415,7 +415,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeTouchModeEvent() { uint32_t consumeSeq; InputEvent* event; - status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event); + status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq, &event); ASSERT_EQ(OK, status) << "consumer consume should return OK"; ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event"; diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp index d01258c783..7cb9526af0 100644 --- a/libs/input/tests/TouchResampling_test.cpp +++ b/libs/input/tests/TouchResampling_test.cpp @@ -56,7 +56,7 @@ protected: mPublisher = std::make_unique(std::move(serverChannel)); mConsumer = std::make_unique(std::move(clientChannel), - true /* enableTouchResampling */); + /*enableTouchResampling=*/true); } status_t publishSimpleMotionEventWithCoords(int32_t action, nsecs_t eventTime, @@ -79,11 +79,11 @@ status_t TouchResamplingTest::publishSimpleMotionEventWithCoords( if (action == AMOTION_EVENT_ACTION_DOWN && eventTime != 0) { ADD_FAILURE() << "Downtime should be equal to 0 (hardcoded for convenience)"; } - return mPublisher->publishMotionEvent(mSeq++, InputEvent::nextId(), 1 /*deviceId*/, - AINPUT_SOURCE_TOUCHSCREEN, 0 /*displayId*/, INVALID_HMAC, - action, 0 /*actionButton*/, 0 /*flags*/, 0 /*edgeFlags*/, - AMETA_NONE, 0 /*buttonState*/, MotionClassification::NONE, - identityTransform, 0 /*xPrecision*/, 0 /*yPrecision*/, + return mPublisher->publishMotionEvent(mSeq++, InputEvent::nextId(), /*deviceId=*/1, + AINPUT_SOURCE_TOUCHSCREEN, /*displayId=*/0, INVALID_HMAC, + action, /*actionButton=*/0, /*flags=*/0, /*edgeFlags=*/0, + AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, + identityTransform, /*xPrecision=*/0, /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, downTime, eventTime, properties.size(), properties.data(), @@ -161,7 +161,7 @@ void TouchResamplingTest::consumeInputEventEntries(const std::vectorconsume(&mEventFactory, true /*consumeBatches*/, frameTime.count(), + status_t status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, frameTime.count(), &consumeSeq, &event); ASSERT_EQ(OK, status); MotionEvent* motionEvent = static_cast(event); diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp index c6ad3a2218..027757973b 100644 --- a/libs/input/tests/VelocityTracker_test.cpp +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -159,13 +159,13 @@ static std::vector createAxisScrollMotionEventStream( MotionEvent event; ui::Transform identityTransform; - event.initialize(InputEvent::nextId(), 5 /*deviceId*/, AINPUT_SOURCE_ROTARY_ENCODER, + event.initialize(InputEvent::nextId(), /*deviceId=*/5, AINPUT_SOURCE_ROTARY_ENCODER, ADISPLAY_ID_NONE, INVALID_HMAC, AMOTION_EVENT_ACTION_SCROLL, - 0 /*actionButton*/, 0 /*flags*/, AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, - 0 /*buttonState*/, MotionClassification::NONE, identityTransform, - 0 /*xPrecision*/, 0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION, - AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0 /*downTime*/, - timeStamp.count(), 1 /*pointerCount*/, properties, coords); + /*actionButton=*/0, /*flags=*/0, AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, + /*buttonState=*/0, MotionClassification::NONE, identityTransform, + /*xPrecision=*/0, /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, /*downTime=*/0, + timeStamp.count(), /*pointerCount=*/1, properties, coords); events.emplace_back(event); } @@ -219,12 +219,12 @@ static std::vector createTouchMotionEventStream( MotionEvent event; ui::Transform identityTransform; - event.initialize(InputEvent::nextId(), 0 /*deviceId*/, AINPUT_SOURCE_TOUCHSCREEN, - DISPLAY_ID, INVALID_HMAC, action, 0 /*actionButton*/, 0 /*flags*/, - AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0 /*buttonState*/, - MotionClassification::NONE, identityTransform, 0 /*xPrecision*/, - 0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION, - AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0 /*downTime*/, + event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_TOUCHSCREEN, + DISPLAY_ID, INVALID_HMAC, action, /*actionButton=*/0, /*flags=*/0, + AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0, + MotionClassification::NONE, identityTransform, /*xPrecision=*/0, + /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, /*downTime=*/0, entry.eventTime.count(), pointerCount, properties, coords); events.emplace_back(event); @@ -341,23 +341,23 @@ TEST_F(VelocityTrackerTest, TestDefaultStrategiesForPlanarAxes) { TEST_F(VelocityTrackerTest, TestComputedVelocity) { VelocityTracker::ComputedVelocity computedVelocity; - computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, 0 /*id*/, 200 /*velocity*/); - computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, 26U /*id*/, 400 /*velocity*/); - computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, 27U /*id*/, 650 /*velocity*/); - computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID, 750 /*velocity*/); - computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, 0 /*id*/, 1000 /*velocity*/); - computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, 26U /*id*/, 2000 /*velocity*/); - computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, 27U /*id*/, 3000 /*velocity*/); - computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, MAX_POINTER_ID, 4000 /*velocity*/); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, /*id=*/0, /*velocity=*/200); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, /*id=*/26U, /*velocity=*/400); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, /*id=*/27U, /*velocity=*/650); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID, /*velocity=*/750); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, /*id=*/0, /*velocity=*/1000); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, /*id=*/26U, /*velocity=*/2000); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, /*id=*/27U, /*velocity=*/3000); + computedVelocity.addVelocity(AMOTION_EVENT_AXIS_Y, MAX_POINTER_ID, /*velocity=*/4000); // Check the axes/indices with velocity. - EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, 0U /*id*/)), 200); - EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, 26U /*id*/)), 400); - EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, 27U /*id*/)), 650); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, /*id=*/0U)), 200); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, /*id=*/26U)), 400); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, /*id=*/27U)), 650); EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID)), 750); - EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, 0U /*id*/)), 1000); - EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, 26U /*id*/)), 2000); - EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, 27U /*id*/)), 3000); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, /*id=*/0U)), 1000); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, /*id=*/26U)), 2000); + EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, /*id=*/27U)), 3000); EXPECT_EQ(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_Y, MAX_POINTER_ID)), 4000); for (uint32_t id = 0; id <= MAX_POINTER_ID; id++) { // Since no data was added for AXIS_SCROLL, expect empty value for the axis for any id. @@ -436,17 +436,17 @@ TEST_F(VelocityTrackerTest, TestGetComputedVelocity) { float maxFloat = std::numeric_limits::max(); VelocityTracker::ComputedVelocity computedVelocity; - computedVelocity = vt.getComputedVelocity(1000 /* units */, maxFloat); + computedVelocity = vt.getComputedVelocity(/*units=*/1000, maxFloat); checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)), 764.345703); // Expect X velocity to be scaled with respective to provided units. - computedVelocity = vt.getComputedVelocity(1000000 /* units */, maxFloat); + computedVelocity = vt.getComputedVelocity(/*units=*/1000000, maxFloat); checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)), 764345.703); // Expect X velocity to be clamped by provided max velocity. - computedVelocity = vt.getComputedVelocity(1000000 /* units */, 1000); + computedVelocity = vt.getComputedVelocity(/*units=*/1000000, 1000); checkVelocity(*(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID)), 1000); // All 0 data for Y; expect 0 velocity. diff --git a/libs/input/tests/VerifiedInputEvent_test.cpp b/libs/input/tests/VerifiedInputEvent_test.cpp index f2b59ea9ab..277d74dd1c 100644 --- a/libs/input/tests/VerifiedInputEvent_test.cpp +++ b/libs/input/tests/VerifiedInputEvent_test.cpp @@ -23,10 +23,10 @@ namespace android { static KeyEvent getKeyEventWithFlags(int32_t flags) { KeyEvent event; - event.initialize(InputEvent::nextId(), 2 /*deviceId*/, AINPUT_SOURCE_GAMEPAD, + event.initialize(InputEvent::nextId(), /*deviceId=*/2, AINPUT_SOURCE_GAMEPAD, ADISPLAY_ID_DEFAULT, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, flags, - AKEYCODE_BUTTON_X, 121 /*scanCode*/, AMETA_ALT_ON, 1 /*repeatCount*/, - 1000 /*downTime*/, 2000 /*eventTime*/); + AKEYCODE_BUTTON_X, /*scanCode=*/121, AMETA_ALT_ON, /*repeatCount=*/1, + /*downTime=*/1000, /*eventTime=*/2000); return event; } @@ -44,12 +44,12 @@ static MotionEvent getMotionEventWithFlags(int32_t flags) { ui::Transform transform; transform.set({2, 0, 4, 0, 3, 5, 0, 0, 1}); ui::Transform identity; - event.initialize(InputEvent::nextId(), 0 /*deviceId*/, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_DEFAULT, - INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, 0 /*actionButton*/, flags, - AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0 /*buttonState*/, - MotionClassification::NONE, transform, 0.1 /*xPrecision*/, 0.2 /*yPrecision*/, - 280 /*xCursorPosition*/, 540 /*yCursorPosition*/, identity, 100 /*downTime*/, - 200 /*eventTime*/, pointerCount, pointerProperties, pointerCoords); + event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_DEFAULT, + INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0, flags, + AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0, + MotionClassification::NONE, transform, /*xPrecision=*/0.1, /*yPrecision=*/0.2, + /*xCursorPosition=*/280, /*yCursorPosition=*/540, identity, /*downTime=*/100, + /*eventTime=*/200, pointerCount, pointerProperties, pointerCoords); return event; } -- GitLab From 5abecc3a9e11dcc5f40cc612b814afb82d79e03e Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 13 Mar 2023 14:59:59 +0000 Subject: [PATCH 1000/1310] Disable stylus integration tests temporarily These tests are failing regularly on presubmit and is affecting development velocity. Even though their failure rate is relatively low (below 5%) on postsubmit runs, we'll disable these tests for now until we can debug the issue. They can be included in local runs by using the following gtest flag: --gunit_also_run_disabled_tests Bug: 261025260 Test: Presubmit Change-Id: I991bf9b1431dcdbfe2017207c0b06cb8bcce7c70 --- services/inputflinger/tests/InputReader_test.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 9732e8d53d..ab4f63e4c7 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -1830,7 +1830,7 @@ using StylusButtonIntegrationTestTypes = ::testing::Types; TYPED_TEST_SUITE(StylusButtonIntegrationTest, StylusButtonIntegrationTestTypes); -TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsGenerateKeyEvents) { +TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsGenerateKeyEvents) { const auto stylusId = TestFixture::mStylusInfo.getId(); TestFixture::mStylus->pressKey(BTN_STYLUS); @@ -1844,7 +1844,7 @@ TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsGenerateKeyEvents) { WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId)))); } -TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsSurroundingTouchGesture) { +TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsSurroundingTouchGesture) { const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint(); const auto touchscreenId = TestFixture::mTouchscreenInfo.getId(); const auto stylusId = TestFixture::mStylusInfo.getId(); @@ -1890,7 +1890,7 @@ TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsSurroundingTouchGesture) { WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId)))); } -TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsSurroundingHoveringTouchGesture) { +TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsSurroundingHoveringTouchGesture) { const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint(); const auto touchscreenId = TestFixture::mTouchscreenInfo.getId(); const auto stylusId = TestFixture::mStylusInfo.getId(); @@ -1966,7 +1966,7 @@ TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsSurroundingHoveringTouchGes WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId)))); } -TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsWithinTouchGesture) { +TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsWithinTouchGesture) { const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint(); const auto touchscreenId = TestFixture::mTouchscreenInfo.getId(); const auto stylusId = TestFixture::mStylusInfo.getId(); @@ -2020,7 +2020,7 @@ TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsWithinTouchGesture) { WithDeviceId(touchscreenId)))); } -TYPED_TEST(StylusButtonIntegrationTest, StylusButtonMotionEventsDisabled) { +TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonMotionEventsDisabled) { TestFixture::mFakePolicy->setStylusButtonMotionEventsEnabled(false); TestFixture::mReader->requestRefreshConfiguration( InputReaderConfiguration::CHANGE_STYLUS_BUTTON_REPORTING); @@ -2077,7 +2077,7 @@ TYPED_TEST(StylusButtonIntegrationTest, StylusButtonMotionEventsDisabled) { // ongoing stylus gesture that is being emitted by the touchscreen. using ExternalStylusIntegrationTest = TouchIntegrationTest; -TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureReported) { +TEST_F(ExternalStylusIntegrationTest, DISABLED_FusedExternalStylusPressureReported) { const Point centerPoint = mDevice->getCenterPoint(); // Create an external stylus capable of reporting pressure data that @@ -2123,7 +2123,7 @@ TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureReported) { ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled()); } -TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureNotReported) { +TEST_F(ExternalStylusIntegrationTest, DISABLED_FusedExternalStylusPressureNotReported) { const Point centerPoint = mDevice->getCenterPoint(); // Create an external stylus capable of reporting pressure data that @@ -2191,7 +2191,7 @@ TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureNotReported) { ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled()); } -TEST_F(ExternalStylusIntegrationTest, UnfusedExternalStylus) { +TEST_F(ExternalStylusIntegrationTest, DISABLED_UnfusedExternalStylus) { const Point centerPoint = mDevice->getCenterPoint(); // Create an external stylus device that does not support pressure. It should not affect any -- GitLab From 76004e0290b32d978df26e08fc57a5c957ddf22a Mon Sep 17 00:00:00 2001 From: Kunal Malhotra Date: Tue, 28 Feb 2023 22:00:21 +0000 Subject: [PATCH 1001/1310] Adding in FGS Logger AIDL methods for use in C++ code Test: manual testing on device Bug: b/263304156 Change-Id: I3b934cbc72dae718479e45a2a794046745a3ca2f --- libs/binder/IActivityManager.cpp | 47 +++++++++++++++++++ .../binder/IActivityManager.h | 7 +++ 2 files changed, 54 insertions(+) diff --git a/libs/binder/IActivityManager.cpp b/libs/binder/IActivityManager.cpp index 08169f5538..ebdaa4cc99 100644 --- a/libs/binder/IActivityManager.cpp +++ b/libs/binder/IActivityManager.cpp @@ -131,6 +131,53 @@ public: *outResult = reply.readInt32(); return NO_ERROR; } + + virtual status_t logFgsApiBegin(int32_t apiType, int32_t appUid, int32_t appPid) { + Parcel data, reply; + data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor()); + data.writeInt32(apiType); + data.writeInt32(appUid); + data.writeInt32(appPid); + status_t err = remote()->transact(LOG_FGS_API_BEGIN_TRANSACTION, data, &reply); + if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) { + ALOGD("FGS Logger Transaction failed"); + ALOGD("%d", err); + return err; + } + return NO_ERROR; + } + + virtual status_t logFgsApiEnd(int32_t apiType, int32_t appUid, int32_t appPid) { + Parcel data, reply; + data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor()); + data.writeInt32(apiType); + data.writeInt32(appUid); + data.writeInt32(appPid); + status_t err = remote()->transact(LOG_FGS_API_END_TRANSACTION, data, &reply); + if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) { + ALOGD("FGS Logger Transaction failed"); + ALOGD("%d", err); + return err; + } + return NO_ERROR; + } + + virtual status_t logFgsApiStateChanged(int32_t apiType, int32_t state, int32_t appUid, + int32_t appPid) { + Parcel data, reply; + data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor()); + data.writeInt32(apiType); + data.writeInt32(state); + data.writeInt32(appUid); + data.writeInt32(appPid); + status_t err = remote()->transact(LOG_FGS_API_BEGIN_TRANSACTION, data, &reply); + if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) { + ALOGD("FGS Logger Transaction failed"); + ALOGD("%d", err); + return err; + } + return NO_ERROR; + } }; // ------------------------------------------------------------------------------------ diff --git a/libs/binder/include_activitymanager/binder/IActivityManager.h b/libs/binder/include_activitymanager/binder/IActivityManager.h index 4632b2eb0f..20d12aea9a 100644 --- a/libs/binder/include_activitymanager/binder/IActivityManager.h +++ b/libs/binder/include_activitymanager/binder/IActivityManager.h @@ -42,6 +42,10 @@ public: const pid_t pid, const uid_t uid, int32_t* outResult) = 0; + virtual status_t logFgsApiBegin(int32_t apiType, int32_t appUid, int32_t appPid) = 0; + virtual status_t logFgsApiEnd(int32_t apiType, int32_t appUid, int32_t appPid) = 0; + virtual status_t logFgsApiStateChanged(int32_t apiType, int32_t state, int32_t appUid, + int32_t appPid) = 0; enum { OPEN_CONTENT_URI_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION, @@ -50,6 +54,9 @@ public: IS_UID_ACTIVE_TRANSACTION, GET_UID_PROCESS_STATE_TRANSACTION, CHECK_PERMISSION_TRANSACTION, + LOG_FGS_API_BEGIN_TRANSACTION, + LOG_FGS_API_END_TRANSACTION, + LOG_FGS_API_STATE_CHANGED_TRANSACTION }; }; -- GitLab From 24492e67d413f9f1330deab239cb30343f25aa3e Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Fri, 10 Mar 2023 15:17:43 +0000 Subject: [PATCH 1002/1310] Add IS_GENERATED_GESTURE flag to touchpad scrolling The IS_GENERATED_GESTURE flag was added to improve scrolling in ARC++, and tells GestureDetector that the finger going down and moving is definitely for scrolling, meaning that it doesn't wait until ~20 pixels of movement have happened before starting the scroll. Adding it here should make scrolling a little more responsive, and also make the events more consistent with those for scrolling on ChromeOS. Bug: 251196347 Test: atest inputflinger_test Test: check touchpad scrolling still works well Change-Id: I17ac063fdea1f38413fd8060258c79bff3ccd5c0 --- .../mapper/gestures/GestureConverter.cpp | 28 +++++++++++-------- .../tests/GestureConverter_test.cpp | 12 +++++--- .../tests/TestInputListenerMatchers.h | 2 +- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp index d636d4458f..7b5f8e58c3 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp @@ -210,10 +210,12 @@ std::list GestureConverter::handleScroll(nsecs_t when, nsecs_t readT coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); mDownTime = when; - out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, - /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1, - mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, - yCursorPosition)); + NotifyMotionArgs args = + makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0, + mButtonState, /* pointerCount= */ 1, mFingerProps.data(), + mFakeFingerCoords.data(), xCursorPosition, yCursorPosition); + args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE; + out.push_back(args); } float deltaX = gesture.details.scroll.dx; float deltaY = gesture.details.scroll.dy; @@ -224,9 +226,12 @@ std::list GestureConverter::handleScroll(nsecs_t when, nsecs_t readT // TODO(b/262876643): set AXIS_GESTURE_{X,Y}_OFFSET. coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, -gesture.details.scroll.dx); coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, -gesture.details.scroll.dy); - out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0, - mButtonState, /* pointerCount= */ 1, mFingerProps.data(), - mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); + NotifyMotionArgs args = + makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0, + mButtonState, /* pointerCount= */ 1, mFingerProps.data(), + mFakeFingerCoords.data(), xCursorPosition, yCursorPosition); + args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE; + out.push_back(args); return out; } @@ -243,10 +248,11 @@ NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const G mPointerController->getPosition(&xCursorPosition, &yCursorPosition); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, 0); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, 0); - NotifyArgs args = makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, - /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1, - mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, - yCursorPosition); + NotifyMotionArgs args = + makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0, + mButtonState, /* pointerCount= */ 1, mFingerProps.data(), + mFakeFingerCoords.data(), xCursorPosition, yCursorPosition); + args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE; mCurrentClassification = MotionClassification::NONE; return args; } diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp index 9c624ba560..bbf7e8e5a8 100644 --- a/services/inputflinger/tests/GestureConverter_test.cpp +++ b/services/inputflinger/tests/GestureConverter_test.cpp @@ -252,14 +252,16 @@ TEST_F(GestureConverterTest, Scroll) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y), WithGestureScrollDistance(0, 0, EPSILON), WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithDownTime(downTime))); + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithDownTime(downTime), + WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))); args.pop_front(); ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(POINTER_X, POINTER_Y - 10), WithGestureScrollDistance(0, 10, EPSILON), WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), + WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))); Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); @@ -269,7 +271,8 @@ TEST_F(GestureConverterTest, Scroll) { WithCoords(POINTER_X, POINTER_Y - 15), WithGestureScrollDistance(0, 5, EPSILON), WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), + WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, GESTURES_FLING_START); @@ -280,7 +283,8 @@ TEST_F(GestureConverterTest, Scroll) { WithCoords(POINTER_X, POINTER_Y - 15), WithGestureScrollDistance(0, 0, EPSILON), WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), + WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))); } TEST_F(GestureConverterTest, Scroll_Rotated) { diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h index edd14f82e7..09f7ae8b1c 100644 --- a/services/inputflinger/tests/TestInputListenerMatchers.h +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -145,7 +145,7 @@ MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") { MATCHER_P(WithFlags, flags, "InputEvent with specified flags") { *result_listener << "expected flags " << flags << ", but got " << arg.flags; - return arg.flags == flags; + return arg.flags == static_cast(flags); } MATCHER_P(WithMotionClassification, classification, -- GitLab From 2719e82de71755e7d4389fe5eefc11fef0dd44c2 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 28 Feb 2023 17:39:36 +0000 Subject: [PATCH 1003/1310] Return values in PointerController's getters Rather than passing pointers in to get output from the functions, return values from getPosition() and getBounds(). Bug: 245989146 Bug: 21566609 Test: Build, presubmit Change-Id: I8ffb88d6ce1e572853df62a27dfd84c470ce8263 --- .../include/PointerControllerInterface.h | 19 +++++++++-- .../reader/mapper/CursorInputMapper.cpp | 11 ++++--- .../reader/mapper/TouchInputMapper.cpp | 32 +++++++------------ .../mapper/gestures/GestureConverter.cpp | 24 ++++++-------- .../tests/FakePointerController.cpp | 17 +++------- .../tests/FakePointerController.h | 4 +-- .../tests/fuzzers/MapperHelpers.h | 15 +++++++-- 7 files changed, 63 insertions(+), 59 deletions(-) diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h index 9dbdd5ad0f..3f1ff30cb2 100644 --- a/services/inputflinger/include/PointerControllerInterface.h +++ b/services/inputflinger/include/PointerControllerInterface.h @@ -22,6 +22,20 @@ namespace android { +struct FloatPoint { + float x; + float y; + + inline FloatPoint(float x, float y) : x(x), y(y) {} + + inline explicit FloatPoint(vec2 p) : x(p.x), y(p.y) {} + + template + operator std::tuple() { + return {x, y}; + } +}; + /** * Interface for tracking a mouse / touch pad pointer and touch pad spots. * @@ -40,8 +54,7 @@ protected: public: /* Gets the bounds of the region that the pointer can traverse. * Returns true if the bounds are available. */ - virtual bool getBounds(float* outMinX, float* outMinY, - float* outMaxX, float* outMaxY) const = 0; + virtual std::optional getBounds() const = 0; /* Move the pointer. */ virtual void move(float deltaX, float deltaY) = 0; @@ -56,7 +69,7 @@ public: virtual void setPosition(float x, float y) = 0; /* Gets the absolute location of the pointer. */ - virtual void getPosition(float* outX, float* outY) const = 0; + virtual FloatPoint getPosition() const = 0; enum class Transition { // Fade/unfade immediately. diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index 13e4d0cfbe..7f0df0f301 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -83,10 +83,11 @@ void CursorInputMapper::populateDeviceInfo(InputDeviceInfo* info) { InputMapper::populateDeviceInfo(info); if (mParameters.mode == Parameters::Mode::POINTER) { - float minX, minY, maxX, maxY; - if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) { - info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, minX, maxX, 0.0f, 0.0f, 0.0f); - info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, minY, maxY, 0.0f, 0.0f, 0.0f); + if (const auto bounds = mPointerController->getBounds(); bounds) { + info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, bounds->left, bounds->right, 0.0f, + 0.0f, 0.0f); + info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, bounds->top, bounds->bottom, 0.0f, + 0.0f, 0.0f); } } else { info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, 0.0f); @@ -379,7 +380,7 @@ std::list CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); } - mPointerController->getPosition(&xCursorPosition, &yCursorPosition); + std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition(); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index b53fc7399a..fffc5e0f4e 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -2692,8 +2692,7 @@ std::list TouchInputMapper::dispatchPointerGestures(nsecs_t when, ns // the pointer is hovering again even if the user is not currently touching // the touch pad. This ensures that a view will receive a fresh hover enter // event after a tap. - float x, y; - mPointerController->getPosition(&x, &y); + const auto [x, y] = mPointerController->getPosition(); PointerProperties pointerProperties; pointerProperties.clear(); @@ -2899,8 +2898,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerVelocityControl.reset(); } - float x, y; - mPointerController->getPosition(&x, &y); + const auto [x, y] = mPointerController->getPosition(); mPointerGesture.currentGestureMode = PointerGesture::Mode::BUTTON_CLICK_OR_DRAG; mPointerGesture.currentGestureIdBits.clear(); @@ -2926,8 +2924,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP_DRAG) && lastFingerCount == 1) { if (when <= mPointerGesture.tapDownTime + mConfig.pointerGestureTapInterval) { - float x, y; - mPointerController->getPosition(&x, &y); + const auto [x, y] = mPointerController->getPosition(); if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop && fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) { ALOGD_IF(DEBUG_GESTURES, "Gestures: TAP"); @@ -2989,8 +2986,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerGesture.currentGestureMode = PointerGesture::Mode::HOVER; if (mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP) { if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) { - float x, y; - mPointerController->getPosition(&x, &y); + const auto [x, y] = mPointerController->getPosition(); if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop && fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) { mPointerGesture.currentGestureMode = PointerGesture::Mode::TAP_DRAG; @@ -3026,8 +3022,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi down = false; } - float x, y; - mPointerController->getPosition(&x, &y); + const auto [x, y] = mPointerController->getPosition(); mPointerGesture.currentGestureIdBits.clear(); mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); @@ -3194,8 +3189,8 @@ void TouchInputMapper::prepareMultiFingerPointerGestures(nsecs_t when, bool* can mCurrentRawState.rawPointerData .getCentroidOfTouchingPointers(&mPointerGesture.referenceTouchX, &mPointerGesture.referenceTouchY); - mPointerController->getPosition(&mPointerGesture.referenceGestureX, - &mPointerGesture.referenceGestureY); + std::tie(mPointerGesture.referenceGestureX, mPointerGesture.referenceGestureY) = + mPointerController->getPosition(); } // Clear the reference deltas for fingers not yet included in the reference calculation. @@ -3510,8 +3505,7 @@ std::list TouchInputMapper::dispatchPointerStylus(nsecs_t when, nsec hovering = mCurrentCookedState.cookedPointerData.hoveringIdBits.hasBit(id); down = !hovering; - float x, y; - mPointerController->getPosition(&x, &y); + const auto [x, y] = mPointerController->getPosition(); mPointerSimple.currentCoords.copyFrom( mCurrentCookedState.cookedPointerData.pointerCoords[index]); mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x); @@ -3549,9 +3543,8 @@ std::list TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs down = isPointerDown(mCurrentRawState.buttonState); hovering = !down; - float x, y; - mPointerController->getPosition(&x, &y); - uint32_t currentIndex = mCurrentRawState.rawPointerData.idToIndex[id]; + const auto [x, y] = mPointerController->getPosition(); + const uint32_t currentIndex = mCurrentRawState.rawPointerData.idToIndex[id]; mPointerSimple.currentCoords.copyFrom( mCurrentCookedState.cookedPointerData.pointerCoords[currentIndex]); mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x); @@ -3598,8 +3591,7 @@ std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsec } int32_t displayId = mPointerController->getDisplayId(); - float xCursorPosition, yCursorPosition; - mPointerController->getPosition(&xCursorPosition, &yCursorPosition); + const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); if (mPointerSimple.down && !down) { mPointerSimple.down = false; @@ -3820,7 +3812,7 @@ NotifyMotionArgs TouchInputMapper::dispatchMotion( float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; if (mDeviceMode == DeviceMode::POINTER) { - mPointerController->getPosition(&xCursorPosition, &yCursorPosition); + std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition(); } const int32_t deviceId = getDeviceId(); std::vector frames = getDeviceContext().getVideoFrames(); diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp index d636d4458f..160b0ece8b 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp @@ -111,8 +111,8 @@ NotifyArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime, const Ge mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); mPointerController->move(deltaX, deltaY); mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); - float xCursorPosition, yCursorPosition; - mPointerController->getPosition(&xCursorPosition, &yCursorPosition); + + const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); PointerCoords coords; coords.clear(); @@ -136,8 +136,7 @@ std::list GestureConverter::handleButtonsChange(nsecs_t when, nsecs_ mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); - float xCursorPosition, yCursorPosition; - mPointerController->getPosition(&xCursorPosition, &yCursorPosition); + const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); PointerCoords coords; coords.clear(); @@ -202,8 +201,7 @@ std::list GestureConverter::handleScroll(nsecs_t when, nsecs_t readT const Gesture& gesture) { std::list out; PointerCoords& coords = mFakeFingerCoords[0]; - float xCursorPosition, yCursorPosition; - mPointerController->getPosition(&xCursorPosition, &yCursorPosition); + const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); if (mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) { mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE; coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); @@ -239,8 +237,7 @@ NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const G return {}; } - float xCursorPosition, yCursorPosition; - mPointerController->getPosition(&xCursorPosition, &yCursorPosition); + const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, 0); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, 0); NotifyArgs args = makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, @@ -256,8 +253,8 @@ NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const G uint32_t fingerCount, float dx, float dy) { std::list out = {}; - float xCursorPosition, yCursorPosition; - mPointerController->getPosition(&xCursorPosition, &yCursorPosition); + + const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) { // If the user changes the number of fingers mid-way through a swipe (e.g. they start with // three and then put a fourth finger down), the gesture library will treat it as two @@ -322,8 +319,7 @@ NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const G if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) { return out; } - float xCursorPosition, yCursorPosition; - mPointerController->getPosition(&xCursorPosition, &yCursorPosition); + const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, 0); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, 0); @@ -347,8 +343,8 @@ NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const G [[nodiscard]] std::list GestureConverter::handlePinch(nsecs_t when, nsecs_t readTime, const Gesture& gesture) { std::list out; - float xCursorPosition, yCursorPosition; - mPointerController->getPosition(&xCursorPosition, &yCursorPosition); + + const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); // Pinch gesture phases are reported a little differently from others, in that the same details // struct is used for all phases of the gesture, just with different zoom_state values. When diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp index 28dad95d47..ad311f9c7a 100644 --- a/services/inputflinger/tests/FakePointerController.cpp +++ b/services/inputflinger/tests/FakePointerController.cpp @@ -45,9 +45,8 @@ int32_t FakePointerController::getButtonState() const { return mButtonState; } -void FakePointerController::getPosition(float* outX, float* outY) const { - *outX = mX; - *outY = mY; +FloatPoint FakePointerController::getPosition() const { + return {mX, mY}; } int32_t FakePointerController::getDisplayId() const { @@ -59,8 +58,7 @@ void FakePointerController::setDisplayViewport(const DisplayViewport& viewport) } void FakePointerController::assertPosition(float x, float y) { - float actualX, actualY; - getPosition(&actualX, &actualY); + const auto [actualX, actualY] = getPosition(); ASSERT_NEAR(x, actualX, 1); ASSERT_NEAR(y, actualY, 1); } @@ -69,13 +67,8 @@ bool FakePointerController::isPointerShown() { return mIsPointerShown; } -bool FakePointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX, - float* outMaxY) const { - *outMinX = mMinX; - *outMinY = mMinY; - *outMaxX = mMaxX; - *outMaxY = mMaxY; - return mHaveBounds; +std::optional FakePointerController::getBounds() const { + return mHaveBounds ? std::make_optional(mMinX, mMinY, mMaxX, mMaxY) : std::nullopt; } void FakePointerController::move(float deltaX, float deltaY) { diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h index dd56e65c01..6d7bbebf92 100644 --- a/services/inputflinger/tests/FakePointerController.h +++ b/services/inputflinger/tests/FakePointerController.h @@ -34,7 +34,7 @@ public: void setPosition(float x, float y) override; void setButtonState(int32_t buttonState) override; int32_t getButtonState() const override; - void getPosition(float* outX, float* outY) const override; + FloatPoint getPosition() const override; int32_t getDisplayId() const override; void setDisplayViewport(const DisplayViewport& viewport) override; @@ -42,7 +42,7 @@ public: bool isPointerShown(); private: - bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override; + std::optional getBounds() const override; void move(float deltaX, float deltaY) override; void fade(Transition) override; void unfade(Transition) override; diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index 546121d6ac..5d7bf4bc44 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -225,14 +225,23 @@ class FuzzPointerController : public PointerControllerInterface { public: FuzzPointerController(std::shared_ptr mFdp) : mFdp(mFdp) {} ~FuzzPointerController() {} - bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override { - return mFdp->ConsumeBool(); + std::optional getBounds() const override { + if (mFdp->ConsumeBool()) { + return {}; + } else { + return FloatRect{mFdp->ConsumeFloatingPoint(), + mFdp->ConsumeFloatingPoint(), + mFdp->ConsumeFloatingPoint(), + mFdp->ConsumeFloatingPoint()}; + } } void move(float deltaX, float deltaY) override {} void setButtonState(int32_t buttonState) override {} int32_t getButtonState() const override { return mFdp->ConsumeIntegral(); } void setPosition(float x, float y) override {} - void getPosition(float* outX, float* outY) const override {} + FloatPoint getPosition() const override { + return {mFdp->ConsumeFloatingPoint(), mFdp->ConsumeFloatingPoint()}; + } void fade(Transition transition) override {} void unfade(Transition transition) override {} void setPresentation(Presentation presentation) override {} -- GitLab From c065d7b9bb22e918e4bab34b816c25984da8af7c Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 2 Mar 2023 14:06:29 -0800 Subject: [PATCH 1004/1310] Look for prediction model in vendor partition When loading the prediction model, we should first check the vendor partition, and then, if that doesn't work, use the system one. This will allow OEMs to customize this model. Bug: 210158587 Test: loaded model into /vendor and deleted from /system, checked that prediction works Bug: 271455682 Test: atest libinput_tests inputflinger_tests Change-Id: I0a369e5ec5cec8ac20b66fb4fcf265e7b1dde38a --- libs/input/TfLiteMotionPredictor.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/libs/input/TfLiteMotionPredictor.cpp b/libs/input/TfLiteMotionPredictor.cpp index 691e87c366..3b061d1cf1 100644 --- a/libs/input/TfLiteMotionPredictor.cpp +++ b/libs/input/TfLiteMotionPredictor.cpp @@ -61,8 +61,21 @@ constexpr char OUTPUT_R[] = "r"; constexpr char OUTPUT_PHI[] = "phi"; constexpr char OUTPUT_PRESSURE[] = "pressure"; +// Ideally, we would just use std::filesystem::exists here, but it requires libc++fs, which causes +// build issues in other parts of the system. +#if defined(__ANDROID__) +bool fileExists(const char* filename) { + struct stat buffer; + return stat(filename, &buffer) == 0; +} +#endif + std::string getModelPath() { #if defined(__ANDROID__) + static const char* oemModel = "/vendor/etc/motion_predictor_model.fb"; + if (fileExists(oemModel)) { + return oemModel; + } return "/system/etc/motion_predictor_model.fb"; #else return base::GetExecutableDirectory() + "/motion_predictor_model.fb"; @@ -217,7 +230,7 @@ void TfLiteMotionPredictorBuffers::pushSample(int64_t timestamp, std::unique_ptr TfLiteMotionPredictorModel::create() { const std::string modelPath = getModelPath(); - const int fd = open(modelPath.c_str(), O_RDONLY); + android::base::unique_fd fd(open(modelPath.c_str(), O_RDONLY)); if (fd == -1) { PLOG(FATAL) << "Could not read model from " << modelPath; } @@ -232,9 +245,6 @@ std::unique_ptr TfLiteMotionPredictorModel::create() if (!modelBuffer) { PLOG(FATAL) << "Failed to mmap model"; } - if (close(fd) == -1) { - PLOG(FATAL) << "Failed to close model fd"; - } return std::unique_ptr( new TfLiteMotionPredictorModel(std::move(modelBuffer))); -- GitLab From f13161a18414986a44354485126473bd8d5c36e6 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Wed, 8 Mar 2023 14:15:49 +0000 Subject: [PATCH 1005/1310] input: use optionals for PropertyMap getters Using optionals rather than returning a boolean forces the caller to check whether the property is present, enforced by the compiler, and also allows a more succinct syntax in some cases. Bug: 245989146 Test: atest inputflinger_test Test: connect Apple Magic Trackpad 2, check that gesture properties are set correctly in dumpsys input Change-Id: Ia0fde1f67269e6e7149f297def626e572fd7790f --- include/input/PropertyMap.h | 17 +- libs/input/Keyboard.cpp | 28 ++-- libs/input/PropertyMap.cpp | 71 ++++---- libs/input/PropertyMap_fuzz.cpp | 3 +- services/inputflinger/host/InputDriver.cpp | 8 +- services/inputflinger/reader/EventHub.cpp | 17 +- .../reader/mapper/CursorInputMapper.cpp | 17 +- .../reader/mapper/KeyboardInputMapper.cpp | 11 +- .../mapper/RotaryEncoderInputMapper.cpp | 15 +- .../reader/mapper/SensorInputMapper.cpp | 48 +++--- .../reader/mapper/SensorInputMapper.h | 6 +- .../reader/mapper/TouchInputMapper.cpp | 153 ++++++++---------- .../reader/mapper/TouchInputMapper.h | 3 + .../mapper/gestures/PropertyProvider.cpp | 27 ++-- .../inputflinger/tests/InputReader_test.cpp | 7 +- 15 files changed, 198 insertions(+), 233 deletions(-) mode change 100755 => 100644 libs/input/PropertyMap_fuzz.cpp diff --git a/include/input/PropertyMap.h b/include/input/PropertyMap.h index 18ce16df36..2e44142927 100644 --- a/include/input/PropertyMap.h +++ b/include/input/PropertyMap.h @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -63,15 +64,15 @@ public: /* Returns a set of all property keys starting with the given prefix. */ std::unordered_set getKeysWithPrefix(const std::string& prefix) const; - /* Gets the value of a property and parses it. - * Returns true and sets outValue if the key was found and its value was parsed successfully. - * Otherwise returns false and does not modify outValue. (Also logs a warning.) + /* Gets the value of a property and parses it. Returns nullopt if the key wasn't found or + * couldn't be parsed as the requested type. (Warnings are also logged in the case of parsing + * failures.) */ - bool tryGetProperty(const std::string& key, std::string& outValue) const; - bool tryGetProperty(const std::string& key, bool& outValue) const; - bool tryGetProperty(const std::string& key, int32_t& outValue) const; - bool tryGetProperty(const std::string& key, float& outValue) const; - bool tryGetProperty(const std::string& key, double& outValue) const; + std::optional getString(const std::string& key) const; + std::optional getBool(const std::string& key) const; + std::optional getInt(const std::string& key) const; + std::optional getFloat(const std::string& key) const; + std::optional getDouble(const std::string& key) const; /* Adds all values from the specified property map. */ void addAll(const PropertyMap* map); diff --git a/libs/input/Keyboard.cpp b/libs/input/Keyboard.cpp index 3f8467d818..0b5c7ff785 100644 --- a/libs/input/Keyboard.cpp +++ b/libs/input/Keyboard.cpp @@ -16,9 +16,10 @@ #define LOG_TAG "Keyboard" +#include #include #include -#include +#include #include #include @@ -49,23 +50,25 @@ status_t KeyMap::load(const InputDeviceIdentifier& deviceIdentifier, const PropertyMap* deviceConfiguration) { // Use the configured key layout if available. if (deviceConfiguration) { - std::string keyLayoutName; - if (deviceConfiguration->tryGetProperty("keyboard.layout", keyLayoutName)) { - status_t status = loadKeyLayout(deviceIdentifier, keyLayoutName.c_str()); + std::optional keyLayoutName = + deviceConfiguration->getString("keyboard.layout"); + if (keyLayoutName.has_value()) { + status_t status = loadKeyLayout(deviceIdentifier, *keyLayoutName); if (status == NAME_NOT_FOUND) { ALOGE("Configuration for keyboard device '%s' requested keyboard layout '%s' but " "it was not found.", - deviceIdentifier.name.c_str(), keyLayoutName.c_str()); + deviceIdentifier.name.c_str(), keyLayoutName->c_str()); } } - std::string keyCharacterMapName; - if (deviceConfiguration->tryGetProperty("keyboard.characterMap", keyCharacterMapName)) { - status_t status = loadKeyCharacterMap(deviceIdentifier, keyCharacterMapName.c_str()); + std::optional keyCharacterMapName = + deviceConfiguration->getString("keyboard.characterMap"); + if (keyCharacterMapName.has_value()) { + status_t status = loadKeyCharacterMap(deviceIdentifier, *keyCharacterMapName); if (status == NAME_NOT_FOUND) { ALOGE("Configuration for keyboard device '%s' requested keyboard character " "map '%s' but it was not found.", - deviceIdentifier.name.c_str(), keyCharacterMapName.c_str()); + deviceIdentifier.name.c_str(), keyCharacterMapName->c_str()); } } @@ -162,9 +165,7 @@ bool isKeyboardSpecialFunction(const PropertyMap* config) { if (config == nullptr) { return false; } - bool isSpecialFunction = false; - config->tryGetProperty("keyboard.specialFunction", isSpecialFunction); - return isSpecialFunction; + return config->getBool("keyboard.specialFunction").value_or(false); } bool isEligibleBuiltInKeyboard(const InputDeviceIdentifier& deviceIdentifier, @@ -177,8 +178,7 @@ bool isEligibleBuiltInKeyboard(const InputDeviceIdentifier& deviceIdentifier, } if (deviceConfiguration) { - bool builtIn = false; - if (deviceConfiguration->tryGetProperty("keyboard.builtIn", builtIn) && builtIn) { + if (deviceConfiguration->getBool("keyboard.builtIn").value_or(false)) { return true; } } diff --git a/libs/input/PropertyMap.cpp b/libs/input/PropertyMap.cpp index 9a4f10b21d..548f894d22 100644 --- a/libs/input/PropertyMap.cpp +++ b/libs/input/PropertyMap.cpp @@ -60,75 +60,62 @@ bool PropertyMap::hasProperty(const std::string& key) const { return mProperties.find(key) != mProperties.end(); } -bool PropertyMap::tryGetProperty(const std::string& key, std::string& outValue) const { +std::optional PropertyMap::getString(const std::string& key) const { auto it = mProperties.find(key); - if (it == mProperties.end()) { - return false; - } - - outValue = it->second; - return true; + return it != mProperties.end() ? std::make_optional(it->second) : std::nullopt; } -bool PropertyMap::tryGetProperty(const std::string& key, bool& outValue) const { - int32_t intValue; - if (!tryGetProperty(key, intValue)) { - return false; - } - - outValue = intValue; - return true; +std::optional PropertyMap::getBool(const std::string& key) const { + std::optional intValue = getInt(key); + return intValue.has_value() ? std::make_optional(*intValue != 0) : std::nullopt; } -bool PropertyMap::tryGetProperty(const std::string& key, int32_t& outValue) const { - std::string stringValue; - if (!tryGetProperty(key, stringValue) || stringValue.length() == 0) { - return false; +std::optional PropertyMap::getInt(const std::string& key) const { + std::optional stringValue = getString(key); + if (!stringValue.has_value() || stringValue->length() == 0) { + return std::nullopt; } char* end; - int32_t value = static_cast(strtol(stringValue.c_str(), &end, 10)); + int32_t value = static_cast(strtol(stringValue->c_str(), &end, 10)); if (*end != '\0') { ALOGW("Property key '%s' has invalid value '%s'. Expected an integer.", key.c_str(), - stringValue.c_str()); - return false; + stringValue->c_str()); + return std::nullopt; } - outValue = value; - return true; + return value; } -bool PropertyMap::tryGetProperty(const std::string& key, float& outValue) const { - std::string stringValue; - if (!tryGetProperty(key, stringValue) || stringValue.length() == 0) { - return false; +std::optional PropertyMap::getFloat(const std::string& key) const { + std::optional stringValue = getString(key); + if (!stringValue.has_value() || stringValue->length() == 0) { + return std::nullopt; } char* end; - float value = strtof(stringValue.c_str(), &end); + float value = strtof(stringValue->c_str(), &end); if (*end != '\0') { ALOGW("Property key '%s' has invalid value '%s'. Expected a float.", key.c_str(), - stringValue.c_str()); - return false; + stringValue->c_str()); + return std::nullopt; } - outValue = value; - return true; + return value; } -bool PropertyMap::tryGetProperty(const std::string& key, double& outValue) const { - std::string stringValue; - if (!tryGetProperty(key, stringValue) || stringValue.length() == 0) { - return false; +std::optional PropertyMap::getDouble(const std::string& key) const { + std::optional stringValue = getString(key); + if (!stringValue.has_value() || stringValue->length() == 0) { + return std::nullopt; } char* end; - double value = strtod(stringValue.c_str(), &end); + double value = strtod(stringValue->c_str(), &end); if (*end != '\0') { ALOGW("Property key '%s' has invalid value '%s'. Expected a double.", key.c_str(), - stringValue.c_str()); - return false; + stringValue->c_str()); + return std::nullopt; } - outValue = value; - return true; + return value; } void PropertyMap::addAll(const PropertyMap* map) { diff --git a/libs/input/PropertyMap_fuzz.cpp b/libs/input/PropertyMap_fuzz.cpp old mode 100755 new mode 100644 index d985dc1748..6299ca8ef9 --- a/libs/input/PropertyMap_fuzz.cpp +++ b/libs/input/PropertyMap_fuzz.cpp @@ -29,8 +29,7 @@ static const std::vector void { std::string key = dataProvider->ConsumeRandomLengthString(MAX_STR_LEN); - std::string out; - propertyMap.tryGetProperty(key, out); + propertyMap.getString(key); }, [](FuzzedDataProvider* dataProvider, android::PropertyMap& /*unused*/) -> void { TemporaryFile tf; diff --git a/services/inputflinger/host/InputDriver.cpp b/services/inputflinger/host/InputDriver.cpp index 97d57e4bd0..ec0388d500 100644 --- a/services/inputflinger/host/InputDriver.cpp +++ b/services/inputflinger/host/InputDriver.cpp @@ -242,13 +242,13 @@ input_property_map_t* InputDriver::inputGetDevicePropertyMap(input_device_identi input_property_t* InputDriver::inputGetDeviceProperty(input_property_map_t* map, const char* key) { if (map != nullptr) { - std::string value; - auto prop = std::make_unique(); - if (!map->propertyMap->tryGetProperty(key, value)) { + std::optional value = map->propertyMap->getString(key); + if (!value.has_value()) { return nullptr; } + auto prop = std::make_unique(); prop->key = key; - prop->value = value.c_str(); + prop->value = value->c_str(); return prop.release(); } return nullptr; diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index 3d3a8ea42d..e65f3af107 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -52,6 +52,7 @@ #include #include +#include #include #include @@ -673,9 +674,9 @@ status_t EventHub::Device::loadKeyMapLocked() { bool EventHub::Device::isExternalDeviceLocked() { if (configuration) { - bool value; - if (configuration->tryGetProperty("device.internal", value)) { - return !value; + std::optional isInternal = configuration->getBool("device.internal"); + if (isInternal.has_value()) { + return !isInternal.value(); } } return identifier.bus == BUS_USB || identifier.bus == BUS_BLUETOOTH; @@ -683,9 +684,9 @@ bool EventHub::Device::isExternalDeviceLocked() { bool EventHub::Device::deviceHasMicLocked() { if (configuration) { - bool value; - if (configuration->tryGetProperty("audio.mic", value)) { - return value; + std::optional hasMic = configuration->getBool("audio.mic"); + if (hasMic.has_value()) { + return hasMic.value(); } } return false; @@ -2281,8 +2282,8 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { } // See if the device is specially configured to be of a certain type. - std::string deviceType; - if (device->configuration && device->configuration->tryGetProperty("device.type", deviceType)) { + if (device->configuration) { + std::string deviceType = device->configuration->getString("device.type").value_or(""); if (deviceType == "rotaryEncoder") { device->classes |= InputDeviceClass::ROTARY_ENCODER; } else if (deviceType == "externalStylus") { diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index 13e4d0cfbe..6b9bf52e24 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -20,6 +20,8 @@ #include "CursorInputMapper.h" +#include + #include "CursorButtonAccumulator.h" #include "CursorScrollAccumulator.h" #include "PointerControllerInterface.h" @@ -250,18 +252,17 @@ std::list CursorInputMapper::configure(nsecs_t when, void CursorInputMapper::configureParameters() { mParameters.mode = Parameters::Mode::POINTER; - std::string cursorModeString; - if (getDeviceContext().getConfiguration().tryGetProperty("cursor.mode", cursorModeString)) { - if (cursorModeString == "navigation") { + const PropertyMap& config = getDeviceContext().getConfiguration(); + std::optional cursorModeString = config.getString("cursor.mode"); + if (cursorModeString.has_value()) { + if (*cursorModeString == "navigation") { mParameters.mode = Parameters::Mode::NAVIGATION; - } else if (cursorModeString != "pointer" && cursorModeString != "default") { - ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString.c_str()); + } else if (*cursorModeString != "pointer" && *cursorModeString != "default") { + ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString->c_str()); } } - mParameters.orientationAware = false; - getDeviceContext().getConfiguration().tryGetProperty("cursor.orientationAware", - mParameters.orientationAware); + mParameters.orientationAware = config.getBool("cursor.orientationAware").value_or(false); mParameters.hasAssociatedDisplay = false; if (mParameters.mode == Parameters::Mode::POINTER || mParameters.orientationAware) { diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index dc0454dc04..f39e004882 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -154,15 +154,10 @@ std::list KeyboardInputMapper::configure(nsecs_t when, } void KeyboardInputMapper::configureParameters() { - mParameters.orientationAware = false; const PropertyMap& config = getDeviceContext().getConfiguration(); - config.tryGetProperty("keyboard.orientationAware", mParameters.orientationAware); - - mParameters.handlesKeyRepeat = false; - config.tryGetProperty("keyboard.handlesKeyRepeat", mParameters.handlesKeyRepeat); - - mParameters.doNotWakeByDefault = false; - config.tryGetProperty("keyboard.doNotWakeByDefault", mParameters.doNotWakeByDefault); + mParameters.orientationAware = config.getBool("keyboard.orientationAware").value_or(false); + mParameters.handlesKeyRepeat = config.getBool("keyboard.handlesKeyRepeat").value_or(false); + mParameters.doNotWakeByDefault = config.getBool("keyboard.doNotWakeByDefault").value_or(false); } void KeyboardInputMapper::dumpParameters(std::string& dump) const { diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp index 19a79d7751..2ff26ed98a 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp @@ -20,6 +20,8 @@ #include "RotaryEncoderInputMapper.h" +#include + #include "CursorScrollAccumulator.h" namespace android { @@ -39,18 +41,19 @@ void RotaryEncoderInputMapper::populateDeviceInfo(InputDeviceInfo* info) { InputMapper::populateDeviceInfo(info); if (mRotaryEncoderScrollAccumulator.haveRelativeVWheel()) { - float res = 0.0f; - if (!getDeviceContext().getConfiguration().tryGetProperty("device.res", res)) { + const PropertyMap& config = getDeviceContext().getConfiguration(); + std::optional res = config.getFloat("device.res"); + if (!res.has_value()) { ALOGW("Rotary Encoder device configuration file didn't specify resolution!\n"); } - if (!getDeviceContext().getConfiguration().tryGetProperty("device.scalingFactor", - mScalingFactor)) { + std::optional scalingFactor = config.getFloat("device.scalingFactor"); + if (!scalingFactor.has_value()) { ALOGW("Rotary Encoder device configuration file didn't specify scaling factor," "default to 1.0!\n"); - mScalingFactor = 1.0f; } + mScalingFactor = scalingFactor.value_or(1.0f); info->addMotionRange(AMOTION_EVENT_AXIS_SCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, - res * mScalingFactor); + res.value_or(0.0f) * mScalingFactor); } } diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp index 3d60bfdfb8..ba088232bc 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp @@ -61,12 +61,6 @@ uint32_t SensorInputMapper::getSources() const { return AINPUT_SOURCE_SENSOR; } -template -bool SensorInputMapper::tryGetProperty(std::string keyName, T& outValue) { - const auto& config = getDeviceContext().getConfiguration(); - return config.tryGetProperty(keyName, outValue); -} - void SensorInputMapper::parseSensorConfiguration(InputDeviceSensorType sensorType, int32_t absCode, int32_t sensorDataIndex, const Axis& axis) { auto it = mSensors.find(sensorType); @@ -201,6 +195,17 @@ std::list SensorInputMapper::reset(nsecs_t when) { SensorInputMapper::Sensor SensorInputMapper::createSensor(InputDeviceSensorType sensorType, const Axis& axis) { InputDeviceIdentifier identifier = getDeviceContext().getDeviceIdentifier(); + const auto& config = getDeviceContext().getConfiguration(); + + std::string prefix = "sensor." + ftl::enum_string(sensorType); + transform(prefix.begin(), prefix.end(), prefix.begin(), ::tolower); + + int32_t flags = 0; + std::optional reportingMode = config.getInt(prefix + ".reportingMode"); + if (reportingMode.has_value()) { + flags |= (*reportingMode & REPORTING_MODE_MASK) << REPORTING_MODE_SHIFT; + } + // Sensor Id will be assigned to device Id to distinguish same sensor from multiple input // devices, in such a way that the sensor Id will be same as input device Id. // The sensorType is to distinguish different sensors within one device. @@ -209,28 +214,15 @@ SensorInputMapper::Sensor SensorInputMapper::createSensor(InputDeviceSensorType identifier.version, sensorType, InputDeviceSensorAccuracy::ACCURACY_HIGH, /*maxRange=*/axis.max, /*resolution=*/axis.scale, - /*power=*/0.0f, /*minDelay=*/0, - /*fifoReservedEventCount=*/0, /*fifoMaxEventCount=*/0, - ftl::enum_string(sensorType), /*maxDelay=*/0, /*flags=*/0, - getDeviceId()); - - std::string prefix = "sensor." + ftl::enum_string(sensorType); - transform(prefix.begin(), prefix.end(), prefix.begin(), ::tolower); - - int32_t reportingMode = 0; - if (tryGetProperty(prefix + ".reportingMode", reportingMode)) { - sensorInfo.flags |= (reportingMode & REPORTING_MODE_MASK) << REPORTING_MODE_SHIFT; - } - - tryGetProperty(prefix + ".maxDelay", sensorInfo.maxDelay); - - tryGetProperty(prefix + ".minDelay", sensorInfo.minDelay); - - tryGetProperty(prefix + ".power", sensorInfo.power); - - tryGetProperty(prefix + ".fifoReservedEventCount", sensorInfo.fifoReservedEventCount); - - tryGetProperty(prefix + ".fifoMaxEventCount", sensorInfo.fifoMaxEventCount); + /*power=*/config.getFloat(prefix + ".power").value_or(0.0f), + /*minDelay=*/config.getInt(prefix + ".minDelay").value_or(0), + /*fifoReservedEventCount=*/ + config.getInt(prefix + ".fifoReservedEventCount").value_or(0), + /*fifoMaxEventCount=*/ + config.getInt(prefix + ".fifoMaxEventCount").value_or(0), + ftl::enum_string(sensorType), + /*maxDelay=*/config.getInt(prefix + ".maxDelay").value_or(0), + /*flags=*/flags, getDeviceId()); return Sensor(sensorInfo); } diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h index 457567ba7b..043a8956cb 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.h +++ b/services/inputflinger/reader/mapper/SensorInputMapper.h @@ -16,6 +16,9 @@ #pragma once +#include +#include + #include "InputMapper.h" namespace android { @@ -120,9 +123,6 @@ private: [[nodiscard]] std::list sync(nsecs_t when, bool force); - template - bool tryGetProperty(std::string keyName, T& outValue); - void parseSensorConfiguration(InputDeviceSensorType sensorType, int32_t absCode, int32_t sensorDataIndex, const Axis& axis); diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 96c163dc3c..ac7bb89d06 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -366,15 +366,15 @@ void TouchInputMapper::configureParameters() { ? Parameters::GestureMode::SINGLE_TOUCH : Parameters::GestureMode::MULTI_TOUCH; - std::string gestureModeString; - if (getDeviceContext().getConfiguration().tryGetProperty("touch.gestureMode", - gestureModeString)) { - if (gestureModeString == "single-touch") { + const PropertyMap& config = getDeviceContext().getConfiguration(); + std::optional gestureModeString = config.getString("touch.gestureMode"); + if (gestureModeString.has_value()) { + if (*gestureModeString == "single-touch") { mParameters.gestureMode = Parameters::GestureMode::SINGLE_TOUCH; - } else if (gestureModeString == "multi-touch") { + } else if (*gestureModeString == "multi-touch") { mParameters.gestureMode = Parameters::GestureMode::MULTI_TOUCH; - } else if (gestureModeString != "default") { - ALOGW("Invalid value for touch.gestureMode: '%s'", gestureModeString.c_str()); + } else if (*gestureModeString != "default") { + ALOGW("Invalid value for touch.gestureMode: '%s'", gestureModeString->c_str()); } } @@ -382,24 +382,23 @@ void TouchInputMapper::configureParameters() { mParameters.hasButtonUnderPad = getDeviceContext().hasInputProperty(INPUT_PROP_BUTTONPAD); - mParameters.orientationAware = mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN; - getDeviceContext().getConfiguration().tryGetProperty("touch.orientationAware", - mParameters.orientationAware); + mParameters.orientationAware = + config.getBool("touch.orientationAware") + .value_or(mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN); mParameters.orientation = ui::ROTATION_0; - std::string orientationString; - if (getDeviceContext().getConfiguration().tryGetProperty("touch.orientation", - orientationString)) { + std::optional orientationString = config.getString("touch.orientation"); + if (orientationString.has_value()) { if (mParameters.deviceType != Parameters::DeviceType::TOUCH_SCREEN) { ALOGW("The configuration 'touch.orientation' is only supported for touchscreens."); - } else if (orientationString == "ORIENTATION_90") { + } else if (*orientationString == "ORIENTATION_90") { mParameters.orientation = ui::ROTATION_90; - } else if (orientationString == "ORIENTATION_180") { + } else if (*orientationString == "ORIENTATION_180") { mParameters.orientation = ui::ROTATION_180; - } else if (orientationString == "ORIENTATION_270") { + } else if (*orientationString == "ORIENTATION_270") { mParameters.orientation = ui::ROTATION_270; - } else if (orientationString != "ORIENTATION_0") { - ALOGW("Invalid value for touch.orientation: '%s'", orientationString.c_str()); + } else if (*orientationString != "ORIENTATION_0") { + ALOGW("Invalid value for touch.orientation: '%s'", orientationString->c_str()); } } @@ -413,10 +412,7 @@ void TouchInputMapper::configureParameters() { mParameters.hasAssociatedDisplay = true; if (mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN) { mParameters.associatedDisplayIsExternal = getDeviceContext().isExternal(); - std::string uniqueDisplayId; - getDeviceContext().getConfiguration().tryGetProperty("touch.displayId", - uniqueDisplayId); - mParameters.uniqueDisplayId = uniqueDisplayId.c_str(); + mParameters.uniqueDisplayId = config.getString("touch.displayId").value_or("").c_str(); } } if (getDeviceContext().getAssociatedDisplayPort()) { @@ -426,20 +422,19 @@ void TouchInputMapper::configureParameters() { // Initial downs on external touch devices should wake the device. // Normally we don't do this for internal touch screens to prevent them from waking // up in your pocket but you can enable it using the input device configuration. - mParameters.wake = getDeviceContext().isExternal(); - getDeviceContext().getConfiguration().tryGetProperty("touch.wake", mParameters.wake); - - InputDeviceUsiVersion usiVersion; - if (getDeviceContext().getConfiguration().tryGetProperty("touch.usiVersionMajor", - usiVersion.majorVersion) && - getDeviceContext().getConfiguration().tryGetProperty("touch.usiVersionMinor", - usiVersion.minorVersion)) { - mParameters.usiVersion = usiVersion; + mParameters.wake = config.getBool("touch.wake").value_or(getDeviceContext().isExternal()); + + std::optional usiVersionMajor = config.getInt("touch.usiVersionMajor"); + std::optional usiVersionMinor = config.getInt("touch.usiVersionMinor"); + if (usiVersionMajor.has_value() && usiVersionMinor.has_value()) { + mParameters.usiVersion = { + .majorVersion = *usiVersionMajor, + .minorVersion = *usiVersionMinor, + }; } - mParameters.enableForInactiveViewport = false; - getDeviceContext().getConfiguration().tryGetProperty("touch.enableForInactiveViewport", - mParameters.enableForInactiveViewport); + mParameters.enableForInactiveViewport = + config.getBool("touch.enableForInactiveViewport").value_or(false); } void TouchInputMapper::configureDeviceType() { @@ -457,7 +452,8 @@ void TouchInputMapper::configureDeviceType() { // Type association takes precedence over the device type found in the idc file. std::string deviceTypeString = getDeviceContext().getDeviceTypeAssociation().value_or(""); if (deviceTypeString.empty()) { - getDeviceContext().getConfiguration().tryGetProperty("touch.deviceType", deviceTypeString); + deviceTypeString = + getDeviceContext().getConfiguration().getString("touch.deviceType").value_or(""); } if (deviceTypeString == "touchScreen") { mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN; @@ -1160,92 +1156,79 @@ void TouchInputMapper::parseCalibration() { // Size out.sizeCalibration = Calibration::SizeCalibration::DEFAULT; - std::string sizeCalibrationString; - if (in.tryGetProperty("touch.size.calibration", sizeCalibrationString)) { - if (sizeCalibrationString == "none") { + std::optional sizeCalibrationString = in.getString("touch.size.calibration"); + if (sizeCalibrationString.has_value()) { + if (*sizeCalibrationString == "none") { out.sizeCalibration = Calibration::SizeCalibration::NONE; - } else if (sizeCalibrationString == "geometric") { + } else if (*sizeCalibrationString == "geometric") { out.sizeCalibration = Calibration::SizeCalibration::GEOMETRIC; - } else if (sizeCalibrationString == "diameter") { + } else if (*sizeCalibrationString == "diameter") { out.sizeCalibration = Calibration::SizeCalibration::DIAMETER; - } else if (sizeCalibrationString == "box") { + } else if (*sizeCalibrationString == "box") { out.sizeCalibration = Calibration::SizeCalibration::BOX; - } else if (sizeCalibrationString == "area") { + } else if (*sizeCalibrationString == "area") { out.sizeCalibration = Calibration::SizeCalibration::AREA; - } else if (sizeCalibrationString != "default") { - ALOGW("Invalid value for touch.size.calibration: '%s'", sizeCalibrationString.c_str()); + } else if (*sizeCalibrationString != "default") { + ALOGW("Invalid value for touch.size.calibration: '%s'", sizeCalibrationString->c_str()); } } - float sizeScale; - - if (in.tryGetProperty("touch.size.scale", sizeScale)) { - out.sizeScale = sizeScale; - } - float sizeBias; - if (in.tryGetProperty("touch.size.bias", sizeBias)) { - out.sizeBias = sizeBias; - } - bool sizeIsSummed; - if (in.tryGetProperty("touch.size.isSummed", sizeIsSummed)) { - out.sizeIsSummed = sizeIsSummed; - } + out.sizeScale = in.getFloat("touch.size.scale"); + out.sizeBias = in.getFloat("touch.size.bias"); + out.sizeIsSummed = in.getBool("touch.size.isSummed"); // Pressure out.pressureCalibration = Calibration::PressureCalibration::DEFAULT; - std::string pressureCalibrationString; - if (in.tryGetProperty("touch.pressure.calibration", pressureCalibrationString)) { - if (pressureCalibrationString == "none") { + std::optional pressureCalibrationString = + in.getString("touch.pressure.calibration"); + if (pressureCalibrationString.has_value()) { + if (*pressureCalibrationString == "none") { out.pressureCalibration = Calibration::PressureCalibration::NONE; - } else if (pressureCalibrationString == "physical") { + } else if (*pressureCalibrationString == "physical") { out.pressureCalibration = Calibration::PressureCalibration::PHYSICAL; - } else if (pressureCalibrationString == "amplitude") { + } else if (*pressureCalibrationString == "amplitude") { out.pressureCalibration = Calibration::PressureCalibration::AMPLITUDE; - } else if (pressureCalibrationString != "default") { + } else if (*pressureCalibrationString != "default") { ALOGW("Invalid value for touch.pressure.calibration: '%s'", - pressureCalibrationString.c_str()); + pressureCalibrationString->c_str()); } } - float pressureScale; - if (in.tryGetProperty("touch.pressure.scale", pressureScale)) { - out.pressureScale = pressureScale; - } + out.pressureScale = in.getFloat("touch.pressure.scale"); // Orientation out.orientationCalibration = Calibration::OrientationCalibration::DEFAULT; - std::string orientationCalibrationString; - if (in.tryGetProperty("touch.orientation.calibration", orientationCalibrationString)) { - if (orientationCalibrationString == "none") { + std::optional orientationCalibrationString = + in.getString("touch.orientation.calibration"); + if (orientationCalibrationString.has_value()) { + if (*orientationCalibrationString == "none") { out.orientationCalibration = Calibration::OrientationCalibration::NONE; - } else if (orientationCalibrationString == "interpolated") { + } else if (*orientationCalibrationString == "interpolated") { out.orientationCalibration = Calibration::OrientationCalibration::INTERPOLATED; - } else if (orientationCalibrationString == "vector") { + } else if (*orientationCalibrationString == "vector") { out.orientationCalibration = Calibration::OrientationCalibration::VECTOR; - } else if (orientationCalibrationString != "default") { + } else if (*orientationCalibrationString != "default") { ALOGW("Invalid value for touch.orientation.calibration: '%s'", - orientationCalibrationString.c_str()); + orientationCalibrationString->c_str()); } } // Distance out.distanceCalibration = Calibration::DistanceCalibration::DEFAULT; - std::string distanceCalibrationString; - if (in.tryGetProperty("touch.distance.calibration", distanceCalibrationString)) { - if (distanceCalibrationString == "none") { + std::optional distanceCalibrationString = + in.getString("touch.distance.calibration"); + if (distanceCalibrationString.has_value()) { + if (*distanceCalibrationString == "none") { out.distanceCalibration = Calibration::DistanceCalibration::NONE; - } else if (distanceCalibrationString == "scaled") { + } else if (*distanceCalibrationString == "scaled") { out.distanceCalibration = Calibration::DistanceCalibration::SCALED; - } else if (distanceCalibrationString != "default") { + } else if (*distanceCalibrationString != "default") { ALOGW("Invalid value for touch.distance.calibration: '%s'", - distanceCalibrationString.c_str()); + distanceCalibrationString->c_str()); } } - float distanceScale; - if (in.tryGetProperty("touch.distance.scale", distanceScale)) { - out.distanceScale = distanceScale; - } + out.distanceScale = in.getFloat("touch.distance.scale"); } void TouchInputMapper::resolveCalibration() { diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 7b464efccb..df66846f9d 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -16,6 +16,9 @@ #pragma once +#include +#include + #include #include diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp index 3d883389c9..be2bfed691 100644 --- a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp +++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp @@ -19,6 +19,7 @@ #include "gestures/PropertyProvider.h" #include +#include #include #include @@ -255,36 +256,34 @@ void GesturesProp::trySetFromIdcProperty(const android::PropertyMap& idcProperti bool parsedSuccessfully = false; Visitor setVisitor{ [&](int*) { - int32_t value; - parsedSuccessfully = idcProperties.tryGetProperty(propertyName, value); - if (parsedSuccessfully) { - setIntValues({value}); + if (std::optional value = idcProperties.getInt(propertyName); value) { + parsedSuccessfully = true; + setIntValues({*value}); } }, [&](GesturesPropBool*) { - bool value; - parsedSuccessfully = idcProperties.tryGetProperty(propertyName, value); - if (parsedSuccessfully) { - setBoolValues({value}); + if (std::optional value = idcProperties.getBool(propertyName); value) { + parsedSuccessfully = true; + setBoolValues({*value}); } }, [&](double*) { - double value; - parsedSuccessfully = idcProperties.tryGetProperty(propertyName, value); - if (parsedSuccessfully) { - setRealValues({value}); + if (std::optional value = idcProperties.getDouble(propertyName); value) { + parsedSuccessfully = true; + setRealValues({*value}); } }, [&](const char**) { ALOGE("Gesture property \"%s\" is a string, and so cannot be set in an IDC file.", mName.c_str()); + // We've already reported the type mismatch, so set parsedSuccessfully. + parsedSuccessfully = true; }, }; std::visit(setVisitor, mDataPointer); - ALOGE_IF(!parsedSuccessfully, "Gesture property \"%s\" could set due to a type mismatch.", + ALOGE_IF(!parsedSuccessfully, "Gesture property \"%s\" couldn't be set due to a type mismatch.", mName.c_str()); - return; } template diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index eb58d0bd95..cdd24399ec 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -2355,10 +2356,10 @@ TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRe InputReaderConfiguration config; std::list unused = mDevice->configure(ARBITRARY_TIME, &config, 0); - std::string propertyValue; - ASSERT_TRUE(mDevice->getConfiguration().tryGetProperty("key", propertyValue)) + std::optional propertyValue = mDevice->getConfiguration().getString("key"); + ASSERT_TRUE(propertyValue.has_value()) << "Device should have read configuration during configuration phase."; - ASSERT_EQ("value", propertyValue); + ASSERT_EQ("value", *propertyValue); ASSERT_NO_FATAL_FAILURE(mapper1.assertConfigureWasCalled()); ASSERT_NO_FATAL_FAILURE(mapper2.assertConfigureWasCalled()); -- GitLab From d11da56932337501ef8145cfcd2cda1eb7681411 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Tue, 14 Mar 2023 17:02:51 +0000 Subject: [PATCH 1006/1310] Remove some old TODOs from GestureConverter.cpp The first (about supporting more gesture types) is obsolete because we now support all the gesture types we need. The other two sadly won't be fixed, since the Gesture library is rather inconsistent about the signs on its dx and dy values, and if I recall correctly the signs can't be changed using gesture properties for multi-finger swipes. Bug: 251196347 Test: none Change-Id: Ib1e2e41a967a6675b258f415c1b411d37da853ec --- .../inputflinger/reader/mapper/gestures/GestureConverter.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp index 7b5f8e58c3..39ed3f6c27 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp @@ -98,7 +98,6 @@ std::list GestureConverter::handleGesture(nsecs_t when, nsecs_t read case kGestureTypePinch: return handlePinch(when, readTime, gesture); default: - // TODO(b/251196347): handle more gesture types. return {}; } } @@ -298,8 +297,6 @@ NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const G yCursorPosition)); } } - // TODO(b/251196347): Set the gesture properties appropriately to avoid needing to negate the Y - // values. float rotatedDeltaX = dx, rotatedDeltaY = -dy; rotateDelta(mOrientation, &rotatedDeltaX, &rotatedDeltaY); for (size_t i = 0; i < mSwipeFingerCount; i++) { @@ -310,8 +307,6 @@ NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const G coords.getAxisValue(AMOTION_EVENT_AXIS_Y) + rotatedDeltaY); } float xOffset = dx / (mXAxisInfo.maxValue - mXAxisInfo.minValue); - // TODO(b/251196347): Set the gesture properties appropriately to avoid needing to negate the Y - // values. float yOffset = -dy / (mYAxisInfo.maxValue - mYAxisInfo.minValue); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, xOffset); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset); -- GitLab From e286f1c7e2bbb0036b31875e51a8735cb163ccd2 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Tue, 14 Mar 2023 00:22:00 +0000 Subject: [PATCH 1007/1310] JPEG/R: minor update for decoder. Expose metadata for the decoder Updata document Rename "jpegr_metadata" to "jpegr_metadata_struct" Bug: b/264715926 Test: jpegr_test Change-Id: I6d97209453b2338996f5dc2aabfbdbe3e8a8c20c --- .../include/jpegrecoverymap/jpegr.h | 46 ++++++++++++++----- .../include/jpegrecoverymap/jpegrutils.h | 6 +-- libs/jpegrecoverymap/jpegr.cpp | 32 ++++++++----- libs/jpegrecoverymap/jpegrutils.cpp | 4 +- libs/jpegrecoverymap/tests/jpegr_test.cpp | 6 +-- .../tests/recoverymapmath_test.cpp | 24 +++++----- 6 files changed, 75 insertions(+), 43 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h index 9b2dde7b94..e2023a6f07 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h @@ -91,7 +91,10 @@ struct jpegr_exif_struct { int length; }; -struct jpegr_metadata { +/* + * Holds information for recovery map related metadata. + */ +struct jpegr_metadata_struct { // JPEG/R version uint32_t version; // Max Content Boost for the map @@ -103,12 +106,14 @@ struct jpegr_metadata { typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr; typedef struct jpegr_compressed_struct* jr_compressed_ptr; typedef struct jpegr_exif_struct* jr_exif_ptr; -typedef struct jpegr_metadata* jr_metadata_ptr; +typedef struct jpegr_metadata_struct* jr_metadata_ptr; typedef struct jpegr_info_struct* jr_info_ptr; class JpegR { public: /* + * Experimental only + * * Encode API-0 * Compress JPEGR image from 10-bit HDR YUV. * @@ -199,21 +204,40 @@ public: * Decode API * Decompress JPEGR image. * - * The output JPEGR image is in RGBA_1010102 data format if decoding to HDR. - * @param compressed_jpegr_image compressed JPEGR image - * @param dest destination of the uncompressed JPEGR image - * @param exif destination of the decoded EXIF metadata. - * @param output_format flag for setting output color format. if set to - * {@code JPEGR_OUTPUT_SDR}, decoder will only decode the primary image - * which is SDR. Default value is JPEGR_OUTPUT_HDR_LINEAR. - * @param recovery_map destination of the decoded recovery map. + * @param compressed_jpegr_image compressed JPEGR image. + * @param dest destination of the uncompressed JPEGR image. + * @param exif destination of the decoded EXIF metadata. The default value is NULL where the + decoder will do nothing about it. If configured not NULL the decoder will write + EXIF data into this structure. The format is defined in {@code jpegr_exif_struct} + * @param output_format flag for setting output color format. Its value configures the output + color format. The default value is {@code JPEGR_OUTPUT_HDR_LINEAR}. + ---------------------------------------------------------------------- + | output_format | decoded color format to be written | + ---------------------------------------------------------------------- + | JPEGR_OUTPUT_SDR | RGBA_8888 | + ---------------------------------------------------------------------- + | JPEGR_OUTPUT_HDR_LINEAR | (default)RGBA_F16 linear | + ---------------------------------------------------------------------- + | JPEGR_OUTPUT_HDR_PQ | RGBA_1010102 PQ | + ---------------------------------------------------------------------- + | JPEGR_OUTPUT_HDR_HLG | RGBA_1010102 HLG | + ---------------------------------------------------------------------- + * @param recovery_map destination of the decoded recovery map. The default value is NULL where + the decoder will do nothing about it. If configured not NULL the decoder + will write the decoded recovery_map data into this structure. The format + is defined in {@code jpegr_uncompressed_struct}. + * @param metadata destination of the decoded metadata. The default value is NULL where the + decoder will do nothing about it. If configured not NULL the decoder will + write metadata into this structure. the format of metadata is defined in + {@code jpegr_metadata}. * @return NO_ERROR if decoding succeeds, error code if error occurs. */ status_t decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, jr_uncompressed_ptr dest, jr_exif_ptr exif = nullptr, jpegr_output_format output_format = JPEGR_OUTPUT_HDR_LINEAR, - jr_uncompressed_ptr recovery_map = nullptr); + jr_uncompressed_ptr recovery_map = nullptr, + jr_metadata_ptr metadata = nullptr); /* * Gets Info from JPEGR file without decoding it. diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h index a381743137..dd06fa24b1 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h @@ -45,7 +45,7 @@ static inline uint16_t EndianSwap16(uint16_t value) { #define Endian_SwapBE16(n) (n) #endif -struct jpegr_metadata; +struct jpegr_metadata_struct; /* * Mutable data structure. Holds information for metadata. */ @@ -87,7 +87,7 @@ status_t Write(jr_compressed_ptr destination, const void* source, size_t length, * @param metadata place to store HDR metadata values * @return true if metadata is successfully retrieved, false otherwise */ -bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata); +bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata_struct* metadata); /* * This method generates XMP metadata for the primary image. @@ -156,7 +156,7 @@ std::string generateXmpForPrimaryImage(int secondary_image_length); * @param metadata JPEG/R metadata to encode as XMP * @return XMP metadata in type of string */ - std::string generateXmpForSecondaryImage(jpegr_metadata& metadata); + std::string generateXmpForSecondaryImage(jpegr_metadata_struct& metadata); } // namespace android::jpegrecoverymap #endif //ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H diff --git a/libs/jpegrecoverymap/jpegr.cpp b/libs/jpegrecoverymap/jpegr.cpp index f8763c637c..e197bf0848 100644 --- a/libs/jpegrecoverymap/jpegr.cpp +++ b/libs/jpegrecoverymap/jpegr.cpp @@ -107,7 +107,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return ERROR_JPEGR_INVALID_INPUT_TYPE; } - jpegr_metadata metadata; + jpegr_metadata_struct metadata; metadata.version = kJpegrVersion; jpegr_uncompressed_struct uncompressed_yuv_420_image; @@ -176,7 +176,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return ERROR_JPEGR_INVALID_INPUT_TYPE; } - jpegr_metadata metadata; + jpegr_metadata_struct metadata; metadata.version = kJpegrVersion; jpegr_uncompressed_struct map; @@ -235,7 +235,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return ERROR_JPEGR_INVALID_INPUT_TYPE; } - jpegr_metadata metadata; + jpegr_metadata_struct metadata; metadata.version = kJpegrVersion; jpegr_uncompressed_struct map; @@ -288,7 +288,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return ERROR_JPEGR_RESOLUTION_MISMATCH; } - jpegr_metadata metadata; + jpegr_metadata_struct metadata; metadata.version = kJpegrVersion; jpegr_uncompressed_struct map; @@ -332,7 +332,8 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, jr_uncompressed_ptr dest, jr_exif_ptr exif, jpegr_output_format output_format, - jr_uncompressed_ptr recovery_map) { + jr_uncompressed_ptr recovery_map, + jr_metadata_ptr metadata) { if (compressed_jpegr_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } @@ -387,6 +388,18 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, memcpy(recovery_map->data, recovery_map_decoder.getDecompressedImagePtr(), size); } + jpegr_metadata_struct jr_metadata; + if (!getMetadataFromXMP(static_cast(recovery_map_decoder.getXMPPtr()), + recovery_map_decoder.getXMPSize(), &jr_metadata)) { + return ERROR_JPEGR_DECODE_ERROR; + } + + if (metadata != nullptr) { + metadata->version = jr_metadata.version; + metadata->minContentBoost = jr_metadata.minContentBoost; + metadata->maxContentBoost = jr_metadata.maxContentBoost; + } + if (output_format == JPEGR_OUTPUT_SDR) { return NO_ERROR; } @@ -417,13 +430,8 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth(); uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight(); - jpegr_metadata metadata; - if (!getMetadataFromXMP(static_cast(recovery_map_decoder.getXMPPtr()), - recovery_map_decoder.getXMPSize(), &metadata)) { - return ERROR_JPEGR_DECODE_ERROR; - } - - JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, output_format, dest)); + JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &jr_metadata, output_format, + dest)); return NO_ERROR; } diff --git a/libs/jpegrecoverymap/jpegrutils.cpp b/libs/jpegrecoverymap/jpegrutils.cpp index 38b78ad19c..ff96447320 100644 --- a/libs/jpegrecoverymap/jpegrutils.cpp +++ b/libs/jpegrecoverymap/jpegrutils.cpp @@ -253,7 +253,7 @@ const string kMapBaseRendition = Name(kRecoveryMapPrefix, "BaseRendition"); const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin; const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax; -bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) { +bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata_struct* metadata) { string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; if (xmp_size < nameSpace.size()+2) { @@ -327,7 +327,7 @@ string generateXmpForPrimaryImage(int secondary_image_length) { return ss.str(); } -string generateXmpForSecondaryImage(jpegr_metadata& metadata) { +string generateXmpForSecondaryImage(jpegr_metadata_struct& metadata) { const vector kConDirSeq({kConDirectory, string("rdf:Seq")}); const vector kLiItem({string("rdf:li"), kConItem}); diff --git a/libs/jpegrecoverymap/tests/jpegr_test.cpp b/libs/jpegrecoverymap/tests/jpegr_test.cpp index 0a7d20a434..be4b972251 100644 --- a/libs/jpegrecoverymap/tests/jpegr_test.cpp +++ b/libs/jpegrecoverymap/tests/jpegr_test.cpp @@ -174,7 +174,7 @@ TEST_F(JpegRTest, build) { } TEST_F(JpegRTest, writeXmpThenRead) { - jpegr_metadata metadata_expected; + jpegr_metadata_struct metadata_expected; metadata_expected.maxContentBoost = 1.25; metadata_expected.minContentBoost = 0.75; const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; @@ -189,7 +189,7 @@ TEST_F(JpegRTest, writeXmpThenRead) { xmpData.insert(xmpData.end(), reinterpret_cast(xmp.c_str()), reinterpret_cast(xmp.c_str()) + xmp.size()); - jpegr_metadata metadata_read; + jpegr_metadata_struct metadata_read; EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read)); ASSERT_EQ(metadata_expected.maxContentBoost, metadata_read.maxContentBoost); ASSERT_EQ(metadata_expected.minContentBoost, metadata_read.minContentBoost); @@ -476,7 +476,7 @@ TEST_F(JpegRTest, ProfileRecoveryMapFuncs) { JpegRBenchmark benchmark; - jpegr_metadata metadata = { .version = 1, + jpegr_metadata_struct metadata = { .version = 1, .maxContentBoost = 8.0f, .minContentBoost = 1.0f / 8.0f }; diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp index 6c61ff13d7..cf6a034053 100644 --- a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp +++ b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp @@ -554,8 +554,8 @@ TEST_F(RecoveryMapMathTest, srgbInvOetfLUT) { TEST_F(RecoveryMapMathTest, applyRecoveryLUT) { for (int boost = 1; boost <= 10; boost++) { - jpegr_metadata metadata = { .maxContentBoost = static_cast(boost), - .minContentBoost = 1.0f / static_cast(boost) }; + jpegr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), + .minContentBoost = 1.0f / static_cast(boost) }; RecoveryLUT recoveryLUT(&metadata); for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) { float value = static_cast(idx) / static_cast(kRecoveryFactorNumEntries - 1); @@ -573,8 +573,8 @@ TEST_F(RecoveryMapMathTest, applyRecoveryLUT) { } for (int boost = 1; boost <= 10; boost++) { - jpegr_metadata metadata = { .maxContentBoost = static_cast(boost), - .minContentBoost = 1.0f }; + jpegr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), + .minContentBoost = 1.0f }; RecoveryLUT recoveryLUT(&metadata); for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) { float value = static_cast(idx) / static_cast(kRecoveryFactorNumEntries - 1); @@ -592,8 +592,8 @@ TEST_F(RecoveryMapMathTest, applyRecoveryLUT) { } for (int boost = 1; boost <= 10; boost++) { - jpegr_metadata metadata = { .maxContentBoost = static_cast(boost), - .minContentBoost = 1.0f / pow(static_cast(boost), + jpegr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), + .minContentBoost = 1.0f / pow(static_cast(boost), 1.0f / 3.0f) }; RecoveryLUT recoveryLUT(&metadata); for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) { @@ -659,8 +659,8 @@ TEST_F(RecoveryMapMathTest, ColorConversionLookup) { } TEST_F(RecoveryMapMathTest, EncodeRecovery) { - jpegr_metadata metadata = { .maxContentBoost = 4.0f, - .minContentBoost = 1.0f / 4.0f }; + jpegr_metadata_struct metadata = { .maxContentBoost = 4.0f, + .minContentBoost = 1.0f / 4.0f }; EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 127); EXPECT_EQ(encodeRecovery(0.0f, 1.0f, &metadata), 127); @@ -717,8 +717,8 @@ TEST_F(RecoveryMapMathTest, EncodeRecovery) { } TEST_F(RecoveryMapMathTest, ApplyRecovery) { - jpegr_metadata metadata = { .maxContentBoost = 4.0f, - .minContentBoost = 1.0f / 4.0f }; + jpegr_metadata_struct metadata = { .maxContentBoost = 4.0f, + .minContentBoost = 1.0f / 4.0f }; EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.0f, &metadata), RgbBlack()); EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.5f, &metadata), RgbBlack()); @@ -981,8 +981,8 @@ TEST_F(RecoveryMapMathTest, GenerateMapLuminancePq) { } TEST_F(RecoveryMapMathTest, ApplyMap) { - jpegr_metadata metadata = { .maxContentBoost = 8.0f, - .minContentBoost = 1.0f / 8.0f }; + jpegr_metadata_struct metadata = { .maxContentBoost = 8.0f, + .minContentBoost = 1.0f / 8.0f }; EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); -- GitLab From b1e10d15e6b38ac8524f5155736e78db5d223a24 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Mon, 13 Mar 2023 15:23:54 -0700 Subject: [PATCH 1008/1310] SF: handle presentTime in the past presentTime in the past would result in Perfetto showing unterminated slices. Fixing this issue by checking for this case specifically, marking it janky, and fixing the presentTime to be the endTime instead. Test: SF unit tests Bug: 271105399 Change-Id: I7e5d641dfe58717322b7a82b0609fc728e9a69fc --- services/surfaceflinger/FrameTimeline/FrameTimeline.cpp | 8 +++++--- .../surfaceflinger/tests/unittests/FrameTimelineTest.cpp | 7 ++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp index 925f111dc6..ded734efad 100644 --- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp +++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp @@ -885,15 +885,17 @@ void FrameTimeline::DisplayFrame::setGpuFence(const std::shared_ptr& void FrameTimeline::DisplayFrame::classifyJank(nsecs_t& deadlineDelta, nsecs_t& deltaToVsync, nsecs_t previousPresentTime) { - if (mPredictionState == PredictionState::Expired || - mSurfaceFlingerActuals.presentTime == Fence::SIGNAL_TIME_INVALID) { + const bool presentTimeValid = + mSurfaceFlingerActuals.presentTime >= mSurfaceFlingerActuals.startTime; + if (mPredictionState == PredictionState::Expired || !presentTimeValid) { // Cannot do jank classification with expired predictions or invalid signal times. Set the // deltas to 0 as both negative and positive deltas are used as real values. mJankType = JankType::Unknown; deadlineDelta = 0; deltaToVsync = 0; - if (mSurfaceFlingerActuals.presentTime == Fence::SIGNAL_TIME_INVALID) { + if (!presentTimeValid) { mSurfaceFlingerActuals.presentTime = mSurfaceFlingerActuals.endTime; + mJankType |= JankType::DisplayHAL; } return; diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp index abd77894af..d26ef3c821 100644 --- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp +++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp @@ -489,7 +489,7 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_invalidSignalTime) { auto displayFrame0 = getDisplayFrame(0); EXPECT_EQ(displayFrame0->getActuals().presentTime, 59); - EXPECT_EQ(displayFrame0->getJankType(), JankType::Unknown); + EXPECT_EQ(displayFrame0->getJankType(), JankType::Unknown | JankType::DisplayHAL); EXPECT_EQ(surfaceFrame1->getActuals().presentTime, -1); EXPECT_EQ(surfaceFrame1->getJankType(), JankType::Unknown); } @@ -2259,6 +2259,7 @@ TEST_F(FrameTimelineTest, jankClassification_presentFenceError) { mFrameTimeline->setSfWakeUp(sfToken3, 72, Fps::fromPeriodNsecs(11)); mFrameTimeline->setSfPresent(80, validPresentFence); + erroneousPresentFence2->signalForTest(2); validPresentFence->signalForTest(80); addEmptyDisplayFrame(); @@ -2268,14 +2269,14 @@ TEST_F(FrameTimelineTest, jankClassification_presentFenceError) { EXPECT_EQ(displayFrame->getActuals().presentTime, 26); EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::UnknownPresent); EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::UnknownFinish); - EXPECT_EQ(displayFrame->getJankType(), JankType::Unknown); + EXPECT_EQ(displayFrame->getJankType(), JankType::Unknown | JankType::DisplayHAL); } { auto displayFrame = getDisplayFrame(1); EXPECT_EQ(displayFrame->getActuals().presentTime, 60); EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::UnknownPresent); EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::UnknownFinish); - EXPECT_EQ(displayFrame->getJankType(), JankType::Unknown); + EXPECT_EQ(displayFrame->getJankType(), JankType::Unknown | JankType::DisplayHAL); } { auto displayFrame = getDisplayFrame(2); -- GitLab From d6271e0eb080025ee5f0cc3fa82b2c0c73e9675a Mon Sep 17 00:00:00 2001 From: Rachel Lee Date: Tue, 14 Mar 2023 17:53:54 +0000 Subject: [PATCH 1009/1310] Revert "Resync on the transaction." This reverts commit a5be328a8f3cb420ecdae34e9b25060631616a8f. Reason for revert: b/272956327 Change-Id: I0484a0396144cbcaa76e9b3a38d157f744d9a303 --- services/surfaceflinger/Scheduler/Scheduler.cpp | 1 - services/surfaceflinger/SurfaceFlinger.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 6e332721b8..f18dfdceb6 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -402,7 +402,6 @@ void Scheduler::disableHardwareVsync(PhysicalDisplayId id, bool disallow) { } void Scheduler::resyncAllToHardwareVsync(bool allowToEnable) { - ATRACE_CALL(); std::scoped_lock lock(mDisplayLock); ftl::FakeGuard guard(kMainThreadContext); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index e708a659a3..2c1e3339d8 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4119,7 +4119,6 @@ void SurfaceFlinger::setTransactionFlags(uint32_t mask, TransactionSchedule sche ATRACE_INT("mTransactionFlags", transactionFlags); if (const bool scheduled = transactionFlags & mask; !scheduled) { - mScheduler->resync(); scheduleCommit(frameHint); } else if (frameHint == FrameHint::kActive) { // Even if the next frame is already scheduled, we should reset the idle timer -- GitLab From 7fee44112b0c71686d69c6de1833760d224acd6e Mon Sep 17 00:00:00 2001 From: Yuxin Hu Date: Tue, 14 Mar 2023 01:43:05 +0000 Subject: [PATCH 1010/1310] Conditionally expose EGL_ANDROID_get_frame_timestamps extension Expose EGL_ANDROID_get_frame_timestamps extension if service.sf.present_timestamp sysprop is true, and we use ANGLE egl. On cuttlefish we disabled service.sf.present_timestamp, this further disables VK_GOOGLE_display_timing extension. ANGLE's eglSurfaceAttrib(EGL_TIMESTAMPS_ANDROID) requires both EGL_ANDROID_get_frame_timestamps and VK_GOOGLE_display_timing extension to work: https://chromium-review.googlesource.com/c/angle/angle/+/3753396. Android EGL Wrapper's eglSurfaceAttrib() implementation forwards the call to ANGLE. Therefore, we need to make ANGLE and Android EGL Wrapper report consistent results: only expose EGL_ANDROID_get_frame_timestamps in Android EGL Wrapper if service.sf.present_timestamp sysprop is true. That way we won't have Android EGL Wrapper claiming the EGL_ANDROID_get_frame_timestamps extension is supported first, but then the subsequent call of eglSurfaceAttrib(EGL_TIMESTAMPS_ANDROID) to ANGLE fails. Bug: b/269060366 Test: m; cts-tradefed run commandAndExit cts -m CtsDeqpTestCases --abi x86 --skip-preconditions --skip-all-system-status-check --module-arg 'CtsDeqpTestCases:deqp-use-angle:vulkan' --module-arg 'CtsDeqpTestCases:include-filter:dEQP-EGL.functional.get_frame_timestamps*'; Change-Id: Id96d0c749c80ba54aa1e48dba85817efab8b7eea --- opengl/libs/EGL/egl_display.cpp | 11 +++++++++++ opengl/libs/EGL/egl_platform_entries.cpp | 23 ++++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/opengl/libs/EGL/egl_display.cpp b/opengl/libs/EGL/egl_display.cpp index 0b755aadca..c2c856e22a 100644 --- a/opengl/libs/EGL/egl_display.cpp +++ b/opengl/libs/EGL/egl_display.cpp @@ -322,6 +322,16 @@ EGLBoolean egl_display_t::initialize(EGLint* major, EGLint* minor) { mExtensionString = gBuiltinExtensionString; + // b/269060366 Conditionally enabled EGL_ANDROID_get_frame_timestamps extension if the + // device's present timestamps are reliable (which may not be the case on emulators). + if (cnx->useAngle) { + if (android::base::GetBoolProperty("service.sf.present_timestamp", false)) { + mExtensionString.append("EGL_ANDROID_get_frame_timestamps"); + } + } else { + mExtensionString.append("EGL_ANDROID_get_frame_timestamps"); + } + hasColorSpaceSupport = findExtension(disp.queryString.extensions, "EGL_KHR_gl_colorspace"); // Note: CDD requires that devices supporting wide color and/or HDR color also support @@ -361,6 +371,7 @@ EGLBoolean egl_display_t::initialize(EGLint* major, EGLint* minor) { findExtension(disp.queryString.extensions, "EGL_KHR_image_gl_colorspace")) { mExtensionString.append("EGL_EXT_image_gl_colorspace "); } + if (findExtension(disp.queryString.extensions, ext.c_str(), len)) { mExtensionString.append(ext + " "); } diff --git a/opengl/libs/EGL/egl_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp index 0527c8a45a..2bca14d2f9 100644 --- a/opengl/libs/EGL/egl_platform_entries.cpp +++ b/opengl/libs/EGL/egl_platform_entries.cpp @@ -84,7 +84,8 @@ extern const char* const gExtensionString; // Extensions implemented by the EGL wrapper. const char* const gBuiltinExtensionString = "EGL_ANDROID_front_buffer_auto_refresh " - "EGL_ANDROID_get_frame_timestamps " + // b/269060366 Conditionally enabled during display initialization: + //"EGL_ANDROID_get_frame_timestamps " "EGL_ANDROID_get_native_client_buffer " "EGL_ANDROID_presentation_time " "EGL_EXT_surface_CTA861_3_metadata " @@ -2185,6 +2186,10 @@ EGLBoolean eglGetNextFrameIdANDROIDImpl(EGLDisplay dpy, EGLSurface surface, EGLu return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE); } + if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) { + return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE); + } + SurfaceRef _s(dp, surface); if (!_s.get()) { return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE); @@ -2218,6 +2223,10 @@ EGLBoolean eglGetCompositorTimingANDROIDImpl(EGLDisplay dpy, EGLSurface surface, return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE); } + if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) { + return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE); + } + SurfaceRef _s(dp, surface); if (!_s.get()) { return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE); @@ -2272,6 +2281,10 @@ EGLBoolean eglGetCompositorTimingSupportedANDROIDImpl(EGLDisplay dpy, EGLSurface return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE); } + if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) { + return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE); + } + SurfaceRef _s(dp, surface); if (!_s.get()) { return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE); @@ -2302,6 +2315,10 @@ EGLBoolean eglGetFrameTimestampsANDROIDImpl(EGLDisplay dpy, EGLSurface surface, return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE); } + if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) { + return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE); + } + SurfaceRef _s(dp, surface); if (!_s.get()) { return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE); @@ -2387,6 +2404,10 @@ EGLBoolean eglGetFrameTimestampSupportedANDROIDImpl(EGLDisplay dpy, EGLSurface s return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE); } + if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) { + return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE); + } + SurfaceRef _s(dp, surface); if (!_s.get()) { return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE); -- GitLab From 2b1b429c49ff59027578d5f046cba4082adcc7dc Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Tue, 14 Mar 2023 16:47:39 +0000 Subject: [PATCH 1011/1310] Skip transaction processing if the surface damage stays the same in Layer::setSurfaceDamageRegion. Bug: 273143519 Test: builds Change-Id: Id4df40e869c2735ab7049eb50f07b81f56024001 --- services/surfaceflinger/Layer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 0f2af2f809..95213718e5 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -3168,6 +3168,7 @@ bool Layer::setHdrMetadata(const HdrMetadata& hdrMetadata) { } bool Layer::setSurfaceDamageRegion(const Region& surfaceDamage) { + if (mDrawingState.surfaceDamageRegion.hasSameRects(surfaceDamage)) return false; mDrawingState.surfaceDamageRegion = surfaceDamage; mDrawingState.modified = true; setTransactionFlags(eTransactionNeeded); -- GitLab From d54717018c529dce495756811de58552a2bcd819 Mon Sep 17 00:00:00 2001 From: Charles Chen Date: Tue, 14 Mar 2023 21:30:38 +0000 Subject: [PATCH 1012/1310] API Feedback: rename AddServiceWithFlag with plurals Make AServiceManager_addServiceWithFlags method plural since the argument flags passed into the methods should be a bitmask which can contain multiple options. Bug: 272102518 Test: libbinder_ndk_unit_test Change-Id: I0c31ef78f187be728e7c86c429a670b3a9081b36 --- libs/binder/ndk/include_platform/android/binder_manager.h | 6 +++--- libs/binder/ndk/libbinder_ndk.map.txt | 2 +- libs/binder/ndk/service_manager.cpp | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h index 43159d8ba2..89fd7a38c1 100644 --- a/libs/binder/ndk/include_platform/android/binder_manager.h +++ b/libs/binder/ndk/include_platform/android/binder_manager.h @@ -56,12 +56,12 @@ __attribute__((warn_unused_result)) binder_exception_t AServiceManager_addServic * * \param binder object to register globally with the service manager. * \param instance identifier of the service. This will be used to lookup the service. - * \param flag an AServiceManager_AddServiceFlag enum to denote how the service should be added. + * \param flags an AServiceManager_AddServiceFlag enum to denote how the service should be added. * * \return EX_NONE on success. */ -__attribute__((warn_unused_result)) binder_exception_t AServiceManager_addServiceWithFlag( - AIBinder* binder, const char* instance, const AServiceManager_AddServiceFlag flag) +__attribute__((warn_unused_result)) binder_exception_t AServiceManager_addServiceWithFlags( + AIBinder* binder, const char* instance, const AServiceManager_AddServiceFlag flags) __INTRODUCED_IN(34); /** diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt index 1078fb2b16..1c5f79f791 100644 --- a/libs/binder/ndk/libbinder_ndk.map.txt +++ b/libs/binder/ndk/libbinder_ndk.map.txt @@ -158,7 +158,7 @@ LIBBINDER_NDK34 { # introduced=UpsideDownCake AServiceManager_getUpdatableApexName; # systemapi AServiceManager_registerForServiceNotifications; # systemapi llndk AServiceManager_NotificationRegistration_delete; # systemapi llndk - AServiceManager_addServiceWithFlag; # systemapi llndk + AServiceManager_addServiceWithFlags; # systemapi llndk }; LIBBINDER_NDK_PLATFORM { diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp index 84da459454..29777866e2 100644 --- a/libs/binder/ndk/service_manager.cpp +++ b/libs/binder/ndk/service_manager.cpp @@ -42,15 +42,15 @@ binder_exception_t AServiceManager_addService(AIBinder* binder, const char* inst return PruneException(exception); } -binder_exception_t AServiceManager_addServiceWithFlag(AIBinder* binder, const char* instance, - const AServiceManager_AddServiceFlag flag) { +binder_exception_t AServiceManager_addServiceWithFlags(AIBinder* binder, const char* instance, + const AServiceManager_AddServiceFlag flags) { if (binder == nullptr || instance == nullptr) { return EX_ILLEGAL_ARGUMENT; } sp sm = defaultServiceManager(); - bool allowIsolated = flag & AServiceManager_AddServiceFlag::ADD_SERVICE_ALLOW_ISOLATED; + bool allowIsolated = flags & AServiceManager_AddServiceFlag::ADD_SERVICE_ALLOW_ISOLATED; status_t exception = sm->addService(String16(instance), binder->getBinder(), allowIsolated); return PruneException(exception); } -- GitLab From 1573a67beda9ae3d940002dcf9150e10567a27fa Mon Sep 17 00:00:00 2001 From: tyiu Date: Tue, 21 Feb 2023 22:38:32 +0000 Subject: [PATCH 1013/1310] Fix HMAC Compare time attack Added constant time HMAC comparison preventing attackers being able to forge HMAC for input by measuring the time difference between non-constant time comparison of HMAC Bug: 261085213 Test: None Tag: #security Change-Id: I7cd6b68589fd0042b9396dc599b917a0f3220ff7 --- services/inputflinger/dispatcher/InputDispatcher.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index cd427f03cf..5ea543fa10 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -31,6 +31,7 @@ #endif #include #include +#include #include #include #include @@ -4629,7 +4630,7 @@ std::unique_ptr InputDispatcher::verifyInputEvent(const Inpu if (calculatedHmac == INVALID_HMAC) { return nullptr; } - if (calculatedHmac != event.getHmac()) { + if (0 != CRYPTO_memcmp(calculatedHmac.data(), event.getHmac().data(), calculatedHmac.size())) { return nullptr; } return result; -- GitLab From d19848fcab877e614ee3a4124fa3df9da45ea55a Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 15 Mar 2023 18:24:28 +0000 Subject: [PATCH 1014/1310] Revert "[LayerTraceGenerator] fix mirrors and rel-z data" This reverts commit 39872bc1b63e955e376f30d2d2012cb6c8becdc9. Reason for revert: b/273495141 Change-Id: I94712f4cbc9dbd6b0c59b28ea9759921d4123bb2 --- .../surfaceflinger/FrontEnd/LayerHierarchy.h | 10 -- .../surfaceflinger/FrontEnd/LayerSnapshot.cpp | 11 +- .../FrontEnd/LayerSnapshotBuilder.h | 14 ++- services/surfaceflinger/LayerProtoHelper.cpp | 111 +++++------------- services/surfaceflinger/LayerProtoHelper.h | 41 +------ services/surfaceflinger/SurfaceFlinger.cpp | 14 ++- .../Tracing/tools/LayerTraceGenerator.cpp | 25 +++- .../surfaceflinger/layerproto/layers.proto | 2 - .../tracing/TransactionTraceTestSuite.cpp | 20 +--- 9 files changed, 88 insertions(+), 160 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h index b25b731356..3dd89badff 100644 --- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h @@ -104,16 +104,6 @@ public: static const TraversalPath ROOT; }; - struct TraversalPathHash { - std::size_t operator()(const LayerHierarchy::TraversalPath& key) const { - uint32_t hashCode = key.id * 31; - if (key.mirrorRootId != UNASSIGNED_LAYER_ID) { - hashCode += key.mirrorRootId * 31; - } - return std::hash{}(hashCode); - } - }; - // Helper class to add nodes to an existing traversal id and removes the // node when it goes out of scope. class ScopedAddToTraversalPath { diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp index 5e7c25946b..8a450933f0 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp @@ -28,14 +28,9 @@ LayerSnapshot::LayerSnapshot(const RequestedLayerState& state, const LayerHierarchy::TraversalPath& path) : path(path) { static uint32_t sUniqueSequenceId = 0; - // Provide a unique id for all snapshots. - // A front end layer can generate multiple snapshots if its mirrored. - // Additionally, if the layer is not reachable, we may choose to destroy - // and recreate the snapshot in which case the unique sequence id will - // change. The consumer shouldn't tie any lifetimes to this unique id but - // register a LayerLifecycleManager::ILifecycleListener or get a list of - // destroyed layers from LayerLifecycleManager. - uniqueSequence = sUniqueSequenceId++; + // Provide a unique id for clones otherwise keeping using the sequence id. + // The seq id can still be useful for debugging if its available. + uniqueSequence = (path.isClone()) ? sUniqueSequenceId++ : state.id; sequence = static_cast(state.id); name = state.name; textureName = state.textureName; diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h index 7b1ff2710b..3997a0ad83 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h @@ -68,7 +68,6 @@ public: void update(const Args&); std::vector>& getSnapshots(); LayerSnapshot* getSnapshot(uint32_t layerId) const; - LayerSnapshot* getSnapshot(const LayerHierarchy::TraversalPath& id) const; typedef std::function ConstVisitor; @@ -87,6 +86,7 @@ public: private: friend class LayerSnapshotTest; + LayerSnapshot* getSnapshot(const LayerHierarchy::TraversalPath& id) const; static LayerSnapshot getRootSnapshot(); // return true if we were able to successfully update the snapshots via @@ -120,8 +120,16 @@ private: void updateChildState(LayerSnapshot& snapshot, const LayerSnapshot& childSnapshot, const Args& args); - std::unordered_map + struct TraversalPathHash { + std::size_t operator()(const LayerHierarchy::TraversalPath& key) const { + uint32_t hashCode = key.id * 31; + if (key.mirrorRootId != UNASSIGNED_LAYER_ID) { + hashCode += key.mirrorRootId * 31; + } + return std::hash{}(hashCode); + } + }; + std::unordered_map mIdToSnapshot; std::vector> mSnapshots; LayerSnapshot mRootSnapshot; diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp index b5ae1a7f5a..5c91b9181b 100644 --- a/services/surfaceflinger/LayerProtoHelper.cpp +++ b/services/surfaceflinger/LayerProtoHelper.cpp @@ -15,8 +15,6 @@ */ // TODO(b/129481165): remove the #pragma below and fix conversion issues -#include "FrontEnd/LayerCreationArgs.h" -#include "FrontEnd/LayerSnapshot.h" #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wconversion" #pragma clang diagnostic ignored "-Wextra" @@ -250,88 +248,47 @@ void LayerProtoHelper::readFromProto(const BlurRegion& proto, android::BlurRegio outRegion.bottom = proto.bottom(); } -LayersProto LayerProtoFromSnapshotGenerator::generate(const frontend::LayerHierarchy& root) { - mLayersProto.clear_layers(); - std::unordered_set stackIdsToSkip; - if ((mTraceFlags & LayerTracing::TRACE_VIRTUAL_DISPLAYS) == 0) { - for (const auto& [layerStack, displayInfo] : mDisplayInfos) { - if (displayInfo.isVirtual) { - stackIdsToSkip.insert(layerStack.id); - } - } - } +void LayerProtoHelper::writeHierarchyToProto( + LayersProto& outLayersProto, const frontend::LayerHierarchy& root, + const frontend::LayerSnapshotBuilder& snapshotBuilder, + const std::unordered_map>& legacyLayers, uint32_t traceFlags) { + using Variant = frontend::LayerHierarchy::Variant; + frontend::LayerSnapshot defaultSnapshot; - frontend::LayerHierarchy::TraversalPath path = frontend::LayerHierarchy::TraversalPath::ROOT; - for (auto& [child, variant] : root.mChildren) { - if (variant != frontend::LayerHierarchy::Variant::Attached || - stackIdsToSkip.find(child->getLayer()->layerStack.id) != stackIdsToSkip.end()) { - continue; - } - frontend::LayerHierarchy::ScopedAddToTraversalPath addChildToPath(path, - child->getLayer()->id, - variant); - LayerProtoFromSnapshotGenerator::writeHierarchyToProto(*child, path); - } + LayerProto* layerProto = outLayersProto.add_layers(); + const frontend::RequestedLayerState& layer = *root.getLayer(); + frontend::LayerSnapshot* snapshot = snapshotBuilder.getSnapshot(layer.id); - // fill in relative and parent info - for (int i = 0; i < mLayersProto.layers_size(); i++) { - auto layerProto = mLayersProto.mutable_layers()->Mutable(i); - auto it = mChildToRelativeParent.find(layerProto->id()); - if (it == mChildToRelativeParent.end()) { - layerProto->set_z_order_relative_of(-1); - } else { - layerProto->set_z_order_relative_of(it->second); - } - it = mChildToParent.find(layerProto->id()); - if (it == mChildToParent.end()) { - layerProto->set_parent(-1); - } else { - layerProto->set_parent(it->second); + if (!snapshot) { + defaultSnapshot.uniqueSequence = layer.id; + snapshot = &defaultSnapshot; + } + writeSnapshotToProto(layerProto, layer, *snapshot, traceFlags); + for (const auto& [child, variant] : root.mChildren) { + if (variant == Variant::Attached || variant == Variant::Detached) { + layerProto->add_children(child->getLayer()->id); + } else if (variant == Variant::Relative) { + layerProto->add_relatives(child->getLayer()->id); } } - mDefaultSnapshots.clear(); - mChildToRelativeParent.clear(); - return std::move(mLayersProto); -} - -frontend::LayerSnapshot* LayerProtoFromSnapshotGenerator::getSnapshot( - frontend::LayerHierarchy::TraversalPath& path, const frontend::RequestedLayerState& layer) { - frontend::LayerSnapshot* snapshot = mSnapshotBuilder.getSnapshot(path); - if (snapshot) { - return snapshot; + auto parent = root.getParent(); + if (parent && parent->getLayer()) { + layerProto->set_parent(parent->getLayer()->id); } else { - mDefaultSnapshots[path] = frontend::LayerSnapshot(layer, path); - return &mDefaultSnapshots[path]; + layerProto->set_parent(-1); } -} - -void LayerProtoFromSnapshotGenerator::writeHierarchyToProto( - const frontend::LayerHierarchy& root, frontend::LayerHierarchy::TraversalPath& path) { - using Variant = frontend::LayerHierarchy::Variant; - LayerProto* layerProto = mLayersProto.add_layers(); - const frontend::RequestedLayerState& layer = *root.getLayer(); - frontend::LayerSnapshot* snapshot = getSnapshot(path, layer); - LayerProtoHelper::writeSnapshotToProto(layerProto, layer, *snapshot, mTraceFlags); - for (const auto& [child, variant] : root.mChildren) { - frontend::LayerHierarchy::ScopedAddToTraversalPath addChildToPath(path, - child->getLayer()->id, - variant); - frontend::LayerSnapshot* childSnapshot = getSnapshot(path, layer); - if (variant == Variant::Attached || variant == Variant::Detached || - variant == Variant::Mirror) { - mChildToParent[childSnapshot->uniqueSequence] = snapshot->uniqueSequence; - layerProto->add_children(childSnapshot->uniqueSequence); - } else if (variant == Variant::Relative) { - mChildToRelativeParent[childSnapshot->uniqueSequence] = snapshot->uniqueSequence; - layerProto->add_relatives(childSnapshot->uniqueSequence); - } + auto relativeParent = root.getRelativeParent(); + if (relativeParent && relativeParent->getLayer()) { + layerProto->set_z_order_relative_of(relativeParent->getLayer()->id); + } else { + layerProto->set_z_order_relative_of(-1); } - if (mTraceFlags & LayerTracing::TRACE_COMPOSITION) { - auto it = mLegacyLayers.find(layer.id); - if (it != mLegacyLayers.end()) { + if (traceFlags & LayerTracing::TRACE_COMPOSITION) { + auto it = legacyLayers.find(layer.id); + if (it != legacyLayers.end()) { it->second->writeCompositionStateToProto(layerProto); } } @@ -341,10 +298,7 @@ void LayerProtoFromSnapshotGenerator::writeHierarchyToProto( if (variant == Variant::Detached) { continue; } - frontend::LayerHierarchy::ScopedAddToTraversalPath addChildToPath(path, - child->getLayer()->id, - variant); - writeHierarchyToProto(*child, path); + writeHierarchyToProto(outLayersProto, *child, snapshotBuilder, legacyLayers, traceFlags); } } @@ -391,7 +345,6 @@ void LayerProtoHelper::writeSnapshotToProto(LayerProto* layerInfo, layerInfo->set_shadow_radius(snapshot.shadowRadius); layerInfo->set_id(snapshot.uniqueSequence); - layerInfo->set_original_id(snapshot.sequence); layerInfo->set_name(requestedState.name); layerInfo->set_type("Layer"); diff --git a/services/surfaceflinger/LayerProtoHelper.h b/services/surfaceflinger/LayerProtoHelper.h index b84a49b27c..38d73f613f 100644 --- a/services/surfaceflinger/LayerProtoHelper.h +++ b/services/surfaceflinger/LayerProtoHelper.h @@ -25,9 +25,6 @@ #include #include #include -#include -#include "FrontEnd/LayerHierarchy.h" -#include "FrontEnd/LayerSnapshot.h" namespace android { namespace surfaceflinger { @@ -61,6 +58,11 @@ public: static void readFromProto(const ColorTransformProto& colorTransformProto, mat4& matrix); static void writeToProto(const android::BlurRegion region, BlurRegion*); static void readFromProto(const BlurRegion& proto, android::BlurRegion& outRegion); + static void writeHierarchyToProto(LayersProto& layersProto, + const frontend::LayerHierarchy& root, + const frontend::LayerSnapshotBuilder& snapshotBuilder, + const std::unordered_map>& mLegacyLayers, + uint32_t traceFlags); static void writeSnapshotToProto(LayerProto* outProto, const frontend::RequestedLayerState& requestedState, const frontend::LayerSnapshot& snapshot, uint32_t traceFlags); @@ -68,38 +70,5 @@ public: const display::DisplayMap& displayInfos); }; -class LayerProtoFromSnapshotGenerator { -public: - LayerProtoFromSnapshotGenerator( - const frontend::LayerSnapshotBuilder& snapshotBuilder, - const display::DisplayMap& displayInfos, - const std::unordered_map>& legacyLayers, uint32_t traceFlags) - : mSnapshotBuilder(snapshotBuilder), - mLegacyLayers(legacyLayers), - mDisplayInfos(displayInfos), - mTraceFlags(traceFlags) {} - LayersProto generate(const frontend::LayerHierarchy& root); - -private: - void writeHierarchyToProto(const frontend::LayerHierarchy& root, - frontend::LayerHierarchy::TraversalPath& path); - frontend::LayerSnapshot* getSnapshot(frontend::LayerHierarchy::TraversalPath& path, - const frontend::RequestedLayerState& layer); - - const frontend::LayerSnapshotBuilder& mSnapshotBuilder; - const std::unordered_map>& mLegacyLayers; - const display::DisplayMap& mDisplayInfos; - uint32_t mTraceFlags; - LayersProto mLayersProto; - // winscope expects all the layers, so provide a snapshot even if it not currently drawing - std::unordered_map - mDefaultSnapshots; - std::unordered_map - mChildToRelativeParent; - std::unordered_map - mChildToParent; -}; - } // namespace surfaceflinger } // namespace android diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 01a7df398b..e47a147ed8 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5786,9 +5786,17 @@ LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const { return layersProto; } - return LayerProtoFromSnapshotGenerator(mLayerSnapshotBuilder, mFrontEndDisplayInfos, {}, - traceFlags) - .generate(mLayerHierarchyBuilder.getHierarchy()); + const frontend::LayerHierarchy& root = mLayerHierarchyBuilder.getHierarchy(); + LayersProto layersProto; + for (auto& [child, variant] : root.mChildren) { + if (variant != frontend::LayerHierarchy::Variant::Attached || + stackIdsToSkip.find(child->getLayer()->layerStack.id) != stackIdsToSkip.end()) { + continue; + } + LayerProtoHelper::writeHierarchyToProto(layersProto, *child, mLayerSnapshotBuilder, + mLegacyLayers, traceFlags); + } + return layersProto; } google::protobuf::RepeatedPtrField SurfaceFlinger::dumpDisplayProto() const { diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index 2418cf22e5..0a7101ccce 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -134,10 +134,29 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, lifecycleManager.getGlobalChanges().string().c_str()); lifecycleManager.commitChanges(); + // write layers trace + auto tracingFlags = LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS; + std::unordered_set stackIdsToSkip; + if ((tracingFlags & LayerTracing::TRACE_VIRTUAL_DISPLAYS) == 0) { + for (const auto& displayInfo : displayInfos) { + if (displayInfo.second.isVirtual) { + stackIdsToSkip.insert(displayInfo.first.id); + } + } + } + + const frontend::LayerHierarchy& root = hierarchyBuilder.getHierarchy(); + + LayersProto layersProto; + for (auto& [child, variant] : root.mChildren) { + if (variant != frontend::LayerHierarchy::Variant::Attached || + stackIdsToSkip.find(child->getLayer()->layerStack.id) != stackIdsToSkip.end()) { + continue; + } + LayerProtoHelper::writeHierarchyToProto(layersProto, *child, snapshotBuilder, {}, + tracingFlags); + } - LayersProto layersProto = LayerProtoFromSnapshotGenerator(snapshotBuilder, displayInfos, {}, - layerTracing.getFlags()) - .generate(hierarchyBuilder.getHierarchy()); auto displayProtos = LayerProtoHelper::writeDisplayInfoToProto(displayInfos); layerTracing.notify(visibleRegionsDirty, entry.elapsed_realtime_nanos(), entry.vsync_id(), &layersProto, {}, &displayProtos); diff --git a/services/surfaceflinger/layerproto/layers.proto b/services/surfaceflinger/layerproto/layers.proto index e9add2e1a4..3598308338 100644 --- a/services/surfaceflinger/layerproto/layers.proto +++ b/services/surfaceflinger/layerproto/layers.proto @@ -138,8 +138,6 @@ message LayerProto { float requested_corner_radius = 56; RectProto destination_frame = 57; - - uint32 original_id = 58; } message PositionProto { diff --git a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp index 7355c35131..5f9214c548 100644 --- a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp +++ b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include @@ -85,9 +84,9 @@ protected: std::vector TransactionTraceTestSuite::sTransactionTraces{}; struct LayerInfo { - int32_t id; + int id; std::string name; - int32_t parent; + int parent; int z; uint64_t curr_frame; float x; @@ -144,27 +143,16 @@ TEST_P(TransactionTraceTestSuite, validateEndState) { expectedLayers.reserve(static_cast(expectedLastEntry.layers().layers_size())); for (int i = 0; i < expectedLastEntry.layers().layers_size(); i++) { auto layer = expectedLastEntry.layers().layers(i); - LayerInfo layerInfo = getLayerInfoFromProto(layer); - expectedLayers.push_back(layerInfo); + expectedLayers.push_back(getLayerInfoFromProto(layer)); } std::sort(expectedLayers.begin(), expectedLayers.end(), compareById); - std::unordered_map snapshotIdToLayerId; std::vector actualLayers; actualLayers.reserve(static_cast(actualLastEntry.layers().layers_size())); for (int i = 0; i < actualLastEntry.layers().layers_size(); i++) { auto layer = actualLastEntry.layers().layers(i); - LayerInfo layerInfo = getLayerInfoFromProto(layer); - snapshotIdToLayerId[layerInfo.id] = static_cast(layer.original_id()); - actualLayers.push_back(layerInfo); - } - - for (auto& layer : actualLayers) { - layer.id = snapshotIdToLayerId[layer.id]; - auto it = snapshotIdToLayerId.find(layer.parent); - layer.parent = it == snapshotIdToLayerId.end() ? -1 : it->second; + actualLayers.push_back(getLayerInfoFromProto(layer)); } - std::sort(actualLayers.begin(), actualLayers.end(), compareById); size_t i = 0; -- GitLab From cb56533cae4994f8e7337b18e3966b02038b221a Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 14 Mar 2023 21:10:55 -0700 Subject: [PATCH 1015/1310] [layertracegenerator] stream layer trace to file Write the generated layer trace entry by entry so we don't hold the entire unserialized layer trace in memory. Fixes: 272158947 Test: presubmit Test: open streamed trace in winscope Test: cat /proc/`pidof layertracegenerator`/status old VmHWM: 959496 kB new VmHWM: 21416 kB Change-Id: I4bf0cd8fe98d1e2a44fb6b7c85c4e06e586a52ed --- .../surfaceflinger/Tracing/LayerTracing.cpp | 21 +++++++++++++----- .../surfaceflinger/Tracing/LayerTracing.h | 5 +++-- services/surfaceflinger/Tracing/RingBuffer.h | 14 ++++++++++++ .../Tracing/tools/LayerTraceGenerator.cpp | 10 +++++++-- .../unittests/TransactionTracingTest.cpp | 22 +++++++++++++++++++ 5 files changed, 63 insertions(+), 9 deletions(-) diff --git a/services/surfaceflinger/Tracing/LayerTracing.cpp b/services/surfaceflinger/Tracing/LayerTracing.cpp index 2918f7cd37..ecdeabe528 100644 --- a/services/surfaceflinger/Tracing/LayerTracing.cpp +++ b/services/surfaceflinger/Tracing/LayerTracing.cpp @@ -18,6 +18,8 @@ #define LOG_TAG "LayerTracing" #define ATRACE_TAG ATRACE_TAG_GRAPHICS +#include + #include #include #include @@ -44,30 +46,39 @@ bool LayerTracing::enable() { return true; } -bool LayerTracing::disable(std::string filename) { +bool LayerTracing::disable(std::string filename, bool writeToFile) { std::scoped_lock lock(mTraceLock); if (!mEnabled) { return false; } mEnabled = false; - LayersTraceFileProto fileProto = createTraceFileProto(); - mBuffer->writeToFile(fileProto, filename); + if (writeToFile) { + LayersTraceFileProto fileProto = createTraceFileProto(); + mBuffer->writeToFile(fileProto, filename); + } mBuffer->reset(); return true; } +void LayerTracing::appendToStream(std::ofstream& out) { + std::scoped_lock lock(mTraceLock); + LayersTraceFileProto fileProto = createTraceFileProto(); + mBuffer->appendToStream(fileProto, out); + mBuffer->reset(); +} + bool LayerTracing::isEnabled() const { std::scoped_lock lock(mTraceLock); return mEnabled; } -status_t LayerTracing::writeToFile() { +status_t LayerTracing::writeToFile(std::string filename) { std::scoped_lock lock(mTraceLock); if (!mEnabled) { return STATUS_OK; } LayersTraceFileProto fileProto = createTraceFileProto(); - return mBuffer->writeToFile(fileProto, FILE_NAME); + return mBuffer->writeToFile(fileProto, filename); } void LayerTracing::setTraceFlags(uint32_t flags) { diff --git a/services/surfaceflinger/Tracing/LayerTracing.h b/services/surfaceflinger/Tracing/LayerTracing.h index 11bb9f45df..40b0fbee18 100644 --- a/services/surfaceflinger/Tracing/LayerTracing.h +++ b/services/surfaceflinger/Tracing/LayerTracing.h @@ -43,9 +43,10 @@ public: LayerTracing(); ~LayerTracing(); bool enable(); - bool disable(std::string filename = FILE_NAME); + bool disable(std::string filename = FILE_NAME, bool writeToFile = true); + void appendToStream(std::ofstream& out); bool isEnabled() const; - status_t writeToFile(); + status_t writeToFile(std::string filename = FILE_NAME); static LayersTraceFileProto createTraceFileProto(); void notify(bool visibleRegionDirty, int64_t time, int64_t vsyncId, LayersProto* layers, std::string hwcDump, google::protobuf::RepeatedPtrField* displays); diff --git a/services/surfaceflinger/Tracing/RingBuffer.h b/services/surfaceflinger/Tracing/RingBuffer.h index 7e38c55840..b41c65b10e 100644 --- a/services/surfaceflinger/Tracing/RingBuffer.h +++ b/services/surfaceflinger/Tracing/RingBuffer.h @@ -24,6 +24,7 @@ #include #include #include +#include #include namespace android { @@ -73,6 +74,19 @@ public: return NO_ERROR; } + status_t appendToStream(FileProto& fileProto, std::ofstream& out) { + ATRACE_CALL(); + writeToProto(fileProto); + std::string output; + if (!fileProto.SerializeToString(&output)) { + ALOGE("Could not serialize proto."); + return UNKNOWN_ERROR; + } + + out << output; + return NO_ERROR; + } + std::vector emplace(std::string&& serializedProto) { std::vector replacedEntries; size_t protoSize = static_cast(serializedProto.size()); diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index 2418cf22e5..0ea421bd24 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include #include #include "FrontEnd/LayerCreationArgs.h" @@ -62,8 +63,11 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, LayerTracing layerTracing; layerTracing.setTraceFlags(LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS); - layerTracing.setBufferSize(512 * 1024 * 1024); // 512MB buffer size + // 10MB buffer size (large enough to hold a single entry) + layerTracing.setBufferSize(10 * 1024 * 1024); layerTracing.enable(); + layerTracing.writeToFile(outputLayersTracePath); + std::ofstream out(outputLayersTracePath, std::ios::binary | std::ios::app); ALOGD("Generating %d transactions...", traceFile.entry_size()); for (int i = 0; i < traceFile.entry_size(); i++) { @@ -141,8 +145,10 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, auto displayProtos = LayerProtoHelper::writeDisplayInfoToProto(displayInfos); layerTracing.notify(visibleRegionsDirty, entry.elapsed_realtime_nanos(), entry.vsync_id(), &layersProto, {}, &displayProtos); + layerTracing.appendToStream(out); } - layerTracing.disable(outputLayersTracePath); + layerTracing.disable("", /*writeToFile=*/false); + out.close(); ALOGD("End of generating trace file. File written to %s", outputLayersTracePath); return true; } diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp index 74541ace0b..82aac7e4bf 100644 --- a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp @@ -21,8 +21,10 @@ #include #include "Client.h" +#include #include "FrontEnd/LayerCreationArgs.h" #include "FrontEnd/Update.h" +#include "Tracing/LayerTracing.h" #include "Tracing/RingBuffer.h" #include "Tracing/TransactionTracing.h" @@ -305,4 +307,24 @@ TEST_F(TransactionTracingMirrorLayerTest, canAddMirrorLayers) { EXPECT_EQ(proto.entry(0).transactions(0).layer_changes().size(), 2); EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(1).z(), 43); } + +// Verify we can write the layers traces by entry to reduce mem pressure +// on the system when generating large traces. +TEST(LayerTraceTest, canStreamLayersTrace) { + LayersTraceFileProto inProto = LayerTracing::createTraceFileProto(); + inProto.add_entry(); + inProto.add_entry(); + + std::string output; + inProto.SerializeToString(&output); + LayersTraceFileProto inProto2 = LayerTracing::createTraceFileProto(); + inProto2.add_entry(); + std::string output2; + inProto2.SerializeToString(&output2); + + LayersTraceFileProto outProto; + outProto.ParseFromString(output + output2); + // magic? + EXPECT_EQ(outProto.entry().size(), 3); +} } // namespace android -- GitLab From 8506e364dee8d22bd6bd5055472bcf8e7da21b00 Mon Sep 17 00:00:00 2001 From: Ian Elliott Date: Wed, 8 Mar 2023 12:12:09 -0700 Subject: [PATCH 1016/1310] SkiaVkRenderEngine: Do not cache textures with protected contexts `SkiaRenderEngine::mTextureCache` is not used with protected contexts with the SkiaVk back-end. This avoids crashed caused by using a VkImage with a VkDevice that it wasn't created with. Test: Face unlock training Bug: 271907068 Bug: 264475961 Bug: 267295152 Change-Id: I1296539677ae63be63614ab2464dbb55b32276cd --- libs/renderengine/skia/SkiaRenderEngine.cpp | 52 +++++++++------------ libs/renderengine/skia/SkiaRenderEngine.h | 6 ++- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index 5965d417f4..e393fb2e92 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -395,10 +395,14 @@ void SkiaRenderEngine::mapExternalTextureBuffer(const sp& buffer, mRenderEngineType != RenderEngineType::SKIA_VK_THREADED) { return; } - // We currently don't attempt to map a buffer if the buffer contains protected content - // because GPU resources for protected buffers is much more limited. + // We don't attempt to map a buffer if the buffer contains protected content. In GL this is + // important because GPU resources for protected buffers are much more limited. (In Vk we + // simply match the existing behavior for protected buffers.) In Vk, we never cache any + // buffers while in a protected context, since Vk cannot share across contexts, and protected + // is less common. const bool isProtectedBuffer = buffer->getUsage() & GRALLOC_USAGE_PROTECTED; - if (isProtectedBuffer) { + if (isProtectedBuffer || + (mRenderEngineType == RenderEngineType::SKIA_VK_THREADED && isProtected())) { return; } ATRACE_CALL(); @@ -461,6 +465,20 @@ void SkiaRenderEngine::unmapExternalTextureBuffer(sp&& buffer) { } } +std::shared_ptr SkiaRenderEngine::getOrCreateBackendTexture( + const sp& buffer, bool isOutputBuffer) { + // Do not lookup the buffer in the cache for protected contexts with the SkiaVk back-end + if (mRenderEngineType == RenderEngineType::SKIA_GL_THREADED || + (mRenderEngineType == RenderEngineType::SKIA_VK_THREADED && !isProtected())) { + if (const auto& it = mTextureCache.find(buffer->getId()); it != mTextureCache.end()) { + return it->second; + } + } + return std::make_shared(getActiveGrContext(), + buffer->toAHardwareBuffer(), + isOutputBuffer, mTextureCleanupMgr); +} + bool SkiaRenderEngine::canSkipPostRenderCleanup() const { std::lock_guard lock(mRenderingMutex); return mTextureCleanupMgr.isEmpty(); @@ -651,21 +669,11 @@ void SkiaRenderEngine::drawLayersInternal( validateOutputBufferUsage(buffer->getBuffer()); auto grContext = getActiveGrContext(); - auto& cache = mTextureCache; // any AutoBackendTexture deletions will now be deferred until cleanupPostRender is called DeferTextureCleanup dtc(mTextureCleanupMgr); - std::shared_ptr surfaceTextureRef; - if (const auto& it = cache.find(buffer->getBuffer()->getId()); it != cache.end()) { - surfaceTextureRef = it->second; - } else { - surfaceTextureRef = - std::make_shared(grContext, - buffer->getBuffer() - ->toAHardwareBuffer(), - true, mTextureCleanupMgr); - } + auto surfaceTextureRef = getOrCreateBackendTexture(buffer->getBuffer(), true); // wait on the buffer to be ready to use prior to using it waitFence(grContext, bufferFence); @@ -904,21 +912,7 @@ void SkiaRenderEngine::drawLayersInternal( ATRACE_NAME("DrawImage"); validateInputBufferUsage(layer.source.buffer.buffer->getBuffer()); const auto& item = layer.source.buffer; - std::shared_ptr imageTextureRef = nullptr; - - if (const auto& iter = cache.find(item.buffer->getBuffer()->getId()); - iter != cache.end()) { - imageTextureRef = iter->second; - } else { - // If we didn't find the image in the cache, then create a local ref but don't cache - // it. If we're using skia, we're guaranteed to run on a dedicated GPU thread so if - // we didn't find anything in the cache then we intentionally did not cache this - // buffer's resources. - imageTextureRef = std::make_shared< - AutoBackendTexture::LocalRef>(grContext, - item.buffer->getBuffer()->toAHardwareBuffer(), - false, mTextureCleanupMgr); - } + auto imageTextureRef = getOrCreateBackendTexture(item.buffer->getBuffer(), false); // if the layer's buffer has a fence, then we must must respect the fence prior to using // the buffer. diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h index dd6646ba71..e4406b437b 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.h +++ b/libs/renderengine/skia/SkiaRenderEngine.h @@ -133,6 +133,8 @@ private: void unmapExternalTextureBuffer(sp&& buffer) override final; bool canSkipPostRenderCleanup() const override final; + std::shared_ptr getOrCreateBackendTexture( + const sp& buffer, bool isOutputBuffer) REQUIRES(mRenderingMutex); void initCanvas(SkCanvas* canvas, const DisplaySettings& display); void drawShadow(SkCanvas* canvas, const SkRRect& casterRRect, const ShadowSettings& shadowSettings); @@ -167,7 +169,9 @@ private: // Number of external holders of ExternalTexture references, per GraphicBuffer ID. std::unordered_map mGraphicBufferExternalRefs GUARDED_BY(mRenderingMutex); - // Cache of GL textures that we'll store per GraphicBuffer ID, shared between GPU contexts. + // For GL, this cache is shared between protected and unprotected contexts. For Vulkan, it is + // only used for the unprotected context, because Vulkan does not allow sharing between + // contexts, and protected is less common. std::unordered_map> mTextureCache GUARDED_BY(mRenderingMutex); std::unordered_map, shaders::LinearEffectHasher> -- GitLab From 10ce157c2109efef8bb32ddd9a32af0aa91f9819 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 15 Mar 2023 09:15:21 -0700 Subject: [PATCH 1017/1310] Remove buttonState from PointerController Before this CL, PointerController contained APIs setButtonState / getButtonState. However, these APIs did not do anything. Therefore, remove these APIs. Bug: 217579815 Test: atest inputflinger_tests Change-Id: I610005593edeeabbd516f8288a464f7a15fa5b37 --- .../include/PointerControllerInterface.h | 6 --- .../reader/mapper/CursorInputMapper.cpp | 5 --- .../reader/mapper/TouchInputMapper.cpp | 4 -- .../tests/FakePointerController.cpp | 8 ---- .../tests/FakePointerController.h | 3 -- .../inputflinger/tests/InputReader_test.cpp | 45 +------------------ .../tests/fuzzers/MapperHelpers.h | 2 - 7 files changed, 2 insertions(+), 71 deletions(-) diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h index 3f1ff30cb2..95f819a8da 100644 --- a/services/inputflinger/include/PointerControllerInterface.h +++ b/services/inputflinger/include/PointerControllerInterface.h @@ -59,12 +59,6 @@ public: /* Move the pointer. */ virtual void move(float deltaX, float deltaY) = 0; - /* Sets a mask that indicates which buttons are pressed. */ - virtual void setButtonState(int32_t buttonState) = 0; - - /* Gets a mask that indicates which buttons are pressed. */ - virtual int32_t getButtonState() const = 0; - /* Sets the absolute location of the pointer. */ virtual void setPosition(float x, float y) = 0; diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index b12a009d7f..b27f70ced3 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -373,11 +373,6 @@ std::list CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { if (moved) { mPointerController->move(deltaX, deltaY); } - - if (buttonsChanged) { - mPointerController->setButtonState(currentButtonState); - } - mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); } diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index a943a03d16..34a4e932b7 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -1658,7 +1658,6 @@ void TouchInputMapper::updateTouchSpots() { mPointerController->setPresentation(PointerControllerInterface::Presentation::SPOT); mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); - mPointerController->setButtonState(mCurrentRawState.buttonState); mPointerController->setSpots(mCurrentCookedState.cookedPointerData.pointerCoords.cbegin(), mCurrentCookedState.cookedPointerData.idToIndex.cbegin(), mCurrentCookedState.cookedPointerData.touchingIdBits | @@ -3029,8 +3028,6 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi prepareMultiFingerPointerGestures(when, outCancelPreviousGesture, outFinishPreviousGesture); } - mPointerController->setButtonState(mCurrentRawState.buttonState); - if (DEBUG_GESTURES) { ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, " "currentGestureMode=%d, currentGestureIdBits=0x%08x, " @@ -3566,7 +3563,6 @@ std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsec if (down || hovering) { mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); mPointerController->clearSpots(); - mPointerController->setButtonState(mCurrentRawState.buttonState); mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); } else if (!down && !hovering && (mPointerSimple.down || mPointerSimple.hovering)) { mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp index ad311f9c7a..ca517f32c9 100644 --- a/services/inputflinger/tests/FakePointerController.cpp +++ b/services/inputflinger/tests/FakePointerController.cpp @@ -37,14 +37,6 @@ void FakePointerController::setPosition(float x, float y) { mY = y; } -void FakePointerController::setButtonState(int32_t buttonState) { - mButtonState = buttonState; -} - -int32_t FakePointerController::getButtonState() const { - return mButtonState; -} - FloatPoint FakePointerController::getPosition() const { return {mX, mY}; } diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h index 6d7bbebf92..c374267ff9 100644 --- a/services/inputflinger/tests/FakePointerController.h +++ b/services/inputflinger/tests/FakePointerController.h @@ -32,8 +32,6 @@ public: const std::map>& getSpots(); void setPosition(float x, float y) override; - void setButtonState(int32_t buttonState) override; - int32_t getButtonState() const override; FloatPoint getPosition() const override; int32_t getDisplayId() const override; void setDisplayViewport(const DisplayViewport& viewport) override; @@ -54,7 +52,6 @@ private: bool mHaveBounds{false}; float mMinX{0}, mMinY{0}, mMaxX{0}, mMaxY{0}; float mX{0}, mY{0}; - int32_t mButtonState{0}; int32_t mDisplayId{ADISPLAY_ID_DEFAULT}; bool mIsPointerShown{false}; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index cdd24399ec..0de3bfb9a2 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -4114,7 +4114,6 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); mFakePointerController->setPosition(100, 200); - mFakePointerController->setButtonState(0); NotifyMotionArgs motionArgs; NotifyKeyArgs keyArgs; @@ -4125,14 +4124,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); @@ -4141,21 +4138,18 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); @@ -4166,26 +4160,20 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, - motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, - mFakePointerController->getButtonState()); + motionArgs.buttonState); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, - mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, - motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, - mFakePointerController->getButtonState()); + motionArgs.buttonState); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); @@ -4194,14 +4182,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f)); @@ -4210,7 +4196,6 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0); @@ -4218,14 +4203,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); @@ -4240,14 +4223,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); @@ -4256,14 +4237,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); @@ -4281,14 +4260,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); @@ -4297,14 +4274,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); @@ -4322,14 +4297,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); @@ -4338,14 +4311,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); @@ -4363,14 +4334,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); - ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); @@ -4379,14 +4348,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); ASSERT_EQ(0, motionArgs.buttonState); - ASSERT_EQ(0, mFakePointerController->getButtonState()); ASSERT_NO_FATAL_FAILURE( assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 0.0f)); @@ -4401,7 +4368,6 @@ TEST_F(CursorInputMapperTest, Process_WhenModeIsPointer_ShouldMoveThePointerArou mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); mFakePointerController->setPosition(100, 200); - mFakePointerController->setButtonState(0); NotifyMotionArgs args; @@ -4428,7 +4394,6 @@ TEST_F(CursorInputMapperTest, Process_PointerCapture) { mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); mFakePointerController->setPosition(100, 200); - mFakePointerController->setButtonState(0); NotifyMotionArgs args; @@ -4607,7 +4572,6 @@ TEST_F(CursorInputMapperTest, ConfigureDisplayId_NoAssociatedViewport) { mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); mFakePointerController->setPosition(100, 200); - mFakePointerController->setButtonState(0); // Ensure input events are generated for the secondary display. process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); @@ -4635,7 +4599,6 @@ TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) { mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); mFakePointerController->setPosition(100, 200); - mFakePointerController->setButtonState(0); process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); @@ -9164,7 +9127,6 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) { std::make_shared(); fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); fakePointerController->setPosition(100, 200); - fakePointerController->setButtonState(0); mFakePolicy->setPointerController(fakePointerController); mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); @@ -10159,7 +10121,6 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) { std::make_shared(); fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); fakePointerController->setPosition(0, 0); - fakePointerController->setButtonState(0); // prepare device and capture prepareDisplay(ui::ROTATION_0); @@ -10309,7 +10270,6 @@ TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) { std::make_shared(); fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); fakePointerController->setPosition(0, 0); - fakePointerController->setButtonState(0); // prepare device and capture prepareDisplay(ui::ROTATION_0); @@ -10449,7 +10409,6 @@ protected: std::make_shared(); fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); fakePointerController->setPosition(0, 0); - fakePointerController->setButtonState(0); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION); diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index 5d7bf4bc44..2cb5cdf9fc 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -236,8 +236,6 @@ public: } } void move(float deltaX, float deltaY) override {} - void setButtonState(int32_t buttonState) override {} - int32_t getButtonState() const override { return mFdp->ConsumeIntegral(); } void setPosition(float x, float y) override {} FloatPoint getPosition() const override { return {mFdp->ConsumeFloatingPoint(), mFdp->ConsumeFloatingPoint()}; -- GitLab From ac15a1bf362128ee4495b372c944122694f5b978 Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Tue, 28 Feb 2023 06:51:28 +0000 Subject: [PATCH 1018/1310] Make SF Hint Session safety margin adjustable with a debug prop Bug: 271016792 Test: manual Change-Id: I59615b90dc109285cd681b89ff80a569037c2992 (cherry picked from commit eae1e257b6dc5a039d5c8422b089849730e04c83) --- .../DisplayHardware/PowerAdvisor.cpp | 9 ++++++--- .../surfaceflinger/DisplayHardware/PowerAdvisor.h | 3 ++- .../tests/unittests/PowerAdvisorTest.cpp | 14 ++++++++++---- .../mock/DisplayHardware/MockAidlPowerHalWrapper.h | 2 ++ 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp index f05223cce0..36f71bb481 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp @@ -219,7 +219,7 @@ void PowerAdvisor::sendActualWorkDuration() { std::lock_guard lock(mPowerHalMutex); HalWrapper* const halWrapper = getPowerHal(); if (halWrapper != nullptr) { - halWrapper->sendActualWorkDuration(*actualDuration + kTargetSafetyMargin, + halWrapper->sendActualWorkDuration(*actualDuration + sTargetSafetyMargin, TimePoint::now()); } } @@ -232,12 +232,11 @@ void PowerAdvisor::sendPredictedWorkDuration() { } const std::optional predictedDuration = estimateWorkDuration(true); - if (predictedDuration.has_value()) { std::lock_guard lock(mPowerHalMutex); HalWrapper* const halWrapper = getPowerHal(); if (halWrapper != nullptr) { - halWrapper->sendActualWorkDuration(*predictedDuration + kTargetSafetyMargin, + halWrapper->sendActualWorkDuration(*predictedDuration + sTargetSafetyMargin, TimePoint::now()); } } @@ -812,6 +811,10 @@ std::optional AidlPowerHalWrapper::getTargetWorkDuration() { const bool AidlPowerHalWrapper::sTraceHintSessionData = base::GetBoolProperty(std::string("debug.sf.trace_hint_sessions"), false); +const Duration PowerAdvisor::sTargetSafetyMargin = std::chrono::microseconds( + base::GetIntProperty("debug.sf.hint_margin_us", + ticks(PowerAdvisor::kDefaultTargetSafetyMargin))); + PowerAdvisor::HalWrapper* PowerAdvisor::getPowerHal() { if (!mHasHal) { return nullptr; diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h index d45e7cb572..c4cfdc3482 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h @@ -274,7 +274,8 @@ private: // An adjustable safety margin which pads the "actual" value sent to PowerHAL, // encouraging more aggressive boosting to give SurfaceFlinger a larger margin for error - static constexpr const Duration kTargetSafetyMargin{1ms}; + static const Duration sTargetSafetyMargin; + static constexpr const Duration kDefaultTargetSafetyMargin{1ms}; // How long we expect hwc to run after the present call until it waits for the fence static constexpr const Duration kFenceWaitStartDelayValidated{150us}; diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp index 2d66d3cf92..d22ce17258 100644 --- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp +++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp @@ -42,12 +42,12 @@ public: void fakeBasicFrameTiming(TimePoint startTime, Duration vsyncPeriod); void setExpectedTiming(Duration totalFrameTargetDuration, TimePoint expectedPresentTime); Duration getFenceWaitDelayDuration(bool skipValidate); + Duration getErrorMargin(); protected: TestableSurfaceFlinger mFlinger; std::unique_ptr mPowerAdvisor; NiceMock* mMockAidlWrapper; - Duration kErrorMargin = 1ms; }; void PowerAdvisorTest::SetUp() FTL_FAKE_GUARD(mPowerAdvisor->mPowerHalMutex) { @@ -77,6 +77,8 @@ void PowerAdvisorTest::fakeBasicFrameTiming(TimePoint startTime, Duration vsyncP mPowerAdvisor->setCommitStart(startTime); mPowerAdvisor->setFrameDelay(0ns); mPowerAdvisor->setTargetWorkDuration(vsyncPeriod); + ON_CALL(*mMockAidlWrapper, getTargetWorkDuration()) + .WillByDefault(Return(std::make_optional(vsyncPeriod))); } Duration PowerAdvisorTest::getFenceWaitDelayDuration(bool skipValidate) { @@ -84,6 +86,10 @@ Duration PowerAdvisorTest::getFenceWaitDelayDuration(bool skipValidate) { : PowerAdvisor::kFenceWaitStartDelayValidated); } +Duration PowerAdvisorTest::getErrorMargin() { + return mPowerAdvisor->sTargetSafetyMargin; +} + namespace { TEST_F(PowerAdvisorTest, hintSessionUseHwcDisplay) { @@ -109,7 +115,7 @@ TEST_F(PowerAdvisorTest, hintSessionUseHwcDisplay) { // increment the frame startTime += vsyncPeriod; - const Duration expectedDuration = kErrorMargin + presentDuration + postCompDuration; + const Duration expectedDuration = getErrorMargin() + presentDuration + postCompDuration; EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1); fakeBasicFrameTiming(startTime, vsyncPeriod); @@ -145,7 +151,7 @@ TEST_F(PowerAdvisorTest, hintSessionSubtractsHwcFenceTime) { // increment the frame startTime += vsyncPeriod; - const Duration expectedDuration = kErrorMargin + presentDuration + + const Duration expectedDuration = getErrorMargin() + presentDuration + getFenceWaitDelayDuration(false) - hwcBlockedDuration + postCompDuration; EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1); @@ -185,7 +191,7 @@ TEST_F(PowerAdvisorTest, hintSessionUsingSecondaryVirtualDisplays) { // increment the frame startTime += vsyncPeriod; - const Duration expectedDuration = kErrorMargin + presentDuration + postCompDuration; + const Duration expectedDuration = getErrorMargin() + presentDuration + postCompDuration; EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1); fakeBasicFrameTiming(startTime, vsyncPeriod); diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h index 5654691884..3ed85e0b1f 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h @@ -47,6 +47,8 @@ public: MOCK_METHOD(void, sendActualWorkDuration, (Duration actualDuration, TimePoint timestamp), (override)); MOCK_METHOD(bool, shouldReconnectHAL, (), (override)); + MOCK_METHOD(std::vector, getPowerHintSessionThreadIds, (), (override)); + MOCK_METHOD(std::optional, getTargetWorkDuration, (), (override)); }; } // namespace android::Hwc2::mock \ No newline at end of file -- GitLab From e3fd4177be783d72ce6031fdba1666d8d225f98b Mon Sep 17 00:00:00 2001 From: Thomas Nguyen Date: Wed, 15 Mar 2023 20:26:49 +0000 Subject: [PATCH 1019/1310] DO NOT MERGE "Add FEATURE_TELEPHONY_SATELLITE feature definition" Bug: 273555341 Test: atest android.telephony.cts.SatelliteManagerTest This reverts commit cd5deb44597f1084d8a3671fe62f690ad6fe146e. Reason for revert: We want to support satellite in CF via vendor service stack. Change-Id: I6bf11bf9165a97145adc1f786a7f7aadd8c1977b --- data/etc/Android.bp | 6 ++++++ .../android.hardware.telephony.satellite.xml | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 data/etc/android.hardware.telephony.satellite.xml diff --git a/data/etc/Android.bp b/data/etc/Android.bp index bdd5172e60..a737bd3fb7 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -166,6 +166,12 @@ prebuilt_etc { defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.hardware.telephony.satellite.prebuilt.xml", + src: "android.hardware.telephony.satellite.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "android.hardware.usb.accessory.prebuilt.xml", src: "android.hardware.usb.accessory.xml", diff --git a/data/etc/android.hardware.telephony.satellite.xml b/data/etc/android.hardware.telephony.satellite.xml new file mode 100644 index 0000000000..d36c958763 --- /dev/null +++ b/data/etc/android.hardware.telephony.satellite.xml @@ -0,0 +1,20 @@ + + + + + + + -- GitLab From b2e4cfdc78dbc1dbbe20e7bf02bd0c0926936a11 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Fri, 10 Mar 2023 17:13:02 -0600 Subject: [PATCH 1020/1310] Fix WindowInfosListener_test Apply the display transform to touchable regions before checking equality. This fixes test failures on devices with non-identity display transforms. Bug: 272315174 Test: atest WindowInfosListenerTest#WindowInfoChanged Change-Id: Ib2dc7bd4b73adf142b28e7212c34a64aba6f3670 --- .../tests/WindowInfosListener_test.cpp | 58 +++++++++++++------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/services/surfaceflinger/tests/WindowInfosListener_test.cpp b/services/surfaceflinger/tests/WindowInfosListener_test.cpp index d71486fca7..042d37b79e 100644 --- a/services/surfaceflinger/tests/WindowInfosListener_test.cpp +++ b/services/surfaceflinger/tests/WindowInfosListener_test.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include namespace android { @@ -24,7 +25,8 @@ using Transaction = SurfaceComposerClient::Transaction; using gui::DisplayInfo; using gui::WindowInfo; -using WindowInfosPredicate = std::function&)>; +using WindowInfosPredicate = + std::function&, const std::vector&)>; class WindowInfosListenerTest : public ::testing::Test { protected: @@ -41,8 +43,8 @@ protected: : mPredicate(std::move(predicate)), mPromise(promise) {} void onWindowInfosChanged(const std::vector& windowInfos, - const std::vector&) override { - if (mPredicate(windowInfos)) { + const std::vector& displayInfos) override { + if (mPredicate(windowInfos, displayInfos)) { mPromise.set_value(); } } @@ -65,14 +67,24 @@ protected: } }; -std::optional findMatchingWindowInfo(WindowInfo targetWindowInfo, - std::vector windowInfos) { - for (WindowInfo windowInfo : windowInfos) { +const WindowInfo* findMatchingWindowInfo(const WindowInfo& targetWindowInfo, + const std::vector& windowInfos) { + for (const WindowInfo& windowInfo : windowInfos) { if (windowInfo.token == targetWindowInfo.token) { - return windowInfo; + return &windowInfo; } } - return std::nullopt; + return nullptr; +} + +const DisplayInfo* findMatchingDisplayInfo(int32_t displayId, + const std::vector& displayInfos) { + for (const DisplayInfo& displayInfo : displayInfos) { + if (displayInfo.displayId == displayId) { + return &displayInfo; + } + } + return nullptr; } TEST_F(WindowInfosListenerTest, WindowInfoAddedAndRemoved) { @@ -92,15 +104,17 @@ TEST_F(WindowInfosListenerTest, WindowInfoAddedAndRemoved) { .setInputWindowInfo(surfaceControl, windowInfo) .apply(); - auto windowPresent = [&](const std::vector& windowInfos) { - return findMatchingWindowInfo(windowInfo, windowInfos).has_value(); + auto windowPresent = [&](const std::vector& windowInfos, + const std::vector&) { + return findMatchingWindowInfo(windowInfo, windowInfos); }; ASSERT_TRUE(waitForWindowInfosPredicate(windowPresent)); Transaction().reparent(surfaceControl, nullptr).apply(); - auto windowNotPresent = [&](const std::vector& windowInfos) { - return !findMatchingWindowInfo(windowInfo, windowInfos).has_value(); + auto windowNotPresent = [&](const std::vector& windowInfos, + const std::vector&) { + return !findMatchingWindowInfo(windowInfo, windowInfos); }; ASSERT_TRUE(waitForWindowInfosPredicate(windowNotPresent)); } @@ -123,7 +137,8 @@ TEST_F(WindowInfosListenerTest, WindowInfoChanged) { .setInputWindowInfo(surfaceControl, windowInfo) .apply(); - auto windowIsPresentAndTouchableRegionEmpty = [&](const std::vector& windowInfos) { + auto windowIsPresentAndTouchableRegionEmpty = [&](const std::vector& windowInfos, + const std::vector&) { auto foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos); if (!foundWindowInfo) { return false; @@ -132,17 +147,26 @@ TEST_F(WindowInfosListenerTest, WindowInfoChanged) { }; ASSERT_TRUE(waitForWindowInfosPredicate(windowIsPresentAndTouchableRegionEmpty)); - Rect touchableRegions(0, 0, 50, 50); - windowInfo.addTouchableRegion(Rect(0, 0, 50, 50)); + windowInfo.addTouchableRegion({0, 0, 50, 50}); Transaction().setInputWindowInfo(surfaceControl, windowInfo).apply(); auto windowIsPresentAndTouchableRegionMatches = - [&](const std::vector& windowInfos) { + [&](const std::vector& windowInfos, + const std::vector& displayInfos) { auto foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos); if (!foundWindowInfo) { return false; } - return foundWindowInfo->touchableRegion.hasSameRects(windowInfo.touchableRegion); + + auto displayInfo = + findMatchingDisplayInfo(foundWindowInfo->displayId, displayInfos); + if (!displayInfo) { + return false; + } + + auto touchableRegion = + displayInfo->transform.transform(foundWindowInfo->touchableRegion); + return touchableRegion.hasSameRects(windowInfo.touchableRegion); }; ASSERT_TRUE(waitForWindowInfosPredicate(windowIsPresentAndTouchableRegionMatches)); } -- GitLab From 6645272868bf7d2ff3932436b79af2d92ddfb44e Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Tue, 14 Mar 2023 17:41:47 -0700 Subject: [PATCH 1021/1310] SF: trace display power state Bug: 267195714 Test: manual Change-Id: If518fabf16d91c1a58f7b68a5f4de0629df90fec --- services/surfaceflinger/DisplayDevice.cpp | 14 ++++++++++- services/surfaceflinger/DisplayDevice.h | 4 +++- services/surfaceflinger/SurfaceFlinger.cpp | 1 + services/surfaceflinger/TracedOrdinal.h | 28 +++++++++++++--------- 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 5f73fbc1a6..a89b23cc28 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -181,11 +181,23 @@ void DisplayDevice::setPowerMode(hal::PowerMode mode) { getCompositionDisplay()->applyDisplayBrightness(true); } - mPowerMode = mode; + if (mPowerMode) { + *mPowerMode = mode; + } else { + mPowerMode.emplace("PowerMode -" + to_string(getId()), mode); + } getCompositionDisplay()->setCompositionEnabled(isPoweredOn()); } +void DisplayDevice::tracePowerMode() { + // assign the same value for tracing + if (mPowerMode) { + const hal::PowerMode powerMode = *mPowerMode; + *mPowerMode = powerMode; + } +} + void DisplayDevice::enableLayerCaching(bool enable) { getCompositionDisplay()->setLayerCachingEnabled(enable); } diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index b86d9bec39..710c582b4f 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -175,6 +175,7 @@ public: std::optional getPowerMode() const; void setPowerMode(hardware::graphics::composer::hal::PowerMode mode); bool isPoweredOn() const; + void tracePowerMode(); // Enables layer caching on this DisplayDevice void enableLayerCaching(bool enable); @@ -275,7 +276,8 @@ private: static ui::Transform::RotationFlags sPrimaryDisplayRotationFlags; // Allow nullopt as initial power mode. - std::optional mPowerMode; + using TracedPowerMode = TracedOrdinal; + std::optional mPowerMode; std::optional mStagedBrightness; std::optional mBrightness; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index eecfeb661a..5a6221a2ed 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2498,6 +2498,7 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) if (!dropFrame) { refreshArgs.outputs.push_back(display->getCompositionDisplay()); } + display->tracePowerMode(); displayIds.push_back(display->getId()); } mPowerAdvisor->setDisplays(displayIds); diff --git a/services/surfaceflinger/TracedOrdinal.h b/services/surfaceflinger/TracedOrdinal.h index 558b3be273..1adc3a531d 100644 --- a/services/surfaceflinger/TracedOrdinal.h +++ b/services/surfaceflinger/TracedOrdinal.h @@ -24,16 +24,24 @@ #include #include -namespace std { +namespace android { + +namespace { template bool signbit(std::chrono::duration v) { - return signbit(std::chrono::duration_cast(v).count()); + return std::signbit(std::chrono::duration_cast(v).count()); } -} // namespace std -namespace android { +template ::value>::type* = nullptr> +bool signbit(Enum e) { + return std::signbit(static_cast::type>(e)); +} + +template ::value>::type* = nullptr> +bool signbit(T t) { + return std::signbit(t); +} -namespace { template int64_t to_int64(T v) { return int64_t(v); @@ -49,14 +57,12 @@ template class TracedOrdinal { public: static_assert(std::is_same() || (std::is_signed() && std::is_integral()) || - std::is_same(), + std::is_same() || std::is_enum(), "Type is not supported. Please test it with systrace before adding " "it to the list."); TracedOrdinal(std::string name, T initialValue) - : mName(std::move(name)), - mHasGoneNegative(std::signbit(initialValue)), - mData(initialValue) { + : mName(std::move(name)), mHasGoneNegative(signbit(initialValue)), mData(initialValue) { trace(); } @@ -66,7 +72,7 @@ public: TracedOrdinal& operator=(T other) { mData = other; - mHasGoneNegative = mHasGoneNegative || std::signbit(mData); + mHasGoneNegative = mHasGoneNegative || signbit(mData); trace(); return *this; } @@ -81,7 +87,7 @@ private: mNameNegative = mName + "Negative"; } - if (!std::signbit(mData)) { + if (!signbit(mData)) { ATRACE_INT64(mName.c_str(), to_int64(mData)); if (mHasGoneNegative) { ATRACE_INT64(mNameNegative.c_str(), 0); -- GitLab From 32ebda4d862db421c2aa8f0ec0b3907a1dfd8be7 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Thu, 16 Mar 2023 10:04:29 -0700 Subject: [PATCH 1022/1310] [layertracegenerator] avoid allocating unnecessary buffers Bug: 273958871 Test: check systrace Change-Id: Id58a41b46b4d03dc6d69259220c04e2f341be4fd --- services/surfaceflinger/Tracing/TransactionProtoParser.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp index 8fd653880c..7642122adc 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp @@ -27,9 +27,7 @@ namespace android::surfaceflinger { class FakeExternalTexture : public renderengine::ExternalTexture { - const sp mEmptyBuffer = - sp::make(1u, 1u, PIXEL_FORMAT_RGBA_8888, - GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_OFTEN); + const sp mEmptyBuffer = nullptr; uint32_t mWidth; uint32_t mHeight; uint64_t mId; -- GitLab From 8cd2abde80d7a508646c13c814b7cae91b3161f1 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Wed, 15 Mar 2023 16:35:56 +0000 Subject: [PATCH 1023/1310] Report motion ranges for touchpads in new stack Bug: 272745137 Test: use a test app to dump motion ranges for a touchpad Change-Id: If51e4124b323b9d915db130aaf3d0df4fbdba97d --- .../reader/mapper/TouchpadInputMapper.cpp | 5 ++++ .../reader/mapper/TouchpadInputMapper.h | 1 + .../mapper/gestures/GestureConverter.cpp | 30 +++++++++++++++---- .../reader/mapper/gestures/GestureConverter.h | 6 ++++ 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 330976719d..4c56b055e4 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -206,6 +206,11 @@ uint32_t TouchpadInputMapper::getSources() const { return AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD; } +void TouchpadInputMapper::populateDeviceInfo(InputDeviceInfo* info) { + InputMapper::populateDeviceInfo(info); + mGestureConverter.populateMotionRanges(*info); +} + void TouchpadInputMapper::dump(std::string& dump) { dump += INDENT2 "Touchpad Input Mapper:\n"; dump += INDENT3 "Gesture converter:\n"; diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index d693bcaf30..94da1b3c79 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -41,6 +41,7 @@ public: ~TouchpadInputMapper(); uint32_t getSources() const override; + void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; void dump(std::string& dump) override; [[nodiscard]] std::list configure(nsecs_t when, diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp index bc9f4c0c95..707b1f3956 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp @@ -16,13 +16,14 @@ #include "gestures/GestureConverter.h" +#include #include #include -#include #include #include #include +#include #include "TouchCursorInputMapperCommon.h" #include "input/Input.h" @@ -75,6 +76,28 @@ void GestureConverter::reset() { mButtonState = 0; } +void GestureConverter::populateMotionRanges(InputDeviceInfo& info) const { + info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0.0f, 1.0f, 0, 0, 0); + + // TODO(b/259547750): set this using the raw axis ranges from the touchpad when pointer capture + // is enabled. + if (std::optional rect = mPointerController->getBounds(); rect.has_value()) { + info.addMotionRange(AMOTION_EVENT_AXIS_X, SOURCE, rect->left, rect->right, 0, 0, 0); + info.addMotionRange(AMOTION_EVENT_AXIS_Y, SOURCE, rect->top, rect->bottom, 0, 0, 0); + } + + info.addMotionRange(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, SOURCE, -1.0f, 1.0f, 0, 0, 0); + info.addMotionRange(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, SOURCE, -1.0f, 1.0f, 0, 0, 0); + + // The other axes that can be reported don't have ranges that are easy to define. RELATIVE_X/Y + // and GESTURE_SCROLL_X/Y_DISTANCE are the result of acceleration functions being applied to + // finger movements, so their maximum values can't simply be derived from the size of the + // touchpad. GESTURE_PINCH_SCALE_FACTOR's maximum value depends on the minimum finger separation + // that the pad can report, which cannot be determined from its raw axis information. (Assuming + // a minimum finger separation of 1 unit would let us calculate a theoretical maximum, but it + // would be orders of magnitude too high, so probably not very useful.) +} + std::list GestureConverter::handleGesture(nsecs_t when, nsecs_t readTime, const Gesture& gesture) { switch (gesture.type) { @@ -418,10 +441,7 @@ NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime const PointerProperties* pointerProperties, const PointerCoords* pointerCoords, float xCursorPosition, float yCursorPosition) { - // TODO(b/260226362): consider what the appropriate source for these events is. - const uint32_t source = AINPUT_SOURCE_MOUSE; - - return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, source, + return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, SOURCE, mPointerController->getDisplayId(), /* policyFlags= */ POLICY_FLAG_WAKE, action, /* actionButton= */ actionButton, /* flags= */ 0, mReaderContext.getGlobalMetaState(), buttonState, diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h index 2ec5841fe0..a10dccece3 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h @@ -21,6 +21,7 @@ #include #include +#include #include #include "EventHub.h" @@ -45,6 +46,8 @@ public: void setOrientation(ui::Rotation orientation) { mOrientation = orientation; } void reset(); + void populateMotionRanges(InputDeviceInfo& info) const; + [[nodiscard]] std::list handleGesture(nsecs_t when, nsecs_t readTime, const Gesture& gesture); @@ -98,6 +101,9 @@ private: {.id = 3, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER}, }}; std::array mFakeFingerCoords = {}; + + // TODO(b/260226362): consider what the appropriate source for these events is. + static constexpr uint32_t SOURCE = AINPUT_SOURCE_MOUSE; }; } // namespace android -- GitLab From 57680eb7c8a21ed28f359f9504eb000709a74d84 Mon Sep 17 00:00:00 2001 From: Max Zhang Date: Thu, 15 Dec 2022 18:15:39 +0000 Subject: [PATCH 1024/1310] [1/4] Add user customizable MACRO_x keys in frameworks Define keycode in frameworks/native as input labels Project details can be found at go/dipper-custom-button Bug: 269742724 Test: local build Change-Id: I9af8e14892f65e14319f34421063ef330a02078e (cherry picked from commit fcf20b80e15ad23e31bb33e422495eaed2fc7448) --- include/android/keycodes.h | 8 ++++++++ libs/input/InputEventLabels.cpp | 6 +++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/include/android/keycodes.h b/include/android/keycodes.h index d4ba321057..f8fb256fae 100644 --- a/include/android/keycodes.h +++ b/include/android/keycodes.h @@ -831,6 +831,14 @@ enum { AKEYCODE_STYLUS_BUTTON_TAIL = 311, /** Key to open recent apps (a.k.a. Overview) */ AKEYCODE_RECENT_APPS = 312, + /** User customizable key #1. */ + AKEYCODE_MACRO_1 = 313, + /** User customizable key #2. */ + AKEYCODE_MACRO_2 = 314, + /** User customizable key #3. */ + AKEYCODE_MACRO_3 = 315, + /** User customizable key #4. */ + AKEYCODE_MACRO_4 = 316, // NOTE: If you add a new keycode here you must also add it to several other files. // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list. diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp index 4a19227694..f99a7d640e 100644 --- a/libs/input/InputEventLabels.cpp +++ b/libs/input/InputEventLabels.cpp @@ -343,7 +343,11 @@ namespace android { DEFINE_KEYCODE(STYLUS_BUTTON_SECONDARY), \ DEFINE_KEYCODE(STYLUS_BUTTON_TERTIARY), \ DEFINE_KEYCODE(STYLUS_BUTTON_TAIL), \ - DEFINE_KEYCODE(RECENT_APPS) + DEFINE_KEYCODE(RECENT_APPS), \ + DEFINE_KEYCODE(MACRO_1), \ + DEFINE_KEYCODE(MACRO_2), \ + DEFINE_KEYCODE(MACRO_3), \ + DEFINE_KEYCODE(MACRO_4) // 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. -- GitLab From 8a5e2f9467bfe319efd974dc78670a79d8e8d70d Mon Sep 17 00:00:00 2001 From: John Reck Date: Thu, 16 Mar 2023 14:27:48 -0400 Subject: [PATCH 1025/1310] Add available since doc Bug: 272103317 Test: n/a doc only change Change-Id: I8bebb31f0bd38e36139026a539030e5b61b75b4c --- include/android/surface_control.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/android/surface_control.h b/include/android/surface_control.h index e4926a6a8d..daeebec9ac 100644 --- a/include/android/surface_control.h +++ b/include/android/surface_control.h @@ -555,6 +555,8 @@ void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* transactio * range in use. * * Must be finite && >= 1.0f + * + * Available since API level 34. */ void ASurfaceTransaction_setExtendedRangeBrightness(ASurfaceTransaction* transaction, ASurfaceControl* surface_control, -- GitLab From d018360181d2efe669e1fc25426ac91f3389847b Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Thu, 16 Mar 2023 18:52:15 +0000 Subject: [PATCH 1026/1310] [LayerTraceGenerator] fix mirrors and rel-z data relanding: actual regression was fixed here: Id58a41b46b4d03dc6d69259220c04e2f341be4fd and was not caused by this cl Now that we are using the new front end to generate layers trace, we need to make a few fixes so we can generate data that's parsable by winscope. This means use a unique id instead of the layer id to identify layers. Test: presubmit Fixes: 255901752 Change-Id: Ife768ada07940f298a0a7b1ff81d95901559c57c --- .../surfaceflinger/FrontEnd/LayerHierarchy.h | 10 ++ .../surfaceflinger/FrontEnd/LayerSnapshot.cpp | 11 +- .../FrontEnd/LayerSnapshotBuilder.h | 14 +-- services/surfaceflinger/LayerProtoHelper.cpp | 111 +++++++++++++----- services/surfaceflinger/LayerProtoHelper.h | 41 ++++++- services/surfaceflinger/SurfaceFlinger.cpp | 14 +-- .../Tracing/tools/LayerTraceGenerator.cpp | 25 +--- .../surfaceflinger/layerproto/layers.proto | 2 + .../tracing/TransactionTraceTestSuite.cpp | 20 +++- 9 files changed, 160 insertions(+), 88 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h index 3dd89badff..b25b731356 100644 --- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h @@ -104,6 +104,16 @@ public: static const TraversalPath ROOT; }; + struct TraversalPathHash { + std::size_t operator()(const LayerHierarchy::TraversalPath& key) const { + uint32_t hashCode = key.id * 31; + if (key.mirrorRootId != UNASSIGNED_LAYER_ID) { + hashCode += key.mirrorRootId * 31; + } + return std::hash{}(hashCode); + } + }; + // Helper class to add nodes to an existing traversal id and removes the // node when it goes out of scope. class ScopedAddToTraversalPath { diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp index 8a450933f0..5e7c25946b 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp @@ -28,9 +28,14 @@ LayerSnapshot::LayerSnapshot(const RequestedLayerState& state, const LayerHierarchy::TraversalPath& path) : path(path) { static uint32_t sUniqueSequenceId = 0; - // Provide a unique id for clones otherwise keeping using the sequence id. - // The seq id can still be useful for debugging if its available. - uniqueSequence = (path.isClone()) ? sUniqueSequenceId++ : state.id; + // Provide a unique id for all snapshots. + // A front end layer can generate multiple snapshots if its mirrored. + // Additionally, if the layer is not reachable, we may choose to destroy + // and recreate the snapshot in which case the unique sequence id will + // change. The consumer shouldn't tie any lifetimes to this unique id but + // register a LayerLifecycleManager::ILifecycleListener or get a list of + // destroyed layers from LayerLifecycleManager. + uniqueSequence = sUniqueSequenceId++; sequence = static_cast(state.id); name = state.name; textureName = state.textureName; diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h index 3997a0ad83..7b1ff2710b 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h @@ -68,6 +68,7 @@ public: void update(const Args&); std::vector>& getSnapshots(); LayerSnapshot* getSnapshot(uint32_t layerId) const; + LayerSnapshot* getSnapshot(const LayerHierarchy::TraversalPath& id) const; typedef std::function ConstVisitor; @@ -86,7 +87,6 @@ public: private: friend class LayerSnapshotTest; - LayerSnapshot* getSnapshot(const LayerHierarchy::TraversalPath& id) const; static LayerSnapshot getRootSnapshot(); // return true if we were able to successfully update the snapshots via @@ -120,16 +120,8 @@ private: void updateChildState(LayerSnapshot& snapshot, const LayerSnapshot& childSnapshot, const Args& args); - struct TraversalPathHash { - std::size_t operator()(const LayerHierarchy::TraversalPath& key) const { - uint32_t hashCode = key.id * 31; - if (key.mirrorRootId != UNASSIGNED_LAYER_ID) { - hashCode += key.mirrorRootId * 31; - } - return std::hash{}(hashCode); - } - }; - std::unordered_map + std::unordered_map mIdToSnapshot; std::vector> mSnapshots; LayerSnapshot mRootSnapshot; diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp index 5c91b9181b..b5ae1a7f5a 100644 --- a/services/surfaceflinger/LayerProtoHelper.cpp +++ b/services/surfaceflinger/LayerProtoHelper.cpp @@ -15,6 +15,8 @@ */ // TODO(b/129481165): remove the #pragma below and fix conversion issues +#include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/LayerSnapshot.h" #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wconversion" #pragma clang diagnostic ignored "-Wextra" @@ -248,47 +250,88 @@ void LayerProtoHelper::readFromProto(const BlurRegion& proto, android::BlurRegio outRegion.bottom = proto.bottom(); } -void LayerProtoHelper::writeHierarchyToProto( - LayersProto& outLayersProto, const frontend::LayerHierarchy& root, - const frontend::LayerSnapshotBuilder& snapshotBuilder, - const std::unordered_map>& legacyLayers, uint32_t traceFlags) { - using Variant = frontend::LayerHierarchy::Variant; - frontend::LayerSnapshot defaultSnapshot; - - LayerProto* layerProto = outLayersProto.add_layers(); - const frontend::RequestedLayerState& layer = *root.getLayer(); - frontend::LayerSnapshot* snapshot = snapshotBuilder.getSnapshot(layer.id); +LayersProto LayerProtoFromSnapshotGenerator::generate(const frontend::LayerHierarchy& root) { + mLayersProto.clear_layers(); + std::unordered_set stackIdsToSkip; + if ((mTraceFlags & LayerTracing::TRACE_VIRTUAL_DISPLAYS) == 0) { + for (const auto& [layerStack, displayInfo] : mDisplayInfos) { + if (displayInfo.isVirtual) { + stackIdsToSkip.insert(layerStack.id); + } + } + } - if (!snapshot) { - defaultSnapshot.uniqueSequence = layer.id; - snapshot = &defaultSnapshot; + frontend::LayerHierarchy::TraversalPath path = frontend::LayerHierarchy::TraversalPath::ROOT; + for (auto& [child, variant] : root.mChildren) { + if (variant != frontend::LayerHierarchy::Variant::Attached || + stackIdsToSkip.find(child->getLayer()->layerStack.id) != stackIdsToSkip.end()) { + continue; + } + frontend::LayerHierarchy::ScopedAddToTraversalPath addChildToPath(path, + child->getLayer()->id, + variant); + LayerProtoFromSnapshotGenerator::writeHierarchyToProto(*child, path); } - writeSnapshotToProto(layerProto, layer, *snapshot, traceFlags); - for (const auto& [child, variant] : root.mChildren) { - if (variant == Variant::Attached || variant == Variant::Detached) { - layerProto->add_children(child->getLayer()->id); - } else if (variant == Variant::Relative) { - layerProto->add_relatives(child->getLayer()->id); + + // fill in relative and parent info + for (int i = 0; i < mLayersProto.layers_size(); i++) { + auto layerProto = mLayersProto.mutable_layers()->Mutable(i); + auto it = mChildToRelativeParent.find(layerProto->id()); + if (it == mChildToRelativeParent.end()) { + layerProto->set_z_order_relative_of(-1); + } else { + layerProto->set_z_order_relative_of(it->second); + } + it = mChildToParent.find(layerProto->id()); + if (it == mChildToParent.end()) { + layerProto->set_parent(-1); + } else { + layerProto->set_parent(it->second); } } - auto parent = root.getParent(); - if (parent && parent->getLayer()) { - layerProto->set_parent(parent->getLayer()->id); + mDefaultSnapshots.clear(); + mChildToRelativeParent.clear(); + return std::move(mLayersProto); +} + +frontend::LayerSnapshot* LayerProtoFromSnapshotGenerator::getSnapshot( + frontend::LayerHierarchy::TraversalPath& path, const frontend::RequestedLayerState& layer) { + frontend::LayerSnapshot* snapshot = mSnapshotBuilder.getSnapshot(path); + if (snapshot) { + return snapshot; } else { - layerProto->set_parent(-1); + mDefaultSnapshots[path] = frontend::LayerSnapshot(layer, path); + return &mDefaultSnapshots[path]; } +} - auto relativeParent = root.getRelativeParent(); - if (relativeParent && relativeParent->getLayer()) { - layerProto->set_z_order_relative_of(relativeParent->getLayer()->id); - } else { - layerProto->set_z_order_relative_of(-1); +void LayerProtoFromSnapshotGenerator::writeHierarchyToProto( + const frontend::LayerHierarchy& root, frontend::LayerHierarchy::TraversalPath& path) { + using Variant = frontend::LayerHierarchy::Variant; + LayerProto* layerProto = mLayersProto.add_layers(); + const frontend::RequestedLayerState& layer = *root.getLayer(); + frontend::LayerSnapshot* snapshot = getSnapshot(path, layer); + LayerProtoHelper::writeSnapshotToProto(layerProto, layer, *snapshot, mTraceFlags); + + for (const auto& [child, variant] : root.mChildren) { + frontend::LayerHierarchy::ScopedAddToTraversalPath addChildToPath(path, + child->getLayer()->id, + variant); + frontend::LayerSnapshot* childSnapshot = getSnapshot(path, layer); + if (variant == Variant::Attached || variant == Variant::Detached || + variant == Variant::Mirror) { + mChildToParent[childSnapshot->uniqueSequence] = snapshot->uniqueSequence; + layerProto->add_children(childSnapshot->uniqueSequence); + } else if (variant == Variant::Relative) { + mChildToRelativeParent[childSnapshot->uniqueSequence] = snapshot->uniqueSequence; + layerProto->add_relatives(childSnapshot->uniqueSequence); + } } - if (traceFlags & LayerTracing::TRACE_COMPOSITION) { - auto it = legacyLayers.find(layer.id); - if (it != legacyLayers.end()) { + if (mTraceFlags & LayerTracing::TRACE_COMPOSITION) { + auto it = mLegacyLayers.find(layer.id); + if (it != mLegacyLayers.end()) { it->second->writeCompositionStateToProto(layerProto); } } @@ -298,7 +341,10 @@ void LayerProtoHelper::writeHierarchyToProto( if (variant == Variant::Detached) { continue; } - writeHierarchyToProto(outLayersProto, *child, snapshotBuilder, legacyLayers, traceFlags); + frontend::LayerHierarchy::ScopedAddToTraversalPath addChildToPath(path, + child->getLayer()->id, + variant); + writeHierarchyToProto(*child, path); } } @@ -345,6 +391,7 @@ void LayerProtoHelper::writeSnapshotToProto(LayerProto* layerInfo, layerInfo->set_shadow_radius(snapshot.shadowRadius); layerInfo->set_id(snapshot.uniqueSequence); + layerInfo->set_original_id(snapshot.sequence); layerInfo->set_name(requestedState.name); layerInfo->set_type("Layer"); diff --git a/services/surfaceflinger/LayerProtoHelper.h b/services/surfaceflinger/LayerProtoHelper.h index 38d73f613f..b84a49b27c 100644 --- a/services/surfaceflinger/LayerProtoHelper.h +++ b/services/surfaceflinger/LayerProtoHelper.h @@ -25,6 +25,9 @@ #include #include #include +#include +#include "FrontEnd/LayerHierarchy.h" +#include "FrontEnd/LayerSnapshot.h" namespace android { namespace surfaceflinger { @@ -58,11 +61,6 @@ public: static void readFromProto(const ColorTransformProto& colorTransformProto, mat4& matrix); static void writeToProto(const android::BlurRegion region, BlurRegion*); static void readFromProto(const BlurRegion& proto, android::BlurRegion& outRegion); - static void writeHierarchyToProto(LayersProto& layersProto, - const frontend::LayerHierarchy& root, - const frontend::LayerSnapshotBuilder& snapshotBuilder, - const std::unordered_map>& mLegacyLayers, - uint32_t traceFlags); static void writeSnapshotToProto(LayerProto* outProto, const frontend::RequestedLayerState& requestedState, const frontend::LayerSnapshot& snapshot, uint32_t traceFlags); @@ -70,5 +68,38 @@ public: const display::DisplayMap& displayInfos); }; +class LayerProtoFromSnapshotGenerator { +public: + LayerProtoFromSnapshotGenerator( + const frontend::LayerSnapshotBuilder& snapshotBuilder, + const display::DisplayMap& displayInfos, + const std::unordered_map>& legacyLayers, uint32_t traceFlags) + : mSnapshotBuilder(snapshotBuilder), + mLegacyLayers(legacyLayers), + mDisplayInfos(displayInfos), + mTraceFlags(traceFlags) {} + LayersProto generate(const frontend::LayerHierarchy& root); + +private: + void writeHierarchyToProto(const frontend::LayerHierarchy& root, + frontend::LayerHierarchy::TraversalPath& path); + frontend::LayerSnapshot* getSnapshot(frontend::LayerHierarchy::TraversalPath& path, + const frontend::RequestedLayerState& layer); + + const frontend::LayerSnapshotBuilder& mSnapshotBuilder; + const std::unordered_map>& mLegacyLayers; + const display::DisplayMap& mDisplayInfos; + uint32_t mTraceFlags; + LayersProto mLayersProto; + // winscope expects all the layers, so provide a snapshot even if it not currently drawing + std::unordered_map + mDefaultSnapshots; + std::unordered_map + mChildToRelativeParent; + std::unordered_map + mChildToParent; +}; + } // namespace surfaceflinger } // namespace android diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index e47a147ed8..01a7df398b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5786,17 +5786,9 @@ LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const { return layersProto; } - const frontend::LayerHierarchy& root = mLayerHierarchyBuilder.getHierarchy(); - LayersProto layersProto; - for (auto& [child, variant] : root.mChildren) { - if (variant != frontend::LayerHierarchy::Variant::Attached || - stackIdsToSkip.find(child->getLayer()->layerStack.id) != stackIdsToSkip.end()) { - continue; - } - LayerProtoHelper::writeHierarchyToProto(layersProto, *child, mLayerSnapshotBuilder, - mLegacyLayers, traceFlags); - } - return layersProto; + return LayerProtoFromSnapshotGenerator(mLayerSnapshotBuilder, mFrontEndDisplayInfos, {}, + traceFlags) + .generate(mLayerHierarchyBuilder.getHierarchy()); } google::protobuf::RepeatedPtrField SurfaceFlinger::dumpDisplayProto() const { diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index 0a7101ccce..2418cf22e5 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -134,29 +134,10 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, lifecycleManager.getGlobalChanges().string().c_str()); lifecycleManager.commitChanges(); - // write layers trace - auto tracingFlags = LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS; - std::unordered_set stackIdsToSkip; - if ((tracingFlags & LayerTracing::TRACE_VIRTUAL_DISPLAYS) == 0) { - for (const auto& displayInfo : displayInfos) { - if (displayInfo.second.isVirtual) { - stackIdsToSkip.insert(displayInfo.first.id); - } - } - } - - const frontend::LayerHierarchy& root = hierarchyBuilder.getHierarchy(); - - LayersProto layersProto; - for (auto& [child, variant] : root.mChildren) { - if (variant != frontend::LayerHierarchy::Variant::Attached || - stackIdsToSkip.find(child->getLayer()->layerStack.id) != stackIdsToSkip.end()) { - continue; - } - LayerProtoHelper::writeHierarchyToProto(layersProto, *child, snapshotBuilder, {}, - tracingFlags); - } + LayersProto layersProto = LayerProtoFromSnapshotGenerator(snapshotBuilder, displayInfos, {}, + layerTracing.getFlags()) + .generate(hierarchyBuilder.getHierarchy()); auto displayProtos = LayerProtoHelper::writeDisplayInfoToProto(displayInfos); layerTracing.notify(visibleRegionsDirty, entry.elapsed_realtime_nanos(), entry.vsync_id(), &layersProto, {}, &displayProtos); diff --git a/services/surfaceflinger/layerproto/layers.proto b/services/surfaceflinger/layerproto/layers.proto index 3598308338..e9add2e1a4 100644 --- a/services/surfaceflinger/layerproto/layers.proto +++ b/services/surfaceflinger/layerproto/layers.proto @@ -138,6 +138,8 @@ message LayerProto { float requested_corner_radius = 56; RectProto destination_frame = 57; + + uint32 original_id = 58; } message PositionProto { diff --git a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp index 5f9214c548..7355c35131 100644 --- a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp +++ b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -84,9 +85,9 @@ protected: std::vector TransactionTraceTestSuite::sTransactionTraces{}; struct LayerInfo { - int id; + int32_t id; std::string name; - int parent; + int32_t parent; int z; uint64_t curr_frame; float x; @@ -143,16 +144,27 @@ TEST_P(TransactionTraceTestSuite, validateEndState) { expectedLayers.reserve(static_cast(expectedLastEntry.layers().layers_size())); for (int i = 0; i < expectedLastEntry.layers().layers_size(); i++) { auto layer = expectedLastEntry.layers().layers(i); - expectedLayers.push_back(getLayerInfoFromProto(layer)); + LayerInfo layerInfo = getLayerInfoFromProto(layer); + expectedLayers.push_back(layerInfo); } std::sort(expectedLayers.begin(), expectedLayers.end(), compareById); + std::unordered_map snapshotIdToLayerId; std::vector actualLayers; actualLayers.reserve(static_cast(actualLastEntry.layers().layers_size())); for (int i = 0; i < actualLastEntry.layers().layers_size(); i++) { auto layer = actualLastEntry.layers().layers(i); - actualLayers.push_back(getLayerInfoFromProto(layer)); + LayerInfo layerInfo = getLayerInfoFromProto(layer); + snapshotIdToLayerId[layerInfo.id] = static_cast(layer.original_id()); + actualLayers.push_back(layerInfo); + } + + for (auto& layer : actualLayers) { + layer.id = snapshotIdToLayerId[layer.id]; + auto it = snapshotIdToLayerId.find(layer.parent); + layer.parent = it == snapshotIdToLayerId.end() ? -1 : it->second; } + std::sort(actualLayers.begin(), actualLayers.end(), compareById); size_t i = 0; -- GitLab From c66057060488858a921754909bfd0779a97b9e4a Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Wed, 15 Mar 2023 18:40:55 -0700 Subject: [PATCH 1027/1310] JPEG/R: Add display_boost parameter to decode API Bug: 264715926 Test: jpegr_test Change-Id: Ifef71a8aee532d603fd20417e32314243c8b4673 --- .../include/jpegrecoverymap/jpegr.h | 6 ++- .../include/jpegrecoverymap/recoverymapmath.h | 11 +++++ libs/jpegrecoverymap/jpegr.cpp | 17 ++++--- libs/jpegrecoverymap/recoverymapmath.cpp | 7 +++ libs/jpegrecoverymap/tests/jpegr_test.cpp | 5 +- .../tests/recoverymapmath_test.cpp | 47 +++++++++++++++++++ 6 files changed, 84 insertions(+), 9 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h index e2023a6f07..1ab1dd7245 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h @@ -206,13 +206,14 @@ public: * * @param compressed_jpegr_image compressed JPEGR image. * @param dest destination of the uncompressed JPEGR image. + * @param max_display_boost (optional) the maximum available boost supported by a display * @param exif destination of the decoded EXIF metadata. The default value is NULL where the decoder will do nothing about it. If configured not NULL the decoder will write EXIF data into this structure. The format is defined in {@code jpegr_exif_struct} * @param output_format flag for setting output color format. Its value configures the output color format. The default value is {@code JPEGR_OUTPUT_HDR_LINEAR}. ---------------------------------------------------------------------- - | output_format | decoded color format to be written | + | output_format | decoded color format to be written | ---------------------------------------------------------------------- | JPEGR_OUTPUT_SDR | RGBA_8888 | ---------------------------------------------------------------------- @@ -234,6 +235,7 @@ public: */ status_t decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, jr_uncompressed_ptr dest, + float max_display_boost = -1.0f, jr_exif_ptr exif = nullptr, jpegr_output_format output_format = JPEGR_OUTPUT_HDR_LINEAR, jr_uncompressed_ptr recovery_map = nullptr, @@ -281,6 +283,7 @@ protected: * @param output_format flag for setting output color format. if set to * {@code JPEGR_OUTPUT_SDR}, decoder will only decode the primary image * which is SDR. Default value is JPEGR_OUTPUT_HDR_LINEAR. + * @param max_display_boost the maximum available boost supported by a display * @param dest reconstructed HDR image * @return NO_ERROR if calculation succeeds, error code if error occurs. */ @@ -288,6 +291,7 @@ protected: jr_uncompressed_ptr uncompressed_recovery_map, jr_metadata_ptr metadata, jpegr_output_format output_format, + float max_display_boost, jr_uncompressed_ptr dest); private: diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h index 8b5318f452..67d2a6a0be 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h @@ -135,6 +135,16 @@ struct RecoveryLUT { } } + RecoveryLUT(jr_metadata_ptr metadata, float displayBoost) { + float boostFactor = displayBoost > 0 ? displayBoost / metadata->maxContentBoost : 1.0f; + for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kRecoveryFactorNumEntries - 1); + float logBoost = log2(metadata->minContentBoost) * (1.0f - value) + + log2(metadata->maxContentBoost) * value; + mRecoveryTable[idx] = exp2(logBoost * boostFactor); + } + } + ~RecoveryLUT() { } @@ -357,6 +367,7 @@ uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata); * value, with the given hdr ratio, to the given sdr input in the range [0, 1]. */ Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata); +Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata, float displayBoost); Color applyRecoveryLUT(Color e, float recovery, RecoveryLUT& recoveryLUT); /* diff --git a/libs/jpegrecoverymap/jpegr.cpp b/libs/jpegrecoverymap/jpegr.cpp index e197bf0848..e395d51a26 100644 --- a/libs/jpegrecoverymap/jpegr.cpp +++ b/libs/jpegrecoverymap/jpegr.cpp @@ -330,6 +330,7 @@ status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_p /* Decode API */ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, jr_uncompressed_ptr dest, + float max_display_boost, jr_exif_ptr exif, jpegr_output_format output_format, jr_uncompressed_ptr recovery_map, @@ -431,7 +432,7 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight(); JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &jr_metadata, output_format, - dest)); + max_display_boost, dest)); return NO_ERROR; } @@ -667,6 +668,7 @@ status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, jr_uncompressed_ptr uncompressed_recovery_map, jr_metadata_ptr metadata, jpegr_output_format output_format, + float max_display_boost, jr_uncompressed_ptr dest) { if (uncompressed_yuv_420_image == nullptr || uncompressed_recovery_map == nullptr @@ -678,13 +680,15 @@ status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, dest->width = uncompressed_yuv_420_image->width; dest->height = uncompressed_yuv_420_image->height; ShepardsIDW idwTable(kMapDimensionScaleFactor); - RecoveryLUT recoveryLUT(metadata); + float display_boost = max_display_boost > 0 ? + std::min(max_display_boost, metadata->maxContentBoost) + : metadata->maxContentBoost; + RecoveryLUT recoveryLUT(metadata, display_boost); JobQueue jobQueue; std::function applyRecMap = [uncompressed_yuv_420_image, uncompressed_recovery_map, metadata, dest, &jobQueue, &idwTable, output_format, - &recoveryLUT]() -> void { - const float hdr_ratio = metadata->maxContentBoost; + &recoveryLUT, display_boost]() -> void { size_t width = uncompressed_yuv_420_image->width; size_t height = uncompressed_yuv_420_image->height; @@ -710,12 +714,13 @@ status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, } else { recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y, idwTable); } + #if USE_APPLY_RECOVERY_LUT Color rgb_hdr = applyRecoveryLUT(rgb_sdr, recovery, recoveryLUT); #else - Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata); + Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata, display_boost); #endif - rgb_hdr = rgb_hdr / metadata->maxContentBoost; + rgb_hdr = rgb_hdr / display_boost; size_t pixel_idx = x + y * width; switch (output_format) { diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp index 20c32ed888..2cffde3c54 100644 --- a/libs/jpegrecoverymap/recoverymapmath.cpp +++ b/libs/jpegrecoverymap/recoverymapmath.cpp @@ -463,6 +463,13 @@ Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata) { return e * recoveryFactor; } +Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata, float displayBoost) { + float logBoost = log2(metadata->minContentBoost) * (1.0f - recovery) + + log2(metadata->maxContentBoost) * recovery; + float recoveryFactor = exp2(logBoost * displayBoost / metadata->maxContentBoost); + return e * recoveryFactor; +} + Color applyRecoveryLUT(Color e, float recovery, RecoveryLUT& recoveryLUT) { float recoveryFactor = recoveryLUT.getRecoveryFactor(recovery); return e * recoveryFactor; diff --git a/libs/jpegrecoverymap/tests/jpegr_test.cpp b/libs/jpegrecoverymap/tests/jpegr_test.cpp index be4b972251..df90f53c41 100644 --- a/libs/jpegrecoverymap/tests/jpegr_test.cpp +++ b/libs/jpegrecoverymap/tests/jpegr_test.cpp @@ -152,7 +152,8 @@ void JpegRBenchmark::BenchmarkApplyRecoveryMap(jr_uncompressed_ptr yuv420Image, timerStart(&applyRecMapTime); for (auto i = 0; i < kProfileCount; i++) { - ASSERT_EQ(OK, applyRecoveryMap(yuv420Image, map, metadata, JPEGR_OUTPUT_HDR_HLG, dest)); + ASSERT_EQ(OK, applyRecoveryMap(yuv420Image, map, metadata, JPEGR_OUTPUT_HDR_HLG, + metadata->maxContentBoost /* displayBoost */, dest)); } timerStop(&applyRecMapTime); @@ -170,7 +171,7 @@ TEST_F(JpegRTest, build) { jpegRCodec.encodeJPEGR(nullptr, nullptr, nullptr, static_cast(0), nullptr); jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast(0), nullptr); - jpegRCodec.decodeJPEGR(nullptr, nullptr, nullptr); + jpegRCodec.decodeJPEGR(nullptr, nullptr); } TEST_F(JpegRTest, writeXmpThenRead) { diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp index cf6a034053..5ef79e9e34 100644 --- a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp +++ b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp @@ -557,6 +557,7 @@ TEST_F(RecoveryMapMathTest, applyRecoveryLUT) { jpegr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), .minContentBoost = 1.0f / static_cast(boost) }; RecoveryLUT recoveryLUT(&metadata); + RecoveryLUT recoveryLUTWithBoost(&metadata, metadata.maxContentBoost); for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) { float value = static_cast(idx) / static_cast(kRecoveryFactorNumEntries - 1); EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata), @@ -569,6 +570,16 @@ TEST_F(RecoveryMapMathTest, applyRecoveryLUT) { applyRecoveryLUT(RgbGreen(), value, recoveryLUT)); EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata), applyRecoveryLUT(RgbBlue(), value, recoveryLUT)); + EXPECT_RGB_EQ(applyRecoveryLUT(RgbBlack(), value, recoveryLUT), + applyRecoveryLUT(RgbBlack(), value, recoveryLUTWithBoost)); + EXPECT_RGB_EQ(applyRecoveryLUT(RgbWhite(), value, recoveryLUT), + applyRecoveryLUT(RgbWhite(), value, recoveryLUTWithBoost)); + EXPECT_RGB_EQ(applyRecoveryLUT(RgbRed(), value, recoveryLUT), + applyRecoveryLUT(RgbRed(), value, recoveryLUTWithBoost)); + EXPECT_RGB_EQ(applyRecoveryLUT(RgbGreen(), value, recoveryLUT), + applyRecoveryLUT(RgbGreen(), value, recoveryLUTWithBoost)); + EXPECT_RGB_EQ(applyRecoveryLUT(RgbBlue(), value, recoveryLUT), + applyRecoveryLUT(RgbBlue(), value, recoveryLUTWithBoost)); } } @@ -576,6 +587,7 @@ TEST_F(RecoveryMapMathTest, applyRecoveryLUT) { jpegr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), .minContentBoost = 1.0f }; RecoveryLUT recoveryLUT(&metadata); + RecoveryLUT recoveryLUTWithBoost(&metadata, metadata.maxContentBoost); for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) { float value = static_cast(idx) / static_cast(kRecoveryFactorNumEntries - 1); EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata), @@ -588,6 +600,16 @@ TEST_F(RecoveryMapMathTest, applyRecoveryLUT) { applyRecoveryLUT(RgbGreen(), value, recoveryLUT)); EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata), applyRecoveryLUT(RgbBlue(), value, recoveryLUT)); + EXPECT_RGB_EQ(applyRecoveryLUT(RgbBlack(), value, recoveryLUT), + applyRecoveryLUT(RgbBlack(), value, recoveryLUTWithBoost)); + EXPECT_RGB_EQ(applyRecoveryLUT(RgbWhite(), value, recoveryLUT), + applyRecoveryLUT(RgbWhite(), value, recoveryLUTWithBoost)); + EXPECT_RGB_EQ(applyRecoveryLUT(RgbRed(), value, recoveryLUT), + applyRecoveryLUT(RgbRed(), value, recoveryLUTWithBoost)); + EXPECT_RGB_EQ(applyRecoveryLUT(RgbGreen(), value, recoveryLUT), + applyRecoveryLUT(RgbGreen(), value, recoveryLUTWithBoost)); + EXPECT_RGB_EQ(applyRecoveryLUT(RgbBlue(), value, recoveryLUT), + applyRecoveryLUT(RgbBlue(), value, recoveryLUTWithBoost)); } } @@ -596,6 +618,7 @@ TEST_F(RecoveryMapMathTest, applyRecoveryLUT) { .minContentBoost = 1.0f / pow(static_cast(boost), 1.0f / 3.0f) }; RecoveryLUT recoveryLUT(&metadata); + RecoveryLUT recoveryLUTWithBoost(&metadata, metadata.maxContentBoost); for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) { float value = static_cast(idx) / static_cast(kRecoveryFactorNumEntries - 1); EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata), @@ -608,6 +631,16 @@ TEST_F(RecoveryMapMathTest, applyRecoveryLUT) { applyRecoveryLUT(RgbGreen(), value, recoveryLUT)); EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata), applyRecoveryLUT(RgbBlue(), value, recoveryLUT)); + EXPECT_RGB_EQ(applyRecoveryLUT(RgbBlack(), value, recoveryLUT), + applyRecoveryLUT(RgbBlack(), value, recoveryLUTWithBoost)); + EXPECT_RGB_EQ(applyRecoveryLUT(RgbWhite(), value, recoveryLUT), + applyRecoveryLUT(RgbWhite(), value, recoveryLUTWithBoost)); + EXPECT_RGB_EQ(applyRecoveryLUT(RgbRed(), value, recoveryLUT), + applyRecoveryLUT(RgbRed(), value, recoveryLUTWithBoost)); + EXPECT_RGB_EQ(applyRecoveryLUT(RgbGreen(), value, recoveryLUT), + applyRecoveryLUT(RgbGreen(), value, recoveryLUTWithBoost)); + EXPECT_RGB_EQ(applyRecoveryLUT(RgbBlue(), value, recoveryLUT), + applyRecoveryLUT(RgbBlue(), value, recoveryLUTWithBoost)); } } } @@ -719,6 +752,7 @@ TEST_F(RecoveryMapMathTest, EncodeRecovery) { TEST_F(RecoveryMapMathTest, ApplyRecovery) { jpegr_metadata_struct metadata = { .maxContentBoost = 4.0f, .minContentBoost = 1.0f / 4.0f }; + float displayBoost = metadata.maxContentBoost; EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.0f, &metadata), RgbBlack()); EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.5f, &metadata), RgbBlack()); @@ -774,6 +808,19 @@ TEST_F(RecoveryMapMathTest, ApplyRecovery) { EXPECT_RGB_NEAR(applyRecovery(e, 0.5f, &metadata), e); EXPECT_RGB_NEAR(applyRecovery(e, 0.75f, &metadata), e * 2.0f); EXPECT_RGB_NEAR(applyRecovery(e, 1.0f, &metadata), e * 4.0f); + + EXPECT_RGB_EQ(applyRecovery(RgbBlack(), 1.0f, &metadata), + applyRecovery(RgbBlack(), 1.0f, &metadata, displayBoost)); + EXPECT_RGB_EQ(applyRecovery(RgbWhite(), 1.0f, &metadata), + applyRecovery(RgbWhite(), 1.0f, &metadata, displayBoost)); + EXPECT_RGB_EQ(applyRecovery(RgbRed(), 1.0f, &metadata), + applyRecovery(RgbRed(), 1.0f, &metadata, displayBoost)); + EXPECT_RGB_EQ(applyRecovery(RgbGreen(), 1.0f, &metadata), + applyRecovery(RgbGreen(), 1.0f, &metadata, displayBoost)); + EXPECT_RGB_EQ(applyRecovery(RgbBlue(), 1.0f, &metadata), + applyRecovery(RgbBlue(), 1.0f, &metadata, displayBoost)); + EXPECT_RGB_EQ(applyRecovery(e, 1.0f, &metadata), + applyRecovery(e, 1.0f, &metadata, displayBoost)); } TEST_F(RecoveryMapMathTest, GetYuv420Pixel) { -- GitLab From a364bd7c0ecc412582eff1160368f3eaa2b4c6f0 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Wed, 15 Mar 2023 15:27:33 -0500 Subject: [PATCH 1028/1310] SF: throttle WindowInfosListener calls This change updates WindowInfosListenerInvoker to delay and drop messages when there are unacked messages. When WindowInfosListener calls are acknowledged before the next windowInfosChanged call, there is no behavior change. If windowInfosChanged is called and there are unacked messages, then the update is delayed and sent once the messages are acked via WindowInfosReportedListener. If windowInfosChanged is called and there is already a delayed update, then the previous delayed update is overwritten and only the latest update is sent. WindowInfosListeners are still called immediately when there are focus requests. This means the number of unacked messages may be greater than one. Bug: 270894765 Test: presubmits Test: manual fuzz testing (random sleeps added to input flinger listener) Change-Id: I78b39b10c52f4e4939f1741516d0edbe9898ef3c --- services/surfaceflinger/SurfaceFlinger.cpp | 7 +- .../WindowInfosListenerInvoker.cpp | 108 +++++++++++------- .../WindowInfosListenerInvoker.h | 24 ++-- 3 files changed, 89 insertions(+), 50 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 639ea27adc..234c6fe9b2 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3725,8 +3725,11 @@ void SurfaceFlinger::updateInputFlinger() { ATRACE_NAME("BackgroundExecutor::updateInputFlinger"); if (updateWindowInfo) { mWindowInfosListenerInvoker - ->windowInfosChanged(windowInfos, displayInfos, - inputWindowCommands.windowInfosReportedListeners); + ->windowInfosChanged(std::move(windowInfos), std::move(displayInfos), + std::move( + inputWindowCommands.windowInfosReportedListeners), + /* forceImmediateCall= */ + !inputWindowCommands.focusRequests.empty()); } else { // If there are listeners but no changes to input windows, call the listeners // immediately. diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp index 292083b9bc..73a7cae6d6 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp +++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp @@ -25,20 +25,14 @@ using gui::DisplayInfo; using gui::IWindowInfosListener; using gui::WindowInfo; -struct WindowInfosListenerInvoker::WindowInfosReportedListener : gui::BnWindowInfosReportedListener, - DeathRecipient { - explicit WindowInfosReportedListener( - size_t callbackCount, - const std::unordered_set, - SpHash>& - windowInfosReportedListeners) +struct WindowInfosReportedListenerInvoker : gui::BnWindowInfosReportedListener, + IBinder::DeathRecipient { + WindowInfosReportedListenerInvoker(size_t callbackCount, + WindowInfosReportedListenerSet windowInfosReportedListeners) : mCallbacksPending(callbackCount), - mWindowInfosReportedListeners(windowInfosReportedListeners) {} + mWindowInfosReportedListeners(std::move(windowInfosReportedListeners)) {} binder::Status onWindowInfosReported() override { - // TODO(b/222421815) There could potentially be callbacks that we don't need to wait for - // before calling the WindowInfosReportedListeners coming from InputWindowCommands. Filter - // the list of callbacks down to those from system server. if (--mCallbacksPending == 0) { for (const auto& listener : mWindowInfosReportedListeners) { sp asBinder = IInterface::asBinder(listener); @@ -54,9 +48,7 @@ struct WindowInfosListenerInvoker::WindowInfosReportedListener : gui::BnWindowIn private: std::atomic mCallbacksPending; - std::unordered_set, - SpHash> - mWindowInfosReportedListeners; + WindowInfosReportedListenerSet mWindowInfosReportedListeners; }; void WindowInfosListenerInvoker::addWindowInfosListener(sp listener) { @@ -82,38 +74,76 @@ void WindowInfosListenerInvoker::binderDied(const wp& who) { } void WindowInfosListenerInvoker::windowInfosChanged( - const std::vector& windowInfos, const std::vector& displayInfos, - const std::unordered_set, - SpHash>& - windowInfosReportedListeners) { - ftl::SmallVector, kStaticCapacity> windowInfosListeners; + std::vector windowInfos, std::vector displayInfos, + WindowInfosReportedListenerSet reportedListeners, bool forceImmediateCall) { + reportedListeners.insert(sp::fromExisting(this)); + auto callListeners = [this, windowInfos = std::move(windowInfos), + displayInfos = std::move(displayInfos), + reportedListeners = std::move(reportedListeners)]() mutable { + ftl::SmallVector, kStaticCapacity> windowInfosListeners; + { + std::scoped_lock lock(mListenersMutex); + for (const auto& [_, listener] : mWindowInfosListeners) { + windowInfosListeners.push_back(listener); + } + } + + auto reportedInvoker = + sp::make(windowInfosListeners.size(), + std::move(reportedListeners)); + + for (const auto& listener : windowInfosListeners) { + sp asBinder = IInterface::asBinder(listener); + + // linkToDeath is used here to ensure that the windowInfosReportedListeners + // are called even if one of the windowInfosListeners dies before + // calling onWindowInfosReported. + asBinder->linkToDeath(reportedInvoker); + + auto status = + listener->onWindowInfosChanged(windowInfos, displayInfos, reportedInvoker); + if (!status.isOk()) { + reportedInvoker->onWindowInfosReported(); + } + } + }; + { - std::scoped_lock lock(mListenersMutex); - for (const auto& [_, listener] : mWindowInfosListeners) { - windowInfosListeners.push_back(listener); + std::scoped_lock lock(mMessagesMutex); + // If there are unacked messages and this isn't a forced call, then return immediately. + // If a forced window infos change doesn't happen first, the update will be sent after + // the WindowInfosReportedListeners are called. If a forced window infos change happens or + // if there are subsequent delayed messages before this update is sent, then this message + // will be dropped and the listeners will only be called with the latest info. This is done + // to reduce the amount of binder memory used. + if (mActiveMessageCount > 0 && !forceImmediateCall) { + mWindowInfosChangedDelayed = std::move(callListeners); + return; } + + mWindowInfosChangedDelayed = nullptr; + mActiveMessageCount++; } + callListeners(); +} - auto windowInfosReportedListener = windowInfosReportedListeners.empty() - ? nullptr - : sp::make(windowInfosListeners.size(), - windowInfosReportedListeners); - for (const auto& listener : windowInfosListeners) { - sp asBinder = IInterface::asBinder(listener); - - // linkToDeath is used here to ensure that the windowInfosReportedListeners - // are called even if one of the windowInfosListeners dies before - // calling onWindowInfosReported. - if (windowInfosReportedListener) { - asBinder->linkToDeath(windowInfosReportedListener); - } +binder::Status WindowInfosListenerInvoker::onWindowInfosReported() { + std::function callListeners; - auto status = listener->onWindowInfosChanged(windowInfos, displayInfos, - windowInfosReportedListener); - if (windowInfosReportedListener && !status.isOk()) { - windowInfosReportedListener->onWindowInfosReported(); + { + std::scoped_lock lock{mMessagesMutex}; + mActiveMessageCount--; + if (!mWindowInfosChangedDelayed || mActiveMessageCount > 0) { + return binder::Status::ok(); } + + mActiveMessageCount++; + callListeners = std::move(mWindowInfosChangedDelayed); + mWindowInfosChangedDelayed = nullptr; } + + callListeners(); + return binder::Status::ok(); } } // namespace android diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h index d60a9c4157..bfe036e025 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.h +++ b/services/surfaceflinger/WindowInfosListenerInvoker.h @@ -23,34 +23,40 @@ #include #include #include +#include #include namespace android { -class SurfaceFlinger; +using WindowInfosReportedListenerSet = + std::unordered_set, + gui::SpHash>; -class WindowInfosListenerInvoker : public IBinder::DeathRecipient { +class WindowInfosListenerInvoker : public gui::BnWindowInfosReportedListener, + public IBinder::DeathRecipient { public: void addWindowInfosListener(sp); void removeWindowInfosListener(const sp& windowInfosListener); - void windowInfosChanged(const std::vector&, - const std::vector&, - const std::unordered_set, - SpHash>& - windowInfosReportedListeners); + void windowInfosChanged(std::vector, std::vector, + WindowInfosReportedListenerSet windowInfosReportedListeners, + bool forceImmediateCall); + + binder::Status onWindowInfosReported() override; protected: void binderDied(const wp& who) override; private: - struct WindowInfosReportedListener; - std::mutex mListenersMutex; static constexpr size_t kStaticCapacity = 3; ftl::SmallMap, const sp, kStaticCapacity> mWindowInfosListeners GUARDED_BY(mListenersMutex); + + std::mutex mMessagesMutex; + uint32_t mActiveMessageCount GUARDED_BY(mMessagesMutex) = 0; + std::function mWindowInfosChangedDelayed GUARDED_BY(mMessagesMutex); }; } // namespace android -- GitLab From d02ea10ecdc76dd41c9738659682a3fc621ba940 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Fri, 17 Mar 2023 18:21:30 +0000 Subject: [PATCH 1029/1310] InputMapper: use a reference for InputDeviceInfo parameter This makes the code a bit safer, especially since we never check for this parameter being null. Bug: 245989146 Test: atest inputflinger_tests Test: dump motion ranges from a test app, check they're still there Change-Id: I2754e57c8b261f86ad69891098c9762b96e9ec55 --- services/inputflinger/reader/InputDevice.cpp | 2 +- .../reader/mapper/CursorInputMapper.cpp | 28 ++++++------- .../reader/mapper/CursorInputMapper.h | 2 +- .../mapper/ExternalStylusInputMapper.cpp | 6 +-- .../reader/mapper/ExternalStylusInputMapper.h | 2 +- .../reader/mapper/InputMapper.cpp | 4 +- .../inputflinger/reader/mapper/InputMapper.h | 2 +- .../reader/mapper/JoystickInputMapper.cpp | 12 +++--- .../reader/mapper/JoystickInputMapper.h | 4 +- .../reader/mapper/KeyboardInputMapper.cpp | 10 ++--- .../reader/mapper/KeyboardInputMapper.h | 2 +- .../mapper/RotaryEncoderInputMapper.cpp | 6 +-- .../reader/mapper/RotaryEncoderInputMapper.h | 2 +- .../reader/mapper/SensorInputMapper.cpp | 6 +-- .../reader/mapper/SensorInputMapper.h | 2 +- .../reader/mapper/TouchInputMapper.cpp | 40 +++++++++---------- .../reader/mapper/TouchInputMapper.h | 2 +- .../reader/mapper/TouchpadInputMapper.cpp | 4 +- .../reader/mapper/TouchpadInputMapper.h | 2 +- .../reader/mapper/VibratorInputMapper.cpp | 4 +- .../reader/mapper/VibratorInputMapper.h | 2 +- .../inputflinger/tests/InputReader_test.cpp | 16 ++++---- .../tests/fuzzers/CursorInputFuzzer.cpp | 2 +- .../tests/fuzzers/KeyboardInputFuzzer.cpp | 2 +- .../tests/fuzzers/MultiTouchInputFuzzer.cpp | 2 +- 25 files changed, 83 insertions(+), 83 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 69363b6e10..ddf6c87c02 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -465,7 +465,7 @@ InputDeviceInfo InputDevice::getDeviceInfo() { mHasMic, getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE)); for_each_mapper( - [&outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(&outDeviceInfo); }); + [&outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(outDeviceInfo); }); if (mController) { mController->populateDeviceInfo(&outDeviceInfo); diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index b12a009d7f..2b1a298510 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -81,31 +81,31 @@ uint32_t CursorInputMapper::getSources() const { return mSource; } -void CursorInputMapper::populateDeviceInfo(InputDeviceInfo* info) { +void CursorInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); if (mParameters.mode == Parameters::Mode::POINTER) { if (const auto bounds = mPointerController->getBounds(); bounds) { - info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, bounds->left, bounds->right, 0.0f, - 0.0f, 0.0f); - info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, bounds->top, bounds->bottom, 0.0f, - 0.0f, 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_X, mSource, bounds->left, bounds->right, 0.0f, + 0.0f, 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, bounds->top, bounds->bottom, 0.0f, + 0.0f, 0.0f); } } else { - info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, 0.0f); - info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale, 0.0f); - info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, - 0.0f); - info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale, - 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale, 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, + 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale, + 0.0f); } - info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mSource, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mSource, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f); if (mCursorScrollAccumulator.haveRelativeVWheel()) { - info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); } if (mCursorScrollAccumulator.haveRelativeHWheel()) { - info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); } } diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index 939cceb6b0..5f02203c1f 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -57,7 +57,7 @@ public: virtual ~CursorInputMapper(); virtual uint32_t getSources() const override; - virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; + virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; virtual void dump(std::string& dump) override; [[nodiscard]] std::list configure(nsecs_t when, const InputReaderConfiguration* config, diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp index 2809939c86..a44d15bcdf 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp @@ -30,11 +30,11 @@ uint32_t ExternalStylusInputMapper::getSources() const { return AINPUT_SOURCE_STYLUS; } -void ExternalStylusInputMapper::populateDeviceInfo(InputDeviceInfo* info) { +void ExternalStylusInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); if (mRawPressureAxis.valid) { - info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, AINPUT_SOURCE_STYLUS, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, AINPUT_SOURCE_STYLUS, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f); } } diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h index b6c9055cb7..11b53154ac 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h @@ -30,7 +30,7 @@ public: virtual ~ExternalStylusInputMapper() = default; uint32_t getSources() const override; - void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; + void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; void dump(std::string& dump) override; [[nodiscard]] std::list configure(nsecs_t when, const InputReaderConfiguration* config, diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp index ba2ea998b5..9cf3696e15 100644 --- a/services/inputflinger/reader/mapper/InputMapper.cpp +++ b/services/inputflinger/reader/mapper/InputMapper.cpp @@ -29,8 +29,8 @@ InputMapper::InputMapper(InputDeviceContext& deviceContext) : mDeviceContext(dev InputMapper::~InputMapper() {} -void InputMapper::populateDeviceInfo(InputDeviceInfo* info) { - info->addSource(getSources()); +void InputMapper::populateDeviceInfo(InputDeviceInfo& info) { + info.addSource(getSources()); } void InputMapper::dump(std::string& dump) {} diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h index 104305b730..2722edd137 100644 --- a/services/inputflinger/reader/mapper/InputMapper.h +++ b/services/inputflinger/reader/mapper/InputMapper.h @@ -51,7 +51,7 @@ public: inline InputReaderPolicyInterface* getPolicy() { return getContext()->getPolicy(); } virtual uint32_t getSources() const = 0; - virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo); + virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo); virtual void dump(std::string& dump); [[nodiscard]] virtual std::list configure(nsecs_t when, const InputReaderConfiguration* config, diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp index d7f8b17490..7724cf7ed5 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp @@ -29,7 +29,7 @@ uint32_t JoystickInputMapper::getSources() const { return AINPUT_SOURCE_JOYSTICK; } -void JoystickInputMapper::populateDeviceInfo(InputDeviceInfo* info) { +void JoystickInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); for (const auto& [_, axis] : mAxes) { @@ -41,16 +41,16 @@ void JoystickInputMapper::populateDeviceInfo(InputDeviceInfo* info) { } } -void JoystickInputMapper::addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo* info) { - info->addMotionRange(axisId, AINPUT_SOURCE_JOYSTICK, axis.min, axis.max, axis.flat, axis.fuzz, - axis.resolution); +void JoystickInputMapper::addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo& info) { + info.addMotionRange(axisId, AINPUT_SOURCE_JOYSTICK, axis.min, axis.max, axis.flat, axis.fuzz, + axis.resolution); /* In order to ease the transition for developers from using the old axes * to the newer, more semantically correct axes, we'll continue to register * the old axes as duplicates of their corresponding new ones. */ int32_t compatAxis = getCompatAxis(axisId); if (compatAxis >= 0) { - info->addMotionRange(compatAxis, AINPUT_SOURCE_JOYSTICK, axis.min, axis.max, axis.flat, - axis.fuzz, axis.resolution); + info.addMotionRange(compatAxis, AINPUT_SOURCE_JOYSTICK, axis.min, axis.max, axis.flat, + axis.fuzz, axis.resolution); } } diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.h b/services/inputflinger/reader/mapper/JoystickInputMapper.h index 72b8a528b5..9ca4176cff 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.h +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h @@ -26,7 +26,7 @@ public: virtual ~JoystickInputMapper(); virtual uint32_t getSources() const override; - virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; + virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; virtual void dump(std::string& dump) override; [[nodiscard]] std::list configure(nsecs_t when, const InputReaderConfiguration* config, @@ -106,7 +106,7 @@ private: static bool isCenteredAxis(int32_t axis); static int32_t getCompatAxis(int32_t axis); - static void addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo* info); + static void addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo& info); static void setPointerCoordsAxisValue(PointerCoords* pointerCoords, int32_t axis, float value); }; diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index f39e004882..269c1060d6 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -85,18 +85,18 @@ int32_t KeyboardInputMapper::getDisplayId() { return ADISPLAY_ID_NONE; } -void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo* info) { +void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); - info->setKeyboardType(mKeyboardType); - info->setKeyCharacterMap(getDeviceContext().getKeyCharacterMap()); + info.setKeyboardType(mKeyboardType); + info.setKeyCharacterMap(getDeviceContext().getKeyCharacterMap()); if (mKeyboardLayoutInfo) { - info->setKeyboardLayoutInfo(*mKeyboardLayoutInfo); + info.setKeyboardLayoutInfo(*mKeyboardLayoutInfo); } else { std::optional layoutInfo = getDeviceContext().getRawLayoutInfo(); if (layoutInfo) { - info->setKeyboardLayoutInfo( + info.setKeyboardLayoutInfo( KeyboardLayoutInfo(layoutInfo->languageTag, layoutInfo->layoutType)); } } diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index da5b8ee4f3..2fc82c3d13 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -27,7 +27,7 @@ public: ~KeyboardInputMapper() override = default; uint32_t getSources() const override; - void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; + void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; void dump(std::string& dump) override; [[nodiscard]] std::list configure(nsecs_t when, const InputReaderConfiguration* config, diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp index 2ff26ed98a..94cc1457a9 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp @@ -37,7 +37,7 @@ uint32_t RotaryEncoderInputMapper::getSources() const { return mSource; } -void RotaryEncoderInputMapper::populateDeviceInfo(InputDeviceInfo* info) { +void RotaryEncoderInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); if (mRotaryEncoderScrollAccumulator.haveRelativeVWheel()) { @@ -52,8 +52,8 @@ void RotaryEncoderInputMapper::populateDeviceInfo(InputDeviceInfo* info) { "default to 1.0!\n"); } mScalingFactor = scalingFactor.value_or(1.0f); - info->addMotionRange(AMOTION_EVENT_AXIS_SCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, - res.value_or(0.0f) * mScalingFactor); + info.addMotionRange(AMOTION_EVENT_AXIS_SCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, + res.value_or(0.0f) * mScalingFactor); } } diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h index cb5fd88209..a0516c44fa 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h @@ -29,7 +29,7 @@ public: virtual ~RotaryEncoderInputMapper(); virtual uint32_t getSources() const override; - virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; + virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; virtual void dump(std::string& dump) override; [[nodiscard]] std::list configure(nsecs_t when, const InputReaderConfiguration* config, diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp index ba088232bc..60e6727ed2 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp @@ -73,12 +73,12 @@ void SensorInputMapper::parseSensorConfiguration(InputDeviceSensorType sensorTyp } } -void SensorInputMapper::populateDeviceInfo(InputDeviceInfo* info) { +void SensorInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); for (const auto& [sensorType, sensor] : mSensors) { - info->addSensorInfo(sensor.sensorInfo); - info->setHasSensor(true); + info.addSensorInfo(sensor.sensorInfo); + info.setHasSensor(true); } } diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h index 043a8956cb..7f47df723e 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.h +++ b/services/inputflinger/reader/mapper/SensorInputMapper.h @@ -31,7 +31,7 @@ public: ~SensorInputMapper() override; uint32_t getSources() const override; - void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; + void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; void dump(std::string& dump) override; [[nodiscard]] std::list configure(nsecs_t when, const InputReaderConfiguration* config, diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index a943a03d16..943097ad25 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -134,16 +134,16 @@ uint32_t TouchInputMapper::getSources() const { return mSource; } -void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) { +void TouchInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); if (mDeviceMode == DeviceMode::DISABLED) { return; } - info->addMotionRange(mOrientedRanges.x); - info->addMotionRange(mOrientedRanges.y); - info->addMotionRange(mOrientedRanges.pressure); + info.addMotionRange(mOrientedRanges.x); + info.addMotionRange(mOrientedRanges.y); + info.addMotionRange(mOrientedRanges.pressure); if (mDeviceMode == DeviceMode::UNSCALED && mSource == AINPUT_SOURCE_TOUCHPAD) { // Populate RELATIVE_X and RELATIVE_Y motion ranges for touchpad capture mode. @@ -153,46 +153,46 @@ void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) { // touchpad in one sample cycle. const InputDeviceInfo::MotionRange& x = mOrientedRanges.x; const InputDeviceInfo::MotionRange& y = mOrientedRanges.y; - info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -x.max, x.max, x.flat, x.fuzz, - x.resolution); - info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -y.max, y.max, y.flat, y.fuzz, - y.resolution); + info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -x.max, x.max, x.flat, x.fuzz, + x.resolution); + info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -y.max, y.max, y.flat, y.fuzz, + y.resolution); } if (mOrientedRanges.size) { - info->addMotionRange(*mOrientedRanges.size); + info.addMotionRange(*mOrientedRanges.size); } if (mOrientedRanges.touchMajor) { - info->addMotionRange(*mOrientedRanges.touchMajor); - info->addMotionRange(*mOrientedRanges.touchMinor); + info.addMotionRange(*mOrientedRanges.touchMajor); + info.addMotionRange(*mOrientedRanges.touchMinor); } if (mOrientedRanges.toolMajor) { - info->addMotionRange(*mOrientedRanges.toolMajor); - info->addMotionRange(*mOrientedRanges.toolMinor); + info.addMotionRange(*mOrientedRanges.toolMajor); + info.addMotionRange(*mOrientedRanges.toolMinor); } if (mOrientedRanges.orientation) { - info->addMotionRange(*mOrientedRanges.orientation); + info.addMotionRange(*mOrientedRanges.orientation); } if (mOrientedRanges.distance) { - info->addMotionRange(*mOrientedRanges.distance); + info.addMotionRange(*mOrientedRanges.distance); } if (mOrientedRanges.tilt) { - info->addMotionRange(*mOrientedRanges.tilt); + info.addMotionRange(*mOrientedRanges.tilt); } if (mCursorScrollAccumulator.haveRelativeVWheel()) { - info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); } if (mCursorScrollAccumulator.haveRelativeHWheel()) { - info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f); } - info->setButtonUnderPad(mParameters.hasButtonUnderPad); - info->setUsiVersion(mParameters.usiVersion); + info.setButtonUnderPad(mParameters.hasButtonUnderPad); + info.setUsiVersion(mParameters.usiVersion); } void TouchInputMapper::dump(std::string& dump) { diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index df66846f9d..ae7faa9909 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -150,7 +150,7 @@ public: ~TouchInputMapper() override; uint32_t getSources() const override; - void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; + void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; void dump(std::string& dump) override; [[nodiscard]] std::list configure(nsecs_t when, const InputReaderConfiguration* config, diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 4c56b055e4..ec4bc21bfd 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -206,9 +206,9 @@ uint32_t TouchpadInputMapper::getSources() const { return AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD; } -void TouchpadInputMapper::populateDeviceInfo(InputDeviceInfo* info) { +void TouchpadInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); - mGestureConverter.populateMotionRanges(*info); + mGestureConverter.populateMotionRanges(info); } void TouchpadInputMapper::dump(std::string& dump) { diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index 94da1b3c79..fb36d920b6 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -41,7 +41,7 @@ public: ~TouchpadInputMapper(); uint32_t getSources() const override; - void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; + void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; void dump(std::string& dump) override; [[nodiscard]] std::list configure(nsecs_t when, diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp index 7645b12f1b..2c77fc47c4 100644 --- a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp @@ -29,10 +29,10 @@ uint32_t VibratorInputMapper::getSources() const { return 0; } -void VibratorInputMapper::populateDeviceInfo(InputDeviceInfo* info) { +void VibratorInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); - info->setVibrator(true); + info.setVibrator(true); } std::list VibratorInputMapper::process(const RawEvent* rawEvent) { diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.h b/services/inputflinger/reader/mapper/VibratorInputMapper.h index e98f63a8b4..e665973818 100644 --- a/services/inputflinger/reader/mapper/VibratorInputMapper.h +++ b/services/inputflinger/reader/mapper/VibratorInputMapper.h @@ -26,7 +26,7 @@ public: virtual ~VibratorInputMapper(); virtual uint32_t getSources() const override; - virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override; + virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; [[nodiscard]] std::list vibrate(const VibrationSequence& sequence, ssize_t repeat, diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index cdd24399ec..62b5e12e9e 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -123,7 +123,7 @@ static ui::Rotation getInverseRotation(ui::Rotation orientation) { static void assertAxisResolution(MultiTouchInputMapper& mapper, int axis, float resolution) { InputDeviceInfo info; - mapper.populateDeviceInfo(&info); + mapper.populateDeviceInfo(info); const InputDeviceInfo::MotionRange* motionRange = info.getMotionRange(axis, AINPUT_SOURCE_TOUCHSCREEN); @@ -132,7 +132,7 @@ static void assertAxisResolution(MultiTouchInputMapper& mapper, int axis, float static void assertAxisNotPresent(MultiTouchInputMapper& mapper, int axis) { InputDeviceInfo info; - mapper.populateDeviceInfo(&info); + mapper.populateDeviceInfo(info); const InputDeviceInfo::MotionRange* motionRange = info.getMotionRange(axis, AINPUT_SOURCE_TOUCHSCREEN); @@ -255,11 +255,11 @@ public: private: uint32_t getSources() const override { return mSources; } - void populateDeviceInfo(InputDeviceInfo* deviceInfo) override { + void populateDeviceInfo(InputDeviceInfo& deviceInfo) override { InputMapper::populateDeviceInfo(deviceInfo); if (mKeyboardType != AINPUT_KEYBOARD_TYPE_NONE) { - deviceInfo->setKeyboardType(mKeyboardType); + deviceInfo.setKeyboardType(mKeyboardType); } } @@ -3808,7 +3808,7 @@ TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeF CursorInputMapper& mapper = addMapperAndConfigure(); InputDeviceInfo info; - mapper.populateDeviceInfo(&info); + mapper.populateDeviceInfo(info); // Initially there may not be a valid motion range. ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE)); @@ -3820,7 +3820,7 @@ TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeF mFakePointerController->setBounds(1, 2, 800 - 1, 480 - 1); InputDeviceInfo info2; - mapper.populateDeviceInfo(&info2); + mapper.populateDeviceInfo(info2); ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, @@ -3838,7 +3838,7 @@ TEST_F(CursorInputMapperTest, WhenModeIsNavigation_PopulateDeviceInfo_ReturnsSca CursorInputMapper& mapper = addMapperAndConfigure(); InputDeviceInfo info; - mapper.populateDeviceInfo(&info); + mapper.populateDeviceInfo(info); ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_TRACKBALL, @@ -8481,7 +8481,7 @@ TEST_F(MultiTouchInputMapperTest, Process_PressureAxis_AmplitudeCalibration) { MultiTouchInputMapper& mapper = addMapperAndConfigure(); InputDeviceInfo info; - mapper.populateDeviceInfo(&info); + mapper.populateDeviceInfo(info); ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_TOUCHSCREEN, 0.0f, RAW_PRESSURE_MAX * 0.01, 0.0f, 0.0f)); diff --git a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp index be85026301..9a19b97f73 100644 --- a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp @@ -60,7 +60,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { std::list unused = mapper.configure(fdp->ConsumeIntegral(), &policyConfig, 0); InputDeviceInfo info; - mapper.populateDeviceInfo(&info); + mapper.populateDeviceInfo(info); }, [&]() -> void { int32_t type, code; diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp index 8e2d677866..33e7dbf488 100644 --- a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp @@ -59,7 +59,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { }, [&]() -> void { InputDeviceInfo info; - mapper.populateDeviceInfo(&info); + mapper.populateDeviceInfo(info); }, [&]() -> void { mapper.getSources(); }, [&]() -> void { diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp index 011455b8a1..59cb94a2e2 100644 --- a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp @@ -74,7 +74,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { }, [&]() -> void { InputDeviceInfo info; - mapper.populateDeviceInfo(&info); + mapper.populateDeviceInfo(info); }, [&]() -> void { mapper.getSources(); }, [&]() -> void { -- GitLab From 787aa7843aa5f10ce1b5a631d4afad392fc0dbaf Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Fri, 17 Mar 2023 13:46:46 -0700 Subject: [PATCH 1030/1310] [sf] add more debug logs to identify layer leaks Bug: 272200348 Test: logcat Change-Id: Icdb37a820b2028e69ff3b1a2e05c8bc9e7dd7dd4 --- services/surfaceflinger/Layer.cpp | 11 ++++++-- services/surfaceflinger/Layer.h | 7 ++++- services/surfaceflinger/SurfaceFlinger.cpp | 31 +++++++++++++++++++++- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 0f2af2f809..b60b387874 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -335,6 +335,7 @@ sp Layer::getHandle() { return nullptr; } mGetHandleCalled = true; + mHandleAlive = true; return sp::make(mFlinger, sp::fromExisting(this)); } @@ -1649,9 +1650,9 @@ void Layer::onDisconnect() { mFlinger->mFrameTracer->onDestroy(layerId); } -size_t Layer::getChildrenCount() const { +size_t Layer::getDescendantCount() const { size_t count = 0; - for (const sp& child : mCurrentChildren) { + for (const sp& child : mDrawingChildren) { count += 1 + child->getChildrenCount(); } return count; @@ -1898,6 +1899,12 @@ void Layer::traverse(LayerVector::StateSet state, const LayerVector::Visitor& vi } } +void Layer::traverseChildren(const LayerVector::Visitor& visitor) { + for (const sp& child : mDrawingChildren) { + visitor(child.get()); + } +} + LayerVector Layer::makeChildrenTraversalList(LayerVector::StateSet stateSet, const std::vector& layersInTree) { LOG_ALWAYS_FATAL_IF(stateSet == LayerVector::StateSet::Invalid, diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 2fb122cac3..6cce1a6ce7 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -700,6 +700,7 @@ public: void traverse(LayerVector::StateSet, const LayerVector::Visitor&); void traverseInReverseZOrder(LayerVector::StateSet, const LayerVector::Visitor&); void traverseInZOrder(LayerVector::StateSet, const LayerVector::Visitor&); + void traverseChildren(const LayerVector::Visitor&); /** * Traverse only children in z order, ignoring relative layers that are not children of the @@ -707,7 +708,10 @@ public: */ void traverseChildrenInZOrder(LayerVector::StateSet, const LayerVector::Visitor&); - size_t getChildrenCount() const; + size_t getDescendantCount() const; + size_t getChildrenCount() const { return mDrawingChildren.size(); } + bool isHandleAlive() const { return mHandleAlive; } + bool onHandleDestroyed() { return mHandleAlive = false; } // ONLY CALL THIS FROM THE LAYER DTOR! // See b/141111965. We need to add current children to offscreen layers in @@ -1191,6 +1195,7 @@ private: std::vector>> mLayerFEs; std::unique_ptr mSnapshot = std::make_unique(); + bool mHandleAlive = false; }; std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 8ce479a213..c0c68357f9 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4090,8 +4090,34 @@ status_t SurfaceFlinger::addClientLayer(LayerCreationArgs& args, const sp= MAX_LAYERS (%zu)", mNumLayers.load(), MAX_LAYERS); static_cast(mScheduler->schedule([=] { + ALOGE("Dumping layer keeping > 20 children alive:"); + bool leakingParentLayerFound = false; + mDrawingState.traverse([&](Layer* layer) { + if (leakingParentLayerFound) { + return; + } + if (layer->getChildrenCount() > 20) { + leakingParentLayerFound = true; + sp parent = sp::fromExisting(layer); + while (parent) { + ALOGE("Parent Layer: %s handleIsAlive: %s", parent->getName().c_str(), + std::to_string(parent->isHandleAlive()).c_str()); + parent = parent->getParent(); + } + // Sample up to 100 layers + ALOGE("Dumping random sampling of child layers total(%zu): ", + layer->getChildrenCount()); + int sampleSize = (layer->getChildrenCount() / 100) + 1; + layer->traverseChildren([&](Layer* layer) { + if (rand() % sampleSize == 0) { + ALOGE("Child Layer: %s", layer->getName().c_str()); + } + }); + } + }); + ALOGE("Dumping random sampling of on-screen layers: "); - mDrawingState.traverse([&](Layer *layer) { + mDrawingState.traverse([&](Layer* layer) { // Aim to dump about 200 layers to avoid totally trashing // logcat. On the other hand, if there really are 4096 layers // something has gone totally wrong its probably the most @@ -4100,6 +4126,8 @@ status_t SurfaceFlinger::addClientLayer(LayerCreationArgs& args, const spgetName().c_str()); } }); + ALOGE("Dumping random sampling of off-screen layers total(%zu): ", + mOffscreenLayers.size()); for (Layer* offscreenLayer : mOffscreenLayers) { if (rand() % 20 == 13) { ALOGE("Offscreen-layer: %s", offscreenLayer->getName().c_str()); @@ -5297,6 +5325,7 @@ void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp& layer, uint32 Mutex::Autolock lock(mStateLock); markLayerPendingRemovalLocked(layer); + layer->onHandleDestroyed(); mBufferCountTracker.remove(handle); layer.clear(); -- GitLab From d7434b1a4cfcb29d0e2fd8f475857399546d817e Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Fri, 17 Mar 2023 16:00:33 -0500 Subject: [PATCH 1031/1310] Use WindowInfo::transform in WindowInfosListenerTest Apply the WindowInfo's transform instead of the display transform to calculate touchable region. Bug: 272315174 Test: atest WindowInfosListenerTest#WindowInfoChanged Change-Id: Ifd81fce032f76484913bd2b17ae2ce1fb5753036 --- .../tests/WindowInfosListener_test.cpp | 37 ++++--------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/services/surfaceflinger/tests/WindowInfosListener_test.cpp b/services/surfaceflinger/tests/WindowInfosListener_test.cpp index 042d37b79e..f4a8f038d7 100644 --- a/services/surfaceflinger/tests/WindowInfosListener_test.cpp +++ b/services/surfaceflinger/tests/WindowInfosListener_test.cpp @@ -25,8 +25,7 @@ using Transaction = SurfaceComposerClient::Transaction; using gui::DisplayInfo; using gui::WindowInfo; -using WindowInfosPredicate = - std::function&, const std::vector&)>; +using WindowInfosPredicate = std::function&)>; class WindowInfosListenerTest : public ::testing::Test { protected: @@ -43,8 +42,8 @@ protected: : mPredicate(std::move(predicate)), mPromise(promise) {} void onWindowInfosChanged(const std::vector& windowInfos, - const std::vector& displayInfos) override { - if (mPredicate(windowInfos, displayInfos)) { + const std::vector&) override { + if (mPredicate(windowInfos)) { mPromise.set_value(); } } @@ -77,16 +76,6 @@ const WindowInfo* findMatchingWindowInfo(const WindowInfo& targetWindowInfo, return nullptr; } -const DisplayInfo* findMatchingDisplayInfo(int32_t displayId, - const std::vector& displayInfos) { - for (const DisplayInfo& displayInfo : displayInfos) { - if (displayInfo.displayId == displayId) { - return &displayInfo; - } - } - return nullptr; -} - TEST_F(WindowInfosListenerTest, WindowInfoAddedAndRemoved) { std::string name = "Test Layer"; sp token = sp::make(); @@ -104,16 +93,14 @@ TEST_F(WindowInfosListenerTest, WindowInfoAddedAndRemoved) { .setInputWindowInfo(surfaceControl, windowInfo) .apply(); - auto windowPresent = [&](const std::vector& windowInfos, - const std::vector&) { + auto windowPresent = [&](const std::vector& windowInfos) { return findMatchingWindowInfo(windowInfo, windowInfos); }; ASSERT_TRUE(waitForWindowInfosPredicate(windowPresent)); Transaction().reparent(surfaceControl, nullptr).apply(); - auto windowNotPresent = [&](const std::vector& windowInfos, - const std::vector&) { + auto windowNotPresent = [&](const std::vector& windowInfos) { return !findMatchingWindowInfo(windowInfo, windowInfos); }; ASSERT_TRUE(waitForWindowInfosPredicate(windowNotPresent)); @@ -137,8 +124,7 @@ TEST_F(WindowInfosListenerTest, WindowInfoChanged) { .setInputWindowInfo(surfaceControl, windowInfo) .apply(); - auto windowIsPresentAndTouchableRegionEmpty = [&](const std::vector& windowInfos, - const std::vector&) { + auto windowIsPresentAndTouchableRegionEmpty = [&](const std::vector& windowInfos) { auto foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos); if (!foundWindowInfo) { return false; @@ -151,21 +137,14 @@ TEST_F(WindowInfosListenerTest, WindowInfoChanged) { Transaction().setInputWindowInfo(surfaceControl, windowInfo).apply(); auto windowIsPresentAndTouchableRegionMatches = - [&](const std::vector& windowInfos, - const std::vector& displayInfos) { + [&](const std::vector& windowInfos) { auto foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos); if (!foundWindowInfo) { return false; } - auto displayInfo = - findMatchingDisplayInfo(foundWindowInfo->displayId, displayInfos); - if (!displayInfo) { - return false; - } - auto touchableRegion = - displayInfo->transform.transform(foundWindowInfo->touchableRegion); + foundWindowInfo->transform.transform(foundWindowInfo->touchableRegion); return touchableRegion.hasSameRects(windowInfo.touchableRegion); }; ASSERT_TRUE(waitForWindowInfosPredicate(windowIsPresentAndTouchableRegionMatches)); -- GitLab From 8e04e76892cd6b640391ac6498694c6e8613b78c Mon Sep 17 00:00:00 2001 From: Arthur Ishiguro Date: Mon, 20 Mar 2023 15:45:42 +0000 Subject: [PATCH 1032/1310] Adds Context Hub prebuilt xml target Bug: 274357710 Test: None Change-Id: I20424880e76d75fad3fb1dda7249d7ec4c1d2d20 --- data/etc/Android.bp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/etc/Android.bp b/data/etc/Android.bp index a737bd3fb7..57c470055e 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -76,6 +76,12 @@ prebuilt_etc { defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.hardware.context_hub.prebuilt.xml", + src: "android.hardware.context_hub.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "android.hardware.ethernet.prebuilt.xml", src: "android.hardware.ethernet.xml", -- GitLab From 46a09e1a030606a0b5b0275ab25a2324e3e92513 Mon Sep 17 00:00:00 2001 From: Arthur Ishiguro Date: Mon, 20 Mar 2023 19:49:10 +0000 Subject: [PATCH 1033/1310] Add missing build targets for sensor feature xml files Bug: 274357710 Test: None Change-Id: I8327faf9bac351dea1668d66c9626dd20d170c01 --- data/etc/Android.bp | 84 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/data/etc/Android.bp b/data/etc/Android.bp index 253324f14a..4ec485d557 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -112,30 +112,102 @@ prebuilt_etc { defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.hardware.sensor.accelerometer_limited_axes_uncalibrated.prebuilt.xml", + src: "android.hardware.sensor.accelerometer_limited_axes_uncalibrated.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + +prebuilt_etc { + name: "android.hardware.sensor.accelerometer_limited_axes.prebuilt.xml", + src: "android.hardware.sensor.accelerometer_limited_axes.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + +prebuilt_etc { + name: "android.hardware.sensor.accelerometer.prebuilt.xml", + src: "android.hardware.sensor.accelerometer.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "android.hardware.sensor.ambient_temperature.prebuilt.xml", src: "android.hardware.sensor.ambient_temperature.xml", defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.hardware.sensor.assist.prebuilt.xml", + src: "android.hardware.sensor.assist.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "android.hardware.sensor.barometer.prebuilt.xml", src: "android.hardware.sensor.barometer.xml", defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.hardware.sensor.compass.prebuilt.xml", + src: "android.hardware.sensor.compass.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "android.hardware.sensor.dynamic.head_tracker.prebuilt.xml", src: "android.hardware.sensor.dynamic.head_tracker.xml", defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.hardware.sensor.gyroscope_limited_axes_uncalibrated.prebuilt.xml", + src: "android.hardware.sensor.gyroscope_limited_axes_uncalibrated.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + +prebuilt_etc { + name: "android.hardware.sensor.gyroscope_limited_axes.prebuilt.xml", + src: "android.hardware.sensor.gyroscope_limited_axes.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "android.hardware.sensor.gyroscope.prebuilt.xml", src: "android.hardware.sensor.gyroscope.xml", defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.hardware.sensor.heading.prebuilt.xml", + src: "android.hardware.sensor.heading.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + +prebuilt_etc { + name: "android.hardware.sensor.heartrate.ecg.prebuilt.xml", + src: "android.hardware.sensor.heartrate.ecg.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + +prebuilt_etc { + name: "android.hardware.sensor.heartrate.fitness.prebuilt.xml", + src: "android.hardware.sensor.heartrate.fitness.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + +prebuilt_etc { + name: "android.hardware.sensor.heartrate.prebuilt.xml", + src: "android.hardware.sensor.heartrate.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + +prebuilt_etc { + name: "android.hardware.sensor.hifi_sensors.prebuilt.xml", + src: "android.hardware.sensor.hifi_sensors.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "android.hardware.sensor.hinge_angle.prebuilt.xml", src: "android.hardware.sensor.hinge_angle.xml", @@ -160,6 +232,18 @@ prebuilt_etc { defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.hardware.sensor.stepcounter.prebuilt.xml", + src: "android.hardware.sensor.stepcounter.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + +prebuilt_etc { + name: "android.hardware.sensor.stepdetector.prebuilt.xml", + src: "android.hardware.sensor.stepdetector.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "android.hardware.telephony.gsm.prebuilt.xml", src: "android.hardware.telephony.gsm.xml", -- GitLab From fed7c12150bcef08ddfd5e77e38a4fdf6a9c971a Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Sat, 18 Mar 2023 01:54:43 +0000 Subject: [PATCH 1034/1310] Take into account surface insets when replacing touch region. When SurfaceFlinger computes window frames, the surface insets are clamped to the input bounds. But when replacing the touchable region, the surface insets are not taken into account. This CL adds consideration for surface insets, so that touchable region is computed correctly. This also fixes a ListPopupWindowTest on freeform windowing devices. Test: atest SurfaceFlinger_test on freeform windowing emulator and aosp_cf_x86_64_phone target Fixes: 272786078 Change-Id: Id8082cb1b39280e846c2bf5a36315df68d37d2e6 --- libs/gui/tests/EndToEndNativeInputTest.cpp | 16 ++ .../FrontEnd/LayerSnapshotBuilder.cpp | 119 +++++++++------ services/surfaceflinger/Layer.cpp | 139 ++++++++++-------- services/surfaceflinger/Layer.h | 4 +- 4 files changed, 168 insertions(+), 110 deletions(-) diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp index 3344e0b690..5f80c16f61 100644 --- a/libs/gui/tests/EndToEndNativeInputTest.cpp +++ b/libs/gui/tests/EndToEndNativeInputTest.cpp @@ -512,6 +512,22 @@ TEST_F(InputSurfacesTest, input_respects_surface_insets) { bgSurface->expectTap(1, 1); } +TEST_F(InputSurfacesTest, input_respects_surface_insets_with_replaceTouchableRegionWithCrop) { + std::unique_ptr bgSurface = makeSurface(100, 100); + std::unique_ptr fgSurface = makeSurface(100, 100); + bgSurface->showAt(100, 100); + + fgSurface->mInputInfo.surfaceInset = 5; + fgSurface->mInputInfo.replaceTouchableRegionWithCrop = true; + fgSurface->showAt(100, 100); + + injectTap(106, 106); + fgSurface->expectTap(1, 1); + + injectTap(101, 101); + bgSurface->expectTap(1, 1); +} + // 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 parentSurface = makeSurface(100, 100); diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index a16de1bcfb..2fc9682849 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -21,6 +21,7 @@ #include "LayerSnapshotBuilder.h" #include +#include #include #include "DisplayHardware/HWC2.h" #include "DisplayHardware/Hal.h" @@ -101,43 +102,52 @@ ui::Transform getInputTransform(const LayerSnapshot& snapshot) { } /** + * Returns the bounds used to fill the input frame and the touchable region. + * * Similar to getInputTransform, we need to update the bounds to include the transform. * This is because bounds don't include the buffer transform, where the input assumes * that's already included. */ -Rect getInputBounds(const LayerSnapshot& snapshot) { - if (!snapshot.hasBufferOrSidebandStream()) { - return snapshot.croppedBufferSize; +std::pair getInputBounds(const LayerSnapshot& snapshot, bool fillParentBounds) { + FloatRect inputBounds = snapshot.croppedBufferSize.toFloatRect(); + if (snapshot.hasBufferOrSidebandStream() && snapshot.croppedBufferSize.isValid() && + snapshot.localTransform.getType() != ui::Transform::IDENTITY) { + inputBounds = snapshot.localTransform.transform(inputBounds); + } + + bool inputBoundsValid = snapshot.croppedBufferSize.isValid(); + if (!inputBoundsValid) { + /** + * Input bounds are based on the layer crop or buffer size. But if we are using + * the layer bounds as the input bounds (replaceTouchableRegionWithCrop flag) then + * we can use the parent bounds as the input bounds if the layer does not have buffer + * or a crop. We want to unify this logic but because of compat reasons we cannot always + * use the parent bounds. A layer without a buffer can get input. So when a window is + * initially added, its touchable region can fill its parent layer bounds and that can + * have negative consequences. + */ + inputBounds = fillParentBounds ? snapshot.geomLayerBounds : FloatRect{}; } - if (snapshot.localTransform.getType() == ui::Transform::IDENTITY || - !snapshot.croppedBufferSize.isValid()) { - return snapshot.croppedBufferSize; - } - return snapshot.localTransform.transform(snapshot.croppedBufferSize); -} + // Clamp surface inset to the input bounds. + const float inset = static_cast(snapshot.inputInfo.surfaceInset); + const float xSurfaceInset = std::clamp(inset, 0.f, inputBounds.getWidth() / 2.f); + const float ySurfaceInset = std::clamp(inset, 0.f, inputBounds.getHeight() / 2.f); -void fillInputFrameInfo(gui::WindowInfo& info, const ui::Transform& screenToDisplay, - const LayerSnapshot& snapshot) { - Rect tmpBounds = getInputBounds(snapshot); - if (!tmpBounds.isValid()) { - info.touchableRegion.clear(); - // A layer could have invalid input bounds and still expect to receive touch input if it has - // replaceTouchableRegionWithCrop. For that case, the input transform needs to be calculated - // correctly to determine the coordinate space for input events. Use an empty rect so that - // the layer will receive input in its own layer space. - tmpBounds = Rect::EMPTY_RECT; - } + // Apply the insets to the input bounds. + inputBounds.left += xSurfaceInset; + inputBounds.top += ySurfaceInset; + inputBounds.right -= xSurfaceInset; + inputBounds.bottom -= ySurfaceInset; + return {inputBounds, inputBoundsValid}; +} +Rect getInputBoundsInDisplaySpace(const LayerSnapshot& snapshot, const FloatRect& insetBounds, + const ui::Transform& screenToDisplay) { // InputDispatcher works in the display device's coordinate space. Here, we calculate the // frame and transform used for the layer, which determines the bounds and the coordinate space // within which the layer will receive input. - // - // The coordinate space within which each of the bounds are specified is explicitly documented - // in the variable name. For example "inputBoundsInLayer" is specified in layer space. A - // Transform converts one coordinate space to another, which is apparent in its naming. For - // example, "layerToDisplay" transforms layer space to display space. - // + // Coordinate space definitions: // - display: The display device's coordinate space. Correlates to pixels on the display. // - screen: The post-rotation coordinate space for the display, a.k.a. logical display space. @@ -145,37 +155,34 @@ void fillInputFrameInfo(gui::WindowInfo& info, const ui::Transform& screenToDisp // - input: The coordinate space in which this layer will receive input events. This could be // different than layer space if a surfaceInset is used, which changes the origin // of the input space. - const FloatRect inputBoundsInLayer = tmpBounds.toFloatRect(); - - // Clamp surface inset to the input bounds. - const auto surfaceInset = static_cast(info.surfaceInset); - const float xSurfaceInset = - std::max(0.f, std::min(surfaceInset, inputBoundsInLayer.getWidth() / 2.f)); - const float ySurfaceInset = - std::max(0.f, std::min(surfaceInset, inputBoundsInLayer.getHeight() / 2.f)); - - // Apply the insets to the input bounds. - const FloatRect insetBoundsInLayer(inputBoundsInLayer.left + xSurfaceInset, - inputBoundsInLayer.top + ySurfaceInset, - inputBoundsInLayer.right - xSurfaceInset, - inputBoundsInLayer.bottom - ySurfaceInset); // Crop the input bounds to ensure it is within the parent's bounds. - const FloatRect croppedInsetBoundsInLayer = - snapshot.geomLayerBounds.intersect(insetBoundsInLayer); + const FloatRect croppedInsetBoundsInLayer = snapshot.geomLayerBounds.intersect(insetBounds); const ui::Transform layerToScreen = getInputTransform(snapshot); const ui::Transform layerToDisplay = screenToDisplay * layerToScreen; - const Rect roundedFrameInDisplay{layerToDisplay.transform(croppedInsetBoundsInLayer)}; + return Rect{layerToDisplay.transform(croppedInsetBoundsInLayer)}; +} + +void fillInputFrameInfo(gui::WindowInfo& info, const ui::Transform& screenToDisplay, + const LayerSnapshot& snapshot) { + auto [inputBounds, inputBoundsValid] = getInputBounds(snapshot, /*fillParentBounds=*/false); + if (!inputBoundsValid) { + info.touchableRegion.clear(); + } + + const Rect roundedFrameInDisplay = + getInputBoundsInDisplaySpace(snapshot, inputBounds, screenToDisplay); info.frameLeft = roundedFrameInDisplay.left; info.frameTop = roundedFrameInDisplay.top; info.frameRight = roundedFrameInDisplay.right; info.frameBottom = roundedFrameInDisplay.bottom; ui::Transform inputToLayer; - inputToLayer.set(insetBoundsInLayer.left, insetBoundsInLayer.top); - const ui::Transform inputToDisplay = layerToDisplay * inputToLayer; + inputToLayer.set(inputBounds.left, inputBounds.top); + const ui::Transform layerToScreen = getInputTransform(snapshot); + const ui::Transform inputToDisplay = screenToDisplay * layerToScreen * inputToLayer; // InputDispatcher expects a display-to-input transform. info.transform = inputToDisplay.inverse(); @@ -1008,12 +1015,26 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, auto cropLayerSnapshot = getSnapshot(requested.touchCropId); if (snapshot.inputInfo.replaceTouchableRegionWithCrop) { - const Rect bounds(cropLayerSnapshot ? cropLayerSnapshot->transformedBounds - : snapshot.transformedBounds); - snapshot.inputInfo.touchableRegion = Region(displayInfo.transform.transform(bounds)); + Rect inputBoundsInDisplaySpace; + if (!cropLayerSnapshot) { + FloatRect inputBounds = getInputBounds(snapshot, /*fillParentBounds=*/true).first; + inputBoundsInDisplaySpace = + getInputBoundsInDisplaySpace(snapshot, inputBounds, displayInfo.transform); + } else { + FloatRect inputBounds = + getInputBounds(*cropLayerSnapshot, /*fillParentBounds=*/true).first; + inputBoundsInDisplaySpace = + getInputBoundsInDisplaySpace(*cropLayerSnapshot, inputBounds, + displayInfo.transform); + } + snapshot.inputInfo.touchableRegion = Region(inputBoundsInDisplaySpace); } else if (cropLayerSnapshot) { + FloatRect inputBounds = getInputBounds(*cropLayerSnapshot, /*fillParentBounds=*/true).first; + Rect inputBoundsInDisplaySpace = + getInputBoundsInDisplaySpace(*cropLayerSnapshot, inputBounds, + displayInfo.transform); snapshot.inputInfo.touchableRegion = snapshot.inputInfo.touchableRegion.intersect( - displayInfo.transform.transform(Rect{cropLayerSnapshot->transformedBounds})); + displayInfo.transform.transform(inputBoundsInDisplaySpace)); } // Inherit the trusted state from the parent hierarchy, but don't clobber the trusted state diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 0f2af2f809..3b41458475 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -52,8 +52,11 @@ #include #include #include +#include #include #include +#include +#include #include #include #include @@ -2297,62 +2300,21 @@ static Region transformTouchableRegionSafely(const ui::Transform& t, const Regio } void Layer::fillInputFrameInfo(WindowInfo& info, const ui::Transform& screenToDisplay) { - Rect tmpBounds = getInputBounds(); - if (!tmpBounds.isValid()) { + auto [inputBounds, inputBoundsValid] = getInputBounds(/*fillParentBounds=*/false); + if (!inputBoundsValid) { info.touchableRegion.clear(); - // A layer could have invalid input bounds and still expect to receive touch input if it has - // replaceTouchableRegionWithCrop. For that case, the input transform needs to be calculated - // correctly to determine the coordinate space for input events. Use an empty rect so that - // the layer will receive input in its own layer space. - tmpBounds = Rect::EMPTY_RECT; } - // InputDispatcher works in the display device's coordinate space. Here, we calculate the - // frame and transform used for the layer, which determines the bounds and the coordinate space - // within which the layer will receive input. - // - // The coordinate space within which each of the bounds are specified is explicitly documented - // in the variable name. For example "inputBoundsInLayer" is specified in layer space. A - // Transform converts one coordinate space to another, which is apparent in its naming. For - // example, "layerToDisplay" transforms layer space to display space. - // - // Coordinate space definitions: - // - display: The display device's coordinate space. Correlates to pixels on the display. - // - screen: The post-rotation coordinate space for the display, a.k.a. logical display space. - // - layer: The coordinate space of this layer. - // - input: The coordinate space in which this layer will receive input events. This could be - // different than layer space if a surfaceInset is used, which changes the origin - // of the input space. - const FloatRect inputBoundsInLayer = tmpBounds.toFloatRect(); - - // Clamp surface inset to the input bounds. - const auto surfaceInset = static_cast(info.surfaceInset); - const float xSurfaceInset = - std::max(0.f, std::min(surfaceInset, inputBoundsInLayer.getWidth() / 2.f)); - const float ySurfaceInset = - std::max(0.f, std::min(surfaceInset, inputBoundsInLayer.getHeight() / 2.f)); - - // Apply the insets to the input bounds. - const FloatRect insetBoundsInLayer(inputBoundsInLayer.left + xSurfaceInset, - inputBoundsInLayer.top + ySurfaceInset, - inputBoundsInLayer.right - xSurfaceInset, - inputBoundsInLayer.bottom - ySurfaceInset); - - // Crop the input bounds to ensure it is within the parent's bounds. - const FloatRect croppedInsetBoundsInLayer = mBounds.intersect(insetBoundsInLayer); - - const ui::Transform layerToScreen = getInputTransform(); - const ui::Transform layerToDisplay = screenToDisplay * layerToScreen; - - const Rect roundedFrameInDisplay{layerToDisplay.transform(croppedInsetBoundsInLayer)}; + const Rect roundedFrameInDisplay = getInputBoundsInDisplaySpace(inputBounds, screenToDisplay); info.frameLeft = roundedFrameInDisplay.left; info.frameTop = roundedFrameInDisplay.top; info.frameRight = roundedFrameInDisplay.right; info.frameBottom = roundedFrameInDisplay.bottom; ui::Transform inputToLayer; - inputToLayer.set(insetBoundsInLayer.left, insetBoundsInLayer.top); - const ui::Transform inputToDisplay = layerToDisplay * inputToLayer; + inputToLayer.set(inputBounds.left, inputBounds.top); + const ui::Transform layerToScreen = getInputTransform(); + const ui::Transform inputToDisplay = screenToDisplay * layerToScreen * inputToLayer; // InputDispatcher expects a display-to-input transform. info.transform = inputToDisplay.inverse(); @@ -2485,13 +2447,23 @@ WindowInfo Layer::fillInputInfo(const InputDisplayArgs& displayArgs) { info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT; } - auto cropLayer = mDrawingState.touchableRegionCrop.promote(); + sp cropLayer = mDrawingState.touchableRegionCrop.promote(); if (info.replaceTouchableRegionWithCrop) { - const Rect bounds(cropLayer ? cropLayer->mScreenBounds : mScreenBounds); - info.touchableRegion = Region(displayTransform.transform(bounds)); + Rect inputBoundsInDisplaySpace; + if (!cropLayer) { + FloatRect inputBounds = getInputBounds(/*fillParentBounds=*/true).first; + inputBoundsInDisplaySpace = getInputBoundsInDisplaySpace(inputBounds, displayTransform); + } else { + FloatRect inputBounds = cropLayer->getInputBounds(/*fillParentBounds=*/true).first; + inputBoundsInDisplaySpace = + cropLayer->getInputBoundsInDisplaySpace(inputBounds, displayTransform); + } + info.touchableRegion = Region(inputBoundsInDisplaySpace); } else if (cropLayer != nullptr) { - info.touchableRegion = info.touchableRegion.intersect( - displayTransform.transform(Rect{cropLayer->mScreenBounds})); + FloatRect inputBounds = cropLayer->getInputBounds(/*fillParentBounds=*/true).first; + Rect inputBoundsInDisplaySpace = + cropLayer->getInputBoundsInDisplaySpace(inputBounds, displayTransform); + info.touchableRegion = info.touchableRegion.intersect(inputBoundsInDisplaySpace); } // Inherit the trusted state from the parent hierarchy, but don't clobber the trusted state @@ -2513,6 +2485,27 @@ WindowInfo Layer::fillInputInfo(const InputDisplayArgs& displayArgs) { return info; } +Rect Layer::getInputBoundsInDisplaySpace(const FloatRect& inputBounds, + const ui::Transform& screenToDisplay) { + // InputDispatcher works in the display device's coordinate space. Here, we calculate the + // frame and transform used for the layer, which determines the bounds and the coordinate space + // within which the layer will receive input. + + // Coordinate space definitions: + // - display: The display device's coordinate space. Correlates to pixels on the display. + // - screen: The post-rotation coordinate space for the display, a.k.a. logical display space. + // - layer: The coordinate space of this layer. + // - input: The coordinate space in which this layer will receive input events. This could be + // different than layer space if a surfaceInset is used, which changes the origin + // of the input space. + + // Crop the input bounds to ensure it is within the parent's bounds. + const FloatRect croppedInputBounds = mBounds.intersect(inputBounds); + const ui::Transform layerToScreen = getInputTransform(); + const ui::Transform layerToDisplay = screenToDisplay * layerToScreen; + return Rect{layerToDisplay.transform(croppedInputBounds)}; +} + sp Layer::getClonedRoot() { if (mClonedChild != nullptr) { return sp::fromExisting(this); @@ -3461,20 +3454,46 @@ ui::Transform Layer::getInputTransform() const { } /** + * Returns the bounds used to fill the input frame and the touchable region. + * * Similar to getInputTransform, we need to update the bounds to include the transform. * This is because bounds don't include the buffer transform, where the input assumes * that's already included. */ -Rect Layer::getInputBounds() const { - if (!hasBufferOrSidebandStream()) { - return getCroppedBufferSize(getDrawingState()); +std::pair Layer::getInputBounds(bool fillParentBounds) const { + Rect croppedBufferSize = getCroppedBufferSize(getDrawingState()); + FloatRect inputBounds = croppedBufferSize.toFloatRect(); + if (hasBufferOrSidebandStream() && croppedBufferSize.isValid() && + mDrawingState.transform.getType() != ui::Transform::IDENTITY) { + inputBounds = mDrawingState.transform.transform(inputBounds); + } + + bool inputBoundsValid = croppedBufferSize.isValid(); + if (!inputBoundsValid) { + /** + * Input bounds are based on the layer crop or buffer size. But if we are using + * the layer bounds as the input bounds (replaceTouchableRegionWithCrop flag) then + * we can use the parent bounds as the input bounds if the layer does not have buffer + * or a crop. We want to unify this logic but because of compat reasons we cannot always + * use the parent bounds. A layer without a buffer can get input. So when a window is + * initially added, its touchable region can fill its parent layer bounds and that can + * have negative consequences. + */ + inputBounds = fillParentBounds ? mBounds : FloatRect{}; } - Rect bufferBounds = getCroppedBufferSize(getDrawingState()); - if (mDrawingState.transform.getType() == ui::Transform::IDENTITY || !bufferBounds.isValid()) { - return bufferBounds; - } - return mDrawingState.transform.transform(bufferBounds); + // Clamp surface inset to the input bounds. + const float inset = static_cast(mDrawingState.inputInfo.surfaceInset); + const float xSurfaceInset = std::clamp(inset, 0.f, inputBounds.getWidth() / 2.f); + const float ySurfaceInset = std::clamp(inset, 0.f, inputBounds.getHeight() / 2.f); + + // Apply the insets to the input bounds. + inputBounds.left += xSurfaceInset; + inputBounds.top += ySurfaceInset; + inputBounds.right -= xSurfaceInset; + inputBounds.bottom -= ySurfaceInset; + + return {inputBounds, inputBoundsValid}; } bool Layer::simpleBufferUpdate(const layer_state_t& s) const { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 2fb122cac3..9ed5899607 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -570,6 +570,8 @@ public: FloatRect getBounds(const Region& activeTransparentRegion) const; FloatRect getBounds() const; + Rect getInputBoundsInDisplaySpace(const FloatRect& insetBounds, + const ui::Transform& displayTransform); // Compute bounds for the layer and cache the results. void computeBounds(FloatRect parentBounds, ui::Transform parentTransform, float shadowRadius); @@ -931,7 +933,7 @@ protected: * "replaceTouchableRegionWithCrop" is specified. In this case, the layer will receive input * in this layer's space, regardless of the specified crop layer. */ - Rect getInputBounds() const; + std::pair getInputBounds(bool fillParentBounds) const; // constant sp mFlinger; -- GitLab From 2b8cecd24133250e40743e3b5188079df07b4ea2 Mon Sep 17 00:00:00 2001 From: Serdar Kocdemir Date: Fri, 17 Mar 2023 13:19:57 +0000 Subject: [PATCH 1035/1310] Add Android version check to GpuWorkTracepointTest Bug: b/248501602 Test: manual Change-Id: I52d732b20b8b26593087f7534aade3ec61b87d3c --- services/gpuservice/vts/Android.bp | 1 + .../gpuservice/GpuWorkTracepointTest.java | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/services/gpuservice/vts/Android.bp b/services/gpuservice/vts/Android.bp index 83a40e7bbf..f4ea4408b6 100644 --- a/services/gpuservice/vts/Android.bp +++ b/services/gpuservice/vts/Android.bp @@ -22,6 +22,7 @@ java_test_host { libs: [ "tradefed", "vts-core-tradefed-harness", + "compatibility-host-util", ], test_suites: [ "general-tests", diff --git a/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java index 9fa901675d..290a6469c6 100644 --- a/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java +++ b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java @@ -19,11 +19,16 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; +import android.platform.test.annotations.RestrictedBuildTest; + import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; import com.android.tradefed.util.CommandResult; import com.android.tradefed.util.CommandStatus; +import com.android.compatibility.common.util.PropertyUtil; +import com.android.compatibility.common.util.GmsTest; + import org.junit.Test; import org.junit.runner.RunWith; @@ -57,15 +62,23 @@ public class GpuWorkTracepointTest extends BaseHostJUnit4Test { commandResult.getStatus(), CommandStatus.SUCCESS); } + @GmsTest(requirement = "VSR-3.3-004") + @RestrictedBuildTest @Test public void testGpuWorkPeriodTracepointFormat() throws Exception { CommandResult commandResult = getDevice().executeShellV2Command( String.format("cat %s", GPU_WORK_PERIOD_TRACEPOINT_FORMAT_PATH)); // If we failed to cat the tracepoint format then the test ends here. - assumeTrue(String.format("Failed to cat the gpu_work_period tracepoint format at %s", - GPU_WORK_PERIOD_TRACEPOINT_FORMAT_PATH), - commandResult.getStatus().equals(CommandStatus.SUCCESS)); + if (!commandResult.getStatus().equals(CommandStatus.SUCCESS)) { + String message = String.format( + "Failed to cat the gpu_work_period tracepoint format at %s\n", + GPU_WORK_PERIOD_TRACEPOINT_FORMAT_PATH); + + // Tracepoint MUST exist on devices released with Android 14 or later + assumeTrue(message, PropertyUtil.getVsrApiLevel(getDevice()) >= 34); + fail(message); + } // Otherwise, we check that the fields match the expected fields. String actualFields = Arrays.stream( -- GitLab From 36d5f8ef5dca730d5df90edd0be264a7fa6a1f2c Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Sun, 19 Mar 2023 13:31:35 -0700 Subject: [PATCH 1036/1310] [sf] fix screenshots when new fe is enabled - Use uid in layer state when filtering by uid for screenshots because there is a mismatch between uid provided by layer and input info (b/271132344) - Pass transform and buffersize into LayerRenderArea so we don't need to check legacy drawing state. Bug: 238781169 Test: atest SurfaceFlinger_test Change-Id: I2bd5ceeba8303cad2996f521ea03f727269a7a96 --- .../surfaceflinger/FrontEnd/LayerSnapshot.cpp | 9 ++++- .../surfaceflinger/FrontEnd/LayerSnapshot.h | 2 ++ services/surfaceflinger/LayerRenderArea.cpp | 10 ++++-- services/surfaceflinger/LayerRenderArea.h | 5 ++- services/surfaceflinger/SurfaceFlinger.cpp | 34 +++++++++++++++---- services/surfaceflinger/SurfaceFlinger.h | 2 +- .../fuzzer/surfaceflinger_layer_fuzzer.cpp | 10 +++++- 7 files changed, 59 insertions(+), 13 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp index 5e7c25946b..2d6d8ad0b0 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp @@ -44,6 +44,8 @@ LayerSnapshot::LayerSnapshot(const RequestedLayerState& state, inputInfo.id = static_cast(uniqueSequence); inputInfo.ownerUid = static_cast(state.ownerUid); inputInfo.ownerPid = state.ownerPid; + uid = state.ownerUid; + pid = state.ownerPid; changes = RequestedLayerState::Changes::Created; mirrorRootPath = path.variant == LayerHierarchy::Variant::Mirror ? path @@ -179,7 +181,12 @@ std::string LayerSnapshot::getDebugString() const { std::stringstream debug; debug << "Snapshot{" << path.toString() << name << " isVisible=" << isVisible << " {" << getIsVisibleReason() << "} changes=" << changes.string() - << " layerStack=" << outputFilter.layerStack.id << "}"; + << " layerStack=" << outputFilter.layerStack.id << " geomLayerBounds={" + << geomLayerBounds.left << "," << geomLayerBounds.top << "," << geomLayerBounds.bottom + << "," << geomLayerBounds.right << "}" + << " geomLayerTransform={tx=" << geomLayerTransform.tx() + << ",ty=" << geomLayerTransform.ty() << "}" + << "}"; return debug.str(); } diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h index 6fb2f57d96..e22c279e0f 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h @@ -93,6 +93,8 @@ struct LayerSnapshot : public compositionengine::LayerFECompositionState { int32_t frameRateSelectionPriority; LayerHierarchy::TraversalPath mirrorRootPath; bool unreachable = true; + uid_t uid; + pid_t pid; ChildState childState; static bool isOpaqueFormat(PixelFormat format); diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp index 03a7f226ed..1b8ff28a76 100644 --- a/services/surfaceflinger/LayerRenderArea.cpp +++ b/services/surfaceflinger/LayerRenderArea.cpp @@ -39,9 +39,12 @@ void reparentForDrawing(const sp& oldParent, const sp& newParent, LayerRenderArea::LayerRenderArea(SurfaceFlinger& flinger, sp layer, const Rect& crop, ui::Size reqSize, ui::Dataspace reqDataSpace, bool childrenOnly, - bool allowSecureLayers) + bool allowSecureLayers, const ui::Transform& layerTransform, + const Rect& layerBufferSize) : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, allowSecureLayers), mLayer(std::move(layer)), + mLayerTransform(layerTransform), + mLayerBufferSize(layerBufferSize), mCrop(crop), mFlinger(flinger), mChildrenOnly(childrenOnly) {} @@ -60,7 +63,8 @@ sp LayerRenderArea::getDisplayDevice() const { Rect LayerRenderArea::getSourceCrop() const { if (mCrop.isEmpty()) { - return mLayer->getBufferSize(mLayer->getDrawingState()); + // TODO this should probably be mBounds instead of just buffer bounds + return mLayerBufferSize; } else { return mCrop; } @@ -70,7 +74,7 @@ void LayerRenderArea::render(std::function drawLayers) { using namespace std::string_literals; if (!mChildrenOnly) { - mTransform = mLayer->getTransform().inverse(); + mTransform = mLayerTransform.inverse(); } if (mFlinger.mLayerLifecycleManagerEnabled) { diff --git a/services/surfaceflinger/LayerRenderArea.h b/services/surfaceflinger/LayerRenderArea.h index 322dbd1bf4..9bb13b3d6a 100644 --- a/services/surfaceflinger/LayerRenderArea.h +++ b/services/surfaceflinger/LayerRenderArea.h @@ -33,7 +33,8 @@ class SurfaceFlinger; class LayerRenderArea : public RenderArea { public: LayerRenderArea(SurfaceFlinger& flinger, sp layer, const Rect& crop, ui::Size reqSize, - ui::Dataspace reqDataSpace, bool childrenOnly, bool allowSecureLayers); + ui::Dataspace reqDataSpace, bool childrenOnly, bool allowSecureLayers, + const ui::Transform& layerTransform, const Rect& layerBufferSize); const ui::Transform& getTransform() const override; bool isSecure() const override; @@ -45,6 +46,8 @@ public: private: const sp mLayer; + const ui::Transform mLayerTransform; + const Rect mLayerBufferSize; const Rect mCrop; ui::Transform mTransform; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 51a11ad5d9..5664d84976 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -7051,13 +7051,34 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, bool childrenOnly = args.childrenOnly; RenderAreaFuture renderAreaFuture = ftl::defer([=]() -> std::unique_ptr { + ui::Transform layerTransform; + Rect layerBufferSize; + if (mLayerLifecycleManagerEnabled) { + frontend::LayerSnapshot* snapshot = + mLayerSnapshotBuilder.getSnapshot(parent->getSequence()); + if (!snapshot) { + ALOGW("Couldn't find layer snapshot for %d", parent->getSequence()); + } else { + layerTransform = snapshot->localTransform; + layerBufferSize = snapshot->bufferSize; + } + } else { + layerTransform = parent->getTransform(); + layerBufferSize = parent->getBufferSize(parent->getDrawingState()); + } + return std::make_unique(*this, parent, crop, reqSize, dataspace, - childrenOnly, args.captureSecureLayers); + childrenOnly, args.captureSecureLayers, + layerTransform, layerBufferSize); }); GetLayerSnapshotsFunction getLayerSnapshots; if (mLayerLifecycleManagerEnabled) { - FloatRect parentCrop = crop.isEmpty() ? FloatRect(0, 0, reqSize.width, reqSize.height) - : crop.toFloatRect(); + std::optional parentCrop = std::nullopt; + if (args.childrenOnly) { + parentCrop = crop.isEmpty() ? FloatRect(0, 0, reqSize.width, reqSize.height) + : crop.toFloatRect(); + } + getLayerSnapshots = getLayerSnapshotsForScreenshots(parent->sequence, args.uid, std::move(excludeLayerIds), args.childrenOnly, parentCrop); @@ -8007,7 +8028,7 @@ SurfaceFlinger::getLayerSnapshotsForScreenshots( if (layerStack && snapshot->outputFilter.layerStack != *layerStack) { return; } - if (uid != CaptureArgs::UNSET_UID && snapshot->inputInfo.ownerUid != uid) { + if (uid != CaptureArgs::UNSET_UID && snapshot->uid != uid) { return; } if (!snapshot->hasSomethingToDraw()) { @@ -8034,7 +8055,8 @@ SurfaceFlinger::getLayerSnapshotsForScreenshots( std::function>>()> SurfaceFlinger::getLayerSnapshotsForScreenshots(uint32_t rootLayerId, uint32_t uid, std::unordered_set excludeLayerIds, - bool childrenOnly, const FloatRect& parentCrop) { + bool childrenOnly, + const std::optional& parentCrop) { return [&, rootLayerId, uid, excludeLayerIds = std::move(excludeLayerIds), childrenOnly, parentCrop]() { auto root = mLayerHierarchyBuilder.getPartialHierarchy(rootLayerId, childrenOnly); @@ -8047,7 +8069,7 @@ SurfaceFlinger::getLayerSnapshotsForScreenshots(uint32_t rootLayerId, uint32_t u .globalShadowSettings = mDrawingState.globalShadowSettings, .supportsBlur = mSupportsBlur, .forceFullDamage = mForceFullDamage, - .parentCrop = {parentCrop}, + .parentCrop = parentCrop, .excludeLayerIds = std::move(excludeLayerIds), .supportedLayerGenericMetadata = getHwComposer().getSupportedLayerGenericMetadata(), diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 42d5db7cc9..74d00dd60c 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1382,7 +1382,7 @@ private: snapshotFilterFn); std::function>>()> getLayerSnapshotsForScreenshots( uint32_t rootLayerId, uint32_t uid, std::unordered_set excludeLayerIds, - bool childrenOnly, const FloatRect& parentCrop); + bool childrenOnly, const std::optional& optionalParentCrop); const sp mWindowInfosListenerInvoker; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp index c088e7b40c..4304259928 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -42,6 +43,7 @@ public: void invokeEffectLayer(); LayerCreationArgs createLayerCreationArgs(TestableSurfaceFlinger* flinger, sp client); Rect getFuzzedRect(); + ui::Transform getFuzzedTransform(); FrameTimelineInfo getFuzzedFrameTimelineInfo(); private: @@ -54,6 +56,12 @@ Rect LayerFuzzer::getFuzzedRect() { mFdp.ConsumeIntegral() /*bottom*/); } +ui::Transform LayerFuzzer::getFuzzedTransform() { + return ui::Transform(mFdp.ConsumeIntegral() /*orientation*/, + mFdp.ConsumeIntegral() /*width*/, + mFdp.ConsumeIntegral() /*height*/); +} + FrameTimelineInfo LayerFuzzer::getFuzzedFrameTimelineInfo() { FrameTimelineInfo ftInfo; ftInfo.vsyncId = mFdp.ConsumeIntegral(); @@ -166,7 +174,7 @@ void LayerFuzzer::invokeBufferStateLayer() { {mFdp.ConsumeIntegral(), mFdp.ConsumeIntegral()} /*reqSize*/, mFdp.PickValueInArray(kDataspaces), mFdp.ConsumeBool(), - mFdp.ConsumeBool()); + mFdp.ConsumeBool(), getFuzzedTransform(), getFuzzedRect()); layerArea.render([]() {} /*drawLayers*/); if (!ownsHandle) { -- GitLab From b76d99abc7c41c8215c73aa9e2bc4b99db3f1732 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Sun, 19 Mar 2023 18:22:31 -0700 Subject: [PATCH 1037/1310] [sf] Add support for transform hint in new front end Bug: 238781169 Test: atest VulkanPreTransformTest --rerun-until-failure Change-Id: Iececcce4101f1a8875ba20e2bfffad1775964ed1 --- .../surfaceflinger/FrontEnd/LayerSnapshot.h | 1 + .../FrontEnd/LayerSnapshotBuilder.cpp | 55 +++++++++++++------ services/surfaceflinger/Layer.cpp | 20 ++++--- services/surfaceflinger/Layer.h | 10 ++-- services/surfaceflinger/SurfaceFlinger.cpp | 6 ++ 5 files changed, 62 insertions(+), 30 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h index e22c279e0f..5491d9af17 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h @@ -89,6 +89,7 @@ struct LayerSnapshot : public compositionengine::LayerFECompositionState { gui::GameMode gameMode; scheduler::LayerInfo::FrameRate frameRate; ui::Transform::RotationFlags fixedTransformHint; + std::optional transformHint; bool handleSkipScreenshotFlag = false; int32_t frameRateSelectionPriority; LayerHierarchy::TraversalPath mirrorRootPath; diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index 2fc9682849..bdd4427d20 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -22,10 +22,15 @@ #include "LayerSnapshotBuilder.h" #include #include + #include +#include + +#include #include "DisplayHardware/HWC2.h" #include "DisplayHardware/Hal.h" #include "LayerLog.h" +#include "LayerSnapshotBuilder.h" #include "TimeStats/TimeStats.h" #include "ftl/small_map.h" @@ -649,12 +654,14 @@ void LayerSnapshotBuilder::resetRelativeState(LayerSnapshot& snapshot) { snapshot.relativeLayerMetadata.mMap.clear(); } -uint32_t getDisplayRotationFlags( - const display::DisplayMap& displays, - const ui::LayerStack& layerStack) { - static frontend::DisplayInfo sDefaultDisplayInfo = {.isPrimary = false}; - auto display = displays.get(layerStack).value_or(sDefaultDisplayInfo).get(); - return display.isPrimary ? display.rotationFlags : 0; +uint32_t getPrimaryDisplayRotationFlags( + const display::DisplayMap& displays) { + for (auto& [_, display] : displays) { + if (display.isPrimary) { + return display.rotationFlags; + } + } + return 0; } void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& args, @@ -681,8 +688,7 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a ? requested.layerStack : parentSnapshot.outputFilter.layerStack; - uint32_t displayRotationFlags = - getDisplayRotationFlags(args.displays, snapshot.outputFilter.layerStack); + uint32_t primaryDisplayRotationFlags = getPrimaryDisplayRotationFlags(args.displays); const bool forceUpdate = args.forceUpdate == ForceUpdateFlags::ALL || snapshot.changes.any(RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Created); @@ -696,7 +702,7 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a : Fence::NO_FENCE; snapshot.buffer = requested.externalTexture ? requested.externalTexture->getBuffer() : nullptr; - snapshot.bufferSize = requested.getBufferSize(displayRotationFlags); + snapshot.bufferSize = requested.getBufferSize(primaryDisplayRotationFlags); snapshot.geomBufferSize = snapshot.bufferSize; snapshot.croppedBufferSize = requested.getCroppedBufferSize(snapshot.bufferSize); snapshot.dataspace = requested.dataspace; @@ -751,15 +757,28 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE) ? requested.gameMode : parentSnapshot.gameMode; - snapshot.fixedTransformHint = requested.fixedTransformHint != ui::Transform::ROT_INVALID - ? requested.fixedTransformHint - : parentSnapshot.fixedTransformHint; // Display mirrors are always placed in a VirtualDisplay so we never want to capture layers // marked as skip capture snapshot.handleSkipScreenshotFlag = parentSnapshot.handleSkipScreenshotFlag || (requested.layerStackToMirror != ui::INVALID_LAYER_STACK); } + if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::AffectsChildren) || + args.displayChanges) { + snapshot.fixedTransformHint = requested.fixedTransformHint != ui::Transform::ROT_INVALID + ? requested.fixedTransformHint + : parentSnapshot.fixedTransformHint; + + if (snapshot.fixedTransformHint != ui::Transform::ROT_INVALID) { + snapshot.transformHint = snapshot.fixedTransformHint; + } else { + const auto display = args.displays.get(snapshot.outputFilter.layerStack); + snapshot.transformHint = display.has_value() + ? std::make_optional<>(display->get().transformHint) + : std::nullopt; + } + } + if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::FrameRate | RequestedLayerState::Changes::Hierarchy)) { @@ -799,7 +818,7 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Geometry)) { - updateLayerBounds(snapshot, requested, parentSnapshot, displayRotationFlags); + updateLayerBounds(snapshot, requested, parentSnapshot, primaryDisplayRotationFlags); updateRoundedCorner(snapshot, requested, parentSnapshot); } @@ -870,10 +889,10 @@ void LayerSnapshotBuilder::updateRoundedCorner(LayerSnapshot& snapshot, void LayerSnapshotBuilder::updateLayerBounds(LayerSnapshot& snapshot, const RequestedLayerState& requested, const LayerSnapshot& parentSnapshot, - uint32_t displayRotationFlags) { + uint32_t primaryDisplayRotationFlags) { snapshot.croppedBufferSize = requested.getCroppedBufferSize(snapshot.bufferSize); snapshot.geomCrop = requested.crop; - snapshot.localTransform = requested.getTransform(displayRotationFlags); + snapshot.localTransform = requested.getTransform(primaryDisplayRotationFlags); snapshot.localTransformInverse = snapshot.localTransform.inverse(); snapshot.geomLayerTransform = parentSnapshot.geomLayerTransform * snapshot.localTransform; const bool transformWasInvalid = snapshot.invalidTransform; @@ -887,14 +906,14 @@ void LayerSnapshotBuilder::updateLayerBounds(LayerSnapshot& snapshot, requestedT.dsdy(), requestedT.dtdx(), requestedT.dtdy()); std::string bufferDebug; if (requested.externalTexture) { - auto unRotBuffer = requested.getUnrotatedBufferSize(displayRotationFlags); + auto unRotBuffer = requested.getUnrotatedBufferSize(primaryDisplayRotationFlags); auto& destFrame = requested.destinationFrame; bufferDebug = base::StringPrintf(" buffer={%d,%d} displayRot=%d" " destFrame={%d,%d,%d,%d} unRotBuffer={%d,%d}", requested.externalTexture->getWidth(), requested.externalTexture->getHeight(), - displayRotationFlags, destFrame.left, destFrame.top, - destFrame.right, destFrame.bottom, + primaryDisplayRotationFlags, destFrame.left, + destFrame.top, destFrame.right, destFrame.bottom, unRotBuffer.getHeight(), unRotBuffer.getWidth()); } ALOGW("Resetting transform for %s because it is invalid.%s%s", diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 64f5c28b65..c1920bae50 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -1505,7 +1505,7 @@ void Layer::updateTransformHint(ui::Transform::RotationFlags transformHint) { transformHint = ui::Transform::ROT_0; } - setTransformHint(transformHint); + setTransformHintLegacy(transformHint); } // ---------------------------------------------------------------------------- @@ -2865,9 +2865,13 @@ void Layer::onSurfaceFrameCreated( void Layer::releasePendingBuffer(nsecs_t dequeueReadyTime) { for (const auto& handle : mDrawingState.callbackHandles) { - handle->transformHint = mSkipReportingTransformHint - ? std::nullopt - : std::make_optional(mTransformHint); + if (mFlinger->mLayerLifecycleManagerEnabled) { + handle->transformHint = mTransformHint; + } else { + handle->transformHint = mSkipReportingTransformHint + ? std::nullopt + : std::make_optional(mTransformHintLegacy); + } handle->dequeueReadyTime = dequeueReadyTime; handle->currentMaxAcquiredBufferCount = mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mOwnerUid); @@ -4027,10 +4031,10 @@ sp Layer::getBuffer() const { return mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getBuffer() : nullptr; } -void Layer::setTransformHint(ui::Transform::RotationFlags displayTransformHint) { - mTransformHint = getFixedTransformHint(); - if (mTransformHint == ui::Transform::ROT_INVALID) { - mTransformHint = displayTransformHint; +void Layer::setTransformHintLegacy(ui::Transform::RotationFlags displayTransformHint) { + mTransformHintLegacy = getFixedTransformHint(); + if (mTransformHintLegacy == ui::Transform::ROT_INVALID) { + mTransformHintLegacy = displayTransformHint; } mSkipReportingTransformHint = false; } diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index c91da3d3a8..552ea3701c 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -455,8 +455,6 @@ public: sp getBuffer() const; const std::shared_ptr& getExternalTexture() const; - ui::Transform::RotationFlags getTransformHint() const { return mTransformHint; } - /* * Returns if a frame is ready */ @@ -878,6 +876,9 @@ public: }; }; bool hasBuffer() const { return mBufferInfo.mBuffer != nullptr; } + void setTransformHint(std::optional transformHint) { + mTransformHint = transformHint; + } protected: // For unit tests @@ -1154,14 +1155,15 @@ private: float mBorderWidth; half4 mBorderColor; - void setTransformHint(ui::Transform::RotationFlags); + void setTransformHintLegacy(ui::Transform::RotationFlags); const uint32_t mTextureName; // Transform hint provided to the producer. This must be accessed holding // the mStateLock. - ui::Transform::RotationFlags mTransformHint = ui::Transform::ROT_0; + ui::Transform::RotationFlags mTransformHintLegacy = ui::Transform::ROT_0; bool mSkipReportingTransformHint = true; + std::optional mTransformHint = std::nullopt; ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID; uint64_t mPreviousReleasedFrameNumber = 0; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 5664d84976..076c6839c6 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5137,6 +5137,12 @@ uint32_t SurfaceFlinger::updateLayerCallbacksAndStats(const FrameTimelineInfo& f if (layer->setSidebandStream(s.sidebandStream)) flags |= eTraversalNeeded; } if (what & layer_state_t::eBufferChanged) { + std::optional transformHint = std::nullopt; + frontend::LayerSnapshot* snapshot = mLayerSnapshotBuilder.getSnapshot(layer->sequence); + if (snapshot) { + transformHint = snapshot->transformHint; + } + layer->setTransformHint(transformHint); if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime, desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp, frameTimelineInfo)) { -- GitLab From e9b71427c540c67b8855918b9bae9c16ac23672d Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Tue, 14 Mar 2023 16:54:44 +0000 Subject: [PATCH 1038/1310] Reset fingers and buttons in GestureConverter::reset Previously, calling reset didn't really reset anything, which could have resulted in an inconsistent event stream (e.g. fake fingers that were put down but never raised). Bug: 251196347 Bug: 259547750 Test: atest inputflinger_tests Test: scroll, swipe, and pinch on a touchpad, check dispatched events Change-Id: If95cde96f1974bf7c98009c68e2637686bff96cc --- .../reader/mapper/TouchpadInputMapper.cpp | 5 +- .../mapper/gestures/GestureConverter.cpp | 104 ++++++++++++++---- .../reader/mapper/gestures/GestureConverter.h | 6 +- .../tests/GestureConverter_test.cpp | 103 +++++++++++++++++ 4 files changed, 196 insertions(+), 22 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 4c56b055e4..5e63e48ac7 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -256,8 +256,9 @@ std::list TouchpadInputMapper::configure(nsecs_t when, std::list TouchpadInputMapper::reset(nsecs_t when) { mStateConverter.reset(); - mGestureConverter.reset(); - return InputMapper::reset(when); + std::list out = mGestureConverter.reset(when); + out += InputMapper::reset(when); + return out; } std::list TouchpadInputMapper::process(const RawEvent* rawEvent) { diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp index 707b1f3956..fd2be5fd79 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp @@ -72,8 +72,32 @@ std::string GestureConverter::dump() const { return out.str(); } -void GestureConverter::reset() { - mButtonState = 0; +std::list GestureConverter::reset(nsecs_t when) { + std::list out; + switch (mCurrentClassification) { + case MotionClassification::TWO_FINGER_SWIPE: + out.push_back(endScroll(when, when)); + break; + case MotionClassification::MULTI_FINGER_SWIPE: + out += handleMultiFingerSwipeLift(when, when); + break; + case MotionClassification::PINCH: + out += endPinch(when, when); + break; + case MotionClassification::NONE: + // When a button is pressed, the Gestures library always ends the current gesture, + // so we don't have to worry about the case where buttons need to be lifted during a + // pinch or swipe. + if (mButtonState) { + out += releaseAllButtons(when, when); + } + break; + default: + break; + } + mCurrentClassification = MotionClassification::NONE; + mDownTime = 0; + return out; } void GestureConverter::populateMotionRanges(InputDeviceInfo& info) const { @@ -219,6 +243,39 @@ std::list GestureConverter::handleButtonsChange(nsecs_t when, nsecs_ return out; } +std::list GestureConverter::releaseAllButtons(nsecs_t when, nsecs_t readTime) { + std::list out; + const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); + + PointerCoords coords; + coords.clear(); + coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); + coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0); + coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0); + const bool pointerDown = isPointerDown(mButtonState); + coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerDown ? 1.0f : 0.0f); + uint32_t newButtonState = mButtonState; + for (uint32_t button = AMOTION_EVENT_BUTTON_PRIMARY; button <= AMOTION_EVENT_BUTTON_FORWARD; + button <<= 1) { + if (mButtonState & button) { + newButtonState &= ~button; + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE, + button, newButtonState, /*pointerCount=*/1, + mFingerProps.data(), &coords, xCursorPosition, + yCursorPosition)); + } + } + if (pointerDown) { + coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f); + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0, + newButtonState, /*pointerCount=*/1, mFingerProps.data(), + &coords, xCursorPosition, yCursorPosition)); + } + mButtonState = 0; + return out; +} + std::list GestureConverter::handleScroll(nsecs_t when, nsecs_t readTime, const Gesture& gesture) { std::list out; @@ -264,6 +321,10 @@ NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const G return {}; } + return endScroll(when, readTime); +} + +NotifyArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) { const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, 0); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, 0); @@ -366,8 +427,6 @@ NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const G [[nodiscard]] std::list GestureConverter::handlePinch(nsecs_t when, nsecs_t readTime, const Gesture& gesture) { - std::list out; - const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); // Pinch gesture phases are reported a little differently from others, in that the same details @@ -391,6 +450,7 @@ NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const G mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); mDownTime = when; + std::list out; out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1, mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, @@ -405,19 +465,7 @@ NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const G } if (gesture.details.pinch.zoom_state == GESTURES_ZOOM_END) { - mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0); - out.push_back(makeMotionArgs(when, readTime, - AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT, - /* actionButton= */ 0, mButtonState, /* pointerCount= */ 2, - mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, - yCursorPosition)); - out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0, - mButtonState, /* pointerCount= */ 1, mFingerProps.data(), - mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); - mCurrentClassification = MotionClassification::NONE; - mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 0); - return out; + return endPinch(when, readTime); } mPinchFingerSeparation *= gesture.details.pinch.dz; @@ -429,9 +477,27 @@ NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const G mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition + mPinchFingerSeparation / 2); mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); - out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0, - mButtonState, /* pointerCount= */ 2, mFingerProps.data(), + return {makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /*actionButton=*/0, + mButtonState, /*pointerCount=*/2, mFingerProps.data(), + mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)}; +} + +std::list GestureConverter::endPinch(nsecs_t when, nsecs_t readTime) { + std::list out; + const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); + + mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0); + out.push_back(makeMotionArgs(when, readTime, + AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT, + /*actionButton=*/0, mButtonState, /*pointerCount=*/2, + mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, + yCursorPosition)); + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0, + mButtonState, /*pointerCount=*/1, mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); + mCurrentClassification = MotionClassification::NONE; + mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 0); return out; } diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h index a10dccece3..2714d03ea3 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h @@ -44,7 +44,7 @@ public: std::string dump() const; void setOrientation(ui::Rotation orientation) { mOrientation = orientation; } - void reset(); + [[nodiscard]] std::list reset(nsecs_t when); void populateMotionRanges(InputDeviceInfo& info) const; @@ -55,15 +55,19 @@ private: [[nodiscard]] NotifyArgs handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture); [[nodiscard]] std::list handleButtonsChange(nsecs_t when, nsecs_t readTime, const Gesture& gesture); + [[nodiscard]] std::list releaseAllButtons(nsecs_t when, nsecs_t readTime); [[nodiscard]] std::list handleScroll(nsecs_t when, nsecs_t readTime, const Gesture& gesture); [[nodiscard]] NotifyArgs handleFling(nsecs_t when, nsecs_t readTime, const Gesture& gesture); + [[nodiscard]] NotifyArgs endScroll(nsecs_t when, nsecs_t readTime); + [[nodiscard]] std::list handleMultiFingerSwipe(nsecs_t when, nsecs_t readTime, uint32_t fingerCount, float dx, float dy); [[nodiscard]] std::list handleMultiFingerSwipeLift(nsecs_t when, nsecs_t readTime); [[nodiscard]] std::list handlePinch(nsecs_t when, nsecs_t readTime, const Gesture& gesture); + [[nodiscard]] std::list endPinch(nsecs_t when, nsecs_t readTime); NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, int32_t actionButton, int32_t buttonState, diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp index bbf7e8e5a8..33f404d56b 100644 --- a/services/inputflinger/tests/GestureConverter_test.cpp +++ b/services/inputflinger/tests/GestureConverter_test.cpp @@ -785,4 +785,107 @@ TEST_F(GestureConverterTest, Pinch_ClearsClassificationAndScaleFactorAfterGestur WithGesturePinchScaleFactor(0, EPSILON))); } +TEST_F(GestureConverterTest, ResetWithButtonPressed) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, + /*down=*/GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT, + /*up=*/GESTURES_BUTTON_NONE, /*is_tap=*/false); + (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture); + + std::list args = converter.reset(ARBITRARY_TIME); + ASSERT_EQ(3u, args.size()); + + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + args.pop_front(); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0), + WithCoords(POINTER_X, POINTER_Y), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); +} + +TEST_F(GestureConverterTest, ResetDuringScroll) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); + (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + + std::list args = converter.reset(ARBITRARY_TIME); + ASSERT_EQ(1u, args.size()); + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithCoords(POINTER_X, POINTER_Y - 10), + WithGestureScrollDistance(0, 0, EPSILON), + WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), + WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))); +} + +TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0, + /*dy=*/10); + (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + + std::list args = converter.reset(ARBITRARY_TIME); + ASSERT_EQ(3u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON), + WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), + WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); +} + +TEST_F(GestureConverterTest, ResetDuringPinch) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, + GESTURES_ZOOM_START); + (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture); + + std::list args = converter.reset(ARBITRARY_TIME); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithMotionClassification(MotionClassification::PINCH), + WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); +} + } // namespace android -- GitLab From 9c1238df67d4b8619523e35f14e4f45d0d714687 Mon Sep 17 00:00:00 2001 From: John Reck Date: Tue, 21 Mar 2023 11:27:40 -0400 Subject: [PATCH 1039/1310] Remove unused code not implemented in gralloc5 Fixes: 261857910 Test: make Change-Id: If567ea91a9881c18f2d068ab1e0c9a37969d6a8a --- libs/ui/Gralloc4.cpp | 156 ----------------------- libs/ui/Gralloc5.cpp | 69 ---------- libs/ui/GraphicBufferMapper.cpp | 79 ------------ libs/ui/include/ui/Gralloc.h | 66 ---------- libs/ui/include/ui/Gralloc4.h | 36 ------ libs/ui/include/ui/Gralloc5.h | 54 -------- libs/ui/include/ui/GraphicBufferMapper.h | 42 ------ 7 files changed, 502 deletions(-) diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp index c3af996b13..b6274ab9c0 100644 --- a/libs/ui/Gralloc4.cpp +++ b/libs/ui/Gralloc4.cpp @@ -766,162 +766,6 @@ status_t Gralloc4Mapper::setSmpte2094_10(buffer_handle_t bufferHandle, gralloc4::encodeSmpte2094_10); } -template -status_t Gralloc4Mapper::getDefault(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - const MetadataType& metadataType, - DecodeFunction decodeFunction, T* outMetadata) const { - if (!outMetadata) { - return BAD_VALUE; - } - - IMapper::BufferDescriptorInfo descriptorInfo; - if (auto error = sBufferDescriptorInfo("getDefault", width, height, format, layerCount, usage, - &descriptorInfo) != OK) { - return error; - } - - hidl_vec vec; - Error error; - auto ret = mMapper->getFromBufferDescriptorInfo(descriptorInfo, metadataType, - [&](const auto& tmpError, - const hidl_vec& tmpVec) { - error = tmpError; - vec = tmpVec; - }); - - if (!ret.isOk()) { - error = kTransactionError; - } - - if (error != Error::NONE) { - ALOGE("getDefault(%s, %" PRIu64 ", ...) failed with %d", metadataType.name.c_str(), - metadataType.value, error); - return static_cast(error); - } - - return decodeFunction(vec, outMetadata); -} - -status_t Gralloc4Mapper::getDefaultPixelFormatFourCC(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - uint32_t* outPixelFormatFourCC) const { - return getDefault(width, height, format, layerCount, usage, - gralloc4::MetadataType_PixelFormatFourCC, gralloc4::decodePixelFormatFourCC, - outPixelFormatFourCC); -} - -status_t Gralloc4Mapper::getDefaultPixelFormatModifier(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - uint64_t* outPixelFormatModifier) const { - return getDefault(width, height, format, layerCount, usage, - gralloc4::MetadataType_PixelFormatModifier, - gralloc4::decodePixelFormatModifier, outPixelFormatModifier); -} - -status_t Gralloc4Mapper::getDefaultAllocationSize(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - uint64_t* outAllocationSize) const { - return getDefault(width, height, format, layerCount, usage, - gralloc4::MetadataType_AllocationSize, gralloc4::decodeAllocationSize, - outAllocationSize); -} - -status_t Gralloc4Mapper::getDefaultProtectedContent(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - uint64_t* outProtectedContent) const { - return getDefault(width, height, format, layerCount, usage, - gralloc4::MetadataType_ProtectedContent, gralloc4::decodeProtectedContent, - outProtectedContent); -} - -status_t Gralloc4Mapper::getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ExtendableType* outCompression) const { - return getDefault(width, height, format, layerCount, usage, gralloc4::MetadataType_Compression, - gralloc4::decodeCompression, outCompression); -} - -status_t Gralloc4Mapper::getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::Compression* outCompression) const { - if (!outCompression) { - return BAD_VALUE; - } - ExtendableType compression; - status_t error = getDefaultCompression(width, height, format, layerCount, usage, &compression); - if (error) { - return error; - } - if (!gralloc4::isStandardCompression(compression)) { - return BAD_TYPE; - } - *outCompression = gralloc4::getStandardCompressionValue(compression); - return NO_ERROR; -} - -status_t Gralloc4Mapper::getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ExtendableType* outInterlaced) const { - return getDefault(width, height, format, layerCount, usage, gralloc4::MetadataType_Interlaced, - gralloc4::decodeInterlaced, outInterlaced); -} - -status_t Gralloc4Mapper::getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::Interlaced* outInterlaced) const { - if (!outInterlaced) { - return BAD_VALUE; - } - ExtendableType interlaced; - status_t error = getDefaultInterlaced(width, height, format, layerCount, usage, &interlaced); - if (error) { - return error; - } - if (!gralloc4::isStandardInterlaced(interlaced)) { - return BAD_TYPE; - } - *outInterlaced = gralloc4::getStandardInterlacedValue(interlaced); - return NO_ERROR; -} - -status_t Gralloc4Mapper::getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ExtendableType* outChromaSiting) const { - return getDefault(width, height, format, layerCount, usage, gralloc4::MetadataType_ChromaSiting, - gralloc4::decodeChromaSiting, outChromaSiting); -} - -status_t Gralloc4Mapper::getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::ChromaSiting* outChromaSiting) const { - if (!outChromaSiting) { - return BAD_VALUE; - } - ExtendableType chromaSiting; - status_t error = - getDefaultChromaSiting(width, height, format, layerCount, usage, &chromaSiting); - if (error) { - return error; - } - if (!gralloc4::isStandardChromaSiting(chromaSiting)) { - return BAD_TYPE; - } - *outChromaSiting = gralloc4::getStandardChromaSitingValue(chromaSiting); - return NO_ERROR; -} - -status_t Gralloc4Mapper::getDefaultPlaneLayouts( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage, - std::vector* outPlaneLayouts) const { - return getDefault(width, height, format, layerCount, usage, gralloc4::MetadataType_PlaneLayouts, - gralloc4::decodePlaneLayouts, outPlaneLayouts); -} - std::vector Gralloc4Mapper::listSupportedMetadataTypes() const { hidl_vec descriptions; Error error; diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp index 6f196b86d5..514b45f0a5 100644 --- a/libs/ui/Gralloc5.cpp +++ b/libs/ui/Gralloc5.cpp @@ -831,73 +831,4 @@ status_t Gralloc5Mapper::setSmpte2094_10(buffer_handle_t bufferHandle, smpte2094_10); } -status_t Gralloc5Mapper::getDefaultPixelFormatFourCC(uint32_t, uint32_t, PixelFormat, uint32_t, - uint64_t, uint32_t *) const { - // TODO(b/261857910): Remove - return UNKNOWN_TRANSACTION; -} - -status_t Gralloc5Mapper::getDefaultPixelFormatModifier(uint32_t, uint32_t, PixelFormat, uint32_t, - uint64_t, uint64_t *) const { - // TODO(b/261857910): Remove - return UNKNOWN_TRANSACTION; -} - -status_t Gralloc5Mapper::getDefaultAllocationSize(uint32_t, uint32_t, PixelFormat, uint32_t, - uint64_t, uint64_t *) const { - // TODO(b/261857910): Remove - return UNKNOWN_TRANSACTION; -} - -status_t Gralloc5Mapper::getDefaultProtectedContent(uint32_t, uint32_t, PixelFormat, uint32_t, - uint64_t, uint64_t *) const { - // TODO(b/261857910): Remove - return UNKNOWN_TRANSACTION; -} - -status_t Gralloc5Mapper::getDefaultCompression( - uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t, - aidl::android::hardware::graphics::common::ExtendableType *) const { - // TODO(b/261857910): Remove - return UNKNOWN_TRANSACTION; -} - -status_t Gralloc5Mapper::getDefaultCompression(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t, - ui::Compression *) const { - // TODO(b/261857910): Remove - return UNKNOWN_TRANSACTION; -} - -status_t Gralloc5Mapper::getDefaultInterlaced( - uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t, - aidl::android::hardware::graphics::common::ExtendableType *) const { - // TODO(b/261857910): Remove - return UNKNOWN_TRANSACTION; -} - -status_t Gralloc5Mapper::getDefaultInterlaced(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t, - ui::Interlaced *) const { - // TODO(b/261857910): Remove - return UNKNOWN_TRANSACTION; -} - -status_t Gralloc5Mapper::getDefaultChromaSiting( - uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t, - aidl::android::hardware::graphics::common::ExtendableType *) const { - // TODO(b/261857910): Remove - return UNKNOWN_TRANSACTION; -} - -status_t Gralloc5Mapper::getDefaultChromaSiting(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t, - ui::ChromaSiting *) const { - // TODO(b/261857910): Remove - return UNKNOWN_TRANSACTION; -} - -status_t Gralloc5Mapper::getDefaultPlaneLayouts(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t, - std::vector *) const { - // TODO(b/261857910): Remove - return UNKNOWN_TRANSACTION; -} - } // namespace android \ No newline at end of file diff --git a/libs/ui/GraphicBufferMapper.cpp b/libs/ui/GraphicBufferMapper.cpp index 6002a6d29e..7086e0470c 100644 --- a/libs/ui/GraphicBufferMapper.cpp +++ b/libs/ui/GraphicBufferMapper.cpp @@ -341,84 +341,5 @@ status_t GraphicBufferMapper::setSmpte2094_10(buffer_handle_t bufferHandle, return mMapper->setSmpte2094_10(bufferHandle, smpte2094_10); } -status_t GraphicBufferMapper::getDefaultPixelFormatFourCC(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - uint32_t* outPixelFormatFourCC) { - return mMapper->getDefaultPixelFormatFourCC(width, height, format, layerCount, usage, - outPixelFormatFourCC); -} - -status_t GraphicBufferMapper::getDefaultPixelFormatModifier(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - uint64_t* outPixelFormatModifier) { - return mMapper->getDefaultPixelFormatModifier(width, height, format, layerCount, usage, - outPixelFormatModifier); -} - -status_t GraphicBufferMapper::getDefaultAllocationSize(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - uint64_t* outAllocationSize) { - return mMapper->getDefaultAllocationSize(width, height, format, layerCount, usage, - outAllocationSize); -} - -status_t GraphicBufferMapper::getDefaultProtectedContent(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - uint64_t* outProtectedContent) { - return mMapper->getDefaultProtectedContent(width, height, format, layerCount, usage, - outProtectedContent); -} - -status_t GraphicBufferMapper::getDefaultCompression( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType* outCompression) { - return mMapper->getDefaultCompression(width, height, format, layerCount, usage, outCompression); -} - -status_t GraphicBufferMapper::getDefaultCompression(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - ui::Compression* outCompression) { - return mMapper->getDefaultCompression(width, height, format, layerCount, usage, outCompression); -} - -status_t GraphicBufferMapper::getDefaultInterlaced( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType* outInterlaced) { - return mMapper->getDefaultInterlaced(width, height, format, layerCount, usage, outInterlaced); -} - -status_t GraphicBufferMapper::getDefaultInterlaced(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, ui::Interlaced* outInterlaced) { - return mMapper->getDefaultInterlaced(width, height, format, layerCount, usage, outInterlaced); -} - -status_t GraphicBufferMapper::getDefaultChromaSiting( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType* outChromaSiting) { - return mMapper->getDefaultChromaSiting(width, height, format, layerCount, usage, - outChromaSiting); -} - -status_t GraphicBufferMapper::getDefaultChromaSiting(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - ui::ChromaSiting* outChromaSiting) { - return mMapper->getDefaultChromaSiting(width, height, format, layerCount, usage, - outChromaSiting); -} - -status_t GraphicBufferMapper::getDefaultPlaneLayouts( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage, - std::vector* outPlaneLayouts) { - return mMapper->getDefaultPlaneLayouts(width, height, format, layerCount, usage, - outPlaneLayouts); -} - // --------------------------------------------------------------------------- }; // namespace android diff --git a/libs/ui/include/ui/Gralloc.h b/libs/ui/include/ui/Gralloc.h index b494cbe4fa..496ba57789 100644 --- a/libs/ui/include/ui/Gralloc.h +++ b/libs/ui/include/ui/Gralloc.h @@ -200,72 +200,6 @@ public: std::optional> /*smpte2094_10*/) const { return INVALID_OPERATION; } - virtual status_t getDefaultPixelFormatFourCC(uint32_t /*width*/, uint32_t /*height*/, - PixelFormat /*format*/, uint32_t /*layerCount*/, - uint64_t /*usage*/, - uint32_t* /*outPixelFormatFourCC*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultPixelFormatModifier(uint32_t /*width*/, uint32_t /*height*/, - PixelFormat /*format*/, uint32_t /*layerCount*/, - uint64_t /*usage*/, - uint64_t* /*outPixelFormatModifier*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultAllocationSize(uint32_t /*width*/, uint32_t /*height*/, - PixelFormat /*format*/, uint32_t /*layerCount*/, - uint64_t /*usage*/, - uint64_t* /*outAllocationSize*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultProtectedContent(uint32_t /*width*/, uint32_t /*height*/, - PixelFormat /*format*/, uint32_t /*layerCount*/, - uint64_t /*usage*/, - uint64_t* /*outProtectedContent*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultCompression( - uint32_t /*width*/, uint32_t /*height*/, PixelFormat /*format*/, - uint32_t /*layerCount*/, uint64_t /*usage*/, - aidl::android::hardware::graphics::common::ExtendableType* /*outCompression*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultCompression(uint32_t /*width*/, uint32_t /*height*/, - PixelFormat /*format*/, uint32_t /*layerCount*/, - uint64_t /*usage*/, - ui::Compression* /*outCompression*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultInterlaced( - uint32_t /*width*/, uint32_t /*height*/, PixelFormat /*format*/, - uint32_t /*layerCount*/, uint64_t /*usage*/, - aidl::android::hardware::graphics::common::ExtendableType* /*outInterlaced*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultInterlaced(uint32_t /*width*/, uint32_t /*height*/, - PixelFormat /*format*/, uint32_t /*layerCount*/, - uint64_t /*usage*/, - ui::Interlaced* /*outInterlaced*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultChromaSiting( - uint32_t /*width*/, uint32_t /*height*/, PixelFormat /*format*/, - uint32_t /*layerCount*/, uint64_t /*usage*/, - aidl::android::hardware::graphics::common::ExtendableType* /*outChromaSiting*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultChromaSiting(uint32_t /*width*/, uint32_t /*height*/, - PixelFormat /*format*/, uint32_t /*layerCount*/, - uint64_t /*usage*/, - ui::ChromaSiting* /*outChromaSiting*/) const { - return INVALID_OPERATION; - } - virtual status_t getDefaultPlaneLayouts( - uint32_t /*width*/, uint32_t /*height*/, PixelFormat /*format*/, - uint32_t /*layerCount*/, uint64_t /*usage*/, - std::vector* /*outPlaneLayouts*/) const { - return INVALID_OPERATION; - } }; // A wrapper to IAllocator diff --git a/libs/ui/include/ui/Gralloc4.h b/libs/ui/include/ui/Gralloc4.h index 6bc5ce5296..df43be87cd 100644 --- a/libs/ui/include/ui/Gralloc4.h +++ b/libs/ui/include/ui/Gralloc4.h @@ -120,42 +120,6 @@ public: std::optional>* outSmpte2094_10) const override; status_t setSmpte2094_10(buffer_handle_t bufferHandle, std::optional> smpte2094_10) const override; - status_t getDefaultPixelFormatFourCC(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - uint32_t* outPixelFormatFourCC) const override; - status_t getDefaultPixelFormatModifier(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - uint64_t* outPixelFormatModifier) const override; - status_t getDefaultAllocationSize(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - uint64_t* outAllocationSize) const override; - status_t getDefaultProtectedContent(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - uint64_t* outProtectedContent) const override; - status_t getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType* - outCompression) const override; - status_t getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::Compression* outCompression) const override; - status_t getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType* - outInterlaced) const override; - status_t getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::Interlaced* outInterlaced) const override; - status_t getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType* - outChromaSiting) const override; - status_t getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::ChromaSiting* outChromaSiting) const override; - status_t getDefaultPlaneLayouts(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - std::vector* outPlaneLayouts) const override; std::vector listSupportedMetadataTypes() const; diff --git a/libs/ui/include/ui/Gralloc5.h b/libs/ui/include/ui/Gralloc5.h index bc1016944a..44b97d1a6f 100644 --- a/libs/ui/include/ui/Gralloc5.h +++ b/libs/ui/include/ui/Gralloc5.h @@ -156,60 +156,6 @@ public: buffer_handle_t bufferHandle, std::optional> smpte2094_10) const override; - [[nodiscard]] status_t getDefaultPixelFormatFourCC( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, - uint64_t usage, uint32_t *outPixelFormatFourCC) const override; - - [[nodiscard]] status_t getDefaultPixelFormatModifier( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, - uint64_t usage, uint64_t *outPixelFormatModifier) const override; - - [[nodiscard]] status_t getDefaultAllocationSize(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - uint64_t *outAllocationSize) const override; - - [[nodiscard]] status_t getDefaultProtectedContent(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - uint64_t *outProtectedContent) const override; - - [[nodiscard]] status_t getDefaultCompression( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, - uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType *outCompression) - const override; - - [[nodiscard]] status_t getDefaultCompression(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - ui::Compression *outCompression) const override; - - [[nodiscard]] status_t getDefaultInterlaced( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, - uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType *outInterlaced) - const override; - - [[nodiscard]] status_t getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::Interlaced *outInterlaced) const override; - - [[nodiscard]] status_t getDefaultChromaSiting( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, - uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType *outChromaSiting) - const override; - - [[nodiscard]] status_t getDefaultChromaSiting(uint32_t width, uint32_t height, - PixelFormat format, uint32_t layerCount, - uint64_t usage, - ui::ChromaSiting *outChromaSiting) const override; - - [[nodiscard]] status_t getDefaultPlaneLayouts( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, - uint64_t usage, std::vector *outPlaneLayouts) const override; - private: void unlockBlocking(buffer_handle_t bufferHandle) const; diff --git a/libs/ui/include/ui/GraphicBufferMapper.h b/libs/ui/include/ui/GraphicBufferMapper.h index 51c6e92f43..3a5167ab25 100644 --- a/libs/ui/include/ui/GraphicBufferMapper.h +++ b/libs/ui/include/ui/GraphicBufferMapper.h @@ -138,48 +138,6 @@ public: status_t setSmpte2094_10(buffer_handle_t bufferHandle, std::optional> smpte2094_10); - /** - * Gets the default metadata for a gralloc buffer allocated with the given parameters. - * - * These functions are supported by gralloc 4.0+. - */ - status_t getDefaultPixelFormatFourCC(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - uint32_t* outPixelFormatFourCC); - status_t getDefaultPixelFormatModifier(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - uint64_t* outPixelFormatModifier); - status_t getDefaultAllocationSize(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - uint64_t* outAllocationSize); - status_t getDefaultProtectedContent(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - uint64_t* outProtectedContent); - status_t getDefaultCompression( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, - uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType* outCompression); - status_t getDefaultCompression(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::Compression* outCompression); - status_t getDefaultInterlaced( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, - uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType* outInterlaced); - status_t getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::Interlaced* outInterlaced); - status_t getDefaultChromaSiting( - uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, - uint64_t usage, - aidl::android::hardware::graphics::common::ExtendableType* outChromaSiting); - status_t getDefaultChromaSiting(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - ui::ChromaSiting* outChromaSiting); - status_t getDefaultPlaneLayouts(uint32_t width, uint32_t height, PixelFormat format, - uint32_t layerCount, uint64_t usage, - std::vector* outPlaneLayouts); - const GrallocMapper& getGrallocMapper() const { return reinterpret_cast(*mMapper); } -- GitLab From 5fb717d92ef2692e649ca99ca406a813754cdd08 Mon Sep 17 00:00:00 2001 From: Rachel Lee Date: Tue, 21 Mar 2023 13:05:04 -0700 Subject: [PATCH 1040/1310] Add rnlee, ramindani to SF OWNERS. Bug: 255664414 Test: n/a (cherry picked from https://android-review.googlesource.com/q/commit:690c3ec156860a9626dfa939429cd58d32ffcc40) Merged-In: Idec2e24581d6aeac127c165bd7a6d51d1defa025 Change-Id: Idec2e24581d6aeac127c165bd7a6d51d1defa025 --- services/surfaceflinger/OWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/surfaceflinger/OWNERS b/services/surfaceflinger/OWNERS index 6011d0d0f7..4e7da829f2 100644 --- a/services/surfaceflinger/OWNERS +++ b/services/surfaceflinger/OWNERS @@ -4,5 +4,7 @@ chaviw@google.com lpy@google.com pdwilliams@google.com racarr@google.com +ramindani@google.com +rnlee@google.com scroggo@google.com vishnun@google.com -- GitLab From 3494fd5fa1a694050e9a482e715b3c4d8f52525e Mon Sep 17 00:00:00 2001 From: Vladimir Komsiyski Date: Wed, 22 Mar 2023 10:31:22 +0100 Subject: [PATCH 1041/1310] Fix a typo Test: n/a Bug: n/a Change-Id: I2bfef7855cc42002cb542f35c479d158d61aa615 --- services/sensorservice/SensorService.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp index 0fb3cadc63..398d60242b 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -1648,7 +1648,7 @@ int SensorService::configureRuntimeSensorDirectChannel( int deviceId = c->getDeviceId(); int sensorDeviceId = getDeviceIdFromHandle(sensorHandle); if (sensorDeviceId != c->getDeviceId()) { - ALOGE("Cannot configure direct channel created for device %d with a sensor that belongs" + ALOGE("Cannot configure direct channel created for device %d with a sensor that belongs " "to device %d", c->getDeviceId(), sensorDeviceId); return BAD_VALUE; } -- GitLab From 9892a3000db1dc71aed6eb371dc5c7a0d8a42e22 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Wed, 22 Mar 2023 15:38:10 +0000 Subject: [PATCH 1042/1310] Fix touchpad palm filtering logic When I added this logic [0], I was under the mistaken impression that the Gesture library required touches that were leaving the pad to be reported as FingerStates with a tracking ID of -1. It turns out that it doesn't, and in fact such FingerStates shouldn't be sent to it [1]. This makes the palm filtering logic simpler, since we can just always ignore a slot if it has the palm tool type, rather than having to make one last FingerState for it if was previously a finger. [0]: Change ID I7d898889d4bf4e8068194d22150c02a69b59c435 [1]: https://crrev.com/c/4360158 Bug: 251196347 Bug: 270041770 Test: atest inputflinger_tests:HardwareStateConverterTest Test: limited manual testing Change-Id: I7f70f1b677ba19a472fd1670918a82dfe65c6f39 --- .../gestures/HardwareStateConverter.cpp | 20 ++++++------------- .../mapper/gestures/HardwareStateConverter.h | 1 - .../tests/HardwareStateConverter_test.cpp | 3 +-- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp index c091a51386..d344babe72 100644 --- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp @@ -14,6 +14,9 @@ * limitations under the License. */ +// clang-format off +#include "../Macros.h" +// clang-format on #include "gestures/HardwareStateConverter.h" #include @@ -80,14 +83,9 @@ SelfContainedHardwareState HardwareStateConverter::produceHardwareState(nsecs_t schs.fingers.clear(); for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) { MultiTouchMotionAccumulator::Slot slot = mMotionAccumulator.getSlot(i); - if (!slot.isInUse()) { - continue; - } // Some touchpads continue to report contacts even after they've identified them as palms. - // We want to exclude these contacts from the HardwareStates, but still need to report a - // tracking ID of -1 if a finger turns into a palm. - const bool isPalm = slot.getToolType() == AMOTION_EVENT_TOOL_TYPE_PALM; - if (isPalm && mFingerSlots.find(i) == mFingerSlots.end()) { + // We want to exclude these contacts from the HardwareStates. + if (!slot.isInUse() || slot.getToolType() == AMOTION_EVENT_TOOL_TYPE_PALM) { continue; } @@ -101,12 +99,7 @@ SelfContainedHardwareState HardwareStateConverter::produceHardwareState(nsecs_t fingerState.orientation = slot.getOrientation(); fingerState.position_x = slot.getX(); fingerState.position_y = slot.getY(); - fingerState.tracking_id = isPalm ? -1 : slot.getTrackingId(); - if (fingerState.tracking_id == -1) { - mFingerSlots.erase(i); - } else { - mFingerSlots.insert(i); - } + fingerState.tracking_id = slot.getTrackingId(); } schs.state.fingers = schs.fingers.data(); schs.state.finger_cnt = schs.fingers.size(); @@ -117,7 +110,6 @@ SelfContainedHardwareState HardwareStateConverter::produceHardwareState(nsecs_t void HardwareStateConverter::reset() { mCursorButtonAccumulator.reset(mDeviceContext); mTouchButtonAccumulator.reset(); - mFingerSlots.clear(); mMscTimestamp = 0; } diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h index d6787b7e3f..c314b0d771 100644 --- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h +++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h @@ -54,7 +54,6 @@ private: MultiTouchMotionAccumulator mMotionAccumulator; TouchButtonAccumulator mTouchButtonAccumulator; int32_t mMscTimestamp = 0; - std::set mFingerSlots; }; } // namespace android diff --git a/services/inputflinger/tests/HardwareStateConverter_test.cpp b/services/inputflinger/tests/HardwareStateConverter_test.cpp index 36b9bab14e..3e9724100c 100644 --- a/services/inputflinger/tests/HardwareStateConverter_test.cpp +++ b/services/inputflinger/tests/HardwareStateConverter_test.cpp @@ -231,8 +231,7 @@ TEST_F(HardwareStateConverterTest, OneFingerTurningIntoAPalm) { schs = processSync(conv, time); ASSERT_TRUE(schs.has_value()); - ASSERT_EQ(1, schs->state.finger_cnt); - EXPECT_EQ(-1, schs->state.fingers[0].tracking_id); + ASSERT_EQ(0, schs->state.finger_cnt); processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 53); processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 97); -- GitLab From 43edfadd52fe7f6dd13c4af00622a9a95d21b4b1 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Wed, 15 Mar 2023 15:27:33 -0500 Subject: [PATCH 1043/1310] SF: throttle WindowInfosListener calls This change updates WindowInfosListenerInvoker to delay and drop messages when there are unacked messages. When WindowInfosListener calls are acknowledged before the next windowInfosChanged call, there is no behavior change. If windowInfosChanged is called and there are unacked messages, then the update is delayed and sent once the messages are acked via WindowInfosReportedListener. If windowInfosChanged is called and there is already a delayed update, then the previous delayed update is overwritten and only the latest update is sent. WindowInfosListeners are still called immediately when there are focus requests. This means the number of unacked messages may be greater than one. Bug: 270894765 Test: presubmits Test: manual fuzz testing (random sleeps added to input flinger listener) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:a364bd7c0ecc412582eff1160368f3eaa2b4c6f0) Merged-In: I78b39b10c52f4e4939f1741516d0edbe9898ef3c Change-Id: I78b39b10c52f4e4939f1741516d0edbe9898ef3c --- services/surfaceflinger/SurfaceFlinger.cpp | 7 +- .../WindowInfosListenerInvoker.cpp | 108 +++++++++++------- .../WindowInfosListenerInvoker.h | 24 ++-- 3 files changed, 89 insertions(+), 50 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index c9d17d5b27..b81c9da34f 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3724,8 +3724,11 @@ void SurfaceFlinger::updateInputFlinger() { ATRACE_NAME("BackgroundExecutor::updateInputFlinger"); if (updateWindowInfo) { mWindowInfosListenerInvoker - ->windowInfosChanged(windowInfos, displayInfos, - inputWindowCommands.windowInfosReportedListeners); + ->windowInfosChanged(std::move(windowInfos), std::move(displayInfos), + std::move( + inputWindowCommands.windowInfosReportedListeners), + /* forceImmediateCall= */ + !inputWindowCommands.focusRequests.empty()); } else { // If there are listeners but no changes to input windows, call the listeners // immediately. diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp index 292083b9bc..73a7cae6d6 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp +++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp @@ -25,20 +25,14 @@ using gui::DisplayInfo; using gui::IWindowInfosListener; using gui::WindowInfo; -struct WindowInfosListenerInvoker::WindowInfosReportedListener : gui::BnWindowInfosReportedListener, - DeathRecipient { - explicit WindowInfosReportedListener( - size_t callbackCount, - const std::unordered_set, - SpHash>& - windowInfosReportedListeners) +struct WindowInfosReportedListenerInvoker : gui::BnWindowInfosReportedListener, + IBinder::DeathRecipient { + WindowInfosReportedListenerInvoker(size_t callbackCount, + WindowInfosReportedListenerSet windowInfosReportedListeners) : mCallbacksPending(callbackCount), - mWindowInfosReportedListeners(windowInfosReportedListeners) {} + mWindowInfosReportedListeners(std::move(windowInfosReportedListeners)) {} binder::Status onWindowInfosReported() override { - // TODO(b/222421815) There could potentially be callbacks that we don't need to wait for - // before calling the WindowInfosReportedListeners coming from InputWindowCommands. Filter - // the list of callbacks down to those from system server. if (--mCallbacksPending == 0) { for (const auto& listener : mWindowInfosReportedListeners) { sp asBinder = IInterface::asBinder(listener); @@ -54,9 +48,7 @@ struct WindowInfosListenerInvoker::WindowInfosReportedListener : gui::BnWindowIn private: std::atomic mCallbacksPending; - std::unordered_set, - SpHash> - mWindowInfosReportedListeners; + WindowInfosReportedListenerSet mWindowInfosReportedListeners; }; void WindowInfosListenerInvoker::addWindowInfosListener(sp listener) { @@ -82,38 +74,76 @@ void WindowInfosListenerInvoker::binderDied(const wp& who) { } void WindowInfosListenerInvoker::windowInfosChanged( - const std::vector& windowInfos, const std::vector& displayInfos, - const std::unordered_set, - SpHash>& - windowInfosReportedListeners) { - ftl::SmallVector, kStaticCapacity> windowInfosListeners; + std::vector windowInfos, std::vector displayInfos, + WindowInfosReportedListenerSet reportedListeners, bool forceImmediateCall) { + reportedListeners.insert(sp::fromExisting(this)); + auto callListeners = [this, windowInfos = std::move(windowInfos), + displayInfos = std::move(displayInfos), + reportedListeners = std::move(reportedListeners)]() mutable { + ftl::SmallVector, kStaticCapacity> windowInfosListeners; + { + std::scoped_lock lock(mListenersMutex); + for (const auto& [_, listener] : mWindowInfosListeners) { + windowInfosListeners.push_back(listener); + } + } + + auto reportedInvoker = + sp::make(windowInfosListeners.size(), + std::move(reportedListeners)); + + for (const auto& listener : windowInfosListeners) { + sp asBinder = IInterface::asBinder(listener); + + // linkToDeath is used here to ensure that the windowInfosReportedListeners + // are called even if one of the windowInfosListeners dies before + // calling onWindowInfosReported. + asBinder->linkToDeath(reportedInvoker); + + auto status = + listener->onWindowInfosChanged(windowInfos, displayInfos, reportedInvoker); + if (!status.isOk()) { + reportedInvoker->onWindowInfosReported(); + } + } + }; + { - std::scoped_lock lock(mListenersMutex); - for (const auto& [_, listener] : mWindowInfosListeners) { - windowInfosListeners.push_back(listener); + std::scoped_lock lock(mMessagesMutex); + // If there are unacked messages and this isn't a forced call, then return immediately. + // If a forced window infos change doesn't happen first, the update will be sent after + // the WindowInfosReportedListeners are called. If a forced window infos change happens or + // if there are subsequent delayed messages before this update is sent, then this message + // will be dropped and the listeners will only be called with the latest info. This is done + // to reduce the amount of binder memory used. + if (mActiveMessageCount > 0 && !forceImmediateCall) { + mWindowInfosChangedDelayed = std::move(callListeners); + return; } + + mWindowInfosChangedDelayed = nullptr; + mActiveMessageCount++; } + callListeners(); +} - auto windowInfosReportedListener = windowInfosReportedListeners.empty() - ? nullptr - : sp::make(windowInfosListeners.size(), - windowInfosReportedListeners); - for (const auto& listener : windowInfosListeners) { - sp asBinder = IInterface::asBinder(listener); - - // linkToDeath is used here to ensure that the windowInfosReportedListeners - // are called even if one of the windowInfosListeners dies before - // calling onWindowInfosReported. - if (windowInfosReportedListener) { - asBinder->linkToDeath(windowInfosReportedListener); - } +binder::Status WindowInfosListenerInvoker::onWindowInfosReported() { + std::function callListeners; - auto status = listener->onWindowInfosChanged(windowInfos, displayInfos, - windowInfosReportedListener); - if (windowInfosReportedListener && !status.isOk()) { - windowInfosReportedListener->onWindowInfosReported(); + { + std::scoped_lock lock{mMessagesMutex}; + mActiveMessageCount--; + if (!mWindowInfosChangedDelayed || mActiveMessageCount > 0) { + return binder::Status::ok(); } + + mActiveMessageCount++; + callListeners = std::move(mWindowInfosChangedDelayed); + mWindowInfosChangedDelayed = nullptr; } + + callListeners(); + return binder::Status::ok(); } } // namespace android diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h index d60a9c4157..bfe036e025 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.h +++ b/services/surfaceflinger/WindowInfosListenerInvoker.h @@ -23,34 +23,40 @@ #include #include #include +#include #include namespace android { -class SurfaceFlinger; +using WindowInfosReportedListenerSet = + std::unordered_set, + gui::SpHash>; -class WindowInfosListenerInvoker : public IBinder::DeathRecipient { +class WindowInfosListenerInvoker : public gui::BnWindowInfosReportedListener, + public IBinder::DeathRecipient { public: void addWindowInfosListener(sp); void removeWindowInfosListener(const sp& windowInfosListener); - void windowInfosChanged(const std::vector&, - const std::vector&, - const std::unordered_set, - SpHash>& - windowInfosReportedListeners); + void windowInfosChanged(std::vector, std::vector, + WindowInfosReportedListenerSet windowInfosReportedListeners, + bool forceImmediateCall); + + binder::Status onWindowInfosReported() override; protected: void binderDied(const wp& who) override; private: - struct WindowInfosReportedListener; - std::mutex mListenersMutex; static constexpr size_t kStaticCapacity = 3; ftl::SmallMap, const sp, kStaticCapacity> mWindowInfosListeners GUARDED_BY(mListenersMutex); + + std::mutex mMessagesMutex; + uint32_t mActiveMessageCount GUARDED_BY(mMessagesMutex) = 0; + std::function mWindowInfosChangedDelayed GUARDED_BY(mMessagesMutex); }; } // namespace android -- GitLab From 20f74e8e05e4f123b6c322732e28aa3cb111318a Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Wed, 22 Mar 2023 18:47:30 +0000 Subject: [PATCH 1044/1310] remove unused vts-core-tradefed harness Bug: 274790216 Test: atest GpuServiceVendorTests Change-Id: Idc9c98807122a6e3b419ba97027db33d9683b669 --- services/gpuservice/vts/Android.bp | 1 - 1 file changed, 1 deletion(-) diff --git a/services/gpuservice/vts/Android.bp b/services/gpuservice/vts/Android.bp index f4ea4408b6..a24822a7d1 100644 --- a/services/gpuservice/vts/Android.bp +++ b/services/gpuservice/vts/Android.bp @@ -21,7 +21,6 @@ java_test_host { srcs: ["src/**/*.java"], libs: [ "tradefed", - "vts-core-tradefed-harness", "compatibility-host-util", ], test_suites: [ -- GitLab From c1cf458c36a96340c3e3e111a158677ed978e858 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Thu, 23 Mar 2023 18:37:44 -0400 Subject: [PATCH 1045/1310] Remove references to MULTI_THREADED_PRESENT We never take advantage of this, so remove it until we do. Bug: 274954820 Test: bouncy ball on DCD Change-Id: I5dc03b2a7b48f16a326eab6676ae2114d9f70c01 --- services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp index bd2680fbb5..f28bfd44cd 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp @@ -1547,6 +1547,8 @@ void AidlComposer::onHotplugDisconnect(Display display) { } bool AidlComposer::hasMultiThreadedPresentSupport(Display display) { +#if 0 + // TODO (b/259132483): Reenable const auto displayId = translate(display); std::vector capabilities; const auto status = mAidlComposerClient->getDisplayCapabilities(displayId, &capabilities); @@ -1556,6 +1558,10 @@ bool AidlComposer::hasMultiThreadedPresentSupport(Display display) { } return std::find(capabilities.begin(), capabilities.end(), AidlDisplayCapability::MULTI_THREADED_PRESENT) != capabilities.end(); +#else + (void) display; + return false; +#endif } void AidlComposer::addReader(Display display) { -- GitLab From bb9ff7f5b943214c6946af113c9d55b28ccb15ce Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Wed, 22 Mar 2023 13:27:19 -0700 Subject: [PATCH 1046/1310] Temp fix for overdimming issue in silkFX. - disable caching if hdr/sdr ratio > 1.01f, which we uses the extended brightness api. Bug: 273624686 Test: play HDR video in silkFX and make sure no overdimming obeserved; atest libcompositionengine_test Change-Id: Iae53c5928e4f91b4b73f9e4ee9dd7bb052e86090 --- .../impl/planner/LayerState.h | 4 ++++ .../src/planner/CachedSet.cpp | 4 ++++ .../tests/planner/CachedSetTest.cpp | 20 +++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h index d5c488e40e..ee9e646c1f 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h @@ -247,6 +247,10 @@ public: ui::Dataspace getDataspace() const { return mOutputDataspace.get(); } + float getHdrSdrRatio() const { + return getOutputLayer()->getLayerFE().getCompositionState()->currentSdrHdrRatio; + }; + wp getBuffer() const { return mBuffer.get(); } bool isProtected() const { return mIsProtected.get(); } diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp index a00ce57e29..8ced0aca36 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp @@ -378,6 +378,10 @@ bool CachedSet::hasUnsupportedDataspace() const { // to avoid flickering/color differences. return true; } + // TODO(b/274804887): temp fix of overdimming issue, skip caching if hsdr/sdr ratio > 1.01f + if (layer.getState()->getHdrSdrRatio() > 1.01f) { + return true; + } return false; }); } diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp index ca5ba69e8e..1f7fb93c6b 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp @@ -662,6 +662,26 @@ TEST_F(CachedSetTest, holePunch_requiresNonBT601_625) { EXPECT_FALSE(cachedSet.requiresHolePunch()); } +TEST_F(CachedSetTest, holePunch_requiresNonHdrWithExtendedBrightness) { + const auto dataspace = static_cast(ui::Dataspace::STANDARD_DCI_P3 | + ui::Dataspace::TRANSFER_SRGB | + ui::Dataspace::RANGE_EXTENDED); + mTestLayers[0]->outputLayerCompositionState.dataspace = dataspace; + mTestLayers[0]->layerFECompositionState.currentSdrHdrRatio = 5.f; + mTestLayers[0]->layerState->update(&mTestLayers[0]->outputLayer); + + CachedSet::Layer& layer = *mTestLayers[0]->cachedSetLayer.get(); + auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState; + layerFECompositionState.buffer = sp::make(); + layerFECompositionState.blendMode = hal::BlendMode::NONE; + sp layerFE = mTestLayers[0]->layerFE; + + CachedSet cachedSet(layer); + EXPECT_CALL(*layerFE, hasRoundedCorners()).WillRepeatedly(Return(true)); + + EXPECT_FALSE(cachedSet.requiresHolePunch()); +} + TEST_F(CachedSetTest, holePunch_requiresNoBlending) { CachedSet::Layer& layer = *mTestLayers[0]->cachedSetLayer.get(); auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState; -- GitLab From 963049b5ff733bc35933d5c565a35f2f7af09f88 Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Thu, 23 Mar 2023 14:06:21 -0700 Subject: [PATCH 1047/1310] Rename Sdr/Hdr to Hdr/Sdr internally to get avoid of confusion. Bug: 267350616 Test: builds Change-Id: I75bb485f01ae2358c77133767f01cb63f8eda6f1 --- libs/gui/LayerState.cpp | 16 +++++++------- libs/gui/SurfaceComposerClient.cpp | 4 ++-- libs/gui/include/gui/LayerState.h | 4 ++-- .../LayerFECompositionState.h | 4 ++-- .../impl/planner/LayerState.h | 2 +- .../src/LayerFECompositionState.cpp | 6 ++--- .../CompositionEngine/src/OutputLayer.cpp | 4 ++-- .../tests/planner/CachedSetTest.cpp | 2 +- .../FrontEnd/LayerSnapshotBuilder.cpp | 4 ++-- .../FrontEnd/RequestedLayerState.cpp | 4 ++-- services/surfaceflinger/Layer.cpp | 22 +++++++++---------- services/surfaceflinger/Layer.h | 10 ++++----- services/surfaceflinger/SurfaceFlinger.cpp | 10 ++++----- 13 files changed, 45 insertions(+), 47 deletions(-) diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index b391337d1d..196949682d 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -190,8 +190,8 @@ status_t layer_state_t::write(Parcel& output) const } SAFE_PARCEL(output.writeParcelable, trustedPresentationThresholds); SAFE_PARCEL(output.writeParcelable, trustedPresentationListener); - SAFE_PARCEL(output.writeFloat, currentSdrHdrRatio); - SAFE_PARCEL(output.writeFloat, desiredSdrHdrRatio); + SAFE_PARCEL(output.writeFloat, currentHdrSdrRatio); + SAFE_PARCEL(output.writeFloat, desiredHdrSdrRatio); SAFE_PARCEL(output.writeInt32, static_cast(cachingHint)) return NO_ERROR; } @@ -335,9 +335,9 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(input.readParcelable, &trustedPresentationListener); SAFE_PARCEL(input.readFloat, &tmpFloat); - currentSdrHdrRatio = tmpFloat; + currentHdrSdrRatio = tmpFloat; SAFE_PARCEL(input.readFloat, &tmpFloat); - desiredSdrHdrRatio = tmpFloat; + desiredHdrSdrRatio = tmpFloat; int32_t tmpInt32; SAFE_PARCEL(input.readInt32, &tmpInt32); @@ -592,8 +592,8 @@ void layer_state_t::merge(const layer_state_t& other) { } if (other.what & eExtendedRangeBrightnessChanged) { what |= eExtendedRangeBrightnessChanged; - desiredSdrHdrRatio = other.desiredSdrHdrRatio; - currentSdrHdrRatio = other.currentSdrHdrRatio; + desiredHdrSdrRatio = other.desiredHdrSdrRatio; + currentHdrSdrRatio = other.currentHdrSdrRatio; } if (other.what & eCachingHintChanged) { what |= eCachingHintChanged; @@ -747,8 +747,8 @@ uint64_t layer_state_t::diff(const layer_state_t& other) const { CHECK_DIFF(diff, eCropChanged, other, crop); if (other.what & eBufferChanged) diff |= eBufferChanged; CHECK_DIFF(diff, eDataspaceChanged, other, dataspace); - CHECK_DIFF2(diff, eExtendedRangeBrightnessChanged, other, currentSdrHdrRatio, - desiredSdrHdrRatio); + CHECK_DIFF2(diff, eExtendedRangeBrightnessChanged, other, currentHdrSdrRatio, + desiredHdrSdrRatio); CHECK_DIFF(diff, eCachingHintChanged, other, cachingHint); CHECK_DIFF(diff, eHdrMetadataChanged, other, hdrMetadata); if (other.what & eSurfaceDamageRegionChanged && diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 2f5830d362..7700aa4044 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -1723,8 +1723,8 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setExten return *this; } s->what |= layer_state_t::eExtendedRangeBrightnessChanged; - s->currentSdrHdrRatio = currentBufferRatio; - s->desiredSdrHdrRatio = desiredRatio; + s->currentHdrSdrRatio = currentBufferRatio; + s->desiredHdrSdrRatio = desiredRatio; registerSurfaceControlForCallback(sc); return *this; diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 6e3be5cef8..5c88a07ba8 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -389,8 +389,8 @@ struct layer_state_t { gui::DropInputMode dropInputMode; bool dimmingEnabled; - float currentSdrHdrRatio = 1.f; - float desiredSdrHdrRatio = 1.f; + float currentHdrSdrRatio = 1.f; + float desiredHdrSdrRatio = 1.f; gui::CachingHint cachingHint = gui::CachingHint::Enabled; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h index eae5871477..35ca3a58d9 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h @@ -210,8 +210,8 @@ struct LayerFECompositionState { // The dimming flag bool dimmingEnabled{true}; - float currentSdrHdrRatio = 1.f; - float desiredSdrHdrRatio = 1.f; + float currentHdrSdrRatio = 1.f; + float desiredHdrSdrRatio = 1.f; gui::CachingHint cachingHint = gui::CachingHint::Enabled; virtual ~LayerFECompositionState(); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h index ee9e646c1f..ce2b96fc2b 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h @@ -248,7 +248,7 @@ public: ui::Dataspace getDataspace() const { return mOutputDataspace.get(); } float getHdrSdrRatio() const { - return getOutputLayer()->getLayerFE().getCompositionState()->currentSdrHdrRatio; + return getOutputLayer()->getLayerFE().getCompositionState()->currentHdrSdrRatio; }; wp getBuffer() const { return mBuffer.get(); } diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp index 615d04b460..426cc57db8 100644 --- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp +++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp @@ -121,9 +121,9 @@ void LayerFECompositionState::dump(std::string& out) const { dumpVal(out, "dataspace", toString(dataspace), dataspace); dumpVal(out, "hdr metadata types", hdrMetadata.validTypes); dumpVal(out, "dimming enabled", dimmingEnabled); - if (currentSdrHdrRatio > 1.01f || desiredSdrHdrRatio > 1.01f) { - dumpVal(out, "current sdr/hdr ratio", currentSdrHdrRatio); - dumpVal(out, "desired sdr/hdr ratio", desiredSdrHdrRatio); + if (currentHdrSdrRatio > 1.01f || desiredHdrSdrRatio > 1.01f) { + dumpVal(out, "current hdr/sdr ratio", currentHdrSdrRatio); + dumpVal(out, "desired hdr/sdr ratio", desiredHdrSdrRatio); } dumpVal(out, "colorTransform", colorTransform); dumpVal(out, "caching hint", toString(cachingHint)); diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp index 1b86cd376a..0ac0ecb727 100644 --- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp +++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp @@ -344,8 +344,8 @@ void OutputLayer::updateCompositionState( // RANGE_EXTENDED can "self-promote" to HDR, but is still rendered for a particular // range that we may need to re-adjust to the current display conditions if ((state.dataspace & HAL_DATASPACE_RANGE_MASK) == HAL_DATASPACE_RANGE_EXTENDED && - layerFEState->currentSdrHdrRatio > 1.01f) { - layerBrightnessNits *= layerFEState->currentSdrHdrRatio; + layerFEState->currentHdrSdrRatio > 1.01f) { + layerBrightnessNits *= layerFEState->currentHdrSdrRatio; } state.dimmingRatio = std::clamp(layerBrightnessNits / getOutput().getState().displayBrightnessNits, 0.f, diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp index 1f7fb93c6b..bd030d090f 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp @@ -667,7 +667,7 @@ TEST_F(CachedSetTest, holePunch_requiresNonHdrWithExtendedBrightness) { ui::Dataspace::TRANSFER_SRGB | ui::Dataspace::RANGE_EXTENDED); mTestLayers[0]->outputLayerCompositionState.dataspace = dataspace; - mTestLayers[0]->layerFECompositionState.currentSdrHdrRatio = 5.f; + mTestLayers[0]->layerFECompositionState.currentHdrSdrRatio = 5.f; mTestLayers[0]->layerState->update(&mTestLayers[0]->outputLayer); CachedSet::Layer& layer = *mTestLayers[0]->cachedSetLayer.get(); diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index a16de1bcfb..70670dd022 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -707,8 +707,8 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a snapshot.sidebandStream = requested.sidebandStream; snapshot.transparentRegionHint = requested.transparentRegion; snapshot.color.rgb = requested.getColor().rgb; - snapshot.currentSdrHdrRatio = requested.currentSdrHdrRatio; - snapshot.desiredSdrHdrRatio = requested.desiredSdrHdrRatio; + snapshot.currentHdrSdrRatio = requested.currentHdrSdrRatio; + snapshot.desiredHdrSdrRatio = requested.desiredHdrSdrRatio; } if (snapshot.isHiddenByPolicyFromParent && diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index a5fdaf4fe3..1f670c81df 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -100,8 +100,8 @@ RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args) layerStack = ui::DEFAULT_LAYER_STACK; transformToDisplayInverse = false; dataspace = ui::Dataspace::UNKNOWN; - desiredSdrHdrRatio = 1.f; - currentSdrHdrRatio = 1.f; + desiredHdrSdrRatio = 1.f; + currentHdrSdrRatio = 1.f; dataspaceRequested = false; hdrMetadata.validTypes = 0; surfaceDamageRegion = Region::INVALID_REGION; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 95213718e5..be021510df 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -639,8 +639,8 @@ void Layer::preparePerFrameCompositionState() { snapshot->surfaceDamage = surfaceDamageRegion; snapshot->hasProtectedContent = isProtected(); snapshot->dimmingEnabled = isDimmingEnabled(); - snapshot->currentSdrHdrRatio = getCurrentSdrHdrRatio(); - snapshot->desiredSdrHdrRatio = getDesiredSdrHdrRatio(); + snapshot->currentHdrSdrRatio = getCurrentHdrSdrRatio(); + snapshot->desiredHdrSdrRatio = getDesiredHdrSdrRatio(); snapshot->cachingHint = getCachingHint(); const bool usesRoundedCorners = hasRoundedCorners(); @@ -3141,11 +3141,11 @@ bool Layer::setDataspace(ui::Dataspace dataspace) { } bool Layer::setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio) { - if (mDrawingState.currentSdrHdrRatio == currentBufferRatio && - mDrawingState.desiredSdrHdrRatio == desiredRatio) + if (mDrawingState.currentHdrSdrRatio == currentBufferRatio && + mDrawingState.desiredHdrSdrRatio == desiredRatio) return false; - mDrawingState.currentSdrHdrRatio = currentBufferRatio; - mDrawingState.desiredSdrHdrRatio = desiredRatio; + mDrawingState.currentHdrSdrRatio = currentBufferRatio; + mDrawingState.desiredHdrSdrRatio = desiredRatio; mDrawingState.modified = true; setTransactionFlags(eTransactionNeeded); return true; @@ -3400,8 +3400,8 @@ void Layer::gatherBufferInfo() { if (lastDataspace != mBufferInfo.mDataspace) { mFlinger->mHdrLayerInfoChanged = true; } - if (mBufferInfo.mDesiredSdrHdrRatio != mDrawingState.desiredSdrHdrRatio) { - mBufferInfo.mDesiredSdrHdrRatio = mDrawingState.desiredSdrHdrRatio; + if (mBufferInfo.mDesiredHdrSdrRatio != mDrawingState.desiredHdrSdrRatio) { + mBufferInfo.mDesiredHdrSdrRatio = mDrawingState.desiredHdrSdrRatio; mFlinger->mHdrLayerInfoChanged = true; } mBufferInfo.mCrop = computeBufferCrop(mDrawingState); @@ -3661,9 +3661,9 @@ bool Layer::simpleBufferUpdate(const layer_state_t& s) const { } if (s.what & layer_state_t::eExtendedRangeBrightnessChanged) { - if (mDrawingState.currentSdrHdrRatio != s.currentSdrHdrRatio || - mDrawingState.desiredSdrHdrRatio != s.desiredSdrHdrRatio) { - ALOGV("%s: false [eDimmingEnabledChanged changed]", __func__); + if (mDrawingState.currentHdrSdrRatio != s.currentHdrSdrRatio || + mDrawingState.desiredHdrSdrRatio != s.desiredHdrSdrRatio) { + ALOGV("%s: false [eExtendedRangeBrightnessChanged changed]", __func__); return false; } } diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index f7e1969ec0..ade3e20dec 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -225,8 +225,8 @@ public: gui::DropInputMode dropInputMode; bool autoRefresh = false; bool dimmingEnabled = true; - float currentSdrHdrRatio = 1.f; - float desiredSdrHdrRatio = 1.f; + float currentHdrSdrRatio = 1.f; + float desiredHdrSdrRatio = 1.f; gui::CachingHint cachingHint = gui::CachingHint::Enabled; int64_t latchedVsyncId = 0; }; @@ -296,8 +296,8 @@ public: virtual bool hasColorTransform() const; virtual bool isColorSpaceAgnostic() const { return mDrawingState.colorSpaceAgnostic; } virtual bool isDimmingEnabled() const { return getDrawingState().dimmingEnabled; } - float getDesiredSdrHdrRatio() const { return getDrawingState().desiredSdrHdrRatio; } - float getCurrentSdrHdrRatio() const { return getDrawingState().currentSdrHdrRatio; } + float getDesiredHdrSdrRatio() const { return getDrawingState().desiredHdrSdrRatio; } + float getCurrentHdrSdrRatio() const { return getDrawingState().currentHdrSdrRatio; } gui::CachingHint getCachingHint() const { return getDrawingState().cachingHint; } bool setTransform(uint32_t /*transform*/); @@ -519,7 +519,7 @@ public: uint64_t mFrameNumber; bool mFrameLatencyNeeded{false}; - float mDesiredSdrHdrRatio = 1.f; + float mDesiredHdrSdrRatio = 1.f; }; BufferInfo mBufferInfo; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 639ea27adc..309b90a04d 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2763,7 +2763,7 @@ bool SurfaceFlinger::isHdrLayer(const frontend::LayerSnapshot& snapshot) const { // RANGE_EXTENDED layers may identify themselves as being "HDR" via a desired sdr/hdr ratio if ((snapshot.dataspace & (int32_t)Dataspace::RANGE_MASK) == (int32_t)Dataspace::RANGE_EXTENDED && - snapshot.desiredSdrHdrRatio > 1.01f) { + snapshot.desiredHdrSdrRatio > 1.01f) { return true; } return false; @@ -2900,11 +2900,9 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { const auto* outputLayer = compositionDisplay->getOutputLayerForLayer(layerFe); if (outputLayer) { - // TODO(b/267350616): Rename SdrHdrRatio -> HdrSdrRatio - // everywhere - const float desiredHdrSdrRatio = snapshot.desiredSdrHdrRatio <= 1.f + const float desiredHdrSdrRatio = snapshot.desiredHdrSdrRatio <= 1.f ? std::numeric_limits::infinity() - : snapshot.desiredSdrHdrRatio; + : snapshot.desiredHdrSdrRatio; info.mergeDesiredRatio(desiredHdrSdrRatio); info.numberOfHdrLayers++; const auto displayFrame = outputLayer->getState().displayFrame; @@ -4931,7 +4929,7 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime if (layer->setDimmingEnabled(s.dimmingEnabled)) flags |= eTraversalNeeded; } if (what & layer_state_t::eExtendedRangeBrightnessChanged) { - if (layer->setExtendedRangeBrightness(s.currentSdrHdrRatio, s.desiredSdrHdrRatio)) { + if (layer->setExtendedRangeBrightness(s.currentHdrSdrRatio, s.desiredHdrSdrRatio)) { flags |= eTraversalNeeded; } } -- GitLab From 6fc45193c591c124c59bbd2263618bc8b518d9c4 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Thu, 16 Mar 2023 12:13:28 -0400 Subject: [PATCH 1048/1310] Avoid deadlock in EventThread::onNewVsyncSchedule EventThread::onNewVsyncSchedule attempts to lock mMutex, but it is currently called while Scheduler::mDisplayLock is held. But shouldConsumeEvent is called while mMutex is locked, and shouldConsumeEvent's call to mThrottleVsyncCallback results calls Scheduler::isVsyncValid, which then calls multiple methods that require locking mDisplayLock, resulting in deadlock. MessageQueue::onNewVsyncSchedule runs into a similar problem. It is also called while mDisplayLock is held. Split up the calls to onNewVsyncSchedule into a separate method, which is called after mDisplayLock is released. Create promotePacesetterDisplayLocked, which returns the new pacesetter's VsyncSchedule so it can be applied afterwards. This avoids the deadlock. In addition, handle the connections the same as other calls: lock mConnectionsLock, store a pointer to the EventThread, and call the method after releasing the lock. This is safe because Scheduler never deletes its EventThreads (until its own teardown). Bug: 272943830 Test: manual (see b/272234280) Change-Id: Ieb05fc3abcf4fc20f04ea5d3595da70155de2df5 --- .../surfaceflinger/Scheduler/Scheduler.cpp | 55 ++++++++++++++----- services/surfaceflinger/Scheduler/Scheduler.h | 11 ++++ 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 6e332721b8..3e12db61e7 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -109,7 +109,6 @@ void Scheduler::startTimers() { void Scheduler::setPacesetterDisplay(std::optional pacesetterIdOpt) { demotePacesetterDisplay(); - std::scoped_lock lock(mDisplayLock); promotePacesetterDisplay(pacesetterIdOpt); } @@ -123,26 +122,34 @@ void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId, std::shared_ptr vsyncSchedule) { demotePacesetterDisplay(); - std::scoped_lock lock(mDisplayLock); - mRefreshRateSelectors.emplace_or_replace(displayId, std::move(selectorPtr)); - mVsyncSchedules.emplace_or_replace(displayId, std::move(vsyncSchedule)); + std::shared_ptr pacesetterVsyncSchedule; + { + std::scoped_lock lock(mDisplayLock); + mRefreshRateSelectors.emplace_or_replace(displayId, std::move(selectorPtr)); + mVsyncSchedules.emplace_or_replace(displayId, std::move(vsyncSchedule)); - promotePacesetterDisplay(); + pacesetterVsyncSchedule = promotePacesetterDisplayLocked(); + } + applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); } void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { demotePacesetterDisplay(); - std::scoped_lock lock(mDisplayLock); - mRefreshRateSelectors.erase(displayId); - mVsyncSchedules.erase(displayId); + std::shared_ptr pacesetterVsyncSchedule; + { + std::scoped_lock lock(mDisplayLock); + mRefreshRateSelectors.erase(displayId); + mVsyncSchedules.erase(displayId); - // Do not allow removing the final display. Code in the scheduler expects - // there to be at least one display. (This may be relaxed in the future with - // headless virtual display.) - LOG_ALWAYS_FATAL_IF(mRefreshRateSelectors.empty(), "Cannot unregister all displays!"); + // Do not allow removing the final display. Code in the scheduler expects + // there to be at least one display. (This may be relaxed in the future with + // headless virtual display.) + LOG_ALWAYS_FATAL_IF(mRefreshRateSelectors.empty(), "Cannot unregister all displays!"); - promotePacesetterDisplay(); + pacesetterVsyncSchedule = promotePacesetterDisplayLocked(); + } + applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); } void Scheduler::run() { @@ -679,6 +686,18 @@ bool Scheduler::updateFrameRateOverrides(GlobalSignals consideredSignals, Fps di } void Scheduler::promotePacesetterDisplay(std::optional pacesetterIdOpt) { + std::shared_ptr pacesetterVsyncSchedule; + + { + std::scoped_lock lock(mDisplayLock); + pacesetterVsyncSchedule = promotePacesetterDisplayLocked(pacesetterIdOpt); + } + + applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); +} + +std::shared_ptr Scheduler::promotePacesetterDisplayLocked( + std::optional pacesetterIdOpt) { // TODO(b/241286431): Choose the pacesetter display. mPacesetterDisplayId = pacesetterIdOpt.value_or(mRefreshRateSelectors.begin()->first); ALOGI("Display %s is the pacesetter", to_string(*mPacesetterDisplayId).c_str()); @@ -698,14 +717,22 @@ void Scheduler::promotePacesetterDisplay(std::optional pacese vsyncSchedule->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod(), true /* force */); } + return vsyncSchedule; +} +void Scheduler::applyNewVsyncSchedule(std::shared_ptr vsyncSchedule) { onNewVsyncSchedule(vsyncSchedule->getDispatch()); + std::vector threads; { std::lock_guard lock(mConnectionsLock); + threads.reserve(mConnections.size()); for (auto& [_, connection] : mConnections) { - connection.thread->onNewVsyncSchedule(vsyncSchedule); + threads.push_back(connection.thread.get()); } } + for (auto* thread : threads) { + thread->onNewVsyncSchedule(vsyncSchedule); + } } void Scheduler::demotePacesetterDisplay() { diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 74547d5ea1..3423652163 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -321,7 +321,18 @@ private: // Chooses a pacesetter among the registered displays, unless `pacesetterIdOpt` is specified. // The new `mPacesetterDisplayId` is never `std::nullopt`. void promotePacesetterDisplay(std::optional pacesetterIdOpt = std::nullopt) + REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); + + // Changes to the displays (e.g. registering and unregistering) must be made + // while mDisplayLock is locked, and the new pacesetter then must be promoted while + // mDisplayLock is still locked. However, a new pacesetter means that + // MessageQueue and EventThread need to use the new pacesetter's + // VsyncSchedule, and this must happen while mDisplayLock is *not* locked, + // or else we may deadlock with EventThread. + std::shared_ptr promotePacesetterDisplayLocked( + std::optional pacesetterIdOpt = std::nullopt) REQUIRES(kMainThreadContext, mDisplayLock); + void applyNewVsyncSchedule(std::shared_ptr) EXCLUDES(mDisplayLock); // Blocks until the pacesetter's idle timer thread exits. `mDisplayLock` must not be locked by // the caller on the main thread to avoid deadlock, since the timer thread locks it before exit. -- GitLab From ed66a3586c303cd580f3dfc5d3eb40bfcddba568 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Thu, 23 Mar 2023 17:55:43 -0400 Subject: [PATCH 1049/1310] EventThread: add annotations for mVsyncSchedule Split out from patch set 1 of Ieb05fc3abcf4fc20f04ea5d3595da70155de2df5. Now that mVsyncSchedule can be changed, document that is guarded by mMutex. Bug: 272943830 Test: make Change-Id: Ie4e492fa667687b36f648b566e13552f96f0d559 --- services/surfaceflinger/Scheduler/EventThread.cpp | 2 +- services/surfaceflinger/Scheduler/EventThread.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index 57661f199a..74665a70aa 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -525,7 +525,7 @@ void EventThread::threadMain(std::unique_lock& lock) { bool EventThread::shouldConsumeEvent(const DisplayEventReceiver::Event& event, const sp& connection) const { - const auto throttleVsync = [&] { + const auto throttleVsync = [&]() REQUIRES(mMutex) { const auto& vsyncData = event.vsync.vsyncData; if (connection->frameRate.isValid()) { return !mVsyncSchedule->getTracker() diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h index 87e20a0636..30869e9cd8 100644 --- a/services/surfaceflinger/Scheduler/EventThread.h +++ b/services/surfaceflinger/Scheduler/EventThread.h @@ -205,7 +205,7 @@ private: TracedOrdinal mVsyncTracer; TracedOrdinal mWorkDuration GUARDED_BY(mMutex); std::chrono::nanoseconds mReadyDuration GUARDED_BY(mMutex); - std::shared_ptr mVsyncSchedule; + std::shared_ptr mVsyncSchedule GUARDED_BY(mMutex); TimePoint mLastVsyncCallbackTime GUARDED_BY(mMutex) = TimePoint::now(); scheduler::VSyncCallbackRegistration mVsyncRegistration GUARDED_BY(mMutex); frametimeline::TokenManager* const mTokenManager; -- GitLab From e4524271502dc5698db5c3dcabf3a01d4863a2e7 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Thu, 16 Mar 2023 12:13:28 -0400 Subject: [PATCH 1050/1310] Avoid deadlock in EventThread::onNewVsyncSchedule EventThread::onNewVsyncSchedule attempts to lock mMutex, but it is currently called while Scheduler::mDisplayLock is held. But shouldConsumeEvent is called while mMutex is locked, and shouldConsumeEvent's call to mThrottleVsyncCallback results calls Scheduler::isVsyncValid, which then calls multiple methods that require locking mDisplayLock, resulting in deadlock. MessageQueue::onNewVsyncSchedule runs into a similar problem. It is also called while mDisplayLock is held. Split up the calls to onNewVsyncSchedule into a separate method, which is called after mDisplayLock is released. Create promotePacesetterDisplayLocked, which returns the new pacesetter's VsyncSchedule so it can be applied afterwards. This avoids the deadlock. In addition, handle the connections the same as other calls: lock mConnectionsLock, store a pointer to the EventThread, and call the method after releasing the lock. This is safe because Scheduler never deletes its EventThreads (until its own teardown). Bug: 272943830 Test: manual (see b/272234280) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:6fc45193c591c124c59bbd2263618bc8b518d9c4) Merged-In: Ieb05fc3abcf4fc20f04ea5d3595da70155de2df5 Change-Id: Ieb05fc3abcf4fc20f04ea5d3595da70155de2df5 --- .../surfaceflinger/Scheduler/Scheduler.cpp | 55 ++++++++++++++----- services/surfaceflinger/Scheduler/Scheduler.h | 11 ++++ 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index f18dfdceb6..7f8d39451e 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -109,7 +109,6 @@ void Scheduler::startTimers() { void Scheduler::setPacesetterDisplay(std::optional pacesetterIdOpt) { demotePacesetterDisplay(); - std::scoped_lock lock(mDisplayLock); promotePacesetterDisplay(pacesetterIdOpt); } @@ -123,26 +122,34 @@ void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId, std::shared_ptr vsyncSchedule) { demotePacesetterDisplay(); - std::scoped_lock lock(mDisplayLock); - mRefreshRateSelectors.emplace_or_replace(displayId, std::move(selectorPtr)); - mVsyncSchedules.emplace_or_replace(displayId, std::move(vsyncSchedule)); + std::shared_ptr pacesetterVsyncSchedule; + { + std::scoped_lock lock(mDisplayLock); + mRefreshRateSelectors.emplace_or_replace(displayId, std::move(selectorPtr)); + mVsyncSchedules.emplace_or_replace(displayId, std::move(vsyncSchedule)); - promotePacesetterDisplay(); + pacesetterVsyncSchedule = promotePacesetterDisplayLocked(); + } + applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); } void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { demotePacesetterDisplay(); - std::scoped_lock lock(mDisplayLock); - mRefreshRateSelectors.erase(displayId); - mVsyncSchedules.erase(displayId); + std::shared_ptr pacesetterVsyncSchedule; + { + std::scoped_lock lock(mDisplayLock); + mRefreshRateSelectors.erase(displayId); + mVsyncSchedules.erase(displayId); - // Do not allow removing the final display. Code in the scheduler expects - // there to be at least one display. (This may be relaxed in the future with - // headless virtual display.) - LOG_ALWAYS_FATAL_IF(mRefreshRateSelectors.empty(), "Cannot unregister all displays!"); + // Do not allow removing the final display. Code in the scheduler expects + // there to be at least one display. (This may be relaxed in the future with + // headless virtual display.) + LOG_ALWAYS_FATAL_IF(mRefreshRateSelectors.empty(), "Cannot unregister all displays!"); - promotePacesetterDisplay(); + pacesetterVsyncSchedule = promotePacesetterDisplayLocked(); + } + applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); } void Scheduler::run() { @@ -678,6 +685,18 @@ bool Scheduler::updateFrameRateOverrides(GlobalSignals consideredSignals, Fps di } void Scheduler::promotePacesetterDisplay(std::optional pacesetterIdOpt) { + std::shared_ptr pacesetterVsyncSchedule; + + { + std::scoped_lock lock(mDisplayLock); + pacesetterVsyncSchedule = promotePacesetterDisplayLocked(pacesetterIdOpt); + } + + applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); +} + +std::shared_ptr Scheduler::promotePacesetterDisplayLocked( + std::optional pacesetterIdOpt) { // TODO(b/241286431): Choose the pacesetter display. mPacesetterDisplayId = pacesetterIdOpt.value_or(mRefreshRateSelectors.begin()->first); ALOGI("Display %s is the pacesetter", to_string(*mPacesetterDisplayId).c_str()); @@ -697,14 +716,22 @@ void Scheduler::promotePacesetterDisplay(std::optional pacese vsyncSchedule->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod(), true /* force */); } + return vsyncSchedule; +} +void Scheduler::applyNewVsyncSchedule(std::shared_ptr vsyncSchedule) { onNewVsyncSchedule(vsyncSchedule->getDispatch()); + std::vector threads; { std::lock_guard lock(mConnectionsLock); + threads.reserve(mConnections.size()); for (auto& [_, connection] : mConnections) { - connection.thread->onNewVsyncSchedule(vsyncSchedule); + threads.push_back(connection.thread.get()); } } + for (auto* thread : threads) { + thread->onNewVsyncSchedule(vsyncSchedule); + } } void Scheduler::demotePacesetterDisplay() { diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 74547d5ea1..3423652163 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -321,7 +321,18 @@ private: // Chooses a pacesetter among the registered displays, unless `pacesetterIdOpt` is specified. // The new `mPacesetterDisplayId` is never `std::nullopt`. void promotePacesetterDisplay(std::optional pacesetterIdOpt = std::nullopt) + REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); + + // Changes to the displays (e.g. registering and unregistering) must be made + // while mDisplayLock is locked, and the new pacesetter then must be promoted while + // mDisplayLock is still locked. However, a new pacesetter means that + // MessageQueue and EventThread need to use the new pacesetter's + // VsyncSchedule, and this must happen while mDisplayLock is *not* locked, + // or else we may deadlock with EventThread. + std::shared_ptr promotePacesetterDisplayLocked( + std::optional pacesetterIdOpt = std::nullopt) REQUIRES(kMainThreadContext, mDisplayLock); + void applyNewVsyncSchedule(std::shared_ptr) EXCLUDES(mDisplayLock); // Blocks until the pacesetter's idle timer thread exits. `mDisplayLock` must not be locked by // the caller on the main thread to avoid deadlock, since the timer thread locks it before exit. -- GitLab From bfab5055f7ce4d89d49e71620e075ed39ce14602 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Fri, 24 Mar 2023 15:45:46 -0400 Subject: [PATCH 1051/1310] Use the FuzzImplVSyncDispatch for fuzzing This class seems to have been written for fuzzing, but it wasn't used anywhere. Icdb80253436b4d0034fc20fcae8583efb7c30292 introduced new attempts to use the VSyncDispatch, resulting in a null pointer dereference in the fuzzer. Override update, which is an abstract method added since this class was. Ignore warnings for unused functions. surfaceflinger_scheduler_fuzzer.h contains a static method, Now(), which is used by other code that pulls in this header, but not by this executable. Fixes: 270785319 Test: surfaceflinger_layer_fuzzer Change-Id: I4e67e3f6383f466fbb092e7c7e9a0f867732c1f5 --- services/surfaceflinger/fuzzer/Android.bp | 1 + .../surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h | 4 +++- .../surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/fuzzer/Android.bp b/services/surfaceflinger/fuzzer/Android.bp index 7350e09cb5..f76a8d762a 100644 --- a/services/surfaceflinger/fuzzer/Android.bp +++ b/services/surfaceflinger/fuzzer/Android.bp @@ -69,6 +69,7 @@ cc_defaults { "-Wno-unused-result", "-Wno-conversion", "-Wno-sign-compare", + "-Wno-unused-function", ], fuzz_config: { cc: [ diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 6074bb7a16..c1bab0e89b 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -49,6 +49,7 @@ #include "SurfaceFlingerDefaultFactory.h" #include "ThreadContext.h" #include "TimeStats/TimeStats.h" +#include "surfaceflinger_scheduler_fuzzer.h" #include "renderengine/mock/RenderEngine.h" #include "scheduler/TimeKeeper.h" @@ -237,7 +238,8 @@ public: const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId(); registerDisplayInternal(displayId, std::move(selectorPtr), std::shared_ptr( - new VsyncSchedule(displayId, std::move(tracker), nullptr, + new VsyncSchedule(displayId, std::move(tracker), + std::make_shared(), std::move(controller)))); } diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h index e6be9a8b21..a32750e657 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h @@ -129,6 +129,11 @@ public: return (scheduler::ScheduleResult)0; } + scheduler::ScheduleResult update(CallbackToken /* token */, + ScheduleTiming /* scheduleTiming */) override { + return (scheduler::ScheduleResult)0; + } + scheduler::CancelResult cancel(CallbackToken /* token */) override { return (scheduler::CancelResult)0; } -- GitLab From e225b28df5f4215d923a94f6ec4277651666d46b Mon Sep 17 00:00:00 2001 From: John Reck Date: Fri, 24 Mar 2023 16:15:17 -0400 Subject: [PATCH 1052/1310] Validate STRIDE Bug: 261856851 Test: build & boot cf Change-Id: I8a43d4eb3cb9d9bd6899e104ab443418587e6b98 --- libs/ui/Gralloc5.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp index 514b45f0a5..21068394d2 100644 --- a/libs/ui/Gralloc5.cpp +++ b/libs/ui/Gralloc5.cpp @@ -352,14 +352,12 @@ status_t Gralloc5Mapper::validateBufferSize(buffer_handle_t bufferHandle, uint32 } } { - (void)stride; - // TODO(b/261856851): Add StandardMetadataType::STRIDE && enable this - // auto value = getStandardMetadata(mMapper, - // bufferHandle); if (static_cast(usage) != value) { - // ALOGW("Layer count didn't match, expected %" PRIu64 " got %" PRId64, usage, - // static_cast(value.value_or(BufferUsage::CPU_READ_NEVER))); - // return BAD_VALUE; - // } + auto value = getStandardMetadata(mMapper, bufferHandle); + if (stride != value) { + ALOGW("Stride didn't match, expected %" PRIu32 " got %" PRId32, stride, + value.value_or(-1)); + return BAD_VALUE; + } } return OK; } -- GitLab From a7e4eaf3101db66f7c4792b63297ec5e47ef7b57 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Fri, 24 Mar 2023 20:17:10 +0000 Subject: [PATCH 1053/1310] [sf] Trigger input updates in post composition - Move input updates off the hotpath - trigger transaction complete/release buffer callbacks a bit sooner. Bug: 256882630 Test: presubmit Test: perfetto traces Change-Id: Ic3640bac6784d3e607268df3fe1605a8ef4a723e --- services/surfaceflinger/SurfaceFlinger.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 0bdede8eb5..3c5a3deaef 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2517,8 +2517,6 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe } updateCursorAsync(); - updateInputFlinger(); - if (mLayerTracingEnabled && !mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) { // This will block and tracing should only be enabled for debugging. mLayerTracing.notify(mVisibleRegionsDirty, frameTime.ns(), vsyncId.value); @@ -2845,6 +2843,11 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { layer->releasePendingBuffer(presentTime.ns()); } + mTransactionCallbackInvoker.addPresentFence(std::move(presentFence)); + mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */); + mTransactionCallbackInvoker.clearCompletedTransactions(); + updateInputFlinger(); + std::vector, sp>> hdrInfoListeners; bool haveNewListeners = false; @@ -2901,10 +2904,6 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { mHdrLayerInfoChanged = false; - mTransactionCallbackInvoker.addPresentFence(std::move(presentFence)); - mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */); - mTransactionCallbackInvoker.clearCompletedTransactions(); - mTimeStats->incrementTotalFrames(); mTimeStats->setPresentFenceGlobal(presentFenceTime); -- GitLab From 5df8a88ccf18e746a82f09666abe586a53df0bc8 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Thu, 16 Mar 2023 12:13:28 -0400 Subject: [PATCH 1054/1310] Avoid deadlock in EventThread::onNewVsyncSchedule EventThread::onNewVsyncSchedule attempts to lock mMutex, but it is currently called while Scheduler::mDisplayLock is held. But shouldConsumeEvent is called while mMutex is locked, and shouldConsumeEvent's call to mThrottleVsyncCallback results calls Scheduler::isVsyncValid, which then calls multiple methods that require locking mDisplayLock, resulting in deadlock. MessageQueue::onNewVsyncSchedule runs into a similar problem. It is also called while mDisplayLock is held. Split up the calls to onNewVsyncSchedule into a separate method, which is called after mDisplayLock is released. Create promotePacesetterDisplayLocked, which returns the new pacesetter's VsyncSchedule so it can be applied afterwards. This avoids the deadlock. In addition, handle the connections the same as other calls: lock mConnectionsLock, store a pointer to the EventThread, and call the method after releasing the lock. This is safe because Scheduler never deletes its EventThreads (until its own teardown). Bug: 272943830 Test: manual (see b/272234280) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:6fc45193c591c124c59bbd2263618bc8b518d9c4) Merged-In: Ieb05fc3abcf4fc20f04ea5d3595da70155de2df5 Change-Id: Ieb05fc3abcf4fc20f04ea5d3595da70155de2df5 --- .../surfaceflinger/Scheduler/Scheduler.cpp | 55 ++++++++++++++----- services/surfaceflinger/Scheduler/Scheduler.h | 11 ++++ 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index f18dfdceb6..7f8d39451e 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -109,7 +109,6 @@ void Scheduler::startTimers() { void Scheduler::setPacesetterDisplay(std::optional pacesetterIdOpt) { demotePacesetterDisplay(); - std::scoped_lock lock(mDisplayLock); promotePacesetterDisplay(pacesetterIdOpt); } @@ -123,26 +122,34 @@ void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId, std::shared_ptr vsyncSchedule) { demotePacesetterDisplay(); - std::scoped_lock lock(mDisplayLock); - mRefreshRateSelectors.emplace_or_replace(displayId, std::move(selectorPtr)); - mVsyncSchedules.emplace_or_replace(displayId, std::move(vsyncSchedule)); + std::shared_ptr pacesetterVsyncSchedule; + { + std::scoped_lock lock(mDisplayLock); + mRefreshRateSelectors.emplace_or_replace(displayId, std::move(selectorPtr)); + mVsyncSchedules.emplace_or_replace(displayId, std::move(vsyncSchedule)); - promotePacesetterDisplay(); + pacesetterVsyncSchedule = promotePacesetterDisplayLocked(); + } + applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); } void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { demotePacesetterDisplay(); - std::scoped_lock lock(mDisplayLock); - mRefreshRateSelectors.erase(displayId); - mVsyncSchedules.erase(displayId); + std::shared_ptr pacesetterVsyncSchedule; + { + std::scoped_lock lock(mDisplayLock); + mRefreshRateSelectors.erase(displayId); + mVsyncSchedules.erase(displayId); - // Do not allow removing the final display. Code in the scheduler expects - // there to be at least one display. (This may be relaxed in the future with - // headless virtual display.) - LOG_ALWAYS_FATAL_IF(mRefreshRateSelectors.empty(), "Cannot unregister all displays!"); + // Do not allow removing the final display. Code in the scheduler expects + // there to be at least one display. (This may be relaxed in the future with + // headless virtual display.) + LOG_ALWAYS_FATAL_IF(mRefreshRateSelectors.empty(), "Cannot unregister all displays!"); - promotePacesetterDisplay(); + pacesetterVsyncSchedule = promotePacesetterDisplayLocked(); + } + applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); } void Scheduler::run() { @@ -678,6 +685,18 @@ bool Scheduler::updateFrameRateOverrides(GlobalSignals consideredSignals, Fps di } void Scheduler::promotePacesetterDisplay(std::optional pacesetterIdOpt) { + std::shared_ptr pacesetterVsyncSchedule; + + { + std::scoped_lock lock(mDisplayLock); + pacesetterVsyncSchedule = promotePacesetterDisplayLocked(pacesetterIdOpt); + } + + applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); +} + +std::shared_ptr Scheduler::promotePacesetterDisplayLocked( + std::optional pacesetterIdOpt) { // TODO(b/241286431): Choose the pacesetter display. mPacesetterDisplayId = pacesetterIdOpt.value_or(mRefreshRateSelectors.begin()->first); ALOGI("Display %s is the pacesetter", to_string(*mPacesetterDisplayId).c_str()); @@ -697,14 +716,22 @@ void Scheduler::promotePacesetterDisplay(std::optional pacese vsyncSchedule->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod(), true /* force */); } + return vsyncSchedule; +} +void Scheduler::applyNewVsyncSchedule(std::shared_ptr vsyncSchedule) { onNewVsyncSchedule(vsyncSchedule->getDispatch()); + std::vector threads; { std::lock_guard lock(mConnectionsLock); + threads.reserve(mConnections.size()); for (auto& [_, connection] : mConnections) { - connection.thread->onNewVsyncSchedule(vsyncSchedule); + threads.push_back(connection.thread.get()); } } + for (auto* thread : threads) { + thread->onNewVsyncSchedule(vsyncSchedule); + } } void Scheduler::demotePacesetterDisplay() { diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 74547d5ea1..3423652163 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -321,7 +321,18 @@ private: // Chooses a pacesetter among the registered displays, unless `pacesetterIdOpt` is specified. // The new `mPacesetterDisplayId` is never `std::nullopt`. void promotePacesetterDisplay(std::optional pacesetterIdOpt = std::nullopt) + REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); + + // Changes to the displays (e.g. registering and unregistering) must be made + // while mDisplayLock is locked, and the new pacesetter then must be promoted while + // mDisplayLock is still locked. However, a new pacesetter means that + // MessageQueue and EventThread need to use the new pacesetter's + // VsyncSchedule, and this must happen while mDisplayLock is *not* locked, + // or else we may deadlock with EventThread. + std::shared_ptr promotePacesetterDisplayLocked( + std::optional pacesetterIdOpt = std::nullopt) REQUIRES(kMainThreadContext, mDisplayLock); + void applyNewVsyncSchedule(std::shared_ptr) EXCLUDES(mDisplayLock); // Blocks until the pacesetter's idle timer thread exits. `mDisplayLock` must not be locked by // the caller on the main thread to avoid deadlock, since the timer thread locks it before exit. -- GitLab From 67e0e4ead8a4e3ca0fa0d6fb0700c37eb4efc8e3 Mon Sep 17 00:00:00 2001 From: Serdar Kocdemir Date: Sun, 26 Mar 2023 23:23:59 +0100 Subject: [PATCH 1055/1310] Update test annotation with new VsrTest tag Bug: b/248501602 Test: atest GpuWorkTracepointTest Change-Id: I61575f671c8403999cecbedfe088185ebc295e9d --- .../com/android/tests/gpuservice/GpuWorkTracepointTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java index 290a6469c6..6c16335359 100644 --- a/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java +++ b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java @@ -27,7 +27,7 @@ import com.android.tradefed.util.CommandResult; import com.android.tradefed.util.CommandStatus; import com.android.compatibility.common.util.PropertyUtil; -import com.android.compatibility.common.util.GmsTest; +import com.android.compatibility.common.util.VsrTest; import org.junit.Test; import org.junit.runner.RunWith; @@ -62,7 +62,7 @@ public class GpuWorkTracepointTest extends BaseHostJUnit4Test { commandResult.getStatus(), CommandStatus.SUCCESS); } - @GmsTest(requirement = "VSR-3.3-004") + @VsrTest(requirements={"VSR-3.3-004"}) @RestrictedBuildTest @Test public void testGpuWorkPeriodTracepointFormat() throws Exception { -- GitLab From 1234a337651a79d492b6c453eb7f4cf30ec341cf Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Mon, 27 Mar 2023 13:22:38 +0000 Subject: [PATCH 1056/1310] Revert "SF: throttle WindowInfosListener calls" This reverts commit a364bd7c0ecc412582eff1160368f3eaa2b4c6f0. Reason for revert: b/274259816 Change-Id: I7b886183d923a6905402bb83a3ec8570d59ef2ce --- services/surfaceflinger/SurfaceFlinger.cpp | 7 +- .../WindowInfosListenerInvoker.cpp | 108 +++++++----------- .../WindowInfosListenerInvoker.h | 24 ++-- 3 files changed, 50 insertions(+), 89 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 234c6fe9b2..639ea27adc 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3725,11 +3725,8 @@ void SurfaceFlinger::updateInputFlinger() { ATRACE_NAME("BackgroundExecutor::updateInputFlinger"); if (updateWindowInfo) { mWindowInfosListenerInvoker - ->windowInfosChanged(std::move(windowInfos), std::move(displayInfos), - std::move( - inputWindowCommands.windowInfosReportedListeners), - /* forceImmediateCall= */ - !inputWindowCommands.focusRequests.empty()); + ->windowInfosChanged(windowInfos, displayInfos, + inputWindowCommands.windowInfosReportedListeners); } else { // If there are listeners but no changes to input windows, call the listeners // immediately. diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp index 73a7cae6d6..292083b9bc 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp +++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp @@ -25,14 +25,20 @@ using gui::DisplayInfo; using gui::IWindowInfosListener; using gui::WindowInfo; -struct WindowInfosReportedListenerInvoker : gui::BnWindowInfosReportedListener, - IBinder::DeathRecipient { - WindowInfosReportedListenerInvoker(size_t callbackCount, - WindowInfosReportedListenerSet windowInfosReportedListeners) +struct WindowInfosListenerInvoker::WindowInfosReportedListener : gui::BnWindowInfosReportedListener, + DeathRecipient { + explicit WindowInfosReportedListener( + size_t callbackCount, + const std::unordered_set, + SpHash>& + windowInfosReportedListeners) : mCallbacksPending(callbackCount), - mWindowInfosReportedListeners(std::move(windowInfosReportedListeners)) {} + mWindowInfosReportedListeners(windowInfosReportedListeners) {} binder::Status onWindowInfosReported() override { + // TODO(b/222421815) There could potentially be callbacks that we don't need to wait for + // before calling the WindowInfosReportedListeners coming from InputWindowCommands. Filter + // the list of callbacks down to those from system server. if (--mCallbacksPending == 0) { for (const auto& listener : mWindowInfosReportedListeners) { sp asBinder = IInterface::asBinder(listener); @@ -48,7 +54,9 @@ struct WindowInfosReportedListenerInvoker : gui::BnWindowInfosReportedListener, private: std::atomic mCallbacksPending; - WindowInfosReportedListenerSet mWindowInfosReportedListeners; + std::unordered_set, + SpHash> + mWindowInfosReportedListeners; }; void WindowInfosListenerInvoker::addWindowInfosListener(sp listener) { @@ -74,76 +82,38 @@ void WindowInfosListenerInvoker::binderDied(const wp& who) { } void WindowInfosListenerInvoker::windowInfosChanged( - std::vector windowInfos, std::vector displayInfos, - WindowInfosReportedListenerSet reportedListeners, bool forceImmediateCall) { - reportedListeners.insert(sp::fromExisting(this)); - auto callListeners = [this, windowInfos = std::move(windowInfos), - displayInfos = std::move(displayInfos), - reportedListeners = std::move(reportedListeners)]() mutable { - ftl::SmallVector, kStaticCapacity> windowInfosListeners; - { - std::scoped_lock lock(mListenersMutex); - for (const auto& [_, listener] : mWindowInfosListeners) { - windowInfosListeners.push_back(listener); - } - } - - auto reportedInvoker = - sp::make(windowInfosListeners.size(), - std::move(reportedListeners)); - - for (const auto& listener : windowInfosListeners) { - sp asBinder = IInterface::asBinder(listener); - - // linkToDeath is used here to ensure that the windowInfosReportedListeners - // are called even if one of the windowInfosListeners dies before - // calling onWindowInfosReported. - asBinder->linkToDeath(reportedInvoker); - - auto status = - listener->onWindowInfosChanged(windowInfos, displayInfos, reportedInvoker); - if (!status.isOk()) { - reportedInvoker->onWindowInfosReported(); - } - } - }; - + const std::vector& windowInfos, const std::vector& displayInfos, + const std::unordered_set, + SpHash>& + windowInfosReportedListeners) { + ftl::SmallVector, kStaticCapacity> windowInfosListeners; { - std::scoped_lock lock(mMessagesMutex); - // If there are unacked messages and this isn't a forced call, then return immediately. - // If a forced window infos change doesn't happen first, the update will be sent after - // the WindowInfosReportedListeners are called. If a forced window infos change happens or - // if there are subsequent delayed messages before this update is sent, then this message - // will be dropped and the listeners will only be called with the latest info. This is done - // to reduce the amount of binder memory used. - if (mActiveMessageCount > 0 && !forceImmediateCall) { - mWindowInfosChangedDelayed = std::move(callListeners); - return; + std::scoped_lock lock(mListenersMutex); + for (const auto& [_, listener] : mWindowInfosListeners) { + windowInfosListeners.push_back(listener); } - - mWindowInfosChangedDelayed = nullptr; - mActiveMessageCount++; } - callListeners(); -} - -binder::Status WindowInfosListenerInvoker::onWindowInfosReported() { - std::function callListeners; - { - std::scoped_lock lock{mMessagesMutex}; - mActiveMessageCount--; - if (!mWindowInfosChangedDelayed || mActiveMessageCount > 0) { - return binder::Status::ok(); + auto windowInfosReportedListener = windowInfosReportedListeners.empty() + ? nullptr + : sp::make(windowInfosListeners.size(), + windowInfosReportedListeners); + for (const auto& listener : windowInfosListeners) { + sp asBinder = IInterface::asBinder(listener); + + // linkToDeath is used here to ensure that the windowInfosReportedListeners + // are called even if one of the windowInfosListeners dies before + // calling onWindowInfosReported. + if (windowInfosReportedListener) { + asBinder->linkToDeath(windowInfosReportedListener); } - mActiveMessageCount++; - callListeners = std::move(mWindowInfosChangedDelayed); - mWindowInfosChangedDelayed = nullptr; + auto status = listener->onWindowInfosChanged(windowInfos, displayInfos, + windowInfosReportedListener); + if (windowInfosReportedListener && !status.isOk()) { + windowInfosReportedListener->onWindowInfosReported(); + } } - - callListeners(); - return binder::Status::ok(); } } // namespace android diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h index bfe036e025..d60a9c4157 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.h +++ b/services/surfaceflinger/WindowInfosListenerInvoker.h @@ -23,40 +23,34 @@ #include #include #include -#include #include namespace android { -using WindowInfosReportedListenerSet = - std::unordered_set, - gui::SpHash>; +class SurfaceFlinger; -class WindowInfosListenerInvoker : public gui::BnWindowInfosReportedListener, - public IBinder::DeathRecipient { +class WindowInfosListenerInvoker : public IBinder::DeathRecipient { public: void addWindowInfosListener(sp); void removeWindowInfosListener(const sp& windowInfosListener); - void windowInfosChanged(std::vector, std::vector, - WindowInfosReportedListenerSet windowInfosReportedListeners, - bool forceImmediateCall); - - binder::Status onWindowInfosReported() override; + void windowInfosChanged(const std::vector&, + const std::vector&, + const std::unordered_set, + SpHash>& + windowInfosReportedListeners); protected: void binderDied(const wp& who) override; private: + struct WindowInfosReportedListener; + std::mutex mListenersMutex; static constexpr size_t kStaticCapacity = 3; ftl::SmallMap, const sp, kStaticCapacity> mWindowInfosListeners GUARDED_BY(mListenersMutex); - - std::mutex mMessagesMutex; - uint32_t mActiveMessageCount GUARDED_BY(mMessagesMutex) = 0; - std::function mWindowInfosChangedDelayed GUARDED_BY(mMessagesMutex); }; } // namespace android -- GitLab From 1ae310a1fcf117b92e64c40373b388e8526c8989 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Thu, 23 Mar 2023 11:56:23 -0700 Subject: [PATCH 1057/1310] libjpegr: add vendor_available Bug: 264715926 Change-Id: Ic97fb566322f5ef4db8fcb7b359d58ae8d395418 --- libs/jpegrecoverymap/Android.bp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp index b470f351f4..a1b0e19509 100644 --- a/libs/jpegrecoverymap/Android.bp +++ b/libs/jpegrecoverymap/Android.bp @@ -24,7 +24,7 @@ package { cc_library { name: "libjpegrecoverymap", host_supported: true, - + vendor_available: true, export_include_dirs: ["include"], local_include_dirs: ["include"], @@ -49,10 +49,12 @@ cc_library { cc_library { name: "libjpegencoder", host_supported: true, + vendor_available: true, shared_libs: [ "libjpeg", "liblog", + "libutils", ], export_include_dirs: ["include"], @@ -65,10 +67,12 @@ cc_library { cc_library { name: "libjpegdecoder", host_supported: true, + vendor_available: true, shared_libs: [ "libjpeg", "liblog", + "libutils", ], export_include_dirs: ["include"], -- GitLab From 0d13b914a0f902d88af25f5385df4ff4317b784f Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Mon, 27 Mar 2023 22:06:38 +0000 Subject: [PATCH 1058/1310] [layertracegenerator] fix display proto parsing - fixes a couple of bugs in how we parsed display data and added tests to cover the logic - additionally create fake input tokens when generating layer state so we don't assume that the token is null and ignore input data Fixes: 275418553 Test: presubmit Change-Id: I60dd2862e0c6ce0e214ad2123a8523b4f74c38e9 --- .../Tracing/TransactionProtoParser.cpp | 4 +- .../Tracing/TransactionProtoParser.h | 9 ++-- .../Tracing/tools/LayerTraceGenerator.cpp | 10 +++++ .../tracing/TransactionTraceTestSuite.cpp | 26 +++++++++-- .../unittests/TransactionProtoParserTest.cpp | 45 +++++++++++++++++++ 5 files changed, 84 insertions(+), 10 deletions(-) diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp index 7642122adc..593c4ffc03 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp @@ -585,7 +585,7 @@ frontend::DisplayInfo TransactionProtoParser::fromProto(const proto::DisplayInfo displayInfo.receivesInput = proto.receives_input(); displayInfo.isSecure = proto.is_secure(); displayInfo.isPrimary = proto.is_primary(); - displayInfo.isPrimary = proto.is_virtual(); + displayInfo.isVirtual = proto.is_virtual(); displayInfo.rotationFlags = (ui::Transform::RotationFlags)proto.rotation_flags(); displayInfo.transformHint = (ui::Transform::RotationFlags)proto.transform_hint(); return displayInfo; @@ -593,7 +593,7 @@ frontend::DisplayInfo TransactionProtoParser::fromProto(const proto::DisplayInfo void TransactionProtoParser::fromProto( const google::protobuf::RepeatedPtrField& proto, - display::DisplayMap outDisplayInfos) { + display::DisplayMap& outDisplayInfos) { outDisplayInfos.clear(); for (const proto::DisplayInfo& displayInfo : proto) { outDisplayInfos.emplace_or_replace(ui::LayerStack::fromValue(displayInfo.layer_stack()), diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.h b/services/surfaceflinger/Tracing/TransactionProtoParser.h index 50944fce0f..d6c98e1120 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.h +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.h @@ -49,15 +49,16 @@ public: proto::TransactionState toProto(const std::map&); proto::LayerCreationArgs toProto(const LayerCreationArgs& args); proto::LayerState toProto(const ResolvedComposerState&); - proto::DisplayInfo toProto(const frontend::DisplayInfo&, uint32_t layerStack); + static proto::DisplayInfo toProto(const frontend::DisplayInfo&, uint32_t layerStack); TransactionState fromProto(const proto::TransactionState&); void mergeFromProto(const proto::LayerState&, TracingLayerState& outState); void fromProto(const proto::LayerCreationArgs&, LayerCreationArgs& outArgs); std::unique_ptr mMapper; - frontend::DisplayInfo fromProto(const proto::DisplayInfo&); - void fromProto(const google::protobuf::RepeatedPtrField&, - display::DisplayMap outDisplayInfos); + static frontend::DisplayInfo fromProto(const proto::DisplayInfo&); + static void fromProto( + const google::protobuf::RepeatedPtrField&, + display::DisplayMap& outDisplayInfos); private: proto::DisplayState toProto(const DisplayState&); diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index 0ea421bd24..4b4ce1891f 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -92,6 +92,16 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, for (int j = 0; j < entry.transactions_size(); j++) { // apply transactions TransactionState transaction = parser.fromProto(entry.transactions(j)); + for (auto& resolvedComposerState : transaction.states) { + if (resolvedComposerState.state.what & layer_state_t::eInputInfoChanged) { + if (!resolvedComposerState.state.windowInfoHandle->getInfo()->inputConfig.test( + gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL)) { + // create a fake token since the FE expects a valid token + resolvedComposerState.state.windowInfoHandle->editInfo()->token = + sp::make(); + } + } + } transactions.emplace_back(std::move(transaction)); } diff --git a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp index 7355c35131..3e3d8f0206 100644 --- a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp +++ b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -94,11 +95,14 @@ struct LayerInfo { float y; uint32_t bufferWidth; uint32_t bufferHeight; + Rect touchableRegionBounds; }; bool operator==(const LayerInfo& lh, const LayerInfo& rh) { - return std::make_tuple(lh.id, lh.name, lh.parent, lh.z, lh.curr_frame) == - std::make_tuple(rh.id, rh.name, rh.parent, rh.z, rh.curr_frame); + return std::make_tuple(lh.id, lh.name, lh.parent, lh.z, lh.curr_frame, lh.bufferWidth, + lh.bufferHeight, lh.touchableRegionBounds) == + std::make_tuple(rh.id, rh.name, rh.parent, rh.z, rh.curr_frame, rh.bufferWidth, + rh.bufferHeight, rh.touchableRegionBounds); } bool compareById(const LayerInfo& a, const LayerInfo& b) { @@ -109,7 +113,9 @@ inline void PrintTo(const LayerInfo& info, ::std::ostream* os) { *os << "Layer [" << info.id << "] name=" << info.name << " parent=" << info.parent << " z=" << info.z << " curr_frame=" << info.curr_frame << " x=" << info.x << " y=" << info.y << " bufferWidth=" << info.bufferWidth - << " bufferHeight=" << info.bufferHeight; + << " bufferHeight=" << info.bufferHeight << "touchableRegionBounds={" + << info.touchableRegionBounds.left << "," << info.touchableRegionBounds.top << "," + << info.touchableRegionBounds.right << "," << info.touchableRegionBounds.bottom << "}"; } struct find_id : std::unary_function { @@ -119,6 +125,17 @@ struct find_id : std::unary_function { }; static LayerInfo getLayerInfoFromProto(::android::surfaceflinger::LayerProto& proto) { + Rect touchableRegionBounds = Rect::INVALID_RECT; + // ignore touchable region for layers without buffers, the new fe aggressively avoids + // calculating state for layers that are not visible which could lead to mismatches + if (proto.has_input_window_info() && proto.input_window_info().has_touchable_region() && + proto.has_active_buffer()) { + Region touchableRegion; + LayerProtoHelper::readFromProto(proto.input_window_info().touchable_region(), + touchableRegion); + touchableRegionBounds = touchableRegion.bounds(); + } + return {proto.id(), proto.name(), proto.parent(), @@ -127,7 +144,8 @@ static LayerInfo getLayerInfoFromProto(::android::surfaceflinger::LayerProto& pr proto.has_position() ? proto.position().x() : -1, proto.has_position() ? proto.position().y() : -1, proto.has_active_buffer() ? proto.active_buffer().width() : 0, - proto.has_active_buffer() ? proto.active_buffer().height() : 0}; + proto.has_active_buffer() ? proto.active_buffer().height() : 0, + touchableRegionBounds}; } TEST_P(TransactionTraceTestSuite, validateEndState) { diff --git a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp index 3dea189ca0..dd72174a37 100644 --- a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp @@ -19,6 +19,7 @@ #include // std::numeric_limits #include +#include #include "LayerProtoHelper.h" #include "Tracing/TransactionProtoParser.h" @@ -103,4 +104,48 @@ TEST(TransactionProtoParserTest, parse) { ASSERT_EQ(t1.displays[0].token, t2.displays[0].token); } +TEST(TransactionProtoParserTest, parseDisplayInfo) { + frontend::DisplayInfo d1; + d1.info.displayId = 42; + d1.info.logicalWidth = 43; + d1.info.logicalHeight = 44; + d1.info.transform.set(1, 2, 3, 4); + d1.transform = d1.info.transform.inverse(); + d1.receivesInput = true; + d1.isSecure = false; + d1.isPrimary = true; + d1.isVirtual = false; + d1.rotationFlags = ui::Transform::ROT_180; + d1.transformHint = ui::Transform::ROT_90; + + const uint32_t layerStack = 2; + google::protobuf::RepeatedPtrField displayProtos; + auto displayInfoProto = displayProtos.Add(); + *displayInfoProto = TransactionProtoParser::toProto(d1, layerStack); + display::DisplayMap displayInfos; + TransactionProtoParser::fromProto(displayProtos, displayInfos); + + ASSERT_TRUE(displayInfos.contains(ui::LayerStack::fromValue(layerStack))); + frontend::DisplayInfo d2 = displayInfos.get(ui::LayerStack::fromValue(layerStack))->get(); + EXPECT_EQ(d1.info.displayId, d2.info.displayId); + EXPECT_EQ(d1.info.logicalWidth, d2.info.logicalWidth); + EXPECT_EQ(d1.info.logicalHeight, d2.info.logicalHeight); + + EXPECT_EQ(d1.info.transform.dsdx(), d2.info.transform.dsdx()); + EXPECT_EQ(d1.info.transform.dsdy(), d2.info.transform.dsdy()); + EXPECT_EQ(d1.info.transform.dtdx(), d2.info.transform.dtdx()); + EXPECT_EQ(d1.info.transform.dtdy(), d2.info.transform.dtdy()); + + EXPECT_EQ(d1.transform.dsdx(), d2.transform.dsdx()); + EXPECT_EQ(d1.transform.dsdy(), d2.transform.dsdy()); + EXPECT_EQ(d1.transform.dtdx(), d2.transform.dtdx()); + EXPECT_EQ(d1.transform.dtdy(), d2.transform.dtdy()); + + EXPECT_EQ(d1.receivesInput, d2.receivesInput); + EXPECT_EQ(d1.isSecure, d2.isSecure); + EXPECT_EQ(d1.isVirtual, d2.isVirtual); + EXPECT_EQ(d1.rotationFlags, d2.rotationFlags); + EXPECT_EQ(d1.transformHint, d2.transformHint); +} + } // namespace android -- GitLab From 6d73f83c0a0ba15a7488916a435d0b2985d4f2a0 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 21 Jul 2022 17:27:03 -0700 Subject: [PATCH 1059/1310] Convert tool type to enum class For better type safety, use enum class when sending tool type. Bug: 198472780 Test: atest libinput_tests inputflinger_tests (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:09a8fe42ba1d218c2f445e4fd5bc387e260ae067) Merged-In: I371f08087b9513b6f75966c124de77bc12f8324e Change-Id: I371f08087b9513b6f75966c124de77bc12f8324e --- include/input/Input.h | 24 +- include/input/InputTransport.h | 1 - libs/input/Input.cpp | 33 +- libs/input/InputTransport.cpp | 9 +- libs/input/MotionPredictor.cpp | 7 +- libs/input/tests/InputEvent_test.cpp | 10 +- .../tests/InputPublisherAndConsumer_test.cpp | 2 +- libs/input/tests/MotionPredictor_test.cpp | 2 +- libs/input/tests/TouchResampling_test.cpp | 2 +- libs/input/tests/VelocityTracker_test.cpp | 2 +- .../inputflinger/InputCommonConverter.cpp | 17 +- services/inputflinger/NotifyArgs.cpp | 6 +- .../PreferStylusOverTouchBlocker.cpp | 4 +- .../UnwantedInteractionBlocker.cpp | 18 +- .../benchmarks/InputDispatcher_benchmarks.cpp | 4 +- .../dispatcher/InputDispatcher.cpp | 7 +- .../inputflinger/reader/include/StylusState.h | 4 +- .../reader/mapper/CursorInputMapper.cpp | 2 +- .../mapper/ExternalStylusInputMapper.cpp | 4 +- .../reader/mapper/JoystickInputMapper.cpp | 2 +- .../reader/mapper/MultiTouchInputMapper.cpp | 17 +- .../mapper/RotaryEncoderInputMapper.cpp | 2 +- .../reader/mapper/SingleTouchInputMapper.cpp | 6 +- .../reader/mapper/TouchInputMapper.cpp | 40 ++- .../reader/mapper/TouchInputMapper.h | 4 +- .../MultiTouchMotionAccumulator.cpp | 10 +- .../accumulator/MultiTouchMotionAccumulator.h | 2 +- .../accumulator/TouchButtonAccumulator.cpp | 12 +- .../accumulator/TouchButtonAccumulator.h | 2 +- .../reader/mapper/gestures/GestureConverter.h | 8 +- .../gestures/HardwareStateConverter.cpp | 2 +- .../tests/GestureConverter_test.cpp | 116 +++--- .../tests/InputDispatcher_test.cpp | 328 ++++++++--------- .../tests/InputProcessorConverter_test.cpp | 2 +- .../tests/InputProcessor_test.cpp | 2 +- .../inputflinger/tests/InputReader_test.cpp | 330 +++++++++--------- .../inputflinger/tests/NotifyArgs_test.cpp | 2 +- .../tests/PreferStylusOverTouch_test.cpp | 14 +- .../tests/TestInputListenerMatchers.h | 4 +- .../tests/UnwantedInteractionBlocker_test.cpp | 26 +- .../tests/fuzzers/InputClassifierFuzzer.cpp | 2 +- .../tests/fuzzers/MapperHelpers.h | 8 + .../tests/fuzzers/MultiTouchInputFuzzer.cpp | 2 +- 43 files changed, 534 insertions(+), 567 deletions(-) diff --git a/include/input/Input.h b/include/input/Input.h index e8af5f7d46..a033535f4b 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -216,7 +216,21 @@ std::string inputEventSourceToString(int32_t source); bool isFromSource(uint32_t source, uint32_t test); -bool isStylusToolType(uint32_t toolType); +/** + * The pointer tool type. + */ +enum class ToolType { + UNKNOWN = AMOTION_EVENT_TOOL_TYPE_UNKNOWN, + FINGER = AMOTION_EVENT_TOOL_TYPE_FINGER, + STYLUS = AMOTION_EVENT_TOOL_TYPE_STYLUS, + MOUSE = AMOTION_EVENT_TOOL_TYPE_MOUSE, + ERASER = AMOTION_EVENT_TOOL_TYPE_ERASER, + PALM = AMOTION_EVENT_TOOL_TYPE_PALM, + ftl_first = UNKNOWN, + ftl_last = PALM, +}; + +bool isStylusToolType(ToolType toolType); /* * Flags that flow alongside events in the input dispatch system to help with certain @@ -320,8 +334,6 @@ enum class MotionClassification : uint8_t { */ const char* motionClassificationToString(MotionClassification classification); -const char* motionToolTypeToString(int32_t toolType); - /** * Portion of FrameMetrics timeline of interest to input code. */ @@ -448,11 +460,11 @@ struct PointerProperties { int32_t id; // The pointer tool type. - int32_t toolType; + ToolType toolType; inline void clear() { id = -1; - toolType = 0; + toolType = ToolType::UNKNOWN; } bool operator==(const PointerProperties& other) const; @@ -638,7 +650,7 @@ public: return mPointerProperties[pointerIndex].id; } - inline int32_t getToolType(size_t pointerIndex) const { + inline ToolType getToolType(size_t pointerIndex) const { return mPointerProperties[pointerIndex].toolType; } diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h index a1be542d7b..4f53c36d6f 100644 --- a/include/input/InputTransport.h +++ b/include/input/InputTransport.h @@ -669,7 +669,6 @@ private: static void addSample(MotionEvent* event, const InputMessage* msg); static bool canAddSample(const Batch& batch, const InputMessage* msg); static ssize_t findSampleNoLaterThan(const Batch& batch, nsecs_t time); - static bool shouldResampleTool(int32_t toolType); static bool isTouchResamplingEnabled(); }; diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index 53b22cb883..4dbf575490 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -79,25 +79,6 @@ const char* motionClassificationToString(MotionClassification classification) { } } -const char* motionToolTypeToString(int32_t toolType) { - switch (toolType) { - case AMOTION_EVENT_TOOL_TYPE_UNKNOWN: - return "UNKNOWN"; - case AMOTION_EVENT_TOOL_TYPE_FINGER: - return "FINGER"; - case AMOTION_EVENT_TOOL_TYPE_STYLUS: - return "STYLUS"; - case AMOTION_EVENT_TOOL_TYPE_MOUSE: - return "MOUSE"; - case AMOTION_EVENT_TOOL_TYPE_ERASER: - return "ERASER"; - case AMOTION_EVENT_TOOL_TYPE_PALM: - return "PALM"; - default: - return "INVALID"; - } -} - // --- IdGenerator --- #if defined(__ANDROID__) [[maybe_unused]] @@ -256,8 +237,8 @@ bool isFromSource(uint32_t source, uint32_t test) { return (source & test) == test; } -bool isStylusToolType(uint32_t toolType) { - return toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS || toolType == AMOTION_EVENT_TOOL_TYPE_ERASER; +bool isStylusToolType(ToolType toolType) { + return toolType == ToolType::STYLUS || toolType == ToolType::ERASER; } VerifiedKeyEvent verifiedKeyEventFromKeyEvent(const KeyEvent& event) { @@ -810,7 +791,7 @@ status_t MotionEvent::readFromParcel(Parcel* parcel) { mPointerProperties.push_back({}); PointerProperties& properties = mPointerProperties.back(); properties.id = parcel->readInt32(); - properties.toolType = parcel->readInt32(); + properties.toolType = static_cast(parcel->readInt32()); } while (sampleCount > 0) { @@ -866,7 +847,7 @@ status_t MotionEvent::writeToParcel(Parcel* parcel) const { for (size_t i = 0; i < pointerCount; i++) { const PointerProperties& properties = mPointerProperties[i]; parcel->writeInt32(properties.id); - parcel->writeInt32(properties.toolType); + parcel->writeInt32(static_cast(properties.toolType)); } const PointerCoords* pc = mSamplePointerCoords.data(); @@ -1030,9 +1011,9 @@ std::ostream& operator<<(std::ostream& out, const MotionEvent& event) { out << ", x[" << i << "]=" << x; out << ", y[" << i << "]=" << y; } - int toolType = event.getToolType(i); - if (toolType != AMOTION_EVENT_TOOL_TYPE_FINGER) { - out << ", toolType[" << i << "]=" << toolType; + ToolType toolType = event.getToolType(i); + if (toolType != ToolType::FINGER) { + out << ", toolType[" << i << "]=" << ftl::enum_string(toolType); } } if (event.getButtonState() != 0) { diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp index 311b2441a4..f6b4648d67 100644 --- a/libs/input/InputTransport.cpp +++ b/libs/input/InputTransport.cpp @@ -145,6 +145,10 @@ inline static const char* toString(bool value) { return value ? "true" : "false"; } +static bool shouldResampleTool(ToolType toolType) { + return toolType == ToolType::FINGER || toolType == ToolType::UNKNOWN; +} + // --- InputMessage --- bool InputMessage::isValid(size_t actualSize) const { @@ -1274,11 +1278,6 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, event->addSample(sampleTime, touchState.lastResample.pointers); } -bool InputConsumer::shouldResampleTool(int32_t toolType) { - return toolType == AMOTION_EVENT_TOOL_TYPE_FINGER - || toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN; -} - status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) { ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, "channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s", diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp index b4151c6ea1..3037573538 100644 --- a/libs/input/MotionPredictor.cpp +++ b/libs/input/MotionPredictor.cpp @@ -30,6 +30,7 @@ #include #include +#include #include namespace android { @@ -108,10 +109,10 @@ android::base::Result MotionPredictor::record(const MotionEvent& event) { return {}; } - const int32_t toolType = event.getPointerProperties(0)->toolType; - if (toolType != AMOTION_EVENT_TOOL_TYPE_STYLUS) { + const ToolType toolType = event.getPointerProperties(0)->toolType; + if (toolType != ToolType::STYLUS) { ALOGD_IF(isDebug(), "Prediction not supported for non-stylus tool: %s", - motionToolTypeToString(toolType)); + ftl::enum_string(toolType).c_str()); return {}; } diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp index 2132dc1bdf..59125dd428 100644 --- a/libs/input/tests/InputEvent_test.cpp +++ b/libs/input/tests/InputEvent_test.cpp @@ -259,10 +259,10 @@ void MotionEventTest::SetUp() { mPointerProperties[0].clear(); mPointerProperties[0].id = 1; - mPointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + mPointerProperties[0].toolType = ToolType::FINGER; mPointerProperties[1].clear(); mPointerProperties[1].id = 2; - mPointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + mPointerProperties[1].toolType = ToolType::STYLUS; mSamples[0].pointerCoords[0].clear(); mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 10); @@ -366,9 +366,9 @@ void MotionEventTest::assertEqualsEventWithHistory(const MotionEvent* event) { ASSERT_EQ(2U, event->getPointerCount()); ASSERT_EQ(1, event->getPointerId(0)); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, event->getToolType(0)); + ASSERT_EQ(ToolType::FINGER, event->getToolType(0)); ASSERT_EQ(2, event->getPointerId(1)); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, event->getToolType(1)); + ASSERT_EQ(ToolType::STYLUS, event->getToolType(1)); ASSERT_EQ(2U, event->getHistorySize()); @@ -692,7 +692,7 @@ TEST_F(MotionEventTest, Transform) { MotionEvent createMotionEvent(int32_t source, uint32_t action, float x, float y, float dx, float dy, const ui::Transform& transform, const ui::Transform& rawTransform) { std::vector pointerProperties; - pointerProperties.push_back(PointerProperties{/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER}); + pointerProperties.push_back(PointerProperties{/*id=*/0, ToolType::FINGER}); std::vector pointerCoords; pointerCoords.emplace_back().clear(); pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, x); diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp index 5d8b9700b3..965fda73b4 100644 --- a/libs/input/tests/InputPublisherAndConsumer_test.cpp +++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp @@ -172,7 +172,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() { for (size_t i = 0; i < pointerCount; i++) { pointerProperties[i].clear(); pointerProperties[i].id = (i + 2) % pointerCount; - pointerProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + pointerProperties[i].toolType = ToolType::FINGER; pointerCoords[i].clear(); pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, 100 * i); diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp index c61efbf9ed..7a62f5ec58 100644 --- a/libs/input/tests/MotionPredictor_test.cpp +++ b/libs/input/tests/MotionPredictor_test.cpp @@ -45,7 +45,7 @@ static MotionEvent getMotionEvent(int32_t action, float x, float y, PointerProperties properties; properties.clear(); properties.id = i; - properties.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + properties.toolType = ToolType::STYLUS; pointerProperties.push_back(properties); PointerCoords coords; coords.clear(); diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp index 7cb9526af0..655de803ae 100644 --- a/libs/input/tests/TouchResampling_test.cpp +++ b/libs/input/tests/TouchResampling_test.cpp @@ -99,7 +99,7 @@ void TouchResamplingTest::publishSimpleMotionEvent(int32_t action, nsecs_t event properties.push_back({}); properties.back().clear(); properties.back().id = pointer.id; - properties.back().toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + properties.back().toolType = ToolType::FINGER; coords.push_back({}); coords.back().clear(); diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp index 027757973b..ae721093a0 100644 --- a/libs/input/tests/VelocityTracker_test.cpp +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -212,7 +212,7 @@ static std::vector createTouchMotionEventStream( coords[pointerIndex].isResampled = position.isResampled; properties[pointerIndex].id = pointerId; - properties[pointerIndex].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + properties[pointerIndex].toolType = ToolType::FINGER; pointerIndex++; } EXPECT_EQ(pointerIndex, pointerCount); diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp index 0c93f5ce40..2437d0fcfc 100644 --- a/services/inputflinger/InputCommonConverter.cpp +++ b/services/inputflinger/InputCommonConverter.cpp @@ -200,17 +200,12 @@ static common::Button getButtonState(int32_t buttonState) { return static_cast(buttonState); } -static common::ToolType getToolType(int32_t toolType) { - static_assert(static_cast(AMOTION_EVENT_TOOL_TYPE_UNKNOWN) == - common::ToolType::UNKNOWN); - static_assert(static_cast(AMOTION_EVENT_TOOL_TYPE_FINGER) == - common::ToolType::FINGER); - static_assert(static_cast(AMOTION_EVENT_TOOL_TYPE_STYLUS) == - common::ToolType::STYLUS); - static_assert(static_cast(AMOTION_EVENT_TOOL_TYPE_MOUSE) == - common::ToolType::MOUSE); - static_assert(static_cast(AMOTION_EVENT_TOOL_TYPE_ERASER) == - common::ToolType::ERASER); +static common::ToolType getToolType(ToolType toolType) { + static_assert(static_cast(ToolType::UNKNOWN) == common::ToolType::UNKNOWN); + static_assert(static_cast(ToolType::FINGER) == common::ToolType::FINGER); + static_assert(static_cast(ToolType::STYLUS) == common::ToolType::STYLUS); + static_assert(static_cast(ToolType::MOUSE) == common::ToolType::MOUSE); + static_assert(static_cast(ToolType::ERASER) == common::ToolType::ERASER); return static_cast(toolType); } diff --git a/services/inputflinger/NotifyArgs.cpp b/services/inputflinger/NotifyArgs.cpp index b192ad73c3..5f2a22f467 100644 --- a/services/inputflinger/NotifyArgs.cpp +++ b/services/inputflinger/NotifyArgs.cpp @@ -161,9 +161,9 @@ std::string NotifyMotionArgs::dump() const { StringPrintf("id=%" PRIu32 " x=%.1f y=%.1f pressure=%.1f", pointerProperties[i].id, pointerCoords[i].getX(), pointerCoords[i].getY(), pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); - const int32_t toolType = pointerProperties[i].toolType; - if (toolType != AMOTION_EVENT_TOOL_TYPE_FINGER) { - coords += StringPrintf(" toolType=%s", motionToolTypeToString(toolType)); + const ToolType toolType = pointerProperties[i].toolType; + if (toolType != ToolType::FINGER) { + coords += StringPrintf(" toolType=%s", ftl::enum_string(toolType).c_str()); } const float major = pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR); const float minor = pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR); diff --git a/services/inputflinger/PreferStylusOverTouchBlocker.cpp b/services/inputflinger/PreferStylusOverTouchBlocker.cpp index ddd514676b..fbd296c131 100644 --- a/services/inputflinger/PreferStylusOverTouchBlocker.cpp +++ b/services/inputflinger/PreferStylusOverTouchBlocker.cpp @@ -24,11 +24,11 @@ static std::pair checkToolType(const NotifyMotionArgs& args) { bool hasTouch = false; for (size_t i = 0; i < args.pointerCount; i++) { // Make sure we are canceling stylus pointers - const int32_t toolType = args.pointerProperties[i].toolType; + const ToolType toolType = args.pointerProperties[i].toolType; if (isStylusToolType(toolType)) { hasStylus = true; } - if (toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) { + if (toolType == ToolType::FINGER) { hasTouch = true; } } diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp index c170b81475..ae20f862dc 100644 --- a/services/inputflinger/UnwantedInteractionBlocker.cpp +++ b/services/inputflinger/UnwantedInteractionBlocker.cpp @@ -18,6 +18,7 @@ #include "UnwantedInteractionBlocker.h" #include +#include #include #include #include @@ -98,18 +99,21 @@ static bool isPalmRejectionEnabled() { return false; } -static int getLinuxToolCode(int toolType) { +static int getLinuxToolCode(ToolType toolType) { switch (toolType) { - case AMOTION_EVENT_TOOL_TYPE_STYLUS: + case ToolType::STYLUS: return BTN_TOOL_PEN; - case AMOTION_EVENT_TOOL_TYPE_ERASER: + case ToolType::ERASER: return BTN_TOOL_RUBBER; - case AMOTION_EVENT_TOOL_TYPE_FINGER: - return BTN_TOOL_FINGER; - default: - ALOGW("Got tool type %" PRId32 ", converting to BTN_TOOL_FINGER", toolType); + case ToolType::FINGER: return BTN_TOOL_FINGER; + case ToolType::UNKNOWN: + case ToolType::MOUSE: + case ToolType::PALM: + break; } + ALOGW("Got tool type %s, converting to BTN_TOOL_FINGER", ftl::enum_string(toolType).c_str()); + return BTN_TOOL_FINGER; } static int32_t getActionUpForPointerId(const NotifyMotionArgs& args, int32_t pointerId) { diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp index f03c837f56..58324c4762 100644 --- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp +++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp @@ -207,7 +207,7 @@ static MotionEvent generateMotionEvent() { pointerProperties[0].clear(); pointerProperties[0].id = 0; - pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + pointerProperties[0].toolType = ToolType::FINGER; pointerCoords[0].clear(); pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 100); @@ -235,7 +235,7 @@ static NotifyMotionArgs generateMotionArgs() { pointerProperties[0].clear(); pointerProperties[0].id = 0; - pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + pointerProperties[0].toolType = ToolType::FINGER; pointerCoords[0].clear(); pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 100); diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index cd427f03cf..0f7991ae09 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -1862,11 +1862,12 @@ void InputDispatcher::logOutboundMotionDetails(const char* prefix, const MotionE entry.yPrecision, entry.downTime); for (uint32_t i = 0; i < entry.pointerCount; i++) { - ALOGD(" Pointer %d: id=%d, toolType=%d, " + ALOGD(" Pointer %d: id=%d, toolType=%s, " "x=%f, y=%f, pressure=%f, size=%f, " "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, " "orientation=%f", - i, entry.pointerProperties[i].id, entry.pointerProperties[i].toolType, + i, entry.pointerProperties[i].id, + ftl::enum_string(entry.pointerProperties[i].toolType).c_str(), entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X), entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y), entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), @@ -4178,7 +4179,7 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) { ALOGD(" Pointer %d: id=%d, toolType=%s, x=%f, y=%f, pressure=%f, size=%f, " "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, orientation=%f", i, args->pointerProperties[i].id, - motionToolTypeToString(args->pointerProperties[i].toolType), + ftl::enum_string(args->pointerProperties[i].toolType).c_str(), args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X), args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y), args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), diff --git a/services/inputflinger/reader/include/StylusState.h b/services/inputflinger/reader/include/StylusState.h index ff15e0c660..d042784c9e 100644 --- a/services/inputflinger/reader/include/StylusState.h +++ b/services/inputflinger/reader/include/StylusState.h @@ -33,8 +33,8 @@ struct StylusState { std::optional pressure{}; /* The state of the stylus buttons as a bitfield (e.g. AMOTION_EVENT_BUTTON_SECONDARY). */ uint32_t buttons{}; - /* Which tool type the stylus is currently using (e.g. AMOTION_EVENT_TOOL_TYPE_ERASER). */ - int32_t toolType{AMOTION_EVENT_TOOL_TYPE_UNKNOWN}; + /* Which tool type the stylus is currently using (e.g. ToolType::ERASER). */ + ToolType toolType{ToolType::UNKNOWN}; void clear() { *this = StylusState{}; } }; diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index 83cf28798c..a3fdcdf19d 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -350,7 +350,7 @@ std::list CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { PointerProperties pointerProperties; pointerProperties.clear(); pointerProperties.id = 0; - pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_MOUSE; + pointerProperties.toolType = ToolType::MOUSE; PointerCoords pointerCoords; pointerCoords.clear(); diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp index a44d15bcdf..99e6cf9a74 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp @@ -77,8 +77,8 @@ std::list ExternalStylusInputMapper::sync(nsecs_t when) { mStylusState.when = when; mStylusState.toolType = mTouchButtonAccumulator.getToolType(); - if (mStylusState.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { - mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + if (mStylusState.toolType == ToolType::UNKNOWN) { + mStylusState.toolType = ToolType::STYLUS; } if (mRawPressureAxis.valid) { diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp index 7724cf7ed5..f65cdcb677 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp @@ -321,7 +321,7 @@ std::list JoystickInputMapper::sync(nsecs_t when, nsecs_t readTime, PointerProperties pointerProperties; pointerProperties.clear(); pointerProperties.id = 0; - pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN; + pointerProperties.toolType = ToolType::UNKNOWN; PointerCoords pointerCoords; pointerCoords.clear(); diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp index 33e72c7c6f..e87128825d 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp @@ -77,7 +77,7 @@ void MultiTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { continue; } - if (inSlot.getToolType() == AMOTION_EVENT_TOOL_TYPE_PALM) { + if (inSlot.getToolType() == ToolType::PALM) { std::optional id = getActiveBitId(inSlot); if (id) { outState->rawPointerData.canceledIdBits.markBit(id.value()); @@ -112,12 +112,12 @@ void MultiTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { outPointer.tiltY = 0; outPointer.toolType = inSlot.getToolType(); - if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { + if (outPointer.toolType == ToolType::UNKNOWN) { outPointer.toolType = mTouchButtonAccumulator.getToolType(); - if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { - outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + if (outPointer.toolType == ToolType::UNKNOWN) { + outPointer.toolType = ToolType::FINGER; } - } else if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS && !mStylusMtToolSeen) { + } else if (outPointer.toolType == ToolType::STYLUS && !mStylusMtToolSeen) { mStylusMtToolSeen = true; // The multi-touch device produced a stylus event with MT_TOOL_PEN. Dynamically // re-configure this input device so that we add SOURCE_STYLUS if we haven't already. @@ -130,12 +130,11 @@ void MultiTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { bumpGeneration(); } } - if (shouldSimulateStylusWithTouch() && - outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) { - outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + if (shouldSimulateStylusWithTouch() && outPointer.toolType == ToolType::FINGER) { + outPointer.toolType = ToolType::STYLUS; } - bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE && + bool isHovering = mTouchButtonAccumulator.getToolType() != ToolType::MOUSE && (mTouchButtonAccumulator.isHovering() || (mRawPointerAxes.pressure.valid && inSlot.getPressure() <= 0)); outPointer.isHovering = isHovering; diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp index 94cc1457a9..c0a35b196e 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp @@ -121,7 +121,7 @@ std::list RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readT PointerProperties pointerProperties; pointerProperties.clear(); pointerProperties.id = 0; - pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN; + pointerProperties.toolType = ToolType::UNKNOWN; uint32_t policyFlags = 0; if (getDeviceContext().isExternal()) { diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp index 13ad224111..f13417a93b 100644 --- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp @@ -41,7 +41,7 @@ void SingleTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { outState->rawPointerData.pointerCount = 1; outState->rawPointerData.idToIndex[0] = 0; - bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE && + bool isHovering = mTouchButtonAccumulator.getToolType() != ToolType::MOUSE && (mTouchButtonAccumulator.isHovering() || (mRawPointerAxes.pressure.valid && mSingleTouchMotionAccumulator.getAbsolutePressure() <= 0)); @@ -61,8 +61,8 @@ void SingleTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { outPointer.tiltX = mSingleTouchMotionAccumulator.getAbsoluteTiltX(); outPointer.tiltY = mSingleTouchMotionAccumulator.getAbsoluteTiltY(); outPointer.toolType = mTouchButtonAccumulator.getToolType(); - if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { - outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + if (outPointer.toolType == ToolType::UNKNOWN) { + outPointer.toolType = ToolType::FINGER; } outPointer.isHovering = isHovering; } diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index df7ba49b4d..073c18babb 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -229,11 +229,12 @@ void TouchInputMapper::dump(std::string& dump) { dump += StringPrintf(INDENT4 "[%d]: id=%d, x=%d, y=%d, pressure=%d, " "touchMajor=%d, touchMinor=%d, toolMajor=%d, toolMinor=%d, " "orientation=%d, tiltX=%d, tiltY=%d, distance=%d, " - "toolType=%d, isHovering=%s\n", + "toolType=%s, isHovering=%s\n", i, pointer.id, pointer.x, pointer.y, pointer.pressure, pointer.touchMajor, pointer.touchMinor, pointer.toolMajor, pointer.toolMinor, pointer.orientation, pointer.tiltX, pointer.tiltY, - pointer.distance, pointer.toolType, toString(pointer.isHovering)); + pointer.distance, ftl::enum_string(pointer.toolType).c_str(), + toString(pointer.isHovering)); } dump += StringPrintf(INDENT3 "Last Cooked Button State: 0x%08x\n", @@ -248,7 +249,7 @@ void TouchInputMapper::dump(std::string& dump) { "pressure=%0.3f, touchMajor=%0.3f, touchMinor=%0.3f, " "toolMajor=%0.3f, toolMinor=%0.3f, " "orientation=%0.3f, tilt=%0.3f, distance=%0.3f, " - "toolType=%d, isHovering=%s\n", + "toolType=%s, isHovering=%s\n", i, pointerProperties.id, pointerCoords.getX(), pointerCoords.getY(), pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X), pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y), @@ -260,7 +261,7 @@ void TouchInputMapper::dump(std::string& dump) { pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION), pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TILT), pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE), - pointerProperties.toolType, + ftl::enum_string(pointerProperties.toolType).c_str(), toString(mLastCookedState.cookedPointerData.isHovering(i))); } @@ -1582,10 +1583,10 @@ std::list TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t re mCurrentRawState.rawPointerData.pointerForId(id); if (isStylusToolType(pointer.toolType)) { mCurrentCookedState.stylusIdBits.markBit(id); - } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER || - pointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { + } else if (pointer.toolType == ToolType::FINGER || + pointer.toolType == ToolType::UNKNOWN) { mCurrentCookedState.fingerIdBits.markBit(id); - } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_MOUSE) { + } else if (pointer.toolType == ToolType::MOUSE) { mCurrentCookedState.mouseIdBits.markBit(id); } } @@ -1704,7 +1705,7 @@ void TouchInputMapper::applyExternalStylusTouchState(nsecs_t when) { PointerCoords& coords = currentPointerData.editPointerCoordsWithId(*mFusedStylusPointerId); coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure); - if (mExternalStylusState.toolType != AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { + if (mExternalStylusState.toolType != ToolType::UNKNOWN) { PointerProperties& properties = currentPointerData.editPointerPropertiesWithId(*mFusedStylusPointerId); properties.toolType = mExternalStylusState.toolType; @@ -2678,7 +2679,7 @@ std::list TouchInputMapper::dispatchPointerGestures(nsecs_t when, ns PointerProperties pointerProperties; pointerProperties.clear(); pointerProperties.id = 0; - pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + pointerProperties.toolType = ToolType::FINGER; PointerCoords pointerCoords; pointerCoords.clear(); @@ -2887,7 +2888,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; mPointerGesture.currentGestureProperties[0].clear(); mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; - mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + mPointerGesture.currentGestureProperties[0].toolType = ToolType::FINGER; mPointerGesture.currentGestureCoords[0].clear(); mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x); mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y); @@ -2922,8 +2923,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerGesture.currentGestureProperties[0].clear(); mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; - mPointerGesture.currentGestureProperties[0].toolType = - AMOTION_EVENT_TOOL_TYPE_FINGER; + mPointerGesture.currentGestureProperties[0].toolType = ToolType::FINGER; mPointerGesture.currentGestureCoords[0].clear(); mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, mPointerGesture.tapX); @@ -3010,7 +3010,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; mPointerGesture.currentGestureProperties[0].clear(); mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; - mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + mPointerGesture.currentGestureProperties[0].toolType = ToolType::FINGER; mPointerGesture.currentGestureCoords[0].clear(); mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x); mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y); @@ -3040,9 +3040,10 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi uint32_t index = mPointerGesture.currentGestureIdToIndex[id]; const PointerProperties& properties = mPointerGesture.currentGestureProperties[index]; const PointerCoords& coords = mPointerGesture.currentGestureCoords[index]; - ALOGD(" currentGesture[%d]: index=%d, toolType=%d, " + ALOGD(" currentGesture[%d]: index=%d, toolType=%s, " "x=%0.3f, y=%0.3f, pressure=%0.3f", - id, index, properties.toolType, coords.getAxisValue(AMOTION_EVENT_AXIS_X), + id, index, ftl::enum_string(properties.toolType).c_str(), + coords.getAxisValue(AMOTION_EVENT_AXIS_X), coords.getAxisValue(AMOTION_EVENT_AXIS_Y), coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); } @@ -3051,9 +3052,10 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi uint32_t index = mPointerGesture.lastGestureIdToIndex[id]; const PointerProperties& properties = mPointerGesture.lastGestureProperties[index]; const PointerCoords& coords = mPointerGesture.lastGestureCoords[index]; - ALOGD(" lastGesture[%d]: index=%d, toolType=%d, " + ALOGD(" lastGesture[%d]: index=%d, toolType=%s, " "x=%0.3f, y=%0.3f, pressure=%0.3f", - id, index, properties.toolType, coords.getAxisValue(AMOTION_EVENT_AXIS_X), + id, index, ftl::enum_string(properties.toolType).c_str(), + coords.getAxisValue(AMOTION_EVENT_AXIS_X), coords.getAxisValue(AMOTION_EVENT_AXIS_Y), coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); } @@ -3342,7 +3344,7 @@ void TouchInputMapper::prepareMultiFingerPointerGestures(nsecs_t when, bool* can mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; mPointerGesture.currentGestureProperties[0].clear(); mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; - mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + mPointerGesture.currentGestureProperties[0].toolType = ToolType::FINGER; mPointerGesture.currentGestureCoords[0].clear(); mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, mPointerGesture.referenceGestureX); @@ -3435,7 +3437,7 @@ void TouchInputMapper::prepareMultiFingerPointerGestures(nsecs_t when, bool* can mPointerGesture.currentGestureProperties[i].clear(); mPointerGesture.currentGestureProperties[i].id = gestureId; - mPointerGesture.currentGestureProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + mPointerGesture.currentGestureProperties[i].toolType = ToolType::FINGER; mPointerGesture.currentGestureCoords[i].clear(); mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, mPointerGesture.referenceGestureX + diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index ae7faa9909..bc358b97e6 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -78,8 +78,8 @@ struct RawPointerData { int32_t distance{}; int32_t tiltX{}; int32_t tiltY{}; - // A fully decoded AMOTION_EVENT_TOOL_TYPE constant. - int32_t toolType{AMOTION_EVENT_TOOL_TYPE_UNKNOWN}; + // A fully decoded ToolType constant. + ToolType toolType{ToolType::UNKNOWN}; bool isHovering{false}; }; diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp index f6a42bdea0..f70be72741 100644 --- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp @@ -154,18 +154,18 @@ void MultiTouchMotionAccumulator::warnIfNotInUse(const RawEvent& event, const Sl // --- MultiTouchMotionAccumulator::Slot --- -int32_t MultiTouchMotionAccumulator::Slot::getToolType() const { +ToolType MultiTouchMotionAccumulator::Slot::getToolType() const { if (mHaveAbsMtToolType) { switch (mAbsMtToolType) { case MT_TOOL_FINGER: - return AMOTION_EVENT_TOOL_TYPE_FINGER; + return ToolType::FINGER; case MT_TOOL_PEN: - return AMOTION_EVENT_TOOL_TYPE_STYLUS; + return ToolType::STYLUS; case MT_TOOL_PALM: - return AMOTION_EVENT_TOOL_TYPE_PALM; + return ToolType::PALM; } } - return AMOTION_EVENT_TOOL_TYPE_UNKNOWN; + return ToolType::UNKNOWN; } } // namespace android diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h index 3c1a2a9e64..943dde5ca2 100644 --- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h @@ -45,7 +45,7 @@ public: inline int32_t getTrackingId() const { return mAbsMtTrackingId; } inline int32_t getPressure() const { return mAbsMtPressure; } inline int32_t getDistance() const { return mAbsMtDistance; } - int32_t getToolType() const; + ToolType getToolType() const; private: friend class MultiTouchMotionAccumulator; diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp index 6b84f32db2..8c4bed3267 100644 --- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp @@ -141,21 +141,21 @@ uint32_t TouchButtonAccumulator::getButtonState() const { return result; } -int32_t TouchButtonAccumulator::getToolType() const { +ToolType TouchButtonAccumulator::getToolType() const { if (mBtnToolMouse || mBtnToolLens) { - return AMOTION_EVENT_TOOL_TYPE_MOUSE; + return ToolType::MOUSE; } if (mBtnToolRubber) { - return AMOTION_EVENT_TOOL_TYPE_ERASER; + return ToolType::ERASER; } if (mBtnToolPen || mBtnToolBrush || mBtnToolPencil || mBtnToolAirbrush) { - return AMOTION_EVENT_TOOL_TYPE_STYLUS; + return ToolType::STYLUS; } if (mBtnToolFinger || mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap || mBtnToolQuintTap) { - return AMOTION_EVENT_TOOL_TYPE_FINGER; + return ToolType::FINGER; } - return AMOTION_EVENT_TOOL_TYPE_UNKNOWN; + return ToolType::UNKNOWN; } bool TouchButtonAccumulator::isToolActive() const { diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h index c2aa2adc0f..c5fd5f5168 100644 --- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h @@ -36,7 +36,7 @@ public: void process(const RawEvent* rawEvent); uint32_t getButtonState() const; - int32_t getToolType() const; + ToolType getToolType() const; bool isToolActive() const; bool isHovering() const; bool hasStylus() const; diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h index 2714d03ea3..70e8fb7758 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h @@ -99,10 +99,10 @@ private: // We never need any PointerProperties other than the finger tool type, so we can just keep a // const array of them. const std::array mFingerProps = {{ - {.id = 0, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER}, - {.id = 1, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER}, - {.id = 2, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER}, - {.id = 3, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER}, + {.id = 0, .toolType = ToolType::FINGER}, + {.id = 1, .toolType = ToolType::FINGER}, + {.id = 2, .toolType = ToolType::FINGER}, + {.id = 3, .toolType = ToolType::FINGER}, }}; std::array mFakeFingerCoords = {}; diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp index d344babe72..e89262a71a 100644 --- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp @@ -85,7 +85,7 @@ SelfContainedHardwareState HardwareStateConverter::produceHardwareState(nsecs_t MultiTouchMotionAccumulator::Slot slot = mMotionAccumulator.getSlot(i); // Some touchpads continue to report contacts even after they've identified them as palms. // We want to exclude these contacts from the HardwareStates. - if (!slot.isInUse() || slot.getToolType() == AMOTION_EVENT_TOOL_TYPE_PALM) { + if (!slot.isInUse() || slot.getToolType() == ToolType::PALM) { continue; } diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp index 33f404d56b..c6d541e962 100644 --- a/services/inputflinger/tests/GestureConverter_test.cpp +++ b/services/inputflinger/tests/GestureConverter_test.cpp @@ -93,7 +93,7 @@ TEST_F(GestureConverterTest, Move) { ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithButtonState(0), + WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f))); ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10)); @@ -111,7 +111,7 @@ TEST_F(GestureConverterTest, Move_Rotated) { ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(POINTER_X + 10, POINTER_Y + 5), WithRelativeMotion(10, 5), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithButtonState(0), + WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f))); ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X + 10, POINTER_Y + 5)); @@ -133,14 +133,14 @@ TEST_F(GestureConverterTest, ButtonsChange) { WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(POINTER_X, POINTER_Y), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); args.pop_front(); ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(POINTER_X, POINTER_Y), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); args.pop_front(); ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), @@ -148,7 +148,7 @@ TEST_F(GestureConverterTest, ButtonsChange) { WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(POINTER_X, POINTER_Y), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); // Then release the left button Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, @@ -162,7 +162,7 @@ TEST_F(GestureConverterTest, ButtonsChange) { WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(POINTER_X, POINTER_Y), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); // Finally release the right button Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, @@ -175,12 +175,12 @@ TEST_F(GestureConverterTest, ButtonsChange) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0), WithCoords(POINTER_X, POINTER_Y), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); args.pop_front(); ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0), WithCoords(POINTER_X, POINTER_Y), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); } TEST_F(GestureConverterTest, DragWithButton) { @@ -198,14 +198,14 @@ TEST_F(GestureConverterTest, DragWithButton) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(POINTER_X, POINTER_Y), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); args.pop_front(); ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(POINTER_X, POINTER_Y), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); // Move Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); @@ -215,7 +215,7 @@ TEST_F(GestureConverterTest, DragWithButton) { ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), + WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f))); ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10)); @@ -231,12 +231,12 @@ TEST_F(GestureConverterTest, DragWithButton) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0), WithCoords(POINTER_X - 5, POINTER_Y + 10), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); args.pop_front(); ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0), WithCoords(POINTER_X - 5, POINTER_Y + 10), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); } TEST_F(GestureConverterTest, Scroll) { @@ -252,7 +252,7 @@ TEST_F(GestureConverterTest, Scroll) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y), WithGestureScrollDistance(0, 0, EPSILON), WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithDownTime(downTime), + WithToolType(ToolType::FINGER), WithDownTime(downTime), WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))); args.pop_front(); ASSERT_THAT(std::get(args.front()), @@ -260,7 +260,7 @@ TEST_F(GestureConverterTest, Scroll) { WithCoords(POINTER_X, POINTER_Y - 10), WithGestureScrollDistance(0, 10, EPSILON), WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), + WithToolType(ToolType::FINGER), WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))); Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); @@ -271,7 +271,7 @@ TEST_F(GestureConverterTest, Scroll) { WithCoords(POINTER_X, POINTER_Y - 15), WithGestureScrollDistance(0, 5, EPSILON), WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), + WithToolType(ToolType::FINGER), WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, @@ -283,7 +283,7 @@ TEST_F(GestureConverterTest, Scroll) { WithCoords(POINTER_X, POINTER_Y - 15), WithGestureScrollDistance(0, 0, EPSILON), WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), + WithToolType(ToolType::FINGER), WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))); } @@ -301,14 +301,14 @@ TEST_F(GestureConverterTest, Scroll_Rotated) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y), WithGestureScrollDistance(0, 0, EPSILON), WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithDownTime(downTime))); + WithToolType(ToolType::FINGER), WithDownTime(downTime))); args.pop_front(); ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(POINTER_X - 10, POINTER_Y), WithGestureScrollDistance(0, 10, EPSILON), WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture); @@ -318,7 +318,7 @@ TEST_F(GestureConverterTest, Scroll_Rotated) { WithCoords(POINTER_X - 15, POINTER_Y), WithGestureScrollDistance(0, 5, EPSILON), WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, GESTURES_FLING_START); @@ -329,7 +329,7 @@ TEST_F(GestureConverterTest, Scroll_Rotated) { WithCoords(POINTER_X - 15, POINTER_Y), WithGestureScrollDistance(0, 0, EPSILON), WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); } TEST_F(GestureConverterTest, Scroll_ClearsClassificationAndOffsetsAfterGesture) { @@ -393,7 +393,7 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(1u), WithToolType(ToolType::FINGER))); PointerCoords finger0Start = arg.pointerCoords[0]; args.pop_front(); arg = std::get(args.front()); @@ -402,7 +402,7 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), WithGestureOffset(0, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(2u), WithToolType(ToolType::FINGER))); PointerCoords finger1Start = arg.pointerCoords[1]; args.pop_front(); arg = std::get(args.front()); @@ -411,7 +411,7 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), WithGestureOffset(0, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(3u), WithToolType(ToolType::FINGER))); PointerCoords finger2Start = arg.pointerCoords[2]; args.pop_front(); @@ -420,7 +420,7 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithGestureOffset(0, -0.01, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(3u), WithToolType(ToolType::FINGER))); EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX()); EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX()); EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX()); @@ -437,7 +437,7 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithGestureOffset(0, -0.005, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(3u), WithToolType(ToolType::FINGER))); EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX()); EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX()); EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX()); @@ -453,19 +453,19 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), WithGestureOffset(0, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(3u), WithToolType(ToolType::FINGER))); args.pop_front(); ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), WithGestureOffset(0, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(2u), WithToolType(ToolType::FINGER))); args.pop_front(); ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(1u), WithToolType(ToolType::FINGER))); } TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) { @@ -560,7 +560,7 @@ TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(1u), WithToolType(ToolType::FINGER))); PointerCoords finger0Start = arg.pointerCoords[0]; args.pop_front(); arg = std::get(args.front()); @@ -569,7 +569,7 @@ TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), WithGestureOffset(0, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(2u), WithToolType(ToolType::FINGER))); PointerCoords finger1Start = arg.pointerCoords[1]; args.pop_front(); arg = std::get(args.front()); @@ -578,7 +578,7 @@ TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), WithGestureOffset(0, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(3u), WithToolType(ToolType::FINGER))); PointerCoords finger2Start = arg.pointerCoords[2]; args.pop_front(); arg = std::get(args.front()); @@ -587,7 +587,7 @@ TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), WithGestureOffset(0, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(4u), WithToolType(ToolType::FINGER))); PointerCoords finger3Start = arg.pointerCoords[3]; args.pop_front(); @@ -596,7 +596,7 @@ TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithGestureOffset(0.01, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(4u), WithToolType(ToolType::FINGER))); EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 10); EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 10); EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 10); @@ -615,7 +615,7 @@ TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithGestureOffset(0.005, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(4u), WithToolType(ToolType::FINGER))); EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 15); EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 15); EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 15); @@ -633,26 +633,26 @@ TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), WithGestureOffset(0, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(4u), WithToolType(ToolType::FINGER))); args.pop_front(); ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), WithGestureOffset(0, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(3u), WithToolType(ToolType::FINGER))); args.pop_front(); ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), WithGestureOffset(0, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(2u), WithToolType(ToolType::FINGER))); args.pop_front(); ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(1u), WithToolType(ToolType::FINGER))); } TEST_F(GestureConverterTest, Pinch_Inwards) { @@ -668,7 +668,7 @@ TEST_F(GestureConverterTest, Pinch_Inwards) { WithMotionClassification(MotionClassification::PINCH), WithGesturePinchScaleFactor(1.0f, EPSILON), WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); args.pop_front(); ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | @@ -676,7 +676,7 @@ TEST_F(GestureConverterTest, Pinch_Inwards) { WithMotionClassification(MotionClassification::PINCH), WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 0.8, GESTURES_ZOOM_UPDATE); @@ -688,7 +688,7 @@ TEST_F(GestureConverterTest, Pinch_Inwards) { WithGesturePinchScaleFactor(0.8f, EPSILON), WithPointerCoords(0, POINTER_X - 80, POINTER_Y), WithPointerCoords(1, POINTER_X + 80, POINTER_Y), WithPointerCount(2u), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, GESTURES_ZOOM_END); @@ -699,13 +699,13 @@ TEST_F(GestureConverterTest, Pinch_Inwards) { 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), WithMotionClassification(MotionClassification::PINCH), WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); args.pop_front(); ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithMotionClassification(MotionClassification::PINCH), WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); } TEST_F(GestureConverterTest, Pinch_Outwards) { @@ -721,7 +721,7 @@ TEST_F(GestureConverterTest, Pinch_Outwards) { WithMotionClassification(MotionClassification::PINCH), WithGesturePinchScaleFactor(1.0f, EPSILON), WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); args.pop_front(); ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | @@ -729,7 +729,7 @@ TEST_F(GestureConverterTest, Pinch_Outwards) { WithMotionClassification(MotionClassification::PINCH), WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1.2, GESTURES_ZOOM_UPDATE); @@ -741,7 +741,7 @@ TEST_F(GestureConverterTest, Pinch_Outwards) { WithGesturePinchScaleFactor(1.2f, EPSILON), WithPointerCoords(0, POINTER_X - 120, POINTER_Y), WithPointerCoords(1, POINTER_X + 120, POINTER_Y), WithPointerCount(2u), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, GESTURES_ZOOM_END); @@ -752,13 +752,13 @@ TEST_F(GestureConverterTest, Pinch_Outwards) { 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), WithMotionClassification(MotionClassification::PINCH), WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); args.pop_front(); ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithMotionClassification(MotionClassification::PINCH), WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); } TEST_F(GestureConverterTest, Pinch_ClearsClassificationAndScaleFactorAfterGesture) { @@ -802,18 +802,18 @@ TEST_F(GestureConverterTest, ResetWithButtonPressed) { WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(POINTER_X, POINTER_Y), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); args.pop_front(); EXPECT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0), WithCoords(POINTER_X, POINTER_Y), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); args.pop_front(); ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0), WithCoords(POINTER_X, POINTER_Y), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); } TEST_F(GestureConverterTest, ResetDuringScroll) { @@ -830,7 +830,7 @@ TEST_F(GestureConverterTest, ResetDuringScroll) { WithCoords(POINTER_X, POINTER_Y - 10), WithGestureScrollDistance(0, 0, EPSILON), WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), + WithToolType(ToolType::FINGER), WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))); } @@ -849,19 +849,19 @@ TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) { 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), WithGestureOffset(0, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(3u), WithToolType(ToolType::FINGER))); args.pop_front(); EXPECT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), WithGestureOffset(0, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(2u), WithToolType(ToolType::FINGER))); args.pop_front(); EXPECT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithPointerCount(1u), WithToolType(ToolType::FINGER))); } TEST_F(GestureConverterTest, ResetDuringPinch) { @@ -879,13 +879,13 @@ TEST_F(GestureConverterTest, ResetDuringPinch) { 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), WithMotionClassification(MotionClassification::PINCH), WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); args.pop_front(); EXPECT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithMotionClassification(MotionClassification::PINCH), WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))); + WithToolType(ToolType::FINGER))); } } // namespace android diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index e2996437bf..a58ad84e90 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -1464,7 +1464,7 @@ static InputEventInjectionResult injectKeyUp(const std::unique_ptrnotifyMotion(&( args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) .build())); // Second touch pointer down mDispatcher->notifyMotion(&( args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) - .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(110).y(100)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(110).y(100)) .build())); // First touch pointer lifts. The second one remains down mDispatcher->notifyMotion(&( args = MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) - .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(110).y(100)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(110).y(100)) .build())); window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); @@ -2069,8 +2069,8 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -2085,8 +2085,8 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT, @@ -2102,7 +2102,7 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/* id */ 1, - AMOTION_EVENT_TOOL_TYPE_FINGER) + ToolType::FINGER) .x(100) .y(100)) .build(), @@ -2154,8 +2154,8 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(300).y(100)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -2179,8 +2179,8 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { const MotionEvent secondFingerMoveEvent = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(310).y(110)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(310).y(110)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT, @@ -2274,15 +2274,15 @@ TEST_F(InputDispatcherTest, TwoPointerCancelInconsistentPolicy) { args = MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) .policyFlags(DEFAULT_POLICY_FLAGS) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) .build())); mDispatcher->notifyMotion(&( args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) .policyFlags(DEFAULT_POLICY_FLAGS) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) - .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(120).y(120)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120)) .build())); spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); spyWindow->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); @@ -2294,8 +2294,8 @@ TEST_F(InputDispatcherTest, TwoPointerCancelInconsistentPolicy) { args = MotionArgsBuilder(AMOTION_EVENT_ACTION_CANCEL, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) .policyFlags(0) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) - .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(120).y(120)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120)) .build())); spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)); window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)); @@ -2313,7 +2313,7 @@ TEST_F(InputDispatcherTest, TwoPointerCancelInconsistentPolicy) { args = MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) .policyFlags(DEFAULT_POLICY_FLAGS) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) .build())); spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); @@ -2358,7 +2358,7 @@ TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { .deviceId(mouseDeviceId) .downTime(baseTime + 10) .eventTime(baseTime + 20) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE) .x(300) .y(100)) .build())); @@ -2372,7 +2372,7 @@ TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { .deviceId(mouseDeviceId) .downTime(baseTime + 10) .eventTime(baseTime + 30) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE) .x(110) .y(100)) .build())); @@ -2386,7 +2386,7 @@ TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { .deviceId(touchDeviceId) .downTime(baseTime + 40) .eventTime(baseTime + 40) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(0, ToolType::FINGER) .x(100) .y(100)) .build())); @@ -2401,7 +2401,7 @@ TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { .deviceId(touchDeviceId) .downTime(baseTime + 40) .eventTime(baseTime + 50) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(0, ToolType::FINGER) .x(100) .y(100)) .build())); @@ -2415,7 +2415,7 @@ TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { .deviceId(touchDeviceId) .downTime(baseTime + 60) .eventTime(baseTime + 60) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(0, ToolType::FINGER) .x(300) .y(100)) .build())); @@ -2429,7 +2429,7 @@ TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { .deviceId(touchDeviceId) .downTime(baseTime + 60) .eventTime(baseTime + 70) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(0, ToolType::FINGER) .x(300) .y(100)) .build())); @@ -2467,7 +2467,7 @@ TEST_F(InputDispatcherTest, MultiDeviceSplitTouch) { mDispatcher->notifyMotion(&( args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) .deviceId(mouseDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) .build())); leftWindow->consumeMotionEvent( AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); @@ -2477,7 +2477,7 @@ TEST_F(InputDispatcherTest, MultiDeviceSplitTouch) { args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) .deviceId(mouseDeviceId) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) .build())); leftWindow->consumeMotionEvent( @@ -2490,7 +2490,7 @@ TEST_F(InputDispatcherTest, MultiDeviceSplitTouch) { .deviceId(mouseDeviceId) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) .build())); leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); @@ -2498,7 +2498,7 @@ TEST_F(InputDispatcherTest, MultiDeviceSplitTouch) { mDispatcher->notifyMotion(&( args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) .build())); leftWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL)); @@ -2508,8 +2508,8 @@ TEST_F(InputDispatcherTest, MultiDeviceSplitTouch) { mDispatcher->notifyMotion(&( args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) - .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100)) .build())); leftWindow->consumeMotionEvent( AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); @@ -2547,21 +2547,21 @@ TEST_F(InputDispatcherTest, MixedTouchAndMouseWithPointerDown) { mDispatcher->notifyMotion(&( args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) .build())); // Second touch pointer down mDispatcher->notifyMotion(&( args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) - .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(350).y(100)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100)) .build())); // First touch pointer lifts. The second one remains down mDispatcher->notifyMotion(&( args = MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) - .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(350).y(100)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100)) .build())); window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); @@ -2572,7 +2572,7 @@ TEST_F(InputDispatcherTest, MixedTouchAndMouseWithPointerDown) { args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) .deviceId(mouseDeviceId) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(320).y(100)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(320).y(100)) .build())); window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId), @@ -2584,7 +2584,7 @@ TEST_F(InputDispatcherTest, MixedTouchAndMouseWithPointerDown) { .deviceId(mouseDeviceId) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(320).y(100)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(320).y(100)) .build())); window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); @@ -2592,8 +2592,8 @@ TEST_F(InputDispatcherTest, MixedTouchAndMouseWithPointerDown) { mDispatcher->notifyMotion(&( args = MotionArgsBuilder(POINTER_0_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) - .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(350).y(100)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100)) .build())); // The pointer_down event should be ignored window->assertNoEvents(); @@ -2619,7 +2619,7 @@ TEST_F(InputDispatcherTest, UnfinishedInjectedEvent) { injectMotionEvent(mDispatcher, MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) .deviceId(ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE) .x(50) .y(50)) .build())); @@ -2631,7 +2631,7 @@ TEST_F(InputDispatcherTest, UnfinishedInjectedEvent) { mDispatcher->notifyMotion(&( args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) .build())); window->consumeMotionEvent( @@ -2673,7 +2673,7 @@ TEST_F(InputDispatcherTest, HoverTapAndSplitTouch) { MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) .deviceId(mouseDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE) .x(50) .y(50)) .build())); @@ -2685,7 +2685,7 @@ TEST_F(InputDispatcherTest, HoverTapAndSplitTouch) { MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(0, ToolType::FINGER) .x(100) .y(100)) .build())); @@ -2695,7 +2695,7 @@ TEST_F(InputDispatcherTest, HoverTapAndSplitTouch) { MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(0, ToolType::FINGER) .x(100) .y(100)) .build())); @@ -2709,7 +2709,7 @@ TEST_F(InputDispatcherTest, HoverTapAndSplitTouch) { MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(0, ToolType::FINGER) .x(300) .y(100)) .build())); @@ -2720,10 +2720,10 @@ TEST_F(InputDispatcherTest, HoverTapAndSplitTouch) { injectMotionEvent(mDispatcher, MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(0, ToolType::FINGER) .x(300) .y(100)) - .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(1, ToolType::FINGER) .x(100) .y(100)) .build())); @@ -2756,7 +2756,7 @@ TEST_F(InputDispatcherTest, StylusHoverAndTouchTap) { MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) .deviceId(stylusDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS) .x(50) .y(50)) .build())); @@ -2768,7 +2768,7 @@ TEST_F(InputDispatcherTest, StylusHoverAndTouchTap) { MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(0, ToolType::FINGER) .x(100) .y(100)) .build())); @@ -2781,7 +2781,7 @@ TEST_F(InputDispatcherTest, StylusHoverAndTouchTap) { MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) .deviceId(stylusDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS) .x(50) .y(50)) .build())); @@ -2794,7 +2794,7 @@ TEST_F(InputDispatcherTest, StylusHoverAndTouchTap) { MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(0, ToolType::FINGER) .x(100) .y(100)) .build())); @@ -2806,7 +2806,7 @@ TEST_F(InputDispatcherTest, StylusHoverAndTouchTap) { MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) .deviceId(stylusDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS) .x(50) .y(50)) .build())); @@ -2839,40 +2839,40 @@ TEST_F(InputDispatcherTest, StylusHoverAndDownNoInputChannel) { // Start hovering with stylus mDispatcher->notifyMotion( &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) .build())); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); // Stop hovering mDispatcher->notifyMotion( &(args = MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) .build())); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); // Stylus touches down mDispatcher->notifyMotion( &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) .build())); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); // Stylus goes up mDispatcher->notifyMotion( &(args = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_STYLUS) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) .build())); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_UP)); // Again hover mDispatcher->notifyMotion( &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) .build())); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); // Stop hovering mDispatcher->notifyMotion( &(args = MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) .build())); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); @@ -2908,7 +2908,7 @@ TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { mDispatcher->notifyMotion(&( args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) .deviceId(mouseDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) .build())); spyWindow->consumeMotionEvent( AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); @@ -2919,7 +2919,7 @@ TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { mDispatcher->notifyMotion( &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) .build())); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); @@ -2929,7 +2929,7 @@ TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { mDispatcher->notifyMotion( &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(55).y(55)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(55).y(55)) .build())); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); window->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); @@ -2941,7 +2941,7 @@ TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { mDispatcher->notifyMotion( &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(60).y(60)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(60).y(60)) .build())); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); @@ -2950,7 +2950,7 @@ TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) .deviceId(mouseDeviceId) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) .build())); spyWindow->consumeMotionEvent( @@ -2964,7 +2964,7 @@ TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { .deviceId(mouseDeviceId) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) .build())); spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); @@ -2974,7 +2974,7 @@ TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE) .deviceId(mouseDeviceId) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(110).y(110)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110)) .build())); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); window->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); @@ -2983,7 +2983,7 @@ TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { mDispatcher->notifyMotion( &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(65).y(65)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(65).y(65)) .build())); // No more events @@ -3129,9 +3129,7 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(900) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(900).y(400)) .build())); windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); @@ -3140,9 +3138,7 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); @@ -3152,9 +3148,7 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); windowLeft->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); windowLeft->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); @@ -3165,9 +3159,7 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { AINPUT_SOURCE_MOUSE) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); @@ -3177,9 +3169,7 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { AINPUT_SOURCE_MOUSE) .buttonState(0) .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE)); @@ -3187,9 +3177,7 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE) .buttonState(0) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); windowLeft->consumeMotionUp(ADISPLAY_ID_DEFAULT); @@ -3198,9 +3186,7 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(900) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(900).y(400)) .build())); windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); @@ -3230,14 +3216,14 @@ TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) { mDispatcher->notifyMotion(&( args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) .build())); mDispatcher->notifyMotion(&( args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) - .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(120).y(120)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120)) .build())); window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); @@ -3247,7 +3233,7 @@ TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) { args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) .deviceId(mouseDeviceId) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(300).y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId), WithPointerCount(2u))); @@ -3258,7 +3244,7 @@ TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) { .deviceId(mouseDeviceId) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(300).y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); @@ -3267,8 +3253,8 @@ TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) { mDispatcher->notifyMotion(&( args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(101).y(101)) - .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(121).y(121)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(101).y(101)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(121).y(121)) .build())); window->assertNoEvents(); } @@ -3293,7 +3279,7 @@ TEST_F(InputDispatcherTest, HoverWithSpyWindows) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE) .x(100) .y(100)) .build())); @@ -3327,7 +3313,7 @@ TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE) .x(100) .y(100)) .build())); @@ -3337,7 +3323,7 @@ TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE) .x(110) .y(110)) .build())); @@ -3356,7 +3342,7 @@ TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) { MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(SECOND_DEVICE_ID) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(0, ToolType::FINGER) .x(200) .y(200)) .build())); @@ -3380,7 +3366,7 @@ TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) { MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(SECOND_DEVICE_ID) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(0, ToolType::FINGER) .x(200) .y(200)) .build())); @@ -3397,7 +3383,7 @@ TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) { MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(SECOND_DEVICE_ID) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(0, ToolType::FINGER) .x(250) .y(250)) .build())); @@ -3412,7 +3398,7 @@ TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) { MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(SECOND_DEVICE_ID) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(0, ToolType::FINGER) .x(250) .y(250)) .build())); @@ -3441,9 +3427,7 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); // Inject a series of mouse events for a mouse click @@ -3451,9 +3435,7 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); @@ -3464,9 +3446,7 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { AINPUT_SOURCE_MOUSE) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); @@ -3476,9 +3456,7 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { AINPUT_SOURCE_MOUSE) .buttonState(0) .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE)); @@ -3486,9 +3464,7 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE) .buttonState(0) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); window->consumeMotionUp(ADISPLAY_ID_DEFAULT); @@ -3496,9 +3472,7 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_MOUSE) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) - .x(300) - .y(400)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); window->assertNoEvents(); } @@ -3521,7 +3495,7 @@ TEST_F(InputDispatcherTest, HoverExitIsSentToRemovedWindow) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE) .x(300) .y(400)) .build())); @@ -3551,7 +3525,7 @@ TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) { mDispatcher->notifyMotion( &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) .deviceId(mouseDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(10).y(10)) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(10).y(10)) .build())); window->consumeMotionEvent( AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); @@ -3560,7 +3534,7 @@ TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) { mDispatcher->notifyMotion( &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) .build())); window->consumeMotionEvent( @@ -3633,7 +3607,7 @@ TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) { MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) .displayId(ADISPLAY_ID_DEFAULT) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE) .x(300) .y(600)) .build())); @@ -3654,7 +3628,7 @@ TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) { MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) .displayId(ADISPLAY_ID_DEFAULT) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE) .x(400) .y(700)) .build())); @@ -3911,8 +3885,8 @@ TEST_F(InputDispatcherTest, NonSplitTouchableWindowReceivesMultiTouch) { MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(-30).y(-50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(-30).y(-50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -4033,7 +4007,7 @@ TEST_F(InputDispatcherDisplayProjectionTest, InjectionWithTransformInLogicalDisp MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER) .x(untransformedPoint.x) .y(untransformedPoint.y)) .build(); @@ -6076,7 +6050,7 @@ TEST_F(InputDispatcherOnPointerDownOutsideFocus, NoFocusChangeFlag) { const MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(20).y(20)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(20).y(20)) .addFlag(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, event)) @@ -7937,7 +7911,7 @@ protected: MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_STYLUS) .buttonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS) .x(50) .y(50)) .build())); @@ -7949,7 +7923,7 @@ protected: MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) .pointer(PointerBuilder(MOUSE_POINTER_ID, - AMOTION_EVENT_TOOL_TYPE_MOUSE) + ToolType::MOUSE) .x(50) .y(50)) .build())); @@ -8038,8 +8012,8 @@ TEST_F(InputDispatcherDragTests, DragEnterAndPointerDownPilfersPointers) { const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(60).y(60)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(60).y(60)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -8093,9 +8067,7 @@ TEST_F(InputDispatcherDragTests, StylusDragAndDrop) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS) .buttonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS) - .x(50) - .y(50)) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) .build())) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); @@ -8107,9 +8079,7 @@ TEST_F(InputDispatcherDragTests, StylusDragAndDrop) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS) .buttonState(0) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS) - .x(150) - .y(50)) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50)) .build())) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); @@ -8122,9 +8092,7 @@ TEST_F(InputDispatcherDragTests, StylusDragAndDrop) { injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_STYLUS) .buttonState(0) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS) - .x(150) - .y(50)) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50)) .build())) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); @@ -8182,8 +8150,8 @@ TEST_F(InputDispatcherDragTests, NoDragAndDropWhenMultiFingers) { MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(75).y(50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(75).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -8209,8 +8177,8 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) { MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(50)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -8225,8 +8193,8 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) { const MotionEvent secondFingerMoveEvent = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(50)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT, @@ -8239,8 +8207,8 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) { const MotionEvent secondFingerUpEvent = MotionEventBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(50)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT, @@ -8265,9 +8233,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenMultiDisplays) { MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(SECOND_DISPLAY_ID) - .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER) - .x(100) - .y(100)) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) .build())); windowInSecondary->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN, SECOND_DISPLAY_ID, /*expectedFlag=*/0); @@ -8311,7 +8277,7 @@ TEST_F(InputDispatcherDragTests, MouseDragAndDrop) { MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) .pointer(PointerBuilder(MOUSE_POINTER_ID, - AMOTION_EVENT_TOOL_TYPE_MOUSE) + ToolType::MOUSE) .x(50) .y(50)) .build())) @@ -8326,7 +8292,7 @@ TEST_F(InputDispatcherDragTests, MouseDragAndDrop) { MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE) .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) .pointer(PointerBuilder(MOUSE_POINTER_ID, - AMOTION_EVENT_TOOL_TYPE_MOUSE) + ToolType::MOUSE) .x(150) .y(50)) .build())) @@ -8341,7 +8307,7 @@ TEST_F(InputDispatcherDragTests, MouseDragAndDrop) { MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE) .buttonState(0) .pointer(PointerBuilder(MOUSE_POINTER_ID, - AMOTION_EVENT_TOOL_TYPE_MOUSE) + ToolType::MOUSE) .x(150) .y(50)) .build())) @@ -8813,8 +8779,8 @@ TEST_F(InputDispatcherSpyWindowTest, ReceivesMultiplePointers) { const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -8845,8 +8811,8 @@ TEST_F(InputDispatcherSpyWindowTest, ReceivesSecondPointerAsDown) { const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -8884,8 +8850,8 @@ TEST_F(InputDispatcherSpyWindowTest, SplitIfNoForegroundWindowTouched) { MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(200)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -9007,8 +8973,8 @@ TEST_F(InputDispatcherPilferPointersTest, ContinuesToReceiveGestureAfterPilfer) MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(200)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -9022,9 +8988,9 @@ TEST_F(InputDispatcherPilferPointersTest, ContinuesToReceiveGestureAfterPilfer) MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(200)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) - .pointer(PointerBuilder(/*id=*/2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(-5).y(-5)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(-5).y(-5)) .build(); ASSERT_EQ(InputEventInjectionResult::FAILED, injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -9058,8 +9024,8 @@ TEST_F(InputDispatcherPilferPointersTest, PartiallyPilferRequiredPointers) { MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(150)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(10).y(10)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -9073,9 +9039,9 @@ TEST_F(InputDispatcherPilferPointersTest, PartiallyPilferRequiredPointers) { MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10)) - .pointer(PointerBuilder(/*id=*/2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(150)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(10).y(10)) + .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -9119,8 +9085,8 @@ TEST_F(InputDispatcherPilferPointersTest, PilferAllRequiredPointers) { MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(10).y(10)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -9166,8 +9132,8 @@ TEST_F(InputDispatcherPilferPointersTest, CanReceivePointersAfterPilfer) { MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .displayId(ADISPLAY_ID_DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) - .pointer(PointerBuilder(/*id=*/0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10)) - .pointer(PointerBuilder(/*id=*/1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(150)) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(10).y(10)) + .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, @@ -9221,7 +9187,7 @@ public: NotifyMotionArgs motionArgs = generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT, {PointF{30, 40}}); - motionArgs.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + motionArgs.pointerProperties[0].toolType = ToolType::STYLUS; mDispatcher->notifyMotion(&motionArgs); } }; diff --git a/services/inputflinger/tests/InputProcessorConverter_test.cpp b/services/inputflinger/tests/InputProcessorConverter_test.cpp index 161a24ff70..4b42f4b141 100644 --- a/services/inputflinger/tests/InputProcessorConverter_test.cpp +++ b/services/inputflinger/tests/InputProcessorConverter_test.cpp @@ -30,7 +30,7 @@ static NotifyMotionArgs generateBasicMotionArgs() { // Create a basic motion event for testing PointerProperties properties; properties.id = 0; - properties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + properties.toolType = ToolType::FINGER; PointerCoords coords; coords.clear(); diff --git a/services/inputflinger/tests/InputProcessor_test.cpp b/services/inputflinger/tests/InputProcessor_test.cpp index b6deed8aae..0ffdef9daa 100644 --- a/services/inputflinger/tests/InputProcessor_test.cpp +++ b/services/inputflinger/tests/InputProcessor_test.cpp @@ -37,7 +37,7 @@ static NotifyMotionArgs generateBasicMotionArgs() { // Create a basic motion event for testing PointerProperties properties; properties.id = 0; - properties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + properties.toolType = ToolType::FINGER; PointerCoords coords; coords.clear(); diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 853c5b0115..92d5357d8e 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -1732,7 +1732,7 @@ TEST_F(TouchIntegrationTest, NotifiesPolicyWhenStylusGestureStarted) { mDevice->sendSync(); ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + WithToolType(ToolType::STYLUS)))); ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId())); @@ -1751,7 +1751,7 @@ TEST_F(TouchIntegrationTest, NotifiesPolicyWhenStylusGestureStarted) { mDevice->sendSync(); ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)))); + WithToolType(ToolType::FINGER)))); ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotNotified()); @@ -1768,7 +1768,7 @@ TEST_F(TouchIntegrationTest, NotifiesPolicyWhenStylusGestureStarted) { mDevice->sendSync(); ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + WithToolType(ToolType::STYLUS)))); ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId())); } @@ -1864,12 +1864,12 @@ TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsSurroundingTouchGe TestFixture::mTouchscreen->sendSync(); ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithToolType(ToolType::STYLUS), WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY), WithDeviceId(touchscreenId)))); ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithToolType(ToolType::STYLUS), WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY), WithDeviceId(touchscreenId)))); @@ -1877,11 +1877,11 @@ TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsSurroundingTouchGe TestFixture::mTouchscreen->sendSync(); ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0), + WithToolType(ToolType::STYLUS), WithButtonState(0), WithDeviceId(touchscreenId)))); ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0), + WithToolType(ToolType::STYLUS), WithButtonState(0), WithDeviceId(touchscreenId)))); // Release the stylus button. @@ -1896,7 +1896,7 @@ TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsSurroundingHoverin const auto touchscreenId = TestFixture::mTouchscreenInfo.getId(); const auto stylusId = TestFixture::mStylusInfo.getId(); auto toolTypeDevice = - AllOf(WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithDeviceId(touchscreenId)); + AllOf(WithToolType(ToolType::STYLUS), WithDeviceId(touchscreenId)); // Press the stylus button. TestFixture::mStylus->pressKey(BTN_STYLUS); @@ -1980,7 +1980,7 @@ TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsWithinTouchGesture TestFixture::mTouchscreen->sendSync(); ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0), + WithToolType(ToolType::STYLUS), WithButtonState(0), WithDeviceId(touchscreenId)))); // Press and release a stylus button. Each change in button state also generates a MOVE event. @@ -1990,12 +1990,12 @@ TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsWithinTouchGesture WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId)))); ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithToolType(ToolType::STYLUS), WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY), WithDeviceId(touchscreenId)))); ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithToolType(ToolType::STYLUS), WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY), WithDeviceId(touchscreenId)))); @@ -2005,11 +2005,11 @@ TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsWithinTouchGesture WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId)))); ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0), + WithToolType(ToolType::STYLUS), WithButtonState(0), WithDeviceId(touchscreenId)))); ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0), + WithToolType(ToolType::STYLUS), WithButtonState(0), WithDeviceId(touchscreenId)))); // Finish the stylus gesture. @@ -2017,7 +2017,7 @@ TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsWithinTouchGesture TestFixture::mTouchscreen->sendSync(); ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0), + WithToolType(ToolType::STYLUS), WithButtonState(0), WithDeviceId(touchscreenId)))); } @@ -2039,7 +2039,7 @@ TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonMotionEventsDisable TestFixture::mTouchscreen->sendSync(); ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0), + WithToolType(ToolType::STYLUS), WithButtonState(0), WithDeviceId(touchscreenId)))); // Press and release a stylus button. Each change only generates a MOVE motion event. @@ -2050,7 +2050,7 @@ TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonMotionEventsDisable WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId)))); ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0), + WithToolType(ToolType::STYLUS), WithButtonState(0), WithDeviceId(touchscreenId)))); TestFixture::mStylus->releaseKey(BTN_STYLUS); @@ -2059,7 +2059,7 @@ TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonMotionEventsDisable WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId)))); ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0), + WithToolType(ToolType::STYLUS), WithButtonState(0), WithDeviceId(touchscreenId)))); // Finish the stylus gesture. @@ -2067,7 +2067,7 @@ TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonMotionEventsDisable TestFixture::mTouchscreen->sendSync(); ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0), + WithToolType(ToolType::STYLUS), WithButtonState(0), WithDeviceId(touchscreenId)))); } @@ -2108,7 +2108,7 @@ TEST_F(ExternalStylusIntegrationTest, DISABLED_FusedExternalStylusPressureReport mDevice->sendSync(); ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0), + WithToolType(ToolType::STYLUS), WithButtonState(0), WithDeviceId(touchscreenId), WithPressure(100.f / RAW_PRESSURE_MAX)))); // Change the pressure on the external stylus, and ensure the touchscreen generates a MOVE @@ -2116,7 +2116,7 @@ TEST_F(ExternalStylusIntegrationTest, DISABLED_FusedExternalStylusPressureReport stylus->setPressure(200); ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0), + WithToolType(ToolType::STYLUS), WithButtonState(0), WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX)))); // The external stylus did not generate any events. @@ -2162,7 +2162,7 @@ TEST_F(ExternalStylusIntegrationTest, DISABLED_FusedExternalStylusPressureNotRep // it shows up as a finger pointer. ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithDeviceId(touchscreenId), + WithToolType(ToolType::FINGER), WithDeviceId(touchscreenId), WithPressure(1.f)))); // Change the pressure on the external stylus. Since the pressure was not present at the start @@ -2175,7 +2175,7 @@ TEST_F(ExternalStylusIntegrationTest, DISABLED_FusedExternalStylusPressureNotRep mDevice->sendSync(); ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)))); + WithToolType(ToolType::FINGER)))); // Start a new gesture. Since we have a valid pressure value, it shows up as a stylus. mDevice->sendTrackingId(FIRST_TRACKING_ID); @@ -2184,7 +2184,7 @@ TEST_F(ExternalStylusIntegrationTest, DISABLED_FusedExternalStylusPressureNotRep mDevice->sendSync(); ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0), + WithToolType(ToolType::STYLUS), WithButtonState(0), WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX)))); // The external stylus did not generate any events. @@ -2220,7 +2220,7 @@ TEST_F(ExternalStylusIntegrationTest, DISABLED_UnfusedExternalStylus) { mTestListener ->assertNotifyMotionWasCalled(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithToolType( - AMOTION_EVENT_TOOL_TYPE_FINGER), + ToolType::FINGER), WithButtonState(0), WithDeviceId(touchscreenId), WithPressure(1.f)), @@ -3875,7 +3875,7 @@ TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaStat ASSERT_EQ(0, args.edgeFlags); ASSERT_EQ(uint32_t(1), args.pointerCount); ASSERT_EQ(0, args.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f)); ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision); ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision); @@ -3893,7 +3893,7 @@ TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaStat ASSERT_EQ(0, args.edgeFlags); ASSERT_EQ(uint32_t(1), args.pointerCount); ASSERT_EQ(0, args.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 1.0f)); ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision); ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision); @@ -3914,7 +3914,7 @@ TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaStat ASSERT_EQ(0, args.edgeFlags); ASSERT_EQ(uint32_t(1), args.pointerCount); ASSERT_EQ(0, args.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision); ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision); @@ -3932,7 +3932,7 @@ TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaStat ASSERT_EQ(0, args.edgeFlags); ASSERT_EQ(uint32_t(1), args.pointerCount); ASSERT_EQ(0, args.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::MOUSE, args.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertCursorPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f)); ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision); ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision); @@ -5195,7 +5195,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfB ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); @@ -5219,7 +5219,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfB ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); @@ -5242,7 +5242,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfB ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); @@ -5292,7 +5292,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenTouchStartsOutsideDisplayAndMoves ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); @@ -5315,7 +5315,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenTouchStartsOutsideDisplayAndMoves ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); @@ -5360,7 +5360,7 @@ TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture_VirtualDispl ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x, VIRTUAL_DISPLAY_WIDTH), toDisplayY(y, VIRTUAL_DISPLAY_HEIGHT), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -5387,7 +5387,7 @@ TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture_VirtualDispl ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x, VIRTUAL_DISPLAY_WIDTH), toDisplayY(y, VIRTUAL_DISPLAY_HEIGHT), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -5412,7 +5412,7 @@ TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture_VirtualDispl ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x, VIRTUAL_DISPLAY_WIDTH), toDisplayY(y, VIRTUAL_DISPLAY_HEIGHT), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -5455,7 +5455,7 @@ TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture) { ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); @@ -5480,7 +5480,7 @@ TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture) { ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); @@ -5503,7 +5503,7 @@ TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture) { ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); @@ -6151,14 +6151,14 @@ TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // eraser processKey(mapper, BTN_TOOL_RUBBER, 1); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::ERASER, motionArgs.pointerProperties[0].toolType); // stylus processKey(mapper, BTN_TOOL_RUBBER, 0); @@ -6166,7 +6166,7 @@ TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); // brush processKey(mapper, BTN_TOOL_PEN, 0); @@ -6174,7 +6174,7 @@ TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); // pencil processKey(mapper, BTN_TOOL_BRUSH, 0); @@ -6182,7 +6182,7 @@ TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); // air-brush processKey(mapper, BTN_TOOL_PENCIL, 0); @@ -6190,7 +6190,7 @@ TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); // mouse processKey(mapper, BTN_TOOL_AIRBRUSH, 0); @@ -6198,7 +6198,7 @@ TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::MOUSE, motionArgs.pointerProperties[0].toolType); // lens processKey(mapper, BTN_TOOL_MOUSE, 0); @@ -6206,7 +6206,7 @@ TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::MOUSE, motionArgs.pointerProperties[0].toolType); // double-tap processKey(mapper, BTN_TOOL_LENS, 0); @@ -6214,7 +6214,7 @@ TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // triple-tap processKey(mapper, BTN_TOOL_DOUBLETAP, 0); @@ -6222,7 +6222,7 @@ TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // quad-tap processKey(mapper, BTN_TOOL_TRIPLETAP, 0); @@ -6230,7 +6230,7 @@ TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // finger processKey(mapper, BTN_TOOL_QUADTAP, 0); @@ -6238,28 +6238,28 @@ TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // stylus trumps finger processKey(mapper, BTN_TOOL_PEN, 1); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); // eraser trumps stylus processKey(mapper, BTN_TOOL_RUBBER, 1); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::ERASER, motionArgs.pointerProperties[0].toolType); // mouse trumps eraser processKey(mapper, BTN_TOOL_MOUSE, 1); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::MOUSE, motionArgs.pointerProperties[0].toolType); // back to default tool type processKey(mapper, BTN_TOOL_MOUSE, 0); @@ -6269,7 +6269,7 @@ TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); } TEST_F(SingleTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIsZero) { @@ -6659,7 +6659,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenConfigEnabled_ShouldShowDirectSty processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithToolType(ToolType::STYLUS), WithPointerCoords(0, toDisplayX(100), toDisplayY(200))))); ASSERT_TRUE(fakePointerController->isPointerShown()); ASSERT_NO_FATAL_FAILURE( @@ -6683,7 +6683,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirec processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithToolType(ToolType::STYLUS), WithPointerCoords(0, toDisplayX(100), toDisplayY(200))))); ASSERT_FALSE(fakePointerController->isPointerShown()); } @@ -7125,7 +7125,7 @@ public: mStylusState.when = ARBITRARY_TIME; mStylusState.pressure = 0.f; - mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + mStylusState.toolType = ToolType::STYLUS; mReader->getContext()->setExternalStylusDevices({mExternalStylusDeviceInfo}); configureDevice(InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE); processExternalStylusState(mapper); @@ -7149,7 +7149,7 @@ protected: void testStartFusedStylusGesture(SingleTouchInputMapper& mapper) { auto toolTypeSource = - AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)); + AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS)); // The first pointer is withheld. processDown(mapper, 100, 200); @@ -7184,7 +7184,7 @@ protected: processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(EXPECTED_SOURCE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + WithToolType(ToolType::STYLUS)))); mStylusState.pressure = 0.f; processExternalStylusState(mapper); @@ -7194,7 +7194,7 @@ protected: void testUnsuccessfulFusionGesture(SingleTouchInputMapper& mapper) { auto toolTypeSource = - AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)); + AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::FINGER)); // The first pointer is withheld when an external stylus is connected, // and a timeout is requested. @@ -7252,7 +7252,7 @@ TEST_F(ExternalStylusFusionTest, SuccessfulFusion_TouchFirst) { TEST_F(ExternalStylusFusionTest, SuccessfulFusion_PressureFirst) { SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); auto toolTypeSource = - AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)); + AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS)); // The external stylus reports pressure first. It is ignored for now. mStylusState.pressure = 1.f; @@ -7295,7 +7295,7 @@ TEST_F(ExternalStylusFusionTest, FusionIsRepeatedForEachNewGesture) { TEST_F(ExternalStylusFusionTest, FusedPointerReportsPressureChanges) { SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); auto toolTypeSource = - AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)); + AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS)); mStylusState.pressure = 0.8f; processExternalStylusState(mapper); @@ -7357,7 +7357,7 @@ TEST_F(ExternalStylusFusionTest, FusedPointerReportsPressureChanges) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(EXPECTED_SOURCE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + WithToolType(ToolType::STYLUS)))); ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); @@ -7368,17 +7368,17 @@ TEST_F(ExternalStylusFusionTest, FusedPointerReportsToolTypeChanges) { auto source = WithSource(EXPECTED_SOURCE); mStylusState.pressure = 1.f; - mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_ERASER; + mStylusState.toolType = ToolType::ERASER; processExternalStylusState(mapper); processDown(mapper, 100, 200); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithToolType(AMOTION_EVENT_TOOL_TYPE_ERASER)))); + WithToolType(ToolType::ERASER)))); ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); // The external stylus reports a tool change. We wait for some time for a touch event. - mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + mStylusState.toolType = ToolType::STYLUS; processExternalStylusState(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); ASSERT_NO_FATAL_FAILURE( @@ -7389,11 +7389,11 @@ TEST_F(ExternalStylusFusionTest, FusedPointerReportsToolTypeChanges) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + WithToolType(ToolType::STYLUS)))); ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); // There is another tool type change. - mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + mStylusState.toolType = ToolType::FINGER; processExternalStylusState(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); ASSERT_NO_FATAL_FAILURE( @@ -7404,13 +7404,13 @@ TEST_F(ExternalStylusFusionTest, FusedPointerReportsToolTypeChanges) { handleTimeout(mapper, ARBITRARY_TIME + TOUCH_DATA_TIMEOUT); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)))); + WithToolType(ToolType::FINGER)))); processUp(mapper); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)))); + WithToolType(ToolType::FINGER)))); ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); @@ -7419,7 +7419,7 @@ TEST_F(ExternalStylusFusionTest, FusedPointerReportsToolTypeChanges) { TEST_F(ExternalStylusFusionTest, FusedPointerReportsButtons) { SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); auto toolTypeSource = - AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)); + AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS)); ASSERT_NO_FATAL_FAILURE(testStartFusedStylusGesture(mapper)); @@ -7636,7 +7636,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); @@ -7655,9 +7655,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7686,9 +7686,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7715,9 +7715,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7738,7 +7738,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(1, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); @@ -7763,7 +7763,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(1, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); @@ -7790,9 +7790,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7819,9 +7819,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7842,7 +7842,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); @@ -7865,7 +7865,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin ASSERT_EQ(0, motionArgs.edgeFlags); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON); @@ -7953,7 +7953,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -7961,9 +7961,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -7983,9 +7983,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -8002,9 +8002,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(ACTION_POINTER_0_UP, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -8014,7 +8014,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(1, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -8029,7 +8029,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(1, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -8047,9 +8047,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(ACTION_POINTER_0_DOWN, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -8066,9 +8066,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(ACTION_POINTER_1_UP, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -8078,7 +8078,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -8090,7 +8090,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -8123,7 +8123,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -8131,9 +8131,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -8151,9 +8151,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -8171,9 +8171,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(ACTION_POINTER_0_UP, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -8183,7 +8183,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(1, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -8196,7 +8196,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(1, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -8212,9 +8212,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(ACTION_POINTER_0_DOWN, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -8232,9 +8232,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(ACTION_POINTER_1_UP, motionArgs.action); ASSERT_EQ(size_t(2), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1, motionArgs.pointerProperties[1].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1], @@ -8244,7 +8244,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -8256,7 +8256,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); ASSERT_EQ(size_t(1), motionArgs.pointerCount); ASSERT_EQ(0, motionArgs.pointerProperties[0].id); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0)); @@ -8783,14 +8783,14 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // eraser processKey(mapper, BTN_TOOL_RUBBER, 1); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::ERASER, motionArgs.pointerProperties[0].toolType); // stylus processKey(mapper, BTN_TOOL_RUBBER, 0); @@ -8798,7 +8798,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); // brush processKey(mapper, BTN_TOOL_PEN, 0); @@ -8806,7 +8806,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); // pencil processKey(mapper, BTN_TOOL_BRUSH, 0); @@ -8814,7 +8814,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); // air-brush processKey(mapper, BTN_TOOL_PENCIL, 0); @@ -8822,7 +8822,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); // mouse processKey(mapper, BTN_TOOL_AIRBRUSH, 0); @@ -8830,7 +8830,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::MOUSE, motionArgs.pointerProperties[0].toolType); // lens processKey(mapper, BTN_TOOL_MOUSE, 0); @@ -8838,7 +8838,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::MOUSE, motionArgs.pointerProperties[0].toolType); // double-tap processKey(mapper, BTN_TOOL_LENS, 0); @@ -8846,7 +8846,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // triple-tap processKey(mapper, BTN_TOOL_DOUBLETAP, 0); @@ -8854,7 +8854,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // quad-tap processKey(mapper, BTN_TOOL_TRIPLETAP, 0); @@ -8862,7 +8862,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // finger processKey(mapper, BTN_TOOL_QUADTAP, 0); @@ -8870,42 +8870,42 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // stylus trumps finger processKey(mapper, BTN_TOOL_PEN, 1); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); // eraser trumps stylus processKey(mapper, BTN_TOOL_RUBBER, 1); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::ERASER, motionArgs.pointerProperties[0].toolType); // mouse trumps eraser processKey(mapper, BTN_TOOL_MOUSE, 1); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::MOUSE, motionArgs.pointerProperties[0].toolType); // MT tool type trumps BTN tool types: MT_TOOL_FINGER processToolType(mapper, MT_TOOL_FINGER); // this is the first time we send MT_TOOL_TYPE processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // MT tool type trumps BTN tool types: MT_TOOL_PEN processToolType(mapper, MT_TOOL_PEN); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::STYLUS, motionArgs.pointerProperties[0].toolType); // back to default tool type processToolType(mapper, -1); // use a deliberately undefined tool type, for testing @@ -8916,7 +8916,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); } TEST_F(MultiTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIsZero) { @@ -9531,7 +9531,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleSingleTouch) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // finger move processId(mapper, 1); @@ -9539,14 +9539,14 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleSingleTouch) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // finger up. processId(mapper, -1); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // new finger down processId(mapper, 1); @@ -9554,7 +9554,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleSingleTouch) { processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); } /** @@ -9576,7 +9576,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_SinglePointer processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // Tool changed to MT_TOOL_PALM expect sending the cancel event. processToolType(mapper, MT_TOOL_PALM); @@ -9602,7 +9602,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_SinglePointer processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); } /** @@ -9624,7 +9624,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_TwoPointers) processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // Second finger down. processSlot(mapper, SECOND_SLOT); @@ -9633,7 +9633,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_TwoPointers) processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[1].toolType); // If the tool type of the first finger changes to MT_TOOL_PALM, // we expect to receive ACTION_POINTER_UP with cancel flag. @@ -9699,7 +9699,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_ShouldCancelW processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // Second finger down. processSlot(mapper, SECOND_SLOT); @@ -9708,7 +9708,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_ShouldCancelW processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // If the tool type of the first finger changes to MT_TOOL_PALM, // we expect to receive ACTION_POINTER_UP with cancel flag. @@ -9743,7 +9743,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_ShouldCancelW processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(uint32_t(1), motionArgs.pointerCount); // third finger move @@ -9797,7 +9797,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_KeepFirstPoin processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // Second finger down. processSlot(mapper, SECOND_SLOT); @@ -9806,7 +9806,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_KeepFirstPoin processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); // If the tool type of the second finger changes to MT_TOOL_PALM, // we expect to receive ACTION_POINTER_UP with cancel flag. @@ -10003,7 +10003,7 @@ TEST_F(MultiTouchInputMapperTest, StylusSourceIsAddedDynamicallyFromToolType) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + WithToolType(ToolType::STYLUS)))); // Now that we know the device supports styluses, ensure that the device is re-configured with // the stylus source. @@ -10025,7 +10025,7 @@ TEST_F(MultiTouchInputMapperTest, StylusSourceIsAddedDynamicallyFromToolType) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + WithToolType(ToolType::STYLUS)))); } TEST_F(MultiTouchInputMapperTest, Process_WhenConfigEnabled_ShouldShowDirectStylusPointer) { @@ -10048,7 +10048,7 @@ TEST_F(MultiTouchInputMapperTest, Process_WhenConfigEnabled_ShouldShowDirectStyl processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithToolType(ToolType::STYLUS), WithPointerCoords(0, toDisplayX(100), toDisplayY(200))))); ASSERT_TRUE(fakePointerController->isPointerShown()); ASSERT_NO_FATAL_FAILURE( @@ -10075,7 +10075,7 @@ TEST_F(MultiTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirect processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithToolType(ToolType::STYLUS), WithPointerCoords(0, toDisplayX(100), toDisplayY(200))))); ASSERT_FALSE(fakePointerController->isPointerShown()); } @@ -10468,7 +10468,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthSwipe) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(1U, motionArgs.pointerCount); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); ASSERT_NO_FATAL_FAILURE( assertPointerCoords(motionArgs.pointerCoords[0], 0, 0, 1, 0, 0, 0, 0, 0, 0, 0)); @@ -10490,7 +10490,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthSwipe) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(1U, motionArgs.pointerCount); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(MotionClassification::TWO_FINGER_SWIPE, motionArgs.classification); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], 0, movingDistance * mPointerMovementScale, 1, 0, 0, 0, @@ -10528,7 +10528,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthLowResolutionSwipe) ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(1U, motionArgs.pointerCount); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); ASSERT_NO_FATAL_FAILURE( assertPointerCoords(motionArgs.pointerCoords[0], 0, 0, 1, 0, 0, 0, 0, 0, 0, 0)); @@ -10550,7 +10550,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthLowResolutionSwipe) ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(1U, motionArgs.pointerCount); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(MotionClassification::TWO_FINGER_SWIPE, motionArgs.classification); // New coordinate is the scaled relative coordinate from the initial coordinate. ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], 0, @@ -10584,7 +10584,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthFreeform) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(1U, motionArgs.pointerCount); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); // One pointer for PRESS, and its coordinate is used as the origin for pointer coordinates. ASSERT_NO_FATAL_FAILURE( @@ -10610,15 +10610,15 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthFreeform) { ASSERT_EQ(1U, motionArgs.pointerCount); ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, motionArgs.action); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(1U, motionArgs.pointerCount); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); ASSERT_EQ(2U, motionArgs.pointerCount); ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN, motionArgs.action & AMOTION_EVENT_ACTION_MASK); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); // Two pointers' scaled relative coordinates from their initial centroid. // Initial y coordinates are 0 as y1 and y2 have the same value. @@ -10648,7 +10648,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthFreeform) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(2U, motionArgs.pointerCount); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType); + ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType); ASSERT_EQ(MotionClassification::NONE, motionArgs.classification); ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], cookedX1, movingDistance * 2 * mPointerMovementScale, 1, 0, 0, @@ -10718,12 +10718,12 @@ TEST_F(MultiTouchPointerModeTest, WhenViewportActiveStatusChanged_PointerGesture ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + WithToolType(ToolType::STYLUS)))); // TODO(b/257078296): Pointer mode generates extra event. ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + WithToolType(ToolType::STYLUS)))); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); // Make the viewport inactive. This will put the device in disabled mode, and the ongoing stylus @@ -10735,12 +10735,12 @@ TEST_F(MultiTouchPointerModeTest, WhenViewportActiveStatusChanged_PointerGesture ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL), WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + WithToolType(ToolType::STYLUS)))); // TODO(b/257078296): Pointer mode generates extra event. ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL), WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), - WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + WithToolType(ToolType::STYLUS)))); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } diff --git a/services/inputflinger/tests/NotifyArgs_test.cpp b/services/inputflinger/tests/NotifyArgs_test.cpp index 671558509d..15367568ab 100644 --- a/services/inputflinger/tests/NotifyArgs_test.cpp +++ b/services/inputflinger/tests/NotifyArgs_test.cpp @@ -54,7 +54,7 @@ TEST(NotifyMotionArgsTest, TestCopyAssignmentOperator) { for (size_t i = 0; i < pointerCount; i++) { pointerProperties[i].clear(); pointerProperties[i].id = i; - pointerProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + pointerProperties[i].toolType = ToolType::FINGER; pointerCoords[i].clear(); pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, x++); diff --git a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp index 9014dfb48b..9818176cb0 100644 --- a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp +++ b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp @@ -45,8 +45,8 @@ static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, PointerCoords pointerCoords[pointerCount]; const int32_t deviceId = isFromSource(source, TOUCHSCREEN) ? TOUCH_DEVICE_ID : STYLUS_DEVICE_ID; - const int32_t toolType = isFromSource(source, TOUCHSCREEN) ? AMOTION_EVENT_TOOL_TYPE_FINGER - : AMOTION_EVENT_TOOL_TYPE_STYLUS; + const ToolType toolType = + isFromSource(source, TOUCHSCREEN) ? ToolType::FINGER : ToolType::STYLUS; for (size_t i = 0; i < pointerCount; i++) { pointerProperties[i].clear(); pointerProperties[i].id = i; @@ -278,20 +278,20 @@ TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchPointersAreIgnored) { // Event from a stylus device, but with finger tool type args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/1, DOWN, {{1, 2}}, STYLUS); // Keep source stylus, but make the tool type touch - args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + args.pointerProperties[0].toolType = ToolType::FINGER; assertNotBlocked(args); // Second pointer (stylus pointer) goes down, from the same device args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/2, POINTER_1_DOWN, {{1, 2}, {10, 20}}, STYLUS); // Keep source stylus, but make the tool type touch - args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args.pointerProperties[0].toolType = ToolType::STYLUS; assertNotBlocked(args); // Second pointer (stylus pointer) goes down, from the same device args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/3, MOVE, {{2, 3}, {11, 21}}, STYLUS); // Keep source stylus, but make the tool type touch - args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + args.pointerProperties[0].toolType = ToolType::FINGER; assertNotBlocked(args); } @@ -418,14 +418,14 @@ TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchDeviceIsCanceledAtFirst) { // Introduce a stylus pointer into the device 1 stream. It should be ignored. args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/3, POINTER_1_DOWN, {{1, 2}, {3, 4}}, TOUCHSCREEN); - args.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args.pointerProperties[1].toolType = ToolType::STYLUS; args.source = STYLUS; assertDropped(args); // Lift up touch from the mixed touch/stylus device args = generateMotionArgs(/*downTime=*/1, /*eventTime=*/4, CANCEL, {{1, 2}, {3, 4}}, TOUCHSCREEN); - args.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args.pointerProperties[1].toolType = ToolType::STYLUS; args.source = STYLUS; assertDropped(args); diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h index 09f7ae8b1c..338b74766e 100644 --- a/services/inputflinger/tests/TestInputListenerMatchers.h +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -138,8 +138,8 @@ MATCHER_P(WithPressure, pressure, "InputEvent with specified pressure") { MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") { const auto argToolType = arg.pointerProperties[0].toolType; - *result_listener << "expected tool type " << motionToolTypeToString(toolType) << ", but got " - << motionToolTypeToString(argToolType); + *result_listener << "expected tool type " << ftl::enum_string(toolType) << ", but got " + << ftl::enum_string(argToolType); return argToolType == toolType; } diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp index 3f749b1fed..2a9ace00c5 100644 --- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp +++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp @@ -79,7 +79,7 @@ static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, for (size_t i = 0; i < pointerCount; i++) { pointerProperties[i].clear(); pointerProperties[i].id = i; - pointerProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + pointerProperties[i].toolType = ToolType::FINGER; pointerCoords[i].clear(); pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, points[i].x); @@ -507,7 +507,7 @@ TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenResetHappens) { TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenStylusSourceWithFingerToolIsReceived) { mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}}); - args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + args.pointerProperties[0].toolType = ToolType::FINGER; args.source = AINPUT_SOURCE_STYLUS; mBlocker->notifyMotion(&args); } @@ -548,15 +548,15 @@ TEST_F(UnwantedInteractionBlockerTest, StylusAfterTouchWorks) { // Now touch down stylus args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 20, 30}}); - args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args.pointerProperties[0].toolType = ToolType::STYLUS; args.source |= AINPUT_SOURCE_STYLUS; mBlocker->notifyMotion(&args); args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/4, MOVE, {{40, 50, 60}}); - args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args.pointerProperties[0].toolType = ToolType::STYLUS; args.source |= AINPUT_SOURCE_STYLUS; mBlocker->notifyMotion(&args); args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/5, UP, {{40, 50, 60}}); - args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args.pointerProperties[0].toolType = ToolType::STYLUS; args.source |= AINPUT_SOURCE_STYLUS; mBlocker->notifyMotion(&args); } @@ -617,14 +617,14 @@ TEST_F(UnwantedInteractionBlockerTest, StylusIsNotBlocked) { info.addSource(AINPUT_SOURCE_STYLUS); mBlocker->notifyInputDevicesChanged({info}); NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); - args1.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args1.pointerProperties[0].toolType = ToolType::STYLUS; mBlocker->notifyMotion(&args1); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN)); // Move the stylus, setting large TOUCH_MAJOR/TOUCH_MINOR dimensions NotifyMotionArgs args2 = generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}}); - args2.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args2.pointerProperties[0].toolType = ToolType::STYLUS; mBlocker->notifyMotion(&args2); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE)); @@ -632,7 +632,7 @@ TEST_F(UnwantedInteractionBlockerTest, StylusIsNotBlocked) { // it's a palm. NotifyMotionArgs args3 = generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}); - args3.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args3.pointerProperties[0].toolType = ToolType::STYLUS; mBlocker->notifyMotion(&args3); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(UP)); } @@ -655,21 +655,21 @@ TEST_F(UnwantedInteractionBlockerTest, TouchIsBlockedWhenMixedWithStylus) { // Stylus pointer down NotifyMotionArgs args2 = generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, POINTER_1_DOWN, {{1, 2, 3}, {10, 20, 30}}); - args2.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args2.pointerProperties[1].toolType = ToolType::STYLUS; mBlocker->notifyMotion(&args2); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(POINTER_1_DOWN)); // Large touch oval on the next finger move NotifyMotionArgs args3 = generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, MOVE, {{1, 2, 300}, {11, 21, 30}}); - args3.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args3.pointerProperties[1].toolType = ToolType::STYLUS; mBlocker->notifyMotion(&args3); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE)); // Lift up the finger pointer. It should be canceled due to the heuristic filter. NotifyMotionArgs args4 = generateMotionArgs(/*downTime=*/0, 3 * RESAMPLE_PERIOD, POINTER_0_UP, {{1, 2, 300}, {11, 21, 30}}); - args4.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args4.pointerProperties[1].toolType = ToolType::STYLUS; mBlocker->notifyMotion(&args4); mTestListener.assertNotifyMotionWasCalled( AllOf(WithMotionAction(POINTER_0_UP), WithFlags(FLAG_CANCELED))); @@ -677,7 +677,7 @@ TEST_F(UnwantedInteractionBlockerTest, TouchIsBlockedWhenMixedWithStylus) { NotifyMotionArgs args5 = generateMotionArgs(/*downTime=*/0, 4 * RESAMPLE_PERIOD, MOVE, {{12, 22, 30}}); args5.pointerProperties[0].id = args4.pointerProperties[1].id; - args5.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args5.pointerProperties[0].toolType = ToolType::STYLUS; mBlocker->notifyMotion(&args5); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE)); @@ -685,7 +685,7 @@ TEST_F(UnwantedInteractionBlockerTest, TouchIsBlockedWhenMixedWithStylus) { NotifyMotionArgs args6 = generateMotionArgs(/*downTime=*/0, 5 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}); args6.pointerProperties[0].id = args4.pointerProperties[1].id; - args6.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; + args6.pointerProperties[0].toolType = ToolType::STYLUS; mBlocker->notifyMotion(&args6); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(UP)); } diff --git a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp index 2909129126..6617f65adf 100644 --- a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp @@ -28,7 +28,7 @@ NotifyMotionArgs generateFuzzedMotionArgs(FuzzedDataProvider &fdp) { // Create a basic motion event for testing PointerProperties properties; properties.id = 0; - properties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + properties.toolType = getFuzzedToolType(fdp); PointerCoords coords; coords.clear(); for (int32_t i = 0; i < fdp.ConsumeIntegralInRange(0, MAX_AXES); i++) { diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index 2cb5cdf9fc..d9a07b96d5 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -66,6 +66,14 @@ constexpr size_t kMaxSize = 256; namespace android { +template +ToolType getFuzzedToolType(Fdp& fdp) { + const int32_t toolType = fdp.template ConsumeIntegralInRange( + static_cast(ToolType::ftl_first), + static_cast(ToolType::ftl_last)); + return static_cast(toolType); +} + class FuzzEventHub : public EventHubInterface { InputDeviceIdentifier mIdentifier; std::vector mVideoFrames; diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp index 59cb94a2e2..20db39d885 100644 --- a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp @@ -128,7 +128,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { StylusState state{fdp->ConsumeIntegral(), fdp->ConsumeFloatingPoint(), fdp->ConsumeIntegral(), - fdp->ConsumeIntegral()}; + getFuzzedToolType(*fdp)}; std::list unused = mapper.updateExternalStylusState(state); }, [&]() -> void { mapper.getAssociatedDisplayId(); }, -- GitLab From f1db8031e211780abde8678d67beae19316e3c61 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Fri, 24 Mar 2023 17:52:34 -0700 Subject: [PATCH 1060/1310] SF: avoid skipping waiting for the earliest time to present When the next expected presentation time is not allocated for the previous frame, SF needs to wait before submitting it to HWC to prevent early presentation. Bug: 273419557 Test: tested by partner Test: atest ASurfaceControlTest Change-Id: Ia83755bbc71968369fd9176944b9a85135d8a04a --- .../CompositionRefreshArgs.h | 9 +++----- .../impl/OutputCompositionState.h | 7 ++----- .../CompositionEngine/src/Display.cpp | 12 +++-------- .../CompositionEngine/src/Output.cpp | 1 - .../CompositionEngine/tests/DisplayTest.cpp | 14 ++++++------- .../CompositionEngine/tests/MockHWComposer.h | 12 +++++------ .../DisplayHardware/HWComposer.cpp | 21 +++++++------------ .../DisplayHardware/HWComposer.h | 17 +++++++-------- services/surfaceflinger/SurfaceFlinger.cpp | 17 +++++++++++++-- .../surfaceflinger_displayhardware_fuzzer.cpp | 5 ++--- 10 files changed, 53 insertions(+), 62 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h index a8322d8572..d93e25e4ca 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h @@ -83,12 +83,9 @@ struct CompositionRefreshArgs { // If set, causes the dirty regions to flash with the delay std::optional devOptFlashDirtyRegionsDelay; - // The earliest time to send the present command to the HAL - std::chrono::steady_clock::time_point earliestPresentTime; - - // The previous present fence. Used together with earliestPresentTime - // to prevent an early presentation of a frame. - std::shared_ptr previousPresentFence; + // Optional. + // The earliest time to send the present command to the HAL. + std::optional earliestPresentTime; // The expected time for the next present nsecs_t expectedPresentTime{0}; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h index c29165210d..a3fda61ecb 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h @@ -122,12 +122,9 @@ struct OutputCompositionState { bool previousDeviceRequestedSuccess = false; + // Optional. // The earliest time to send the present command to the HAL - std::chrono::steady_clock::time_point earliestPresentTime; - - // The previous present fence. Used together with earliestPresentTime - // to prevent an early presentation of a frame. - std::shared_ptr previousPresentFence; + std::optional earliestPresentTime; // The expected time for the next present nsecs_t expectedPresentTime{0}; diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp index d50a768474..85fc09549b 100644 --- a/services/surfaceflinger/CompositionEngine/src/Display.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp @@ -263,7 +263,6 @@ bool Display::chooseCompositionStrategy( if (status_t result = hwc.getDeviceCompositionChanges(*halDisplayId, requiresClientComposition, getState().earliestPresentTime, - getState().previousPresentFence, getState().expectedPresentTime, outChanges); result != NO_ERROR) { ALOGE("chooseCompositionStrategy failed for %s: %d (%s)", getName().c_str(), result, @@ -380,16 +379,11 @@ compositionengine::Output::FrameFences Display::presentAndGetFrameFences() { const TimePoint startTime = TimePoint::now(); - if (isPowerHintSessionEnabled()) { - if (!getCompositionEngine().getHwComposer().getComposer()->isSupported( - Hwc2::Composer::OptionalFeature::ExpectedPresentTime) && - getState().previousPresentFence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) { - mPowerAdvisor->setHwcPresentDelayedTime(mId, getState().earliestPresentTime); - } + if (isPowerHintSessionEnabled() && getState().earliestPresentTime) { + mPowerAdvisor->setHwcPresentDelayedTime(mId, *getState().earliestPresentTime); } - hwc.presentAndGetReleaseFences(*halDisplayIdOpt, getState().earliestPresentTime, - getState().previousPresentFence); + hwc.presentAndGetReleaseFences(*halDisplayIdOpt, getState().earliestPresentTime); if (isPowerHintSessionEnabled()) { mPowerAdvisor->setHwcPresentTiming(mId, startTime, TimePoint::now()); diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 175dd1d825..e720af5a33 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -842,7 +842,6 @@ void Output::writeCompositionState(const compositionengine::CompositionRefreshAr } editState().earliestPresentTime = refreshArgs.earliestPresentTime; - editState().previousPresentFence = refreshArgs.previousPresentFence; editState().expectedPresentTime = refreshArgs.expectedPresentTime; compositionengine::OutputLayer* peekThroughLayer = nullptr; diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp index 0756c1becd..9be6bc2075 100644 --- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp @@ -595,7 +595,7 @@ TEST_F(DisplayChooseCompositionStrategyTest, takesEarlyOutIfGpuDisplay) { TEST_F(DisplayChooseCompositionStrategyTest, takesEarlyOutOnHwcError) { EXPECT_CALL(*mDisplay, anyLayersRequireClientComposition()).WillOnce(Return(false)); EXPECT_CALL(mHwComposer, - getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), false, _, _, _, _)) + getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), false, _, _, _)) .WillOnce(Return(INVALID_OPERATION)); chooseCompositionStrategy(mDisplay.get()); @@ -619,8 +619,8 @@ TEST_F(DisplayChooseCompositionStrategyTest, normalOperation) { .WillOnce(Return(false)); EXPECT_CALL(mHwComposer, - getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _, _)) - .WillOnce(testing::DoAll(testing::SetArgPointee<5>(mDeviceRequestedChanges), + getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _)) + .WillOnce(testing::DoAll(testing::SetArgPointee<4>(mDeviceRequestedChanges), Return(NO_ERROR))); EXPECT_CALL(*mDisplay, applyChangedTypesToLayers(mDeviceRequestedChanges.changedTypes)) .Times(1); @@ -672,8 +672,8 @@ TEST_F(DisplayChooseCompositionStrategyTest, normalOperationWithChanges) { .WillOnce(Return(false)); EXPECT_CALL(mHwComposer, - getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _, _)) - .WillOnce(DoAll(SetArgPointee<5>(mDeviceRequestedChanges), Return(NO_ERROR))); + getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _)) + .WillOnce(DoAll(SetArgPointee<4>(mDeviceRequestedChanges), Return(NO_ERROR))); EXPECT_CALL(*mDisplay, applyChangedTypesToLayers(mDeviceRequestedChanges.changedTypes)) .Times(1); EXPECT_CALL(*mDisplay, applyDisplayRequests(mDeviceRequestedChanges.displayRequests)).Times(1); @@ -901,7 +901,7 @@ TEST_F(DisplayPresentAndGetFrameFencesTest, returnsPresentAndLayerFences) { sp layer1Fence = sp::make(); sp layer2Fence = sp::make(); - EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(HalDisplayId(DEFAULT_DISPLAY_ID), _, _)) + EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(HalDisplayId(DEFAULT_DISPLAY_ID), _)) .Times(1); EXPECT_CALL(mHwComposer, getPresentFence(HalDisplayId(DEFAULT_DISPLAY_ID))) .WillOnce(Return(presentFence)); @@ -1078,7 +1078,7 @@ TEST_F(DisplayFunctionalTest, postFramebufferCriticalCallsAreOrdered) { mDisplay->editState().isEnabled = true; - EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(_, _, _)); + EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(_, _)); EXPECT_CALL(*mDisplaySurface, onFrameCommitted()); mDisplay->postFramebuffer(); diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h index 1a56ab751f..67b94ee749 100644 --- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h +++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h @@ -54,16 +54,14 @@ public: MOCK_METHOD2(allocatePhysicalDisplay, void(hal::HWDisplayId, PhysicalDisplayId)); MOCK_METHOD1(createLayer, std::shared_ptr(HalDisplayId)); - MOCK_METHOD6(getDeviceCompositionChanges, - status_t(HalDisplayId, bool, std::chrono::steady_clock::time_point, - const std::shared_ptr&, nsecs_t, - std::optional*)); + MOCK_METHOD5(getDeviceCompositionChanges, + status_t(HalDisplayId, bool, std::optional, + nsecs_t, std::optional*)); MOCK_METHOD5(setClientTarget, status_t(HalDisplayId, uint32_t, const sp&, const sp&, ui::Dataspace)); - MOCK_METHOD3(presentAndGetReleaseFences, - status_t(HalDisplayId, std::chrono::steady_clock::time_point, - const std::shared_ptr&)); + MOCK_METHOD2(presentAndGetReleaseFences, + status_t(HalDisplayId, std::optional)); MOCK_METHOD2(setPowerMode, status_t(PhysicalDisplayId, hal::PowerMode)); MOCK_METHOD2(setActiveConfig, status_t(HalDisplayId, size_t)); MOCK_METHOD2(setColorTransform, status_t(HalDisplayId, const mat4&)); diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp index 28148ac1dc..f350eba7ca 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -395,8 +395,8 @@ status_t HWComposer::setClientTarget(HalDisplayId displayId, uint32_t slot, status_t HWComposer::getDeviceCompositionChanges( HalDisplayId displayId, bool frameUsesClientComposition, - std::chrono::steady_clock::time_point earliestPresentTime, - const std::shared_ptr& previousPresentFence, nsecs_t expectedPresentTime, + std::optional earliestPresentTime, + nsecs_t expectedPresentTime, std::optional* outChanges) { ATRACE_CALL(); @@ -426,14 +426,13 @@ status_t HWComposer::getDeviceCompositionChanges( // If composer supports getting the expected present time, we can skip // as composer will make sure to prevent early presentation - if (mComposer->isSupported(Hwc2::Composer::OptionalFeature::ExpectedPresentTime)) { + if (!earliestPresentTime) { return true; } // composer doesn't support getting the expected present time. We can only // skip validate if we know that we are not going to present early. - return std::chrono::steady_clock::now() >= earliestPresentTime || - previousPresentFence->getSignalTime() == Fence::SIGNAL_TIME_PENDING; + return std::chrono::steady_clock::now() >= *earliestPresentTime; }(); displayData.validateWasSkipped = false; @@ -508,8 +507,8 @@ sp HWComposer::getLayerReleaseFence(HalDisplayId displayId, HWC2::Layer* } status_t HWComposer::presentAndGetReleaseFences( - HalDisplayId displayId, std::chrono::steady_clock::time_point earliestPresentTime, - const std::shared_ptr& previousPresentFence) { + HalDisplayId displayId, + std::optional earliestPresentTime) { ATRACE_CALL(); RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX); @@ -525,13 +524,9 @@ status_t HWComposer::presentAndGetReleaseFences( return NO_ERROR; } - const bool waitForEarliestPresent = - !mComposer->isSupported(Hwc2::Composer::OptionalFeature::ExpectedPresentTime) && - previousPresentFence->getSignalTime() != Fence::SIGNAL_TIME_PENDING; - - if (waitForEarliestPresent) { + if (earliestPresentTime) { ATRACE_NAME("wait for earliest present time"); - std::this_thread::sleep_until(earliestPresentTime); + std::this_thread::sleep_until(*earliestPresentTime); } auto error = hwcDisplay->present(&displayData.lastPresentFence); diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h index 7a3f41cc1c..3702c62b65 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.h +++ b/services/surfaceflinger/DisplayHardware/HWComposer.h @@ -143,17 +143,16 @@ public: // expected. virtual status_t getDeviceCompositionChanges( HalDisplayId, bool frameUsesClientComposition, - std::chrono::steady_clock::time_point earliestPresentTime, - const std::shared_ptr& previousPresentFence, nsecs_t expectedPresentTime, - std::optional* outChanges) = 0; + std::optional earliestPresentTime, + nsecs_t expectedPresentTime, std::optional* outChanges) = 0; virtual status_t setClientTarget(HalDisplayId, uint32_t slot, const sp& acquireFence, const sp& target, ui::Dataspace) = 0; // Present layers to the display and read releaseFences. virtual status_t presentAndGetReleaseFences( - HalDisplayId, std::chrono::steady_clock::time_point earliestPresentTime, - const std::shared_ptr& previousPresentFence) = 0; + HalDisplayId, + std::optional earliestPresentTime) = 0; // set power mode virtual status_t setPowerMode(PhysicalDisplayId, hal::PowerMode) = 0; @@ -339,8 +338,8 @@ public: status_t getDeviceCompositionChanges( HalDisplayId, bool frameUsesClientComposition, - std::chrono::steady_clock::time_point earliestPresentTime, - const std::shared_ptr& previousPresentFence, nsecs_t expectedPresentTime, + std::optional earliestPresentTime, + nsecs_t expectedPresentTime, std::optional* outChanges) override; status_t setClientTarget(HalDisplayId, uint32_t slot, const sp& acquireFence, @@ -348,8 +347,8 @@ public: // Present layers to the display and read releaseFences. status_t presentAndGetReleaseFences( - HalDisplayId, std::chrono::steady_clock::time_point earliestPresentTime, - const std::shared_ptr& previousPresentFence) override; + HalDisplayId, + std::optional earliestPresentTime) override; // set power mode status_t setPowerMode(PhysicalDisplayId, hal::PowerMode mode) override; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index a27108378a..862069a409 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2621,8 +2621,21 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) const auto prevVsyncTime = mExpectedPresentTime - mScheduler->getVsyncSchedule()->period(); const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration; - refreshArgs.earliestPresentTime = prevVsyncTime - hwcMinWorkDuration; - refreshArgs.previousPresentFence = mPreviousPresentFences[0].fenceTime; + const Period vsyncPeriod = mScheduler->getVsyncSchedule()->period(); + const bool threeVsyncsAhead = mExpectedPresentTime - frameTime > 2 * vsyncPeriod; + + // We should wait for the earliest present time if HWC doesn't support ExpectedPresentTime, + // and the next vsync is not already taken by the previous frame. + const bool waitForEarliestPresent = + !getHwComposer().getComposer()->isSupported( + Hwc2::Composer::OptionalFeature::ExpectedPresentTime) && + (threeVsyncsAhead || + mPreviousPresentFences[0].fenceTime->getSignalTime() != Fence::SIGNAL_TIME_PENDING); + + if (waitForEarliestPresent) { + refreshArgs.earliestPresentTime = prevVsyncTime - hwcMinWorkDuration; + } + refreshArgs.scheduledFrameTime = mScheduler->getScheduledFrameTime(); refreshArgs.expectedPresentTime = mExpectedPresentTime.ns(); refreshArgs.hasTrustedPresentationListener = mNumTrustedPresentationListeners > 0; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp index 8a6af10f58..a9247fe903 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp @@ -222,7 +222,7 @@ void DisplayHardwareFuzzer::getDeviceCompositionChanges(HalDisplayId halDisplayI std::optional outChanges; mHwc.getDeviceCompositionChanges(halDisplayID, mFdp.ConsumeBool() /*frameUsesClientComposition*/, - std::chrono::steady_clock::now(), FenceTime::NO_FENCE, + std::chrono::steady_clock::now(), mFdp.ConsumeIntegral(), &outChanges); } @@ -555,8 +555,7 @@ void DisplayHardwareFuzzer::invokeComposer() { mHwc.setClientTarget(halDisplayID, mFdp.ConsumeIntegral(), Fence::NO_FENCE, sp::make(), mFdp.PickValueInArray(kDataspaces)); - mHwc.presentAndGetReleaseFences(halDisplayID, std::chrono::steady_clock::now(), - FenceTime::NO_FENCE); + mHwc.presentAndGetReleaseFences(halDisplayID, std::chrono::steady_clock::now()); mHwc.setPowerMode(mPhysicalDisplayId, mFdp.PickValueInArray(kPowerModes)); -- GitLab From 5fc7d853d855b7968ee3b3d020a2362b87001f9f Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Fri, 17 Mar 2023 18:43:33 +0000 Subject: [PATCH 1061/1310] Support native API to report sysfs nodes changes When a sysfs node change is reported, check if any new perpherals are added and if added, recreate input device. Test: atest inputfliger_tests Bug: 265057712 Change-Id: Ic6c0c00de9ae963f0651859bdbc57e45af7ccd07 --- .../inputflinger/include/InputReaderBase.h | 3 ++ services/inputflinger/reader/EventHub.cpp | 50 +++++++++++++++++++ services/inputflinger/reader/InputReader.cpp | 4 ++ .../inputflinger/reader/include/EventHub.h | 7 +++ .../inputflinger/reader/include/InputReader.h | 2 + services/inputflinger/tests/FakeEventHub.cpp | 29 +++++++++++ services/inputflinger/tests/FakeEventHub.h | 4 +- .../inputflinger/tests/InputReader_test.cpp | 24 +++++++++ .../tests/fuzzers/InputReaderFuzzer.cpp | 4 ++ .../tests/fuzzers/MapperHelpers.h | 1 + 10 files changed, 127 insertions(+), 1 deletion(-) diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index 841c914d60..1750c64eca 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -149,6 +149,9 @@ public: /* Get the Bluetooth address of an input device, if known. */ virtual std::optional getBluetoothAddress(int32_t deviceId) const = 0; + + /* Sysfs node change reported. Recreate device if required to incorporate the new sysfs nodes */ + virtual void sysfsNodeChanged(const std::string& sysfsNodePath) = 0; }; // --- InputReaderConfiguration --- diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index e65f3af107..ee8dde1d32 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -1531,6 +1531,20 @@ std::shared_ptr EventHub::obtainAssociatedDevi return associatedDevice; } +bool EventHub::AssociatedDevice::isChanged() const { + std::unordered_map newBatteryInfos = + readBatteryConfiguration(sysfsRootPath); + std::unordered_map newLightInfos = + readLightsConfiguration(sysfsRootPath); + std::optional newLayoutInfo = readLayoutConfiguration(sysfsRootPath); + + if (newBatteryInfos == batteryInfos && newLightInfos == lightInfos && + newLayoutInfo == layoutInfo) { + return false; + } + return true; +} + void EventHub::vibrate(int32_t deviceId, const VibrationElement& element) { std::scoped_lock _l(mLock); Device* device = getDeviceLocked(deviceId); @@ -2536,6 +2550,42 @@ status_t EventHub::disableDevice(int32_t deviceId) { return device->disable(); } +// TODO(b/274755573): Shift to uevent handling on native side and remove this method +// Currently using Java UEventObserver to trigger this which uses UEvent infrastructure that uses a +// NETLINK socket to observe UEvents. We can create similar infrastructure on Eventhub side to +// directly observe UEvents instead of triggering from Java side. +void EventHub::sysfsNodeChanged(const std::string& sysfsNodePath) { + std::scoped_lock _l(mLock); + + // Check in opening devices + for (auto it = mOpeningDevices.begin(); it != mOpeningDevices.end(); it++) { + std::unique_ptr& device = *it; + if (device->associatedDevice && + sysfsNodePath.find(device->associatedDevice->sysfsRootPath.string()) != + std::string::npos && + device->associatedDevice->isChanged()) { + it = mOpeningDevices.erase(it); + openDeviceLocked(device->path); + } + } + + // Check in already added device + std::vector devicesToReopen; + for (const auto& [id, device] : mDevices) { + if (device->associatedDevice && + sysfsNodePath.find(device->associatedDevice->sysfsRootPath.string()) != + std::string::npos && + device->associatedDevice->isChanged()) { + devicesToReopen.push_back(device.get()); + } + } + for (const auto& device : devicesToReopen) { + closeDeviceLocked(*device); + openDeviceLocked(device->path); + } + devicesToReopen.clear(); +} + void EventHub::createVirtualKeyboardLocked() { InputDeviceIdentifier identifier; identifier.name = "Virtual"; diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 9080cc1d26..81ac03b7b3 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -928,6 +928,10 @@ bool InputReader::canDispatchToDisplay(int32_t deviceId, int32_t displayId) { return *associatedDisplayId == displayId; } +void InputReader::sysfsNodeChanged(const std::string& sysfsNodePath) { + mEventHub->sysfsNodeChanged(sysfsNodePath); +} + void InputReader::dump(std::string& dump) { std::scoped_lock _l(mLock); diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index 0b15efed12..20612c767c 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -388,6 +388,10 @@ public: /* Disable an input device. Closes file descriptor to that device. */ virtual status_t disableDevice(int32_t deviceId) = 0; + + /* Sysfs node changed. Reopen the Eventhub device if any new Peripheral like Light, Battery, + * etc. is detected. */ + virtual void sysfsNodeChanged(const std::string& sysfsNodePath) = 0; }; template @@ -567,6 +571,8 @@ public: status_t disableDevice(int32_t deviceId) override final; + void sysfsNodeChanged(const std::string& sysfsNodePath) override final; + ~EventHub() override; private: @@ -578,6 +584,7 @@ private: std::unordered_map lightInfos; std::optional layoutInfo; + bool isChanged() const; bool operator==(const AssociatedDevice&) const = default; bool operator!=(const AssociatedDevice&) const = default; std::string dump() const; diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h index e9c989a224..120e15070c 100644 --- a/services/inputflinger/reader/include/InputReader.h +++ b/services/inputflinger/reader/include/InputReader.h @@ -117,6 +117,8 @@ public: std::optional getBluetoothAddress(int32_t deviceId) const override; + void sysfsNodeChanged(const std::string& sysfsNodePath) override; + protected: // These members are protected so they can be instrumented by test cases. virtual std::shared_ptr createDeviceLocked(int32_t deviceId, diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp index ff6d584007..4626f5add7 100644 --- a/services/inputflinger/tests/FakeEventHub.cpp +++ b/services/inputflinger/tests/FakeEventHub.cpp @@ -594,4 +594,33 @@ std::optional> FakeEventHub::getLightInt return lightIt->second; }; +void FakeEventHub::setSysfsRootPath(int32_t deviceId, std::string sysfsRootPath) const { + Device* device = getDevice(deviceId); + if (device == nullptr) { + return; + } + device->sysfsRootPath = sysfsRootPath; +} + +void FakeEventHub::sysfsNodeChanged(const std::string& sysfsNodePath) { + int32_t foundDeviceId = -1; + Device* foundDevice = nullptr; + for (size_t i = 0; i < mDevices.size(); i++) { + Device* d = mDevices.valueAt(i); + if (sysfsNodePath.find(d->sysfsRootPath) != std::string::npos) { + foundDeviceId = mDevices.keyAt(i); + foundDevice = d; + } + } + if (foundDevice == nullptr) { + return; + } + // If device sysfs changed -> reopen the device + if (!mRawLightInfos.empty() && !foundDevice->classes.test(InputDeviceClass::LIGHT)) { + removeDevice(foundDeviceId); + addDevice(foundDeviceId, foundDevice->identifier.name, + foundDevice->classes | InputDeviceClass::LIGHT, foundDevice->identifier.bus); + } +} + } // namespace android diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h index e0a3f9eee1..8e06940aec 100644 --- a/services/inputflinger/tests/FakeEventHub.h +++ b/services/inputflinger/tests/FakeEventHub.h @@ -64,6 +64,7 @@ class FakeEventHub : public EventHubInterface { std::vector virtualKeys; bool enabled; std::optional layoutInfo; + std::string sysfsRootPath; status_t enable() { enabled = true; @@ -152,6 +153,7 @@ public: void enqueueEvent(nsecs_t when, nsecs_t readTime, int32_t deviceId, int32_t type, int32_t code, int32_t value); void assertQueueIsEmpty(); + void setSysfsRootPath(int32_t deviceId, std::string sysfsRootPath) const; private: Device* getDevice(int32_t deviceId) const; @@ -212,7 +214,7 @@ private: std::optional getLightBrightness(int32_t deviceId, int32_t lightId) const override; std::optional> getLightIntensities( int32_t deviceId, int32_t lightId) const override; - + void sysfsNodeChanged(const std::string& sysfsNodePath) override; void dump(std::string&) const override {} void monitor() const override {} void requestReopenDevices() override {} diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 853c5b0115..48ffc15820 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -644,6 +644,30 @@ TEST_F(InputReaderTest, PolicyGetInputDevices) { ASSERT_EQ(0U, inputDevices[0].getMotionRanges().size()); } +TEST_F(InputReaderTest, InputDeviceRecreatedOnSysfsNodeChanged) { + ASSERT_NO_FATAL_FAILURE(addDevice(1, "keyboard", InputDeviceClass::KEYBOARD, nullptr)); + mFakeEventHub->setSysfsRootPath(1, "xyz"); + + // Should also have received a notification describing the new input device. + ASSERT_EQ(1U, mFakePolicy->getInputDevices().size()); + InputDeviceInfo inputDevice = mFakePolicy->getInputDevices()[0]; + ASSERT_EQ(0U, inputDevice.getLights().size()); + + RawLightInfo infoMonolight = {.id = 123, + .name = "mono_keyboard_backlight", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS, + .path = ""}; + mFakeEventHub->addRawLightInfo(/*rawId=*/123, std::move(infoMonolight)); + mReader->sysfsNodeChanged("xyz"); + mReader->loopOnce(); + + // Should also have received a notification describing the new recreated input device. + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); + inputDevice = mFakePolicy->getInputDevices()[0]; + ASSERT_EQ(1U, inputDevice.getLights().size()); +} + TEST_F(InputReaderTest, GetMergedInputDevices) { constexpr int32_t deviceId = END_RESERVED_ID + 1000; constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1}; diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp index 20242b1c15..baece3c110 100644 --- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp @@ -165,6 +165,10 @@ public: return reader->getBluetoothAddress(deviceId); } + void sysfsNodeChanged(const std::string& sysfsNodePath) { + reader->sysfsNodeChanged(sysfsNodePath); + } + private: std::unique_ptr reader; }; diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index 2cb5cdf9fc..c082128d9e 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -217,6 +217,7 @@ public: bool isDeviceEnabled(int32_t deviceId) const override { return mFdp->ConsumeBool(); } status_t enableDevice(int32_t deviceId) override { return mFdp->ConsumeIntegral(); } status_t disableDevice(int32_t deviceId) override { return mFdp->ConsumeIntegral(); } + void sysfsNodeChanged(const std::string& sysfsNodePath) override {} }; class FuzzPointerController : public PointerControllerInterface { -- GitLab From 29354ec571bc01e94d72cb2ee69ee0b7e39f7175 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 28 Mar 2023 18:51:28 -0700 Subject: [PATCH 1062/1310] [sf] Update touchable region crop correctly We need an extra pass to crop touchable region since we may need to crop the region with bounds of another layer. So track snapshots requiring a touchable region crop and updated them after the all the snapshots have been updated. Also make the snapshot ids unique for non clone layers for input. Before this change the ids could change if the snapshot went offscreen. Test: presubmit Test: atest com.android.launcher3.jank.BinderTests Bug: 238781169 Change-Id: I323168bd4546813acbe7fa841b26cd390b83b951 --- .../surfaceflinger/FrontEnd/LayerSnapshot.cpp | 2 + .../surfaceflinger/FrontEnd/LayerSnapshot.h | 1 + .../FrontEnd/LayerSnapshotBuilder.cpp | 131 ++++++++++++++---- .../FrontEnd/LayerSnapshotBuilder.h | 4 + .../tests/unittests/LayerHierarchyTest.h | 19 +++ .../tests/unittests/LayerSnapshotTest.cpp | 25 ++++ 6 files changed, 152 insertions(+), 30 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp index 2d6d8ad0b0..1e931a7a1a 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp @@ -187,6 +187,8 @@ std::string LayerSnapshot::getDebugString() const { << " geomLayerTransform={tx=" << geomLayerTransform.tx() << ",ty=" << geomLayerTransform.ty() << "}" << "}"; + debug << " input{ touchCropId=" << touchCropId + << " replaceTouchableRegionWithCrop=" << inputInfo.replaceTouchableRegionWithCrop << "}"; return debug.str(); } diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h index 5491d9af17..b167d3ea1b 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h @@ -94,6 +94,7 @@ struct LayerSnapshot : public compositionengine::LayerFECompositionState { int32_t frameRateSelectionPriority; LayerHierarchy::TraversalPath mirrorRootPath; bool unreachable = true; + uint32_t touchCropId; uid_t uid; pid_t pid; ChildState childState; diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index babcbe785a..25cbe7ae54 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -447,23 +447,36 @@ void LayerSnapshotBuilder::updateSnapshots(const Args& args) { } } + // Update touchable region crops outside the main update pass. This is because a layer could be + // cropped by any other layer and it requires both snapshots to be updated. + updateTouchableRegionCrop(args); + const bool hasUnreachableSnapshots = sortSnapshotsByZ(args); clearChanges(mRootSnapshot); - // Destroy unreachable snapshots - if (!hasUnreachableSnapshots) { + // Destroy unreachable snapshots for clone layers. And destroy snapshots for non-clone + // layers if the layer have been destroyed. + // TODO(b/238781169) consider making clone layer ids stable as well + if (!hasUnreachableSnapshots && args.layerLifecycleManager.getDestroyedLayers().empty()) { return; } + std::unordered_set destroyedLayerIds; + for (auto& destroyedLayer : args.layerLifecycleManager.getDestroyedLayers()) { + destroyedLayerIds.insert(destroyedLayer->id); + } + auto it = mSnapshots.begin(); while (it < mSnapshots.end()) { auto& traversalPath = it->get()->path; - if (!it->get()->unreachable) { + if (!it->get()->unreachable && + destroyedLayerIds.find(traversalPath.id) == destroyedLayerIds.end()) { it++; continue; } mIdToSnapshot.erase(traversalPath); + mNeedsTouchableRegionCrop.erase(traversalPath); mSnapshots.back()->globalZ = it->get()->globalZ; std::iter_swap(it, mSnapshots.end() - 1); mSnapshots.erase(mSnapshots.end() - 1); @@ -554,7 +567,7 @@ bool LayerSnapshotBuilder::sortSnapshotsByZ(const Args& args) { mResortSnapshots = false; for (auto& snapshot : mSnapshots) { - snapshot->unreachable = true; + snapshot->unreachable = snapshot->path.isClone(); } size_t globalZ = 0; @@ -739,6 +752,8 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a // If root layer, use the layer stack otherwise get the parent's layer stack. snapshot.color.a = parentSnapshot.color.a * requested.color.a; snapshot.alpha = snapshot.color.a; + snapshot.inputInfo.alpha = snapshot.color.a; + snapshot.isSecure = parentSnapshot.isSecure || (requested.flags & layer_state_t::eLayerSecure); snapshot.isTrustedOverlay = parentSnapshot.isTrustedOverlay || requested.isTrustedOverlay; @@ -986,9 +1001,11 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, snapshot.inputInfo.ownerUid = static_cast(requested.ownerUid); snapshot.inputInfo.ownerPid = requested.ownerPid; } + snapshot.touchCropId = requested.touchCropId; snapshot.inputInfo.id = static_cast(snapshot.uniqueSequence); snapshot.inputInfo.displayId = static_cast(snapshot.outputFilter.layerStack.id); + updateVisibility(snapshot, snapshot.isVisible); if (!needsInputInfo(snapshot, requested)) { return; } @@ -1033,27 +1050,13 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, } auto cropLayerSnapshot = getSnapshot(requested.touchCropId); - if (snapshot.inputInfo.replaceTouchableRegionWithCrop) { - Rect inputBoundsInDisplaySpace; - if (!cropLayerSnapshot) { - FloatRect inputBounds = getInputBounds(snapshot, /*fillParentBounds=*/true).first; - inputBoundsInDisplaySpace = - getInputBoundsInDisplaySpace(snapshot, inputBounds, displayInfo.transform); - } else { - FloatRect inputBounds = - getInputBounds(*cropLayerSnapshot, /*fillParentBounds=*/true).first; - inputBoundsInDisplaySpace = - getInputBoundsInDisplaySpace(*cropLayerSnapshot, inputBounds, - displayInfo.transform); - } - snapshot.inputInfo.touchableRegion = Region(inputBoundsInDisplaySpace); - } else if (cropLayerSnapshot) { - FloatRect inputBounds = getInputBounds(*cropLayerSnapshot, /*fillParentBounds=*/true).first; + if (cropLayerSnapshot) { + mNeedsTouchableRegionCrop.insert(path); + } else if (snapshot.inputInfo.replaceTouchableRegionWithCrop) { + FloatRect inputBounds = getInputBounds(snapshot, /*fillParentBounds=*/true).first; Rect inputBoundsInDisplaySpace = - getInputBoundsInDisplaySpace(*cropLayerSnapshot, inputBounds, - displayInfo.transform); - snapshot.inputInfo.touchableRegion = snapshot.inputInfo.touchableRegion.intersect( - displayInfo.transform.transform(inputBoundsInDisplaySpace)); + getInputBoundsInDisplaySpace(snapshot, inputBounds, displayInfo.transform); + snapshot.inputInfo.touchableRegion = Region(inputBoundsInDisplaySpace); } // Inherit the trusted state from the parent hierarchy, but don't clobber the trusted state @@ -1066,12 +1069,7 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, // touches from going outside the cloned area. if (path.isClone()) { snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::CLONE; - auto clonedRootSnapshot = getSnapshot(snapshot.mirrorRootPath); - if (clonedRootSnapshot) { - const Rect rect = - displayInfo.transform.transform(Rect{clonedRootSnapshot->transformedBounds}); - snapshot.inputInfo.touchableRegion = snapshot.inputInfo.touchableRegion.intersect(rect); - } + mNeedsTouchableRegionCrop.insert(path); } } @@ -1117,4 +1115,77 @@ void LayerSnapshotBuilder::forEachInputSnapshot(const ConstVisitor& visitor) con } } +void LayerSnapshotBuilder::updateTouchableRegionCrop(const Args& args) { + if (mNeedsTouchableRegionCrop.empty()) { + return; + } + + static constexpr ftl::Flags AFFECTS_INPUT = + RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Created | + RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Geometry | + RequestedLayerState::Changes::Input; + + if (args.forceUpdate != ForceUpdateFlags::ALL && + !args.layerLifecycleManager.getGlobalChanges().any(AFFECTS_INPUT)) { + return; + } + + for (auto& path : mNeedsTouchableRegionCrop) { + frontend::LayerSnapshot* snapshot = getSnapshot(path); + if (!snapshot) { + continue; + } + const std::optional displayInfoOpt = + args.displays.get(snapshot->outputFilter.layerStack); + static frontend::DisplayInfo sDefaultInfo = {.isSecure = false}; + auto displayInfo = displayInfoOpt.value_or(sDefaultInfo); + + bool needsUpdate = + args.forceUpdate == ForceUpdateFlags::ALL || snapshot->changes.any(AFFECTS_INPUT); + auto cropLayerSnapshot = getSnapshot(snapshot->touchCropId); + needsUpdate = + needsUpdate || (cropLayerSnapshot && cropLayerSnapshot->changes.any(AFFECTS_INPUT)); + auto clonedRootSnapshot = path.isClone() ? getSnapshot(snapshot->mirrorRootPath) : nullptr; + needsUpdate = needsUpdate || + (clonedRootSnapshot && clonedRootSnapshot->changes.any(AFFECTS_INPUT)); + + if (!needsUpdate) { + continue; + } + + if (snapshot->inputInfo.replaceTouchableRegionWithCrop) { + Rect inputBoundsInDisplaySpace; + if (!cropLayerSnapshot) { + FloatRect inputBounds = getInputBounds(*snapshot, /*fillParentBounds=*/true).first; + inputBoundsInDisplaySpace = + getInputBoundsInDisplaySpace(*snapshot, inputBounds, displayInfo.transform); + } else { + FloatRect inputBounds = + getInputBounds(*cropLayerSnapshot, /*fillParentBounds=*/true).first; + inputBoundsInDisplaySpace = + getInputBoundsInDisplaySpace(*cropLayerSnapshot, inputBounds, + displayInfo.transform); + } + snapshot->inputInfo.touchableRegion = Region(inputBoundsInDisplaySpace); + } else if (cropLayerSnapshot) { + FloatRect inputBounds = + getInputBounds(*cropLayerSnapshot, /*fillParentBounds=*/true).first; + Rect inputBoundsInDisplaySpace = + getInputBoundsInDisplaySpace(*cropLayerSnapshot, inputBounds, + displayInfo.transform); + snapshot->inputInfo.touchableRegion = snapshot->inputInfo.touchableRegion.intersect( + displayInfo.transform.transform(inputBoundsInDisplaySpace)); + } + + // If the layer is a clone, we need to crop the input region to cloned root to prevent + // touches from going outside the cloned area. + if (clonedRootSnapshot) { + const Rect rect = + displayInfo.transform.transform(Rect{clonedRootSnapshot->transformedBounds}); + snapshot->inputInfo.touchableRegion = + snapshot->inputInfo.touchableRegion.intersect(rect); + } + } +} + } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h index 7b1ff2710b..148c98e2b1 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h @@ -119,10 +119,14 @@ private: const LayerSnapshot& parentSnapshot); void updateChildState(LayerSnapshot& snapshot, const LayerSnapshot& childSnapshot, const Args& args); + void updateTouchableRegionCrop(const Args& args); std::unordered_map mIdToSnapshot; + // Track snapshots that needs touchable region crop from other snapshots + std::unordered_set + mNeedsTouchableRegionCrop; std::vector> mSnapshots; LayerSnapshot mRootSnapshot; bool mResortSnapshots = false; diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h index 5b3c7efe92..79cfd6a891 100644 --- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h +++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h @@ -278,6 +278,25 @@ protected: mLifecycleManager.applyTransactions(transactions); } + void setTouchableRegionCrop(uint32_t id, Region region, uint32_t touchCropId, + bool replaceTouchableRegionWithCrop) { + std::vector transactions; + transactions.emplace_back(); + transactions.back().states.push_back({}); + + transactions.back().states.front().state.what = layer_state_t::eInputInfoChanged; + transactions.back().states.front().layerId = id; + transactions.back().states.front().state.windowInfoHandle = + sp::make(); + auto inputInfo = transactions.back().states.front().state.windowInfoHandle->editInfo(); + inputInfo->touchableRegion = region; + inputInfo->replaceTouchableRegionWithCrop = replaceTouchableRegionWithCrop; + transactions.back().states.front().touchCropId = touchCropId; + + inputInfo->token = sp::make(); + mLifecycleManager.applyTransactions(transactions); + } + LayerLifecycleManager mLifecycleManager; }; diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index b8c47817ca..5a066a6482 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -308,6 +308,31 @@ TEST_F(LayerSnapshotTest, NoLayerVoteForParentWithChildVotes) { EXPECT_EQ(getSnapshot(1)->frameRate.type, scheduler::LayerInfo::FrameRateCompatibility::NoVote); } +TEST_F(LayerSnapshotTest, canCropTouchableRegion) { + // ROOT + // ├── 1 + // │ ├── 11 + // │ │ └── 111 (touchregion set to touch but cropped by layer 13) + // │ ├── 12 + // │ │ ├── 121 + // │ │ └── 122 + // │ │ └── 1221 + // │ └── 13 (crop set to touchCrop) + // └── 2 + + Rect touchCrop{300, 300, 400, 500}; + setCrop(13, touchCrop); + Region touch{Rect{0, 0, 1000, 1000}}; + setTouchableRegionCrop(111, touch, /*touchCropId=*/13, /*replaceTouchableRegionWithCrop=*/true); + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + EXPECT_EQ(getSnapshot({.id = 111})->inputInfo.touchableRegion.bounds(), touchCrop); + + Rect modifiedTouchCrop{100, 300, 400, 700}; + setCrop(13, modifiedTouchCrop); + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + EXPECT_EQ(getSnapshot({.id = 111})->inputInfo.touchableRegion.bounds(), modifiedTouchCrop); +} + // Display Mirroring Tests // tree with 3 levels of children // ROOT (DISPLAY 0) -- GitLab From 2cf827faebace4039cb0c0af0def9efdf454af62 Mon Sep 17 00:00:00 2001 From: Ying Wei Date: Wed, 22 Mar 2023 22:25:28 +0000 Subject: [PATCH 1063/1310] Grab lock before reparenting in SurfaceFlinger commitMirrorDisplays() Bug: 271644537 Test: n/a Change-Id: I45384210791bd9280f63d95e3bdaef3d299fc818 (cherry picked from commit 9cba62a07f393d852dabf20b6e4d10e67b29c8d9) Merged-In: I45384210791bd9280f63d95e3bdaef3d299fc818 --- services/surfaceflinger/SurfaceFlinger.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 6a2e3475b9..9ceb0afa20 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -7913,9 +7913,14 @@ bool SurfaceFlinger::commitMirrorDisplays(VsyncId vsyncId) { ISurfaceComposerClient::eNoColorFill, gui::LayerMetadata()); sp childMirror; - createEffectLayer(mirrorArgs, &unused, &childMirror); - childMirror->setClonedChild(layer->createClone()); - childMirror->reparent(mirrorDisplay.rootHandle); + { + Mutex::Autolock lock(mStateLock); + createEffectLayer(mirrorArgs, &unused, &childMirror); + childMirror->setClonedChild(layer->createClone()); + childMirror->reparent(mirrorDisplay.rootHandle); + } + // lock on mStateLock needs to be released before binder handle gets destroyed + unused.clear(); } } return true; -- GitLab From 20e1f9625f009948e721e5194557d0d3beee212e Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 29 Mar 2023 15:58:34 -0700 Subject: [PATCH 1064/1310] [layertracegenerator] be more resilient to unknown layer ids While unexpected, the layer lifecycle in legacy which is used to generate the transaction traces is less explicit. We rely on layer destruction when the object becomes unreachable. The new front end logic will crash if called with an unknown layer id. While we have to interop between both sets of logic, log and ignore the unknown layer ids so we can still generate useful layer traces. Fixes: 275630566 Test: presbumit Change-Id: I5b2a217c78b4a9e94f0772459d6aa0c68a8a51d0 --- .../FrontEnd/LayerCreationArgs.cpp | 19 ++++++ .../FrontEnd/LayerCreationArgs.h | 1 + .../FrontEnd/LayerLifecycleManager.cpp | 6 +- .../FrontEnd/LayerLifecycleManager.h | 6 +- .../Tracing/TransactionTracing.cpp | 1 + .../Tracing/tools/LayerTraceGenerator.cpp | 23 ++++--- .../tracing/TransactionTraceTestSuite.cpp | 58 ++++++++++-------- .../testdata/layers_trace_b275630566.winscope | Bin 0 -> 46969 bytes .../transactions_trace_b275630566.winscope | Bin 0 -> 552698 bytes 9 files changed, 76 insertions(+), 38 deletions(-) create mode 100644 services/surfaceflinger/tests/tracing/testdata/layers_trace_b275630566.winscope create mode 100644 services/surfaceflinger/tests/tracing/testdata/transactions_trace_b275630566.winscope diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp index ce21233424..6af352c9f4 100644 --- a/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp +++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp @@ -74,4 +74,23 @@ LayerCreationArgs LayerCreationArgs::fromOtherArgs(const LayerCreationArgs& othe return LayerCreationArgs(other.flinger, other.client, other.name, other.flags, other.metadata); } +std::string LayerCreationArgs::getDebugString() const { + std::stringstream stream; + stream << "LayerCreationArgs{" << name << "[" << sequence << "] flags=" << flags + << " pid=" << ownerPid << " uid=" << ownerUid; + if (addToRoot) { + stream << " addToRoot=" << addToRoot; + } + if (parentId != UNASSIGNED_LAYER_ID) { + stream << " parentId=" << parentId; + } + if (layerIdToMirror != UNASSIGNED_LAYER_ID) { + stream << " layerIdToMirror=" << layerIdToMirror; + } + if (layerStackToMirror != ui::INVALID_LAYER_STACK) { + stream << " layerStackToMirror=" << layerStackToMirror.id; + } + return stream.str(); +} + } // namespace android::surfaceflinger diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h index 011250c52d..3a0fc6d7a3 100644 --- a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h +++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h @@ -44,6 +44,7 @@ struct LayerCreationArgs { bool internalLayer = false); LayerCreationArgs(std::optional id, bool internalLayer = false); LayerCreationArgs() = default; // for tracing + std::string getDebugString() const; android::SurfaceFlinger* flinger; sp client; diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp index 3706225228..33d9dbe796 100644 --- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp +++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp @@ -164,7 +164,8 @@ void LayerLifecycleManager::onHandlesDestroyed(const std::vector& dest } } -void LayerLifecycleManager::applyTransactions(const std::vector& transactions) { +void LayerLifecycleManager::applyTransactions(const std::vector& transactions, + bool ignoreUnknownLayers) { for (const auto& transaction : transactions) { for (const auto& resolvedComposerState : transaction.states) { const auto& clientState = resolvedComposerState.state; @@ -176,7 +177,8 @@ void LayerLifecycleManager::applyTransactions(const std::vector>); - void applyTransactions(const std::vector&); + // Ignore unknown layers when interoping with legacy front end. In legacy we destroy + // the layers it is unreachable. When using the LayerLifecycleManager for layer trace + // generation we may encounter layers which are known because we don't have an explicit + // lifecycle. Ignore these errors while we have to interop with legacy. + void applyTransactions(const std::vector&, bool ignoreUnknownLayers = false); // Ignore unknown handles when iteroping with legacy front end. In the old world, we // would create child layers which are not necessary with the new front end. This means // we will get notified for handle changes that don't exist in the new front end. diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp index 26ed878396..87a633fc9d 100644 --- a/services/surfaceflinger/Tracing/TransactionTracing.cpp +++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp @@ -271,6 +271,7 @@ void TransactionTracing::updateStartingStateLocked( for (const proto::LayerState& layerState : transaction.layer_changes()) { auto it = mStartingStates.find(layerState.layer_id()); if (it == mStartingStates.end()) { + // TODO(b/238781169) make this log fatal when we switch over to using new fe ALOGW("Could not find layer id %d", layerState.layer_id()); continue; } diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index 4b4ce1891f..55004c5e70 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -14,14 +14,6 @@ * limitations under the License. */ -#include -#include -#include -#include "FrontEnd/LayerCreationArgs.h" -#include "FrontEnd/RequestedLayerState.h" -#include "Tracing/LayerTracing.h" -#include "TransactionState.h" -#include "cutils/properties.h" #undef LOG_TAG #define LOG_TAG "LayerTraceGenerator" //#define LOG_NDEBUG 0 @@ -33,8 +25,15 @@ #include #include #include +#include #include +#include +#include "FrontEnd/LayerCreationArgs.h" +#include "FrontEnd/RequestedLayerState.h" #include "LayerProtoHelper.h" +#include "Tracing/LayerTracing.h" +#include "TransactionState.h" +#include "cutils/properties.h" #include "LayerTraceGenerator.h" @@ -84,6 +83,7 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, for (int j = 0; j < entry.added_layers_size(); j++) { LayerCreationArgs args; parser.fromProto(entry.added_layers(j), args); + ALOGV(" %s", args.getDebugString().c_str()); addedLayers.emplace_back(std::make_unique(args)); } @@ -105,9 +105,14 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, transactions.emplace_back(std::move(transaction)); } + for (int j = 0; j < entry.destroyed_layers_size(); j++) { + ALOGV(" destroyedHandles=%d", entry.destroyed_layers(j)); + } + std::vector destroyedHandles; destroyedHandles.reserve((size_t)entry.destroyed_layer_handles_size()); for (int j = 0; j < entry.destroyed_layer_handles_size(); j++) { + ALOGV(" destroyedHandles=%d", entry.destroyed_layer_handles(j)); destroyedHandles.push_back(entry.destroyed_layer_handles(j)); } @@ -118,7 +123,7 @@ bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile, // apply updates lifecycleManager.addLayers(std::move(addedLayers)); - lifecycleManager.applyTransactions(transactions); + lifecycleManager.applyTransactions(transactions, /*ignoreUnknownHandles=*/true); lifecycleManager.onHandlesDestroyed(destroyedHandles, /*ignoreUnknownHandles=*/true); if (lifecycleManager.getGlobalChanges().test( diff --git a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp index 3e3d8f0206..2b295309e7 100644 --- a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp +++ b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp @@ -148,42 +148,48 @@ static LayerInfo getLayerInfoFromProto(::android::surfaceflinger::LayerProto& pr touchableRegionBounds}; } -TEST_P(TransactionTraceTestSuite, validateEndState) { - ASSERT_GT(mActualLayersTraceProto.entry_size(), 0); - ASSERT_GT(mExpectedLayersTraceProto.entry_size(), 0); - - auto expectedLastEntry = - mExpectedLayersTraceProto.entry(mExpectedLayersTraceProto.entry_size() - 1); - auto actualLastEntry = mActualLayersTraceProto.entry(mActualLayersTraceProto.entry_size() - 1); - - EXPECT_EQ(expectedLastEntry.layers().layers_size(), actualLastEntry.layers().layers_size()); - - std::vector expectedLayers; - expectedLayers.reserve(static_cast(expectedLastEntry.layers().layers_size())); - for (int i = 0; i < expectedLastEntry.layers().layers_size(); i++) { - auto layer = expectedLastEntry.layers().layers(i); - LayerInfo layerInfo = getLayerInfoFromProto(layer); - expectedLayers.push_back(layerInfo); - } - std::sort(expectedLayers.begin(), expectedLayers.end(), compareById); - +static std::vector getLayerInfosFromProto( + android::surfaceflinger::LayersTraceProto& entry) { std::unordered_map snapshotIdToLayerId; - std::vector actualLayers; - actualLayers.reserve(static_cast(actualLastEntry.layers().layers_size())); - for (int i = 0; i < actualLastEntry.layers().layers_size(); i++) { - auto layer = actualLastEntry.layers().layers(i); + std::vector layers; + layers.reserve(static_cast(entry.layers().layers_size())); + bool mapSnapshotIdToLayerId = false; + for (int i = 0; i < entry.layers().layers_size(); i++) { + auto layer = entry.layers().layers(i); LayerInfo layerInfo = getLayerInfoFromProto(layer); + snapshotIdToLayerId[layerInfo.id] = static_cast(layer.original_id()); - actualLayers.push_back(layerInfo); + if (layer.original_id() != 0) { + mapSnapshotIdToLayerId = true; + } + layers.push_back(layerInfo); } + std::sort(layers.begin(), layers.end(), compareById); - for (auto& layer : actualLayers) { + if (!mapSnapshotIdToLayerId) { + return layers; + } + for (auto& layer : layers) { layer.id = snapshotIdToLayerId[layer.id]; auto it = snapshotIdToLayerId.find(layer.parent); layer.parent = it == snapshotIdToLayerId.end() ? -1 : it->second; } + return layers; +} + +TEST_P(TransactionTraceTestSuite, validateEndState) { + ASSERT_GT(mActualLayersTraceProto.entry_size(), 0); + ASSERT_GT(mExpectedLayersTraceProto.entry_size(), 0); + + auto expectedLastEntry = + mExpectedLayersTraceProto.entry(mExpectedLayersTraceProto.entry_size() - 1); + auto actualLastEntry = mActualLayersTraceProto.entry(mActualLayersTraceProto.entry_size() - 1); + + EXPECT_EQ(expectedLastEntry.layers().layers_size(), actualLastEntry.layers().layers_size()); - std::sort(actualLayers.begin(), actualLayers.end(), compareById); + std::vector expectedLayers = getLayerInfosFromProto(expectedLastEntry); + std::vector actualLayers = getLayerInfosFromProto(actualLastEntry); + ; size_t i = 0; for (; i < actualLayers.size() && i < expectedLayers.size(); i++) { diff --git a/services/surfaceflinger/tests/tracing/testdata/layers_trace_b275630566.winscope b/services/surfaceflinger/tests/tracing/testdata/layers_trace_b275630566.winscope new file mode 100644 index 0000000000000000000000000000000000000000..fe504d741b8a782bdf69dddb08d70e5527dbf294 GIT binary patch literal 46969 zcmd<|i3|z}a&&eTdi;)wb8~asb}d;31|i|H%;LpRfy`V#eY03(Rwwx$ZM^Fbc_pXXd5km#3!qCZ^|Qre!84mSpDVSs7Rv z8!DSfu`{J)=a*E~Lqmc(K8ARg_FiP+`oPG+BqZUHnUV^Pedp4W{L&ID11m!lWm73e zW~jet8^aL)o?v98y_Z?JUNUkp3kmq=rFtahrKF}nylN)J#F9)`zcSI@uWVdD7&%yk zN-=UkJs-uz%fMiANR)x$#Gxc4Hm%D(9$tY8 zcL&vqa(4#})aqW|_ISAckG zx~0rqf0#Hpgp5cIJtHYbPAs8E+YsBp$heD_%wekmP}E3g&^qrhA^QvxBXfDjD@ zHDQ_<#ds$=F0m73U|?vF1re%zPD|`GSn&k^GG?ytj2xUolJIn?;N%pjP+pW+P>@=r zY+__gQV_Gdq^2d7=9J80i*OoDDl$xk)Vqhx8$E&JkWa{c0D5iIq zaRn1JC;}L1o5o-*5;~OPQ<=FgF>>$eMqx z&gZ4B^V7MOGI8(=ML8yyWR_)?R0gFc=NF|^n;Dp=rWzS4lp265n)Lkq^qf?9eUn&F zP^_1iT2We@lUSOUoRM0jujdEi`arob)0E8&ETvck1Z9Mw!9llJ=A&*fJ!IiJ#Ka*W zlwfIKnQCHTp#bwCB)mNH3Q9|YGxM@33@t-5XgCElG&D5)0F}dZ%KrRhgx4=7A%SQ7 zN*qFL5(l^xHi#E6HZd~b$to{cxTZ642nyvI8zm-MrkWEOSfIL^U;@OI{tOMEAxfQ$ z2rDhg2`)S#^N5A(CL@Q85Wi<`s&jr`Nn&PRYLT*`krdMode;Nw_-qCR zn1pPR8|_KvhUSJwmI@`61*x`128MubPEzJ_?l<>`=Ax5tT1UU%@Pnf-A;d;%;p$e;4O$?Py3{9k% z)HLYiEu<yG z!`ZI_wf`9!?2-JWMteUkX6E|C$e{s`K4WDQLo+ExO_;A3qgX{57_42BAQUeDQKyaq zd!0#x@em`E2IC1vVxpA6|2Q=DE--f|J@qbN=DNkmp@|ZuMzD$(5~a`pz~ysNV;1Z^ zh7*h$M8qwHo?F7q^@@>03l_Jg2Fm7UCQ^*ru()NylGfFWr#M?DM4Y>TGbUkn&WtQeb81O*V za|Q=qNMce_Y6>hel+DfH1tq;Z2-;K%$zM!bpiYB=kQ~RxpMOBb{r~@r0tlwWg*Ik| z3vJAVIung-T>lw4bcGxZlhOvDqWhx#+;~=U12RqY6TabHbW~LsD)%C z#0wdDFf{^=JQzzc!aNkfOt)-ChZa&N2iFoN4r3t?SZ09qysIq}O;QXk&0!@ItQS$1 znwOH9m#(kpSX`W1;shzFU|K-|Wg^9BLhsVZh)Q_~Pon7J;QGPDVIq{3W?+Y>yGI5v-B_}3X7$zo~;?0p11r?&nP4C8m8I^*OT3PV{2UiCZhlP-rsYRl3 znkl$#KvB>d!p5JdlLBF_1ZssFp5%9%12pzzDU=1vSI|6an3QN@0dLR~9XOyooSa!w zsRuL8DL+5kIkTkFEj7<4HB;Hp(9A%J$;ujn8h+i4S2n#bhWZ4c*u76A% zRzd~F$(Dv@_*?YU47mXt{kI%s`L~Y~G*e(L6lrc~oS2*nnS`QdNE<@aCv~$kwSo~( zcJAZ^P1xB8#li9)G&@_Sm>HTHo044R!i;eS*TA3^Gs=dB<|a~1wsz1U3Sgp6Aknwh zoyN)aiHXBjC?z?`C^5+_nL4#D*3hzmk95(y+O-*E*>(;mR~Hk9osf@ll1W;!feC5# zF4n*`r%lC6t&qc$Q>SxstzqJ@7fOZs3z}2Y%o0;lEs{viscuDy>A9(SCC;$iUnSIlW5_41aic%9(Qj7FTGvV!IztoZ-u%NP`k%74sld}sfyRuLxZ0K9RZs+8B z$;9C-6m6W6W{_rVLRxkuCXfu_P_ zs5_7X%Z}6vFg)3D2PfAXMh;i_;F_7Tp|O#<6qB15EE};7r;)YwTwME@INXF1&`KY3 z)8tfhGra5aauW-R!J}+wYoi@V1qUtA~lhL&z`LFgYE3h?hz?CyZgjL!tz}sRqIwBryaVb*1~zm>-p5iCPWX`&?Wvm^cE2Rx&0gr5dG~K&GM~ zaWy!j&Ip>Zs9TOxD_-#Ao;zGz?-)4(;pMnFsHiZLVhIY3h=%$p0O!(h>i8CE42=#& z`2#MlZA=_NLWQuh5!&XpFfcVvF@nz!pd~hN37wmtl$n!ST9TQQS(2GrtdA&)6AKD* zpeJ_}>*Zt?m+1NCC#B@4!|VVBs<{+Xa0oO=sUC2FRLa44Qp!Uvu2yD_V4-s3WFr$p zbNq7!R1ZE2_|6)74@?G8DQKxxIKJlMI?BWmBGks1YGGz#lm?mbBPPJHtTe=yu1QMP zmb97Fr&gfjNzd=OxQ;M!gbLNdGAXnmFflMSNlUTB(WJu`V7Ll`+|;~MJx4^*;F4OB zn3;pr*9GlYVG0X}778@z>(ZfE=-}p>#LN*U)XZp-m|~h}WI#&ui@Kp{G++&=&_Py4 zOyuUe$ixvYG>OqD(ZIyOzzAF47Hi;BRz?s`@$lu6)Tsht!-~|306gVI4>#9ZCXNW9 z614In*}}-g(j<+%@**uKF&)}sKq@HQK%y{flnqUcEv1+uqhQ4Z(7Kde04$z5j2LxP1 z84OrlNgr|LM?1q5=kcZB3>q#tj$Qg- z4s)q2gM+<`s_6xO*CuAr!Ka|3nLb##;t7ZcY@i_(ag0@47Rlz8X=ced%0x(y6>aEM zFBm*d4sZKdfOarTNJ4`YIY4nI7s>)o3^}*bIN%<#ah+l2kPzx;OtVZ$GB8XdrAWjY zez=;Gn&0)1O(m*tF(9u@PHUiK1 zrFb|Fvj`j(kT}eua9BWzPYA~U%c}5K5VZAKjQ70da|fiu^&Ty1aMWNWHXl4=1C1z1 z3S}9lnHZ*EUw8nC0kl+)CDyR1!^j-)ZPduILvhBSRV)x2j4#UnUks)4q}Fbm@wOv z4NWYhm<(V8e8{0raT>!Bx_TsMcJf1a3M1EEW)1_P2FAqHB%{%n%^j3!wBfvWj{R&hXVSv!l7tCNZ2Fe7~a&qCSI%-C3p=?d)d{QxeIha0qA zL>U^`57R6$LJI^?3sx0W=3;NgZg{f`OEdO^m8&WP!wMI0GZyAj*~<)3E~CxnxZ1qb^5Wu>#Jm*9u6xjKdzf9yhGwP)QXF>~zcPMf>}FD8RDjlZ$iYvoau+?A z52GB7f0l`+;rxk*>k2c+T}GjLi{w-j6BGQ~1ZfxAuw61}(KMeq|IA^+_!$Ni9msuTaoc$Vp8rQ8qNQfR&!$gUP^7V?<8aMQjq&85It2;Y#2r zC(QBi5+8ix&fuZ}a>_82gIZ27-_(Va6Tg^zLBWR}Jlk0njIe~-c2)&r_?g@k1>6)~ z&@$X_j6&(=$*GCv=1I86W2jNeSipwGqqulMeg~K9NvymK4C2nXf`1Vsk)3Lcv<*HD zkBHO_GGF|#EJK4KfwCN3A)W$#DlgZ3CJqH5WAI7A3eNd?#ihBaMG8f!$*FlI#qo)G znYrNGR^l^3M+wI#Llr2SS;Bk3j789Mc5w#{nMW#sGXaQTyuf&c5i&Rf8f#&&c7>!? zYgkBtMq!|wUrc(SGzxap0WK`b5|o)87{Lh>l&2Z+pAx%?mFqndM>msDnwg1dss+|1 zuvDwx%ne{$3IafRA3g2Sue=?|G`WfwbgaKr0-dN0{pNv#n7#iR{7H&I?lmZNbxsI2}E zIyZ6QG-%_BQKBJJ;lWEr14eLJi>E5Oz{}Om%(0zGXd`1VWLdWc_!hLJ#55CAa|@g` zK6N8SQ{Ta$BGtguz{1o(L017@|LNwWCKhKXNG8&fyi)zaXS&opVA<_!VB7pzKKaF8_VD|jf!trhYgwv zah2W;jEvxv30owCaZ?Sxy8xS*5aGBHB^*H))EwXv*dPwQphkEX69c5$L}|4_IiHyL z!C6HXG%%ss^wrw+7n2Uhf~J-p4gp3f4gr49?G5|ImAHi9Yy(DU!>tLF9YFWkfG>Es zzQv=w!3iL@0iL_4*-4}& zX;Z}Uf^MQs6#`!YjqN5{sOL?j7}KDhr%&2Wp?%us<9f=-ktW0ouH&KpG?ijZr>j3x zY41;duGvf+>1YLds=0}oMKY{FH!#3e!zY?1r5dKB)+(Erm`X8igSCF?lL*piA2@rN zK_|CvV-gBQ8C*y)PD?dRF;xgINh~QXc1kP)_y5z3(~^wMjj{LtGxLg5kw?Zs0|3TS zjG55nMBT3%z}E1K1pF4N@svJW#K+%kdL#3c;T_G zY+_2dN++wcjOgZMB1a{(r#zhz+Ed=ZkJ`-d~(V)S#SGhSdm%FOkJkt1733iFD zXeJ>CrILqu*IbG*7wTR5r0Z$r<8# zLn+2WsORYu)A_WIX;Dy%wGft^A^tRyVl1MoKMQE@PchJ#R1s>6)zHw=EY;Eo)?zim z*;VIc6UN@CuET^m2OKITmY33HGCWzSvY*%YrB%7rgq$brWn;IHOF;>zwSjuT1EdQ9fell`Y z3Mrd|PNqvWRR{x}TjvQ{#REE{EI0Q*)z41;50y%yjUgQ>Vlt1Mq;H zky)C7ajFsV19C=EjLOhd1-jvRf)V(N>j_4#)XFyCi=M%&Bo&DWFiwW!vJ4DCZmJA< zm)*c?s~IrX4&qrFb%U4dFC&MtkQDYHQZ_L*kz!Oq^)Icn5;y=DPB3mIX26a@|8HUj z`Cmmy2sLRao0z~l`Orwl?P;`0hH|N*8wI;X8I`&`KAE2}Z7v zl`fzQ*MBi7aI9#U(846}m|u!hV5Jynt;+^+_~0Fr1|xKJOFQEQ=7Zq5+G^C|B+8VbM$=U9y#U-UhsUC@WDLJV{2FfNTuv!(AUK}R6fbP9_m`KJBS#p=|RU@aOM@*o_ zD;SFzu6#NMEiBeNAfvF@1x_rrsENhG(#$eBDHT@t;7%+_hQ_Jp=9ZwuVlKs42TLq; zS_eai#Im26YZ4Pjosfb_idl-GVVXj4YI1&FN@7tZXsa0{sen=nY{3U;3l4(@Xeae< zmn1G;&?yn33=F&Jm}YA6ruwBh4IO=K9LWQIDru?6O7+*OOHQ;4~Nr9yF# zyAybNU7;v3FFlpCbH`v_uE!tOn5_oV8xyizzZp3i8F6>Njm)f!%$1D|q?nrM>-h#c zG|c6=o-lGWF~WKw5U;|Px6mh!8|mOpd9G)S9L;cVT38ubC>z5nKYCUD^oUOdP|L6d zUuw1lm0zHaR4Xhs(`$m09{yJZrRP?7L_<7F-(levI;3YMuJ4Q-ZH(9|C}SflV^d{g zQz^D~#!kj=cqB8@BRA8&xK`$R%gE7=QbU1!Y@}>#CdJeN^DRC6dV;Zy4l%6)O3)p! z1P%2n>;N?Sc(a`j-c$vZTb+2yEn{OVV`F7w3n`{9SR~V{+@?n~tAV1q3m(l7&%%21 z^oiz9Iz+QNXr)m%YVI`wl~*PPQcOMYh^15aiXIWG0cu|M;7iyL{~Jm%^}_s5pZM;k zLwsw3l65aUSwp-EYj@Gdn>}>!rWR&^#$|kTCbo6On(Z1%@2K9;i8HFSr zlaoQyGD)CIEh-iK%TkMS5-W?95r-#(_PWyi1`sAlACdO{)&=={0;73GERhLTgcWp+NSCajCAw24p$cw$3aFR?%;x=%#u{lDNlw{Oo!mUov}?6G#C`4 z;v&kRw-Y=V1Y=Vp3>@UGu$^D(Aa4ae_z64?4W4kAfIov5Jaj!SS|I6pTz0}f2Y1y8 zZMN>H0{#-4g%lW9FfqcH%vLz5GBC_@ zf-ISZa=?Cu#g8n*9b*?&2EM~Cr=gVjFhAe{MfXIc=r%PmmSUO&kM5b(Y*^4-MgUC4dsBN8|Fe;hK5iV zRfYz3aCFBi#9mn(bu}P` z8?~kjK|5WkRC51fG7z}WuSEF31;hzAcryA{X0FLh98(yD1t?sf?(TD8?41iDnjNgeOrFQ$fcqg0{;VnMg5BgBK}H4Gj$}R4F_f?6DRe z4fc>m!U;y`NgV(GqvR+YGfoF!XGJg^Am^-z72xUFX^cXOi6$1QNhanBkX`pKsmb|8 z9_7i<7L&4xk*O5ZbXaK6Cmg2I!T&3nx!RdHrZWmj;_x?oouUQhAZ;=M9mSx* zv7@P>g-PHAbd$*jaV2gcPL3BW64zK2u)12ZV!%s|UG7Kw=|sVRnp^NmR= z=uqgST4fU>GbyH-u#^%oO!LhuX0C6H95We(MTpq!@!1G=L_lW)JsdvW}UnkBMV8qmYVW zijj$-kp-4y;*?)flAo(=Vho$g0xfV5Faxi85-=m#%*seLO%M05S9mKL7v# literal 0 HcmV?d00001 diff --git a/services/surfaceflinger/tests/tracing/testdata/transactions_trace_b275630566.winscope b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_b275630566.winscope new file mode 100644 index 0000000000000000000000000000000000000000..6f7ba154211ee22d5872bf5327dfe4ef5bb61326 GIT binary patch literal 552698 zcmd-K@rwuva&&eTdNz-d4sHP_ z4aNXQD`5^EfddWA4NMx00ga3W57{bSvRQF(@Is~8IQYO?**N&YGzW(O)Ce{XL9hrL zhY*w&2GblIA_7ht%mIv6Y#gFs5jGAnFwMasF5slW0+x{gi?DG>LTM>5&A}lp;H1F{ zcB+hklLi}Dtt?oMjYAGf%Y$hS4h5(iIXD#IZc+lv@p32&90GfZv0yH<6&r^NSeT7N z6-;w*s6n-|ai~K@G~h;Pf<@Rkw7@hkhc?(KrU0-}f*d+v?*IS)|Fgq{biqRZ5JHL^ z_TY%)K#MpIM7$|+q=S{9$L$QHxK-tt!U$31!H8zkOeAFn95WaNni?7!STq_M8XA6p z+{So|QBvR;zZ8Q5h{x}Nl7Ot_IA$>l9Bw$&(8UtKSTKcg6(d6f6G$N#Gg`58%wPih zo`+*5lfYq6;wo6dV`arLl^Nn5P|`t3lI$>_iAjLX01=D|AQFrjtt>faK=q>eWD^e| zt@a!g2f?_H>C~(=$%`ul*;7}tZldyBlV-{f2V6@`ln9nTG)X>zxR4|X( zN|<8-vp_S5r^>)!?W(~D5@hFC$SmL#01A*r%-{eK;8+aO#G(Q67JC;XgOvcs5;PH6 zj-|{3fwBw?7Kcaaoi4Ecks~9!d4WL3677h(!w-|Lfb~H7#FbTZim*N)U zBj7NeZNeSRqhBzw>@4JXG=PzZnuO&*S2VBb4TbODFmZb%qOaO`0Q z7rG5B0gPxFYA-|y563=`-JsHBD~}Z?$9|BJpb}Y-;{daOqbvi1hKni#!)}ndxf}<< zF%1b+rhsNpOfxjtgMzWW0TR;(xFp&c75)fmup2;yz)^jRQITUs!vq$A$EaRHPDv<1 z*Uo5F#&HyE-F$EyqFUFFux>h|!XrKnb_1vo)H*GWj)sOF4uK1h6nub-$S9l6XvN9# z4HO8VaAoKC4hbf9j(?DhC&AIcB5)8?tf2=fH^)O3fkV)mPl)3Yi@C0i)Jle9IY_3pfeESd3Sg{Q%myl4nK@RlSvhm8WD_`aV8dJ?52gU-O^k{h zprpQG8>_$pE{Ut63a^fWN)f0SI6WR>0!P+WQG`9F9LI1t8x&4aT)b&%ZT6}lqT&eH z;SF307g(+QIF7Rk95}Gy?xapgY0I^VQG?^|`;LYU%Q*xZxFjyHD!lIl*$)-7Sj^2x2AE+b!*K$ucaMO*Ysv9|4HEaLVOg-5U?r){@esS#2hgGz)L1m& zct&z+c!`u6K-mDbrC`bNkX_&=A~k?h0*F9suvA=yw-BIFV=x7m-pa|YGuID&p}#Vv*p+fv!9g=`&Bt^LA7IO-i*}Ll;*e%awbyY z2#O#u29=TzNOZI;$43r<(+y6l3=@o80~iZH2Ju6xdr%rv;rPTMaJ#_?Ed`^gQs?O4 zgoZpL0mpZ8qL%C+lfW3{_&!e3LR_6=8q8jBq@lz(YM@QWXYU-6?Og)17qbP6YVT5f z_O2q?-t91ZF;h6Iy*u#PyO%_JZ8=tQf#a7$gB>G&3Fj;_N~3vGvnX`tGqK>^XweIsTi z!?7RaMbt7ffU#mH7pUG;=Qs=tKg{g4o(qv%WjT(3JlX{6JTij4iqdYC<2VhHhISMW zqlbf)0mlUrLzb80HW#GX1_~uBK8`yeZyW%Paezt>1CIM78feS$0OnN=kdY`kns7=} z<#-6yj-mMhQaBlKye83Iavbj=LD#_&z*z7QBk0sQI=I0(gi8Z6ijb|A=IG(ZQvy!p zCN*i#2ix1irGZ%jqT0IvpS?>-ws#%a-Uco#eRx!RH{!E*2Z{FTakTRY+y>mB2YJJa?EI&0(KL;&x6!S#_6_ljtx-DCcA(d{mo1nVi2c* zEdAEl(6C_^4>Tk|BNb2)sHM6b3!09gTB^Zl0PSjlYJYy57ISlK-FW-dC$TS` z;2NN%_j++FagHrK0w+PC3vT>CJOnDZggCZ>C1683s~8!Sj5t_?q{1`vQu51FQ+yNC z^D@&ilM_oa^Yg3>tc(pMS{f7;n870z{058`U|%Weaj*)Bd1R)fx?~m?eV>dg!ugPQauv$Qc_b8>R8dtHQ`_vl5s1|$tg}QN=?l}ca5PD z*mO2DT?!l=LL5G+iD@8%!7A9%R48(A32}l|7+XONL06)~!7U`>nVTArlbD>Ek)M;2 zT4ZHt4D%4Wl`0%OLOhKd?gNkAuh1D5Ni0))aY{v2#I4xm7$rHp&3@UsB;LSI2x`{0L^4m4k0{_ zhHDc<)275B40AMGjS!j|bq)~}H<())nqzgWDTgQ?H^8-tpqa14AqH~;T#YE28hs9N ztN~|XWn_%iE$SQ+D2|3J6h|}JltU7aqv6^l(6lLWNWmNpS0jm`MjEQd$i&JBdt%e) zkiq6LBQscTM^ByV9J0tBLnxF%v)zjW$e;nUyi5R6-9=4GtY40cdTb;9r(nl#^IlEYZ?|t!~m4 z5_3#WPAx9ZOv=p3EUCn=K9s{=$TmDNC#N8>Ahifm5r*Vvr{+}~8KfFpB&R5pfOxiv zc_~HtnJIet#d=Pec`2zy4#vqz25D*Lwcy0af#xX%j&vc8ki_C_TT^3DrO2tEho+*R zkz)#@P?=+LNoHAQNo7!Ka(+=twMCMdnNd=*LaBj5a(=E}dVYR-PO2Wv^2+?ulG3D9 zeJoOm1qH==PrVEt-p_F>=gc6sk!~O)^SN zOjCfVg}BT!ub{LfI5RJsw(e?ZP*j-0h~}_5j#-RCDdtJ0rb%Yzw6!19+`+IvkYfgu zkOgwuNH#S!G)+oYD5)$+wKXy@G*o~^5h8sUrdTAWB&I>q2M?N;bU9`+2?-~srWzTf z87Y7R*Eh8!BR>TkGc%ablpAwQWfl^1Nli;E%_#vTD`;MDEJ{t3XldY705{CgG%0h; zU>0HnskQ~R5Mbb5m`VoJuouN^~>x6oSDG zHYIS5#O4$wj(KofOdyV#ik`=nIOZeNfJ#eF1uSY7Ak=`0JuGU%I2JMs*&@f5Nvg4# zd5Q%rwu~(>V=JI2zoOE?#MmGuDcKMbWn5^%X~?mNSx7v|!qn8u#%(0kRh|?uAS0N{{65LAUP?(RYXbFa*1?Y!}>^X+B#RW|WZW*Y z;#kitq+pn0WMXJ!fyG5m`6VU!x!{mmhwd(Yjt$I0qL3Vq9<)aq1Qk}IYY5}mh~^Qq zL`zExL%2r_Epf$nvZ--WQW~@jl}1a#1{|B1g~ZI06HU#H5*7Rs%QDl!9apEsA_K6~ zH=?`5jAJXakW93DYH>+vQL2J(eqLrteo?GKYD#*lZgF{LK`L0^Ty%Y596Ql`kZ59& zY?21|0qzuQY+!C;WCV>0bRSr9>|z#@H#bjCGO|cia7{^1bxKUmhB^<_Th2*^dSxf3 zlXjyy$I+6}?*1;rPxhBm}O`6oT^eOB8%k6N@tx zAfEb$?utN;f6S;&A=5N-i&PUt<%+E-WRYx^YLE&oA*IkF*^;AyMM&N}B{eNE)l?xY zKQlSiGp{7IC@~o{3Ka}$$mi#Q^W{Hu*W`0NWDyEOZKzh88JMT08X4kfLWA4Md8rkp z#W{(kdC3{6Mf!SvAg<3o{LDV>+5!n;AJCu?S@v8zm-MrebZ1Vlf|7$)YvnF;(Gl z#6$ERc^StG7NHnR1Itts3ky=(>6q5y@;s;=j$v;zBgab?p-l5sOS8m*^SuwpCRQPR zaFbcVIX|zsG&i+Kp(r&uHLs*NJ~1yd7hDO%XM#E`@ySpH;Ml}oH*aATlESPKz_o#H zX#rRxMunin@rM=GYBmLB6)f%0Opd>-LjJJi1nEUp8z!2W7^RxQ5@%9TVp*zQQc-?+ zacU9HZe4OlQGRZ!o^N6%Bp>`iA2_YyXk-&gN=-~NN-;6Sn;=jv%r8pU0~?-MnhRmT z9i!(AwlpL$DJeAt<_QTVJC7dV-xbE+-waeAIIPjO^JpML4~EL zR%CK4XA=qp)#&L(`K5U&3TgR83Y6O@(bBZ&RHsZ}k913VoQsQ{P1~0U5bi%Va9eSg zP&Z?mWlEBPAxR@nSS^&Mpc3vO zx#dj^VIa zjxG)%XILykbE{>dNs6JRIjo5R^Fmo_UP@+Oy1t%cadB#i6Qs!j(+bX?o#=zu^&I^i zLaAv6MrkI7W_X(w=$3$d4N2reAk8U040vih%mr0 z+*ibLi$f>^W)3V24AYWR%@bi^P?4FKpOl!Xmz1BM4X%1}GD}KwQp+*M45|b6}9D#z`h=$p$8*Wm6nhOhXUfN{%_4Lh;E-Mu|yg$<)cGSgZyG zDTdWm97{Nb5@4#KL7HZkn38IdL~@Y26(y$UrskD6!xpr_%*7tE85~PFh5Sq`lFd?} zvk^omAN)2gK@Z>tMvhgSLTMJJsRqfWiPQ;U+)e-mG=>whIJR>N1;7FX8qh{5#ukRA zDL4ZmryiX z_?w$1r<$AL9puYREGPz7_Gk?ekhq?2VnH#?NN`lFM;{lh=Ge+5l$d6jl9rfE`XC#~ z65=ceh1~{p%ab{Fa0z)P8zv{FB|)b9&^!U|*AQ_q??5{exg7hr;7tlD*#qxBbOQ$1~oPXdI&*K(c)f|m{$@2 zH;6#8PUbkmCFGfumY8g2inS+8T7-l14o1qF$jEV;OQ_s9B`qmAB^6wbp#?a?3pDch zVQ8X&4>qh~>Qj$Z?xXXd8NBGB!_2OiMI@)jDWl z3gHqSg8;`cbW9|%xHz-8Br&f;D{NfVm!W3B22PcY^=n=4=k>d`R&=SVPq*S9c6G-b9;$(Oq5yBlTKDdqUgX@eO z_ql}jFeV!qo2MF^gIlNq7NpQOKM4CW)5mC zgb{@+7&&^lg?br{5)DiY42-bV5Ln$nS(p*y)DHAj{4*FiCUOhanIxu|CK?%#lIOAd z07rBZavCV*VK}Xmkz+o$P(E7JCR-SpSem4f7qw|QiRsX+h!m%8AW@h#*fZsPMvevC zLM@EO#>uHEhBS;(VqG{NJtF%VIhJw@l^U8D8k#4jQZXWlaScX8X40pOcE0l&1jb$AwfmJdF#+)u7{Ch2e(j> zrA1;=vZXQAV-AmVK+%Tb9C?m*9wD~if}+flREd@Yj0!qvqi(7k9XvwZNlAse@O6*} z7!}&l*8o>=Oyd!X!&0O{Y7fHAAZ+R|CzAvfUZT%uH8XO|;StJq0?kSngQul+vBfzy z^Rabmv8ls!hoZtXXb3?2g*hB6c!YwW{ZGhHGnNI>;IZFS^Aw|$WCJ3{enAVOF_utQ zbFAkPO2iV{;9?qeMG#S{aIV-9RCtNLVyBv81Klitgl@SZ$3`9@@sOg#yy8sIk}B{r zFDnK3!f-{*b=Dpnn|Oo_P@)E+92^@a1_q{SX2$U)a3dju{@~>y8&M7*H_BNxGoWLo6q!Y_D=KSphox*4EVO3*8!y*n0`8(HUf#p9!<)<)$40y5THlxDr zNl(ACY3$y(cK50o>;_LD3at1!*7686fHt5)4$80+;aCUSv)cwfjS9SK3bbKgkYf!< zv1EeoH-L&@xKE2~InOel zwLI&1*6;}BbNpI!=m;oAu7YAD1QejF*4}2+__yur9+1o5L4y)0Kv+NlauFp!7J&i; zDumy{=}a7Zz8yIMvhfMXMo3_71X;P`%KO70E4zsZtmUTPomIs>9)kjFhflkm>f%ej zcBu_U0^q>vvBeo!?4ZE@^~Y5! zyVQyVVHcc%$Oa0;A1Hyysljdl6~XW>BoI%49JZ-vA=qJGa0KEdlt5ep3Ph+7hN;Mb zDAdWs(Y61;1yJBMt-H+#4U2mqD_?G(cpMaHlZXh5J&*o7s}{At28BhQMv0wj>q-N= z)Hx!e1i}Kke;&G{9&~~TR0PA-kg&K8a@e1DTfq+dh9fMXrxQR8T?z^ds1Sy!C}EM$ zG5x}hE1;m52nq^t3VsH%@b|$j=RhIx1BZps5)EqMGLVH(A^aBJW8yfs@yQL4jWaib9QFqq7M7sn86X8d3&_xkkYgC2VZp4yZU7a*G!z`b zU_+;EeRvUM=r5?D_zTVDpa3SS(EQHC@o4AHJD>nw4+`K4%%b5>$D5lVtN%cY1`E&` zdIuUB;b-U(eRQ5Q=mbqcSq6p%&{2DArEy0sI2NFF7&I1m@YHIMeATkJSb&zwP(xRMVgV|I5w}8nnK-U3JpTX`@<%}-Z-*K3y*sDg1zGnO8uIY)YXXIz zASnEPiu*dK#)ZU7a*@F^txCW9Pyqx~K@{2Eq+n_*VysTf)yLk(RC3O}e2hN+BEs+9 zY;G4-{p}2{po2?zb?sD5YmV5amY6Bw4nHPP_(5wls8>NJZ$d>dd*hOPpIA5;j#R8aUW*|O&aDEvO+2*3A-&x69(;6K*z69I+a_G(`TRjF!t z`0WQd^U;GB;B>p1i16zR=XFtCwK0RkFG-(`U;Ox|f zBlMsZ4%E;!pwNQ~VTRssMve{Nw|xMG-ds@VL45xNWX-l4$DVEu^7_t^@fUDuiLG z&`(B=D{UKpfc(B2;rHnvzxOSf{0`*zy~O%Go{uEILrYkwGldEED<*&(wfDlow;)IL z;P5-NfQ1^m9^`kZ5LUlWJbC*M$nU2ReqRQ%=Gn7lA3@d}B-ZaW=fSC-*m#GQj!-Wi7B>5e+tP>+x*6js3s-sqpM&?O~K)JXu$?GbTi2B zP$8^-f4=Q_56JIr8*Vd7LCX0@AZt2$t~Rco!ESJoSicL(k>q#OvQC0vS$7-csMlu> zgB>*uhu@(E8`SSxKz@e`VfFjmf7>R2{5}=o_m3cJKD=Jv3iA6^V*RemOp@PG%Q{Jd zW!)=~qn;c)4tCTG9DauuY*4>%1^FE+gw^j)o==zp^7|5m-y1gEX4IH|`CKQ+?>C9{ zyYpLc+m6_B9<{8KB3Rb_1o{2b^_yTv&BEb#Xu$^c`!w{-dF52FUN55q|Fn z`Tg6vOT8e!-zC=Xw{DW;chs^@nqXPi0diFH;hA7Z&B5V!Xu$^c`*x7up+Z>wK5gg6 z*&x3kLil|i$eM`<=T8Fp{UNb_Uw?)qzoV9QG6c)I=^#fuLW6i?c%+uAZwly>-XD-Nb);sStm=dtXl?h)U0>2!H!yh z!|%|74eIxuAiqO}u=>4yUgHvw-yb0Sz6WGY$GMp^L4JQptlu4WljL{QvQCa*S+^DB zsQCx_!H!yl!|%|74eIw@AiqO}u=>4q%J1bMzrRQL{WQp$tDh##1^N9gv3{4^PLkhI z%Q|_2W!({wqqf|i33k*H9DauuY*4@N2KgN-gw^jY=hv(O`TZZl@3%nK+`PA9A;|9^ ziS@haR+9XVTGlBLEbA_V9Cf9y4eY38IQ$MR*r0yj1M)jm2&>m@H@0%gZh0h$nQ`gtbYIV z;QnTi-{&Cw{sUyq6nzb{?)Y&Xd7yAXb#2C`=J=0}@Aes4U3V?G{fUKBiT^L7tO zen&0qR0x)J{UArRO}F9lgMspZoa zkTtEu`aS3%Nq$Ey>r@Grb@M=uy4N@z?5GVm{0=SHpng9H@;g)rtKaXxe|H$<_ZtYm zZvk0z`R{=pAisAK>vxZ1B>5e+tWzUc)~y9O>eK!yU`K7j;df}k2KDRy8Q*{H0r|a`SieV+)}}=*>(mLBb$dXLdV6Rl*il<>_#IlXLH&Lh zah|NJz_@81x9zXY;o-ktjgKz^S{tlzmVkreN!Wt|4WvhFmBF1$N@1myQA#QOcwb&~v!TGnY2EbDH8 z95rYD8?d8x;P5-NV1xSoD9G%%D-Pp zawTe>)Wee}L9U#6=f-o8E7u`hIRoU%S$A&U0l9J+v92`zO_D27bB{iO+;eo>zSkgE zZbP_o0mzlNyQVwe32C2H<5B9MFfmhAZs za^*RMD|dig`RV0`Hy~GTBG#2Zcu8_4YVI*6kbCCMUHcp4%4-N$9ss%Wdd%pL~{||EIJ%lSyfLuBKz|=1wSMDIzmB&O$awTf+F(r_De$1ZN zyk-Wwz%zs^FMwRx{$lSBkSli+>&i4ql3aU*+l**mf?U~f zsHbDi40eO##JcjWGD)sP%{^9la*r0%HXcx2e)UezbdXE?5H9@za_Pc#7kfc2Jw>cb z84O5rDQXU~#+QR8GI8AAdTutzmD513T*1iEl_&5IWeZ6A@0}oT7;JzpX5$6jC)Uuw z1l^Wl3%jG2Lxa-;?Uq|2?u<3(n87SCd%~dx4$wM4h6a1^Wyyp!N(F;vGT20Y~<;BePRJfeix({h#~*}&{eSfZXELKUf%@E z@4+GeV9gz{{9cH>(iBFH9XvwSD4Vq)r^Z5V#3A|EeofFBaTci-hKYve*!S*%FV}%= z{z}U)0^K;ksepC>yA~)kUovy_^t3Mrh2|+xXrk|l`f%;dQczIsgLp+s2^!i*KruVF zr+qO<^6?$W(nVH|9XtXYQhS-8ia6%R<; z$p-LEh74BR9H2c@j2es;5812?LHnQ%G&D4TFR&%%jzlX+jsR)7#Yoysf>x&BMfeb{ z#NT{qrOR=USpb?N6ggHjOkfds%#ZUDM=MUy(s&0@@nOXYy0{z0e846EW=dh%d2{#4 zOHhh8XoChH>dqT1Szz6^W#D+b2gw3XOmldIQaRqamv3Km)aL4nwHt`8Kv2HjAH!UIv^4k)rNCxWUAP&|P#qsE1O`!|3*b{Sd%bAp1! z1C#=#uq2)Jr|*MR_d!gTvcim&3%ky40U6j2QG%SLQ34H83%mrmdCiKW;E22kbps@u zf;JI0V%ch8hp%C@i;?5urBgdWVY3K1Y^H9x&8Ts8+Xqmn8T3M(1`nG9oc7of^7bdQ zwu8Jf0jeI6kRgt50~tE`*T0P*MOSe+9<{w>kEgvP)WgUzXT`C-Ajj`OcKj-kJ==GG z2YJh2BGfT($2*AIL!2`Qr0nazO<*@)$Kf2*Mv())M$rUDjyVSo90WP%II?qgg6w(t z^aIGD29q(IqYQPSh0aYJ&OvRHIO1uOI5Dl@5t_ir@oK@LqaY{UMRw8& zkXGxL0Fm~s;2wvRtr!MiAVL3JFw%z{Mob&$aw)Ak$& zDZGor6R3R&XM9=a5+lcoZyU~nJkYcisW|xpvS)VZagf&xrlEVF!QNiV1=9nQH=PHo zp9EFU3cCK5$%++pJuZma!6U$A#l!KEO#r?(6x@J)2D0q!r4PqIp1P02Q>a#22Ch`K+ZXPa_bq8a~|Sw4r*3% z#gmnsn5OXvO=9F&d1?Jskdqc5J82fkNo^m_fE;Tu3*AW$zECGk0x6q$?EQI=lOE%6 z5^8pG!;_t~Sf=sJ;Sn@Id)B2@D!x+4zkAcAdUN09RnF@Fb`ehf+Uc}b?aul z1ZjMYtnnU5o`F0zA46jrNaMfWiElv~e<5pp3(~lE!3L0#1`E(NDrA8)eqJ;A zBS>TCHl%#|2c&WJ-(?^p4HlwnJdg|0cz@Q!uON*xkTrI1yUnO^@y1?|kp_#+!Z~4K^nIqYg_@+xNqN1kdX#U&^0b7 z1!+9~sl9RS40eH|$QrkUH11r!4P>OjQgn?C^=*QQ>O#!tu^UxPHB{;&XKq`?Yw zjSe*+jo+92odD9^D34>u_bD4emN)FU z%_tR(X?fq3XCTKJY=Y`1yGQB7w1Y=zCnLwi+eiC0%wQL|vFkRYR4Cdp4i~>}@7XYe z-C!%!HdBrc9s&67FIkS3rUp>ML?BioRsnR92b6EY=&)G`e9{PuM65!rls~3FPE6VX zHhBxwWF3xn9)a7St}&x5gG0BAD#L?ME*dkpi3TttkAOiABhdo6-In9fi%r`=PQSPN zHlvgtrX5}X8aIPnzXNIqH^>f9Pjx|^z0eFsjw`>G?gHt5i$j0+qL%F-{d>^$A1JYx ziUjqXF7Cd~sIc_#@4bu~Z+gFjEqjim_CjrfdEje;*>ZF)c(4!TvadK?_GSJVkjoAB zp}R~W+g|80GsoK7D-VJ6x9qvi2x%rh*nOK(Ui$&8VKY<%J4Yvn0Fwr@6+6df5QD{vont+h0Fwr*6+6dD z5QEK%o#O$B!46`u2{37JSg~`=WENo3;2ePA97GS$N(^$q31~#9feGB11`&)(?i{;# zgmlf4(@c#L4Hbg(5(|nm@=JnC5{pVQ^U@(hd*I{0N)n5+JyUE=q1TzC-+(N1k%ePY z@5j3s1uoTdcl1uoIM&430x137&}@BJ8EG zl#YwfZ9WWg&0(l|UXEQn0#4xL+1LXZk^28qx|k{A!|T&WL7GoOHFI(71`Y9njv>Nk zS6Am!u+L9Hl_L!ZvRHA0EQZ|T%q|s$Ie>qovk?@U1~1k(H7t0@Yh}){hezOILqjVQ zxX78$zQA79m1}|hCPrC~YkiX&Hkb=+=8(84q>!S)W&jZYm2+ZJa%eLHo2JYH8~h4l zu#!E;ZXO|3VxwMAVHfoHE(1pBDQ)2#dw7Iwi84SzR{@lebc>-0O&5BmutZCPpu%o6 zgN07Aah#pH<}@fxZ3m?(EJHjGm+b+2<`^W1rM_Xw#|!7Z0IN9-QDbGou?#-SslhR$ zjaA?-zZ8cMn?#$a!Ul0$28ITE$V32g$!?{KG^DD?F{5cp4~GB~?y*sQ@J3`%e!In} z%(0;92t>I7BQEoeU}=fv7NZ)+jHZ?b4gqGcvkq{Dh{`M~GuTSP!yt;-+aJ<*Y*h zqmnVlULGMy?1$OGiZ6_72J$)f(fi=I{d7Axj%g2%P!B7|@`khLL3!ycC@*1YkM#dP z0FK)$(71&sh6H!41CEay8qa_d&t<53{HbyO(*0o7*C48;_Aqi_4DX)3e+sPTJVcEZ z563|s(15rGW5Hg~?8*Tif$jz-4HgJ*7bxW%Xy5{+DQ=D_i~^wP6{Y}2D>+b=dmD5# z9dd3dIKTr*gH}8ovlzi6I$*7Wpp16{REEP+os|%1dh;~MfP#ZCO}rd?c?1rDlxc7V zU~SKxKD`eVz6Lj-u7`)8f-Q6$VHK!8JlXsloGstts1H#`MZEEhiYR$;9Ke2bqOmD> zV$CSUB+1ay0y29BKG_lJ7{a$ zJ^p(g=I>cpKEL(ZD@CZ$23LL!+4WOn1XwC0*Muo?qQ+A+2Xls6_V@9V5rHRSWKeT(t(|DlFy4saq#Nt~PjxuAf1{UTPiq%#UjzZ>+t33+#>G z(2zFfIKU$?a{}n-#+dWrE~+m=R)CIY%3W~TP8CF?GB6}MuVUok*v~BRol~NbU12A$ zl?te6nAvckfeAT}Lozt@IFJqE&^9-&xfx%4y_|bY9POt*JO+9A5DpLTICv5iAO?@o zJ$yja9$RgD;Ph6Ic?M50)N|WoJ5cA>sV89d_n_)g57ZF_9jG&9t*?XXk2Ssy&;Wh| z3gB7q?%V}Myc*tF~X8L8cnKhU$U`lfyeZZ0Tyx%$s2Kub}Et(-rujtIR%M2i3E^@N~5f6wFWG z+<6E}SIwBgd|uecMYUC70VtT;Rz9;+1re!>o8rL1yp>ttHm5`vyTTQ6gBi6_48T(< z3VmVZc)#x6dr&aX+=qSSuuCZClF7i zBhTy}*{KB^Sk zLHLU8FN_>rueN;$1;b@fFkq>tUT@h04u%iVV1UQI!ckc4x9+>msPXCbt2ZET_h5Sa zhlh@fs`G;mkhkrNdF@p}L~2c77|7cU4ECVdpUN!opF?65yTW>My^UI!2IDJCpD=N3 zn7;8h$lD)4-o_I9lU~071&hHasJBsLUl>%I*yZ~=s4mLGUdnu**6{-r9ABXN;lZ|G zqaC(VY1*6lAom!2Lsy@$5EjgfK*2og^70QLbNexa*?*;^i>lrGa!@cU1xVYgf{4`T z-vdCwte^)D<_2bgBODU@*%e-s8_cLhcnH2C{1p?&uJ*tGLBZU+|2Cr(mLmN4jI&^G ze};M+UWA`W@^w(vNx~kut5(egnQHJ8stX>t4|=c`;qyMN0jvK3RgW5(XlFe_O0@l; zz}_@x`4>=NPr?lBY3?j8s$QY7punCbu4%6dB2s6q_5uYqLpV6FpEC*kW0$zWuCS4x z%&d%BkcZ+c$hRQ#JQ>Q0+8>mwFt`u-Oj2-Iou#`haB~{R|OHNvR@35yxqnm@QPJp2ZzE@ zK{CCKIx7*5ui$TC;&{7nbvMY{Pe9(rQUdmM-3EL6AJp5Zu@63A)=bXVL3NfaJocyU zzs;y|V8ix?jWgH{W@84!rX?oMs@iW(I7e~uCQgXAR|OHNR`p6q!ElmMU>}RbR}O`b zLSzO5YDEx%uOe8&%yD-9(|%AeG#H}-0Yz9TsroR3bkUQsLMpNPnZfDi>%(ekInGNUMblzNflJI1 zvpE&+3X>U4s1->hzKY}sBgco=Et5gfG#wO8kRtj5$Q$dHPV4}AV)odAa85|c>_xk zy?Rd{$eRWo(Ab2>zQY`>vA=4@5wQApsCv}c=L4lS-LpOpsuR!nVC@V&{L%q(k3lz7 zKis|rqP9|-!EO7upkVHLavYqhmq3Hr0@1dg(Qwd7b=ChcP%xkNEwEPw5vi4Xc;Laz z%~2w7nn~gmr@~`$Lm9P1kH%M`e`Mm=IPdCgP$*vng))|+?Z=#bU|)AZeT^Dwyr58H zTIb`Sn!eV@0b8DXyrUOnzCka#{u7GQ8tJ3iDKfg-jAR zITiXu$xPX()kzG#>f|sZN8^Us^Fd+$4ix4PUn~Im;>@e*Q$W60h3SjH*OQ!7H_ulD z`J!k_iM=X_NM*jtisXw(fq#q=PdODfkn0Q7+8`ERZP3ib(f_n>F~}D!2eF^p+;(>h z$d?9v(3pd#9B|BYgJOP7zK?^dV7`w7H2>@cg~HrJFK2*4VJ&7Tuy1H`Qk~(=2MPsk z)-rol5Rp3Bn;9t-`~@B{O1$S(I7@CQpcb}q_zK%*CXQLBrY#4B!Ymx2(6ntcD3lB) zqK85SDE)JRLc!h3$3b_6d57mWY*@~Lt4J$epg92*V%Gcmzwi7d;ini!Esh+9W1q!H=xs~>+ zAR<-w^ADteG7*@@DAC2GFq7PXLM=d(@D!j*WgLfjgkrGV_Ewo+T2h*nitVyH@O5!e zdA-Df0=@FYlH`o?jLe)=y>JjS9K=MrEK^Wn79)5C3O{U3h0u9cj%%B{wu2(5;m~bH zDGRjN`8DI-7EtUgfy9oL1jk{}cA|y`0gnczfCi>hOl%V1bpuj4n4`>%E0=(x-e4*; z-UL9JL6>PYFhMe<6~4JPcm+`a$_{LxSYETm+d)-ui?@T67p83&8y0|_It^+Y^1>G@ zUdU7|Xq`j@6QdP3$R(g9a10IhQmt6lAMBae0SXj@=}^P0KpH?!XMwo<7NZ8of~FSm zVhhlc4saM~Ko?!$Ts>ih>L(VEpWOPr9aPu!c{@lMU}lbYdpCpqv;Z3Xyr8uX9{UTTYOwS-q@1KL(8ee+flFZ}xj6~75>Ccf3HLK`ygI*o zHz-9<1Epwh%#`x}+wWZ<-^_qUE<9FpKq-wG6e~-Ty&Y6Jlf4~KlgcJ=yZ$aHNX~8l z11^;ILxaQ|(XJOb!t10Oc5?XNhl!)(?6kumPaFby0!y@Row^w0NrRc_(V78@Rwhui z+S++Ls4lWak5(*(yR>K%UhFz2Jc2KpJKsO8$yq$+`Giuyhcn(~O9K{UY zV;^2Ps>bA(fr5AA!)kj~5Rv+U=>t;mDhs4Y%;8cvO>Xd_c0&NqBpjJw#GK!Taw}6c_K0XI>7fanHQ4=9>AS5IM2;_#TiUCon@~7wdIL z)hlVqpb$CcU1P5bB2u?3dWRGu3IZ__^SBf)ksBhYHF7$h8d<2HiDS~-`KLi4@(xFc zv_0qt$s5eU8X|u{Au{QpmxJn$1K2}kD=0+fY-l?G3X#*8A@b|kaYxlVl7XNQS=Llz zuL>ek%bngLg@~*`sKi1pg8e~zRf6wCD&|z2$naPiyk5g zpfvIe6e1s{dO4_uO~oD}r$Hgo^7#cg>z%_45vK)P9aZC^tU)1?@U+HW6-1<(Y}%h|F{?2u(lH7I09D3^kieh)$M`1Q%jlOP9Q z#Ei%PuNF9}u9~6=ipMoBwf3qYA~iJb6;eD(2)IZr=Tdk@Zakt^E?Iag7o|o(W8xBb2bO7iou=Jqe3-2TkP%R$xG1wFSz^38WpzFBx=-WiY~S1|KUT=o=4 z)xDQwLHUNcpw?a$M5Ip0eubtv>LWQYQGH%kz+VQWP1Po zTOiLYKo5}!Q2F->6e6yYUJj~jB(R4_@8R2w8eiVu0988%*D*t6X?TmH>hnlGP>96u zthHAK5vj#dFOWh+M8HyFHJ8Fyazg~Q)sRD=)$rk3>eCvs|9kmz~A_wQKz6>(t7G{Vn`BdVl+OvuQ6e1E&YVB1)MC!Wq=SU$U zBw!-3mP_F`xgmnuYRDzfYPhoG?;}u%e8LeT`|q3v$r~&}50Ma18hHZ>5t|d94yuce zV-Jy?pb+`@>@g^78r;PU5g+~}N7a&-4;-VocugGY>{UTT>dTU6NFgF1pf9nGOQAuW z%t0~KRzn`XRzp7%NAv6j&p{#5aRhris;Bk+Gf?y{#u_58Kp}Evk*9;I^&;#cavl^S zcP1VLWle(zm?0t(6yT^jPwy-!L|#VM*{gzx)KwFnB83Q_fR@AtE`?TdLj<+{&Bs&! z3LRqP__nnBH7G>pfkFgQ7(NAg;@H}Kpxk8e7}FEl$E_Sy_dVGS^2F)sb@r+tBK67p zCrF;)6;PAd$feLht|w3n!vX?@;k}06??9ed3-Sb(=JAG?*Ff?HOVFd$2b9pCfui+H zpr?bXaUk|+{S69{6Ib^<2BnQ>m?5$wL)B6B*w)pc5aC)>XRit(Qsw=TLxe{_SzocGiuCV z^dB6ouP{U8=3h=n)wG;WP>3Y{uCrGK5vi3wA0ed?P626&ty~I|$qfAyiC@*YQs9G!jl z7sxZqv4+S!P>5W4?%|+n{oDgkA4I1B-flRS=QN*M}S; zYy!d(JGc~PlN%zaC2J|ZlJzkQ$AtyEnl{Z~7nltSkxa}X-Tj|GHg1~1Zm<#>B1BCr zfJ5sRD72gxdpM}BTIAti#S0q!MvRx+Vp?_m$a0WV3|14iO6m(Es1v^!v{_>A`fCj! z7jA-dl&yqd{c0i597+S1!Ua| z@ztGOOdL0#o$mt0&v8)vU|F4XZT)(Xyuk*jm*J7(04f)+fgye~k3AvnL@UKK>7Ui)?*DMc^~a7pauQdmN6V4&8Nm3V4OEw&SE z$3TJ6#KbYQ4yX;G0}qS~puku?>mxWt zHJ*d)_(GcHRJ@KusO7~ zzMcjOgwA8ww+!50e+?vWuo)T%g0Nj*PlP=jRDFfvUVjGi`lZP|{h%Og!Swpn>7ov* zGV>OIy#C~7y}c@kNM-Io^*RU0>&wXXI%>&Xjj!ZxV&Z7L@^}`=>+?Zg$5Jw!y|D-+ zZ?FaGb$H1Dj{ggw`2Y6H-9a_(mpiOv_yY=r8Q&g((@i^OAWS}_?x5PZqX`rUw{F$j ztAdEs6(Puhz$Cy93WOEp1_EmFSc9*4Y+~X#^y>CJP#|o@5eR?&&IidGY()iF7T^@Bw$C=j?G)!VCrh|~vCsDZ#H zv6o9>CAoosT0GX`DISHIm^e1=y0-`v2&Zub!sA2JLGlLMum-{zP#^@KcXv?TeGVQ7 zD?owpM^GUA0R;kxNNst352;vW6kr7f!YXnD0kv4H!&58@ z{b1x+x%9#^P#`=61p=1ML;HUAg5(XhLjwU`65KiD?x5;=2=4WLAg{mra~+&+CSZD< zlWnKHD&Im^kk{WmuD4eO5viJ6P`%D9v5!k(HMw3#Ed=ZF6@mvDIiAlszY65_?;x*l z1h3b)0`kPJW4FQaJ{jr>Q^a}=2B8=Bs{NNWL7sT@q~2Z?M5LZ=KzM>ffC=PMVp-5pe=7P~u0LBjAAC=3riIt)%W z)3AhL)mD2|hBwRXk+;dG3Z`I$;aYOT5Vb691cf0~2um0;a$KFaU=t_|SAoJ1;)jOg zw;45#HM|Fh-ApWgkXT`_${=zc)enz+ko~|2N(W^70ktS=0{H&T5g)bgttDS7&%Vx2<1R0`!wTBs=0AmD)!U<%QN#*^2_5B^D=W2OEUBGBtU0}pdU7-#kP}YFV7(!p}$NV9d8fp z21UhPP*l`mb`4jpxv&EixVxcIA;)ooN8l#th<@Y@0a-iWkonGas51kPb3nC{VIjW!NzfloS|XH*Tcfl|>{4HVH}+0%~dA0!j!_ zA&i6o5C6?99MhiNJ^%{;&!F&!`Tsu1FOzq#+6D6Y2Iy`(aZt!hVurfZQ7r2`pS9oI z3(~v~8f?0tLwZ1?BP^h+6CkH!;W>Q^<#7Eej1^NDkxq8AlH@oEK4l6#MguxL9ufUq zpd zugcJ&hZ>Iy#L(h#J-HbgT7N-@gIYoH2o=JJN2MH&lk~d@0G?`=FmarE+Ia+&YNnq+ z>Jj}0h1{d3r}l#)elIlQv8I{@1@_p+Zdz`49t7##577(FSkFMluG@GSoEVp4iI5Ef z_NojgWKkoenvYr$0<8?85z+>V5U3DFgusHxiD@T~&{P(V-~F?Wfr4oZaxkquahp-& z#-GWDKwdon@v0TpU}8uH9ovp^%GQFPi@w~Pohj76Uu(h%T9rOjhhDd{x zG?SodqYD(4*Pq?r2XgKTXm}tOx)B`qstgbKP}7DjJGIgVw2=W#8||R90TsfTHiYJ| zah&OYa0-+zZh+DS`b9TK9-KM>^3HzfMK|d3lcwGP%O8Ns!wT)gps?6`_{~v}=0nh! z#+t4YLhYrn+^@8;<-l=}-Xl=GsvI*w$E-Cpu;9I4NfEQsp8WGY*rWrH1SBOV@C#*c z%BIDC!Cu$^i7^cMUoSdNf?To@D!&=aJwDsIZ-dnwhPsx#Qz4B(9SYF7bI@yjh&t-g z5OkCzk|rXKlZ00{0u}BKsukt#4pt`Mc>vJP2{Jc5aB@6kfn3yM#levdWpRSCCuHO1 zQgFLu0Vr8)>39k%uMO5g0@O+q-bq8wcBu>u$Qc8?_kze}8|WP~Xd4uoF*-mQ11f|w zV>q#Z+k{O_9BXzro&#l%uh_E3>b0lNg3`u8wB-K)RZCxJ4S!7*r}z?%FS-0U$@ z+=i#Er$MG}f&>t@t7Z=NTn8&T0W}-7J;4b&loS%~SdQhsv--ptknyLm>D3ZA$a8>a z1J8P%6Fj?l_VDcD5t_%uF=6hUOQ1lQbP{~V@?C$41E8G+TNm#Jdw&abx(jJG34D7^ z8_H}FlLos1R0Jd3;jP{7leZZ)F1-5>Huogdg;+x_!2;_+k{2IM0mYZWX{cVL>IFUL zckEsWR=OD)&gk+@&3nP}TOjh7LF2@7faf6iWbsA2_uc>n&^Ay2!3x3UAirFjdkGYD z24|q|!Ri+VO;F|fn;Y#&@@@O?f%U$I-Xz8g%IokN3)?L!k5;x_2F2|qxQ-1-)iI_I zgw`=~%zwM*4#@47k=?!zZXj70EavXf!+XM1&&zakd*t%pNR_z1@h{1iRbCBvxEbWJh({6xOKY*%6X?tKgz=>@a z&u*R*JVL%4&o1>(0J&ok4tIQ+{}1F&gNIO)5$?b=R*08lQftQyka5S(+-4L?;<#|G zWe$k@7-Tv){~rS7!hJ8VO$UYb9voS!110~nYOotXMKJQeP!h+lMePefb~K!YHH7|v z?3g-p>r9Xx`*7F+ZT3L3EgPPOkWdoGow#01i8#r54l< z&otv9Lb{xWCM<+^HacHm`Kt-_GajE_Nc90$S z;C3{fz0Iic^7JQ=)doj!*nwKaapEcBgpxSc9DBDLWXD^$9Wy|7e4BWB3&AL%94rZRpS#VdaredfqaZ6!;II-k32@^} z0=yin&tAU{vSU5O%3TjG+y-&C!>wEivT|YHjjJFlPvNi<8vjsV^5Ci2gm^jT-n#T0 zWXD;EmG@U1cn#uSgbt$YfyvSCxhH;|R*a9D|&Uik2(7haBO9Xs2%%wQMjJP(e`ji=XkgSZpV!{V~} z{B1^!)<=_Cx6EKSxPZe-)a1jDKlwa8wPiZUj^z+5&&{4Y8^m1?w{juK$_rnQOa)na z35S)a$wvTx@>#TK>T-}BMe# zTs_nV;+}+Cc?e|Xq510@x6WWUc!0x7)Z`QkR1=}46@@H4m(g2h!nmAlEm?$W$8MQ9UtL#JOUGr{}zxP?{L_G8s~EO<9uoR z-hCiDCc^D#zIdBaW95?x+d+1Gz+ne!oXg{n^E;0Y90J)f6K+R0$d2kR2=GcFYFZv2@DW zBOp7z;jjZW&Xw@T`IJjr&w=dN2)AP~$c|?ZA0G$V@dJk)sBx~0FV2%We!uv&YUd1g zfyNuR8Kpu%*Lr-p4muTa{l*nLXRsT5#Br_1WRz@7ELGYVyJJfHCA7>Iic$>RZ<>r}1^q!6P7x{@}0( zHLIxN%PJWhvwLQp2U+wDWD(d)XFwKBd)U;>q|r2g^$Czo|8UrZnqk!NWta?(MXhTu zgKTQOd7DuRY||rk@53ZqR8+ClyCWGV3)z#NQHcf%s z^aEtmt)}UVnKjPOeRct4QxguGP&1DPzRZ)vv3|kpTOgZOfouW~6|~>H&8Ttp_2Vlb zJ6dqqftnOF@g;=}j+cKI-v`-o0AvR^+@{^U&8To==b7D%8a;oSZ-Q)U!(kI@TF}Cu z7LNQ{`50u=HMmV{KsK%2vThQyM%TMVcR)6E;IIibEokFQ3o{rwrrn(L9AwoSkX5ju zGth7q7&B@dym%et6oco`91K4&SD^xQjp$u}i9;Z#HJxZ)!KCs2%d&?cr*+|Q8fxOv z!IyZZGjhy7)$v z!)2(6O&4Ecv*ldqXTp`<1g_zk#$b#G!rmtI3~1+TWvVKM(@a{$%>6Um)!(acF;cXwr9(_K)b= zA9#VZ&uYE#52Sq~4(+QawEhNZ|BSAk!4af=U;CA&T{GAPcH+?f`RIR;@djTpw3~yp zZQ4n*Ip=>40eN`=-MABVZE~H z#hzCn^9+7tsFwh_cI}4?lR(zp#9`gM_S3VTfs8k3e28|?d%`z6AzO~G3y;kMY5#~r`{Y^8vq6q; z#?bx^NT>*f8x;o`eyfhkoHz|?GMg@w9h}kZ5c>=$8GF+|K7goi$U7k(X~75 zu@joa$Z>tiu~i`b(?R;d^}#z(eQ@yN?&TnDeK?8%)cQaVPkn&xPLro+o`M{0(24G- zhLv`pi`!oQTeA-2sAV8WVJT}DF8U2}v_Utz{smL*AT7eK+qW4tW?t)F0rJFW94*30 zC@sRLpcWxi2(v|K%dxz9?dHjyC8;ca%dDD5BQy+^`L#eH#w#vz9k) z206ALLwk&!&@o1iD{ZTGf%G2*>2CvXyluI4n^9xR(wW;q`X)kSiVw04wgt4K7PRqI z5we%Ip@B=5fkDPel|f;$3;60=W8+$T)w5@6?2)&^f)_8KR>Asss$gu9`*GuIkVg$B zqI*Qa4D6A$lXmU{dE_j}BUmE$^^TVyj~Yxy*U!LjCk65NOi=8$ujvP?n?j7o3qbAh zGZ2rTJyT+@>NURv)8nWu0t0+4f@6#v&;D&X1oFrokVha6+W~UetYb^|gJNVlF%Ii+ zQe}AX6ymU1GYjoi1qBN+9fsN-FvQayz!oF7KYa#;xWQEP5Pfjb7L>GZcdb1J^2lqD zN3g`m>B}!c9yOSbuKz%XEi^{XgFL?dz^8*CkIy2;;}e`z85%x7JkG#SV6O@yFp?r_ zbHfN21`a!30myAT*7U)_z7&$Jz@4g0d z*i3}Oy6@a()cE@5-UX1umJ#EyWlpLL4qOn2&6=5QueyOX8`EK^S=S6t)&+&wm8KoH zKn`1oaM)y!!w$7AzY22LN@5(g%1M>0>mI}r|B z4RYA}QJaM*5;!zORJ^%CT;9mG0} zK?35iv@}h7)hTwGm<~ft{5Av<|MC8xA3+Ygh;Z0pki(X4TJ#p=u-(KuEI|_Du(M}m z?Nu+S%VIhVHSyaLNc>mceE$k^*iD4PPJ=|}@)$($7OoyQ+etQCm z|99j2zaWRbL^$j=$YDGGt@sUc*b!nK_CW^XFk|DtcB;n<|JY%)T~HIh1A)ZaQi|F&&1Q_?-zP{-rx!_ktYOiE!9&ki*&q3w+NOaVHWT5nZji$^zFIy3h>dwg*w$YBc+4x0>eSm%pVQ$P;8Nvy*rKpn=wu*ObxTf%BghoL5Z zcLIt3#IIj-K@MAqaM)~+!{&XT2Xe5%9bz5!0P3)`v}K?)wiMH0sEOZ$K;l2#+qeki zu#E_ZEe1KPWybtDpb)!HtiueHAW>;-ywFaSd)op`hoL5ZPXdX*rT6$!ki&K&9JU(d zuuaeZgB)z|h**aez#TRhRPxWkbQo&l_acz^S6yga1#;Lygu^z29Jc7`4zaPHDpTx2D+Wx&D zcfA0)3p~nu8RV>G+b3=VIqL(|S@`EL{qd#at&ALp8YeE_H-lZE_u*|uDJ(PQ3)lZ$ zyl)1(!CdH|1?r6XY!_7q22J8;%qO3H1~zRT)HL)N^A@BTbI?*+C?9>s+?M0(k15AN z&YtxM`w05Sw{H)FoV^fg5qx5AL5;o83`UMqU;9pj^l!kS|L2!WAoC3tqw8lVhE5Fr ze|Vcwq5b8wPs|!?JEk24S@sD>jG$)V0DKwPmgDcu-RD6r+mFL#a}Jyax!hnWy2}zW z?S=L-a%|tY=`u+FIUM@CA3On>Z?GI)|ABaWr8Nio`CA{&`CKo-HTT2sp6IKv|p zfMQ{aaayWjim5_yNn%N9u~T9Z*se6=v?OD5V-oDzi*8pL$60z@CwB^ZX9N5?x$Vpx z%ln?+2BoRS$5^hDTQI->1}J^}g63WcjHGf_!X*y6XZY#52gSnZ1V<-ch3i#zaArh_}WYP zVD9ZXv2Oub{W_?6#3mLi0gy$Y3+9mS#zQ&}2Al1hI-Y<{+W<9<8)O9RQadcO6mJ@~ zf?~#CGhBT;j{r#h0cU%ma*nBsJMM!bZaOI9GB6##aowalpt#!v)oul{4&-8ruEZ0v zDUVyakLf5`P4WxbQte*EE?YD4fUv_cpTaflW=-M4*LE10< zY55G&{t}1wJ5TeY6Vq@V9cnoV)eAYAOntKi&Jcg?#k1hAh#HtfNFr3 z0tbHB32k8FSap3Ubgf8c-38f?SJb zKhHiMCzgFY`*}cTOwOC~w+|G&%izH~59F<-duMio0-zmk;b9aDQG3w5@ZKa)7#;?NA*?`I4)X5NOY8bUuDtjaR6*P-g!Id}vd0vZ^tz$eXtx>)By_1;P3?vf1)JgC#Jdls@edDv+j2ge*teXQ;`2?yG>iguBr5L_4B6>D~yg%*UNwCUSP?gZ= z`S-gX-TMw9_Mnh_cys$IkkxlUR)d?f>p{uw@b?8PK{oc|@Ln%Ubb-z{fQn#v4_k8E z@@LX=km>KCu7rni0tdoBCqVvbI6n`p@*Pwq)ITp|qtN}cAj)3o3KPeI>-W}y{PPLb zKb@zqt_ImS8SWp{$_aJwFBH$%x!KP1yn$8I-9z8&(2SRed9FSLLzF5B& zEEO{7I0sx6GPWT7`0xiP9t}Q00}39b4JT~j_31s3J6eu*fgSq+suCKc z3=G!jLCTO|@5FSHN9ZCWNB`}Qn?S+Z{~T5-f&KL1&7}1pKTU`G2{l+zdp%)zdOdJA zegV00{=@&ESTy*I>c$&xa_DY+kb*TAub*%j?67Z81K`1NpvYDWX+`%&WrwV4}^r zpn%fBY(YHf+qE6!r{7SQA}yJOmCfrwK5D;x4y^7MR2|eu3<;N<(V8|4c~BqC0O{L( zaRpf4d>lSPZCOU(YgvLzIIy9YUrgQ#a`ardp{UUeEv}&jIA~)aR0N}h6Z*u+@#?~^ zeV`z^4+4zZ4e`>x63J!yR zsGgp%pc>uN4y8~}Ujpe{F>A*jkf)c!-G}OF)M7dcUoj2#G}zGJoBr+w8M+j1D5|IX zQ0g+!xh+r;te*b8;>a&Sij|ecsB*&`<;Q^c0Z3J>O0r2f1%E+V8KZ1&2^>oXjz1KjV zehl(7I1e2Fx%~I$hvz{q-wn3})#a$AQ9QoV2v#MW2bsHc>LXAX8#JSZq?Ee;-+MsJriV4a=o zo_YQ@$m36;B|i^n@)~sV9%I22Mk`*BNr)r&PzFdKh1VO915Q4_c^zcPVYo+8Ll3n? zOe9z$f(@NF891~OoGxRPnaje=s`w=Mgrh`H+39})1YUV3Y z=o+*`!;sQ}XKcg&SAM;^4+^FZs2x~>^EW6sTOah_0jWC<_c>~CqL#Hu1j|~mp~s)> z0Y~&vxS^=Q2`yQn!3jEQ3MzsToK8&Vc!XXua=iaKB%Ph0c=;ufbiop{Op0=1Hi_K}U%}MXGC9w=`Ayw4$NQ|cnWv;b&$iC-uniM34^C_J5U{tnnAPhWKf|bjz_bnw1Mn+ z3%BDw$d2x*FF;{t@EnI7s97l+PgW92;+Vc@LKn!6uW&n_gY0;Gva99540eN;IP5@8 z^f`DEy-*Uz|GS@hL3aFw+wmS`$B934+d+1`#$g9)^327PJcW`t=52m65oAZ}E7*A9 zcaR;Q{@myW+3^;K9jFN@4^Ki8O5*64*)t7fM=#ut{~$YBX3Xpd+3_BS9jI}hk3Y`8 z@18mfWXDvv9qq4fGiv;weS0#`@g0X9sBvD5Kh6*IPg(`CV=LT_gf9(a?aTjjKd5|3+X1)V?&!8EH9jI{*x(*sD zg3XS3x8EED+3^%^$90e$%O+je1@cEL4m+SR46UO;w=6?Nu-P$f=DDLFJKnp z$&0D`L3Xs`umc*yP&+`^8AC;|+0pUk{7H}UpRq~jnvx^dV6jbW%Apv!onBA9mYa%_6@;kQ? zZ!-$mh#43P>sG)odMu*MFrxe<9{9m{0C&<~4-e6xsdHVVDuOMglLoI^O zj49OE3$0|~*mipQUy%O)ApLTn!v?|Y?mz^i#_rZPAj1tNLPA&yUH;6ne_;7ZIOIFN zxBmiJJ{c;HWv=_e?hjxiCZfBqp$K}S+VMBH85O2)+ZChHzr6Ja$hdxpaaQP4s?hEQ zG{Qk=!$L(cA{?~n^1@JAbcyZ8QKfv2i*((UAhe5>W6h~qjmKuN3(R?Yn-L=|Y~IrG z9~3!LAd!P1zwY{cu>4f0JeF1XQ(tdrI5vabU>Zb;6)(p{9s#`T_7A>43Dz+KuHzE2 z4lLcO+1^lmU2J-sPp+9NKIdV8BuW8(L#V3lo9mEs&1cm%Lr7y1Cp{+83nZ~Oy= zMi*4G0!Z`i1_x}*jvK(sjwL~Zh>Z7kC<&KsgYM8Bwq5fbWF42wJ9f8hM$P zI4HdE?u5An(sKq`j}XTt@X{{ioiGBBoiGO)I#^aQGBhwTVjGxSz3(YFKBqx_h&nKb zWnJy^1J}TDH65ZsDU0J0y|%ds-Dc!Cf8um2DDmzDC0;BEqi54LP~tY232`1`fvQ5h zJ$&MCH7H>mxP1bwaw=4%3@C0TRfFd=qtAgID6yA{#vC_&b@wFLfEiE&bdeV9Dsn7n zYU$w+z;l-@VocD9=n8>$lOxtC3!(LJY-ZLh_0n&%7;XkF2yE1kU{2mAs#06Z*r2xQvxJrlb?_RfOl zPxNh(&?XwR#0Fhf4;8^EvEfm04`lAdt?xkoHJFd;N`?S^jKva3SQm_ZKhOtu*aD~l z@BmrhV=uItnPbkQ{S!g{nf?xYwfg(zPLO{M7NhHDa6ts&5NVHX!Dj2pSrb5EI0tF~=7P=3pj>?(Iaf<^Tn6WALJKw_gUZi9?woR>zZ+}~ zj;MmRbfHmIkEf5Tl*4hEev52{)-iFsy|rK}D7JQkVhhWHt&6`VO#=CC0n~4tpx}Wl z*n-W3&3^}4?Kt}_DAo;@LW3F_SK8Bh(BtZXKh|W`xaJqwfaOpF;K_=?3hUa?`MsTB zhb@DuM`UfO5KvpJ8RV*OPfzuMTt5%$dMoffn+G<`oz%#(iBW+A+CPBCOao|>4payu z7dbJV=MlQZ$Z@)F?MzUB-3J92EC^14EZg}1G$<$yRzh6_4FZM*i_wFC!2@d$e4c#{ zY`|)$0q`JDFtmqwYB|WB#l2s^K3{~xQ_v0x)KiThPeFw+Jf#Kl)Hz0uC1-Zc1$pWZ zvZo$^EW5w@B*?o4YoKm|dg`jo9&}GFu*d4DXS=U~4Oj;?0Pd*ZvA>r=UU@o)S98$k8`r$3l>YX1>Q>2>o676y#xpjTo+yLli=PK@K}T z`x{v0dZ-}v;jTd)%7J(eL8LATMA%+)^(LKaq1ocoWNZ*xD zm*#==t-#?S)KU(7={!^jtA{47o4gX_q02ZtH09rCkcSPnL!AltkOMEmL)$?PyRa9c zavM}7)I;0#e_(h>8|tA&Abp!I{96q2&?+1rf|dl(7-|K@5L5`OhYsJ^v=-!{4Pw1@fxFPE-fBRj{D%%Scegnuq4jI0ZIf7t{cFSTwLeJ+vQW&&LCsmV-RB z28V~Br2y1J;M>cgLRdX?ZIX9C*+@#GruH zLyw|sF_{wdl5LOQz+rDHg z$V01;J=FK%HlxO*H}64SHP{Pv57a}xo+9WTYLLR}p#_)zgAF(UH306RhNn;u{RDaF z;PZ=Me{aCyA!rEz^$_@~WT+5U4_&*md?(05$B{j>8swpyH}8PFYH$$h9;k=xPfMYD zh(Qdiht{oq2{zy`)Bw1L9Iiq=G!0}=>y_`DKryrlhlijg0MtXBpgaT>!s?-g2fyzD zdFTnUhfaVjI0zf^~1@aJ72&;#dykBq-WY5{h7k7c|*@nYI&=LUZp>B|ephB1)s^FOYdd^Xh zf2M={gQY9@>G*Y!e+|x{JL+|LfYAU_{^ckvF$=Gn+L zw|%!jo7oY8syY%>)P&uoO%zc66#bOo5l?=M_BeQSp+soB|m654zi{XGz!OW#S0qcK`iUz1eplANf%}0OK1rr z$FEhVpMnD794H{LEb?0aan2)%gj?K~=+poq^L9+rZqm_sbrG zOn(klkK#Nhwo^Q3c`on>6>>B!T=WX$$|pEn`K)*KGmts2pynWL^T6V#1?NtIRlkI* z-oz>Z4j(PHQ#==V&hmh+f|~X0>4wuY*ahZ(#(uEX^2LAGou0vN@D^$c$|OsJf{QAH zg1IZvS3&i3yaJo{4r&_uBn$K)EAS)>Xh}Sjk3Pv_z|sBvKad_Ds2<1UlFYKql1hn|20?|F zEU2d%3GHL%===ElASg^azuab&(&CulByh@K0+hSJm{H^Zx;Ohl!T4uoV?)Ct4l8z! zE1v3q}vIIvVc(~1F6QexG zq}B#d>V&p6K}}7l2u4#=N{i!iqQEbdG`_88DcJPery3eI%;K?P2L+Ks0F#svW^mr! zc=n^ze>x5Fy}^Ho5-SvA z9n4&%EYRG1Xx}`ro41~WxLE?^W^CC_XfY$l3QS$G=~;8B8RPe z`Q~~5uYs&>Lo>nwR0S0nW3{&R>F*mLJsoIz7(jX&j6l}D|GxYl$lBJgw;8dyeAe9F z+aM#l(2Qu1cTr_HV2IV)ZQr)u1?lNQ)58GL!(a%q_UDW*X6Kl}EZ`8ph;6g?yY^>bTbDv@g}2ld%Iu}4VOb@x^wuGeHw>0QHCTe88R`aI zZ1tr9&KlE#V-u^ufriEg7SIxdiYbh@7*#>OfHyT@mF*NpR3BlhL=G%?4R+UZh`WRy zGIK1r^7t<(#J_<;97``{#mA$+Kq0;oq6IO1exLyBIaqraefkYjzXGCO>L?>-UAf`) zn?E2miy&&O*2<+~5Hn zAF9DxaE-@G5ERTOKrJfRss}3}j+x8?r$I&++<+O!!|{kkprPSF0~hF6I3AAcJObSf zOd2d8Be+400;myITpZ~zo3MrE?sq#uL1(ZU;teGaj_W)^1}Mk9L6ST8KsMtPgA@Zx zv-lEtme9>fO)SokXlPJWxCT8((*oRNRPy4u!6RgZX_Be2DcG#!RP$5=0~2D*5?aH^ zvHaG&rZb>bc;9X_Vq3Q`b;X*0p!iq=@rV`qY0d#N%g&$w5p4K6sNwL?=KzI18%H`Q z*IBW1JY)eegibJXJeqW_7v$WHAm?I9lE=Ot={_@q-C!fsM0j>wkZv!v8cTLOFb$$% zEmQ+|sSW5Pz?+~6_KToqICKQTN}S^+kHAULS%TOOodxN+1=4c~S&tCMEwCO~q67I0 z$Dy-UypXg9>JK(BfzF`4N!Qh0N?9DY=(afLKNH9C<)0^lqVgOlDuY4Q;LWeM88trd zzB>UFgD0WYAf$W*RfC|JZ+VV?C{-ZnP&23qW)(OCOMLIS{1udd3^qgKTNkMhPQ*GK zvXj3RO4`O&lYH1Tvk&C)El`)ilWsyHs5I@$1KrgPDg(fnQRDN`UHu^S+actYim7i5AO6o=ZGVESGpL@(4w89K7*hDkuW(f+7IRP{Oay->s9 zA*m2y4|QTQBgdBZEptImZ2FFUru|maj#(fl?ngJ}L4duK9cE#6{!rU&korAP_0k+m znFR!885k@MsWLE}I~2vm%l^pSK{fxOy93k}&Ky&}HZB4=Vg?RJth)XNu_`1gcl(54343zZ!m#bx|xw<^OOTC zLALG%**cdIl!5!c-DcD{|83O@P&k~2W?+6$B?>wUmpWwqe#}N7aTD zWzg{g+FvT{Rdv2p*r$FnyypZ_yNVG!XtG}13aK^B0&5L}7EW%M#RF{(gANpfieMB4 z(C}W!!g2V>pS2)YUk16_0JHsZ@YhR_uM93jUC#?D^bzY(v81;B>n5%O`S%2RXfbHp zOJT90^WY}1`m1moZV_0&`eOY}kedxoq1%w4gmtg_w6~wZ>JLKIqZB<>@O0?|N|(<- z>GDjVr-Q0-pr?aW6lev~*KfBO6>dD4a)CwT#j12qO-p zAZcU?D2;Ttz62Y45gG>g(#VQ44Nj^Fm0v+=BrY-EUbU<-&p!2)9fvc4G$M-GV*(x0 zhO5T}O(SL;7mps@2udQ~K}iHlxj6Ue(siH+KLZI~sJ^|79LuLq+X~V+`v>+C<>-Ms zAcGCgK{O)T%MKFuQWcmb%84iYH-RiV3sp~k5NTEGV@7K%A~x|{Za3^{s}6!}J&(iI8()`#Ok)!k1ftw&#oyOs+<)1czTy5|W z!&MD-*y7{j+qod~3?5^skAV8(BFGnSS9V_k`QiW$UqDwlKz%U*gQyH{|P3WzG zy=qm2mVGLV`ZREPU0{#1P8R@`*NvcyXW(@@=+sH52!{V43rkym-e%Nz{Ba4`d53U> z8njA+hT23>s6mA=OcgrJ#IbHk`$JHu{R4#>mX_Yp+XwD}LgNB7)ZnFS0jP9kKODuy zd+oBfgR1pqZwF{7-T`I#i>Kd%o%xW6P?SqQ=&V}!L=O~-VzagFRr$MA>{Fj9&jp2| zLJsawj;iegcfyBLnnbk5h{dXs?b~} zj@_ruJ_Uv1>|fXmmyXSwL7{B$0vd|&>f`~39k%xMjE+5E_0OT|;h|mv3Uv-psDI@4 zaZnB8_JM_ZJ19#{-gy2N$Xl<82=zX_P#4vXNBcpcUg9^&PSq(c#V+-LCBI86Bg2DT zwm8!+J1EqltxsqGfsT!YieQG1EytB76JCOXXCsc_dHd}QDEJIsL%jd=`sOOU^8Ul z4s141U^k%@6rjT!p&}T8jcu;*@rI8e{~LUS`i~Pd%?4h&Ahev3<3v-_Pmu54f_yg} z+_iZH^4*cf(;#0Pe8%NFT}0QWX?=`~>Z|F;K)x&4(rTxA{*H%TDrc;)3(iCY_7}8y z3Qa_y^BJKc82%D^&d71E`QsmuOPhb+X2h0h`tSFFd}8nt>Q8v4X#r&#AyB5d?&<5G zYVGOkAO-Ph`>)%K8hc+K`UZ-}pG5fd=7}m7)iqHMK|U>?s&1!hc6GaL>ed6wxP8h9 zia}_>4)rN$$179>)2B}vId;8x({OeMyTD8wKHYM8;a`x8KV$W&2*{_~t9>0*rK)`$ zq^5%7^c%>hO=k~*L+C#dJ}ru=by58@54_&o)=u6|bsb`4d|7Lj4I^bOIH@>d*6?Ut2)_T!F)%UygkPg@eHtbbq#iVpJ65&natt9aMj; zf%~)PH|SE1=|{og(|R5}g=B@%;_-4`cn+#&kLJ<9aN1s!~MAk zI~qayx{2`TU&%%n)mOKkgZwFRir-GP?rF1a>WLNVc>M`2XQ2KBowf)S!Sv^1Mvj&l zuX;fKJO=XTWN^CM2y*Avt?R)4n?Qs+AMb5+QT5*Y0_4uy+j#9%Z|<(MO=V!vKyoMg zI4QK$fVvWNJR(#Ct1Iu^>zN30-?SlNN$p`3~XA2OwAee{^*w$dxOJa3zOtvx};(FG;S1<`t+bK_}lqMXUKbs_1Lh}kVEJ3H)K}E2Jy0ok#~2!RpF+SD)?xxpE%Dl@ma&{IhBDCXg$S6XD8=d(AGY zkM5D=N@!kzx)OBy98?6WE3Y&?*#mOrGK4E-T|Nd5s8H8a!-vQJwt|5tc-hm(aWdb)}33y8%=L zt1Ev$yKw^K%6$k|ZUDKmY37TAAXnZd!j(U6H@m1N-a)vMh#C@_SD>zx)nGS(iePo+ zjz#CsfLwVD;mRE#S2i5}dKBc!2Sm6s`)ad`>cy)hxe}UJpstkDU^jq@V0GoUa~Cdv zTzL-R$^#%*Zt7cb3gpVCM7Xl_Y_p5%^|J_95)tFjyaIKlyau}gR0OLl?@W4o1?0+W z2v?o}x$@JuQ)fZ0d_{ySEer?dJA^AAfL!_V%iLQaSNDK+s4JBTwr{?Kv{nXEB_%}`2pn0A9sH}1i7;50^yN|sokJF-c6D#p?L-BN>u{6 zXZpt*Z$PeW`-^B_f?T=#z|*H7SGE!1%0~^&E~@g4B)Jkb_oxxbJ@4ON`v7ufAHtOl ze{VBt-1&a!708ucM7XlJtl33%bs0&nM9n?w1ai-xx5vJKTsaNl$_|h#C;s2?4&=%{ zB3v1h)9j+!pF@%>QFD(5f!uSd_3;moE9W6xIRWI#jUT%|fm}I>2v^o6HoK^j_Bt|X#VN6kGt1ai-TB_}&TuH1)kOCa~mn0}}S0({tsqw}Bf^!(&6{0R zADSavNkmwp<{mu)x#x1v-U%RAo}1)8G(}@IcLJW_WU%7SkOb zq2o*(JFidN01A^nP?&gNE>%3#-nSkUB;TPyqRR0Cw2!8t0q@Q#h?5)s-DcGI{{Im; zOpoGlGHSLn$CK@prZ95cXlZF-XsS?BS&(XLWMF8i zkeHWJl%JWRmtU;sl$n>3S`<)}Us36hY-*g8l$KTt-dAIhYGIgYXs)2EkeHX5n+V#s z2HB*eke{Yd0x}q~9>m4sQgFeIqC&KSK7(Vq0}TW5a2%k`0i+hJ<1VC=^%h_zRAP<2XVQwOFvgQ!HqKoV$pb zP7Yis?-C`tS ze+DGygSODzVkB`l9huwqbvX_)!}bs=a;#{Wz#{ONUy6b7E_W+Vjwy_g<1(x`K_v)y z`3bhuJvO(V1I2;CA87EQtmhU=<@oXI%WhB{odm^^1~mBhfa2)s=0{+izo9z0L4nQD z%E*vVWruB9%(2T&yFd~352_xqQCsLM6Gz*z1&2WPJOkMSUdcKEWXYLzQ}=>29mBD1 z47zL;+CQHRS~mt2!WiC?g1PkEsX9oNo#3=`5~>K2UqP#7AiKLzTLhMPS_DE97&-p0oO22kFq8h@W`ujS_5W=~jT5JD zf`ZJT3F>rs(n;V3UAOqlU*a3cIW3=F90s}hG!EyWHXN+*G#s3mj_?TeFmjy!yx|a6Z9Ba^m?xX_}P$%_(lr^?rI}UQvc^pnct-G!9)ZHNG9KW;n6396l zke#y_WY3!kr$G)iXhnC<0u`upW`mTSz20#evVQuH$eHYI3&6lbk`J^I&7sBam}GBRl6l$evvb zH-a2$(23z3cc^o&gOqhod~zG)oSQhDgPN2b2qfjrOFEx{oYUEG2a(yog6x^Fe?G{e z23_dRVeo@G=NU-Z`ftA;fShw1hjUPqvLk_{{OH^NmmufNM|Mtk!yQJAi|c-Y9BR;w z?wo`WsB`{+oU`I)>tm2}?&5F`YEpJ0kd&8y?0pY%&Q@gSECe~{bldz_AbWbyowFbk z>YPa+Wp5|7g8XA}ABS^Lld>~`qWv%Y)xHI&(iIM&NFiCdHC@!$Y)a;?=VU!a_n{j?Mwo<*FglM z#*gO{K=K9`(G33&GJNU2Iln-LPe3=kAJgTMipAc}7p)+Bu7d2r5^%>q|7|%xgWcdVTEOiB zIc4dUjwX=g6sXZ~r#vVCZ5I8_E%1UJX_!Fc$me5V!#*@anrytFE)o1hLZL=Rj(>|^ zbb_4o3FI7@x1WOST7I~r4P@6ebh{FY?WJ5W!+d&6KUn=#sCrh=*#%5ite_pRAnGoU z0FxE!$%0IWd4y~^PCt9v3vxn#6H>|C)O3eYW97u@T_7jSfEtPLZmGQ%%VC})JVNFi zHy_-Y2-3b3S^Gqg_MYAaeIV_#(6t{Zw-++!XxnseDoEpQWR1%~8lP|e2Qt!NHoC?I zl^~5R%b(2zX}pN6aW6sEP#$SJ$=7BWM!_Zg@(zt2U`K2I@lbeyk_CH8t*U7&iBMs)G zYfPvEX*_Z4!Ag+EmB<>WHs4{?*tGuv$Vh_)=o%I3K^kAIzP%QtaX+%g)gX;qk9-0d zX|NDoV*^;@oSWA+f;3)5)_4%4arOG&AR`SHp=&$<*4Xgu>Q<1(*T@>LgEU@$^B-iS z!D4ic3=JTs&VF-gCrD#s3sT^`1!?>L+`IL5t}wj}y~f@F|>s-tL@sc?P?{!>&7wQlV(2OykU%lP}L;H&_L= z5&h6i=uKqcLo-3^`k{QZLo=nY+@N;t(0s7TE1@Q%9_}duI(Lc}=i#10kC`}DpISZ- z7!?;)2ECmw8Z)+u1~4KIr?xUOILKRJ z8v^P4H*+q?z|BwtIXUjbI@4xo!M1qG&)FcQ>!3=-I39oo9FUHrWRb8_@W%-5{okK~ z-Mj&+&lIGu0oDnY<>&@&ZQ9Q$U?<_P02(cU@{xnvPQhPEp5s1`5L<9TQD#Z1M9Tq2 z1s&88MYtHL1r2pM$_W}iWmp5wX1&o z-2}4sFb->9PU+kLvi2x8BNVV(JA2pPjUYY8vFTypaRpsBv;5%6?I3$Ef$WV2oepxe z`wpYRu6ql9FluzKnz;>Rz!PZe9&^(aYTwWmPu~#Rp^E#aeBKN)|0FgCe&EI$g&QtD z+zQfj8k?R6+^(R;)`f>Vc7r_i0^}(p%zES4g}We68=S>v#szLyY+Ll69(@VcvjVCI zzC{nqhAyEFCXSDL?(PS<;X4jDENZ$0a-+cosClr=3QiLTxLx5l-hrFiAc9fj%ByQ& zJ?Ei%1VLxl2t;x5KAh$2pz1oy*8y87?>%rEO%3>2JDIF=BXj=oct5El$CNl8u$jm-p z2i3E^*u&)6qh&`y`fo$^!@>mYzXjOC#r-A&mb?>V;Od2;P90LdZd#I<-2fR_oF+A~& zV_ae6czXKgd61XRgS=#d>7}zPW}g9h=@!&^@C2{h;p?C}u^oHl_B@;jGS%P-R2M9A z9Y7Hk0L#Z~K%Tn&=*u={jlRasCqUMGf_jQj@$H4D_!gST#IgVRo68_?Jq39SOX@wm z@h;ebkD(5Pr(Wd^=fA$c0&?GLH1{=t zBJBYe)+%n%mp9iydfsBwbAStL^>q039gsT=oc0UB zlU38_>|)Zmx#!UZkcHo&fr6g;Q42XAe1+T|wke8mU?b*$wab)s+aJc`1dI{}dGVsoJ z)N;yK15Y`1g^8p0bn^p{x0Zvvg(dZ$-~9mOZG-nvC!)pxcn`lwfUkpUg+KP}-Mi=I z9gr8lLiM8r%L7iVDP_ayC-*>lzN6`35O+~!DB#4JQV!jGbswbX3seu>olT&f>0o!h zFz|Iy^)>KyfCOtx?;S>kEuW5^V%B)swGJG2fAIt>YFX)rudJNH#4+*RxyPVj-3tm< zBh0e$-shX3U^n;$buKJez+U{ofi+luT)glI&V>a_1IUXP*s%u7hJ6p8f%G&!g^W_d^e{-cs4^I^V-1#x zU-yCBVek)X4?I{JK&gcf6fAaId>m94ZT4}HiUDQ94WMATdFT+A#`Cwgz){zXJygJ_ zE})jS0r*PWDNG#gvyZ$6h004Dq4N3c<`*CzwxId20p!C4Y*<6(?}MYSKziEH^c(=` zQDDOwDpUKmfZSow4Alb>CNOKvn|c}?b#2&# zg&FfOia-J-?%(@+-h+bWKaOD8|9I{jkQY0l&V@z1gCwYl$BH#rUUm1r1L^6;rso3- zw8Xsw^49BhzYjBNd}wWb3DVGs-CL|6Z=sgBK^pi=+yx7deg=7KYTq5Kr>3lI*$48r zK?l@{@K`_`HB|ZE+d=i-KX0rZqu1ZNK7d@@2i1=fECDQ7gJtEc<)1)$CScPuff;LR zX_~Vehs2;dG!3R;Xf>Mj=3U3G1SM`V_4Q?N-1T4&R`6jo zs3mSNz7qEeBS+WbUEe{$x*8O$Mwlh;ncZ)}&YcW(E=sUmV8R+K`#gCTSZ1N= zX#i!s1_rEpp3OJ_@`u43G(87EdJ_J-U>lq`+i@7IXF605+;1r$zx_EB#l_2c&dWhH z|Ew3Zl%5BQiXGjnoitAVSosCy#!1*?f)f-IsHJo$zEXNJ6UXO`U;l$*;yNfMuq3d3 z>yG~g**G8ST3ASeec139s}HAitpb^8Fc+!^?!y?655IwYI4j)CL6srg3u|7Ve_;z) z|3avKn4Jt#ph1w|E|9uw3n)}JKb^p#(fV!k50D$DVhi?I#=Fp zzA}Sd;4O|&`SfOMH=xjJ^*=Z!IB40 zm^ChMTGaruWHxqh@qxUBTF6G=D`Y1#aco|`zZ>MOnILarDUB8{z65sS3aAs|u>d~q z_X#K#CVG21sQ&Qs#G0Phu3Fs*a`8H-ew1K2@EL2cw4R&^a+ARZG(8TWJkan7ng_o1 z-CHt&E!<>@&O2UXv59#})=;;Ko#AQx|j>W762*oO)qvF3*d=lel!GS~#w1Gm!w zWal-Ion;Xo4yyOUvD?|cY8qJoR;YdyJ0HAIv-xKo%oR{uOX8x*V`LBVQ-nOM#?{F?#t;vuMWVZj3S z;)7RMgXQSnd$T}#j-csj00m3JE3Cn?|J($SI}8p&^`HjJ2~e;IRJc2+R+Phn(Hm|I7lA3r}Nnp#yebO}Myy5lGKjY{shPL2B^2thZj)`yLfzs-5f@aPuC9|1bOK^$V=dH{0S58Fe)^>+IWCTW8c#Y z`$2Bogu^P-5-0&r352b=we9S+y&%)iqxl@1H56XD2+d*SSlD^&D9CyDK+eOGy_PiJ zJq$AAA~rJ^V4*i3UH3O|%b{OPZ zgR9u|9C!f^@cqpj&w_mP2jn9o%<}9>y@_`LC))*h`n4n(lGHn$hMo4*?pWk{J*ZkvG6I#W!G@Hta;W`kjo7o zqr2=tB6QXH>WOz4HFli7bqnOEBRCv|nw&H7CFeunsfE;b`65UY`oS`9g z1LUY{6Bj)LIqDP+N1>+n9DJ#L5+ld)ziYmN95o4tquy+4{{*t_HM*l5te}p14sz7A zw{u^Cl%2uhDAa_WOCX^?Y+m&f9QEzyb&#VC-l98dfg#jU-$0H!v2xElkfYAw za1?4n&m)k~&re?Q7v!i7I2?6o`8klI4c?gkDG>q5tcj-wATmT^x?u zdHV^-(FR}99mN3KOt}fo-r#Ul+q2yuM;m-c zchmt!XhIn`j$k7Jf=#FxbfI8|r z$WdFjE?)(5)EgX*LQUwk1QPm#ZGAgIj+%(WQJu~EL5?=)MR(MJSGJH6{TawnO;c8{ z13Bs)4o9IT^g03weg1~-y&y*|z~QJ#XODp#ZP1VIs0Rz6j`|96)SuIvHh~=V0f(bd z6M8+qgg%*(W80m32SAQmkHb-ot$RR@HkgR+r~^i}QX!xLp_VCk7&V^Un!OF=s83Kw zS%L0Lfp30M;6P324WRumP$7)dLWCwWa-3?vas=e6eK=foZ}NnLAnPWhyDEX(2I8v8 zAXiQPI)4{P*%utHLQUz7AXhIty}@4(8C&h9eC}LDo%$x(adq z5X4Q3KyKRqZQeeRqHj3dgqqNsKyHExVY(@q<7n5fGax5j1vv>E9UDQG?7jQ~96mpA zSb~~xn?aU9g|J!DaqZUykR^BFmK*?CviSFdV<4aW!eI$<mw90u{n$$?~KBuYfFh z3b*7u$dZ4j7M=!K@&|_{&{PCHhNcx{2~-H1B`-Vw-T+zh7H-LPkR^XUG@b`p@(+h4 z&_n^Xqzz;VR0x|T?Th~00a@}DZpnR+CEGe4Tmo6ra0#;L4?T-OV;O2mJIE5K5H?Ge zZU6oNWXWH+CC@>YoIQ5*I>?eH9F{<18EQ!f$P%a!HcNiJ`uGH7N$b=*jF2S!9%RX~ zMb~eGENQ`E2{e|WmUMzFfeK->WaacvFF=;`!Y%m@vSiEAE%!l|wBfJ>8p}{ix)6vbAWNpgE%^_!cX{&{kR?mumh^)xxzV)$708kv9F{<18EOgm zl2fPiCV zkR@lHU-<;GWC9LLps@_K1bpQuR0x|Ti}o&QxHf}bU@zQ~@#ByTNp5^@F-AR~@u)Ne1VxT#W6kO@}^T1(`MjY8v|P;TF)Y zo&Aj9-NT^UvY>qA?X0m1u|kdF3}|oX z+=VyZgN&Gp!-&h@X1xWubuKm|0%WkRn|pt%7i6Zvd~A9?NQ12X+56x#$l4`1tbNusk8Jq%4Wwr|Ha!MXAm8p=@cuW*+Cw<3-Sy(d50DWnu^Dkd605cQE`0t4(z64|d=p5|W^8&A#G!sV0`k-EKTUm18mI5g1P8(l9Dafp%}_sqPw;~Z zVfE9ADaYDDetM6?PYp-+w1C{V6`T7mh+*~9q_4Z%Kzg=g)8ilp_0wgLpZ;8W0$Sg4 ze^nz$!z>(rg4P63KTQEu*ia#?e)>G?csIyT|8V%}-^ybhAnSHwbKeG0tbY2?xV{Ud zXE!!I2BJ_uJp%b@^Tm5NnKkb0{s0byIXL_Tt$3h*nhNq0R0z{g7Z^Ey?77kp@>B10 z?B(tL{j+;O?%RvaeH%ou`sw8N>3twQ`?2X!5P@!x{0Q>XxqZi2G;aRb4i1BPIJ^X{ z=Ad4h2J#YA2-8a!896Teel;27r8zjfwERTx1d#I%qB##-5Kj=s>ZMDUCQSnAIgCvY zgD^Cj8m8Z2R5*Bd-vVZh#ZUM2f;23^;U{Q$0QJ*!ke{GJSpBqi&)(@EKdr{$r|x-+ zr+}#!$4t;E2 zM<`pqwj(ERdg|LRkIu;rf=v zAV1x};itCGFF<}axQNYt4*XdC^nKg)g&-$h#-`^2AJk8KKz>@a@_0Y9#*@Ce^FSI_ z;P4Z)K!EybHpovMKCFKFG3~@skQ1+C z({q3qTI`($`Dwx7)1eyIp56k7!73bHf|dtRFUP~&j{_Jcuw_bmsx!{9EOo(7N}0Ul^D+yVu| z1hDz z5#WMG(+^NM>{{^4Ok>Ng^WbpXfFl^7B?vSa=7WL(Dufvf7Z^D@CZF973Wmivf}#D= z|1BUdJx6mMgBIwxA?zEL&#n5n4W#EKnjQy`9?))H@OrZL8Fv^JwtsmYrg45}BRJSL z;qVf)0D*dG0mw^GA*^1y@%rd)ke4>$@KVR?cRN7NdyVG229Wa#ux~c*K5%^(NY7g| zJq+3|sth04p|La#yDQms$@V*bnm3K^$JX z|K;f(kn`T7Ij;fayaMbSg@1M)+XvF~5ls(+4k&@cytEYLr3dG`IW#V|Pu~erzYT|% zpoIa{ON&5Wf(l`JX$~XDgV!4mgS>PBhnF6&J97YJ-DfoCHGrIVfejksTR_fh{LAgiE4m{!eXHthqs+Tb_3s~lpWuKEvh)w}H*E`VHh2#2enX&36M6(CnZg|NEnwDB>>RVQ$` z>dczQAXghSK1W*wqTm2^)f$kiT3_tC3v$&79Ik>UZK$hOgIom_!s@E#hK8n{2Dz$l!S{zCSDnJ)DrnM%x@rx`RZtM6)oXK=U*nzW&=S_^U&R0yl94qo~54&6>o2JPssQc#7u>L$ol$6sE533Amr9Ik>UZK$i(fm{U@!s@C6x3_!-xvFIr_VVh_ zvNP{N)^(!0szDm+suv(vEm`y7ElAk~9Ik>UZK$i(gIom_!s@CEe`bCMxoR>FSABoe z^#x>IH@d47grKha4sz9mm1jPJlwHE%DrnM%x@rT+RZtQxxC)xIp|08paurkvtE;Adee)0Gs*N~Y^}6jV z$khh@7_MS~x@s!ORkvn8`U!H?H5{&jCT*yzHi29P6~gMO4NqP)-JQWMZ~%v^E-st( zA7tG`bXO&Ou!GctOF*ugeB#Srkg^*%Tm?KYDLwO-oT ze0K)B!DMt-Ew}@9)n<^Z4sQR|2vT+nhpV7T8|tbpAXhkLG{s1G%bcHujR? z$o^^FAnT^1yDDLuom7;-KYxk)v+gh|Z0p>6l2xPS?6D4z;(zO#8Wud{wXyo8ogh>1;qWmu+ki~nAg-_t zAbu-a@{9q9%9{%}DdTBd{i zx(g@wUvbiXo8fcmw2_8mrz4LfdwgQN+EUt3W8`XA(1s1TZ~V18`@nY#AZ zoCzSmKEmNwXcmY1bqC0=P$4u^A$~o7w_!HOuSanBb;r3qps+BQi|*G4)lk1K1i9+_ z+SVx`SGC~qYX^#78-z934WL43u7de>7Rc04h)D7YayFq@1 z3Za<_@#}`&KbL|0+BOHge(ImU#AA>x&kp?ud$9|L+o1&u$d-0Sg=Uc3p+ac3v}4^? zHF4uFPzW0=Mh`&+0Z<73@|U;+a>avh7Z-p+@D&cfLJK00D>jHL>;d@|Dum_=h+ls- z-B|_l>m0aWe}ZheFyq={kS#qp{0c2tK(@3qDzt$73Kc@L1-oAxt}a^+GJYw#UlT4s zm#2OLxnlL)NlQS=-r(>nv>*bxVuQHCUXWj*LTIjl`1R%K+v`An-30e*|C~FF8ux#` z0z0M;hhL!u3&@ssMuk?8U!g*1wqW;bV`Ix|kSCU-`<0;!>emjCE0#7cTme${4u@Z% z1rf*<8^jg%f&2;;LURSgua7U>-URaN3AkUEf^1nfbtTv_6L9zyTCjj@X=hYu1Njvy zgk}qNzyA8wz8>U>mFRv|_z7L$I}hZFv!@@f0V(@{!>`bS2;_qw-HSPu~o(ZZ*_ZC<}c5`Ah5ux#?2ZF0j)l;RqRMF$8i` zJEKB7C}f~QXl{a)Oj|&v{yp|-1IX%6ID85%hCrrn5LY+=@+njZ&D0IzLdhJrw_Vx= z^5|QTM?*lXxKDvBdA|Ag7LX-ha99G(|4>T~f-HdwVOp}2k>kpzOZz~!{DIi=&tKv> z$d-GX_kn~Brr-!5XjuocrJYfs0~A0|Av9ZH0rUW5>ibiZc7ojg4TsyI`59#D262T$ zAh$z>&`bpf(79QM4uPEBJr`VZ|MHi353=NCL+f6UB|mUj0?kNJOAdo9feK+-vXhbH zVfWf&AX{cZZ29Le(K`1IqsF#rtM`L!nT8{PpamqzmUc#kPEY_rh0tt)1<*f`+y5;) zegI_ZFC1=%W(<(28^jfkfZPrhLNj%PxX?~Uj^__nodUUi5!~%lKyLr=?&)!mEi-Vq z9a`XlY-wjy=mNPNDuiYW%qiK z&VVdwxQu-jI5bT{EjbRd1S*7S$xcR&oo}aI1KDyOV#_~&iCrLDKF^za9%Rcb9I*f` zib1xtGb(h0VgV|IW(zD9HiJyvae3JVkf}{L+zw6ZAX7JpE1Uqi9V&!oDkKHnTzBdg z$nE#wZa)dK<@m};mqE77!QpmjVF|LOol&6&B`P)ITsVz9%4o&JH zQ#Xh!oCLWYDuiY#I0fySvf&=c>2D!U|K%@n5oAeY->mB(r?=s-1ezwHmYf1v0u{n$ zN!PLMk3g3Efm`wbWXY08w{L?i>A+zLG)+P+ISsM|DuijtPDYNqXLdXT+0s1^T+;pX zm-q&<!_d&MI!x0P65*cJmJEKA`C>EeXXtuy&;XTOI+xr%PP3^+rc4$%unYuw- z;S9*_P$4u^!Le{--{w~!r_X{o{g=PQKaeG_I`=*XIlTvmCD1epwd5?w5~vV1Oa89e z@eX9kO1LGx^X@Qe?0J3RImnVe9F{;657d%#AWNV^n3n8h9F{=SB-D}%AWNV^n3n8h7PNiEW!~$(7FX=OFN@NKPZ5pLTI+Y0%#Y=)Uy-se*u{~ z1&7&`gCCbPZcp{sX!FDctQBK(;KJ_XiYk21{_b9a>xc7ZPt zr~mSoxB;?c_2IjJK$gtFVF@&;LoK-svIHuG&5}v4=Cy$=X`T-*f`0i+JO^2_a?kOG zdo$P#X5p{|nkJ!^Tme}E6~bo8|KszzK$c8`Tk;KL$>QCwT0oY}!C?tBO+qcX3bF($ zgw2v0`{(q5ESUwdo1Tc?TiW&K`{*#LbIfuQHYnL zujk!NkR==D-(eI==6JL6^;{5l2i(q8AUk)j{W1+?=L#HlLQ89?os&RzLWQu|IcehE zr65a=L+sqO;O0sY_dMLrgCIM5?%Y}gvU3#9F{;+2-K2UAWNV^n3nKzv_I>c39@9tLU5wl z&^u!;h`VATEYZwbc!yEr=(j`D?$2O1*oDJRXbOSaIU8grR0x}$Eq|I;f-KnwvGd`% zIcq`OV{kinfb4wnzhybd&OJEngr*RvopV5TLWQu|`TPB>tsqOTLhRi9bMa0P_b%Md z^B_B$m+#yJvU48}JE18AYUf;#olqfcc5YvJ+OCUQB;II>#LZEie1K9}`!e(dVdi2Dt0=L?XXvzNEDJ($66a0G{) z&;$dua{jHSlCWke1}nE=C^BeL3SR)VJ9@f zKf_B1XRr<>|#!ul@raAnw&AFn=Cj0?MCnr!+mB!ESI0hn>&_1GRH8$WEvb zHaizBoU{OBNyAdGojbpFEdg;mmcs1(1+w$*n*DP?cAmjuCp5u8?OXz~6Doww&QBkH zp95KP4PxiKrhAt`+0`4uUTb|5d7udJ*4kNak zcDo+U-}qz(yTMxMh%?Hb-UBW!stg8l#P8`nG_N0Q+B&Fd=zDshHvoh8^zv!28$kJ( zdwL)5d$tGU?6V+eV_CjFXVzAbyun7OMX)`+46ZJ!3=DFh?WUKvKRW<2;tmcY`rBsj z1NnS2nh_2lBQD5d-Q3XnwHIWj!B#Xq4In)YvLI_W|GjktWbJDl)=vA?cL-$db~Gan zfQ)d!Ztb0(4v?7!JF)3`0N%m;`&+|FkhQ;XSbJpkucIJqccU4>;O3&rkbr#$bJLI3 z6Cgc%(eyZg^gIA>B|dfN{~3_AovW~KomhMC9>`e+`_YVO02vX0eFyW=<Fy@5@b)y9|z_nZe-hqRQYPi8W2#-hK8m$ljA^dK^G{4oE;(k9V)S!>F*R zZ>@^PyiHwTch1Anp@a?;fC`8W;tJP59ZIMWS^=>^9JD)ne$&qzATMph;iUurPG1E% z?=+h88bHo-z`i^B%HdttL3++&({ll~lXw=$OS4YB?Pb!K@~Rsg1`BZb2|9oP_0x5b zpP)im{q*MPzdIm59mV0N)7Q7$1i9}#HunW!-yJ=rch_x@o{QM@fOa2)m)@@g`KkTl z`prxl@BVaM18G=a}}E&(C%ZfpLT%!G;7`Uos1f5|2BgIVF?aDL5D}6e!2S_G6m{Fti(9BmL4Qp`t30fdP{d5=PC#VpnpIVqWF8_J@1>~ps zIQ-Q5XX;0gboO%HDLu4Aoo4O;l8fhXF={a_>Sg22ax+3uy2oUS+S|<=?r#*pJ;j-KzamVJHGdW{M7Mm zW0J=6QzyY;unmWopv3{yOOHTaf(l`Jsg;T2!t3K5ATRyE;ibRpZ-cyS@Egr}2SCo- zfPMG&>y5|TKwkQbO-}-LJx8B3g4|*7A59N~4`@dx4>W4efr8=OifzxCG&;}rfx~SF zj&OjM2hebM3|}9yT;W2$67%ix`Lw% z4jnK7+11XdumaQthYF$D)y|0R;*oc2TDm~SH@<*u%7XP>9YB6Rz>T$&@ciKCZjhd4 zYkc={W$>1G&eBcPxK z7|7@i;tEee0R)Xj+?pLAmL^akwv?w@_xI~k;> z8%+;`FKA;sm#Y*wpr(TY>hb<<;CQ%!BcPyTU?AVLGb*eC1r$^W%{T3!Z1KxqV#1m` zj0z`j?3&A@aroQqK9Do^;Rq>cQ3^79gSf&oP)I?A(9DK})R_s>XMjTL0*;V+bpFs( zkafM-d>w#2q#hkVIvu2^A5D(~D5OBwvV%ivEhwZeUOWkoh+8;93ObMn@=ZIV!fH@R zL50wK0}H8Tppe>d^hiIW#^;T%CxM)C07pncOK6bU8^jf!gF*@_gl0A*^F8g`HU|_^ z&v1m)(Fr?df~=c}&DRXrLu%EQm9s&5CS%hBy4@WdQinkyb$wF{I3n)g2r20BBgi-H zj0$T&Aq5pe^9?NX?E!_N~V{ z0VtsUfC38aftw%?T-!Sj96tAOcmP^>gKTMMR9Fl008|Lg7VPEu=8K2sf`W1?wxB$K z{oIKYKNrpi>6wmA&je0r{dWlzQ2%fKpU0?iv+Eq#4@Yo>6twUL`C)^&!b?y{L50x# z012s2C$=sDh18_Au)*i|ppZIrr3vhn2RK3sTBw3-X=hYe2MQ^u5SlI6L+bneEek=u zo{8q`22l1az#dY~uNN-{>6wkD=Kx5L11zLofI{l%x~FZ78Yiz@od?oz3`a;oOK*@L zHi#>{0)-S*2+a?Wkeaq+{R&V>t$>GA>)Jbv8cTlt0wpzrM>s+XTB?Fw9tV(~102va^$!$MM|N!4!>I9S z<}Yw`oWK!M(9#>^hYjKiuR$RN6+-g^B%}_1Zd?NjsePc3!qP@qv484Hkoy**xvv4_ zz6R`vnKb z3iK&!?=UL-TEFKCvqtxyr(m;B;Rq>c=?yY_gSf&QP)I?A(9DK})V$t}8$cm-1xHBz zxI1?($k$8JeC^;5n%%%2QuB`9UkB2&98FIHNY4d!Xh>}Yh18NMtHBZR3`a;oOK*^G z+8Gr#f4!1!IGN{|$gWLuc!gSkvMvm7< z`uBj`HVuc{j@*6+a=XE547U}5Hi`f9m-q~djh3&gwt`&s3P)@}s|b**+8Gr#gJJ_J zgyt$(Y`g*ac+09|OPMrwzTXHo`w|WxLklF3*&D4wsCvJDX-v#pV6&$XDW`3xvK7w2Y6~c7YOGb`) z|K}e8x#|!OSIwPw9pq|*_2{m0NC3I&pTER>kgIxEo!tv^)f*h~0Ih&Pu4-pg*aC_N zs1TZ~VCy|+fK1(XWY<2Bsn>A$6`FlPrfv{d_yqDRR0z#fNDTDPnRf!@*BdzedhO$D zP*@mjME7e#ILNQR{3X_bT-AEI@es&WH*mNLnkAvG`V4XvR0z{mFBv)ZPnvKB(Nn=tKQ*=0ceE-a#cH{!d6fWK!wm;1&e_r zAXC3gZafAu^%f4lLbEu?)D7YaUqF6^3Za<_iGlTAXk0B5d+YQ4CJbIMulyl7=Q|)xe68oA3&zg{gDrC#!jT19&fc&}vhhGnVJ^~60gPrJpJzxy=Yw!9y zj2fGE-oFTP)h8T&g;seWSG6-LYzO%jDum`Lm|xpKrmns6>=MY-2RQr+&DtZUx8)uvEDUy|`*nd9$giNiQy^Du_|<(CJAQ9-TuGy4#>K_=&n*wg1TxG$W<3kU%mxW_5_EkpxF@W zs-GZNL4`0~^^%d}{g2y^L9Y6Q!&QqXYy-L4U_ZL67D#|x_0M18JjhksPkjM9{tJ$D z0j+#Nu4-pg*a1oxP$4u|!P3PEkg4k$7TyE-^%)MoLNhnW)D7Yazd(M43Za<_Nf#5Q zHM{`%wPOSJQfPnQgeM^D4r2I~ALQ3x{u1{_cw28YpIb$}J>sxKf{Z9lyEDaci?aJUMZVW6)119BBq2-8(B z89A1{dioyZsx3HNb#>*6Hz4bdqPt4rmmOqqaN>qLj2aVn&Upz^_6dJ9ta4u`9t*%0cge;`*ug)m+9l9A)ak9Xfe zu6lsORm=CC`U0};lCAR0z#g zuo&0|GPUjKT(GGhaQGFP4MC=E5LfsQ@+(vb%~VM3w06;>-ypw!!{OH}bJqL-dEzv> zUlWc%{dyVXsx`0wf`jB24!=UHU68BV85MSe{0bF9a}~_5XF#StY2W${Wceo?euZXk zkf|HQ6&jfEU70qSk)x~s?SGJ8dp2S(g-&nz3knN^v*>+wbXPI-L0#3i@eZTL z;<>*YLCSvMa1}HgLS5Aiaurkv(^W4SISx)e*$r~lIUKHYu;F zYLKh;FP+i~QuYT&YK1nOK(1IQLz z7LZ?|LTIK!(#7XnPy0cBeS*WU3vRvyg@wUYbiXbL0r~Zpzr-%EtL{Dp2gx5Cu0qX2 ztsqxHg|ND6;mY-sL9Y6R!&L{~Y@7fJ+Uw}9V$g-U>H^4BAJ#nW1u6T7!&T4>0}ZP- zkgK3VSY0(^(bef7S54T2J!c=;^9|%`gPRzx;)c5F8OT+?H_n{|a#h0>?Az&~83yXA zc95%}LRejOb<5M)AXm-D;i?IzT4#W)yN&LugyWziSpNA-G;g}YsB!GT-YFnu|8S&M zXmbwas&+<&y`a-$KtMC^FfYUhr>~?zx2)l*>)G*Q3tARg={$%ZaB9X zqh`bsM3Hj2tJw?^zDge+Hx<%b_UmFV9>8(*F>uAN^324WJM? zx9>05l7@}gk5_{>WS}9k4-_I$A}rscu7I2b9(LRhvgE^~n_!nT;jjc+ zS3@n?53&R*glP#c$MwV8c7rVWxCy);;QN7n`$62Fa64av?0kK(c_+yCEja9i)|pT{ z4}k213SqNz#nguLAWNog2HW|!@y{g?civ{$G|+_2cNjIkFW7blWM>-=JE0{X)Xsw- zJE20@?0j}<&TWt-Yawvp2To4939_>Thn>*$3$^nQ$WEvbHamZO znf@4L$zF(^Z96wU2XT+W?c5HsvvdB_2OvAUaM%e=Ay7LHgY1L~VY72<@5J9AOI||k z?3n-MABg)AZs%i=om;0g{RG+BgTqc}f`Qt31Y{>v2-D7FjtLjvH9eofF7Oj>$!m}$ ze_rkR1G1zKhb7Py0=48Q$P%a!rX{=_&3_xFgDjc51)ON!EV?oa#9g`tmT0DLxx=Wj zaZmfy=QG$1Cg89WnnIv<9s}746~boc;q@n%fh^e#vGeiL7pp+r!*Dydg6!g|OK< zdELL(7c&sykHb!Af`Qt38e}I_2%DYvK0Tca zvSj);aM(8d{J0RroxcqhwiCDAVbnNs_VR3yopW&52~99iJI{dZgbHD^^V8ddCqb4x zgV=fZ%%pQ5?mM`h4?uQqTC(pL$j*5@mR#BawsXphT^B*zTW~wi?6||I zv31g?lOQ{n;II>#LZEhD0NDu@!nAWMBgf<$7hZsDY2Ar^$K1?`&!2*up1A6Nvc*h*S2D<^2kGW&+>bh$UuV%0d zwCuvZM*rY}$Nxdjeh#$=w(1b<;sQm`*0n=VHnf0@n1#cLFE1}QzM8>q@DiI528vi0 zE?)R`uNkE0HJTm=P(U3}09pHD)r1a^wHt6)d*t`2R*(^I(Tr#S84;j>)!MiFHnxNG zyhqb>0Ho)FJjmM3Yo_;rtUZRq+TLAvJ3&T#L^FaR+C`NiKpv~LyYFx82I={XrpE!K z=K^?J;;~~hCxEQIgTvbYvzGRPjQEOXL<7i(1UanMzSuOqAEf6ynw|q7Jr88Tw_v`X zzGe!@-cLB}eYtP?M35Oj(adm&0c{Y(zAy2}xAw^(J-^ZPG=TJckb$l^IkD>wqeB0> zhg%pm&NMakfiyJX=uJU8`k<`3L0q8&)SH3|p=DLb-nDgar_BKQsbe?xH2-&5%T$nc zf6?4`0OY;`?3*6nPWm?uq~|}H9)?&KRfY%BP(R%R`RVzx{kIr3KHj(w4!9N^eu8%B zp?>NF`3Wk7)lbu2u9ySz(>xr0>c7)46Xd?emuRbpLGHt`qjKTekF!8}n$h$e0O>g( z1@+Sxke?=hT+zj(vG?F?a3Hkd@Dp@60P3eMke{GJSpBqf*}MfHKW)L`r#q`&g8XdI zisn9sI2Tn01?<~3FC4ly59FtIG(8OF*QZB7G|#-w>q!2#ES!%xt` z1gM|7L4JY?Vftw;6G!X$j94N+BItzC_D{% z(frPk;G)U^+B1q}McMHO`d-;bup0i?$Pw$F4M$WNahH8nD6e0=h9AxJ|H4nIK) z8>pXpL4JY?VfE9b_p{f4{PYcnpS~U20rIoKL^Ss`fZVqM`<~3N{cBf&oH!Xx&jFAg z1=x+XXFz^By`X6^qsE@^CzpaW^x^Omw6KBtsSo5Qs1Q~^-M-hk0pzEiJ=n|CUDFSO z{A@54&3z1spvCCe_hinSyaIDqspz;CPt`RU4?>+_g2ran6g4ulCf`~)p* zpnmEH`3Wk7)lWjxL4`2=bb*oMeE*}}AU~bM;it3P*MalING}I><4-22@WsqyWh75WZhD1 z&Rc+eo9X0jFZY7NAq5peGaFJtynJ^3Fes#c;RvbI zD<&TR`FbTbU*p)2+w}70L6Dx+XnGt#2^xN1@dc2drq6lxhEZefjz)0M&cfj*Xek5r z(-e@OphB2_YGvX$a^T%@ke?>(#a@_xTkrtnXM?q9?mGZ--vaC#a=*Mje-z}W^=Nt+ zl0p3v?0UZMxdw8F!A5L)3}73RpMk<*&ef;y7&Q*BnhFlMIXJ=rS~5YyVJavbphB48 zFqe_z!vAxpLE*3rM>uTVbmAn)Pn)s1j{)YsKOpx_{5Jg}lg7s0|KLEFhr@l)QU>b2 zX(0DOg)rUM%EYl|?Wyx1_wB*qzV+K*f!uGf6`T7WVBZA&zw_`}ke{}r>2UzX6X;%P zEDf67O*=vEFxZKvrvapA0_-m8Nqg@wDy+RXZ7-w7zq_Zw0k;50I6#XgXgEvCdf}vAxuAAVB}al7`7LIa}Y|2l;5qKJbRzU;YxWKsK$J`Ky^p((vI;7M zY1Ldtjve0*JqFpe3S<|STsZg8lLw%XIF2nO7+|s6u-8_5gPeB&&Qg60IM zRdYdBL4`1_n!(6%@5bKOAiJ)C?80)P>+^dHUx2JT2@MMP?u3LId&o}OH6XWLJ<(FD z(X-;lQ;^zqIGhGe&`_t%133*Ugw<&?7Ce6sa@tcIPP^6p0pxUp)96lfD26)i5XfnF z_H9qmIK1Q{*c%&gI1QRGp-!6*avD?!(`mLG+m}B44072Q94>3yx8?&VAkJdAEYn`d zmgCXd2j4;3oA+a1nQ-yUsxKhz=h3w{B!INHUO)I7q7SdGuQ<-;LzSZdt<}v8SDmE(X}VIgS5}T zbfF!jeLoKEi$Bb30cpREu6=o-Fb|~t?1ex3K-wqb(tfyY56Je<7}|S4+9z*Wei)>E0S@hZ z4$VIR(*6})J3~20`|PjNj)SzX$DzID&#@yQ?cXu9hl8{)dDMFvq+jBiw4cSHed@(iAma^wqia_X2WdaIsO>UH`yCwGFa5l99_0AH=-MAV z0FLw6VzI`7|WVAkkry#5em*(Mxq z7HA6>+Gbe*YO_FvFxxDN9H)*Pc@1*fKO9c`e)T!X=>`+gotBVcucXBBkVl9kB(XT# z*3{HOqNRaTVJfo*C_4EK7%jjw=qSlwm(RQh+0lFW4x^M7W~?_p>jB9d9D52$vFscV zc?6g=xRj&d=3L&(BZKN!3ft zODW3FOwmg$C@9v;O-xU$%FIhG)<;$Wl1a%gDMnVSmk2dn&$+ayC^fI7IJhLSs03!E zL`#FB!b2W3KW$;+n6moe2auoUfc!L%5z|voKTQWIFqj4nI7^PJJOT|34Gk=}74$w$qFpr*5qJ3JSK>pkTw2lTLn|{}Gh9XQ8`_A#{)^9Y$I8kr}hCZ;F^7vv-sXD9?G7p11=5l%$lMDPeb5iDTl zc(m>550Gz8;PB1f=H1^wu3G?g9XyN~0_~;lU`aIR|4#i5(l7_A!GvQOGuS&Uw-_}z zX0)*i+~o(SnKn^{4dS>WULPr`DZ`T*1MUQ-ihcLj2es;4_TzJ)D0bt7eT>gun6iqggKBqyr&($!>F-f;@2;rAlQZ@!9jbi&;+*# zl;EI3mLz#;Ie6PkVM(A@=G_CUUxu!JffqE} zYytUV^0T|3bZ)Q%hcBQVT&OP=gM0xM!t_Nl$C>*l8$N(8lK^=Fd~4!GkR>gvxBUbK z)h-;CK)b3?OO}8vfeK+-vYClvW8cJ9kS)zeu(u3WpZo=Kg~1ARuO~QQ4XU|!AGCa! z!EUe$UA=-CG^n0~eDUwVt-l~&?7`s+Xypa<#Zr(jph6hFP%`Ft!XqRWTw0Wtn4IdI zpI1_ppA(juTJDiwTmsI;poGegTAC}#b3Ek{V)aQ)EY6TjBsiY&NHjDE zDm+D#5xT|1@u>ZC2Po7gfkMq2vxM3+YhN2E6xKjPL4)HND3^fi0T~Vj0VXL14Ms?z z@4<*%>`Uol8us?u6Od&Fi=l>baXg0@*1&` z6oFvOsPS;_#BPwYw?XHyxH(?&2sn5&FaoBp*Eq<%G2Jv%7y90Hi6 zOfl6S+q1U|q;?fltq{ix9)SZ54grj-7@;+`lp?0KO$U~OowO684O^VdTYnYgC4-IV zal)WvFJ*z|G0#c@pMiw_foCl#LAz;D}9V zX$g(Z<)GMv3Sq`(7{?19AseUs(!7*ng|z%41zoc0&gW=Rs}#oZl1In}nomI)G@8T| z6ARAGFVM}6<#@#--f51zq1LDvUY9q*1lcv1r2Pr#(!&T5~4eF|uAXhka* z`!hhUdW^$W7d~v53bJk+hN}dz)^vwfZwHxYumeLqC)5|mLB5!Ma3Y=V$4RRG!2-8)`96hUcF912J@hEux_b-2mryxrXELjM4%PAa|KuaK~C2K&IK!q?Z zSD2orAX_GYY(XzHuPta_2=dTDc%k_WRCc2jnsZnb=CDYeV+57AyFqo}?ujQr z?l#y9)x^i~1f;2{fhmBo;1!RR9LH-AzqNr$1C-5vfa)MdP~WQJIZqcOgOw1d@&U;R zfy!jC@m8iBFL?xRHXLl=l4W2x>8#4&Fw;eYF#x5OuXxVW$_VP^f$9~AHcN0`KCof# zq(+xbjPe}UTN@e{JmiIj4QQ7(R0Jbvu+=y16Apqswg){GIJ~x#GRCaJ8uz>ftKSa| z2zF3=#UX&nik;&rkAPzUqm&|Man$gBDLBYCLo9(b=f8j=zUlMBd7#KRgCpXhg$LAg zYe5kY6~geHk}<~{9wEt)qQt!7%#zIfJcXeA{1Ph#r11wu%<+dmtQ;$TJX--uEz3Zu zr4>uJ>-2~H%Rr9Y4{<(H$!Mj@@dDDJWVyvCDe#P6iUC_&&B}n|EssDGsCSQ)Jn-qj z7K(Fbp9DMO5X2c$+ZZ`6CxY%s2PHNzX4Lq*_wI6#Qx8DYSn+VY=MiXbVA5bLc*A4G z!|{$spu2%dg9XIn<#@v*aHyf7fk}fafYC}0G^}zPl%SETfr59?1ZX7!>dPH!IMBca zZXmOR+B%?wJ%d@`G)Py$dzeaYjz^%(&;Sy(5(FjJ6Chgx7$Hd+68+6b?=WiInSE|C z$hqfmL_f5YhDQH7Q1nBEFr$ACBgb1Fp+-gvQ!~Rfi$n$37!71t$TP2?v?Mq)FPm_G zUZ2uIp@s%U1@!R%B`=P5JVHj$)__J{Vs5G}EM;oyI~Y`?nH!`gCnm+0z|*g84!F+& zGVKktsR0=@Q}W<=&m&}jX_g__B*SDQ1G7X!VoVa6!^Cmy#jQ1<?hjYL_eDq@K1+b=LP)+cP`@uCkY{k%)8<#+iFgSss{tUD- znh8oTn;JfWllMg&=>=LJLetB7P%h&CCf08n|R*_&HouUwql`62--vyWp~&Du_sBU`TX^Y2xA7&n)nrQ=*YwVJEMZ z6?lLOT*I+!VpQOOwkM#mx&h>Fs1UNd!PZLIVJ6`ADIdTwbQl^#(i}^f1q5Xo7}^f0 zGBAi8j^g6Iu-Vr^)p)b71Gco;-MktU*al~z`r&E$z*0M*Q%oF}zFpe^ij04t$iR|9 zCQn`gk~cVyu0LU-9W(CAxp)sKFsB^DKFYM^<06o}!A11Ibf|&_<|~k^ zCcpa!4$Md71}4*GHW$@zoA!Yct$Xb?J5>;oTDdv_Dbem_7WfQKw0kfUEwq^e4a`lT zz=R4R`x}yI!GZY^}5SzJ_mPVEK-ru5A_cB&vE^~J?_ zq`=(8Ebx+3qLp3YC}vja9}DmGje?75z4f%NHQ}>qu0a|dK*G$H8V#`!|5ZSC_M*?(s<0muJ!3jaFm{b zMkzbTM;-xCZH+P%kLcT5p_YcCpwe*4T3-j%A8W9ehJWwO13AFpCe#9WY1p6%OEc3z zfj?)){=J~Ue?e}V`6j{OqFP|H9h7F;Rz9;+1re!>o8pkt%vNTB+nf?z>!j%-A~I@f;kOH=uzj2&$R{qPTb?mis!W?q25W0IeW4gWUFE+T4R6x4kAe zNFRuOaaL7J*#HXCkA7e6R6#^)NPY}bkgjDGIKnA0iCy6}W{^T_U1*SQ1qCTo2-){Y zLAnd%?6dD%4uhP15l4_h+aS;&W!GRgfQld+3JKC~7LG$-)}H|dX~%Kw6~y#68$dyB za1R=!@bvkB0c*?i@6B$Ic?J*B)gM@I3l041AYZKA^a~tS@5l}OX$zh@t3Kgd0Sf%k zG-i8M5Rt00F%l{87cmPg<&>DquF%Pc(F%fA#n8at1`2$r5OO#n1^zvdvoFnTISvZ^ z%QylbS{FkDp94?rtklQI@rg$WKKBjjS5=#vC7LB$no?o9JukJqxVR)SFGVjSKQ~p+ zBR@A4)`tTR=VHvWKW5`t`)Kk7P->V5N)4N^^xdytJ9Q4^|J(5BMVgW10Tqr74F?)H zRxvVIadB*77HDi>D)`J}#mjLMG;_jK0P0usTZwag<`FpA;GoJd!N?WXkA>*j2hsBd zq~{j09wCk|U_Hpn;raXFEMEsz*IB*}QU;h~EU%Y!oCmr2CN$1?LAHCq%)zGP-p>hO z9k<~+zVHYjbYK~h`MGc_SjS^XxL|Ad&U`Zg6t)IWAxf;^IZ43^mNUM9lGxOy`6ofn z{YY-kkm$eStUA$i9w=wn7xUVyf{4_bz%ZnoF_l^1KZnFBc7^qrIRo0Rg653vpqv2} z!jUt6gPc8c<-1cLXJ3Ve8B&i7+*yJ)te`oA6Hil1DWBsrk5DK|f80Dd(bU{1QNb^< zEHgb3G#urWSY!b1pBtH_85pM;Dd;LB=4IxBtV$`)YEsPUw4{Y6kX-iL&vR4ryT_w2_3a7uatO-YnZMO zsnOR#HLTIs0b2*~?BaQsKrVa^wI1H&0qYkA>9@=Gbx>WDhrP+OqPG|1eS=p}{qT&? zu-6(|MRXm%!>G~q>@&D3`9f|+nB~65Syjq)0w^OW1xVYgf{4`T-vf{`LIbnF5e|v{ z>3K}uQwn}78+ZK??25;dy z+dOd8gU0W2kg5OPF94_X@8rhsB>zRu zs{d9tg5r0UxTd`-h)A8a+6yUupEC*kW0$zWuCS3GqX__Qze3}8Cn$cQLKyL@RL${~ zM<@|_D3ewz-~<&uLPyPDtNN7kIlj@mK>SXx0#S?QCeJM%p`|Pw_ckrK14`sy!HHbp zZ~*dJ)miVGZiA9wGicW5H;a`7C|P5jo7dsEp^m0`+K*qiK$<~Q1`WSitT;iM9Xyx< zn4}h9Y2<9!Jr}I}4Kz=n^!4D)ns1;c%4bmCLNsf>ftx6}n>F7-dcGj*5#sm`)&pyf zfLcMkkaiSkexrej5i;s|36x`|P5A}R=1*|sLuf-Enh$yKH2tCZ@HEKO=|^6J%i~|< z=EIDa&CaSz%Hlxz@N$Ety()-EZQ1RJln;+E3Cv=b_|2{`RRAL&LOVUse7Fmg51~RB z`A}#fGe_(CLk~ddr2E7jMyYzt!f@KQ<#$1`{T>>9CZN~`B^&Uvz6DJ!Jsbjzh}n7# z=t@wW^Yw@dvI$f{3V|v(RdZhl)lM^C2T1I^0EOf8BU3=>*WekB*tv}|KEjKqZvc&* zhagkGt#7{x^37jzW9PekWxp6A#ZDWOz$;dX9UKZr1u~IKncBeDm@Kki5YMXdJ=Y5a1vd z1O>5~oUeoGELm6(x1PAesB!b;;`^Y$c!48`Q5%_jcp90|ApQpm;_>P z$s_V16J9u}CeQio9L2?(I3eC%6-1<3)hi(d@kvI3eJm1RITSt$VFWR>c?}KXJ)j_l z3Sk7X79@yoF>%~#eD?wr#M?nZ46dtZgMz5};A4=m!7Cg=gxdT79cBgpVEVo5>C0r2dUgs>=lrfP&a~ak{-K zh)Cr)FM$-qix~wjF-y$mRJbdQ5ya4j8#IXbf`S+-gzRr{cOSGO=<|-J??Hik1r)fD z$(vOmf9#(y;~gjgwv+3R-$%NgR8yYK0{MeaHrHMiM5Lam5Jd7vJEK4Yv&0Tgh4msB z{(v@6p#Ing@&{B1(;tf%IW9cD`5EMo#~^=z6Uj!9H!dFj@d@OOH#iarYJ(26TLUVB zYzfXpGH+V{N08~C&=W}msQlyuB@*4UJ`SoA&-ge9wJ>o!+Pvup$dNxmj>J+}yxP9` z3&_0BSk2=Fna8xw$3ZoHt&fA$7Vu2l0gx}dH!cC|>mn(#ED$qoCMCX3s(r#Kpvc;@ zvdCT)M5Hpj?3$q`5E599NF~`V9(( zj+1wwmHs)9FHWyM{1fE+cR1n-wbB>FSLp|X+C?Woz4dvMmVgZf9l;1{5m|!kNbnpX zc>WYrT{iq?fjS*De+m`Ba5=V8VqMD@P=p$ML65K+Py*lqMcDN|9|u+IJ|72cOEcz9 z`TGmx_OJTi@Novaz&ud2V_C=F z^Zw6YP+wxwWy6DA$*M?NYuUonY#1;7I3ni zKyK9DtCn?A4f&`Kidt>fGJ91JkviF%87XT01s*XYE8? z!I@?fxnVPHGq;m!>t{Jo*esPPw^s!bsoC+2NMYk4aE4LhJEy`U%&>u0r_iuD1PU9d z5VEturJYhf#}B%8XoWViaNO>^+yRP=Q=rILiKW|jxn(~{fx#bWqQ~Ct`vL00eM9cT ziF5n_cj0h%`+kD-d`H$J#PJiX2f5oPhTQD~j|PGWXjQNd6va=DeQEkQgWcc@j)Vhk z$wB*nB6x2aAjN^EO53UsI7;xgw%?gV=l7sY%sOh zCdYnHx&!vfA86v|1r>jY$uTSwaFb6g1qbjyxQ?I5!3DYi3Vrv`tJ`P5A^aa2T)d#5 zXlQ_qw4oHute~=#$%+-^4kjx_(0a<*6Am?SATOsYcnDoh3koG=j!!%SU?mzTtHY5M zNr7|wEl|GP^8ZU0DC&MdosT}CkJ?bcJUKGRE3;9i>a9F*k^8aF}ip+S!_ z)YhjszSid|EG>oEQ}=+4{|il>x}Y2dDhpU{F)DIEMi`l-h}qE#ANK;cra3{a=?5x4 z4yvvyJ`Pp}9KRv?pM`>D?(5b}ng~i~jjzGm{h<~AWl$*nZ=c=?lA1?u65P_V)ls$Q z##vAjj8CYtR|OHN!P4K5lAyjo8>7SoE`^ntNf6p}h9<#dpd<(t!bpNbH<&mU{%n~6 zit8!xxc(3F#o?dxrhZD(j@EP-!K5X;!ZrpY%z3Cy4Y5`2j6PXVa=!v>15 zHCwzLR0X$qJ4kH?k3BpBx$4h{N#HVXAxU9tju?ALn7G_gb=u^;ps+o1w#r@=M5OAg ze}RWB7l*b$wZs%Ig)Nvt3vGErgZ4NmXrV$_gLdk>)pI~Wy9yMv*h1m)t(l-OZblD< z98f4QgF<0RvbTdOXR&X3&NsYEav&lK9%Ikf8km3feC>JHVzc zAvb8BF?2Yp%8ASb1?|R%)%L0&BJ~5)2c)1?7D$np!=-QuU zR|OHNTNb@T3TXv_7>RjY3YRcL8ru4VhV)5LNJE7%LwW`?$L||=mV<)kDJW>LB;xxM z=Pm&y;#TyaP5}k=ze7=6yg#0KIjF`x^TN9B;me|%AoC5{uSI+{FsjJA1ni8`-N7cNWji9Jm)>LD! z3L;X=o!%lvjjTYZ#6m8GTbNM;Z7M*c<`gJuphB2YbAyTFdvn(sP}Ka!5j73nO(1!L zcJ!c407cC&P|$vu>gAvsHWe1M_d$WsbL=)aMtX1rEowC{gRh#01no(XsoNjS2S?ai za)Xv-R=A^T+{Fw~&?Y>su~!8VsV3WABL%ILfRDryE``UKK?|({p+S2Z6tqww%%DBY z$g%79vGt&!op|~VBeVj#0rExXiuK^szJXj{?2EE;R9!Va2;_@3F17ZmAR;w1?iG?R zBm`U}mUAh*!t@2Swt@QM49FKyAxvM~VB*-c`{QPiFBX7&0WQ|Qf;=(f*P#udRNjXp zf>G;sSrY5^XCE5DrgxyHx)@NZ`vyvNzFA%ls+%*tV0HUDkgFCi-oFaus?FpE?VmPX zN7cO>oIyd$Tu^JT3L;V`WWPiTS}_58iIrRm?=gcGTH8Q__ADr9p+Z=L_U)$^+d)CQ z4IZ@Jr|&Ro9NIZ?3&;}_a0D%C-7JT%ZpKp9u37yL6rTp2ScCQpC}^L#csZ#0y1;|B z1>~wzn;wFrWIMS*%OxP~sOrAX02H+GJ8SJ#K}2eC)C;7b6%nwMSk0yI6*Fj|)eAIe z&w+v#Dugv?PyYR~8x*u>KtYQo6!x^W?f`{x7kVf}fD+g{P$(>Z>*=7%`PLI!V{8Bg z!okP)wt}=x!Vxv7mAO3O%6t~c)R#YRgQH|8xj~!qhu=|^=axJuXeFN1+N*+y)OG34 zk%Cr8z(is#m%?w%poLZs(4ajJ3R_`YLUdqJiiA~$HSHQjYk-S_k*C}>Ylud`PL5vfn!KS2swUI8_Uja&*Hm_ZAz zbD%+c2^6$YAV|oBcLp?nML%C%)t>fsMU}P;cBQ6WNP#EOW-VY zg502CID5cBwc*HqP|z?OsIylE5vdNd9wVh%E&+LoEnEr{FoOnK=Rkwz3MgoxLYP5w zgNftJg_D;-L305VG+4^oNu9euL1)m9HPt-=rMgrdPY2br+OSl&1r!MXzPvjN3fg%% zf)=$JQzcxDO$V8}VDi+HAX8718?>8bRye4pWv&7RZQ}1bdsPsTTKV%4QqXb=NK0(x zQkaYxw9q;W8njnIK?@ba8nkoU*Ifq%?K2!f`~SpCP|zDpKo43EP=>w-3fe2rJsec6 zpL;+XPp3eEu)gv0MNl9tz!9{l)tDOLYHTgY)Uywlfg|i3xj~zAY@&neMXr8O&`!~> zw^s!bsaN8WgO)=;LSh@2!gS1_h1OZnpuGkPTBs0a(9U4uxZS2ib zPjxP!RCfoI>hcbGIH+Db2upR}K!NbE<>gIKAS}TVw5ZjH2H|StCdkz8zZ0*3Oua&G z(8dV0I;fryt_20HfknN&Du_ts>q8D&HUVLY9b5{tF@qLbXF!AY1}JEuLRf?L*oMQ8 zK|#A7N6>!S@EH{J29vM`?F~@SO0;-5s5Ugif_Cy*&=I9mpWg)q!ZI8|i&_n760U~c zgM#+PxmTdl$KX1-LA%A#*+G@zUnD4KJJRdzRY62*s5WZQ3QFwcQkaVww9q;S8nib- zK?@ba8njo>?0OCg+M_svwxxY8NZw#FdeAz6QtdTR(7sFYa8M0Pfd%bKP|%(`_w^wt zXjk9}TGVPti*Pm612VPY&dfU?Q*V(QwCCm;JE%t4S%HG~P+`5jDu_sR-SYscKwuT% zm)OOnumCe?p>+;4Xm5dn7Ak}_XeTVc^cobjw{Zk*bK4A%yulQ#L3;%hw1-?h98|Sj zVL`he6bNT8e18fGgjG0#7PT7ECR`0I0-4%2<2g7B-6c0@d9La@sO~Zm2L+;4Xm5jp7Ak}_Xt(@4`yLduA8`cj+{aTu@&;4UgVqL= zYA=C;_OcGBhSz}w?Nv}9Ol$l45)`y+a0D%CHKs$j8ruOfb;E+cpwi3W0l7iDIYiSz zb*eBQC};)e*W0Uth}3J}?jxmIW&tjV-CPPwFoPCaXF-GZ4k&1$LYP5&j)`OcqHUi+ zLEC=r4x<#7lMrSe?+3{nOoIk3e5dpiVGjpYUtw5)z6N>y`o&LgK>@lBM}VSMUb=)U zuX7+%KVJF`E|VUU8=xXazwA{zWEemJ`e}K+y()-Ejo*zLpqvtWxD=LR1}L(FavZ36UV+2o4Pt5oY3YyKJpuy5)`oDO|50G!BL(>|3O#?U^UjSv}Z@=6fRO5cRJ4j(UW99wJ zPLTNqGobp}IsOoN#)<`~oHz%v!0Cm%gX*g1?$DmVbWq%NJo@$(Jz2qmQ%Tbh`fq)~cMAHLN&plch@SLZmfeB=4fBXp06W7&*#|3T^J znvYRg}@f#eP5K;s{te!$Ut1{A%)=iMDtcb|hr?^;mw-dy?rCnzGe;D}z-8rFbt z4Lbp3>i4_QXfd6MlYMhUM_`|n9&QZPNC8J z02IAYA<NV)@SVjR&+Q;P>4A=1()&1)hNd7+iK826<)D?LU7(0kjQA0HM~ChJ@?M zWgt^Krtbmg_V?rlP}7VP_Np#fg`fcX0}3Dzk=pY79#TEYD8LE|pjDUw1g#XI0rU_Q zKu{r!022Dc%CYa|%63rDG@pm{gl~d8apdZcmQOR-4R+uN8q^xk2w#ndr9f=i@&gnf z26M3{=u@Bst+~P7L3QGKcLymf_wt`0<4UNe-d>gOdlo3J-aW3jR|OHNnp;rgidkYGm%?hy zxPsQV(71X8iYurPMqDYCaQx*FilT6TJ^nIEP~i{S3PPoPj(>FBhoF?j@t`33H>*y(PChKuK*rG^wF1tmTAXX@fY-=O1V*!w=-G4B{OBz*`w` zZ-4j?((@Bpj}XUyupZ>?4_2^L&H_o~pgUL=G!wU38(w9YfLa9L9JXn(yMwCKVs{5C zUP$(UZKua_KESy}H^HIM4_d+Z3_F zkm1ord({W4EI^st@m0OODu_sRtVU#R4gn@m)?R~|wV~}rXx4rV%GyvNjI1s6n2BTO zk%Rr9bhsXr4zXO@apC$Qki5YHXfi~N&l8{$^H7(&gQ`}SyMq-sD0Da=i3GAtd-C}^ zj2bUid<3O+gFQIn2er9pg0Hy;3FQ}{P+t9X6S$`ML3SuhO|(~KNV#B-yc|7MFa;x& z*J6e;v}Fek ziKDT3)pSt6+y(`VBW5Ysw`d+n-e4g#`N4b3Z7uE&s_&bzCc6EgP&oMH$3#%r9>5W{ zsBJ7Wd~Ga9*e(K@dUf^X9+0X3$PQZ$L3>rBf-X?l&i!9+uL>ek&l;hI?S3wWb(o0` z+MI%h?K4o=LWMBHb_x^6<=>xYfx`A9C~U!r?kdO&GcJCg3i84s907yc!ZOF#!dee5 z^LBtt{XA{r1dyqX*GM_3!5FE`W8t<}Wsq163K-Bm0N^X%Qt!$l0)|Zjd$2%TF3@0k z4hj~i5N5DkXX4n`_G%s|SlTb#VU)s>LEzTo&%ZsXhYK!kf|+Xhin9gy(&Y1BWlPl5JL;u^_U3`T5&)_ z_5~d3)}{6DFR(Vfn1IkOl5}DL#r6YkWMR70N3jvf)Vx3HcQa{ zEXd9yF4D|_RCJ9O?l5ZX*#B!T$hF6CBmmTk&Jtfm2T1@=KndX0&OhKXupJs~#2tcy zoV6lY?Nu30Xrd;7YCggV09vO(6TnMQ0)Pr(CV;&x9J7wiTnOov{wS{n3gObiE&h$-$8hm!H85Wui1+usDC#l?BT{Zd(Dd7@PCA&fX1Hav$n^ zaIrfbls5kMtX%}sd=Y=F+g^QN4|2sh9BB=;2VsM!2cfi!k)wfEXg(v#3BRedFk;*sb#5&IpD*I(dSWcB@ibTP#q_LLvn$iM~BDY343aU~Plg zFNNjIiN}|I?g1&84szCS&?y!FkPlJ7qT~AQ$skV~%!KRskE{cn)Bb^S+NGDf!BKS) zM@~cSQQG3^QR0k)GLBW_(7upDzW<@67*Yqvv!0O`mnY*)W7yE}CsC^W7@ zoQ%`nDvsHw_8kS;dj(`KxO)5s8rSRne&`U$fZ5Q%L8>0X$CbB1#Gyqvs04?K5MP3; zava;c;3UZII~VUT;&kFiCXVl&i_e18y#~3l4aR%KuicLp^E5&0NjIt2&Tj*E8~ zHGaPPejH@|Tu7i;p$8VUbZU7a*hPSJ>P$T?0pZiR}u5{ zpuV4Pzkxh-8&cd!<%2E_m;#ETCo_+NWo|%ZtfWAd^+eEV@2U(8384LG@L^6Xq^nNB zW!zopfy4*6BsPdEfXXH47AU9)hM~9u zMW~07RY+dxO;qwf3fhm{4%Lon!NGt?}#ks>j|ASny4B}@3qI*@_p`TSjVh1O~KdfF=yky+lwE;uA&{M`!Qz z2_U;K!0kQ+vU}O1S-l{;SK+W5ngpSCgRbR)iV$!2PezX8(~oQcS^f-U`4sSN#v4I- zuz6|YW{~aoh{%J2pj(PThi`F#=e`>%?X3hLmmRk>FoDW+(DlaX8TB4YMg`TcP!SC0 zLo(_qP?$Vfx@!~2RcmmB33x#X{D@c3g)C4J3`2!JGI3nLc4P<0?|(pk$5KLVef420 z$hOCjFo291fJ0~UzR6&j2M`&I`!G<4tL^X&S3|=594O2`?`YZvvhNAhRHSn6INveQ zl2JsthZg1u{kBAeIrP?NXp#ZdZBP-6;DLnsB~X~(*tGv$Za&@)^7;#?*YO{2Wsh%+7hJ601R45j@mH|R zH{ftNG_OKk4!Yt4DuU_q&x{-kUmiLDa`_6l%UiG9Vbth6esVX+|n zdj#a`?Qmbu2Kl=2>eu}s_kDu84?Vt62QnP-3}is-eUPDxcfQ^WGV}w~P$W+~?7M+7 z&W=&(_uzIc0@-o&@}Y~MVBLnp4rmbxwF7j)08|8<9ZlO7KLFYB7H-D{ zkR3m}=UfBXu>*%4&>{h92k3-;s0cPY&h47@1Z2loxE(h@c3izO^(M%UT{!H3W?rZr zpab5aBG~L${bK41kR5;Fc032!apB^tdmuaZ;IIQ4!%#aw2a`iZFzxup#PQbeudV*m6Jh68T^Cf4M<(I6XfJ4tqZ`0 z{D#P2)J3Qx9Zq;gIv`nb70A%{Oa6m`$KV^(P^7Gw@OwG_tau-#W(PHjpduJy1gT}- zgDmXa(f<$>6#H-lJT%!u10Hm?HdF+|P@y%f98#+x7Wo`Oxrkv5>k6*OugaDF$~?4?g2!yDqDv>=I@4zLY_Zuqn4BPh1pA-=|@ zr1#1$u#!&PO4h8s4p!2QTglEBufa-sp-Rw;+^4&LfaUve$p2h2`xD5a6QJ_ohUb4! zf<1Mp6I{+5!jWL1bssdrg3dIDieMyICytjquXtYbyx2W zy>RRL&mf0Pgg68vo!o{N$dC((UGO!Y(ZgWcyD6ZQX)qaLG)B3w``9e7{1m7>Y}?*( zkh`w*9|ubwhNdvAsUV@q9@~k`bEX{m3i8ubh{0C89M3=nB(%7~aL49{&%ny3L6l=N z@z2$lU?npkO00M}o+6v*hnYi`Z{PbJP<>4E zI_JCxn|Bmy9+vWMDk$7u+=5gVM{pz`XtM^Id_YIBLq#x>j~3f2o|inYd4xVOamKHqRtJ5mmse} zVbHZ{6WB3FppL3;Ie70-#}gi4YfcnxcSQ?09pP39{Ys0h@cJxod69L!Em4!#DOyy zIUek7YymlN4YC90fgJemz-Exs4Nl^7-~xB31Al=Wxc%iDaHO5W5rWWa8R|d@4R!;l z2!;cN-Z66QoHw-t7dg`4hKkdf=q2$^c@_DXK;8JTC9Ui z-5{<2It&;pf@bIjaUoD#Z28^|^6*7u4<7;9b9nAnP*510#^>P$TF|&y2GVl9VP4~x z8SDn98>PMd@5w0@A&-pyGEaDYGkL)139hZz#ovK4KCty z+JaS3r?rE$>{@>V9MHFLI1QTUp-uyx`v?`m>a;C;o-GGCZ5OiBwt(z;K7aBOP!L?g z=d^@=sMDr_w9J0}4II#Sa5xQ`=%G#notp?1!RoZri@vS~Iqf8})AoSu>1qEBaW-w;ANL=g3Zb2(o8=PwNJd z)2`xkn!*cPNDe&=(z5&iofRN0k8n5*n&_cU1D$&Z6~XGX16ECWz4|#WwA}==4IcuKAE<=~ldu<}IurQUtXDv@ZgT88x0S z`@0il&vB?d!k}@ECipl9NEN>oFUWFuXTgdSWGZ;z&x)G^)O=>tV66DVW5o)()de)~ z{s$xonRkc8>Mf9$<{iHSP6*F%1RXR}LW2%;E*w+@Gw4n-a(v!!VLvG7W`lwb%W9@u zvpV;HJa!A}F~r=h6qe4;$DR#f)wiLlv2=D`g8Xp*)>&|hd4a;cyx> z_d=ZpIsgqSg5fkRjwW7^<6iIxC2`z(cJnyMZ97431KZYj1GLxk*FtcBy}@A{G($pd z1Dy#56~VAgD2e0Kqm!pWb{vG;u>@qtv9G_t-hPL}4rtDT+5tKo3o3%mjycy)p9k4- z5^l$4kR4m!tUU?}+7CGFfF?nx9iUU9pd#4pSa$XNWsn^g;dbl>*|F&Q>JuP4KH;zf znna*>fR0OoieR(j_Wq;SL3Z4P+i@6V$G+QZ&w%Xsg2N7ITtn>uogf4i!Dh$ZhQqf( zc07dJaT;XDoi}SPfb95&!wzT+L+t<^#RC<=X2;&9z4t+OyoB3v8Dz(dyC1HA?D&Dh z4rmNR?EoDa0~Nt$$F27V9)s-o2)E-l$c{fJPTc_6@e79?&=`i=0Xnw?DuT_9M>CH- z2ifrxZpUMg9Ubq#-2vJ02ZtTd7>3#bI{E`Dg3XSX4R>FI>}b3Ro`yagAn_Vx$G($` zAAs!mhrtiKeShKz1}VHZ)-C#Y1BlY6ob~ zJyZmn9YQOOiVq+=+HlwbjbW%A zpsmPI5o~rWY(DoNWXDRl9g{(J9Jw*+E69!x9Ckos7-|P-S1(iqn;i?b&TsxYgI!=F z+>Y5GJMKO{_5)-`7Y;k1F$}c>w7(T9g3XRU>vpt*?AQsnV=>5%MK7=X0ol=m!wzT+ zL+t=Prn@XRsUe;jjZ5!%#awTk)VG*zCCUb#gz* zj+1aZHiPWwnlh^eWXA*?c0gkoY6oaP4pao29Shd&o(!_%BHWJMAUi&M?Ck*AF$sqq z&=`i=0osQF6~Si5nFo`mgY38ox8pF#j+U0KJs>-#;IIQ4!%#awE6|}L*z9<}W6o@l z9S`AloCeu(;p=Ts5E@LwVFxsZp>}}QAwfm3*>Q8ln)x6*Uc&9T46&F{IUqac;IIQ4!%#awOIDyF*zCC7vwStkj>cQ?`U7Og`_(rWfb5uu z!wzT+L+t>~#Y07~+423~?{y$MI^lMF2HCOY>C`14I~L%u0~*6nJ3zC7P!Vi)wC-EA z8Dz&qxE;Sic1&4+ZUxAWML6t$#xT?l(2NpP1e+b(AI{hgvSTLPj^oih^_QA9fb3X?!wzT+L+xOa|F;Zu#*oAUjszumc*yP&?f5EGQRB;<$0@%^{E-8{u}$2HEj(&VwBw zJ67Sa0~*6nJ3KVl4WJ^};^X?l-s2!UcEasg46@^2*QGrmJJ#T^0~*6nJ3KYm4WJ^} z?3l3p%_)!_2jO}b3VuRlO`EW3340mzPh zIP5@;^C0|j{^IeMcOW}D;dXom*|GTVnkOJT4&bl@HO_OKh9Tf+4T!# z$3nOr-5@(Y9RK+qWXCZacA&<282&hKSo`T8$c~k8J0^qd*xI`uGB618&D`kR3Mp(Refw1x$c}?>J6412*zn=Se~=yLaM*zw=TZ3MeA||LeIPqd!tK}$vg6Q$ zAI;xpup3;!VFzlQN8^w4_3i5?f$X>lw_`WRj-B5>wu9`rgu@QhIFG>}=g)fYPXpO; z6K=<0kR6?0Cij5sxPrqD)HsjDALl#!Z_fhR@eppuX^h?6`)*4%9f0!yo6( zt1rw0+3^x?$7PTmM?O5C46@?}4m(ieJRX0XKYM;=5y*~@a64{;?3j0P^K_6Mw{X~j z8s`c4<9yokeak?0{Dj-_7-YxH#SdnK?6`x&4%9eL#2@D;UoTh%vZL`1y#4^$akF>9 ze2^XYaM*zw=SleEyk*_dbs#%B;dXom+0oVgU@^#!2RQ6Njq_yuaeist)J-5eCc^Fb z4YFg+$+gQtc09si2Wp(B;E(g`O*^)M?3f9+qxsGqMvY~cAFl@4@dSq*sBxZ(Kh6&x z+_wv4$3nOr-5@)jO?|K)WXCfccA&<28vZz+yKn72kR2=Gc1#A@vH8@2%^*8o;IIQV z&eQS7`H?wW4uS002)AQ4$d2P1Hf;yl@d}3>sBxZwKhF1l+)Hu(^ALkEyzh45`aS?9EZjc?{|L#5xvf~pDJ5b|12Y;OZys`2c z$c~$EI}U^FI6ZyB8IT=caM*zw=ehXfeA$$Lw?KA0gxhf%WXGn(ofklMe8XV}YMkfc zkMr|0SKI^H@e*#wWsn_vW>32Uvf~F1J5b|1AAg)rT(Ia7$c~S2J8pyQxc2e*b&wsu zaM*zw=LPuVy!-Oq1$N9qk$*(|mG~R{RA0Rs> zf8KQ;WC!S|I&5FaY|gY0N& z!ak3K8t0|>g;~D<^tQDFWXB{N zcA&<24gNUqo6|ZCWXDao9fv`7-0MEt1+rrb4m(ieycU0)pW1S8CdiJ5a63+e?6|$D ze*(ykX*ldajq^JEasF}H=eZy|Uc&9T46%&TrC5`v)FiL@a@)~5x?dQ$&L6*$JVF_xi zH-RjH3SqOP?c?gTAWJ&omV5?T@~3tBVvr>Za9DyG?ad%dphDOzdA9ZbMvx^F;g)hvgtk7U8f2HQrl5mOzEDS#o^NiLD?@X2LCLzITUF@#pwXkR=P@mUM$G`8DarI*=vHa99G3WvC@>AWNV^*eqG{_~c%YB`e{U zOa@spwR7iYkR>Z{SOSe@s3q+nOQ1s7Ecv(i_(6~*8{wAB23d0A#glCyOIG2q1RBdw zOFBT7K!vbbvUSGHqaaIm!Yx@0vgGF5ySqS^tifRkG?t;3bb>5_3SqP4L;s|cAWIIy zEm;k+UvVYB3C%dxW{OHRTq*$lE|($2SsK$dL4VF@&r zp_X)mEP)DPv*hU2Nf$wuT!dS)8)V6=hbxbPEZKy^5@;+#E$IPS0u{n$$(L0Vu7WJN z3Af}h$dVZwr=0>>vIU1F&{&3A(hIT#Dum6FMLTpav5Yvf7|IxAWL@Ouml>*P)qtjmOzEDSu%aj@`oTx zKEf@z4YFk7)bH0pmh8e|2{e|WmP`Oy0u{n$N$-r#ryxsy!Yz3WvSikS-=I)5*n`6o zXe>i5nFz83Dum6FNvHn409n#_A6}n;ELrmU;60E}_TjJu8p}{iCV?!03SqP4)A1Q^ zL6&sFE%^+x9c16gtmhb7QhhFUTWWC>IVn^U@l63CJpI4pt2GSrg!AWNV^*evO5YFP-fV;O46Vvr?JA#9f1d;4Mo$dZY0OMZhaIn&s^3}ne89F{<18EVNA zkR?zdY?gfOf3^i=$xOH<%@07QTkU_k5@g8}9F{<18EVN=kR?zdY?jQLb$tiOl7(X86~bo8owmJ4K$h%; zTe28r$*fH)c7ZH;gToSNEJH0>39IVn!d} zMYtuqL6$U3Za4i`|g4)`G><2Xe>i5*#xo#Dum6F6Gu+J0a?-sx8yU(lFt9rAAu}s zXogHRpbrm1V;O46W{@RNA#9dh{jm1~$dZY0OMZha`MUn{Q;;Q1I4pt2GSreSAWNV^ z*escOaqSn7B{SicG(WtDK9kR=^BEP=){)ROHW zOQ1s7EIB-LXTwiW{{(KyY>*{uX21OkvZM=#CD2%gTCxLV2~-H1CC?gHwSX+y3Abc1 z$dbqVHva@!(u2biXe>i5*$J`)Dum6Frp2GzL6#hZTe2Et$)Zme|AH*(!(j*P)qiJEP)DPv*h%h6;nW#+=N?l7-Y%h7ppo!mQ2B62{e|W zmh1&t0u{n$$+;qW>6~bo8ibwP3fGl|l zx8yR&l7`pwCW0)Pfx{ALEJH2X53&R*gw2v;o98Y7S@IEX$!(A&TX$cX3bJGt4ojf1 z47KC{$P%a!HcK9_nz#gH$xpZ?k3p8K>^w0OWXT*HmOx_}YRN&6B~T%3mfY{@TLH49 z@e#Z}0a>!^&yl$xOXlIQ1RBdwOAdi7feK->9=t4zgq++>&mPB?sosUkkEi z84gRJu?)537|0T+5H?GGwEo!*vScOPlF1-T&L7yf5oE~<9F{<18EVOKkR?zdY?dro z_hmoGl8tanW`iubabm_+kR_{dSOSe@s3j*rmOzEDS+aR&;}MW0JK>fr23c}<z+nkAmZ6rM23Z0X!e+_(h9BoamRy8evKwT{=l=6Y zL6&U7VF@&rp_ZHhSppTpX32_sUoV3!xe2%AFvya7-5MFAWL>X+ER9XKq3#xm5B^B_y0Lf9-h*zxl|$dZq6OKyWKnZ9)URgfjSa99G3WvC?=K$bv- zuvs$q`KHGpOMb#Fc?`1T$L5naK$h&mVF@&rp_W_(SppTpX375_8=iwKX?zT?Pe7JD zd-3!R$dY|HEP=){)RIdeOQ1s7EIIk|$ZL=#op4J&gDjc9q30pUk^?v_fyOe_lFJ}V zphDOzxq9{ddypj);gGva_L6*#fThjda z4x`4k2lHQmEIES15@;+#Ex8J^1S*8hl8%FyzJn}T2)CpgWXYPIZ*M@B9K&G=G?t;3 zTmxAG6~bo8%*m5~gDhDIw`4NNlEd4cegIi=0*58gScY119b^eq2%9BKuATc2vScIN zlGz|jdJld6034ojf147KD2$P%a!HcPHe+SB}N2D`vcxFw50mb_Zr^b=&s861{C zV;O46O^_u}A#9d>I(MoaWXVCeC96S}y#Mz656F^pI4pt2GSre=AWNV^*eqH1@IW`n zl9O;tHiIl_oBN~T7ij(jhb7QhhFWqPWC>IVni5xeKxcDum6F?ngVP zgDiOnx8yX)lADb;dq9?4!(jpav5aF-^uePf-Jd# z!xCsLLoK-vvIHuG&5}i(E9ZkO`3Se3CS#k@9CD2%gTJiv72~-H1CCBG3 zSq!q|C)|?9AWL=}SUv+}$sHV)Kw}wd$wQDOP$6uVw7*%p9Aru36L@_BvgG}`Cv!lS z+{0lBG?t;3JOWt)6~bmo=lX@KL6&sFE%^+xWdD~d3qY1Uz+nkAmZ6qB23Z0X!e+^w z&y&`JESU(mIVnea3E(B`e{UOa@tU?aB5HAWL50uml>*P)nYJEP)DPv*g|X*ZV-0Y=m1f8)V7k z1IM?3EO~{)5@;+#EqMX51S*8hl7&-m9RgXh6K=_3kR?C;YNw4u>VsScY2i3SoCD2%gTJi>D z2~-H1C2hwVE`uz&3Af}h$dadve}Y2M;0q2*ps@_KnSmRtr|a_!Em^B_xp;IIT5%TP<+ zgDimxVYB4x`RDgQmVAU;avNmHgiU`zp=j_6hb7QhhFbCgWC>IVnWZjK7pFoz(gj>@5^bVuOu~(B{fGlaj zVF@&rp_Y6FSppTpW=YSyd*48oEQDLq4YFkGfzPi&mbBrp1RBdwOTK|DfeK->WXH36 zzd)9(gj+HhWJ$}a&+kE&bl|WA8p}{izJn}*3SqP4%lfPTK$dKTTQVDD$@h;7zJM(0 z!eI$CmZ6sX09gVR!e+_Mj@wPYLH!fBC5u6pOug~>JIInA9F{<18EVN-kR?zdY?kai zbgB(x$w9a!t3j5$`rq^iWJw2U90yKJUIntG?HR1R`~kA$ z&a_QSK$gtFVF@(Sp_cpySppTpX355_E7pN5>4RI+@aztw#gToSNq(d!f1X%(V!e+_!dmFZaESU$l zWCF;N4PQ2_2U#)?hb7QRhg#AEvIHuG&60~}{_O-=vJ7s?43H)3ukYCmvSa}cOQ5j~ zwWJwj2~-H1C7=5L?*&=14sOW;kR=cQpWF_zWDyQaps@_Kqy=ONR0x|TmoBb81hQls z+>#X_OOBo1w;N>15*(I5V;O2mE65V45H?G8Eq-$pWXV3bB^yAN{Q9$LKgg10I4pt2 zGSrebkR?zdY?gd^(sBx9$uYPkJ3y8^*njvi$dVN}EP=){)RK0PB~T%3mTd3pKL@hp z9NdxvAWNG2=N$)GvI>VK&{&3A(gCsrDum6F**8C21X*$oZpjIdC9|)uJ`J*D4Gv47 zu?)4O6J!Zg2%9Ahd*59HS#l3<$pw%l^FM4q53*z(4ojf147H>SWC>IVn#d{Oa3nTa|>k2795s9V;O2mFUS(85H?E|w*7w! zvg9A!k`EwD9{s&=4`j(U9F{<18EQ!%$P%a!HcK8Z{rVDQN!xRHeFCy%#i!QCAWL@O zuml>*P)qtjmOzEDS<*b=_*;-AeQ-+}p5I~An6m5JGms^_a99G3WvC?+K$bv-uvyY} z<^M;JCDY)Rbbu_mb$an@kR^L?SOSe@s3j9YmOzEDS#s~ev#%ga=D{tQ0J3D?$w%)% zmh8h}2{e|WmP`U!0u{n$$*wzpe}XJo2DfAe$da#@Hh%_LasY=V&{&3AG8tqER0x|T zj}Er~16i^TZpi|WC6g9T_ztq<5DrVAu?)3j3djrtQDfhyJ7Cl9LrpW~=s>y&NtOe&Cx63!MuAv~SOw4}olw37qr+yQLre^(m{=rY z6=EUVR#$*RW9p&4-=NSqf+IAb$q^bFQ$e8t6~YXSWRC9rcUwV$aTF97;JvXMK$g7! zu<}31l4Ce5fhI?&CDTBbK!q?Z$>f;0@ogu_k}DufqCi`T_dmbGsIYv)vuTVP9o^U3 zLBV|jhgHx-3AJiE$SSB1rd7Ng4SQbpf~4Y+3`d3GAr}FYYiZ{J*rMhf(9`(YcdB zR-MCP6*MhCt(pb03MzzYRR+he87t<3tlAB-3hb$6AgdOf`Cz88dfL2cAe%1WunC$V zpf=40*#s5Bv?-J0!0Sf~K{j0k*#!2~R*+2#uU$LOsB!Z5#MvOLF5$2WnjoN7%>h{j z6~eSCnWOWjr>MLfA z+lzLu0$FtnhgHychg!7&WEE5hn^niIOxX&uYCqhn#UQIbEuOQ6Nn_fdU+X|t-N9iM zG~S_BEd*Hw6~eS?CL_nDxBGU2?79lF3w8+$xabBEj2f?}o&veW;1RU=<_1*;9IcED z4K?r}%okabVc-S(i!UJ9I9&x71{;nOu4jpO^D?*OTNfWv9fM|Fili$W@)MkZ#lg#UmIqYHVA!3*>5p7wE29kN|bn7m%wO?_Avva@7+Yu7W0A zsH>KOTm==vbk#d%j_VI5odmgRBFI(X;->l49Y&1>Ypxyx+42I1Eznd8wPhK|7N`)0 zE%4ie!2Sdgj2b(eZ-BzW;1#-GAB5Q})o?WP3MC~QrX*V$ClS6sSU)*GS1&z3KRqW^ zFR`GYST7~Nq*yO8FQq6yGes{iwY<2vBrz{VFC#xURnH?oH`Ot@B(p5Dq*9`#K~Vwy z8eye;juu{_P?U>}&65*N&5aTj{1VGD(-TWF^Yfe%iwwZ`78{wR85pM;VZXOHGp{(c zq!{hq;*HE2pfKP!V6*_!N?9DOyg~se7N!`dr5dJ~Dg>7#mXsDdB^H70N;6JNGB!6R z!LGe%b_xAqse5QF9$_9mNMmQ$6io^FnEsaO(86ma#L>zta2j;CHKc*Mijl#Jm*XGk zk|yZQP*&m`Enp?83=@o80~itNxH$Gf%xUGd;^p{{tVfIke9u!uLjy|#6DX9ym{BO3 zJj%J3E6VYyxe&e zWZ6+1mQ8v58RQs)uTaf!%Nnfhg(fp{tl!>n6Qut(NIy7lE(GPxgJ)iVj5TR>czo*_dx;q28XAhD?pxt3K8Y0*IU=#1sVSv-Bl0d zL7wVA{O}3LQT-rCVe!<=NmCwzwEsodt{?{X)apY&UV!vZ2k8gL)Gv^yR`k4h4$}4x zho_*`1=Le3L7svN5#_1blP5d{8UG){RXku<{ag6p4ail?K(4~#tIaR|f?RFT_y%&j zI6VD5V1(8Z;WsS3!lay6V~C8(%=KI)cMhm+mb6 z2(qpf-Bk{^p{`mFa@F-!yWfM9eZk=>Xhwv(Y7NL$P$8_Y`t|I_50I-a<8am8hrdCt zHfYCi)p4k+_JLfrZT+6lAXk0E;VNiOg1Txg$W>4wOjn&@;`ny-+#isu9^r7+#{biQ zf~@PraMflzDM!pPjq~$Yfy^`b0!_yP9BsS;;K*Wv6!%uBm)?Oh*jbRrX1`zk9c0rF z93F$_CaA~OfjkBk!tj_<2}c{RP!w`w0@{QCH!5-y(-W&Q^HPf`X>JHAH1nd?{z7+| zIF8J|((rc%yTC_Ka9V)+=HTiIL@;Vh{;=;a$cGJ}Wb&KEN&*x%2O1h01Uwp;0vec3 zF|kP;;8NHiE@i-RLmf@?@yipyn*Tt9hZj_rdcZ1CY&z~tTL#wg53Zw?R{)_y3)7A- zJtx6Bopa?1vL_Vngr>&LISi9t}oY28ITEhle~-T)ZHbDu}Q`v=<;n+iQ^j zyDnV&4~p|oOGlK zWOPS4_}dBDa?I#BJprVBEe`GHkFDwjX`hO&{eUJ&d&~D-Q$X6c;?RDzxn&|q`*d{e z4a{~z{Y)IK-!9Dn=|2L}9}LQMPeFaN&#NX)1!?O9dHgqvl_e-;K#m`D*~BQ%(FGc( zg-0A{P8lkK5klB<&-?vfL5?< zCf8bafK|_fs)i@YhG1JMMNF?wdw3qKem+D!wtG~+%$PX~ zUjXvPOB~+#wr~4fkSU9xrr`I+qz7NYsux35!@ZHvYy-8@kz?nA-Ah1L{=i}7)^~Rn zf~;HyHG~s1$m8I_7{CZMVh1C~@i!MvfXrxk3u{!r2Dd+_O*;zG+6zsO=miP1l7|+g zn?P9>DukIJ!KoZ<>Z&>Gj(|+GXI%dvXKnlm7m_rC=% z2|0DX>jH>79qy5)w|5vd)?fL23S?zJ)Jk-ZKr3~qM>d1(gbHEWxrdSC^!NE!K(@?) z+j0d|!p%B$5M;T*WE{3YD;cOQTR^rzg)nS^TrLbY_5Os9=Rt0t2sITwxOz~6i&=x+ z04joEs1{Qruh1-Jj;`6uZh)M>0Ob5U%-pcx{om^#8<#*+3_AypfI~n7qZJp&OeS#G zyB*YJZsZj}+?*`Q(GKlqGl)Bb5*MT{19iwz$_+?0)d`A}yASt(t(gw>7m+D! zZ3P7vR0zXokl+HFdjILQYaqu@g_?>UT)im4#iGG(02RS7R4Ip}omVIbWn?8eHPy%< z%}BvBub{NVH?<@qKLtFtl4_n}l#*;fC4@ekMVQ&M)4^#*vC?W9!Hns7}cCZI#g4Ckcap2q! zt!9EZO*y#P7?G2Hp* zK+Zq5?C4{VO-rFRp{HYLCWqR@jwjngoDVj1PVfJRAVU{J4Mlf8w9tbZx&!2Vs1Szp zg?2D$#&@uT_uqh=er(#LmmsIFh1!DdbZA)t zwS^N;=>c&%*wCx%AA(}oU^UcGbf-g0HK?JxKu(7WVRibiPk%mxoIU~W^xq(-pY40{ z7UcAeP+QQQ4$VeTTe$FKEQr&=hW1XJ05)_z)KGM%gM<-fz;2M!p+Z=lerNfs?;xko zhdaIN-5o}amq+(}1UY>xNH3N$0h%$PPUj|=Rl$Zft-SXEWawt7q3BMB=1Zuddq7Ty z3So8n$Ii#UK~7%>clu0_(|_K){S{=(PN*&Dp^lnOc?f1xu%SP?d%%WnhZ>6RbZDN0 zI(;w5=};l8PTxEI(|?fD_rjgN0_60Lj(I;pPTvc)1>Nb;ObNAxmtZyp8+zvQ`5z!d zcS8+DcRDmrLJi#qaynE9tJ7!PdENYP2D`u+xYM_ToZfi%++UC_2cfp0I~_Hf@)69Y zU_&P?`1S{6=zgf7=uU^`NvPBJgPaZ(!s_%F3-7dpoPHbb^kX2WuRpoD@!t$~gQHMe z(4CH&P5B9CQ?Q{+x*j%w3_T1r6y50{VMI1P0CGB12-E4w9N$l0>jt^}70Bh__P|w; z%Mbn8(E_sMI7sVn7Atg1pt%PcJ_kXTK!vbba{BP;evlG}?XVCWZ&wmvq6?DgIh8YWXbQdo2P&* zIgi5?NK{ChkM_YG;EcpVra{G0V zCHvu)ECyM!Xx;9sAWNR(uml>*(BQfVvIHuG%_pb!PQ4AXT^T2_qu@3djuEH?$oVMK$d*OVF@&rp}}IVn@^7ZS@Ira z$v3zqFF=-bt(fovWXWe7mOx_}YRPqwB~T%3mMojI>@&!ce{f4afGqhuf6^O}C0}t^ z0*z&;B{x8pK!vbbGVAQZ?;uOsKElebA0SKK{X72wWXX3NmVks25q}e82~-H1CCA<@ z`3i1>RTOQ1s7EZM(pazDtDZE#CgfGqjkGNS`zNiz;hK*9)1?t?6W z3SqP4{p!h+L6+=;Te1OU$;z`wdO()6g0y1s2{e|W!Sw)S2~-H1B{#P|o(8hy7~GN_ zAWNRl{RZ-&K|2mhps@_K!$zOBVlZnga4kCk{)Xu?)53 z5y%p#5H?Hpe7rReWXUzSB_}|Zym|9-I>?f49F{<18EVO6kR?zdY?i#6J83bXu8HXj%ScY2i9ApVp2%9BO*8JK8vg9A!k`EwD&VBj^@}R*~9F{<18EVN3 zkR?zdY?iET{kIKdN!urQeFCzi`^AP0AfHUfVF^eW5%DiUmOzEDS#oYc*KUv{eQ-+} zKHXu|xVY%v7LX+~L0Sn${40IVn@{HU{5S-%WFFj-2_Q>uHZI%?vScm}OQ5j~wd4)R5~vV1OSZrKatvh2GPor( zK$d*(-*6CQ$$T7^Kw}wd$y<;mP$6uV{9FF^6v&cwa7z||Ecvnbz!8uo3vpNi5=KP) zJCG$%A#9dh>iBUEWXU$TB`ZLdEML=c5@g9@kXC{b{~lxso`|3N;rS(yCHvr(YyerZ zcxK~SkR?lTSOSe@XmEW1SppTp=99aZpIif3atvpxroS+X35CD2%gTJjNO z2~-H1B~2^t-2z#14sOW-kR{J=AG!jvWF-zups@_KlJz(&fyOd4xW0lcfeK;s$&K4LUx6%n2e;$_$dcwM z^PYk%*@(juXe>i5`3ABCDum6F)nAT%0$K7GZpjOfC5sO}{RpyTGY(6ju?)53JIE5K z5H?Geub%!5WJ&91SW*80WXaP-vp<6@*^0vwkT4?Re}F833SqNk*QP_iK$c8_Tk;)b zN%N#1U`w`xv=WT?pCC){MEu=ZGyj1snFF_^?=xsI+16v&V^OAg=v{u^Y;ZXA|CV;O46Z;&NWA#9c$-qY6xvSbb1l6fFY zRzCjyA7sg19F{<18EVNNkR?zdY?hqcw4e)Q$riXJ%RrXwxpJbVVJ5r5ejJv7gb@+{ z7i0-k2%9C>8ru6nmh6FBvJPa)lDU^VK$aW?X(br(|3H@DiTKy|S55+1as+P4HjpLv zmo)W&EIEwB5@;+#gX=%Y5~vV1pB%imWE#klGjL1xfh;+4>*NHGB}Z{s0*z&;B@Li8 zH&7vLmfTw~c^1f$D{xDWfh>8yWZe{yCC71C0*z&;C5<3UphDOzxiR6}T#zMq;Fg>N zS@L;y&kT?yCvjK;5=KOP6UY*%5H?HB|DUo5WXTh_CD%ZftX%MUHpr6GAgu%=z8Pc* zo`^sD?#oh;C2!!C+yhy%>h0J0AWP2Tuml>*(BNtTSppTp=99Ts+gE`s`2x4(8Q7Ap zuZuyJoX24aG?t;3w1O;w3SqP4$He}1AWQzhEqMpBUv zVY6iYs;*5SOIp6biu!LLOP)MvSOc=;G7d{X!ib1(2U!9Y!e+^vNfWn$Ea`z;@(*Om z&aN-(L6%$vX(br(9Ux2aMEvqk({_O@nF6<@?aLiTjgOnUwty_Tj>8gYEJK5<6J!Zg z2%ArC9J{+0WXT-3C4C@E9>0IH17yif9F{<18EQ!v$P%a!HcOs9`Fjv#$r89F(?FJN znRI;*$dcPQEP=){)RJzHB~T%3mfSw~_bAAcHE>Jjfh_s+z3l+VlDjx80SO}_z6WFp zR0x|Tdmgr&0$H*JZpku`C5IVn@<)lx_l92$q~3E+d!6doSSe4WXWS3mOx_}YDquH5~vV1 zOP*YLcNJvG8Mr0;K$e_dIq3q(lBYNIVn@_IYdifM&$s4#O_du3(zd!N- zWXW3`mOx_}YRMFkB~T%3mh681;w8wEFK|nqfh>9VWA9^-CGT-q0*z&;B~w9`K!vbb zvUuChw;)UYz%6+PvSi`OrO!c@e8gc1NEi|E(?FI$g|J!janj9?AWK@l!s`=|C1)S4 zeFL)OGe|4Jh@TF!1W&|2_;UR#$dVqoCI3K{wB39C9%RW^9F{<185&$OK$bv-u=(W7 zsYgFSmP~;$dcbUECC54B7Qc=5~vV1 zOO`D>(+aX=4cwA>AWJ$M_B1rkWHJm=YTB17xCXObb>6|0=Hxt$dYHhZ<|4u z{KsJlG?t;kH5X(FR0x|-Zcabl3$kPn+>&)5OMW#h>;PHP2-1qhC(u}iS~3r02~-H1 zB`4nAo(Qt!2;7owAWL3fTG#`!q#1`L&{&3AG9P3KR0x|Thc9283bN!3+>(7DOPVKt z>IYfUio+66C?X<$0mu@l5H?HZH_n_1vg8Wfl4BrCCbqqt46>vhhb7QhhWcb7$P%a! zHcQ?d+C3L!$sM>Q=fIZCzd9XcNhc0VK*9*0ECN{q6~bo8r4{4bn<5;unJ~!4vV*P)nAAEP)DPv*h`Koohjs{DE8Y4rIyAEzQe8 zmQ2K92{e|WmMjBV0u{n$$?@HrH-ao_`3A2~K$hHp@pTo*lF2wM0SO}_emTezs1PUvVYB4? z+MfqNmMno=G7V(O|Me}qL6*$MVF@&rp_Z%$SppTpX34bvl}ACAtbtoH4`j*y8^`y7 zESZbL5|A(=;@5yIfeK->WOvKDlORjBz%5w@vSikym4`u=%m-;D81ZXCmf(r_!(ZQ= z0a>yKZpk{3CG&s2J`S>EAr4ERu?!8abs$TiLfCw=^IGdgkR?apmTUuAvf|GkklhB0 zaaaP4WvC_VL6$&;uvxPBcH>o$C1>E4>;qZS@Os&KkWZH4uml>*P)jy|EP)DPv!vnu zts5XquD~rh2C}4U+pfzXOP1rX1SE`z_>CY-phDOzd9vlk9grn=;Fg>NS#t2g#cLo- zR)Vw=jQC9;OYlT|+nFm5K$bj#TXGF#$>y_HZh|aXjl&XXEJK59GsqIC5H_EjUibM4 z$dWg3OYVUznSS%fU63VfaaaP4WvC@vK$bv-uvxO<^0^lvOTNG@c?Pm%>-F~!L6)q? zVF@&rp_Xg~SppTpX36K)#(DFOFsX*^&Vu&RveZ< zV;LG;J3y8|g|PYL^6WQ1K$c8_ThjLZ4x`4Lb;mz~EZL635@;+#E!hdO1S*8hlHQG5 z|9~u+1Gl6PWXXf(H(x=P?8IRSG?t;3>;hQ=6~bo8vO7;2nn3*%xFyp-maJUd{R?Et zZXA|?gb@+H8)OMo2%9A@?mcY*S+WLh$vluHtM(oF3$kP{NGrjJ-vhD)PsBexc)bH; z$riXJ%RrWF*tDgo2{eC#!xCsLLxXEC$P%a!HlM6}c&7(s$sV{R>p+(Lowu+JWXVAs zmOx_}YRNv3B~T%3mbCnRHUVVG5x6DWK$a{xy|@cx$zdFpKw}wd$$pR}P$6uV{CIJ1 z3doW(a7*@qEcy0#dLPJ=qc|)92_qu@0LT)k5H?G0-McmeWXTn{CC5OP^ek+c46@`n zNGrjJKM1k}PsF#czB~tH$sM>Q=RlThoN#v<$dZ#dEP=){G`J3dEP)DP^U0qzcNc&x zc>=fO8px704J&7XEIEzC5@;+#EjbLb1S*8hl1Ez(ECE^a25!kckR{s=tegk3c^sC2gb@*c6l4ih2%9CBE+1F} zvg8lkl6N3W9$oyu6lBRokXC{be+*;^o``SSeq;m4l9nIv`UGUju8v;P}mLAWLrI zummKGi1AF16lH9@}6BFOKyX-5{&rMAWQH?e9!J9AWPQ3 zEtv|XWf5Xh4II4pt2 zGSrf@AWNV^*eq#l`gp+(D+`MuOWXVGumOx_}YRNf}B~T%3mb5=#d;w(1 z5x6DWK$d*oI}K#F!DAejfP@hde;#BBR0x|TuR0cA0a37&|5F=6u!kR?~(mK+0F^7!44OCU?0r@p@pvg8ij zl5-$SzOHz36=cav9F{<18EVNTkR?zdY?iEvW z;FdfCS+aTN&PO0i-h;FfjQFb{OYlT|=lJmZ-Feq6Y*R3^fWYs`X_Kp zrhzQEdusVFkR`uySOSe@XmH&ISppTp=9Aq&Pd9@sSp&CZ9>|i8UEltKEcuJW5@;+# zEx7}-1S*8hl7Dyqw1X_!0=Hxt$day|E1H^TvK#!zVF@&rp_beQSppTpX32#eN4r6m z?15Xd4rIx)>6cnTmNbGw8cUpj>_$ZVJ&+|(A#9d>TK~BpWXTb@CEGxjoY}Le3uH+% z4og772utpREP)DPvt-};r;|aJoPk@i4`fNhr$0R)OIkr%vG@cU%h2F@0I~!sgw2wU zCm*JREV%-=_Xe>i5c>%HnDum6F9V>5c2U#)&Zb{p(JB%7nW;+je z8;2#(ScV4IYmg;SAxxj}avc9WeahsS>;lby!RI7w|2}yJh}-fPoDTl^OMLqS+M508 z{DjFf*$w7xZfaQYkk`tJ<0!KLtO^ga0{S6Huagd$U85N#`jzWP7q1ibdbfe;> z&OQG@mQ03Q()RZbqsH&;ZNEX5EWlw2=(v>y4|$=MyZ~7O6~bo8kM6nKr_5v*SP8de z4#<+G*1pYCX0jVB!ePl|6ra2VSppTpX35-1TTg*3*#Wm?HOP`{XZM}}S+WF&C6iDr zc?GfrDum6F-Y@M}cg$oLxC6K3JjjwQFE3o!F_Yb384gP(pjh%6WC>IV(-K~ewjCQb zzMsi1@C1?$&Kz036~uiCxAPvz&W_(-*1w<0Zm{Z~ z4J?Qgb{VBOgor(1S16g|$gz1>FVA8ZSO~H*MBtyl#FT$`7&TVk-f{8rEOvu6IKl;* zPNCuQ4iqj>A0csKa znAQhX_ChllIX>;W_W-2-B@X>>o^1h{Z?Fkne?lSjs++S7cNrD#zj<(uS>wf{#kWD0 z&4X4l=;ueFo|Ptm=d3iLNsJuNcHevga@v0!PJ6ZeC&=jrThN{MAl)8vsN>^?yNnwD z&#k=&a?}DGjzT>JO;7{(8KpwD9L;avy#TqXuMvB2f9hWJ6lB>pbT=J{wFmiW$(y@x zK>Fu`^n=fX{0Va7>EriafwV2c;YQTs&OiwQDuR}jH;7|P1BbWGcnLCn2fCviLcorC z_3!BikfYYX9o5lzmr>)}t*h@r+LqvO6sY(_G=7Bf9b|^(8040owznYDccD9KftS6| zAx4g~f8KlnIqDF|QCRdp{`&4CNdF#m{S6NG*aD&L>Qj(;2K&&}Kd^wNmgyi*FW9~U z6cz@{aCjQ^2s9BqN1zEMacnvB{RhYoH$Z*>$M!Oi9d{mI0Nb$whaITdM3g`_nb39X z56F^tAWN_W)S}~aeu4t(0J_H)=s`Wc2jr;UnGeC?xeAA)P_u~`zHBmyk>lO%#>Sa* z*#%mfu;-I6b0+=;IqDF)qY_l1j=BVLRO_W>UqQ;&;BXW)7l6w64dM#o8n{pE1ZBSC zpT4$$95oAvqk7k`0y)~?2)d&lNJAa<0_3RO6W@RXbR7;yq2>z-Joy6TsHuG)J3x-w zfWuMS)~srsIhWnw7`mel2tgh759FwcAI^dUbOR1Yp{83&eCc))BgghzpL#%!I)=kh zXD?6c1Uc#ix}z3wLLD`w=`N$jzQa$z0lEo?qfisN6uyN1iIL;`pM4WRj=BnRlpN?_ zdeAizV9cm7`QSp3q`@|bRZ{5kuTQN8%Wuaazva?4u>20FJUF-R1bJfnkqLbuzixrZ zARl}WJ`@-=V@Tu47}$!X6+hSaf=oY!9+C+R_ClYSISx+#H3j79C)hmw@%GM1Ak8}= zPQ&o@qc2av^1Gn&SW1Vt3qFATeFokB1MlplCS$o0>*ulelRfl-4opnieR^|fKFByfZS7dprM0h6(d6f6QfcVM;E;= zgL7hQ=56C`=M}ok$g$(v**T!VY-+|{74>er4GL_7OVF@^2PVTwJ8ajY?e3m66BL#g zq3V&&C&zM4&7FHE!KUwqWI-!lPoeTHggDrMi5JP4&aqQc%X$i=bnK(?D|9Raa zkf*Pso3dbmofMX1`rlsIx)`MX3RFGjG5y`3l-Gfr@+3LB!Kp}<0sk@mN;w?e^eX^_ zJ~MKBU%P4rD0J3?LI=yi_Xln-SPJs(L1>h5f_wr#_#Sdx_2lNej2fq=w}Tb!hblrk zqSgg88U`H#g|=nhfJWe;LPRz4cTYPBia~>0&=7~mb3+f-WW3_X6|nl7Q1ysp?8MZ; zD>R*vV`9tlH6ULf1^F6FDRk}rIZ&t=+=ZF|x6z>hdbiF>kiUO6eFFRYFs8r3qkzy+ zS*X9?g8U5?!tl2i$YqlmId-jDvH|3>D>z(s;KgB(%MI?MyKI3w)MfiYF55nB%}P+T z9mU}?kT9Zy^A6-Ps1SzBK$pKgdNO+p$W@PVxN67Kb0Ak6JVbX@gB;XV=RmG{He=UX zkgJY^>>@ZE{2t^goWsGB89CaPZ`c8H)h8UTYPfL#cU9|oH$W;??xa$11vmjR+JV$qxf;7}szd^3rczoSX zkgLw(a1}IpLtXU=uWu z?lNjzYG1Jzt-*! z0J3B|+>(_bOQtTmcM@dDRUDQ;QxVjXZy-ycLf9<1uw>R1kR|)!mTUxDGHvQvkR{h~ zSOQHHP)ojpEP)DPTJoNWW96gs+h)&Y7dQ{H#R+rF`N*rMTV~H?H@E{&Cq29ZP8!Ss zj8^O%y}SYr0gOnu+5X{?ioi7E#=DasI}AR+&Dg{$0KTxvN`Rve-_3?rTp&9bRT&y| zU6sN(dU%Cw!!z?z^2@jLuS7uK;eUSyWtH+g&SChZFhhC4-V&#&~WAkX$IYX&d^{lrNz;ehh|yVx>I1w zHbX6A2dQ@mU;M$AeTU9 zD#4fAJ!yr^RNi2exWT9Zy4(&bf}9b-;Ubj8ar@i%V<1QW1vwgQ#{-ZZmzp1fZQqS) z2Y4Le1&SS@!-1e8*zEW*{p%@^9j&c*86p1o4zgqN_UA`I{@9Dd4$%44pvh2(KR^Wv zR0NwH4d2h51KH6Fx8py^jy+57oB-LeABP>!Q2YV9#0n~c&5n&LPhJAqF$Zo(d+S|B zjgGT-&VcMVh{F!hVc!UUfG%Q!ieR&2!ivk+Kz1yF+tClQqpg41d5|54ao7QkVF5^3 zf$m^}ieR(j--#o)Kz6Kw+c6Jh$BX~xuYl}0io*_Q3`6YzU6ceB!Dh#Vi^uPQ?AQXg zV;RVfTL;$P0NHUIhaJ!uhS~wT(FiJn&5ms|PCWwIu?KF)I*=W+CZ4U)9JEq*b`vhdiSsZpiV;E`&w+6cbR0NwHJ#+TG1KDu}ZpSf@9XJ0TdI7TIJPtdc zF$}c>bn6aO1e+cGOHY0R*>MMM$2pK4Uv6)C1G3{H4m+SR47CGvJq}a^n;n~f9{L8d z;|bi3Yalze9a{eZWXEM3c0gkoY6oa~6)J+wj+N`D{sP(Y25!eakR9u0y!-;P<0=k2 zpfL=!19agGR0NwH%NtMp1KIHfZpSl_9W4ho`~cZ;9fuvz7>3#bnv{f!V6)@bw!KYr z=CTX?f!py8WXJCJ$NqrqxQW9KXbeN`0L?K%MX=fNz}#~QdD^FVg=ecL$&WXDq+c0gkoY6ock3o3%mj``ny&js1B1#ZVOkR9hQZ=M0N z<2epHpfL=!12mll6~Si5$9XFjf$Z1=w__d1j^hhA%mLZ)5{Dho7>3#bnw^4*V6$V{ z$5qQfb{v7*u?=j;|5pn@cD%-62Wp&yCYzul*z9OtwP+Q{jx%sO_JQn}er?MVkR5Mv z*a3}Us6Rk+K~NEFcI=%ye;vqf#I}V*$ zya{B-9k?CmKz2;t-L)2E$44A?Kw}tchpYy>0aOH=9Y6YKZ3Efy1a8MQkR6|9wQmI3 z@fn95&=`i=A*aD^02RSzN9(oeyFhllf!lEpWJk~QpIbn7e8pi0G=`ye$m7rFOKwiy z2eRV}+>U1;J07)n?gZKK9fuvz7>3%Rpuuhc6~X3@6RZ0Vf$aDLx8ohij%%G0_JZvA ziNg+P3`6Zu#2@GVQzjk*+0oJtuRlO`+`Qa(5M;-19Ckos7;1+S{y5(=?czz09X)V6 z{(`3Pjk9=ILrKz8h!*?b3NM<)(DP~%(^f1GdL{Q4=# zjw5h8wt?)p+tK#`WJfm+JD|B5ns&AD$N7QAuP;G%oPpc14`j!oruk1mcJ$(~12xXI z@yGepz7KCfc3gqmaSUX~=M}48fb8hUVFzlQ>uBID+uJv9`UtY)4&07&AUj^Q&Uyo~ zV#}l|6*Fbhm->~@u$d1W4>_ClkJ^XP#;q~pGAUoc`?YIZB zW8>uBFCaUn;;;iX&h_!f`LA17{(|iI0=MHC$c{@7JAZ)en2y5^)HpZ5ALsAx+-{r; z>VLrPcn7j$>X%!;L3Yf6CGac+h`&Yx{QF%x9R8n_+vKz7`o@@X>2j-@#4K#g;A{BeHg@6ov+ zJGQ{>SO&7AciY+-AUl@humd&DE%3+r()TA8g6!A>w__d1j(fk`=78*2iNg-mIJd+f z=ga>zE(O_f1a8MRkR5ZTtXKfDV>J#tP~+SRf1Dp{+PM;B#~HXC`#^T|{d>C@WXD<@ zcA&<&HU2pNdF0$$kR7+-b{qrQapYR#3XmP^aoB+x=QjA`{M@l^8$otFhTCxtWJgzH z>ne~P8*$iy8t1n7;{#_|;u(+~2XWYe8t1O~SR> z3$|n9pYtF)4&$%`HO}4e$NA}9{Wn2&Y=+yh6l6#9=cX$lJC5S812xXw@yGekmWg*k zcI<}Ru@+>WgvJ3dbMavNmFNgQ^d#j}t?({MZXg6udm?Z_ClkZ~SrIF?rTokR7+-cAN#-vF1VB3y?p~4%9gJ!yo6{mi_nvvg0${ zj;A0yK0WyS31r7r9Co0_xj+6m@4E8)56F(+a68_D?CAX1{T*b-bsTn}#(4n#INvb! zL&Lne>;lbQ@cIK}NAKrpzd?4~#9;?&oCo5M^W6`2w}9;EhTHKMWJhQFkAEOLZsV{6 zHO_T|0rh@F~Ts5s5WXD4scA&<2DE>IVGyldEkR7Yx zcFYCY@p?~NKgf>9IP5@;^Dz8z-gfBz43Hh0;dU$q*>QPV%VdxpPjT3R8t38o&u-8iyUIaUO*~&Ue1LumWVqWw;$jL3Z@+ZCebo z<1G$5P~$urf1Gb!b8ZdDj@xiM&VuZ?zOQRJ$d30o>_Cn482oYm=l{tKAUhtz?YIiE zWAonct3Y;q#9;?&oX6si^A*ecwt(z-4Y%Vi$c|atKduAW@fn95sBs>LKhF2I?%x5j z<1^fjryx5f>}uZ(vg0cbJ5b|19)FyFda!>F$d2D|JKloq*mu5pJIIdjIP5@;^91~H z{^iG>10Xw^yW#Z*$d2=^?{|Uh_=&>~)HqMXALrk{Z8-w6qZ@9=UyvPde}3Huvg0=n zJ5b|134ffo@A-WkWXEK<9j)DW88zBwzCQ%A<1Y?7P~$uqf1JPhyWtGTj@fWKdO>#l zJlX-W+TcG9J5b|11%I61`LXT-$d1KuJEnr{n0RXTX^=k}w_>0FK#lWM{Bgc$@v18z zJ66N(mKp5Bfb7@|w__>Djzx1GUjo_Dio*`nI8VnP z=ZDX%yaTdhH{6c3AUob3dwvaMM>`HXP~$uUf1LNPU;F@M$6>e~TS0boPVKo3vZE7+ z9jI}hi9gPdFPi%VWXEZ^9eY7`^d4=x53-{hhaISKo`pZoyKYZ?0kY#V+>WClJ9?UC zJO0qaTMIsBxZyKh7VYpZo!2$78r1 zS3!2{zWU)6$c~9P>_Cn4T>No_Cn4Li}+)_eVp^{JHD`-Ecepg6!BZ`};qT z9kX%Rfg0yU_~X27>(zFU9h2d9wD#O()HwS6N7MYd>;`jj*nt}7#rWfV;r{>KAUkHm z?dS#B@$T@7R*)U@aoB+x=Oy^#e8bt#{UAFQ!|j*~vg5$?SDhd`7UHl2HO@=%$NA#M zZ<9fGtcKe$7i7o91>br>b}YtW2Wp&`;g9p}2j5Kx*|8aJ$5N0To&Sza1lh3^haISK zUXDM`KknZ)8)V0BxE*Ujc5Hk7e=5k1jq?ipaej8shxs5o4#Vx(3bLc?^5>Z# zJ67Vb12xVo@yGe+NiP^Kd#V=u^#CA;6u1=+D0haISKUWGr-kNmyA9Aw93xE)79 zb}XAZYZ1tfwK(iRjq_^!asIMl`)ZIKx8Zi21=-R4p?Mj|j`cX~K#lVn{Bizt?fvy2 zJ08RBxC*l4?SzS|Kz3}zVFzlQ*W!=!?`yAZ2HEi%ZpU4a9WCGftOePz8HXLHabAZ% z&fB|hZwJ}&8E(f@kR4mD{N4z%V=E3jP~*HFUz{g%EIW5&H^`3PAUnVZSiJ?=v0&%# ztspzLL+!9a?9Bl0A4QGx2GHgVs1W7?tYnU+o=f{dmNfU?Wt0M2@)cxBU(1bMAWL@Q zumm;I8$p&pg|Jz2y>-fAkR{!4Oa6i^X*}Aw4`j)19G0NQdK1VJs1Pgl3tJ{4<^q#2C`&74ogtu zy#-_mR0x|TbN(MZ53*!2+>)svOPc$Co&;HP5QinuScWdhX$4sV6~bo8;$!UvVY6h@rft_jmTZPwvJ_;=hu&KkL6#iFVF@&r zp_a6REP)DPv*gZ|y|+P@?1o#i7Gz1=jNey5mK?`n2{e|WmUMtDfeK->5_3SqNk-r`MUvVY6h#(+$r-mRyEgauj6As_lOtf-E_U!xCsLLoMkBSppTpX36ol8()Jg zxed4EEXb1nWa*W4??IM4hFfwKWXY```(J`AxroCO zXe>i5=>=H=6~bo8{B0{fgDiOsx8yF!lD#LNzXe%x8HXj%ScY2C2eJezgw2wU(~G`? zEcpz#i5nF6u|Dum6F zWmB&9fh<`Jw`3~Fl8J4fJ3*E_#9;|EmZ6qR1z7?W!e+_ui=QWfELjb=WG={(XZybP zf-HHA!xCsLLoJyGvIHuG&62Gb-b@2ovKem4QjjIBd*4h1S@INzCD2%gS~4AE2~-H1 zB@fR$n+39DH{6o7AWJsx{x=n5$#Wc*Kw}wd$qbMsP$6uVtlxfP9>|iza7(s=Ecx5` zX(q^$mpCke#xm5BnIKD`Lf9ikR_+#mh1&t^5X7?xgbkkUv zVYB4))62_1mRyEgauj6AyIWfqfGl~7!xCsLLoJyNvIHuG&63sk&aVPlavN^RS&${O zZ@gauvgADuOQ5j~wPX&+5~vV1OHQpiy$)o_W4I+(L6&qJyS4&k$wwTPKw}wd$y|^n zP$6uVJbrg#6UdU+a7*rjESdN7%Nmd+pK(|Mjb*4M^FWqBg|J!jeASU{AWJ^OEqMyE zWaXZ78$gzP#bF6FmZ6r+2U!9Y!e+_WPak%HEcp$$za7$YI?=os^YrAy-WXWF~mOx_} zYRO`dB~T%3mNd?~a|&e1Y`7)8AWL4&{eA>w$$uP{Kw}wd$r6wyP$6uVoW61W9LSQz za7(6wESa`x^9hh8joToTBi5SqicQDum6F*AvfO0$H*eZpmDbB^%HGIRmn! z8HXj%ScY1%3}gvZ2%9CXS1w!wS+W^!$x@IdoBzMR0J5YNhb7QhhFY>5WC>IVn*T@OJ2`vz6-LX8;2#(ScY1%3Sy}?2OMb&Gc?+`S)vP~XK$c9$VF@&rp_Xg_SppTp zX356G8~%YTX`TSDPe7Jz-r4^XWXVh%mOx_}YRN{BB~T%3mRw!_y>TI^e*(AUFUXRa zP0#*-ESZhN5@;+#E!hOJ1S*8hlK=acwt*~}47a3p!d*sl=dv5j$6*OHmZ6qx0a*eS!e&WF$NWB!C5z#f zOa)o8=FpoCkR=OoSOSe@s3luLmOzEDSu%0K>Pa9=R>Lis3$o1RBdw zOSXY5feK->Wckx6(?FJNhFh`}WXbWjUnYPoS&G9FXe>i5*$%PXO$Ax99ET;)ScY1%17rzQ2%9DEK2M$pvg9z_lC2<1{;&Kx17yic9F{<1 z8EVN+kR?zdY?ds3IAIaUlGAWY_JS-Y{y{Lis3$kSM&%dWZmK?@m2{e|WmK+0F0u{n$ z$*oOKuYxSu47X${$dcu6zMcnJaukOp&{&3AavWp{R0x|TlQ+D)39@82+>*5*OBT2N zzYMbEI1Wpou?)531jrJo5H?F%KR&t(vg9z_lC2<19=>e80kY&I4ojf147KDW$P%a! zHcMV^UiuJZ$!WMHdqI{QKlJG~$dc1IEP=){)RI#mOQ1s7Ea`i4{VB+j%WzAMf-HIW z_Tzn!C1-J10*z&;C8t4_K!vbbGV|KCmmo`S!!0=rvgGZ%*-t>0oX24aG?t;3oB>$^ z6~bmo$HB{QL6$s*TXGd-$;8Z&xsAgTXe>i5 zxdgHVDum6Fy)XB*f-IQ~x1@E_T}F+Sm;U?*S#lSLCD2%gT5=g=2~-H1C5Nx>?F3me z8*WK2$db#uel{IVn8we^B5Sq!&iD#(&&&%U>VEP05- z5@;+#Ex8J^1S*8hl5-cECW0(k4Yy=2$dd0(Jv|^x9^zKw}wd$t{p2P$6uVe15xhDaew`a7&JYEa{){aX!eB zw>T_;#xm5B+aODzLf9;s-oAJx$dcP|OU{BU`91Z|Vvr^8aaaP4WvC^0K$bv-uvxPI z%A&O(OCG~5xeBu6$(v8hL6&^PVF@&rp_beQSppTpX35s=^EZMlc@4MZF36IbUCnDi zmVCxx2{e|WmfQnb0u{n$$^SWXw}LGB47cPd$dVgB|EvdD@)d_A&{&3Aavx*~R0x|T z2b*W?1X=PMZpmAaCEpi(*$lGeI}S^ru?)530mu@l5H?F*FQ2s+WJ&X6czpu0r0vJQ z?I25j;;;l7%TP-mf-HdwVY6h$obH1lOS<8f`~_L^?DqHFAWMGZuml>*P)i6@=>=Ib z=fVHOAWQz^uml>*P)nYGEP)DPvt-|mj3bJI?%hnShOB%OhpFe@dGSrf% zAWNV^*eqH4spTTblGSia=7KDFbG!8n$dYCpmOx_}YRNN@B~T%3mMs7J?+VD0&2USW zf-E_*rtt#El2#m+Kw}wd$#ak;P$6uVT>SFq2FQ}#a7)&LEP1oz!6lF-?Kmuf#xm5B z7a&WZLf9->+1+s$WXWN;C0jw3G)`;14zi>Zhb7QhhFbCxWC>IVnBV6QG?t;3 zyarhU6~bo8<7IDOfGoKUx8y9ylAr6k9)m3D$6*OHmZ6rs0a*eS!e+_S&+p%WEO`vK zIVn1N{if9K*4 zkS$G9?lMYYxw7^BgWX?2*8PB-_ilxHW$P6uRfYt8SB;t5L<1O+&#-T0WB{GAA1d&I zT>^B#Js2};v^Q@8oAwiGnkm|qtu07bwt`N^h4Rs^Y=x{jehUhXjzgUvLC%|wBQ&7N z5gHmFK%oH@!VC>wj;p`A{(=Hy{*=3nLYW-XKiz9sJeOTy4=8M+KnLA-Pr1ve@cI19 zE+&oZ`>*~4**p`6&CoOowfQ5+W~dOR&B+{(?k{KoS#=X+6?jSY`YCrAH752<{|~Zc zHV#XmX%uS7Cy*sjA#9fPe7V{Ivg8}wk~1JnK5cu@v>3Eu28SikGzzukGsqIC5H?F* zzq;H5vZQA!tnu&?WXZx0AKO5d%*SB~G>t+n`2w;8Dum6F<{4)vfGk-Fx1??AT}F+w z%f5AiELn)d5@^&zE%^$v1S*8hlDnIaO#xYQ3~tFBkR_MyJ?sNnvKWUY&{&3A@(pAO zR0x|Ti<*zk09o=FZpluNB|Q_bOafW56o)0yScY2i9b^eq2-A|uj2y2P_RRs=(l8DC zRYA9o_sj%Y_XnCU5!X;v*h6yLHIS>`%{@B}q-;43S3#2n)Kxz~u7V0-y6PSaN5{tg z1t3>V!{Msc&-Z~`ZSWu6RSWX%rNH^|&D6V$3KM_M+`_D}VeTKW7gpkM8#FaR-S!jY zHmDGW+mxm-a`f{GRR@)kMAM{H!<1Brv&_s)l9N+Xh<6r0+J!Mw7&#{5 za~8?h(ttzD#3ace&B&N|XQ73b&~iqOHM{>V1|_gHpacfH&1c56yNnteHy!{bJ%bys z+dy}OTXA#r^9n%khOy%2n25}o0OvrmLMO<8mp}K<17(HPIFcK*T!ALHU!dd$6~ahv zLdhKeA5LEZa_AwDL&3%We2^u(KAu$;h!}^SkXJ=R5*ChvPRlTGMC!yVD?f zgQmBT6lcgWnOERYLqjVQ*evz{#!ZZ}98K4!G;A=3-d2^O!Davv05yQbq~y?yT(3WTjV0s)#7p@Hxp6bMiuj6e|L`YTJzJ2LCFpikE{+~vun7}E*I@485@-Mcju}jhR{Y?5vSdM* zV5%}O^txcXHMskA56Bk=ZIIBjg4cNmQn6ls{`&A)u=);k^$()$r4%{ddkVbvM{euR zxpQPMC=gm(8yY^$<;13B)yETHCGAip;GF*i6oKzwpV$S`yd6gbLbEwE0vkX#9YKXK zB2Y<-V+ya3fKO^kNorA2euaXrLQZO0i9|~Shr%ZwaN^@PV6*_W(D{X~vvAD6{ptuP zcBf6h%P94m2{R?XzA^hC$Zf6AfH2|s%L5L3mRpP(91EISdN>3a!M9w^6jM0BrNIcf z=jtI3atFc6l;bEfOc(fWtR4=53;a^-LaY)S#1;G@dcclC(Wc9B5Ux#;V@1OR7J2mC7 zYG`2s-S@>U#L4l3MdBK(!gOd*V0uf0V-u^u!3OXxaH|;EKtXVT3)}r$2iN}v2YweM z@UbQA?JJia0>w`cL35^cmkY{cj8Ee(0Txx z3L8PG5GsU`3Wa_$aje-f>m(>;Z3d+*ESI4^T-tdQ5QNdKg%g42kPa4q6dr_HSWCn zeGKHJ{g4pAkpH^0`#4Dc01o+A&6C0M2XV;%eKQ*@e+ViME&-aR-(}P|K5a878yf6} z$XKDTjD}W^(6Da;g*{XVGwjbWadaM;aTXNz7jT6A*7Y||fjlx98kX?V(7_FBqUoGC z^E62PM2LDRECIf*Va^$ll6_DgAO$$bbViB2puz!LwEk$h0@gPbVzd=fv|>p#P0dXp zj~PsZs)jqf!2#NJSq2Ku3Gc6f1Nt!3G3dbwZ394qvl$eeP$A6V+`z=~qUY#EP;fp2 z1*e?A;Q-{4e`f3K3m}i*1YH35n+4-a`9-G}f#q-EkiYwW30VF%4*6p@R)OX3;E?}% zaWh!{E>vC-vkag9V);3c6K6m|4I^DoZ~p-{;xr_? zu>_*TiW^j3L%V3$ij5);2({&dVE8B3y%BSByK(S`90;(OJyckrmR#h99w%r6dZaGvvlH;%>liruRz^Ye4 zRAck)=OdSHgA7~^QG%3PoH!=(_VD)d_VEfGXX1FecI!isM~;F#g2k#A-P=JPHCO{N z2U|ouo^lMVWGzGqA_X3h!s^%iojvzJPFn|6kKeD4KQ`Y7sonrljm@u*uHOPH*$7dB zP$gJO!86|`fow6@4pD;aS1hi--v1t~dM8vh-1QD@ z_CnSicW$hI33Bo)kdv`k_G#X#XCNoQfHeOEX~tsrmeyM!!wvR8 zHN)*@_+ckx%Q5xvg%2R@jWe;AYwMr90vT_x4_*6%=XOFfnK=H;nf4W=zY~Z4?enL6 z1iAeHx_*YscG!}|+qrMQfYk4Xsz>-0TbiFa?=x8SQK)LT=N{~{Q>x*Z$}5y)o}8L! zZl0u&oS&1ZaJ}_pja;ZrF@R*yh5QUBdX@fiKga8i3)y+Wtr*V;ZUc< zA_MT?pOIObfpMx4_QAi*yyDc7;&}KVDtN4PBl?ic6K0Oa2L~FK%w-oi3<@R-v_{&2 z8$bVp!srR4kz^&oF^yLM?P_Uo|5eI>bXFsgT_x9n5aAm zisqZ3XvR`dOa%Lkh6q>?awcjMlFu==x5^(L^OfsAi76F0trzkHc} z^DfBE1{a}rz}@7K04h9p|Jcz3^3r=8Ui$O(O2?A9>;{+7wLdTeXMxt z?48~V(tZ_Py8@q`P%g)zb5o{*wExGUegCElAma_LqiavNW{WLtuHNu$3dp*f=;{}= z+6vin{M@*H4#>K`S=eie`&*vO0BOICuHE6XjgU3Rl2_{%fV58oX~$B@O<3~?WW2#$ zsCG_{b{+xnK#R~-Mvj#ydX|E8PY3D7(mvXB`q3hgx8#3qdk3 zq50DWoJo+Eohb&s@*FCH+@}N=GD_YYGkJv!LqPLC znI)O|c?v=K`6X5gE}6vzIf<1D1`6OwC?%)T%$yS4%shqQam)sdY_ouC0FWX783P!*82Nb_8W`+EL4+)bu;S*J#Vg>XG2wbu0Hc)% z2a7;+gOdhp!AxHG0GO2|$3q^0NNh_BtfV+rFbhmnjWZ!f~g#%o-7*#-C=MZ88^@|R0 z;pi8AU}Urs=lH`T(AaROfeSp!TmbSX|0+fXD`8Msc(CCBR3d;8Bq$3S?4Q`sgvGH1 zvv{reAtR_w4NMSotVB3wfLwi`feCp?oSS1NC}0~vqE@^dp!jNRVA5c$n96I#2^wOD zc$uH03sf2-OylS1L>1rxmF&$R=M>D~wGsp++!LUXgjr@K398RdHyi-DS7EvfOdRAD zNsg(^IHH6HRBu3xozCkd&jAV(FlH2B(O@)!j%r##X(b1aS-e7Oj4V(%y(Hj+79J6_a1e_9+vz_yE@{6D~jzmib2ZmaqXUrU(hwg0w z<%xx$Jb@*zKfJXclxGYcLlUx-6=qt$(y?qKDBE0y^zp3(Knb+5!2y(nHi#=6;Id)` zg%2aP*8jWhr@(rjK#j-Zf-UDSfR#LjDlvh&VC(7|j2dU}Hf#dfJ$nvht)Dn3|41T_ zjzdzkR19WzKib>`*82=%u$3OtFuD?G7`=rF?>IVAZKjWD`u+>Az^1=|n$82t0}Txa z8aP%lGDw*td*Wkr%Vv-#=0ZIo#la#VDa*j%=c3Bc!0w8ijXfBHj6lP(2e`EOL7Ceg zl)3ktf^xeNSOAjSbwL6aj1G(*j1epeEJC~-zfND-0gC4hv+gnqU1R2Wa%}1z5dSPF z-eEzyYSvvwjg$9xfKr6PHE49=3{sdMz`Ax`x&qd99jXhDA20^a=D+*^)_Vt{7eoHa zi$7rbyEx>}y=vVG3cq_egXQl-<=bG>b9<(P?si(YaT`eYb7({%qSeY4G`!LP zs=gLNsxJkOm#yHDa!~cPL0kcRJOWh6!os0J><|;fDJE!Uwh{yt$AZX-(F!#cU`g$* zmriX5x#ktbHQ0J>{U^4AqSD|sLdU0vS zPLLySLrqcvC)lu`9xkfS-mtkwiSW+85o06D^Q(TpEXWL15LajyBS+7+V@E-bdyVY4 zYaqwnf4>)$9t|Esjf6UG@eVyFRr$r64@HUaRi5fn%uoe!G1A<(1@rcSOnC}1 z1r)Mp-rP6=a{Omx$3F!*{@>ZHpfERh3{eaVS+^_^XH}c9=EG4UyzHy1?M0tXv9^maQDu~<4$k0%Ua6H(gU-K@3O?m<~3F7#NAv`Xs+l&6VM2YY|pJHt%%D%dKzbwcM zRS;LGgNftc^WA4aj{6OA9JoQYcjjG2jm^`SgWPQJ8dMGbX0bwQkg-4;WD`&tWS~KQ zs0c=bObV8q-hw>+?D^>RWa}TT+ouZR zf?})$;c2i*Yi7IvoAexN62#LL+cr3>&gFj&@^r(ab#|gjvkLahg3M3_akWJHc_;98 z@&4iY%hSm_nRh1dEZ*6?LK_%4W*t0q0TdL?v+pv(o%|K#C%4YN%c#-!_#i0!4cX2$o z|4fjRL1u`yzBJq~+xpVbUKPZ}?c{gO-FHDwUWx4FDIh0z|5^hIe}i{Wi=a;4P}kw2 zdO9W*klQj@d2AlNt>0z)*AE72eocu=W`yth7T^S%JgUk?J z70I(-7G#Dhh>P3F-A8{v203{rvXfVXoV@1xLQwb{e1cj8b#lO=l`g6(Jp~{qpAY$A zCmM4<-d;B5e*At_5Eqm%co9wpoAms^L9j`mp(a6`JjsCTuxfm@7s$yVGepma{MaW8 zGD8)_#o^>_j2unJ*1iBa`5>~BH-ntK_w33CpnU!XY7x}Q)t=j2R2`laf}E^ab;nM$ zh$r1%wumQvzbc3e3gz2&Qtr??6KvAqJGa3meTAB&1+FuBCh{Lv<;pb#Ik}i8-Ck6& z>drn{kQu5VZYv|hgAca29e(c7#y22`UqE*FZji$lzL@v~!1$Tsy^hRDz*#k z@U}Sz>_kr(=Gx1iFwEVr3gUvwjXs3K!6t3Gc?E3Jcc@7ahljo3JFL2rQxfEGkQt&a za}Mm2ZJBexP8GxjIlRmcx5M8bZTJ9k_zh%-9|k%6`<3OOxG?wuwGQg=kl&|WR2`>- z6Um&URd%8)GYaixS7sFMR|RoF)e0}d;b4=#+&K<5=_k}Ah{M@pxelvFaI%3M4l+Y@ zcG9YSva^#`*{OoKpu&CwE{7jy;`sk{=@*d0AAlV01dZTJAcvoxv<4Ix2EU-zsY04p zQIoH@sD3@22TCdtkEhy+o;^`wFMIYx$$nK37oJGKW;9*j_6`(lzoBM8oP0^2`LOD= zO)s6JM0i2EMI#8?Sg1Dd-8LK@GC*NV>SiH33 z56H>?ke&Pz5H;5~=GvWx| z?~ELe-~Rp&3f?Jm?lMZj^41Sf@NU}M4@xTrjnJTh2JfrfmoBQ)Bho>^o37|;C#q&v zZ7-{4R=r;p#Kkt?aAw<5uu1PAfdY>Kur&*3Yz0d;L9Kzrz=k8I4ygutOasLL$O6$6 zMb~|@DT=Oksvs_?#89=DQi2Wvd;qz3`Qxi#gBl^WSh0e_mjj2-JD54zCcSG}I+tBw z6|&F!=GgEw&Ek=l#pU>fb}vBYMQ}-~zkt#>Bl~buCc0L4xa~ z&h|s9k0P5u!3A=aX!2|AeX_}~we3_vT#&U!_89%Gd+Qee1tr!lh*=o&ukWr0%Xj0D zU-NedSiT2`{Ie$q!1BEic^pyki;?5~kC*MBsMv`d6>~ws`1arQhNW}a4cZ_^SV5!0 zRpr~+Y6tEN3E~vGa)h?*ruL|PAqhbljj5{;tfX(;_4O&E0lp$&_ux-7& zwt-c4LT!Tt?wlR-52>bHiv$HO$R1J0MuB~@j*S9#svs^XPdZ=*?%scUz-IMB%);UM zTZ|mf);{S5dHw{l=Qo2K{;hv|E6DSG5F=ooU%B?Xi|UC5@gUEyoAbj~G*r6IUN%&^ zZoevs3-|mQkQvh#+ya~N8Qt>-HXu9?wr&5n=U|l+pteCg@5a`9NOjvJE0E_w_K0qn z^JA~k=e=;1-nSSz`erSf0P@^*WX~M}Ic(vdrVfzjCPJ-)#_YAm-!7_p zIP*>e{`4%@r( zH7HIECPS@*dXD|!KNnT)`%xgz9d}%7D=HvbZ!aq#TEAZv#D#nA6v&LD(>{UC_=@hi z23C7`nEy0%YaN6OTZ#W$+!{$^)vnf?)$A$C?!@=755sWiC?v z_#R~5w5u<{=1qq>3>pj<1({q`ds)Ik!BBcP%T`opO1-_T&XoH7svs^r=D=or>TjC@ z3d$K!Ga#OO;~{WJ_43iRAkXPeskax+yPLIFHt%khttyBMD$UbzIr$bN$NXET=7XF( z8QIDIKu&&spam4C1~Z}7L7mLz!|bZ+B^?TKvWBmjttkKgdV5*^{q_4*L0ot+eg~QH zZ6-Kf82msF#sm{Y%z)D_PoDKCOG(;;* zSzJ|DEe-~S=tEw9TT#{<_4cx?H|qDRg1GPyZ3CI{@$`SN89&iOl)(WJqF~#8tnC6N zMuRy}+aMt-R=VY&D%+wUP>6!;5xv37zgPAKFTbrShzrW-g^0W`6=dc5g%?0c#NZc( zl{gFYb|#K*k54QIh5Zgt*oT5vsDoO*8hHKNJsZ)>&+F}F zFF&u}uL|PAvl!Tn8IRwB&6o!@0}^H#GkXuJzH3nig&9blh`%3$jP_*z0L~WRJa`W}^z?f(Cag?4?qn%~kNsD2QOxI6M2;Dv)uXpcZp+K=(e) z1@&Ggp!Qzo;_SU}gAxFAg)FFxJQrUVnGTypL0x3D&7wG(W6B&WPQP3airKq!@8aq; zt!Lu+e`MWekgCtf@qZl@ck7^f!v|D5SI2L6B*xx4}#R`BNZ zfka$6W*!sAux9#TE}7G&(vp8KE>H&_a>6V}R&5o32%-Mq&S6k^R)6RbsJ z|JU2g#{RG0uL|NynL?BAGmse{|2zhp(eMtksgWD(G*HRMZijGSLk1%IfUTb0_8+Wm z8PsY>(B*HkIiPChaoHhCgcoF~Xjs*R-Lhd-6RcH1Tu}C@vxnRN6=eVE#Wz9e!JrY{ z{s(19_A}tD*q$?TEPQlmHzk&Tfo_Ci~^9_|afWO=wRSgC@zpz^*Rhm)OnCh+$2{^9B5{mawEE3})Hqj}21!ypfA19`v# zIv%+K+0s?pnS_sm4VOGx`P$V~_hik%P zq;ORzwUL2Og4OY4$3s}@X zh61Mao9jI2e{x#^bN?1>zBO%dETHG>Pom59lVjecmP-RBoxK5v+v1m zkeh#l+zgApFCeowUic02jX^)U*$aY^%w}l7k)?&II6nN|bRT4P*ZjMT@Lo#u{JV@A ziyJP2Y&DpGZgxYoJ+_tpPme9W3kt$MsCrJ&!3&_pq0&~opuur?p{T{u%{!M@=nON* zy@O59K$b56Sq_ejQ=rIrduQ2WkV&6#M+S5`4QR>OOfdyk4R!;l2xer2gF3P+XWV7f zXqx=wA;{eA&??FnH1h%(M1UU(%BHze6hyDGlH`~N zTCMA#3OaAXgAsYPEJ`_`l*2KPS11U&OciphP;zRjkwKb~f@fYqX^C%YNk)DOcxyze zd5Td=vH_7hU_d>s8IXfS;Ac5NqqC8TW7q%RFG10{4HTWQ*^(8Y(7C>AA1I;?CPRG- z51j|u_EK4xp>yKM5wQBHQ1#?5BPF~#@YLEv&p|OU9by-}PXwwWz?e~E^1q)iKuTsn zl%Sdk@xK#OH?L4O$KhMw-hzVQC~^?&0|mi@TYFxC%$No>0}%uTSPQrQBTL_a)X#*f zM=IPP`R6br$Eu&-KZ0z#01C(;$iT5nXZBMQqe+N=G z8)_HaZ3!jzkevv-KpmC7CEvha_yRQ!%fh#*D1{`O2D<@N1UdVF$KfEsCS=Poul4;` zkS894JON8JS3%BNyl&?wkhA7sIIG+quKoMxcRxYeKOt*>0@B`ma?>}E_PH3^t6be?7Ft$-2faRl2ECvn2){xH zyDsg{g;10>SsaKqhxhxFl=PvINIOcv}`WBz;uv5 ztT40G-#IVAsN zb96s{+6oG?wV)t_#nF6_w>y7t2L-0V5~v&D-e$nw5^0{X53GJUR6Qbap%G-uv3dW! zPLO4XkuBQ;vh3%&Ep5x@vKy>~YKB|3pdOY^Z8`p*y3q^LehFFo5s>ytUlwZ^a85tASVk*i!h6+Y2UxG_Qqf#+JfsI3DjiGZW;5wuN^YrIIld zc-Q6&(?F)IgPKC-!9VCx=fpIZSE!qj<5TCQxghK3fUFM#H5@m9a&yC>8z5I1{J>o% z&Oj*>*)`Y=pdyHJ0@iRuKjCA=!_#2Xe?m-$CZ<%5)sOaU1Ucq7+%b1Sj+wIm+j>wC z{DvsU){32u;usDMb_1vghGT?MITo$lwiRT}HMlizK-R3gbbkZLn!mWMfi{hy?K4h1 zjb))!j-%7J?gUx$6mHGZg?AY>woX~U31rQG+}1!FeNbz-G;lWqg;F^lz2CGKWX%`2 zH5WkEG;F)Q8DvdkXG22+_T}8rIt*$JH=bHeD3#;m&$S0Z)-*4=%Lu7F?}M!Q^Je`v zkTuP?t$`*Bs5Lxz5{ghN$Nr8@M?uz1f?M+rWKGk}b-O^;wBoh~8sSiDc=0EJ-92kg zf~;8xx8^U%nu}NN?E_iUj@ug4Bmk-&p(5B)+UjZZ&w{Mk0Jo-R(OpK3Q%9d10$J0E z+Zxm)z>hx(Z0(>+ zgb8Or*7V}G1~mx?;!gt0-XFRNvgR(_nw=nPx)*FX53;5ow>79qKnQ;lXgq%8F36fU zaBEJ0teH9G+$E4T6LDJujc{m61J%h;5o}4|UFU{}AZvcZt+@`i=Eb&aAZsS$wgxo` zh~Q5Gvwkgk3bLkaF)VvN16i~E&)ZudYo_A11~mzY;!gsP*3W+lvSudSny(;hKKwg) zA7ssR+}5Bb0WthZVE=^fw;*d4!mVjpe3wz<%)>R0K-SE}Z4GJ?5XYYco;;iR5oFCu zxHXeO)+}wg{tRTzY~0pBBOICpB=A=RS2|{W1zEEZZq00vHGldJyaHJ>7q>O2Nk9^R z5}1DD&kv9_JK@$W23fP_$c=X(Yv$v&1~myt;ZFkFX8!pDvgRP%n$;j{PT$=531rPe z+}5Bb0cre6VAjLF#^rO_1x~`P*$lGgeZ!S+AZr%mwgxo`$ly-`Zx(d7f~>g+w`MoU zns@hS{{mUF6t^{~NkA5V5@>Je>;zeJ6K>66kTtER_WlD|vmCcIs7XK$e-fBBZ)z{d znul;}PJ^sjb7W@Ia!^wow>79qKpuY*c(}cPBFLJTaBD7utogodavR8+)wr!eO#%w| zlfa?V9aBNpe1uzb8)QxU^KD%qYu4hn1~myN;!gt8m#&=&vgRk;n#Uk(=1e)#2eM{8 zZfj7JfD-;B@a)i!IUs8qm%vKs*C1>5?N~4gWX(q0)}STUaP1TMV!zXW8>M7TA-LDsadSThS`%~ssjpe6xT{7GPW$DI`* zYi7c&XLW+B{~Zjd#Zvk1e5pKd+@zM^EH9O(fECyL~cEbO)AZzyHwgxo`XyH!+v-gTm6_AZ}|=lYlnF9Y=8TBC&-$kxUE4=0=oE%bY1H4ov|oCaBQVB)kxAZt$Iwgxo`=;KcUUCZ`g09o@AZp~$oHRtEvJ_@qt zG;V89lYjyKBryHU&nqBnKEkcJ4YFqIp-rbi)||y{4QdiF#GeFKEPi?eWX(^wHIG5o ztUUVbEXbPkxUE4=0!H|gz`eP5?trXmTnevmK-SD^T6qa%%|+bSpe6xh{7K-};$06w z)^x(H`3$n=(1MNEK-OHwZ4GJ?Fu|V$rXAY)1Z2%bxHZ2))-3x!`4-5UtGKN}O#-I) zlfa6zH(!9PnF+V1dFfq7jSHW8?t!eij@ug4Bw&U=3B1|;_zlRKg>Y-SLDn4Ic^KqL zgPXXmK}`bY_>;iP=LJvm0d1#~nw1f~o2PUgNd~H3>N2PXgQTuAc(3<|Evi+aPO>|N7DivgR#rYfzJbBmN|CrepsM zkTpNy);tDT^Wx_BK9DuG~B46TCxXZ%|W;|t3lR0Ui4=Z$ePA3obxxRNx&O_68PM^^Z>}3lW=P`gRD7upl2J% znr7VApe6wy{7K;IJvm0d1#LZ`Sf~;x9Z4GJ?@Wr15zE7Tc0%XlixHX4C z*1SKve;>%2cHGvWCILVEN#NJ@wlg4W9>T3T4YKCiuP=u{)^y^w1~m!z<4*#oHvB&i zvgRe+n#&+-_I9p12C}9bw>79qAOL?7XgSw<1!T=fxHY#y);xQ2=@iJCUfkB8CV@cw zNnr8$wi_U8e!{JJ46^2BXYV;hcT~qFWtZ7^huWvxs9N9MgBFLJF zxUE4=0>Su`z{P*R?t`r9gj@3&WXS)FcpsKM7oI`TrPX%|y60zd_ct z9&fn?vSuo7YfzIwDE=gH_3_E)AZupAt!Z9)xi%IuD)=bB34Qdhy!=D6>wf=hz zvSuONnr@Iax3;W%2(o4-Zfj7JKsf#+@TC9Odyq9N;nqwBS@Zwmji(@MX5+R7H3>xE zPXhZ7eg6!yW+U91*&u5^zixg7vSu!BYfzIwB>p6@^wgK{AZvEQtyv7RX5E7Q??Bef z$88O25{SZ|1dhGB@*8B$LAW)mLDu}9xbYLnnuWNnK}`bD_>;h$vp4^PtT_p{W;4i| zv#-{C16i{ew>79qAO?RDSpEH0^UAsG0vF-d>;_qL_w0&aAZwQ5wgxo`#Ntl^r++_f z2U&9yZp~qkHHZHl_zSXTIc{rElRzB)B=GLQ^KOte58>9F23fP=_PNHDp!plz)}SVV zc>GD=!K3;8AZuR2t+@=cX4;#RtsrYw0k3aB z)_mJOX(Gs)jkv8rO#;dIlfdp{SLcJQ>4aPJ8Dve{$%9it)@;UY4Qdid!Jh=K%{;vr zWX(jlHNQdD>^^gLCdittxUE4=0;%|uz=iLpmxHXC3Ad(s1!y9_@5Wq^HQRApgPH`= z@F#(j&1Y7FtXT-RrW<6<_a)tnK-TQUZ4GJ?NXMT9-f!Ex9%Ri*xHXeO)*M*#W+}*; z-MFnmO#&JClfd%}%Ql0o*$B60HprT54aZl3tl5j(8q_3^i9ZRP-o9o#$eNvSYZim7 zxq9vAT97sSaa)6$1hViafoB`{?FLzM5N^$CkTu`mp4b4g<{)lsP?JD5{v@z%+wT1! zYfi$g*$lGgQQX#`CV^c1 zNnrY~CC5S5+=N?m7-Y??ZMXM=tT~R`8q_3^M<5B@UvnB{%|o~~r$N@NJhSQ`$eNS5 ztwBu!`S_E-rKxYufvkB6x8^d)nm?Ocj)JT?joTX3Bv61q3CudU{4&Uzk8o>lgRI%M zddf+VHD_^KgPH^i@h5?84a=^BtoaGI<}t{ciJOm~1zB?*w>79qpa_2wSbl%OZICsM zE8+DG$eNw|{$BuDa}l>Ss7at0e-fB*W6FJyHJxy4K7*|J@#XzhkTsWaTZ5VeO7JIv zPfIsF23a!^Zq09yHRt>0-UL~56}L60NuU&e5;)L2`8mj%nQ&{GSKejRXl%cK2V~84 z+}5BbfinC_pnvY<*C1;a!ma5BS#xi4!$XiYH*s5ongq)6CxHp)`rm`BSqZmhGRT_M z^BO>&G`Nl18q_3Efj67kTvJ;-F*YH=00v~P?JCv{v>eZ+OJY&6lRyprByi;6<)&4j{teuk-5_foyl?&qvgRpn zYfzIwE&e2M|Hqd$kTo~q)*J>|bM@B2KOk$KMN)Qegi)2HA6P?#&*MJ@0YbgPI7MK=wd|u-S9<_{Lcvd#1td`2@1(!;DQ6LH2ya zZ4YWHXa?B>6~bmu*T!e_K=v$!+taw}E~Cbh54)y-?D>q_9@J#e0_xopcP~fR0x|r&(SBVG02|Zxb1;PI@F#HkUdZ#Z1x;jHgg-up8s%r&VcN>akG0R$ezEr z?SV!*)Sga|Jy0QR_VgS%unS~Q-)dO?xecmXC zzI`Bj=ECiH1+r(?^$igdT6~boE&5g&8f$Z50x2I$ET}F+YzfORH)1Vc%Jvp8Xu;?1qiluU8|*>Xo)7`jKI6lxUm)#YacFOB zJ_Rz~U>~~n20weDT#g^Rmiz;0@7{zxsK0zY2r}N_0J?StSFAyObme@Ic?O5j)hAfn z3*~UkJGr81&0KbYg&^x1`nE_JOR!atGt0wv`~~7#u-&?EyoqzU!Pd1!SJVNp$rL8um&x9P@dF zl2R;8jSN#v=zagHQa;B5y53}}l*O@-Zr5pUVB$D9dtw(TDj$KO@;H{$c@|#T2#%Ze z(6|xgm=C#@u7O2^F@UjwiP4H1d^sJX24ls1UMnt+P0ZkvGZuhO&X|sTa)vm^0_e#Z zXt&I9L2jsHDp&~8GXq(V5XVBW9^_*-5SxRo#2|N%LGB_2pA!Khpsmgwo9;4dw9j1J zx@IoBK{xKy53M4hslOkT`k_LYso$2Pd&R^)P>8i{#-6p?-_HSssKFU%fWb>q20447 zX^b4#*Ug;-(mw~JAKW^=19IntuZKa#8ua3JC$x%%x^n`^olqeRcVa89w>_E%w&5JQ z!yX8Oima_W?@j}`>j21ISTgm7U&}!5Hn@PHofB)OKGfC&GSA==x_Si$kaaVz^(_Ed z_Zf$E_m*s!1&X&T=-LzB*kSYCn{)5xgVbL`SO4G!=#tdmrs z#_~i^EJKAbVp*w#V-f9dcb<=S5hFMeFZ;C?6dX5k1jmFaGuD8D^A37&JeUg&j_aV{ zXq&$U92^sI2M4qr1`Uo$px}TCVFZWJbS92n6WTX{9QF<5Ff4aDPv5nDBgkRXp*He@ z$|S^H&REj@nl-KKKsxSWc%{ou$``ZoxBvau^&s`9q3Q)V7D28jZD4{NTZekUh7}j6 zKxG8qD+);{+qT?g)aW>PcLm6l$xwG z%P3_5Y9oM(N-$>B*!bz+7LaxGq1H)&9F6sUY%C#kWYJVmm>WEV`WRl~HRRbzWnhNT zrUNU%>K{PWTY)SC9WTHF37}hy8XOCnKo>7FNr7%>1)o%-!3epZ^&t=Pd4WjhmS8Is zj$G~o`PtwJ)F!yg7I@fUySMws)I(sG&4va8FR1oGqz)|(jJv^atmxVb3b-z)4t9`V z90HgiQF$2@mFrhN11F%VP$gKR5?b9rqjCx;DxpG{QJKbZ?EBbxb$}a@Y?X4r`w@e;+7TUZ6Ydz;9bf z%*|NwfPvxVSU@MS2HIjP6IjI;1#;V z7&h5rOTc?3tp=HA@CIGIf)CUqS3w>*a{kwDkVlr`_6W3SfO=#G$Rki8OplyrGn*jRogHF8_>8Kg2OYQyDp) zy}WQ0*mIrjfsa0le9uI+ajv4!*1GZ#V5Huw%T1>r2AeT*FEHZOev(tQ-98(bpo0#)xf z-rRozvU&|PwGb@%W`dF>u96ROj0@P{UtOOcgA85;H5e%wLXM)6=a_|(3_+KFLPao= zp%5>}(wmFkfZYFR`&~wme}3Pb@d3pD0rC$x_21Zbmr-Ni-6L;7)^5OUEi|n|vl-|L z1gHpxwUBb?>UPljl6^;Bfy`Y8H5W@bL5pdqxwAmw1Qo(CSm*{L$GkU_zJNT?w*xkW z_z7gu%p)7#fh^jD+ahSO3$-0Ii>;&@m2S$$LdnPojoy#t8 z9pr7WeOo}@zSFtmAIQD~xb1`HTBv=XBNU+`81`W+EtlSZ1`2M2pU^ajC@mo=Xf4PU z>wAv=2DxG%)D>6)A6gVbT`><7_)sAXXMnsdl*Y0D*5VeBkH3I?436lFAiH+1c=8`) z*CE_?L5mZpUGqV9L4`2v0u_;6*P1#&7Ip1}t@n5bvgqNdbIoh#vKt(OT7)Gk=b)rs z&`E4i5e$1Efdw{q-J2VYAajpE&BYQ}&|(VefdwEBK!q?226^Denx-C*2Nu9R(6aL` zqsIHii`zjKoq}3~#RJgH2DOL-Pxga&0Br8g7r$FU=AM9>i^T)b!U1aTLXZcbLRdX; zxbbH{$OG%)9+(C4z@e^beISd@K`p}K0ch5NS_C>54l06S55xmtbDu0;4GJ%VGf;D} zcmP^3K+RnQ@&Hr_!(dPhOg{E~GROl*;T~8C^1znob0>g2a0zM=77xrtNkUv2>;_N~ z40|9R0Gs>q=L zeFchegF8@jv3LNQW1${c2J!$@2&)I49q(8I^1xiU2O4+XWz_iIzIHCi0}r4UVetSo z%R(*U(_lA%ieT6SiGg3BfO$D}^*oTdkD%sa@c=Z(Ld{(c@&Hr_!(gE_jz{e(Z&oYobj~;Gb2D0ZF zZhN5VA8OA^kUdZ#Z1!wu?c4ye=RVw?Js^8F|KGFXX~Zs+d=kp?}l{*UV!YGHf6;o zkUej3+XIbss6A^y_CSTO*>mUolieVD7Q*fM2eRko<>lKz_PoPw4>Z!D_N)cj0~Nw% zPtU6t`$6_>hubq{H|U1A-b1@U_I$u?4>Z!D_N)Wh0~Nw%&$qpw4ukAD3%6$#$ewkd zcJBk(^9i>-&`5{cvmRs*R0x|rlWre64zlMl+@9kgd;UyrJP5Mq3vPR$kq)(I1IQkz z5H@?>_a8hBvgaq{$x8r)ke!MvZ@)&zuF>^9#2<(@@$D zn?d$Kg|OLk{Oj54AbWPh?U@0x=Scsqiy(Xc;I;=E=}`Y{0oel;!e&p;=kvEg_FRP9 zvkqj>j~`dAg6#Q++a747L+#lLvIi=J&7M66&)x^w^Biu^5s*ELU+uUFvZtX3=b9gA zq(kl52C@e#gw3AcTh~1X+4C1}&n=KWH#c0k3$mvPw>{8EhuX6pWDisbn?2jVEqe~K zXYyWHdiwyf=Wg@ahah`eaN7fobf`T$K=wd|u-WtZ=+W08dsf2jY1@03QRC0j{ZB#m zwBfb~8tG7bc7p7I3SqNnOUKdoAba-1?U@6zXZM3+FG2Qn;I;=E=}>!if$V__VYBD{ zvPGXk_FRSAvk7ER!|{6r+$6~+0%#H9%!UP?b!>m2P%Zk zp2riW{|DJK9d6GTkUa+vKKu!?X98|}ppg!>XCKHOs1P=LPIPW-UN@IrU@hF9u6>}B z1b^NA3$kYtZhN4S4z*`L$R4N=HhWer+t3cO=P=x!1t5DG9_(mbH<#UD3T}I#kq))z z0LUJw5H@=@f1A<`vgan;o^2p|HvG8J3bJP!ZhN4S4z=eX$R4N=HhcD8JKYbm=RMq> zGa!4euejR@vS$Wvd!UgHwdWAX9;gsDdw$R9nhdh1bw4biK<%;_L|X2b3I0kUWB@kbLu_RPU;4>Z!D_8bA(0~Nw%&-$MYvqAQ3 zgxk}%|1P7(vq@*Cg6x@x+a747L+v>VvIi=J&7N2Drp*W0a~y8Z5|BN&Z)~3lvS$Hq zd!UgHwdWYf9;gsDd(Q9gUktM6F5I47AbW1jcsCbh&m!FRKqDP$&vB4FP$6vg>^jo7 z9AwXDxIGs@_Wbz0Z6U~>CAjT@Mmp4<6Cit_LfGtiGGpRukUgCTVCn4{$eu4Pjmtpx zEW>RNG}58=oCMhe6~bmu`;V9FK=v$y+w%ux&(!NjSAy(Wf!iKvq(kjF1+oV!glW%S zMvg6C7jFhxv2S5c}-?=p)i;hAs89}=7g=GdKJLr~a=%vO2;2U2)gF3!Y z5rjR^8(+Zv(U~A)53D@97G&%y-0p`aCaC*QgWL}lLYND^AwnpPW6_2;+d$601adyu zp7kJm9zEW%5oFIA-1a~d6V#qFAbX%f*zDPSXU1-jJul$)90l1kd%@$aAbZx~wg;M+ zp!S>v*#i~AX3w9;Klg#``3JY>HprftyKd|R*|Pz+J~aR_8j z??G5P{s^*X`_yB5LH2CIZ4WdtLG3vYvIi=J&7ONR9~=YOGY4)@`@y@68i)Q~IS8_6 z3vPR$kq))z0>~by5H@=r{OLXovS&5io@pR^4orS=6lBjf-1a~t9cs@-kUdZ#Z1!w8 zaq}F=o?UQzmV)fLwE4$LkUcwa+XIbss6CfJ_CSTO*|X|a%Vm%~C*k&N0ogP8<#dqs z2D@??IaC;7e?0Nd=@n2dt5+a<7Q^l7J9L*( z!e-CP1CQQ;?AZjjXD-N|=WQ=vfb2Pe+a747L+!Z* zvIi=J&7O6q9(@AYa}aLN8jwAcA3l8pvgZ_Td!UgHwdXd-9;gsDduG46^9^Lrb+|pd zLH4}(`TqmRo-?@Zfkryio;x6WphDQ}`LXZlFOWUY;PzYu+0(!9=4X&S=WyEtjdZ9z zcR}_*g|ON4?!@hXAbZ}y?RfyQ=j)1V-$C|Vz-naU;bt;yTMu9mO}jnvGhC0Qm6`JJa&QC5KB+?zwQHZ-@q+>3bJ(H z{N)`J=CK=G#%(FoUl2=wfh>iJU|5>M@$JLzo0I0T3pAaDMdDYGEk9PizA|YZyTMi5 zwm>}svE?_&7N`h@Exa5TUUco=KaX9Y>nu3s%)j5VAH?m3TiSB=E~CcYm49~ZpT}-+ z9k-=Wk3cN_1F{qGnzkBDi3+#b})7#w(8v5q5 z3+x5C0Njt<1`4N+e>Z;g&Sy8ci#wd4Q2`03|DbS!ieQ8jFURCZYnDBj&n|EjV(Ij) zKURXc$KaOk2U+@P^TEXr=Cd2z$89MzDj=3NfG$ddieRzy-NK8rKh0+sxC60tchjZ? zAnp^mrPn}~Uj4aw)~EUG1`lyt3iTJn(ngS_P!TMa_Ba1L^mYNez#E9AKWBYC0ph-e zTlx%a>At3eZx^r|JjQJ))L#%wn?ROAMX*@9?8e+b-3!?Tn$LlA`G#3@8hRG83$&br z6)k^3md-u+`)Bt;c7vz5Ert3EVretTQm6=qr70Yn7Ie>_ypUaBA>5WJ=RiYV%a6^O zypY}CIc{5^9)Z}>0uQ$^d zvKzd_Z7I|v5KCJ@mO@3aSo-AAiz|y4vJ1R|Sh{lBk((gyYq+IPK$b3h@c+`{h3p2e zaa#)Y7sS#wkfl%&ES6qg_3`xjh3o>KA(l2?YCjL+et}#17G&w34ZlyUU&wCo7PqBP ze?cs52U!Xg!D8u&BOmWw1f`tw;FNRt{<+5>Zufb3$^lur=ECp07Zwe9R8c7dC4TP~l!%cybUmi`Wf5;FCcz>;_+PTM7+5h)=sgmO@3aSo-?XlvS;Z*#&l9 z07v5D<vAjcA3A&cf>c{017jnF#JrTE{LB=+ z{9-+)%)FG;B8QX|O9NwL^ID0P21NxW4bTP%_)YXeCmA_jUHRMsa?VSTb96DC^YY%V zrb~<24H_H3M;BOeaXexX0L{xcFiK%jzOUtAGe~*E`!>)?2Uc7htC{gAZ=G@#th^Df zd=Wn7yI`%J;w>cao8#chmg|Ajfs!a9r1w)4d?uI^f#Z!?a^_+=~_O`#{P& z;mRBEC4#AkXZ3@Wcfpmw#-u3Db_vFH5ei0xR!@E8hfHuE??5N#L$O@{t~YH_Vy{GX3hY`3)PEbAV$0)Sg!} zKo07{;h^33e@_Kz>4V$w5?^S|T)ko%NO?b8`2o0tu;j3(4_8hHDW3pW{*nzExI&d2 zuYd3Ay|$QLU=qk}SneI)@!);uwZ-fP|DmM|FQ{~BXkaP;7YxXIG_hEH^7ML;;RX}o zR>R8zq4i80yPmC|2(o)2$Zjm!4;{S=);<}o9dBmea&kc*$f;A{%HjDBTfBZg`U0$c zDqK0Z`3$&nc=ps{TEgqZw1ija z2P4Oc!|SGkLTn|D5Ib=294JH$X2OlZTPW;({vND+7F;QRHOpw=C;qdzW1D(@A+ULQw<1KPF zKUxJ=J|C_eZ)tn>>j|*(1#snftAZ7q9)OiEge%9Jqc>h_nGSOMB7|~y7Pa9xHgU;Z zkk41+@cG7N{j)&27sGYK%L5@^jumf??Y+I2U0~Nma0Bf2#xn;&+(RJ4zNTa16)M$Z~Dq2!<`Z9GAY%T?4YE@iN$J-7{xw0C77n!z}#`vUJjy*3}?O z8+SG}EO^Lkg=N|q+WLZ6IuT?kR0NBqJqJ&`eYcohU?RlQ89OI@1aT+9E$zMxYS1qG z@ao-Sc7tZzmO?`hV(BE1rBD$JOSdv|+~3mn`qyH1frTJzupCRZ;oAQfzZSC_EQO{4 zlw+wFoSjt}7(NhxEY<(+7huztK}|zHmZ}BmSSnEsb^|CM{a7knj*}06_WxeYE^zS* z_C3=r$4~YAUd(Q=5^51*6MvPx&}>GI&tE=H2I+r}L;ryzuP1=?uSVC;P-rg|13C!d z^p(4e3LEz|9AMJe^>}I@NbzOpgaej^_t32-&aFmhb__GBwK(70X z!*$bl9-RWRa4ov)6w;_N~On;uit~6{}8vkP}98_zI1<-k)yk7(iV`rrsHte zsvA>4?l#zs;Vvbt#l*Iw6G7$~>_u0vAOZEqZ;(HpKmQ2!?ql5kK+PxO1oFwZDf4%L z{IM2?KVGl?vK z!(DH8Kiv&-*Fki5DX`c}$$=^fP+ta&88vR4-3wOy65=`x`FnSdgXLdA<-t3TmxKH@ z{r)#_v^I_~(!b~ZLV=4Vk9S}Or%rWQXy~Cg|eh3QVX;{MS;=v_{ zK!JP|8fNe_@Ror>VF7X|h;uB1h61iL@Ro!0EJW5L#IYQ#2l)&<0m#932O2t9RxvU( zFfn2z!p)2Kf>MUT8%VHW$iJHV5G?-|BCnLsv5ej)9WJNWNrz5s^LZEXF6I@w#K_UN z;>&SRY<>X6CYBSzR$p9j1QeO?AmL)g3yLws$uC%Re7U+Ctm6Y*$8uf)gbpmNm_r+% zfl`#gacF{pH)$E}+hIHA<$d=Aunor`>b2PB^DgFH#OuVekaq#EP$9?7!E@8IAw?J{DQU{$4`L5;3QN%;`{1#147i;Sw%IA=aIBzX9^T2s@oxI8Dd1_y#? zl3;P)mL)eYgWPusY9Bo87xZF{?{5#Efz@Axsz<~(XlU~0tryopVRsM|c35ot-E*|Kc=tZYanEo#?#%0pAbEqE=#EQJfI99X$Z@leF1P^-xgWS42VKGk zZ3RCBISwj>;W*IwjW4?{J_b4LD-MSpnR@~xZ*UvkVGi%1=Z-x9Iqd7fo;x6i{le`q z=$b#M!ybVg1{K2Uu%7!5pMxCMaszuw*7#r_NZ#Nsy2BRCg*xm#$YFbXr#%2U>ac$xhy7nX@d-%TKim$3 zrg*5so`4(%6~c5_8pqA8-`|7WwFu-c@J58*8+REsKCkY70kWr|7w3jfXyS$1^Auzc zR0z|aDU2LPrr!7rvS=g7A}j}&9c+5@5oF^-XaFEiVuPGNFbCwU6WjlRL#YY3v!KZq z>a1rVXF-K9ot4Hh?bpHYAXn`KxeDz4)gXJ`{G9Xw2Ed)jc@15H#=dtQL-feK->XTyhiO@9`%3!H@8vm0d3)7C9N zK=yRtwg;LXNH?Rf>V2P%YV zPYTDGhpT4%U(7CW6J!r~DETtTp1J)mC;wl}ZqSN*=o31m3mN*H3>s#GiXg0kF0f7E zSlxT0t8odtzzeu7w?VcXo3gX5aS6LYJ8oN`W4I7orhsgLieT8n%kgpj>Ah=~unR1^ z1s+P?+<5H}h`an2Y$$obt-FjG3$M@Iy=Do!K__lYp<}oZOQ(V?g^FNU+QZ0k{Kl4P zYnQMKYyeq@^2RR4JgwEOKtZntfn33AK}xN`X1BDT4!=@YJlmA`~5$2&9jweKTX`75|``1BoU5yZZoi|2tt z>Nt*&y0>S-Y>?w#!?nYw^RPK?&#}vMK+501mE)a^n(*=VT#)j&aOHR>qpp4Z3|9UQ zt{gG3c9N0f>F%YAKz=!a!!KKou9*+A?LAyO-f6tEOJ0DLe}F5;JEyqr(S(H{(?7zM zkriOF+uM zz?H*i@q}zRcD$Lh3S`+u9F{%n?p*=W{S~1bKB?!$G3&>RSsRzI3)}?h#xn1IdHbRn z8<(&fJca7!1!aB2SPfDuKA&UFwMX+nW<3O%g(ct4yuD=($gFQrv%qKagIc`Um*jj{ z)CMxn;0IheJi?urR`LpUGIR8A`@aZe;|ClzF751}53=zm+z7l`|Kp*pVCBEy%JF8- z=c_M+mH&n-$D6y3z5D}K{s*oco|m!3`sxiE7J%IT7p@#{Dc8I19$5K5xN^LiaY|>y zLXheI;mYwATWi{WMd=T2)sG$ z^Y`mu(8W?h_8jZJP3NCCG6taP9E?j4f_1e3$|< z&Y%^p9G)YwDW86E3s`v@TshwSzINUfu<~}ea>PoR6poiCx6e4Ygk9hl$Sdd<`OJT^ zb?Uh#>;_#>t>`nR(2IM(7x{qZ3!r@T8Phn9qYXP}fo$o#js52RK*+)Na1@YJ1!OpciT%d;nlU z1=h)lx3e0zfC8c)UHySPdnxesHJx|vGAgv*__Ul+$s9;mF(-=8U@3^xQIwC!#wqA=4hxmzjU(E~Cb!Yt5igG3dkX zFldJY>aaH;he3rf9kzglJQmV(SPn1QbT zKq%BBdq5s}FzW>lo=2*omlxSvRnre|c0QdE*ruQiJ z8hVXl3q4@snDS@JAy5+e3Q8i_?(6x!9-M{@zC-H{L5@|p@9S9w8g*QbJnAUKu?AzV z0Y06v8l-#~vT|{b)nMhS47jE<*6<>&|HFA-50+8ISx3M=HJFW&c)g*C_Yx@a?mKW~ z4=7Je!JT-a-C$_qeFsXsP$7)O3mO}1f7^Eq6vUJ6VlVAJtY`tr8_Y#_*aAnW!`_1& z_WfkzL6F0y;dU6b-wbuwdyvDRLYNLqBgdbA`!0bTb{mJo`gYC$Iox0&y2BReVl5vREp7&x zXRsJu{R3sFNA`m}a(2zmGa!%5!R--fVF>leCy+;=LYN+z#>jDNLfbWvM?T{4$f9$t zAbEqO7!H$!I_xUQVY8Orxd3w5Jlqb0CUmI7K7$+v6~c5_8pnoNzixuu)qW3ly1;9Y zJ$H}ny8^Oj0d9Ms=@x3w7mz(rAxwLwF><_G-**pW(Oi&4Sd!TE!w+tOY+Q~J0Q}GZ zXuNlqQRBdm(_m$ba61f|(4h|d3UU}!2&==+oc{9=+9CqZ;6Oh9VR-!xX04vmC z(?Je<`u+4Bko%Y5b{I6FLml=F^sO|P$3M5DV1=nrTqm|tI#i?TF=aJc*p0Lpy0R< z3J&z!jNY8;1xXrgfO=AbV=b=$*4vD*%;x^SyX6Hagw{Yc^Ma~4#B44W9qT8)2J2V{ z*Rck9{#Oh0)}VPE)4>j$2-U$3avQ-?w6Es6}oj`>!(uq(5 z6UU=<=i$u=sQS`#<0?*#LDnq7!ii>n&2>mMnY?a^ZTYdMlVeDZ51qT(RB+ z1;MF~{osIHg*ynKMI1B;eu9DkDufvX#~3+Yp6UMt3WAyUv9~PVbvyzEk-=uDm*7Eg z-~iS;@4ilI2AOBD6a6%`@JcFI+>JN0kU; z1Fay}eFV7DPiddoB~l$@VE-AZPbIz@F%T&70c>a`qvpG4Mp+poDd;!p;X5!Rik} z)gwkZv8|kWdh`oe^%00_Y!@W0{`sgK$k!G<)db6_C3P&Y-*NfxE5H5k`)G z@BYpNx$7RtUEneT?5@6fe`kQ)wHN9xEcFVsu>mb3{(v$QREQ{tZC!pD6d(rY&>hww zYlAIz4xD`oa;U)tsCvY_wa^+Sjy+putps^v!b4b#xZ}ZHMvV!(cP#^X;~;KtKuy`3-7(kr{x^W3Ag5ffVm%%}Ku=mtzkh#a9=3?6(J`;V{f*bcJiB5r%2g&)+OMvy&FA#C;>xb!sx5q&i^*(}?j2}Q2J-PGc2*{%AxGjRFaHvJiAd8?v7#2a2 zEZ7I_zZZfHz6v!MOH40BDH%aGOF>02%mv*Y^XT!L(;yGbfqS6+5$Lwkm1mEFJa7|g z50;pRrdOy3T0kCv3Sn3T@c`K1OX2!^>J4{W*Hbphmo z)o>3?19{-ngRdt*_S}WqgT({TbPDxAE64*-Aq)ZOcWF!B3$EWAOmAY=asMx>*V;f?=*u8poRxS8s#d ze*^CRJs@jdU)**LWX(&cHCWsa&9G2wIzaA+3SqOS`PskwAbXy}?KusyXXS%!w?Ov1 z#%&KYUqS8Z1la=>!e&p;>I;uS_I!iea|>k8h0iPQf$VvU+a750huYHxvIi=J&7LFE zA3g`!)A|@z_PqqzGv(&oM<9FN^8;kh-M&rF zK=ypZZ4WdtLG9@Q*#i~AX3v~USKfo{Sq!(Q{V`|}>FX7*K=ypbZ4Wfkq4xBG?12hl zv**ys>z_gPY=YY}4P?*ZmHXa-?D>k@9%!UP?db#A0~Nw%&#bon-$C{qgxj+eWY5y? z>pp_)`HtHjXrx2!=?B>Z6~boE&wH1CgY3Bgw`U8;p4V$neg)a{6SqCkNQc@p0b~zU z2%9|%dM^A2*>fLm&tZ@~e;ytB39{!mZhN4S4z*_@$R4N=HhUUxA8o#{gk9hr+@4Dy zdmemR_ZMW(U)=UUBOPkbB#=E&A#C>Snzp$eWY1r?Jr6{);H&}5K3tKjxbeR7vk?12hlv**j7 zE3-lNoPgW224v67hU-&7_H^R52O8;6duD*_feK->XY%YV^Fj7phugCoWY5BvyJmvy z>BemjG}58=%mmp36~boEgr@n6LH0a@+j9zJ&#Bw{=7Q|$#cdBX(xLXu0@(um#Z`OP4E7QyXldwQ2q<8I%9&XQOkUfX5Ufl_@XEttoppg!>XCcTQs1P=LTJD`Z46^4Q+@3=q zdv5lB*$c8~E^d3Ekq)(I5y&2>5H@>yUQRd;vga+_o{J!RF3-Ah5M}D!*|QS2JXC=rUs1P=LF5jH~7-Y{ixIMc-_MHCm;x5RZwYcqpMmp4!CgY1C{VY6q}w3)9#_I!cca|2}0hl|gjg6!Fd z+a747L+x1uvIi=J&7SYQbKisPX?_mNKhHt-+{8EhuX6NWDisb zn>~+Sb^iz1vk`93Vvs$pumAr9*|QtBJf1TJ zXB)^Ks1P=LrtJSQ4P?(lxILFa_WYasV*<#Y9(|_a66p%e9aoYopbf`T$K=wd|u-Wr%*_U}Bdp^SLc?`1W+|RQ!K=z!*Z4Wfk zq4w+q*#i~AX3yCpPZxpg`3bk@HOQVBO>gFa>^Y0u9%!UP?b!vg2P%Zko~^fDECboo z_ySg5e+JoeL!pqGvW4hgY21h`TiP^Jy&tt1C4a3J^MiRK!vc`^Y;AhZ6JFV!tI$1vghZQ9~(gS zT*qw>G}58=><8Hc6~boEtp10)K=!PJ+cO(v&!>wmTS4~R#BC2W(xLVo0NDc-!e-Ce z=Zp7&?AZvnXEDg0zTOWzK=$0mZ4Wfkq4pdE*#i~AX3xR-mk)vL*$KC2HOQV>7k=#l z*>e}SJFaC>%x?0NFA?I_5ehqMmp4pISZ?0Jsc9%!UP?Kuvz2P%YV4=+di-D5YNEMXV;@B+M} zeeU9|cR}2bAZx*!^&f+*J^OF+wI@s14Z3mfXoqgihwNyd2HMdM6+zev-K>9*h2zzF1MQ^&P0=>6G%$hBf&&qZN?{xu zc!g|{57AFbF*UR>NQUpUH^V$cKcFbTqSC?CAl1}3F&VPmUS0!a9{ePIB~y-#yh74x zsfKChrl|_S$wjHDdBqv|C60NSxrrs2`FY^;_cx#|k5f3wCm+vMyUwQYX|0>?7r}F3A@29Xu2}x z*upDtxZywp3pn7}0~l8^GTdU66S&VW#bC?8&|vQXjt~#VC@x+QR~1AknQ?646_SZ| zPc1GfElO4J&Ckm$$uEjUiXd>j%w-0fiil2Kj;*{x!q}rxP+=olG-|P|;a$xu6u|NQ zPvZ@cuO@+f<%sF4zU9lVfqZon>MH?`t-J!b7kmqSW8!#nx$zFj$b}#ywK$$S3H(Dj zpZ?3*>9;^eHZ?RfTv)?l#SStOv_9PeP2tOne{X^mHbWIkfE1#wwBI1EaDYoH8`I$v z4xIpNz5{i*El4xS;qVpQ+8hhIc5n!M;Rjz41vW^75h-GjqXlW3nUoEtWu5b0-3Gbz zF4QvA)vnwit2tU3LGcTjZ|`||mr>)>*{z_UH+TsxaIws{LsytU3tS!zb_1vgqU3{Z zJx%3!)YI|+6z&^9;SOHJGzDZ$=dl)WK)l9n4Rjp{)EZt5b_1vgrZv1An=kx)46*1iMz|1)m? zLo)-&T{FcLK&L-LMUdS!Q%vCim(X)Yj#Vvhzk~e$2jqV&XH4yT_Ua4B!iP|IBI;&r zYrjwS{{^dl1W}D`?e~jso4$bzd<;>7w)PwB)0-flPTM{86G+`x+&+coN2pIh=Sf3F zFnxNSkz?kQx4%I?o%HH1qf`cFUYRpv;}4L9PoNI70>uHS>A`Y~Q4gL=xP>@5rZY;c z6Ib{{LKb-s^3jZWN56tB_>S90(7X=y5$Ggps0gNyQaQHHd;TBfp@kq1fdj7f)m=u7 zebZWhfvow7+Zt%TgIWVRSQ;vV&6>#1k;+mj2zFppSFYS*$A=cpTERnP^dlMzM8j23$;cZUqzG3vEa=KSjN2M$N`hJgI!qZVg3X$9 z`@T*GS#uF$%|CyMZ=g8uem4aqYydj353Tft#W}S31F~j=xB_TNK2!wR8gQHorE=^% zd2u$#noDqNdO*I}v8r(<$Ty&8j+f%*otMi?rB z>6>kg97msS+7GhlGsqq+d!U*=9^V79@F}#IMyWWZ$}r3IrrE1O1{yqrYQS5ruO+rz zN3FMn3^*=*+ISe`?xxpw8L?H3I}e^d2y*vxh%-WLX^@Eq zFCaRs@H%`ou?`nXdp+pr}0fao#PE3qhkkI1(l_@k65$v|1V} zf*F<57&*T5-FyJDXBNmFEKzyq;}eiO3|>M#h#Hk(hrIB~UO^oO$vcocbPeE50bU_)j_Hg7 z>JkgZh-?g?CK_z1WKYx2MB z1EHCT+Pb~ zpinrDBNP_>`~L#usdrFMVM(K#Kn}aH@;)d@8G!0k91eq)dQgXf`mInAOovTm;yAVE z|2vSwu7Mm@iJ3;fE3TDkH=LN~8N2C4LJktl@-o@z!vX(8a@& zG8$*Ky#a;E2dLFp!sH+*OfGiLe+g0tYW3j=6KL@P4HHl&8!Cb|OeP=t^9d9tPjQ6F z-rgneK^A_5It*`^{AQGxEuip+0}>{GI4BE~n`r6=*>Q4HMAV0aOHQm^|3?`4=cmn%~@I zl){pCcJA8q9ptdDP>10SlQw3FBRmT8p{sCG218OyvF!>7#ldU(ufTL{%?l6ItsL(J04SqmHu!hOcD<7I(FJTv0h$Bqe zSN{ADa@bF(!*GYmTtOWAcv4qJ?P?+@m`V5Y?HMqkBTB1V31T@eB6~P)N>yAF@ z0)@$b9AVP5a&tS#VSk|x!yP7@83mrONHkC{OkUra(E+mhAJl3rVX_JoCdY5z1xMRD z++hMOQK4Z18WnH9{SRJ4_BS3LIgTc)+Ic2^uD!C=8R$?|*`=Zh~5kB}`6$!eqg-3*fNZ zggZ>2B`P#bK*M5C5zH`Iz{K(E(&Oo%FnI$ClXfgq&y%mroD2$-W~j>|ks7kT@P@3G z5D&*OMuE4i5^LBL?m`3QE<1?V2(5-1DXfP2+s}X<(gJk|mLR$g3ZnCeE`oz{3+^C- z7O>DD0uAXwMX(0Z`(;;Wfr98at{_^mzh?$0h+3g8!yQDY7zHMJThJ^c56D zzt=U+1UYyI?jV8|ztA884W&Xwum;hoC07=Kf@mhLAo~6Lz-4EVGwPZ)Ug2MkS?f0umn-d+q;Y!({`T#2jwo@K?E(0p+O|2!EOK* z!3?5_OdR(Xy;=qeq7|SZ!qP9eFnPyfki)v64#QnOpJx# z3CQXmsMT1)WHKmB&d+WIhut3BVFE3UpxDWDcbHsY6!^*}afEtd^6mG1u+@D~tFeU1Y*3hdyR#7-ZToPC3AFfyhKY;@y8%=L zYnc4I_Gld_Opf6QlZUTbR)fN%AL=l?Vba1*%`kbmVkg+@2~exCgvmxwn7qGq860+? zl_5lpGstSN8{jN~Co*wNIdo?eC`_(`!UQ}lc>rY3->JLSgM#7^?y!dzTF|hU16@oF zQH&Ax*!t>6pErRmm?GLgNl>d%!dhq|6Gv;y(QTkGc>)R(Eal1G z*PlUQW-u9Q0A9Dxr>ffzHJ#iHikB%+t5Mtz8CE_Iioa<)rfvYKI|7LkY-2Cb5)~SM z@&wA*b2|_10)@$E9APr)M8|fJg;Svp!y6_msTwAiA9d^iSv?JEHI^{B2MUvAXLf+Y z?ilVcftIMyFi{{-#!fqQcpoTCn%-d_kLvl-wHxHH=}?E^4UC1c%)z++hMOQK4a?M4*hF_G#BKP?#*h5hjZleguV?!EC6* z@P^4js)osvdFu{?qGJx!YAj*W{q8QK#=PT;z+ra=cbK5o2g-Qr15g=zWcJ2WpfFjF zBTQy*oP8YRu(?o&;SH1HR1K4JTV|dBSv?PGHI^`$1qzb~zh8pG?i}tgK`mod2$Zoi zUT-`H3X^>}!lbRW>kPbm2R=&D<9uysmpjKlElZ~J-*>q_q zIP5Or4inTeRt;YnJBf*7*ZH;AKw)wR6eieu&7EtfT?V;qG1O&v1LZ2&fr2)7tcW?A zxorC$utSzW90F;e90vu;!*@%;fp-OWpr97A>I4eeQ_mLN0tL!zP@rIGwB0$g@&+hO zmO`9jg*QxYk{u@4)<*6=w)ZB;>Sa)?u_To9pfI`g^awb#uHg<7)IwH+Kq32g*W7!c zF!_ZeOm?k*1`0ESa4cuXZTF7eR zD`YP*a_qdc`Y|X>I^W-A#I}}x&y4q=Ff&*Qbr|mD4y3QD{^u|81>~u(8zzH2e+### zP)kWIe5GV6$C{S8&p>{f0rC^L6W9FyE~CcUUt8{j!uSquYfuXwZF~h!D#zRvvtNO% zSpc_Y63Cj5XJ$PCS#uA!HKZ z@#T(Gj)xzYe*#&v0dCDAkTw5We}g<}@Cdgxs0mIFUxG{JSa^5IH;^?u;MS}HS<`#* z8OW0cPjFj0xYfzJb0sbT~`)kubkToaZ z*6adVvvXh1cTn8Cz-C# zYfzJb3H~I|)HtyZWX%h>HMc<49BF$7@}$8B+}5Bb0aN@*VBMwuNg!)Jz^!=%vS#*# zyIpUVup4~BZ4GJ?FvFh&elG8y2D0V{+?rP)Yu4|%2lAxB7u?pMCINH&N#OE}uQNf` zG<>+r2x*>t0$FqD)D4g)4Zh*F1~my-;7_?}DGRK-T=iZ4GJ?u)?1NI^O?Z3bJMf+?pz z%>!BU2e&n-Nx&Lk65!?Nyw$u4WX*yP;Dy)6X8m6a;;w+(ItgU!^Z6&2fo%PU+g8*R zVS_(KJnnt45oFBJfI2KlB5w>79K!VZ6mc>d$sPLMSR;MS}HS#$r&(G4JLT5wx~nj-A+ zr-&=x-tPrja{_M7CXhATI?inZS<{Bw8q^fwfGLH7Ir*@I=uZSAZ(mp~S-f;Ql> z^ei5M95(UnqEjGslW;o>HPN`?OEeRiI5teYa}(sS_K(^eq)6C4sZI2B$Kx2zgVm^|C_1Z?#hsMT1)UZ!xL_SEL;n97-?a0ljtx3cZz<4yx6hz#C4F3({OtcHFtU7 z%U#nLIi~zN{}ANGr64b23HvisHs1$XxDM(?EMY(4<6TCLll|xJfYi;v?J(5*=!q{s zPGsV^bZqBSki)is9EN2?q<7`4M<5H=Lmft1*xw>L?6HK&idD@|Kvr*nT8$-4=7YlI z!P@o*Aa%2FhY4!o;f1g8n8?J@{(r+uP?#LX5hhP>y#s}r!A7XVNDGrYl!VFbeO=E% ze%%DM8cUe01BFTB&u8Fhn}a({P|GcE0_E18RU6-e!sHT;FuD8jIVj8wHbWgoTA19U zBup0ne)b9!CR?CZV+oVJpfI^{>f$qyx_P+61hw4q!B=igWa4<%wBaKtOdjG0lY6s& zgTl;UE7W16g~9r_6hlm9rvWX*{WpfEGo33V80Ve*`kF!^$M&o_`?cR{Vj5+;v9VRCBn zQ*hWV!5t>3WvoAeGWOKxIe$T6()S5_J81LnPoOX}*bQ|UX<_o3k}zpGap)H)O!h#n z#u6q!L1A)c?oV*oEyEoqsAX&bfim{Ymradtm#_=W#Std2&wmGnnZaJD!$=F0_mqT5 z^Uq8FKw+{EYBiQH>G*V)QRC8?hTkA{D{zMiY8e|ypp3n8U~4NVOxEBCle?4NfWpjR zKh$BQg~=C6!ermeM@?^+up1nJT8$-4CV;}^+owt3uv>*YOi;_%AOdCV(I1mLL1D5R zN0=u`q& zY8e|spp2a{p?@MMOm5-`liTy&fWpk+DAZx3g-J6P1%1uOzfSdm!sHm#YAj*07ZfI6 z@6QK^-3HuYf?CFg5-4LkelML03X>N&!eru^SD-L6I1Y6fX<^byNtjG){5lB~CMTd) zV+oVXpfK5g`64*%HsKBv)G{`VKpFdC!GxKhF!_!nOfJ291qw5RlTe3|7A6xY36tH6 zZ%zY+$tkGSSiRjgVRul zkrpP?C<&8E=jY4M+v6WC`kCBxdF8rOPCx1g~{df3&CM`2zQvEma%aJ%Gi&`n|6Z2TR*=JP zLLEk0n4F*_O!gg`xD90WEvVI4!sHexOwK&-2Z!Ae++l)R#>NvUV^3fFzXud1|8RuK z$D{K=VPM+v6@AVbZj;=Pbx!PoWMYElfUB z5+)OW&pZdR`We(}EMc+*6eja7oCSy71>9kRTE?alC}XE=xOxQ?CO2?|$&KBsFM=HQ z9O^LA!sI6T;SLkjGB%As8GEVe`3+E*JjW3x z)0TH%1v%^`)M2EB$$v`1Wa<1rV5?t2t;P~2mq20i{L&h5*j>RLCa7g>I)O6w_~j>e zKwXNCa7g> z27xm6$egDSKw;AQ6?^-n=izrym>Il7gV{-cERP7ZfJ%pjKlElMkRUX})*} z9CkNwhY4yKn@OOI{WkON6Hu5;!4W2Zn_C`&9QGdSFw(+gA|+w6?RV=VkkuccR$~d1 z|DZ5Aw_po6>~7%>6Vx&`i$EEB;O^ZQpfFjCBTQDG`2Y$tgO5;$krpP?DG8J3FW)@{ zg~=zV)mXx$@9SMgjgF;P!O?aHcbK4-vDpO5*kd>Ez5#{FCLCe%sP#K2%nUw59Y$K1 z%%LPqwqD=%5)>w1pjKlElewTU>ASuO9Cr6`hY4yKn?sP!|nm@FhMP2a|x8OcRpYG0t%B0 zIKrg=?^jTm8GMI2jI=OWN=cYp`TO}JC`^7pt;P~2yFp=c=KEG~*ge7>Ca7g>9)U9U z+3yEGKw)wpN0|J6{S*{t20x(=BP~o;P!cASe!l(+3X@+@tFeU1DNvZap0pnvc297J z32GUePoRuFx%S~7P?)^K5hiVCHva@U>^Ib5q=m^EO2Xvjq~Bnx|3IzA5+*l6VKVvg zYH-*+!yP85Wo!X~GIr&OTMh4)unYXf5hiD!H2wvJ$zQ0$NDGtol!VEI_q)MX|AShM zB}`s`!sPbG{ot^Bfjdl4%h*B!W$dHvkwY#d>-`}}KAm>D!e z9Y$K1Y@sAf=DnTW3JQ}ZsMT1~N!z!(j2f>Gp9P2A8{A=nTE-R=C}VfrIM4$MlT|pv zWWu^9pfEFNhB}P2Fxg5;n5^45trHX`El{hmgvnG;nC#m)4;*&yaEA$M8C!y{jD5h! zapl|D37|09i6cz9mOcc9nL#VmVWfr0cA~=sysm!<$cu~SF9!$32i#snEiy~-6`83V z2mYR#0`lPrkPpGuYzA5L`~H$%P>6rRZ4GKEREDn{^HC; zkTqX$TZ5Xh%kgFGhm0KO&$a*hxP)EcBFG*G%##{UO!)Eh;}Uj*HfV%#a%|%jaMEB7 zV6PGV{`d@=L(CG&+@*ltAxl+=h0^pU`ARjzjzB{{{K+0mzR* zn10;R)(?_5=z;oCfMW;R{eJu)uQJLqH0ZjjGBEVIpq_A{WWce5S4a$}s{|Fcqr2)J zBgd*KGa5fFVHbD-a+MZGS03nQ4$y6gV9cm-^X$5Rppfr_UJA+%auw)`z)VaZwEevU zR@)2pfi*}i=rS|-MSq$c9Ssc}0xuvJ{oP39jeIBC zKYxi^pe*qI?r%`I8BBq?!3Lxd^{%$Btqlze9`Xv@U%!6Aq0c6dw?Z^8;*380f%_kIVKzA!aMKG=5<(M#k#Y~Vj zYrliH_@4j0W-f@k6>jSSkgdP(+@1!qbq;P@Kcd(Qx?2e=tiGY_{l(DVgOaiF`Epd#3;dD${=Dae|maBH@Mta-iV?R=0m3vgQljen>$ zpu3fzBG{~%du;MbkTqxF*6asa({t?8Vvse9a9ab7f2cK}dw-xJ*sN(=ICm|`nyYYY zj)Sb3u;}e_kTpwiTLX=Ms5PKle4rxOtXaLgeM&(gRJ?~dE_w2noYQ^ zfkrsg8qgITP!Vj_9RKj=49J?PaBJFs+-21GaO>4^kTqLyTLX=7s5PKlFQ6jWths;S z=>?EAbK%zXgRE(|_2x9lnr*nPfkrsg8qh@&P!Vj_T>10u3dowJaBHT6tohNt?L5eu z9k{K5MmW?O(A^AB5p349z4&wkWX)Q*HSOBvHbIOkTrX7TLX=7s5PJ)1fU|=ta)?t*#nR@d*Rls2U#<1!QI;+ zYxd!`1{&c|Ye0wcLq)Jz)7|^z3CNnGaBH@Mtl2Vs*L{#R2XI>hjc}+npo8t9BG|0C zy8Gb^kTqxF*6asaGke*t#~^DC;kE`E;ZSQphr>ffuvznS$-OrqYp%krIS#Vs*OS%H zLDn3>Z4ETSq1J#7Sci&Wv*yr|#~(n}+=W|n9%Rjt4VPYntT~3;8fb(=tpOcU4i&*> z&5xDWzksZH3b*Du$eQD)cD(~xa{{+D&3tT~6<8fb(=tpOcQ4Hdy=&8+p8T0qvc{)E>zAZr@e z-uVZz<^pbOpb-wW26TopR0NwfYfhZ#09n%ux8^^{nloM7n?5gLH@Jk`8fb(=tpOcN z3>Cp<&AFD7Js@kQ!mVlld6!Y+cgy8AkTq9uTLX=7s5PLIexV}RteLUy-~^C0bK%zX zgRJRX_^=CP%{AQCKqDM#4e0P(s0cP|_MhH61!T=qxHZ#3*1Yc7(Fd~T25xJh5e~Hm zbRI2K1e-Mn*X)`BvSuyZn)x7Wo}61h31rPJ+}1!N9BK{dh*zixHf#Re-aiLq%~rTI z%R$yOU%WgGWX&Di)<7d1Y7OYrRHz6xYyQvPvH)bwUbr>uLDo#(v2GT~ntQmdfkrsg z8qh(cP!Vj_9Ne&R3CNnGaBH@Mt$BE69>|&pxUGRkIMf=@(VkEdY}U-(y?6!4nzL|g z_Jge1*0W|2$eKsEt${{3)EdxfnNSgI*6et`VGYQdt8ie`OiSnkTrefkrsg z8qfihP!Vj_tbRXb1IU`YaBI$ktXXmW>?)8o&v07Ila9ab7aHuuf_;_N~Y}V|!Hsb)un!j*s z-h-@}{p8OskTvgcTLX=7s5PJ?A)zAJta&_r+7Xa7t-s*)4al0)AI|IpS@Qw6HP8r$ zS_3)~5-Nhtnu$069|u{}3%BM!$eNEgRvrRb^9i>#s7U~HBqUS>n>86KqDM#4d_Tns0cP|X12e%4YFn{+?wSeYrdXbe+^{KAKcbJ zBOGcC=txMY2v%#}|Gy8iW-r{D^&o3@zIb&DWX(U^)<7d1Y7OW}NT>)lYaY%0^%!K$ zQMfhRLDsz8{p}venuc9XpwVC}q=91C2rD$gq1J$ogoKJ7F*C1=I!mT+DvgXRx2hTv(wBWV|8sSiDKu1DC zMX*`(V!_AvAZzZztvL^}=J2*RuRzwc;kE`E;ZSQpM?ykHuvxQk=7-N9Yu>=Exel`C z$jRUDK-P5NwgwvEP-{R(LPABbS#x~JweKKnzQC=y53=S`*W3>vYr1e-1C4N~HJ~#f zp(5CDg>N8hdT?6)lYrZ@>F&Sjd8n`v{K-QdXf87hRW(IC+pb-wW26P4_R0NwflQ-|3 z4zgwo+?r(|Yv!GLHxXpbEZo*WBOGcC=nP1x2sUf3?0G*MWX&G9HS0juG)*}@6=cmE z+}1!N9BK{d3`nR5Hf!FT`Z^zE%@MdY+d$Urn%FQ4WX(L>)<7d1Y7OW_N2mxkYYsGj zT@1434BVQ1AZt#4e?1pu%>vxkKqDM#4d{?Ws0cP|c3s)99AwQExHZQ>*0kQ3xCms; zBHY$MBOGcC=%7NV2sUdjZvLcWHfwh8 zd%PZG%@epa*Fe_1n6_#a$eLxit${{3)Edx9eozr?)@-@;WHZQ`H*jn2fvj2c<;Pl( zH7jsi1C4N~HK4=spd#3;dAs)hc91n+;MP0?S+i$t(y#f~?tq+Zt$uL#+WFGzJyH zX3fGK=Z}M|nF6<_?ay6Cjs6RJ4uY)NgxeZughQuOaocdv~|ZxkTu(ITLX=7s5PJy zrl2C&teLj%#AT2*Yv9(*16lK9=ccnDYj)ta1{&c|Yl88Q3!G@_y$-Tw3*4GzAZs>n z+Hw(O%`V*5KqDM#4d@&ss0cRSJUwynHprSiaBJ3qtl9c>-Bpk^dvIF=jc}+nq4>uI zdj4#^53=S6+?s75Yqss&coSsJKHSzoBOGcC=(HZF2sYpR_`LTq$eJ^7YxaSxxp95N zU63^ga9ab7aHuum_)CG=zt%qoS#t$$%`uQQx4vw82(so7Zfj7J0O(8^s0cRSe0;m^ zHOQJfaBI$iteLWK-BXY?M{rvMjc}-MK*zK|MX*`3ul@HskTp->)?5Qw^QeFGOOQ3k za9e|#1VAUBKt-@wbAQIh&me2wz^%ClvgYa3|8GIooWN}jG{T|20Uag+6~SiB?a6Du zgRJ=ix8@nhnx8wie*{@`3b!?=NdR;{2UG-`HM6Fz_zkk=58Rq}AZzA+I{Ou5%^BR* zKqDOL8_xUGRkIMkXX{1w5xqw^+%tXTuMW**3z58GDtf~>iL z+Zt$uL#+XAa)*jw^Ud0YQ>KHg*#fs_8OWNZb8jbttht5T8fb(=tpV*Dhl*gcX7QmZ zvq9GEfm^c6~W|f{}+L*IRm$5AIO@iOTW$qS@Qt5HK<7dv|$)3g3ULFcl9j?S#t$$ z%`uQQn8n{Hm(L)a|dqCIgm9UmbEVfS@Q(9HP8r$ zS_9hF3KhX-&8t<7>p|8$fm?G8WX;rNJ63?Kd4}5>XoN$p$->_poWJzQCXh96;MUv& zS@Yyl_gauOFK}Cfngl?bFQFpXeDnYE_H7_*zQC<{2C}B-Nz+D^Whua!xghQ1C4N~Z$Nv)pd#3;xiIm~DUdaD;MVkk zta<-?+EI`-UvOIkjc}+npzT&r5p33+KY!;O$eJZ^Yo>v$xqkcO36M44a9ab7aHut) z9ZXOWY}T~B+IVTX7L&%`e>6KqDM#4QQVYR0Nwf*Y3`{1+r!j+?sVDYr5w4Tm@P42e&oQ2!~oz zioYVb-+l2O$eJT?Yqo)`Y24X$6J*Ul+}1!N9BK_{uM1QJn{T>TUwH(w<_z4LeIRQ# z-}!L|WKF|vobxx(2!~n&+Uo)p!DdbKn%-w1Yp%epIR>)k>e0@JAZwa%TLX=7s5KS% zJB817u6YHr<__GNb0BLjPwjmQvZe*MHP8r$T2qO?BDj5X!8?#OPvF*E16i|tde2Lc zHEp=9K}`Zx_>;iPw~Ic3ta$^s<{rqJeQ%rIf~@JlZ4ETSp}wic-yD29Vg5IeHDBP? zJOf#CYWnvNAZxmCTLX=7s5Lbj>;_N~Y)PP}W7;o}HGkmNyaQRYX6}bCAZvPXTZ5Ve zYVjw5MeC>j16kAZA70;pthv&#ci{G%1KBg{=+-45dzRp~2O8;6dpbe( zK!vc`)4BQWT97?Y;PzYt*>maR+7%#smf^Ms8tG7bxD3oEfb3a?+a747L+$AS z*#i~AW>43mhdV*`{DIr^4rI@{)}`9%!UP?db*C0~Nw%&!Nlr_JZtbX}HHI z1un0@f$UlTa{UgFJ?n7W1C4a3J$)d1phDQ}>0R*PAjqB`xIO1VL*I_mAbWP;wg(#NP|<|A#C;> zzkKf@$euHBd-j3sdC|1)Hprd>xb1;PI@F%&AbX%f*zB3w^yn$bo-1&Bj)Cl%wr=Nr zkUfWR+XIbss68`4_CSTO*|VYX{!5TOci{G%1KHC%Yx`r6Jx6fc1C4a3Ju^Y}K!vc` zv-{nRw;+3-!0ovPvS;UxbpDg(bvgZrjo@XF?`u?qZ53=VJZhN4S4z*_v$R4N= zR(md-{t2?@58R%2Aba||H+%-!a|X9P&`5{cGZ$nJR0x|rr(PcV3$mxB5nlg*>^U-j z{dbT(=WyEtjdZ9z^Fa1Mg|OMP`R&QZ@1X7v+@60RdtU9F`v+vt1>E*PBOPkbe2_g* zA#C<6`g^JsWX}}1J#CHm7&XqXzWyI%&n4XUKqDP$&jOG=P$6vgTzqr66J*aExIKL! zdv0F-*Zh46yTKLQ_CO;YYR^KDJy0QR_RQ~`&3EZA(AbS?w+u9DY=NfK%ppg!> zXA#IAs1P=L9{pM{5oFIAxIObg_FO%%xf^894czuXBOPkbVvs#hA#CD@DWX~43 zJp=GGKX!96$eug6?SV!* z)Sjgvd!Rzt?CCl?b1ul9BXE1Rf$aHy<@t1wJ@;_i1C4a3JN+a747L+x1!vIi=J&7OZhde?&N zc>=fR8pxhIr>`yt+4BsyJ{8EhuX6iWDisbn?3(-{M-Ywr=XWP=Yvmkqx!0nj^vggo-$HzhTe8X)IG}58=Yy#N>6~boE z?fDZfg6vrXw`U&6p1C)koCewR1GhcUNQc_98DtMs2%A02x4gRovS$n2o@F3=&Td|P z9%Roi-1a~t9cs@OkUdZ#Z1&uk_2>r3o;`4T)`9H#f9S$xkUf8J+XIbss6AUj_CSTO z+4K7T{W~Cgj==5N2D0bw{oB_;_WZ+b4>Z!D_G|;$0~Nw%PshSL4?y;uf!nhWWY6kl zyKjT+Y1jjq_8~Z~yd7i@o^;S~e(p1nJy+oN90S>N=l!<(AbXl{+XIbssDE~V?12hl z^UsG*7hZzwxdXT79LS!}f3H6R+0%mC9%!UP?b!*k2P%Zkp1mLUz5&_u1a8kYkUbrb zracGQ(}vp~Xrx2!*#)u(Dum6RJ!|%T0NL{fZqGfCJ@0?Le+9Cq1GhcUNQc_98)Oeu z2%9}e&g}UDvgZrjo@XF?{{OuG4rEUkZhN4S4z*_w$R4N=HhVU$d-@Y(&mXuw??Co6 z|6ceRWKR!nd!UgHwP!EL9;gsDd*(gZ@CRg1OEbLw0oiln`MGZ(d-`zO1C4a3J^MiR zK!vc`^Ra(@!w*pZ2X4IX98|}ppg!>XFtdus1P=L?$27?03E{8E zhuU)xWDisbn>~;3wDp4QSpv6b8pxh)J14b+?3sq!9%!UP?KuRp2P%Zko>PApOaR%l z25!$hkUi~f+q*&b%)o6AG}58=90u6~6~boE>W(>6K=y2b+p`R0PvhkieIR>g;kE}F z=}>!)fb4+^VY6q|=6N$f_UwV%vkqj>!o`awgY21u+a747L+v>VvIi=J&7SGav*&>9 zIRdw58_1qD506d**)tEfJ=Qzk7s1P=L?q6B71Z2+@xIM=}_FO;ocpk`}MY!#OMmp4<6Cit_LfGs%`=oOP z$eufJd(MIE>AL)Q5y+k;xb1;PI@F$%AbX%f*zCE|-m?Z|&l9*k*Fg46o&0``IdxJu7hA1C4a3J*PqTK!vc`v*yvi z%^-We!0mYkvggg|o9jUKtio*%G}58=oB`Pb6~boE+Wr5xgY5YOx91(mp8lTwn?Uxg z!EFyT(xLX81=#}?!e-B$qu+Le>}hF%*FPY8`er=Z2C`=zZhN4S4z=eT$R4N=HhaE5 z{k|V$PY>Lle;|9l&V8~AWX}fN_CO;YYR`F)Jy0QR_UwB4@i54qDR6t*TJAAwJluL^ zAIP3fxb1;PI@F#EAbX%f*z7s*`R#F#J#*mp^nvWTGV8%1kUd*)+XIbss67`!_CSTO z*)!wztJ5HRmcZ?q2D0bZ!D_FMwl0~Nw%&%Qg~&x7n)1Gi@$$et$) zkDLP8vjew1&`5{ca~Wh0R0x|rw^y}Z0ok(!ZqG81J&&gzJ_oXA7jAo?kq))z3dkO) z5H@=zpI>wXWX~SBJ?lXBoLg|=63CuCxb1;PI@F%4AbX%f*zCFfyyXtao+EI3wt?)~ zdw0V%kUjfw+XIbss6E#}_CSTO*>mRB|N9_&&cN;22eN0<+0C~=_8h=%4>Z!D_FM6OcVu;PxB?+4Hi0?Kuas zr)TE1M<9ES;I;=E=}>!ag6x3`VY6rFyOuW~d!E4UxdyVQ`QVCYAbXDCwg(#NP=ib_jpF#F~f!p&8 zWKVzB#&;llPT{r(8tG7b?ttup3SqNn`nU7nLH7KC+w%@&&#ks6pFsAU!EFyT(xLX; z1=#}?!e-CLU*~><>}hF**FPY8*3CKm4P?(b-1a~t9cs@#kUdZ#Z1zl=ck(~To*uY8 z|3LO!o^#|E$es(h?SV!*)Smkwd!Rzt?D_NMQ1j0v>;hBZ_O!L$W7K%ibo4LCo=dpx zfkryio(CX%phDQ}*?8bkJIJ0naC`bd_Ut~eukj~n{s*@`&`5{c^AKbYR0x|rZ)fi6 z2HCR&ZqGE3J#+8wXa(7G4YxheNQc_<2xJdb2%9~P^LO@x>{$c1XCBC&-JdUag6z40 z+a747L+yDCvIi=J&7O-7CQSj^vjuL?GLSv5{_pAq*>elGJ^a=MYbwZ| zd${d^Mmp4!~gY1C{VY6q$ z(R+(Q_FRG6a|~q9w#9qrg6w&O+a747L+yD1vIi=J&7KL{S1t$Ha|dqEIgmYn|2$X- zvgZkId!UgHwdWMFvKnO16SzIsK=#b;-MbWI&okWiKqDP$&nu8UP$6vg z%=@@vJ;^a{3Y$eE^7r5<#Mmp4<*C2bKLfGt?_##fb4+^VY8?2#jNchd;Y-fc?YtmZSj?jAbZ~6wg(#NPr(@mX;~;w$!|j<0 zvgh>cEeApNe8O!HGz~!Q`2ey9Dum6Rlbs!>LH4YM+cOtr&x2=cj)Ls@g4-Tw8i3mK z5o8Zk2%9}m=Px)9vS%~go~0mrcJ{4439{!KZhN3<0BX-CkUdZ#Z1(IwIp;FSp51VJ z)`IN0c5ByJkUc+e+XIbss6C%S_CSTO+0%Ap)^(6QhvD{Y1=(|D^@fWedw$`z2O8;6 zd%l3|feK->XaBhww?X!thTF3jWY4CiHCI9Q{K0JxG}58=df9i&smT?O}p3J1=-WE7w2>k zG}58=dx92Lzp6#DDJ_Om*gxelyq(klb0kQ`wgw39RuV%gm z+4CB1&s~r`yEbll3bLmKw>{8EhuZTKWDisbn>`mVPkRrt=QG@%ryzU2%sTiIWKSDz zd!UgHwdWVe9;gsDd;aa8^ciH&Z@4{gLH1mnxA`r|o(|mhKqDP$&u@@DP$6vgG_QaD z4P;MqJFL9^3bJSYugxDp_H^O42O8;6d;WmzfeK->=hf8S-ynOs;r9Fm*>mOBmaiat zdT`qVjdZ9ze?j&@g|OMvFsZ!D_B4Plw1Em?vuE4v?rxAhtKs&{1=+Ll;hI*EJyUSo1C4a3J&hoH zphDQ}>Alt753*-7+@7T%d+yHI)d{j^8g6@_kq)(|31kmc2%9}yHg`@2*|QsN&svZ@ z&n|E61=%wLw>{8EhuYH&vIi=J&7S5RZPP*a9ERJo6=cu3#x)Z`_RPX<4>Z!D_OyWP zfeK->XWx|9vq1KohTF3jWY4v|8>WKnnS6~boE|AsH~K=xdQ+jA6T z&)1DFW`gXQhua=#q(kj#1K9%=!e-Bm?`?}g_S}Zsa~5RJ<(r@9fb3a-+a747L+xn? z*#i~AX3x4^pO%5_c?`GbD#)H46Sgb>*|P|@J{8EhuYH(vIi=J&7Q5>K5PTo)7$~C ze?az}o4joU$evZW?SV!*)Se!YJy0QR_PlC+vkPQTH{71TAbZxI*}Mg0&l=qJKqDP$ zPcO(Gs1P=L{vUX>4`k0|xIL{M_ZT%gn-A^)*|QF}J~>-T`{*?`*~Xrx2!=?B>Z6~boE!Sgqcf$Uifw`VHIp6lCJ9{}023Aa7a zNQc@p0b~zU2%9}Sr#?6ZvS&5ip1B}if^`AZ;scEjyi3$kbH-&JQo_UyoI z4>Z!D_DlxZ0~Nw%&*%MDu7T`147X=1$exB*t1p1;*@fF4Xrx2!nF6v0Dum6Rsb5aq z0@-sKZqHtjJ{8EhuSk6WDisbn>{~YZTtqZr@0ee|A6gzwBiHE zo>RE(fkryio;e_UphDQ}dGcu6FOWUmaC`oO?Ad?!%@>e8XK>pCjdZ9zb3yh%g|OK( z`RuNLAbTdm?P=}2$Efk`?#dq^d(Ppu2O8;6d**@cfeK->=V908rr)6c58R$!kUdZD zFaHCw=K^kfppg!>XFkXts1P=L-ac5@2C`={+@7f*d#=A<*YF#3+#7Ctppg!>X936_ zs1P=LJ|6$l39@H3+@850dwS2VZ2{SH1-CuWNQc_95M&Qj2%9}UZ8Q2n_H2gRvlL{{ z&D-lbK=xe2Z4Wfkq4q2S*#i~AX3w7OZzh85*$ua6E!dvT2YW#F+`w%QG}58=EC$&F z6~boE;if6mK=z!1+p`s9&zofjCxGm^h1(uzq(kjl0eeQ&t8x{ zA7AX71hVH2ZhN4S4z*_~$R4N=HhX^Um^BY%&n>t;XF&GczI<>7$ew$+?SV!*)ShJ^ zd!Rzt?76$CZxP6zM{s+tfb99Oe$5<^Jr8i(1C4a3JAZhN4S4z*_$$R4N=HhZ4lZ`=g3r>P58 zFMI*nGv(;&H6VLl;I;=E=}>!CgY1C{VY6rU-t${Q_H@DR`2(`&P5bT*AbVcnwg(#N zPkI34fb4mP+a747L+x1yvIi=J&7LXi-y8(lvj}d_6p%fe@2=ksvgZSC zd!UgHwP!uZ9;gsDdoE7+d=zBQD!4s!K=$nWv3ftqo=>>#fkryio(&*-phDQ}xpVBz zNsv98;Pxy5+4Jk}=ffa-zTmb88tG7bHiGPd3SqP7z}wGfLH6u|+p`8_Pygnx$3gad z!)*^V(xLWj0@(u^TItXA8)lCzsZq2HEoiw>{8EhuX6lWDisbn>|0y z-Mb31=M>zYJs^ATEZTn_WX~_$_CO;YYR?vsJy0QR_UwOqDgtF zJ%4c91C4a3JzGKcK!vc`b9B+=yC8dR!RXUEoyPeJy)g4=TkWY5=! z&+dclX~Jy}G}58=>;TyV6~boEzq#{Yg6#POx917So;`;?KL**;g4-Twq(kl539<(& zgw38m^Nzg*+4Bo-&l`|Elm32w4zi~Ww>{8EhuX6XWDisbn>`mE9R3Khr>PrW|A6e- zbLZ1*kUbr^?SV!*)SlfSd!Rzt?CJPA{VT|xF1S5^K=xew{^>o)o-W+>KqDP$&mNFH zP$6vgY&bjNC&->jaC=(1?=fopnf&WB$eteD_CO;YYR_JfJy0QR_PqOe;V;OZS#W!L zK=$mt^Wi(lo<7|6KqDP$&pwbnP$6vgtUGwT@y`-=fkkk8rhx4E`@Q84$eszf?SV!* z)Smqyd!Rzt>^XY*a4X23Rd9Refb3aw=>30?J(FnBZyXHTj?LfHgfkryio`WEJphDQ}+1IqI7i7;axIJq?_H6pNsU2j`G~D(; zBOPkbA&@;#A#C=v{@64TWX~bEJzGHbJeu;d8)VN6-1a~t9cs^EkUdZ#Z1!ybwSFqd zo>Opp_JHj9wf=8E$evla?SV!*)Se?Cd!Rzt?D=qe?M#q8m*Dms0on8P$G6EKd*XWo^Kb3yjpg4=TjWY2=5-7`S;%)@ODG}58=90S<{6~boEi|w-( zg6w$&x91ATp3`^E%?8=C0JlBRNQc^U9Apnv2%9~(mM&QevgZ}to;x6WelL4FA7sxW z-1a~t9cs@BkUdZ#Z1(K@xoIWHo=#lfb4+^VY6q$ z&1E}5_Dq7?)6#Q~QRC9NSDQihtif#$G}58=oCVnf6~boEzxK&{LH5jo+tUNG=g<2s z+d=lM!)*^V(xLX81K9%=!e-C(H~j}e_AG+iGX-SN`{f&UgY4OW+a747L+v>avIi=J z&7OtJyN`nGSp~Oe4#=L){-y&Udp6;=2O8;6doF=k}xDCqVXWg4?qMWY4O5 z9}a`;*@D|1Xrx2!xd^fcDum6R-X&Mgfb7`?w`UE=o;_#390%F64YxheNQc^U31kmc z2%A0YKYzObvgZ)oo-H7I&U|SES#Pidw>{8EhuU))WDisbn>~-F{l5aT=M>zYJs^7y zOl|~OZ?FruJ#FgY1C{VYBDX?7vSy_Pm1Ia|dKk%jYxqK=vHMZ4Wfkq4wMW z*#i~AX3yEzFJ6G``2@G;3CN!F-?l#j*>eQ9JZjvgh5`+pj?OoWN}lG}58= z+y>bL6~boE!`C0afb8jl+w%uxPw%Y9??CpP!fg*U(xLX;0oel;!e-C=|DS$pp?(IfL6CXrx2!xeKxfDum6RL+_sb0ogMPZch)$o~sj2eFNEZ4!1qf zNQc^U4`dHi2%9}SK0j#q3+n&C?U@3yXY!owzd-g}z-4K!vc`bHC}v z1du(4;Pz|*+0)T^p$lZs4czuXBOPkbV~{;iA#C>Sx^iL)$evShd-j0rIkf0-AIP3t zxb1;PI@F#gAbX%f*zDQ&@cayrJ(uA290A$$e%+@@AbalMwg(#NPa?74^A9%!UP?Rf^W2P%Zko}2SeE&$o{2yV|6kUe*g-=Q+q8s1R0r_MBJ(vgZ}to;x6Wp1prD4`k0H-1a~t9cs@DkUdZ#Z1z0q z?_2@0=M&tXCm?&~e*L-#WX}`a_CO;YYR^lMJy0QR_T1aGVGYQhUvPWgfb6-w{rxhK zJ-P129P~Xeen7RWKZ9>FRMWIyufV_G}58=yaw3=6~boE z!-H$Lfb8jl+w%ux&$9Pl)`9GKh1(uzq(kj_1F{Dygw3A!r@w3m*)s`lPfOoDMvc$k z|7`-<^9Hv)&`5{c^A=82HEogw>{8EhuZTVWDisbn>`y3FFXRWXBFI@IUsxf z?)kkBWX~tu_CO;YYR?CdJy0QR_MCk<ldbfb4m>YvN&$JzsF!1C4a3Js&~# zK!vc`vwY2*Ga!3*!R=WCvS(X=_i>Ot-*DRljdZ9zpFs9Ng|OK(<>~YbAbSqM?b!md zXZDe^r$F}nz-^TLuXAj7pedn6bgY5Z*+a747L+$wj zvIi=J&7P|(Cfoqoa|v$G5s*DC7aBp<8~nj-4>Z!D_Iw4|0~Nw%&&MlmcR=>sg4=Tj zWY4_Ieb+($`G?ycXrx2!`3ABFDum6RC1)o;0NL{hZqF5vJ<}gF+y>dxupj6A4>Z!D z_IwA~0~Nw%Pv@;kPeAs(g4=TkWY6TT?)xBnnsD0#jdZ9zKS1_Cg|ON4>}K~1kUgK^ z_B;XE({ldTBal5Uxb1;PI@F$@AbX%f*zCE!t>X>Io?mc#-hk}sI=baK$euRb_CO;Y zYR@l_Jy0QR_8jf*{Q$D3sUKedfb5yH;m<3OJsr61fkryip5GvQphDQ}`Tn}+3&@@> zxIKSB_S}E}`7OwvF5LD&BOPkbACNszA#CPE@WX~kHJuUtB7&UI(?)wC?rw6w^ z&`5{c^A}_fR0x|r-7nw#2H7(UZch)$p11SoegoOlhua=#q(klb2eJn$gw39lb3XnD z*|P|4&lHe7-{8EhuZTWWDisbn>|Y={A~WWgk4}2+@3igd-iPj`WIx+ zB;58uBOPi_12ew6n$kF~-~QSTvS$x?D=)Adm6}| zdARL?Mmp3#Z6JG~LfGt?*fM`U$eu@Vd#-@&dG%n%ERa15aN7fobf`V;AbX%f*zD;$ z{9rN2o>y>t?ttuR`uSxp$eu;G?SV!*)SeEIJy0QR_B?v`VmZj3PjGvlfb99!_iZ7_ zo+Y^Lfkryio=%WGP$6vgv~TZQ1G48A+@3cedk)WgxfEp2GTin+BOPi_7swu{5H@?( zEI+&+WKYusc>M#i=ko5WD?#?Gz-*&u_H4jy4>Z!D_Vk17feK->=k4|xhe7tNg4;6(WY4A}yY_{=XuZE>mYkB!RecDJ zXC}xVs1P=LrmpLG4YKDK+@3ced;WiY^Au#yG2He*BOPkbERa1=A#C=Xcs$`f$eyN& z@cIX2&(~vbUxMs8f!iKvq(kkQ4YCI+gw3Arf9;<^_H@DR`2(`2?dX}eAbU>Xwg(#N zP#-g6x3`VY6q!rOUrS z_RNCY(*v^S``3+MLH3-(Z4Wfkq4vxJ*#i~AX3wF&zy5*jSp>Id3do-OE7tx5*>eH6 zJDdoJO&2O8;6dlrD~feK->=iKDe zZ6JF#!R=WBvZryu#>W4k`5)Z&KqDP$&q9zrP$6vg{9WGJ4YFq!+@3Wcds&wcF!*>ebP&lZq9>$k1y0NHZ`w>{8EhuX6kWDisbn>};R zewqZb=M>zYJs^Ab9A4Q2vga0Vd!UgHwPy*)9;gsDdnWAuKMiEhCAd9DK=#bMwrT>% zo;$ehfkryio~0mrphDQ}Xx?0I*6!?gY1C{VY6q#@*Rsn_Pm1Ia|dM4%nj@3 zfb4mM+a747L+x1svIi=J&7RkH-Yx^#^9gRx6OcU@)^1n;vgZkId!UgHwPz*B9;gsD zd*;4dw+dv>FStE#K=!d@wphDQ}x%}wFI*>h0li>9a$ezDj zHmm^I^8&X$&`5{cvl?U%R0x|rix%D71hS_KZqFZ(JtscBT?4Y`6>fW=kq)(I4agp- z5H@?hPdc{^WX~kHJuQ>&F>3ssv10?so;SGdfkryip0yx*phDQ}nYroOE|5L5;P&)@ z?3vc|dJD*&cew3=Mmp4#t zgY1C{VYBDK-!q3m_N;>2GY4eP*+m=ofb98%+a747L+#lBvIi=J&7M22jvfQqvk7j` z5|BNc7j8HJvgZqKd!UgHwPz#99;gsDdnWuoaSCM5F1S5wK=#c3v+W4To^QDAfkryi zo=qTophDQ}X}HyY4rI?ExIJ4y_AH(I^#sVCAGqy-Mmp4<%^-WALfGs%`gHXrkUgj1 z_Ur-K^Sg1&8IV1{aN7fobf`UBK=wd|u-S9=?$&D{doIE4IRdh$>+H@8AbbAcwg(#N zPZ!D_G|;$0~Nw%&w*ny#4{%^K{Sa7a)5& zaN7fobf`VMLH0m}u-S9<_qtCYd%EEE`~lfB_rm5kAbYxS+XIbss6Bf?_CSTO*)!+c znr|R`Cc*7#nS75?qkY}l4gf(zi`tR zkUf34?SV!*)Si7Hd!Rzt>^Z)E=|7M?i{SQ50oil1_4N;sJri)-1C4a3J^MlSK!vc` zvtaq+riP{L0;}Nm%mLYRckjnPAbTd^wg(#NP1`l;Ho@�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 I1H=^=0g1}0asU7T literal 0 HcmV?d00001 -- GitLab From d96101daa56de3e0f9e4f13cc36851ceb8115685 Mon Sep 17 00:00:00 2001 From: Pablo Gamito Date: Wed, 29 Mar 2023 16:16:19 +0000 Subject: [PATCH 1065/1310] Save transition trace in bugreports Bug: 275331993 Test: adb bugreport Ignore-AOSP-First: Trace not available in AOSP yet Change-Id: I857946f6194cf675132fb0ee7d4e592b2ec2aa3a --- cmds/dumpstate/dumpstate.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp index baf8e424c3..1403b77652 100644 --- a/cmds/dumpstate/dumpstate.cpp +++ b/cmds/dumpstate/dumpstate.cpp @@ -3361,8 +3361,7 @@ void Dumpstate::MaybeSnapshotUiTraces() { "WMShell", "protolog", "save-for-bugreport"}, CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build()); - // Currently WindowManagerService and InputMethodManagerSerivice support WinScope protocol. - for (const auto& service : {"input_method", "window"}) { + for (const auto& service : {"input_method", "window", "window shell"}) { RunCommand( // Empty name because it's not intended to be classified as a bugreport section. // Actual tracing files can be found in "/data/misc/wmtrace/" in the bugreport. -- GitLab From 194ff39de17ef4596b2b6ce1520516d7cc10fde6 Mon Sep 17 00:00:00 2001 From: Josh Gao Date: Thu, 8 Sep 2022 16:19:29 -0700 Subject: [PATCH 1066/1310] SurfaceFlinger: add more thread-safety annotations. As part of debugging a crash in SurfaceFlinger, we became suspicious of markLayerPendingRemovalLocked because it wasn't annotated with thread safety annotations. This ended up being a red herring, because mStateLock is held on all paths into that function. Add some helpers to MutexUtils.h to require/assert mutexes, and use them to sprinkle some thread safety annotations to make things more clear. Cherry-picked from aosp/2212955 Bug: 271644537 Test: m surfaceflinger Change-Id: Ib48433765bb6ffcb351ec10cd5bc89e254b48545 --- services/surfaceflinger/Layer.cpp | 13 +++++++++---- services/surfaceflinger/Layer.h | 15 ++++++++------- services/surfaceflinger/MutexUtils.h | 10 ++++++++++ services/surfaceflinger/SurfaceFlinger.cpp | 3 +++ services/surfaceflinger/SurfaceFlinger.h | 2 +- 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 755e58521d..8dec57bf3c 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -75,6 +75,7 @@ #include "FrontEnd/LayerCreationArgs.h" #include "FrontEnd/LayerHandle.h" #include "LayerProtoHelper.h" +#include "MutexUtils.h" #include "SurfaceFlinger.h" #include "TimeStats/TimeStats.h" #include "TunnelModeEnabledReporter.h" @@ -305,10 +306,12 @@ void Layer::onRemovedFromCurrentState() { auto layersInTree = getRootLayer()->getLayersInTree(LayerVector::StateSet::Current); std::sort(layersInTree.begin(), layersInTree.end()); - traverse(LayerVector::StateSet::Current, [&](Layer* layer) { - layer->removeFromCurrentState(); - layer->removeRelativeZ(layersInTree); - }); + REQUIRE_MUTEX(mFlinger->mStateLock); + traverse(LayerVector::StateSet::Current, + [&](Layer* layer) REQUIRES(layer->mFlinger->mStateLock) { + layer->removeFromCurrentState(); + layer->removeRelativeZ(layersInTree); + }); } void Layer::addToCurrentState() { @@ -1009,10 +1012,12 @@ bool Layer::setBackgroundColor(const half3& color, float alpha, ui::Dataspace da mFlinger->mLayersAdded = true; // set up SF to handle added color layer if (isRemovedFromCurrentState()) { + MUTEX_ALIAS(mFlinger->mStateLock, mDrawingState.bgColorLayer->mFlinger->mStateLock); mDrawingState.bgColorLayer->onRemovedFromCurrentState(); } mFlinger->setTransactionFlags(eTransactionNeeded); } else if (mDrawingState.bgColorLayer && alpha == 0) { + MUTEX_ALIAS(mFlinger->mStateLock, mDrawingState.bgColorLayer->mFlinger->mStateLock); mDrawingState.bgColorLayer->reparent(nullptr); mDrawingState.bgColorLayer = nullptr; return true; diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 1af648a20f..0bfab7cc77 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -290,7 +290,7 @@ public: virtual bool setMetadata(const LayerMetadata& data); virtual void setChildrenDrawingParent(const sp&); - virtual bool reparent(const sp& newParentHandle); + virtual bool reparent(const sp& newParentHandle) REQUIRES(mFlinger->mStateLock); virtual bool setColorTransform(const mat4& matrix); virtual mat4 getColorTransform() const; virtual bool hasColorTransform() const; @@ -316,7 +316,8 @@ public: bool setSidebandStream(const sp& /*sidebandStream*/); bool setTransactionCompletedListeners(const std::vector>& /*handles*/, bool willPresent); - virtual bool setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace); + virtual bool setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace) + REQUIRES(mFlinger->mStateLock); virtual bool setColorSpaceAgnostic(const bool agnostic); virtual bool setDimmingEnabled(const bool dimmingEnabled); virtual bool setDefaultFrameRateCompatibility(FrameRateCompatibility compatibility); @@ -641,13 +642,13 @@ public: /* * Remove from current state and mark for removal. */ - void removeFromCurrentState(); + void removeFromCurrentState() REQUIRES(mFlinger->mStateLock); /* * called with the state lock from a binder thread when the layer is * removed from the current list to the pending removal list */ - void onRemovedFromCurrentState(); + void onRemovedFromCurrentState() REQUIRES(mFlinger->mStateLock); /* * Called when the layer is added back to the current state list. @@ -880,6 +881,9 @@ public: mTransformHint = transformHint; } + // Exposed so SurfaceFlinger can assert that it's held + const sp mFlinger; + protected: // For unit tests friend class TestableSurfaceFlinger; @@ -941,9 +945,6 @@ protected: */ std::pair getInputBounds(bool fillParentBounds) const; - // constant - sp mFlinger; - bool mPremultipliedAlpha{true}; const std::string mName; const std::string mTransactionName{"TX - " + mName}; diff --git a/services/surfaceflinger/MutexUtils.h b/services/surfaceflinger/MutexUtils.h index f8be6f3b85..58f7cb4c43 100644 --- a/services/surfaceflinger/MutexUtils.h +++ b/services/surfaceflinger/MutexUtils.h @@ -50,4 +50,14 @@ struct SCOPED_CAPABILITY TimedLock { const status_t status; }; +// Require, under penalty of compilation failure, that the compiler thinks that a mutex is held. +#define REQUIRE_MUTEX(expr) ([]() REQUIRES(expr) {})() + +// Tell the compiler that we know that a mutex is held. +#define ASSERT_MUTEX(expr) ([]() ASSERT_CAPABILITY(expr) {})() + +// Specify that one mutex is an alias for another. +// (e.g. SurfaceFlinger::mStateLock and Layer::mFlinger->mStateLock) +#define MUTEX_ALIAS(held, alias) (REQUIRE_MUTEX(held), ASSERT_MUTEX(alias)) + } // namespace android diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 9ceb0afa20..a538335bff 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4769,6 +4769,7 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime } return 0; } + MUTEX_ALIAS(mStateLock, layer->mFlinger->mStateLock); ui::LayerStack oldLayerStack = layer->getLayerStack(LayerVector::StateSet::Current); @@ -7739,6 +7740,7 @@ void SurfaceFlinger::handleLayerCreatedLocked(const LayerCreatedState& state, Vs ALOGD("Layer was destroyed soon after creation %p", state.layer.unsafe_get()); return; } + MUTEX_ALIAS(mStateLock, layer->mFlinger->mStateLock); sp parent; bool addToRoot = state.addToRoot; @@ -7916,6 +7918,7 @@ bool SurfaceFlinger::commitMirrorDisplays(VsyncId vsyncId) { { Mutex::Autolock lock(mStateLock); createEffectLayer(mirrorArgs, &unused, &childMirror); + MUTEX_ALIAS(mStateLock, childMirror->mFlinger->mStateLock); childMirror->setClonedChild(layer->createClone()); childMirror->reparent(mirrorDisplay.rootHandle); } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 74d00dd60c..834c8b68ff 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -797,7 +797,7 @@ private: status_t mirrorDisplay(DisplayId displayId, const LayerCreationArgs& args, gui::CreateSurfaceResult& outResult); - void markLayerPendingRemovalLocked(const sp& layer); + void markLayerPendingRemovalLocked(const sp& layer) REQUIRES(mStateLock); // add a layer to SurfaceFlinger status_t addClientLayer(LayerCreationArgs& args, const sp& handle, -- GitLab From 847e85100dfa25f8b6911b6a2409f460b12c4bfc Mon Sep 17 00:00:00 2001 From: Chavi Weingarten Date: Wed, 29 Mar 2023 00:26:09 +0000 Subject: [PATCH 1067/1310] Add focusTransferTarget in WindowInfo A transfer focus request was originally added on FocusRequest object. This meant when a host wanted to provide focus to an embedded window, it would request to transfer focus, but provide its own token as the focusedToken in the request. In FocusResolver, it would ensure that the focusedToken was current focus before allow the request to proceed. This worked in some cases, but created races where clients had to understand timing in order to properly transfer focus. The new code creates a persistent focusTransferTarget value on the WindowInfo object. The host will add the embedded token as part of its own WindowInfo#focusTransferTarget. When FocusResolver determines that a window should gain focus, it will check to see if there is any focusTransferTarget value set. If there is, it will attempt to give focus to that window, if possible. Otherwise, it will fallback to the previous window in the chain. This solves a few issues with embedded windows. 1. Apps can request focus to the embedded by requesting focus to the SV that hosts the embedded. However, if they request focus too early, it will drop the request since the host has not yet gained focus. With the current code, it will transfer focus to the embedded once the host could have gained focus. If the host wants to revoke, the focusTransferTarget can be set to null, which will give the host focus again. 2. This fixes the issue if another window becomes focus. When WM gives focus back to the host window, it should automatically give focus to the embedded if it was last requested. The app shouldn't be required to maintain the last state of the embedded focus to see if it needs to transfer it again. It's actually not even possible once focus can be given to embedded via touch since only WM and Input know about this. By adding focusTransferTarget to WindowInfo, the focusedToken value in FocusRequest can be removed, as well. Test: InputDispatcher_test Test: FocusResolver_test Test: WindowInfo_test Bug: 230340812 Change-Id: I252403184f7060c37c82353638ac6ee25a0979fc --- libs/gui/WindowInfo.cpp | 5 +- libs/gui/android/gui/FocusRequest.aidl | 9 -- libs/gui/include/gui/WindowInfo.h | 5 + libs/gui/tests/EndToEndNativeInputTest.cpp | 2 - libs/gui/tests/WindowInfo_test.cpp | 2 + .../inputflinger/dispatcher/FocusResolver.cpp | 104 ++++++++++------- .../inputflinger/dispatcher/FocusResolver.h | 8 +- .../inputflinger/tests/FocusResolver_test.cpp | 110 ++++++++++++++---- .../tests/InputDispatcher_test.cpp | 23 ++-- 9 files changed, 183 insertions(+), 85 deletions(-) diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp index 804ce4fac0..6df9ff1664 100644 --- a/libs/gui/WindowInfo.cpp +++ b/libs/gui/WindowInfo.cpp @@ -125,6 +125,7 @@ status_t WindowInfo::writeToParcel(android::Parcel* parcel) const { parcel->writeBool(replaceTouchableRegionWithCrop) ?: parcel->writeStrongBinder(touchableRegionCropHandle.promote()) ?: parcel->writeStrongBinder(windowToken); + parcel->writeStrongBinder(focusTransferTarget); // clang-format on return status; } @@ -175,7 +176,9 @@ status_t WindowInfo::readFromParcel(const android::Parcel* parcel) { parcel->read(touchableRegion) ?: parcel->readBool(&replaceTouchableRegionWithCrop) ?: parcel->readNullableStrongBinder(&touchableRegionCropHandleSp) ?: - parcel->readNullableStrongBinder(&windowToken); + parcel->readNullableStrongBinder(&windowToken) ?: + parcel->readNullableStrongBinder(&focusTransferTarget); + // clang-format on if (status != OK) { diff --git a/libs/gui/android/gui/FocusRequest.aidl b/libs/gui/android/gui/FocusRequest.aidl index b13c60049c..62d1b68147 100644 --- a/libs/gui/android/gui/FocusRequest.aidl +++ b/libs/gui/android/gui/FocusRequest.aidl @@ -23,15 +23,6 @@ parcelable FocusRequest { */ @nullable IBinder token; @utf8InCpp String windowName; - /** - * The token that the caller expects currently to be focused. If the - * specified token does not match the currently focused window, this request will be dropped. - * If the specified focused token matches the currently focused window, the call will succeed. - * Set this to "null" if this call should succeed no matter what the currently focused token - * is. - */ - @nullable IBinder focusedToken; - @utf8InCpp String focusedWindowName; /** * SYSTEM_TIME_MONOTONIC timestamp in nanos set by the client (wm) when requesting the focus * change. This determines which request gets precedence if there is a focus change request diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h index b01a3db52d..70b2ee8e32 100644 --- a/libs/gui/include/gui/WindowInfo.h +++ b/libs/gui/include/gui/WindowInfo.h @@ -236,6 +236,11 @@ struct WindowInfo : public Parcelable { Type layoutParamsType = Type::UNKNOWN; ftl::Flags layoutParamsFlags; + // The input token for the window to which focus should be transferred when this input window + // can be successfully focused. If null, this input window will not transfer its focus to + // any other window. + sp focusTransferTarget; + void setInputConfig(ftl::Flags config, bool value); void addTouchableRegion(const Rect& region); diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp index 5f80c16f61..9e8c65c678 100644 --- a/libs/gui/tests/EndToEndNativeInputTest.cpp +++ b/libs/gui/tests/EndToEndNativeInputTest.cpp @@ -272,8 +272,6 @@ public: FocusRequest request; request.token = mInputInfo.token; request.windowName = mInputInfo.name; - request.focusedToken = nullptr; - request.focusedWindowName = ""; request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC); request.displayId = displayId; t.setFocusedWindow(request); diff --git a/libs/gui/tests/WindowInfo_test.cpp b/libs/gui/tests/WindowInfo_test.cpp index c51b244c50..11b87efda7 100644 --- a/libs/gui/tests/WindowInfo_test.cpp +++ b/libs/gui/tests/WindowInfo_test.cpp @@ -71,6 +71,7 @@ TEST(WindowInfo, Parcelling) { i.applicationInfo.name = "ApplicationFooBar"; i.applicationInfo.token = new BBinder(); i.applicationInfo.dispatchingTimeoutMillis = 0x12345678ABCD; + i.focusTransferTarget = new BBinder(); Parcel p; i.writeToParcel(&p); @@ -101,6 +102,7 @@ TEST(WindowInfo, Parcelling) { ASSERT_EQ(i.replaceTouchableRegionWithCrop, i2.replaceTouchableRegionWithCrop); ASSERT_EQ(i.touchableRegionCropHandle, i2.touchableRegionCropHandle); ASSERT_EQ(i.applicationInfo, i2.applicationInfo); + ASSERT_EQ(i.focusTransferTarget, i2.focusTransferTarget); } TEST(InputApplicationInfo, Parcelling) { diff --git a/services/inputflinger/dispatcher/FocusResolver.cpp b/services/inputflinger/dispatcher/FocusResolver.cpp index 4da846bcf8..0e4e79e88e 100644 --- a/services/inputflinger/dispatcher/FocusResolver.cpp +++ b/services/inputflinger/dispatcher/FocusResolver.cpp @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include #define LOG_TAG "InputDispatcher" #define ATRACE_TAG ATRACE_TAG_INPUT @@ -25,6 +26,7 @@ #include #include #include +#include #include "DebugConfig.h" #include "FocusResolver.h" @@ -34,6 +36,11 @@ using android::gui::WindowInfoHandle; namespace android::inputdispatcher { +template +struct SpHash { + size_t operator()(const sp& k) const { return std::hash()(k.get()); } +}; + sp FocusResolver::getFocusedWindowToken(int32_t displayId) const { auto it = mFocusedWindowTokenByDisplay.find(displayId); return it != mFocusedWindowTokenByDisplay.end() ? it->second.second : nullptr; @@ -54,30 +61,30 @@ std::optional FocusResolver::setInputWindows( int32_t displayId, const std::vector>& windows) { std::string removeFocusReason; - // Check if the currently focused window is still focusable. + const std::optional request = getFocusRequest(displayId); const sp currentFocus = getFocusedWindowToken(displayId); - if (currentFocus) { - Focusability result = isTokenFocusable(currentFocus, windows); - if (result == Focusability::OK) { - return std::nullopt; - } - removeFocusReason = ftl::enum_string(result); - } - // We don't have a focused window or the currently focused window is no longer focusable. Check - // to see if we can grant focus to the window that previously requested focus. - const std::optional request = getFocusRequest(displayId); + // Find the next focused token based on the latest FocusRequest. If the requested focus window + // cannot be focused, focus will be removed. if (request) { sp requestedFocus = request->token; - const Focusability result = isTokenFocusable(requestedFocus, windows); + sp resolvedFocusWindow; + Focusability result = getResolvedFocusWindow(requestedFocus, windows, resolvedFocusWindow); + if (result == Focusability::OK && resolvedFocusWindow->getToken() == currentFocus) { + return std::nullopt; + } const Focusability previousResult = mLastFocusResultByDisplay[displayId]; mLastFocusResultByDisplay[displayId] = result; if (result == Focusability::OK) { + LOG_ALWAYS_FATAL_IF(!resolvedFocusWindow, + "Focused window should be non-null when result is OK!"); return updateFocusedWindow(displayId, "Window became focusable. Previous reason: " + ftl::enum_string(previousResult), - requestedFocus, request->windowName); + resolvedFocusWindow->getToken(), + resolvedFocusWindow->getName()); } + removeFocusReason = ftl::enum_string(result); } // Focused window is no longer focusable and we don't have a suitable focus request to grant. @@ -96,35 +103,18 @@ std::optional FocusResolver::setFocusedWindow( return std::nullopt; } - // Handle conditional focus requests, i.e. requests that have a focused token. These requests - // are not persistent. If the window is no longer focusable, we expect focus to go back to the - // previously focused window. - if (request.focusedToken) { - if (currentFocus != request.focusedToken) { - ALOGW("setFocusedWindow %s on display %" PRId32 - " ignored, reason: focusedToken %s is not focused", - request.windowName.c_str(), displayId, request.focusedWindowName.c_str()); - return std::nullopt; - } - Focusability result = isTokenFocusable(request.token, windows); - if (result == Focusability::OK) { - return updateFocusedWindow(displayId, "setFocusedWindow with focus check", - request.token, request.windowName); - } - ALOGW("setFocusedWindow %s on display %" PRId32 " ignored, reason: %s", - request.windowName.c_str(), displayId, ftl::enum_string(result).c_str()); - return std::nullopt; - } - - Focusability result = isTokenFocusable(request.token, windows); + sp resolvedFocusWindow; + Focusability result = getResolvedFocusWindow(request.token, windows, resolvedFocusWindow); // Update focus request. The focus resolver will always try to handle this request if there is // no focused window on the display. mFocusRequestByDisplay[displayId] = request; mLastFocusResultByDisplay[displayId] = result; if (result == Focusability::OK) { - return updateFocusedWindow(displayId, "setFocusedWindow", request.token, - request.windowName); + LOG_ALWAYS_FATAL_IF(!resolvedFocusWindow, + "Focused window should be non-null when result is OK!"); + return updateFocusedWindow(displayId, "setFocusedWindow", resolvedFocusWindow->getToken(), + resolvedFocusWindow->getName()); } // The requested window is not currently focusable. Wait for the window to become focusable @@ -134,11 +124,43 @@ std::optional FocusResolver::setFocusedWindow( nullptr); } +FocusResolver::Focusability FocusResolver::getResolvedFocusWindow( + const sp& token, const std::vector>& windows, + sp& outFocusableWindow) { + sp curFocusCandidate = token; + bool focusedWindowFound = false; + + // Keep track of all windows reached to prevent a cyclical transferFocus request. + std::unordered_set, SpHash> tokensReached; + + while (curFocusCandidate != nullptr && tokensReached.count(curFocusCandidate) == 0) { + tokensReached.emplace(curFocusCandidate); + Focusability result = isTokenFocusable(curFocusCandidate, windows, outFocusableWindow); + if (result == Focusability::OK) { + LOG_ALWAYS_FATAL_IF(!outFocusableWindow, + "Focused window should be non-null when result is OK!"); + focusedWindowFound = true; + // outFocusableWindow has been updated by isTokenFocusable to contain + // the window info for curFocusCandidate. See if we can grant focus + // to the token that it wants to transfer its focus to. + curFocusCandidate = outFocusableWindow->getInfo()->focusTransferTarget; + } + + // If the initial token is not focusable, return early with the failed result. + if (!focusedWindowFound) { + return result; + } + } + + return focusedWindowFound ? Focusability::OK : Focusability::NO_WINDOW; +} + FocusResolver::Focusability FocusResolver::isTokenFocusable( - const sp& token, const std::vector>& windows) { + const sp& token, const std::vector>& windows, + sp& outFocusableWindow) { bool allWindowsAreFocusable = true; - bool visibleWindowFound = false; bool windowFound = false; + sp visibleWindowHandle = nullptr; for (const sp& window : windows) { if (window->getToken() != token) { continue; @@ -146,7 +168,7 @@ FocusResolver::Focusability FocusResolver::isTokenFocusable( windowFound = true; if (!window->getInfo()->inputConfig.test(gui::WindowInfo::InputConfig::NOT_VISIBLE)) { // Check if at least a single window is visible. - visibleWindowFound = true; + visibleWindowHandle = window; } if (window->getInfo()->inputConfig.test(gui::WindowInfo::InputConfig::NOT_FOCUSABLE)) { // Check if all windows with the window token are focusable. @@ -161,10 +183,12 @@ FocusResolver::Focusability FocusResolver::isTokenFocusable( if (!allWindowsAreFocusable) { return Focusability::NOT_FOCUSABLE; } - if (!visibleWindowFound) { + if (!visibleWindowHandle) { return Focusability::NOT_VISIBLE; } + // Only set the outFoundWindow if the window can be focused + outFocusableWindow = visibleWindowHandle; return Focusability::OK; } diff --git a/services/inputflinger/dispatcher/FocusResolver.h b/services/inputflinger/dispatcher/FocusResolver.h index 6d11a77aad..5bb157b7c6 100644 --- a/services/inputflinger/dispatcher/FocusResolver.h +++ b/services/inputflinger/dispatcher/FocusResolver.h @@ -92,7 +92,13 @@ private: // static Focusability isTokenFocusable( const sp& token, - const std::vector>& windows); + const std::vector>& windows, + sp& outFocusableWindow); + + static FocusResolver::Focusability getResolvedFocusWindow( + const sp& token, + const std::vector>& windows, + sp& outFocusableWindow); // Focus tracking for keys, trackball, etc. A window token can be associated with one or // more InputWindowHandles. If a window is mirrored, the window and its mirror will share diff --git a/services/inputflinger/tests/FocusResolver_test.cpp b/services/inputflinger/tests/FocusResolver_test.cpp index ccdb37afb0..5440a98db6 100644 --- a/services/inputflinger/tests/FocusResolver_test.cpp +++ b/services/inputflinger/tests/FocusResolver_test.cpp @@ -237,7 +237,7 @@ TEST(FocusResolverTest, FocusRequestsArePersistent) { ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken); } -TEST(FocusResolverTest, ConditionalFocusRequestsAreNotPersistent) { +TEST(FocusResolverTest, FocusTransferTarget) { sp hostWindowToken = sp::make(); std::vector> windows; @@ -247,47 +247,117 @@ TEST(FocusResolverTest, ConditionalFocusRequestsAreNotPersistent) { windows.push_back(hostWindow); sp embeddedWindowToken = sp::make(); sp embeddedWindow = - sp::make("Embedded Window", embeddedWindowToken, /*focusable=*/true, + sp::make("Embedded Window", embeddedWindowToken, /*focusable=*/false, /*visible=*/true); windows.push_back(embeddedWindow); FocusRequest request; request.displayId = 42; request.token = hostWindowToken; + + // Host wants to transfer touch to embedded. + hostWindow->editInfo()->focusTransferTarget = embeddedWindowToken; + FocusResolver focusResolver; std::optional changes = focusResolver.setFocusedWindow(request, windows); + // Embedded was not focusable so host gains focus. ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ hostWindowToken); - request.focusedToken = hostWindow->getToken(); - request.token = embeddedWindowToken; - changes = focusResolver.setFocusedWindow(request, windows); + // Embedded is now focusable so will gain focus + embeddedWindow->setFocusable(true); + changes = focusResolver.setInputWindows(request.displayId, windows); ASSERT_FOCUS_CHANGE(changes, /*from*/ hostWindowToken, /*to*/ embeddedWindowToken); - embeddedWindow->setFocusable(false); + // Embedded is not visible so host will get focus + embeddedWindow->setVisible(false); changes = focusResolver.setInputWindows(request.displayId, windows); - // The embedded window is no longer focusable, provide focus back to the original focused - // window. ASSERT_FOCUS_CHANGE(changes, /*from*/ embeddedWindowToken, /*to*/ hostWindowToken); - embeddedWindow->setFocusable(true); + // Embedded is now visible so will get focus + embeddedWindow->setVisible(true); changes = focusResolver.setInputWindows(request.displayId, windows); - // The embedded window is focusable again, but we it cannot gain focus unless there is another - // focus request. - ASSERT_FALSE(changes); + ASSERT_FOCUS_CHANGE(changes, /*from*/ hostWindowToken, /*to*/ embeddedWindowToken); - embeddedWindow->setVisible(false); - changes = focusResolver.setFocusedWindow(request, windows); - // If the embedded window is not visible/focusable, then we do not grant it focus and the - // request is dropped. - ASSERT_FALSE(changes); + // Remove focusTransferTarget from host. Host will gain focus. + hostWindow->editInfo()->focusTransferTarget = nullptr; + changes = focusResolver.setInputWindows(request.displayId, windows); + ASSERT_FOCUS_CHANGE(changes, /*from*/ embeddedWindowToken, /*to*/ hostWindowToken); - embeddedWindow->setVisible(true); + // Set invalid token for focusTransferTarget. Host will remain focus + hostWindow->editInfo()->focusTransferTarget = sp::make(); changes = focusResolver.setInputWindows(request.displayId, windows); - // If the embedded window becomes visble/focusable, nothing changes since the request has been - // dropped. ASSERT_FALSE(changes); } + +TEST(FocusResolverTest, FocusTransferMultipleInChain) { + sp hostWindowToken = sp::make(); + std::vector> windows; + + sp hostWindow = + sp::make("Host Window", hostWindowToken, /*focusable=*/true, + /*visible=*/true); + windows.push_back(hostWindow); + sp embeddedWindowToken = sp::make(); + sp embeddedWindow = + sp::make("Embedded Window", embeddedWindowToken, /*focusable=*/true, + /*visible=*/true); + windows.push_back(embeddedWindow); + + sp embeddedWindowToken2 = sp::make(); + sp embeddedWindow2 = + sp::make("Embedded Window2", embeddedWindowToken2, /*focusable=*/true, + /*visible=*/true); + windows.push_back(embeddedWindow2); + + FocusRequest request; + request.displayId = 42; + request.token = hostWindowToken; + + hostWindow->editInfo()->focusTransferTarget = embeddedWindowToken; + embeddedWindow->editInfo()->focusTransferTarget = embeddedWindowToken2; + + FocusResolver focusResolver; + std::optional changes = + focusResolver.setFocusedWindow(request, windows); + ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ embeddedWindowToken2); +} + +TEST(FocusResolverTest, FocusTransferTargetCycle) { + sp hostWindowToken = sp::make(); + std::vector> windows; + + sp hostWindow = + sp::make("Host Window", hostWindowToken, /*focusable=*/true, + /*visible=*/true); + windows.push_back(hostWindow); + sp embeddedWindowToken = sp::make(); + sp embeddedWindow = + sp::make("Embedded Window", embeddedWindowToken, /*focusable=*/true, + /*visible=*/true); + windows.push_back(embeddedWindow); + + sp embeddedWindowToken2 = sp::make(); + sp embeddedWindow2 = + sp::make("Embedded Window2", embeddedWindowToken2, /*focusable=*/true, + /*visible=*/true); + windows.push_back(embeddedWindow2); + + FocusRequest request; + request.displayId = 42; + request.token = hostWindowToken; + + hostWindow->editInfo()->focusTransferTarget = embeddedWindowToken; + embeddedWindow->editInfo()->focusTransferTarget = embeddedWindowToken2; + embeddedWindow2->editInfo()->focusTransferTarget = hostWindowToken; + + FocusResolver focusResolver; + std::optional changes = + focusResolver.setFocusedWindow(request, windows); + // Cycle will be detected and stop right before trying to transfer token to host again. + ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ embeddedWindowToken2); +} + TEST(FocusResolverTest, FocusRequestsAreClearedWhenWindowIsRemoved) { sp windowToken = sp::make(); std::vector> windows; diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index a58ad84e90..fb808eb2a3 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -637,14 +637,10 @@ protected: } } - void setFocusedWindow(const sp& window, - const sp& focusedWindow = nullptr) { + void setFocusedWindow(const sp& window) { FocusRequest request; request.token = window->getToken(); request.windowName = window->getName(); - if (focusedWindow) { - request.focusedToken = focusedWindow->getToken(); - } request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC); request.displayId = window->getInfo()->displayId; mDispatcher->setFocusedWindow(request); @@ -5229,7 +5225,8 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_CheckFocusedToken) { setFocusedWindow(windowTop); windowTop->consumeFocusEvent(true); - setFocusedWindow(windowSecond, windowTop); + windowTop->editInfo()->focusTransferTarget = windowSecond->getToken(); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowTop, windowSecond}}}); windowSecond->consumeFocusEvent(true); windowTop->consumeFocusEvent(false); @@ -5240,7 +5237,7 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_CheckFocusedToken) { windowSecond->consumeKeyDown(ADISPLAY_ID_NONE); } -TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestFocusTokenNotFocused) { +TEST_F(InputDispatcherTest, SetFocusedWindow_TransferFocusTokenNotFocusable) { std::shared_ptr application = std::make_shared(); sp windowTop = sp::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); @@ -5249,15 +5246,17 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestFocusTokenNotFocused) { mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); windowTop->setFocusable(true); - windowSecond->setFocusable(true); + windowSecond->setFocusable(false); + windowTop->editInfo()->focusTransferTarget = windowSecond->getToken(); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowTop, windowSecond}}}); - setFocusedWindow(windowSecond, windowTop); + setFocusedWindow(windowTop); + windowTop->consumeFocusEvent(true); - ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(mDispatcher)) - << "Inject key event should return InputEventInjectionResult::TIMED_OUT"; + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher)) + << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; // Event should be dropped. - windowTop->assertNoEvents(); + windowTop->consumeKeyDown(ADISPLAY_ID_NONE); windowSecond->assertNoEvents(); } -- GitLab From b26872ada5781414b4efb2810bd7baff94c744e9 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Wed, 29 Mar 2023 17:51:20 -0700 Subject: [PATCH 1068/1310] SF: use render rates when checking if allowed by policy This fixed a bug where SF might pick a render rate outside the policy. Bug: 276355056 Test: SF unit tests Change-Id: I9d54b4fd62859d99b2c50cbc43320eb84c2b6cdf --- .../Scheduler/RefreshRateSelector.cpp | 2 +- .../tests/unittests/RefreshRateSelectorTest.cpp | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index eec7c085cc..f136e9f9df 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -564,7 +564,7 @@ auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vectorprimaryRanges.physical.includes(modePtr->getFps()); + const bool inPrimaryRange = policy->primaryRanges.render.includes(fps); if ((primaryRangeIsSingleRate || !inPrimaryRange) && !(layer.focused && (layer.vote == LayerVoteType::ExplicitDefault || diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp index 63ed87b846..d63e187ac4 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp @@ -3026,5 +3026,21 @@ TEST_P(RefreshRateSelectorTest, frameRateIsCappedByPolicy) { EXPECT_FRAME_RATE_MODE(kMode60, 30_Hz, selector.getBestScoredFrameRate(layers).frameRateMode); } +TEST_P(RefreshRateSelectorTest, frameRateNotInRange) { + auto selector = createSelector(kModes_60_90, kModeId60); + + constexpr FpsRanges k60Only = {{60_Hz, 90_Hz}, {60_Hz, 60_Hz}}; + constexpr FpsRanges kAll = {{0_Hz, 90_Hz}, {0_Hz, 90_Hz}}; + + EXPECT_EQ(SetPolicyResult::Changed, + selector.setDisplayManagerPolicy({DisplayModeId(kModeId60), k60Only, kAll})); + + std::vector layers = {{.weight = 1.f}}; + layers[0].name = "Test layer"; + layers[0].vote = LayerVoteType::Heuristic; + layers[0].desiredRefreshRate = 45_Hz; + EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers).frameRateMode); +} + } // namespace } // namespace android::scheduler -- GitLab From 0284463f01bad248b39fce18b44a62551c91a070 Mon Sep 17 00:00:00 2001 From: Ajinkya Chalke Date: Wed, 1 Mar 2023 12:10:14 +0000 Subject: [PATCH 1069/1310] Move exclude layer to CaptureArgs. Test: atest ScreenCaptureTest ScreenCaptureChildOnlyTest Bug: 267324693 Change-Id: I6ab421e2f9e5bc0ab1422d88ddf2628776347a6f --- libs/gui/LayerState.cpp | 27 +++---- libs/gui/include/gui/DisplayCaptureArgs.h | 4 ++ libs/gui/include/gui/LayerCaptureArgs.h | 3 - .../surfaceflinger/RegionSamplingThread.cpp | 3 +- services/surfaceflinger/SurfaceFlinger.cpp | 70 +++++++++++++++++-- services/surfaceflinger/SurfaceFlinger.h | 7 +- .../tests/ScreenCapture_test.cpp | 8 +++ .../tests/unittests/CompositionTest.cpp | 2 +- .../tests/unittests/TestableSurfaceFlinger.h | 4 +- 9 files changed, 104 insertions(+), 24 deletions(-) diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index b391337d1d..cf171dcec9 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -897,6 +897,11 @@ status_t CaptureArgs::writeToParcel(Parcel* output) const { SAFE_PARCEL(output->writeInt32, static_cast(dataspace)); SAFE_PARCEL(output->writeBool, allowProtected); SAFE_PARCEL(output->writeBool, grayscale); + + SAFE_PARCEL(output->writeInt32, excludeHandles.size()); + for (auto& excludeHandle : excludeHandles) { + SAFE_PARCEL(output->writeStrongBinder, excludeHandle); + } return NO_ERROR; } @@ -913,6 +918,15 @@ status_t CaptureArgs::readFromParcel(const Parcel* input) { dataspace = static_cast(value); SAFE_PARCEL(input->readBool, &allowProtected); SAFE_PARCEL(input->readBool, &grayscale); + + int32_t numExcludeHandles = 0; + SAFE_PARCEL_READ_SIZE(input->readInt32, &numExcludeHandles, input->dataSize()); + excludeHandles.reserve(numExcludeHandles); + for (int i = 0; i < numExcludeHandles; i++) { + sp binder; + SAFE_PARCEL(input->readStrongBinder, &binder); + excludeHandles.emplace(binder); + } return NO_ERROR; } @@ -940,10 +954,6 @@ status_t LayerCaptureArgs::writeToParcel(Parcel* output) const { SAFE_PARCEL(CaptureArgs::writeToParcel, output); SAFE_PARCEL(output->writeStrongBinder, layerHandle); - SAFE_PARCEL(output->writeInt32, excludeHandles.size()); - for (auto el : excludeHandles) { - SAFE_PARCEL(output->writeStrongBinder, el); - } SAFE_PARCEL(output->writeBool, childrenOnly); return NO_ERROR; } @@ -953,15 +963,6 @@ status_t LayerCaptureArgs::readFromParcel(const Parcel* input) { SAFE_PARCEL(input->readStrongBinder, &layerHandle); - int32_t numExcludeHandles = 0; - SAFE_PARCEL_READ_SIZE(input->readInt32, &numExcludeHandles, input->dataSize()); - excludeHandles.reserve(numExcludeHandles); - for (int i = 0; i < numExcludeHandles; i++) { - sp binder; - SAFE_PARCEL(input->readStrongBinder, &binder); - excludeHandles.emplace(binder); - } - SAFE_PARCEL(input->readBool, &childrenOnly); return NO_ERROR; } diff --git a/libs/gui/include/gui/DisplayCaptureArgs.h b/libs/gui/include/gui/DisplayCaptureArgs.h index c826c17d2c..5c794aea37 100644 --- a/libs/gui/include/gui/DisplayCaptureArgs.h +++ b/libs/gui/include/gui/DisplayCaptureArgs.h @@ -22,9 +22,11 @@ #include #include #include +#include #include #include #include +#include namespace android::gui { @@ -55,6 +57,8 @@ struct CaptureArgs : public Parcelable { bool grayscale = false; + std::unordered_set, SpHash> excludeHandles; + virtual status_t writeToParcel(Parcel* output) const; virtual status_t readFromParcel(const Parcel* input); }; diff --git a/libs/gui/include/gui/LayerCaptureArgs.h b/libs/gui/include/gui/LayerCaptureArgs.h index 05ff9d5b7b..fae2bcc787 100644 --- a/libs/gui/include/gui/LayerCaptureArgs.h +++ b/libs/gui/include/gui/LayerCaptureArgs.h @@ -20,14 +20,11 @@ #include #include -#include -#include namespace android::gui { struct LayerCaptureArgs : CaptureArgs { sp layerHandle; - std::unordered_set, SpHash> excludeHandles; bool childrenOnly{false}; status_t writeToParcel(Parcel* output) const override; diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp index 327ca3f0aa..531d277ffc 100644 --- a/services/surfaceflinger/RegionSamplingThread.cpp +++ b/services/surfaceflinger/RegionSamplingThread.cpp @@ -347,7 +347,8 @@ void RegionSamplingThread::captureSample() { } visitor(layer); }; - mFlinger.traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, filterVisitor); + mFlinger.traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, {}, + filterVisitor); }; getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 076c6839c6..791b42512c 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -6888,6 +6888,7 @@ status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, wp displayWeak; ui::LayerStack layerStack; ui::Size reqSize(args.width, args.height); + std::unordered_set excludeLayerIds; ui::Dataspace dataspace; { Mutex::Autolock lock(mStateLock); @@ -6901,6 +6902,16 @@ status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, reqSize = display->getLayerStackSpaceRect().getSize(); } + for (const auto& handle : args.excludeHandles) { + uint32_t excludeLayer = LayerHandle::getLayerId(handle); + if (excludeLayer != UNASSIGNED_LAYER_ID) { + excludeLayerIds.emplace(excludeLayer); + } else { + ALOGW("Invalid layer handle passed as excludeLayer to captureDisplay"); + return NAME_NOT_FOUND; + } + } + // Allow the caller to specify a dataspace regardless of the display's color mode, e.g. if // it wants sRGB regardless of the display's wide color mode. dataspace = args.dataspace == ui::Dataspace::UNKNOWN @@ -6916,10 +6927,11 @@ status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, GetLayerSnapshotsFunction getLayerSnapshots; if (mLayerLifecycleManagerEnabled) { getLayerSnapshots = - getLayerSnapshotsForScreenshots(layerStack, args.uid, /*snapshotFilterFn=*/nullptr); + getLayerSnapshotsForScreenshots(layerStack, args.uid, std::move(excludeLayerIds)); } else { - auto traverseLayers = [this, args, layerStack](const LayerVector::Visitor& visitor) { - traverseLayersInLayerStack(layerStack, args.uid, visitor); + auto traverseLayers = [this, args, excludeLayerIds, + layerStack](const LayerVector::Visitor& visitor) { + traverseLayersInLayerStack(layerStack, args.uid, std::move(excludeLayerIds), visitor); }; getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); } @@ -6962,7 +6974,7 @@ status_t SurfaceFlinger::captureDisplay(DisplayId displayId, /*snapshotFilterFn=*/nullptr); } else { auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) { - traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, visitor); + traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, {}, visitor); }; getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); } @@ -7386,6 +7398,7 @@ void SurfaceFlinger::State::traverseInReverseZOrder(const LayerVector::Visitor& } void SurfaceFlinger::traverseLayersInLayerStack(ui::LayerStack layerStack, const int32_t uid, + std::unordered_set excludeLayerIds, const LayerVector::Visitor& visitor) { // We loop through the first level of layers without traversing, // as we need to determine which layers belong to the requested display. @@ -7404,6 +7417,17 @@ void SurfaceFlinger::traverseLayersInLayerStack(ui::LayerStack layerStack, const if (uid != CaptureArgs::UNSET_UID && layer->getOwnerUid() != uid) { return; } + + if (!excludeLayerIds.empty()) { + auto p = sp::fromExisting(layer); + while (p != nullptr) { + if (excludeLayerIds.count(p->sequence) != 0) { + return; + } + p = p->getParent(); + } + } + visitor(layer); }); } @@ -8058,6 +8082,44 @@ SurfaceFlinger::getLayerSnapshotsForScreenshots( }; } +std::function>>()> +SurfaceFlinger::getLayerSnapshotsForScreenshots(std::optional layerStack, + uint32_t uid, + std::unordered_set excludeLayerIds) { + return [&, layerStack, uid, excludeLayerIds = std::move(excludeLayerIds)]() { + if (excludeLayerIds.empty()) { + auto getLayerSnapshotsFn = + getLayerSnapshotsForScreenshots(layerStack, uid, /*snapshotFilterFn=*/nullptr); + std::vector>> layers = getLayerSnapshotsFn(); + return layers; + } + + frontend::LayerSnapshotBuilder::Args + args{.root = mLayerHierarchyBuilder.getHierarchy(), + .layerLifecycleManager = mLayerLifecycleManager, + .forceUpdate = frontend::LayerSnapshotBuilder::ForceUpdateFlags::HIERARCHY, + .displays = mFrontEndDisplayInfos, + .displayChanges = true, + .globalShadowSettings = mDrawingState.globalShadowSettings, + .supportsBlur = mSupportsBlur, + .forceFullDamage = mForceFullDamage, + .excludeLayerIds = std::move(excludeLayerIds), + .supportedLayerGenericMetadata = + getHwComposer().getSupportedLayerGenericMetadata(), + .genericLayerMetadataKeyMap = getGenericLayerMetadataKeyMap()}; + mLayerSnapshotBuilder.update(args); + + auto getLayerSnapshotsFn = + getLayerSnapshotsForScreenshots(layerStack, uid, /*snapshotFilterFn=*/nullptr); + std::vector>> layers = getLayerSnapshotsFn(); + + args.excludeLayerIds.clear(); + mLayerSnapshotBuilder.update(args); + + return layers; + }; +} + std::function>>()> SurfaceFlinger::getLayerSnapshotsForScreenshots(uint32_t rootLayerId, uint32_t uid, std::unordered_set excludeLayerIds, diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 74d00dd60c..44847885ca 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -826,7 +826,9 @@ private: // If the uid provided is not UNSET_UID, the traverse will skip any layers that don't have a // matching ownerUid - void traverseLayersInLayerStack(ui::LayerStack, const int32_t uid, const LayerVector::Visitor&); + void traverseLayersInLayerStack(ui::LayerStack, const int32_t uid, + std::unordered_set excludeLayerIds, + const LayerVector::Visitor&); void readPersistentProperties(); @@ -1380,6 +1382,9 @@ private: std::optional layerStack, uint32_t uid, std::function snapshotFilterFn); + std::function>>()> getLayerSnapshotsForScreenshots( + std::optional layerStack, uint32_t uid, + std::unordered_set excludeLayerIds); std::function>>()> getLayerSnapshotsForScreenshots( uint32_t rootLayerId, uint32_t uid, std::unordered_set excludeLayerIds, bool childrenOnly, const std::optional& optionalParentCrop); diff --git a/services/surfaceflinger/tests/ScreenCapture_test.cpp b/services/surfaceflinger/tests/ScreenCapture_test.cpp index 976ee3519c..013694fd47 100644 --- a/services/surfaceflinger/tests/ScreenCapture_test.cpp +++ b/services/surfaceflinger/tests/ScreenCapture_test.cpp @@ -222,6 +222,14 @@ TEST_F(ScreenCaptureTest, CaptureLayerExclude) { mCapture->checkPixel(0, 0, 200, 200, 200); } +TEST_F(ScreenCaptureTest, CaptureLayerExcludeThroughDisplayArgs) { + mCaptureArgs.excludeHandles = {mFGSurfaceControl->getHandle()}; + ScreenCapture::captureDisplay(&mCapture, mCaptureArgs); + mCapture->expectBGColor(0, 0); + // Doesn't capture FG layer which is at 64, 64 + mCapture->expectBGColor(64, 64); +} + // Like the last test but verifies that children are also exclude. TEST_F(ScreenCaptureTest, CaptureLayerExcludeTree) { auto fgHandle = mFGSurfaceControl->getHandle(); diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 19a93e1820..f5960614b0 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -204,7 +204,7 @@ void CompositionTest::captureScreenComposition() { auto traverseLayers = [this](const LayerVector::Visitor& visitor) { return mFlinger.traverseLayersInLayerStack(mDisplay->getLayerStack(), - CaptureArgs::UNSET_UID, visitor); + CaptureArgs::UNSET_UID, {}, visitor); }; auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index fc9e653ba4..89a190e9ea 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -446,8 +446,10 @@ public: } auto traverseLayersInLayerStack(ui::LayerStack layerStack, int32_t uid, + std::unordered_set excludeLayerIds, const LayerVector::Visitor& visitor) { - return mFlinger->SurfaceFlinger::traverseLayersInLayerStack(layerStack, uid, visitor); + return mFlinger->SurfaceFlinger::traverseLayersInLayerStack(layerStack, uid, + excludeLayerIds, visitor); } auto getDisplayNativePrimaries(const sp& displayToken, -- GitLab From 0538caea402a7f8eab2d32c348db190bf5304e4f Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Tue, 8 Nov 2022 23:12:04 +0000 Subject: [PATCH 1070/1310] Rewrite the PowerAdvisor using standard power wrappers and clean up * Replace custom AIDL/HIDL wrappers with libpowermanager * Remove early hint code as it is deprecated for load change hints Bug: b/245975645 Bug: b/244358432 Test: atest libsurfaceflinger_unittest Change-Id: I2a11779ce1f78bcf29ea0e7978cb8933e74e9f7b Merged-In: I2a11779ce1f78bcf29ea0e7978cb8933e74e9f7b (cherry picked from commit cbfe23c938226f5fa83d9b37f9f4212ee0c65a6f) --- services/powermanager/Android.bp | 8 + services/surfaceflinger/Android.bp | 5 +- .../tests/MockPowerAdvisor.h | 9 +- .../DisplayHardware/PowerAdvisor.cpp | 569 ++++-------------- .../DisplayHardware/PowerAdvisor.h | 130 ++-- services/surfaceflinger/SurfaceFlinger.cpp | 29 +- services/surfaceflinger/SurfaceFlinger.h | 4 - .../unittests/AidlPowerHalWrapperTest.cpp | 241 -------- .../surfaceflinger/tests/unittests/Android.bp | 4 +- .../tests/unittests/PowerAdvisorTest.cpp | 55 +- .../SurfaceFlinger_PowerHintTest.cpp | 9 +- .../tests/unittests/TestableSurfaceFlinger.h | 4 - .../DisplayHardware/MockAidlPowerHalWrapper.h | 54 -- .../mock/DisplayHardware/MockPowerAdvisor.h | 9 +- ...Wrapper.cpp => MockPowerHalController.cpp} | 10 +- .../DisplayHardware/MockPowerHalController.h | 49 ++ 16 files changed, 285 insertions(+), 904 deletions(-) delete mode 100644 services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp delete mode 100644 services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h rename services/surfaceflinger/tests/unittests/mock/DisplayHardware/{MockAidlPowerHalWrapper.cpp => MockPowerHalController.cpp} (67%) create mode 100644 services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp index 7fb33e5dcf..b34e54fd6b 100644 --- a/services/powermanager/Android.bp +++ b/services/powermanager/Android.bp @@ -43,6 +43,14 @@ cc_library_shared { "android.hardware.power-V4-cpp", ], + export_shared_lib_headers: [ + "android.hardware.power@1.0", + "android.hardware.power@1.1", + "android.hardware.power@1.2", + "android.hardware.power@1.3", + "android.hardware.power-V4-cpp", + ], + cflags: [ "-Wall", "-Werror", diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index fe7cff7577..5683a9280f 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -47,8 +47,6 @@ cc_defaults { "android.hardware.graphics.composer@2.2", "android.hardware.graphics.composer@2.3", "android.hardware.graphics.composer@2.4", - "android.hardware.power@1.0", - "android.hardware.power@1.3", "android.hardware.power-V4-cpp", "libbase", "libbinder", @@ -63,6 +61,7 @@ cc_defaults { "liblayers_proto", "liblog", "libnativewindow", + "libpowermanager", "libprocessgroup", "libprotobuf-cpp-lite", "libsync", @@ -105,7 +104,7 @@ cc_defaults { "android.hardware.graphics.composer@2.2", "android.hardware.graphics.composer@2.3", "android.hardware.graphics.composer@2.4", - "android.hardware.power@1.3", + "libpowermanager", "libhidlbase", "libtimestats", ], diff --git a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h index c555b39db1..961ec808e8 100644 --- a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h +++ b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h @@ -37,11 +37,10 @@ public: MOCK_METHOD(void, notifyDisplayUpdateImminentAndCpuReset, (), (override)); MOCK_METHOD(bool, usePowerHintSession, (), (override)); MOCK_METHOD(bool, supportsPowerHintSession, (), (override)); - MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override)); - MOCK_METHOD(void, setTargetWorkDuration, (Duration targetDuration), (override)); - MOCK_METHOD(void, sendActualWorkDuration, (), (override)); - MOCK_METHOD(void, sendPredictedWorkDuration, (), (override)); - MOCK_METHOD(void, enablePowerHint, (bool enabled), (override)); + MOCK_METHOD(bool, ensurePowerHintSessionRunning, (), (override)); + MOCK_METHOD(void, updateTargetWorkDuration, (Duration targetDuration), (override)); + MOCK_METHOD(void, reportActualWorkDuration, (), (override)); + MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override)); MOCK_METHOD(bool, startPowerHintSession, (const std::vector& threadIds), (override)); MOCK_METHOD(void, setGpuFenceTime, (DisplayId displayId, std::unique_ptr&& fenceTime), (override)); diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp index 36f71bb481..37b68c865e 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp @@ -31,7 +31,7 @@ #include #include -#include +#include #include #include @@ -49,12 +49,7 @@ PowerAdvisor::~PowerAdvisor() = default; namespace impl { -namespace V1_0 = android::hardware::power::V1_0; -namespace V1_3 = android::hardware::power::V1_3; -using V1_3::PowerHint; - using android::hardware::power::Boost; -using android::hardware::power::IPower; using android::hardware::power::IPowerHintSession; using android::hardware::power::Mode; using android::hardware::power::SessionHint; @@ -80,7 +75,8 @@ void traceExpensiveRendering(bool enabled) { } // namespace -PowerAdvisor::PowerAdvisor(SurfaceFlinger& flinger) : mFlinger(flinger) { +PowerAdvisor::PowerAdvisor(SurfaceFlinger& flinger) + : mPowerHal(std::make_unique()), mFlinger(flinger) { if (getUpdateTimeout() > 0ms) { mScreenUpdateTimer.emplace("UpdateImminentTimer", getUpdateTimeout(), /* resetCallback */ nullptr, @@ -117,6 +113,10 @@ void PowerAdvisor::onBootFinished() { } void PowerAdvisor::setExpensiveRenderingExpected(DisplayId displayId, bool expected) { + if (!mHasExpensiveRendering) { + ALOGV("Skipped sending EXPENSIVE_RENDERING because HAL doesn't support it"); + return; + } if (expected) { mExpensiveDisplays.insert(displayId); } else { @@ -125,19 +125,16 @@ void PowerAdvisor::setExpensiveRenderingExpected(DisplayId displayId, bool expec const bool expectsExpensiveRendering = !mExpensiveDisplays.empty(); if (mNotifiedExpensiveRendering != expectsExpensiveRendering) { - std::lock_guard lock(mPowerHalMutex); - HalWrapper* const halWrapper = getPowerHal(); - if (halWrapper == nullptr) { - return; - } - - if (!halWrapper->setExpensiveRendering(expectsExpensiveRendering)) { - // The HAL has become unavailable; attempt to reconnect later - mReconnectPowerHal = true; + auto ret = getPowerHal().setMode(Mode::EXPENSIVE_RENDERING, expectsExpensiveRendering); + if (!ret.isOk()) { + if (ret.isUnsupported()) { + mHasExpensiveRendering = false; + } return; } mNotifiedExpensiveRendering = expectsExpensiveRendering; + traceExpensiveRendering(mNotifiedExpensiveRendering); } } @@ -149,16 +146,22 @@ void PowerAdvisor::notifyDisplayUpdateImminentAndCpuReset() { } if (mSendUpdateImminent.exchange(false)) { - std::lock_guard lock(mPowerHalMutex); - HalWrapper* const halWrapper = getPowerHal(); - if (halWrapper == nullptr) { - return; + ALOGV("AIDL notifyDisplayUpdateImminentAndCpuReset"); + if (usePowerHintSession() && ensurePowerHintSessionRunning()) { + std::lock_guard lock(mHintSessionMutex); + auto ret = mHintSession->sendHint(SessionHint::CPU_LOAD_RESET); + if (!ret.isOk()) { + mHintSessionRunning = false; + } } - if (!halWrapper->notifyDisplayUpdateImminentAndCpuReset()) { - // The HAL has become unavailable; attempt to reconnect later - mReconnectPowerHal = true; - return; + if (!mHasDisplayUpdateImminent) { + ALOGV("Skipped sending DISPLAY_UPDATE_IMMINENT because HAL doesn't support it"); + } else { + auto ret = getPowerHal().setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 0); + if (ret.isUnsupported()) { + mHasDisplayUpdateImminent = false; + } } if (mScreenUpdateTimer) { @@ -178,87 +181,123 @@ void PowerAdvisor::notifyDisplayUpdateImminentAndCpuReset() { // checks both if it supports and if it's enabled bool PowerAdvisor::usePowerHintSession() { // uses cached value since the underlying support and flag are unlikely to change at runtime - return mPowerHintEnabled.value_or(false) && supportsPowerHintSession(); + return mHintSessionEnabled.value_or(false) && supportsPowerHintSession(); } bool PowerAdvisor::supportsPowerHintSession() { // cache to avoid needing lock every time - if (!mSupportsPowerHint.has_value()) { - std::lock_guard lock(mPowerHalMutex); - HalWrapper* const halWrapper = getPowerHal(); - mSupportsPowerHint = halWrapper && halWrapper->supportsPowerHintSession(); + if (!mSupportsHintSession.has_value()) { + mSupportsHintSession = getPowerHal().getHintSessionPreferredRate().isOk(); } - return *mSupportsPowerHint; + return *mSupportsHintSession; } -bool PowerAdvisor::isPowerHintSessionRunning() { - return mPowerHintSessionRunning; +bool PowerAdvisor::ensurePowerHintSessionRunning() { + if (!mHintSessionRunning && !mHintSessionThreadIds.empty() && usePowerHintSession()) { + startPowerHintSession(mHintSessionThreadIds); + } + return mHintSessionRunning; } -void PowerAdvisor::setTargetWorkDuration(Duration targetDuration) { +void PowerAdvisor::updateTargetWorkDuration(Duration targetDuration) { if (!usePowerHintSession()) { ALOGV("Power hint session target duration cannot be set, skipping"); return; } + ATRACE_CALL(); { - std::lock_guard lock(mPowerHalMutex); - HalWrapper* const halWrapper = getPowerHal(); - if (halWrapper != nullptr) { - halWrapper->setTargetWorkDuration(targetDuration); + mTargetDuration = targetDuration; + if (sTraceHintSessionData) ATRACE_INT64("Time target", targetDuration.ns()); + if (ensurePowerHintSessionRunning() && (targetDuration != mLastTargetDurationSent)) { + ALOGV("Sending target time: %" PRId64 "ns", targetDuration.ns()); + mLastTargetDurationSent = targetDuration; + std::lock_guard lock(mHintSessionMutex); + auto ret = mHintSession->updateTargetWorkDuration(targetDuration.ns()); + if (!ret.isOk()) { + ALOGW("Failed to set power hint target work duration with error: %s", + ret.exceptionMessage().c_str()); + mHintSessionRunning = false; + } } } } -void PowerAdvisor::sendActualWorkDuration() { +void PowerAdvisor::reportActualWorkDuration() { if (!mBootFinished || !usePowerHintSession()) { ALOGV("Actual work duration power hint cannot be sent, skipping"); return; } - const std::optional actualDuration = estimateWorkDuration(false); - if (actualDuration.has_value()) { - std::lock_guard lock(mPowerHalMutex); - HalWrapper* const halWrapper = getPowerHal(); - if (halWrapper != nullptr) { - halWrapper->sendActualWorkDuration(*actualDuration + sTargetSafetyMargin, - TimePoint::now()); - } + ATRACE_CALL(); + std::optional actualDuration = estimateWorkDuration(); + if (!actualDuration.has_value() || actualDuration < 0ns || !ensurePowerHintSessionRunning()) { + ALOGV("Failed to send actual work duration, skipping"); + return; } -} + actualDuration = std::make_optional(*actualDuration + sTargetSafetyMargin); + mActualDuration = actualDuration; + WorkDuration duration; + duration.durationNanos = actualDuration->ns(); + duration.timeStampNanos = TimePoint::now().ns(); + mHintSessionQueue.push_back(duration); -void PowerAdvisor::sendPredictedWorkDuration() { - if (!mBootFinished || !usePowerHintSession()) { - ALOGV("Actual work duration power hint cannot be sent, skipping"); - return; + if (sTraceHintSessionData) { + ATRACE_INT64("Measured duration", actualDuration->ns()); + ATRACE_INT64("Target error term", Duration{*actualDuration - mTargetDuration}.ns()); + ATRACE_INT64("Reported duration", actualDuration->ns()); + ATRACE_INT64("Reported target", mLastTargetDurationSent.ns()); + ATRACE_INT64("Reported target error term", + Duration{*actualDuration - mLastTargetDurationSent}.ns()); } - const std::optional predictedDuration = estimateWorkDuration(true); - if (predictedDuration.has_value()) { - std::lock_guard lock(mPowerHalMutex); - HalWrapper* const halWrapper = getPowerHal(); - if (halWrapper != nullptr) { - halWrapper->sendActualWorkDuration(*predictedDuration + sTargetSafetyMargin, - TimePoint::now()); + ALOGV("Sending actual work duration of: %" PRId64 " on reported target: %" PRId64 + " with error: %" PRId64, + actualDuration->ns(), mLastTargetDurationSent.ns(), + Duration{*actualDuration - mLastTargetDurationSent}.ns()); + + { + std::lock_guard lock(mHintSessionMutex); + auto ret = mHintSession->reportActualWorkDuration(mHintSessionQueue); + if (!ret.isOk()) { + ALOGW("Failed to report actual work durations with error: %s", + ret.exceptionMessage().c_str()); + mHintSessionRunning = false; + return; } } + mHintSessionQueue.clear(); } -void PowerAdvisor::enablePowerHint(bool enabled) { - mPowerHintEnabled = enabled; +void PowerAdvisor::enablePowerHintSession(bool enabled) { + mHintSessionEnabled = enabled; } bool PowerAdvisor::startPowerHintSession(const std::vector& threadIds) { + if (!mBootFinished.load()) { + return false; + } if (!usePowerHintSession()) { - ALOGI("Power hint session cannot be started, skipping"); + ALOGI("Cannot start power hint session: disabled or unsupported"); + return false; } + if (mHintSessionRunning) { + ALOGE("Cannot start power hint session: already running"); + return false; + } + LOG_ALWAYS_FATAL_IF(threadIds.empty(), "No thread IDs provided to power hint session!"); { - std::lock_guard lock(mPowerHalMutex); - HalWrapper* halWrapper = getPowerHal(); - if (halWrapper != nullptr && usePowerHintSession()) { - halWrapper->setPowerHintSessionThreadIds(threadIds); - mPowerHintSessionRunning = halWrapper->startPowerHintSession(); + std::lock_guard lock(mHintSessionMutex); + mHintSession = nullptr; + mHintSessionThreadIds = threadIds; + + auto ret = getPowerHal().createHintSession(getpid(), static_cast(getuid()), + threadIds, mTargetDuration.ns()); + + if (ret.isOk()) { + mHintSessionRunning = true; + mHintSession = ret.value(); } } - return mPowerHintSessionRunning; + return mHintSessionRunning; } void PowerAdvisor::setGpuFenceTime(DisplayId displayId, std::unique_ptr&& fenceTime) { @@ -356,13 +395,13 @@ std::vector PowerAdvisor::getOrderedDisplayIds( return sortedDisplays; } -std::optional PowerAdvisor::estimateWorkDuration(bool earlyHint) { - if (earlyHint && (!mExpectedPresentTimes.isFull() || !mCommitStartTimes.isFull())) { +std::optional PowerAdvisor::estimateWorkDuration() { + if (!mExpectedPresentTimes.isFull() || !mCommitStartTimes.isFull()) { return std::nullopt; } // Tracks when we finish presenting to hwc - TimePoint estimatedEndTime = mCommitStartTimes[0]; + TimePoint estimatedHwcEndTime = mCommitStartTimes[0]; // How long we spent this frame not doing anything, waiting for fences or vsync Duration idleDuration = 0ns; @@ -375,21 +414,11 @@ std::optional PowerAdvisor::estimateWorkDuration(bool earlyHint) { // used to accumulate gpu time as we iterate over the active displays std::optional estimatedGpuEndTime; - // If we're predicting at the start of the frame, we use last frame as our reference point - // If we're predicting at the end of the frame, we use the current frame as a reference point - TimePoint referenceFrameStartTime = (earlyHint ? mCommitStartTimes[-1] : mCommitStartTimes[0]); - - // When the prior frame should be presenting to the display - // If we're predicting at the start of the frame, we use last frame's expected present time - // If we're predicting at the end of the frame, the present fence time is already known - TimePoint lastFramePresentTime = - (earlyHint ? mExpectedPresentTimes[-1] : mLastPresentFenceTime); - // The timing info for the previously calculated display, if there was one - std::optional previousDisplayReferenceTiming; + std::optional previousDisplayTiming; std::vector&& displayIds = getOrderedDisplayIds(&DisplayTimingData::hwcPresentStartTime); - DisplayTimeline referenceTiming, estimatedTiming; + DisplayTimeline displayTiming; // Iterate over the displays that use hwc in the same order they are presented for (DisplayId displayId : displayIds) { @@ -399,35 +428,26 @@ std::optional PowerAdvisor::estimateWorkDuration(bool earlyHint) { auto& displayData = mDisplayTimingData.at(displayId); - // mLastPresentFenceTime should always be the time of the reference frame, since it will be - // the previous frame's present fence if called at the start, and current frame's if called - // at the end - referenceTiming = displayData.calculateDisplayTimeline(mLastPresentFenceTime); + displayTiming = displayData.calculateDisplayTimeline(mLastPresentFenceTime); // If this is the first display, include the duration before hwc present starts - if (!previousDisplayReferenceTiming.has_value()) { - estimatedEndTime += referenceTiming.hwcPresentStartTime - referenceFrameStartTime; + if (!previousDisplayTiming.has_value()) { + estimatedHwcEndTime += displayTiming.hwcPresentStartTime - mCommitStartTimes[0]; } else { // Otherwise add the time since last display's hwc present finished - estimatedEndTime += referenceTiming.hwcPresentStartTime - - previousDisplayReferenceTiming->hwcPresentEndTime; + estimatedHwcEndTime += + displayTiming.hwcPresentStartTime - previousDisplayTiming->hwcPresentEndTime; } - // Late hint can re-use reference timing here since it's estimating its own reference frame - estimatedTiming = earlyHint - ? referenceTiming.estimateTimelineFromReference(lastFramePresentTime, - estimatedEndTime) - : referenceTiming; - // Update predicted present finish time with this display's present time - estimatedEndTime = estimatedTiming.hwcPresentEndTime; + estimatedHwcEndTime = displayTiming.hwcPresentEndTime; // Track how long we spent waiting for the fence, can be excluded from the timing estimate - idleDuration += estimatedTiming.probablyWaitsForPresentFence - ? lastFramePresentTime - estimatedTiming.presentFenceWaitStartTime + idleDuration += displayTiming.probablyWaitsForPresentFence + ? mLastPresentFenceTime - displayTiming.presentFenceWaitStartTime : 0ns; // Track how long we spent waiting to present, can be excluded from the timing estimate - idleDuration += earlyHint ? 0ns : referenceTiming.hwcPresentDelayDuration; + idleDuration += displayTiming.hwcPresentDelayDuration; // Estimate the reference frame's gpu timing auto gpuTiming = displayData.estimateGpuTiming(previousValidGpuEndTime); @@ -435,24 +455,24 @@ std::optional PowerAdvisor::estimateWorkDuration(bool earlyHint) { previousValidGpuEndTime = gpuTiming->startTime + gpuTiming->duration; // Estimate the prediction frame's gpu end time from the reference frame - estimatedGpuEndTime = std::max(estimatedTiming.hwcPresentStartTime, + estimatedGpuEndTime = std::max(displayTiming.hwcPresentStartTime, estimatedGpuEndTime.value_or(TimePoint{0ns})) + gpuTiming->duration; } - previousDisplayReferenceTiming = referenceTiming; + previousDisplayTiming = displayTiming; } ATRACE_INT64("Idle duration", idleDuration.ns()); - TimePoint estimatedFlingerEndTime = earlyHint ? estimatedEndTime : mLastSfPresentEndTime; + TimePoint estimatedFlingerEndTime = mLastSfPresentEndTime; // Don't count time spent idly waiting in the estimate as we could do more work in that time - estimatedEndTime -= idleDuration; + estimatedHwcEndTime -= idleDuration; estimatedFlingerEndTime -= idleDuration; // We finish the frame when both present and the gpu are done, so wait for the later of the two // Also add the frame delay duration since the target did not move while we were delayed Duration totalDuration = mFrameDelayDuration + - std::max(estimatedEndTime, estimatedGpuEndTime.value_or(TimePoint{0ns})) - + std::max(estimatedHwcEndTime, estimatedGpuEndTime.value_or(TimePoint{0ns})) - mCommitStartTimes[0]; // We finish SurfaceFlinger when post-composition finishes, so add that in here @@ -467,10 +487,7 @@ std::optional PowerAdvisor::estimateWorkDuration(bool earlyHint) { Duration PowerAdvisor::combineTimingEstimates(Duration totalDuration, Duration flingerDuration) { Duration targetDuration{0ns}; - { - std::lock_guard lock(mPowerHalMutex); - targetDuration = *getPowerHal()->getTargetWorkDuration(); - } + targetDuration = mTargetDuration; if (!mTotalFrameTargetDuration.has_value()) return flingerDuration; // Normalize total to the flinger target (vsync period) since that's how often we actually send @@ -480,26 +497,6 @@ Duration PowerAdvisor::combineTimingEstimates(Duration totalDuration, Duration f return std::max(flingerDuration, normalizedTotalDuration); } -PowerAdvisor::DisplayTimeline PowerAdvisor::DisplayTimeline::estimateTimelineFromReference( - TimePoint fenceTime, TimePoint displayStartTime) { - DisplayTimeline estimated; - estimated.hwcPresentStartTime = displayStartTime; - - // We don't predict waiting for vsync alignment yet - estimated.hwcPresentDelayDuration = 0ns; - - // How long we expect to run before we start waiting for the fence - // For now just re-use last frame's post-present duration and assume it will not change much - // Excludes time spent waiting for vsync since that's not going to be consistent - estimated.presentFenceWaitStartTime = estimated.hwcPresentStartTime + - (presentFenceWaitStartTime - (hwcPresentStartTime + hwcPresentDelayDuration)); - estimated.probablyWaitsForPresentFence = fenceTime > estimated.presentFenceWaitStartTime; - estimated.hwcPresentEndTime = postPresentFenceHwcPresentDuration + - (estimated.probablyWaitsForPresentFence ? fenceTime - : estimated.presentFenceWaitStartTime); - return estimated; -} - PowerAdvisor::DisplayTimeline PowerAdvisor::DisplayTimingData::calculateDisplayTimeline( TimePoint fenceTime) { DisplayTimeline timeline; @@ -560,321 +557,17 @@ std::optional PowerAdvisor::DisplayTimingData::estima return GpuTimeline{.duration = gpuDuration, .startTime = latestGpuStartTime}; } -class HidlPowerHalWrapper : public PowerAdvisor::HalWrapper { -public: - HidlPowerHalWrapper(sp powerHal) : mPowerHal(std::move(powerHal)) {} - - ~HidlPowerHalWrapper() override = default; - - static std::unique_ptr connect() { - // Power HAL 1.3 is not guaranteed to be available, thus we need to query - // Power HAL 1.0 first and try to cast it to Power HAL 1.3. - sp powerHal = nullptr; - sp powerHal_1_0 = V1_0::IPower::getService(); - if (powerHal_1_0 != nullptr) { - // Try to cast to Power HAL 1.3 - powerHal = V1_3::IPower::castFrom(powerHal_1_0); - if (powerHal == nullptr) { - ALOGW("No Power HAL 1.3 service in system, disabling PowerAdvisor"); - } else { - ALOGI("Loaded Power HAL 1.3 service"); - } - } else { - ALOGW("No Power HAL found, disabling PowerAdvisor"); - } - - if (powerHal == nullptr) { - return nullptr; - } - - return std::make_unique(std::move(powerHal)); - } - - bool setExpensiveRendering(bool enabled) override { - ALOGV("HIDL setExpensiveRendering %s", enabled ? "T" : "F"); - auto ret = mPowerHal->powerHintAsync_1_3(PowerHint::EXPENSIVE_RENDERING, enabled); - if (ret.isOk()) { - traceExpensiveRendering(enabled); - } - return ret.isOk(); - } - - bool notifyDisplayUpdateImminentAndCpuReset() override { - // Power HAL 1.x doesn't have a notification for this - ALOGV("HIDL notifyUpdateImminent received but can't send"); - return true; - } - - bool supportsPowerHintSession() override { return false; } - - bool isPowerHintSessionRunning() override { return false; } - - void restartPowerHintSession() override {} - - void setPowerHintSessionThreadIds(const std::vector&) override {} - - bool startPowerHintSession() override { return false; } - - void setTargetWorkDuration(Duration) override {} - - void sendActualWorkDuration(Duration, TimePoint) override {} - - bool shouldReconnectHAL() override { return false; } - - std::vector getPowerHintSessionThreadIds() override { return std::vector{}; } - - std::optional getTargetWorkDuration() override { return std::nullopt; } - -private: - const sp mPowerHal = nullptr; -}; - -AidlPowerHalWrapper::AidlPowerHalWrapper(sp powerHal) : mPowerHal(std::move(powerHal)) { - auto ret = mPowerHal->isModeSupported(Mode::EXPENSIVE_RENDERING, &mHasExpensiveRendering); - if (!ret.isOk()) { - mHasExpensiveRendering = false; - } - - ret = mPowerHal->isBoostSupported(Boost::DISPLAY_UPDATE_IMMINENT, &mHasDisplayUpdateImminent); - if (!ret.isOk()) { - mHasDisplayUpdateImminent = false; - } - - mSupportsPowerHint = checkPowerHintSessionSupported(); -} - -AidlPowerHalWrapper::~AidlPowerHalWrapper() { - if (mPowerHintSession != nullptr) { - mPowerHintSession->close(); - mPowerHintSession = nullptr; - } -} - -std::unique_ptr AidlPowerHalWrapper::connect() { - // This only waits if the service is actually declared - sp powerHal = waitForVintfService(); - if (powerHal == nullptr) { - return nullptr; - } - ALOGI("Loaded AIDL Power HAL service"); - - return std::make_unique(std::move(powerHal)); -} - -bool AidlPowerHalWrapper::setExpensiveRendering(bool enabled) { - ALOGV("AIDL setExpensiveRendering %s", enabled ? "T" : "F"); - if (!mHasExpensiveRendering) { - ALOGV("Skipped sending EXPENSIVE_RENDERING because HAL doesn't support it"); - return true; - } - - auto ret = mPowerHal->setMode(Mode::EXPENSIVE_RENDERING, enabled); - if (ret.isOk()) { - traceExpensiveRendering(enabled); - } - return ret.isOk(); -} - -bool AidlPowerHalWrapper::notifyDisplayUpdateImminentAndCpuReset() { - ALOGV("AIDL notifyDisplayUpdateImminentAndCpuReset"); - if (isPowerHintSessionRunning()) { - mPowerHintSession->sendHint(SessionHint::CPU_LOAD_RESET); - } - - if (!mHasDisplayUpdateImminent) { - ALOGV("Skipped sending DISPLAY_UPDATE_IMMINENT because HAL doesn't support it"); - return true; - } - - auto ret = mPowerHal->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 0); - return ret.isOk(); -} - -// Only version 2+ of the aidl supports power hint sessions, hidl has no support -bool AidlPowerHalWrapper::supportsPowerHintSession() { - return mSupportsPowerHint; -} - -bool AidlPowerHalWrapper::checkPowerHintSessionSupported() { - int64_t unused; - // Try to get preferred rate to determine if hint sessions are supported - // We check for isOk not EX_UNSUPPORTED_OPERATION to lump together errors - return mPowerHal->getHintSessionPreferredRate(&unused).isOk(); -} - -bool AidlPowerHalWrapper::isPowerHintSessionRunning() { - return mPowerHintSession != nullptr; -} - -void AidlPowerHalWrapper::closePowerHintSession() { - if (mPowerHintSession != nullptr) { - mPowerHintSession->close(); - mPowerHintSession = nullptr; - } -} - -void AidlPowerHalWrapper::restartPowerHintSession() { - closePowerHintSession(); - startPowerHintSession(); -} - -void AidlPowerHalWrapper::setPowerHintSessionThreadIds(const std::vector& threadIds) { - if (threadIds != mPowerHintThreadIds) { - mPowerHintThreadIds = threadIds; - if (isPowerHintSessionRunning()) { - restartPowerHintSession(); - } - } -} - -bool AidlPowerHalWrapper::startPowerHintSession() { - if (mPowerHintSession != nullptr || mPowerHintThreadIds.empty()) { - ALOGV("Cannot start power hint session, skipping"); - return false; - } - auto ret = mPowerHal->createHintSession(getpid(), static_cast(getuid()), - mPowerHintThreadIds, mTargetDuration.ns(), - &mPowerHintSession); - if (!ret.isOk()) { - ALOGW("Failed to start power hint session with error: %s", - ret.exceptionToString(ret.exceptionCode()).c_str()); - } else { - mLastTargetDurationSent = mTargetDuration; - } - return isPowerHintSessionRunning(); -} - -void AidlPowerHalWrapper::setTargetWorkDuration(Duration targetDuration) { - ATRACE_CALL(); - mTargetDuration = targetDuration; - if (sTraceHintSessionData) ATRACE_INT64("Time target", targetDuration.ns()); - if (isPowerHintSessionRunning() && (targetDuration != mLastTargetDurationSent)) { - ALOGV("Sending target time: %" PRId64 "ns", targetDuration.ns()); - mLastTargetDurationSent = targetDuration; - auto ret = mPowerHintSession->updateTargetWorkDuration(targetDuration.ns()); - if (!ret.isOk()) { - ALOGW("Failed to set power hint target work duration with error: %s", - ret.exceptionMessage().c_str()); - mShouldReconnectHal = true; - } - } -} - -void AidlPowerHalWrapper::sendActualWorkDuration(Duration actualDuration, TimePoint timestamp) { - ATRACE_CALL(); - if (actualDuration < 0ns || !isPowerHintSessionRunning()) { - ALOGV("Failed to send actual work duration, skipping"); - return; - } - mActualDuration = actualDuration; - WorkDuration duration; - duration.durationNanos = actualDuration.ns(); - duration.timeStampNanos = timestamp.ns(); - mPowerHintQueue.push_back(duration); - - if (sTraceHintSessionData) { - ATRACE_INT64("Measured duration", actualDuration.ns()); - ATRACE_INT64("Target error term", Duration{actualDuration - mTargetDuration}.ns()); - - ATRACE_INT64("Reported duration", actualDuration.ns()); - ATRACE_INT64("Reported target", mLastTargetDurationSent.ns()); - ATRACE_INT64("Reported target error term", - Duration{actualDuration - mLastTargetDurationSent}.ns()); - } - - ALOGV("Sending actual work duration of: %" PRId64 " on reported target: %" PRId64 - " with error: %" PRId64, - actualDuration.ns(), mLastTargetDurationSent.ns(), - Duration{actualDuration - mLastTargetDurationSent}.ns()); - - auto ret = mPowerHintSession->reportActualWorkDuration(mPowerHintQueue); - if (!ret.isOk()) { - ALOGW("Failed to report actual work durations with error: %s", - ret.exceptionMessage().c_str()); - mShouldReconnectHal = true; - } - mPowerHintQueue.clear(); -} - -bool AidlPowerHalWrapper::shouldReconnectHAL() { - return mShouldReconnectHal; -} - -std::vector AidlPowerHalWrapper::getPowerHintSessionThreadIds() { - return mPowerHintThreadIds; -} - -std::optional AidlPowerHalWrapper::getTargetWorkDuration() { - return mTargetDuration; -} - -const bool AidlPowerHalWrapper::sTraceHintSessionData = +const bool PowerAdvisor::sTraceHintSessionData = base::GetBoolProperty(std::string("debug.sf.trace_hint_sessions"), false); const Duration PowerAdvisor::sTargetSafetyMargin = std::chrono::microseconds( base::GetIntProperty("debug.sf.hint_margin_us", ticks(PowerAdvisor::kDefaultTargetSafetyMargin))); -PowerAdvisor::HalWrapper* PowerAdvisor::getPowerHal() { - if (!mHasHal) { - return nullptr; - } - - // Grab old hint session values before we destroy any existing wrapper - std::vector oldPowerHintSessionThreadIds; - std::optional oldTargetWorkDuration; - - if (mHalWrapper != nullptr) { - oldPowerHintSessionThreadIds = mHalWrapper->getPowerHintSessionThreadIds(); - oldTargetWorkDuration = mHalWrapper->getTargetWorkDuration(); - } - - // If we used to have a HAL, but it stopped responding, attempt to reconnect - if (mReconnectPowerHal) { - mHalWrapper = nullptr; - mReconnectPowerHal = false; - } - - if (mHalWrapper != nullptr) { - auto wrapper = mHalWrapper.get(); - // If the wrapper is fine, return it, but if it indicates a reconnect, remake it - if (!wrapper->shouldReconnectHAL()) { - return wrapper; - } - ALOGD("Reconnecting Power HAL"); - mHalWrapper = nullptr; - } - - // At this point, we know for sure there is no running session - mPowerHintSessionRunning = false; - - // First attempt to connect to the AIDL Power HAL - mHalWrapper = AidlPowerHalWrapper::connect(); - - // If that didn't succeed, attempt to connect to the HIDL Power HAL - if (mHalWrapper == nullptr) { - mHalWrapper = HidlPowerHalWrapper::connect(); - } else { - ALOGD("Successfully connecting AIDL Power HAL"); - // If AIDL, pass on any existing hint session values - mHalWrapper->setPowerHintSessionThreadIds(oldPowerHintSessionThreadIds); - // Only set duration and start if duration is defined - if (oldTargetWorkDuration.has_value()) { - mHalWrapper->setTargetWorkDuration(*oldTargetWorkDuration); - // Only start if possible to run and both threadids and duration are defined - if (usePowerHintSession() && !oldPowerHintSessionThreadIds.empty()) { - mPowerHintSessionRunning = mHalWrapper->startPowerHintSession(); - } - } - } - - // If we make it to this point and still don't have a HAL, it's unlikely we - // will, so stop trying - if (mHalWrapper == nullptr) { - mHasHal = false; - } - - return mHalWrapper.get(); +power::PowerHalController& PowerAdvisor::getPowerHal() { + static std::once_flag halFlag; + std::call_once(halFlag, [this] { mPowerHal->init(); }); + return *mPowerHal; } } // namespace impl diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h index c4cfdc3482..7a0d4267fe 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h @@ -27,6 +27,7 @@ #include #include +#include #include #include #include "../Scheduler/OneShotTimer.h" @@ -52,15 +53,14 @@ public: // Checks both if it supports and if it's enabled virtual bool usePowerHintSession() = 0; virtual bool supportsPowerHintSession() = 0; - virtual bool isPowerHintSessionRunning() = 0; + + virtual bool ensurePowerHintSessionRunning() = 0; // Sends a power hint that updates to the target work duration for the frame - virtual void setTargetWorkDuration(Duration targetDuration) = 0; + virtual void updateTargetWorkDuration(Duration targetDuration) = 0; // Sends a power hint for the actual known work duration at the end of the frame - virtual void sendActualWorkDuration() = 0; - // Sends a power hint for the upcoming frame predicted from previous frame timing - virtual void sendPredictedWorkDuration() = 0; + virtual void reportActualWorkDuration() = 0; // Sets whether the power hint session is enabled - virtual void enablePowerHint(bool enabled) = 0; + virtual void enablePowerHintSession(bool enabled) = 0; // Initializes the power hint session virtual bool startPowerHintSession(const std::vector& threadIds) = 0; // Provides PowerAdvisor with a copy of the gpu fence so it can determine the gpu end time @@ -101,24 +101,6 @@ namespace impl { // full state of the system when sending out power hints to things like the GPU. class PowerAdvisor final : public Hwc2::PowerAdvisor { public: - class HalWrapper { - public: - virtual ~HalWrapper() = default; - - virtual bool setExpensiveRendering(bool enabled) = 0; - virtual bool notifyDisplayUpdateImminentAndCpuReset() = 0; - virtual bool supportsPowerHintSession() = 0; - virtual bool isPowerHintSessionRunning() = 0; - virtual void restartPowerHintSession() = 0; - virtual void setPowerHintSessionThreadIds(const std::vector& threadIds) = 0; - virtual bool startPowerHintSession() = 0; - virtual void setTargetWorkDuration(Duration targetDuration) = 0; - virtual void sendActualWorkDuration(Duration actualDuration, TimePoint timestamp) = 0; - virtual bool shouldReconnectHAL() = 0; - virtual std::vector getPowerHintSessionThreadIds() = 0; - virtual std::optional getTargetWorkDuration() = 0; - }; - PowerAdvisor(SurfaceFlinger& flinger); ~PowerAdvisor() override; @@ -129,11 +111,10 @@ public: void notifyDisplayUpdateImminentAndCpuReset() override; bool usePowerHintSession() override; bool supportsPowerHintSession() override; - bool isPowerHintSessionRunning() override; - void setTargetWorkDuration(Duration targetDuration) override; - void sendActualWorkDuration() override; - void sendPredictedWorkDuration() override; - void enablePowerHint(bool enabled) override; + bool ensurePowerHintSessionRunning() override; + void updateTargetWorkDuration(Duration targetDuration) override; + void reportActualWorkDuration() override; + void enablePowerHintSession(bool enabled) override; bool startPowerHintSession(const std::vector& threadIds) override; void setGpuFenceTime(DisplayId displayId, std::unique_ptr&& fenceTime); void setHwcValidateTiming(DisplayId displayId, TimePoint validateStartTime, @@ -155,15 +136,7 @@ public: private: friend class PowerAdvisorTest; - // Tracks if powerhal exists - bool mHasHal = true; - // Holds the hal wrapper for getPowerHal - std::unique_ptr mHalWrapper GUARDED_BY(mPowerHalMutex) = nullptr; - - HalWrapper* getPowerHal() REQUIRES(mPowerHalMutex); - bool mReconnectPowerHal GUARDED_BY(mPowerHalMutex) = false; - std::mutex mPowerHalMutex; - + std::unique_ptr mPowerHal; std::atomic_bool mBootFinished = false; std::unordered_set mExpensiveDisplays; @@ -189,9 +162,6 @@ private: Duration postPresentFenceHwcPresentDuration{0ns}; // Are we likely to have waited for the present fence during composition bool probablyWaitsForPresentFence = false; - // Estimate one frame's timeline from that of a previous frame - DisplayTimeline estimateTimelineFromReference(TimePoint fenceTime, - TimePoint displayStartTime); }; struct GpuTimeline { @@ -243,8 +213,7 @@ private: std::vector getOrderedDisplayIds( std::optional DisplayTimingData::*sortBy); // Estimates a frame's total work duration including gpu time. - // Runs either at the beginning or end of a frame, using the most recent data available - std::optional estimateWorkDuration(bool earlyHint); + std::optional estimateWorkDuration(); // There are two different targets and actual work durations we care about, // this normalizes them together and takes the max of the two Duration combineTimingEstimates(Duration totalDuration, Duration flingerDuration); @@ -268,68 +237,41 @@ private: // Updated list of display IDs std::vector mDisplayIds; - std::optional mPowerHintEnabled; - std::optional mSupportsPowerHint; - bool mPowerHintSessionRunning = false; - - // An adjustable safety margin which pads the "actual" value sent to PowerHAL, - // encouraging more aggressive boosting to give SurfaceFlinger a larger margin for error - static const Duration sTargetSafetyMargin; - static constexpr const Duration kDefaultTargetSafetyMargin{1ms}; - - // How long we expect hwc to run after the present call until it waits for the fence - static constexpr const Duration kFenceWaitStartDelayValidated{150us}; - static constexpr const Duration kFenceWaitStartDelaySkippedValidate{250us}; -}; - -class AidlPowerHalWrapper : public PowerAdvisor::HalWrapper { -public: - explicit AidlPowerHalWrapper(sp powerHal); - ~AidlPowerHalWrapper() override; - - static std::unique_ptr connect(); + // Ensure powerhal connection is initialized + power::PowerHalController& getPowerHal(); - bool setExpensiveRendering(bool enabled) override; - bool notifyDisplayUpdateImminentAndCpuReset() override; - bool supportsPowerHintSession() override; - bool isPowerHintSessionRunning() override; - void restartPowerHintSession() override; - void setPowerHintSessionThreadIds(const std::vector& threadIds) override; - bool startPowerHintSession() override; - void setTargetWorkDuration(Duration targetDuration) override; - void sendActualWorkDuration(Duration actualDuration, TimePoint timestamp) override; - bool shouldReconnectHAL() override; - std::vector getPowerHintSessionThreadIds() override; - std::optional getTargetWorkDuration() override; - -private: - friend class AidlPowerHalWrapperTest; + std::optional mHintSessionEnabled; + std::optional mSupportsHintSession; + bool mHintSessionRunning = false; - bool checkPowerHintSessionSupported(); - void closePowerHintSession(); + std::mutex mHintSessionMutex; + sp mHintSession GUARDED_BY(mHintSessionMutex) = nullptr; - const sp mPowerHal = nullptr; - bool mHasExpensiveRendering = false; - bool mHasDisplayUpdateImminent = false; - // Used to indicate an error state and need for reconstruction - bool mShouldReconnectHal = false; - - // Power hint session data - - // Concurrent access for this is protected by mPowerHalMutex - sp mPowerHintSession = nullptr; + // Initialize to true so we try to call, to check if it's supported + bool mHasExpensiveRendering = true; + bool mHasDisplayUpdateImminent = true; // Queue of actual durations saved to report - std::vector mPowerHintQueue; + std::vector mHintSessionQueue; // The latest values we have received for target and actual Duration mTargetDuration = kDefaultTargetDuration; std::optional mActualDuration; // The list of thread ids, stored so we can restart the session from this class if needed - std::vector mPowerHintThreadIds; - bool mSupportsPowerHint = false; + std::vector mHintSessionThreadIds; Duration mLastTargetDurationSent = kDefaultTargetDuration; // Whether we should emit ATRACE_INT data for hint sessions static const bool sTraceHintSessionData; - static constexpr Duration kDefaultTargetDuration{16ms}; + + // Default target duration for the hint session + static constexpr const Duration kDefaultTargetDuration{16ms}; + + // An adjustable safety margin which pads the "actual" value sent to PowerHAL, + // encouraging more aggressive boosting to give SurfaceFlinger a larger margin for error + static const Duration sTargetSafetyMargin; + static constexpr const Duration kDefaultTargetSafetyMargin{1ms}; + + // How long we expect hwc to run after the present call until it waits for the fence + static constexpr const Duration kFenceWaitStartDelayValidated{150us}; + static constexpr const Duration kFenceWaitStartDelaySkippedValidate{250us}; }; } // namespace impl diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 9ceb0afa20..ce8125d2f9 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -478,10 +478,6 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI mIgnoreHdrCameraLayers = ignore_hdr_camera_layers(false); - // Power hint session mode, representing which hint(s) to send: early, late, or both) - mPowerHintSessionMode = - {.late = base::GetBoolProperty("debug.sf.send_late_power_session_hint"s, true), - .early = base::GetBoolProperty("debug.sf.send_early_power_session_hint"s, false)}; mLayerLifecycleManagerEnabled = base::GetBoolProperty("persist.debug.sf.enable_layer_lifecycle_manager"s, false); mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled || @@ -715,12 +711,12 @@ void SurfaceFlinger::bootFinished() { readPersistentProperties(); mPowerAdvisor->onBootFinished(); - const bool powerHintEnabled = mFlagManager.use_adpf_cpu_hint(); - mPowerAdvisor->enablePowerHint(powerHintEnabled); - const bool powerHintUsed = mPowerAdvisor->usePowerHintSession(); + const bool hintSessionEnabled = mFlagManager.use_adpf_cpu_hint(); + mPowerAdvisor->enablePowerHintSession(hintSessionEnabled); + const bool hintSessionUsed = mPowerAdvisor->usePowerHintSession(); ALOGD("Power hint is %s", - powerHintUsed ? "supported" : (powerHintEnabled ? "unsupported" : "disabled")); - if (powerHintUsed) { + hintSessionUsed ? "supported" : (hintSessionEnabled ? "unsupported" : "disabled")); + if (hintSessionUsed) { std::optional renderEngineTid = getRenderEngine().getRenderEngineTid(); std::vector tidList; tidList.emplace_back(gettid()); @@ -2461,16 +2457,7 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe mPowerAdvisor->setFrameDelay(frameDelay); mPowerAdvisor->setTotalFrameTargetWorkDuration(idealSfWorkDuration); - - const auto& display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get(); - const Period vsyncPeriod = display->getActiveMode().fps.getPeriod(); - mPowerAdvisor->setTargetWorkDuration(vsyncPeriod); - - // Send early hint here to make sure there's not another frame pending - if (mPowerHintSessionMode.early) { - // Send a rough prediction for this frame based on last frame's timing info - mPowerAdvisor->sendPredictedWorkDuration(); - } + mPowerAdvisor->updateTargetWorkDuration(vsyncPeriod); } if (mRefreshRateOverlaySpinner) { @@ -2669,9 +2656,7 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) mPowerAdvisor->setSfPresentTiming(TimePoint::fromNs(mPreviousPresentFences[0] .fenceTime->getSignalTime()), TimePoint::now()); - if (mPowerHintSessionMode.late) { - mPowerAdvisor->sendActualWorkDuration(); - } + mPowerAdvisor->reportActualWorkDuration(); } if (mScheduler->onPostComposition(presentTime)) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 74d00dd60c..03e188beeb 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1408,10 +1408,6 @@ private: // These classes do not store any client state but help with managing transaction callbacks // and stats. std::unordered_map> mLegacyLayers; - struct { - bool late = false; - bool early = false; - } mPowerHintSessionMode; TransactionHandler mTransactionHandler; display::DisplayMap mFrontEndDisplayInfos; diff --git a/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp b/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp deleted file mode 100644 index 513f77989b..0000000000 --- a/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#undef LOG_TAG -#define LOG_TAG "AidlPowerHalWrapperTest" - -#include -#include -#include -#include -#include -#include -#include -#include -#include "DisplayHardware/PowerAdvisor.h" -#include "android/hardware/power/WorkDuration.h" -#include "binder/Status.h" -#include "log/log_main.h" -#include "mock/DisplayHardware/MockIPower.h" -#include "mock/DisplayHardware/MockIPowerHintSession.h" -#include "utils/Timers.h" - -using namespace android; -using namespace android::Hwc2::mock; -using namespace android::hardware::power; -using namespace std::chrono_literals; -using namespace testing; - -namespace android::Hwc2::impl { - -class AidlPowerHalWrapperTest : public testing::Test { -public: - void SetUp() override; - -protected: - std::unique_ptr mWrapper = nullptr; - sp> mMockHal = nullptr; - sp> mMockSession = nullptr; - void verifyAndClearExpectations(); - void sendActualWorkDurationGroup(std::vector durations); - static constexpr std::chrono::duration kStaleTimeout = 100ms; -}; - -void AidlPowerHalWrapperTest::SetUp() { - mMockHal = sp>::make(); - mMockSession = sp>::make(); - ON_CALL(*mMockHal.get(), getHintSessionPreferredRate(_)).WillByDefault(Return(Status::ok())); - mWrapper = std::make_unique(mMockHal); -} - -void AidlPowerHalWrapperTest::verifyAndClearExpectations() { - Mock::VerifyAndClearExpectations(mMockHal.get()); - Mock::VerifyAndClearExpectations(mMockSession.get()); -} - -void AidlPowerHalWrapperTest::sendActualWorkDurationGroup(std::vector durations) { - for (size_t i = 0; i < durations.size(); i++) { - auto duration = durations[i]; - mWrapper->sendActualWorkDuration(Duration::fromNs(duration.durationNanos), - TimePoint::fromNs(duration.timeStampNanos)); - } -} - -WorkDuration toWorkDuration(std::chrono::nanoseconds durationNanos, int64_t timeStampNanos) { - WorkDuration duration; - duration.durationNanos = durationNanos.count(); - duration.timeStampNanos = timeStampNanos; - return duration; -} - -WorkDuration toWorkDuration(std::pair timePair) { - return toWorkDuration(timePair.first, timePair.second); -} - -std::string printWorkDurations(const ::std::vector& durations) { - std::ostringstream os; - for (auto duration : durations) { - os << duration.toString(); - os << "\n"; - } - return os.str(); -} - -namespace { -TEST_F(AidlPowerHalWrapperTest, supportsPowerHintSession) { - ASSERT_TRUE(mWrapper->supportsPowerHintSession()); - Mock::VerifyAndClearExpectations(mMockHal.get()); - ON_CALL(*mMockHal.get(), getHintSessionPreferredRate(_)) - .WillByDefault(Return(Status::fromExceptionCode(Status::Exception::EX_ILLEGAL_STATE))); - auto newWrapper = AidlPowerHalWrapper(mMockHal); - EXPECT_FALSE(newWrapper.supportsPowerHintSession()); -} - -TEST_F(AidlPowerHalWrapperTest, startPowerHintSession) { - ASSERT_TRUE(mWrapper->supportsPowerHintSession()); - std::vector threadIds = {1, 2}; - mWrapper->setPowerHintSessionThreadIds(threadIds); - EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _)) - .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok()))); - EXPECT_TRUE(mWrapper->startPowerHintSession()); - EXPECT_FALSE(mWrapper->startPowerHintSession()); -} - -TEST_F(AidlPowerHalWrapperTest, restartNewPowerHintSessionWithNewThreadIds) { - ASSERT_TRUE(mWrapper->supportsPowerHintSession()); - - std::vector threadIds = {1, 2}; - EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _)) - .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok()))); - mWrapper->setPowerHintSessionThreadIds(threadIds); - EXPECT_EQ(mWrapper->getPowerHintSessionThreadIds(), threadIds); - ASSERT_TRUE(mWrapper->startPowerHintSession()); - verifyAndClearExpectations(); - - threadIds = {2, 3}; - EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _)) - .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok()))); - EXPECT_CALL(*mMockSession.get(), close()).Times(1); - mWrapper->setPowerHintSessionThreadIds(threadIds); - EXPECT_EQ(mWrapper->getPowerHintSessionThreadIds(), threadIds); - verifyAndClearExpectations(); - - EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _)).Times(0); - EXPECT_CALL(*mMockSession.get(), close()).Times(0); - mWrapper->setPowerHintSessionThreadIds(threadIds); - verifyAndClearExpectations(); -} - -TEST_F(AidlPowerHalWrapperTest, setTargetWorkDuration) { - ASSERT_TRUE(mWrapper->supportsPowerHintSession()); - - std::vector threadIds = {1, 2}; - mWrapper->setPowerHintSessionThreadIds(threadIds); - EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _)) - .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok()))); - ASSERT_TRUE(mWrapper->startPowerHintSession()); - verifyAndClearExpectations(); - - std::chrono::nanoseconds base = 100ms; - // test cases with target work duration and whether it should update hint against baseline 100ms - const std::vector> testCases = - {{0ms, true}, {-1ms, true}, {200ms, true}, {2ms, true}, {100ms, false}, {109ms, true}}; - - for (const auto& test : testCases) { - // reset to 100ms baseline - mWrapper->setTargetWorkDuration(1ns); - mWrapper->setTargetWorkDuration(base); - - std::chrono::nanoseconds target = test.first; - EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(target.count())) - .Times(test.second ? 1 : 0); - mWrapper->setTargetWorkDuration(target); - verifyAndClearExpectations(); - } -} - -TEST_F(AidlPowerHalWrapperTest, setTargetWorkDuration_shouldReconnectOnError) { - ASSERT_TRUE(mWrapper->supportsPowerHintSession()); - - std::vector threadIds = {1, 2}; - mWrapper->setPowerHintSessionThreadIds(threadIds); - EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _)) - .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok()))); - ASSERT_TRUE(mWrapper->startPowerHintSession()); - verifyAndClearExpectations(); - - EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(1)) - .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_ILLEGAL_STATE))); - mWrapper->setTargetWorkDuration(1ns); - EXPECT_TRUE(mWrapper->shouldReconnectHAL()); -} - -TEST_F(AidlPowerHalWrapperTest, sendActualWorkDuration) { - ASSERT_TRUE(mWrapper->supportsPowerHintSession()); - - std::vector threadIds = {1, 2}; - mWrapper->setPowerHintSessionThreadIds(threadIds); - EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _)) - .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok()))); - ASSERT_TRUE(mWrapper->startPowerHintSession()); - verifyAndClearExpectations(); - - auto base = toWorkDuration(100ms, 0); - // test cases with actual work durations and whether it should update hint against baseline - // 100ms - const std::vector>, bool>> - testCases = {{{{-1ms, 100}}, false}, - {{{50ms, 100}}, true}, - {{{100ms, 100}, {200ms, 200}}, true}, - {{{100ms, 500}, {100ms, 600}, {3ms, 600}}, true}}; - - for (const auto& test : testCases) { - // reset actual duration - sendActualWorkDurationGroup({base}); - - auto raw = test.first; - std::vector durations(raw.size()); - std::transform(raw.begin(), raw.end(), durations.begin(), - [](auto d) { return toWorkDuration(d); }); - for (auto& duration : durations) { - EXPECT_CALL(*mMockSession.get(), - reportActualWorkDuration(std::vector{duration})) - .Times(test.second ? 1 : 0); - } - sendActualWorkDurationGroup(durations); - verifyAndClearExpectations(); - } -} - -TEST_F(AidlPowerHalWrapperTest, sendActualWorkDuration_shouldReconnectOnError) { - ASSERT_TRUE(mWrapper->supportsPowerHintSession()); - - std::vector threadIds = {1, 2}; - mWrapper->setPowerHintSessionThreadIds(threadIds); - EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _)) - .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok()))); - ASSERT_TRUE(mWrapper->startPowerHintSession()); - verifyAndClearExpectations(); - WorkDuration duration; - duration.durationNanos = 1; - EXPECT_CALL(*mMockSession.get(), reportActualWorkDuration(_)) - .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_ILLEGAL_STATE))); - sendActualWorkDurationGroup({duration}); - EXPECT_TRUE(mWrapper->shouldReconnectHAL()); -} - -} // namespace -} // namespace android::Hwc2::impl diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index df3ffd288c..201d37ff7e 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -24,7 +24,7 @@ package { filegroup { name: "libsurfaceflinger_mock_sources", srcs: [ - "mock/DisplayHardware/MockAidlPowerHalWrapper.cpp", + "mock/DisplayHardware/MockPowerHalController.cpp", "mock/DisplayHardware/MockComposer.cpp", "mock/DisplayHardware/MockHWC2.cpp", "mock/DisplayHardware/MockIPower.cpp", @@ -70,7 +70,6 @@ cc_test { ":libsurfaceflinger_mock_sources", ":libsurfaceflinger_sources", "libsurfaceflinger_unittest_main.cpp", - "AidlPowerHalWrapperTest.cpp", "CompositionTest.cpp", "DisplayIdGeneratorTest.cpp", "DisplayTransactionTest.cpp", @@ -196,6 +195,7 @@ cc_defaults { "libinput", "liblog", "libnativewindow", + "libpowermanager", "libprocessgroup", "libprotobuf-cpp-lite", "libSurfaceFlingerProp", diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp index d22ce17258..0d66d59f26 100644 --- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp +++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp @@ -25,13 +25,15 @@ #include #include #include "TestableSurfaceFlinger.h" -#include "mock/DisplayHardware/MockAidlPowerHalWrapper.h" +#include "mock/DisplayHardware/MockIPowerHintSession.h" +#include "mock/DisplayHardware/MockPowerHalController.h" using namespace android; using namespace android::Hwc2::mock; using namespace android::hardware::power; using namespace std::chrono_literals; using namespace testing; +using namespace android::power; namespace android::Hwc2::impl { @@ -47,23 +49,27 @@ public: protected: TestableSurfaceFlinger mFlinger; std::unique_ptr mPowerAdvisor; - NiceMock* mMockAidlWrapper; + MockPowerHalController* mMockPowerHalController; + sp mMockPowerHintSession; }; -void PowerAdvisorTest::SetUp() FTL_FAKE_GUARD(mPowerAdvisor->mPowerHalMutex) { - std::unique_ptr mockAidlWrapper = - std::make_unique>(); - mPowerAdvisor = std::make_unique(*mFlinger.flinger()); - ON_CALL(*mockAidlWrapper.get(), supportsPowerHintSession()).WillByDefault(Return(true)); - ON_CALL(*mockAidlWrapper.get(), startPowerHintSession()).WillByDefault(Return(true)); - mPowerAdvisor->mHalWrapper = std::move(mockAidlWrapper); - mMockAidlWrapper = - reinterpret_cast*>(mPowerAdvisor->mHalWrapper.get()); +void PowerAdvisorTest::SetUp() { + mPowerAdvisor = std::make_unique(*mFlinger.flinger()); + mPowerAdvisor->mPowerHal = std::make_unique>(); + mMockPowerHalController = + reinterpret_cast(mPowerAdvisor->mPowerHal.get()); + ON_CALL(*mMockPowerHalController, getHintSessionPreferredRate) + .WillByDefault(Return(HalResult::fromStatus(binder::Status::ok(), 16000))); } void PowerAdvisorTest::startPowerHintSession() { const std::vector threadIds = {1, 2, 3}; - mPowerAdvisor->enablePowerHint(true); + mMockPowerHintSession = android::sp>::make(); + ON_CALL(*mMockPowerHalController, createHintSession) + .WillByDefault( + Return(HalResult>::fromStatus(binder::Status::ok(), + mMockPowerHintSession))); + mPowerAdvisor->enablePowerHintSession(true); mPowerAdvisor->startPowerHintSession(threadIds); } @@ -76,9 +82,7 @@ void PowerAdvisorTest::setExpectedTiming(Duration totalFrameTargetDuration, void PowerAdvisorTest::fakeBasicFrameTiming(TimePoint startTime, Duration vsyncPeriod) { mPowerAdvisor->setCommitStart(startTime); mPowerAdvisor->setFrameDelay(0ns); - mPowerAdvisor->setTargetWorkDuration(vsyncPeriod); - ON_CALL(*mMockAidlWrapper, getTargetWorkDuration()) - .WillByDefault(Return(std::make_optional(vsyncPeriod))); + mPowerAdvisor->updateTargetWorkDuration(vsyncPeriod); } Duration PowerAdvisorTest::getFenceWaitDelayDuration(bool skipValidate) { @@ -116,7 +120,10 @@ TEST_F(PowerAdvisorTest, hintSessionUseHwcDisplay) { startTime += vsyncPeriod; const Duration expectedDuration = getErrorMargin() + presentDuration + postCompDuration; - EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1); + EXPECT_CALL(*mMockPowerHintSession, + reportActualWorkDuration(ElementsAre( + Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns()))))) + .Times(1); fakeBasicFrameTiming(startTime, vsyncPeriod); setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod); @@ -124,7 +131,7 @@ TEST_F(PowerAdvisorTest, hintSessionUseHwcDisplay) { mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1ms, startTime + 1500us); mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2ms, startTime + 2500us); mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration); - mPowerAdvisor->sendActualWorkDuration(); + mPowerAdvisor->reportActualWorkDuration(); } TEST_F(PowerAdvisorTest, hintSessionSubtractsHwcFenceTime) { @@ -153,7 +160,10 @@ TEST_F(PowerAdvisorTest, hintSessionSubtractsHwcFenceTime) { const Duration expectedDuration = getErrorMargin() + presentDuration + getFenceWaitDelayDuration(false) - hwcBlockedDuration + postCompDuration; - EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1); + EXPECT_CALL(*mMockPowerHintSession, + reportActualWorkDuration(ElementsAre( + Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns()))))) + .Times(1); fakeBasicFrameTiming(startTime, vsyncPeriod); setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod); @@ -163,7 +173,7 @@ TEST_F(PowerAdvisorTest, hintSessionSubtractsHwcFenceTime) { // now report the fence as having fired during the display HWC time mPowerAdvisor->setSfPresentTiming(startTime + 2ms + hwcBlockedDuration, startTime + presentDuration); - mPowerAdvisor->sendActualWorkDuration(); + mPowerAdvisor->reportActualWorkDuration(); } TEST_F(PowerAdvisorTest, hintSessionUsingSecondaryVirtualDisplays) { @@ -192,7 +202,10 @@ TEST_F(PowerAdvisorTest, hintSessionUsingSecondaryVirtualDisplays) { startTime += vsyncPeriod; const Duration expectedDuration = getErrorMargin() + presentDuration + postCompDuration; - EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1); + EXPECT_CALL(*mMockPowerHintSession, + reportActualWorkDuration(ElementsAre( + Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns()))))) + .Times(1); fakeBasicFrameTiming(startTime, vsyncPeriod); setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod); @@ -202,7 +215,7 @@ TEST_F(PowerAdvisorTest, hintSessionUsingSecondaryVirtualDisplays) { mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1ms, startTime + 1500us); mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2ms, startTime + 2500us); mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration); - mPowerAdvisor->sendActualWorkDuration(); + mPowerAdvisor->reportActualWorkDuration(); } } // namespace diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp index 7839ef0dbb..d0290ea03e 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp @@ -70,7 +70,6 @@ void SurfaceFlingerPowerHintTest::SetUp() { mFlinger.setupRenderEngine(std::unique_ptr(mRenderEngine)); mFlinger.setupTimeStats(std::shared_ptr(mTimeStats)); mFlinger.setupComposer(std::unique_ptr(mComposer)); - mFlinger.setPowerHintSessionMode(true, true); mFlinger.setupPowerAdvisor(std::unique_ptr(mPowerAdvisor)); static constexpr bool kIsPrimary = true; FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, hal::DisplayType::PHYSICAL, kIsPrimary) @@ -100,7 +99,7 @@ void SurfaceFlingerPowerHintTest::SetUp() { TEST_F(SurfaceFlingerPowerHintTest, sendDurationsIncludingHwcWaitTime) { ON_CALL(*mPowerAdvisor, usePowerHintSession()).WillByDefault(Return(true)); - EXPECT_CALL(*mPowerAdvisor, setTargetWorkDuration(_)).Times(1); + EXPECT_CALL(*mPowerAdvisor, updateTargetWorkDuration(_)).Times(1); EXPECT_CALL(*mDisplaySurface, prepareFrame(compositionengine::DisplaySurface::CompositionType::Hwc)) .Times(1); @@ -109,7 +108,7 @@ TEST_F(SurfaceFlingerPowerHintTest, sendDurationsIncludingHwcWaitTime) { std::this_thread::sleep_for(kMockHwcRunTime); return hardware::graphics::composer::V2_1::Error::NONE; }); - EXPECT_CALL(*mPowerAdvisor, sendActualWorkDuration()).Times(1); + EXPECT_CALL(*mPowerAdvisor, reportActualWorkDuration()).Times(1); const TimePoint frameTime = scheduler::SchedulerClock::now(); constexpr Period kMockVsyncPeriod = 15ms; @@ -121,7 +120,7 @@ TEST_F(SurfaceFlingerPowerHintTest, inactiveOnDisplayDoze) { mDisplay->setPowerMode(hal::PowerMode::DOZE); - EXPECT_CALL(*mPowerAdvisor, setTargetWorkDuration(_)).Times(0); + EXPECT_CALL(*mPowerAdvisor, updateTargetWorkDuration(_)).Times(0); EXPECT_CALL(*mDisplaySurface, prepareFrame(compositionengine::DisplaySurface::CompositionType::Hwc)) .Times(1); @@ -130,7 +129,7 @@ TEST_F(SurfaceFlingerPowerHintTest, inactiveOnDisplayDoze) { std::this_thread::sleep_for(kMockHwcRunTime); return hardware::graphics::composer::V2_1::Error::NONE; }); - EXPECT_CALL(*mPowerAdvisor, sendActualWorkDuration()).Times(0); + EXPECT_CALL(*mPowerAdvisor, reportActualWorkDuration()).Times(0); const TimePoint frameTime = scheduler::SchedulerClock::now(); constexpr Period kMockVsyncPeriod = 15ms; diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index fc9e653ba4..bbb355469c 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -344,10 +344,6 @@ public: layer->mDrawingParent = drawingParent; } - void setPowerHintSessionMode(bool early, bool late) { - mFlinger->mPowerHintSessionMode = {.late = late, .early = early}; - } - /* ------------------------------------------------------------------------ * Forwarding for functions being tested */ diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h deleted file mode 100644 index 3ed85e0b1f..0000000000 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -#include "DisplayHardware/PowerAdvisor.h" - -namespace android { -namespace hardware { -namespace power { -class IPower; -} -} // namespace hardware -} // namespace android - -namespace android::Hwc2::mock { - -class MockAidlPowerHalWrapper : public Hwc2::impl::AidlPowerHalWrapper { -public: - MockAidlPowerHalWrapper(); - ~MockAidlPowerHalWrapper() override; - MOCK_METHOD(bool, setExpensiveRendering, (bool enabled), (override)); - MOCK_METHOD(bool, notifyDisplayUpdateImminentAndCpuReset, (), (override)); - MOCK_METHOD(bool, supportsPowerHintSession, (), (override)); - MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override)); - MOCK_METHOD(void, restartPowerHintSession, (), (override)); - MOCK_METHOD(void, setPowerHintSessionThreadIds, (const std::vector& threadIds), - (override)); - MOCK_METHOD(bool, startPowerHintSession, (), (override)); - MOCK_METHOD(void, setTargetWorkDuration, (Duration targetDuration), (override)); - MOCK_METHOD(void, sendActualWorkDuration, (Duration actualDuration, TimePoint timestamp), - (override)); - MOCK_METHOD(bool, shouldReconnectHAL, (), (override)); - MOCK_METHOD(std::vector, getPowerHintSessionThreadIds, (), (override)); - MOCK_METHOD(std::optional, getTargetWorkDuration, (), (override)); -}; - -} // namespace android::Hwc2::mock \ No newline at end of file diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h index 7fc625c4b6..3caa2b9847 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h @@ -35,11 +35,10 @@ public: MOCK_METHOD(void, notifyDisplayUpdateImminentAndCpuReset, (), (override)); MOCK_METHOD(bool, usePowerHintSession, (), (override)); MOCK_METHOD(bool, supportsPowerHintSession, (), (override)); - MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override)); - MOCK_METHOD(void, setTargetWorkDuration, (Duration targetDuration), (override)); - MOCK_METHOD(void, sendActualWorkDuration, (), (override)); - MOCK_METHOD(void, sendPredictedWorkDuration, (), (override)); - MOCK_METHOD(void, enablePowerHint, (bool enabled), (override)); + MOCK_METHOD(bool, ensurePowerHintSessionRunning, (), (override)); + MOCK_METHOD(void, updateTargetWorkDuration, (Duration targetDuration), (override)); + MOCK_METHOD(void, reportActualWorkDuration, (), (override)); + MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override)); MOCK_METHOD(bool, startPowerHintSession, (const std::vector& threadIds), (override)); MOCK_METHOD(void, setGpuFenceTime, (DisplayId displayId, std::unique_ptr&& fenceTime), (override)); diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.cpp b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.cpp similarity index 67% rename from services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.cpp rename to services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.cpp index 5049b1d367..3ec5c2d09b 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.cpp +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,11 @@ * limitations under the License. */ -#include "MockAidlPowerHalWrapper.h" -#include "MockIPower.h" +#include "MockPowerHalController.h" namespace android::Hwc2::mock { -MockAidlPowerHalWrapper::MockAidlPowerHalWrapper() - : AidlPowerHalWrapper(sp>::make()){}; -MockAidlPowerHalWrapper::~MockAidlPowerHalWrapper() = default; +MockPowerHalController::MockPowerHalController() = default; +MockPowerHalController::~MockPowerHalController() = default; } // namespace android::Hwc2::mock diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h new file mode 100644 index 0000000000..358395d323 --- /dev/null +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include + +namespace android { +namespace hardware { +namespace power { +class IPower; +} +} // namespace hardware +} // namespace android + +namespace android::Hwc2::mock { + +using android::hardware::power::Boost; +using android::hardware::power::Mode; +using android::power::HalResult; + +class MockPowerHalController : public power::PowerHalController { +public: + MockPowerHalController(); + ~MockPowerHalController() override; + MOCK_METHOD(HalResult, setBoost, (Boost, int32_t), (override)); + MOCK_METHOD(HalResult, setMode, (Mode, bool), (override)); + MOCK_METHOD(HalResult>, createHintSession, + (int32_t, int32_t, const std::vector&, int64_t), (override)); + MOCK_METHOD(HalResult, getHintSessionPreferredRate, (), (override)); +}; + +} // namespace android::Hwc2::mock \ No newline at end of file -- GitLab From 7d9cb5a6a67601e1990de2eda9f25c8d3a2e8909 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 14 Mar 2023 21:18:07 +0000 Subject: [PATCH 1071/1310] TouchInputMapper: Use a fixed scale factor for external drawing tablets When using an external drawing tablet with a stylus, we expect there to be a direct mapping between a subsection of the drawing tablet and the entire display. In other words, we expect the user to be able to access all parts of the display from within the drawing tablet surface. We also expect to use the same scale factor in both directions so that angles are preserved between the drawing tablet's space and the display space. Moving a unit north on the drawing tablet should move the pointer by the same amount on the display as moving a unit east. External drawing tablets are almost always orientation un-aware. We ensure that the active area of the drawing tablet is configured so that the top-left point of that area (0, 0) always corresponds to the top-left corner of the rotated display space. Bug: 236798672 Test: atest inputflinger_tests Test: Wacom Inutos S drawing tablet Change-Id: I201874d66f276c57a7a7d0e23c14d5d13a2bf08d --- .../reader/mapper/TouchInputMapper.cpp | 18 +++++++++++++++--- .../reader/mapper/TouchInputMapper.h | 1 + 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 073c18babb..50736aa116 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -873,14 +873,26 @@ void TouchInputMapper::computeInputTransforms() { : ui::Transform(); // Step 4: Scale the raw coordinates to the display space. - // - Here, we assume that the raw surface of the touch device maps perfectly to the surface - // of the display panel. This is usually true for touchscreens. + // - In DIRECT mode, we assume that the raw surface of the touch device maps perfectly to + // the surface of the display panel. This is usually true for touchscreens. + // - In POINTER mode, we cannot assume that the display and the touch device have the same + // aspect ratio, since it is likely to be untrue for devices like external drawing tablets. + // In this case, we used a fixed scale so that 1) we use the same scale across both the x and + // y axes to ensure the mapping does not stretch gestures, and 2) the entire region of the + // display can be reached by the touch device. // - From this point onward, we are no longer in the discrete space of the raw coordinates but // are in the continuous space of the logical display. ui::Transform scaleRawToDisplay; const float xScale = static_cast(mViewport.deviceWidth) / rotatedRawSize.width; const float yScale = static_cast(mViewport.deviceHeight) / rotatedRawSize.height; - scaleRawToDisplay.set(xScale, 0, 0, yScale); + if (mDeviceMode == DeviceMode::DIRECT) { + scaleRawToDisplay.set(xScale, 0, 0, yScale); + } else if (mDeviceMode == DeviceMode::POINTER) { + const float fixedScale = std::max(xScale, yScale); + scaleRawToDisplay.set(fixedScale, 0, 0, fixedScale); + } else { + LOG_ALWAYS_FATAL("computeInputTransform can only be used for DIRECT and POINTER modes"); + } // Step 5: Undo the display rotation to bring us back to the un-rotated display coordinate space // that InputReader uses. diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index bc358b97e6..cdd51241e4 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -821,6 +821,7 @@ private: static void assignPointerIds(const RawState& last, RawState& current); + // Compute input transforms for DIRECT and POINTER modes. void computeInputTransforms(); void configureDeviceType(); -- GitLab From e71e570696473ff9f2729d1d827c9eb388b88195 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 29 Mar 2023 14:51:38 +0000 Subject: [PATCH 1072/1310] Fix display association for drawing tablets Drawing tablets are stylus devices that are configured in pointer mode. All touch devices, including styluses, are configured specifically for one display. Since a drawing tablet is supposed to show a pointer icon, it updates the PointerController. If the PointerController and the drawing tablet are not configured for the same display, it does not make sense to update the PointerController. We fix this inconsistency by only updating the PointerController's position for a drawing tablet when they're both configured for the same display. Bug: 236798672 Test: atest DrawingTabletTest Change-Id: I0a336dc456f6432a1e74e96a22ff57bb16a65ad7 --- .../reader/mapper/TouchInputMapper.cpp | 23 +++++++++++-------- .../reader/mapper/TouchInputMapper.h | 2 +- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 50736aa116..ecf50c3a3d 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -3491,14 +3491,19 @@ std::list TouchInputMapper::dispatchPointerStylus(nsecs_t when, nsec if (!mCurrentCookedState.stylusIdBits.isEmpty()) { uint32_t id = mCurrentCookedState.stylusIdBits.firstMarkedBit(); uint32_t index = mCurrentCookedState.cookedPointerData.idToIndex[id]; - mPointerController - ->setPosition(mCurrentCookedState.cookedPointerData.pointerCoords[index].getX(), - mCurrentCookedState.cookedPointerData.pointerCoords[index].getY()); - hovering = mCurrentCookedState.cookedPointerData.hoveringIdBits.hasBit(id); down = !hovering; - const auto [x, y] = mPointerController->getPosition(); + float x = mCurrentCookedState.cookedPointerData.pointerCoords[index].getX(); + float y = mCurrentCookedState.cookedPointerData.pointerCoords[index].getY(); + // Styluses are configured specifically for one display. We only update the + // PointerController for this stylus if the PointerController is configured for + // the same display as this stylus, + if (getAssociatedDisplayId() == mViewport.displayId) { + mPointerController->setPosition(x, y); + std::tie(x, y) = mPointerController->getPosition(); + } + mPointerSimple.currentCoords.copyFrom( mCurrentCookedState.cookedPointerData.pointerCoords[index]); mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x); @@ -3511,7 +3516,7 @@ std::list TouchInputMapper::dispatchPointerStylus(nsecs_t when, nsec hovering = false; } - return dispatchPointerSimple(when, readTime, policyFlags, down, hovering); + return dispatchPointerSimple(when, readTime, policyFlags, down, hovering, mViewport.displayId); } std::list TouchInputMapper::abortPointerStylus(nsecs_t when, nsecs_t readTime, @@ -3554,7 +3559,8 @@ std::list TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs hovering = false; } - return dispatchPointerSimple(when, readTime, policyFlags, down, hovering); + const int32_t displayId = mPointerController->getDisplayId(); + return dispatchPointerSimple(when, readTime, policyFlags, down, hovering, displayId); } std::list TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t readTime, @@ -3568,7 +3574,7 @@ std::list TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, bool down, - bool hovering) { + bool hovering, int32_t displayId) { LOG_ALWAYS_FATAL_IF(mDeviceMode != DeviceMode::POINTER, "%s cannot be used when the device is not in POINTER mode.", __func__); std::list out; @@ -3581,7 +3587,6 @@ std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsec } else if (!down && !hovering && (mPointerSimple.down || mPointerSimple.hovering)) { mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); } - int32_t displayId = mPointerController->getDisplayId(); const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index cdd51241e4..336d5243eb 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -789,7 +789,7 @@ private: [[nodiscard]] std::list dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, bool down, - bool hovering); + bool hovering, int32_t displayId); [[nodiscard]] std::list abortPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); -- GitLab From e1e309ace801c81e36df47c0a6ca76b9ea279990 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 29 Nov 2022 02:54:27 +0000 Subject: [PATCH 1073/1310] TouchInputMapper: Consume hovering pointers outside physical frame Similar to how we consume pointers that touch outside the physical frame, we consume the hovering pointers when all the hovering pointers are outside the physical frame. This means hovering over the unmapped area of an external drawing tablet will no longer produce hover events at the edge of the display. Bug: 236798672 Test: atest inputflinger_tests Test: manual with Wacom Intuos S Change-Id: I6a2deb90311a2c9a98789ccd8318297cb2346208 --- .../reader/mapper/TouchInputMapper.cpp | 23 ++++++++- .../inputflinger/tests/InputReader_test.cpp | 48 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index ecf50c3a3d..2e0f54455c 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -1855,6 +1855,27 @@ std::list TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t } } + if (!mCurrentRawState.rawPointerData.hoveringIdBits.isEmpty() && + mCurrentRawState.rawPointerData.touchingIdBits.isEmpty() && + mDeviceMode != DeviceMode::UNSCALED) { + // We have hovering pointers, and there are no touching pointers. + bool hoveringPointersInFrame = false; + auto hoveringIds = mCurrentRawState.rawPointerData.hoveringIdBits; + while (!hoveringIds.isEmpty()) { + uint32_t id = hoveringIds.clearFirstMarkedBit(); + const auto& pointer = mCurrentRawState.rawPointerData.pointerForId(id); + if (isPointInsidePhysicalFrame(pointer.x, pointer.y)) { + hoveringPointersInFrame = true; + break; + } + } + if (!hoveringPointersInFrame) { + // All hovering pointers are outside the physical frame. + outConsumed = true; + return out; + } + } + if (mLastRawState.rawPointerData.touchingIdBits.isEmpty() && !mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) { // Pointer just went down. Check for virtual key press or off-screen touches. @@ -1865,7 +1886,7 @@ std::list TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t if (!isPointInsidePhysicalFrame(pointer.x, pointer.y) && mDeviceMode != DeviceMode::UNSCALED) { // If exactly one pointer went down, check for virtual key hit. - // Otherwise we will drop the entire stroke. + // Otherwise, we will drop the entire stroke. if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) { const VirtualKey* virtualKey = findVirtualKeyHit(pointer.x, pointer.y); if (virtualKey) { diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 3d0fbb4455..b6e7b5218d 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -6735,6 +6735,54 @@ TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsChangedToTouchNavigation_upda ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mDevice->getSources()); } +TEST_F(SingleTouchInputMapperTest, HoverEventsOutsidePhysicalFrameAreIgnored) { + // Initialize the device without setting device source to touch navigation. + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareButtons(); + prepareAxes(POSITION); + mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); + + // Set a physical frame in the display viewport. + auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); + viewport->physicalLeft = 0; + viewport->physicalTop = 0; + viewport->physicalRight = DISPLAY_WIDTH / 2; + viewport->physicalBottom = DISPLAY_HEIGHT / 2; + mFakePolicy->updateViewport(*viewport); + configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + + SingleTouchInputMapper& mapper = addMapperAndConfigure(); + + // Hovering inside the physical frame produces events. + processKey(mapper, BTN_TOOL_PEN, 1); + processMove(mapper, RAW_X_MIN + 1, RAW_Y_MIN + 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))); + + // Leaving the physical frame ends the hovering gesture. + processMove(mapper, RAW_X_MAX - 1, RAW_Y_MAX - 1); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT))); + + // Moving outside the physical frame does not produce events. + processMove(mapper, RAW_X_MAX - 2, RAW_Y_MAX - 2); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + + // Re-entering the physical frame produces events. + processMove(mapper, RAW_X_MIN, RAW_Y_MIN); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))); +} + // --- TouchDisplayProjectionTest --- class TouchDisplayProjectionTest : public SingleTouchInputMapperTest { -- GitLab From 999db23368617262e4c548ed743fa516d17682d4 Mon Sep 17 00:00:00 2001 From: Cody Northrop Date: Mon, 27 Feb 2023 17:02:50 -0700 Subject: [PATCH 1074/1310] EGL BlobCache: Add CRC check to multifile Apply a CRC check during file write, ensuring it is the last thing applied to the file. During initialization, verify the contents of the file and remove if CRC doesn't match. Add a new test (ModifiedCacheEndMisses) that modifies the end of the entry and ensures no cache hit. Test: pubg_mobile_launch ANGLE trace Test: /data/nativetest64/EGL_test/EGL_test Test: /data/nativetest64/libEGL_test/libEGL_test Bug: b/267629017 Change-Id: I54015548b509c9ef2440e80f35b5ec97820a2144 --- opengl/libs/Android.bp | 1 + opengl/libs/EGL/FileBlobCache.cpp | 2 +- opengl/libs/EGL/FileBlobCache.h | 2 + opengl/libs/EGL/MultifileBlobCache.cpp | 102 ++++++++++++++++-------- opengl/libs/EGL/MultifileBlobCache.h | 4 + opengl/tests/EGLTest/egl_cache_test.cpp | 66 +++++++++++++-- 6 files changed, 135 insertions(+), 42 deletions(-) diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp index 49e1cbafb4..16de3908f8 100644 --- a/opengl/libs/Android.bp +++ b/opengl/libs/Android.bp @@ -205,6 +205,7 @@ cc_test { srcs: [ "EGL/BlobCache.cpp", "EGL/BlobCache_test.cpp", + "EGL/FileBlobCache.cpp", "EGL/MultifileBlobCache.cpp", "EGL/MultifileBlobCache_test.cpp", ], diff --git a/opengl/libs/EGL/FileBlobCache.cpp b/opengl/libs/EGL/FileBlobCache.cpp index 3f7ae7e5f4..1026842f87 100644 --- a/opengl/libs/EGL/FileBlobCache.cpp +++ b/opengl/libs/EGL/FileBlobCache.cpp @@ -31,7 +31,7 @@ static const size_t cacheFileHeaderSize = 8; namespace android { -static uint32_t crc32c(const uint8_t* buf, size_t len) { +uint32_t crc32c(const uint8_t* buf, size_t len) { const uint32_t polyBits = 0x82F63B78; uint32_t r = 0; for (size_t i = 0; i < len; i++) { diff --git a/opengl/libs/EGL/FileBlobCache.h b/opengl/libs/EGL/FileBlobCache.h index 8220723b2c..f083b0d6ca 100644 --- a/opengl/libs/EGL/FileBlobCache.h +++ b/opengl/libs/EGL/FileBlobCache.h @@ -22,6 +22,8 @@ namespace android { +uint32_t crc32c(const uint8_t* buf, size_t len); + class FileBlobCache : public BlobCache { public: // FileBlobCache attempts to load the saved cache contents from disk into diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp index 99af299f8d..607e2148e0 100644 --- a/opengl/libs/EGL/MultifileBlobCache.cpp +++ b/opengl/libs/EGL/MultifileBlobCache.cpp @@ -39,21 +39,10 @@ using namespace std::literals; -namespace { - -// Open the file and determine the size of the value it contains -size_t getValueSizeFromFile(int fd, const std::string& entryPath) { - // Read the beginning of the file to get header - android::MultifileHeader header; - size_t result = read(fd, static_cast(&header), sizeof(android::MultifileHeader)); - if (result != sizeof(android::MultifileHeader)) { - ALOGE("Error reading MultifileHeader from cache entry (%s): %s", entryPath.c_str(), - std::strerror(errno)); - return 0; - } +constexpr uint32_t kMultifileMagic = 'MFB$'; +constexpr uint32_t kCrcPlaceholder = 0; - return header.valueSize; -} +namespace { // Helper function to close entries or free them void freeHotCacheEntry(android::MultifileHotCache& entry) { @@ -129,6 +118,15 @@ MultifileBlobCache::MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSi return; } + // If the cache entry is damaged or no good, remove it + if (st.st_size <= 0 || st.st_atime <= 0) { + ALOGE("INIT: Entry %u has invalid stats! Removing.", entryHash); + if (remove(fullPath.c_str()) != 0) { + ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno)); + } + continue; + } + // Open the file so we can read its header int fd = open(fullPath.c_str(), O_RDONLY); if (fd == -1) { @@ -137,40 +135,67 @@ MultifileBlobCache::MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSi return; } - // Look up the details we track about each file - size_t valueSize = getValueSizeFromFile(fd, fullPath); + // Read the beginning of the file to get header + MultifileHeader header; + size_t result = read(fd, static_cast(&header), sizeof(MultifileHeader)); + if (result != sizeof(MultifileHeader)) { + ALOGE("Error reading MultifileHeader from cache entry (%s): %s", + fullPath.c_str(), std::strerror(errno)); + return; + } - // If the cache entry is damaged or no good, remove it - // TODO: Perform any other checks - if (valueSize <= 0 || st.st_size <= 0 || st.st_atime <= 0) { - ALOGV("INIT: Entry %u has a problem! Removing.", entryHash); + // Verify header magic + if (header.magic != kMultifileMagic) { + ALOGE("INIT: Entry %u has bad magic (%u)! Removing.", entryHash, header.magic); if (remove(fullPath.c_str()) != 0) { ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno)); } continue; } - ALOGV("INIT: Entry %u is good, tracking it now.", entryHash); - // Note: Converting from off_t (signed) to size_t (unsigned) size_t fileSize = static_cast(st.st_size); - time_t accessTime = st.st_atime; + + // Memory map the file + uint8_t* mappedEntry = reinterpret_cast( + mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0)); + if (mappedEntry == MAP_FAILED) { + ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); + return; + } + + // Ensure we have a good CRC + if (header.crc != + crc32c(mappedEntry + sizeof(MultifileHeader), + fileSize - sizeof(MultifileHeader))) { + ALOGE("INIT: Entry %u failed CRC check! Removing.", entryHash); + if (remove(fullPath.c_str()) != 0) { + ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno)); + } + continue; + } + + // If the cache entry is damaged or no good, remove it + if (header.keySize <= 0 || header.valueSize <= 0) { + ALOGE("INIT: Entry %u has a bad header keySize (%lu) or valueSize (%lu), " + "removing.", + entryHash, header.keySize, header.valueSize); + if (remove(fullPath.c_str()) != 0) { + ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno)); + } + continue; + } + + ALOGV("INIT: Entry %u is good, tracking it now.", entryHash); // Track details for rapid lookup later - trackEntry(entryHash, valueSize, fileSize, accessTime); + trackEntry(entryHash, header.valueSize, fileSize, st.st_atime); // Track the total size increaseTotalCacheSize(fileSize); // Preload the entry for fast retrieval if ((mHotCacheSize + fileSize) < mHotCacheLimit) { - // Memory map the file - uint8_t* mappedEntry = reinterpret_cast( - mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0)); - if (mappedEntry == MAP_FAILED) { - ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); - } - ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for " "entryHash %u", fd, mappedEntry, entryHash); @@ -183,6 +208,8 @@ MultifileBlobCache::MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSi return; } } else { + // If we're not keeping it in hot cache, unmap it now + munmap(mappedEntry, fileSize); close(fd); } } @@ -247,10 +274,12 @@ void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const voi uint8_t* buffer = new uint8_t[fileSize]; - // Write the key and value after the header - android::MultifileHeader header = {keySize, valueSize}; + // Write placeholders for magic and CRC until deferred thread complets the write + android::MultifileHeader header = {kMultifileMagic, kCrcPlaceholder, keySize, valueSize}; memcpy(static_cast(buffer), static_cast(&header), sizeof(android::MultifileHeader)); + + // Write the key and value after the header memcpy(static_cast(buffer + sizeof(MultifileHeader)), static_cast(key), keySize); memcpy(static_cast(buffer + sizeof(MultifileHeader) + keySize), @@ -600,6 +629,11 @@ void MultifileBlobCache::processTask(DeferredTask& task) { ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str()); + // Add CRC check to the header (always do this last!) + MultifileHeader* header = reinterpret_cast(buffer); + header->crc = + crc32c(buffer + sizeof(MultifileHeader), bufferSize - sizeof(MultifileHeader)); + ssize_t result = write(fd, buffer, bufferSize); if (result != bufferSize) { ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(), @@ -686,4 +720,4 @@ void MultifileBlobCache::waitForWorkComplete() { mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); }); } -}; // namespace android \ No newline at end of file +}; // namespace android diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h index c0cc9dc2a9..cb105fba0d 100644 --- a/opengl/libs/EGL/MultifileBlobCache.h +++ b/opengl/libs/EGL/MultifileBlobCache.h @@ -28,9 +28,13 @@ #include #include +#include "FileBlobCache.h" + namespace android { struct MultifileHeader { + uint32_t magic; + uint32_t crc; EGLsizeiANDROID keySize; EGLsizeiANDROID valueSize; }; diff --git a/opengl/tests/EGLTest/egl_cache_test.cpp b/opengl/tests/EGLTest/egl_cache_test.cpp index 2b3e3a46af..ff4022cfe5 100644 --- a/opengl/tests/EGLTest/egl_cache_test.cpp +++ b/opengl/tests/EGLTest/egl_cache_test.cpp @@ -15,7 +15,7 @@ */ #define LOG_TAG "EGL_test" -//#define LOG_NDEBUG 0 +// #define LOG_NDEBUG 0 #include @@ -27,6 +27,7 @@ #include "MultifileBlobCache.h" #include "egl_display.h" +#include #include using namespace std::literals; @@ -144,7 +145,7 @@ std::string EGLCacheTest::getCachefileName() { return cachefileName; } -TEST_P(EGLCacheTest, ModifiedCacheMisses) { +TEST_P(EGLCacheTest, ModifiedCacheBeginMisses) { // Skip if not in multifile mode if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) { GTEST_SKIP() << "Skipping test designed for multifile"; @@ -168,11 +169,12 @@ TEST_P(EGLCacheTest, ModifiedCacheMisses) { ASSERT_TRUE(cachefileName.length() > 0); // Stomp on the beginning of the cache file, breaking the key match - const long stomp = 0xbadf00d; - FILE *file = fopen(cachefileName.c_str(), "w"); - fprintf(file, "%ld", stomp); - fflush(file); - fclose(file); + const char* stomp = "BADF00D"; + std::fstream fs(cachefileName); + fs.seekp(0, std::ios_base::beg); + fs.write(stomp, strlen(stomp)); + fs.flush(); + fs.close(); // Ensure no cache hit mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); @@ -185,6 +187,56 @@ TEST_P(EGLCacheTest, ModifiedCacheMisses) { ASSERT_EQ(0xee, buf2[3]); } +TEST_P(EGLCacheTest, ModifiedCacheEndMisses) { + // Skip if not in multifile mode + if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) { + GTEST_SKIP() << "Skipping test designed for multifile"; + } + + uint8_t buf[16] = { 0xee, 0xee, 0xee, 0xee, + 0xee, 0xee, 0xee, 0xee, + 0xee, 0xee, 0xee, 0xee, + 0xee, 0xee, 0xee, 0xee }; + + mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + + mCache->setBlob("abcdefghij", 10, "klmnopqrstuvwxyz", 16); + ASSERT_EQ(16, mCache->getBlob("abcdefghij", 10, buf, 16)); + ASSERT_EQ('w', buf[12]); + ASSERT_EQ('x', buf[13]); + ASSERT_EQ('y', buf[14]); + ASSERT_EQ('z', buf[15]); + + // Ensure the cache file is written to disk + mCache->terminate(); + + // Depending on the cache mode, the file will be in different locations + std::string cachefileName = getCachefileName(); + ASSERT_TRUE(cachefileName.length() > 0); + + // Stomp on the END of the cache file, modifying its contents + const char* stomp = "BADF00D"; + std::fstream fs(cachefileName); + fs.seekp(-strlen(stomp), std::ios_base::end); + fs.write(stomp, strlen(stomp)); + fs.flush(); + fs.close(); + + // Ensure no cache hit + mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + uint8_t buf2[16] = { 0xee, 0xee, 0xee, 0xee, + 0xee, 0xee, 0xee, 0xee, + 0xee, 0xee, 0xee, 0xee, + 0xee, 0xee, 0xee, 0xee }; + + // getBlob may return junk for required size, but should not return a cache hit + mCache->getBlob("abcdefghij", 10, buf2, 16); + ASSERT_EQ(0xee, buf2[0]); + ASSERT_EQ(0xee, buf2[1]); + ASSERT_EQ(0xee, buf2[2]); + ASSERT_EQ(0xee, buf2[3]); +} + TEST_P(EGLCacheTest, TerminatedCacheBelowCacheLimit) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); -- GitLab From 1fe1117da5f793969752f44407e4618690b41d52 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Mon, 3 Apr 2023 18:10:09 +0000 Subject: [PATCH 1075/1310] Revert "[sf] Trigger input updates in post composition" This reverts commit a7e4eaf3101db66f7c4792b63297ec5e47ef7b57. Reason for revert: b/275858990, b/276272159, b/275647448 Change-Id: I1578af4f6aaa9f208742ea5692636f43ca4fe28e --- services/surfaceflinger/SurfaceFlinger.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 3c5a3deaef..0bdede8eb5 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2517,6 +2517,8 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe } updateCursorAsync(); + updateInputFlinger(); + if (mLayerTracingEnabled && !mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) { // This will block and tracing should only be enabled for debugging. mLayerTracing.notify(mVisibleRegionsDirty, frameTime.ns(), vsyncId.value); @@ -2843,11 +2845,6 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { layer->releasePendingBuffer(presentTime.ns()); } - mTransactionCallbackInvoker.addPresentFence(std::move(presentFence)); - mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */); - mTransactionCallbackInvoker.clearCompletedTransactions(); - updateInputFlinger(); - std::vector, sp>> hdrInfoListeners; bool haveNewListeners = false; @@ -2904,6 +2901,10 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { mHdrLayerInfoChanged = false; + mTransactionCallbackInvoker.addPresentFence(std::move(presentFence)); + mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */); + mTransactionCallbackInvoker.clearCompletedTransactions(); + mTimeStats->incrementTotalFrames(); mTimeStats->setPresentFenceGlobal(presentFenceTime); -- GitLab From 9f0f647ffbb76308a4bd610fa37dabfedf2ca154 Mon Sep 17 00:00:00 2001 From: Arthur Ishiguro Date: Mon, 3 Apr 2023 17:01:40 +0000 Subject: [PATCH 1076/1310] Perform cleanup operation on destroy() In some cases, it's possible that the destructor of the SensorEventConnection object is never called, because unless the cleanupConnection() call is made, the SensorService still has a strong reference to the SensorEventConnection. This may result in leaked resources, because the connection still exists in the SensorService, though the connection has been destroyed from the client's perspective. This CL fixes this issue by doing the cleanup operation in the destroy() call instead. Bug: 274595196 Test: Run STS test, and verify all connections are destroyed afterwards Change-Id: I7edb72d17337420e19352874bcae1d36b5981b95 --- services/sensorservice/SensorEventConnection.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/services/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp index b94b1c0a1a..7a6b31d642 100644 --- a/services/sensorservice/SensorEventConnection.cpp +++ b/services/sensorservice/SensorEventConnection.cpp @@ -55,14 +55,13 @@ SensorService::SensorEventConnection::SensorEventConnection( SensorService::SensorEventConnection::~SensorEventConnection() { ALOGD_IF(DEBUG_CONNECTIONS, "~SensorEventConnection(%p)", this); destroy(); - mService->cleanupConnection(this); - if (mEventCache != nullptr) { - delete[] mEventCache; - } + delete[] mEventCache; } void SensorService::SensorEventConnection::destroy() { - mDestroyed = true; + if (!mDestroyed.exchange(true)) { + mService->cleanupConnection(this); + } } void SensorService::SensorEventConnection::onFirstRef() { -- GitLab From be16373f15a245b4c14ad4f84b20ff8e76741893 Mon Sep 17 00:00:00 2001 From: Cody Northrop Date: Wed, 22 Mar 2023 10:14:26 -0600 Subject: [PATCH 1077/1310] EGL BlobCache: Synchronize access to deferred write status Protect access to mDeferredWrites with a mutex. It is updated by the worker thread upon completion. Test: pubg_mobile_launch ANGLE trace Test: /data/nativetest64/EGL_test/EGL_test Test: /data/nativetest64/libEGL_test/libEGL_test Bug: b/271975248 Change-Id: Id278f4677a72f2d981a9400ee871bdcc1b0168ef --- opengl/libs/EGL/MultifileBlobCache.cpp | 41 +++++++++++++++------ opengl/libs/EGL/MultifileBlobCache.h | 4 +- opengl/libs/EGL/MultifileBlobCache_test.cpp | 20 ++++++++++ 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp index 607e2148e0..f1b7a5c064 100644 --- a/opengl/libs/EGL/MultifileBlobCache.cpp +++ b/opengl/libs/EGL/MultifileBlobCache.cpp @@ -298,13 +298,17 @@ void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const voi // Sending -1 as the fd indicates we don't have an fd for this if (!addToHotCache(entryHash, -1, buffer, fileSize)) { - ALOGE("GET: Failed to add %u to hot cache", entryHash); + ALOGE("SET: Failed to add %u to hot cache", entryHash); return; } // Track that we're creating a pending write for this entry // Include the buffer to handle the case when multiple writes are pending for an entry - mDeferredWrites.insert(std::make_pair(entryHash, buffer)); + { + // Synchronize access to deferred write status + std::lock_guard lock(mDeferredWriteStatusMutex); + mDeferredWrites.insert(std::make_pair(entryHash, buffer)); + } // Create deferred task to write to storage ALOGV("SET: Adding task to queue."); @@ -371,8 +375,15 @@ EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize } else { ALOGV("GET: HotCache MISS for entry: %u", entryHash); - if (mDeferredWrites.find(entryHash) != mDeferredWrites.end()) { - // Wait for writes to complete if there is an outstanding write for this entry + // Wait for writes to complete if there is an outstanding write for this entry + bool wait = false; + { + // Synchronize access to deferred write status + std::lock_guard lock(mDeferredWriteStatusMutex); + wait = mDeferredWrites.find(entryHash) != mDeferredWrites.end(); + } + + if (wait) { ALOGV("GET: Waiting for write to complete for %u", entryHash); waitForWorkComplete(); } @@ -484,6 +495,7 @@ bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash); // Wait for all the files to complete writing so our hot cache is accurate + ALOGV("HOTCACHE(ADD): Waiting for work to complete for %u", newEntryHash); waitForWorkComplete(); // Free up old entries until under the limit @@ -520,6 +532,7 @@ bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) { ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash); // Wait for all the files to complete writing so our hot cache is accurate + ALOGV("HOTCACHE(REMOVE): Waiting for work to complete for %u", entryHash); waitForWorkComplete(); ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash); @@ -590,6 +603,7 @@ void MultifileBlobCache::trimCache(size_t cacheByteLimit) { size_t limit = cacheByteLimit; // Wait for all deferred writes to complete + ALOGV("TRIM: Waiting for work to complete."); waitForWorkComplete(); size_t size = getTotalSize(); @@ -646,13 +660,18 @@ void MultifileBlobCache::processTask(DeferredTask& task) { // Erase the entry from mDeferredWrites // Since there could be multiple outstanding writes for an entry, find the matching one - typedef std::multimap::iterator entryIter; - std::pair iterPair = mDeferredWrites.equal_range(entryHash); - for (entryIter it = iterPair.first; it != iterPair.second; ++it) { - if (it->second == buffer) { - ALOGV("DEFERRED: Marking write complete for %u at %p", it->first, it->second); - mDeferredWrites.erase(it); - break; + { + // Synchronize access to deferred write status + std::lock_guard lock(mDeferredWriteStatusMutex); + typedef std::multimap::iterator entryIter; + std::pair iterPair = mDeferredWrites.equal_range(entryHash); + for (entryIter it = iterPair.first; it != iterPair.second; ++it) { + if (it->second == buffer) { + ALOGV("DEFERRED: Marking write complete for %u at %p", it->first, + it->second); + mDeferredWrites.erase(it); + break; + } } } diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h index cb105fba0d..b461ef4fc6 100644 --- a/opengl/libs/EGL/MultifileBlobCache.h +++ b/opengl/libs/EGL/MultifileBlobCache.h @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -139,7 +140,8 @@ private: // Below are the components used for deferred writes // Track whether we have pending writes for an entry - std::multimap mDeferredWrites; + std::mutex mDeferredWriteStatusMutex; + std::multimap mDeferredWrites GUARDED_BY(mDeferredWriteStatusMutex); // Functions to work through tasks in the queue void processTasks(); diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp index 1a55a4fcdd..670e1139dd 100644 --- a/opengl/libs/EGL/MultifileBlobCache_test.cpp +++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp @@ -190,6 +190,26 @@ TEST_F(MultifileBlobCacheTest, CacheMaxValueSizeSucceeds) { } } +TEST_F(MultifileBlobCacheTest, CacheMaxKeyAndValueSizeSucceeds) { + char key[kMaxKeySize]; + for (int i = 0; i < kMaxKeySize; i++) { + key[i] = 'a'; + } + char buf[kMaxValueSize]; + for (int i = 0; i < kMaxValueSize; i++) { + buf[i] = 'b'; + } + mMBC->set(key, kMaxKeySize, buf, kMaxValueSize); + for (int i = 0; i < kMaxValueSize; i++) { + buf[i] = 0xee; + } + mMBC->get(key, kMaxKeySize, buf, kMaxValueSize); + for (int i = 0; i < kMaxValueSize; i++) { + SCOPED_TRACE(i); + ASSERT_EQ('b', buf[i]); + } +} + TEST_F(MultifileBlobCacheTest, CacheMinKeyAndValueSizeSucceeds) { unsigned char buf[1] = {0xee}; mMBC->set("x", 1, "y", 1); -- GitLab From f2588a8172a8481e4989924098eafa08a145b5c1 Mon Sep 17 00:00:00 2001 From: Cody Northrop Date: Mon, 27 Mar 2023 23:03:49 -0600 Subject: [PATCH 1078/1310] EGL BlobCache: Trim immediately on multifile overflow While adding a fuzzer for this feature, discovered that cache trimming wasn't happening right when the overflow happened, but on the next iteration. This CL updates the code to make it trim right away, since we've already determined it needs to happen. Also add a test to ensure trimming happens right away. Test: /data/nativetest64/EGL_test/EGL_test Bug: b/266725576 Change-Id: Ica1e9ca688268e7e746750c27acdfced04e74b06 --- opengl/libs/EGL/MultifileBlobCache.cpp | 23 +++------- opengl/libs/EGL/MultifileBlobCache.h | 2 +- opengl/tests/EGLTest/egl_cache_test.cpp | 59 ++++++++++++++++++++++++- 3 files changed, 66 insertions(+), 18 deletions(-) diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp index f1b7a5c064..1c0c045bb4 100644 --- a/opengl/libs/EGL/MultifileBlobCache.cpp +++ b/opengl/libs/EGL/MultifileBlobCache.cpp @@ -267,7 +267,7 @@ void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const voi // If we're going to be over the cache limit, kick off a trim to clear space if (getTotalSize() + fileSize > mMaxTotalSize) { ALOGV("SET: Cache is full, calling trimCache to clear space"); - trimCache(mMaxTotalSize); + trimCache(); } ALOGV("SET: Add %u to cache", entryHash); @@ -589,7 +589,7 @@ bool MultifileBlobCache::applyLRU(size_t cacheLimit) { } } - ALOGV("LRU: Cache is emptry"); + ALOGV("LRU: Cache is empty"); return false; } @@ -598,24 +598,15 @@ bool MultifileBlobCache::applyLRU(size_t cacheLimit) { constexpr uint32_t kCacheLimitDivisor = 2; // Calculate the cache size and remove old entries until under the limit -void MultifileBlobCache::trimCache(size_t cacheByteLimit) { - // Start with the value provided by egl_cache - size_t limit = cacheByteLimit; - +void MultifileBlobCache::trimCache() { // Wait for all deferred writes to complete ALOGV("TRIM: Waiting for work to complete."); waitForWorkComplete(); - size_t size = getTotalSize(); - - // If size is larger than the threshold, remove files using LRU - if (size > limit) { - ALOGV("TRIM: Multifile cache size is larger than %zu, removing old entries", - cacheByteLimit); - if (!applyLRU(limit / kCacheLimitDivisor)) { - ALOGE("Error when clearing multifile shader cache"); - return; - } + ALOGV("TRIM: Reducing multifile cache size to %zu", mMaxTotalSize / kCacheLimitDivisor); + if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor)) { + ALOGE("Error when clearing multifile shader cache"); + return; } } diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h index b461ef4fc6..f266011fda 100644 --- a/opengl/libs/EGL/MultifileBlobCache.h +++ b/opengl/libs/EGL/MultifileBlobCache.h @@ -119,7 +119,7 @@ private: bool addToHotCache(uint32_t entryHash, int fd, uint8_t* entryBufer, size_t entrySize); bool removeFromHotCache(uint32_t entryHash); - void trimCache(size_t cacheByteLimit); + void trimCache(); bool applyLRU(size_t cacheLimit); bool mInitialized; diff --git a/opengl/tests/EGLTest/egl_cache_test.cpp b/opengl/tests/EGLTest/egl_cache_test.cpp index ff4022cfe5..f81c68f66e 100644 --- a/opengl/tests/EGLTest/egl_cache_test.cpp +++ b/opengl/tests/EGLTest/egl_cache_test.cpp @@ -265,11 +265,68 @@ TEST_P(EGLCacheTest, TerminatedCacheBelowCacheLimit) { // Cache should contain both the key and the value // So 8 bytes per entry, at least 24 bytes ASSERT_GE(mCache->getCacheSize(), 24); - mCache->setCacheLimit(4); + + // Set the new limit and initialize cache mCache->terminate(); + mCache->setCacheLimit(4); + mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + + // Ensure the new limit is respected ASSERT_LE(mCache->getCacheSize(), 4); } +TEST_P(EGLCacheTest, TrimCacheOnOverflow) { + // Skip if not in multifile mode + if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) { + GTEST_SKIP() << "Skipping test designed for multifile"; + } + + uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; + mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + + // Set one value in the cache + mCache->setBlob("abcd", 4, "efgh", 4); + ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); + + // Get the size of cache with a single entry + size_t cacheEntrySize = mCache->getCacheSize(); + + // Now reinitialize the cache, using max size equal to a single entry + mCache->terminate(); + mCache->setCacheLimit(cacheEntrySize); + mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + + // Ensure our cache still has original value + ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); + + // Set another value, which should overflow the cache and trim + mCache->setBlob("ijkl", 4, "mnop", 4); + ASSERT_EQ(4, mCache->getBlob("ijkl", 4, buf, 4)); + ASSERT_EQ('m', buf[0]); + ASSERT_EQ('n', buf[1]); + ASSERT_EQ('o', buf[2]); + ASSERT_EQ('p', buf[3]); + + // The cache should still be under the limit + ASSERT_TRUE(mCache->getCacheSize() == cacheEntrySize); + + // And no cache hit on trimmed entry + uint8_t buf2[4] = { 0xee, 0xee, 0xee, 0xee }; + mCache->getBlob("abcd", 4, buf2, 4); + ASSERT_EQ(0xee, buf2[0]); + ASSERT_EQ(0xee, buf2[1]); + ASSERT_EQ(0xee, buf2[2]); + ASSERT_EQ(0xee, buf2[3]); +} + INSTANTIATE_TEST_CASE_P(MonolithicCacheTests, EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Monolithic)); INSTANTIATE_TEST_CASE_P(MultifileCacheTests, -- GitLab From 5dbcfa78580895cb6361bbd1a1853e291b982d5e Mon Sep 17 00:00:00 2001 From: Cody Northrop Date: Fri, 24 Mar 2023 15:34:09 -0600 Subject: [PATCH 1079/1310] EGL BlobCache: Update multifile key and value size Make it clearer what the key and value size limits are. Base the hot cache on those values, rather than the other way around. Test: pubg_mobile_launch ANGLE trace Test: /data/nativetest64/EGL_test/EGL_test Test: /data/nativetest64/libEGL_test/libEGL_test Bug: b/266725576 Change-Id: I337beaf0e01c6497f619c29c01f94242c7b5c959 --- opengl/libs/EGL/MultifileBlobCache.cpp | 16 +++++++++------- opengl/libs/EGL/MultifileBlobCache.h | 3 ++- opengl/libs/EGL/MultifileBlobCache_test.cpp | 9 ++++----- opengl/libs/EGL/egl_cache.cpp | 12 +++++++----- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp index 1c0c045bb4..b5ddd601ce 100644 --- a/opengl/libs/EGL/MultifileBlobCache.cpp +++ b/opengl/libs/EGL/MultifileBlobCache.cpp @@ -62,12 +62,14 @@ void freeHotCacheEntry(android::MultifileHotCache& entry) { namespace android { -MultifileBlobCache::MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize, +MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize, const std::string& baseDir) : mInitialized(false), + mMaxKeySize(maxKeySize), + mMaxValueSize(maxValueSize), mMaxTotalSize(maxTotalSize), mTotalCacheSize(0), - mHotCacheLimit(maxHotCacheSize), + mHotCacheLimit(0), mHotCacheSize(0), mWorkerThreadIdle(true) { if (baseDir.empty()) { @@ -78,9 +80,9 @@ MultifileBlobCache::MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSi // Establish the name of our multifile directory mMultifileDirName = baseDir + ".multifile"; - // Set a limit for max key and value, ensuring at least one entry can always fit in hot cache - mMaxKeySize = mHotCacheLimit / 4; - mMaxValueSize = mHotCacheLimit / 2; + // Set the hotcache limit to be large enough to contain one max entry + // This ensure the hot cache is always large enough for single entry + mHotCacheLimit = mMaxKeySize + mMaxValueSize + sizeof(MultifileHeader); ALOGV("INIT: Initializing multifile blobcache with maxKeySize=%zu and maxValueSize=%zu", mMaxKeySize, mMaxValueSize); @@ -254,7 +256,7 @@ void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const voi // Ensure key and value are under their limits if (keySize > mMaxKeySize || valueSize > mMaxValueSize) { - ALOGV("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize, + ALOGW("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize, valueSize, mMaxValueSize); return; } @@ -326,7 +328,7 @@ EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize // Ensure key and value are under their limits if (keySize > mMaxKeySize || valueSize > mMaxValueSize) { - ALOGV("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize, + ALOGW("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize, valueSize, mMaxValueSize); return 0; } diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h index f266011fda..5e527dcf35 100644 --- a/opengl/libs/EGL/MultifileBlobCache.h +++ b/opengl/libs/EGL/MultifileBlobCache.h @@ -91,7 +91,8 @@ private: class MultifileBlobCache { public: - MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize, const std::string& baseDir); + MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize, + const std::string& baseDir); ~MultifileBlobCache(); void set(const void* key, EGLsizeiANDROID keySize, const void* value, diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp index 670e1139dd..dbee13bb2e 100644 --- a/opengl/libs/EGL/MultifileBlobCache_test.cpp +++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp @@ -28,17 +28,16 @@ namespace android { template using sp = std::shared_ptr; +constexpr size_t kMaxKeySize = 2 * 1024; +constexpr size_t kMaxValueSize = 6 * 1024; constexpr size_t kMaxTotalSize = 32 * 1024; -constexpr size_t kMaxPreloadSize = 8 * 1024; - -constexpr size_t kMaxKeySize = kMaxPreloadSize / 4; -constexpr size_t kMaxValueSize = kMaxPreloadSize / 2; class MultifileBlobCacheTest : public ::testing::Test { protected: virtual void SetUp() { mTempFile.reset(new TemporaryFile()); - mMBC.reset(new MultifileBlobCache(kMaxTotalSize, kMaxPreloadSize, &mTempFile->path[0])); + mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, + &mTempFile->path[0])); } virtual void TearDown() { mMBC.reset(); } diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp index 3dc93ee0b7..1b68344cb1 100644 --- a/opengl/libs/EGL/egl_cache.cpp +++ b/opengl/libs/EGL/egl_cache.cpp @@ -38,8 +38,9 @@ static const size_t kMaxMonolithicTotalSize = 2 * 1024 * 1024; static const unsigned int kDeferredMonolithicSaveDelay = 4; // Multifile cache size limits -constexpr uint32_t kMultifileHotCacheLimit = 8 * 1024 * 1024; -constexpr uint32_t kMultifileCacheByteLimit = 32 * 1024 * 1024; +constexpr uint32_t kMaxMultifileKeySize = 1 * 1024 * 1024; +constexpr uint32_t kMaxMultifileValueSize = 8 * 1024 * 1024; +constexpr uint32_t kMaxMultifileTotalSize = 32 * 1024 * 1024; namespace android { @@ -250,7 +251,7 @@ void egl_cache_t::updateMode() { if (mMultifileMode) { mCacheByteLimit = static_cast( base::GetUintProperty("ro.egl.blobcache.multifile_limit", - kMultifileCacheByteLimit)); + kMaxMultifileTotalSize)); // Check for a debug value int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.multifile_limit", -1); @@ -274,8 +275,9 @@ BlobCache* egl_cache_t::getBlobCacheLocked() { MultifileBlobCache* egl_cache_t::getMultifileBlobCacheLocked() { if (mMultifileBlobCache == nullptr) { - mMultifileBlobCache.reset( - new MultifileBlobCache(mCacheByteLimit, kMultifileHotCacheLimit, mFilename)); + mMultifileBlobCache.reset(new MultifileBlobCache(kMaxMultifileKeySize, + kMaxMultifileValueSize, mCacheByteLimit, + mFilename)); } return mMultifileBlobCache.get(); } -- GitLab From 45ce680a26ef4844370d4ca87dba25c7d2da19dd Mon Sep 17 00:00:00 2001 From: Cody Northrop Date: Thu, 23 Mar 2023 11:07:09 -0600 Subject: [PATCH 1080/1310] EGL BlobCache: Add fuzzer for multifile This fuzzer breaks up the incoming data buffer into a key and value, then invokes the cache in a few different ways. Also includes one small fix to delete a buffer, discovered while writing the test. Test: /data/fuzz/arm64/MultifileBlobCache_fuzzer/MultifileBlobCache_fuzzer Bug: b/261868299 Change-Id: I724584ddb5a92b4b33e371f22f4511cdeb965775 --- opengl/libs/EGL/MultifileBlobCache.cpp | 4 +- opengl/libs/EGL/fuzzer/Android.bp | 42 +++++ .../EGL/fuzzer/MultifileBlobCache_fuzzer.cpp | 158 ++++++++++++++++++ 3 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 opengl/libs/EGL/fuzzer/Android.bp create mode 100644 opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp index b5ddd601ce..7ffdac7ea7 100644 --- a/opengl/libs/EGL/MultifileBlobCache.cpp +++ b/opengl/libs/EGL/MultifileBlobCache.cpp @@ -276,11 +276,10 @@ void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const voi uint8_t* buffer = new uint8_t[fileSize]; - // Write placeholders for magic and CRC until deferred thread complets the write + // Write placeholders for magic and CRC until deferred thread completes the write android::MultifileHeader header = {kMultifileMagic, kCrcPlaceholder, keySize, valueSize}; memcpy(static_cast(buffer), static_cast(&header), sizeof(android::MultifileHeader)); - // Write the key and value after the header memcpy(static_cast(buffer + sizeof(MultifileHeader)), static_cast(key), keySize); @@ -301,6 +300,7 @@ void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const voi // Sending -1 as the fd indicates we don't have an fd for this if (!addToHotCache(entryHash, -1, buffer, fileSize)) { ALOGE("SET: Failed to add %u to hot cache", entryHash); + delete[] buffer; return; } diff --git a/opengl/libs/EGL/fuzzer/Android.bp b/opengl/libs/EGL/fuzzer/Android.bp new file mode 100644 index 0000000000..022a2a3f06 --- /dev/null +++ b/opengl/libs/EGL/fuzzer/Android.bp @@ -0,0 +1,42 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_native_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_native_license"], +} + +cc_fuzz { + name: "MultifileBlobCache_fuzzer", + + fuzz_config: { + cc: ["cnorthrop@google.com"], + libfuzzer_options: ["len_control=0"], + }, + + static_libs: [ + "libbase", + "libEGL_blobCache", + "liblog", + "libutils", + ], + + srcs: [ + "MultifileBlobCache_fuzzer.cpp", + ], +} diff --git a/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp new file mode 100644 index 0000000000..633cc9c51b --- /dev/null +++ b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp @@ -0,0 +1,158 @@ +/* + ** Copyright 2023, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +#include "MultifileBlobCache.h" + +#include +#include +#include +#include +#include +#include + +namespace android { + +constexpr size_t kMaxKeySize = 2 * 1024; +constexpr size_t kMaxValueSize = 6 * 1024; +constexpr size_t kMaxTotalSize = 32 * 1024; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + // To fuzz this, we're going to create a key/value pair from data + // and use them with MultifileBlobCache in a random order + // - Use the first entry in data to determine keySize + // - Use the second entry in data to determine valueSize + // - Mod each of them against half the remaining size, ensuring both fit + // - Create key and value using sizes from data + // - Use remaining data to switch between GET and SET while + // tweaking the keys slightly + // - Ensure two cache cleaning scenarios are hit at the end + + // Ensure we have enough data to create interesting key/value pairs + size_t kMinInputLength = 128; + if (size < kMinInputLength) { + return 0; + } + + // Need non-zero sizes for interesting results + if (data[0] == 0 || data[1] == 0) { + return 0; + } + + // We need to divide the data up into buffers and sizes + FuzzedDataProvider fdp(data, size); + + // Pull two values from data for key and value size + EGLsizeiANDROID keySize = static_cast(fdp.ConsumeIntegral()); + EGLsizeiANDROID valueSize = static_cast(fdp.ConsumeIntegral()); + size -= 2 * sizeof(uint8_t); + + // Ensure key and value fit in the remaining space (cap them at half data size) + keySize = keySize % (size >> 1); + valueSize = valueSize % (size >> 1); + + // If either size ended up zero, just move on to save time + if (keySize == 0 || valueSize == 0) { + return 0; + } + + // Create key and value from remaining data + std::vector key; + std::vector value; + key = fdp.ConsumeBytes(keySize); + value = fdp.ConsumeBytes(valueSize); + + // Create a tempfile and a cache + std::unique_ptr tempFile; + std::unique_ptr mbc; + + tempFile.reset(new TemporaryFile()); + mbc.reset( + new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0])); + // With remaining data, select different paths below + int loopCount = 1; + uint8_t bumpCount = 0; + while (fdp.remaining_bytes() > 0) { + // Bounce back and forth between gets and sets + if (fdp.ConsumeBool()) { + mbc->set(key.data(), keySize, value.data(), valueSize); + } else { + uint8_t* buffer = new uint8_t[valueSize]; + mbc->get(key.data(), keySize, buffer, valueSize); + delete[] buffer; + } + + // Bump the key and values periodically, causing different hits/misses + if (fdp.ConsumeBool()) { + key[0]++; + value[0]++; + bumpCount++; + } + + // Reset the key and value periodically to hit old entries + if (fdp.ConsumeBool()) { + key[0] -= bumpCount; + value[0] -= bumpCount; + bumpCount = 0; + } + + loopCount++; + } + mbc->finish(); + + // Fill 2 keys and 2 values to max size with unique values + std::vector maxKey1, maxKey2, maxValue1, maxValue2; + maxKey1.resize(kMaxKeySize, 0); + maxKey2.resize(kMaxKeySize, 0); + maxValue1.resize(kMaxValueSize, 0); + maxValue2.resize(kMaxValueSize, 0); + for (int i = 0; i < keySize && i < kMaxKeySize; ++i) { + maxKey1[i] = key[i]; + maxKey2[i] = key[i] - 1; + } + for (int i = 0; i < valueSize && i < kMaxValueSize; ++i) { + maxValue1[i] = value[i]; + maxValue2[i] = value[i] - 1; + } + + // Trigger hot cache trimming + // Place the maxKey/maxValue twice + // The first will fit, the second will trigger hot cache trimming + tempFile.reset(new TemporaryFile()); + mbc.reset( + new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0])); + uint8_t* buffer = new uint8_t[kMaxValueSize]; + mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize); + mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize); + mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize); + mbc->finish(); + + // Trigger cold cache trimming + // Create a total size small enough only one entry fits + // Since the cache will add a header, 2 * key + value will only hold one value, the second will + // overflow + tempFile.reset(new TemporaryFile()); + mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, 2 * (kMaxKeySize + kMaxValueSize), + &tempFile->path[0])); + mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize); + mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize); + mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize); + mbc->finish(); + + delete[] buffer; + return 0; +} + +} // namespace android -- GitLab From 9d51f9c357677a7d1c4c6c62432a31b84e4ab04f Mon Sep 17 00:00:00 2001 From: Rachel Lee Date: Mon, 3 Apr 2023 16:41:16 -0700 Subject: [PATCH 1081/1310] Add trace to resync. Test: atest from b/266128330#comment6 Test: Perfetto trace from aforementioned atest Bug: 266128330 Change-Id: I62eb973b57f0dec5cde0cd1ffcefa6074556bea4 --- services/surfaceflinger/Scheduler/Scheduler.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 7f8d39451e..3e12db61e7 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -409,6 +409,7 @@ void Scheduler::disableHardwareVsync(PhysicalDisplayId id, bool disallow) { } void Scheduler::resyncAllToHardwareVsync(bool allowToEnable) { + ATRACE_CALL(); std::scoped_lock lock(mDisplayLock); ftl::FakeGuard guard(kMainThreadContext); -- GitLab From f01a6f1491ec94dca77d80d3c76c473e806226da Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Mon, 3 Apr 2023 22:34:17 +0000 Subject: [PATCH 1082/1310] [sf] Fix latch unsignaled issues With LatchUnsignaledConfig::AutoSingleLayer flag, we should only apply a transaction with an unsignaled fence if its the only transaction. If we pass an unsignaled fence to HWC, HWC might miss presenting the frame if the fence does not fire in time. If we apply another transaction we may penalize the other transaction unfairly. With LatchUnsignaledConfig::Always flag, respect backpressure flag and take the opportunity to simplify the logic by treating the flag as a signal to ignore the fence when applying transactions. Test: surfaceflinger unit tests Bug: 276229937 Change-Id: Ib5cd8205d62fdf8912b7f5c2cad312a01cae4300 --- .../FrontEnd/TransactionHandler.cpp | 101 ++++++++++-------- .../FrontEnd/TransactionHandler.h | 17 ++- services/surfaceflinger/SurfaceFlinger.cpp | 29 +++-- services/surfaceflinger/SurfaceFlinger.h | 5 + .../unittests/TransactionApplicationTest.cpp | 84 ++++++++++++++- 5 files changed, 171 insertions(+), 65 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp index 8629671214..a209cad612 100644 --- a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp +++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp @@ -64,11 +64,61 @@ std::vector TransactionHandler::flushTransactions() { transactionsPendingBarrier = flushPendingTransactionQueues(transactions, flushState); } while (lastTransactionsPendingBarrier != transactionsPendingBarrier); + applyUnsignaledBufferTransaction(transactions, flushState); + mPendingTransactionCount.fetch_sub(transactions.size()); ATRACE_INT("TransactionQueue", static_cast(mPendingTransactionCount.load())); return transactions; } +void TransactionHandler::applyUnsignaledBufferTransaction( + std::vector& transactions, TransactionFlushState& flushState) { + // only apply an unsignaled buffer transaction if it's the first one + if (!transactions.empty()) { + return; + } + + if (!flushState.queueWithUnsignaledBuffer) { + return; + } + + auto it = mPendingTransactionQueues.find(flushState.queueWithUnsignaledBuffer); + LOG_ALWAYS_FATAL_IF(it == mPendingTransactionQueues.end(), + "Could not find queue with unsignaled buffer!"); + + auto& queue = it->second; + popTransactionFromPending(transactions, flushState, queue); + if (queue.empty()) { + it = mPendingTransactionQueues.erase(it); + } +} + +void TransactionHandler::popTransactionFromPending(std::vector& transactions, + TransactionFlushState& flushState, + std::queue& queue) { + auto& transaction = queue.front(); + // Transaction is ready move it from the pending queue. + flushState.firstTransaction = false; + removeFromStalledTransactions(transaction.id); + transactions.emplace_back(std::move(transaction)); + queue.pop(); + + auto& readyToApplyTransaction = transactions.back(); + readyToApplyTransaction.traverseStatesWithBuffers([&](const layer_state_t& state) { + const bool frameNumberChanged = + state.bufferData->flags.test(BufferData::BufferDataChange::frameNumberChanged); + if (frameNumberChanged) { + flushState.bufferLayersReadyToPresent.emplace_or_replace(state.surface.get(), + state.bufferData->frameNumber); + } else { + // Barrier function only used for BBQ which always includes a frame number. + // This value only used for barrier logic. + flushState.bufferLayersReadyToPresent + .emplace_or_replace(state.surface.get(), std::numeric_limits::max()); + } + }); +} + TransactionHandler::TransactionReadiness TransactionHandler::applyFilters( TransactionFlushState& flushState) { auto ready = TransactionReadiness::Ready; @@ -79,8 +129,7 @@ TransactionHandler::TransactionReadiness TransactionHandler::applyFilters( case TransactionReadiness::NotReadyBarrier: return perFilterReady; - case TransactionReadiness::ReadyUnsignaled: - case TransactionReadiness::ReadyUnsignaledSingle: + case TransactionReadiness::NotReadyUnsignaled: // If one of the filters allows latching an unsignaled buffer, latch this ready // state. ready = perFilterReady; @@ -97,17 +146,7 @@ int TransactionHandler::flushPendingTransactionQueues(std::vectorsecond; - IBinder* queueToken = it->first.get(); - - // if we have already flushed a transaction with an unsignaled buffer then stop queue - // processing - if (std::find(flushState.queuesWithUnsignaledBuffers.begin(), - flushState.queuesWithUnsignaledBuffers.end(), - queueToken) != flushState.queuesWithUnsignaledBuffers.end()) { - continue; - } - + auto& [applyToken, queue] = *it; while (!queue.empty()) { auto& transaction = queue.front(); flushState.transaction = &transaction; @@ -117,38 +156,14 @@ int TransactionHandler::flushPendingTransactionQueues(std::vectorflags.test( - BufferData::BufferDataChange::frameNumberChanged); - if (frameNumberChanged) { - flushState.bufferLayersReadyToPresent - .emplace_or_replace(state.surface.get(), - state.bufferData->frameNumber); - } else { - // Barrier function only used for BBQ which always includes a frame number. - // This value only used for barrier logic. - flushState.bufferLayersReadyToPresent - .emplace_or_replace(state.surface.get(), - std::numeric_limits::max()); - } - }); - } else if (ready == TransactionReadiness::ReadyUnsignaledSingle) { - // Track queues with a flushed unsingaled buffer. - flushState.queuesWithUnsignaledBuffers.emplace_back(queueToken); + } else if (ready == TransactionReadiness::NotReadyUnsignaled) { + // We maybe able to latch this transaction if it's the only transaction + // ready to be applied. + flushState.queueWithUnsignaledBuffer = applyToken; break; } + // ready == TransactionReadiness::Ready + popTransactionFromPending(transactions, flushState, queue); } if (queue.empty()) { diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h index 7fc825eba3..865835f92d 100644 --- a/services/surfaceflinger/FrontEnd/TransactionHandler.h +++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h @@ -40,14 +40,20 @@ public: // Layer handles that have transactions with buffers that are ready to be applied. ftl::SmallMap bufferLayersReadyToPresent = {}; - ftl::SmallVector queuesWithUnsignaledBuffers; + // Tracks the queue with an unsignaled buffer. This is used to handle + // LatchUnsignaledConfig::AutoSingleLayer to ensure we only apply an unsignaled buffer + // if it's the only transaction that is ready to be applied. + sp queueWithUnsignaledBuffer = nullptr; }; enum class TransactionReadiness { + // Transaction is ready to be applied + Ready, + // Transaction has unmet conditions (fence, present time, etc) and cannot be applied. NotReady, + // Transaction is waiting on a barrier (another buffer to be latched first) NotReadyBarrier, - Ready, - ReadyUnsignaled, - ReadyUnsignaledSingle, + // Transaction has an unsignaled fence but can be applied if it's the only transaction + NotReadyUnsignaled, }; using TransactionFilter = std::function; @@ -64,6 +70,9 @@ private: friend class ::android::TestableSurfaceFlinger; int flushPendingTransactionQueues(std::vector&, TransactionFlushState&); + void applyUnsignaledBufferTransaction(std::vector&, TransactionFlushState&); + void popTransactionFromPending(std::vector&, TransactionFlushState&, + std::queue&); TransactionReadiness applyFilters(TransactionFlushState&); std::unordered_map, std::queue, IListenerHash> mPendingTransactionQueues; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index a27108378a..4be4d5901b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4266,20 +4266,23 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC return TraverseBuffersReturnValues::STOP_TRAVERSAL; } - // check fence status - const bool allowLatchUnsignaled = shouldLatchUnsignaled(layer, s, transaction.states.size(), - flushState.firstTransaction); - ATRACE_FORMAT("%s allowLatchUnsignaled=%s", layer->getName().c_str(), - allowLatchUnsignaled ? "true" : "false"); - - const bool acquireFenceChanged = s.bufferData && + // ignore the acquire fence if LatchUnsignaledConfig::Always is set. + const bool checkAcquireFence = enableLatchUnsignaledConfig != LatchUnsignaledConfig::Always; + const bool acquireFenceAvailable = s.bufferData && s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) && s.bufferData->acquireFence; - const bool fenceSignaled = - (!acquireFenceChanged || - s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled); + const bool fenceSignaled = !checkAcquireFence || !acquireFenceAvailable || + s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled; if (!fenceSignaled) { - if (!allowLatchUnsignaled) { + // check fence status + const bool allowLatchUnsignaled = + shouldLatchUnsignaled(layer, s, transaction.states.size(), + flushState.firstTransaction); + ATRACE_FORMAT("%s allowLatchUnsignaled=%s", layer->getName().c_str(), + allowLatchUnsignaled ? "true" : "false"); + if (allowLatchUnsignaled) { + ready = TransactionReadiness::NotReadyUnsignaled; + } else { ready = TransactionReadiness::NotReady; auto& listener = s.bufferData->releaseBufferListener; if (listener && @@ -4292,10 +4295,6 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC } return TraverseBuffersReturnValues::STOP_TRAVERSAL; } - - ready = enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer - ? TransactionReadiness::ReadyUnsignaledSingle - : TransactionReadiness::ReadyUnsignaled; } return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL; }); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 74d00dd60c..fd6125c0e3 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -174,10 +174,15 @@ enum class LatchUnsignaledConfig { // Latch unsignaled is permitted when a single layer is updated in a frame, // and the update includes just a buffer update (i.e. no sync transactions // or geometry changes). + // Latch unsignaled is also only permitted when a single transaction is ready + // to be applied. If we pass an unsignaled fence to HWC, HWC might miss presenting + // the frame if the fence does not fire in time. If we apply another transaction, + // we may penalize the other transaction unfairly. AutoSingleLayer, // All buffers are latched unsignaled. This behaviour is discouraged as it // can break sync transactions, stall the display and cause undesired side effects. + // This is equivalent to ignoring the acquire fence when applying transactions. Always, }; diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index d4e2357b6b..03c4e713a0 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -294,7 +294,8 @@ public: return fence; } - ComposerState createComposerState(int layerId, sp fence, uint64_t what) { + ComposerState createComposerState(int layerId, sp fence, uint64_t what, + std::optional> layerHandle = std::nullopt) { ComposerState state; state.state.bufferData = std::make_shared(/* bufferId */ 123L, /* width */ 1, @@ -302,15 +303,20 @@ public: /* outUsage */ 0); state.state.bufferData->acquireFence = std::move(fence); state.state.layerId = layerId; - state.state.surface = + state.state.surface = layerHandle.value_or( sp::make(LayerCreationArgs(mFlinger.flinger(), nullptr, "TestLayer", 0, {})) - ->getHandle(); + ->getHandle()); state.state.bufferData->flags = BufferData::BufferDataChange::fenceChanged; state.state.what = what; if (what & layer_state_t::eCropChanged) { state.state.crop = Rect(1, 2, 3, 4); } + if (what & layer_state_t::eFlagsChanged) { + state.state.flags = layer_state_t::eEnableBackpressure; + state.state.mask = layer_state_t::eEnableBackpressure; + } + return state; } @@ -601,6 +607,41 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, DontLatchUnsignaledWhenEarlyOffset) { setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending); } +TEST_F(LatchUnsignaledAutoSingleLayerTest, UnsignaledNotAppliedWhenThereAreSignaled_SignaledFirst) { + const sp kApplyToken1 = + IInterface::asBinder(TransactionCompletedListener::getIInstance()); + const sp kApplyToken2 = sp::make(); + const sp kApplyToken3 = sp::make(); + const auto kLayerId1 = 1; + const auto kLayerId2 = 2; + const auto kExpectedTransactionsPending = 1u; + + const auto signaledTransaction = + createTransactionInfo(kApplyToken1, + { + createComposerState(kLayerId1, + fence(Fence::Status::Signaled), + layer_state_t::eBufferChanged), + }); + const auto signaledTransaction2 = + createTransactionInfo(kApplyToken2, + { + createComposerState(kLayerId1, + fence(Fence::Status::Signaled), + layer_state_t::eBufferChanged), + }); + const auto unsignaledTransaction = + createTransactionInfo(kApplyToken3, + { + createComposerState(kLayerId2, + fence(Fence::Status::Unsignaled), + layer_state_t::eBufferChanged), + }); + + setTransactionStates({signaledTransaction, signaledTransaction2, unsignaledTransaction}, + kExpectedTransactionsPending); +} + class LatchUnsignaledDisabledTest : public LatchUnsignaledTest { public: void SetUp() override { @@ -943,6 +984,43 @@ TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesUnsignaledFromTheQueue) { kExpectedTransactionsPending); } +TEST_F(LatchUnsignaledAlwaysTest, RespectsBackPressureFlag) { + const sp kApplyToken1 = + IInterface::asBinder(TransactionCompletedListener::getIInstance()); + const sp kApplyToken2 = sp::make(); + const auto kLayerId1 = 1; + const auto kExpectedTransactionsPending = 1u; + auto layer = + sp::make(LayerCreationArgs(mFlinger.flinger(), nullptr, "TestLayer", 0, {})); + auto layerHandle = layer->getHandle(); + const auto setBackPressureFlagTransaction = + createTransactionInfo(kApplyToken1, + {createComposerState(kLayerId1, fence(Fence::Status::Unsignaled), + layer_state_t::eBufferChanged | + layer_state_t::eFlagsChanged, + {layerHandle})}); + setTransactionStates({setBackPressureFlagTransaction}, 0u); + + const auto unsignaledTransaction = + createTransactionInfo(kApplyToken1, + { + createComposerState(kLayerId1, + fence(Fence::Status::Unsignaled), + layer_state_t::eBufferChanged, + {layerHandle}), + }); + const auto unsignaledTransaction2 = + createTransactionInfo(kApplyToken1, + { + createComposerState(kLayerId1, + fence(Fence::Status::Unsignaled), + layer_state_t::eBufferChanged, + {layerHandle}), + }); + setTransactionStates({unsignaledTransaction, unsignaledTransaction2}, + kExpectedTransactionsPending); +} + TEST_F(LatchUnsignaledAlwaysTest, LatchUnsignaledWhenEarlyOffset) { const sp kApplyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance()); -- GitLab From 2f65e97a72d7a79380713c98b324d9dfb1a148a6 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 4 Apr 2023 16:36:28 +0000 Subject: [PATCH 1083/1310] [layer tracing] generate composition state for non-default displays Fixes: 264688936 Test: load multi display trace on winscope Change-Id: Id6c07af31a3d1175b91c943e9d744a62cb9fbba5 --- services/surfaceflinger/Layer.cpp | 11 +++++++---- services/surfaceflinger/Layer.h | 2 +- services/surfaceflinger/LayerProtoHelper.cpp | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 9 +++++++++ services/surfaceflinger/SurfaceFlinger.h | 2 ++ 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 755e58521d..cf4d2157b4 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -2122,7 +2122,9 @@ LayerProto* Layer::writeToProto(LayersProto& layersProto, uint32_t traceFlags) { writeToProtoCommonState(layerProto, LayerVector::StateSet::Drawing, traceFlags); if (traceFlags & LayerTracing::TRACE_COMPOSITION) { - writeCompositionStateToProto(layerProto); + ui::LayerStack layerStack = + (mSnapshot) ? mSnapshot->outputFilter.layerStack : ui::INVALID_LAYER_STACK; + writeCompositionStateToProto(layerProto, layerStack); } for (const sp& layer : mDrawingChildren) { @@ -2132,14 +2134,15 @@ LayerProto* Layer::writeToProto(LayersProto& layersProto, uint32_t traceFlags) { return layerProto; } -void Layer::writeCompositionStateToProto(LayerProto* layerProto) { +void Layer::writeCompositionStateToProto(LayerProto* layerProto, ui::LayerStack layerStack) { ftl::FakeGuard guard(mFlinger->mStateLock); // Called from the main thread. + ftl::FakeGuard mainThreadGuard(kMainThreadContext); // Only populate for the primary display. - if (const auto display = mFlinger->getDefaultDisplayDeviceLocked()) { + if (const auto display = mFlinger->getDisplayFromLayerStack(layerStack)) { const auto compositionType = getCompositionType(*display); layerProto->set_hwc_composition_type(static_cast(compositionType)); - LayerProtoHelper::writeToProto(getVisibleRegion(display.get()), + LayerProtoHelper::writeToProto(getVisibleRegion(display), [&]() { return layerProto->mutable_visible_region(); }); } } diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 1af648a20f..72c44cc8ce 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -610,7 +610,7 @@ public: bool isRemovedFromCurrentState() const; LayerProto* writeToProto(LayersProto& layersProto, uint32_t traceFlags); - void writeCompositionStateToProto(LayerProto* layerProto); + void writeCompositionStateToProto(LayerProto* layerProto, ui::LayerStack layerStack); // Write states that are modified by the main thread. This includes drawing // state as well as buffer data. This should be called in the main or tracing diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp index b5ae1a7f5a..5d924852a4 100644 --- a/services/surfaceflinger/LayerProtoHelper.cpp +++ b/services/surfaceflinger/LayerProtoHelper.cpp @@ -332,7 +332,7 @@ void LayerProtoFromSnapshotGenerator::writeHierarchyToProto( if (mTraceFlags & LayerTracing::TRACE_COMPOSITION) { auto it = mLegacyLayers.find(layer.id); if (it != mLegacyLayers.end()) { - it->second->writeCompositionStateToProto(layerProto); + it->second->writeCompositionStateToProto(layerProto, snapshot->outputFilter.layerStack); } } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index a27108378a..968115c103 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -8903,6 +8903,15 @@ void SurfaceFlinger::forceFutureUpdate(int delayInMs) { static_cast(mScheduler->scheduleDelayed([&]() { scheduleRepaint(); }, ms2ns(delayInMs))); } +const DisplayDevice* SurfaceFlinger::getDisplayFromLayerStack(ui::LayerStack layerStack) { + for (const auto& [_, display] : mDisplays) { + if (display->getLayerStack() == layerStack) { + return display.get(); + } + } + return nullptr; +} + } // namespace android #if defined(__gl_h_) diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 74d00dd60c..4199910335 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -323,6 +323,8 @@ public: bool mIgnoreHwcPhysicalDisplayOrientation = false; void forceFutureUpdate(int delayInMs); + const DisplayDevice* getDisplayFromLayerStack(ui::LayerStack) + REQUIRES(mStateLock, kMainThreadContext); protected: // We're reference counted, never destroy SurfaceFlinger directly -- GitLab From 080dff216a3cccd527847c8a06167f9e6b9df805 Mon Sep 17 00:00:00 2001 From: Lakshman Annadorai Date: Tue, 4 Apr 2023 13:44:14 -0700 Subject: [PATCH 1084/1310] Add automovive interfaces to the list of interfaces to dump. Test: m Bug: 261767596 Change-Id: I6396a2679017b20035af1f4d9adb9c201825e186 --- libs/dumputils/dump_utils.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libs/dumputils/dump_utils.cpp b/libs/dumputils/dump_utils.cpp index 067ce17c33..56c3854f35 100644 --- a/libs/dumputils/dump_utils.cpp +++ b/libs/dumputils/dump_utils.cpp @@ -61,7 +61,10 @@ static const char* hidl_hal_interfaces_to_dump[] { "android.hardware.audio@7.0::IDevicesFactory", "android.hardware.automotive.audiocontrol@1.0::IAudioControl", "android.hardware.automotive.audiocontrol@2.0::IAudioControl", + "android.hardware.automotive.can@1.0::ICanBus", + "android.hardware.automotive.can@1.0::ICanController", "android.hardware.automotive.evs@1.0::IEvsCamera", + "android.hardware.automotive.sv@1.0::ISurroundViewService", "android.hardware.automotive.vehicle@2.0::IVehicle", "android.hardware.biometrics.face@1.0::IBiometricsFace", "android.hardware.biometrics.fingerprint@2.1::IBiometricsFingerprint", @@ -86,7 +89,12 @@ static const char* hidl_hal_interfaces_to_dump[] { /* list of hal interface to dump containing process during native dumps */ static const std::vector aidl_interfaces_to_dump { "android.hardware.automotive.audiocontrol.IAudioControl", + "android.hardware.automotive.can.ICanController", "android.hardware.automotive.evs.IEvsEnumerator", + "android.hardware.automotive.ivn.IIvnAndroidDevice", + "android.hardware.automotive.occupant_awareness.IOccupantAwareness", + "android.hardware.automotive.remoteaccess.IRemoteAccess", + "android.hardware.automotive.vehicle.IVehicle", "android.hardware.biometrics.face.IBiometricsFace", "android.hardware.biometrics.fingerprint.IBiometricsFingerprint", "android.hardware.camera.provider.ICameraProvider", -- GitLab From 628ef6ec39d08666745d4aafc39db3422e2dfec0 Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Thu, 30 Mar 2023 14:49:03 -0700 Subject: [PATCH 1085/1310] Fix tone shift issue when switching between GPU and DPU composition. - This fixes the issue that extended layer gets crushed in Adaptive mode. - When playing with extended HDR api in Adaptive mode, dim in gamma space and convert transfer to gamma22 to get a `OETF_gamma22(EOTF_sRGB(color))` SkRuntimeEffect. Bug: 267530123 Test: play extended HDR on SilkFx and toggle between GPU/DPU composition. Change-Id: I029c86ea98969d736a72b9099d8e457006d9db38 --- libs/renderengine/skia/SkiaRenderEngine.cpp | 59 +++++++++++++-------- libs/renderengine/skia/SkiaRenderEngine.h | 1 + 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index e393fb2e92..8256dd8e69 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -264,14 +264,11 @@ void SkiaRenderEngine::setEnableTracing(bool tracingEnabled) { SkAndroidFrameworkTraceUtil::setEnableTracing(tracingEnabled); } -SkiaRenderEngine::SkiaRenderEngine( - RenderEngineType type, - PixelFormat pixelFormat, - bool useColorManagement, - bool supportsBackgroundBlur) : - RenderEngine(type), - mDefaultPixelFormat(pixelFormat), - mUseColorManagement(useColorManagement) { +SkiaRenderEngine::SkiaRenderEngine(RenderEngineType type, PixelFormat pixelFormat, + bool useColorManagement, bool supportsBackgroundBlur) + : RenderEngine(type), + mDefaultPixelFormat(pixelFormat), + mUseColorManagement(useColorManagement) { if (supportsBackgroundBlur) { ALOGD("Background Blurs Enabled"); mBlurFilter = new KawaseBlurFilter(); @@ -507,15 +504,9 @@ sk_sp SkiaRenderEngine::createRuntimeEffectShader( } if (parameters.requiresLinearEffect) { - const ui::Dataspace inputDataspace = mUseColorManagement ? parameters.layer.sourceDataspace - : ui::Dataspace::V0_SRGB_LINEAR; - const ui::Dataspace outputDataspace = mUseColorManagement - ? parameters.display.outputDataspace - : ui::Dataspace::V0_SRGB_LINEAR; - auto effect = - shaders::LinearEffect{.inputDataspace = inputDataspace, - .outputDataspace = outputDataspace, + shaders::LinearEffect{.inputDataspace = parameters.layer.sourceDataspace, + .outputDataspace = parameters.outputDataSpace, .undoPremultipliedAlpha = parameters.undoPremultipliedAlpha}; auto effectIter = mRuntimeEffects.find(effect); @@ -678,9 +669,8 @@ void SkiaRenderEngine::drawLayersInternal( // wait on the buffer to be ready to use prior to using it waitFence(grContext, bufferFence); - const ui::Dataspace dstDataspace = - mUseColorManagement ? display.outputDataspace : ui::Dataspace::V0_SRGB_LINEAR; - sk_sp dstSurface = surfaceTextureRef->getOrCreateSurface(dstDataspace, grContext); + sk_sp dstSurface = + surfaceTextureRef->getOrCreateSurface(display.outputDataspace, grContext); SkCanvas* dstCanvas = mCapture->tryCapture(dstSurface.get()); if (dstCanvas == nullptr) { @@ -888,10 +878,31 @@ void SkiaRenderEngine::drawLayersInternal( const bool dimInLinearSpace = display.dimmingStage != aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF; + const bool isExtendedHdr = (layer.sourceDataspace & ui::Dataspace::RANGE_MASK) == + static_cast(ui::Dataspace::RANGE_EXTENDED) && + (display.outputDataspace & ui::Dataspace::TRANSFER_MASK) == + static_cast(ui::Dataspace::TRANSFER_SRGB); + + const ui::Dataspace runtimeEffectDataspace = !dimInLinearSpace && isExtendedHdr + ? static_cast( + (display.outputDataspace & ui::Dataspace::STANDARD_MASK) | + ui::Dataspace::TRANSFER_GAMMA2_2 | + (display.outputDataspace & ui::Dataspace::RANGE_MASK)) + : display.outputDataspace; + + // If the input dataspace is range extended, the output dataspace transfer is sRGB + // and dimmingStage is GAMMA_OETF, dim in linear space instead, and + // set the output dataspace's transfer to be GAMMA2_2. + // This allows DPU side to use oetf_gamma_2p2 for extended HDR layer + // to avoid tone shift. + // The reason of tone shift here is because HDR layers manage white point + // luminance in linear space, which color pipelines request GAMMA_OETF break + // without a gamma 2.2 fixup. const bool requiresLinearEffect = layer.colorTransform != mat4() || (mUseColorManagement && needsToneMapping(layer.sourceDataspace, display.outputDataspace)) || - (dimInLinearSpace && !equalsWithinMargin(1.f, layerDimmingRatio)); + (dimInLinearSpace && !equalsWithinMargin(1.f, layerDimmingRatio)) || + (!dimInLinearSpace && isExtendedHdr); // quick abort from drawing the remaining portion of the layer if (layer.skipContentDraw || @@ -904,7 +915,7 @@ void SkiaRenderEngine::drawLayersInternal( // image with the same colorspace as the destination surface so that Skia's color // management is a no-op. const ui::Dataspace layerDataspace = (!mUseColorManagement || requiresLinearEffect) - ? dstDataspace + ? display.outputDataspace : layer.sourceDataspace; SkPaint paint; @@ -985,7 +996,8 @@ void SkiaRenderEngine::drawLayersInternal( .requiresLinearEffect = requiresLinearEffect, .layerDimmingRatio = dimInLinearSpace ? layerDimmingRatio - : 1.f})); + : 1.f, + .outputDataSpace = runtimeEffectDataspace})); // Turn on dithering when dimming beyond this (arbitrary) threshold... static constexpr float kDimmingThreshold = 0.2f; @@ -1048,7 +1060,8 @@ void SkiaRenderEngine::drawLayersInternal( .display = display, .undoPremultipliedAlpha = false, .requiresLinearEffect = requiresLinearEffect, - .layerDimmingRatio = layerDimmingRatio})); + .layerDimmingRatio = layerDimmingRatio, + .outputDataSpace = runtimeEffectDataspace})); } if (layer.disableBlending) { diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h index e4406b437b..6457bfa9eb 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.h +++ b/libs/renderengine/skia/SkiaRenderEngine.h @@ -156,6 +156,7 @@ private: bool undoPremultipliedAlpha; bool requiresLinearEffect; float layerDimmingRatio; + const ui::Dataspace outputDataSpace; }; sk_sp createRuntimeEffectShader(const RuntimeEffectShaderParameters&); -- GitLab From 949cb3d098fd98715826fc92ea3c26a51aa2d976 Mon Sep 17 00:00:00 2001 From: Evan Severson Date: Tue, 4 Apr 2023 14:46:06 -0700 Subject: [PATCH 1086/1310] Add AppOps overload to be able to watch foreground changes. We have never offered the native API to register mode watchers that are invoked for foregroundness changes when the raw mode is MODE_FOREGROUND. Test: Add logging to verify invocation Bug: 247768581 Change-Id: I89af46de557fbfc31d69613367a4e26a5222430a --- libs/permission/AppOpsManager.cpp | 8 ++++++++ libs/permission/IAppOpsService.cpp | 11 +++++++++++ libs/permission/include/binder/AppOpsManager.h | 6 ++++++ libs/permission/include/binder/IAppOpsService.h | 3 +++ 4 files changed, 28 insertions(+) diff --git a/libs/permission/AppOpsManager.cpp b/libs/permission/AppOpsManager.cpp index baa9d75116..695927418d 100644 --- a/libs/permission/AppOpsManager.cpp +++ b/libs/permission/AppOpsManager.cpp @@ -146,6 +146,14 @@ void AppOpsManager::startWatchingMode(int32_t op, const String16& packageName, } } +void AppOpsManager::startWatchingMode(int32_t op, const String16& packageName, int32_t flags, + const sp& callback) { + sp service = getService(); + if (service != nullptr) { + service->startWatchingModeWithFlags(op, packageName, flags, callback); + } +} + void AppOpsManager::stopWatchingMode(const sp& callback) { sp service = getService(); if (service != nullptr) { diff --git a/libs/permission/IAppOpsService.cpp b/libs/permission/IAppOpsService.cpp index d59f44562e..7f235a4541 100644 --- a/libs/permission/IAppOpsService.cpp +++ b/libs/permission/IAppOpsService.cpp @@ -166,6 +166,17 @@ public: } return reply.readBool(); } + + virtual void startWatchingModeWithFlags(int32_t op, const String16& packageName, + int32_t flags, const sp& callback) { + Parcel data, reply; + data.writeInterfaceToken(IAppOpsService::getInterfaceDescriptor()); + data.writeInt32(op); + data.writeString16(packageName); + data.writeInt32(flags); + data.writeStrongBinder(IInterface::asBinder(callback)); + remote()->transact(START_WATCHING_MODE_WITH_FLAGS_TRANSACTION, data, &reply); + } }; IMPLEMENT_META_INTERFACE(AppOpsService, "com.android.internal.app.IAppOpsService") diff --git a/libs/permission/include/binder/AppOpsManager.h b/libs/permission/include/binder/AppOpsManager.h index abcd527966..243532bc4d 100644 --- a/libs/permission/include/binder/AppOpsManager.h +++ b/libs/permission/include/binder/AppOpsManager.h @@ -151,6 +151,10 @@ public: _NUM_OP = 117 }; + enum { + WATCH_FOREGROUND_CHANGES = 1 << 0 + }; + AppOpsManager(); int32_t checkOp(int32_t op, int32_t uid, const String16& callingPackage); @@ -174,6 +178,8 @@ public: const std::optional& attributionTag); void startWatchingMode(int32_t op, const String16& packageName, const sp& callback); + void startWatchingMode(int32_t op, const String16& packageName, int32_t flags, + const sp& callback); void stopWatchingMode(const sp& callback); int32_t permissionToOpCode(const String16& permission); void setCameraAudioRestriction(int32_t mode); diff --git a/libs/permission/include/binder/IAppOpsService.h b/libs/permission/include/binder/IAppOpsService.h index 22f056b235..918fcdbce1 100644 --- a/libs/permission/include/binder/IAppOpsService.h +++ b/libs/permission/include/binder/IAppOpsService.h @@ -52,6 +52,8 @@ public: const String16& packageName) = 0; virtual void setCameraAudioRestriction(int32_t mode) = 0; virtual bool shouldCollectNotes(int32_t opCode) = 0; + virtual void startWatchingModeWithFlags(int32_t op, const String16& packageName, + int32_t flags, const sp& callback) = 0; enum { CHECK_OPERATION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION, @@ -64,6 +66,7 @@ public: CHECK_AUDIO_OPERATION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+7, SHOULD_COLLECT_NOTES_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+8, SET_CAMERA_AUDIO_RESTRICTION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+9, + START_WATCHING_MODE_WITH_FLAGS_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+10, }; enum { -- GitLab From 31f03e33d5c2e240afe5ff9f675429a43990af2d Mon Sep 17 00:00:00 2001 From: Nick Deakin Date: Fri, 31 Mar 2023 16:00:13 -0400 Subject: [PATCH 1087/1310] jpegrecoverymap: XMP fixes. * Update semantic type from RecoveryMap to GainMap * Add parseType="Resource" to Directory rdf:li's, for XMP toolkit * Fix writing of HDRCapacity Min and Max values Bug: 264715926 Test: Tests pass (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:42d08f450b07080b3d2a64875a26046cce150822) Merged-In: Ia4073cd75646dd79aee391e8d44169b3527f6e93 Change-Id: Ia4073cd75646dd79aee391e8d44169b3527f6e93 --- .../include/jpegrecoverymap/jpegrutils.h | 18 ++--- libs/jpegrecoverymap/jpegrutils.cpp | 71 +++++++++++-------- libs/jpegrecoverymap/tests/jpegr_test.cpp | 4 +- 3 files changed, 53 insertions(+), 40 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h index dd06fa24b1..09f416555c 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h @@ -105,14 +105,16 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata_struc * xmlns:Item="http://ns.google.com/photos/1.0/container/item/"> * * - * + * * * - * + * * * @@ -142,14 +144,14 @@ std::string generateXmpForPrimaryImage(int secondary_image_length); * + * hdrgm:HDRCapacityMin="0" + * hdrgm:HDRCapacityMax="3" + * hdrgm:BaseRenditionIsHDR="False"/> * * * diff --git a/libs/jpegrecoverymap/jpegrutils.cpp b/libs/jpegrecoverymap/jpegrutils.cpp index ff96447320..cde0ceb520 100644 --- a/libs/jpegrecoverymap/jpegrutils.cpp +++ b/libs/jpegrecoverymap/jpegrutils.cpp @@ -15,14 +15,17 @@ */ #include -#include + +#include +#include + #include #include #include #include #include #include -#include +#include using namespace photos_editing_formats::image_io; using namespace std; @@ -230,26 +233,26 @@ const string kItemMime = Name(kItemPrefix, "Mime"); const string kItemSemantic = Name(kItemPrefix, "Semantic"); // Item XMP constants - element and attribute values -const string kSemanticPrimary = "Primary"; -const string kSemanticRecoveryMap = "RecoveryMap"; -const string kMimeImageJpeg = "image/jpeg"; - -// RecoveryMap XMP constants - URI and namespace prefix -const string kRecoveryMapUri = "http://ns.adobe.com/hdr-gain-map/1.0/"; -const string kRecoveryMapPrefix = "hdrgm"; - -// RecoveryMap XMP constants - element and attribute names -const string kMapVersion = Name(kRecoveryMapPrefix, "Version"); -const string kMapGainMapMin = Name(kRecoveryMapPrefix, "GainMapMin"); -const string kMapGainMapMax = Name(kRecoveryMapPrefix, "GainMapMax"); -const string kMapGamma = Name(kRecoveryMapPrefix, "Gamma"); -const string kMapOffsetSdr = Name(kRecoveryMapPrefix, "OffsetSDR"); -const string kMapOffsetHdr = Name(kRecoveryMapPrefix, "OffsetHDR"); -const string kMapHDRCapacityMin = Name(kRecoveryMapPrefix, "HDRCapacityMin"); -const string kMapHDRCapacityMax = Name(kRecoveryMapPrefix, "HDRCapacityMax"); -const string kMapBaseRendition = Name(kRecoveryMapPrefix, "BaseRendition"); - -// RecoveryMap XMP constants - names for XMP handlers +const string kSemanticPrimary = "Primary"; +const string kSemanticGainMap = "GainMap"; +const string kMimeImageJpeg = "image/jpeg"; + +// GainMap XMP constants - URI and namespace prefix +const string kGainMapUri = "http://ns.adobe.com/hdr-gain-map/1.0/"; +const string kGainMapPrefix = "hdrgm"; + +// GainMap XMP constants - element and attribute names +const string kMapVersion = Name(kGainMapPrefix, "Version"); +const string kMapGainMapMin = Name(kGainMapPrefix, "GainMapMin"); +const string kMapGainMapMax = Name(kGainMapPrefix, "GainMapMax"); +const string kMapGamma = Name(kGainMapPrefix, "Gamma"); +const string kMapOffsetSdr = Name(kGainMapPrefix, "OffsetSDR"); +const string kMapOffsetHdr = Name(kGainMapPrefix, "OffsetHDR"); +const string kMapHDRCapacityMin = Name(kGainMapPrefix, "HDRCapacityMin"); +const string kMapHDRCapacityMax = Name(kGainMapPrefix, "HDRCapacityMax"); +const string kMapBaseRenditionIsHDR = Name(kGainMapPrefix, "BaseRenditionIsHDR"); + +// GainMap XMP constants - names for XMP handlers const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin; const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax; @@ -313,15 +316,23 @@ string generateXmpForPrimaryImage(int secondary_image_length) { writer.StartWritingElement("rdf:Description"); writer.WriteXmlns(kContainerPrefix, kContainerUri); writer.WriteXmlns(kItemPrefix, kItemUri); + writer.StartWritingElements(kConDirSeq); - size_t item_depth = writer.StartWritingElements(kLiItem); + + size_t item_depth = writer.StartWritingElement("rdf:li"); + writer.WriteAttributeNameAndValue("rdf:parseType", "Resource"); + writer.StartWritingElement(kConItem); writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary); writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg); writer.FinishWritingElementsToDepth(item_depth); - writer.StartWritingElements(kLiItem); - writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticRecoveryMap); + + writer.StartWritingElement("rdf:li"); + writer.WriteAttributeNameAndValue("rdf:parseType", "Resource"); + writer.StartWritingElement(kConItem); + writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticGainMap); writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg); writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length); + writer.FinishWriting(); return ss.str(); @@ -329,7 +340,6 @@ string generateXmpForPrimaryImage(int secondary_image_length) { string generateXmpForSecondaryImage(jpegr_metadata_struct& metadata) { const vector kConDirSeq({kConDirectory, string("rdf:Seq")}); - const vector kLiItem({string("rdf:li"), kConItem}); std::stringstream ss; photos_editing_formats::image_io::XmlWriter writer(ss); @@ -339,16 +349,17 @@ string generateXmpForSecondaryImage(jpegr_metadata_struct& metadata) { writer.StartWritingElement("rdf:RDF"); writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); writer.StartWritingElement("rdf:Description"); - writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri); + writer.WriteXmlns(kGainMapPrefix, kGainMapUri); writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost)); writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost)); writer.WriteAttributeNameAndValue(kMapGamma, "1"); writer.WriteAttributeNameAndValue(kMapOffsetSdr, "0"); writer.WriteAttributeNameAndValue(kMapOffsetHdr, "0"); - writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, "0"); - writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, "2.3"); - writer.WriteAttributeNameAndValue(kMapBaseRendition, "SDR"); + writer.WriteAttributeNameAndValue( + kMapHDRCapacityMin, std::max(log2(metadata.minContentBoost), 0.0f)); + writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, log2(metadata.maxContentBoost)); + writer.WriteAttributeNameAndValue(kMapBaseRenditionIsHDR, "False"); writer.FinishWriting(); return ss.str(); diff --git a/libs/jpegrecoverymap/tests/jpegr_test.cpp b/libs/jpegrecoverymap/tests/jpegr_test.cpp index df90f53c41..7c669aba0a 100644 --- a/libs/jpegrecoverymap/tests/jpegr_test.cpp +++ b/libs/jpegrecoverymap/tests/jpegr_test.cpp @@ -192,8 +192,8 @@ TEST_F(JpegRTest, writeXmpThenRead) { jpegr_metadata_struct metadata_read; EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read)); - ASSERT_EQ(metadata_expected.maxContentBoost, metadata_read.maxContentBoost); - ASSERT_EQ(metadata_expected.minContentBoost, metadata_read.minContentBoost); + EXPECT_FLOAT_EQ(metadata_expected.maxContentBoost, metadata_read.maxContentBoost); + EXPECT_FLOAT_EQ(metadata_expected.minContentBoost, metadata_read.minContentBoost); } /* Test Encode API-0 and decode */ -- GitLab From 4be4eefa428bb63c423b6a1cd97940d3af53b18b Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Tue, 28 Mar 2023 14:26:01 +0000 Subject: [PATCH 1088/1310] InputMapper refactor: Rename configure to reconfigure We are refactoring Input-mapper to ensure they are configured at the time of initialisation. In this CL, we start by renaming the InputMapper::configure method to InputMapper::reconfigure. Test: build, pre-submit, inputflinger_tests Bug: 256009910 Change-Id: Ifafe1170d892285e904cf08115efdac9923420e7 --- services/inputflinger/reader/InputDevice.cpp | 2 +- .../inputflinger/reader/mapper/CursorInputMapper.cpp | 8 ++++---- .../inputflinger/reader/mapper/CursorInputMapper.h | 6 +++--- .../reader/mapper/ExternalStylusInputMapper.cpp | 6 +++--- .../reader/mapper/ExternalStylusInputMapper.h | 6 +++--- services/inputflinger/reader/mapper/InputMapper.cpp | 4 ++-- services/inputflinger/reader/mapper/InputMapper.h | 6 +++--- .../inputflinger/reader/mapper/JoystickInputMapper.cpp | 8 ++++---- .../inputflinger/reader/mapper/JoystickInputMapper.h | 6 +++--- .../inputflinger/reader/mapper/KeyboardInputMapper.cpp | 8 ++++---- .../inputflinger/reader/mapper/KeyboardInputMapper.h | 6 +++--- .../reader/mapper/RotaryEncoderInputMapper.cpp | 8 ++++---- .../reader/mapper/RotaryEncoderInputMapper.h | 6 +++--- .../inputflinger/reader/mapper/SensorInputMapper.cpp | 8 ++++---- .../inputflinger/reader/mapper/SensorInputMapper.h | 6 +++--- .../inputflinger/reader/mapper/TouchInputMapper.cpp | 8 ++++---- services/inputflinger/reader/mapper/TouchInputMapper.h | 6 +++--- .../inputflinger/reader/mapper/TouchpadInputMapper.cpp | 6 +++--- .../inputflinger/reader/mapper/TouchpadInputMapper.h | 6 +++--- services/inputflinger/tests/InputReader_test.cpp | 4 ++-- .../inputflinger/tests/fuzzers/CursorInputFuzzer.cpp | 10 +++++----- .../inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp | 4 ++-- .../tests/fuzzers/MultiTouchInputFuzzer.cpp | 7 +++---- 23 files changed, 72 insertions(+), 73 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index ddf6c87c02..eaed987320 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -385,7 +385,7 @@ std::list InputDevice::configure(nsecs_t when, const InputReaderConf } for_each_mapper([this, when, &config, changes, &out](InputMapper& mapper) { - out += mapper.configure(when, config, changes); + out += mapper.reconfigure(when, config, changes); mSources |= mapper.getSources(); }); diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index a3fdcdf19d..d7dc2aec70 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -133,10 +133,10 @@ void CursorInputMapper::dump(std::string& dump) { dump += StringPrintf(INDENT3 "DownTime: %" PRId64 "\n", mDownTime); } -std::list CursorInputMapper::configure(nsecs_t when, - const InputReaderConfiguration* config, - uint32_t changes) { - std::list out = InputMapper::configure(when, config, changes); +std::list CursorInputMapper::reconfigure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) { + std::list out = InputMapper::reconfigure(when, config, changes); if (!changes) { // first time only mCursorScrollAccumulator.configure(getDeviceContext()); diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index 5f02203c1f..987b9debb9 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -59,9 +59,9 @@ public: virtual uint32_t getSources() const override; virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; virtual void dump(std::string& dump) override; - [[nodiscard]] std::list configure(nsecs_t when, - const InputReaderConfiguration* config, - uint32_t changes) override; + [[nodiscard]] std::list reconfigure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp index 99e6cf9a74..c5a3075657 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp @@ -46,9 +46,9 @@ void ExternalStylusInputMapper::dump(std::string& dump) { dumpStylusState(dump, mStylusState); } -std::list ExternalStylusInputMapper::configure(nsecs_t when, - const InputReaderConfiguration* config, - uint32_t changes) { +std::list ExternalStylusInputMapper::reconfigure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) { getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPressureAxis); mTouchButtonAccumulator.configure(); return {}; diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h index 11b53154ac..0df8cf7b1b 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h @@ -32,9 +32,9 @@ public: uint32_t getSources() const override; void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; void dump(std::string& dump) override; - [[nodiscard]] std::list configure(nsecs_t when, - const InputReaderConfiguration* config, - uint32_t changes) override; + [[nodiscard]] std::list reconfigure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp index 9cf3696e15..9d1e9ceb1c 100644 --- a/services/inputflinger/reader/mapper/InputMapper.cpp +++ b/services/inputflinger/reader/mapper/InputMapper.cpp @@ -35,8 +35,8 @@ void InputMapper::populateDeviceInfo(InputDeviceInfo& info) { void InputMapper::dump(std::string& dump) {} -std::list InputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, - uint32_t changes) { +std::list InputMapper::reconfigure(nsecs_t when, const InputReaderConfiguration* config, + uint32_t changes) { return {}; } diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h index 2722edd137..bb15e4d86b 100644 --- a/services/inputflinger/reader/mapper/InputMapper.h +++ b/services/inputflinger/reader/mapper/InputMapper.h @@ -53,9 +53,9 @@ public: virtual uint32_t getSources() const = 0; virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo); virtual void dump(std::string& dump); - [[nodiscard]] virtual std::list configure(nsecs_t when, - const InputReaderConfiguration* config, - uint32_t changes); + [[nodiscard]] virtual std::list reconfigure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes); [[nodiscard]] virtual std::list reset(nsecs_t when); [[nodiscard]] virtual std::list process(const RawEvent* rawEvent) = 0; [[nodiscard]] virtual std::list timeoutExpired(nsecs_t when); diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp index f65cdcb677..f60035bd00 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp @@ -103,10 +103,10 @@ void JoystickInputMapper::dump(std::string& dump) { } } -std::list JoystickInputMapper::configure(nsecs_t when, - const InputReaderConfiguration* config, - uint32_t changes) { - std::list out = InputMapper::configure(when, config, changes); +std::list JoystickInputMapper::reconfigure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) { + std::list out = InputMapper::reconfigure(when, config, changes); if (!changes) { // first time only // Collect all axes. diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.h b/services/inputflinger/reader/mapper/JoystickInputMapper.h index 9ca4176cff..9adb07fb4e 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.h +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h @@ -28,9 +28,9 @@ public: virtual uint32_t getSources() const override; virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; virtual void dump(std::string& dump) override; - [[nodiscard]] std::list configure(nsecs_t when, - const InputReaderConfiguration* config, - uint32_t changes) override; + [[nodiscard]] std::list reconfigure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index 269c1060d6..fc00c4886d 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -131,10 +131,10 @@ std::optional KeyboardInputMapper::findViewport( return std::nullopt; } -std::list KeyboardInputMapper::configure(nsecs_t when, - const InputReaderConfiguration* config, - uint32_t changes) { - std::list out = InputMapper::configure(when, config, changes); +std::list KeyboardInputMapper::reconfigure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) { + std::list out = InputMapper::reconfigure(when, config, changes); if (!changes) { // first time only // Configure basic parameters. diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index 2fc82c3d13..52576c339c 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -29,9 +29,9 @@ public: uint32_t getSources() const override; void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; void dump(std::string& dump) override; - [[nodiscard]] std::list configure(nsecs_t when, - const InputReaderConfiguration* config, - uint32_t changes) override; + [[nodiscard]] std::list reconfigure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp index c0a35b196e..5b7b29509e 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp @@ -63,10 +63,10 @@ void RotaryEncoderInputMapper::dump(std::string& dump) { toString(mRotaryEncoderScrollAccumulator.haveRelativeVWheel())); } -std::list RotaryEncoderInputMapper::configure(nsecs_t when, - const InputReaderConfiguration* config, - uint32_t changes) { - std::list out = InputMapper::configure(when, config, changes); +std::list RotaryEncoderInputMapper::reconfigure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) { + std::list out = InputMapper::reconfigure(when, config, changes); if (!changes) { mRotaryEncoderScrollAccumulator.configure(getDeviceContext()); } diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h index a0516c44fa..639a9876c7 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h @@ -31,9 +31,9 @@ public: virtual uint32_t getSources() const override; virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; virtual void dump(std::string& dump) override; - [[nodiscard]] std::list configure(nsecs_t when, - const InputReaderConfiguration* config, - uint32_t changes) override; + [[nodiscard]] std::list reconfigure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp index 60e6727ed2..720fc6962d 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp @@ -116,10 +116,10 @@ void SensorInputMapper::dump(std::string& dump) { } } -std::list SensorInputMapper::configure(nsecs_t when, - const InputReaderConfiguration* config, - uint32_t changes) { - std::list out = InputMapper::configure(when, config, changes); +std::list SensorInputMapper::reconfigure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) { + std::list out = InputMapper::reconfigure(when, config, changes); if (!changes) { // first time only mDeviceEnabled = true; diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h index 7f47df723e..93cc244481 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.h +++ b/services/inputflinger/reader/mapper/SensorInputMapper.h @@ -33,9 +33,9 @@ public: uint32_t getSources() const override; void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; void dump(std::string& dump) override; - [[nodiscard]] std::list configure(nsecs_t when, - const InputReaderConfiguration* config, - uint32_t changes) override; + [[nodiscard]] std::list reconfigure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; bool enableSensor(InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod, diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 073c18babb..66d3849e5c 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -287,10 +287,10 @@ void TouchInputMapper::dump(std::string& dump) { } } -std::list TouchInputMapper::configure(nsecs_t when, - const InputReaderConfiguration* config, - uint32_t changes) { - std::list out = InputMapper::configure(when, config, changes); +std::list TouchInputMapper::reconfigure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) { + std::list out = InputMapper::reconfigure(when, config, changes); mConfig = *config; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index bc358b97e6..dd6a9c330a 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -152,9 +152,9 @@ public: uint32_t getSources() const override; void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; void dump(std::string& dump) override; - [[nodiscard]] std::list configure(nsecs_t when, - const InputReaderConfiguration* config, - uint32_t changes) override; + [[nodiscard]] std::list reconfigure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 661461bacc..33f368e9eb 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -219,9 +219,9 @@ void TouchpadInputMapper::dump(std::string& dump) { dump += addLinePrefix(mPropertyProvider.dump(), INDENT4); } -std::list TouchpadInputMapper::configure(nsecs_t when, - const InputReaderConfiguration* config, - uint32_t changes) { +std::list TouchpadInputMapper::reconfigure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) { if (!changes) { // First time configuration mPropertyProvider.loadPropertiesFromIdcFile(getDeviceContext().getConfiguration()); diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index fb36d920b6..6f152fa557 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -44,9 +44,9 @@ public: void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; void dump(std::string& dump) override; - [[nodiscard]] std::list configure(nsecs_t when, - const InputReaderConfiguration* config, - uint32_t changes) override; + [[nodiscard]] std::list reconfigure(nsecs_t when, + const InputReaderConfiguration* config, + uint32_t changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 3d0fbb4455..2223b35183 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -263,8 +263,8 @@ private: } } - std::list configure(nsecs_t, const InputReaderConfiguration* config, - uint32_t changes) override { + std::list reconfigure(nsecs_t, const InputReaderConfiguration* config, + uint32_t changes) override { std::scoped_lock lock(mLock); mConfigureWasCalled = true; diff --git a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp index 9a19b97f73..0d5f30c046 100644 --- a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp @@ -52,13 +52,13 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { [&]() -> void { mapper.getSources(); }, [&]() -> void { std::list unused = - mapper.configure(fdp->ConsumeIntegral(), &policyConfig, - fdp->ConsumeIntegral()); + mapper.reconfigure(fdp->ConsumeIntegral(), &policyConfig, + fdp->ConsumeIntegral()); }, [&]() -> void { // Need to reconfigure with 0 or you risk a NPE. std::list unused = - mapper.configure(fdp->ConsumeIntegral(), &policyConfig, 0); + mapper.reconfigure(fdp->ConsumeIntegral(), &policyConfig, 0); InputDeviceInfo info; mapper.populateDeviceInfo(info); }, @@ -71,7 +71,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { // Need to reconfigure with 0 or you risk a NPE. std::list unused = - mapper.configure(fdp->ConsumeIntegral(), &policyConfig, 0); + mapper.reconfigure(fdp->ConsumeIntegral(), &policyConfig, 0); RawEvent rawEvent{fdp->ConsumeIntegral(), fdp->ConsumeIntegral(), fdp->ConsumeIntegral(), @@ -90,7 +90,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { [&]() -> void { // Need to reconfigure with 0 or you risk a NPE. std::list unused = - mapper.configure(fdp->ConsumeIntegral(), &policyConfig, 0); + mapper.reconfigure(fdp->ConsumeIntegral(), &policyConfig, 0); mapper.getAssociatedDisplayId(); }, })(); diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp index 33e7dbf488..14cb8a5280 100644 --- a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp @@ -64,8 +64,8 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { [&]() -> void { mapper.getSources(); }, [&]() -> void { std::list unused = - mapper.configure(fdp->ConsumeIntegral(), &policyConfig, - fdp->ConsumeIntegral()); + mapper.reconfigure(fdp->ConsumeIntegral(), &policyConfig, + fdp->ConsumeIntegral()); }, [&]() -> void { std::list unused = mapper.reset(fdp->ConsumeIntegral()); diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp index 20db39d885..8352a9011e 100644 --- a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp @@ -79,8 +79,8 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { [&]() -> void { mapper.getSources(); }, [&]() -> void { std::list unused = - mapper.configure(fdp->ConsumeIntegral(), &policyConfig, - fdp->ConsumeIntegral()); + mapper.reconfigure(fdp->ConsumeIntegral(), &policyConfig, + fdp->ConsumeIntegral()); }, [&]() -> void { std::list unused = mapper.reset(fdp->ConsumeIntegral()); @@ -127,8 +127,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { [&]() -> void { StylusState state{fdp->ConsumeIntegral(), fdp->ConsumeFloatingPoint(), - fdp->ConsumeIntegral(), - getFuzzedToolType(*fdp)}; + fdp->ConsumeIntegral(), getFuzzedToolType(*fdp)}; std::list unused = mapper.updateExternalStylusState(state); }, [&]() -> void { mapper.getAssociatedDisplayId(); }, -- GitLab From fb7ed56c090725d2b39f85656e71febf637fc3e7 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Wed, 5 Apr 2023 11:39:25 -0400 Subject: [PATCH 1089/1310] Avoid deadlock in MessageQueue::onNewVsyncSchedule It is hard to reproduce, but if the VSyncDispatchTimerQueueEntry::callback is called at just the right time, it will set mRunning to true, then wait on MessageQueue's mVsync.mutex, which was already locked by onNewVsyncSchedule. When onNewVsyncScheduleLocked destructs the old VSyncCallbackRegistration, it needs to wait until mRunning is set back to false. But VSyncDispatchTimerQueueEntry::callback cannot do that until MessageQueue::vsyncCallback can lock the mutex. Avoid this deadlock by moving the old VSyncCallbackRegistration's destruction until after mVsync.mutex is released. Bug: 276367387 Test: infeasible Change-Id: I86e66df59c64e81c4aa721a25250697f61237488 --- .../surfaceflinger/Scheduler/MessageQueue.cpp | 37 +++++++++++++++---- .../surfaceflinger/Scheduler/MessageQueue.h | 7 +++- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp index 7457b84011..18c0a696d5 100644 --- a/services/surfaceflinger/Scheduler/MessageQueue.cpp +++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp @@ -78,20 +78,42 @@ void MessageQueue::vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, ns void MessageQueue::initVsync(std::shared_ptr dispatch, frametimeline::TokenManager& tokenManager, std::chrono::nanoseconds workDuration) { - std::lock_guard lock(mVsync.mutex); - mVsync.workDuration = workDuration; - mVsync.tokenManager = &tokenManager; - onNewVsyncScheduleLocked(std::move(dispatch)); + std::unique_ptr oldRegistration; + { + std::lock_guard lock(mVsync.mutex); + mVsync.workDuration = workDuration; + mVsync.tokenManager = &tokenManager; + oldRegistration = onNewVsyncScheduleLocked(std::move(dispatch)); + } + + // See comments in onNewVsyncSchedule. Today, oldRegistration should be + // empty, but nothing prevents us from calling initVsync multiple times, so + // go ahead and destruct it outside the lock for safety. + oldRegistration.reset(); } void MessageQueue::onNewVsyncSchedule(std::shared_ptr dispatch) { - std::lock_guard lock(mVsync.mutex); - onNewVsyncScheduleLocked(std::move(dispatch)); + std::unique_ptr oldRegistration; + { + std::lock_guard lock(mVsync.mutex); + oldRegistration = onNewVsyncScheduleLocked(std::move(dispatch)); + } + + // The old registration needs to be deleted after releasing mVsync.mutex to + // avoid deadlock. This is because the callback may be running on the timer + // thread. In that case, timerCallback sets + // VSyncDispatchTimerQueueEntry::mRunning to true, then attempts to lock + // mVsync.mutex. But if it's already locked, the VSyncCallbackRegistration's + // destructor has to wait until VSyncDispatchTimerQueueEntry::mRunning is + // set back to false, but it won't be until mVsync.mutex is released. + oldRegistration.reset(); } -void MessageQueue::onNewVsyncScheduleLocked(std::shared_ptr dispatch) { +std::unique_ptr MessageQueue::onNewVsyncScheduleLocked( + std::shared_ptr dispatch) { const bool reschedule = mVsync.registration && mVsync.registration->cancel() == scheduler::CancelResult::Cancelled; + auto oldRegistration = std::move(mVsync.registration); mVsync.registration = std::make_unique< scheduler::VSyncCallbackRegistration>(std::move(dispatch), std::bind(&MessageQueue::vsyncCallback, this, @@ -105,6 +127,7 @@ void MessageQueue::onNewVsyncScheduleLocked(std::shared_ptr registration; mutable std::mutex mutex; + std::unique_ptr registration GUARDED_BY(mutex); TracedOrdinal workDuration GUARDED_BY(mutex) = {"VsyncWorkDuration-sf", std::chrono::nanoseconds(0)}; TimePoint lastCallbackTime GUARDED_BY(mutex); @@ -129,7 +129,10 @@ private: Vsync mVsync; - void onNewVsyncScheduleLocked(std::shared_ptr) REQUIRES(mVsync.mutex); + // Returns the old registration so it can be destructed outside the lock to + // avoid deadlock. + std::unique_ptr onNewVsyncScheduleLocked( + std::shared_ptr) REQUIRES(mVsync.mutex); public: explicit MessageQueue(ICompositor&); -- GitLab From e4f832e21dbad52f7b455d557584d8baad66dd6f Mon Sep 17 00:00:00 2001 From: John Reck Date: Wed, 5 Apr 2023 15:07:27 -0400 Subject: [PATCH 1090/1310] Remove incorrect isDataSpaceValid Bug: 276807477 Test: SilkFX demo Change-Id: I3d11924199b4eb18cd0ec3fbddc88ed7d5c42a6e --- libs/nativewindow/ANativeWindow.cpp | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp index 5306529fcb..dd5958de28 100644 --- a/libs/nativewindow/ANativeWindow.cpp +++ b/libs/nativewindow/ANativeWindow.cpp @@ -79,27 +79,6 @@ static int64_t query64(ANativeWindow* window, int what) { return res < 0 ? res : value; } -static bool isDataSpaceValid(ANativeWindow* window, int32_t dataSpace) { - bool supported = false; - switch (dataSpace) { - case HAL_DATASPACE_UNKNOWN: - case HAL_DATASPACE_V0_SRGB: - return true; - // These data space need wide gamut support. - case HAL_DATASPACE_V0_SCRGB_LINEAR: - case HAL_DATASPACE_V0_SCRGB: - case HAL_DATASPACE_DISPLAY_P3: - native_window_get_wide_color_support(window, &supported); - return supported; - // These data space need HDR support. - case HAL_DATASPACE_BT2020_PQ: - native_window_get_hdr_support(window, &supported); - return supported; - default: - return false; - } -} - /************************************************************************************************** * NDK **************************************************************************************************/ @@ -219,8 +198,7 @@ int32_t ANativeWindow_setBuffersDataSpace(ANativeWindow* window, int32_t dataSpa static_assert(static_cast(ADATASPACE_DEPTH) == static_cast(HAL_DATASPACE_DEPTH)); static_assert(static_cast(ADATASPACE_DYNAMIC_DEPTH) == static_cast(HAL_DATASPACE_DYNAMIC_DEPTH)); - if (!window || !query(window, NATIVE_WINDOW_IS_VALID) || - !isDataSpaceValid(window, dataSpace)) { + if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) { return -EINVAL; } return native_window_set_buffers_data_space(window, -- GitLab From 265f96fb55dcacb3dc038ef5b2f9d9387c9fc037 Mon Sep 17 00:00:00 2001 From: Ayush Jain Date: Thu, 6 Apr 2023 01:00:25 +0000 Subject: [PATCH 1091/1310] Revert "Remove incorrect isDataSpaceValid" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit e4f832e21dbad52f7b455d557584d8baad66dd6f. Reason for revert: DroidMonitor: Potential culprit for Bug b/277122111 - verifying through ABTD before revert submission. This is part of the standard investigation process, and does not mean your CL will be reverted.†Change-Id: I0fc4f98eb83448562c57433aa68018bcfee746bf --- libs/nativewindow/ANativeWindow.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp index dd5958de28..5306529fcb 100644 --- a/libs/nativewindow/ANativeWindow.cpp +++ b/libs/nativewindow/ANativeWindow.cpp @@ -79,6 +79,27 @@ static int64_t query64(ANativeWindow* window, int what) { return res < 0 ? res : value; } +static bool isDataSpaceValid(ANativeWindow* window, int32_t dataSpace) { + bool supported = false; + switch (dataSpace) { + case HAL_DATASPACE_UNKNOWN: + case HAL_DATASPACE_V0_SRGB: + return true; + // These data space need wide gamut support. + case HAL_DATASPACE_V0_SCRGB_LINEAR: + case HAL_DATASPACE_V0_SCRGB: + case HAL_DATASPACE_DISPLAY_P3: + native_window_get_wide_color_support(window, &supported); + return supported; + // These data space need HDR support. + case HAL_DATASPACE_BT2020_PQ: + native_window_get_hdr_support(window, &supported); + return supported; + default: + return false; + } +} + /************************************************************************************************** * NDK **************************************************************************************************/ @@ -198,7 +219,8 @@ int32_t ANativeWindow_setBuffersDataSpace(ANativeWindow* window, int32_t dataSpa static_assert(static_cast(ADATASPACE_DEPTH) == static_cast(HAL_DATASPACE_DEPTH)); static_assert(static_cast(ADATASPACE_DYNAMIC_DEPTH) == static_cast(HAL_DATASPACE_DYNAMIC_DEPTH)); - if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) { + if (!window || !query(window, NATIVE_WINDOW_IS_VALID) || + !isDataSpaceValid(window, dataSpace)) { return -EINVAL; } return native_window_set_buffers_data_space(window, -- GitLab From dbd14eb7bcfc3255455c343da1b52f99bbd864ff Mon Sep 17 00:00:00 2001 From: Cody Heiner Date: Thu, 30 Mar 2023 18:41:45 -0700 Subject: [PATCH 1092/1310] Add outputLength method Test: build succeeds, `atest libinput-tests` passes. Bug: 268245099 Change-Id: I030da703a907eef44e85d186144eddc53b5998cc --- include/input/TfLiteMotionPredictor.h | 3 +++ libs/input/TfLiteMotionPredictor.cpp | 4 ++++ libs/input/tests/TfLiteMotionPredictor_test.cpp | 6 ++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/include/input/TfLiteMotionPredictor.h b/include/input/TfLiteMotionPredictor.h index 7de551b417..a340bd0575 100644 --- a/include/input/TfLiteMotionPredictor.h +++ b/include/input/TfLiteMotionPredictor.h @@ -106,6 +106,9 @@ public: // Returns the length of the model's input buffers. size_t inputLength() const; + // Returns the length of the model's output buffers. + size_t outputLength() const; + // Executes the model. // Returns true if the model successfully executed and the output tensors can be read. bool invoke(); diff --git a/libs/input/TfLiteMotionPredictor.cpp b/libs/input/TfLiteMotionPredictor.cpp index 3b061d1cf1..85fa176129 100644 --- a/libs/input/TfLiteMotionPredictor.cpp +++ b/libs/input/TfLiteMotionPredictor.cpp @@ -346,6 +346,10 @@ size_t TfLiteMotionPredictorModel::inputLength() const { return getTensorBuffer(mInputR).size(); } +size_t TfLiteMotionPredictorModel::outputLength() const { + return getTensorBuffer(mOutputR).size(); +} + std::span TfLiteMotionPredictorModel::inputR() { return getTensorBuffer(mInputR); } diff --git a/libs/input/tests/TfLiteMotionPredictor_test.cpp b/libs/input/tests/TfLiteMotionPredictor_test.cpp index 6e76ac1e52..b5ed9e4430 100644 --- a/libs/input/tests/TfLiteMotionPredictor_test.cpp +++ b/libs/input/tests/TfLiteMotionPredictor_test.cpp @@ -139,8 +139,10 @@ TEST(TfLiteMotionPredictorTest, ModelInputOutputLength) { ASSERT_TRUE(model->invoke()); - ASSERT_EQ(model->outputR().size(), model->outputPhi().size()); - ASSERT_EQ(model->outputR().size(), model->outputPressure().size()); + const int outputLength = model->outputLength(); + ASSERT_EQ(outputLength, model->outputR().size()); + ASSERT_EQ(outputLength, model->outputPhi().size()); + ASSERT_EQ(outputLength, model->outputPressure().size()); } TEST(TfLiteMotionPredictorTest, ModelOutput) { -- GitLab From f30d7a20884274011e1f8005eed5c2a763d2f7a2 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Thu, 30 Mar 2023 13:44:41 +0000 Subject: [PATCH 1093/1310] Refactor HardwareStateConverterTest In a follow-up CL, I'll be modifying HardwareStateConverter to need more constructor parameters, so moving the initialization into the test constructor will make the tests a bit cleaner. This removes the ability to add the MSC_TIMESTAMP axis before initializing the converter in the MscTimestamp test. However, none of the code under test actually depends on this axis being present in the EventHub. Bug: 259547750 Test: atest inputflinger_tests:HardwareStateConverterTest Change-Id: Id99218c4dc81ef80014512489306465aee9fe427 --- .../tests/HardwareStateConverter_test.cpp | 216 ++++++++---------- 1 file changed, 98 insertions(+), 118 deletions(-) diff --git a/services/inputflinger/tests/HardwareStateConverter_test.cpp b/services/inputflinger/tests/HardwareStateConverter_test.cpp index 3e9724100c..b2385269ec 100644 --- a/services/inputflinger/tests/HardwareStateConverter_test.cpp +++ b/services/inputflinger/tests/HardwareStateConverter_test.cpp @@ -13,11 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include + +#include #include -#include #include #include +#include #include "FakeEventHub.h" #include "FakeInputReaderPolicy.h" @@ -28,38 +31,37 @@ namespace android { class HardwareStateConverterTest : public testing::Test { +public: + HardwareStateConverterTest() + : mFakeEventHub(std::make_shared()), + mFakePolicy(sp::make()), + mReader(mFakeEventHub, mFakePolicy, mFakeListener), + mDevice(newDevice()), + mDeviceContext(*mDevice, EVENTHUB_ID) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, 7, 0, 0, 0); + mConverter = std::make_unique(mDeviceContext); + } + protected: static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000; static constexpr int32_t EVENTHUB_ID = 1; - void SetUp() { - mFakeEventHub = std::make_unique(); - mFakePolicy = sp::make(); - mFakeListener = std::make_unique(); - mReader = std::make_unique(mFakeEventHub, mFakePolicy, - *mFakeListener); - mDevice = newDevice(); - - mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, 7, 0, 0, 0); - } - std::shared_ptr newDevice() { InputDeviceIdentifier identifier; identifier.name = "device"; identifier.location = "USB1"; identifier.bus = 0; std::shared_ptr device = - std::make_shared(mReader->getContext(), DEVICE_ID, /* generation= */ 2, + std::make_shared(mReader.getContext(), DEVICE_ID, /*generation=*/2, identifier); - mReader->pushNextDevice(device); + mReader.pushNextDevice(device); mFakeEventHub->addDevice(EVENTHUB_ID, identifier.name, InputDeviceClass::TOUCHPAD, identifier.bus); - mReader->loopOnce(); + mReader.loopOnce(); return device; } - void processAxis(HardwareStateConverter& conv, nsecs_t when, int32_t type, int32_t code, - int32_t value) { + void processAxis(nsecs_t when, int32_t type, int32_t code, int32_t value) { RawEvent event; event.when = when; event.readTime = READ_TIME; @@ -67,12 +69,11 @@ protected: event.type = type; event.code = code; event.value = value; - std::optional schs = conv.processRawEvent(&event); + std::optional schs = mConverter->processRawEvent(&event); EXPECT_FALSE(schs.has_value()); } - std::optional processSync(HardwareStateConverter& conv, - nsecs_t when) { + std::optional processSync(nsecs_t when) { RawEvent event; event.when = when; event.readTime = READ_TIME; @@ -80,37 +81,37 @@ protected: event.type = EV_SYN; event.code = SYN_REPORT; event.value = 0; - return conv.processRawEvent(&event); + return mConverter->processRawEvent(&event); } std::shared_ptr mFakeEventHub; sp mFakePolicy; - std::unique_ptr mFakeListener; - std::unique_ptr mReader; + TestInputListener mFakeListener; + InstrumentedInputReader mReader; std::shared_ptr mDevice; + InputDeviceContext mDeviceContext; + std::unique_ptr mConverter; }; TEST_F(HardwareStateConverterTest, OneFinger) { const nsecs_t time = 1500000000; - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - HardwareStateConverter conv(deviceContext); - - processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0); - processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123); - processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50); - processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100); - processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MAJOR, 5); - processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MINOR, 4); - processAxis(conv, time, EV_ABS, ABS_MT_PRESSURE, 42); - processAxis(conv, time, EV_ABS, ABS_MT_ORIENTATION, 2); - - processAxis(conv, time, EV_ABS, ABS_X, 50); - processAxis(conv, time, EV_ABS, ABS_Y, 100); - processAxis(conv, time, EV_ABS, ABS_PRESSURE, 42); - - processAxis(conv, time, EV_KEY, BTN_TOUCH, 1); - processAxis(conv, time, EV_KEY, BTN_TOOL_FINGER, 1); - std::optional schs = processSync(conv, time); + + processAxis(time, EV_ABS, ABS_MT_SLOT, 0); + processAxis(time, EV_ABS, ABS_MT_TRACKING_ID, 123); + processAxis(time, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(time, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(time, EV_ABS, ABS_MT_TOUCH_MAJOR, 5); + processAxis(time, EV_ABS, ABS_MT_TOUCH_MINOR, 4); + processAxis(time, EV_ABS, ABS_MT_PRESSURE, 42); + processAxis(time, EV_ABS, ABS_MT_ORIENTATION, 2); + + processAxis(time, EV_ABS, ABS_X, 50); + processAxis(time, EV_ABS, ABS_Y, 100); + processAxis(time, EV_ABS, ABS_PRESSURE, 42); + + processAxis(time, EV_KEY, BTN_TOUCH, 1); + processAxis(time, EV_KEY, BTN_TOOL_FINGER, 1); + std::optional schs = processSync(time); ASSERT_TRUE(schs.has_value()); const HardwareState& state = schs->state; @@ -138,35 +139,31 @@ TEST_F(HardwareStateConverterTest, OneFinger) { } TEST_F(HardwareStateConverterTest, TwoFingers) { - const nsecs_t time = ARBITRARY_TIME; - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - HardwareStateConverter conv(deviceContext); - - processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0); - processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123); - processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50); - processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100); - processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MAJOR, 5); - processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MINOR, 4); - processAxis(conv, time, EV_ABS, ABS_MT_PRESSURE, 42); - processAxis(conv, time, EV_ABS, ABS_MT_ORIENTATION, 2); - - processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 1); - processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 456); - processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, -20); - processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 40); - processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MAJOR, 8); - processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MINOR, 7); - processAxis(conv, time, EV_ABS, ABS_MT_PRESSURE, 21); - processAxis(conv, time, EV_ABS, ABS_MT_ORIENTATION, 1); - - processAxis(conv, time, EV_ABS, ABS_X, 50); - processAxis(conv, time, EV_ABS, ABS_Y, 100); - processAxis(conv, time, EV_ABS, ABS_PRESSURE, 42); - - processAxis(conv, time, EV_KEY, BTN_TOUCH, 1); - processAxis(conv, time, EV_KEY, BTN_TOOL_DOUBLETAP, 1); - std::optional schs = processSync(conv, time); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 0); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 123); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOUCH_MAJOR, 5); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOUCH_MINOR, 4); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_PRESSURE, 42); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_ORIENTATION, 2); + + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 1); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 456); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, -20); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 40); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOUCH_MAJOR, 8); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOUCH_MINOR, 7); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_PRESSURE, 21); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_ORIENTATION, 1); + + processAxis(ARBITRARY_TIME, EV_ABS, ABS_X, 50); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_Y, 100); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_PRESSURE, 42); + + processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOUCH, 1); + processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOOL_DOUBLETAP, 1); + std::optional schs = processSync(ARBITRARY_TIME); ASSERT_TRUE(schs.has_value()); ASSERT_EQ(2, schs->state.finger_cnt); @@ -192,58 +189,50 @@ TEST_F(HardwareStateConverterTest, TwoFingers) { } TEST_F(HardwareStateConverterTest, OnePalm) { - const nsecs_t time = ARBITRARY_TIME; - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - HardwareStateConverter conv(deviceContext); - - processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0); - processAxis(conv, time, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); - processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123); - processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50); - processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100); - - processAxis(conv, time, EV_KEY, BTN_TOUCH, 1); - std::optional schs = processSync(conv, time); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 0); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 123); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 100); + + processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOUCH, 1); + std::optional schs = processSync(ARBITRARY_TIME); ASSERT_TRUE(schs.has_value()); EXPECT_EQ(0, schs->state.finger_cnt); } TEST_F(HardwareStateConverterTest, OneFingerTurningIntoAPalm) { - const nsecs_t time = ARBITRARY_TIME; - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - HardwareStateConverter conv(deviceContext); - - processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0); - processAxis(conv, time, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); - processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123); - processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50); - processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_SLOT, 0); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TRACKING_ID, 123); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 100); - processAxis(conv, time, EV_KEY, BTN_TOUCH, 1); + processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOUCH, 1); - std::optional schs = processSync(conv, time); + std::optional schs = processSync(ARBITRARY_TIME); ASSERT_TRUE(schs.has_value()); EXPECT_EQ(1, schs->state.finger_cnt); - processAxis(conv, time, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); - processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 51); - processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 99); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 51); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 99); - schs = processSync(conv, time); + schs = processSync(ARBITRARY_TIME); ASSERT_TRUE(schs.has_value()); ASSERT_EQ(0, schs->state.finger_cnt); - processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 53); - processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 97); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 53); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 97); - schs = processSync(conv, time); + schs = processSync(ARBITRARY_TIME); ASSERT_TRUE(schs.has_value()); EXPECT_EQ(0, schs->state.finger_cnt); - processAxis(conv, time, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); - processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 55); - processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 95); - schs = processSync(conv, time); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 55); + processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 95); + schs = processSync(ARBITRARY_TIME); ASSERT_TRUE(schs.has_value()); ASSERT_EQ(1, schs->state.finger_cnt); const FingerState& newFinger = schs->state.fingers[0]; @@ -253,25 +242,16 @@ TEST_F(HardwareStateConverterTest, OneFingerTurningIntoAPalm) { } TEST_F(HardwareStateConverterTest, ButtonPressed) { - const nsecs_t time = ARBITRARY_TIME; - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - HardwareStateConverter conv(deviceContext); - - processAxis(conv, time, EV_KEY, BTN_LEFT, 1); - std::optional schs = processSync(conv, time); + processAxis(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 1); + std::optional schs = processSync(ARBITRARY_TIME); ASSERT_TRUE(schs.has_value()); EXPECT_EQ(GESTURES_BUTTON_LEFT, schs->state.buttons_down); } TEST_F(HardwareStateConverterTest, MscTimestamp) { - const nsecs_t time = ARBITRARY_TIME; - mFakeEventHub->setMscEvent(EVENTHUB_ID, MSC_TIMESTAMP); - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - HardwareStateConverter conv(deviceContext); - - processAxis(conv, time, EV_MSC, MSC_TIMESTAMP, 1200000); - std::optional schs = processSync(conv, time); + processAxis(ARBITRARY_TIME, EV_MSC, MSC_TIMESTAMP, 1200000); + std::optional schs = processSync(ARBITRARY_TIME); ASSERT_TRUE(schs.has_value()); EXPECT_NEAR(1.2, schs->state.msc_timestamp, EPSILON); -- GitLab From 195c5f8010282591655f20f8576a2f139b7f713f Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Thu, 30 Mar 2023 15:20:38 +0000 Subject: [PATCH 1094/1310] Touchpad: correct HardwareState::touch_cnt for palms The palm filtering logic previously only reduced the HardwareState::finger_cnt field when the touchpad had identified palms, not touch_cnt. I suspect that this was leading the Gestures library to go into T5R2 mode (for touchpads that sometimes report more touches than they can track), meaning that palms sometimes caused tap-to-clicks. I can't reproduce this exact issue myself, but correcting this inconsistency is good to do anyway. Bug: 275616121 Test: atest inputflinger_tests:HardwareStateConverterTest Test: check debug logs to verify palms don't cause touch_cnt to increase Change-Id: Ic7b22864214351875afe9969fdbaab42db9d7877 --- .../reader/mapper/gestures/HardwareStateConverter.cpp | 9 +++++++-- .../inputflinger/tests/HardwareStateConverter_test.cpp | 7 +++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp index e89262a71a..8841b6e27f 100644 --- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp @@ -81,11 +81,16 @@ SelfContainedHardwareState HardwareStateConverter::produceHardwareState(nsecs_t } schs.fingers.clear(); + size_t numPalms = 0; for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) { MultiTouchMotionAccumulator::Slot slot = mMotionAccumulator.getSlot(i); + if (!slot.isInUse()) { + continue; + } // Some touchpads continue to report contacts even after they've identified them as palms. // We want to exclude these contacts from the HardwareStates. - if (!slot.isInUse() || slot.getToolType() == ToolType::PALM) { + if (slot.getToolType() == ToolType::PALM) { + numPalms++; continue; } @@ -103,7 +108,7 @@ SelfContainedHardwareState HardwareStateConverter::produceHardwareState(nsecs_t } schs.state.fingers = schs.fingers.data(); schs.state.finger_cnt = schs.fingers.size(); - schs.state.touch_cnt = mTouchButtonAccumulator.getTouchCount(); + schs.state.touch_cnt = mTouchButtonAccumulator.getTouchCount() - numPalms; return schs; } diff --git a/services/inputflinger/tests/HardwareStateConverter_test.cpp b/services/inputflinger/tests/HardwareStateConverter_test.cpp index b2385269ec..19d46c8a22 100644 --- a/services/inputflinger/tests/HardwareStateConverter_test.cpp +++ b/services/inputflinger/tests/HardwareStateConverter_test.cpp @@ -196,8 +196,10 @@ TEST_F(HardwareStateConverterTest, OnePalm) { processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 100); processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOUCH, 1); + processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOOL_FINGER, 1); std::optional schs = processSync(ARBITRARY_TIME); ASSERT_TRUE(schs.has_value()); + EXPECT_EQ(0, schs->state.touch_cnt); EXPECT_EQ(0, schs->state.finger_cnt); } @@ -209,9 +211,11 @@ TEST_F(HardwareStateConverterTest, OneFingerTurningIntoAPalm) { processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 100); processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOUCH, 1); + processAxis(ARBITRARY_TIME, EV_KEY, BTN_TOOL_FINGER, 1); std::optional schs = processSync(ARBITRARY_TIME); ASSERT_TRUE(schs.has_value()); + EXPECT_EQ(1, schs->state.touch_cnt); EXPECT_EQ(1, schs->state.finger_cnt); processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); @@ -220,6 +224,7 @@ TEST_F(HardwareStateConverterTest, OneFingerTurningIntoAPalm) { schs = processSync(ARBITRARY_TIME); ASSERT_TRUE(schs.has_value()); + EXPECT_EQ(0, schs->state.touch_cnt); ASSERT_EQ(0, schs->state.finger_cnt); processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_X, 53); @@ -227,6 +232,7 @@ TEST_F(HardwareStateConverterTest, OneFingerTurningIntoAPalm) { schs = processSync(ARBITRARY_TIME); ASSERT_TRUE(schs.has_value()); + EXPECT_EQ(0, schs->state.touch_cnt); EXPECT_EQ(0, schs->state.finger_cnt); processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); @@ -234,6 +240,7 @@ TEST_F(HardwareStateConverterTest, OneFingerTurningIntoAPalm) { processAxis(ARBITRARY_TIME, EV_ABS, ABS_MT_POSITION_Y, 95); schs = processSync(ARBITRARY_TIME); ASSERT_TRUE(schs.has_value()); + EXPECT_EQ(1, schs->state.touch_cnt); ASSERT_EQ(1, schs->state.finger_cnt); const FingerState& newFinger = schs->state.fingers[0]; EXPECT_EQ(123, newFinger.tracking_id); -- GitLab From 086048019b38645f3cbd703bfe1e7f714e471132 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Thu, 6 Apr 2023 10:19:34 +0000 Subject: [PATCH 1095/1310] TouchButtonAccumulator: note that getTouchCount can include palms Bug: b/275616121 Test: none, since this is a comment-only change Change-Id: Iad0e57adc15d3b2d58a5cac839b2a17b7a852a0e --- .../reader/mapper/accumulator/TouchButtonAccumulator.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h index c5fd5f5168..e829692206 100644 --- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h @@ -41,6 +41,12 @@ public: bool isHovering() const; bool hasStylus() const; bool hasButtonTouch() const; + + /* + * Returns the number of touches reported by the device through its BTN_TOOL_FINGER and + * BTN_TOOL_*TAP "buttons". Note that this count includes touches reported with their + * ABS_MT_TOOL_TYPE set to MT_TOOL_PALM. + */ int getTouchCount() const; private: -- GitLab From 74c7ae1ff65d92c9e5a79498ab6f6ce9c17fc2a9 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Sun, 26 Mar 2023 02:57:47 +0000 Subject: [PATCH 1096/1310] Strengthen dataspace guarantees in SurfaceFlinger. Correctly converting from yuv to rgb in RenderEngine requires that the buffer dataspace is accurate. So, if the dataspace on a layer differs from the buffer then update the dataspace in the buffer's metadata to be consistent. Moreover, some GPU drivers do not perform yuv2rgb in a reasonable way when the dataspace is UNKNOWN. In that case, reauthor the dataspace to be sRGB so that there is consistent behavior for an UNKNOWN dataspace. Finally, some GPU drivers cache gralloc metadata on creation of GPU resources, which is not compliant with gralloc expectations. So that vendors have some time to fix the drivers and so that GSI tests pass, recreate GPU resources when the vendor partition is old and the buffer is a YCbCr format. Bug: 247826480 Test: SurfaceControlTest Change-Id: Iee2641acce3926c826e96c56ececb431868d8598 --- libs/renderengine/ExternalTexture.cpp | 17 +++++-- .../include/renderengine/ExternalTexture.h | 2 + .../renderengine/impl/ExternalTexture.h | 2 + .../renderengine/mock/FakeExternalTexture.h | 1 + services/surfaceflinger/Layer.cpp | 47 +++++++++++++++---- services/surfaceflinger/Layer.h | 2 - .../Tracing/TransactionProtoParser.cpp | 1 + .../tests/unittests/CompositionTest.cpp | 6 +-- 8 files changed, 61 insertions(+), 17 deletions(-) diff --git a/libs/renderengine/ExternalTexture.cpp b/libs/renderengine/ExternalTexture.cpp index 210dca5429..9eb42cd8e1 100644 --- a/libs/renderengine/ExternalTexture.cpp +++ b/libs/renderengine/ExternalTexture.cpp @@ -14,17 +14,17 @@ * limitations under the License. */ +#include #include #include #include - -#include "log/log_main.h" +#include namespace android::renderengine::impl { ExternalTexture::ExternalTexture(const sp& buffer, renderengine::RenderEngine& renderEngine, uint32_t usage) - : mBuffer(buffer), mRenderEngine(renderEngine) { + : mBuffer(buffer), mRenderEngine(renderEngine), mWritable(usage & WRITEABLE) { LOG_ALWAYS_FATAL_IF(buffer == nullptr, "Attempted to bind a null buffer to an external texture!"); // GLESRenderEngine has a separate texture cache for output buffers, @@ -35,11 +35,20 @@ ExternalTexture::ExternalTexture(const sp& buffer, renderengine::RenderEngine::RenderEngineType::THREADED)) { return; } - mRenderEngine.mapExternalTextureBuffer(mBuffer, usage & WRITEABLE); + mRenderEngine.mapExternalTextureBuffer(mBuffer, mWritable); } ExternalTexture::~ExternalTexture() { mRenderEngine.unmapExternalTextureBuffer(std::move(mBuffer)); } +void ExternalTexture::remapBuffer() { + ATRACE_CALL(); + { + auto buf = mBuffer; + mRenderEngine.unmapExternalTextureBuffer(std::move(buf)); + } + mRenderEngine.mapExternalTextureBuffer(mBuffer, mWritable); +} + } // namespace android::renderengine::impl diff --git a/libs/renderengine/include/renderengine/ExternalTexture.h b/libs/renderengine/include/renderengine/ExternalTexture.h index 621a209afa..82e5d83c30 100644 --- a/libs/renderengine/include/renderengine/ExternalTexture.h +++ b/libs/renderengine/include/renderengine/ExternalTexture.h @@ -46,6 +46,8 @@ public: // Retrieves the buffer that is bound to this texture. virtual const sp& getBuffer() const = 0; + virtual void remapBuffer() = 0; + Rect getBounds() const { return {0, 0, static_cast(getWidth()), static_cast(getHeight())}; } diff --git a/libs/renderengine/include/renderengine/impl/ExternalTexture.h b/libs/renderengine/include/renderengine/impl/ExternalTexture.h index c0e24f0c10..d30262d985 100644 --- a/libs/renderengine/include/renderengine/impl/ExternalTexture.h +++ b/libs/renderengine/include/renderengine/impl/ExternalTexture.h @@ -51,10 +51,12 @@ public: bool hasSameBuffer(const renderengine::ExternalTexture& other) const override { return getBuffer() == other.getBuffer(); } + void remapBuffer() override; private: sp mBuffer; android::renderengine::RenderEngine& mRenderEngine; + const bool mWritable; }; } // namespace android::renderengine::impl diff --git a/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h b/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h index b95f011753..474e2e72c8 100644 --- a/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h +++ b/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h @@ -45,6 +45,7 @@ public: uint64_t getId() const override { return mId; } PixelFormat getPixelFormat() const override { return mPixelFormat; } uint64_t getUsage() const override { return mUsage; } + void remapBuffer() override {} ~FakeExternalTexture() = default; }; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index c1920bae50..7d16aad9bf 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -171,8 +171,7 @@ Layer::Layer(const LayerCreationArgs& args) mDrawingState.crop.makeInvalid(); mDrawingState.acquireFence = sp::make(-1); mDrawingState.acquireFenceTime = std::make_shared(mDrawingState.acquireFence); - mDrawingState.dataspace = ui::Dataspace::UNKNOWN; - mDrawingState.dataspaceRequested = false; + mDrawingState.dataspace = ui::Dataspace::V0_SRGB; mDrawingState.hdrMetadata.validTypes = 0; mDrawingState.surfaceDamageRegion = Region::INVALID_REGION; mDrawingState.cornerRadius = 0.0f; @@ -208,7 +207,6 @@ Layer::Layer(const LayerCreationArgs& args) mPremultipliedAlpha = !(args.flags & ISurfaceComposerClient::eNonPremultiplied); mPotentialCursor = args.flags & ISurfaceComposerClient::eCursorWindow; mProtectedByApp = args.flags & ISurfaceComposerClient::eProtectedByApp; - mDrawingState.dataspace = ui::Dataspace::V0_SRGB; mSnapshot->sequence = sequence; mSnapshot->name = getDebugName(); @@ -3136,7 +3134,6 @@ void Layer::recordLayerHistoryAnimationTx(const scheduler::LayerProps& layerProp } bool Layer::setDataspace(ui::Dataspace dataspace) { - mDrawingState.dataspaceRequested = true; if (mDrawingState.dataspace == dataspace) return false; mDrawingState.dataspace = dataspace; mDrawingState.modified = true; @@ -3401,6 +3398,42 @@ void Layer::gatherBufferInfo() { mBufferInfo.mTransform = mDrawingState.bufferTransform; auto lastDataspace = mBufferInfo.mDataspace; mBufferInfo.mDataspace = translateDataspace(mDrawingState.dataspace); + if (mBufferInfo.mBuffer != nullptr) { + auto& mapper = GraphicBufferMapper::get(); + // TODO: We should measure if it's faster to do a blind write if we're on newer api levels + // and don't need to possibly remaps buffers. + ui::Dataspace dataspace = ui::Dataspace::UNKNOWN; + status_t err = OK; + { + ATRACE_NAME("getDataspace"); + err = mapper.getDataspace(mBufferInfo.mBuffer->getBuffer()->handle, &dataspace); + } + if (err != OK || dataspace != mBufferInfo.mDataspace) { + { + ATRACE_NAME("setDataspace"); + err = mapper.setDataspace(mBufferInfo.mBuffer->getBuffer()->handle, + static_cast(mBufferInfo.mDataspace)); + } + + // Some GPU drivers may cache gralloc metadata which means before we composite we need + // to upsert RenderEngine's caches. Put in a special workaround to be backwards + // compatible with old vendors, with a ticking clock. + static const int32_t kVendorVersion = + base::GetIntProperty("ro.vndk.version", __ANDROID_API_FUTURE__); + if (const auto format = + static_cast( + mBufferInfo.mBuffer->getPixelFormat()); + err == OK && kVendorVersion < __ANDROID_API_U__ && + (format == + aidl::android::hardware::graphics::common::PixelFormat:: + IMPLEMENTATION_DEFINED || + format == aidl::android::hardware::graphics::common::PixelFormat::YCBCR_420_888 || + format == aidl::android::hardware::graphics::common::PixelFormat::YV12 || + format == aidl::android::hardware::graphics::common::PixelFormat::YCBCR_P010)) { + mBufferInfo.mBuffer->remapBuffer(); + } + } + } if (lastDataspace != mBufferInfo.mDataspace) { mFlinger->mHdrLayerInfoChanged = true; } @@ -3991,10 +4024,6 @@ uint32_t Layer::getBufferTransform() const { } ui::Dataspace Layer::getDataSpace() const { - return mDrawingState.dataspaceRequested ? getRequestedDataSpace() : ui::Dataspace::UNKNOWN; -} - -ui::Dataspace Layer::getRequestedDataSpace() const { return hasBufferOrSidebandStream() ? mBufferInfo.mDataspace : mDrawingState.dataspace; } @@ -4002,6 +4031,8 @@ ui::Dataspace Layer::translateDataspace(ui::Dataspace dataspace) { ui::Dataspace updatedDataspace = dataspace; // translate legacy dataspaces to modern dataspaces switch (dataspace) { + // Treat unknown dataspaces as V0_sRGB + case ui::Dataspace::UNKNOWN: case ui::Dataspace::SRGB: updatedDataspace = ui::Dataspace::V0_SRGB; break; diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 552ea3701c..429df37f14 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -137,7 +137,6 @@ public: wp touchableRegionCrop; ui::Dataspace dataspace; - bool dataspaceRequested; uint64_t frameNumber; ui::Transform transform; @@ -332,7 +331,6 @@ public: virtual FrameRateCompatibility getDefaultFrameRateCompatibility() const; // ui::Dataspace getDataSpace() const; - ui::Dataspace getRequestedDataSpace() const; virtual sp getCompositionEngineLayerFE() const; virtual sp copyCompositionEngineLayerFE() const; diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp index 7642122adc..a64a9af310 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp @@ -47,6 +47,7 @@ public: uint64_t getId() const override { return mId; } PixelFormat getPixelFormat() const override { return mPixelFormat; } uint64_t getUsage() const override { return mUsage; } + void remapBuffer() override {} ~FakeExternalTexture() = default; }; diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 19a93e1820..6391fec577 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -605,7 +605,7 @@ struct BaseLayerProperties { EXPECT_EQ(false, layer.source.buffer.isOpaque); EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x); EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y); - EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace); + EXPECT_EQ(ui::Dataspace::V0_SRGB, layer.sourceDataspace); EXPECT_EQ(LayerProperties::COLOR[3], layer.alpha); return resultFuture; }); @@ -654,7 +654,7 @@ struct BaseLayerProperties { layer.source.solidColor); EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x); EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y); - EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace); + EXPECT_EQ(ui::Dataspace::V0_SRGB, layer.sourceDataspace); EXPECT_EQ(LayerProperties::COLOR[3], layer.alpha); return resultFuture; }); @@ -731,7 +731,7 @@ struct CommonSecureLayerProperties : public BaseLayerProperties EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), layer.source.solidColor); EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x); EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y); - EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace); + EXPECT_EQ(ui::Dataspace::V0_SRGB, layer.sourceDataspace); EXPECT_EQ(1.0f, layer.alpha); return resultFuture; }); -- GitLab From cbfd41f514985e89f14e8f40bafa3d087fd32f58 Mon Sep 17 00:00:00 2001 From: John Reck Date: Thu, 6 Apr 2023 17:01:32 +0000 Subject: [PATCH 1097/1310] Revert "Revert "Remove incorrect isDataSpaceValid"" This reverts commit 265f96fb55dcacb3dc038ef5b2f9d9387c9fc037. Reason for revert: Fixed underlying issue Bug: 276807477 Test: atest android.graphics.cts.ANativeWindowTest#testSetBuffersDataSpace Change-Id: I101c246f673bf279c3c441759b82bdc607794b75 --- libs/nativewindow/ANativeWindow.cpp | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp index 5306529fcb..dd5958de28 100644 --- a/libs/nativewindow/ANativeWindow.cpp +++ b/libs/nativewindow/ANativeWindow.cpp @@ -79,27 +79,6 @@ static int64_t query64(ANativeWindow* window, int what) { return res < 0 ? res : value; } -static bool isDataSpaceValid(ANativeWindow* window, int32_t dataSpace) { - bool supported = false; - switch (dataSpace) { - case HAL_DATASPACE_UNKNOWN: - case HAL_DATASPACE_V0_SRGB: - return true; - // These data space need wide gamut support. - case HAL_DATASPACE_V0_SCRGB_LINEAR: - case HAL_DATASPACE_V0_SCRGB: - case HAL_DATASPACE_DISPLAY_P3: - native_window_get_wide_color_support(window, &supported); - return supported; - // These data space need HDR support. - case HAL_DATASPACE_BT2020_PQ: - native_window_get_hdr_support(window, &supported); - return supported; - default: - return false; - } -} - /************************************************************************************************** * NDK **************************************************************************************************/ @@ -219,8 +198,7 @@ int32_t ANativeWindow_setBuffersDataSpace(ANativeWindow* window, int32_t dataSpa static_assert(static_cast(ADATASPACE_DEPTH) == static_cast(HAL_DATASPACE_DEPTH)); static_assert(static_cast(ADATASPACE_DYNAMIC_DEPTH) == static_cast(HAL_DATASPACE_DYNAMIC_DEPTH)); - if (!window || !query(window, NATIVE_WINDOW_IS_VALID) || - !isDataSpaceValid(window, dataSpace)) { + if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) { return -EINVAL; } return native_window_set_buffers_data_space(window, -- GitLab From 98db8e0c2301035c79a440b7cc5e18b318362319 Mon Sep 17 00:00:00 2001 From: Peiyong Lin Date: Wed, 5 Apr 2023 20:09:16 +0000 Subject: [PATCH 1098/1310] Do not allow wildcard matching in GL loader. Previously when ro.hardware.egl and ro.board.platform were not set, the loader would attempt to load the exact GLES drivers, and if it failed, it would attempt to use wildcard matching to find the GLES drivers. However, ro.hardware.egl must be set to point to the GLES drivers and hence wildcard matching should be disallowed. This patch makes sure the GL loader no longer uses that path if a device is launched in Android 14 and beyond. Bug: b/277100371 Test: boot Test: atest CtsAngleIntegrationHostTestCases Change-Id: Ie9221ba37947c7462de8321819543a4c67979f67 --- opengl/libs/EGL/Loader.cpp | 45 ++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp index 34b1251388..415e8eab70 100644 --- a/opengl/libs/EGL/Loader.cpp +++ b/opengl/libs/EGL/Loader.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -236,29 +237,22 @@ void* Loader::open(egl_connection_t* cnx) LOG_ALWAYS_FATAL("couldn't find an OpenGL ES implementation from %s", android::GraphicsEnv::getInstance().getDriverPath().c_str()); } - // Finally, try to load system driver. If ANGLE is the system driver - // (i.e. we are forcing the legacy system driver instead of ANGLE), use - // the driver suffix that was passed down from above. - if (shouldForceLegacyDriver) { - std::string suffix = android::GraphicsEnv::getInstance().getLegacySuffix(); - hnd = attempt_to_load_system_driver(cnx, suffix.c_str(), true); - } else { - // Start by searching for the library name appended by the system - // properties of the GLES userspace driver in both locations. - // i.e.: - // libGLES_${prop}.so, or: - // libEGL_${prop}.so, libGLESv1_CM_${prop}.so, libGLESv2_${prop}.so - for (auto key : HAL_SUBNAME_KEY_PROPERTIES) { - auto prop = base::GetProperty(key, ""); - if (prop.empty()) { - continue; - } - hnd = attempt_to_load_system_driver(cnx, prop.c_str(), true); - if (hnd) { - break; - } else if (strcmp(key, DRIVER_SUFFIX_PROPERTY) == 0) { - failToLoadFromDriverSuffixProperty = true; - } + // Finally, try to load system driver. + // Start by searching for the library name appended by the system + // properties of the GLES userspace driver in both locations. + // i.e.: + // libGLES_${prop}.so, or: + // libEGL_${prop}.so, libGLESv1_CM_${prop}.so, libGLESv2_${prop}.so + for (auto key : HAL_SUBNAME_KEY_PROPERTIES) { + auto prop = base::GetProperty(key, ""); + if (prop.empty()) { + continue; + } + hnd = attempt_to_load_system_driver(cnx, prop.c_str(), true); + if (hnd) { + break; + } else if (strcmp(key, DRIVER_SUFFIX_PROPERTY) == 0) { + failToLoadFromDriverSuffixProperty = true; } } } @@ -272,7 +266,10 @@ void* Loader::open(egl_connection_t* cnx) hnd = attempt_to_load_system_driver(cnx, nullptr, true); } - if (!hnd && !failToLoadFromDriverSuffixProperty) { + if (!hnd && !failToLoadFromDriverSuffixProperty && + property_get_int32("ro.vendor.api_level", 0) < __ANDROID_API_U__) { + // Still can't find the graphics drivers with the exact name. This time try to use wildcard + // matching if the device is launched before Android 14. hnd = attempt_to_load_system_driver(cnx, nullptr, false); } -- GitLab From 66ca6e39599d81e64b897e96b8e5999a2351e27e Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Wed, 5 Apr 2023 12:22:54 -0700 Subject: [PATCH 1099/1310] JPEG/R: stride support Bug: 264715926 Test: jpegr_test.cpp, manual test with camera/display app Change-Id: I92c1d19b8bd49060794b4a9427c67017aa6743a9 --- .../include/jpegrecoverymap/jpegr.h | 28 +++- .../include/jpegrecoverymap/jpegrerrorcode.h | 2 + libs/jpegrecoverymap/jpegr.cpp | 136 ++++++++++++------ libs/jpegrecoverymap/recoverymapmath.cpp | 29 ++-- .../data/raw_p010_image_with_stride.p010 | Bin 0 -> 2782080 bytes libs/jpegrecoverymap/tests/jpegr_test.cpp | 59 ++++++++ 6 files changed, 199 insertions(+), 55 deletions(-) create mode 100644 libs/jpegrecoverymap/tests/data/raw_p010_image_with_stride.p010 diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h index 1ab1dd7245..766d473520 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h @@ -59,12 +59,26 @@ struct jpegr_info_struct { struct jpegr_uncompressed_struct { // Pointer to the data location. void* data; - // Width of the recovery map or image in pixels. + // Width of the recovery map or the luma plane of the image in pixels. int width; - // Height of the recovery map or image in pixels. + // Height of the recovery map or the luma plane of the image in pixels. int height; // Color gamut. jpegr_color_gamut colorGamut; + + // Values below are optional + // Pointer to chroma data, if it's NULL, chroma plane is considered to be immediately + // following after the luma plane. + // Note: currently this feature is only supported for P010 image (HDR input). + void* chroma_data = nullptr; + // Strides of Y plane in number of pixels, using 0 to present uninitialized, must be + // larger than or equal to luma width. + // Note: currently this feature is only supported for P010 image (HDR input). + int luma_stride = 0; + // Strides of UV plane in number of pixels, using 0 to present uninitialized, must be + // larger than or equal to chroma width. + // Note: currently this feature is only supported for P010 image (HDR input). + int chroma_stride = 0; }; /* @@ -358,6 +372,16 @@ private: */ status_t toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest); + + /* + * This method will check the validity of the input images. + * + * @param uncompressed_p010_image uncompressed HDR image in P010 color format + * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format + * @return NO_ERROR if the input images are valid, error code is not valid. + */ + status_t areInputImagesValid(jr_uncompressed_ptr uncompressed_p010_image, + jr_uncompressed_ptr uncompressed_yuv_420_image); }; } // namespace android::jpegrecoverymap diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h index f73034338b..159aaa8107 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h @@ -46,6 +46,8 @@ enum { ERROR_JPEGR_CALCULATION_ERROR = JPEGR_RUNTIME_ERROR_BASE - 3, ERROR_JPEGR_METADATA_ERROR = JPEGR_RUNTIME_ERROR_BASE - 4, ERROR_JPEGR_TONEMAP_ERROR = JPEGR_RUNTIME_ERROR_BASE - 5, + + ERROR_JPEGR_UNSUPPORTED_FEATURE = -20000, }; } // namespace android::jpegrecoverymap diff --git a/libs/jpegrecoverymap/jpegr.cpp b/libs/jpegrecoverymap/jpegr.cpp index e395d51a26..646b79fd78 100644 --- a/libs/jpegrecoverymap/jpegr.cpp +++ b/libs/jpegrecoverymap/jpegr.cpp @@ -86,6 +86,54 @@ int GetCPUCoreCount() { return cpuCoreCount; } +status_t JpegR::areInputImagesValid(jr_uncompressed_ptr uncompressed_p010_image, + jr_uncompressed_ptr uncompressed_yuv_420_image) { + if (uncompressed_p010_image == nullptr) { + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + if (uncompressed_p010_image->width % kJpegBlock != 0 + || uncompressed_p010_image->height % 2 != 0) { + ALOGE("Image size can not be handled: %dx%d.", + uncompressed_p010_image->width, uncompressed_p010_image->height); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + if (uncompressed_p010_image->luma_stride != 0 + && uncompressed_p010_image->luma_stride < uncompressed_p010_image->width) { + ALOGE("Image stride can not be smaller than width, stride=%d, width=%d", + uncompressed_p010_image->luma_stride, uncompressed_p010_image->width); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + if (uncompressed_yuv_420_image == nullptr) { + return NO_ERROR; + } + + if (uncompressed_yuv_420_image->luma_stride != 0) { + ALOGE("Stride is not supported for YUV420 image"); + return ERROR_JPEGR_UNSUPPORTED_FEATURE; + } + + if (uncompressed_yuv_420_image->chroma_data != nullptr) { + ALOGE("Pointer to chroma plane is not supported for YUV420 image, chroma data must" + "be immediately after the luma data."); + return ERROR_JPEGR_UNSUPPORTED_FEATURE; + } + + if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width + || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) { + ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d", + uncompressed_p010_image->width, + uncompressed_p010_image->height, + uncompressed_yuv_420_image->width, + uncompressed_yuv_420_image->height); + return ERROR_JPEGR_RESOLUTION_MISMATCH; + } + + return NO_ERROR; +} + /* Encode API-0 */ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpegr_transfer_function hdr_tf, @@ -100,11 +148,9 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return ERROR_JPEGR_INVALID_INPUT_TYPE; } - if (uncompressed_p010_image->width % kJpegBlock != 0 - || uncompressed_p010_image->height % 2 != 0) { - ALOGE("Image size can not be handled: %dx%d", - uncompressed_p010_image->width, uncompressed_p010_image->height); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + if (status_t ret = areInputImagesValid( + uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr) != NO_ERROR) { + return ret; } jpegr_metadata_struct metadata; @@ -164,16 +210,9 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return ERROR_JPEGR_INVALID_INPUT_TYPE; } - if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width - || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) { - return ERROR_JPEGR_RESOLUTION_MISMATCH; - } - - if (uncompressed_p010_image->width % kJpegBlock != 0 - || uncompressed_p010_image->height % 2 != 0) { - ALOGE("Image size can not be handled: %dx%d", - uncompressed_p010_image->width, uncompressed_p010_image->height); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + if (status_t ret = areInputImagesValid( + uncompressed_p010_image, uncompressed_yuv_420_image) != NO_ERROR) { + return ret; } jpegr_metadata_struct metadata; @@ -223,16 +262,9 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return ERROR_JPEGR_INVALID_NULL_PTR; } - if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width - || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) { - return ERROR_JPEGR_RESOLUTION_MISMATCH; - } - - if (uncompressed_p010_image->width % kJpegBlock != 0 - || uncompressed_p010_image->height % 2 != 0) { - ALOGE("Image size can not be handled: %dx%d", - uncompressed_p010_image->width, uncompressed_p010_image->height); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + if (status_t ret = areInputImagesValid( + uncompressed_p010_image, uncompressed_yuv_420_image) != NO_ERROR) { + return ret; } jpegr_metadata_struct metadata; @@ -266,11 +298,9 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return ERROR_JPEGR_INVALID_NULL_PTR; } - if (uncompressed_p010_image->width % kJpegBlock != 0 - || uncompressed_p010_image->height % 2 != 0) { - ALOGE("Image size can not be handled: %dx%d", - uncompressed_p010_image->width, uncompressed_p010_image->height); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + if (status_t ret = areInputImagesValid( + uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr) != NO_ERROR) { + return ret; } JpegDecoderHelper jpeg_decoder; @@ -993,25 +1023,43 @@ status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) { return ERROR_JPEGR_INVALID_NULL_PTR; } + size_t src_luma_stride = src->luma_stride; + size_t src_chroma_stride = src->chroma_stride; + uint16_t* src_luma_data = reinterpret_cast(src->data); + uint16_t* src_chroma_data = reinterpret_cast(src->chroma_data); + + if (src_chroma_data == nullptr) { + src_chroma_data = &reinterpret_cast(src->data)[src->luma_stride * src->height]; + } + if (src_luma_stride == 0) { + src_luma_stride = src->width; + } + if (src_chroma_stride == 0) { + src_chroma_stride = src_luma_stride; + } + dest->width = src->width; dest->height = src->height; - size_t pixel_count = src->width * src->height; + size_t dest_luma_pixel_count = dest->width * dest->height; + for (size_t y = 0; y < src->height; ++y) { for (size_t x = 0; x < src->width; ++x) { - size_t pixel_y_idx = x + y * src->width; - size_t pixel_uv_idx = x / 2 + (y / 2) * (src->width / 2); - - uint16_t y_uint = reinterpret_cast(src->data)[pixel_y_idx] - >> 6; - uint16_t u_uint = reinterpret_cast(src->data)[pixel_count + pixel_uv_idx * 2] - >> 6; - uint16_t v_uint = reinterpret_cast(src->data)[pixel_count + pixel_uv_idx * 2 + 1] - >> 6; - - uint8_t* y = &reinterpret_cast(dest->data)[pixel_y_idx]; - uint8_t* u = &reinterpret_cast(dest->data)[pixel_count + pixel_uv_idx]; - uint8_t* v = &reinterpret_cast(dest->data)[pixel_count * 5 / 4 + pixel_uv_idx]; + size_t src_y_idx = y * src_luma_stride + x; + size_t src_u_idx = (y >> 1) * src_chroma_stride + (x & ~0x1); + size_t src_v_idx = src_u_idx + 1; + + uint16_t y_uint = src_luma_data[src_y_idx] >> 6; + uint16_t u_uint = src_chroma_data[src_u_idx] >> 6; + uint16_t v_uint = src_chroma_data[src_v_idx] >> 6; + + size_t dest_y_idx = x + y * dest->width; + size_t dest_uv_idx = x / 2 + (y / 2) * (dest->width / 2); + + uint8_t* y = &reinterpret_cast(dest->data)[dest_y_idx]; + uint8_t* u = &reinterpret_cast(dest->data)[dest_luma_pixel_count + dest_uv_idx]; + uint8_t* v = &reinterpret_cast( + dest->data)[dest_luma_pixel_count * 5 / 4 + dest_uv_idx]; *y = static_cast((y_uint >> 2) & 0xff); *u = static_cast((u_uint >> 2) & 0xff); diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp index 2cffde3c54..7c0c15ae35 100644 --- a/libs/jpegrecoverymap/recoverymapmath.cpp +++ b/libs/jpegrecoverymap/recoverymapmath.cpp @@ -493,17 +493,28 @@ Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) { } Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) { - size_t pixel_count = image->width * image->height; + size_t luma_stride = image->luma_stride; + size_t chroma_stride = image->chroma_stride; + uint16_t* luma_data = reinterpret_cast(image->data); + uint16_t* chroma_data = reinterpret_cast(image->chroma_data); - size_t pixel_y_idx = x + y * image->width; - size_t pixel_uv_idx = x / 2 + (y / 2) * (image->width / 2); + if (luma_stride == 0) { + luma_stride = image->width; + } + if (chroma_stride == 0) { + chroma_stride = luma_stride; + } + if (chroma_data == nullptr) { + chroma_data = &reinterpret_cast(image->data)[image->luma_stride * image->height]; + } + + size_t pixel_y_idx = y * luma_stride + x; + size_t pixel_u_idx = (y >> 1) * chroma_stride + (x & ~0x1); + size_t pixel_v_idx = pixel_u_idx + 1; - uint16_t y_uint = reinterpret_cast(image->data)[pixel_y_idx] - >> 6; - uint16_t u_uint = reinterpret_cast(image->data)[pixel_count + pixel_uv_idx * 2] - >> 6; - uint16_t v_uint = reinterpret_cast(image->data)[pixel_count + pixel_uv_idx * 2 + 1] - >> 6; + uint16_t y_uint = luma_data[pixel_y_idx] >> 6; + uint16_t u_uint = chroma_data[pixel_u_idx] >> 6; + uint16_t v_uint = chroma_data[pixel_v_idx] >> 6; // Conversions include taking narrow-range into account. return {{{ (static_cast(y_uint) - 64.0f) / 876.0f, diff --git a/libs/jpegrecoverymap/tests/data/raw_p010_image_with_stride.p010 b/libs/jpegrecoverymap/tests/data/raw_p010_image_with_stride.p010 new file mode 100644 index 0000000000000000000000000000000000000000..e7a5dc84dc7ab98f35303753e7e7191a8d158993 GIT binary patch literal 2782080 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 literal 0 HcmV?d00001 diff --git a/libs/jpegrecoverymap/tests/jpegr_test.cpp b/libs/jpegrecoverymap/tests/jpegr_test.cpp index 7c669aba0a..229d7dc93b 100644 --- a/libs/jpegrecoverymap/tests/jpegr_test.cpp +++ b/libs/jpegrecoverymap/tests/jpegr_test.cpp @@ -24,10 +24,12 @@ #include #define RAW_P010_IMAGE "/sdcard/Documents/raw_p010_image.p010" +#define RAW_P010_IMAGE_WITH_STRIDE "/sdcard/Documents/raw_p010_image_with_stride.p010" #define RAW_YUV420_IMAGE "/sdcard/Documents/raw_yuv420_image.yuv420" #define JPEG_IMAGE "/sdcard/Documents/jpeg_image.jpg" #define TEST_IMAGE_WIDTH 1280 #define TEST_IMAGE_HEIGHT 720 +#define TEST_IMAGE_STRIDE 1288 #define DEFAULT_JPEG_QUALITY 90 #define SAVE_ENCODING_RESULT true @@ -97,6 +99,7 @@ protected: virtual void TearDown(); struct jpegr_uncompressed_struct mRawP010Image; + struct jpegr_uncompressed_struct mRawP010ImageWithStride; struct jpegr_uncompressed_struct mRawYuv420Image; struct jpegr_compressed_struct mJpegImage; }; @@ -107,6 +110,7 @@ JpegRTest::~JpegRTest() {} void JpegRTest::SetUp() {} void JpegRTest::TearDown() { free(mRawP010Image.data); + free(mRawP010ImageWithStride.data); free(mRawYuv420Image.data); free(mJpegImage.data); } @@ -249,6 +253,61 @@ TEST_F(JpegRTest, encodeFromP010ThenDecode) { free(decodedJpegR.data); } +/* Test Encode API-0 (with stride) and decode */ +TEST_F(JpegRTest, encodeFromP010WithStrideThenDecode) { + int ret; + + // Load input files. + if (!loadFile(RAW_P010_IMAGE_WITH_STRIDE, mRawP010ImageWithStride.data, nullptr)) { + FAIL() << "Load file " << RAW_P010_IMAGE_WITH_STRIDE << " failed"; + } + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100; + + JpegR jpegRCodec; + + jpegr_compressed_struct jpegR; + jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + ret = jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + if (SAVE_ENCODING_RESULT) { + // Output image data to file + std::string filePath = "/sdcard/Documents/encoded_from_p010_input.jpgr"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)jpegR.data, jpegR.length); + } + + jpegr_uncompressed_struct decodedJpegR; + int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8; + decodedJpegR.data = malloc(decodedJpegRSize); + ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + if (SAVE_DECODING_RESULT) { + // Output image data to file + std::string filePath = "/sdcard/Documents/decoded_from_p010_input.rgb"; + std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); + if (!imageFile.is_open()) { + ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); + } + imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize); + } + + free(jpegR.data); + free(decodedJpegR.data); +} + /* Test Encode API-1 and decode */ TEST_F(JpegRTest, encodeFromRawHdrAndSdrThenDecode) { int ret; -- GitLab From d649463aa27ae1aea7101afc4684cc9422d4808a Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Wed, 5 Apr 2023 11:57:38 -0400 Subject: [PATCH 1100/1310] Only call onNewVsyncSchedule if the pacesetter changes When re-calculating the pacesetter display, we may end up with the same display, and therefore the same VsyncSchedule. In that case, we don't need to apply the new one. Bug: 276367387 Test: atest libsurfaceflinger_unittest:SchedulerTest Change-Id: I76750ffdba6c3d790a98b65aabbc8b13eab3b4ae --- .../surfaceflinger/Scheduler/Scheduler.cpp | 34 +++++++++++++------ services/surfaceflinger/Scheduler/Scheduler.h | 4 ++- .../tests/unittests/SchedulerTest.cpp | 19 +++++++++++ 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 3e12db61e7..8ddcfa1ee6 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -130,7 +130,7 @@ void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId, pacesetterVsyncSchedule = promotePacesetterDisplayLocked(); } - applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); + applyNewVsyncScheduleIfNonNull(std::move(pacesetterVsyncSchedule)); } void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { @@ -149,7 +149,7 @@ void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { pacesetterVsyncSchedule = promotePacesetterDisplayLocked(); } - applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); + applyNewVsyncScheduleIfNonNull(std::move(pacesetterVsyncSchedule)); } void Scheduler::run() { @@ -693,16 +693,17 @@ void Scheduler::promotePacesetterDisplay(std::optional pacese pacesetterVsyncSchedule = promotePacesetterDisplayLocked(pacesetterIdOpt); } - applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); + applyNewVsyncScheduleIfNonNull(std::move(pacesetterVsyncSchedule)); } std::shared_ptr Scheduler::promotePacesetterDisplayLocked( std::optional pacesetterIdOpt) { // TODO(b/241286431): Choose the pacesetter display. + const auto oldPacesetterDisplayIdOpt = mPacesetterDisplayId; mPacesetterDisplayId = pacesetterIdOpt.value_or(mRefreshRateSelectors.begin()->first); ALOGI("Display %s is the pacesetter", to_string(*mPacesetterDisplayId).c_str()); - auto vsyncSchedule = getVsyncScheduleLocked(*mPacesetterDisplayId); + auto newVsyncSchedule = getVsyncScheduleLocked(*mPacesetterDisplayId); if (const auto pacesetterPtr = pacesetterSelectorPtrLocked()) { pacesetterPtr->setIdleTimerCallbacks( {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); }, @@ -713,15 +714,28 @@ std::shared_ptr Scheduler::promotePacesetterDisplayLocked( pacesetterPtr->startIdleTimer(); + // Track the new period, which may have changed due to switching to a + // new pacesetter or due to a hotplug event. In the former case, this + // is important so that VSYNC modulation does not get stuck in the + // initiated state if a transition started on the old pacesetter. const Fps refreshRate = pacesetterPtr->getActiveMode().modePtr->getFps(); - vsyncSchedule->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod(), - true /* force */); + newVsyncSchedule->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod(), + true /* force */); } - return vsyncSchedule; + if (oldPacesetterDisplayIdOpt == mPacesetterDisplayId) { + return nullptr; + } + return newVsyncSchedule; } -void Scheduler::applyNewVsyncSchedule(std::shared_ptr vsyncSchedule) { - onNewVsyncSchedule(vsyncSchedule->getDispatch()); +void Scheduler::applyNewVsyncScheduleIfNonNull( + std::shared_ptr pacesetterSchedulePtr) { + if (!pacesetterSchedulePtr) { + // The pacesetter has not changed, so there is no new VsyncSchedule to + // apply. + return; + } + onNewVsyncSchedule(pacesetterSchedulePtr->getDispatch()); std::vector threads; { std::lock_guard lock(mConnectionsLock); @@ -731,7 +745,7 @@ void Scheduler::applyNewVsyncSchedule(std::shared_ptr vsyncSchedu } } for (auto* thread : threads) { - thread->onNewVsyncSchedule(vsyncSchedule); + thread->onNewVsyncSchedule(pacesetterSchedulePtr); } } diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 3423652163..720a1cbba2 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -329,10 +329,12 @@ private: // MessageQueue and EventThread need to use the new pacesetter's // VsyncSchedule, and this must happen while mDisplayLock is *not* locked, // or else we may deadlock with EventThread. + // Returns the new pacesetter's VsyncSchedule, or null if the pacesetter is + // unchanged. std::shared_ptr promotePacesetterDisplayLocked( std::optional pacesetterIdOpt = std::nullopt) REQUIRES(kMainThreadContext, mDisplayLock); - void applyNewVsyncSchedule(std::shared_ptr) EXCLUDES(mDisplayLock); + void applyNewVsyncScheduleIfNonNull(std::shared_ptr) EXCLUDES(mDisplayLock); // Blocks until the pacesetter's idle timer thread exits. `mDisplayLock` must not be locked by // the caller on the main thread to avoid deadlock, since the timer thread locks it before exit. diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index dc76b4c90f..0c43831455 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -384,4 +384,23 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { } } +TEST_F(SchedulerTest, changingPacesetterChangesVsyncSchedule) { + // Add a second display so we can change the pacesetter. + mScheduler->registerDisplay(kDisplayId2, + std::make_shared(kDisplay2Modes, + kDisplay2Mode60->getId())); + // Ensure that the pacesetter is the one we expect. + mScheduler->setPacesetterDisplay(kDisplayId1); + + // Switching to the other will call onNewVsyncSchedule. + EXPECT_CALL(*mEventThread, onNewVsyncSchedule(mScheduler->getVsyncSchedule(kDisplayId2))) + .Times(1); + mScheduler->setPacesetterDisplay(kDisplayId2); +} + +TEST_F(SchedulerTest, promotingSamePacesetterDoesNotChangeVsyncSchedule) { + EXPECT_CALL(*mEventThread, onNewVsyncSchedule(_)).Times(0); + mScheduler->setPacesetterDisplay(kDisplayId1); +} + } // namespace android::scheduler -- GitLab From 632212179b4e7b2d1533379b97ed0538cb161153 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Thu, 6 Apr 2023 15:17:37 -0700 Subject: [PATCH 1101/1310] [sf] protect against out of order transactions If the client submits transactions out of order, we could end up in a situation where a frame barrier will never be satisfied and bring down the client. Fix this by being more resiliant to out of order transactions. Bug: 272189296 Test: repro steps from bug Change-Id: I781b7751bdd6259fc164692248734c0cb268c238 --- .../FrontEnd/RequestedLayerState.cpp | 15 +++++++++------ .../surfaceflinger/FrontEnd/RequestedLayerState.h | 2 ++ services/surfaceflinger/Layer.cpp | 9 +++++++++ services/surfaceflinger/Layer.h | 7 +++++++ services/surfaceflinger/SurfaceFlinger.cpp | 4 ++-- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index 1f670c81df..4dcdd964b3 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -158,12 +158,15 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta RequestedLayerState::Changes::VisibleRegion | RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Input; } - if (clientState.what & layer_state_t::eBufferChanged) { - changes |= RequestedLayerState::Changes::Buffer; - } - if (clientState.what & layer_state_t::eSidebandStreamChanged) { - changes |= RequestedLayerState::Changes::SidebandStream; - } + } + if (clientState.what & layer_state_t::eBufferChanged) { + barrierProducerId = std::max(bufferData->producerId, barrierProducerId); + barrierFrameNumber = std::max(bufferData->frameNumber, barrierFrameNumber); + // TODO(b/277265947) log and flush transaction trace when we detect out of order updates + changes |= RequestedLayerState::Changes::Buffer; + } + if (clientState.what & layer_state_t::eSidebandStreamChanged) { + changes |= RequestedLayerState::Changes::SidebandStream; } if (what & (layer_state_t::eAlphaChanged)) { if (oldAlpha == 0 || color.a == 0) { diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h index 216e95f414..f15f023c43 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h @@ -111,6 +111,8 @@ struct RequestedLayerState : layer_state_t { ui::LayerStack layerStackToMirror = ui::INVALID_LAYER_STACK; uint32_t touchCropId = UNASSIGNED_LAYER_ID; uint32_t bgColorLayerId = UNASSIGNED_LAYER_ID; + uint64_t barrierFrameNumber = 0; + uint32_t barrierProducerId = 0; // book keeping states bool handleAlive = true; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 755e58521d..b919cc2716 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -166,6 +166,9 @@ Layer::Layer(const LayerCreationArgs& args) mDrawingState.sequence = 0; mDrawingState.transform.set(0, 0); mDrawingState.frameNumber = 0; + mDrawingState.barrierFrameNumber = 0; + mDrawingState.producerId = 0; + mDrawingState.barrierProducerId = 0; mDrawingState.bufferTransform = 0; mDrawingState.transformToDisplayInverse = false; mDrawingState.crop.makeInvalid(); @@ -3064,7 +3067,13 @@ bool Layer::setBuffer(std::shared_ptr& buffer, } mDrawingState.producerId = bufferData.producerId; + mDrawingState.barrierProducerId = + std::max(mDrawingState.producerId, mDrawingState.barrierProducerId); mDrawingState.frameNumber = frameNumber; + mDrawingState.barrierFrameNumber = + std::max(mDrawingState.frameNumber, mDrawingState.barrierFrameNumber); + + // TODO(b/277265947) log and flush transaction trace when we detect out of order updates mDrawingState.releaseBufferListener = bufferData.releaseBufferListener; mDrawingState.buffer = std::move(buffer); mDrawingState.clientCacheId = bufferData.cachedBuffer; diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 1af648a20f..248fcec102 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -140,9 +140,16 @@ public: bool dataspaceRequested; uint64_t frameNumber; + // high watermark framenumber to use to check for barriers to protect ourselves + // from out of order transactions + uint64_t barrierFrameNumber; ui::Transform transform; uint32_t producerId = 0; + // high watermark producerId to use to check for barriers to protect ourselves + // from out of order transactions + uint32_t barrierProducerId = 0; + uint32_t bufferTransform; bool transformToDisplayInverse; Region transparentRegionHint; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index a27108378a..7ace58c859 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4232,7 +4232,7 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC // The current producerId is already a newer producer than the buffer that has a // barrier. This means the incoming buffer is older and we can release it here. We // don't wait on the barrier since we know that's stale information. - if (layer->getDrawingState().producerId > s.bufferData->producerId) { + if (layer->getDrawingState().barrierProducerId > s.bufferData->producerId) { layer->callReleaseBufferCallback(s.bufferData->releaseBufferListener, externalTexture->getBuffer(), s.bufferData->frameNumber, @@ -4243,7 +4243,7 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL; } - if (layer->getDrawingState().frameNumber < s.bufferData->barrierFrameNumber) { + if (layer->getDrawingState().barrierFrameNumber < s.bufferData->barrierFrameNumber) { const bool willApplyBarrierFrame = flushState.bufferLayersReadyToPresent.contains(s.surface.get()) && ((flushState.bufferLayersReadyToPresent.get(s.surface.get()) >= -- GitLab From f7a9e1726962018b51bfe9bc66590c3697bafcb7 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Thu, 6 Apr 2023 18:23:47 -0700 Subject: [PATCH 1102/1310] JPEG/R Optimization: remove repeating log2() calculation Bug: 264715926 Test: jpegr_test.cpp recoverymapmath_test.cpp Change-Id: If135b1003b7835c9c59418f61842c26724eb6e80 --- .../include/jpegrecoverymap/recoverymapmath.h | 2 ++ libs/jpegrecoverymap/jpegr.cpp | 7 +++++-- libs/jpegrecoverymap/recoverymapmath.cpp | 10 ++++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h index 67d2a6a0be..bd5ca8b9f0 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h @@ -361,6 +361,8 @@ ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gam * luminances in linear space, and the hdr ratio to encode against. */ uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata); +uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata, + float log2MinContentBoost, float log2MaxContentBoost); /* * Calculates the linear luminance in nits after applying the given recovery diff --git a/libs/jpegrecoverymap/jpegr.cpp b/libs/jpegrecoverymap/jpegr.cpp index e395d51a26..559ca3564a 100644 --- a/libs/jpegrecoverymap/jpegr.cpp +++ b/libs/jpegrecoverymap/jpegr.cpp @@ -586,6 +586,8 @@ status_t JpegR::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ima metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits; metadata->minContentBoost = 1.0f; + float log2MinBoost = log2(metadata->minContentBoost); + float log2MaxBoost = log2(metadata->maxContentBoost); ColorTransformFn hdrGamutConversionFn = getHdrConversionFn( uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut); @@ -613,7 +615,8 @@ status_t JpegR::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ima std::function generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image, metadata, dest, hdrInvOetf, hdrGamutConversionFn, - luminanceFn, hdr_white_nits, &jobQueue]() -> void { + luminanceFn, hdr_white_nits, log2MinBoost, log2MaxBoost, + &jobQueue]() -> void { size_t rowStart, rowEnd; size_t dest_map_width = uncompressed_yuv_420_image->width / kMapDimensionScaleFactor; size_t dest_map_stride = dest->width; @@ -638,7 +641,7 @@ status_t JpegR::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ima size_t pixel_idx = x + y * dest_map_stride; reinterpret_cast(dest->data)[pixel_idx] = - encodeRecovery(sdr_y_nits, hdr_y_nits, metadata); + encodeRecovery(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost); } } } diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp index 2cffde3c54..8808b55b0c 100644 --- a/libs/jpegrecoverymap/recoverymapmath.cpp +++ b/libs/jpegrecoverymap/recoverymapmath.cpp @@ -443,6 +443,12 @@ ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gam //////////////////////////////////////////////////////////////////////////////// // Recovery map calculations uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata) { + return encodeRecovery(y_sdr, y_hdr, metadata, + log2(metadata->minContentBoost), log2(metadata->maxContentBoost)); +} + +uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata, + float log2MinContentBoost, float log2MaxContentBoost) { float gain = 1.0f; if (y_sdr > 0.0f) { gain = y_hdr / y_sdr; @@ -451,8 +457,8 @@ uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata) { if (gain < metadata->minContentBoost) gain = metadata->minContentBoost; if (gain > metadata->maxContentBoost) gain = metadata->maxContentBoost; - return static_cast((log2(gain) - log2(metadata->minContentBoost)) - / (log2(metadata->maxContentBoost) - log2(metadata->minContentBoost)) + return static_cast((log2(gain) - log2MinContentBoost) + / (log2MaxContentBoost - log2MinContentBoost) * 255.0f); } -- GitLab From f5272953652b8cee06b1376a7324dd9741160644 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Thu, 6 Apr 2023 14:55:22 -0400 Subject: [PATCH 1103/1310] Temporary workaround for getDisplayStats Prior to Icdb80253436b4d0034fc20fcae8583efb7c30292, there was only one VsyncSchedule, so the display parameter was not interesting. Now that there are multiple VsyncSchedules, we should return the VsyncSchedule corresponding to the passed in display. But current callers just pass a null binder token. Use the pacesetter's VsyncSchedule for now. Most of the time, for example on devices with only one display (or one active display), this is correct. For a device with more than one active display, this may not be correct, so a follow-on change is necessary. Bug: 275691508 Bug: 275691150 Test: manual Change-Id: I01ed2978b04a7707a706050902d3041ec9ab4eca --- services/surfaceflinger/SurfaceFlinger.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 8ce479a213..8c77253fc4 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1153,7 +1153,10 @@ status_t SurfaceFlinger::getDisplayStats(const sp& displayToken, displayIdOpt = getPhysicalDisplayIdLocked(displayToken); } - if (!displayIdOpt) { + // TODO (b/277364366): Clients should be updated to pass in the display they + // want, rather than us picking an arbitrary one (the pacesetter, in this + // case). + if (displayToken && !displayIdOpt) { ALOGE("%s: Invalid physical display token %p", __func__, displayToken.get()); return NAME_NOT_FOUND; } -- GitLab From e0320e7ccb9f8ad8017eeccdb6a45b01516cb18a Mon Sep 17 00:00:00 2001 From: Wenxin Feng Date: Sat, 8 Apr 2023 19:26:02 -0700 Subject: [PATCH 1104/1310] Update the touchpad sensitivity levels and default sensitivity. We provide a new sensitivity array with a lower default value in the middle. The UX results suggest the touchpad sensitivity setting is a little high. Bug: 276962681 Test: manually Change-Id: I37e8c690237b4854409e0e8eac7096d709115428 --- services/inputflinger/reader/mapper/TouchpadInputMapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 33f368e9eb..8838cc76d6 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -57,7 +57,7 @@ const std::vector segments = { {std::numeric_limits::infinity(), 15.04, -857.758}, }; -const std::vector sensitivityFactors = {1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 18, 20}; +const std::vector sensitivityFactors = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 18}; std::vector createAccelerationCurveForSensitivity(int32_t sensitivity, size_t propertySize) { -- GitLab From 63db24e62af7cd27b5ab49d735b2fddae3f2fa5c Mon Sep 17 00:00:00 2001 From: ramindani Date: Mon, 3 Apr 2023 10:56:05 -0700 Subject: [PATCH 1105/1310] SF: Clear layer history when inconclusive to frequent layer transition BUG: 265561529 Test: atest LayerHistoryTest Benchmark shows improvement with the changes https://android-build.googleplex.com/builds/abtd/run/L82600000959656128 Change-Id: I152aa97f00592960411133de150990747ca8b31d --- .../surfaceflinger/Scheduler/LayerInfo.cpp | 32 +++++---- services/surfaceflinger/Scheduler/LayerInfo.h | 11 ++- .../tests/unittests/LayerHistoryTest.cpp | 72 ++++++++++++++++++- 3 files changed, 100 insertions(+), 15 deletions(-) diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp index 5a90d5866e..bae3739501 100644 --- a/services/surfaceflinger/Scheduler/LayerInfo.cpp +++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp @@ -78,17 +78,16 @@ bool LayerInfo::isFrameTimeValid(const FrameTimeData& frameTime) const { .count(); } -bool LayerInfo::isFrequent(nsecs_t now) const { - using fps_approx_ops::operator>=; +LayerInfo::Frequent LayerInfo::isFrequent(nsecs_t now) const { // If we know nothing about this layer (e.g. after touch event), // we consider it as frequent as it might be the start of an animation. if (mFrameTimes.size() < kFrequentLayerWindowSize) { - return true; + return {/* isFrequent */ true, /* clearHistory */ false, /* isConclusive */ true}; } // Non-active layers are also infrequent if (mLastUpdatedTime < getActiveLayerThreshold(now)) { - return false; + return {/* isFrequent */ false, /* clearHistory */ false, /* isConclusive */ true}; } // We check whether we can classify this layer as frequent or infrequent: @@ -111,12 +110,20 @@ bool LayerInfo::isFrequent(nsecs_t now) const { } if (isFrequent || isInfrequent) { - return isFrequent; + // If the layer was previously inconclusive, we clear + // the history as indeterminate layers changed to frequent, + // and we should not look at the stale data. + return {isFrequent, isFrequent && !mIsFrequencyConclusive, /* isConclusive */ true}; } // If we can't determine whether the layer is frequent or not, we return - // the last known classification. - return !mLastRefreshRate.infrequent; + // the last known classification and mark the layer frequency as inconclusive. + isFrequent = !mLastRefreshRate.infrequent; + + // If the layer was previously tagged as animating, we clear + // the history as it is likely the layer just changed its behavior, + // and we should not look at stale data. + return {isFrequent, isFrequent && mLastRefreshRate.animating, /* isConclusive */ false}; } Fps LayerInfo::getFps(nsecs_t now) const { @@ -273,19 +280,18 @@ LayerInfo::LayerVote LayerInfo::getRefreshRateVote(const RefreshRateSelector& se return {LayerHistory::LayerVoteType::Max, Fps()}; } - if (!isFrequent(now)) { + const LayerInfo::Frequent frequent = isFrequent(now); + mIsFrequencyConclusive = frequent.isConclusive; + if (!frequent.isFrequent) { ATRACE_FORMAT_INSTANT("infrequent"); ALOGV("%s is infrequent", mName.c_str()); mLastRefreshRate.infrequent = true; - // Infrequent layers vote for mininal refresh rate for + // Infrequent layers vote for minimal refresh rate for // battery saving purposes and also to prevent b/135718869. return {LayerHistory::LayerVoteType::Min, Fps()}; } - // If the layer was previously tagged as animating or infrequent, we clear - // the history as it is likely the layer just changed its behavior - // and we should not look at stale data - if (mLastRefreshRate.animating || mLastRefreshRate.infrequent) { + if (frequent.clearHistory) { clearHistory(now); } diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h index a3523ac25e..c5a60573f5 100644 --- a/services/surfaceflinger/Scheduler/LayerInfo.h +++ b/services/surfaceflinger/Scheduler/LayerInfo.h @@ -181,6 +181,7 @@ public: mFrameTimeValidSince = std::chrono::time_point(timePoint); mLastRefreshRate = {}; mRefreshRateHistory.clear(); + mIsFrequencyConclusive = true; } void clearHistory(nsecs_t now) { @@ -251,7 +252,15 @@ private: static constexpr float MARGIN_CONSISTENT_FPS = 1.0; }; - bool isFrequent(nsecs_t now) const; + // Represents whether we were able to determine either layer is frequent or infrequent + bool mIsFrequencyConclusive = true; + struct Frequent { + bool isFrequent; + bool clearHistory; + // Represents whether we were able to determine isFrequent conclusively + bool isConclusive; + }; + Frequent isFrequent(nsecs_t now) const; bool isAnimating(nsecs_t now) const; bool hasEnoughDataForHeuristic() const; std::optional calculateRefreshRateIfPossible(const RefreshRateSelector&, nsecs_t now); diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp index b7672768b4..85d86a7acc 100644 --- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp @@ -84,7 +84,7 @@ protected: auto frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS { const auto& infos = history().mActiveLayerInfos; return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) { - return pair.second.second->isFrequent(now); + return pair.second.second->isFrequent(now).isFrequent; }); } @@ -95,6 +95,13 @@ protected: }); } + auto clearLayerHistoryCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS { + const auto& infos = history().mActiveLayerInfos; + return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) { + return pair.second.second->isFrequent(now).clearHistory; + }); + } + void setDefaultLayerVote(Layer* layer, LayerHistory::LayerVoteType vote) NO_THREAD_SAFETY_ANALYSIS { auto [found, layerPair] = history().findLayer(layer->getSequence()); @@ -764,6 +771,7 @@ TEST_F(LayerHistoryTest, frequentLayerBecomingInfrequentAndBack) { time += std::chrono::nanoseconds(3s).count(); history().record(layer->getSequence(), layer->getLayerProps(), time, time, LayerHistory::LayerUpdateType::Buffer); + EXPECT_EQ(0, clearLayerHistoryCount(time)); ASSERT_EQ(1, summarizeLayerHistory(time).size()); EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summarizeLayerHistory(time)[0].vote); EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate); @@ -778,6 +786,7 @@ TEST_F(LayerHistoryTest, frequentLayerBecomingInfrequentAndBack) { time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count(); history().record(layer->getSequence(), layer->getLayerProps(), time, time, LayerHistory::LayerUpdateType::Buffer); + EXPECT_EQ(0, clearLayerHistoryCount(time)); ASSERT_EQ(1, summarizeLayerHistory(time).size()); EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote); EXPECT_EQ(1, activeLayerCount()); @@ -787,6 +796,7 @@ TEST_F(LayerHistoryTest, frequentLayerBecomingInfrequentAndBack) { // posting another buffer should keep the layer infrequent history().record(layer->getSequence(), layer->getLayerProps(), time, time, LayerHistory::LayerUpdateType::Buffer); + EXPECT_EQ(0, clearLayerHistoryCount(time)); ASSERT_EQ(1, summarizeLayerHistory(time).size()); EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote); EXPECT_EQ(1, activeLayerCount()); @@ -798,6 +808,7 @@ TEST_F(LayerHistoryTest, frequentLayerBecomingInfrequentAndBack) { LayerHistory::LayerUpdateType::Buffer); history().record(layer->getSequence(), layer->getLayerProps(), time, time, LayerHistory::LayerUpdateType::Buffer); + EXPECT_EQ(1, clearLayerHistoryCount(time)); ASSERT_EQ(1, summarizeLayerHistory(time).size()); EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote); EXPECT_EQ(1, activeLayerCount()); @@ -808,6 +819,7 @@ TEST_F(LayerHistoryTest, frequentLayerBecomingInfrequentAndBack) { time += std::chrono::nanoseconds(3s).count(); history().record(layer->getSequence(), layer->getLayerProps(), time, time, LayerHistory::LayerUpdateType::Buffer); + EXPECT_EQ(0, clearLayerHistoryCount(time)); ASSERT_EQ(1, summarizeLayerHistory(time).size()); EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote); EXPECT_EQ(1, activeLayerCount()); @@ -818,6 +830,64 @@ TEST_F(LayerHistoryTest, frequentLayerBecomingInfrequentAndBack) { time += (60_Hz).getPeriodNsecs(); history().record(layer->getSequence(), layer->getLayerProps(), time, time, LayerHistory::LayerUpdateType::Buffer); + EXPECT_EQ(0, clearLayerHistoryCount(time)); + ASSERT_EQ(1, summarizeLayerHistory(time).size()); + EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote); + EXPECT_EQ(1, activeLayerCount()); + EXPECT_EQ(1, frequentLayerCount(time)); + EXPECT_EQ(0, animatingLayerCount(time)); +} + +TEST_F(LayerHistoryTest, inconclusiveLayerBecomingFrequent) { + auto layer = createLayer(); + + EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true)); + EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate())); + + nsecs_t time = systemTime(); + + EXPECT_EQ(1, layerCount()); + EXPECT_EQ(0, activeLayerCount()); + EXPECT_EQ(0, frequentLayerCount(time)); + EXPECT_EQ(0, animatingLayerCount(time)); + + // Fill up the window with frequent updates + for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE; i++) { + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); + time += (60_Hz).getPeriodNsecs(); + + EXPECT_EQ(1, layerCount()); + ASSERT_EQ(1, summarizeLayerHistory(time).size()); + EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote); + EXPECT_EQ(1, activeLayerCount()); + EXPECT_EQ(1, frequentLayerCount(time)); + } + + // posting infrequent buffers after long inactivity should make the layer + // inconclusive but frequent. + time += std::chrono::nanoseconds(3s).count(); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); + time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count(); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); + EXPECT_EQ(0, clearLayerHistoryCount(time)); + ASSERT_EQ(1, summarizeLayerHistory(time).size()); + EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summarizeLayerHistory(time)[0].vote); + EXPECT_EQ(1, activeLayerCount()); + EXPECT_EQ(1, frequentLayerCount(time)); + EXPECT_EQ(0, animatingLayerCount(time)); + + // posting more buffers should make the layer frequent and switch the refresh rate to max + // by clearing the history + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); + EXPECT_EQ(1, clearLayerHistoryCount(time)); ASSERT_EQ(1, summarizeLayerHistory(time).size()); EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote); EXPECT_EQ(1, activeLayerCount()); -- GitLab From a4c59bd8b5a36821303889f03e2a39a60cb87b4b Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 10 Apr 2023 20:54:04 +0000 Subject: [PATCH 1106/1310] Fix InputSurfacesTest.input_respects_scaled_touchable_region_overflow When there is an overflow while the touchable region is being transformed, we skip the touchable region calculation, which results in the touchable region being empty. This means the layer that was misconfigured will not be touchable, so the touch will go to the background surface. Bug: 240566619 Bug: 240437119 Test: atest libgui_test:InputSurfacesTest Change-Id: I4ec7f2b7544297aa25ee216cbbe1ddf1cd32f9f1 --- libs/gui/tests/EndToEndNativeInputTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp index 9e8c65c678..986add913b 100644 --- a/libs/gui/tests/EndToEndNativeInputTest.cpp +++ b/libs/gui/tests/EndToEndNativeInputTest.cpp @@ -628,7 +628,7 @@ TEST_F(InputSurfacesTest, input_respects_scaled_touchable_region_overflow) { // Expect no crash for overflow. injectTap(12, 24); - fgSurface->expectTap(6, 12); + bgSurface->expectTap(12, 24); } // Ensure we ignore transparent region when getting screen bounds when positioning input frame. -- GitLab From 43b5d521210b89dbc72c3c5cca8c10454fad3348 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Mon, 10 Apr 2023 15:53:45 -0400 Subject: [PATCH 1107/1310] Skip renderCachedSets if the Output is not enabled This method was being called for a display that had not turned on yet. The call to getSkipColorTransform resulted in a log warning - the call only checks a cached value, and the value is only cached when the display is first turned on. Skip the call when it is not enabled (e.g. when it is off), removing the log spam. Bug: 277358841 Test: look at logs Change-Id: Ic5eb3a69d50719cf68e3b41d6eca659bd712f65f --- services/surfaceflinger/CompositionEngine/src/Output.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index e720af5a33..d64231f9e7 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -1574,9 +1574,10 @@ void Output::postFramebuffer() { } void Output::renderCachedSets(const CompositionRefreshArgs& refreshArgs) { - if (mPlanner) { - mPlanner->renderCachedSets(getState(), refreshArgs.scheduledFrameTime, - getState().usesDeviceComposition || getSkipColorTransform()); + const auto& outputState = getState(); + if (mPlanner && outputState.isEnabled) { + mPlanner->renderCachedSets(outputState, refreshArgs.scheduledFrameTime, + outputState.usesDeviceComposition || getSkipColorTransform()); } } -- GitLab From f106df7ec1f3ba70a56b9836688ec6e2cb6734e0 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Tue, 4 Apr 2023 17:44:46 +0000 Subject: [PATCH 1108/1310] jpegr: fix float to float_16 bug Bug: b/264715926 Change-Id: I7d78b87f971916503ef84b056f4fc8db8720d2fe --- .../include/jpegrecoverymap/recoverymapmath.h | 16 +++++++++----- .../tests/recoverymapmath_test.cpp | 21 +++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h index 67d2a6a0be..0aef9b9b92 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h @@ -116,11 +116,17 @@ inline Color operator/(const Color& lhs, const float rhs) { } inline uint16_t floatToHalf(float f) { - uint32_t x = *((uint32_t*)&f); - uint16_t h = ((x >> 16) & 0x8000) - | ((((x & 0x7f800000) - 0x38000000) >> 13) & 0x7c00) - | ((x >> 13) & 0x03ff); - return h; + // round-to-nearest-even: add last bit after truncated mantissa + const uint32_t b = *((uint32_t*)&f) + 0x00001000; + + const uint32_t e = (b & 0x7F800000) >> 23; // exponent + const uint32_t m = b & 0x007FFFFF; // mantissa + + // sign : normalized : denormalized : saturate + return (b & 0x80000000) >> 16 + | (e > 112) * ((((e - 112) << 10) & 0x7C00) | m >> 13) + | ((e < 113) & (e > 101)) * ((((0x007FF000 + m) >> (125 - e)) + 1) >> 1) + | (e > 143) * 0x7FFF; } constexpr size_t kRecoveryFactorPrecision = 10; diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp index 5ef79e9e34..2369a7e4e7 100644 --- a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp +++ b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp @@ -952,6 +952,27 @@ TEST_F(RecoveryMapMathTest, ColorToRgba1010102) { | static_cast(0.3f * static_cast(0x3ff)) << 20); } +TEST_F(RecoveryMapMathTest, ColorToRgbaF16) { + EXPECT_EQ(colorToRgbaF16(RgbBlack()), ((uint64_t) 0x3C00) << 48); + EXPECT_EQ(colorToRgbaF16(RgbWhite()), 0x3C003C003C003C00); + EXPECT_EQ(colorToRgbaF16(RgbRed()), (((uint64_t) 0x3C00) << 48) | ((uint64_t) 0x3C00)); + EXPECT_EQ(colorToRgbaF16(RgbGreen()), (((uint64_t) 0x3C00) << 48) | (((uint64_t) 0x3C00) << 16)); + EXPECT_EQ(colorToRgbaF16(RgbBlue()), (((uint64_t) 0x3C00) << 48) | (((uint64_t) 0x3C00) << 32)); + + Color e_gamma = {{{ 0.1f, 0.2f, 0.3f }}}; + EXPECT_EQ(colorToRgbaF16(e_gamma), 0x3C0034CD32662E66); +} + +TEST_F(RecoveryMapMathTest, Float32ToFloat16) { + EXPECT_EQ(floatToHalf(0.1f), 0x2E66); + EXPECT_EQ(floatToHalf(0.0f), 0x0); + EXPECT_EQ(floatToHalf(1.0f), 0x3C00); + EXPECT_EQ(floatToHalf(-1.0f), 0xBC00); + EXPECT_EQ(floatToHalf(0x1.fffffep127f), 0x7FFF); // float max + EXPECT_EQ(floatToHalf(-0x1.fffffep127f), 0xFFFF); // float min + EXPECT_EQ(floatToHalf(0x1.0p-126f), 0x0); // float zero +} + TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgb) { EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), srgbLuminance), 0.0f); -- GitLab From 0f23909aac1cb9d9547fef808020927d94113f34 Mon Sep 17 00:00:00 2001 From: Suprabh Shukla Date: Tue, 4 Apr 2023 03:33:09 -0700 Subject: [PATCH 1109/1310] Pipe wakeup sensor events to batterystats Creating another method in BatteryService to send notifcations from sensor event connection to BatteryStatsService. Passing the sensor handle so java code can retrieve a Sensor object and then query any properties of the sensor from there. Test: Manually trigger sensor events and check logcat Ignore-AOSP-First: AIDL method definition not present in AOSP. Bug: 275436924 Change-Id: I7c3009b33a38047463214eb32ba67925ddc20773 --- libs/binder/IBatteryStats.cpp | 19 +++++++++++++++++++ .../batterystats/IBatteryStats.h | 5 ++++- services/sensorservice/BatteryService.cpp | 8 ++++++++ services/sensorservice/BatteryService.h | 14 ++++++++++++++ .../sensorservice/SensorEventConnection.cpp | 3 +++ 5 files changed, 48 insertions(+), 1 deletion(-) diff --git a/libs/binder/IBatteryStats.cpp b/libs/binder/IBatteryStats.cpp index 0de804c3c2..69b11c0ee9 100644 --- a/libs/binder/IBatteryStats.cpp +++ b/libs/binder/IBatteryStats.cpp @@ -128,6 +128,15 @@ public: remote()->transact(NOTE_RESET_FLASHLIGHT_TRANSACTION, data, &reply); } + virtual binder::Status noteWakeupSensorEvent(int64_t elapsedNanos, int uid, int handle) { + Parcel data, reply; + data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor()); + data.writeInt64(elapsedNanos); + data.writeInt32(uid); + data.writeInt32(handle); + status_t ret = remote()->transact(NOTE_WAKEUP_SENSOR_EVENT_TRANSACTION, data, &reply); + return binder::Status::fromStatusT(ret); + } }; IMPLEMENT_META_INTERFACE(BatteryStats, "com.android.internal.app.IBatteryStats") @@ -235,6 +244,16 @@ status_t BnBatteryStats::onTransact( reply->writeNoException(); return NO_ERROR; } break; + case NOTE_WAKEUP_SENSOR_EVENT_TRANSACTION: { + CHECK_INTERFACE(IBatteryStats, data, reply); + int64_t elapsedNanos = data.readInt64(); + int uid = data.readInt32(); + int handle = data.readInt32(); + noteWakeupSensorEvent(elapsedNanos, uid, handle); + reply->writeNoException(); + return NO_ERROR; + } break; + default: return BBinder::onTransact(code, data, reply, flags); } diff --git a/libs/binder/include_batterystats/batterystats/IBatteryStats.h b/libs/binder/include_batterystats/batterystats/IBatteryStats.h index 6defc7fb0b..5bb01dd86b 100644 --- a/libs/binder/include_batterystats/batterystats/IBatteryStats.h +++ b/libs/binder/include_batterystats/batterystats/IBatteryStats.h @@ -19,6 +19,7 @@ #ifndef __ANDROID_VNDK__ #include +#include namespace android { @@ -43,6 +44,7 @@ public: virtual void noteStopCamera(int uid) = 0; virtual void noteResetCamera() = 0; virtual void noteResetFlashlight() = 0; + virtual binder::Status noteWakeupSensorEvent(int64_t elapsedNanos, int uid, int sensor) = 0; enum { NOTE_START_SENSOR_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION, @@ -58,7 +60,8 @@ public: NOTE_START_CAMERA_TRANSACTION, NOTE_STOP_CAMERA_TRANSACTION, NOTE_RESET_CAMERA_TRANSACTION, - NOTE_RESET_FLASHLIGHT_TRANSACTION + NOTE_RESET_FLASHLIGHT_TRANSACTION, + NOTE_WAKEUP_SENSOR_EVENT_TRANSACTION }; }; diff --git a/services/sensorservice/BatteryService.cpp b/services/sensorservice/BatteryService.cpp index 94de55c124..b0fbe5dc4e 100644 --- a/services/sensorservice/BatteryService.cpp +++ b/services/sensorservice/BatteryService.cpp @@ -74,6 +74,14 @@ void BatteryService::disableSensorImpl(uid_t uid, int handle) { } } +void BatteryService::noteWakeupSensorEventImpl(int64_t elapsedNanos, uid_t uid, int handle) { + if (checkService()) { + int64_t identity = IPCThreadState::self()->clearCallingIdentity(); + mBatteryStatService->noteWakeupSensorEvent(elapsedNanos, uid, handle); + IPCThreadState::self()->restoreCallingIdentity(identity); + } +} + bool BatteryService::checkService() { if (mBatteryStatService == nullptr) { const sp sm(defaultServiceManager()); diff --git a/services/sensorservice/BatteryService.h b/services/sensorservice/BatteryService.h index 13fc58aadb..60ef03f685 100644 --- a/services/sensorservice/BatteryService.h +++ b/services/sensorservice/BatteryService.h @@ -19,11 +19,14 @@ #include #include +#include +#include namespace android { // --------------------------------------------------------------------------- class BatteryService : public Singleton { + static constexpr int64_t WAKEUP_SENSOR_EVENT_DEBOUNCE_MS = 1000; friend class Singleton; sp mBatteryStatService; @@ -32,6 +35,7 @@ class BatteryService : public Singleton { void enableSensorImpl(uid_t uid, int handle); void disableSensorImpl(uid_t uid, int handle); + void noteWakeupSensorEventImpl(int64_t elapsedNanos, uid_t uid, int handle); struct Info { uid_t uid; @@ -44,6 +48,7 @@ class BatteryService : public Singleton { } }; + int64_t mLastWakeupSensorEventReportedMs; Mutex mActivationsLock; SortedVector mActivations; bool addSensor(uid_t uid, int handle); @@ -57,6 +62,15 @@ public: static void disableSensor(uid_t uid, int handle) { BatteryService::getInstance().disableSensorImpl(uid, handle); } + static void noteWakeupSensorEvent(int64_t elapsed, uid_t uid, int handle) { + BatteryService& instance = BatteryService::getInstance(); + const int64_t nowElapsedMs = elapsedRealtime(); + if (nowElapsedMs >= (instance.mLastWakeupSensorEventReportedMs + + WAKEUP_SENSOR_EVENT_DEBOUNCE_MS)) { + instance.noteWakeupSensorEventImpl(elapsed, uid, handle); + instance.mLastWakeupSensorEventReportedMs = nowElapsedMs; + } + } }; // --------------------------------------------------------------------------- diff --git a/services/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp index 7a6b31d642..dc5070c315 100644 --- a/services/sensorservice/SensorEventConnection.cpp +++ b/services/sensorservice/SensorEventConnection.cpp @@ -23,6 +23,7 @@ #include #include "vec.h" +#include "BatteryService.h" #include "SensorEventConnection.h" #include "SensorDevice.h" @@ -391,6 +392,8 @@ status_t SensorService::SensorEventConnection::sendEvents( if (hasSensorAccess()) { index_wake_up_event = findWakeUpSensorEventLocked(scratch, count); if (index_wake_up_event >= 0) { + BatteryService::noteWakeupSensorEvent(scratch[index_wake_up_event].timestamp, + mUid, scratch[index_wake_up_event].sensor); scratch[index_wake_up_event].flags |= WAKE_UP_SENSOR_EVENT_NEEDS_ACK; ++mWakeLockRefCount; #if DEBUG_CONNECTIONS -- GitLab From 18838bcf7d0b61a31d039615287fb3a5ca189231 Mon Sep 17 00:00:00 2001 From: Cody Heiner Date: Wed, 5 Apr 2023 16:52:27 -0700 Subject: [PATCH 1110/1310] Add `front` and `back` methods to RingBuffer Test: Corresponding added RingBuffer tests pass (`atest libinput_tests`) Bug: 268245099 Change-Id: If330940e67e70d809748b2aaa6156bed835c6c9d --- include/input/RingBuffer.h | 5 +++++ libs/input/tests/RingBuffer_test.cpp | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/include/input/RingBuffer.h b/include/input/RingBuffer.h index 67984b7c80..37fe5afeea 100644 --- a/include/input/RingBuffer.h +++ b/include/input/RingBuffer.h @@ -103,6 +103,11 @@ public: iterator end() { return {*this, mSize}; } const_iterator end() const { return {*this, mSize}; } + reference front() { return mBuffer[mBegin]; } + const_reference front() const { return mBuffer[mBegin]; } + reference back() { return mBuffer[bufferIndex(mSize - 1)]; } + const_reference back() const { return mBuffer[bufferIndex(mSize - 1)]; } + reference operator[](size_type i) { return mBuffer[bufferIndex(i)]; } const_reference operator[](size_type i) const { return mBuffer[bufferIndex(i)]; } diff --git a/libs/input/tests/RingBuffer_test.cpp b/libs/input/tests/RingBuffer_test.cpp index 8a6ef4c21b..a2ef658934 100644 --- a/libs/input/tests/RingBuffer_test.cpp +++ b/libs/input/tests/RingBuffer_test.cpp @@ -118,6 +118,21 @@ TEST(RingBufferTest, Assignment) { EXPECT_EQ(0u, d.capacity()); } +TEST(RingBufferTest, FrontBackAccess) { + RingBuffer buffer(/*capacity=*/2); + buffer.pushBack(1); + EXPECT_EQ(1, buffer.front()); + EXPECT_EQ(1, buffer.back()); + + buffer.pushFront(0); + EXPECT_EQ(0, buffer.front()); + EXPECT_EQ(1, buffer.back()); + + buffer.pushFront(-1); + EXPECT_EQ(-1, buffer.front()); + EXPECT_EQ(0, buffer.back()); +} + TEST(RingBufferTest, Subscripting) { RingBuffer buffer(/*capacity=*/2); buffer.pushBack(1); -- GitLab From 17b816f72c99e4b71bfd9f1f41910b3da282fec7 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 10 Apr 2023 19:23:24 +0000 Subject: [PATCH 1111/1310] Remove MultiDisplayTests#many_to_one_display_mapping More than one display cannot map to one layer stack anymore, so this test is no longer valid. Bug: 277344479 Test: atest libgui_test:MultiDisplayTests Change-Id: I12e70d4e7df47d7b8fcb1b757ecc590856b4045e --- libs/gui/tests/EndToEndNativeInputTest.cpp | 26 ---------------------- 1 file changed, 26 deletions(-) diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp index 9e8c65c678..2983a107f5 100644 --- a/libs/gui/tests/EndToEndNativeInputTest.cpp +++ b/libs/gui/tests/EndToEndNativeInputTest.cpp @@ -1235,32 +1235,6 @@ TEST_F(MultiDisplayTests, virtual_display_receives_input) { surface->expectKey(AKEYCODE_V); } -/** - * When multiple DisplayDevices are mapped to the same layerStack, use the configuration for the - * display that can receive input. - */ -TEST_F(MultiDisplayTests, many_to_one_display_mapping) { - ui::LayerStack layerStack = ui::LayerStack::fromValue(42); - createDisplay(1000, 1000, false /*isSecure*/, layerStack, false /*receivesInput*/, - 100 /*offsetX*/, 100 /*offsetY*/); - createDisplay(1000, 1000, false /*isSecure*/, layerStack, true /*receivesInput*/, - 200 /*offsetX*/, 200 /*offsetY*/); - createDisplay(1000, 1000, false /*isSecure*/, layerStack, false /*receivesInput*/, - 300 /*offsetX*/, 300 /*offsetY*/); - std::unique_ptr surface = makeSurface(100, 100); - surface->doTransaction([&](auto &t, auto &sc) { t.setLayerStack(sc, layerStack); }); - surface->showAt(10, 10); - - // Input injection happens in logical display coordinates. - injectTapOnDisplay(11, 11, layerStack.id); - // Expect that the display transform for the display that receives input was used. - surface->expectTapInDisplayCoordinates(211, 211); - - surface->requestFocus(layerStack.id); - surface->assertFocusChange(true); - injectKeyOnDisplay(AKEYCODE_V, layerStack.id); -} - TEST_F(MultiDisplayTests, drop_input_for_secure_layer_on_nonsecure_display) { ui::LayerStack layerStack = ui::LayerStack::fromValue(42); createDisplay(1000, 1000, false /*isSecure*/, layerStack); -- GitLab From b96ba8f51723c0c38074839c53e4f55f9e86bed2 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Tue, 21 Mar 2023 23:33:41 +0000 Subject: [PATCH 1112/1310] JPEG/R: add restriction to max_display_boost input from user Bug: b/264715926 Change-Id: I82c24c285fb01cab57ec004f1440ac9b5adacc39 (cherry picked from commit 4e49f18a515173523992f359a6a74feeb7b822a7) --- libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h | 9 +++++++-- libs/jpegrecoverymap/jpegr.cpp | 8 +++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h index 1ab1dd7245..6262e18479 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h @@ -19,6 +19,10 @@ #include "jpegrerrorcode.h" +#ifndef FLT_MAX +#define FLT_MAX 0x1.fffffep127f +#endif + namespace android::jpegrecoverymap { // Color gamuts for image data @@ -206,7 +210,8 @@ public: * * @param compressed_jpegr_image compressed JPEGR image. * @param dest destination of the uncompressed JPEGR image. - * @param max_display_boost (optional) the maximum available boost supported by a display + * @param max_display_boost (optional) the maximum available boost supported by a display, + * the value must be greater than or equal to 1.0. * @param exif destination of the decoded EXIF metadata. The default value is NULL where the decoder will do nothing about it. If configured not NULL the decoder will write EXIF data into this structure. The format is defined in {@code jpegr_exif_struct} @@ -235,7 +240,7 @@ public: */ status_t decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, jr_uncompressed_ptr dest, - float max_display_boost = -1.0f, + float max_display_boost = FLT_MAX, jr_exif_ptr exif = nullptr, jpegr_output_format output_format = JPEGR_OUTPUT_HDR_LINEAR, jr_uncompressed_ptr recovery_map = nullptr, diff --git a/libs/jpegrecoverymap/jpegr.cpp b/libs/jpegrecoverymap/jpegr.cpp index 559ca3564a..d147130209 100644 --- a/libs/jpegrecoverymap/jpegr.cpp +++ b/libs/jpegrecoverymap/jpegr.cpp @@ -339,6 +339,10 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, return ERROR_JPEGR_INVALID_NULL_PTR; } + if (max_display_boost < 1.0f) { + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + if (output_format == JPEGR_OUTPUT_SDR) { JpegDecoderHelper jpeg_decoder; if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length, @@ -683,9 +687,7 @@ status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, dest->width = uncompressed_yuv_420_image->width; dest->height = uncompressed_yuv_420_image->height; ShepardsIDW idwTable(kMapDimensionScaleFactor); - float display_boost = max_display_boost > 0 ? - std::min(max_display_boost, metadata->maxContentBoost) - : metadata->maxContentBoost; + float display_boost = std::min(max_display_boost, metadata->maxContentBoost); RecoveryLUT recoveryLUT(metadata, display_boost); JobQueue jobQueue; -- GitLab From 444f395b6820a7761b801e7af4e1c1a19dd27413 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 11 Apr 2023 13:01:02 -0700 Subject: [PATCH 1113/1310] [sf] Fix blur alpha for new sf frontend - update the blur alpha when alpha changes - also update layer stack ordering to match legacy order Test: presubmit Bug: 238781169 Change-Id: I6d122a9721a3de0f8b04e60ac21fa879b29b22e3 --- .../FrontEnd/LayerHierarchy.cpp | 2 +- .../FrontEnd/LayerSnapshotBuilder.cpp | 7 ++++- .../FrontEnd/RequestedLayerState.cpp | 14 +++++----- .../tests/unittests/LayerHierarchyTest.cpp | 15 +++++----- .../tests/unittests/LayerHierarchyTest.h | 11 ++++++++ .../tests/unittests/LayerSnapshotTest.cpp | 28 ++++++++++++++----- 6 files changed, 53 insertions(+), 24 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp index c30465fbd4..5913d4b589 100644 --- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp @@ -30,7 +30,7 @@ auto layerZCompare = [](const std::pairgetLayer(); auto rhsLayer = rhs.first->getLayer(); if (lhsLayer->layerStack.id != rhsLayer->layerStack.id) { - return lhsLayer->layerStack.id > rhsLayer->layerStack.id; + return lhsLayer->layerStack.id < rhsLayer->layerStack.id; } if (lhsLayer->z != rhsLayer->z) { return lhsLayer->z < rhsLayer->z; diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index 25cbe7ae54..ce7d37e69b 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -817,7 +817,8 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a snapshot.frameRateSelectionPriority = requested.frameRateSelectionPriority; } - if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Content)) { + if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Content) || + snapshot.changes.any(RequestedLayerState::Changes::AffectsChildren)) { snapshot.color.rgb = requested.getColor().rgb; snapshot.isColorspaceAgnostic = requested.colorSpaceAgnostic; snapshot.backgroundBlurRadius = args.supportsBlur @@ -1069,6 +1070,10 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, // touches from going outside the cloned area. if (path.isClone()) { snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::CLONE; + // Cloned layers shouldn't handle watch outside since their z order is not determined by + // WM or the client. + snapshot.inputInfo.inputConfig.clear(gui::WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH); + mNeedsTouchableRegionCrop.insert(path); } } diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index 1f670c81df..beb69a6035 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -32,10 +32,6 @@ using ftl::Flags; using namespace ftl::flag_operators; namespace { -std::string layerIdToString(uint32_t layerId) { - return layerId == UNASSIGNED_LAYER_ID ? "none" : std::to_string(layerId); -} - std::string layerIdsToString(const std::vector& layerIds) { std::stringstream stream; stream << "{"; @@ -323,9 +319,13 @@ ui::Transform RequestedLayerState::getTransform(uint32_t displayRotationFlags) c std::string RequestedLayerState::getDebugString() const { std::stringstream debug; - debug << "RequestedLayerState{" << name << " parent=" << layerIdToString(parentId) - << " relativeParent=" << layerIdToString(relativeParentId) - << " mirrorId=" << layerIdsToString(mirrorIds) << " handle=" << handleAlive << " z=" << z; + debug << "RequestedLayerState{" << name; + if (parentId != UNASSIGNED_LAYER_ID) debug << " parentId=" << parentId; + if (relativeParentId != UNASSIGNED_LAYER_ID) debug << " relativeParentId=" << relativeParentId; + if (!mirrorIds.empty()) debug << " mirrorId=" << layerIdsToString(mirrorIds); + if (!handleAlive) debug << " !handle"; + if (z != 0) debug << " z=" << z; + if (layerStack.id != 0) debug << " layerStack=" << layerStack.id; return debug.str(); } diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp index ddf3363244..88d39db17d 100644 --- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp @@ -662,10 +662,9 @@ TEST_F(LayerHierarchyTest, zorderRespectsLayerStack) { mLifecycleManager.commitChanges(); LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers()); UPDATE_AND_VERIFY(hierarchyBuilder); - std::vector expectedTraversalPath = {1, 11, 2, 21}; + std::vector expectedTraversalPath = {2, 21, 1, 11}; EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); - expectedTraversalPath = {1, 11, 2, 21}; EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); expectedTraversalPath = {}; EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); @@ -678,8 +677,8 @@ TEST_F(LayerHierarchyTest, canMirrorDisplay) { setLayerStack(3, 1); UPDATE_AND_VERIFY(hierarchyBuilder); - std::vector expected = {3, 1, 11, 111, 12, 121, 122, 1221, 13, 2, - 1, 11, 111, 12, 121, 122, 1221, 13, 2}; + std::vector expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3, + 1, 11, 111, 12, 121, 122, 1221, 13, 2}; EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected); EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected); expected = {}; @@ -693,7 +692,7 @@ TEST_F(LayerHierarchyTest, mirrorNonExistingDisplay) { setLayerStack(3, 1); UPDATE_AND_VERIFY(hierarchyBuilder); - std::vector expected = {3, 1, 11, 111, 12, 121, 122, 1221, 13, 2}; + std::vector expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3}; EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected); EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected); expected = {}; @@ -710,8 +709,8 @@ TEST_F(LayerHierarchyTest, newRootLayerIsMirrored) { createRootLayer(4); UPDATE_AND_VERIFY(hierarchyBuilder); - std::vector expected = {3, 1, 11, 111, 12, 121, 122, 1221, 13, 2, 4, - 1, 11, 111, 12, 121, 122, 1221, 13, 2, 4}; + std::vector expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 4, 3, + 1, 11, 111, 12, 121, 122, 1221, 13, 2, 4}; EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected); EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected); expected = {}; @@ -729,7 +728,7 @@ TEST_F(LayerHierarchyTest, removedRootLayerIsNoLongerMirrored) { destroyLayerHandle(1); UPDATE_AND_VERIFY(hierarchyBuilder); - std::vector expected = {3, 2, 2}; + std::vector expected = {2, 3, 2}; EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected); EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected); expected = {11, 111, 12, 121, 122, 1221, 13}; diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h index 79cfd6a891..43011863ff 100644 --- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h +++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h @@ -297,6 +297,17 @@ protected: mLifecycleManager.applyTransactions(transactions); } + void setBackgroundBlurRadius(uint32_t id, uint32_t backgroundBlurRadius) { + std::vector transactions; + transactions.emplace_back(); + transactions.back().states.push_back({}); + + transactions.back().states.front().state.what = layer_state_t::eBackgroundBlurRadiusChanged; + transactions.back().states.front().layerId = id; + transactions.back().states.front().state.backgroundBlurRadius = backgroundBlurRadius; + mLifecycleManager.applyTransactions(transactions); + } + LayerLifecycleManager mLifecycleManager; }; diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index 5a066a6482..b8a7446b3a 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -79,6 +79,7 @@ protected: .displays = mFrontEndDisplayInfos, .displayChanges = hasDisplayChanges, .globalShadowSettings = globalShadowSettings, + .supportsBlur = true, .supportedLayerGenericMetadata = {}, .genericLayerMetadataKeyMap = {}}; actualBuilder.update(args); @@ -333,6 +334,19 @@ TEST_F(LayerSnapshotTest, canCropTouchableRegion) { EXPECT_EQ(getSnapshot({.id = 111})->inputInfo.touchableRegion.bounds(), modifiedTouchCrop); } +TEST_F(LayerSnapshotTest, blurUpdatesWhenAlphaChanges) { + static constexpr int blurRadius = 42; + setBackgroundBlurRadius(1221, blurRadius); + + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + EXPECT_EQ(getSnapshot({.id = 1221})->backgroundBlurRadius, blurRadius); + + static constexpr float alpha = 0.5; + setAlpha(12, alpha); + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + EXPECT_EQ(getSnapshot({.id = 1221})->backgroundBlurRadius, blurRadius * alpha); +} + // Display Mirroring Tests // tree with 3 levels of children // ROOT (DISPLAY 0) @@ -352,7 +366,7 @@ TEST_F(LayerSnapshotTest, displayMirrorRespectsLayerSkipScreenshotFlag) { createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0)); setLayerStack(3, 1); - std::vector expected = {3, 1, 11, 111, 13, 2, 1, 11, 111, 12, 121, 122, 1221, 13, 2}; + std::vector expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3, 1, 11, 111, 13, 2}; UPDATE_AND_VERIFY(mSnapshotBuilder, expected); } @@ -371,8 +385,8 @@ TEST_F(LayerSnapshotTest, mirrorLayerGetsCorrectLayerStack) { createDisplayMirrorLayer(4, ui::LayerStack::fromValue(0)); setLayerStack(4, 4); - std::vector expected = {4, 1, 11, 111, 13, 2, 3, 1, 11, - 111, 13, 2, 1, 11, 111, 13, 2}; + std::vector expected = {1, 11, 111, 13, 2, 3, 1, 11, 111, + 13, 2, 4, 1, 11, 111, 13, 2}; UPDATE_AND_VERIFY(mSnapshotBuilder, expected); EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootId = 3})->outputFilter.layerStack.id, 3u); EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootId = 4})->outputFilter.layerStack.id, 4u); @@ -395,7 +409,7 @@ TEST_F(LayerSnapshotTest, mirrorLayerTouchIsCroppedByMirrorRoot) { setCrop(111, Rect{200, 200}); Region touch{Rect{0, 0, 1000, 1000}}; setTouchableRegion(111, touch); - std::vector expected = {3, 1, 11, 111, 13, 2, 1, 11, 111, 13, 2}; + std::vector expected = {1, 11, 111, 13, 2, 3, 1, 11, 111, 13, 2}; UPDATE_AND_VERIFY(mSnapshotBuilder, expected); EXPECT_TRUE(getSnapshot({.id = 111})->inputInfo.touchableRegion.hasSameRects(touch)); Region touchCroppedByMirrorRoot{Rect{0, 0, 50, 50}}; @@ -407,7 +421,7 @@ TEST_F(LayerSnapshotTest, canRemoveDisplayMirror) { setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot); createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0)); setLayerStack(3, 1); - std::vector expected = {3, 1, 11, 111, 13, 2, 1, 11, 111, 12, 121, 122, 1221, 13, 2}; + std::vector expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3, 1, 11, 111, 13, 2}; UPDATE_AND_VERIFY(mSnapshotBuilder, expected); destroyLayerHandle(3); UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); @@ -417,8 +431,8 @@ TEST_F(LayerSnapshotTest, cleanUpUnreachableSnapshotsAfterMirroring) { size_t startingNumSnapshots = mSnapshotBuilder.getSnapshots().size(); createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0)); setLayerStack(3, 1); - std::vector expected = {3, 1, 11, 111, 12, 121, 122, 1221, 13, 2, - 1, 11, 111, 12, 121, 122, 1221, 13, 2}; + std::vector expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3, + 1, 11, 111, 12, 121, 122, 1221, 13, 2}; UPDATE_AND_VERIFY(mSnapshotBuilder, expected); destroyLayerHandle(3); UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); -- GitLab From 74c80593852afb700c85448c62391e0f5b93a2da Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 11 Apr 2023 19:09:20 +0000 Subject: [PATCH 1114/1310] Add EndToEndNativeInputTest to SF presubmit Bug: 240437119 Test: presubmit Change-Id: I5e9203772e10e7596333a57a939147f484396e19 --- services/surfaceflinger/TEST_MAPPING | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/services/surfaceflinger/TEST_MAPPING b/services/surfaceflinger/TEST_MAPPING index 57752b7a39..155a27531b 100644 --- a/services/surfaceflinger/TEST_MAPPING +++ b/services/surfaceflinger/TEST_MAPPING @@ -6,6 +6,14 @@ { "name": "libcompositionengine_test" }, + { + "name": "libgui_test", + "options": [ + { + "native-test-flag": "--gtest_filter=\"InputSurfacesTest*:MultiDisplayTests*\"" + } + ] + }, { "name": "libscheduler_test" } -- GitLab From 310702e35327d7f4f40b8a663e5e3f12bf132ba6 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 27 Jul 2022 17:16:40 +0000 Subject: [PATCH 1115/1310] Add relevant libgui_tests to input presubmit Bug: 240437119 Change-Id: Ide5e4d5f67ff0a1feb206b028624da6f9e061cb3 Test: presubmit --- services/inputflinger/TEST_MAPPING | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING index 495334e99a..f0b107222a 100644 --- a/services/inputflinger/TEST_MAPPING +++ b/services/inputflinger/TEST_MAPPING @@ -26,6 +26,14 @@ { "name": "libinputservice_test" }, + { + "name": "libgui_test", + "options": [ + { + "native-test-flag": "--gtest_filter=\"InputSurfacesTest*:MultiDisplayTests*\"" + } + ] + }, { "name": "CtsHardwareTestCases", "options": [ @@ -122,6 +130,14 @@ { "name": "libinputservice_test" }, + { + "name": "libgui_test", + "options": [ + { + "native-test-flag": "--gtest_filter=\"InputSurfacesTest*:MultiDisplayTests*\"" + } + ] + }, { "name": "CtsHardwareTestCases", "options": [ -- GitLab From c2eb8505edd83a38f4f4ff0454ba958615ccda12 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 11 Apr 2023 18:33:36 -0700 Subject: [PATCH 1116/1310] Expand fatal log for HOVER_MOVE Add the entry information to the log to make it more useful. Bug: 273376858 Test: none Change-Id: I94aaf650acef638b9e189a6ebed1e7d92dc05cd6 --- services/inputflinger/dispatcher/InputDispatcher.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 6e2f86223f..851f13c0d5 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -626,7 +626,9 @@ std::vector getHoveringWindowsLocked(const TouchState* oldState, touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_HOVER_ENTER; } else { // This pointer was already sent to the window. Use ACTION_HOVER_MOVE. - LOG_ALWAYS_FATAL_IF(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE); + if (CC_UNLIKELY(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE)) { + LOG(FATAL) << "Expected ACTION_HOVER_MOVE instead of " << entry.getDescription(); + } touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_IS; } touchedWindow.pointerIds.set(pointerId); -- GitLab From 4ffa4d5b6af53fe765ed155a3d4dd6b16e3ffd5b Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 12 Apr 2023 19:38:34 +0000 Subject: [PATCH 1117/1310] Fix display association for drawing tablets - part 2 This is a follow-up to the incomplete changes in: I0a336dc456f6432a1e74e96a22ff57bb16a65ad7 When a drawing tablet is associated with a display that is not the same as the PointerController's display, do not update the pointer controller, and do not get the cursor position from the pointer controller. Bug: 277174674 Bug: 236798672 Test: atest DrawingTabletTest Change-Id: I84eb166b8fedfc2b5f668b3aa1bea03628177ca3 --- .../reader/mapper/TouchInputMapper.cpp | 59 ++++++++++--------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index c19737d672..c1b23857f0 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -3600,17 +3600,19 @@ std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsec "%s cannot be used when the device is not in POINTER mode.", __func__); std::list out; int32_t metaState = getContext()->getGlobalMetaState(); + auto cursorPosition = mPointerSimple.currentCoords.getXYValue(); - if (down || hovering) { - mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); - mPointerController->clearSpots(); - mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); - } else if (!down && !hovering && (mPointerSimple.down || mPointerSimple.hovering)) { - mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); + if (displayId == mPointerController->getDisplayId()) { + std::tie(cursorPosition.x, cursorPosition.y) = mPointerController->getPosition(); + if (down || hovering) { + mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); + mPointerController->clearSpots(); + mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); + } else if (!down && !hovering && (mPointerSimple.down || mPointerSimple.hovering)) { + mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); + } } - const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); - if (mPointerSimple.down && !down) { mPointerSimple.down = false; @@ -3620,8 +3622,9 @@ std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsec 0, metaState, mLastRawState.buttonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties, &mPointerSimple.lastCoords, - mOrientedXPrecision, mOrientedYPrecision, xCursorPosition, - yCursorPosition, mPointerSimple.downTime, + mOrientedXPrecision, mOrientedYPrecision, + mPointerSimple.lastCursorX, mPointerSimple.lastCursorY, + mPointerSimple.downTime, /* videoFrames */ {})); } @@ -3629,15 +3632,15 @@ std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsec mPointerSimple.hovering = false; // Send hover exit. - out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), - mSource, displayId, policyFlags, - AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0, metaState, - mLastRawState.buttonState, MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, 1, - &mPointerSimple.lastProperties, &mPointerSimple.lastCoords, - mOrientedXPrecision, mOrientedYPrecision, xCursorPosition, - yCursorPosition, mPointerSimple.downTime, - /* videoFrames */ {})); + out.push_back( + NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, + displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0, + metaState, mLastRawState.buttonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties, + &mPointerSimple.lastCoords, mOrientedXPrecision, + mOrientedYPrecision, mPointerSimple.lastCursorX, + mPointerSimple.lastCursorY, mPointerSimple.downTime, + /* videoFrames */ {})); } if (down) { @@ -3653,7 +3656,7 @@ std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsec AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords, mOrientedXPrecision, - mOrientedYPrecision, xCursorPosition, yCursorPosition, + mOrientedYPrecision, cursorPosition.x, cursorPosition.y, mPointerSimple.downTime, /* videoFrames */ {})); } @@ -3664,7 +3667,7 @@ std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsec MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords, mOrientedXPrecision, - mOrientedYPrecision, xCursorPosition, yCursorPosition, + mOrientedYPrecision, cursorPosition.x, cursorPosition.y, mPointerSimple.downTime, /* videoFrames */ {})); } @@ -3680,7 +3683,7 @@ std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsec AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords, mOrientedXPrecision, - mOrientedYPrecision, xCursorPosition, yCursorPosition, + mOrientedYPrecision, cursorPosition.x, cursorPosition.y, mPointerSimple.downTime, /* videoFrames */ {})); } @@ -3691,8 +3694,8 @@ std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsec metaState, mCurrentRawState.buttonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords, - mOrientedXPrecision, mOrientedYPrecision, xCursorPosition, - yCursorPosition, mPointerSimple.downTime, /* videoFrames */ {})); + mOrientedXPrecision, mOrientedYPrecision, cursorPosition.x, + cursorPosition.y, mPointerSimple.downTime, /* videoFrames */ {})); } if (mCurrentRawState.rawVScroll || mCurrentRawState.rawHScroll) { @@ -3712,8 +3715,8 @@ std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsec 0, 0, metaState, mCurrentRawState.buttonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties, &pointerCoords, - mOrientedXPrecision, mOrientedYPrecision, xCursorPosition, - yCursorPosition, mPointerSimple.downTime, + mOrientedXPrecision, mOrientedYPrecision, cursorPosition.x, + cursorPosition.y, mPointerSimple.downTime, /* videoFrames */ {})); } @@ -3723,8 +3726,8 @@ std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsec mPointerSimple.lastProperties.copyFrom(mPointerSimple.currentProperties); mPointerSimple.displayId = displayId; mPointerSimple.source = mSource; - mPointerSimple.lastCursorX = xCursorPosition; - mPointerSimple.lastCursorY = yCursorPosition; + mPointerSimple.lastCursorX = cursorPosition.x; + mPointerSimple.lastCursorY = cursorPosition.y; } else { mPointerSimple.reset(); } -- GitLab From 10959a4eff0e149dd12d844cf4372a517b7c7606 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Mon, 10 Apr 2023 16:28:16 -0700 Subject: [PATCH 1118/1310] JPEG/R refactor: rename "recovery map" to "gain map" Bug: b/264715926 Test: build, jpegr_test, gainmapmath_test Change-Id: I9ff07ed4201f0d7ee664209fc0450808f2ec143d --- libs/jpegrecoverymap/Android.bp | 2 +- .../{recoverymapmath.cpp => gainmapmath.cpp} | 40 +- libs/jpegrecoverymap/icc.cpp | 2 +- .../{recoverymapmath.h => gainmapmath.h} | 60 +-- .../include/jpegrecoverymap/jpegr.h | 102 ++--- libs/jpegrecoverymap/jpegr.cpp | 168 +++---- libs/jpegrecoverymap/tests/Android.bp | 2 +- ...ymapmath_test.cpp => gainmapmath_test.cpp} | 412 +++++++++--------- libs/jpegrecoverymap/tests/jpegr_test.cpp | 44 +- 9 files changed, 416 insertions(+), 416 deletions(-) rename libs/jpegrecoverymap/{recoverymapmath.cpp => gainmapmath.cpp} (94%) rename libs/jpegrecoverymap/include/jpegrecoverymap/{recoverymapmath.h => gainmapmath.h} (86%) rename libs/jpegrecoverymap/tests/{recoverymapmath_test.cpp => gainmapmath_test.cpp} (72%) diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp index a1b0e19509..a376ced7f1 100644 --- a/libs/jpegrecoverymap/Android.bp +++ b/libs/jpegrecoverymap/Android.bp @@ -31,7 +31,7 @@ cc_library { srcs: [ "icc.cpp", "jpegr.cpp", - "recoverymapmath.cpp", + "gainmapmath.cpp", "jpegrutils.cpp", "multipictureformat.cpp", ], diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/gainmapmath.cpp similarity index 94% rename from libs/jpegrecoverymap/recoverymapmath.cpp rename to libs/jpegrecoverymap/gainmapmath.cpp index ce6fc8fa47..f15a0784e8 100644 --- a/libs/jpegrecoverymap/recoverymapmath.cpp +++ b/libs/jpegrecoverymap/gainmapmath.cpp @@ -16,7 +16,7 @@ #include #include -#include +#include namespace android::jpegrecoverymap { @@ -441,14 +441,14 @@ ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gam //////////////////////////////////////////////////////////////////////////////// -// Recovery map calculations -uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata) { - return encodeRecovery(y_sdr, y_hdr, metadata, - log2(metadata->minContentBoost), log2(metadata->maxContentBoost)); +// Gain map calculations +uint8_t encodeGain(float y_sdr, float y_hdr, jr_metadata_ptr metadata) { + return encodeGain(y_sdr, y_hdr, metadata, + log2(metadata->minContentBoost), log2(metadata->maxContentBoost)); } -uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata, - float log2MinContentBoost, float log2MaxContentBoost) { +uint8_t encodeGain(float y_sdr, float y_hdr, jr_metadata_ptr metadata, + float log2MinContentBoost, float log2MaxContentBoost) { float gain = 1.0f; if (y_sdr > 0.0f) { gain = y_hdr / y_sdr; @@ -462,23 +462,23 @@ uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata, * 255.0f); } -Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata) { - float logBoost = log2(metadata->minContentBoost) * (1.0f - recovery) - + log2(metadata->maxContentBoost) * recovery; - float recoveryFactor = exp2(logBoost); - return e * recoveryFactor; +Color applyGain(Color e, float gain, jr_metadata_ptr metadata) { + float logBoost = log2(metadata->minContentBoost) * (1.0f - gain) + + log2(metadata->maxContentBoost) * gain; + float gainFactor = exp2(logBoost); + return e * gainFactor; } -Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata, float displayBoost) { - float logBoost = log2(metadata->minContentBoost) * (1.0f - recovery) - + log2(metadata->maxContentBoost) * recovery; - float recoveryFactor = exp2(logBoost * displayBoost / metadata->maxContentBoost); - return e * recoveryFactor; +Color applyGain(Color e, float gain, jr_metadata_ptr metadata, float displayBoost) { + float logBoost = log2(metadata->minContentBoost) * (1.0f - gain) + + log2(metadata->maxContentBoost) * gain; + float gainFactor = exp2(logBoost * displayBoost / metadata->maxContentBoost); + return e * gainFactor; } -Color applyRecoveryLUT(Color e, float recovery, RecoveryLUT& recoveryLUT) { - float recoveryFactor = recoveryLUT.getRecoveryFactor(recovery); - return e * recoveryFactor; +Color applyGainLUT(Color e, float gain, GainLUT& gainLUT) { + float gainFactor = gainLUT.getGainFactor(gain); + return e * gainFactor; } Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) { diff --git a/libs/jpegrecoverymap/icc.cpp b/libs/jpegrecoverymap/icc.cpp index 5412cb1102..6e78f671e5 100644 --- a/libs/jpegrecoverymap/icc.cpp +++ b/libs/jpegrecoverymap/icc.cpp @@ -15,7 +15,7 @@ */ #include -#include +#include #include #include diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/gainmapmath.h similarity index 86% rename from libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h rename to libs/jpegrecoverymap/include/jpegrecoverymap/gainmapmath.h index a32b29119e..57fddd0a42 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/gainmapmath.h @@ -129,40 +129,40 @@ inline uint16_t floatToHalf(float f) { | (e > 143) * 0x7FFF; } -constexpr size_t kRecoveryFactorPrecision = 10; -constexpr size_t kRecoveryFactorNumEntries = 1 << kRecoveryFactorPrecision; -struct RecoveryLUT { - RecoveryLUT(jr_metadata_ptr metadata) { - for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kRecoveryFactorNumEntries - 1); +constexpr size_t kGainFactorPrecision = 10; +constexpr size_t kGainFactorNumEntries = 1 << kGainFactorPrecision; +struct GainLUT { + GainLUT(jr_metadata_ptr metadata) { + for (int idx = 0; idx < kGainFactorNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); float logBoost = log2(metadata->minContentBoost) * (1.0f - value) + log2(metadata->maxContentBoost) * value; - mRecoveryTable[idx] = exp2(logBoost); + mGainTable[idx] = exp2(logBoost); } } - RecoveryLUT(jr_metadata_ptr metadata, float displayBoost) { + GainLUT(jr_metadata_ptr metadata, float displayBoost) { float boostFactor = displayBoost > 0 ? displayBoost / metadata->maxContentBoost : 1.0f; - for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kRecoveryFactorNumEntries - 1); + for (int idx = 0; idx < kGainFactorNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); float logBoost = log2(metadata->minContentBoost) * (1.0f - value) + log2(metadata->maxContentBoost) * value; - mRecoveryTable[idx] = exp2(logBoost * boostFactor); + mGainTable[idx] = exp2(logBoost * boostFactor); } } - ~RecoveryLUT() { + ~GainLUT() { } - float getRecoveryFactor(float recovery) { - uint32_t idx = static_cast(recovery * (kRecoveryFactorNumEntries - 1)); + float getGainFactor(float gain) { + uint32_t idx = static_cast(gain * (kGainFactorNumEntries - 1)); //TODO() : Remove once conversion modules have appropriate clamping in place - idx = CLIP3(idx, 0, kRecoveryFactorNumEntries - 1); - return mRecoveryTable[idx]; + idx = CLIP3(idx, 0, kGainFactorNumEntries - 1); + return mGainTable[idx]; } private: - float mRecoveryTable[kRecoveryFactorNumEntries]; + float mGainTable[kGainFactorNumEntries]; }; struct ShepardsIDW { @@ -195,11 +195,11 @@ struct ShepardsIDW { // p60 p61 p62 p63 p64 p65 p66 p67 // p70 p71 p72 p73 p74 p75 p76 p77 - // Recovery Map (for 4 scale factor) :- + // Gain Map (for 4 scale factor) :- // m00 p01 // m10 m11 - // Recovery sample of curr 4x4, right 4x4, bottom 4x4, bottom right 4x4 are used during + // Gain sample of curr 4x4, right 4x4, bottom 4x4, bottom right 4x4 are used during // reconstruction. hence table weight size is 4. float* mWeights; // TODO: check if its ok to mWeights at places @@ -354,29 +354,29 @@ Color bt2100ToP3(Color e); inline Color identityConversion(Color e) { return e; } /* - * Get the conversion to apply to the HDR image for recovery map generation + * Get the conversion to apply to the HDR image for gain map generation */ ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gamut hdr_gamut); //////////////////////////////////////////////////////////////////////////////// -// Recovery map calculations +// Gain map calculations /* - * Calculate the 8-bit unsigned integer recovery value for the given SDR and HDR + * Calculate the 8-bit unsigned integer gain value for the given SDR and HDR * luminances in linear space, and the hdr ratio to encode against. */ -uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata); -uint8_t encodeRecovery(float y_sdr, float y_hdr, jr_metadata_ptr metadata, - float log2MinContentBoost, float log2MaxContentBoost); +uint8_t encodeGain(float y_sdr, float y_hdr, jr_metadata_ptr metadata); +uint8_t encodeGain(float y_sdr, float y_hdr, jr_metadata_ptr metadata, + float log2MinContentBoost, float log2MaxContentBoost); /* - * Calculates the linear luminance in nits after applying the given recovery + * Calculates the linear luminance in nits after applying the given gain * value, with the given hdr ratio, to the given sdr input in the range [0, 1]. */ -Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata); -Color applyRecovery(Color e, float recovery, jr_metadata_ptr metadata, float displayBoost); -Color applyRecoveryLUT(Color e, float recovery, RecoveryLUT& recoveryLUT); +Color applyGain(Color e, float gain, jr_metadata_ptr metadata); +Color applyGain(Color e, float gain, jr_metadata_ptr metadata, float displayBoost); +Color applyGainLUT(Color e, float gain, GainLUT& gainLUT); /* * Helper for sampling from YUV 420 images. @@ -405,7 +405,7 @@ Color sampleYuv420(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, s Color sampleP010(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y); /* - * Sample the recovery value for the map from a given x,y coordinate on a scale + * Sample the gain value for the map from a given x,y coordinate on a scale * that is map scale factor larger than the map size. */ float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y); diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h index afec0655b3..ce7b33b495 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h @@ -58,14 +58,14 @@ struct jpegr_info_struct { }; /* - * Holds information for uncompressed image or recovery map. + * Holds information for uncompressed image or gain map. */ struct jpegr_uncompressed_struct { // Pointer to the data location. void* data; - // Width of the recovery map or the luma plane of the image in pixels. + // Width of the gain map or the luma plane of the image in pixels. int width; - // Height of the recovery map or the luma plane of the image in pixels. + // Height of the gain map or the luma plane of the image in pixels. int height; // Color gamut. jpegr_color_gamut colorGamut; @@ -86,7 +86,7 @@ struct jpegr_uncompressed_struct { }; /* - * Holds information for compressed image or recovery map. + * Holds information for compressed image or gain map. */ struct jpegr_compressed_struct { // Pointer to the data location. @@ -110,7 +110,7 @@ struct jpegr_exif_struct { }; /* - * Holds information for recovery map related metadata. + * Holds information for gain map related metadata. */ struct jpegr_metadata_struct { // JPEG/R version @@ -135,8 +135,8 @@ public: * Encode API-0 * Compress JPEGR image from 10-bit HDR YUV. * - * Tonemap the HDR input to a SDR image, generate recovery map from the HDR and SDR images, - * compress SDR YUV to 8-bit JPEG and append the recovery map to the end of the compressed + * Tonemap the HDR input to a SDR image, generate gain map from the HDR and SDR images, + * compress SDR YUV to 8-bit JPEG and append the gain map to the end of the compressed * JPEG. * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param hdr_tf transfer function of the HDR image @@ -156,8 +156,8 @@ public: * Encode API-1 * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. * - * Generate recovery map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append - * the recovery map to the end of the compressed JPEG. HDR and SDR inputs must be the same + * Generate gain map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append + * the gain map to the end of the compressed JPEG. HDR and SDR inputs must be the same * resolution. * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format @@ -181,7 +181,7 @@ public: * * This method requires HAL Hardware JPEG encoder. * - * Generate recovery map from the HDR and SDR inputs, append the recovery map to the end of the + * Generate gain map from the HDR and SDR inputs, append the gain map to the end of the * compressed JPEG. HDR and SDR inputs must be the same resolution and color space. * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format @@ -204,8 +204,8 @@ public: * * This method requires HAL Hardware JPEG encoder. * - * Decode the compressed 8-bit JPEG image to YUV SDR, generate recovery map from the HDR input - * and the decoded SDR result, append the recovery map to the end of the compressed JPEG. HDR + * Decode the compressed 8-bit JPEG image to YUV SDR, generate gain map from the HDR input + * and the decoded SDR result, append the gain map to the end of the compressed JPEG. HDR * and SDR inputs must be the same resolution. * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param compressed_jpeg_image compressed 8-bit JPEG image @@ -242,9 +242,9 @@ public: ---------------------------------------------------------------------- | JPEGR_OUTPUT_HDR_HLG | RGBA_1010102 HLG | ---------------------------------------------------------------------- - * @param recovery_map destination of the decoded recovery map. The default value is NULL where + * @param gain_map destination of the decoded gain map. The default value is NULL where the decoder will do nothing about it. If configured not NULL the decoder - will write the decoded recovery_map data into this structure. The format + will write the decoded gain_map data into this structure. The format is defined in {@code jpegr_uncompressed_struct}. * @param metadata destination of the decoded metadata. The default value is NULL where the decoder will do nothing about it. If configured not NULL the decoder will @@ -257,7 +257,7 @@ public: float max_display_boost = FLT_MAX, jr_exif_ptr exif = nullptr, jpegr_output_format output_format = JPEGR_OUTPUT_HDR_LINEAR, - jr_uncompressed_ptr recovery_map = nullptr, + jr_uncompressed_ptr gain_map = nullptr, jr_metadata_ptr metadata = nullptr); /* @@ -274,30 +274,30 @@ public: protected: /* * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and - * 10-bit yuv images as input, and calculate the uncompressed recovery map. The input images + * 10-bit yuv images as input, and calculate the uncompressed gain map. The input images * must be the same resolution. * * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param hdr_tf transfer function of the HDR image - * @param dest recovery map; caller responsible for memory of data + * @param dest gain map; caller responsible for memory of data * @param metadata max_content_boost is filled in * @return NO_ERROR if calculation succeeds, error code if error occurs. */ - status_t generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, - jr_uncompressed_ptr uncompressed_p010_image, - jpegr_transfer_function hdr_tf, - jr_metadata_ptr metadata, - jr_uncompressed_ptr dest); + status_t generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, + jr_uncompressed_ptr uncompressed_p010_image, + jpegr_transfer_function hdr_tf, + jr_metadata_ptr metadata, + jr_uncompressed_ptr dest); /* * This method is called in the decoding pipeline. It will take the uncompressed (decoded) - * 8-bit yuv image, the uncompressed (decoded) recovery map, and extracted JPEG/R metadata as + * 8-bit yuv image, the uncompressed (decoded) gain map, and extracted JPEG/R metadata as * input, and calculate the 10-bit recovered image. The recovered output image is the same * color gamut as the SDR image, with HLG transfer function, and is in RGBA1010102 data format. * * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format - * @param uncompressed_recovery_map uncompressed recovery map + * @param uncompressed_gain_map uncompressed gain map * @param metadata JPEG/R metadata extracted from XMP. * @param output_format flag for setting output color format. if set to * {@code JPEGR_OUTPUT_SDR}, decoder will only decode the primary image @@ -306,67 +306,67 @@ protected: * @param dest reconstructed HDR image * @return NO_ERROR if calculation succeeds, error code if error occurs. */ - status_t applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, - jr_uncompressed_ptr uncompressed_recovery_map, - jr_metadata_ptr metadata, - jpegr_output_format output_format, - float max_display_boost, - jr_uncompressed_ptr dest); + status_t applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, + jr_uncompressed_ptr uncompressed_gain_map, + jr_metadata_ptr metadata, + jpegr_output_format output_format, + float max_display_boost, + jr_uncompressed_ptr dest); private: /* - * This method is called in the encoding pipeline. It will encode the recovery map. + * This method is called in the encoding pipeline. It will encode the gain map. * - * @param uncompressed_recovery_map uncompressed recovery map + * @param uncompressed_gain_map uncompressed gain map * @param dest encoded recover map * @return NO_ERROR if encoding succeeds, error code if error occurs. */ - status_t compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, - jr_compressed_ptr dest); + status_t compressGainMap(jr_uncompressed_ptr uncompressed_gain_map, + jr_compressed_ptr dest); /* - * This methoud is called to separate primary image and recovery map image from JPEGR + * This methoud is called to separate primary image and gain map image from JPEGR * * @param compressed_jpegr_image compressed JPEGR image * @param primary_image destination of primary image - * @param recovery_map destination of compressed recovery map + * @param gain_map destination of compressed gain map * @return NO_ERROR if calculation succeeds, error code if error occurs. */ - status_t extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image, - jr_compressed_ptr primary_image, - jr_compressed_ptr recovery_map); + status_t extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr_image, + jr_compressed_ptr primary_image, + jr_compressed_ptr gain_map); /* * This method is called in the decoding pipeline. It will read XMP metadata to find the start - * position of the compressed recovery map, and will extract the compressed recovery map. + * position of the compressed gain map, and will extract the compressed gain map. * * @param compressed_jpegr_image compressed JPEGR image - * @param dest destination of compressed recovery map + * @param dest destination of compressed gain map * @return NO_ERROR if calculation succeeds, error code if error occurs. */ - status_t extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image, - jr_compressed_ptr dest); + status_t extractGainMap(jr_compressed_ptr compressed_jpegr_image, + jr_compressed_ptr dest); /* * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image, - * the compressed recovery map and optionally the exif package as inputs, and generate the XMP + * the compressed gain map and optionally the exif package as inputs, and generate the XMP * metadata, and finally append everything in the order of: - * SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, recovery map + * SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, gain map * Note that EXIF package is only available for encoding API-0 and API-1. For encoding API-2 and * API-3 this parameter is null, but the primary image in JPEG/R may still have EXIF as long as * the input JPEG has EXIF. * * @param compressed_jpeg_image compressed 8-bit JPEG image - * @param compress_recovery_map compressed recover map + * @param compress_gain_map compressed recover map * @param (nullable) exif EXIF package * @param metadata JPEG/R metadata to encode in XMP of the jpeg * @param dest compressed JPEGR image * @return NO_ERROR if calculation succeeds, error code if error occurs. */ - status_t appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, - jr_compressed_ptr compressed_recovery_map, - jr_exif_ptr exif, - jr_metadata_ptr metadata, - jr_compressed_ptr dest); + status_t appendGainMap(jr_compressed_ptr compressed_jpeg_image, + jr_compressed_ptr compressed_gain_map, + jr_exif_ptr exif, + jr_metadata_ptr metadata, + jr_compressed_ptr dest); /* * This method will tone map a HDR image to an SDR image. diff --git a/libs/jpegrecoverymap/jpegr.cpp b/libs/jpegrecoverymap/jpegr.cpp index cdf685e005..2590f63623 100644 --- a/libs/jpegrecoverymap/jpegr.cpp +++ b/libs/jpegrecoverymap/jpegr.cpp @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include #include @@ -50,7 +50,7 @@ namespace android::jpegrecoverymap { #define USE_PQ_OETF_LUT 1 #define USE_HLG_INVOETF_LUT 1 #define USE_PQ_INVOETF_LUT 1 -#define USE_APPLY_RECOVERY_LUT 1 +#define USE_APPLY_GAIN_LUT 1 #define JPEGR_CHECK(x) \ { \ @@ -69,7 +69,7 @@ static const size_t kMapDimensionScaleFactor = 4; // JPEG encoding / decoding will require 8 x 8 DCT transform. // Width must be 8 dividable, and height must be 2 dividable. static const size_t kJpegBlock = 8; -// JPEG compress quality (0 ~ 100) for recovery map +// JPEG compress quality (0 ~ 100) for gain map static const int kMapCompressQuality = 85; #define CONFIG_MULTITHREAD 1 @@ -163,7 +163,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image)); jpegr_uncompressed_struct map; - JPEGR_CHECK(generateRecoveryMap( + JPEGR_CHECK(generateGainMap( &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map)); std::unique_ptr map_data; map_data.reset(reinterpret_cast(map.data)); @@ -172,7 +172,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, compressed_map.maxLength = map.width * map.height; unique_ptr compressed_map_data = make_unique(compressed_map.maxLength); compressed_map.data = compressed_map_data.get(); - JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); + JPEGR_CHECK(compressGainMap(&map, &compressed_map)); sp icc = IccHelper::writeIccProfile(JPEGR_TF_SRGB, uncompressed_yuv_420_image.colorGamut); @@ -188,7 +188,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpeg.data = jpeg_encoder.getCompressedImagePtr(); jpeg.length = jpeg_encoder.getCompressedImageSize(); - JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, exif, &metadata, dest)); + JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, &metadata, dest)); return NO_ERROR; } @@ -219,7 +219,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, metadata.version = kJpegrVersion; jpegr_uncompressed_struct map; - JPEGR_CHECK(generateRecoveryMap( + JPEGR_CHECK(generateGainMap( uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map)); std::unique_ptr map_data; map_data.reset(reinterpret_cast(map.data)); @@ -228,7 +228,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, compressed_map.maxLength = map.width * map.height; unique_ptr compressed_map_data = make_unique(compressed_map.maxLength); compressed_map.data = compressed_map_data.get(); - JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); + JPEGR_CHECK(compressGainMap(&map, &compressed_map)); sp icc = IccHelper::writeIccProfile(JPEGR_TF_SRGB, uncompressed_yuv_420_image->colorGamut); @@ -244,7 +244,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpeg.data = jpeg_encoder.getCompressedImagePtr(); jpeg.length = jpeg_encoder.getCompressedImageSize(); - JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, exif, &metadata, dest)); + JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, &metadata, dest)); return NO_ERROR; } @@ -271,7 +271,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, metadata.version = kJpegrVersion; jpegr_uncompressed_struct map; - JPEGR_CHECK(generateRecoveryMap( + JPEGR_CHECK(generateGainMap( uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map)); std::unique_ptr map_data; map_data.reset(reinterpret_cast(map.data)); @@ -280,9 +280,9 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, compressed_map.maxLength = map.width * map.height; unique_ptr compressed_map_data = make_unique(compressed_map.maxLength); compressed_map.data = compressed_map_data.get(); - JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); + JPEGR_CHECK(compressGainMap(&map, &compressed_map)); - JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest)); + JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest)); return NO_ERROR; } @@ -322,7 +322,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, metadata.version = kJpegrVersion; jpegr_uncompressed_struct map; - JPEGR_CHECK(generateRecoveryMap( + JPEGR_CHECK(generateGainMap( &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map)); std::unique_ptr map_data; map_data.reset(reinterpret_cast(map.data)); @@ -331,9 +331,9 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, compressed_map.maxLength = map.width * map.height; unique_ptr compressed_map_data = make_unique(compressed_map.maxLength); compressed_map.data = compressed_map_data.get(); - JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); + JPEGR_CHECK(compressGainMap(&map, &compressed_map)); - JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest)); + JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest)); return NO_ERROR; } @@ -343,9 +343,9 @@ status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_p return ERROR_JPEGR_INVALID_NULL_PTR; } - jpegr_compressed_struct primary_image, recovery_map; - JPEGR_CHECK(extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, - &primary_image, &recovery_map)); + jpegr_compressed_struct primary_image, gain_map; + JPEGR_CHECK(extractPrimaryImageAndGainMap(compressed_jpegr_image, + &primary_image, &gain_map)); JpegDecoderHelper jpeg_decoder; if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length, @@ -363,7 +363,7 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, float max_display_boost, jr_exif_ptr exif, jpegr_output_format output_format, - jr_uncompressed_ptr recovery_map, + jr_uncompressed_ptr gain_map, jr_metadata_ptr metadata) { if (compressed_jpegr_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; @@ -388,7 +388,7 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, dest->width = uncompressed_rgba_image.width; dest->height = uncompressed_rgba_image.height; - if (recovery_map == nullptr && exif == nullptr) { + if (gain_map == nullptr && exif == nullptr) { return NO_ERROR; } @@ -402,30 +402,30 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, memcpy(exif->data, jpeg_decoder.getEXIFPtr(), jpeg_decoder.getEXIFSize()); exif->length = jpeg_decoder.getEXIFSize(); } - if (recovery_map == nullptr) { + if (gain_map == nullptr) { return NO_ERROR; } } jpegr_compressed_struct compressed_map; - JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map)); + JPEGR_CHECK(extractGainMap(compressed_jpegr_image, &compressed_map)); - JpegDecoderHelper recovery_map_decoder; - if (!recovery_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) { + JpegDecoderHelper gain_map_decoder; + if (!gain_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) { return ERROR_JPEGR_DECODE_ERROR; } - if (recovery_map != nullptr) { - recovery_map->width = recovery_map_decoder.getDecompressedImageWidth(); - recovery_map->height = recovery_map_decoder.getDecompressedImageHeight(); - int size = recovery_map->width * recovery_map->height; - recovery_map->data = malloc(size); - memcpy(recovery_map->data, recovery_map_decoder.getDecompressedImagePtr(), size); + if (gain_map != nullptr) { + gain_map->width = gain_map_decoder.getDecompressedImageWidth(); + gain_map->height = gain_map_decoder.getDecompressedImageHeight(); + int size = gain_map->width * gain_map->height; + gain_map->data = malloc(size); + memcpy(gain_map->data, gain_map_decoder.getDecompressedImagePtr(), size); } jpegr_metadata_struct jr_metadata; - if (!getMetadataFromXMP(static_cast(recovery_map_decoder.getXMPPtr()), - recovery_map_decoder.getXMPSize(), &jr_metadata)) { + if (!getMetadataFromXMP(static_cast(gain_map_decoder.getXMPPtr()), + gain_map_decoder.getXMPSize(), &jr_metadata)) { return ERROR_JPEGR_DECODE_ERROR; } @@ -456,30 +456,30 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, } jpegr_uncompressed_struct map; - map.data = recovery_map_decoder.getDecompressedImagePtr(); - map.width = recovery_map_decoder.getDecompressedImageWidth(); - map.height = recovery_map_decoder.getDecompressedImageHeight(); + map.data = gain_map_decoder.getDecompressedImagePtr(); + map.width = gain_map_decoder.getDecompressedImageWidth(); + map.height = gain_map_decoder.getDecompressedImageHeight(); jpegr_uncompressed_struct uncompressed_yuv_420_image; uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr(); uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth(); uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight(); - JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &jr_metadata, output_format, - max_display_boost, dest)); + JPEGR_CHECK(applyGainMap(&uncompressed_yuv_420_image, &map, &jr_metadata, output_format, + max_display_boost, dest)); return NO_ERROR; } -status_t JpegR::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, - jr_compressed_ptr dest) { - if (uncompressed_recovery_map == nullptr || dest == nullptr) { +status_t JpegR::compressGainMap(jr_uncompressed_ptr uncompressed_gain_map, + jr_compressed_ptr dest) { + if (uncompressed_gain_map == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } JpegEncoderHelper jpeg_encoder; - if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data, - uncompressed_recovery_map->width, - uncompressed_recovery_map->height, + if (!jpeg_encoder.compressImage(uncompressed_gain_map->data, + uncompressed_gain_map->width, + uncompressed_gain_map->height, kMapCompressQuality, nullptr, 0, @@ -554,11 +554,11 @@ void JobQueue::reset() { mQueuedAllJobs = false; } -status_t JpegR::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, - jr_uncompressed_ptr uncompressed_p010_image, - jpegr_transfer_function hdr_tf, - jr_metadata_ptr metadata, - jr_uncompressed_ptr dest) { +status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, + jr_uncompressed_ptr uncompressed_p010_image, + jpegr_transfer_function hdr_tf, + jr_metadata_ptr metadata, + jr_uncompressed_ptr dest) { if (uncompressed_yuv_420_image == nullptr || uncompressed_p010_image == nullptr || metadata == nullptr @@ -675,7 +675,7 @@ status_t JpegR::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ima size_t pixel_idx = x + y * dest_map_stride; reinterpret_cast(dest->data)[pixel_idx] = - encodeRecovery(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost); + encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost); } } } @@ -701,14 +701,14 @@ status_t JpegR::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ima return NO_ERROR; } -status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, - jr_uncompressed_ptr uncompressed_recovery_map, - jr_metadata_ptr metadata, - jpegr_output_format output_format, - float max_display_boost, - jr_uncompressed_ptr dest) { +status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, + jr_uncompressed_ptr uncompressed_gain_map, + jr_metadata_ptr metadata, + jpegr_output_format output_format, + float max_display_boost, + jr_uncompressed_ptr dest) { if (uncompressed_yuv_420_image == nullptr - || uncompressed_recovery_map == nullptr + || uncompressed_gain_map == nullptr || metadata == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; @@ -718,12 +718,12 @@ status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, dest->height = uncompressed_yuv_420_image->height; ShepardsIDW idwTable(kMapDimensionScaleFactor); float display_boost = std::min(max_display_boost, metadata->maxContentBoost); - RecoveryLUT recoveryLUT(metadata, display_boost); + GainLUT gainLUT(metadata, display_boost); JobQueue jobQueue; - std::function applyRecMap = [uncompressed_yuv_420_image, uncompressed_recovery_map, + std::function applyRecMap = [uncompressed_yuv_420_image, uncompressed_gain_map, metadata, dest, &jobQueue, &idwTable, output_format, - &recoveryLUT, display_boost]() -> void { + &gainLUT, display_boost]() -> void { size_t width = uncompressed_yuv_420_image->width; size_t height = uncompressed_yuv_420_image->height; @@ -738,22 +738,22 @@ status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, #else Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr); #endif - float recovery; + float gain; // TODO: determine map scaling factor based on actual map dims size_t map_scale_factor = kMapDimensionScaleFactor; // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following. // Currently map_scale_factor is of type size_t, but it could be changed to a float // later. if (map_scale_factor != floorf(map_scale_factor)) { - recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y); + gain = sampleMap(uncompressed_gain_map, map_scale_factor, x, y); } else { - recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y, idwTable); + gain = sampleMap(uncompressed_gain_map, map_scale_factor, x, y, idwTable); } -#if USE_APPLY_RECOVERY_LUT - Color rgb_hdr = applyRecoveryLUT(rgb_sdr, recovery, recoveryLUT); +#if USE_APPLY_GAIN_LUT + Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT); #else - Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata, display_boost); + Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost); #endif rgb_hdr = rgb_hdr / display_boost; size_t pixel_idx = x + y * width; @@ -815,9 +815,9 @@ status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, return NO_ERROR; } -status_t JpegR::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image, - jr_compressed_ptr primary_image, - jr_compressed_ptr recovery_map) { +status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr_image, + jr_compressed_ptr primary_image, + jr_compressed_ptr gain_map) { if (compressed_jpegr_image == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } @@ -855,23 +855,23 @@ status_t JpegR::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_j primary_image->length = image_ranges[0].GetLength(); } - if (recovery_map != nullptr) { - recovery_map->data = static_cast(compressed_jpegr_image->data) + + if (gain_map != nullptr) { + gain_map->data = static_cast(compressed_jpegr_image->data) + image_ranges[1].GetBegin(); - recovery_map->length = image_ranges[1].GetLength(); + gain_map->length = image_ranges[1].GetLength(); } return NO_ERROR; } -status_t JpegR::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image, - jr_compressed_ptr dest) { +status_t JpegR::extractGainMap(jr_compressed_ptr compressed_jpegr_image, + jr_compressed_ptr dest) { if (compressed_jpegr_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } - return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest); + return extractPrimaryImageAndGainMap(compressed_jpegr_image, nullptr, dest); } // JPEG/R structure: @@ -900,20 +900,20 @@ status_t JpegR::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image, // name space ("http://ns.adobe.com/xap/1.0/\0") // XMP // -// (Required) secondary image (the recovery map, without the first two bytes (SOI)) +// (Required) secondary image (the gain map, without the first two bytes (SOI)) // // Metadata versions we are using: // ECMA TR-98 for JFIF marker // Exif 2.2 spec for EXIF marker // Adobe XMP spec part 3 for XMP marker // ICC v4.3 spec for ICC -status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, - jr_compressed_ptr compressed_recovery_map, - jr_exif_ptr exif, - jr_metadata_ptr metadata, - jr_compressed_ptr dest) { +status_t JpegR::appendGainMap(jr_compressed_ptr compressed_jpeg_image, + jr_compressed_ptr compressed_gain_map, + jr_exif_ptr exif, + jr_metadata_ptr metadata, + jr_compressed_ptr dest) { if (compressed_jpeg_image == nullptr - || compressed_recovery_map == nullptr + || compressed_gain_map == nullptr || metadata == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; @@ -930,7 +930,7 @@ status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, + xmp_secondary.size(); /* length of xmp packet */ const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */ + xmp_secondary_length - + compressed_recovery_map->length; + + compressed_gain_map->length; // primary image const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size); // same as primary @@ -994,7 +994,7 @@ status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos)); // Finish primary image - // Begin secondary image (recovery map) + // Begin secondary image (gain map) // Write SOI JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos)); @@ -1014,7 +1014,7 @@ status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, // Write secondary image JPEGR_CHECK(Write(dest, - (uint8_t*)compressed_recovery_map->data + 2, compressed_recovery_map->length - 2, pos)); + (uint8_t*)compressed_gain_map->data + 2, compressed_gain_map->length - 2, pos)); // Set back length dest->length = pos; diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp index d5da7fb646..59b1237a68 100644 --- a/libs/jpegrecoverymap/tests/Android.bp +++ b/libs/jpegrecoverymap/tests/Android.bp @@ -26,7 +26,7 @@ cc_test { test_suites: ["device-tests"], srcs: [ "jpegr_test.cpp", - "recoverymapmath_test.cpp", + "gainmapmath_test.cpp", ], shared_libs: [ "libimage_io", diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/gainmapmath_test.cpp similarity index 72% rename from libs/jpegrecoverymap/tests/recoverymapmath_test.cpp rename to libs/jpegrecoverymap/tests/gainmapmath_test.cpp index 2369a7e4e7..21de2e6c22 100644 --- a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp +++ b/libs/jpegrecoverymap/tests/gainmapmath_test.cpp @@ -17,14 +17,14 @@ #include #include #include -#include +#include namespace android::jpegrecoverymap { -class RecoveryMapMathTest : public testing::Test { +class GainMapMathTest : public testing::Test { public: - RecoveryMapMathTest(); - ~RecoveryMapMathTest(); + GainMapMathTest(); + ~GainMapMathTest(); float ComparisonEpsilon() { return 1e-4f; } float LuminanceEpsilon() { return 1e-2f; } @@ -88,10 +88,10 @@ public: return luminance_scaled * scale_factor; } - Color Recover(Color yuv_gamma, float recovery, jr_metadata_ptr metadata) { + Color Recover(Color yuv_gamma, float gain, jr_metadata_ptr metadata) { Color rgb_gamma = srgbYuvToRgb(yuv_gamma); Color rgb = srgbInvOetf(rgb_gamma); - return applyRecovery(rgb, recovery, metadata); + return applyGain(rgb, gain, metadata); } jpegr_uncompressed_struct Yuv420Image() { @@ -193,11 +193,11 @@ protected: virtual void TearDown(); }; -RecoveryMapMathTest::RecoveryMapMathTest() {} -RecoveryMapMathTest::~RecoveryMapMathTest() {} +GainMapMathTest::GainMapMathTest() {} +GainMapMathTest::~GainMapMathTest() {} -void RecoveryMapMathTest::SetUp() {} -void RecoveryMapMathTest::TearDown() {} +void GainMapMathTest::SetUp() {} +void GainMapMathTest::TearDown() {} #define EXPECT_RGB_EQ(e1, e2) \ EXPECT_FLOAT_EQ((e1).r, (e2).r); \ @@ -231,7 +231,7 @@ void RecoveryMapMathTest::TearDown() {} // TODO: a bunch of these tests can be parameterized. -TEST_F(RecoveryMapMathTest, ColorConstruct) { +TEST_F(GainMapMathTest, ColorConstruct) { Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; EXPECT_FLOAT_EQ(e1.r, 0.1f); @@ -243,7 +243,7 @@ TEST_F(RecoveryMapMathTest, ColorConstruct) { EXPECT_FLOAT_EQ(e1.v, 0.3f); } -TEST_F(RecoveryMapMathTest, ColorAddColor) { +TEST_F(GainMapMathTest, ColorAddColor) { Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; Color e2 = e1 + e1; @@ -257,7 +257,7 @@ TEST_F(RecoveryMapMathTest, ColorAddColor) { EXPECT_FLOAT_EQ(e2.b, e1.b * 3.0f); } -TEST_F(RecoveryMapMathTest, ColorAddFloat) { +TEST_F(GainMapMathTest, ColorAddFloat) { Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; Color e2 = e1 + 0.1f; @@ -271,7 +271,7 @@ TEST_F(RecoveryMapMathTest, ColorAddFloat) { EXPECT_FLOAT_EQ(e2.b, e1.b + 0.2f); } -TEST_F(RecoveryMapMathTest, ColorSubtractColor) { +TEST_F(GainMapMathTest, ColorSubtractColor) { Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; Color e2 = e1 - e1; @@ -285,7 +285,7 @@ TEST_F(RecoveryMapMathTest, ColorSubtractColor) { EXPECT_FLOAT_EQ(e2.b, -e1.b); } -TEST_F(RecoveryMapMathTest, ColorSubtractFloat) { +TEST_F(GainMapMathTest, ColorSubtractFloat) { Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; Color e2 = e1 - 0.1f; @@ -299,7 +299,7 @@ TEST_F(RecoveryMapMathTest, ColorSubtractFloat) { EXPECT_FLOAT_EQ(e2.b, e1.b - 0.2f); } -TEST_F(RecoveryMapMathTest, ColorMultiplyFloat) { +TEST_F(GainMapMathTest, ColorMultiplyFloat) { Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; Color e2 = e1 * 2.0f; @@ -313,7 +313,7 @@ TEST_F(RecoveryMapMathTest, ColorMultiplyFloat) { EXPECT_FLOAT_EQ(e2.b, e1.b * 4.0f); } -TEST_F(RecoveryMapMathTest, ColorDivideFloat) { +TEST_F(GainMapMathTest, ColorDivideFloat) { Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; Color e2 = e1 / 2.0f; @@ -327,7 +327,7 @@ TEST_F(RecoveryMapMathTest, ColorDivideFloat) { EXPECT_FLOAT_EQ(e2.b, e1.b / 4.0f); } -TEST_F(RecoveryMapMathTest, SrgbLuminance) { +TEST_F(GainMapMathTest, SrgbLuminance) { EXPECT_FLOAT_EQ(srgbLuminance(RgbBlack()), 0.0f); EXPECT_FLOAT_EQ(srgbLuminance(RgbWhite()), 1.0f); EXPECT_FLOAT_EQ(srgbLuminance(RgbRed()), 0.2126f); @@ -335,7 +335,7 @@ TEST_F(RecoveryMapMathTest, SrgbLuminance) { EXPECT_FLOAT_EQ(srgbLuminance(RgbBlue()), 0.0722f); } -TEST_F(RecoveryMapMathTest, SrgbYuvToRgb) { +TEST_F(GainMapMathTest, SrgbYuvToRgb) { Color rgb_black = srgbYuvToRgb(YuvBlack()); EXPECT_RGB_NEAR(rgb_black, RgbBlack()); @@ -352,7 +352,7 @@ TEST_F(RecoveryMapMathTest, SrgbYuvToRgb) { EXPECT_RGB_NEAR(rgb_b, RgbBlue()); } -TEST_F(RecoveryMapMathTest, SrgbRgbToYuv) { +TEST_F(GainMapMathTest, SrgbRgbToYuv) { Color yuv_black = srgbRgbToYuv(RgbBlack()); EXPECT_YUV_NEAR(yuv_black, YuvBlack()); @@ -369,7 +369,7 @@ TEST_F(RecoveryMapMathTest, SrgbRgbToYuv) { EXPECT_YUV_NEAR(yuv_b, SrgbYuvBlue()); } -TEST_F(RecoveryMapMathTest, SrgbRgbYuvRoundtrip) { +TEST_F(GainMapMathTest, SrgbRgbYuvRoundtrip) { Color rgb_black = srgbYuvToRgb(srgbRgbToYuv(RgbBlack())); EXPECT_RGB_NEAR(rgb_black, RgbBlack()); @@ -386,7 +386,7 @@ TEST_F(RecoveryMapMathTest, SrgbRgbYuvRoundtrip) { EXPECT_RGB_NEAR(rgb_b, RgbBlue()); } -TEST_F(RecoveryMapMathTest, SrgbTransferFunction) { +TEST_F(GainMapMathTest, SrgbTransferFunction) { EXPECT_FLOAT_EQ(srgbInvOetf(0.0f), 0.0f); EXPECT_NEAR(srgbInvOetf(0.02f), 0.00154f, ComparisonEpsilon()); EXPECT_NEAR(srgbInvOetf(0.04045f), 0.00313f, ComparisonEpsilon()); @@ -394,7 +394,7 @@ TEST_F(RecoveryMapMathTest, SrgbTransferFunction) { EXPECT_FLOAT_EQ(srgbInvOetf(1.0f), 1.0f); } -TEST_F(RecoveryMapMathTest, P3Luminance) { +TEST_F(GainMapMathTest, P3Luminance) { EXPECT_FLOAT_EQ(p3Luminance(RgbBlack()), 0.0f); EXPECT_FLOAT_EQ(p3Luminance(RgbWhite()), 1.0f); EXPECT_FLOAT_EQ(p3Luminance(RgbRed()), 0.20949f); @@ -402,7 +402,7 @@ TEST_F(RecoveryMapMathTest, P3Luminance) { EXPECT_FLOAT_EQ(p3Luminance(RgbBlue()), 0.06891f); } -TEST_F(RecoveryMapMathTest, Bt2100Luminance) { +TEST_F(GainMapMathTest, Bt2100Luminance) { EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlack()), 0.0f); EXPECT_FLOAT_EQ(bt2100Luminance(RgbWhite()), 1.0f); EXPECT_FLOAT_EQ(bt2100Luminance(RgbRed()), 0.2627f); @@ -410,7 +410,7 @@ TEST_F(RecoveryMapMathTest, Bt2100Luminance) { EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlue()), 0.0593f); } -TEST_F(RecoveryMapMathTest, Bt2100YuvToRgb) { +TEST_F(GainMapMathTest, Bt2100YuvToRgb) { Color rgb_black = bt2100YuvToRgb(YuvBlack()); EXPECT_RGB_NEAR(rgb_black, RgbBlack()); @@ -427,7 +427,7 @@ TEST_F(RecoveryMapMathTest, Bt2100YuvToRgb) { EXPECT_RGB_NEAR(rgb_b, RgbBlue()); } -TEST_F(RecoveryMapMathTest, Bt2100RgbToYuv) { +TEST_F(GainMapMathTest, Bt2100RgbToYuv) { Color yuv_black = bt2100RgbToYuv(RgbBlack()); EXPECT_YUV_NEAR(yuv_black, YuvBlack()); @@ -444,7 +444,7 @@ TEST_F(RecoveryMapMathTest, Bt2100RgbToYuv) { EXPECT_YUV_NEAR(yuv_b, Bt2100YuvBlue()); } -TEST_F(RecoveryMapMathTest, Bt2100RgbYuvRoundtrip) { +TEST_F(GainMapMathTest, Bt2100RgbYuvRoundtrip) { Color rgb_black = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlack())); EXPECT_RGB_NEAR(rgb_black, RgbBlack()); @@ -461,7 +461,7 @@ TEST_F(RecoveryMapMathTest, Bt2100RgbYuvRoundtrip) { EXPECT_RGB_NEAR(rgb_b, RgbBlue()); } -TEST_F(RecoveryMapMathTest, HlgOetf) { +TEST_F(GainMapMathTest, HlgOetf) { EXPECT_FLOAT_EQ(hlgOetf(0.0f), 0.0f); EXPECT_NEAR(hlgOetf(0.04167f), 0.35357f, ComparisonEpsilon()); EXPECT_NEAR(hlgOetf(0.08333f), 0.5f, ComparisonEpsilon()); @@ -473,7 +473,7 @@ TEST_F(RecoveryMapMathTest, HlgOetf) { EXPECT_RGB_NEAR(hlgOetf(e), e_gamma); } -TEST_F(RecoveryMapMathTest, HlgInvOetf) { +TEST_F(GainMapMathTest, HlgInvOetf) { EXPECT_FLOAT_EQ(hlgInvOetf(0.0f), 0.0f); EXPECT_NEAR(hlgInvOetf(0.25f), 0.02083f, ComparisonEpsilon()); EXPECT_NEAR(hlgInvOetf(0.5f), 0.08333f, ComparisonEpsilon()); @@ -485,7 +485,7 @@ TEST_F(RecoveryMapMathTest, HlgInvOetf) { EXPECT_RGB_NEAR(hlgInvOetf(e_gamma), e); } -TEST_F(RecoveryMapMathTest, HlgTransferFunctionRoundtrip) { +TEST_F(GainMapMathTest, HlgTransferFunctionRoundtrip) { EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(0.0f)), 0.0f); EXPECT_NEAR(hlgInvOetf(hlgOetf(0.04167f)), 0.04167f, ComparisonEpsilon()); EXPECT_NEAR(hlgInvOetf(hlgOetf(0.08333f)), 0.08333f, ComparisonEpsilon()); @@ -493,7 +493,7 @@ TEST_F(RecoveryMapMathTest, HlgTransferFunctionRoundtrip) { EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(1.0f)), 1.0f); } -TEST_F(RecoveryMapMathTest, PqOetf) { +TEST_F(GainMapMathTest, PqOetf) { EXPECT_FLOAT_EQ(pqOetf(0.0f), 0.0f); EXPECT_NEAR(pqOetf(0.01f), 0.50808f, ComparisonEpsilon()); EXPECT_NEAR(pqOetf(0.5f), 0.92655f, ComparisonEpsilon()); @@ -505,7 +505,7 @@ TEST_F(RecoveryMapMathTest, PqOetf) { EXPECT_RGB_NEAR(pqOetf(e), e_gamma); } -TEST_F(RecoveryMapMathTest, PqInvOetf) { +TEST_F(GainMapMathTest, PqInvOetf) { EXPECT_FLOAT_EQ(pqInvOetf(0.0f), 0.0f); EXPECT_NEAR(pqInvOetf(0.01f), 2.31017e-7f, ComparisonEpsilon()); EXPECT_NEAR(pqInvOetf(0.5f), 0.00922f, ComparisonEpsilon()); @@ -517,99 +517,99 @@ TEST_F(RecoveryMapMathTest, PqInvOetf) { EXPECT_RGB_NEAR(pqInvOetf(e_gamma), e); } -TEST_F(RecoveryMapMathTest, PqInvOetfLUT) { +TEST_F(GainMapMathTest, PqInvOetfLUT) { for (int idx = 0; idx < kPqInvOETFNumEntries; idx++) { float value = static_cast(idx) / static_cast(kPqInvOETFNumEntries - 1); EXPECT_FLOAT_EQ(pqInvOetf(value), pqInvOetfLUT(value)); } } -TEST_F(RecoveryMapMathTest, HlgInvOetfLUT) { +TEST_F(GainMapMathTest, HlgInvOetfLUT) { for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++) { float value = static_cast(idx) / static_cast(kHlgInvOETFNumEntries - 1); EXPECT_FLOAT_EQ(hlgInvOetf(value), hlgInvOetfLUT(value)); } } -TEST_F(RecoveryMapMathTest, pqOetfLUT) { +TEST_F(GainMapMathTest, pqOetfLUT) { for (int idx = 0; idx < kPqOETFNumEntries; idx++) { float value = static_cast(idx) / static_cast(kPqOETFNumEntries - 1); EXPECT_FLOAT_EQ(pqOetf(value), pqOetfLUT(value)); } } -TEST_F(RecoveryMapMathTest, hlgOetfLUT) { +TEST_F(GainMapMathTest, hlgOetfLUT) { for (int idx = 0; idx < kHlgOETFNumEntries; idx++) { float value = static_cast(idx) / static_cast(kHlgOETFNumEntries - 1); EXPECT_FLOAT_EQ(hlgOetf(value), hlgOetfLUT(value)); } } -TEST_F(RecoveryMapMathTest, srgbInvOetfLUT) { +TEST_F(GainMapMathTest, srgbInvOetfLUT) { for (int idx = 0; idx < kSrgbInvOETFNumEntries; idx++) { float value = static_cast(idx) / static_cast(kSrgbInvOETFNumEntries - 1); EXPECT_FLOAT_EQ(srgbInvOetf(value), srgbInvOetfLUT(value)); } } -TEST_F(RecoveryMapMathTest, applyRecoveryLUT) { +TEST_F(GainMapMathTest, applyGainLUT) { for (int boost = 1; boost <= 10; boost++) { jpegr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), .minContentBoost = 1.0f / static_cast(boost) }; - RecoveryLUT recoveryLUT(&metadata); - RecoveryLUT recoveryLUTWithBoost(&metadata, metadata.maxContentBoost); - for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kRecoveryFactorNumEntries - 1); - EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata), - applyRecoveryLUT(RgbBlack(), value, recoveryLUT)); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, &metadata), - applyRecoveryLUT(RgbWhite(), value, recoveryLUT)); - EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, &metadata), - applyRecoveryLUT(RgbRed(), value, recoveryLUT)); - EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, &metadata), - applyRecoveryLUT(RgbGreen(), value, recoveryLUT)); - EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata), - applyRecoveryLUT(RgbBlue(), value, recoveryLUT)); - EXPECT_RGB_EQ(applyRecoveryLUT(RgbBlack(), value, recoveryLUT), - applyRecoveryLUT(RgbBlack(), value, recoveryLUTWithBoost)); - EXPECT_RGB_EQ(applyRecoveryLUT(RgbWhite(), value, recoveryLUT), - applyRecoveryLUT(RgbWhite(), value, recoveryLUTWithBoost)); - EXPECT_RGB_EQ(applyRecoveryLUT(RgbRed(), value, recoveryLUT), - applyRecoveryLUT(RgbRed(), value, recoveryLUTWithBoost)); - EXPECT_RGB_EQ(applyRecoveryLUT(RgbGreen(), value, recoveryLUT), - applyRecoveryLUT(RgbGreen(), value, recoveryLUTWithBoost)); - EXPECT_RGB_EQ(applyRecoveryLUT(RgbBlue(), value, recoveryLUT), - applyRecoveryLUT(RgbBlue(), value, recoveryLUTWithBoost)); + GainLUT gainLUT(&metadata); + GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost); + for (int idx = 0; idx < kGainFactorNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); + EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata), + applyGainLUT(RgbBlack(), value, gainLUT)); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata), + applyGainLUT(RgbWhite(), value, gainLUT)); + EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata), + applyGainLUT(RgbRed(), value, gainLUT)); + EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata), + applyGainLUT(RgbGreen(), value, gainLUT)); + EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata), + applyGainLUT(RgbBlue(), value, gainLUT)); + EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT), + applyGainLUT(RgbBlack(), value, gainLUTWithBoost)); + EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT), + applyGainLUT(RgbWhite(), value, gainLUTWithBoost)); + EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT), + applyGainLUT(RgbRed(), value, gainLUTWithBoost)); + EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT), + applyGainLUT(RgbGreen(), value, gainLUTWithBoost)); + EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT), + applyGainLUT(RgbBlue(), value, gainLUTWithBoost)); } } for (int boost = 1; boost <= 10; boost++) { jpegr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), .minContentBoost = 1.0f }; - RecoveryLUT recoveryLUT(&metadata); - RecoveryLUT recoveryLUTWithBoost(&metadata, metadata.maxContentBoost); - for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kRecoveryFactorNumEntries - 1); - EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata), - applyRecoveryLUT(RgbBlack(), value, recoveryLUT)); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, &metadata), - applyRecoveryLUT(RgbWhite(), value, recoveryLUT)); - EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, &metadata), - applyRecoveryLUT(RgbRed(), value, recoveryLUT)); - EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, &metadata), - applyRecoveryLUT(RgbGreen(), value, recoveryLUT)); - EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata), - applyRecoveryLUT(RgbBlue(), value, recoveryLUT)); - EXPECT_RGB_EQ(applyRecoveryLUT(RgbBlack(), value, recoveryLUT), - applyRecoveryLUT(RgbBlack(), value, recoveryLUTWithBoost)); - EXPECT_RGB_EQ(applyRecoveryLUT(RgbWhite(), value, recoveryLUT), - applyRecoveryLUT(RgbWhite(), value, recoveryLUTWithBoost)); - EXPECT_RGB_EQ(applyRecoveryLUT(RgbRed(), value, recoveryLUT), - applyRecoveryLUT(RgbRed(), value, recoveryLUTWithBoost)); - EXPECT_RGB_EQ(applyRecoveryLUT(RgbGreen(), value, recoveryLUT), - applyRecoveryLUT(RgbGreen(), value, recoveryLUTWithBoost)); - EXPECT_RGB_EQ(applyRecoveryLUT(RgbBlue(), value, recoveryLUT), - applyRecoveryLUT(RgbBlue(), value, recoveryLUTWithBoost)); + GainLUT gainLUT(&metadata); + GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost); + for (int idx = 0; idx < kGainFactorNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); + EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata), + applyGainLUT(RgbBlack(), value, gainLUT)); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata), + applyGainLUT(RgbWhite(), value, gainLUT)); + EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata), + applyGainLUT(RgbRed(), value, gainLUT)); + EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata), + applyGainLUT(RgbGreen(), value, gainLUT)); + EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata), + applyGainLUT(RgbBlue(), value, gainLUT)); + EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT), + applyGainLUT(RgbBlack(), value, gainLUTWithBoost)); + EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT), + applyGainLUT(RgbWhite(), value, gainLUTWithBoost)); + EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT), + applyGainLUT(RgbRed(), value, gainLUTWithBoost)); + EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT), + applyGainLUT(RgbGreen(), value, gainLUTWithBoost)); + EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT), + applyGainLUT(RgbBlue(), value, gainLUTWithBoost)); } } @@ -617,35 +617,35 @@ TEST_F(RecoveryMapMathTest, applyRecoveryLUT) { jpegr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), .minContentBoost = 1.0f / pow(static_cast(boost), 1.0f / 3.0f) }; - RecoveryLUT recoveryLUT(&metadata); - RecoveryLUT recoveryLUTWithBoost(&metadata, metadata.maxContentBoost); - for (int idx = 0; idx < kRecoveryFactorNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kRecoveryFactorNumEntries - 1); - EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), value, &metadata), - applyRecoveryLUT(RgbBlack(), value, recoveryLUT)); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), value, &metadata), - applyRecoveryLUT(RgbWhite(), value, recoveryLUT)); - EXPECT_RGB_NEAR(applyRecovery(RgbRed(), value, &metadata), - applyRecoveryLUT(RgbRed(), value, recoveryLUT)); - EXPECT_RGB_NEAR(applyRecovery(RgbGreen(), value, &metadata), - applyRecoveryLUT(RgbGreen(), value, recoveryLUT)); - EXPECT_RGB_NEAR(applyRecovery(RgbBlue(), value, &metadata), - applyRecoveryLUT(RgbBlue(), value, recoveryLUT)); - EXPECT_RGB_EQ(applyRecoveryLUT(RgbBlack(), value, recoveryLUT), - applyRecoveryLUT(RgbBlack(), value, recoveryLUTWithBoost)); - EXPECT_RGB_EQ(applyRecoveryLUT(RgbWhite(), value, recoveryLUT), - applyRecoveryLUT(RgbWhite(), value, recoveryLUTWithBoost)); - EXPECT_RGB_EQ(applyRecoveryLUT(RgbRed(), value, recoveryLUT), - applyRecoveryLUT(RgbRed(), value, recoveryLUTWithBoost)); - EXPECT_RGB_EQ(applyRecoveryLUT(RgbGreen(), value, recoveryLUT), - applyRecoveryLUT(RgbGreen(), value, recoveryLUTWithBoost)); - EXPECT_RGB_EQ(applyRecoveryLUT(RgbBlue(), value, recoveryLUT), - applyRecoveryLUT(RgbBlue(), value, recoveryLUTWithBoost)); + GainLUT gainLUT(&metadata); + GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost); + for (int idx = 0; idx < kGainFactorNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); + EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata), + applyGainLUT(RgbBlack(), value, gainLUT)); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata), + applyGainLUT(RgbWhite(), value, gainLUT)); + EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata), + applyGainLUT(RgbRed(), value, gainLUT)); + EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata), + applyGainLUT(RgbGreen(), value, gainLUT)); + EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata), + applyGainLUT(RgbBlue(), value, gainLUT)); + EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT), + applyGainLUT(RgbBlack(), value, gainLUTWithBoost)); + EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT), + applyGainLUT(RgbWhite(), value, gainLUTWithBoost)); + EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT), + applyGainLUT(RgbRed(), value, gainLUTWithBoost)); + EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT), + applyGainLUT(RgbGreen(), value, gainLUTWithBoost)); + EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT), + applyGainLUT(RgbBlue(), value, gainLUTWithBoost)); } } } -TEST_F(RecoveryMapMathTest, PqTransferFunctionRoundtrip) { +TEST_F(GainMapMathTest, PqTransferFunctionRoundtrip) { EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(0.0f)), 0.0f); EXPECT_NEAR(pqInvOetf(pqOetf(0.01f)), 0.01f, ComparisonEpsilon()); EXPECT_NEAR(pqInvOetf(pqOetf(0.5f)), 0.5f, ComparisonEpsilon()); @@ -653,7 +653,7 @@ TEST_F(RecoveryMapMathTest, PqTransferFunctionRoundtrip) { EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(1.0f)), 1.0f); } -TEST_F(RecoveryMapMathTest, ColorConversionLookup) { +TEST_F(GainMapMathTest, ColorConversionLookup) { EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_UNSPECIFIED), nullptr); EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_BT709), @@ -691,139 +691,139 @@ TEST_F(RecoveryMapMathTest, ColorConversionLookup) { nullptr); } -TEST_F(RecoveryMapMathTest, EncodeRecovery) { +TEST_F(GainMapMathTest, EncodeGain) { jpegr_metadata_struct metadata = { .maxContentBoost = 4.0f, .minContentBoost = 1.0f / 4.0f }; - EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 127); - EXPECT_EQ(encodeRecovery(0.0f, 1.0f, &metadata), 127); - EXPECT_EQ(encodeRecovery(1.0f, 0.0f, &metadata), 0); - EXPECT_EQ(encodeRecovery(0.5f, 0.0f, &metadata), 0); + EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 127); + EXPECT_EQ(encodeGain(0.0f, 1.0f, &metadata), 127); + EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0); + EXPECT_EQ(encodeGain(0.5f, 0.0f, &metadata), 0); - EXPECT_EQ(encodeRecovery(1.0f, 1.0f, &metadata), 127); - EXPECT_EQ(encodeRecovery(1.0f, 4.0f, &metadata), 255); - EXPECT_EQ(encodeRecovery(1.0f, 5.0f, &metadata), 255); - EXPECT_EQ(encodeRecovery(4.0f, 1.0f, &metadata), 0); - EXPECT_EQ(encodeRecovery(4.0f, 0.5f, &metadata), 0); - EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 191); - EXPECT_EQ(encodeRecovery(2.0f, 1.0f, &metadata), 63); + EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 127); + EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 255); + EXPECT_EQ(encodeGain(1.0f, 5.0f, &metadata), 255); + EXPECT_EQ(encodeGain(4.0f, 1.0f, &metadata), 0); + EXPECT_EQ(encodeGain(4.0f, 0.5f, &metadata), 0); + EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 191); + EXPECT_EQ(encodeGain(2.0f, 1.0f, &metadata), 63); metadata.maxContentBoost = 2.0f; metadata.minContentBoost = 1.0f / 2.0f; - EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 255); - EXPECT_EQ(encodeRecovery(2.0f, 1.0f, &metadata), 0); - EXPECT_EQ(encodeRecovery(1.0f, 1.41421f, &metadata), 191); - EXPECT_EQ(encodeRecovery(1.41421f, 1.0f, &metadata), 63); + EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 255); + EXPECT_EQ(encodeGain(2.0f, 1.0f, &metadata), 0); + EXPECT_EQ(encodeGain(1.0f, 1.41421f, &metadata), 191); + EXPECT_EQ(encodeGain(1.41421f, 1.0f, &metadata), 63); metadata.maxContentBoost = 8.0f; metadata.minContentBoost = 1.0f / 8.0f; - EXPECT_EQ(encodeRecovery(1.0f, 8.0f, &metadata), 255); - EXPECT_EQ(encodeRecovery(8.0f, 1.0f, &metadata), 0); - EXPECT_EQ(encodeRecovery(1.0f, 2.82843f, &metadata), 191); - EXPECT_EQ(encodeRecovery(2.82843f, 1.0f, &metadata), 63); + EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255); + EXPECT_EQ(encodeGain(8.0f, 1.0f, &metadata), 0); + EXPECT_EQ(encodeGain(1.0f, 2.82843f, &metadata), 191); + EXPECT_EQ(encodeGain(2.82843f, 1.0f, &metadata), 63); metadata.maxContentBoost = 8.0f; metadata.minContentBoost = 1.0f; - EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 0); - EXPECT_EQ(encodeRecovery(1.0f, 0.0f, &metadata), 0); + EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 0); + EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0); - EXPECT_EQ(encodeRecovery(1.0f, 1.0f, &metadata), 0); - EXPECT_EQ(encodeRecovery(1.0f, 8.0f, &metadata), 255); - EXPECT_EQ(encodeRecovery(1.0f, 4.0f, &metadata), 170); - EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 85); + EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 0); + EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255); + EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 170); + EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 85); metadata.maxContentBoost = 8.0f; metadata.minContentBoost = 0.5f; - EXPECT_EQ(encodeRecovery(0.0f, 0.0f, &metadata), 63); - EXPECT_EQ(encodeRecovery(1.0f, 0.0f, &metadata), 0); + EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 63); + EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0); - EXPECT_EQ(encodeRecovery(1.0f, 1.0f, &metadata), 63); - EXPECT_EQ(encodeRecovery(1.0f, 8.0f, &metadata), 255); - EXPECT_EQ(encodeRecovery(1.0f, 4.0f, &metadata), 191); - EXPECT_EQ(encodeRecovery(1.0f, 2.0f, &metadata), 127); - EXPECT_EQ(encodeRecovery(1.0f, 0.7071f, &metadata), 31); - EXPECT_EQ(encodeRecovery(1.0f, 0.5f, &metadata), 0); + EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 63); + EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255); + EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 191); + EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 127); + EXPECT_EQ(encodeGain(1.0f, 0.7071f, &metadata), 31); + EXPECT_EQ(encodeGain(1.0f, 0.5f, &metadata), 0); } -TEST_F(RecoveryMapMathTest, ApplyRecovery) { +TEST_F(GainMapMathTest, ApplyGain) { jpegr_metadata_struct metadata = { .maxContentBoost = 4.0f, .minContentBoost = 1.0f / 4.0f }; float displayBoost = metadata.maxContentBoost; - EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.0f, &metadata), RgbBlack()); - EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.5f, &metadata), RgbBlack()); - EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 1.0f, &metadata), RgbBlack()); + EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.0f, &metadata), RgbBlack()); + EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.5f, &metadata), RgbBlack()); + EXPECT_RGB_NEAR(applyGain(RgbBlack(), 1.0f, &metadata), RgbBlack()); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 4.0f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.0f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite()); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.0f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 4.0f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 4.0f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.0f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite()); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.0f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 4.0f); metadata.maxContentBoost = 2.0f; metadata.minContentBoost = 1.0f / 2.0f; - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite() / 1.41421f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite()); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 1.41421f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 2.0f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 1.41421f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite()); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 1.41421f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 2.0f); metadata.maxContentBoost = 8.0f; metadata.minContentBoost = 1.0f / 8.0f; - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 8.0f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.82843f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite()); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.82843f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 8.0f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.82843f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite()); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.82843f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); metadata.maxContentBoost = 8.0f; metadata.minContentBoost = 1.0f; - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite()); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite()); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); metadata.maxContentBoost = 8.0f; metadata.minContentBoost = 0.5f; - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.25f, &metadata), RgbWhite()); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, &metadata), RgbWhite() * 2.0f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.75f, &metadata), RgbWhite() * 4.0f); - EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite()); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite() * 2.0f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 4.0f); + EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); Color e = {{{ 0.0f, 0.5f, 1.0f }}}; metadata.maxContentBoost = 4.0f; metadata.minContentBoost = 1.0f / 4.0f; - EXPECT_RGB_NEAR(applyRecovery(e, 0.0f, &metadata), e / 4.0f); - EXPECT_RGB_NEAR(applyRecovery(e, 0.25f, &metadata), e / 2.0f); - EXPECT_RGB_NEAR(applyRecovery(e, 0.5f, &metadata), e); - EXPECT_RGB_NEAR(applyRecovery(e, 0.75f, &metadata), e * 2.0f); - EXPECT_RGB_NEAR(applyRecovery(e, 1.0f, &metadata), e * 4.0f); - - EXPECT_RGB_EQ(applyRecovery(RgbBlack(), 1.0f, &metadata), - applyRecovery(RgbBlack(), 1.0f, &metadata, displayBoost)); - EXPECT_RGB_EQ(applyRecovery(RgbWhite(), 1.0f, &metadata), - applyRecovery(RgbWhite(), 1.0f, &metadata, displayBoost)); - EXPECT_RGB_EQ(applyRecovery(RgbRed(), 1.0f, &metadata), - applyRecovery(RgbRed(), 1.0f, &metadata, displayBoost)); - EXPECT_RGB_EQ(applyRecovery(RgbGreen(), 1.0f, &metadata), - applyRecovery(RgbGreen(), 1.0f, &metadata, displayBoost)); - EXPECT_RGB_EQ(applyRecovery(RgbBlue(), 1.0f, &metadata), - applyRecovery(RgbBlue(), 1.0f, &metadata, displayBoost)); - EXPECT_RGB_EQ(applyRecovery(e, 1.0f, &metadata), - applyRecovery(e, 1.0f, &metadata, displayBoost)); + EXPECT_RGB_NEAR(applyGain(e, 0.0f, &metadata), e / 4.0f); + EXPECT_RGB_NEAR(applyGain(e, 0.25f, &metadata), e / 2.0f); + EXPECT_RGB_NEAR(applyGain(e, 0.5f, &metadata), e); + EXPECT_RGB_NEAR(applyGain(e, 0.75f, &metadata), e * 2.0f); + EXPECT_RGB_NEAR(applyGain(e, 1.0f, &metadata), e * 4.0f); + + EXPECT_RGB_EQ(applyGain(RgbBlack(), 1.0f, &metadata), + applyGain(RgbBlack(), 1.0f, &metadata, displayBoost)); + EXPECT_RGB_EQ(applyGain(RgbWhite(), 1.0f, &metadata), + applyGain(RgbWhite(), 1.0f, &metadata, displayBoost)); + EXPECT_RGB_EQ(applyGain(RgbRed(), 1.0f, &metadata), + applyGain(RgbRed(), 1.0f, &metadata, displayBoost)); + EXPECT_RGB_EQ(applyGain(RgbGreen(), 1.0f, &metadata), + applyGain(RgbGreen(), 1.0f, &metadata, displayBoost)); + EXPECT_RGB_EQ(applyGain(RgbBlue(), 1.0f, &metadata), + applyGain(RgbBlue(), 1.0f, &metadata, displayBoost)); + EXPECT_RGB_EQ(applyGain(e, 1.0f, &metadata), + applyGain(e, 1.0f, &metadata, displayBoost)); } -TEST_F(RecoveryMapMathTest, GetYuv420Pixel) { +TEST_F(GainMapMathTest, GetYuv420Pixel) { jpegr_uncompressed_struct image = Yuv420Image(); Color (*colors)[4] = Yuv420Colors(); @@ -834,7 +834,7 @@ TEST_F(RecoveryMapMathTest, GetYuv420Pixel) { } } -TEST_F(RecoveryMapMathTest, GetP010Pixel) { +TEST_F(GainMapMathTest, GetP010Pixel) { jpegr_uncompressed_struct image = P010Image(); Color (*colors)[4] = P010Colors(); @@ -845,7 +845,7 @@ TEST_F(RecoveryMapMathTest, GetP010Pixel) { } } -TEST_F(RecoveryMapMathTest, SampleYuv420) { +TEST_F(GainMapMathTest, SampleYuv420) { jpegr_uncompressed_struct image = Yuv420Image(); Color (*colors)[4] = Yuv420Colors(); @@ -871,7 +871,7 @@ TEST_F(RecoveryMapMathTest, SampleYuv420) { } } -TEST_F(RecoveryMapMathTest, SampleP010) { +TEST_F(GainMapMathTest, SampleP010) { jpegr_uncompressed_struct image = P010Image(); Color (*colors)[4] = P010Colors(); @@ -897,7 +897,7 @@ TEST_F(RecoveryMapMathTest, SampleP010) { } } -TEST_F(RecoveryMapMathTest, SampleMap) { +TEST_F(GainMapMathTest, SampleMap) { jpegr_uncompressed_struct image = MapImage(); float (*values)[4] = MapValues(); @@ -937,7 +937,7 @@ TEST_F(RecoveryMapMathTest, SampleMap) { } } -TEST_F(RecoveryMapMathTest, ColorToRgba1010102) { +TEST_F(GainMapMathTest, ColorToRgba1010102) { EXPECT_EQ(colorToRgba1010102(RgbBlack()), 0x3 << 30); EXPECT_EQ(colorToRgba1010102(RgbWhite()), 0xFFFFFFFF); EXPECT_EQ(colorToRgba1010102(RgbRed()), 0x3 << 30 | 0x3ff); @@ -952,7 +952,7 @@ TEST_F(RecoveryMapMathTest, ColorToRgba1010102) { | static_cast(0.3f * static_cast(0x3ff)) << 20); } -TEST_F(RecoveryMapMathTest, ColorToRgbaF16) { +TEST_F(GainMapMathTest, ColorToRgbaF16) { EXPECT_EQ(colorToRgbaF16(RgbBlack()), ((uint64_t) 0x3C00) << 48); EXPECT_EQ(colorToRgbaF16(RgbWhite()), 0x3C003C003C003C00); EXPECT_EQ(colorToRgbaF16(RgbRed()), (((uint64_t) 0x3C00) << 48) | ((uint64_t) 0x3C00)); @@ -963,7 +963,7 @@ TEST_F(RecoveryMapMathTest, ColorToRgbaF16) { EXPECT_EQ(colorToRgbaF16(e_gamma), 0x3C0034CD32662E66); } -TEST_F(RecoveryMapMathTest, Float32ToFloat16) { +TEST_F(GainMapMathTest, Float32ToFloat16) { EXPECT_EQ(floatToHalf(0.1f), 0x2E66); EXPECT_EQ(floatToHalf(0.0f), 0x0); EXPECT_EQ(floatToHalf(1.0f), 0x3C00); @@ -973,7 +973,7 @@ TEST_F(RecoveryMapMathTest, Float32ToFloat16) { EXPECT_EQ(floatToHalf(0x1.0p-126f), 0x0); // float zero } -TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgb) { +TEST_F(GainMapMathTest, GenerateMapLuminanceSrgb) { EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), srgbLuminance), 0.0f); EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), srgbLuminance), @@ -986,7 +986,7 @@ TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgb) { srgbLuminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon()); } -TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgbP3) { +TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbP3) { EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), p3Luminance), 0.0f); EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), p3Luminance), @@ -999,7 +999,7 @@ TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgbP3) { p3Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon()); } -TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgbBt2100) { +TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbBt2100) { EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), bt2100Luminance), 0.0f); EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), bt2100Luminance), @@ -1012,7 +1012,7 @@ TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgbBt2100) { bt2100Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon()); } -TEST_F(RecoveryMapMathTest, GenerateMapLuminanceHlg) { +TEST_F(GainMapMathTest, GenerateMapLuminanceHlg) { EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), hlgInvOetf, identityConversion, bt2100Luminance, kHlgMaxNits), 0.0f); @@ -1030,7 +1030,7 @@ TEST_F(RecoveryMapMathTest, GenerateMapLuminanceHlg) { bt2100Luminance(RgbBlue()) * kHlgMaxNits, LuminanceEpsilon()); } -TEST_F(RecoveryMapMathTest, GenerateMapLuminancePq) { +TEST_F(GainMapMathTest, GenerateMapLuminancePq) { EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), pqInvOetf, identityConversion, bt2100Luminance, kPqMaxNits), 0.0f); @@ -1048,7 +1048,7 @@ TEST_F(RecoveryMapMathTest, GenerateMapLuminancePq) { bt2100Luminance(RgbBlue()) * kPqMaxNits, LuminanceEpsilon()); } -TEST_F(RecoveryMapMathTest, ApplyMap) { +TEST_F(GainMapMathTest, ApplyMap) { jpegr_metadata_struct metadata = { .maxContentBoost = 8.0f, .minContentBoost = 1.0f / 8.0f }; diff --git a/libs/jpegrecoverymap/tests/jpegr_test.cpp b/libs/jpegrecoverymap/tests/jpegr_test.cpp index 229d7dc93b..620f4310df 100644 --- a/libs/jpegrecoverymap/tests/jpegr_test.cpp +++ b/libs/jpegrecoverymap/tests/jpegr_test.cpp @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include #include @@ -117,18 +117,18 @@ void JpegRTest::TearDown() { class JpegRBenchmark : public JpegR { public: - void BenchmarkGenerateRecoveryMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image, - jr_metadata_ptr metadata, jr_uncompressed_ptr map); - void BenchmarkApplyRecoveryMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map, - jr_metadata_ptr metadata, jr_uncompressed_ptr dest); + void BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image, + jr_metadata_ptr metadata, jr_uncompressed_ptr map); + void BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map, + jr_metadata_ptr metadata, jr_uncompressed_ptr dest); private: const int kProfileCount = 10; }; -void JpegRBenchmark::BenchmarkGenerateRecoveryMap(jr_uncompressed_ptr yuv420Image, - jr_uncompressed_ptr p010Image, - jr_metadata_ptr metadata, - jr_uncompressed_ptr map) { +void JpegRBenchmark::BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, + jr_uncompressed_ptr p010Image, + jr_metadata_ptr metadata, + jr_uncompressed_ptr map) { ASSERT_EQ(yuv420Image->width, p010Image->width); ASSERT_EQ(yuv420Image->height, p010Image->height); @@ -136,38 +136,38 @@ void JpegRBenchmark::BenchmarkGenerateRecoveryMap(jr_uncompressed_ptr yuv420Imag timerStart(&genRecMapTime); for (auto i = 0; i < kProfileCount; i++) { - ASSERT_EQ(OK, generateRecoveryMap( + ASSERT_EQ(OK, generateGainMap( yuv420Image, p010Image, jpegr_transfer_function::JPEGR_TF_HLG, metadata, map)); if (i != kProfileCount - 1) delete[] static_cast(map->data); } timerStop(&genRecMapTime); - ALOGE("Generate Recovery Map:- Res = %i x %i, time = %f ms", + ALOGE("Generate Gain Map:- Res = %i x %i, time = %f ms", yuv420Image->width, yuv420Image->height, elapsedTime(&genRecMapTime) / (kProfileCount * 1000.f)); } -void JpegRBenchmark::BenchmarkApplyRecoveryMap(jr_uncompressed_ptr yuv420Image, - jr_uncompressed_ptr map, - jr_metadata_ptr metadata, - jr_uncompressed_ptr dest) { +void JpegRBenchmark::BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, + jr_uncompressed_ptr map, + jr_metadata_ptr metadata, + jr_uncompressed_ptr dest) { Timer applyRecMapTime; timerStart(&applyRecMapTime); for (auto i = 0; i < kProfileCount; i++) { - ASSERT_EQ(OK, applyRecoveryMap(yuv420Image, map, metadata, JPEGR_OUTPUT_HDR_HLG, - metadata->maxContentBoost /* displayBoost */, dest)); + ASSERT_EQ(OK, applyGainMap(yuv420Image, map, metadata, JPEGR_OUTPUT_HDR_HLG, + metadata->maxContentBoost /* displayBoost */, dest)); } timerStop(&applyRecMapTime); - ALOGE("Apply Recovery Map:- Res = %i x %i, time = %f ms", + ALOGE("Apply Gain Map:- Res = %i x %i, time = %f ms", yuv420Image->width, yuv420Image->height, elapsedTime(&applyRecMapTime) / (kProfileCount * 1000.f)); } TEST_F(JpegRTest, build) { - // Force all of the recovery map lib to be linked by calling all public functions. + // Force all of the gain map lib to be linked by calling all public functions. JpegR jpegRCodec; jpegRCodec.encodeJPEGR(nullptr, static_cast(0), nullptr, 0, nullptr); jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast(0), @@ -515,7 +515,7 @@ TEST_F(JpegRTest, encodeFromJpegThenDecode) { free(decodedJpegR.data); } -TEST_F(JpegRTest, ProfileRecoveryMapFuncs) { +TEST_F(JpegRTest, ProfileGainMapFuncs) { const size_t kWidth = TEST_IMAGE_WIDTH; const size_t kHeight = TEST_IMAGE_HEIGHT; @@ -545,7 +545,7 @@ TEST_F(JpegRTest, ProfileRecoveryMapFuncs) { .height = 0, .colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED }; - benchmark.BenchmarkGenerateRecoveryMap(&mRawYuv420Image, &mRawP010Image, &metadata, &map); + benchmark.BenchmarkGenerateGainMap(&mRawYuv420Image, &mRawP010Image, &metadata, &map); const int dstSize = mRawYuv420Image.width * mRawYuv420Image.height * 4; auto bufferDst = std::make_unique(dstSize); @@ -554,7 +554,7 @@ TEST_F(JpegRTest, ProfileRecoveryMapFuncs) { .height = 0, .colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED }; - benchmark.BenchmarkApplyRecoveryMap(&mRawYuv420Image, &map, &metadata, &dest); + benchmark.BenchmarkApplyGainMap(&mRawYuv420Image, &map, &metadata, &dest); } } // namespace android::recoverymap -- GitLab From 5b5f6936cf3105f5fc4f0e517f66d4d4420303da Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 12 Apr 2023 16:28:19 -0700 Subject: [PATCH 1119/1310] [bbq] always update size if scaling mode changes to non freeze Test: presubmit (coming soon :) ) Fixes: 277346192 Change-Id: I7d86cbd794c2bfc5f4a6847c844c6bc81f80bbcf --- libs/gui/BLASTBufferQueue.cpp | 3 ++- libs/gui/tests/BLASTBufferQueue_test.cpp | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index 821dd37a85..fd42b33e49 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -582,7 +582,8 @@ status_t BLASTBufferQueue::acquireNextBufferLocked( // Only update mSize for destination bounds if the incoming buffer matches the requested size. // Otherwise, it could cause stretching since the destination bounds will update before the // buffer with the new size is acquired. - if (mRequestedSize == getBufferSize(bufferItem)) { + if (mRequestedSize == getBufferSize(bufferItem) || + bufferItem.mScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) { mSize = mRequestedSize; } Rect crop = computeCrop(bufferItem); diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp index cf2593dc81..b9e75be57d 100644 --- a/libs/gui/tests/BLASTBufferQueue_test.cpp +++ b/libs/gui/tests/BLASTBufferQueue_test.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -198,11 +199,13 @@ protected: t.apply(); t.clear(); - ui::DisplayMode mode; - ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(mDisplayToken, &mode)); - const ui::Size& resolution = mode.resolution; + ui::DisplayState displayState; + ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(mDisplayToken, &displayState)); + const ui::Size& resolution = displayState.layerStackSpaceRect; mDisplayWidth = resolution.getWidth(); mDisplayHeight = resolution.getHeight(); + ALOGV("Display: %dx%d orientation:%d", mDisplayWidth, mDisplayHeight, + displayState.orientation); mSurfaceControl = mClient->createSurface(String8("TestSurface"), mDisplayWidth, mDisplayHeight, PIXEL_FORMAT_RGBA_8888, -- GitLab From c398c0100accdcb45907f15579e9068d0bbe0a67 Mon Sep 17 00:00:00 2001 From: Chavi Weingarten Date: Wed, 12 Apr 2023 17:26:02 +0000 Subject: [PATCH 1120/1310] Add explicit clearSyncTransaction instead of passing in null When attempting to clear the last syncNextTransaction, the code was ambiguous and it was unclear what the expectation should be. Instead, don't allow null when calling syncNextTransaction and don't allow the syncTransaction to be overwritten. If the caller wants to clear the syncTransaction before a frame has arrived, they can do so by calling clearSyncTransaction. Test: No warning log in sync with no buffer. Test: BLASTBufferQueueTest Bug: 272189296 Change-Id: I3a945f5503220225f2147b0331d1fb2f9ea8dc63 --- libs/gui/BLASTBufferQueue.cpp | 59 +++++++++++++----------- libs/gui/include/gui/BLASTBufferQueue.h | 3 +- libs/gui/tests/BLASTBufferQueue_test.cpp | 43 +++++++++++++++-- 3 files changed, 74 insertions(+), 31 deletions(-) diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index 821dd37a85..aeb5406bbd 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -800,34 +800,24 @@ void BLASTBufferQueue::onFrameCancelled(const uint64_t bufferId) { mDequeueTimestamps.erase(bufferId); }; -void BLASTBufferQueue::syncNextTransaction( +bool BLASTBufferQueue::syncNextTransaction( std::function callback, bool acquireSingleBuffer) { - std::function prevCallback = nullptr; - SurfaceComposerClient::Transaction* prevTransaction = nullptr; - - { - std::lock_guard _lock{mMutex}; - BBQ_TRACE(); - // We're about to overwrite the previous call so we should invoke that callback - // immediately. - if (mTransactionReadyCallback) { - prevCallback = mTransactionReadyCallback; - prevTransaction = mSyncTransaction; - } + LOG_ALWAYS_FATAL_IF(!callback, + "BLASTBufferQueue: callback passed in to syncNextTransaction must not be " + "NULL"); - mTransactionReadyCallback = callback; - if (callback) { - mSyncTransaction = new SurfaceComposerClient::Transaction(); - } else { - mSyncTransaction = nullptr; - } - mAcquireSingleBuffer = mTransactionReadyCallback ? acquireSingleBuffer : true; + std::lock_guard _lock{mMutex}; + BBQ_TRACE(); + if (mTransactionReadyCallback) { + ALOGW("Attempting to overwrite transaction callback in syncNextTransaction"); + return false; } - if (prevCallback) { - prevCallback(prevTransaction); - } + mTransactionReadyCallback = callback; + mSyncTransaction = new SurfaceComposerClient::Transaction(); + mAcquireSingleBuffer = acquireSingleBuffer; + return true; } void BLASTBufferQueue::stopContinuousSyncTransaction() { @@ -835,20 +825,35 @@ void BLASTBufferQueue::stopContinuousSyncTransaction() { SurfaceComposerClient::Transaction* prevTransaction = nullptr; { std::lock_guard _lock{mMutex}; - bool invokeCallback = mTransactionReadyCallback && !mAcquireSingleBuffer; - if (invokeCallback) { - prevCallback = mTransactionReadyCallback; - prevTransaction = mSyncTransaction; + if (mAcquireSingleBuffer || !mTransactionReadyCallback) { + ALOGW("Attempting to stop continuous sync when none are active"); + return; } + + prevCallback = mTransactionReadyCallback; + prevTransaction = mSyncTransaction; + mTransactionReadyCallback = nullptr; mSyncTransaction = nullptr; mAcquireSingleBuffer = true; } + if (prevCallback) { prevCallback(prevTransaction); } } +void BLASTBufferQueue::clearSyncTransaction() { + std::lock_guard _lock{mMutex}; + if (!mAcquireSingleBuffer) { + ALOGW("Attempting to clear sync transaction when none are active"); + return; + } + + mTransactionReadyCallback = nullptr; + mSyncTransaction = nullptr; +} + bool BLASTBufferQueue::rejectBuffer(const BufferItem& item) { if (item.mScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) { // Only reject buffers if scaling mode is freeze. diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h index 69e9f8a4fb..a49a85984f 100644 --- a/libs/gui/include/gui/BLASTBufferQueue.h +++ b/libs/gui/include/gui/BLASTBufferQueue.h @@ -97,9 +97,10 @@ public: void releaseBufferCallbackLocked(const ReleaseCallbackId& id, const sp& releaseFence, std::optional currentMaxAcquiredBufferCount, bool fakeRelease) REQUIRES(mMutex); - void syncNextTransaction(std::function callback, + bool syncNextTransaction(std::function callback, bool acquireSingleBuffer = true); void stopContinuousSyncTransaction(); + void clearSyncTransaction(); void mergeWithNextTransaction(SurfaceComposerClient::Transaction* t, uint64_t frameNumber); void applyPendingTransactions(uint64_t frameNumber); diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp index cf2593dc81..fd69702843 100644 --- a/libs/gui/tests/BLASTBufferQueue_test.cpp +++ b/libs/gui/tests/BLASTBufferQueue_test.cpp @@ -116,15 +116,17 @@ public: mBlastBufferQueueAdapter->syncNextTransaction(callback, acquireSingleBuffer); } - void syncNextTransaction(std::function callback, + bool syncNextTransaction(std::function callback, bool acquireSingleBuffer = true) { - mBlastBufferQueueAdapter->syncNextTransaction(callback, acquireSingleBuffer); + return mBlastBufferQueueAdapter->syncNextTransaction(callback, acquireSingleBuffer); } void stopContinuousSyncTransaction() { mBlastBufferQueueAdapter->stopContinuousSyncTransaction(); } + void clearSyncTransaction() { mBlastBufferQueueAdapter->clearSyncTransaction(); } + int getWidth() { return mBlastBufferQueueAdapter->mSize.width; } int getHeight() { return mBlastBufferQueueAdapter->mSize.height; } @@ -1108,7 +1110,11 @@ TEST_F(BLASTBufferQueueTest, SyncNextTransactionOverwrite) { ASSERT_NE(nullptr, adapter.getTransactionReadyCallback()); auto callback2 = [](Transaction*) {}; - adapter.syncNextTransaction(callback2); + ASSERT_FALSE(adapter.syncNextTransaction(callback2)); + + sp igbProducer; + setUpProducer(adapter, igbProducer); + queueBuffer(igbProducer, 0, 255, 0, 0); std::unique_lock lock(mutex); if (!receivedCallback) { @@ -1120,6 +1126,37 @@ TEST_F(BLASTBufferQueueTest, SyncNextTransactionOverwrite) { ASSERT_TRUE(receivedCallback); } +TEST_F(BLASTBufferQueueTest, ClearSyncTransaction) { + std::mutex mutex; + std::condition_variable callbackReceivedCv; + bool receivedCallback = false; + + BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight); + ASSERT_EQ(nullptr, adapter.getTransactionReadyCallback()); + auto callback = [&](Transaction*) { + std::unique_lock lock(mutex); + receivedCallback = true; + callbackReceivedCv.notify_one(); + }; + adapter.syncNextTransaction(callback); + ASSERT_NE(nullptr, adapter.getTransactionReadyCallback()); + + adapter.clearSyncTransaction(); + + sp igbProducer; + setUpProducer(adapter, igbProducer); + queueBuffer(igbProducer, 0, 255, 0, 0); + + std::unique_lock lock(mutex); + if (!receivedCallback) { + ASSERT_EQ(callbackReceivedCv.wait_for(lock, std::chrono::seconds(3)), + std::cv_status::timeout) + << "did not receive callback"; + } + + ASSERT_FALSE(receivedCallback); +} + TEST_F(BLASTBufferQueueTest, SyncNextTransactionDropBuffer) { uint8_t r = 255; uint8_t g = 0; -- GitLab From 782d8a7a69c59051e3ae63e4bdaee96b538fbfd3 Mon Sep 17 00:00:00 2001 From: Changyeon Jo Date: Sun, 2 Apr 2023 16:03:31 -0700 Subject: [PATCH 1121/1310] Enable libbufferqueueconverter static library Clients of the automotive display service employ libbufferqueueconverter to generate a Surface from HGBP object obtained from the service. Because this library may not exist while the automotive display service is available by default in AAOS builds, VtsHalCarDisplayTargetTest needs to be linked to libbufferqueueconverter library statically. Bug: 276643241 Test: atest VtsHalCarDisplayTargetTest Change-Id: I33379f17d4e6dd4072533811420d655680a959b4 --- libs/bufferqueueconverter/Android.bp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/bufferqueueconverter/Android.bp b/libs/bufferqueueconverter/Android.bp index 5f145a149d..d4605ea13a 100644 --- a/libs/bufferqueueconverter/Android.bp +++ b/libs/bufferqueueconverter/Android.bp @@ -13,7 +13,7 @@ cc_library_headers { export_include_dirs: ["include"], } -cc_library_shared { +cc_library { name: "libbufferqueueconverter", vendor_available: true, vndk: { -- GitLab From ed6c3defc344d5ac0a6cc05752d4aeb789283e2b Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Wed, 5 Apr 2023 19:24:37 +0000 Subject: [PATCH 1122/1310] InputMapper refactor: Change readerconfig pointer to const ref We are refactoring Input-mapper to ensure they are configured at the time of initialisation. In this CL we are changing InputReaderConfiguration raw pointer to a const reference and rename it to readerConfig for clarity. Test: build, inputflinger_tests, presubmit checks Bug: 256009910 Change-Id: Iae22c8cfba836bd311a51fca13d680456a61e604 --- services/inputflinger/reader/InputDevice.cpp | 29 +++++++++++-------- services/inputflinger/reader/InputReader.cpp | 6 ++-- .../inputflinger/reader/include/InputDevice.h | 2 +- .../reader/mapper/CursorInputMapper.cpp | 14 ++++----- .../reader/mapper/CursorInputMapper.h | 2 +- .../mapper/ExternalStylusInputMapper.cpp | 2 +- .../reader/mapper/ExternalStylusInputMapper.h | 2 +- .../reader/mapper/InputMapper.cpp | 2 +- .../inputflinger/reader/mapper/InputMapper.h | 2 +- .../reader/mapper/JoystickInputMapper.cpp | 2 +- .../reader/mapper/JoystickInputMapper.h | 2 +- .../reader/mapper/KeyboardInputMapper.cpp | 8 ++--- .../reader/mapper/KeyboardInputMapper.h | 4 +-- .../mapper/RotaryEncoderInputMapper.cpp | 4 +-- .../reader/mapper/RotaryEncoderInputMapper.h | 2 +- .../reader/mapper/SensorInputMapper.cpp | 2 +- .../reader/mapper/SensorInputMapper.h | 2 +- .../reader/mapper/TouchInputMapper.cpp | 4 +-- .../reader/mapper/TouchInputMapper.h | 2 +- .../reader/mapper/TouchpadInputMapper.cpp | 12 ++++---- .../reader/mapper/TouchpadInputMapper.h | 2 +- .../tests/FakeInputReaderPolicy.cpp | 4 +-- .../tests/FakeInputReaderPolicy.h | 2 +- .../inputflinger/tests/InputReader_test.cpp | 10 +++---- .../tests/fuzzers/CursorInputFuzzer.cpp | 8 ++--- .../tests/fuzzers/FuzzContainer.h | 2 +- .../tests/fuzzers/KeyboardInputFuzzer.cpp | 2 +- .../tests/fuzzers/MultiTouchInputFuzzer.cpp | 2 +- 28 files changed, 71 insertions(+), 66 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index eaed987320..ccf41189bc 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -253,7 +253,8 @@ void InputDevice::removeEventHubDevice(int32_t eventHubId) { mDevices.erase(eventHubId); } -std::list InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config, +std::list InputDevice::configure(nsecs_t when, + const InputReaderConfiguration& readerConfig, uint32_t changes) { std::list out; mSources = 0; @@ -291,7 +292,7 @@ std::list InputDevice::configure(nsecs_t when, const InputReaderConf }); mAssociatedDeviceType = - getValueByKey(config->deviceTypeAssociations, mIdentifier.location); + getValueByKey(readerConfig.deviceTypeAssociations, mIdentifier.location); } if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS)) { @@ -325,8 +326,8 @@ std::list InputDevice::configure(nsecs_t when, const InputReaderConf // Do not execute this code on the first configure, because 'setEnabled' would call // InputMapper::reset, and you can't reset a mapper before it has been configured. // The mappers are configured for the first time at the bottom of this function. - auto it = config->disabledDevices.find(mId); - bool enabled = it == config->disabledDevices.end(); + auto it = readerConfig.disabledDevices.find(mId); + bool enabled = it == readerConfig.disabledDevices.end(); out += setEnabled(enabled, when); } @@ -338,13 +339,14 @@ std::list InputDevice::configure(nsecs_t when, const InputReaderConf // Find the display port that corresponds to the current input port. const std::string& inputPort = mIdentifier.location; if (!inputPort.empty()) { - const std::unordered_map& ports = config->portAssociations; + const std::unordered_map& ports = + readerConfig.portAssociations; const auto& displayPort = ports.find(inputPort); if (displayPort != ports.end()) { mAssociatedDisplayPort = std::make_optional(displayPort->second); } else { const std::unordered_map& displayUniqueIds = - config->uniqueIdAssociations; + readerConfig.uniqueIdAssociations; const auto& displayUniqueId = displayUniqueIds.find(inputPort); if (displayUniqueId != displayUniqueIds.end()) { mAssociatedDisplayUniqueId = displayUniqueId->second; @@ -356,9 +358,11 @@ std::list InputDevice::configure(nsecs_t when, const InputReaderConf // "disabledDevices" list. If it is associated with a specific display, and it was not // explicitly disabled, then enable/disable the device based on whether we can find the // corresponding viewport. - bool enabled = (config->disabledDevices.find(mId) == config->disabledDevices.end()); + bool enabled = + (readerConfig.disabledDevices.find(mId) == readerConfig.disabledDevices.end()); if (mAssociatedDisplayPort) { - mAssociatedViewport = config->getDisplayViewportByPort(*mAssociatedDisplayPort); + mAssociatedViewport = + readerConfig.getDisplayViewportByPort(*mAssociatedDisplayPort); if (!mAssociatedViewport) { ALOGW("Input device %s should be associated with display on port %" PRIu8 ", " "but the corresponding viewport is not found.", @@ -367,7 +371,7 @@ std::list InputDevice::configure(nsecs_t when, const InputReaderConf } } else if (mAssociatedDisplayUniqueId != std::nullopt) { mAssociatedViewport = - config->getDisplayViewportByUniqueId(*mAssociatedDisplayUniqueId); + readerConfig.getDisplayViewportByUniqueId(*mAssociatedDisplayUniqueId); if (!mAssociatedViewport) { ALOGW("Input device %s should be associated with display %s but the " "corresponding viewport cannot be found", @@ -384,15 +388,16 @@ std::list InputDevice::configure(nsecs_t when, const InputReaderConf } } - for_each_mapper([this, when, &config, changes, &out](InputMapper& mapper) { - out += mapper.reconfigure(when, config, changes); + for_each_mapper([this, when, &readerConfig, changes, &out](InputMapper& mapper) { + out += mapper.reconfigure(when, readerConfig, changes); mSources |= mapper.getSources(); }); // If a device is just plugged but it might be disabled, we need to update some info like // axis range of touch from each InputMapper first, then disable it. if (!changes) { - out += setEnabled(config->disabledDevices.find(mId) == config->disabledDevices.end(), + out += setEnabled(readerConfig.disabledDevices.find(mId) == + readerConfig.disabledDevices.end(), when); } } diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 81ac03b7b3..80459a2945 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -234,7 +234,7 @@ void InputReader::addDeviceLocked(nsecs_t when, int32_t eventHubId) { InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(eventHubId); std::shared_ptr device = createDeviceLocked(eventHubId, identifier); - notifyAll(device->configure(when, &mConfig, 0)); + notifyAll(device->configure(when, mConfig, 0)); notifyAll(device->reset(when)); if (device->isIgnored()) { @@ -310,7 +310,7 @@ void InputReader::removeDeviceLocked(nsecs_t when, int32_t eventHubId) { std::list resetEvents; if (device->hasEventHubDevices()) { - resetEvents += device->configure(when, &mConfig, 0); + resetEvents += device->configure(when, mConfig, 0); } resetEvents += device->reset(when); notifyAll(std::move(resetEvents)); @@ -408,7 +408,7 @@ void InputReader::refreshConfigurationLocked(uint32_t changes) { } else { for (auto& devicePair : mDevices) { std::shared_ptr& device = devicePair.second; - notifyAll(device->configure(now, &mConfig, changes)); + notifyAll(device->configure(now, mConfig, changes)); } } diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 4ae06fe77b..ad45cb102f 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -83,7 +83,7 @@ public: void addEventHubDevice(int32_t eventHubId, bool populateMappers = true); void removeEventHubDevice(int32_t eventHubId); [[nodiscard]] std::list configure(nsecs_t when, - const InputReaderConfiguration* config, + const InputReaderConfiguration& readerConfig, uint32_t changes); [[nodiscard]] std::list reset(nsecs_t when); [[nodiscard]] std::list process(const RawEvent* rawEvents, size_t count); diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index d7dc2aec70..1cc614ea4e 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -134,7 +134,7 @@ void CursorInputMapper::dump(std::string& dump) { } std::list CursorInputMapper::reconfigure(nsecs_t when, - const InputReaderConfiguration* config, + const InputReaderConfiguration& config, uint32_t changes) { std::list out = InputMapper::reconfigure(when, config, changes); @@ -173,10 +173,10 @@ std::list CursorInputMapper::reconfigure(nsecs_t when, } const bool configurePointerCapture = mParameters.mode != Parameters::Mode::NAVIGATION && - ((!changes && config->pointerCaptureRequest.enable) || + ((!changes && config.pointerCaptureRequest.enable) || (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE)); if (configurePointerCapture) { - if (config->pointerCaptureRequest.enable) { + if (config.pointerCaptureRequest.enable) { if (mParameters.mode == Parameters::Mode::POINTER) { mParameters.mode = Parameters::Mode::POINTER_RELATIVE; mSource = AINPUT_SOURCE_MOUSE_RELATIVE; @@ -207,9 +207,9 @@ std::list CursorInputMapper::reconfigure(nsecs_t when, mWheelXVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); mWheelYVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); } else { - mPointerVelocityControl.setParameters(config->pointerVelocityControlParameters); - mWheelXVelocityControl.setParameters(config->wheelVelocityControlParameters); - mWheelYVelocityControl.setParameters(config->wheelVelocityControlParameters); + mPointerVelocityControl.setParameters(config.pointerVelocityControlParameters); + mWheelXVelocityControl.setParameters(config.wheelVelocityControlParameters); + mWheelYVelocityControl.setParameters(config.wheelVelocityControlParameters); } } @@ -241,7 +241,7 @@ std::list CursorInputMapper::reconfigure(nsecs_t when, // rotations and report values directly from the input device. if (!isOrientedDevice && mDisplayId && mParameters.mode != Parameters::Mode::POINTER_RELATIVE) { - if (auto viewport = config->getDisplayViewportById(*mDisplayId); viewport) { + if (auto viewport = config.getDisplayViewportById(*mDisplayId); viewport) { mOrientation = getInverseRotation(viewport->orientation); } } diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index 987b9debb9..5f7a3adc88 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -60,7 +60,7 @@ public: virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; virtual void dump(std::string& dump) override; [[nodiscard]] std::list reconfigure(nsecs_t when, - const InputReaderConfiguration* config, + const InputReaderConfiguration& config, uint32_t changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp index c5a3075657..bbb641eb76 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp @@ -47,7 +47,7 @@ void ExternalStylusInputMapper::dump(std::string& dump) { } std::list ExternalStylusInputMapper::reconfigure(nsecs_t when, - const InputReaderConfiguration* config, + const InputReaderConfiguration& config, uint32_t changes) { getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPressureAxis); mTouchButtonAccumulator.configure(); diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h index 0df8cf7b1b..3eac10d07f 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h @@ -33,7 +33,7 @@ public: void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; void dump(std::string& dump) override; [[nodiscard]] std::list reconfigure(nsecs_t when, - const InputReaderConfiguration* config, + const InputReaderConfiguration& config, uint32_t changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp index 9d1e9ceb1c..5dd7bc4f8b 100644 --- a/services/inputflinger/reader/mapper/InputMapper.cpp +++ b/services/inputflinger/reader/mapper/InputMapper.cpp @@ -35,7 +35,7 @@ void InputMapper::populateDeviceInfo(InputDeviceInfo& info) { void InputMapper::dump(std::string& dump) {} -std::list InputMapper::reconfigure(nsecs_t when, const InputReaderConfiguration* config, +std::list InputMapper::reconfigure(nsecs_t when, const InputReaderConfiguration& config, uint32_t changes) { return {}; } diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h index bb15e4d86b..ab573f054b 100644 --- a/services/inputflinger/reader/mapper/InputMapper.h +++ b/services/inputflinger/reader/mapper/InputMapper.h @@ -54,7 +54,7 @@ public: virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo); virtual void dump(std::string& dump); [[nodiscard]] virtual std::list reconfigure(nsecs_t when, - const InputReaderConfiguration* config, + const InputReaderConfiguration& config, uint32_t changes); [[nodiscard]] virtual std::list reset(nsecs_t when); [[nodiscard]] virtual std::list process(const RawEvent* rawEvent) = 0; diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp index f60035bd00..3e840ee500 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp @@ -104,7 +104,7 @@ void JoystickInputMapper::dump(std::string& dump) { } std::list JoystickInputMapper::reconfigure(nsecs_t when, - const InputReaderConfiguration* config, + const InputReaderConfiguration& config, uint32_t changes) { std::list out = InputMapper::reconfigure(when, config, changes); diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.h b/services/inputflinger/reader/mapper/JoystickInputMapper.h index 9adb07fb4e..6f1c6b7655 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.h +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h @@ -29,7 +29,7 @@ public: virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; virtual void dump(std::string& dump) override; [[nodiscard]] std::list reconfigure(nsecs_t when, - const InputReaderConfiguration* config, + const InputReaderConfiguration& config, uint32_t changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index fc00c4886d..f8dd3a85ac 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -118,21 +118,21 @@ void KeyboardInputMapper::dump(std::string& dump) { } std::optional KeyboardInputMapper::findViewport( - const InputReaderConfiguration* config) { + const InputReaderConfiguration& readerConfig) { if (getDeviceContext().getAssociatedViewport()) { return getDeviceContext().getAssociatedViewport(); } // No associated display defined, try to find default display if orientationAware. if (mParameters.orientationAware) { - return config->getDisplayViewportByType(ViewportType::INTERNAL); + return readerConfig.getDisplayViewportByType(ViewportType::INTERNAL); } return std::nullopt; } std::list KeyboardInputMapper::reconfigure(nsecs_t when, - const InputReaderConfiguration* config, + const InputReaderConfiguration& config, uint32_t changes) { std::list out = InputMapper::reconfigure(when, config, changes); @@ -147,7 +147,7 @@ std::list KeyboardInputMapper::reconfigure(nsecs_t when, if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUT_ASSOCIATION)) { mKeyboardLayoutInfo = - getValueByKey(config->keyboardLayoutAssociations, getDeviceContext().getLocation()); + getValueByKey(config.keyboardLayoutAssociations, getDeviceContext().getLocation()); } return out; diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index 52576c339c..0cb130d4cd 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -30,7 +30,7 @@ public: void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; void dump(std::string& dump) override; [[nodiscard]] std::list reconfigure(nsecs_t when, - const InputReaderConfiguration* config, + const InputReaderConfiguration& config, uint32_t changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; @@ -96,7 +96,7 @@ private: void resetLedState(); void initializeLedState(LedState& ledState, int32_t led); void updateLedStateForModifier(LedState& ledState, int32_t led, int32_t modifier, bool reset); - std::optional findViewport(const InputReaderConfiguration* config); + std::optional findViewport(const InputReaderConfiguration& readerConfig); [[nodiscard]] std::list cancelAllDownKeys(nsecs_t when); }; diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp index 5b7b29509e..b181aa0bf8 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp @@ -64,7 +64,7 @@ void RotaryEncoderInputMapper::dump(std::string& dump) { } std::list RotaryEncoderInputMapper::reconfigure(nsecs_t when, - const InputReaderConfiguration* config, + const InputReaderConfiguration& config, uint32_t changes) { std::list out = InputMapper::reconfigure(when, config, changes); if (!changes) { @@ -72,7 +72,7 @@ std::list RotaryEncoderInputMapper::reconfigure(nsecs_t when, } if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { std::optional internalViewport = - config->getDisplayViewportByType(ViewportType::INTERNAL); + config.getDisplayViewportByType(ViewportType::INTERNAL); if (internalViewport) { mOrientation = internalViewport->orientation; } else { diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h index 639a9876c7..37c94422e3 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h @@ -32,7 +32,7 @@ public: virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; virtual void dump(std::string& dump) override; [[nodiscard]] std::list reconfigure(nsecs_t when, - const InputReaderConfiguration* config, + const InputReaderConfiguration& config, uint32_t changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp index 720fc6962d..f8a520d9f4 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp @@ -117,7 +117,7 @@ void SensorInputMapper::dump(std::string& dump) { } std::list SensorInputMapper::reconfigure(nsecs_t when, - const InputReaderConfiguration* config, + const InputReaderConfiguration& config, uint32_t changes) { std::list out = InputMapper::reconfigure(when, config, changes); diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h index 93cc244481..fa36ab30b1 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.h +++ b/services/inputflinger/reader/mapper/SensorInputMapper.h @@ -34,7 +34,7 @@ public: void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; void dump(std::string& dump) override; [[nodiscard]] std::list reconfigure(nsecs_t when, - const InputReaderConfiguration* config, + const InputReaderConfiguration& config, uint32_t changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index c19737d672..9a426bf014 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -288,11 +288,11 @@ void TouchInputMapper::dump(std::string& dump) { } std::list TouchInputMapper::reconfigure(nsecs_t when, - const InputReaderConfiguration* config, + const InputReaderConfiguration& config, uint32_t changes) { std::list out = InputMapper::reconfigure(when, config, changes); - mConfig = *config; + mConfig = config; // Full configuration should happen the first time configure is called and // when the device type is changed. Changing a device type can affect diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index d98ae607e9..0e8ff4b568 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -153,7 +153,7 @@ public: void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; void dump(std::string& dump) override; [[nodiscard]] std::list reconfigure(nsecs_t when, - const InputReaderConfiguration* config, + const InputReaderConfiguration& config, uint32_t changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 33f368e9eb..8135071da2 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -220,7 +220,7 @@ void TouchpadInputMapper::dump(std::string& dump) { } std::list TouchpadInputMapper::reconfigure(nsecs_t when, - const InputReaderConfiguration* config, + const InputReaderConfiguration& config, uint32_t changes) { if (!changes) { // First time configuration @@ -231,7 +231,7 @@ std::list TouchpadInputMapper::reconfigure(nsecs_t when, std::optional displayId = mPointerController->getDisplayId(); ui::Rotation orientation = ui::ROTATION_0; if (displayId.has_value()) { - if (auto viewport = config->getDisplayViewportById(*displayId); viewport) { + if (auto viewport = config.getDisplayViewportById(*displayId); viewport) { orientation = getInverseRotation(viewport->orientation); } } @@ -242,14 +242,14 @@ std::list TouchpadInputMapper::reconfigure(nsecs_t when, .setBoolValues({true}); GesturesProp accelCurveProp = mPropertyProvider.getProperty("Pointer Accel Curve"); accelCurveProp.setRealValues( - createAccelerationCurveForSensitivity(config->touchpadPointerSpeed, + createAccelerationCurveForSensitivity(config.touchpadPointerSpeed, accelCurveProp.getCount())); mPropertyProvider.getProperty("Invert Scrolling") - .setBoolValues({config->touchpadNaturalScrollingEnabled}); + .setBoolValues({config.touchpadNaturalScrollingEnabled}); mPropertyProvider.getProperty("Tap Enable") - .setBoolValues({config->touchpadTapToClickEnabled}); + .setBoolValues({config.touchpadTapToClickEnabled}); mPropertyProvider.getProperty("Button Right Click Zone Enable") - .setBoolValues({config->touchpadRightClickZoneEnabled}); + .setBoolValues({config.touchpadRightClickZoneEnabled}); } return {}; } diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index 6f152fa557..27cdde1d45 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -45,7 +45,7 @@ public: void dump(std::string& dump) override; [[nodiscard]] std::list reconfigure(nsecs_t when, - const InputReaderConfiguration* config, + const InputReaderConfiguration& config, uint32_t changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp index 30c1719c35..3486d0f2a4 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp +++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp @@ -154,8 +154,8 @@ void FakeInputReaderPolicy::setPointerController( mPointerController = std::move(controller); } -const InputReaderConfiguration* FakeInputReaderPolicy::getReaderConfiguration() const { - return &mConfig; +const InputReaderConfiguration& FakeInputReaderPolicy::getReaderConfiguration() const { + return mConfig; } const std::vector& FakeInputReaderPolicy::getInputDevices() const { diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h index 28ac505284..85ff01a071 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.h +++ b/services/inputflinger/tests/FakeInputReaderPolicy.h @@ -63,7 +63,7 @@ public: void addDisabledDevice(int32_t deviceId); void removeDisabledDevice(int32_t deviceId); void setPointerController(std::shared_ptr controller); - const InputReaderConfiguration* getReaderConfiguration() const; + const InputReaderConfiguration& getReaderConfiguration() const; const std::vector& getInputDevices() const; TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor, ui::Rotation surfaceRotation); diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index fb082da8e1..dcf8557d4b 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -263,7 +263,7 @@ private: } } - std::list reconfigure(nsecs_t, const InputReaderConfiguration* config, + std::list reconfigure(nsecs_t, const InputReaderConfiguration& config, uint32_t changes) override { std::scoped_lock lock(mLock); mConfigureWasCalled = true; @@ -271,7 +271,7 @@ private: // Find the associated viewport if exist. const std::optional displayPort = getDeviceContext().getAssociatedDisplayPort(); if (displayPort && (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { - mViewport = config->getDisplayViewportByPort(*displayPort); + mViewport = config.getDisplayViewportByPort(*displayPort); } mStateChangedCondition.notify_all(); @@ -2319,7 +2319,7 @@ TEST_F(InputDeviceTest, WhenDeviceCreated_EnabledIsFalse) { TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) { // Configuration. InputReaderConfiguration config; - std::list unused = mDevice->configure(ARBITRARY_TIME, &config, 0); + std::list unused = mDevice->configure(ARBITRARY_TIME, config, 0); // Reset. unused += mDevice->reset(ARBITRARY_TIME); @@ -2378,7 +2378,7 @@ TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRe mapper2.setMetaState(AMETA_SHIFT_ON); InputReaderConfiguration config; - std::list unused = mDevice->configure(ARBITRARY_TIME, &config, 0); + std::list unused = mDevice->configure(ARBITRARY_TIME, config, 0); std::optional propertyValue = mDevice->getConfiguration().getString("key"); ASSERT_TRUE(propertyValue.has_value()) @@ -3669,7 +3669,7 @@ TEST_F(KeyboardInputMapperTest, LayoutInfoCorrectlyMapped) { addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); InputReaderConfiguration config; - std::list unused = mDevice->configure(ARBITRARY_TIME, &config, 0); + std::list unused = mDevice->configure(ARBITRARY_TIME, config, 0); ASSERT_EQ("en", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->languageTag); ASSERT_EQ("extended", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->layoutType); diff --git a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp index 0d5f30c046..28873a39ea 100644 --- a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp @@ -52,13 +52,13 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { [&]() -> void { mapper.getSources(); }, [&]() -> void { std::list unused = - mapper.reconfigure(fdp->ConsumeIntegral(), &policyConfig, + mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, fdp->ConsumeIntegral()); }, [&]() -> void { // Need to reconfigure with 0 or you risk a NPE. std::list unused = - mapper.reconfigure(fdp->ConsumeIntegral(), &policyConfig, 0); + mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, 0); InputDeviceInfo info; mapper.populateDeviceInfo(info); }, @@ -71,7 +71,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { // Need to reconfigure with 0 or you risk a NPE. std::list unused = - mapper.reconfigure(fdp->ConsumeIntegral(), &policyConfig, 0); + mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, 0); RawEvent rawEvent{fdp->ConsumeIntegral(), fdp->ConsumeIntegral(), fdp->ConsumeIntegral(), @@ -90,7 +90,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { [&]() -> void { // Need to reconfigure with 0 or you risk a NPE. std::list unused = - mapper.reconfigure(fdp->ConsumeIntegral(), &policyConfig, 0); + mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, 0); mapper.getAssociatedDisplayId(); }, })(); diff --git a/services/inputflinger/tests/fuzzers/FuzzContainer.h b/services/inputflinger/tests/fuzzers/FuzzContainer.h index 76d2bcd03d..d42d11c240 100644 --- a/services/inputflinger/tests/fuzzers/FuzzContainer.h +++ b/services/inputflinger/tests/fuzzers/FuzzContainer.h @@ -59,7 +59,7 @@ public: void configureDevice() { nsecs_t arbitraryTime = mFdp->ConsumeIntegral(); std::list out; - out += mFuzzDevice->configure(arbitraryTime, &mPolicyConfig, 0); + out += mFuzzDevice->configure(arbitraryTime, mPolicyConfig, 0); out += mFuzzDevice->reset(arbitraryTime); for (const NotifyArgs& args : out) { mFuzzListener.notify(args); diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp index 14cb8a5280..00b44b5158 100644 --- a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp @@ -64,7 +64,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { [&]() -> void { mapper.getSources(); }, [&]() -> void { std::list unused = - mapper.reconfigure(fdp->ConsumeIntegral(), &policyConfig, + mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, fdp->ConsumeIntegral()); }, [&]() -> void { diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp index 8352a9011e..70908ff8b6 100644 --- a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp @@ -79,7 +79,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { [&]() -> void { mapper.getSources(); }, [&]() -> void { std::list unused = - mapper.reconfigure(fdp->ConsumeIntegral(), &policyConfig, + mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, fdp->ConsumeIntegral()); }, [&]() -> void { -- GitLab From 8e6fb254b1a8248111c9d3a8b989daa4a5076942 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Thu, 6 Apr 2023 11:49:17 +0000 Subject: [PATCH 1123/1310] InputMapper refactor: Modify InputMapper constructor for configuration We are refactoring Input-mapper(s) to ensure they are configured at the time of initialisation. In this CL we are changing InputMapper's constructor to accept the reader configuration to facilitate initialisation. Test: build, inputflinger_tests, presubmit checks Bug: 256009910 Change-Id: I710b892f51268a001d076f387163ac3274f5b0d1 --- services/inputflinger/reader/InputDevice.cpp | 179 ++++++++++-------- services/inputflinger/reader/InputReader.cpp | 2 +- .../inputflinger/reader/include/InputDevice.h | 10 +- .../reader/mapper/CursorInputMapper.cpp | 6 +- .../reader/mapper/CursorInputMapper.h | 3 +- .../mapper/ExternalStylusInputMapper.cpp | 5 +- .../reader/mapper/ExternalStylusInputMapper.h | 3 +- .../reader/mapper/InputMapper.cpp | 4 +- .../inputflinger/reader/mapper/InputMapper.h | 6 +- .../reader/mapper/JoystickInputMapper.cpp | 5 +- .../reader/mapper/JoystickInputMapper.h | 3 +- .../reader/mapper/KeyboardInputMapper.cpp | 7 +- .../reader/mapper/KeyboardInputMapper.h | 4 +- .../reader/mapper/MultiTouchInputMapper.cpp | 5 +- .../reader/mapper/MultiTouchInputMapper.h | 3 +- .../mapper/RotaryEncoderInputMapper.cpp | 5 +- .../reader/mapper/RotaryEncoderInputMapper.h | 3 +- .../reader/mapper/SensorInputMapper.cpp | 5 +- .../reader/mapper/SensorInputMapper.h | 3 +- .../reader/mapper/SingleTouchInputMapper.cpp | 5 +- .../reader/mapper/SingleTouchInputMapper.h | 3 +- .../reader/mapper/SwitchInputMapper.cpp | 5 +- .../reader/mapper/SwitchInputMapper.h | 3 +- .../reader/mapper/TouchInputMapper.cpp | 5 +- .../reader/mapper/TouchInputMapper.h | 3 +- .../reader/mapper/TouchpadInputMapper.cpp | 5 +- .../reader/mapper/TouchpadInputMapper.h | 3 +- .../reader/mapper/VibratorInputMapper.cpp | 5 +- .../reader/mapper/VibratorInputMapper.h | 3 +- services/inputflinger/tests/InputMapperTest.h | 3 +- .../inputflinger/tests/InputReader_test.cpp | 92 ++++++--- .../tests/fuzzers/CursorInputFuzzer.cpp | 2 +- .../tests/fuzzers/KeyboardInputFuzzer.cpp | 4 +- .../tests/fuzzers/MultiTouchInputFuzzer.cpp | 2 +- .../tests/fuzzers/SwitchInputFuzzer.cpp | 2 +- 35 files changed, 242 insertions(+), 164 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index ccf41189bc..b5ee0445c2 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -146,98 +146,23 @@ void InputDevice::dump(std::string& dump, const std::string& eventHubDevStr) { } } -void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) { +void InputDevice::addEmptyEventHubDevice(int32_t eventHubId) { if (mDevices.find(eventHubId) != mDevices.end()) { return; } std::unique_ptr contextPtr(new InputDeviceContext(*this, eventHubId)); - ftl::Flags classes = contextPtr->getDeviceClasses(); std::vector> mappers; - // Check if we should skip population - if (!populateMappers) { - mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))}); - return; - } - - // Switch-like devices. - if (classes.test(InputDeviceClass::SWITCH)) { - mappers.push_back(std::make_unique(*contextPtr)); - } - - // Scroll wheel-like devices. - if (classes.test(InputDeviceClass::ROTARY_ENCODER)) { - mappers.push_back(std::make_unique(*contextPtr)); - } - - // Vibrator-like devices. - if (classes.test(InputDeviceClass::VIBRATOR)) { - mappers.push_back(std::make_unique(*contextPtr)); - } - - // Battery-like devices or light-containing devices. - // PeripheralController will be created with associated EventHub device. - if (classes.test(InputDeviceClass::BATTERY) || classes.test(InputDeviceClass::LIGHT)) { - mController = std::make_unique(*contextPtr); - } - - // Keyboard-like devices. - uint32_t keyboardSource = 0; - int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC; - if (classes.test(InputDeviceClass::KEYBOARD)) { - keyboardSource |= AINPUT_SOURCE_KEYBOARD; - } - if (classes.test(InputDeviceClass::ALPHAKEY)) { - keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC; - } - if (classes.test(InputDeviceClass::DPAD)) { - keyboardSource |= AINPUT_SOURCE_DPAD; - } - if (classes.test(InputDeviceClass::GAMEPAD)) { - keyboardSource |= AINPUT_SOURCE_GAMEPAD; - } - - if (keyboardSource != 0) { - mappers.push_back( - std::make_unique(*contextPtr, keyboardSource, keyboardType)); - } - - // Cursor-like devices. - if (classes.test(InputDeviceClass::CURSOR)) { - mappers.push_back(std::make_unique(*contextPtr)); - } - - // Touchscreens and touchpad devices. - static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY = - sysprop::InputProperties::enable_touchpad_gestures_library().value_or(true); - // TODO(b/272518665): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) touchpads, or - // at least load this setting from the IDC file. - const InputDeviceIdentifier identifier = contextPtr->getDeviceIdentifier(); - const bool isSonyDualShock4Touchpad = identifier.vendor == 0x054c && - (identifier.product == 0x05c4 || identifier.product == 0x09cc); - if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) && - classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) { - mappers.push_back(std::make_unique(*contextPtr)); - } else if (classes.test(InputDeviceClass::TOUCH_MT)) { - mappers.push_back(std::make_unique(*contextPtr)); - } else if (classes.test(InputDeviceClass::TOUCH)) { - mappers.push_back(std::make_unique(*contextPtr)); - } - - // Joystick-like devices. - if (classes.test(InputDeviceClass::JOYSTICK)) { - mappers.push_back(std::make_unique(*contextPtr)); - } - - // Motion sensor enabled devices. - if (classes.test(InputDeviceClass::SENSOR)) { - mappers.push_back(std::make_unique(*contextPtr)); - } + mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))}); +} - // External stylus-like devices. - if (classes.test(InputDeviceClass::EXTERNAL_STYLUS)) { - mappers.push_back(std::make_unique(*contextPtr)); +void InputDevice::addEventHubDevice(int32_t eventHubId, + const InputReaderConfiguration& readerConfig) { + if (mDevices.find(eventHubId) != mDevices.end()) { + return; } + std::unique_ptr contextPtr(new InputDeviceContext(*this, eventHubId)); + std::vector> mappers = createMappers(*contextPtr, readerConfig); // insert the context into the devices set mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))}); @@ -512,6 +437,92 @@ int32_t InputDevice::getState(uint32_t sourceMask, int32_t code, GetStateFunc ge return result; } +std::vector> InputDevice::createMappers( + InputDeviceContext& contextPtr, const InputReaderConfiguration& readerConfig) { + ftl::Flags classes = contextPtr.getDeviceClasses(); + std::vector> mappers; + + // Switch-like devices. + if (classes.test(InputDeviceClass::SWITCH)) { + mappers.push_back(std::make_unique(contextPtr, readerConfig)); + } + + // Scroll wheel-like devices. + if (classes.test(InputDeviceClass::ROTARY_ENCODER)) { + mappers.push_back(std::make_unique(contextPtr, readerConfig)); + } + + // Vibrator-like devices. + if (classes.test(InputDeviceClass::VIBRATOR)) { + mappers.push_back(std::make_unique(contextPtr, readerConfig)); + } + + // Battery-like devices or light-containing devices. + // PeripheralController will be created with associated EventHub device. + if (classes.test(InputDeviceClass::BATTERY) || classes.test(InputDeviceClass::LIGHT)) { + mController = std::make_unique(contextPtr); + } + + // Keyboard-like devices. + uint32_t keyboardSource = 0; + int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC; + if (classes.test(InputDeviceClass::KEYBOARD)) { + keyboardSource |= AINPUT_SOURCE_KEYBOARD; + } + if (classes.test(InputDeviceClass::ALPHAKEY)) { + keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC; + } + if (classes.test(InputDeviceClass::DPAD)) { + keyboardSource |= AINPUT_SOURCE_DPAD; + } + if (classes.test(InputDeviceClass::GAMEPAD)) { + keyboardSource |= AINPUT_SOURCE_GAMEPAD; + } + + if (keyboardSource != 0) { + mappers.push_back(std::make_unique(contextPtr, readerConfig, + keyboardSource, keyboardType)); + } + + // Cursor-like devices. + if (classes.test(InputDeviceClass::CURSOR)) { + mappers.push_back(std::make_unique(contextPtr, readerConfig)); + } + + // Touchscreens and touchpad devices. + static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY = + sysprop::InputProperties::enable_touchpad_gestures_library().value_or(true); + // TODO(b/272518665): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) touchpads, or + // at least load this setting from the IDC file. + const InputDeviceIdentifier identifier = contextPtr.getDeviceIdentifier(); + const bool isSonyDualShock4Touchpad = identifier.vendor == 0x054c && + (identifier.product == 0x05c4 || identifier.product == 0x09cc); + if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) && + classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) { + mappers.push_back(std::make_unique(contextPtr, readerConfig)); + } else if (classes.test(InputDeviceClass::TOUCH_MT)) { + mappers.push_back(std::make_unique(contextPtr, readerConfig)); + } else if (classes.test(InputDeviceClass::TOUCH)) { + mappers.push_back(std::make_unique(contextPtr, readerConfig)); + } + + // Joystick-like devices. + if (classes.test(InputDeviceClass::JOYSTICK)) { + mappers.push_back(std::make_unique(contextPtr, readerConfig)); + } + + // Motion sensor enabled devices. + if (classes.test(InputDeviceClass::SENSOR)) { + mappers.push_back(std::make_unique(contextPtr, readerConfig)); + } + + // External stylus-like devices. + if (classes.test(InputDeviceClass::EXTERNAL_STYLUS)) { + mappers.push_back(std::make_unique(contextPtr, readerConfig)); + } + return mappers; +} + bool InputDevice::markSupportedKeyCodes(uint32_t sourceMask, const std::vector& keyCodes, uint8_t* outFlags) { bool result = false; diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 80459a2945..6b8bc51a4a 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -332,7 +332,7 @@ std::shared_ptr InputReader::createDeviceLocked( device = std::make_shared(&mContext, deviceId, bumpGenerationLocked(), identifier); } - device->addEventHubDevice(eventHubId); + device->addEventHubDevice(eventHubId, mConfig); return device; } diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index ad45cb102f..2091e16cf7 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -80,7 +80,8 @@ public: [[nodiscard]] std::list setEnabled(bool enabled, nsecs_t when); void dump(std::string& dump, const std::string& eventHubDevStr); - void addEventHubDevice(int32_t eventHubId, bool populateMappers = true); + void addEmptyEventHubDevice(int32_t eventHubId); + void addEventHubDevice(int32_t eventHubId, const InputReaderConfiguration& readerConfig); void removeEventHubDevice(int32_t eventHubId); [[nodiscard]] std::list configure(nsecs_t when, const InputReaderConfiguration& readerConfig, @@ -137,7 +138,7 @@ public: template T& addMapper(int32_t eventHubId, Args... args) { // ensure a device entry exists for this eventHubId - addEventHubDevice(eventHubId, false); + addEmptyEventHubDevice(eventHubId); // create mapper auto& devicePair = mDevices[eventHubId]; @@ -152,7 +153,7 @@ public: template T& addController(int32_t eventHubId) { // ensure a device entry exists for this eventHubId - addEventHubDevice(eventHubId, false); + addEmptyEventHubDevice(eventHubId); // create controller auto& devicePair = mDevices[eventHubId]; @@ -191,6 +192,9 @@ private: typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code); int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc); + std::vector> createMappers( + InputDeviceContext& contextPtr, const InputReaderConfiguration& readerConfig); + PropertyMap mConfiguration; // helpers to interate over the devices collection diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index 1cc614ea4e..4099b9108f 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -68,8 +68,10 @@ void CursorMotionAccumulator::finishSync() { // --- CursorInputMapper --- -CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext), mLastEventTime(std::numeric_limits::min()) {} +CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : InputMapper(deviceContext, readerConfig), + mLastEventTime(std::numeric_limits::min()) {} CursorInputMapper::~CursorInputMapper() { if (mPointerController != nullptr) { diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index 5f7a3adc88..93413f227d 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -53,7 +53,8 @@ private: class CursorInputMapper : public InputMapper { public: - explicit CursorInputMapper(InputDeviceContext& deviceContext); + explicit CursorInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); virtual ~CursorInputMapper(); virtual uint32_t getSources() const override; diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp index bbb641eb76..7100f88e79 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp @@ -23,8 +23,9 @@ namespace android { -ExternalStylusInputMapper::ExternalStylusInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext), mTouchButtonAccumulator(deviceContext) {} +ExternalStylusInputMapper::ExternalStylusInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : InputMapper(deviceContext, readerConfig), mTouchButtonAccumulator(deviceContext) {} uint32_t ExternalStylusInputMapper::getSources() const { return AINPUT_SOURCE_STYLUS; diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h index 3eac10d07f..7f926a72e7 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h @@ -26,7 +26,8 @@ namespace android { class ExternalStylusInputMapper : public InputMapper { public: - explicit ExternalStylusInputMapper(InputDeviceContext& deviceContext); + explicit ExternalStylusInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); virtual ~ExternalStylusInputMapper() = default; uint32_t getSources() const override; diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp index 5dd7bc4f8b..bd86a5a539 100644 --- a/services/inputflinger/reader/mapper/InputMapper.cpp +++ b/services/inputflinger/reader/mapper/InputMapper.cpp @@ -25,7 +25,9 @@ namespace android { -InputMapper::InputMapper(InputDeviceContext& deviceContext) : mDeviceContext(deviceContext) {} +InputMapper::InputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : mDeviceContext(deviceContext) {} InputMapper::~InputMapper() {} diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h index ab573f054b..dda20dffda 100644 --- a/services/inputflinger/reader/mapper/InputMapper.h +++ b/services/inputflinger/reader/mapper/InputMapper.h @@ -31,8 +31,7 @@ namespace android { * different classes of events. * * InputMapper lifecycle: - * - create - * - configure with 0 changes + * - create and configure with 0 changes * - reset * - process, process, process (may occasionally reconfigure with non-zero changes or reset) * - reset @@ -40,7 +39,8 @@ namespace android { */ class InputMapper { public: - explicit InputMapper(InputDeviceContext& deviceContext); + explicit InputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); virtual ~InputMapper(); inline int32_t getDeviceId() { return mDeviceContext.getId(); } diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp index 3e840ee500..3450f86fbf 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp @@ -20,8 +20,9 @@ namespace android { -JoystickInputMapper::JoystickInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext) {} +JoystickInputMapper::JoystickInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : InputMapper(deviceContext, readerConfig) {} JoystickInputMapper::~JoystickInputMapper() {} diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.h b/services/inputflinger/reader/mapper/JoystickInputMapper.h index 6f1c6b7655..c9e92defbf 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.h +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h @@ -22,7 +22,8 @@ namespace android { class JoystickInputMapper : public InputMapper { public: - explicit JoystickInputMapper(InputDeviceContext& deviceContext); + explicit JoystickInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); virtual ~JoystickInputMapper(); virtual uint32_t getSources() const override; diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index f8dd3a85ac..63ea3a877e 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -63,9 +63,10 @@ static bool isSupportedScanCode(int32_t scanCode) { // --- KeyboardInputMapper --- -KeyboardInputMapper::KeyboardInputMapper(InputDeviceContext& deviceContext, uint32_t source, - int32_t keyboardType) - : InputMapper(deviceContext), mSource(source), mKeyboardType(keyboardType) {} +KeyboardInputMapper::KeyboardInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + uint32_t source, int32_t keyboardType) + : InputMapper(deviceContext, readerConfig), mSource(source), mKeyboardType(keyboardType) {} uint32_t KeyboardInputMapper::getSources() const { return mSource; diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index 0cb130d4cd..25fad57e31 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -23,7 +23,9 @@ namespace android { class KeyboardInputMapper : public InputMapper { public: - KeyboardInputMapper(InputDeviceContext& deviceContext, uint32_t source, int32_t keyboardType); + KeyboardInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, uint32_t source, + int32_t keyboardType); ~KeyboardInputMapper() override = default; uint32_t getSources() const override; diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp index e87128825d..9c87c62a7c 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp @@ -28,8 +28,9 @@ static constexpr size_t MAX_SLOTS = 32; // --- MultiTouchInputMapper --- -MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext) - : TouchInputMapper(deviceContext) {} +MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : TouchInputMapper(deviceContext, readerConfig) {} MultiTouchInputMapper::~MultiTouchInputMapper() {} diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h index 5f8bccf9e6..a617420dc9 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h @@ -23,7 +23,8 @@ namespace android { class MultiTouchInputMapper : public TouchInputMapper { public: - explicit MultiTouchInputMapper(InputDeviceContext& deviceContext); + explicit MultiTouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); ~MultiTouchInputMapper() override; [[nodiscard]] std::list reset(nsecs_t when) override; diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp index b181aa0bf8..2ffa5cd3e0 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp @@ -26,8 +26,9 @@ namespace android { -RotaryEncoderInputMapper::RotaryEncoderInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext), mOrientation(ui::ROTATION_0) { +RotaryEncoderInputMapper::RotaryEncoderInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : InputMapper(deviceContext, readerConfig), mOrientation(ui::ROTATION_0) { mSource = AINPUT_SOURCE_ROTARY_ENCODER; } diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h index 37c94422e3..8feaf8e3bc 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h @@ -25,7 +25,8 @@ namespace android { class RotaryEncoderInputMapper : public InputMapper { public: - explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext); + explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); virtual ~RotaryEncoderInputMapper(); virtual uint32_t getSources() const override; diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp index f8a520d9f4..f7f23a41a7 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp @@ -52,8 +52,9 @@ static void convertFromLinuxToAndroid(std::vector& values, } } -SensorInputMapper::SensorInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext) {} +SensorInputMapper::SensorInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : InputMapper(deviceContext, readerConfig) {} SensorInputMapper::~SensorInputMapper() {} diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h index fa36ab30b1..2f3a39665d 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.h +++ b/services/inputflinger/reader/mapper/SensorInputMapper.h @@ -27,7 +27,8 @@ static constexpr ssize_t SENSOR_VEC_LEN = 3; class SensorInputMapper : public InputMapper { public: - explicit SensorInputMapper(InputDeviceContext& deviceContext); + explicit SensorInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); ~SensorInputMapper() override; uint32_t getSources() const override; diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp index f13417a93b..ed0e27067f 100644 --- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp @@ -18,8 +18,9 @@ namespace android { -SingleTouchInputMapper::SingleTouchInputMapper(InputDeviceContext& deviceContext) - : TouchInputMapper(deviceContext) {} +SingleTouchInputMapper::SingleTouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : TouchInputMapper(deviceContext, readerConfig) {} SingleTouchInputMapper::~SingleTouchInputMapper() {} diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h index 662e6bca25..93410078d1 100644 --- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h @@ -23,7 +23,8 @@ namespace android { class SingleTouchInputMapper : public TouchInputMapper { public: - explicit SingleTouchInputMapper(InputDeviceContext& deviceContext); + explicit SingleTouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); ~SingleTouchInputMapper() override; [[nodiscard]] std::list reset(nsecs_t when) override; diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp index c4564a4380..05338da146 100644 --- a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp @@ -20,8 +20,9 @@ namespace android { -SwitchInputMapper::SwitchInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext), mSwitchValues(0), mUpdatedSwitchMask(0) {} +SwitchInputMapper::SwitchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : InputMapper(deviceContext, readerConfig), mSwitchValues(0), mUpdatedSwitchMask(0) {} SwitchInputMapper::~SwitchInputMapper() {} diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.h b/services/inputflinger/reader/mapper/SwitchInputMapper.h index 06d6504684..7ec282b721 100644 --- a/services/inputflinger/reader/mapper/SwitchInputMapper.h +++ b/services/inputflinger/reader/mapper/SwitchInputMapper.h @@ -22,7 +22,8 @@ namespace android { class SwitchInputMapper : public InputMapper { public: - explicit SwitchInputMapper(InputDeviceContext& deviceContext); + explicit SwitchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); virtual ~SwitchInputMapper(); virtual uint32_t getSources() const override; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 9a426bf014..d09bc6590f 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -121,8 +121,9 @@ void RawPointerData::getCentroidOfTouchingPointers(float* outX, float* outY) con // --- TouchInputMapper --- -TouchInputMapper::TouchInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext), +TouchInputMapper::TouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : InputMapper(deviceContext, readerConfig), mTouchButtonAccumulator(deviceContext), mSource(0), mDeviceMode(DeviceMode::DISABLED), diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 0e8ff4b568..e7d66a1c4d 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -146,7 +146,8 @@ struct CookedPointerData { class TouchInputMapper : public InputMapper { public: - explicit TouchInputMapper(InputDeviceContext& deviceContext); + explicit TouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); ~TouchInputMapper() override; uint32_t getSources() const override; diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 8135071da2..ac1ba141ce 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -169,8 +169,9 @@ void gestureInterpreterCallback(void* clientData, const Gesture* gesture) { } // namespace -TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext), +TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : InputMapper(deviceContext, readerConfig), mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter), mPointerController(getContext()->getPointerController(getDeviceId())), mStateConverter(deviceContext), diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index 27cdde1d45..e05109730a 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -37,7 +37,8 @@ namespace android { class TouchpadInputMapper : public InputMapper { public: - explicit TouchpadInputMapper(InputDeviceContext& deviceContext); + explicit TouchpadInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); ~TouchpadInputMapper(); uint32_t getSources() const override; diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp index 2c77fc47c4..8d78d0fd80 100644 --- a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp @@ -20,8 +20,9 @@ namespace android { -VibratorInputMapper::VibratorInputMapper(InputDeviceContext& deviceContext) - : InputMapper(deviceContext), mVibrating(false), mSequence(0) {} +VibratorInputMapper::VibratorInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig) + : InputMapper(deviceContext, readerConfig), mVibrating(false), mSequence(0) {} VibratorInputMapper::~VibratorInputMapper() {} diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.h b/services/inputflinger/reader/mapper/VibratorInputMapper.h index e665973818..384c07512a 100644 --- a/services/inputflinger/reader/mapper/VibratorInputMapper.h +++ b/services/inputflinger/reader/mapper/VibratorInputMapper.h @@ -22,7 +22,8 @@ namespace android { class VibratorInputMapper : public InputMapper { public: - explicit VibratorInputMapper(InputDeviceContext& deviceContext); + explicit VibratorInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); virtual ~VibratorInputMapper(); virtual uint32_t getSources() const override; diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h index 63ca44c402..df601ea920 100644 --- a/services/inputflinger/tests/InputMapperTest.h +++ b/services/inputflinger/tests/InputMapperTest.h @@ -60,7 +60,8 @@ protected: ftl::Flags classes, int bus = 0); template T& addMapperAndConfigure(Args... args) { - T& mapper = mDevice->addMapper(EVENTHUB_ID, args...); + T& mapper = + mDevice->addMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), args...); configureDevice(0); std::list resetArgList = mDevice->reset(ARBITRARY_TIME); resetArgList += mapper.reset(ARBITRARY_TIME); diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index dcf8557d4b..014cc781ee 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -171,8 +171,9 @@ class FakeInputMapper : public InputMapper { std::optional mViewport; public: - FakeInputMapper(InputDeviceContext& deviceContext, uint32_t sources) - : InputMapper(deviceContext), + FakeInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig, + uint32_t sources) + : InputMapper(deviceContext, readerConfig), mSources(sources), mKeyboardType(AINPUT_KEYBOARD_TYPE_NONE), mMetaState(0), @@ -622,7 +623,9 @@ protected: uint32_t sources, const PropertyMap* configuration) { std::shared_ptr device = mReader->newDevice(deviceId, name); - FakeInputMapper& mapper = device->addMapper(eventHubId, sources); + FakeInputMapper& mapper = + device->addMapper(eventHubId, + mFakePolicy->getReaderConfiguration(), sources); mReader->pushNextDevice(device); addDevice(eventHubId, name, classes, configuration); return mapper; @@ -674,8 +677,10 @@ TEST_F(InputReaderTest, GetMergedInputDevices) { // Add two subdevices to device std::shared_ptr device = mReader->newDevice(deviceId, "fake"); // Must add at least one mapper or the device will be ignored! - device->addMapper(eventHubIds[0], AINPUT_SOURCE_KEYBOARD); - device->addMapper(eventHubIds[1], AINPUT_SOURCE_KEYBOARD); + device->addMapper(eventHubIds[0], mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + device->addMapper(eventHubIds[1], mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); // Push same device instance for next device to be added, so they'll have same identifier. mReader->pushNextDevice(device); @@ -695,8 +700,10 @@ TEST_F(InputReaderTest, GetMergedInputDevicesEnabled) { // Add two subdevices to device std::shared_ptr device = mReader->newDevice(deviceId, "fake"); // Must add at least one mapper or the device will be ignored! - device->addMapper(eventHubIds[0], AINPUT_SOURCE_KEYBOARD); - device->addMapper(eventHubIds[1], AINPUT_SOURCE_KEYBOARD); + device->addMapper(eventHubIds[0], mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + device->addMapper(eventHubIds[1], mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); // Push same device instance for next device to be added, so they'll have same identifier. mReader->pushNextDevice(device); @@ -721,7 +728,8 @@ TEST_F(InputReaderTest, WhenEnabledChanges_SendsDeviceResetNotification) { constexpr int32_t eventHubId = 1; std::shared_ptr device = mReader->newDevice(deviceId, "fake"); // Must add at least one mapper or the device will be ignored! - device->addMapper(eventHubId, AINPUT_SOURCE_KEYBOARD); + device->addMapper(eventHubId, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); mReader->pushNextDevice(device); ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); @@ -967,7 +975,8 @@ TEST_F(InputReaderTest, DeviceReset_RandomId) { constexpr int32_t eventHubId = 1; std::shared_ptr device = mReader->newDevice(deviceId, "fake"); // Must add at least one mapper or the device will be ignored! - device->addMapper(eventHubId, AINPUT_SOURCE_KEYBOARD); + device->addMapper(eventHubId, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); mReader->pushNextDevice(device); ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); @@ -1000,7 +1009,8 @@ TEST_F(InputReaderTest, DeviceReset_GenerateIdWithInputReaderSource) { constexpr int32_t eventHubId = 1; std::shared_ptr device = mReader->newDevice(deviceId, "fake"); // Must add at least one mapper or the device will be ignored! - device->addMapper(eventHubId, AINPUT_SOURCE_KEYBOARD); + device->addMapper(eventHubId, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); mReader->pushNextDevice(device); ASSERT_NO_FATAL_FAILURE(addDevice(deviceId, "fake", deviceClass, nullptr)); @@ -1016,7 +1026,8 @@ TEST_F(InputReaderTest, Device_CanDispatchToDisplay) { const char* DEVICE_LOCATION = "USB1"; std::shared_ptr device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION); FakeInputMapper& mapper = - device->addMapper(eventHubId, AINPUT_SOURCE_TOUCHSCREEN); + device->addMapper(eventHubId, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_TOUCHSCREEN); mReader->pushNextDevice(device); const uint8_t hdmi1 = 1; @@ -1059,8 +1070,10 @@ TEST_F(InputReaderTest, WhenEnabledChanges_AllSubdevicesAreUpdated) { constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1}; std::shared_ptr device = mReader->newDevice(deviceId, "fake"); // Must add at least one mapper or the device will be ignored! - device->addMapper(eventHubIds[0], AINPUT_SOURCE_KEYBOARD); - device->addMapper(eventHubIds[1], AINPUT_SOURCE_KEYBOARD); + device->addMapper(eventHubIds[0], mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + device->addMapper(eventHubIds[1], mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); mReader->pushNextDevice(device); mReader->pushNextDevice(device); ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "fake1", deviceClass, nullptr)); @@ -1101,9 +1114,13 @@ TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToSubdeviceMappers) { // Add two subdevices to device std::shared_ptr device = mReader->newDevice(deviceId, "fake"); FakeInputMapper& mapperDevice1 = - device->addMapper(eventHubIds[0], AINPUT_SOURCE_KEYBOARD); + device->addMapper(eventHubIds[0], + mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); FakeInputMapper& mapperDevice2 = - device->addMapper(eventHubIds[1], AINPUT_SOURCE_KEYBOARD); + device->addMapper(eventHubIds[1], + mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); mReader->pushNextDevice(device); mReader->pushNextDevice(device); ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "fake1", deviceClass, nullptr)); @@ -1145,8 +1162,9 @@ TEST_F(InputReaderTest, ChangingPointerCaptureNotifiesInputListener) { class FakeVibratorInputMapper : public FakeInputMapper { public: - FakeVibratorInputMapper(InputDeviceContext& deviceContext, uint32_t sources) - : FakeInputMapper(deviceContext, sources) {} + FakeVibratorInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, uint32_t sources) + : FakeInputMapper(deviceContext, readerConfig, sources) {} std::vector getVibratorIds() override { return getDeviceContext().getVibratorIds(); } }; @@ -1159,7 +1177,9 @@ TEST_F(InputReaderTest, VibratorGetVibratorIds) { const char* DEVICE_LOCATION = "BLUETOOTH"; std::shared_ptr device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION); FakeVibratorInputMapper& mapper = - device->addMapper(eventHubId, AINPUT_SOURCE_KEYBOARD); + device->addMapper(eventHubId, + mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); mReader->pushNextDevice(device); ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr)); @@ -2362,7 +2382,8 @@ TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRe mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "key", "value"); FakeInputMapper& mapper1 = - mDevice->addMapper(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD); + mDevice->addMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); mapper1.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC); mapper1.setMetaState(AMETA_ALT_ON); mapper1.addSupportedKeyCode(AKEYCODE_A); @@ -2374,7 +2395,8 @@ TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRe mapper1.setSwitchState(4, AKEY_STATE_DOWN); FakeInputMapper& mapper2 = - mDevice->addMapper(EVENTHUB_ID, AINPUT_SOURCE_TOUCHSCREEN); + mDevice->addMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_TOUCHSCREEN); mapper2.setMetaState(AMETA_SHIFT_ON); InputReaderConfiguration config; @@ -2455,7 +2477,8 @@ TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRe // 1. Device is disabled if the viewport corresponding to the associated display is not found // 2. Device is disabled when setEnabled API is called TEST_F(InputDeviceTest, Configure_AssignsDisplayPort) { - mDevice->addMapper(EVENTHUB_ID, AINPUT_SOURCE_TOUCHSCREEN); + mDevice->addMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_TOUCHSCREEN); // First Configuration. std::list unused = @@ -2498,7 +2521,8 @@ TEST_F(InputDeviceTest, Configure_AssignsDisplayPort) { TEST_F(InputDeviceTest, Configure_AssignsDisplayUniqueId) { // Device should be enabled by default. mFakePolicy->clearViewports(); - mDevice->addMapper(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD); + mDevice->addMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); std::list unused = mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); ASSERT_TRUE(mDevice->isEnabled()); @@ -2532,7 +2556,8 @@ TEST_F(InputDeviceTest, Configure_AssignsDisplayUniqueId) { TEST_F(InputDeviceTest, Configure_UniqueId_CorrectlyMatches) { mFakePolicy->clearViewports(); - mDevice->addMapper(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD); + mDevice->addMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); std::list unused = mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); @@ -2554,7 +2579,7 @@ TEST_F(InputDeviceTest, DumpDoesNotCrash) { mFakeEventHub->addDevice(TEST_EVENTHUB_ID, "Test EventHub device", InputDeviceClass::BATTERY); InputDevice device(mReader->getContext(), /*id=*/1, /*generation=*/2, /*identifier=*/{}); - device.addEventHubDevice(TEST_EVENTHUB_ID, /*populateMappers=*/true); + device.addEventHubDevice(TEST_EVENTHUB_ID, mFakePolicy->getReaderConfiguration()); device.removeEventHubDevice(TEST_EVENTHUB_ID); std::string dumpStr, eventHubDevStr; device.dump(dumpStr, eventHubDevStr); @@ -3390,7 +3415,9 @@ TEST_F(KeyboardInputMapperTest, Configure_AssignsDisplayPort) { AINPUT_KEYBOARD_TYPE_ALPHABETIC); KeyboardInputMapper& mapper2 = - device2->addMapper(SECOND_EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD, + device2->addMapper(SECOND_EVENTHUB_ID, + mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), @@ -3500,7 +3527,9 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleAfterReattach) { mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); KeyboardInputMapper& mapper2 = - device2->addMapper(SECOND_EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD, + device2->addMapper(SECOND_EVENTHUB_ID, + mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), @@ -3561,7 +3590,9 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleInMultiDevices) { mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); KeyboardInputMapper& mapper2 = - device2->addMapper(SECOND_EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD, + device2->addMapper(SECOND_EVENTHUB_ID, + mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), @@ -3644,7 +3675,8 @@ TEST_F(KeyboardInputMapperTest, Process_DisabledDevice) { } TEST_F(KeyboardInputMapperTest, Configure_AssignKeyboardLayoutInfo) { - mDevice->addMapper(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD, + mDevice->addMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); std::list unused = mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); @@ -9366,7 +9398,9 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { String8("touchScreen")); // Setup the second touch screen device. - MultiTouchInputMapper& mapper2 = device2->addMapper(SECOND_EVENTHUB_ID); + MultiTouchInputMapper& mapper2 = + device2->addMapper(SECOND_EVENTHUB_ID, + mFakePolicy->getReaderConfiguration()); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), /*changes=*/0); diff --git a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp index 28873a39ea..8ed1f31535 100644 --- a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp @@ -38,8 +38,8 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { std::make_shared(data, size); FuzzContainer fuzzer(fdp); - CursorInputMapper& mapper = fuzzer.getMapper(); auto policyConfig = fuzzer.getPolicyConfig(); + CursorInputMapper& mapper = fuzzer.getMapper(policyConfig); // Loop through mapper operations until randomness is exhausted. while (fdp->remaining_bytes() > 0) { diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp index 00b44b5158..0a6327d07a 100644 --- a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp @@ -44,10 +44,10 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { std::make_shared(data, size); FuzzContainer fuzzer(fdp); + auto policyConfig = fuzzer.getPolicyConfig(); KeyboardInputMapper& mapper = - fuzzer.getMapper(fdp->ConsumeIntegral(), + fuzzer.getMapper(policyConfig, fdp->ConsumeIntegral(), fdp->ConsumeIntegral()); - auto policyConfig = fuzzer.getPolicyConfig(); // Loop through mapper operations until randomness is exhausted. while (fdp->remaining_bytes() > 0) { diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp index 70908ff8b6..fdf9f41873 100644 --- a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp @@ -61,8 +61,8 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { std::make_shared(data, size); FuzzContainer fuzzer(fdp); - MultiTouchInputMapper& mapper = fuzzer.getMapper(); auto policyConfig = fuzzer.getPolicyConfig(); + MultiTouchInputMapper& mapper = fuzzer.getMapper(policyConfig); // Loop through mapper operations until randomness is exhausted. while (fdp->remaining_bytes() > 0) { diff --git a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp index c4938f2aec..590207ea22 100644 --- a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp @@ -24,8 +24,8 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { std::make_shared(data, size); FuzzContainer fuzzer(fdp); - SwitchInputMapper& mapper = fuzzer.getMapper(); auto policyConfig = fuzzer.getPolicyConfig(); + SwitchInputMapper& mapper = fuzzer.getMapper(policyConfig); // Loop through mapper operations until randomness is exhausted. while (fdp->remaining_bytes() > 0) { -- GitLab From 7c6712df8514071392b2aaa0f77254e3c3f39ff5 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Tue, 11 Apr 2023 14:27:13 +0000 Subject: [PATCH 1124/1310] InputMapper refactor: CursorInputMapper Refactor CursorInputMapper to be configured with zero changes on initilisation Test: m checkinput && atest --host inputflinger_tests Bug: 256009910 Change-Id: I4102e2045f7ca6d6e38c4fb79c8e2e730f35db73 --- .../reader/mapper/CursorInputMapper.cpp | 232 ++++++++++-------- .../reader/mapper/CursorInputMapper.h | 7 +- .../inputflinger/reader/mapper/InputMapper.h | 2 +- 3 files changed, 135 insertions(+), 106 deletions(-) diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index 4099b9108f..adc893b12b 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -71,7 +71,9 @@ void CursorMotionAccumulator::finishSync() { CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig) : InputMapper(deviceContext, readerConfig), - mLastEventTime(std::numeric_limits::min()) {} + mLastEventTime(std::numeric_limits::min()) { + configureWithZeroChanges(readerConfig); +} CursorInputMapper::~CursorInputMapper() { if (mPointerController != nullptr) { @@ -136,119 +138,28 @@ void CursorInputMapper::dump(std::string& dump) { } std::list CursorInputMapper::reconfigure(nsecs_t when, - const InputReaderConfiguration& config, + const InputReaderConfiguration& readerConfig, uint32_t changes) { - std::list out = InputMapper::reconfigure(when, config, changes); - - if (!changes) { // first time only - mCursorScrollAccumulator.configure(getDeviceContext()); - - // Configure basic parameters. - configureParameters(); - - // Configure device mode. - switch (mParameters.mode) { - case Parameters::Mode::POINTER_RELATIVE: - // Should not happen during first time configuration. - ALOGE("Cannot start a device in MODE_POINTER_RELATIVE, starting in MODE_POINTER"); - mParameters.mode = Parameters::Mode::POINTER; - [[fallthrough]]; - case Parameters::Mode::POINTER: - mSource = AINPUT_SOURCE_MOUSE; - mXPrecision = 1.0f; - mYPrecision = 1.0f; - mXScale = 1.0f; - mYScale = 1.0f; - mPointerController = getContext()->getPointerController(getDeviceId()); - break; - case Parameters::Mode::NAVIGATION: - mSource = AINPUT_SOURCE_TRACKBALL; - mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD; - mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD; - mXScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; - mYScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; - break; - } + std::list out = InputMapper::reconfigure(when, readerConfig, changes); - mVWheelScale = 1.0f; - mHWheelScale = 1.0f; + if (!changes) { + configureWithZeroChanges(readerConfig); + return out; } const bool configurePointerCapture = mParameters.mode != Parameters::Mode::NAVIGATION && - ((!changes && config.pointerCaptureRequest.enable) || - (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE)); + (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE); if (configurePointerCapture) { - if (config.pointerCaptureRequest.enable) { - if (mParameters.mode == Parameters::Mode::POINTER) { - mParameters.mode = Parameters::Mode::POINTER_RELATIVE; - mSource = AINPUT_SOURCE_MOUSE_RELATIVE; - // Keep PointerController around in order to preserve the pointer position. - mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); - } else { - ALOGE("Cannot request pointer capture, device is not in MODE_POINTER"); - } - } else { - if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) { - mParameters.mode = Parameters::Mode::POINTER; - mSource = AINPUT_SOURCE_MOUSE; - } else { - ALOGE("Cannot release pointer capture, device is not in MODE_POINTER_RELATIVE"); - } - } - bumpGeneration(); - if (changes) { - out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId())); - } + configureOnPointerCapture(readerConfig); + out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId())); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED) || - configurePointerCapture) { - if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) { - // Disable any acceleration or scaling for the pointer when Pointer Capture is enabled. - mPointerVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); - mWheelXVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); - mWheelYVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); - } else { - mPointerVelocityControl.setParameters(config.pointerVelocityControlParameters); - mWheelXVelocityControl.setParameters(config.wheelVelocityControlParameters); - mWheelYVelocityControl.setParameters(config.wheelVelocityControlParameters); - } + if ((changes & InputReaderConfiguration::CHANGE_POINTER_SPEED) || configurePointerCapture) { + configureOnChangePointerSpeed(readerConfig); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO) || - configurePointerCapture) { - const bool isPointer = mParameters.mode == Parameters::Mode::POINTER; - - mDisplayId = ADISPLAY_ID_NONE; - if (auto viewport = mDeviceContext.getAssociatedViewport(); viewport) { - // This InputDevice is associated with a viewport. - // Only generate events for the associated display. - const bool mismatchedPointerDisplay = - isPointer && (viewport->displayId != mPointerController->getDisplayId()); - mDisplayId = mismatchedPointerDisplay ? std::nullopt - : std::make_optional(viewport->displayId); - } else if (isPointer) { - // The InputDevice is not associated with a viewport, but it controls the mouse pointer. - mDisplayId = mPointerController->getDisplayId(); - } - - mOrientation = ui::ROTATION_0; - const bool isOrientedDevice = - (mParameters.orientationAware && mParameters.hasAssociatedDisplay); - // InputReader works in the un-rotated display coordinate space, so we don't need to do - // anything if the device is already orientation-aware. If the device is not - // orientation-aware, then we need to apply the inverse rotation of the display so that - // when the display rotation is applied later as a part of the per-window transform, we - // get the expected screen coordinates. When pointer capture is enabled, we do not apply any - // rotations and report values directly from the input device. - if (!isOrientedDevice && mDisplayId && - mParameters.mode != Parameters::Mode::POINTER_RELATIVE) { - if (auto viewport = config.getDisplayViewportById(*mDisplayId); viewport) { - mOrientation = getInverseRotation(viewport->orientation); - } - } - - bumpGeneration(); + if ((changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO) || configurePointerCapture) { + configureOnChangeDisplayInfo(readerConfig); } return out; } @@ -513,4 +424,117 @@ std::optional CursorInputMapper::getAssociatedDisplayId() { return mDisplayId; } +void CursorInputMapper::configureWithZeroChanges(const InputReaderConfiguration& readerConfig) { + // Configuration with zero changes + configureBasicParams(); + if (mParameters.mode != Parameters::Mode::NAVIGATION && + readerConfig.pointerCaptureRequest.enable) { + configureOnPointerCapture(readerConfig); + } + configureOnChangePointerSpeed(readerConfig); + configureOnChangeDisplayInfo(readerConfig); +} + +void CursorInputMapper::configureBasicParams() { + mCursorScrollAccumulator.configure(getDeviceContext()); + + // Configure basic parameters. + configureParameters(); + + // Configure device mode. + switch (mParameters.mode) { + case Parameters::Mode::POINTER_RELATIVE: + // Should not happen during first time configuration. + ALOGE("Cannot start a device in MODE_POINTER_RELATIVE, starting in MODE_POINTER"); + mParameters.mode = Parameters::Mode::POINTER; + [[fallthrough]]; + case Parameters::Mode::POINTER: + mSource = AINPUT_SOURCE_MOUSE; + mXPrecision = 1.0f; + mYPrecision = 1.0f; + mXScale = 1.0f; + mYScale = 1.0f; + mPointerController = getContext()->getPointerController(getDeviceId()); + break; + case Parameters::Mode::NAVIGATION: + mSource = AINPUT_SOURCE_TRACKBALL; + mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD; + mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD; + mXScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; + mYScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; + break; + } + + mVWheelScale = 1.0f; + mHWheelScale = 1.0f; +} + +void CursorInputMapper::configureOnPointerCapture(const InputReaderConfiguration& config) { + if (config.pointerCaptureRequest.enable) { + if (mParameters.mode == Parameters::Mode::POINTER) { + mParameters.mode = Parameters::Mode::POINTER_RELATIVE; + mSource = AINPUT_SOURCE_MOUSE_RELATIVE; + // Keep PointerController around in order to preserve the pointer position. + mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); + } else { + ALOGE("Cannot request pointer capture, device is not in MODE_POINTER"); + } + } else { + if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) { + mParameters.mode = Parameters::Mode::POINTER; + mSource = AINPUT_SOURCE_MOUSE; + } else { + ALOGE("Cannot release pointer capture, device is not in MODE_POINTER_RELATIVE"); + } + } + bumpGeneration(); +} + +void CursorInputMapper::configureOnChangePointerSpeed(const InputReaderConfiguration& config) { + if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) { + // Disable any acceleration or scaling for the pointer when Pointer Capture is enabled. + mPointerVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); + mWheelXVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); + mWheelYVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS); + } else { + mPointerVelocityControl.setParameters(config.pointerVelocityControlParameters); + mWheelXVelocityControl.setParameters(config.wheelVelocityControlParameters); + mWheelYVelocityControl.setParameters(config.wheelVelocityControlParameters); + } +} + +void CursorInputMapper::configureOnChangeDisplayInfo(const InputReaderConfiguration& config) { + const bool isPointer = mParameters.mode == Parameters::Mode::POINTER; + + mDisplayId = ADISPLAY_ID_NONE; + if (auto viewport = mDeviceContext.getAssociatedViewport(); viewport) { + // This InputDevice is associated with a viewport. + // Only generate events for the associated display. + const bool mismatchedPointerDisplay = + isPointer && (viewport->displayId != mPointerController->getDisplayId()); + mDisplayId = + mismatchedPointerDisplay ? std::nullopt : std::make_optional(viewport->displayId); + } else if (isPointer) { + // The InputDevice is not associated with a viewport, but it controls the mouse pointer. + mDisplayId = mPointerController->getDisplayId(); + } + + mOrientation = ui::ROTATION_0; + const bool isOrientedDevice = + (mParameters.orientationAware && mParameters.hasAssociatedDisplay); + // InputReader works in the un-rotated display coordinate space, so we don't need to do + // anything if the device is already orientation-aware. If the device is not + // orientation-aware, then we need to apply the inverse rotation of the display so that + // when the display rotation is applied later as a part of the per-window transform, we + // get the expected screen coordinates. When pointer capture is enabled, we do not apply any + // rotations and report values directly from the input device. + if (!isOrientedDevice && mDisplayId && mParameters.mode != Parameters::Mode::POINTER_RELATIVE) { + if (auto viewport = config.getDisplayViewportById(*mDisplayId); viewport) { + mOrientation = getInverseRotation(viewport->orientation); + } + } + + bumpGeneration(); +} + } // namespace android diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index 93413f227d..a7cdd51df1 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -61,7 +61,7 @@ public: virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; virtual void dump(std::string& dump) override; [[nodiscard]] std::list reconfigure(nsecs_t when, - const InputReaderConfiguration& config, + const InputReaderConfiguration& readerConfig, uint32_t changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; @@ -127,6 +127,11 @@ private: void configureParameters(); void dumpParameters(std::string& dump); + void configureWithZeroChanges(const InputReaderConfiguration& readerConfig); + void configureBasicParams(); + void configureOnPointerCapture(const InputReaderConfiguration& config); + void configureOnChangePointerSpeed(const InputReaderConfiguration& config); + void configureOnChangeDisplayInfo(const InputReaderConfiguration& config); [[nodiscard]] std::list sync(nsecs_t when, nsecs_t readTime); }; diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h index dda20dffda..211be9fd32 100644 --- a/services/inputflinger/reader/mapper/InputMapper.h +++ b/services/inputflinger/reader/mapper/InputMapper.h @@ -33,7 +33,7 @@ namespace android { * InputMapper lifecycle: * - create and configure with 0 changes * - reset - * - process, process, process (may occasionally reconfigure with non-zero changes or reset) + * - process, process, process (may occasionally reconfigure or reset) * - reset * - destroy */ -- GitLab From 63b6361f3e23ab44c7e1adfe06ce9e11270d5100 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 12 Apr 2023 11:00:23 -0700 Subject: [PATCH 1125/1310] Convert input event type to enum class This will increase type safety and simplify some of the printing. Bug: 274073185 Test: m checkinput Change-Id: I848c2f156cc23232c50d2338b4788be3232dba1a --- include/input/Input.h | 31 +++- libs/gui/tests/EndToEndNativeInputTest.cpp | 18 +-- libs/input/Input.cpp | 126 +++++++++------- .../android/os/InputEventInjectionSync.aidl | 3 + libs/input/tests/InputEvent_test.cpp | 4 +- .../tests/InputPublisherAndConsumer_test.cpp | 13 +- services/inputflinger/Android.bp | 1 + .../dispatcher/InputDispatcher.cpp | 34 ++--- .../tests/InputDispatcher_test.cpp | 137 ++++++++---------- 9 files changed, 193 insertions(+), 174 deletions(-) diff --git a/include/input/Input.h b/include/input/Input.h index a033535f4b..1e810b438a 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -210,7 +210,20 @@ vec2 transformWithoutTranslation(const ui::Transform& transform, const vec2& xy) */ float transformAngle(const ui::Transform& transform, float angleRadians); -const char* inputEventTypeToString(int32_t type); +/** + * The type of the InputEvent. + * This should have 1:1 correspondence with the values of anonymous enum defined in input.h. + */ +enum class InputEventType { + KEY = AINPUT_EVENT_TYPE_KEY, + MOTION = AINPUT_EVENT_TYPE_MOTION, + FOCUS = AINPUT_EVENT_TYPE_FOCUS, + CAPTURE = AINPUT_EVENT_TYPE_CAPTURE, + DRAG = AINPUT_EVENT_TYPE_DRAG, + TOUCH_MODE = AINPUT_EVENT_TYPE_TOUCH_MODE, + ftl_first = KEY, + ftl_last = TOUCH_MODE, +}; std::string inputEventSourceToString(int32_t source); @@ -482,7 +495,7 @@ class InputEvent : public AInputEvent { public: virtual ~InputEvent() { } - virtual int32_t getType() const = 0; + virtual InputEventType getType() const = 0; inline int32_t getId() const { return mId; } @@ -513,6 +526,8 @@ protected: std::array mHmac; }; +std::ostream& operator<<(std::ostream& out, const InputEvent& event); + /* * Key events. */ @@ -520,7 +535,7 @@ class KeyEvent : public InputEvent { public: virtual ~KeyEvent() { } - virtual int32_t getType() const { return AINPUT_EVENT_TYPE_KEY; } + virtual InputEventType getType() const { return InputEventType::KEY; } inline int32_t getAction() const { return mAction; } @@ -571,7 +586,7 @@ class MotionEvent : public InputEvent { public: virtual ~MotionEvent() { } - virtual int32_t getType() const { return AINPUT_EVENT_TYPE_MOTION; } + virtual InputEventType getType() const { return InputEventType::MOTION; } inline int32_t getAction() const { return mAction; } @@ -899,7 +914,7 @@ class FocusEvent : public InputEvent { public: virtual ~FocusEvent() {} - virtual int32_t getType() const override { return AINPUT_EVENT_TYPE_FOCUS; } + virtual InputEventType getType() const override { return InputEventType::FOCUS; } inline bool getHasFocus() const { return mHasFocus; } @@ -918,7 +933,7 @@ class CaptureEvent : public InputEvent { public: virtual ~CaptureEvent() {} - virtual int32_t getType() const override { return AINPUT_EVENT_TYPE_CAPTURE; } + virtual InputEventType getType() const override { return InputEventType::CAPTURE; } inline bool getPointerCaptureEnabled() const { return mPointerCaptureEnabled; } @@ -937,7 +952,7 @@ class DragEvent : public InputEvent { public: virtual ~DragEvent() {} - virtual int32_t getType() const override { return AINPUT_EVENT_TYPE_DRAG; } + virtual InputEventType getType() const override { return InputEventType::DRAG; } inline bool isExiting() const { return mIsExiting; } @@ -961,7 +976,7 @@ class TouchModeEvent : public InputEvent { public: virtual ~TouchModeEvent() {} - virtual int32_t getType() const override { return AINPUT_EVENT_TYPE_TOUCH_MODE; } + virtual InputEventType getType() const override { return InputEventType::TOUCH_MODE; } inline bool isInTouchMode() const { return mIsInTouchMode; } diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp index 9e8c65c678..a5734b7af0 100644 --- a/libs/gui/tests/EndToEndNativeInputTest.cpp +++ b/libs/gui/tests/EndToEndNativeInputTest.cpp @@ -164,7 +164,7 @@ public: void assertFocusChange(bool hasFocus) { InputEvent *ev = consumeEvent(); ASSERT_NE(ev, nullptr); - ASSERT_EQ(AINPUT_EVENT_TYPE_FOCUS, ev->getType()); + ASSERT_EQ(InputEventType::FOCUS, ev->getType()); FocusEvent *focusEvent = static_cast(ev); EXPECT_EQ(hasFocus, focusEvent->getHasFocus()); } @@ -172,7 +172,7 @@ public: void expectTap(int x, int y) { InputEvent* ev = consumeEvent(); ASSERT_NE(ev, nullptr); - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType()); + ASSERT_EQ(InputEventType::MOTION, ev->getType()); MotionEvent* mev = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction()); EXPECT_EQ(x, mev->getX(0)); @@ -181,7 +181,7 @@ public: ev = consumeEvent(); ASSERT_NE(ev, nullptr); - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType()); + ASSERT_EQ(InputEventType::MOTION, ev->getType()); mev = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction()); EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS); @@ -190,7 +190,7 @@ public: void expectTapWithFlag(int x, int y, int32_t flags) { InputEvent *ev = consumeEvent(); ASSERT_NE(ev, nullptr); - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType()); + ASSERT_EQ(InputEventType::MOTION, ev->getType()); MotionEvent *mev = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction()); EXPECT_EQ(x, mev->getX(0)); @@ -199,7 +199,7 @@ public: ev = consumeEvent(); ASSERT_NE(ev, nullptr); - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType()); + ASSERT_EQ(InputEventType::MOTION, ev->getType()); mev = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction()); EXPECT_EQ(flags, mev->getFlags() & flags); @@ -208,7 +208,7 @@ public: void expectTapInDisplayCoordinates(int displayX, int displayY) { InputEvent *ev = consumeEvent(); ASSERT_NE(ev, nullptr); - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType()); + ASSERT_EQ(InputEventType::MOTION, ev->getType()); MotionEvent *mev = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction()); const PointerCoords &coords = *mev->getRawPointerCoords(0 /*pointerIndex*/); @@ -218,7 +218,7 @@ public: ev = consumeEvent(); ASSERT_NE(ev, nullptr); - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType()); + ASSERT_EQ(InputEventType::MOTION, ev->getType()); mev = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction()); EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS); @@ -227,7 +227,7 @@ public: void expectKey(uint32_t keycode) { InputEvent *ev = consumeEvent(); ASSERT_NE(ev, nullptr); - ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, ev->getType()); + ASSERT_EQ(InputEventType::KEY, ev->getType()); KeyEvent *keyEvent = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, keyEvent->getAction()); EXPECT_EQ(keycode, keyEvent->getKeyCode()); @@ -235,7 +235,7 @@ public: ev = consumeEvent(); ASSERT_NE(ev, nullptr); - ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, ev->getType()); + ASSERT_EQ(InputEventType::KEY, ev->getType()); keyEvent = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_UP, keyEvent->getAction()); EXPECT_EQ(keycode, keyEvent->getKeyCode()); diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index 4dbf575490..00925ba555 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -170,30 +170,6 @@ float transformAngle(const ui::Transform& transform, float angleRadians) { return atan2f(transformedPoint.x, -transformedPoint.y); } -const char* inputEventTypeToString(int32_t type) { - switch (type) { - case AINPUT_EVENT_TYPE_KEY: { - return "KEY"; - } - case AINPUT_EVENT_TYPE_MOTION: { - return "MOTION"; - } - case AINPUT_EVENT_TYPE_FOCUS: { - return "FOCUS"; - } - case AINPUT_EVENT_TYPE_CAPTURE: { - return "CAPTURE"; - } - case AINPUT_EVENT_TYPE_DRAG: { - return "DRAG"; - } - case AINPUT_EVENT_TYPE_TOUCH_MODE: { - return "TOUCH_MODE"; - } - } - return "UNKNOWN"; -} - std::string inputEventSourceToString(int32_t source) { if (source == AINPUT_SOURCE_UNKNOWN) { return "UNKNOWN"; @@ -287,6 +263,37 @@ int32_t InputEvent::nextId() { return idGen.nextId(); } +std::ostream& operator<<(std::ostream& out, const InputEvent& event) { + switch (event.getType()) { + case InputEventType::KEY: { + const KeyEvent& keyEvent = static_cast(event); + out << keyEvent; + return out; + } + case InputEventType::MOTION: { + const MotionEvent& motionEvent = static_cast(event); + out << motionEvent; + return out; + } + case InputEventType::FOCUS: { + out << "FocusEvent"; + return out; + } + case InputEventType::CAPTURE: { + out << "CaptureEvent"; + return out; + } + case InputEventType::DRAG: { + out << "DragEvent"; + return out; + } + case InputEventType::TOUCH_MODE: { + out << "TouchModeEvent"; + return out; + } + } +} + // --- KeyEvent --- const char* KeyEvent::getLabel(int32_t keyCode) { @@ -1165,44 +1172,51 @@ TouchModeEvent* PooledInputEventFactory::createTouchModeEvent() { void PooledInputEventFactory::recycle(InputEvent* event) { switch (event->getType()) { - case AINPUT_EVENT_TYPE_KEY: - if (mKeyEventPool.size() < mMaxPoolSize) { - mKeyEventPool.push(std::unique_ptr(static_cast(event))); - return; + case InputEventType::KEY: { + if (mKeyEventPool.size() < mMaxPoolSize) { + mKeyEventPool.push(std::unique_ptr(static_cast(event))); + return; + } + break; } - break; - case AINPUT_EVENT_TYPE_MOTION: - if (mMotionEventPool.size() < mMaxPoolSize) { - mMotionEventPool.push(std::unique_ptr(static_cast(event))); - return; + case InputEventType::MOTION: { + if (mMotionEventPool.size() < mMaxPoolSize) { + mMotionEventPool.push( + std::unique_ptr(static_cast(event))); + return; + } + break; } - break; - case AINPUT_EVENT_TYPE_FOCUS: - if (mFocusEventPool.size() < mMaxPoolSize) { - mFocusEventPool.push(std::unique_ptr(static_cast(event))); - return; + case InputEventType::FOCUS: { + if (mFocusEventPool.size() < mMaxPoolSize) { + mFocusEventPool.push(std::unique_ptr(static_cast(event))); + return; + } + break; } - break; - case AINPUT_EVENT_TYPE_CAPTURE: - if (mCaptureEventPool.size() < mMaxPoolSize) { - mCaptureEventPool.push( - std::unique_ptr(static_cast(event))); - return; + case InputEventType::CAPTURE: { + if (mCaptureEventPool.size() < mMaxPoolSize) { + mCaptureEventPool.push( + std::unique_ptr(static_cast(event))); + return; + } + break; } - break; - case AINPUT_EVENT_TYPE_DRAG: - if (mDragEventPool.size() < mMaxPoolSize) { - mDragEventPool.push(std::unique_ptr(static_cast(event))); - return; + case InputEventType::DRAG: { + if (mDragEventPool.size() < mMaxPoolSize) { + mDragEventPool.push(std::unique_ptr(static_cast(event))); + return; + } + break; } - break; - case AINPUT_EVENT_TYPE_TOUCH_MODE: - if (mTouchModeEventPool.size() < mMaxPoolSize) { - mTouchModeEventPool.push( - std::unique_ptr(static_cast(event))); - return; + case InputEventType::TOUCH_MODE: { + if (mTouchModeEventPool.size() < mMaxPoolSize) { + mTouchModeEventPool.push( + std::unique_ptr(static_cast(event))); + return; + } + break; } - break; } delete event; } diff --git a/libs/input/android/os/InputEventInjectionSync.aidl b/libs/input/android/os/InputEventInjectionSync.aidl index 95d24cb443..2d225fa452 100644 --- a/libs/input/android/os/InputEventInjectionSync.aidl +++ b/libs/input/android/os/InputEventInjectionSync.aidl @@ -33,4 +33,7 @@ enum InputEventInjectionSync { /* Waits for the input event to be completely processed. */ WAIT_FOR_FINISHED = 2, + + ftl_first = NONE, + ftl_last = WAIT_FOR_FINISHED, } diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp index 59125dd428..a9655730fc 100644 --- a/libs/input/tests/InputEvent_test.cpp +++ b/libs/input/tests/InputEvent_test.cpp @@ -197,7 +197,7 @@ TEST_F(KeyEventTest, Properties) { ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME); ASSERT_EQ(id, event.getId()); - ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, event.getType()); + ASSERT_EQ(InputEventType::KEY, event.getType()); ASSERT_EQ(2, event.getDeviceId()); ASSERT_EQ(AINPUT_SOURCE_GAMEPAD, event.getSource()); ASSERT_EQ(DISPLAY_ID, event.getDisplayId()); @@ -346,7 +346,7 @@ void MotionEventTest::initializeEventWithHistory(MotionEvent* event) { void MotionEventTest::assertEqualsEventWithHistory(const MotionEvent* event) { // Check properties. ASSERT_EQ(mId, event->getId()); - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType()); + ASSERT_EQ(InputEventType::MOTION, event->getType()); ASSERT_EQ(2, event->getDeviceId()); ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, event->getSource()); ASSERT_EQ(DISPLAY_ID, event->getDisplayId()); diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp index 965fda73b4..3ecf8eed50 100644 --- a/libs/input/tests/InputPublisherAndConsumer_test.cpp +++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp @@ -98,8 +98,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeKeyEvent() { ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event"; - ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, event->getType()) - << "consumer should have returned a key event"; + ASSERT_EQ(InputEventType::KEY, event->getType()) << "consumer should have returned a key event"; KeyEvent* keyEvent = static_cast(event); EXPECT_EQ(seq, consumeSeq); @@ -207,7 +206,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() { ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event"; - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType()) + ASSERT_EQ(InputEventType::MOTION, event->getType()) << "consumer should have returned a motion event"; MotionEvent* motionEvent = static_cast(event); @@ -298,7 +297,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeFocusEvent() { ASSERT_EQ(OK, status) << "consumer consume should return OK"; ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event"; - ASSERT_EQ(AINPUT_EVENT_TYPE_FOCUS, event->getType()) + ASSERT_EQ(InputEventType::FOCUS, event->getType()) << "consumer should have returned a focus event"; FocusEvent* focusEvent = static_cast(event); @@ -339,7 +338,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeCaptureEvent() { ASSERT_EQ(OK, status) << "consumer consume should return OK"; ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event"; - ASSERT_EQ(AINPUT_EVENT_TYPE_CAPTURE, event->getType()) + ASSERT_EQ(InputEventType::CAPTURE, event->getType()) << "consumer should have returned a capture event"; const CaptureEvent* captureEvent = static_cast(event); @@ -381,7 +380,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeDragEvent() { ASSERT_EQ(OK, status) << "consumer consume should return OK"; ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event"; - ASSERT_EQ(AINPUT_EVENT_TYPE_DRAG, event->getType()) + ASSERT_EQ(InputEventType::DRAG, event->getType()) << "consumer should have returned a drag event"; const DragEvent& dragEvent = static_cast(*event); @@ -423,7 +422,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeTouchModeEvent() { ASSERT_EQ(OK, status) << "consumer consume should return OK"; ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event"; - ASSERT_EQ(AINPUT_EVENT_TYPE_TOUCH_MODE, event->getType()) + ASSERT_EQ(InputEventType::TOUCH_MODE, event->getType()) << "consumer should have returned a touch mode event"; const TouchModeEvent& touchModeEvent = static_cast(*event); diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp index b8854352ad..e04481ca50 100644 --- a/services/inputflinger/Android.bp +++ b/services/inputflinger/Android.bp @@ -213,6 +213,7 @@ phony { name: "checkinput", required: [ // native targets + "libgui_test", "libinput", "libinputflinger", "inputflinger_tests", diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 851f13c0d5..c39c408e0d 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -117,11 +117,7 @@ inline nsecs_t now() { return systemTime(SYSTEM_TIME_MONOTONIC); } -inline const char* toString(bool value) { - return value ? "true" : "false"; -} - -inline const std::string toString(const sp& binder) { +inline const std::string binderToString(const sp& binder) { if (binder == nullptr) { return ""; } @@ -2909,7 +2905,7 @@ std::string InputDispatcher::dumpWindowForTouchOcclusion(const WindowInfo* info, info->frameBottom, dumpRegion(info->touchableRegion).c_str(), info->name.c_str(), info->inputConfig.string().c_str(), toString(info->token != nullptr), info->applicationInfo.name.c_str(), - toString(info->applicationInfo.token).c_str()); + binderToString(info->applicationInfo.token).c_str()); } bool InputDispatcher::isTouchTrustedLocked(const TouchOcclusionInfo& occlusionInfo) const { @@ -3623,8 +3619,8 @@ void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime, const sp& connection, bool notify) { if (DEBUG_DISPATCH_CYCLE) { - ALOGD("channel '%s' ~ abortBrokenDispatchCycle - notify=%s", - connection->getInputChannelName().c_str(), toString(notify)); + LOG(DEBUG) << "channel '" << connection->getInputChannelName() << "'~ " << __func__ + << " - notify=" << toString(notify); } // Clear the dispatch queues. @@ -4376,10 +4372,10 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev std::chrono::milliseconds timeout, uint32_t policyFlags) { if (debugInboundEventDetails()) { - ALOGD("injectInputEvent - eventType=%d, targetUid=%s, syncMode=%d, timeout=%lld, " - "policyFlags=0x%08x", - event->getType(), targetUid ? std::to_string(*targetUid).c_str() : "none", syncMode, - timeout.count(), policyFlags); + LOG(DEBUG) << __func__ << ": targetUid=" << toString(targetUid) + << ", syncMode=" << ftl::enum_string(syncMode) << ", timeout=" << timeout.count() + << "ms, policyFlags=0x" << std::hex << policyFlags << std::dec + << ", event=" << *event; } nsecs_t endTime = now() + std::chrono::duration_cast(timeout).count(); @@ -4398,7 +4394,7 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev std::queue> injectedEntries; switch (event->getType()) { - case AINPUT_EVENT_TYPE_KEY: { + case InputEventType::KEY: { const KeyEvent& incomingKey = static_cast(*event); int32_t action = incomingKey.getAction(); if (!validateKeyEvent(action)) { @@ -4444,7 +4440,7 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev break; } - case AINPUT_EVENT_TYPE_MOTION: { + case InputEventType::MOTION: { const MotionEvent& motionEvent = static_cast(*event); const int32_t action = motionEvent.getAction(); const bool isPointerEvent = @@ -4520,7 +4516,7 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev } default: - ALOGW("Cannot inject %s events", inputEventTypeToString(event->getType())); + LOG(WARNING) << "Cannot inject " << ftl::enum_string(event->getType()) << " events"; return InputEventInjectionResult::FAILED; } @@ -4610,14 +4606,14 @@ std::unique_ptr InputDispatcher::verifyInputEvent(const Inpu std::array calculatedHmac; std::unique_ptr result; switch (event.getType()) { - case AINPUT_EVENT_TYPE_KEY: { + case InputEventType::KEY: { const KeyEvent& keyEvent = static_cast(event); VerifiedKeyEvent verifiedKeyEvent = verifiedKeyEventFromKeyEvent(keyEvent); result = std::make_unique(verifiedKeyEvent); calculatedHmac = sign(verifiedKeyEvent); break; } - case AINPUT_EVENT_TYPE_MOTION: { + case InputEventType::MOTION: { const MotionEvent& motionEvent = static_cast(event); VerifiedMotionEvent verifiedMotionEvent = verifiedMotionEventFromMotionEvent(motionEvent); @@ -5519,14 +5515,14 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) { windowInfo->frameTop, windowInfo->frameRight, windowInfo->frameBottom, windowInfo->globalScaleFactor, windowInfo->applicationInfo.name.c_str(), - toString(windowInfo->applicationInfo.token).c_str()); + binderToString(windowInfo->applicationInfo.token).c_str()); dump += dumpRegion(windowInfo->touchableRegion); dump += StringPrintf(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%" PRId64 "ms, hasToken=%s, " "touchOcclusionMode=%s\n", windowInfo->ownerPid, windowInfo->ownerUid, millis(windowInfo->dispatchingTimeout), - toString(windowInfo->token != nullptr), + binderToString(windowInfo->token).c_str(), toString(windowInfo->touchOcclusionMode).c_str()); windowInfo->transform.dump(dump, "transform", INDENT4); } diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index fb808eb2a3..5e51bfc1ea 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -213,7 +213,7 @@ public: void assertFilterInputEventWasCalled(const NotifyKeyArgs& args) { assertFilterInputEventWasCalledInternal([&args](const InputEvent& event) { - ASSERT_EQ(event.getType(), AINPUT_EVENT_TYPE_KEY); + ASSERT_EQ(event.getType(), InputEventType::KEY); EXPECT_EQ(event.getDisplayId(), args.displayId); const auto& keyEvent = static_cast(event); @@ -224,7 +224,7 @@ public: void assertFilterInputEventWasCalled(const NotifyMotionArgs& args, vec2 point) { assertFilterInputEventWasCalledInternal([&](const InputEvent& event) { - ASSERT_EQ(event.getType(), AINPUT_EVENT_TYPE_MOTION); + ASSERT_EQ(event.getType(), InputEventType::MOTION); EXPECT_EQ(event.getDisplayId(), args.displayId); const auto& motionEvent = static_cast(event); @@ -530,17 +530,21 @@ private: bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) override { std::scoped_lock lock(mLock); switch (inputEvent->getType()) { - case AINPUT_EVENT_TYPE_KEY: { + case InputEventType::KEY: { const KeyEvent* keyEvent = static_cast(inputEvent); mFilteredEvent = std::make_unique(*keyEvent); break; } - case AINPUT_EVENT_TYPE_MOTION: { + case InputEventType::MOTION: { const MotionEvent* motionEvent = static_cast(inputEvent); mFilteredEvent = std::make_unique(*motionEvent); break; } + default: { + ADD_FAILURE() << "Should only filter keys or motions"; + break; + } } return true; } @@ -924,7 +928,7 @@ public: ASSERT_EQ(OK, status); } - void consumeEvent(int32_t expectedEventType, int32_t expectedAction, + void consumeEvent(InputEventType expectedEventType, int32_t expectedAction, std::optional expectedDisplayId, std::optional expectedFlags) { InputEvent* event = consume(); @@ -932,15 +936,15 @@ public: ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; ASSERT_EQ(expectedEventType, event->getType()) - << mName.c_str() << " expected " << inputEventTypeToString(expectedEventType) - << " event, got " << inputEventTypeToString(event->getType()) << " event"; + << mName.c_str() << " expected " << ftl::enum_string(expectedEventType) + << " event, got " << *event; if (expectedDisplayId.has_value()) { EXPECT_EQ(expectedDisplayId, event->getDisplayId()); } switch (expectedEventType) { - case AINPUT_EVENT_TYPE_KEY: { + case InputEventType::KEY: { const KeyEvent& keyEvent = static_cast(*event); EXPECT_EQ(expectedAction, keyEvent.getAction()); if (expectedFlags.has_value()) { @@ -948,7 +952,7 @@ public: } break; } - case AINPUT_EVENT_TYPE_MOTION: { + case InputEventType::MOTION: { const MotionEvent& motionEvent = static_cast(*event); assertMotionAction(expectedAction, motionEvent.getAction()); @@ -957,21 +961,18 @@ public: } break; } - case AINPUT_EVENT_TYPE_FOCUS: { + case InputEventType::FOCUS: { FAIL() << "Use 'consumeFocusEvent' for FOCUS events"; } - case AINPUT_EVENT_TYPE_CAPTURE: { + case InputEventType::CAPTURE: { FAIL() << "Use 'consumeCaptureEvent' for CAPTURE events"; } - case AINPUT_EVENT_TYPE_TOUCH_MODE: { + case InputEventType::TOUCH_MODE: { FAIL() << "Use 'consumeTouchModeEvent' for TOUCH_MODE events"; } - case AINPUT_EVENT_TYPE_DRAG: { + case InputEventType::DRAG: { FAIL() << "Use 'consumeDragEvent' for DRAG events"; } - default: { - FAIL() << mName.c_str() << ": invalid event type: " << expectedEventType; - } } } @@ -983,9 +984,8 @@ public: return nullptr; } - if (event->getType() != AINPUT_EVENT_TYPE_MOTION) { - ADD_FAILURE() << mName << " expected a MotionEvent, got " - << inputEventTypeToString(event->getType()) << " event"; + if (event->getType() != InputEventType::MOTION) { + ADD_FAILURE() << mName << " expected a MotionEvent, got " << *event; return nullptr; } return static_cast(event); @@ -1001,9 +1001,8 @@ public: InputEvent* event = consume(); ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; - ASSERT_EQ(AINPUT_EVENT_TYPE_FOCUS, event->getType()) - << "Got " << inputEventTypeToString(event->getType()) - << " event instead of FOCUS event"; + ASSERT_EQ(InputEventType::FOCUS, event->getType()) + << "Instead of FocusEvent, got " << *event; ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) << mName.c_str() << ": event displayId should always be NONE."; @@ -1016,9 +1015,8 @@ public: const InputEvent* event = consume(); ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; - ASSERT_EQ(AINPUT_EVENT_TYPE_CAPTURE, event->getType()) - << "Got " << inputEventTypeToString(event->getType()) - << " event instead of CAPTURE event"; + ASSERT_EQ(InputEventType::CAPTURE, event->getType()) + << "Instead of CaptureEvent, got " << *event; ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) << mName.c_str() << ": event displayId should always be NONE."; @@ -1031,9 +1029,7 @@ public: const InputEvent* event = consume(); ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; - ASSERT_EQ(AINPUT_EVENT_TYPE_DRAG, event->getType()) - << "Got " << inputEventTypeToString(event->getType()) - << " event instead of DRAG event"; + ASSERT_EQ(InputEventType::DRAG, event->getType()) << "Instead of DragEvent, got " << *event; EXPECT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) << mName.c_str() << ": event displayId should always be NONE."; @@ -1048,9 +1044,8 @@ public: const InputEvent* event = consume(); ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; - ASSERT_EQ(AINPUT_EVENT_TYPE_TOUCH_MODE, event->getType()) - << "Got " << inputEventTypeToString(event->getType()) - << " event instead of TOUCH_MODE event"; + ASSERT_EQ(InputEventType::TOUCH_MODE, event->getType()) + << "Instead of TouchModeEvent, got " << *event; ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) << mName.c_str() << ": event displayId should always be NONE."; @@ -1063,23 +1058,23 @@ public: if (event == nullptr) { return; } - if (event->getType() == AINPUT_EVENT_TYPE_KEY) { + if (event->getType() == InputEventType::KEY) { KeyEvent& keyEvent = static_cast(*event); ADD_FAILURE() << "Received key event " << KeyEvent::actionToString(keyEvent.getAction()); - } else if (event->getType() == AINPUT_EVENT_TYPE_MOTION) { + } else if (event->getType() == InputEventType::MOTION) { MotionEvent& motionEvent = static_cast(*event); ADD_FAILURE() << "Received motion event " << MotionEvent::actionToString(motionEvent.getAction()); - } else if (event->getType() == AINPUT_EVENT_TYPE_FOCUS) { + } else if (event->getType() == InputEventType::FOCUS) { FocusEvent& focusEvent = static_cast(*event); ADD_FAILURE() << "Received focus event, hasFocus = " << (focusEvent.getHasFocus() ? "true" : "false"); - } else if (event->getType() == AINPUT_EVENT_TYPE_CAPTURE) { + } else if (event->getType() == InputEventType::CAPTURE) { const auto& captureEvent = static_cast(*event); ADD_FAILURE() << "Received capture event, pointerCaptureEnabled = " << (captureEvent.getPointerCaptureEnabled() ? "true" : "false"); - } else if (event->getType() == AINPUT_EVENT_TYPE_TOUCH_MODE) { + } else if (event->getType() == InputEventType::TOUCH_MODE) { const auto& touchModeEvent = static_cast(*event); ADD_FAILURE() << "Received touch mode event, inTouchMode = " << (touchModeEvent.isInTouchMode() ? "true" : "false"); @@ -1239,12 +1234,11 @@ public: void setWindowOffset(float offsetX, float offsetY) { mInfo.transform.set(offsetX, offsetY); } void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) { - consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId, - expectedFlags); + consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId, expectedFlags); } void consumeKeyUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) { - consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, expectedDisplayId, expectedFlags); + consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, expectedDisplayId, expectedFlags); } void consumeMotionCancel(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, @@ -1266,7 +1260,7 @@ public: void consumeAnyMotionDown(std::optional expectedDisplayId = std::nullopt, std::optional expectedFlags = std::nullopt) { - consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId, + consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId, expectedFlags); } @@ -1275,25 +1269,25 @@ public: int32_t expectedFlags = 0) { int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); - consumeEvent(AINPUT_EVENT_TYPE_MOTION, action, expectedDisplayId, expectedFlags); + consumeEvent(InputEventType::MOTION, action, expectedDisplayId, expectedFlags); } void consumeMotionPointerUp(int32_t pointerIdx, int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { int32_t action = AMOTION_EVENT_ACTION_POINTER_UP | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); - consumeEvent(AINPUT_EVENT_TYPE_MOTION, action, expectedDisplayId, expectedFlags); + consumeEvent(InputEventType::MOTION, action, expectedDisplayId, expectedFlags); } void consumeMotionUp(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { - consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId, + consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId, expectedFlags); } void consumeMotionOutside(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { - consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE, expectedDisplayId, + consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE, expectedDisplayId, expectedFlags); } @@ -1301,7 +1295,7 @@ public: int32_t expectedFlags = 0) { InputEvent* event = consume(); ASSERT_NE(nullptr, event); - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType()); + ASSERT_EQ(InputEventType::MOTION, event->getType()); const MotionEvent& motionEvent = static_cast(*event); EXPECT_EQ(AMOTION_EVENT_ACTION_OUTSIDE, motionEvent.getActionMasked()); EXPECT_EQ(0.f, motionEvent.getRawPointerCoords(0)->getX()); @@ -1326,7 +1320,7 @@ public: ASSERT_THAT(*motionEvent, matcher); } - void consumeEvent(int32_t expectedEventType, int32_t expectedAction, + void consumeEvent(InputEventType expectedEventType, int32_t expectedAction, std::optional expectedDisplayId, std::optional expectedFlags) { ASSERT_NE(mInputReceiver, nullptr) << "Invalid consume event on window with no receiver"; @@ -1375,9 +1369,8 @@ public: ADD_FAILURE() << "Consume failed : no event"; return nullptr; } - if (event->getType() != AINPUT_EVENT_TYPE_MOTION) { - ADD_FAILURE() << "Instead of motion event, got " - << inputEventTypeToString(event->getType()); + if (event->getType() != InputEventType::MOTION) { + ADD_FAILURE() << "Instead of motion event, got " << *event; return nullptr; } return static_cast(event); @@ -3678,7 +3671,7 @@ TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsKeyStream) { // on the app side. NotifyDeviceResetArgs args(/*id=*/10, /*eventTime=*/20, DEVICE_ID); mDispatcher->notifyDeviceReset(&args); - window->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT, + window->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT, AKEY_EVENT_FLAG_CANCELED); } @@ -4752,8 +4745,8 @@ public: sp getToken() { return mInputReceiver->getToken(); } void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) { - mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_DOWN, - expectedDisplayId, expectedFlags); + mInputReceiver->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId, + expectedFlags); } std::optional receiveEvent() { return mInputReceiver->receiveEvent(); } @@ -4761,17 +4754,17 @@ public: void finishEvent(uint32_t consumeSeq) { return mInputReceiver->finishEvent(consumeSeq); } void consumeMotionDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) { - mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN, + mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId, expectedFlags); } void consumeMotionMove(int32_t expectedDisplayId, int32_t expectedFlags = 0) { - mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE, + mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE, expectedDisplayId, expectedFlags); } void consumeMotionUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) { - mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_UP, + mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId, expectedFlags); } @@ -4785,7 +4778,7 @@ public: void consumeMotionPointerDown(int32_t pointerIdx) { int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); - mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, action, ADISPLAY_ID_DEFAULT, + mInputReceiver->consumeEvent(InputEventType::MOTION, action, ADISPLAY_ID_DEFAULT, /*expectedFlags=*/0); } @@ -4795,8 +4788,8 @@ public: ADD_FAILURE() << "No event was produced"; return nullptr; } - if (event->getType() != AINPUT_EVENT_TYPE_MOTION) { - ADD_FAILURE() << "Received event of type " << event->getType() << " instead of motion"; + if (event->getType() != InputEventType::MOTION) { + ADD_FAILURE() << "Expected MotionEvent, got " << *event; return nullptr; } return static_cast(event); @@ -4952,7 +4945,7 @@ TEST_F(InputDispatcherTest, TestMoveEvent) { motionArgs.pointerCoords[0].getX() - 10); mDispatcher->notifyMotion(&motionArgs); - window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE, ADISPLAY_ID_DEFAULT, + window->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE, ADISPLAY_ID_DEFAULT, /*expectedFlags=*/0); } @@ -5423,8 +5416,7 @@ protected: InputEvent* repeatEvent = mWindow->consume(); ASSERT_NE(nullptr, repeatEvent); - uint32_t eventType = repeatEvent->getType(); - ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, eventType); + ASSERT_EQ(InputEventType::KEY, repeatEvent->getType()); KeyEvent* repeatKeyEvent = static_cast(repeatEvent); uint32_t eventAction = repeatKeyEvent->getAction(); @@ -5439,7 +5431,7 @@ protected: mDispatcher->notifyKey(&keyArgs); // Window should receive key down event. - mWindow->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT, + mWindow->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT, /*expectedFlags=*/0); } }; @@ -5612,7 +5604,7 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, SetInputWindow_MultiDisplayFocus) mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {}}}); // Old focus should receive a cancel event. - windowInSecondary->consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_NONE, + windowInSecondary->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_NONE, AKEY_EVENT_FLAG_CANCELED); // Test inject a key down, should timeout because of no target window. @@ -5883,7 +5875,7 @@ protected: InputEvent* received = mWindow->consume(); ASSERT_NE(nullptr, received); ASSERT_EQ(resolvedDeviceId, received->getDeviceId()); - ASSERT_EQ(received->getType(), AINPUT_EVENT_TYPE_KEY); + ASSERT_EQ(received->getType(), InputEventType::KEY); KeyEvent& keyEvent = static_cast(*received); ASSERT_EQ(flags, keyEvent.getFlags()); } @@ -5918,7 +5910,7 @@ protected: InputEvent* received = mWindow->consume(); ASSERT_NE(nullptr, received); ASSERT_EQ(resolvedDeviceId, received->getDeviceId()); - ASSERT_EQ(received->getType(), AINPUT_EVENT_TYPE_MOTION); + ASSERT_EQ(received->getType(), InputEventType::MOTION); MotionEvent& motionEvent = static_cast(*received); ASSERT_EQ(flags, motionEvent.getFlags()); } @@ -6099,9 +6091,8 @@ protected: ASSERT_NE(nullptr, event) << name.c_str() << ": consumer should have returned non-NULL event."; - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType()) - << name.c_str() << "expected " << inputEventTypeToString(AINPUT_EVENT_TYPE_MOTION) - << " event, got " << inputEventTypeToString(event->getType()) << " event"; + ASSERT_EQ(InputEventType::MOTION, event->getType()) + << name.c_str() << ": expected MotionEvent, got " << *event; const MotionEvent& motionEvent = static_cast(*event); assertMotionAction(expectedAction, motionEvent.getAction()); @@ -6798,7 +6789,7 @@ TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsive) { FOCUSED_WINDOW_LOCATION)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; mFocusedWindow->consumeMotionDown(); - mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE, + mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE, ADISPLAY_ID_DEFAULT, /*flags=*/0); // We consumed all events, so no ANR ASSERT_TRUE(mDispatcher->waitForIdle()); @@ -6872,7 +6863,7 @@ TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsiveWithSameTimeout // At the same time, FLAG_WATCH_OUTSIDE_TOUCH targets should not receive any events. TEST_F(InputDispatcherMultiWindowAnr, DuringAnr_SecondTapIsIgnored) { tapOnFocusedWindow(); - mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE, + mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE, ADISPLAY_ID_DEFAULT, /*flags=*/0); // Receive the events, but don't respond std::optional downEventSequenceNum = mFocusedWindow->receiveEvent(); // ACTION_DOWN @@ -7001,7 +6992,7 @@ TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) { generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {FOCUSED_WINDOW_LOCATION}); mDispatcher->notifyMotion(&motionArgs); - mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE, + mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE, ADISPLAY_ID_DEFAULT, /*flags=*/0); // Touch Window 2 @@ -7022,7 +7013,7 @@ TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) { ASSERT_TRUE(moveOrCancelSequenceNum); mFocusedWindow->finishEvent(*moveOrCancelSequenceNum); ASSERT_NE(nullptr, event); - ASSERT_EQ(event->getType(), AINPUT_EVENT_TYPE_MOTION); + ASSERT_EQ(event->getType(), InputEventType::MOTION); MotionEvent& motionEvent = static_cast(*event); if (motionEvent.getAction() == AMOTION_EVENT_ACTION_MOVE) { mFocusedWindow->consumeMotionCancel(); @@ -8234,7 +8225,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenMultiDisplays) { .displayId(SECOND_DISPLAY_ID) .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) .build())); - windowInSecondary->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN, + windowInSecondary->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN, SECOND_DISPLAY_ID, /*expectedFlag=*/0); // Update window again. mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {windowInSecondary}}}); -- GitLab From f50aebc9bb0581517c4c47e34e17f30d793e7814 Mon Sep 17 00:00:00 2001 From: Nolan Scobie Date: Thu, 13 Apr 2023 17:49:18 +0000 Subject: [PATCH 1126/1310] Convert ALOGV to ALOGD in various libgui_test cases ALOGV was not appearing in the logs collected by TradeFed/atest/friends. I don't want to investigate too deeply right now, since ALOGD works fine. Bug: 257123981 Bug: 277347398 Change-Id: If3811e5e553ecf97c35967128f3d59b0079d9f3a Test: N/A (manually compared logs collected after running atest) --- libs/gui/tests/BLASTBufferQueue_test.cpp | 6 +++--- libs/gui/tests/BufferItemConsumer_test.cpp | 10 +++++----- libs/gui/tests/BufferQueue_test.cpp | 4 ++-- libs/gui/tests/CpuConsumer_test.cpp | 20 +++++++++---------- libs/gui/tests/GLTest.cpp | 4 ++-- .../gui/tests/IGraphicBufferProducer_test.cpp | 4 ++-- libs/gui/tests/StreamSplitter_test.cpp | 4 ++-- libs/gui/tests/SurfaceTextureClient_test.cpp | 4 ++-- 8 files changed, 28 insertions(+), 28 deletions(-) diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp index 7067c111a3..a3ad6807c5 100644 --- a/libs/gui/tests/BLASTBufferQueue_test.cpp +++ b/libs/gui/tests/BLASTBufferQueue_test.cpp @@ -179,13 +179,13 @@ protected: BLASTBufferQueueTest() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); + ALOGD("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); } ~BLASTBufferQueueTest() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("End test: %s.%s", testInfo->test_case_name(), testInfo->name()); + ALOGD("End test: %s.%s", testInfo->test_case_name(), testInfo->name()); } void SetUp() { @@ -206,7 +206,7 @@ protected: const ui::Size& resolution = displayState.layerStackSpaceRect; mDisplayWidth = resolution.getWidth(); mDisplayHeight = resolution.getHeight(); - ALOGV("Display: %dx%d orientation:%d", mDisplayWidth, mDisplayHeight, + ALOGD("Display: %dx%d orientation:%d", mDisplayWidth, mDisplayHeight, displayState.orientation); mSurfaceControl = mClient->createSurface(String8("TestSurface"), mDisplayWidth, diff --git a/libs/gui/tests/BufferItemConsumer_test.cpp b/libs/gui/tests/BufferItemConsumer_test.cpp index fc6551c8e6..6880678050 100644 --- a/libs/gui/tests/BufferItemConsumer_test.cpp +++ b/libs/gui/tests/BufferItemConsumer_test.cpp @@ -68,7 +68,7 @@ class BufferItemConsumerTest : public ::testing::Test { void HandleBufferFreed() { std::lock_guard lock(mMutex); mFreedBufferCount++; - ALOGV("HandleBufferFreed, mFreedBufferCount=%d", mFreedBufferCount); + ALOGD("HandleBufferFreed, mFreedBufferCount=%d", mFreedBufferCount); } void DequeueBuffer(int* outSlot) { @@ -80,7 +80,7 @@ class BufferItemConsumerTest : public ::testing::Test { nullptr, nullptr); ASSERT_GE(ret, 0); - ALOGV("dequeueBuffer: slot=%d", slot); + ALOGD("dequeueBuffer: slot=%d", slot); if (ret & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) { ret = mProducer->requestBuffer(slot, &mBuffers[slot]); ASSERT_EQ(NO_ERROR, ret); @@ -89,7 +89,7 @@ class BufferItemConsumerTest : public ::testing::Test { } void QueueBuffer(int slot) { - ALOGV("enqueueBuffer: slot=%d", slot); + ALOGD("enqueueBuffer: slot=%d", slot); IGraphicBufferProducer::QueueBufferInput bufferInput( 0ULL, true, HAL_DATASPACE_UNKNOWN, Rect::INVALID_RECT, NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, Fence::NO_FENCE); @@ -104,12 +104,12 @@ class BufferItemConsumerTest : public ::testing::Test { status_t ret = mBIC->acquireBuffer(&buffer, 0, false); ASSERT_EQ(NO_ERROR, ret); - ALOGV("acquireBuffer: slot=%d", buffer.mSlot); + ALOGD("acquireBuffer: slot=%d", buffer.mSlot); *outSlot = buffer.mSlot; } void ReleaseBuffer(int slot) { - ALOGV("releaseBuffer: slot=%d", slot); + ALOGD("releaseBuffer: slot=%d", slot); BufferItem buffer; buffer.mSlot = slot; buffer.mGraphicBuffer = mBuffers[slot]; diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp index d1208ee5ae..2f1fd3e78f 100644 --- a/libs/gui/tests/BufferQueue_test.cpp +++ b/libs/gui/tests/BufferQueue_test.cpp @@ -49,14 +49,14 @@ protected: BufferQueueTest() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("Begin test: %s.%s", testInfo->test_case_name(), + ALOGD("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); } ~BufferQueueTest() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("End test: %s.%s", testInfo->test_case_name(), + ALOGD("End test: %s.%s", testInfo->test_case_name(), testInfo->name()); } diff --git a/libs/gui/tests/CpuConsumer_test.cpp b/libs/gui/tests/CpuConsumer_test.cpp index 00e32d9124..0a14afac55 100644 --- a/libs/gui/tests/CpuConsumer_test.cpp +++ b/libs/gui/tests/CpuConsumer_test.cpp @@ -62,7 +62,7 @@ protected: const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); CpuConsumerTestParams params = GetParam(); - ALOGV("** Starting test %s (%d x %d, %d, 0x%x)", + ALOGD("** Starting test %s (%d x %d, %d, 0x%x)", test_info->name(), params.width, params.height, params.maxLockedBuffers, params.format); @@ -582,7 +582,7 @@ TEST_P(CpuConsumerTest, FromCpuManyInQueue) { uint32_t stride[numInQueue]; for (int i = 0; i < numInQueue; i++) { - ALOGV("Producing frame %d", i); + ALOGD("Producing frame %d", i); ASSERT_NO_FATAL_FAILURE(produceOneFrame(mANW, params, time[i], &stride[i])); } @@ -590,7 +590,7 @@ TEST_P(CpuConsumerTest, FromCpuManyInQueue) { // Consume for (int i = 0; i < numInQueue; i++) { - ALOGV("Consuming frame %d", i); + ALOGD("Consuming frame %d", i); CpuConsumer::LockedBuffer b; err = mCC->lockNextBuffer(&b); ASSERT_NO_ERROR(err, "getNextBuffer error: "); @@ -624,7 +624,7 @@ TEST_P(CpuConsumerTest, FromCpuLockMax) { uint32_t stride; for (int i = 0; i < params.maxLockedBuffers + 1; i++) { - ALOGV("Producing frame %d", i); + ALOGD("Producing frame %d", i); ASSERT_NO_FATAL_FAILURE(produceOneFrame(mANW, params, time, &stride)); } @@ -633,7 +633,7 @@ TEST_P(CpuConsumerTest, FromCpuLockMax) { std::vector b(params.maxLockedBuffers); for (int i = 0; i < params.maxLockedBuffers; i++) { - ALOGV("Locking frame %d", i); + ALOGD("Locking frame %d", i); err = mCC->lockNextBuffer(&b[i]); ASSERT_NO_ERROR(err, "getNextBuffer error: "); @@ -647,16 +647,16 @@ TEST_P(CpuConsumerTest, FromCpuLockMax) { checkAnyBuffer(b[i], GetParam().format); } - ALOGV("Locking frame %d (too many)", params.maxLockedBuffers); + ALOGD("Locking frame %d (too many)", params.maxLockedBuffers); CpuConsumer::LockedBuffer bTooMuch; err = mCC->lockNextBuffer(&bTooMuch); ASSERT_TRUE(err == NOT_ENOUGH_DATA) << "Allowing too many locks"; - ALOGV("Unlocking frame 0"); + ALOGD("Unlocking frame 0"); err = mCC->unlockBuffer(b[0]); ASSERT_NO_ERROR(err, "Could not unlock buffer 0: "); - ALOGV("Locking frame %d (should work now)", params.maxLockedBuffers); + ALOGD("Locking frame %d (should work now)", params.maxLockedBuffers); err = mCC->lockNextBuffer(&bTooMuch); ASSERT_NO_ERROR(err, "Did not allow new lock after unlock"); @@ -669,11 +669,11 @@ TEST_P(CpuConsumerTest, FromCpuLockMax) { checkAnyBuffer(bTooMuch, GetParam().format); - ALOGV("Unlocking extra buffer"); + ALOGD("Unlocking extra buffer"); err = mCC->unlockBuffer(bTooMuch); ASSERT_NO_ERROR(err, "Could not unlock extra buffer: "); - ALOGV("Locking frame %d (no more available)", params.maxLockedBuffers + 1); + ALOGD("Locking frame %d (no more available)", params.maxLockedBuffers + 1); err = mCC->lockNextBuffer(&b[0]); ASSERT_EQ(BAD_VALUE, err) << "Not out of buffers somehow"; diff --git a/libs/gui/tests/GLTest.cpp b/libs/gui/tests/GLTest.cpp index a1405fcb11..3ae4b6d052 100644 --- a/libs/gui/tests/GLTest.cpp +++ b/libs/gui/tests/GLTest.cpp @@ -31,7 +31,7 @@ static int abs(int value) { void GLTest::SetUp() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); + ALOGD("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); ASSERT_EQ(EGL_SUCCESS, eglGetError()); @@ -135,7 +135,7 @@ void GLTest::TearDown() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("End test: %s.%s", testInfo->test_case_name(), testInfo->name()); + ALOGD("End test: %s.%s", testInfo->test_case_name(), testInfo->name()); } EGLint const* GLTest::getConfigAttribs() { diff --git a/libs/gui/tests/IGraphicBufferProducer_test.cpp b/libs/gui/tests/IGraphicBufferProducer_test.cpp index 3427731fff..e6cb89cb83 100644 --- a/libs/gui/tests/IGraphicBufferProducer_test.cpp +++ b/libs/gui/tests/IGraphicBufferProducer_test.cpp @@ -84,7 +84,7 @@ protected: virtual void SetUp() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("Begin test: %s.%s", testInfo->test_case_name(), + ALOGD("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); mMC = new MockConsumer; @@ -114,7 +114,7 @@ protected: virtual void TearDown() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("End test: %s.%s", testInfo->test_case_name(), + ALOGD("End test: %s.%s", testInfo->test_case_name(), testInfo->name()); } diff --git a/libs/gui/tests/StreamSplitter_test.cpp b/libs/gui/tests/StreamSplitter_test.cpp index b65cddaea3..2f14924a15 100644 --- a/libs/gui/tests/StreamSplitter_test.cpp +++ b/libs/gui/tests/StreamSplitter_test.cpp @@ -36,14 +36,14 @@ protected: StreamSplitterTest() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("Begin test: %s.%s", testInfo->test_case_name(), + ALOGD("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); } ~StreamSplitterTest() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("End test: %s.%s", testInfo->test_case_name(), + ALOGD("End test: %s.%s", testInfo->test_case_name(), testInfo->name()); } }; diff --git a/libs/gui/tests/SurfaceTextureClient_test.cpp b/libs/gui/tests/SurfaceTextureClient_test.cpp index c7458a3755..82b66972d9 100644 --- a/libs/gui/tests/SurfaceTextureClient_test.cpp +++ b/libs/gui/tests/SurfaceTextureClient_test.cpp @@ -42,7 +42,7 @@ protected: virtual void SetUp() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("Begin test: %s.%s", testInfo->test_case_name(), + ALOGD("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); sp producer; @@ -99,7 +99,7 @@ protected: const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("End test: %s.%s", testInfo->test_case_name(), + ALOGD("End test: %s.%s", testInfo->test_case_name(), testInfo->name()); } -- GitLab From e3da4bbc9c7e0f07a7241ee937c39f1007a9a449 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 5 Apr 2023 23:51:23 +0000 Subject: [PATCH 1127/1310] Notify InputListener when there an changes to input devices There are now more than one input listener stages that need to know when the devices change. Notify listeners directly instead of routing it through the policy. Bug: 275726706 Test: atest inputflinger_tests Change-Id: I37019f8069bad3bbc585805f792beb514faa8cb1 --- services/inputflinger/InputListener.cpp | 7 ++++- services/inputflinger/InputProcessor.cpp | 6 ++++ services/inputflinger/InputProcessor.h | 1 + services/inputflinger/NotifyArgs.cpp | 7 +++++ .../UnwantedInteractionBlocker.cpp | 7 +++++ .../inputflinger/UnwantedInteractionBlocker.h | 4 ++- .../inputflinger/dispatcher/InputDispatcher.h | 1 + services/inputflinger/include/InputListener.h | 2 ++ services/inputflinger/include/NotifyArgs.h | 22 +++++++++++--- .../UnwantedInteractionBlockerInterface.h | 12 +------- services/inputflinger/reader/InputReader.cpp | 2 ++ .../inputflinger/tests/InputReader_test.cpp | 2 ++ .../inputflinger/tests/TestInputListener.cpp | 12 ++++++++ .../inputflinger/tests/TestInputListener.h | 8 ++++- .../tests/UnwantedInteractionBlocker_test.cpp | 30 +++++++++---------- .../tests/fuzzers/MapperHelpers.h | 1 + 16 files changed, 91 insertions(+), 33 deletions(-) diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp index d33b29888f..1bc1adf154 100644 --- a/services/inputflinger/InputListener.cpp +++ b/services/inputflinger/InputListener.cpp @@ -24,7 +24,6 @@ #include #include -#include #include using android::base::StringPrintf; @@ -47,6 +46,7 @@ Visitor(V...) -> Visitor; void InputListenerInterface::notify(const NotifyArgs& generalArgs) { Visitor v{ + [&](const NotifyInputDevicesChangedArgs& args) { notifyInputDevicesChanged(args); }, [&](const NotifyConfigurationChangedArgs& args) { notifyConfigurationChanged(&args); }, [&](const NotifyKeyArgs& args) { notifyKey(&args); }, [&](const NotifyMotionArgs& args) { notifyMotion(&args); }, @@ -73,6 +73,11 @@ static inline void traceEvent(const char* functionName, int32_t id) { QueuedInputListener::QueuedInputListener(InputListenerInterface& innerListener) : mInnerListener(innerListener) {} +void QueuedInputListener::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) { + traceEvent(__func__, args.id); + mArgsQueue.emplace_back(args); +} + void QueuedInputListener::notifyConfigurationChanged( const NotifyConfigurationChangedArgs* args) { traceEvent(__func__, args->id); diff --git a/services/inputflinger/InputProcessor.cpp b/services/inputflinger/InputProcessor.cpp index a98b383037..6c0bcffd6b 100644 --- a/services/inputflinger/InputProcessor.cpp +++ b/services/inputflinger/InputProcessor.cpp @@ -413,6 +413,12 @@ void InputProcessor::setMotionClassifierEnabled(bool enabled) { } } +void InputProcessor::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) { + // pass through + mQueuedListener.notify(args); + mQueuedListener.flush(); +} + void InputProcessor::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) { // pass through mQueuedListener.notifyConfigurationChanged(args); diff --git a/services/inputflinger/InputProcessor.h b/services/inputflinger/InputProcessor.h index f4d02b6f30..01795a8983 100644 --- a/services/inputflinger/InputProcessor.h +++ b/services/inputflinger/InputProcessor.h @@ -245,6 +245,7 @@ class InputProcessor : public InputProcessorInterface { public: explicit InputProcessor(InputListenerInterface& listener); + void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override; void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override; void notifyKey(const NotifyKeyArgs* args) override; void notifyMotion(const NotifyMotionArgs* args) override; diff --git a/services/inputflinger/NotifyArgs.cpp b/services/inputflinger/NotifyArgs.cpp index 5f2a22f467..408fbed99e 100644 --- a/services/inputflinger/NotifyArgs.cpp +++ b/services/inputflinger/NotifyArgs.cpp @@ -29,6 +29,12 @@ using android::base::StringPrintf; namespace android { +// --- NotifyInputDevicesChangedArgs --- + +NotifyInputDevicesChangedArgs::NotifyInputDevicesChangedArgs(int32_t id, + std::vector infos) + : id(id), inputDeviceInfos(std::move(infos)) {} + // --- NotifyConfigurationChangedArgs --- NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs(int32_t id, nsecs_t eventTime) @@ -234,6 +240,7 @@ Visitor(V...) -> Visitor; const char* toString(const NotifyArgs& args) { Visitor toStringVisitor{ + [&](const NotifyInputDevicesChangedArgs&) { return "NotifyInputDevicesChangedArgs"; }, [&](const NotifyConfigurationChangedArgs&) { return "NotifyConfigurationChangedArgs"; }, [&](const NotifyKeyArgs&) { return "NotifyKeyArgs"; }, [&](const NotifyMotionArgs&) { return "NotifyMotionArgs"; }, diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp index ae20f862dc..6d43e8d009 100644 --- a/services/inputflinger/UnwantedInteractionBlocker.cpp +++ b/services/inputflinger/UnwantedInteractionBlocker.cpp @@ -411,6 +411,13 @@ void UnwantedInteractionBlocker::notifyPointerCaptureChanged( } void UnwantedInteractionBlocker::notifyInputDevicesChanged( + const NotifyInputDevicesChangedArgs& args) { + onInputDevicesChanged(args.inputDeviceInfos); + mQueuedListener.notify(args); + mQueuedListener.flush(); +} + +void UnwantedInteractionBlocker::onInputDevicesChanged( const std::vector& inputDevices) { std::scoped_lock lock(mLock); if (!mEnablePalmRejection) { diff --git a/services/inputflinger/UnwantedInteractionBlocker.h b/services/inputflinger/UnwantedInteractionBlocker.h index 5d0dde8e64..3bc5240eb6 100644 --- a/services/inputflinger/UnwantedInteractionBlocker.h +++ b/services/inputflinger/UnwantedInteractionBlocker.h @@ -90,6 +90,7 @@ public: explicit UnwantedInteractionBlocker(InputListenerInterface& listener); explicit UnwantedInteractionBlocker(InputListenerInterface& listener, bool enablePalmRejection); + void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override; void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override; void notifyKey(const NotifyKeyArgs* args) override; void notifyMotion(const NotifyMotionArgs* args) override; @@ -99,7 +100,6 @@ public: void notifyDeviceReset(const NotifyDeviceResetArgs* args) override; void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override; - void notifyInputDevicesChanged(const std::vector& inputDevices) override; void dump(std::string& dump) override; void monitor() override; @@ -123,6 +123,8 @@ private: // Call this function for outbound events so that they can be logged when logging is enabled. void enqueueOutboundMotionLocked(const NotifyMotionArgs& args) REQUIRES(mLock); + + void onInputDevicesChanged(const std::vector& inputDevices); }; class SlotState { diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 2246d47e48..aaf12146af 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -93,6 +93,7 @@ public: status_t start() override; status_t stop() override; + void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override{}; void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override; void notifyKey(const NotifyKeyArgs* args) override; void notifyMotion(const NotifyMotionArgs* args) override; diff --git a/services/inputflinger/include/InputListener.h b/services/inputflinger/include/InputListener.h index 1bb19686e7..d1b86c8cc4 100644 --- a/services/inputflinger/include/InputListener.h +++ b/services/inputflinger/include/InputListener.h @@ -37,6 +37,7 @@ public: InputListenerInterface& operator=(const InputListenerInterface&) = delete; virtual ~InputListenerInterface() { } + virtual void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) = 0; virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) = 0; virtual void notifyKey(const NotifyKeyArgs* args) = 0; virtual void notifyMotion(const NotifyMotionArgs* args) = 0; @@ -58,6 +59,7 @@ class QueuedInputListener : public InputListenerInterface { public: explicit QueuedInputListener(InputListenerInterface& innerListener); + virtual void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override; virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override; virtual void notifyKey(const NotifyKeyArgs* args) override; virtual void notifyMotion(const NotifyMotionArgs* args) override; diff --git a/services/inputflinger/include/NotifyArgs.h b/services/inputflinger/include/NotifyArgs.h index c46f90501f..f12482b1b2 100644 --- a/services/inputflinger/include/NotifyArgs.h +++ b/services/inputflinger/include/NotifyArgs.h @@ -24,6 +24,20 @@ namespace android { +/* Describes a change in any of the connected input devices. */ +struct NotifyInputDevicesChangedArgs { + int32_t id; + std::vector inputDeviceInfos; + + inline NotifyInputDevicesChangedArgs() {} + + NotifyInputDevicesChangedArgs(int32_t id, std::vector infos); + + bool operator==(const NotifyInputDevicesChangedArgs& rhs) const = default; + + NotifyInputDevicesChangedArgs(const NotifyInputDevicesChangedArgs& other) = default; +}; + /* Describes a configuration change event. */ struct NotifyConfigurationChangedArgs { int32_t id; @@ -183,7 +197,6 @@ struct NotifyDeviceResetArgs { /* Describes a change in the state of Pointer Capture. */ struct NotifyPointerCaptureChangedArgs { - // The sequence number of the Pointer Capture request, if enabled. int32_t id; nsecs_t eventTime; @@ -211,9 +224,10 @@ struct NotifyVibratorStateArgs { NotifyVibratorStateArgs(const NotifyVibratorStateArgs& other) = default; }; -using NotifyArgs = std::variant; +using NotifyArgs = + std::variant; const char* toString(const NotifyArgs& args); diff --git a/services/inputflinger/include/UnwantedInteractionBlockerInterface.h b/services/inputflinger/include/UnwantedInteractionBlockerInterface.h index 1a6f8472a5..64c6114ceb 100644 --- a/services/inputflinger/include/UnwantedInteractionBlockerInterface.h +++ b/services/inputflinger/include/UnwantedInteractionBlockerInterface.h @@ -27,23 +27,13 @@ namespace android { */ class UnwantedInteractionBlockerInterface : public InputListenerInterface { public: - /* Notifies the input reader policy that some input devices have changed - * and provides information about all current input devices. - * Important! This call should happen on the same thread as the calls to the - * InputListenerInterface methods. - * That is, same thread should call 'notifyMotion' and 'notifyInputDevicesChanged' and - * 'notifyDeviceReset'. If this architecture changes, we will need to make the implementation - * of this interface thread-safe. - */ - virtual void notifyInputDevicesChanged(const std::vector& inputDevices) = 0; - /** * Dump the state of the interaction blocker. * This method may be called on any thread (usually by the input manager on a binder thread). */ virtual void dump(std::string& dump) = 0; - /* Called by the heatbeat to ensures that the blocker has not deadlocked. */ + /* Called by the heartbeat to ensures that the blocker has not deadlocked. */ virtual void monitor() = 0; UnwantedInteractionBlockerInterface() {} diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 81ac03b7b3..02d79b08ea 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -157,6 +157,8 @@ void InputReader::loopOnce() { if (oldGeneration != mGeneration) { inputDevicesChanged = true; inputDevices = getInputDevicesLocked(); + notifyArgs.emplace_back( + NotifyInputDevicesChangedArgs{mContext.getNextId(), inputDevices}); } } // release lock diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index fb082da8e1..2b8b70b54c 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -603,6 +603,7 @@ protected: mReader->loopOnce(); mReader->loopOnce(); ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyInputDevicesChangedWasCalled()); ASSERT_NO_FATAL_FAILURE(mFakeEventHub->assertQueueIsEmpty()); } @@ -1324,6 +1325,7 @@ protected: // to the test device will show up in mReader. We wait for those input devices to // show up before beginning the tests. ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyInputDevicesChangedWasCalled()); ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); } diff --git a/services/inputflinger/tests/TestInputListener.cpp b/services/inputflinger/tests/TestInputListener.cpp index 2801072b6a..ac1dc05f6d 100644 --- a/services/inputflinger/tests/TestInputListener.cpp +++ b/services/inputflinger/tests/TestInputListener.cpp @@ -29,6 +29,14 @@ TestInputListener::TestInputListener(std::chrono::milliseconds eventHappenedTime TestInputListener::~TestInputListener() {} +void TestInputListener::assertNotifyInputDevicesChangedWasCalled( + NotifyInputDevicesChangedArgs* outEventArgs) { + ASSERT_NO_FATAL_FAILURE( + assertCalled(outEventArgs, + "Expected notifyInputDevicesChanged() " + "to have been called.")); +} + void TestInputListener::assertNotifyConfigurationChangedWasCalled( NotifyConfigurationChangedArgs* outEventArgs) { ASSERT_NO_FATAL_FAILURE( @@ -168,6 +176,10 @@ void TestInputListener::addToQueue(const NotifyArgsType* args) { mCondition.notify_all(); } +void TestInputListener::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) { + addToQueue(&args); +} + void TestInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) { addToQueue(args); } diff --git a/services/inputflinger/tests/TestInputListener.h b/services/inputflinger/tests/TestInputListener.h index 9665f702a1..da2cab3351 100644 --- a/services/inputflinger/tests/TestInputListener.h +++ b/services/inputflinger/tests/TestInputListener.h @@ -35,6 +35,9 @@ public: using TimePoint = std::chrono::time_point; + void assertNotifyInputDevicesChangedWasCalled( + NotifyInputDevicesChangedArgs* outEventArgs = nullptr); + void assertNotifyConfigurationChangedWasCalled( NotifyConfigurationChangedArgs* outEventArgs = nullptr); @@ -76,6 +79,8 @@ private: template void addToQueue(const NotifyArgsType* args); + virtual void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override; + virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override; virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) override; @@ -97,7 +102,8 @@ private: const std::chrono::milliseconds mEventHappenedTimeout; const std::chrono::milliseconds mEventDidNotHappenTimeout; - std::tuple, // + std::tuple, // + std::vector, // std::vector, // std::vector, // std::vector, // diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp index 2a9ace00c5..be731b1a2f 100644 --- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp +++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp @@ -492,7 +492,7 @@ TEST_F(UnwantedInteractionBlockerTest, DeviceResetIsPassedToNextListener) { */ TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenResetHappens) { NotifyMotionArgs args; - mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); + mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); mBlocker->notifyMotion( &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}}))); mBlocker->notifyMotion( @@ -505,7 +505,7 @@ TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenResetHappens) { } TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenStylusSourceWithFingerToolIsReceived) { - mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); + mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}}); args.pointerProperties[0].toolType = ToolType::FINGER; args.source = AINPUT_SOURCE_STYLUS; @@ -518,14 +518,14 @@ TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenStylusSourceWithFingerToolIsRe */ TEST_F(UnwantedInteractionBlockerTest, NoResetIfDeviceInfoChanges) { NotifyMotionArgs args; - mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); + mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); mBlocker->notifyMotion( &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}}))); mBlocker->notifyMotion( &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}}))); // Now pretend the device changed, even though nothing is different for DEVICE_ID in practice. - mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); + mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); // The MOVE event continues the gesture that started before 'devices changed', so it should not // cause a crash. @@ -538,7 +538,7 @@ TEST_F(UnwantedInteractionBlockerTest, NoResetIfDeviceInfoChanges) { */ TEST_F(UnwantedInteractionBlockerTest, StylusAfterTouchWorks) { NotifyMotionArgs args; - mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); + mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); mBlocker->notifyMotion(&args); args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{4, 5, 6}}); @@ -568,7 +568,7 @@ TEST_F(UnwantedInteractionBlockerTest, StylusAfterTouchWorks) { * options */ TEST_F(UnwantedInteractionBlockerTest, DumpCanBeAccessedOnAnotherThread) { - mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); + mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); mBlocker->notifyMotion(&args1); std::thread dumpThread([this]() { @@ -587,7 +587,7 @@ TEST_F(UnwantedInteractionBlockerTest, DumpCanBeAccessedOnAnotherThread) { * of the touch is large. This is an integration test that checks that this filter kicks in. */ TEST_F(UnwantedInteractionBlockerTest, HeuristicFilterWorks) { - mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); + mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); // Small touch down NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); mBlocker->notifyMotion(&args1); @@ -613,9 +613,9 @@ TEST_F(UnwantedInteractionBlockerTest, HeuristicFilterWorks) { * This is similar to `HeuristicFilterWorks` test, but for stylus tool. */ TEST_F(UnwantedInteractionBlockerTest, StylusIsNotBlocked) { - InputDeviceInfo info = generateTestDeviceInfo(); - info.addSource(AINPUT_SOURCE_STYLUS); - mBlocker->notifyInputDevicesChanged({info}); + NotifyInputDevicesChangedArgs deviceChangedArgs = {/*id=*/0, {generateTestDeviceInfo()}}; + deviceChangedArgs.inputDeviceInfos[0].addSource(AINPUT_SOURCE_STYLUS); + mBlocker->notifyInputDevicesChanged(deviceChangedArgs); NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); args1.pointerProperties[0].toolType = ToolType::STYLUS; mBlocker->notifyMotion(&args1); @@ -643,9 +643,9 @@ TEST_F(UnwantedInteractionBlockerTest, StylusIsNotBlocked) { * Stylus event should continue to work even after touch is detected as a palm. */ TEST_F(UnwantedInteractionBlockerTest, TouchIsBlockedWhenMixedWithStylus) { - InputDeviceInfo info = generateTestDeviceInfo(); - info.addSource(AINPUT_SOURCE_STYLUS); - mBlocker->notifyInputDevicesChanged({info}); + NotifyInputDevicesChangedArgs deviceChangedArgs = {/*id=*/0, {generateTestDeviceInfo()}}; + deviceChangedArgs.inputDeviceInfos[0].addSource(AINPUT_SOURCE_STYLUS); + mBlocker->notifyInputDevicesChanged(deviceChangedArgs); // Touch down NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); @@ -699,7 +699,7 @@ using UnwantedInteractionBlockerTestDeathTest = UnwantedInteractionBlockerTest; TEST_F(UnwantedInteractionBlockerTestDeathTest, InconsistentEventAfterResetCausesACrash) { ScopedSilentDeath _silentDeath; NotifyMotionArgs args; - mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); + mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); mBlocker->notifyMotion( &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}}))); mBlocker->notifyMotion( @@ -721,7 +721,7 @@ TEST_F(UnwantedInteractionBlockerTestDeathTest, InconsistentEventAfterResetCause TEST_F(UnwantedInteractionBlockerTestDeathTest, WhenMoveWithoutDownCausesACrash) { ScopedSilentDeath _silentDeath; NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 2, 3}}); - mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()}); + mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); ASSERT_DEATH({ mBlocker->notifyMotion(&args); }, "Could not find slot"); } diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index 9f4aa5cd5e..0dc627a8f7 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -293,6 +293,7 @@ public: class FuzzInputListener : public virtual InputListenerInterface { public: + void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override {} void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override {} void notifyKey(const NotifyKeyArgs* args) override {} void notifyMotion(const NotifyMotionArgs* args) override {} -- GitLab From 370dbc945d04cf3301562639833f3d425a9cde71 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 6 Apr 2023 00:02:05 +0000 Subject: [PATCH 1128/1310] Dump all native input components from InputManager This makes it so that we don't have to expose all native components to NativeInputManager just to dump them. This also means we no longer have to expose the UnwantedInteractionBlocker to NativeInputManager. Bug: 275726706 Test: build Change-Id: I43ef5012d5767d211dfc9456cef34d538b9fcd91 --- services/inputflinger/InputManager.cpp | 15 +++++++++++---- services/inputflinger/InputManager.h | 8 ++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp index 9182503692..472d7a15b4 100644 --- a/services/inputflinger/InputManager.cpp +++ b/services/inputflinger/InputManager.cpp @@ -110,10 +110,6 @@ InputReaderInterface& InputManager::getReader() { return *mReader; } -UnwantedInteractionBlockerInterface& InputManager::getBlocker() { - return *mBlocker; -} - InputProcessorInterface& InputManager::getProcessor() { return *mProcessor; } @@ -129,6 +125,17 @@ void InputManager::monitor() { mDispatcher->monitor(); } +void InputManager::dump(std::string& dump) { + mReader->dump(dump); + dump += '\n'; + mBlocker->dump(dump); + dump += '\n'; + mProcessor->dump(dump); + dump += '\n'; + mDispatcher->dump(dump); + dump += '\n'; +} + // Used by tests only. binder::Status InputManager::createInputChannel(const std::string& name, InputChannel* outChannel) { IPCThreadState* ipc = IPCThreadState::self(); diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h index 11371934c2..793757d369 100644 --- a/services/inputflinger/InputManager.h +++ b/services/inputflinger/InputManager.h @@ -82,9 +82,6 @@ public: /* Gets the input reader. */ virtual InputReaderInterface& getReader() = 0; - /* Gets the unwanted interaction blocker. */ - virtual UnwantedInteractionBlockerInterface& getBlocker() = 0; - /* Gets the input processor */ virtual InputProcessorInterface& getProcessor() = 0; @@ -93,6 +90,9 @@ public: /* Check that the input stages have not deadlocked. */ virtual void monitor() = 0; + + /* Dump the state of the components controlled by the input manager. */ + virtual void dump(std::string& dump) = 0; }; class InputManager : public InputManagerInterface, public BnInputFlinger { @@ -108,10 +108,10 @@ public: status_t stop() override; InputReaderInterface& getReader() override; - UnwantedInteractionBlockerInterface& getBlocker() override; InputProcessorInterface& getProcessor() override; InputDispatcherInterface& getDispatcher() override; void monitor() override; + void dump(std::string& dump) override; status_t dump(int fd, const Vector& args) override; binder::Status createInputChannel(const std::string& name, InputChannel* outChannel) override; -- GitLab From 3d6f363bdd7c72305d8f24abe9af44494c9f2daf Mon Sep 17 00:00:00 2001 From: Alina Kalyakina Date: Wed, 22 Mar 2023 17:13:47 +0000 Subject: [PATCH 1129/1310] Processing native_window_get_refresh_cycle_duration error Added processing the situation when native_window_get_refresh_cycle_duration returns error in CreateSwapchainKHR. Bug: 271795577 Test: dEQP-VK.wsi.android.display_timing.mailbox.display_timing and CtsDeqpTestCases:include-filter:dEQP-VK.wsi.android.display_timing.fifo.display_timing pass Test: other existing dEQP-VK.wsi.* tests still pass Change-Id: Ib990ab1178dea9d75c27028ed1df2b91b3a7cabb --- vulkan/libvulkan/swapchain.cpp | 37 +++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp index dec3b20831..922a44fa05 100644 --- a/vulkan/libvulkan/swapchain.cpp +++ b/vulkan/libvulkan/swapchain.cpp @@ -252,27 +252,31 @@ struct Swapchain { Swapchain(Surface& surface_, uint32_t num_images_, VkPresentModeKHR present_mode, - int pre_transform_) + int pre_transform_, + int64_t refresh_duration_) : surface(surface_), num_images(num_images_), mailbox_mode(present_mode == VK_PRESENT_MODE_MAILBOX_KHR), pre_transform(pre_transform_), frame_timestamps_enabled(false), + refresh_duration(refresh_duration_), acquire_next_image_timeout(-1), shared(IsSharedPresentMode(present_mode)) { - ANativeWindow* window = surface.window.get(); - native_window_get_refresh_cycle_duration( - window, - &refresh_duration); } - uint64_t get_refresh_duration() + + VkResult get_refresh_duration(uint64_t& outRefreshDuration) { ANativeWindow* window = surface.window.get(); - native_window_get_refresh_cycle_duration( + int err = native_window_get_refresh_cycle_duration( window, &refresh_duration); - return static_cast(refresh_duration); - + if (err != android::OK) { + ALOGE("%s:native_window_get_refresh_cycle_duration failed: %s (%d)", + __func__, strerror(-err), err ); + return VK_ERROR_SURFACE_LOST_KHR; + } + outRefreshDuration = refresh_duration; + return VK_SUCCESS; } Surface& surface; @@ -1626,6 +1630,13 @@ VkResult CreateSwapchainKHR(VkDevice device, return VK_ERROR_SURFACE_LOST_KHR; } + int64_t refresh_duration; + err = native_window_get_refresh_cycle_duration(window, &refresh_duration); + if (err != android::OK) { + ALOGE("native_window_get_refresh_cycle_duration query failed: %s (%d)", + strerror(-err), err); + return VK_ERROR_SURFACE_LOST_KHR; + } // -- Allocate our Swapchain object -- // After this point, we must deallocate the swapchain on error. @@ -1636,8 +1647,8 @@ VkResult CreateSwapchainKHR(VkDevice device, return VK_ERROR_OUT_OF_HOST_MEMORY; Swapchain* swapchain = new (mem) Swapchain(surface, num_images, create_info->presentMode, - TranslateVulkanToNativeTransform(create_info->preTransform)); - + TranslateVulkanToNativeTransform(create_info->preTransform), + refresh_duration); VkSwapchainImageCreateInfoANDROID swapchain_image_create = { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wold-style-cast" @@ -2308,9 +2319,7 @@ VkResult GetRefreshCycleDurationGOOGLE( ATRACE_CALL(); Swapchain& swapchain = *SwapchainFromHandle(swapchain_handle); - VkResult result = VK_SUCCESS; - - pDisplayTimingProperties->refreshDuration = swapchain.get_refresh_duration(); + VkResult result = swapchain.get_refresh_duration(pDisplayTimingProperties->refreshDuration); return result; } -- GitLab From 1caf3b7d82024c9814b5ca41b144c193ade5da26 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Fri, 31 Mar 2023 10:59:10 -0500 Subject: [PATCH 1130/1310] SF: throttle WindowInfosListener calls This change updates WindowInfosListenerInvoker to delay and drop messages when there are unacked messages. When WindowInfosListener calls are acknowledged before the next windowInfosChanged call, there is no behavior change. If windowInfosChanged is called and there are unacked messages, then the update is delayed and sent once the messages are acked via WindowInfosReportedListener. If windowInfosChanged is called and there is already a delayed update, then the previous delayed update is overwritten and only the latest update is sent. WindowInfosListeners are still called immediately when there are focus requests. This means the number of unacked messages may be greater than one. This reverts commit 1234a337651a79d492b6c453eb7f4cf30ec341cf. Bug: 270894765 Test: presubmits Test: manual fuzz testing (random sleeps added to input flinger listener) Change-Id: If43b7ab91e05df863e9e6ac51b0bbd36cabe85d7 --- services/surfaceflinger/Layer.cpp | 11 +- services/surfaceflinger/Layer.h | 15 +++ services/surfaceflinger/SurfaceFlinger.cpp | 22 ++- services/surfaceflinger/SurfaceFlinger.h | 5 + .../WindowInfosListenerInvoker.cpp | 126 ++++++++++++------ .../WindowInfosListenerInvoker.h | 26 ++-- 6 files changed, 143 insertions(+), 62 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index a538c6d6fb..9c232b11a7 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -2442,16 +2442,7 @@ WindowInfo Layer::fillInputInfo(const InputDisplayArgs& displayArgs) { info.inputConfig |= WindowInfo::InputConfig::NOT_TOUCHABLE; } - // For compatibility reasons we let layers which can receive input - // receive input before they have actually submitted a buffer. Because - // of this we use canReceiveInput instead of isVisible to check the - // policy-visibility, ignoring the buffer state. However for layers with - // hasInputInfo()==false we can use the real visibility state. - // We are just using these layers for occlusion detection in - // InputDispatcher, and obviously if they aren't visible they can't occlude - // anything. - const bool visible = hasInputInfo() ? canReceiveInput() : isVisible(); - info.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !visible); + info.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !isVisibleForInput()); info.alpha = getAlpha(); fillTouchOcclusionMode(info); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index acdd01da77..8d7c362241 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -371,6 +371,21 @@ public: */ bool canReceiveInput() const; + /* + * Whether or not the layer should be considered visible for input calculations. + */ + virtual bool isVisibleForInput() const { + // For compatibility reasons we let layers which can receive input + // receive input before they have actually submitted a buffer. Because + // of this we use canReceiveInput instead of isVisible to check the + // policy-visibility, ignoring the buffer state. However for layers with + // hasInputInfo()==false we can use the real visibility state. + // We are just using these layers for occlusion detection in + // InputDispatcher, and obviously if they aren't visible they can't occlude + // anything. + return hasInputInfo() ? canReceiveInput() : isVisible(); + } + /* * isProtected - true if the layer may contain protected contents in the * GRALLOC_USAGE_PROTECTED sense. diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 8394ffbca8..31bf7ef70b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3715,17 +3715,33 @@ void SurfaceFlinger::updateInputFlinger() { return; } + std::unordered_set visibleLayers; + mDrawingState.traverse([&visibleLayers](Layer* layer) { + if (layer->isVisibleForInput()) { + visibleLayers.insert(layer); + } + }); + bool visibleLayersChanged = false; + if (visibleLayers != mVisibleLayers) { + visibleLayersChanged = true; + mVisibleLayers = std::move(visibleLayers); + } + BackgroundExecutor::getInstance().sendCallbacks({[updateWindowInfo, windowInfos = std::move(windowInfos), displayInfos = std::move(displayInfos), inputWindowCommands = std::move(mInputWindowCommands), - inputFlinger = mInputFlinger, this]() { + inputFlinger = mInputFlinger, this, + visibleLayersChanged]() { ATRACE_NAME("BackgroundExecutor::updateInputFlinger"); if (updateWindowInfo) { mWindowInfosListenerInvoker - ->windowInfosChanged(windowInfos, displayInfos, - inputWindowCommands.windowInfosReportedListeners); + ->windowInfosChanged(std::move(windowInfos), std::move(displayInfos), + std::move( + inputWindowCommands.windowInfosReportedListeners), + /* forceImmediateCall= */ visibleLayersChanged || + !inputWindowCommands.focusRequests.empty()); } else { // If there are listeners but no changes to input windows, call the listeners // immediately. diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 5783c8dc58..e82d6f991c 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1424,6 +1424,11 @@ private: TransactionHandler mTransactionHandler; display::DisplayMap mFrontEndDisplayInfos; bool mFrontEndDisplayInfosChanged = false; + + // Layers visible during the last commit. This set should only be used for testing set equality + // and membership. The pointers should not be dereferenced as it's possible the set contains + // pointers to freed layers. + std::unordered_set mVisibleLayers; }; class SurfaceComposerAIDL : public gui::BnSurfaceComposer { diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp index 292083b9bc..856fbbbf33 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp +++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp @@ -25,20 +25,17 @@ using gui::DisplayInfo; using gui::IWindowInfosListener; using gui::WindowInfo; -struct WindowInfosListenerInvoker::WindowInfosReportedListener : gui::BnWindowInfosReportedListener, - DeathRecipient { - explicit WindowInfosReportedListener( - size_t callbackCount, - const std::unordered_set, - SpHash>& - windowInfosReportedListeners) - : mCallbacksPending(callbackCount), - mWindowInfosReportedListeners(windowInfosReportedListeners) {} +using WindowInfosListenerVector = ftl::SmallVector, 3>; + +struct WindowInfosReportedListenerInvoker : gui::BnWindowInfosReportedListener, + IBinder::DeathRecipient { + WindowInfosReportedListenerInvoker(WindowInfosListenerVector windowInfosListeners, + WindowInfosReportedListenerSet windowInfosReportedListeners) + : mCallbacksPending(windowInfosListeners.size()), + mWindowInfosListeners(std::move(windowInfosListeners)), + mWindowInfosReportedListeners(std::move(windowInfosReportedListeners)) {} binder::Status onWindowInfosReported() override { - // TODO(b/222421815) There could potentially be callbacks that we don't need to wait for - // before calling the WindowInfosReportedListeners coming from InputWindowCommands. Filter - // the list of callbacks down to those from system server. if (--mCallbacksPending == 0) { for (const auto& listener : mWindowInfosReportedListeners) { sp asBinder = IInterface::asBinder(listener); @@ -46,6 +43,12 @@ struct WindowInfosListenerInvoker::WindowInfosReportedListener : gui::BnWindowIn listener->onWindowInfosReported(); } } + + auto wpThis = wp::fromExisting(this); + for (const auto& listener : mWindowInfosListeners) { + sp binder = IInterface::asBinder(listener); + binder->unlinkToDeath(wpThis); + } } return binder::Status::ok(); } @@ -54,9 +57,9 @@ struct WindowInfosListenerInvoker::WindowInfosReportedListener : gui::BnWindowIn private: std::atomic mCallbacksPending; - std::unordered_set, - SpHash> - mWindowInfosReportedListeners; + static constexpr size_t kStaticCapacity = 3; + const WindowInfosListenerVector mWindowInfosListeners; + WindowInfosReportedListenerSet mWindowInfosReportedListeners; }; void WindowInfosListenerInvoker::addWindowInfosListener(sp listener) { @@ -82,38 +85,81 @@ void WindowInfosListenerInvoker::binderDied(const wp& who) { } void WindowInfosListenerInvoker::windowInfosChanged( - const std::vector& windowInfos, const std::vector& displayInfos, - const std::unordered_set, - SpHash>& - windowInfosReportedListeners) { - ftl::SmallVector, kStaticCapacity> windowInfosListeners; + std::vector windowInfos, std::vector displayInfos, + WindowInfosReportedListenerSet reportedListeners, bool forceImmediateCall) { + reportedListeners.insert(sp::fromExisting(this)); + auto callListeners = [this, windowInfos = std::move(windowInfos), + displayInfos = std::move(displayInfos)]( + WindowInfosReportedListenerSet reportedListeners) mutable { + WindowInfosListenerVector windowInfosListeners; + { + std::scoped_lock lock(mListenersMutex); + for (const auto& [_, listener] : mWindowInfosListeners) { + windowInfosListeners.push_back(listener); + } + } + + auto reportedInvoker = + sp::make(windowInfosListeners, + std::move(reportedListeners)); + + for (const auto& listener : windowInfosListeners) { + sp asBinder = IInterface::asBinder(listener); + + // linkToDeath is used here to ensure that the windowInfosReportedListeners + // are called even if one of the windowInfosListeners dies before + // calling onWindowInfosReported. + asBinder->linkToDeath(reportedInvoker); + + auto status = + listener->onWindowInfosChanged(windowInfos, displayInfos, reportedInvoker); + if (!status.isOk()) { + reportedInvoker->onWindowInfosReported(); + } + } + }; + { - std::scoped_lock lock(mListenersMutex); - for (const auto& [_, listener] : mWindowInfosListeners) { - windowInfosListeners.push_back(listener); + std::scoped_lock lock(mMessagesMutex); + // If there are unacked messages and this isn't a forced call, then return immediately. + // If a forced window infos change doesn't happen first, the update will be sent after + // the WindowInfosReportedListeners are called. If a forced window infos change happens or + // if there are subsequent delayed messages before this update is sent, then this message + // will be dropped and the listeners will only be called with the latest info. This is done + // to reduce the amount of binder memory used. + if (mActiveMessageCount > 0 && !forceImmediateCall) { + mWindowInfosChangedDelayed = std::move(callListeners); + mReportedListenersDelayed.merge(reportedListeners); + return; } + + mWindowInfosChangedDelayed = nullptr; + reportedListeners.merge(mReportedListenersDelayed); + mActiveMessageCount++; } + callListeners(std::move(reportedListeners)); +} - auto windowInfosReportedListener = windowInfosReportedListeners.empty() - ? nullptr - : sp::make(windowInfosListeners.size(), - windowInfosReportedListeners); - for (const auto& listener : windowInfosListeners) { - sp asBinder = IInterface::asBinder(listener); - - // linkToDeath is used here to ensure that the windowInfosReportedListeners - // are called even if one of the windowInfosListeners dies before - // calling onWindowInfosReported. - if (windowInfosReportedListener) { - asBinder->linkToDeath(windowInfosReportedListener); - } +binder::Status WindowInfosListenerInvoker::onWindowInfosReported() { + std::function callListeners; + WindowInfosReportedListenerSet reportedListeners; - auto status = listener->onWindowInfosChanged(windowInfos, displayInfos, - windowInfosReportedListener); - if (windowInfosReportedListener && !status.isOk()) { - windowInfosReportedListener->onWindowInfosReported(); + { + std::scoped_lock lock{mMessagesMutex}; + mActiveMessageCount--; + if (!mWindowInfosChangedDelayed || mActiveMessageCount > 0) { + return binder::Status::ok(); } + + mActiveMessageCount++; + callListeners = std::move(mWindowInfosChangedDelayed); + mWindowInfosChangedDelayed = nullptr; + reportedListeners = std::move(mReportedListenersDelayed); + mReportedListenersDelayed.clear(); } + + callListeners(std::move(reportedListeners)); + return binder::Status::ok(); } } // namespace android diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h index d60a9c4157..4da98282a9 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.h +++ b/services/surfaceflinger/WindowInfosListenerInvoker.h @@ -23,34 +23,42 @@ #include #include #include +#include #include namespace android { -class SurfaceFlinger; +using WindowInfosReportedListenerSet = + std::unordered_set, + gui::SpHash>; -class WindowInfosListenerInvoker : public IBinder::DeathRecipient { +class WindowInfosListenerInvoker : public gui::BnWindowInfosReportedListener, + public IBinder::DeathRecipient { public: void addWindowInfosListener(sp); void removeWindowInfosListener(const sp& windowInfosListener); - void windowInfosChanged(const std::vector&, - const std::vector&, - const std::unordered_set, - SpHash>& - windowInfosReportedListeners); + void windowInfosChanged(std::vector, std::vector, + WindowInfosReportedListenerSet windowInfosReportedListeners, + bool forceImmediateCall); + + binder::Status onWindowInfosReported() override; protected: void binderDied(const wp& who) override; private: - struct WindowInfosReportedListener; - std::mutex mListenersMutex; static constexpr size_t kStaticCapacity = 3; ftl::SmallMap, const sp, kStaticCapacity> mWindowInfosListeners GUARDED_BY(mListenersMutex); + + std::mutex mMessagesMutex; + uint32_t mActiveMessageCount GUARDED_BY(mMessagesMutex) = 0; + std::function mWindowInfosChangedDelayed + GUARDED_BY(mMessagesMutex); + WindowInfosReportedListenerSet mReportedListenersDelayed; }; } // namespace android -- GitLab From 59a60b7b4f457bba27be21006a9ee148bb7e51e0 Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Wed, 8 Mar 2023 21:44:59 +0000 Subject: [PATCH 1131/1310] Round up virtual display refresh rate This semantics guarantees the virtual display will at least have the specified refresh rate, e.g., if 60hz is requested on a 90hz display, the virtual dislay will get a 90hz. Bug: 266965278 Test: atest libsurfaceflinger_unittest Change-Id: Ie7b30c5766454d0ad25cfd437f0498594c690a2e --- services/surfaceflinger/DisplayDevice.cpp | 11 +- services/surfaceflinger/SurfaceFlinger.h | 2 +- .../SurfaceFlinger_CreateDisplayTest.cpp | 127 +++++++++++++++++- .../tests/unittests/TestableSurfaceFlinger.h | 25 +++- 4 files changed, 158 insertions(+), 7 deletions(-) diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 01db0cdfdb..20f4de1d67 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -535,8 +535,8 @@ void DisplayDevice::clearDesiredActiveModeState() { } void DisplayDevice::adjustRefreshRate(Fps pacesetterDisplayRefreshRate) { - using fps_approx_ops::operator==; - if (mRequestedRefreshRate == 0_Hz) { + using fps_approx_ops::operator<=; + if (mRequestedRefreshRate <= 0_Hz) { return; } @@ -547,7 +547,12 @@ void DisplayDevice::adjustRefreshRate(Fps pacesetterDisplayRefreshRate) { } unsigned divisor = static_cast( - std::round(pacesetterDisplayRefreshRate.getValue() / mRequestedRefreshRate.getValue())); + std::floor(pacesetterDisplayRefreshRate.getValue() / mRequestedRefreshRate.getValue())); + if (divisor == 0) { + mAdjustedRefreshRate = 0_Hz; + return; + } + mAdjustedRefreshRate = pacesetterDisplayRefreshRate / divisor; } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 834c8b68ff..7c5b0ce81c 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -492,7 +492,7 @@ private: // Implements ISurfaceComposer sp createDisplay(const String8& displayName, bool secure, - float requestedRefreshRate = 0); + float requestedRefreshRate = 0.0f); void destroyDisplay(const sp& displayToken); std::vector getPhysicalDisplayIds() const EXCLUDES(mStateLock) { Mutex::Autolock lock(mStateLock); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp index 6a9c970bc3..1cc9ba40bd 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp @@ -17,12 +17,60 @@ #undef LOG_TAG #define LOG_TAG "LibSurfaceFlingerUnittests" +#include + #include "DisplayTransactionTestHelpers.h" +#include "FpsOps.h" namespace android { namespace { -class CreateDisplayTest : public DisplayTransactionTest {}; +class CreateDisplayTest : public DisplayTransactionTest { +public: + void createDisplayWithRequestedRefreshRate(const String8& name, uint64_t displayId, + float pacesetterDisplayRefreshRate, + float requestedRefreshRate, + float expectedAdjustedRefreshRate) { + // -------------------------------------------------------------------- + // Call Expectations + + // -------------------------------------------------------------------- + // Invocation + + sp displayToken = mFlinger.createDisplay(name, false, requestedRefreshRate); + + // -------------------------------------------------------------------- + // Postconditions + + // The display should have been added to the current state + ASSERT_TRUE(hasCurrentDisplayState(displayToken)); + const auto& display = getCurrentDisplayState(displayToken); + EXPECT_TRUE(display.isVirtual()); + EXPECT_EQ(display.requestedRefreshRate, Fps::fromValue(requestedRefreshRate)); + EXPECT_EQ(name.string(), display.displayName); + + std::optional vid = + DisplayId::fromValue(displayId | DisplayId::FLAG_VIRTUAL); + ASSERT_TRUE(vid.has_value()); + + sp device = + mFlinger.createVirtualDisplayDevice(displayToken, *vid, requestedRefreshRate); + EXPECT_TRUE(device->isVirtual()); + device->adjustRefreshRate(Fps::fromValue(pacesetterDisplayRefreshRate)); + // verifying desired value + EXPECT_EQ(device->getAdjustedRefreshRate(), Fps::fromValue(expectedAdjustedRefreshRate)); + // verifying rounding up + if (requestedRefreshRate < pacesetterDisplayRefreshRate) { + EXPECT_GE(device->getAdjustedRefreshRate(), Fps::fromValue(requestedRefreshRate)); + } else { + EXPECT_EQ(device->getAdjustedRefreshRate(), + Fps::fromValue(pacesetterDisplayRefreshRate)); + } + + // -------------------------------------------------------------------- + // Cleanup conditions + } +}; TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForNonsecureDisplay) { const String8 name("virtual.test"); @@ -84,5 +132,82 @@ TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForSecureDisplay) { EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1); } +// Requesting 0 tells SF not to do anything, i.e., default to refresh as physical displays +TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRate0) { + const String8 displayName("virtual.test"); + const uint64_t displayId = 123ull; + const float kPacesetterDisplayRefreshRate = 60.f; + const float kRequestedRefreshRate = 0.f; + const float kExpectedAdjustedRefreshRate = 0.f; + createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate, + kRequestedRefreshRate, kExpectedAdjustedRefreshRate); +} + +// Requesting negative refresh rate, will be ignored, same as requesting 0 +TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateNegative) { + const String8 displayName("virtual.test"); + const uint64_t displayId = 123ull; + const float kPacesetterDisplayRefreshRate = 60.f; + const float kRequestedRefreshRate = -60.f; + const float kExpectedAdjustedRefreshRate = 0.f; + createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate, + kRequestedRefreshRate, kExpectedAdjustedRefreshRate); +} + +// Requesting a higher refresh rate than the pacesetter +TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateHigh) { + const String8 displayName("virtual.test"); + const uint64_t displayId = 123ull; + const float kPacesetterDisplayRefreshRate = 60.f; + const float kRequestedRefreshRate = 90.f; + const float kExpectedAdjustedRefreshRate = 60.f; + createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate, + kRequestedRefreshRate, kExpectedAdjustedRefreshRate); +} + +// Requesting the same refresh rate as the pacesetter +TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateSame) { + const String8 displayName("virtual.test"); + const uint64_t displayId = 123ull; + const float kPacesetterDisplayRefreshRate = 60.f; + const float kRequestedRefreshRate = 60.f; + const float kExpectedAdjustedRefreshRate = 60.f; + createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate, + kRequestedRefreshRate, kExpectedAdjustedRefreshRate); +} + +// Requesting a divisor (30) of the pacesetter (60) should be honored +TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateDivisor) { + const String8 displayName("virtual.test"); + const uint64_t displayId = 123ull; + const float kPacesetterDisplayRefreshRate = 60.f; + const float kRequestedRefreshRate = 30.f; + const float kExpectedAdjustedRefreshRate = 30.f; + createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate, + kRequestedRefreshRate, kExpectedAdjustedRefreshRate); +} + +// Requesting a non divisor (45) of the pacesetter (120) should round up to a divisor (60) +TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateNoneDivisor) { + const String8 displayName("virtual.test"); + const uint64_t displayId = 123ull; + const float kPacesetterDisplayRefreshRate = 120.f; + const float kRequestedRefreshRate = 45.f; + const float kExpectedAdjustedRefreshRate = 60.f; + createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate, + kRequestedRefreshRate, kExpectedAdjustedRefreshRate); +} + +// Requesting a non divisor (75) of the pacesetter (120) should round up to pacesetter (120) +TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateNoneDivisorMax) { + const String8 displayName("virtual.test"); + const uint64_t displayId = 123ull; + const float kPacesetterDisplayRefreshRate = 120.f; + const float kRequestedRefreshRate = 75.f; + const float kExpectedAdjustedRefreshRate = 120.f; + createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate, + kRequestedRefreshRate, kExpectedAdjustedRefreshRate); +} + } // namespace } // namespace android diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index fc9e653ba4..862a8deca3 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -48,10 +48,12 @@ #include "TestableScheduler.h" #include "mock/DisplayHardware/MockComposer.h" #include "mock/DisplayHardware/MockDisplayMode.h" +#include "mock/DisplayHardware/MockPowerAdvisor.h" #include "mock/MockEventThread.h" #include "mock/MockFrameTimeline.h" #include "mock/MockFrameTracer.h" #include "mock/MockSchedulerCallback.h" +#include "mock/system/window/MockNativeWindow.h" namespace android { namespace renderengine { @@ -379,8 +381,8 @@ public: void commitAndComposite() { mFlinger->composite(commit(), kVsyncId); } - auto createDisplay(const String8& displayName, bool secure) { - return mFlinger->createDisplay(displayName, secure); + auto createDisplay(const String8& displayName, bool secure, float requestedRefreshRate = 0.0f) { + return mFlinger->createDisplay(displayName, secure, requestedRefreshRate); } auto destroyDisplay(const sp& displayToken) { @@ -527,6 +529,24 @@ public: mFlinger->getDynamicDisplayInfoFromToken(displayToken, dynamicDisplayInfo); } + sp createVirtualDisplayDevice(const sp displayToken, + VirtualDisplayId displayId, + float requestedRefreshRate) { + constexpr ui::Size kResolution = {1080, 1920}; + auto compositionDisplay = compositionengine::impl:: + createDisplay(mFlinger->getCompositionEngine(), + compositionengine::DisplayCreationArgsBuilder() + .setId(displayId) + .setPixels(kResolution) + .setPowerAdvisor(&mPowerAdvisor) + .build()); + DisplayDeviceCreationArgs creationArgs(mFlinger, mFlinger->getHwComposer(), displayToken, + compositionDisplay); + creationArgs.requestedRefreshRate = Fps::fromValue(requestedRefreshRate); + creationArgs.nativeWindow = sp::make(); + return sp::make(creationArgs); + } + /* ------------------------------------------------------------------------ * Read-only access to private data to assert post-conditions. */ @@ -964,6 +984,7 @@ private: scheduler::mock::NoOpSchedulerCallback mNoOpSchedulerCallback; std::unique_ptr mTokenManager; scheduler::TestableScheduler* mScheduler = nullptr; + Hwc2::mock::PowerAdvisor mPowerAdvisor; }; } // namespace android -- GitLab From c10321d271d4cea78d7745531c2d3b3533769d6c Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Fri, 14 Apr 2023 17:01:09 -0400 Subject: [PATCH 1132/1310] Better logs for "Unable to generate SkImage/SkSurface" In both cases, we don't have enough information to debug the issue, and none of these have been reproducible, so we need more info in the logs to know what's going on. Combine the logs, since they're complaining about similar issues. Add the data that was added in https://skia-review.googlesource.com/c/skia/+/573879 for SkSurfaces. Tested by converting the fatal logs to ALOGD and removing the if statements. The logs then look like: D RenderEngine: Unable to generate SkSurface. isTextureValid:1 dataspace:143261696 D RenderEngine: GrBackendTexture: (2208 x 1840) hasMipmaps: 0 isProtected: 0 texType: 1 D RenderEngine: GrGLTextureInfo: success: 0 fTarget: 0 fFormat: 0 colorType 4 Bug: 275475401 Bug: 242603007 Bug: 223762683 Test: m; look at logs Change-Id: If1f3a933ea612279e9911c191ab2553160e744c2 --- libs/renderengine/skia/AutoBackendTexture.cpp | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp index 5c122d4154..932be56cde 100644 --- a/libs/renderengine/skia/AutoBackendTexture.cpp +++ b/libs/renderengine/skia/AutoBackendTexture.cpp @@ -82,6 +82,18 @@ void AutoBackendTexture::releaseImageProc(SkImage::ReleaseContext releaseContext textureRelease->unref(false); } +void logFatalTexture(const char* msg, const GrBackendTexture& tex, ui::Dataspace dataspace, + SkColorType colorType) { + GrGLTextureInfo textureInfo; + bool retrievedTextureInfo = tex.getGLTextureInfo(&textureInfo); + LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d" + "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i texType: %i" + "\n\t\tGrGLTextureInfo: success: %i fTarget: %u fFormat: %u colorType %i", + msg, tex.isValid(), dataspace, tex.width(), tex.height(), tex.hasMipmaps(), + tex.isProtected(), static_cast(tex.textureType()), retrievedTextureInfo, + textureInfo.fTarget, textureInfo.fFormat, colorType); +} + sk_sp AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaType alphaType, GrDirectContext* context) { ATRACE_CALL(); @@ -107,9 +119,9 @@ sk_sp AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaTyp mImage = image; mDataspace = dataspace; - LOG_ALWAYS_FATAL_IF(mImage == nullptr, - "Unable to generate SkImage. isTextureValid:%d dataspace:%d", - mBackendTexture.isValid(), dataspace); + if (!mImage) { + logFatalTexture("Unable to generate SkImage.", mBackendTexture, dataspace, colorType); + } return mImage; } @@ -131,9 +143,9 @@ sk_sp AutoBackendTexture::getOrCreateSurface(ui::Dataspace dataspace, } mDataspace = dataspace; - LOG_ALWAYS_FATAL_IF(mSurface == nullptr, - "Unable to generate SkSurface. isTextureValid:%d dataspace:%d", - mBackendTexture.isValid(), dataspace); + if (!mSurface) { + logFatalTexture("Unable to generate SkSurface.", mBackendTexture, dataspace, mColorType); + } return mSurface; } -- GitLab From 678438e20e5bf21db670b5a599a220320c25bc94 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 13 Apr 2023 19:32:51 +0000 Subject: [PATCH 1133/1310] InputListener: Pass NotifyArgs by reference Bug: 245989146 Test: Presubmit Change-Id: I15af8a6737625a062f31acc1ab6974d52eb91d66 Merged-In: I15af8a6737625a062f31acc1ab6974d52eb91d66 --- services/inputflinger/InputListener.cpp | 67 +- services/inputflinger/InputProcessor.cpp | 28 +- services/inputflinger/InputProcessor.h | 16 +- .../UnwantedInteractionBlocker.cpp | 40 +- .../inputflinger/UnwantedInteractionBlocker.h | 18 +- .../benchmarks/InputDispatcher_benchmarks.cpp | 4 +- .../dispatcher/InputDispatcher.cpp | 190 ++-- .../inputflinger/dispatcher/InputDispatcher.h | 20 +- services/inputflinger/include/InputListener.h | 32 +- services/inputflinger/include/NotifyArgs.h | 9 +- services/inputflinger/reader/InputReader.cpp | 8 +- .../tests/InputDispatcher_test.cpp | 873 ++++++++---------- .../tests/InputProcessor_test.cpp | 18 +- .../inputflinger/tests/TestInputListener.cpp | 22 +- .../inputflinger/tests/TestInputListener.h | 18 +- .../tests/UnwantedInteractionBlocker_test.cpp | 120 +-- .../tests/fuzzers/InputClassifierFuzzer.cpp | 51 +- .../tests/fuzzers/MapperHelpers.h | 16 +- 18 files changed, 695 insertions(+), 855 deletions(-) diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp index 1bc1adf154..aa55873aa5 100644 --- a/services/inputflinger/InputListener.cpp +++ b/services/inputflinger/InputListener.cpp @@ -47,16 +47,14 @@ Visitor(V...) -> Visitor; void InputListenerInterface::notify(const NotifyArgs& generalArgs) { Visitor v{ [&](const NotifyInputDevicesChangedArgs& args) { notifyInputDevicesChanged(args); }, - [&](const NotifyConfigurationChangedArgs& args) { notifyConfigurationChanged(&args); }, - [&](const NotifyKeyArgs& args) { notifyKey(&args); }, - [&](const NotifyMotionArgs& args) { notifyMotion(&args); }, - [&](const NotifySwitchArgs& args) { notifySwitch(&args); }, - [&](const NotifySensorArgs& args) { notifySensor(&args); }, - [&](const NotifyVibratorStateArgs& args) { notifyVibratorState(&args); }, - [&](const NotifyDeviceResetArgs& args) { notifyDeviceReset(&args); }, - [&](const NotifyPointerCaptureChangedArgs& args) { - notifyPointerCaptureChanged(&args); - }, + [&](const NotifyConfigurationChangedArgs& args) { notifyConfigurationChanged(args); }, + [&](const NotifyKeyArgs& args) { notifyKey(args); }, + [&](const NotifyMotionArgs& args) { notifyMotion(args); }, + [&](const NotifySwitchArgs& args) { notifySwitch(args); }, + [&](const NotifySensorArgs& args) { notifySensor(args); }, + [&](const NotifyVibratorStateArgs& args) { notifyVibratorState(args); }, + [&](const NotifyDeviceResetArgs& args) { notifyDeviceReset(args); }, + [&](const NotifyPointerCaptureChangedArgs& args) { notifyPointerCaptureChanged(args); }, }; std::visit(v, generalArgs); } @@ -78,45 +76,44 @@ void QueuedInputListener::notifyInputDevicesChanged(const NotifyInputDevicesChan mArgsQueue.emplace_back(args); } -void QueuedInputListener::notifyConfigurationChanged( - const NotifyConfigurationChangedArgs* args) { - traceEvent(__func__, args->id); - mArgsQueue.emplace_back(*args); +void QueuedInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) { + traceEvent(__func__, args.id); + mArgsQueue.emplace_back(args); } -void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) { - traceEvent(__func__, args->id); - mArgsQueue.emplace_back(*args); +void QueuedInputListener::notifyKey(const NotifyKeyArgs& args) { + traceEvent(__func__, args.id); + mArgsQueue.emplace_back(args); } -void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) { - traceEvent(__func__, args->id); - mArgsQueue.emplace_back(*args); +void QueuedInputListener::notifyMotion(const NotifyMotionArgs& args) { + traceEvent(__func__, args.id); + mArgsQueue.emplace_back(args); } -void QueuedInputListener::notifySwitch(const NotifySwitchArgs* args) { - traceEvent(__func__, args->id); - mArgsQueue.emplace_back(*args); +void QueuedInputListener::notifySwitch(const NotifySwitchArgs& args) { + traceEvent(__func__, args.id); + mArgsQueue.emplace_back(args); } -void QueuedInputListener::notifySensor(const NotifySensorArgs* args) { - traceEvent(__func__, args->id); - mArgsQueue.emplace_back(*args); +void QueuedInputListener::notifySensor(const NotifySensorArgs& args) { + traceEvent(__func__, args.id); + mArgsQueue.emplace_back(args); } -void QueuedInputListener::notifyVibratorState(const NotifyVibratorStateArgs* args) { - traceEvent(__func__, args->id); - mArgsQueue.emplace_back(*args); +void QueuedInputListener::notifyVibratorState(const NotifyVibratorStateArgs& args) { + traceEvent(__func__, args.id); + mArgsQueue.emplace_back(args); } -void QueuedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) { - traceEvent(__func__, args->id); - mArgsQueue.emplace_back(*args); +void QueuedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs& args) { + traceEvent(__func__, args.id); + mArgsQueue.emplace_back(args); } -void QueuedInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) { - traceEvent(__func__, args->id); - mArgsQueue.emplace_back(*args); +void QueuedInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) { + traceEvent(__func__, args.id); + mArgsQueue.emplace_back(args); } void QueuedInputListener::flush() { diff --git a/services/inputflinger/InputProcessor.cpp b/services/inputflinger/InputProcessor.cpp index 6c0bcffd6b..7a84be93b1 100644 --- a/services/inputflinger/InputProcessor.cpp +++ b/services/inputflinger/InputProcessor.cpp @@ -419,63 +419,63 @@ void InputProcessor::notifyInputDevicesChanged(const NotifyInputDevicesChangedAr mQueuedListener.flush(); } -void InputProcessor::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) { +void InputProcessor::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) { // pass through mQueuedListener.notifyConfigurationChanged(args); mQueuedListener.flush(); } -void InputProcessor::notifyKey(const NotifyKeyArgs* args) { +void InputProcessor::notifyKey(const NotifyKeyArgs& args) { // pass through mQueuedListener.notifyKey(args); mQueuedListener.flush(); } -void InputProcessor::notifyMotion(const NotifyMotionArgs* args) { +void InputProcessor::notifyMotion(const NotifyMotionArgs& args) { { // acquire lock std::scoped_lock lock(mLock); // MotionClassifier is only used for touch events, for now - const bool sendToMotionClassifier = mMotionClassifier && isTouchEvent(*args); + const bool sendToMotionClassifier = mMotionClassifier && isTouchEvent(args); if (!sendToMotionClassifier) { mQueuedListener.notifyMotion(args); } else { - NotifyMotionArgs newArgs(*args); + NotifyMotionArgs newArgs(args); const MotionClassification newClassification = mMotionClassifier->classify(newArgs); - LOG_ALWAYS_FATAL_IF(args->classification != MotionClassification::NONE && + LOG_ALWAYS_FATAL_IF(args.classification != MotionClassification::NONE && newClassification != MotionClassification::NONE, "Conflicting classifications %s (new) and %s (old)!", motionClassificationToString(newClassification), - motionClassificationToString(args->classification)); + motionClassificationToString(args.classification)); newArgs.classification = newClassification; - mQueuedListener.notifyMotion(&newArgs); + mQueuedListener.notifyMotion(newArgs); } } // release lock mQueuedListener.flush(); } -void InputProcessor::notifySensor(const NotifySensorArgs* args) { +void InputProcessor::notifySensor(const NotifySensorArgs& args) { // pass through mQueuedListener.notifySensor(args); mQueuedListener.flush(); } -void InputProcessor::notifyVibratorState(const NotifyVibratorStateArgs* args) { +void InputProcessor::notifyVibratorState(const NotifyVibratorStateArgs& args) { // pass through mQueuedListener.notifyVibratorState(args); mQueuedListener.flush(); } -void InputProcessor::notifySwitch(const NotifySwitchArgs* args) { +void InputProcessor::notifySwitch(const NotifySwitchArgs& args) { // pass through mQueuedListener.notifySwitch(args); mQueuedListener.flush(); } -void InputProcessor::notifyDeviceReset(const NotifyDeviceResetArgs* args) { +void InputProcessor::notifyDeviceReset(const NotifyDeviceResetArgs& args) { { // acquire lock std::scoped_lock lock(mLock); if (mMotionClassifier) { - mMotionClassifier->reset(*args); + mMotionClassifier->reset(args); } } // release lock @@ -484,7 +484,7 @@ void InputProcessor::notifyDeviceReset(const NotifyDeviceResetArgs* args) { mQueuedListener.flush(); } -void InputProcessor::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) { +void InputProcessor::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) { // pass through mQueuedListener.notifyPointerCaptureChanged(args); mQueuedListener.flush(); diff --git a/services/inputflinger/InputProcessor.h b/services/inputflinger/InputProcessor.h index 01795a8983..dcbfebc62f 100644 --- a/services/inputflinger/InputProcessor.h +++ b/services/inputflinger/InputProcessor.h @@ -246,14 +246,14 @@ public: explicit InputProcessor(InputListenerInterface& listener); void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override; - void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override; - void notifyKey(const NotifyKeyArgs* args) override; - void notifyMotion(const NotifyMotionArgs* args) override; - void notifySwitch(const NotifySwitchArgs* args) override; - void notifySensor(const NotifySensorArgs* args) override; - void notifyVibratorState(const NotifyVibratorStateArgs* args) override; - void notifyDeviceReset(const NotifyDeviceResetArgs* args) override; - void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override; + void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override; + void notifyKey(const NotifyKeyArgs& args) override; + void notifyMotion(const NotifyMotionArgs& args) override; + void notifySwitch(const NotifySwitchArgs& args) override; + void notifySensor(const NotifySensorArgs& args) override; + void notifyVibratorState(const NotifyVibratorStateArgs& args) override; + void notifyDeviceReset(const NotifyDeviceResetArgs& args) override; + void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override; void dump(std::string& dump) override; void monitor() override; diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp index 6d43e8d009..02bc47d6bb 100644 --- a/services/inputflinger/UnwantedInteractionBlocker.cpp +++ b/services/inputflinger/UnwantedInteractionBlocker.cpp @@ -329,24 +329,24 @@ UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& l : mQueuedListener(listener), mEnablePalmRejection(enablePalmRejection) {} void UnwantedInteractionBlocker::notifyConfigurationChanged( - const NotifyConfigurationChangedArgs* args) { + const NotifyConfigurationChangedArgs& args) { mQueuedListener.notifyConfigurationChanged(args); mQueuedListener.flush(); } -void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs* args) { +void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs& args) { mQueuedListener.notifyKey(args); mQueuedListener.flush(); } -void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs* args) { - ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args->dump().c_str()); +void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs& args) { + ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args.dump().c_str()); { // acquire lock std::scoped_lock lock(mLock); const std::vector processedArgs = - mPreferStylusOverTouchBlocker.processMotion(*args); + mPreferStylusOverTouchBlocker.processMotion(args); for (const NotifyMotionArgs& loopArgs : processedArgs) { - notifyMotionLocked(&loopArgs); + notifyMotionLocked(loopArgs); } } // release lock @@ -356,56 +356,56 @@ void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs* args) { void UnwantedInteractionBlocker::enqueueOutboundMotionLocked(const NotifyMotionArgs& args) { ALOGD_IF(DEBUG_OUTBOUND_MOTION, "%s: %s", __func__, args.dump().c_str()); - mQueuedListener.notifyMotion(&args); + mQueuedListener.notifyMotion(args); } -void UnwantedInteractionBlocker::notifyMotionLocked(const NotifyMotionArgs* args) { - auto it = mPalmRejectors.find(args->deviceId); - const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args->source); +void UnwantedInteractionBlocker::notifyMotionLocked(const NotifyMotionArgs& args) { + auto it = mPalmRejectors.find(args.deviceId); + const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args.source); if (!sendToPalmRejector) { - enqueueOutboundMotionLocked(*args); + enqueueOutboundMotionLocked(args); return; } - std::vector processedArgs = it->second.processMotion(*args); + std::vector processedArgs = it->second.processMotion(args); for (const NotifyMotionArgs& loopArgs : processedArgs) { enqueueOutboundMotionLocked(loopArgs); } } -void UnwantedInteractionBlocker::notifySwitch(const NotifySwitchArgs* args) { +void UnwantedInteractionBlocker::notifySwitch(const NotifySwitchArgs& args) { mQueuedListener.notifySwitch(args); mQueuedListener.flush(); } -void UnwantedInteractionBlocker::notifySensor(const NotifySensorArgs* args) { +void UnwantedInteractionBlocker::notifySensor(const NotifySensorArgs& args) { mQueuedListener.notifySensor(args); mQueuedListener.flush(); } -void UnwantedInteractionBlocker::notifyVibratorState(const NotifyVibratorStateArgs* args) { +void UnwantedInteractionBlocker::notifyVibratorState(const NotifyVibratorStateArgs& args) { mQueuedListener.notifyVibratorState(args); mQueuedListener.flush(); } -void UnwantedInteractionBlocker::notifyDeviceReset(const NotifyDeviceResetArgs* args) { +void UnwantedInteractionBlocker::notifyDeviceReset(const NotifyDeviceResetArgs& args) { { // acquire lock std::scoped_lock lock(mLock); - auto it = mPalmRejectors.find(args->deviceId); + auto it = mPalmRejectors.find(args.deviceId); if (it != mPalmRejectors.end()) { AndroidPalmFilterDeviceInfo info = it->second.getPalmFilterDeviceInfo(); // Re-create the object instead of resetting it mPalmRejectors.erase(it); - mPalmRejectors.emplace(args->deviceId, info); + mPalmRejectors.emplace(args.deviceId, info); } mQueuedListener.notifyDeviceReset(args); - mPreferStylusOverTouchBlocker.notifyDeviceReset(*args); + mPreferStylusOverTouchBlocker.notifyDeviceReset(args); } // release lock // Send events to the next stage without holding the lock mQueuedListener.flush(); } void UnwantedInteractionBlocker::notifyPointerCaptureChanged( - const NotifyPointerCaptureChangedArgs* args) { + const NotifyPointerCaptureChangedArgs& args) { mQueuedListener.notifyPointerCaptureChanged(args); mQueuedListener.flush(); } diff --git a/services/inputflinger/UnwantedInteractionBlocker.h b/services/inputflinger/UnwantedInteractionBlocker.h index 3bc5240eb6..419da8366e 100644 --- a/services/inputflinger/UnwantedInteractionBlocker.h +++ b/services/inputflinger/UnwantedInteractionBlocker.h @@ -91,14 +91,14 @@ public: explicit UnwantedInteractionBlocker(InputListenerInterface& listener, bool enablePalmRejection); void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override; - void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override; - void notifyKey(const NotifyKeyArgs* args) override; - void notifyMotion(const NotifyMotionArgs* args) override; - void notifySwitch(const NotifySwitchArgs* args) override; - void notifySensor(const NotifySensorArgs* args) override; - void notifyVibratorState(const NotifyVibratorStateArgs* args) override; - void notifyDeviceReset(const NotifyDeviceResetArgs* args) override; - void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override; + void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override; + void notifyKey(const NotifyKeyArgs& args) override; + void notifyMotion(const NotifyMotionArgs& args) override; + void notifySwitch(const NotifySwitchArgs& args) override; + void notifySensor(const NotifySensorArgs& args) override; + void notifyVibratorState(const NotifyVibratorStateArgs& args) override; + void notifyDeviceReset(const NotifyDeviceResetArgs& args) override; + void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override; void dump(std::string& dump) override; void monitor() override; @@ -119,7 +119,7 @@ private: // Use a separate palm rejector for every touch device. std::map mPalmRejectors GUARDED_BY(mLock); // TODO(b/210159205): delete this when simultaneous stylus and touch is supported - void notifyMotionLocked(const NotifyMotionArgs* args) REQUIRES(mLock); + void notifyMotionLocked(const NotifyMotionArgs& args) REQUIRES(mLock); // Call this function for outbound events so that they can be logged when logging is enabled. void enqueueOutboundMotionLocked(const NotifyMotionArgs& args) REQUIRES(mLock); diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp index 58324c4762..f852001679 100644 --- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp +++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp @@ -277,12 +277,12 @@ static void benchmarkNotifyMotion(benchmark::State& state) { motionArgs.action = AMOTION_EVENT_ACTION_DOWN; motionArgs.downTime = now(); motionArgs.eventTime = motionArgs.downTime; - dispatcher.notifyMotion(&motionArgs); + dispatcher.notifyMotion(motionArgs); // Send ACTION_UP motionArgs.action = AMOTION_EVENT_ACTION_UP; motionArgs.eventTime = now(); - dispatcher.notifyMotion(&motionArgs); + dispatcher.notifyMotion(motionArgs); window->consumeEvent(); window->consumeEvent(); diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index c39c408e0d..d30df098be 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -4025,9 +4025,9 @@ std::unique_ptr InputDispatcher::splitMotionEvent( return splitMotionEntry; } -void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) { +void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) { if (debugInboundEventDetails()) { - ALOGD("notifyConfigurationChanged - eventTime=%" PRId64, args->eventTime); + ALOGD("notifyConfigurationChanged - eventTime=%" PRId64, args.eventTime); } bool needWake = false; @@ -4035,7 +4035,7 @@ void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChange std::scoped_lock _l(mLock); std::unique_ptr newEntry = - std::make_unique(args->id, args->eventTime); + std::make_unique(args.id, args.eventTime); needWake = enqueueInboundEventLocked(std::move(newEntry)); } // release lock @@ -4083,23 +4083,22 @@ void InputDispatcher::accelerateMetaShortcuts(const int32_t deviceId, const int3 } } -void InputDispatcher::notifyKey(const NotifyKeyArgs* args) { +void InputDispatcher::notifyKey(const NotifyKeyArgs& args) { ALOGD_IF(debugInboundEventDetails(), "notifyKey - id=%" PRIx32 ", eventTime=%" PRId64 ", deviceId=%d, source=%s, displayId=%" PRId32 "policyFlags=0x%x, action=%s, flags=0x%x, keyCode=%s, scanCode=0x%x, metaState=0x%x, " "downTime=%" PRId64, - args->id, args->eventTime, args->deviceId, - inputEventSourceToString(args->source).c_str(), args->displayId, args->policyFlags, - KeyEvent::actionToString(args->action), args->flags, KeyEvent::getLabel(args->keyCode), - args->scanCode, args->metaState, args->downTime); - if (!validateKeyEvent(args->action)) { + args.id, args.eventTime, args.deviceId, inputEventSourceToString(args.source).c_str(), + args.displayId, args.policyFlags, KeyEvent::actionToString(args.action), args.flags, + KeyEvent::getLabel(args.keyCode), args.scanCode, args.metaState, args.downTime); + if (!validateKeyEvent(args.action)) { return; } - uint32_t policyFlags = args->policyFlags; - int32_t flags = args->flags; - int32_t metaState = args->metaState; + uint32_t policyFlags = args.policyFlags; + int32_t flags = args.flags; + int32_t metaState = args.metaState; // InputDispatcher tracks and generates key repeats on behalf of // whatever notifies it, so repeatCount should always be set to 0 constexpr int32_t repeatCount = 0; @@ -4113,13 +4112,13 @@ void InputDispatcher::notifyKey(const NotifyKeyArgs* args) { policyFlags |= POLICY_FLAG_TRUSTED; - int32_t keyCode = args->keyCode; - accelerateMetaShortcuts(args->deviceId, args->action, keyCode, metaState); + int32_t keyCode = args.keyCode; + accelerateMetaShortcuts(args.deviceId, args.action, keyCode, metaState); KeyEvent event; - event.initialize(args->id, args->deviceId, args->source, args->displayId, INVALID_HMAC, - args->action, flags, keyCode, args->scanCode, metaState, repeatCount, - args->downTime, args->eventTime); + event.initialize(args.id, args.deviceId, args.source, args.displayId, INVALID_HMAC, args.action, + flags, keyCode, args.scanCode, metaState, repeatCount, args.downTime, + args.eventTime); android::base::Timer t; mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags); @@ -4144,10 +4143,9 @@ void InputDispatcher::notifyKey(const NotifyKeyArgs* args) { } std::unique_ptr newEntry = - std::make_unique(args->id, args->eventTime, args->deviceId, args->source, - args->displayId, policyFlags, args->action, flags, - keyCode, args->scanCode, metaState, repeatCount, - args->downTime); + std::make_unique(args.id, args.eventTime, args.deviceId, args.source, + args.displayId, policyFlags, args.action, flags, keyCode, + args.scanCode, metaState, repeatCount, args.downTime); needWake = enqueueInboundEventLocked(std::move(newEntry)); mLock.unlock(); @@ -4158,50 +4156,50 @@ void InputDispatcher::notifyKey(const NotifyKeyArgs* args) { } } -bool InputDispatcher::shouldSendKeyToInputFilterLocked(const NotifyKeyArgs* args) { +bool InputDispatcher::shouldSendKeyToInputFilterLocked(const NotifyKeyArgs& args) { return mInputFilterEnabled; } -void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) { +void InputDispatcher::notifyMotion(const NotifyMotionArgs& args) { if (debugInboundEventDetails()) { ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=%s, " "displayId=%" PRId32 ", policyFlags=0x%x, " "action=%s, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, " "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, xCursorPosition=%f, " "yCursorPosition=%f, downTime=%" PRId64, - args->id, args->eventTime, args->deviceId, - inputEventSourceToString(args->source).c_str(), args->displayId, args->policyFlags, - MotionEvent::actionToString(args->action).c_str(), args->actionButton, args->flags, - args->metaState, args->buttonState, args->edgeFlags, args->xPrecision, - args->yPrecision, args->xCursorPosition, args->yCursorPosition, args->downTime); - for (uint32_t i = 0; i < args->pointerCount; i++) { + args.id, args.eventTime, args.deviceId, inputEventSourceToString(args.source).c_str(), + args.displayId, args.policyFlags, MotionEvent::actionToString(args.action).c_str(), + args.actionButton, args.flags, args.metaState, args.buttonState, args.edgeFlags, + args.xPrecision, args.yPrecision, args.xCursorPosition, args.yCursorPosition, + args.downTime); + for (uint32_t i = 0; i < args.pointerCount; i++) { ALOGD(" Pointer %d: id=%d, toolType=%s, x=%f, y=%f, pressure=%f, size=%f, " "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, orientation=%f", - i, args->pointerProperties[i].id, - ftl::enum_string(args->pointerProperties[i].toolType).c_str(), - args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X), - args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y), - args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), - args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE), - args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), - args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), - args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), - args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), - args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION)); - } - } - - if (!validateMotionEvent(args->action, args->actionButton, args->pointerCount, - args->pointerProperties)) { - LOG(ERROR) << "Invalid event: " << args->dump(); + i, args.pointerProperties[i].id, + ftl::enum_string(args.pointerProperties[i].toolType).c_str(), + args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X), + args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y), + args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), + args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE), + args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), + args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), + args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), + args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), + args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION)); + } + } + + if (!validateMotionEvent(args.action, args.actionButton, args.pointerCount, + args.pointerProperties)) { + LOG(ERROR) << "Invalid event: " << args.dump(); return; } - uint32_t policyFlags = args->policyFlags; + uint32_t policyFlags = args.policyFlags; policyFlags |= POLICY_FLAG_TRUSTED; android::base::Timer t; - mPolicy->interceptMotionBeforeQueueing(args->displayId, args->eventTime, /*byref*/ policyFlags); + mPolicy->interceptMotionBeforeQueueing(args.displayId, args.eventTime, policyFlags); if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) { ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms", std::to_string(t.duration().count()).c_str()); @@ -4213,10 +4211,10 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) { if (!(policyFlags & POLICY_FLAG_PASS_TO_USER)) { // Set the flag anyway if we already have an ongoing gesture. That would allow us to // complete the processing of the current stroke. - const auto touchStateIt = mTouchStatesByDisplay.find(args->displayId); + const auto touchStateIt = mTouchStatesByDisplay.find(args.displayId); if (touchStateIt != mTouchStatesByDisplay.end()) { const TouchState& touchState = touchStateIt->second; - if (touchState.deviceId == args->deviceId && touchState.isDown()) { + if (touchState.deviceId == args.deviceId && touchState.isDown()) { policyFlags |= POLICY_FLAG_PASS_TO_USER; } } @@ -4224,20 +4222,20 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) { if (shouldSendMotionToInputFilterLocked(args)) { ui::Transform displayTransform; - if (const auto it = mDisplayInfos.find(args->displayId); it != mDisplayInfos.end()) { + if (const auto it = mDisplayInfos.find(args.displayId); it != mDisplayInfos.end()) { displayTransform = it->second.transform; } mLock.unlock(); MotionEvent event; - event.initialize(args->id, args->deviceId, args->source, args->displayId, INVALID_HMAC, - args->action, args->actionButton, args->flags, args->edgeFlags, - args->metaState, args->buttonState, args->classification, - displayTransform, args->xPrecision, args->yPrecision, - args->xCursorPosition, args->yCursorPosition, displayTransform, - args->downTime, args->eventTime, args->pointerCount, - args->pointerProperties, args->pointerCoords); + event.initialize(args.id, args.deviceId, args.source, args.displayId, INVALID_HMAC, + args.action, args.actionButton, args.flags, args.edgeFlags, + args.metaState, args.buttonState, args.classification, + displayTransform, args.xPrecision, args.yPrecision, + args.xCursorPosition, args.yCursorPosition, displayTransform, + args.downTime, args.eventTime, args.pointerCount, + args.pointerProperties, args.pointerCoords); policyFlags |= POLICY_FLAG_FILTERED; if (!mPolicy->filterInputEvent(&event, policyFlags)) { @@ -4249,21 +4247,20 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) { // Just enqueue a new motion event. std::unique_ptr newEntry = - std::make_unique(args->id, args->eventTime, args->deviceId, - args->source, args->displayId, policyFlags, - args->action, args->actionButton, args->flags, - args->metaState, args->buttonState, - args->classification, args->edgeFlags, - args->xPrecision, args->yPrecision, - args->xCursorPosition, args->yCursorPosition, - args->downTime, args->pointerCount, - args->pointerProperties, args->pointerCoords); - - if (args->id != android::os::IInputConstants::INVALID_INPUT_EVENT_ID && - IdGenerator::getSource(args->id) == IdGenerator::Source::INPUT_READER && + std::make_unique(args.id, args.eventTime, args.deviceId, args.source, + args.displayId, policyFlags, args.action, + args.actionButton, args.flags, args.metaState, + args.buttonState, args.classification, args.edgeFlags, + args.xPrecision, args.yPrecision, + args.xCursorPosition, args.yCursorPosition, + args.downTime, args.pointerCount, + args.pointerProperties, args.pointerCoords); + + if (args.id != android::os::IInputConstants::INVALID_INPUT_EVENT_ID && + IdGenerator::getSource(args.id) == IdGenerator::Source::INPUT_READER && !mInputFilterEnabled) { - const bool isDown = args->action == AMOTION_EVENT_ACTION_DOWN; - mLatencyTracker.trackListener(args->id, isDown, args->eventTime, args->readTime); + const bool isDown = args.action == AMOTION_EVENT_ACTION_DOWN; + mLatencyTracker.trackListener(args.id, isDown, args.eventTime, args.readTime); } needWake = enqueueInboundEventLocked(std::move(newEntry)); @@ -4275,12 +4272,12 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) { } } -void InputDispatcher::notifySensor(const NotifySensorArgs* args) { +void InputDispatcher::notifySensor(const NotifySensorArgs& args) { if (debugInboundEventDetails()) { ALOGD("notifySensor - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=0x%x, " " sensorType=%s", - args->id, args->eventTime, args->deviceId, args->source, - ftl::enum_string(args->sensorType).c_str()); + args.id, args.eventTime, args.deviceId, args.source, + ftl::enum_string(args.sensorType).c_str()); } bool needWake = false; @@ -4289,10 +4286,9 @@ void InputDispatcher::notifySensor(const NotifySensorArgs* args) { // Just enqueue a new sensor event. std::unique_ptr newEntry = - std::make_unique(args->id, args->eventTime, args->deviceId, - args->source, /* policyFlags=*/0, args->hwTimestamp, - args->sensorType, args->accuracy, - args->accuracyChanged, args->values); + std::make_unique(args.id, args.eventTime, args.deviceId, args.source, + /* policyFlags=*/0, args.hwTimestamp, args.sensorType, + args.accuracy, args.accuracyChanged, args.values); needWake = enqueueInboundEventLocked(std::move(newEntry)); mLock.unlock(); @@ -4303,34 +4299,34 @@ void InputDispatcher::notifySensor(const NotifySensorArgs* args) { } } -void InputDispatcher::notifyVibratorState(const NotifyVibratorStateArgs* args) { +void InputDispatcher::notifyVibratorState(const NotifyVibratorStateArgs& args) { if (debugInboundEventDetails()) { - ALOGD("notifyVibratorState - eventTime=%" PRId64 ", device=%d, isOn=%d", args->eventTime, - args->deviceId, args->isOn); + ALOGD("notifyVibratorState - eventTime=%" PRId64 ", device=%d, isOn=%d", args.eventTime, + args.deviceId, args.isOn); } - mPolicy->notifyVibratorState(args->deviceId, args->isOn); + mPolicy->notifyVibratorState(args.deviceId, args.isOn); } -bool InputDispatcher::shouldSendMotionToInputFilterLocked(const NotifyMotionArgs* args) { +bool InputDispatcher::shouldSendMotionToInputFilterLocked(const NotifyMotionArgs& args) { return mInputFilterEnabled; } -void InputDispatcher::notifySwitch(const NotifySwitchArgs* args) { +void InputDispatcher::notifySwitch(const NotifySwitchArgs& args) { if (debugInboundEventDetails()) { ALOGD("notifySwitch - eventTime=%" PRId64 ", policyFlags=0x%x, switchValues=0x%08x, " "switchMask=0x%08x", - args->eventTime, args->policyFlags, args->switchValues, args->switchMask); + args.eventTime, args.policyFlags, args.switchValues, args.switchMask); } - uint32_t policyFlags = args->policyFlags; + uint32_t policyFlags = args.policyFlags; policyFlags |= POLICY_FLAG_TRUSTED; - mPolicy->notifySwitch(args->eventTime, args->switchValues, args->switchMask, policyFlags); + mPolicy->notifySwitch(args.eventTime, args.switchValues, args.switchMask, policyFlags); } -void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args) { +void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs& args) { if (debugInboundEventDetails()) { - ALOGD("notifyDeviceReset - eventTime=%" PRId64 ", deviceId=%d", args->eventTime, - args->deviceId); + ALOGD("notifyDeviceReset - eventTime=%" PRId64 ", deviceId=%d", args.eventTime, + args.deviceId); } bool needWake = false; @@ -4338,7 +4334,7 @@ void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args) { std::scoped_lock _l(mLock); std::unique_ptr newEntry = - std::make_unique(args->id, args->eventTime, args->deviceId); + std::make_unique(args.id, args.eventTime, args.deviceId); needWake = enqueueInboundEventLocked(std::move(newEntry)); } // release lock @@ -4347,17 +4343,17 @@ void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args) { } } -void InputDispatcher::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) { +void InputDispatcher::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) { if (debugInboundEventDetails()) { - ALOGD("notifyPointerCaptureChanged - eventTime=%" PRId64 ", enabled=%s", args->eventTime, - args->request.enable ? "true" : "false"); + ALOGD("notifyPointerCaptureChanged - eventTime=%" PRId64 ", enabled=%s", args.eventTime, + args.request.enable ? "true" : "false"); } bool needWake = false; { // acquire lock std::scoped_lock _l(mLock); - auto entry = std::make_unique(args->id, args->eventTime, - args->request); + auto entry = + std::make_unique(args.id, args.eventTime, args.request); needWake = enqueueInboundEventLocked(std::move(entry)); } // release lock diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index aaf12146af..4aba24cc72 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -94,14 +94,14 @@ public: status_t stop() override; void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override{}; - void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override; - void notifyKey(const NotifyKeyArgs* args) override; - void notifyMotion(const NotifyMotionArgs* args) override; - void notifySwitch(const NotifySwitchArgs* args) override; - void notifySensor(const NotifySensorArgs* args) override; - void notifyVibratorState(const NotifyVibratorStateArgs* args) override; - void notifyDeviceReset(const NotifyDeviceResetArgs* args) override; - void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override; + void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override; + void notifyKey(const NotifyKeyArgs& args) override; + void notifyMotion(const NotifyMotionArgs& args) override; + void notifySwitch(const NotifySwitchArgs& args) override; + void notifySensor(const NotifySensorArgs& args) override; + void notifyVibratorState(const NotifyVibratorStateArgs& args) override; + void notifyDeviceReset(const NotifyDeviceResetArgs& args) override; + void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override; android::os::InputEventInjectionResult injectInputEvent( const InputEvent* event, std::optional targetUid, @@ -332,8 +332,8 @@ private: REQUIRES(mLock); // Input filter processing. - bool shouldSendKeyToInputFilterLocked(const NotifyKeyArgs* args) REQUIRES(mLock); - bool shouldSendMotionToInputFilterLocked(const NotifyMotionArgs* args) REQUIRES(mLock); + bool shouldSendKeyToInputFilterLocked(const NotifyKeyArgs& args) REQUIRES(mLock); + bool shouldSendMotionToInputFilterLocked(const NotifyMotionArgs& args) REQUIRES(mLock); // Inbound event processing. void drainInboundQueueLocked() REQUIRES(mLock); diff --git a/services/inputflinger/include/InputListener.h b/services/inputflinger/include/InputListener.h index d1b86c8cc4..4f78f032c7 100644 --- a/services/inputflinger/include/InputListener.h +++ b/services/inputflinger/include/InputListener.h @@ -38,14 +38,14 @@ public: virtual ~InputListenerInterface() { } virtual void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) = 0; - virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) = 0; - virtual void notifyKey(const NotifyKeyArgs* args) = 0; - virtual void notifyMotion(const NotifyMotionArgs* args) = 0; - virtual void notifySwitch(const NotifySwitchArgs* args) = 0; - virtual void notifySensor(const NotifySensorArgs* args) = 0; - virtual void notifyVibratorState(const NotifyVibratorStateArgs* args) = 0; - virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) = 0; - virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) = 0; + virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) = 0; + virtual void notifyKey(const NotifyKeyArgs& args) = 0; + virtual void notifyMotion(const NotifyMotionArgs& args) = 0; + virtual void notifySwitch(const NotifySwitchArgs& args) = 0; + virtual void notifySensor(const NotifySensorArgs& args) = 0; + virtual void notifyVibratorState(const NotifyVibratorStateArgs& args) = 0; + virtual void notifyDeviceReset(const NotifyDeviceResetArgs& args) = 0; + virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) = 0; void notify(const NotifyArgs& args); }; @@ -60,14 +60,14 @@ public: explicit QueuedInputListener(InputListenerInterface& innerListener); virtual void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override; - virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override; - virtual void notifyKey(const NotifyKeyArgs* args) override; - virtual void notifyMotion(const NotifyMotionArgs* args) override; - virtual void notifySwitch(const NotifySwitchArgs* args) override; - virtual void notifySensor(const NotifySensorArgs* args) override; - virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) override; - void notifyVibratorState(const NotifyVibratorStateArgs* args) override; - void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override; + virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override; + virtual void notifyKey(const NotifyKeyArgs& args) override; + virtual void notifyMotion(const NotifyMotionArgs& args) override; + virtual void notifySwitch(const NotifySwitchArgs& args) override; + virtual void notifySensor(const NotifySensorArgs& args) override; + virtual void notifyDeviceReset(const NotifyDeviceResetArgs& args) override; + void notifyVibratorState(const NotifyVibratorStateArgs& args) override; + void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override; void flush(); diff --git a/services/inputflinger/include/NotifyArgs.h b/services/inputflinger/include/NotifyArgs.h index f12482b1b2..7d29dd9cb2 100644 --- a/services/inputflinger/include/NotifyArgs.h +++ b/services/inputflinger/include/NotifyArgs.h @@ -36,6 +36,7 @@ struct NotifyInputDevicesChangedArgs { bool operator==(const NotifyInputDevicesChangedArgs& rhs) const = default; NotifyInputDevicesChangedArgs(const NotifyInputDevicesChangedArgs& other) = default; + NotifyInputDevicesChangedArgs& operator=(const NotifyInputDevicesChangedArgs&) = default; }; /* Describes a configuration change event. */ @@ -50,6 +51,7 @@ struct NotifyConfigurationChangedArgs { bool operator==(const NotifyConfigurationChangedArgs& rhs) const = default; NotifyConfigurationChangedArgs(const NotifyConfigurationChangedArgs& other) = default; + NotifyConfigurationChangedArgs& operator=(const NotifyConfigurationChangedArgs&) = default; }; /* Describes a key event. */ @@ -79,6 +81,7 @@ struct NotifyKeyArgs { bool operator==(const NotifyKeyArgs& rhs) const = default; NotifyKeyArgs(const NotifyKeyArgs& other) = default; + NotifyKeyArgs& operator=(const NotifyKeyArgs&) = default; }; /* Describes a motion event. */ @@ -129,7 +132,6 @@ struct NotifyMotionArgs { const std::vector& videoFrames); NotifyMotionArgs(const NotifyMotionArgs& other); - NotifyMotionArgs& operator=(const android::NotifyMotionArgs&) = default; bool operator==(const NotifyMotionArgs& rhs) const; @@ -157,6 +159,7 @@ struct NotifySensorArgs { bool accuracyChanged, nsecs_t hwTimestamp, std::vector values); NotifySensorArgs(const NotifySensorArgs& other) = default; + NotifySensorArgs& operator=(const NotifySensorArgs&) = default; }; /* Describes a switch event. */ @@ -174,6 +177,7 @@ struct NotifySwitchArgs { uint32_t switchMask); NotifySwitchArgs(const NotifySwitchArgs& other) = default; + NotifySwitchArgs& operator=(const NotifySwitchArgs&) = default; bool operator==(const NotifySwitchArgs& rhs) const = default; }; @@ -191,6 +195,7 @@ struct NotifyDeviceResetArgs { NotifyDeviceResetArgs(int32_t id, nsecs_t eventTime, int32_t deviceId); NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other) = default; + NotifyDeviceResetArgs& operator=(const NotifyDeviceResetArgs&) = default; bool operator==(const NotifyDeviceResetArgs& rhs) const = default; }; @@ -207,6 +212,7 @@ struct NotifyPointerCaptureChangedArgs { NotifyPointerCaptureChangedArgs(int32_t id, nsecs_t eventTime, const PointerCaptureRequest&); NotifyPointerCaptureChangedArgs(const NotifyPointerCaptureChangedArgs& other) = default; + NotifyPointerCaptureChangedArgs& operator=(const NotifyPointerCaptureChangedArgs&) = default; }; /* Describes a vibrator state event. */ @@ -222,6 +228,7 @@ struct NotifyVibratorStateArgs { NotifyVibratorStateArgs(int32_t id, nsecs_t eventTIme, int32_t deviceId, bool isOn); NotifyVibratorStateArgs(const NotifyVibratorStateArgs& other) = default; + NotifyVibratorStateArgs& operator=(const NotifyVibratorStateArgs&) = default; }; using NotifyArgs = diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 6f54faacee..0afe79dcc4 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -387,8 +387,7 @@ void InputReader::handleConfigurationChangedLocked(nsecs_t when) { updateGlobalMetaStateLocked(); // Enqueue configuration changed. - NotifyConfigurationChangedArgs args(mContext.getNextId(), when); - mQueuedListener.notifyConfigurationChanged(&args); + mQueuedListener.notifyConfigurationChanged({mContext.getNextId(), when}); } void InputReader::refreshConfigurationLocked(uint32_t changes) { @@ -420,9 +419,8 @@ void InputReader::refreshConfigurationLocked(uint32_t changes) { "There was no change in the pointer capture state."); } else { mCurrentPointerCaptureRequest = mConfig.pointerCaptureRequest; - const NotifyPointerCaptureChangedArgs args(mContext.getNextId(), now, - mCurrentPointerCaptureRequest); - mQueuedListener.notifyPointerCaptureChanged(&args); + mQueuedListener.notifyPointerCaptureChanged( + {mContext.getNextId(), now, mCurrentPointerCaptureRequest}); } } } diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 5e51bfc1ea..27d7b9c93f 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -818,8 +818,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { TEST_F(InputDispatcherTest, NotifyConfigurationChanged_CallsPolicy) { constexpr nsecs_t eventTime = 20; - NotifyConfigurationChangedArgs args(/*id=*/10, eventTime); - mDispatcher->notifyConfigurationChanged(&args); + mDispatcher->notifyConfigurationChanged({/*id=*/10, eventTime}); ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertNotifyConfigurationChangedWasCalled(eventTime); @@ -828,7 +827,7 @@ TEST_F(InputDispatcherTest, NotifyConfigurationChanged_CallsPolicy) { TEST_F(InputDispatcherTest, NotifySwitch_CallsPolicy) { NotifySwitchArgs args(/*id=*/10, /*eventTime=*/20, /*policyFlags=*/0, /*switchValues=*/1, /*switchMask=*/2); - mDispatcher->notifySwitch(&args); + mDispatcher->notifySwitch(args); // InputDispatcher adds POLICY_FLAG_TRUSTED because the event went through InputListener args.policyFlags |= POLICY_FLAG_TRUSTED; @@ -1743,8 +1742,9 @@ static NotifyKeyArgs generateKeyArgs(int32_t action, int32_t displayId = ADISPLA return args; } -static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source, int32_t displayId, - const std::vector& points) { +[[nodiscard]] static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source, + int32_t displayId, + const std::vector& points) { size_t pointerCount = points.size(); if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_UP) { EXPECT_EQ(1U, pointerCount) << "Actions DOWN and UP can only contain a single pointer"; @@ -1946,26 +1946,20 @@ TEST_F(InputDispatcherTest, CancelAfterPointer0Up) { sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); - NotifyMotionArgs args; // First touch pointer down on right window - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); // Second touch pointer down - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - - .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) - .pointer(PointerBuilder(1, ToolType::FINGER).x(110).y(100)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(110).y(100)) + .build()); // First touch pointer lifts. The second one remains down - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) - - .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) - .pointer(PointerBuilder(1, ToolType::FINGER).x(110).y(100)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(110).y(100)) + .build()); window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); window->consumeMotionEvent(WithMotionAction(POINTER_0_UP)); @@ -2256,54 +2250,48 @@ TEST_F(InputDispatcherTest, TwoPointerCancelInconsistentPolicy) { mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}}); const int32_t touchDeviceId = 4; - NotifyMotionArgs args; // Two pointers down - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .policyFlags(DEFAULT_POLICY_FLAGS) - .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) - .build())); - - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .policyFlags(DEFAULT_POLICY_FLAGS) - .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) - .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120)) - .build())); + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .policyFlags(DEFAULT_POLICY_FLAGS) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .policyFlags(DEFAULT_POLICY_FLAGS) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120)) + .build()); spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); spyWindow->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); // Cancel the current gesture. Send the cancel without the default policy flags. - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(AMOTION_EVENT_ACTION_CANCEL, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .policyFlags(0) - .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) - .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120)) - .build())); + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_CANCEL, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .policyFlags(0) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120)) + .build()); spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)); window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)); // We don't need to reset the device to reproduce the issue, but the reset event typically // follows, so we keep it here to model the actual listener behaviour more closely. - NotifyDeviceResetArgs resetArgs; - resetArgs.id = 1; // arbitrary id - resetArgs.eventTime = systemTime(SYSTEM_TIME_MONOTONIC); - resetArgs.deviceId = touchDeviceId; - mDispatcher->notifyDeviceReset(&resetArgs); + mDispatcher->notifyDeviceReset({/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC), touchDeviceId}); // Start new gesture - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .policyFlags(DEFAULT_POLICY_FLAGS) - .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) - .build())); + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .policyFlags(DEFAULT_POLICY_FLAGS) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); @@ -2453,53 +2441,49 @@ TEST_F(InputDispatcherTest, MultiDeviceSplitTouch) { NotifyMotionArgs args; // Start hovering over the left window - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) - .deviceId(mouseDeviceId) - .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); leftWindow->consumeMotionEvent( AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); // Mouse down on left window - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) - .deviceId(mouseDeviceId) - .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); leftWindow->consumeMotionEvent( AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId))); leftWindow->consumeMotionEvent( AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) - .deviceId(mouseDeviceId) - .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) - .build())); + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); // First touch pointer down on right window - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); leftWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL)); rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); // Second touch pointer down on left window - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) - .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100)) + .build()); leftWindow->consumeMotionEvent( AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); // This MOVE event is not necessary (doesn't carry any new information), but it's there in the @@ -2533,57 +2517,52 @@ TEST_F(InputDispatcherTest, MixedTouchAndMouseWithPointerDown) { NotifyMotionArgs args; // First touch pointer down - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); // Second touch pointer down - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) - .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100)) + .build()); // First touch pointer lifts. The second one remains down - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) - .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100)) + .build()); window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); window->consumeMotionEvent(WithMotionAction(POINTER_0_UP)); // Mouse down. The touch should be canceled - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) - .deviceId(mouseDeviceId) - .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, ToolType::MOUSE).x(320).y(100)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(320).y(100)) + .build()); window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId), WithPointerCount(1u))); window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) - .deviceId(mouseDeviceId) - .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, ToolType::MOUSE).x(320).y(100)) - .build())); + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(320).y(100)) + .build()); window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); // Second touch pointer down. - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(POINTER_0_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) - .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100)) + .build()); // The pointer_down event should be ignored window->assertNoEvents(); } @@ -2617,11 +2596,10 @@ TEST_F(InputDispatcherTest, UnfinishedInjectedEvent) { // Now a real touch comes. Rather than crashing or dropping the real event, the injected pointer // should be canceled and the new gesture should take over. - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100)) + .build()); window->consumeMotionEvent( AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(VIRTUAL_KEYBOARD_ID))); @@ -2823,46 +2801,38 @@ TEST_F(InputDispatcherTest, StylusHoverAndDownNoInputChannel) { mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}}); - NotifyMotionArgs args; - // Start hovering with stylus - mDispatcher->notifyMotion( - &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) - .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) + .build()); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); // Stop hovering - mDispatcher->notifyMotion( - &(args = MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) - .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) + .build()); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); // Stylus touches down - mDispatcher->notifyMotion( - &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) - .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) + .build()); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); // Stylus goes up - mDispatcher->notifyMotion( - &(args = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_STYLUS) - .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) + .build()); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_UP)); // Again hover - mDispatcher->notifyMotion( - &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) - .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) + .build()); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); // Stop hovering - mDispatcher->notifyMotion( - &(args = MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) - .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) + .build()); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); // No more events @@ -2891,35 +2861,31 @@ TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { const int32_t mouseDeviceId = 7; const int32_t touchDeviceId = 4; - NotifyMotionArgs args; // Hover a bit with mouse first - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) - .deviceId(mouseDeviceId) - .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); spyWindow->consumeMotionEvent( AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); window->consumeMotionEvent( AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); // Start touching - mDispatcher->notifyMotion( - &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .build()); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); - mDispatcher->notifyMotion( - &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, ToolType::FINGER).x(55).y(55)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(55).y(55)) + .build()); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); window->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); @@ -2927,20 +2893,18 @@ TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken())); window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL)); - mDispatcher->notifyMotion( - &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, ToolType::FINGER).x(60).y(60)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(60).y(60)) + .build()); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); // Mouse down - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) - .deviceId(mouseDeviceId) - .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); spyWindow->consumeMotionEvent( AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId))); @@ -2948,32 +2912,30 @@ TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) - .deviceId(mouseDeviceId) - .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) - .build())); + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100)) + .build()); spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); // Mouse move! - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE) - .deviceId(mouseDeviceId) - .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110)) + .build()); spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); window->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); // Touch move! - mDispatcher->notifyMotion( - &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, ToolType::FINGER).x(65).y(65)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(65).y(65)) + .build()); // No more events spyWindow->assertNoEvents(); @@ -2991,16 +2953,15 @@ TEST_F(InputDispatcherTest, SplitWorksWhenEmptyAreaIsTouched) { sp::make(application, mDispatcher, "Window", DISPLAY_ID); mDispatcher->setInputWindows({{DISPLAY_ID, {window}}}); - NotifyMotionArgs args; // Touch down on the empty space - mDispatcher->notifyMotion(&(args = generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{-1, -1}}))); + mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{-1, -1}})); mDispatcher->waitForIdle(); window->assertNoEvents(); // Now touch down on the window with another pointer - mDispatcher->notifyMotion(&(args = generateTouchArgs(POINTER_1_DOWN, {{-1, -1}, {10, 10}}))); + mDispatcher->notifyMotion(generateTouchArgs(POINTER_1_DOWN, {{-1, -1}, {10, 10}})); mDispatcher->waitForIdle(); window->consumeMotionDown(); } @@ -3021,16 +2982,15 @@ TEST_F(InputDispatcherTest, SplitWorksWhenNonTouchableWindowIsTouched) { mDispatcher->setInputWindows({{DISPLAY_ID, {window1, window2}}}); - NotifyMotionArgs args; // Touch down on the non-touchable window - mDispatcher->notifyMotion(&(args = generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}}))); + mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}})); mDispatcher->waitForIdle(); window1->assertNoEvents(); window2->assertNoEvents(); // Now touch down on the window with another pointer - mDispatcher->notifyMotion(&(args = generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}}))); + mDispatcher->notifyMotion(generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}})); mDispatcher->waitForIdle(); window2->consumeMotionDown(); } @@ -3050,9 +3010,8 @@ TEST_F(InputDispatcherTest, SplitTouchesSendCorrectActionDownTime) { mDispatcher->setInputWindows({{DISPLAY_ID, {window1, window2}}}); - NotifyMotionArgs args; // Touch down on the first window - mDispatcher->notifyMotion(&(args = generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}}))); + mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}})); mDispatcher->waitForIdle(); InputEvent* inputEvent1 = window1->consume(); @@ -3063,7 +3022,7 @@ TEST_F(InputDispatcherTest, SplitTouchesSendCorrectActionDownTime) { ASSERT_EQ(motionEvent1.getDownTime(), motionEvent1.getEventTime()); // Now touch down on the window with another pointer - mDispatcher->notifyMotion(&(args = generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}}))); + mDispatcher->notifyMotion(generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}})); mDispatcher->waitForIdle(); InputEvent* inputEvent2 = window2->consume(); ASSERT_NE(inputEvent2, nullptr); @@ -3073,14 +3032,12 @@ TEST_F(InputDispatcherTest, SplitTouchesSendCorrectActionDownTime) { ASSERT_EQ(motionEvent2.getDownTime(), motionEvent2.getEventTime()); // Now move the pointer on the second window - mDispatcher->notifyMotion( - &(args = generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{50, 50}, {151, 51}}))); + mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{50, 50}, {151, 51}})); mDispatcher->waitForIdle(); window2->consumeMotionEvent(WithDownTime(downTimeForWindow2)); // Now add new touch down on the second window - mDispatcher->notifyMotion( - &(args = generateTouchArgs(POINTER_2_DOWN, {{50, 50}, {151, 51}, {150, 50}}))); + mDispatcher->notifyMotion(generateTouchArgs(POINTER_2_DOWN, {{50, 50}, {151, 51}, {150, 50}})); mDispatcher->waitForIdle(); window2->consumeMotionEvent(WithDownTime(downTimeForWindow2)); @@ -3089,13 +3046,13 @@ TEST_F(InputDispatcherTest, SplitTouchesSendCorrectActionDownTime) { window1->assertNoEvents(); // Now move the pointer on the first window - mDispatcher->notifyMotion(&( - args = generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{51, 51}, {151, 51}, {150, 50}}))); + mDispatcher->notifyMotion( + generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{51, 51}, {151, 51}, {150, 50}})); mDispatcher->waitForIdle(); window1->consumeMotionEvent(WithDownTime(downTimeForWindow1)); - mDispatcher->notifyMotion(&( - args = generateTouchArgs(POINTER_3_DOWN, {{51, 51}, {151, 51}, {150, 50}, {50, 50}}))); + mDispatcher->notifyMotion( + generateTouchArgs(POINTER_3_DOWN, {{51, 51}, {151, 51}, {150, 50}, {50, 50}})); mDispatcher->waitForIdle(); window1->consumeMotionEvent(WithDownTime(downTimeForWindow1)); } @@ -3199,52 +3156,47 @@ TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) { const int32_t touchDeviceId = 4; const int32_t mouseDeviceId = 6; - NotifyMotionArgs args; // Two pointers down - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) - .build())); - - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) - .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .build()); + + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120)) + .build()); window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); // Inject a series of mouse events for a mouse click - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) - .deviceId(mouseDeviceId) - .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) + .build()); window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId), WithPointerCount(2u))); window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) - .deviceId(mouseDeviceId) - .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) - .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) - .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) - .build())); + mDispatcher->notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) + .build()); window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); // Try to send more touch events while the mouse is down. Since it's a continuation of an // already canceled gesture, it should be ignored. - mDispatcher->notifyMotion(&( - args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, ToolType::FINGER).x(101).y(101)) - .pointer(PointerBuilder(1, ToolType::FINGER).x(121).y(121)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(101).y(101)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(121).y(121)) + .build()); window->assertNoEvents(); } @@ -3508,23 +3460,20 @@ TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) { const int32_t mouseDeviceId = 7; const int32_t touchDeviceId = 4; - NotifyMotionArgs args; // Start hovering with the mouse - mDispatcher->notifyMotion( - &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) - .deviceId(mouseDeviceId) - .pointer(PointerBuilder(0, ToolType::MOUSE).x(10).y(10)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(10).y(10)) + .build()); window->consumeMotionEvent( AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); // Touch goes down - mDispatcher->notifyMotion( - &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .deviceId(touchDeviceId) - .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) - .build())); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .build()); window->consumeMotionEvent( AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId))); @@ -3550,15 +3499,15 @@ TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) { ADISPLAY_ID_DEFAULT, {{50, 50}}); motionArgs.xCursorPosition = 50; motionArgs.yCursorPosition = 50; - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(motionArgs); ASSERT_NO_FATAL_FAILURE( window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithSource(AINPUT_SOURCE_MOUSE)))); // Tap on the window - motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {{10, 10}}); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {{10, 10}})); ASSERT_NO_FATAL_FAILURE( window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), WithSource(AINPUT_SOURCE_MOUSE)))); @@ -3567,9 +3516,8 @@ TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) { window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN)))); - motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {{10, 10}}); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {{10, 10}})); ASSERT_NO_FATAL_FAILURE( window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_TOUCHSCREEN)))); @@ -3661,16 +3609,14 @@ TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsKeyStream) { window->consumeFocusEvent(true); - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); // Window should receive key down event. window->consumeKeyDown(ADISPLAY_ID_DEFAULT); // When device reset happens, that key stream should be terminated with FLAG_CANCELED // on the app side. - NotifyDeviceResetArgs args(/*id=*/10, /*eventTime=*/20, DEVICE_ID); - mDispatcher->notifyDeviceReset(&args); + mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID}); window->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT, AKEY_EVENT_FLAG_CANCELED); } @@ -3682,18 +3628,15 @@ TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsMotionStream) { mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); // Window should receive motion down event. window->consumeMotionDown(ADISPLAY_ID_DEFAULT); // When device reset happens, that motion stream should be terminated with ACTION_CANCEL // on the app side. - NotifyDeviceResetArgs args(/*id=*/10, /*eventTime=*/20, DEVICE_ID); - mDispatcher->notifyDeviceReset(&args); + mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID}); window->consumeMotionEvent( AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); } @@ -3709,11 +3652,11 @@ TEST_F(InputDispatcherTest, InterceptKeyByPolicy) { window->consumeFocusEvent(true); - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); + const NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); const std::chrono::milliseconds interceptKeyTimeout = 50ms; const nsecs_t injectTime = keyArgs.eventTime; mFakePolicy->setInterceptKeyTimeout(interceptKeyTimeout); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(keyArgs); // The dispatching time should be always greater than or equal to intercept key timeout. window->consumeKeyDown(ADISPLAY_ID_DEFAULT); ASSERT_TRUE((systemTime(SYSTEM_TIME_MONOTONIC) - injectTime) >= @@ -3731,11 +3674,9 @@ TEST_F(InputDispatcherTest, InterceptKeyIfKeyUp) { window->consumeFocusEvent(true); - NotifyKeyArgs keyDown = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - NotifyKeyArgs keyUp = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT); mFakePolicy->setInterceptKeyTimeout(150ms); - mDispatcher->notifyKey(&keyDown); - mDispatcher->notifyKey(&keyUp); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT)); // Window should receive key event immediately when same key up. window->consumeKeyDown(ADISPLAY_ID_DEFAULT); @@ -3764,10 +3705,9 @@ TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinates) { mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {outsideWindow, window}}}); // Tap on first window. - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {PointF{50, 50}}); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {PointF{50, 50}})); window->consumeMotionDown(); // The coordinates of the tap in 'outsideWindow' are relative to its top left corner. // Therefore, we should offset them by (100, 100) relative to the screen's top left corner. @@ -3798,18 +3738,17 @@ TEST_F(InputDispatcherTest, ActionOutsideSentOnlyWhenAWindowIsTouched) { mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, secondWindow, thirdWindow}}}); // First pointer lands outside all windows. `window` does not get ACTION_OUTSIDE. - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {PointF{-10, -10}}); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {PointF{-10, -10}})); window->assertNoEvents(); secondWindow->assertNoEvents(); // The second pointer lands inside `secondWindow`, which should receive a DOWN event. // Now, `window` should get ACTION_OUTSIDE. - motionArgs = generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {PointF{-10, -10}, PointF{105, 105}}); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, + {PointF{-10, -10}, PointF{105, 105}})); const std::map expectedPointers{{0, PointF{-10, -10}}, {1, PointF{105, 105}}}; window->consumeMotionEvent( AllOf(WithMotionAction(ACTION_OUTSIDE), WithPointers(expectedPointers))); @@ -3818,9 +3757,9 @@ TEST_F(InputDispatcherTest, ActionOutsideSentOnlyWhenAWindowIsTouched) { // The third pointer lands inside `thirdWindow`, which should receive a DOWN event. There is // no ACTION_OUTSIDE sent to `window` because one has already been sent for this gesture. - motionArgs = generateMotionArgs(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {PointF{-10, -10}, PointF{105, 105}, PointF{205, 205}}); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion( + generateMotionArgs(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {PointF{-10, -10}, PointF{105, 105}, PointF{205, 205}})); window->assertNoEvents(); secondWindow->consumeMotionMove(); thirdWindow->consumeMotionDown(); @@ -3837,10 +3776,10 @@ TEST_F(InputDispatcherTest, OnWindowInfosChanged_RemoveAllWindowsOnDisplay) { window->consumeFocusEvent(true); - NotifyKeyArgs keyDown = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - NotifyKeyArgs keyUp = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyDown); - mDispatcher->notifyKey(&keyUp); + const NotifyKeyArgs keyDown = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); + const NotifyKeyArgs keyUp = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT); + mDispatcher->notifyKey(keyDown); + mDispatcher->notifyKey(keyUp); window->consumeKeyDown(ADISPLAY_ID_DEFAULT); window->consumeKeyUp(ADISPLAY_ID_DEFAULT); @@ -3850,8 +3789,8 @@ TEST_F(InputDispatcherTest, OnWindowInfosChanged_RemoveAllWindowsOnDisplay) { window->consumeFocusEvent(false); - mDispatcher->notifyKey(&keyDown); - mDispatcher->notifyKey(&keyUp); + mDispatcher->notifyKey(keyDown); + mDispatcher->notifyKey(keyUp); window->assertNoEvents(); } @@ -3959,10 +3898,9 @@ TEST_F(InputDispatcherDisplayProjectionTest, HitTestCoordinateSpaceConsistency) // Send down to the first window. The point is represented in the display space. The point is // selected so that if the hit test was performed with the point and the bounds being in // different coordinate spaces, the event would end up in the incorrect window. - NotifyMotionArgs downMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {PointF{75, 55}}); - mDispatcher->notifyMotion(&downMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {PointF{75, 55}})); firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); @@ -4013,10 +3951,9 @@ TEST_F(InputDispatcherDisplayProjectionTest, WindowGetsEventsInCorrectCoordinate auto [firstWindow, secondWindow] = setupScaledDisplayScenario(); // Send down to the second window. - NotifyMotionArgs downMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {PointF{150, 220}}); - mDispatcher->notifyMotion(&downMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {PointF{150, 220}})); firstWindow->assertNoEvents(); const MotionEvent* event = secondWindow->consumeMotion(); @@ -4071,14 +4008,14 @@ TEST_P(InputDispatcherDisplayOrientationFixture, HitTestInDifferentOrientations) for (const auto pointInsideWindow : insidePoints) { const vec2 p = displayTransform.inverse().transform(pointInsideWindow); const PointF pointInDisplaySpace{p.x, p.y}; - const auto down = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {pointInDisplaySpace}); - mDispatcher->notifyMotion(&down); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {pointInDisplaySpace})); window->consumeMotionDown(); - const auto up = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {pointInDisplaySpace}); - mDispatcher->notifyMotion(&up); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {pointInDisplaySpace})); window->consumeMotionUp(); } @@ -4088,13 +4025,13 @@ TEST_P(InputDispatcherDisplayOrientationFixture, HitTestInDifferentOrientations) for (const auto pointOutsideWindow : outsidePoints) { const vec2 p = displayTransform.inverse().transform(pointOutsideWindow); const PointF pointInDisplaySpace{p.x, p.y}; - const auto down = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {pointInDisplaySpace}); - mDispatcher->notifyMotion(&down); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {pointInDisplaySpace})); - const auto up = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {pointInDisplaySpace}); - mDispatcher->notifyMotion(&up); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {pointInDisplaySpace})); } window->assertNoEvents(); } @@ -4132,10 +4069,8 @@ TEST_P(TransferTouchFixture, TransferTouch_OnePointer) { mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow, wallpaper}}}); // Send down to the first window - NotifyMotionArgs downMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&downMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); // Only the first window should get the down event firstWindow->consumeMotionDown(); @@ -4152,10 +4087,8 @@ TEST_P(TransferTouchFixture, TransferTouch_OnePointer) { wallpaper->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); // Send up event to the second window - NotifyMotionArgs upMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&upMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT)); // The first window gets no events and the second gets up firstWindow->assertNoEvents(); secondWindow->consumeMotionUp(); @@ -4191,10 +4124,8 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWindowsWithSpy) { mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, firstWindow, secondWindow}}}); // Send down to the first window - NotifyMotionArgs downMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&downMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); // Only the first window and spy should get the down event spyWindow->consumeMotionDown(); firstWindow->consumeMotionDown(); @@ -4209,10 +4140,8 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWindowsWithSpy) { secondWindow->consumeMotionDown(); // Send up event to the second window - NotifyMotionArgs upMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&upMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT)); // The first window gets no events and the second+spy get up firstWindow->assertNoEvents(); spyWindow->consumeMotionUp(); @@ -4238,19 +4167,16 @@ TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) { mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}}); // Send down to the first window - NotifyMotionArgs downMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {touchPoint}); - mDispatcher->notifyMotion(&downMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {touchPoint})); // Only the first window should get the down event firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); // Send pointer down to the first window - NotifyMotionArgs pointerDownMotionArgs = - generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {touchPoint, touchPoint}); - mDispatcher->notifyMotion(&pointerDownMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint})); // Only the first window should get the pointer down event firstWindow->consumeMotionPointerDown(1); secondWindow->assertNoEvents(); @@ -4265,19 +4191,15 @@ TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) { secondWindow->consumeMotionPointerDown(1); // Send pointer up to the second window - NotifyMotionArgs pointerUpMotionArgs = - generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {touchPoint, touchPoint}); - mDispatcher->notifyMotion(&pointerUpMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint})); // The first window gets nothing and the second gets pointer up firstWindow->assertNoEvents(); secondWindow->consumeMotionPointerUp(1); // Send up event to the second window - NotifyMotionArgs upMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&upMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT)); // The first window gets nothing and the second gets up firstWindow->assertNoEvents(); secondWindow->consumeMotionUp(); @@ -4308,10 +4230,8 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) { {{ADISPLAY_ID_DEFAULT, {firstWindow, wallpaper1, secondWindow, wallpaper2}}}); // Send down to the first window - NotifyMotionArgs downMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&downMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); // Only the first window should get the down event firstWindow->consumeMotionDown(); @@ -4331,10 +4251,8 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) { wallpaper2->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); // Send up event to the second window - NotifyMotionArgs upMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&upMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT)); // The first window gets no events and the second gets up firstWindow->assertNoEvents(); secondWindow->consumeMotionUp(); @@ -4378,19 +4296,17 @@ TEST_F(InputDispatcherTest, TransferTouchFocus_TwoPointersSplitTouch) { PointF pointInSecond = {300, 600}; // Send down to the first window - NotifyMotionArgs firstDownMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {pointInFirst}); - mDispatcher->notifyMotion(&firstDownMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {pointInFirst})); // Only the first window should get the down event firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); // Send down to the second window - NotifyMotionArgs secondDownMotionArgs = - generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInFirst, pointInSecond}); - mDispatcher->notifyMotion(&secondDownMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, + {pointInFirst, pointInSecond})); // The first window gets a move and the second a down firstWindow->consumeMotionMove(); secondWindow->consumeMotionDown(); @@ -4402,19 +4318,16 @@ TEST_F(InputDispatcherTest, TransferTouchFocus_TwoPointersSplitTouch) { secondWindow->consumeMotionPointerDown(1); // Send pointer up to the second window - NotifyMotionArgs pointerUpMotionArgs = - generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInFirst, pointInSecond}); - mDispatcher->notifyMotion(&pointerUpMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, + {pointInFirst, pointInSecond})); // The first window gets nothing and the second gets pointer up firstWindow->assertNoEvents(); secondWindow->consumeMotionPointerUp(1); // Send up event to the second window - NotifyMotionArgs upMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&upMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT)); // The first window gets nothing and the second gets up firstWindow->assertNoEvents(); secondWindow->consumeMotionUp(); @@ -4444,19 +4357,17 @@ TEST_F(InputDispatcherTest, TransferTouch_TwoPointersSplitTouch) { PointF pointInSecond = {300, 600}; // Send down to the first window - NotifyMotionArgs firstDownMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {pointInFirst}); - mDispatcher->notifyMotion(&firstDownMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {pointInFirst})); // Only the first window should get the down event firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); // Send down to the second window - NotifyMotionArgs secondDownMotionArgs = - generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInFirst, pointInSecond}); - mDispatcher->notifyMotion(&secondDownMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, + {pointInFirst, pointInSecond})); // The first window gets a move and the second a down firstWindow->consumeMotionMove(); secondWindow->consumeMotionDown(); @@ -4471,19 +4382,16 @@ TEST_F(InputDispatcherTest, TransferTouch_TwoPointersSplitTouch) { // The rest of the dispatch should proceed as normal // Send pointer up to the second window - NotifyMotionArgs pointerUpMotionArgs = - generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInFirst, pointInSecond}); - mDispatcher->notifyMotion(&pointerUpMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, + {pointInFirst, pointInSecond})); // The first window gets MOVE and the second gets pointer up firstWindow->consumeMotionMove(); secondWindow->consumeMotionUp(); // Send up event to the first window - NotifyMotionArgs upMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&upMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT)); // The first window gets nothing and the second gets up firstWindow->consumeMotionUp(); secondWindow->assertNoEvents(); @@ -4618,8 +4526,7 @@ TEST_F(InputDispatcherTest, FocusedWindow_ReceivesFocusEventAndKeyEvent) { window->consumeFocusEvent(true); - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); // Window should receive key down event. window->consumeKeyDown(ADISPLAY_ID_DEFAULT); @@ -4632,8 +4539,7 @@ TEST_F(InputDispatcherTest, UnfocusedWindow_DoesNotReceiveFocusEventOrKeyEvent) mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); mDispatcher->waitForIdle(); window->assertNoEvents(); @@ -4648,13 +4554,10 @@ TEST_F(InputDispatcherTest, UnfocusedWindow_ReceivesMotionsButNotKeys) { mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); // Send key - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); // Send motion - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); // Window should receive only the motion event window->consumeMotionDown(ADISPLAY_ID_DEFAULT); @@ -4681,19 +4584,17 @@ TEST_F(InputDispatcherTest, PointerCancel_SendCancelWhenSplitTouch) { PointF pointInSecond = {300, 600}; // Send down to the first window - NotifyMotionArgs firstDownMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {pointInFirst}); - mDispatcher->notifyMotion(&firstDownMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {pointInFirst})); // Only the first window should get the down event firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); // Send down to the second window - NotifyMotionArgs secondDownMotionArgs = - generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInFirst, pointInSecond}); - mDispatcher->notifyMotion(&secondDownMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, + {pointInFirst, pointInSecond})); // The first window gets a move and the second a down firstWindow->consumeMotionMove(); secondWindow->consumeMotionDown(); @@ -4703,16 +4604,14 @@ TEST_F(InputDispatcherTest, PointerCancel_SendCancelWhenSplitTouch) { generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {pointInFirst, pointInSecond}); pointerUpMotionArgs.flags |= AMOTION_EVENT_FLAG_CANCELED; - mDispatcher->notifyMotion(&pointerUpMotionArgs); + mDispatcher->notifyMotion(pointerUpMotionArgs); // The first window gets move and the second gets cancel. firstWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED); secondWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED); // Send up event. - NotifyMotionArgs upMotionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&upMotionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT)); // The first window gets up and the second gets nothing. firstWindow->consumeMotionUp(); secondWindow->assertNoEvents(); @@ -4934,7 +4833,7 @@ TEST_F(InputDispatcherTest, TestMoveEvent) { generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(motionArgs); // Window should receive motion down event. window->consumeMotionDown(ADISPLAY_ID_DEFAULT); @@ -4944,7 +4843,7 @@ TEST_F(InputDispatcherTest, TestMoveEvent) { motionArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, motionArgs.pointerCoords[0].getX() - 10); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(motionArgs); window->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE, ADISPLAY_ID_DEFAULT, /*expectedFlags=*/0); } @@ -5013,8 +4912,8 @@ TEST_F(InputDispatcherTest, VerifyInputEvent_KeyEvent) { window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN); - mDispatcher->notifyKey(&keyArgs); + const NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN); + mDispatcher->notifyKey(keyArgs); InputEvent* event = window->consume(); ASSERT_NE(event, nullptr); @@ -5055,10 +4954,10 @@ TEST_F(InputDispatcherTest, VerifyInputEvent_MotionEvent) { mDispatcher->onWindowInfosChanged({*window->getInfo()}, {displayInfo}); - NotifyMotionArgs motionArgs = + const NotifyMotionArgs motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(motionArgs); InputEvent* event = window->consume(); ASSERT_NE(event, nullptr); @@ -5355,17 +5254,17 @@ TEST_F(InputDispatcherTest, SlipperyWindow_SetsFlagPartiallyObscured) { {{ADISPLAY_ID_DEFAULT, {slipperyExitWindow, slipperyEnterWindow}}}); // Use notifyMotion instead of injecting to avoid dealing with injection permissions - NotifyMotionArgs args = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {{50, 50}}); - mDispatcher->notifyMotion(&args); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {{50, 50}})); slipperyExitWindow->consumeMotionDown(); slipperyExitWindow->setFrame(Rect(70, 70, 100, 100)); mDispatcher->setInputWindows( {{ADISPLAY_ID_DEFAULT, {slipperyExitWindow, slipperyEnterWindow}}}); - args = generateMotionArgs(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {{51, 51}}); - mDispatcher->notifyMotion(&args); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_MOVE, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {{51, 51}})); slipperyExitWindow->consumeMotionCancel(); @@ -5405,7 +5304,7 @@ protected: NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); keyArgs.deviceId = deviceId; keyArgs.policyFlags |= POLICY_FLAG_TRUSTED; // Otherwise it won't generate repeat event - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(keyArgs); // Window should receive key down event. mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT); @@ -5428,7 +5327,7 @@ protected: NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT); keyArgs.deviceId = deviceId; keyArgs.policyFlags |= POLICY_FLAG_TRUSTED; // Unless it won't generate repeat event - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(keyArgs); // Window should receive key down event. mWindow->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT, @@ -5491,8 +5390,7 @@ TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_KeyRepeatStopsAfterRepeatingK TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_StopsKeyRepeatAfterDisableInputDevice) { sendAndConsumeKeyDown(DEVICE_ID); expectKeyRepeatOnce(/*repeatCount=*/1); - NotifyDeviceResetArgs args(/*id=*/10, /*eventTime=*/20, DEVICE_ID); - mDispatcher->notifyDeviceReset(&args); + mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID}); mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT, AKEY_EVENT_FLAG_CANCELED | AKEY_EVENT_FLAG_LONG_PRESS); mWindow->assertNoEvents(); @@ -5744,10 +5642,10 @@ protected: motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, displayId); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(motionArgs); motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, displayId); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(motionArgs); ASSERT_TRUE(mDispatcher->waitForIdle()); if (expectToBeFiltered) { const auto xy = transform.transform(motionArgs.pointerCoords->getXYValue()); @@ -5761,9 +5659,9 @@ protected: NotifyKeyArgs keyArgs; keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(keyArgs); keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(keyArgs); ASSERT_TRUE(mDispatcher->waitForIdle()); if (expectToBeFiltered) { @@ -6113,9 +6011,8 @@ protected: void touchAndAssertPositions(int32_t action, const std::vector& touchedPoints, std::vector expectedPoints) { - NotifyMotionArgs motionArgs = generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, touchedPoints); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, touchedPoints)); // Always consume from window1 since it's the window that has the InputReceiver consumeMotionEvent(mWindow1, action, expectedPoints); @@ -6988,17 +6885,16 @@ TEST_F(InputDispatcherMultiWindowAnr, PendingKey_GoesToNewlyFocusedWindow) { // The other window should not be affected by that. TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) { // Touch Window 1 - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {FOCUSED_WINDOW_LOCATION}); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {FOCUSED_WINDOW_LOCATION})); mUnfocusedWindow->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_OUTSIDE, ADISPLAY_ID_DEFAULT, /*flags=*/0); // Touch Window 2 - motionArgs = generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {FOCUSED_WINDOW_LOCATION, UNFOCUSED_WINDOW_LOCATION}); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion( + generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {FOCUSED_WINDOW_LOCATION, UNFOCUSED_WINDOW_LOCATION})); const std::chrono::duration timeout = mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); @@ -7070,10 +6966,9 @@ TEST_F(InputDispatcherMultiWindowAnr, FocusedWindowWithoutSetFocusedApplication_ std::this_thread::sleep_for(10ms); // Touch unfocused window. This should force the pending key to get dropped. - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {UNFOCUSED_WINDOW_LOCATION}); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {UNFOCUSED_WINDOW_LOCATION})); // We do not consume the motion right away, because that would require dispatcher to first // process (== drop) the key event, and by that time, ANR will be raised. @@ -7128,10 +7023,9 @@ protected: TEST_F(InputDispatcherMultiWindowOcclusionTests, NoInputChannelFeature_DropsTouches) { PointF touchedPoint = {10, 10}; - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {touchedPoint}); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {touchedPoint})); mNoInputWindow->assertNoEvents(); // Even though the window 'mNoInputWindow' positioned above 'mBottomWindow' does not have @@ -7156,10 +7050,9 @@ TEST_F(InputDispatcherMultiWindowOcclusionTests, PointF touchedPoint = {10, 10}; - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {touchedPoint}); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {touchedPoint})); mNoInputWindow->assertNoEvents(); mBottomWindow->assertNoEvents(); @@ -7332,8 +7225,7 @@ protected: } void notifyPointerCaptureChanged(const PointerCaptureRequest& request) { - const NotifyPointerCaptureChangedArgs args = generatePointerCaptureChangedArgs(request); - mDispatcher->notifyPointerCaptureChanged(&args); + mDispatcher->notifyPointerCaptureChanged(generatePointerCaptureChangedArgs(request)); } PointerCaptureRequest requestAndVerifyPointerCapture(const sp& window, @@ -7509,10 +7401,9 @@ protected: } void touch(const std::vector& points = {PointF{100, 200}}) { - NotifyMotionArgs args = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, points); - mDispatcher->notifyMotion(&args); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + points)); } }; @@ -8322,27 +8213,22 @@ TEST_F(InputDispatcherDropInputFeatureTest, WindowDropsInput) { window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); // With the flag set, window should not get any input - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); window->assertNoEvents(); - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); window->assertNoEvents(); // With the flag cleared, the window should get input window->setDropInput(false); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); - keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT)); window->consumeKeyUp(ADISPLAY_ID_DEFAULT); - motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); window->consumeMotionDown(ADISPLAY_ID_DEFAULT); window->assertNoEvents(); } @@ -8368,27 +8254,22 @@ TEST_F(InputDispatcherDropInputFeatureTest, ObscuredWindowDropsInput) { window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); // With the flag set, window should not get any input - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); window->assertNoEvents(); - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); window->assertNoEvents(); // With the flag cleared, the window should get input window->setDropInputIfObscured(false); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}}); - keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT)); window->consumeKeyUp(ADISPLAY_ID_DEFAULT); - motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); window->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED); window->assertNoEvents(); } @@ -8414,26 +8295,21 @@ TEST_F(InputDispatcherDropInputFeatureTest, UnobscuredWindowGetsInput) { window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); // With the flag set, window should not get any input - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); window->assertNoEvents(); - NotifyMotionArgs motionArgs = - generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); window->assertNoEvents(); // When the window is no longer obscured because it went on top, it should get input mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, obscuringWindow}}}); - keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT); - mDispatcher->notifyKey(&keyArgs); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT)); window->consumeKeyUp(ADISPLAY_ID_DEFAULT); - motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); window->consumeMotionDown(ADISPLAY_ID_DEFAULT); window->assertNoEvents(); } @@ -9167,10 +9043,9 @@ public: } void sendFingerEvent(int32_t action) { - NotifyMotionArgs motionArgs = + mDispatcher->notifyMotion( generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, - ADISPLAY_ID_DEFAULT, {PointF{20, 20}}); - mDispatcher->notifyMotion(&motionArgs); + ADISPLAY_ID_DEFAULT, {PointF{20, 20}})); } void sendStylusEvent(int32_t action) { @@ -9178,7 +9053,7 @@ public: generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT, {PointF{30, 40}}); motionArgs.pointerProperties[0].toolType = ToolType::STYLUS; - mDispatcher->notifyMotion(&motionArgs); + mDispatcher->notifyMotion(motionArgs); } }; diff --git a/services/inputflinger/tests/InputProcessor_test.cpp b/services/inputflinger/tests/InputProcessor_test.cpp index 0ffdef9daa..3b7cbfac5c 100644 --- a/services/inputflinger/tests/InputProcessor_test.cpp +++ b/services/inputflinger/tests/InputProcessor_test.cpp @@ -72,7 +72,7 @@ TEST_F(InputProcessorTest, SendToNextStage_NotifyConfigurationChangedArgs) { // Create a basic configuration change and send to processor NotifyConfigurationChangedArgs args(/*sequenceNum=*/1, /*eventTime=*/2); - mProcessor->notifyConfigurationChanged(&args); + mProcessor->notifyConfigurationChanged(args); NotifyConfigurationChangedArgs outArgs; ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyConfigurationChangedWasCalled(&outArgs)); ASSERT_EQ(args, outArgs); @@ -85,10 +85,8 @@ TEST_F(InputProcessorTest, SendToNextStage_NotifyKeyArgs) { AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5, AMETA_NONE, /*downTime=*/6); - mProcessor->notifyKey(&args); - NotifyKeyArgs outArgs; - ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyKeyWasCalled(&outArgs)); - ASSERT_EQ(args, outArgs); + mProcessor->notifyKey(args); + ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyKeyWasCalled(testing::Eq(args))); } /** @@ -97,10 +95,8 @@ TEST_F(InputProcessorTest, SendToNextStage_NotifyKeyArgs) { */ TEST_F(InputProcessorTest, SendToNextStage_NotifyMotionArgs) { NotifyMotionArgs motionArgs = generateBasicMotionArgs(); - mProcessor->notifyMotion(&motionArgs); - NotifyMotionArgs args; - ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(motionArgs, args); + mProcessor->notifyMotion(motionArgs); + ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyMotionWasCalled(testing::Eq(motionArgs))); } /** @@ -111,7 +107,7 @@ TEST_F(InputProcessorTest, SendToNextStage_NotifySwitchArgs) { NotifySwitchArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*policyFlags=*/3, /*switchValues=*/4, /*switchMask=*/5); - mProcessor->notifySwitch(&args); + mProcessor->notifySwitch(args); NotifySwitchArgs outArgs; ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifySwitchWasCalled(&outArgs)); ASSERT_EQ(args, outArgs); @@ -124,7 +120,7 @@ TEST_F(InputProcessorTest, SendToNextStage_NotifySwitchArgs) { TEST_F(InputProcessorTest, SendToNextStage_NotifyDeviceResetArgs) { NotifyDeviceResetArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*deviceId=*/3); - mProcessor->notifyDeviceReset(&args); + mProcessor->notifyDeviceReset(args); NotifyDeviceResetArgs outArgs; ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyDeviceResetWasCalled(&outArgs)); ASSERT_EQ(args, outArgs); diff --git a/services/inputflinger/tests/TestInputListener.cpp b/services/inputflinger/tests/TestInputListener.cpp index ac1dc05f6d..fc917dd582 100644 --- a/services/inputflinger/tests/TestInputListener.cpp +++ b/services/inputflinger/tests/TestInputListener.cpp @@ -168,47 +168,47 @@ void TestInputListener::assertNotCalled(std::string message, std::optional -void TestInputListener::addToQueue(const NotifyArgsType* args) { +void TestInputListener::addToQueue(const NotifyArgsType& args) { std::scoped_lock lock(mLock); std::vector& queue = std::get>(mQueues); - queue.push_back(*args); + queue.push_back(args); mCondition.notify_all(); } void TestInputListener::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) { - addToQueue(&args); + addToQueue(args); } -void TestInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) { +void TestInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) { addToQueue(args); } -void TestInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) { +void TestInputListener::notifyDeviceReset(const NotifyDeviceResetArgs& args) { addToQueue(args); } -void TestInputListener::notifyKey(const NotifyKeyArgs* args) { +void TestInputListener::notifyKey(const NotifyKeyArgs& args) { addToQueue(args); } -void TestInputListener::notifyMotion(const NotifyMotionArgs* args) { +void TestInputListener::notifyMotion(const NotifyMotionArgs& args) { addToQueue(args); } -void TestInputListener::notifySwitch(const NotifySwitchArgs* args) { +void TestInputListener::notifySwitch(const NotifySwitchArgs& args) { addToQueue(args); } -void TestInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) { +void TestInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) { addToQueue(args); } -void TestInputListener::notifySensor(const NotifySensorArgs* args) { +void TestInputListener::notifySensor(const NotifySensorArgs& args) { addToQueue(args); } -void TestInputListener::notifyVibratorState(const NotifyVibratorStateArgs* args) { +void TestInputListener::notifyVibratorState(const NotifyVibratorStateArgs& args) { addToQueue(args); } diff --git a/services/inputflinger/tests/TestInputListener.h b/services/inputflinger/tests/TestInputListener.h index da2cab3351..deb60483fb 100644 --- a/services/inputflinger/tests/TestInputListener.h +++ b/services/inputflinger/tests/TestInputListener.h @@ -77,25 +77,25 @@ private: void assertNotCalled(std::string message, std::optional timeout = {}); template - void addToQueue(const NotifyArgsType* args); + void addToQueue(const NotifyArgsType& args); virtual void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override; - virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override; + virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override; - virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) override; + virtual void notifyDeviceReset(const NotifyDeviceResetArgs& args) override; - virtual void notifyKey(const NotifyKeyArgs* args) override; + virtual void notifyKey(const NotifyKeyArgs& args) override; - virtual void notifyMotion(const NotifyMotionArgs* args) override; + virtual void notifyMotion(const NotifyMotionArgs& args) override; - virtual void notifySwitch(const NotifySwitchArgs* args) override; + virtual void notifySwitch(const NotifySwitchArgs& args) override; - virtual void notifySensor(const NotifySensorArgs* args) override; + virtual void notifySensor(const NotifySensorArgs& args) override; - virtual void notifyVibratorState(const NotifyVibratorStateArgs* args) override; + virtual void notifyVibratorState(const NotifyVibratorStateArgs& args) override; - virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override; + virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override; std::mutex mLock; std::condition_variable mCondition; diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp index be731b1a2f..1fff2c7590 100644 --- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp +++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp @@ -421,7 +421,7 @@ TEST_F(UnwantedInteractionBlockerTest, ConfigurationChangedIsPassedToNextListene // Create a basic configuration change and send to blocker NotifyConfigurationChangedArgs args(/*sequenceNum=*/1, /*eventTime=*/2); - mBlocker->notifyConfigurationChanged(&args); + mBlocker->notifyConfigurationChanged(args); NotifyConfigurationChangedArgs outArgs; ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyConfigurationChangedWasCalled(&outArgs)); ASSERT_EQ(args, outArgs); @@ -438,10 +438,8 @@ TEST_F(UnwantedInteractionBlockerTest, KeyIsPassedToNextListener) { AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5, AMETA_NONE, /*downTime=*/6); - mBlocker->notifyKey(&args); - NotifyKeyArgs outArgs; - ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyKeyWasCalled(&outArgs)); - ASSERT_EQ(args, outArgs); + mBlocker->notifyKey(args); + ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyKeyWasCalled(testing::Eq(args))); } /** @@ -452,10 +450,8 @@ TEST_F(UnwantedInteractionBlockerTest, KeyIsPassedToNextListener) { TEST_F(UnwantedInteractionBlockerTest, DownEventIsPassedToNextListener) { NotifyMotionArgs motionArgs = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); - mBlocker->notifyMotion(&motionArgs); - NotifyMotionArgs args; - ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyMotionWasCalled(&args)); - ASSERT_EQ(motionArgs, args); + mBlocker->notifyMotion(motionArgs); + ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyMotionWasCalled(testing::Eq(motionArgs))); } /** @@ -466,7 +462,7 @@ TEST_F(UnwantedInteractionBlockerTest, SwitchIsPassedToNextListener) { NotifySwitchArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*policyFlags=*/3, /*switchValues=*/4, /*switchMask=*/5); - mBlocker->notifySwitch(&args); + mBlocker->notifySwitch(args); NotifySwitchArgs outArgs; ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifySwitchWasCalled(&outArgs)); ASSERT_EQ(args, outArgs); @@ -479,7 +475,7 @@ TEST_F(UnwantedInteractionBlockerTest, SwitchIsPassedToNextListener) { TEST_F(UnwantedInteractionBlockerTest, DeviceResetIsPassedToNextListener) { NotifyDeviceResetArgs args(/*sequenceNum=*/1, /*eventTime=*/2, DEVICE_ID); - mBlocker->notifyDeviceReset(&args); + mBlocker->notifyDeviceReset(args); NotifyDeviceResetArgs outArgs; ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyDeviceResetWasCalled(&outArgs)); ASSERT_EQ(args, outArgs); @@ -491,17 +487,12 @@ TEST_F(UnwantedInteractionBlockerTest, DeviceResetIsPassedToNextListener) { * a crash due to inconsistent event stream could have occurred. */ TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenResetHappens) { - NotifyMotionArgs args; mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); - mBlocker->notifyMotion( - &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}}))); - mBlocker->notifyMotion( - &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}}))); - NotifyDeviceResetArgs resetArgs(/*sequenceNum=*/1, /*eventTime=*/3, DEVICE_ID); - mBlocker->notifyDeviceReset(&resetArgs); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}})); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}})); + mBlocker->notifyDeviceReset({/*sequenceNum=*/1, /*eventTime=*/3, DEVICE_ID}); // Start a new gesture with a DOWN event, even though the previous event stream was incomplete. - mBlocker->notifyMotion( - &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, DOWN, {{7, 8, 9}}))); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, DOWN, {{7, 8, 9}})); } TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenStylusSourceWithFingerToolIsReceived) { @@ -509,7 +500,7 @@ TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenStylusSourceWithFingerToolIsRe NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}}); args.pointerProperties[0].toolType = ToolType::FINGER; args.source = AINPUT_SOURCE_STYLUS; - mBlocker->notifyMotion(&args); + mBlocker->notifyMotion(args); } /** @@ -517,48 +508,41 @@ TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenStylusSourceWithFingerToolIsRe * UnwantedInteractionBlocker has not changed, there should not be a reset. */ TEST_F(UnwantedInteractionBlockerTest, NoResetIfDeviceInfoChanges) { - NotifyMotionArgs args; mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); - mBlocker->notifyMotion( - &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}}))); - mBlocker->notifyMotion( - &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}}))); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}})); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}})); // Now pretend the device changed, even though nothing is different for DEVICE_ID in practice. mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); // The MOVE event continues the gesture that started before 'devices changed', so it should not // cause a crash. - mBlocker->notifyMotion( - &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, MOVE, {{7, 8, 9}}))); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, MOVE, {{7, 8, 9}})); } /** * Send a touch event, and then a stylus event. Make sure that both work. */ TEST_F(UnwantedInteractionBlockerTest, StylusAfterTouchWorks) { - NotifyMotionArgs args; mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); - args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); - mBlocker->notifyMotion(&args); - args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{4, 5, 6}}); - mBlocker->notifyMotion(&args); - args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{4, 5, 6}}); - mBlocker->notifyMotion(&args); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}})); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{4, 5, 6}})); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{4, 5, 6}})); // Now touch down stylus + NotifyMotionArgs args; args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/3, DOWN, {{10, 20, 30}}); args.pointerProperties[0].toolType = ToolType::STYLUS; args.source |= AINPUT_SOURCE_STYLUS; - mBlocker->notifyMotion(&args); + mBlocker->notifyMotion(args); args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/4, MOVE, {{40, 50, 60}}); args.pointerProperties[0].toolType = ToolType::STYLUS; args.source |= AINPUT_SOURCE_STYLUS; - mBlocker->notifyMotion(&args); + mBlocker->notifyMotion(args); args = generateMotionArgs(/*downTime=*/3, /*eventTime=*/5, UP, {{40, 50, 60}}); args.pointerProperties[0].toolType = ToolType::STYLUS; args.source |= AINPUT_SOURCE_STYLUS; - mBlocker->notifyMotion(&args); + mBlocker->notifyMotion(args); } /** @@ -569,16 +553,13 @@ TEST_F(UnwantedInteractionBlockerTest, StylusAfterTouchWorks) { */ TEST_F(UnwantedInteractionBlockerTest, DumpCanBeAccessedOnAnotherThread) { mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); - NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); - mBlocker->notifyMotion(&args1); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}})); std::thread dumpThread([this]() { std::string dump; mBlocker->dump(dump); }); - NotifyMotionArgs args2 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{4, 5, 6}}); - mBlocker->notifyMotion(&args2); - NotifyMotionArgs args3 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{4, 5, 6}}); - mBlocker->notifyMotion(&args3); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{4, 5, 6}})); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, UP, {{4, 5, 6}})); dumpThread.join(); } @@ -589,20 +570,17 @@ TEST_F(UnwantedInteractionBlockerTest, DumpCanBeAccessedOnAnotherThread) { TEST_F(UnwantedInteractionBlockerTest, HeuristicFilterWorks) { mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); // Small touch down - NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); - mBlocker->notifyMotion(&args1); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}})); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN)); // Large touch oval on the next move - NotifyMotionArgs args2 = - generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}}); - mBlocker->notifyMotion(&args2); + mBlocker->notifyMotion( + generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}})); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE)); // Lift up the touch to force the model to decide on whether it's a palm - NotifyMotionArgs args3 = - generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}); - mBlocker->notifyMotion(&args3); + mBlocker->notifyMotion( + generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}})); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(CANCEL)); } @@ -618,14 +596,14 @@ TEST_F(UnwantedInteractionBlockerTest, StylusIsNotBlocked) { mBlocker->notifyInputDevicesChanged(deviceChangedArgs); NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); args1.pointerProperties[0].toolType = ToolType::STYLUS; - mBlocker->notifyMotion(&args1); + mBlocker->notifyMotion(args1); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN)); // Move the stylus, setting large TOUCH_MAJOR/TOUCH_MINOR dimensions NotifyMotionArgs args2 = generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, MOVE, {{4, 5, 200}}); args2.pointerProperties[0].toolType = ToolType::STYLUS; - mBlocker->notifyMotion(&args2); + mBlocker->notifyMotion(args2); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE)); // Lift up the stylus. If it were a touch event, this would force the model to decide on whether @@ -633,7 +611,7 @@ TEST_F(UnwantedInteractionBlockerTest, StylusIsNotBlocked) { NotifyMotionArgs args3 = generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}); args3.pointerProperties[0].toolType = ToolType::STYLUS; - mBlocker->notifyMotion(&args3); + mBlocker->notifyMotion(args3); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(UP)); } @@ -649,28 +627,28 @@ TEST_F(UnwantedInteractionBlockerTest, TouchIsBlockedWhenMixedWithStylus) { // Touch down NotifyMotionArgs args1 = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}}); - mBlocker->notifyMotion(&args1); + mBlocker->notifyMotion(args1); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(DOWN)); // Stylus pointer down NotifyMotionArgs args2 = generateMotionArgs(/*downTime=*/0, RESAMPLE_PERIOD, POINTER_1_DOWN, {{1, 2, 3}, {10, 20, 30}}); args2.pointerProperties[1].toolType = ToolType::STYLUS; - mBlocker->notifyMotion(&args2); + mBlocker->notifyMotion(args2); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(POINTER_1_DOWN)); // Large touch oval on the next finger move NotifyMotionArgs args3 = generateMotionArgs(/*downTime=*/0, 2 * RESAMPLE_PERIOD, MOVE, {{1, 2, 300}, {11, 21, 30}}); args3.pointerProperties[1].toolType = ToolType::STYLUS; - mBlocker->notifyMotion(&args3); + mBlocker->notifyMotion(args3); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE)); // Lift up the finger pointer. It should be canceled due to the heuristic filter. NotifyMotionArgs args4 = generateMotionArgs(/*downTime=*/0, 3 * RESAMPLE_PERIOD, POINTER_0_UP, {{1, 2, 300}, {11, 21, 30}}); args4.pointerProperties[1].toolType = ToolType::STYLUS; - mBlocker->notifyMotion(&args4); + mBlocker->notifyMotion(args4); mTestListener.assertNotifyMotionWasCalled( AllOf(WithMotionAction(POINTER_0_UP), WithFlags(FLAG_CANCELED))); @@ -678,7 +656,7 @@ TEST_F(UnwantedInteractionBlockerTest, TouchIsBlockedWhenMixedWithStylus) { generateMotionArgs(/*downTime=*/0, 4 * RESAMPLE_PERIOD, MOVE, {{12, 22, 30}}); args5.pointerProperties[0].id = args4.pointerProperties[1].id; args5.pointerProperties[0].toolType = ToolType::STYLUS; - mBlocker->notifyMotion(&args5); + mBlocker->notifyMotion(args5); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(MOVE)); // Lift up the stylus pointer @@ -686,7 +664,7 @@ TEST_F(UnwantedInteractionBlockerTest, TouchIsBlockedWhenMixedWithStylus) { generateMotionArgs(/*downTime=*/0, 5 * RESAMPLE_PERIOD, UP, {{4, 5, 200}}); args6.pointerProperties[0].id = args4.pointerProperties[1].id; args6.pointerProperties[0].toolType = ToolType::STYLUS; - mBlocker->notifyMotion(&args6); + mBlocker->notifyMotion(args6); mTestListener.assertNotifyMotionWasCalled(WithMotionAction(UP)); } @@ -700,17 +678,15 @@ TEST_F(UnwantedInteractionBlockerTestDeathTest, InconsistentEventAfterResetCause ScopedSilentDeath _silentDeath; NotifyMotionArgs args; mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); - mBlocker->notifyMotion( - &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}}))); - mBlocker->notifyMotion( - &(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}}))); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, DOWN, {{1, 2, 3}})); + mBlocker->notifyMotion(generateMotionArgs(/*downTime=*/0, /*eventTime=*/2, MOVE, {{4, 5, 6}})); NotifyDeviceResetArgs resetArgs(/*sequenceNum=*/1, /*eventTime=*/3, DEVICE_ID); - mBlocker->notifyDeviceReset(&resetArgs); + mBlocker->notifyDeviceReset(resetArgs); // Sending MOVE without a DOWN -> should crash! ASSERT_DEATH( { - mBlocker->notifyMotion(&(args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, - MOVE, {{7, 8, 9}}))); + mBlocker->notifyMotion( + generateMotionArgs(/*downTime=*/0, /*eventTime=*/4, MOVE, {{7, 8, 9}})); }, "Could not find slot"); } @@ -720,9 +696,13 @@ TEST_F(UnwantedInteractionBlockerTestDeathTest, InconsistentEventAfterResetCause */ TEST_F(UnwantedInteractionBlockerTestDeathTest, WhenMoveWithoutDownCausesACrash) { ScopedSilentDeath _silentDeath; - NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 2, 3}}); mBlocker->notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); - ASSERT_DEATH({ mBlocker->notifyMotion(&args); }, "Could not find slot"); + ASSERT_DEATH( + { + mBlocker->notifyMotion( + generateMotionArgs(/*downTime=*/0, /*eventTime=*/1, MOVE, {{1, 2, 3}})); + }, + "Could not find slot"); } class PalmRejectorTest : public testing::Test { diff --git a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp index 6617f65adf..f8ebc97ccd 100644 --- a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp @@ -67,51 +67,42 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { fdp.PickValueInArray>({ [&]() -> void { // SendToNextStage_NotifyConfigurationChangedArgs - NotifyConfigurationChangedArgs - args(/*sequenceNum=*/fdp.ConsumeIntegral(), - /*eventTime=*/fdp.ConsumeIntegral()); - mClassifier->notifyConfigurationChanged(&args); + mClassifier->notifyConfigurationChanged( + {/*sequenceNum=*/fdp.ConsumeIntegral(), + /*eventTime=*/fdp.ConsumeIntegral()}); }, [&]() -> void { // SendToNextStage_NotifyKeyArgs const nsecs_t eventTime = fdp.ConsumeIntegral(); const nsecs_t readTime = eventTime + fdp.ConsumeIntegralInRange(0, 1E8); - NotifyKeyArgs keyArgs(/*sequenceNum=*/fdp.ConsumeIntegral(), - eventTime, readTime, - /*deviceId=*/fdp.ConsumeIntegral(), - AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, - /*policyFlags=*/fdp.ConsumeIntegral(), - AKEY_EVENT_ACTION_DOWN, - /*flags=*/fdp.ConsumeIntegral(), AKEYCODE_HOME, - /*scanCode=*/fdp.ConsumeIntegral(), AMETA_NONE, - /*downTime=*/fdp.ConsumeIntegral()); - - mClassifier->notifyKey(&keyArgs); + mClassifier->notifyKey({/*sequenceNum=*/fdp.ConsumeIntegral(), + eventTime, readTime, + /*deviceId=*/fdp.ConsumeIntegral(), + AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, + /*policyFlags=*/fdp.ConsumeIntegral(), + AKEY_EVENT_ACTION_DOWN, + /*flags=*/fdp.ConsumeIntegral(), AKEYCODE_HOME, + /*scanCode=*/fdp.ConsumeIntegral(), AMETA_NONE, + /*downTime=*/fdp.ConsumeIntegral()}); }, [&]() -> void { // SendToNextStage_NotifyMotionArgs - NotifyMotionArgs motionArgs = generateFuzzedMotionArgs(fdp); - mClassifier->notifyMotion(&motionArgs); + mClassifier->notifyMotion(generateFuzzedMotionArgs(fdp)); }, [&]() -> void { // SendToNextStage_NotifySwitchArgs - NotifySwitchArgs switchArgs(/*sequenceNum=*/fdp.ConsumeIntegral(), - /*eventTime=*/fdp.ConsumeIntegral(), - /*policyFlags=*/fdp.ConsumeIntegral(), - /*switchValues=*/fdp.ConsumeIntegral(), - /*switchMask=*/fdp.ConsumeIntegral()); - - mClassifier->notifySwitch(&switchArgs); + mClassifier->notifySwitch({/*sequenceNum=*/fdp.ConsumeIntegral(), + /*eventTime=*/fdp.ConsumeIntegral(), + /*policyFlags=*/fdp.ConsumeIntegral(), + /*switchValues=*/fdp.ConsumeIntegral(), + /*switchMask=*/fdp.ConsumeIntegral()}); }, [&]() -> void { // SendToNextStage_NotifyDeviceResetArgs - NotifyDeviceResetArgs resetArgs( - /*sequenceNum=*/fdp.ConsumeIntegral(), - /*eventTime=*/fdp.ConsumeIntegral(), - /*deviceId=*/fdp.ConsumeIntegral()); - - mClassifier->notifyDeviceReset(&resetArgs); + mClassifier->notifyDeviceReset({/*sequenceNum=*/fdp.ConsumeIntegral(), + /*eventTime=*/fdp.ConsumeIntegral(), + /*deviceId=*/fdp.ConsumeIntegral()}); }, [&]() -> void { // InputClassifierConverterTest diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index 0dc627a8f7..1e44e0fba0 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -294,14 +294,14 @@ public: class FuzzInputListener : public virtual InputListenerInterface { public: void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override {} - void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override {} - void notifyKey(const NotifyKeyArgs* args) override {} - void notifyMotion(const NotifyMotionArgs* args) override {} - void notifySwitch(const NotifySwitchArgs* args) override {} - void notifySensor(const NotifySensorArgs* args) override{}; - void notifyVibratorState(const NotifyVibratorStateArgs* args) override{}; - void notifyDeviceReset(const NotifyDeviceResetArgs* args) override {} - void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override{}; + void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override {} + void notifyKey(const NotifyKeyArgs& args) override {} + void notifyMotion(const NotifyMotionArgs& args) override {} + void notifySwitch(const NotifySwitchArgs& args) override {} + void notifySensor(const NotifySensorArgs& args) override{}; + void notifyVibratorState(const NotifyVibratorStateArgs& args) override{}; + void notifyDeviceReset(const NotifyDeviceResetArgs& args) override {} + void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override{}; }; class FuzzInputReaderContext : public InputReaderContext { -- GitLab From afbf2ae6ac3f050e3e420bf517dfbe58d8eabfa7 Mon Sep 17 00:00:00 2001 From: Suprabh Shukla Date: Fri, 14 Apr 2023 14:49:11 -0700 Subject: [PATCH 1134/1310] Initialize uninitialized member in BatteryService A field was left uninitialized, which could lead to undefined behavior. Test: Builds, boots, sensor events show up in BatteryStats as expected. Bug: 275436924 Change-Id: I42e42356980d75d563be763d60f56bedb863bc33 --- services/sensorservice/BatteryService.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/sensorservice/BatteryService.cpp b/services/sensorservice/BatteryService.cpp index b0fbe5dc4e..0ea548c958 100644 --- a/services/sensorservice/BatteryService.cpp +++ b/services/sensorservice/BatteryService.cpp @@ -30,8 +30,8 @@ namespace android { // --------------------------------------------------------------------------- -BatteryService::BatteryService() : mBatteryStatService(nullptr) { -} +BatteryService::BatteryService() + : mBatteryStatService(nullptr), mLastWakeupSensorEventReportedMs(0) {} bool BatteryService::addSensor(uid_t uid, int handle) { Mutex::Autolock _l(mActivationsLock); -- GitLab From dbceb0e260f708ed4a0aafb7deed2198b4590b39 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Fri, 14 Apr 2023 19:03:18 +0000 Subject: [PATCH 1135/1310] JPEG/R refactor: rename jpegrecoverymap library to ultrahdr Test: build Bug: b/264715926 Change-Id: I227fb5960f8fc7e13aae354bf77ec033850faf10 --- libs/{jpegrecoverymap => ultrahdr}/Android.bp | 2 +- libs/{jpegrecoverymap => ultrahdr}/OWNERS | 0 .../gainmapmath.cpp | 51 ++++----- libs/{jpegrecoverymap => ultrahdr}/icc.cpp | 67 ++++++------ .../include/ultrahdr}/gainmapmath.h | 26 ++--- .../include/ultrahdr}/icc.h | 24 ++--- .../include/ultrahdr}/jpegdecoderhelper.h | 10 +- .../include/ultrahdr}/jpegencoderhelper.h | 10 +- .../include/ultrahdr}/jpegr.h | 77 ++++---------- .../include/ultrahdr}/jpegrerrorcode.h | 9 +- .../include/ultrahdr}/jpegrutils.h | 18 ++-- .../include/ultrahdr}/multipictureformat.h | 12 +-- libs/ultrahdr/include/ultrahdr/ultrahdr.h | 61 +++++++++++ .../jpegdecoderhelper.cpp | 6 +- .../jpegencoderhelper.cpp | 6 +- libs/{jpegrecoverymap => ultrahdr}/jpegr.cpp | 98 +++++++++--------- .../jpegrutils.cpp | 10 +- .../multipictureformat.cpp | 8 +- .../tests/Android.bp | 4 +- .../tests/data/jpeg_image.jpg | Bin .../tests/data/minnie-318x240.yu12 | 0 .../tests/data/minnie-320x240-y.jpg | Bin .../tests/data/minnie-320x240-yuv.jpg | Bin .../tests/data/minnie-320x240.y | 0 .../tests/data/minnie-320x240.yu12 | 0 .../tests/data/raw_p010_image.p010 | Bin .../data/raw_p010_image_with_stride.p010 | Bin .../tests/data/raw_yuv420_image.yuv420 | 0 .../tests/gainmapmath_test.cpp | 58 +++++------ .../tests/jpegdecoderhelper_test.cpp | 6 +- .../tests/jpegencoderhelper_test.cpp | 6 +- .../tests/jpegr_test.cpp | 74 ++++++------- 32 files changed, 338 insertions(+), 305 deletions(-) rename libs/{jpegrecoverymap => ultrahdr}/Android.bp (98%) rename libs/{jpegrecoverymap => ultrahdr}/OWNERS (100%) rename libs/{jpegrecoverymap => ultrahdr}/gainmapmath.cpp (94%) rename libs/{jpegrecoverymap => ultrahdr}/icc.cpp (93%) rename libs/{jpegrecoverymap/include/jpegrecoverymap => ultrahdr/include/ultrahdr}/gainmapmath.h (93%) rename libs/{jpegrecoverymap/include/jpegrecoverymap => ultrahdr/include/ultrahdr}/icc.h (93%) rename libs/{jpegrecoverymap/include/jpegrecoverymap => ultrahdr/include/ultrahdr}/jpegdecoderhelper.h (94%) rename libs/{jpegrecoverymap/include/jpegrecoverymap => ultrahdr/include/ultrahdr}/jpegencoderhelper.h (93%) rename libs/{jpegrecoverymap/include/jpegrecoverymap => ultrahdr/include/ultrahdr}/jpegr.h (88%) rename libs/{jpegrecoverymap/include/jpegrecoverymap => ultrahdr/include/ultrahdr}/jpegrerrorcode.h (91%) rename libs/{jpegrecoverymap/include/jpegrecoverymap => ultrahdr/include/ultrahdr}/jpegrutils.h (90%) rename libs/{jpegrecoverymap/include/jpegrecoverymap => ultrahdr/include/ultrahdr}/multipictureformat.h (87%) create mode 100644 libs/ultrahdr/include/ultrahdr/ultrahdr.h rename libs/{jpegrecoverymap => ultrahdr}/jpegdecoderhelper.cpp (99%) rename libs/{jpegrecoverymap => ultrahdr}/jpegencoderhelper.cpp (98%) rename libs/{jpegrecoverymap => ultrahdr}/jpegr.cpp (93%) rename libs/{jpegrecoverymap => ultrahdr}/jpegrutils.cpp (97%) rename libs/{jpegrecoverymap => ultrahdr}/multipictureformat.cpp (96%) rename libs/{jpegrecoverymap => ultrahdr}/tests/Android.bp (96%) rename libs/{jpegrecoverymap => ultrahdr}/tests/data/jpeg_image.jpg (100%) rename libs/{jpegrecoverymap => ultrahdr}/tests/data/minnie-318x240.yu12 (100%) rename libs/{jpegrecoverymap => ultrahdr}/tests/data/minnie-320x240-y.jpg (100%) rename libs/{jpegrecoverymap => ultrahdr}/tests/data/minnie-320x240-yuv.jpg (100%) rename libs/{jpegrecoverymap => ultrahdr}/tests/data/minnie-320x240.y (100%) rename libs/{jpegrecoverymap => ultrahdr}/tests/data/minnie-320x240.yu12 (100%) rename libs/{jpegrecoverymap => ultrahdr}/tests/data/raw_p010_image.p010 (100%) rename libs/{jpegrecoverymap => ultrahdr}/tests/data/raw_p010_image_with_stride.p010 (100%) rename libs/{jpegrecoverymap => ultrahdr}/tests/data/raw_yuv420_image.yuv420 (100%) rename libs/{jpegrecoverymap => ultrahdr}/tests/gainmapmath_test.cpp (95%) rename libs/{jpegrecoverymap => ultrahdr}/tests/jpegdecoderhelper_test.cpp (95%) rename libs/{jpegrecoverymap => ultrahdr}/tests/jpegencoderhelper_test.cpp (97%) rename libs/{jpegrecoverymap => ultrahdr}/tests/jpegr_test.cpp (86%) diff --git a/libs/jpegrecoverymap/Android.bp b/libs/ultrahdr/Android.bp similarity index 98% rename from libs/jpegrecoverymap/Android.bp rename to libs/ultrahdr/Android.bp index a376ced7f1..e3f709b7f5 100644 --- a/libs/jpegrecoverymap/Android.bp +++ b/libs/ultrahdr/Android.bp @@ -22,7 +22,7 @@ package { } cc_library { - name: "libjpegrecoverymap", + name: "libultrahdr", host_supported: true, vendor_available: true, export_include_dirs: ["include"], diff --git a/libs/jpegrecoverymap/OWNERS b/libs/ultrahdr/OWNERS similarity index 100% rename from libs/jpegrecoverymap/OWNERS rename to libs/ultrahdr/OWNERS diff --git a/libs/jpegrecoverymap/gainmapmath.cpp b/libs/ultrahdr/gainmapmath.cpp similarity index 94% rename from libs/jpegrecoverymap/gainmapmath.cpp rename to libs/ultrahdr/gainmapmath.cpp index f15a0784e8..37c3cf3d3b 100644 --- a/libs/jpegrecoverymap/gainmapmath.cpp +++ b/libs/ultrahdr/gainmapmath.cpp @@ -16,9 +16,9 @@ #include #include -#include +#include -namespace android::jpegrecoverymap { +namespace android::ultrahdr { static const std::vector kPqOETF = [] { std::vector result; @@ -396,45 +396,46 @@ Color bt2100ToP3(Color e) { // TODO: confirm we always want to convert like this before calculating // luminance. -ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gamut hdr_gamut) { +ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut, + ultrahdr_color_gamut hdr_gamut) { switch (sdr_gamut) { - case JPEGR_COLORGAMUT_BT709: + case ULTRAHDR_COLORGAMUT_BT709: switch (hdr_gamut) { - case JPEGR_COLORGAMUT_BT709: + case ULTRAHDR_COLORGAMUT_BT709: return identityConversion; - case JPEGR_COLORGAMUT_P3: + case ULTRAHDR_COLORGAMUT_P3: return p3ToBt709; - case JPEGR_COLORGAMUT_BT2100: + case ULTRAHDR_COLORGAMUT_BT2100: return bt2100ToBt709; - case JPEGR_COLORGAMUT_UNSPECIFIED: + case ULTRAHDR_COLORGAMUT_UNSPECIFIED: return nullptr; } break; - case JPEGR_COLORGAMUT_P3: + case ULTRAHDR_COLORGAMUT_P3: switch (hdr_gamut) { - case JPEGR_COLORGAMUT_BT709: + case ULTRAHDR_COLORGAMUT_BT709: return bt709ToP3; - case JPEGR_COLORGAMUT_P3: + case ULTRAHDR_COLORGAMUT_P3: return identityConversion; - case JPEGR_COLORGAMUT_BT2100: + case ULTRAHDR_COLORGAMUT_BT2100: return bt2100ToP3; - case JPEGR_COLORGAMUT_UNSPECIFIED: + case ULTRAHDR_COLORGAMUT_UNSPECIFIED: return nullptr; } break; - case JPEGR_COLORGAMUT_BT2100: + case ULTRAHDR_COLORGAMUT_BT2100: switch (hdr_gamut) { - case JPEGR_COLORGAMUT_BT709: + case ULTRAHDR_COLORGAMUT_BT709: return bt709ToBt2100; - case JPEGR_COLORGAMUT_P3: + case ULTRAHDR_COLORGAMUT_P3: return p3ToBt2100; - case JPEGR_COLORGAMUT_BT2100: + case ULTRAHDR_COLORGAMUT_BT2100: return identityConversion; - case JPEGR_COLORGAMUT_UNSPECIFIED: + case ULTRAHDR_COLORGAMUT_UNSPECIFIED: return nullptr; } break; - case JPEGR_COLORGAMUT_UNSPECIFIED: + case ULTRAHDR_COLORGAMUT_UNSPECIFIED: return nullptr; } } @@ -442,12 +443,12 @@ ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gam //////////////////////////////////////////////////////////////////////////////// // Gain map calculations -uint8_t encodeGain(float y_sdr, float y_hdr, jr_metadata_ptr metadata) { +uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata) { return encodeGain(y_sdr, y_hdr, metadata, log2(metadata->minContentBoost), log2(metadata->maxContentBoost)); } -uint8_t encodeGain(float y_sdr, float y_hdr, jr_metadata_ptr metadata, +uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata, float log2MinContentBoost, float log2MaxContentBoost) { float gain = 1.0f; if (y_sdr > 0.0f) { @@ -462,14 +463,14 @@ uint8_t encodeGain(float y_sdr, float y_hdr, jr_metadata_ptr metadata, * 255.0f); } -Color applyGain(Color e, float gain, jr_metadata_ptr metadata) { +Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata) { float logBoost = log2(metadata->minContentBoost) * (1.0f - gain) + log2(metadata->maxContentBoost) * gain; float gainFactor = exp2(logBoost); return e * gainFactor; } -Color applyGain(Color e, float gain, jr_metadata_ptr metadata, float displayBoost) { +Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata, float displayBoost) { float logBoost = log2(metadata->minContentBoost) * (1.0f - gain) + log2(metadata->maxContentBoost) * gain; float gainFactor = exp2(logBoost * displayBoost / metadata->maxContentBoost); @@ -511,7 +512,7 @@ Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) { chroma_stride = luma_stride; } if (chroma_data == nullptr) { - chroma_data = &reinterpret_cast(image->data)[image->luma_stride * image->height]; + chroma_data = &reinterpret_cast(image->data)[luma_stride * image->height]; } size_t pixel_y_idx = y * luma_stride + x; @@ -662,4 +663,4 @@ uint64_t colorToRgbaF16(Color e_gamma) { | (((uint64_t) floatToHalf(1.0f)) << 48); } -} // namespace android::jpegrecoverymap +} // namespace android::ultrahdr diff --git a/libs/jpegrecoverymap/icc.cpp b/libs/ultrahdr/icc.cpp similarity index 93% rename from libs/jpegrecoverymap/icc.cpp rename to libs/ultrahdr/icc.cpp index 6e78f671e5..c807705528 100644 --- a/libs/jpegrecoverymap/icc.cpp +++ b/libs/ultrahdr/icc.cpp @@ -14,8 +14,8 @@ * limitations under the License. */ -#include -#include +#include +#include #include #include @@ -23,7 +23,7 @@ #define FLT_MAX 0x1.fffffep127f #endif -namespace android::jpegrecoverymap { +namespace android::ultrahdr { static void Matrix3x3_apply(const Matrix3x3* m, float* x) { float y0 = x[0] * m->vals[0][0] + x[1] * m->vals[0][1] + x[2] * m->vals[0][2]; float y1 = x[0] * m->vals[1][0] + x[1] * m->vals[1][1] + x[2] * m->vals[1][2]; @@ -127,17 +127,17 @@ static void float_XYZD50_to_grid16_lab(const float* xyz_float, uint8_t* grid16_l } } -std::string IccHelper::get_desc_string(const jpegr_transfer_function tf, - const jpegr_color_gamut gamut) { +std::string IccHelper::get_desc_string(const ultrahdr_transfer_function tf, + const ultrahdr_color_gamut gamut) { std::string result; switch (gamut) { - case JPEGR_COLORGAMUT_BT709: + case ULTRAHDR_COLORGAMUT_BT709: result += "sRGB"; break; - case JPEGR_COLORGAMUT_P3: + case ULTRAHDR_COLORGAMUT_P3: result += "Display P3"; break; - case JPEGR_COLORGAMUT_BT2100: + case ULTRAHDR_COLORGAMUT_BT2100: result += "Rec2020"; break; default: @@ -146,16 +146,16 @@ std::string IccHelper::get_desc_string(const jpegr_transfer_function tf, } result += " Gamut with "; switch (tf) { - case JPEGR_TF_SRGB: + case ULTRAHDR_TF_SRGB: result += "sRGB"; break; - case JPEGR_TF_LINEAR: + case ULTRAHDR_TF_LINEAR: result += "Linear"; break; - case JPEGR_TF_PQ: + case ULTRAHDR_TF_PQ: result += "PQ"; break; - case JPEGR_TF_HLG: + case ULTRAHDR_TF_HLG: result += "HLG"; break; default: @@ -234,11 +234,11 @@ sp IccHelper::write_trc_tag_for_linear() { return dataStruct; } -float IccHelper::compute_tone_map_gain(const jpegr_transfer_function tf, float L) { +float IccHelper::compute_tone_map_gain(const ultrahdr_transfer_function tf, float L) { if (L <= 0.f) { return 1.f; } - if (tf == JPEGR_TF_PQ) { + if (tf == ULTRAHDR_TF_PQ) { // The PQ transfer function will map to the range [0, 1]. Linearly scale // it up to the range [0, 10,000/203]. We will then tone map that back // down to [0, 1]. @@ -251,7 +251,7 @@ float IccHelper::compute_tone_map_gain(const jpegr_transfer_function tf, float L constexpr float kToneMapB = 1.f / kOutputMaxLuminance; return kInputMaxLuminance * (1.f + kToneMapA * L) / (1.f + kToneMapB * L); } - if (tf == JPEGR_TF_HLG) { + if (tf == ULTRAHDR_TF_HLG) { // Let Lw be the brightness of the display in nits. constexpr float Lw = 203.f; const float gamma = 1.2f + 0.42f * std::log(Lw / 1000.f) / std::log(10.f); @@ -295,7 +295,7 @@ void IccHelper::compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]) float L = bt2100Luminance({{{rgb[0], rgb[1], rgb[2]}}}); // Compute the tone map gain based on the luminance. - float tone_map_gain = compute_tone_map_gain(JPEGR_TF_PQ, L); + float tone_map_gain = compute_tone_map_gain(ULTRAHDR_TF_PQ, L); // Apply the tone map gain. for (size_t i = 0; i < kNumChannels; ++i) { @@ -397,7 +397,8 @@ sp IccHelper::write_mAB_or_mBA_tag(uint32_t type, return dataStruct; } -sp IccHelper::writeIccProfile(jpegr_transfer_function tf, jpegr_color_gamut gamut) { +sp IccHelper::writeIccProfile(ultrahdr_transfer_function tf, + ultrahdr_color_gamut gamut) { ICCHeader header; std::vector>> tags; @@ -409,13 +410,13 @@ sp IccHelper::writeIccProfile(jpegr_transfer_function tf, jpegr_colo Matrix3x3 toXYZD50; switch (gamut) { - case JPEGR_COLORGAMUT_BT709: + case ULTRAHDR_COLORGAMUT_BT709: toXYZD50 = kSRGB; break; - case JPEGR_COLORGAMUT_P3: + case ULTRAHDR_COLORGAMUT_P3: toXYZD50 = kDisplayP3; break; - case JPEGR_COLORGAMUT_BT2100: + case ULTRAHDR_COLORGAMUT_BT2100: toXYZD50 = kRec2020; break; default: @@ -437,8 +438,8 @@ sp IccHelper::writeIccProfile(jpegr_transfer_function tf, jpegr_colo tags.emplace_back(kTAG_wtpt, write_xyz_tag(kD50_x, kD50_y, kD50_z)); // Compute transfer curves. - if (tf != JPEGR_TF_PQ) { - if (tf == JPEGR_TF_HLG) { + if (tf != ULTRAHDR_TF_PQ) { + if (tf == ULTRAHDR_TF_HLG) { std::vector trc_table; trc_table.resize(kTrcTableSize * 2); for (uint32_t i = 0; i < kTrcTableSize; ++i) { @@ -462,32 +463,32 @@ sp IccHelper::writeIccProfile(jpegr_transfer_function tf, jpegr_colo } // Compute CICP. - if (tf == JPEGR_TF_HLG || tf == JPEGR_TF_PQ) { + if (tf == ULTRAHDR_TF_HLG || tf == ULTRAHDR_TF_PQ) { // The CICP tag is present in ICC 4.4, so update the header's version. header.version = Endian_SwapBE32(0x04400000); uint32_t color_primaries = 0; - if (gamut == JPEGR_COLORGAMUT_BT709) { + if (gamut == ULTRAHDR_COLORGAMUT_BT709) { color_primaries = kCICPPrimariesSRGB; - } else if (gamut == JPEGR_COLORGAMUT_P3) { + } else if (gamut == ULTRAHDR_COLORGAMUT_P3) { color_primaries = kCICPPrimariesP3; } uint32_t transfer_characteristics = 0; - if (tf == JPEGR_TF_SRGB) { + if (tf == ULTRAHDR_TF_SRGB) { transfer_characteristics = kCICPTrfnSRGB; - } else if (tf == JPEGR_TF_LINEAR) { + } else if (tf == ULTRAHDR_TF_LINEAR) { transfer_characteristics = kCICPTrfnLinear; - } else if (tf == JPEGR_TF_PQ) { + } else if (tf == ULTRAHDR_TF_PQ) { transfer_characteristics = kCICPTrfnPQ; - } else if (tf == JPEGR_TF_HLG) { + } else if (tf == ULTRAHDR_TF_HLG) { transfer_characteristics = kCICPTrfnHLG; } tags.emplace_back(kTAG_cicp, write_cicp_tag(color_primaries, transfer_characteristics)); } // Compute A2B0. - if (tf == JPEGR_TF_PQ) { + if (tf == ULTRAHDR_TF_PQ) { std::vector a2b_grid; a2b_grid.resize(kGridSize * kGridSize * kGridSize * kNumChannels * 2); size_t a2b_grid_index = 0; @@ -520,7 +521,7 @@ sp IccHelper::writeIccProfile(jpegr_transfer_function tf, jpegr_colo } // Compute B2A0. - if (tf == JPEGR_TF_PQ) { + if (tf == ULTRAHDR_TF_PQ) { auto b2a_data = write_mAB_or_mBA_tag(kTAG_mBAType, /* has_a_curves */ false, /* grid_points */ nullptr, @@ -541,7 +542,7 @@ sp IccHelper::writeIccProfile(jpegr_transfer_function tf, jpegr_colo // Write the header. header.data_color_space = Endian_SwapBE32(Signature_RGB); - header.pcs = Endian_SwapBE32(tf == JPEGR_TF_PQ ? Signature_Lab : Signature_XYZ); + header.pcs = Endian_SwapBE32(tf == ULTRAHDR_TF_PQ ? Signature_Lab : Signature_XYZ); header.size = Endian_SwapBE32(profile_size); header.tag_count = Endian_SwapBE32(tags.size()); @@ -581,4 +582,4 @@ sp IccHelper::writeIccProfile(jpegr_transfer_function tf, jpegr_colo return dataStruct; } -} // namespace android::jpegrecoverymap \ No newline at end of file +} // namespace android::ultrahdr \ No newline at end of file diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/gainmapmath.h b/libs/ultrahdr/include/ultrahdr/gainmapmath.h similarity index 93% rename from libs/jpegrecoverymap/include/jpegrecoverymap/gainmapmath.h rename to libs/ultrahdr/include/ultrahdr/gainmapmath.h index 57fddd0a42..abc93567f2 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/gainmapmath.h +++ b/libs/ultrahdr/include/ultrahdr/gainmapmath.h @@ -14,15 +14,15 @@ * limitations under the License. */ -#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H -#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H +#ifndef ANDROID_ULTRAHDR_RECOVERYMAPMATH_H +#define ANDROID_ULTRAHDR_RECOVERYMAPMATH_H #include #include -#include +#include -namespace android::jpegrecoverymap { +namespace android::ultrahdr { #define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x) @@ -132,7 +132,7 @@ inline uint16_t floatToHalf(float f) { constexpr size_t kGainFactorPrecision = 10; constexpr size_t kGainFactorNumEntries = 1 << kGainFactorPrecision; struct GainLUT { - GainLUT(jr_metadata_ptr metadata) { + GainLUT(ultrahdr_metadata_ptr metadata) { for (int idx = 0; idx < kGainFactorNumEntries; idx++) { float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); float logBoost = log2(metadata->minContentBoost) * (1.0f - value) @@ -141,7 +141,7 @@ struct GainLUT { } } - GainLUT(jr_metadata_ptr metadata, float displayBoost) { + GainLUT(ultrahdr_metadata_ptr metadata, float displayBoost) { float boostFactor = displayBoost > 0 ? displayBoost / metadata->maxContentBoost : 1.0f; for (int idx = 0; idx < kGainFactorNumEntries; idx++) { float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); @@ -356,7 +356,7 @@ inline Color identityConversion(Color e) { return e; } /* * Get the conversion to apply to the HDR image for gain map generation */ -ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gamut hdr_gamut); +ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut, ultrahdr_color_gamut hdr_gamut); //////////////////////////////////////////////////////////////////////////////// @@ -366,16 +366,16 @@ ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gam * Calculate the 8-bit unsigned integer gain value for the given SDR and HDR * luminances in linear space, and the hdr ratio to encode against. */ -uint8_t encodeGain(float y_sdr, float y_hdr, jr_metadata_ptr metadata); -uint8_t encodeGain(float y_sdr, float y_hdr, jr_metadata_ptr metadata, +uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata); +uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata, float log2MinContentBoost, float log2MaxContentBoost); /* * Calculates the linear luminance in nits after applying the given gain * value, with the given hdr ratio, to the given sdr input in the range [0, 1]. */ -Color applyGain(Color e, float gain, jr_metadata_ptr metadata); -Color applyGain(Color e, float gain, jr_metadata_ptr metadata, float displayBoost); +Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata); +Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata, float displayBoost); Color applyGainLUT(Color e, float gain, GainLUT& gainLUT); /* @@ -426,6 +426,6 @@ uint32_t colorToRgba1010102(Color e_gamma); */ uint64_t colorToRgbaF16(Color e_gamma); -} // namespace android::jpegrecoverymap +} // namespace android::ultrahdr -#endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H +#endif // ANDROID_ULTRAHDR_RECOVERYMAPMATH_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/icc.h b/libs/ultrahdr/include/ultrahdr/icc.h similarity index 93% rename from libs/jpegrecoverymap/include/jpegrecoverymap/icc.h rename to libs/ultrahdr/include/ultrahdr/icc.h index a81aa62627..7f6ab882c6 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/icc.h +++ b/libs/ultrahdr/include/ultrahdr/icc.h @@ -14,11 +14,11 @@ * limitations under the License. */ -#ifndef ANDROID_JPEGRECOVERYMAP_ICC_H -#define ANDROID_JPEGRECOVERYMAP_ICC_H +#ifndef ANDROID_ULTRAHDR_ICC_H +#define ANDROID_ULTRAHDR_ICC_H -#include -#include +#include +#include #include #include #include @@ -28,7 +28,7 @@ #define USE_BIG_ENDIAN true #endif -namespace android::jpegrecoverymap { +namespace android::ultrahdr { typedef int32_t Fixed; #define Fixed1 (1 << 16) @@ -210,12 +210,12 @@ private: static constexpr size_t kNumChannels = 3; static sp write_text_tag(const char* text); - static std::string get_desc_string(const jpegr_transfer_function tf, - const jpegr_color_gamut gamut); + static std::string get_desc_string(const ultrahdr_transfer_function tf, + const ultrahdr_color_gamut gamut); static sp write_xyz_tag(float x, float y, float z); static sp write_trc_tag(const int table_entries, const void* table_16); static sp write_trc_tag_for_linear(); - static float compute_tone_map_gain(const jpegr_transfer_function tf, float L); + static float compute_tone_map_gain(const ultrahdr_transfer_function tf, float L); static sp write_cicp_tag(uint32_t color_primaries, uint32_t transfer_characteristics); static sp write_mAB_or_mBA_tag(uint32_t type, @@ -226,9 +226,9 @@ private: static sp write_clut(const uint8_t* grid_points, const uint8_t* grid_16); public: - static sp writeIccProfile(const jpegr_transfer_function tf, - const jpegr_color_gamut gamut); + static sp writeIccProfile(const ultrahdr_transfer_function tf, + const ultrahdr_color_gamut gamut); }; -} // namespace android::jpegrecoverymap +} // namespace android::ultrahdr -#endif //ANDROID_JPEGRECOVERYMAP_ICC_H \ No newline at end of file +#endif //ANDROID_ULTRAHDR_ICC_H \ No newline at end of file diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h similarity index 94% rename from libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h rename to libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h index 874823709c..f642bad89c 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoderhelper.h +++ b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_JPEGRECOVERYMAP_JPEGDECODERHELPER_H -#define ANDROID_JPEGRECOVERYMAP_JPEGDECODERHELPER_H +#ifndef ANDROID_ULTRAHDR_JPEGDECODERHELPER_H +#define ANDROID_ULTRAHDR_JPEGDECODERHELPER_H // We must include cstdio before jpeglib.h. It is a requirement of libjpeg. #include @@ -25,7 +25,7 @@ extern "C" { } #include #include -namespace android::jpegrecoverymap { +namespace android::ultrahdr { /* * Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format. * This class is not thread-safe. @@ -115,6 +115,6 @@ private: // Position of EXIF package, default value is -1 which means no EXIF package appears. size_t mExifPos; }; -} /* namespace android::jpegrecoverymap */ +} /* namespace android::ultrahdr */ -#endif // ANDROID_JPEGRECOVERYMAP_JPEGDECODERHELPER_H +#endif // ANDROID_ULTRAHDR_JPEGDECODERHELPER_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h similarity index 93% rename from libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h rename to libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h index 8b82b2b00a..ac0215559d 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoderhelper.h +++ b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_JPEGRECOVERYMAP_JPEGENCODERHELPER_H -#define ANDROID_JPEGRECOVERYMAP_JPEGENCODERHELPER_H +#ifndef ANDROID_ULTRAHDR_JPEGENCODERHELPER_H +#define ANDROID_ULTRAHDR_JPEGENCODERHELPER_H // We must include cstdio before jpeglib.h. It is a requirement of libjpeg. #include @@ -28,7 +28,7 @@ extern "C" { #include #include -namespace android::jpegrecoverymap { +namespace android::ultrahdr { /* * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format. @@ -90,6 +90,6 @@ private: std::vector mResultBuffer; }; -} /* namespace android::jpegrecoverymap */ +} /* namespace android::ultrahdr */ -#endif // ANDROID_JPEGRECOVERYMAP_JPEGENCODERHELPER_H +#endif // ANDROID_ULTRAHDR_JPEGENCODERHELPER_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h b/libs/ultrahdr/include/ultrahdr/jpegr.h similarity index 88% rename from libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h rename to libs/ultrahdr/include/ultrahdr/jpegr.h index ce7b33b495..b755b19b26 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegr.h +++ b/libs/ultrahdr/include/ultrahdr/jpegr.h @@ -14,41 +14,17 @@ * limitations under the License. */ -#ifndef ANDROID_JPEGRECOVERYMAP_JPEGR_H -#define ANDROID_JPEGRECOVERYMAP_JPEGR_H +#ifndef ANDROID_ULTRAHDR_JPEGR_H +#define ANDROID_ULTRAHDR_JPEGR_H #include "jpegrerrorcode.h" +#include "ultrahdr.h" #ifndef FLT_MAX #define FLT_MAX 0x1.fffffep127f #endif -namespace android::jpegrecoverymap { - -// Color gamuts for image data -typedef enum { - JPEGR_COLORGAMUT_UNSPECIFIED, - JPEGR_COLORGAMUT_BT709, - JPEGR_COLORGAMUT_P3, - JPEGR_COLORGAMUT_BT2100, -} jpegr_color_gamut; - -// Transfer functions for image data -typedef enum { - JPEGR_TF_UNSPECIFIED = -1, - JPEGR_TF_LINEAR = 0, - JPEGR_TF_HLG = 1, - JPEGR_TF_PQ = 2, - JPEGR_TF_SRGB = 3, -} jpegr_transfer_function; - -// Target output formats for decoder -typedef enum { - JPEGR_OUTPUT_SDR, // SDR in RGBA_8888 color format - JPEGR_OUTPUT_HDR_LINEAR, // HDR in F16 color format (linear) - JPEGR_OUTPUT_HDR_PQ, // HDR in RGBA_1010102 color format (PQ transfer function) - JPEGR_OUTPUT_HDR_HLG, // HDR in RGBA_1010102 color format (HLG transfer function) -} jpegr_output_format; +namespace android::ultrahdr { struct jpegr_info_struct { size_t width; @@ -68,7 +44,7 @@ struct jpegr_uncompressed_struct { // Height of the gain map or the luma plane of the image in pixels. int height; // Color gamut. - jpegr_color_gamut colorGamut; + ultrahdr_color_gamut colorGamut; // Values below are optional // Pointer to chroma data, if it's NULL, chroma plane is considered to be immediately @@ -96,7 +72,7 @@ struct jpegr_compressed_struct { // Maximum available data length in bytes. int maxLength; // Color gamut. - jpegr_color_gamut colorGamut; + ultrahdr_color_gamut colorGamut; }; /* @@ -109,22 +85,9 @@ struct jpegr_exif_struct { int length; }; -/* - * Holds information for gain map related metadata. - */ -struct jpegr_metadata_struct { - // JPEG/R version - uint32_t version; - // Max Content Boost for the map - float maxContentBoost; - // Min Content Boost for the map - float minContentBoost; -}; - typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr; typedef struct jpegr_compressed_struct* jr_compressed_ptr; typedef struct jpegr_exif_struct* jr_exif_ptr; -typedef struct jpegr_metadata_struct* jr_metadata_ptr; typedef struct jpegr_info_struct* jr_info_ptr; class JpegR { @@ -147,7 +110,7 @@ public: * @return NO_ERROR if encoding succeeds, error code if error occurs. */ status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, - jpegr_transfer_function hdr_tf, + ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality, jr_exif_ptr exif); @@ -170,7 +133,7 @@ public: */ status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, - jpegr_transfer_function hdr_tf, + ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality, jr_exif_ptr exif); @@ -195,7 +158,7 @@ public: status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, jr_compressed_ptr compressed_jpeg_image, - jpegr_transfer_function hdr_tf, + ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest); /* @@ -215,7 +178,7 @@ public: */ status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_compressed_ptr compressed_jpeg_image, - jpegr_transfer_function hdr_tf, + ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest); /* @@ -249,16 +212,16 @@ public: * @param metadata destination of the decoded metadata. The default value is NULL where the decoder will do nothing about it. If configured not NULL the decoder will write metadata into this structure. the format of metadata is defined in - {@code jpegr_metadata}. + {@code ultrahdr_metadata_struct}. * @return NO_ERROR if decoding succeeds, error code if error occurs. */ status_t decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, jr_uncompressed_ptr dest, float max_display_boost = FLT_MAX, jr_exif_ptr exif = nullptr, - jpegr_output_format output_format = JPEGR_OUTPUT_HDR_LINEAR, + ultrahdr_output_format output_format = ULTRAHDR_OUTPUT_HDR_LINEAR, jr_uncompressed_ptr gain_map = nullptr, - jr_metadata_ptr metadata = nullptr); + ultrahdr_metadata_ptr metadata = nullptr); /* * Gets Info from JPEGR file without decoding it. @@ -286,8 +249,8 @@ protected: */ status_t generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, jr_uncompressed_ptr uncompressed_p010_image, - jpegr_transfer_function hdr_tf, - jr_metadata_ptr metadata, + ultrahdr_transfer_function hdr_tf, + ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest); /* @@ -308,8 +271,8 @@ protected: */ status_t applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, jr_uncompressed_ptr uncompressed_gain_map, - jr_metadata_ptr metadata, - jpegr_output_format output_format, + ultrahdr_metadata_ptr metadata, + ultrahdr_output_format output_format, float max_display_boost, jr_uncompressed_ptr dest); @@ -365,7 +328,7 @@ private: status_t appendGainMap(jr_compressed_ptr compressed_jpeg_image, jr_compressed_ptr compressed_gain_map, jr_exif_ptr exif, - jr_metadata_ptr metadata, + ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest); /* @@ -389,6 +352,6 @@ private: jr_uncompressed_ptr uncompressed_yuv_420_image); }; -} // namespace android::jpegrecoverymap +} // namespace android::ultrahdr -#endif // ANDROID_JPEGRECOVERYMAP_JPEGR_H +#endif // ANDROID_ULTRAHDR_JPEGR_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h b/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h similarity index 91% rename from libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h rename to libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h index 159aaa8107..9f59c3eaf3 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h +++ b/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h @@ -14,9 +14,12 @@ * limitations under the License. */ +#ifndef ANDROID_ULTRAHDR_JPEGRERRORCODE_H +#define ANDROID_ULTRAHDR_JPEGRERRORCODE_H + #include -namespace android::jpegrecoverymap { +namespace android::ultrahdr { enum { // status_t map for errors in the media framework @@ -50,4 +53,6 @@ enum { ERROR_JPEGR_UNSUPPORTED_FEATURE = -20000, }; -} // namespace android::jpegrecoverymap +} // namespace android::ultrahdr + +#endif // ANDROID_ULTRAHDR_JPEGRERRORCODE_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h b/libs/ultrahdr/include/ultrahdr/jpegrutils.h similarity index 90% rename from libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h rename to libs/ultrahdr/include/ultrahdr/jpegrutils.h index 09f416555c..ed38e07c2d 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h +++ b/libs/ultrahdr/include/ultrahdr/jpegrutils.h @@ -14,10 +14,10 @@ * limitations under the License. */ -#ifndef ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H -#define ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H +#ifndef ANDROID_ULTRAHDR_JPEGRUTILS_H +#define ANDROID_ULTRAHDR_JPEGRUTILS_H -#include +#include #include #include @@ -25,7 +25,7 @@ #include #include -namespace android::jpegrecoverymap { +namespace android::ultrahdr { static constexpr uint32_t EndianSwap32(uint32_t value) { return ((value & 0xFF) << 24) | @@ -45,7 +45,7 @@ static inline uint16_t EndianSwap16(uint16_t value) { #define Endian_SwapBE16(n) (n) #endif -struct jpegr_metadata_struct; +struct ultrahdr_metadata_struct; /* * Mutable data structure. Holds information for metadata. */ @@ -87,7 +87,7 @@ status_t Write(jr_compressed_ptr destination, const void* source, size_t length, * @param metadata place to store HDR metadata values * @return true if metadata is successfully retrieved, false otherwise */ -bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata_struct* metadata); +bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, ultrahdr_metadata_struct* metadata); /* * This method generates XMP metadata for the primary image. @@ -158,7 +158,7 @@ std::string generateXmpForPrimaryImage(int secondary_image_length); * @param metadata JPEG/R metadata to encode as XMP * @return XMP metadata in type of string */ - std::string generateXmpForSecondaryImage(jpegr_metadata_struct& metadata); -} // namespace android::jpegrecoverymap + std::string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata); +} // namespace android::ultrahdr -#endif //ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H +#endif //ANDROID_ULTRAHDR_JPEGRUTILS_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h b/libs/ultrahdr/include/ultrahdr/multipictureformat.h similarity index 87% rename from libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h rename to libs/ultrahdr/include/ultrahdr/multipictureformat.h index cf3387d9b6..c5bd09d6f2 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h +++ b/libs/ultrahdr/include/ultrahdr/multipictureformat.h @@ -14,17 +14,17 @@ * limitations under the License. */ -#ifndef ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H -#define ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H +#ifndef ANDROID_ULTRAHDR_MULTIPICTUREFORMAT_H +#define ANDROID_ULTRAHDR_MULTIPICTUREFORMAT_H -#include +#include #ifdef USE_BIG_ENDIAN #undef USE_BIG_ENDIAN #define USE_BIG_ENDIAN true #endif -namespace android::jpegrecoverymap { +namespace android::ultrahdr { constexpr size_t kNumPictures = 2; constexpr size_t kMpEndianSize = 4; @@ -59,6 +59,6 @@ size_t calculateMpfSize(); sp generateMpf(int primary_image_size, int primary_image_offset, int secondary_image_size, int secondary_image_offset); -} // namespace android::jpegrecoverymap +} // namespace android::ultrahdr -#endif //ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H +#endif //ANDROID_ULTRAHDR_MULTIPICTUREFORMAT_H diff --git a/libs/ultrahdr/include/ultrahdr/ultrahdr.h b/libs/ultrahdr/include/ultrahdr/ultrahdr.h new file mode 100644 index 0000000000..302aeee528 --- /dev/null +++ b/libs/ultrahdr/include/ultrahdr/ultrahdr.h @@ -0,0 +1,61 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_ULTRAHDR_ULTRAHDR_H +#define ANDROID_ULTRAHDR_ULTRAHDR_H + +namespace android::ultrahdr { +// Color gamuts for image data +typedef enum { + ULTRAHDR_COLORGAMUT_UNSPECIFIED, + ULTRAHDR_COLORGAMUT_BT709, + ULTRAHDR_COLORGAMUT_P3, + ULTRAHDR_COLORGAMUT_BT2100, +} ultrahdr_color_gamut; + +// Transfer functions for image data +typedef enum { + ULTRAHDR_TF_UNSPECIFIED = -1, + ULTRAHDR_TF_LINEAR = 0, + ULTRAHDR_TF_HLG = 1, + ULTRAHDR_TF_PQ = 2, + ULTRAHDR_TF_SRGB = 3, +} ultrahdr_transfer_function; + +// Target output formats for decoder +typedef enum { + ULTRAHDR_OUTPUT_SDR, // SDR in RGBA_8888 color format + ULTRAHDR_OUTPUT_HDR_LINEAR, // HDR in F16 color format (linear) + ULTRAHDR_OUTPUT_HDR_PQ, // HDR in RGBA_1010102 color format (PQ transfer function) + ULTRAHDR_OUTPUT_HDR_HLG, // HDR in RGBA_1010102 color format (HLG transfer function) +} ultrahdr_output_format; + +/* + * Holds information for gain map related metadata. + */ +struct ultrahdr_metadata_struct { + // Ultra HDR library version + uint32_t version; + // Max Content Boost for the map + float maxContentBoost; + // Min Content Boost for the map + float minContentBoost; +}; +typedef struct ultrahdr_metadata_struct* ultrahdr_metadata_ptr; + +} // namespace android::ultrahdr + +#endif //ANDROID_ULTRAHDR_ULTRAHDR_H \ No newline at end of file diff --git a/libs/jpegrecoverymap/jpegdecoderhelper.cpp b/libs/ultrahdr/jpegdecoderhelper.cpp similarity index 99% rename from libs/jpegrecoverymap/jpegdecoderhelper.cpp rename to libs/ultrahdr/jpegdecoderhelper.cpp index d36bbf8165..12217b7906 100644 --- a/libs/jpegrecoverymap/jpegdecoderhelper.cpp +++ b/libs/ultrahdr/jpegdecoderhelper.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include @@ -24,7 +24,7 @@ using namespace std; -namespace android::jpegrecoverymap { +namespace android::ultrahdr { const uint32_t kAPP0Marker = JPEG_APP0; // JFIF const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP @@ -413,4 +413,4 @@ bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo, c return true; } -} // namespace jpegrecoverymap +} // namespace ultrahdr diff --git a/libs/jpegrecoverymap/jpegencoderhelper.cpp b/libs/ultrahdr/jpegencoderhelper.cpp similarity index 98% rename from libs/jpegrecoverymap/jpegencoderhelper.cpp rename to libs/ultrahdr/jpegencoderhelper.cpp index 586cd346e4..fc6e4d1e64 100644 --- a/libs/jpegrecoverymap/jpegencoderhelper.cpp +++ b/libs/ultrahdr/jpegencoderhelper.cpp @@ -14,13 +14,13 @@ * limitations under the License. */ -#include +#include #include #include -namespace android::jpegrecoverymap { +namespace android::ultrahdr { // The destination manager that can access |mResultBuffer| in JpegEncoderHelper. struct destination_mgr { @@ -236,4 +236,4 @@ bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const return true; } -} // namespace jpegrecoverymap +} // namespace ultrahdr diff --git a/libs/jpegrecoverymap/jpegr.cpp b/libs/ultrahdr/jpegr.cpp similarity index 93% rename from libs/jpegrecoverymap/jpegr.cpp rename to libs/ultrahdr/jpegr.cpp index 2590f63623..8e1dc8c529 100644 --- a/libs/jpegrecoverymap/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -14,13 +14,13 @@ * limitations under the License. */ -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -43,7 +43,7 @@ using namespace std; using namespace photos_editing_formats::image_io; -namespace android::jpegrecoverymap { +namespace android::ultrahdr { #define USE_SRGB_INVOETF_LUT 1 #define USE_HLG_OETF_LUT 1 @@ -136,7 +136,7 @@ status_t JpegR::areInputImagesValid(jr_uncompressed_ptr uncompressed_p010_image, /* Encode API-0 */ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, - jpegr_transfer_function hdr_tf, + ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality, jr_exif_ptr exif) { @@ -153,7 +153,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return ret; } - jpegr_metadata_struct metadata; + ultrahdr_metadata_struct metadata; metadata.version = kJpegrVersion; jpegr_uncompressed_struct uncompressed_yuv_420_image; @@ -174,7 +174,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, compressed_map.data = compressed_map_data.get(); JPEGR_CHECK(compressGainMap(&map, &compressed_map)); - sp icc = IccHelper::writeIccProfile(JPEGR_TF_SRGB, + sp icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, uncompressed_yuv_420_image.colorGamut); JpegEncoderHelper jpeg_encoder; @@ -196,7 +196,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, /* Encode API-1 */ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, - jpegr_transfer_function hdr_tf, + ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality, jr_exif_ptr exif) { @@ -215,7 +215,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return ret; } - jpegr_metadata_struct metadata; + ultrahdr_metadata_struct metadata; metadata.version = kJpegrVersion; jpegr_uncompressed_struct map; @@ -230,7 +230,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, compressed_map.data = compressed_map_data.get(); JPEGR_CHECK(compressGainMap(&map, &compressed_map)); - sp icc = IccHelper::writeIccProfile(JPEGR_TF_SRGB, + sp icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, uncompressed_yuv_420_image->colorGamut); JpegEncoderHelper jpeg_encoder; @@ -253,7 +253,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, jr_compressed_ptr compressed_jpeg_image, - jpegr_transfer_function hdr_tf, + ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) { if (uncompressed_p010_image == nullptr || uncompressed_yuv_420_image == nullptr @@ -267,7 +267,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return ret; } - jpegr_metadata_struct metadata; + ultrahdr_metadata_struct metadata; metadata.version = kJpegrVersion; jpegr_uncompressed_struct map; @@ -290,7 +290,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, /* Encode API-3 */ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_compressed_ptr compressed_jpeg_image, - jpegr_transfer_function hdr_tf, + ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) { if (uncompressed_p010_image == nullptr || compressed_jpeg_image == nullptr @@ -318,7 +318,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return ERROR_JPEGR_RESOLUTION_MISMATCH; } - jpegr_metadata_struct metadata; + ultrahdr_metadata_struct metadata; metadata.version = kJpegrVersion; jpegr_uncompressed_struct map; @@ -362,9 +362,9 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, jr_uncompressed_ptr dest, float max_display_boost, jr_exif_ptr exif, - jpegr_output_format output_format, + ultrahdr_output_format output_format, jr_uncompressed_ptr gain_map, - jr_metadata_ptr metadata) { + ultrahdr_metadata_ptr metadata) { if (compressed_jpegr_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } @@ -373,7 +373,7 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, return ERROR_JPEGR_INVALID_INPUT_TYPE; } - if (output_format == JPEGR_OUTPUT_SDR) { + if (output_format == ULTRAHDR_OUTPUT_SDR) { JpegDecoderHelper jpeg_decoder; if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length, true)) { @@ -423,19 +423,19 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, memcpy(gain_map->data, gain_map_decoder.getDecompressedImagePtr(), size); } - jpegr_metadata_struct jr_metadata; + ultrahdr_metadata_struct uhdr_metadata; if (!getMetadataFromXMP(static_cast(gain_map_decoder.getXMPPtr()), - gain_map_decoder.getXMPSize(), &jr_metadata)) { + gain_map_decoder.getXMPSize(), &uhdr_metadata)) { return ERROR_JPEGR_DECODE_ERROR; } if (metadata != nullptr) { - metadata->version = jr_metadata.version; - metadata->minContentBoost = jr_metadata.minContentBoost; - metadata->maxContentBoost = jr_metadata.maxContentBoost; + metadata->version = uhdr_metadata.version; + metadata->minContentBoost = uhdr_metadata.minContentBoost; + metadata->maxContentBoost = uhdr_metadata.maxContentBoost; } - if (output_format == JPEGR_OUTPUT_SDR) { + if (output_format == ULTRAHDR_OUTPUT_SDR) { return NO_ERROR; } @@ -465,7 +465,7 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth(); uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight(); - JPEGR_CHECK(applyGainMap(&uncompressed_yuv_420_image, &map, &jr_metadata, output_format, + JPEGR_CHECK(applyGainMap(&uncompressed_yuv_420_image, &map, &uhdr_metadata, output_format, max_display_boost, dest)); return NO_ERROR; } @@ -493,7 +493,7 @@ status_t JpegR::compressGainMap(jr_uncompressed_ptr uncompressed_gain_map, memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize()); dest->length = jpeg_encoder.getCompressedImageSize(); - dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED; + dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; return NO_ERROR; } @@ -556,8 +556,8 @@ void JobQueue::reset() { status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, jr_uncompressed_ptr uncompressed_p010_image, - jpegr_transfer_function hdr_tf, - jr_metadata_ptr metadata, + ultrahdr_transfer_function hdr_tf, + ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest) { if (uncompressed_yuv_420_image == nullptr || uncompressed_p010_image == nullptr @@ -571,8 +571,8 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, return ERROR_JPEGR_RESOLUTION_MISMATCH; } - if (uncompressed_yuv_420_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED - || uncompressed_p010_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED) { + if (uncompressed_yuv_420_image->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED + || uncompressed_p010_image->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) { return ERROR_JPEGR_INVALID_COLORGAMUT; } @@ -586,7 +586,7 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, dest->width = map_stride; dest->height = map_height_aligned; - dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED; + dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; dest->data = new uint8_t[map_stride * map_height_aligned]; std::unique_ptr map_data; map_data.reset(reinterpret_cast(dest->data)); @@ -594,10 +594,10 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, ColorTransformFn hdrInvOetf = nullptr; float hdr_white_nits = 0.0f; switch (hdr_tf) { - case JPEGR_TF_LINEAR: + case ULTRAHDR_TF_LINEAR: hdrInvOetf = identityConversion; break; - case JPEGR_TF_HLG: + case ULTRAHDR_TF_HLG: #if USE_HLG_INVOETF_LUT hdrInvOetf = hlgInvOetfLUT; #else @@ -605,7 +605,7 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, #endif hdr_white_nits = kHlgMaxNits; break; - case JPEGR_TF_PQ: + case ULTRAHDR_TF_PQ: #if USE_PQ_INVOETF_LUT hdrInvOetf = pqInvOetfLUT; #else @@ -628,16 +628,16 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, ColorCalculationFn luminanceFn = nullptr; switch (uncompressed_yuv_420_image->colorGamut) { - case JPEGR_COLORGAMUT_BT709: + case ULTRAHDR_COLORGAMUT_BT709: luminanceFn = srgbLuminance; break; - case JPEGR_COLORGAMUT_P3: + case ULTRAHDR_COLORGAMUT_P3: luminanceFn = p3Luminance; break; - case JPEGR_COLORGAMUT_BT2100: + case ULTRAHDR_COLORGAMUT_BT2100: luminanceFn = bt2100Luminance; break; - case JPEGR_COLORGAMUT_UNSPECIFIED: + case ULTRAHDR_COLORGAMUT_UNSPECIFIED: // Should be impossible to hit after input validation. return ERROR_JPEGR_INVALID_COLORGAMUT; } @@ -703,8 +703,8 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, jr_uncompressed_ptr uncompressed_gain_map, - jr_metadata_ptr metadata, - jpegr_output_format output_format, + ultrahdr_metadata_ptr metadata, + ultrahdr_output_format output_format, float max_display_boost, jr_uncompressed_ptr dest) { if (uncompressed_yuv_420_image == nullptr @@ -759,13 +759,13 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, size_t pixel_idx = x + y * width; switch (output_format) { - case JPEGR_OUTPUT_HDR_LINEAR: + case ULTRAHDR_OUTPUT_HDR_LINEAR: { uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr); reinterpret_cast(dest->data)[pixel_idx] = rgba_f16; break; } - case JPEGR_OUTPUT_HDR_HLG: + case ULTRAHDR_OUTPUT_HDR_HLG: { #if USE_HLG_OETF_LUT ColorTransformFn hdrOetf = hlgOetfLUT; @@ -777,7 +777,7 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, reinterpret_cast(dest->data)[pixel_idx] = rgba_1010102; break; } - case JPEGR_OUTPUT_HDR_PQ: + case ULTRAHDR_OUTPUT_HDR_PQ: { #if USE_HLG_OETF_LUT ColorTransformFn hdrOetf = pqOetfLUT; @@ -910,7 +910,7 @@ status_t JpegR::extractGainMap(jr_compressed_ptr compressed_jpegr_image, status_t JpegR::appendGainMap(jr_compressed_ptr compressed_jpeg_image, jr_compressed_ptr compressed_gain_map, jr_exif_ptr exif, - jr_metadata_ptr metadata, + ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest) { if (compressed_jpeg_image == nullptr || compressed_gain_map == nullptr @@ -1034,7 +1034,7 @@ status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) { uint16_t* src_chroma_data = reinterpret_cast(src->chroma_data); if (src_chroma_data == nullptr) { - src_chroma_data = &reinterpret_cast(src->data)[src->luma_stride * src->height]; + src_chroma_data = &reinterpret_cast(src->data)[src_luma_stride * src->height]; } if (src_luma_stride == 0) { src_luma_stride = src->width; @@ -1077,4 +1077,4 @@ status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) { return NO_ERROR; } -} // namespace android::jpegrecoverymap +} // namespace android::ultrahdr diff --git a/libs/jpegrecoverymap/jpegrutils.cpp b/libs/ultrahdr/jpegrutils.cpp similarity index 97% rename from libs/jpegrecoverymap/jpegrutils.cpp rename to libs/ultrahdr/jpegrutils.cpp index cde0ceb520..9d07a6f889 100644 --- a/libs/jpegrecoverymap/jpegrutils.cpp +++ b/libs/ultrahdr/jpegrutils.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include #include @@ -30,7 +30,7 @@ using namespace photos_editing_formats::image_io; using namespace std; -namespace android::jpegrecoverymap { +namespace android::ultrahdr { /* * Helper function used for generating XMP metadata. * @@ -256,7 +256,7 @@ const string kMapBaseRenditionIsHDR = Name(kGainMapPrefix, "BaseRenditionIsHDR") const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin; const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax; -bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata_struct* metadata) { +bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, ultrahdr_metadata_struct* metadata) { string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; if (xmp_size < nameSpace.size()+2) { @@ -338,7 +338,7 @@ string generateXmpForPrimaryImage(int secondary_image_length) { return ss.str(); } -string generateXmpForSecondaryImage(jpegr_metadata_struct& metadata) { +string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata) { const vector kConDirSeq({kConDirectory, string("rdf:Seq")}); std::stringstream ss; @@ -365,4 +365,4 @@ string generateXmpForSecondaryImage(jpegr_metadata_struct& metadata) { return ss.str(); } -} // namespace android::jpegrecoverymap +} // namespace android::ultrahdr diff --git a/libs/jpegrecoverymap/multipictureformat.cpp b/libs/ultrahdr/multipictureformat.cpp similarity index 96% rename from libs/jpegrecoverymap/multipictureformat.cpp rename to libs/ultrahdr/multipictureformat.cpp index a219aef106..7a265c61b7 100644 --- a/libs/jpegrecoverymap/multipictureformat.cpp +++ b/libs/ultrahdr/multipictureformat.cpp @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include -#include +#include +#include -namespace android::jpegrecoverymap { +namespace android::ultrahdr { size_t calculateMpfSize() { return sizeof(kMpfSig) + // Signature kMpEndianSize + // Endianness @@ -91,4 +91,4 @@ sp generateMpf(int primary_image_size, int primary_image_offset, return dataStruct; } -} // namespace android::jpegrecoverymap +} // namespace android::ultrahdr diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/ultrahdr/tests/Android.bp similarity index 96% rename from libs/jpegrecoverymap/tests/Android.bp rename to libs/ultrahdr/tests/Android.bp index 59b1237a68..7dd9d04fbd 100644 --- a/libs/jpegrecoverymap/tests/Android.bp +++ b/libs/ultrahdr/tests/Android.bp @@ -22,7 +22,7 @@ package { } cc_test { - name: "libjpegrecoverymap_test", + name: "libultrahdr_test", test_suites: ["device-tests"], srcs: [ "jpegr_test.cpp", @@ -38,7 +38,7 @@ cc_test { "libgtest", "libjpegdecoder", "libjpegencoder", - "libjpegrecoverymap", + "libultrahdr", "libutils", ], } diff --git a/libs/jpegrecoverymap/tests/data/jpeg_image.jpg b/libs/ultrahdr/tests/data/jpeg_image.jpg similarity index 100% rename from libs/jpegrecoverymap/tests/data/jpeg_image.jpg rename to libs/ultrahdr/tests/data/jpeg_image.jpg diff --git a/libs/jpegrecoverymap/tests/data/minnie-318x240.yu12 b/libs/ultrahdr/tests/data/minnie-318x240.yu12 similarity index 100% rename from libs/jpegrecoverymap/tests/data/minnie-318x240.yu12 rename to libs/ultrahdr/tests/data/minnie-318x240.yu12 diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240-y.jpg b/libs/ultrahdr/tests/data/minnie-320x240-y.jpg similarity index 100% rename from libs/jpegrecoverymap/tests/data/minnie-320x240-y.jpg rename to libs/ultrahdr/tests/data/minnie-320x240-y.jpg diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240-yuv.jpg b/libs/ultrahdr/tests/data/minnie-320x240-yuv.jpg similarity index 100% rename from libs/jpegrecoverymap/tests/data/minnie-320x240-yuv.jpg rename to libs/ultrahdr/tests/data/minnie-320x240-yuv.jpg diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240.y b/libs/ultrahdr/tests/data/minnie-320x240.y similarity index 100% rename from libs/jpegrecoverymap/tests/data/minnie-320x240.y rename to libs/ultrahdr/tests/data/minnie-320x240.y diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240.yu12 b/libs/ultrahdr/tests/data/minnie-320x240.yu12 similarity index 100% rename from libs/jpegrecoverymap/tests/data/minnie-320x240.yu12 rename to libs/ultrahdr/tests/data/minnie-320x240.yu12 diff --git a/libs/jpegrecoverymap/tests/data/raw_p010_image.p010 b/libs/ultrahdr/tests/data/raw_p010_image.p010 similarity index 100% rename from libs/jpegrecoverymap/tests/data/raw_p010_image.p010 rename to libs/ultrahdr/tests/data/raw_p010_image.p010 diff --git a/libs/jpegrecoverymap/tests/data/raw_p010_image_with_stride.p010 b/libs/ultrahdr/tests/data/raw_p010_image_with_stride.p010 similarity index 100% rename from libs/jpegrecoverymap/tests/data/raw_p010_image_with_stride.p010 rename to libs/ultrahdr/tests/data/raw_p010_image_with_stride.p010 diff --git a/libs/jpegrecoverymap/tests/data/raw_yuv420_image.yuv420 b/libs/ultrahdr/tests/data/raw_yuv420_image.yuv420 similarity index 100% rename from libs/jpegrecoverymap/tests/data/raw_yuv420_image.yuv420 rename to libs/ultrahdr/tests/data/raw_yuv420_image.yuv420 diff --git a/libs/jpegrecoverymap/tests/gainmapmath_test.cpp b/libs/ultrahdr/tests/gainmapmath_test.cpp similarity index 95% rename from libs/jpegrecoverymap/tests/gainmapmath_test.cpp rename to libs/ultrahdr/tests/gainmapmath_test.cpp index 21de2e6c22..c456653821 100644 --- a/libs/jpegrecoverymap/tests/gainmapmath_test.cpp +++ b/libs/ultrahdr/tests/gainmapmath_test.cpp @@ -17,9 +17,9 @@ #include #include #include -#include +#include -namespace android::jpegrecoverymap { +namespace android::ultrahdr { class GainMapMathTest : public testing::Test { public: @@ -88,7 +88,7 @@ public: return luminance_scaled * scale_factor; } - Color Recover(Color yuv_gamma, float gain, jr_metadata_ptr metadata) { + Color Recover(Color yuv_gamma, float gain, ultrahdr_metadata_ptr metadata) { Color rgb_gamma = srgbYuvToRgb(yuv_gamma); Color rgb = srgbInvOetf(rgb_gamma); return applyGain(rgb, gain, metadata); @@ -108,7 +108,7 @@ public: 0xB0, 0xB1, 0xB2, 0xB3, }; - return { pixels, 4, 4, JPEGR_COLORGAMUT_BT709 }; + return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709 }; } Color (*Yuv420Colors())[4] { @@ -141,7 +141,7 @@ public: 0xA0 << 6, 0xB0 << 6, 0xA1 << 6, 0xB1 << 6, 0xA2 << 6, 0xB2 << 6, 0xA3 << 6, 0xB3 << 6, }; - return { pixels, 4, 4, JPEGR_COLORGAMUT_BT709 }; + return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709 }; } Color (*P010Colors())[4] { @@ -170,7 +170,7 @@ public: 0x02, 0x12, 0x22, 0x32, 0x03, 0x13, 0x23, 0x33, }; - return { pixels, 4, 4, JPEGR_COLORGAMUT_UNSPECIFIED }; + return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_UNSPECIFIED }; } float (*MapValues())[4] { @@ -554,7 +554,7 @@ TEST_F(GainMapMathTest, srgbInvOetfLUT) { TEST_F(GainMapMathTest, applyGainLUT) { for (int boost = 1; boost <= 10; boost++) { - jpegr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), + ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), .minContentBoost = 1.0f / static_cast(boost) }; GainLUT gainLUT(&metadata); GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost); @@ -584,7 +584,7 @@ TEST_F(GainMapMathTest, applyGainLUT) { } for (int boost = 1; boost <= 10; boost++) { - jpegr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), + ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), .minContentBoost = 1.0f }; GainLUT gainLUT(&metadata); GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost); @@ -614,7 +614,7 @@ TEST_F(GainMapMathTest, applyGainLUT) { } for (int boost = 1; boost <= 10; boost++) { - jpegr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), + ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), .minContentBoost = 1.0f / pow(static_cast(boost), 1.0f / 3.0f) }; GainLUT gainLUT(&metadata); @@ -654,45 +654,45 @@ TEST_F(GainMapMathTest, PqTransferFunctionRoundtrip) { } TEST_F(GainMapMathTest, ColorConversionLookup) { - EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_UNSPECIFIED), + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_UNSPECIFIED), nullptr); - EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_BT709), + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_BT709), identityConversion); - EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_P3), + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3), p3ToBt709); - EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_BT2100), + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_BT2100), bt2100ToBt709); - EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_UNSPECIFIED), + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_UNSPECIFIED), nullptr); - EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_BT709), + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT709), bt709ToP3); - EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_P3), + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_P3), identityConversion); - EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_BT2100), + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT2100), bt2100ToP3); - EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_UNSPECIFIED), + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_UNSPECIFIED), nullptr); - EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_BT709), + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_BT709), bt709ToBt2100); - EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_P3), + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_P3), p3ToBt2100); - EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_BT2100), + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_BT2100), identityConversion); - EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_UNSPECIFIED), + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_UNSPECIFIED), nullptr); - EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_BT709), + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_BT709), nullptr); - EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_P3), + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_P3), nullptr); - EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_BT2100), + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_BT2100), nullptr); } TEST_F(GainMapMathTest, EncodeGain) { - jpegr_metadata_struct metadata = { .maxContentBoost = 4.0f, + ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f, .minContentBoost = 1.0f / 4.0f }; EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 127); @@ -750,7 +750,7 @@ TEST_F(GainMapMathTest, EncodeGain) { } TEST_F(GainMapMathTest, ApplyGain) { - jpegr_metadata_struct metadata = { .maxContentBoost = 4.0f, + ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f, .minContentBoost = 1.0f / 4.0f }; float displayBoost = metadata.maxContentBoost; @@ -1049,7 +1049,7 @@ TEST_F(GainMapMathTest, GenerateMapLuminancePq) { } TEST_F(GainMapMathTest, ApplyMap) { - jpegr_metadata_struct metadata = { .maxContentBoost = 8.0f, + ultrahdr_metadata_struct metadata = { .maxContentBoost = 8.0f, .minContentBoost = 1.0f / 8.0f }; EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), @@ -1134,4 +1134,4 @@ TEST_F(GainMapMathTest, ApplyMap) { RgbWhite() / 2.0f); } -} // namespace android::jpegrecoverymap +} // namespace android::ultrahdr diff --git a/libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp similarity index 95% rename from libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp rename to libs/ultrahdr/tests/jpegdecoderhelper_test.cpp index 2f32a5685b..c79dbe328b 100644 --- a/libs/jpegrecoverymap/tests/jpegdecoderhelper_test.cpp +++ b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp @@ -14,13 +14,13 @@ * limitations under the License. */ -#include +#include #include #include #include -namespace android::jpegrecoverymap { +namespace android::ultrahdr { #define YUV_IMAGE "/sdcard/Documents/minnie-320x240-yuv.jpg" #define YUV_IMAGE_SIZE 20193 @@ -99,4 +99,4 @@ TEST_F(JpegDecoderHelperTest, decodeGreyImage) { ASSERT_GT(decoder.getDecompressedImageSize(), static_cast(0)); } -} // namespace android::jpegrecoverymap \ No newline at end of file +} // namespace android::ultrahdr \ No newline at end of file diff --git a/libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp similarity index 97% rename from libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp rename to libs/ultrahdr/tests/jpegencoderhelper_test.cpp index 095ac2fbf6..b9a2d84807 100644 --- a/libs/jpegrecoverymap/tests/jpegencoderhelper_test.cpp +++ b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp @@ -14,13 +14,13 @@ * limitations under the License. */ -#include +#include #include #include #include -namespace android::jpegrecoverymap { +namespace android::ultrahdr { #define VALID_IMAGE "/sdcard/Documents/minnie-320x240.yu12" #define VALID_IMAGE_WIDTH 320 @@ -121,5 +121,5 @@ TEST_F(JpegEncoderHelperTest, singleChannelImage) { ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); } -} // namespace android::jpegrecoverymap +} // namespace android::ultrahdr diff --git a/libs/jpegrecoverymap/tests/jpegr_test.cpp b/libs/ultrahdr/tests/jpegr_test.cpp similarity index 86% rename from libs/jpegrecoverymap/tests/jpegr_test.cpp rename to libs/ultrahdr/tests/jpegr_test.cpp index 620f4310df..ba3b4d0418 100644 --- a/libs/jpegrecoverymap/tests/jpegr_test.cpp +++ b/libs/ultrahdr/tests/jpegr_test.cpp @@ -14,9 +14,9 @@ * limitations under the License. */ -#include -#include -#include +#include +#include +#include #include #include #include @@ -36,7 +36,7 @@ #define SAVE_DECODING_RESULT true #define SAVE_INPUT_RGBA true -namespace android::jpegrecoverymap { +namespace android::ultrahdr { struct Timer { struct timeval StartingTime; @@ -118,16 +118,16 @@ void JpegRTest::TearDown() { class JpegRBenchmark : public JpegR { public: void BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image, - jr_metadata_ptr metadata, jr_uncompressed_ptr map); + ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr map); void BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map, - jr_metadata_ptr metadata, jr_uncompressed_ptr dest); + ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest); private: const int kProfileCount = 10; }; void JpegRBenchmark::BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image, - jr_metadata_ptr metadata, + ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr map) { ASSERT_EQ(yuv420Image->width, p010Image->width); ASSERT_EQ(yuv420Image->height, p010Image->height); @@ -137,7 +137,7 @@ void JpegRBenchmark::BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, timerStart(&genRecMapTime); for (auto i = 0; i < kProfileCount; i++) { ASSERT_EQ(OK, generateGainMap( - yuv420Image, p010Image, jpegr_transfer_function::JPEGR_TF_HLG, metadata, map)); + yuv420Image, p010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, metadata, map)); if (i != kProfileCount - 1) delete[] static_cast(map->data); } timerStop(&genRecMapTime); @@ -150,13 +150,13 @@ void JpegRBenchmark::BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, void JpegRBenchmark::BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map, - jr_metadata_ptr metadata, + ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest) { Timer applyRecMapTime; timerStart(&applyRecMapTime); for (auto i = 0; i < kProfileCount; i++) { - ASSERT_EQ(OK, applyGainMap(yuv420Image, map, metadata, JPEGR_OUTPUT_HDR_HLG, + ASSERT_EQ(OK, applyGainMap(yuv420Image, map, metadata, ULTRAHDR_OUTPUT_HDR_HLG, metadata->maxContentBoost /* displayBoost */, dest)); } timerStop(&applyRecMapTime); @@ -169,17 +169,17 @@ void JpegRBenchmark::BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, TEST_F(JpegRTest, build) { // Force all of the gain map lib to be linked by calling all public functions. JpegR jpegRCodec; - jpegRCodec.encodeJPEGR(nullptr, static_cast(0), nullptr, 0, nullptr); - jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast(0), + jpegRCodec.encodeJPEGR(nullptr, static_cast(0), nullptr, 0, nullptr); + jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast(0), nullptr, 0, nullptr); - jpegRCodec.encodeJPEGR(nullptr, nullptr, nullptr, static_cast(0), + jpegRCodec.encodeJPEGR(nullptr, nullptr, nullptr, static_cast(0), nullptr); - jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast(0), nullptr); + jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast(0), nullptr); jpegRCodec.decodeJPEGR(nullptr, nullptr); } TEST_F(JpegRTest, writeXmpThenRead) { - jpegr_metadata_struct metadata_expected; + ultrahdr_metadata_struct metadata_expected; metadata_expected.maxContentBoost = 1.25; metadata_expected.minContentBoost = 0.75; const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; @@ -194,7 +194,7 @@ TEST_F(JpegRTest, writeXmpThenRead) { xmpData.insert(xmpData.end(), reinterpret_cast(xmp.c_str()), reinterpret_cast(xmp.c_str()) + xmp.size()); - jpegr_metadata_struct metadata_read; + ultrahdr_metadata_struct metadata_read; EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read)); EXPECT_FLOAT_EQ(metadata_expected.maxContentBoost, metadata_read.maxContentBoost); EXPECT_FLOAT_EQ(metadata_expected.minContentBoost, metadata_read.minContentBoost); @@ -210,7 +210,7 @@ TEST_F(JpegRTest, encodeFromP010ThenDecode) { } mRawP010Image.width = TEST_IMAGE_WIDTH; mRawP010Image.height = TEST_IMAGE_HEIGHT; - mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100; + mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; JpegR jpegRCodec; @@ -218,7 +218,8 @@ TEST_F(JpegRTest, encodeFromP010ThenDecode) { jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); jpegR.data = malloc(jpegR.maxLength); ret = jpegRCodec.encodeJPEGR( - &mRawP010Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY, nullptr); + &mRawP010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY, + nullptr); if (ret != OK) { FAIL() << "Error code is " << ret; } @@ -264,7 +265,7 @@ TEST_F(JpegRTest, encodeFromP010WithStrideThenDecode) { mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; - mRawP010ImageWithStride.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; JpegR jpegRCodec; @@ -272,7 +273,7 @@ TEST_F(JpegRTest, encodeFromP010WithStrideThenDecode) { jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); jpegR.data = malloc(jpegR.maxLength); ret = jpegRCodec.encodeJPEGR( - &mRawP010ImageWithStride, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY, nullptr); if (ret != OK) { FAIL() << "Error code is " << ret; @@ -318,14 +319,14 @@ TEST_F(JpegRTest, encodeFromRawHdrAndSdrThenDecode) { } mRawP010Image.width = TEST_IMAGE_WIDTH; mRawP010Image.height = TEST_IMAGE_HEIGHT; - mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100; + mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) { FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; } mRawYuv420Image.width = TEST_IMAGE_WIDTH; mRawYuv420Image.height = TEST_IMAGE_HEIGHT; - mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709; + mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; JpegR jpegRCodec; @@ -333,7 +334,7 @@ TEST_F(JpegRTest, encodeFromRawHdrAndSdrThenDecode) { jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); jpegR.data = malloc(jpegR.maxLength); ret = jpegRCodec.encodeJPEGR( - &mRawP010Image, &mRawYuv420Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, + &mRawP010Image, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY, nullptr); if (ret != OK) { FAIL() << "Error code is " << ret; @@ -379,19 +380,19 @@ TEST_F(JpegRTest, encodeFromRawHdrAndSdrAndJpegThenDecode) { } mRawP010Image.width = TEST_IMAGE_WIDTH; mRawP010Image.height = TEST_IMAGE_HEIGHT; - mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100; + mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) { FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; } mRawYuv420Image.width = TEST_IMAGE_WIDTH; mRawYuv420Image.height = TEST_IMAGE_HEIGHT; - mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709; + mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) { FAIL() << "Load file " << JPEG_IMAGE << " failed"; } - mJpegImage.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709; + mJpegImage.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; JpegR jpegRCodec; @@ -399,7 +400,8 @@ TEST_F(JpegRTest, encodeFromRawHdrAndSdrAndJpegThenDecode) { jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); jpegR.data = malloc(jpegR.maxLength); ret = jpegRCodec.encodeJPEGR( - &mRawP010Image, &mRawYuv420Image, &mJpegImage, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR); + &mRawP010Image, &mRawYuv420Image, &mJpegImage, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR); if (ret != OK) { FAIL() << "Error code is " << ret; } @@ -444,7 +446,7 @@ TEST_F(JpegRTest, encodeFromJpegThenDecode) { } mRawP010Image.width = TEST_IMAGE_WIDTH; mRawP010Image.height = TEST_IMAGE_HEIGHT; - mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100; + mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; if (SAVE_INPUT_RGBA) { size_t rgbaSize = mRawP010Image.width * mRawP010Image.height * sizeof(uint32_t); @@ -472,7 +474,7 @@ TEST_F(JpegRTest, encodeFromJpegThenDecode) { if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) { FAIL() << "Load file " << JPEG_IMAGE << " failed"; } - mJpegImage.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709; + mJpegImage.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; JpegR jpegRCodec; @@ -480,7 +482,7 @@ TEST_F(JpegRTest, encodeFromJpegThenDecode) { jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); jpegR.data = malloc(jpegR.maxLength); ret = jpegRCodec.encodeJPEGR( - &mRawP010Image, &mJpegImage, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR); + &mRawP010Image, &mJpegImage, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR); if (ret != OK) { FAIL() << "Error code is " << ret; } @@ -525,25 +527,25 @@ TEST_F(JpegRTest, ProfileGainMapFuncs) { } mRawP010Image.width = kWidth; mRawP010Image.height = kHeight; - mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100; + mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) { FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; } mRawYuv420Image.width = kWidth; mRawYuv420Image.height = kHeight; - mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709; + mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; JpegRBenchmark benchmark; - jpegr_metadata_struct metadata = { .version = 1, + ultrahdr_metadata_struct metadata = { .version = 1, .maxContentBoost = 8.0f, .minContentBoost = 1.0f / 8.0f }; jpegr_uncompressed_struct map = { .data = NULL, .width = 0, .height = 0, - .colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED }; + .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED }; benchmark.BenchmarkGenerateGainMap(&mRawYuv420Image, &mRawP010Image, &metadata, &map); @@ -552,9 +554,9 @@ TEST_F(JpegRTest, ProfileGainMapFuncs) { jpegr_uncompressed_struct dest = { .data = bufferDst.get(), .width = 0, .height = 0, - .colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED }; + .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED }; benchmark.BenchmarkApplyGainMap(&mRawYuv420Image, &map, &metadata, &dest); } -} // namespace android::recoverymap +} // namespace android::ultrahdr -- GitLab From 98e8716d4584e5942d187c94b80a64d1937454e9 Mon Sep 17 00:00:00 2001 From: Ian Elliott Date: Fri, 14 Apr 2023 14:05:19 -0600 Subject: [PATCH 1136/1310] RE-SkiaVk: Ref-count semaphores to prevent premature deletion The lower-level parts of SkiaVk occassionally deletes a semaphore still being used by SkiaVkRenderEngine. This fixes that by ref-counting the SkiaVkRenderEngine-created semaphores. Test: Logcat with a HWASAN build Bug: 274419744 Bug: 270287296 Change-Id: I05cecff69b4712daa4d7b52eb84c0027491369f5 --- libs/renderengine/skia/SkiaVkRenderEngine.cpp | 63 ++++++++++++++----- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp index 936e31679f..b99e3853ee 100644 --- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp @@ -52,6 +52,20 @@ struct VulkanFuncs { PFN_vkDestroyInstance vkDestroyInstance = nullptr; }; +// Ref-Count a semaphore +struct DestroySemaphoreInfo { + VkSemaphore mSemaphore; + // We need to make sure we don't delete the VkSemaphore until it is done being used by both Skia + // (including by the GPU) and inside SkiaVkRenderEngine. So we always start with two refs, one + // owned by Skia and one owned by the SkiaVkRenderEngine. The refs are decremented each time + // delete_semaphore* is called with this object. Skia will call destroy_semaphore* once it is + // done with the semaphore and the GPU has finished work on the semaphore. SkiaVkRenderEngine + // calls delete_semaphore* after sending the semaphore to Skia and exporting it if need be. + int mRefs = 2; + + DestroySemaphoreInfo(VkSemaphore semaphore) : mSemaphore(semaphore) {} +}; + struct VulkanInterface { bool initialized = false; VkInstance instance; @@ -588,14 +602,22 @@ bool SkiaVkRenderEngine::useProtectedContextImpl(GrProtected) { return true; } -static void delete_semaphore(void* _semaphore) { - VkSemaphore semaphore = (VkSemaphore)_semaphore; - sVulkanInterface.destroySemaphore(semaphore); +static void delete_semaphore(void* semaphore) { + DestroySemaphoreInfo* info = reinterpret_cast(semaphore); + --info->mRefs; + if (!info->mRefs) { + sVulkanInterface.destroySemaphore(info->mSemaphore); + delete info; + } } -static void delete_semaphore_protected(void* _semaphore) { - VkSemaphore semaphore = (VkSemaphore)_semaphore; - sProtectedContentVulkanInterface.destroySemaphore(semaphore); +static void delete_semaphore_protected(void* semaphore) { + DestroySemaphoreInfo* info = reinterpret_cast(semaphore); + --info->mRefs; + if (!info->mRefs) { + sProtectedContentVulkanInterface.destroySemaphore(info->mSemaphore); + delete info; + } } static VulkanInterface& getVulkanInterface(bool protectedContext) { @@ -624,19 +646,30 @@ void SkiaVkRenderEngine::waitFence(GrDirectContext* grContext, base::borrowed_fd } base::unique_fd SkiaVkRenderEngine::flushAndSubmit(GrDirectContext* grContext) { - VkSemaphore signalSemaphore = getVulkanInterface(isProtected()).createExportableSemaphore(); - GrBackendSemaphore beSignalSemaphore; - beSignalSemaphore.initVulkan(signalSemaphore); + VulkanInterface& vi = getVulkanInterface(isProtected()); + VkSemaphore semaphore = vi.createExportableSemaphore(); + + GrBackendSemaphore backendSemaphore; + backendSemaphore.initVulkan(semaphore); + GrFlushInfo flushInfo; - flushInfo.fNumSemaphores = 1; - flushInfo.fSignalSemaphores = &beSignalSemaphore; - flushInfo.fFinishedProc = isProtected() ? delete_semaphore_protected : delete_semaphore; - flushInfo.fFinishedContext = (void*)signalSemaphore; + DestroySemaphoreInfo* destroySemaphoreInfo = nullptr; + if (semaphore != VK_NULL_HANDLE) { + destroySemaphoreInfo = new DestroySemaphoreInfo(semaphore); + flushInfo.fNumSemaphores = 1; + flushInfo.fSignalSemaphores = &backendSemaphore; + flushInfo.fFinishedProc = isProtected() ? delete_semaphore_protected : delete_semaphore; + flushInfo.fFinishedContext = destroySemaphoreInfo; + } GrSemaphoresSubmitted submitted = grContext->flush(flushInfo); grContext->submit(false /* no cpu sync */); int drawFenceFd = -1; - if (GrSemaphoresSubmitted::kYes == submitted) { - drawFenceFd = getVulkanInterface(isProtected()).exportSemaphoreSyncFd(signalSemaphore); + if (semaphore != VK_NULL_HANDLE) { + if (GrSemaphoresSubmitted::kYes == submitted) { + drawFenceFd = vi.exportSemaphoreSyncFd(semaphore); + } + // Now that drawFenceFd has been created, we can delete our reference to this semaphore + flushInfo.fFinishedProc(destroySemaphoreInfo); } base::unique_fd res(drawFenceFd); return res; -- GitLab From 0fe0126d4a0cab98a8c5cc92496d7abe9f0fb781 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 17 Apr 2023 08:11:37 -0700 Subject: [PATCH 1137/1310] Use std::map for fallback keys Before this CL, custom data structures were used to store fallback keys. Those structures have some known bugs. To fix this, convert to std::map and use std::optional to store the result of fallback key lookup. Bug: 278299254 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: Id37c41694d0b4cfa2ae107b7db13e9389d21c562 --- .../dispatcher/InputDispatcher.cpp | 42 +++++++++---------- .../inputflinger/dispatcher/InputState.cpp | 27 +++++------- services/inputflinger/dispatcher/InputState.h | 8 ++-- 3 files changed, 35 insertions(+), 42 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index c39c408e0d..d6bf5e854f 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -6197,7 +6197,7 @@ bool InputDispatcher::afterKeyEventLockedInterruptable(const sp& con // Get the fallback key state. // Clear it out after dispatching the UP. int32_t originalKeyCode = keyEntry.keyCode; - int32_t fallbackKeyCode = connection->inputState.getFallbackKey(originalKeyCode); + std::optional fallbackKeyCode = connection->inputState.getFallbackKey(originalKeyCode); if (keyEntry.action == AKEY_EVENT_ACTION_UP) { connection->inputState.removeFallbackKey(originalKeyCode); } @@ -6206,7 +6206,7 @@ bool InputDispatcher::afterKeyEventLockedInterruptable(const sp& con // If the application handles the original key for which we previously // generated a fallback or if the window is not a foreground window, // then cancel the associated fallback key, if any. - if (fallbackKeyCode != -1) { + if (fallbackKeyCode) { // Dispatch the unhandled key to the policy with the cancel flag. if (DEBUG_OUTBOUND_EVENT_DETAILS) { ALOGD("Unhandled key event: Asking policy to cancel fallback action. " @@ -6225,12 +6225,12 @@ bool InputDispatcher::afterKeyEventLockedInterruptable(const sp& con mLock.lock(); // Cancel the fallback key. - if (fallbackKeyCode != AKEYCODE_UNKNOWN) { + if (*fallbackKeyCode != AKEYCODE_UNKNOWN) { CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS, "application handled the original non-fallback key " "or is no longer a foreground target, " "canceling previously dispatched fallback key"); - options.keyCode = fallbackKeyCode; + options.keyCode = *fallbackKeyCode; synthesizeCancelationEventsForConnectionLocked(connection, options); } connection->inputState.removeFallbackKey(originalKeyCode); @@ -6240,7 +6240,7 @@ bool InputDispatcher::afterKeyEventLockedInterruptable(const sp& con // that we are in a good state to perform unhandled key event processing // Then ask the policy what to do with it. bool initialDown = keyEntry.action == AKEY_EVENT_ACTION_DOWN && keyEntry.repeatCount == 0; - if (fallbackKeyCode == -1 && !initialDown) { + if (!fallbackKeyCode && !initialDown) { if (DEBUG_OUTBOUND_EVENT_DETAILS) { ALOGD("Unhandled key event: Skipping unhandled key event processing " "since this is not an initial down. " @@ -6275,53 +6275,53 @@ bool InputDispatcher::afterKeyEventLockedInterruptable(const sp& con // The fallback keycode cannot change at any other point in the lifecycle. if (initialDown) { if (fallback) { - fallbackKeyCode = event.getKeyCode(); + *fallbackKeyCode = event.getKeyCode(); } else { - fallbackKeyCode = AKEYCODE_UNKNOWN; + *fallbackKeyCode = AKEYCODE_UNKNOWN; } - connection->inputState.setFallbackKey(originalKeyCode, fallbackKeyCode); + connection->inputState.setFallbackKey(originalKeyCode, *fallbackKeyCode); } - ALOG_ASSERT(fallbackKeyCode != -1); + ALOG_ASSERT(fallbackKeyCode); // Cancel the fallback key if the policy decides not to send it anymore. // We will continue to dispatch the key to the policy but we will no // longer dispatch a fallback key to the application. - if (fallbackKeyCode != AKEYCODE_UNKNOWN && - (!fallback || fallbackKeyCode != event.getKeyCode())) { + if (*fallbackKeyCode != AKEYCODE_UNKNOWN && + (!fallback || *fallbackKeyCode != event.getKeyCode())) { if (DEBUG_OUTBOUND_EVENT_DETAILS) { if (fallback) { ALOGD("Unhandled key event: Policy requested to send key %d" "as a fallback for %d, but on the DOWN it had requested " "to send %d instead. Fallback canceled.", - event.getKeyCode(), originalKeyCode, fallbackKeyCode); + event.getKeyCode(), originalKeyCode, *fallbackKeyCode); } else { ALOGD("Unhandled key event: Policy did not request fallback for %d, " "but on the DOWN it had requested to send %d. " "Fallback canceled.", - originalKeyCode, fallbackKeyCode); + originalKeyCode, *fallbackKeyCode); } } CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS, "canceling fallback, policy no longer desires it"); - options.keyCode = fallbackKeyCode; + options.keyCode = *fallbackKeyCode; synthesizeCancelationEventsForConnectionLocked(connection, options); fallback = false; - fallbackKeyCode = AKEYCODE_UNKNOWN; + *fallbackKeyCode = AKEYCODE_UNKNOWN; if (keyEntry.action != AKEY_EVENT_ACTION_UP) { - connection->inputState.setFallbackKey(originalKeyCode, fallbackKeyCode); + connection->inputState.setFallbackKey(originalKeyCode, *fallbackKeyCode); } } if (DEBUG_OUTBOUND_EVENT_DETAILS) { { std::string msg; - const KeyedVector& fallbackKeys = + const std::map& fallbackKeys = connection->inputState.getFallbackKeys(); - for (size_t i = 0; i < fallbackKeys.size(); i++) { - msg += StringPrintf(", %d->%d", fallbackKeys.keyAt(i), fallbackKeys.valueAt(i)); + for (const auto& [key, value] : fallbackKeys) { + msg += StringPrintf(", %d->%d", key, value); } ALOGD("Unhandled key event: %zu currently tracked fallback keys%s.", fallbackKeys.size(), msg.c_str()); @@ -6335,7 +6335,7 @@ bool InputDispatcher::afterKeyEventLockedInterruptable(const sp& con keyEntry.source = event.getSource(); keyEntry.displayId = event.getDisplayId(); keyEntry.flags = event.getFlags() | AKEY_EVENT_FLAG_FALLBACK; - keyEntry.keyCode = fallbackKeyCode; + keyEntry.keyCode = *fallbackKeyCode; keyEntry.scanCode = event.getScanCode(); keyEntry.metaState = event.getMetaState(); keyEntry.repeatCount = event.getRepeatCount(); @@ -6345,7 +6345,7 @@ bool InputDispatcher::afterKeyEventLockedInterruptable(const sp& con if (DEBUG_OUTBOUND_EVENT_DETAILS) { ALOGD("Unhandled key event: Dispatching fallback key. " "originalKeyCode=%d, fallbackKeyCode=%d, fallbackMetaState=%08x", - originalKeyCode, fallbackKeyCode, keyEntry.metaState); + originalKeyCode, *fallbackKeyCode, keyEntry.metaState); } return true; // restart the event } else { diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp index 94f38131b6..4652c2dd12 100644 --- a/services/inputflinger/dispatcher/InputState.cpp +++ b/services/inputflinger/dispatcher/InputState.cpp @@ -42,13 +42,8 @@ bool InputState::trackKey(const KeyEntry& entry, int32_t action, int32_t flags) switch (action) { case AKEY_EVENT_ACTION_UP: { if (entry.flags & AKEY_EVENT_FLAG_FALLBACK) { - for (size_t i = 0; i < mFallbackKeys.size();) { - if (mFallbackKeys.valueAt(i) == entry.keyCode) { - mFallbackKeys.removeItemsAt(i); - } else { - i += 1; - } - } + std::erase_if(mFallbackKeys, + [&entry](const auto& item) { return item.second == entry.keyCode; }); } ssize_t index = findKeyMemento(entry); if (index >= 0) { @@ -481,22 +476,20 @@ void InputState::mergePointerStateTo(InputState& other) { } } -int32_t InputState::getFallbackKey(int32_t originalKeyCode) { - ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode); - return index >= 0 ? mFallbackKeys.valueAt(index) : -1; +std::optional InputState::getFallbackKey(int32_t originalKeyCode) { + auto it = mFallbackKeys.find(originalKeyCode); + if (it == mFallbackKeys.end()) { + return {}; + } + return it->second; } void InputState::setFallbackKey(int32_t originalKeyCode, int32_t fallbackKeyCode) { - ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode); - if (index >= 0) { - mFallbackKeys.replaceValueAt(index, fallbackKeyCode); - } else { - mFallbackKeys.add(originalKeyCode, fallbackKeyCode); - } + mFallbackKeys.insert_or_assign(originalKeyCode, fallbackKeyCode); } void InputState::removeFallbackKey(int32_t originalKeyCode) { - mFallbackKeys.removeItem(originalKeyCode); + mFallbackKeys.erase(originalKeyCode); } bool InputState::shouldCancelKey(const KeyMemento& memento, const CancelationOptions& options) { diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h index d788e47429..af2a3cbc93 100644 --- a/services/inputflinger/dispatcher/InputState.h +++ b/services/inputflinger/dispatcher/InputState.h @@ -62,9 +62,9 @@ public: void mergePointerStateTo(InputState& other); // Gets the fallback key associated with a keycode. - // Returns -1 if none. + // Returns std::nullopt if none. // Returns AKEYCODE_UNKNOWN if we are only dispatching the unhandled key to the policy. - int32_t getFallbackKey(int32_t originalKeyCode); + std::optional getFallbackKey(int32_t originalKeyCode); // Sets the fallback key for a particular keycode. void setFallbackKey(int32_t originalKeyCode, int32_t fallbackKeyCode); @@ -72,7 +72,7 @@ public: // Removes the fallback key for a particular keycode. void removeFallbackKey(int32_t originalKeyCode); - inline const KeyedVector& getFallbackKeys() const { return mFallbackKeys; } + inline const std::map& getFallbackKeys() const { return mFallbackKeys; } private: struct KeyMemento { @@ -113,7 +113,7 @@ private: std::vector mKeyMementos; std::vector mMotionMementos; - KeyedVector mFallbackKeys; + std::map mFallbackKeys; ssize_t findKeyMemento(const KeyEntry& entry) const; ssize_t findMotionMemento(const MotionEntry& entry, bool hovering) const; -- GitLab From 5af2834b5ec418e7d1c8b5925b488df12c18aab4 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 17 Apr 2023 08:45:31 -0700 Subject: [PATCH 1138/1310] Pass reference to finishKey Since the parameter is assumed to be non-null, pass it by reference. Bug: 274058082 Test: m libinput_tests && $ANDROID_HOST_OUT/nativetest64/libinput_tests/libinput_tests Change-Id: Iedf8970a57e4463e6addc8ee1013feb3ee60c009 --- include/input/KeyCharacterMap.h | 2 +- libs/input/KeyCharacterMap.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h index c67310eaec..9f1c0e28c6 100644 --- a/include/input/KeyCharacterMap.h +++ b/include/input/KeyCharacterMap.h @@ -227,7 +227,7 @@ private: status_t parseMapKey(); status_t parseKey(); status_t parseKeyProperty(); - status_t finishKey(Key* key); + status_t finishKey(Key& key); status_t parseModifier(const std::string& token, int32_t* outMetaState); status_t parseCharacterLiteral(char16_t* outCharacter); }; diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp index 737bd15901..65398d717d 100644 --- a/libs/input/KeyCharacterMap.cpp +++ b/libs/input/KeyCharacterMap.cpp @@ -1048,7 +1048,7 @@ status_t KeyCharacterMap::Parser::parseKeyProperty() { String8 token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER); if (token == "}") { mState = STATE_TOP; - return finishKey(key); + return finishKey(*key); } Vector properties; @@ -1230,12 +1230,12 @@ status_t KeyCharacterMap::Parser::parseKeyProperty() { return NO_ERROR; } -status_t KeyCharacterMap::Parser::finishKey(Key* key) { +status_t KeyCharacterMap::Parser::finishKey(Key& key) { // Fill in default number property. - if (!key->number) { + if (!key.number) { char16_t digit = 0; char16_t symbol = 0; - for (const Behavior& b : key->behaviors) { + for (const Behavior& b : key.behaviors) { char16_t ch = b.character; if (ch) { if (ch >= '0' && ch <= '9') { @@ -1247,7 +1247,7 @@ status_t KeyCharacterMap::Parser::finishKey(Key* key) { } } } - key->number = digit ? digit : symbol; + key.number = digit ? digit : symbol; } return NO_ERROR; } -- GitLab From 0bae1a0fbde55fc78ecfb203bc555009a86c4233 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 17 Apr 2023 09:00:59 -0700 Subject: [PATCH 1139/1310] Return Key* from getKey The call getKey can fail. Rather than returning a separate bool that the caller might ignore, return a pointer to simplify the code. Bug: 278299254 Test: m libinput_tests && $ANDROID_HOST_OUT/nativetest64/libinput_tests/libinput_tests Change-Id: I28c25bee8890bdc90ca7e069c803423a7420e6b4 --- include/input/KeyCharacterMap.h | 2 +- libs/input/KeyCharacterMap.cpp | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h index 9f1c0e28c6..9423041b68 100644 --- a/include/input/KeyCharacterMap.h +++ b/include/input/KeyCharacterMap.h @@ -243,7 +243,7 @@ private: KeyCharacterMap(const std::string& filename); - bool getKey(int32_t keyCode, const Key** outKey) const; + const Key* getKey(int32_t keyCode) const; const Behavior* getKeyBehavior(int32_t keyCode, int32_t metaState) const; static bool matchesMetaState(int32_t eventMetaState, int32_t behaviorMetaState); diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp index 65398d717d..136a560aea 100644 --- a/libs/input/KeyCharacterMap.cpp +++ b/libs/input/KeyCharacterMap.cpp @@ -272,8 +272,8 @@ const std::string KeyCharacterMap::getLoadFileName() const { char16_t KeyCharacterMap::getDisplayLabel(int32_t keyCode) const { char16_t result = 0; - const Key* key; - if (getKey(keyCode, &key)) { + const Key* key = getKey(keyCode); + if (key != nullptr) { result = key->label; } #if DEBUG_MAPPING @@ -284,8 +284,8 @@ char16_t KeyCharacterMap::getDisplayLabel(int32_t keyCode) const { char16_t KeyCharacterMap::getNumber(int32_t keyCode) const { char16_t result = 0; - const Key* key; - if (getKey(keyCode, &key)) { + const Key* key = getKey(keyCode); + if (key != nullptr) { result = key->number; } #if DEBUG_MAPPING @@ -332,8 +332,8 @@ bool KeyCharacterMap::getFallbackAction(int32_t keyCode, int32_t metaState, char16_t KeyCharacterMap::getMatch(int32_t keyCode, const char16_t* chars, size_t numChars, int32_t metaState) const { char16_t result = 0; - const Key* key; - if (getKey(keyCode, &key)) { + const Key* key = getKey(keyCode); + if (key != nullptr) { // Try to find the most general behavior that maps to this character. // For example, the base key behavior will usually be last in the list. // However, if we find a perfect meta state match for one behavior then use that one. @@ -493,19 +493,18 @@ std::pair KeyCharacterMap::applyKeyBehavior(int32_t fromKeyCod return std::make_pair(toKeyCode, toMetaState); } -bool KeyCharacterMap::getKey(int32_t keyCode, const Key** outKey) const { +const KeyCharacterMap::Key* KeyCharacterMap::getKey(int32_t keyCode) const { ssize_t index = mKeys.indexOfKey(keyCode); if (index >= 0) { - *outKey = mKeys.valueAt(index); - return true; + return mKeys.valueAt(index); } - return false; + return nullptr; } const KeyCharacterMap::Behavior* KeyCharacterMap::getKeyBehavior(int32_t keyCode, int32_t metaState) const { - const Key* key; - if (getKey(keyCode, &key)) { + const Key* key = getKey(keyCode); + if (key != nullptr) { for (const Behavior& behavior : key->behaviors) { if (matchesMetaState(metaState, behavior.metaState)) { return &behavior; -- GitLab From 92e6c6bc6b5f8e68b5c27cfc1c0cab7bac2ad4ed Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Fri, 14 Apr 2023 20:20:14 +0000 Subject: [PATCH 1140/1310] JPEG/R: add encode API-4 Test: jpegr_test.cpp Bug: b/264715926 Change-Id: I33f57400e35eebb258faf8753d2ce3a8b51e3dd3 --- libs/ultrahdr/include/ultrahdr/jpegr.h | 39 +++++++++++++++++++++++--- libs/ultrahdr/jpegr.cpp | 10 +++++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/libs/ultrahdr/include/ultrahdr/jpegr.h b/libs/ultrahdr/include/ultrahdr/jpegr.h index b755b19b26..88038f11a4 100644 --- a/libs/ultrahdr/include/ultrahdr/jpegr.h +++ b/libs/ultrahdr/include/ultrahdr/jpegr.h @@ -103,7 +103,10 @@ public: * JPEG. * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image + * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} + * represents the maximum available size of the desitination buffer, and it must be + * set before calling this method. If the encoded JPEGR size exceeds + * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is * the highest quality * @param exif pointer to the exif metadata. @@ -125,7 +128,10 @@ public: * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image + * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} + * represents the maximum available size of the desitination buffer, and it must be + * set before calling this method. If the encoded JPEGR size exceeds + * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is * the highest quality * @param exif pointer to the exif metadata. @@ -152,7 +158,10 @@ public: * input * @param compressed_jpeg_image compressed 8-bit JPEG image * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image + * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} + * represents the maximum available size of the desitination buffer, and it must be + * set before calling this method. If the encoded JPEGR size exceeds + * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. * @return NO_ERROR if encoding succeeds, error code if error occurs. */ status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, @@ -173,7 +182,10 @@ public: * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param compressed_jpeg_image compressed 8-bit JPEG image * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image + * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} + * represents the maximum available size of the desitination buffer, and it must be + * set before calling this method. If the encoded JPEGR size exceeds + * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. * @return NO_ERROR if encoding succeeds, error code if error occurs. */ status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, @@ -181,6 +193,25 @@ public: ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest); + /* + * Encode API-4 + * Assemble JPEGR image from SDR JPEG and gainmap JPEG. + * + * Assemble the primary JPEG image, the gain map and the metadata to JPEG/R format. + * @param compressed_jpeg_image compressed 8-bit JPEG image + * @param compressed_gainmap compressed 8-bit JPEG single channel image + * @param metadata metadata to be written in XMP of the primary jpeg + * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} + * represents the maximum available size of the desitination buffer, and it must be + * set before calling this method. If the encoded JPEGR size exceeds + * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. + * @return NO_ERROR if encoding succeeds, error code if error occurs. + */ + status_t encodeJPEGR(jr_compressed_ptr compressed_jpeg_image, + jr_compressed_ptr compressed_gainmap, + ultrahdr_metadata_ptr metadata, + jr_compressed_ptr dest); + /* * Decode API * Decompress JPEGR image. diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp index 8e1dc8c529..14e6cba667 100644 --- a/libs/ultrahdr/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -338,6 +338,16 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return NO_ERROR; } +/* Encode API-4 */ +status_t JpegR::encodeJPEGR(jr_compressed_ptr compressed_jpeg_image, + jr_compressed_ptr compressed_gainmap, + ultrahdr_metadata_ptr metadata, + jr_compressed_ptr dest) { + JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr, + metadata, dest)); + return NO_ERROR; +} + status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info) { if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; -- GitLab From 56a7d594d98466b21d5ec499b817d55874bb8063 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Fri, 14 Apr 2023 16:57:34 +0000 Subject: [PATCH 1141/1310] JPEG/R: lift the checking criteria for width 8-alignment JPEG/R library uses jpeg-turbo for JPEG encoding, which runs DCT transform on block size of 16x16 for luma, and 8x8 for chroma. The resolution in the bug report is not 16-aligned and it results in null pointer dereference for the last line in jpeg-turbo. The original checking for 8-alignment width was wrong (should check 16-alignment). jpeg-turbo has some edge case handling for this case, and it requires some extra room at the end of input. This change removed the checking criteria by adding a padding zero method. A reason size of the padding zeros is a CB block, which is 8x8, 64 bytes. Bug: 277982036 Test: CTS: ImageReaderTest#testJpegR, uint test: jpegrencoderhelper_test.cpp Change-Id: I1313a002db6d4bc63b32dc3dd3d6ccdf06779149 --- .../include/ultrahdr/jpegencoderhelper.h | 8 ++- libs/ultrahdr/jpegencoderhelper.cpp | 5 -- libs/ultrahdr/jpegr.cpp | 25 ++++---- .../ultrahdr/tests/jpegencoderhelper_test.cpp | 58 +++++++++++-------- 4 files changed, 52 insertions(+), 44 deletions(-) diff --git a/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h index ac0215559d..2c6778e299 100644 --- a/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h +++ b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h @@ -61,6 +61,11 @@ public: */ size_t getCompressedImageSize(); + /* + * Process 16 lines of Y and 16 lines of U/V each time. + * We must pass at least 16 scanlines according to libjpeg documentation. + */ + static const int kCompressBatchSize = 16; private: // initDestination(), emptyOutputBuffer() and emptyOutputBuffer() are callback functions to be // passed into jpeg library. @@ -82,9 +87,6 @@ private: // The block size for encoded jpeg image buffer. static const int kBlockSize = 16384; - // Process 16 lines of Y and 16 lines of U/V each time. - // We must pass at least 16 scanlines according to libjpeg documentation. - static const int kCompressBatchSize = 16; // The buffer that holds the compressed result. std::vector mResultBuffer; diff --git a/libs/ultrahdr/jpegencoderhelper.cpp b/libs/ultrahdr/jpegencoderhelper.cpp index fc6e4d1e64..10a763035f 100644 --- a/libs/ultrahdr/jpegencoderhelper.cpp +++ b/libs/ultrahdr/jpegencoderhelper.cpp @@ -38,11 +38,6 @@ JpegEncoderHelper::~JpegEncoderHelper() { bool JpegEncoderHelper::compressImage(const void* image, int width, int height, int quality, const void* iccBuffer, unsigned int iccSize, bool isSingleChannel) { - if (width % 8 != 0 || height % 2 != 0) { - ALOGE("Image size can not be handled: %dx%d", width, height); - return false; - } - mResultBuffer.clear(); if (!encode(image, width, height, quality, iccBuffer, iccSize, isSingleChannel)) { return false; diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp index 8e1dc8c529..24d1911853 100644 --- a/libs/ultrahdr/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -66,9 +66,12 @@ static const uint32_t kJpegrVersion = 1; // Map is quarter res / sixteenth size static const size_t kMapDimensionScaleFactor = 4; // JPEG block size. -// JPEG encoding / decoding will require 8 x 8 DCT transform. -// Width must be 8 dividable, and height must be 2 dividable. -static const size_t kJpegBlock = 8; +// JPEG encoding / decoding will require block based DCT transform 16 x 16 for luma, +// and 8 x 8 for chroma. +// Width must be 16 dividable for luma, and 8 dividable for chroma. +// If this criteria is not ficilitated, we will pad zeros based on the required block size. +static const size_t kJpegBlock = JpegEncoderHelper::kCompressBatchSize; +static const size_t kJpegBlockSquare = kJpegBlock * kJpegBlock; // JPEG compress quality (0 ~ 100) for gain map static const int kMapCompressQuality = 85; @@ -92,13 +95,6 @@ status_t JpegR::areInputImagesValid(jr_uncompressed_ptr uncompressed_p010_image, return ERROR_JPEGR_INVALID_NULL_PTR; } - if (uncompressed_p010_image->width % kJpegBlock != 0 - || uncompressed_p010_image->height % 2 != 0) { - ALOGE("Image size can not be handled: %dx%d.", - uncompressed_p010_image->width, uncompressed_p010_image->height); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - if (uncompressed_p010_image->luma_stride != 0 && uncompressed_p010_image->luma_stride < uncompressed_p010_image->width) { ALOGE("Image stride can not be smaller than width, stride=%d, width=%d", @@ -157,8 +153,13 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, metadata.version = kJpegrVersion; jpegr_uncompressed_struct uncompressed_yuv_420_image; - unique_ptr uncompressed_yuv_420_image_data = make_unique( - uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2); + size_t gain_map_length = uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2; + // Pad a pseudo chroma block (kJpegBlock / 2) x (kJpegBlock / 2) + // if width is not kJpegBlock aligned. + if (uncompressed_p010_image->width % kJpegBlock != 0) { + gain_map_length += kJpegBlockSquare / 4; + } + unique_ptr uncompressed_yuv_420_image_data = make_unique(gain_map_length); uncompressed_yuv_420_image.data = uncompressed_yuv_420_image_data.get(); JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image)); diff --git a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp index b9a2d84807..8f18ac0004 100644 --- a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp +++ b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp @@ -22,15 +22,15 @@ namespace android::ultrahdr { -#define VALID_IMAGE "/sdcard/Documents/minnie-320x240.yu12" -#define VALID_IMAGE_WIDTH 320 -#define VALID_IMAGE_HEIGHT 240 +#define ALIGNED_IMAGE "/sdcard/Documents/minnie-320x240.yu12" +#define ALIGNED_IMAGE_WIDTH 320 +#define ALIGNED_IMAGE_HEIGHT 240 #define SINGLE_CHANNEL_IMAGE "/sdcard/Documents/minnie-320x240.y" -#define SINGLE_CHANNEL_IMAGE_WIDTH VALID_IMAGE_WIDTH -#define SINGLE_CHANNEL_IMAGE_HEIGHT VALID_IMAGE_HEIGHT -#define INVALID_SIZE_IMAGE "/sdcard/Documents/minnie-318x240.yu12" -#define INVALID_SIZE_IMAGE_WIDTH 318 -#define INVALID_SIZE_IMAGE_HEIGHT 240 +#define SINGLE_CHANNEL_IMAGE_WIDTH ALIGNED_IMAGE_WIDTH +#define SINGLE_CHANNEL_IMAGE_HEIGHT ALIGNED_IMAGE_HEIGHT +#define UNALIGNED_IMAGE "/sdcard/Documents/minnie-318x240.yu12" +#define UNALIGNED_IMAGE_WIDTH 318 +#define UNALIGNED_IMAGE_HEIGHT 240 #define JPEG_QUALITY 90 class JpegEncoderHelperTest : public testing::Test { @@ -46,7 +46,7 @@ protected: virtual void SetUp(); virtual void TearDown(); - Image mValidImage, mInvalidSizeImage, mSingleChannelImage; + Image mAlignedImage, mUnalignedImage, mSingleChannelImage; }; JpegEncoderHelperTest::JpegEncoderHelperTest() {} @@ -82,16 +82,16 @@ static bool loadFile(const char filename[], JpegEncoderHelperTest::Image* result } void JpegEncoderHelperTest::SetUp() { - if (!loadFile(VALID_IMAGE, &mValidImage)) { - FAIL() << "Load file " << VALID_IMAGE << " failed"; + if (!loadFile(ALIGNED_IMAGE, &mAlignedImage)) { + FAIL() << "Load file " << ALIGNED_IMAGE << " failed"; } - mValidImage.width = VALID_IMAGE_WIDTH; - mValidImage.height = VALID_IMAGE_HEIGHT; - if (!loadFile(INVALID_SIZE_IMAGE, &mInvalidSizeImage)) { - FAIL() << "Load file " << INVALID_SIZE_IMAGE << " failed"; + mAlignedImage.width = ALIGNED_IMAGE_WIDTH; + mAlignedImage.height = ALIGNED_IMAGE_HEIGHT; + if (!loadFile(UNALIGNED_IMAGE, &mUnalignedImage)) { + FAIL() << "Load file " << UNALIGNED_IMAGE << " failed"; } - mInvalidSizeImage.width = INVALID_SIZE_IMAGE_WIDTH; - mInvalidSizeImage.height = INVALID_SIZE_IMAGE_HEIGHT; + mUnalignedImage.width = UNALIGNED_IMAGE_WIDTH; + mUnalignedImage.height = UNALIGNED_IMAGE_HEIGHT; if (!loadFile(SINGLE_CHANNEL_IMAGE, &mSingleChannelImage)) { FAIL() << "Load file " << SINGLE_CHANNEL_IMAGE << " failed"; } @@ -101,20 +101,30 @@ void JpegEncoderHelperTest::SetUp() { void JpegEncoderHelperTest::TearDown() {} -TEST_F(JpegEncoderHelperTest, validImage) { +TEST_F(JpegEncoderHelperTest, encodeAlignedImage) { JpegEncoderHelper encoder; - EXPECT_TRUE(encoder.compressImage(mValidImage.buffer.get(), mValidImage.width, - mValidImage.height, JPEG_QUALITY, NULL, 0)); + EXPECT_TRUE(encoder.compressImage(mAlignedImage.buffer.get(), mAlignedImage.width, + mAlignedImage.height, JPEG_QUALITY, NULL, 0)); ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); } -TEST_F(JpegEncoderHelperTest, invalidSizeImage) { +// The width of the "unaligned" image is not 16-aligned, and will fail if encoded directly. +// Should pass with the padding zero method. +TEST_F(JpegEncoderHelperTest, encodeUnalignedImage) { JpegEncoderHelper encoder; - EXPECT_FALSE(encoder.compressImage(mInvalidSizeImage.buffer.get(), mInvalidSizeImage.width, - mInvalidSizeImage.height, JPEG_QUALITY, NULL, 0)); + const size_t paddingZeroLength = JpegEncoderHelper::kCompressBatchSize + * JpegEncoderHelper::kCompressBatchSize / 4; + std::unique_ptr imageWithPaddingZeros( + new uint8_t[UNALIGNED_IMAGE_WIDTH * UNALIGNED_IMAGE_HEIGHT * 3 / 2 + + paddingZeroLength]); + memcpy(imageWithPaddingZeros.get(), mUnalignedImage.buffer.get(), + UNALIGNED_IMAGE_WIDTH * UNALIGNED_IMAGE_HEIGHT * 3 / 2); + EXPECT_TRUE(encoder.compressImage(imageWithPaddingZeros.get(), mUnalignedImage.width, + mUnalignedImage.height, JPEG_QUALITY, NULL, 0)); + ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); } -TEST_F(JpegEncoderHelperTest, singleChannelImage) { +TEST_F(JpegEncoderHelperTest, encodeSingleChannelImage) { JpegEncoderHelper encoder; EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), mSingleChannelImage.width, mSingleChannelImage.height, JPEG_QUALITY, NULL, 0, true)); -- GitLab From 150065b4f7ab2c674f3989e6810281920020c3d8 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Mon, 17 Apr 2023 19:14:11 -0700 Subject: [PATCH 1142/1310] [sf] Use layer id as snapshot ids Flicker tests rely on layer ids for some assertions. So when possible use the layer id for the snapshot used to generate layer traces. Also keep track of the order in which layers are added so its easier to write tests. Test: atest FlickerTests w/new fe Bug: 238781169 Change-Id: Ie0f93ca956e6d043c9d95d00bc205d242e47c4cc --- .../FrontEnd/LayerCreationArgs.cpp | 11 +++++----- .../FrontEnd/LayerCreationArgs.h | 1 + .../FrontEnd/LayerLifecycleManager.cpp | 22 ++++++++----------- .../FrontEnd/LayerLifecycleManager.h | 3 +++ .../surfaceflinger/FrontEnd/LayerSnapshot.cpp | 8 +++++-- services/surfaceflinger/LayerProtoHelper.cpp | 6 ++++- .../tests/unittests/LayerHierarchyTest.cpp | 3 ++- .../unittests/LayerLifecycleManagerTest.cpp | 15 ++++++++----- 8 files changed, 40 insertions(+), 29 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp index 6af352c9f4..cfa2b031e9 100644 --- a/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp +++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp @@ -23,6 +23,7 @@ namespace android::surfaceflinger { std::atomic LayerCreationArgs::sSequence{1}; +std::atomic LayerCreationArgs::sInternalSequence{1}; uint32_t LayerCreationArgs::getInternalLayerId(uint32_t id) { return id | INTERNAL_LAYER_PREFIX; @@ -48,13 +49,11 @@ LayerCreationArgs::LayerCreationArgs(SurfaceFlinger* flinger, sp client, metadata.getInt32(gui::METADATA_OWNER_UID, static_cast(ownerUid))); } - if (id) { + if (internalLayer) { + sequence = getInternalLayerId(sInternalSequence++); + } else if (id) { sequence = *id; - if (internalLayer) { - sequence = getInternalLayerId(*id); - } else { - sSequence = *id + 1; - } + sSequence = *id + 1; } else { sequence = sSequence++; if (sequence >= INTERNAL_LAYER_PREFIX) { diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h index 3a0fc6d7a3..c26edb5d22 100644 --- a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h +++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h @@ -36,6 +36,7 @@ namespace android::surfaceflinger { struct LayerCreationArgs { static std::atomic sSequence; + static std::atomic sInternalSequence; static uint32_t getInternalLayerId(uint32_t id); static LayerCreationArgs fromOtherArgs(const LayerCreationArgs& other); diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp index 33d9dbe796..6cacfb5946 100644 --- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp +++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp @@ -41,7 +41,7 @@ void LayerLifecycleManager::addLayers(std::vectorsecond.owner.getDebugString().c_str()); } - + mAddedLayers.push_back(newLayer.get()); layer.parentId = linkLayer(layer.parentId, layer.id); layer.relativeParentId = linkLayer(layer.relativeParentId, layer.id); if (layer.layerStackToMirror != ui::INVALID_LAYER_STACK) { @@ -258,23 +258,19 @@ void LayerLifecycleManager::applyTransactions(const std::vectorchanges.test(RequestedLayerState::Changes::Created)) { - for (auto listener : mListeners) { - listener->onLayerAdded(*layer); - } + for (auto layer : mAddedLayers) { + for (auto& listener : mListeners) { + listener->onLayerAdded(*layer); } + } + mAddedLayers.clear(); + + for (auto& layer : mLayers) { layer->clearChanges(); } for (auto& destroyedLayer : mDestroyedLayers) { - if (destroyedLayer->changes.test(RequestedLayerState::Changes::Created)) { - for (auto listener : mListeners) { - listener->onLayerAdded(*destroyedLayer); - } - } - - for (auto listener : mListeners) { + for (auto& listener : mListeners) { listener->onLayerDestroyed(*destroyedLayer); } } diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h index f258678598..f0d2c22e5a 100644 --- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h +++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h @@ -108,6 +108,9 @@ private: std::vector> mLayers; // Layers pending destruction. Layers will be destroyed once changes are committed. std::vector> mDestroyedLayers; + // Keeps track of all the layers that were added in order. Changes will be cleared once + // committed. + std::vector mAddedLayers; }; } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp index 1e931a7a1a..a9925843df 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp @@ -27,7 +27,6 @@ using namespace ftl::flag_operators; LayerSnapshot::LayerSnapshot(const RequestedLayerState& state, const LayerHierarchy::TraversalPath& path) : path(path) { - static uint32_t sUniqueSequenceId = 0; // Provide a unique id for all snapshots. // A front end layer can generate multiple snapshots if its mirrored. // Additionally, if the layer is not reachable, we may choose to destroy @@ -35,7 +34,12 @@ LayerSnapshot::LayerSnapshot(const RequestedLayerState& state, // change. The consumer shouldn't tie any lifetimes to this unique id but // register a LayerLifecycleManager::ILifecycleListener or get a list of // destroyed layers from LayerLifecycleManager. - uniqueSequence = sUniqueSequenceId++; + if (path.isClone()) { + uniqueSequence = + LayerCreationArgs::getInternalLayerId(LayerCreationArgs::sInternalSequence++); + } else { + uniqueSequence = state.id; + } sequence = static_cast(state.id); name = state.name; textureName = state.textureName; diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp index 5d924852a4..3472d201f3 100644 --- a/services/surfaceflinger/LayerProtoHelper.cpp +++ b/services/surfaceflinger/LayerProtoHelper.cpp @@ -392,7 +392,11 @@ void LayerProtoHelper::writeSnapshotToProto(LayerProto* layerInfo, layerInfo->set_id(snapshot.uniqueSequence); layerInfo->set_original_id(snapshot.sequence); - layerInfo->set_name(requestedState.name); + if (!snapshot.path.isClone()) { + layerInfo->set_name(requestedState.name); + } else { + layerInfo->set_name(requestedState.name + "(Mirror)"); + } layerInfo->set_type("Layer"); LayerProtoHelper::writeToProto(requestedState.transparentRegion, diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp index ddf3363244..0d6a486ad0 100644 --- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp @@ -466,7 +466,8 @@ TEST_F(LayerHierarchyTest, backgroundLayersAreBehindParentLayer) { updateBackgroundColor(1, 0.5); UPDATE_AND_VERIFY(hierarchyBuilder); - auto bgLayerId = LayerCreationArgs::getInternalLayerId(1); + auto hierarchy = hierarchyBuilder.getPartialHierarchy(1, /*childrenOnly=*/true); + auto bgLayerId = hierarchy.mChildren.front().first->getLayer()->id; std::vector expectedTraversalPath = {1, bgLayerId, 11, 111, 12, 121, 122, 1221, 13, 2}; EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp index 14b8e4bbac..97ef5a268d 100644 --- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp @@ -36,13 +36,13 @@ namespace android::surfaceflinger::frontend { class ExpectLayerLifecycleListener : public LayerLifecycleManager::ILifecycleListener { public: void onLayerAdded(const RequestedLayerState& layer) override { - mActualLayersAdded.emplace(layer.id); + mActualLayersAdded.push_back(layer.id); }; void onLayerDestroyed(const RequestedLayerState& layer) override { mActualLayersDestroyed.emplace(layer.id); }; - void expectLayersAdded(const std::unordered_set& expectedLayersAdded) { + void expectLayersAdded(const std::vector& expectedLayersAdded) { EXPECT_EQ(expectedLayersAdded, mActualLayersAdded); mActualLayersAdded.clear(); } @@ -51,7 +51,7 @@ public: mActualLayersDestroyed.clear(); } - std::unordered_set mActualLayersAdded; + std::vector mActualLayersAdded; std::unordered_set mActualLayersDestroyed; }; @@ -318,7 +318,8 @@ TEST_F(LayerLifecycleManagerTest, canAddBackgroundLayer) { EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)); lifecycleManager.commitChanges(); - auto bgLayerId = LayerCreationArgs::getInternalLayerId(1); + ASSERT_EQ(listener->mActualLayersAdded.size(), 2u); + auto bgLayerId = listener->mActualLayersAdded[1]; listener->expectLayersAdded({1, bgLayerId}); listener->expectLayersDestroyed({}); EXPECT_EQ(getRequestedLayerState(lifecycleManager, bgLayerId)->color.a, 0.5_hf); @@ -352,7 +353,8 @@ TEST_F(LayerLifecycleManagerTest, canDestroyBackgroundLayer) { EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)); lifecycleManager.commitChanges(); - auto bgLayerId = LayerCreationArgs::getInternalLayerId(1); + ASSERT_EQ(listener->mActualLayersAdded.size(), 2u); + auto bgLayerId = listener->mActualLayersAdded[1]; listener->expectLayersAdded({1, bgLayerId}); listener->expectLayersDestroyed({bgLayerId}); } @@ -381,7 +383,8 @@ TEST_F(LayerLifecycleManagerTest, onParentDestroyDestroysBackgroundLayer) { EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)); lifecycleManager.commitChanges(); - auto bgLayerId = LayerCreationArgs::getInternalLayerId(1); + ASSERT_EQ(listener->mActualLayersAdded.size(), 2u); + auto bgLayerId = listener->mActualLayersAdded[1]; listener->expectLayersAdded({1, bgLayerId}); listener->expectLayersDestroyed({1, bgLayerId}); } -- GitLab From edcd042535eb2f56100f69593b502770b9bee92d Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 17 Apr 2023 16:11:22 -0700 Subject: [PATCH 1143/1310] Convert Vector usage to std::vector Prefer std::vector implementation over the custom Vector data type. Bug: 278299254 Test: atest android.view.cts.KeyCharacterMapTest Change-Id: If5b4e270a5c89c56619ae0c576495024438bd651 --- libs/input/KeyCharacterMap.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp index 136a560aea..f703901ed9 100644 --- a/libs/input/KeyCharacterMap.cpp +++ b/libs/input/KeyCharacterMap.cpp @@ -1050,14 +1050,14 @@ status_t KeyCharacterMap::Parser::parseKeyProperty() { return finishKey(*key); } - Vector properties; + std::vector properties; // Parse all comma-delimited property names up to the first colon. for (;;) { if (token == "label") { - properties.add(Property(PROPERTY_LABEL)); + properties.emplace_back(PROPERTY_LABEL); } else if (token == "number") { - properties.add(Property(PROPERTY_NUMBER)); + properties.emplace_back(PROPERTY_NUMBER); } else { int32_t metaState; status_t status = parseModifier(token.string(), &metaState); @@ -1066,7 +1066,7 @@ status_t KeyCharacterMap::Parser::parseKeyProperty() { mTokenizer->getLocation().string(), token.string()); return status; } - properties.add(Property(PROPERTY_META, metaState)); + properties.emplace_back(PROPERTY_META, metaState); } mTokenizer->skipDelimiters(WHITESPACE); @@ -1181,8 +1181,7 @@ status_t KeyCharacterMap::Parser::parseKeyProperty() { } while (!mTokenizer->isEol() && mTokenizer->peekChar() != '#'); // Add the behavior. - for (size_t i = 0; i < properties.size(); i++) { - const Property& property = properties.itemAt(i); + for (const Property& property : properties) { switch (property.property) { case PROPERTY_LABEL: if (key->label) { -- GitLab From 6bd756321e62cdd17ec2fac2ae103120e0d06e55 Mon Sep 17 00:00:00 2001 From: Vinh Tran Date: Fri, 14 Apr 2023 18:55:25 -0400 Subject: [PATCH 1144/1310] Create filegroup for aidl implicit deps The new filegroup doesn't result to any change to Soong build. However, it explicates the aidl implicit deps so that they are compatible with Bazel conversion. Bug: 278059962 Test: WIP Change-Id: Icc97b412c9924463328e65e840a4f04da15d73ef --- libs/gui/Android.bp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index 21900a073a..0a63c1564e 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -66,6 +66,18 @@ filegroup { ], } +filegroup { + name: "android_gui_aidl", + srcs: [ + "android/gui/DisplayInfo.aidl", + "android/gui/FocusRequest.aidl", + "android/gui/InputApplicationInfo.aidl", + "android/gui/IWindowInfosListener.aidl", + "android/gui/IWindowInfosReportedListener.aidl", + "android/gui/WindowInfo.aidl", + ], +} + cc_library_static { name: "libgui_window_info_static", vendor_available: true, @@ -118,6 +130,9 @@ filegroup { name: "libgui_aidl", srcs: ["aidl/**/*.aidl"], path: "aidl/", + aidl: { + deps: [":android_gui_aidl"], + }, } filegroup { -- GitLab From e9fe2dfd709703308cd8d8d6d9926412812a5ca1 Mon Sep 17 00:00:00 2001 From: Biswarup Pal Date: Wed, 5 Apr 2023 18:20:54 +0000 Subject: [PATCH 1145/1310] Pass virtual input event timestamps to uinput Test: Verify that virtual input timestamps are propagated to EventHub correctly, provided that uinput supports event timestamps Bug: 271946580 Change-Id: Ifb79654000e1040ccfb85d35dee856a24db5eb73 --- include/input/VirtualInputDevice.h | 27 +++++---- libs/input/VirtualInputDevice.cpp | 88 ++++++++++++++++++------------ 2 files changed, 70 insertions(+), 45 deletions(-) diff --git a/include/input/VirtualInputDevice.h b/include/input/VirtualInputDevice.h index 13ffb581b4..21a28770b6 100644 --- a/include/input/VirtualInputDevice.h +++ b/include/input/VirtualInputDevice.h @@ -34,10 +34,12 @@ public: protected: const android::base::unique_fd mFd; - bool writeInputEvent(uint16_t type, uint16_t code, int32_t value); + bool writeInputEvent(uint16_t type, uint16_t code, int32_t value, + std::chrono::nanoseconds eventTime); bool writeEvKeyEvent(int32_t androidCode, int32_t androidAction, const std::map& evKeyCodeMapping, - const std::map& actionMapping); + const std::map& actionMapping, + std::chrono::nanoseconds eventTime); }; class VirtualKeyboard : public VirtualInputDevice { @@ -47,7 +49,8 @@ public: static const std::map KEY_ACTION_MAPPING; VirtualKeyboard(android::base::unique_fd fd); virtual ~VirtualKeyboard() override; - bool writeKeyEvent(int32_t androidKeyCode, int32_t androidAction); + bool writeKeyEvent(int32_t androidKeyCode, int32_t androidAction, + std::chrono::nanoseconds eventTime); }; class VirtualDpad : public VirtualInputDevice { @@ -55,17 +58,20 @@ public: static const std::map DPAD_KEY_CODE_MAPPING; VirtualDpad(android::base::unique_fd fd); virtual ~VirtualDpad() override; - bool writeDpadKeyEvent(int32_t androidKeyCode, int32_t androidAction); + bool writeDpadKeyEvent(int32_t androidKeyCode, int32_t androidAction, + std::chrono::nanoseconds eventTime); }; class VirtualMouse : public VirtualInputDevice { public: VirtualMouse(android::base::unique_fd fd); virtual ~VirtualMouse() override; - bool writeButtonEvent(int32_t androidButtonCode, int32_t androidAction); + bool writeButtonEvent(int32_t androidButtonCode, int32_t androidAction, + std::chrono::nanoseconds eventTime); // TODO(b/259554911): changing float parameters to int32_t. - bool writeRelativeEvent(float relativeX, float relativeY); - bool writeScrollEvent(float xAxisMovement, float yAxisMovement); + bool writeRelativeEvent(float relativeX, float relativeY, std::chrono::nanoseconds eventTime); + bool writeScrollEvent(float xAxisMovement, float yAxisMovement, + std::chrono::nanoseconds eventTime); private: static const std::map BUTTON_ACTION_MAPPING; @@ -78,7 +84,8 @@ public: virtual ~VirtualTouchscreen() override; // TODO(b/259554911): changing float parameters to int32_t. bool writeTouchEvent(int32_t pointerId, int32_t toolType, int32_t action, float locationX, - float locationY, float pressure, float majorAxisSize); + float locationY, float pressure, float majorAxisSize, + std::chrono::nanoseconds eventTime); private: static const std::map TOUCH_ACTION_MAPPING; @@ -91,7 +98,7 @@ private: */ std::bitset mActivePointers{}; bool isValidPointerId(int32_t pointerId, UinputAction uinputAction); - bool handleTouchDown(int32_t pointerId); - bool handleTouchUp(int32_t pointerId); + bool handleTouchDown(int32_t pointerId, std::chrono::nanoseconds eventTime); + bool handleTouchUp(int32_t pointerId, std::chrono::nanoseconds eventTime); }; } // namespace android diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp index 3c1f2b6b56..af9ce3a38f 100644 --- a/libs/input/VirtualInputDevice.cpp +++ b/libs/input/VirtualInputDevice.cpp @@ -44,15 +44,24 @@ VirtualInputDevice::~VirtualInputDevice() { ioctl(mFd, UI_DEV_DESTROY); } -bool VirtualInputDevice::writeInputEvent(uint16_t type, uint16_t code, int32_t value) { - struct input_event ev = {.type = type, .code = code, .value = value}; +bool VirtualInputDevice::writeInputEvent(uint16_t type, uint16_t code, int32_t value, + std::chrono::nanoseconds eventTime) { + std::chrono::seconds seconds = std::chrono::duration_cast(eventTime); + std::chrono::microseconds microseconds = + std::chrono::duration_cast(eventTime - seconds); + struct input_event ev = {.type = type, + .code = code, + .value = value, + .input_event_sec = static_cast(seconds.count()), + .input_event_usec = static_cast(microseconds.count())}; return TEMP_FAILURE_RETRY(write(mFd, &ev, sizeof(struct input_event))) == sizeof(ev); } /** Utility method to write keyboard key events or mouse button events. */ bool VirtualInputDevice::writeEvKeyEvent(int32_t androidCode, int32_t androidAction, const std::map& evKeyCodeMapping, - const std::map& actionMapping) { + const std::map& actionMapping, + std::chrono::nanoseconds eventTime) { auto evKeyCodeIterator = evKeyCodeMapping.find(androidCode); if (evKeyCodeIterator == evKeyCodeMapping.end()) { ALOGE("Unsupported native EV keycode for android code %d", androidCode); @@ -63,10 +72,10 @@ bool VirtualInputDevice::writeEvKeyEvent(int32_t androidCode, int32_t androidAct return false; } if (!writeInputEvent(EV_KEY, static_cast(evKeyCodeIterator->second), - static_cast(actionIterator->second))) { + static_cast(actionIterator->second), eventTime)) { return false; } - if (!writeInputEvent(EV_SYN, SYN_REPORT, 0)) { + if (!writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime)) { return false; } return true; @@ -189,8 +198,10 @@ const std::map VirtualKeyboard::KEY_CODE_MAPPING = { VirtualKeyboard::VirtualKeyboard(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} VirtualKeyboard::~VirtualKeyboard() {} -bool VirtualKeyboard::writeKeyEvent(int32_t androidKeyCode, int32_t androidAction) { - return writeEvKeyEvent(androidKeyCode, androidAction, KEY_CODE_MAPPING, KEY_ACTION_MAPPING); +bool VirtualKeyboard::writeKeyEvent(int32_t androidKeyCode, int32_t androidAction, + std::chrono::nanoseconds eventTime) { + return writeEvKeyEvent(androidKeyCode, androidAction, KEY_CODE_MAPPING, KEY_ACTION_MAPPING, + eventTime); } // --- VirtualDpad --- @@ -210,9 +221,10 @@ VirtualDpad::VirtualDpad(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} VirtualDpad::~VirtualDpad() {} -bool VirtualDpad::writeDpadKeyEvent(int32_t androidKeyCode, int32_t androidAction) { +bool VirtualDpad::writeDpadKeyEvent(int32_t androidKeyCode, int32_t androidAction, + std::chrono::nanoseconds eventTime) { return writeEvKeyEvent(androidKeyCode, androidAction, DPAD_KEY_CODE_MAPPING, - VirtualKeyboard::KEY_ACTION_MAPPING); + VirtualKeyboard::KEY_ACTION_MAPPING, eventTime); } // --- VirtualMouse --- @@ -236,20 +248,24 @@ VirtualMouse::VirtualMouse(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} VirtualMouse::~VirtualMouse() {} -bool VirtualMouse::writeButtonEvent(int32_t androidButtonCode, int32_t androidAction) { +bool VirtualMouse::writeButtonEvent(int32_t androidButtonCode, int32_t androidAction, + std::chrono::nanoseconds eventTime) { return writeEvKeyEvent(androidButtonCode, androidAction, BUTTON_CODE_MAPPING, - BUTTON_ACTION_MAPPING); + BUTTON_ACTION_MAPPING, eventTime); } -bool VirtualMouse::writeRelativeEvent(float relativeX, float relativeY) { - return writeInputEvent(EV_REL, REL_X, relativeX) && writeInputEvent(EV_REL, REL_Y, relativeY) && - writeInputEvent(EV_SYN, SYN_REPORT, 0); +bool VirtualMouse::writeRelativeEvent(float relativeX, float relativeY, + std::chrono::nanoseconds eventTime) { + return writeInputEvent(EV_REL, REL_X, relativeX, eventTime) && + writeInputEvent(EV_REL, REL_Y, relativeY, eventTime) && + writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); } -bool VirtualMouse::writeScrollEvent(float xAxisMovement, float yAxisMovement) { - return writeInputEvent(EV_REL, REL_HWHEEL, xAxisMovement) && - writeInputEvent(EV_REL, REL_WHEEL, yAxisMovement) && - writeInputEvent(EV_SYN, SYN_REPORT, 0); +bool VirtualMouse::writeScrollEvent(float xAxisMovement, float yAxisMovement, + std::chrono::nanoseconds eventTime) { + return writeInputEvent(EV_REL, REL_HWHEEL, xAxisMovement, eventTime) && + writeInputEvent(EV_REL, REL_WHEEL, yAxisMovement, eventTime) && + writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); } // --- VirtualTouchscreen --- @@ -291,7 +307,7 @@ bool VirtualTouchscreen::isValidPointerId(int32_t pointerId, UinputAction uinput bool VirtualTouchscreen::writeTouchEvent(int32_t pointerId, int32_t toolType, int32_t action, float locationX, float locationY, float pressure, - float majorAxisSize) { + float majorAxisSize, std::chrono::nanoseconds eventTime) { auto actionIterator = TOUCH_ACTION_MAPPING.find(action); if (actionIterator == TOUCH_ACTION_MAPPING.end()) { return false; @@ -300,44 +316,44 @@ bool VirtualTouchscreen::writeTouchEvent(int32_t pointerId, int32_t toolType, in if (!isValidPointerId(pointerId, uinputAction)) { return false; } - if (!writeInputEvent(EV_ABS, ABS_MT_SLOT, pointerId)) { + if (!writeInputEvent(EV_ABS, ABS_MT_SLOT, pointerId, eventTime)) { return false; } auto toolTypeIterator = TOOL_TYPE_MAPPING.find(toolType); if (toolTypeIterator == TOOL_TYPE_MAPPING.end()) { return false; } - if (!writeInputEvent(EV_ABS, ABS_MT_TOOL_TYPE, - static_cast(toolTypeIterator->second))) { + if (!writeInputEvent(EV_ABS, ABS_MT_TOOL_TYPE, static_cast(toolTypeIterator->second), + eventTime)) { return false; } - if (uinputAction == UinputAction::PRESS && !handleTouchDown(pointerId)) { + if (uinputAction == UinputAction::PRESS && !handleTouchDown(pointerId, eventTime)) { return false; } - if (uinputAction == UinputAction::RELEASE && !handleTouchUp(pointerId)) { + if (uinputAction == UinputAction::RELEASE && !handleTouchUp(pointerId, eventTime)) { return false; } - if (!writeInputEvent(EV_ABS, ABS_MT_POSITION_X, locationX)) { + if (!writeInputEvent(EV_ABS, ABS_MT_POSITION_X, locationX, eventTime)) { return false; } - if (!writeInputEvent(EV_ABS, ABS_MT_POSITION_Y, locationY)) { + if (!writeInputEvent(EV_ABS, ABS_MT_POSITION_Y, locationY, eventTime)) { return false; } if (!isnan(pressure)) { - if (!writeInputEvent(EV_ABS, ABS_MT_PRESSURE, pressure)) { + if (!writeInputEvent(EV_ABS, ABS_MT_PRESSURE, pressure, eventTime)) { return false; } } if (!isnan(majorAxisSize)) { - if (!writeInputEvent(EV_ABS, ABS_MT_TOUCH_MAJOR, majorAxisSize)) { + if (!writeInputEvent(EV_ABS, ABS_MT_TOUCH_MAJOR, majorAxisSize, eventTime)) { return false; } } - return writeInputEvent(EV_SYN, SYN_REPORT, 0); + return writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); } -bool VirtualTouchscreen::handleTouchUp(int32_t pointerId) { - if (!writeInputEvent(EV_ABS, ABS_MT_TRACKING_ID, static_cast(-1))) { +bool VirtualTouchscreen::handleTouchUp(int32_t pointerId, std::chrono::nanoseconds eventTime) { + if (!writeInputEvent(EV_ABS, ABS_MT_TRACKING_ID, static_cast(-1), eventTime)) { return false; } // When a pointer is no longer in touch, remove the pointer id from the corresponding @@ -347,7 +363,8 @@ bool VirtualTouchscreen::handleTouchUp(int32_t pointerId) { // Only sends the BTN UP event when there's no pointers on the touchscreen. if (mActivePointers.none()) { - if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast(UinputAction::RELEASE))) { + if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast(UinputAction::RELEASE), + eventTime)) { return false; } ALOGD_IF(isDebug(), "No pointers on touchscreen %d, BTN UP event sent.", mFd.get()); @@ -355,12 +372,13 @@ bool VirtualTouchscreen::handleTouchUp(int32_t pointerId) { return true; } -bool VirtualTouchscreen::handleTouchDown(int32_t pointerId) { +bool VirtualTouchscreen::handleTouchDown(int32_t pointerId, std::chrono::nanoseconds eventTime) { // When a new pointer is down on the touchscreen, add the pointer id in the corresponding // entry in the unreleased touches map. if (mActivePointers.none()) { // Only sends the BTN Down event when the first pointer on the touchscreen is down. - if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast(UinputAction::PRESS))) { + if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast(UinputAction::PRESS), + eventTime)) { return false; } ALOGD_IF(isDebug(), "First pointer %d down under touchscreen %d, BTN DOWN event sent", @@ -369,7 +387,7 @@ bool VirtualTouchscreen::handleTouchDown(int32_t pointerId) { mActivePointers.set(pointerId); ALOGD_IF(isDebug(), "Added pointer %d under touchscreen %d in the map", pointerId, mFd.get()); - if (!writeInputEvent(EV_ABS, ABS_MT_TRACKING_ID, static_cast(pointerId))) { + if (!writeInputEvent(EV_ABS, ABS_MT_TRACKING_ID, static_cast(pointerId), eventTime)) { return false; } return true; -- GitLab From 73cc9fd7ce71a1bd3398606a35a7c034145c539f Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 18 Apr 2023 11:18:07 -0700 Subject: [PATCH 1146/1310] [sf] Clean up transaction handling trace tags - Only add a tag if a transaction is not ready to be applied - Add sufficient info on why its not ready and which layer was affected - Remove unused tags Test: perfetto traces Bug: 277799011 Change-Id: I956823699721aaa2260eefd3762ec60a20790140 --- .../FrontEnd/TransactionHandler.cpp | 8 ++++-- services/surfaceflinger/SurfaceFlinger.cpp | 28 +++++++++++-------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp index a209cad612..9cbe0bb224 100644 --- a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp +++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp @@ -21,6 +21,7 @@ #include #include +#include #include "TransactionHandler.h" @@ -73,12 +74,13 @@ std::vector TransactionHandler::flushTransactions() { void TransactionHandler::applyUnsignaledBufferTransaction( std::vector& transactions, TransactionFlushState& flushState) { - // only apply an unsignaled buffer transaction if it's the first one - if (!transactions.empty()) { + if (!flushState.queueWithUnsignaledBuffer) { return; } - if (!flushState.queueWithUnsignaledBuffer) { + // only apply an unsignaled buffer transaction if it's the first one + if (!transactions.empty()) { + ATRACE_NAME("fence unsignaled"); return; } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 8394ffbca8..a9649f0ed1 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4187,20 +4187,20 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyTimelin const TransactionHandler::TransactionFlushState& flushState) { using TransactionReadiness = TransactionHandler::TransactionReadiness; const auto& transaction = *flushState.transaction; - ATRACE_FORMAT("transactionIsReadyToBeApplied vsyncId: %" PRId64, - transaction.frameTimelineInfo.vsyncId); TimePoint desiredPresentTime = TimePoint::fromNs(transaction.desiredPresentTime); // Do not present if the desiredPresentTime has not passed unless it is more than // one second in the future. We ignore timestamps more than 1 second in the future // for stability reasons. if (!transaction.isAutoTimestamp && desiredPresentTime >= mExpectedPresentTime && desiredPresentTime < mExpectedPresentTime + 1s) { - ATRACE_NAME("not current"); + ATRACE_FORMAT("not current desiredPresentTime: %" PRId64 " expectedPresentTime: %" PRId64, + desiredPresentTime, mExpectedPresentTime); return TransactionReadiness::NotReady; } if (!mScheduler->isVsyncValid(mExpectedPresentTime, transaction.originUid)) { - ATRACE_NAME("!isVsyncValid"); + ATRACE_FORMAT("!isVsyncValid expectedPresentTime: %" PRId64 " uid: %d", + mExpectedPresentTime, transaction.originUid); return TransactionReadiness::NotReady; } @@ -4208,7 +4208,8 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyTimelin // expected present time of this transaction. if (transaction.isAutoTimestamp && frameIsEarly(mExpectedPresentTime, VsyncId{transaction.frameTimelineInfo.vsyncId})) { - ATRACE_NAME("frameIsEarly"); + ATRACE_FORMAT("frameIsEarly vsyncId: %" PRId64 " expectedPresentTime: %" PRId64, + transaction.frameTimelineInfo.vsyncId, mExpectedPresentTime); return TransactionReadiness::NotReady; } return TransactionReadiness::Ready; @@ -4238,7 +4239,9 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC s.bufferData->acquireFence); // Delete the entire state at this point and not just release the buffer because // everything associated with the Layer in this Transaction is now out of date. - ATRACE_NAME("DeleteStaleBuffer"); + ATRACE_FORMAT("DeleteStaleBuffer %s barrierProducerId:%d > %d", + layer->getDebugName(), layer->getDrawingState().barrierProducerId, + s.bufferData->producerId); return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL; } @@ -4248,7 +4251,10 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC ((flushState.bufferLayersReadyToPresent.get(s.surface.get()) >= s.bufferData->barrierFrameNumber)); if (!willApplyBarrierFrame) { - ATRACE_NAME("NotReadyBarrier"); + ATRACE_FORMAT("NotReadyBarrier %s barrierFrameNumber:%" PRId64 " > %" PRId64, + layer->getDebugName(), + layer->getDrawingState().barrierFrameNumber, + s.bufferData->barrierFrameNumber); ready = TransactionReadiness::NotReadyBarrier; return TraverseBuffersReturnValues::STOP_TRAVERSAL; } @@ -4260,7 +4266,7 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC const bool hasPendingBuffer = flushState.bufferLayersReadyToPresent.contains(s.surface.get()); if (layer->backpressureEnabled() && hasPendingBuffer && transaction.isAutoTimestamp) { - ATRACE_NAME("hasPendingBuffer"); + ATRACE_FORMAT("hasPendingBuffer %s", layer->getDebugName()); ready = TransactionReadiness::NotReady; return TraverseBuffersReturnValues::STOP_TRAVERSAL; } @@ -4277,9 +4283,9 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC const bool allowLatchUnsignaled = shouldLatchUnsignaled(layer, s, transaction.states.size(), flushState.firstTransaction); - ATRACE_FORMAT("%s allowLatchUnsignaled=%s", layer->getName().c_str(), - allowLatchUnsignaled ? "true" : "false"); if (allowLatchUnsignaled) { + ATRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s", + layer->getDebugName()); ready = TransactionReadiness::NotReadyUnsignaled; } else { ready = TransactionReadiness::NotReady; @@ -4292,12 +4298,12 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferC "Buffer processing hung up due to stuck " "fence. Indicates GPU hang"); } + ATRACE_FORMAT("fence unsignaled %s", layer->getDebugName()); return TraverseBuffersReturnValues::STOP_TRAVERSAL; } } return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL; }); - ATRACE_INT("TransactionReadiness", static_cast(ready)); return ready; } -- GitLab From 12f8913b184f0be4e7a9187bb6dec5ccc0ab54a0 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 18 Apr 2023 20:47:03 +0000 Subject: [PATCH 1147/1310] ftl_flags: Add default parameter to any() Add a default parameter to any() so that we can easily test if any of the bits in the bitfield have been set. Bug: 245989146 Test: atest ftl_test Change-Id: Ibee03c2f70288d1a353e1d86539fb7f88b1e5d3c --- include/ftl/flags.h | 2 +- libs/ftl/flags_test.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/ftl/flags.h b/include/ftl/flags.h index cdb4e840a4..dbe3148fc5 100644 --- a/include/ftl/flags.h +++ b/include/ftl/flags.h @@ -120,7 +120,7 @@ public: } /* Tests whether any of the given flags are set */ - bool any(Flags f) const { return (mFlags & f.mFlags) != 0; } + bool any(Flags f = ~Flags()) const { return (mFlags & f.mFlags) != 0; } /* Tests whether all of the given flags are set */ bool all(Flags f) const { return (mFlags & f.mFlags) == f.mFlags; } diff --git a/libs/ftl/flags_test.cpp b/libs/ftl/flags_test.cpp index eea052ba33..1279d1147d 100644 --- a/libs/ftl/flags_test.cpp +++ b/libs/ftl/flags_test.cpp @@ -35,6 +35,7 @@ TEST(Flags, Test) { TEST(Flags, Any) { Flags flags = TestFlags::ONE | TestFlags::TWO; + ASSERT_TRUE(flags.any()); ASSERT_TRUE(flags.any(TestFlags::ONE)); ASSERT_TRUE(flags.any(TestFlags::TWO)); ASSERT_FALSE(flags.any(TestFlags::THREE)); @@ -42,6 +43,9 @@ TEST(Flags, Any) { ASSERT_TRUE(flags.any(TestFlags::TWO | TestFlags::THREE)); ASSERT_TRUE(flags.any(TestFlags::ONE | TestFlags::THREE)); ASSERT_TRUE(flags.any(TestFlags::ONE | TestFlags::TWO | TestFlags::THREE)); + + Flags emptyFlags; + ASSERT_FALSE(emptyFlags.any()); } TEST(Flags, All) { -- GitLab From 80872bddcc529d750c12349675504ea00271712a Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Tue, 15 Nov 2022 11:34:33 -0500 Subject: [PATCH 1148/1310] SF: Clean up ftl::SmallMap lookup fallbacks Avoid std::cref to a local variable, which is not intuitive and incurs construction in the non-fallback case. Bug: 185536303 Test: ftl_test Change-Id: I1c5a94bdab105a04f8230fe762bdc433eea5c97a --- include/ftl/algorithm.h | 25 +++++++++++++++++++ libs/ftl/algorithm_test.cpp | 16 ++++++++++++ .../Scheduler/RefreshRateStats.h | 20 +++++++++++---- services/surfaceflinger/Scheduler/Scheduler.h | 5 ++-- services/surfaceflinger/SurfaceFlinger.h | 10 +++++--- 5 files changed, 65 insertions(+), 11 deletions(-) diff --git a/include/ftl/algorithm.h b/include/ftl/algorithm.h index c5ff03b80d..c0f67683ab 100644 --- a/include/ftl/algorithm.h +++ b/include/ftl/algorithm.h @@ -68,4 +68,29 @@ constexpr auto to_mapped_ref(const Pair& pair) -> std::reference_wrapper::or_else when T is std::reference_wrapper. Given a +// lambda argument that returns a `constexpr` value, ftl::static_ref binds a reference to a +// static T initialized to that constant. +// +// const ftl::SmallMap map = ftl::init::map(13, "tiramisu"sv)(14, "upside-down cake"sv); +// assert("???"sv == +// map.get(20).or_else(ftl::static_ref([] { return "???"sv; }))->get()); +// +// using Map = decltype(map); +// +// assert("snow cone"sv == +// ftl::find_if(map, [](const auto& pair) { return pair.second.front() == 's'; }) +// .transform(ftl::to_mapped_ref) +// .or_else(ftl::static_ref([] { return "snow cone"sv; })) +// ->get()); +// +template +constexpr auto static_ref(F&& f) { + return [f = std::forward(f)] { + constexpr auto kInitializer = f(); + static const T kValue = kInitializer; + return Optional(std::cref(kValue)); + }; +} + } // namespace android::ftl diff --git a/libs/ftl/algorithm_test.cpp b/libs/ftl/algorithm_test.cpp index 8052caf642..487b1b8759 100644 --- a/libs/ftl/algorithm_test.cpp +++ b/libs/ftl/algorithm_test.cpp @@ -47,4 +47,20 @@ TEST(Algorithm, FindIf) { EXPECT_EQ(opt->get(), ftl::StaticVector("tiramisu"sv)); } +TEST(Algorithm, StaticRef) { + using namespace std::string_view_literals; + + const ftl::SmallMap map = ftl::init::map(13, "tiramisu"sv)(14, "upside-down cake"sv); + ASSERT_EQ("???"sv, + map.get(20).or_else(ftl::static_ref([] { return "???"sv; }))->get()); + + using Map = decltype(map); + + ASSERT_EQ("snow cone"sv, + ftl::find_if(map, [](const auto& pair) { return pair.second.front() == 's'; }) + .transform(ftl::to_mapped_ref) + .or_else(ftl::static_ref([] { return "snow cone"sv; })) + ->get()); +} + } // namespace android::test diff --git a/services/surfaceflinger/Scheduler/RefreshRateStats.h b/services/surfaceflinger/Scheduler/RefreshRateStats.h index ed65bc607d..67e1b9c2ea 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateStats.h +++ b/services/surfaceflinger/Scheduler/RefreshRateStats.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -82,12 +83,18 @@ public: flushTime(); TotalTimes totalTimes = ftl::init::map("ScreenOff", mScreenOffTime); - const auto zero = std::chrono::milliseconds::zero(); // Sum the times for modes that map to the same name, e.g. "60 Hz". for (const auto& [fps, time] : mFpsTotalTimes) { const auto string = to_string(fps); - const auto total = std::as_const(totalTimes).get(string).value_or(std::cref(zero)); + const auto total = std::as_const(totalTimes) + .get(string) + .or_else(ftl::static_ref([] { + using namespace std::chrono_literals; + return 0ms; + })) + .value(); + totalTimes.emplace_or_replace(string, total.get() + time); } @@ -114,15 +121,18 @@ private: mPreviousRecordedTime = currentTime; const auto duration = std::chrono::milliseconds{ns2ms(timeElapsed)}; - const auto zero = std::chrono::milliseconds::zero(); - uint32_t fps = 0; if (mCurrentPowerMode == PowerMode::ON) { // Normal power mode is counted under different config modes. const auto total = std::as_const(mFpsTotalTimes) .get(mCurrentRefreshRate) - .value_or(std::cref(zero)); + .or_else(ftl::static_ref([] { + using namespace std::chrono_literals; + return 0ms; + })) + .value(); + mFpsTotalTimes.emplace_or_replace(mCurrentRefreshRate, total.get() + duration); fps = static_cast(mCurrentRefreshRate.getIntValue()); diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 720a1cbba2..4b2983b33d 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -32,6 +32,7 @@ #include #pragma clang diagnostic pop // ignored "-Wconversion -Wextra" +#include #include #include #include @@ -438,13 +439,13 @@ private: RefreshRateSelectorPtr pacesetterSelectorPtrLocked() const REQUIRES(mDisplayLock) { ftl::FakeGuard guard(kMainThreadContext); - const RefreshRateSelectorPtr noPacesetter; return mPacesetterDisplayId .and_then([this](PhysicalDisplayId pacesetterId) REQUIRES(mDisplayLock, kMainThreadContext) { return mRefreshRateSelectors.get(pacesetterId); }) - .value_or(std::cref(noPacesetter)); + .or_else(ftl::static_ref([] { return nullptr; })) + .value(); } std::shared_ptr getVsyncScheduleLocked( diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index eb9dc7494e..04fcfb9097 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -854,8 +855,9 @@ private: } sp getDisplayDeviceLocked(const wp& displayToken) REQUIRES(mStateLock) { - const sp nullDisplay; - return mDisplays.get(displayToken).value_or(std::cref(nullDisplay)); + return mDisplays.get(displayToken) + .or_else(ftl::static_ref>([] { return nullptr; })) + .value(); } sp getDisplayDeviceLocked(PhysicalDisplayId id) const @@ -1011,10 +1013,10 @@ private: */ sp getPhysicalDisplayTokenLocked(PhysicalDisplayId displayId) const REQUIRES(mStateLock) { - const sp nullToken; return mPhysicalDisplays.get(displayId) .transform([](const display::PhysicalDisplay& display) { return display.token(); }) - .value_or(std::cref(nullToken)); + .or_else([] { return std::optional>(nullptr); }) + .value(); } std::optional getPhysicalDisplayIdLocked( -- GitLab From 4c9d6ffc779d430aa83f9eabb4317df6222fd4f8 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 18 Apr 2023 11:23:20 -0700 Subject: [PATCH 1149/1310] Add const annotations to dispatcher These allow us to dump the dispatcher state from other const dispatcher methods. Bug: 274073185 Test: m Change-Id: I483d957b243bca595c147656d8305fdc4f43fb33 --- .../inputflinger/dispatcher/InputDispatcher.cpp | 14 +++++++------- services/inputflinger/dispatcher/InputDispatcher.h | 10 +++++----- .../inputflinger/dispatcher/LatencyAggregator.cpp | 2 +- .../inputflinger/dispatcher/LatencyAggregator.h | 2 +- .../inputflinger/dispatcher/LatencyTracker.cpp | 2 +- services/inputflinger/dispatcher/LatencyTracker.h | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index e8434be6cc..a6d181047a 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -1297,7 +1297,7 @@ bool InputDispatcher::isAppSwitchKeyEvent(const KeyEntry& keyEntry) { (keyEntry.policyFlags & POLICY_FLAG_PASS_TO_USER); } -bool InputDispatcher::isAppSwitchPendingLocked() { +bool InputDispatcher::isAppSwitchPendingLocked() const { return mAppSwitchDueTime != LLONG_MAX; } @@ -5411,7 +5411,7 @@ void InputDispatcher::resetAndDropEverythingLocked(const char* reason) { mReplacedKeys.clear(); } -void InputDispatcher::logDispatchStateLocked() { +void InputDispatcher::logDispatchStateLocked() const { std::string dump; dumpDispatchStateLocked(dump); @@ -5423,7 +5423,7 @@ void InputDispatcher::logDispatchStateLocked() { } } -std::string InputDispatcher::dumpPointerCaptureStateLocked() { +std::string InputDispatcher::dumpPointerCaptureStateLocked() const { std::string dump; dump += StringPrintf(INDENT "Pointer Capture Requested: %s\n", @@ -5441,7 +5441,7 @@ std::string InputDispatcher::dumpPointerCaptureStateLocked() { return dump; } -void InputDispatcher::dumpDispatchStateLocked(std::string& dump) { +void InputDispatcher::dumpDispatchStateLocked(std::string& dump) const { dump += StringPrintf(INDENT "DispatchEnabled: %s\n", toString(mDispatchEnabled)); dump += StringPrintf(INDENT "DispatchFrozen: %s\n", toString(mDispatchFrozen)); dump += StringPrintf(INDENT "InputFilterEnabled: %s\n", toString(mInputFilterEnabled)); @@ -5544,7 +5544,7 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) { // Dump recently dispatched or dropped events from oldest to newest. if (!mRecentQueue.empty()) { dump += StringPrintf(INDENT "RecentQueue: length=%zu\n", mRecentQueue.size()); - for (std::shared_ptr& entry : mRecentQueue) { + for (const std::shared_ptr& entry : mRecentQueue) { dump += INDENT2; dump += entry->getDescription(); dump += StringPrintf(", age=%" PRId64 "ms\n", ns2ms(currentTime - entry->eventTime)); @@ -5567,7 +5567,7 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) { // Dump inbound events from oldest to newest. if (!mInboundQueue.empty()) { dump += StringPrintf(INDENT "InboundQueue: length=%zu\n", mInboundQueue.size()); - for (std::shared_ptr& entry : mInboundQueue) { + for (const std::shared_ptr& entry : mInboundQueue) { dump += INDENT2; dump += entry->getDescription(); dump += StringPrintf(", age=%" PRId64 "ms\n", ns2ms(currentTime - entry->eventTime)); @@ -5649,7 +5649,7 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) { dump += mLatencyAggregator.dump(INDENT2); } -void InputDispatcher::dumpMonitors(std::string& dump, const std::vector& monitors) { +void InputDispatcher::dumpMonitors(std::string& dump, const std::vector& monitors) const { const size_t numMonitors = monitors.size(); for (size_t i = 0; i < numMonitors; i++) { const Monitor& monitor = monitors[i]; diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 4aba24cc72..76b15e2b1d 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -232,7 +232,7 @@ private: nsecs_t mAppSwitchDueTime GUARDED_BY(mLock); bool isAppSwitchKeyEvent(const KeyEntry& keyEntry); - bool isAppSwitchPendingLocked() REQUIRES(mLock); + bool isAppSwitchPendingLocked() const REQUIRES(mLock); void resetPendingAppSwitchLocked(bool handled) REQUIRES(mLock); // Blocked event latency optimization. Drops old events when the user intends @@ -647,10 +647,10 @@ private: void resetAndDropEverythingLocked(const char* reason) REQUIRES(mLock); // Dump state. - void dumpDispatchStateLocked(std::string& dump) REQUIRES(mLock); - void dumpMonitors(std::string& dump, const std::vector& monitors); - void logDispatchStateLocked() REQUIRES(mLock); - std::string dumpPointerCaptureStateLocked() REQUIRES(mLock); + void dumpDispatchStateLocked(std::string& dump) const REQUIRES(mLock); + void dumpMonitors(std::string& dump, const std::vector& monitors) const; + void logDispatchStateLocked() const REQUIRES(mLock); + std::string dumpPointerCaptureStateLocked() const REQUIRES(mLock); // Registration. void removeMonitorChannelLocked(const sp& connectionToken) REQUIRES(mLock); diff --git a/services/inputflinger/dispatcher/LatencyAggregator.cpp b/services/inputflinger/dispatcher/LatencyAggregator.cpp index a5bfc25e78..96d78c375b 100644 --- a/services/inputflinger/dispatcher/LatencyAggregator.cpp +++ b/services/inputflinger/dispatcher/LatencyAggregator.cpp @@ -256,7 +256,7 @@ void LatencyAggregator::processSlowEvent(const InputEventTimeline& timeline) { } } -std::string LatencyAggregator::dump(const char* prefix) { +std::string LatencyAggregator::dump(const char* prefix) const { std::string sketchDump = StringPrintf("%s Sketches:\n", prefix); for (size_t i = 0; i < SketchIndex::SIZE; i++) { const int64_t numDown = mDownSketches[i]->num_values(); diff --git a/services/inputflinger/dispatcher/LatencyAggregator.h b/services/inputflinger/dispatcher/LatencyAggregator.h index accfc29d8e..60b6813158 100644 --- a/services/inputflinger/dispatcher/LatencyAggregator.h +++ b/services/inputflinger/dispatcher/LatencyAggregator.h @@ -56,7 +56,7 @@ public: */ void processTimeline(const InputEventTimeline& timeline) override; - std::string dump(const char* prefix); + std::string dump(const char* prefix) const; ~LatencyAggregator(); diff --git a/services/inputflinger/dispatcher/LatencyTracker.cpp b/services/inputflinger/dispatcher/LatencyTracker.cpp index 2dd7d3f529..b7c36a8db8 100644 --- a/services/inputflinger/dispatcher/LatencyTracker.cpp +++ b/services/inputflinger/dispatcher/LatencyTracker.cpp @@ -165,7 +165,7 @@ void LatencyTracker::reportAndPruneMatureRecords(nsecs_t newEventTime) { } } -std::string LatencyTracker::dump(const char* prefix) { +std::string LatencyTracker::dump(const char* prefix) const { return StringPrintf("%sLatencyTracker:\n", prefix) + StringPrintf("%s mTimelines.size() = %zu\n", prefix, mTimelines.size()) + StringPrintf("%s mEventTimes.size() = %zu\n", prefix, mEventTimes.size()); diff --git a/services/inputflinger/dispatcher/LatencyTracker.h b/services/inputflinger/dispatcher/LatencyTracker.h index 64dfeef20d..4212da876f 100644 --- a/services/inputflinger/dispatcher/LatencyTracker.h +++ b/services/inputflinger/dispatcher/LatencyTracker.h @@ -55,7 +55,7 @@ public: void trackGraphicsLatency(int32_t inputEventId, const sp& connectionToken, std::array timeline); - std::string dump(const char* prefix); + std::string dump(const char* prefix) const; private: /** -- GitLab From 4bf6d455dc8f1d0b22614028062c5d0a21130139 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 18 Apr 2023 21:26:56 +0000 Subject: [PATCH 1150/1310] Use ftl::Flags for InputReaderConfiguration::Change Convert the config flags into an enum class and use ftl::Flags when dealing with configuration changes. Bug: 245989146 Test: Presubmit Change-Id: I0aed947ce433a1def11a60e73a14575561374700 --- services/inputflinger/InputReaderBase.cpp | 44 --- .../inputflinger/include/InputReaderBase.h | 256 +++++++++--------- services/inputflinger/reader/InputDevice.cpp | 18 +- services/inputflinger/reader/InputReader.cpp | 34 +-- .../inputflinger/reader/include/InputDevice.h | 2 +- .../inputflinger/reader/include/InputReader.h | 6 +- .../reader/mapper/CursorInputMapper.cpp | 10 +- .../reader/mapper/CursorInputMapper.h | 2 +- .../mapper/ExternalStylusInputMapper.cpp | 2 +- .../reader/mapper/ExternalStylusInputMapper.h | 2 +- .../reader/mapper/InputMapper.cpp | 2 +- .../inputflinger/reader/mapper/InputMapper.h | 2 +- .../reader/mapper/JoystickInputMapper.cpp | 4 +- .../reader/mapper/JoystickInputMapper.h | 2 +- .../reader/mapper/KeyboardInputMapper.cpp | 9 +- .../reader/mapper/KeyboardInputMapper.h | 2 +- .../mapper/RotaryEncoderInputMapper.cpp | 6 +- .../reader/mapper/RotaryEncoderInputMapper.h | 2 +- .../reader/mapper/SensorInputMapper.cpp | 4 +- .../reader/mapper/SensorInputMapper.h | 2 +- .../reader/mapper/TouchInputMapper.cpp | 27 +- .../reader/mapper/TouchInputMapper.h | 2 +- .../reader/mapper/TouchpadInputMapper.cpp | 8 +- .../reader/mapper/TouchpadInputMapper.h | 2 +- .../inputflinger/tests/InputMapperTest.cpp | 14 +- services/inputflinger/tests/InputMapperTest.h | 4 +- .../inputflinger/tests/InputReader_test.cpp | 128 +++++---- .../tests/fuzzers/CursorInputFuzzer.cpp | 12 +- .../tests/fuzzers/FuzzContainer.h | 2 +- .../tests/fuzzers/InputReaderFuzzer.cpp | 5 +- .../tests/fuzzers/KeyboardInputFuzzer.cpp | 3 +- .../tests/fuzzers/MultiTouchInputFuzzer.cpp | 3 +- 32 files changed, 290 insertions(+), 331 deletions(-) diff --git a/services/inputflinger/InputReaderBase.cpp b/services/inputflinger/InputReaderBase.cpp index 2450235ec1..4ec5b898b1 100644 --- a/services/inputflinger/InputReaderBase.cpp +++ b/services/inputflinger/InputReaderBase.cpp @@ -38,50 +38,6 @@ namespace android { // --- InputReaderConfiguration --- -std::string InputReaderConfiguration::changesToString(uint32_t changes) { - if (changes == 0) { - return ""; - } - std::string result; - if (changes & CHANGE_POINTER_SPEED) { - result += "POINTER_SPEED | "; - } - if (changes & CHANGE_POINTER_GESTURE_ENABLEMENT) { - result += "POINTER_GESTURE_ENABLEMENT | "; - } - if (changes & CHANGE_DISPLAY_INFO) { - result += "DISPLAY_INFO | "; - } - if (changes & CHANGE_SHOW_TOUCHES) { - result += "SHOW_TOUCHES | "; - } - if (changes & CHANGE_KEYBOARD_LAYOUTS) { - result += "KEYBOARD_LAYOUTS | "; - } - if (changes & CHANGE_DEVICE_ALIAS) { - result += "DEVICE_ALIAS | "; - } - if (changes & CHANGE_TOUCH_AFFINE_TRANSFORMATION) { - result += "TOUCH_AFFINE_TRANSFORMATION | "; - } - if (changes & CHANGE_EXTERNAL_STYLUS_PRESENCE) { - result += "EXTERNAL_STYLUS_PRESENCE | "; - } - if (changes & CHANGE_POINTER_CAPTURE) { - result += "POINTER_CAPTURE | "; - } - if (changes & CHANGE_ENABLED_STATE) { - result += "ENABLED_STATE | "; - } - if (changes & CHANGE_TOUCHPAD_SETTINGS) { - result += "TOUCHPAD_SETTINGS | "; - } - if (changes & CHANGE_MUST_REOPEN) { - result += "MUST_REOPEN | "; - } - return result; -} - std::optional InputReaderConfiguration::getDisplayViewportByUniqueId( const std::string& uniqueDisplayId) const { if (uniqueDisplayId.empty()) { diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index 1750c64eca..a93a2ea615 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -42,118 +42,6 @@ namespace android { -// --- InputReaderInterface --- - -/* The interface for the InputReader shared library. - * - * Manages one or more threads that process raw input events and sends cooked event data to an - * input listener. - * - * The implementation must guarantee thread safety for this interface. However, since the input - * listener is NOT thread safe, all calls to the listener must happen from the same thread. - */ -class InputReaderInterface { -public: - InputReaderInterface() { } - virtual ~InputReaderInterface() { } - - /* Dumps the state of the input reader. - * - * This method may be called on any thread (usually by the input manager). */ - virtual void dump(std::string& dump) = 0; - - /* Called by the heartbeat to ensures that the reader has not deadlocked. */ - virtual void monitor() = 0; - - /* Returns true if the input device is enabled. */ - virtual bool isInputDeviceEnabled(int32_t deviceId) = 0; - - /* Makes the reader start processing events from the kernel. */ - virtual status_t start() = 0; - - /* Makes the reader stop processing any more events. */ - virtual status_t stop() = 0; - - /* Gets information about all input devices. - * - * This method may be called on any thread (usually by the input manager). - */ - virtual std::vector getInputDevices() const = 0; - - /* Query current input state. */ - virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, - int32_t scanCode) = 0; - virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, - int32_t keyCode) = 0; - virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, - int32_t sw) = 0; - - virtual void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, - int32_t toKeyCode) const = 0; - - virtual int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const = 0; - - /* Toggle Caps Lock */ - virtual void toggleCapsLockState(int32_t deviceId) = 0; - - /* Determine whether physical keys exist for the given framework-domain key codes. */ - virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask, - const std::vector& keyCodes, uint8_t* outFlags) = 0; - - /* Requests that a reconfiguration of all input devices. - * The changes flag is a bitfield that indicates what has changed and whether - * the input devices must all be reopened. */ - virtual void requestRefreshConfiguration(uint32_t changes) = 0; - - /* Controls the vibrator of a particular input device. */ - virtual void vibrate(int32_t deviceId, const VibrationSequence& sequence, ssize_t repeat, - int32_t token) = 0; - virtual void cancelVibrate(int32_t deviceId, int32_t token) = 0; - - virtual bool isVibrating(int32_t deviceId) = 0; - - virtual std::vector getVibratorIds(int32_t deviceId) = 0; - /* Get battery level of a particular input device. */ - virtual std::optional getBatteryCapacity(int32_t deviceId) = 0; - /* Get battery status of a particular input device. */ - virtual std::optional getBatteryStatus(int32_t deviceId) = 0; - /* Get the device path for the battery of an input device. */ - virtual std::optional getBatteryDevicePath(int32_t deviceId) = 0; - - virtual std::vector getLights(int32_t deviceId) = 0; - - virtual std::vector getSensors(int32_t deviceId) = 0; - - /* Return true if the device can send input events to the specified display. */ - virtual bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) = 0; - - /* Enable sensor in input reader mapper. */ - virtual bool enableSensor(int32_t deviceId, InputDeviceSensorType sensorType, - std::chrono::microseconds samplingPeriod, - std::chrono::microseconds maxBatchReportLatency) = 0; - - /* Disable sensor in input reader mapper. */ - virtual void disableSensor(int32_t deviceId, InputDeviceSensorType sensorType) = 0; - - /* Flush sensor data in input reader mapper. */ - virtual void flushSensor(int32_t deviceId, InputDeviceSensorType sensorType) = 0; - - /* Set color for the light */ - virtual bool setLightColor(int32_t deviceId, int32_t lightId, int32_t color) = 0; - /* Set player ID for the light */ - virtual bool setLightPlayerId(int32_t deviceId, int32_t lightId, int32_t playerId) = 0; - /* Get light color */ - virtual std::optional getLightColor(int32_t deviceId, int32_t lightId) = 0; - /* Get light player ID */ - virtual std::optional getLightPlayerId(int32_t deviceId, int32_t lightId) = 0; - - /* Get the Bluetooth address of an input device, if known. */ - virtual std::optional getBluetoothAddress(int32_t deviceId) const = 0; - - /* Sysfs node change reported. Recreate device if required to incorporate the new sysfs nodes */ - virtual void sysfsNodeChanged(const std::string& sysfsNodePath) = 0; -}; - // --- InputReaderConfiguration --- /* @@ -163,51 +51,51 @@ public: */ struct InputReaderConfiguration { // Describes changes that have occurred. - enum { + enum class Change : uint32_t { // The mouse pointer speed changed. - CHANGE_POINTER_SPEED = 1 << 0, + POINTER_SPEED = 1u << 0, // The pointer gesture control changed. - CHANGE_POINTER_GESTURE_ENABLEMENT = 1 << 1, + POINTER_GESTURE_ENABLEMENT = 1u << 1, // The display size or orientation changed. - CHANGE_DISPLAY_INFO = 1 << 2, + DISPLAY_INFO = 1u << 2, // The visible touches option changed. - CHANGE_SHOW_TOUCHES = 1 << 3, + SHOW_TOUCHES = 1u << 3, // The keyboard layouts must be reloaded. - CHANGE_KEYBOARD_LAYOUTS = 1 << 4, + KEYBOARD_LAYOUTS = 1u << 4, // The device name alias supplied by the may have changed for some devices. - CHANGE_DEVICE_ALIAS = 1 << 5, + DEVICE_ALIAS = 1u << 5, // The location calibration matrix changed. - CHANGE_TOUCH_AFFINE_TRANSFORMATION = 1 << 6, + TOUCH_AFFINE_TRANSFORMATION = 1u << 6, // The presence of an external stylus has changed. - CHANGE_EXTERNAL_STYLUS_PRESENCE = 1 << 7, + EXTERNAL_STYLUS_PRESENCE = 1u << 7, // The pointer capture mode has changed. - CHANGE_POINTER_CAPTURE = 1 << 8, + POINTER_CAPTURE = 1u << 8, // The set of disabled input devices (disabledDevices) has changed. - CHANGE_ENABLED_STATE = 1 << 9, + ENABLED_STATE = 1u << 9, // The device type has been updated. - CHANGE_DEVICE_TYPE = 1 << 10, + DEVICE_TYPE = 1u << 10, // The keyboard layout association has changed. - CHANGE_KEYBOARD_LAYOUT_ASSOCIATION = 1 << 11, + KEYBOARD_LAYOUT_ASSOCIATION = 1u << 11, // The stylus button reporting configurations has changed. - CHANGE_STYLUS_BUTTON_REPORTING = 1 << 12, + STYLUS_BUTTON_REPORTING = 1u << 12, // The touchpad settings changed. - CHANGE_TOUCHPAD_SETTINGS = 1 << 13, + TOUCHPAD_SETTINGS = 1u << 13, // All devices must be reopened. - CHANGE_MUST_REOPEN = 1 << 31, + MUST_REOPEN = 1u << 31, }; // Gets the amount of time to disable virtual keys after the screen is touched @@ -367,8 +255,6 @@ struct InputReaderConfiguration { stylusButtonMotionEventsEnabled(true), stylusPointerIconEnabled(false) {} - static std::string changesToString(uint32_t changes); - std::optional getDisplayViewportByType(ViewportType type) const; std::optional getDisplayViewportByUniqueId(const std::string& uniqueDisplayId) const; @@ -383,6 +269,116 @@ private: std::vector mDisplays; }; +using ConfigurationChanges = ftl::Flags; + +// --- InputReaderInterface --- + +/* The interface for the InputReader shared library. + * + * Manages one or more threads that process raw input events and sends cooked event data to an + * input listener. + * + * The implementation must guarantee thread safety for this interface. However, since the input + * listener is NOT thread safe, all calls to the listener must happen from the same thread. + */ +class InputReaderInterface { +public: + InputReaderInterface() {} + virtual ~InputReaderInterface() {} + /* Dumps the state of the input reader. + * + * This method may be called on any thread (usually by the input manager). */ + virtual void dump(std::string& dump) = 0; + + /* Called by the heartbeat to ensures that the reader has not deadlocked. */ + virtual void monitor() = 0; + + /* Returns true if the input device is enabled. */ + virtual bool isInputDeviceEnabled(int32_t deviceId) = 0; + + /* Makes the reader start processing events from the kernel. */ + virtual status_t start() = 0; + + /* Makes the reader stop processing any more events. */ + virtual status_t stop() = 0; + + /* Gets information about all input devices. + * + * This method may be called on any thread (usually by the input manager). + */ + virtual std::vector getInputDevices() const = 0; + + /* Query current input state. */ + virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, int32_t scanCode) = 0; + virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, int32_t keyCode) = 0; + virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) = 0; + + virtual void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, + int32_t toKeyCode) const = 0; + + virtual int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const = 0; + + /* Toggle Caps Lock */ + virtual void toggleCapsLockState(int32_t deviceId) = 0; + + /* Determine whether physical keys exist for the given framework-domain key codes. */ + virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask, + const std::vector& keyCodes, uint8_t* outFlags) = 0; + + /* Requests that a reconfiguration of all input devices. + * The changes flag is a bitfield that indicates what has changed and whether + * the input devices must all be reopened. */ + virtual void requestRefreshConfiguration(ConfigurationChanges changes) = 0; + + /* Controls the vibrator of a particular input device. */ + virtual void vibrate(int32_t deviceId, const VibrationSequence& sequence, ssize_t repeat, + int32_t token) = 0; + virtual void cancelVibrate(int32_t deviceId, int32_t token) = 0; + + virtual bool isVibrating(int32_t deviceId) = 0; + + virtual std::vector getVibratorIds(int32_t deviceId) = 0; + /* Get battery level of a particular input device. */ + virtual std::optional getBatteryCapacity(int32_t deviceId) = 0; + /* Get battery status of a particular input device. */ + virtual std::optional getBatteryStatus(int32_t deviceId) = 0; + /* Get the device path for the battery of an input device. */ + virtual std::optional getBatteryDevicePath(int32_t deviceId) = 0; + + virtual std::vector getLights(int32_t deviceId) = 0; + + virtual std::vector getSensors(int32_t deviceId) = 0; + + /* Return true if the device can send input events to the specified display. */ + virtual bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) = 0; + + /* Enable sensor in input reader mapper. */ + virtual bool enableSensor(int32_t deviceId, InputDeviceSensorType sensorType, + std::chrono::microseconds samplingPeriod, + std::chrono::microseconds maxBatchReportLatency) = 0; + + /* Disable sensor in input reader mapper. */ + virtual void disableSensor(int32_t deviceId, InputDeviceSensorType sensorType) = 0; + + /* Flush sensor data in input reader mapper. */ + virtual void flushSensor(int32_t deviceId, InputDeviceSensorType sensorType) = 0; + + /* Set color for the light */ + virtual bool setLightColor(int32_t deviceId, int32_t lightId, int32_t color) = 0; + /* Set player ID for the light */ + virtual bool setLightPlayerId(int32_t deviceId, int32_t lightId, int32_t playerId) = 0; + /* Get light color */ + virtual std::optional getLightColor(int32_t deviceId, int32_t lightId) = 0; + /* Get light player ID */ + virtual std::optional getLightPlayerId(int32_t deviceId, int32_t lightId) = 0; + + /* Get the Bluetooth address of an input device, if known. */ + virtual std::optional getBluetoothAddress(int32_t deviceId) const = 0; + + /* Sysfs node change reported. Recreate device if required to incorporate the new sysfs nodes */ + virtual void sysfsNodeChanged(const std::string& sysfsNodePath) = 0; +}; + // --- TouchAffineTransformation --- struct TouchAffineTransformation { diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index b5ee0445c2..b86906b2cd 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -180,7 +180,7 @@ void InputDevice::removeEventHubDevice(int32_t eventHubId) { std::list InputDevice::configure(nsecs_t when, const InputReaderConfiguration& readerConfig, - uint32_t changes) { + ConfigurationChanges changes) { std::list out; mSources = 0; mClasses = ftl::Flags(0); @@ -201,12 +201,14 @@ std::list InputDevice::configure(nsecs_t when, mIsExternal = mClasses.test(InputDeviceClass::EXTERNAL); mHasMic = mClasses.test(InputDeviceClass::MIC); + using Change = InputReaderConfiguration::Change; + if (!isIgnored()) { // Full configuration should happen the first time configure is called // and when the device type is changed. Changing a device type can // affect various other parameters so should result in a // reconfiguration. - if (!changes || (changes & InputReaderConfiguration::CHANGE_DEVICE_TYPE)) { + if (!changes.any() || changes.test(Change::DEVICE_TYPE)) { mConfiguration.clear(); for_each_subdevice([this](InputDeviceContext& context) { std::optional configuration = @@ -220,7 +222,7 @@ std::list InputDevice::configure(nsecs_t when, getValueByKey(readerConfig.deviceTypeAssociations, mIdentifier.location); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS)) { + if (!changes.any() || changes.test(Change::KEYBOARD_LAYOUTS)) { if (!mClasses.test(InputDeviceClass::VIRTUAL)) { std::shared_ptr keyboardLayout = mContext->getPolicy()->getKeyboardLayoutOverlay(mIdentifier); @@ -237,7 +239,7 @@ std::list InputDevice::configure(nsecs_t when, } } - if (!changes || (changes & InputReaderConfiguration::CHANGE_DEVICE_ALIAS)) { + if (!changes.any() || changes.test(Change::DEVICE_ALIAS)) { if (!(mClasses.test(InputDeviceClass::VIRTUAL))) { std::string alias = mContext->getPolicy()->getDeviceAlias(mIdentifier); if (mAlias != alias) { @@ -247,7 +249,7 @@ std::list InputDevice::configure(nsecs_t when, } } - if (changes & InputReaderConfiguration::CHANGE_ENABLED_STATE) { + if (changes.test(Change::ENABLED_STATE)) { // Do not execute this code on the first configure, because 'setEnabled' would call // InputMapper::reset, and you can't reset a mapper before it has been configured. // The mappers are configured for the first time at the bottom of this function. @@ -256,7 +258,7 @@ std::list InputDevice::configure(nsecs_t when, out += setEnabled(enabled, when); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { + if (!changes.any() || changes.test(Change::DISPLAY_INFO)) { // In most situations, no port or name will be specified. mAssociatedDisplayPort = std::nullopt; mAssociatedDisplayUniqueId = std::nullopt; @@ -305,7 +307,7 @@ std::list InputDevice::configure(nsecs_t when, } } - if (changes) { + if (changes.any()) { // For first-time configuration, only allow device to be disabled after mappers have // finished configuring. This is because we need to read some of the properties from // the device's open fd. @@ -320,7 +322,7 @@ std::list InputDevice::configure(nsecs_t when, // If a device is just plugged but it might be disabled, we need to update some info like // axis range of touch from each InputMapper first, then disable it. - if (!changes) { + if (!changes.any()) { out += setEnabled(readerConfig.disabledDevices.find(mId) == readerConfig.disabledDevices.end(), when); diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 0afe79dcc4..ea95f7857a 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -85,7 +85,7 @@ InputReader::InputReader(std::shared_ptr eventHub, mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX), mConfigurationChangesToRefresh(0) { - refreshConfigurationLocked(0); + refreshConfigurationLocked(/*changes=*/{}); updateGlobalMetaStateLocked(); } @@ -122,9 +122,9 @@ void InputReader::loopOnce() { oldGeneration = mGeneration; timeoutMillis = -1; - uint32_t changes = mConfigurationChangesToRefresh; - if (changes) { - mConfigurationChangesToRefresh = 0; + auto changes = mConfigurationChangesToRefresh; + if (changes.any()) { + mConfigurationChangesToRefresh.clear(); timeoutMillis = 0; refreshConfigurationLocked(changes); } else if (mNextTimeout != LLONG_MAX) { @@ -236,7 +236,7 @@ void InputReader::addDeviceLocked(nsecs_t when, int32_t eventHubId) { InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(eventHubId); std::shared_ptr device = createDeviceLocked(eventHubId, identifier); - notifyAll(device->configure(when, mConfig, 0)); + notifyAll(device->configure(when, mConfig, /*changes=*/{})); notifyAll(device->reset(when)); if (device->isIgnored()) { @@ -312,7 +312,7 @@ void InputReader::removeDeviceLocked(nsecs_t when, int32_t eventHubId) { std::list resetEvents; if (device->hasEventHubDevices()) { - resetEvents += device->configure(when, mConfig, 0); + resetEvents += device->configure(when, mConfig, /*changes=*/{}); } resetEvents += device->reset(when); notifyAll(std::move(resetEvents)); @@ -390,21 +390,21 @@ void InputReader::handleConfigurationChangedLocked(nsecs_t when) { mQueuedListener.notifyConfigurationChanged({mContext.getNextId(), when}); } -void InputReader::refreshConfigurationLocked(uint32_t changes) { +void InputReader::refreshConfigurationLocked(ConfigurationChanges changes) { mPolicy->getReaderConfiguration(&mConfig); mEventHub->setExcludedDevices(mConfig.excludedDeviceNames); - if (!changes) return; + using Change = InputReaderConfiguration::Change; + if (!changes.any()) return; - ALOGI("Reconfiguring input devices, changes=%s", - InputReaderConfiguration::changesToString(changes).c_str()); + ALOGI("Reconfiguring input devices, changes=%s", changes.string().c_str()); nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); - if (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO) { + if (changes.test(Change::DISPLAY_INFO)) { updatePointerDisplayLocked(); } - if (changes & InputReaderConfiguration::CHANGE_MUST_REOPEN) { + if (changes.test(Change::MUST_REOPEN)) { mEventHub->requestReopenDevices(); } else { for (auto& devicePair : mDevices) { @@ -413,7 +413,7 @@ void InputReader::refreshConfigurationLocked(uint32_t changes) { } } - if (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE) { + if (changes.test(Change::POINTER_CAPTURE)) { if (mCurrentPointerCaptureRequest == mConfig.pointerCaptureRequest) { ALOGV("Skipping notifying pointer capture changes: " "There was no change in the pointer capture state."); @@ -457,7 +457,7 @@ int32_t InputReader::getLedMetaStateLocked() { } void InputReader::notifyExternalStylusPresenceChangedLocked() { - refreshConfigurationLocked(InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE); + refreshConfigurationLocked(InputReaderConfiguration::Change::EXTERNAL_STYLUS_PRESENCE); } void InputReader::getExternalStylusDevicesLocked(std::vector& outDevices) { @@ -671,11 +671,11 @@ int32_t InputReader::getKeyCodeForKeyLocation(int32_t deviceId, int32_t location return device->getKeyCodeForKeyLocation(locationKeyCode); } -void InputReader::requestRefreshConfiguration(uint32_t changes) { +void InputReader::requestRefreshConfiguration(ConfigurationChanges changes) { std::scoped_lock _l(mLock); - if (changes) { - bool needWake = !mConfigurationChangesToRefresh; + if (changes.any()) { + bool needWake = !mConfigurationChangesToRefresh.any(); mConfigurationChangesToRefresh |= changes; if (needWake) { diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 2091e16cf7..73351c407a 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -85,7 +85,7 @@ public: void removeEventHubDevice(int32_t eventHubId); [[nodiscard]] std::list configure(nsecs_t when, const InputReaderConfiguration& readerConfig, - uint32_t changes); + ConfigurationChanges changes); [[nodiscard]] std::list reset(nsecs_t when); [[nodiscard]] std::list process(const RawEvent* rawEvents, size_t count); [[nodiscard]] std::list timeoutExpired(nsecs_t when); diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h index 120e15070c..9112913565 100644 --- a/services/inputflinger/reader/include/InputReader.h +++ b/services/inputflinger/reader/include/InputReader.h @@ -77,7 +77,7 @@ public: bool hasKeys(int32_t deviceId, uint32_t sourceMask, const std::vector& keyCodes, uint8_t* outFlags) override; - void requestRefreshConfiguration(uint32_t changes) override; + void requestRefreshConfiguration(ConfigurationChanges changes) override; void vibrate(int32_t deviceId, const VibrationSequence& sequence, ssize_t repeat, int32_t token) override; @@ -233,8 +233,8 @@ private: nsecs_t mNextTimeout GUARDED_BY(mLock); void requestTimeoutAtTimeLocked(nsecs_t when) REQUIRES(mLock); - uint32_t mConfigurationChangesToRefresh GUARDED_BY(mLock); - void refreshConfigurationLocked(uint32_t changes) REQUIRES(mLock); + ConfigurationChanges mConfigurationChangesToRefresh GUARDED_BY(mLock); + void refreshConfigurationLocked(ConfigurationChanges changes) REQUIRES(mLock); void notifyAll(std::list&& argsList); diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index adc893b12b..8ef5ff6358 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -139,26 +139,26 @@ void CursorInputMapper::dump(std::string& dump) { std::list CursorInputMapper::reconfigure(nsecs_t when, const InputReaderConfiguration& readerConfig, - uint32_t changes) { + ConfigurationChanges changes) { std::list out = InputMapper::reconfigure(when, readerConfig, changes); - if (!changes) { + if (!changes.any()) { configureWithZeroChanges(readerConfig); return out; } const bool configurePointerCapture = mParameters.mode != Parameters::Mode::NAVIGATION && - (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE); + changes.test(InputReaderConfiguration::Change::POINTER_CAPTURE); if (configurePointerCapture) { configureOnPointerCapture(readerConfig); out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId())); } - if ((changes & InputReaderConfiguration::CHANGE_POINTER_SPEED) || configurePointerCapture) { + if (changes.test(InputReaderConfiguration::Change::POINTER_SPEED) || configurePointerCapture) { configureOnChangePointerSpeed(readerConfig); } - if ((changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO) || configurePointerCapture) { + if (changes.test(InputReaderConfiguration::Change::DISPLAY_INFO) || configurePointerCapture) { configureOnChangeDisplayInfo(readerConfig); } return out; diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index a7cdd51df1..caf2e5a4c4 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -62,7 +62,7 @@ public: virtual void dump(std::string& dump) override; [[nodiscard]] std::list reconfigure(nsecs_t when, const InputReaderConfiguration& readerConfig, - uint32_t changes) override; + ConfigurationChanges changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp index 7100f88e79..987d2d0221 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp @@ -49,7 +49,7 @@ void ExternalStylusInputMapper::dump(std::string& dump) { std::list ExternalStylusInputMapper::reconfigure(nsecs_t when, const InputReaderConfiguration& config, - uint32_t changes) { + ConfigurationChanges changes) { getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPressureAxis); mTouchButtonAccumulator.configure(); return {}; diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h index 7f926a72e7..841c437543 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h @@ -35,7 +35,7 @@ public: void dump(std::string& dump) override; [[nodiscard]] std::list reconfigure(nsecs_t when, const InputReaderConfiguration& config, - uint32_t changes) override; + ConfigurationChanges changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp index bd86a5a539..0692dbbe0a 100644 --- a/services/inputflinger/reader/mapper/InputMapper.cpp +++ b/services/inputflinger/reader/mapper/InputMapper.cpp @@ -38,7 +38,7 @@ void InputMapper::populateDeviceInfo(InputDeviceInfo& info) { void InputMapper::dump(std::string& dump) {} std::list InputMapper::reconfigure(nsecs_t when, const InputReaderConfiguration& config, - uint32_t changes) { + ConfigurationChanges changes) { return {}; } diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h index 211be9fd32..f017317485 100644 --- a/services/inputflinger/reader/mapper/InputMapper.h +++ b/services/inputflinger/reader/mapper/InputMapper.h @@ -55,7 +55,7 @@ public: virtual void dump(std::string& dump); [[nodiscard]] virtual std::list reconfigure(nsecs_t when, const InputReaderConfiguration& config, - uint32_t changes); + ConfigurationChanges changes); [[nodiscard]] virtual std::list reset(nsecs_t when); [[nodiscard]] virtual std::list process(const RawEvent* rawEvent) = 0; [[nodiscard]] virtual std::list timeoutExpired(nsecs_t when); diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp index 3450f86fbf..099a95541e 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp @@ -106,10 +106,10 @@ void JoystickInputMapper::dump(std::string& dump) { std::list JoystickInputMapper::reconfigure(nsecs_t when, const InputReaderConfiguration& config, - uint32_t changes) { + ConfigurationChanges changes) { std::list out = InputMapper::reconfigure(when, config, changes); - if (!changes) { // first time only + if (!changes.any()) { // first time only // Collect all axes. for (int32_t abs = 0; abs <= ABS_MAX; abs++) { if (!(getAbsAxisUsage(abs, getDeviceContext().getDeviceClasses()) diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.h b/services/inputflinger/reader/mapper/JoystickInputMapper.h index c9e92defbf..49673a2f4e 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.h +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h @@ -31,7 +31,7 @@ public: virtual void dump(std::string& dump) override; [[nodiscard]] std::list reconfigure(nsecs_t when, const InputReaderConfiguration& config, - uint32_t changes) override; + ConfigurationChanges changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index 63ea3a877e..582fb46550 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -134,19 +134,20 @@ std::optional KeyboardInputMapper::findViewport( std::list KeyboardInputMapper::reconfigure(nsecs_t when, const InputReaderConfiguration& config, - uint32_t changes) { + ConfigurationChanges changes) { std::list out = InputMapper::reconfigure(when, config, changes); - if (!changes) { // first time only + if (!changes.any()) { // first time only // Configure basic parameters. configureParameters(); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { + if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) { mViewport = findViewport(config); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUT_ASSOCIATION)) { + if (!changes.any() || + changes.test(InputReaderConfiguration::Change::KEYBOARD_LAYOUT_ASSOCIATION)) { mKeyboardLayoutInfo = getValueByKey(config.keyboardLayoutAssociations, getDeviceContext().getLocation()); } diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index 25fad57e31..bd27383296 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -33,7 +33,7 @@ public: void dump(std::string& dump) override; [[nodiscard]] std::list reconfigure(nsecs_t when, const InputReaderConfiguration& config, - uint32_t changes) override; + ConfigurationChanges changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp index 2ffa5cd3e0..13f2e59db9 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp @@ -66,12 +66,12 @@ void RotaryEncoderInputMapper::dump(std::string& dump) { std::list RotaryEncoderInputMapper::reconfigure(nsecs_t when, const InputReaderConfiguration& config, - uint32_t changes) { + ConfigurationChanges changes) { std::list out = InputMapper::reconfigure(when, config, changes); - if (!changes) { + if (!changes.any()) { mRotaryEncoderScrollAccumulator.configure(getDeviceContext()); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { + if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) { std::optional internalViewport = config.getDisplayViewportByType(ViewportType::INTERNAL); if (internalViewport) { diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h index 8feaf8e3bc..d3dcbe1bb4 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h @@ -34,7 +34,7 @@ public: virtual void dump(std::string& dump) override; [[nodiscard]] std::list reconfigure(nsecs_t when, const InputReaderConfiguration& config, - uint32_t changes) override; + ConfigurationChanges changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp index f7f23a41a7..a131e3598f 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp @@ -119,10 +119,10 @@ void SensorInputMapper::dump(std::string& dump) { std::list SensorInputMapper::reconfigure(nsecs_t when, const InputReaderConfiguration& config, - uint32_t changes) { + ConfigurationChanges changes) { std::list out = InputMapper::reconfigure(when, config, changes); - if (!changes) { // first time only + if (!changes.any()) { // first time only mDeviceEnabled = true; // Check if device has MSC_TIMESTAMP event. mHasHardwareTimestamp = getDeviceContext().hasMscEvent(MSC_TIMESTAMP); diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h index 2f3a39665d..1f82559db8 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.h +++ b/services/inputflinger/reader/mapper/SensorInputMapper.h @@ -36,7 +36,7 @@ public: void dump(std::string& dump) override; [[nodiscard]] std::list reconfigure(nsecs_t when, const InputReaderConfiguration& config, - uint32_t changes) override; + ConfigurationChanges changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; bool enableSensor(InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod, diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 60bf857318..55b869a735 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -290,7 +290,7 @@ void TouchInputMapper::dump(std::string& dump) { std::list TouchInputMapper::reconfigure(nsecs_t when, const InputReaderConfiguration& config, - uint32_t changes) { + ConfigurationChanges changes) { std::list out = InputMapper::reconfigure(when, config, changes); mConfig = config; @@ -298,7 +298,7 @@ std::list TouchInputMapper::reconfigure(nsecs_t when, // Full configuration should happen the first time configure is called and // when the device type is changed. Changing a device type can affect // various other parameters so should result in a reconfiguration. - if (!changes || (changes & InputReaderConfiguration::CHANGE_DEVICE_TYPE)) { + if (!changes.any() || changes.test(InputReaderConfiguration::Change::DEVICE_TYPE)) { // Configure basic parameters. configureParameters(); @@ -314,33 +314,34 @@ std::list TouchInputMapper::reconfigure(nsecs_t when, resolveCalibration(); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_TOUCH_AFFINE_TRANSFORMATION)) { + if (!changes.any() || + changes.test(InputReaderConfiguration::Change::TOUCH_AFFINE_TRANSFORMATION)) { // Update location calibration to reflect current settings updateAffineTransformation(); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED)) { + if (!changes.any() || changes.test(InputReaderConfiguration::Change::POINTER_SPEED)) { // Update pointer speed. mPointerVelocityControl.setParameters(mConfig.pointerVelocityControlParameters); mWheelXVelocityControl.setParameters(mConfig.wheelVelocityControlParameters); mWheelYVelocityControl.setParameters(mConfig.wheelVelocityControlParameters); } + using namespace ftl::flag_operators; bool resetNeeded = false; - if (!changes || - (changes & - (InputReaderConfiguration::CHANGE_DISPLAY_INFO | - InputReaderConfiguration::CHANGE_POINTER_CAPTURE | - InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT | - InputReaderConfiguration::CHANGE_SHOW_TOUCHES | - InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE | - InputReaderConfiguration::CHANGE_DEVICE_TYPE))) { + if (!changes.any() || + changes.any(InputReaderConfiguration::Change::DISPLAY_INFO | + InputReaderConfiguration::Change::POINTER_CAPTURE | + InputReaderConfiguration::Change::POINTER_GESTURE_ENABLEMENT | + InputReaderConfiguration::Change::SHOW_TOUCHES | + InputReaderConfiguration::Change::EXTERNAL_STYLUS_PRESENCE | + InputReaderConfiguration::Change::DEVICE_TYPE)) { // Configure device sources, display dimensions, orientation and // scaling factors. configureInputDevice(when, &resetNeeded); } - if (changes && resetNeeded) { + if (changes.any() && resetNeeded) { out += reset(when); // Send reset, unless this is the first time the device has been configured, diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index e7d66a1c4d..e023e90758 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -155,7 +155,7 @@ public: void dump(std::string& dump) override; [[nodiscard]] std::list reconfigure(nsecs_t when, const InputReaderConfiguration& config, - uint32_t changes) override; + ConfigurationChanges changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 5a1ced486a..8753b487e3 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -222,13 +222,13 @@ void TouchpadInputMapper::dump(std::string& dump) { std::list TouchpadInputMapper::reconfigure(nsecs_t when, const InputReaderConfiguration& config, - uint32_t changes) { - if (!changes) { + ConfigurationChanges changes) { + if (!changes.any()) { // First time configuration mPropertyProvider.loadPropertiesFromIdcFile(getDeviceContext().getConfiguration()); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { + if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) { std::optional displayId = mPointerController->getDisplayId(); ui::Rotation orientation = ui::ROTATION_0; if (displayId.has_value()) { @@ -238,7 +238,7 @@ std::list TouchpadInputMapper::reconfigure(nsecs_t when, } mGestureConverter.setOrientation(orientation); } - if (!changes || (changes & InputReaderConfiguration::CHANGE_TOUCHPAD_SETTINGS)) { + if (!changes.any() || changes.test(InputReaderConfiguration::Change::TOUCHPAD_SETTINGS)) { mPropertyProvider.getProperty("Use Custom Touchpad Pointer Accel Curve") .setBoolValues({true}); GesturesProp accelCurveProp = mPropertyProvider.getProperty("Pointer Accel Curve"); diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index e05109730a..268b275584 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -47,7 +47,7 @@ public: [[nodiscard]] std::list reconfigure(nsecs_t when, const InputReaderConfiguration& config, - uint32_t changes) override; + ConfigurationChanges changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; [[nodiscard]] std::list process(const RawEvent* rawEvent) override; diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp index ae300066d2..ad48a79731 100644 --- a/services/inputflinger/tests/InputMapperTest.cpp +++ b/services/inputflinger/tests/InputMapperTest.cpp @@ -50,12 +50,12 @@ void InputMapperTest::addConfigurationProperty(const char* key, const char* valu mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, key, value); } -std::list InputMapperTest::configureDevice(uint32_t changes) { - if (!changes || - (changes & - (InputReaderConfiguration::CHANGE_DISPLAY_INFO | - InputReaderConfiguration::CHANGE_POINTER_CAPTURE | - InputReaderConfiguration::CHANGE_DEVICE_TYPE))) { +std::list InputMapperTest::configureDevice(ConfigurationChanges changes) { + using namespace ftl::flag_operators; + if (!changes.any() || + (changes.any(InputReaderConfiguration::Change::DISPLAY_INFO | + InputReaderConfiguration::Change::POINTER_CAPTURE | + InputReaderConfiguration::Change::DEVICE_TYPE))) { mReader->requestRefreshConfiguration(changes); mReader->loopOnce(); } @@ -94,7 +94,7 @@ void InputMapperTest::setDisplayInfoAndReconfigure(int32_t displayId, int32_t wi ViewportType viewportType) { mFakePolicy->addDisplayViewport(displayId, width, height, orientation, /* isActive= */ true, uniqueId, physicalPort, viewportType); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); } void InputMapperTest::clearViewports() { diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h index df601ea920..d9690349fb 100644 --- a/services/inputflinger/tests/InputMapperTest.h +++ b/services/inputflinger/tests/InputMapperTest.h @@ -54,7 +54,7 @@ protected: void TearDown() override; void addConfigurationProperty(const char* key, const char* value); - std::list configureDevice(uint32_t changes); + std::list configureDevice(ConfigurationChanges changes); std::shared_ptr newDevice(int32_t deviceId, const std::string& name, const std::string& location, int32_t eventHubId, ftl::Flags classes, int bus = 0); @@ -62,7 +62,7 @@ protected: T& addMapperAndConfigure(Args... args) { T& mapper = mDevice->addMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), args...); - configureDevice(0); + configureDevice(/*changes=*/{}); std::list resetArgList = mDevice->reset(ARBITRARY_TIME); resetArgList += mapper.reset(ARBITRARY_TIME); // Loop the reader to flush the input listener queue. diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index da3fe5bc4f..0b505045bd 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -265,13 +265,13 @@ private: } std::list reconfigure(nsecs_t, const InputReaderConfiguration& config, - uint32_t changes) override { + ConfigurationChanges changes) override { std::scoped_lock lock(mLock); mConfigureWasCalled = true; // Find the associated viewport if exist. const std::optional displayPort = getDeviceContext().getAssociatedDisplayPort(); - if (displayPort && (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { + if (displayPort && changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) { mViewport = config.getDisplayViewportByPort(*displayPort); } @@ -610,12 +610,12 @@ protected: void disableDevice(int32_t deviceId) { mFakePolicy->addDisabledDevice(deviceId); - mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_ENABLED_STATE); + mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::ENABLED_STATE); } void enableDevice(int32_t deviceId) { mFakePolicy->removeDisabledDevice(deviceId); - mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_ENABLED_STATE); + mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::ENABLED_STATE); } FakeInputMapper& addDeviceWithFakeInputMapper(int32_t deviceId, int32_t eventHubId, @@ -1043,7 +1043,7 @@ TEST_F(InputReaderTest, Device_CanDispatchToDisplay) { mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, /*isActive=*/true, "local:1", hdmi1, ViewportType::EXTERNAL); - mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::DISPLAY_INFO); mReader->loopOnce(); // Add the device, and make sure all of the callbacks are triggered. @@ -1142,21 +1142,21 @@ TEST_F(InputReaderTest, ChangingPointerCaptureNotifiesInputListener) { NotifyPointerCaptureChangedArgs args; auto request = mFakePolicy->setPointerCapture(true); - mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); + mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE); mReader->loopOnce(); mFakeListener->assertNotifyCaptureWasCalled(&args); ASSERT_TRUE(args.request.enable) << "Pointer Capture should be enabled."; ASSERT_EQ(args.request, request) << "Pointer Capture sequence number should match."; mFakePolicy->setPointerCapture(false); - mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); + mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE); mReader->loopOnce(); mFakeListener->assertNotifyCaptureWasCalled(&args); ASSERT_FALSE(args.request.enable) << "Pointer Capture should be disabled."; // Verify that the Pointer Capture state is not updated when the configuration value // does not change. - mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); + mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE); mReader->loopOnce(); mFakeListener->assertNotifyCaptureWasNotCalled(); } @@ -1527,7 +1527,7 @@ protected: ViewportType viewportType) { mFakePolicy->addDisplayViewport(displayId, width, height, orientation, /*isActive=*/true, uniqueId, physicalPort, viewportType); - mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::DISPLAY_INFO); } void assertReceivedMotion(int32_t action, const std::vector& points) { @@ -2070,7 +2070,7 @@ TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonsWithinTouchGesture TYPED_TEST(StylusButtonIntegrationTest, DISABLED_StylusButtonMotionEventsDisabled) { TestFixture::mFakePolicy->setStylusButtonMotionEventsEnabled(false); TestFixture::mReader->requestRefreshConfiguration( - InputReaderConfiguration::CHANGE_STYLUS_BUTTON_REPORTING); + InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING); const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint(); const auto touchscreenId = TestFixture::mTouchscreenInfo.getId(); @@ -2341,7 +2341,7 @@ TEST_F(InputDeviceTest, WhenDeviceCreated_EnabledIsFalse) { TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) { // Configuration. InputReaderConfiguration config; - std::list unused = mDevice->configure(ARBITRARY_TIME, config, 0); + std::list unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{}); // Reset. unused += mDevice->reset(ARBITRARY_TIME); @@ -2402,7 +2402,7 @@ TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRe mapper2.setMetaState(AMETA_SHIFT_ON); InputReaderConfiguration config; - std::list unused = mDevice->configure(ARBITRARY_TIME, config, 0); + std::list unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{}); std::optional propertyValue = mDevice->getConfiguration().getString("key"); ASSERT_TRUE(propertyValue.has_value()) @@ -2484,7 +2484,8 @@ TEST_F(InputDeviceTest, Configure_AssignsDisplayPort) { // First Configuration. std::list unused = - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + /*changes=*/{}); // Device should be enabled by default. ASSERT_TRUE(mDevice->isEnabled()); @@ -2495,7 +2496,7 @@ TEST_F(InputDeviceTest, Configure_AssignsDisplayPort) { mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi); unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + InputReaderConfiguration::Change::DISPLAY_INFO); // Device should be disabled because it is associated with a specific display via // input port <-> display port association, but the corresponding display is not found ASSERT_FALSE(mDevice->isEnabled()); @@ -2505,18 +2506,18 @@ TEST_F(InputDeviceTest, Configure_AssignsDisplayPort) { ui::ROTATION_0, /*isActive=*/true, UNIQUE_ID, hdmi, ViewportType::INTERNAL); unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + InputReaderConfiguration::Change::DISPLAY_INFO); ASSERT_TRUE(mDevice->isEnabled()); // Device should be disabled after set disable. mFakePolicy->addDisabledDevice(mDevice->getId()); unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_ENABLED_STATE); + InputReaderConfiguration::Change::ENABLED_STATE); ASSERT_FALSE(mDevice->isEnabled()); // Device should still be disabled even found the associated display. unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + InputReaderConfiguration::Change::DISPLAY_INFO); ASSERT_FALSE(mDevice->isEnabled()); } @@ -2526,14 +2527,15 @@ TEST_F(InputDeviceTest, Configure_AssignsDisplayUniqueId) { mDevice->addMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), AINPUT_SOURCE_KEYBOARD); std::list unused = - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + /*changes=*/{}); ASSERT_TRUE(mDevice->isEnabled()); // Device should be disabled because it is associated with a specific display, but the // corresponding display is not found. mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + InputReaderConfiguration::Change::DISPLAY_INFO); ASSERT_FALSE(mDevice->isEnabled()); // Device should be enabled when a display is found. @@ -2541,18 +2543,18 @@ TEST_F(InputDeviceTest, Configure_AssignsDisplayUniqueId) { ui::ROTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + InputReaderConfiguration::Change::DISPLAY_INFO); ASSERT_TRUE(mDevice->isEnabled()); // Device should be disabled after set disable. mFakePolicy->addDisabledDevice(mDevice->getId()); unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_ENABLED_STATE); + InputReaderConfiguration::Change::ENABLED_STATE); ASSERT_FALSE(mDevice->isEnabled()); // Device should still be disabled even found the associated display. unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + InputReaderConfiguration::Change::DISPLAY_INFO); ASSERT_FALSE(mDevice->isEnabled()); } @@ -2561,14 +2563,15 @@ TEST_F(InputDeviceTest, Configure_UniqueId_CorrectlyMatches) { mDevice->addMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), AINPUT_SOURCE_KEYBOARD); std::list unused = - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + /*changes=*/{}); mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID); mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + InputReaderConfiguration::Change::DISPLAY_INFO); ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueId()); } @@ -3423,7 +3426,7 @@ TEST_F(KeyboardInputMapperTest, Configure_AssignsDisplayPort) { AINPUT_KEYBOARD_TYPE_ALPHABETIC); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - /*changes=*/0); + /*changes=*/{}); unused += device2->reset(ARBITRARY_TIME); // Prepared displays and associated info. @@ -3436,7 +3439,7 @@ TEST_F(KeyboardInputMapperTest, Configure_AssignsDisplayPort) { // No associated display viewport found, should disable the device. unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + InputReaderConfiguration::Change::DISPLAY_INFO); ASSERT_FALSE(device2->isEnabled()); // Prepare second display. @@ -3447,7 +3450,7 @@ TEST_F(KeyboardInputMapperTest, Configure_AssignsDisplayPort) { SECONDARY_UNIQUE_ID, hdmi2, ViewportType::EXTERNAL); // Default device will reconfigure above, need additional reconfiguration for another device. unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO); + InputReaderConfiguration::Change::DISPLAY_INFO); // Device should be enabled after the associated display is found. ASSERT_TRUE(mDevice->isEnabled()); @@ -3535,7 +3538,7 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleAfterReattach) { AINPUT_KEYBOARD_TYPE_ALPHABETIC); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - /*changes=*/0); + /*changes=*/{}); unused += device2->reset(ARBITRARY_TIME); ASSERT_TRUE(mFakeEventHub->getLedState(SECOND_EVENTHUB_ID, LED_CAPSL)); @@ -3598,7 +3601,7 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleInMultiDevices) { AINPUT_KEYBOARD_TYPE_ALPHABETIC); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - /*changes=*/0); + /*changes=*/{}); unused += device2->reset(ARBITRARY_TIME); // Initial metastate is AMETA_NONE. @@ -3667,7 +3670,7 @@ TEST_F(KeyboardInputMapperTest, Process_DisabledDevice) { // Disable device, it should synthesize cancellation events for down events. mFakePolicy->addDisabledDevice(DEVICE_ID); - configureDevice(InputReaderConfiguration::CHANGE_ENABLED_STATE); + configureDevice(InputReaderConfiguration::Change::ENABLED_STATE); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action); @@ -3681,12 +3684,13 @@ TEST_F(KeyboardInputMapperTest, Configure_AssignKeyboardLayoutInfo) { AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); std::list unused = - mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0); + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + /*changes=*/{}); mFakePolicy->addKeyboardLayoutAssociation(DEVICE_LOCATION, DEVICE_KEYBOARD_LAYOUT_INFO); unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUT_ASSOCIATION); + InputReaderConfiguration::Change::KEYBOARD_LAYOUT_ASSOCIATION); InputDeviceInfo deviceInfo = mDevice->getDeviceInfo(); ASSERT_EQ(DEVICE_KEYBOARD_LAYOUT_INFO.languageTag, @@ -3703,7 +3707,7 @@ TEST_F(KeyboardInputMapperTest, LayoutInfoCorrectlyMapped) { addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); InputReaderConfiguration config; - std::list unused = mDevice->configure(ARBITRARY_TIME, config, 0); + std::list unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{}); ASSERT_EQ("en", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->languageTag); ASSERT_EQ("extended", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->layoutType); @@ -4509,7 +4513,7 @@ TEST_F(CursorInputMapperTest, Process_PointerCapture) { // and events are generated the usual way. const uint32_t generation = mReader->getContext()->getGeneration(); mFakePolicy->setPointerCapture(false); - configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); + configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE); ASSERT_TRUE(mReader->getContext()->getGeneration() != generation); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); @@ -4558,7 +4562,7 @@ TEST_F(CursorInputMapperTest, PointerCaptureDisablesVelocityProcessing) { // Enable Pointer Capture mFakePolicy->setPointerCapture(true); - configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); + configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE); NotifyPointerCaptureChangedArgs captureArgs; ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs)); ASSERT_TRUE(captureArgs.request.enable); @@ -4600,7 +4604,7 @@ TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) { // Enable Pointer Capture. mFakePolicy->setPointerCapture(true); - configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); + configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE); NotifyPointerCaptureChangedArgs captureArgs; ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs)); ASSERT_TRUE(captureArgs.request.enable); @@ -4626,7 +4630,7 @@ TEST_F(CursorInputMapperTest, ConfigureDisplayId_NoAssociatedViewport) { // The InputDevice is not associated with any display. prepareSecondaryDisplay(); mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); mFakePointerController->setPosition(100, 200); @@ -4653,7 +4657,7 @@ TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) { prepareSecondaryDisplay(); mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); mFakePointerController->setPosition(100, 200); @@ -4678,7 +4682,7 @@ TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPoint // Associate the InputDevice with the secondary display. prepareSecondaryDisplay(); mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); // The mapper should not generate any events because it is associated with a display that is // different from the pointer display. @@ -5845,7 +5849,7 @@ TEST_F(SingleTouchInputMapperTest, Process_IgnoresTouchesOutsidePhysicalFrame) { viewport->physicalRight = 30; viewport->physicalBottom = 610; mFakePolicy->updateViewport(*viewport); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); // Start the touch. process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 1); @@ -6570,7 +6574,7 @@ TEST_F(SingleTouchInputMapperTest, auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); viewport->isActive = false; mFakePolicy->updateViewport(*viewport); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); // We should receive a cancel event for the ongoing gesture. ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); @@ -6595,7 +6599,7 @@ TEST_F(SingleTouchInputMapperTest, // Make the viewport active again. The device should resume processing events. viewport->isActive = true; mFakePolicy->updateViewport(*viewport); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); // The device is reset because it changes back to direct mode, without generating any events. ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); @@ -6763,7 +6767,7 @@ TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsChangedToTouchNavigation_upda // Send update to the mapper. std::list unused2 = mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DEVICE_TYPE /*changes*/); + InputReaderConfiguration::Change::DEVICE_TYPE /*changes*/); // Check whether device type update was successful. ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mDevice->getSources()); @@ -6784,7 +6788,7 @@ TEST_F(SingleTouchInputMapperTest, HoverEventsOutsidePhysicalFrameAreIgnored) { viewport->physicalRight = DISPLAY_WIDTH / 2; viewport->physicalBottom = DISPLAY_HEIGHT / 2; mFakePolicy->updateViewport(*viewport); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); SingleTouchInputMapper& mapper = addMapperAndConfigure(); @@ -6873,7 +6877,7 @@ public: v.uniqueId = UNIQUE_ID; v.type = ViewportType::INTERNAL; mFakePolicy->updateViewport(v); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); } void assertReceivedMove(const Point& point) { @@ -7233,7 +7237,7 @@ public: mStylusState.pressure = 0.f; mStylusState.toolType = ToolType::STYLUS; mReader->getContext()->setExternalStylusDevices({mExternalStylusDeviceInfo}); - configureDevice(InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE); + configureDevice(InputReaderConfiguration::Change::EXTERNAL_STYLUS_PRESENCE); processExternalStylusState(mapper); return mapper; } @@ -9289,7 +9293,7 @@ TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreDropped) { // Don't set touch.enableForInactiveViewport to verify the default behavior. mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, /*isActive=*/false, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); prepareAxes(POSITION); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -9309,7 +9313,7 @@ TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreProcessed) { addConfigurationProperty("touch.enableForInactiveViewport", "1"); mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, /*isActive=*/false, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); prepareAxes(POSITION); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -9331,7 +9335,7 @@ TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_AbortTouches) { ASSERT_TRUE(optionalDisplayViewport.has_value()); DisplayViewport displayViewport = *optionalDisplayViewport; - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); prepareAxes(POSITION); MultiTouchInputMapper& mapper = addMapperAndConfigure(); @@ -9347,7 +9351,7 @@ TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_AbortTouches) { // Deactivate display viewport displayViewport.isActive = false; ASSERT_TRUE(mFakePolicy->updateViewport(displayViewport)); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); // The ongoing touch should be canceled immediately ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); @@ -9362,7 +9366,7 @@ TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_AbortTouches) { // Reactivate display viewport displayViewport.isActive = true; ASSERT_TRUE(mFakePolicy->updateViewport(displayViewport)); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); // Finger move again starts new gesture x += 10, y += 10; @@ -9405,7 +9409,7 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { mFakePolicy->getReaderConfiguration()); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - /*changes=*/0); + /*changes=*/{}); unused += device2->reset(ARBITRARY_TIME); // Setup PointerController. @@ -9426,8 +9430,8 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { // Default device will reconfigure above, need additional reconfiguration for another device. unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_DISPLAY_INFO | - InputReaderConfiguration::CHANGE_SHOW_TOUCHES); + InputReaderConfiguration::Change::DISPLAY_INFO | + InputReaderConfiguration::Change::SHOW_TOUCHES); // Two fingers down at default display. int32_t x1 = 100, y1 = 125, x2 = 300, y2 = 500; @@ -9458,7 +9462,7 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { // Disable the show touches configuration and ensure the spots are cleared. mFakePolicy->setShowTouches(false); unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::CHANGE_SHOW_TOUCHES); + InputReaderConfiguration::Change::SHOW_TOUCHES); ASSERT_TRUE(fakePointerController->getSpots().empty()); } @@ -10368,7 +10372,7 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) { // non captured touchpad should be a mouse source mFakePolicy->setPointerCapture(false); - configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); + configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); } @@ -10453,7 +10457,7 @@ TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) { // captured touchpad should be a touchpad device mFakePolicy->setPointerCapture(true); - configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); + configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE); ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); } @@ -10839,7 +10843,7 @@ TEST_F(MultiTouchPointerModeTest, WhenViewportActiveStatusChanged_PointerGesture auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); viewport->isActive = false; mFakePolicy->updateViewport(*viewport); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL), WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), @@ -10947,14 +10951,6 @@ protected: mFakePolicy.clear(); } - std::list configureDevice(uint32_t changes) { - if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { - mReader->requestRefreshConfiguration(changes); - mReader->loopOnce(); - } - return mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), changes); - } - std::shared_ptr newDevice(int32_t deviceId, const std::string& name, const std::string& location, int32_t eventHubId, ftl::Flags classes) { diff --git a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp index 8ed1f31535..8098ef2edd 100644 --- a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp @@ -53,12 +53,14 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { [&]() -> void { std::list unused = mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, - fdp->ConsumeIntegral()); + InputReaderConfiguration::Change( + fdp->ConsumeIntegral())); }, [&]() -> void { // Need to reconfigure with 0 or you risk a NPE. std::list unused = - mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, 0); + mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, + InputReaderConfiguration::Change(0)); InputDeviceInfo info; mapper.populateDeviceInfo(info); }, @@ -71,7 +73,8 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { // Need to reconfigure with 0 or you risk a NPE. std::list unused = - mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, 0); + mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, + InputReaderConfiguration::Change(0)); RawEvent rawEvent{fdp->ConsumeIntegral(), fdp->ConsumeIntegral(), fdp->ConsumeIntegral(), @@ -90,7 +93,8 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { [&]() -> void { // Need to reconfigure with 0 or you risk a NPE. std::list unused = - mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, 0); + mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, + InputReaderConfiguration::Change(0)); mapper.getAssociatedDisplayId(); }, })(); diff --git a/services/inputflinger/tests/fuzzers/FuzzContainer.h b/services/inputflinger/tests/fuzzers/FuzzContainer.h index d42d11c240..84ac0fd262 100644 --- a/services/inputflinger/tests/fuzzers/FuzzContainer.h +++ b/services/inputflinger/tests/fuzzers/FuzzContainer.h @@ -59,7 +59,7 @@ public: void configureDevice() { nsecs_t arbitraryTime = mFdp->ConsumeIntegral(); std::list out; - out += mFuzzDevice->configure(arbitraryTime, mPolicyConfig, 0); + out += mFuzzDevice->configure(arbitraryTime, mPolicyConfig, /*changes=*/{}); out += mFuzzDevice->reset(arbitraryTime); for (const NotifyArgs& args : out) { mFuzzListener.notify(args); diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp index baece3c110..9223287114 100644 --- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp @@ -82,7 +82,7 @@ public: return reader->hasKeys(deviceId, sourceMask, keyCodes, outFlags); } - void requestRefreshConfiguration(uint32_t changes) { + void requestRefreshConfiguration(ConfigurationChanges changes) { reader->requestRefreshConfiguration(changes); } @@ -232,7 +232,8 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { fdp->ConsumeIntegral(), keyCodes, outFlags.data()); }, [&]() -> void { - reader->requestRefreshConfiguration(fdp->ConsumeIntegral()); + reader->requestRefreshConfiguration( + InputReaderConfiguration::Change(fdp->ConsumeIntegral())); }, [&]() -> void { reader->cancelVibrate(fdp->ConsumeIntegral(), diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp index 0a6327d07a..616e870811 100644 --- a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp @@ -65,7 +65,8 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { [&]() -> void { std::list unused = mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, - fdp->ConsumeIntegral()); + InputReaderConfiguration::Change( + fdp->ConsumeIntegral())); }, [&]() -> void { std::list unused = mapper.reset(fdp->ConsumeIntegral()); diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp index fdf9f41873..212462d700 100644 --- a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp @@ -80,7 +80,8 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { [&]() -> void { std::list unused = mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, - fdp->ConsumeIntegral()); + InputReaderConfiguration::Change( + fdp->ConsumeIntegral())); }, [&]() -> void { std::list unused = mapper.reset(fdp->ConsumeIntegral()); -- GitLab From 3b88f31851e1e32236f4887c29f65c6532e0e907 Mon Sep 17 00:00:00 2001 From: Yiwei Zhang Date: Tue, 18 Apr 2023 23:11:35 +0000 Subject: [PATCH 1151/1310] swapchain: avoid redundant disconnect/connect for new surface Platform API contract ensures VkSurfaceKHR is created with a new or disconnected ANativeWindow. Then the first swapchain created against the new surface can skip the disconnect/connect of the native window, which saves 2 binder IPC calls. In theory, the same can be skipped until the first successful present call. We dirty the bit at the end of swapchain creation to avoid extra external synchronization. Bug: 275176234 Bug: 265763295 Test: no deadlock against ANGLE eglCreateWindowSurface Change-Id: Ic2d09e4547e0ec7e910863c77a4657d52e9366fd --- vulkan/libvulkan/swapchain.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp index 922a44fa05..5965953b38 100644 --- a/vulkan/libvulkan/swapchain.cpp +++ b/vulkan/libvulkan/swapchain.cpp @@ -227,6 +227,10 @@ struct Surface { android::sp window; VkSwapchainKHR swapchain_handle; uint64_t consumer_usage; + + // Indicate whether this surface has been used by a swapchain, no matter the + // swapchain is still current or has been destroyed. + bool used_by_swapchain; }; VkSurfaceKHR HandleFromSurface(Surface* surface) { @@ -601,6 +605,7 @@ VkResult CreateAndroidSurfaceKHR( surface->window = pCreateInfo->window; surface->swapchain_handle = VK_NULL_HANDLE; + surface->used_by_swapchain = false; int err = native_window_get_consumer_usage(surface->window.get(), &surface->consumer_usage); if (err != android::OK) { @@ -1394,14 +1399,20 @@ VkResult CreateSwapchainKHR(VkDevice device, // orphans the previous buffers, getting us back to the state where we can // dequeue all buffers. // + // This is not necessary if the surface was never used previously. + // // TODO(http://b/134186185) recycle swapchain images more efficiently ANativeWindow* window = surface.window.get(); - err = native_window_api_disconnect(window, NATIVE_WINDOW_API_EGL); - ALOGW_IF(err != android::OK, "native_window_api_disconnect failed: %s (%d)", - strerror(-err), err); - err = native_window_api_connect(window, NATIVE_WINDOW_API_EGL); - ALOGW_IF(err != android::OK, "native_window_api_connect failed: %s (%d)", - strerror(-err), err); + if (surface.used_by_swapchain) { + err = native_window_api_disconnect(window, NATIVE_WINDOW_API_EGL); + ALOGW_IF(err != android::OK, + "native_window_api_disconnect failed: %s (%d)", strerror(-err), + err); + err = native_window_api_connect(window, NATIVE_WINDOW_API_EGL); + ALOGW_IF(err != android::OK, + "native_window_api_connect failed: %s (%d)", strerror(-err), + err); + } err = window->perform(window, NATIVE_WINDOW_SET_DEQUEUE_TIMEOUT, nsecs_t{-1}); @@ -1787,6 +1798,7 @@ VkResult CreateSwapchainKHR(VkDevice device, android::GraphicsEnv::getInstance().setTargetStats( android::GpuStatsInfo::Stats::CREATED_VULKAN_SWAPCHAIN); + surface.used_by_swapchain = true; surface.swapchain_handle = HandleFromSwapchain(swapchain); *swapchain_handle = surface.swapchain_handle; return VK_SUCCESS; -- GitLab From 7ee4f464bf6e70007a17df078e905898efc5a80e Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 19 Apr 2023 09:54:09 -0700 Subject: [PATCH 1152/1310] [sf] Release the currently presented buffer when setBuffer is called with null Fixes a regression introduced in T which ignores a setBuffer call with a null buffer. The expected behavior should be to release the currently presented buffer from surfaceflinger. The subsequent frame will not present this layer so the region behind the layer will be composited instead. Bug: 241271897 Test: presubmit Change-Id: Ie06025c59c58cc75a267b783729996a3cbceef45 --- libs/gui/SurfaceComposerClient.cpp | 52 ++++++++----- libs/gui/include/gui/SurfaceComposerClient.h | 1 + libs/gui/include/gui/test/CallbackUtils.h | 30 +++++--- .../include/compositionengine/LayerFE.h | 3 +- .../include/compositionengine/mock/LayerFE.h | 3 +- .../CompositionEngine/src/Output.cpp | 8 +- .../CompositionEngine/tests/OutputTest.cpp | 30 +++++--- .../FrontEnd/RequestedLayerState.cpp | 40 ++++++---- .../FrontEnd/RequestedLayerState.h | 1 + services/surfaceflinger/Layer.cpp | 75 ++++++++++++++----- services/surfaceflinger/Layer.h | 17 ++++- services/surfaceflinger/LayerFE.cpp | 5 +- services/surfaceflinger/LayerFE.h | 4 +- services/surfaceflinger/SurfaceFlinger.cpp | 68 +++++++++++------ services/surfaceflinger/SurfaceFlinger.h | 1 + services/surfaceflinger/TransactionState.h | 6 +- .../fuzzer/surfaceflinger_layer_fuzzer.cpp | 9 ++- .../tests/LayerCallback_test.cpp | 71 ++++++++++++++++++ .../tests/LayerRenderTypeTransaction_test.cpp | 59 +++++++++++++++ .../unittests/TransactionApplicationTest.cpp | 34 ++++++++- 20 files changed, 402 insertions(+), 115 deletions(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 7700aa4044..eb5cc4f8ab 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -898,7 +898,7 @@ status_t SurfaceComposerClient::Transaction::writeToParcel(Parcel* parcel) const } void SurfaceComposerClient::Transaction::releaseBufferIfOverwriting(const layer_state_t& state) { - if (!(state.what & layer_state_t::eBufferChanged)) { + if (!(state.what & layer_state_t::eBufferChanged) || !state.bufferData->hasBuffer()) { return; } @@ -1642,28 +1642,25 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffe releaseBufferIfOverwriting(*s); - if (buffer == nullptr) { - s->what &= ~layer_state_t::eBufferChanged; - s->bufferData = nullptr; - return *this; - } - std::shared_ptr bufferData = std::make_shared(); bufferData->buffer = buffer; - uint64_t frameNumber = sc->resolveFrameNumber(optFrameNumber); - bufferData->frameNumber = frameNumber; - bufferData->producerId = producerId; - bufferData->flags |= BufferData::BufferDataChange::frameNumberChanged; - if (fence) { - bufferData->acquireFence = *fence; - bufferData->flags |= BufferData::BufferDataChange::fenceChanged; - } - bufferData->releaseBufferEndpoint = - IInterface::asBinder(TransactionCompletedListener::getIInstance()); + if (buffer) { + uint64_t frameNumber = sc->resolveFrameNumber(optFrameNumber); + bufferData->frameNumber = frameNumber; + bufferData->producerId = producerId; + bufferData->flags |= BufferData::BufferDataChange::frameNumberChanged; + if (fence) { + bufferData->acquireFence = *fence; + bufferData->flags |= BufferData::BufferDataChange::fenceChanged; + } + bufferData->releaseBufferEndpoint = + IInterface::asBinder(TransactionCompletedListener::getIInstance()); + setReleaseBufferCallback(bufferData.get(), callback); + } + if (mIsAutoTimestamp) { mDesiredPresentTime = systemTime(); } - setReleaseBufferCallback(bufferData.get(), callback); s->what |= layer_state_t::eBufferChanged; s->bufferData = std::move(bufferData); registerSurfaceControlForCallback(sc); @@ -1684,6 +1681,25 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffe return *this; } +SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::unsetBuffer( + const sp& sc) { + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + + if (!(s->what & layer_state_t::eBufferChanged)) { + return *this; + } + + releaseBufferIfOverwriting(*s); + + s->what &= ~layer_state_t::eBufferChanged; + s->bufferData = nullptr; + return *this; +} + void SurfaceComposerClient::Transaction::setReleaseBufferCallback(BufferData* bufferData, ReleaseBufferCallback callback) { if (!callback) { diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index d431b4381a..945b164fdc 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -541,6 +541,7 @@ public: const std::optional>& fence = std::nullopt, const std::optional& frameNumber = std::nullopt, uint32_t producerId = 0, ReleaseBufferCallback callback = nullptr); + Transaction& unsetBuffer(const sp& sc); std::shared_ptr getAndClearBuffer(const sp& sc); /** diff --git a/libs/gui/include/gui/test/CallbackUtils.h b/libs/gui/include/gui/test/CallbackUtils.h index 08785b49c1..1c900e9da5 100644 --- a/libs/gui/include/gui/test/CallbackUtils.h +++ b/libs/gui/include/gui/test/CallbackUtils.h @@ -51,6 +51,7 @@ public: enum Buffer { NOT_ACQUIRED = 0, ACQUIRED, + ACQUIRED_NULL, }; enum PreviousBuffer { @@ -133,17 +134,28 @@ private: : mBufferResult(bufferResult), mPreviousBufferResult(previousBufferResult) {} void verifySurfaceControlStats(const SurfaceControlStats& surfaceControlStats, - nsecs_t latchTime) const { + nsecs_t /* latchTime */) const { const auto& [surfaceControl, latch, acquireTimeOrFence, presentFence, previousReleaseFence, transformHint, frameEvents, ignore] = - surfaceControlStats; - - ASSERT_TRUE(std::holds_alternative(acquireTimeOrFence)); - ASSERT_EQ(std::get(acquireTimeOrFence) > 0, - mBufferResult == ExpectedResult::Buffer::ACQUIRED) - << "bad acquire time"; - ASSERT_LE(std::get(acquireTimeOrFence), latchTime) - << "acquire time should be <= latch time"; + surfaceControlStats; + + nsecs_t acquireTime = -1; + if (std::holds_alternative(acquireTimeOrFence)) { + acquireTime = std::get(acquireTimeOrFence); + } else { + auto fence = std::get>(acquireTimeOrFence); + if (fence) { + ASSERT_EQ(fence->wait(3000), NO_ERROR); + acquireTime = fence->getSignalTime(); + } + } + + if (mBufferResult == ExpectedResult::Buffer::ACQUIRED) { + ASSERT_GT(acquireTime, 0) << "acquire time should be valid"; + } else { + ASSERT_LE(acquireTime, 0) << "acquire time should not be valid"; + } + ASSERT_EQ(acquireTime > 0, mBufferResult == ExpectedResult::Buffer::ACQUIRED); if (mPreviousBufferResult == ExpectedResult::PreviousBuffer::RELEASED) { ASSERT_NE(previousReleaseFence, nullptr) diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h index 608c53a21a..ccff1eccb6 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h @@ -19,6 +19,7 @@ #include #include #include +#include "ui/LayerStack.h" // TODO(b/129481165): remove the #pragma below and fix conversion issues #pragma clang diagnostic push @@ -140,7 +141,7 @@ public: ClientCompositionTargetSettings&) const = 0; // Called after the layer is displayed to update the presentation fence - virtual void onLayerDisplayed(ftl::SharedFuture) = 0; + virtual void onLayerDisplayed(ftl::SharedFuture, ui::LayerStack layerStack) = 0; // Gets some kind of identifier for the layer for debug purposes. virtual const char* getDebugName() const = 0; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h index 12e063b5d3..15e4577ae0 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h @@ -49,7 +49,8 @@ public: std::optional( compositionengine::LayerFE::ClientCompositionTargetSettings&)); - MOCK_METHOD(void, onLayerDisplayed, (ftl::SharedFuture), (override)); + MOCK_METHOD(void, onLayerDisplayed, (ftl::SharedFuture, ui::LayerStack), + (override)); MOCK_CONST_METHOD0(getDebugName, const char*()); MOCK_CONST_METHOD0(getSequence, int32_t()); diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index d64231f9e7..793959cea6 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -1556,8 +1556,9 @@ void Output::postFramebuffer() { releaseFence = Fence::merge("LayerRelease", releaseFence, frame.clientTargetAcquireFence); } - layer->getLayerFE().onLayerDisplayed( - ftl::yield(std::move(releaseFence)).share()); + layer->getLayerFE() + .onLayerDisplayed(ftl::yield(std::move(releaseFence)).share(), + outputState.layerFilter.layerStack); } // We've got a list of layers needing fences, that are disjoint with @@ -1565,7 +1566,8 @@ void Output::postFramebuffer() { // supply them with the present fence. for (auto& weakLayer : mReleasedLayers) { if (const auto layer = weakLayer.promote()) { - layer->onLayerDisplayed(ftl::yield(frame.presentFence).share()); + layer->onLayerDisplayed(ftl::yield(frame.presentFence).share(), + outputState.layerFilter.layerStack); } } diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index aaf0f06724..9e0e7b5a53 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -3220,16 +3220,19 @@ TEST_F(OutputPostFramebufferTest, releaseFencesAreSentToLayerFE) { // are passed. This happens to work with the current implementation, but // would not survive certain calls like Fence::merge() which would return a // new instance. - EXPECT_CALL(*mLayer1.layerFE, onLayerDisplayed(_)) - .WillOnce([&layer1Fence](ftl::SharedFuture futureFenceResult) { + EXPECT_CALL(*mLayer1.layerFE, onLayerDisplayed(_, _)) + .WillOnce([&layer1Fence](ftl::SharedFuture futureFenceResult, + ui::LayerStack) { EXPECT_EQ(FenceResult(layer1Fence), futureFenceResult.get()); }); - EXPECT_CALL(*mLayer2.layerFE, onLayerDisplayed(_)) - .WillOnce([&layer2Fence](ftl::SharedFuture futureFenceResult) { + EXPECT_CALL(*mLayer2.layerFE, onLayerDisplayed(_, _)) + .WillOnce([&layer2Fence](ftl::SharedFuture futureFenceResult, + ui::LayerStack) { EXPECT_EQ(FenceResult(layer2Fence), futureFenceResult.get()); }); - EXPECT_CALL(*mLayer3.layerFE, onLayerDisplayed(_)) - .WillOnce([&layer3Fence](ftl::SharedFuture futureFenceResult) { + EXPECT_CALL(*mLayer3.layerFE, onLayerDisplayed(_, _)) + .WillOnce([&layer3Fence](ftl::SharedFuture futureFenceResult, + ui::LayerStack) { EXPECT_EQ(FenceResult(layer3Fence), futureFenceResult.get()); }); @@ -3285,16 +3288,19 @@ TEST_F(OutputPostFramebufferTest, releasedLayersSentPresentFence) { EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted()); // Each released layer should be given the presentFence. - EXPECT_CALL(*releasedLayer1, onLayerDisplayed(_)) - .WillOnce([&presentFence](ftl::SharedFuture futureFenceResult) { + EXPECT_CALL(*releasedLayer1, onLayerDisplayed(_, _)) + .WillOnce([&presentFence](ftl::SharedFuture futureFenceResult, + ui::LayerStack) { EXPECT_EQ(FenceResult(presentFence), futureFenceResult.get()); }); - EXPECT_CALL(*releasedLayer2, onLayerDisplayed(_)) - .WillOnce([&presentFence](ftl::SharedFuture futureFenceResult) { + EXPECT_CALL(*releasedLayer2, onLayerDisplayed(_, _)) + .WillOnce([&presentFence](ftl::SharedFuture futureFenceResult, + ui::LayerStack) { EXPECT_EQ(FenceResult(presentFence), futureFenceResult.get()); }); - EXPECT_CALL(*releasedLayer3, onLayerDisplayed(_)) - .WillOnce([&presentFence](ftl::SharedFuture futureFenceResult) { + EXPECT_CALL(*releasedLayer3, onLayerDisplayed(_, _)) + .WillOnce([&presentFence](ftl::SharedFuture futureFenceResult, + ui::LayerStack) { EXPECT_EQ(FenceResult(presentFence), futureFenceResult.get()); }); diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index 4dcdd964b3..040ebc1e0f 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -134,7 +134,8 @@ RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args) void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerState) { const uint32_t oldFlags = flags; const half oldAlpha = color.a; - const bool hadBufferOrSideStream = hasValidBuffer() || sidebandStream != nullptr; + const bool hadBuffer = externalTexture != nullptr; + const bool hadSideStream = sidebandStream != nullptr; const layer_state_t& clientState = resolvedComposerState.state; const bool hadBlur = hasBlur(); uint64_t clientChanges = what | layer_state_t::diff(clientState); @@ -150,23 +151,32 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta changes |= RequestedLayerState::Changes::Geometry; } } - if (clientState.what & - (layer_state_t::eBufferChanged | layer_state_t::eSidebandStreamChanged)) { - const bool hasBufferOrSideStream = hasValidBuffer() || sidebandStream != nullptr; - if (hadBufferOrSideStream != hasBufferOrSideStream) { - changes |= RequestedLayerState::Changes::Geometry | - RequestedLayerState::Changes::VisibleRegion | - RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Input; - } - } if (clientState.what & layer_state_t::eBufferChanged) { + externalTexture = resolvedComposerState.externalTexture; barrierProducerId = std::max(bufferData->producerId, barrierProducerId); barrierFrameNumber = std::max(bufferData->frameNumber, barrierFrameNumber); // TODO(b/277265947) log and flush transaction trace when we detect out of order updates - changes |= RequestedLayerState::Changes::Buffer; + + const bool hasBuffer = externalTexture != nullptr; + if (hasBuffer || hasBuffer != hadBuffer) { + changes |= RequestedLayerState::Changes::Buffer; + } + + if (hasBuffer != hadBuffer) { + changes |= RequestedLayerState::Changes::Geometry | + RequestedLayerState::Changes::VisibleRegion | + RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Input; + } } + if (clientState.what & layer_state_t::eSidebandStreamChanged) { changes |= RequestedLayerState::Changes::SidebandStream; + const bool hasSideStream = sidebandStream != nullptr; + if (hasSideStream != hadSideStream) { + changes |= RequestedLayerState::Changes::Geometry | + RequestedLayerState::Changes::VisibleRegion | + RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Input; + } } if (what & (layer_state_t::eAlphaChanged)) { if (oldAlpha == 0 || color.a == 0) { @@ -240,10 +250,6 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta // TODO(b/238781169) handle callbacks } - if (clientState.what & layer_state_t::eBufferChanged) { - externalTexture = resolvedComposerState.externalTexture; - } - if (clientState.what & layer_state_t::ePositionChanged) { requestedTransform.set(x, y); } @@ -465,6 +471,10 @@ bool RequestedLayerState::hasSidebandStreamFrame() const { return hasFrameUpdate() && sidebandStream.get(); } +bool RequestedLayerState::willReleaseBufferOnLatch() const { + return changes.test(Changes::Buffer) && !externalTexture; +} + void RequestedLayerState::clearChanges() { what = 0; changes.clear(); diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h index f15f023c43..0ef50bc60a 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h @@ -79,6 +79,7 @@ struct RequestedLayerState : layer_state_t { bool hasFrameUpdate() const; bool hasReadyFrame() const; bool hasSidebandStreamFrame() const; + bool willReleaseBufferOnLatch() const; // Layer serial number. This gives layers an explicit ordering, so we // have a stable sort order when their layer stack and Z-order are diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index a538c6d6fb..d02d4dd5c7 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -2804,7 +2804,8 @@ void Layer::callReleaseBufferCallback(const sp& l currentMaxAcquiredBufferCount); } -void Layer::onLayerDisplayed(ftl::SharedFuture futureFenceResult) { +void Layer::onLayerDisplayed(ftl::SharedFuture futureFenceResult, + ui::LayerStack layerStack) { // If we are displayed on multiple displays in a single composition cycle then we would // need to do careful tracking to enable the use of the mLastClientCompositionFence. // For example we can only use it if all the displays are client comp, and we need @@ -2834,8 +2835,7 @@ void Layer::onLayerDisplayed(ftl::SharedFuture futureFenceResult) { // transaction doesn't need a previous release fence. sp ch; for (auto& handle : mDrawingState.callbackHandles) { - if (handle->releasePreviousBuffer && - mDrawingState.releaseBufferEndpoint == handle->listener) { + if (handle->releasePreviousBuffer && mPreviousReleaseBufferEndpoint == handle->listener) { ch = handle; break; } @@ -2851,6 +2851,7 @@ void Layer::onLayerDisplayed(ftl::SharedFuture futureFenceResult) { ch->previousReleaseFences.emplace_back(std::move(futureFenceResult)); ch->name = mName; } + mPreviouslyPresentedLayerStacks.push_back(layerStack); } void Layer::onSurfaceFrameCreated( @@ -2889,8 +2890,7 @@ void Layer::releasePendingBuffer(nsecs_t dequeueReadyTime) { } for (auto& handle : mDrawingState.callbackHandles) { - if (handle->releasePreviousBuffer && - mDrawingState.releaseBufferEndpoint == handle->listener) { + if (handle->releasePreviousBuffer && mPreviousReleaseBufferEndpoint == handle->listener) { handle->previousReleaseCallbackId = mPreviousReleaseCallbackId; break; } @@ -3027,14 +3027,22 @@ bool Layer::setPosition(float x, float y) { return true; } +void Layer::resetDrawingStateBufferInfo() { + mDrawingState.producerId = 0; + mDrawingState.frameNumber = 0; + mDrawingState.releaseBufferListener = nullptr; + mDrawingState.buffer = nullptr; + mDrawingState.acquireFence = sp::make(-1); + mDrawingState.acquireFenceTime = std::make_unique(mDrawingState.acquireFence); + mCallbackHandleAcquireTimeOrFence = mDrawingState.acquireFenceTime->getSignalTime(); + mDrawingState.releaseBufferEndpoint = nullptr; +} + bool Layer::setBuffer(std::shared_ptr& buffer, const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime, bool isAutoTimestamp, std::optional dequeueTime, const FrameTimelineInfo& info) { ATRACE_FORMAT("setBuffer %s - hasBuffer=%s", getDebugName(), (buffer ? "true" : "false")); - if (!buffer) { - return false; - } const bool frameNumberChanged = bufferData.flags.test(BufferData::BufferDataChange::frameNumberChanged); @@ -3066,12 +3074,24 @@ bool Layer::setBuffer(std::shared_ptr& buffer, mLastClientCompositionFence); mLastClientCompositionFence = nullptr; } - } else { + } else if (buffer) { // if we are latching a buffer for the first time then clear the mLastLatchTime since // we don't want to incorrectly classify a frame if we miss the desired present time. updateLastLatchTime(0); } + mDrawingState.desiredPresentTime = desiredPresentTime; + mDrawingState.isAutoTimestamp = isAutoTimestamp; + mDrawingState.latchedVsyncId = info.vsyncId; + mDrawingState.modified = true; + if (!buffer) { + resetDrawingStateBufferInfo(); + setTransactionFlags(eTransactionNeeded); + mDrawingState.bufferSurfaceFrameTX = nullptr; + setFrameTimelineVsyncForBufferlessTransaction(info, postTime); + return true; + } + mDrawingState.producerId = bufferData.producerId; mDrawingState.barrierProducerId = std::max(mDrawingState.producerId, mDrawingState.barrierProducerId); @@ -3082,7 +3102,6 @@ bool Layer::setBuffer(std::shared_ptr& buffer, // TODO(b/277265947) log and flush transaction trace when we detect out of order updates mDrawingState.releaseBufferListener = bufferData.releaseBufferListener; mDrawingState.buffer = std::move(buffer); - mDrawingState.clientCacheId = bufferData.cachedBuffer; mDrawingState.acquireFence = bufferData.flags.test(BufferData::BufferDataChange::fenceChanged) ? bufferData.acquireFence : Fence::NO_FENCE; @@ -3095,15 +3114,11 @@ bool Layer::setBuffer(std::shared_ptr& buffer, } else { mCallbackHandleAcquireTimeOrFence = mDrawingState.acquireFenceTime->getSignalTime(); } - mDrawingState.latchedVsyncId = info.vsyncId; - mDrawingState.modified = true; setTransactionFlags(eTransactionNeeded); const int32_t layerId = getSequence(); mFlinger->mTimeStats->setPostTime(layerId, mDrawingState.frameNumber, getName().c_str(), mOwnerUid, postTime, getGameMode()); - mDrawingState.desiredPresentTime = desiredPresentTime; - mDrawingState.isAutoTimestamp = isAutoTimestamp; if (mFlinger->mLegacyFrontEndEnabled) { recordLayerHistoryBufferUpdate(getLayerProps()); @@ -3351,7 +3366,7 @@ void Layer::updateTexImage(nsecs_t latchTime, bool bgColorOnly) { const State& s(getDrawingState()); if (!s.buffer) { - if (bgColorOnly) { + if (bgColorOnly || mBufferInfo.mBuffer) { for (auto& handle : mDrawingState.callbackHandles) { handle->latchTime = latchTime; } @@ -3398,12 +3413,19 @@ void Layer::updateTexImage(nsecs_t latchTime, bool bgColorOnly) { } void Layer::gatherBufferInfo() { - if (!mBufferInfo.mBuffer || !mDrawingState.buffer->hasSameBuffer(*mBufferInfo.mBuffer)) { + mPreviousReleaseCallbackId = {getCurrentBufferId(), mBufferInfo.mFrameNumber}; + mPreviousReleaseBufferEndpoint = mBufferInfo.mReleaseBufferEndpoint; + if (!mDrawingState.buffer) { + mBufferInfo = {}; + return; + } + + if ((!mBufferInfo.mBuffer || !mDrawingState.buffer->hasSameBuffer(*mBufferInfo.mBuffer))) { decrementPendingBufferCount(); } - mPreviousReleaseCallbackId = {getCurrentBufferId(), mBufferInfo.mFrameNumber}; mBufferInfo.mBuffer = mDrawingState.buffer; + mBufferInfo.mReleaseBufferEndpoint = mDrawingState.releaseBufferEndpoint; mBufferInfo.mFence = mDrawingState.acquireFence; mBufferInfo.mFrameNumber = mDrawingState.frameNumber; mBufferInfo.mPixelFormat = @@ -3933,6 +3955,10 @@ void Layer::onPostComposition(const DisplayDevice* display, mBufferInfo.mFrameLatencyNeeded = false; } +bool Layer::willReleaseBufferOnLatch() const { + return !mDrawingState.buffer && mBufferInfo.mBuffer; +} + bool Layer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime) { const bool bgColorOnly = mDrawingState.bgColorLayer != nullptr; return latchBufferImpl(recomputeVisibleRegions, latchTime, bgColorOnly); @@ -3956,9 +3982,6 @@ bool Layer::latchBufferImpl(bool& recomputeVisibleRegions, nsecs_t latchTime, bo return false; } updateTexImage(latchTime, bgColorOnly); - if (mDrawingState.buffer == nullptr) { - return false; - } // Capture the old state of the layer for comparisons later BufferInfo oldBufferInfo = mBufferInfo; @@ -3967,6 +3990,18 @@ bool Layer::latchBufferImpl(bool& recomputeVisibleRegions, nsecs_t latchTime, bo mCurrentFrameNumber = mDrawingState.frameNumber; gatherBufferInfo(); + if (mBufferInfo.mBuffer) { + // We latched a buffer that will be presented soon. Clear the previously presented layer + // stack list. + mPreviouslyPresentedLayerStacks.clear(); + } + + if (mDrawingState.buffer == nullptr) { + const bool bufferReleased = oldBufferInfo.mBuffer != nullptr; + recomputeVisibleRegions = bufferReleased; + return bufferReleased; + } + if (oldBufferInfo.mBuffer == nullptr) { // the first time we receive a buffer, we need to trigger a // geometry invalidation. diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index acdd01da77..c824690e12 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -153,7 +153,6 @@ public: bool transformToDisplayInverse; Region transparentRegionHint; std::shared_ptr buffer; - client_cache_t clientCacheId; sp acquireFence; std::shared_ptr acquireFenceTime; HdrMetadata hdrMetadata; @@ -439,6 +438,12 @@ public: bool latchBufferImpl(bool& /*recomputeVisibleRegions*/, nsecs_t /*latchTime*/, bool bgColorOnly); + /* + * Returns true if the currently presented buffer will be released when this layer state + * is latched. This will return false if there is no buffer currently presented. + */ + bool willReleaseBufferOnLatch() const; + /* * Calls latchBuffer if the buffer has a frame queued and then releases the buffer. * This is used if the buffer is just latched and releases to free up the buffer @@ -521,6 +526,7 @@ public: std::shared_ptr mBuffer; uint64_t mFrameNumber; + sp mReleaseBufferEndpoint; bool mFrameLatencyNeeded{false}; float mDesiredHdrSdrRatio = 1.f; @@ -532,7 +538,7 @@ public: const compositionengine::LayerFECompositionState* getCompositionState() const; bool fenceHasSignaled() const; void onPreComposition(nsecs_t refreshStartTime); - void onLayerDisplayed(ftl::SharedFuture); + void onLayerDisplayed(ftl::SharedFuture, ui::LayerStack layerStack); void setWasClientComposed(const sp& fence) { mLastClientCompositionFence = fence; @@ -885,7 +891,10 @@ public: void setTransformHint(std::optional transformHint) { mTransformHint = transformHint; } - + // Keeps track of the previously presented layer stacks. This is used to get + // the release fences from the correct displays when we release the last buffer + // from the layer. + std::vector mPreviouslyPresentedLayerStacks; // Exposed so SurfaceFlinger can assert that it's held const sp mFlinger; @@ -1162,6 +1171,7 @@ private: half4 mBorderColor; void setTransformHintLegacy(ui::Transform::RotationFlags); + void resetDrawingStateBufferInfo(); const uint32_t mTextureName; @@ -1172,6 +1182,7 @@ private: std::optional mTransformHint = std::nullopt; ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID; + sp mPreviousReleaseBufferEndpoint; uint64_t mPreviousReleasedFrameNumber = 0; uint64_t mPreviousBarrierFrameNumber = 0; diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp index b9c8b78f55..e713263433 100644 --- a/services/surfaceflinger/LayerFE.cpp +++ b/services/surfaceflinger/LayerFE.cpp @@ -325,8 +325,9 @@ void LayerFE::prepareShadowClientComposition(LayerFE::LayerSettings& caster, caster.shadow = state; } -void LayerFE::onLayerDisplayed(ftl::SharedFuture futureFenceResult) { - mCompositionResult.releaseFences.emplace_back(std::move(futureFenceResult)); +void LayerFE::onLayerDisplayed(ftl::SharedFuture futureFenceResult, + ui::LayerStack layerStack) { + mCompositionResult.releaseFences.emplace_back(std::move(futureFenceResult), layerStack); } CompositionResult&& LayerFE::stealCompositionResult() { diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h index c23bd31d1a..d584fb7eab 100644 --- a/services/surfaceflinger/LayerFE.h +++ b/services/surfaceflinger/LayerFE.h @@ -29,7 +29,7 @@ struct CompositionResult { // TODO(b/238781169) update CE to no longer pass refreshStartTime to LayerFE::onPreComposition // and remove this field. nsecs_t refreshStartTime = 0; - std::vector> releaseFences; + std::vector, ui::LayerStack>> releaseFences; sp lastClientCompositionFence = nullptr; }; @@ -40,7 +40,7 @@ public: // compositionengine::LayerFE overrides const compositionengine::LayerFECompositionState* getCompositionState() const override; bool onPreComposition(nsecs_t refreshStartTime, bool updatingOutputGeometryThisFrame) override; - void onLayerDisplayed(ftl::SharedFuture) override; + void onLayerDisplayed(ftl::SharedFuture, ui::LayerStack) override; const char* getDebugName() const override; int32_t getSequence() const override; bool hasRoundedCorners() const override; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 8394ffbca8..5c699b1178 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2303,13 +2303,17 @@ bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, frontend::Update& upd std::make_optional(layer->parentId), true)); mLegacyLayers[bgColorLayer->sequence] = bgColorLayer; } - if (!layer->hasReadyFrame()) continue; + const bool willReleaseBufferOnLatch = layer->willReleaseBufferOnLatch(); + if (!layer->hasReadyFrame() && !willReleaseBufferOnLatch) continue; auto it = mLegacyLayers.find(layer->id); LOG_ALWAYS_FATAL_IF(it == mLegacyLayers.end(), "Couldnt find layer object for %s", layer->getDebugString().c_str()); const bool bgColorOnly = !layer->externalTexture && (layer->bgColorLayerId != UNASSIGNED_LAYER_ID); + if (willReleaseBufferOnLatch) { + mLayersWithBuffersRemoved.emplace(it->second); + } it->second->latchBufferImpl(unused, latchTime, bgColorOnly); mLayersWithQueuedFrames.emplace(it->second); } @@ -2644,10 +2648,10 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) for (auto [layer, layerFE] : layers) { CompositionResult compositionResult{layerFE->stealCompositionResult()}; layer->onPreComposition(compositionResult.refreshStartTime); - for (auto releaseFence : compositionResult.releaseFences) { + for (auto& [releaseFence, layerStack] : compositionResult.releaseFences) { Layer* clonedFrom = layer->getClonedFrom().get(); auto owningLayer = clonedFrom ? clonedFrom : layer; - owningLayer->onLayerDisplayed(releaseFence); + owningLayer->onLayerDisplayed(std::move(releaseFence), layerStack); } if (compositionResult.lastClientCompositionFence) { layer->setWasClientComposed(compositionResult.lastClientCompositionFence); @@ -2858,6 +2862,29 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { const CompositorTiming compositorTiming(vsyncDeadline.ns(), vsyncPeriod.ns(), vsyncPhase, presentLatency.ns()); + display::DisplayMap layerStackToDisplay; + { + if (!mLayersWithBuffersRemoved.empty() || mNumTrustedPresentationListeners > 0) { + Mutex::Autolock lock(mStateLock); + for (const auto& [token, display] : mDisplays) { + layerStackToDisplay.emplace_or_replace(display->getLayerStack(), display.get()); + } + } + } + + for (auto layer : mLayersWithBuffersRemoved) { + for (auto layerStack : layer->mPreviouslyPresentedLayerStacks) { + auto optDisplay = layerStackToDisplay.get(layerStack); + if (optDisplay && !optDisplay->get()->isVirtual()) { + auto fence = getHwComposer().getPresentFence(optDisplay->get()->getPhysicalId()); + layer->onLayerDisplayed(ftl::yield(fence).share(), + ui::INVALID_LAYER_STACK); + } + } + layer->releasePendingBuffer(presentTime.ns()); + } + mLayersWithBuffersRemoved.clear(); + for (const auto& layer: mLayersWithQueuedFrames) { layer->onPostComposition(defaultDisplay, glCompositionDoneFenceTime, presentFenceTime, compositorTiming); @@ -2984,14 +3011,6 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { } if (mNumTrustedPresentationListeners > 0) { - display::DisplayMap layerStackToDisplay; - { - Mutex::Autolock lock(mStateLock); - for (const auto& [token, display] : mDisplays) { - layerStackToDisplay.emplace_or_replace(display->getLayerStack(), display.get()); - } - } - // We avoid any reverse traversal upwards so this shouldn't be too expensive traverseLegacyLayers([&](Layer* layer) { if (!layer->hasTrustedPresentationListener()) { @@ -4023,7 +4042,7 @@ bool SurfaceFlinger::latchBuffers() { } } - if (layer->hasReadyFrame()) { + if (layer->hasReadyFrame() || layer->willReleaseBufferOnLatch()) { frameQueued = true; mLayersWithQueuedFrames.emplace(sp::fromExisting(layer)); } else { @@ -4054,6 +4073,9 @@ bool SurfaceFlinger::latchBuffers() { Mutex::Autolock lock(mStateLock); for (const auto& layer : mLayersWithQueuedFrames) { + if (layer->willReleaseBufferOnLatch()) { + mLayersWithBuffersRemoved.emplace(layer); + } if (layer->latchBuffer(visibleRegions, latchTime)) { mLayersPendingRefresh.push_back(layer); newDataLatched = true; @@ -5047,7 +5069,8 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime } if (layer->setTransactionCompletedListeners(callbackHandles, - layer->willPresentCurrentTransaction())) { + layer->willPresentCurrentTransaction() || + layer->willReleaseBufferOnLatch())) { flags |= eTraversalNeeded; } @@ -5161,8 +5184,9 @@ uint32_t SurfaceFlinger::updateLayerCallbacksAndStats(const FrameTimelineInfo& f } const auto& requestedLayerState = mLayerLifecycleManager.getLayerFromId(layer->getSequence()); - bool willPresentCurrentTransaction = - requestedLayerState && requestedLayerState->hasReadyFrame(); + bool willPresentCurrentTransaction = requestedLayerState && + (requestedLayerState->hasReadyFrame() || + requestedLayerState->willReleaseBufferOnLatch()); if (layer->setTransactionCompletedListeners(callbackHandles, willPresentCurrentTransaction)) flags |= eTraversalNeeded; @@ -7358,12 +7382,14 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( : ftl::yield(present()).share(); for (auto& [layer, layerFE] : layers) { - layer->onLayerDisplayed( - ftl::Future(presentFuture) - .then([layerFE = std::move(layerFE)](FenceResult) { - return layerFE->stealCompositionResult().releaseFences.back().get(); - }) - .share()); + layer->onLayerDisplayed(ftl::Future(presentFuture) + .then([layerFE = std::move(layerFE)](FenceResult) { + return layerFE->stealCompositionResult() + .releaseFences.back() + .first.get(); + }) + .share(), + ui::INVALID_LAYER_STACK); } return presentFuture; diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 5783c8dc58..2cb08a8952 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1191,6 +1191,7 @@ private: // Tracks layers that have pending frames which are candidates for being // latched. std::unordered_set, SpHash> mLayersWithQueuedFrames; + std::unordered_set, SpHash> mLayersWithBuffersRemoved; // Tracks layers that need to update a display's dirty region. std::vector> mLayersPendingRefresh; diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h index 2daea259fa..35c8b6c647 100644 --- a/services/surfaceflinger/TransactionState.h +++ b/services/surfaceflinger/TransactionState.h @@ -78,8 +78,7 @@ struct TransactionState { template void traverseStatesWithBuffers(Visitor&& visitor) const { for (const auto& state : states) { - if (state.state.hasBufferChanges() && state.state.hasValidBuffer() && - state.state.surface) { + if (state.state.hasBufferChanges() && state.externalTexture && state.state.surface) { visitor(state.state); } } @@ -88,8 +87,7 @@ struct TransactionState { template void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) { for (auto state = states.begin(); state != states.end();) { - if (state->state.hasBufferChanges() && state->state.hasValidBuffer() && - state->state.surface) { + if (state->state.hasBufferChanges() && state->externalTexture && state->state.surface) { int result = visitor(state->state, state->externalTexture); if (result == STOP_TRAVERSAL) return; if (result == DELETE_AND_CONTINUE_TRAVERSAL) { diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp index 4304259928..c3dcb85686 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp @@ -125,9 +125,12 @@ void LayerFuzzer::invokeBufferStateLayer() { mFdp.ConsumeIntegral(), mFdp.ConsumeIntegral()); - layer->onLayerDisplayed(ftl::yield(fence).share()); - layer->onLayerDisplayed( - ftl::yield(base::unexpected(mFdp.ConsumeIntegral())).share()); + layer->onLayerDisplayed(ftl::yield(fence).share(), + ui::LayerStack::fromValue(mFdp.ConsumeIntegral())); + layer->onLayerDisplayed(ftl::yield( + base::unexpected(mFdp.ConsumeIntegral())) + .share(), + ui::LayerStack::fromValue(mFdp.ConsumeIntegral())); layer->releasePendingBuffer(mFdp.ConsumeIntegral()); layer->onPostComposition(nullptr, fenceTime, fenceTime, compositorTiming); diff --git a/services/surfaceflinger/tests/LayerCallback_test.cpp b/services/surfaceflinger/tests/LayerCallback_test.cpp index 26dbc76c3c..79886bde45 100644 --- a/services/surfaceflinger/tests/LayerCallback_test.cpp +++ b/services/surfaceflinger/tests/LayerCallback_test.cpp @@ -1224,4 +1224,75 @@ TEST_F(LayerCallbackTest, TransactionCommittedCallback_NoLayer) { EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true)); } +TEST_F(LayerCallbackTest, SetNullBuffer) { + sp layer; + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); + + Transaction transaction; + CallbackHelper callback; + int err = fillTransaction(transaction, &callback, layer, /*setBuffer=*/true, + /*setBackgroundColor=*/false); + if (err) { + GTEST_SUCCEED() << "test not supported"; + return; + } + transaction.apply(); + + { + ExpectedResult expected; + expected.addSurface(ExpectedResult::Transaction::PRESENTED, layer, + ExpectedResult::Buffer::ACQUIRED, + ExpectedResult::PreviousBuffer::NOT_RELEASED); + EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true)); + } + + transaction.setBuffer(layer, nullptr); + transaction.addTransactionCompletedCallback(callback.function, callback.getContext()); + transaction.apply(); + + { + ExpectedResult expected; + expected.addSurface(ExpectedResult::Transaction::PRESENTED, layer, + ExpectedResult::Buffer::ACQUIRED_NULL, + ExpectedResult::PreviousBuffer::RELEASED); + EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true)); + } + + err = fillTransaction(transaction, &callback, layer, /*setBuffer=*/true, + /*setBackgroundColor=*/false); + if (err) { + GTEST_SUCCEED() << "test not supported"; + return; + } + + transaction.apply(); + + { + ExpectedResult expected; + expected.addSurface(ExpectedResult::Transaction::PRESENTED, layer, + ExpectedResult::Buffer::ACQUIRED, + ExpectedResult::PreviousBuffer::NOT_RELEASED); + EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true)); + } +} + +TEST_F(LayerCallbackTest, SetNullBufferOnLayerWithoutBuffer) { + sp layer; + ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer()); + + Transaction transaction; + transaction.setBuffer(layer, nullptr); + CallbackHelper callback; + transaction.addTransactionCompletedCallback(callback.function, callback.getContext()); + transaction.apply(); + + { + ExpectedResult expected; + expected.addSurface(ExpectedResult::Transaction::NOT_PRESENTED, layer, + ExpectedResult::Buffer::NOT_ACQUIRED, + ExpectedResult::PreviousBuffer::NOT_RELEASED); + EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true)); + } +} + } // namespace android diff --git a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp index 0b8c51ec1d..b8068f79a4 100644 --- a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp +++ b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp @@ -1636,6 +1636,65 @@ TEST_P(LayerRenderTypeTransactionTest, SetColorTransformOnChildAndParent) { getScreenCapture()->expectColor(Rect(0, 0, 32, 32), expectedColor, tolerance); } } + +TEST_P(LayerRenderTypeTransactionTest, SetNullBuffer) { + const Rect bounds(0, 0, 32, 32); + sp layer; + ASSERT_NO_FATAL_FAILURE( + layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); + + sp buffer = + sp::make(32u, 32u, PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags, "test"); + + ASSERT_NO_FATAL_FAILURE(TransactionUtils::fillGraphicBufferColor(buffer, bounds, Color::GREEN)); + Transaction().setBuffer(layer, buffer).apply(); + { + SCOPED_TRACE("before null buffer"); + auto shot = getScreenCapture(); + shot->expectColor(bounds, Color::GREEN); + } + + Transaction().setBuffer(layer, nullptr).apply(); + { + SCOPED_TRACE("null buffer removes buffer"); + auto shot = getScreenCapture(); + shot->expectColor(bounds, Color::BLACK); + } + + Transaction().setBuffer(layer, buffer).apply(); + { + SCOPED_TRACE("after null buffer"); + auto shot = getScreenCapture(); + shot->expectColor(bounds, Color::GREEN); + } +} + +TEST_P(LayerRenderTypeTransactionTest, SetNullBufferOnLayerWithoutBuffer) { + const Rect bounds(0, 0, 32, 32); + sp layer; + ASSERT_NO_FATAL_FAILURE( + layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState)); + { + SCOPED_TRACE("starting state"); + auto shot = getScreenCapture(); + shot->expectColor(bounds, Color::BLACK); + } + + Transaction().setBuffer(layer, nullptr).apply(); + { + SCOPED_TRACE("null buffer has no effect"); + auto shot = getScreenCapture(); + shot->expectColor(bounds, Color::BLACK); + } + + Transaction().setBuffer(layer, nullptr).apply(); + { + SCOPED_TRACE("null buffer has no effect"); + auto shot = getScreenCapture(); + shot->expectColor(bounds, Color::BLACK); + } +} + } // namespace android // TODO(b/129481165): remove the #pragma below and fix conversion issues diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index 03c4e713a0..dbb7c6ce63 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -274,6 +274,34 @@ TEST_F(TransactionApplicationTest, FromHandle) { EXPECT_EQ(nullptr, ret.get()); } +class FakeExternalTexture : public renderengine::ExternalTexture { + const sp mEmptyBuffer = nullptr; + uint32_t mWidth; + uint32_t mHeight; + uint64_t mId; + PixelFormat mPixelFormat; + uint64_t mUsage; + +public: + FakeExternalTexture(BufferData& bufferData) + : mWidth(bufferData.getWidth()), + mHeight(bufferData.getHeight()), + mId(bufferData.getId()), + mPixelFormat(bufferData.getPixelFormat()), + mUsage(bufferData.getUsage()) {} + const sp& getBuffer() const { return mEmptyBuffer; } + bool hasSameBuffer(const renderengine::ExternalTexture& other) const override { + return getId() == other.getId(); + } + uint32_t getWidth() const override { return mWidth; } + uint32_t getHeight() const override { return mHeight; } + uint64_t getId() const override { return mId; } + PixelFormat getPixelFormat() const override { return mPixelFormat; } + uint64_t getUsage() const override { return mUsage; } + void remapBuffer() override {} + ~FakeExternalTexture() = default; +}; + class LatchUnsignaledTest : public TransactionApplicationTest { public: void TearDown() override { @@ -346,7 +374,11 @@ public: std::vector resolvedStates; resolvedStates.reserve(transaction.states.size()); for (auto& state : transaction.states) { - resolvedStates.emplace_back(std::move(state)); + ResolvedComposerState resolvedState; + resolvedState.state = std::move(state.state); + resolvedState.externalTexture = + std::make_shared(*resolvedState.state.bufferData); + resolvedStates.emplace_back(resolvedState); } TransactionState transactionState(transaction.frameTimelineInfo, resolvedStates, -- GitLab From b69e9884fc1707141b21dbe1b67214e956e6f4a7 Mon Sep 17 00:00:00 2001 From: Yuxin Hu Date: Thu, 13 Apr 2023 03:51:41 +0000 Subject: [PATCH 1153/1310] Add new API to toggle ANGLE as the default system GLES driver Bug:b/270994705 Test: Flash, verify Pixel 7 can boot. Toggle the developer option switch. adb shell getprop persist.graphics.egl is returning right values with switch set on and off. Change-Id: Idce453d79e97c48cc965900315799784a001e053 --- libs/graphicsenv/GraphicsEnv.cpp | 9 +++++++ libs/graphicsenv/IGpuService.cpp | 18 +++++++++++++ .../include/graphicsenv/GraphicsEnv.h | 2 ++ .../include/graphicsenv/IGpuService.h | 4 +++ services/gpuservice/GpuService.cpp | 26 +++++++++++++++++++ services/gpuservice/GpuService.h | 1 + 6 files changed, 60 insertions(+) diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp index 46dd62d3bf..c480056b40 100644 --- a/libs/graphicsenv/GraphicsEnv.cpp +++ b/libs/graphicsenv/GraphicsEnv.cpp @@ -689,4 +689,13 @@ android_namespace_t* GraphicsEnv::getAngleNamespace() { return mAngleNamespace; } +void GraphicsEnv::nativeToggleAngleAsSystemDriver(bool enabled) { + const sp gpuService = getGpuService(); + if (!gpuService) { + ALOGE("No GPU service"); + return; + } + gpuService->toggleAngleAsSystemDriver(enabled); +} + } // namespace android diff --git a/libs/graphicsenv/IGpuService.cpp b/libs/graphicsenv/IGpuService.cpp index ceb52f71d8..4c070aec01 100644 --- a/libs/graphicsenv/IGpuService.cpp +++ b/libs/graphicsenv/IGpuService.cpp @@ -78,6 +78,15 @@ public: IBinder::FLAG_ONEWAY); } + void toggleAngleAsSystemDriver(bool enabled) override { + Parcel data, reply; + data.writeInterfaceToken(IGpuService::getInterfaceDescriptor()); + data.writeBool(enabled); + + remote()->transact(BnGpuService::TOGGLE_ANGLE_AS_SYSTEM_DRIVER, data, &reply, + IBinder::FLAG_ONEWAY); + } + std::string getUpdatableDriverPath() override { Parcel data, reply; data.writeInterfaceToken(IGpuService::getInterfaceDescriptor()); @@ -189,6 +198,15 @@ status_t BnGpuService::onTransact(uint32_t code, const Parcel& data, Parcel* rep return OK; } + case TOGGLE_ANGLE_AS_SYSTEM_DRIVER: { + CHECK_INTERFACE(IGpuService, data, reply); + + bool enableAngleAsSystemDriver; + if ((status = data.readBool(&enableAngleAsSystemDriver)) != OK) return status; + + toggleAngleAsSystemDriver(enableAngleAsSystemDriver); + return OK; + } default: return BBinder::onTransact(code, data, reply, flags); } diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h index b58a6d90fe..1274c46b7b 100644 --- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h +++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h @@ -142,6 +142,8 @@ public: const std::string& getDebugLayers(); // Get the debug layers to load. const std::string& getDebugLayersGLES(); + // Set the persist.graphics.egl system property value. + void nativeToggleAngleAsSystemDriver(bool enabled); private: enum UseAngle { UNKNOWN, YES, NO }; diff --git a/libs/graphicsenv/include/graphicsenv/IGpuService.h b/libs/graphicsenv/include/graphicsenv/IGpuService.h index b708b0fec0..e3857d2ec0 100644 --- a/libs/graphicsenv/include/graphicsenv/IGpuService.h +++ b/libs/graphicsenv/include/graphicsenv/IGpuService.h @@ -50,6 +50,9 @@ public: // setter and getter for updatable driver path. virtual void setUpdatableDriverPath(const std::string& driverPath) = 0; virtual std::string getUpdatableDriverPath() = 0; + + // sets ANGLE as system GLES driver if enabled==true by setting persist.graphics.egl to true. + virtual void toggleAngleAsSystemDriver(bool enabled) = 0; }; class BnGpuService : public BnInterface { @@ -59,6 +62,7 @@ public: SET_TARGET_STATS, SET_UPDATABLE_DRIVER_PATH, GET_UPDATABLE_DRIVER_PATH, + TOGGLE_ANGLE_AS_SYSTEM_DRIVER, // Always append new enum to the end. }; diff --git a/services/gpuservice/GpuService.cpp b/services/gpuservice/GpuService.cpp index aaa8c18508..5e7b2e8df8 100644 --- a/services/gpuservice/GpuService.cpp +++ b/services/gpuservice/GpuService.cpp @@ -19,6 +19,7 @@ #include "GpuService.h" #include +#include #include #include #include @@ -46,6 +47,8 @@ void dumpGameDriverInfo(std::string* result); } // namespace const String16 sDump("android.permission.DUMP"); +const String16 sAccessGpuServicePermission("android.permission.ACCESS_GPU_SERVICE"); +const std::string sAngleGlesDriverSuffix = "angle"; const char* const GpuService::SERVICE_NAME = "gpu"; @@ -88,6 +91,29 @@ void GpuService::setTargetStatsArray(const std::string& appPackageName, mGpuStats->insertTargetStatsArray(appPackageName, driverVersionCode, stats, values, valueCount); } +void GpuService::toggleAngleAsSystemDriver(bool enabled) { + IPCThreadState* ipc = IPCThreadState::self(); + const int pid = ipc->getCallingPid(); + const int uid = ipc->getCallingUid(); + + // only system_server with the ACCESS_GPU_SERVICE permission is allowed to set + // persist.graphics.egl + if (uid != AID_SYSTEM || + !PermissionCache::checkPermission(sAccessGpuServicePermission, pid, uid)) { + ALOGE("Permission Denial: can't set persist.graphics.egl from setAngleAsSystemDriver() " + "pid=%d, uid=%d\n", pid, uid); + return; + } + + std::lock_guard lock(mLock); + if (enabled) { + android::base::SetProperty("persist.graphics.egl", sAngleGlesDriverSuffix); + } else { + android::base::SetProperty("persist.graphics.egl", ""); + } +} + + void GpuService::setUpdatableDriverPath(const std::string& driverPath) { IPCThreadState* ipc = IPCThreadState::self(); const int pid = ipc->getCallingPid(); diff --git a/services/gpuservice/GpuService.h b/services/gpuservice/GpuService.h index e7e0cba689..0e559f2c34 100644 --- a/services/gpuservice/GpuService.h +++ b/services/gpuservice/GpuService.h @@ -61,6 +61,7 @@ private: const uint64_t* values, const uint32_t valueCount) override; void setUpdatableDriverPath(const std::string& driverPath) override; std::string getUpdatableDriverPath() override; + void toggleAngleAsSystemDriver(bool enabled) override; /* * IBinder interface -- GitLab From 05ceebfbaec627732ba0467e6b95d77dac865c51 Mon Sep 17 00:00:00 2001 From: Nick Deakin Date: Wed, 19 Apr 2023 15:27:13 -0400 Subject: [PATCH 1154/1310] UltraHDR: update Version handling. Version is now a string as spec'd, and identifier is added to Primary image XMP to indicate presence of a gain map. Bug: 278784125 Test: tests pass Change-Id: Ia76879ca3d187edb78927ad5bab79fc8eef07da8 (cherry picked from commit 4ac2a268cbd211389d7f1d27853b8eeb6f956b72) --- libs/ultrahdr/include/ultrahdr/jpegrutils.h | 7 +++++-- libs/ultrahdr/include/ultrahdr/ultrahdr.h | 4 ++-- libs/ultrahdr/jpegr.cpp | 4 ++-- libs/ultrahdr/jpegrutils.cpp | 4 +++- libs/ultrahdr/tests/jpegr_test.cpp | 3 ++- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/libs/ultrahdr/include/ultrahdr/jpegrutils.h b/libs/ultrahdr/include/ultrahdr/jpegrutils.h index ed38e07c2d..4ab664e798 100644 --- a/libs/ultrahdr/include/ultrahdr/jpegrutils.h +++ b/libs/ultrahdr/include/ultrahdr/jpegrutils.h @@ -102,7 +102,9 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, ultrahdr_metadata_st * xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> * + * xmlns:Item="http://ns.google.com/photos/1.0/container/item/" + * xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/" + * hdrgm:Version="1"> * * * length; // primary image - const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size); + const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata); // same as primary const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size(); diff --git a/libs/ultrahdr/jpegrutils.cpp b/libs/ultrahdr/jpegrutils.cpp index 9d07a6f889..6430af12c7 100644 --- a/libs/ultrahdr/jpegrutils.cpp +++ b/libs/ultrahdr/jpegrutils.cpp @@ -302,7 +302,7 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, ultrahdr_metadata_st return true; } -string generateXmpForPrimaryImage(int secondary_image_length) { +string generateXmpForPrimaryImage(int secondary_image_length, ultrahdr_metadata_struct& metadata) { const vector kConDirSeq({kConDirectory, string("rdf:Seq")}); const vector kLiItem({string("rdf:li"), kConItem}); @@ -316,6 +316,8 @@ string generateXmpForPrimaryImage(int secondary_image_length) { writer.StartWritingElement("rdf:Description"); writer.WriteXmlns(kContainerPrefix, kContainerUri); writer.WriteXmlns(kItemPrefix, kItemUri); + writer.WriteXmlns(kGainMapPrefix, kGainMapUri); + writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); writer.StartWritingElements(kConDirSeq); diff --git a/libs/ultrahdr/tests/jpegr_test.cpp b/libs/ultrahdr/tests/jpegr_test.cpp index ba3b4d0418..58cd8f4711 100644 --- a/libs/ultrahdr/tests/jpegr_test.cpp +++ b/libs/ultrahdr/tests/jpegr_test.cpp @@ -180,6 +180,7 @@ TEST_F(JpegRTest, build) { TEST_F(JpegRTest, writeXmpThenRead) { ultrahdr_metadata_struct metadata_expected; + metadata_expected.version = "1.0"; metadata_expected.maxContentBoost = 1.25; metadata_expected.minContentBoost = 0.75; const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; @@ -538,7 +539,7 @@ TEST_F(JpegRTest, ProfileGainMapFuncs) { JpegRBenchmark benchmark; - ultrahdr_metadata_struct metadata = { .version = 1, + ultrahdr_metadata_struct metadata = { .version = "1.0", .maxContentBoost = 8.0f, .minContentBoost = 1.0f / 8.0f }; -- GitLab From 1fb63cc53396df922be029b5147cf36f2b6b7ee3 Mon Sep 17 00:00:00 2001 From: John Reck Date: Wed, 19 Apr 2023 14:39:11 -0400 Subject: [PATCH 1155/1310] Tweak ASC_setExtendedRangeBrightness docs Bug: 278780207 Test: n/a doc only change Change-Id: I7e47f63ce70d28827188e3848a725e1608722172 --- include/android/surface_control.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/include/android/surface_control.h b/include/android/surface_control.h index daeebec9ac..e4ba58adc7 100644 --- a/include/android/surface_control.h +++ b/include/android/surface_control.h @@ -548,11 +548,15 @@ void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* transactio * to the max display brightness. The system may not be able to, or may choose * not to, deliver the requested range. * - * If unspecified, the system will attempt to provide the best range it can - * for the given ambient conditions & device state. However, voluntarily - * reducing the requested range can help improve battery life as well as can - * improve quality by ensuring greater bit depth is allocated to the luminance - * range in use. + * While requesting a large desired ratio will result in the most + * dynamic range, voluntarily reducing the requested range can help + * improve battery life as well as can improve quality by ensuring + * greater bit depth is allocated to the luminance range in use. + * + * Default value is 1.0f and indicates that extended range brightness + * is not being used, so the resulting SDR or HDR behavior will be + * determined entirely by the dataspace being used (ie, typically SDR + * however PQ or HLG transfer functions will still result in HDR) * * Must be finite && >= 1.0f * -- GitLab From 1069fa8a9f2dbb30656a3d1071d297541c86e905 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 19 Apr 2023 12:14:39 -0700 Subject: [PATCH 1156/1310] Move Connection off of RefBase The Connection class doesn't need to be inside sp<>. Convert it to use shared_ptr. There are still a couple of places where the connection is passed around, so we can't make it a unique_ptr. Bug: 211379801 Test: m checkinput Change-Id: I6755e7c9a39a015d30738c1b9052733c182628d5 --- .../inputflinger/dispatcher/Connection.cpp | 2 - services/inputflinger/dispatcher/Connection.h | 5 +- .../dispatcher/InputDispatcher.cpp | 95 ++++++++++--------- .../inputflinger/dispatcher/InputDispatcher.h | 59 ++++++------ 4 files changed, 83 insertions(+), 78 deletions(-) diff --git a/services/inputflinger/dispatcher/Connection.cpp b/services/inputflinger/dispatcher/Connection.cpp index b4497fd653..ed95de787c 100644 --- a/services/inputflinger/dispatcher/Connection.cpp +++ b/services/inputflinger/dispatcher/Connection.cpp @@ -28,8 +28,6 @@ Connection::Connection(const std::shared_ptr& inputChannel, bool m inputPublisher(inputChannel), inputState(idGenerator) {} -Connection::~Connection() {} - const std::string Connection::getWindowName() const { if (inputChannel != nullptr) { return inputChannel->getName(); diff --git a/services/inputflinger/dispatcher/Connection.h b/services/inputflinger/dispatcher/Connection.h index 6040e9b078..2929d61871 100644 --- a/services/inputflinger/dispatcher/Connection.h +++ b/services/inputflinger/dispatcher/Connection.h @@ -27,10 +27,7 @@ namespace android::inputdispatcher { struct DispatchEntry; /* Manages the dispatch state associated with a single input channel. */ -class Connection : public RefBase { -protected: - virtual ~Connection(); - +class Connection { public: enum class Status { // Everything is peachy. diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index a6d181047a..6b9ad446ce 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -688,7 +688,7 @@ InputDispatcher::~InputDispatcher() { mCommandQueue.clear(); while (!mConnectionsByToken.empty()) { - sp connection = mConnectionsByToken.begin()->second; + std::shared_ptr connection = mConnectionsByToken.begin()->second; removeInputChannelLocked(connection->inputChannel->getConnectionToken(), /*notify=*/false); } } @@ -802,7 +802,7 @@ nsecs_t InputDispatcher::processAnrsLocked() { } // If we reached here, we have an unresponsive connection. - sp connection = getConnectionLocked(mAnrTracker.firstToken()); + std::shared_ptr connection = getConnectionLocked(mAnrTracker.firstToken()); if (connection == nullptr) { ALOGE("Could not find connection for entry %" PRId64, mAnrTracker.firstTimeout()); return nextAnrCheck; @@ -815,7 +815,7 @@ nsecs_t InputDispatcher::processAnrsLocked() { } std::chrono::nanoseconds InputDispatcher::getDispatchingTimeoutLocked( - const sp& connection) { + const std::shared_ptr& connection) { if (connection->monitor) { return mMonitorDispatchingTimeout; } @@ -1058,7 +1058,8 @@ bool InputDispatcher::shouldPruneInboundQueueLocked(const MotionEntry& motionEnt const std::vector> touchedSpies = findTouchedSpyWindowsAtLocked(displayId, x, y, isStylus); for (const auto& windowHandle : touchedSpies) { - const sp connection = getConnectionLocked(windowHandle->getToken()); + const std::shared_ptr connection = + getConnectionLocked(windowHandle->getToken()); if (connection != nullptr && connection->responsive) { // This spy window could take more input. Drop all events preceding this // event, so that the spy window can get a chance to receive the stream. @@ -1895,7 +1896,7 @@ void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, pokeUserActivityLocked(*eventEntry); for (const InputTarget& inputTarget : inputTargets) { - sp connection = + std::shared_ptr connection = getConnectionLocked(inputTarget.inputChannel->getConnectionToken()); if (connection != nullptr) { prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget); @@ -1909,7 +1910,7 @@ void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, } } -void InputDispatcher::cancelEventsForAnrLocked(const sp& connection) { +void InputDispatcher::cancelEventsForAnrLocked(const std::shared_ptr& connection) { // We will not be breaking any connections here, even if the policy wants us to abort dispatch. // If the policy decides to close the app, we will get a channel removal event via // unregisterInputChannel, and will clean up the connection that way. We are already not @@ -2101,7 +2102,7 @@ std::vector InputDispatcher::selectResponsiveMonitorsLocked( std::vector responsiveMonitors; std::copy_if(monitors.begin(), monitors.end(), std::back_inserter(responsiveMonitors), [this](const Monitor& monitor) REQUIRES(mLock) { - sp connection = + std::shared_ptr connection = getConnectionLocked(monitor.inputChannel->getConnectionToken()); if (connection == nullptr) { ALOGE("Could not find connection for monitor %s", @@ -3027,7 +3028,7 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) { } void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, - const sp& connection, + const std::shared_ptr& connection, std::shared_ptr eventEntry, const InputTarget& inputTarget) { if (ATRACE_ENABLED()) { @@ -3097,7 +3098,7 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, } void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime, - const sp& connection, + const std::shared_ptr& connection, std::shared_ptr eventEntry, const InputTarget& inputTarget) { if (ATRACE_ENABLED()) { @@ -3131,7 +3132,7 @@ void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime, } } -void InputDispatcher::enqueueDispatchEntryLocked(const sp& connection, +void InputDispatcher::enqueueDispatchEntryLocked(const std::shared_ptr& connection, std::shared_ptr eventEntry, const InputTarget& inputTarget, ftl::Flags dispatchMode) { @@ -3316,14 +3317,14 @@ void InputDispatcher::updateInteractionTokensLocked(const EventEntry& entry, } std::unordered_set, StrongPointerHash> newConnectionTokens; - std::vector> newConnections; + std::vector> newConnections; for (const InputTarget& target : targets) { if (target.flags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) { continue; // Skip windows that receive ACTION_OUTSIDE } sp token = target.inputChannel->getConnectionToken(); - sp connection = getConnectionLocked(token); + std::shared_ptr connection = getConnectionLocked(token); if (connection == nullptr) { continue; } @@ -3336,7 +3337,7 @@ void InputDispatcher::updateInteractionTokensLocked(const EventEntry& entry, mInteractionConnectionTokens = newConnectionTokens; std::string targetList; - for (const sp& connection : newConnections) { + for (const std::shared_ptr& connection : newConnections) { targetList += connection->getWindowName() + ", "; } std::string message = "Interaction with: " + targetList; @@ -3416,7 +3417,7 @@ status_t InputDispatcher::publishMotionEvent(Connection& connection, } void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, - const sp& connection) { + const std::shared_ptr& connection) { if (ATRACE_ENABLED()) { std::string message = StringPrintf("startDispatchCycleLocked(inputChannel=%s)", connection->getInputChannelName().c_str()); @@ -3596,8 +3597,8 @@ const std::array InputDispatcher::getSignature( } void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, - const sp& connection, uint32_t seq, - bool handled, nsecs_t consumeTime) { + const std::shared_ptr& connection, + uint32_t seq, bool handled, nsecs_t consumeTime) { if (DEBUG_DISPATCH_CYCLE) { ALOGD("channel '%s' ~ finishDispatchCycle - seq=%u, handled=%s", connection->getInputChannelName().c_str(), seq, toString(handled)); @@ -3616,7 +3617,7 @@ void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, } void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime, - const sp& connection, + const std::shared_ptr& connection, bool notify) { if (DEBUG_DISPATCH_CYCLE) { LOG(DEBUG) << "channel '" << connection->getInputChannelName() << "'~ " << __func__ @@ -3665,7 +3666,7 @@ void InputDispatcher::releaseDispatchEntry(DispatchEntry* dispatchEntry) { int InputDispatcher::handleReceiveCallback(int events, sp connectionToken) { std::scoped_lock _l(mLock); - sp connection = getConnectionLocked(connectionToken); + std::shared_ptr connection = getConnectionLocked(connectionToken); if (connection == nullptr) { ALOGW("Received looper callback for unknown input channel token %p. events=0x%x", connectionToken.get(), events); @@ -3757,7 +3758,7 @@ void InputDispatcher::synthesizeCancelationEventsForMonitorsLocked( void InputDispatcher::synthesizeCancelationEventsForInputChannelLocked( const std::shared_ptr& channel, const CancelationOptions& options) { - sp connection = getConnectionLocked(channel->getConnectionToken()); + std::shared_ptr connection = getConnectionLocked(channel->getConnectionToken()); if (connection == nullptr) { return; } @@ -3766,7 +3767,7 @@ void InputDispatcher::synthesizeCancelationEventsForInputChannelLocked( } void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( - const sp& connection, const CancelationOptions& options) { + const std::shared_ptr& connection, const CancelationOptions& options) { if (connection->status == Connection::Status::BROKEN) { return; } @@ -3844,7 +3845,7 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( } void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( - const nsecs_t downTime, const sp& connection, + const nsecs_t downTime, const std::shared_ptr& connection, ftl::Flags targetFlags) { if (connection->status == Connection::Status::BROKEN) { return; @@ -3909,7 +3910,8 @@ void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( void InputDispatcher::synthesizeCancelationEventsForWindowLocked( const sp& windowHandle, const CancelationOptions& options) { if (windowHandle != nullptr) { - sp wallpaperConnection = getConnectionLocked(windowHandle->getToken()); + std::shared_ptr wallpaperConnection = + getConnectionLocked(windowHandle->getToken()); if (wallpaperConnection != nullptr) { synthesizeCancelationEventsForConnectionLocked(wallpaperConnection, options); } @@ -4796,7 +4798,7 @@ bool InputDispatcher::canWindowReceiveMotionLocked(const sp& w return false; } - sp connection = getConnectionLocked(window->getToken()); + std::shared_ptr connection = getConnectionLocked(window->getToken()); if (connection == nullptr) { ALOGW("Not sending touch to %s because there's no corresponding connection", window->getName().c_str()); @@ -5318,8 +5320,8 @@ bool InputDispatcher::transferTouchFocus(const sp& fromToken, const sp< } // Synthesize cancel for old window and down for new window. - sp fromConnection = getConnectionLocked(fromToken); - sp toConnection = getConnectionLocked(toToken); + std::shared_ptr fromConnection = getConnectionLocked(fromToken); + std::shared_ptr toConnection = getConnectionLocked(toToken); if (fromConnection != nullptr && toConnection != nullptr) { fromConnection->inputState.mergePointerStateTo(toConnection->inputState); CancelationOptions @@ -5685,8 +5687,9 @@ Result> InputDispatcher::createInputChannel(const std::scoped_lock _l(mLock); const sp& token = serverChannel->getConnectionToken(); int fd = serverChannel->getFd(); - sp connection = - sp::make(std::move(serverChannel), /*monitor=*/false, mIdGenerator); + std::shared_ptr connection = + std::make_shared(std::move(serverChannel), /*monitor=*/false, + mIdGenerator); if (mConnectionsByToken.find(token) != mConnectionsByToken.end()) { ALOGE("Created a new connection, but the token %p is already known", token.get()); @@ -5723,8 +5726,8 @@ Result> InputDispatcher::createInputMonitor(int32_ << " without a specified display."; } - sp connection = - sp::make(serverChannel, /*monitor=*/true, mIdGenerator); + std::shared_ptr connection = + std::make_shared(serverChannel, /*monitor=*/true, mIdGenerator); const sp& token = serverChannel->getConnectionToken(); const int fd = serverChannel->getFd(); @@ -5764,7 +5767,7 @@ status_t InputDispatcher::removeInputChannel(const sp& connectionToken) status_t InputDispatcher::removeInputChannelLocked(const sp& connectionToken, bool notify) { - sp connection = getConnectionLocked(connectionToken); + std::shared_ptr connection = getConnectionLocked(connectionToken); if (connection == nullptr) { // Connection can be removed via socket hang up or an explicit call to 'removeInputChannel' return BAD_VALUE; @@ -5910,7 +5913,8 @@ std::optional InputDispatcher::findMonitorPidByTokenLocked(const sp InputDispatcher::getConnectionLocked(const sp& inputConnectionToken) const { +std::shared_ptr InputDispatcher::getConnectionLocked( + const sp& inputConnectionToken) const { if (inputConnectionToken == nullptr) { return nullptr; } @@ -5925,21 +5929,22 @@ sp InputDispatcher::getConnectionLocked(const sp& inputConn } std::string InputDispatcher::getConnectionNameLocked(const sp& connectionToken) const { - sp connection = getConnectionLocked(connectionToken); + std::shared_ptr connection = getConnectionLocked(connectionToken); if (connection == nullptr) { return ""; } return connection->getInputChannelName(); } -void InputDispatcher::removeConnectionLocked(const sp& connection) { +void InputDispatcher::removeConnectionLocked(const std::shared_ptr& connection) { mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken()); mConnectionsByToken.erase(connection->inputChannel->getConnectionToken()); } void InputDispatcher::doDispatchCycleFinishedCommand(nsecs_t finishTime, - const sp& connection, uint32_t seq, - bool handled, nsecs_t consumeTime) { + const std::shared_ptr& connection, + uint32_t seq, bool handled, + nsecs_t consumeTime) { // Handle post-event policy actions. std::deque::iterator dispatchEntryIt = connection->findWaitQueueEntry(seq); if (dispatchEntryIt == connection->waitQueue.end()) { @@ -6017,7 +6022,7 @@ void InputDispatcher::sendDropWindowCommandLocked(const sp& token, floa postCommandLocked(std::move(command)); } -void InputDispatcher::onAnrLocked(const sp& connection) { +void InputDispatcher::onAnrLocked(const std::shared_ptr& connection) { if (connection == nullptr) { LOG_ALWAYS_FATAL("Caller must check for nullness"); } @@ -6179,9 +6184,9 @@ void InputDispatcher::processConnectionResponsiveLocked(const Connection& connec sendWindowResponsiveCommandLocked(connectionToken, pid); } -bool InputDispatcher::afterKeyEventLockedInterruptable(const sp& connection, - DispatchEntry* dispatchEntry, - KeyEntry& keyEntry, bool handled) { +bool InputDispatcher::afterKeyEventLockedInterruptable( + const std::shared_ptr& connection, DispatchEntry* dispatchEntry, + KeyEntry& keyEntry, bool handled) { if (keyEntry.flags & AKEY_EVENT_FLAG_FALLBACK) { if (!handled) { // Report the key as unhandled, since the fallback was not handled. @@ -6356,9 +6361,9 @@ bool InputDispatcher::afterKeyEventLockedInterruptable(const sp& con return false; } -bool InputDispatcher::afterMotionEventLockedInterruptable(const sp& connection, - DispatchEntry* dispatchEntry, - MotionEntry& motionEntry, bool handled) { +bool InputDispatcher::afterMotionEventLockedInterruptable( + const std::shared_ptr& connection, DispatchEntry* dispatchEntry, + MotionEntry& motionEntry, bool handled) { return false; } @@ -6680,9 +6685,11 @@ void InputDispatcher::transferWallpaperTouch(ftl::Flags oldT wallpaperFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED | InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED; state.addOrUpdateWindow(newWallpaper, wallpaperFlags, pointerIds, downTimeInTarget); - sp wallpaperConnection = getConnectionLocked(newWallpaper->getToken()); + std::shared_ptr wallpaperConnection = + getConnectionLocked(newWallpaper->getToken()); if (wallpaperConnection != nullptr) { - sp toConnection = getConnectionLocked(toWindowHandle->getToken()); + std::shared_ptr toConnection = + getConnectionLocked(toWindowHandle->getToken()); toConnection->inputState.mergePointerStateTo(wallpaperConnection->inputState); synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, wallpaperConnection, wallpaperFlags); diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 76b15e2b1d..7aa1a2d313 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -249,12 +249,12 @@ private: sp findTouchedForegroundWindowLocked(int32_t displayId) const REQUIRES(mLock); - sp getConnectionLocked(const sp& inputConnectionToken) const + std::shared_ptr getConnectionLocked(const sp& inputConnectionToken) const REQUIRES(mLock); std::string getConnectionNameLocked(const sp& connectionToken) const REQUIRES(mLock); - void removeConnectionLocked(const sp& connection) REQUIRES(mLock); + void removeConnectionLocked(const std::shared_ptr& connection) REQUIRES(mLock); status_t pilferPointersLocked(const sp& token) REQUIRES(mLock); @@ -264,8 +264,8 @@ private: }; // All registered connections mapped by input channel token. - std::unordered_map, sp, StrongPointerHash> mConnectionsByToken - GUARDED_BY(mLock); + std::unordered_map, std::shared_ptr, StrongPointerHash> + mConnectionsByToken GUARDED_BY(mLock); // Find a monitor pid by the provided token. std::optional findMonitorPidByTokenLocked(const sp& token) REQUIRES(mLock); @@ -328,8 +328,8 @@ private: std::chrono::nanoseconds mMonitorDispatchingTimeout GUARDED_BY(mLock); nsecs_t processAnrsLocked() REQUIRES(mLock); - std::chrono::nanoseconds getDispatchingTimeoutLocked(const sp& connection) - REQUIRES(mLock); + std::chrono::nanoseconds getDispatchingTimeoutLocked( + const std::shared_ptr& connection) REQUIRES(mLock); // Input filter processing. bool shouldSendKeyToInputFilterLocked(const NotifyKeyArgs& args) REQUIRES(mLock); @@ -533,7 +533,7 @@ private: // prevent unneeded wakeups. AnrTracker mAnrTracker GUARDED_BY(mLock); - void cancelEventsForAnrLocked(const sp& connection) REQUIRES(mLock); + void cancelEventsForAnrLocked(const std::shared_ptr& connection) REQUIRES(mLock); // If a focused application changes, we should stop counting down the "no focused window" time, // because we will have no way of knowing when the previous application actually added a window. // This also means that we will miss cases like pulling down notification shade when the @@ -594,22 +594,26 @@ private: // These methods are deliberately not Interruptible because doing all of the work // with the mutex held makes it easier to ensure that connection invariants are maintained. // If needed, the methods post commands to run later once the critical bits are done. - void prepareDispatchCycleLocked(nsecs_t currentTime, const sp& connection, + void prepareDispatchCycleLocked(nsecs_t currentTime, + const std::shared_ptr& connection, std::shared_ptr, const InputTarget& inputTarget) REQUIRES(mLock); - void enqueueDispatchEntriesLocked(nsecs_t currentTime, const sp& connection, + void enqueueDispatchEntriesLocked(nsecs_t currentTime, + const std::shared_ptr& connection, std::shared_ptr, const InputTarget& inputTarget) REQUIRES(mLock); - void enqueueDispatchEntryLocked(const sp& connection, std::shared_ptr, - const InputTarget& inputTarget, + void enqueueDispatchEntryLocked(const std::shared_ptr& connection, + std::shared_ptr, const InputTarget& inputTarget, ftl::Flags dispatchMode) REQUIRES(mLock); status_t publishMotionEvent(Connection& connection, DispatchEntry& dispatchEntry) const; - void startDispatchCycleLocked(nsecs_t currentTime, const sp& connection) + void startDispatchCycleLocked(nsecs_t currentTime, + const std::shared_ptr& connection) REQUIRES(mLock); + void finishDispatchCycleLocked(nsecs_t currentTime, + const std::shared_ptr& connection, uint32_t seq, + bool handled, nsecs_t consumeTime) REQUIRES(mLock); + void abortBrokenDispatchCycleLocked(nsecs_t currentTime, + const std::shared_ptr& connection, bool notify) REQUIRES(mLock); - void finishDispatchCycleLocked(nsecs_t currentTime, const sp& connection, - uint32_t seq, bool handled, nsecs_t consumeTime) REQUIRES(mLock); - void abortBrokenDispatchCycleLocked(nsecs_t currentTime, const sp& connection, - bool notify) REQUIRES(mLock); void drainDispatchQueue(std::deque& queue); void releaseDispatchEntry(DispatchEntry* dispatchEntry); int handleReceiveCallback(int events, sp connectionToken); @@ -624,14 +628,13 @@ private: void synthesizeCancelationEventsForInputChannelLocked( const std::shared_ptr& channel, const CancelationOptions& options) REQUIRES(mLock); - void synthesizeCancelationEventsForConnectionLocked(const sp& connection, - const CancelationOptions& options) + void synthesizeCancelationEventsForConnectionLocked( + const std::shared_ptr& connection, const CancelationOptions& options) REQUIRES(mLock); - void synthesizePointerDownEventsForConnectionLocked(const nsecs_t downTime, - const sp& connection, - ftl::Flags targetFlags) - REQUIRES(mLock); + void synthesizePointerDownEventsForConnectionLocked( + const nsecs_t downTime, const std::shared_ptr& connection, + ftl::Flags targetFlags) REQUIRES(mLock); void synthesizeCancelationEventsForWindowLocked( const sp& windowHandle, @@ -658,16 +661,16 @@ private: REQUIRES(mLock); // Interesting events that we might like to log or tell the framework about. - void doDispatchCycleFinishedCommand(nsecs_t finishTime, const sp& connection, - uint32_t seq, bool handled, nsecs_t consumeTime) - REQUIRES(mLock); + void doDispatchCycleFinishedCommand(nsecs_t finishTime, + const std::shared_ptr& connection, uint32_t seq, + bool handled, nsecs_t consumeTime) REQUIRES(mLock); void doInterceptKeyBeforeDispatchingCommand(const sp& focusedWindowToken, KeyEntry& entry) REQUIRES(mLock); void onFocusChangedLocked(const FocusResolver::FocusChanges& changes) REQUIRES(mLock); void sendFocusChangedCommandLocked(const sp& oldToken, const sp& newToken) REQUIRES(mLock); void sendDropWindowCommandLocked(const sp& token, float x, float y) REQUIRES(mLock); - void onAnrLocked(const sp& connection) REQUIRES(mLock); + void onAnrLocked(const std::shared_ptr& connection) REQUIRES(mLock); void onAnrLocked(std::shared_ptr application) REQUIRES(mLock); void updateLastAnrStateLocked(const sp& window, const std::string& reason) REQUIRES(mLock); @@ -675,10 +678,10 @@ private: const std::string& reason) REQUIRES(mLock); void updateLastAnrStateLocked(const std::string& windowLabel, const std::string& reason) REQUIRES(mLock); - bool afterKeyEventLockedInterruptable(const sp& connection, + bool afterKeyEventLockedInterruptable(const std::shared_ptr& connection, DispatchEntry* dispatchEntry, KeyEntry& keyEntry, bool handled) REQUIRES(mLock); - bool afterMotionEventLockedInterruptable(const sp& connection, + bool afterMotionEventLockedInterruptable(const std::shared_ptr& connection, DispatchEntry* dispatchEntry, MotionEntry& motionEntry, bool handled) REQUIRES(mLock); -- GitLab From 403e53c1f62070bac72a1cbf07b5e3f2c54c0d39 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Tue, 18 Apr 2023 11:46:56 +0000 Subject: [PATCH 1157/1310] InputMapper refactor: TouchInputMapper::Parameters Refactor TouchInputMapper to be configured Parameters on initilisation Test: m checkinput && atest --host inputflinger_tests Bug: 256009910 Change-Id: I6aae632e31ae2eb1d200d3bd97b1824eccda54a3 --- .../inputflinger/reader/include/InputDevice.h | 4 +- .../reader/mapper/TouchInputMapper.cpp | 96 ++++++++++--------- .../reader/mapper/TouchInputMapper.h | 11 +-- 3 files changed, 58 insertions(+), 53 deletions(-) diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 73351c407a..63035466cc 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -407,7 +407,7 @@ public: inline const std::string getName() const { return mDevice.getName(); } inline const std::string getDescriptor() { return mDevice.getDescriptor(); } inline const std::string getLocation() { return mDevice.getLocation(); } - inline bool isExternal() { return mDevice.isExternal(); } + inline bool isExternal() const { return mDevice.isExternal(); } inline std::optional getAssociatedDisplayPort() const { return mDevice.getAssociatedDisplayPort(); } @@ -424,7 +424,7 @@ public: return mDevice.cancelTouch(when, readTime); } inline void bumpGeneration() { mDevice.bumpGeneration(); } - inline const PropertyMap& getConfiguration() { return mDevice.getConfiguration(); } + inline const PropertyMap& getConfiguration() const { return mDevice.getConfiguration(); } private: InputDevice& mDevice; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 55b869a735..c72a263dcd 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -125,9 +125,8 @@ TouchInputMapper::TouchInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig) : InputMapper(deviceContext, readerConfig), mTouchButtonAccumulator(deviceContext), - mSource(0), - mDeviceMode(DeviceMode::DISABLED), - mInputDeviceOrientation(ui::ROTATION_0) {} + mConfig(readerConfig), + mParameters(computeParameters(deviceContext)) {} TouchInputMapper::~TouchInputMapper() {} @@ -300,7 +299,7 @@ std::list TouchInputMapper::reconfigure(nsecs_t when, // various other parameters so should result in a reconfiguration. if (!changes.any() || changes.test(InputReaderConfiguration::Change::DEVICE_TYPE)) { // Configure basic parameters. - configureParameters(); + mParameters = computeParameters(getDeviceContext()); // Configure common accumulators. mCursorScrollAccumulator.configure(getDeviceContext()); @@ -361,112 +360,119 @@ void TouchInputMapper::resolveExternalStylusPresence() { } } -void TouchInputMapper::configureParameters() { +TouchInputMapper::Parameters TouchInputMapper::computeParameters( + const InputDeviceContext& deviceContext) { + Parameters parameters; // Use the pointer presentation mode for devices that do not support distinct // multitouch. The spot-based presentation relies on being able to accurately // locate two or more fingers on the touch pad. - mParameters.gestureMode = getDeviceContext().hasInputProperty(INPUT_PROP_SEMI_MT) + parameters.gestureMode = deviceContext.hasInputProperty(INPUT_PROP_SEMI_MT) ? Parameters::GestureMode::SINGLE_TOUCH : Parameters::GestureMode::MULTI_TOUCH; - const PropertyMap& config = getDeviceContext().getConfiguration(); + const PropertyMap& config = deviceContext.getConfiguration(); std::optional gestureModeString = config.getString("touch.gestureMode"); if (gestureModeString.has_value()) { if (*gestureModeString == "single-touch") { - mParameters.gestureMode = Parameters::GestureMode::SINGLE_TOUCH; + parameters.gestureMode = Parameters::GestureMode::SINGLE_TOUCH; } else if (*gestureModeString == "multi-touch") { - mParameters.gestureMode = Parameters::GestureMode::MULTI_TOUCH; + parameters.gestureMode = Parameters::GestureMode::MULTI_TOUCH; } else if (*gestureModeString != "default") { ALOGW("Invalid value for touch.gestureMode: '%s'", gestureModeString->c_str()); } } - configureDeviceType(); + parameters.deviceType = computeDeviceType(deviceContext); - mParameters.hasButtonUnderPad = getDeviceContext().hasInputProperty(INPUT_PROP_BUTTONPAD); + parameters.hasButtonUnderPad = deviceContext.hasInputProperty(INPUT_PROP_BUTTONPAD); - mParameters.orientationAware = + parameters.orientationAware = config.getBool("touch.orientationAware") - .value_or(mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN); + .value_or(parameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN); - mParameters.orientation = ui::ROTATION_0; + parameters.orientation = ui::ROTATION_0; std::optional orientationString = config.getString("touch.orientation"); if (orientationString.has_value()) { - if (mParameters.deviceType != Parameters::DeviceType::TOUCH_SCREEN) { + if (parameters.deviceType != Parameters::DeviceType::TOUCH_SCREEN) { ALOGW("The configuration 'touch.orientation' is only supported for touchscreens."); } else if (*orientationString == "ORIENTATION_90") { - mParameters.orientation = ui::ROTATION_90; + parameters.orientation = ui::ROTATION_90; } else if (*orientationString == "ORIENTATION_180") { - mParameters.orientation = ui::ROTATION_180; + parameters.orientation = ui::ROTATION_180; } else if (*orientationString == "ORIENTATION_270") { - mParameters.orientation = ui::ROTATION_270; + parameters.orientation = ui::ROTATION_270; } else if (*orientationString != "ORIENTATION_0") { ALOGW("Invalid value for touch.orientation: '%s'", orientationString->c_str()); } } - mParameters.hasAssociatedDisplay = false; - mParameters.associatedDisplayIsExternal = false; - if (mParameters.orientationAware || - mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN || - mParameters.deviceType == Parameters::DeviceType::POINTER || - (mParameters.deviceType == Parameters::DeviceType::TOUCH_NAVIGATION && - getDeviceContext().getAssociatedViewport())) { - mParameters.hasAssociatedDisplay = true; - if (mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN) { - mParameters.associatedDisplayIsExternal = getDeviceContext().isExternal(); - mParameters.uniqueDisplayId = config.getString("touch.displayId").value_or("").c_str(); + parameters.hasAssociatedDisplay = false; + parameters.associatedDisplayIsExternal = false; + if (parameters.orientationAware || + parameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN || + parameters.deviceType == Parameters::DeviceType::POINTER || + (parameters.deviceType == Parameters::DeviceType::TOUCH_NAVIGATION && + deviceContext.getAssociatedViewport())) { + parameters.hasAssociatedDisplay = true; + if (parameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN) { + parameters.associatedDisplayIsExternal = deviceContext.isExternal(); + parameters.uniqueDisplayId = config.getString("touch.displayId").value_or("").c_str(); } } - if (getDeviceContext().getAssociatedDisplayPort()) { - mParameters.hasAssociatedDisplay = true; + if (deviceContext.getAssociatedDisplayPort()) { + parameters.hasAssociatedDisplay = true; } // Initial downs on external touch devices should wake the device. // Normally we don't do this for internal touch screens to prevent them from waking // up in your pocket but you can enable it using the input device configuration. - mParameters.wake = config.getBool("touch.wake").value_or(getDeviceContext().isExternal()); + parameters.wake = config.getBool("touch.wake").value_or(deviceContext.isExternal()); std::optional usiVersionMajor = config.getInt("touch.usiVersionMajor"); std::optional usiVersionMinor = config.getInt("touch.usiVersionMinor"); if (usiVersionMajor.has_value() && usiVersionMinor.has_value()) { - mParameters.usiVersion = { + parameters.usiVersion = { .majorVersion = *usiVersionMajor, .minorVersion = *usiVersionMinor, }; } - mParameters.enableForInactiveViewport = + parameters.enableForInactiveViewport = config.getBool("touch.enableForInactiveViewport").value_or(false); + + return parameters; } -void TouchInputMapper::configureDeviceType() { - if (getDeviceContext().hasInputProperty(INPUT_PROP_DIRECT)) { +TouchInputMapper::Parameters::DeviceType TouchInputMapper::computeDeviceType( + const InputDeviceContext& deviceContext) { + Parameters::DeviceType deviceType; + if (deviceContext.hasInputProperty(INPUT_PROP_DIRECT)) { // The device is a touch screen. - mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN; - } else if (getDeviceContext().hasInputProperty(INPUT_PROP_POINTER)) { + deviceType = Parameters::DeviceType::TOUCH_SCREEN; + } else if (deviceContext.hasInputProperty(INPUT_PROP_POINTER)) { // The device is a pointing device like a track pad. - mParameters.deviceType = Parameters::DeviceType::POINTER; + deviceType = Parameters::DeviceType::POINTER; } else { // The device is a touch pad of unknown purpose. - mParameters.deviceType = Parameters::DeviceType::POINTER; + deviceType = Parameters::DeviceType::POINTER; } // Type association takes precedence over the device type found in the idc file. - std::string deviceTypeString = getDeviceContext().getDeviceTypeAssociation().value_or(""); + std::string deviceTypeString = deviceContext.getDeviceTypeAssociation().value_or(""); if (deviceTypeString.empty()) { deviceTypeString = - getDeviceContext().getConfiguration().getString("touch.deviceType").value_or(""); + deviceContext.getConfiguration().getString("touch.deviceType").value_or(""); } if (deviceTypeString == "touchScreen") { - mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN; + deviceType = Parameters::DeviceType::TOUCH_SCREEN; } else if (deviceTypeString == "touchNavigation") { - mParameters.deviceType = Parameters::DeviceType::TOUCH_NAVIGATION; + deviceType = Parameters::DeviceType::TOUCH_NAVIGATION; } else if (deviceTypeString == "pointer") { - mParameters.deviceType = Parameters::DeviceType::POINTER; + deviceType = Parameters::DeviceType::POINTER; } else if (deviceTypeString != "default" && deviceTypeString != "") { ALOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.c_str()); } + return deviceType; } void TouchInputMapper::dumpParameters(std::string& dump) { diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index e023e90758..7141924e83 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -192,7 +192,7 @@ protected: }; // Input sources and device mode. - uint32_t mSource; + uint32_t mSource{0}; enum class DeviceMode { DISABLED, // input is disabled @@ -203,7 +203,7 @@ protected: ftl_last = POINTER }; - DeviceMode mDeviceMode; + DeviceMode mDeviceMode{DeviceMode::DISABLED}; // The reader's configuration. InputReaderConfiguration mConfig; @@ -377,7 +377,6 @@ protected: std::vector mVirtualKeys; - virtual void configureParameters(); virtual void dumpParameters(std::string& dump); virtual void configureRawPointerAxes(); virtual void dumpRawPointerAxes(std::string& dump); @@ -413,7 +412,7 @@ private: // The orientation of the input device relative to that of the display panel. It specifies // the rotation of the input device coordinates required to produce the display panel // orientation, so it will depend on whether the device is orientation aware. - ui::Rotation mInputDeviceOrientation; + ui::Rotation mInputDeviceOrientation{ui::ROTATION_0}; // The transform that maps the input device's raw coordinate space to the un-rotated display's // coordinate space. InputReader generates events in the un-rotated display's coordinate space. @@ -824,8 +823,8 @@ private: // Compute input transforms for DIRECT and POINTER modes. void computeInputTransforms(); - - void configureDeviceType(); + static Parameters::DeviceType computeDeviceType(const InputDeviceContext& deviceContext); + static Parameters computeParameters(const InputDeviceContext& deviceContext); }; } // namespace android -- GitLab From 39d25347953d6d7551e6d56c94d4e90cdb22b20b Mon Sep 17 00:00:00 2001 From: Leon Scroggins Date: Wed, 19 Apr 2023 17:11:01 +0000 Subject: [PATCH 1158/1310] Revert "Only call onNewVsyncSchedule if the pacesetter changes" This reverts commit d649463aa27ae1aea7101afc4684cc9422d4808a. Reason for revert: Speculative fix for a regression in SF performance. Bug: 278987666 Test: APC Change-Id: I5bf393a2eef4dc7254ccf931a0fab3c67eb315ad --- .../surfaceflinger/Scheduler/Scheduler.cpp | 34 ++++++------------- services/surfaceflinger/Scheduler/Scheduler.h | 4 +-- .../tests/unittests/SchedulerTest.cpp | 19 ----------- 3 files changed, 11 insertions(+), 46 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 8ddcfa1ee6..3e12db61e7 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -130,7 +130,7 @@ void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId, pacesetterVsyncSchedule = promotePacesetterDisplayLocked(); } - applyNewVsyncScheduleIfNonNull(std::move(pacesetterVsyncSchedule)); + applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); } void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { @@ -149,7 +149,7 @@ void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { pacesetterVsyncSchedule = promotePacesetterDisplayLocked(); } - applyNewVsyncScheduleIfNonNull(std::move(pacesetterVsyncSchedule)); + applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); } void Scheduler::run() { @@ -693,17 +693,16 @@ void Scheduler::promotePacesetterDisplay(std::optional pacese pacesetterVsyncSchedule = promotePacesetterDisplayLocked(pacesetterIdOpt); } - applyNewVsyncScheduleIfNonNull(std::move(pacesetterVsyncSchedule)); + applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); } std::shared_ptr Scheduler::promotePacesetterDisplayLocked( std::optional pacesetterIdOpt) { // TODO(b/241286431): Choose the pacesetter display. - const auto oldPacesetterDisplayIdOpt = mPacesetterDisplayId; mPacesetterDisplayId = pacesetterIdOpt.value_or(mRefreshRateSelectors.begin()->first); ALOGI("Display %s is the pacesetter", to_string(*mPacesetterDisplayId).c_str()); - auto newVsyncSchedule = getVsyncScheduleLocked(*mPacesetterDisplayId); + auto vsyncSchedule = getVsyncScheduleLocked(*mPacesetterDisplayId); if (const auto pacesetterPtr = pacesetterSelectorPtrLocked()) { pacesetterPtr->setIdleTimerCallbacks( {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); }, @@ -714,28 +713,15 @@ std::shared_ptr Scheduler::promotePacesetterDisplayLocked( pacesetterPtr->startIdleTimer(); - // Track the new period, which may have changed due to switching to a - // new pacesetter or due to a hotplug event. In the former case, this - // is important so that VSYNC modulation does not get stuck in the - // initiated state if a transition started on the old pacesetter. const Fps refreshRate = pacesetterPtr->getActiveMode().modePtr->getFps(); - newVsyncSchedule->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod(), - true /* force */); + vsyncSchedule->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod(), + true /* force */); } - if (oldPacesetterDisplayIdOpt == mPacesetterDisplayId) { - return nullptr; - } - return newVsyncSchedule; + return vsyncSchedule; } -void Scheduler::applyNewVsyncScheduleIfNonNull( - std::shared_ptr pacesetterSchedulePtr) { - if (!pacesetterSchedulePtr) { - // The pacesetter has not changed, so there is no new VsyncSchedule to - // apply. - return; - } - onNewVsyncSchedule(pacesetterSchedulePtr->getDispatch()); +void Scheduler::applyNewVsyncSchedule(std::shared_ptr vsyncSchedule) { + onNewVsyncSchedule(vsyncSchedule->getDispatch()); std::vector threads; { std::lock_guard lock(mConnectionsLock); @@ -745,7 +731,7 @@ void Scheduler::applyNewVsyncScheduleIfNonNull( } } for (auto* thread : threads) { - thread->onNewVsyncSchedule(pacesetterSchedulePtr); + thread->onNewVsyncSchedule(vsyncSchedule); } } diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 720a1cbba2..3423652163 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -329,12 +329,10 @@ private: // MessageQueue and EventThread need to use the new pacesetter's // VsyncSchedule, and this must happen while mDisplayLock is *not* locked, // or else we may deadlock with EventThread. - // Returns the new pacesetter's VsyncSchedule, or null if the pacesetter is - // unchanged. std::shared_ptr promotePacesetterDisplayLocked( std::optional pacesetterIdOpt = std::nullopt) REQUIRES(kMainThreadContext, mDisplayLock); - void applyNewVsyncScheduleIfNonNull(std::shared_ptr) EXCLUDES(mDisplayLock); + void applyNewVsyncSchedule(std::shared_ptr) EXCLUDES(mDisplayLock); // Blocks until the pacesetter's idle timer thread exits. `mDisplayLock` must not be locked by // the caller on the main thread to avoid deadlock, since the timer thread locks it before exit. diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 0c43831455..dc76b4c90f 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -384,23 +384,4 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { } } -TEST_F(SchedulerTest, changingPacesetterChangesVsyncSchedule) { - // Add a second display so we can change the pacesetter. - mScheduler->registerDisplay(kDisplayId2, - std::make_shared(kDisplay2Modes, - kDisplay2Mode60->getId())); - // Ensure that the pacesetter is the one we expect. - mScheduler->setPacesetterDisplay(kDisplayId1); - - // Switching to the other will call onNewVsyncSchedule. - EXPECT_CALL(*mEventThread, onNewVsyncSchedule(mScheduler->getVsyncSchedule(kDisplayId2))) - .Times(1); - mScheduler->setPacesetterDisplay(kDisplayId2); -} - -TEST_F(SchedulerTest, promotingSamePacesetterDoesNotChangeVsyncSchedule) { - EXPECT_CALL(*mEventThread, onNewVsyncSchedule(_)).Times(0); - mScheduler->setPacesetterDisplay(kDisplayId1); -} - } // namespace android::scheduler -- GitLab From 9f746f631d3608cabbc749bd06dcccf4e6949126 Mon Sep 17 00:00:00 2001 From: Chris Glover Date: Thu, 20 Apr 2023 10:23:22 +0100 Subject: [PATCH 1159/1310] Add support for EGL_EXT_gl_colorspace_bt2020_hlg to libEGL This implements EGL_EXT_gl_colorspace_bt2020_hlg in terms of HAL data formats such that applications can create HLG encoded surfaces. As per the spec, applications need to ensure correct HLG encoding, which made it possible to implement this extension without involving the driver. The change includes a manual update of eglext.h because the spec is not yet ready. https://github.com/KhronosGroup/EGL-Registry/pull/177 Test: Manually ran tests implemented in dEQP in a separate change https://gerrit.khronos.org/c/vk-gl-cts/+/11608 Bug: 277210442 Change-Id: I3e011a786930d978fbc0d86aad29d25e4cdcc148 (cherry picked from commit 82396e8990bdffde91b9249a4edbeb03b3e73819) Merged-In: I3e011a786930d978fbc0d86aad29d25e4cdcc148 --- opengl/include/EGL/eglext.h | 5 +++++ opengl/libs/EGL/egl_display.cpp | 5 +++-- opengl/libs/EGL/egl_platform_entries.cpp | 9 ++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/opengl/include/EGL/eglext.h b/opengl/include/EGL/eglext.h index 501bf58531..32c21f61b9 100644 --- a/opengl/include/EGL/eglext.h +++ b/opengl/include/EGL/eglext.h @@ -697,6 +697,11 @@ EGLAPI EGLBoolean EGLAPIENTRY eglQueryDisplayAttribEXT (EGLDisplay dpy, EGLint a #define EGL_EXT_device_query 1 #endif /* EGL_EXT_device_query */ +#ifndef EGL_EXT_gl_colorspace_bt2020_hlg +#define EGL_EXT_gl_colorspace_bt2020_hlg 1 +#define EGL_GL_COLORSPACE_BT2020_HLG_EXT 0x333E +#endif /* EGL_EXT_gl_colorspace_bt2020_hlg */ + #ifndef EGL_EXT_gl_colorspace_bt2020_linear #define EGL_EXT_gl_colorspace_bt2020_linear 1 #define EGL_GL_COLORSPACE_BT2020_LINEAR_EXT 0x333F diff --git a/opengl/libs/EGL/egl_display.cpp b/opengl/libs/EGL/egl_display.cpp index c2c856e22a..9823fc839e 100644 --- a/opengl/libs/EGL/egl_display.cpp +++ b/opengl/libs/EGL/egl_display.cpp @@ -353,8 +353,9 @@ EGLBoolean egl_display_t::initialize(EGLint* major, EGLint* minor) { // Typically that means there is an HDR capable display attached, but could be // support for attaching an HDR display. In either case, advertise support for // HDR color spaces. - mExtensionString.append( - "EGL_EXT_gl_colorspace_bt2020_linear EGL_EXT_gl_colorspace_bt2020_pq "); + mExtensionString.append("EGL_EXT_gl_colorspace_bt2020_hlg " + "EGL_EXT_gl_colorspace_bt2020_linear " + "EGL_EXT_gl_colorspace_bt2020_pq "); } char const* start = gExtensionString; diff --git a/opengl/libs/EGL/egl_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp index 2bca14d2f9..48718bb78a 100644 --- a/opengl/libs/EGL/egl_platform_entries.cpp +++ b/opengl/libs/EGL/egl_platform_entries.cpp @@ -18,6 +18,7 @@ #include "egl_platform_entries.h" +#include #include #include #include @@ -29,7 +30,6 @@ #include #include #include -#include #include #include @@ -421,11 +421,14 @@ static android_dataspace dataSpaceFromEGLColorSpace(EGLint colorspace) { return HAL_DATASPACE_V0_SCRGB; } else if (colorspace == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT) { return HAL_DATASPACE_V0_SCRGB_LINEAR; + } else if (colorspace == EGL_GL_COLORSPACE_BT2020_HLG_EXT) { + return static_cast(HAL_DATASPACE_BT2020_HLG); } else if (colorspace == EGL_GL_COLORSPACE_BT2020_LINEAR_EXT) { return HAL_DATASPACE_BT2020_LINEAR; } else if (colorspace == EGL_GL_COLORSPACE_BT2020_PQ_EXT) { return HAL_DATASPACE_BT2020_PQ; } + return HAL_DATASPACE_UNKNOWN; } @@ -452,6 +455,9 @@ static std::vector getDriverColorSpaces(egl_display_t* dp) { if (findExtension(dp->disp.queryString.extensions, "EGL_EXT_gl_colorspace_scrgb_linear")) { colorSpaces.push_back(EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT); } + if (findExtension(dp->disp.queryString.extensions, "EGL_EXT_gl_colorspace_bt2020_hlg")) { + colorSpaces.push_back(EGL_GL_COLORSPACE_BT2020_HLG_EXT); + } if (findExtension(dp->disp.queryString.extensions, "EGL_EXT_gl_colorspace_bt2020_linear")) { colorSpaces.push_back(EGL_GL_COLORSPACE_BT2020_LINEAR_EXT); } @@ -485,6 +491,7 @@ static EGLBoolean processAttributes(egl_display_t* dp, ANativeWindow* window, case EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT: case EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT: case EGL_GL_COLORSPACE_SCRGB_EXT: + case EGL_GL_COLORSPACE_BT2020_HLG_EXT: case EGL_GL_COLORSPACE_BT2020_LINEAR_EXT: case EGL_GL_COLORSPACE_BT2020_PQ_EXT: case EGL_GL_COLORSPACE_DISPLAY_P3_LINEAR_EXT: -- GitLab From c404cb4288475dcb48986d38f857400cc474f257 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Fri, 3 Mar 2023 19:57:53 -0500 Subject: [PATCH 1160/1310] SF: Merge per-display maps in Scheduler Fixes: 266715559 Test: libsurfaceflinger_unittest Change-Id: Ia8934f73f237c37e8f477be5183f84a9c1a40a5a --- .../surfaceflinger/Scheduler/Scheduler.cpp | 118 ++++++++++-------- services/surfaceflinger/Scheduler/Scheduler.h | 73 +++++++---- .../tests/unittests/TestableScheduler.h | 4 - 3 files changed, 113 insertions(+), 82 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 3e12db61e7..63a0173519 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -50,8 +50,9 @@ #include "FrontEnd/LayerHandle.h" #include "OneShotTimer.h" #include "SurfaceFlingerProperties.h" -#include "VSyncPredictor.h" -#include "VSyncReactor.h" +#include "VSyncTracker.h" +#include "VsyncController.h" +#include "VsyncSchedule.h" #define RETURN_IF_INVALID_HANDLE(handle, ...) \ do { \ @@ -119,14 +120,13 @@ void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelector void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr, - std::shared_ptr vsyncSchedule) { + VsyncSchedulePtr schedulePtr) { demotePacesetterDisplay(); std::shared_ptr pacesetterVsyncSchedule; { std::scoped_lock lock(mDisplayLock); - mRefreshRateSelectors.emplace_or_replace(displayId, std::move(selectorPtr)); - mVsyncSchedules.emplace_or_replace(displayId, std::move(vsyncSchedule)); + mDisplays.emplace_or_replace(displayId, std::move(selectorPtr), std::move(schedulePtr)); pacesetterVsyncSchedule = promotePacesetterDisplayLocked(); } @@ -139,13 +139,12 @@ void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { std::shared_ptr pacesetterVsyncSchedule; { std::scoped_lock lock(mDisplayLock); - mRefreshRateSelectors.erase(displayId); - mVsyncSchedules.erase(displayId); + mDisplays.erase(displayId); // Do not allow removing the final display. Code in the scheduler expects // there to be at least one display. (This may be relaxed in the future with // headless virtual display.) - LOG_ALWAYS_FATAL_IF(mRefreshRateSelectors.empty(), "Cannot unregister all displays!"); + LOG_ALWAYS_FATAL_IF(mDisplays.empty(), "Cannot unregister all displays!"); pacesetterVsyncSchedule = promotePacesetterDisplayLocked(); } @@ -199,20 +198,27 @@ impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const { return [this](uid_t uid) { - const Fps refreshRate = pacesetterSelectorPtr()->getActiveMode().fps; - const nsecs_t currentPeriod = - getVsyncSchedule()->period().ns() ?: refreshRate.getPeriodNsecs(); + const auto [refreshRate, period] = [this] { + std::scoped_lock lock(mDisplayLock); + const auto pacesetterOpt = pacesetterDisplayLocked(); + LOG_ALWAYS_FATAL_IF(!pacesetterOpt); + const Display& pacesetter = *pacesetterOpt; + return std::make_pair(pacesetter.selectorPtr->getActiveMode().fps, + pacesetter.schedulePtr->period()); + }(); + + const Period currentPeriod = period != Period::zero() ? period : refreshRate.getPeriod(); const auto frameRate = getFrameRateOverride(uid); if (!frameRate.has_value()) { - return currentPeriod; + return currentPeriod.ns(); } const auto divisor = RefreshRateSelector::getFrameRateDivisor(refreshRate, *frameRate); if (divisor <= 1) { - return currentPeriod; + return currentPeriod.ns(); } - return currentPeriod * divisor; + return currentPeriod.ns() * divisor; }; } @@ -413,23 +419,24 @@ void Scheduler::resyncAllToHardwareVsync(bool allowToEnable) { std::scoped_lock lock(mDisplayLock); ftl::FakeGuard guard(kMainThreadContext); - for (const auto& [id, _] : mRefreshRateSelectors) { + for (const auto& [id, _] : mDisplays) { resyncToHardwareVsyncLocked(id, allowToEnable); } } void Scheduler::resyncToHardwareVsyncLocked(PhysicalDisplayId id, bool allowToEnable, std::optional refreshRate) { - auto schedule = getVsyncScheduleLocked(id); - if (schedule->isHardwareVsyncAllowed(allowToEnable)) { + const auto displayOpt = mDisplays.get(id); + LOG_ALWAYS_FATAL_IF(!displayOpt); + const Display& display = *displayOpt; + + if (display.schedulePtr->isHardwareVsyncAllowed(allowToEnable)) { if (!refreshRate) { - auto selectorPtr = mRefreshRateSelectors.get(id); - LOG_ALWAYS_FATAL_IF(!selectorPtr); - refreshRate = selectorPtr->get()->getActiveMode().modePtr->getFps(); + refreshRate = display.selectorPtr->getActiveMode().modePtr->getFps(); } if (refreshRate->isValid()) { - schedule->startPeriodTransition(mSchedulerCallback, refreshRate->getPeriod(), - false /* force */); + display.schedulePtr->startPeriodTransition(mSchedulerCallback, refreshRate->getPeriod(), + false /* force */); } } } @@ -438,9 +445,10 @@ void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate) { std::scoped_lock lock(mDisplayLock); ftl::FakeGuard guard(kMainThreadContext); - auto selectorPtr = mRefreshRateSelectors.get(id); - LOG_ALWAYS_FATAL_IF(!selectorPtr); - const auto mode = selectorPtr->get()->getActiveMode(); + const auto displayOpt = mDisplays.get(id); + LOG_ALWAYS_FATAL_IF(!displayOpt); + const Display& display = *displayOpt; + const auto mode = display.selectorPtr->getActiveMode(); using fps_approx_ops::operator!=; LOG_ALWAYS_FATAL_IF(renderFrameRate != mode.fps, @@ -451,7 +459,7 @@ void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate) { ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(), to_string(mode.modePtr->getFps()).c_str()); - getVsyncScheduleLocked(id)->getTracker().setRenderRate(renderFrameRate); + display.schedulePtr->getTracker().setRenderRate(renderFrameRate); } void Scheduler::resync() { @@ -558,22 +566,24 @@ void Scheduler::setDisplayPowerMode(PhysicalDisplayId id, hal::PowerMode powerMo mLayerHistory.clear(); } -std::shared_ptr Scheduler::getVsyncSchedule( - std::optional idOpt) const { +auto Scheduler::getVsyncSchedule(std::optional idOpt) const + -> ConstVsyncSchedulePtr { std::scoped_lock lock(mDisplayLock); return getVsyncScheduleLocked(idOpt); } -std::shared_ptr Scheduler::getVsyncScheduleLocked( - std::optional idOpt) const { +auto Scheduler::getVsyncScheduleLocked(std::optional idOpt) const + -> ConstVsyncSchedulePtr { ftl::FakeGuard guard(kMainThreadContext); + if (!idOpt) { LOG_ALWAYS_FATAL_IF(!mPacesetterDisplayId, "Missing a pacesetter!"); idOpt = mPacesetterDisplayId; } - auto scheduleOpt = mVsyncSchedules.get(*idOpt); - LOG_ALWAYS_FATAL_IF(!scheduleOpt); - return std::const_pointer_cast(scheduleOpt->get()); + + const auto displayOpt = mDisplays.get(*idOpt); + LOG_ALWAYS_FATAL_IF(!displayOpt); + return displayOpt->get().schedulePtr; } void Scheduler::kernelIdleTimerCallback(TimerState state) { @@ -597,9 +607,9 @@ void Scheduler::kernelIdleTimerCallback(TimerState state) { // need to update the VsyncController model anyway. std::scoped_lock lock(mDisplayLock); ftl::FakeGuard guard(kMainThreadContext); - constexpr bool disallow = false; - for (auto& [_, schedule] : mVsyncSchedules) { - schedule->disableHardwareVsync(mSchedulerCallback, disallow); + for (const auto& [_, display] : mDisplays) { + constexpr bool kDisallow = false; + display.schedulePtr->disableHardwareVsync(mSchedulerCallback, kDisallow); } } @@ -664,12 +674,12 @@ void Scheduler::dumpVsync(std::string& out) const { to_string(*mPacesetterDisplayId).c_str()); getVsyncScheduleLocked()->dump(out); } - for (auto& [id, vsyncSchedule] : mVsyncSchedules) { + for (auto& [id, display] : mDisplays) { if (id == mPacesetterDisplayId) { continue; } base::StringAppendF(&out, "VsyncSchedule for follower %s:\n", to_string(id).c_str()); - vsyncSchedule->dump(out); + display.schedulePtr->dump(out); } } @@ -699,25 +709,29 @@ void Scheduler::promotePacesetterDisplay(std::optional pacese std::shared_ptr Scheduler::promotePacesetterDisplayLocked( std::optional pacesetterIdOpt) { // TODO(b/241286431): Choose the pacesetter display. - mPacesetterDisplayId = pacesetterIdOpt.value_or(mRefreshRateSelectors.begin()->first); + mPacesetterDisplayId = pacesetterIdOpt.value_or(mDisplays.begin()->first); ALOGI("Display %s is the pacesetter", to_string(*mPacesetterDisplayId).c_str()); - auto vsyncSchedule = getVsyncScheduleLocked(*mPacesetterDisplayId); - if (const auto pacesetterPtr = pacesetterSelectorPtrLocked()) { - pacesetterPtr->setIdleTimerCallbacks( + std::shared_ptr newVsyncSchedulePtr; + if (const auto pacesetterOpt = pacesetterDisplayLocked()) { + const Display& pacesetter = *pacesetterOpt; + + pacesetter.selectorPtr->setIdleTimerCallbacks( {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); }, .onExpired = [this] { idleTimerCallback(TimerState::Expired); }}, .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); }, .onExpired = [this] { kernelIdleTimerCallback(TimerState::Expired); }}}); - pacesetterPtr->startIdleTimer(); + pacesetter.selectorPtr->startIdleTimer(); + + newVsyncSchedulePtr = pacesetter.schedulePtr; - const Fps refreshRate = pacesetterPtr->getActiveMode().modePtr->getFps(); - vsyncSchedule->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod(), - true /* force */); + const Fps refreshRate = pacesetter.selectorPtr->getActiveMode().modePtr->getFps(); + newVsyncSchedulePtr->startPeriodTransition(mSchedulerCallback, refreshRate.getPeriod(), + true /* force */); } - return vsyncSchedule; + return newVsyncSchedulePtr; } void Scheduler::applyNewVsyncSchedule(std::shared_ptr vsyncSchedule) { @@ -831,9 +845,10 @@ auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap { const auto globalSignals = makeGlobalSignals(); - for (const auto& [id, selectorPtr] : mRefreshRateSelectors) { + for (const auto& [id, display] : mDisplays) { auto rankedFrameRates = - selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, globalSignals); + display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, + globalSignals); for (const auto& [frameRateMode, score] : rankedFrameRates.ranking) { const auto [it, inserted] = refreshRateTallies.try_emplace(frameRateMode.fps, score); @@ -852,7 +867,7 @@ auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap { // Find the first refresh rate common to all displays. while (maxScoreIt != refreshRateTallies.cend() && - maxScoreIt->second.displayCount != mRefreshRateSelectors.size()) { + maxScoreIt->second.displayCount != mDisplays.size()) { ++maxScoreIt; } @@ -861,8 +876,7 @@ auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap { for (auto it = maxScoreIt + 1; it != refreshRateTallies.cend(); ++it) { const auto [fps, tally] = *it; - if (tally.displayCount == mRefreshRateSelectors.size() && - tally.score > maxScoreIt->second.score) { + if (tally.displayCount == mDisplays.size() && tally.score > maxScoreIt->second.score) { maxScoreIt = it; } } diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 1f6d378d5a..43aab2d993 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -32,7 +32,6 @@ #include #pragma clang diagnostic pop // ignored "-Wconversion -Wextra" -#include #include #include #include @@ -51,7 +50,6 @@ #include "RefreshRateSelector.h" #include "Utils/Dumper.h" #include "VsyncModulator.h" -#include "VsyncSchedule.h" namespace android::scheduler { @@ -94,6 +92,8 @@ namespace scheduler { using GlobalSignals = RefreshRateSelector::GlobalSignals; +class VsyncSchedule; + class Scheduler : android::impl::MessageQueue { using Impl = android::impl::MessageQueue; @@ -109,6 +109,9 @@ public: using RefreshRateSelectorPtr = std::shared_ptr; + using ConstVsyncSchedulePtr = std::shared_ptr; + using VsyncSchedulePtr = std::shared_ptr; + void registerDisplay(PhysicalDisplayId, RefreshRateSelectorPtr) REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); void unregisterDisplay(PhysicalDisplayId) REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); @@ -237,12 +240,12 @@ public: void setDisplayPowerMode(PhysicalDisplayId, hal::PowerMode powerMode) REQUIRES(kMainThreadContext); - std::shared_ptr getVsyncSchedule( - std::optional idOpt = std::nullopt) const EXCLUDES(mDisplayLock); - std::shared_ptr getVsyncSchedule( - std::optional idOpt = std::nullopt) EXCLUDES(mDisplayLock) { - return std::const_pointer_cast( - static_cast(this)->getVsyncSchedule(idOpt)); + ConstVsyncSchedulePtr getVsyncSchedule(std::optional = std::nullopt) const + EXCLUDES(mDisplayLock); + + VsyncSchedulePtr getVsyncSchedule(std::optional idOpt = std::nullopt) + EXCLUDES(mDisplayLock) { + return std::const_pointer_cast(std::as_const(*this).getVsyncSchedule(idOpt)); } // Returns true if a given vsync timestamp is considered valid vsync @@ -339,9 +342,8 @@ private: // the caller on the main thread to avoid deadlock, since the timer thread locks it before exit. void demotePacesetterDisplay() REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock, mPolicyLock); - void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr, - std::shared_ptr) REQUIRES(kMainThreadContext) - EXCLUDES(mDisplayLock); + void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr, VsyncSchedulePtr) + REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); struct Policy; @@ -420,16 +422,37 @@ private: // must lock for writes but not reads. See also mPolicyLock for locking order. mutable std::mutex mDisplayLock; - display::PhysicalDisplayMap mRefreshRateSelectors - GUARDED_BY(mDisplayLock) GUARDED_BY(kMainThreadContext); + struct Display { + Display(RefreshRateSelectorPtr selectorPtr, VsyncSchedulePtr schedulePtr) + : selectorPtr(std::move(selectorPtr)), schedulePtr(std::move(schedulePtr)) {} + + // Effectively const except in move constructor. + RefreshRateSelectorPtr selectorPtr; + VsyncSchedulePtr schedulePtr; + }; + + using DisplayRef = std::reference_wrapper; + using ConstDisplayRef = std::reference_wrapper; - // TODO (b/266715559): Store in the same map as mRefreshRateSelectors. - display::PhysicalDisplayMap> mVsyncSchedules - GUARDED_BY(mDisplayLock) GUARDED_BY(kMainThreadContext); + display::PhysicalDisplayMap mDisplays GUARDED_BY(mDisplayLock) + GUARDED_BY(kMainThreadContext); ftl::Optional mPacesetterDisplayId GUARDED_BY(mDisplayLock) GUARDED_BY(kMainThreadContext); + ftl::Optional pacesetterDisplayLocked() REQUIRES(mDisplayLock) { + return static_cast(this)->pacesetterDisplayLocked().transform( + [](const Display& display) { return std::ref(const_cast(display)); }); + } + + ftl::Optional pacesetterDisplayLocked() const REQUIRES(mDisplayLock) { + ftl::FakeGuard guard(kMainThreadContext); + return mPacesetterDisplayId.and_then([this](PhysicalDisplayId pacesetterId) + REQUIRES(mDisplayLock, kMainThreadContext) { + return mDisplays.get(pacesetterId); + }); + } + RefreshRateSelectorPtr pacesetterSelectorPtr() const EXCLUDES(mDisplayLock) { std::scoped_lock lock(mDisplayLock); return pacesetterSelectorPtrLocked(); @@ -437,19 +460,17 @@ private: RefreshRateSelectorPtr pacesetterSelectorPtrLocked() const REQUIRES(mDisplayLock) { ftl::FakeGuard guard(kMainThreadContext); - return mPacesetterDisplayId - .and_then([this](PhysicalDisplayId pacesetterId) - REQUIRES(mDisplayLock, kMainThreadContext) { - return mRefreshRateSelectors.get(pacesetterId); - }) - .or_else(ftl::static_ref([] { return nullptr; })) + return pacesetterDisplayLocked() + .transform([](const Display& display) { return display.selectorPtr; }) + .or_else([] { return std::optional(nullptr); }) .value(); } - std::shared_ptr getVsyncScheduleLocked( - std::optional idOpt = std::nullopt) const REQUIRES(mDisplayLock); - std::shared_ptr getVsyncScheduleLocked( - std::optional idOpt = std::nullopt) REQUIRES(mDisplayLock) { + ConstVsyncSchedulePtr getVsyncScheduleLocked( + std::optional = std::nullopt) const REQUIRES(mDisplayLock); + + VsyncSchedulePtr getVsyncScheduleLocked(std::optional idOpt = std::nullopt) + REQUIRES(mDisplayLock) { return std::const_pointer_cast( static_cast(this)->getVsyncScheduleLocked(idOpt)); } diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index f1a5fc46bf..3b6a987796 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -65,10 +65,6 @@ public: auto refreshRateSelector() { return pacesetterSelectorPtr(); } - const auto& refreshRateSelectors() const NO_THREAD_SAFETY_ANALYSIS { - return mRefreshRateSelectors; - } - void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) { registerDisplay(displayId, std::move(selectorPtr), std::make_unique(), -- GitLab From 22be349cc5298a7f88af478f6ff673778563730d Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Thu, 20 Apr 2023 10:08:21 -0700 Subject: [PATCH 1161/1310] [sf] fix layer trace generation with new fe Fixes an incorrect rebase. Test: presubmit Bug: 238781169 Change-Id: I13596a035dc930447ee5c3b66a420e43b7a8b737 --- services/surfaceflinger/SurfaceFlinger.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 8394ffbca8..38bde2b173 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5825,8 +5825,8 @@ LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const { return layersProto; } - return LayerProtoFromSnapshotGenerator(mLayerSnapshotBuilder, mFrontEndDisplayInfos, {}, - traceFlags) + return LayerProtoFromSnapshotGenerator(mLayerSnapshotBuilder, mFrontEndDisplayInfos, + mLegacyLayers, traceFlags) .generate(mLayerHierarchyBuilder.getHierarchy()); } -- GitLab From 0a6fee80fbda45cd72a8e75948e0925798064648 Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Tue, 11 Apr 2023 18:53:04 +0000 Subject: [PATCH 1162/1310] Force input device changed callback when Keyboard layout info is updated Virtual keyboard creation uses KEYBOARD_LAYOUT_ASSOCIATION reconfig to associated input device with keyboard layout info. But this can happen after input device added (Race condition between device creation and input device added callback.) If input device already added then we sometimes don't get input device changed callback since the generation is same. Test: atest VirtualKeyboardLayoutTest Bug: 277778640 Change-Id: I218b6d797da27ec3a383fbcd2ebeacb0afbf5fab --- include/input/InputDevice.h | 5 +++++ .../inputflinger/reader/mapper/KeyboardInputMapper.cpp | 6 +++++- services/inputflinger/tests/InputReader_test.cpp | 9 +++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h index 66d3435907..1a40fdb90c 100644 --- a/include/input/InputDevice.h +++ b/include/input/InputDevice.h @@ -212,6 +212,11 @@ struct KeyboardLayoutInfo { std::string languageTag; // The layout type such as QWERTY or AZERTY. std::string layoutType; + + inline bool operator==(const KeyboardLayoutInfo& other) const { + return languageTag == other.languageTag && layoutType == other.layoutType; + } + inline bool operator!=(const KeyboardLayoutInfo& other) const { return !(*this == other); } }; // The version of the Universal Stylus Initiative (USI) protocol supported by the input device. diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index 582fb46550..7388752805 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -148,8 +148,12 @@ std::list KeyboardInputMapper::reconfigure(nsecs_t when, if (!changes.any() || changes.test(InputReaderConfiguration::Change::KEYBOARD_LAYOUT_ASSOCIATION)) { - mKeyboardLayoutInfo = + std::optional newKeyboardLayoutInfo = getValueByKey(config.keyboardLayoutAssociations, getDeviceContext().getLocation()); + if (mKeyboardLayoutInfo != newKeyboardLayoutInfo) { + mKeyboardLayoutInfo = newKeyboardLayoutInfo; + bumpGeneration(); + } } return out; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 0b505045bd..d56db113ad 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -3687,6 +3687,7 @@ TEST_F(KeyboardInputMapperTest, Configure_AssignKeyboardLayoutInfo) { mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), /*changes=*/{}); + uint32_t generation = mReader->getContext()->getGeneration(); mFakePolicy->addKeyboardLayoutAssociation(DEVICE_LOCATION, DEVICE_KEYBOARD_LAYOUT_INFO); unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), @@ -3697,6 +3698,14 @@ TEST_F(KeyboardInputMapperTest, Configure_AssignKeyboardLayoutInfo) { deviceInfo.getKeyboardLayoutInfo()->languageTag); ASSERT_EQ(DEVICE_KEYBOARD_LAYOUT_INFO.layoutType, deviceInfo.getKeyboardLayoutInfo()->layoutType); + ASSERT_TRUE(mReader->getContext()->getGeneration() != generation); + + // Call change layout association with the same values: Generation shouldn't change + generation = mReader->getContext()->getGeneration(); + mFakePolicy->addKeyboardLayoutAssociation(DEVICE_LOCATION, DEVICE_KEYBOARD_LAYOUT_INFO); + unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + InputReaderConfiguration::Change::KEYBOARD_LAYOUT_ASSOCIATION); + ASSERT_TRUE(mReader->getContext()->getGeneration() == generation); } TEST_F(KeyboardInputMapperTest, LayoutInfoCorrectlyMapped) { -- GitLab From 87112a7b0f769a096d2101d9456c6360cb61617c Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 20 Apr 2023 19:13:39 +0000 Subject: [PATCH 1163/1310] Notify dispatcher when its configuration needs to be updated Bug: 278992287 Test: Presubmit Change-Id: Ieb743ee37b818affb09baa420d2e75f0a694a2f0 --- services/inputflinger/dispatcher/InputDispatcher.cpp | 9 ++++++++- services/inputflinger/dispatcher/InputDispatcher.h | 4 +++- .../dispatcher/include/InputDispatcherInterface.h | 6 ++++++ services/inputflinger/tests/InputDispatcher_test.cpp | 1 + 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 6b9ad446ce..cc9fc33ae6 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -676,7 +676,6 @@ InputDispatcher::InputDispatcher(const sp& polic SurfaceComposerClient::getDefault()->addWindowInfosListener(mWindowInfoListener); #endif mKeyRepeatState.lastKeyEntry = nullptr; - policy->getDispatcherConfiguration(&mConfig); } InputDispatcher::~InputDispatcher() { @@ -6607,6 +6606,14 @@ void InputDispatcher::cancelCurrentTouch() { mLooper->wake(); } +void InputDispatcher::requestRefreshConfiguration() { + InputDispatcherConfiguration config; + mPolicy->getDispatcherConfiguration(&config); + + std::scoped_lock _l(mLock); + mConfig = config; +} + void InputDispatcher::setMonitorDispatchingTimeoutForTest(std::chrono::nanoseconds timeout) { std::scoped_lock _l(mLock); mMonitorDispatchingTimeout = timeout; diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 7aa1a2d313..dd7f7fe2b0 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -149,6 +149,8 @@ public: void cancelCurrentTouch() override; + void requestRefreshConfiguration() override; + // Public to allow tests to verify that a Monitor can get ANR. void setMonitorDispatchingTimeoutForTest(std::chrono::nanoseconds timeout); @@ -166,7 +168,7 @@ private: std::unique_ptr mThread; sp mPolicy; - android::InputDispatcherConfiguration mConfig; + android::InputDispatcherConfiguration mConfig GUARDED_BY(mLock); std::mutex mLock; diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h index 76dce63aac..c752ddd699 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h @@ -225,6 +225,12 @@ public: * Abort the current touch stream. */ virtual void cancelCurrentTouch() = 0; + + /** + * Request that the InputDispatcher's configuration, which can be obtained through the policy, + * be updated. + */ + virtual void requestRefreshConfiguration() = 0; }; } // namespace android diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 27d7b9c93f..4bd5f73730 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -5284,6 +5284,7 @@ protected: mFakePolicy = sp::make(); mFakePolicy->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY); mDispatcher = std::make_unique(mFakePolicy); + mDispatcher->requestRefreshConfiguration(); mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); ASSERT_EQ(OK, mDispatcher->start()); -- GitLab From db5999423e9b618cd88627f987a43ba9dae1e009 Mon Sep 17 00:00:00 2001 From: Tai Kuo Date: Fri, 21 Apr 2023 17:43:31 +0800 Subject: [PATCH 1164/1310] Update LOG_TAG and setAmplitudeCached timeout To support the force-feedback vibrator, the type of ff_replay length is __u16 (i.e. 65535). 6000000 is out of the bound. Reference: Kernel header file include/uapi/linux/input.h Bug: 279134938 Test: atest \ libvibratorservice_benchmarks:VibratorBench/setAmplitudeCached Change-Id: I59d7776c1a24f0695bd7ce06d334a07b7d3ecf72 --- .../benchmarks/VibratorHalControllerBenchmarks.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp index 53f3daf969..971a0b960e 100644 --- a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp +++ b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "PowerHalControllerBenchmarks" +#define LOG_TAG "VibratorHalControllerBenchmarks" #include #include @@ -183,7 +183,7 @@ BENCHMARK_WRAPPER(VibratorBench, setAmplitudeCached, { return; } - auto duration = 6000s; + auto duration = 60s; auto callback = []() {}; auto amplitude = 1.0f; -- GitLab From 42ccc9346c6036feec6b658405a3905613cf199f Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Thu, 20 Apr 2023 10:08:04 -0400 Subject: [PATCH 1165/1310] SF: Extract SurfaceFlinger::wouldPresentEarly Short-circuit a few fetches and arithmetic operations. Bug: 241285475 Test: libscheduler_test after Idf9f43b37f3479c94a478d154eaa46f43e0c6c9d Change-Id: Ib40794cca0c44f8151ca9f37ba58ac4d892c66ee --- services/surfaceflinger/SurfaceFlinger.cpp | 25 +++++++++++----------- services/surfaceflinger/SurfaceFlinger.h | 4 +++- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 38f8db0945..7171adc951 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2146,7 +2146,13 @@ void SurfaceFlinger::setVsyncEnabled(PhysicalDisplayId id, bool enabled) { })); } -auto SurfaceFlinger::getPreviousPresentFence(TimePoint frameTime, Period vsyncPeriod) +bool SurfaceFlinger::wouldPresentEarly(TimePoint frameTime, Period vsyncPeriod) const { + const bool isThreeVsyncsAhead = mExpectedPresentTime - frameTime > 2 * vsyncPeriod; + return isThreeVsyncsAhead || + mPreviousPresentFences[0].fenceTime->getSignalTime() != Fence::SIGNAL_TIME_PENDING; +} + +auto SurfaceFlinger::getPreviousPresentFence(TimePoint frameTime, Period vsyncPeriod) const -> const FenceTimePtr& { const bool isTwoVsyncsAhead = mExpectedPresentTime - frameTime > vsyncPeriod; const size_t i = static_cast(isTwoVsyncsAhead); @@ -2610,21 +2616,14 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) refreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::milliseconds(mDebugFlashDelay); } - const auto prevVsyncTime = mExpectedPresentTime - mScheduler->getVsyncSchedule()->period(); - const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration; - const Period vsyncPeriod = mScheduler->getVsyncSchedule()->period(); - const bool threeVsyncsAhead = mExpectedPresentTime - frameTime > 2 * vsyncPeriod; - // We should wait for the earliest present time if HWC doesn't support ExpectedPresentTime, - // and the next vsync is not already taken by the previous frame. - const bool waitForEarliestPresent = - !getHwComposer().getComposer()->isSupported( - Hwc2::Composer::OptionalFeature::ExpectedPresentTime) && - (threeVsyncsAhead || - mPreviousPresentFences[0].fenceTime->getSignalTime() != Fence::SIGNAL_TIME_PENDING); + if (!getHwComposer().getComposer()->isSupported( + Hwc2::Composer::OptionalFeature::ExpectedPresentTime) && + wouldPresentEarly(frameTime, vsyncPeriod)) { + const auto prevVsyncTime = mExpectedPresentTime - vsyncPeriod; + const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration; - if (waitForEarliestPresent) { refreshArgs.earliestPresentTime = prevVsyncTime - hwcMinWorkDuration; } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 04fcfb9097..994c9e8028 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -998,7 +998,9 @@ private: using FenceTimePtr = std::shared_ptr; - const FenceTimePtr& getPreviousPresentFence(TimePoint frameTime, Period) + bool wouldPresentEarly(TimePoint frameTime, Period) const REQUIRES(kMainThreadContext); + + const FenceTimePtr& getPreviousPresentFence(TimePoint frameTime, Period) const REQUIRES(kMainThreadContext); // Blocks the thread waiting for up to graceTimeMs in case the fence is about to signal. -- GitLab From 5123e684dc464d7de28599333c6678e7884d7ac8 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Thu, 20 Apr 2023 10:15:09 -0400 Subject: [PATCH 1166/1310] SF: Fix two queries for the past VSYNC's fence ...which did not consider the case of targeting two VSYNCs ahead. Bug: 241285475 Test: libscheduler_test after Idf9f43b37f3479c94a478d154eaa46f43e0c6c9d Change-Id: Ib114ffc1866aa32e6d493e06dcf5a27652d19a39 --- services/surfaceflinger/SurfaceFlinger.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 7171adc951..c39122a828 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2149,7 +2149,8 @@ void SurfaceFlinger::setVsyncEnabled(PhysicalDisplayId id, bool enabled) { bool SurfaceFlinger::wouldPresentEarly(TimePoint frameTime, Period vsyncPeriod) const { const bool isThreeVsyncsAhead = mExpectedPresentTime - frameTime > 2 * vsyncPeriod; return isThreeVsyncsAhead || - mPreviousPresentFences[0].fenceTime->getSignalTime() != Fence::SIGNAL_TIME_PENDING; + getPreviousPresentFence(frameTime, vsyncPeriod)->getSignalTime() != + Fence::SIGNAL_TIME_PENDING; } auto SurfaceFlinger::getPreviousPresentFence(TimePoint frameTime, Period vsyncPeriod) const @@ -2657,9 +2658,10 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) // Send a power hint hint after presentation is finished if (mPowerHintSessionEnabled) { - mPowerAdvisor->setSfPresentTiming(TimePoint::fromNs(mPreviousPresentFences[0] - .fenceTime->getSignalTime()), - TimePoint::now()); + const nsecs_t pastPresentTime = + getPreviousPresentFence(frameTime, vsyncPeriod)->getSignalTime(); + + mPowerAdvisor->setSfPresentTiming(TimePoint::fromNs(pastPresentTime), TimePoint::now()); mPowerAdvisor->reportActualWorkDuration(); } -- GitLab From d86bd8c1d00fa1d91086399b8f6a360b95bd9218 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Fri, 21 Apr 2023 11:54:11 -0500 Subject: [PATCH 1167/1310] Use WindowInfo to calculate visible windows Bug: 278476799 Test: LongClickInternetMicrobenchmark on Redfin Change-Id: Ifb14c66e59c0b01346a3e151e7ee83993f5532d8 --- services/surfaceflinger/SurfaceFlinger.cpp | 28 ++++++++++------------ services/surfaceflinger/SurfaceFlinger.h | 6 ++--- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 48b41448a1..f5f0711a29 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3718,10 +3718,10 @@ void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { } void SurfaceFlinger::updateInputFlinger() { - ATRACE_CALL(); - if (!mInputFlinger) { + if (!mInputFlinger || (!mUpdateInputInfo && mInputWindowCommands.empty())) { return; } + ATRACE_CALL(); std::vector windowInfos; std::vector displayInfos; @@ -3730,20 +3730,18 @@ void SurfaceFlinger::updateInputFlinger() { mUpdateInputInfo = false; updateWindowInfo = true; buildWindowInfos(windowInfos, displayInfos); - } else if (mInputWindowCommands.empty()) { - return; } - std::unordered_set visibleLayers; - mDrawingState.traverse([&visibleLayers](Layer* layer) { - if (layer->isVisibleForInput()) { - visibleLayers.insert(layer); + std::unordered_set visibleWindowIds; + for (WindowInfo& windowInfo : windowInfos) { + if (!windowInfo.inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) { + visibleWindowIds.insert(windowInfo.id); } - }); - bool visibleLayersChanged = false; - if (visibleLayers != mVisibleLayers) { - visibleLayersChanged = true; - mVisibleLayers = std::move(visibleLayers); + } + bool visibleWindowsChanged = false; + if (visibleWindowIds != mVisibleWindowIds) { + visibleWindowsChanged = true; + mVisibleWindowIds = std::move(visibleWindowIds); } BackgroundExecutor::getInstance().sendCallbacks({[updateWindowInfo, @@ -3752,14 +3750,14 @@ void SurfaceFlinger::updateInputFlinger() { inputWindowCommands = std::move(mInputWindowCommands), inputFlinger = mInputFlinger, this, - visibleLayersChanged]() { + visibleWindowsChanged]() { ATRACE_NAME("BackgroundExecutor::updateInputFlinger"); if (updateWindowInfo) { mWindowInfosListenerInvoker ->windowInfosChanged(std::move(windowInfos), std::move(displayInfos), std::move( inputWindowCommands.windowInfosReportedListeners), - /* forceImmediateCall= */ visibleLayersChanged || + /* forceImmediateCall= */ visibleWindowsChanged || !inputWindowCommands.focusRequests.empty()); } else { // If there are listeners but no changes to input windows, call the listeners diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index fffd63a515..6d2b8c3676 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1428,10 +1428,8 @@ private: display::DisplayMap mFrontEndDisplayInfos; bool mFrontEndDisplayInfosChanged = false; - // Layers visible during the last commit. This set should only be used for testing set equality - // and membership. The pointers should not be dereferenced as it's possible the set contains - // pointers to freed layers. - std::unordered_set mVisibleLayers; + // WindowInfo ids visible during the last commit. + std::unordered_set mVisibleWindowIds; }; class SurfaceComposerAIDL : public gui::BnSurfaceComposer { -- GitLab From de17725511baa95ea11f1cd7c76377c1970286eb Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Fri, 21 Apr 2023 12:44:10 -0700 Subject: [PATCH 1168/1310] [sf] populate snashot path for legacy frontend Provide a consistent way for middleend/backend to identify mirrored or cloned layers. Test: presubmit Bug: 258231640 Change-Id: Id7cbadf1e581b9420859ddff024e9ce101b28e95 --- services/surfaceflinger/Layer.cpp | 11 +++++++---- services/surfaceflinger/Layer.h | 4 ++-- services/surfaceflinger/SurfaceFlinger.cpp | 4 ++-- .../fuzzer/surfaceflinger_scheduler_fuzzer.h | 2 +- .../surfaceflinger/tests/unittests/mock/MockLayer.h | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index a538c6d6fb..aa832f6c4c 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -2561,7 +2561,10 @@ Region Layer::getVisibleRegion(const DisplayDevice* display) const { return outputLayer ? outputLayer->getState().visibleRegion : Region(); } -void Layer::setInitialValuesForClone(const sp& clonedFrom) { +void Layer::setInitialValuesForClone(const sp& clonedFrom, uint32_t mirrorRootId) { + mSnapshot->path.id = clonedFrom->getSequence(); + mSnapshot->path.mirrorRootId = mirrorRootId; + cloneDrawingState(clonedFrom.get()); mClonedFrom = clonedFrom; mPremultipliedAlpha = clonedFrom->mPremultipliedAlpha; @@ -2662,7 +2665,7 @@ void Layer::updateClonedChildren(const sp& mirrorRoot, } sp clonedChild = clonedLayersMap[child]; if (clonedChild == nullptr) { - clonedChild = child->createClone(); + clonedChild = child->createClone(mirrorRoot->getSequence()); clonedLayersMap[child] = clonedChild; } addChildToDrawing(clonedChild); @@ -3478,11 +3481,11 @@ Rect Layer::computeBufferCrop(const State& s) { } } -sp Layer::createClone() { +sp Layer::createClone(uint32_t mirrorRootId) { LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata()); args.textureName = mTextureName; sp layer = mFlinger->getFactory().createBufferStateLayer(args); - layer->setInitialValuesForClone(sp::fromExisting(this)); + layer->setInitialValuesForClone(sp::fromExisting(this), mirrorRootId); return layer; } diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index acdd01da77..7a7c503ae2 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -249,7 +249,7 @@ public: // true if this layer is visible, false otherwise virtual bool isVisible() const; - virtual sp createClone(); + virtual sp createClone(uint32_t mirrorRoot); // Set a 2x2 transformation matrix on the layer. This transform // will be applied after parent transforms, but before any final @@ -898,7 +898,7 @@ protected: friend class TransactionFrameTracerTest; friend class TransactionSurfaceFrameTest; - virtual void setInitialValuesForClone(const sp& clonedFrom); + virtual void setInitialValuesForClone(const sp& clonedFrom, uint32_t mirrorRootId); void preparePerFrameCompositionState(); void preparePerFrameBufferCompositionState(); void preparePerFrameEffectsCompositionState(); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 38bde2b173..6a4f07c1a1 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5198,7 +5198,7 @@ status_t SurfaceFlinger::mirrorLayer(const LayerCreationArgs& args, return result; } - mirrorLayer->setClonedChild(mirrorFrom->createClone()); + mirrorLayer->setClonedChild(mirrorFrom->createClone(mirrorLayer->getSequence())); } outResult.layerId = mirrorLayer->sequence; @@ -7931,7 +7931,7 @@ bool SurfaceFlinger::commitMirrorDisplays(VsyncId vsyncId) { Mutex::Autolock lock(mStateLock); createEffectLayer(mirrorArgs, &unused, &childMirror); MUTEX_ALIAS(mStateLock, childMirror->mFlinger->mStateLock); - childMirror->setClonedChild(layer->createClone()); + childMirror->setClonedChild(layer->createClone(childMirror->getSequence())); childMirror->reparent(mirrorDisplay.rootHandle); } // lock on mStateLock needs to be released before binder handle gets destroyed diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h index a32750e657..8061a8f2dc 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h @@ -75,7 +75,7 @@ public: bool isVisible() const override { return true; } - sp createClone() override { return nullptr; } + sp createClone(uint32_t /* mirrorRootId */) override { return nullptr; } }; class FuzzImplVSyncTracker : public scheduler::VSyncTracker { diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h index 0d94f4c644..50e07fc92c 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockLayer.h +++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h @@ -32,7 +32,7 @@ public: MOCK_CONST_METHOD0(getType, const char*()); MOCK_METHOD0(getFrameSelectionPriority, int32_t()); MOCK_CONST_METHOD0(isVisible, bool()); - MOCK_METHOD0(createClone, sp()); + MOCK_METHOD1(createClone, sp(uint32_t)); MOCK_CONST_METHOD0(getFrameRateForLayerTree, FrameRate()); MOCK_CONST_METHOD0(getDefaultFrameRateCompatibility, scheduler::LayerInfo::FrameRateCompatibility()); -- GitLab From 42e5e142076607dfad53803c0bc89a175ab069e6 Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Fri, 14 Apr 2023 21:03:01 +0000 Subject: [PATCH 1169/1310] freeze rpc binder wire protocol for Android U Bug: 266741352 Test: binderRpcWireProtocolTest Change-Id: Iafa1f49a9f0f536162c59ff44143f05b61e9c9e8 --- libs/binder/RpcState.cpp | 2 +- libs/binder/include/binder/RpcSession.h | 4 ++-- libs/binder/tests/binderRpcWireProtocolTest.cpp | 13 ++++++++++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp index ed3ce24e46..03fa69973d 100644 --- a/libs/binder/RpcState.cpp +++ b/libs/binder/RpcState.cpp @@ -928,7 +928,7 @@ processTransactInternalTailCall: transactionData.size() - offsetof(RpcWireTransaction, data)}; Span objectTableSpan; - if (session->getProtocolVersion().value() > + if (session->getProtocolVersion().value() >= RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_SIZE) { std::optional> objectTableBytes = parcelSpan.splitOff(transaction->parcelDataSize); diff --git a/libs/binder/include/binder/RpcSession.h b/libs/binder/include/binder/RpcSession.h index a323febbc7..cb6460398d 100644 --- a/libs/binder/include/binder/RpcSession.h +++ b/libs/binder/include/binder/RpcSession.h @@ -37,9 +37,9 @@ class RpcState; class RpcTransport; class FdTrigger; -constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_NEXT = 1; +constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_NEXT = 2; constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL = 0xF0000000; -constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION = RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL; +constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION = 1; // Starting with this version: // diff --git a/libs/binder/tests/binderRpcWireProtocolTest.cpp b/libs/binder/tests/binderRpcWireProtocolTest.cpp index 3dab2c748b..642cea440d 100644 --- a/libs/binder/tests/binderRpcWireProtocolTest.cpp +++ b/libs/binder/tests/binderRpcWireProtocolTest.cpp @@ -237,14 +237,25 @@ TEST(RpcWire, V0) { checkRepr(kCurrentRepr, 0); } +TEST(RpcWire, V1) { + checkRepr(kCurrentRepr, 1); +} + TEST(RpcWire, CurrentVersion) { checkRepr(kCurrentRepr, RPC_WIRE_PROTOCOL_VERSION); } -static_assert(RPC_WIRE_PROTOCOL_VERSION == RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL, +static_assert(RPC_WIRE_PROTOCOL_VERSION == 1, "If the binder wire protocol is updated, this test should test additional versions. " "The binder wire protocol should only be updated on upstream AOSP."); +TEST(RpcWire, NextIsPlusOneReminder) { + if (RPC_WIRE_PROTOCOL_VERSION != RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL) { + EXPECT_EQ(RPC_WIRE_PROTOCOL_VERSION + 1, RPC_WIRE_PROTOCOL_VERSION_NEXT) + << "Make sure to note what the next version should be."; + } +} + TEST(RpcWire, ReleaseBranchHasFrozenRpcWireProtocol) { if (RPC_WIRE_PROTOCOL_VERSION == RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL) { EXPECT_FALSE(base::GetProperty("ro.build.version.codename", "") == "REL") -- GitLab From 14a3c1103c3da545614a87053b8873d2459a1c8b Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Fri, 21 Apr 2023 14:49:47 -0700 Subject: [PATCH 1170/1310] [bufferqueue] Add better trace points for buffer allocations Add tracepoints to understand why a buffer was reallocated. Also trace changes to the queue that can affect queue size. Test: check traces in perfetto Fixes: 279220179 Change-Id: Ifaff0851200d8faa9644c23d49009a3932f6e01f --- libs/gui/BufferQueueConsumer.cpp | 3 ++- libs/gui/BufferQueueProducer.cpp | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp index 7f7a0437f1..52172090af 100644 --- a/libs/gui/BufferQueueConsumer.cpp +++ b/libs/gui/BufferQueueConsumer.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #ifndef __ANDROID_VNDK__ @@ -646,7 +647,7 @@ status_t BufferQueueConsumer::setMaxBufferCount(int bufferCount) { status_t BufferQueueConsumer::setMaxAcquiredBufferCount( int maxAcquiredBuffers) { - ATRACE_CALL(); + ATRACE_FORMAT("%s(%d)", __func__, maxAcquiredBuffers); if (maxAcquiredBuffers < 1 || maxAcquiredBuffers > BufferQueueCore::MAX_MAX_ACQUIRED_BUFFERS) { diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp index 9eb1a9f526..9a2343bffb 100644 --- a/libs/gui/BufferQueueProducer.cpp +++ b/libs/gui/BufferQueueProducer.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -125,7 +126,7 @@ status_t BufferQueueProducer::setMaxDequeuedBufferCount( status_t BufferQueueProducer::setMaxDequeuedBufferCount(int maxDequeuedBuffers, int* maxBufferCount) { - ATRACE_CALL(); + ATRACE_FORMAT("%s(%d)", __func__, maxDequeuedBuffers); BQ_LOGV("setMaxDequeuedBufferCount: maxDequeuedBuffers = %d", maxDequeuedBuffers); @@ -502,6 +503,20 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp* ou if ((buffer == nullptr) || buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage)) { + if (CC_UNLIKELY(ATRACE_ENABLED())) { + if (buffer == nullptr) { + ATRACE_FORMAT_INSTANT("%s buffer reallocation: null", mConsumerName.string()); + } else { + ATRACE_FORMAT_INSTANT("%s buffer reallocation actual %dx%d format:%d " + "layerCount:%d " + "usage:%d requested: %dx%d format:%d layerCount:%d " + "usage:%d ", + mConsumerName.string(), width, height, format, + BQ_LAYER_COUNT, usage, buffer->getWidth(), + buffer->getHeight(), buffer->getPixelFormat(), + buffer->getLayerCount(), buffer->getUsage()); + } + } mSlots[found].mAcquireCalled = false; mSlots[found].mGraphicBuffer = nullptr; mSlots[found].mRequestBufferCalled = false; -- GitLab From b557f7b36abd537cb2a8020f00e0fc1f42449c3a Mon Sep 17 00:00:00 2001 From: Austin Borger Date: Thu, 30 Mar 2023 17:52:47 -0700 Subject: [PATCH 1171/1310] UidObserver / Camera: Track the OOM adj of a uid via UidObserver. Previously, onUidProcAdjChanged merely signaled that the OOM adj of a uid changed, but did not provide the actual OOM adj score. Having this information allows the camera service to cut out redundant calls to onCameraAccessPrioritiesChanged and avoid overwhelming apps. The number of calls to onCameraAccessPrioritiesChanged is reduced by only signaling when it's likely the uid owning a camera would lose access to it if another uid tried to open that camera. This is opposed to the status quo, which signals every time a watched uid changes its OOM adj, which is highly inefficient. Bug: 274486653 Test: -- on physical device: -- testCamera2AccessCallbackInSplitMode x10 -- ActivityManagerServiceTest -- ActivityManagerProcessStateTest -- ActivityManagerFgsBgStartTest -- UidObserverControllerTest -- Alternate focus in split screen between Camera2 + GCA x20 Change-Id: Ia8d36e7a49156d537ae4da3540a1046e3200d930 --- libs/binder/IUidObserver.cpp | 6 ++++-- libs/binder/include_activitymanager/binder/IUidObserver.h | 2 +- services/sensorservice/SensorService.h | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/libs/binder/IUidObserver.cpp b/libs/binder/IUidObserver.cpp index d952dc71f9..1c35f5359b 100644 --- a/libs/binder/IUidObserver.cpp +++ b/libs/binder/IUidObserver.cpp @@ -67,9 +67,10 @@ public: remote()->transact(ON_UID_STATE_CHANGED_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY); } - virtual void onUidProcAdjChanged(uid_t uid) { + virtual void onUidProcAdjChanged(uid_t uid, int32_t adj) { Parcel data, reply; data.writeInt32((int32_t)uid); + data.writeInt32((int32_t)adj); remote()->transact(ON_UID_PROC_ADJ_CHANGED_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY); } }; @@ -121,7 +122,8 @@ status_t BnUidObserver::onTransact( case ON_UID_PROC_ADJ_CHANGED_TRANSACTION: { CHECK_INTERFACE(IUidObserver, data, reply); uid_t uid = data.readInt32(); - onUidProcAdjChanged(uid); + int32_t adj = data.readInt32(); + onUidProcAdjChanged(uid, adj); return NO_ERROR; } break; diff --git a/libs/binder/include_activitymanager/binder/IUidObserver.h b/libs/binder/include_activitymanager/binder/IUidObserver.h index 17f03a9201..5ea7447ef2 100644 --- a/libs/binder/include_activitymanager/binder/IUidObserver.h +++ b/libs/binder/include_activitymanager/binder/IUidObserver.h @@ -34,7 +34,7 @@ public: virtual void onUidIdle(uid_t uid, bool disabled) = 0; virtual void onUidStateChanged(uid_t uid, int32_t procState, int64_t procStateSeq, int32_t capability) = 0; - virtual void onUidProcAdjChanged(uid_t uid) = 0; + virtual void onUidProcAdjChanged(uid_t uid, int32_t adj) = 0; enum { ON_UID_GONE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION, diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h index fe72a69f15..0aa1bcbd72 100644 --- a/services/sensorservice/SensorService.h +++ b/services/sensorservice/SensorService.h @@ -288,7 +288,7 @@ private: void onUidStateChanged(uid_t uid __unused, int32_t procState __unused, int64_t procStateSeq __unused, int32_t capability __unused) override {} - void onUidProcAdjChanged(uid_t uid __unused) override {} + void onUidProcAdjChanged(uid_t uid __unused, int32_t adj __unused) override {} void addOverrideUid(uid_t uid, bool active); void removeOverrideUid(uid_t uid); -- GitLab From e7382c1cde2a381aad705f46943849b2d6cf8ba9 Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Fri, 21 Apr 2023 20:24:32 +0000 Subject: [PATCH 1172/1310] Reset pending commands Allow takePendingCommands to have ownership for mComamnds so commands are reset on each call. resetCommands methods are removed since they are not really used. Bug: 273525126 Test: manual Change-Id: If91cf6253b0d8f0d8c5d761f7cf257d090fcaf6f --- .../DisplayHardware/AidlComposerHal.cpp | 13 +------------ .../DisplayHardware/AidlComposerHal.h | 4 ---- .../surfaceflinger/DisplayHardware/ComposerHal.h | 4 ---- .../DisplayHardware/HidlComposerHal.cpp | 4 ---- .../DisplayHardware/HidlComposerHal.h | 4 ---- .../surfaceflinger_displayhardware_fuzzer.cpp | 1 - .../unittests/mock/DisplayHardware/MockComposer.h | 1 - 7 files changed, 1 insertion(+), 30 deletions(-) diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp index f28bfd44cd..f7049b98e7 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp @@ -329,14 +329,6 @@ void AidlComposer::registerCallback(HWC2::ComposerCallback& callback) { } } -void AidlComposer::resetCommands(Display display) { - mMutex.lock_shared(); - if (auto writer = getWriter(display)) { - writer->get().reset(); - } - mMutex.unlock_shared(); -} - Error AidlComposer::executeCommands(Display display) { mMutex.lock_shared(); auto error = execute(display); @@ -1054,9 +1046,8 @@ Error AidlComposer::execute(Display display) { return Error::BAD_DISPLAY; } - const auto& commands = writer->get().getPendingCommands(); + auto commands = writer->get().takePendingCommands(); if (commands.empty()) { - writer->get().reset(); return Error::NONE; } @@ -1088,8 +1079,6 @@ Error AidlComposer::execute(Display display) { } } - writer->get().reset(); - return error; } diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h index 8313c09e58..ce05b382cc 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h @@ -72,10 +72,6 @@ public: void registerCallback(HWC2::ComposerCallback& callback) override; - // Reset all pending commands in the command buffer. Useful if you want to - // skip a frame but have already queued some commands. - void resetCommands(Display) override; - // Explicitly flush all pending commands in the command buffer. Error executeCommands(Display) override; diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h index c65c572920..cf677955bf 100644 --- a/services/surfaceflinger/DisplayHardware/ComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h @@ -110,10 +110,6 @@ public: virtual void registerCallback(HWC2::ComposerCallback& callback) = 0; - // Reset all pending commands in the command buffer. Useful if you want to - // skip a frame but have already queued some commands. - virtual void resetCommands(Display) = 0; - // Explicitly flush all pending commands in the command buffer. virtual Error executeCommands(Display) = 0; diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp index 23de4fa42e..e0f6c45f70 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp @@ -293,10 +293,6 @@ void HidlComposer::registerCallback(const sp& callback) { } } -void HidlComposer::resetCommands(Display) { - mWriter.reset(); -} - Error HidlComposer::executeCommands(Display) { return execute(); } diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h index d04652bf21..0521acf9c4 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h @@ -174,10 +174,6 @@ public: void registerCallback(HWC2::ComposerCallback& callback) override; - // Reset all pending commands in the command buffer. Useful if you want to - // skip a frame but have already queued some commands. - void resetCommands(Display) override; - // Explicitly flush all pending commands in the command buffer. Error executeCommands(Display) override; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp index a9247fe903..9fac14ed4c 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp @@ -327,7 +327,6 @@ void DisplayHardwareFuzzer::invokeAidlComposer() { invokeComposerHal2_4(&composer, display, outLayer); composer.executeCommands(display); - composer.resetCommands(display); composer.destroyLayer(display, outLayer); composer.destroyVirtualDisplay(display); diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h index 5dc3490eb8..d3fb9fc3f8 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h @@ -55,7 +55,6 @@ public: std::vector()); MOCK_METHOD0(dumpDebugInfo, std::string()); MOCK_METHOD1(registerCallback, void(HWC2::ComposerCallback&)); - MOCK_METHOD1(resetCommands, void(Display)); MOCK_METHOD1(executeCommands, Error(Display)); MOCK_METHOD0(getMaxVirtualDisplayCount, uint32_t()); MOCK_METHOD4(createVirtualDisplay, Error(uint32_t, uint32_t, PixelFormat*, Display*)); -- GitLab From bcbfe602cce7406bdf66b3d3b2043856eb866c91 Mon Sep 17 00:00:00 2001 From: Yiwei Zhang Date: Sat, 22 Apr 2023 04:43:33 +0000 Subject: [PATCH 1173/1310] Add blob cache related tracepoints Generally useful for getting a sense of blob cache init, cache ejection, cache flush operations. Bug: 277861132 Test: build and take traces Change-Id: I8b1d7beab27845856c13ab8088a3a702553e51b0 --- opengl/libs/EGL/BlobCache.cpp | 6 ++++++ opengl/libs/EGL/FileBlobCache.cpp | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/opengl/libs/EGL/BlobCache.cpp b/opengl/libs/EGL/BlobCache.cpp index aecfc6b077..b3a4bc19fd 100644 --- a/opengl/libs/EGL/BlobCache.cpp +++ b/opengl/libs/EGL/BlobCache.cpp @@ -15,6 +15,7 @@ */ //#define LOG_NDEBUG 0 +#define ATRACE_TAG ATRACE_TAG_GRAPHICS #include "BlobCache.h" @@ -22,6 +23,7 @@ #include #include #include +#include #include @@ -230,6 +232,8 @@ int BlobCache::flatten(void* buffer, size_t size) const { } int BlobCache::unflatten(void const* buffer, size_t size) { + ATRACE_NAME("BlobCache::unflatten"); + // All errors should result in the BlobCache being in an empty state. clear(); @@ -293,6 +297,8 @@ long int BlobCache::blob_random() { } void BlobCache::clean() { + ATRACE_NAME("BlobCache::clean"); + // Remove a random cache entry until the total cache size gets below half // the maximum total cache size. while (mTotalSize > mMaxTotalSize / 2) { diff --git a/opengl/libs/EGL/FileBlobCache.cpp b/opengl/libs/EGL/FileBlobCache.cpp index 1026842f87..4a0fac4ce5 100644 --- a/opengl/libs/EGL/FileBlobCache.cpp +++ b/opengl/libs/EGL/FileBlobCache.cpp @@ -14,6 +14,8 @@ ** limitations under the License. */ +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + #include "FileBlobCache.h" #include @@ -24,6 +26,7 @@ #include #include +#include // Cache file header static const char* cacheFileMagic = "EGL$"; @@ -51,6 +54,8 @@ FileBlobCache::FileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxT const std::string& filename) : BlobCache(maxKeySize, maxValueSize, maxTotalSize) , mFilename(filename) { + ATRACE_CALL(); + if (mFilename.length() > 0) { size_t headerSize = cacheFileHeaderSize; @@ -117,6 +122,8 @@ FileBlobCache::FileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxT } void FileBlobCache::writeToFile() { + ATRACE_CALL(); + if (mFilename.length() > 0) { size_t cacheSize = getFlattenedSize(); size_t headerSize = cacheFileHeaderSize; -- GitLab From 2a916c4622b762c1f39d69a2991f459a8fe7ec81 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 17 Apr 2023 10:37:05 -0700 Subject: [PATCH 1174/1310] Use std::unordered_map in KeyCharacterMap Previously, KeyedVector was used in KeyCharacterMap. Convert this to the std::unordered_map. Bug: 278299254 Test: m checkinput Test: m libinput_tests && $ANDROID_HOST_OUT/nativetest64/libinput_tests/libinput_tests Change-Id: I643aa8cc8ae0c68ade4d11d02e34be64faf7f157 --- include/input/KeyCharacterMap.h | 24 ++-- libs/input/KeyCharacterMap.cpp | 179 +++++++------------------- services/inputflinger/Android.bp | 3 + services/inputflinger/host/Android.bp | 9 +- 4 files changed, 63 insertions(+), 152 deletions(-) diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h index 9423041b68..b2e8baade3 100644 --- a/include/input/KeyCharacterMap.h +++ b/include/input/KeyCharacterMap.h @@ -26,9 +26,9 @@ #include #include #include -#include #include #include +#include // Maximum number of keys supported by KeyCharacterMaps #define MAX_KEYS 8192 @@ -152,13 +152,9 @@ public: void writeToParcel(Parcel* parcel) const; #endif - bool operator==(const KeyCharacterMap& other) const; + bool operator==(const KeyCharacterMap& other) const = default; - bool operator!=(const KeyCharacterMap& other) const; - - KeyCharacterMap(const KeyCharacterMap& other); - - virtual ~KeyCharacterMap(); + KeyCharacterMap(const KeyCharacterMap& other) = default; private: struct Behavior { @@ -173,17 +169,18 @@ private: /* The replacement keycode if the key has to be replaced outright. */ int32_t replacementKeyCode = 0; + + bool operator==(const Behavior&) const = default; }; struct Key { - Key(); - Key(const Key& other); + bool operator==(const Key&) const = default; /* The single character label printed on the key, or 0 if none. */ - char16_t label; + char16_t label = 0; /* The number or symbol character generated by the key, or 0 if none. */ - char16_t number; + char16_t number = 0; /* The list of key behaviors sorted from most specific to least specific * meta key binding. */ @@ -218,7 +215,6 @@ private: public: Parser(KeyCharacterMap* map, Tokenizer* tokenizer, Format format); - ~Parser(); status_t parse(); private: @@ -232,8 +228,8 @@ private: status_t parseCharacterLiteral(char16_t* outCharacter); }; - KeyedVector mKeys; - KeyboardType mType; + std::map mKeys; + KeyboardType mType = KeyboardType::UNKNOWN; std::string mLoadFileName; bool mLayoutOverlayApplied = false; diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp index f703901ed9..12c9e533c3 100644 --- a/libs/input/KeyCharacterMap.cpp +++ b/libs/input/KeyCharacterMap.cpp @@ -85,63 +85,7 @@ static String8 toString(const char16_t* chars, size_t numChars) { // --- KeyCharacterMap --- -KeyCharacterMap::KeyCharacterMap(const std::string& filename) - : mType(KeyboardType::UNKNOWN), mLoadFileName(filename) {} - -KeyCharacterMap::KeyCharacterMap(const KeyCharacterMap& other) - : mType(other.mType), - mLoadFileName(other.mLoadFileName), - mLayoutOverlayApplied(other.mLayoutOverlayApplied), - mKeyRemapping(other.mKeyRemapping), - mKeysByScanCode(other.mKeysByScanCode), - mKeysByUsageCode(other.mKeysByUsageCode) { - for (size_t i = 0; i < other.mKeys.size(); i++) { - mKeys.add(other.mKeys.keyAt(i), new Key(*other.mKeys.valueAt(i))); - } -} - -KeyCharacterMap::~KeyCharacterMap() { - clear(); -} - -bool KeyCharacterMap::operator==(const KeyCharacterMap& other) const { - if (mType != other.mType) { - return false; - } - if (mLoadFileName != other.mLoadFileName) { - return false; - } - if (mLayoutOverlayApplied != other.mLayoutOverlayApplied) { - return false; - } - if (mKeys.size() != other.mKeys.size() || mKeyRemapping.size() != other.mKeyRemapping.size() || - mKeysByScanCode.size() != other.mKeysByScanCode.size() || - mKeysByUsageCode.size() != other.mKeysByUsageCode.size()) { - return false; - } - - for (size_t i = 0; i < mKeys.size(); i++) { - if (mKeys.keyAt(i) != other.mKeys.keyAt(i)) { - return false; - } - const Key* key = mKeys.valueAt(i); - const Key* otherKey = other.mKeys.valueAt(i); - if (key->label != otherKey->label || key->number != otherKey->number) { - return false; - } - } - - if (mKeyRemapping != other.mKeyRemapping || mKeysByScanCode != other.mKeysByScanCode || - mKeysByUsageCode != other.mKeysByUsageCode) { - return false; - } - - return true; -} - -bool KeyCharacterMap::operator!=(const KeyCharacterMap& other) const { - return !(*this == other); -} +KeyCharacterMap::KeyCharacterMap(const std::string& filename) : mLoadFileName(filename) {} base::Result> KeyCharacterMap::load(const std::string& filename, Format format) { @@ -207,10 +151,6 @@ status_t KeyCharacterMap::load(Tokenizer* tokenizer, Format format) { void KeyCharacterMap::clear() { mKeysByScanCode.clear(); mKeysByUsageCode.clear(); - for (size_t i = 0; i < mKeys.size(); i++) { - Key* key = mKeys.editValueAt(i); - delete key; - } mKeys.clear(); mLayoutOverlayApplied = false; mType = KeyboardType::UNKNOWN; @@ -233,24 +173,16 @@ void KeyCharacterMap::combine(const KeyCharacterMap& overlay) { if (mLayoutOverlayApplied) { reloadBaseFromFile(); } - for (size_t i = 0; i < overlay.mKeys.size(); i++) { - int32_t keyCode = overlay.mKeys.keyAt(i); - Key* key = overlay.mKeys.valueAt(i); - ssize_t oldIndex = mKeys.indexOfKey(keyCode); - if (oldIndex >= 0) { - delete mKeys.valueAt(oldIndex); - mKeys.editValueAt(oldIndex) = new Key(*key); - } else { - mKeys.add(keyCode, new Key(*key)); - } + for (const auto& [keyCode, key] : overlay.mKeys) { + mKeys.insert_or_assign(keyCode, key); } - for (auto const& it : overlay.mKeysByScanCode) { - mKeysByScanCode.insert_or_assign(it.first, it.second); + for (const auto& [fromScanCode, toAndroidKeyCode] : overlay.mKeysByScanCode) { + mKeysByScanCode.insert_or_assign(fromScanCode, toAndroidKeyCode); } - for (auto const& it : overlay.mKeysByUsageCode) { - mKeysByUsageCode.insert_or_assign(it.first, it.second); + for (const auto& [fromHidUsageCode, toAndroidKeyCode] : overlay.mKeysByUsageCode) { + mKeysByUsageCode.insert_or_assign(fromHidUsageCode, toAndroidKeyCode); } mLayoutOverlayApplied = true; } @@ -343,19 +275,15 @@ char16_t KeyCharacterMap::getMatch(int32_t keyCode, const char16_t* chars, size_ if (behavior.character == chars[i]) { result = behavior.character; if ((behavior.metaState & metaState) == behavior.metaState) { - goto ExactMatch; + // Found exact match! + return result; } break; } } } } - ExactMatch: ; } -#if DEBUG_MAPPING - ALOGD("getMatch: keyCode=%d, chars=[%s], metaState=0x%08x ~ Result %d.", - keyCode, toString(chars, numChars).string(), metaState, result); -#endif return result; } @@ -494,9 +422,9 @@ std::pair KeyCharacterMap::applyKeyBehavior(int32_t fromKeyCod } const KeyCharacterMap::Key* KeyCharacterMap::getKey(int32_t keyCode) const { - ssize_t index = mKeys.indexOfKey(keyCode); - if (index >= 0) { - return mKeys.valueAt(index); + auto it = mKeys.find(keyCode); + if (it != mKeys.end()) { + return &it->second; } return nullptr; } @@ -550,19 +478,17 @@ bool KeyCharacterMap::findKey(char16_t ch, int32_t* outKeyCode, int32_t* outMeta return false; } - for (size_t i = 0; i < mKeys.size(); i++) { - const Key* key = mKeys.valueAt(i); - + for (const auto& [keyCode, key] : mKeys) { // Try to find the most general behavior that maps to this character. // For example, the base key behavior will usually be last in the list. const Behavior* found = nullptr; - for (const Behavior& behavior : key->behaviors) { + for (const Behavior& behavior : key.behaviors) { if (behavior.character == ch) { found = &behavior; } } if (found != nullptr) { - *outKeyCode = mKeys.keyAt(i); + *outKeyCode = keyCode; *outMetaState = found->metaState; return true; } @@ -714,11 +640,7 @@ std::shared_ptr KeyCharacterMap::readFromParcel(Parcel* parcel) return nullptr; } - Key* key = new Key(); - key->label = label; - key->number = number; - map->mKeys.add(keyCode, key); - + Key key{.label = label, .number = number}; while (parcel->readInt32()) { int32_t metaState = parcel->readInt32(); char16_t character = parcel->readInt32(); @@ -728,13 +650,14 @@ std::shared_ptr KeyCharacterMap::readFromParcel(Parcel* parcel) return nullptr; } - key->behaviors.push_back({ + key.behaviors.push_back({ .metaState = metaState, .character = character, .fallbackKeyCode = fallbackKeyCode, .replacementKeyCode = replacementKeyCode, }); } + map->mKeys.emplace(keyCode, std::move(key)); if (parcel->errorCheck()) { return nullptr; @@ -790,13 +713,11 @@ void KeyCharacterMap::writeToParcel(Parcel* parcel) const { size_t numKeys = mKeys.size(); parcel->writeInt32(numKeys); - for (size_t i = 0; i < numKeys; i++) { - int32_t keyCode = mKeys.keyAt(i); - const Key* key = mKeys.valueAt(i); + for (const auto& [keyCode, key] : mKeys) { parcel->writeInt32(keyCode); - parcel->writeInt32(key->label); - parcel->writeInt32(key->number); - for (const Behavior& behavior : key->behaviors) { + parcel->writeInt32(key.label); + parcel->writeInt32(key.number); + for (const Behavior& behavior : key.behaviors) { parcel->writeInt32(1); parcel->writeInt32(behavior.metaState); parcel->writeInt32(behavior.character); @@ -826,22 +747,12 @@ void KeyCharacterMap::writeToParcel(Parcel* parcel) const { } #endif // __linux__ -// --- KeyCharacterMap::Key --- - -KeyCharacterMap::Key::Key() : label(0), number(0) {} - -KeyCharacterMap::Key::Key(const Key& other) - : label(other.label), number(other.number), behaviors(other.behaviors) {} - // --- KeyCharacterMap::Parser --- KeyCharacterMap::Parser::Parser(KeyCharacterMap* map, Tokenizer* tokenizer, Format format) : mMap(map), mTokenizer(tokenizer), mFormat(format), mState(STATE_TOP) { } -KeyCharacterMap::Parser::~Parser() { -} - status_t KeyCharacterMap::Parser::parse() { while (!mTokenizer->isEof()) { #if DEBUG_PARSER @@ -1021,7 +932,7 @@ status_t KeyCharacterMap::Parser::parseKey() { keyCodeToken.string()); return BAD_VALUE; } - if (mMap->mKeys.indexOfKey(*keyCode) >= 0) { + if (mMap->mKeys.find(*keyCode) != mMap->mKeys.end()) { ALOGE("%s: Duplicate entry for key code '%s'.", mTokenizer->getLocation().string(), keyCodeToken.string()); return BAD_VALUE; @@ -1037,17 +948,17 @@ status_t KeyCharacterMap::Parser::parseKey() { ALOGD_IF(DEBUG_PARSER, "Parsed beginning of key: keyCode=%d.", *keyCode); mKeyCode = *keyCode; - mMap->mKeys.add(*keyCode, new Key()); + mMap->mKeys.emplace(*keyCode, Key{}); mState = STATE_KEY; return NO_ERROR; } status_t KeyCharacterMap::Parser::parseKeyProperty() { - Key* key = mMap->mKeys.valueFor(mKeyCode); + Key& key = mMap->mKeys[mKeyCode]; String8 token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER); if (token == "}") { mState = STATE_TOP; - return finishKey(*key); + return finishKey(key); } std::vector properties; @@ -1184,43 +1095,41 @@ status_t KeyCharacterMap::Parser::parseKeyProperty() { for (const Property& property : properties) { switch (property.property) { case PROPERTY_LABEL: - if (key->label) { - ALOGE("%s: Duplicate label for key.", - mTokenizer->getLocation().string()); - return BAD_VALUE; - } - key->label = behavior.character; + if (key.label) { + ALOGE("%s: Duplicate label for key.", mTokenizer->getLocation().string()); + return BAD_VALUE; + } + key.label = behavior.character; #if DEBUG_PARSER - ALOGD("Parsed key label: keyCode=%d, label=%d.", mKeyCode, key->label); + ALOGD("Parsed key label: keyCode=%d, label=%d.", mKeyCode, key.label); #endif break; case PROPERTY_NUMBER: - if (key->number) { - ALOGE("%s: Duplicate number for key.", - mTokenizer->getLocation().string()); - return BAD_VALUE; + if (key.number) { + ALOGE("%s: Duplicate number for key.", mTokenizer->getLocation().string()); + return BAD_VALUE; } - key->number = behavior.character; + key.number = behavior.character; #if DEBUG_PARSER - ALOGD("Parsed key number: keyCode=%d, number=%d.", mKeyCode, key->number); + ALOGD("Parsed key number: keyCode=%d, number=%d.", mKeyCode, key.number); #endif break; case PROPERTY_META: { - for (const Behavior& b : key->behaviors) { - if (b.metaState == property.metaState) { + for (const Behavior& b : key.behaviors) { + if (b.metaState == property.metaState) { ALOGE("%s: Duplicate key behavior for modifier.", mTokenizer->getLocation().string()); return BAD_VALUE; - } + } } Behavior newBehavior = behavior; newBehavior.metaState = property.metaState; - key->behaviors.push_front(newBehavior); + key.behaviors.push_front(newBehavior); ALOGD_IF(DEBUG_PARSER, "Parsed key meta: keyCode=%d, meta=0x%x, char=%d, fallback=%d replace=%d.", - mKeyCode, key->behaviors.front().metaState, key->behaviors.front().character, - key->behaviors.front().fallbackKeyCode, - key->behaviors.front().replacementKeyCode); + mKeyCode, key.behaviors.front().metaState, key.behaviors.front().character, + key.behaviors.front().fallbackKeyCode, + key.behaviors.front().replacementKeyCode); break; } } diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp index e04481ca50..69df45bc3e 100644 --- a/services/inputflinger/Android.bp +++ b/services/inputflinger/Android.bp @@ -224,6 +224,9 @@ phony { "libinputservice_test", "Bug-115739809", "StructLayout_test", + // currently unused, but still must build correctly + "inputflinger", + "libinputflingerhost", // native fuzzers "inputflinger_latencytracker_fuzzer", diff --git a/services/inputflinger/host/Android.bp b/services/inputflinger/host/Android.bp index 743587c6f8..4d2839f038 100644 --- a/services/inputflinger/host/Android.bp +++ b/services/inputflinger/host/Android.bp @@ -23,7 +23,7 @@ package { cc_library_shared { name: "libinputflingerhost", - + cpp_std: "c++20", srcs: [ "InputFlinger.cpp", "InputDriver.cpp", @@ -64,14 +64,17 @@ cc_binary { srcs: ["main.cpp"], - cflags: ["-Wall", "-Werror"], + cflags: [ + "-Wall", + "-Werror", + ], shared_libs: [ "libbase", "libbinder", "libinputflingerhost", "libutils", - "libinput" + "libinput", ], static_libs: [ "libarect", -- GitLab From 0f6558d6808fcc6f6e852af3b40b2e8e400ad3e7 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 21 Apr 2023 12:05:13 -0700 Subject: [PATCH 1175/1310] Do not slip touch into window with same input channel When two windows have the same input channel, do not slip touch from one of them into the other. We don't have a use case for that, and supporting this behaviour would be harder than simply disabling it. Without slipping, the input channel also receives a continuous gesture, which is generally desirable. The original window may not know that there's a clone out there with the same input channel, and therefore may not know to remove FLAG_SLIPPERY whenever it desires to receive a continuous gesture. Bug: 274073185 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: I577724c1c2e77928e695c250b5f5fbe8f1592a2f --- .../dispatcher/InputDispatcher.cpp | 13 +++++------ .../tests/InputDispatcher_test.cpp | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 6b9ad446ce..97b57b4dd9 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2402,6 +2402,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( const bool isStylus = isPointerFromStylus(entry, /*pointerIndex=*/0); sp oldTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle(); + LOG_ALWAYS_FATAL_IF(oldTouchedWindowHandle == nullptr); auto [newTouchedWindowHandle, _] = findTouchedWindowAtLocked(displayId, x, y, isStylus); // Verify targeted injection. @@ -2417,13 +2418,11 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( newTouchedWindowHandle = nullptr; } - if (oldTouchedWindowHandle != newTouchedWindowHandle && - oldTouchedWindowHandle != nullptr && newTouchedWindowHandle != nullptr) { - if (DEBUG_FOCUS) { - ALOGD("Touch is slipping out of window %s into window %s in display %" PRId32, - oldTouchedWindowHandle->getName().c_str(), - newTouchedWindowHandle->getName().c_str(), displayId); - } + if (!haveSameToken(oldTouchedWindowHandle, newTouchedWindowHandle)) { + ALOGD("Touch is slipping out of window %s into window %s in display %" PRId32, + oldTouchedWindowHandle->getName().c_str(), + newTouchedWindowHandle->getName().c_str(), displayId); + // Make a slippery exit from the old window. std::bitset pointerIds; const int32_t pointerId = entry.pointerProperties[0].id; diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 27d7b9c93f..3bfc7bf9fe 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -6140,6 +6140,28 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleWindowsFirstTouchWithSc touchAndAssertPositions(AMOTION_EVENT_ACTION_MOVE, touchedPoints, expectedPoints); } +/** + * When one of the windows is slippery, the touch should not slip into the other window with the + * same input channel. + */ +TEST_F(InputDispatcherMultiWindowSameTokenTests, TouchDoesNotSlipEvenIfSlippery) { + mWindow1->setSlippery(true); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow1, mWindow2}}}); + + // Touch down in window 1 + mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {{50, 50}})); + consumeMotionEvent(mWindow1, ACTION_DOWN, {{50, 50}}); + + // Move touch to be above window 2. Even though window 1 is slippery, touch should not slip. + // That means the gesture should continue normally, without any ACTION_CANCEL or ACTION_DOWN + // getting generated. + mDispatcher->notifyMotion(generateMotionArgs(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {{150, 150}})); + + consumeMotionEvent(mWindow1, ACTION_MOVE, {{150, 150}}); +} + class InputDispatcherSingleWindowAnr : public InputDispatcherTest { virtual void SetUp() override { InputDispatcherTest::SetUp(); -- GitLab From 3e524ef3aad56cbe1c879d7881ceb7c315b937d7 Mon Sep 17 00:00:00 2001 From: Jiakai Zhang Date: Mon, 24 Apr 2023 20:01:37 +0100 Subject: [PATCH 1176/1310] Move the package directory before deleting it. This change avoids the race between installd and artd. Bug: 261431149 Test: - 1. Add a "sleep" before artd creating the files. 2. Add a "sleep" before installd moving the dir and deleting the dir. 3. Install an app. 4. Run "pm compile" on the app. 5. While "pm compile" is on "sleep", uninstall the app. 6. See the compilation failed and the failure handled properly. 7. See the uninstallation succeeded and the package dir no longer present. Test: - 1. Add a "sleep" between artd invoking dex2oat and moving the files. 2. Do step 2-5 above. 3. See the same results. Ignore-AOSP-First: ART Services Change-Id: I4583943aeeb03915f76a5d66f44eff7347f8db14 --- cmds/installd/utils_default.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmds/installd/utils_default.cpp b/cmds/installd/utils_default.cpp index a6025e67c8..85ce4509e1 100644 --- a/cmds/installd/utils_default.cpp +++ b/cmds/installd/utils_default.cpp @@ -23,7 +23,7 @@ namespace installd { // platform dependent logic. int rm_package_dir(const std::string& package_dir) { - return delete_dir_contents_and_dir(package_dir); + return rename_delete_dir_contents_and_dir(package_dir); } } // namespace installd -- GitLab From 22f2ead8e2aa52fb3a41aa540367e3df9c84177e Mon Sep 17 00:00:00 2001 From: ramindani Date: Fri, 21 Apr 2023 10:27:11 -0700 Subject: [PATCH 1177/1310] [SF] Refresh rate selection based on the pacesetter display Pacesetter display makes the refresh rate selection. Other displays follow the refresh rate of the pacesetter. When no matching rate is found with the pacesetter, the display selects the best scored refresh rate. BUG: 270742628 Test: atest SchedulerTest and manual test Change-Id: I4fded96c83089f7bdc6390eb882ca9d16121ceed --- .../surfaceflinger/Scheduler/Scheduler.cpp | 78 ++++--------------- .../tests/unittests/SchedulerTest.cpp | 23 +++++- 2 files changed, 38 insertions(+), 63 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 63a0173519..1e45b41aa6 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -830,81 +830,35 @@ auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap { using RankedRefreshRates = RefreshRateSelector::RankedFrameRates; display::PhysicalDisplayVector perDisplayRanking; - - // Tallies the score of a refresh rate across `displayCount` displays. - struct RefreshRateTally { - explicit RefreshRateTally(float score) : score(score) {} - - float score; - size_t displayCount = 1; - }; - - // Chosen to exceed a typical number of refresh rates across displays. - constexpr size_t kStaticCapacity = 8; - ftl::SmallMap refreshRateTallies; - const auto globalSignals = makeGlobalSignals(); + Fps pacesetterFps; for (const auto& [id, display] : mDisplays) { auto rankedFrameRates = display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, globalSignals); - - for (const auto& [frameRateMode, score] : rankedFrameRates.ranking) { - const auto [it, inserted] = refreshRateTallies.try_emplace(frameRateMode.fps, score); - - if (!inserted) { - auto& tally = it->second; - tally.score += score; - tally.displayCount++; - } + if (id == *mPacesetterDisplayId) { + pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps; } - perDisplayRanking.push_back(std::move(rankedFrameRates)); } - auto maxScoreIt = refreshRateTallies.cbegin(); - - // Find the first refresh rate common to all displays. - while (maxScoreIt != refreshRateTallies.cend() && - maxScoreIt->second.displayCount != mDisplays.size()) { - ++maxScoreIt; - } - - if (maxScoreIt != refreshRateTallies.cend()) { - // Choose the highest refresh rate common to all displays, if any. - for (auto it = maxScoreIt + 1; it != refreshRateTallies.cend(); ++it) { - const auto [fps, tally] = *it; - - if (tally.displayCount == mDisplays.size() && tally.score > maxScoreIt->second.score) { - maxScoreIt = it; - } - } - } - - const std::optional chosenFps = maxScoreIt != refreshRateTallies.cend() - ? std::make_optional(maxScoreIt->first) - : std::nullopt; - DisplayModeChoiceMap modeChoices; - using fps_approx_ops::operator==; - for (auto& [ranking, signals] : perDisplayRanking) { - if (!chosenFps) { - const auto& [frameRateMode, _] = ranking.front(); - modeChoices.try_emplace(frameRateMode.modePtr->getPhysicalDisplayId(), - DisplayModeChoice{frameRateMode, signals}); - continue; - } - - for (auto& [frameRateMode, _] : ranking) { - if (frameRateMode.fps == *chosenFps) { - modeChoices.try_emplace(frameRateMode.modePtr->getPhysicalDisplayId(), - DisplayModeChoice{frameRateMode, signals}); - break; - } - } + for (auto& [rankings, signals] : perDisplayRanking) { + const auto chosenFrameRateMode = + ftl::find_if(rankings, + [&](const auto& ranking) { + return ranking.frameRateMode.fps == pacesetterFps; + }) + .transform([](const auto& scoredFrameRate) { + return scoredFrameRate.get().frameRateMode; + }) + .value_or(rankings.front().frameRateMode); + + modeChoices.try_emplace(chosenFrameRateMode.modePtr->getPhysicalDisplayId(), + DisplayModeChoice{chosenFrameRateMode, signals}); } return modeChoices; } diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index dc76b4c90f..965e37873f 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -359,7 +359,8 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { EXPECT_EQ(expectedChoices, actualChoices); } { - // This display does not support 120 Hz, so we should choose 60 Hz despite the touch signal. + // The kDisplayId3 does not support 120Hz, The pacesetter display rate is chosen to be 120 + // Hz. In this case only the display kDisplayId3 choose 60Hz as it does not support 120Hz. mScheduler ->registerDisplay(kDisplayId3, std::make_shared(kDisplay3Modes, @@ -369,6 +370,26 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { mScheduler->replaceTouchTimer(10); mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); + expectedChoices = ftl::init::map< + const PhysicalDisplayId&, + DisplayModeChoice>(kDisplayId1, FrameRateMode{120_Hz, kDisplay1Mode120}, + globalSignals)(kDisplayId2, + FrameRateMode{120_Hz, kDisplay2Mode120}, + globalSignals)(kDisplayId3, + FrameRateMode{60_Hz, + kDisplay3Mode60}, + globalSignals); + + const auto actualChoices = mScheduler->chooseDisplayModes(); + EXPECT_EQ(expectedChoices, actualChoices); + } + { + // We should choose 60Hz despite the touch signal as pacesetter only supports 60Hz + mScheduler->setPacesetterDisplay(kDisplayId3); + const GlobalSignals globalSignals = {.touch = true}; + mScheduler->replaceTouchTimer(10); + mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); + expectedChoices = ftl::init::map< const PhysicalDisplayId&, DisplayModeChoice>(kDisplayId1, FrameRateMode{60_Hz, kDisplay1Mode60}, -- GitLab From a68f3a49e36e043b1640fe85010b0005d1bdb875 Mon Sep 17 00:00:00 2001 From: Lajos Molnar Date: Tue, 6 Sep 2022 17:27:01 -0700 Subject: [PATCH 1178/1310] media: define DCI and Display P3 primaries Also mark AdobeRGB constant defined in graphics Datapace Bug: 232164440 Change-Id: I5e77c151a2a915571fa9a3d1d5fe8ad6552a6762 --- headers/media_plugin/media/hardware/VideoAPI.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/headers/media_plugin/media/hardware/VideoAPI.h b/headers/media_plugin/media/hardware/VideoAPI.h index a09087698c..54666804c0 100644 --- a/headers/media_plugin/media/hardware/VideoAPI.h +++ b/headers/media_plugin/media/hardware/VideoAPI.h @@ -127,6 +127,8 @@ struct __attribute__ ((__packed__, aligned(alignof(uint32_t)))) ColorAspects { PrimariesBT601_6_525, // Rec.ITU-R BT.601-6 525 or equivalent PrimariesGenericFilm, // Generic Film PrimariesBT2020, // Rec.ITU-R BT.2020 or equivalent + PrimariesRP431, // SMPTE RP 431-2 (DCI P3) + PrimariesEG432, // SMPTE EG 432-1 (Display P3) PrimariesOther = 0xff, }; @@ -173,6 +175,8 @@ struct __attribute__ ((__packed__, aligned(alignof(uint32_t)))) ColorAspects { StandardBT2020Constant, // PrimariesBT2020 and MatrixBT2020Constant StandardBT470M, // PrimariesBT470_6M and MatrixBT470_6M StandardFilm, // PrimariesGenericFilm and KR=0.253, KB=0.068 + StandardDisplayP3, // PrimariesEG432 and MatrixBT601_6 + // StandardAdobeRGB, // for placeholder only (not used by media) StandardOther = 0xff, }; @@ -282,6 +286,8 @@ inline static const char *asString(ColorAspects::Primaries i, const char *def = case ColorAspects::PrimariesBT601_6_525: return "BT601_6_525"; case ColorAspects::PrimariesGenericFilm: return "GenericFilm"; case ColorAspects::PrimariesBT2020: return "BT2020"; + case ColorAspects::PrimariesRP431: return "RP431"; + case ColorAspects::PrimariesEG432: return "EG432"; case ColorAspects::PrimariesOther: return "Other"; default: return def; } @@ -332,6 +338,8 @@ inline static const char *asString(ColorAspects::Standard i, const char *def = " case ColorAspects::StandardBT2020Constant: return "BT2020Constant"; case ColorAspects::StandardBT470M: return "BT470M"; case ColorAspects::StandardFilm: return "Film"; + case ColorAspects::StandardDisplayP3: return "DisplayP3"; + // case ColorAspects::StandardAdobeRGB: return "AdobeRGB"; case ColorAspects::StandardOther: return "Other"; default: return def; } -- GitLab From 3e5965f3184a828a1b7ee2b5bd5d5c2177807a35 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Fri, 7 Apr 2023 18:00:58 +0000 Subject: [PATCH 1179/1310] Support screenshots of HDR content Previously screenshots always rendered to either an SDR or a wide gamut colorspace. For screenshotting HDR content, this is only appropriate when the resulting screenshot (a) never leaves the device and (b) the relevant code has workarounds for the display to appropriately handle its luminance range. HDR screenshots will now have two paths: * A standard path for rendering to HLG. HLG was chosen because the OOTF shape is less hand-wavey than PQ's, does not require metadata, and bands less at 8-bits of color. * A special path for "display-native" screenshots. This is for use-cases like screen rotation where there are stricter color accuracy requirements for round-tripping. Skia already encodes the resulting screenshot by supplying an HLG CICP alongside a backwards-compatible transfer function, so it's only sufficient to change how SurfaceFlinger renders. Bug: 242324609 Bug: 276812775 Test: screencap binary Test: rotation animation Test: swiping in Recents Change-Id: Ic9edb92391d3beb38d076fba8f15e3fdcc2b8f50 --- libs/gui/LayerState.cpp | 4 +- .../aidl/android/gui/ISurfaceComposer.aidl | 4 + libs/gui/include/gui/DisplayCaptureArgs.h | 11 +- libs/renderengine/skia/SkiaRenderEngine.cpp | 6 +- libs/shaders/shaders.cpp | 58 ++++++--- services/surfaceflinger/DisplayRenderArea.cpp | 11 +- services/surfaceflinger/DisplayRenderArea.h | 4 +- services/surfaceflinger/LayerRenderArea.cpp | 5 +- services/surfaceflinger/LayerRenderArea.h | 3 +- .../surfaceflinger/RegionSamplingThread.cpp | 4 +- services/surfaceflinger/RenderArea.h | 12 +- .../surfaceflinger/ScreenCaptureOutput.cpp | 2 +- services/surfaceflinger/ScreenCaptureOutput.h | 2 + services/surfaceflinger/SurfaceFlinger.cpp | 118 ++++++++++++------ .../fuzzer/surfaceflinger_layer_fuzzer.cpp | 3 +- .../tests/unittests/CompositionTest.cpp | 2 +- 16 files changed, 173 insertions(+), 76 deletions(-) diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index fee91a40c2..2322b70d1c 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -897,11 +897,11 @@ status_t CaptureArgs::writeToParcel(Parcel* output) const { SAFE_PARCEL(output->writeInt32, static_cast(dataspace)); SAFE_PARCEL(output->writeBool, allowProtected); SAFE_PARCEL(output->writeBool, grayscale); - SAFE_PARCEL(output->writeInt32, excludeHandles.size()); for (auto& excludeHandle : excludeHandles) { SAFE_PARCEL(output->writeStrongBinder, excludeHandle); } + SAFE_PARCEL(output->writeBool, hintForSeamlessTransition); return NO_ERROR; } @@ -918,7 +918,6 @@ status_t CaptureArgs::readFromParcel(const Parcel* input) { dataspace = static_cast(value); SAFE_PARCEL(input->readBool, &allowProtected); SAFE_PARCEL(input->readBool, &grayscale); - int32_t numExcludeHandles = 0; SAFE_PARCEL_READ_SIZE(input->readInt32, &numExcludeHandles, input->dataSize()); excludeHandles.reserve(numExcludeHandles); @@ -927,6 +926,7 @@ status_t CaptureArgs::readFromParcel(const Parcel* input) { SAFE_PARCEL(input->readStrongBinder, &binder); excludeHandles.emplace(binder); } + SAFE_PARCEL(input->readBool, &hintForSeamlessTransition); return NO_ERROR; } diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index aa58e2e580..ec3266ca83 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -230,6 +230,10 @@ interface ISurfaceComposer { */ void captureDisplay(in DisplayCaptureArgs args, IScreenCaptureListener listener); + /** + * Capture the specified screen. This requires the READ_FRAME_BUFFER + * permission. + */ void captureDisplayById(long displayId, IScreenCaptureListener listener); /** diff --git a/libs/gui/include/gui/DisplayCaptureArgs.h b/libs/gui/include/gui/DisplayCaptureArgs.h index 5c794aea37..2676e0a338 100644 --- a/libs/gui/include/gui/DisplayCaptureArgs.h +++ b/libs/gui/include/gui/DisplayCaptureArgs.h @@ -41,7 +41,7 @@ struct CaptureArgs : public Parcelable { bool captureSecureLayers{false}; int32_t uid{UNSET_UID}; // Force capture to be in a color space. If the value is ui::Dataspace::UNKNOWN, the captured - // result will be in the display's colorspace. + // result will be in a colorspace appropriate for capturing the display contents // The display may use non-RGB dataspace (ex. displayP3) that could cause pixel data could be // different from SRGB (byte per color), and failed when checking colors in tests. // NOTE: In normal cases, we want the screen to be captured in display's colorspace. @@ -59,6 +59,15 @@ struct CaptureArgs : public Parcelable { std::unordered_set, SpHash> excludeHandles; + // Hint that the caller will use the screenshot animation as part of a transition animation. + // The canonical example would be screen rotation - in such a case any color shift in the + // screenshot is a detractor so composition in the display's colorspace is required. + // Otherwise, the system may choose a colorspace that is more appropriate for use-cases + // such as file encoding or for blending HDR content into an ap's UI, where the display's + // exact colorspace is not an appropriate intermediate result. + // Note that if the caller is requesting a specific dataspace, this hint does nothing. + bool hintForSeamlessTransition = false; + virtual status_t writeToParcel(Parcel* output) const; virtual status_t readFromParcel(const Parcel* input); }; diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index 8256dd8e69..2a51493cea 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -517,16 +517,18 @@ sk_sp SkiaRenderEngine::createRuntimeEffectShader( } else { runtimeEffect = effectIter->second; } + mat4 colorTransform = parameters.layer.colorTransform; colorTransform *= mat4::scale(vec4(parameters.layerDimmingRatio, parameters.layerDimmingRatio, parameters.layerDimmingRatio, 1.f)); + const auto targetBuffer = parameters.layer.source.buffer.buffer; const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr; const auto hardwareBuffer = graphicBuffer ? graphicBuffer->toAHardwareBuffer() : nullptr; - return createLinearEffectShader(parameters.shader, effect, runtimeEffect, colorTransform, - parameters.display.maxLuminance, + return createLinearEffectShader(parameters.shader, effect, runtimeEffect, + std::move(colorTransform), parameters.display.maxLuminance, parameters.display.currentLuminanceNits, parameters.layer.source.buffer.maxLuminanceNits, hardwareBuffer, parameters.display.renderIntent); diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp index a3c403e551..19518eaecc 100644 --- a/libs/shaders/shaders.cpp +++ b/libs/shaders/shaders.cpp @@ -191,31 +191,35 @@ void generateLuminanceScalesForOOTF(ui::Dataspace inputDataspace, ui::Dataspace )"); break; default: + // Input is SDR so map to its white point luminance switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) { - case HAL_DATASPACE_TRANSFER_ST2084: + // Max HLG output is nominally 1000 nits, but BT. 2100-2 allows + // for gamma correcting the HLG OOTF for displays with a different + // dynamic range. Scale to 1000 nits to apply an inverse OOTF against + // a reference display correctly. + // TODO: Use knowledge of the dimming ratio here to prevent + // unintended gamma shaft. case HAL_DATASPACE_TRANSFER_HLG: - // SDR -> HDR tonemap shader.append(R"( float3 ScaleLuminance(float3 xyz) { - return xyz * in_libtonemap_inputMaxLuminance; + return xyz * 1000.0; } )"); break; default: - // Input and output are both SDR, so no tone-mapping is expected so - // no-op the luminance normalization. shader.append(R"( - float3 ScaleLuminance(float3 xyz) { - return xyz * in_libtonemap_displayMaxLuminance; - } - )"); + float3 ScaleLuminance(float3 xyz) { + return xyz * in_libtonemap_displayMaxLuminance; + } + )"); break; } } } // Normalizes from absolute light back to relative light (maps from [0, maxNits] back to [0, 1]) -static void generateLuminanceNormalizationForOOTF(ui::Dataspace outputDataspace, +static void generateLuminanceNormalizationForOOTF(ui::Dataspace inputDataspace, + ui::Dataspace outputDataspace, std::string& shader) { switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) { case HAL_DATASPACE_TRANSFER_ST2084: @@ -226,11 +230,28 @@ static void generateLuminanceNormalizationForOOTF(ui::Dataspace outputDataspace, )"); break; case HAL_DATASPACE_TRANSFER_HLG: - shader.append(R"( - float3 NormalizeLuminance(float3 xyz) { - return xyz / 1000.0; - } - )"); + switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) { + case HAL_DATASPACE_TRANSFER_HLG: + shader.append(R"( + float3 NormalizeLuminance(float3 xyz) { + return xyz / 1000.0; + } + )"); + break; + default: + // Transcoding to HLG requires applying the inverse OOTF + // with the expectation that the OOTF is then applied during + // tonemapping downstream. + shader.append(R"( + float3 NormalizeLuminance(float3 xyz) { + // BT. 2100-2 operates on normalized luminances, + // so renormalize to the input + float ootfGain = pow(xyz.y / 1000.0, -0.2 / 1.2) / 1000.0; + return xyz * ootfGain; + } + )"); + break; + } break; default: shader.append(R"( @@ -250,7 +271,7 @@ void generateOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace, .c_str()); generateLuminanceScalesForOOTF(inputDataspace, outputDataspace, shader); - generateLuminanceNormalizationForOOTF(outputDataspace, shader); + generateLuminanceNormalizationForOOTF(inputDataspace, outputDataspace, shader); shader.append(R"( float3 OOTF(float3 linearRGB, float3 xyz) { @@ -501,9 +522,8 @@ std::vector buildLinearEffectUniforms( tonemap::Metadata metadata{.displayMaxLuminance = maxDisplayLuminance, // If the input luminance is unknown, use display luminance (aka, - // no-op any luminance changes) - // This will be the case for eg screenshots in addition to - // uncalibrated displays + // no-op any luminance changes). + // This is expected to only be meaningful for PQ content .contentMaxLuminance = maxLuminance > 0 ? maxLuminance : maxDisplayLuminance, .currentDisplayLuminance = currentDisplayLuminanceNits > 0 diff --git a/services/surfaceflinger/DisplayRenderArea.cpp b/services/surfaceflinger/DisplayRenderArea.cpp index 8f39e26e0f..e55cd3ea42 100644 --- a/services/surfaceflinger/DisplayRenderArea.cpp +++ b/services/surfaceflinger/DisplayRenderArea.cpp @@ -35,21 +35,24 @@ std::unique_ptr DisplayRenderArea::create(wp di const Rect& sourceCrop, ui::Size reqSize, ui::Dataspace reqDataSpace, bool useIdentityTransform, + bool hintForSeamlessTransition, bool allowSecureLayers) { if (auto display = displayWeak.promote()) { // Using new to access a private constructor. return std::unique_ptr( new DisplayRenderArea(std::move(display), sourceCrop, reqSize, reqDataSpace, - useIdentityTransform, allowSecureLayers)); + useIdentityTransform, hintForSeamlessTransition, + allowSecureLayers)); } return nullptr; } DisplayRenderArea::DisplayRenderArea(sp display, const Rect& sourceCrop, ui::Size reqSize, ui::Dataspace reqDataSpace, - bool useIdentityTransform, bool allowSecureLayers) - : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, allowSecureLayers, - applyDeviceOrientation(useIdentityTransform, *display)), + bool useIdentityTransform, bool hintForSeamlessTransition, + bool allowSecureLayers) + : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, hintForSeamlessTransition, + allowSecureLayers, applyDeviceOrientation(useIdentityTransform, *display)), mDisplay(std::move(display)), mSourceCrop(sourceCrop) {} diff --git a/services/surfaceflinger/DisplayRenderArea.h b/services/surfaceflinger/DisplayRenderArea.h index ce5410a90d..9a4981c881 100644 --- a/services/surfaceflinger/DisplayRenderArea.h +++ b/services/surfaceflinger/DisplayRenderArea.h @@ -30,6 +30,7 @@ public: static std::unique_ptr create(wp, const Rect& sourceCrop, ui::Size reqSize, ui::Dataspace, bool useIdentityTransform, + bool hintForSeamlessTransition, bool allowSecureLayers = true); const ui::Transform& getTransform() const override; @@ -39,7 +40,8 @@ public: private: DisplayRenderArea(sp, const Rect& sourceCrop, ui::Size reqSize, - ui::Dataspace, bool useIdentityTransform, bool allowSecureLayers = true); + ui::Dataspace, bool useIdentityTransform, bool hintForSeamlessTransition, + bool allowSecureLayers = true); const sp mDisplay; const Rect mSourceCrop; diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp index 1b8ff28a76..f6b5afc6a8 100644 --- a/services/surfaceflinger/LayerRenderArea.cpp +++ b/services/surfaceflinger/LayerRenderArea.cpp @@ -40,8 +40,9 @@ void reparentForDrawing(const sp& oldParent, const sp& newParent, LayerRenderArea::LayerRenderArea(SurfaceFlinger& flinger, sp layer, const Rect& crop, ui::Size reqSize, ui::Dataspace reqDataSpace, bool childrenOnly, bool allowSecureLayers, const ui::Transform& layerTransform, - const Rect& layerBufferSize) - : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, allowSecureLayers), + const Rect& layerBufferSize, bool hintForSeamlessTransition) + : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, hintForSeamlessTransition, + allowSecureLayers), mLayer(std::move(layer)), mLayerTransform(layerTransform), mLayerBufferSize(layerBufferSize), diff --git a/services/surfaceflinger/LayerRenderArea.h b/services/surfaceflinger/LayerRenderArea.h index 9bb13b3d6a..aa609eea38 100644 --- a/services/surfaceflinger/LayerRenderArea.h +++ b/services/surfaceflinger/LayerRenderArea.h @@ -34,7 +34,8 @@ class LayerRenderArea : public RenderArea { public: LayerRenderArea(SurfaceFlinger& flinger, sp layer, const Rect& crop, ui::Size reqSize, ui::Dataspace reqDataSpace, bool childrenOnly, bool allowSecureLayers, - const ui::Transform& layerTransform, const Rect& layerBufferSize); + const ui::Transform& layerTransform, const Rect& layerBufferSize, + bool hintForSeamlessTransition); const ui::Transform& getTransform() const override; bool isSecure() const override; diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp index 531d277ffc..8f658d5a09 100644 --- a/services/surfaceflinger/RegionSamplingThread.cpp +++ b/services/surfaceflinger/RegionSamplingThread.cpp @@ -277,10 +277,12 @@ void RegionSamplingThread::captureSample() { const Rect sampledBounds = sampleRegion.bounds(); constexpr bool kUseIdentityTransform = false; + constexpr bool kHintForSeamlessTransition = false; SurfaceFlinger::RenderAreaFuture renderAreaFuture = ftl::defer([=] { return DisplayRenderArea::create(displayWeak, sampledBounds, sampledBounds.getSize(), - ui::Dataspace::V0_SRGB, kUseIdentityTransform); + ui::Dataspace::V0_SRGB, kUseIdentityTransform, + kHintForSeamlessTransition); }); std::unordered_set, SpHash> listeners; diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h index 910fce043c..71b85bd3b2 100644 --- a/services/surfaceflinger/RenderArea.h +++ b/services/surfaceflinger/RenderArea.h @@ -25,12 +25,14 @@ public: static float getCaptureFillValue(CaptureFill captureFill); RenderArea(ui::Size reqSize, CaptureFill captureFill, ui::Dataspace reqDataSpace, - bool allowSecureLayers = false, RotationFlags rotation = ui::Transform::ROT_0) + bool hintForSeamlessTransition, bool allowSecureLayers = false, + RotationFlags rotation = ui::Transform::ROT_0) : mAllowSecureLayers(allowSecureLayers), mReqSize(reqSize), mReqDataSpace(reqDataSpace), mCaptureFill(captureFill), - mRotationFlags(rotation) {} + mRotationFlags(rotation), + mHintForSeamlessTransition(hintForSeamlessTransition) {} static std::function>>()> fromTraverseLayersLambda( std::function traverseLayers) { @@ -90,6 +92,10 @@ public: // capture operation. virtual sp getParentLayer() const { return nullptr; } + // Returns whether the render result may be used for system animations that + // must preserve the exact colors of the display. + bool getHintForSeamlessTransition() const { return mHintForSeamlessTransition; } + protected: const bool mAllowSecureLayers; @@ -98,7 +104,7 @@ private: const ui::Dataspace mReqDataSpace; const CaptureFill mCaptureFill; const RotationFlags mRotationFlags; - const Rect mLayerStackSpaceRect; + const bool mHintForSeamlessTransition; }; } // namespace android diff --git a/services/surfaceflinger/ScreenCaptureOutput.cpp b/services/surfaceflinger/ScreenCaptureOutput.cpp index a1d5cd7379..b70b53d05a 100644 --- a/services/surfaceflinger/ScreenCaptureOutput.cpp +++ b/services/surfaceflinger/ScreenCaptureOutput.cpp @@ -36,6 +36,7 @@ std::shared_ptr createScreenCaptureOutput(ScreenCaptureOutp output->setLayerFilter({args.layerStack}); output->setRenderSurface(std::make_unique(std::move(args.buffer))); output->setDisplayBrightness(args.sdrWhitePointNits, args.displayBrightnessNits); + output->editState().clientTargetBrightness = args.targetBrightness; output->setDisplayColorProfile(std::make_unique( compositionengine::DisplayColorProfileCreationArgsBuilder() @@ -75,7 +76,6 @@ renderengine::DisplaySettings ScreenCaptureOutput::generateClientCompositionDisp auto clientCompositionDisplay = compositionengine::impl::Output::generateClientCompositionDisplaySettings(); clientCompositionDisplay.clip = mRenderArea.getSourceCrop(); - clientCompositionDisplay.targetLuminanceNits = -1; return clientCompositionDisplay; } diff --git a/services/surfaceflinger/ScreenCaptureOutput.h b/services/surfaceflinger/ScreenCaptureOutput.h index 4e5a0ccfd2..3c307b0733 100644 --- a/services/surfaceflinger/ScreenCaptureOutput.h +++ b/services/surfaceflinger/ScreenCaptureOutput.h @@ -33,6 +33,8 @@ struct ScreenCaptureOutputArgs { std::shared_ptr buffer; float sdrWhitePointNits; float displayBrightnessNits; + // Counterintuitively, when targetBrightness > 1.0 then dim the scene. + float targetBrightness; bool regionSampling; }; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index c88bff5ed8..d406afff1f 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -6915,6 +6915,32 @@ status_t SurfaceFlinger::setSchedAttr(bool enabled) { return NO_ERROR; } +namespace { + +ui::Dataspace pickBestDataspace(ui::Dataspace requestedDataspace, const DisplayDevice* display, + bool capturingHdrLayers, bool hintForSeamlessTransition) { + if (requestedDataspace != ui::Dataspace::UNKNOWN || display == nullptr) { + return requestedDataspace; + } + + const auto& state = display->getCompositionDisplay()->getState(); + + const auto dataspaceForColorMode = ui::pickDataspaceFor(state.colorMode); + + if (capturingHdrLayers && !hintForSeamlessTransition) { + // For now since we only support 8-bit screenshots, just use HLG and + // assume that 1.0 >= display max luminance. This isn't quite as future + // proof as PQ is, but is good enough. + // Consider using PQ once we support 16-bit screenshots and we're able + // to consistently supply metadata to image encoders. + return ui::Dataspace::BT2020_HLG; + } + + return dataspaceForColorMode; +} + +} // namespace + status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, const sp& captureListener) { ATRACE_CALL(); @@ -6930,7 +6956,6 @@ status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, ui::LayerStack layerStack; ui::Size reqSize(args.width, args.height); std::unordered_set excludeLayerIds; - ui::Dataspace dataspace; { Mutex::Autolock lock(mStateLock); sp display = getDisplayDeviceLocked(args.displayToken); @@ -6952,17 +6977,12 @@ status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, return NAME_NOT_FOUND; } } - - // Allow the caller to specify a dataspace regardless of the display's color mode, e.g. if - // it wants sRGB regardless of the display's wide color mode. - dataspace = args.dataspace == ui::Dataspace::UNKNOWN - ? ui::pickDataspaceFor(display->getCompositionDisplay()->getState().colorMode) - : args.dataspace; } RenderAreaFuture renderAreaFuture = ftl::defer([=] { - return DisplayRenderArea::create(displayWeak, args.sourceCrop, reqSize, dataspace, - args.useIdentityTransform, args.captureSecureLayers); + return DisplayRenderArea::create(displayWeak, args.sourceCrop, reqSize, + ui::Dataspace::UNKNOWN, args.useIdentityTransform, + args.hintForSeamlessTransition, args.captureSecureLayers); }); GetLayerSnapshotsFunction getLayerSnapshots; @@ -6988,7 +7008,6 @@ status_t SurfaceFlinger::captureDisplay(DisplayId displayId, ui::LayerStack layerStack; wp displayWeak; ui::Size size; - ui::Dataspace dataspace; { Mutex::Autolock lock(mStateLock); @@ -7000,12 +7019,12 @@ status_t SurfaceFlinger::captureDisplay(DisplayId displayId, displayWeak = display; layerStack = display->getLayerStack(); size = display->getLayerStackSpaceRect().getSize(); - dataspace = ui::pickDataspaceFor(display->getCompositionDisplay()->getState().colorMode); } RenderAreaFuture renderAreaFuture = ftl::defer([=] { - return DisplayRenderArea::create(displayWeak, Rect(), size, dataspace, + return DisplayRenderArea::create(displayWeak, Rect(), size, ui::Dataspace::UNKNOWN, false /* useIdentityTransform */, + false /* hintForSeamlessTransition */, false /* captureSecureLayers */); }); @@ -7047,7 +7066,7 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, sp parent; Rect crop(args.sourceCrop); std::unordered_set excludeLayerIds; - ui::Dataspace dataspace; + ui::Dataspace dataspace = args.dataspace; // Call this before holding mStateLock to avoid any deadlocking. bool canCaptureBlackoutContent = hasCaptureBlackoutContentPermission(); @@ -7094,12 +7113,6 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, return NAME_NOT_FOUND; } } - - // The dataspace is depended on the color mode of display, that could use non-native mode - // (ex. displayP3) to enhance the content, but some cases are checking native RGB in bytes, - // and failed if display is not in native mode. This provide a way to force using native - // colors when capture. - dataspace = args.dataspace; } // mStateLock // really small crop or frameScale @@ -7128,7 +7141,8 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, return std::make_unique(*this, parent, crop, reqSize, dataspace, childrenOnly, args.captureSecureLayers, - layerTransform, layerBufferSize); + layerTransform, layerBufferSize, + args.hintForSeamlessTransition); }); GetLayerSnapshotsFunction getLayerSnapshots; if (mLayerLifecycleManagerEnabled) { @@ -7314,29 +7328,48 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( return ftl::yield(base::unexpected(PERMISSION_DENIED)).share(); } - captureResults.buffer = buffer->getBuffer(); - auto dataspace = renderArea->getReqDataSpace(); + auto capturedBuffer = buffer; + + auto requestedDataspace = renderArea->getReqDataSpace(); auto parent = renderArea->getParentLayer(); auto renderIntent = RenderIntent::TONE_MAP_COLORIMETRIC; auto sdrWhitePointNits = DisplayDevice::sDefaultMaxLumiance; auto displayBrightnessNits = DisplayDevice::sDefaultMaxLumiance; - if (dataspace == ui::Dataspace::UNKNOWN && parent) { + captureResults.capturedDataspace = requestedDataspace; + + { Mutex::Autolock lock(mStateLock); - auto display = findDisplay([layerStack = parent->getLayerStack()](const auto& display) { - return display.getLayerStack() == layerStack; - }); - if (!display) { - // If the layer is not on a display, use the dataspace for the default display. - display = getDefaultDisplayDeviceLocked(); + const DisplayDevice* display = nullptr; + if (parent) { + display = findDisplay([layerStack = parent->getLayerStack()](const auto& display) { + return display.getLayerStack() == layerStack; + }).get(); + } + + if (display == nullptr) { + display = renderArea->getDisplayDevice().get(); + } + + if (display == nullptr) { + display = getDefaultDisplayDeviceLocked().get(); } - dataspace = ui::pickDataspaceFor(display->getCompositionDisplay()->getState().colorMode); - renderIntent = display->getCompositionDisplay()->getState().renderIntent; - sdrWhitePointNits = display->getCompositionDisplay()->getState().sdrWhitePointNits; - displayBrightnessNits = display->getCompositionDisplay()->getState().displayBrightnessNits; + if (display != nullptr) { + const auto& state = display->getCompositionDisplay()->getState(); + captureResults.capturedDataspace = + pickBestDataspace(requestedDataspace, display, captureResults.capturedHdrLayers, + renderArea->getHintForSeamlessTransition()); + sdrWhitePointNits = state.sdrWhitePointNits; + displayBrightnessNits = state.displayBrightnessNits; + + if (requestedDataspace == ui::Dataspace::UNKNOWN) { + renderIntent = state.renderIntent; + } + } } - captureResults.capturedDataspace = dataspace; + + captureResults.buffer = capturedBuffer->getBuffer(); ui::LayerStack layerStack{ui::DEFAULT_LAYER_STACK}; if (!layers.empty()) { @@ -7353,9 +7386,9 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( return layerFEs; }; - auto present = [this, buffer = std::move(buffer), dataspace, sdrWhitePointNits, - displayBrightnessNits, grayscale, layerFEs = copyLayerFEs(), layerStack, - regionSampling, renderArea = std::move(renderArea), + auto present = [this, buffer = capturedBuffer, dataspace = captureResults.capturedDataspace, + sdrWhitePointNits, displayBrightnessNits, grayscale, layerFEs = copyLayerFEs(), + layerStack, regionSampling, renderArea = std::move(renderArea), renderIntent]() -> FenceResult { std::unique_ptr compositionEngine = mFactory.createCompositionEngine(); @@ -7364,6 +7397,16 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( compositionengine::Output::ColorProfile colorProfile{.dataspace = dataspace, .renderIntent = renderIntent}; + float targetBrightness = 1.0f; + if (dataspace == ui::Dataspace::BT2020_HLG) { + const float maxBrightnessNits = displayBrightnessNits / sdrWhitePointNits * 203; + // With a low dimming ratio, don't fit the entire curve. Otherwise mixed content + // will appear way too bright. + if (maxBrightnessNits < 1000.f) { + targetBrightness = 1000.f / maxBrightnessNits; + } + } + std::shared_ptr output = createScreenCaptureOutput( ScreenCaptureOutputArgs{.compositionEngine = *compositionEngine, .colorProfile = colorProfile, @@ -7372,6 +7415,7 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( .buffer = std::move(buffer), .sdrWhitePointNits = sdrWhitePointNits, .displayBrightnessNits = displayBrightnessNits, + .targetBrightness = targetBrightness, .regionSampling = regionSampling}); const float colorSaturation = grayscale ? 0 : 1; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp index c3dcb85686..921cae4e41 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp @@ -177,7 +177,8 @@ void LayerFuzzer::invokeBufferStateLayer() { {mFdp.ConsumeIntegral(), mFdp.ConsumeIntegral()} /*reqSize*/, mFdp.PickValueInArray(kDataspaces), mFdp.ConsumeBool(), - mFdp.ConsumeBool(), getFuzzedTransform(), getFuzzedRect()); + mFdp.ConsumeBool(), getFuzzedTransform(), getFuzzedRect(), + mFdp.ConsumeBool()); layerArea.render([]() {} /*drawLayers*/); if (!ownsHandle) { diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 156007b862..6ca21bd458 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -200,7 +200,7 @@ void CompositionTest::captureScreenComposition() { constexpr bool regionSampling = false; auto renderArea = DisplayRenderArea::create(mDisplay, sourceCrop, sourceCrop.getSize(), - ui::Dataspace::V0_SRGB, ui::Transform::ROT_0); + ui::Dataspace::V0_SRGB, true, true); auto traverseLayers = [this](const LayerVector::Visitor& visitor) { return mFlinger.traverseLayersInLayerStack(mDisplay->getLayerStack(), -- GitLab From 5ed11df4d12f9b8256dabbef9f4c7e77021e1c4c Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 25 Apr 2023 15:44:57 -0700 Subject: [PATCH 1180/1310] [sf] run transaction trace tests with hwasan Test: presubmit Fixes: 272810305 Change-Id: Idf934df00a218cacee9d743a158e35a378bcfec3 --- services/surfaceflinger/tests/tracing/Android.bp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/surfaceflinger/tests/tracing/Android.bp b/services/surfaceflinger/tests/tracing/Android.bp index aa6c74eb65..21ebaea8ad 100644 --- a/services/surfaceflinger/tests/tracing/Android.bp +++ b/services/surfaceflinger/tests/tracing/Android.bp @@ -30,7 +30,7 @@ cc_test { ], test_suites: ["device-tests"], sanitize: { - address: false, + address: true, }, srcs: [ ":libsurfaceflinger_sources", -- GitLab From 3fd3acc99b20cd29837053716086eca8406cd3b0 Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Tue, 25 Apr 2023 20:40:52 +0000 Subject: [PATCH 1181/1310] Add pending command buffer reset when error occurs, so it won't affect the upcoming commands. Bug: 273525126 Test: manual Merged-In: If91cf6253b0d8f0d8c5d761f7cf257d090fcaf6f Change-Id: I08f938764dda78e6a85e625ae6767816582f5312 --- services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp index 79dcd159d3..fb5f0cb30f 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp @@ -765,6 +765,7 @@ Error AidlComposer::execute() { auto status = mAidlComposerClient->executeCommands(commands, &results); if (!status.isOk()) { ALOGE("executeCommands failed %s", status.getDescription().c_str()); + mWriter.reset(); return static_cast(status.getServiceSpecificError()); } @@ -776,6 +777,7 @@ Error AidlComposer::execute() { const auto index = static_cast(cmdErr.commandIndex); if (index < 0 || index >= commands.size()) { ALOGE("invalid command index %zu", index); + mWriter.reset(); return Error::BAD_PARAMETER; } -- GitLab From efdc59246f25f17a1c3dafdc74640cbe474b4961 Mon Sep 17 00:00:00 2001 From: Pablo Gamito Date: Wed, 19 Apr 2023 09:44:47 +0000 Subject: [PATCH 1182/1310] Dump shell side transition trace with bug reports Test: atest dumpstate_test Bug: 276701336 Bug: 277181336 Bug: 278691227 Change-Id: I6050899eb739bd640b1842caff46c1dee9dbf3a7 --- cmds/dumpstate/dumpstate.cpp | 22 ++++++++++++---------- cmds/dumpstate/tests/dumpstate_test.cpp | 23 ++++++++++++++--------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp index 8ca927e920..5dbf7ac715 100644 --- a/cmds/dumpstate/dumpstate.cpp +++ b/cmds/dumpstate/dumpstate.cpp @@ -3362,23 +3362,25 @@ void Dumpstate::MaybeSnapshotUiTraces() { return; } - // Include the proto logging from WMShell. - RunCommand( - // Empty name because it's not intended to be classified as a bugreport section. - // Actual logging files can be found as "/data/misc/wmtrace/shell_log.winscope" - // in the bugreport. - "", {"dumpsys", "activity", "service", "SystemUIService", - "WMShell", "protolog", "save-for-bugreport"}, - CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build()); + const std::vector> dumpTracesForBugReportCommands = { + {"dumpsys", "activity", "service", "SystemUIService", "WMShell", "protolog", + "save-for-bugreport"}, + {"dumpsys", "activity", "service", "SystemUIService", "WMShell", "transitions", "tracing", + "save-for-bugreport"}, + {"cmd", "input_method", "tracing", "save-for-bugreport"}, + {"cmd", "window", "tracing", "save-for-bugreport"}, + {"cmd", "window", "shell", "tracing", "save-for-bugreport"}, + }; - for (const auto& service : {"input_method", "window", "window shell"}) { + for (const auto& command : dumpTracesForBugReportCommands) { RunCommand( // Empty name because it's not intended to be classified as a bugreport section. // Actual tracing files can be found in "/data/misc/wmtrace/" in the bugreport. - "", {"cmd", service, "tracing", "save-for-bugreport"}, + "", command, CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build()); } + // This command needs to be run as root static const auto SURFACEFLINGER_COMMAND_SAVE_ALL_TRACES = std::vector { "service", "call", "SurfaceFlinger", "1042" }; diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp index 93d8cdfb83..5cbcf9fdf6 100644 --- a/cmds/dumpstate/tests/dumpstate_test.cpp +++ b/cmds/dumpstate/tests/dumpstate_test.cpp @@ -997,17 +997,22 @@ TEST_F(DumpstateTest, DumpPool_withParallelRunDisabled_isNull) { EXPECT_FALSE(ds.dump_pool_); } -TEST_F(DumpstateBaseTest, PreDumpUiData) { - // SurfaceFlinger's transactions trace is always enabled, i.e. it is always pre-dumped - static const auto kTransactionsTrace = - std::filesystem::path {"/data/misc/wmtrace/transactions_trace.winscope"}; +TEST_F(DumpstateTest, PreDumpUiData) { + // These traces are always enabled, i.e. they are always pre-dumped + const std::vector uiTraces = { + std::filesystem::path{"/data/misc/wmtrace/transactions_trace.winscope"}, + std::filesystem::path{"/data/misc/wmtrace/transition_trace.winscope"}, + std::filesystem::path{"/data/misc/wmtrace/shell_transition_trace.winscope"}, + }; - std::system(("rm " + kTransactionsTrace.string()).c_str()); - EXPECT_FALSE(std::filesystem::exists(kTransactionsTrace)); + for (const auto traceFile : uiTraces) { + std::system(("rm -f " + traceFile.string()).c_str()); + EXPECT_FALSE(std::filesystem::exists(traceFile)) << traceFile << " was not deleted."; - Dumpstate& ds_ = Dumpstate::GetInstance(); - ds_.PreDumpUiData(); - EXPECT_TRUE(std::filesystem::exists(kTransactionsTrace)); + Dumpstate& ds_ = Dumpstate::GetInstance(); + ds_.PreDumpUiData(); + EXPECT_TRUE(std::filesystem::exists(traceFile)) << traceFile << " was not created."; + } } class ZippedBugReportStreamTest : public DumpstateBaseTest { -- GitLab From 49600b10aa5675d4e7e985203d69f252ead13e45 Mon Sep 17 00:00:00 2001 From: Devin Moore Date: Tue, 25 Apr 2023 00:17:13 +0000 Subject: [PATCH 1183/1310] Allow sensors list to be empty Test: atest VtsHalSensorManagerV1_0TargetTest Bug: 278013275 Change-Id: I091f57de9570b0ace3a8da76f16fe0e83f0aa624 --- libs/sensor/SensorManager.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp index ba190e0855..980f8d16d2 100644 --- a/libs/sensor/SensorManager.cpp +++ b/libs/sensor/SensorManager.cpp @@ -176,11 +176,8 @@ status_t SensorManager::assertStateLocked() { mSensors = mSensorServer->getSensorList(mOpPackageName); size_t count = mSensors.size(); - if (count == 0) { - ALOGE("Failed to get Sensor list"); - mSensorServer.clear(); - return UNKNOWN_ERROR; - } + // If count is 0, mSensorList will be non-null. This is old + // existing behavior and callers expect this. mSensorList = static_cast(malloc(count * sizeof(Sensor*))); LOG_ALWAYS_FATAL_IF(mSensorList == nullptr, "mSensorList NULL"); -- GitLab From fd1f557c48bf3a36f3a5313484698abf61df9b45 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Wed, 26 Apr 2023 15:59:48 -0400 Subject: [PATCH 1184/1310] Convert backend texture creation failure from ALOGE to FATAL log If the texture is invalid, we'll hit a fatal log later, but at that point we won't have the stack trace to see where the invalid texture was coming from. Convert this log into fatal so we can get a stack trace that includes the caller. In addition, abort if the texture has a width or height of zero, which has been seen in recent crashes. Bug: 279524845 Test: make Change-Id: I1c8593c3da533692a43bd251dcdf2baa9572041a --- libs/renderengine/skia/AutoBackendTexture.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp index 932be56cde..c412c9cff7 100644 --- a/libs/renderengine/skia/AutoBackendTexture.cpp +++ b/libs/renderengine/skia/AutoBackendTexture.cpp @@ -43,10 +43,12 @@ AutoBackendTexture::AutoBackendTexture(GrDirectContext* context, AHardwareBuffer createProtectedImage, backendFormat, isOutputBuffer); mColorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format); - ALOGE_IF(!mBackendTexture.isValid(), - "Failed to create a valid texture. [%p]:[%d,%d] isProtected:%d isWriteable:%d " - "format:%d", - this, desc.width, desc.height, createProtectedImage, isOutputBuffer, desc.format); + if (!mBackendTexture.isValid() || !desc.width || !desc.height) { + LOG_ALWAYS_FATAL("Failed to create a valid texture. [%p]:[%d,%d] isProtected:%d " + "isWriteable:%d format:%d", + this, desc.width, desc.height, createProtectedImage, isOutputBuffer, + desc.format); + } } AutoBackendTexture::~AutoBackendTexture() { -- GitLab From 6c440aeb79fdadca718d34f6eca754589730afb0 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Fri, 21 Apr 2023 15:01:03 -0400 Subject: [PATCH 1185/1310] Make VSyncCallbackRegistration's move operator= call unregisterCallback Skipping unregisterCallback means that we might delete a callback while it is being called. Note that EventThread uses the move operator=, and this behaves differently than if it were stored in a unique_ptr like in MessageQueue. Update move operator= so that they do behave the same, and add tests verifying this, along with a couple other behavior tests. Note that this may result in deadlocks similar to those in b/276367387. That is fixed by If490f88115aa298d31aa9ad392a1f9f9dc987549. Cleanups: - Remove comment for VsyncCallbackRegistration regarding the lifetime requirement for the VSyncDispatch. Icdb80253436b4d0034fc20fcae8583efb7c30292 switched the VSyncDispatch to an std::shared_ptr from a reference, so the client no longer needs to ensure that the VSyncDispatch outlives the VsyncCallbackRegistration. - Replace mValidToken with using an std::optional for the token. Bug: 279209321 Test: VSyncCallbackRegistrationTest Change-Id: I3c1eccb36914f29560600d48bb08b1b8f2fe7c96 --- .../surfaceflinger/Scheduler/VSyncDispatch.h | 9 +- .../Scheduler/VSyncDispatchTimerQueue.cpp | 31 ++-- .../surfaceflinger/tests/unittests/Android.bp | 1 + .../VSyncCallbackRegistrationTest.cpp | 144 ++++++++++++++++++ 4 files changed, 162 insertions(+), 23 deletions(-) create mode 100644 services/surfaceflinger/tests/unittests/VSyncCallbackRegistrationTest.cpp diff --git a/services/surfaceflinger/Scheduler/VSyncDispatch.h b/services/surfaceflinger/Scheduler/VSyncDispatch.h index 77875e3b4d..c3a952f689 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatch.h +++ b/services/surfaceflinger/Scheduler/VSyncDispatch.h @@ -155,10 +155,6 @@ protected: VSyncDispatch& operator=(const VSyncDispatch&) = delete; }; -/* - * Helper class to operate on registered callbacks. It is up to user of the class to ensure - * that VsyncDispatch lifetime exceeds the lifetime of VSyncCallbackRegistation. - */ class VSyncCallbackRegistration { public: VSyncCallbackRegistration(std::shared_ptr, VSyncDispatch::Callback, @@ -178,9 +174,10 @@ public: CancelResult cancel(); private: + friend class VSyncCallbackRegistrationTest; + std::shared_ptr mDispatch; - VSyncDispatch::CallbackToken mToken; - bool mValidToken; + std::optional mToken; }; } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp index 26389eb8cc..d431fbfff0 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp @@ -438,47 +438,44 @@ VSyncCallbackRegistration::VSyncCallbackRegistration(std::shared_ptrregisterCallback(std::move(callback), std::move(callbackName))), - mValidToken(true) {} + mToken(mDispatch->registerCallback(std::move(callback), std::move(callbackName))) {} VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncCallbackRegistration&& other) - : mDispatch(std::move(other.mDispatch)), - mToken(std::move(other.mToken)), - mValidToken(std::move(other.mValidToken)) { - other.mValidToken = false; -} + : mDispatch(std::move(other.mDispatch)), mToken(std::exchange(other.mToken, std::nullopt)) {} VSyncCallbackRegistration& VSyncCallbackRegistration::operator=(VSyncCallbackRegistration&& other) { + if (this == &other) return *this; + if (mToken) { + mDispatch->unregisterCallback(*mToken); + } mDispatch = std::move(other.mDispatch); - mToken = std::move(other.mToken); - mValidToken = std::move(other.mValidToken); - other.mValidToken = false; + mToken = std::exchange(other.mToken, std::nullopt); return *this; } VSyncCallbackRegistration::~VSyncCallbackRegistration() { - if (mValidToken) mDispatch->unregisterCallback(mToken); + if (mToken) mDispatch->unregisterCallback(*mToken); } ScheduleResult VSyncCallbackRegistration::schedule(VSyncDispatch::ScheduleTiming scheduleTiming) { - if (!mValidToken) { + if (!mToken) { return std::nullopt; } - return mDispatch->schedule(mToken, scheduleTiming); + return mDispatch->schedule(*mToken, scheduleTiming); } ScheduleResult VSyncCallbackRegistration::update(VSyncDispatch::ScheduleTiming scheduleTiming) { - if (!mValidToken) { + if (!mToken) { return std::nullopt; } - return mDispatch->update(mToken, scheduleTiming); + return mDispatch->update(*mToken, scheduleTiming); } CancelResult VSyncCallbackRegistration::cancel() { - if (!mValidToken) { + if (!mToken) { return CancelResult::Error; } - return mDispatch->cancel(mToken); + return mDispatch->cancel(*mToken); } } // namespace android::scheduler diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index 201d37ff7e..3713275b1b 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -128,6 +128,7 @@ cc_test { "TransactionTracingTest.cpp", "TunnelModeEnabledReporterTest.cpp", "StrongTypingTest.cpp", + "VSyncCallbackRegistrationTest.cpp", "VSyncDispatchTimerQueueTest.cpp", "VSyncDispatchRealtimeTest.cpp", "VsyncModulatorTest.cpp", diff --git a/services/surfaceflinger/tests/unittests/VSyncCallbackRegistrationTest.cpp b/services/surfaceflinger/tests/unittests/VSyncCallbackRegistrationTest.cpp new file mode 100644 index 0000000000..69b3861a69 --- /dev/null +++ b/services/surfaceflinger/tests/unittests/VSyncCallbackRegistrationTest.cpp @@ -0,0 +1,144 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "LibSurfaceFlingerUnittests" + +#include +#include + +#include "Scheduler/VSyncDispatch.h" +#include "mock/MockVSyncDispatch.h" + +using namespace testing; + +namespace android::scheduler { + +class VSyncCallbackRegistrationTest : public Test { +protected: + VSyncDispatch::Callback mCallback = [](nsecs_t, nsecs_t, nsecs_t) {}; + + std::shared_ptr mVsyncDispatch = std::make_shared(); + VSyncDispatch::CallbackToken mCallbackToken{7}; + std::string mCallbackName = "callback"; + + std::shared_ptr mVsyncDispatch2 = std::make_shared(); + VSyncDispatch::CallbackToken mCallbackToken2{42}; + std::string mCallbackName2 = "callback2"; + + void assertDispatch(const VSyncCallbackRegistration& registration, + std::shared_ptr dispatch) { + ASSERT_EQ(registration.mDispatch, dispatch); + } + + void assertToken(const VSyncCallbackRegistration& registration, + const std::optional& token) { + ASSERT_EQ(registration.mToken, token); + } +}; + +TEST_F(VSyncCallbackRegistrationTest, unregistersCallbackOnDestruction) { + // TODO (b/279581095): With ftl::Function, `_` can be replaced with + // `mCallback`, here and in other calls to `registerCallback, since the + // ftl version has an operator==, unlike std::function. + EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName)) + .WillOnce(Return(mCallbackToken)); + EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1); + + VSyncCallbackRegistration registration(mVsyncDispatch, mCallback, mCallbackName); + ASSERT_NO_FATAL_FAILURE(assertDispatch(registration, mVsyncDispatch)); + ASSERT_NO_FATAL_FAILURE(assertToken(registration, mCallbackToken)); +} + +TEST_F(VSyncCallbackRegistrationTest, unregistersCallbackOnPointerMove) { + { + InSequence seq; + EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName)) + .WillOnce(Return(mCallbackToken)); + EXPECT_CALL(*mVsyncDispatch2, registerCallback(_, mCallbackName2)) + .WillOnce(Return(mCallbackToken2)); + EXPECT_CALL(*mVsyncDispatch2, unregisterCallback(mCallbackToken2)).Times(1); + EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1); + } + + auto registration = + std::make_unique(mVsyncDispatch, mCallback, mCallbackName); + + auto registration2 = + std::make_unique(mVsyncDispatch2, mCallback, mCallbackName2); + + registration2 = std::move(registration); + + ASSERT_NO_FATAL_FAILURE(assertDispatch(*registration2.get(), mVsyncDispatch)); + ASSERT_NO_FATAL_FAILURE(assertToken(*registration2.get(), mCallbackToken)); +} + +TEST_F(VSyncCallbackRegistrationTest, unregistersCallbackOnMoveOperator) { + { + InSequence seq; + EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName)) + .WillOnce(Return(mCallbackToken)); + EXPECT_CALL(*mVsyncDispatch2, registerCallback(_, mCallbackName2)) + .WillOnce(Return(mCallbackToken2)); + EXPECT_CALL(*mVsyncDispatch2, unregisterCallback(mCallbackToken2)).Times(1); + EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1); + } + + VSyncCallbackRegistration registration(mVsyncDispatch, mCallback, mCallbackName); + + VSyncCallbackRegistration registration2(mVsyncDispatch2, mCallback, mCallbackName2); + + registration2 = std::move(registration); + + ASSERT_NO_FATAL_FAILURE(assertDispatch(registration, nullptr)); + ASSERT_NO_FATAL_FAILURE(assertToken(registration, std::nullopt)); + + ASSERT_NO_FATAL_FAILURE(assertDispatch(registration2, mVsyncDispatch)); + ASSERT_NO_FATAL_FAILURE(assertToken(registration2, mCallbackToken)); +} + +TEST_F(VSyncCallbackRegistrationTest, moveConstructor) { + EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName)) + .WillOnce(Return(mCallbackToken)); + EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1); + + VSyncCallbackRegistration registration(mVsyncDispatch, mCallback, mCallbackName); + VSyncCallbackRegistration registration2(std::move(registration)); + + ASSERT_NO_FATAL_FAILURE(assertDispatch(registration, nullptr)); + ASSERT_NO_FATAL_FAILURE(assertToken(registration, std::nullopt)); + + ASSERT_NO_FATAL_FAILURE(assertDispatch(registration2, mVsyncDispatch)); + ASSERT_NO_FATAL_FAILURE(assertToken(registration2, mCallbackToken)); +} + +TEST_F(VSyncCallbackRegistrationTest, moveOperatorEqualsSelf) { + EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName)) + .WillOnce(Return(mCallbackToken)); + EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1); + + VSyncCallbackRegistration registration(mVsyncDispatch, mCallback, mCallbackName); + + // Use a reference so the compiler doesn't realize that registration is + // being moved to itself. + VSyncCallbackRegistration& registrationRef = registration; + registration = std::move(registrationRef); + + ASSERT_NO_FATAL_FAILURE(assertDispatch(registration, mVsyncDispatch)); + ASSERT_NO_FATAL_FAILURE(assertToken(registration, mCallbackToken)); +} + +} // namespace android::scheduler -- GitLab From b2253605afdb7496ed534d6f745359edbdb58cea Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Wed, 19 Apr 2023 17:04:04 -0400 Subject: [PATCH 1186/1310] Fix for potential deadlock in EventThread::onNewVsyncSchedule I haven't seen such a deadlock, but looking over I86e66df59c64e81c4aa721a25250697f61237488 and the potential deadlock that inspired it, I think there could be a similar problem in EventThread. As I understand it, this is partially because of the fact that EventThread does not unregister the old callback. This is addressed in I3c1eccb36914f29560600d48bb08b1b8f2fe7c96. Fix the deadlock with a similar approach as I86e66df59c64e81c4aa721a25250697f61237488: hold on to the old registration until after releasing the mutex. Bug: 279209321 Test: infeasible Change-Id: If490f88115aa298d31aa9ad392a1f9f9dc987549 --- .../surfaceflinger/Scheduler/EventThread.cpp | 16 +++++++++++++--- services/surfaceflinger/Scheduler/EventThread.h | 7 ++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index 74665a70aa..1c0bf0d3d1 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -692,17 +692,27 @@ const char* EventThread::toCString(State state) { } void EventThread::onNewVsyncSchedule(std::shared_ptr schedule) { + // Hold onto the old registration until after releasing the mutex to avoid deadlock. + scheduler::VSyncCallbackRegistration oldRegistration = + onNewVsyncScheduleInternal(std::move(schedule)); +} + +scheduler::VSyncCallbackRegistration EventThread::onNewVsyncScheduleInternal( + std::shared_ptr schedule) { std::lock_guard lock(mMutex); const bool reschedule = mVsyncRegistration.cancel() == scheduler::CancelResult::Cancelled; mVsyncSchedule = std::move(schedule); - mVsyncRegistration = - scheduler::VSyncCallbackRegistration(mVsyncSchedule->getDispatch(), - createDispatchCallback(), mThreadName); + auto oldRegistration = + std::exchange(mVsyncRegistration, + scheduler::VSyncCallbackRegistration(mVsyncSchedule->getDispatch(), + createDispatchCallback(), + mThreadName)); if (reschedule) { mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(), .readyDuration = mReadyDuration.count(), .earliestVsync = mLastVsyncCallbackTime.ns()}); } + return oldRegistration; } scheduler::VSyncDispatch::Callback EventThread::createDispatchCallback() { diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h index 30869e9cd8..684745b71b 100644 --- a/services/surfaceflinger/Scheduler/EventThread.h +++ b/services/surfaceflinger/Scheduler/EventThread.h @@ -174,7 +174,7 @@ public: size_t getEventThreadConnectionCount() override; - void onNewVsyncSchedule(std::shared_ptr) override; + void onNewVsyncSchedule(std::shared_ptr) override EXCLUDES(mMutex); private: friend EventThreadTest; @@ -201,6 +201,11 @@ private: scheduler::VSyncDispatch::Callback createDispatchCallback(); + // Returns the old registration so it can be destructed outside the lock to + // avoid deadlock. + scheduler::VSyncCallbackRegistration onNewVsyncScheduleInternal( + std::shared_ptr) EXCLUDES(mMutex); + const char* const mThreadName; TracedOrdinal mVsyncTracer; TracedOrdinal mWorkDuration GUARDED_BY(mMutex); -- GitLab From 0773813474604ca4588fbdf476885cb358046dbf Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Mon, 24 Apr 2023 11:43:53 -0400 Subject: [PATCH 1187/1310] Before deleting callbacks, ensure they aren't running I3c1eccb36914f29560600d48bb08b1b8f2fe7c96 makes sure that a VSyncDispatchTimerQueue unregisters its callback if a new one is moved into it, but it's possible for the class to be used directly, so there is still the possibility of deleting the callbacks while running. Update the destructor to wait for any callbacks to finish before deleting them. Also log an error, since the caller should've unregistered them. Bug: 279209321 Test: infeasible Change-Id: Ia2c5436944c88284912be4963740be41de5c21d3 --- .../surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp index d431fbfff0..1f922f11fb 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp @@ -21,12 +21,16 @@ #include #include #include +#include #include #include "VSyncDispatchTimerQueue.h" #include "VSyncTracker.h" +#undef LOG_TAG +#define LOG_TAG "VSyncDispatch" + namespace android::scheduler { using base::StringAppendF; @@ -225,6 +229,10 @@ VSyncDispatchTimerQueue::VSyncDispatchTimerQueue(std::unique_ptr tk, VSyncDispatchTimerQueue::~VSyncDispatchTimerQueue() { std::lock_guard lock(mMutex); cancelTimer(); + for (auto& [_, entry] : mCallbacks) { + ALOGE("Forgot to unregister a callback on VSyncDispatch!"); + entry->ensureNotRunning(); + } } void VSyncDispatchTimerQueue::cancelTimer() { -- GitLab From a3816abcf2cc2cf284eb85c2b88ba53e60824b7f Mon Sep 17 00:00:00 2001 From: ramindani Date: Tue, 25 Apr 2023 18:41:16 -0700 Subject: [PATCH 1188/1310] [SF] Disable caching for RefreshRateOverlay SetCachingHint to disable the caching of the RefreshRateOverlay BUG: 277704843 Test: manual test Change-Id: Id144135c4536ef0e3883079ced0ddff078b6467b --- services/surfaceflinger/RefreshRateOverlay.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp index 9a4261d087..f1fd6db0a0 100644 --- a/services/surfaceflinger/RefreshRateOverlay.cpp +++ b/services/surfaceflinger/RefreshRateOverlay.cpp @@ -355,6 +355,8 @@ SurfaceComposerClient::Transaction RefreshRateOverlay::createTransaction() const if (isSetByHwc()) { transaction.setFlags(surface, layer_state_t::eLayerIsRefreshRateIndicator, layer_state_t::eLayerIsRefreshRateIndicator); + // Disable overlay layer caching when refresh rate is updated by the HWC. + transaction.setCachingHint(surface, gui::CachingHint::Disabled); } transaction.setFrameRate(surface, kFrameRate, kCompatibility, kSeamlessness); return transaction; -- GitLab From 6773db6d38ee0965d68c4d08d33e998b56f93fc0 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 21 Apr 2023 11:30:20 -0700 Subject: [PATCH 1189/1310] Validate events before printing them Before this patch, enabling the injection logs would cause a crash when running inputflinger_tests. This happened because the events were getting printed prior to getting checked for validity. To fix this, move the check to the beginning of the function. Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests Bug: 274073185 Merged-In: I8c92e47133e0eea55186be049434bdfd5c0348ff Change-Id: I8c92e47133e0eea55186be049434bdfd5c0348ff --- .../dispatcher/InputDispatcher.cpp | 157 +++++++++++------- 1 file changed, 100 insertions(+), 57 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 97b57b4dd9..0fce4eb51c 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -54,6 +54,7 @@ #define INDENT4 " " using namespace android::ftl::flag_operators; +using android::base::Error; using android::base::HwTimeoutMultiplier; using android::base::Result; using android::base::StringPrintf; @@ -129,48 +130,68 @@ inline int32_t getMotionEventActionPointerIndex(int32_t action) { AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; } -bool isValidKeyAction(int32_t action) { +Result checkKeyAction(int32_t action) { switch (action) { case AKEY_EVENT_ACTION_DOWN: case AKEY_EVENT_ACTION_UP: - return true; + return {}; default: - return false; + return Error() << "Key event has invalid action code " << action; } } -bool validateKeyEvent(int32_t action) { - if (!isValidKeyAction(action)) { - ALOGE("Key event has invalid action code 0x%x", action); - return false; - } - return true; +Result validateKeyEvent(int32_t action) { + return checkKeyAction(action); } -bool isValidMotionAction(int32_t action, int32_t actionButton, int32_t pointerCount) { +Result checkMotionAction(int32_t action, int32_t actionButton, int32_t pointerCount) { switch (MotionEvent::getActionMasked(action)) { case AMOTION_EVENT_ACTION_DOWN: - case AMOTION_EVENT_ACTION_UP: - return pointerCount == 1; + case AMOTION_EVENT_ACTION_UP: { + if (pointerCount != 1) { + return Error() << "invalid pointer count " << pointerCount; + } + return {}; + } case AMOTION_EVENT_ACTION_MOVE: case AMOTION_EVENT_ACTION_HOVER_ENTER: case AMOTION_EVENT_ACTION_HOVER_MOVE: - case AMOTION_EVENT_ACTION_HOVER_EXIT: - return pointerCount >= 1; + case AMOTION_EVENT_ACTION_HOVER_EXIT: { + if (pointerCount < 1) { + return Error() << "invalid pointer count " << pointerCount; + } + return {}; + } case AMOTION_EVENT_ACTION_CANCEL: case AMOTION_EVENT_ACTION_OUTSIDE: case AMOTION_EVENT_ACTION_SCROLL: - return true; + return {}; case AMOTION_EVENT_ACTION_POINTER_DOWN: case AMOTION_EVENT_ACTION_POINTER_UP: { const int32_t index = MotionEvent::getActionIndex(action); - return index >= 0 && index < pointerCount && pointerCount > 1; + if (index < 0) { + return Error() << "invalid index " << index << " for " + << MotionEvent::actionToString(action); + } + if (index >= pointerCount) { + return Error() << "invalid index " << index << " for pointerCount " << pointerCount; + } + if (pointerCount <= 1) { + return Error() << "invalid pointer count " << pointerCount << " for " + << MotionEvent::actionToString(action); + } + return {}; } case AMOTION_EVENT_ACTION_BUTTON_PRESS: - case AMOTION_EVENT_ACTION_BUTTON_RELEASE: - return actionButton != 0; + case AMOTION_EVENT_ACTION_BUTTON_RELEASE: { + if (actionButton == 0) { + return Error() << "action button should be nonzero for " + << MotionEvent::actionToString(action); + } + return {}; + } default: - return false; + return Error() << "invalid action " << action; } } @@ -178,32 +199,50 @@ int64_t millis(std::chrono::nanoseconds t) { return std::chrono::duration_cast(t).count(); } -bool validateMotionEvent(int32_t action, int32_t actionButton, size_t pointerCount, - const PointerProperties* pointerProperties) { - if (!isValidMotionAction(action, actionButton, pointerCount)) { - ALOGE("Motion event has invalid action code 0x%x", action); - return false; +Result validateMotionEvent(int32_t action, int32_t actionButton, size_t pointerCount, + const PointerProperties* pointerProperties) { + Result actionCheck = checkMotionAction(action, actionButton, pointerCount); + if (!actionCheck.ok()) { + return actionCheck; } if (pointerCount < 1 || pointerCount > MAX_POINTERS) { - ALOGE("Motion event has invalid pointer count %zu; value must be between 1 and %zu.", - pointerCount, MAX_POINTERS); - return false; + return Error() << "Motion event has invalid pointer count " << pointerCount + << "; value must be between 1 and " << MAX_POINTERS << "."; } std::bitset pointerIdBits; for (size_t i = 0; i < pointerCount; i++) { int32_t id = pointerProperties[i].id; if (id < 0 || id > MAX_POINTER_ID) { - ALOGE("Motion event has invalid pointer id %d; value must be between 0 and %d", id, - MAX_POINTER_ID); - return false; + return Error() << "Motion event has invalid pointer id " << id + << "; value must be between 0 and " << MAX_POINTER_ID; } if (pointerIdBits.test(id)) { - ALOGE("Motion event has duplicate pointer id %d", id); - return false; + return Error() << "Motion event has duplicate pointer id " << id; } pointerIdBits.set(id); } - return true; + return {}; +} + +Result validateInputEvent(const InputEvent& event) { + switch (event.getType()) { + case InputEventType::KEY: { + const KeyEvent& key = static_cast(event); + const int32_t action = key.getAction(); + return validateKeyEvent(action); + } + case InputEventType::MOTION: { + const MotionEvent& motion = static_cast(event); + const int32_t action = motion.getAction(); + const size_t pointerCount = motion.getPointerCount(); + const PointerProperties* pointerProperties = motion.getPointerProperties(); + const int32_t actionButton = motion.getActionButton(); + return validateMotionEvent(action, actionButton, pointerCount, pointerProperties); + } + default: { + return {}; + } + } } std::string dumpRegion(const Region& region) { @@ -4093,7 +4132,9 @@ void InputDispatcher::notifyKey(const NotifyKeyArgs& args) { args.id, args.eventTime, args.deviceId, inputEventSourceToString(args.source).c_str(), args.displayId, args.policyFlags, KeyEvent::actionToString(args.action), args.flags, KeyEvent::getLabel(args.keyCode), args.scanCode, args.metaState, args.downTime); - if (!validateKeyEvent(args.action)) { + Result keyCheck = validateKeyEvent(args.action); + if (!keyCheck.ok()) { + LOG(ERROR) << "invalid key event: " << keyCheck.error(); return; } @@ -4190,9 +4231,10 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs& args) { } } - if (!validateMotionEvent(args.action, args.actionButton, args.pointerCount, - args.pointerProperties)) { - LOG(ERROR) << "Invalid event: " << args.dump(); + Result motionCheck = validateMotionEvent(args.action, args.actionButton, + args.pointerCount, args.pointerProperties); + if (!motionCheck.ok()) { + LOG(ERROR) << "Invalid event: " << args.dump() << "; reason: " << motionCheck.error(); return; } @@ -4368,6 +4410,12 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev InputEventInjectionSync syncMode, std::chrono::milliseconds timeout, uint32_t policyFlags) { + Result eventValidation = validateInputEvent(*event); + if (!eventValidation.ok()) { + LOG(INFO) << "Injection failed: invalid event: " << eventValidation.error(); + return InputEventInjectionResult::FAILED; + } + if (debugInboundEventDetails()) { LOG(DEBUG) << __func__ << ": targetUid=" << toString(targetUid) << ", syncMode=" << ftl::enum_string(syncMode) << ", timeout=" << timeout.count() @@ -4393,11 +4441,7 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev switch (event->getType()) { case InputEventType::KEY: { const KeyEvent& incomingKey = static_cast(*event); - int32_t action = incomingKey.getAction(); - if (!validateKeyEvent(action)) { - return InputEventInjectionResult::FAILED; - } - + const int32_t action = incomingKey.getAction(); int32_t flags = incomingKey.getFlags(); if (policyFlags & POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY) { flags |= AKEY_EVENT_FLAG_IS_ACCESSIBILITY_EVENT; @@ -4439,20 +4483,13 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev case InputEventType::MOTION: { const MotionEvent& motionEvent = static_cast(*event); - const int32_t action = motionEvent.getAction(); const bool isPointerEvent = isFromSource(event->getSource(), AINPUT_SOURCE_CLASS_POINTER); // If a pointer event has no displayId specified, inject it to the default display. const uint32_t displayId = isPointerEvent && (event->getDisplayId() == ADISPLAY_ID_NONE) ? ADISPLAY_ID_DEFAULT : event->getDisplayId(); - const size_t pointerCount = motionEvent.getPointerCount(); - const PointerProperties* pointerProperties = motionEvent.getPointerProperties(); - const int32_t actionButton = motionEvent.getActionButton(); int32_t flags = motionEvent.getFlags(); - if (!validateMotionEvent(action, actionButton, pointerCount, pointerProperties)) { - return InputEventInjectionResult::FAILED; - } if (!(policyFlags & POLICY_FLAG_FILTERED)) { nsecs_t eventTime = motionEvent.getEventTime(); @@ -4474,8 +4511,9 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev std::unique_ptr injectedEntry = std::make_unique(motionEvent.getId(), *sampleEventTimes, resolvedDeviceId, motionEvent.getSource(), - displayId, policyFlags, action, actionButton, - flags, motionEvent.getMetaState(), + displayId, policyFlags, motionEvent.getAction(), + motionEvent.getActionButton(), flags, + motionEvent.getMetaState(), motionEvent.getButtonState(), motionEvent.getClassification(), motionEvent.getEdgeFlags(), @@ -4483,18 +4521,22 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev motionEvent.getYPrecision(), motionEvent.getRawXCursorPosition(), motionEvent.getRawYCursorPosition(), - motionEvent.getDownTime(), uint32_t(pointerCount), - pointerProperties, samplePointerCoords); + motionEvent.getDownTime(), + motionEvent.getPointerCount(), + motionEvent.getPointerProperties(), + samplePointerCoords); transformMotionEntryForInjectionLocked(*injectedEntry, motionEvent.getTransform()); injectedEntries.push(std::move(injectedEntry)); for (size_t i = motionEvent.getHistorySize(); i > 0; i--) { sampleEventTimes += 1; - samplePointerCoords += pointerCount; + samplePointerCoords += motionEvent.getPointerCount(); std::unique_ptr nextInjectedEntry = std::make_unique(motionEvent.getId(), *sampleEventTimes, resolvedDeviceId, motionEvent.getSource(), - displayId, policyFlags, action, actionButton, - flags, motionEvent.getMetaState(), + displayId, policyFlags, + motionEvent.getAction(), + motionEvent.getActionButton(), flags, + motionEvent.getMetaState(), motionEvent.getButtonState(), motionEvent.getClassification(), motionEvent.getEdgeFlags(), @@ -4503,7 +4545,8 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev motionEvent.getRawXCursorPosition(), motionEvent.getRawYCursorPosition(), motionEvent.getDownTime(), - uint32_t(pointerCount), pointerProperties, + motionEvent.getPointerCount(), + motionEvent.getPointerProperties(), samplePointerCoords); transformMotionEntryForInjectionLocked(*nextInjectedEntry, motionEvent.getTransform()); -- GitLab From a41d244c8b7985f587aada2d89e2ae3ad1f00dff Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 20 Apr 2023 21:30:40 +0000 Subject: [PATCH 1190/1310] Clean up InputDispatcherPolicyInterface - Remove RefBase from policy - Store reference instead of pointer to policy - Use references instead of pointers in parameters - Use return values for all outputs for functions instead of passing an out parameter Bug: 279927189 Bug: 245989146 Test: Presubmit Change-Id: I31a2a3e8c67960020169ddfc59bc0a59f3e65c52 --- services/inputflinger/InputManager.cpp | 5 +- services/inputflinger/InputManager.h | 5 +- .../benchmarks/InputDispatcher_benchmarks.cpp | 29 ++++---- .../dispatcher/InputDispatcher.cpp | 70 ++++++++++--------- .../inputflinger/dispatcher/InputDispatcher.h | 6 +- .../dispatcher/InputDispatcherFactory.cpp | 2 +- .../include/InputDispatcherFactory.h | 2 +- .../include/InputDispatcherPolicyInterface.h | 26 +++---- .../tests/InputDispatcher_test.cpp | 45 ++++++------ 9 files changed, 94 insertions(+), 96 deletions(-) diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp index 472d7a15b4..ddebcad0d3 100644 --- a/services/inputflinger/InputManager.cpp +++ b/services/inputflinger/InputManager.cpp @@ -57,9 +57,8 @@ static int32_t exceptionCodeFromStatusT(status_t status) { * The event flow is via the "InputListener" interface, as follows: * InputReader -> UnwantedInteractionBlocker -> InputProcessor -> InputDispatcher */ -InputManager::InputManager( - const sp& readerPolicy, - const sp& dispatcherPolicy) { +InputManager::InputManager(const sp& readerPolicy, + InputDispatcherPolicyInterface& dispatcherPolicy) { mDispatcher = createInputDispatcher(dispatcherPolicy); mProcessor = std::make_unique(*mDispatcher); mBlocker = std::make_unique(*mProcessor); diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h index 793757d369..b6ad419f31 100644 --- a/services/inputflinger/InputManager.h +++ b/services/inputflinger/InputManager.h @@ -100,9 +100,8 @@ protected: ~InputManager() override; public: - InputManager( - const sp& readerPolicy, - const sp& dispatcherPolicy); + InputManager(const sp& readerPolicy, + InputDispatcherPolicyInterface& dispatcherPolicy); status_t start() override; status_t stop() override; diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp index f852001679..cf299c0f6e 100644 --- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp +++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp @@ -48,10 +48,8 @@ static nsecs_t now() { class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface { public: - FakeInputDispatcherPolicy() {} - -protected: - virtual ~FakeInputDispatcherPolicy() {} + FakeInputDispatcherPolicy() = default; + virtual ~FakeInputDispatcherPolicy() = default; private: void notifyConfigurationChanged(nsecs_t) override {} @@ -82,24 +80,23 @@ private: void notifyVibratorState(int32_t deviceId, bool isOn) override {} - void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override { - *outConfig = mConfig; - } + InputDispatcherConfiguration getDispatcherConfiguration() override { return mConfig; } - bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) override { - return true; + bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override { + return true; // dispatch event normally } - void interceptKeyBeforeQueueing(const KeyEvent*, uint32_t&) override {} + void interceptKeyBeforeQueueing(const KeyEvent&, uint32_t&) override {} void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {} - nsecs_t interceptKeyBeforeDispatching(const sp&, const KeyEvent*, uint32_t) override { + nsecs_t interceptKeyBeforeDispatching(const sp&, const KeyEvent&, uint32_t) override { return 0; } - bool dispatchUnhandledKey(const sp&, const KeyEvent*, uint32_t, KeyEvent*) override { - return false; + std::optional dispatchUnhandledKey(const sp&, const KeyEvent&, + uint32_t) override { + return {}; } void notifySwitch(nsecs_t, uint32_t, uint32_t, uint32_t) override {} @@ -258,7 +255,7 @@ static NotifyMotionArgs generateMotionArgs() { static void benchmarkNotifyMotion(benchmark::State& state) { // Create dispatcher - sp fakePolicy = sp::make(); + FakeInputDispatcherPolicy fakePolicy; InputDispatcher dispatcher(fakePolicy); dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); dispatcher.start(); @@ -293,7 +290,7 @@ static void benchmarkNotifyMotion(benchmark::State& state) { static void benchmarkInjectMotion(benchmark::State& state) { // Create dispatcher - sp fakePolicy = sp::make(); + FakeInputDispatcherPolicy fakePolicy; InputDispatcher dispatcher(fakePolicy); dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); dispatcher.start(); @@ -327,7 +324,7 @@ static void benchmarkInjectMotion(benchmark::State& state) { static void benchmarkOnWindowInfosChanged(benchmark::State& state) { // Create dispatcher - sp fakePolicy = sp::make(); + FakeInputDispatcherPolicy fakePolicy; InputDispatcher dispatcher(fakePolicy); dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); dispatcher.start(); diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index cc9fc33ae6..470f857906 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -646,10 +646,10 @@ std::vector& operator+=(std::vector& left, const std::vector& right) { // --- InputDispatcher --- -InputDispatcher::InputDispatcher(const sp& policy) +InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy) : InputDispatcher(policy, STALE_EVENT_TIMEOUT) {} -InputDispatcher::InputDispatcher(const sp& policy, +InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy, std::chrono::nanoseconds staleEventTimeout) : mPolicy(policy), mPendingEvent(nullptr), @@ -1401,7 +1401,7 @@ bool InputDispatcher::dispatchConfigurationChangedLocked(nsecs_t currentTime, // Enqueue a command to run outside the lock to tell the policy that the configuration changed. auto command = [this, eventTime = entry.eventTime]() REQUIRES(mLock) { scoped_unlock unlock(mLock); - mPolicy->notifyConfigurationChanged(eventTime); + mPolicy.notifyConfigurationChanged(eventTime); }; postCommandLocked(std::move(command)); return true; @@ -1716,10 +1716,10 @@ void InputDispatcher::dispatchSensorLocked(nsecs_t currentTime, scoped_unlock unlock(mLock); if (entry->accuracyChanged) { - mPolicy->notifySensorAccuracy(entry->deviceId, entry->sensorType, entry->accuracy); + mPolicy.notifySensorAccuracy(entry->deviceId, entry->sensorType, entry->accuracy); } - mPolicy->notifySensorEvent(entry->deviceId, entry->sensorType, entry->accuracy, - entry->hwTimestamp, entry->values); + mPolicy.notifySensorEvent(entry->deviceId, entry->sensorType, entry->accuracy, + entry->hwTimestamp, entry->values); }; postCommandLocked(std::move(command)); } @@ -3021,7 +3021,7 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) { auto command = [this, eventTime = eventEntry.eventTime, eventType, displayId]() REQUIRES(mLock) { scoped_unlock unlock(mLock); - mPolicy->pokeUserActivity(eventTime, eventType, displayId); + mPolicy.pokeUserActivity(eventTime, eventType, displayId); }; postCommandLocked(std::move(command)); } @@ -3362,7 +3362,7 @@ void InputDispatcher::dispatchPointerDownOutsideFocus(uint32_t source, int32_t a auto command = [this, token]() REQUIRES(mLock) { scoped_unlock unlock(mLock); - mPolicy->onPointerDownOutsideFocus(token); + mPolicy.onPointerDownOutsideFocus(token); }; postCommandLocked(std::move(command)); } @@ -3641,7 +3641,7 @@ void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime, auto command = [this, connection]() REQUIRES(mLock) { scoped_unlock unlock(mLock); - mPolicy->notifyInputChannelBroken(connection->inputChannel->getConnectionToken()); + mPolicy.notifyInputChannelBroken(connection->inputChannel->getConnectionToken()); }; postCommandLocked(std::move(command)); } @@ -4122,7 +4122,7 @@ void InputDispatcher::notifyKey(const NotifyKeyArgs& args) { args.eventTime); android::base::Timer t; - mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags); + mPolicy.interceptKeyBeforeQueueing(event, /*byref*/ policyFlags); if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) { ALOGW("Excessive delay in interceptKeyBeforeQueueing; took %s ms", std::to_string(t.duration().count()).c_str()); @@ -4136,7 +4136,7 @@ void InputDispatcher::notifyKey(const NotifyKeyArgs& args) { mLock.unlock(); policyFlags |= POLICY_FLAG_FILTERED; - if (!mPolicy->filterInputEvent(&event, policyFlags)) { + if (!mPolicy.filterInputEvent(event, policyFlags)) { return; // event was consumed by the filter } @@ -4200,7 +4200,7 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs& args) { policyFlags |= POLICY_FLAG_TRUSTED; android::base::Timer t; - mPolicy->interceptMotionBeforeQueueing(args.displayId, args.eventTime, policyFlags); + mPolicy.interceptMotionBeforeQueueing(args.displayId, args.eventTime, policyFlags); if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) { ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms", std::to_string(t.duration().count()).c_str()); @@ -4239,7 +4239,7 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs& args) { args.pointerProperties, args.pointerCoords); policyFlags |= POLICY_FLAG_FILTERED; - if (!mPolicy->filterInputEvent(&event, policyFlags)) { + if (!mPolicy.filterInputEvent(event, policyFlags)) { return; // event was consumed by the filter } @@ -4305,7 +4305,7 @@ void InputDispatcher::notifyVibratorState(const NotifyVibratorStateArgs& args) { ALOGD("notifyVibratorState - eventTime=%" PRId64 ", device=%d, isOn=%d", args.eventTime, args.deviceId, args.isOn); } - mPolicy->notifyVibratorState(args.deviceId, args.isOn); + mPolicy.notifyVibratorState(args.deviceId, args.isOn); } bool InputDispatcher::shouldSendMotionToInputFilterLocked(const NotifyMotionArgs& args) { @@ -4321,7 +4321,7 @@ void InputDispatcher::notifySwitch(const NotifySwitchArgs& args) { uint32_t policyFlags = args.policyFlags; policyFlags |= POLICY_FLAG_TRUSTED; - mPolicy->notifySwitch(args.eventTime, args.switchValues, args.switchMask, policyFlags); + mPolicy.notifySwitch(args.eventTime, args.switchValues, args.switchMask, policyFlags); } void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs& args) { @@ -4418,7 +4418,7 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev if (!(policyFlags & POLICY_FLAG_FILTERED)) { android::base::Timer t; - mPolicy->interceptKeyBeforeQueueing(&keyEvent, /*byref*/ policyFlags); + mPolicy.interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags); if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) { ALOGW("Excessive delay in interceptKeyBeforeQueueing; took %s ms", std::to_string(t.duration().count()).c_str()); @@ -4457,7 +4457,7 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev if (!(policyFlags & POLICY_FLAG_FILTERED)) { nsecs_t eventTime = motionEvent.getEventTime(); android::base::Timer t; - mPolicy->interceptMotionBeforeQueueing(displayId, eventTime, /*byref*/ policyFlags); + mPolicy.interceptMotionBeforeQueueing(displayId, eventTime, /*byref*/ policyFlags); if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) { ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms", std::to_string(t.duration().count()).c_str()); @@ -6008,7 +6008,7 @@ void InputDispatcher::sendFocusChangedCommandLocked(const sp& oldToken, const sp& newToken) { auto command = [this, oldToken, newToken]() REQUIRES(mLock) { scoped_unlock unlock(mLock); - mPolicy->notifyFocusChanged(oldToken, newToken); + mPolicy.notifyFocusChanged(oldToken, newToken); }; postCommandLocked(std::move(command)); } @@ -6016,7 +6016,7 @@ void InputDispatcher::sendFocusChangedCommandLocked(const sp& oldToken, void InputDispatcher::sendDropWindowCommandLocked(const sp& token, float x, float y) { auto command = [this, token, x, y]() REQUIRES(mLock) { scoped_unlock unlock(mLock); - mPolicy->notifyDropWindow(token, x, y); + mPolicy.notifyDropWindow(token, x, y); }; postCommandLocked(std::move(command)); } @@ -6063,7 +6063,7 @@ void InputDispatcher::onAnrLocked(std::shared_ptr applic auto command = [this, application = std::move(application)]() REQUIRES(mLock) { scoped_unlock unlock(mLock); - mPolicy->notifyNoFocusedWindowAnr(application); + mPolicy.notifyNoFocusedWindowAnr(application); }; postCommandLocked(std::move(command)); } @@ -6103,8 +6103,7 @@ void InputDispatcher::doInterceptKeyBeforeDispatchingCommand(const sp& { // release lock scoped_unlock unlock(mLock); android::base::Timer t; - delay = mPolicy->interceptKeyBeforeDispatching(focusedWindowToken, &event, - entry.policyFlags); + delay = mPolicy.interceptKeyBeforeDispatching(focusedWindowToken, event, entry.policyFlags); if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) { ALOGW("Excessive delay in interceptKeyBeforeDispatching; took %s ms", std::to_string(t.duration().count()).c_str()); @@ -6126,7 +6125,7 @@ void InputDispatcher::sendWindowUnresponsiveCommandLocked(const sp& tok std::string reason) { auto command = [this, token, pid, reason = std::move(reason)]() REQUIRES(mLock) { scoped_unlock unlock(mLock); - mPolicy->notifyWindowUnresponsive(token, pid, reason); + mPolicy.notifyWindowUnresponsive(token, pid, reason); }; postCommandLocked(std::move(command)); } @@ -6135,7 +6134,7 @@ void InputDispatcher::sendWindowResponsiveCommandLocked(const sp& token std::optional pid) { auto command = [this, token, pid]() REQUIRES(mLock) { scoped_unlock unlock(mLock); - mPolicy->notifyWindowResponsive(token, pid); + mPolicy.notifyWindowResponsive(token, pid); }; postCommandLocked(std::move(command)); } @@ -6219,8 +6218,12 @@ bool InputDispatcher::afterKeyEventLockedInterruptable( mLock.unlock(); - mPolicy->dispatchUnhandledKey(connection->inputChannel->getConnectionToken(), &event, - keyEntry.policyFlags, &event); + if (const auto unhandledKeyFallback = + mPolicy.dispatchUnhandledKey(connection->inputChannel->getConnectionToken(), + event, keyEntry.policyFlags); + unhandledKeyFallback) { + event = *unhandledKeyFallback; + } mLock.lock(); @@ -6260,9 +6263,13 @@ bool InputDispatcher::afterKeyEventLockedInterruptable( mLock.unlock(); - bool fallback = - mPolicy->dispatchUnhandledKey(connection->inputChannel->getConnectionToken(), - &event, keyEntry.policyFlags, &event); + bool fallback = false; + if (auto fb = mPolicy.dispatchUnhandledKey(connection->inputChannel->getConnectionToken(), + event, keyEntry.policyFlags); + fb) { + fallback = true; + event = *fb; + } mLock.lock(); @@ -6514,7 +6521,7 @@ void InputDispatcher::setPointerCaptureLocked(bool enable) { mCurrentPointerCaptureRequest.seq++; auto command = [this, request = mCurrentPointerCaptureRequest]() REQUIRES(mLock) { scoped_unlock unlock(mLock); - mPolicy->setPointerCapture(request); + mPolicy.setPointerCapture(request); }; postCommandLocked(std::move(command)); } @@ -6607,8 +6614,7 @@ void InputDispatcher::cancelCurrentTouch() { } void InputDispatcher::requestRefreshConfiguration() { - InputDispatcherConfiguration config; - mPolicy->getDispatcherConfiguration(&config); + InputDispatcherConfiguration config = mPolicy.getDispatcherConfiguration(); std::scoped_lock _l(mLock); mConfig = config; diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index dd7f7fe2b0..2a06908584 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -82,8 +82,8 @@ class InputDispatcher : public android::InputDispatcherInterface { public: static constexpr bool kDefaultInTouchMode = true; - explicit InputDispatcher(const sp& policy); - explicit InputDispatcher(const sp& policy, + explicit InputDispatcher(InputDispatcherPolicyInterface& policy); + explicit InputDispatcher(InputDispatcherPolicyInterface& policy, std::chrono::nanoseconds staleEventTimeout); ~InputDispatcher() override; @@ -167,7 +167,7 @@ private: std::unique_ptr mThread; - sp mPolicy; + InputDispatcherPolicyInterface& mPolicy; android::InputDispatcherConfiguration mConfig GUARDED_BY(mLock); std::mutex mLock; diff --git a/services/inputflinger/dispatcher/InputDispatcherFactory.cpp b/services/inputflinger/dispatcher/InputDispatcherFactory.cpp index bca1600db9..3ef8419221 100644 --- a/services/inputflinger/dispatcher/InputDispatcherFactory.cpp +++ b/services/inputflinger/dispatcher/InputDispatcherFactory.cpp @@ -20,7 +20,7 @@ namespace android { std::unique_ptr createInputDispatcher( - const sp& policy) { + InputDispatcherPolicyInterface& policy) { return std::make_unique(policy); } diff --git a/services/inputflinger/dispatcher/include/InputDispatcherFactory.h b/services/inputflinger/dispatcher/include/InputDispatcherFactory.h index 5247d8eabb..6b298c2dac 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherFactory.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherFactory.h @@ -25,6 +25,6 @@ namespace android { // This factory method is used to encapsulate implementation details in internal header files. std::unique_ptr createInputDispatcher( - const sp& policy); + InputDispatcherPolicyInterface& policy); } // namespace android diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h index 7843923a1f..5539915694 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h @@ -35,12 +35,11 @@ namespace android { * The actual implementation is partially supported by callbacks into the DVM * via JNI. This interface is also mocked in the unit tests. */ -class InputDispatcherPolicyInterface : public virtual RefBase { -protected: - InputDispatcherPolicyInterface() {} - virtual ~InputDispatcherPolicyInterface() {} - +class InputDispatcherPolicyInterface { public: + InputDispatcherPolicyInterface() = default; + virtual ~InputDispatcherPolicyInterface() = default; + /* Notifies the system that a configuration change has occurred. */ virtual void notifyConfigurationChanged(nsecs_t when) = 0; @@ -75,14 +74,14 @@ public: virtual void notifyVibratorState(int32_t deviceId, bool isOn) = 0; /* Gets the input dispatcher configuration. */ - virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) = 0; + virtual InputDispatcherConfiguration getDispatcherConfiguration() = 0; /* Filters an input event. * Return true to dispatch the event unmodified, false to consume the event. * A filter can also transform and inject events later by passing POLICY_FLAG_FILTERED * to injectInputEvent. */ - virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) = 0; + virtual bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) = 0; /* Intercepts a key event immediately before queueing it. * The policy can use this method as an opportunity to perform power management functions @@ -91,7 +90,7 @@ public: * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event * should be dispatched to applications. */ - virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) = 0; + virtual void interceptKeyBeforeQueueing(const KeyEvent& keyEvent, uint32_t& policyFlags) = 0; /* Intercepts a touch, trackball or other motion event before queueing it. * The policy can use this method as an opportunity to perform power management functions @@ -100,18 +99,19 @@ public: * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event * should be dispatched to applications. */ - virtual void interceptMotionBeforeQueueing(const int32_t displayId, nsecs_t when, + virtual void interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when, uint32_t& policyFlags) = 0; /* Allows the policy a chance to intercept a key before dispatching. */ virtual nsecs_t interceptKeyBeforeDispatching(const sp& token, - const KeyEvent* keyEvent, + const KeyEvent& keyEvent, uint32_t policyFlags) = 0; /* Allows the policy a chance to perform default processing for an unhandled key. - * Returns an alternate keycode to redispatch as a fallback, or 0 to give up. */ - virtual bool dispatchUnhandledKey(const sp& token, const KeyEvent* keyEvent, - uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) = 0; + * Returns an alternate key event to redispatch as a fallback, if needed. */ + virtual std::optional dispatchUnhandledKey(const sp& token, + const KeyEvent& keyEvent, + uint32_t policyFlags) = 0; /* Notifies the policy about switch events. */ diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 4bd5f73730..74e4bc5330 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -205,11 +205,9 @@ class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface { using AnrResult = std::pair, int32_t /*pid*/>; -protected: - virtual ~FakeInputDispatcherPolicy() {} - public: - FakeInputDispatcherPolicy() {} + FakeInputDispatcherPolicy() = default; + virtual ~FakeInputDispatcherPolicy() = default; void assertFilterInputEventWasCalled(const NotifyKeyArgs& args) { assertFilterInputEventWasCalledInternal([&args](const InputEvent& event) { @@ -523,22 +521,20 @@ private: void notifyVibratorState(int32_t deviceId, bool isOn) override {} - void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override { - *outConfig = mConfig; - } + InputDispatcherConfiguration getDispatcherConfiguration() override { return mConfig; } - bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) override { + bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override { std::scoped_lock lock(mLock); - switch (inputEvent->getType()) { + switch (inputEvent.getType()) { case InputEventType::KEY: { - const KeyEvent* keyEvent = static_cast(inputEvent); - mFilteredEvent = std::make_unique(*keyEvent); + const KeyEvent& keyEvent = static_cast(inputEvent); + mFilteredEvent = std::make_unique(keyEvent); break; } case InputEventType::MOTION: { - const MotionEvent* motionEvent = static_cast(inputEvent); - mFilteredEvent = std::make_unique(*motionEvent); + const MotionEvent& motionEvent = static_cast(inputEvent); + mFilteredEvent = std::make_unique(motionEvent); break; } default: { @@ -549,8 +545,8 @@ private: return true; } - void interceptKeyBeforeQueueing(const KeyEvent* inputEvent, uint32_t&) override { - if (inputEvent->getAction() == AKEY_EVENT_ACTION_UP) { + void interceptKeyBeforeQueueing(const KeyEvent& inputEvent, uint32_t&) override { + if (inputEvent.getAction() == AKEY_EVENT_ACTION_UP) { // Clear intercept state when we handled the event. mInterceptKeyTimeout = 0ms; } @@ -558,15 +554,16 @@ private: void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {} - nsecs_t interceptKeyBeforeDispatching(const sp&, const KeyEvent*, uint32_t) override { + nsecs_t interceptKeyBeforeDispatching(const sp&, const KeyEvent&, uint32_t) override { nsecs_t delay = std::chrono::nanoseconds(mInterceptKeyTimeout).count(); // Clear intercept state so we could dispatch the event in next wake. mInterceptKeyTimeout = 0ms; return delay; } - bool dispatchUnhandledKey(const sp&, const KeyEvent*, uint32_t, KeyEvent*) override { - return false; + std::optional dispatchUnhandledKey(const sp&, const KeyEvent&, + uint32_t) override { + return {}; } void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask, @@ -610,12 +607,12 @@ private: class InputDispatcherTest : public testing::Test { protected: - sp mFakePolicy; + std::unique_ptr mFakePolicy; std::unique_ptr mDispatcher; void SetUp() override { - mFakePolicy = sp::make(); - mDispatcher = std::make_unique(mFakePolicy, STALE_EVENT_TIMEOUT); + mFakePolicy = std::make_unique(); + mDispatcher = std::make_unique(*mFakePolicy, STALE_EVENT_TIMEOUT); mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); // Start InputDispatcher thread ASSERT_EQ(OK, mDispatcher->start()); @@ -623,7 +620,7 @@ protected: void TearDown() override { ASSERT_EQ(OK, mDispatcher->stop()); - mFakePolicy.clear(); + mFakePolicy.reset(); mDispatcher.reset(); } @@ -5281,9 +5278,9 @@ protected: sp mWindow; virtual void SetUp() override { - mFakePolicy = sp::make(); + mFakePolicy = std::make_unique(); mFakePolicy->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY); - mDispatcher = std::make_unique(mFakePolicy); + mDispatcher = std::make_unique(*mFakePolicy); mDispatcher->requestRefreshConfiguration(); mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false); ASSERT_EQ(OK, mDispatcher->start()); -- GitLab From 9679a98e2845d9317672cec811a0c698e74fdf73 Mon Sep 17 00:00:00 2001 From: Peiyong Lin Date: Mon, 10 Apr 2023 22:00:30 +0000 Subject: [PATCH 1191/1310] Refactor ANGLE usage code. Previously when ANGLE is the default system driver, the ro.hardware.egl points to ANGLE. This is fine if ANGLE is the only system drvier. However, we would like to make ANGLE coexist with the native GLES drivers and allow a global switch. Hence this patch refactors the majority of the ANGLE selection logic. Loading ANGLE in the form of an apk as well as using ANGLE as a game mode intervention should remain functional. Bug: b/270994705 Test: atest CtsAngleIntegrationHostTestCases Change-Id: I31a6a5dda04a1ffaeed101e58368ad4b4ad0d54e --- libs/graphicsenv/GraphicsEnv.cpp | 64 ++----------------- .../include/graphicsenv/GraphicsEnv.h | 17 +---- opengl/libs/EGL/Loader.cpp | 24 ++++--- 3 files changed, 16 insertions(+), 89 deletions(-) diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp index c480056b40..5fbae3c712 100644 --- a/libs/graphicsenv/GraphicsEnv.cpp +++ b/libs/graphicsenv/GraphicsEnv.cpp @@ -433,61 +433,24 @@ bool GraphicsEnv::shouldUseAngle() { return (mUseAngle == YES) ? true : false; } -bool GraphicsEnv::angleIsSystemDriver() { - // Make sure we are init'ed - if (mAngleAppName.empty()) { - ALOGV("App name is empty. setAngleInfo() has not been called to enable ANGLE."); - return false; - } - - return (mAngleIsSystemDriver == YES) ? true : false; -} - -bool GraphicsEnv::shouldForceLegacyDriver() { - // Make sure we are init'ed - if (mAngleAppName.empty()) { - ALOGV("App name is empty. setAngleInfo() has not been called to enable ANGLE."); - return false; - } - - return (mAngleIsSystemDriver == YES && mUseAngle == NO) ? true : false; -} - -std::string GraphicsEnv::getLegacySuffix() { - return mLegacyDriverSuffix; -} - void GraphicsEnv::updateUseAngle() { - mUseAngle = NO; - const char* ANGLE_PREFER_ANGLE = "angle"; - const char* ANGLE_PREFER_LEGACY = "legacy"; - // The following is a deprecated version of "legacy" const char* ANGLE_PREFER_NATIVE = "native"; mUseAngle = NO; if (mAngleDeveloperOptIn == ANGLE_PREFER_ANGLE) { - ALOGI("Using ANGLE, the %s GLES driver for package '%s'", - mAngleIsSystemDriver == YES ? "system" : "optional", mAngleAppName.c_str()); + ALOGV("User set \"Developer Options\" to force the use of ANGLE"); mUseAngle = YES; - } else if (mAngleDeveloperOptIn == ANGLE_PREFER_LEGACY || - mAngleDeveloperOptIn == ANGLE_PREFER_NATIVE) { - ALOGI("Using the (%s) Legacy GLES driver for package '%s'", - mAngleIsSystemDriver == YES ? "optional" : "system", mAngleAppName.c_str()); + } else if (mAngleDeveloperOptIn == ANGLE_PREFER_NATIVE) { + ALOGV("User set \"Developer Options\" to force the use of Native"); } else { ALOGV("User set invalid \"Developer Options\": '%s'", mAngleDeveloperOptIn.c_str()); } } void GraphicsEnv::setAngleInfo(const std::string path, const std::string appName, - const bool angleIsSystemDriver, const std::string developerOptIn, + const std::string developerOptIn, const std::vector eglFeatures) { - // Set whether ANGLE is the system driver: - mAngleIsSystemDriver = angleIsSystemDriver ? YES : NO; - - // Note: Given the current logic and lack of the old rules file processing, - // there seems to be little chance that mUseAngle != UNKNOWN. Leave this - // for now, even though it seems outdated. if (mUseAngle != UNKNOWN) { // We've already figured out an answer for this app, so just return. ALOGV("Already evaluated the rules file for '%s': use ANGLE = %s", appName.c_str(), @@ -508,25 +471,6 @@ void GraphicsEnv::setAngleInfo(const std::string path, const std::string appName updateUseAngle(); } -void GraphicsEnv::setLegacyDriverInfo(const std::string appName, const bool angleIsSystemDriver, - const std::string legacyDriverName) { - ALOGV("setting legacy app name to '%s'", appName.c_str()); - mAngleAppName = appName; - - // Force the use of the legacy driver instead of ANGLE - const char* ANGLE_PREFER_LEGACY = "legacy"; - mAngleDeveloperOptIn = ANGLE_PREFER_LEGACY; - ALOGV("setting ANGLE application opt-in to 'legacy'"); - - // Set whether ANGLE is the system driver: - mAngleIsSystemDriver = angleIsSystemDriver ? YES : NO; - - mLegacyDriverSuffix = legacyDriverName; - - // Update the current status of whether we should use ANGLE or not - updateUseAngle(); -} - void GraphicsEnv::setLayerPaths(NativeLoaderNamespace* appNamespace, const std::string layerPaths) { if (mLayerPaths.empty()) { mLayerPaths = layerPaths; diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h index 1274c46b7b..f9b234a047 100644 --- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h +++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h @@ -100,28 +100,17 @@ public: bool shouldUseAngle(std::string appName); // Check if this app process should use ANGLE. bool shouldUseAngle(); - // If ANGLE is the system GLES driver - bool angleIsSystemDriver(); - // If should use legacy driver instead of a system ANGLE driver - bool shouldForceLegacyDriver(); // Set a search path for loading ANGLE libraries. The path is a list of // directories separated by ':'. A directory can be contained in a zip file // (libraries must be stored uncompressed and page aligned); such elements // in the search path must have a '!' after the zip filename, e.g. // /system/app/ANGLEPrebuilt/ANGLEPrebuilt.apk!/lib/arm64-v8a - void setAngleInfo(const std::string path, const std::string appName, - const bool angleIsSystemDriver, std::string devOptIn, + void setAngleInfo(const std::string path, const std::string appName, std::string devOptIn, const std::vector eglFeatures); - // Set the state so that the legacy driver will be used, and in case ANGLE - // is the system driver, provide the name of the legacy driver. - void setLegacyDriverInfo(const std::string appName, const bool angleIsSystemDriver, - const std::string legacyDriverName); // Get the ANGLE driver namespace. android_namespace_t* getAngleNamespace(); // Get the app name for ANGLE debug message. std::string& getAngleAppName(); - // Get the legacy driver's suffix name. - std::string getLegacySuffix(); const std::vector& getAngleEglFeatures(); @@ -178,10 +167,6 @@ private: std::string mAngleDeveloperOptIn; // ANGLE EGL features; std::vector mAngleEglFeatures; - // ANGLE is System Driver flag. - UseAngle mAngleIsSystemDriver = UNKNOWN; - // Legacy driver name to use when ANGLE is the system driver. - std::string mLegacyDriverSuffix; // Use ANGLE flag. UseAngle mUseAngle = UNKNOWN; // Vulkan debug layers libs. diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp index 415e8eab70..2c3ce16f66 100644 --- a/opengl/libs/EGL/Loader.cpp +++ b/opengl/libs/EGL/Loader.cpp @@ -139,9 +139,10 @@ static void* load_wrapper(const char* path) { static const char* DRIVER_SUFFIX_PROPERTY = "ro.hardware.egl"; -static const char* HAL_SUBNAME_KEY_PROPERTIES[2] = { - DRIVER_SUFFIX_PROPERTY, - "ro.board.platform", +static const char* HAL_SUBNAME_KEY_PROPERTIES[3] = { + "persist.graphics.egl", + DRIVER_SUFFIX_PROPERTY, + "ro.board.platform", }; static bool should_unload_system_driver(egl_connection_t* cnx) { @@ -208,8 +209,7 @@ void* Loader::open(egl_connection_t* cnx) ATRACE_CALL(); const nsecs_t openTime = systemTime(); - if (!android::GraphicsEnv::getInstance().angleIsSystemDriver() && - should_unload_system_driver(cnx)) { + if (should_unload_system_driver(cnx)) { unload_system_driver(cnx); } @@ -218,12 +218,8 @@ void* Loader::open(egl_connection_t* cnx) return cnx->dso; } - // Firstly, try to load ANGLE driver, unless we know that we shouldn't. - bool shouldForceLegacyDriver = android::GraphicsEnv::getInstance().shouldForceLegacyDriver(); - driver_t* hnd = nullptr; - if (!shouldForceLegacyDriver) { - hnd = attempt_to_load_angle(cnx); - } + // Firstly, try to load ANGLE driver. + driver_t* hnd = attempt_to_load_angle(cnx); if (!hnd) { // Secondly, try to load from driver apk. @@ -285,8 +281,10 @@ void* Loader::open(egl_connection_t* cnx) } LOG_ALWAYS_FATAL_IF(!hnd, - "couldn't find an OpenGL ES implementation, make sure you set %s or %s", - HAL_SUBNAME_KEY_PROPERTIES[0], HAL_SUBNAME_KEY_PROPERTIES[1]); + "couldn't find an OpenGL ES implementation, make sure one of %s, %s and %s " + "is set", + HAL_SUBNAME_KEY_PROPERTIES[0], HAL_SUBNAME_KEY_PROPERTIES[1], + HAL_SUBNAME_KEY_PROPERTIES[2]); if (!cnx->libEgl) { cnx->libEgl = load_wrapper(EGL_WRAPPER_DIR "/libEGL.so"); -- GitLab From 172d5a3281d6b1dd1e6cf5871db2484098f14dc2 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 25 Apr 2023 22:23:41 +0000 Subject: [PATCH 1192/1310] Add docs for ASurfaceControl default behavior Test: spellcheck Fixes: 271461920 Change-Id: I7a86abd1f764af73288edf575a288d4fcab02b86 --- include/android/surface_control.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/android/surface_control.h b/include/android/surface_control.h index e4ba58adc7..cce2e46471 100644 --- a/include/android/surface_control.h +++ b/include/android/surface_control.h @@ -54,6 +54,12 @@ typedef struct ASurfaceControl ASurfaceControl; * The caller takes ownership of the ASurfaceControl returned and must release it * using ASurfaceControl_release below. * + * By default the \a ASurfaceControl will be visible and display any buffer submitted. In + * addition, the default buffer submission control may release and not display all buffers + * that are submitted before receiving a callback for the previous buffer. See + * \a ASurfaceTransaction_setVisibility and \a ASurfaceTransaction_setEnableBackPressure to + * change the default behaviors after creation. + * * Available since API level 29. */ ASurfaceControl* ASurfaceControl_createFromWindow(ANativeWindow* parent, const char* debug_name) -- GitLab From 7adc2aad5289f0d6bdb0ceabe9b4d3853b183628 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 26 Apr 2023 19:47:29 -0700 Subject: [PATCH 1193/1310] [sf] Properly update clones of clones When updating a cloned layer hierarchy that contains another cloned layer hierarchy, we are dependent on the order in which we update each hierarchy. The inner cloned hierarchy must be updated first otherwise the outer clone will not capture the entire tree and some layers might be omitted from composition. To fix this we track all layer mirror roots. When updating each root, we check to see if they mirror another root. If they do mirror another root and that root has not been updated, we update the root again at the end. Test: repro steps in bug Bug: 279622227 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:f0a146c9e425b9959389749309ad32bfd795d841) Merged-In: I07e3f2c89d62d44455fda9fe3f8203215c22abf7 Change-Id: I07e3f2c89d62d44455fda9fe3f8203215c22abf7 --- services/surfaceflinger/Layer.cpp | 16 ++++++++++++---- services/surfaceflinger/Layer.h | 2 +- services/surfaceflinger/LayerRenderArea.cpp | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 19 ++++++++++++++++--- services/surfaceflinger/SurfaceFlinger.h | 3 +-- 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 9c232b11a7..6e5e0a7358 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -254,7 +254,8 @@ Layer::~Layer() { mFlinger->mTunnelModeEnabledReporter->decrementTunnelModeCount(); } if (mHadClonedChild) { - mFlinger->mNumClones--; + auto& roots = mFlinger->mLayerMirrorRoots; + roots.erase(std::remove(roots.begin(), roots.end(), this), roots.end()); } if (hasTrustedPresentationListener()) { mFlinger->mNumTrustedPresentationListeners--; @@ -2591,7 +2592,7 @@ void Layer::updateCloneBufferInfo() { mDrawingState.inputInfo = tmpInputInfo; } -void Layer::updateMirrorInfo() { +bool Layer::updateMirrorInfo(const std::deque& cloneRootsPendingUpdates) { if (mClonedChild == nullptr || !mClonedChild->isClonedFromAlive()) { // If mClonedChild is null, there is nothing to mirror. If isClonedFromAlive returns false, // it means that there is a clone, but the layer it was cloned from has been destroyed. In @@ -2599,7 +2600,7 @@ void Layer::updateMirrorInfo() { // destroyed. The root, this layer, will still be around since the client can continue // to hold a reference, but no cloned layers will be displayed. mClonedChild = nullptr; - return; + return true; } std::map, sp> clonedLayersMap; @@ -2614,6 +2615,13 @@ void Layer::updateMirrorInfo() { mClonedChild->updateClonedDrawingState(clonedLayersMap); mClonedChild->updateClonedChildren(sp::fromExisting(this), clonedLayersMap); mClonedChild->updateClonedRelatives(clonedLayersMap); + + for (Layer* root : cloneRootsPendingUpdates) { + if (clonedLayersMap.find(sp::fromExisting(root)) != clonedLayersMap.end()) { + return false; + } + } + return true; } void Layer::updateClonedDrawingState(std::map, sp>& clonedLayersMap) { @@ -2761,7 +2769,7 @@ bool Layer::isInternalDisplayOverlay() const { void Layer::setClonedChild(const sp& clonedChild) { mClonedChild = clonedChild; mHadClonedChild = true; - mFlinger->mNumClones++; + mFlinger->mLayerMirrorRoots.push_back(this); } bool Layer::setDropInputMode(gui::DropInputMode mode) { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 8d7c362241..940edf4192 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -645,7 +645,7 @@ public: gui::WindowInfo::Type getWindowType() const { return mWindowType; } - void updateMirrorInfo(); + bool updateMirrorInfo(const std::deque& cloneRootsPendingUpdates); /* * doTransaction - process the transaction. This is a good place to figure diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp index 1b8ff28a76..fd56d7a69f 100644 --- a/services/surfaceflinger/LayerRenderArea.cpp +++ b/services/surfaceflinger/LayerRenderArea.cpp @@ -84,7 +84,7 @@ void LayerRenderArea::render(std::function drawLayers) { // If layer is offscreen, update mirroring info if it exists if (mLayer->isRemovedFromCurrentState()) { mLayer->traverse(LayerVector::StateSet::Drawing, - [&](Layer* layer) { layer->updateMirrorInfo(); }); + [&](Layer* layer) { layer->updateMirrorInfo({}); }); mLayer->traverse(LayerVector::StateSet::Drawing, [&](Layer* layer) { layer->updateCloneBufferInfo(); }); } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 31bf7ef70b..d589046fb5 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3988,8 +3988,21 @@ void SurfaceFlinger::doCommitTransactions() { } commitOffscreenLayers(); - if (mNumClones > 0) { - mDrawingState.traverse([&](Layer* layer) { layer->updateMirrorInfo(); }); + if (mLayerMirrorRoots.size() > 0) { + std::deque pendingUpdates; + pendingUpdates.insert(pendingUpdates.end(), mLayerMirrorRoots.begin(), + mLayerMirrorRoots.end()); + std::vector needsUpdating; + for (Layer* cloneRoot : mLayerMirrorRoots) { + pendingUpdates.pop_front(); + if (cloneRoot->updateMirrorInfo(pendingUpdates)) { + } else { + needsUpdating.push_back(cloneRoot); + } + } + for (Layer* cloneRoot : needsUpdating) { + cloneRoot->updateMirrorInfo({}); + } } } @@ -4093,7 +4106,7 @@ bool SurfaceFlinger::latchBuffers() { mBootStage = BootStage::BOOTANIMATION; } - if (mNumClones > 0) { + if (mLayerMirrorRoots.size() > 0) { mDrawingState.traverse([&](Layer* layer) { layer->updateCloneBufferInfo(); }); } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index eb9dc7494e..0a5fbd692f 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -295,8 +295,7 @@ public: // the client can no longer modify this layer directly. void onHandleDestroyed(BBinder* handle, sp& layer, uint32_t layerId); - // TODO: Remove atomic if move dtor to main thread CL lands - std::atomic mNumClones; + std::vector mLayerMirrorRoots; TransactionCallbackInvoker& getTransactionCallbackInvoker() { return mTransactionCallbackInvoker; -- GitLab From e4af0956e0a5a0080f21e276f4fccadf95d5fe40 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Mon, 1 May 2023 09:11:33 -0700 Subject: [PATCH 1194/1310] [sf] Fix layer ids for background color layers Fixes a regression caused by Ie0f93ca956e6d043c9d95d00bc205d242e47c4cc which created duplicate layer ids. Also deflakes corner radius tests by tracking eCornerRadiusChanged state changes correctly. Test: presubmit w/new fe Bug: 238781169 Change-Id: I2d5321b5ba77e1074ef039bcd90c78e82c1a0049 --- libs/gui/include/gui/LayerState.h | 7 ++++--- services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp | 2 +- .../surfaceflinger/FrontEnd/LayerLifecycleManager.cpp | 9 ++++++--- services/surfaceflinger/SurfaceFlinger.cpp | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 5c88a07ba8..a6f503ef55 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -233,9 +233,10 @@ struct layer_state_t { // Geometry updates. static constexpr uint64_t GEOMETRY_CHANGES = layer_state_t::eBufferCropChanged | - layer_state_t::eBufferTransformChanged | layer_state_t::eCropChanged | - layer_state_t::eDestinationFrameChanged | layer_state_t::eMatrixChanged | - layer_state_t::ePositionChanged | layer_state_t::eTransformToDisplayInverseChanged | + 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::eTransparentRegionChanged; // Buffer and related updates. diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp index cfa2b031e9..97af445513 100644 --- a/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp +++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp @@ -50,7 +50,7 @@ LayerCreationArgs::LayerCreationArgs(SurfaceFlinger* flinger, sp client, } if (internalLayer) { - sequence = getInternalLayerId(sInternalSequence++); + sequence = id.value_or(getInternalLayerId(sInternalSequence++)); } else if (id) { sequence = *id; sSequence = *id + 1; diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp index 6cacfb5946..cd9515cd02 100644 --- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp +++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp @@ -38,7 +38,8 @@ void LayerLifecycleManager::addLayers(std::vectorsecond.owner.getDebugString().c_str()); } mAddedLayers.push_back(newLayer.get()); @@ -200,8 +201,10 @@ void LayerLifecycleManager::applyTransactions(const std::vectorwhat & layer_state_t::eBackgroundColorChanged) { if (layer->bgColorLayerId == UNASSIGNED_LAYER_ID && layer->bgColor.a != 0) { - LayerCreationArgs backgroundLayerArgs(layer->id, - /*internalLayer=*/true); + LayerCreationArgs + backgroundLayerArgs(LayerCreationArgs::getInternalLayerId( + LayerCreationArgs::sInternalSequence++), + /*internalLayer=*/true); backgroundLayerArgs.parentId = layer->id; backgroundLayerArgs.name = layer->name + "BackgroundColorLayer"; backgroundLayerArgs.flags = ISurfaceComposerClient::eFXSurfaceEffect; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index c88bff5ed8..e803baa9b7 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2307,7 +2307,7 @@ bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, frontend::Update& upd sp bgColorLayer = getFactory().createEffectLayer( LayerCreationArgs(this, nullptr, layer->name, ISurfaceComposerClient::eFXSurfaceEffect, LayerMetadata(), - std::make_optional(layer->parentId), true)); + std::make_optional(layer->id), true)); mLegacyLayers[bgColorLayer->sequence] = bgColorLayer; } const bool willReleaseBufferOnLatch = layer->willReleaseBufferOnLatch(); -- GitLab From bf88052327da62fceeb07d98ac62f3c8f56047aa Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 1 May 2023 11:03:22 -0700 Subject: [PATCH 1195/1310] Mark slipWallpaperTouch as const This function is already not modifying the dispatcher state, so let's mark it as const. This will allow other functions that depend on it to also be marked as const in the future. Other small fixes in this CL: 1. Remove code after fatal log. This code will never execute. 2. Use MotionEvent::getActionMasked Bug: 273376858 Test: presubmit Change-Id: I9aaba33f7c3b5dd29a45b120c2823cd41f7f8380 --- services/inputflinger/dispatcher/InputDispatcher.cpp | 9 +++------ services/inputflinger/dispatcher/InputDispatcher.h | 4 ++-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 8c08ef25e3..62bfc1727c 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2203,7 +2203,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( // event injection will be allowed. const int32_t displayId = entry.displayId; const int32_t action = entry.action; - const int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK; + const int32_t maskedAction = MotionEvent::getActionMasked(action); // Update the touch state as needed based on the properties of the touch event. outInjectionResult = InputEventInjectionResult::PENDING; @@ -2704,9 +2704,6 @@ void InputDispatcher::addDragEventLocked(const MotionEntry& entry) { if (uint32_t(pointerIndex) == entry.pointerCount) { LOG_ALWAYS_FATAL("Should find a valid pointer index by id %d", mDragState->pointerId); - sendDropWindowCommandLocked(nullptr, 0, 0); - mDragState.reset(); - return; } const int32_t maskedAction = entry.action & AMOTION_EVENT_ACTION_MASK; @@ -3611,7 +3608,7 @@ std::array InputDispatcher::sign(const VerifiedInputEvent& event) c const std::array InputDispatcher::getSignature( const MotionEntry& motionEntry, const DispatchEntry& dispatchEntry) const { - const int32_t actionMasked = dispatchEntry.resolvedAction & AMOTION_EVENT_ACTION_MASK; + const int32_t actionMasked = MotionEvent::getActionMasked(dispatchEntry.resolvedAction); if (actionMasked != AMOTION_EVENT_ACTION_UP && actionMasked != AMOTION_EVENT_ACTION_DOWN) { // Only sign events up and down events as the purely move events // are tied to their up/down counterparts so signing would be redundant. @@ -6665,7 +6662,7 @@ void InputDispatcher::slipWallpaperTouch(ftl::Flags targetFl const sp& oldWindowHandle, const sp& newWindowHandle, TouchState& state, int32_t pointerId, - std::vector& targets) { + std::vector& targets) const { std::bitset pointerIds; pointerIds.set(pointerId); const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test( diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index dd7f7fe2b0..187429d578 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -707,8 +707,8 @@ private: void slipWallpaperTouch(ftl::Flags targetFlags, const sp& oldWindowHandle, const sp& newWindowHandle, - TouchState& state, int32_t pointerId, std::vector& targets) - REQUIRES(mLock); + TouchState& state, int32_t pointerId, + std::vector& targets) const REQUIRES(mLock); void transferWallpaperTouch(ftl::Flags oldTargetFlags, ftl::Flags newTargetFlags, const sp fromWindowHandle, -- GitLab From b681c200e37cbec36ed9f4e80841b12eec97dece Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 1 May 2023 11:22:33 -0700 Subject: [PATCH 1196/1310] Simplify hovering pointer condition The hovering pointers are already getting cleared earlier in the function (the call is 'tempTouchState.clearHoveringPointers();'). Therefore, the code path for "removeHoveringPointer" in response to ACTION_HOVER_EXIT is never hit. In this CL, we simplify this condition for clarity. Bug: 273376858 Test: presubmit Change-Id: Ifaa3b07cb8398a1a5715d6ce07c744a3bd5e7af5 --- .../inputflinger/dispatcher/InputDispatcher.cpp | 13 ++++--------- .../inputflinger/tests/InputDispatcher_test.cpp | 1 + 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 62bfc1727c..80a70f2bf4 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2330,16 +2330,11 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( continue; } - if (isHoverAction) { + if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER || + maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) { const int32_t pointerId = entry.pointerProperties[0].id; - if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) { - // Pointer left. Remove it - tempTouchState.removeHoveringPointer(entry.deviceId, pointerId); - } else { - // The "windowHandle" is the target of this hovering pointer. - tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, - pointerId); - } + // The "windowHandle" is the target of this hovering pointer. + tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointerId); } // Set target flags. diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index b3c509595b..f92b3a3c05 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -3957,6 +3957,7 @@ TEST_F(InputDispatcherDisplayProjectionTest, WindowGetsEventsInCorrectCoordinate firstWindow->assertNoEvents(); const MotionEvent* event = secondWindow->consumeMotion(); + ASSERT_NE(nullptr, event); EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, event->getAction()); // Ensure that the events from the "getRaw" API are in logical display coordinates. -- GitLab From 4235ea0f6bc1bd27f7fc5eafa10aed3a84feaafb Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Mon, 17 Apr 2023 15:14:20 -0400 Subject: [PATCH 1197/1310] Loosen requirement that getVsyncSchedule returns an object In some cases, a display may have been removed in between referring to a display and when we attempt to retrieve its VsyncSchedule. Remove the assert that there must be an associated VsyncSchedule, replacing it with handling that is specific to the call site. Anywhere std::null_opt is passed to getVsyncSchedule (the default), leave the call sites unchanged. This will pick the pacesetter, which should have a VsyncSchedule every time the method is called. This should always return a valid VsyncSchedule. Otherwise, the old code would LOG_ALWAYS_FATAL, while the new code would have a null pointer dereference. LOG_ALWAYS_FATAL if there is no associated VsyncSchedule in the following methods. They are all called on the main thread, when the schedule should always be available when requested. This matches the old behavior. Where these are not documented as main-thread only, add this annotation, since that's what makes this safe to assume. - enableHardwareVsync - disableHardwareVsync - setDisplayPowerMode - addPresentFence In addResyncSample, which is called from a binder thread, the display may have been removed since the callback was initiated. No-op with a warning in this case. In getDisplayStats, which also may be called from a binder thread, fall back to mActiveDisplayId, just in case it is called in between Scheduler demoting and promoting the pacesetter display. Add a test for this method. In the following methods, log an error and early exit. This may be more conservative than necessary. - resyncToHardwareVsyncLocked - setRenderRate In setVsyncEnabled, fail to setPendingHardwareVsyncState silently, which matches the behavior for the call to setHWCVsyncEnabled. Bug: 266094575 Bug: 277623302 Bug: 267636813 Bug: 278806588 Bug: 275691508 Bug: 275691150 Bug: 277364366 Test: builds, boots Test: SurfaceFlingerGetDisplayStatsTest Change-Id: I3541909448745b04252e88f299a4283f37e755e8 --- .../surfaceflinger/Scheduler/Scheduler.cpp | 27 +++- services/surfaceflinger/Scheduler/Scheduler.h | 7 +- services/surfaceflinger/SurfaceFlinger.cpp | 27 ++-- .../surfaceflinger/tests/unittests/Android.bp | 1 + .../SurfaceFlinger_GetDisplayStatsTest.cpp | 116 ++++++++++++++++++ .../tests/unittests/TestableSurfaceFlinger.h | 7 ++ 6 files changed, 169 insertions(+), 16 deletions(-) create mode 100644 services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 63a0173519..316ea8ee32 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -406,11 +406,13 @@ void Scheduler::setVsyncConfig(const VsyncConfig& config, Period vsyncPeriod) { void Scheduler::enableHardwareVsync(PhysicalDisplayId id) { auto schedule = getVsyncSchedule(id); + LOG_ALWAYS_FATAL_IF(!schedule); schedule->enableHardwareVsync(mSchedulerCallback); } void Scheduler::disableHardwareVsync(PhysicalDisplayId id, bool disallow) { auto schedule = getVsyncSchedule(id); + LOG_ALWAYS_FATAL_IF(!schedule); schedule->disableHardwareVsync(mSchedulerCallback, disallow); } @@ -427,7 +429,10 @@ void Scheduler::resyncAllToHardwareVsync(bool allowToEnable) { void Scheduler::resyncToHardwareVsyncLocked(PhysicalDisplayId id, bool allowToEnable, std::optional refreshRate) { const auto displayOpt = mDisplays.get(id); - LOG_ALWAYS_FATAL_IF(!displayOpt); + if (!displayOpt) { + ALOGW("%s: Invalid display %s!", __func__, to_string(id).c_str()); + return; + } const Display& display = *displayOpt; if (display.schedulePtr->isHardwareVsyncAllowed(allowToEnable)) { @@ -446,7 +451,10 @@ void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate) { ftl::FakeGuard guard(kMainThreadContext); const auto displayOpt = mDisplays.get(id); - LOG_ALWAYS_FATAL_IF(!displayOpt); + if (!displayOpt) { + ALOGW("%s: Invalid display %s!", __func__, to_string(id).c_str()); + return; + } const Display& display = *displayOpt; const auto mode = display.selectorPtr->getActiveMode(); @@ -478,12 +486,18 @@ bool Scheduler::addResyncSample(PhysicalDisplayId id, nsecs_t timestamp, const auto hwcVsyncPeriod = ftl::Optional(hwcVsyncPeriodIn).transform([](nsecs_t nanos) { return Period::fromNs(nanos); }); - return getVsyncSchedule(id)->addResyncSample(mSchedulerCallback, TimePoint::fromNs(timestamp), - hwcVsyncPeriod); + auto schedule = getVsyncSchedule(id); + if (!schedule) { + ALOGW("%s: Invalid display %s!", __func__, to_string(id).c_str()); + return false; + } + return schedule->addResyncSample(mSchedulerCallback, TimePoint::fromNs(timestamp), + hwcVsyncPeriod); } void Scheduler::addPresentFence(PhysicalDisplayId id, std::shared_ptr fence) { auto schedule = getVsyncSchedule(id); + LOG_ALWAYS_FATAL_IF(!schedule); const bool needMoreSignals = schedule->getController().addPresentFence(std::move(fence)); if (needMoreSignals) { schedule->enableHardwareVsync(mSchedulerCallback); @@ -553,6 +567,7 @@ void Scheduler::setDisplayPowerMode(PhysicalDisplayId id, hal::PowerMode powerMo { std::scoped_lock lock(mDisplayLock); auto vsyncSchedule = getVsyncScheduleLocked(id); + LOG_ALWAYS_FATAL_IF(!vsyncSchedule); vsyncSchedule->getController().setDisplayPowerMode(powerMode); } if (!isPacesetter) return; @@ -582,7 +597,9 @@ auto Scheduler::getVsyncScheduleLocked(std::optional idOpt) c } const auto displayOpt = mDisplays.get(*idOpt); - LOG_ALWAYS_FATAL_IF(!displayOpt); + if (!displayOpt) { + return nullptr; + } return displayOpt->get().schedulePtr; } diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 43aab2d993..f13c878b67 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -196,8 +196,8 @@ public: // Sets the render rate for the scheduler to run at. void setRenderRate(PhysicalDisplayId, Fps); - void enableHardwareVsync(PhysicalDisplayId); - void disableHardwareVsync(PhysicalDisplayId, bool disallow); + void enableHardwareVsync(PhysicalDisplayId) REQUIRES(kMainThreadContext); + void disableHardwareVsync(PhysicalDisplayId, bool disallow) REQUIRES(kMainThreadContext); // Resyncs the scheduler to hardware vsync. // If allowToEnable is true, then hardware vsync will be turned on. @@ -219,7 +219,8 @@ public: // otherwise. bool addResyncSample(PhysicalDisplayId, nsecs_t timestamp, std::optional hwcVsyncPeriod); - void addPresentFence(PhysicalDisplayId, std::shared_ptr) EXCLUDES(mDisplayLock); + void addPresentFence(PhysicalDisplayId, std::shared_ptr) EXCLUDES(mDisplayLock) + REQUIRES(kMainThreadContext); // Layers are registered on creation, and unregistered when the weak reference expires. void registerLayer(Layer*); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 48b41448a1..043ad86fca 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1146,17 +1146,26 @@ status_t SurfaceFlinger::getDisplayStats(const sp& displayToken, std::optional displayIdOpt; { Mutex::Autolock lock(mStateLock); - displayIdOpt = getPhysicalDisplayIdLocked(displayToken); + if (displayToken) { + displayIdOpt = getPhysicalDisplayIdLocked(displayToken); + if (!displayIdOpt) { + ALOGW("%s: Invalid physical display token %p", __func__, displayToken.get()); + return NAME_NOT_FOUND; + } + } else { + // TODO (b/277364366): Clients should be updated to pass in the display they + // want, rather than us picking an arbitrary one (the active display, in this + // case). + displayIdOpt = mActiveDisplayId; + } } - // TODO (b/277364366): Clients should be updated to pass in the display they - // want, rather than us picking an arbitrary one (the pacesetter, in this - // case). - if (displayToken && !displayIdOpt) { - ALOGE("%s: Invalid physical display token %p", __func__, displayToken.get()); + const auto schedule = mScheduler->getVsyncSchedule(displayIdOpt); + if (!schedule) { + ALOGE("%s: Missing VSYNC schedule for display %s!", __func__, + to_string(*displayIdOpt).c_str()); return NAME_NOT_FOUND; } - const auto schedule = mScheduler->getVsyncSchedule(displayIdOpt); outStats->vsyncTime = schedule->vsyncDeadlineAfter(TimePoint::now()).ns(); outStats->vsyncPeriod = schedule->period().ns(); return NO_ERROR; @@ -2136,7 +2145,9 @@ void SurfaceFlinger::setVsyncEnabled(PhysicalDisplayId id, bool enabled) { static_cast(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) { { ftl::FakeGuard guard(kMainThreadContext); - mScheduler->getVsyncSchedule(id)->setPendingHardwareVsyncState(enabled); + if (auto schedule = mScheduler->getVsyncSchedule(id)) { + schedule->setPendingHardwareVsyncState(enabled); + } } ATRACE_FORMAT("%s (%d) for %" PRIu64 " (main thread)", whence, enabled, id.value); diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index 201d37ff7e..84ee7c9fdb 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -104,6 +104,7 @@ cc_test { "SurfaceFlinger_DisplayTransactionCommitTest.cpp", "SurfaceFlinger_ExcludeDolbyVisionTest.cpp", "SurfaceFlinger_GetDisplayNativePrimariesTest.cpp", + "SurfaceFlinger_GetDisplayStatsTest.cpp", "SurfaceFlinger_HdrOutputControlTest.cpp", "SurfaceFlinger_HotplugTest.cpp", "SurfaceFlinger_InitializeDisplaysTest.cpp", diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp new file mode 100644 index 0000000000..29acfaa1a5 --- /dev/null +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp @@ -0,0 +1,116 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "SurfaceFlingerGetDisplayStatsTest" + +#include +#include +#include +#include +#include +#include +#include "TestableSurfaceFlinger.h" +#include "mock/DisplayHardware/MockComposer.h" +#include "mock/DisplayHardware/MockPowerAdvisor.h" +#include "mock/MockTimeStats.h" +#include "mock/system/window/MockNativeWindow.h" + +using namespace android; +using namespace testing; + +namespace android { +namespace { +using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; +using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector; + +constexpr hal::HWDisplayId HWC_DISPLAY = FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID; +constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u); +constexpr int DEFAULT_DISPLAY_WIDTH = 1920; +constexpr int DEFAULT_DISPLAY_HEIGHT = 1024; + +class SurfaceFlingerGetDisplayStatsTest : public Test { +public: + void SetUp() override; + +protected: + TestableSurfaceFlinger mFlinger; + renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine(); + sp mDisplay; + sp mDisplaySurface = + sp::make(); + sp mNativeWindow = sp::make(); + mock::TimeStats* mTimeStats = new mock::TimeStats(); + Hwc2::mock::PowerAdvisor* mPowerAdvisor = nullptr; + Hwc2::mock::Composer* mComposer = nullptr; +}; + +void SurfaceFlingerGetDisplayStatsTest::SetUp() { + mFlinger.setupMockScheduler({.displayId = DEFAULT_DISPLAY_ID}); + mComposer = new Hwc2::mock::Composer(); + mPowerAdvisor = new Hwc2::mock::PowerAdvisor(); + mFlinger.setupRenderEngine(std::unique_ptr(mRenderEngine)); + mFlinger.setupTimeStats(std::shared_ptr(mTimeStats)); + mFlinger.setupComposer(std::unique_ptr(mComposer)); + mFlinger.setupPowerAdvisor(std::unique_ptr(mPowerAdvisor)); + static constexpr bool kIsPrimary = true; + FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, hal::DisplayType::PHYSICAL, kIsPrimary) + .setPowerMode(hal::PowerMode::ON) + .inject(&mFlinger, mComposer); + auto compostionEngineDisplayArgs = + compositionengine::DisplayCreationArgsBuilder() + .setId(DEFAULT_DISPLAY_ID) + .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT}) + .setPowerAdvisor(mPowerAdvisor) + .setName("injected display") + .build(); + auto compositionDisplay = + compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(), + std::move(compostionEngineDisplayArgs)); + mDisplay = + FakeDisplayDeviceInjector(mFlinger, compositionDisplay, + ui::DisplayConnectionType::Internal, HWC_DISPLAY, kIsPrimary) + .setDisplaySurface(mDisplaySurface) + .setNativeWindow(mNativeWindow) + .setPowerMode(hal::PowerMode::ON) + .setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector()) + .skipRegisterDisplay() + .inject(); +} + +// TODO (b/277364366): Clients should be updated to pass in the display they want. +TEST_F(SurfaceFlingerGetDisplayStatsTest, nullptrSucceeds) { + DisplayStatInfo info; + status_t status = mFlinger.getDisplayStats(nullptr, &info); + EXPECT_EQ(status, NO_ERROR); +} + +TEST_F(SurfaceFlingerGetDisplayStatsTest, explicitToken) { + DisplayStatInfo info; + status_t status = mFlinger.getDisplayStats(mDisplay->getDisplayToken().promote(), &info); + EXPECT_EQ(status, NO_ERROR); +} + +TEST_F(SurfaceFlingerGetDisplayStatsTest, invalidToken) { + const String8 displayName("fakeDisplay"); + sp displayToken = mFlinger.createDisplay(displayName, false); + DisplayStatInfo info; + status_t status = mFlinger.getDisplayStats(displayToken, &info); + EXPECT_EQ(status, NAME_NOT_FOUND); +} + +} // namespace +} // namespace android diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index cfa366f803..a189c002c9 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -56,6 +56,9 @@ #include "mock/system/window/MockNativeWindow.h" namespace android { + +struct DisplayStatInfo; + namespace renderengine { class RenderEngine; @@ -545,6 +548,10 @@ public: return sp::make(creationArgs); } + status_t getDisplayStats(const sp& displayToken, DisplayStatInfo* outInfo) { + return mFlinger->getDisplayStats(displayToken, outInfo); + } + /* ------------------------------------------------------------------------ * Read-only access to private data to assert post-conditions. */ -- GitLab From f10a87bddbe89067002dac8384311e191bb9d5f4 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Mon, 1 May 2023 14:02:51 -0700 Subject: [PATCH 1198/1310] [sf] Fix crash caused by invalid access of mPreviouslyPresentedLayerStacks While trying to update the release fence for layers with buffer removed, we incorrectly accessed and updated the same data struct. Test: presbumit Fixes: 279397500 Change-Id: I2488da1696774669777e94e23301af6ecc2ae581 --- services/surfaceflinger/SurfaceFlinger.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index c88bff5ed8..4e0ac109ff 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2874,7 +2874,10 @@ void SurfaceFlinger::postComposition(nsecs_t callTime) { } for (auto layer : mLayersWithBuffersRemoved) { - for (auto layerStack : layer->mPreviouslyPresentedLayerStacks) { + std::vector previouslyPresentedLayerStacks = + std::move(layer->mPreviouslyPresentedLayerStacks); + layer->mPreviouslyPresentedLayerStacks.clear(); + for (auto layerStack : previouslyPresentedLayerStacks) { auto optDisplay = layerStackToDisplay.get(layerStack); if (optDisplay && !optDisplay->get()->isVirtual()) { auto fence = getHwComposer().getPresentFence(optDisplay->get()->getPhysicalId()); -- GitLab From 8a186104276cdf57d552c54dbad488e14f0d3d16 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Tue, 25 Apr 2023 00:34:30 +0000 Subject: [PATCH 1199/1310] Align HLG and PQ color management to 203 nits == SDR max by default The public HLG and PQ definitions map 203 nits to SDR white by scaling the respective transfer functions. Bug: 278121691 Bug: 278122024 Test: HwAccelerationTest test gradients Test: HDR test videos match DPU and GPU composition Test: SilkFX test HLG and PQ images Change-Id: Id830e2ac72f5bcf8566556053fcf3af6b945581b --- libs/renderengine/skia/ColorSpaces.cpp | 9 +- libs/renderengine/skia/SkiaRenderEngine.cpp | 10 +- libs/shaders/include/shaders/shaders.h | 27 +- libs/shaders/shaders.cpp | 481 ++++++-------------- libs/shaders/tests/shaders_test.cpp | 42 +- 5 files changed, 167 insertions(+), 402 deletions(-) diff --git a/libs/renderengine/skia/ColorSpaces.cpp b/libs/renderengine/skia/ColorSpaces.cpp index 37ff5dfede..92b01e07e6 100644 --- a/libs/renderengine/skia/ColorSpaces.cpp +++ b/libs/renderengine/skia/ColorSpaces.cpp @@ -21,6 +21,8 @@ namespace renderengine { namespace skia { // please keep in sync with hwui/utils/Color.cpp +// TODO: Scale by the dimming ratio here instead of in a generic 3x3 transform +// Otherwise there may be luminance shift for e.g., HLG. sk_sp toSkColorSpace(ui::Dataspace dataspace) { skcms_Matrix3x3 gamut; switch (dataspace & HAL_DATASPACE_STANDARD_MASK) { @@ -61,13 +63,14 @@ sk_sp toSkColorSpace(ui::Dataspace dataspace) { case HAL_DATASPACE_TRANSFER_GAMMA2_8: return SkColorSpace::MakeRGB({2.8f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut); case HAL_DATASPACE_TRANSFER_ST2084: - return SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut); + return SkColorSpace::MakeRGB({-2.f, -1.55522297832f, 1.86045365631f, 32 / 2523.0f, + 2413 / 128.0f, -2392 / 128.0f, 8192 / 1305.0f}, + gamut); case HAL_DATASPACE_TRANSFER_SMPTE_170M: return SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut); case HAL_DATASPACE_TRANSFER_HLG: - // return HLG transfer but scale by 1/12 skcms_TransferFunction hlgFn; - if (skcms_TransferFunction_makeScaledHLGish(&hlgFn, 1.f / 12.f, 2.f, 2.f, + if (skcms_TransferFunction_makeScaledHLGish(&hlgFn, 0.314509843, 2.f, 2.f, 1.f / 0.17883277f, 0.28466892f, 0.55991073f)) { return SkColorSpace::MakeRGB(hlgFn, gamut); diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index 2a51493cea..cfea85f98b 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -913,12 +913,10 @@ void SkiaRenderEngine::drawLayersInternal( continue; } - // If we need to map to linear space or color management is disabled, then mark the source - // image with the same colorspace as the destination surface so that Skia's color - // management is a no-op. - const ui::Dataspace layerDataspace = (!mUseColorManagement || requiresLinearEffect) - ? display.outputDataspace - : layer.sourceDataspace; + // If color management is disabled, then mark the source image with the same colorspace as + // the destination surface so that Skia's color management is a no-op. + const ui::Dataspace layerDataspace = + !mUseColorManagement ? display.outputDataspace : layer.sourceDataspace; SkPaint paint; if (layer.source.buffer.buffer) { diff --git a/libs/shaders/include/shaders/shaders.h b/libs/shaders/include/shaders/shaders.h index 42b0cc131c..5a4aaab851 100644 --- a/libs/shaders/include/shaders/shaders.h +++ b/libs/shaders/include/shaders/shaders.h @@ -51,23 +51,20 @@ struct LinearEffect { // Input dataspace of the source colors. const ui::Dataspace inputDataspace = ui::Dataspace::SRGB; - // Working dataspace for the output surface, for conversion from linear space. + // Working dataspace for the output surface. const ui::Dataspace outputDataspace = ui::Dataspace::SRGB; // Sets whether alpha premultiplication must be undone. // This is required if the source colors use premultiplied alpha and is not opaque. const bool undoPremultipliedAlpha = false; - // "Fake" dataspace of the source colors. This is used for applying an EOTF to compute linear - // RGB. This is used when Skia is expected to color manage the input image based on the - // dataspace of the provided source image and destination surface. SkRuntimeEffects use the - // destination color space as the working color space. RenderEngine deliberately sets the color - // space for input images and destination surfaces to be the same whenever LinearEffects are - // expected to be used so that color-management is controlled by RenderEngine, but other users - // of a LinearEffect may not be able to control the color space of the images and surfaces. So - // fakeInputDataspace is used to essentially masquerade the input dataspace to be the output - // dataspace for correct conversion to linear colors. - ui::Dataspace fakeInputDataspace = ui::Dataspace::UNKNOWN; + // "Fake" dataspace of the destination colors. This is used for applying an OETF to compute + // non-linear RGB. This is used when Skia is expected to color manage the input image based on + // the dataspace of the provided source image and destination surface. Some use-cases in + // RenderEngine expect to apply a different OETF than what is expected by Skia. As in, + // RenderEngine will color manage to a custom destination and "cast" the result to Skia's + // working space. + ui::Dataspace fakeOutputDataspace = ui::Dataspace::UNKNOWN; enum SkSLType { Shader, ColorFilter }; SkSLType type = Shader; @@ -76,7 +73,7 @@ struct LinearEffect { static inline bool operator==(const LinearEffect& lhs, const LinearEffect& rhs) { return lhs.inputDataspace == rhs.inputDataspace && lhs.outputDataspace == rhs.outputDataspace && lhs.undoPremultipliedAlpha == rhs.undoPremultipliedAlpha && - lhs.fakeInputDataspace == rhs.fakeInputDataspace; + lhs.fakeOutputDataspace == rhs.fakeOutputDataspace; } struct LinearEffectHasher { @@ -89,7 +86,7 @@ struct LinearEffectHasher { size_t result = std::hash{}(le.inputDataspace); result = HashCombine(result, std::hash{}(le.outputDataspace)); result = HashCombine(result, std::hash{}(le.undoPremultipliedAlpha)); - return HashCombine(result, std::hash{}(le.fakeInputDataspace)); + return HashCombine(result, std::hash{}(le.fakeOutputDataspace)); } }; @@ -99,10 +96,6 @@ struct LinearEffectHasher { // 2. Apply color transform matrices in linear space std::string buildLinearEffectSkSL(const LinearEffect& linearEffect); -// Generates a shader string that applies color transforms in linear space. -// This is intended to be plugged into an SkColorFilter -std::string buildLinearEffectSkSLForColorFilter(const LinearEffect& linearEffect); - // Generates a list of uniforms to set on the LinearEffect shader above. std::vector buildLinearEffectUniforms( const LinearEffect& linearEffect, const mat4& colorTransform, float maxDisplayLuminance, diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp index 19518eaecc..b8f2be111e 100644 --- a/libs/shaders/shaders.cpp +++ b/libs/shaders/shaders.cpp @@ -33,187 +33,46 @@ aidl::android::hardware::graphics::common::Dataspace toAidlDataspace(ui::Dataspa return static_cast(dataspace); } -void generateEOTF(ui::Dataspace dataspace, std::string& shader) { - switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) { - case HAL_DATASPACE_TRANSFER_ST2084: - shader.append(R"( - - float3 EOTF(float3 color) { - float m1 = (2610.0 / 4096.0) / 4.0; - float m2 = (2523.0 / 4096.0) * 128.0; - float c1 = (3424.0 / 4096.0); - float c2 = (2413.0 / 4096.0) * 32.0; - float c3 = (2392.0 / 4096.0) * 32.0; - - float3 tmp = pow(clamp(color, 0.0, 1.0), 1.0 / float3(m2)); - tmp = max(tmp - c1, 0.0) / (c2 - c3 * tmp); - return pow(tmp, 1.0 / float3(m1)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_HLG: - shader.append(R"( - float EOTF_channel(float channel) { - const float a = 0.17883277; - const float b = 0.28466892; - const float c = 0.55991073; - return channel <= 0.5 ? channel * channel / 3.0 : - (exp((channel - c) / a) + b) / 12.0; - } - - float3 EOTF(float3 color) { - return float3(EOTF_channel(color.r), EOTF_channel(color.g), - EOTF_channel(color.b)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_LINEAR: - shader.append(R"( - float3 EOTF(float3 color) { - return color; - } - )"); - break; - case HAL_DATASPACE_TRANSFER_SMPTE_170M: - shader.append(R"( - - float EOTF_sRGB(float srgb) { - return srgb <= 0.08125 ? srgb / 4.50 : pow((srgb + 0.099) / 1.099, 1 / 0.45); - } - - float3 EOTF_sRGB(float3 srgb) { - return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b)); - } - - float3 EOTF(float3 srgb) { - return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_GAMMA2_2: - shader.append(R"( - - float EOTF_sRGB(float srgb) { - return pow(srgb, 2.2); - } - - float3 EOTF_sRGB(float3 srgb) { - return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b)); - } - - float3 EOTF(float3 srgb) { - return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_GAMMA2_6: - shader.append(R"( - - float EOTF_sRGB(float srgb) { - return pow(srgb, 2.6); - } - - float3 EOTF_sRGB(float3 srgb) { - return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b)); - } - - float3 EOTF(float3 srgb) { - return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_GAMMA2_8: - shader.append(R"( - - float EOTF_sRGB(float srgb) { - return pow(srgb, 2.8); - } - - float3 EOTF_sRGB(float3 srgb) { - return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b)); - } - - float3 EOTF(float3 srgb) { - return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_SRGB: - default: - shader.append(R"( - - float EOTF_sRGB(float srgb) { - return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4); - } - - float3 EOTF_sRGB(float3 srgb) { - return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b)); - } - - float3 EOTF(float3 srgb) { - return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb)); - } - )"); - break; - } -} - void generateXYZTransforms(std::string& shader) { shader.append(R"( - uniform float4x4 in_rgbToXyz; - uniform float4x4 in_xyzToRgb; + uniform float3x3 in_rgbToXyz; + uniform float3x3 in_xyzToSrcRgb; + uniform float4x4 in_colorTransform; float3 ToXYZ(float3 rgb) { - return (in_rgbToXyz * float4(rgb, 1.0)).rgb; + return in_rgbToXyz * rgb; + } + + float3 ToSrcRGB(float3 xyz) { + return in_xyzToSrcRgb * xyz; } - float3 ToRGB(float3 xyz) { - return clamp((in_xyzToRgb * float4(xyz, 1.0)).rgb, 0.0, 1.0); + float3 ApplyColorTransform(float3 rgb) { + return (in_colorTransform * float4(rgb, 1.0)).rgb; } )"); } -// Conversion from relative light to absolute light (maps from [0, 1] to [0, maxNits]) -void generateLuminanceScalesForOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace, - std::string& shader) { +// Conversion from relative light to absolute light +// Note that 1.0 == 203 nits. +void generateLuminanceScalesForOOTF(ui::Dataspace inputDataspace, std::string& shader) { switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) { - case HAL_DATASPACE_TRANSFER_ST2084: - shader.append(R"( - float3 ScaleLuminance(float3 xyz) { - return xyz * 10000.0; - } - )"); - break; case HAL_DATASPACE_TRANSFER_HLG: + // BT. 2408 says that a signal level of 0.75 == 203 nits for HLG, but that's after + // applying OOTF. But we haven't applied OOTF yet, so we need to scale by a different + // constant instead. shader.append(R"( - float3 ScaleLuminance(float3 xyz) { - return xyz * 1000.0; - } - )"); + float3 ScaleLuminance(float3 xyz) { + return xyz * 264.96; + } + )"); break; default: - // Input is SDR so map to its white point luminance - switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) { - // Max HLG output is nominally 1000 nits, but BT. 2100-2 allows - // for gamma correcting the HLG OOTF for displays with a different - // dynamic range. Scale to 1000 nits to apply an inverse OOTF against - // a reference display correctly. - // TODO: Use knowledge of the dimming ratio here to prevent - // unintended gamma shaft. - case HAL_DATASPACE_TRANSFER_HLG: - shader.append(R"( - float3 ScaleLuminance(float3 xyz) { - return xyz * 1000.0; - } - )"); - break; - default: - shader.append(R"( - float3 ScaleLuminance(float3 xyz) { - return xyz * in_libtonemap_displayMaxLuminance; - } - )"); - break; - } + shader.append(R"( + float3 ScaleLuminance(float3 xyz) { + return xyz * 203.0; + } + )"); + break; } } @@ -224,17 +83,17 @@ static void generateLuminanceNormalizationForOOTF(ui::Dataspace inputDataspace, switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) { case HAL_DATASPACE_TRANSFER_ST2084: shader.append(R"( - float3 NormalizeLuminance(float3 xyz) { - return xyz / 10000.0; - } - )"); + float3 NormalizeLuminance(float3 xyz) { + return xyz / 203.0; + } + )"); break; case HAL_DATASPACE_TRANSFER_HLG: switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) { case HAL_DATASPACE_TRANSFER_HLG: shader.append(R"( float3 NormalizeLuminance(float3 xyz) { - return xyz / 1000.0; + return xyz / 264.96; } )"); break; @@ -242,24 +101,39 @@ static void generateLuminanceNormalizationForOOTF(ui::Dataspace inputDataspace, // Transcoding to HLG requires applying the inverse OOTF // with the expectation that the OOTF is then applied during // tonemapping downstream. + // BT. 2100-2 operates on normalized luminances, so renormalize to the input to + // correctly adjust gamma. shader.append(R"( float3 NormalizeLuminance(float3 xyz) { - // BT. 2100-2 operates on normalized luminances, - // so renormalize to the input - float ootfGain = pow(xyz.y / 1000.0, -0.2 / 1.2) / 1000.0; - return xyz * ootfGain; + float ootfGain = pow(xyz.y / 1000.0, -0.2 / 1.2); + return xyz * ootfGain / 203.0; } )"); break; } break; default: - shader.append(R"( - float3 NormalizeLuminance(float3 xyz) { - return xyz / in_libtonemap_displayMaxLuminance; - } - )"); - break; + switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) { + case HAL_DATASPACE_TRANSFER_HLG: + case HAL_DATASPACE_TRANSFER_ST2084: + // libtonemap outputs a range [0, in_libtonemap_displayMaxLuminance], so + // normalize back to [0, 1] when the output is SDR. + shader.append(R"( + float3 NormalizeLuminance(float3 xyz) { + return xyz / in_libtonemap_displayMaxLuminance; + } + )"); + break; + default: + // Otherwise normalize back down to the range [0, 1] + // TODO: get this working for extended range outputs + shader.append(R"( + float3 NormalizeLuminance(float3 xyz) { + return xyz / 203.0; + } + )"); + break; + } } } @@ -270,145 +144,34 @@ void generateOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace, toAidlDataspace(outputDataspace)) .c_str()); - generateLuminanceScalesForOOTF(inputDataspace, outputDataspace, shader); + generateLuminanceScalesForOOTF(inputDataspace, shader); generateLuminanceNormalizationForOOTF(inputDataspace, outputDataspace, shader); + // Some tonemappers operate on CIE luminance, other tonemappers operate on linear rgb + // luminance in the source gamut. shader.append(R"( - float3 OOTF(float3 linearRGB, float3 xyz) { + float3 OOTF(float3 linearRGB) { float3 scaledLinearRGB = ScaleLuminance(linearRGB); - float3 scaledXYZ = ScaleLuminance(xyz); + float3 scaledXYZ = ToXYZ(scaledLinearRGB); - float gain = libtonemap_LookupTonemapGain(scaledLinearRGB, scaledXYZ); + float gain = libtonemap_LookupTonemapGain(ToSrcRGB(scaledXYZ), scaledXYZ); return NormalizeLuminance(scaledXYZ * gain); } )"); } -void generateOETF(ui::Dataspace dataspace, std::string& shader) { - switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) { - case HAL_DATASPACE_TRANSFER_ST2084: - shader.append(R"( - - float3 OETF(float3 xyz) { - float m1 = (2610.0 / 4096.0) / 4.0; - float m2 = (2523.0 / 4096.0) * 128.0; - float c1 = (3424.0 / 4096.0); - float c2 = (2413.0 / 4096.0) * 32.0; - float c3 = (2392.0 / 4096.0) * 32.0; - - float3 tmp = pow(xyz, float3(m1)); - tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp); - return pow(tmp, float3(m2)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_HLG: - shader.append(R"( - float OETF_channel(float channel) { - const float a = 0.17883277; - const float b = 0.28466892; - const float c = 0.55991073; - return channel <= 1.0 / 12.0 ? sqrt(3.0 * channel) : - a * log(12.0 * channel - b) + c; - } - - float3 OETF(float3 linear) { - return float3(OETF_channel(linear.r), OETF_channel(linear.g), - OETF_channel(linear.b)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_LINEAR: - shader.append(R"( - float3 OETF(float3 linear) { - return linear; - } - )"); - break; - case HAL_DATASPACE_TRANSFER_SMPTE_170M: - shader.append(R"( - float OETF_sRGB(float linear) { - return linear <= 0.018 ? - linear * 4.50 : (pow(linear, 0.45) * 1.099) - 0.099; - } - - float3 OETF_sRGB(float3 linear) { - return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b)); - } - - float3 OETF(float3 linear) { - return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_GAMMA2_2: - shader.append(R"( - float OETF_sRGB(float linear) { - return pow(linear, (1.0 / 2.2)); - } - - float3 OETF_sRGB(float3 linear) { - return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b)); - } - - float3 OETF(float3 linear) { - return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_GAMMA2_6: - shader.append(R"( - float OETF_sRGB(float linear) { - return pow(linear, (1.0 / 2.6)); - } - - float3 OETF_sRGB(float3 linear) { - return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b)); - } - - float3 OETF(float3 linear) { - return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_GAMMA2_8: - shader.append(R"( - float OETF_sRGB(float linear) { - return pow(linear, (1.0 / 2.8)); - } - - float3 OETF_sRGB(float3 linear) { - return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b)); - } - - float3 OETF(float3 linear) { - return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)); - } - )"); - break; - case HAL_DATASPACE_TRANSFER_SRGB: - default: - shader.append(R"( - float OETF_sRGB(float linear) { - return linear <= 0.0031308 ? - linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055; - } - - float3 OETF_sRGB(float3 linear) { - return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b)); - } - - float3 OETF(float3 linear) { - return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)); - } - )"); - break; - } +void generateOETF(std::string& shader) { + // Only support gamma 2.2 for now + shader.append(R"( + float OETF(float3 linear) { + return sign(linear) * pow(abs(linear), (1.0 / 2.2)); + } + )"); } void generateEffectiveOOTF(bool undoPremultipliedAlpha, LinearEffect::SkSLType type, - std::string& shader) { + bool needsCustomOETF, std::string& shader) { switch (type) { case LinearEffect::SkSLType::ColorFilter: shader.append(R"( @@ -429,11 +192,19 @@ void generateEffectiveOOTF(bool undoPremultipliedAlpha, LinearEffect::SkSLType t c.rgb = c.rgb / (c.a + 0.0019); )"); } + // We are using linear sRGB as a working space, with 1.0 == 203 nits shader.append(R"( - float3 linearRGB = EOTF(c.rgb); - float3 xyz = ToXYZ(linearRGB); - c.rgb = OETF(ToRGB(OOTF(linearRGB, xyz))); + c.rgb = ApplyColorTransform(OOTF(toLinearSrgb(c.rgb))); )"); + if (needsCustomOETF) { + shader.append(R"( + c.rgb = OETF(c.rgb); + )"); + } else { + shader.append(R"( + c.rgb = fromLinearSrgb(c.rgb); + )"); + } if (undoPremultipliedAlpha) { shader.append(R"( c.rgb = c.rgb * (c.a + 0.0019); @@ -445,7 +216,31 @@ void generateEffectiveOOTF(bool undoPremultipliedAlpha, LinearEffect::SkSLType t )"); } -// please keep in sync with toSkColorSpace function in renderengine/skia/ColorSpaces.cpp +template ::value, bool> = true> +std::vector buildUniformValue(T value) { + std::vector result; + result.resize(sizeof(value)); + std::memcpy(result.data(), &value, sizeof(value)); + return result; +} + +} // namespace + +std::string buildLinearEffectSkSL(const LinearEffect& linearEffect) { + std::string shaderString; + generateXYZTransforms(shaderString); + generateOOTF(linearEffect.inputDataspace, linearEffect.outputDataspace, shaderString); + + const bool needsCustomOETF = (linearEffect.fakeOutputDataspace & HAL_DATASPACE_TRANSFER_MASK) == + HAL_DATASPACE_TRANSFER_GAMMA2_2; + if (needsCustomOETF) { + generateOETF(shaderString); + } + generateEffectiveOOTF(linearEffect.undoPremultipliedAlpha, linearEffect.type, needsCustomOETF, + shaderString); + return shaderString; +} + ColorSpace toColorSpace(ui::Dataspace dataspace) { switch (dataspace & HAL_DATASPACE_STANDARD_MASK) { case HAL_DATASPACE_STANDARD_BT709: @@ -457,14 +252,14 @@ ColorSpace toColorSpace(ui::Dataspace dataspace) { return ColorSpace::BT2020(); case HAL_DATASPACE_STANDARD_ADOBE_RGB: return ColorSpace::AdobeRGB(); - // TODO(b/208290320): BT601 format and variants return different primaries + // TODO(b/208290320): BT601 format and variants return different primaries 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: - // TODO(b/208290329): BT407M format returns different primaries + // TODO(b/208290329): BT407M format returns different primaries case HAL_DATASPACE_STANDARD_BT470M: - // TODO(b/208290904): FILM format returns different primaries + // TODO(b/208290904): FILM format returns different primaries case HAL_DATASPACE_STANDARD_FILM: case HAL_DATASPACE_STANDARD_UNSPECIFIED: default: @@ -472,29 +267,6 @@ ColorSpace toColorSpace(ui::Dataspace dataspace) { } } -template ::value, bool> = true> -std::vector buildUniformValue(T value) { - std::vector result; - result.resize(sizeof(value)); - std::memcpy(result.data(), &value, sizeof(value)); - return result; -} - -} // namespace - -std::string buildLinearEffectSkSL(const LinearEffect& linearEffect) { - std::string shaderString; - generateEOTF(linearEffect.fakeInputDataspace == ui::Dataspace::UNKNOWN - ? linearEffect.inputDataspace - : linearEffect.fakeInputDataspace, - shaderString); - generateXYZTransforms(shaderString); - generateOOTF(linearEffect.inputDataspace, linearEffect.outputDataspace, shaderString); - generateOETF(linearEffect.outputDataspace, shaderString); - generateEffectiveOOTF(linearEffect.undoPremultipliedAlpha, linearEffect.type, shaderString); - return shaderString; -} - // Generates a list of uniforms to set on the LinearEffect shader above. std::vector buildLinearEffectUniforms( const LinearEffect& linearEffect, const mat4& colorTransform, float maxDisplayLuminance, @@ -502,23 +274,24 @@ std::vector buildLinearEffectUniforms( aidl::android::hardware::graphics::composer3::RenderIntent renderIntent) { std::vector uniforms; - const ui::Dataspace inputDataspace = linearEffect.fakeInputDataspace == ui::Dataspace::UNKNOWN - ? linearEffect.inputDataspace - : linearEffect.fakeInputDataspace; - - if (inputDataspace == linearEffect.outputDataspace) { - uniforms.push_back({.name = "in_rgbToXyz", .value = buildUniformValue(mat4())}); - uniforms.push_back( - {.name = "in_xyzToRgb", .value = buildUniformValue(colorTransform)}); - } else { - ColorSpace inputColorSpace = toColorSpace(inputDataspace); - ColorSpace outputColorSpace = toColorSpace(linearEffect.outputDataspace); - uniforms.push_back({.name = "in_rgbToXyz", - .value = buildUniformValue(mat4(inputColorSpace.getRGBtoXYZ()))}); - uniforms.push_back({.name = "in_xyzToRgb", - .value = buildUniformValue( - colorTransform * mat4(outputColorSpace.getXYZtoRGB()))}); - } + auto inputColorSpace = toColorSpace(linearEffect.inputDataspace); + auto outputColorSpace = toColorSpace(linearEffect.outputDataspace); + + uniforms.push_back( + {.name = "in_rgbToXyz", + .value = buildUniformValue(ColorSpace::linearExtendedSRGB().getRGBtoXYZ())}); + uniforms.push_back({.name = "in_xyzToSrcRgb", + .value = buildUniformValue(inputColorSpace.getXYZtoRGB())}); + // Transforms xyz colors to linear source colors, then applies the color transform, then + // transforms to linear extended RGB for skia to color manage. + uniforms.push_back({.name = "in_colorTransform", + .value = buildUniformValue( + mat4(ColorSpace::linearExtendedSRGB().getXYZtoRGB()) * + // TODO: the color transform ideally should be applied + // in the source colorspace, but doing that breaks + // renderengine tests + mat4(outputColorSpace.getRGBtoXYZ()) * colorTransform * + mat4(outputColorSpace.getXYZtoRGB()))}); tonemap::Metadata metadata{.displayMaxLuminance = maxDisplayLuminance, // If the input luminance is unknown, use display luminance (aka, diff --git a/libs/shaders/tests/shaders_test.cpp b/libs/shaders/tests/shaders_test.cpp index d45fb246c7..ba8bed23e3 100644 --- a/libs/shaders/tests/shaders_test.cpp +++ b/libs/shaders/tests/shaders_test.cpp @@ -35,6 +35,10 @@ MATCHER_P2(UniformEq, name, value, "") { return arg.name == name && arg.value == value; } +MATCHER_P(UniformNameEq, name, "") { + return arg.name == name; +} + template ::value, bool> = true> std::vector buildUniformValue(T value) { std::vector result; @@ -49,50 +53,44 @@ TEST_F(ShadersTest, buildLinearEffectUniforms_selectsNoOpGamutMatrices) { shaders::LinearEffect effect = shaders::LinearEffect{.inputDataspace = ui::Dataspace::V0_SRGB_LINEAR, .outputDataspace = ui::Dataspace::V0_SRGB_LINEAR, - .fakeInputDataspace = ui::Dataspace::UNKNOWN}; + .fakeOutputDataspace = ui::Dataspace::UNKNOWN}; mat4 colorTransform = mat4::scale(vec4(.9, .9, .9, 1.)); auto uniforms = shaders::buildLinearEffectUniforms(effect, colorTransform, 1.f, 1.f, 1.f, nullptr, aidl::android::hardware::graphics::composer3:: RenderIntent::COLORIMETRIC); - EXPECT_THAT(uniforms, Contains(UniformEq("in_rgbToXyz", buildUniformValue(mat4())))); EXPECT_THAT(uniforms, - Contains(UniformEq("in_xyzToRgb", buildUniformValue(colorTransform)))); + Contains(UniformEq("in_rgbToXyz", + buildUniformValue( + ColorSpace::linearExtendedSRGB().getRGBtoXYZ())))); + EXPECT_THAT(uniforms, + Contains(UniformEq("in_xyzToSrcRgb", + buildUniformValue( + ColorSpace::linearSRGB().getXYZtoRGB())))); + // color transforms are already tested in renderengine's tests + EXPECT_THAT(uniforms, Contains(UniformNameEq("in_colorTransform"))); } TEST_F(ShadersTest, buildLinearEffectUniforms_selectsGamutTransformMatrices) { shaders::LinearEffect effect = shaders::LinearEffect{.inputDataspace = ui::Dataspace::V0_SRGB, .outputDataspace = ui::Dataspace::DISPLAY_P3, - .fakeInputDataspace = ui::Dataspace::UNKNOWN}; + .fakeOutputDataspace = ui::Dataspace::UNKNOWN}; ColorSpace inputColorSpace = ColorSpace::sRGB(); - ColorSpace outputColorSpace = ColorSpace::DisplayP3(); auto uniforms = shaders::buildLinearEffectUniforms(effect, mat4(), 1.f, 1.f, 1.f, nullptr, aidl::android::hardware::graphics::composer3:: RenderIntent::COLORIMETRIC); EXPECT_THAT(uniforms, Contains(UniformEq("in_rgbToXyz", - buildUniformValue(mat4(inputColorSpace.getRGBtoXYZ()))))); + buildUniformValue( + ColorSpace::linearExtendedSRGB().getRGBtoXYZ())))); EXPECT_THAT(uniforms, - Contains(UniformEq("in_xyzToRgb", - buildUniformValue(mat4(outputColorSpace.getXYZtoRGB()))))); -} - -TEST_F(ShadersTest, buildLinearEffectUniforms_respectsFakeInputDataspace) { - shaders::LinearEffect effect = - shaders::LinearEffect{.inputDataspace = ui::Dataspace::V0_SRGB, - .outputDataspace = ui::Dataspace::DISPLAY_P3, - .fakeInputDataspace = ui::Dataspace::DISPLAY_P3}; - - auto uniforms = - shaders::buildLinearEffectUniforms(effect, mat4(), 1.f, 1.f, 1.f, nullptr, - aidl::android::hardware::graphics::composer3:: - RenderIntent::COLORIMETRIC); - EXPECT_THAT(uniforms, Contains(UniformEq("in_rgbToXyz", buildUniformValue(mat4())))); - EXPECT_THAT(uniforms, Contains(UniformEq("in_xyzToRgb", buildUniformValue(mat4())))); + Contains(UniformEq("in_xyzToSrcRgb", + buildUniformValue(inputColorSpace.getXYZtoRGB())))); + EXPECT_THAT(uniforms, Contains(UniformNameEq("in_colorTransform"))); } } // namespace android -- GitLab From 0cec3ed248d1f0b82e3d86270a018ed7e7f8b620 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 26 Apr 2023 19:47:29 -0700 Subject: [PATCH 1200/1310] [sf] Properly update clones of clones When updating a cloned layer hierarchy that contains another cloned layer hierarchy, we are dependent on the order in which we update each hierarchy. The inner cloned hierarchy must be updated first otherwise the outer clone will not capture the entire tree and some layers might be omitted from composition. To fix this we track all layer mirror roots. When updating each root, we check to see if they mirror another root. If they do mirror another root and that root has not been updated, we update the root again at the end. Test: repro steps in bug Fixes: 279622227 Change-Id: I07e3f2c89d62d44455fda9fe3f8203215c22abf7 --- services/surfaceflinger/Layer.cpp | 16 ++++++++++++---- services/surfaceflinger/Layer.h | 2 +- services/surfaceflinger/LayerRenderArea.cpp | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 19 ++++++++++++++++--- services/surfaceflinger/SurfaceFlinger.h | 3 +-- 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 9e40d7f136..bbf855154c 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -254,7 +254,8 @@ Layer::~Layer() { mFlinger->mTunnelModeEnabledReporter->decrementTunnelModeCount(); } if (mHadClonedChild) { - mFlinger->mNumClones--; + auto& roots = mFlinger->mLayerMirrorRoots; + roots.erase(std::remove(roots.begin(), roots.end(), this), roots.end()); } if (hasTrustedPresentationListener()) { mFlinger->mNumTrustedPresentationListeners--; @@ -2591,7 +2592,7 @@ void Layer::updateCloneBufferInfo() { mDrawingState.inputInfo = tmpInputInfo; } -void Layer::updateMirrorInfo() { +bool Layer::updateMirrorInfo(const std::deque& cloneRootsPendingUpdates) { if (mClonedChild == nullptr || !mClonedChild->isClonedFromAlive()) { // If mClonedChild is null, there is nothing to mirror. If isClonedFromAlive returns false, // it means that there is a clone, but the layer it was cloned from has been destroyed. In @@ -2599,7 +2600,7 @@ void Layer::updateMirrorInfo() { // destroyed. The root, this layer, will still be around since the client can continue // to hold a reference, but no cloned layers will be displayed. mClonedChild = nullptr; - return; + return true; } std::map, sp> clonedLayersMap; @@ -2614,6 +2615,13 @@ void Layer::updateMirrorInfo() { mClonedChild->updateClonedDrawingState(clonedLayersMap); mClonedChild->updateClonedChildren(sp::fromExisting(this), clonedLayersMap); mClonedChild->updateClonedRelatives(clonedLayersMap); + + for (Layer* root : cloneRootsPendingUpdates) { + if (clonedLayersMap.find(sp::fromExisting(root)) != clonedLayersMap.end()) { + return false; + } + } + return true; } void Layer::updateClonedDrawingState(std::map, sp>& clonedLayersMap) { @@ -2761,7 +2769,7 @@ bool Layer::isInternalDisplayOverlay() const { void Layer::setClonedChild(const sp& clonedChild) { mClonedChild = clonedChild; mHadClonedChild = true; - mFlinger->mNumClones++; + mFlinger->mLayerMirrorRoots.push_back(this); } bool Layer::setDropInputMode(gui::DropInputMode mode) { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index b37fa153e9..41228c757b 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -651,7 +651,7 @@ public: gui::WindowInfo::Type getWindowType() const { return mWindowType; } - void updateMirrorInfo(); + bool updateMirrorInfo(const std::deque& cloneRootsPendingUpdates); /* * doTransaction - process the transaction. This is a good place to figure diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp index 1b8ff28a76..fd56d7a69f 100644 --- a/services/surfaceflinger/LayerRenderArea.cpp +++ b/services/surfaceflinger/LayerRenderArea.cpp @@ -84,7 +84,7 @@ void LayerRenderArea::render(std::function drawLayers) { // If layer is offscreen, update mirroring info if it exists if (mLayer->isRemovedFromCurrentState()) { mLayer->traverse(LayerVector::StateSet::Drawing, - [&](Layer* layer) { layer->updateMirrorInfo(); }); + [&](Layer* layer) { layer->updateMirrorInfo({}); }); mLayer->traverse(LayerVector::StateSet::Drawing, [&](Layer* layer) { layer->updateCloneBufferInfo(); }); } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 48b41448a1..e20b4cb0aa 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4007,8 +4007,21 @@ void SurfaceFlinger::doCommitTransactions() { } commitOffscreenLayers(); - if (mNumClones > 0) { - mDrawingState.traverse([&](Layer* layer) { layer->updateMirrorInfo(); }); + if (mLayerMirrorRoots.size() > 0) { + std::deque pendingUpdates; + pendingUpdates.insert(pendingUpdates.end(), mLayerMirrorRoots.begin(), + mLayerMirrorRoots.end()); + std::vector needsUpdating; + for (Layer* cloneRoot : mLayerMirrorRoots) { + pendingUpdates.pop_front(); + if (cloneRoot->updateMirrorInfo(pendingUpdates)) { + } else { + needsUpdating.push_back(cloneRoot); + } + } + for (Layer* cloneRoot : needsUpdating) { + cloneRoot->updateMirrorInfo({}); + } } } @@ -4115,7 +4128,7 @@ bool SurfaceFlinger::latchBuffers() { mBootStage = BootStage::BOOTANIMATION; } - if (mNumClones > 0) { + if (mLayerMirrorRoots.size() > 0) { mDrawingState.traverse([&](Layer* layer) { layer->updateCloneBufferInfo(); }); } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index fffd63a515..6855562ad7 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -296,8 +296,7 @@ public: // the client can no longer modify this layer directly. void onHandleDestroyed(BBinder* handle, sp& layer, uint32_t layerId); - // TODO: Remove atomic if move dtor to main thread CL lands - std::atomic mNumClones; + std::vector mLayerMirrorRoots; TransactionCallbackInvoker& getTransactionCallbackInvoker() { return mTransactionCallbackInvoker; -- GitLab From 792b150a708a61faedaaa5983fbc5c5185984f50 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Tue, 2 May 2023 00:03:08 +0000 Subject: [PATCH 1201/1310] Don't overdim SDR content in an HLG screenshot. Aligning HLG and PQ to 1.0 == 203 nits made SDR assets in screenshots too dim, since both the colorspace and the color transform applied dimming. Removing dimming application from the color transform is a larger change, so just compensate when configuring the screenshot in SurfaceFlinger instead. Bug: 280347733 Test: HwAccelerationTest Test: Navigate in and out of recents Change-Id: Idfdb74c0c3b977717b870b2bb9a469be37d27dc9 --- libs/shaders/shaders.cpp | 6 +++++- services/surfaceflinger/ScreenCaptureOutput.cpp | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp index b8f2be111e..c85517a976 100644 --- a/libs/shaders/shaders.cpp +++ b/libs/shaders/shaders.cpp @@ -103,10 +103,14 @@ static void generateLuminanceNormalizationForOOTF(ui::Dataspace inputDataspace, // tonemapping downstream. // BT. 2100-2 operates on normalized luminances, so renormalize to the input to // correctly adjust gamma. + // Note that following BT. 2408 for HLG OETF actually maps 0.75 == ~264.96 nits, + // rather than 203 nits, because 203 nits == OOTF(invOETF(0.75)), so even though + // we originally scaled by 203 nits we need to re-normalize to 264.96 nits when + // converting to the correct brightness range. shader.append(R"( float3 NormalizeLuminance(float3 xyz) { float ootfGain = pow(xyz.y / 1000.0, -0.2 / 1.2); - return xyz * ootfGain / 203.0; + return xyz * ootfGain / 264.96; } )"); break; diff --git a/services/surfaceflinger/ScreenCaptureOutput.cpp b/services/surfaceflinger/ScreenCaptureOutput.cpp index b70b53d05a..09dac23410 100644 --- a/services/surfaceflinger/ScreenCaptureOutput.cpp +++ b/services/surfaceflinger/ScreenCaptureOutput.cpp @@ -94,6 +94,16 @@ ScreenCaptureOutput::generateClientCompositionRequests( } } + if (outputDataspace == ui::Dataspace::BT2020_HLG) { + for (auto& layer : clientCompositionLayers) { + auto transfer = layer.sourceDataspace & ui::Dataspace::TRANSFER_MASK; + if (transfer != static_cast(ui::Dataspace::TRANSFER_HLG) && + transfer != static_cast(ui::Dataspace::TRANSFER_ST2084)) { + layer.whitePointNits *= (1000.0f / 203.0f); + } + } + } + Rect sourceCrop = mRenderArea.getSourceCrop(); compositionengine::LayerFE::LayerSettings fillLayer; fillLayer.source.buffer.buffer = nullptr; -- GitLab From fb219eb8413273e413780983db18a356e440f680 Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Tue, 2 May 2023 13:20:37 +0000 Subject: [PATCH 1202/1310] Fix use after delete reference for device Test: manually in HWASAN builds Bug: 280101294 Change-Id: I6e15cac095be96fecf02cf5e7ea33bf483fe5ecb --- services/inputflinger/tests/FakeEventHub.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp index 4626f5add7..212fcebecd 100644 --- a/services/inputflinger/tests/FakeEventHub.cpp +++ b/services/inputflinger/tests/FakeEventHub.cpp @@ -617,9 +617,11 @@ void FakeEventHub::sysfsNodeChanged(const std::string& sysfsNodePath) { } // If device sysfs changed -> reopen the device if (!mRawLightInfos.empty() && !foundDevice->classes.test(InputDeviceClass::LIGHT)) { + InputDeviceIdentifier identifier = foundDevice->identifier; + ftl::Flags classes = foundDevice->classes; removeDevice(foundDeviceId); - addDevice(foundDeviceId, foundDevice->identifier.name, - foundDevice->classes | InputDeviceClass::LIGHT, foundDevice->identifier.bus); + addDevice(foundDeviceId, identifier.name, classes | InputDeviceClass::LIGHT, + identifier.bus); } } -- GitLab From 0655a9157bc7ab4771202d2d4e206f91d5352481 Mon Sep 17 00:00:00 2001 From: Rachel Lee Date: Thu, 20 Apr 2023 19:54:18 -0700 Subject: [PATCH 1203/1310] Fix Choreographer affecting ASurfaceControlTest Fixes `testSurfaceTransaction_setFrameTimeline_notPreferredIndex` case on devices that do not have high refresh rates (e.g. max 60Hz). This occurred because the transaction-ready logic in SF does not consider transactions to be too early if the expected presentation time is over 100ms in the future, so the frame would just be deemed ready and presented asap. Thus, no longer provide frame timelines that are far into the future, which are not useful as well. Test: atest ASurfaceControlTest Test: atest ChoreographerTest Test: atest ChoreographerNativeTest Test: atest DisplayEventStructLayoutTest Test: atest ParcelableVsyncEventData Test: atest libsurfacefligner_unittest Fixes: 270612751 Change-Id: Ic05717bc153a9b07409b8d7912a1c40e1e31a57e --- libs/gui/VsyncEventData.cpp | 15 +- libs/gui/include/gui/VsyncEventData.h | 5 +- .../tests/DisplayEventStructLayout_test.cpp | 1 + libs/gui/tests/VsyncEventData_test.cpp | 2 + libs/nativedisplay/AChoreographer.cpp | 2 +- .../surfaceflinger/Scheduler/EventThread.cpp | 38 +++-- .../Scheduler/include/scheduler/VsyncConfig.h | 6 + services/surfaceflinger/SurfaceFlinger.cpp | 6 +- .../tests/DisplayEventReceiver_test.cpp | 7 +- .../tests/unittests/EventThreadTest.cpp | 132 +++++++++++++++--- 10 files changed, 166 insertions(+), 48 deletions(-) diff --git a/libs/gui/VsyncEventData.cpp b/libs/gui/VsyncEventData.cpp index 76c60c23c4..91fc9c0ffe 100644 --- a/libs/gui/VsyncEventData.cpp +++ b/libs/gui/VsyncEventData.cpp @@ -46,11 +46,15 @@ status_t ParcelableVsyncEventData::readFromParcel(const Parcel* parcel) { SAFE_PARCEL(parcel->readInt64, &vsync.frameInterval); - uint64_t uintPreferredFrameTimelineIndex; - SAFE_PARCEL(parcel->readUint64, &uintPreferredFrameTimelineIndex); + uint32_t uintPreferredFrameTimelineIndex; + SAFE_PARCEL(parcel->readUint32, &uintPreferredFrameTimelineIndex); vsync.preferredFrameTimelineIndex = static_cast(uintPreferredFrameTimelineIndex); - for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) { + uint32_t uintFrameTimelinesLength; + SAFE_PARCEL(parcel->readUint32, &uintFrameTimelinesLength); + vsync.frameTimelinesLength = static_cast(uintFrameTimelinesLength); + + for (size_t i = 0; i < vsync.frameTimelinesLength; i++) { SAFE_PARCEL(parcel->readInt64, &vsync.frameTimelines[i].vsyncId); SAFE_PARCEL(parcel->readInt64, &vsync.frameTimelines[i].deadlineTimestamp); SAFE_PARCEL(parcel->readInt64, &vsync.frameTimelines[i].expectedPresentationTime); @@ -60,8 +64,9 @@ status_t ParcelableVsyncEventData::readFromParcel(const Parcel* parcel) { } status_t ParcelableVsyncEventData::writeToParcel(Parcel* parcel) const { SAFE_PARCEL(parcel->writeInt64, vsync.frameInterval); - SAFE_PARCEL(parcel->writeUint64, vsync.preferredFrameTimelineIndex); - for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) { + SAFE_PARCEL(parcel->writeUint32, vsync.preferredFrameTimelineIndex); + SAFE_PARCEL(parcel->writeUint32, vsync.frameTimelinesLength); + for (size_t i = 0; i < vsync.frameTimelinesLength; i++) { SAFE_PARCEL(parcel->writeInt64, vsync.frameTimelines[i].vsyncId); SAFE_PARCEL(parcel->writeInt64, vsync.frameTimelines[i].deadlineTimestamp); SAFE_PARCEL(parcel->writeInt64, vsync.frameTimelines[i].expectedPresentationTime); diff --git a/libs/gui/include/gui/VsyncEventData.h b/libs/gui/include/gui/VsyncEventData.h index dfdae214d2..9ddb7cd82b 100644 --- a/libs/gui/include/gui/VsyncEventData.h +++ b/libs/gui/include/gui/VsyncEventData.h @@ -24,7 +24,7 @@ namespace android::gui { // Plain Old Data (POD) vsync data structure. For example, it can be easily used in the // DisplayEventReceiver::Event union. struct VsyncEventData { - // Max amount of frame timelines is arbitrarily set to be reasonable. + // Max capacity of frame timelines is arbitrarily set to be reasonable. static constexpr int64_t kFrameTimelinesLength = 7; // The current frame interval in ns when this frame was scheduled. @@ -33,6 +33,9 @@ struct VsyncEventData { // Index into the frameTimelines that represents the platform's preferred frame timeline. uint32_t preferredFrameTimelineIndex; + // Size of frame timelines provided by the platform; max is kFrameTimelinesLength. + uint32_t frameTimelinesLength; + 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/tests/DisplayEventStructLayout_test.cpp b/libs/gui/tests/DisplayEventStructLayout_test.cpp index da88463d63..b7b4ab605a 100644 --- a/libs/gui/tests/DisplayEventStructLayout_test.cpp +++ b/libs/gui/tests/DisplayEventStructLayout_test.cpp @@ -35,6 +35,7 @@ TEST(DisplayEventStructLayoutTest, TestEventAlignment) { CHECK_OFFSET(DisplayEventReceiver::Event::VSync, count, 0); 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.frameTimelines[0].deadlineTimestamp, diff --git a/libs/gui/tests/VsyncEventData_test.cpp b/libs/gui/tests/VsyncEventData_test.cpp index f114522951..6e039ee6e7 100644 --- a/libs/gui/tests/VsyncEventData_test.cpp +++ b/libs/gui/tests/VsyncEventData_test.cpp @@ -36,6 +36,7 @@ TEST(ParcelableVsyncEventData, Parcelling) { FrameTimeline timeline1 = FrameTimeline{4, 5, 6}; data.vsync.frameTimelines[0] = timeline0; data.vsync.frameTimelines[1] = timeline1; + data.vsync.frameTimelinesLength = 2; Parcel p; data.writeToParcel(&p); @@ -45,6 +46,7 @@ TEST(ParcelableVsyncEventData, Parcelling) { data2.readFromParcel(&p); ASSERT_EQ(data.vsync.frameInterval, data2.vsync.frameInterval); ASSERT_EQ(data.vsync.preferredFrameTimelineIndex, data2.vsync.preferredFrameTimelineIndex); + ASSERT_EQ(data.vsync.frameTimelinesLength, data2.vsync.frameTimelinesLength); for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) { ASSERT_EQ(data.vsync.frameTimelines[i].vsyncId, data2.vsync.frameTimelines[i].vsyncId); ASSERT_EQ(data.vsync.frameTimelines[i].deadlineTimestamp, diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp index 66a40f1278..80b0041923 100644 --- a/libs/nativedisplay/AChoreographer.cpp +++ b/libs/nativedisplay/AChoreographer.cpp @@ -197,7 +197,7 @@ size_t AChoreographerFrameCallbackData_getFrameTimelinesLength( AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data); LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(), "Data is only valid in callback"); - return VsyncEventData::kFrameTimelinesLength; + return frameCallbackData->vsyncEventData.frameTimelinesLength; } size_t AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex( const AChoreographerFrameCallbackData* data) { diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index 74665a70aa..f79688dfe0 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -42,6 +42,7 @@ #include #include +#include #include "DisplayHardware/DisplayMode.h" #include "FrameTimeline.h" #include "VSyncDispatch.h" @@ -597,25 +598,34 @@ void EventThread::generateFrameTimeline(VsyncEventData& outVsyncEventData, nsecs nsecs_t timestamp, nsecs_t preferredExpectedPresentationTime, nsecs_t preferredDeadlineTimestamp) const { + uint32_t currentIndex = 0; // Add 1 to ensure the preferredFrameTimelineIndex entry (when multiplier == 0) is included. - for (int64_t multiplier = -VsyncEventData::kFrameTimelinesLength + 1, currentIndex = 0; + for (int64_t multiplier = -VsyncEventData::kFrameTimelinesLength + 1; currentIndex < VsyncEventData::kFrameTimelinesLength; multiplier++) { nsecs_t deadlineTimestamp = preferredDeadlineTimestamp + multiplier * frameInterval; - // Valid possible frame timelines must have future values. - if (deadlineTimestamp > timestamp) { - if (multiplier == 0) { - outVsyncEventData.preferredFrameTimelineIndex = currentIndex; - } - nsecs_t expectedPresentationTime = - preferredExpectedPresentationTime + multiplier * frameInterval; - outVsyncEventData.frameTimelines[currentIndex] = - {.vsyncId = - generateToken(timestamp, deadlineTimestamp, expectedPresentationTime), - .deadlineTimestamp = deadlineTimestamp, - .expectedPresentationTime = expectedPresentationTime}; - currentIndex++; + // Valid possible frame timelines must have future values, so find a later frame timeline. + if (deadlineTimestamp <= timestamp) { + continue; + } + + nsecs_t expectedPresentationTime = + preferredExpectedPresentationTime + multiplier * frameInterval; + if (expectedPresentationTime >= preferredExpectedPresentationTime + + scheduler::VsyncConfig::kEarlyLatchMaxThreshold.count()) { + break; } + + if (multiplier == 0) { + outVsyncEventData.preferredFrameTimelineIndex = currentIndex; + } + + outVsyncEventData.frameTimelines[currentIndex] = + {.vsyncId = generateToken(timestamp, deadlineTimestamp, expectedPresentationTime), + .deadlineTimestamp = deadlineTimestamp, + .expectedPresentationTime = expectedPresentationTime}; + currentIndex++; } + outVsyncEventData.frameTimelinesLength = currentIndex; } void EventThread::dispatchEvent(const DisplayEventReceiver::Event& event, diff --git a/services/surfaceflinger/Scheduler/include/scheduler/VsyncConfig.h b/services/surfaceflinger/Scheduler/include/scheduler/VsyncConfig.h index 3b1985f56d..47d95a8a9a 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/VsyncConfig.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/VsyncConfig.h @@ -22,6 +22,8 @@ namespace android::scheduler { +using namespace std::chrono_literals; + // Phase offsets and work durations for SF and app deadlines from VSYNC. struct VsyncConfig { nsecs_t sfOffset; @@ -35,6 +37,10 @@ struct VsyncConfig { } bool operator!=(const VsyncConfig& other) const { return !(*this == other); } + + // The duration for which SF can delay a frame if it is considered early based on the + // VsyncConfig::appWorkDuration. + static constexpr std::chrono::nanoseconds kEarlyLatchMaxThreshold = 100ms; }; struct VsyncConfigSet { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 48b41448a1..584b125f50 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4395,10 +4395,8 @@ bool SurfaceFlinger::frameIsEarly(TimePoint expectedPresentTime, VsyncId vsyncId const auto predictedPresentTime = TimePoint::fromNs(prediction->presentTime); - // The duration for which SF can delay a frame if it is considered early based on the - // VsyncConfig::appWorkDuration. - if (constexpr std::chrono::nanoseconds kEarlyLatchMaxThreshold = 100ms; - std::chrono::abs(predictedPresentTime - expectedPresentTime) >= kEarlyLatchMaxThreshold) { + if (std::chrono::abs(predictedPresentTime - expectedPresentTime) >= + scheduler::VsyncConfig::kEarlyLatchMaxThreshold) { return false; } diff --git a/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp b/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp index 0df7e2fafa..761ac8c538 100644 --- a/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp +++ b/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp @@ -33,9 +33,14 @@ TEST_F(DisplayEventReceiverTest, getLatestVsyncEventData) { const VsyncEventData& vsyncEventData = parcelableVsyncEventData.vsync; EXPECT_NE(std::numeric_limits::max(), vsyncEventData.preferredFrameTimelineIndex); + EXPECT_GT(static_cast(vsyncEventData.frameTimelinesLength), 0) + << "Frame timelines length should be greater than 0"; + EXPECT_LE(static_cast(vsyncEventData.frameTimelinesLength), + VsyncEventData::kFrameTimelinesLength) + << "Frame timelines length should not exceed max capacity"; EXPECT_GT(vsyncEventData.frameTimelines[0].deadlineTimestamp, now) << "Deadline timestamp should be greater than frame time"; - for (size_t i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) { + for (size_t i = 0; i < vsyncEventData.frameTimelinesLength; i++) { EXPECT_NE(gui::FrameTimelineInfo::INVALID_VSYNC_ID, vsyncEventData.frameTimelines[i].vsyncId); EXPECT_GT(vsyncEventData.frameTimelines[i].expectedPresentationTime, diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp index f1cdca3ee1..6debbaae2d 100644 --- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp +++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "AsyncCallRecorder.h" @@ -77,7 +78,7 @@ protected: EventThreadTest(); ~EventThreadTest() override; - void createThread(); + void setupEventThread(std::chrono::nanoseconds vsyncPeriod); sp createConnection(ConnectionEventRecorder& recorder, EventRegistrationFlags eventRegistration = {}, uid_t ownerUid = mConnectionUid); @@ -90,8 +91,9 @@ protected: nsecs_t expectedTimestamp, unsigned expectedCount); void expectVsyncEventReceivedByConnection(nsecs_t expectedTimestamp, unsigned expectedCount); void expectVsyncEventFrameTimelinesCorrect( - nsecs_t expectedTimestamp, - /*VSyncSource::VSyncData*/ gui::VsyncEventData::FrameTimeline preferredVsyncData); + nsecs_t expectedTimestamp, gui::VsyncEventData::FrameTimeline preferredVsyncData); + void expectVsyncEventDataFrameTimelinesValidLength(VsyncEventData vsyncEventData, + std::chrono::nanoseconds vsyncPeriod); void expectHotplugEventReceivedByConnection(PhysicalDisplayId expectedDisplayId, bool expectedConnected); void expectConfigChangedEventReceivedByConnection(PhysicalDisplayId expectedDisplayId, @@ -154,19 +156,6 @@ EventThreadTest::EventThreadTest() { .WillRepeatedly(Invoke(mVSyncCallbackUpdateRecorder.getInvocable())); EXPECT_CALL(mockDispatch, unregisterCallback(_)) .WillRepeatedly(Invoke(mVSyncCallbackUnregisterRecorder.getInvocable())); - - createThread(); - mConnection = - createConnection(mConnectionEventCallRecorder, - gui::ISurfaceComposer::EventRegistration::modeChanged | - gui::ISurfaceComposer::EventRegistration::frameRateOverride); - mThrottledConnection = createConnection(mThrottledConnectionEventCallRecorder, - gui::ISurfaceComposer::EventRegistration::modeChanged, - mThrottledConnectionUid); - - // A display must be connected for VSYNC events to be delivered. - mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, true); - expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, true); } EventThreadTest::~EventThreadTest() { @@ -179,14 +168,12 @@ EventThreadTest::~EventThreadTest() { EXPECT_TRUE(mVSyncCallbackUnregisterRecorder.waitForCall().has_value()); } -void EventThreadTest::createThread() { +void EventThreadTest::setupEventThread(std::chrono::nanoseconds vsyncPeriod) { const auto throttleVsync = [&](nsecs_t expectedVsyncTimestamp, uid_t uid) { mThrottleVsyncCallRecorder.getInvocable()(expectedVsyncTimestamp, uid); return (uid == mThrottledConnectionUid); }; - const auto getVsyncPeriod = [](uid_t uid) { - return VSYNC_PERIOD.count(); - }; + const auto getVsyncPeriod = [vsyncPeriod](uid_t uid) { return vsyncPeriod.count(); }; mTokenManager = std::make_unique(); mThread = std::make_unique("EventThreadTest", mVsyncSchedule, @@ -195,6 +182,18 @@ void EventThreadTest::createThread() { // EventThread should register itself as VSyncSource callback. EXPECT_TRUE(mVSyncCallbackRegisterRecorder.waitForCall().has_value()); + + mConnection = + createConnection(mConnectionEventCallRecorder, + gui::ISurfaceComposer::EventRegistration::modeChanged | + gui::ISurfaceComposer::EventRegistration::frameRateOverride); + mThrottledConnection = createConnection(mThrottledConnectionEventCallRecorder, + gui::ISurfaceComposer::EventRegistration::modeChanged, + mThrottledConnectionUid); + + // A display must be connected for VSYNC events to be delivered. + mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, true); + expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, true); } sp EventThreadTest::createConnection( @@ -259,7 +258,7 @@ void EventThreadTest::expectVsyncEventFrameTimelinesCorrect( ASSERT_TRUE(args.has_value()) << " did not receive an event for timestamp " << expectedTimestamp; const auto& event = std::get<0>(args.value()); - for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) { + for (int i = 0; i < event.vsync.vsyncData.frameTimelinesLength; i++) { auto prediction = mTokenManager->getPredictionsForToken( event.vsync.vsyncData.frameTimelines[i].vsyncId); EXPECT_TRUE(prediction.has_value()); @@ -293,6 +292,21 @@ void EventThreadTest::expectVsyncEventFrameTimelinesCorrect( } } +void EventThreadTest::expectVsyncEventDataFrameTimelinesValidLength( + VsyncEventData vsyncEventData, std::chrono::nanoseconds vsyncPeriod) { + float nonPreferredTimelinesAmount = + scheduler::VsyncConfig::kEarlyLatchMaxThreshold / vsyncPeriod; + EXPECT_LE(vsyncEventData.frameTimelinesLength, nonPreferredTimelinesAmount + 1) + << "Amount of non-preferred frame timelines too many;" + << " expected presentation time will be over threshold"; + EXPECT_LT(nonPreferredTimelinesAmount, VsyncEventData::kFrameTimelinesLength) + << "Amount of non-preferred frame timelines should be less than max capacity"; + EXPECT_GT(static_cast(vsyncEventData.frameTimelinesLength), 0) + << "Frame timelines length should be greater than 0"; + EXPECT_LT(vsyncEventData.preferredFrameTimelineIndex, vsyncEventData.frameTimelinesLength) + << "Preferred frame timeline index should be less than frame timelines length"; +} + void EventThreadTest::expectHotplugEventReceivedByConnection(PhysicalDisplayId expectedDisplayId, bool expectedConnected) { auto args = mConnectionEventCallRecorder.waitForCall(); @@ -343,6 +357,8 @@ using namespace testing; */ TEST_F(EventThreadTest, canCreateAndDestroyThreadWithNoEventsSent) { + setupEventThread(VSYNC_PERIOD); + EXPECT_FALSE(mVSyncCallbackRegisterRecorder.waitForCall(0us).has_value()); EXPECT_FALSE(mVSyncCallbackScheduleRecorder.waitForCall(0us).has_value()); EXPECT_FALSE(mVSyncCallbackUpdateRecorder.waitForCall(0us).has_value()); @@ -352,6 +368,8 @@ TEST_F(EventThreadTest, canCreateAndDestroyThreadWithNoEventsSent) { } TEST_F(EventThreadTest, vsyncRequestIsIgnoredIfDisplayIsDisconnected) { + setupEventThread(VSYNC_PERIOD); + mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, false); expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, false); @@ -363,6 +381,8 @@ TEST_F(EventThreadTest, vsyncRequestIsIgnoredIfDisplayIsDisconnected) { } TEST_F(EventThreadTest, requestNextVsyncPostsASingleVSyncEventToTheConnection) { + setupEventThread(VSYNC_PERIOD); + // Signal that we want the next vsync event to be posted to the connection mThread->requestNextVsync(mConnection); @@ -394,6 +414,8 @@ TEST_F(EventThreadTest, requestNextVsyncPostsASingleVSyncEventToTheConnection) { } TEST_F(EventThreadTest, requestNextVsyncEventFrameTimelinesCorrect) { + setupEventThread(VSYNC_PERIOD); + // Signal that we want the next vsync event to be posted to the connection mThread->requestNextVsync(mConnection); @@ -405,7 +427,34 @@ TEST_F(EventThreadTest, requestNextVsyncEventFrameTimelinesCorrect) { expectVsyncEventFrameTimelinesCorrect(123, {-1, 789, 456}); } +TEST_F(EventThreadTest, requestNextVsyncEventFrameTimelinesValidLength) { + // The VsyncEventData should not have kFrameTimelinesLength amount of valid frame timelines, due + // to longer vsync period and kEarlyLatchMaxThreshold. + // Use length-2 to avoid decimal truncation (e.g. 60Hz has 16.6... ms vsync period). + std::chrono::nanoseconds vsyncPeriod(scheduler::VsyncConfig::kEarlyLatchMaxThreshold / + (VsyncEventData::kFrameTimelinesLength - 2)); + setupEventThread(vsyncPeriod); + + // Signal that we want the next vsync event to be posted to the connection + mThread->requestNextVsync(mConnection); + + expectVSyncCallbackScheduleReceived(true); + + // Use the received callback to signal a vsync event. + // The throttler should receive the event, as well as the connection. + nsecs_t expectedTimestamp = 123; + onVSyncEvent(expectedTimestamp, 456, 789); + + auto args = mConnectionEventCallRecorder.waitForCall(); + ASSERT_TRUE(args.has_value()) << " did not receive an event for timestamp " + << expectedTimestamp; + const VsyncEventData vsyncEventData = std::get<0>(args.value()).vsync.vsyncData; + expectVsyncEventDataFrameTimelinesValidLength(vsyncEventData, vsyncPeriod); +} + TEST_F(EventThreadTest, getLatestVsyncEventData) { + setupEventThread(VSYNC_PERIOD); + const nsecs_t now = systemTime(); const nsecs_t preferredExpectedPresentationTime = now + 20000000; const nsecs_t preferredDeadline = preferredExpectedPresentationTime - kReadyDuration.count(); @@ -420,9 +469,10 @@ TEST_F(EventThreadTest, getLatestVsyncEventData) { // Check EventThread immediately requested a resync. EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value()); + expectVsyncEventDataFrameTimelinesValidLength(vsyncEventData, VSYNC_PERIOD); EXPECT_GT(vsyncEventData.frameTimelines[0].deadlineTimestamp, now) << "Deadline timestamp should be greater than frame time"; - for (size_t i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) { + for (size_t i = 0; i < vsyncEventData.frameTimelinesLength; i++) { auto prediction = mTokenManager->getPredictionsForToken(vsyncEventData.frameTimelines[i].vsyncId); EXPECT_TRUE(prediction.has_value()); @@ -458,6 +508,8 @@ TEST_F(EventThreadTest, getLatestVsyncEventData) { } TEST_F(EventThreadTest, setVsyncRateZeroPostsNoVSyncEventsToThatConnection) { + setupEventThread(VSYNC_PERIOD); + // Create a first connection, register it, and request a vsync rate of zero. ConnectionEventRecorder firstConnectionEventRecorder{0}; sp firstConnection = createConnection(firstConnectionEventRecorder); @@ -485,6 +537,8 @@ TEST_F(EventThreadTest, setVsyncRateZeroPostsNoVSyncEventsToThatConnection) { } TEST_F(EventThreadTest, setVsyncRateOnePostsAllEventsToThatConnection) { + setupEventThread(VSYNC_PERIOD); + mThread->setVsyncRate(1, mConnection); // EventThread should enable vsync callbacks. @@ -508,6 +562,8 @@ TEST_F(EventThreadTest, setVsyncRateOnePostsAllEventsToThatConnection) { } TEST_F(EventThreadTest, setVsyncRateTwoPostsEveryOtherEventToThatConnection) { + setupEventThread(VSYNC_PERIOD); + mThread->setVsyncRate(2, mConnection); // EventThread should enable vsync callbacks. @@ -534,6 +590,8 @@ TEST_F(EventThreadTest, setVsyncRateTwoPostsEveryOtherEventToThatConnection) { } TEST_F(EventThreadTest, connectionsRemovedIfInstanceDestroyed) { + setupEventThread(VSYNC_PERIOD); + mThread->setVsyncRate(1, mConnection); // EventThread should enable vsync callbacks. @@ -551,6 +609,8 @@ TEST_F(EventThreadTest, connectionsRemovedIfInstanceDestroyed) { } TEST_F(EventThreadTest, connectionsRemovedIfEventDeliveryError) { + setupEventThread(VSYNC_PERIOD); + ConnectionEventRecorder errorConnectionEventRecorder{NO_MEMORY}; sp errorConnection = createConnection(errorConnectionEventRecorder); mThread->setVsyncRate(1, errorConnection); @@ -575,6 +635,8 @@ TEST_F(EventThreadTest, connectionsRemovedIfEventDeliveryError) { } TEST_F(EventThreadTest, tracksEventConnections) { + setupEventThread(VSYNC_PERIOD); + EXPECT_EQ(2, mThread->getEventThreadConnectionCount()); ConnectionEventRecorder errorConnectionEventRecorder{NO_MEMORY}; sp errorConnection = createConnection(errorConnectionEventRecorder); @@ -598,6 +660,8 @@ TEST_F(EventThreadTest, tracksEventConnections) { } TEST_F(EventThreadTest, eventsDroppedIfNonfatalEventDeliveryError) { + setupEventThread(VSYNC_PERIOD); + ConnectionEventRecorder errorConnectionEventRecorder{WOULD_BLOCK}; sp errorConnection = createConnection(errorConnectionEventRecorder); mThread->setVsyncRate(1, errorConnection); @@ -622,31 +686,43 @@ TEST_F(EventThreadTest, eventsDroppedIfNonfatalEventDeliveryError) { } TEST_F(EventThreadTest, setPhaseOffsetForwardsToVSyncSource) { + setupEventThread(VSYNC_PERIOD); + mThread->setDuration(321ns, 456ns); expectVSyncSetDurationCallReceived(321ns, 456ns); } TEST_F(EventThreadTest, postHotplugInternalDisconnect) { + setupEventThread(VSYNC_PERIOD); + mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, false); expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, false); } TEST_F(EventThreadTest, postHotplugInternalConnect) { + setupEventThread(VSYNC_PERIOD); + mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, true); expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, true); } TEST_F(EventThreadTest, postHotplugExternalDisconnect) { + setupEventThread(VSYNC_PERIOD); + mThread->onHotplugReceived(EXTERNAL_DISPLAY_ID, false); expectHotplugEventReceivedByConnection(EXTERNAL_DISPLAY_ID, false); } TEST_F(EventThreadTest, postHotplugExternalConnect) { + setupEventThread(VSYNC_PERIOD); + mThread->onHotplugReceived(EXTERNAL_DISPLAY_ID, true); expectHotplugEventReceivedByConnection(EXTERNAL_DISPLAY_ID, true); } TEST_F(EventThreadTest, postConfigChangedPrimary) { + setupEventThread(VSYNC_PERIOD); + const auto mode = DisplayMode::Builder(hal::HWConfigId(0)) .setPhysicalDisplayId(INTERNAL_DISPLAY_ID) .setId(DisplayModeId(7)) @@ -659,6 +735,8 @@ TEST_F(EventThreadTest, postConfigChangedPrimary) { } TEST_F(EventThreadTest, postConfigChangedExternal) { + setupEventThread(VSYNC_PERIOD); + const auto mode = DisplayMode::Builder(hal::HWConfigId(0)) .setPhysicalDisplayId(EXTERNAL_DISPLAY_ID) .setId(DisplayModeId(5)) @@ -671,6 +749,8 @@ TEST_F(EventThreadTest, postConfigChangedExternal) { } TEST_F(EventThreadTest, postConfigChangedPrimary64bit) { + setupEventThread(VSYNC_PERIOD); + const auto mode = DisplayMode::Builder(hal::HWConfigId(0)) .setPhysicalDisplayId(DISPLAY_ID_64BIT) .setId(DisplayModeId(7)) @@ -682,6 +762,8 @@ TEST_F(EventThreadTest, postConfigChangedPrimary64bit) { } TEST_F(EventThreadTest, suppressConfigChanged) { + setupEventThread(VSYNC_PERIOD); + ConnectionEventRecorder suppressConnectionEventRecorder{0}; sp suppressConnection = createConnection(suppressConnectionEventRecorder); @@ -701,6 +783,8 @@ TEST_F(EventThreadTest, suppressConfigChanged) { } TEST_F(EventThreadTest, postUidFrameRateMapping) { + setupEventThread(VSYNC_PERIOD); + const std::vector overrides = { {.uid = 1, .frameRateHz = 20}, {.uid = 3, .frameRateHz = 40}, @@ -712,6 +796,8 @@ TEST_F(EventThreadTest, postUidFrameRateMapping) { } TEST_F(EventThreadTest, suppressUidFrameRateMapping) { + setupEventThread(VSYNC_PERIOD); + const std::vector overrides = { {.uid = 1, .frameRateHz = 20}, {.uid = 3, .frameRateHz = 40}, @@ -730,6 +816,8 @@ TEST_F(EventThreadTest, suppressUidFrameRateMapping) { } TEST_F(EventThreadTest, requestNextVsyncWithThrottleVsyncDoesntPostVSync) { + setupEventThread(VSYNC_PERIOD); + // Signal that we want the next vsync event to be posted to the throttled connection mThread->requestNextVsync(mThrottledConnection); -- GitLab From 40aef42d44e1f0465b8dc17e3ecd8a1aee074236 Mon Sep 17 00:00:00 2001 From: Rachel Lee Date: Tue, 25 Apr 2023 14:35:47 -0700 Subject: [PATCH 1204/1310] Rename const to kFrameTimelinesCapacity From `kFrameTimelinesLength`. Bug: 270612751 Test: build Change-Id: I83e61a19254ab4a63479a2004124cfd2c082da35 --- libs/gui/VsyncEventData.cpp | 4 ++-- libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp | 2 +- libs/gui/include/gui/VsyncEventData.h | 6 +++--- libs/gui/tests/DisplayEventStructLayout_test.cpp | 8 ++++---- libs/gui/tests/VsyncEventData_test.cpp | 2 +- libs/nativedisplay/AChoreographer.cpp | 6 +++--- services/surfaceflinger/Scheduler/EventThread.cpp | 4 ++-- .../surfaceflinger/tests/DisplayEventReceiver_test.cpp | 2 +- .../surfaceflinger/tests/unittests/EventThreadTest.cpp | 10 +++++----- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/libs/gui/VsyncEventData.cpp b/libs/gui/VsyncEventData.cpp index 91fc9c0ffe..8e00c2fe32 100644 --- a/libs/gui/VsyncEventData.cpp +++ b/libs/gui/VsyncEventData.cpp @@ -23,8 +23,8 @@ namespace android::gui { -static_assert(VsyncEventData::kFrameTimelinesLength == 7, - "Must update value in DisplayEventReceiver.java#FRAME_TIMELINES_LENGTH (and here)"); +static_assert(VsyncEventData::kFrameTimelinesCapacity == 7, + "Must update value in DisplayEventReceiver.java#FRAME_TIMELINES_CAPACITY (and here)"); int64_t VsyncEventData::preferredVsyncId() const { return frameTimelines[preferredFrameTimelineIndex].vsyncId; diff --git a/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp b/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp index 6d5ae49635..6e4f074825 100644 --- a/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp +++ b/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp @@ -51,7 +51,7 @@ DisplayEventReceiver::Event buildDisplayEvent(FuzzedDataProvider* fdp, uint32_t event.vsync.count = fdp->ConsumeIntegral(); event.vsync.vsyncData.frameInterval = fdp->ConsumeIntegral(); event.vsync.vsyncData.preferredFrameTimelineIndex = fdp->ConsumeIntegral(); - for (size_t idx = 0; idx < gui::VsyncEventData::kFrameTimelinesLength; ++idx) { + for (size_t idx = 0; idx < gui::VsyncEventData::kFrameTimelinesCapacity; ++idx) { event.vsync.vsyncData.frameTimelines[idx].vsyncId = fdp->ConsumeIntegral(); event.vsync.vsyncData.frameTimelines[idx].deadlineTimestamp = fdp->ConsumeIntegral(); diff --git a/libs/gui/include/gui/VsyncEventData.h b/libs/gui/include/gui/VsyncEventData.h index 9ddb7cd82b..b40a84099c 100644 --- a/libs/gui/include/gui/VsyncEventData.h +++ b/libs/gui/include/gui/VsyncEventData.h @@ -25,7 +25,7 @@ namespace android::gui { // DisplayEventReceiver::Event union. struct VsyncEventData { // Max capacity of frame timelines is arbitrarily set to be reasonable. - static constexpr int64_t kFrameTimelinesLength = 7; + static constexpr int64_t kFrameTimelinesCapacity = 7; // The current frame interval in ns when this frame was scheduled. int64_t frameInterval; @@ -33,7 +33,7 @@ struct VsyncEventData { // Index into the frameTimelines that represents the platform's preferred frame timeline. uint32_t preferredFrameTimelineIndex; - // Size of frame timelines provided by the platform; max is kFrameTimelinesLength. + // Size of frame timelines provided by the platform; max is kFrameTimelinesCapacity. uint32_t frameTimelinesLength; struct alignas(8) FrameTimeline { @@ -48,7 +48,7 @@ struct VsyncEventData { // The anticipated Vsync presentation time in nanos. int64_t expectedPresentationTime; - } frameTimelines[kFrameTimelinesLength]; // Sorted possible frame timelines. + } frameTimelines[kFrameTimelinesCapacity]; // Sorted possible frame timelines. // Gets the preferred frame timeline's vsync ID. int64_t preferredVsyncId() const; diff --git a/libs/gui/tests/DisplayEventStructLayout_test.cpp b/libs/gui/tests/DisplayEventStructLayout_test.cpp index b7b4ab605a..3949d70aac 100644 --- a/libs/gui/tests/DisplayEventStructLayout_test.cpp +++ b/libs/gui/tests/DisplayEventStructLayout_test.cpp @@ -45,16 +45,16 @@ TEST(DisplayEventStructLayoutTest, TestEventAlignment) { // 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 + - (VsyncEventData::kFrameTimelinesLength - 1) * /* Size of FrameTimeline */ 24; + (VsyncEventData::kFrameTimelinesCapacity - 1) * /* Size of FrameTimeline */ 24; CHECK_OFFSET(DisplayEventReceiver::Event::VSync, - vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesLength - 1].vsyncId, + vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesCapacity - 1].vsyncId, lastFrameTimelineOffset); CHECK_OFFSET(DisplayEventReceiver::Event::VSync, - vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesLength - 1] + vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesCapacity - 1] .deadlineTimestamp, lastFrameTimelineOffset + 8); CHECK_OFFSET(DisplayEventReceiver::Event::VSync, - vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesLength - 1] + vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesCapacity - 1] .expectedPresentationTime, lastFrameTimelineOffset + 16); diff --git a/libs/gui/tests/VsyncEventData_test.cpp b/libs/gui/tests/VsyncEventData_test.cpp index 6e039ee6e7..a2138f2144 100644 --- a/libs/gui/tests/VsyncEventData_test.cpp +++ b/libs/gui/tests/VsyncEventData_test.cpp @@ -47,7 +47,7 @@ TEST(ParcelableVsyncEventData, Parcelling) { ASSERT_EQ(data.vsync.frameInterval, data2.vsync.frameInterval); ASSERT_EQ(data.vsync.preferredFrameTimelineIndex, data2.vsync.preferredFrameTimelineIndex); ASSERT_EQ(data.vsync.frameTimelinesLength, data2.vsync.frameTimelinesLength); - for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) { + for (int i = 0; i < VsyncEventData::kFrameTimelinesCapacity; i++) { ASSERT_EQ(data.vsync.frameTimelines[i].vsyncId, data2.vsync.frameTimelines[i].vsyncId); ASSERT_EQ(data.vsync.frameTimelines[i].deadlineTimestamp, data2.vsync.frameTimelines[i].deadlineTimestamp); diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp index 80b0041923..8f005a56f8 100644 --- a/libs/nativedisplay/AChoreographer.cpp +++ b/libs/nativedisplay/AChoreographer.cpp @@ -213,7 +213,7 @@ AVsyncId AChoreographerFrameCallbackData_getFrameTimelineVsyncId( AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data); LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(), "Data is only valid in callback"); - LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesLength, "Index out of bounds"); + LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesCapacity, "Index out of bounds"); return frameCallbackData->vsyncEventData.frameTimelines[index].vsyncId; } int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos( @@ -222,7 +222,7 @@ int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTime AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data); LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(), "Data is only valid in callback"); - LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesLength, "Index out of bounds"); + LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesCapacity, "Index out of bounds"); return frameCallbackData->vsyncEventData.frameTimelines[index].expectedPresentationTime; } int64_t AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos( @@ -231,7 +231,7 @@ int64_t AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos( AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data); LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(), "Data is only valid in callback"); - LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesLength, "Index out of bounds"); + LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesCapacity, "Index out of bounds"); return frameCallbackData->vsyncEventData.frameTimelines[index].deadlineTimestamp; } diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index f79688dfe0..7b59ca5203 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -600,8 +600,8 @@ void EventThread::generateFrameTimeline(VsyncEventData& outVsyncEventData, nsecs nsecs_t preferredDeadlineTimestamp) const { uint32_t currentIndex = 0; // Add 1 to ensure the preferredFrameTimelineIndex entry (when multiplier == 0) is included. - for (int64_t multiplier = -VsyncEventData::kFrameTimelinesLength + 1; - currentIndex < VsyncEventData::kFrameTimelinesLength; multiplier++) { + for (int64_t multiplier = -VsyncEventData::kFrameTimelinesCapacity + 1; + currentIndex < VsyncEventData::kFrameTimelinesCapacity; multiplier++) { nsecs_t deadlineTimestamp = preferredDeadlineTimestamp + multiplier * frameInterval; // Valid possible frame timelines must have future values, so find a later frame timeline. if (deadlineTimestamp <= timestamp) { diff --git a/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp b/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp index 761ac8c538..4c26017241 100644 --- a/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp +++ b/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp @@ -36,7 +36,7 @@ TEST_F(DisplayEventReceiverTest, getLatestVsyncEventData) { EXPECT_GT(static_cast(vsyncEventData.frameTimelinesLength), 0) << "Frame timelines length should be greater than 0"; EXPECT_LE(static_cast(vsyncEventData.frameTimelinesLength), - VsyncEventData::kFrameTimelinesLength) + VsyncEventData::kFrameTimelinesCapacity) << "Frame timelines length should not exceed max capacity"; EXPECT_GT(vsyncEventData.frameTimelines[0].deadlineTimestamp, now) << "Deadline timestamp should be greater than frame time"; diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp index 6debbaae2d..5fed9b45a6 100644 --- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp +++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp @@ -299,7 +299,7 @@ void EventThreadTest::expectVsyncEventDataFrameTimelinesValidLength( EXPECT_LE(vsyncEventData.frameTimelinesLength, nonPreferredTimelinesAmount + 1) << "Amount of non-preferred frame timelines too many;" << " expected presentation time will be over threshold"; - EXPECT_LT(nonPreferredTimelinesAmount, VsyncEventData::kFrameTimelinesLength) + EXPECT_LT(nonPreferredTimelinesAmount, VsyncEventData::kFrameTimelinesCapacity) << "Amount of non-preferred frame timelines should be less than max capacity"; EXPECT_GT(static_cast(vsyncEventData.frameTimelinesLength), 0) << "Frame timelines length should be greater than 0"; @@ -428,11 +428,11 @@ TEST_F(EventThreadTest, requestNextVsyncEventFrameTimelinesCorrect) { } TEST_F(EventThreadTest, requestNextVsyncEventFrameTimelinesValidLength) { - // The VsyncEventData should not have kFrameTimelinesLength amount of valid frame timelines, due - // to longer vsync period and kEarlyLatchMaxThreshold. - // Use length-2 to avoid decimal truncation (e.g. 60Hz has 16.6... ms vsync period). + // The VsyncEventData should not have kFrameTimelinesCapacity amount of valid frame timelines, + // due to longer vsync period and kEarlyLatchMaxThreshold. Use length-2 to avoid decimal + // truncation (e.g. 60Hz has 16.6... ms vsync period). std::chrono::nanoseconds vsyncPeriod(scheduler::VsyncConfig::kEarlyLatchMaxThreshold / - (VsyncEventData::kFrameTimelinesLength - 2)); + (VsyncEventData::kFrameTimelinesCapacity - 2)); setupEventThread(vsyncPeriod); // Signal that we want the next vsync event to be posted to the connection -- GitLab From a13a7622f05aafcdab5d8769ee0946b37afaf4bf Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Tue, 2 May 2023 23:56:58 +0000 Subject: [PATCH 1205/1310] libbinder: keep uninit descriptor empty Java users creating custom binder interfaces may rely on empty descriptors. Bug: 278655444 Test: binderAllocationLimits Change-Id: I8bba68a3906517dff060853fc6224b5b5d7d37ab Merged-In: I8bba68a3906517dff060853fc6224b5b5d7d37ab --- libs/binder/BpBinder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp index 53852d88ed..8d9955dd6b 100644 --- a/libs/binder/BpBinder.cpp +++ b/libs/binder/BpBinder.cpp @@ -47,7 +47,7 @@ std::atomic_bool BpBinder::sCountByUidEnabled(false); binder_proxy_limit_callback BpBinder::sLimitCallback; bool BpBinder::sBinderProxyThrottleCreate = false; -static StaticString16 kDescriptorUninit(u""); +static StaticString16 kDescriptorUninit(u""); // Arbitrarily high value that probably distinguishes a bad behaving app uint32_t BpBinder::sBinderProxyCountHighWatermark = 2500; -- GitLab From b398162b7619750bb611185a03cbd56279fc1cf6 Mon Sep 17 00:00:00 2001 From: Josep del Rio Date: Tue, 18 Apr 2023 15:49:45 +0000 Subject: [PATCH 1206/1310] Trigger system key user activity during keyguard This CL changes the user activity handling in input so system keys will always trigger user activity bypassing the DISABLE_USER_ACTIVITY flag that is used in the keyguard. With the current information at our disposal, we believe that the flag in question was added to avoid accidental screen touches from waking up the device and consume the battery. However, this concern does not exist with system keys as accidental presses should be a lot rarer. Bug: 272052147 Test: Flashed and tried using system shortcuts Change-Id: I89c3402a6ae4c2c75927e548ff38c871eb60479c --- .../dispatcher/InputDispatcher.cpp | 42 ++++-- .../tests/InputDispatcher_test.cpp | 130 +++++++++++++++++- 2 files changed, 162 insertions(+), 10 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 80a70f2bf4..12ac77cc5a 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -498,14 +498,14 @@ bool isConnectionResponsive(const Connection& connection) { // Returns true if the event type passed as argument represents a user activity. bool isUserActivityEvent(const EventEntry& eventEntry) { switch (eventEntry.type) { + case EventEntry::Type::CONFIGURATION_CHANGED: + case EventEntry::Type::DEVICE_RESET: + case EventEntry::Type::DRAG: case EventEntry::Type::FOCUS: case EventEntry::Type::POINTER_CAPTURE_CHANGED: - case EventEntry::Type::DRAG: - case EventEntry::Type::TOUCH_MODE_CHANGED: case EventEntry::Type::SENSOR: - case EventEntry::Type::CONFIGURATION_CHANGED: + case EventEntry::Type::TOUCH_MODE_CHANGED: return false; - case EventEntry::Type::DEVICE_RESET: case EventEntry::Type::KEY: case EventEntry::Type::MOTION: return true; @@ -1684,6 +1684,8 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptrinterceptKeyResult = KeyEntry::InterceptKeyResult::CONTINUE; @@ -1700,6 +1702,8 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptrreportDroppedKey(entry->id); + // Poke user activity for undispatched keys + pokeUserActivityLocked(*entry); return true; } @@ -3010,13 +3014,11 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) { } int32_t displayId = getTargetDisplayId(eventEntry); sp focusedWindowHandle = getFocusedWindowHandleLocked(displayId); + const WindowInfo* windowDisablingUserActivityInfo = nullptr; if (focusedWindowHandle != nullptr) { const WindowInfo* info = focusedWindowHandle->getInfo(); if (info->inputConfig.test(WindowInfo::InputConfig::DISABLE_USER_ACTIVITY)) { - if (DEBUG_DISPATCH_CYCLE) { - ALOGD("Not poking user activity: disabled by window '%s'.", info->name.c_str()); - } - return; + windowDisablingUserActivityInfo = info; } } @@ -3027,7 +3029,13 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) { if (motionEntry.action == AMOTION_EVENT_ACTION_CANCEL) { return; } - + if (windowDisablingUserActivityInfo != nullptr) { + if (DEBUG_DISPATCH_CYCLE) { + ALOGD("Not poking user activity: disabled by window '%s'.", + windowDisablingUserActivityInfo->name.c_str()); + } + return; + } if (MotionEvent::isTouchEvent(motionEntry.source, motionEntry.action)) { eventType = USER_ACTIVITY_EVENT_TOUCH; } @@ -3038,6 +3046,22 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) { if (keyEntry.flags & AKEY_EVENT_FLAG_CANCELED) { return; } + // If the key code is unknown, we don't consider it user activity + if (keyEntry.keyCode == AKEYCODE_UNKNOWN) { + return; + } + // Don't inhibit events that were intercepted or are not passed to + // the apps, like system shortcuts + if (windowDisablingUserActivityInfo != nullptr && + keyEntry.interceptKeyResult != KeyEntry::InterceptKeyResult::SKIP && + keyEntry.policyFlags & POLICY_FLAG_PASS_TO_USER) { + if (DEBUG_DISPATCH_CYCLE) { + ALOGD("Not poking user activity: disabled by window '%s'.", + windowDisablingUserActivityInfo->name.c_str()); + } + return; + } + eventType = USER_ACTIVITY_EVENT_BUTTON; break; } diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index f92b3a3c05..d419e52587 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -404,6 +404,16 @@ public: mInterceptKeyTimeout = timeout; } + void assertUserActivityPoked() { + std::scoped_lock lock(mLock); + ASSERT_TRUE(mPokedUserActivity) << "Expected user activity to have been poked"; + } + + void assertUserActivityNotPoked() { + std::scoped_lock lock(mLock); + ASSERT_FALSE(mPokedUserActivity) << "Expected user activity not to have been poked"; + } + private: std::mutex mLock; std::unique_ptr mFilteredEvent GUARDED_BY(mLock); @@ -425,6 +435,7 @@ private: sp mDropTargetWindowToken GUARDED_BY(mLock); bool mNotifyDropWindowWasCalled GUARDED_BY(mLock) = false; + bool mPokedUserActivity GUARDED_BY(mLock) = false; std::chrono::milliseconds mInterceptKeyTimeout = 0ms; @@ -578,7 +589,10 @@ private: mLastNotifySwitch = NotifySwitchArgs(/*id=*/1, when, policyFlags, switchValues, switchMask); } - void pokeUserActivity(nsecs_t, int32_t, int32_t) override {} + void pokeUserActivity(nsecs_t, int32_t, int32_t) override { + std::scoped_lock lock(mLock); + mPokedUserActivity = true; + } void onPointerDownOutsideFocus(const sp& newToken) override { std::scoped_lock lock(mLock); @@ -1190,6 +1204,10 @@ public: mInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, noInputChannel); } + void setDisableUserActivity(bool disableUserActivity) { + mInfo.setInputConfig(WindowInfo::InputConfig::DISABLE_USER_ACTIVITY, disableUserActivity); + } + void setAlpha(float alpha) { mInfo.alpha = alpha; } void setTouchOcclusionMode(TouchOcclusionMode mode) { mInfo.touchOcclusionMode = mode; } @@ -1742,6 +1760,28 @@ static NotifyKeyArgs generateKeyArgs(int32_t action, int32_t displayId = ADISPLA return args; } +static NotifyKeyArgs generateSystemShortcutArgs(int32_t action, + int32_t displayId = ADISPLAY_ID_NONE) { + nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); + // Define a valid key event. + NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD, + displayId, 0, action, /* flags */ 0, AKEYCODE_C, KEY_C, AMETA_META_ON, + currentTime); + + return args; +} + +static NotifyKeyArgs generateAssistantKeyArgs(int32_t action, + int32_t displayId = ADISPLAY_ID_NONE) { + nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); + // Define a valid key event. + NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD, + displayId, 0, action, /* flags */ 0, AKEYCODE_ASSIST, KEY_ASSISTANT, + AMETA_NONE, currentTime); + + return args; +} + [[nodiscard]] static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source, int32_t displayId, const std::vector& points) { @@ -4531,6 +4571,94 @@ TEST_F(InputDispatcherTest, FocusedWindow_ReceivesFocusEventAndKeyEvent) { // Window should receive key down event. window->consumeKeyDown(ADISPLAY_ID_DEFAULT); + + // Should have poked user activity + mFakePolicy->assertUserActivityPoked(); +} + +TEST_F(InputDispatcherTest, FocusedWindow_DisableUserActivity) { + std::shared_ptr application = std::make_shared(); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); + + window->setDisableUserActivity(true); + window->setFocusable(true); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + setFocusedWindow(window); + + window->consumeFocusEvent(true); + + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + + // Window should receive key down event. + window->consumeKeyDown(ADISPLAY_ID_DEFAULT); + + // Should have poked user activity + mFakePolicy->assertUserActivityNotPoked(); +} + +TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveSystemShortcut) { + std::shared_ptr application = std::make_shared(); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); + + window->setFocusable(true); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + setFocusedWindow(window); + + window->consumeFocusEvent(true); + + mDispatcher->notifyKey(generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->waitForIdle(); + + // System key is not passed down + window->assertNoEvents(); + + // Should have poked user activity + mFakePolicy->assertUserActivityPoked(); +} + +TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveAssistantKey) { + std::shared_ptr application = std::make_shared(); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); + + window->setFocusable(true); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + setFocusedWindow(window); + + window->consumeFocusEvent(true); + + mDispatcher->notifyKey(generateAssistantKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->waitForIdle(); + + // System key is not passed down + window->assertNoEvents(); + + // Should have poked user activity + mFakePolicy->assertUserActivityPoked(); +} + +TEST_F(InputDispatcherTest, FocusedWindow_SystemKeyIgnoresDisableUserActivity) { + std::shared_ptr application = std::make_shared(); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); + + window->setDisableUserActivity(true); + window->setFocusable(true); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + setFocusedWindow(window); + + window->consumeFocusEvent(true); + + mDispatcher->notifyKey(generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->waitForIdle(); + + // System key is not passed down + window->assertNoEvents(); + + // Should have poked user activity + mFakePolicy->assertUserActivityPoked(); } TEST_F(InputDispatcherTest, UnfocusedWindow_DoesNotReceiveFocusEventOrKeyEvent) { -- GitLab From 0686f0c4cb814a47ee4447bf0b0d0c522aac8344 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 2 May 2023 11:56:15 -0700 Subject: [PATCH 1207/1310] Reset touch state when device resets At any point in time, an input device may be reset. We already generate cancel events to handle this situation. However, that's not sufficient. In this CL, touch state is cleared of all pointers for the device that is getting reset. Bug: 273376858 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests --gtest_filter="*NotifyDeviceResetCancelsHoveringStream*" Change-Id: I66da4f3eec125a6b260ed1478bca00164bb40c9e --- .../dispatcher/InputDispatcher.cpp | 5 ++++ .../inputflinger/dispatcher/TouchState.cpp | 12 ++++++++++ services/inputflinger/dispatcher/TouchState.h | 2 ++ .../inputflinger/dispatcher/TouchedWindow.cpp | 8 +++++++ .../inputflinger/dispatcher/TouchedWindow.h | 3 +++ .../tests/InputDispatcher_test.cpp | 24 +++++++++++++++++++ 6 files changed, 54 insertions(+) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index ed5cd100fc..e4f367dd5f 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -1461,6 +1461,11 @@ bool InputDispatcher::dispatchDeviceResetLocked(nsecs_t currentTime, CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, "device was reset"); options.deviceId = entry.deviceId; synthesizeCancelationEventsForAllConnectionsLocked(options); + + // Remove all active pointers from this device + for (auto& [_, touchState] : mTouchStatesByDisplay) { + touchState.removeAllPointersForDevice(entry.deviceId); + } return true; } diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index 9c443f14cf..0a61d48165 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -242,6 +242,18 @@ void TouchState::removeHoveringPointer(int32_t hoveringDeviceId, int32_t hoverin clearWindowsWithoutPointers(); } +void TouchState::removeAllPointersForDevice(int32_t removedDeviceId) { + for (TouchedWindow& window : windows) { + window.removeAllHoveringPointersForDevice(removedDeviceId); + } + if (deviceId == removedDeviceId) { + for (TouchedWindow& window : windows) { + window.removeAllTouchingPointers(); + } + } + clearWindowsWithoutPointers(); +} + std::string TouchState::dump() const { std::string out; out += StringPrintf("deviceId=%d, source=%s\n", deviceId, diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index a20080f534..15b840f125 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -54,6 +54,8 @@ struct TouchState { int32_t deviceId, int32_t hoveringPointerId); void removeHoveringPointer(int32_t deviceId, int32_t hoveringPointerId); void clearHoveringPointers(); + + void removeAllPointersForDevice(int32_t removedDeviceId); void removeWindowByToken(const sp& token); void filterNonAsIsTouchWindows(); diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp index 99c4769712..d55d657667 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.cpp +++ b/services/inputflinger/dispatcher/TouchedWindow.cpp @@ -58,6 +58,10 @@ void TouchedWindow::removeTouchingPointer(int32_t pointerId) { } } +void TouchedWindow::removeAllTouchingPointers() { + pointerIds.reset(); +} + void TouchedWindow::removeHoveringPointer(int32_t deviceId, int32_t pointerId) { const auto it = mHoveringPointerIdsByDevice.find(deviceId); if (it == mHoveringPointerIdsByDevice.end()) { @@ -70,6 +74,10 @@ void TouchedWindow::removeHoveringPointer(int32_t deviceId, int32_t pointerId) { } } +void TouchedWindow::removeAllHoveringPointersForDevice(int32_t deviceId) { + mHoveringPointerIdsByDevice.erase(deviceId); +} + std::string TouchedWindow::dump() const { std::string out; std::string hoveringPointers = diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h index aa2e9dd677..43e716973c 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -44,6 +44,9 @@ struct TouchedWindow { void addHoveringPointer(int32_t deviceId, int32_t pointerId); void removeHoveringPointer(int32_t deviceId, int32_t pointerId); void removeTouchingPointer(int32_t pointerId); + + void removeAllTouchingPointers(); + void removeAllHoveringPointersForDevice(int32_t deviceId); void clearHoveringPointers(); std::string dump() const; diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 0741be0a85..147b7d4a83 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -3678,6 +3678,30 @@ TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsMotionStream) { AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); } +TEST_F(InputDispatcherTest, NotifyDeviceResetCancelsHoveringStream) { + std::shared_ptr application = std::make_shared(); + sp window = sp::make(application, mDispatcher, + "Fake Window", ADISPLAY_ID_DEFAULT); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(10).y(10)) + .build()); + + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + + // When device reset happens, that hover stream should be terminated with ACTION_HOVER_EXIT + mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID}); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + + // After the device has been reset, a new hovering stream can be sent to the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(15).y(15)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); +} + TEST_F(InputDispatcherTest, InterceptKeyByPolicy) { std::shared_ptr application = std::make_shared(); sp window = sp::make(application, mDispatcher, -- GitLab From d828f302cbf75fe838f6c96a6b9938dda4ea22a5 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Fri, 28 Apr 2023 17:52:08 -0500 Subject: [PATCH 1208/1310] Add Window Infos state to dumpsys Update SurfaceFlinger and InputDispatcher dumpsys output to include the vsync id and timestamp of Window Infos changes. Bug: 279792237 Test: presubmits Change-Id: I17fba2d09972467cfdd452e5041666ffbeabddc9 --- libs/gui/Android.bp | 3 + libs/gui/WindowInfosListenerReporter.cpp | 9 +-- libs/gui/WindowInfosUpdate.cpp | 72 +++++++++++++++++++ .../gui/android/gui/IWindowInfosListener.aidl | 9 ++- libs/gui/android/gui/WindowInfosUpdate.aidl | 22 ++++++ libs/gui/include/gui/WindowInfosListener.h | 8 +-- .../include/gui/WindowInfosListenerReporter.h | 4 +- libs/gui/include/gui/WindowInfosUpdate.h | 44 ++++++++++++ .../benchmarks/InputDispatcher_benchmarks.cpp | 6 +- .../dispatcher/InputDispatcher.cpp | 29 ++++++-- .../inputflinger/dispatcher/InputDispatcher.h | 14 ++-- .../tests/InputDispatcher_test.cpp | 14 ++-- services/surfaceflinger/SurfaceFlinger.cpp | 33 ++++++++- services/surfaceflinger/SurfaceFlinger.h | 5 +- .../WindowInfosListenerInvoker.cpp | 20 ++++-- .../WindowInfosListenerInvoker.h | 21 +++++- .../fuzzer/surfaceflinger_fuzzers_utils.h | 2 +- .../tests/WindowInfosListener_test.cpp | 6 +- 18 files changed, 269 insertions(+), 52 deletions(-) create mode 100644 libs/gui/WindowInfosUpdate.cpp create mode 100644 libs/gui/android/gui/WindowInfosUpdate.aidl create mode 100644 libs/gui/include/gui/WindowInfosUpdate.h diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index 0a63c1564e..33bb343c9d 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -75,6 +75,7 @@ filegroup { "android/gui/IWindowInfosListener.aidl", "android/gui/IWindowInfosReportedListener.aidl", "android/gui/WindowInfo.aidl", + "android/gui/WindowInfosUpdate.aidl", ], } @@ -90,9 +91,11 @@ cc_library_static { "android/gui/InputApplicationInfo.aidl", "android/gui/IWindowInfosListener.aidl", "android/gui/IWindowInfosReportedListener.aidl", + "android/gui/WindowInfosUpdate.aidl", "android/gui/WindowInfo.aidl", "DisplayInfo.cpp", "WindowInfo.cpp", + "WindowInfosUpdate.cpp", ], shared_libs: [ diff --git a/libs/gui/WindowInfosListenerReporter.cpp b/libs/gui/WindowInfosListenerReporter.cpp index 2b34a0fc47..76e7b6e162 100644 --- a/libs/gui/WindowInfosListenerReporter.cpp +++ b/libs/gui/WindowInfosListenerReporter.cpp @@ -17,6 +17,7 @@ #include #include #include +#include "gui/WindowInfosUpdate.h" namespace android { @@ -84,7 +85,7 @@ status_t WindowInfosListenerReporter::removeWindowInfosListener( } binder::Status WindowInfosListenerReporter::onWindowInfosChanged( - const std::vector& windowInfos, const std::vector& displayInfos, + const gui::WindowInfosUpdate& update, const sp& windowInfosReportedListener) { std::unordered_set, gui::SpHash> windowInfosListeners; @@ -95,12 +96,12 @@ binder::Status WindowInfosListenerReporter::onWindowInfosChanged( windowInfosListeners.insert(listener); } - mLastWindowInfos = windowInfos; - mLastDisplayInfos = displayInfos; + mLastWindowInfos = update.windowInfos; + mLastDisplayInfos = update.displayInfos; } for (auto listener : windowInfosListeners) { - listener->onWindowInfosChanged(windowInfos, displayInfos); + listener->onWindowInfosChanged(update); } if (windowInfosReportedListener) { diff --git a/libs/gui/WindowInfosUpdate.cpp b/libs/gui/WindowInfosUpdate.cpp new file mode 100644 index 0000000000..38ae5ef102 --- /dev/null +++ b/libs/gui/WindowInfosUpdate.cpp @@ -0,0 +1,72 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +namespace android::gui { + +status_t WindowInfosUpdate::readFromParcel(const android::Parcel* parcel) { + if (parcel == nullptr) { + ALOGE("%s: Null parcel", __func__); + return BAD_VALUE; + } + + uint32_t size; + + SAFE_PARCEL(parcel->readUint32, &size); + windowInfos.reserve(size); + for (uint32_t i = 0; i < size; i++) { + windowInfos.push_back({}); + SAFE_PARCEL(windowInfos.back().readFromParcel, parcel); + } + + SAFE_PARCEL(parcel->readUint32, &size); + displayInfos.reserve(size); + for (uint32_t i = 0; i < size; i++) { + displayInfos.push_back({}); + SAFE_PARCEL(displayInfos.back().readFromParcel, parcel); + } + + SAFE_PARCEL(parcel->readInt64, &vsyncId); + SAFE_PARCEL(parcel->readInt64, ×tamp); + + return OK; +} + +status_t WindowInfosUpdate::writeToParcel(android::Parcel* parcel) const { + if (parcel == nullptr) { + ALOGE("%s: Null parcel", __func__); + return BAD_VALUE; + } + + SAFE_PARCEL(parcel->writeUint32, static_cast(windowInfos.size())); + for (auto& windowInfo : windowInfos) { + SAFE_PARCEL(windowInfo.writeToParcel, parcel); + } + + SAFE_PARCEL(parcel->writeUint32, static_cast(displayInfos.size())); + for (auto& displayInfo : displayInfos) { + SAFE_PARCEL(displayInfo.writeToParcel, parcel); + } + + SAFE_PARCEL(parcel->writeInt64, vsyncId); + SAFE_PARCEL(parcel->writeInt64, timestamp); + + return OK; +} + +} // namespace android::gui diff --git a/libs/gui/android/gui/IWindowInfosListener.aidl b/libs/gui/android/gui/IWindowInfosListener.aidl index a5b2762318..400229d99f 100644 --- a/libs/gui/android/gui/IWindowInfosListener.aidl +++ b/libs/gui/android/gui/IWindowInfosListener.aidl @@ -16,12 +16,11 @@ package android.gui; -import android.gui.DisplayInfo; import android.gui.IWindowInfosReportedListener; -import android.gui.WindowInfo; +import android.gui.WindowInfosUpdate; /** @hide */ -oneway interface IWindowInfosListener -{ - void onWindowInfosChanged(in WindowInfo[] windowInfos, in DisplayInfo[] displayInfos, in @nullable IWindowInfosReportedListener windowInfosReportedListener); +oneway interface IWindowInfosListener { + void onWindowInfosChanged( + in WindowInfosUpdate update, in @nullable IWindowInfosReportedListener windowInfosReportedListener); } diff --git a/libs/gui/android/gui/WindowInfosUpdate.aidl b/libs/gui/android/gui/WindowInfosUpdate.aidl new file mode 100644 index 0000000000..0c6109da8f --- /dev/null +++ b/libs/gui/android/gui/WindowInfosUpdate.aidl @@ -0,0 +1,22 @@ +/* +** Copyright 2023, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.gui; + +import android.gui.DisplayInfo; +import android.gui.WindowInfo; + +parcelable WindowInfosUpdate cpp_header "gui/WindowInfosUpdate.h"; diff --git a/libs/gui/include/gui/WindowInfosListener.h b/libs/gui/include/gui/WindowInfosListener.h index a18a498c5e..02c8eb5ef3 100644 --- a/libs/gui/include/gui/WindowInfosListener.h +++ b/libs/gui/include/gui/WindowInfosListener.h @@ -16,15 +16,13 @@ #pragma once -#include -#include +#include #include namespace android::gui { class WindowInfosListener : public virtual RefBase { public: - virtual void onWindowInfosChanged(const std::vector&, - const std::vector&) = 0; + virtual void onWindowInfosChanged(const WindowInfosUpdate& update) = 0; }; -} // namespace android::gui \ No newline at end of file +} // namespace android::gui diff --git a/libs/gui/include/gui/WindowInfosListenerReporter.h b/libs/gui/include/gui/WindowInfosListenerReporter.h index 2754442a95..38cb108912 100644 --- a/libs/gui/include/gui/WindowInfosListenerReporter.h +++ b/libs/gui/include/gui/WindowInfosListenerReporter.h @@ -22,6 +22,7 @@ #include #include #include +#include #include namespace android { @@ -29,8 +30,7 @@ namespace android { class WindowInfosListenerReporter : public gui::BnWindowInfosListener { public: static sp getInstance(); - binder::Status onWindowInfosChanged(const std::vector&, - const std::vector&, + binder::Status onWindowInfosChanged(const gui::WindowInfosUpdate& update, const sp&) override; status_t addWindowInfosListener( const sp& windowInfosListener, diff --git a/libs/gui/include/gui/WindowInfosUpdate.h b/libs/gui/include/gui/WindowInfosUpdate.h new file mode 100644 index 0000000000..2ca59fb497 --- /dev/null +++ b/libs/gui/include/gui/WindowInfosUpdate.h @@ -0,0 +1,44 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +namespace android::gui { + +struct WindowInfosUpdate : public Parcelable { + WindowInfosUpdate() {} + + WindowInfosUpdate(std::vector windowInfos, std::vector displayInfos, + int64_t vsyncId, int64_t timestamp) + : windowInfos(std::move(windowInfos)), + displayInfos(std::move(displayInfos)), + vsyncId(vsyncId), + timestamp(timestamp) {} + + std::vector windowInfos; + std::vector displayInfos; + int64_t vsyncId; + int64_t timestamp; + + status_t writeToParcel(android::Parcel*) const override; + status_t readFromParcel(const android::Parcel*) override; +}; + +} // namespace android::gui diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp index f852001679..becf70a369 100644 --- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp +++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp @@ -343,8 +343,10 @@ static void benchmarkOnWindowInfosChanged(benchmark::State& state) { std::vector displayInfos{info}; for (auto _ : state) { - dispatcher.onWindowInfosChanged(windowInfos, displayInfos); - dispatcher.onWindowInfosChanged(/*windowInfos=*/{}, /*displayInfos=*/{}); + dispatcher.onWindowInfosChanged( + {windowInfos, displayInfos, /*vsyncId=*/0, /*timestamp=*/0}); + dispatcher.onWindowInfosChanged( + {/*windowInfos=*/{}, /*displayInfos=*/{}, /*vsyncId=*/{}, /*timestamp=*/0}); } dispatcher.stop(); } diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 8c08ef25e3..bf5250288a 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -5572,6 +5572,14 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) const { } else { dump += INDENT "Displays: \n"; } + dump += INDENT "Window Infos:\n"; + dump += StringPrintf(INDENT2 "vsync id: %" PRId64 "\n", mWindowInfosVsyncId); + dump += StringPrintf(INDENT2 "timestamp (ns): %" PRId64 "\n", mWindowInfosTimestamp); + dump += "\n"; + dump += StringPrintf(INDENT2 "max update delay (ns): %" PRId64 "\n", mMaxWindowInfosDelay); + dump += StringPrintf(INDENT2 "max update delay vsync id: %" PRId64 "\n", + mMaxWindowInfosDelayVsyncId); + dump += "\n"; if (!mGlobalMonitorsByDisplay.empty()) { for (const auto& [displayId, monitors] : mGlobalMonitorsByDisplay) { @@ -6580,12 +6588,11 @@ void InputDispatcher::displayRemoved(int32_t displayId) { mLooper->wake(); } -void InputDispatcher::onWindowInfosChanged(const std::vector& windowInfos, - const std::vector& displayInfos) { +void InputDispatcher::onWindowInfosChanged(const gui::WindowInfosUpdate& update) { // The listener sends the windows as a flattened array. Separate the windows by display for // more convenient parsing. std::unordered_map>> handlesPerDisplay; - for (const auto& info : windowInfos) { + for (const auto& info : update.windowInfos) { handlesPerDisplay.emplace(info.displayId, std::vector>()); handlesPerDisplay[info.displayId].push_back(sp::make(info)); } @@ -6600,13 +6607,22 @@ void InputDispatcher::onWindowInfosChanged(const std::vector& window } mDisplayInfos.clear(); - for (const auto& displayInfo : displayInfos) { + for (const auto& displayInfo : update.displayInfos) { mDisplayInfos.emplace(displayInfo.displayId, displayInfo); } for (const auto& [displayId, handles] : handlesPerDisplay) { setInputWindowsLocked(handles, displayId); } + + mWindowInfosVsyncId = update.vsyncId; + mWindowInfosTimestamp = update.timestamp; + + int64_t delay = systemTime() - update.timestamp; + if (delay > mMaxWindowInfosDelay) { + mMaxWindowInfosDelay = delay; + mMaxWindowInfosDelayVsyncId = update.vsyncId; + } } // Wake up poll loop since it may need to make new input dispatching choices. mLooper->wake(); @@ -6629,9 +6645,8 @@ bool InputDispatcher::shouldDropInput( } void InputDispatcher::DispatcherWindowListener::onWindowInfosChanged( - const std::vector& windowInfos, - const std::vector& displayInfos) { - mDispatcher.onWindowInfosChanged(windowInfos, displayInfos); + const gui::WindowInfosUpdate& update) { + mDispatcher.onWindowInfosChanged(update); } void InputDispatcher::cancelCurrentTouch() { diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index dd7f7fe2b0..791dd63077 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -36,7 +36,7 @@ #include #include -#include +#include #include #include #include @@ -144,8 +144,7 @@ public: void displayRemoved(int32_t displayId) override; // Public because it's also used by tests to simulate the WindowInfosListener callback - void onWindowInfosChanged(const std::vector& windowInfos, - const std::vector& displayInfos); + void onWindowInfosChanged(const gui::WindowInfosUpdate&); void cancelCurrentTouch() override; @@ -205,6 +204,11 @@ private: const IdGenerator mIdGenerator; + int64_t mWindowInfosVsyncId GUARDED_BY(mLock); + int64_t mWindowInfosTimestamp GUARDED_BY(mLock); + int64_t mMaxWindowInfosDelay GUARDED_BY(mLock) = -1; + int64_t mMaxWindowInfosDelayVsyncId GUARDED_BY(mLock) = -1; + // With each iteration, InputDispatcher nominally processes one queued event, // a timeout, or a response from an input consumer. // This method should only be called on the input dispatcher's own thread. @@ -356,9 +360,7 @@ private: class DispatcherWindowListener : public gui::WindowInfosListener { public: explicit DispatcherWindowListener(InputDispatcher& dispatcher) : mDispatcher(dispatcher){}; - void onWindowInfosChanged( - const std::vector& windowInfos, - const std::vector& displayInfos) override; + void onWindowInfosChanged(const gui::WindowInfosUpdate&) override; private: InputDispatcher& mDispatcher; diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index b3c509595b..c0bc68c292 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -3771,7 +3771,7 @@ TEST_F(InputDispatcherTest, OnWindowInfosChanged_RemoveAllWindowsOnDisplay) { "Fake Window", ADISPLAY_ID_DEFAULT); window->setFocusable(true); - mDispatcher->onWindowInfosChanged({*window->getInfo()}, {}); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); setFocusedWindow(window); window->consumeFocusEvent(true); @@ -3785,7 +3785,7 @@ TEST_F(InputDispatcherTest, OnWindowInfosChanged_RemoveAllWindowsOnDisplay) { window->consumeKeyUp(ADISPLAY_ID_DEFAULT); // All windows are removed from the display. Ensure that we can no longer dispatch to it. - mDispatcher->onWindowInfosChanged({}, {}); + mDispatcher->onWindowInfosChanged({{}, {}, 0, 0}); window->consumeFocusEvent(false); @@ -3801,7 +3801,7 @@ TEST_F(InputDispatcherTest, NonSplitTouchableWindowReceivesMultiTouch) { // Ensure window is non-split and have some transform. window->setPreventSplitting(true); window->setWindowOffset(20, 40); - mDispatcher->onWindowInfosChanged({*window->getInfo()}, {}); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, @@ -3848,12 +3848,12 @@ public: info.displayId = displayId; info.transform = transform; mDisplayInfos.push_back(std::move(info)); - mDispatcher->onWindowInfosChanged(mWindowInfos, mDisplayInfos); + mDispatcher->onWindowInfosChanged({mWindowInfos, mDisplayInfos, 0, 0}); } void addWindow(const sp& windowHandle) { mWindowInfos.push_back(*windowHandle->getInfo()); - mDispatcher->onWindowInfosChanged(mWindowInfos, mDisplayInfos); + mDispatcher->onWindowInfosChanged({mWindowInfos, mDisplayInfos, 0, 0}); } void removeAllWindowsAndDisplays() { @@ -4952,7 +4952,7 @@ TEST_F(InputDispatcherTest, VerifyInputEvent_MotionEvent) { displayInfo.displayId = ADISPLAY_ID_DEFAULT; displayInfo.transform = transform; - mDispatcher->onWindowInfosChanged({*window->getInfo()}, {displayInfo}); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {displayInfo}, 0, 0}); const NotifyMotionArgs motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, @@ -5722,7 +5722,7 @@ TEST_F(InputFilterTest, MotionEvent_UsesLogicalDisplayCoordinates_notifyMotion) displayInfos[1].displayId = SECOND_DISPLAY_ID; displayInfos[1].transform = secondDisplayTransform; - mDispatcher->onWindowInfosChanged({}, displayInfos); + mDispatcher->onWindowInfosChanged({{}, displayInfos, 0, 0}); // Enable InputFilter mDispatcher->setInputFilterEnabled(true); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index d406afff1f..0e3c47b682 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2533,7 +2533,7 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe } updateCursorAsync(); - updateInputFlinger(); + updateInputFlinger(vsyncId); if (mLayerTracingEnabled && !mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) { // This will block and tracing should only be enabled for debugging. @@ -3718,7 +3718,7 @@ void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { doCommitTransactions(); } -void SurfaceFlinger::updateInputFlinger() { +void SurfaceFlinger::updateInputFlinger(VsyncId vsyncId) { if (!mInputFlinger || (!mUpdateInputInfo && mInputWindowCommands.empty())) { return; } @@ -3730,6 +3730,8 @@ void SurfaceFlinger::updateInputFlinger() { if (mUpdateInputInfo) { mUpdateInputInfo = false; updateWindowInfo = true; + mLastInputFlingerUpdateVsyncId = vsyncId; + mLastInputFlingerUpdateTimestamp = systemTime(); buildWindowInfos(windowInfos, displayInfos); } @@ -3759,7 +3761,9 @@ void SurfaceFlinger::updateInputFlinger() { std::move( inputWindowCommands.windowInfosReportedListeners), /* forceImmediateCall= */ visibleWindowsChanged || - !inputWindowCommands.focusRequests.empty()); + !inputWindowCommands.focusRequests.empty(), + mLastInputFlingerUpdateVsyncId, + mLastInputFlingerUpdateTimestamp); } else { // If there are listeners but no changes to input windows, call the listeners // immediately. @@ -6105,6 +6109,29 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& comp result.append(mTimeStats->miniDump()); result.append("\n"); + + result.append("Window Infos:\n"); + StringAppendF(&result, " input flinger update vsync id: %" PRId64 "\n", + mLastInputFlingerUpdateVsyncId.value); + StringAppendF(&result, " input flinger update timestamp (ns): %" PRId64 "\n", + mLastInputFlingerUpdateTimestamp); + result.append("\n"); + + if (int64_t unsentVsyncId = mWindowInfosListenerInvoker->getUnsentMessageVsyncId().value; + unsentVsyncId != -1) { + StringAppendF(&result, " unsent input flinger update vsync id: %" PRId64 "\n", + unsentVsyncId); + StringAppendF(&result, " unsent input flinger update timestamp (ns): %" PRId64 "\n", + mWindowInfosListenerInvoker->getUnsentMessageTimestamp()); + result.append("\n"); + } + + if (uint32_t pendingMessages = mWindowInfosListenerInvoker->getPendingMessageCount(); + pendingMessages != 0) { + StringAppendF(&result, " pending input flinger calls: %" PRIu32 "\n", + mWindowInfosListenerInvoker->getPendingMessageCount()); + result.append("\n"); + } } mat4 SurfaceFlinger::calculateColorMatrix(float saturation) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index cd7659bc0b..8eaa1c7f09 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -717,7 +717,7 @@ private: void updateLayerHistory(const frontend::LayerSnapshot& snapshot); frontend::Update flushLifecycleUpdates() REQUIRES(kMainThreadContext); - void updateInputFlinger(); + void updateInputFlinger(VsyncId); void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext); void buildWindowInfos(std::vector& outWindowInfos, std::vector& outDisplayInfos); @@ -1248,6 +1248,9 @@ private: VsyncId mLastCommittedVsyncId; + VsyncId mLastInputFlingerUpdateVsyncId; + nsecs_t mLastInputFlingerUpdateTimestamp; + // If blurs should be enabled on this device. bool mSupportsBlur = false; std::atomic mFrameMissedCount = 0; diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp index 856fbbbf33..2b62638c61 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp +++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp @@ -16,6 +16,7 @@ #include #include +#include #include "WindowInfosListenerInvoker.h" @@ -86,11 +87,12 @@ void WindowInfosListenerInvoker::binderDied(const wp& who) { void WindowInfosListenerInvoker::windowInfosChanged( std::vector windowInfos, std::vector displayInfos, - WindowInfosReportedListenerSet reportedListeners, bool forceImmediateCall) { + WindowInfosReportedListenerSet reportedListeners, bool forceImmediateCall, VsyncId vsyncId, + nsecs_t timestamp) { reportedListeners.insert(sp::fromExisting(this)); auto callListeners = [this, windowInfos = std::move(windowInfos), - displayInfos = std::move(displayInfos)]( - WindowInfosReportedListenerSet reportedListeners) mutable { + displayInfos = std::move(displayInfos), vsyncId, + timestamp](WindowInfosReportedListenerSet reportedListeners) mutable { WindowInfosListenerVector windowInfosListeners; { std::scoped_lock lock(mListenersMutex); @@ -103,6 +105,9 @@ void WindowInfosListenerInvoker::windowInfosChanged( sp::make(windowInfosListeners, std::move(reportedListeners)); + gui::WindowInfosUpdate update(std::move(windowInfos), std::move(displayInfos), + vsyncId.value, timestamp); + for (const auto& listener : windowInfosListeners) { sp asBinder = IInterface::asBinder(listener); @@ -111,8 +116,7 @@ void WindowInfosListenerInvoker::windowInfosChanged( // calling onWindowInfosReported. asBinder->linkToDeath(reportedInvoker); - auto status = - listener->onWindowInfosChanged(windowInfos, displayInfos, reportedInvoker); + auto status = listener->onWindowInfosChanged(update, reportedInvoker); if (!status.isOk()) { reportedInvoker->onWindowInfosReported(); } @@ -129,11 +133,15 @@ void WindowInfosListenerInvoker::windowInfosChanged( // to reduce the amount of binder memory used. if (mActiveMessageCount > 0 && !forceImmediateCall) { mWindowInfosChangedDelayed = std::move(callListeners); + mUnsentVsyncId = vsyncId; + mUnsentTimestamp = timestamp; mReportedListenersDelayed.merge(reportedListeners); return; } mWindowInfosChangedDelayed = nullptr; + mUnsentVsyncId = {-1}; + mUnsentTimestamp = -1; reportedListeners.merge(mReportedListenersDelayed); mActiveMessageCount++; } @@ -154,6 +162,8 @@ binder::Status WindowInfosListenerInvoker::onWindowInfosReported() { mActiveMessageCount++; callListeners = std::move(mWindowInfosChangedDelayed); mWindowInfosChangedDelayed = nullptr; + mUnsentVsyncId = {-1}; + mUnsentTimestamp = -1; reportedListeners = std::move(mReportedListenersDelayed); mReportedListenersDelayed.clear(); } diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h index 4da98282a9..e35d0564b5 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.h +++ b/services/surfaceflinger/WindowInfosListenerInvoker.h @@ -26,6 +26,8 @@ #include #include +#include "scheduler/VsyncId.h" + namespace android { using WindowInfosReportedListenerSet = @@ -40,10 +42,25 @@ public: void windowInfosChanged(std::vector, std::vector, WindowInfosReportedListenerSet windowInfosReportedListeners, - bool forceImmediateCall); + bool forceImmediateCall, VsyncId vsyncId, nsecs_t timestamp); binder::Status onWindowInfosReported() override; + VsyncId getUnsentMessageVsyncId() { + std::scoped_lock lock(mMessagesMutex); + return mUnsentVsyncId; + } + + nsecs_t getUnsentMessageTimestamp() { + std::scoped_lock lock(mMessagesMutex); + return mUnsentTimestamp; + } + + uint32_t getPendingMessageCount() { + std::scoped_lock lock(mMessagesMutex); + return mActiveMessageCount; + } + protected: void binderDied(const wp& who) override; @@ -58,6 +75,8 @@ private: uint32_t mActiveMessageCount GUARDED_BY(mMessagesMutex) = 0; std::function mWindowInfosChangedDelayed GUARDED_BY(mMessagesMutex); + VsyncId mUnsentVsyncId GUARDED_BY(mMessagesMutex) = {-1}; + nsecs_t mUnsentTimestamp GUARDED_BY(mMessagesMutex) = -1; WindowInfosReportedListenerSet mReportedListenersDelayed; }; diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index c1bab0e89b..4d13aca5bb 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -590,7 +590,7 @@ public: mFlinger->binderDied(display); mFlinger->onFirstRef(); - mFlinger->updateInputFlinger(); + mFlinger->updateInputFlinger(VsyncId{0}); mFlinger->updateCursorAsync(); mutableScheduler().setVsyncConfig({.sfOffset = mFdp.ConsumeIntegral(), diff --git a/services/surfaceflinger/tests/WindowInfosListener_test.cpp b/services/surfaceflinger/tests/WindowInfosListener_test.cpp index f4a8f038d7..3f27360cb7 100644 --- a/services/surfaceflinger/tests/WindowInfosListener_test.cpp +++ b/services/surfaceflinger/tests/WindowInfosListener_test.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -41,9 +42,8 @@ protected: WindowInfosListener(WindowInfosPredicate predicate, std::promise& promise) : mPredicate(std::move(predicate)), mPromise(promise) {} - void onWindowInfosChanged(const std::vector& windowInfos, - const std::vector&) override { - if (mPredicate(windowInfos)) { + void onWindowInfosChanged(const gui::WindowInfosUpdate& update) override { + if (mPredicate(update.windowInfos)) { mPromise.set_value(); } } -- GitLab From 1ca5066f2e1b750667eea475aae2d13107f44c92 Mon Sep 17 00:00:00 2001 From: Wenxin Feng Date: Wed, 3 May 2023 14:00:12 -0700 Subject: [PATCH 1209/1310] Align the scroll ballistics with the pointing ballistics. The scroll ballistics function is much faster than pointing, which will make the scrolling feel less controllable than pointing. We therefore use the same ballistic curves for both pointing and scroll to provide a consistent touchpad interaction experience. Bug: 278172968 Test: manuallly Change-Id: I28c7b597cd86aec4d72dbdf76e3f1078e9c2c706 --- .../inputflinger/reader/mapper/TouchpadInputMapper.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 8753b487e3..692552e60d 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -245,6 +245,14 @@ std::list TouchpadInputMapper::reconfigure(nsecs_t when, accelCurveProp.setRealValues( createAccelerationCurveForSensitivity(config.touchpadPointerSpeed, accelCurveProp.getCount())); + mPropertyProvider.getProperty("Use Custom Touchpad Scroll Accel Curve") + .setBoolValues({true}); + GesturesProp scrollCurveProp = mPropertyProvider.getProperty("Scroll Accel Curve"); + scrollCurveProp.setRealValues( + createAccelerationCurveForSensitivity(config.touchpadPointerSpeed, + scrollCurveProp.getCount())); + mPropertyProvider.getProperty("Scroll X Out Scale").setRealValues({1.0}); + mPropertyProvider.getProperty("Scroll Y Out Scale").setRealValues({1.0}); mPropertyProvider.getProperty("Invert Scrolling") .setBoolValues({config.touchpadNaturalScrollingEnabled}); mPropertyProvider.getProperty("Tap Enable") -- GitLab From 7994df26ce02e1f996718bc5a958acb8f6794d11 Mon Sep 17 00:00:00 2001 From: Liz Kammer Date: Thu, 4 May 2023 12:25:58 +0000 Subject: [PATCH 1210/1310] Remove excess dependency on libGLESv2 libgui_bufferqueue_static is linked statically as part of com.android.media.swcodec, libGLESv2 is neither part of the apex nor providing apex stubs, so the dependency should be avoided. It also appears to be unnecessary. Test: m libgui_bufferqueue_static libgui libgui_mocks Change-Id: Ia9d94d8b294ad1c3d30d4794bf71d4f6da7dc623 --- libs/gui/Android.bp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index 0a63c1564e..a00dff433e 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -242,6 +242,7 @@ cc_library_shared { shared_libs: [ "libbinder", + "libGLESv2", ], export_shared_lib_headers: [ @@ -367,7 +368,6 @@ cc_defaults { "libbase", "libcutils", "libEGL", - "libGLESv2", "libhidlbase", "liblog", "libnativewindow", -- GitLab From 029851242d42d57cc3ee76cb1fff24a2235f6c22 Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Wed, 26 Apr 2023 00:40:34 +0000 Subject: [PATCH 1211/1310] libbinder: replace flaky tests with real coverage NOTE: in U release, flaky tests are only removed to remove noise. The replacements are configured downstream. A lazy service shouldn't quit when it has clients, but sometimes it needs to, such as when the device is shutting down, so we test that it works. In Android U, I broke this behavior, and it was caught by other tests. However, now we have test support for this directly in aidl_lazy_test. Fixes: 280665659 Fixes: 279301793 Fixes: 278337172 Fixes: 277886514 Fixes: 276536663 Fixes: 278117892 Test: aidl_lazy_test Change-Id: I594305d5fd9ad2b4193ec828ccd012817a481b1a Merged-In: I594305d5fd9ad2b4193ec828ccd012817a481b1a --- libs/binder/TEST_MAPPING | 6 ------ 1 file changed, 6 deletions(-) diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING index 41707d48b2..0e8e18747b 100644 --- a/libs/binder/TEST_MAPPING +++ b/libs/binder/TEST_MAPPING @@ -91,12 +91,6 @@ { "name": "binderRpcTest" }, - { - "name": "CtsRootRollbackManagerHostTestCases" - }, - { - "name": "StagedRollbackTest" - }, { "name": "binderRpcTestNoKernel" }, -- GitLab From 6e701afa3013c7a0a606d0847b113386c0515dad Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Thu, 4 May 2023 17:27:48 +0000 Subject: [PATCH 1212/1310] Revert "[sf] Properly update clones of clones" This reverts commit 0cec3ed248d1f0b82e3d86270a018ed7e7f8b620. Reason for revert: testing to see if this is related to layer leaks seen here: b/280503684 Change-Id: I0ba48a412bbb771b70570a5f34e5c1a11d0ee28d --- services/surfaceflinger/Layer.cpp | 16 ++++------------ services/surfaceflinger/Layer.h | 2 +- services/surfaceflinger/LayerRenderArea.cpp | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 19 +++---------------- services/surfaceflinger/SurfaceFlinger.h | 3 ++- 5 files changed, 11 insertions(+), 31 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index bbf855154c..9e40d7f136 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -254,8 +254,7 @@ Layer::~Layer() { mFlinger->mTunnelModeEnabledReporter->decrementTunnelModeCount(); } if (mHadClonedChild) { - auto& roots = mFlinger->mLayerMirrorRoots; - roots.erase(std::remove(roots.begin(), roots.end(), this), roots.end()); + mFlinger->mNumClones--; } if (hasTrustedPresentationListener()) { mFlinger->mNumTrustedPresentationListeners--; @@ -2592,7 +2591,7 @@ void Layer::updateCloneBufferInfo() { mDrawingState.inputInfo = tmpInputInfo; } -bool Layer::updateMirrorInfo(const std::deque& cloneRootsPendingUpdates) { +void Layer::updateMirrorInfo() { if (mClonedChild == nullptr || !mClonedChild->isClonedFromAlive()) { // If mClonedChild is null, there is nothing to mirror. If isClonedFromAlive returns false, // it means that there is a clone, but the layer it was cloned from has been destroyed. In @@ -2600,7 +2599,7 @@ bool Layer::updateMirrorInfo(const std::deque& cloneRootsPendingUpdates) // destroyed. The root, this layer, will still be around since the client can continue // to hold a reference, but no cloned layers will be displayed. mClonedChild = nullptr; - return true; + return; } std::map, sp> clonedLayersMap; @@ -2615,13 +2614,6 @@ bool Layer::updateMirrorInfo(const std::deque& cloneRootsPendingUpdates) mClonedChild->updateClonedDrawingState(clonedLayersMap); mClonedChild->updateClonedChildren(sp::fromExisting(this), clonedLayersMap); mClonedChild->updateClonedRelatives(clonedLayersMap); - - for (Layer* root : cloneRootsPendingUpdates) { - if (clonedLayersMap.find(sp::fromExisting(root)) != clonedLayersMap.end()) { - return false; - } - } - return true; } void Layer::updateClonedDrawingState(std::map, sp>& clonedLayersMap) { @@ -2769,7 +2761,7 @@ bool Layer::isInternalDisplayOverlay() const { void Layer::setClonedChild(const sp& clonedChild) { mClonedChild = clonedChild; mHadClonedChild = true; - mFlinger->mLayerMirrorRoots.push_back(this); + mFlinger->mNumClones++; } bool Layer::setDropInputMode(gui::DropInputMode mode) { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 41228c757b..b37fa153e9 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -651,7 +651,7 @@ public: gui::WindowInfo::Type getWindowType() const { return mWindowType; } - bool updateMirrorInfo(const std::deque& cloneRootsPendingUpdates); + void updateMirrorInfo(); /* * doTransaction - process the transaction. This is a good place to figure diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp index fd56d7a69f..1b8ff28a76 100644 --- a/services/surfaceflinger/LayerRenderArea.cpp +++ b/services/surfaceflinger/LayerRenderArea.cpp @@ -84,7 +84,7 @@ void LayerRenderArea::render(std::function drawLayers) { // If layer is offscreen, update mirroring info if it exists if (mLayer->isRemovedFromCurrentState()) { mLayer->traverse(LayerVector::StateSet::Drawing, - [&](Layer* layer) { layer->updateMirrorInfo({}); }); + [&](Layer* layer) { layer->updateMirrorInfo(); }); mLayer->traverse(LayerVector::StateSet::Drawing, [&](Layer* layer) { layer->updateCloneBufferInfo(); }); } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index e20b4cb0aa..48b41448a1 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4007,21 +4007,8 @@ void SurfaceFlinger::doCommitTransactions() { } commitOffscreenLayers(); - if (mLayerMirrorRoots.size() > 0) { - std::deque pendingUpdates; - pendingUpdates.insert(pendingUpdates.end(), mLayerMirrorRoots.begin(), - mLayerMirrorRoots.end()); - std::vector needsUpdating; - for (Layer* cloneRoot : mLayerMirrorRoots) { - pendingUpdates.pop_front(); - if (cloneRoot->updateMirrorInfo(pendingUpdates)) { - } else { - needsUpdating.push_back(cloneRoot); - } - } - for (Layer* cloneRoot : needsUpdating) { - cloneRoot->updateMirrorInfo({}); - } + if (mNumClones > 0) { + mDrawingState.traverse([&](Layer* layer) { layer->updateMirrorInfo(); }); } } @@ -4128,7 +4115,7 @@ bool SurfaceFlinger::latchBuffers() { mBootStage = BootStage::BOOTANIMATION; } - if (mLayerMirrorRoots.size() > 0) { + if (mNumClones > 0) { mDrawingState.traverse([&](Layer* layer) { layer->updateCloneBufferInfo(); }); } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 6855562ad7..fffd63a515 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -296,7 +296,8 @@ public: // the client can no longer modify this layer directly. void onHandleDestroyed(BBinder* handle, sp& layer, uint32_t layerId); - std::vector mLayerMirrorRoots; + // TODO: Remove atomic if move dtor to main thread CL lands + std::atomic mNumClones; TransactionCallbackInvoker& getTransactionCallbackInvoker() { return mTransactionCallbackInvoker; -- GitLab From a235c040edb9f4132b4cb30eb09e3dbc7cd85dbb Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 2 May 2023 09:59:09 -0700 Subject: [PATCH 1213/1310] Treat HOVER_EXIT as continuation of existing gesture Before this CL, the HOVER_EXIT events used to get treated the same way as the HOVER_ENTER or HOVER_MOVE events. However, that's not quite correct. These events can end gestures, but it never makes sense to use these for starting a new gesture. In this CL, we move the handling of HOVER_EXIT to Case 2 instead of Case 1 inside findTouchedWindowTargetsLocked. Now, both hovering and touching pointers will be considered there. This also means that we should modify the requirement of "at least 1 foreground window", because the gesture needs to be ended correctly even if there's no foreground window present. For example, the window might no longer be foreground, but it already started receiving touch. The foreground check is replaced by an explicit check for empty or exclusively ACTION_OUTSIDE targets. Bug: 273376858 Test: atest inputflinger_tests:InputDispatcherTest.HoverWhileWindowAppears Change-Id: Ib7562ae65ee505debaed92e04aea972221af255d --- .../dispatcher/InputDispatcher.cpp | 56 ++++++--- .../tests/InputDispatcher_test.cpp | 115 +++++++++++++++++- 2 files changed, 151 insertions(+), 20 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index e4f367dd5f..a10c957d15 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2239,7 +2239,9 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( const bool wasDown = oldState != nullptr && oldState->isDown(); const bool isDown = (maskedAction == AMOTION_EVENT_ACTION_DOWN) || (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN && !wasDown); - const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction; + const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL || + maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER || + maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE; const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE); // If pointers are already down, let's finish the current gesture and ignore the new events @@ -2427,7 +2429,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */ // If the pointer is not currently down, then ignore the event. - if (!tempTouchState.isDown()) { + if (!tempTouchState.isDown() && maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) { LOG(INFO) << "Dropping event because the pointer is not down or we previously " "dropped the pointer down event in display " << displayId << ": " << entry.getDescription(); @@ -2435,6 +2437,20 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( return {}; } + // If the pointer is not currently hovering, then ignore the event. + if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) { + const int32_t pointerId = entry.pointerProperties[0].id; + if (oldState == nullptr || + oldState->getWindowsWithHoveringPointer(entry.deviceId, pointerId).empty()) { + LOG(INFO) << "Dropping event because the hovering pointer is not in any windows in " + "display " + << displayId << ": " << entry.getDescription(); + outInjectionResult = InputEventInjectionResult::FAILED; + return {}; + } + tempTouchState.removeHoveringPointer(entry.deviceId, pointerId); + } + addDragEventLocked(entry); // Check whether touches should slip outside of the current foreground window. @@ -2530,21 +2546,6 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( targets); } } - // Ensure that we have at least one foreground window or at least one window that cannot be a - // foreground target. If we only have windows that are not receiving foreground touches (e.g. we - // only have windows getting ACTION_OUTSIDE), then drop the event, because there is no window - // that is actually receiving the entire gesture. - if (std::none_of(tempTouchState.windows.begin(), tempTouchState.windows.end(), - [](const TouchedWindow& touchedWindow) { - return !canReceiveForegroundTouches( - *touchedWindow.windowHandle->getInfo()) || - touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND); - })) { - ALOGI("Dropping event because there is no touched window on display %d to receive it: %s", - displayId, entry.getDescription().c_str()); - outInjectionResult = InputEventInjectionResult::FAILED; - return {}; - } // Ensure that all touched windows are valid for injection. if (entry.injectionState != nullptr) { @@ -2586,7 +2587,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } } - // Success! Output targets from the touch state. + // Output targets from the touch state. for (const TouchedWindow& touchedWindow : tempTouchState.windows) { if (touchedWindow.pointerIds.none() && !touchedWindow.hasHoveringPointers(entry.deviceId)) { // Windows with hovering pointers are getting persisted inside TouchState. @@ -2598,6 +2599,23 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( targets); } + if (targets.empty()) { + LOG(INFO) << "Dropping event because no targets were found: " << entry.getDescription(); + outInjectionResult = InputEventInjectionResult::FAILED; + return {}; + } + + // If we only have windows getting ACTION_OUTSIDE, then drop the event, because there is no + // window that is actually receiving the entire gesture. + if (std::all_of(targets.begin(), targets.end(), [](const InputTarget& target) { + return target.flags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE); + })) { + LOG(INFO) << "Dropping event because all windows would just receive ACTION_OUTSIDE: " + << entry.getDescription(); + outInjectionResult = InputEventInjectionResult::FAILED; + return {}; + } + outInjectionResult = InputEventInjectionResult::SUCCEEDED; // Drop the outside or hover touch windows since we will not care about them // in the next iteration. @@ -5483,7 +5501,7 @@ void InputDispatcher::logDispatchStateLocked() const { std::string line; while (std::getline(stream, line, '\n')) { - ALOGD("%s", line.c_str()); + ALOGI("%s", line.c_str()); } } diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 147b7d4a83..41c4d69946 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -62,6 +62,7 @@ static constexpr int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN; static constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE; static constexpr int32_t ACTION_UP = AMOTION_EVENT_ACTION_UP; static constexpr int32_t ACTION_HOVER_ENTER = AMOTION_EVENT_ACTION_HOVER_ENTER; +static constexpr int32_t ACTION_HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE; static constexpr int32_t ACTION_HOVER_EXIT = AMOTION_EVENT_ACTION_HOVER_EXIT; static constexpr int32_t ACTION_OUTSIDE = AMOTION_EVENT_ACTION_OUTSIDE; static constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL; @@ -2454,6 +2455,116 @@ TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { rightWindow->assertNoEvents(); } +/** + * Start hovering in a window. While this hover is still active, make another window appear on top. + * The top, obstructing window has no input channel, so it's not supposed to receive input. + * While the top window is present, the hovering is stopped. + * Later, hovering gets resumed again. + * Ensure that new hover gesture is handled correctly. + * This test reproduces a crash where the HOVER_EXIT event wasn't getting dispatched correctly + * to the window that's currently being hovered over. + */ +TEST_F(InputDispatcherTest, HoverWhileWindowAppears) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + // Only a single window is present at first + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + // Start hovering in the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + + // Now, an obscuring window appears! + sp obscuringWindow = + sp::make(application, mDispatcher, "Obscuring window", + ADISPLAY_ID_DEFAULT, + /*token=*/std::make_optional>(nullptr)); + obscuringWindow->setFrame(Rect(0, 0, 200, 200)); + obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED); + obscuringWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID); + obscuringWindow->setNoInputChannel(true); + obscuringWindow->setFocusable(false); + obscuringWindow->setAlpha(1.0); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}}); + + // While this new obscuring window is present, the hovering is stopped + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + + // Now the obscuring window goes away. + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + // And a new hover gesture starts. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); +} + +/** + * Same test as 'HoverWhileWindowAppears' above, but here, we also send some HOVER_MOVE events to + * the obscuring window. + */ +TEST_F(InputDispatcherTest, HoverMoveWhileWindowAppears) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + // Only a single window is present at first + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + // Start hovering in the window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + + // Now, an obscuring window appears! + sp obscuringWindow = + sp::make(application, mDispatcher, "Obscuring window", + ADISPLAY_ID_DEFAULT, + /*token=*/std::make_optional>(nullptr)); + obscuringWindow->setFrame(Rect(0, 0, 200, 200)); + obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED); + obscuringWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID); + obscuringWindow->setNoInputChannel(true); + obscuringWindow->setFocusable(false); + obscuringWindow->setAlpha(1.0); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}}); + + // While this new obscuring window is present, the hovering continues. The event can't go to the + // bottom window due to obstructed touches, so it should generate HOVER_EXIT for that window. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) + .build()); + obscuringWindow->assertNoEvents(); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + + // Now the obscuring window goes away. + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + // Hovering continues in the same position. The hovering pointer re-enters the bottom window, + // so it should generate a HOVER_ENTER + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + + // Now the MOVE should be getting dispatched normally + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110)) + .build()); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE)); +} + /** * Two windows: a window on the left and a window on the right. * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains @@ -3446,7 +3557,9 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { .build())); window->consumeMotionUp(ADISPLAY_ID_DEFAULT); - ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + // We already canceled the hovering implicitly by injecting the "DOWN" event without lifting the + // hover first. Therefore, injection of HOVER_EXIT is inconsistent, and should fail. + ASSERT_EQ(InputEventInjectionResult::FAILED, injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_MOUSE) -- GitLab From bb24e27c086fa7f06a914a8051e71bab764662be Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Tue, 21 Mar 2023 10:49:47 +0000 Subject: [PATCH 1214/1310] TouchpadInputMapper: support touchpad capture This allows an app to capture the touchpad and receive "raw" touch information from it. Since some specifics (such as how size is calculated) aren't well-defined in documentation or tests currently, when in doubt I tried to match the behaviour of MultiTouchInputMapper in captured mode. Bug: b/259547750 Test: atest inputflinger_tests Test: use a test app to dump captured events, manually verify and compare with old behaviour Change-Id: I482cce6d16ed6f947ce86e8453ddb2c9789d4d00 --- services/inputflinger/reader/Android.bp | 1 + .../mapper/CapturedTouchpadEventConverter.cpp | 302 ++++++++++ .../mapper/CapturedTouchpadEventConverter.h | 83 +++ .../reader/mapper/TouchpadInputMapper.cpp | 71 ++- .../reader/mapper/TouchpadInputMapper.h | 13 + .../gestures/HardwareStateConverter.cpp | 15 +- .../mapper/gestures/HardwareStateConverter.h | 5 +- services/inputflinger/tests/Android.bp | 1 + .../CapturedTouchpadEventConverter_test.cpp | 556 ++++++++++++++++++ .../tests/HardwareStateConverter_test.cpp | 8 +- .../tests/TestInputListenerMatchers.h | 30 + 11 files changed, 1067 insertions(+), 18 deletions(-) create mode 100644 services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp create mode 100644 services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h create mode 100644 services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index 132c3a1004..b0edb5746c 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -42,6 +42,7 @@ filegroup { "Macros.cpp", "TouchVideoDevice.cpp", "controller/PeripheralController.cpp", + "mapper/CapturedTouchpadEventConverter.cpp", "mapper/CursorInputMapper.cpp", "mapper/ExternalStylusInputMapper.cpp", "mapper/InputMapper.cpp", diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp new file mode 100644 index 0000000000..023372aa97 --- /dev/null +++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp @@ -0,0 +1,302 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "CapturedTouchpadEventConverter.h" + +#include + +#include +#include +#include +#include +#include + +namespace android { + +namespace { + +int32_t actionWithIndex(int32_t action, int32_t index) { + return action | (index << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); +} + +template +size_t firstUnmarkedBit(T set) { + // TODO: replace with std::countr_one from when that's available + LOG_ALWAYS_FATAL_IF(set.all()); + size_t i = 0; + while (set.test(i)) { + i++; + } + return i; +} + +} // namespace + +CapturedTouchpadEventConverter::CapturedTouchpadEventConverter( + InputReaderContext& readerContext, const InputDeviceContext& deviceContext, + MultiTouchMotionAccumulator& motionAccumulator, int32_t deviceId) + : mDeviceId(deviceId), + mReaderContext(readerContext), + mDeviceContext(deviceContext), + mMotionAccumulator(motionAccumulator), + mHasTouchMinor(deviceContext.hasAbsoluteAxis(ABS_MT_TOUCH_MINOR)), + mHasToolMinor(deviceContext.hasAbsoluteAxis(ABS_MT_WIDTH_MINOR)) { + RawAbsoluteAxisInfo orientationInfo; + deviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &orientationInfo); + if (orientationInfo.valid) { + if (orientationInfo.maxValue > 0) { + mOrientationScale = M_PI_2 / orientationInfo.maxValue; + } else if (orientationInfo.minValue < 0) { + mOrientationScale = -M_PI_2 / orientationInfo.minValue; + } + } + + // TODO(b/275369880): support touch.pressure.calibration and .scale properties when captured. + RawAbsoluteAxisInfo pressureInfo; + deviceContext.getAbsoluteAxisInfo(ABS_MT_PRESSURE, &pressureInfo); + if (pressureInfo.valid && pressureInfo.maxValue > 0) { + mPressureScale = 1.0 / pressureInfo.maxValue; + } + + RawAbsoluteAxisInfo touchMajorInfo, toolMajorInfo; + deviceContext.getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR, &touchMajorInfo); + deviceContext.getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR, &toolMajorInfo); + mHasTouchMajor = touchMajorInfo.valid; + mHasToolMajor = toolMajorInfo.valid; + if (mHasTouchMajor && touchMajorInfo.maxValue != 0) { + mSizeScale = 1.0f / touchMajorInfo.maxValue; + } else if (mHasToolMajor && toolMajorInfo.maxValue != 0) { + mSizeScale = 1.0f / toolMajorInfo.maxValue; + } +} + +std::string CapturedTouchpadEventConverter::dump() const { + std::stringstream out; + out << "Orientation scale: " << mOrientationScale << "\n"; + out << "Pressure scale: " << mPressureScale << "\n"; + out << "Size scale: " << mSizeScale << "\n"; + + out << "Dimension axes:"; + if (mHasTouchMajor) out << " touch major"; + if (mHasTouchMinor) out << ", touch minor"; + if (mHasToolMajor) out << ", tool major"; + if (mHasToolMinor) out << ", tool minor"; + out << "\n"; + + out << "Down time: " << mDownTime << "\n"; + out << StringPrintf("Button state: 0x%08x\n", mButtonState); + + out << StringPrintf("Pointer IDs in use: %s\n", mPointerIdsInUse.to_string().c_str()); + + out << "Pointer IDs for slot numbers:\n"; + out << addLinePrefix(dumpMap(mPointerIdForSlotNumber), " ") << "\n"; + return out.str(); +} + +void CapturedTouchpadEventConverter::populateMotionRanges(InputDeviceInfo& info) const { + tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_X, ABS_MT_POSITION_X); + tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_Y, ABS_MT_POSITION_Y); + tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MAJOR, ABS_MT_TOUCH_MAJOR); + tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MINOR, ABS_MT_TOUCH_MINOR); + tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MAJOR, ABS_MT_WIDTH_MAJOR); + tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MINOR, ABS_MT_WIDTH_MINOR); + + RawAbsoluteAxisInfo pressureInfo; + mDeviceContext.getAbsoluteAxisInfo(ABS_MT_PRESSURE, &pressureInfo); + if (pressureInfo.valid) { + info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0, 1, 0, 0, 0); + } + + RawAbsoluteAxisInfo orientationInfo; + mDeviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &orientationInfo); + if (orientationInfo.valid && (orientationInfo.maxValue > 0 || orientationInfo.minValue < 0)) { + info.addMotionRange(AMOTION_EVENT_AXIS_ORIENTATION, SOURCE, -M_PI_2, M_PI_2, 0, 0, 0); + } + + if (mHasTouchMajor || mHasToolMajor) { + info.addMotionRange(AMOTION_EVENT_AXIS_SIZE, SOURCE, 0, 1, 0, 0, 0); + } +} + +void CapturedTouchpadEventConverter::tryAddRawMotionRange(InputDeviceInfo& deviceInfo, + int32_t androidAxis, + int32_t evdevAxis) const { + RawAbsoluteAxisInfo info; + mDeviceContext.getAbsoluteAxisInfo(evdevAxis, &info); + if (info.valid) { + deviceInfo.addMotionRange(androidAxis, SOURCE, info.minValue, info.maxValue, info.flat, + info.fuzz, info.resolution); + } +} + +void CapturedTouchpadEventConverter::reset() { + mCursorButtonAccumulator.reset(mDeviceContext); + mDownTime = 0; + mPointerIdsInUse.reset(); + mPointerIdForSlotNumber.clear(); +} + +std::list CapturedTouchpadEventConverter::process(const RawEvent& rawEvent) { + std::list out; + if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) { + out = sync(rawEvent.when, rawEvent.readTime); + mMotionAccumulator.finishSync(); + } + + mCursorButtonAccumulator.process(&rawEvent); + mMotionAccumulator.process(&rawEvent); + return out; +} + +std::list CapturedTouchpadEventConverter::sync(nsecs_t when, nsecs_t readTime) { + // TODO(b/259547750): filter out touches marked as palms (using MT_TOOL_PALM). + std::list out; + std::vector coords; + std::vector properties; + std::map coordsIndexForSlotNumber; + + // For all the touches that were already down, send a MOVE event with their updated coordinates. + // A convention of the MotionEvent API is that pointer coordinates in UP events match the + // pointer's coordinates from the previous MOVE, so we still include touches here even if + // they've been lifted in this evdev frame. + if (!mPointerIdForSlotNumber.empty()) { + for (const auto [slotNumber, pointerId] : mPointerIdForSlotNumber) { + // Note that we don't check whether the touch has actually moved — it's rare for a touch + // to stay perfectly still between frames, and if it does the worst that can happen is + // an extra MOVE event, so it's not worth the overhead of checking for changes. + coordsIndexForSlotNumber[slotNumber] = coords.size(); + coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber))); + properties.push_back({.id = pointerId, .toolType = ToolType::FINGER}); + } + out.push_back( + makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, coords, properties)); + } + + std::vector upSlots, downSlots; + for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) { + const bool isInUse = mMotionAccumulator.getSlot(i).isInUse(); + const bool wasInUse = mPointerIdForSlotNumber.find(i) != mPointerIdForSlotNumber.end(); + if (isInUse && !wasInUse) { + downSlots.push_back(i); + } else if (!isInUse && wasInUse) { + upSlots.push_back(i); + } + } + + // For any touches that were lifted, send UP or POINTER_UP events. + for (size_t slotNumber : upSlots) { + const size_t indexToRemove = coordsIndexForSlotNumber.at(slotNumber); + const int32_t action = coords.size() == 1 + ? AMOTION_EVENT_ACTION_UP + : actionWithIndex(AMOTION_EVENT_ACTION_POINTER_UP, indexToRemove); + out.push_back(makeMotionArgs(when, readTime, action, coords, properties)); + + freePointerIdForSlot(slotNumber); + coords.erase(coords.begin() + indexToRemove); + properties.erase(properties.begin() + indexToRemove); + // Now that we've removed some coords and properties, we might have to update the slot + // number to coords index mapping. + coordsIndexForSlotNumber.erase(slotNumber); + for (auto& [_, index] : coordsIndexForSlotNumber) { + if (index > indexToRemove) { + index--; + } + } + } + + // For new touches, send DOWN or POINTER_DOWN events. + for (size_t slotNumber : downSlots) { + const size_t coordsIndex = coords.size(); + const int32_t action = coords.empty() + ? AMOTION_EVENT_ACTION_DOWN + : actionWithIndex(AMOTION_EVENT_ACTION_POINTER_DOWN, coordsIndex); + + coordsIndexForSlotNumber[slotNumber] = coordsIndex; + coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber))); + properties.push_back( + {.id = allocatePointerIdToSlot(slotNumber), .toolType = ToolType::FINGER}); + + out.push_back(makeMotionArgs(when, readTime, action, coords, properties)); + } + + const uint32_t newButtonState = mCursorButtonAccumulator.getButtonState(); + for (uint32_t button = 1; button <= AMOTION_EVENT_BUTTON_FORWARD; button <<= 1) { + if (newButtonState & button && !(mButtonState & button)) { + mButtonState |= button; + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS, coords, + properties, /*actionButton=*/button)); + } else if (!(newButtonState & button) && mButtonState & button) { + mButtonState &= ~button; + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE, + coords, properties, /*actionButton=*/button)); + } + } + return out; +} + +NotifyMotionArgs CapturedTouchpadEventConverter::makeMotionArgs( + nsecs_t when, nsecs_t readTime, int32_t action, const std::vector& coords, + const std::vector& properties, int32_t actionButton) { + LOG_ALWAYS_FATAL_IF(coords.size() != properties.size(), + "Mismatched coords and properties arrays."); + return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, SOURCE, + ADISPLAY_ID_NONE, /*policyFlags=*/POLICY_FLAG_WAKE, action, + /*actionButton=*/actionButton, /*flags=*/0, + mReaderContext.getGlobalMetaState(), mButtonState, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, coords.size(), + properties.data(), coords.data(), /*xPrecision=*/1.0f, + /*yPrecision=*/1.0f, AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, mDownTime, /*videoFrames=*/{}); +} + +PointerCoords CapturedTouchpadEventConverter::makePointerCoordsForSlot( + const MultiTouchMotionAccumulator::Slot& slot) const { + PointerCoords coords; + coords.clear(); + coords.setAxisValue(AMOTION_EVENT_AXIS_X, slot.getX()); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, slot.getY()); + coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, slot.getTouchMajor()); + coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, slot.getTouchMinor()); + coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, slot.getToolMajor()); + coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, slot.getToolMinor()); + coords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, slot.getOrientation() * mOrientationScale); + coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, slot.getPressure() * mPressureScale); + float size = 0; + // TODO(b/275369880): support touch.size.calibration and .isSummed properties when captured. + if (mHasTouchMajor) { + size = mHasTouchMinor ? (slot.getTouchMajor() + slot.getTouchMinor()) / 2 + : slot.getTouchMajor(); + } else if (mHasToolMajor) { + size = mHasToolMinor ? (slot.getToolMajor() + slot.getToolMinor()) / 2 + : slot.getToolMajor(); + } + coords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size * mSizeScale); + return coords; +} + +int32_t CapturedTouchpadEventConverter::allocatePointerIdToSlot(size_t slotNumber) { + const int32_t pointerId = firstUnmarkedBit(mPointerIdsInUse); + mPointerIdsInUse.set(pointerId); + mPointerIdForSlotNumber[slotNumber] = pointerId; + return pointerId; +} + +void CapturedTouchpadEventConverter::freePointerIdForSlot(size_t slotNumber) { + mPointerIdsInUse.reset(mPointerIdForSlotNumber.at(slotNumber)); + mPointerIdForSlotNumber.erase(slotNumber); +} + +} // namespace android diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h new file mode 100644 index 0000000000..d81692d810 --- /dev/null +++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h @@ -0,0 +1,83 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "EventHub.h" +#include "InputDevice.h" +#include "accumulator/CursorButtonAccumulator.h" +#include "accumulator/MultiTouchMotionAccumulator.h" +#include "accumulator/TouchButtonAccumulator.h" + +namespace android { + +class CapturedTouchpadEventConverter { +public: + explicit CapturedTouchpadEventConverter(InputReaderContext& readerContext, + const InputDeviceContext& deviceContext, + MultiTouchMotionAccumulator& motionAccumulator, + int32_t deviceId); + std::string dump() const; + void populateMotionRanges(InputDeviceInfo& info) const; + void reset(); + [[nodiscard]] std::list process(const RawEvent& rawEvent); + +private: + void tryAddRawMotionRange(InputDeviceInfo& deviceInfo, int32_t androidAxis, + int32_t evdevAxis) const; + [[nodiscard]] std::list sync(nsecs_t when, nsecs_t readTime); + [[nodiscard]] NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, + const std::vector& coords, + const std::vector& properties, + int32_t actionButton = 0); + PointerCoords makePointerCoordsForSlot(const MultiTouchMotionAccumulator::Slot& slot) const; + int32_t allocatePointerIdToSlot(size_t slotNumber); + void freePointerIdForSlot(size_t slotNumber); + + const int32_t mDeviceId; + InputReaderContext& mReaderContext; + const InputDeviceContext& mDeviceContext; + CursorButtonAccumulator mCursorButtonAccumulator; + MultiTouchMotionAccumulator& mMotionAccumulator; + + float mOrientationScale = 0; + float mPressureScale = 1; + float mSizeScale = 0; + bool mHasTouchMajor; + const bool mHasTouchMinor; + bool mHasToolMajor; + const bool mHasToolMinor; + nsecs_t mDownTime = 0; + uint32_t mButtonState = 0; + + std::bitset mPointerIdsInUse; + std::map mPointerIdForSlotNumber; + + static constexpr uint32_t SOURCE = AINPUT_SOURCE_TOUCHPAD; +}; + +} // namespace android diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 8753b487e3..a5da3cdccc 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -16,9 +16,11 @@ #include "../Macros.h" +#include #include #include +#include #include #include #include @@ -174,8 +176,18 @@ TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext, : InputMapper(deviceContext, readerConfig), mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter), mPointerController(getContext()->getPointerController(getDeviceId())), - mStateConverter(deviceContext), - mGestureConverter(*getContext(), deviceContext, getDeviceId()) { + mStateConverter(deviceContext, mMotionAccumulator), + mGestureConverter(*getContext(), deviceContext, getDeviceId()), + mCapturedEventConverter(*getContext(), deviceContext, mMotionAccumulator, getDeviceId()) { + RawAbsoluteAxisInfo slotAxisInfo; + deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo); + if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) { + ALOGW("Touchpad \"%s\" doesn't have a valid ABS_MT_SLOT axis, and probably won't work " + "properly.", + deviceContext.getName().c_str()); + } + mMotionAccumulator.configure(deviceContext, slotAxisInfo.maxValue + 1, true); + mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD); mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext)); // Even though we don't explicitly delete copy/move semantics, it's safe to @@ -209,15 +221,28 @@ uint32_t TouchpadInputMapper::getSources() const { void TouchpadInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); - mGestureConverter.populateMotionRanges(info); + if (mPointerCaptured) { + mCapturedEventConverter.populateMotionRanges(info); + } else { + mGestureConverter.populateMotionRanges(info); + } } void TouchpadInputMapper::dump(std::string& dump) { dump += INDENT2 "Touchpad Input Mapper:\n"; + if (mProcessing) { + dump += INDENT3 "Currently processing a hardware state\n"; + } + if (mResettingInterpreter) { + dump += INDENT3 "Currently resetting gesture interpreter\n"; + } + dump += StringPrintf(INDENT3 "Pointer captured: %s\n", toString(mPointerCaptured)); dump += INDENT3 "Gesture converter:\n"; dump += addLinePrefix(mGestureConverter.dump(), INDENT4); dump += INDENT3 "Gesture properties:\n"; dump += addLinePrefix(mPropertyProvider.dump(), INDENT4); + dump += INDENT3 "Captured event converter:\n"; + dump += addLinePrefix(mCapturedEventConverter.dump(), INDENT4); } std::list TouchpadInputMapper::reconfigure(nsecs_t when, @@ -252,17 +277,50 @@ std::list TouchpadInputMapper::reconfigure(nsecs_t when, mPropertyProvider.getProperty("Button Right Click Zone Enable") .setBoolValues({config.touchpadRightClickZoneEnabled}); } - return {}; + std::list out; + if ((!changes.any() && config.pointerCaptureRequest.enable) || + changes.test(InputReaderConfiguration::Change::POINTER_CAPTURE)) { + mPointerCaptured = config.pointerCaptureRequest.enable; + // The motion ranges are going to change, so bump the generation to clear the cached ones. + bumpGeneration(); + if (mPointerCaptured) { + // The touchpad is being captured, so we need to tidy up any fake fingers etc. that are + // still being reported for a gesture in progress. + out += reset(when); + mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); + } else { + // We're transitioning from captured to uncaptured. + mCapturedEventConverter.reset(); + } + if (changes.any()) { + out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId())); + } + } + return out; } std::list TouchpadInputMapper::reset(nsecs_t when) { mStateConverter.reset(); + resetGestureInterpreter(when); std::list out = mGestureConverter.reset(when); out += InputMapper::reset(when); return out; } +void TouchpadInputMapper::resetGestureInterpreter(nsecs_t when) { + // The GestureInterpreter has no official reset method, but sending a HardwareState with no + // fingers down or buttons pressed should get it into a clean state. + HardwareState state; + state.timestamp = std::chrono::duration(std::chrono::nanoseconds(when)).count(); + mResettingInterpreter = true; + mGestureInterpreter->PushHardwareState(&state); + mResettingInterpreter = false; +} + std::list TouchpadInputMapper::process(const RawEvent* rawEvent) { + if (mPointerCaptured) { + return mCapturedEventConverter.process(*rawEvent); + } std::optional state = mStateConverter.processRawEvent(rawEvent); if (state) { return sendHardwareState(rawEvent->when, rawEvent->readTime, *state); @@ -283,6 +341,11 @@ std::list TouchpadInputMapper::sendHardwareState(nsecs_t when, nsecs void TouchpadInputMapper::consumeGesture(const Gesture* gesture) { ALOGD_IF(DEBUG_TOUCHPAD_GESTURES, "Gesture ready: %s", gesture->String().c_str()); + if (mResettingInterpreter) { + // We already handle tidying up fake fingers etc. in GestureConverter::reset, so we should + // ignore any gestures produced from the interpreter while we're resetting it. + return; + } if (!mProcessing) { ALOGE("Received gesture outside of the normal processing flow; ignoring it."); return; diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index 268b275584..3128d18a05 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -21,12 +21,15 @@ #include #include +#include +#include "CapturedTouchpadEventConverter.h" #include "EventHub.h" #include "InputDevice.h" #include "InputMapper.h" #include "InputReaderBase.h" #include "NotifyArgs.h" +#include "accumulator/MultiTouchMotionAccumulator.h" #include "gestures/GestureConverter.h" #include "gestures/HardwareStateConverter.h" #include "gestures/PropertyProvider.h" @@ -54,6 +57,7 @@ public: void consumeGesture(const Gesture* gesture); private: + void resetGestureInterpreter(nsecs_t when); [[nodiscard]] std::list sendHardwareState(nsecs_t when, nsecs_t readTime, SelfContainedHardwareState schs); [[nodiscard]] std::list processGestures(nsecs_t when, nsecs_t readTime); @@ -64,10 +68,19 @@ private: PropertyProvider mPropertyProvider; + // The MultiTouchMotionAccumulator is shared between the HardwareStateConverter and + // CapturedTouchpadEventConverter, so that if the touchpad is captured or released while touches + // are down, the relevant converter can still benefit from the current axis values stored in the + // accumulator. + MultiTouchMotionAccumulator mMotionAccumulator; + HardwareStateConverter mStateConverter; GestureConverter mGestureConverter; + CapturedTouchpadEventConverter mCapturedEventConverter; + bool mPointerCaptured = false; bool mProcessing = false; + bool mResettingInterpreter = false; std::vector mGesturesToProcess; }; diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp index 8841b6e27f..6780dcedec 100644 --- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp @@ -26,16 +26,11 @@ namespace android { -HardwareStateConverter::HardwareStateConverter(const InputDeviceContext& deviceContext) - : mDeviceContext(deviceContext), mTouchButtonAccumulator(deviceContext) { - RawAbsoluteAxisInfo slotAxisInfo; - deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo); - if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) { - ALOGW("Touchpad \"%s\" doesn't have a valid ABS_MT_SLOT axis, and probably won't work " - "properly.", - deviceContext.getName().c_str()); - } - mMotionAccumulator.configure(deviceContext, slotAxisInfo.maxValue + 1, true); +HardwareStateConverter::HardwareStateConverter(const InputDeviceContext& deviceContext, + MultiTouchMotionAccumulator& motionAccumulator) + : mDeviceContext(deviceContext), + mMotionAccumulator(motionAccumulator), + mTouchButtonAccumulator(deviceContext) { mTouchButtonAccumulator.configure(); } diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h index c314b0d771..633448e67e 100644 --- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h +++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h @@ -41,7 +41,8 @@ struct SelfContainedHardwareState { // Converts RawEvents into the HardwareState structs used by the gestures library. class HardwareStateConverter { public: - HardwareStateConverter(const InputDeviceContext& deviceContext); + HardwareStateConverter(const InputDeviceContext& deviceContext, + MultiTouchMotionAccumulator& motionAccumulator); std::optional processRawEvent(const RawEvent* event); void reset(); @@ -51,7 +52,7 @@ private: const InputDeviceContext& mDeviceContext; CursorButtonAccumulator mCursorButtonAccumulator; - MultiTouchMotionAccumulator mMotionAccumulator; + MultiTouchMotionAccumulator& mMotionAccumulator; TouchButtonAccumulator mTouchButtonAccumulator; int32_t mMscTimestamp = 0; }; diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 97138c73c0..52277ff078 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -39,6 +39,7 @@ cc_test { srcs: [ "AnrTracker_test.cpp", "BlockingQueue_test.cpp", + "CapturedTouchpadEventConverter_test.cpp", "EventHub_test.cpp", "FakeEventHub.cpp", "FakeInputReaderPolicy.cpp", diff --git a/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp new file mode 100644 index 0000000000..694641cf87 --- /dev/null +++ b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp @@ -0,0 +1,556 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include +#include +#include +#include + +#include "FakeEventHub.h" +#include "FakeInputReaderPolicy.h" +#include "InstrumentedInputReader.h" +#include "TestConstants.h" +#include "TestInputListener.h" +#include "TestInputListenerMatchers.h" + +namespace android { + +using testing::AllOf; + +class CapturedTouchpadEventConverterTest : public testing::Test { +public: + CapturedTouchpadEventConverterTest() + : mFakeEventHub(std::make_unique()), + mFakePolicy(sp::make()), + mReader(mFakeEventHub, mFakePolicy, mFakeListener), + mDevice(newDevice()), + mDeviceContext(*mDevice, EVENTHUB_ID) { + const size_t slotCount = 8; + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, slotCount - 1, 0, 0, 0); + mAccumulator.configure(mDeviceContext, slotCount, /*usingSlotsProtocol=*/true); + } + +protected: + static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000; + static constexpr int32_t EVENTHUB_ID = 1; + + std::shared_ptr newDevice() { + InputDeviceIdentifier identifier; + identifier.name = "device"; + identifier.location = "USB1"; + identifier.bus = 0; + std::shared_ptr device = + std::make_shared(mReader.getContext(), DEVICE_ID, /*generation=*/2, + identifier); + mReader.pushNextDevice(device); + mFakeEventHub->addDevice(EVENTHUB_ID, identifier.name, InputDeviceClass::TOUCHPAD, + identifier.bus); + mReader.loopOnce(); + return device; + } + + void addBasicAxesToEventHub() { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_PRESSURE, 0, 256, 0, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 1000, 0, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MINOR, 0, 1000, 0, 0, 0); + } + + CapturedTouchpadEventConverter createConverter() { + addBasicAxesToEventHub(); + return CapturedTouchpadEventConverter(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + } + + void processAxis(CapturedTouchpadEventConverter& conv, int32_t type, int32_t code, + int32_t value) { + RawEvent event; + event.when = ARBITRARY_TIME; + event.readTime = READ_TIME; + event.deviceId = EVENTHUB_ID; + event.type = type; + event.code = code; + event.value = value; + std::list out = conv.process(event); + EXPECT_TRUE(out.empty()); + } + + std::list processSync(CapturedTouchpadEventConverter& conv) { + RawEvent event; + event.when = ARBITRARY_TIME; + event.readTime = READ_TIME; + event.deviceId = EVENTHUB_ID; + event.type = EV_SYN; + event.code = SYN_REPORT; + event.value = 0; + return conv.process(event); + } + + NotifyMotionArgs processSyncAndExpectSingleMotionArg(CapturedTouchpadEventConverter& conv) { + std::list args = processSync(conv); + EXPECT_EQ(1u, args.size()); + return std::get(args.front()); + } + + std::shared_ptr mFakeEventHub; + sp mFakePolicy; + TestInputListener mFakeListener; + InstrumentedInputReader mReader; + std::shared_ptr mDevice; + InputDeviceContext mDeviceContext; + MultiTouchMotionAccumulator mAccumulator; +}; + +TEST_F(CapturedTouchpadEventConverterTest, MotionRanges_allAxesPresent_populatedCorrectly) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 1100, 0, 0, 35); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MINOR, 0, 1000, 0, 0, 30); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 900, 0, 0, 25); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, 0, 800, 0, 0, 20); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_ORIENTATION, -3, 4, 0, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_PRESSURE, 0, 256, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + InputDeviceInfo info; + conv.populateMotionRanges(info); + + // Most axes should have min, max, and resolution matching the evdev axes. + const InputDeviceInfo::MotionRange* posX = + info.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHPAD); + ASSERT_NE(nullptr, posX); + EXPECT_NEAR(0, posX->min, EPSILON); + EXPECT_NEAR(4000, posX->max, EPSILON); + EXPECT_NEAR(45, posX->resolution, EPSILON); + + const InputDeviceInfo::MotionRange* posY = + info.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHPAD); + ASSERT_NE(nullptr, posY); + EXPECT_NEAR(0, posY->min, EPSILON); + EXPECT_NEAR(2500, posY->max, EPSILON); + EXPECT_NEAR(40, posY->resolution, EPSILON); + + const InputDeviceInfo::MotionRange* touchMajor = + info.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MAJOR, AINPUT_SOURCE_TOUCHPAD); + ASSERT_NE(nullptr, touchMajor); + EXPECT_NEAR(0, touchMajor->min, EPSILON); + EXPECT_NEAR(1100, touchMajor->max, EPSILON); + EXPECT_NEAR(35, touchMajor->resolution, EPSILON); + + const InputDeviceInfo::MotionRange* touchMinor = + info.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MINOR, AINPUT_SOURCE_TOUCHPAD); + ASSERT_NE(nullptr, touchMinor); + EXPECT_NEAR(0, touchMinor->min, EPSILON); + EXPECT_NEAR(1000, touchMinor->max, EPSILON); + EXPECT_NEAR(30, touchMinor->resolution, EPSILON); + + const InputDeviceInfo::MotionRange* toolMajor = + info.getMotionRange(AMOTION_EVENT_AXIS_TOOL_MAJOR, AINPUT_SOURCE_TOUCHPAD); + ASSERT_NE(nullptr, toolMajor); + EXPECT_NEAR(0, toolMajor->min, EPSILON); + EXPECT_NEAR(900, toolMajor->max, EPSILON); + EXPECT_NEAR(25, toolMajor->resolution, EPSILON); + + const InputDeviceInfo::MotionRange* toolMinor = + info.getMotionRange(AMOTION_EVENT_AXIS_TOOL_MINOR, AINPUT_SOURCE_TOUCHPAD); + ASSERT_NE(nullptr, toolMinor); + EXPECT_NEAR(0, toolMinor->min, EPSILON); + EXPECT_NEAR(800, toolMinor->max, EPSILON); + EXPECT_NEAR(20, toolMinor->resolution, EPSILON); + + // ...except orientation and pressure, which get scaled, and size, which is generated from other + // values. + const InputDeviceInfo::MotionRange* orientation = + info.getMotionRange(AMOTION_EVENT_AXIS_ORIENTATION, AINPUT_SOURCE_TOUCHPAD); + ASSERT_NE(nullptr, orientation); + EXPECT_NEAR(-M_PI_2, orientation->min, EPSILON); + EXPECT_NEAR(M_PI_2, orientation->max, EPSILON); + EXPECT_NEAR(0, orientation->resolution, EPSILON); + + const InputDeviceInfo::MotionRange* pressure = + info.getMotionRange(AMOTION_EVENT_AXIS_PRESSURE, AINPUT_SOURCE_TOUCHPAD); + ASSERT_NE(nullptr, pressure); + EXPECT_NEAR(0, pressure->min, EPSILON); + EXPECT_NEAR(1, pressure->max, EPSILON); + EXPECT_NEAR(0, pressure->resolution, EPSILON); + + const InputDeviceInfo::MotionRange* size = + info.getMotionRange(AMOTION_EVENT_AXIS_SIZE, AINPUT_SOURCE_TOUCHPAD); + ASSERT_NE(nullptr, size); + EXPECT_NEAR(0, size->min, EPSILON); + EXPECT_NEAR(1, size->max, EPSILON); + EXPECT_NEAR(0, size->resolution, EPSILON); +} + +TEST_F(CapturedTouchpadEventConverterTest, MotionRanges_bareMinimumAxesPresent_populatedCorrectly) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + InputDeviceInfo info; + conv.populateMotionRanges(info); + + // Only the bare minimum motion ranges should be reported, and no others (e.g. size shouldn't be + // present, since it's generated from axes that aren't provided by this device). + EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHPAD)); + EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHPAD)); + EXPECT_EQ(2u, info.getMotionRanges().size()); +} + +TEST_F(CapturedTouchpadEventConverterTest, OneFinger_motionReportedCorrectly) { + CapturedTouchpadEventConverter conv = createConverter(); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u), + WithCoords(50, 100), WithToolType(ToolType::FINGER))); + + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 99); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u), + WithCoords(52, 99), WithToolType(ToolType::FINGER))); + + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1); + processAxis(conv, EV_KEY, BTN_TOUCH, 0); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0); + + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u), + WithCoords(52, 99), WithToolType(ToolType::FINGER))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithPointerCount(1u), + WithCoords(52, 99), WithToolType(ToolType::FINGER))); +} + +TEST_F(CapturedTouchpadEventConverterTest, OneFinger_touchDimensionsPassedThrough) { + addBasicAxesToEventHub(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 1000, 0, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, 0, 1000, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_TOUCH_MAJOR, 250); + processAxis(conv, EV_ABS, ABS_MT_TOUCH_MINOR, 120); + processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 400); + processAxis(conv, EV_ABS, ABS_MT_WIDTH_MINOR, 200); + + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u), + WithTouchDimensions(250, 120), WithToolDimensions(400, 200))); +} + +TEST_F(CapturedTouchpadEventConverterTest, OneFinger_orientationCalculatedCorrectly) { + addBasicAxesToEventHub(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_ORIENTATION, -3, 4, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_ORIENTATION, -3); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_NEAR(-3 * M_PI / 8, + processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue( + AMOTION_EVENT_AXIS_ORIENTATION), + EPSILON); + + processAxis(conv, EV_ABS, ABS_MT_ORIENTATION, 0); + + EXPECT_NEAR(0, + processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue( + AMOTION_EVENT_AXIS_ORIENTATION), + EPSILON); + + processAxis(conv, EV_ABS, ABS_MT_ORIENTATION, 4); + + EXPECT_NEAR(M_PI / 2, + processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue( + AMOTION_EVENT_AXIS_ORIENTATION), + EPSILON); +} + +TEST_F(CapturedTouchpadEventConverterTest, OneFinger_pressureScaledCorrectly) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_PRESSURE, 0, 256, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_PRESSURE, 128); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), WithPressure(0.5)); +} + +TEST_F(CapturedTouchpadEventConverterTest, + OneFinger_withAllSizeAxes_sizeCalculatedFromTouchMajorMinorAverage) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 256, 0, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MINOR, 0, 256, 0, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 256, 0, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, 0, 256, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_TOUCH_MAJOR, 138); + processAxis(conv, EV_ABS, ABS_MT_TOUCH_MINOR, 118); + processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 200); + processAxis(conv, EV_ABS, ABS_MT_WIDTH_MINOR, 210); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_NEAR(0.5, + processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue( + AMOTION_EVENT_AXIS_SIZE), + EPSILON); +} + +TEST_F(CapturedTouchpadEventConverterTest, + OneFinger_withMajorDimensionsOnly_sizeCalculatedFromTouchMajor) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 256, 0, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 256, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_TOUCH_MAJOR, 128); + processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 200); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_NEAR(0.5, + processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue( + AMOTION_EVENT_AXIS_SIZE), + EPSILON); +} + +TEST_F(CapturedTouchpadEventConverterTest, + OneFinger_withToolDimensionsOnly_sizeCalculatedFromToolMajorMinorAverage) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 256, 0, 0, 0); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, 0, 256, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 138); + processAxis(conv, EV_ABS, ABS_MT_WIDTH_MINOR, 118); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_NEAR(0.5, + processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue( + AMOTION_EVENT_AXIS_SIZE), + EPSILON); +} + +TEST_F(CapturedTouchpadEventConverterTest, + OneFinger_withToolMajorOnly_sizeCalculatedFromTouchMajor) { + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 256, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 128); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_NEAR(0.5, + processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue( + AMOTION_EVENT_AXIS_SIZE), + EPSILON); +} + +TEST_F(CapturedTouchpadEventConverterTest, TwoFingers_motionReportedCorrectly) { + CapturedTouchpadEventConverter conv = createConverter(); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u), + WithCoords(50, 100), WithToolType(ToolType::FINGER))); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 99); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 250); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 200); + + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0); + processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1); + + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u), + WithCoords(52, 99), WithToolType(ToolType::FINGER))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithPointerCount(2u), WithPointerCoords(0, 52, 99), + WithPointerCoords(1, 250, 200), WithPointerToolType(0, ToolType::FINGER), + WithPointerToolType(1, ToolType::FINGER))); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1); + processAxis(conv, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 255); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 202); + + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 0); + + args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(2u), + WithPointerCoords(0, 52, 99), WithPointerCoords(1, 255, 202), + WithPointerToolType(1, ToolType::FINGER), + WithPointerToolType(0, ToolType::FINGER))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithPointerCount(2u), WithPointerCoords(0, 52, 99), + WithPointerCoords(1, 255, 202), WithPointerToolType(0, ToolType::FINGER), + WithPointerToolType(1, ToolType::FINGER))); + + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0); + processAxis(conv, EV_KEY, BTN_TOUCH, 0); + + args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u), + WithCoords(255, 202), WithToolType(ToolType::FINGER))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithPointerCount(1u), + WithCoords(255, 202), WithToolType(ToolType::FINGER))); +} + +// Pointer IDs max out at 31, and so must be reused once a touch is lifted to avoid running out. +TEST_F(CapturedTouchpadEventConverterTest, PointerIdsReusedAfterLift) { + CapturedTouchpadEventConverter conv = createConverter(); + + // Put down two fingers, which should get IDs 0 and 1. + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 10); + processAxis(conv, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 20); + + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1); + + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u), + WithPointerId(/*index=*/0, /*id=*/0))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithPointerCount(2u), WithPointerId(/*index=*/0, /*id=*/0), + WithPointerId(/*index=*/1, /*id=*/1))); + + // Lift the finger in slot 0, freeing up pointer ID 0... + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1); + + // ...and simultaneously add a finger in slot 2. + processAxis(conv, EV_ABS, ABS_MT_SLOT, 2); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 3); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 30); + + args = processSync(conv); + ASSERT_EQ(3u, args.size()); + // Slot 1 being present will result in a MOVE event, even though it hasn't actually moved (see + // comments in CapturedTouchpadEventConverter::sync). + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(2u), + WithPointerId(/*index=*/0, /*id=*/0), WithPointerId(/*index=*/1, /*id=*/1))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithPointerCount(2u), WithPointerId(/*index=*/0, /*id=*/0), + WithPointerId(/*index=*/1, /*id=*/1))); + args.pop_front(); + // Slot 0 being lifted causes the finger from slot 1 to move up to index 0, but keep its + // previous ID. The new finger in slot 2 should take ID 0, which was just freed up. + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithPointerCount(2u), WithPointerId(/*index=*/0, /*id=*/1), + WithPointerId(/*index=*/1, /*id=*/0))); +} + +} // namespace android diff --git a/services/inputflinger/tests/HardwareStateConverter_test.cpp b/services/inputflinger/tests/HardwareStateConverter_test.cpp index 19d46c8a22..5bea2bac18 100644 --- a/services/inputflinger/tests/HardwareStateConverter_test.cpp +++ b/services/inputflinger/tests/HardwareStateConverter_test.cpp @@ -25,6 +25,7 @@ #include "FakeEventHub.h" #include "FakeInputReaderPolicy.h" #include "InstrumentedInputReader.h" +#include "MultiTouchMotionAccumulator.h" #include "TestConstants.h" #include "TestInputListener.h" @@ -38,8 +39,10 @@ public: mReader(mFakeEventHub, mFakePolicy, mFakeListener), mDevice(newDevice()), mDeviceContext(*mDevice, EVENTHUB_ID) { - mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, 7, 0, 0, 0); - mConverter = std::make_unique(mDeviceContext); + const size_t slotCount = 8; + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, slotCount - 1, 0, 0, 0); + mAccumulator.configure(mDeviceContext, slotCount, /*usingSlotsProtocol=*/true); + mConverter = std::make_unique(mDeviceContext, mAccumulator); } protected: @@ -90,6 +93,7 @@ protected: InstrumentedInputReader mReader; std::shared_ptr mDevice; InputDeviceContext mDeviceContext; + MultiTouchMotionAccumulator mAccumulator; std::unique_ptr mConverter; }; diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h index 338b74766e..db6f2548e8 100644 --- a/services/inputflinger/tests/TestInputListenerMatchers.h +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -73,6 +73,12 @@ MATCHER_P(WithPointerCount, count, "MotionEvent with specified number of pointer return arg.pointerCount == count; } +MATCHER_P2(WithPointerId, index, id, "MotionEvent with specified pointer ID for pointer index") { + const auto argPointerId = arg.pointerProperties[index].id; + *result_listener << "expected pointer with index " << index << " to have ID " << argPointerId; + return argPointerId == id; +} + MATCHER_P2(WithCoords, x, y, "InputEvent with specified coords") { const auto argX = arg.pointerCoords[0].getX(); const auto argY = arg.pointerCoords[0].getY(); @@ -136,6 +142,22 @@ MATCHER_P(WithPressure, pressure, "InputEvent with specified pressure") { return argPressure == pressure; } +MATCHER_P2(WithTouchDimensions, maj, min, "InputEvent with specified touch dimensions") { + const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR); + const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR); + *result_listener << "expected touch dimensions " << maj << " major x " << min + << " minor, but got " << argMajor << " major x " << argMinor << " minor"; + return argMajor == maj && argMinor == min; +} + +MATCHER_P2(WithToolDimensions, maj, min, "InputEvent with specified tool dimensions") { + const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR); + const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR); + *result_listener << "expected tool dimensions " << maj << " major x " << min + << " minor, but got " << argMajor << " major x " << argMinor << " minor"; + return argMajor == maj && argMinor == min; +} + MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") { const auto argToolType = arg.pointerProperties[0].toolType; *result_listener << "expected tool type " << ftl::enum_string(toolType) << ", but got " @@ -143,6 +165,14 @@ MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") { return argToolType == toolType; } +MATCHER_P2(WithPointerToolType, pointer, toolType, + "InputEvent with specified tool type for pointer") { + const auto argToolType = arg.pointerProperties[pointer].toolType; + *result_listener << "expected pointer " << pointer << " to have tool type " + << ftl::enum_string(toolType) << ", but got " << ftl::enum_string(argToolType); + return argToolType == toolType; +} + MATCHER_P(WithFlags, flags, "InputEvent with specified flags") { *result_listener << "expected flags " << flags << ", but got " << arg.flags; return arg.flags == static_cast(flags); -- GitLab From 892bce50744f0718c7578aa31aa345591b2ff4db Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Wed, 12 Apr 2023 16:30:09 +0000 Subject: [PATCH 1215/1310] CapturedTouchpadEventConverter: filter out palms When a touchpad marks a touch as a palm, we don't want to pass it on to apps that have captured the touchpad. In future, we could pass these through if we wanted by removing @hide from MotionEvent.TOOL_TYPE_PALM and using that to denote palm touches. Bug: b/259547750 Test: atest inputflinger_tests Test: on a suitable touchpad captured by a test app, move a palm around. Check it's not reported at all when the pad identifies it straight away, and that it's cancelled when identified after touching down. (`getevent -l | grep TOOL_TYPE` is useful to tell when the pad changes its classification of the touch) Change-Id: Iefe84120321246f3661a4d2d06e0ec01ba9fe52b --- .../mapper/CapturedTouchpadEventConverter.cpp | 24 +- .../mapper/CapturedTouchpadEventConverter.h | 2 +- .../CapturedTouchpadEventConverter_test.cpp | 228 ++++++++++++++++++ 3 files changed, 245 insertions(+), 9 deletions(-) diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp index 023372aa97..dab4661442 100644 --- a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp +++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp @@ -162,7 +162,6 @@ std::list CapturedTouchpadEventConverter::process(const RawEvent& ra } std::list CapturedTouchpadEventConverter::sync(nsecs_t when, nsecs_t readTime) { - // TODO(b/259547750): filter out touches marked as palms (using MT_TOOL_PALM). std::list out; std::vector coords; std::vector properties; @@ -187,7 +186,11 @@ std::list CapturedTouchpadEventConverter::sync(nsecs_t when, nsecs_t std::vector upSlots, downSlots; for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) { - const bool isInUse = mMotionAccumulator.getSlot(i).isInUse(); + const MultiTouchMotionAccumulator::Slot& slot = mMotionAccumulator.getSlot(i); + // Some touchpads continue to report contacts even after they've identified them as palms. + // We don't currently have a way to mark these as palms when reporting to apps, so don't + // report them at all. + const bool isInUse = slot.isInUse() && slot.getToolType() != ToolType::PALM; const bool wasInUse = mPointerIdForSlotNumber.find(i) != mPointerIdForSlotNumber.end(); if (isInUse && !wasInUse) { downSlots.push_back(i); @@ -199,10 +202,15 @@ std::list CapturedTouchpadEventConverter::sync(nsecs_t when, nsecs_t // For any touches that were lifted, send UP or POINTER_UP events. for (size_t slotNumber : upSlots) { const size_t indexToRemove = coordsIndexForSlotNumber.at(slotNumber); - const int32_t action = coords.size() == 1 - ? AMOTION_EVENT_ACTION_UP - : actionWithIndex(AMOTION_EVENT_ACTION_POINTER_UP, indexToRemove); - out.push_back(makeMotionArgs(when, readTime, action, coords, properties)); + const bool cancel = mMotionAccumulator.getSlot(slotNumber).getToolType() == ToolType::PALM; + int32_t action; + if (coords.size() == 1) { + action = cancel ? AMOTION_EVENT_ACTION_CANCEL : AMOTION_EVENT_ACTION_UP; + } else { + action = actionWithIndex(AMOTION_EVENT_ACTION_POINTER_UP, indexToRemove); + } + out.push_back(makeMotionArgs(when, readTime, action, coords, properties, /*actionButton=*/0, + /*flags=*/cancel ? AMOTION_EVENT_FLAG_CANCELED : 0)); freePointerIdForSlot(slotNumber); coords.erase(coords.begin() + indexToRemove); @@ -249,12 +257,12 @@ std::list CapturedTouchpadEventConverter::sync(nsecs_t when, nsecs_t NotifyMotionArgs CapturedTouchpadEventConverter::makeMotionArgs( nsecs_t when, nsecs_t readTime, int32_t action, const std::vector& coords, - const std::vector& properties, int32_t actionButton) { + const std::vector& properties, int32_t actionButton, int32_t flags) { LOG_ALWAYS_FATAL_IF(coords.size() != properties.size(), "Mismatched coords and properties arrays."); return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, SOURCE, ADISPLAY_ID_NONE, /*policyFlags=*/POLICY_FLAG_WAKE, action, - /*actionButton=*/actionButton, /*flags=*/0, + /*actionButton=*/actionButton, flags, mReaderContext.getGlobalMetaState(), mButtonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, coords.size(), properties.data(), coords.data(), /*xPrecision=*/1.0f, diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h index d81692d810..9b6df7a222 100644 --- a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h +++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h @@ -53,7 +53,7 @@ private: [[nodiscard]] NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, const std::vector& coords, const std::vector& properties, - int32_t actionButton = 0); + int32_t actionButton = 0, int32_t flags = 0); PointerCoords makePointerCoordsForSlot(const MultiTouchMotionAccumulator::Slot& slot) const; int32_t allocatePointerIdToSlot(size_t slotNumber); void freePointerIdForSlot(size_t slotNumber); diff --git a/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp index 694641cf87..3dc515204e 100644 --- a/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp +++ b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "FakeEventHub.h" @@ -415,6 +416,233 @@ TEST_F(CapturedTouchpadEventConverterTest, EPSILON); } +TEST_F(CapturedTouchpadEventConverterTest, OnePalm_neverReported) { + addBasicAxesToEventHub(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_EQ(0u, processSync(conv).size()); + + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 51); + + EXPECT_EQ(0u, processSync(conv).size()); + + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1); + processAxis(conv, EV_KEY, BTN_TOUCH, 0); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0); + + EXPECT_EQ(0u, processSync(conv).size()); +} + +TEST_F(CapturedTouchpadEventConverterTest, FingerTurningIntoPalm_cancelled) { + addBasicAxesToEventHub(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithToolType(ToolType::FINGER), + WithPointerCount(1u))); + + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 51); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); + + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL), WithPointerCount(1u))); + + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52); + + EXPECT_EQ(0u, processSync(conv).size()); + + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1); + processAxis(conv, EV_KEY, BTN_TOUCH, 0); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0); + + EXPECT_EQ(0u, processSync(conv).size()); +} + +TEST_F(CapturedTouchpadEventConverterTest, PalmTurningIntoFinger_reported) { + addBasicAxesToEventHub(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_EQ(0u, processSync(conv).size()); + + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 51); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u), + WithCoords(51, 100))); + + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u), + WithCoords(52, 100))); +} + +TEST_F(CapturedTouchpadEventConverterTest, FingerArrivingAfterPalm_onlyFingerReported) { + addBasicAxesToEventHub(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_EQ(0u, processSync(conv).size()); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 100); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 150); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0); + processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u), + WithCoords(100, 150))); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 102); + processAxis(conv, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 98); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 148); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u), + WithCoords(98, 148))); +} + +TEST_F(CapturedTouchpadEventConverterTest, FingerAndFingerTurningIntoPalm_partiallyCancelled) { + addBasicAxesToEventHub(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 250); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1); + + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u), + WithToolType(ToolType::FINGER))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithPointerCount(2u), WithPointerToolType(0, ToolType::FINGER), + WithPointerToolType(1, ToolType::FINGER))); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 51); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 251); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); + + args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(2u))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithFlags(AMOTION_EVENT_FLAG_CANCELED), WithPointerCount(2u))); +} + +TEST_F(CapturedTouchpadEventConverterTest, FingerAndPalmTurningIntoFinger_reported) { + addBasicAxesToEventHub(); + mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0, 0); + CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator, + DEVICE_ID); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 250); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM); + + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u), + WithToolType(ToolType::FINGER))); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 51); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 251); + processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER); + + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | + 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + WithPointerCount(2u))); +} + TEST_F(CapturedTouchpadEventConverterTest, TwoFingers_motionReportedCorrectly) { CapturedTouchpadEventConverter conv = createConverter(); -- GitLab From 56adebc49718b63046c3bca3bdd632f987a26962 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Tue, 25 Apr 2023 13:56:05 +0000 Subject: [PATCH 1216/1310] InputMapper refactor: TouchInputMapper Add a factory method for TouchInputMapper(s) to be configured on initilisation Test: m checkinput && atest libinput_tests inputflinger_tests Bug: 256009910 Change-Id: I738e2947ff98016e00c290365292ab01b04e7d26 (cherry picked from commit a8c236b2220cc7f9b0479a82d76d0962a84e902f) --- services/inputflinger/reader/InputDevice.cpp | 6 +- .../inputflinger/reader/include/InputDevice.h | 10 + .../inputflinger/reader/mapper/InputMapper.h | 28 ++- .../reader/mapper/MultiTouchInputMapper.h | 9 +- .../reader/mapper/SingleTouchInputMapper.h | 9 +- .../reader/mapper/TouchInputMapper.cpp | 3 +- .../reader/mapper/TouchInputMapper.h | 17 +- services/inputflinger/tests/InputMapperTest.h | 11 + .../inputflinger/tests/InputReader_test.cpp | 190 +++++++++--------- .../tests/fuzzers/FuzzContainer.h | 7 +- 10 files changed, 174 insertions(+), 116 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index b86906b2cd..ec8a443eba 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -203,7 +203,7 @@ std::list InputDevice::configure(nsecs_t when, using Change = InputReaderConfiguration::Change; - if (!isIgnored()) { + if (!changes.any() || !isIgnored()) { // Full configuration should happen the first time configure is called // and when the device type is changed. Changing a device type can // affect various other parameters so should result in a @@ -503,9 +503,9 @@ std::vector> InputDevice::createMappers( classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) { mappers.push_back(std::make_unique(contextPtr, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH_MT)) { - mappers.push_back(std::make_unique(contextPtr, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH)) { - mappers.push_back(std::make_unique(contextPtr, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // Joystick-like devices. diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 63035466cc..0b8a608891 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -149,6 +149,16 @@ public: return *mapper; } + template + T& constructAndAddMapper(int32_t eventHubId, Args... args) { + // create mapper + auto& devicePair = mDevices[eventHubId]; + auto& deviceContext = devicePair.first; + auto& mappers = devicePair.second; + mappers.push_back(createInputMapper(*deviceContext, args...)); + return static_cast(*mappers.back()); + } + // construct and add a controller to the input device template T& addController(int32_t eventHubId) { diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h index f017317485..06de4c25e3 100644 --- a/services/inputflinger/reader/mapper/InputMapper.h +++ b/services/inputflinger/reader/mapper/InputMapper.h @@ -25,6 +25,20 @@ #include "VibrationElement.h" namespace android { +/** + * This is the factory method that must be used to create any InputMapper + */ +template +std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, Args... args) { + // Using `new` to access non-public constructors. + std::unique_ptr mapper(new T(deviceContext, readerConfig, args...)); + // We need to reset and configure the mapper to ensure it is ready to process event + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + std::list unused = mapper->reset(now); + unused += mapper->reconfigure(now, readerConfig, /*changes=*/{}); + return mapper; +} /* An input mapper transforms raw input events into cooked event data. * A single input device can have multiple associated input mappers in order to interpret @@ -39,8 +53,15 @@ namespace android { */ class InputMapper { public: - explicit InputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); + /** + * Subclasses must either provide a public constructor + * or must be-friend the factory method. + */ + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); + virtual ~InputMapper(); inline int32_t getDeviceId() { return mDeviceContext.getId(); } @@ -102,6 +123,9 @@ public: protected: InputDeviceContext& mDeviceContext; + explicit InputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); + status_t getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo); void bumpGeneration(); diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h index a617420dc9..1d788dffd4 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h @@ -23,8 +23,11 @@ namespace android { class MultiTouchInputMapper : public TouchInputMapper { public: - explicit MultiTouchInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); + ~MultiTouchInputMapper() override; [[nodiscard]] std::list reset(nsecs_t when) override; @@ -36,6 +39,8 @@ protected: bool hasStylus() const override; private: + explicit MultiTouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); // simulate_stylus_with_touch is a debug mode that converts all finger pointers reported by this // mapper's touchscreen into stylus pointers, and adds SOURCE_STYLUS to the input device. // It is used to simulate stylus events for debugging and testing on a device that does not diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h index 93410078d1..7726bfb159 100644 --- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h @@ -23,8 +23,11 @@ namespace android { class SingleTouchInputMapper : public TouchInputMapper { public: - explicit SingleTouchInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); + ~SingleTouchInputMapper() override; [[nodiscard]] std::list reset(nsecs_t when) override; @@ -37,6 +40,8 @@ protected: private: SingleTouchMotionAccumulator mSingleTouchMotionAccumulator; + explicit SingleTouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); }; } // namespace android diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index c72a263dcd..f4d50b8fa1 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -125,8 +125,7 @@ TouchInputMapper::TouchInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig) : InputMapper(deviceContext, readerConfig), mTouchButtonAccumulator(deviceContext), - mConfig(readerConfig), - mParameters(computeParameters(deviceContext)) {} + mConfig(readerConfig) {} TouchInputMapper::~TouchInputMapper() {} diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 7141924e83..d8b59ca39b 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -146,8 +146,6 @@ struct CookedPointerData { class TouchInputMapper : public InputMapper { public: - explicit TouchInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); ~TouchInputMapper() override; uint32_t getSources() const override; @@ -358,25 +356,28 @@ protected: nsecs_t mExternalStylusFusionTimeout; bool mExternalStylusDataPending; // A subset of the buttons in mCurrentRawState that came from an external stylus. - int32_t mExternalStylusButtonsApplied; + int32_t mExternalStylusButtonsApplied{0}; // True if we sent a HOVER_ENTER event. - bool mSentHoverEnter; + bool mSentHoverEnter{false}; // Have we assigned pointer IDs for this stream - bool mHavePointerIds; + bool mHavePointerIds{false}; // Is the current stream of direct touch events aborted - bool mCurrentMotionAborted; + bool mCurrentMotionAborted{false}; // The time the primary pointer last went down. - nsecs_t mDownTime; + nsecs_t mDownTime{0}; // The pointer controller, or null if the device is not a pointer. std::shared_ptr mPointerController; std::vector mVirtualKeys; + explicit TouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); + virtual void dumpParameters(std::string& dump); virtual void configureRawPointerAxes(); virtual void dumpRawPointerAxes(std::string& dump); @@ -513,7 +514,7 @@ private: STYLUS, MOUSE, }; - PointerUsage mPointerUsage; + PointerUsage mPointerUsage{PointerUsage::NONE}; struct PointerGesture { enum class Mode { diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h index d9690349fb..2b6655c45e 100644 --- a/services/inputflinger/tests/InputMapperTest.h +++ b/services/inputflinger/tests/InputMapperTest.h @@ -73,6 +73,17 @@ protected: return mapper; } + template + T& constructAndAddMapper(Args... args) { + // ensure a device entry exists for this eventHubId + mDevice->addEmptyEventHubDevice(EVENTHUB_ID); + // configure the empty device + configureDevice(/*changes=*/{}); + + return mDevice->constructAndAddMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), + args...); + } + void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, ui::Rotation orientation, const std::string& uniqueId, std::optional physicalPort, diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index d56db113ad..ae3ae6c006 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -5069,7 +5069,7 @@ void SingleTouchInputMapperTest::processSync(SingleTouchInputMapper& mapper) { TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndNotACursor_ReturnsPointer) { prepareButtons(); prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); } @@ -5078,7 +5078,7 @@ TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchScreen_Return prepareButtons(); prepareAxes(POSITION); addConfigurationProperty("touch.deviceType", "touchScreen"); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources()); } @@ -5089,7 +5089,7 @@ TEST_F(SingleTouchInputMapperTest, GetKeyCodeState) { prepareButtons(); prepareAxes(POSITION); prepareVirtualKeys(); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); // Unknown key. ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A)); @@ -5117,7 +5117,7 @@ TEST_F(SingleTouchInputMapperTest, GetScanCodeState) { prepareButtons(); prepareAxes(POSITION); prepareVirtualKeys(); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); // Unknown key. ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper.getScanCodeState(AINPUT_SOURCE_ANY, KEY_A)); @@ -5145,7 +5145,7 @@ TEST_F(SingleTouchInputMapperTest, MarkSupportedKeyCodes) { prepareButtons(); prepareAxes(POSITION); prepareVirtualKeys(); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); uint8_t flags[2] = { 0, 0 }; ASSERT_TRUE( @@ -5160,7 +5160,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndReleasedNor prepareButtons(); prepareAxes(POSITION); prepareVirtualKeys(); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); @@ -5210,7 +5210,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfB prepareButtons(); prepareAxes(POSITION); prepareVirtualKeys(); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); @@ -5331,7 +5331,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenTouchStartsOutsideDisplayAndMoves prepareButtons(); prepareAxes(POSITION); prepareVirtualKeys(); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); @@ -5406,7 +5406,7 @@ TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture_VirtualDispl prepareButtons(); prepareAxes(POSITION); prepareVirtualKeys(); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); @@ -5502,7 +5502,7 @@ TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture) { prepareButtons(); prepareAxes(POSITION); prepareVirtualKeys(); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); @@ -5592,7 +5592,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationAware_DoesNotRotateMot prepareAxes(POSITION); // InputReader works in the un-rotated coordinate space, so orientation-aware devices do not // need to be rotated. Touchscreens are orientation-aware by default. - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs args; @@ -5617,7 +5617,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_RotatesMotion // Since InputReader works in the un-rotated coordinate space, only devices that are not // orientation-aware are affected by display rotation. addConfigurationProperty("touch.orientationAware", "0"); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs args; @@ -5686,7 +5686,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation0_RotatesMotions) { addConfigurationProperty("touch.orientation", "ORIENTATION_0"); clearViewports(); prepareDisplay(ui::ROTATION_0); - auto& mapper = addMapperAndConfigure(); + auto& mapper = constructAndAddMapper(); NotifyMotionArgs args; // Orientation 0. @@ -5710,7 +5710,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation90_RotatesMotions) { addConfigurationProperty("touch.orientation", "ORIENTATION_90"); clearViewports(); prepareDisplay(ui::ROTATION_0); - auto& mapper = addMapperAndConfigure(); + auto& mapper = constructAndAddMapper(); NotifyMotionArgs args; // Orientation 90. @@ -5734,7 +5734,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation180_RotatesMotions) { addConfigurationProperty("touch.orientation", "ORIENTATION_180"); clearViewports(); prepareDisplay(ui::ROTATION_0); - auto& mapper = addMapperAndConfigure(); + auto& mapper = constructAndAddMapper(); NotifyMotionArgs args; // Orientation 180. @@ -5758,7 +5758,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation270_RotatesMotions) { addConfigurationProperty("touch.orientation", "ORIENTATION_270"); clearViewports(); prepareDisplay(ui::ROTATION_0); - auto& mapper = addMapperAndConfigure(); + auto& mapper = constructAndAddMapper(); NotifyMotionArgs args; // Orientation 270. @@ -5782,7 +5782,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationSpecified_RotatesMotio // orientation-aware are affected by display rotation. addConfigurationProperty("touch.orientationAware", "0"); addConfigurationProperty("touch.orientation", "ORIENTATION_90"); - auto& mapper = addMapperAndConfigure(); + auto& mapper = constructAndAddMapper(); NotifyMotionArgs args; @@ -5849,7 +5849,7 @@ TEST_F(SingleTouchInputMapperTest, Process_IgnoresTouchesOutsidePhysicalFrame) { prepareAxes(POSITION); addConfigurationProperty("touch.orientationAware", "1"); prepareDisplay(ui::ROTATION_0); - auto& mapper = addMapperAndConfigure(); + auto& mapper = constructAndAddMapper(); // Set a physical frame in the display viewport. auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); @@ -5903,7 +5903,7 @@ TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) { prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION | PRESSURE | TOOL | DISTANCE | TILT); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); // These calculations are based on the input device calibration documentation. int32_t rawX = 100; @@ -5948,7 +5948,7 @@ TEST_F(SingleTouchInputMapperTest, Process_XYAxes_AffineCalibration) { prepareLocationCalibration(); prepareButtons(); prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); int32_t rawX = 100; int32_t rawY = 200; @@ -5970,7 +5970,7 @@ TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllButtons) { prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; NotifyKeyArgs keyArgs; @@ -6213,7 +6213,7 @@ TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -6349,7 +6349,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueI prepareButtons(); prepareAxes(POSITION); mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_FINGER, 0, AKEYCODE_UNKNOWN, 0); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -6420,7 +6420,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenAbsPressureIsPresent_HoversIfItsV prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION | PRESSURE); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -6491,7 +6491,7 @@ TEST_F(SingleTouchInputMapperTest, Reset_CancelsOngoingGesture) { prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION | PRESSURE); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); // Touch down. processDown(mapper, 100, 200); @@ -6513,7 +6513,7 @@ TEST_F(SingleTouchInputMapperTest, Reset_RecreatesTouchState) { prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION | PRESSURE); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); // Set the initial state for the touch pointer. mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 100); @@ -6541,7 +6541,7 @@ TEST_F(SingleTouchInputMapperTest, prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; // Down. @@ -6569,7 +6569,7 @@ TEST_F(SingleTouchInputMapperTest, prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); NotifyMotionArgs motionArgs; @@ -6629,7 +6629,7 @@ TEST_F(SingleTouchInputMapperTest, ButtonIsReleasedOnTouchUp) { prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); // Press a stylus button. @@ -6670,7 +6670,7 @@ TEST_F(SingleTouchInputMapperTest, StylusButtonMotionEventsDisabled) { mFakePolicy->setStylusButtonMotionEventsEnabled(false); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); // Press a stylus button. @@ -6707,7 +6707,7 @@ TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsSetToTouchNavigation_setsCorr prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mapper.getSources()); @@ -6723,7 +6723,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenConfigEnabled_ShouldShowDirectSty mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); mFakePolicy->setPointerController(fakePointerController); mFakePolicy->setStylusPointerIconEnabled(true); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); processKey(mapper, BTN_TOOL_PEN, 1); processMove(mapper, 100, 200); @@ -6747,7 +6747,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirec mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); mFakePolicy->setPointerController(fakePointerController); mFakePolicy->setStylusPointerIconEnabled(false); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); processKey(mapper, BTN_TOOL_PEN, 1); processMove(mapper, 100, 200); @@ -6765,7 +6765,7 @@ TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsChangedToTouchNavigation_upda prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); // Ensure that the device is created as a touchscreen, not touch navigation. ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources()); @@ -6799,7 +6799,7 @@ TEST_F(SingleTouchInputMapperTest, HoverEventsOutsidePhysicalFrameAreIgnored) { mFakePolicy->updateViewport(*viewport); configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); // Hovering inside the physical frame produces events. processKey(mapper, BTN_TOOL_PEN, 1); @@ -6905,7 +6905,7 @@ TEST_F(TouchDisplayProjectionTest, IgnoresTouchesOutsidePhysicalDisplay) { prepareButtons(); prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -6940,7 +6940,7 @@ TEST_F(TouchDisplayProjectionTest, EmitsTouchDownAfterEnteringPhysicalDisplay) { prepareButtons(); prepareAxes(POSITION); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -7069,7 +7069,7 @@ TEST_P(TouchscreenPrecisionTestsFixture, OrientationPrecision) { addConfigurationProperty("touch.orientation", ftl::enum_string(touchscreenOrientation).c_str()); prepareDisplay(ui::ROTATION_0); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); // If the touchscreen is installed in a rotated orientation relative to the display (i.e. in // orientations of either 90 or 270) this means the display's natural resolution will be @@ -7121,7 +7121,7 @@ TEST_P(TouchscreenPrecisionTestsFixture, RotationPrecisionWhenOrientationAware) addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(displayRotation); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); const auto& expectedPoints = kMappedCorners.at(displayRotation); @@ -7159,7 +7159,7 @@ TEST_P(TouchscreenPrecisionTestsFixture, RotationPrecisionOrientationAwareInOri2 addConfigurationProperty("touch.deviceType", "touchScreen"); addConfigurationProperty("touch.orientation", "ORIENTATION_270"); - SingleTouchInputMapper& mapper = addMapperAndConfigure(); + SingleTouchInputMapper& mapper = constructAndAddMapper(); // Ori 270, so width and height swapped const Rect physicalFrame{0, 0, DISPLAY_HEIGHT, DISPLAY_WIDTH}; @@ -7195,7 +7195,7 @@ TEST_P(TouchscreenPrecisionTestsFixture, MotionRangesAreOrientedInRotatedDisplay prepareDisplay(displayRotation); __attribute__((unused)) SingleTouchInputMapper& mapper = - addMapperAndConfigure(); + constructAndAddMapper(); const InputDeviceInfo deviceInfo = mDevice->getDeviceInfo(); // MotionRanges use display pixels as their units @@ -7240,7 +7240,7 @@ public: prepareDisplay(ui::ROTATION_0); prepareButtons(); prepareAxes(POSITION); - auto& mapper = addMapperAndConfigure(); + auto& mapper = constructAndAddMapper(); mStylusState.when = ARBITRARY_TIME; mStylusState.pressure = 0.f; @@ -7729,7 +7729,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION); prepareVirtualKeys(); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); @@ -8013,7 +8013,7 @@ TEST_F(MultiTouchInputMapperTest, AxisResolution_IsPopulated) { mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, RAW_TOOL_MIN, RAW_TOOL_MAX, /*flat*/ 0, /*flat*/ 0, /*resolution*/ 15); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // X and Y axes assertAxisResolution(mapper, AMOTION_EVENT_AXIS_X, 10 / X_PRECISION); @@ -8037,7 +8037,7 @@ TEST_F(MultiTouchInputMapperTest, TouchMajorAndMinorAxes_DoNotAppearIfNotSupport // We do not add ABS_MT_TOUCH_MAJOR / MINOR or ABS_MT_WIDTH_MAJOR / MINOR axes - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // Touch major and minor assertAxisNotPresent(mapper, AMOTION_EVENT_AXIS_TOUCH_MAJOR); @@ -8052,7 +8052,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID); prepareVirtualKeys(); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); @@ -8223,7 +8223,7 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) { prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT); prepareVirtualKeys(); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); @@ -8388,7 +8388,7 @@ TEST_F(MultiTouchInputMapperTest, Process_AllAxes_WithDefaultCalibration) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | TOUCH | TOOL | PRESSURE | ORIENTATION | ID | MINOR | DISTANCE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // These calculations are based on the input device calibration documentation. int32_t rawX = 100; @@ -8438,7 +8438,7 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_GeometricCalibration) prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | TOUCH | TOOL | MINOR); addConfigurationProperty("touch.size.calibration", "geometric"); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // These calculations are based on the input device calibration documentation. int32_t rawX = 100; @@ -8478,7 +8478,7 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_SummedLinearCalibrati addConfigurationProperty("touch.size.scale", "10"); addConfigurationProperty("touch.size.bias", "160"); addConfigurationProperty("touch.size.isSummed", "1"); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // These calculations are based on the input device calibration documentation. // Note: We only provide a single common touch/tool value because the device is assumed @@ -8528,7 +8528,7 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_AreaCalibration) { addConfigurationProperty("touch.size.calibration", "area"); addConfigurationProperty("touch.size.scale", "43"); addConfigurationProperty("touch.size.bias", "3"); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // These calculations are based on the input device calibration documentation. int32_t rawX = 100; @@ -8560,7 +8560,7 @@ TEST_F(MultiTouchInputMapperTest, Process_PressureAxis_AmplitudeCalibration) { prepareAxes(POSITION | PRESSURE); addConfigurationProperty("touch.pressure.calibration", "amplitude"); addConfigurationProperty("touch.pressure.scale", "0.01"); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); InputDeviceInfo info; mapper.populateDeviceInfo(info); @@ -8592,7 +8592,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllButtons) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; NotifyKeyArgs keyArgs; @@ -8835,7 +8835,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleMappedStylusButtons) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); mFakeEventHub->addKey(EVENTHUB_ID, BTN_A, 0, AKEYCODE_STYLUS_BUTTON_PRIMARY, 0); mFakeEventHub->addKey(EVENTHUB_ID, 0, 0xabcd, AKEYCODE_STYLUS_BUTTON_SECONDARY, 0); @@ -8892,7 +8892,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -9043,7 +9043,7 @@ TEST_F(MultiTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIs prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT); mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -9113,7 +9113,7 @@ TEST_F(MultiTouchInputMapperTest, Process_WhenAbsMTPressureIsPresent_HoversIfIts addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -9194,7 +9194,7 @@ TEST_F(MultiTouchInputMapperTest, Configure_AssignsDisplayPort) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareAxes(POSITION); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi1); mFakePolicy->addInputPortAssociation(usb2, hdmi2); @@ -9224,7 +9224,7 @@ TEST_F(MultiTouchInputMapperTest, Configure_AssignsDisplayPort) { TEST_F(MultiTouchInputMapperTest, Configure_AssignsDisplayUniqueId) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareAxes(POSITION); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, VIRTUAL_DISPLAY_UNIQUE_ID); @@ -9253,7 +9253,7 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) { prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // Check source is mouse that would obtain the PointerController. ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); @@ -9273,7 +9273,7 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) { TEST_F(MultiTouchInputMapperTest, Process_SendsReadTime) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareAxes(POSITION); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); prepareDisplay(ui::ROTATION_0); process(mapper, 10, /*readTime=*/11, EV_ABS, ABS_MT_TRACKING_ID, 1); @@ -9304,7 +9304,7 @@ TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreDropped) { /*isActive=*/false, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); prepareAxes(POSITION); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; processPosition(mapper, 100, 100); @@ -9324,7 +9324,7 @@ TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreProcessed) { /*isActive=*/false, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); prepareAxes(POSITION); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; processPosition(mapper, 100, 100); @@ -9346,7 +9346,7 @@ TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_AbortTouches) { configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); prepareAxes(POSITION); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // Finger down int32_t x = 100, y = 100; @@ -9389,7 +9389,7 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { // Setup the first touch screen device. prepareAxes(POSITION | ID | SLOT); addConfigurationProperty("touch.deviceType", "touchScreen"); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // Create the second touch screen device, and enable multi fingers. const std::string USB2 = "USB2"; @@ -9413,9 +9413,9 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { String8("touchScreen")); // Setup the second touch screen device. - MultiTouchInputMapper& mapper2 = - device2->addMapper(SECOND_EVENTHUB_ID, - mFakePolicy->getReaderConfiguration()); + device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID); + MultiTouchInputMapper& mapper2 = device2->constructAndAddMapper< + MultiTouchInputMapper>(SECOND_EVENTHUB_ID, mFakePolicy->getReaderConfiguration()); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), /*changes=*/{}); @@ -9480,7 +9480,7 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_ReceivedByListener) { prepareAxes(POSITION); addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; // Unrotated video frame @@ -9504,7 +9504,7 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_ReceivedByListener) { TEST_F(MultiTouchInputMapperTest, VideoFrames_AreNotRotated) { prepareAxes(POSITION); addConfigurationProperty("touch.deviceType", "touchScreen"); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // Unrotated video frame TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, {1, 2}); NotifyMotionArgs motionArgs; @@ -9529,7 +9529,7 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_WhenNotOrientationAware_AreRotated // Since InputReader works in the un-rotated coordinate space, only devices that are not // orientation-aware are affected by display rotation. addConfigurationProperty("touch.orientationAware", "0"); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // Unrotated video frame TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, {1, 2}); NotifyMotionArgs motionArgs; @@ -9560,7 +9560,7 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_WhenNotOrientationAware_AreRotated TEST_F(MultiTouchInputMapperTest, VideoFrames_MultipleFramesAreNotRotated) { prepareAxes(POSITION); addConfigurationProperty("touch.deviceType", "touchScreen"); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // Unrotated video frames. There's no rule that they must all have the same dimensions, // so mix these. TouchVideoFrame frame1(3, 2, {1, 2, 3, 4, 5, 6}, {1, 2}); @@ -9583,7 +9583,7 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_WhenNotOrientationAware_MultipleFr // Since InputReader works in the un-rotated coordinate space, only devices that are not // orientation-aware are affected by display rotation. addConfigurationProperty("touch.orientationAware", "0"); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // Unrotated video frames. There's no rule that they must all have the same dimensions, // so mix these. TouchVideoFrame frame1(3, 2, {1, 2, 3, 4, 5, 6}, {1, 2}); @@ -9620,7 +9620,7 @@ TEST_F(MultiTouchInputMapperTest, Configure_EnabledForAssociatedDisplay) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareAxes(POSITION); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); ASSERT_EQ(mDevice->isEnabled(), false); @@ -9641,7 +9641,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleSingleTouch) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -9686,7 +9686,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_SinglePointer addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -9734,7 +9734,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_TwoPointers) addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -9809,7 +9809,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_ShouldCancelW addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -9907,7 +9907,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_KeepFirstPoin addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | TOOL_TYPE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -9979,7 +9979,7 @@ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -10036,7 +10036,7 @@ TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // First finger down. processId(mapper, FIRST_TRACKING_ID); @@ -10077,7 +10077,7 @@ TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState_NoPointersDown) addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // First finger touches down and releases. processId(mapper, FIRST_TRACKING_ID); @@ -10105,7 +10105,7 @@ TEST_F(MultiTouchInputMapperTest, StylusSourceIsAddedDynamicallyFromToolType) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE | TOOL_TYPE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); // Even if the device supports reporting the ABS_MT_TOOL_TYPE axis, which could give it the @@ -10160,7 +10160,7 @@ TEST_F(MultiTouchInputMapperTest, Process_WhenConfigEnabled_ShouldShowDirectStyl std::make_shared(); mFakePolicy->setPointerController(fakePointerController); mFakePolicy->setStylusPointerIconEnabled(true); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); processId(mapper, FIRST_TRACKING_ID); processPressure(mapper, RAW_PRESSURE_MIN); @@ -10187,7 +10187,7 @@ TEST_F(MultiTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirect std::make_shared(); mFakePolicy->setPointerController(fakePointerController); mFakePolicy->setStylusPointerIconEnabled(false); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); processId(mapper, FIRST_TRACKING_ID); processPressure(mapper, RAW_PRESSURE_MIN); @@ -10215,7 +10215,7 @@ TEST_F(MultiTouchInputMapperTest_ExternalDevice, Viewports_Fallback) { prepareAxes(POSITION); addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources()); @@ -10250,7 +10250,7 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) { mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0); mFakePolicy->setPointerCapture(true); mFakePolicy->setPointerController(fakePointerController); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // captured touchpad should be a touchpad source NotifyDeviceResetArgs resetArgs; @@ -10398,7 +10398,7 @@ TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) { mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0); mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0); mFakePolicy->setPointerController(fakePointerController); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // run uncaptured pointer tests - pushes out generic events // FINGER 0 DOWN processId(mapper, 3); @@ -10459,7 +10459,7 @@ TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) { mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0); mFakePolicy->setPointerController(fakePointerController); mFakePolicy->setPointerCapture(false); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); // uncaptured touchpad should be a pointer device ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); @@ -10483,7 +10483,7 @@ TEST_F(BluetoothMultiTouchInputMapperTest, TimestampSmoothening) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); nsecs_t kernelEventTime = ARBITRARY_TIME; nsecs_t expectedEventTime = ARBITRARY_TIME; @@ -10570,7 +10570,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthSwipe) { // which is greater than fraction of the diagnal length of the touchpad (349). // Thus, MaxSwipWidth is 750. preparePointerMode(/*xResolution=*/25, /*yResolution=*/25); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; // Two fingers down at once. @@ -10630,7 +10630,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthLowResolutionSwipe) // which is greater than fraction of the diagnal length of the touchpad (349). // Thus, MaxSwipWidth is the fraction of the diagnal length, 349. preparePointerMode(/*xResolution=*/5, /*yResolution=*/5); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; // Two fingers down at once. @@ -10686,7 +10686,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthLowResolutionSwipe) */ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthFreeform) { preparePointerMode(/*xResolution=*/25, /*yResolution=*/25); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; @@ -10781,7 +10781,7 @@ TEST_F(MultiTouchPointerModeTest, PointerGestureMaxSwipeWidthFreeform) { TEST_F(MultiTouchPointerModeTest, TwoFingerSwipeOffsets) { preparePointerMode(/*xResolution=*/25, /*yResolution=*/25); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs motionArgs; // Place two fingers down. @@ -10828,7 +10828,7 @@ TEST_F(MultiTouchPointerModeTest, TwoFingerSwipeOffsets) { TEST_F(MultiTouchPointerModeTest, WhenViewportActiveStatusChanged_PointerGestureIsReset) { preparePointerMode(/*xResolution=*/25, /*yResolution=*/25); mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); - MultiTouchInputMapper& mapper = addMapperAndConfigure(); + MultiTouchInputMapper& mapper = constructAndAddMapper(); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); // Start a stylus gesture. diff --git a/services/inputflinger/tests/fuzzers/FuzzContainer.h b/services/inputflinger/tests/fuzzers/FuzzContainer.h index 84ac0fd262..b9929289f1 100644 --- a/services/inputflinger/tests/fuzzers/FuzzContainer.h +++ b/services/inputflinger/tests/fuzzers/FuzzContainer.h @@ -75,9 +75,12 @@ public: template T& getMapper(Args... args) { - T& mapper = mFuzzDevice->addMapper(mFdp->ConsumeIntegral(), args...); + int32_t eventhubId = mFdp->ConsumeIntegral(); + // ensure a device entry exists for this eventHubId + mFuzzDevice->addEmptyEventHubDevice(eventhubId); configureDevice(); - return mapper; + + return mFuzzDevice->template constructAndAddMapper(eventhubId, args...); } }; -- GitLab From fd9c44fb10610d8c9d7f698868a4f6a5d0547fd9 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Fri, 28 Apr 2023 09:51:07 -0500 Subject: [PATCH 1217/1310] DO NOT MERGE Reverts WindowInfosListenerInvoker throttling Revert "DO NOT MERGE: Force input window updates when layer visibility changes" Revert "SF: throttle WindowInfosListener calls" The reverted CLs may be causing input issues where InputDispatcher doesn't seem to align with the windows currently displayed. This reverts commit 243e2e9af82700c76f743ee71e0f27fe1a3f4383. This reverts commit f3213d2ca821472b12ce2452e4bd33b4cd152e8f. Bug: 279649321 Bug: 270894765 Test: presubmits Change-Id: I8eb46caced6f395460fabc1d6a529a88c1c4566b --- services/surfaceflinger/Layer.cpp | 11 ++- services/surfaceflinger/Layer.h | 15 --- services/surfaceflinger/SurfaceFlinger.cpp | 24 +---- services/surfaceflinger/SurfaceFlinger.h | 5 - .../WindowInfosListenerInvoker.cpp | 93 ++++--------------- .../WindowInfosListenerInvoker.h | 12 +-- 6 files changed, 38 insertions(+), 122 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 4593b40b7d..aff94d132e 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -2391,7 +2391,16 @@ WindowInfo Layer::fillInputInfo(const InputDisplayArgs& displayArgs) { info.inputConfig |= WindowInfo::InputConfig::NOT_TOUCHABLE; } - info.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !isVisibleForInput()); + // For compatibility reasons we let layers which can receive input + // receive input before they have actually submitted a buffer. Because + // of this we use canReceiveInput instead of isVisible to check the + // policy-visibility, ignoring the buffer state. However for layers with + // hasInputInfo()==false we can use the real visibility state. + // We are just using these layers for occlusion detection in + // InputDispatcher, and obviously if they aren't visible they can't occlude + // anything. + const bool visible = hasInputInfo() ? canReceiveInput() : isVisible(); + info.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !visible); info.alpha = getAlpha(); fillTouchOcclusionMode(info); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 1724c150ad..200baf0ba1 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -470,21 +470,6 @@ public: */ virtual bool canReceiveInput() const; - /* - * Whether or not the layer should be considered visible for input calculations. - */ - virtual bool isVisibleForInput() const { - // For compatibility reasons we let layers which can receive input - // receive input before they have actually submitted a buffer. Because - // of this we use canReceiveInput instead of isVisible to check the - // policy-visibility, ignoring the buffer state. However for layers with - // hasInputInfo()==false we can use the real visibility state. - // We are just using these layers for occlusion detection in - // InputDispatcher, and obviously if they aren't visible they can't occlude - // anything. - return hasInputInfo() ? canReceiveInput() : isVisible(); - } - /* * isProtected - true if the layer may contain protected contents in the * GRALLOC_USAGE_PROTECTED sense. diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 1dfb1d3e9b..494aa2c2cf 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3249,34 +3249,16 @@ void SurfaceFlinger::updateInputFlinger() { if (!updateWindowInfo && mInputWindowCommands.empty()) { return; } - - std::unordered_set visibleLayers; - mDrawingState.traverse([&visibleLayers](Layer* layer) { - if (layer->isVisibleForInput()) { - visibleLayers.insert(layer); - } - }); - bool visibleLayersChanged = false; - if (visibleLayers != mVisibleLayers) { - visibleLayersChanged = true; - mVisibleLayers = std::move(visibleLayers); - } - BackgroundExecutor::getInstance().sendCallbacks({[updateWindowInfo, windowInfos = std::move(windowInfos), displayInfos = std::move(displayInfos), inputWindowCommands = std::move(mInputWindowCommands), - inputFlinger = mInputFlinger, this, - visibleLayersChanged]() { + inputFlinger = mInputFlinger, this]() { ATRACE_NAME("BackgroundExecutor::updateInputFlinger"); if (updateWindowInfo) { - mWindowInfosListenerInvoker - ->windowInfosChanged(std::move(windowInfos), std::move(displayInfos), - /* shouldSync= */ inputWindowCommands.syncInputWindows, - /* forceImmediateCall= */ - visibleLayersChanged || - !inputWindowCommands.focusRequests.empty()); + mWindowInfosListenerInvoker->windowInfosChanged(windowInfos, displayInfos, + inputWindowCommands.syncInputWindows); } else if (inputWindowCommands.syncInputWindows) { // If the caller requested to sync input windows, but there are no // changes to input windows, notify immediately. diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index a78144dea2..3a45229ed3 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1449,11 +1449,6 @@ private: nsecs_t mAnimationTransactionTimeout = s2ns(5); friend class SurfaceComposerAIDL; - - // Layers visible during the last commit. This set should only be used for testing set equality - // and membership. The pointers should not be dereferenced as it's possible the set contains - // pointers to freed layers. - std::unordered_set mVisibleLayers; }; class SurfaceComposerAIDL : public gui::BnSurfaceComposer { diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp index 023402f747..30b9d8f1cb 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp +++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp @@ -28,26 +28,19 @@ using gui::WindowInfo; struct WindowInfosListenerInvoker::WindowInfosReportedListener : gui::BnWindowInfosReportedListener { - explicit WindowInfosReportedListener(WindowInfosListenerInvoker& invoker, size_t callbackCount, - bool shouldSync) - : mInvoker(invoker), mCallbacksPending(callbackCount), mShouldSync(shouldSync) {} + explicit WindowInfosReportedListener(WindowInfosListenerInvoker& invoker) : mInvoker(invoker) {} binder::Status onWindowInfosReported() override { - mCallbacksPending--; - if (mCallbacksPending == 0) { - mInvoker.windowInfosReported(mShouldSync); - } + mInvoker.windowInfosReported(); return binder::Status::ok(); } -private: WindowInfosListenerInvoker& mInvoker; - std::atomic mCallbacksPending; - bool mShouldSync; }; WindowInfosListenerInvoker::WindowInfosListenerInvoker(SurfaceFlinger& flinger) - : mFlinger(flinger) {} + : mFlinger(flinger), + mWindowInfosReportedListener(sp::make(*this)) {} void WindowInfosListenerInvoker::addWindowInfosListener(sp listener) { sp asBinder = IInterface::asBinder(listener); @@ -71,76 +64,30 @@ void WindowInfosListenerInvoker::binderDied(const wp& who) { mWindowInfosListeners.erase(who); } -void WindowInfosListenerInvoker::windowInfosChanged(std::vector windowInfos, - std::vector displayInfos, - bool shouldSync, bool forceImmediateCall) { - auto callListeners = [this, windowInfos = std::move(windowInfos), - displayInfos = std::move(displayInfos)](bool shouldSync) mutable { - ftl::SmallVector, kStaticCapacity> windowInfosListeners; - { - std::scoped_lock lock(mListenersMutex); - for (const auto& [_, listener] : mWindowInfosListeners) { - windowInfosListeners.push_back(listener); - } - } - - auto reportedListener = - sp::make(*this, windowInfosListeners.size(), - shouldSync); - - for (const auto& listener : windowInfosListeners) { - auto status = - listener->onWindowInfosChanged(windowInfos, displayInfos, reportedListener); - if (!status.isOk()) { - reportedListener->onWindowInfosReported(); - } - } - }; - +void WindowInfosListenerInvoker::windowInfosChanged(const std::vector& windowInfos, + const std::vector& displayInfos, + bool shouldSync) { + ftl::SmallVector, kStaticCapacity> windowInfosListeners; { - std::scoped_lock lock(mMessagesMutex); - // If there are unacked messages and this isn't a forced call, then return immediately. - // If a forced window infos change doesn't happen first, the update will be sent after - // the WindowInfosReportedListeners are called. If a forced window infos change happens or - // if there are subsequent delayed messages before this update is sent, then this message - // will be dropped and the listeners will only be called with the latest info. This is done - // to reduce the amount of binder memory used. - if (mActiveMessageCount > 0 && !forceImmediateCall) { - mWindowInfosChangedDelayed = std::move(callListeners); - mShouldSyncDelayed |= shouldSync; - return; + std::scoped_lock lock(mListenersMutex); + for (const auto& [_, listener] : mWindowInfosListeners) { + windowInfosListeners.push_back(listener); } + } - mWindowInfosChangedDelayed = nullptr; - shouldSync |= mShouldSyncDelayed; - mShouldSyncDelayed = false; - mActiveMessageCount++; + mCallbacksPending = windowInfosListeners.size(); + + for (const auto& listener : windowInfosListeners) { + listener->onWindowInfosChanged(windowInfos, displayInfos, + shouldSync ? mWindowInfosReportedListener : nullptr); } - callListeners(shouldSync); } -void WindowInfosListenerInvoker::windowInfosReported(bool shouldSync) { - if (shouldSync) { +void WindowInfosListenerInvoker::windowInfosReported() { + mCallbacksPending--; + if (mCallbacksPending == 0) { mFlinger.windowInfosReported(); } - - std::function callListeners; - bool shouldSyncDelayed; - { - std::scoped_lock lock{mMessagesMutex}; - mActiveMessageCount--; - if (!mWindowInfosChangedDelayed || mActiveMessageCount > 0) { - return; - } - - mActiveMessageCount++; - callListeners = std::move(mWindowInfosChangedDelayed); - mWindowInfosChangedDelayed = nullptr; - shouldSyncDelayed = mShouldSyncDelayed; - mShouldSyncDelayed = false; - } - - callListeners(shouldSyncDelayed); } } // namespace android diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h index 701f11efcd..d8d8d0f570 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.h +++ b/services/surfaceflinger/WindowInfosListenerInvoker.h @@ -34,15 +34,15 @@ public: void addWindowInfosListener(sp); void removeWindowInfosListener(const sp& windowInfosListener); - void windowInfosChanged(std::vector, std::vector, - bool shouldSync, bool forceImmediateCall); + void windowInfosChanged(const std::vector&, + const std::vector&, bool shouldSync); protected: void binderDied(const wp& who) override; private: struct WindowInfosReportedListener; - void windowInfosReported(bool shouldSync); + void windowInfosReported(); SurfaceFlinger& mFlinger; std::mutex mListenersMutex; @@ -51,10 +51,8 @@ private: ftl::SmallMap, const sp, kStaticCapacity> mWindowInfosListeners GUARDED_BY(mListenersMutex); - std::mutex mMessagesMutex; - uint32_t mActiveMessageCount GUARDED_BY(mMessagesMutex) = 0; - std::function mWindowInfosChangedDelayed GUARDED_BY(mMessagesMutex); - bool mShouldSyncDelayed; + sp mWindowInfosReportedListener; + std::atomic mCallbacksPending{0}; }; } // namespace android -- GitLab From dcdd46d53f8fe3ce13c5defed11004d60787e878 Mon Sep 17 00:00:00 2001 From: Pablo Gamito Date: Mon, 8 May 2023 10:18:42 +0000 Subject: [PATCH 1218/1310] Update expected pre dump file names Fixes: 281047036 Test: atest dumpstate_test Change-Id: I21ef99717fb71dcf079a76cf2ba6f93b53323e36 --- cmds/dumpstate/tests/dumpstate_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp index 5cbcf9fdf6..a417837ef9 100644 --- a/cmds/dumpstate/tests/dumpstate_test.cpp +++ b/cmds/dumpstate/tests/dumpstate_test.cpp @@ -1001,7 +1001,7 @@ TEST_F(DumpstateTest, PreDumpUiData) { // These traces are always enabled, i.e. they are always pre-dumped const std::vector uiTraces = { std::filesystem::path{"/data/misc/wmtrace/transactions_trace.winscope"}, - std::filesystem::path{"/data/misc/wmtrace/transition_trace.winscope"}, + std::filesystem::path{"/data/misc/wmtrace/wm_transition_trace.winscope"}, std::filesystem::path{"/data/misc/wmtrace/shell_transition_trace.winscope"}, }; -- GitLab From 805d804635d8fecaf81b406b08994869097836c4 Mon Sep 17 00:00:00 2001 From: Austin Borger Date: Fri, 21 Apr 2023 20:07:49 -0700 Subject: [PATCH 1219/1310] Camera / UidObserver: Add the ability to subscribe to specific UIDs UidObserver sends updates about the state of all packages installed on the system. In the case of the cameraserver, we only care about a handful of them. The current status quo is to filter out these callbacks but there is a significant IPC cost that is not addressed by that approach. This patch adds new entrypoints to ActivityManagerService to listen only to specified UIDs. This set of uids can be updated dynamically. Change-Id: I669f27b94fb691187bb77942f53ebc02cb90ace4 Bug: 274486653 Test: -- on physical device: -- testCamera2AccessCallbackInSplitMode x10 -- ActivityManagerServiceTest -- ActivityManagerProcessStateTest -- ActivityManagerFgsBgStartTest -- UidObserverControllerTest -- Alternate focus in split screen between Camera2 + GCA x20 Ignore-AOSP: Soak time in U --- libs/binder/ActivityManager.cpp | 34 ++++++++++++ libs/binder/IActivityManager.cpp | 52 +++++++++++++++++++ .../binder/ActivityManager.h | 8 +++ .../binder/IActivityManager.h | 12 +++++ 4 files changed, 106 insertions(+) diff --git a/libs/binder/ActivityManager.cpp b/libs/binder/ActivityManager.cpp index e45a656d29..aca5009148 100644 --- a/libs/binder/ActivityManager.cpp +++ b/libs/binder/ActivityManager.cpp @@ -75,6 +75,20 @@ status_t ActivityManager::registerUidObserver(const sp& observer, return DEAD_OBJECT; } +status_t ActivityManager::registerUidObserverForUids(const sp& observer, + const int32_t event, const int32_t cutpoint, + const String16& callingPackage, + const int32_t uids[], size_t nUids, + /*out*/ sp& observerToken) { + sp service = getService(); + if (service != nullptr) { + return service->registerUidObserverForUids(observer, event, cutpoint, callingPackage, uids, + nUids, observerToken); + } + // ActivityManagerService appears dead. Return usual error code for dead service. + return DEAD_OBJECT; +} + status_t ActivityManager::unregisterUidObserver(const sp& observer) { sp service = getService(); @@ -85,6 +99,26 @@ status_t ActivityManager::unregisterUidObserver(const sp& observer return DEAD_OBJECT; } +status_t ActivityManager::addUidToObserver(const sp& observerToken, + const String16& callingPackage, int32_t uid) { + sp service = getService(); + if (service != nullptr) { + return service->addUidToObserver(observerToken, callingPackage, uid); + } + // ActivityManagerService appears dead. Return usual error code for dead service. + return DEAD_OBJECT; +} + +status_t ActivityManager::removeUidFromObserver(const sp& observerToken, + const String16& callingPackage, int32_t uid) { + sp service = getService(); + if (service != nullptr) { + return service->removeUidFromObserver(observerToken, callingPackage, uid); + } + // ActivityManagerService appears dead. Return usual error code for dead service. + return DEAD_OBJECT; +} + bool ActivityManager::isUidActive(const uid_t uid, const String16& callingPackage) { sp service = getService(); diff --git a/libs/binder/IActivityManager.cpp b/libs/binder/IActivityManager.cpp index ebdaa4cc99..84900a72ff 100644 --- a/libs/binder/IActivityManager.cpp +++ b/libs/binder/IActivityManager.cpp @@ -77,6 +77,30 @@ public: return OK; } + virtual status_t registerUidObserverForUids(const sp& observer, + const int32_t event, const int32_t cutpoint, + const String16& callingPackage, + const int32_t uids[], size_t nUids, + /*out*/ sp& observerToken) { + Parcel data, reply; + data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor()); + data.writeStrongBinder(IInterface::asBinder(observer)); + data.writeInt32(event); + data.writeInt32(cutpoint); + data.writeString16(callingPackage); + data.writeInt32Array(nUids, uids); + status_t err = + remote()->transact(REGISTER_UID_OBSERVER_FOR_UIDS_TRANSACTION, data, &reply); + if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) { + return err; + } + err = reply.readStrongBinder(&observerToken); + if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) { + return err; + } + return OK; + } + virtual status_t unregisterUidObserver(const sp& observer) { Parcel data, reply; @@ -89,6 +113,34 @@ public: return OK; } + virtual status_t addUidToObserver(const sp& observerToken, + const String16& callingPackage, int32_t uid) { + Parcel data, reply; + data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor()); + data.writeStrongBinder(observerToken); + data.writeString16(callingPackage); + data.writeInt32(uid); + status_t err = remote()->transact(ADD_UID_TO_OBSERVER_TRANSACTION, data, &reply); + if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) { + return err; + } + return OK; + } + + virtual status_t removeUidFromObserver(const sp& observerToken, + const String16& callingPackage, int32_t uid) { + Parcel data, reply; + data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor()); + data.writeStrongBinder(observerToken); + data.writeString16(callingPackage); + data.writeInt32(uid); + status_t err = remote()->transact(REMOVE_UID_FROM_OBSERVER_TRANSACTION, data, &reply); + if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) { + return err; + } + return OK; + } + virtual bool isUidActive(const uid_t uid, const String16& callingPackage) { Parcel data, reply; diff --git a/libs/binder/include_activitymanager/binder/ActivityManager.h b/libs/binder/include_activitymanager/binder/ActivityManager.h index 5dfbd44dc4..9c634c7238 100644 --- a/libs/binder/include_activitymanager/binder/ActivityManager.h +++ b/libs/binder/include_activitymanager/binder/ActivityManager.h @@ -82,7 +82,15 @@ public: const int32_t event, const int32_t cutpoint, const String16& callingPackage); + status_t registerUidObserverForUids(const sp& observer, const int32_t event, + const int32_t cutpoint, const String16& callingPackage, + const int32_t uids[], size_t nUids, + /*out*/ sp& observerToken); status_t unregisterUidObserver(const sp& observer); + status_t addUidToObserver(const sp& observerToken, const String16& callingPackage, + int32_t uid); + status_t removeUidFromObserver(const sp& observerToken, const String16& callingPackage, + int32_t uid); bool isUidActive(const uid_t uid, const String16& callingPackage); int getUidProcessState(const uid_t uid, const String16& callingPackage); status_t checkPermission(const String16& permission, const pid_t pid, const uid_t uid, int32_t* outResult); diff --git a/libs/binder/include_activitymanager/binder/IActivityManager.h b/libs/binder/include_activitymanager/binder/IActivityManager.h index 20d12aea9a..07450c6af9 100644 --- a/libs/binder/include_activitymanager/binder/IActivityManager.h +++ b/libs/binder/include_activitymanager/binder/IActivityManager.h @@ -35,7 +35,16 @@ public: const int32_t event, const int32_t cutpoint, const String16& callingPackage) = 0; + virtual status_t registerUidObserverForUids(const sp& observer, + const int32_t event, const int32_t cutpoint, + const String16& callingPackage, + const int32_t uids[], size_t nUids, + /*out*/ sp& observerToken) = 0; virtual status_t unregisterUidObserver(const sp& observer) = 0; + virtual status_t addUidToObserver(const sp& observerToken, + const String16& callingPackage, int32_t uid) = 0; + virtual status_t removeUidFromObserver(const sp& observerToken, + const String16& callingPackage, int32_t uid) = 0; virtual bool isUidActive(const uid_t uid, const String16& callingPackage) = 0; virtual int32_t getUidProcessState(const uid_t uid, const String16& callingPackage) = 0; virtual status_t checkPermission(const String16& permission, @@ -51,6 +60,9 @@ public: OPEN_CONTENT_URI_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION, REGISTER_UID_OBSERVER_TRANSACTION, UNREGISTER_UID_OBSERVER_TRANSACTION, + REGISTER_UID_OBSERVER_FOR_UIDS_TRANSACTION, + ADD_UID_TO_OBSERVER_TRANSACTION, + REMOVE_UID_FROM_OBSERVER_TRANSACTION, IS_UID_ACTIVE_TRANSACTION, GET_UID_PROCESS_STATE_TRANSACTION, CHECK_PERMISSION_TRANSACTION, -- GitLab From c78f53cd0e9ce68cc52a851584b6ce5b34baef7d Mon Sep 17 00:00:00 2001 From: Chavi Weingarten Date: Fri, 14 Apr 2023 18:50:53 +0000 Subject: [PATCH 1220/1310] Cleaned up transaction sanitize calls Exposed a way for a client to invoke sanitize with a uid and pid to ensure we don't remove states when the process that added it was privileged. Added a helper function to get the permission ints based on the String permission values so SF and clients can call the same API. In SF, call sanitize as soon as setTransactionState is called since that's the point where the Transaction has been passed over binder so we can identify the calling uid. This allows us to remove the permission values passed to applyTransactionState and unifies the places that were calling sanitize. Test: CredentialsTest Bug: 267794530 Change-Id: I30c1800f0fee43df1cee82464139db7b56a7d911 --- libs/gui/Android.bp | 1 + libs/gui/ISurfaceComposer.cpp | 8 +- libs/gui/LayerStatePermissions.cpp | 58 +++++++++++++++ libs/gui/SurfaceComposerClient.cpp | 12 ++- libs/gui/include/gui/ISurfaceComposer.h | 2 +- libs/gui/include/gui/LayerStatePermissions.h | 29 ++++++++ libs/gui/include/gui/SurfaceComposerClient.h | 2 +- libs/gui/tests/Surface_test.cpp | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 64 +++++++--------- services/surfaceflinger/SurfaceFlinger.h | 27 +++---- services/surfaceflinger/TransactionState.h | 4 +- .../surfaceflinger/tests/Credentials_test.cpp | 53 +++++++++++++ .../tests/WindowInfosListener_test.cpp | 39 ++-------- .../unittests/TransactionApplicationTest.cpp | 2 +- .../tests/utils/WindowInfosListenerUtils.h | 74 +++++++++++++++++++ 15 files changed, 277 insertions(+), 100 deletions(-) create mode 100644 libs/gui/LayerStatePermissions.cpp create mode 100644 libs/gui/include/gui/LayerStatePermissions.h create mode 100644 services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index 33bb343c9d..80fed98434 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -226,6 +226,7 @@ cc_library_shared { "ITransactionCompletedListener.cpp", "LayerDebugInfo.cpp", "LayerMetadata.cpp", + "LayerStatePermissions.cpp", "LayerState.cpp", "OccupancyTracker.cpp", "StreamSplitter.cpp", diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index cefb9a71d6..d72f65eb7a 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -62,7 +62,7 @@ public: status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo, Vector& state, const Vector& displays, uint32_t flags, const sp& applyToken, - const InputWindowCommands& commands, int64_t desiredPresentTime, + InputWindowCommands commands, int64_t desiredPresentTime, bool isAutoTimestamp, const std::vector& uncacheBuffers, bool hasListenerCallbacks, @@ -188,9 +188,9 @@ status_t BnSurfaceComposer::onTransact( SAFE_PARCEL(data.readUint64, &transactionId); return setTransactionState(frameTimelineInfo, state, displays, stateFlags, applyToken, - inputWindowCommands, desiredPresentTime, isAutoTimestamp, - uncacheBuffers, hasListenerCallbacks, listenerCallbacks, - transactionId); + std::move(inputWindowCommands), desiredPresentTime, + isAutoTimestamp, uncacheBuffers, hasListenerCallbacks, + listenerCallbacks, transactionId); } default: { return BBinder::onTransact(code, data, reply, flags); diff --git a/libs/gui/LayerStatePermissions.cpp b/libs/gui/LayerStatePermissions.cpp new file mode 100644 index 0000000000..28697ca953 --- /dev/null +++ b/libs/gui/LayerStatePermissions.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#ifndef __ANDROID_VNDK__ +#include +#endif // __ANDROID_VNDK__ +#include + +namespace android { +std::unordered_map LayerStatePermissions::mPermissionMap = { + // If caller has ACCESS_SURFACE_FLINGER, they automatically get ROTATE_SURFACE_FLINGER + // permission, as well + {"android.permission.ACCESS_SURFACE_FLINGER", + layer_state_t::Permission::ACCESS_SURFACE_FLINGER | + layer_state_t::Permission::ROTATE_SURFACE_FLINGER}, + {"android.permission.ROTATE_SURFACE_FLINGER", + layer_state_t::Permission::ROTATE_SURFACE_FLINGER}, + {"android.permission.INTERNAL_SYSTEM_WINDOW", + layer_state_t::Permission::INTERNAL_SYSTEM_WINDOW}, +}; + +static bool callingThreadHasPermission(const std::string& permission __attribute__((unused)), + int pid __attribute__((unused)), + int uid __attribute__((unused))) { +#ifndef __ANDROID_VNDK__ + return uid == AID_GRAPHICS || uid == AID_SYSTEM || + PermissionCache::checkPermission(String16(permission.c_str()), pid, uid); +#endif // __ANDROID_VNDK__ + return false; +} + +uint32_t LayerStatePermissions::getTransactionPermissions(int pid, int uid) { + uint32_t permissions = 0; + for (auto [permissionName, permissionVal] : mPermissionMap) { + if (callingThreadHasPermission(permissionName, pid, uid)) { + permissions |= permissionVal; + } + } + + return permissions; +} +} // namespace android diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index eb5cc4f8ab..1b13ec1c06 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -54,6 +54,7 @@ #include #include +#include #include #include @@ -716,11 +717,16 @@ SurfaceComposerClient::Transaction::Transaction(const Transaction& other) mListenerCallbacks = other.mListenerCallbacks; } -void SurfaceComposerClient::Transaction::sanitize() { +void SurfaceComposerClient::Transaction::sanitize(int pid, int uid) { + uint32_t permissions = LayerStatePermissions::getTransactionPermissions(pid, uid); for (auto & [handle, composerState] : mComposerStates) { - composerState.state.sanitize(0 /* permissionMask */); + composerState.state.sanitize(permissions); + } + if (!mInputWindowCommands.empty() && + (permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) == 0) { + ALOGE("Only privileged callers are allowed to send input commands."); + mInputWindowCommands.clear(); } - mInputWindowCommands.clear(); } std::unique_ptr diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index 1e67225a4e..bd21851c14 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -113,7 +113,7 @@ public: virtual status_t setTransactionState( const FrameTimelineInfo& frameTimelineInfo, Vector& state, const Vector& displays, uint32_t flags, const sp& applyToken, - const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, + InputWindowCommands inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, const std::vector& uncacheBuffer, bool hasListenerCallbacks, const std::vector& listenerCallbacks, uint64_t transactionId) = 0; diff --git a/libs/gui/include/gui/LayerStatePermissions.h b/libs/gui/include/gui/LayerStatePermissions.h new file mode 100644 index 0000000000..a90f30c621 --- /dev/null +++ b/libs/gui/include/gui/LayerStatePermissions.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +namespace android { +class LayerStatePermissions { +public: + static uint32_t getTransactionPermissions(int pid, int uid); + +private: + static std::unordered_map mPermissionMap; +}; +} // namespace android \ No newline at end of file diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 945b164fdc..8d2cdaf5b8 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -744,7 +744,7 @@ public: * * TODO (b/213644870): Remove all permissioned things from Transaction */ - void sanitize(); + void sanitize(int pid, int uid); static sp getDefaultApplyToken(); static void setDefaultApplyToken(sp applyToken); diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index fccc408473..5bc6904563 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -699,7 +699,7 @@ public: Vector& /*state*/, const Vector& /*displays*/, uint32_t /*flags*/, const sp& /*applyToken*/, - const InputWindowCommands& /*inputWindowCommands*/, + InputWindowCommands /*inputWindowCommands*/, int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/, const std::vector& /*cachedBuffer*/, bool /*hasListenerCallbacks*/, diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 2d406788cf..95159d7d2a 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -110,6 +110,7 @@ #include #include +#include #include #include "BackgroundExecutor.h" #include "Client.h" @@ -4405,7 +4406,7 @@ bool SurfaceFlinger::applyTransactionsLocked(std::vector& tran transaction.inputWindowCommands, transaction.desiredPresentTime, transaction.isAutoTimestamp, std::move(transaction.uncacheBufferIds), transaction.postTime, - transaction.permissions, transaction.hasListenerCallbacks, + transaction.hasListenerCallbacks, transaction.listenerCallbacks, transaction.originPid, transaction.originUid, transaction.id); } @@ -4484,24 +4485,27 @@ bool SurfaceFlinger::shouldLatchUnsignaled(const sp& layer, const layer_s status_t SurfaceFlinger::setTransactionState( const FrameTimelineInfo& frameTimelineInfo, Vector& states, const Vector& displays, uint32_t flags, const sp& applyToken, - const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, - bool isAutoTimestamp, const std::vector& uncacheBuffers, - bool hasListenerCallbacks, const std::vector& listenerCallbacks, - uint64_t transactionId) { + InputWindowCommands inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, + const std::vector& uncacheBuffers, bool hasListenerCallbacks, + const std::vector& listenerCallbacks, uint64_t transactionId) { ATRACE_CALL(); - uint32_t permissions = - callingThreadHasUnscopedSurfaceFlingerAccess() ? - layer_state_t::Permission::ACCESS_SURFACE_FLINGER : 0; - // Avoid checking for rotation permissions if the caller already has ACCESS_SURFACE_FLINGER - // permissions. - if ((permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) || - callingThreadHasPermission(sRotateSurfaceFlinger)) { - permissions |= layer_state_t::Permission::ROTATE_SURFACE_FLINGER; + IPCThreadState* ipc = IPCThreadState::self(); + const int originPid = ipc->getCallingPid(); + const int originUid = ipc->getCallingUid(); + uint32_t permissions = LayerStatePermissions::getTransactionPermissions(originPid, originUid); + for (auto composerState : states) { + composerState.state.sanitize(permissions); } - if (callingThreadHasPermission(sInternalSystemWindow)) { - permissions |= layer_state_t::Permission::INTERNAL_SYSTEM_WINDOW; + for (DisplayState display : displays) { + display.sanitize(permissions); + } + + if (!inputWindowCommands.empty() && + (permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) == 0) { + ALOGE("Only privileged callers are allowed to send input commands."); + inputWindowCommands.clear(); } if (flags & (eEarlyWakeupStart | eEarlyWakeupEnd)) { @@ -4517,10 +4521,6 @@ status_t SurfaceFlinger::setTransactionState( const int64_t postTime = systemTime(); - IPCThreadState* ipc = IPCThreadState::self(); - const int originPid = ipc->getCallingPid(); - const int originUid = ipc->getCallingUid(); - std::vector uncacheBufferIds; uncacheBufferIds.reserve(uncacheBuffers.size()); for (const auto& uncacheBuffer : uncacheBuffers) { @@ -4567,12 +4567,11 @@ status_t SurfaceFlinger::setTransactionState( displays, flags, applyToken, - inputWindowCommands, + std::move(inputWindowCommands), desiredPresentTime, isAutoTimestamp, std::move(uncacheBufferIds), postTime, - permissions, hasListenerCallbacks, listenerCallbacks, originPid, @@ -4601,14 +4600,12 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime, bool isAutoTimestamp, const std::vector& uncacheBufferIds, - const int64_t postTime, uint32_t permissions, - bool hasListenerCallbacks, + const int64_t postTime, bool hasListenerCallbacks, const std::vector& listenerCallbacks, int originPid, int originUid, uint64_t transactionId) { uint32_t transactionFlags = 0; if (!mLayerLifecycleManagerEnabled) { for (DisplayState& display : displays) { - display.sanitize(permissions); transactionFlags |= setDisplayStateLocked(display); } } @@ -4625,12 +4622,12 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin if (mLegacyFrontEndEnabled) { clientStateFlags |= setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime, - isAutoTimestamp, postTime, permissions, transactionId); + isAutoTimestamp, postTime, transactionId); } else /*mLayerLifecycleManagerEnabled*/ { clientStateFlags |= updateLayerCallbacksAndStats(frameTimelineInfo, resolvedState, desiredPresentTime, isAutoTimestamp, - postTime, permissions, transactionId); + postTime, transactionId); } if ((flags & eAnimation) && resolvedState.state.surface) { if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) { @@ -4647,12 +4644,7 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin } transactionFlags |= clientStateFlags; - - if (permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) { - transactionFlags |= addInputWindowCommands(inputWindowCommands); - } else if (!inputWindowCommands.empty()) { - ALOGE("Only privileged callers are allowed to send input commands."); - } + transactionFlags |= addInputWindowCommands(inputWindowCommands); for (uint64_t uncacheBufferId : uncacheBufferIds) { mBufferIdsToUncache.push_back(uncacheBufferId); @@ -4689,7 +4681,6 @@ bool SurfaceFlinger::applyAndCommitDisplayTransactionStates( uint32_t transactionFlags = 0; for (auto& transaction : transactions) { for (DisplayState& display : transaction.displays) { - display.sanitize(transaction.permissions); transactionFlags |= setDisplayStateLocked(display); } } @@ -4788,10 +4779,8 @@ bool SurfaceFlinger::callingThreadHasUnscopedSurfaceFlingerAccess(bool usePermis uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTimelineInfo, ResolvedComposerState& composerState, int64_t desiredPresentTime, bool isAutoTimestamp, - int64_t postTime, uint32_t permissions, - uint64_t transactionId) { + int64_t postTime, uint64_t transactionId) { layer_state_t& s = composerState.state; - s.sanitize(permissions); std::vector filteredListeners; for (auto& listener : s.listeners) { @@ -5140,10 +5129,8 @@ uint32_t SurfaceFlinger::updateLayerCallbacksAndStats(const FrameTimelineInfo& f ResolvedComposerState& composerState, int64_t desiredPresentTime, bool isAutoTimestamp, int64_t postTime, - uint32_t permissions, uint64_t transactionId) { layer_state_t& s = composerState.state; - s.sanitize(permissions); std::vector filteredListeners; for (auto& listener : s.listeners) { @@ -5425,7 +5412,6 @@ void SurfaceFlinger::initializeDisplays() { const nsecs_t now = systemTime(); state.desiredPresentTime = now; state.postTime = now; - state.permissions = layer_state_t::ACCESS_SURFACE_FLINGER; state.originPid = mPid; state.originUid = static_cast(getuid()); const uint64_t transactionId = (static_cast(mPid) << 32) | mUniqueTransactionId++; diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 8cc0184e99..cfaa221a45 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -510,7 +510,7 @@ private: status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo, Vector& state, const Vector& displays, uint32_t flags, const sp& applyToken, - const InputWindowCommands& inputWindowCommands, + InputWindowCommands inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, const std::vector& uncacheBuffers, bool hasListenerCallbacks, @@ -731,14 +731,16 @@ private: /* * Transactions */ - bool applyTransactionState( - const FrameTimelineInfo& info, std::vector& state, - Vector& displays, uint32_t flags, - const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime, - bool isAutoTimestamp, const std::vector& uncacheBufferIds, - const int64_t postTime, uint32_t permissions, bool hasListenerCallbacks, - const std::vector& listenerCallbacks, int originPid, int originUid, - uint64_t transactionId) REQUIRES(mStateLock); + bool applyTransactionState(const FrameTimelineInfo& info, + std::vector& state, + Vector& displays, uint32_t flags, + const InputWindowCommands& inputWindowCommands, + const int64_t desiredPresentTime, bool isAutoTimestamp, + const std::vector& uncacheBufferIds, + const int64_t postTime, bool hasListenerCallbacks, + const std::vector& listenerCallbacks, + int originPid, int originUid, uint64_t transactionId) + REQUIRES(mStateLock); // Flush pending transactions that were presented after desiredPresentTime. // For test only bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext); @@ -759,12 +761,11 @@ private: uint32_t setClientStateLocked(const FrameTimelineInfo&, ResolvedComposerState&, int64_t desiredPresentTime, bool isAutoTimestamp, - int64_t postTime, uint32_t permissions, uint64_t transactionId) - REQUIRES(mStateLock); + int64_t postTime, uint64_t transactionId) REQUIRES(mStateLock); uint32_t updateLayerCallbacksAndStats(const FrameTimelineInfo&, ResolvedComposerState&, int64_t desiredPresentTime, bool isAutoTimestamp, - int64_t postTime, uint32_t permissions, - uint64_t transactionId) REQUIRES(mStateLock); + int64_t postTime, uint64_t transactionId) + REQUIRES(mStateLock); uint32_t getTransactionFlags() const; // Sets the masked bits, and schedules a commit if needed. diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h index 35c8b6c647..62a7dfd0f1 100644 --- a/services/surfaceflinger/TransactionState.h +++ b/services/surfaceflinger/TransactionState.h @@ -54,7 +54,7 @@ struct TransactionState { const Vector& displayStates, uint32_t transactionFlags, const sp& applyToken, const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, - std::vector uncacheBufferIds, int64_t postTime, uint32_t permissions, + std::vector uncacheBufferIds, int64_t postTime, bool hasListenerCallbacks, std::vector listenerCallbacks, int originPid, int originUid, uint64_t transactionId) : frameTimelineInfo(frameTimelineInfo), @@ -67,7 +67,6 @@ struct TransactionState { isAutoTimestamp(isAutoTimestamp), uncacheBufferIds(std::move(uncacheBufferIds)), postTime(postTime), - permissions(permissions), hasListenerCallbacks(hasListenerCallbacks), listenerCallbacks(listenerCallbacks), originPid(originPid), @@ -126,7 +125,6 @@ struct TransactionState { bool isAutoTimestamp; std::vector uncacheBufferIds; int64_t postTime; - uint32_t permissions; bool hasListenerCallbacks; std::vector listenerCallbacks; int originPid; diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp index 4a45eb5586..69e9a169e3 100644 --- a/services/surfaceflinger/tests/Credentials_test.cpp +++ b/services/surfaceflinger/tests/Credentials_test.cpp @@ -31,6 +31,7 @@ #include #include #include "utils/ScreenshotUtils.h" +#include "utils/WindowInfosListenerUtils.h" namespace android { @@ -378,6 +379,58 @@ TEST_F(CredentialsTest, GetActiveColorModeBasicCorrectness) { ASSERT_NE(static_cast(BAD_VALUE), colorMode); } +TEST_F(CredentialsTest, TransactionPermissionTest) { + WindowInfosListenerUtils windowInfosListenerUtils; + std::string name = "Test Layer"; + sp token = sp::make(); + WindowInfo windowInfo; + windowInfo.name = name; + windowInfo.token = token; + sp surfaceControl = + mComposerClient->createSurface(String8(name.c_str()), 100, 100, PIXEL_FORMAT_RGBA_8888, + ISurfaceComposerClient::eFXSurfaceBufferState); + const Rect crop(0, 0, 100, 100); + { + UIDFaker f(AID_SYSTEM); + Transaction() + .setLayerStack(surfaceControl, ui::DEFAULT_LAYER_STACK) + .show(surfaceControl) + .setLayer(surfaceControl, INT32_MAX - 1) + .setCrop(surfaceControl, crop) + .setInputWindowInfo(surfaceControl, windowInfo) + .apply(); + } + + // Called from non privileged process + Transaction().setTrustedOverlay(surfaceControl, true); + { + UIDFaker f(AID_SYSTEM); + auto windowIsPresentAndNotTrusted = [&](const std::vector& windowInfos) { + auto foundWindowInfo = + WindowInfosListenerUtils::findMatchingWindowInfo(windowInfo, windowInfos); + if (!foundWindowInfo) { + return false; + } + return !foundWindowInfo->inputConfig.test(WindowInfo::InputConfig::TRUSTED_OVERLAY); + }; + windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndNotTrusted); + } + + { + UIDFaker f(AID_SYSTEM); + Transaction().setTrustedOverlay(surfaceControl, true); + auto windowIsPresentAndTrusted = [&](const std::vector& windowInfos) { + auto foundWindowInfo = + WindowInfosListenerUtils::findMatchingWindowInfo(windowInfo, windowInfos); + if (!foundWindowInfo) { + return false; + } + return foundWindowInfo->inputConfig.test(WindowInfo::InputConfig::TRUSTED_OVERLAY); + }; + windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndTrusted); + } +} + } // namespace android // TODO(b/129481165): remove the #pragma below and fix conversion issues diff --git a/services/surfaceflinger/tests/WindowInfosListener_test.cpp b/services/surfaceflinger/tests/WindowInfosListener_test.cpp index 3f27360cb7..ad9a674456 100644 --- a/services/surfaceflinger/tests/WindowInfosListener_test.cpp +++ b/services/surfaceflinger/tests/WindowInfosListener_test.cpp @@ -20,11 +20,13 @@ #include #include #include +#include "utils/WindowInfosListenerUtils.h" namespace android { using Transaction = SurfaceComposerClient::Transaction; using gui::DisplayInfo; using gui::WindowInfo; +constexpr auto findMatchingWindowInfo = WindowInfosListenerUtils::findMatchingWindowInfo; using WindowInfosPredicate = std::function&)>; @@ -37,45 +39,14 @@ protected: void TearDown() override { seteuid(AID_ROOT); } - struct WindowInfosListener : public gui::WindowInfosListener { - public: - WindowInfosListener(WindowInfosPredicate predicate, std::promise& promise) - : mPredicate(std::move(predicate)), mPromise(promise) {} - - void onWindowInfosChanged(const gui::WindowInfosUpdate& update) override { - if (mPredicate(update.windowInfos)) { - mPromise.set_value(); - } - } - - private: - WindowInfosPredicate mPredicate; - std::promise& mPromise; - }; - sp mClient; + WindowInfosListenerUtils mWindowInfosListenerUtils; - bool waitForWindowInfosPredicate(WindowInfosPredicate predicate) { - std::promise promise; - auto listener = sp::make(std::move(predicate), promise); - mClient->addWindowInfosListener(listener); - auto future = promise.get_future(); - bool satisfied = future.wait_for(std::chrono::seconds{1}) == std::future_status::ready; - mClient->removeWindowInfosListener(listener); - return satisfied; + bool waitForWindowInfosPredicate(const WindowInfosPredicate& predicate) { + return mWindowInfosListenerUtils.waitForWindowInfosPredicate(std::move(predicate)); } }; -const WindowInfo* findMatchingWindowInfo(const WindowInfo& targetWindowInfo, - const std::vector& windowInfos) { - for (const WindowInfo& windowInfo : windowInfos) { - if (windowInfo.token == targetWindowInfo.token) { - return &windowInfo; - } - } - return nullptr; -} - TEST_F(WindowInfosListenerTest, WindowInfoAddedAndRemoved) { std::string name = "Test Layer"; sp token = sp::make(); diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index dbb7c6ce63..6a641b3926 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -386,7 +386,7 @@ public: transaction.applyToken, transaction.inputWindowCommands, transaction.desiredPresentTime, - transaction.isAutoTimestamp, {}, systemTime(), 0, + transaction.isAutoTimestamp, {}, systemTime(), mHasListenerCallbacks, mCallbacks, getpid(), static_cast(getuid()), transaction.id); mFlinger.setTransactionStateInternal(transactionState); diff --git a/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h b/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h new file mode 100644 index 0000000000..8e28a75ed4 --- /dev/null +++ b/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h @@ -0,0 +1,74 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +namespace android { +using Transaction = SurfaceComposerClient::Transaction; +using gui::DisplayInfo; +using gui::WindowInfo; + +using WindowInfosPredicate = std::function&)>; + +class WindowInfosListenerUtils { +public: + WindowInfosListenerUtils() { mClient = sp::make(); } + + bool waitForWindowInfosPredicate(const WindowInfosPredicate& predicate) { + std::promise promise; + auto listener = sp::make(std::move(predicate), promise); + mClient->addWindowInfosListener(listener); + auto future = promise.get_future(); + bool satisfied = future.wait_for(std::chrono::seconds{1}) == std::future_status::ready; + mClient->removeWindowInfosListener(listener); + return satisfied; + } + + static const WindowInfo* findMatchingWindowInfo(const WindowInfo& targetWindowInfo, + const std::vector& windowInfos) { + for (const WindowInfo& windowInfo : windowInfos) { + if (windowInfo.token == targetWindowInfo.token) { + return &windowInfo; + } + } + return nullptr; + } + +private: + struct WindowInfosListener : public gui::WindowInfosListener { + public: + WindowInfosListener(WindowInfosPredicate predicate, std::promise& promise) + : mPredicate(std::move(predicate)), mPromise(promise) {} + + void onWindowInfosChanged(const gui::WindowInfosUpdate& update) override { + if (mPredicate(update.windowInfos)) { + mPromise.set_value(); + } + } + + private: + WindowInfosPredicate mPredicate; + std::promise& mPromise; + }; + + sp mClient; +}; + +} // namespace android -- GitLab From 0bc64a80291111ad9356377e7247acdf77325e75 Mon Sep 17 00:00:00 2001 From: Yuxin Hu Date: Mon, 8 May 2023 23:48:42 +0000 Subject: [PATCH 1221/1310] Fix the missing extension EGL_ANDROID_image_native_buffer In http://ag/22040002, EGL_ANDROID_get_frame_timestamps is conditionally added to the mExtensionString. A space at the end of the extension name is mandatory, because we use ' ' or '\0' as an indicator when parsing the mExtensionString to find each individual extension name. Bug: b/269060366 Bug: b/280016556 Test: m; atest android.hardware.nativehardware.cts.AHardwareBufferNativeTests Change-Id: Ie8ddc3018c37d77167b95210d89c7e7f7e6e69e7 --- opengl/libs/EGL/egl_display.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opengl/libs/EGL/egl_display.cpp b/opengl/libs/EGL/egl_display.cpp index c2c856e22a..6593c1bb16 100644 --- a/opengl/libs/EGL/egl_display.cpp +++ b/opengl/libs/EGL/egl_display.cpp @@ -326,10 +326,10 @@ EGLBoolean egl_display_t::initialize(EGLint* major, EGLint* minor) { // device's present timestamps are reliable (which may not be the case on emulators). if (cnx->useAngle) { if (android::base::GetBoolProperty("service.sf.present_timestamp", false)) { - mExtensionString.append("EGL_ANDROID_get_frame_timestamps"); + mExtensionString.append("EGL_ANDROID_get_frame_timestamps "); } } else { - mExtensionString.append("EGL_ANDROID_get_frame_timestamps"); + mExtensionString.append("EGL_ANDROID_get_frame_timestamps "); } hasColorSpaceSupport = findExtension(disp.queryString.extensions, "EGL_KHR_gl_colorspace"); -- GitLab From afa08cc62d993eabafd8676a296f55e4c86e05bb Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 8 May 2023 22:35:50 -0700 Subject: [PATCH 1222/1310] Slip touch when the new window is valid Previously, the condition for slipping touch was based on the difference in window tokens. However, that check missed the possibility of the new window handle being null. In this CL, that check is returned. To reproduce this crash, touch needs to try to slip from a slippery window into a window that requests to drop input. Bug: 281601168 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: I0add7ee65a6492695e9b2368c83fe553677a32f6 --- .../dispatcher/InputDispatcher.cpp | 3 +- .../tests/InputDispatcher_test.cpp | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 326ca87c41..f3ada8e298 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2476,7 +2476,8 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( newTouchedWindowHandle = nullptr; } - if (!haveSameToken(oldTouchedWindowHandle, newTouchedWindowHandle)) { + if (newTouchedWindowHandle != nullptr && + !haveSameToken(oldTouchedWindowHandle, newTouchedWindowHandle)) { ALOGD("Touch is slipping out of window %s into window %s in display %" PRId32, oldTouchedWindowHandle->getName().c_str(), newTouchedWindowHandle->getName().c_str(), displayId); diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index a6cdee5fb1..f6f02d8241 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -5535,6 +5535,46 @@ TEST_F(InputDispatcherTest, SlipperyWindow_SetsFlagPartiallyObscured) { AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED); } +/** + * Two windows, one on the left and another on the right. The left window is slippery. The right + * window isn't eligible to receive touch because it specifies InputConfig::DROP_INPUT. When the + * touch moves from the left window into the right window, the gesture should continue to go to the + * left window. Touch shouldn't slip because the right window can't receive touches. This test + * reproduces a crash. + */ +TEST_F(InputDispatcherTest, TouchSlippingIntoWindowThatDropsTouches) { + std::shared_ptr application = std::make_shared(); + + sp leftSlipperyWindow = + sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftSlipperyWindow->setSlippery(true); + leftSlipperyWindow->setFrame(Rect(0, 0, 100, 100)); + + sp rightDropTouchesWindow = + sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightDropTouchesWindow->setFrame(Rect(100, 0, 200, 100)); + rightDropTouchesWindow->setDropInput(true); + + mDispatcher->setInputWindows( + {{ADISPLAY_ID_DEFAULT, {leftSlipperyWindow, rightDropTouchesWindow}}}); + + // Start touch in the left window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .build()); + leftSlipperyWindow->consumeMotionDown(); + + // And move it into the right window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50)) + .build()); + + // Since the right window isn't eligible to receive input, touch does not slip. + // The left window continues to receive the gesture. + leftSlipperyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + rightDropTouchesWindow->assertNoEvents(); +} + class InputDispatcherKeyRepeatTest : public InputDispatcherTest { protected: static constexpr nsecs_t KEY_REPEAT_TIMEOUT = 40 * 1000000; // 40 ms -- GitLab From 0bd940a85d6f202aeeeb97d63b0ae4998f931776 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Thu, 27 Apr 2023 13:01:58 +0000 Subject: [PATCH 1223/1310] InputMapper refactor: ExternalStylusInputMapper Add a factory method for ExternalStylusInputMapper to be configured on initilisation Test: m checkinput && atest libinput_tests inputflinger_tests Bug: 256009910 Change-Id: If75377416f4f6d3954480bf4e32886f3daf7f1f3 (cherry picked from commit c55f75fc90e99c55a7133f82c407520f22937d96) --- services/inputflinger/reader/InputDevice.cpp | 2 +- .../reader/mapper/ExternalStylusInputMapper.h | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index ec8a443eba..721bb0a962 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -520,7 +520,7 @@ std::vector> InputDevice::createMappers( // External stylus-like devices. if (classes.test(InputDeviceClass::EXTERNAL_STYLUS)) { - mappers.push_back(std::make_unique(contextPtr, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } return mappers; } diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h index 841c437543..97df02b69f 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h @@ -26,8 +26,10 @@ namespace android { class ExternalStylusInputMapper : public InputMapper { public: - explicit ExternalStylusInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); virtual ~ExternalStylusInputMapper() = default; uint32_t getSources() const override; @@ -46,6 +48,8 @@ private: StylusState mStylusState; + explicit ExternalStylusInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); [[nodiscard]] std::list sync(nsecs_t when); }; -- GitLab From 2be4a366e270c32c41160ebd9bdc4c897245e866 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Wed, 26 Apr 2023 14:16:50 +0000 Subject: [PATCH 1224/1310] InputMapper refactor: JoystickInputMapper Add a factory method for JoystickInputMapper to be configured on initilisation Test: m checkinput && atest libinput_tests inputflinger_tests Bug: 256009910 Change-Id: I7fbcd9590a91af8b3b61bb6f3824f66eda800410 (cherry picked from commit ae876351487643d78691486792dfe7fd00dd60e9) --- services/inputflinger/reader/InputDevice.cpp | 2 +- .../inputflinger/reader/mapper/JoystickInputMapper.h | 9 +++++++-- services/inputflinger/tests/InputReader_test.cpp | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index ec8a443eba..3259aaf47c 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -510,7 +510,7 @@ std::vector> InputDevice::createMappers( // Joystick-like devices. if (classes.test(InputDeviceClass::JOYSTICK)) { - mappers.push_back(std::make_unique(contextPtr, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // Motion sensor enabled devices. diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.h b/services/inputflinger/reader/mapper/JoystickInputMapper.h index 49673a2f4e..313f0922b7 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.h +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h @@ -22,8 +22,10 @@ namespace android { class JoystickInputMapper : public InputMapper { public: - explicit JoystickInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); virtual ~JoystickInputMapper(); virtual uint32_t getSources() const override; @@ -87,6 +89,9 @@ private: } }; + explicit JoystickInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); + static Axis createAxis(const AxisInfo& AxisInfo, const RawAbsoluteAxisInfo& rawAxisInfo, bool explicitlyMapped); diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index ae3ae6c006..b331196817 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -10904,7 +10904,7 @@ const int32_t JoystickInputMapperTest::RAW_Y_MAX = 32767; TEST_F(JoystickInputMapperTest, Configure_AssignsDisplayUniqueId) { prepareAxes(); - JoystickInputMapper& mapper = addMapperAndConfigure(); + JoystickInputMapper& mapper = constructAndAddMapper(); mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, VIRTUAL_DISPLAY_UNIQUE_ID); -- GitLab From d8510bd14f60e91fc2e6e47a04af604e7eac69c5 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Thu, 27 Apr 2023 12:48:15 +0000 Subject: [PATCH 1225/1310] InputMapper refactor: CursorInputMapper Add a factory method for CursorInputMapper(s) to be configured on initilisation Test: m checkinput && atest libinput_tests inputflinger_tests Bug: 256009910 Change-Id: Id789ff890b7e147de16c07a9dcd095a21eba43f9 (cherry picked from commit e036ad77ed50cbd0b908cb7304ce5e9539f06d8f) --- services/inputflinger/reader/InputDevice.cpp | 2 +- .../reader/mapper/CursorInputMapper.cpp | 52 ++++++++----------- .../reader/mapper/CursorInputMapper.h | 12 +++-- .../inputflinger/tests/InputReader_test.cpp | 42 +++++++-------- 4 files changed, 52 insertions(+), 56 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index ec8a443eba..b0c44880c5 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -488,7 +488,7 @@ std::vector> InputDevice::createMappers( // Cursor-like devices. if (classes.test(InputDeviceClass::CURSOR)) { - mappers.push_back(std::make_unique(contextPtr, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // Touchscreens and touchpad devices. diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index 8ef5ff6358..c684ed40b6 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -71,9 +71,7 @@ void CursorMotionAccumulator::finishSync() { CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig) : InputMapper(deviceContext, readerConfig), - mLastEventTime(std::numeric_limits::min()) { - configureWithZeroChanges(readerConfig); -} + mLastEventTime(std::numeric_limits::min()) {} CursorInputMapper::~CursorInputMapper() { if (mPointerController != nullptr) { @@ -142,46 +140,51 @@ std::list CursorInputMapper::reconfigure(nsecs_t when, ConfigurationChanges changes) { std::list out = InputMapper::reconfigure(when, readerConfig, changes); - if (!changes.any()) { - configureWithZeroChanges(readerConfig); - return out; + if (!changes.any()) { // first time only + configureBasicParams(); } - const bool configurePointerCapture = mParameters.mode != Parameters::Mode::NAVIGATION && - changes.test(InputReaderConfiguration::Change::POINTER_CAPTURE); + const bool configurePointerCapture = !changes.any() || + (mParameters.mode != Parameters::Mode::NAVIGATION && + changes.test(InputReaderConfiguration::Change::POINTER_CAPTURE)); if (configurePointerCapture) { configureOnPointerCapture(readerConfig); out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId())); } - if (changes.test(InputReaderConfiguration::Change::POINTER_SPEED) || configurePointerCapture) { + if (!changes.any() || changes.test(InputReaderConfiguration::Change::POINTER_SPEED) || + configurePointerCapture) { configureOnChangePointerSpeed(readerConfig); } - if (changes.test(InputReaderConfiguration::Change::DISPLAY_INFO) || configurePointerCapture) { + if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO) || + configurePointerCapture) { configureOnChangeDisplayInfo(readerConfig); } return out; } -void CursorInputMapper::configureParameters() { - mParameters.mode = Parameters::Mode::POINTER; - const PropertyMap& config = getDeviceContext().getConfiguration(); +CursorInputMapper::Parameters CursorInputMapper::computeParameters( + const InputDeviceContext& deviceContext) { + Parameters parameters; + parameters.mode = Parameters::Mode::POINTER; + const PropertyMap& config = deviceContext.getConfiguration(); std::optional cursorModeString = config.getString("cursor.mode"); if (cursorModeString.has_value()) { if (*cursorModeString == "navigation") { - mParameters.mode = Parameters::Mode::NAVIGATION; + parameters.mode = Parameters::Mode::NAVIGATION; } else if (*cursorModeString != "pointer" && *cursorModeString != "default") { ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString->c_str()); } } - mParameters.orientationAware = config.getBool("cursor.orientationAware").value_or(false); + parameters.orientationAware = config.getBool("cursor.orientationAware").value_or(false); - mParameters.hasAssociatedDisplay = false; - if (mParameters.mode == Parameters::Mode::POINTER || mParameters.orientationAware) { - mParameters.hasAssociatedDisplay = true; + parameters.hasAssociatedDisplay = false; + if (parameters.mode == Parameters::Mode::POINTER || parameters.orientationAware) { + parameters.hasAssociatedDisplay = true; } + return parameters; } void CursorInputMapper::dumpParameters(std::string& dump) { @@ -424,22 +427,11 @@ std::optional CursorInputMapper::getAssociatedDisplayId() { return mDisplayId; } -void CursorInputMapper::configureWithZeroChanges(const InputReaderConfiguration& readerConfig) { - // Configuration with zero changes - configureBasicParams(); - if (mParameters.mode != Parameters::Mode::NAVIGATION && - readerConfig.pointerCaptureRequest.enable) { - configureOnPointerCapture(readerConfig); - } - configureOnChangePointerSpeed(readerConfig); - configureOnChangeDisplayInfo(readerConfig); -} - void CursorInputMapper::configureBasicParams() { mCursorScrollAccumulator.configure(getDeviceContext()); // Configure basic parameters. - configureParameters(); + mParameters = computeParameters(getDeviceContext()); // Configure device mode. switch (mParameters.mode) { diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index caf2e5a4c4..b879bfdd08 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -53,8 +53,10 @@ private: class CursorInputMapper : public InputMapper { public: - explicit CursorInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); virtual ~CursorInputMapper(); virtual uint32_t getSources() const override; @@ -125,15 +127,17 @@ private: nsecs_t mDownTime; nsecs_t mLastEventTime; - void configureParameters(); + explicit CursorInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); void dumpParameters(std::string& dump); - void configureWithZeroChanges(const InputReaderConfiguration& readerConfig); void configureBasicParams(); void configureOnPointerCapture(const InputReaderConfiguration& config); void configureOnChangePointerSpeed(const InputReaderConfiguration& config); void configureOnChangeDisplayInfo(const InputReaderConfiguration& config); [[nodiscard]] std::list sync(nsecs_t when, nsecs_t readTime); + + static Parameters computeParameters(const InputDeviceContext& deviceContext); }; } // namespace android diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index ae3ae6c006..db88e4d5de 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -3862,21 +3862,21 @@ void CursorInputMapperTest::testMotionRotation(CursorInputMapper& mapper, int32_ TEST_F(CursorInputMapperTest, WhenModeIsPointer_GetSources_ReturnsMouse) { addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); } TEST_F(CursorInputMapperTest, WhenModeIsNavigation_GetSources_ReturnsTrackball) { addConfigurationProperty("cursor.mode", "navigation"); - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, mapper.getSources()); } TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeFromPointerController) { addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); InputDeviceInfo info; mapper.populateDeviceInfo(info); @@ -3906,7 +3906,7 @@ TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeF TEST_F(CursorInputMapperTest, WhenModeIsNavigation_PopulateDeviceInfo_ReturnsScaledRange) { addConfigurationProperty("cursor.mode", "navigation"); - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); InputDeviceInfo info; mapper.populateDeviceInfo(info); @@ -3924,7 +3924,7 @@ TEST_F(CursorInputMapperTest, WhenModeIsNavigation_PopulateDeviceInfo_ReturnsSca TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaState) { addConfigurationProperty("cursor.mode", "navigation"); - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON); @@ -4012,7 +4012,7 @@ TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaStat TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentXYUpdates) { addConfigurationProperty("cursor.mode", "navigation"); - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs args; @@ -4036,7 +4036,7 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentXYUpdates) { TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) { addConfigurationProperty("cursor.mode", "navigation"); - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs args; @@ -4065,7 +4065,7 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) { TEST_F(CursorInputMapperTest, Process_ShouldHandleCombinedXYAndButtonUpdates) { addConfigurationProperty("cursor.mode", "navigation"); - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); NotifyMotionArgs args; @@ -4114,7 +4114,7 @@ TEST_F(CursorInputMapperTest, Process_WhenOrientationAware_ShouldNotRotateMotion // InputReader works in the un-rotated coordinate space, so orientation-aware devices do not // need to be rotated. addConfigurationProperty("cursor.orientationAware", "1"); - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); prepareDisplay(ui::ROTATION_90); ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, 1)); @@ -4132,7 +4132,7 @@ TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotion addConfigurationProperty("cursor.mode", "navigation"); // Since InputReader works in the un-rotated coordinate space, only devices that are not // orientation-aware are affected by display rotation. - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); clearViewports(); prepareDisplay(ui::ROTATION_0); @@ -4181,7 +4181,7 @@ TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotion TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); mFakePointerController->setPosition(100, 200); @@ -4435,7 +4435,7 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) { TEST_F(CursorInputMapperTest, Process_WhenModeIsPointer_ShouldMoveThePointerAround) { addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); mFakePointerController->setPosition(100, 200); @@ -4456,7 +4456,7 @@ TEST_F(CursorInputMapperTest, Process_WhenModeIsPointer_ShouldMoveThePointerArou TEST_F(CursorInputMapperTest, Process_PointerCapture) { addConfigurationProperty("cursor.mode", "pointer"); mFakePolicy->setPointerCapture(true); - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); NotifyDeviceResetArgs resetArgs; ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); @@ -4548,7 +4548,7 @@ TEST_F(CursorInputMapperTest, PointerCaptureDisablesVelocityProcessing) { const VelocityControlParameters testParams(/*scale=*/5.f, /*low threshold=*/0.f, /*high threshold=*/100.f, /*acceleration=*/10.f); mFakePolicy->setVelocityControlParams(testParams); - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); NotifyDeviceResetArgs resetArgs; ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); @@ -4589,7 +4589,7 @@ TEST_F(CursorInputMapperTest, PointerCaptureDisablesVelocityProcessing) { TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) { addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); NotifyDeviceResetArgs resetArgs; ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); @@ -4630,7 +4630,7 @@ TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) { } TEST_F(CursorInputMapperTest, ConfigureDisplayId_NoAssociatedViewport) { - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); // Set up the default display. prepareDisplay(ui::ROTATION_90); @@ -4656,7 +4656,7 @@ TEST_F(CursorInputMapperTest, ConfigureDisplayId_NoAssociatedViewport) { } TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) { - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); // Set up the default display. prepareDisplay(ui::ROTATION_90); @@ -4682,7 +4682,7 @@ TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) { } TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPointerDisplay) { - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); // Set up the default display as the display on which the pointer should be shown. prepareDisplay(ui::ROTATION_90); @@ -4715,7 +4715,7 @@ protected: TEST_F(BluetoothCursorInputMapperTest, TimestampSmoothening) { addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); nsecs_t kernelEventTime = ARBITRARY_TIME; nsecs_t expectedEventTime = ARBITRARY_TIME; @@ -4742,7 +4742,7 @@ TEST_F(BluetoothCursorInputMapperTest, TimestampSmoothening) { TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningIsCapped) { addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); nsecs_t expectedEventTime = ARBITRARY_TIME; process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); @@ -4779,7 +4779,7 @@ TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningIsCapped) { TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningNotUsed) { addConfigurationProperty("cursor.mode", "pointer"); - CursorInputMapper& mapper = addMapperAndConfigure(); + CursorInputMapper& mapper = constructAndAddMapper(); nsecs_t kernelEventTime = ARBITRARY_TIME; nsecs_t expectedEventTime = ARBITRARY_TIME; -- GitLab From 54a45f6450005c3527046273d18a08209a92a8aa Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Wed, 26 Apr 2023 16:12:10 +0000 Subject: [PATCH 1226/1310] InputMapper refactor: SwitchInputMapper Add a factory method for SwitchInputMapper to be configured on initilisation Test: m checkinput && atest libinput_tests inputflinger_tests Bug: 256009910 Change-Id: Ib2d8e680f72a3e07202fc6e4db7a48e54528555f (cherry picked from commit df992eb9dac0100bfcb8a67a992abb5ff97ea35b) --- services/inputflinger/reader/InputDevice.cpp | 2 +- services/inputflinger/reader/mapper/SwitchInputMapper.h | 8 ++++++-- services/inputflinger/tests/InputReader_test.cpp | 6 +++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index ec8a443eba..4a45e175bb 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -446,7 +446,7 @@ std::vector> InputDevice::createMappers( // Switch-like devices. if (classes.test(InputDeviceClass::SWITCH)) { - mappers.push_back(std::make_unique(contextPtr, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // Scroll wheel-like devices. diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.h b/services/inputflinger/reader/mapper/SwitchInputMapper.h index 7ec282b721..2fb48bbf25 100644 --- a/services/inputflinger/reader/mapper/SwitchInputMapper.h +++ b/services/inputflinger/reader/mapper/SwitchInputMapper.h @@ -22,8 +22,10 @@ namespace android { class SwitchInputMapper : public InputMapper { public: - explicit SwitchInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); virtual ~SwitchInputMapper(); virtual uint32_t getSources() const override; @@ -36,6 +38,8 @@ private: uint32_t mSwitchValues; uint32_t mUpdatedSwitchMask; + explicit SwitchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); void processSwitch(int32_t switchCode, int32_t switchValue); [[nodiscard]] std::list sync(nsecs_t when); }; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index ae3ae6c006..e74284f02b 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -2603,13 +2603,13 @@ protected: }; TEST_F(SwitchInputMapperTest, GetSources) { - SwitchInputMapper& mapper = addMapperAndConfigure(); + SwitchInputMapper& mapper = constructAndAddMapper(); ASSERT_EQ(uint32_t(AINPUT_SOURCE_SWITCH), mapper.getSources()); } TEST_F(SwitchInputMapperTest, GetSwitchState) { - SwitchInputMapper& mapper = addMapperAndConfigure(); + SwitchInputMapper& mapper = constructAndAddMapper(); mFakeEventHub->setSwitchState(EVENTHUB_ID, SW_LID, 1); ASSERT_EQ(1, mapper.getSwitchState(AINPUT_SOURCE_ANY, SW_LID)); @@ -2619,7 +2619,7 @@ TEST_F(SwitchInputMapperTest, GetSwitchState) { } TEST_F(SwitchInputMapperTest, Process) { - SwitchInputMapper& mapper = addMapperAndConfigure(); + SwitchInputMapper& mapper = constructAndAddMapper(); std::list out; out = process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_LID, 1); ASSERT_TRUE(out.empty()); -- GitLab From e08bdbdcadf2143946b7bd5b7020db834b1d8ff9 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Wed, 26 Apr 2023 15:20:33 +0000 Subject: [PATCH 1227/1310] InputMapper refactor: TouchpadInputMapper Add a factory method for TouchpadInputMapper to be configured on initilisation Test: m checkinput && atest libinput_tests inputflinger_tests Bug: 256009910 Change-Id: I1189eede635330b24f3f1175d2aa4b46775362b7 (cherry picked from commit 04e643ceacce438363afb521b7a1ff429562aab7) --- services/inputflinger/reader/InputDevice.cpp | 2 +- services/inputflinger/reader/mapper/TouchpadInputMapper.h | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index ec8a443eba..7a1f78c2cb 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -501,7 +501,7 @@ std::vector> InputDevice::createMappers( (identifier.product == 0x05c4 || identifier.product == 0x09cc); if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) && classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) { - mappers.push_back(std::make_unique(contextPtr, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH_MT)) { mappers.push_back(createInputMapper(contextPtr, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH)) { diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index 3128d18a05..23d0fd3cf3 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -40,8 +40,10 @@ namespace android { class TouchpadInputMapper : public InputMapper { public: - explicit TouchpadInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); ~TouchpadInputMapper(); uint32_t getSources() const override; @@ -58,6 +60,8 @@ public: private: void resetGestureInterpreter(nsecs_t when); + explicit TouchpadInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); [[nodiscard]] std::list sendHardwareState(nsecs_t when, nsecs_t readTime, SelfContainedHardwareState schs); [[nodiscard]] std::list processGestures(nsecs_t when, nsecs_t readTime); -- GitLab From 61dd33e220cb88bcd76aaf7be92ce8e4f06b3468 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Wed, 26 Apr 2023 15:07:55 +0000 Subject: [PATCH 1228/1310] InputMapper refactor: SensorInputMapper Add a factory method for SensorInputMapper to be configured on initilisation Test: m checkinput && atest libinput_tests inputflinger_tests Bug: 256009910 Change-Id: Ib1b32424cebc57c789288ce86d7c5e8d145dc552 (cherry picked from commit fb706c3835608366381f4bbb5e921e798be4fe52) --- services/inputflinger/reader/InputDevice.cpp | 2 +- services/inputflinger/reader/mapper/SensorInputMapper.h | 9 +++++++-- services/inputflinger/tests/InputReader_test.cpp | 6 +++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index ec8a443eba..93b26faee2 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -515,7 +515,7 @@ std::vector> InputDevice::createMappers( // Motion sensor enabled devices. if (classes.test(InputDeviceClass::SENSOR)) { - mappers.push_back(std::make_unique(contextPtr, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // External stylus-like devices. diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h index 1f82559db8..a55dcd1905 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.h +++ b/services/inputflinger/reader/mapper/SensorInputMapper.h @@ -27,8 +27,10 @@ static constexpr ssize_t SENSOR_VEC_LEN = 3; class SensorInputMapper : public InputMapper { public: - explicit SensorInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); ~SensorInputMapper() override; uint32_t getSources() const override; @@ -106,6 +108,9 @@ private: } }; + explicit SensorInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); + static Axis createAxis(const AxisInfo& AxisInfo, const RawAbsoluteAxisInfo& rawAxisInfo); // Axes indexed by raw ABS_* axis index. diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index ae3ae6c006..f821ef430a 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -2784,7 +2784,7 @@ void SensorInputMapperTest::setGyroProperties() { } TEST_F(SensorInputMapperTest, GetSources) { - SensorInputMapper& mapper = addMapperAndConfigure(); + SensorInputMapper& mapper = constructAndAddMapper(); ASSERT_EQ(static_cast(AINPUT_SOURCE_SENSOR), mapper.getSources()); } @@ -2792,7 +2792,7 @@ TEST_F(SensorInputMapperTest, GetSources) { TEST_F(SensorInputMapperTest, ProcessAccelerometerSensor) { setAccelProperties(); prepareAccelAxes(); - SensorInputMapper& mapper = addMapperAndConfigure(); + SensorInputMapper& mapper = constructAndAddMapper(); ASSERT_TRUE(mapper.enableSensor(InputDeviceSensorType::ACCELEROMETER, std::chrono::microseconds(10000), @@ -2822,7 +2822,7 @@ TEST_F(SensorInputMapperTest, ProcessAccelerometerSensor) { TEST_F(SensorInputMapperTest, ProcessGyroscopeSensor) { setGyroProperties(); prepareGyroAxes(); - SensorInputMapper& mapper = addMapperAndConfigure(); + SensorInputMapper& mapper = constructAndAddMapper(); ASSERT_TRUE(mapper.enableSensor(InputDeviceSensorType::GYROSCOPE, std::chrono::microseconds(10000), -- GitLab From 5ed7749a61fcf6be171d9498ca7c441a5cc5d8f4 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Wed, 26 Apr 2023 14:54:20 +0000 Subject: [PATCH 1229/1310] InputMapper refactor: RotaryEncoderInputMapper Add a factory method for RotaryEncoderInputMapper to be configured on initilisation Test: m checkinput && atest libinput_tests inputflinger_tests Bug: 256009910 Change-Id: I9b2c9409e98f5a8fa389538bb99c4c9c10d780a5 (cherry picked from commit f224f62b67d47aa1d675f4274b3e2389c3b7589a) --- services/inputflinger/reader/InputDevice.cpp | 2 +- .../inputflinger/reader/mapper/RotaryEncoderInputMapper.h | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index ec8a443eba..ff422cf257 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -451,7 +451,7 @@ std::vector> InputDevice::createMappers( // Scroll wheel-like devices. if (classes.test(InputDeviceClass::ROTARY_ENCODER)) { - mappers.push_back(std::make_unique(contextPtr, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // Vibrator-like devices. diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h index d3dcbe1bb4..9e2e8c4342 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h @@ -25,8 +25,10 @@ namespace android { class RotaryEncoderInputMapper : public InputMapper { public: - explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); virtual ~RotaryEncoderInputMapper(); virtual uint32_t getSources() const override; @@ -45,6 +47,8 @@ private: float mScalingFactor; ui::Rotation mOrientation; + explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); [[nodiscard]] std::list sync(nsecs_t when, nsecs_t readTime); }; -- GitLab From cf0bb43409962a0184f2e79bac547dd11f07abab Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Tue, 9 May 2023 11:49:16 -0400 Subject: [PATCH 1230/1310] SF: Tie main thread priority to active display Otherwise, the powering off of the outer display when exiting concurrent mode causes SF to drop real-time priority until the next powering on of either display. Fixes: 271431077 Test: Logs during boot, power on/off, fold/unfold, concurrent mode. Change-Id: I4f1590d7abf82977c1acd162605f578624bc9faa --- services/surfaceflinger/SurfaceFlinger.cpp | 43 ++++++++++++------- .../surfaceflinger/main_surfaceflinger.cpp | 4 +- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 3c55caf188..91f945b08b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5488,14 +5488,19 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: onActiveDisplayChangedLocked(activeDisplay.get(), *display); } - // Keep uclamp in a separate syscall and set it before changing to RT due to b/190237315. - // We can merge the syscall later. - if (SurfaceFlinger::setSchedAttr(true) != NO_ERROR) { - ALOGW("Couldn't set uclamp.min on display on: %s\n", strerror(errno)); - } - if (SurfaceFlinger::setSchedFifo(true) != NO_ERROR) { - ALOGW("Couldn't set SCHED_FIFO on display on: %s\n", strerror(errno)); + if (displayId == mActiveDisplayId) { + // TODO(b/281692563): Merge the syscalls. For now, keep uclamp in a separate syscall and + // set it before SCHED_FIFO due to b/190237315. + if (setSchedAttr(true) != NO_ERROR) { + ALOGW("Failed to set uclamp.min after powering on active display: %s", + strerror(errno)); + } + if (setSchedFifo(true) != NO_ERROR) { + ALOGW("Failed to set SCHED_FIFO after powering on active display: %s", + strerror(errno)); + } } + getHwComposer().setPowerMode(displayId, mode); if (displayId == mActiveDisplayId && mode != hal::PowerMode::DOZE_SUSPEND) { setHWCVsyncEnabled(displayId, @@ -5509,15 +5514,21 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: scheduleComposite(FrameHint::kActive); } else if (mode == hal::PowerMode::OFF) { // Turn off the display - if (SurfaceFlinger::setSchedFifo(false) != NO_ERROR) { - ALOGW("Couldn't set SCHED_OTHER on display off: %s\n", strerror(errno)); - } - if (SurfaceFlinger::setSchedAttr(false) != NO_ERROR) { - ALOGW("Couldn't set uclamp.min on display off: %s\n", strerror(errno)); - } - if (displayId == mActiveDisplayId && *currentModeOpt != hal::PowerMode::DOZE_SUSPEND) { - mScheduler->disableHardwareVsync(displayId, true); - mScheduler->enableSyntheticVsync(); + + if (displayId == mActiveDisplayId) { + if (setSchedFifo(false) != NO_ERROR) { + ALOGW("Failed to set SCHED_OTHER after powering off active display: %s", + strerror(errno)); + } + if (setSchedAttr(false) != NO_ERROR) { + ALOGW("Failed set uclamp.min after powering off active display: %s", + strerror(errno)); + } + + if (*currentModeOpt != hal::PowerMode::DOZE_SUSPEND) { + mScheduler->disableHardwareVsync(displayId, true); + mScheduler->enableSyntheticVsync(); + } } // Make sure HWVsync is disabled before turning off the display diff --git a/services/surfaceflinger/main_surfaceflinger.cpp b/services/surfaceflinger/main_surfaceflinger.cpp index 049567878f..cf23169eae 100644 --- a/services/surfaceflinger/main_surfaceflinger.cpp +++ b/services/surfaceflinger/main_surfaceflinger.cpp @@ -91,7 +91,7 @@ int main(int, char**) { // Set uclamp.min setting on all threads, maybe an overkill but we want // to cover important threads like RenderEngine. if (SurfaceFlinger::setSchedAttr(true) != NO_ERROR) { - ALOGW("Couldn't set uclamp.min: %s\n", strerror(errno)); + ALOGW("Failed to set uclamp.min during boot: %s", strerror(errno)); } // The binder threadpool we start will inherit sched policy and priority @@ -155,7 +155,7 @@ int main(int, char**) { startDisplayService(); // dependency on SF getting registered above if (SurfaceFlinger::setSchedFifo(true) != NO_ERROR) { - ALOGW("Couldn't set to SCHED_FIFO: %s", strerror(errno)); + ALOGW("Failed to set SCHED_FIFO during boot: %s", strerror(errno)); } // run surface flinger in this thread -- GitLab From d39fb0823880f508419da4b75aaee74e5abadd34 Mon Sep 17 00:00:00 2001 From: Ram Mohan Date: Tue, 9 May 2023 17:25:49 +0000 Subject: [PATCH 1231/1310] ultrahdr: signal all threads that no further jobs will be queued After queuing all jobs, the master thread quietly leaves without intimating the worker threads about the same. If worker threads are waiting for more jobs, they will be waiting forever or until a spurious wake occurs. Instead of waiting for spurious wake, signal all workers that no more jobs will be queued Bug: 279984162 Test: CTS: ImageReaderTest#testSRGBJpeg (cherry picked from https://partner-android-review.googlesource.com/q/commit:db2a72623137e59cae7d9a74b0ba4ebc3f7ee36a) Merged-In: Ic0e06b8ee355b08f0cd1031488cbf0ead8021fa2 Change-Id: Ic0e06b8ee355b08f0cd1031488cbf0ead8021fa2 --- libs/ultrahdr/jpegr.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp index 0f7aa27354..ef927e3c56 100644 --- a/libs/ultrahdr/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -534,7 +534,7 @@ bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) { if (mQueuedAllJobs) { return false; } else { - mCv.wait(lock); + mCv.wait_for(lock, std::chrono::milliseconds(100)); } } else { auto it = mJobs.begin(); @@ -557,6 +557,8 @@ void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) { void JobQueue::markQueueForEnd() { std::unique_lock lock{mMutex}; mQueuedAllJobs = true; + lock.unlock(); + mCv.notify_all(); } void JobQueue::reset() { -- GitLab From 552691605bc21fd94b5ae739c4b0bd32164ca4ea Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Tue, 9 May 2023 11:26:06 -0700 Subject: [PATCH 1232/1310] SF: use TextureView hint when selecting the refresh rate SF would only try to heuristically calculate the frame rate of a layer when TextureView is updating. This fixes a bug where SF tries to heuristically calculate the frame rate for UI animations but fails due to long frames. Bug: 280249265 Test: Playing a video on Facebook and observe refresh rate Test: go/cb-pcmark Change-Id: Ic98484b8313f1e8e6ad01297b63bb64da6d4a6dd --- libs/gui/Surface.cpp | 2 ++ libs/gui/SurfaceComposerClient.cpp | 3 +++ .../aidl/android/gui/FrameTimelineInfo.aidl | 4 +++ libs/nativewindow/include/system/window.h | 11 ++++---- services/surfaceflinger/Layer.cpp | 25 ++++++++++++++++--- services/surfaceflinger/Layer.h | 1 + 6 files changed, 36 insertions(+), 10 deletions(-) diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index a5cf8d6bf0..44d359488e 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -1871,12 +1871,14 @@ int Surface::dispatchSetFrameTimelineInfo(va_list args) { auto frameTimelineVsyncId = static_cast(va_arg(args, int64_t)); auto inputEventId = static_cast(va_arg(args, int32_t)); auto startTimeNanos = static_cast(va_arg(args, int64_t)); + auto useForRefreshRateSelection = static_cast(va_arg(args, int32_t)); ALOGV("Surface::%s", __func__); FrameTimelineInfo ftlInfo; ftlInfo.vsyncId = frameTimelineVsyncId; ftlInfo.inputEventId = inputEventId; ftlInfo.startTimeNanos = startTimeNanos; + ftlInfo.useForRefreshRateSelection = useForRefreshRateSelection; return setFrameTimelineInfo(frameNumber, ftlInfo); } diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 1b13ec1c06..c6c7367f9e 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2245,11 +2245,13 @@ void SurfaceComposerClient::Transaction::mergeFrameTimelineInfo(FrameTimelineInf t.vsyncId = other.vsyncId; t.inputEventId = other.inputEventId; t.startTimeNanos = other.startTimeNanos; + t.useForRefreshRateSelection = other.useForRefreshRateSelection; } } else if (t.vsyncId == FrameTimelineInfo::INVALID_VSYNC_ID) { t.vsyncId = other.vsyncId; t.inputEventId = other.inputEventId; t.startTimeNanos = other.startTimeNanos; + t.useForRefreshRateSelection = other.useForRefreshRateSelection; } } @@ -2258,6 +2260,7 @@ void SurfaceComposerClient::Transaction::clearFrameTimelineInfo(FrameTimelineInf t.vsyncId = FrameTimelineInfo::INVALID_VSYNC_ID; t.inputEventId = os::IInputConstants::INVALID_INPUT_EVENT_ID; t.startTimeNanos = 0; + t.useForRefreshRateSelection = false; } SurfaceComposerClient::Transaction& diff --git a/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl b/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl index 6ffe466f20..6a86c6a5cd 100644 --- a/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl +++ b/libs/gui/aidl/android/gui/FrameTimelineInfo.aidl @@ -33,4 +33,8 @@ parcelable FrameTimelineInfo { // The current time in nanoseconds the application started to render the frame. long startTimeNanos = 0; + + // Whether this vsyncId should be used to heuristically select the display refresh rate + // TODO(b/281695725): Clean this up once TextureView use setFrameRate API + boolean useForRefreshRateSelection = false; } diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h index 6c54635824..0fee3c112e 100644 --- a/libs/nativewindow/include/system/window.h +++ b/libs/nativewindow/include/system/window.h @@ -1066,13 +1066,12 @@ static inline int native_window_set_frame_rate(struct ANativeWindow* window, flo (int)compatibility, (int)changeFrameRateStrategy); } -static inline int native_window_set_frame_timeline_info(struct ANativeWindow* window, - uint64_t frameNumber, - int64_t frameTimelineVsyncId, - int32_t inputEventId, - int64_t startTimeNanos) { +static inline int native_window_set_frame_timeline_info( + struct ANativeWindow* window, uint64_t frameNumber, int64_t frameTimelineVsyncId, + int32_t inputEventId, int64_t startTimeNanos, int32_t useForRefreshRateSelection) { return window->perform(window, NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO, frameNumber, - frameTimelineVsyncId, inputEventId, startTimeNanos); + frameTimelineVsyncId, inputEventId, startTimeNanos, + useForRefreshRateSelection); } // ------------------------------------------------------------------------------------------------ diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 3371ae2d8c..a8214662c6 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -3077,6 +3077,7 @@ bool Layer::setBuffer(std::shared_ptr& buffer, mDrawingState.desiredPresentTime = desiredPresentTime; mDrawingState.isAutoTimestamp = isAutoTimestamp; mDrawingState.latchedVsyncId = info.vsyncId; + mDrawingState.useVsyncIdForRefreshRateSelection = info.useForRefreshRateSelection; mDrawingState.modified = true; if (!buffer) { resetDrawingStateBufferInfo(); @@ -3139,15 +3140,31 @@ void Layer::setDesiredPresentTime(nsecs_t desiredPresentTime, bool isAutoTimesta } void Layer::recordLayerHistoryBufferUpdate(const scheduler::LayerProps& layerProps) { + ATRACE_CALL(); const nsecs_t presentTime = [&] { - if (!mDrawingState.isAutoTimestamp) return mDrawingState.desiredPresentTime; + if (!mDrawingState.isAutoTimestamp) { + ATRACE_FORMAT_INSTANT("desiredPresentTime"); + return mDrawingState.desiredPresentTime; + } - const auto prediction = mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken( - mDrawingState.latchedVsyncId); - if (prediction.has_value()) return prediction->presentTime; + if (mDrawingState.useVsyncIdForRefreshRateSelection) { + const auto prediction = + mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken( + mDrawingState.latchedVsyncId); + if (prediction.has_value()) { + ATRACE_FORMAT_INSTANT("predictedPresentTime"); + return prediction->presentTime; + } + } return static_cast(0); }(); + + if (ATRACE_ENABLED() && presentTime > 0) { + const auto presentIn = TimePoint::fromNs(presentTime) - TimePoint::now(); + ATRACE_FORMAT_INSTANT("presentIn %s", to_string(presentIn).c_str()); + } + mFlinger->mScheduler->recordLayerHistory(sequence, layerProps, presentTime, scheduler::LayerHistory::LayerUpdateType::Buffer); } diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 2640c92b9d..a6c70e50ad 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -234,6 +234,7 @@ public: float desiredHdrSdrRatio = 1.f; gui::CachingHint cachingHint = gui::CachingHint::Enabled; int64_t latchedVsyncId = 0; + bool useVsyncIdForRefreshRateSelection = false; }; explicit Layer(const LayerCreationArgs& args); -- GitLab From 0e7d8fde28f67650ce663f3f3e43f9377dc90d50 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Wed, 3 May 2023 23:58:43 +0000 Subject: [PATCH 1233/1310] Add tracing thread for RenderEngine's fences RenderEngine doesn't always render to FramebufferSurface, so tracing that is built into BufferQueue is lost. Add a tracing thread so that screenshots and caching can also have GPU work traced. Bug: 280684246 Bug: 269822044 Test: perfetto Change-Id: I067430f6b6b588dd3046d1cf66d0a3fc8465746d --- libs/gui/Android.bp | 1 + libs/gui/FenceMonitor.cpp | 89 +++++++++++++++++++++ libs/gui/Surface.cpp | 83 +------------------ libs/gui/include/gui/FenceMonitor.h | 44 ++++++++++ libs/renderengine/skia/SkiaRenderEngine.cpp | 17 ++-- 5 files changed, 150 insertions(+), 84 deletions(-) create mode 100644 libs/gui/FenceMonitor.cpp create mode 100644 libs/gui/include/gui/FenceMonitor.h diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index 80fed98434..bf34987b9e 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -217,6 +217,7 @@ cc_library_shared { "DebugEGLImageTracker.cpp", "DisplayEventDispatcher.cpp", "DisplayEventReceiver.cpp", + "FenceMonitor.cpp", "GLConsumer.cpp", "IConsumerListener.cpp", "IGraphicBufferConsumer.cpp", diff --git a/libs/gui/FenceMonitor.cpp b/libs/gui/FenceMonitor.cpp new file mode 100644 index 0000000000..230c81a0b3 --- /dev/null +++ b/libs/gui/FenceMonitor.cpp @@ -0,0 +1,89 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + +#include +#include +#include + +#include + +namespace android::gui { + +FenceMonitor::FenceMonitor(const char* name) : mName(name), mFencesQueued(0), mFencesSignaled(0) { + std::thread thread(&FenceMonitor::loop, this); + pthread_setname_np(thread.native_handle(), mName); + thread.detach(); +} + +void FenceMonitor::queueFence(const sp& fence) { + char message[64]; + + std::lock_guard lock(mMutex); + if (fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) { + snprintf(message, sizeof(message), "%s fence %u has signaled", mName, mFencesQueued); + ATRACE_NAME(message); + // Need an increment on both to make the trace number correct. + mFencesQueued++; + mFencesSignaled++; + return; + } + snprintf(message, sizeof(message), "Trace %s fence %u", mName, mFencesQueued); + ATRACE_NAME(message); + + mQueue.push_back(fence); + mCondition.notify_one(); + mFencesQueued++; + ATRACE_INT(mName, int32_t(mQueue.size())); +} + +void FenceMonitor::loop() { + while (true) { + threadLoop(); + } +} + +void FenceMonitor::threadLoop() { + sp fence; + uint32_t fenceNum; + { + std::unique_lock lock(mMutex); + while (mQueue.empty()) { + mCondition.wait(lock); + } + fence = mQueue[0]; + fenceNum = mFencesSignaled; + } + { + char message[64]; + snprintf(message, sizeof(message), "waiting for %s %u", mName, fenceNum); + ATRACE_NAME(message); + + status_t result = fence->waitForever(message); + if (result != OK) { + ALOGE("Error waiting for fence: %d", result); + } + } + { + std::lock_guard lock(mMutex); + mQueue.pop_front(); + mFencesSignaled++; + ATRACE_INT(mName, int32_t(mQueue.size())); + } +} + +} // namespace android::gui \ No newline at end of file diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index a5cf8d6bf0..3a1e600f46 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -545,82 +546,6 @@ int Surface::setSwapInterval(int interval) { return NO_ERROR; } -class FenceMonitor { -public: - explicit FenceMonitor(const char* name) : mName(name), mFencesQueued(0), mFencesSignaled(0) { - std::thread thread(&FenceMonitor::loop, this); - pthread_setname_np(thread.native_handle(), mName); - thread.detach(); - } - - void queueFence(const sp& fence) { - char message[64]; - - std::lock_guard lock(mMutex); - if (fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) { - snprintf(message, sizeof(message), "%s fence %u has signaled", mName, mFencesQueued); - ATRACE_NAME(message); - // Need an increment on both to make the trace number correct. - mFencesQueued++; - mFencesSignaled++; - return; - } - snprintf(message, sizeof(message), "Trace %s fence %u", mName, mFencesQueued); - ATRACE_NAME(message); - - mQueue.push_back(fence); - mCondition.notify_one(); - mFencesQueued++; - ATRACE_INT(mName, int32_t(mQueue.size())); - } - -private: -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wmissing-noreturn" - void loop() { - while (true) { - threadLoop(); - } - } -#pragma clang diagnostic pop - - void threadLoop() { - sp fence; - uint32_t fenceNum; - { - std::unique_lock lock(mMutex); - while (mQueue.empty()) { - mCondition.wait(lock); - } - fence = mQueue[0]; - fenceNum = mFencesSignaled; - } - { - char message[64]; - snprintf(message, sizeof(message), "waiting for %s %u", mName, fenceNum); - ATRACE_NAME(message); - - status_t result = fence->waitForever(message); - if (result != OK) { - ALOGE("Error waiting for fence: %d", result); - } - } - { - std::lock_guard lock(mMutex); - mQueue.pop_front(); - mFencesSignaled++; - ATRACE_INT(mName, int32_t(mQueue.size())); - } - } - - const char* mName; - uint32_t mFencesQueued; - uint32_t mFencesSignaled; - std::deque> mQueue; - std::condition_variable mCondition; - std::mutex mMutex; -}; - void Surface::getDequeueBufferInputLocked( IGraphicBufferProducer::DequeueBufferInput* dequeueInput) { LOG_ALWAYS_FATAL_IF(dequeueInput == nullptr, "input is null"); @@ -694,7 +619,7 @@ int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) { ALOGE_IF(fence == nullptr, "Surface::dequeueBuffer: received null Fence! buf=%d", buf); if (CC_UNLIKELY(atrace_is_tag_enabled(ATRACE_TAG_GRAPHICS))) { - static FenceMonitor hwcReleaseThread("HWC release"); + static gui::FenceMonitor hwcReleaseThread("HWC release"); hwcReleaseThread.queueFence(fence); } @@ -893,7 +818,7 @@ int Surface::dequeueBuffers(std::vector* buffers) { sp& gbuf(mSlots[slot].buffer); if (CC_UNLIKELY(atrace_is_tag_enabled(ATRACE_TAG_GRAPHICS))) { - static FenceMonitor hwcReleaseThread("HWC release"); + static gui::FenceMonitor hwcReleaseThread("HWC release"); hwcReleaseThread.queueFence(output.fence); } @@ -1163,7 +1088,7 @@ void Surface::onBufferQueuedLocked(int slot, sp fence, mQueueBufferCondition.broadcast(); if (CC_UNLIKELY(atrace_is_tag_enabled(ATRACE_TAG_GRAPHICS))) { - static FenceMonitor gpuCompletionThread("GPU completion"); + static gui::FenceMonitor gpuCompletionThread("GPU completion"); gpuCompletionThread.queueFence(fence); } } diff --git a/libs/gui/include/gui/FenceMonitor.h b/libs/gui/include/gui/FenceMonitor.h new file mode 100644 index 0000000000..62ceddee5f --- /dev/null +++ b/libs/gui/include/gui/FenceMonitor.h @@ -0,0 +1,44 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include + +namespace android::gui { + +class FenceMonitor { +public: + explicit FenceMonitor(const char* name); + void queueFence(const sp& fence); + +private: + void loop(); + void threadLoop(); + + const char* mName; + uint32_t mFencesQueued; + uint32_t mFencesSignaled; + std::deque> mQueue; + std::condition_variable mCondition; + std::mutex mMutex; +}; + +} // namespace android::gui \ No newline at end of file diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index cfea85f98b..5854135afe 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -39,10 +39,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -51,9 +51,11 @@ #include #include #include -#include #include +#include #include +#include +#include #include #include #include @@ -63,6 +65,7 @@ #include #include +#include #include #include @@ -229,7 +232,6 @@ static inline SkM44 getSkM44(const android::mat4& matrix) { static inline SkPoint3 getSkPoint3(const android::vec3& vector) { return SkPoint3::Make(vector.x, vector.y, vector.z); } - } // namespace namespace android { @@ -1134,8 +1136,13 @@ void SkiaRenderEngine::drawLayersInternal( activeSurface->flush(); } - base::unique_fd drawFence = flushAndSubmit(grContext); - resultPromise->set_value(sp::make(std::move(drawFence))); + auto drawFence = sp::make(flushAndSubmit(grContext)); + + if (ATRACE_ENABLED()) { + static gui::FenceMonitor sMonitor("RE Completion"); + sMonitor.queueFence(drawFence); + } + resultPromise->set_value(std::move(drawFence)); } size_t SkiaRenderEngine::getMaxTextureSize() const { -- GitLab From c68f85b54d8e3b02f38e20824ec01cc622a3c8d1 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Wed, 26 Apr 2023 16:23:13 +0000 Subject: [PATCH 1234/1310] InputMapper refactor: VibratorInputMapper Add a factory method for VibratorInputMapper to be configured with on initilisation Test: m checkinput && atest libinput_tests inputflinger_tests Bug: 256009910 Change-Id: I3c1ad85a8fc293ec1a8c1a5c82da36e304538046 (cherry picked from commit 0f26b30cc1a730a828f1b7bd16a1ff77dbc7282d) --- services/inputflinger/reader/InputDevice.cpp | 2 +- services/inputflinger/reader/mapper/VibratorInputMapper.h | 8 ++++++-- services/inputflinger/tests/InputReader_test.cpp | 6 +++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 721bb0a962..ae38236257 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -456,7 +456,7 @@ std::vector> InputDevice::createMappers( // Vibrator-like devices. if (classes.test(InputDeviceClass::VIBRATOR)) { - mappers.push_back(std::make_unique(contextPtr, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // Battery-like devices or light-containing devices. diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.h b/services/inputflinger/reader/mapper/VibratorInputMapper.h index 384c07512a..9079c73f8e 100644 --- a/services/inputflinger/reader/mapper/VibratorInputMapper.h +++ b/services/inputflinger/reader/mapper/VibratorInputMapper.h @@ -22,8 +22,10 @@ namespace android { class VibratorInputMapper : public InputMapper { public: - explicit VibratorInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); virtual ~VibratorInputMapper(); virtual uint32_t getSources() const override; @@ -46,6 +48,8 @@ private: ssize_t mIndex; nsecs_t mNextStepTime; + explicit VibratorInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); [[nodiscard]] std::list nextStep(); [[nodiscard]] NotifyVibratorStateArgs stopVibrating(); }; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index ae3ae6c006..e1dbaa47bc 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -2645,13 +2645,13 @@ protected: }; TEST_F(VibratorInputMapperTest, GetSources) { - VibratorInputMapper& mapper = addMapperAndConfigure(); + VibratorInputMapper& mapper = constructAndAddMapper(); ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mapper.getSources()); } TEST_F(VibratorInputMapperTest, GetVibratorIds) { - VibratorInputMapper& mapper = addMapperAndConfigure(); + VibratorInputMapper& mapper = constructAndAddMapper(); ASSERT_EQ(mapper.getVibratorIds().size(), 2U); } @@ -2659,7 +2659,7 @@ TEST_F(VibratorInputMapperTest, GetVibratorIds) { TEST_F(VibratorInputMapperTest, Vibrate) { constexpr uint8_t DEFAULT_AMPLITUDE = 192; constexpr int32_t VIBRATION_TOKEN = 100; - VibratorInputMapper& mapper = addMapperAndConfigure(); + VibratorInputMapper& mapper = constructAndAddMapper(); VibrationElement pattern(2); VibrationSequence sequence(2); -- GitLab From 3be61905f09f265de9732eed00985e324540e5b8 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 26 Apr 2023 19:47:29 -0700 Subject: [PATCH 1235/1310] [sf] Properly update clones of clones Relanding with the change to only update onscreen mirror roots and cleaning up some debug logs. When updating a cloned layer hierarchy that contains another cloned layer hierarchy, we are dependent on the order in which we update each hierarchy. The inner cloned hierarchy must be updated first otherwise the outer clone will not capture the entire tree and some layers might be omitted from composition. To fix this we track all layer mirror roots. When updating each root, we check to see if they mirror another root. If they do mirror another root and that root has not been updated, we update the root again at the end. Test: repro steps in bug Fixes: 279622227 Change-Id: I125f7c788716d59909b77a88ef4098b7cac08919 --- services/surfaceflinger/Layer.cpp | 20 +++++++---- services/surfaceflinger/Layer.h | 2 +- services/surfaceflinger/LayerRenderArea.cpp | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 39 ++++++++++++++++----- services/surfaceflinger/SurfaceFlinger.h | 3 +- 5 files changed, 48 insertions(+), 18 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 3371ae2d8c..f5bfb31c8c 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -254,7 +254,8 @@ Layer::~Layer() { mFlinger->mTunnelModeEnabledReporter->decrementTunnelModeCount(); } if (mHadClonedChild) { - mFlinger->mNumClones--; + auto& roots = mFlinger->mLayerMirrorRoots; + roots.erase(std::remove(roots.begin(), roots.end(), this), roots.end()); } if (hasTrustedPresentationListener()) { mFlinger->mNumTrustedPresentationListeners--; @@ -1649,8 +1650,8 @@ void Layer::getFrameStats(FrameStats* outStats) const { void Layer::dumpOffscreenDebugInfo(std::string& result) const { std::string hasBuffer = hasBufferOrSidebandStream() ? " (contains buffer)" : ""; - StringAppendF(&result, "Layer %s%s pid:%d uid:%d\n", getName().c_str(), hasBuffer.c_str(), - mOwnerPid, mOwnerUid); + StringAppendF(&result, "Layer %s%s pid:%d uid:%d%s\n", getName().c_str(), hasBuffer.c_str(), + mOwnerPid, mOwnerUid, isHandleAlive() ? " handleAlive" : ""); } void Layer::onDisconnect() { @@ -2594,7 +2595,7 @@ void Layer::updateCloneBufferInfo() { mDrawingState.inputInfo = tmpInputInfo; } -void Layer::updateMirrorInfo() { +bool Layer::updateMirrorInfo(const std::deque& cloneRootsPendingUpdates) { if (mClonedChild == nullptr || !mClonedChild->isClonedFromAlive()) { // If mClonedChild is null, there is nothing to mirror. If isClonedFromAlive returns false, // it means that there is a clone, but the layer it was cloned from has been destroyed. In @@ -2602,7 +2603,7 @@ void Layer::updateMirrorInfo() { // destroyed. The root, this layer, will still be around since the client can continue // to hold a reference, but no cloned layers will be displayed. mClonedChild = nullptr; - return; + return true; } std::map, sp> clonedLayersMap; @@ -2617,6 +2618,13 @@ void Layer::updateMirrorInfo() { mClonedChild->updateClonedDrawingState(clonedLayersMap); mClonedChild->updateClonedChildren(sp::fromExisting(this), clonedLayersMap); mClonedChild->updateClonedRelatives(clonedLayersMap); + + for (Layer* root : cloneRootsPendingUpdates) { + if (clonedLayersMap.find(sp::fromExisting(root)) != clonedLayersMap.end()) { + return false; + } + } + return true; } void Layer::updateClonedDrawingState(std::map, sp>& clonedLayersMap) { @@ -2764,7 +2772,7 @@ bool Layer::isInternalDisplayOverlay() const { void Layer::setClonedChild(const sp& clonedChild) { mClonedChild = clonedChild; mHadClonedChild = true; - mFlinger->mNumClones++; + mFlinger->mLayerMirrorRoots.push_back(this); } bool Layer::setDropInputMode(gui::DropInputMode mode) { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 2640c92b9d..43749149af 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -651,7 +651,7 @@ public: gui::WindowInfo::Type getWindowType() const { return mWindowType; } - void updateMirrorInfo(); + bool updateMirrorInfo(const std::deque& cloneRootsPendingUpdates); /* * doTransaction - process the transaction. This is a good place to figure diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp index f6b5afc6a8..d606cffe40 100644 --- a/services/surfaceflinger/LayerRenderArea.cpp +++ b/services/surfaceflinger/LayerRenderArea.cpp @@ -85,7 +85,7 @@ void LayerRenderArea::render(std::function drawLayers) { // If layer is offscreen, update mirroring info if it exists if (mLayer->isRemovedFromCurrentState()) { mLayer->traverse(LayerVector::StateSet::Drawing, - [&](Layer* layer) { layer->updateMirrorInfo(); }); + [&](Layer* layer) { layer->updateMirrorInfo({}); }); mLayer->traverse(LayerVector::StateSet::Drawing, [&](Layer* layer) { layer->updateCloneBufferInfo(); }); } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index dfe8c72bca..84ad551c62 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4025,8 +4025,24 @@ void SurfaceFlinger::doCommitTransactions() { } commitOffscreenLayers(); - if (mNumClones > 0) { - mDrawingState.traverse([&](Layer* layer) { layer->updateMirrorInfo(); }); + if (mLayerMirrorRoots.size() > 0) { + std::deque pendingUpdates; + pendingUpdates.insert(pendingUpdates.end(), mLayerMirrorRoots.begin(), + mLayerMirrorRoots.end()); + std::vector needsUpdating; + for (Layer* cloneRoot : mLayerMirrorRoots) { + pendingUpdates.pop_front(); + if (cloneRoot->isRemovedFromCurrentState()) { + continue; + } + if (cloneRoot->updateMirrorInfo(pendingUpdates)) { + } else { + needsUpdating.push_back(cloneRoot); + } + } + for (Layer* cloneRoot : needsUpdating) { + cloneRoot->updateMirrorInfo({}); + } } } @@ -4133,7 +4149,7 @@ bool SurfaceFlinger::latchBuffers() { mBootStage = BootStage::BOOTANIMATION; } - if (mNumClones > 0) { + if (mLayerMirrorRoots.size() > 0) { mDrawingState.traverse([&](Layer* layer) { layer->updateCloneBufferInfo(); }); } @@ -4158,8 +4174,8 @@ status_t SurfaceFlinger::addClientLayer(LayerCreationArgs& args, const sp parent = sp::fromExisting(layer); while (parent) { - ALOGE("Parent Layer: %s handleIsAlive: %s", parent->getName().c_str(), - std::to_string(parent->isHandleAlive()).c_str()); + ALOGE("Parent Layer: %s%s", parent->getName().c_str(), + (parent->isHandleAlive() ? "handleAlive" : "")); parent = parent->getParent(); } // Sample up to 100 layers @@ -4174,21 +4190,28 @@ status_t SurfaceFlinger::addClientLayer(LayerCreationArgs& args, const spgetName().c_str()); + ALOGE("Layer: %s%s", layer->getName().c_str(), + (layer->isHandleAlive() ? "handleAlive" : "")); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); } }); ALOGE("Dumping random sampling of off-screen layers total(%zu): ", mOffscreenLayers.size()); for (Layer* offscreenLayer : mOffscreenLayers) { if (rand() % 20 == 13) { - ALOGE("Offscreen-layer: %s", offscreenLayer->getName().c_str()); + ALOGE("Offscreen-layer: %s%s", offscreenLayer->getName().c_str(), + (offscreenLayer->isHandleAlive() ? "handleAlive" : "")); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); } } })); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index d92ec7a3b6..cfaa221a45 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -296,8 +296,7 @@ public: // the client can no longer modify this layer directly. void onHandleDestroyed(BBinder* handle, sp& layer, uint32_t layerId); - // TODO: Remove atomic if move dtor to main thread CL lands - std::atomic mNumClones; + std::vector mLayerMirrorRoots; TransactionCallbackInvoker& getTransactionCallbackInvoker() { return mTransactionCallbackInvoker; -- GitLab From 23c8dff7e1b9d15f65f79af6ad708267e9c90b8e Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Wed, 10 May 2023 17:39:59 +0000 Subject: [PATCH 1236/1310] CapturedTouchpadEventConverter: fix button reporting The previous implementation had a couple of issues with reporting BUTTON_PRESS or _RELEASE events with no pointers, which are invalid. This occurred when the evdev device reported a button press one sync before reporting a touch going down (observed when tapping hard and fast on an Apple Magic Trackpad 2), or when the last touch lifted in the same evdev sync as the button being released. Bug: b/281825527 Test: atest inputflinger_tests Test: manual checking of events reported to a test app capturing the pad Change-Id: I1676a1ab9281872c745416ad41186c65a44eb581 --- .../mapper/CapturedTouchpadEventConverter.cpp | 28 ++- .../CapturedTouchpadEventConverter_test.cpp | 171 ++++++++++++++++++ 2 files changed, 194 insertions(+), 5 deletions(-) diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp index dab4661442..061c6a3e75 100644 --- a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp +++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp @@ -199,6 +199,29 @@ std::list CapturedTouchpadEventConverter::sync(nsecs_t when, nsecs_t } } + // Send BUTTON_RELEASE events. (This has to happen before any UP events to avoid sending + // BUTTON_RELEASE events without any pointers.) + uint32_t newButtonState; + if (coords.size() - upSlots.size() + downSlots.size() == 0) { + // If there won't be any pointers down after this evdev sync, we won't be able to send + // button updates on their own, as motion events without pointers are invalid. To avoid + // erroneously reporting buttons being held for long periods, send BUTTON_RELEASE events for + // all pressed buttons when the last pointer is lifted. + // + // This also prevents us from sending BUTTON_PRESS events too early in the case of touchpads + // which report a button press one evdev sync before reporting a touch going down. + newButtonState = 0; + } else { + newButtonState = mCursorButtonAccumulator.getButtonState(); + } + for (uint32_t button = 1; button <= AMOTION_EVENT_BUTTON_FORWARD; button <<= 1) { + if (!(newButtonState & button) && mButtonState & button) { + mButtonState &= ~button; + out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE, + coords, properties, /*actionButton=*/button)); + } + } + // For any touches that were lifted, send UP or POINTER_UP events. for (size_t slotNumber : upSlots) { const size_t indexToRemove = coordsIndexForSlotNumber.at(slotNumber); @@ -240,16 +263,11 @@ std::list CapturedTouchpadEventConverter::sync(nsecs_t when, nsecs_t out.push_back(makeMotionArgs(when, readTime, action, coords, properties)); } - const uint32_t newButtonState = mCursorButtonAccumulator.getButtonState(); for (uint32_t button = 1; button <= AMOTION_EVENT_BUTTON_FORWARD; button <<= 1) { if (newButtonState & button && !(mButtonState & button)) { mButtonState |= button; out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS, coords, properties, /*actionButton=*/button)); - } else if (!(newButtonState & button) && mButtonState & button) { - mButtonState &= ~button; - out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE, - coords, properties, /*actionButton=*/button)); } } return out; diff --git a/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp index 3dc515204e..99a6a1f5db 100644 --- a/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp +++ b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp @@ -781,4 +781,175 @@ TEST_F(CapturedTouchpadEventConverterTest, PointerIdsReusedAfterLift) { WithPointerId(/*index=*/1, /*id=*/0))); } +// Motion events without any pointers are invalid, so when a button press is reported in the same +// frame as a touch down, the button press must be reported second. Similarly with a button release +// and a touch lift. +TEST_F(CapturedTouchpadEventConverterTest, + ButtonPressedAndReleasedInSameFrameAsTouch_ReportedWithPointers) { + CapturedTouchpadEventConverter conv = createConverter(); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + processAxis(conv, EV_KEY, BTN_LEFT, 1); + + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), WithPointerCount(1u), + WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))); + + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1); + processAxis(conv, EV_KEY, BTN_TOUCH, 0); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0); + + processAxis(conv, EV_KEY, BTN_LEFT, 0); + args = processSync(conv); + ASSERT_EQ(3u, args.size()); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_MOVE)); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithPointerCount(1u), + WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(0))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_UP)); +} + +// Some touchpads sometimes report a button press before they report the finger touching the pad. In +// that case we need to wait until the touch comes to report the button press. +TEST_F(CapturedTouchpadEventConverterTest, ButtonPressedBeforeTouch_ReportedOnceTouchOccurs) { + CapturedTouchpadEventConverter conv = createConverter(); + + processAxis(conv, EV_KEY, BTN_LEFT, 1); + ASSERT_EQ(0u, processSync(conv).size()); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), WithPointerCount(1u), + WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))); +} + +// When all fingers are lifted from a touchpad, we should release any buttons that are down, since +// we won't be able to report them being lifted later if no pointers are present. +TEST_F(CapturedTouchpadEventConverterTest, ButtonReleasedAfterTouchLifts_ReportedWithLift) { + CapturedTouchpadEventConverter conv = createConverter(); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + processAxis(conv, EV_KEY, BTN_LEFT, 1); + + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1); + processAxis(conv, EV_KEY, BTN_TOUCH, 0); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0); + args = processSync(conv); + ASSERT_EQ(3u, args.size()); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_MOVE)); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithPointerCount(1u), + WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(0))); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_UP)); + + processAxis(conv, EV_KEY, BTN_LEFT, 0); + ASSERT_EQ(0u, processSync(conv).size()); +} + +TEST_F(CapturedTouchpadEventConverterTest, MultipleButtonsPressedDuringTouch_ReportedCorrectly) { + CapturedTouchpadEventConverter conv = createConverter(); + + processAxis(conv, EV_ABS, ABS_MT_SLOT, 0); + processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1); + processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50); + processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100); + processAxis(conv, EV_KEY, BTN_TOUCH, 1); + processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1); + + EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), + WithMotionAction(AMOTION_EVENT_ACTION_DOWN)); + + processAxis(conv, EV_KEY, BTN_LEFT, 1); + std::list args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_MOVE)); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))); + + processAxis(conv, EV_KEY, BTN_RIGHT, 1); + args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_MOVE)); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), + WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | + AMOTION_EVENT_BUTTON_SECONDARY))); + + processAxis(conv, EV_KEY, BTN_LEFT, 0); + args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_MOVE)); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), + WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY))); + + processAxis(conv, EV_KEY, BTN_RIGHT, 0); + args = processSync(conv); + ASSERT_EQ(2u, args.size()); + EXPECT_THAT(std::get(args.front()), + WithMotionAction(AMOTION_EVENT_ACTION_MOVE)); + args.pop_front(); + EXPECT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0))); +} + } // namespace android -- GitLab From 983cae052ad7d807387b2a0634c272edc449f0f1 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Tue, 9 May 2023 22:45:35 -0700 Subject: [PATCH 1237/1310] Fix VirtualInputDevice build for musl libc The struct input_event timespec types come from the kernel headers, which can vary in their exact definition based on which libc they came from. Use decltype to cast to the exact definition of the fields. Bug: 271946580 Test: m USE_HOST_MUSL=true host-native Change-Id: Iddcc6ea7c1e5d70edf8c159ddaff585eb38ef7fa --- libs/input/VirtualInputDevice.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp index af9ce3a38f..9a459b135c 100644 --- a/libs/input/VirtualInputDevice.cpp +++ b/libs/input/VirtualInputDevice.cpp @@ -49,11 +49,10 @@ bool VirtualInputDevice::writeInputEvent(uint16_t type, uint16_t code, int32_t v std::chrono::seconds seconds = std::chrono::duration_cast(eventTime); std::chrono::microseconds microseconds = std::chrono::duration_cast(eventTime - seconds); - struct input_event ev = {.type = type, - .code = code, - .value = value, - .input_event_sec = static_cast(seconds.count()), - .input_event_usec = static_cast(microseconds.count())}; + struct input_event ev = {.type = type, .code = code, .value = value}; + ev.input_event_sec = static_cast(seconds.count()); + ev.input_event_usec = static_cast(microseconds.count()); + return TEMP_FAILURE_RETRY(write(mFd, &ev, sizeof(struct input_event))) == sizeof(ev); } -- GitLab From 802ac4f6e84a830077963db7299ae9184b56e12e Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 10 May 2023 13:55:22 -0700 Subject: [PATCH 1238/1310] [sf] avoid traversals for cursor updates and buffer udpates In U, we have a unified way to traverse the layer hierarchy from both new FrontEnd and legacy logic to generate the snapshots to provide to CE. We update CE twice, once to draw the cursor and once for the remaining layers. In T we relied on the previous frame's composition state to update cursor state. This extra traversal increases the cpu usage for buffer updates. Fix this by keeping track of the previous composition state and expand on this to avoid all traversals when there are only buffer updates. Bug: 278634536 Test: simple perf and check the instruction count between T and U Change-Id: I26989bf42aa00650ee97c3c60e7f34171c385c5c --- services/surfaceflinger/SurfaceFlinger.cpp | 19 +++++++++++++++++-- services/surfaceflinger/SurfaceFlinger.h | 5 +++++ .../tests/unittests/CompositionTest.cpp | 5 +++++ .../tests/unittests/TestableSurfaceFlinger.h | 2 ++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index dfe8c72bca..9476c45452 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -8144,7 +8144,7 @@ std::vector> SurfaceFlinger::moveSnapshotsToComposit }); } if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) { - mDrawingState.traverseInZOrder([&refreshArgs, cursorOnly, &layers](Layer* layer) { + auto moveSnapshots = [&layers, &refreshArgs, cursorOnly](Layer* layer) { if (const auto& layerFE = layer->getCompositionEngineLayerFE()) { if (cursorOnly && layer->getLayerSnapshot()->compositionType != @@ -8155,7 +8155,22 @@ std::vector> SurfaceFlinger::moveSnapshotsToComposit refreshArgs.layers.push_back(layerFE); layers.emplace_back(layer, layerFE.get()); } - }); + }; + + if (cursorOnly || !mVisibleRegionsDirty) { + // for hot path avoid traversals by walking though the previous composition list + for (sp layer : mPreviouslyComposedLayers) { + moveSnapshots(layer.get()); + } + } else { + mPreviouslyComposedLayers.clear(); + mDrawingState.traverseInZOrder( + [&moveSnapshots](Layer* layer) { moveSnapshots(layer); }); + mPreviouslyComposedLayers.reserve(layers.size()); + for (auto [layer, _] : layers) { + mPreviouslyComposedLayers.push_back(sp::fromExisting(layer)); + } + } } return layers; diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index d92ec7a3b6..adf52cba75 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1199,6 +1199,11 @@ private: std::unordered_set, SpHash> mLayersWithBuffersRemoved; // Tracks layers that need to update a display's dirty region. std::vector> mLayersPendingRefresh; + // Sorted list of layers that were composed during previous frame. This is used to + // avoid an expensive traversal of the layer hierarchy when there are no + // visible region changes. Because this is a list of strong pointers, this will + // extend the life of the layer but this list is only updated in the main thread. + std::vector> mPreviouslyComposedLayers; BootStage mBootStage = BootStage::BOOTLOADER; diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 6ca21bd458..e8a9cfe9d7 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -680,6 +680,9 @@ struct SidebandLayerProperties : public BaseLayerProperties(DEFAULT_SIDEBAND_STREAM), false); test->mFlinger.setLayerSidebandStream(layer, stream); + auto& layerDrawingState = test->mFlinger.mutableLayerDrawingState(layer); + layerDrawingState.crop = + Rect(0, 0, SidebandLayerProperties::HEIGHT, SidebandLayerProperties::WIDTH); } static void setupHwcSetSourceCropBufferCallExpectations(CompositionTest* test) { @@ -814,6 +817,7 @@ struct BaseLayerVariant { Mock::VerifyAndClear(test->mComposer); test->mFlinger.mutableDrawingState().layersSortedByZ.add(layer); + test->mFlinger.mutableVisibleRegionsDirty() = true; } static void cleanupInjectedLayers(CompositionTest* test) { @@ -822,6 +826,7 @@ struct BaseLayerVariant { test->mDisplay->getCompositionDisplay()->clearOutputLayers(); test->mFlinger.mutableDrawingState().layersSortedByZ.clear(); + test->mFlinger.mutablePreviouslyComposedLayers().clear(); // Layer should be unregistered with scheduler. test->mFlinger.commit(); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index a189c002c9..f135c2cfa9 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -588,6 +588,7 @@ public: auto& mutablePhysicalDisplays() { return mFlinger->mPhysicalDisplays; } auto& mutableDrawingState() { return mFlinger->mDrawingState; } auto& mutableGeometryDirty() { return mFlinger->mGeometryDirty; } + auto& mutableVisibleRegionsDirty() { return mFlinger->mVisibleRegionsDirty; } auto& mutableMainThreadId() { return mFlinger->mMainThreadId; } auto& mutablePendingHotplugEvents() { return mFlinger->mPendingHotplugEvents; } auto& mutableTexturePool() { return mFlinger->mTexturePool; } @@ -599,6 +600,7 @@ public: auto& mutableHwcPhysicalDisplayIdMap() { return getHwComposer().mPhysicalDisplayIdMap; } auto& mutablePrimaryHwcDisplayId() { return getHwComposer().mPrimaryHwcDisplayId; } auto& mutableActiveDisplayId() { return mFlinger->mActiveDisplayId; } + auto& mutablePreviouslyComposedLayers() { return mFlinger->mPreviouslyComposedLayers; } auto fromHandle(const sp& handle) { return LayerHandle::getLayer(handle); } -- GitLab From 9550c0945ee72bdda05972f70255e7a6b5c46c29 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Tue, 9 May 2023 13:58:18 -0400 Subject: [PATCH 1239/1310] Move rotation flags to SF The rotation flags are typically only used for a camera preview, which wants to avoid changing its orientation and flicker during rotation. Prior to this CL, the rotation flags were tied to the primary display, meaning that if the camera preview was on another display, the rotation flags may not be up to date. For example, if the primary display is off, its flags will not be updated on rotation. Ideally, the flags should be based on the display where the preview will be shown, but this is a much larger architectural change, tracked in b/259407931. As a temporary workaround, associate the flags with the active display. Store the flags in SurfaceFlinger, which knows when the active display changes. Update when the active display switches to a different display or when the active display rotates, matching the behavior of mActiveDisplayTransformHint, which seems similar but is different. Store the flags as a static variable so that LayerFE can access it. LayerFE does not have a way to access the actual SurfaceFlinger object, and it should not. Access to the new flags is safe because it is only read or written from the main thread. Bug: 269685949 Bug: 259407931 Test: ActiveDisplayRotationFlagsTest Change-Id: I5532e140a603be222cb3ea1ae563638317c1d745 Merged-In: I5532e140a603be222cb3ea1ae563638317c1d745 --- services/surfaceflinger/DisplayDevice.cpp | 10 -- services/surfaceflinger/DisplayDevice.h | 4 - .../surfaceflinger/FrontEnd/DisplayInfo.h | 2 +- .../FrontEnd/LayerSnapshotBuilder.cpp | 1 + services/surfaceflinger/Layer.cpp | 4 +- services/surfaceflinger/LayerFE.cpp | 4 +- services/surfaceflinger/SurfaceFlinger.cpp | 7 +- services/surfaceflinger/SurfaceFlinger.h | 12 ++ .../ActiveDisplayRotationFlagsTest.cpp | 148 ++++++++++++++++++ .../surfaceflinger/tests/unittests/Android.bp | 1 + .../tests/unittests/TestableSurfaceFlinger.h | 4 + 11 files changed, 177 insertions(+), 20 deletions(-) create mode 100644 services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 20f4de1d67..f6ca9e2856 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -50,8 +50,6 @@ namespace android { namespace hal = hardware::graphics::composer::hal; -ui::Transform::RotationFlags DisplayDevice::sPrimaryDisplayRotationFlags = ui::Transform::ROT_0; - DisplayDeviceCreationArgs::DisplayDeviceCreationArgs( const sp& flinger, HWComposer& hwComposer, const wp& displayToken, std::shared_ptr compositionDisplay) @@ -282,10 +280,6 @@ void DisplayDevice::setProjection(ui::Rotation orientation, Rect layerStackSpace Rect orientedDisplaySpaceRect) { mOrientation = orientation; - if (isPrimary()) { - sPrimaryDisplayRotationFlags = ui::Transform::toRotationFlags(orientation); - } - // We need to take care of display rotation for globalTransform for case if the panel is not // installed aligned with device orientation. const auto transformOrientation = orientation + mPhysicalOrientation; @@ -326,10 +320,6 @@ std::optional DisplayDevice::getStagedBrightness() const { return mStagedBrightness; } -ui::Transform::RotationFlags DisplayDevice::getPrimaryDisplayRotationFlags() { - return sPrimaryDisplayRotationFlags; -} - void DisplayDevice::dump(utils::Dumper& dumper) const { using namespace std::string_view_literals; diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index 51876e79a7..dc5f8a85af 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -109,8 +109,6 @@ public: ui::Rotation getPhysicalOrientation() const { return mPhysicalOrientation; } ui::Rotation getOrientation() const { return mOrientation; } - static ui::Transform::RotationFlags getPrimaryDisplayRotationFlags(); - std::optional getStagedBrightness() const REQUIRES(kMainThreadContext); ui::Transform::RotationFlags getTransformHint() const; const ui::Transform& getTransform() const; @@ -274,8 +272,6 @@ private: const ui::Rotation mPhysicalOrientation; ui::Rotation mOrientation = ui::ROTATION_0; - static ui::Transform::RotationFlags sPrimaryDisplayRotationFlags; - // Allow nullopt as initial power mode. using TracedPowerMode = TracedOrdinal; std::optional mPowerMode; diff --git a/services/surfaceflinger/FrontEnd/DisplayInfo.h b/services/surfaceflinger/FrontEnd/DisplayInfo.h index 76b36fe0f2..218a64a8d6 100644 --- a/services/surfaceflinger/FrontEnd/DisplayInfo.h +++ b/services/surfaceflinger/FrontEnd/DisplayInfo.h @@ -28,7 +28,7 @@ struct DisplayInfo { ui::Transform transform; bool receivesInput; bool isSecure; - // TODO(b/238781169) can eliminate once sPrimaryDisplayRotationFlags is removed. + // TODO(b/259407931): can eliminate once SurfaceFlinger::sActiveDisplayRotationFlags is removed. bool isPrimary; bool isVirtual; ui::Transform::RotationFlags rotationFlags; diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index ce7d37e69b..985c6f9fc9 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -667,6 +667,7 @@ void LayerSnapshotBuilder::resetRelativeState(LayerSnapshot& snapshot) { snapshot.relativeLayerMetadata.mMap.clear(); } +// TODO (b/259407931): Remove. uint32_t getPrimaryDisplayRotationFlags( const display::DisplayMap& displays) { for (auto& [_, display] : displays) { diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 3371ae2d8c..0c18bf296f 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -2977,7 +2977,7 @@ bool Layer::updateGeometry() { if (mDrawingState.bufferTransform & ui::Transform::ROT_90) { std::swap(bufferWidth, bufferHeight); } - uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags(); + uint32_t invTransform = SurfaceFlinger::getActiveDisplayRotationFlags(); if (mDrawingState.transformToDisplayInverse) { if (invTransform & ui::Transform::ROT_90) { std::swap(bufferWidth, bufferHeight); @@ -3287,7 +3287,7 @@ Rect Layer::getBufferSize(const State& /*s*/) const { } if (getTransformToDisplayInverse()) { - uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags(); + uint32_t invTransform = SurfaceFlinger::getActiveDisplayRotationFlags(); if (invTransform & ui::Transform::ROT_90) { std::swap(bufWidth, bufHeight); } diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp index e713263433..f855f278c3 100644 --- a/services/surfaceflinger/LayerFE.cpp +++ b/services/surfaceflinger/LayerFE.cpp @@ -25,8 +25,8 @@ #include #include -#include "DisplayDevice.h" #include "LayerFE.h" +#include "SurfaceFlinger.h" namespace android { @@ -260,7 +260,7 @@ void LayerFE::prepareBufferStateClientComposition( * the code below applies the primary display's inverse transform to * the texture transform */ - uint32_t transform = DisplayDevice::getPrimaryDisplayRotationFlags(); + uint32_t transform = SurfaceFlinger::getActiveDisplayRotationFlags(); mat4 tr = inverseOrientation(transform); /** diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 06ac8cb5af..799e646cc8 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -356,6 +356,8 @@ bool callingThreadHasPermission(const String16& permission) { PermissionCache::checkPermission(permission, pid, uid); } +ui::Transform::RotationFlags SurfaceFlinger::sActiveDisplayRotationFlags = ui::Transform::ROT_0; + SurfaceFlinger::SurfaceFlinger(Factory& factory, SkipInitializationTag) : mFactory(factory), mPid(getpid()), @@ -2619,7 +2621,7 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty; refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty; - refreshArgs.internalDisplayRotationFlags = DisplayDevice::getPrimaryDisplayRotationFlags(); + refreshArgs.internalDisplayRotationFlags = getActiveDisplayRotationFlags(); if (CC_UNLIKELY(mDrawingState.colorMatrixChanged)) { refreshArgs.colorTransformMatrix = mDrawingState.colorMatrix; @@ -3563,6 +3565,8 @@ void SurfaceFlinger::processDisplayChanged(const wp& displayToken, currentState.orientedDisplaySpaceRect); if (display->getId() == mActiveDisplayId) { mActiveDisplayTransformHint = display->getTransformHint(); + sActiveDisplayRotationFlags = + ui::Transform::toRotationFlags(display->getOrientation()); } } if (currentState.width != drawingState.width || @@ -7954,6 +7958,7 @@ void SurfaceFlinger::onActiveDisplayChangedLocked(const DisplayDevice* inactiveD onActiveDisplaySizeChanged(activeDisplay); mActiveDisplayTransformHint = activeDisplay.getTransformHint(); + sActiveDisplayRotationFlags = ui::Transform::toRotationFlags(activeDisplay.getOrientation()); // The policy of the new active/pacesetter display may have changed while it was inactive. In // that case, its preferred mode has not been propagated to HWC (via setDesiredActiveMode). In diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index d92ec7a3b6..ec36e95a41 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -332,6 +332,14 @@ public: const DisplayDevice* getDisplayFromLayerStack(ui::LayerStack) REQUIRES(mStateLock, kMainThreadContext); + // TODO (b/259407931): Remove. + // TODO (b/281857977): This should be annotated with REQUIRES(kMainThreadContext), but this + // would require thread safety annotations throughout the frontend (in particular Layer and + // LayerFE). + static ui::Transform::RotationFlags getActiveDisplayRotationFlags() { + return sActiveDisplayRotationFlags; + } + protected: // We're reference counted, never destroy SurfaceFlinger directly virtual ~SurfaceFlinger(); @@ -1390,6 +1398,10 @@ private: std::atomic mActiveDisplayTransformHint; + // Must only be accessed on the main thread. + // TODO (b/259407931): Remove. + static ui::Transform::RotationFlags sActiveDisplayRotationFlags; + bool isRefreshRateOverlayEnabled() const REQUIRES(mStateLock) { return hasDisplay( [](const auto& display) { return display.isRefreshRateOverlayEnabled(); }); diff --git a/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp new file mode 100644 index 0000000000..7077523fc3 --- /dev/null +++ b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp @@ -0,0 +1,148 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "LibSurfaceFlingerUnittests" + +#include "DisplayTransactionTestHelpers.h" + +#include +#include + +namespace android { +namespace { + +struct ActiveDisplayRotationFlagsTest : DisplayTransactionTest { + static constexpr bool kWithMockScheduler = false; + ActiveDisplayRotationFlagsTest() : DisplayTransactionTest(kWithMockScheduler) {} + + void SetUp() override { + injectMockScheduler(kInnerDisplayId); + + // Inject inner and outer displays with uninitialized power modes. + constexpr bool kInitPowerMode = false; + { + InnerDisplayVariant::injectHwcDisplay(this); + auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this); + injector.setPowerMode(std::nullopt); + injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector()); + mInnerDisplay = injector.inject(); + } + { + OuterDisplayVariant::injectHwcDisplay(this); + auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this); + injector.setPowerMode(std::nullopt); + mOuterDisplay = injector.inject(); + } + + mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON); + mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::ON); + + // The flags are a static variable, so by modifying them in the test, we + // are modifying the real ones used by SurfaceFlinger. Save the original + // flags so we can restore them on teardown. This isn't perfect - the + // phone may have been rotated during the test, so we're restoring the + // wrong flags. But if the phone is rotated, this may also fail the test. + mOldRotationFlags = mFlinger.mutableActiveDisplayRotationFlags(); + + // Reset to the expected default state. + mFlinger.mutableActiveDisplayRotationFlags() = ui::Transform::ROT_0; + } + + void TearDown() override { mFlinger.mutableActiveDisplayRotationFlags() = mOldRotationFlags; } + + static inline PhysicalDisplayId kInnerDisplayId = InnerDisplayVariant::DISPLAY_ID::get(); + static inline PhysicalDisplayId kOuterDisplayId = OuterDisplayVariant::DISPLAY_ID::get(); + + sp mInnerDisplay, mOuterDisplay; + ui::Transform::RotationFlags mOldRotationFlags; +}; + +TEST_F(ActiveDisplayRotationFlagsTest, defaultRotation) { + ASSERT_EQ(ui::Transform::ROT_0, SurfaceFlinger::getActiveDisplayRotationFlags()); +} + +TEST_F(ActiveDisplayRotationFlagsTest, rotate90) { + auto displayToken = mInnerDisplay->getDisplayToken().promote(); + mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0; + mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation = + ui::ROTATION_90; + + mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded); + ASSERT_EQ(ui::Transform::ROT_90, SurfaceFlinger::getActiveDisplayRotationFlags()); +} + +TEST_F(ActiveDisplayRotationFlagsTest, rotate90_inactive) { + auto displayToken = mOuterDisplay->getDisplayToken().promote(); + mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0; + mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation = + ui::ROTATION_90; + + mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded); + ASSERT_EQ(ui::Transform::ROT_0, SurfaceFlinger::getActiveDisplayRotationFlags()); +} + +TEST_F(ActiveDisplayRotationFlagsTest, rotateBoth_innerActive) { + auto displayToken = mInnerDisplay->getDisplayToken().promote(); + mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0; + mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation = + ui::ROTATION_180; + + displayToken = mOuterDisplay->getDisplayToken().promote(); + mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0; + mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation = + ui::ROTATION_270; + + mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded); + ASSERT_EQ(ui::Transform::ROT_180, SurfaceFlinger::getActiveDisplayRotationFlags()); +} + +TEST_F(ActiveDisplayRotationFlagsTest, rotateBoth_outerActive) { + mFlinger.mutableActiveDisplayId() = kOuterDisplayId; + auto displayToken = mInnerDisplay->getDisplayToken().promote(); + mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0; + mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation = + ui::ROTATION_180; + + displayToken = mOuterDisplay->getDisplayToken().promote(); + mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0; + mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation = + ui::ROTATION_270; + + mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded); + ASSERT_EQ(ui::Transform::ROT_270, SurfaceFlinger::getActiveDisplayRotationFlags()); +} + +TEST_F(ActiveDisplayRotationFlagsTest, onActiveDisplayChanged) { + auto displayToken = mInnerDisplay->getDisplayToken().promote(); + mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0; + mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation = + ui::ROTATION_180; + + displayToken = mOuterDisplay->getDisplayToken().promote(); + mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0; + mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation = + ui::ROTATION_270; + + mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded); + ASSERT_EQ(ui::Transform::ROT_180, SurfaceFlinger::getActiveDisplayRotationFlags()); + + mFlinger.onActiveDisplayChanged(mInnerDisplay.get(), *mOuterDisplay); + ASSERT_EQ(ui::Transform::ROT_270, SurfaceFlinger::getActiveDisplayRotationFlags()); +} + +} // namespace +} // namespace android diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index 55705cab76..70f8a8321e 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -70,6 +70,7 @@ cc_test { ":libsurfaceflinger_mock_sources", ":libsurfaceflinger_sources", "libsurfaceflinger_unittest_main.cpp", + "ActiveDisplayRotationFlagsTest.cpp", "CompositionTest.cpp", "DisplayIdGeneratorTest.cpp", "DisplayTransactionTest.cpp", diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index a189c002c9..8e18a3f0ee 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -600,6 +600,10 @@ public: auto& mutablePrimaryHwcDisplayId() { return getHwComposer().mPrimaryHwcDisplayId; } auto& mutableActiveDisplayId() { return mFlinger->mActiveDisplayId; } + auto& mutableActiveDisplayRotationFlags() { + return SurfaceFlinger::sActiveDisplayRotationFlags; + } + auto fromHandle(const sp& handle) { return LayerHandle::getLayer(handle); } ~TestableSurfaceFlinger() { -- GitLab From 23780be14df464f815d3fab7466d216cfbb2eae3 Mon Sep 17 00:00:00 2001 From: Pablo Gamito Date: Tue, 18 Apr 2023 08:30:00 +0000 Subject: [PATCH 1240/1310] Track transaction merges through transaction trace Bug: 233371599 Bug: 278663063 Test: dump a transaction trace and ensure transactions have the list of merged transaction ids in the proto Change-Id: I3edf8f1443a2573ef2086f221ceeef57172ecdbe --- libs/gui/ISurfaceComposer.cpp | 29 ++-- libs/gui/SurfaceComposerClient.cpp | 41 ++++- libs/gui/include/gui/ISurfaceComposer.h | 2 +- libs/gui/include/gui/SurfaceComposerClient.h | 7 + libs/gui/tests/Surface_test.cpp | 18 +- services/surfaceflinger/SurfaceFlinger.cpp | 6 +- services/surfaceflinger/SurfaceFlinger.h | 16 +- .../Tracing/TransactionProtoParser.cpp | 7 + services/surfaceflinger/TransactionState.h | 7 +- .../fuzzer/surfaceflinger_fuzzer.cpp | 5 +- .../fuzzer/surfaceflinger_fuzzers_utils.h | 19 +-- .../layerproto/transactions.proto | 1 + .../tests/unittests/TestableSurfaceFlinger.h | 19 +-- .../unittests/TransactionApplicationTest.cpp | 154 +++++++++++++++++- .../unittests/TransactionTracingTest.cpp | 11 +- 15 files changed, 274 insertions(+), 68 deletions(-) diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index d72f65eb7a..b526a6c92c 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -59,15 +59,13 @@ public: virtual ~BpSurfaceComposer(); - status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo, - Vector& state, const Vector& displays, - uint32_t flags, const sp& applyToken, - InputWindowCommands commands, int64_t desiredPresentTime, - bool isAutoTimestamp, - const std::vector& uncacheBuffers, - bool hasListenerCallbacks, - const std::vector& listenerCallbacks, - uint64_t transactionId) override { + status_t setTransactionState( + const FrameTimelineInfo& frameTimelineInfo, Vector& state, + const Vector& displays, uint32_t flags, const sp& applyToken, + InputWindowCommands commands, int64_t desiredPresentTime, bool isAutoTimestamp, + const std::vector& uncacheBuffers, bool hasListenerCallbacks, + const std::vector& listenerCallbacks, uint64_t transactionId, + const std::vector& mergedTransactionIds) override { Parcel data, reply; data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); @@ -103,6 +101,11 @@ public: SAFE_PARCEL(data.writeUint64, transactionId); + SAFE_PARCEL(data.writeUint32, static_cast(mergedTransactionIds.size())); + for (auto mergedTransactionId : mergedTransactionIds) { + SAFE_PARCEL(data.writeUint64, mergedTransactionId); + } + if (flags & ISurfaceComposer::eOneWay) { return remote()->transact(BnSurfaceComposer::SET_TRANSACTION_STATE, data, &reply, IBinder::FLAG_ONEWAY); @@ -187,10 +190,16 @@ status_t BnSurfaceComposer::onTransact( uint64_t transactionId = -1; SAFE_PARCEL(data.readUint64, &transactionId); + SAFE_PARCEL_READ_SIZE(data.readUint32, &count, data.dataSize()); + std::vector mergedTransactions(count); + for (size_t i = 0; i < count; i++) { + SAFE_PARCEL(data.readUint64, &mergedTransactions[i]); + } + return setTransactionState(frameTimelineInfo, state, displays, stateFlags, applyToken, std::move(inputWindowCommands), desiredPresentTime, isAutoTimestamp, uncacheBuffers, hasListenerCallbacks, - listenerCallbacks, transactionId); + listenerCallbacks, transactionId, mergedTransactions); } default: { return BBinder::onTransact(code, data, reply, flags); diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 1b13ec1c06..08a1a058e1 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -827,6 +827,15 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel SAFE_PARCEL(parcel->readUint64, &uncacheBuffers[i].id); } + count = static_cast(parcel->readUint32()); + if (count > parcel->dataSize()) { + return BAD_VALUE; + } + std::vector mergedTransactionIds(count); + for (size_t i = 0; i < count; i++) { + SAFE_PARCEL(parcel->readUint64, &mergedTransactionIds[i]); + } + // Parsing was successful. Update the object. mId = transactionId; mTransactionNestCount = transactionNestCount; @@ -842,6 +851,7 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel mInputWindowCommands = inputWindowCommands; mApplyToken = applyToken; mUncacheBuffers = std::move(uncacheBuffers); + mMergedTransactionIds = std::move(mergedTransactionIds); return NO_ERROR; } @@ -900,6 +910,11 @@ status_t SurfaceComposerClient::Transaction::writeToParcel(Parcel* parcel) const SAFE_PARCEL(parcel->writeUint64, uncacheBuffer.id); } + SAFE_PARCEL(parcel->writeUint32, static_cast(mMergedTransactionIds.size())); + for (auto mergedTransactionId : mMergedTransactionIds) { + SAFE_PARCEL(parcel->writeUint64, mergedTransactionId); + } + return NO_ERROR; } @@ -924,6 +939,22 @@ void SurfaceComposerClient::Transaction::releaseBufferIfOverwriting(const layer_ } SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::merge(Transaction&& other) { + while (mMergedTransactionIds.size() + other.mMergedTransactionIds.size() > + MAX_MERGE_HISTORY_LENGTH - 1 && + mMergedTransactionIds.size() > 0) { + mMergedTransactionIds.pop_back(); + } + if (other.mMergedTransactionIds.size() == MAX_MERGE_HISTORY_LENGTH) { + mMergedTransactionIds.insert(mMergedTransactionIds.begin(), + other.mMergedTransactionIds.begin(), + other.mMergedTransactionIds.end() - 1); + } else if (other.mMergedTransactionIds.size() > 0u) { + mMergedTransactionIds.insert(mMergedTransactionIds.begin(), + other.mMergedTransactionIds.begin(), + other.mMergedTransactionIds.end()); + } + mMergedTransactionIds.insert(mMergedTransactionIds.begin(), other.mId); + for (auto const& [handle, composerState] : other.mComposerStates) { if (mComposerStates.count(handle) == 0) { mComposerStates[handle] = composerState; @@ -998,12 +1029,17 @@ void SurfaceComposerClient::Transaction::clear() { mIsAutoTimestamp = true; clearFrameTimelineInfo(mFrameTimelineInfo); mApplyToken = nullptr; + mMergedTransactionIds.clear(); } uint64_t SurfaceComposerClient::Transaction::getId() { return mId; } +std::vector SurfaceComposerClient::Transaction::getMergedTransactionIds() { + return mMergedTransactionIds; +} + void SurfaceComposerClient::doUncacheBufferTransaction(uint64_t cacheId) { sp sf(ComposerService::getComposerService()); @@ -1014,7 +1050,7 @@ void SurfaceComposerClient::doUncacheBufferTransaction(uint64_t cacheId) { status_t status = sf->setTransactionState(FrameTimelineInfo{}, composerStates, {}, ISurfaceComposer::eOneWay, Transaction::getDefaultApplyToken(), {}, systemTime(), - true, {uncacheBuffer}, false, {}, generateId()); + true, {uncacheBuffer}, false, {}, generateId(), {}); if (status != NO_ERROR) { ALOGE_AND_TRACE("SurfaceComposerClient::doUncacheBufferTransaction - %s", strerror(-status)); @@ -1189,7 +1225,8 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay sp sf(ComposerService::getComposerService()); sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken, mInputWindowCommands, mDesiredPresentTime, mIsAutoTimestamp, - mUncacheBuffers, hasListenerCallbacks, listenerCallbacks, mId); + mUncacheBuffers, hasListenerCallbacks, listenerCallbacks, mId, + mMergedTransactionIds); mId = generateId(); // Clear the current states and flags diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index bd21851c14..7c150d53d9 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -116,7 +116,7 @@ public: InputWindowCommands inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, const std::vector& uncacheBuffer, bool hasListenerCallbacks, const std::vector& listenerCallbacks, - uint64_t transactionId) = 0; + uint64_t transactionId, const std::vector& mergedTransactionIds) = 0; }; // ---------------------------------------------------------------------------- diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 8d2cdaf5b8..fb57f63dad 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -419,6 +419,11 @@ public: mListenerCallbacks; std::vector mUncacheBuffers; + // We keep track of the last MAX_MERGE_HISTORY_LENGTH merged transaction ids. + // Ordered most recently merged to least recently merged. + static const size_t MAX_MERGE_HISTORY_LENGTH = 10u; + std::vector mMergedTransactionIds; + uint64_t mId; uint32_t mTransactionNestCount = 0; @@ -482,6 +487,8 @@ public: // The id is updated every time the transaction is applied. uint64_t getId(); + std::vector getMergedTransactionIds(); + status_t apply(bool synchronous = false, bool oneWay = false); // Merge another transaction in to this one, clearing other // as if it had been applied. diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 5bc6904563..096a43cd95 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -695,16 +695,14 @@ public: mSupportsPresent = supportsPresent; } - status_t setTransactionState(const FrameTimelineInfo& /*frameTimelineInfo*/, - Vector& /*state*/, - const Vector& /*displays*/, uint32_t /*flags*/, - const sp& /*applyToken*/, - InputWindowCommands /*inputWindowCommands*/, - int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/, - const std::vector& /*cachedBuffer*/, - bool /*hasListenerCallbacks*/, - const std::vector& /*listenerCallbacks*/, - uint64_t /*transactionId*/) override { + status_t setTransactionState( + const FrameTimelineInfo& /*frameTimelineInfo*/, Vector& /*state*/, + const Vector& /*displays*/, uint32_t /*flags*/, + const sp& /*applyToken*/, InputWindowCommands /*inputWindowCommands*/, + int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/, + const std::vector& /*cachedBuffer*/, bool /*hasListenerCallbacks*/, + const std::vector& /*listenerCallbacks*/, uint64_t /*transactionId*/, + const std::vector& /*mergedTransactionIds*/) override { return NO_ERROR; } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index dfe8c72bca..b87b36f7db 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4474,7 +4474,8 @@ status_t SurfaceFlinger::setTransactionState( const Vector& displays, uint32_t flags, const sp& applyToken, InputWindowCommands inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp, const std::vector& uncacheBuffers, bool hasListenerCallbacks, - const std::vector& listenerCallbacks, uint64_t transactionId) { + const std::vector& listenerCallbacks, uint64_t transactionId, + const std::vector& mergedTransactionIds) { ATRACE_CALL(); IPCThreadState* ipc = IPCThreadState::self(); @@ -4563,7 +4564,8 @@ status_t SurfaceFlinger::setTransactionState( listenerCallbacks, originPid, originUid, - transactionId}; + transactionId, + mergedTransactionIds}; if (mTransactionTracing) { mTransactionTracing->addQueuedTransaction(state); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index d92ec7a3b6..45a82ed23e 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -508,15 +508,13 @@ private: } sp getPhysicalDisplayToken(PhysicalDisplayId displayId) const; - status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo, - Vector& state, const Vector& displays, - uint32_t flags, const sp& applyToken, - InputWindowCommands inputWindowCommands, - int64_t desiredPresentTime, bool isAutoTimestamp, - const std::vector& uncacheBuffers, - bool hasListenerCallbacks, - const std::vector& listenerCallbacks, - uint64_t transactionId) override; + status_t setTransactionState( + const FrameTimelineInfo& frameTimelineInfo, Vector& state, + const Vector& displays, uint32_t flags, const sp& applyToken, + InputWindowCommands inputWindowCommands, int64_t desiredPresentTime, + bool isAutoTimestamp, const std::vector& uncacheBuffers, + bool hasListenerCallbacks, const std::vector& listenerCallbacks, + uint64_t transactionId, const std::vector& mergedTransactionIds) override; void bootFinished(); virtual status_t getSupportedFrameTimestamps(std::vector* outSupported) const; sp createDisplayEventConnection( diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp index 57d927b6bb..06941809ba 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp @@ -69,6 +69,13 @@ proto::TransactionState TransactionProtoParser::toProto(const TransactionState& for (auto& displayState : t.displays) { proto.mutable_display_changes()->Add(std::move(toProto(displayState))); } + + proto.mutable_merged_transaction_ids()->Reserve( + static_cast(t.mergedTransactionIds.size())); + for (auto& mergedTransactionId : t.mergedTransactionIds) { + proto.mutable_merged_transaction_ids()->Add(mergedTransactionId); + } + return proto; } diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h index 62a7dfd0f1..7132a59016 100644 --- a/services/surfaceflinger/TransactionState.h +++ b/services/surfaceflinger/TransactionState.h @@ -56,7 +56,8 @@ struct TransactionState { int64_t desiredPresentTime, bool isAutoTimestamp, std::vector uncacheBufferIds, int64_t postTime, bool hasListenerCallbacks, std::vector listenerCallbacks, - int originPid, int originUid, uint64_t transactionId) + int originPid, int originUid, uint64_t transactionId, + std::vector mergedTransactionIds) : frameTimelineInfo(frameTimelineInfo), states(std::move(composerStates)), displays(displayStates), @@ -71,7 +72,8 @@ struct TransactionState { listenerCallbacks(listenerCallbacks), originPid(originPid), originUid(originUid), - id(transactionId) {} + id(transactionId), + mergedTransactionIds(std::move(mergedTransactionIds)) {} // Invokes `void(const layer_state_t&)` visitor for matching layers. template @@ -131,6 +133,7 @@ struct TransactionState { int originUid; uint64_t id; bool sentFenceTimeoutWarning = false; + std::vector mergedTransactionIds; }; } // namespace android diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp index ce4d18fbe1..80943b5b63 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp @@ -185,11 +185,12 @@ void SurfaceFlingerFuzzer::setTransactionState() { bool hasListenerCallbacks = mFdp.ConsumeBool(); std::vector listenerCallbacks{}; uint64_t transactionId = mFdp.ConsumeIntegral(); + std::vector mergedTransactionIds{}; mTestableFlinger.setTransactionState(FrameTimelineInfo{}, states, displays, flags, applyToken, InputWindowCommands{}, desiredPresentTime, isAutoTimestamp, - {}, hasListenerCallbacks, listenerCallbacks, - transactionId); + {}, hasListenerCallbacks, listenerCallbacks, transactionId, + mergedTransactionIds); } void SurfaceFlingerFuzzer::setDisplayStateLocked() { diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 4d13aca5bb..da5ec480cc 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -737,19 +737,18 @@ public: return mFlinger->mTransactionHandler.mPendingTransactionQueues; } - auto setTransactionState(const FrameTimelineInfo& frameTimelineInfo, - Vector& states, const Vector& displays, - uint32_t flags, const sp& applyToken, - const InputWindowCommands& inputWindowCommands, - int64_t desiredPresentTime, bool isAutoTimestamp, - const std::vector& uncacheBuffers, - bool hasListenerCallbacks, - std::vector& listenerCallbacks, - uint64_t transactionId) { + auto setTransactionState( + const FrameTimelineInfo& frameTimelineInfo, Vector& states, + const Vector& displays, uint32_t flags, const sp& applyToken, + const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, + bool isAutoTimestamp, const std::vector& uncacheBuffers, + bool hasListenerCallbacks, std::vector& listenerCallbacks, + uint64_t transactionId, const std::vector& mergedTransactionIds) { return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken, inputWindowCommands, desiredPresentTime, isAutoTimestamp, uncacheBuffers, hasListenerCallbacks, - listenerCallbacks, transactionId); + listenerCallbacks, transactionId, + mergedTransactionIds); } auto flushTransactionQueues() { diff --git a/services/surfaceflinger/layerproto/transactions.proto b/services/surfaceflinger/layerproto/transactions.proto index 2c4eb101e6..b0cee9b398 100644 --- a/services/surfaceflinger/layerproto/transactions.proto +++ b/services/surfaceflinger/layerproto/transactions.proto @@ -100,6 +100,7 @@ message TransactionState { uint64 transaction_id = 6; repeated LayerState layer_changes = 7; repeated DisplayState display_changes = 8; + repeated uint64 merged_transaction_ids = 9; } // Keep insync with layer_state_t diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index a189c002c9..856606488b 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -466,19 +466,18 @@ public: return mFlinger->mTransactionHandler.mPendingTransactionCount.load(); } - auto setTransactionState(const FrameTimelineInfo& frameTimelineInfo, - Vector& states, const Vector& displays, - uint32_t flags, const sp& applyToken, - const InputWindowCommands& inputWindowCommands, - int64_t desiredPresentTime, bool isAutoTimestamp, - const std::vector& uncacheBuffers, - bool hasListenerCallbacks, - std::vector& listenerCallbacks, - uint64_t transactionId) { + auto setTransactionState( + const FrameTimelineInfo& frameTimelineInfo, Vector& states, + const Vector& displays, uint32_t flags, const sp& applyToken, + const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime, + bool isAutoTimestamp, const std::vector& uncacheBuffers, + bool hasListenerCallbacks, std::vector& listenerCallbacks, + uint64_t transactionId, const std::vector& mergedTransactionIds) { return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken, inputWindowCommands, desiredPresentTime, isAutoTimestamp, uncacheBuffers, hasListenerCallbacks, - listenerCallbacks, transactionId); + listenerCallbacks, transactionId, + mergedTransactionIds); } auto setTransactionStateInternal(TransactionState& transaction) { diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp index 6a641b3926..afb8efbfff 100644 --- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp @@ -73,6 +73,7 @@ public: FrameTimelineInfo frameTimelineInfo; std::vector uncacheBuffers; uint64_t id = static_cast(-1); + std::vector mergedTransactionIds; static_assert(0xffffffffffffffff == static_cast(-1)); }; @@ -108,7 +109,7 @@ public: transaction.applyToken, transaction.inputWindowCommands, transaction.desiredPresentTime, transaction.isAutoTimestamp, transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks, - transaction.id); + transaction.id, transaction.mergedTransactionIds); // If transaction is synchronous, SF applyTransactionState should time out (5s) wating for // SF to commit the transaction. If this is animation, it should not time out waiting. @@ -135,7 +136,7 @@ public: transaction.applyToken, transaction.inputWindowCommands, transaction.desiredPresentTime, transaction.isAutoTimestamp, transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks, - transaction.id); + transaction.id, transaction.mergedTransactionIds); nsecs_t returnedTime = systemTime(); EXPECT_LE(returnedTime, applicationSentTime + TRANSACTION_TIMEOUT); @@ -166,7 +167,7 @@ public: transactionA.applyToken, transactionA.inputWindowCommands, transactionA.desiredPresentTime, transactionA.isAutoTimestamp, transactionA.uncacheBuffers, mHasListenerCallbacks, mCallbacks, - transactionA.id); + transactionA.id, transactionA.mergedTransactionIds); // This thread should not have been blocked by the above transaction // (5s is the timeout period that applyTransactionState waits for SF to @@ -181,7 +182,7 @@ public: transactionB.applyToken, transactionB.inputWindowCommands, transactionB.desiredPresentTime, transactionB.isAutoTimestamp, transactionB.uncacheBuffers, mHasListenerCallbacks, mCallbacks, - transactionB.id); + transactionB.id, transactionB.mergedTransactionIds); // this thread should have been blocked by the above transaction // if this is an animation, this thread should be blocked for 5s @@ -218,7 +219,8 @@ TEST_F(TransactionApplicationTest, AddToPendingQueue) { transactionA.displays, transactionA.flags, transactionA.applyToken, transactionA.inputWindowCommands, transactionA.desiredPresentTime, transactionA.isAutoTimestamp, transactionA.uncacheBuffers, - mHasListenerCallbacks, mCallbacks, transactionA.id); + mHasListenerCallbacks, mCallbacks, transactionA.id, + transactionA.mergedTransactionIds); auto& transactionQueue = mFlinger.getTransactionQueue(); ASSERT_FALSE(transactionQueue.isEmpty()); @@ -238,7 +240,8 @@ TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) { transactionA.displays, transactionA.flags, transactionA.applyToken, transactionA.inputWindowCommands, transactionA.desiredPresentTime, transactionA.isAutoTimestamp, transactionA.uncacheBuffers, - mHasListenerCallbacks, mCallbacks, transactionA.id); + mHasListenerCallbacks, mCallbacks, transactionA.id, + transactionA.mergedTransactionIds); auto& transactionQueue = mFlinger.getTransactionQueue(); ASSERT_FALSE(transactionQueue.isEmpty()); @@ -251,7 +254,8 @@ TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) { mFlinger.setTransactionState(empty.frameTimelineInfo, empty.states, empty.displays, empty.flags, empty.applyToken, empty.inputWindowCommands, empty.desiredPresentTime, empty.isAutoTimestamp, - empty.uncacheBuffers, mHasListenerCallbacks, mCallbacks, empty.id); + empty.uncacheBuffers, mHasListenerCallbacks, mCallbacks, empty.id, + empty.mergedTransactionIds); // flush transaction queue should flush as desiredPresentTime has // passed @@ -388,7 +392,8 @@ public: transaction.desiredPresentTime, transaction.isAutoTimestamp, {}, systemTime(), mHasListenerCallbacks, mCallbacks, getpid(), - static_cast(getuid()), transaction.id); + static_cast(getuid()), transaction.id, + transaction.mergedTransactionIds); mFlinger.setTransactionStateInternal(transactionState); } mFlinger.flushTransactionQueues(); @@ -1083,4 +1088,137 @@ TEST(TransactionHandlerTest, QueueTransaction) { EXPECT_EQ(transactionsReadyToBeApplied.front().id, 42u); } +TEST(TransactionHandlerTest, TransactionsKeepTrackOfDirectMerges) { + SurfaceComposerClient::Transaction transaction1, transaction2, transaction3, transaction4; + + uint64_t transaction2Id = transaction2.getId(); + uint64_t transaction3Id = transaction3.getId(); + EXPECT_NE(transaction2Id, transaction3Id); + + transaction1.merge(std::move(transaction2)); + transaction1.merge(std::move(transaction3)); + + EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 2u); + EXPECT_EQ(transaction1.getMergedTransactionIds()[0], transaction3Id); + EXPECT_EQ(transaction1.getMergedTransactionIds()[1], transaction2Id); +} + +TEST(TransactionHandlerTest, TransactionsKeepTrackOfIndirectMerges) { + SurfaceComposerClient::Transaction transaction1, transaction2, transaction3, transaction4; + + uint64_t transaction2Id = transaction2.getId(); + uint64_t transaction3Id = transaction3.getId(); + uint64_t transaction4Id = transaction4.getId(); + EXPECT_NE(transaction2Id, transaction3Id); + EXPECT_NE(transaction2Id, transaction4Id); + EXPECT_NE(transaction3Id, transaction4Id); + + transaction4.merge(std::move(transaction2)); + transaction4.merge(std::move(transaction3)); + + EXPECT_EQ(transaction4.getMergedTransactionIds().size(), 2u); + EXPECT_EQ(transaction4.getMergedTransactionIds()[0], transaction3Id); + EXPECT_EQ(transaction4.getMergedTransactionIds()[1], transaction2Id); + + transaction1.merge(std::move(transaction4)); + + EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 3u); + EXPECT_EQ(transaction1.getMergedTransactionIds()[0], transaction4Id); + EXPECT_EQ(transaction1.getMergedTransactionIds()[1], transaction3Id); + EXPECT_EQ(transaction1.getMergedTransactionIds()[2], transaction2Id); +} + +TEST(TransactionHandlerTest, TransactionMergesAreCleared) { + SurfaceComposerClient::Transaction transaction1, transaction2, transaction3; + + transaction1.merge(std::move(transaction2)); + transaction1.merge(std::move(transaction3)); + + EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 2u); + + transaction1.clear(); + + EXPECT_EQ(transaction1.getMergedTransactionIds().empty(), true); +} + +TEST(TransactionHandlerTest, TransactionMergesAreCapped) { + SurfaceComposerClient::Transaction transaction; + std::vector mergedTransactionIds; + + for (uint i = 0; i < 20u; i++) { + SurfaceComposerClient::Transaction transactionToMerge; + mergedTransactionIds.push_back(transactionToMerge.getId()); + transaction.merge(std::move(transactionToMerge)); + } + + // Keeps latest 10 merges in order of merge recency + EXPECT_EQ(transaction.getMergedTransactionIds().size(), 10u); + for (uint i = 0; i < 10u; i++) { + EXPECT_EQ(transaction.getMergedTransactionIds()[i], + mergedTransactionIds[mergedTransactionIds.size() - 1 - i]); + } +} + +TEST(TransactionHandlerTest, KeepsMergesFromMoreRecentMerge) { + SurfaceComposerClient::Transaction transaction1, transaction2, transaction3; + std::vector mergedTransactionIds1, mergedTransactionIds2, mergedTransactionIds3; + uint64_t transaction2Id = transaction2.getId(); + uint64_t transaction3Id = transaction3.getId(); + + for (uint i = 0; i < 20u; i++) { + SurfaceComposerClient::Transaction transactionToMerge; + mergedTransactionIds1.push_back(transactionToMerge.getId()); + transaction1.merge(std::move(transactionToMerge)); + } + + for (uint i = 0; i < 5u; i++) { + SurfaceComposerClient::Transaction transactionToMerge; + mergedTransactionIds2.push_back(transactionToMerge.getId()); + transaction2.merge(std::move(transactionToMerge)); + } + + transaction1.merge(std::move(transaction2)); + EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 10u); + EXPECT_EQ(transaction1.getMergedTransactionIds()[0], transaction2Id); + for (uint i = 0; i < 5u; i++) { + EXPECT_EQ(transaction1.getMergedTransactionIds()[i + 1u], + mergedTransactionIds2[mergedTransactionIds2.size() - 1 - i]); + } + for (uint i = 0; i < 4u; i++) { + EXPECT_EQ(transaction1.getMergedTransactionIds()[i + 6u], + mergedTransactionIds1[mergedTransactionIds1.size() - 1 - i]); + } + + for (uint i = 0; i < 20u; i++) { + SurfaceComposerClient::Transaction transactionToMerge; + mergedTransactionIds3.push_back(transactionToMerge.getId()); + transaction3.merge(std::move(transactionToMerge)); + } + + transaction1.merge(std::move(transaction3)); + EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 10u); + EXPECT_EQ(transaction1.getMergedTransactionIds()[0], transaction3Id); + for (uint i = 0; i < 9u; i++) { + EXPECT_EQ(transaction1.getMergedTransactionIds()[i + 1], + mergedTransactionIds3[mergedTransactionIds3.size() - 1 - i]); + } +} + +TEST(TransactionHandlerTest, CanAddTransactionWithFullMergedIds) { + SurfaceComposerClient::Transaction transaction1, transaction2; + for (uint i = 0; i < 20u; i++) { + SurfaceComposerClient::Transaction transactionToMerge; + transaction1.merge(std::move(transactionToMerge)); + } + + EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 10u); + + auto transaction1Id = transaction1.getId(); + transaction2.merge(std::move(transaction1)); + EXPECT_EQ(transaction2.getMergedTransactionIds().size(), 10u); + auto mergedTransactionIds = transaction2.getMergedTransactionIds(); + EXPECT_TRUE(std::count(mergedTransactionIds.begin(), mergedTransactionIds.end(), + transaction1Id) > 0); +} + } // namespace android diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp index 82aac7e4bf..92411a7ba9 100644 --- a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp @@ -67,8 +67,14 @@ protected: ASSERT_EQ(actualProto.transactions().size(), static_cast(expectedTransactions.size())); for (uint32_t i = 0; i < expectedTransactions.size(); i++) { - EXPECT_EQ(actualProto.transactions(static_cast(i)).pid(), - expectedTransactions[i].originPid); + const auto expectedTransaction = expectedTransactions[i]; + const auto protoTransaction = actualProto.transactions(static_cast(i)); + EXPECT_EQ(protoTransaction.transaction_id(), expectedTransaction.id); + EXPECT_EQ(protoTransaction.pid(), expectedTransaction.originPid); + for (uint32_t i = 0; i < expectedTransaction.mergedTransactionIds.size(); i++) { + EXPECT_EQ(protoTransaction.merged_transaction_ids(static_cast(i)), + expectedTransaction.mergedTransactionIds[i]); + } } } @@ -92,6 +98,7 @@ TEST_F(TransactionTracingTest, addTransactions) { TransactionState transaction; transaction.id = i; transaction.originPid = static_cast(i); + transaction.mergedTransactionIds = std::vector{i + 100, i + 102}; transactions.emplace_back(transaction); mTracing.addQueuedTransaction(transaction); } -- GitLab From 835f1e74426a320274d95bd9a06bc09b1c4f36f8 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 10 May 2023 21:41:04 +0000 Subject: [PATCH 1241/1310] GestureConverter: Do not use NotifyArgs as an optional return type NotifyArgs is an std::variant, which is not an optional type. Default-constructing a value of that type results in the creation of the first type in the variant, which in this case is a NotifyInputDevicesChangedArgs. It is incorrect to send that event event when we intend to send no event. Bug: 281671810 Test: atest inputflinger_tests Change-Id: Ib78b3c354129f8494ee52115b2b99e210ceba6bf --- .../mapper/gestures/GestureConverter.cpp | 43 +++++++++++++------ .../reader/mapper/gestures/GestureConverter.h | 8 ++-- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp index fd2be5fd79..84de7d5d44 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp @@ -132,7 +132,7 @@ std::list GestureConverter::handleGesture(nsecs_t when, nsecs_t read case kGestureTypeScroll: return handleScroll(when, readTime, gesture); case kGestureTypeFling: - return {handleFling(when, readTime, gesture)}; + return handleFling(when, readTime, gesture); case kGestureTypeSwipe: return handleMultiFingerSwipe(when, readTime, 3, gesture.details.swipe.dx, gesture.details.swipe.dy); @@ -149,7 +149,8 @@ std::list GestureConverter::handleGesture(nsecs_t when, nsecs_t read } } -NotifyArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture) { +NotifyMotionArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime, + const Gesture& gesture) { float deltaX = gesture.details.move.dx; float deltaY = gesture.details.move.dy; rotateDelta(mOrientation, &deltaX, &deltaY); @@ -312,7 +313,8 @@ std::list GestureConverter::handleScroll(nsecs_t when, nsecs_t readT return out; } -NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const Gesture& gesture) { +std::list GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, + const Gesture& gesture) { // We don't actually want to use the gestures library's fling velocity values (to ensure // consistency between touchscreen and touchpad flings), so we're just using the "start fling" // gestures as a marker for the end of a two-finger scroll gesture. @@ -321,10 +323,10 @@ NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const G return {}; } - return endScroll(when, readTime); + return {endScroll(when, readTime)}; } -NotifyArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) { +NotifyMotionArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) { const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, 0); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, 0); @@ -507,14 +509,29 @@ NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime const PointerProperties* pointerProperties, const PointerCoords* pointerCoords, float xCursorPosition, float yCursorPosition) { - return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, SOURCE, - mPointerController->getDisplayId(), /* policyFlags= */ POLICY_FLAG_WAKE, - action, /* actionButton= */ actionButton, /* flags= */ 0, - mReaderContext.getGlobalMetaState(), buttonState, - mCurrentClassification, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, - pointerProperties, pointerCoords, /* xPrecision= */ 1.0f, - /* yPrecision= */ 1.0f, xCursorPosition, yCursorPosition, - /* downTime= */ mDownTime, /* videoFrames= */ {}); + return {mReaderContext.getNextId(), + when, + readTime, + mDeviceId, + SOURCE, + mPointerController->getDisplayId(), + /* policyFlags= */ POLICY_FLAG_WAKE, + action, + /* actionButton= */ actionButton, + /* flags= */ 0, + mReaderContext.getGlobalMetaState(), + buttonState, + mCurrentClassification, + AMOTION_EVENT_EDGE_FLAG_NONE, + pointerCount, + pointerProperties, + pointerCoords, + /* xPrecision= */ 1.0f, + /* yPrecision= */ 1.0f, + xCursorPosition, + yCursorPosition, + /* downTime= */ mDownTime, + /* videoFrames= */ {}}; } } // namespace android diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h index 70e8fb7758..b613b887cd 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h @@ -52,14 +52,16 @@ public: const Gesture& gesture); private: - [[nodiscard]] NotifyArgs handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture); + [[nodiscard]] NotifyMotionArgs handleMove(nsecs_t when, nsecs_t readTime, + const Gesture& gesture); [[nodiscard]] std::list handleButtonsChange(nsecs_t when, nsecs_t readTime, const Gesture& gesture); [[nodiscard]] std::list releaseAllButtons(nsecs_t when, nsecs_t readTime); [[nodiscard]] std::list handleScroll(nsecs_t when, nsecs_t readTime, const Gesture& gesture); - [[nodiscard]] NotifyArgs handleFling(nsecs_t when, nsecs_t readTime, const Gesture& gesture); - [[nodiscard]] NotifyArgs endScroll(nsecs_t when, nsecs_t readTime); + [[nodiscard]] std::list handleFling(nsecs_t when, nsecs_t readTime, + const Gesture& gesture); + [[nodiscard]] NotifyMotionArgs endScroll(nsecs_t when, nsecs_t readTime); [[nodiscard]] std::list handleMultiFingerSwipe(nsecs_t when, nsecs_t readTime, uint32_t fingerCount, float dx, -- GitLab From f7c4b0ebb6299d967d1141c83a0b88b936c3503e Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 10 May 2023 21:25:16 +0000 Subject: [PATCH 1242/1310] Use fling tap down gesture as a signal for touchpad contact ... to show the mouse cursor icon immediately upon contact with the touchpad. While the gestures library contains kGestureTypeContactInitiated, it is no longer used. Instead, when there is contact with the touchpad, the library produces a fling tap down gesture. The fling tap down gesture can be used as a signal to mark the end of a previouly started fling event. However, since the fling tap down gesture being generated roughly corresponds to a stable contact being detected in the current version of the library, we can use it as an alias for "contact initiated" as a workaround. This works because the fling tap down gesture is produced even if there was no fling start gesture produced in the past. Whenever we detect that a contact is initiated, we treat is as a move event of zero magnitude so that the mouse cursor is shown and a hover event is produced for apps to update their UI. An alternative approach that was considered was to track touching pointers from the evdev state, and use that ensure PointerController is showing the cursor whenever there is a valid pointer. This was disregarded as it could lead to inconsistencies with the gesture library. For example, if the gestures library immediately detects a contact as a palm, we don't want the cursor to be shown. Bug: 281671810 Test: atest inputflinger_tests Test: manual, with touchpad Change-Id: I01dbdd8a139cc6766fcd7e545a961e32237c2af9 --- .../mapper/gestures/GestureConverter.cpp | 33 +++++++++++++++---- .../tests/GestureConverter_test.cpp | 17 ++++++++++ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp index 84de7d5d44..7eca6fa0b4 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp @@ -315,15 +315,34 @@ std::list GestureConverter::handleScroll(nsecs_t when, nsecs_t readT std::list GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const Gesture& gesture) { - // We don't actually want to use the gestures library's fling velocity values (to ensure - // consistency between touchscreen and touchpad flings), so we're just using the "start fling" - // gestures as a marker for the end of a two-finger scroll gesture. - if (gesture.details.fling.fling_state != GESTURES_FLING_START || - mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) { - return {}; + switch (gesture.details.fling.fling_state) { + case GESTURES_FLING_START: + if (mCurrentClassification == MotionClassification::TWO_FINGER_SWIPE) { + // We don't actually want to use the gestures library's fling velocity values (to + // ensure consistency between touchscreen and touchpad flings), so we're just using + // the "start fling" gestures as a marker for the end of a two-finger scroll + // gesture. + return {endScroll(when, readTime)}; + } + break; + case GESTURES_FLING_TAP_DOWN: + if (mCurrentClassification == MotionClassification::NONE) { + // Use the tap down state of a fling gesture as an indicator that a contact + // has been initiated with the touchpad. We treat this as a move event with zero + // magnitude, which will also result in the pointer icon being updated. + // TODO(b/282023644): Add a signal in libgestures for when a stable contact has been + // initiated with a touchpad. + return {handleMove(when, readTime, + Gesture(kGestureMove, gesture.start_time, gesture.end_time, + /*dx=*/0.f, + /*dy=*/0.f))}; + } + break; + default: + break; } - return {endScroll(when, readTime)}; + return {}; } NotifyMotionArgs GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) { diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp index c6d541e962..a723636519 100644 --- a/services/inputflinger/tests/GestureConverter_test.cpp +++ b/services/inputflinger/tests/GestureConverter_test.cpp @@ -888,4 +888,21 @@ TEST_F(GestureConverterTest, ResetDuringPinch) { WithToolType(ToolType::FINGER))); } +TEST_F(GestureConverterTest, FlingTapDown) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + + Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, + /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN); + std::list args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapDownGesture); + + ASSERT_THAT(std::get(args.front()), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f), + WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f))); + + ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X, POINTER_Y)); + ASSERT_TRUE(mFakePointerController->isPointerShown()); +} + } // namespace android -- GitLab From b166c00440e3c01c6db04592ff48517350e3077f Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Tue, 9 May 2023 13:06:05 +0000 Subject: [PATCH 1243/1310] InputDispatcher: Only send touchpad nav gestures to trusted overlays Because we don't want apps using the touchpad navigation gestures (at the moment, any swipes with three or more fingers), we need to only send them to system UI windows. For now, determine this by checking for the TRUSTED_OVERLAY InputConfig flag. Bug: b/279803433 Test: atest inputflinger_tests Test: check that no events are sent to a test app when making three- or four-finger swipes on a touchpad Change-Id: I0830cb37d1ed615df0d6c546f1febbac89bab410 --- .../dispatcher/InputDispatcher.cpp | 28 ++++ .../tests/InputDispatcher_test.cpp | 130 +++++++++++++++++- 2 files changed, 157 insertions(+), 1 deletion(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 326ca87c41..0dd3dde718 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -681,6 +681,25 @@ std::vector& operator+=(std::vector& left, const std::vector& right) { return left; } +// Filter windows in a TouchState and targets in a vector to remove untrusted windows/targets from +// both. +void filterUntrustedTargets(TouchState& touchState, std::vector& targets) { + std::erase_if(touchState.windows, [&](const TouchedWindow& window) { + if (!window.windowHandle->getInfo()->inputConfig.test( + WindowInfo::InputConfig::TRUSTED_OVERLAY)) { + // In addition to TouchState, erase this window from the input targets! We don't have a + // good way to do this today except by adding a nested loop. + // TODO(b/282025641): simplify this code once InputTargets are being identified + // separately from TouchedWindows. + std::erase_if(targets, [&](const InputTarget& target) { + return target.inputChannel->getConnectionToken() == window.windowHandle->getToken(); + }); + return true; + } + return false; + }); +} + } // namespace // --- InputDispatcher --- @@ -2587,6 +2606,14 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } } + // If this is a touchpad navigation gesture, it needs to only be sent to trusted targets, as we + // only want the system UI to handle these gestures. + const bool isTouchpadNavGesture = isFromSource(entry.source, AINPUT_SOURCE_MOUSE) && + entry.classification == MotionClassification::MULTI_FINGER_SWIPE; + if (isTouchpadNavGesture) { + filterUntrustedTargets(/* byref */ tempTouchState, /* byref */ targets); + } + // Output targets from the touch state. for (const TouchedWindow& touchedWindow : tempTouchState.windows) { if (touchedWindow.pointerIds.none() && !touchedWindow.hasHoveringPointers(entry.deviceId)) { @@ -2594,6 +2621,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( // Do not send this event to those windows. continue; } + addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget, targets); diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index a6cdee5fb1..d4103fd19c 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -86,6 +86,8 @@ static constexpr int32_t POINTER_0_UP = AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); static constexpr int32_t POINTER_1_UP = AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); +static constexpr int32_t POINTER_2_UP = + AMOTION_EVENT_ACTION_POINTER_UP | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); // The default pid and uid for windows created on the primary display by the test. static constexpr int32_t WINDOW_PID = 999; @@ -1660,6 +1662,11 @@ public: return *this; } + MotionArgsBuilder& classification(MotionClassification classification) { + mClassification = classification; + return *this; + } + NotifyMotionArgs build() { std::vector pointerProperties; std::vector pointerCoords; @@ -1678,7 +1685,7 @@ public: NotifyMotionArgs args(InputEvent::nextId(), mEventTime, /*readTime=*/mEventTime, mDeviceId, mSource, mDisplayId, mPolicyFlags, mAction, mActionButton, mFlags, - AMETA_NONE, mButtonState, MotionClassification::NONE, /*edgeFlags=*/0, + AMETA_NONE, mButtonState, mClassification, /*edgeFlags=*/0, mPointers.size(), pointerProperties.data(), pointerCoords.data(), /*xPrecision=*/0, /*yPrecision=*/0, mRawXCursorPosition, mRawYCursorPosition, mDownTime, /*videoFrames=*/{}); @@ -1697,6 +1704,7 @@ private: int32_t mActionButton{0}; int32_t mButtonState{0}; int32_t mFlags{0}; + MotionClassification mClassification{MotionClassification::NONE}; float mRawXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION}; float mRawYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION}; @@ -4003,6 +4011,126 @@ TEST_F(InputDispatcherTest, NonSplitTouchableWindowReceivesMultiTouch) { EXPECT_EQ(-10, event->getY(1)); // -50 + 40 } +TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeOnlySentToTrustedOverlays) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 400, 400)); + sp trustedOverlay = + sp::make(application, mDispatcher, "Trusted Overlay", + ADISPLAY_ID_DEFAULT); + trustedOverlay->setSpy(true); + trustedOverlay->setTrustedOverlay(true); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {trustedOverlay, window}}}); + + // Start a three-finger touchpad swipe + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(100)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_2_DOWN, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(100)) + .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(100)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + + trustedOverlay->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + trustedOverlay->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + trustedOverlay->consumeMotionEvent(WithMotionAction(POINTER_2_DOWN)); + + // Move the swipe a bit + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105)) + .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(105)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + + trustedOverlay->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + + // End the swipe + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_2_UP, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105)) + .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(105)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + + trustedOverlay->consumeMotionEvent(WithMotionAction(POINTER_2_UP)); + trustedOverlay->consumeMotionEvent(WithMotionAction(POINTER_1_UP)); + trustedOverlay->consumeMotionEvent(WithMotionAction(ACTION_UP)); + + window->assertNoEvents(); +} + +TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeNotSentToSingleWindow) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 400, 400)); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + // Start a three-finger touchpad swipe + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(100)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_2_DOWN, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(100)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(100)) + .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(100)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + + // Move the swipe a bit + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105)) + .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(105)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + + // End the swipe + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_2_UP, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105)) + .pointer(PointerBuilder(2, ToolType::FINGER).x(300).y(105)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(250).y(105)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(105)) + .classification(MotionClassification::MULTI_FINGER_SWIPE) + .build()); + + window->assertNoEvents(); +} + /** * Ensure the correct coordinate spaces are used by InputDispatcher. * -- GitLab From 3fea8b580fd4e857b2e89cc5404b4a73a3e8d42f Mon Sep 17 00:00:00 2001 From: Kunal Malhotra Date: Fri, 5 May 2023 22:42:05 +0000 Subject: [PATCH 1244/1310] Updating CPP ActivityManager interface to be one way Ignore-AOSP-First: This code will go into AOSP later with U. Test: manual testing on device Bug: 279787820 Change-Id: I53b895982816144c00290bbb550b6bb290d86c30 --- libs/binder/IActivityManager.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libs/binder/IActivityManager.cpp b/libs/binder/IActivityManager.cpp index ebdaa4cc99..29bbf94f86 100644 --- a/libs/binder/IActivityManager.cpp +++ b/libs/binder/IActivityManager.cpp @@ -138,7 +138,8 @@ public: data.writeInt32(apiType); data.writeInt32(appUid); data.writeInt32(appPid); - status_t err = remote()->transact(LOG_FGS_API_BEGIN_TRANSACTION, data, &reply); + status_t err = remote()->transact(LOG_FGS_API_BEGIN_TRANSACTION, data, &reply, + IBinder::FLAG_ONEWAY); if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) { ALOGD("FGS Logger Transaction failed"); ALOGD("%d", err); @@ -153,7 +154,8 @@ public: data.writeInt32(apiType); data.writeInt32(appUid); data.writeInt32(appPid); - status_t err = remote()->transact(LOG_FGS_API_END_TRANSACTION, data, &reply); + status_t err = + remote()->transact(LOG_FGS_API_END_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY); if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) { ALOGD("FGS Logger Transaction failed"); ALOGD("%d", err); @@ -170,7 +172,8 @@ public: data.writeInt32(state); data.writeInt32(appUid); data.writeInt32(appPid); - status_t err = remote()->transact(LOG_FGS_API_BEGIN_TRANSACTION, data, &reply); + status_t err = remote()->transact(LOG_FGS_API_BEGIN_TRANSACTION, data, &reply, + IBinder::FLAG_ONEWAY); if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) { ALOGD("FGS Logger Transaction failed"); ALOGD("%d", err); -- GitLab From e5420e792426eb46e74e3fa7ba1aef66365097e8 Mon Sep 17 00:00:00 2001 From: Sergej Salnikov Date: Thu, 11 May 2023 11:52:54 +0000 Subject: [PATCH 1245/1310] InputDevice.hasKeys also checks for key usage Bug: 277216611 Bug: 282028956 Change-Id: I854888951ae0dd1287b8c519b92d9db36bbd28bf --- services/inputflinger/reader/EventHub.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index 0eb4ad25ee..0354164155 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -1072,16 +1072,8 @@ bool EventHub::markSupportedKeyCodes(int32_t deviceId, const std::vectorkeyMap.haveKeyLayout()) { for (size_t codeIndex = 0; codeIndex < keyCodes.size(); codeIndex++) { - std::vector scanCodes = - device->keyMap.keyLayoutMap->findScanCodesForKey(keyCodes[codeIndex]); - - // check the possible scan codes identified by the layout map against the - // map of codes actually emitted by the driver - for (const int32_t scanCode : scanCodes) { - if (device->keyBitmask.test(scanCode)) { - outFlags[codeIndex] = 1; - break; - } + if (device->hasKeycodeLocked(keyCodes[codeIndex])) { + outFlags[codeIndex] = 1; } } return true; -- GitLab From d88f807e621199456c68c77e1c623b45f32aaa8c Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Tue, 9 May 2023 13:58:18 -0400 Subject: [PATCH 1246/1310] Move rotation flags to SF The rotation flags are typically only used for a camera preview, which wants to avoid changing its orientation and flicker during rotation. Prior to this CL, the rotation flags were tied to the primary display, meaning that if the camera preview was on another display, the rotation flags may not be up to date. For example, if the primary display is off, its flags will not be updated on rotation. Ideally, the flags should be based on the display where the preview will be shown, but this is a much larger architectural change, tracked in b/259407931. As a temporary workaround, associate the flags with the active display. Store the flags in SurfaceFlinger, which knows when the active display changes. Update when the active display switches to a different display or when the active display rotates, matching the behavior of mActiveDisplayTransformHint, which seems similar but is different. Store the flags as a static variable so that LayerFE can access it. LayerFE does not have a way to access the actual SurfaceFlinger object, and it should not. Access to the new flags is safe because it is only read or written from the main thread. Bug: 269685949 Bug: 259407931 Test: ActiveDisplayRotationFlagsTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:9550c0945ee72bdda05972f70255e7a6b5c46c29) Merged-In: I5532e140a603be222cb3ea1ae563638317c1d745 Change-Id: I5532e140a603be222cb3ea1ae563638317c1d745 --- services/surfaceflinger/DisplayDevice.cpp | 10 -- services/surfaceflinger/DisplayDevice.h | 4 - .../surfaceflinger/FrontEnd/DisplayInfo.h | 2 +- .../FrontEnd/LayerSnapshotBuilder.cpp | 1 + services/surfaceflinger/Layer.cpp | 4 +- services/surfaceflinger/LayerFE.cpp | 4 +- services/surfaceflinger/SurfaceFlinger.cpp | 7 +- services/surfaceflinger/SurfaceFlinger.h | 12 ++ .../ActiveDisplayRotationFlagsTest.cpp | 148 ++++++++++++++++++ .../surfaceflinger/tests/unittests/Android.bp | 1 + .../tests/unittests/TestableSurfaceFlinger.h | 4 + 11 files changed, 177 insertions(+), 20 deletions(-) create mode 100644 services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 20f4de1d67..f6ca9e2856 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -50,8 +50,6 @@ namespace android { namespace hal = hardware::graphics::composer::hal; -ui::Transform::RotationFlags DisplayDevice::sPrimaryDisplayRotationFlags = ui::Transform::ROT_0; - DisplayDeviceCreationArgs::DisplayDeviceCreationArgs( const sp& flinger, HWComposer& hwComposer, const wp& displayToken, std::shared_ptr compositionDisplay) @@ -282,10 +280,6 @@ void DisplayDevice::setProjection(ui::Rotation orientation, Rect layerStackSpace Rect orientedDisplaySpaceRect) { mOrientation = orientation; - if (isPrimary()) { - sPrimaryDisplayRotationFlags = ui::Transform::toRotationFlags(orientation); - } - // We need to take care of display rotation for globalTransform for case if the panel is not // installed aligned with device orientation. const auto transformOrientation = orientation + mPhysicalOrientation; @@ -326,10 +320,6 @@ std::optional DisplayDevice::getStagedBrightness() const { return mStagedBrightness; } -ui::Transform::RotationFlags DisplayDevice::getPrimaryDisplayRotationFlags() { - return sPrimaryDisplayRotationFlags; -} - void DisplayDevice::dump(utils::Dumper& dumper) const { using namespace std::string_view_literals; diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index 51876e79a7..dc5f8a85af 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -109,8 +109,6 @@ public: ui::Rotation getPhysicalOrientation() const { return mPhysicalOrientation; } ui::Rotation getOrientation() const { return mOrientation; } - static ui::Transform::RotationFlags getPrimaryDisplayRotationFlags(); - std::optional getStagedBrightness() const REQUIRES(kMainThreadContext); ui::Transform::RotationFlags getTransformHint() const; const ui::Transform& getTransform() const; @@ -274,8 +272,6 @@ private: const ui::Rotation mPhysicalOrientation; ui::Rotation mOrientation = ui::ROTATION_0; - static ui::Transform::RotationFlags sPrimaryDisplayRotationFlags; - // Allow nullopt as initial power mode. using TracedPowerMode = TracedOrdinal; std::optional mPowerMode; diff --git a/services/surfaceflinger/FrontEnd/DisplayInfo.h b/services/surfaceflinger/FrontEnd/DisplayInfo.h index 76b36fe0f2..218a64a8d6 100644 --- a/services/surfaceflinger/FrontEnd/DisplayInfo.h +++ b/services/surfaceflinger/FrontEnd/DisplayInfo.h @@ -28,7 +28,7 @@ struct DisplayInfo { ui::Transform transform; bool receivesInput; bool isSecure; - // TODO(b/238781169) can eliminate once sPrimaryDisplayRotationFlags is removed. + // TODO(b/259407931): can eliminate once SurfaceFlinger::sActiveDisplayRotationFlags is removed. bool isPrimary; bool isVirtual; ui::Transform::RotationFlags rotationFlags; diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index ce7d37e69b..985c6f9fc9 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -667,6 +667,7 @@ void LayerSnapshotBuilder::resetRelativeState(LayerSnapshot& snapshot) { snapshot.relativeLayerMetadata.mMap.clear(); } +// TODO (b/259407931): Remove. uint32_t getPrimaryDisplayRotationFlags( const display::DisplayMap& displays) { for (auto& [_, display] : displays) { diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index a8214662c6..bf9b13b91a 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -2977,7 +2977,7 @@ bool Layer::updateGeometry() { if (mDrawingState.bufferTransform & ui::Transform::ROT_90) { std::swap(bufferWidth, bufferHeight); } - uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags(); + uint32_t invTransform = SurfaceFlinger::getActiveDisplayRotationFlags(); if (mDrawingState.transformToDisplayInverse) { if (invTransform & ui::Transform::ROT_90) { std::swap(bufferWidth, bufferHeight); @@ -3304,7 +3304,7 @@ Rect Layer::getBufferSize(const State& /*s*/) const { } if (getTransformToDisplayInverse()) { - uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags(); + uint32_t invTransform = SurfaceFlinger::getActiveDisplayRotationFlags(); if (invTransform & ui::Transform::ROT_90) { std::swap(bufWidth, bufHeight); } diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp index e713263433..f855f278c3 100644 --- a/services/surfaceflinger/LayerFE.cpp +++ b/services/surfaceflinger/LayerFE.cpp @@ -25,8 +25,8 @@ #include #include -#include "DisplayDevice.h" #include "LayerFE.h" +#include "SurfaceFlinger.h" namespace android { @@ -260,7 +260,7 @@ void LayerFE::prepareBufferStateClientComposition( * the code below applies the primary display's inverse transform to * the texture transform */ - uint32_t transform = DisplayDevice::getPrimaryDisplayRotationFlags(); + uint32_t transform = SurfaceFlinger::getActiveDisplayRotationFlags(); mat4 tr = inverseOrientation(transform); /** diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 06ac8cb5af..799e646cc8 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -356,6 +356,8 @@ bool callingThreadHasPermission(const String16& permission) { PermissionCache::checkPermission(permission, pid, uid); } +ui::Transform::RotationFlags SurfaceFlinger::sActiveDisplayRotationFlags = ui::Transform::ROT_0; + SurfaceFlinger::SurfaceFlinger(Factory& factory, SkipInitializationTag) : mFactory(factory), mPid(getpid()), @@ -2619,7 +2621,7 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty; refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty; - refreshArgs.internalDisplayRotationFlags = DisplayDevice::getPrimaryDisplayRotationFlags(); + refreshArgs.internalDisplayRotationFlags = getActiveDisplayRotationFlags(); if (CC_UNLIKELY(mDrawingState.colorMatrixChanged)) { refreshArgs.colorTransformMatrix = mDrawingState.colorMatrix; @@ -3563,6 +3565,8 @@ void SurfaceFlinger::processDisplayChanged(const wp& displayToken, currentState.orientedDisplaySpaceRect); if (display->getId() == mActiveDisplayId) { mActiveDisplayTransformHint = display->getTransformHint(); + sActiveDisplayRotationFlags = + ui::Transform::toRotationFlags(display->getOrientation()); } } if (currentState.width != drawingState.width || @@ -7954,6 +7958,7 @@ void SurfaceFlinger::onActiveDisplayChangedLocked(const DisplayDevice* inactiveD onActiveDisplaySizeChanged(activeDisplay); mActiveDisplayTransformHint = activeDisplay.getTransformHint(); + sActiveDisplayRotationFlags = ui::Transform::toRotationFlags(activeDisplay.getOrientation()); // The policy of the new active/pacesetter display may have changed while it was inactive. In // that case, its preferred mode has not been propagated to HWC (via setDesiredActiveMode). In diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index d92ec7a3b6..ec36e95a41 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -332,6 +332,14 @@ public: const DisplayDevice* getDisplayFromLayerStack(ui::LayerStack) REQUIRES(mStateLock, kMainThreadContext); + // TODO (b/259407931): Remove. + // TODO (b/281857977): This should be annotated with REQUIRES(kMainThreadContext), but this + // would require thread safety annotations throughout the frontend (in particular Layer and + // LayerFE). + static ui::Transform::RotationFlags getActiveDisplayRotationFlags() { + return sActiveDisplayRotationFlags; + } + protected: // We're reference counted, never destroy SurfaceFlinger directly virtual ~SurfaceFlinger(); @@ -1390,6 +1398,10 @@ private: std::atomic mActiveDisplayTransformHint; + // Must only be accessed on the main thread. + // TODO (b/259407931): Remove. + static ui::Transform::RotationFlags sActiveDisplayRotationFlags; + bool isRefreshRateOverlayEnabled() const REQUIRES(mStateLock) { return hasDisplay( [](const auto& display) { return display.isRefreshRateOverlayEnabled(); }); diff --git a/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp new file mode 100644 index 0000000000..7077523fc3 --- /dev/null +++ b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp @@ -0,0 +1,148 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "LibSurfaceFlingerUnittests" + +#include "DisplayTransactionTestHelpers.h" + +#include +#include + +namespace android { +namespace { + +struct ActiveDisplayRotationFlagsTest : DisplayTransactionTest { + static constexpr bool kWithMockScheduler = false; + ActiveDisplayRotationFlagsTest() : DisplayTransactionTest(kWithMockScheduler) {} + + void SetUp() override { + injectMockScheduler(kInnerDisplayId); + + // Inject inner and outer displays with uninitialized power modes. + constexpr bool kInitPowerMode = false; + { + InnerDisplayVariant::injectHwcDisplay(this); + auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this); + injector.setPowerMode(std::nullopt); + injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector()); + mInnerDisplay = injector.inject(); + } + { + OuterDisplayVariant::injectHwcDisplay(this); + auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this); + injector.setPowerMode(std::nullopt); + mOuterDisplay = injector.inject(); + } + + mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON); + mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::ON); + + // The flags are a static variable, so by modifying them in the test, we + // are modifying the real ones used by SurfaceFlinger. Save the original + // flags so we can restore them on teardown. This isn't perfect - the + // phone may have been rotated during the test, so we're restoring the + // wrong flags. But if the phone is rotated, this may also fail the test. + mOldRotationFlags = mFlinger.mutableActiveDisplayRotationFlags(); + + // Reset to the expected default state. + mFlinger.mutableActiveDisplayRotationFlags() = ui::Transform::ROT_0; + } + + void TearDown() override { mFlinger.mutableActiveDisplayRotationFlags() = mOldRotationFlags; } + + static inline PhysicalDisplayId kInnerDisplayId = InnerDisplayVariant::DISPLAY_ID::get(); + static inline PhysicalDisplayId kOuterDisplayId = OuterDisplayVariant::DISPLAY_ID::get(); + + sp mInnerDisplay, mOuterDisplay; + ui::Transform::RotationFlags mOldRotationFlags; +}; + +TEST_F(ActiveDisplayRotationFlagsTest, defaultRotation) { + ASSERT_EQ(ui::Transform::ROT_0, SurfaceFlinger::getActiveDisplayRotationFlags()); +} + +TEST_F(ActiveDisplayRotationFlagsTest, rotate90) { + auto displayToken = mInnerDisplay->getDisplayToken().promote(); + mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0; + mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation = + ui::ROTATION_90; + + mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded); + ASSERT_EQ(ui::Transform::ROT_90, SurfaceFlinger::getActiveDisplayRotationFlags()); +} + +TEST_F(ActiveDisplayRotationFlagsTest, rotate90_inactive) { + auto displayToken = mOuterDisplay->getDisplayToken().promote(); + mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0; + mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation = + ui::ROTATION_90; + + mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded); + ASSERT_EQ(ui::Transform::ROT_0, SurfaceFlinger::getActiveDisplayRotationFlags()); +} + +TEST_F(ActiveDisplayRotationFlagsTest, rotateBoth_innerActive) { + auto displayToken = mInnerDisplay->getDisplayToken().promote(); + mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0; + mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation = + ui::ROTATION_180; + + displayToken = mOuterDisplay->getDisplayToken().promote(); + mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0; + mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation = + ui::ROTATION_270; + + mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded); + ASSERT_EQ(ui::Transform::ROT_180, SurfaceFlinger::getActiveDisplayRotationFlags()); +} + +TEST_F(ActiveDisplayRotationFlagsTest, rotateBoth_outerActive) { + mFlinger.mutableActiveDisplayId() = kOuterDisplayId; + auto displayToken = mInnerDisplay->getDisplayToken().promote(); + mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0; + mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation = + ui::ROTATION_180; + + displayToken = mOuterDisplay->getDisplayToken().promote(); + mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0; + mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation = + ui::ROTATION_270; + + mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded); + ASSERT_EQ(ui::Transform::ROT_270, SurfaceFlinger::getActiveDisplayRotationFlags()); +} + +TEST_F(ActiveDisplayRotationFlagsTest, onActiveDisplayChanged) { + auto displayToken = mInnerDisplay->getDisplayToken().promote(); + mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0; + mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation = + ui::ROTATION_180; + + displayToken = mOuterDisplay->getDisplayToken().promote(); + mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0; + mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation = + ui::ROTATION_270; + + mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded); + ASSERT_EQ(ui::Transform::ROT_180, SurfaceFlinger::getActiveDisplayRotationFlags()); + + mFlinger.onActiveDisplayChanged(mInnerDisplay.get(), *mOuterDisplay); + ASSERT_EQ(ui::Transform::ROT_270, SurfaceFlinger::getActiveDisplayRotationFlags()); +} + +} // namespace +} // namespace android diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index 55705cab76..70f8a8321e 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -70,6 +70,7 @@ cc_test { ":libsurfaceflinger_mock_sources", ":libsurfaceflinger_sources", "libsurfaceflinger_unittest_main.cpp", + "ActiveDisplayRotationFlagsTest.cpp", "CompositionTest.cpp", "DisplayIdGeneratorTest.cpp", "DisplayTransactionTest.cpp", diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index a189c002c9..8e18a3f0ee 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -600,6 +600,10 @@ public: auto& mutablePrimaryHwcDisplayId() { return getHwComposer().mPrimaryHwcDisplayId; } auto& mutableActiveDisplayId() { return mFlinger->mActiveDisplayId; } + auto& mutableActiveDisplayRotationFlags() { + return SurfaceFlinger::sActiveDisplayRotationFlags; + } + auto fromHandle(const sp& handle) { return LayerHandle::getLayer(handle); } ~TestableSurfaceFlinger() { -- GitLab From afb6b50ee815108a6f5f290e1b3753069f3bd9d3 Mon Sep 17 00:00:00 2001 From: Rachel Lee Date: Fri, 12 May 2023 15:53:05 -0700 Subject: [PATCH 1247/1310] Populate preferred frame timeline always `getLatestVsyncEventData` sometimes ends up with 0 length frame timelines. When `generateFrameTimeline`, always populate with the preferred timeline information from the vsync schedule. Bug: 282110947 Test: atest EventThreadTest Test: atest DisplayEventReceiverTest Change-Id: I936d3b7095ce89ce3bd898f6b6a4f73145c27160 --- .../surfaceflinger/Scheduler/EventThread.cpp | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index af9acf3346..281b0ae559 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -612,6 +612,15 @@ void EventThread::generateFrameTimeline(VsyncEventData& outVsyncEventData, nsecs preferredExpectedPresentationTime + multiplier * frameInterval; if (expectedPresentationTime >= preferredExpectedPresentationTime + scheduler::VsyncConfig::kEarlyLatchMaxThreshold.count()) { + if (currentIndex == 0) { + ALOGW("%s: Expected present time is too far in the future but no timelines are " + "valid. preferred EPT=%" PRId64 ", Calculated EPT=%" PRId64 + ", multiplier=%" PRId64 ", frameInterval=%" PRId64 ", threshold=%" PRId64, + __func__, preferredExpectedPresentationTime, expectedPresentationTime, + multiplier, frameInterval, + static_cast( + scheduler::VsyncConfig::kEarlyLatchMaxThreshold.count())); + } break; } @@ -625,6 +634,20 @@ void EventThread::generateFrameTimeline(VsyncEventData& outVsyncEventData, nsecs .expectedPresentationTime = expectedPresentationTime}; currentIndex++; } + + if (currentIndex == 0) { + ALOGW("%s: No timelines are valid. preferred EPT=%" PRId64 ", frameInterval=%" PRId64 + ", threshold=%" PRId64, + __func__, preferredExpectedPresentationTime, frameInterval, + static_cast(scheduler::VsyncConfig::kEarlyLatchMaxThreshold.count())); + outVsyncEventData.frameTimelines[currentIndex] = + {.vsyncId = generateToken(timestamp, preferredDeadlineTimestamp, + preferredExpectedPresentationTime), + .deadlineTimestamp = preferredDeadlineTimestamp, + .expectedPresentationTime = preferredExpectedPresentationTime}; + currentIndex++; + } + outVsyncEventData.frameTimelinesLength = currentIndex; } -- GitLab From 341477e54b672fba0ea19e7f470c5801df2d4bbe Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Fri, 3 Mar 2023 23:31:17 +0000 Subject: [PATCH 1248/1310] Revert^2 "Use "SessionHint" enum in ndk API" Bug: 266596626 63a0fd1944c0fe541e6cc3af16cc1119ac7d1065 Change-Id: Icd1164728c38b9d813dbe5f47118a08c4bef48dc --- include/private/performance_hint_private.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h index eaf3b5e791..d50c5f846e 100644 --- a/include/private/performance_hint_private.h +++ b/include/private/performance_hint_private.h @@ -17,6 +17,8 @@ #ifndef ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H #define ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H +#include + __BEGIN_DECLS /** @@ -27,7 +29,7 @@ void APerformanceHint_setIHintManagerForTesting(void* iManager); /** * Hints for the session used to signal upcoming changes in the mode or workload. */ -enum SessionHint { +enum SessionHint: int32_t { /** * This hint indicates a sudden increase in CPU workload intensity. It means * that this hint session needs extra CPU resources immediately to meet the @@ -61,7 +63,7 @@ enum SessionHint { * @return 0 on success * EPIPE if communication with the system service has failed. */ -int APerformanceHint_sendHint(void* session, int hint); +int APerformanceHint_sendHint(void* session, SessionHint hint); /** * Return the list of thread ids, this API should only be used for testing only. -- GitLab From 0987fd8824d38e8bb4642fec705842a1c27bace8 Mon Sep 17 00:00:00 2001 From: Priyank Singh Date: Mon, 15 May 2023 12:30:46 -0700 Subject: [PATCH 1249/1310] fix touch issue on portrait reference Fix: 282076064 Test: Manual Change-Id: I3fd499ff9d8195af73e960242005965bca08e650 --- services/inputflinger/reader/InputDevice.cpp | 4 ++-- services/inputflinger/reader/mapper/MultiTouchInputMapper.h | 4 ++-- services/inputflinger/reader/mapper/SingleTouchInputMapper.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 3f6d557aa8..c8c5115d64 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -503,9 +503,9 @@ std::vector> InputDevice::createMappers( classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) { mappers.push_back(createInputMapper(contextPtr, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH_MT)) { - mappers.push_back(createInputMapper(contextPtr, readerConfig)); + mappers.push_back(std::make_unique(contextPtr, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH)) { - mappers.push_back(createInputMapper(contextPtr, readerConfig)); + mappers.push_back(std::make_unique(contextPtr, readerConfig)); } // Joystick-like devices. diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h index 1d788dffd4..f300ee15bd 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h @@ -27,6 +27,8 @@ public: friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig, Args... args); + explicit MultiTouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); ~MultiTouchInputMapper() override; @@ -39,8 +41,6 @@ protected: bool hasStylus() const override; private: - explicit MultiTouchInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); // simulate_stylus_with_touch is a debug mode that converts all finger pointers reported by this // mapper's touchscreen into stylus pointers, and adds SOURCE_STYLUS to the input device. // It is used to simulate stylus events for debugging and testing on a device that does not diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h index 7726bfb159..dac53cf700 100644 --- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h @@ -27,6 +27,8 @@ public: friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig, Args... args); + explicit SingleTouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); ~SingleTouchInputMapper() override; @@ -40,8 +42,6 @@ protected: private: SingleTouchMotionAccumulator mSingleTouchMotionAccumulator; - explicit SingleTouchInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); }; } // namespace android -- GitLab From 70c6ee8fff6c40fa78d4cff5643c6156d99b764f Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 15 May 2023 17:43:48 -0700 Subject: [PATCH 1250/1310] Set cursor position to the first pointer for mouse events In tests, a common task is to create an instance of NotifyMotionArgs. A builder is used to define it. Before this CL, the builder used to set cursor position only for the cases where there is exactly 1 pointer in the event. However, now, with the new touchpad stack, there may be > 1 pointer created for the SOURCE_MOUSE events. Relax the restriction to avoid the boilderplate of setting the cursorposition for mouse events in the tests. This patch is a temporary workaround for the ubsan failure in a newly added test TouchpadThreeFingerSwipeNotSentToSingleWindow. Bug: 263319225 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: Ifa8858c3f0f22978de822473d2362e70aba4e01c --- services/inputflinger/tests/InputDispatcher_test.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index fecd0d1315..3f2658a71d 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -1564,8 +1564,7 @@ public: // Set mouse cursor position for the most common cases to avoid boilerplate. if (mSource == AINPUT_SOURCE_MOUSE && - !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) && - mPointers.size() == 1) { + !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition)) { mRawXCursorPosition = pointerCoords[0].getX(); mRawYCursorPosition = pointerCoords[0].getY(); } @@ -1677,8 +1676,7 @@ public: // Set mouse cursor position for the most common cases to avoid boilerplate. if (mSource == AINPUT_SOURCE_MOUSE && - !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) && - mPointers.size() == 1) { + !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition)) { mRawXCursorPosition = pointerCoords[0].getX(); mRawYCursorPosition = pointerCoords[0].getY(); } -- GitLab From 6c377b36aaacf19192484fac3b6c864f5e6f886a Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 15 May 2023 17:03:39 -0700 Subject: [PATCH 1251/1310] Create InputTarget in a separate function To make the code more readable and reusable, move the creation of InputTarget into a separate function. In a future commit, the new function ' createInputTargetLocked' will be needed in other places of InputDispatcher, too. Bug: 263319225 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: Iae78c7c812afe5bb059c56669a3de00fdfc496ef --- include/input/Input.h | 1 - .../dispatcher/InputDispatcher.cpp | 45 ++++++++++++------- .../inputflinger/dispatcher/InputDispatcher.h | 4 ++ 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/include/input/Input.h b/include/input/Input.h index 1e810b438a..fe0c775fd3 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index a3c129e31a..9125fe49e0 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2811,6 +2811,30 @@ void InputDispatcher::addDragEventLocked(const MotionEntry& entry) { } } +std::optional InputDispatcher::createInputTargetLocked( + const sp& windowHandle, + ftl::Flags targetFlags, + std::optional firstDownTimeInTarget) const { + std::shared_ptr inputChannel = getInputChannelLocked(windowHandle->getToken()); + if (inputChannel == nullptr) { + ALOGW("Not creating InputTarget for %s, no input channel", windowHandle->getName().c_str()); + return {}; + } + InputTarget inputTarget; + inputTarget.inputChannel = inputChannel; + inputTarget.flags = targetFlags; + inputTarget.globalScaleFactor = windowHandle->getInfo()->globalScaleFactor; + inputTarget.firstDownTimeInTarget = firstDownTimeInTarget; + const auto& displayInfoIt = mDisplayInfos.find(windowHandle->getInfo()->displayId); + if (displayInfoIt != mDisplayInfos.end()) { + inputTarget.displayTransform = displayInfoIt->second.transform; + } else { + // DisplayInfo not found for this window on display windowInfo->displayId. + // TODO(b/198444055): Make this an error message after 'setInputWindows' API is removed. + } + return inputTarget; +} + void InputDispatcher::addWindowTargetLocked(const sp& windowHandle, ftl::Flags targetFlags, std::bitset pointerIds, @@ -2826,25 +2850,12 @@ void InputDispatcher::addWindowTargetLocked(const sp& windowHa const WindowInfo* windowInfo = windowHandle->getInfo(); if (it == inputTargets.end()) { - InputTarget inputTarget; - std::shared_ptr inputChannel = - getInputChannelLocked(windowHandle->getToken()); - if (inputChannel == nullptr) { - ALOGW("Window %s already unregistered input channel", windowHandle->getName().c_str()); + std::optional target = + createInputTargetLocked(windowHandle, targetFlags, firstDownTimeInTarget); + if (!target) { return; } - inputTarget.inputChannel = inputChannel; - inputTarget.flags = targetFlags; - inputTarget.globalScaleFactor = windowInfo->globalScaleFactor; - inputTarget.firstDownTimeInTarget = firstDownTimeInTarget; - const auto& displayInfoIt = mDisplayInfos.find(windowInfo->displayId); - if (displayInfoIt != mDisplayInfos.end()) { - inputTarget.displayTransform = displayInfoIt->second.transform; - } else { - // DisplayInfo not found for this window on display windowInfo->displayId. - // TODO(b/198444055): Make this an error message after 'setInputWindows' API is removed. - } - inputTargets.push_back(inputTarget); + inputTargets.push_back(*target); it = inputTargets.end() - 1; } diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 9b12f2f64c..0e9cfeffe4 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -556,6 +556,10 @@ private: std::vector selectResponsiveMonitorsLocked( const std::vector& gestureMonitors) const REQUIRES(mLock); + std::optional createInputTargetLocked( + const sp& windowHandle, + ftl::Flags targetFlags, + std::optional firstDownTimeInTarget) const REQUIRES(mLock); void addWindowTargetLocked(const sp& windowHandle, ftl::Flags targetFlags, std::bitset pointerIds, -- GitLab From 2964072dd866b76fe3b42c925271e930cbdb3ca7 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Tue, 16 May 2023 23:22:41 +0000 Subject: [PATCH 1252/1310] Disable HDR screenshots Bug: 282823628 Change-Id: I60867302eaf968f6b756e3ec37dd55caddf98a49 Test: screencap --- services/surfaceflinger/SurfaceFlinger.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index dfe8c72bca..799652bcc1 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -6952,7 +6952,8 @@ ui::Dataspace pickBestDataspace(ui::Dataspace requestedDataspace, const DisplayD const auto dataspaceForColorMode = ui::pickDataspaceFor(state.colorMode); - if (capturingHdrLayers && !hintForSeamlessTransition) { + // TODO: Enable once HDR screenshots are ready. + if constexpr (/* DISABLES CODE */ (false)) { // For now since we only support 8-bit screenshots, just use HLG and // assume that 1.0 >= display max luminance. This isn't quite as future // proof as PQ is, but is good enough. -- GitLab From 6ba61eb54c70218394de22913baa55d610fb97a4 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 16 May 2023 17:07:02 -0700 Subject: [PATCH 1253/1310] [sf] Add readme for surfaceflinger frontend Test: presubmit Bug: 280751178, 238781169 Change-Id: I6511548a431b715acd598b009208c59ee65b1e3f --- services/surfaceflinger/FrontEnd/readme.md | 110 +++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 services/surfaceflinger/FrontEnd/readme.md diff --git a/services/surfaceflinger/FrontEnd/readme.md b/services/surfaceflinger/FrontEnd/readme.md new file mode 100644 index 0000000000..e5f51a5773 --- /dev/null +++ b/services/surfaceflinger/FrontEnd/readme.md @@ -0,0 +1,110 @@ +# SurfaceFlinger FrontEnd + +SurfaceFlinger FrontEnd implements the client APIs that describe how buffers should be +composited on the screen. Layers are used to capture how the buffer should be composited +and each buffer is associated with a Layer. Transactions contain an atomic set of changes +to one or more of these layers. The FrontEnd consumes these transactions, maintains the +layer lifecycle, and provides a snapshot to the composition engine every frame that +describes how a set of buffers should be composited. + + + +## Layers +Layers are used to describe how a buffer should be placed on the display relative to other +buffers. They are represented as a hierarchy, similar to a scene graph. Child layers can +inherit some properties from their parents, which allows higher-level system components to +maintain policies at different levels without needing to understand the entire hierarchy. +This allows control to be delegated to different parts of the system - such as SystemServer, +SysUI and Apps. + +### Layer Lifecycle +Layer is created by a client. The client receives a strong binder reference to the layer +handle, which will keep the layer alive as long as the client holds the reference. The +layer can also be kept alive if the layer has a parent, since the parent will hold a +strong reference to the children. If the layer is not reachable but its handle is alive, +the layer will be offscreen and its resources will not be freed. Clients must explicitly +release all references to the handle as soon as it's done with the layer. It's strongly +recommended to explicitly release the layer in Java and not rely on the GC. + + + +## Transactions +Transactions contain a group of changes to one or more layers that are applied together. +Transactions can be merged to apply a set of changes atomically. Merges are associative, +meaning how you group the merges does not matter, but they are not commutative, meaning +that the order in which you merge them does. +For example: + +`Transaction a; a.setAlpha(sc, 2);` + +`Transaction b; b.setAlpha(sc, 4);` + +`a.merge(b)` is not the same as `b.merge(a)` + +

+ +`Transaction c; c.setAlpha(sc, 6);` + +`a.merge(b).merge(c)` is the same as `b.merge(c); a.merge(b);` + +Transactions are queued in SurfaceFlinger per ApplyToken so order is only guaranteed for +Transactions with the same applyToken. By default each process and each buffer producer +provides a unique ApplyToken. This prevents clients from affecting one another, and possibly +slowing each other down. + + + +## Architecture +SurfaceFlinger FrontEnd intends to optimize for predictability and performance because state +generation is on the hotpath. Simple buffer updates should be as fast as possible, and they +should be consistently fast. This means avoiding contention (e.g., locks) and context +switching. We also want to avoid doing anything that does not contribute to putting a pixel +on the display. + +The pipeline can be broken down into five stages: +- Queue and filter transactions that are ready to be committed. +- Handle layer lifecycles and update server-side state per layer. +- Generate and/or update the traversal trees. +- Generate a z-ordered list of snapshots. +- Emit callbacks back to clients + + +### TransactionHandler +TransactionHandler is responsible for queuing and filtering transactions that are ready to +be applied. On commit, we filter the transactions that are ready. We provide an interface +for other components to apply their own filter to determine if a transaction is ready to be +applied. + + +### LayerLifecycleManager +RequestedLayerState is a simple data class that stores the server side layer state. +Transactions are merged into this state, similar to how transactions can be merged on the +client side. The states can always be reconstructed from LayerCreationArgs and a list of +transactions. LayerLifecycleManager keeps track of Layer handle lifecycle and the layer +lifecycle itself. It consumes a list of transactions and generates a list of server side +states and change flags. Other components can register to listen to layer lifecycles. + + +### LayerHierarchyBuilder +LayerHierarchyBuilder consumes a list of RequestedLayerStates to generate a LayerHierarchy. +The hierarchy provides functions for breadth-first traversal and z-order traversal of the +entire tree or a subtree. Internally, the hierarchy is represented by a graph. Mirrored +layers are represented by the same node in the graph with multiple parents. This allows us +to implement mirroring without cloning Layers and maintaining complex hierarchies. + + +### LayerSnapshotBuilder +LayerSnapshotBuilder consumes a LayerHierarchy along with a list of RequestedLayerStates to +generate a flattened z-ordered list of LayerSnapshots. LayerSnapshots contain all the data +required for CompositionEngine and RenderEngine. It has no dependencies to FrontEnd, or the +LayerHierarchy used to create them. They can be cloned and consumed freely. Other consumers +like WindowInfo listeners (input and accessibility) also updated from these snapshots. + +Change flags are used to efficiently traverse this hierarchy where possible. This allows us +to support short circuiting parts of the hierarchy, partial hierarchy updates and fast paths +for buffer updates. + + +While they can be cloned, the current implementation moves the snapshot from FrontEnd to +CompositionEngine to avoid needless work in the hotpath. For snapshot consumers not critical +to composition, the goal is to clone the snapshots and consume them on a background thread. -- GitLab From d5876bad04b61e2dcf97abf1e66a3df18163a8e7 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 15 May 2023 17:58:34 -0700 Subject: [PATCH 1254/1310] Create new input targets for hover events Hover events don't need to reuse the existing input targets in order to lump events with some pointers into others. It's possible that we need to send the same pointer as both DISPATCH_AS_HOVER_EXIT and as DISPATCH_AS_HOVER_ENTER from the dispatcher for the same input channel. This is the case when we are hovering over two windows with the same input token. Since the windows may have different transforms, we should re-start the hovering gesture. Bug: 263319225 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests Change-Id: I2bd90c9893183c0526128b124b983654e0e6a37c --- .../dispatcher/InputDispatcher.cpp | 14 ++++++++--- .../tests/InputDispatcher_test.cpp | 23 +++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 9125fe49e0..bdd45dc9b4 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2561,9 +2561,17 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( std::vector hoveringWindows = getHoveringWindowsLocked(oldState, tempTouchState, entry); for (const TouchedWindow& touchedWindow : hoveringWindows) { - addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, - touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget, - targets); + std::optional target = + createInputTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, + touchedWindow.firstDownTimeInTarget); + if (!target) { + continue; + } + // Hardcode to single hovering pointer for now. + std::bitset pointerIds; + pointerIds.set(entry.pointerProperties[0].id); + target->addPointers(pointerIds, touchedWindow.windowHandle->getInfo()->transform); + targets.push_back(*target); } } diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 3f2658a71d..017f10baf8 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -6592,6 +6592,29 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, TouchDoesNotSlipEvenIfSlippery) consumeMotionEvent(mWindow1, ACTION_MOVE, {{150, 150}}); } +/** + * When hover starts in one window and continues into the other, there should be a HOVER_EXIT and + * a HOVER_ENTER generated, even if the windows have the same token. This is because the new window + * that the pointer is hovering over may have a different transform. + */ +TEST_F(InputDispatcherMultiWindowSameTokenTests, HoverIntoClone) { + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow1, mWindow2}}}); + + // Start hover in window 1 + mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_ENTER, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {{50, 50}})); + consumeMotionEvent(mWindow1, ACTION_HOVER_ENTER, + {getPointInWindow(mWindow1->getInfo(), PointF{50, 50})}); + + // Move hover to window 2. + mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {{150, 150}})); + + consumeMotionEvent(mWindow1, ACTION_HOVER_EXIT, {{50, 50}}); + consumeMotionEvent(mWindow1, ACTION_HOVER_ENTER, + {getPointInWindow(mWindow2->getInfo(), PointF{150, 150})}); +} + class InputDispatcherSingleWindowAnr : public InputDispatcherTest { virtual void SetUp() override { InputDispatcherTest::SetUp(); -- GitLab From 98b068599fb73d130cdd877cd3cef0325a0b421e Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Wed, 17 May 2023 16:56:51 +0000 Subject: [PATCH 1255/1310] ultrahdr: fix input buffer access in tonemap Test: jpegr_test.cpp Bug: b/283149277 Change-Id: I5363994fdf5f7556e4e22c1fee16617cf8420669 --- libs/ultrahdr/jpegr.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp index ef927e3c56..cb8197c387 100644 --- a/libs/ultrahdr/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -1041,21 +1041,18 @@ status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) { return ERROR_JPEGR_INVALID_NULL_PTR; } - size_t src_luma_stride = src->luma_stride; - size_t src_chroma_stride = src->chroma_stride; uint16_t* src_luma_data = reinterpret_cast(src->data); - uint16_t* src_chroma_data = reinterpret_cast(src->chroma_data); + size_t src_luma_stride = src->luma_stride == 0 ? src->width : src->luma_stride; - if (src_chroma_data == nullptr) { - src_chroma_data = &reinterpret_cast(src->data)[src_luma_stride * src->height]; + uint16_t* src_chroma_data; + size_t src_chroma_stride; + if (src->chroma_data == nullptr) { + src_chroma_stride = src_luma_stride; + src_chroma_data = &reinterpret_cast(src->data)[src_luma_stride * src->height]; + } else { + src_chroma_stride = src->chroma_stride; + src_chroma_data = reinterpret_cast(src->chroma_data); } - if (src_luma_stride == 0) { - src_luma_stride = src->width; - } - if (src_chroma_stride == 0) { - src_chroma_stride = src_luma_stride; - } - dest->width = src->width; dest->height = src->height; -- GitLab From ac1cfec4ae0643d629c19ef8feaee5ee5a9ae24c Mon Sep 17 00:00:00 2001 From: Ram Mohan Date: Thu, 18 May 2023 14:41:15 +0530 Subject: [PATCH 1256/1310] ultrahdr: Add more error checks for input arguments of encode API Bug: 282643658 Test: ./libultrahdr_test Change-Id: I474124f4a6a580d514669bc016b6e347da1103ca --- libs/ultrahdr/include/ultrahdr/jpegr.h | 35 ++++- libs/ultrahdr/include/ultrahdr/ultrahdr.h | 2 + libs/ultrahdr/jpegr.cpp | 157 +++++++++++++++++----- 3 files changed, 158 insertions(+), 36 deletions(-) diff --git a/libs/ultrahdr/include/ultrahdr/jpegr.h b/libs/ultrahdr/include/ultrahdr/jpegr.h index 88038f11a4..00b66aeaf6 100644 --- a/libs/ultrahdr/include/ultrahdr/jpegr.h +++ b/libs/ultrahdr/include/ultrahdr/jpegr.h @@ -373,14 +373,41 @@ private: jr_uncompressed_ptr dest); /* - * This method will check the validity of the input images. + * This method will check the validity of the input arguments. * * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format - * @return NO_ERROR if the input images are valid, error code is not valid. + * @param hdr_tf transfer function of the HDR image + * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} + * represents the maximum available size of the desitination buffer, and it must be + * set before calling this method. If the encoded JPEGR size exceeds + * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. + * @return NO_ERROR if the input args are valid, error code is not valid. + */ + status_t areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image, + jr_uncompressed_ptr uncompressed_yuv_420_image, + ultrahdr_transfer_function hdr_tf, + jr_compressed_ptr dest); + + /* + * This method will check the validity of the input arguments. + * + * @param uncompressed_p010_image uncompressed HDR image in P010 color format + * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format + * @param hdr_tf transfer function of the HDR image + * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} + * represents the maximum available size of the desitination buffer, and it must be + * set before calling this method. If the encoded JPEGR size exceeds + * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. + * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is + * the highest quality + * @return NO_ERROR if the input args are valid, error code is not valid. */ - status_t areInputImagesValid(jr_uncompressed_ptr uncompressed_p010_image, - jr_uncompressed_ptr uncompressed_yuv_420_image); + status_t areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image, + jr_uncompressed_ptr uncompressed_yuv_420_image, + ultrahdr_transfer_function hdr_tf, + jr_compressed_ptr dest, + int quality); }; } // namespace android::ultrahdr diff --git a/libs/ultrahdr/include/ultrahdr/ultrahdr.h b/libs/ultrahdr/include/ultrahdr/ultrahdr.h index f9709369ee..e87a025867 100644 --- a/libs/ultrahdr/include/ultrahdr/ultrahdr.h +++ b/libs/ultrahdr/include/ultrahdr/ultrahdr.h @@ -24,6 +24,7 @@ typedef enum { ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT2100, + ULTRAHDR_COLORGAMUT_MAX = ULTRAHDR_COLORGAMUT_BT2100, } ultrahdr_color_gamut; // Transfer functions for image data @@ -33,6 +34,7 @@ typedef enum { ULTRAHDR_TF_HLG = 1, ULTRAHDR_TF_PQ = 2, ULTRAHDR_TF_SRGB = 3, + ULTRAHDR_TF_MAX = ULTRAHDR_TF_SRGB, } ultrahdr_transfer_function; // Target output formats for decoder diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp index cb8197c387..da257266ee 100644 --- a/libs/ultrahdr/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -89,23 +89,69 @@ int GetCPUCoreCount() { return cpuCoreCount; } -status_t JpegR::areInputImagesValid(jr_uncompressed_ptr uncompressed_p010_image, - jr_uncompressed_ptr uncompressed_yuv_420_image) { - if (uncompressed_p010_image == nullptr) { +status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image, + jr_uncompressed_ptr uncompressed_yuv_420_image, + ultrahdr_transfer_function hdr_tf, + jr_compressed_ptr dest) { + if (uncompressed_p010_image == nullptr || uncompressed_p010_image->data == nullptr) { + ALOGE("received nullptr for uncompressed p010 image"); return ERROR_JPEGR_INVALID_NULL_PTR; } + if (uncompressed_p010_image->width % 2 != 0 + || uncompressed_p010_image->height % 2 != 0) { + ALOGE("Image dimensions cannot be odd, image dimensions %dx%d", + uncompressed_p010_image->width, uncompressed_p010_image->height); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + if (uncompressed_p010_image->width == 0 + || uncompressed_p010_image->height == 0) { + ALOGE("Image dimensions cannot be zero, image dimensions %dx%d", + uncompressed_p010_image->width, uncompressed_p010_image->height); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + if (uncompressed_p010_image->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED + || uncompressed_p010_image->colorGamut > ULTRAHDR_COLORGAMUT_MAX) { + ALOGE("Unrecognized p010 color gamut %d", uncompressed_p010_image->colorGamut); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + if (uncompressed_p010_image->luma_stride != 0 && uncompressed_p010_image->luma_stride < uncompressed_p010_image->width) { - ALOGE("Image stride can not be smaller than width, stride=%d, width=%d", + ALOGE("Luma stride can not be smaller than width, stride=%d, width=%d", uncompressed_p010_image->luma_stride, uncompressed_p010_image->width); return ERROR_JPEGR_INVALID_INPUT_TYPE; } + if (uncompressed_p010_image->chroma_data != nullptr + && uncompressed_p010_image->chroma_stride < uncompressed_p010_image->width) { + ALOGE("Chroma stride can not be smaller than width, stride=%d, width=%d", + uncompressed_p010_image->chroma_stride, + uncompressed_p010_image->width); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + if (dest == nullptr || dest->data == nullptr) { + ALOGE("received nullptr for destination"); + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX) { + ALOGE("Invalid hdr transfer function %d", hdr_tf); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + if (uncompressed_yuv_420_image == nullptr) { return NO_ERROR; } + if (uncompressed_yuv_420_image->data == nullptr) { + ALOGE("received nullptr for uncompressed 420 image"); + return ERROR_JPEGR_INVALID_NULL_PTR; + } + if (uncompressed_yuv_420_image->luma_stride != 0) { ALOGE("Stride is not supported for YUV420 image"); return ERROR_JPEGR_UNSUPPORTED_FEATURE; @@ -127,6 +173,30 @@ status_t JpegR::areInputImagesValid(jr_uncompressed_ptr uncompressed_p010_image, return ERROR_JPEGR_RESOLUTION_MISMATCH; } + if (uncompressed_yuv_420_image->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED + || uncompressed_yuv_420_image->colorGamut > ULTRAHDR_COLORGAMUT_MAX) { + ALOGE("Unrecognized 420 color gamut %d", uncompressed_yuv_420_image->colorGamut); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + return NO_ERROR; +} + +status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image, + jr_uncompressed_ptr uncompressed_yuv_420_image, + ultrahdr_transfer_function hdr_tf, + jr_compressed_ptr dest, + int quality) { + if (status_t ret = areInputArgumentsValid( + uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf, dest) != NO_ERROR) { + return ret; + } + + if (quality < 0 || quality > 100) { + ALOGE("quality factor is out side range [0-100], quality factor : %d", quality); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + return NO_ERROR; } @@ -136,17 +206,15 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_compressed_ptr dest, int quality, jr_exif_ptr exif) { - if (uncompressed_p010_image == nullptr || dest == nullptr) { - return ERROR_JPEGR_INVALID_NULL_PTR; - } - - if (quality < 0 || quality > 100) { - return ERROR_JPEGR_INVALID_INPUT_TYPE; + if (status_t ret = areInputArgumentsValid( + uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr, + hdr_tf, dest, quality) != NO_ERROR) { + return ret; } - if (status_t ret = areInputImagesValid( - uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr) != NO_ERROR) { - return ret; + if (exif != nullptr && exif->data == nullptr) { + ALOGE("received nullptr for exif metadata"); + return ERROR_JPEGR_INVALID_NULL_PTR; } ultrahdr_metadata_struct metadata; @@ -201,18 +269,19 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_compressed_ptr dest, int quality, jr_exif_ptr exif) { - if (uncompressed_p010_image == nullptr - || uncompressed_yuv_420_image == nullptr - || dest == nullptr) { + if (uncompressed_yuv_420_image == nullptr) { + ALOGE("received nullptr for uncompressed 420 image"); return ERROR_JPEGR_INVALID_NULL_PTR; } - if (quality < 0 || quality > 100) { - return ERROR_JPEGR_INVALID_INPUT_TYPE; + if (exif != nullptr && exif->data == nullptr) { + ALOGE("received nullptr for exif metadata"); + return ERROR_JPEGR_INVALID_NULL_PTR; } - if (status_t ret = areInputImagesValid( - uncompressed_p010_image, uncompressed_yuv_420_image) != NO_ERROR) { + if (status_t ret = areInputArgumentsValid( + uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf, + dest, quality) != NO_ERROR) { return ret; } @@ -256,15 +325,18 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_compressed_ptr compressed_jpeg_image, ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) { - if (uncompressed_p010_image == nullptr - || uncompressed_yuv_420_image == nullptr - || compressed_jpeg_image == nullptr - || dest == nullptr) { + if (uncompressed_yuv_420_image == nullptr) { + ALOGE("received nullptr for uncompressed 420 image"); return ERROR_JPEGR_INVALID_NULL_PTR; } - if (status_t ret = areInputImagesValid( - uncompressed_p010_image, uncompressed_yuv_420_image) != NO_ERROR) { + if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) { + ALOGE("received nullptr for compressed jpeg image"); + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + if (status_t ret = areInputArgumentsValid( + uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf, dest) != NO_ERROR) { return ret; } @@ -293,14 +365,14 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_compressed_ptr compressed_jpeg_image, ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) { - if (uncompressed_p010_image == nullptr - || compressed_jpeg_image == nullptr - || dest == nullptr) { + if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) { + ALOGE("received nullptr for compressed jpeg image"); return ERROR_JPEGR_INVALID_NULL_PTR; } - if (status_t ret = areInputImagesValid( - uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr) != NO_ERROR) { + if (status_t ret = areInputArgumentsValid( + uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr, + hdr_tf, dest) != NO_ERROR) { return ret; } @@ -344,13 +416,34 @@ status_t JpegR::encodeJPEGR(jr_compressed_ptr compressed_jpeg_image, jr_compressed_ptr compressed_gainmap, ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest) { + if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) { + ALOGE("received nullptr for compressed jpeg image"); + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + if (compressed_gainmap == nullptr || compressed_gainmap->data == nullptr) { + ALOGE("received nullptr for compressed gain map"); + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + if (dest == nullptr || dest->data == nullptr) { + ALOGE("received nullptr for destination"); + return ERROR_JPEGR_INVALID_NULL_PTR; + } + JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr, metadata, dest)); return NO_ERROR; } status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info) { - if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) { + if (compressed_jpegr_image == nullptr || compressed_jpegr_image->data == nullptr) { + ALOGE("received nullptr for compressed jpegr image"); + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + if (jpegr_info == nullptr) { + ALOGE("received nullptr for compressed jpegr info struct"); return ERROR_JPEGR_INVALID_NULL_PTR; } -- GitLab From 033e3ec97ab78e505f3a1a37b8a7ed2fa27fa5b3 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Wed, 26 Apr 2023 14:43:16 +0000 Subject: [PATCH 1257/1310] InputMapper refactor: KeyboardInputMapper Add a factory method for KeyboardInputMapper to be configured on initilisation Test: m checkinput && atest libinput_tests inputflinger_tests Bug: 256009910 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:67ca684ad1e5d6286bdbae568ee0fc97be40d3c1) Merged-In: Iceb8676542de85309c208af93ee7cc385e46a067 Change-Id: Iceb8676542de85309c208af93ee7cc385e46a067 --- services/inputflinger/reader/InputDevice.cpp | 4 +- .../reader/mapper/KeyboardInputMapper.h | 10 ++- .../inputflinger/tests/InputReader_test.cpp | 83 ++++++++++--------- 3 files changed, 53 insertions(+), 44 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index c8c5115d64..0a64a1c4a8 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -482,8 +482,8 @@ std::vector> InputDevice::createMappers( } if (keyboardSource != 0) { - mappers.push_back(std::make_unique(contextPtr, readerConfig, - keyboardSource, keyboardType)); + mappers.push_back(createInputMapper(contextPtr, readerConfig, + keyboardSource, keyboardType)); } // Cursor-like devices. diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index bd27383296..cd3d3c49e9 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -23,9 +23,10 @@ namespace android { class KeyboardInputMapper : public InputMapper { public: - KeyboardInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig, uint32_t source, - int32_t keyboardType); + template + friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, + Args... args); ~KeyboardInputMapper() override = default; uint32_t getSources() const override; @@ -82,6 +83,9 @@ private: bool doNotWakeByDefault{}; } mParameters{}; + KeyboardInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig, uint32_t source, + int32_t keyboardType); void configureParameters(); void dumpParameters(std::string& dump) const; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 9fbe7626e2..bfb371f02a 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -2892,7 +2892,7 @@ void KeyboardInputMapperTest::testDPadKeyRotation(KeyboardInputMapper& mapper, TEST_F(KeyboardInputMapperTest, GetSources) { KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, mapper.getSources()); @@ -2908,7 +2908,7 @@ TEST_F(KeyboardInputMapperTest, Process_SimpleKeyPress) { mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, POLICY_FLAG_WAKE); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); // Initial metastate is AMETA_NONE. ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); @@ -3009,7 +3009,7 @@ TEST_F(KeyboardInputMapperTest, Process_KeyRemapping) { mFakeEventHub->addKeyRemapping(EVENTHUB_ID, AKEYCODE_A, AKEYCODE_B); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); // Key down by scan code. @@ -3031,7 +3031,7 @@ TEST_F(KeyboardInputMapperTest, Process_SendsReadTime) { mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); NotifyKeyArgs args; @@ -3054,7 +3054,7 @@ TEST_F(KeyboardInputMapperTest, Process_ShouldUpdateMetaState) { mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); // Initial metastate is AMETA_NONE. @@ -3095,7 +3095,7 @@ TEST_F(KeyboardInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateD mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); prepareDisplay(ui::ROTATION_90); @@ -3117,7 +3117,7 @@ TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) { addConfigurationProperty("keyboard.orientationAware", "1"); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); prepareDisplay(ui::ROTATION_0); @@ -3189,7 +3189,7 @@ TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_NotOrientationAware mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); NotifyKeyArgs args; @@ -3215,7 +3215,7 @@ TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_OrientationAware) { addConfigurationProperty("keyboard.orientationAware", "1"); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); NotifyKeyArgs args; @@ -3243,7 +3243,7 @@ TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_OrientationAware) { TEST_F(KeyboardInputMapperTest, GetKeyCodeState) { KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); mFakeEventHub->setKeyCodeState(EVENTHUB_ID, AKEYCODE_A, 1); @@ -3255,7 +3255,7 @@ TEST_F(KeyboardInputMapperTest, GetKeyCodeState) { TEST_F(KeyboardInputMapperTest, GetKeyCodeForKeyLocation) { KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); mFakeEventHub->addKeyCodeMapping(EVENTHUB_ID, AKEYCODE_Y, AKEYCODE_Z); @@ -3268,7 +3268,7 @@ TEST_F(KeyboardInputMapperTest, GetKeyCodeForKeyLocation) { TEST_F(KeyboardInputMapperTest, GetScanCodeState) { KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); mFakeEventHub->setScanCodeState(EVENTHUB_ID, KEY_A, 1); @@ -3280,7 +3280,7 @@ TEST_F(KeyboardInputMapperTest, GetScanCodeState) { TEST_F(KeyboardInputMapperTest, MarkSupportedKeyCodes) { KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0); @@ -3300,7 +3300,7 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleMetaStateAndLeds) mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); // Initial metastate is AMETA_NONE. ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); @@ -3366,7 +3366,7 @@ TEST_F(KeyboardInputMapperTest, NoMetaStateWhenMetaKeysNotPresent) { mFakeEventHub->addKey(EVENTHUB_ID, BTN_Y, 0, AKEYCODE_BUTTON_Y, 0); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC); // Meta state should be AMETA_NONE after reset @@ -3416,14 +3416,16 @@ TEST_F(KeyboardInputMapperTest, Configure_AssignsDisplayPort) { mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); + device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID); KeyboardInputMapper& mapper2 = - device2->addMapper(SECOND_EVENTHUB_ID, - mFakePolicy->getReaderConfiguration(), - AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + device2->constructAndAddMapper(SECOND_EVENTHUB_ID, + mFakePolicy + ->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), /*changes=*/{}); @@ -3485,7 +3487,7 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleAfterReattach) { mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); // Initial metastate is AMETA_NONE. ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); @@ -3531,11 +3533,13 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleAfterReattach) { mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0); mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); + device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID); KeyboardInputMapper& mapper2 = - device2->addMapper(SECOND_EVENTHUB_ID, - mFakePolicy->getReaderConfiguration(), - AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + device2->constructAndAddMapper(SECOND_EVENTHUB_ID, + mFakePolicy + ->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), /*changes=*/{}); @@ -3554,10 +3558,10 @@ TEST_F(KeyboardInputMapperTest, Process_toggleCapsLockState) { mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); // Suppose we have two mappers. (DPAD + KEYBOARD) - addMapperAndConfigure(AINPUT_SOURCE_DPAD, + constructAndAddMapper(AINPUT_SOURCE_DPAD, AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); // Initial metastate is AMETA_NONE. ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); @@ -3576,7 +3580,7 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleInMultiDevices) { mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); KeyboardInputMapper& mapper1 = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); // keyboard 2. @@ -3594,11 +3598,13 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleInMultiDevices) { mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0); mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); + device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID); KeyboardInputMapper& mapper2 = - device2->addMapper(SECOND_EVENTHUB_ID, - mFakePolicy->getReaderConfiguration(), - AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + device2->constructAndAddMapper(SECOND_EVENTHUB_ID, + mFakePolicy + ->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), /*changes=*/{}); @@ -3654,7 +3660,7 @@ TEST_F(KeyboardInputMapperTest, Process_DisabledDevice) { mFakeEventHub->addKey(EVENTHUB_ID, 0, USAGE_A, AKEYCODE_A, POLICY_FLAG_WAKE); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); // Key down by scan code. process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1); @@ -3680,9 +3686,8 @@ TEST_F(KeyboardInputMapperTest, Process_DisabledDevice) { } TEST_F(KeyboardInputMapperTest, Configure_AssignKeyboardLayoutInfo) { - mDevice->addMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), - AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, + AINPUT_KEYBOARD_TYPE_ALPHABETIC); std::list unused = mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), /*changes=*/{}); @@ -3713,7 +3718,7 @@ TEST_F(KeyboardInputMapperTest, LayoutInfoCorrectlyMapped) { RawLayoutInfo{.languageTag = "en", .layoutType = "extended"}); // Configuration - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); InputReaderConfiguration config; std::list unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{}); @@ -3739,7 +3744,7 @@ TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior) { POLICY_FLAG_WAKE); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1); @@ -3777,7 +3782,7 @@ TEST_F(KeyboardInputMapperTest_ExternalDevice, DoNotWakeByDefaultBehavior) { addConfigurationProperty("keyboard.doNotWakeByDefault", "1"); KeyboardInputMapper& mapper = - addMapperAndConfigure(AINPUT_SOURCE_KEYBOARD, + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1); -- GitLab From 35ab3a874b584fcfb38ff960e002d56b74d4c0f4 Mon Sep 17 00:00:00 2001 From: Ram Mohan Date: Thu, 18 May 2023 18:38:16 +0530 Subject: [PATCH 1258/1310] ultrahdr: Add unit tests for encoder APIs Test encoder APIs for invalid arguments Bug: 282643658 Test: ./libultrahdr_test Change-Id: Ic6e52708ab30f112d8060a5c682647ab0c968603 --- libs/ultrahdr/tests/jpegr_test.cpp | 591 +++++++++++++++++++++++++++++ 1 file changed, 591 insertions(+) diff --git a/libs/ultrahdr/tests/jpegr_test.cpp b/libs/ultrahdr/tests/jpegr_test.cpp index 58cd8f4711..ac358872b4 100644 --- a/libs/ultrahdr/tests/jpegr_test.cpp +++ b/libs/ultrahdr/tests/jpegr_test.cpp @@ -178,6 +178,597 @@ TEST_F(JpegRTest, build) { jpegRCodec.decodeJPEGR(nullptr, nullptr); } +/* Test Encode API-0 invalid arguments */ +TEST_F(JpegRTest, encodeAPI0ForInvalidArgs) { + int ret; + + // we are not really compressing anything so lets keep allocs to a minimum + jpegr_compressed_struct jpegR; + jpegR.maxLength = 16 * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + + JpegR jpegRCodec; + + // we are not really compressing anything so lets keep allocs to a minimum + mRawP010ImageWithStride.data = malloc(16); + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + + // test quality factor + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + -1, nullptr)) << "fail, API allows bad jpeg quality factor"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + 101, nullptr)) << "fail, API allows bad jpeg quality factor"; + + // test hdr transfer function + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, + static_cast(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, + static_cast(-10), + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function"; + + // test dest + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr dest"; + + // test p010 input + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr p010 image"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = static_cast( + ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height"; + + mRawP010ImageWithStride.width = 0; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = 0; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad luma stride"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data; + mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad chroma stride"; + + free(jpegR.data); +} + +/* Test Encode API-1 invalid arguments */ +TEST_F(JpegRTest, encodeAPI1ForInvalidArgs) { + int ret; + + // we are not really compressing anything so lets keep allocs to a minimum + jpegr_compressed_struct jpegR; + jpegR.maxLength = 16 * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + + JpegR jpegRCodec; + + // we are not really compressing anything so lets keep allocs to a minimum + mRawP010ImageWithStride.data = malloc(16); + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + + // we are not really compressing anything so lets keep allocs to a minimum + mRawYuv420Image.data = malloc(16); + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; + + // test quality factor + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, -1, nullptr)) << "fail, API allows bad jpeg quality factor"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, 101, nullptr)) << "fail, API allows bad jpeg quality factor"; + + // test hdr transfer function + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, + ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, &jpegR, DEFAULT_JPEG_QUALITY, + nullptr)) << "fail, API allows bad hdr transfer function"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, + static_cast(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, + static_cast(-10), + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function"; + + // test dest + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + nullptr, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr dest"; + + // test p010 input + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + nullptr, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr p010 image"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = static_cast( + ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height"; + + mRawP010ImageWithStride.width = 0; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = 0; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad luma stride"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data; + mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad chroma stride"; + + // test 420 input + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.chroma_data = nullptr; + mRawP010ImageWithStride.chroma_stride = 0; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, + DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr for 420 image"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 image width"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH - 2; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 image height"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.luma_stride = TEST_IMAGE_STRIDE; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad luma stride for 420"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.luma_stride = 0; + mRawYuv420Image.chroma_data = mRawYuv420Image.data; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows chroma pointer for 420"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.luma_stride = 0; + mRawYuv420Image.chroma_data = nullptr; + mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 color gamut"; + + mRawYuv420Image.colorGamut = static_cast( + ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 color gamut"; + + free(jpegR.data); +} + +/* Test Encode API-2 invalid arguments */ +TEST_F(JpegRTest, encodeAPI2ForInvalidArgs) { + int ret; + + // we are not really compressing anything so lets keep allocs to a minimum + jpegr_compressed_struct jpegR; + jpegR.maxLength = 16 * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + + JpegR jpegRCodec; + + // we are not really compressing anything so lets keep allocs to a minimum + mRawP010ImageWithStride.data = malloc(16); + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + + // we are not really compressing anything so lets keep allocs to a minimum + mRawYuv420Image.data = malloc(16); + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; + + // test hdr transfer function + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, + &jpegR)) << "fail, API allows bad hdr transfer function"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + static_cast(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), + &jpegR)) << "fail, API allows bad hdr transfer function"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + static_cast(-10), + &jpegR)) << "fail, API allows bad hdr transfer function"; + + // test dest + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr)) << "fail, API allows nullptr dest"; + + // test p010 input + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + nullptr, &mRawYuv420Image, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows nullptr p010 image"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad p010 color gamut"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = static_cast( + ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad p010 color gamut"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image width"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image height"; + + mRawP010ImageWithStride.width = 0; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image width"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = 0; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image height"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad luma stride"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data; + mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad chroma stride"; + + // test 420 input + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.chroma_data = nullptr; + mRawP010ImageWithStride.chroma_stride = 0; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, nullptr, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows nullptr for 420 image"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad 420 image width"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH - 2; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad 420 image height"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.luma_stride = TEST_IMAGE_STRIDE; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad luma stride for 420"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.luma_stride = 0; + mRawYuv420Image.chroma_data = mRawYuv420Image.data; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows chroma pointer for 420"; + + mRawYuv420Image.width = TEST_IMAGE_WIDTH; + mRawYuv420Image.height = TEST_IMAGE_HEIGHT; + mRawYuv420Image.luma_stride = 0; + mRawYuv420Image.chroma_data = nullptr; + mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad 420 color gamut"; + + mRawYuv420Image.colorGamut = static_cast( + ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad 420 color gamut"; + + // bad compressed image + mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &mRawYuv420Image, nullptr, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad 420 color gamut"; + + free(jpegR.data); +} + +/* Test Encode API-3 invalid arguments */ +TEST_F(JpegRTest, encodeAPI3ForInvalidArgs) { + int ret; + + // we are not really compressing anything so lets keep allocs to a minimum + jpegr_compressed_struct jpegR; + jpegR.maxLength = 16 * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + + JpegR jpegRCodec; + + // we are not really compressing anything so lets keep allocs to a minimum + mRawP010ImageWithStride.data = malloc(16); + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + + // test hdr transfer function + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, + &jpegR)) << "fail, API allows bad hdr transfer function"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, + static_cast(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), + &jpegR)) << "fail, API allows bad hdr transfer function"; + + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, static_cast(-10), + &jpegR)) << "fail, API allows bad hdr transfer function"; + + // test dest + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + nullptr)) << "fail, API allows nullptr dest"; + + // test p010 input + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + nullptr, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows nullptr p010 image"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad p010 color gamut"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = static_cast( + ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad p010 color gamut"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad image width"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad image height"; + + mRawP010ImageWithStride.width = 0; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad image width"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = 0; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad image height"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad luma stride"; + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE; + mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data; + mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad chroma stride"; + + // bad compressed image + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegR)) << "fail, API allows bad 420 color gamut"; + + free(jpegR.data); +} + +/* Test Encode API-4 invalid arguments */ +TEST_F(JpegRTest, encodeAPI4ForInvalidArgs) { + int ret; + + // we are not really compressing anything so lets keep allocs to a minimum + jpegr_compressed_struct jpegR; + jpegR.maxLength = 16 * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + + JpegR jpegRCodec; + + // test dest + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &jpegR, &jpegR, nullptr, nullptr)) << "fail, API allows nullptr dest"; + + // test primary image + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + nullptr, &jpegR, nullptr, &jpegR)) << "fail, API allows nullptr primary image"; + + // test gain map + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &jpegR, nullptr, nullptr, &jpegR)) << "fail, API allows nullptr gainmap image"; + + free(jpegR.data); +} + TEST_F(JpegRTest, writeXmpThenRead) { ultrahdr_metadata_struct metadata_expected; metadata_expected.version = "1.0"; -- GitLab From 2c5692bd42154e938474b6f9638d3edb1c6b4c91 Mon Sep 17 00:00:00 2001 From: Brian Duddie Date: Thu, 18 May 2023 15:04:32 -0700 Subject: [PATCH 1259/1310] Fix NDK documentation for ASensorEvent Correctly associate comments with the applicable fields by moving the comment to before the field declaration. Fixes: 283307966 Test: presubmit Change-Id: Ibb87216f5d0deb458248df8989fdfeed79edf0e4 --- include/android/sensor.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/include/android/sensor.h b/include/android/sensor.h index 085fc270c1..16c5dde60f 100644 --- a/include/android/sensor.h +++ b/include/android/sensor.h @@ -611,10 +611,14 @@ typedef struct AHeadingEvent { * sensors_event_t */ typedef struct ASensorEvent { - int32_t version; /* sizeof(struct ASensorEvent) */ - int32_t sensor; /** The sensor that generates this event */ - int32_t type; /** Sensor type for the event, such as {@link ASENSOR_TYPE_ACCELEROMETER} */ - int32_t reserved0; /** do not use */ + /* sizeof(struct ASensorEvent) */ + int32_t version; + /** The sensor that generates this event */ + int32_t sensor; + /** Sensor type for the event, such as {@link ASENSOR_TYPE_ACCELEROMETER} */ + int32_t type; + /** do not use */ + int32_t reserved0; /** * The time in nanoseconds at which the event happened, and its behavior * is identical to -- GitLab From 13310b86295ae139c504242f1a40c7c9754e691e Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Wed, 17 May 2023 14:40:18 -0500 Subject: [PATCH 1260/1310] Update BackgroundExecutor to use LocklessQueue This change allows BackgroundExecutor to receive callbacks from multiple producers. Bug: 283133164 Test: BackgroundExecutorTest Change-Id: I63f3266184a2ae90b15d7d1b7d9502847e268763 --- .../surfaceflinger/BackgroundExecutor.cpp | 36 ++++-------- services/surfaceflinger/BackgroundExecutor.h | 20 ++----- .../surfaceflinger/tests/unittests/Android.bp | 1 + .../unittests/BackgroundExecutorTest.cpp | 57 +++++++++++++++++++ 4 files changed, 73 insertions(+), 41 deletions(-) create mode 100644 services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp diff --git a/services/surfaceflinger/BackgroundExecutor.cpp b/services/surfaceflinger/BackgroundExecutor.cpp index a15de2b183..6ddf790d47 100644 --- a/services/surfaceflinger/BackgroundExecutor.cpp +++ b/services/surfaceflinger/BackgroundExecutor.cpp @@ -28,29 +28,19 @@ namespace android { ANDROID_SINGLETON_STATIC_INSTANCE(BackgroundExecutor); BackgroundExecutor::BackgroundExecutor() : Singleton() { + // mSemaphore must be initialized before any calls to + // BackgroundExecutor::sendCallbacks. For this reason, we initialize it + // within the constructor instead of within mThread. + LOG_ALWAYS_FATAL_IF(sem_init(&mSemaphore, 0, 0), "sem_init failed"); mThread = std::thread([&]() { - LOG_ALWAYS_FATAL_IF(sem_init(&mSemaphore, 0, 0), "sem_init failed"); while (!mDone) { LOG_ALWAYS_FATAL_IF(sem_wait(&mSemaphore), "sem_wait failed (%d)", errno); - - ftl::SmallVector workItems; - - Work* work = mWorks.pop(); - while (work) { - workItems.push_back(work); - work = mWorks.pop(); + auto callbacks = mCallbacksQueue.pop(); + if (!callbacks) { + continue; } - - // Sequence numbers are guaranteed to be in intended order, as we assume a single - // producer and single consumer. - std::stable_sort(workItems.begin(), workItems.end(), [](Work* left, Work* right) { - return left->sequence < right->sequence; - }); - for (Work* work : workItems) { - for (auto& task : work->tasks) { - task(); - } - delete work; + for (auto& callback : *callbacks) { + callback(); } } }); @@ -66,12 +56,8 @@ BackgroundExecutor::~BackgroundExecutor() { } void BackgroundExecutor::sendCallbacks(Callbacks&& tasks) { - Work* work = new Work(); - work->sequence = mSequence; - work->tasks = std::move(tasks); - mWorks.push(work); - mSequence++; + mCallbacksQueue.push(std::move(tasks)); LOG_ALWAYS_FATAL_IF(sem_post(&mSemaphore), "sem_post failed"); } -} // namespace android \ No newline at end of file +} // namespace android diff --git a/services/surfaceflinger/BackgroundExecutor.h b/services/surfaceflinger/BackgroundExecutor.h index eeaf3bdd2e..0fae5a5c93 100644 --- a/services/surfaceflinger/BackgroundExecutor.h +++ b/services/surfaceflinger/BackgroundExecutor.h @@ -16,15 +16,13 @@ #pragma once -#include -#include #include #include #include -#include -#include #include +#include "LocklessQueue.h" + namespace android { // Executes tasks off the main thread. @@ -34,24 +32,14 @@ public: ~BackgroundExecutor(); using Callbacks = ftl::SmallVector, 10>; // Queues callbacks onto a work queue to be executed by a background thread. - // Note that this is not thread-safe - a single producer is assumed. + // This is safe to call from multiple threads. void sendCallbacks(Callbacks&& tasks); private: sem_t mSemaphore; std::atomic_bool mDone = false; - // Sequence number for work items. - // Work items are batched by sequence number. Work items for earlier sequence numbers are - // executed first. Work items with the same sequence number are executed in the same order they - // were added to the stack (meaning the stack must reverse the order after popping from the - // queue) - int32_t mSequence = 0; - struct Work { - int32_t sequence = 0; - Callbacks tasks; - }; - LocklessStack mWorks; + LocklessQueue mCallbacksQueue; std::thread mThread; }; diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index 70f8a8321e..881b362f1e 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -71,6 +71,7 @@ cc_test { ":libsurfaceflinger_sources", "libsurfaceflinger_unittest_main.cpp", "ActiveDisplayRotationFlagsTest.cpp", + "BackgroundExecutorTest.cpp", "CompositionTest.cpp", "DisplayIdGeneratorTest.cpp", "DisplayTransactionTest.cpp", diff --git a/services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp b/services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp new file mode 100644 index 0000000000..5413bae55c --- /dev/null +++ b/services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp @@ -0,0 +1,57 @@ +#include +#include + +#include "BackgroundExecutor.h" + +namespace android { + +class BackgroundExecutorTest : public testing::Test {}; + +namespace { + +TEST_F(BackgroundExecutorTest, singleProducer) { + std::mutex mutex; + std::condition_variable condition_variable; + bool backgroundTaskComplete = false; + + BackgroundExecutor::getInstance().sendCallbacks( + {[&mutex, &condition_variable, &backgroundTaskComplete]() { + std::lock_guard lock{mutex}; + condition_variable.notify_one(); + backgroundTaskComplete = true; + }}); + + std::unique_lock lock{mutex}; + condition_variable.wait(lock, [&backgroundTaskComplete]() { return backgroundTaskComplete; }); + ASSERT_TRUE(backgroundTaskComplete); +} + +TEST_F(BackgroundExecutorTest, multipleProducers) { + std::mutex mutex; + std::condition_variable condition_variable; + const int backgroundTaskCount = 10; + int backgroundTaskCompleteCount = 0; + + for (int i = 0; i < backgroundTaskCount; i++) { + std::thread([&mutex, &condition_variable, &backgroundTaskCompleteCount]() { + BackgroundExecutor::getInstance().sendCallbacks( + {[&mutex, &condition_variable, &backgroundTaskCompleteCount]() { + std::lock_guard lock{mutex}; + backgroundTaskCompleteCount++; + if (backgroundTaskCompleteCount == backgroundTaskCount) { + condition_variable.notify_one(); + } + }}); + }).detach(); + } + + std::unique_lock lock{mutex}; + condition_variable.wait(lock, [&backgroundTaskCompleteCount]() { + return backgroundTaskCompleteCount == backgroundTaskCount; + }); + ASSERT_EQ(backgroundTaskCount, backgroundTaskCompleteCount); +} + +} // namespace + +} // namespace android -- GitLab From 432f6c00f78bd49a5306dac2e0f12311b41c8986 Mon Sep 17 00:00:00 2001 From: Himanshu Jakhmola Date: Mon, 13 Mar 2023 16:00:47 +0530 Subject: [PATCH 1261/1310] Use cpu_number to index mapping Change applicable when compiling data from vals policywise. Instead of using cpu_number as index to access vals, use cpu_number to index mapping to get index for accessing vals for a cpu. CRs-Fixed: 3413580 Bug: 280947753 Test: CtsStatsdAtomHostTestCases.android.cts.statsdatom.cpu.CpuStatsTests#testCpuCyclesPerUidCluster (cherry picked from https://android-review.googlesource.com/q/commit:bbd443fd6c2abb3e01dd0d4870a432421012ee54) Merged-In: Id17661c50bda4cd4aecee6c7658e97c718dd4ec4 Change-Id: Id17661c50bda4cd4aecee6c7658e97c718dd4ec4 --- libs/cputimeinstate/cputimeinstate.cpp | 32 ++++++++++++++++++-------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/libs/cputimeinstate/cputimeinstate.cpp b/libs/cputimeinstate/cputimeinstate.cpp index 706704ad34..4a7bd36202 100644 --- a/libs/cputimeinstate/cputimeinstate.cpp +++ b/libs/cputimeinstate/cputimeinstate.cpp @@ -55,6 +55,7 @@ static uint32_t gNPolicies = 0; static uint32_t gNCpus = 0; static std::vector> gPolicyFreqs; static std::vector> gPolicyCpus; +static std::vector gCpuIndexMap; static std::set gAllFreqs; static unique_fd gTisTotalMapFd; static unique_fd gTisMapFd; @@ -108,7 +109,7 @@ static bool initGlobals() { free(dirlist[i]); } free(dirlist); - + uint32_t max_cpu_number = 0; for (const auto &policy : policyFileNames) { std::vector freqs; for (const auto &name : {"available", "boost"}) { @@ -127,8 +128,19 @@ static bool initGlobals() { std::string path = StringPrintf("%s/%s/%s", basepath, policy.c_str(), "related_cpus"); auto cpus = readNumbersFromFile(path); if (!cpus) return false; + for (auto cpu : *cpus) { + if(cpu > max_cpu_number) + max_cpu_number = cpu; + } gPolicyCpus.emplace_back(*cpus); } + gCpuIndexMap = std::vector(max_cpu_number+1, -1); + uint32_t cpuorder = 0; + for (const auto &cpuList : gPolicyCpus) { + for (auto cpu : cpuList) { + gCpuIndexMap[cpu] = cpuorder++; + } + } gTisTotalMapFd = unique_fd{bpf_obj_get(BPF_FS_PATH "map_timeInState_total_time_in_state_map")}; @@ -277,7 +289,7 @@ std::optional>> getTotalCpuFreqTimes() { for (uint32_t policyIdx = 0; policyIdx < gNPolicies; ++policyIdx) { if (freqIdx >= gPolicyFreqs[policyIdx].size()) continue; for (const auto &cpu : gPolicyCpus[policyIdx]) { - out[policyIdx][freqIdx] += vals[cpu]; + out[policyIdx][freqIdx] += vals[gCpuIndexMap[cpu]]; } } } @@ -316,7 +328,8 @@ std::optional>> getUidCpuFreqTimes(uint32_t ui auto end = nextOffset < gPolicyFreqs[j].size() ? begin + FREQS_PER_ENTRY : out[j].end(); for (const auto &cpu : gPolicyCpus[j]) { - std::transform(begin, end, std::begin(vals[cpu].ar), begin, std::plus()); + std::transform(begin, end, std::begin(vals[gCpuIndexMap[cpu]].ar), begin, + std::plus()); } } } @@ -382,7 +395,8 @@ getUidsUpdatedCpuFreqTimes(uint64_t *lastUpdate) { auto end = nextOffset < gPolicyFreqs[i].size() ? begin + FREQS_PER_ENTRY : map[key.uid][i].end(); for (const auto &cpu : gPolicyCpus[i]) { - std::transform(begin, end, std::begin(vals[cpu].ar), begin, std::plus()); + std::transform(begin, end, std::begin(vals[gCpuIndexMap[cpu]].ar), begin, + std::plus()); } } prevKey = key; @@ -437,8 +451,8 @@ std::optional getUidConcurrentTimes(uint32_t uid, bool retry) : ret.policy[policy].end(); for (const auto &cpu : gPolicyCpus[policy]) { - std::transform(policyBegin, policyEnd, std::begin(vals[cpu].policy), policyBegin, - std::plus()); + std::transform(policyBegin, policyEnd, std::begin(vals[gCpuIndexMap[cpu]].policy), + policyBegin, std::plus()); } } } @@ -506,8 +520,8 @@ std::optional> getUidsUpdatedCon : ret[key.uid].policy[policy].end(); for (const auto &cpu : gPolicyCpus[policy]) { - std::transform(policyBegin, policyEnd, std::begin(vals[cpu].policy), policyBegin, - std::plus()); + std::transform(policyBegin, policyEnd, std::begin(vals[gCpuIndexMap[cpu]].policy), + policyBegin, std::plus()); } } } while (prevKey = key, !getNextMapKey(gConcurrentMapFd, &prevKey, &key)); @@ -640,7 +654,7 @@ getAggregatedTaskCpuFreqTimes(pid_t tgid, const std::vector &aggregati auto end = nextOffset < gPolicyFreqs[j].size() ? begin + FREQS_PER_ENTRY : map[key.aggregation_key][j].end(); for (const auto &cpu : gPolicyCpus[j]) { - std::transform(begin, end, std::begin(vals[cpu].ar), begin, + std::transform(begin, end, std::begin(vals[gCpuIndexMap[cpu]].ar), begin, std::plus()); } } -- GitLab From 61dfca4bedbdee697ae07f2e660fa0eb4fab17cf Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Fri, 19 May 2023 14:42:24 -0400 Subject: [PATCH 1262/1310] SF: Fix ADPF regression due to wrong present fence Partially revert Ib114ffc1866aa32e6d493e06dcf5a27652d19a39. PowerAdvisor needs the present fence of the previous frame, as the query happens after presenting the current frame so should not be adjusted for targeting 2 VSYNCs ahead. Fixes: 283189729 Test: Perfetto Change-Id: Ifbd6aa961bc92fe1d2f4de1247413fcd49330645 --- services/surfaceflinger/SurfaceFlinger.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 5d96fc45be..0612f5a24f 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2674,12 +2674,15 @@ void SurfaceFlinger::composite(TimePoint frameTime, VsyncId vsyncId) mTimeStats->recordFrameDuration(frameTime.ns(), systemTime()); - // Send a power hint hint after presentation is finished + // Send a power hint after presentation is finished. if (mPowerHintSessionEnabled) { - const nsecs_t pastPresentTime = - getPreviousPresentFence(frameTime, vsyncPeriod)->getSignalTime(); + // Now that the current frame has been presented above, PowerAdvisor needs the present time + // of the previous frame (whose fence is signaled by now) to determine how long the HWC had + // waited on that fence to retire before presenting. + const auto& previousPresentFence = mPreviousPresentFences[0].fenceTime; - mPowerAdvisor->setSfPresentTiming(TimePoint::fromNs(pastPresentTime), TimePoint::now()); + mPowerAdvisor->setSfPresentTiming(TimePoint::fromNs(previousPresentFence->getSignalTime()), + TimePoint::now()); mPowerAdvisor->reportActualWorkDuration(); } -- GitLab From c44a1ffe6e278e167246c2cf0a6e75266630beb0 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Fri, 19 May 2023 23:49:05 +0000 Subject: [PATCH 1263/1310] Fix SurfaceFlinger_test on WCG devices Some tests force sRGB screenshots for consistent test output. Make sure that output colorspace is still respected. Bug: 283305205 Test: SurfaceFlinger_test Change-Id: I42ffe36022fa65568bcd75df28a33389340a7d7f --- services/surfaceflinger/SurfaceFlinger.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 5d96fc45be..6cac25e1c7 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -7046,9 +7046,9 @@ status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, } RenderAreaFuture renderAreaFuture = ftl::defer([=] { - return DisplayRenderArea::create(displayWeak, args.sourceCrop, reqSize, - ui::Dataspace::UNKNOWN, args.useIdentityTransform, - args.hintForSeamlessTransition, args.captureSecureLayers); + return DisplayRenderArea::create(displayWeak, args.sourceCrop, reqSize, args.dataspace, + args.useIdentityTransform, args.hintForSeamlessTransition, + args.captureSecureLayers); }); GetLayerSnapshotsFunction getLayerSnapshots; -- GitLab From b037505a7f789a57decac7828337324866961dcf Mon Sep 17 00:00:00 2001 From: Ram Mohan Date: Sat, 20 May 2023 04:03:48 +0530 Subject: [PATCH 1264/1310] ultrahdr: Add argument check for decoder api Validate input arguments for decodeJPEGR. Add unit tests corresponding to the same Bug: 283503768 Test: ./libultrahdr_test Change-Id: Id02666030dd9661bad27eb0723dd8a1ec282be6b --- libs/ultrahdr/include/ultrahdr/ultrahdr.h | 2 ++ libs/ultrahdr/jpegr.cpp | 26 +++++++++++++-- libs/ultrahdr/tests/jpegr_test.cpp | 39 +++++++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/libs/ultrahdr/include/ultrahdr/ultrahdr.h b/libs/ultrahdr/include/ultrahdr/ultrahdr.h index e87a025867..d6153e9890 100644 --- a/libs/ultrahdr/include/ultrahdr/ultrahdr.h +++ b/libs/ultrahdr/include/ultrahdr/ultrahdr.h @@ -39,10 +39,12 @@ typedef enum { // Target output formats for decoder typedef enum { + ULTRAHDR_OUTPUT_UNSPECIFIED = -1, ULTRAHDR_OUTPUT_SDR, // SDR in RGBA_8888 color format ULTRAHDR_OUTPUT_HDR_LINEAR, // HDR in F16 color format (linear) ULTRAHDR_OUTPUT_HDR_PQ, // HDR in RGBA_1010102 color format (PQ transfer function) ULTRAHDR_OUTPUT_HDR_HLG, // HDR in RGBA_1010102 color format (HLG transfer function) + ULTRAHDR_OUTPUT_MAX = ULTRAHDR_OUTPUT_HDR_HLG, } ultrahdr_output_format; /* diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp index da257266ee..13e43fd6db 100644 --- a/libs/ultrahdr/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -469,12 +469,34 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, ultrahdr_output_format output_format, jr_uncompressed_ptr gain_map, ultrahdr_metadata_ptr metadata) { - if (compressed_jpegr_image == nullptr || dest == nullptr) { + if (compressed_jpegr_image == nullptr || compressed_jpegr_image->data == nullptr) { + ALOGE("received nullptr for compressed jpegr image"); + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + if (dest == nullptr || dest->data == nullptr) { + ALOGE("received nullptr for dest image"); return ERROR_JPEGR_INVALID_NULL_PTR; } if (max_display_boost < 1.0f) { - return ERROR_JPEGR_INVALID_INPUT_TYPE; + ALOGE("received bad value for max_display_boost %f", max_display_boost); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + if (exif != nullptr && exif->data == nullptr) { + ALOGE("received nullptr address for exif data"); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) { + ALOGE("received bad value for output format %d", output_format); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + if (gain_map != nullptr && gain_map->data == nullptr) { + ALOGE("received nullptr address for gain map data"); + return ERROR_JPEGR_INVALID_INPUT_TYPE; } if (output_format == ULTRAHDR_OUTPUT_SDR) { diff --git a/libs/ultrahdr/tests/jpegr_test.cpp b/libs/ultrahdr/tests/jpegr_test.cpp index ac358872b4..5f5c19684b 100644 --- a/libs/ultrahdr/tests/jpegr_test.cpp +++ b/libs/ultrahdr/tests/jpegr_test.cpp @@ -769,6 +769,45 @@ TEST_F(JpegRTest, encodeAPI4ForInvalidArgs) { free(jpegR.data); } +/* Test Decode API invalid arguments */ +TEST_F(JpegRTest, decodeAPIForInvalidArgs) { + int ret; + + // we are not really compressing anything so lets keep allocs to a minimum + jpegr_compressed_struct jpegR; + jpegR.maxLength = 16 * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + + // we are not really decoding anything so lets keep allocs to a minimum + mRawP010Image.data = malloc(16); + + JpegR jpegRCodec; + + // test jpegr image + EXPECT_NE(OK, jpegRCodec.decodeJPEGR( + nullptr, &mRawP010Image)) << "fail, API allows nullptr for jpegr img"; + + // test dest image + EXPECT_NE(OK, jpegRCodec.decodeJPEGR( + &jpegR, nullptr)) << "fail, API allows nullptr for dest"; + + // test max display boost + EXPECT_NE(OK, jpegRCodec.decodeJPEGR( + &jpegR, &mRawP010Image, 0.5)) << "fail, API allows invalid max display boost"; + + // test output format + EXPECT_NE(OK, jpegRCodec.decodeJPEGR( + &jpegR, &mRawP010Image, 0.5, nullptr, + static_cast(-1))) << "fail, API allows invalid output format"; + + EXPECT_NE(OK, jpegRCodec.decodeJPEGR( + &jpegR, &mRawP010Image, 0.5, nullptr, + static_cast(ULTRAHDR_OUTPUT_MAX + 1))) + << "fail, API allows invalid output format"; + + free(jpegR.data); +} + TEST_F(JpegRTest, writeXmpThenRead) { ultrahdr_metadata_struct metadata_expected; metadata_expected.version = "1.0"; -- GitLab From fa9e2ef3d6ece2fef67ef460cbdfca009fb330fb Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Mon, 22 May 2023 15:10:20 -0700 Subject: [PATCH 1265/1310] [sf] Update snapshots after screenshots Screenshots can be taken for a partial hierarchy and may require some of the layers to be temporarily reparented and their geometry to be updated. Sceenshots use a copy of the layer snapshots and another instance of the composition engine to render the contents to a buffer. There was an issue where the snapshots were updated before they were copied and if the screenshot was taken during a period where there were no visible region changes (and no reason to rebuild the snapshots) we would pass invalid snapshot data when compositing to the display. With legacy frontend, its harder to split the screenshot path and the display composition path, so as a fix, update the snapshots after screenshots are taken. Bug: 282884552 Test: instrumented load to verify snapshots are updated correctly Test: repro steps in bug Change-Id: Ie778b02b57c5b1ddf0f09afe82675e04c5535ee5 --- services/surfaceflinger/Layer.h | 3 +-- services/surfaceflinger/LayerRenderArea.cpp | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 38590e6f20..f7596e20e5 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -877,6 +877,7 @@ public: // TODO(b/238781169) Remove direct calls to RenderEngine::drawLayers that don't go through // CompositionEngine to create a single path for composing layers. void updateSnapshot(bool updateGeometry); + void updateChildrenSnapshots(bool updateGeometry); void updateMetadataSnapshot(const LayerMetadata& parentMetadata); void updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata, std::unordered_set& visited); @@ -1134,8 +1135,6 @@ private: bool hasSomethingToDraw() const { return hasEffect() || hasBufferOrSidebandStream(); } - void updateChildrenSnapshots(bool updateGeometry); - // Fills the provided vector with the currently available JankData and removes the processed // JankData from the pending list. void transferAvailableJankData(const std::deque>& handles, diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp index d606cffe40..51d4ff854f 100644 --- a/services/surfaceflinger/LayerRenderArea.cpp +++ b/services/surfaceflinger/LayerRenderArea.cpp @@ -116,6 +116,8 @@ void LayerRenderArea::render(std::function drawLayers) { mLayer->setChildrenDrawingParent(mLayer); } } + mLayer->updateSnapshot(/*updateGeometry=*/true); + mLayer->updateChildrenSnapshots(/*updateGeometry=*/true); } } // namespace android -- GitLab From 603ace7bce645bdb4fdbe0398ba9864346091b2f Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Wed, 17 May 2023 16:55:08 -0500 Subject: [PATCH 1266/1310] Remove window infos from InputFlinger dumpsys This partially reverts a change that triggered a performance regression in InputFlinger. Bug: 281227113 Bug: 281248006 Test: presubmits Change-Id: Ia7bd668e3cab8a353b33f4fce4f98b6e57186b86 --- .../inputflinger/dispatcher/InputDispatcher.cpp | 17 ----------------- .../inputflinger/dispatcher/InputDispatcher.h | 5 ----- 2 files changed, 22 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index bdd45dc9b4..0cc7cfbcc8 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -5659,14 +5659,6 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) const { } else { dump += INDENT "Displays: \n"; } - dump += INDENT "Window Infos:\n"; - dump += StringPrintf(INDENT2 "vsync id: %" PRId64 "\n", mWindowInfosVsyncId); - dump += StringPrintf(INDENT2 "timestamp (ns): %" PRId64 "\n", mWindowInfosTimestamp); - dump += "\n"; - dump += StringPrintf(INDENT2 "max update delay (ns): %" PRId64 "\n", mMaxWindowInfosDelay); - dump += StringPrintf(INDENT2 "max update delay vsync id: %" PRId64 "\n", - mMaxWindowInfosDelayVsyncId); - dump += "\n"; if (!mGlobalMonitorsByDisplay.empty()) { for (const auto& [displayId, monitors] : mGlobalMonitorsByDisplay) { @@ -6708,15 +6700,6 @@ void InputDispatcher::onWindowInfosChanged(const gui::WindowInfosUpdate& update) for (const auto& [displayId, handles] : handlesPerDisplay) { setInputWindowsLocked(handles, displayId); } - - mWindowInfosVsyncId = update.vsyncId; - mWindowInfosTimestamp = update.timestamp; - - int64_t delay = systemTime() - update.timestamp; - if (delay > mMaxWindowInfosDelay) { - mMaxWindowInfosDelay = delay; - mMaxWindowInfosDelayVsyncId = update.vsyncId; - } } // Wake up poll loop since it may need to make new input dispatching choices. mLooper->wake(); diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 0e9cfeffe4..8ca01b7a09 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -204,11 +204,6 @@ private: const IdGenerator mIdGenerator; - int64_t mWindowInfosVsyncId GUARDED_BY(mLock); - int64_t mWindowInfosTimestamp GUARDED_BY(mLock); - int64_t mMaxWindowInfosDelay GUARDED_BY(mLock) = -1; - int64_t mMaxWindowInfosDelayVsyncId GUARDED_BY(mLock) = -1; - // With each iteration, InputDispatcher nominally processes one queued event, // a timeout, or a response from an input consumer. // This method should only be called on the input dispatcher's own thread. -- GitLab From 033270a19f2656a6697f402535d92f2c090eb501 Mon Sep 17 00:00:00 2001 From: Ram Mohan Date: Thu, 18 May 2023 15:26:33 +0530 Subject: [PATCH 1267/1310] ultrahdr: Add lower bounds to input resolution to avoid empty jpeg image If the test image is 2x2, then gain map size is 0x0, which causes problems during gainmap compression. Add lower bounds to supported resolution. Bug: 283500706 Test: ./libultrahdr_test Change-Id: I761330bcd5cd4506c38d053a479a135eed6c38ac --- libs/ultrahdr/jpegr.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp index da257266ee..78fa2af4b8 100644 --- a/libs/ultrahdr/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -65,6 +65,13 @@ static const char* const kJpegrVersion = "1.0"; // Map is quarter res / sixteenth size static const size_t kMapDimensionScaleFactor = 4; + +// Gain Map width is (image_width / kMapDimensionScaleFactor). If we were to +// compress 420 GainMap in jpeg, then we need at least 2 samples. For Grayscale +// 1 sample is sufficient. We are using 2 here anyways +static const int kMinWidth = 2 * kMapDimensionScaleFactor; +static const int kMinHeight = 2 * kMapDimensionScaleFactor; + // JPEG block size. // JPEG encoding / decoding will require block based DCT transform 16 x 16 for luma, // and 8 x 8 for chroma. @@ -105,10 +112,10 @@ status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_ima return ERROR_JPEGR_INVALID_INPUT_TYPE; } - if (uncompressed_p010_image->width == 0 - || uncompressed_p010_image->height == 0) { - ALOGE("Image dimensions cannot be zero, image dimensions %dx%d", - uncompressed_p010_image->width, uncompressed_p010_image->height); + if (uncompressed_p010_image->width < kMinWidth + || uncompressed_p010_image->height < kMinHeight) { + ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %dx%d", + kMinWidth, kMinHeight, uncompressed_p010_image->width, uncompressed_p010_image->height); return ERROR_JPEGR_INVALID_INPUT_TYPE; } -- GitLab From 2838bec41351d0eab9cc5a2c784da33e16b0aac7 Mon Sep 17 00:00:00 2001 From: Ram Mohan Date: Fri, 19 May 2023 02:46:49 +0530 Subject: [PATCH 1268/1310] ultrahdr: decompress image writes outside bounds for non aligned widths jpeg_read_raw_data() returns one MCU row per call, and thus one must pass buffer of at least max_v_samp_factor * DCTSIZE scanlines. The buffer must be large enough to hold the actual data plus padding to DCT-block boundaries. Bug: 283500706 Test: ./ultrahdr_enc_fuzzer Change-Id: Icb7c7e3eda590efc05d5945e7c906c1e4a012eab --- libs/ultrahdr/jpegdecoderhelper.cpp | 73 +++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/libs/ultrahdr/jpegdecoderhelper.cpp b/libs/ultrahdr/jpegdecoderhelper.cpp index 12217b7906..fac90c503d 100644 --- a/libs/ultrahdr/jpegdecoderhelper.cpp +++ b/libs/ultrahdr/jpegdecoderhelper.cpp @@ -26,6 +26,8 @@ using namespace std; namespace android::ultrahdr { +#define ALIGNM(x, m) ((((x) + ((m) - 1)) / (m)) * (m)) + const uint32_t kAPP0Marker = JPEG_APP0; // JFIF const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP const uint32_t kAPP2Marker = JPEG_APP0 + 2; // ICC @@ -224,7 +226,14 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) cinfo.out_color_space = JCS_EXT_RGBA; } else { if (cinfo.jpeg_color_space == JCS_YCbCr) { - // 1 byte per pixel for Y, 0.5 byte per pixel for U+V + if (cinfo.comp_info[0].h_samp_factor != 2 || + cinfo.comp_info[1].h_samp_factor != 1 || + cinfo.comp_info[2].h_samp_factor != 1 || + cinfo.comp_info[0].v_samp_factor != 2 || + cinfo.comp_info[1].v_samp_factor != 1 || + cinfo.comp_info[2].v_samp_factor != 1) { + return false; + } mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0); } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0); @@ -342,7 +351,6 @@ bool JpegDecoderHelper::decompressRGBA(jpeg_decompress_struct* cinfo, const uint } bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) { - JSAMPROW y[kCompressBatchSize]; JSAMPROW cb[kCompressBatchSize / 2]; JSAMPROW cr[kCompressBatchSize / 2]; @@ -356,6 +364,32 @@ bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8 std::unique_ptr empty(new uint8_t[cinfo->image_width]); memset(empty.get(), 0, cinfo->image_width); + const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); + bool is_width_aligned = (aligned_width == cinfo->image_width); + std::unique_ptr buffer_intrm = nullptr; + uint8_t* y_plane_intrm = nullptr; + uint8_t* u_plane_intrm = nullptr; + uint8_t* v_plane_intrm = nullptr; + JSAMPROW y_intrm[kCompressBatchSize]; + JSAMPROW cb_intrm[kCompressBatchSize / 2]; + JSAMPROW cr_intrm[kCompressBatchSize / 2]; + JSAMPARRAY planes_intrm[3] {y_intrm, cb_intrm, cr_intrm}; + if (!is_width_aligned) { + size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2; + buffer_intrm = std::make_unique(mcu_row_size); + y_plane_intrm = buffer_intrm.get(); + u_plane_intrm = y_plane_intrm + (aligned_width * kCompressBatchSize); + v_plane_intrm = u_plane_intrm + (aligned_width * kCompressBatchSize) / 4; + for (int i = 0; i < kCompressBatchSize; ++i) { + y_intrm[i] = y_plane_intrm + i * aligned_width; + } + for (int i = 0; i < kCompressBatchSize / 2; ++i) { + int offset_intrm = i * (aligned_width / 2); + cb_intrm[i] = u_plane_intrm + offset_intrm; + cr_intrm[i] = v_plane_intrm + offset_intrm; + } + } + while (cinfo->output_scanline < cinfo->image_height) { for (int i = 0; i < kCompressBatchSize; ++i) { size_t scanline = cinfo->output_scanline + i; @@ -377,11 +411,21 @@ bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8 } } - int processed = jpeg_read_raw_data(cinfo, planes, kCompressBatchSize); + int processed = jpeg_read_raw_data(cinfo, is_width_aligned ? planes : planes_intrm, + kCompressBatchSize); if (processed != kCompressBatchSize) { ALOGE("Number of processed lines does not equal input lines."); return false; } + if (!is_width_aligned) { + for (int i = 0; i < kCompressBatchSize; ++i) { + memcpy(y[i], y_intrm[i], cinfo->image_width); + } + for (int i = 0; i < kCompressBatchSize / 2; ++i) { + memcpy(cb[i], cb_intrm[i], cinfo->image_width / 2); + memcpy(cr[i], cr_intrm[i], cinfo->image_width / 2); + } + } } return true; } @@ -394,6 +438,21 @@ bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo, c std::unique_ptr empty(new uint8_t[cinfo->image_width]); memset(empty.get(), 0, cinfo->image_width); + int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); + bool is_width_aligned = (aligned_width == cinfo->image_width); + std::unique_ptr buffer_intrm = nullptr; + uint8_t* y_plane_intrm = nullptr; + JSAMPROW y_intrm[kCompressBatchSize]; + JSAMPARRAY planes_intrm[1] {y_intrm}; + if (!is_width_aligned) { + size_t mcu_row_size = aligned_width * kCompressBatchSize; + buffer_intrm = std::make_unique(mcu_row_size); + y_plane_intrm = buffer_intrm.get(); + for (int i = 0; i < kCompressBatchSize; ++i) { + y_intrm[i] = y_plane_intrm + i * aligned_width; + } + } + while (cinfo->output_scanline < cinfo->image_height) { for (int i = 0; i < kCompressBatchSize; ++i) { size_t scanline = cinfo->output_scanline + i; @@ -404,11 +463,17 @@ bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo, c } } - int processed = jpeg_read_raw_data(cinfo, planes, kCompressBatchSize); + int processed = jpeg_read_raw_data(cinfo, is_width_aligned ? planes : planes_intrm, + kCompressBatchSize); if (processed != kCompressBatchSize / 2) { ALOGE("Number of processed lines does not equal input lines."); return false; } + if (!is_width_aligned) { + for (int i = 0; i < kCompressBatchSize; ++i) { + memcpy(y[i], y_intrm[i], cinfo->image_width); + } + } } return true; } -- GitLab From fd7bb5407f860e0176b392dd024c03a548efb617 Mon Sep 17 00:00:00 2001 From: Ram Mohan Date: Sat, 20 May 2023 00:55:56 +0530 Subject: [PATCH 1269/1310] ultrahdr: unit test for encode api0 This test checks if luma stride, chroma stride, chroma_data, data fields of p010 raw struct have expected effects on the compressed jpegr data. Bug: 283495487 Test: ./libultrahdr_test Change-Id: Ie5c2e04181c08bb4ea7b2ba4cfcb56b0acc661f5 --- libs/ultrahdr/tests/jpegr_test.cpp | 136 ++++++++++++++++++++++++++++- 1 file changed, 132 insertions(+), 4 deletions(-) diff --git a/libs/ultrahdr/tests/jpegr_test.cpp b/libs/ultrahdr/tests/jpegr_test.cpp index ac358872b4..34f8afd7e4 100644 --- a/libs/ultrahdr/tests/jpegr_test.cpp +++ b/libs/ultrahdr/tests/jpegr_test.cpp @@ -89,6 +89,51 @@ static bool loadFile(const char filename[], void*& result, int* fileLength) { return true; } +static bool loadP010Image(const char *filename, jr_uncompressed_ptr img, + bool isUVContiguous) { + int fd = open(filename, O_CLOEXEC); + if (fd < 0) { + return false; + } + const int bpp = 2; + int lumaStride = img->luma_stride == 0 ? img->width : img->luma_stride; + int lumaSize = bpp * lumaStride * img->height; + int chromaSize = bpp * (img->height / 2) * + (isUVContiguous ? lumaStride : img->chroma_stride); + img->data = malloc(lumaSize + (isUVContiguous ? chromaSize : 0)); + if (img->data == nullptr) { + ALOGE("loadP010Image(): failed to allocate memory for luma data."); + return false; + } + uint8_t *mem = static_cast(img->data); + for (int i = 0; i < img->height; i++) { + if (read(fd, mem, img->width * bpp) != img->width * bpp) { + close(fd); + return false; + } + mem += lumaStride * bpp; + } + int chromaStride = lumaStride; + if (!isUVContiguous) { + img->chroma_data = malloc(chromaSize); + if (img->chroma_data == nullptr) { + ALOGE("loadP010Image(): failed to allocate memory for chroma data."); + return false; + } + mem = static_cast(img->chroma_data); + chromaStride = img->chroma_stride; + } + for (int i = 0; i < img->height / 2; i++) { + if (read(fd, mem, img->width * bpp) != img->width * bpp) { + close(fd); + return false; + } + mem += chromaStride * bpp; + } + close(fd); + return true; +} + class JpegRTest : public testing::Test { public: JpegRTest(); @@ -98,10 +143,11 @@ protected: virtual void SetUp(); virtual void TearDown(); - struct jpegr_uncompressed_struct mRawP010Image; - struct jpegr_uncompressed_struct mRawP010ImageWithStride; - struct jpegr_uncompressed_struct mRawYuv420Image; - struct jpegr_compressed_struct mJpegImage; + struct jpegr_uncompressed_struct mRawP010Image{}; + struct jpegr_uncompressed_struct mRawP010ImageWithStride{}; + struct jpegr_uncompressed_struct mRawP010ImageWithChromaData{}; + struct jpegr_uncompressed_struct mRawYuv420Image{}; + struct jpegr_compressed_struct mJpegImage{}; }; JpegRTest::JpegRTest() {} @@ -110,7 +156,11 @@ JpegRTest::~JpegRTest() {} void JpegRTest::SetUp() {} void JpegRTest::TearDown() { free(mRawP010Image.data); + free(mRawP010Image.chroma_data); free(mRawP010ImageWithStride.data); + free(mRawP010ImageWithStride.chroma_data); + free(mRawP010ImageWithChromaData.data); + free(mRawP010ImageWithChromaData.chroma_data); free(mRawYuv420Image.data); free(mJpegImage.data); } @@ -286,6 +336,8 @@ TEST_F(JpegRTest, encodeAPI0ForInvalidArgs) { &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad chroma stride"; + mRawP010ImageWithStride.chroma_data = nullptr; + free(jpegR.data); } @@ -734,6 +786,7 @@ TEST_F(JpegRTest, encodeAPI3ForInvalidArgs) { EXPECT_NE(OK, jpegRCodec.encodeJPEGR( &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad chroma stride"; + mRawP010ImageWithStride.chroma_data = nullptr; // bad compressed image EXPECT_NE(OK, jpegRCodec.encodeJPEGR( @@ -792,6 +845,81 @@ TEST_F(JpegRTest, writeXmpThenRead) { EXPECT_FLOAT_EQ(metadata_expected.minContentBoost, metadata_read.minContentBoost); } +/* Test Encode API-0 */ +TEST_F(JpegRTest, encodeFromP010) { + int ret; + + mRawP010Image.width = TEST_IMAGE_WIDTH; + mRawP010Image.height = TEST_IMAGE_HEIGHT; + mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + // Load input files. + if (!loadP010Image(RAW_P010_IMAGE, &mRawP010Image, true)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + + JpegR jpegRCodec; + + jpegr_compressed_struct jpegR; + jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t); + jpegR.data = malloc(jpegR.maxLength); + ret = jpegRCodec.encodeJPEGR( + &mRawP010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY, + nullptr); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + + mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH + 128; + mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + // Load input files. + if (!loadP010Image(RAW_P010_IMAGE, &mRawP010ImageWithStride, true)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + + jpegr_compressed_struct jpegRWithStride; + jpegRWithStride.maxLength = jpegR.length; + jpegRWithStride.data = malloc(jpegRWithStride.maxLength); + ret = jpegRCodec.encodeJPEGR( + &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegRWithStride, + DEFAULT_JPEG_QUALITY, nullptr); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + ASSERT_EQ(jpegR.length, jpegRWithStride.length) + << "Same input is yielding different output"; + ASSERT_EQ(0, memcmp(jpegR.data, jpegRWithStride.data, jpegR.length)) + << "Same input is yielding different output"; + + mRawP010ImageWithChromaData.width = TEST_IMAGE_WIDTH; + mRawP010ImageWithChromaData.height = TEST_IMAGE_HEIGHT; + mRawP010ImageWithChromaData.luma_stride = TEST_IMAGE_WIDTH + 64; + mRawP010ImageWithChromaData.chroma_stride = TEST_IMAGE_WIDTH + 256; + mRawP010ImageWithChromaData.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; + // Load input files. + if (!loadP010Image(RAW_P010_IMAGE, &mRawP010ImageWithChromaData, false)) { + FAIL() << "Load file " << RAW_P010_IMAGE << " failed"; + } + jpegr_compressed_struct jpegRWithChromaData; + jpegRWithChromaData.maxLength = jpegR.length; + jpegRWithChromaData.data = malloc(jpegRWithChromaData.maxLength); + ret = jpegRCodec.encodeJPEGR( + &mRawP010ImageWithChromaData, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + &jpegRWithChromaData, DEFAULT_JPEG_QUALITY, nullptr); + if (ret != OK) { + FAIL() << "Error code is " << ret; + } + ASSERT_EQ(jpegR.length, jpegRWithChromaData.length) + << "Same input is yielding different output"; + ASSERT_EQ(0, memcmp(jpegR.data, jpegRWithChromaData.data, jpegR.length)) + << "Same input is yielding different output"; + + free(jpegR.data); + free(jpegRWithStride.data); + free(jpegRWithChromaData.data); +} + /* Test Encode API-0 and decode */ TEST_F(JpegRTest, encodeFromP010ThenDecode) { int ret; -- GitLab From 7f4dd5124cd17197c27c80802b4885a9ad23ba90 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Thu, 18 May 2023 13:22:12 +0000 Subject: [PATCH 1270/1310] InputMapper refactor: Configure empty InputDevice Configure the Device prior to populating mappers for mappers to receive correct property map Test: m checkinput && atest libinput_tests inputflinger_tests Bug: 256009910 Change-Id: I2a348029afa6c566506f1d79b655173bb8e7a8af --- services/inputflinger/reader/InputDevice.cpp | 61 ++++++++++--------- services/inputflinger/reader/InputReader.cpp | 4 +- .../inputflinger/reader/include/InputDevice.h | 4 +- .../inputflinger/tests/InputReader_test.cpp | 5 +- 4 files changed, 40 insertions(+), 34 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 0a64a1c4a8..90ba003846 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -151,21 +151,22 @@ void InputDevice::addEmptyEventHubDevice(int32_t eventHubId) { return; } std::unique_ptr contextPtr(new InputDeviceContext(*this, eventHubId)); - std::vector> mappers; - - mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))}); -} - -void InputDevice::addEventHubDevice(int32_t eventHubId, - const InputReaderConfiguration& readerConfig) { - if (mDevices.find(eventHubId) != mDevices.end()) { - return; - } - std::unique_ptr contextPtr(new InputDeviceContext(*this, eventHubId)); - std::vector> mappers = createMappers(*contextPtr, readerConfig); - - // insert the context into the devices set - mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))}); + mDevices.insert( + {eventHubId, + std::make_pair, + std::vector>>(std::move(contextPtr), {})}); +} + +void InputDevice::populateMappers(int32_t eventHubId, + const InputReaderConfiguration& readerConfig) { + auto targetDevice = mDevices.find(eventHubId); + LOG_ALWAYS_FATAL_IF(targetDevice == mDevices.end(), + "InputDevice::populateMappers(): missing device with eventHubId %d ", + eventHubId); + // create and add mappers to device + InputDeviceContext& context = *targetDevice->second.first; + std::vector> mappers = createMappers(context, readerConfig); + targetDevice->second.second = std::move(mappers); // Must change generation to flag this device as changed bumpGeneration(); } @@ -440,29 +441,29 @@ int32_t InputDevice::getState(uint32_t sourceMask, int32_t code, GetStateFunc ge } std::vector> InputDevice::createMappers( - InputDeviceContext& contextPtr, const InputReaderConfiguration& readerConfig) { - ftl::Flags classes = contextPtr.getDeviceClasses(); + InputDeviceContext& context, const InputReaderConfiguration& readerConfig) { + ftl::Flags classes = context.getDeviceClasses(); std::vector> mappers; // Switch-like devices. if (classes.test(InputDeviceClass::SWITCH)) { - mappers.push_back(createInputMapper(contextPtr, readerConfig)); + mappers.push_back(createInputMapper(context, readerConfig)); } // Scroll wheel-like devices. if (classes.test(InputDeviceClass::ROTARY_ENCODER)) { - mappers.push_back(createInputMapper(contextPtr, readerConfig)); + mappers.push_back(createInputMapper(context, readerConfig)); } // Vibrator-like devices. if (classes.test(InputDeviceClass::VIBRATOR)) { - mappers.push_back(createInputMapper(contextPtr, readerConfig)); + mappers.push_back(createInputMapper(context, readerConfig)); } // Battery-like devices or light-containing devices. // PeripheralController will be created with associated EventHub device. if (classes.test(InputDeviceClass::BATTERY) || classes.test(InputDeviceClass::LIGHT)) { - mController = std::make_unique(contextPtr); + mController = std::make_unique(context); } // Keyboard-like devices. @@ -482,13 +483,13 @@ std::vector> InputDevice::createMappers( } if (keyboardSource != 0) { - mappers.push_back(createInputMapper(contextPtr, readerConfig, + mappers.push_back(createInputMapper(context, readerConfig, keyboardSource, keyboardType)); } // Cursor-like devices. if (classes.test(InputDeviceClass::CURSOR)) { - mappers.push_back(createInputMapper(contextPtr, readerConfig)); + mappers.push_back(createInputMapper(context, readerConfig)); } // Touchscreens and touchpad devices. @@ -496,31 +497,31 @@ std::vector> InputDevice::createMappers( sysprop::InputProperties::enable_touchpad_gestures_library().value_or(true); // TODO(b/272518665): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) touchpads, or // at least load this setting from the IDC file. - const InputDeviceIdentifier identifier = contextPtr.getDeviceIdentifier(); + const InputDeviceIdentifier identifier = context.getDeviceIdentifier(); const bool isSonyDualShock4Touchpad = identifier.vendor == 0x054c && (identifier.product == 0x05c4 || identifier.product == 0x09cc); if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) && classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) { - mappers.push_back(createInputMapper(contextPtr, readerConfig)); + mappers.push_back(createInputMapper(context, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH_MT)) { - mappers.push_back(std::make_unique(contextPtr, readerConfig)); + mappers.push_back(std::make_unique(context, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH)) { - mappers.push_back(std::make_unique(contextPtr, readerConfig)); + mappers.push_back(std::make_unique(context, readerConfig)); } // Joystick-like devices. if (classes.test(InputDeviceClass::JOYSTICK)) { - mappers.push_back(createInputMapper(contextPtr, readerConfig)); + mappers.push_back(createInputMapper(context, readerConfig)); } // Motion sensor enabled devices. if (classes.test(InputDeviceClass::SENSOR)) { - mappers.push_back(createInputMapper(contextPtr, readerConfig)); + mappers.push_back(createInputMapper(context, readerConfig)); } // External stylus-like devices. if (classes.test(InputDeviceClass::EXTERNAL_STYLUS)) { - mappers.push_back(createInputMapper(contextPtr, readerConfig)); + mappers.push_back(createInputMapper(context, readerConfig)); } return mappers; } diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index ea95f7857a..8a33dff868 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -334,7 +334,9 @@ std::shared_ptr InputReader::createDeviceLocked( device = std::make_shared(&mContext, deviceId, bumpGenerationLocked(), identifier); } - device->addEventHubDevice(eventHubId, mConfig); + device->addEmptyEventHubDevice(eventHubId); + auto unused = device->configure(systemTime(SYSTEM_TIME_MONOTONIC), mConfig, /*changes=*/{}); + device->populateMappers(eventHubId, mConfig); return device; } diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 0b8a608891..1729d4620d 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -81,7 +81,7 @@ public: void dump(std::string& dump, const std::string& eventHubDevStr); void addEmptyEventHubDevice(int32_t eventHubId); - void addEventHubDevice(int32_t eventHubId, const InputReaderConfiguration& readerConfig); + void populateMappers(int32_t eventHubId, const InputReaderConfiguration& readerConfig); void removeEventHubDevice(int32_t eventHubId); [[nodiscard]] std::list configure(nsecs_t when, const InputReaderConfiguration& readerConfig, @@ -203,7 +203,7 @@ private: int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc); std::vector> createMappers( - InputDeviceContext& contextPtr, const InputReaderConfiguration& readerConfig); + InputDeviceContext& context, const InputReaderConfiguration& readerConfig); PropertyMap mConfiguration; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index bfb371f02a..c045e15f19 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -2584,7 +2584,10 @@ TEST_F(InputDeviceTest, DumpDoesNotCrash) { mFakeEventHub->addDevice(TEST_EVENTHUB_ID, "Test EventHub device", InputDeviceClass::BATTERY); InputDevice device(mReader->getContext(), /*id=*/1, /*generation=*/2, /*identifier=*/{}); - device.addEventHubDevice(TEST_EVENTHUB_ID, mFakePolicy->getReaderConfiguration()); + device.addEmptyEventHubDevice(TEST_EVENTHUB_ID); + auto unused = device.configure(systemTime(SYSTEM_TIME_MONOTONIC), + mFakePolicy->getReaderConfiguration(), /*changes=*/{}); + device.populateMappers(TEST_EVENTHUB_ID, mFakePolicy->getReaderConfiguration()); device.removeEventHubDevice(TEST_EVENTHUB_ID); std::string dumpStr, eventHubDevStr; device.dump(dumpStr, eventHubDevStr); -- GitLab From 2aae677f5a53ce018d86e29e77ac0bb6c0f551db Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Thu, 18 May 2023 16:56:41 +0000 Subject: [PATCH 1271/1310] InputMapper refactor: Revert "fix touch issue on portrait reference" This reverts commit 0987fd8824d38e8bb4642fec705842a1c27bace8. Test: m checkinput && atest libinput_tests inputflinger_tests Bug: 256009910 Change-Id: I452b8292a5a797f21707f56ee01765650db99b94 --- services/inputflinger/reader/InputDevice.cpp | 4 ++-- services/inputflinger/reader/mapper/MultiTouchInputMapper.h | 4 ++-- services/inputflinger/reader/mapper/SingleTouchInputMapper.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 90ba003846..c6b93a51c3 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -504,9 +504,9 @@ std::vector> InputDevice::createMappers( classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) { mappers.push_back(createInputMapper(context, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH_MT)) { - mappers.push_back(std::make_unique(context, readerConfig)); + mappers.push_back(createInputMapper(context, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH)) { - mappers.push_back(std::make_unique(context, readerConfig)); + mappers.push_back(createInputMapper(context, readerConfig)); } // Joystick-like devices. diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h index f300ee15bd..1d788dffd4 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h @@ -27,8 +27,6 @@ public: friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig, Args... args); - explicit MultiTouchInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); ~MultiTouchInputMapper() override; @@ -41,6 +39,8 @@ protected: bool hasStylus() const override; private: + explicit MultiTouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); // simulate_stylus_with_touch is a debug mode that converts all finger pointers reported by this // mapper's touchscreen into stylus pointers, and adds SOURCE_STYLUS to the input device. // It is used to simulate stylus events for debugging and testing on a device that does not diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h index dac53cf700..7726bfb159 100644 --- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h @@ -27,8 +27,6 @@ public: friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig, Args... args); - explicit SingleTouchInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); ~SingleTouchInputMapper() override; @@ -42,6 +40,8 @@ protected: private: SingleTouchMotionAccumulator mSingleTouchMotionAccumulator; + explicit SingleTouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); }; } // namespace android -- GitLab From 65c005a6e90ed1db8f5c6004ab74f1a6ef6e2046 Mon Sep 17 00:00:00 2001 From: chaviw Date: Fri, 6 Aug 2021 17:09:47 -0500 Subject: [PATCH 1272/1310] Set windowInfo.displayId to be the layerStack Instead of using the displayId sent from WMS, it's better to use the layerStack since it more accurately represents where the layer is presented. Previously, the only case where WMS needed to set a displayId different from layerStack was when using portal displays, which is no longer supported. This also fixes the case with cloned layers. If a cloned layer was placed on a different display than the main layer, the WindowInfo for the clone would end up having the same display as the main window since the properties were directly copied over. With this fix, the cloned layer's WindowInfo will get the correct layerStack that represents where it's actually rendered. Test: MediaProjection capture with mirror Bug: 195280234 Bug: 194480991 Change-Id: Id1794798a543260c9868ccdc1be3cdb574b73dd5 Merged-In: Id1794798a543260c9868ccdc1be3cdb574b73dd5 (cherry picked from commit 6eada62e3f020f9c54c188a745945df5265e2e5c) --- services/surfaceflinger/Layer.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 694230268d..8291578b64 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -2330,10 +2330,7 @@ InputWindowInfo Layer::fillInputInfo(const sp& display) { InputWindowInfo info = mDrawingState.inputInfo; info.id = sequence; - - if (info.displayId == ADISPLAY_ID_NONE) { - info.displayId = getLayerStack(); - } + info.displayId = getLayerStack(); // Transform that goes from "logical(rotated)" display to physical/unrotated display. // This is for when inputflinger operates in physical display-space. -- GitLab From 979f2d8a9553e5788f49fc423b7785b47997d143 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 16 May 2023 14:26:24 -0700 Subject: [PATCH 1273/1310] Add a unit test for TouchpadInputMapper This test will provide RawEvent's to TouchpadInputMapper and will look at the returned events. The main purpose of this CL is to document the existing behaviour of CursorInputMapper and TouchpadInputMapper. The tests highlights that today, dispatcher must handle the case where the touch starts without HOVER_EXIT. That means that the dispatcher logs of "conflicting pointer actions" can be mostly ignored. The goal was to make the tests for the two mappers as similar to each other as possible. A slightly different testing infra is introduced here compared to the one used in InputReader_test. Changes: * Use mocks for interfaces instead of constructed objects This helps figure out which parts are important to mock for a specific test vs which ones are not. When a function is called with a parameters that the mocks aren't expecting, a warning is printed during test run. This helps identify the complete state needed in order for the test to execute. * No longer require InstrumentedInputReader * No longer require a listener. We only check the events that are coming from the 'process' call, which is what the interface for the mapper does. Limitations: * Still require an InputDevice object to be constructed in order to test InputMappers. Ideally, a mapper would only depend on the EventHub state (to read the current value of keys / axes after a reset). Bug: 263319225 Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests --gtest_filter="*HoverAndLeftButtonPress*" Change-Id: I7de0dee7abcf6bcb9d3283e29d9a85de2f331a44 --- include/input/Input.h | 13 ++ services/inputflinger/tests/Android.bp | 2 + .../tests/CursorInputMapper_test.cpp | 105 ++++++++++++ .../inputflinger/tests/InputMapperTest.cpp | 68 ++++++++ services/inputflinger/tests/InputMapperTest.h | 32 ++++ services/inputflinger/tests/InterfaceMocks.h | 146 +++++++++++++++++ .../tests/TouchpadInputMapper_test.cpp | 155 ++++++++++++++++++ 7 files changed, 521 insertions(+) create mode 100644 services/inputflinger/tests/CursorInputMapper_test.cpp create mode 100644 services/inputflinger/tests/InterfaceMocks.h create mode 100644 services/inputflinger/tests/TouchpadInputMapper_test.cpp diff --git a/include/input/Input.h b/include/input/Input.h index fe0c775fd3..527a47741c 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -242,6 +242,19 @@ enum class ToolType { ftl_last = PALM, }; +/** + * The state of the key. This should have 1:1 correspondence with the values of anonymous enum + * defined in input.h + */ +enum class KeyState { + UNKNOWN = AKEY_STATE_UNKNOWN, + UP = AKEY_STATE_UP, + DOWN = AKEY_STATE_DOWN, + VIRTUAL = AKEY_STATE_VIRTUAL, + ftl_first = UNKNOWN, + ftl_last = VIRTUAL, +}; + bool isStylusToolType(ToolType toolType); /* diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 52277ff078..569690ab78 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -40,6 +40,7 @@ cc_test { "AnrTracker_test.cpp", "BlockingQueue_test.cpp", "CapturedTouchpadEventConverter_test.cpp", + "CursorInputMapper_test.cpp", "EventHub_test.cpp", "FakeEventHub.cpp", "FakeInputReaderPolicy.cpp", @@ -58,6 +59,7 @@ cc_test { "PreferStylusOverTouch_test.cpp", "PropertyProvider_test.cpp", "TestInputListener.cpp", + "TouchpadInputMapper_test.cpp", "UinputDevice.cpp", "UnwantedInteractionBlocker_test.cpp", ], diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp new file mode 100644 index 0000000000..6774b1793f --- /dev/null +++ b/services/inputflinger/tests/CursorInputMapper_test.cpp @@ -0,0 +1,105 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "CursorInputMapper.h" + +#include +#include + +#include "FakePointerController.h" +#include "InputMapperTest.h" +#include "InterfaceMocks.h" +#include "TestInputListenerMatchers.h" + +#define TAG "CursorInputMapper_test" + +namespace android { + +using testing::Return; +using testing::VariantWith; +constexpr auto ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN; +constexpr auto ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE; +constexpr auto ACTION_UP = AMOTION_EVENT_ACTION_UP; +constexpr auto BUTTON_PRESS = AMOTION_EVENT_ACTION_BUTTON_PRESS; +constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE; +constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE; + +/** + * Unit tests for CursorInputMapper. + * This class is named 'CursorInputMapperUnitTest' to avoid name collision with the existing + * 'CursorInputMapperTest'. If all of the CursorInputMapper tests are migrated here, the name + * can be simplified to 'CursorInputMapperTest'. + * TODO(b/283812079): move CursorInputMapper tests here. + */ +class CursorInputMapperUnitTest : public InputMapperUnitTest { +protected: + void SetUp() override { + InputMapperUnitTest::SetUp(); + + // Current scan code state - all keys are UP by default + setScanCodeState(KeyState::UP, + {BTN_LEFT, BTN_RIGHT, BTN_MIDDLE, BTN_BACK, BTN_SIDE, BTN_FORWARD, + BTN_EXTRA, BTN_TASK}); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL)) + .WillRepeatedly(Return(false)); + + EXPECT_CALL(mMockInputReaderContext, bumpGeneration()).WillRepeatedly(Return(1)); + + mMapper = createInputMapper(*mDeviceContext, mReaderConfiguration); + } +}; + +/** + * Move the mouse and then click the button. Check whether HOVER_EXIT is generated when hovering + * ends. Currently, it is not. + */ +TEST_F(CursorInputMapperUnitTest, HoverAndLeftButtonPress) { + std::list args; + + // Move the cursor a little + args += process(EV_REL, REL_X, 10); + args += process(EV_REL, REL_Y, 20); + args += process(EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, ElementsAre(VariantWith(WithMotionAction(HOVER_MOVE)))); + + // Now click the mouse button + args.clear(); + args += process(EV_KEY, BTN_LEFT, 1); + args += process(EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, + ElementsAre(VariantWith(WithMotionAction(ACTION_DOWN)), + VariantWith(WithMotionAction(BUTTON_PRESS)))); + + // Move some more. + args.clear(); + args += process(EV_REL, REL_X, 10); + args += process(EV_REL, REL_Y, 20); + args += process(EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, ElementsAre(VariantWith(WithMotionAction(ACTION_MOVE)))); + + // Release the button + args.clear(); + args += process(EV_KEY, BTN_LEFT, 0); + args += process(EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, + ElementsAre(VariantWith(WithMotionAction(BUTTON_RELEASE)), + VariantWith(WithMotionAction(ACTION_UP)), + VariantWith(WithMotionAction(HOVER_MOVE)))); +} + +} // namespace android diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp index ad48a79731..0eee2b9be4 100644 --- a/services/inputflinger/tests/InputMapperTest.cpp +++ b/services/inputflinger/tests/InputMapperTest.cpp @@ -22,6 +22,74 @@ namespace android { +using testing::Return; + +void InputMapperUnitTest::SetUp() { + mFakePointerController = std::make_shared(); + mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); + mFakePointerController->setPosition(400, 240); + + EXPECT_CALL(mMockInputReaderContext, getPointerController(DEVICE_ID)) + .WillRepeatedly(Return(mFakePointerController)); + + EXPECT_CALL(mMockInputReaderContext, getEventHub()).WillRepeatedly(Return(&mMockEventHub)); + InputDeviceIdentifier identifier; + identifier.name = "device"; + identifier.location = "USB1"; + identifier.bus = 0; + + EXPECT_CALL(mMockEventHub, getDeviceIdentifier(EVENTHUB_ID)).WillRepeatedly(Return(identifier)); + mDevice = std::make_unique(&mMockInputReaderContext, DEVICE_ID, + /*generation=*/2, identifier); + mDeviceContext = std::make_unique(*mDevice, EVENTHUB_ID); +} + +void InputMapperUnitTest::setupAxis(int axis, bool valid, int32_t min, int32_t max, + int32_t resolution) { + EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, testing::_)) + .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) { + outAxisInfo->valid = valid; + outAxisInfo->minValue = min; + outAxisInfo->maxValue = max; + outAxisInfo->flat = 0; + outAxisInfo->fuzz = 0; + outAxisInfo->resolution = resolution; + return valid ? OK : -1; + }); +} + +void InputMapperUnitTest::expectScanCodes(bool present, std::set scanCodes) { + for (const auto& scanCode : scanCodes) { + EXPECT_CALL(mMockEventHub, hasScanCode(EVENTHUB_ID, scanCode)) + .WillRepeatedly(testing::Return(present)); + } +} + +void InputMapperUnitTest::setScanCodeState(KeyState state, std::set scanCodes) { + for (const auto& scanCode : scanCodes) { + EXPECT_CALL(mMockEventHub, getScanCodeState(EVENTHUB_ID, scanCode)) + .WillRepeatedly(testing::Return(static_cast(state))); + } +} + +void InputMapperUnitTest::setKeyCodeState(KeyState state, std::set keyCodes) { + for (const auto& keyCode : keyCodes) { + EXPECT_CALL(mMockEventHub, getKeyCodeState(EVENTHUB_ID, keyCode)) + .WillRepeatedly(testing::Return(static_cast(state))); + } +} + +std::list InputMapperUnitTest::process(int32_t type, int32_t code, int32_t value) { + RawEvent event; + event.when = systemTime(SYSTEM_TIME_MONOTONIC); + event.readTime = event.when; + event.deviceId = mMapper->getDeviceContext().getEventHubId(); + event.type = type; + event.code = code; + event.value = value; + return mMapper->process(&event); +} + const char* InputMapperTest::DEVICE_NAME = "device"; const char* InputMapperTest::DEVICE_LOCATION = "USB1"; const ftl::Flags InputMapperTest::DEVICE_CLASSES = diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h index 2b6655c45e..909bd9c056 100644 --- a/services/inputflinger/tests/InputMapperTest.h +++ b/services/inputflinger/tests/InputMapperTest.h @@ -23,16 +23,48 @@ #include #include #include +#include #include #include "FakeEventHub.h" #include "FakeInputReaderPolicy.h" #include "InstrumentedInputReader.h" +#include "InterfaceMocks.h" #include "TestConstants.h" #include "TestInputListener.h" namespace android { +class InputMapperUnitTest : public testing::Test { +protected: + static constexpr int32_t EVENTHUB_ID = 1; + static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000; + virtual void SetUp() override; + + void setupAxis(int axis, bool valid, int32_t min, int32_t max, int32_t resolution); + + void expectScanCodes(bool present, std::set scanCodes); + + void setScanCodeState(KeyState state, std::set scanCodes); + + void setKeyCodeState(KeyState state, std::set keyCodes); + + std::list process(int32_t type, int32_t code, int32_t value); + + MockEventHubInterface mMockEventHub; + std::shared_ptr mFakePointerController; + MockInputReaderContext mMockInputReaderContext; + std::unique_ptr mDevice; + + std::unique_ptr mDeviceContext; + InputReaderConfiguration mReaderConfiguration; + // The mapper should be created by the subclasses. + std::unique_ptr mMapper; +}; + +/** + * Deprecated - use InputMapperUnitTest instead. + */ class InputMapperTest : public testing::Test { protected: static const char* DEVICE_NAME; diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h new file mode 100644 index 0000000000..d720a902dc --- /dev/null +++ b/services/inputflinger/tests/InterfaceMocks.h @@ -0,0 +1,146 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +namespace android { + +class MockInputReaderContext : public InputReaderContext { +public: + MOCK_METHOD(void, updateGlobalMetaState, (), (override)); + int32_t getGlobalMetaState() override { return 0; }; + + MOCK_METHOD(void, disableVirtualKeysUntil, (nsecs_t time), (override)); + MOCK_METHOD(bool, shouldDropVirtualKey, (nsecs_t now, int32_t keyCode, int32_t scanCode), + (override)); + + MOCK_METHOD(void, fadePointer, (), (override)); + MOCK_METHOD(std::shared_ptr, getPointerController, + (int32_t deviceId), (override)); + + MOCK_METHOD(void, requestTimeoutAtTime, (nsecs_t when), (override)); + MOCK_METHOD(int32_t, bumpGeneration, (), (override)); + + MOCK_METHOD(void, getExternalStylusDevices, (std::vector & outDevices), + (override)); + MOCK_METHOD(std::list, dispatchExternalStylusState, (const StylusState& outState), + (override)); + + MOCK_METHOD(InputReaderPolicyInterface*, getPolicy, (), (override)); + MOCK_METHOD(EventHubInterface*, getEventHub, (), (override)); + + int32_t getNextId() override { return 1; }; + + MOCK_METHOD(void, updateLedMetaState, (int32_t metaState), (override)); + MOCK_METHOD(int32_t, getLedMetaState, (), (override)); +}; + +class MockEventHubInterface : public EventHubInterface { +public: + MOCK_METHOD(ftl::Flags, getDeviceClasses, (int32_t deviceId), (const)); + MOCK_METHOD(InputDeviceIdentifier, getDeviceIdentifier, (int32_t deviceId), (const)); + MOCK_METHOD(int32_t, getDeviceControllerNumber, (int32_t deviceId), (const)); + MOCK_METHOD(std::optional, getConfiguration, (int32_t deviceId), (const)); + MOCK_METHOD(status_t, getAbsoluteAxisInfo, + (int32_t deviceId, int axis, RawAbsoluteAxisInfo* outAxisInfo), (const)); + MOCK_METHOD(bool, hasRelativeAxis, (int32_t deviceId, int axis), (const)); + MOCK_METHOD(bool, hasInputProperty, (int32_t deviceId, int property), (const)); + MOCK_METHOD(bool, hasMscEvent, (int32_t deviceId, int mscEvent), (const)); + MOCK_METHOD(void, addKeyRemapping, (int32_t deviceId, int fromKeyCode, int toKeyCode), (const)); + MOCK_METHOD(status_t, mapKey, + (int32_t deviceId, int scanCode, int usageCode, int32_t metaState, + int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags), + (const)); + MOCK_METHOD(status_t, mapAxis, (int32_t deviceId, int scanCode, AxisInfo* outAxisInfo), + (const)); + MOCK_METHOD(void, setExcludedDevices, (const std::vector& devices)); + MOCK_METHOD(std::vector, getEvents, (int timeoutMillis)); + MOCK_METHOD(std::vector, getVideoFrames, (int32_t deviceId)); + MOCK_METHOD((base::Result>), mapSensor, + (int32_t deviceId, int32_t absCode), (const, override)); + MOCK_METHOD(std::vector, getRawBatteryIds, (int32_t deviceId), (const, override)); + MOCK_METHOD(std::optional, getRawBatteryInfo, + (int32_t deviceId, int32_t BatteryId), (const, override)); + MOCK_METHOD(std::vector, getRawLightIds, (int32_t deviceId), (const, override)); + MOCK_METHOD(std::optional, getRawLightInfo, (int32_t deviceId, int32_t lightId), + (const, override)); + MOCK_METHOD(std::optional, getLightBrightness, (int32_t deviceId, int32_t lightId), + (const, override)); + MOCK_METHOD(void, setLightBrightness, (int32_t deviceId, int32_t lightId, int32_t brightness), + (override)); + MOCK_METHOD((std::optional>), getLightIntensities, + (int32_t deviceId, int32_t lightId), (const, override)); + MOCK_METHOD(void, setLightIntensities, + (int32_t deviceId, int32_t lightId, + (std::unordered_map)intensities), + (override)); + + MOCK_METHOD(std::optional, getRawLayoutInfo, (int32_t deviceId), + (const, override)); + MOCK_METHOD(int32_t, getScanCodeState, (int32_t deviceId, int32_t scanCode), (const, override)); + MOCK_METHOD(int32_t, getKeyCodeState, (int32_t deviceId, int32_t keyCode), (const, override)); + MOCK_METHOD(int32_t, getSwitchState, (int32_t deviceId, int32_t sw), (const, override)); + + MOCK_METHOD(status_t, getAbsoluteAxisValue, (int32_t deviceId, int32_t axis, int32_t* outValue), + (const, override)); + MOCK_METHOD(int32_t, getKeyCodeForKeyLocation, (int32_t deviceId, int32_t locationKeyCode), + (const, override)); + MOCK_METHOD(bool, markSupportedKeyCodes, + (int32_t deviceId, const std::vector& keyCodes, uint8_t* outFlags), + (const, override)); + + MOCK_METHOD(bool, hasScanCode, (int32_t deviceId, int32_t scanCode), (const, override)); + + MOCK_METHOD(bool, hasKeyCode, (int32_t deviceId, int32_t keyCode), (const, override)); + + MOCK_METHOD(bool, hasLed, (int32_t deviceId, int32_t led), (const, override)); + + MOCK_METHOD(void, setLedState, (int32_t deviceId, int32_t led, bool on), (override)); + + MOCK_METHOD(void, getVirtualKeyDefinitions, + (int32_t deviceId, std::vector& outVirtualKeys), + (const, override)); + + MOCK_METHOD(const std::shared_ptr, getKeyCharacterMap, (int32_t deviceId), + (const, override)); + + MOCK_METHOD(bool, setKeyboardLayoutOverlay, + (int32_t deviceId, std::shared_ptr map), (override)); + + MOCK_METHOD(void, vibrate, (int32_t deviceId, const VibrationElement& effect), (override)); + MOCK_METHOD(void, cancelVibrate, (int32_t deviceId), (override)); + + MOCK_METHOD(std::vector, getVibratorIds, (int32_t deviceId), (const, override)); + MOCK_METHOD(std::optional, getBatteryCapacity, (int32_t deviceId, int32_t batteryId), + (const, override)); + + MOCK_METHOD(std::optional, getBatteryStatus, (int32_t deviceId, int32_t batteryId), + (const, override)); + MOCK_METHOD(void, requestReopenDevices, (), (override)); + MOCK_METHOD(void, wake, (), (override)); + + MOCK_METHOD(void, dump, (std::string & dump), (const, override)); + MOCK_METHOD(void, monitor, (), (const, override)); + MOCK_METHOD(bool, isDeviceEnabled, (int32_t deviceId), (const, override)); + MOCK_METHOD(status_t, enableDevice, (int32_t deviceId), (override)); + MOCK_METHOD(status_t, disableDevice, (int32_t deviceId), (override)); + MOCK_METHOD(void, sysfsNodeChanged, (const std::string& sysfsNodePath), (override)); +}; + +} // namespace android diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp new file mode 100644 index 0000000000..92cd462c9a --- /dev/null +++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp @@ -0,0 +1,155 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TouchpadInputMapper.h" + +#include +#include + +#include +#include "FakePointerController.h" +#include "InputMapperTest.h" +#include "InterfaceMocks.h" +#include "TestInputListenerMatchers.h" + +#define TAG "TouchpadInputMapper_test" + +namespace android { + +using testing::Return; +using testing::VariantWith; +constexpr auto ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN; +constexpr auto ACTION_UP = AMOTION_EVENT_ACTION_UP; +constexpr auto BUTTON_PRESS = AMOTION_EVENT_ACTION_BUTTON_PRESS; +constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE; +constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE; + +/** + * Unit tests for TouchpadInputMapper. + */ +class TouchpadInputMapperTest : public InputMapperUnitTest { +protected: + void SetUp() override { + InputMapperUnitTest::SetUp(); + + // Present scan codes: BTN_TOUCH and BTN_TOOL_FINGER + expectScanCodes(/*present=*/true, + {BTN_LEFT, BTN_RIGHT, BTN_TOOL_FINGER, BTN_TOOL_QUINTTAP, BTN_TOUCH, + BTN_TOOL_DOUBLETAP, BTN_TOOL_TRIPLETAP, BTN_TOOL_QUADTAP}); + // Missing scan codes that the mapper checks for. + expectScanCodes(/*present=*/false, + {BTN_TOOL_PEN, BTN_TOOL_RUBBER, BTN_TOOL_BRUSH, BTN_TOOL_PENCIL, + BTN_TOOL_AIRBRUSH}); + + // Current scan code state - all keys are UP by default + setScanCodeState(KeyState::UP, {BTN_TOUCH, BTN_STYLUS, + BTN_STYLUS2, BTN_0, + BTN_TOOL_FINGER, BTN_TOOL_PEN, + BTN_TOOL_RUBBER, BTN_TOOL_BRUSH, + BTN_TOOL_PENCIL, BTN_TOOL_AIRBRUSH, + BTN_TOOL_MOUSE, BTN_TOOL_LENS, + BTN_TOOL_DOUBLETAP, BTN_TOOL_TRIPLETAP, + BTN_TOOL_QUADTAP, BTN_TOOL_QUINTTAP, + BTN_LEFT, BTN_RIGHT, + BTN_MIDDLE, BTN_BACK, + BTN_SIDE, BTN_FORWARD, + BTN_EXTRA, BTN_TASK}); + + setKeyCodeState(KeyState::UP, + {AKEYCODE_STYLUS_BUTTON_PRIMARY, AKEYCODE_STYLUS_BUTTON_SECONDARY}); + + // Key mappings + EXPECT_CALL(mMockEventHub, + mapKey(EVENTHUB_ID, BTN_LEFT, /*usageCode=*/0, /*metaState=*/0, testing::_, + testing::_, testing::_)) + .WillRepeatedly(Return(NAME_NOT_FOUND)); + + // Input properties - only INPUT_PROP_BUTTONPAD + EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, INPUT_PROP_BUTTONPAD)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, INPUT_PROP_SEMI_MT)) + .WillRepeatedly(Return(false)); + + // Axes that the device has + setupAxis(ABS_MT_SLOT, /*valid=*/true, /*min=*/0, /*max=*/4, /*resolution=*/0); + setupAxis(ABS_MT_POSITION_X, /*valid=*/true, /*min=*/0, /*max=*/2000, /*resolution=*/24); + setupAxis(ABS_MT_POSITION_Y, /*valid=*/true, /*min=*/0, /*max=*/1000, /*resolution=*/24); + setupAxis(ABS_MT_PRESSURE, /*valid=*/true, /*min*/ 0, /*max=*/255, /*resolution=*/0); + // Axes that the device does not have + setupAxis(ABS_MT_ORIENTATION, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_TOUCH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_TOUCH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_WIDTH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_WIDTH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + + EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, testing::_)) + .WillRepeatedly([](int32_t eventHubId, int32_t, int32_t* outValue) { + *outValue = 0; + return OK; + }); + mMapper = createInputMapper(*mDeviceContext, mReaderConfiguration); + } +}; + +/** + * Start moving the finger and then click the left touchpad button. Check whether HOVER_EXIT is + * generated when hovering stops. Currently, it is not. + * In the current implementation, HOVER_MOVE and ACTION_DOWN events are not sent out right away, + * but only after the button is released. + */ +TEST_F(TouchpadInputMapperTest, HoverAndLeftButtonPress) { + std::list args; + + args += process(EV_ABS, ABS_MT_TRACKING_ID, 1); + args += process(EV_KEY, BTN_TOUCH, 1); + setScanCodeState(KeyState::DOWN, {BTN_TOOL_FINGER}); + args += process(EV_KEY, BTN_TOOL_FINGER, 1); + args += process(EV_ABS, ABS_MT_POSITION_X, 50); + args += process(EV_ABS, ABS_MT_POSITION_Y, 50); + args += process(EV_ABS, ABS_MT_PRESSURE, 1); + args += process(EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, testing::IsEmpty()); + + // Without this sleep, the test fails. + // TODO(b/284133337): Figure out whether this can be removed + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + args += process(EV_KEY, BTN_LEFT, 1); + setScanCodeState(KeyState::DOWN, {BTN_LEFT}); + args += process(EV_SYN, SYN_REPORT, 0); + + args += process(EV_KEY, BTN_LEFT, 0); + setScanCodeState(KeyState::UP, {BTN_LEFT}); + args += process(EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, + ElementsAre(VariantWith(WithMotionAction(HOVER_MOVE)), + VariantWith(WithMotionAction(ACTION_DOWN)), + VariantWith(WithMotionAction(BUTTON_PRESS)), + VariantWith(WithMotionAction(BUTTON_RELEASE)), + VariantWith(WithMotionAction(ACTION_UP)))); + + // Liftoff + args.clear(); + args += process(EV_ABS, ABS_MT_PRESSURE, 0); + args += process(EV_ABS, ABS_MT_TRACKING_ID, -1); + args += process(EV_KEY, BTN_TOUCH, 0); + setScanCodeState(KeyState::UP, {BTN_TOOL_FINGER}); + args += process(EV_KEY, BTN_TOOL_FINGER, 0); + args += process(EV_SYN, SYN_REPORT, 0); + ASSERT_THAT(args, testing::IsEmpty()); +} + +} // namespace android -- GitLab From d1d846c5e1fe4a168bf1a19066cb4155a571e787 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Mon, 15 May 2023 13:38:16 -0500 Subject: [PATCH 1274/1310] Skip window infos updates if no listeners This change fixes a bug where WindowInfosListenerInvoker's active message count is incremented for messages when there are no listeners. In this case, onWindowInfosReported is never called, leading to out-of-sync window infos once listeners are added back. This scenario may occur if system server dies. Bug: 279792237 Test: WindowInfosListenerInvokerTest, manual tested by killing system server Merged-In: If62f7cc56b48570f633e8e640006f020b053ea6f Change-Id: I521dad46a763d0380807ba20b9a4ec9dcecf6bfc --- services/surfaceflinger/SurfaceFlinger.cpp | 43 +-- services/surfaceflinger/SurfaceFlinger.h | 5 +- .../WindowInfosListenerInvoker.cpp | 148 ++++++----- .../WindowInfosListenerInvoker.h | 40 ++- .../fuzzer/surfaceflinger_fuzzers_utils.h | 2 +- .../surfaceflinger/tests/unittests/Android.bp | 1 + .../WindowInfosListenerInvokerTest.cpp | 244 ++++++++++++++++++ 7 files changed, 364 insertions(+), 119 deletions(-) create mode 100644 services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index b1d4b3c837..2ac1db961f 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2547,7 +2547,7 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe } updateCursorAsync(); - updateInputFlinger(vsyncId); + updateInputFlinger(vsyncId, frameTime); if (mLayerTracingEnabled && !mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) { // This will block and tracing should only be enabled for debugging. @@ -3740,7 +3740,7 @@ void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) { doCommitTransactions(); } -void SurfaceFlinger::updateInputFlinger(VsyncId vsyncId) { +void SurfaceFlinger::updateInputFlinger(VsyncId vsyncId, TimePoint frameTime) { if (!mInputFlinger || (!mUpdateInputInfo && mInputWindowCommands.empty())) { return; } @@ -3752,8 +3752,6 @@ void SurfaceFlinger::updateInputFlinger(VsyncId vsyncId) { if (mUpdateInputInfo) { mUpdateInputInfo = false; updateWindowInfo = true; - mLastInputFlingerUpdateVsyncId = vsyncId; - mLastInputFlingerUpdateTimestamp = systemTime(); buildWindowInfos(windowInfos, displayInfos); } @@ -3775,17 +3773,17 @@ void SurfaceFlinger::updateInputFlinger(VsyncId vsyncId) { inputWindowCommands = std::move(mInputWindowCommands), inputFlinger = mInputFlinger, this, - visibleWindowsChanged]() { + visibleWindowsChanged, vsyncId, frameTime]() { ATRACE_NAME("BackgroundExecutor::updateInputFlinger"); if (updateWindowInfo) { mWindowInfosListenerInvoker - ->windowInfosChanged(std::move(windowInfos), std::move(displayInfos), + ->windowInfosChanged(gui::WindowInfosUpdate{std::move(windowInfos), + std::move(displayInfos), + vsyncId.value, frameTime.ns()}, std::move( inputWindowCommands.windowInfosReportedListeners), /* forceImmediateCall= */ visibleWindowsChanged || - !inputWindowCommands.focusRequests.empty(), - mLastInputFlingerUpdateVsyncId, - mLastInputFlingerUpdateTimestamp); + !inputWindowCommands.focusRequests.empty()); } else { // If there are listeners but no changes to input windows, call the listeners // immediately. @@ -6152,27 +6150,14 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& comp result.append("\n"); result.append("Window Infos:\n"); - StringAppendF(&result, " input flinger update vsync id: %" PRId64 "\n", - mLastInputFlingerUpdateVsyncId.value); - StringAppendF(&result, " input flinger update timestamp (ns): %" PRId64 "\n", - mLastInputFlingerUpdateTimestamp); + auto windowInfosDebug = mWindowInfosListenerInvoker->getDebugInfo(); + StringAppendF(&result, " max send vsync id: %" PRId64 "\n", + windowInfosDebug.maxSendDelayVsyncId.value); + StringAppendF(&result, " max send delay (ns): %" PRId64 " ns\n", + windowInfosDebug.maxSendDelayDuration); + StringAppendF(&result, " unsent messages: %" PRIu32 "\n", + windowInfosDebug.pendingMessageCount); result.append("\n"); - - if (int64_t unsentVsyncId = mWindowInfosListenerInvoker->getUnsentMessageVsyncId().value; - unsentVsyncId != -1) { - StringAppendF(&result, " unsent input flinger update vsync id: %" PRId64 "\n", - unsentVsyncId); - StringAppendF(&result, " unsent input flinger update timestamp (ns): %" PRId64 "\n", - mWindowInfosListenerInvoker->getUnsentMessageTimestamp()); - result.append("\n"); - } - - if (uint32_t pendingMessages = mWindowInfosListenerInvoker->getPendingMessageCount(); - pendingMessages != 0) { - StringAppendF(&result, " pending input flinger calls: %" PRIu32 "\n", - mWindowInfosListenerInvoker->getPendingMessageCount()); - result.append("\n"); - } } mat4 SurfaceFlinger::calculateColorMatrix(float saturation) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index e2691ab39a..0bc506f1fe 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -722,7 +722,7 @@ private: void updateLayerHistory(const frontend::LayerSnapshot& snapshot); frontend::Update flushLifecycleUpdates() REQUIRES(kMainThreadContext); - void updateInputFlinger(VsyncId); + void updateInputFlinger(VsyncId vsyncId, TimePoint frameTime); void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext); void buildWindowInfos(std::vector& outWindowInfos, std::vector& outDisplayInfos); @@ -1259,9 +1259,6 @@ private: VsyncId mLastCommittedVsyncId; - VsyncId mLastInputFlingerUpdateVsyncId; - nsecs_t mLastInputFlingerUpdateTimestamp; - // If blurs should be enabled on this device. bool mSupportsBlur = false; std::atomic mFrameMissedCount = 0; diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp index 2b62638c61..20699ef123 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp +++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp @@ -16,8 +16,11 @@ #include #include +#include #include +#include +#include "BackgroundExecutor.h" #include "WindowInfosListenerInvoker.h" namespace android { @@ -26,7 +29,7 @@ using gui::DisplayInfo; using gui::IWindowInfosListener; using gui::WindowInfo; -using WindowInfosListenerVector = ftl::SmallVector, 3>; +using WindowInfosListenerVector = ftl::SmallVector, 3>; struct WindowInfosReportedListenerInvoker : gui::BnWindowInfosReportedListener, IBinder::DeathRecipient { @@ -86,45 +89,19 @@ void WindowInfosListenerInvoker::binderDied(const wp& who) { } void WindowInfosListenerInvoker::windowInfosChanged( - std::vector windowInfos, std::vector displayInfos, - WindowInfosReportedListenerSet reportedListeners, bool forceImmediateCall, VsyncId vsyncId, - nsecs_t timestamp) { - reportedListeners.insert(sp::fromExisting(this)); - auto callListeners = [this, windowInfos = std::move(windowInfos), - displayInfos = std::move(displayInfos), vsyncId, - timestamp](WindowInfosReportedListenerSet reportedListeners) mutable { - WindowInfosListenerVector windowInfosListeners; - { - std::scoped_lock lock(mListenersMutex); - for (const auto& [_, listener] : mWindowInfosListeners) { - windowInfosListeners.push_back(listener); - } - } - - auto reportedInvoker = - sp::make(windowInfosListeners, - std::move(reportedListeners)); - - gui::WindowInfosUpdate update(std::move(windowInfos), std::move(displayInfos), - vsyncId.value, timestamp); - - for (const auto& listener : windowInfosListeners) { - sp asBinder = IInterface::asBinder(listener); - - // linkToDeath is used here to ensure that the windowInfosReportedListeners - // are called even if one of the windowInfosListeners dies before - // calling onWindowInfosReported. - asBinder->linkToDeath(reportedInvoker); + gui::WindowInfosUpdate update, WindowInfosReportedListenerSet reportedListeners, + bool forceImmediateCall) { + WindowInfosListenerVector listeners; + { + std::scoped_lock lock{mMessagesMutex}; - auto status = listener->onWindowInfosChanged(update, reportedInvoker); - if (!status.isOk()) { - reportedInvoker->onWindowInfosReported(); - } + if (!mDelayInfo) { + mDelayInfo = DelayInfo{ + .vsyncId = update.vsyncId, + .frameTime = update.timestamp, + }; } - }; - { - std::scoped_lock lock(mMessagesMutex); // If there are unacked messages and this isn't a forced call, then return immediately. // If a forced window infos change doesn't happen first, the update will be sent after // the WindowInfosReportedListeners are called. If a forced window infos change happens or @@ -132,44 +109,87 @@ void WindowInfosListenerInvoker::windowInfosChanged( // will be dropped and the listeners will only be called with the latest info. This is done // to reduce the amount of binder memory used. if (mActiveMessageCount > 0 && !forceImmediateCall) { - mWindowInfosChangedDelayed = std::move(callListeners); - mUnsentVsyncId = vsyncId; - mUnsentTimestamp = timestamp; - mReportedListenersDelayed.merge(reportedListeners); + mDelayedUpdate = std::move(update); + mReportedListeners.merge(reportedListeners); + return; + } + + if (mDelayedUpdate) { + mDelayedUpdate.reset(); + } + + { + std::scoped_lock lock{mListenersMutex}; + for (const auto& [_, listener] : mWindowInfosListeners) { + listeners.push_back(listener); + } + } + if (CC_UNLIKELY(listeners.empty())) { + mReportedListeners.merge(reportedListeners); + mDelayInfo.reset(); return; } - mWindowInfosChangedDelayed = nullptr; - mUnsentVsyncId = {-1}; - mUnsentTimestamp = -1; - reportedListeners.merge(mReportedListenersDelayed); + reportedListeners.insert(sp::fromExisting(this)); + reportedListeners.merge(mReportedListeners); + mReportedListeners.clear(); + mActiveMessageCount++; + updateMaxSendDelay(); + mDelayInfo.reset(); } - callListeners(std::move(reportedListeners)); -} -binder::Status WindowInfosListenerInvoker::onWindowInfosReported() { - std::function callListeners; - WindowInfosReportedListenerSet reportedListeners; + auto reportedInvoker = + sp::make(listeners, std::move(reportedListeners)); - { - std::scoped_lock lock{mMessagesMutex}; - mActiveMessageCount--; - if (!mWindowInfosChangedDelayed || mActiveMessageCount > 0) { - return binder::Status::ok(); - } + for (const auto& listener : listeners) { + sp asBinder = IInterface::asBinder(listener); - mActiveMessageCount++; - callListeners = std::move(mWindowInfosChangedDelayed); - mWindowInfosChangedDelayed = nullptr; - mUnsentVsyncId = {-1}; - mUnsentTimestamp = -1; - reportedListeners = std::move(mReportedListenersDelayed); - mReportedListenersDelayed.clear(); + // linkToDeath is used here to ensure that the windowInfosReportedListeners + // are called even if one of the windowInfosListeners dies before + // calling onWindowInfosReported. + asBinder->linkToDeath(reportedInvoker); + + auto status = listener->onWindowInfosChanged(update, reportedInvoker); + if (!status.isOk()) { + reportedInvoker->onWindowInfosReported(); + } } +} - callListeners(std::move(reportedListeners)); +binder::Status WindowInfosListenerInvoker::onWindowInfosReported() { + BackgroundExecutor::getInstance().sendCallbacks({[this]() { + gui::WindowInfosUpdate update; + { + std::scoped_lock lock{mMessagesMutex}; + mActiveMessageCount--; + if (!mDelayedUpdate || mActiveMessageCount > 0) { + return; + } + update = std::move(*mDelayedUpdate); + mDelayedUpdate.reset(); + } + windowInfosChanged(std::move(update), {}, false); + }}); return binder::Status::ok(); } +WindowInfosListenerInvoker::DebugInfo WindowInfosListenerInvoker::getDebugInfo() { + std::scoped_lock lock{mMessagesMutex}; + updateMaxSendDelay(); + mDebugInfo.pendingMessageCount = mActiveMessageCount; + return mDebugInfo; +} + +void WindowInfosListenerInvoker::updateMaxSendDelay() { + if (!mDelayInfo) { + return; + } + nsecs_t delay = TimePoint::now().ns() - mDelayInfo->frameTime; + if (delay > mDebugInfo.maxSendDelayDuration) { + mDebugInfo.maxSendDelayDuration = delay; + mDebugInfo.maxSendDelayVsyncId = VsyncId{mDelayInfo->vsyncId}; + } +} + } // namespace android diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h index e35d0564b5..bc465a3a2b 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.h +++ b/services/surfaceflinger/WindowInfosListenerInvoker.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include @@ -40,26 +41,18 @@ public: void addWindowInfosListener(sp); void removeWindowInfosListener(const sp& windowInfosListener); - void windowInfosChanged(std::vector, std::vector, + void windowInfosChanged(gui::WindowInfosUpdate update, WindowInfosReportedListenerSet windowInfosReportedListeners, - bool forceImmediateCall, VsyncId vsyncId, nsecs_t timestamp); + bool forceImmediateCall); binder::Status onWindowInfosReported() override; - VsyncId getUnsentMessageVsyncId() { - std::scoped_lock lock(mMessagesMutex); - return mUnsentVsyncId; - } - - nsecs_t getUnsentMessageTimestamp() { - std::scoped_lock lock(mMessagesMutex); - return mUnsentTimestamp; - } - - uint32_t getPendingMessageCount() { - std::scoped_lock lock(mMessagesMutex); - return mActiveMessageCount; - } + struct DebugInfo { + VsyncId maxSendDelayVsyncId; + nsecs_t maxSendDelayDuration; + uint32_t pendingMessageCount; + }; + DebugInfo getDebugInfo(); protected: void binderDied(const wp& who) override; @@ -73,11 +66,16 @@ private: std::mutex mMessagesMutex; uint32_t mActiveMessageCount GUARDED_BY(mMessagesMutex) = 0; - std::function mWindowInfosChangedDelayed - GUARDED_BY(mMessagesMutex); - VsyncId mUnsentVsyncId GUARDED_BY(mMessagesMutex) = {-1}; - nsecs_t mUnsentTimestamp GUARDED_BY(mMessagesMutex) = -1; - WindowInfosReportedListenerSet mReportedListenersDelayed; + std::optional mDelayedUpdate GUARDED_BY(mMessagesMutex); + WindowInfosReportedListenerSet mReportedListeners; + + DebugInfo mDebugInfo GUARDED_BY(mMessagesMutex); + struct DelayInfo { + int64_t vsyncId; + nsecs_t frameTime; + }; + std::optional mDelayInfo GUARDED_BY(mMessagesMutex); + void updateMaxSendDelay() REQUIRES(mMessagesMutex); }; } // namespace android diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index da5ec480cc..4d03be04b3 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -590,7 +590,7 @@ public: mFlinger->binderDied(display); mFlinger->onFirstRef(); - mFlinger->updateInputFlinger(VsyncId{0}); + mFlinger->updateInputFlinger(VsyncId{}, TimePoint{}); mFlinger->updateCursorAsync(); mutableScheduler().setVsyncConfig({.sfOffset = mFdp.ConsumeIntegral(), diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index 881b362f1e..db81bad968 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -139,6 +139,7 @@ cc_test { "VSyncReactorTest.cpp", "VsyncConfigurationTest.cpp", "VsyncScheduleTest.cpp", + "WindowInfosListenerInvokerTest.cpp", ], } diff --git a/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp b/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp new file mode 100644 index 0000000000..af4971b063 --- /dev/null +++ b/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp @@ -0,0 +1,244 @@ +#include +#include +#include +#include +#include + +#include "BackgroundExecutor.h" +#include "WindowInfosListenerInvoker.h" +#include "android/gui/IWindowInfosReportedListener.h" + +namespace android { + +class WindowInfosListenerInvokerTest : public testing::Test { +protected: + WindowInfosListenerInvokerTest() : mInvoker(sp::make()) {} + + ~WindowInfosListenerInvokerTest() { + std::mutex mutex; + std::condition_variable cv; + bool flushComplete = false; + // Flush the BackgroundExecutor thread to ensure any scheduled tasks are complete. + // Otherwise, references those tasks hold may go out of scope before they are done + // executing. + BackgroundExecutor::getInstance().sendCallbacks({[&]() { + std::scoped_lock lock{mutex}; + flushComplete = true; + cv.notify_one(); + }}); + std::unique_lock lock{mutex}; + cv.wait(lock, [&]() { return flushComplete; }); + } + + sp mInvoker; +}; + +using WindowInfosUpdateConsumer = std::function&)>; + +class Listener : public gui::BnWindowInfosListener { +public: + Listener(WindowInfosUpdateConsumer consumer) : mConsumer(std::move(consumer)) {} + + binder::Status onWindowInfosChanged( + const gui::WindowInfosUpdate& update, + const sp& reportedListener) override { + mConsumer(update, reportedListener); + return binder::Status::ok(); + } + +private: + WindowInfosUpdateConsumer mConsumer; +}; + +// Test that WindowInfosListenerInvoker#windowInfosChanged calls a single window infos listener. +TEST_F(WindowInfosListenerInvokerTest, callsSingleListener) { + std::mutex mutex; + std::condition_variable cv; + + int callCount = 0; + + mInvoker->addWindowInfosListener( + sp::make([&](const gui::WindowInfosUpdate&, + const sp& reportedListener) { + std::scoped_lock lock{mutex}; + callCount++; + cv.notify_one(); + + reportedListener->onWindowInfosReported(); + })); + + BackgroundExecutor::getInstance().sendCallbacks( + {[this]() { mInvoker->windowInfosChanged({}, {}, false); }}); + + std::unique_lock lock{mutex}; + cv.wait(lock, [&]() { return callCount == 1; }); + EXPECT_EQ(callCount, 1); +} + +// Test that WindowInfosListenerInvoker#windowInfosChanged calls multiple window infos listeners. +TEST_F(WindowInfosListenerInvokerTest, callsMultipleListeners) { + std::mutex mutex; + std::condition_variable cv; + + int callCount = 0; + const int expectedCallCount = 3; + + for (int i = 0; i < expectedCallCount; i++) { + mInvoker->addWindowInfosListener(sp::make( + [&](const gui::WindowInfosUpdate&, + const sp& reportedListener) { + std::scoped_lock lock{mutex}; + callCount++; + if (callCount == expectedCallCount) { + cv.notify_one(); + } + + reportedListener->onWindowInfosReported(); + })); + } + + BackgroundExecutor::getInstance().sendCallbacks( + {[&]() { mInvoker->windowInfosChanged({}, {}, false); }}); + + std::unique_lock lock{mutex}; + cv.wait(lock, [&]() { return callCount == expectedCallCount; }); + EXPECT_EQ(callCount, expectedCallCount); +} + +// Test that WindowInfosListenerInvoker#windowInfosChanged delays sending a second message until +// after the WindowInfosReportedListener is called. +TEST_F(WindowInfosListenerInvokerTest, delaysUnackedCall) { + std::mutex mutex; + std::condition_variable cv; + + int callCount = 0; + + // Simulate a slow ack by not calling the WindowInfosReportedListener. + mInvoker->addWindowInfosListener(sp::make( + [&](const gui::WindowInfosUpdate&, const sp&) { + std::scoped_lock lock{mutex}; + callCount++; + cv.notify_one(); + })); + + BackgroundExecutor::getInstance().sendCallbacks({[&]() { + mInvoker->windowInfosChanged({}, {}, false); + mInvoker->windowInfosChanged({}, {}, false); + }}); + + { + std::unique_lock lock{mutex}; + cv.wait(lock, [&]() { return callCount == 1; }); + } + EXPECT_EQ(callCount, 1); + + // Ack the first message. + mInvoker->onWindowInfosReported(); + + { + std::unique_lock lock{mutex}; + cv.wait(lock, [&]() { return callCount == 2; }); + } + EXPECT_EQ(callCount, 2); +} + +// Test that WindowInfosListenerInvoker#windowInfosChanged immediately sends a second message when +// forceImmediateCall is true. +TEST_F(WindowInfosListenerInvokerTest, sendsForcedMessage) { + std::mutex mutex; + std::condition_variable cv; + + int callCount = 0; + const int expectedCallCount = 2; + + // Simulate a slow ack by not calling the WindowInfosReportedListener. + mInvoker->addWindowInfosListener(sp::make( + [&](const gui::WindowInfosUpdate&, const sp&) { + std::scoped_lock lock{mutex}; + callCount++; + if (callCount == expectedCallCount) { + cv.notify_one(); + } + })); + + BackgroundExecutor::getInstance().sendCallbacks({[&]() { + mInvoker->windowInfosChanged({}, {}, false); + mInvoker->windowInfosChanged({}, {}, true); + }}); + + { + std::unique_lock lock{mutex}; + cv.wait(lock, [&]() { return callCount == expectedCallCount; }); + } + EXPECT_EQ(callCount, expectedCallCount); +} + +// Test that WindowInfosListenerInvoker#windowInfosChanged skips old messages when more than one +// message is delayed. +TEST_F(WindowInfosListenerInvokerTest, skipsDelayedMessage) { + std::mutex mutex; + std::condition_variable cv; + + int64_t lastUpdateId = -1; + + // Simulate a slow ack by not calling the WindowInfosReportedListener. + mInvoker->addWindowInfosListener( + sp::make([&](const gui::WindowInfosUpdate& update, + const sp&) { + std::scoped_lock lock{mutex}; + lastUpdateId = update.vsyncId; + cv.notify_one(); + })); + + BackgroundExecutor::getInstance().sendCallbacks({[&]() { + mInvoker->windowInfosChanged({{}, {}, /* vsyncId= */ 1, 0}, {}, false); + mInvoker->windowInfosChanged({{}, {}, /* vsyncId= */ 2, 0}, {}, false); + mInvoker->windowInfosChanged({{}, {}, /* vsyncId= */ 3, 0}, {}, false); + }}); + + { + std::unique_lock lock{mutex}; + cv.wait(lock, [&]() { return lastUpdateId == 1; }); + } + EXPECT_EQ(lastUpdateId, 1); + + // Ack the first message. The third update should be sent. + mInvoker->onWindowInfosReported(); + + { + std::unique_lock lock{mutex}; + cv.wait(lock, [&]() { return lastUpdateId == 3; }); + } + EXPECT_EQ(lastUpdateId, 3); +} + +// Test that WindowInfosListenerInvoker#windowInfosChanged immediately calls listener after a call +// where no listeners were configured. +TEST_F(WindowInfosListenerInvokerTest, noListeners) { + std::mutex mutex; + std::condition_variable cv; + + int callCount = 0; + + // Test that calling windowInfosChanged without any listeners doesn't cause the next call to be + // delayed. + BackgroundExecutor::getInstance().sendCallbacks({[&]() { + mInvoker->windowInfosChanged({}, {}, false); + mInvoker->addWindowInfosListener(sp::make( + [&](const gui::WindowInfosUpdate&, const sp&) { + std::scoped_lock lock{mutex}; + callCount++; + cv.notify_one(); + })); + mInvoker->windowInfosChanged({}, {}, false); + }}); + + { + std::unique_lock lock{mutex}; + cv.wait(lock, [&]() { return callCount == 1; }); + } + EXPECT_EQ(callCount, 1); +} + +} // namespace android -- GitLab From f954040ee1fdeaeb11d2a91125f12e28d79925e8 Mon Sep 17 00:00:00 2001 From: Ram Mohan Date: Thu, 18 May 2023 20:08:55 +0530 Subject: [PATCH 1275/1310] ultrahdr: Avoid possible failure during gainmap compression Allocating memory for compressed gainmap is always tricky because the size is dependent on the data and quality factor. Too less a size can cause intermittent errors. Avoid alloc altogether and remove memcpy Bug: 283503763 Test: ./libultrahdr_test Change-Id: Ib57bac8bb25a2d00582c65067608a591f43cdc3a --- libs/ultrahdr/include/ultrahdr/jpegr.h | 5 +- libs/ultrahdr/jpegr.cpp | 67 +++++++++++++------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/libs/ultrahdr/include/ultrahdr/jpegr.h b/libs/ultrahdr/include/ultrahdr/jpegr.h index 00b66aeaf6..1f9bd0f930 100644 --- a/libs/ultrahdr/include/ultrahdr/jpegr.h +++ b/libs/ultrahdr/include/ultrahdr/jpegr.h @@ -17,6 +17,7 @@ #ifndef ANDROID_ULTRAHDR_JPEGR_H #define ANDROID_ULTRAHDR_JPEGR_H +#include "jpegencoderhelper.h" #include "jpegrerrorcode.h" #include "ultrahdr.h" @@ -312,11 +313,11 @@ private: * This method is called in the encoding pipeline. It will encode the gain map. * * @param uncompressed_gain_map uncompressed gain map - * @param dest encoded recover map + * @param resource to compress gain map * @return NO_ERROR if encoding succeeds, error code if error occurs. */ status_t compressGainMap(jr_uncompressed_ptr uncompressed_gain_map, - jr_compressed_ptr dest); + JpegEncoderHelper* jpeg_encoder); /* * This methoud is called to separate primary image and gain map image from JPEGR diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp index 5ebca399a9..c250aa02fd 100644 --- a/libs/ultrahdr/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -244,11 +244,13 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, std::unique_ptr map_data; map_data.reset(reinterpret_cast(map.data)); + JpegEncoderHelper jpeg_encoder_gainmap; + JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap)); jpegr_compressed_struct compressed_map; - compressed_map.maxLength = map.width * map.height; - unique_ptr compressed_map_data = make_unique(compressed_map.maxLength); - compressed_map.data = compressed_map_data.get(); - JPEGR_CHECK(compressGainMap(&map, &compressed_map)); + compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize(); + compressed_map.length = compressed_map.maxLength; + compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr(); + compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; sp icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, uncompressed_yuv_420_image.colorGamut); @@ -301,11 +303,13 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, std::unique_ptr map_data; map_data.reset(reinterpret_cast(map.data)); + JpegEncoderHelper jpeg_encoder_gainmap; + JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap)); jpegr_compressed_struct compressed_map; - compressed_map.maxLength = map.width * map.height; - unique_ptr compressed_map_data = make_unique(compressed_map.maxLength); - compressed_map.data = compressed_map_data.get(); - JPEGR_CHECK(compressGainMap(&map, &compressed_map)); + compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize(); + compressed_map.length = compressed_map.maxLength; + compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr(); + compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; sp icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, uncompressed_yuv_420_image->colorGamut); @@ -356,11 +360,13 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, std::unique_ptr map_data; map_data.reset(reinterpret_cast(map.data)); + JpegEncoderHelper jpeg_encoder_gainmap; + JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap)); jpegr_compressed_struct compressed_map; - compressed_map.maxLength = map.width * map.height; - unique_ptr compressed_map_data = make_unique(compressed_map.maxLength); - compressed_map.data = compressed_map_data.get(); - JPEGR_CHECK(compressGainMap(&map, &compressed_map)); + compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize(); + compressed_map.length = compressed_map.maxLength; + compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr(); + compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest)); @@ -407,11 +413,13 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, std::unique_ptr map_data; map_data.reset(reinterpret_cast(map.data)); + JpegEncoderHelper jpeg_encoder_gainmap; + JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap)); jpegr_compressed_struct compressed_map; - compressed_map.maxLength = map.width * map.height; - unique_ptr compressed_map_data = make_unique(compressed_map.maxLength); - compressed_map.data = compressed_map_data.get(); - JPEGR_CHECK(compressGainMap(&map, &compressed_map)); + compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize(); + compressed_map.length = compressed_map.maxLength; + compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr(); + compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest)); @@ -604,30 +612,21 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, } status_t JpegR::compressGainMap(jr_uncompressed_ptr uncompressed_gain_map, - jr_compressed_ptr dest) { - if (uncompressed_gain_map == nullptr || dest == nullptr) { + JpegEncoderHelper* jpeg_encoder) { + if (uncompressed_gain_map == nullptr || jpeg_encoder == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } - JpegEncoderHelper jpeg_encoder; - if (!jpeg_encoder.compressImage(uncompressed_gain_map->data, - uncompressed_gain_map->width, - uncompressed_gain_map->height, - kMapCompressQuality, - nullptr, - 0, - true /* isSingleChannel */)) { + if (!jpeg_encoder->compressImage(uncompressed_gain_map->data, + uncompressed_gain_map->width, + uncompressed_gain_map->height, + kMapCompressQuality, + nullptr, + 0, + true /* isSingleChannel */)) { return ERROR_JPEGR_ENCODE_ERROR; } - if (dest->maxLength < jpeg_encoder.getCompressedImageSize()) { - return ERROR_JPEGR_BUFFER_TOO_SMALL; - } - - memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize()); - dest->length = jpeg_encoder.getCompressedImageSize(); - dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; - return NO_ERROR; } -- GitLab From 47390ece3c85ef17091b49d3503286e632b11c41 Mon Sep 17 00:00:00 2001 From: John Reck Date: Wed, 24 May 2023 13:46:37 -0400 Subject: [PATCH 1276/1310] Fix USAGE_FRONT_BUFFER failure on Cuttlefish Bug: 280866371 Test: repro in bug Change-Id: I2963143d88a6ffba68012f47b79155a01367d49d --- libs/ui/Gralloc5.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp index 21068394d2..c3b2d3d808 100644 --- a/libs/ui/Gralloc5.cpp +++ b/libs/ui/Gralloc5.cpp @@ -343,14 +343,17 @@ status_t Gralloc5Mapper::validateBufferSize(buffer_handle_t bufferHandle, uint32 return BAD_VALUE; } } - { - auto value = getStandardMetadata(mMapper, bufferHandle); - if (static_cast(usage) != value) { - ALOGW("Usage didn't match, expected %" PRIu64 " got %" PRId64, usage, - static_cast(value.value_or(BufferUsage::CPU_READ_NEVER))); - return BAD_VALUE; - } - } + // TODO: This can false-positive fail if the allocator adjusted the USAGE bits internally + // Investigate further & re-enable or remove, but for now ignoring usage should be OK + (void)usage; + // { + // auto value = getStandardMetadata(mMapper, bufferHandle); + // if (static_cast(usage) != value) { + // ALOGW("Usage didn't match, expected %" PRIu64 " got %" PRId64, usage, + // static_cast(value.value_or(BufferUsage::CPU_READ_NEVER))); + // return BAD_VALUE; + // } + // } { auto value = getStandardMetadata(mMapper, bufferHandle); if (stride != value) { -- GitLab From 342e1dd013c4c5f51a15222d5b112fb14583a636 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 25 May 2023 00:12:00 +0000 Subject: [PATCH 1277/1310] Revert "InputMapper refactor: Revert "fix touch issue on portrai..." Revert submission 23316821 Reason for revert: breaks touch on some devices b/284203485 Reverted changes: /q/submissionid:23316821 Change-Id: Ic4537a645d710a900d6a3b9328bd6489baedbb1d --- services/inputflinger/reader/InputDevice.cpp | 4 ++-- services/inputflinger/reader/mapper/MultiTouchInputMapper.h | 4 ++-- services/inputflinger/reader/mapper/SingleTouchInputMapper.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index c6b93a51c3..90ba003846 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -504,9 +504,9 @@ std::vector> InputDevice::createMappers( classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) { mappers.push_back(createInputMapper(context, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH_MT)) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(std::make_unique(context, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH)) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(std::make_unique(context, readerConfig)); } // Joystick-like devices. diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h index 1d788dffd4..f300ee15bd 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h @@ -27,6 +27,8 @@ public: friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig, Args... args); + explicit MultiTouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); ~MultiTouchInputMapper() override; @@ -39,8 +41,6 @@ protected: bool hasStylus() const override; private: - explicit MultiTouchInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); // simulate_stylus_with_touch is a debug mode that converts all finger pointers reported by this // mapper's touchscreen into stylus pointers, and adds SOURCE_STYLUS to the input device. // It is used to simulate stylus events for debugging and testing on a device that does not diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h index 7726bfb159..dac53cf700 100644 --- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h @@ -27,6 +27,8 @@ public: friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig, Args... args); + explicit SingleTouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); ~SingleTouchInputMapper() override; @@ -40,8 +42,6 @@ protected: private: SingleTouchMotionAccumulator mSingleTouchMotionAccumulator; - explicit SingleTouchInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); }; } // namespace android -- GitLab From c96ed759fb428c7b982c921611e5da05040e3cea Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 25 May 2023 00:12:00 +0000 Subject: [PATCH 1278/1310] Revert "InputMapper refactor: Configure empty InputDevice" Revert submission 23316821 Reason for revert: breaks touch on some devices b/284203485 Reverted changes: /q/submissionid:23316821 Change-Id: Id957e51b53e48b874302b24b612554f3024aa87b --- services/inputflinger/reader/InputDevice.cpp | 61 +++++++++---------- services/inputflinger/reader/InputReader.cpp | 4 +- .../inputflinger/reader/include/InputDevice.h | 4 +- .../inputflinger/tests/InputReader_test.cpp | 5 +- 4 files changed, 34 insertions(+), 40 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 90ba003846..0a64a1c4a8 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -151,22 +151,21 @@ void InputDevice::addEmptyEventHubDevice(int32_t eventHubId) { return; } std::unique_ptr contextPtr(new InputDeviceContext(*this, eventHubId)); - mDevices.insert( - {eventHubId, - std::make_pair, - std::vector>>(std::move(contextPtr), {})}); -} - -void InputDevice::populateMappers(int32_t eventHubId, - const InputReaderConfiguration& readerConfig) { - auto targetDevice = mDevices.find(eventHubId); - LOG_ALWAYS_FATAL_IF(targetDevice == mDevices.end(), - "InputDevice::populateMappers(): missing device with eventHubId %d ", - eventHubId); - // create and add mappers to device - InputDeviceContext& context = *targetDevice->second.first; - std::vector> mappers = createMappers(context, readerConfig); - targetDevice->second.second = std::move(mappers); + std::vector> mappers; + + mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))}); +} + +void InputDevice::addEventHubDevice(int32_t eventHubId, + const InputReaderConfiguration& readerConfig) { + if (mDevices.find(eventHubId) != mDevices.end()) { + return; + } + std::unique_ptr contextPtr(new InputDeviceContext(*this, eventHubId)); + std::vector> mappers = createMappers(*contextPtr, readerConfig); + + // insert the context into the devices set + mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))}); // Must change generation to flag this device as changed bumpGeneration(); } @@ -441,29 +440,29 @@ int32_t InputDevice::getState(uint32_t sourceMask, int32_t code, GetStateFunc ge } std::vector> InputDevice::createMappers( - InputDeviceContext& context, const InputReaderConfiguration& readerConfig) { - ftl::Flags classes = context.getDeviceClasses(); + InputDeviceContext& contextPtr, const InputReaderConfiguration& readerConfig) { + ftl::Flags classes = contextPtr.getDeviceClasses(); std::vector> mappers; // Switch-like devices. if (classes.test(InputDeviceClass::SWITCH)) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // Scroll wheel-like devices. if (classes.test(InputDeviceClass::ROTARY_ENCODER)) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // Vibrator-like devices. if (classes.test(InputDeviceClass::VIBRATOR)) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // Battery-like devices or light-containing devices. // PeripheralController will be created with associated EventHub device. if (classes.test(InputDeviceClass::BATTERY) || classes.test(InputDeviceClass::LIGHT)) { - mController = std::make_unique(context); + mController = std::make_unique(contextPtr); } // Keyboard-like devices. @@ -483,13 +482,13 @@ std::vector> InputDevice::createMappers( } if (keyboardSource != 0) { - mappers.push_back(createInputMapper(context, readerConfig, + mappers.push_back(createInputMapper(contextPtr, readerConfig, keyboardSource, keyboardType)); } // Cursor-like devices. if (classes.test(InputDeviceClass::CURSOR)) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // Touchscreens and touchpad devices. @@ -497,31 +496,31 @@ std::vector> InputDevice::createMappers( sysprop::InputProperties::enable_touchpad_gestures_library().value_or(true); // TODO(b/272518665): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) touchpads, or // at least load this setting from the IDC file. - const InputDeviceIdentifier identifier = context.getDeviceIdentifier(); + const InputDeviceIdentifier identifier = contextPtr.getDeviceIdentifier(); const bool isSonyDualShock4Touchpad = identifier.vendor == 0x054c && (identifier.product == 0x05c4 || identifier.product == 0x09cc); if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) && classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH_MT)) { - mappers.push_back(std::make_unique(context, readerConfig)); + mappers.push_back(std::make_unique(contextPtr, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH)) { - mappers.push_back(std::make_unique(context, readerConfig)); + mappers.push_back(std::make_unique(contextPtr, readerConfig)); } // Joystick-like devices. if (classes.test(InputDeviceClass::JOYSTICK)) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // Motion sensor enabled devices. if (classes.test(InputDeviceClass::SENSOR)) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // External stylus-like devices. if (classes.test(InputDeviceClass::EXTERNAL_STYLUS)) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } return mappers; } diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 8a33dff868..ea95f7857a 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -334,9 +334,7 @@ std::shared_ptr InputReader::createDeviceLocked( device = std::make_shared(&mContext, deviceId, bumpGenerationLocked(), identifier); } - device->addEmptyEventHubDevice(eventHubId); - auto unused = device->configure(systemTime(SYSTEM_TIME_MONOTONIC), mConfig, /*changes=*/{}); - device->populateMappers(eventHubId, mConfig); + device->addEventHubDevice(eventHubId, mConfig); return device; } diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 1729d4620d..0b8a608891 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -81,7 +81,7 @@ public: void dump(std::string& dump, const std::string& eventHubDevStr); void addEmptyEventHubDevice(int32_t eventHubId); - void populateMappers(int32_t eventHubId, const InputReaderConfiguration& readerConfig); + void addEventHubDevice(int32_t eventHubId, const InputReaderConfiguration& readerConfig); void removeEventHubDevice(int32_t eventHubId); [[nodiscard]] std::list configure(nsecs_t when, const InputReaderConfiguration& readerConfig, @@ -203,7 +203,7 @@ private: int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc); std::vector> createMappers( - InputDeviceContext& context, const InputReaderConfiguration& readerConfig); + InputDeviceContext& contextPtr, const InputReaderConfiguration& readerConfig); PropertyMap mConfiguration; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index c045e15f19..bfb371f02a 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -2584,10 +2584,7 @@ TEST_F(InputDeviceTest, DumpDoesNotCrash) { mFakeEventHub->addDevice(TEST_EVENTHUB_ID, "Test EventHub device", InputDeviceClass::BATTERY); InputDevice device(mReader->getContext(), /*id=*/1, /*generation=*/2, /*identifier=*/{}); - device.addEmptyEventHubDevice(TEST_EVENTHUB_ID); - auto unused = device.configure(systemTime(SYSTEM_TIME_MONOTONIC), - mFakePolicy->getReaderConfiguration(), /*changes=*/{}); - device.populateMappers(TEST_EVENTHUB_ID, mFakePolicy->getReaderConfiguration()); + device.addEventHubDevice(TEST_EVENTHUB_ID, mFakePolicy->getReaderConfiguration()); device.removeEventHubDevice(TEST_EVENTHUB_ID); std::string dumpStr, eventHubDevStr; device.dump(dumpStr, eventHubDevStr); -- GitLab From 9464b2cc5c9789eacd0b14a3754660df28a89e7f Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Tue, 23 May 2023 11:22:04 -0500 Subject: [PATCH 1279/1310] Log out-of-order window infos updates We're not aware of any condition that would lead this to happen. This check is to help provide information in case we see future input windows syncronization issues. Bug: 279792237 Test: presubmits Change-Id: I9bdcf34c71c1a741f863feb0881cd09122c7bfb2 --- services/inputflinger/dispatcher/InputDispatcher.cpp | 7 +++++++ services/inputflinger/dispatcher/InputDispatcher.h | 2 ++ 2 files changed, 9 insertions(+) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 0cc7cfbcc8..fbbb38835a 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -6700,6 +6700,13 @@ void InputDispatcher::onWindowInfosChanged(const gui::WindowInfosUpdate& update) for (const auto& [displayId, handles] : handlesPerDisplay) { setInputWindowsLocked(handles, displayId); } + + if (update.vsyncId < mWindowInfosVsyncId) { + ALOGE("Received out of order window infos update. Last update vsync id: %" PRId64 + ", current update vsync id: %" PRId64, + mWindowInfosVsyncId, update.vsyncId); + } + mWindowInfosVsyncId = update.vsyncId; } // Wake up poll loop since it may need to make new input dispatching choices. mLooper->wake(); diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 8ca01b7a09..6b22f2f24f 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -204,6 +204,8 @@ private: const IdGenerator mIdGenerator; + int64_t mWindowInfosVsyncId GUARDED_BY(mLock); + // With each iteration, InputDispatcher nominally processes one queued event, // a timeout, or a response from an input consumer. // This method should only be called on the input dispatcher's own thread. -- GitLab From 3fd805e8e5a0b774919c5a29be3745232f42dc74 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 25 May 2023 00:12:00 +0000 Subject: [PATCH 1280/1310] Revert "InputMapper refactor: Revert "fix touch issue on portrai..." Revert submission 23316821 Reason for revert: breaks touch on some devices b/284203485 Reverted changes: /q/submissionid:23316821 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:342e1dd013c4c5f51a15222d5b112fb14583a636) Merged-In: Ic4537a645d710a900d6a3b9328bd6489baedbb1d Change-Id: Ic4537a645d710a900d6a3b9328bd6489baedbb1d --- services/inputflinger/reader/InputDevice.cpp | 4 ++-- services/inputflinger/reader/mapper/MultiTouchInputMapper.h | 4 ++-- services/inputflinger/reader/mapper/SingleTouchInputMapper.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index c6b93a51c3..90ba003846 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -504,9 +504,9 @@ std::vector> InputDevice::createMappers( classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) { mappers.push_back(createInputMapper(context, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH_MT)) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(std::make_unique(context, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH)) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(std::make_unique(context, readerConfig)); } // Joystick-like devices. diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h index 1d788dffd4..f300ee15bd 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h @@ -27,6 +27,8 @@ public: friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig, Args... args); + explicit MultiTouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); ~MultiTouchInputMapper() override; @@ -39,8 +41,6 @@ protected: bool hasStylus() const override; private: - explicit MultiTouchInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); // simulate_stylus_with_touch is a debug mode that converts all finger pointers reported by this // mapper's touchscreen into stylus pointers, and adds SOURCE_STYLUS to the input device. // It is used to simulate stylus events for debugging and testing on a device that does not diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h index 7726bfb159..dac53cf700 100644 --- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h @@ -27,6 +27,8 @@ public: friend std::unique_ptr createInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig, Args... args); + explicit SingleTouchInputMapper(InputDeviceContext& deviceContext, + const InputReaderConfiguration& readerConfig); ~SingleTouchInputMapper() override; @@ -40,8 +42,6 @@ protected: private: SingleTouchMotionAccumulator mSingleTouchMotionAccumulator; - explicit SingleTouchInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig); }; } // namespace android -- GitLab From 3e2ffbb0089f220d4900d8cfcda2a16d518c7cc3 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 25 May 2023 00:12:00 +0000 Subject: [PATCH 1281/1310] Revert "InputMapper refactor: Configure empty InputDevice" Revert submission 23316821 Reason for revert: breaks touch on some devices b/284203485 Reverted changes: /q/submissionid:23316821 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c96ed759fb428c7b982c921611e5da05040e3cea) Merged-In: Id957e51b53e48b874302b24b612554f3024aa87b Change-Id: Id957e51b53e48b874302b24b612554f3024aa87b --- services/inputflinger/reader/InputDevice.cpp | 61 +++++++++---------- services/inputflinger/reader/InputReader.cpp | 4 +- .../inputflinger/reader/include/InputDevice.h | 4 +- .../inputflinger/tests/InputReader_test.cpp | 5 +- 4 files changed, 34 insertions(+), 40 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 90ba003846..0a64a1c4a8 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -151,22 +151,21 @@ void InputDevice::addEmptyEventHubDevice(int32_t eventHubId) { return; } std::unique_ptr contextPtr(new InputDeviceContext(*this, eventHubId)); - mDevices.insert( - {eventHubId, - std::make_pair, - std::vector>>(std::move(contextPtr), {})}); -} - -void InputDevice::populateMappers(int32_t eventHubId, - const InputReaderConfiguration& readerConfig) { - auto targetDevice = mDevices.find(eventHubId); - LOG_ALWAYS_FATAL_IF(targetDevice == mDevices.end(), - "InputDevice::populateMappers(): missing device with eventHubId %d ", - eventHubId); - // create and add mappers to device - InputDeviceContext& context = *targetDevice->second.first; - std::vector> mappers = createMappers(context, readerConfig); - targetDevice->second.second = std::move(mappers); + std::vector> mappers; + + mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))}); +} + +void InputDevice::addEventHubDevice(int32_t eventHubId, + const InputReaderConfiguration& readerConfig) { + if (mDevices.find(eventHubId) != mDevices.end()) { + return; + } + std::unique_ptr contextPtr(new InputDeviceContext(*this, eventHubId)); + std::vector> mappers = createMappers(*contextPtr, readerConfig); + + // insert the context into the devices set + mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))}); // Must change generation to flag this device as changed bumpGeneration(); } @@ -441,29 +440,29 @@ int32_t InputDevice::getState(uint32_t sourceMask, int32_t code, GetStateFunc ge } std::vector> InputDevice::createMappers( - InputDeviceContext& context, const InputReaderConfiguration& readerConfig) { - ftl::Flags classes = context.getDeviceClasses(); + InputDeviceContext& contextPtr, const InputReaderConfiguration& readerConfig) { + ftl::Flags classes = contextPtr.getDeviceClasses(); std::vector> mappers; // Switch-like devices. if (classes.test(InputDeviceClass::SWITCH)) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // Scroll wheel-like devices. if (classes.test(InputDeviceClass::ROTARY_ENCODER)) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // Vibrator-like devices. if (classes.test(InputDeviceClass::VIBRATOR)) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // Battery-like devices or light-containing devices. // PeripheralController will be created with associated EventHub device. if (classes.test(InputDeviceClass::BATTERY) || classes.test(InputDeviceClass::LIGHT)) { - mController = std::make_unique(context); + mController = std::make_unique(contextPtr); } // Keyboard-like devices. @@ -483,13 +482,13 @@ std::vector> InputDevice::createMappers( } if (keyboardSource != 0) { - mappers.push_back(createInputMapper(context, readerConfig, + mappers.push_back(createInputMapper(contextPtr, readerConfig, keyboardSource, keyboardType)); } // Cursor-like devices. if (classes.test(InputDeviceClass::CURSOR)) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // Touchscreens and touchpad devices. @@ -497,31 +496,31 @@ std::vector> InputDevice::createMappers( sysprop::InputProperties::enable_touchpad_gestures_library().value_or(true); // TODO(b/272518665): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) touchpads, or // at least load this setting from the IDC file. - const InputDeviceIdentifier identifier = context.getDeviceIdentifier(); + const InputDeviceIdentifier identifier = contextPtr.getDeviceIdentifier(); const bool isSonyDualShock4Touchpad = identifier.vendor == 0x054c && (identifier.product == 0x05c4 || identifier.product == 0x09cc); if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) && classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH_MT)) { - mappers.push_back(std::make_unique(context, readerConfig)); + mappers.push_back(std::make_unique(contextPtr, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH)) { - mappers.push_back(std::make_unique(context, readerConfig)); + mappers.push_back(std::make_unique(contextPtr, readerConfig)); } // Joystick-like devices. if (classes.test(InputDeviceClass::JOYSTICK)) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // Motion sensor enabled devices. if (classes.test(InputDeviceClass::SENSOR)) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } // External stylus-like devices. if (classes.test(InputDeviceClass::EXTERNAL_STYLUS)) { - mappers.push_back(createInputMapper(context, readerConfig)); + mappers.push_back(createInputMapper(contextPtr, readerConfig)); } return mappers; } diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 8a33dff868..ea95f7857a 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -334,9 +334,7 @@ std::shared_ptr InputReader::createDeviceLocked( device = std::make_shared(&mContext, deviceId, bumpGenerationLocked(), identifier); } - device->addEmptyEventHubDevice(eventHubId); - auto unused = device->configure(systemTime(SYSTEM_TIME_MONOTONIC), mConfig, /*changes=*/{}); - device->populateMappers(eventHubId, mConfig); + device->addEventHubDevice(eventHubId, mConfig); return device; } diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 1729d4620d..0b8a608891 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -81,7 +81,7 @@ public: void dump(std::string& dump, const std::string& eventHubDevStr); void addEmptyEventHubDevice(int32_t eventHubId); - void populateMappers(int32_t eventHubId, const InputReaderConfiguration& readerConfig); + void addEventHubDevice(int32_t eventHubId, const InputReaderConfiguration& readerConfig); void removeEventHubDevice(int32_t eventHubId); [[nodiscard]] std::list configure(nsecs_t when, const InputReaderConfiguration& readerConfig, @@ -203,7 +203,7 @@ private: int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc); std::vector> createMappers( - InputDeviceContext& context, const InputReaderConfiguration& readerConfig); + InputDeviceContext& contextPtr, const InputReaderConfiguration& readerConfig); PropertyMap mConfiguration; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index c045e15f19..bfb371f02a 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -2584,10 +2584,7 @@ TEST_F(InputDeviceTest, DumpDoesNotCrash) { mFakeEventHub->addDevice(TEST_EVENTHUB_ID, "Test EventHub device", InputDeviceClass::BATTERY); InputDevice device(mReader->getContext(), /*id=*/1, /*generation=*/2, /*identifier=*/{}); - device.addEmptyEventHubDevice(TEST_EVENTHUB_ID); - auto unused = device.configure(systemTime(SYSTEM_TIME_MONOTONIC), - mFakePolicy->getReaderConfiguration(), /*changes=*/{}); - device.populateMappers(TEST_EVENTHUB_ID, mFakePolicy->getReaderConfiguration()); + device.addEventHubDevice(TEST_EVENTHUB_ID, mFakePolicy->getReaderConfiguration()); device.removeEventHubDevice(TEST_EVENTHUB_ID); std::string dumpStr, eventHubDevStr; device.dump(dumpStr, eventHubDevStr); -- GitLab From 81ca6f8cc7c2a45322fbabf0ce98fd2ede92882e Mon Sep 17 00:00:00 2001 From: Ram Mohan Date: Mon, 24 Apr 2023 21:31:21 +0530 Subject: [PATCH 1282/1310] ultrahdr: Add fuzz application for encode API Bug: 282640328 Test: ./ultrahdr_enc_fuzzer Change-Id: I85f7515b599604cf80ed0e8eecc1783bfcaa04eb --- libs/ultrahdr/fuzzer/Android.bp | 65 +++++ libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp | 286 +++++++++++++++++++ 2 files changed, 351 insertions(+) create mode 100644 libs/ultrahdr/fuzzer/Android.bp create mode 100644 libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp diff --git a/libs/ultrahdr/fuzzer/Android.bp b/libs/ultrahdr/fuzzer/Android.bp new file mode 100644 index 0000000000..27b38c3590 --- /dev/null +++ b/libs/ultrahdr/fuzzer/Android.bp @@ -0,0 +1,65 @@ +// Copyright 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_native_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_native_license"], +} + +cc_defaults { + name: "ultrahdr_fuzzer_defaults", + host_supported: true, + static_libs: ["liblog"], + target: { + darwin: { + enabled: false, + }, + }, + fuzz_config: { + cc: [ + "android-media-fuzzing-reports@google.com", + ], + description: "The fuzzers target the APIs of jpeg hdr", + service_privilege: "constrained", + users: "multi_user", + }, +} + +cc_fuzz { + name: "ultrahdr_enc_fuzzer", + defaults: ["ultrahdr_fuzzer_defaults"], + srcs: [ + "ultrahdr_enc_fuzzer.cpp", + ], + shared_libs: [ + "libimage_io", + "libjpeg", + "liblog", + ], + static_libs: [ + "libjpegdecoder", + "libjpegencoder", + "libultrahdr", + "libutils", + ], + fuzz_config: { + fuzzed_code_usage: "future_version", + vector: "local_no_privileges_required", + }, +} + diff --git a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp new file mode 100644 index 0000000000..472699bb9e --- /dev/null +++ b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp @@ -0,0 +1,286 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// System include files +#include +#include +#include +#include +#include + +// User include files +#include "ultrahdr/gainmapmath.h" +#include "ultrahdr/jpegencoderhelper.h" +#include "utils/Log.h" + +using namespace android::ultrahdr; + +// constants +const int kMinWidth = 8; +const int kMaxWidth = 7680; + +const int kMinHeight = 8; +const int kMaxHeight = 4320; + +const int kScaleFactor = 4; + +const int kJpegBlock = 16; + +// Color gamuts for image data, sync with ultrahdr.h +const int kCgMin = ULTRAHDR_COLORGAMUT_UNSPECIFIED + 1; +const int kCgMax = ULTRAHDR_COLORGAMUT_MAX; + +// Transfer functions for image data, sync with ultrahdr.h +const int kTfMin = ULTRAHDR_TF_UNSPECIFIED + 1; +const int kTfMax = ULTRAHDR_TF_MAX; + +// Transfer functions for image data, sync with ultrahdr.h +const int kOfMin = ULTRAHDR_OUTPUT_UNSPECIFIED + 1; +const int kOfMax = ULTRAHDR_OUTPUT_MAX; + +// quality factor +const int kQfMin = 0; +const int kQfMax = 100; + +// seed +const unsigned kSeed = 0x7ab7; + +class JpegHDRFuzzer { +public: + JpegHDRFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; + void process(); + void fillP010Buffer(uint16_t* data, int width, int height, int stride); + void fill420Buffer(uint8_t* data, int size); + +private: + FuzzedDataProvider mFdp; +}; + +void JpegHDRFuzzer::fillP010Buffer(uint16_t* data, int width, int height, int stride) { + uint16_t* tmp = data; + std::vector buffer(16); + for (int i = 0; i < buffer.size(); i++) { + buffer[i] = mFdp.ConsumeIntegralInRange(0, (1 << 10) - 1); + } + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i += buffer.size()) { + memcpy(data + i, buffer.data(), std::min((int)buffer.size(), (width - i))); + std::shuffle(buffer.begin(), buffer.end(), std::default_random_engine(kSeed)); + } + tmp += stride; + } +} + +void JpegHDRFuzzer::fill420Buffer(uint8_t* data, int size) { + std::vector buffer(16); + mFdp.ConsumeData(buffer.data(), buffer.size()); + for (int i = 0; i < size; i += buffer.size()) { + memcpy(data + i, buffer.data(), std::min((int)buffer.size(), (size - i))); + std::shuffle(buffer.begin(), buffer.end(), std::default_random_engine(kSeed)); + } +} + +void JpegHDRFuzzer::process() { + while (mFdp.remaining_bytes()) { + struct jpegr_uncompressed_struct p010Img {}; + struct jpegr_uncompressed_struct yuv420Img {}; + struct jpegr_uncompressed_struct grayImg {}; + struct jpegr_compressed_struct jpegImgR {}; + struct jpegr_compressed_struct jpegImg {}; + struct jpegr_compressed_struct jpegGainMap {}; + + // which encode api to select + int muxSwitch = mFdp.ConsumeIntegralInRange(0, 4); + + // quality factor + int quality = mFdp.ConsumeIntegralInRange(kQfMin, kQfMax); + + // hdr_tf + auto tf = static_cast( + mFdp.ConsumeIntegralInRange(kTfMin, kTfMax)); + + // p010 Cg + auto p010Cg = + static_cast(mFdp.ConsumeIntegralInRange(kCgMin, kCgMax)); + + // 420 Cg + auto yuv420Cg = + static_cast(mFdp.ConsumeIntegralInRange(kCgMin, kCgMax)); + + // hdr_of + auto of = static_cast( + mFdp.ConsumeIntegralInRange(kOfMin, kOfMax)); + + int width = mFdp.ConsumeIntegralInRange(kMinWidth, kMaxWidth); + width = (width >> 1) << 1; + + int height = mFdp.ConsumeIntegralInRange(kMinHeight, kMaxHeight); + height = (height >> 1) << 1; + + std::unique_ptr bufferY = nullptr; + std::unique_ptr bufferUV = nullptr; + std::unique_ptr yuv420ImgRaw = nullptr; + std::unique_ptr grayImgRaw = nullptr; + if (muxSwitch != 4) { + // init p010 image + bool isUVContiguous = mFdp.ConsumeBool(); + bool hasYStride = mFdp.ConsumeBool(); + int yStride = hasYStride ? mFdp.ConsumeIntegralInRange(width, width + 128) : width; + p010Img.width = width; + p010Img.height = height; + p010Img.colorGamut = p010Cg; + p010Img.luma_stride = hasYStride ? yStride : 0; + int bppP010 = 2; + if (isUVContiguous) { + size_t p010Size = yStride * height * 3 / 2; + bufferY = std::make_unique(p010Size); + p010Img.data = bufferY.get(); + p010Img.chroma_data = nullptr; + p010Img.chroma_stride = 0; + fillP010Buffer(bufferY.get(), width, height, yStride); + fillP010Buffer(bufferY.get() + yStride * height, width, height / 2, yStride); + } else { + int uvStride = mFdp.ConsumeIntegralInRange(width, width + 128); + size_t p010YSize = yStride * height; + bufferY = std::make_unique(p010YSize); + p010Img.data = bufferY.get(); + fillP010Buffer(bufferY.get(), width, height, yStride); + size_t p010UVSize = uvStride * p010Img.height / 2; + bufferUV = std::make_unique(p010UVSize); + p010Img.chroma_data = bufferUV.get(); + p010Img.chroma_stride = uvStride; + fillP010Buffer(bufferUV.get(), width, height / 2, uvStride); + } + } else { + int map_width = width / kScaleFactor; + int map_height = height / kScaleFactor; + map_width = static_cast(floor((map_width + kJpegBlock - 1) / kJpegBlock)) * + kJpegBlock; + map_height = ((map_height + 1) >> 1) << 1; + // init 400 image + grayImg.width = map_width; + grayImg.height = map_height; + grayImg.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; + + const size_t graySize = map_width * map_height; + grayImgRaw = std::make_unique(graySize); + grayImg.data = grayImgRaw.get(); + fill420Buffer(grayImgRaw.get(), graySize); + grayImg.chroma_data = nullptr; + grayImg.luma_stride = 0; + grayImg.chroma_stride = 0; + } + + if (muxSwitch > 0) { + // init 420 image + yuv420Img.width = width; + yuv420Img.height = height; + yuv420Img.colorGamut = yuv420Cg; + + const size_t yuv420Size = (yuv420Img.width * yuv420Img.height * 3) / 2; + yuv420ImgRaw = std::make_unique(yuv420Size); + yuv420Img.data = yuv420ImgRaw.get(); + fill420Buffer(yuv420ImgRaw.get(), yuv420Size); + yuv420Img.chroma_data = nullptr; + yuv420Img.luma_stride = 0; + yuv420Img.chroma_stride = 0; + } + + // dest + // 2 * p010 size as input data is random, DCT compression might not behave as expected + jpegImgR.maxLength = std::max(8 * 1024 /* min size 8kb */, width * height * 3 * 2); + auto jpegImgRaw = std::make_unique(jpegImgR.maxLength); + jpegImgR.data = jpegImgRaw.get(); + +//#define DUMP_PARAM +#ifdef DUMP_PARAM + std::cout << "Api Select " << muxSwitch << std::endl; + std::cout << "image dimensions " << width << " x " << height << std::endl; + std::cout << "p010 color gamut " << p010Img.colorGamut << std::endl; + std::cout << "p010 luma stride " << p010Img.luma_stride << std::endl; + std::cout << "p010 chroma stride " << p010Img.chroma_stride << std::endl; + std::cout << "420 color gamut " << yuv420Img.colorGamut << std::endl; + std::cout << "quality factor " << quality << std::endl; +#endif + + JpegR jpegHdr; + android::status_t status = android::UNKNOWN_ERROR; + if (muxSwitch == 0) { // api 0 + jpegImgR.length = 0; + status = jpegHdr.encodeJPEGR(&p010Img, tf, &jpegImgR, quality, nullptr); + } else if (muxSwitch == 1) { // api 1 + jpegImgR.length = 0; + status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, tf, &jpegImgR, quality, nullptr); + } else { + // compressed img + JpegEncoderHelper encoder; + if (encoder.compressImage(yuv420Img.data, yuv420Img.width, yuv420Img.height, quality, + nullptr, 0)) { + jpegImg.length = encoder.getCompressedImageSize(); + jpegImg.maxLength = jpegImg.length; + jpegImg.data = encoder.getCompressedImagePtr(); + jpegImg.colorGamut = yuv420Cg; + + if (muxSwitch == 2) { // api 2 + jpegImgR.length = 0; + status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, &jpegImg, tf, &jpegImgR); + } else if (muxSwitch == 3) { // api 3 + jpegImgR.length = 0; + status = jpegHdr.encodeJPEGR(&p010Img, &jpegImg, tf, &jpegImgR); + } else if (muxSwitch == 4) { // api 4 + jpegImgR.length = 0; + JpegEncoderHelper gainMapEncoder; + if (gainMapEncoder.compressImage(grayImg.data, grayImg.width, grayImg.height, + quality, nullptr, 0, true)) { + jpegGainMap.length = gainMapEncoder.getCompressedImageSize(); + jpegGainMap.maxLength = jpegImg.length; + jpegGainMap.data = gainMapEncoder.getCompressedImagePtr(); + jpegGainMap.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; + ultrahdr_metadata_struct metadata; + metadata.version = "1.3.1"; + if (tf == ULTRAHDR_TF_HLG) { + metadata.maxContentBoost = kHlgMaxNits / kSdrWhiteNits; + } else if (tf == ULTRAHDR_TF_PQ) { + metadata.maxContentBoost = kPqMaxNits / kSdrWhiteNits; + } else { + metadata.maxContentBoost = 0; + } + metadata.minContentBoost = 1.0f; + status = jpegHdr.encodeJPEGR(&jpegImg, &jpegGainMap, &metadata, &jpegImgR); + } + } + } + } + if (status == android::OK) { + jpegr_uncompressed_struct decodedJpegR; + auto decodedRaw = std::make_unique(width * height * 8); + decodedJpegR.data = decodedRaw.get(); + jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR, + mFdp.ConsumeFloatingPointInRange(1.0, FLT_MAX), nullptr, of, + nullptr, nullptr); + std::vector iccData(0); + std::vector exifData(0); + jpegr_info_struct info{0, 0, &iccData, &exifData}; + jpegHdr.getJPEGRInfo(&jpegImgR, &info); + } + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + JpegHDRFuzzer fuzzHandle(data, size); + fuzzHandle.process(); + return 0; +} -- GitLab From 9b3d6859c8a19dcf478d66ff60436c423a77b8a8 Mon Sep 17 00:00:00 2001 From: Ram Mohan Date: Fri, 26 May 2023 00:09:50 +0530 Subject: [PATCH 1283/1310] ultrahdr: update error checks in encode/decode path 1. library assumes the gain map resolution is 1/kMapDimensionScaleFactor of primary image resolution. If it is not the case the output hdr image computations are not according to the scale factor. Signal error if map scale factor is not kMapDimensionScaleFactor. 2. library expects primary image to be 420 and gain map image to be 400. Validate the same. 3. disallow srgb transfer function way early as this is not handled. 4. avoid nullptr access in metadata. Bug: 284120011 Test: ./ultrahdr_enc_fuzzer Change-Id: Ie2cd12ac95306958cbb18679039681fc4eb3d9cc --- libs/ultrahdr/include/ultrahdr/ultrahdr.h | 4 +-- libs/ultrahdr/jpegr.cpp | 33 ++++++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/libs/ultrahdr/include/ultrahdr/ultrahdr.h b/libs/ultrahdr/include/ultrahdr/ultrahdr.h index d6153e9890..21751b4634 100644 --- a/libs/ultrahdr/include/ultrahdr/ultrahdr.h +++ b/libs/ultrahdr/include/ultrahdr/ultrahdr.h @@ -20,7 +20,7 @@ namespace android::ultrahdr { // Color gamuts for image data typedef enum { - ULTRAHDR_COLORGAMUT_UNSPECIFIED, + ULTRAHDR_COLORGAMUT_UNSPECIFIED = -1, ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT2100, @@ -52,7 +52,7 @@ typedef enum { */ struct ultrahdr_metadata_struct { // Ultra HDR library version - const char* version; + std::string version; // Max Content Boost for the map float maxContentBoost; // Min Content Boost for the map diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp index c250aa02fd..366706ac53 100644 --- a/libs/ultrahdr/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -145,7 +145,8 @@ status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_ima return ERROR_JPEGR_INVALID_NULL_PTR; } - if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX) { + if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX + || hdr_tf == ULTRAHDR_TF_SRGB) { ALOGE("Invalid hdr transfer function %d", hdr_tf); return ERROR_JPEGR_INVALID_INPUT_TYPE; } @@ -509,11 +510,6 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, return ERROR_JPEGR_INVALID_INPUT_TYPE; } - if (gain_map != nullptr && gain_map->data == nullptr) { - ALOGE("received nullptr address for gain map data"); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - if (output_format == ULTRAHDR_OUTPUT_SDR) { JpegDecoderHelper jpeg_decoder; if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length, @@ -555,6 +551,11 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, if (!gain_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) { return ERROR_JPEGR_DECODE_ERROR; } + if ((gain_map_decoder.getDecompressedImageWidth() * + gain_map_decoder.getDecompressedImageHeight()) > + gain_map_decoder.getDecompressedImageSize()) { + return ERROR_JPEGR_CALCULATION_ERROR; + } if (gain_map != nullptr) { gain_map->width = gain_map_decoder.getDecompressedImageWidth(); @@ -584,6 +585,11 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) { return ERROR_JPEGR_DECODE_ERROR; } + if ((jpeg_decoder.getDecompressedImageWidth() * + jpeg_decoder.getDecompressedImageHeight() * 3 / 2) > + jpeg_decoder.getDecompressedImageSize()) { + return ERROR_JPEGR_CALCULATION_ERROR; + } if (exif != nullptr) { if (exif->data == nullptr) { @@ -605,7 +611,6 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr(); uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth(); uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight(); - JPEGR_CHECK(applyGainMap(&uncompressed_yuv_420_image, &map, &uhdr_metadata, output_format, max_display_boost, dest)); return NO_ERROR; @@ -848,6 +853,20 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, return ERROR_JPEGR_INVALID_NULL_PTR; } + // TODO: remove once map scaling factor is computed based on actual map dims + size_t image_width = uncompressed_yuv_420_image->width; + size_t image_height = uncompressed_yuv_420_image->height; + size_t map_width = image_width / kMapDimensionScaleFactor; + size_t map_height = image_height / kMapDimensionScaleFactor; + map_width = static_cast( + floor((map_width + kJpegBlock - 1) / kJpegBlock)) * kJpegBlock; + map_height = ((map_height + 1) >> 1) << 1; + if (map_width != uncompressed_gain_map->width + || map_height != uncompressed_gain_map->height) { + ALOGE("gain map dimensions and primary image dimensions are not to scale"); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + dest->width = uncompressed_yuv_420_image->width; dest->height = uncompressed_yuv_420_image->height; ShepardsIDW idwTable(kMapDimensionScaleFactor); -- GitLab From e905ef7f27e0d97f586ee7f3e03b9ac3f8105f9f Mon Sep 17 00:00:00 2001 From: Serdar Kocdemir Date: Tue, 30 May 2023 14:43:29 +0000 Subject: [PATCH 1284/1310] Add dEQP level 2023 prebuilts Bug: b/284056586 Test: manual Change-Id: Ieb2ef32789237dc9e426a26a67f319feeb028548 --- data/etc/Android.bp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/data/etc/Android.bp b/data/etc/Android.bp index 754e7b2ac0..226cae12aa 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -328,6 +328,12 @@ prebuilt_etc { defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.software.opengles.deqp.level-2023-03-01.prebuilt.xml", + src: "android.software.opengles.deqp.level-2023-03-01.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "android.software.sip.voip.prebuilt.xml", src: "android.software.sip.voip.xml", @@ -352,6 +358,12 @@ prebuilt_etc { defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.software.vulkan.deqp.level-2023-03-01.prebuilt.xml", + src: "android.software.vulkan.deqp.level-2023-03-01.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "aosp_excluded_hardware.prebuilt.xml", src: "aosp_excluded_hardware.xml", -- GitLab From 676e4390c3dc26910edfb6c6a3c52ffe4b898bf3 Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Thu, 25 May 2023 22:09:26 +0000 Subject: [PATCH 1285/1310] Reduce number of binders from SurfaceFlinger for ADPF Currently, SF is using the wrong vsync value when calculating the target for ADPF, causing it to send a binder every frame instead of every vsync change. This patch fixes that to reduce the binder frequency from SF, and also adds a debug option to disable reportActual to reduce the amount of binders even more in certain cases. Bug: 284357218 Test: manual Change-Id: I8435b170016c5af8219d8ed8cc9ed65a0a2cb0e7 --- services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp | 5 ++++- services/surfaceflinger/DisplayHardware/PowerAdvisor.h | 3 +++ services/surfaceflinger/SurfaceFlinger.cpp | 5 ++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp index 37b68c865e..f8b466c93c 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp @@ -223,7 +223,7 @@ void PowerAdvisor::updateTargetWorkDuration(Duration targetDuration) { } void PowerAdvisor::reportActualWorkDuration() { - if (!mBootFinished || !usePowerHintSession()) { + if (!mBootFinished || !sUseReportActualDuration || !usePowerHintSession()) { ALOGV("Actual work duration power hint cannot be sent, skipping"); return; } @@ -564,6 +564,9 @@ const Duration PowerAdvisor::sTargetSafetyMargin = std::chrono::microseconds( base::GetIntProperty("debug.sf.hint_margin_us", ticks(PowerAdvisor::kDefaultTargetSafetyMargin))); +const bool PowerAdvisor::sUseReportActualDuration = + base::GetBoolProperty(std::string("debug.adpf.use_report_actual_duration"), true); + power::PowerHalController& PowerAdvisor::getPowerHal() { static std::once_flag halFlag; std::call_once(halFlag, [this] { mPowerHal->init(); }); diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h index 7a0d4267fe..f0d3fd8518 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h @@ -269,6 +269,9 @@ private: static const Duration sTargetSafetyMargin; static constexpr const Duration kDefaultTargetSafetyMargin{1ms}; + // Whether we should send reportActualWorkDuration calls + static const bool sUseReportActualDuration; + // How long we expect hwc to run after the present call until it waits for the fence static constexpr const Duration kFenceWaitStartDelayValidated{150us}; static constexpr const Duration kFenceWaitStartDelaySkippedValidate{250us}; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 5d96fc45be..bbfed8a823 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2485,7 +2485,10 @@ bool SurfaceFlinger::commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expe mPowerAdvisor->setFrameDelay(frameDelay); mPowerAdvisor->setTotalFrameTargetWorkDuration(idealSfWorkDuration); - mPowerAdvisor->updateTargetWorkDuration(vsyncPeriod); + + const auto& display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get(); + const Period idealVsyncPeriod = display->getActiveMode().fps.getPeriod(); + mPowerAdvisor->updateTargetWorkDuration(idealVsyncPeriod); } if (mRefreshRateOverlaySpinner) { -- GitLab From 4a72c19f8861c59f23bd075ac665fe49e3dd0a40 Mon Sep 17 00:00:00 2001 From: Ram Mohan Date: Mon, 22 May 2023 19:05:06 +0530 Subject: [PATCH 1286/1310] ultrahdr: compress image reads outside bounds for non aligned widths jpeg_write_raw_data() processes one MCU row per call, and thus one must pass buffer of at least max_v_samp_factor * DCTSIZE scanlines. The buffer must be large enough to hold the actual data plus padding to DCT-block boundaries. Bug: 284117683 Test: ./ultrahdr_enc_fuzzer Change-Id: I993773817bf3805463bb21bb977624d6c2d45a0b --- libs/ultrahdr/jpegencoderhelper.cpp | 67 ++++++++++++++++++- libs/ultrahdr/jpegr.cpp | 13 ++-- .../ultrahdr/tests/jpegencoderhelper_test.cpp | 11 +-- 3 files changed, 69 insertions(+), 22 deletions(-) diff --git a/libs/ultrahdr/jpegencoderhelper.cpp b/libs/ultrahdr/jpegencoderhelper.cpp index 10a763035f..ab2f8c7b5a 100644 --- a/libs/ultrahdr/jpegencoderhelper.cpp +++ b/libs/ultrahdr/jpegencoderhelper.cpp @@ -22,6 +22,8 @@ namespace android::ultrahdr { +#define ALIGNM(x, m) ((((x) + ((m) - 1)) / (m)) * (m)) + // The destination manager that can access |mResultBuffer| in JpegEncoderHelper. struct destination_mgr { public: @@ -175,6 +177,37 @@ bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* std::unique_ptr empty(new uint8_t[cinfo->image_width]); memset(empty.get(), 0, cinfo->image_width); + const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); + const bool is_width_aligned = (aligned_width == cinfo->image_width); + std::unique_ptr buffer_intrm = nullptr; + uint8_t* y_plane_intrm = nullptr; + uint8_t* u_plane_intrm = nullptr; + uint8_t* v_plane_intrm = nullptr; + JSAMPROW y_intrm[kCompressBatchSize]; + JSAMPROW cb_intrm[kCompressBatchSize / 2]; + JSAMPROW cr_intrm[kCompressBatchSize / 2]; + JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm}; + if (!is_width_aligned) { + size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2; + buffer_intrm = std::make_unique(mcu_row_size); + y_plane_intrm = buffer_intrm.get(); + u_plane_intrm = y_plane_intrm + (aligned_width * kCompressBatchSize); + v_plane_intrm = u_plane_intrm + (aligned_width * kCompressBatchSize) / 4; + for (int i = 0; i < kCompressBatchSize; ++i) { + y_intrm[i] = y_plane_intrm + i * aligned_width; + memset(y_intrm[i] + cinfo->image_width, 0, aligned_width - cinfo->image_width); + } + for (int i = 0; i < kCompressBatchSize / 2; ++i) { + int offset_intrm = i * (aligned_width / 2); + cb_intrm[i] = u_plane_intrm + offset_intrm; + cr_intrm[i] = v_plane_intrm + offset_intrm; + memset(cb_intrm[i] + cinfo->image_width / 2, 0, + (aligned_width - cinfo->image_width) / 2); + memset(cr_intrm[i] + cinfo->image_width / 2, 0, + (aligned_width - cinfo->image_width) / 2); + } + } + while (cinfo->next_scanline < cinfo->image_height) { for (int i = 0; i < kCompressBatchSize; ++i) { size_t scanline = cinfo->next_scanline + i; @@ -183,6 +216,9 @@ bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* } else { y[i] = empty.get(); } + if (!is_width_aligned) { + memcpy(y_intrm[i], y[i], cinfo->image_width); + } } // cb, cr only have half scanlines for (int i = 0; i < kCompressBatchSize / 2; ++i) { @@ -194,9 +230,13 @@ bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* } else { cb[i] = cr[i] = empty.get(); } + if (!is_width_aligned) { + memcpy(cb_intrm[i], cb[i], cinfo->image_width / 2); + memcpy(cr_intrm[i], cr[i], cinfo->image_width / 2); + } } - - int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize); + int processed = jpeg_write_raw_data(cinfo, is_width_aligned ? planes : planes_intrm, + kCompressBatchSize); if (processed != kCompressBatchSize) { ALOGE("Number of processed lines does not equal input lines."); return false; @@ -213,6 +253,23 @@ bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const std::unique_ptr empty(new uint8_t[cinfo->image_width]); memset(empty.get(), 0, cinfo->image_width); + const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); + bool is_width_aligned = (aligned_width == cinfo->image_width); + std::unique_ptr buffer_intrm = nullptr; + uint8_t* y_plane_intrm = nullptr; + uint8_t* u_plane_intrm = nullptr; + JSAMPROW y_intrm[kCompressBatchSize]; + JSAMPARRAY planes_intrm[]{y_intrm}; + if (!is_width_aligned) { + size_t mcu_row_size = aligned_width * kCompressBatchSize; + buffer_intrm = std::make_unique(mcu_row_size); + y_plane_intrm = buffer_intrm.get(); + for (int i = 0; i < kCompressBatchSize; ++i) { + y_intrm[i] = y_plane_intrm + i * aligned_width; + memset(y_intrm[i] + cinfo->image_width, 0, aligned_width - cinfo->image_width); + } + } + while (cinfo->next_scanline < cinfo->image_height) { for (int i = 0; i < kCompressBatchSize; ++i) { size_t scanline = cinfo->next_scanline + i; @@ -221,8 +278,12 @@ bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const } else { y[i] = empty.get(); } + if (!is_width_aligned) { + memcpy(y_intrm[i], y[i], cinfo->image_width); + } } - int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize); + int processed = jpeg_write_raw_data(cinfo, is_width_aligned ? planes : planes_intrm, + kCompressBatchSize); if (processed != kCompressBatchSize / 2) { ALOGE("Number of processed lines does not equal input lines."); return false; diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp index c250aa02fd..9aadb74ca6 100644 --- a/libs/ultrahdr/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -76,9 +76,9 @@ static const int kMinHeight = 2 * kMapDimensionScaleFactor; // JPEG encoding / decoding will require block based DCT transform 16 x 16 for luma, // and 8 x 8 for chroma. // Width must be 16 dividable for luma, and 8 dividable for chroma. -// If this criteria is not ficilitated, we will pad zeros based on the required block size. +// If this criteria is not facilitated, we will pad zeros based to each line on the +// required block size. static const size_t kJpegBlock = JpegEncoderHelper::kCompressBatchSize; -static const size_t kJpegBlockSquare = kJpegBlock * kJpegBlock; // JPEG compress quality (0 ~ 100) for gain map static const int kMapCompressQuality = 85; @@ -228,13 +228,8 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, metadata.version = kJpegrVersion; jpegr_uncompressed_struct uncompressed_yuv_420_image; - size_t gain_map_length = uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2; - // Pad a pseudo chroma block (kJpegBlock / 2) x (kJpegBlock / 2) - // if width is not kJpegBlock aligned. - if (uncompressed_p010_image->width % kJpegBlock != 0) { - gain_map_length += kJpegBlockSquare / 4; - } - unique_ptr uncompressed_yuv_420_image_data = make_unique(gain_map_length); + unique_ptr uncompressed_yuv_420_image_data = make_unique( + uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2); uncompressed_yuv_420_image.data = uncompressed_yuv_420_image_data.get(); JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image)); diff --git a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp index 8f18ac0004..f0e1fa4968 100644 --- a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp +++ b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp @@ -108,18 +108,9 @@ TEST_F(JpegEncoderHelperTest, encodeAlignedImage) { ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); } -// The width of the "unaligned" image is not 16-aligned, and will fail if encoded directly. -// Should pass with the padding zero method. TEST_F(JpegEncoderHelperTest, encodeUnalignedImage) { JpegEncoderHelper encoder; - const size_t paddingZeroLength = JpegEncoderHelper::kCompressBatchSize - * JpegEncoderHelper::kCompressBatchSize / 4; - std::unique_ptr imageWithPaddingZeros( - new uint8_t[UNALIGNED_IMAGE_WIDTH * UNALIGNED_IMAGE_HEIGHT * 3 / 2 - + paddingZeroLength]); - memcpy(imageWithPaddingZeros.get(), mUnalignedImage.buffer.get(), - UNALIGNED_IMAGE_WIDTH * UNALIGNED_IMAGE_HEIGHT * 3 / 2); - EXPECT_TRUE(encoder.compressImage(imageWithPaddingZeros.get(), mUnalignedImage.width, + EXPECT_TRUE(encoder.compressImage(mUnalignedImage.buffer.get(), mUnalignedImage.width, mUnalignedImage.height, JPEG_QUALITY, NULL, 0)); ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); } -- GitLab From b8b235f6a04facd7b95d7d0ae8fdebe9d8454ff7 Mon Sep 17 00:00:00 2001 From: Ram Mohan Date: Mon, 24 Apr 2023 21:31:21 +0530 Subject: [PATCH 1287/1310] ultrahdr: Update fuzz application for encode API Bug: 282640328 Test: ./ultrahdr_enc_fuzzer Change-Id: I0a547f63da570433dc54eb23fbd41021022aec4e --- libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp | 29 ++++++++++++++------ 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp index 472699bb9e..7faa1570f0 100644 --- a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp +++ b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp @@ -45,7 +45,7 @@ const int kCgMax = ULTRAHDR_COLORGAMUT_MAX; // Transfer functions for image data, sync with ultrahdr.h const int kTfMin = ULTRAHDR_TF_UNSPECIFIED + 1; -const int kTfMax = ULTRAHDR_TF_MAX; +const int kTfMax = ULTRAHDR_TF_PQ; // Transfer functions for image data, sync with ultrahdr.h const int kOfMin = ULTRAHDR_OUTPUT_UNSPECIFIED + 1; @@ -265,16 +265,29 @@ void JpegHDRFuzzer::process() { } } if (status == android::OK) { - jpegr_uncompressed_struct decodedJpegR; - auto decodedRaw = std::make_unique(width * height * 8); - decodedJpegR.data = decodedRaw.get(); - jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR, - mFdp.ConsumeFloatingPointInRange(1.0, FLT_MAX), nullptr, of, - nullptr, nullptr); std::vector iccData(0); std::vector exifData(0); jpegr_info_struct info{0, 0, &iccData, &exifData}; - jpegHdr.getJPEGRInfo(&jpegImgR, &info); + status = jpegHdr.getJPEGRInfo(&jpegImgR, &info); + if (status == android::OK) { + size_t outSize = info.width * info.height * ((of == ULTRAHDR_OUTPUT_SDR) ? 4 : 8); + jpegr_uncompressed_struct decodedJpegR; + auto decodedRaw = std::make_unique(outSize); + decodedJpegR.data = decodedRaw.get(); + ultrahdr_metadata_struct metadata; + jpegr_uncompressed_struct decodedGainMap{}; + status = jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR, + mFdp.ConsumeFloatingPointInRange(1.0, FLT_MAX), + nullptr, of, &decodedGainMap, &metadata); + if (status != android::OK) { + ALOGE("encountered error during decoding %d", status); + } + if (decodedGainMap.data) free(decodedGainMap.data); + } else { + ALOGE("encountered error during get jpeg info %d", status); + } + } else { + ALOGE("encountered error during encoding %d", status); } } } -- GitLab From cd3f63736194d676c30e2903f87c5d10b30e4b3a Mon Sep 17 00:00:00 2001 From: Ram Mohan Date: Fri, 2 Jun 2023 15:16:15 +0530 Subject: [PATCH 1288/1310] ultrahdr: Fix nan occurence during xmp generation The default value of hdr white nits is zero, making log content boost to nan in certain scenarios. This can cause decode to fail on select architectures Bug: 285545078 Test: ./ultrahdr_enc_fuzzer Change-Id: I72064d4f04c25d6f3c951f5c20b0b674e675d541 --- libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp | 2 +- libs/ultrahdr/jpegr.cpp | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp index 7faa1570f0..290576d56f 100644 --- a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp +++ b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp @@ -256,7 +256,7 @@ void JpegHDRFuzzer::process() { } else if (tf == ULTRAHDR_TF_PQ) { metadata.maxContentBoost = kPqMaxNits / kSdrWhiteNits; } else { - metadata.maxContentBoost = 0; + metadata.maxContentBoost = 1.0f; } metadata.minContentBoost = 1.0f; status = jpegHdr.encodeJPEGR(&jpegImg, &jpegGainMap, &metadata, &jpegImgR); diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp index b2bde6c485..ed151f3ad2 100644 --- a/libs/ultrahdr/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -726,7 +726,7 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, map_data.reset(reinterpret_cast(dest->data)); ColorTransformFn hdrInvOetf = nullptr; - float hdr_white_nits = 0.0f; + float hdr_white_nits = kSdrWhiteNits; switch (hdr_tf) { case ULTRAHDR_TF_LINEAR: hdrInvOetf = identityConversion; @@ -1067,6 +1067,12 @@ status_t JpegR::appendGainMap(jr_compressed_ptr compressed_jpeg_image, return ERROR_JPEGR_INVALID_NULL_PTR; } + if (metadata->minContentBoost < 1.0f || metadata->maxContentBoost < metadata->minContentBoost) { + ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost, + metadata->maxContentBoost); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + const string nameSpace = "http://ns.adobe.com/xap/1.0/"; const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator -- GitLab From d136b8a2621f064f496cc114957b24588937befa Mon Sep 17 00:00:00 2001 From: Ram Mohan Date: Fri, 2 Jun 2023 09:06:40 +0530 Subject: [PATCH 1289/1310] ultrahdr: Add fuzz application for decode api Bug: 282640328 Test: ./ultrahdr_dec_fuzzer Change-Id: I9bd2a314d05122fd3010889b661455a6cba364e6 --- libs/ultrahdr/fuzzer/Android.bp | 36 +++++---- libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp | 73 +++++++++++++++++++ libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp | 21 +++--- .../include/ultrahdr/jpegdecoderhelper.h | 4 + libs/ultrahdr/jpegdecoderhelper.cpp | 6 ++ libs/ultrahdr/jpegr.cpp | 7 ++ 6 files changed, 120 insertions(+), 27 deletions(-) create mode 100644 libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp diff --git a/libs/ultrahdr/fuzzer/Android.bp b/libs/ultrahdr/fuzzer/Android.bp index 27b38c3590..6c0a2f577c 100644 --- a/libs/ultrahdr/fuzzer/Android.bp +++ b/libs/ultrahdr/fuzzer/Android.bp @@ -24,7 +24,17 @@ package { cc_defaults { name: "ultrahdr_fuzzer_defaults", host_supported: true, - static_libs: ["liblog"], + shared_libs: [ + "libimage_io", + "libjpeg", + ], + static_libs: [ + "libjpegdecoder", + "libjpegencoder", + "libultrahdr", + "libutils", + "liblog", + ], target: { darwin: { enabled: false, @@ -37,6 +47,8 @@ cc_defaults { description: "The fuzzers target the APIs of jpeg hdr", service_privilege: "constrained", users: "multi_user", + fuzzed_code_usage: "future_version", + vector: "local_no_privileges_required", }, } @@ -46,20 +58,12 @@ cc_fuzz { srcs: [ "ultrahdr_enc_fuzzer.cpp", ], - shared_libs: [ - "libimage_io", - "libjpeg", - "liblog", - ], - static_libs: [ - "libjpegdecoder", - "libjpegencoder", - "libultrahdr", - "libutils", - ], - fuzz_config: { - fuzzed_code_usage: "future_version", - vector: "local_no_privileges_required", - }, } +cc_fuzz { + name: "ultrahdr_dec_fuzzer", + defaults: ["ultrahdr_fuzzer_defaults"], + srcs: [ + "ultrahdr_dec_fuzzer.cpp", + ], +} diff --git a/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp new file mode 100644 index 0000000000..ad1d57aaee --- /dev/null +++ b/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp @@ -0,0 +1,73 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// System include files +#include +#include +#include + +// User include files +#include "ultrahdr/jpegr.h" + +using namespace android::ultrahdr; + +// Transfer functions for image data, sync with ultrahdr.h +const int kOfMin = ULTRAHDR_OUTPUT_UNSPECIFIED + 1; +const int kOfMax = ULTRAHDR_OUTPUT_MAX; + +class UltraHdrDecFuzzer { +public: + UltraHdrDecFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; + void process(); + +private: + FuzzedDataProvider mFdp; +}; + +void UltraHdrDecFuzzer::process() { + // hdr_of + auto of = static_cast(mFdp.ConsumeIntegralInRange(kOfMin, kOfMax)); + auto buffer = mFdp.ConsumeRemainingBytes(); + jpegr_compressed_struct jpegImgR{buffer.data(), (int)buffer.size(), (int)buffer.size(), + ULTRAHDR_COLORGAMUT_UNSPECIFIED}; + + std::vector iccData(0); + std::vector exifData(0); + jpegr_info_struct info{0, 0, &iccData, &exifData}; + JpegR jpegHdr; + (void)jpegHdr.getJPEGRInfo(&jpegImgR, &info); +//#define DUMP_PARAM +#ifdef DUMP_PARAM + std::cout << "input buffer size " << jpegImgR.length << std::endl; + std::cout << "image dimensions " << info.width << " x " << info.width << std::endl; +#endif + size_t outSize = info.width * info.height * ((of == ULTRAHDR_OUTPUT_SDR) ? 4 : 8); + jpegr_uncompressed_struct decodedJpegR; + auto decodedRaw = std::make_unique(outSize); + decodedJpegR.data = decodedRaw.get(); + ultrahdr_metadata_struct metadata; + jpegr_uncompressed_struct decodedGainMap{}; + (void)jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR, + mFdp.ConsumeFloatingPointInRange(1.0, FLT_MAX), nullptr, of, + &decodedGainMap, &metadata); + if (decodedGainMap.data) free(decodedGainMap.data); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + UltraHdrDecFuzzer fuzzHandle(data, size); + fuzzHandle.process(); + return 0; +} diff --git a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp index 7faa1570f0..dca0c15761 100644 --- a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp +++ b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp @@ -55,12 +55,9 @@ const int kOfMax = ULTRAHDR_OUTPUT_MAX; const int kQfMin = 0; const int kQfMax = 100; -// seed -const unsigned kSeed = 0x7ab7; - -class JpegHDRFuzzer { +class UltraHdrEncFuzzer { public: - JpegHDRFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; + UltraHdrEncFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; void process(); void fillP010Buffer(uint16_t* data, int width, int height, int stride); void fill420Buffer(uint8_t* data, int size); @@ -69,7 +66,7 @@ private: FuzzedDataProvider mFdp; }; -void JpegHDRFuzzer::fillP010Buffer(uint16_t* data, int width, int height, int stride) { +void UltraHdrEncFuzzer::fillP010Buffer(uint16_t* data, int width, int height, int stride) { uint16_t* tmp = data; std::vector buffer(16); for (int i = 0; i < buffer.size(); i++) { @@ -78,22 +75,24 @@ void JpegHDRFuzzer::fillP010Buffer(uint16_t* data, int width, int height, int st for (int j = 0; j < height; j++) { for (int i = 0; i < width; i += buffer.size()) { memcpy(data + i, buffer.data(), std::min((int)buffer.size(), (width - i))); - std::shuffle(buffer.begin(), buffer.end(), std::default_random_engine(kSeed)); + std::shuffle(buffer.begin(), buffer.end(), + std::default_random_engine(std::random_device{}())); } tmp += stride; } } -void JpegHDRFuzzer::fill420Buffer(uint8_t* data, int size) { +void UltraHdrEncFuzzer::fill420Buffer(uint8_t* data, int size) { std::vector buffer(16); mFdp.ConsumeData(buffer.data(), buffer.size()); for (int i = 0; i < size; i += buffer.size()) { memcpy(data + i, buffer.data(), std::min((int)buffer.size(), (size - i))); - std::shuffle(buffer.begin(), buffer.end(), std::default_random_engine(kSeed)); + std::shuffle(buffer.begin(), buffer.end(), + std::default_random_engine(std::random_device{}())); } } -void JpegHDRFuzzer::process() { +void UltraHdrEncFuzzer::process() { while (mFdp.remaining_bytes()) { struct jpegr_uncompressed_struct p010Img {}; struct jpegr_uncompressed_struct yuv420Img {}; @@ -293,7 +292,7 @@ void JpegHDRFuzzer::process() { } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - JpegHDRFuzzer fuzzHandle(data, size); + UltraHdrEncFuzzer fuzzHandle(data, size); fuzzHandle.process(); return 0; } diff --git a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h index f642bad89c..4f2b7423c8 100644 --- a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h +++ b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h @@ -25,6 +25,10 @@ extern "C" { } #include #include + +static const int kMaxWidth = 8192; +static const int kMaxHeight = 8192; + namespace android::ultrahdr { /* * Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format. diff --git a/libs/ultrahdr/jpegdecoderhelper.cpp b/libs/ultrahdr/jpegdecoderhelper.cpp index fac90c503d..2a9bc9ac1e 100644 --- a/libs/ultrahdr/jpegdecoderhelper.cpp +++ b/libs/ultrahdr/jpegdecoderhelper.cpp @@ -213,6 +213,12 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) } } + if (cinfo.image_width > kMaxWidth || cinfo.image_height > kMaxHeight) { + // constraint on max width and max height is only due to alloc constraints + // tune these values basing on the target device + return false; + } + mWidth = cinfo.image_width; mHeight = cinfo.image_height; diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp index b2bde6c485..25197d6787 100644 --- a/libs/ultrahdr/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -119,6 +119,13 @@ status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_ima return ERROR_JPEGR_INVALID_INPUT_TYPE; } + if (uncompressed_p010_image->width > kMaxWidth + || uncompressed_p010_image->height > kMaxHeight) { + ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %dx%d", + kMaxWidth, kMaxHeight, uncompressed_p010_image->width, uncompressed_p010_image->height); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + if (uncompressed_p010_image->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED || uncompressed_p010_image->colorGamut > ULTRAHDR_COLORGAMUT_MAX) { ALOGE("Unrecognized p010 color gamut %d", uncompressed_p010_image->colorGamut); -- GitLab From e69d9d2e7fde9dff70ba36dbc46b60bdd000cebb Mon Sep 17 00:00:00 2001 From: Ram Mohan Date: Fri, 2 Jun 2023 17:44:45 +0530 Subject: [PATCH 1290/1310] ultrahdr: release memory if encode/decode fails If calls to encode/decode failed, release the allocated memory before returning the control to caller Bug: 285546217 Test: ./ultrahdr_dec_fuzzer Test: ./ultrahdr_enc_fuzzer Change-Id: I276c31cc56656aa41845a16f5d28783bc3adc772 --- libs/ultrahdr/icc.cpp | 18 +++++++++--------- libs/ultrahdr/jpegdecoderhelper.cpp | 20 +++++++++++++------- libs/ultrahdr/jpegencoderhelper.cpp | 11 +++++------ libs/ultrahdr/multipictureformat.cpp | 2 +- 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/libs/ultrahdr/icc.cpp b/libs/ultrahdr/icc.cpp index c807705528..32d08aa525 100644 --- a/libs/ultrahdr/icc.cpp +++ b/libs/ultrahdr/icc.cpp @@ -180,7 +180,7 @@ sp IccHelper::write_text_tag(const char* text) { uint32_t total_length = text_length * 2 + sizeof(header); total_length = (((total_length + 2) >> 2) << 2); // 4 aligned - sp dataStruct = new DataStruct(total_length); + sp dataStruct = sp::make(total_length); if (!dataStruct->write(header, sizeof(header))) { ALOGE("write_text_tag(): error in writing data"); @@ -204,7 +204,7 @@ sp IccHelper::write_xyz_tag(float x, float y, float z) { static_cast(Endian_SwapBE32(float_round_to_fixed(y))), static_cast(Endian_SwapBE32(float_round_to_fixed(z))), }; - sp dataStruct = new DataStruct(sizeof(data)); + sp dataStruct = sp::make(sizeof(data)); dataStruct->write(&data, sizeof(data)); return dataStruct; } @@ -212,7 +212,7 @@ sp IccHelper::write_xyz_tag(float x, float y, float z) { sp IccHelper::write_trc_tag(const int table_entries, const void* table_16) { int total_length = 4 + 4 + 4 + table_entries * 2; total_length = (((total_length + 2) >> 2) << 2); // 4 aligned - sp dataStruct = new DataStruct(total_length); + sp dataStruct = sp::make(total_length); dataStruct->write32(Endian_SwapBE32(kTAG_CurveType)); // Type dataStruct->write32(0); // Reserved dataStruct->write32(Endian_SwapBE32(table_entries)); // Value count @@ -225,7 +225,7 @@ sp IccHelper::write_trc_tag(const int table_entries, const void* tab sp IccHelper::write_trc_tag_for_linear() { int total_length = 16; - sp dataStruct = new DataStruct(total_length); + sp dataStruct = sp::make(total_length); dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType)); // Type dataStruct->write32(0); // Reserved dataStruct->write32(Endian_SwapBE16(kExponential_ParaCurveType)); @@ -263,7 +263,7 @@ float IccHelper::compute_tone_map_gain(const ultrahdr_transfer_function tf, floa sp IccHelper::write_cicp_tag(uint32_t color_primaries, uint32_t transfer_characteristics) { int total_length = 12; // 4 + 4 + 1 + 1 + 1 + 1 - sp dataStruct = new DataStruct(total_length); + sp dataStruct = sp::make(total_length); dataStruct->write32(Endian_SwapBE32(kTAG_cicp)); // Type signature dataStruct->write32(0); // Reserved dataStruct->write8(color_primaries); // Color primaries @@ -314,7 +314,7 @@ sp IccHelper::write_clut(const uint8_t* grid_points, const uint8_t* int total_length = 20 + 2 * value_count; total_length = (((total_length + 2) >> 2) << 2); // 4 aligned - sp dataStruct = new DataStruct(total_length); + sp dataStruct = sp::make(total_length); for (size_t i = 0; i < 16; ++i) { dataStruct->write8(i < kNumChannels ? grid_points[i] : 0); // Grid size @@ -372,7 +372,7 @@ sp IccHelper::write_mAB_or_mBA_tag(uint32_t type, total_length += a_curves_data[i]->getLength(); } } - sp dataStruct = new DataStruct(total_length); + sp dataStruct = sp::make(total_length); dataStruct->write32(Endian_SwapBE32(type)); // Type signature dataStruct->write32(0); // Reserved dataStruct->write8(kNumChannels); // Input channels @@ -421,7 +421,7 @@ sp IccHelper::writeIccProfile(ultrahdr_transfer_function tf, break; default: // Should not fall here. - return new DataStruct(0); + return nullptr; } // Compute primaries. @@ -546,7 +546,7 @@ sp IccHelper::writeIccProfile(ultrahdr_transfer_function tf, header.size = Endian_SwapBE32(profile_size); header.tag_count = Endian_SwapBE32(tags.size()); - sp dataStruct = new DataStruct(profile_size); + sp dataStruct = sp::make(profile_size); if (!dataStruct->write(&header, sizeof(header))) { ALOGE("writeIccProfile(): error in header"); return dataStruct; diff --git a/libs/ultrahdr/jpegdecoderhelper.cpp b/libs/ultrahdr/jpegdecoderhelper.cpp index 2a9bc9ac1e..0bad4a4de0 100644 --- a/libs/ultrahdr/jpegdecoderhelper.cpp +++ b/libs/ultrahdr/jpegdecoderhelper.cpp @@ -150,6 +150,7 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) jpeg_decompress_struct cinfo; jpegr_source_mgr mgr(static_cast(image), length); jpegrerror_mgr myerr; + bool status = true; cinfo.err = jpeg_std_error(&myerr.pub); myerr.pub.error_exit = jpegrerror_exit; @@ -216,7 +217,8 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) if (cinfo.image_width > kMaxWidth || cinfo.image_height > kMaxHeight) { // constraint on max width and max height is only due to alloc constraints // tune these values basing on the target device - return false; + status = false; + goto CleanUp; } mWidth = cinfo.image_width; @@ -225,7 +227,8 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) if (decodeToRGBA) { if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { // We don't intend to support decoding grayscale to RGBA - return false; + status = false; + goto CleanUp; } // 4 bytes per pixel mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 4); @@ -238,7 +241,8 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) cinfo.comp_info[0].v_samp_factor != 2 || cinfo.comp_info[1].v_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) { - return false; + status = false; + goto CleanUp; } mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0); } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { @@ -254,13 +258,15 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) if (!decompress(&cinfo, static_cast(mResultBuffer.data()), cinfo.jpeg_color_space == JCS_GRAYSCALE)) { - return false; + status = false; + goto CleanUp; } +CleanUp: jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); - return true; + return status; } bool JpegDecoderHelper::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, @@ -367,7 +373,7 @@ bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8 uint8_t* y_plane = const_cast(dest); uint8_t* u_plane = const_cast(dest + y_plane_size); uint8_t* v_plane = const_cast(dest + y_plane_size + uv_plane_size); - std::unique_ptr empty(new uint8_t[cinfo->image_width]); + std::unique_ptr empty = std::make_unique(cinfo->image_width); memset(empty.get(), 0, cinfo->image_width); const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); @@ -441,7 +447,7 @@ bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo, c JSAMPARRAY planes[1] {y}; uint8_t* y_plane = const_cast(dest); - std::unique_ptr empty(new uint8_t[cinfo->image_width]); + std::unique_ptr empty = std::make_unique(cinfo->image_width); memset(empty.get(), 0, cinfo->image_width); int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); diff --git a/libs/ultrahdr/jpegencoderhelper.cpp b/libs/ultrahdr/jpegencoderhelper.cpp index ab2f8c7b5a..a03547b538 100644 --- a/libs/ultrahdr/jpegencoderhelper.cpp +++ b/libs/ultrahdr/jpegencoderhelper.cpp @@ -107,12 +107,11 @@ bool JpegEncoderHelper::encode(const void* image, int width, int height, int jpe jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast(iccBuffer), iccSize); } - if (!compress(&cinfo, static_cast(image), isSingleChannel)) { - return false; - } + bool status = compress(&cinfo, static_cast(image), isSingleChannel); jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); - return true; + + return status; } void JpegEncoderHelper::setJpegDestination(jpeg_compress_struct* cinfo) { @@ -174,7 +173,7 @@ bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* uint8_t* y_plane = const_cast(yuv); uint8_t* u_plane = const_cast(yuv + y_plane_size); uint8_t* v_plane = const_cast(yuv + y_plane_size + uv_plane_size); - std::unique_ptr empty(new uint8_t[cinfo->image_width]); + std::unique_ptr empty = std::make_unique(cinfo->image_width); memset(empty.get(), 0, cinfo->image_width); const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); @@ -250,7 +249,7 @@ bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const JSAMPARRAY planes[1] {y}; uint8_t* y_plane = const_cast(image); - std::unique_ptr empty(new uint8_t[cinfo->image_width]); + std::unique_ptr empty = std::make_unique(cinfo->image_width); memset(empty.get(), 0, cinfo->image_width); const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); diff --git a/libs/ultrahdr/multipictureformat.cpp b/libs/ultrahdr/multipictureformat.cpp index 7a265c61b7..f1679ef1b3 100644 --- a/libs/ultrahdr/multipictureformat.cpp +++ b/libs/ultrahdr/multipictureformat.cpp @@ -30,7 +30,7 @@ size_t calculateMpfSize() { sp generateMpf(int primary_image_size, int primary_image_offset, int secondary_image_size, int secondary_image_offset) { size_t mpf_size = calculateMpfSize(); - sp dataStruct = new DataStruct(mpf_size); + sp dataStruct = sp::make(mpf_size); dataStruct->write(static_cast(kMpfSig), sizeof(kMpfSig)); #if USE_BIG_ENDIAN -- GitLab From 1d3509ed5dd2c9b38acb4b5dc8c02a97a5a38ee6 Mon Sep 17 00:00:00 2001 From: Trevor David Black Date: Thu, 8 Jun 2023 00:17:40 +0000 Subject: [PATCH 1291/1310] Correctly implement minImageCount for swapchain Bug: 284807752 Test: atest CtsDeqpTestCases -- --module-arg 'CtsDeqpTestCases:include-filter:dEQP-VK.wsi.android.maintenance1.release_images.*' Change-Id: I36bd7a3dac8f8a3f6b913733fbd0ba140ee0b092 --- vulkan/libvulkan/swapchain.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp index 5965953b38..af873065ff 100644 --- a/vulkan/libvulkan/swapchain.cpp +++ b/vulkan/libvulkan/swapchain.cpp @@ -877,6 +877,7 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR( int width, height; int transform_hint; int max_buffer_count; + int min_undequeued_buffers; if (surface == VK_NULL_HANDLE) { const InstanceData& instance_data = GetData(physicalDevice); ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query; @@ -929,17 +930,24 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR( return VK_ERROR_SURFACE_LOST_KHR; } + err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, + &min_undequeued_buffers); + if (err != android::OK) { + ALOGE("NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS query failed: %s (%d)", + strerror(-err), err); + return VK_ERROR_SURFACE_LOST_KHR; + } + if (pPresentMode && IsSharedPresentMode(pPresentMode->presentMode)) { capabilities->minImageCount = 1; capabilities->maxImageCount = 1; } else if (pPresentMode && pPresentMode->presentMode == VK_PRESENT_MODE_MAILBOX_KHR) { - // TODO: use undequeued buffer requirement for more precise bound - capabilities->minImageCount = std::min(max_buffer_count, 4); + capabilities->minImageCount = + std::min(max_buffer_count, min_undequeued_buffers + 2); capabilities->maxImageCount = static_cast(max_buffer_count); } else { - // TODO: if we're able to, provide better bounds on the number of buffers - // for other modes as well. - capabilities->minImageCount = std::min(max_buffer_count, 3); + capabilities->minImageCount = + std::min(max_buffer_count, min_undequeued_buffers + 1); capabilities->maxImageCount = static_cast(max_buffer_count); } } -- GitLab From 0db53ee3c9ae908d14c09290a4fb51036df25620 Mon Sep 17 00:00:00 2001 From: Nick Deakin Date: Fri, 19 May 2023 17:14:45 -0400 Subject: [PATCH 1292/1310] libultrahdr: correct srgb, p3 calculations and jpeg yuv handling * Correct luminance calculation for sRGB to utilize actual luminance coefficients for the gamut, rather than 601 luma coefficients. * Correct YUV<->RGB conversion for sRGB to utilize Rec.709 coefficients rather than Rec.601 coefficients as it was previously. * New P3 YUV<->RGB conversion, which uses Rec.601 coefficients. * Also ICC Profile fixes to make things work; more below. * Update things to correctly convert to and from Rec.601 YUV for jpeg encoding; more below. This setup for YUV<->RGB coefficients is chosen to match the expectations of DataSpace when it comes to interpretting YUV encoding of data. Generally, the interpretation is cued off of the color primaries, since the specifications around color primaries generally also specify a YUV interpretation. Display-P3 is a bit of an outlier; the best specification of Display-P3 is in SMPTE EG 432-1, but EG 432-1 doesn't cover YUV interpretation. So, since DataSpace interprets Display-P3 YUV data via the Rec.601 coefficients, we should do the same here. ICC Profile fixes; ICC profiles we wrote were broken before this for a variety of reasons: * The endianness macro wasn't actually swapping endiannesas to provide the correct encoding in our output. * We weren't writing out the identifier for the app segment, including the chunk count and ID. * We were assuming input JPEGs have ICC data, which may not be the case. * We also need to read in the ICC profile during decode to apply the map properly, and we didn't have any mechanism previously to read the ICC profile and determine the gamut of the encoded JPEGR file. * Upon adding ICC reading code to our JPEG decoding, also remove some dead code from previous EXIF reading. * Add a number of tests to verify all of this stuff stays fixed. YUV interpretation and Rec.601: * Previously, we were feeding YUV right into the JPEG encoder; this is problematic because JPEG encoders usually (and definitely in our specific case) expect Rec.601 YUV encoded input data, since this is by definition the format of JPEG YUV data according to ECMA TR/98. * Now properly convert from Rec.709 or Rec.2100 YUV encoding to Rec.601 (when necessary) prior to passing YUV data to the jpeg encoder. * Also make sure we properly interpret decoded YUV output as Rec.601 after decode. * This involved added some new methods to facilitate these conversions. * Added some new tests to verify these conversions. * Note that to do these YUV conversions for subsampled 420 data, we take each set of 4 Y and 1 UV, and calculate the result against each combination. The new Y values each get the corresponding result, and the new UV value is equal to the average of the set. * Note that none of this is a concern for gain map encoding/decoding via JPEG because gain maps are single channel. Bug: 283143961 Test: added new tests, all tests pass Change-Id: Ibc7b1779fc3a8244f85abb581c554963f57dc5a4 --- libs/ultrahdr/gainmapmath.cpp | 149 ++++++++++-- libs/ultrahdr/icc.cpp | 96 +++++++- libs/ultrahdr/include/ultrahdr/gainmapmath.h | 65 ++++- libs/ultrahdr/include/ultrahdr/icc.h | 25 +- .../include/ultrahdr/jpegdecoderhelper.h | 15 +- libs/ultrahdr/include/ultrahdr/jpegr.h | 44 +++- libs/ultrahdr/jpegdecoderhelper.cpp | 52 ++-- libs/ultrahdr/jpegr.cpp | 228 +++++++++++++++-- libs/ultrahdr/tests/Android.bp | 5 +- .../tests/data/minnie-320x240-yuv-icc.jpg | Bin 0 -> 37101 bytes libs/ultrahdr/tests/gainmapmath_test.cpp | 229 +++++++++++++++++- libs/ultrahdr/tests/icchelper_test.cpp | 77 ++++++ .../ultrahdr/tests/jpegdecoderhelper_test.cpp | 58 ++++- 13 files changed, 954 insertions(+), 89 deletions(-) create mode 100644 libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg create mode 100644 libs/ultrahdr/tests/icchelper_test.cpp diff --git a/libs/ultrahdr/gainmapmath.cpp b/libs/ultrahdr/gainmapmath.cpp index 37c3cf3d3b..ee15363b69 100644 --- a/libs/ultrahdr/gainmapmath.cpp +++ b/libs/ultrahdr/gainmapmath.cpp @@ -119,34 +119,39 @@ static float clampPixelFloat(float value) { return (value < 0.0f) ? 0.0f : (value > kMaxPixelFloat) ? kMaxPixelFloat : value; } -// See IEC 61966-2-1, Equation F.7. +// See IEC 61966-2-1/Amd 1:2003, Equation F.7. static const float kSrgbR = 0.2126f, kSrgbG = 0.7152f, kSrgbB = 0.0722f; float srgbLuminance(Color e) { return kSrgbR * e.r + kSrgbG * e.g + kSrgbB * e.b; } -// See ECMA TR/98, Section 7. -static const float kSrgbRCr = 1.402f, kSrgbGCb = 0.34414f, kSrgbGCr = 0.71414f, kSrgbBCb = 1.772f; +// See ITU-R BT.709-6, Section 3. +// Uses the same coefficients for deriving luma signal as +// IEC 61966-2-1/Amd 1:2003 states for luminance, so we reuse the luminance +// function above. +static const float kSrgbCb = 1.8556f, kSrgbCr = 1.5748f; -Color srgbYuvToRgb(Color e_gamma) { - return {{{ clampPixelFloat(e_gamma.y + kSrgbRCr * e_gamma.v), - clampPixelFloat(e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v), - clampPixelFloat(e_gamma.y + kSrgbBCb * e_gamma.u) }}}; +Color srgbRgbToYuv(Color e_gamma) { + float y_gamma = srgbLuminance(e_gamma); + return {{{ y_gamma, + (e_gamma.b - y_gamma) / kSrgbCb, + (e_gamma.r - y_gamma) / kSrgbCr }}}; } -// See ECMA TR/98, Section 7. -static const float kSrgbYR = 0.299f, kSrgbYG = 0.587f, kSrgbYB = 0.114f; -static const float kSrgbUR = -0.1687f, kSrgbUG = -0.3313f, kSrgbUB = 0.5f; -static const float kSrgbVR = 0.5f, kSrgbVG = -0.4187f, kSrgbVB = -0.0813f; +// See ITU-R BT.709-6, Section 3. +// Same derivation to BT.2100's YUV->RGB, below. Similar to srgbRgbToYuv, we +// can reuse the luminance coefficients since they are the same. +static const float kSrgbGCb = kSrgbB * kSrgbCb / kSrgbG; +static const float kSrgbGCr = kSrgbR * kSrgbCr / kSrgbG; -Color srgbRgbToYuv(Color e_gamma) { - return {{{ kSrgbYR * e_gamma.r + kSrgbYG * e_gamma.g + kSrgbYB * e_gamma.b, - kSrgbUR * e_gamma.r + kSrgbUG * e_gamma.g + kSrgbUB * e_gamma.b, - kSrgbVR * e_gamma.r + kSrgbVG * e_gamma.g + kSrgbVB * e_gamma.b }}}; +Color srgbYuvToRgb(Color e_gamma) { + return {{{ clampPixelFloat(e_gamma.y + kSrgbCr * e_gamma.v), + clampPixelFloat(e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v), + clampPixelFloat(e_gamma.y + kSrgbCb * e_gamma.u) }}}; } -// See IEC 61966-2-1, Equations F.5 and F.6. +// See IEC 61966-2-1/Amd 1:2003, Equations F.5 and F.6. float srgbInvOetf(float e_gamma) { if (e_gamma <= 0.04045f) { return e_gamma / 12.92f; @@ -178,13 +183,38 @@ Color srgbInvOetfLUT(Color e_gamma) { //////////////////////////////////////////////////////////////////////////////// // Display-P3 transformations -// See SMPTE EG 432-1, Table 7-2. +// See SMPTE EG 432-1, Equation 7-8. static const float kP3R = 0.20949f, kP3G = 0.72160f, kP3B = 0.06891f; float p3Luminance(Color e) { return kP3R * e.r + kP3G * e.g + kP3B * e.b; } +// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2. +// Unfortunately, calculation of luma signal differs from calculation of +// luminance for Display-P3, so we can't reuse p3Luminance here. +static const float kP3YR = 0.299f, kP3YG = 0.587f, kP3YB = 0.114f; +static const float kP3Cb = 1.772f, kP3Cr = 1.402f; + +Color p3RgbToYuv(Color e_gamma) { + float y_gamma = kP3YR * e_gamma.r + kP3YG * e_gamma.g + kP3YB * e_gamma.b; + return {{{ y_gamma, + (e_gamma.b - y_gamma) / kP3Cb, + (e_gamma.r - y_gamma) / kP3Cr }}}; +} + +// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2. +// Same derivation to BT.2100's YUV->RGB, below. Similar to p3RgbToYuv, we must +// use luma signal coefficients rather than the luminance coefficients. +static const float kP3GCb = kP3YB * kP3Cb / kP3YG; +static const float kP3GCr = kP3YR * kP3Cr / kP3YG; + +Color p3YuvToRgb(Color e_gamma) { + return {{{ clampPixelFloat(e_gamma.y + kP3Cr * e_gamma.v), + clampPixelFloat(e_gamma.y - kP3GCb * e_gamma.u - kP3GCr * e_gamma.v), + clampPixelFloat(e_gamma.y + kP3Cb * e_gamma.u) }}}; +} + //////////////////////////////////////////////////////////////////////////////// // BT.2100 transformations - according to ITU-R BT.2100-2 @@ -197,6 +227,8 @@ float bt2100Luminance(Color e) { } // See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals. +// BT.2100 uses the same coefficients for calculating luma signal and luminance, +// so we reuse the luminance function here. static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f; Color bt2100RgbToYuv(Color e_gamma) { @@ -206,6 +238,10 @@ Color bt2100RgbToYuv(Color e_gamma) { (e_gamma.r - y_gamma) / kBt2100Cr }}}; } +// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals. +// +// Similar to bt2100RgbToYuv above, we can reuse the luminance coefficients. +// // Derived by inversing bt2100RgbToYuv. The derivation for R and B are pretty // straight forward; we just invert the formulas for U and V above. But deriving // the formula for G is a bit more complicated: @@ -440,6 +476,85 @@ ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut, } } +// All of these conversions are derived from the respective input YUV->RGB conversion followed by +// the RGB->YUV for the receiving encoding. They are consistent with the RGB<->YUV functions in this +// file, given that we uses BT.709 encoding for sRGB and BT.601 encoding for Display-P3, to match +// DataSpace. + +Color yuv709To601(Color e_gamma) { + return {{{ 1.0f * e_gamma.y + 0.101579f * e_gamma.u + 0.196076f * e_gamma.v, + 0.0f * e_gamma.y + 0.989854f * e_gamma.u + -0.110653f * e_gamma.v, + 0.0f * e_gamma.y + -0.072453f * e_gamma.u + 0.983398f * e_gamma.v }}}; +} + +Color yuv709To2100(Color e_gamma) { + return {{{ 1.0f * e_gamma.y + -0.016969f * e_gamma.u + 0.096312f * e_gamma.v, + 0.0f * e_gamma.y + 0.995306f * e_gamma.u + -0.051192f * e_gamma.v, + 0.0f * e_gamma.y + 0.011507f * e_gamma.u + 1.002637f * e_gamma.v }}}; +} + +Color yuv601To709(Color e_gamma) { + return {{{ 1.0f * e_gamma.y + -0.118188f * e_gamma.u + -0.212685f * e_gamma.v, + 0.0f * e_gamma.y + 1.018640f * e_gamma.u + 0.114618f * e_gamma.v, + 0.0f * e_gamma.y + 0.075049f * e_gamma.u + 1.025327f * e_gamma.v }}}; +} + +Color yuv601To2100(Color e_gamma) { + return {{{ 1.0f * e_gamma.y + -0.128245f * e_gamma.u + -0.115879f * e_gamma.v, + 0.0f * e_gamma.y + 1.010016f * e_gamma.u + 0.061592f * e_gamma.v, + 0.0f * e_gamma.y + 0.086969f * e_gamma.u + 1.029350f * e_gamma.v }}}; +} + +Color yuv2100To709(Color e_gamma) { + return {{{ 1.0f * e_gamma.y + 0.018149f * e_gamma.u + -0.095132f * e_gamma.v, + 0.0f * e_gamma.y + 1.004123f * e_gamma.u + 0.051267f * e_gamma.v, + 0.0f * e_gamma.y + -0.011524f * e_gamma.u + 0.996782f * e_gamma.v }}}; +} + +Color yuv2100To601(Color e_gamma) { + return {{{ 1.0f * e_gamma.y + 0.117887f * e_gamma.u + 0.105521f * e_gamma.v, + 0.0f * e_gamma.y + 0.995211f * e_gamma.u + -0.059549f * e_gamma.v, + 0.0f * e_gamma.y + -0.084085f * e_gamma.u + 0.976518f * e_gamma.v }}}; +} + +void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma, + ColorTransformFn fn) { + Color yuv1 = getYuv420Pixel(image, x_chroma * 2, y_chroma * 2 ); + Color yuv2 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2 ); + Color yuv3 = getYuv420Pixel(image, x_chroma * 2, y_chroma * 2 + 1); + Color yuv4 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2 + 1); + + yuv1 = fn(yuv1); + yuv2 = fn(yuv2); + yuv3 = fn(yuv3); + yuv4 = fn(yuv4); + + Color new_uv = (yuv1 + yuv2 + yuv3 + yuv4) / 4.0f; + + size_t pixel_y1_idx = x_chroma * 2 + y_chroma * 2 * image->width; + size_t pixel_y2_idx = (x_chroma * 2 + 1) + y_chroma * 2 * image->width; + size_t pixel_y3_idx = x_chroma * 2 + (y_chroma * 2 + 1) * image->width; + size_t pixel_y4_idx = (x_chroma * 2 + 1) + (y_chroma * 2 + 1) * image->width; + + uint8_t& y1_uint = reinterpret_cast(image->data)[pixel_y1_idx]; + uint8_t& y2_uint = reinterpret_cast(image->data)[pixel_y2_idx]; + uint8_t& y3_uint = reinterpret_cast(image->data)[pixel_y3_idx]; + uint8_t& y4_uint = reinterpret_cast(image->data)[pixel_y4_idx]; + + size_t pixel_count = image->width * image->height; + size_t pixel_uv_idx = x_chroma + y_chroma * (image->width / 2); + + uint8_t& u_uint = reinterpret_cast(image->data)[pixel_count + pixel_uv_idx]; + uint8_t& v_uint = reinterpret_cast(image->data)[pixel_count * 5 / 4 + pixel_uv_idx]; + + y1_uint = static_cast(floor(yuv1.y * 255.0f + 0.5f)); + y2_uint = static_cast(floor(yuv2.y * 255.0f + 0.5f)); + y3_uint = static_cast(floor(yuv3.y * 255.0f + 0.5f)); + y4_uint = static_cast(floor(yuv4.y * 255.0f + 0.5f)); + + u_uint = static_cast(floor(new_uv.u * 255.0f + 128.0f + 0.5f)); + v_uint = static_cast(floor(new_uv.v * 255.0f + 128.0f + 0.5f)); +} //////////////////////////////////////////////////////////////////////////////// // Gain map calculations diff --git a/libs/ultrahdr/icc.cpp b/libs/ultrahdr/icc.cpp index 32d08aa525..1ab3c7c793 100644 --- a/libs/ultrahdr/icc.cpp +++ b/libs/ultrahdr/icc.cpp @@ -14,6 +14,10 @@ * limitations under the License. */ +#ifndef USE_BIG_ENDIAN +#define USE_BIG_ENDIAN true +#endif + #include #include #include @@ -540,13 +544,21 @@ sp IccHelper::writeIccProfile(ultrahdr_transfer_function tf, size_t tag_table_size = kICCTagTableEntrySize * tags.size(); size_t profile_size = kICCHeaderSize + tag_table_size + tag_data_size; + sp dataStruct = sp::make(profile_size + kICCIdentifierSize); + + // Write identifier, chunk count, and chunk ID + if (!dataStruct->write(kICCIdentifier, sizeof(kICCIdentifier)) || + !dataStruct->write8(1) || !dataStruct->write8(1)) { + ALOGE("writeIccProfile(): error in identifier"); + return dataStruct; + } + // Write the header. header.data_color_space = Endian_SwapBE32(Signature_RGB); header.pcs = Endian_SwapBE32(tf == ULTRAHDR_TF_PQ ? Signature_Lab : Signature_XYZ); header.size = Endian_SwapBE32(profile_size); header.tag_count = Endian_SwapBE32(tags.size()); - sp dataStruct = sp::make(profile_size); if (!dataStruct->write(&header, sizeof(header))) { ALOGE("writeIccProfile(): error in header"); return dataStruct; @@ -582,4 +594,84 @@ sp IccHelper::writeIccProfile(ultrahdr_transfer_function tf, return dataStruct; } -} // namespace android::ultrahdr \ No newline at end of file +bool IccHelper::tagsEqualToMatrix(const Matrix3x3& matrix, + const uint8_t* red_tag, + const uint8_t* green_tag, + const uint8_t* blue_tag) { + sp red_tag_test = write_xyz_tag(matrix.vals[0][0], matrix.vals[1][0], + matrix.vals[2][0]); + sp green_tag_test = write_xyz_tag(matrix.vals[0][1], matrix.vals[1][1], + matrix.vals[2][1]); + sp blue_tag_test = write_xyz_tag(matrix.vals[0][2], matrix.vals[1][2], + matrix.vals[2][2]); + return memcmp(red_tag, red_tag_test->getData(), kColorantTagSize) == 0 && + memcmp(green_tag, green_tag_test->getData(), kColorantTagSize) == 0 && + memcmp(blue_tag, blue_tag_test->getData(), kColorantTagSize) == 0; +} + +ultrahdr_color_gamut IccHelper::readIccColorGamut(void* icc_data, size_t icc_size) { + // Each tag table entry consists of 3 fields of 4 bytes each. + static const size_t kTagTableEntrySize = 12; + + if (icc_data == nullptr || icc_size < sizeof(ICCHeader) + kICCIdentifierSize) { + return ULTRAHDR_COLORGAMUT_UNSPECIFIED; + } + + if (memcmp(icc_data, kICCIdentifier, sizeof(kICCIdentifier)) != 0) { + return ULTRAHDR_COLORGAMUT_UNSPECIFIED; + } + + uint8_t* icc_bytes = reinterpret_cast(icc_data) + kICCIdentifierSize; + + ICCHeader* header = reinterpret_cast(icc_bytes); + + // Use 0 to indicate not found, since offsets are always relative to start + // of ICC data and therefore a tag offset of zero would never be valid. + size_t red_primary_offset = 0, green_primary_offset = 0, blue_primary_offset = 0; + size_t red_primary_size = 0, green_primary_size = 0, blue_primary_size = 0; + for (size_t tag_idx = 0; tag_idx < Endian_SwapBE32(header->tag_count); ++tag_idx) { + uint32_t* tag_entry_start = reinterpret_cast( + icc_bytes + sizeof(ICCHeader) + tag_idx * kTagTableEntrySize); + // first 4 bytes are the tag signature, next 4 bytes are the tag offset, + // last 4 bytes are the tag length in bytes. + if (red_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_rXYZ)) { + red_primary_offset = Endian_SwapBE32(*(tag_entry_start+1)); + red_primary_size = Endian_SwapBE32(*(tag_entry_start+2)); + } else if (green_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_gXYZ)) { + green_primary_offset = Endian_SwapBE32(*(tag_entry_start+1)); + green_primary_size = Endian_SwapBE32(*(tag_entry_start+2)); + } else if (blue_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_bXYZ)) { + blue_primary_offset = Endian_SwapBE32(*(tag_entry_start+1)); + blue_primary_size = Endian_SwapBE32(*(tag_entry_start+2)); + } + } + + if (red_primary_offset == 0 || red_primary_size != kColorantTagSize || + kICCIdentifierSize + red_primary_offset + red_primary_size > icc_size || + green_primary_offset == 0 || green_primary_size != kColorantTagSize || + kICCIdentifierSize + green_primary_offset + green_primary_size > icc_size || + blue_primary_offset == 0 || blue_primary_size != kColorantTagSize || + kICCIdentifierSize + blue_primary_offset + blue_primary_size > icc_size) { + return ULTRAHDR_COLORGAMUT_UNSPECIFIED; + } + + uint8_t* red_tag = icc_bytes + red_primary_offset; + uint8_t* green_tag = icc_bytes + green_primary_offset; + uint8_t* blue_tag = icc_bytes + blue_primary_offset; + + // Serialize tags as we do on encode and compare what we find to that to + // determine the gamut (since we don't have a need yet for full deserialize). + if (tagsEqualToMatrix(kSRGB, red_tag, green_tag, blue_tag)) { + return ULTRAHDR_COLORGAMUT_BT709; + } else if (tagsEqualToMatrix(kDisplayP3, red_tag, green_tag, blue_tag)) { + return ULTRAHDR_COLORGAMUT_P3; + } else if (tagsEqualToMatrix(kRec2020, red_tag, green_tag, blue_tag)) { + return ULTRAHDR_COLORGAMUT_BT2100; + } + + // Didn't find a match to one of the profiles we write; indicate the gamut + // is unspecified since we don't understand it. + return ULTRAHDR_COLORGAMUT_UNSPECIFIED; +} + +} // namespace android::ultrahdr diff --git a/libs/ultrahdr/include/ultrahdr/gainmapmath.h b/libs/ultrahdr/include/ultrahdr/gainmapmath.h index abc93567f2..13832db752 100644 --- a/libs/ultrahdr/include/ultrahdr/gainmapmath.h +++ b/libs/ultrahdr/include/ultrahdr/gainmapmath.h @@ -218,24 +218,30 @@ struct ShepardsIDW { // except for those concerning transfer functions. /* - * Calculate the luminance of a linear RGB sRGB pixel, according to IEC 61966-2-1. + * Calculate the luminance of a linear RGB sRGB pixel, according to + * IEC 61966-2-1/Amd 1:2003. * * [0.0, 1.0] range in and out. */ float srgbLuminance(Color e); /* - * Convert from OETF'd srgb YUV to RGB, according to ECMA TR/98. + * Convert from OETF'd srgb RGB to YUV, according to ITU-R BT.709-6. + * + * BT.709 YUV<->RGB matrix is used to match expectations for DataSpace. */ -Color srgbYuvToRgb(Color e_gamma); +Color srgbRgbToYuv(Color e_gamma); + /* - * Convert from OETF'd srgb RGB to YUV, according to ECMA TR/98. + * Convert from OETF'd srgb YUV to RGB, according to ITU-R BT.709-6. + * + * BT.709 YUV<->RGB matrix is used to match expectations for DataSpace. */ -Color srgbRgbToYuv(Color e_gamma); +Color srgbYuvToRgb(Color e_gamma); /* - * Convert from srgb to linear, according to IEC 61966-2-1. + * Convert from srgb to linear, according to IEC 61966-2-1/Amd 1:2003. * * [0.0, 1.0] range in and out. */ @@ -257,6 +263,20 @@ constexpr size_t kSrgbInvOETFNumEntries = 1 << kSrgbInvOETFPrecision; */ float p3Luminance(Color e); +/* + * Convert from OETF'd P3 RGB to YUV, according to ITU-R BT.601-7. + * + * BT.601 YUV<->RGB matrix is used to match expectations for DataSpace. + */ +Color p3RgbToYuv(Color e_gamma); + +/* + * Convert from OETF'd P3 YUV to RGB, according to ITU-R BT.601-7. + * + * BT.601 YUV<->RGB matrix is used to match expectations for DataSpace. + */ +Color p3YuvToRgb(Color e_gamma); + //////////////////////////////////////////////////////////////////////////////// // BT.2100 transformations - according to ITU-R BT.2100-2 @@ -269,12 +289,16 @@ float p3Luminance(Color e); float bt2100Luminance(Color e); /* - * Convert from OETF'd BT.2100 RGB to YUV. + * Convert from OETF'd BT.2100 RGB to YUV, according to ITU-R BT.2100-2. + * + * BT.2100 YUV<->RGB matrix is used to match expectations for DataSpace. */ Color bt2100RgbToYuv(Color e_gamma); /* - * Convert from OETF'd BT.2100 YUV to RGB. + * Convert from OETF'd BT.2100 YUV to RGB, according to ITU-R BT.2100-2. + * + * BT.2100 YUV<->RGB matrix is used to match expectations for DataSpace. */ Color bt2100YuvToRgb(Color e_gamma); @@ -358,6 +382,31 @@ inline Color identityConversion(Color e) { return e; } */ ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut, ultrahdr_color_gamut hdr_gamut); +/* + * Convert between YUV encodings, according to ITU-R BT.709-6, ITU-R BT.601-7, and ITU-R BT.2100-2. + * + * Bt.709 and Bt.2100 have well-defined YUV encodings; Display-P3's is less well defined, but is + * treated as Bt.601 by DataSpace, hence we do the same. + */ +Color yuv709To601(Color e_gamma); +Color yuv709To2100(Color e_gamma); +Color yuv601To709(Color e_gamma); +Color yuv601To2100(Color e_gamma); +Color yuv2100To709(Color e_gamma); +Color yuv2100To601(Color e_gamma); + +/* + * Performs a transformation at the chroma x and y coordinates provided on a YUV420 image. + * + * Apply the transformation by determining transformed YUV for each of the 4 Y + 1 UV; each Y gets + * this result, and UV gets the averaged result. + * + * x_chroma and y_chroma should be less than or equal to half the image's width and height + * respecitively, since input is 4:2:0 subsampled. + */ +void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma, + ColorTransformFn fn); + //////////////////////////////////////////////////////////////////////////////// // Gain map calculations diff --git a/libs/ultrahdr/include/ultrahdr/icc.h b/libs/ultrahdr/include/ultrahdr/icc.h index 7f6ab882c6..7f047f8f5b 100644 --- a/libs/ultrahdr/include/ultrahdr/icc.h +++ b/libs/ultrahdr/include/ultrahdr/icc.h @@ -56,12 +56,16 @@ enum { Signature_XYZ = 0x58595A20, }; - typedef uint32_t FourByteTag; static inline constexpr FourByteTag SetFourByteTag(char a, char b, char c, char d) { return (((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | (uint32_t)d); } +static constexpr char kICCIdentifier[] = "ICC_PROFILE"; +// 12 for the actual identifier, +2 for the chunk count and chunk index which +// will always follow. +static constexpr size_t kICCIdentifierSize = 14; + // This is equal to the header size according to the ICC specification (128) // plus the size of the tag count (4). We include the tag count since we // always require it to be present anyway. @@ -70,6 +74,10 @@ static constexpr size_t kICCHeaderSize = 132; // Contains a signature (4), offset (4), and size (4). static constexpr size_t kICCTagTableEntrySize = 12; +// size should be 20; 4 bytes for type descriptor, 4 bytes reserved, 12 +// bytes for a single XYZ number type (4 bytes per coordinate). +static constexpr size_t kColorantTagSize = 20; + static constexpr uint32_t kDisplay_Profile = SetFourByteTag('m', 'n', 't', 'r'); static constexpr uint32_t kRGB_ColorSpace = SetFourByteTag('R', 'G', 'B', ' '); static constexpr uint32_t kXYZ_PCSSpace = SetFourByteTag('X', 'Y', 'Z', ' '); @@ -225,10 +233,23 @@ private: static void compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]); static sp write_clut(const uint8_t* grid_points, const uint8_t* grid_16); + // Checks if a set of xyz tags is equivalent to a 3x3 Matrix. Each input + // tag buffer assumed to be at least kColorantTagSize in size. + static bool tagsEqualToMatrix(const Matrix3x3& matrix, + const uint8_t* red_tag, + const uint8_t* green_tag, + const uint8_t* blue_tag); + public: + // Output includes JPEG embedding identifier and chunk information, but not + // APPx information. static sp writeIccProfile(const ultrahdr_transfer_function tf, const ultrahdr_color_gamut gamut); + // NOTE: this function is not robust; it can infer gamuts that IccHelper + // writes out but should not be considered a reference implementation for + // robust parsing of ICC profiles or their gamuts. + static ultrahdr_color_gamut readIccColorGamut(void* icc_data, size_t icc_size); }; } // namespace android::ultrahdr -#endif //ANDROID_ULTRAHDR_ICC_H \ No newline at end of file +#endif //ANDROID_ULTRAHDR_ICC_H diff --git a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h index 4f2b7423c8..8b5499a2c0 100644 --- a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h +++ b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h @@ -83,11 +83,14 @@ public: */ size_t getEXIFSize(); /* - * Returns the position offset of EXIF package - * (4 bypes offset to FF sign, the byte after FF E1 XX XX ), - * or -1 if no EXIF exists. + * Returns the ICC data from the image. */ - int getEXIFPos() { return mExifPos; } + void* getICCPtr(); + /* + * Returns the decompressed ICC buffer size. This method must be called only after + * calling decompressImage() or getCompressedImageParameters(). + */ + size_t getICCSize(); /* * Decompresses metadata of the image. All vectors are owned by the caller. */ @@ -112,12 +115,12 @@ private: std::vector mXMPBuffer; // The buffer that holds EXIF Data. std::vector mEXIFBuffer; + // The buffer that holds ICC Data. + std::vector mICCBuffer; // Resolution of the decompressed image. size_t mWidth; size_t mHeight; - // Position of EXIF package, default value is -1 which means no EXIF package appears. - size_t mExifPos; }; } /* namespace android::ultrahdr */ diff --git a/libs/ultrahdr/include/ultrahdr/jpegr.h b/libs/ultrahdr/include/ultrahdr/jpegr.h index 1f9bd0f930..9546ca4762 100644 --- a/libs/ultrahdr/include/ultrahdr/jpegr.h +++ b/libs/ultrahdr/include/ultrahdr/jpegr.h @@ -125,7 +125,7 @@ public: * * Generate gain map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append * the gain map to the end of the compressed JPEG. HDR and SDR inputs must be the same - * resolution. + * resolution. SDR input is assumed to use the sRGB transfer function. * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format * @param hdr_tf transfer function of the HDR image @@ -152,7 +152,9 @@ public: * This method requires HAL Hardware JPEG encoder. * * Generate gain map from the HDR and SDR inputs, append the gain map to the end of the - * compressed JPEG. HDR and SDR inputs must be the same resolution and color space. + * compressed JPEG. Adds an ICC profile if one isn't present in the input JPEG image. HDR and + * SDR inputs must be the same resolution and color space. SDR image is assumed to use the sRGB + * transfer function. * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format * Note: the SDR image must be the decoded version of the JPEG @@ -178,8 +180,9 @@ public: * This method requires HAL Hardware JPEG encoder. * * Decode the compressed 8-bit JPEG image to YUV SDR, generate gain map from the HDR input - * and the decoded SDR result, append the gain map to the end of the compressed JPEG. HDR - * and SDR inputs must be the same resolution. + * and the decoded SDR result, append the gain map to the end of the compressed JPEG. Adds an + * ICC profile if one isn't present in the input JPEG image. HDR and SDR inputs must be the same + * resolution. JPEG image is assumed to use the sRGB transfer function. * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param compressed_jpeg_image compressed 8-bit JPEG image * @param hdr_tf transfer function of the HDR image @@ -198,7 +201,8 @@ public: * Encode API-4 * Assemble JPEGR image from SDR JPEG and gainmap JPEG. * - * Assemble the primary JPEG image, the gain map and the metadata to JPEG/R format. + * Assemble the primary JPEG image, the gain map and the metadata to JPEG/R format. Adds an ICC + * profile if one isn't present in the input JPEG image. * @param compressed_jpeg_image compressed 8-bit JPEG image * @param compressed_gainmap compressed 8-bit JPEG single channel image * @param metadata metadata to be written in XMP of the primary jpeg @@ -217,6 +221,9 @@ public: * Decode API * Decompress JPEGR image. * + * This method assumes that the JPEGR image contains an ICC profile with primaries that match + * those of a color gamut that this library is aware of; Bt.709, Display-P3, or Bt.2100. + * * @param compressed_jpegr_image compressed JPEGR image. * @param dest destination of the uncompressed JPEGR image. * @param max_display_boost (optional) the maximum available boost supported by a display, @@ -270,26 +277,30 @@ protected: /* * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and * 10-bit yuv images as input, and calculate the uncompressed gain map. The input images - * must be the same resolution. + * must be the same resolution. The SDR input is assumed to use the sRGB transfer function. * * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param hdr_tf transfer function of the HDR image * @param dest gain map; caller responsible for memory of data * @param metadata max_content_boost is filled in + * @param sdr_is_601 if true, then use BT.601 decoding of YUV regardless of SDR image gamut * @return NO_ERROR if calculation succeeds, error code if error occurs. */ status_t generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, jr_uncompressed_ptr uncompressed_p010_image, ultrahdr_transfer_function hdr_tf, ultrahdr_metadata_ptr metadata, - jr_uncompressed_ptr dest); + jr_uncompressed_ptr dest, + bool sdr_is_601 = false); /* * This method is called in the decoding pipeline. It will take the uncompressed (decoded) * 8-bit yuv image, the uncompressed (decoded) gain map, and extracted JPEG/R metadata as * input, and calculate the 10-bit recovered image. The recovered output image is the same * color gamut as the SDR image, with HLG transfer function, and is in RGBA1010102 data format. + * The SDR image is assumed to use the sRGB transfer function. The SDR image is also assumed to + * be a decoded JPEG for the purpose of YUV interpration. * * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format * @param uncompressed_gain_map uncompressed gain map @@ -353,6 +364,8 @@ private: * @param compressed_jpeg_image compressed 8-bit JPEG image * @param compress_gain_map compressed recover map * @param (nullable) exif EXIF package + * @param (nullable) icc ICC package + * @param icc_size length in bytes of ICC package * @param metadata JPEG/R metadata to encode in XMP of the jpeg * @param dest compressed JPEGR image * @return NO_ERROR if calculation succeeds, error code if error occurs. @@ -360,6 +373,7 @@ private: status_t appendGainMap(jr_compressed_ptr compressed_jpeg_image, jr_compressed_ptr compressed_gain_map, jr_exif_ptr exif, + void* icc, size_t icc_size, ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest); @@ -373,6 +387,22 @@ private: status_t toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest); + /* + * This method will convert a YUV420 image from one YUV encoding to another in-place (eg. + * Bt.709 to Bt.601 YUV encoding). + * + * src_encoding and dest_encoding indicate the encoding via the YUV conversion defined for that + * gamut. P3 indicates Rec.601, since this is how DataSpace encodes Display-P3 YUV data. + * + * @param image the YUV420 image to convert + * @param src_encoding input YUV encoding + * @param dest_encoding output YUV encoding + * @return NO_ERROR if calculation succeeds, error code if error occurs. + */ + status_t convertYuv(jr_uncompressed_ptr image, + ultrahdr_color_gamut src_encoding, + ultrahdr_color_gamut dest_encoding); + /* * This method will check the validity of the input arguments. * diff --git a/libs/ultrahdr/jpegdecoderhelper.cpp b/libs/ultrahdr/jpegdecoderhelper.cpp index 0bad4a4de0..fef544452a 100644 --- a/libs/ultrahdr/jpegdecoderhelper.cpp +++ b/libs/ultrahdr/jpegdecoderhelper.cpp @@ -93,7 +93,6 @@ static void jpegrerror_exit(j_common_ptr cinfo) { } JpegDecoderHelper::JpegDecoderHelper() { - mExifPos = 0; } JpegDecoderHelper::~JpegDecoderHelper() { @@ -138,6 +137,14 @@ size_t JpegDecoderHelper::getEXIFSize() { return mEXIFBuffer.size(); } +void* JpegDecoderHelper::getICCPtr() { + return mICCBuffer.data(); +} + +size_t JpegDecoderHelper::getICCSize() { + return mICCBuffer.size(); +} + size_t JpegDecoderHelper::getDecompressedImageWidth() { return mWidth; } @@ -168,31 +175,21 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) cinfo.src = &mgr; jpeg_read_header(&cinfo, TRUE); - // Save XMP data and EXIF data. - // Here we only handle the first XMP / EXIF package. - // The parameter pos is used for capturing start offset of EXIF, which is hacky, but working... + // Save XMP data, EXIF data, and ICC data. + // Here we only handle the first XMP / EXIF / ICC package. // We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package), // two bytes of package length which is stored in marker->original_length, and the real data - // which is stored in marker->data. The pos is adding up all previous package lengths ( - // 4 bytes marker and length, marker->original_length) before EXIF appears. Note that here we - // we are using marker->original_length instead of marker->data_length because in case the real - // package length is larger than the limitation, jpeg-turbo will only copy the data within the - // limitation (represented by data_length) and this may vary from original_length / real offset. - // A better solution is making jpeg_marker_struct holding the offset, but currently it doesn't. + // which is stored in marker->data. bool exifAppears = false; bool xmpAppears = false; - size_t pos = 2; // position after SOI + bool iccAppears = false; for (jpeg_marker_struct* marker = cinfo.marker_list; - marker && !(exifAppears && xmpAppears); + marker && !(exifAppears && xmpAppears && iccAppears); marker = marker->next) { - pos += 4; - pos += marker->original_length; - - if (marker->marker != kAPP1Marker) { + if (marker->marker != kAPP1Marker && marker->marker != kAPP2Marker) { continue; } - const unsigned int len = marker->data_length; if (!xmpAppears && len > kXmpNameSpace.size() && @@ -210,7 +207,12 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) mEXIFBuffer.resize(len, 0); memcpy(static_cast(mEXIFBuffer.data()), marker->data, len); exifAppears = true; - mExifPos = pos - marker->original_length; + } else if (!iccAppears && + len > sizeof(kICCSig) && + !memcmp(marker->data, kICCSig, sizeof(kICCSig))) { + mICCBuffer.resize(len, 0); + memcpy(static_cast(mICCBuffer.data()), marker->data, len); + iccAppears = true; } } @@ -228,6 +230,7 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { // We don't intend to support decoding grayscale to RGBA status = false; + ALOGE("%s: decoding grayscale to RGBA is unsupported", __func__); goto CleanUp; } // 4 bytes per pixel @@ -242,6 +245,7 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) cinfo.comp_info[1].v_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) { status = false; + ALOGE("%s: decoding to YUV only supports 4:2:0 subsampling", __func__); goto CleanUp; } mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0); @@ -304,8 +308,12 @@ bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int leng return false; } - *pWidth = cinfo.image_width; - *pHeight = cinfo.image_height; + if (pWidth != nullptr) { + *pWidth = cinfo.image_width; + } + if (pHeight != nullptr) { + *pHeight = cinfo.image_height; + } if (iccData != nullptr) { for (jpeg_marker_struct* marker = cinfo.marker_list; marker; @@ -318,9 +326,7 @@ bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int leng continue; } - const unsigned int len = marker->data_length - kICCMarkerHeaderSize; - const uint8_t *src = marker->data + kICCMarkerHeaderSize; - iccData->insert(iccData->end(), src, src+len); + iccData->insert(iccData->end(), marker->data, marker->data + marker->data_length); } } diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp index 415255d4ea..9af5af75e5 100644 --- a/libs/ultrahdr/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -258,6 +258,10 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, sp icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, uncompressed_yuv_420_image.colorGamut); + // Convert to Bt601 YUV encoding for JPEG encode + JPEGR_CHECK(convertYuv(&uncompressed_yuv_420_image, uncompressed_yuv_420_image.colorGamut, + ULTRAHDR_COLORGAMUT_P3)); + JpegEncoderHelper jpeg_encoder; if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data, uncompressed_yuv_420_image.width, @@ -269,7 +273,9 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpeg.data = jpeg_encoder.getCompressedImagePtr(); jpeg.length = jpeg_encoder.getCompressedImageSize(); - JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, &metadata, dest)); + // No ICC since JPEG encode already did it + JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0, + &metadata, dest)); return NO_ERROR; } @@ -317,10 +323,22 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, sp icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, uncompressed_yuv_420_image->colorGamut); + // Convert to Bt601 YUV encoding for JPEG encode; make a copy so as to no clobber client data + unique_ptr yuv_420_bt601_data = make_unique( + uncompressed_yuv_420_image->width * uncompressed_yuv_420_image->height * 3 / 2); + memcpy(yuv_420_bt601_data.get(), uncompressed_yuv_420_image->data, + uncompressed_yuv_420_image->width * uncompressed_yuv_420_image->height * 3 / 2); + + jpegr_uncompressed_struct yuv_420_bt601_image = { + yuv_420_bt601_data.get(), uncompressed_yuv_420_image->width, uncompressed_yuv_420_image->height, + uncompressed_yuv_420_image->colorGamut }; + JPEGR_CHECK(convertYuv(&yuv_420_bt601_image, yuv_420_bt601_image.colorGamut, + ULTRAHDR_COLORGAMUT_P3)); + JpegEncoderHelper jpeg_encoder; - if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data, - uncompressed_yuv_420_image->width, - uncompressed_yuv_420_image->height, quality, + if (!jpeg_encoder.compressImage(yuv_420_bt601_image.data, + yuv_420_bt601_image.width, + yuv_420_bt601_image.height, quality, icc->getData(), icc->getLength())) { return ERROR_JPEGR_ENCODE_ERROR; } @@ -328,7 +346,9 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpeg.data = jpeg_encoder.getCompressedImagePtr(); jpeg.length = jpeg_encoder.getCompressedImageSize(); - JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, &metadata, dest)); + // No ICC since jpeg encode already did it + JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0, + &metadata, dest)); return NO_ERROR; } @@ -371,7 +391,24 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr(); compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; - JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest)); + // We just want to check if ICC is present, so don't do a full decode. Note, + // this doesn't verify that the ICC is valid. + JpegDecoderHelper decoder; + std::vector icc; + decoder.getCompressedImageParameters(compressed_jpeg_image->data, compressed_jpeg_image->length, + /* pWidth */ nullptr, /* pHeight */ nullptr, + &icc, /* exifData */ nullptr); + + // Add ICC if not already present. + if (icc.size() > 0) { + JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr, + /* icc */ nullptr, /* icc size */ 0, &metadata, dest)); + } else { + sp newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, + uncompressed_yuv_420_image->colorGamut); + JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr, + newIcc->getData(), newIcc->getLength(), &metadata, dest)); + } return NO_ERROR; } @@ -392,6 +429,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return ret; } + // Note: output is Bt.601 YUV encoded regardless of gamut, due to jpeg decode. JpegDecoderHelper jpeg_decoder; if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) { return ERROR_JPEGR_DECODE_ERROR; @@ -411,8 +449,10 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, metadata.version = kJpegrVersion; jpegr_uncompressed_struct map; + // Indicate that the SDR image is Bt.601 YUV encoded. JPEGR_CHECK(generateGainMap( - &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map)); + &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map, + true /* sdr_is_601 */ )); std::unique_ptr map_data; map_data.reset(reinterpret_cast(map.data)); @@ -424,7 +464,24 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr(); compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; - JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest)); + // We just want to check if ICC is present, so don't do a full decode. Note, + // this doesn't verify that the ICC is valid. + JpegDecoderHelper decoder; + std::vector icc; + decoder.getCompressedImageParameters(compressed_jpeg_image->data, compressed_jpeg_image->length, + /* pWidth */ nullptr, /* pHeight */ nullptr, + &icc, /* exifData */ nullptr); + + // Add ICC if not already present. + if (icc.size() > 0) { + JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr, + /* icc */ nullptr, /* icc size */ 0, &metadata, dest)); + } else { + sp newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, + uncompressed_yuv_420_image.colorGamut); + JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr, + newIcc->getData(), newIcc->getLength(), &metadata, dest)); + } return NO_ERROR; } @@ -449,8 +506,25 @@ status_t JpegR::encodeJPEGR(jr_compressed_ptr compressed_jpeg_image, return ERROR_JPEGR_INVALID_NULL_PTR; } - JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr, - metadata, dest)); + // We just want to check if ICC is present, so don't do a full decode. Note, + // this doesn't verify that the ICC is valid. + JpegDecoderHelper decoder; + std::vector icc; + decoder.getCompressedImageParameters(compressed_jpeg_image->data, compressed_jpeg_image->length, + /* pWidth */ nullptr, /* pHeight */ nullptr, + &icc, /* exifData */ nullptr); + + // Add ICC if not already present. + if (icc.size() > 0) { + JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr, + /* icc */ nullptr, /* icc size */ 0, metadata, dest)); + } else { + sp newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, + compressed_jpeg_image->colorGamut); + JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr, + newIcc->getData(), newIcc->getLength(), metadata, dest)); + } + return NO_ERROR; } @@ -613,6 +687,9 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr(); uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth(); uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight(); + uncompressed_yuv_420_image.colorGamut = IccHelper::readIccColorGamut( + jpeg_decoder.getICCPtr(), jpeg_decoder.getICCSize()); + JPEGR_CHECK(applyGainMap(&uncompressed_yuv_420_image, &map, &uhdr_metadata, output_format, max_display_boost, dest)); return NO_ERROR; @@ -624,6 +701,7 @@ status_t JpegR::compressGainMap(jr_uncompressed_ptr uncompressed_gain_map, return ERROR_JPEGR_INVALID_NULL_PTR; } + // Don't need to convert YUV to Bt601 since single channel if (!jpeg_encoder->compressImage(uncompressed_gain_map->data, uncompressed_gain_map->width, uncompressed_gain_map->height, @@ -699,7 +777,8 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, jr_uncompressed_ptr uncompressed_p010_image, ultrahdr_transfer_function hdr_tf, ultrahdr_metadata_ptr metadata, - jr_uncompressed_ptr dest) { + jr_uncompressed_ptr dest, + bool sdr_is_601) { if (uncompressed_yuv_420_image == nullptr || uncompressed_p010_image == nullptr || metadata == nullptr @@ -768,15 +847,38 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut); ColorCalculationFn luminanceFn = nullptr; + ColorTransformFn sdrYuvToRgbFn = nullptr; switch (uncompressed_yuv_420_image->colorGamut) { case ULTRAHDR_COLORGAMUT_BT709: luminanceFn = srgbLuminance; + sdrYuvToRgbFn = srgbYuvToRgb; break; case ULTRAHDR_COLORGAMUT_P3: luminanceFn = p3Luminance; + sdrYuvToRgbFn = p3YuvToRgb; break; case ULTRAHDR_COLORGAMUT_BT2100: luminanceFn = bt2100Luminance; + sdrYuvToRgbFn = bt2100YuvToRgb; + break; + case ULTRAHDR_COLORGAMUT_UNSPECIFIED: + // Should be impossible to hit after input validation. + return ERROR_JPEGR_INVALID_COLORGAMUT; + } + if (sdr_is_601) { + sdrYuvToRgbFn = p3YuvToRgb; + } + + ColorTransformFn hdrYuvToRgbFn = nullptr; + switch (uncompressed_p010_image->colorGamut) { + case ULTRAHDR_COLORGAMUT_BT709: + hdrYuvToRgbFn = srgbYuvToRgb; + break; + case ULTRAHDR_COLORGAMUT_P3: + hdrYuvToRgbFn = p3YuvToRgb; + break; + case ULTRAHDR_COLORGAMUT_BT2100: + hdrYuvToRgbFn = bt2100YuvToRgb; break; case ULTRAHDR_COLORGAMUT_UNSPECIFIED: // Should be impossible to hit after input validation. @@ -790,8 +892,8 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, std::function generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image, metadata, dest, hdrInvOetf, hdrGamutConversionFn, - luminanceFn, hdr_white_nits, log2MinBoost, log2MaxBoost, - &jobQueue]() -> void { + luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, hdr_white_nits, + log2MinBoost, log2MaxBoost, &jobQueue]() -> void { size_t rowStart, rowEnd; size_t dest_map_width = uncompressed_yuv_420_image->width / kMapDimensionScaleFactor; size_t dest_map_stride = dest->width; @@ -800,7 +902,8 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, for (size_t x = 0; x < dest_map_width; ++x) { Color sdr_yuv_gamma = sampleYuv420(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y); - Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma); + Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma); + // We are assuming the SDR input is always sRGB transfer. #if USE_SRGB_INVOETF_LUT Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma); #else @@ -809,7 +912,7 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits; Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y); - Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma); + Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma); Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma); hdr_rgb = hdrGamutConversionFn(hdr_rgb); float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits; @@ -887,7 +990,9 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, for (size_t y = rowStart; y < rowEnd; ++y) { for (size_t x = 0; x < width; ++x) { Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y); - Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr); + // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients + Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr); + // We are assuming the SDR base image is always sRGB transfer. #if USE_SRGB_INVOETF_LUT Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr); #else @@ -1065,6 +1170,7 @@ status_t JpegR::extractGainMap(jr_compressed_ptr compressed_jpegr_image, status_t JpegR::appendGainMap(jr_compressed_ptr compressed_jpeg_image, jr_compressed_ptr compressed_gain_map, jr_exif_ptr exif, + void* icc, size_t icc_size, ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest) { if (compressed_jpeg_image == nullptr @@ -1128,6 +1234,18 @@ status_t JpegR::appendGainMap(jr_compressed_ptr compressed_jpeg_image, JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos)); } + // Write ICC + if (icc != nullptr && icc_size > 0) { + const int length = icc_size + 2; + const uint8_t lengthH = ((length >> 8) & 0xff); + const uint8_t lengthL = (length & 0xff); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos)); + JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); + JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); + JPEGR_CHECK(Write(dest, icc, icc_size, pos)); + } + // Prepare and write MPF { const int length = 2 + calculateMpfSize(); @@ -1235,4 +1353,82 @@ status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) { return NO_ERROR; } +status_t JpegR::convertYuv(jr_uncompressed_ptr image, + ultrahdr_color_gamut src_encoding, + ultrahdr_color_gamut dest_encoding) { + if (image == nullptr) { + return ERROR_JPEGR_INVALID_NULL_PTR; + } + + if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED + || dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) { + return ERROR_JPEGR_INVALID_COLORGAMUT; + } + + ColorTransformFn conversionFn = nullptr; + switch (src_encoding) { + case ULTRAHDR_COLORGAMUT_BT709: + switch (dest_encoding) { + case ULTRAHDR_COLORGAMUT_BT709: + return NO_ERROR; + case ULTRAHDR_COLORGAMUT_P3: + conversionFn = yuv709To601; + break; + case ULTRAHDR_COLORGAMUT_BT2100: + conversionFn = yuv709To2100; + break; + default: + // Should be impossible to hit after input validation + return ERROR_JPEGR_INVALID_COLORGAMUT; + } + break; + case ULTRAHDR_COLORGAMUT_P3: + switch (dest_encoding) { + case ULTRAHDR_COLORGAMUT_BT709: + conversionFn = yuv601To709; + break; + case ULTRAHDR_COLORGAMUT_P3: + return NO_ERROR; + case ULTRAHDR_COLORGAMUT_BT2100: + conversionFn = yuv601To2100; + break; + default: + // Should be impossible to hit after input validation + return ERROR_JPEGR_INVALID_COLORGAMUT; + } + break; + case ULTRAHDR_COLORGAMUT_BT2100: + switch (dest_encoding) { + case ULTRAHDR_COLORGAMUT_BT709: + conversionFn = yuv2100To709; + break; + case ULTRAHDR_COLORGAMUT_P3: + conversionFn = yuv2100To601; + break; + case ULTRAHDR_COLORGAMUT_BT2100: + return NO_ERROR; + default: + // Should be impossible to hit after input validation + return ERROR_JPEGR_INVALID_COLORGAMUT; + } + break; + default: + // Should be impossible to hit after input validation + return ERROR_JPEGR_INVALID_COLORGAMUT; + } + + if (conversionFn == nullptr) { + // Should be impossible to hit after input validation + return ERROR_JPEGR_INVALID_COLORGAMUT; + } + + for (size_t y = 0; y < image->height / 2; ++y) { + for (size_t x = 0; x < image->width / 2; ++x) { + transformYuv420(image, x, y, conversionFn); + } + } + + return NO_ERROR; +} + } // namespace android::ultrahdr diff --git a/libs/ultrahdr/tests/Android.bp b/libs/ultrahdr/tests/Android.bp index 7dd9d04fbd..594413018c 100644 --- a/libs/ultrahdr/tests/Android.bp +++ b/libs/ultrahdr/tests/Android.bp @@ -25,8 +25,9 @@ cc_test { name: "libultrahdr_test", test_suites: ["device-tests"], srcs: [ - "jpegr_test.cpp", "gainmapmath_test.cpp", + "icchelper_test.cpp", + "jpegr_test.cpp", ], shared_libs: [ "libimage_io", @@ -72,5 +73,7 @@ cc_test { static_libs: [ "libgtest", "libjpegdecoder", + "libultrahdr", + "libutils", ], } diff --git a/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg b/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f61e0e85251895ad4f386dedcc86ceaf6a5de196 GIT binary patch literal 37101 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 literal 0 HcmV?d00001 diff --git a/libs/ultrahdr/tests/gainmapmath_test.cpp b/libs/ultrahdr/tests/gainmapmath_test.cpp index c456653821..af90365e56 100644 --- a/libs/ultrahdr/tests/gainmapmath_test.cpp +++ b/libs/ultrahdr/tests/gainmapmath_test.cpp @@ -28,6 +28,7 @@ public: float ComparisonEpsilon() { return 1e-4f; } float LuminanceEpsilon() { return 1e-2f; } + float YuvConversionEpsilon() { return 1.0f / (255.0f * 2.0f); } Color Yuv420(uint8_t y, uint8_t u, uint8_t v) { return {{{ static_cast(y) / 255.0f, @@ -63,9 +64,13 @@ public: Color YuvBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; } Color YuvWhite() { return {{{ 1.0f, 0.0f, 0.0f }}}; } - Color SrgbYuvRed() { return {{{ 0.299f, -0.1687f, 0.5f }}}; } - Color SrgbYuvGreen() { return {{{ 0.587f, -0.3313f, -0.4187f }}}; } - Color SrgbYuvBlue() { return {{{ 0.114f, 0.5f, -0.0813f }}}; } + Color SrgbYuvRed() { return {{{ 0.2126f, -0.11457f, 0.5f }}}; } + Color SrgbYuvGreen() { return {{{ 0.7152f, -0.38543f, -0.45415f }}}; } + Color SrgbYuvBlue() { return {{{ 0.0722f, 0.5f, -0.04585f }}}; } + + Color P3YuvRed() { return {{{ 0.299f, -0.16874f, 0.5f }}}; } + Color P3YuvGreen() { return {{{ 0.587f, -0.33126f, -0.41869f }}}; } + Color P3YuvBlue() { return {{{ 0.114f, 0.5f, -0.08131f }}}; } Color Bt2100YuvRed() { return {{{ 0.2627f, -0.13963f, 0.5f }}}; } Color Bt2100YuvGreen() { return {{{ 0.6780f, -0.36037f, -0.45979f }}}; } @@ -78,6 +83,13 @@ public: return luminance_scaled * kSdrWhiteNits; } + float P3YuvToLuminance(Color yuv_gamma, ColorCalculationFn luminanceFn) { + Color rgb_gamma = p3YuvToRgb(yuv_gamma); + Color rgb = srgbInvOetf(rgb_gamma); + float luminance_scaled = luminanceFn(rgb); + return luminance_scaled * kSdrWhiteNits; + } + float Bt2100YuvToLuminance(Color yuv_gamma, ColorTransformFn hdrInvOetf, ColorTransformFn gamutConversionFn, ColorCalculationFn luminanceFn, float scale_factor) { @@ -402,6 +414,56 @@ TEST_F(GainMapMathTest, P3Luminance) { EXPECT_FLOAT_EQ(p3Luminance(RgbBlue()), 0.06891f); } +TEST_F(GainMapMathTest, P3YuvToRgb) { + Color rgb_black = p3YuvToRgb(YuvBlack()); + EXPECT_RGB_NEAR(rgb_black, RgbBlack()); + + Color rgb_white = p3YuvToRgb(YuvWhite()); + EXPECT_RGB_NEAR(rgb_white, RgbWhite()); + + Color rgb_r = p3YuvToRgb(P3YuvRed()); + EXPECT_RGB_NEAR(rgb_r, RgbRed()); + + Color rgb_g = p3YuvToRgb(P3YuvGreen()); + EXPECT_RGB_NEAR(rgb_g, RgbGreen()); + + Color rgb_b = p3YuvToRgb(P3YuvBlue()); + EXPECT_RGB_NEAR(rgb_b, RgbBlue()); +} + +TEST_F(GainMapMathTest, P3RgbToYuv) { + Color yuv_black = p3RgbToYuv(RgbBlack()); + EXPECT_YUV_NEAR(yuv_black, YuvBlack()); + + Color yuv_white = p3RgbToYuv(RgbWhite()); + EXPECT_YUV_NEAR(yuv_white, YuvWhite()); + + Color yuv_r = p3RgbToYuv(RgbRed()); + EXPECT_YUV_NEAR(yuv_r, P3YuvRed()); + + Color yuv_g = p3RgbToYuv(RgbGreen()); + EXPECT_YUV_NEAR(yuv_g, P3YuvGreen()); + + Color yuv_b = p3RgbToYuv(RgbBlue()); + EXPECT_YUV_NEAR(yuv_b, P3YuvBlue()); +} + +TEST_F(GainMapMathTest, P3RgbYuvRoundtrip) { + Color rgb_black = p3YuvToRgb(p3RgbToYuv(RgbBlack())); + EXPECT_RGB_NEAR(rgb_black, RgbBlack()); + + Color rgb_white = p3YuvToRgb(p3RgbToYuv(RgbWhite())); + EXPECT_RGB_NEAR(rgb_white, RgbWhite()); + + Color rgb_r = p3YuvToRgb(p3RgbToYuv(RgbRed())); + EXPECT_RGB_NEAR(rgb_r, RgbRed()); + + Color rgb_g = p3YuvToRgb(p3RgbToYuv(RgbGreen())); + EXPECT_RGB_NEAR(rgb_g, RgbGreen()); + + Color rgb_b = p3YuvToRgb(p3RgbToYuv(RgbBlue())); + EXPECT_RGB_NEAR(rgb_b, RgbBlue()); +} TEST_F(GainMapMathTest, Bt2100Luminance) { EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlack()), 0.0f); EXPECT_FLOAT_EQ(bt2100Luminance(RgbWhite()), 1.0f); @@ -461,6 +523,163 @@ TEST_F(GainMapMathTest, Bt2100RgbYuvRoundtrip) { EXPECT_RGB_NEAR(rgb_b, RgbBlue()); } +TEST_F(GainMapMathTest, Bt709ToBt601YuvConversion) { + Color yuv_black = srgbRgbToYuv(RgbBlack()); + EXPECT_YUV_NEAR(yuv709To601(yuv_black), YuvBlack()); + + Color yuv_white = srgbRgbToYuv(RgbWhite()); + EXPECT_YUV_NEAR(yuv709To601(yuv_white), YuvWhite()); + + Color yuv_r = srgbRgbToYuv(RgbRed()); + EXPECT_YUV_NEAR(yuv709To601(yuv_r), P3YuvRed()); + + Color yuv_g = srgbRgbToYuv(RgbGreen()); + EXPECT_YUV_NEAR(yuv709To601(yuv_g), P3YuvGreen()); + + Color yuv_b = srgbRgbToYuv(RgbBlue()); + EXPECT_YUV_NEAR(yuv709To601(yuv_b), P3YuvBlue()); +} + +TEST_F(GainMapMathTest, Bt709ToBt2100YuvConversion) { + Color yuv_black = srgbRgbToYuv(RgbBlack()); + EXPECT_YUV_NEAR(yuv709To2100(yuv_black), YuvBlack()); + + Color yuv_white = srgbRgbToYuv(RgbWhite()); + EXPECT_YUV_NEAR(yuv709To2100(yuv_white), YuvWhite()); + + Color yuv_r = srgbRgbToYuv(RgbRed()); + EXPECT_YUV_NEAR(yuv709To2100(yuv_r), Bt2100YuvRed()); + + Color yuv_g = srgbRgbToYuv(RgbGreen()); + EXPECT_YUV_NEAR(yuv709To2100(yuv_g), Bt2100YuvGreen()); + + Color yuv_b = srgbRgbToYuv(RgbBlue()); + EXPECT_YUV_NEAR(yuv709To2100(yuv_b), Bt2100YuvBlue()); +} + +TEST_F(GainMapMathTest, Bt601ToBt709YuvConversion) { + Color yuv_black = p3RgbToYuv(RgbBlack()); + EXPECT_YUV_NEAR(yuv601To709(yuv_black), YuvBlack()); + + Color yuv_white = p3RgbToYuv(RgbWhite()); + EXPECT_YUV_NEAR(yuv601To709(yuv_white), YuvWhite()); + + Color yuv_r = p3RgbToYuv(RgbRed()); + EXPECT_YUV_NEAR(yuv601To709(yuv_r), SrgbYuvRed()); + + Color yuv_g = p3RgbToYuv(RgbGreen()); + EXPECT_YUV_NEAR(yuv601To709(yuv_g), SrgbYuvGreen()); + + Color yuv_b = p3RgbToYuv(RgbBlue()); + EXPECT_YUV_NEAR(yuv601To709(yuv_b), SrgbYuvBlue()); +} + +TEST_F(GainMapMathTest, Bt601ToBt2100YuvConversion) { + Color yuv_black = p3RgbToYuv(RgbBlack()); + EXPECT_YUV_NEAR(yuv601To2100(yuv_black), YuvBlack()); + + Color yuv_white = p3RgbToYuv(RgbWhite()); + EXPECT_YUV_NEAR(yuv601To2100(yuv_white), YuvWhite()); + + Color yuv_r = p3RgbToYuv(RgbRed()); + EXPECT_YUV_NEAR(yuv601To2100(yuv_r), Bt2100YuvRed()); + + Color yuv_g = p3RgbToYuv(RgbGreen()); + EXPECT_YUV_NEAR(yuv601To2100(yuv_g), Bt2100YuvGreen()); + + Color yuv_b = p3RgbToYuv(RgbBlue()); + EXPECT_YUV_NEAR(yuv601To2100(yuv_b), Bt2100YuvBlue()); +} + +TEST_F(GainMapMathTest, Bt2100ToBt709YuvConversion) { + Color yuv_black = bt2100RgbToYuv(RgbBlack()); + EXPECT_YUV_NEAR(yuv2100To709(yuv_black), YuvBlack()); + + Color yuv_white = bt2100RgbToYuv(RgbWhite()); + EXPECT_YUV_NEAR(yuv2100To709(yuv_white), YuvWhite()); + + Color yuv_r = bt2100RgbToYuv(RgbRed()); + EXPECT_YUV_NEAR(yuv2100To709(yuv_r), SrgbYuvRed()); + + Color yuv_g = bt2100RgbToYuv(RgbGreen()); + EXPECT_YUV_NEAR(yuv2100To709(yuv_g), SrgbYuvGreen()); + + Color yuv_b = bt2100RgbToYuv(RgbBlue()); + EXPECT_YUV_NEAR(yuv2100To709(yuv_b), SrgbYuvBlue()); +} + +TEST_F(GainMapMathTest, Bt2100ToBt601YuvConversion) { + Color yuv_black = bt2100RgbToYuv(RgbBlack()); + EXPECT_YUV_NEAR(yuv2100To601(yuv_black), YuvBlack()); + + Color yuv_white = bt2100RgbToYuv(RgbWhite()); + EXPECT_YUV_NEAR(yuv2100To601(yuv_white), YuvWhite()); + + Color yuv_r = bt2100RgbToYuv(RgbRed()); + EXPECT_YUV_NEAR(yuv2100To601(yuv_r), P3YuvRed()); + + Color yuv_g = bt2100RgbToYuv(RgbGreen()); + EXPECT_YUV_NEAR(yuv2100To601(yuv_g), P3YuvGreen()); + + Color yuv_b = bt2100RgbToYuv(RgbBlue()); + EXPECT_YUV_NEAR(yuv2100To601(yuv_b), P3YuvBlue()); +} + +TEST_F(GainMapMathTest, TransformYuv420) { + ColorTransformFn transforms[] = { yuv709To601, yuv709To2100, yuv601To709, yuv601To2100, + yuv2100To709, yuv2100To601 }; + for (const ColorTransformFn& transform : transforms) { + jpegr_uncompressed_struct input = Yuv420Image(); + + size_t out_buf_size = input.width * input.height * 3 / 2; + std::unique_ptr out_buf = std::make_unique(out_buf_size); + memcpy(out_buf.get(), input.data, out_buf_size); + jpegr_uncompressed_struct output = Yuv420Image(); + output.data = out_buf.get(); + + transformYuv420(&output, 1, 1, transform); + + for (size_t y = 0; y < 4; ++y) { + for (size_t x = 0; x < 4; ++x) { + // Skip the last chroma sample, which we modified above + if (x >= 2 && y >= 2) { + continue; + } + + // All other pixels should remain unchanged + EXPECT_YUV_EQ(getYuv420Pixel(&input, x, y), getYuv420Pixel(&output, x, y)); + } + } + + // modified pixels should be updated as intended by the transformYuv420 algorithm + Color in1 = getYuv420Pixel(&input, 2, 2); + Color in2 = getYuv420Pixel(&input, 3, 2); + Color in3 = getYuv420Pixel(&input, 2, 3); + Color in4 = getYuv420Pixel(&input, 3, 3); + Color out1 = getYuv420Pixel(&output, 2, 2); + Color out2 = getYuv420Pixel(&output, 3, 2); + Color out3 = getYuv420Pixel(&output, 2, 3); + Color out4 = getYuv420Pixel(&output, 3, 3); + + EXPECT_NEAR(transform(in1).y, out1.y, YuvConversionEpsilon()); + EXPECT_NEAR(transform(in2).y, out2.y, YuvConversionEpsilon()); + EXPECT_NEAR(transform(in3).y, out3.y, YuvConversionEpsilon()); + EXPECT_NEAR(transform(in4).y, out4.y, YuvConversionEpsilon()); + + Color expect_uv = (transform(in1) + transform(in2) + transform(in3) + transform(in4)) / 4.0f; + + EXPECT_NEAR(expect_uv.u, out1.u, YuvConversionEpsilon()); + EXPECT_NEAR(expect_uv.u, out2.u, YuvConversionEpsilon()); + EXPECT_NEAR(expect_uv.u, out3.u, YuvConversionEpsilon()); + EXPECT_NEAR(expect_uv.u, out4.u, YuvConversionEpsilon()); + + EXPECT_NEAR(expect_uv.v, out1.v, YuvConversionEpsilon()); + EXPECT_NEAR(expect_uv.v, out2.v, YuvConversionEpsilon()); + EXPECT_NEAR(expect_uv.v, out3.v, YuvConversionEpsilon()); + EXPECT_NEAR(expect_uv.v, out4.v, YuvConversionEpsilon()); + } +} + TEST_F(GainMapMathTest, HlgOetf) { EXPECT_FLOAT_EQ(hlgOetf(0.0f), 0.0f); EXPECT_NEAR(hlgOetf(0.04167f), 0.35357f, ComparisonEpsilon()); @@ -693,7 +912,7 @@ TEST_F(GainMapMathTest, ColorConversionLookup) { TEST_F(GainMapMathTest, EncodeGain) { ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f, - .minContentBoost = 1.0f / 4.0f }; + .minContentBoost = 1.0f / 4.0f }; EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 127); EXPECT_EQ(encodeGain(0.0f, 1.0f, &metadata), 127); @@ -751,7 +970,7 @@ TEST_F(GainMapMathTest, EncodeGain) { TEST_F(GainMapMathTest, ApplyGain) { ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f, - .minContentBoost = 1.0f / 4.0f }; + .minContentBoost = 1.0f / 4.0f }; float displayBoost = metadata.maxContentBoost; EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.0f, &metadata), RgbBlack()); diff --git a/libs/ultrahdr/tests/icchelper_test.cpp b/libs/ultrahdr/tests/icchelper_test.cpp new file mode 100644 index 0000000000..ff61c08574 --- /dev/null +++ b/libs/ultrahdr/tests/icchelper_test.cpp @@ -0,0 +1,77 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +namespace android::ultrahdr { + +class IccHelperTest : public testing::Test { +public: + IccHelperTest(); + ~IccHelperTest(); +protected: + virtual void SetUp(); + virtual void TearDown(); +}; + +IccHelperTest::IccHelperTest() {} + +IccHelperTest::~IccHelperTest() {} + +void IccHelperTest::SetUp() {} + +void IccHelperTest::TearDown() {} + +TEST_F(IccHelperTest, iccWriteThenRead) { + sp iccBt709 = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, + ULTRAHDR_COLORGAMUT_BT709); + ASSERT_NE(iccBt709->getLength(), 0); + ASSERT_NE(iccBt709->getData(), nullptr); + EXPECT_EQ(IccHelper::readIccColorGamut(iccBt709->getData(), iccBt709->getLength()), + ULTRAHDR_COLORGAMUT_BT709); + + sp iccP3 = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_P3); + ASSERT_NE(iccP3->getLength(), 0); + ASSERT_NE(iccP3->getData(), nullptr); + EXPECT_EQ(IccHelper::readIccColorGamut(iccP3->getData(), iccP3->getLength()), + ULTRAHDR_COLORGAMUT_P3); + + sp iccBt2100 = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, + ULTRAHDR_COLORGAMUT_BT2100); + ASSERT_NE(iccBt2100->getLength(), 0); + ASSERT_NE(iccBt2100->getData(), nullptr); + EXPECT_EQ(IccHelper::readIccColorGamut(iccBt2100->getData(), iccBt2100->getLength()), + ULTRAHDR_COLORGAMUT_BT2100); +} + +TEST_F(IccHelperTest, iccEndianness) { + sp icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_BT709); + size_t profile_size = icc->getLength() - kICCIdentifierSize; + + uint8_t* icc_bytes = reinterpret_cast(icc->getData()) + kICCIdentifierSize; + uint32_t encoded_size = static_cast(icc_bytes[0]) << 24 | + static_cast(icc_bytes[1]) << 16 | + static_cast(icc_bytes[2]) << 8 | + static_cast(icc_bytes[3]); + + EXPECT_EQ(static_cast(encoded_size), profile_size); +} + +} // namespace android::ultrahdr + diff --git a/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp index c79dbe328b..e2da01c373 100644 --- a/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp +++ b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp @@ -15,6 +15,7 @@ */ #include +#include #include #include @@ -22,11 +23,19 @@ namespace android::ultrahdr { +// No ICC or EXIF #define YUV_IMAGE "/sdcard/Documents/minnie-320x240-yuv.jpg" #define YUV_IMAGE_SIZE 20193 +// Has ICC and EXIF +#define YUV_ICC_IMAGE "/sdcard/Documents/minnie-320x240-yuv-icc.jpg" +#define YUV_ICC_IMAGE_SIZE 34266 +// No ICC or EXIF #define GREY_IMAGE "/sdcard/Documents/minnie-320x240-y.jpg" #define GREY_IMAGE_SIZE 20193 +#define IMAGE_WIDTH 320 +#define IMAGE_HEIGHT 240 + class JpegDecoderHelperTest : public testing::Test { public: struct Image { @@ -39,7 +48,7 @@ protected: virtual void SetUp(); virtual void TearDown(); - Image mYuvImage, mGreyImage; + Image mYuvImage, mYuvIccImage, mGreyImage; }; JpegDecoderHelperTest::JpegDecoderHelperTest() {} @@ -79,6 +88,10 @@ void JpegDecoderHelperTest::SetUp() { FAIL() << "Load file " << YUV_IMAGE << " failed"; } mYuvImage.size = YUV_IMAGE_SIZE; + if (!loadFile(YUV_ICC_IMAGE, &mYuvIccImage)) { + FAIL() << "Load file " << YUV_ICC_IMAGE << " failed"; + } + mYuvIccImage.size = YUV_ICC_IMAGE_SIZE; if (!loadFile(GREY_IMAGE, &mGreyImage)) { FAIL() << "Load file " << GREY_IMAGE << " failed"; } @@ -91,6 +104,16 @@ TEST_F(JpegDecoderHelperTest, decodeYuvImage) { JpegDecoderHelper decoder; EXPECT_TRUE(decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size)); ASSERT_GT(decoder.getDecompressedImageSize(), static_cast(0)); + EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()), + ULTRAHDR_COLORGAMUT_UNSPECIFIED); +} + +TEST_F(JpegDecoderHelperTest, decodeYuvIccImage) { + JpegDecoderHelper decoder; + EXPECT_TRUE(decoder.decompressImage(mYuvIccImage.buffer.get(), mYuvIccImage.size)); + ASSERT_GT(decoder.getDecompressedImageSize(), static_cast(0)); + EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()), + ULTRAHDR_COLORGAMUT_BT709); } TEST_F(JpegDecoderHelperTest, decodeGreyImage) { @@ -99,4 +122,35 @@ TEST_F(JpegDecoderHelperTest, decodeGreyImage) { ASSERT_GT(decoder.getDecompressedImageSize(), static_cast(0)); } -} // namespace android::ultrahdr \ No newline at end of file +TEST_F(JpegDecoderHelperTest, getCompressedImageParameters) { + size_t width = 0, height = 0; + std::vector icc, exif; + + JpegDecoderHelper decoder; + EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvImage.buffer.get(), mYuvImage.size, + &width, &height, &icc, &exif)); + + EXPECT_EQ(width, IMAGE_WIDTH); + EXPECT_EQ(height, IMAGE_HEIGHT); + EXPECT_EQ(icc.size(), 0); + EXPECT_EQ(exif.size(), 0); +} + +TEST_F(JpegDecoderHelperTest, getCompressedImageParametersIcc) { + size_t width = 0, height = 0; + std::vector icc, exif; + + JpegDecoderHelper decoder; + EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvIccImage.buffer.get(), mYuvIccImage.size, + &width, &height, &icc, &exif)); + + EXPECT_EQ(width, IMAGE_WIDTH); + EXPECT_EQ(height, IMAGE_HEIGHT); + EXPECT_GT(icc.size(), 0); + EXPECT_GT(exif.size(), 0); + + EXPECT_EQ(IccHelper::readIccColorGamut(icc.data(), icc.size()), + ULTRAHDR_COLORGAMUT_BT709); +} + +} // namespace android::ultrahdr -- GitLab From 094946bd8c1a8f205417a21755023118857950f7 Mon Sep 17 00:00:00 2001 From: Nick Deakin Date: Fri, 9 Jun 2023 11:58:41 -0400 Subject: [PATCH 1293/1310] ultrahdr: update metadata requirements This updates metadata requirements for Version, GainMapMax, and HDRCapacityMax to be required, and all others optional. Also update the yuv-icc test image, since it seems this file was missed on a previous CL, and the checked in version was 4:4:4 instad of 4:2:0, which doesn't work with our decoder setup. Bug: 286457050 Test: Added new tests, all tests pass Change-Id: I1a6669f7bc6a8f254c4ce4ea4d359008e8e10f3e --- libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp | 7 +- libs/ultrahdr/include/ultrahdr/gainmapmath.h | 8 + libs/ultrahdr/include/ultrahdr/jpegr.h | 9 +- .../include/ultrahdr/jpegrerrorcode.h | 2 + libs/ultrahdr/include/ultrahdr/ultrahdr.h | 16 +- libs/ultrahdr/jpegr.cpp | 56 +++- libs/ultrahdr/jpegrutils.cpp | 254 +++++++++++++++++- .../tests/data/minnie-320x240-yuv-icc.jpg | Bin 37101 -> 34266 bytes libs/ultrahdr/tests/jpegr_test.cpp | 64 ++++- 9 files changed, 394 insertions(+), 22 deletions(-) diff --git a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp index acb9b795c0..bbe58e0f2e 100644 --- a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp +++ b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp @@ -249,7 +249,7 @@ void UltraHdrEncFuzzer::process() { jpegGainMap.data = gainMapEncoder.getCompressedImagePtr(); jpegGainMap.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; ultrahdr_metadata_struct metadata; - metadata.version = "1.3.1"; + metadata.version = "1.0"; if (tf == ULTRAHDR_TF_HLG) { metadata.maxContentBoost = kHlgMaxNits / kSdrWhiteNits; } else if (tf == ULTRAHDR_TF_PQ) { @@ -258,6 +258,11 @@ void UltraHdrEncFuzzer::process() { metadata.maxContentBoost = 1.0f; } metadata.minContentBoost = 1.0f; + metadata.gamma = 1.0f; + metadata.offsetSdr = 0.0f; + metadata.offsetHdr = 0.0f; + metadata.hdrCapacityMin = 1.0f; + metadata.hdrCapacityMax = metadata.maxContentBoost; status = jpegHdr.encodeJPEGR(&jpegImg, &jpegGainMap, &metadata, &jpegImgR); } } diff --git a/libs/ultrahdr/include/ultrahdr/gainmapmath.h b/libs/ultrahdr/include/ultrahdr/gainmapmath.h index 13832db752..edf152d8ed 100644 --- a/libs/ultrahdr/include/ultrahdr/gainmapmath.h +++ b/libs/ultrahdr/include/ultrahdr/gainmapmath.h @@ -414,6 +414,10 @@ void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma /* * Calculate the 8-bit unsigned integer gain value for the given SDR and HDR * luminances in linear space, and the hdr ratio to encode against. + * + * Note: since this library always uses gamma of 1.0, offsetSdr of 0.0, and + * offsetHdr of 0.0, this function doesn't handle different metadata values for + * these fields. */ uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata); uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata, @@ -422,6 +426,10 @@ uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata, /* * Calculates the linear luminance in nits after applying the given gain * value, with the given hdr ratio, to the given sdr input in the range [0, 1]. + * + * Note: similar to encodeGain(), this function only supports gamma 1.0, + * offsetSdr 0.0, offsetHdr 0.0, hdrCapacityMin 1.0, and hdrCapacityMax equal to + * gainMapMax, as this library encodes. */ Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata); Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata, float displayBoost); diff --git a/libs/ultrahdr/include/ultrahdr/jpegr.h b/libs/ultrahdr/include/ultrahdr/jpegr.h index 9546ca4762..a35fd30634 100644 --- a/libs/ultrahdr/include/ultrahdr/jpegr.h +++ b/libs/ultrahdr/include/ultrahdr/jpegr.h @@ -222,7 +222,11 @@ public: * Decompress JPEGR image. * * This method assumes that the JPEGR image contains an ICC profile with primaries that match - * those of a color gamut that this library is aware of; Bt.709, Display-P3, or Bt.2100. + * those of a color gamut that this library is aware of; Bt.709, Display-P3, or Bt.2100. It also + * assumes the base image uses the sRGB transfer function. + * + * This method only supports single gain map metadata values for fields that allow multi-channel + * metadata values. * * @param compressed_jpegr_image compressed JPEGR image. * @param dest destination of the uncompressed JPEGR image. @@ -265,6 +269,9 @@ public: /* * Gets Info from JPEGR file without decoding it. * + * This method only supports single gain map metadata values for fields that allow multi-channel + * metadata values. + * * The output is filled jpegr_info structure * @param compressed_jpegr_image compressed JPEGR image * @param jpegr_info pointer to output JPEGR info. Members of jpegr_info diff --git a/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h b/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h index 9f59c3eaf3..064123210f 100644 --- a/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h +++ b/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h @@ -42,6 +42,8 @@ enum { ERROR_JPEGR_BUFFER_TOO_SMALL = JPEGR_IO_ERROR_BASE - 4, ERROR_JPEGR_INVALID_COLORGAMUT = JPEGR_IO_ERROR_BASE - 5, ERROR_JPEGR_INVALID_TRANS_FUNC = JPEGR_IO_ERROR_BASE - 6, + ERROR_JPEGR_INVALID_METADATA = JPEGR_IO_ERROR_BASE - 7, + ERROR_JPEGR_UNSUPPORTED_METADATA = JPEGR_IO_ERROR_BASE - 8, JPEGR_RUNTIME_ERROR_BASE = -20000, ERROR_JPEGR_ENCODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 1, diff --git a/libs/ultrahdr/include/ultrahdr/ultrahdr.h b/libs/ultrahdr/include/ultrahdr/ultrahdr.h index 21751b4634..17cc97173c 100644 --- a/libs/ultrahdr/include/ultrahdr/ultrahdr.h +++ b/libs/ultrahdr/include/ultrahdr/ultrahdr.h @@ -49,14 +49,28 @@ typedef enum { /* * Holds information for gain map related metadata. + * + * Not: all values stored in linear. This differs from the metadata encoding in XMP, where + * maxContentBoost (aka gainMapMax), minContentBoost (aka gainMapMin), hdrCapacityMin, and + * hdrCapacityMax are stored in log2 space. */ struct ultrahdr_metadata_struct { - // Ultra HDR library version + // Ultra HDR format version std::string version; // Max Content Boost for the map float maxContentBoost; // Min Content Boost for the map float minContentBoost; + // Gamma of the map data + float gamma; + // Offset for SDR data in map calculations + float offsetSdr; + // Offset for HDR data in map calculations + float offsetHdr; + // HDR capacity to apply the map at all + float hdrCapacityMin; + // HDR capacity to apply the map completely + float hdrCapacityMax; }; typedef struct ultrahdr_metadata_struct* ultrahdr_metadata_ptr; diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp index 9af5af75e5..9c57f34c2a 100644 --- a/libs/ultrahdr/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -644,13 +644,18 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, ultrahdr_metadata_struct uhdr_metadata; if (!getMetadataFromXMP(static_cast(gain_map_decoder.getXMPPtr()), gain_map_decoder.getXMPSize(), &uhdr_metadata)) { - return ERROR_JPEGR_DECODE_ERROR; + return ERROR_JPEGR_INVALID_METADATA; } if (metadata != nullptr) { metadata->version = uhdr_metadata.version; metadata->minContentBoost = uhdr_metadata.minContentBoost; metadata->maxContentBoost = uhdr_metadata.maxContentBoost; + metadata->gamma = uhdr_metadata.gamma; + metadata->offsetSdr = uhdr_metadata.offsetSdr; + metadata->offsetHdr = uhdr_metadata.offsetHdr; + metadata->hdrCapacityMin = uhdr_metadata.hdrCapacityMin; + metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax; } if (output_format == ULTRAHDR_OUTPUT_SDR) { @@ -840,6 +845,12 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits; metadata->minContentBoost = 1.0f; + metadata->gamma = 1.0f; + metadata->offsetSdr = 0.0f; + metadata->offsetHdr = 0.0f; + metadata->hdrCapacityMin = 1.0f; + metadata->hdrCapacityMax = metadata->maxContentBoost; + float log2MinBoost = log2(metadata->minContentBoost); float log2MaxBoost = log2(metadata->maxContentBoost); @@ -958,6 +969,26 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image, return ERROR_JPEGR_INVALID_NULL_PTR; } + if (metadata->version.compare("1.0")) { + ALOGE("Unsupported metadata version: %s", metadata->version.c_str()); + return ERROR_JPEGR_UNSUPPORTED_METADATA; + } + if (metadata->gamma != 1.0f) { + ALOGE("Unsupported metadata gamma: %f", metadata->gamma); + return ERROR_JPEGR_UNSUPPORTED_METADATA; + } + if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) { + ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr, + metadata->offsetHdr); + return ERROR_JPEGR_UNSUPPORTED_METADATA; + } + if (metadata->hdrCapacityMin != metadata->minContentBoost + || metadata->hdrCapacityMax != metadata->maxContentBoost) { + ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin, + metadata->hdrCapacityMax); + return ERROR_JPEGR_UNSUPPORTED_METADATA; + } + // TODO: remove once map scaling factor is computed based on actual map dims size_t image_width = uncompressed_yuv_420_image->width; size_t image_height = uncompressed_yuv_420_image->height; @@ -1180,12 +1211,33 @@ status_t JpegR::appendGainMap(jr_compressed_ptr compressed_jpeg_image, return ERROR_JPEGR_INVALID_NULL_PTR; } - if (metadata->minContentBoost < 1.0f || metadata->maxContentBoost < metadata->minContentBoost) { + if (metadata->version.compare("1.0")) { + ALOGE("received bad value for version: %s", metadata->version.c_str()); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + if (metadata->maxContentBoost < metadata->minContentBoost) { ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost, metadata->maxContentBoost); return ERROR_JPEGR_INVALID_INPUT_TYPE; } + if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) { + ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin, + metadata->hdrCapacityMax); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) { + ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr, + metadata->offsetHdr); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + + if (metadata->gamma <= 0.0f) { + ALOGE("received bad value for gamma %f", metadata->gamma); + return ERROR_JPEGR_INVALID_INPUT_TYPE; + } + const string nameSpace = "http://ns.adobe.com/xap/1.0/"; const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator diff --git a/libs/ultrahdr/jpegrutils.cpp b/libs/ultrahdr/jpegrutils.cpp index 6430af12c7..c434eb6459 100644 --- a/libs/ultrahdr/jpegrutils.cpp +++ b/libs/ultrahdr/jpegrutils.cpp @@ -113,6 +113,15 @@ public: XMPXmlHandler() : XmlHandler() { state = NotStrarted; + versionFound = false; + minContentBoostFound = false; + maxContentBoostFound = false; + gammaFound = false; + offsetSdrFound = false; + offsetHdrFound = false; + hdrCapacityMinFound = false; + hdrCapacityMaxFound = false; + baseRenditionIsHdrFound = false; } enum ParseState { @@ -147,10 +156,24 @@ public: string val; if (state == Started) { if (context.BuildTokenValue(&val)) { - if (!val.compare(maxContentBoostAttrName)) { + if (!val.compare(versionAttrName)) { + lastAttributeName = versionAttrName; + } else if (!val.compare(maxContentBoostAttrName)) { lastAttributeName = maxContentBoostAttrName; } else if (!val.compare(minContentBoostAttrName)) { lastAttributeName = minContentBoostAttrName; + } else if (!val.compare(gammaAttrName)) { + lastAttributeName = gammaAttrName; + } else if (!val.compare(offsetSdrAttrName)) { + lastAttributeName = offsetSdrAttrName; + } else if (!val.compare(offsetHdrAttrName)) { + lastAttributeName = offsetHdrAttrName; + } else if (!val.compare(hdrCapacityMinAttrName)) { + lastAttributeName = hdrCapacityMinAttrName; + } else if (!val.compare(hdrCapacityMaxAttrName)) { + lastAttributeName = hdrCapacityMaxAttrName; + } else if (!val.compare(baseRenditionIsHdrAttrName)) { + lastAttributeName = baseRenditionIsHdrAttrName; } else { lastAttributeName = ""; } @@ -163,18 +186,52 @@ public: string val; if (state == Started) { if (context.BuildTokenValue(&val, true)) { - if (!lastAttributeName.compare(maxContentBoostAttrName)) { + if (!lastAttributeName.compare(versionAttrName)) { + versionStr = val; + versionFound = true; + } else if (!lastAttributeName.compare(maxContentBoostAttrName)) { maxContentBoostStr = val; + maxContentBoostFound = true; } else if (!lastAttributeName.compare(minContentBoostAttrName)) { minContentBoostStr = val; + minContentBoostFound = true; + } else if (!lastAttributeName.compare(gammaAttrName)) { + gammaStr = val; + gammaFound = true; + } else if (!lastAttributeName.compare(offsetSdrAttrName)) { + offsetSdrStr = val; + offsetSdrFound = true; + } else if (!lastAttributeName.compare(offsetHdrAttrName)) { + offsetHdrStr = val; + offsetHdrFound = true; + } else if (!lastAttributeName.compare(hdrCapacityMinAttrName)) { + hdrCapacityMinStr = val; + hdrCapacityMinFound = true; + } else if (!lastAttributeName.compare(hdrCapacityMaxAttrName)) { + hdrCapacityMaxStr = val; + hdrCapacityMaxFound = true; + } else if (!lastAttributeName.compare(baseRenditionIsHdrAttrName)) { + baseRenditionIsHdrStr = val; + baseRenditionIsHdrFound = true; } } } return context.GetResult(); } - bool getMaxContentBoost(float* max_content_boost) { + bool getVersion(string* version, bool* present) { if (state == Done) { + *version = versionStr; + *present = versionFound; + return true; + } else { + return false; + } + } + + bool getMaxContentBoost(float* max_content_boost, bool* present) { + if (state == Done) { + *present = maxContentBoostFound; stringstream ss(maxContentBoostStr); float val; if (ss >> val) { @@ -188,8 +245,9 @@ public: } } - bool getMinContentBoost(float* min_content_boost) { + bool getMinContentBoost(float* min_content_boost, bool* present) { if (state == Done) { + *present = minContentBoostFound; stringstream ss(minContentBoostStr); float val; if (ss >> val) { @@ -203,12 +261,141 @@ public: } } + bool getGamma(float* gamma, bool* present) { + if (state == Done) { + *present = gammaFound; + stringstream ss(gammaStr); + float val; + if (ss >> val) { + *gamma = val; + return true; + } else { + return false; + } + } else { + return false; + } + } + + + bool getOffsetSdr(float* offset_sdr, bool* present) { + if (state == Done) { + *present = offsetSdrFound; + stringstream ss(offsetSdrStr); + float val; + if (ss >> val) { + *offset_sdr = val; + return true; + } else { + return false; + } + } else { + return false; + } + } + + + bool getOffsetHdr(float* offset_hdr, bool* present) { + if (state == Done) { + *present = offsetHdrFound; + stringstream ss(offsetHdrStr); + float val; + if (ss >> val) { + *offset_hdr = val; + return true; + } else { + return false; + } + } else { + return false; + } + } + + + bool getHdrCapacityMin(float* hdr_capacity_min, bool* present) { + if (state == Done) { + *present = hdrCapacityMinFound; + stringstream ss(hdrCapacityMinStr); + float val; + if (ss >> val) { + *hdr_capacity_min = exp2(val); + return true; + } else { + return false; + } + } else { + return false; + } + } + + + bool getHdrCapacityMax(float* hdr_capacity_max, bool* present) { + if (state == Done) { + *present = hdrCapacityMaxFound; + stringstream ss(hdrCapacityMaxStr); + float val; + if (ss >> val) { + *hdr_capacity_max = exp2(val); + return true; + } else { + return false; + } + } else { + return false; + } + } + + + bool getBaseRenditionIsHdr(bool* base_rendition_is_hdr, bool* present) { + if (state == Done) { + *present = baseRenditionIsHdrFound; + if (!baseRenditionIsHdrStr.compare("False")) { + *base_rendition_is_hdr = false; + return true; + } else if (!baseRenditionIsHdrStr.compare("True")) { + *base_rendition_is_hdr = true; + return true; + } else { + return false; + } + } else { + return false; + } + } + + + private: static const string containerName; + + static const string versionAttrName; + string versionStr; + bool versionFound; static const string maxContentBoostAttrName; string maxContentBoostStr; + bool maxContentBoostFound; static const string minContentBoostAttrName; string minContentBoostStr; + bool minContentBoostFound; + static const string gammaAttrName; + string gammaStr; + bool gammaFound; + static const string offsetSdrAttrName; + string offsetSdrStr; + bool offsetSdrFound; + static const string offsetHdrAttrName; + string offsetHdrStr; + bool offsetHdrFound; + static const string hdrCapacityMinAttrName; + string hdrCapacityMinStr; + bool hdrCapacityMinFound; + static const string hdrCapacityMaxAttrName; + string hdrCapacityMaxStr; + bool hdrCapacityMaxFound; + static const string baseRenditionIsHdrAttrName; + string baseRenditionIsHdrStr; + bool baseRenditionIsHdrFound; + string lastAttributeName; ParseState state; }; @@ -253,8 +440,15 @@ const string kMapHDRCapacityMax = Name(kGainMapPrefix, "HDRCapacityMax"); const string kMapBaseRenditionIsHDR = Name(kGainMapPrefix, "BaseRenditionIsHDR"); // GainMap XMP constants - names for XMP handlers +const string XMPXmlHandler::versionAttrName = kMapVersion; const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin; const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax; +const string XMPXmlHandler::gammaAttrName = kMapGamma; +const string XMPXmlHandler::offsetSdrAttrName = kMapOffsetSdr; +const string XMPXmlHandler::offsetHdrAttrName = kMapOffsetHdr; +const string XMPXmlHandler::hdrCapacityMinAttrName = kMapHDRCapacityMin; +const string XMPXmlHandler::hdrCapacityMaxAttrName = kMapHDRCapacityMax; +const string XMPXmlHandler::baseRenditionIsHdrAttrName = kMapBaseRenditionIsHDR; bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, ultrahdr_metadata_struct* metadata) { string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; @@ -291,11 +485,48 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, ultrahdr_metadata_st return false; } - if (!handler.getMaxContentBoost(&metadata->maxContentBoost)) { + // Apply default values to any not-present fields, except for Version, + // maxContentBoost, and hdrCapacityMax, which are required. Return false if + // we encounter a present field that couldn't be parsed, since this + // indicates it is invalid (eg. string where there should be a float). + bool present = false; + if (!handler.getVersion(&metadata->version, &present) || !present) { + return false; + } + if (!handler.getMaxContentBoost(&metadata->maxContentBoost, &present) || !present) { + return false; + } + if (!handler.getHdrCapacityMax(&metadata->hdrCapacityMax, &present) || !present) { return false; } + if (!handler.getMinContentBoost(&metadata->minContentBoost, &present)) { + if (present) return false; + metadata->minContentBoost = 1.0f; + } + if (!handler.getGamma(&metadata->gamma, &present)) { + if (present) return false; + metadata->gamma = 1.0f; + } + if (!handler.getOffsetSdr(&metadata->offsetSdr, &present)) { + if (present) return false; + metadata->offsetSdr = 1.0f / 64.0f; + } + if (!handler.getOffsetHdr(&metadata->offsetHdr, &present)) { + if (present) return false; + metadata->offsetHdr = 1.0f / 64.0f; + } + if (!handler.getHdrCapacityMin(&metadata->hdrCapacityMin, &present)) { + if (present) return false; + metadata->hdrCapacityMin = 1.0f; + } - if (!handler.getMinContentBoost(&metadata->minContentBoost)) { + bool base_rendition_is_hdr; + if (!handler.getBaseRenditionIsHdr(&base_rendition_is_hdr, &present)) { + if (present) return false; + base_rendition_is_hdr = false; + } + if (base_rendition_is_hdr) { + ALOGE("Base rendition of HDR is not supported!"); return false; } @@ -355,12 +586,11 @@ string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata) { writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost)); writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost)); - writer.WriteAttributeNameAndValue(kMapGamma, "1"); - writer.WriteAttributeNameAndValue(kMapOffsetSdr, "0"); - writer.WriteAttributeNameAndValue(kMapOffsetHdr, "0"); - writer.WriteAttributeNameAndValue( - kMapHDRCapacityMin, std::max(log2(metadata.minContentBoost), 0.0f)); - writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, log2(metadata.maxContentBoost)); + writer.WriteAttributeNameAndValue(kMapGamma, metadata.gamma); + writer.WriteAttributeNameAndValue(kMapOffsetSdr, metadata.offsetSdr); + writer.WriteAttributeNameAndValue(kMapOffsetHdr, metadata.offsetHdr); + writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, log2(metadata.hdrCapacityMin)); + writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, log2(metadata.hdrCapacityMax)); writer.WriteAttributeNameAndValue(kMapBaseRenditionIsHDR, "False"); writer.FinishWriting(); diff --git a/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg b/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg index f61e0e85251895ad4f386dedcc86ceaf6a5de196..c7f45385346d7d6af5ca648ecf9813e176a6dc6d 100644 GIT binary patch delta 3451 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 delta 6308 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 O{rFOTc=P;xYcT+UOgsDl diff --git a/libs/ultrahdr/tests/jpegr_test.cpp b/libs/ultrahdr/tests/jpegr_test.cpp index d482ea1f79..41d55ec497 100644 --- a/libs/ultrahdr/tests/jpegr_test.cpp +++ b/libs/ultrahdr/tests/jpegr_test.cpp @@ -819,6 +819,52 @@ TEST_F(JpegRTest, encodeAPI4ForInvalidArgs) { EXPECT_NE(OK, jpegRCodec.encodeJPEGR( &jpegR, nullptr, nullptr, &jpegR)) << "fail, API allows nullptr gainmap image"; + // test metadata + ultrahdr_metadata_struct good_metadata; + good_metadata.version = "1.0"; + good_metadata.minContentBoost = 1.0f; + good_metadata.maxContentBoost = 2.0f; + good_metadata.gamma = 1.0f; + good_metadata.offsetSdr = 0.0f; + good_metadata.offsetHdr = 0.0f; + good_metadata.hdrCapacityMin = 1.0f; + good_metadata.hdrCapacityMax = 2.0f; + + ultrahdr_metadata_struct metadata = good_metadata; + metadata.version = "1.1"; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata version"; + + metadata = good_metadata; + metadata.minContentBoost = 3.0f; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata content boost"; + + metadata = good_metadata; + metadata.gamma = -0.1f; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata gamma"; + + metadata = good_metadata; + metadata.offsetSdr = -0.1f; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata offset sdr"; + + metadata = good_metadata; + metadata.offsetHdr = -0.1f; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata offset hdr"; + + metadata = good_metadata; + metadata.hdrCapacityMax = 0.5f; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata hdr capacity max"; + + metadata = good_metadata; + metadata.hdrCapacityMin = 0.5f; + EXPECT_NE(OK, jpegRCodec.encodeJPEGR( + &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata hdr capacity min"; + free(jpegR.data); } @@ -864,8 +910,13 @@ TEST_F(JpegRTest, decodeAPIForInvalidArgs) { TEST_F(JpegRTest, writeXmpThenRead) { ultrahdr_metadata_struct metadata_expected; metadata_expected.version = "1.0"; - metadata_expected.maxContentBoost = 1.25; - metadata_expected.minContentBoost = 0.75; + metadata_expected.maxContentBoost = 1.25f; + metadata_expected.minContentBoost = 0.75f; + metadata_expected.gamma = 1.0f; + metadata_expected.offsetSdr = 0.0f; + metadata_expected.offsetHdr = 0.0f; + metadata_expected.hdrCapacityMin = 1.0f; + metadata_expected.hdrCapacityMax = metadata_expected.maxContentBoost; const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator @@ -882,6 +933,11 @@ TEST_F(JpegRTest, writeXmpThenRead) { EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read)); EXPECT_FLOAT_EQ(metadata_expected.maxContentBoost, metadata_read.maxContentBoost); EXPECT_FLOAT_EQ(metadata_expected.minContentBoost, metadata_read.minContentBoost); + EXPECT_FLOAT_EQ(metadata_expected.gamma, metadata_read.gamma); + EXPECT_FLOAT_EQ(metadata_expected.offsetSdr, metadata_read.offsetSdr); + EXPECT_FLOAT_EQ(metadata_expected.offsetHdr, metadata_read.offsetHdr); + EXPECT_FLOAT_EQ(metadata_expected.hdrCapacityMin, metadata_read.hdrCapacityMin); + EXPECT_FLOAT_EQ(metadata_expected.hdrCapacityMax, metadata_read.hdrCapacityMax); } /* Test Encode API-0 */ @@ -1297,9 +1353,7 @@ TEST_F(JpegRTest, ProfileGainMapFuncs) { JpegRBenchmark benchmark; - ultrahdr_metadata_struct metadata = { .version = "1.0", - .maxContentBoost = 8.0f, - .minContentBoost = 1.0f / 8.0f }; + ultrahdr_metadata_struct metadata = { .version = "1.0" }; jpegr_uncompressed_struct map = { .data = NULL, .width = 0, -- GitLab From 3b9574a68afb242d573f062ce8e0a99a452d5842 Mon Sep 17 00:00:00 2001 From: Vladimir Komsiyski Date: Mon, 12 Jun 2023 10:23:53 +0200 Subject: [PATCH 1294/1310] Handle runtime sensor events even if there are no real ones. SensorService::threadLoop() "continues" if there are no real sensor events, which skips the handling of runtime sensor events. Fix: 281452823 Test: m Change-Id: I0fe9fe9a7baa19ce9c8cb32d36e736ac83d2fe52 --- services/sensorservice/SensorService.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp index 398d60242b..90d75414d6 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -1055,7 +1055,12 @@ bool SensorService::threadLoop() { if (count < 0) { if(count == DEAD_OBJECT && device.isReconnecting()) { device.reconnect(); - continue; + // There are no "real" events at this point, but do not skip the rest of the loop + // if there are pending runtime events. + Mutex::Autolock _l(&mLock); + if (mRuntimeSensorEventQueue.empty()) { + continue; + } } else { ALOGE("sensor poll failed (%s)", strerror(-count)); break; -- GitLab From 37d4dcbba2052f2906e19a4973b6801058bf76e9 Mon Sep 17 00:00:00 2001 From: Vinh Tran Date: Thu, 25 May 2023 09:19:56 -0400 Subject: [PATCH 1295/1310] Migrate aidl filegroup to aidl_library ag/22717101 uses aidl.deps prop in filegroup to include aidl headers. aosp/2571770 introduced aidl_library module type to better enforce explicit aidl headers in Android.bp. This CL moves the libgui_aidl modified in ag/22717101 to aidl_library so we can deprecate the aidl.deps prop in filegroup. Bug: 279960133 Test: m libgui Change-Id: I17b448607d27ede681ffc42dc35077109463b9d7 (cherry picked from commit cbbf330356a3845be4c48adb792aede9968c5934) --- libs/gui/Android.bp | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index bf2d7b6243..342f132f0c 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -129,13 +129,24 @@ cc_library_static { }, } -filegroup { +aidl_library { + name: "libgui_aidl_hdrs", + hdrs: [ + "android/gui/DisplayInfo.aidl", + "android/gui/FocusRequest.aidl", + "android/gui/InputApplicationInfo.aidl", + "android/gui/IWindowInfosListener.aidl", + "android/gui/IWindowInfosReportedListener.aidl", + "android/gui/WindowInfo.aidl", + "android/gui/WindowInfosUpdate.aidl", + ], +} + +aidl_library { name: "libgui_aidl", srcs: ["aidl/**/*.aidl"], - path: "aidl/", - aidl: { - deps: [":android_gui_aidl"], - }, + strip_import_prefix: "aidl", + deps: ["libgui_aidl_hdrs"], } filegroup { @@ -147,9 +158,6 @@ filegroup { cc_library_static { name: "libgui_aidl_static", vendor_available: true, - srcs: [ - ":libgui_aidl", - ], shared_libs: [ "libbinder", @@ -175,9 +183,7 @@ cc_library_static { aidl: { export_aidl_headers: true, - include_dirs: [ - "frameworks/native/libs/gui", - ], + libs: ["libgui_aidl"], }, } -- GitLab From 5b02fc47b36af39a94a22148eb49b85820d17a5f Mon Sep 17 00:00:00 2001 From: John Reck Date: Wed, 14 Jun 2023 14:41:10 -0400 Subject: [PATCH 1296/1310] Adjust screenshot behavior of HDR content Cap the max amount of HDR headroom beyond which the layer is clipped. This avoids over-dimming the SDR content range in the resulting SDR screenshot Test: screenshot of silkfx Bug: 286942637 Change-Id: I67df32d9c192da18df516972ed92e9bd64063186 --- libs/renderengine/skia/SkiaRenderEngine.cpp | 4 +++- services/surfaceflinger/SurfaceFlinger.cpp | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index 5854135afe..fda6ea189e 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -86,6 +86,7 @@ namespace { // Debugging settings static const bool kPrintLayerSettings = false; static const bool kFlushAfterEveryLayer = kPrintLayerSettings; +static constexpr bool kEnableLayerBrightening = true; } // namespace @@ -699,7 +700,8 @@ void SkiaRenderEngine::drawLayersInternal( // ...and compute the dimming ratio if dimming is requested const float displayDimmingRatio = display.targetLuminanceNits > 0.f && - maxLayerWhitePoint > 0.f && display.targetLuminanceNits > maxLayerWhitePoint + maxLayerWhitePoint > 0.f && + (kEnableLayerBrightening || display.targetLuminanceNits > maxLayerWhitePoint) ? maxLayerWhitePoint / display.targetLuminanceNits : 1.f; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 79378befcc..fe2db940f7 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -7419,6 +7419,13 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( renderArea->getHintForSeamlessTransition()); sdrWhitePointNits = state.sdrWhitePointNits; displayBrightnessNits = state.displayBrightnessNits; + if (sdrWhitePointNits > 1.0f) { + // Restrict the amount of HDR "headroom" in the screenshot to avoid over-dimming + // the SDR portion. 2.0 chosen by experimentation + constexpr float kMaxScreenshotHeadroom = 2.0f; + displayBrightnessNits = + std::min(sdrWhitePointNits * kMaxScreenshotHeadroom, displayBrightnessNits); + } if (requestedDataspace == ui::Dataspace::UNKNOWN) { renderIntent = state.renderIntent; -- GitLab From b70622a45d17f6bfe7357bebfc59a5871fba5b9e Mon Sep 17 00:00:00 2001 From: Nikhil Kumar Date: Thu, 15 Jun 2023 15:37:19 +0100 Subject: [PATCH 1297/1310] Use unsigned calling_uid in case of bug report triggered from adb The utility method to extract user_id and app_id from calling_uid takes unsigned int_32. Changed the default value of calling_uid to 0 from -1 for the case when bug report is triggered by adb and calling_uid is not relevant. Bug: 260989962 Test: tested manually by initiating a bug report through adb and observing the BUGREPORT_STARTED broadcast in logcat. Ignore-AOSP-First: will do it later with other headless changes. Change-Id: I1c410853ef99c6292039382714394066a72318f7 --- cmds/dumpstate/dumpstate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp index 5dbf7ac715..8a337569c4 100644 --- a/cmds/dumpstate/dumpstate.cpp +++ b/cmds/dumpstate/dumpstate.cpp @@ -3586,7 +3586,7 @@ Dumpstate::RunStatus Dumpstate::ParseCommandlineAndRun(int argc, char* argv[]) { // an app; they are irrelevant here because bugreport is triggered via command line. // Update Last ID before calling Run(). Initialize(); - status = Run(-1 /* calling_uid */, "" /* calling_package */); + status = Run(0 /* calling_uid */, "" /* calling_package */); } return status; } -- GitLab From 9971bae91d9095dc47bc1c6075e623497b7bff99 Mon Sep 17 00:00:00 2001 From: Nikita Ioffe Date: Thu, 15 Jun 2023 17:01:11 +0100 Subject: [PATCH 1298/1310] Also mount /sys/fs/selinux This mount point is required for apexd, which uses libselinux to adjust label of the compressed APEXes that are decompressed as part of the postinstall hook. Bug: 284277137 Test: m dist Test: system/update_engine/scripts/update_device.py out/dist/ota.zip Change-Id: Ia1a65a4e69b27351941a290210004a93470ae87f Merged-In: Ia1a65a4e69b27351941a290210004a93470ae87f (cherry picked from commit 56fd71b0d4466983d8f5c11466a43df1c6c3a998) --- cmds/installd/otapreopt_chroot.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmds/installd/otapreopt_chroot.cpp b/cmds/installd/otapreopt_chroot.cpp index 1b7acabf70..c86993cb06 100644 --- a/cmds/installd/otapreopt_chroot.cpp +++ b/cmds/installd/otapreopt_chroot.cpp @@ -165,7 +165,8 @@ static int otapreopt_chroot(const int argc, char **arg) { // Bind mount necessary directories. constexpr const char* kBindMounts[] = { - "/data", "/dev", "/proc", "/sys" + "/data", "/dev", "/proc", "/sys", + "/sys/fs/selinux" /* Required for apexd which includes libselinux */ }; for (size_t i = 0; i < arraysize(kBindMounts); ++i) { std::string trg = StringPrintf("/postinstall%s", kBindMounts[i]); -- GitLab From 1db43995a5e1cda14aaa1079bbcd39a1c8ccb8fc Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Mon, 19 Jun 2023 17:05:07 +0000 Subject: [PATCH 1299/1310] TouchInputMapper: don't check touchpad touches by screen bounds Uncaptured touchpads (with DeviceMode::POINTER) still on the old stack (i.e. the Sony DualShock 4 touchpad) were having touches ignored if they started at coordinates that would be outside of the bounds of the touchscreen. Since touchpads use relative motions and don't directly relate raw touch locations to screen locations, this was incorrect, and caused touches to be ignored with certain screen/touchpad dimension and orientation combinations. Bug: 280396539 Test: connect Sony DualShock 4 by USB, rotate screen to portrait, check touches starting in all areas of the pad are turned into pointer movements Test: atest inputflinger_tests Change-Id: I9a92ba79246ee8fc25418c2e248ae2c3839a0d58 --- .../reader/mapper/TouchInputMapper.cpp | 4 +-- .../inputflinger/tests/InputReader_test.cpp | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index f4d50b8fa1..f2b0a4b0a7 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -1889,9 +1889,9 @@ std::list TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t uint32_t id = mCurrentRawState.rawPointerData.touchingIdBits.firstMarkedBit(); const RawPointerData::Pointer& pointer = mCurrentRawState.rawPointerData.pointerForId(id); // Skip checking whether the pointer is inside the physical frame if the device is in - // unscaled mode. + // unscaled or pointer mode. if (!isPointInsidePhysicalFrame(pointer.x, pointer.y) && - mDeviceMode != DeviceMode::UNSCALED) { + mDeviceMode != DeviceMode::UNSCALED && mDeviceMode != DeviceMode::POINTER) { // If exactly one pointer went down, check for virtual key hit. // Otherwise, we will drop the entire stroke. if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) { diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index bfb371f02a..5141acb5b9 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -5903,6 +5903,40 @@ TEST_F(SingleTouchInputMapperTest, Process_IgnoresTouchesOutsidePhysicalFrame) { mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP))); } +TEST_F(SingleTouchInputMapperTest, Process_DoesntCheckPhysicalFrameForTouchpads) { + std::shared_ptr fakePointerController = + std::make_shared(); + mFakePolicy->setPointerController(fakePointerController); + + addConfigurationProperty("touch.deviceType", "pointer"); + prepareAxes(POSITION); + prepareDisplay(ui::ROTATION_0); + auto& mapper = constructAndAddMapper(); + + // Set a physical frame in the display viewport. + auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); + viewport->physicalLeft = 20; + viewport->physicalTop = 600; + viewport->physicalRight = 30; + viewport->physicalBottom = 610; + mFakePolicy->updateViewport(*viewport); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); + + // Start the touch. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 1); + processSync(mapper); + + // Expect all input starting outside the physical frame to result in NotifyMotionArgs being + // produced. + const std::array outsidePoints = { + {{0, 0}, {19, 605}, {31, 605}, {25, 599}, {25, 611}, {DISPLAY_WIDTH, DISPLAY_HEIGHT}}}; + for (const auto& p : outsidePoints) { + processMove(mapper, toRawX(p.x), toRawY(p.y)); + processSync(mapper); + EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled()); + } +} + TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); -- GitLab From 704ade2ba7ec2f546b5b4757a441bd534d658005 Mon Sep 17 00:00:00 2001 From: Chris Glover Date: Fri, 2 Jun 2023 09:04:50 +0100 Subject: [PATCH 1300/1310] Update EGL_GL_COLORSPACE_BT2020_HLG_EXT to the correct value ag/22764137 added support for HLG formats, but the spec change in the Khronos gitlab hadn't landed yet. The enum I used had already been used by an unpublished NVIDIA EXT, so I needed to choose a different one. This change uses the new one which is the final correct enum found here: https://github.com/KhronosGroup/EGL-Registry/commit/526f9a6471106090e44b8d6a123f78886d5d88fa Bug: 277210442 Test: Manually ran new test in CL https://gerrit.khronos.org/c/vk-gl-cts/+/11608 Change-Id: I5c163b7ef2c3e6fa6f90ac8de6f75a7b7e67cf8c --- opengl/include/EGL/eglext.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opengl/include/EGL/eglext.h b/opengl/include/EGL/eglext.h index 32c21f61b9..c787fc9717 100644 --- a/opengl/include/EGL/eglext.h +++ b/opengl/include/EGL/eglext.h @@ -699,7 +699,7 @@ EGLAPI EGLBoolean EGLAPIENTRY eglQueryDisplayAttribEXT (EGLDisplay dpy, EGLint a #ifndef EGL_EXT_gl_colorspace_bt2020_hlg #define EGL_EXT_gl_colorspace_bt2020_hlg 1 -#define EGL_GL_COLORSPACE_BT2020_HLG_EXT 0x333E +#define EGL_GL_COLORSPACE_BT2020_HLG_EXT 0x3540 #endif /* EGL_EXT_gl_colorspace_bt2020_hlg */ #ifndef EGL_EXT_gl_colorspace_bt2020_linear -- GitLab From ab84e11cb5f3e3d257f78ceae64dff80e8e83315 Mon Sep 17 00:00:00 2001 From: Brian Lindahl Date: Thu, 15 Jun 2023 14:19:43 -0600 Subject: [PATCH 1301/1310] Force HALs to explicitly enable legacy method for clearing buffer caches Some HAL implementations can't support setLayerBuffer multiple times to clear the per-layer buffer caches. Therefore, default this behavior to disabled, and allow HALs to explcitily enable this behavior to obtain the necessary memory savings. Test: play videos with both true and false on both HIDL and AIDL Bug: 285561686 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:f70d9c4528888134e16003470b76dd1e81b8631d) Merged-In: I928cef25e35cfc5337db4ceb8581bf5926b4fbe3 Change-Id: I928cef25e35cfc5337db4ceb8581bf5926b4fbe3 --- .../DisplayHardware/AidlComposerHal.cpp | 30 +++++++++++-------- .../DisplayHardware/AidlComposerHal.h | 2 ++ .../DisplayHardware/HidlComposerHal.cpp | 13 ++++++-- .../SurfaceFlingerProperties.cpp | 4 +++ .../surfaceflinger/SurfaceFlingerProperties.h | 2 ++ .../sysprop/SurfaceFlingerProperties.sysprop | 16 +++++++++- .../api/SurfaceFlingerProperties-current.txt | 4 +++ 7 files changed, 55 insertions(+), 16 deletions(-) diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp index f7049b98e7..c0eb36dc02 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp @@ -20,6 +20,7 @@ #include "AidlComposerHal.h" +#include #include #include #include @@ -249,15 +250,18 @@ AidlComposer::AidlComposer(const std::string& serviceName) { ALOGE("getInterfaceVersion for AidlComposer constructor failed %s", status.getDescription().c_str()); } - if (version == 1) { - mClearSlotBuffer = sp::make(1, 1, PIXEL_FORMAT_RGBX_8888, - GraphicBuffer::USAGE_HW_COMPOSER | - GraphicBuffer::USAGE_SW_READ_OFTEN | - GraphicBuffer::USAGE_SW_WRITE_OFTEN, - "AidlComposer"); - if (!mClearSlotBuffer || mClearSlotBuffer->initCheck() != ::android::OK) { - LOG_ALWAYS_FATAL("Failed to allocate a buffer for clearing layer buffer slots"); - return; + mSupportsBufferSlotsToClear = version > 1; + if (!mSupportsBufferSlotsToClear) { + if (sysprop::clear_slots_with_set_layer_buffer(false)) { + mClearSlotBuffer = sp::make(1, 1, PIXEL_FORMAT_RGBX_8888, + GraphicBuffer::USAGE_HW_COMPOSER | + GraphicBuffer::USAGE_SW_READ_OFTEN | + GraphicBuffer::USAGE_SW_WRITE_OFTEN, + "AidlComposer"); + if (!mClearSlotBuffer || mClearSlotBuffer->initCheck() != ::android::OK) { + LOG_ALWAYS_FATAL("Failed to allocate a buffer for clearing layer buffer slots"); + return; + } } } @@ -844,12 +848,12 @@ Error AidlComposer::setLayerBufferSlotsToClear(Display display, Layer layer, Error error = Error::NONE; mMutex.lock_shared(); if (auto writer = getWriter(display)) { - // Backwards compatible way of clearing buffer is to set the layer buffer with a placeholder - // buffer, using the slot that needs to cleared... tricky. - if (mClearSlotBuffer == nullptr) { + if (mSupportsBufferSlotsToClear) { writer->get().setLayerBufferSlotsToClear(translate(display), translate(layer), slotsToClear); - } else { + // Backwards compatible way of clearing buffer slots is to set the layer buffer with a + // placeholder buffer, using the slot that needs to cleared... tricky. + } else if (mClearSlotBuffer != nullptr) { for (uint32_t slot : slotsToClear) { // Don't clear the active buffer slot because we need to restore the active buffer // after clearing the requested buffer slots with a placeholder buffer. diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h index ce05b382cc..b8ae26f879 100644 --- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h @@ -284,6 +284,8 @@ private: // threading annotations. ftl::SharedMutex mMutex; + // Whether or not explicitly clearing buffer slots is supported. + bool mSupportsBufferSlotsToClear; // Buffer slots for layers are cleared by setting the slot buffer to this buffer. sp mClearSlotBuffer; diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp index e0f6c45f70..9b41da5754 100644 --- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp @@ -24,12 +24,14 @@ #include "HidlComposerHal.h" +#include #include #include #include #include #include #include + #include "HWC2.h" #include "Hal.h" @@ -189,6 +191,9 @@ std::vector translate(const hidl_vec& in) { } sp allocateClearSlotBuffer() { + if (!sysprop::clear_slots_with_set_layer_buffer(false)) { + return nullptr; + } sp buffer = sp::make(1, 1, PIXEL_FORMAT_RGBX_8888, GraphicBuffer::USAGE_HW_COMPOSER | GraphicBuffer::USAGE_SW_READ_OFTEN | @@ -246,7 +251,7 @@ HidlComposer::HidlComposer(const std::string& serviceName) LOG_ALWAYS_FATAL("failed to create composer client"); } - if (!mClearSlotBuffer) { + if (!mClearSlotBuffer && sysprop::clear_slots_with_set_layer_buffer(false)) { LOG_ALWAYS_FATAL("Failed to allocate a buffer for clearing layer buffer slots"); return; } @@ -716,7 +721,11 @@ Error HidlComposer::setLayerBufferSlotsToClear(Display display, Layer layer, if (slotsToClear.empty()) { return Error::NONE; } - // Backwards compatible way of clearing buffer is to set the layer buffer with a placeholder + // This can be null when the HAL hasn't explicitly enabled this feature. + if (mClearSlotBuffer == nullptr) { + return Error::NONE; + } + // Backwards compatible way of clearing buffer is to set the layer buffer with a placeholder // buffer, using the slot that needs to cleared... tricky. for (uint32_t slot : slotsToClear) { // Don't clear the active buffer slot because we need to restore the active buffer after diff --git a/services/surfaceflinger/SurfaceFlingerProperties.cpp b/services/surfaceflinger/SurfaceFlingerProperties.cpp index 20fa091730..96c8b54005 100644 --- a/services/surfaceflinger/SurfaceFlingerProperties.cpp +++ b/services/surfaceflinger/SurfaceFlingerProperties.cpp @@ -375,5 +375,9 @@ bool ignore_hdr_camera_layers(bool defaultValue) { return SurfaceFlingerProperties::ignore_hdr_camera_layers().value_or(defaultValue); } +bool clear_slots_with_set_layer_buffer(bool defaultValue) { + return SurfaceFlingerProperties::clear_slots_with_set_layer_buffer().value_or(defaultValue); +} + } // namespace sysprop } // namespace android diff --git a/services/surfaceflinger/SurfaceFlingerProperties.h b/services/surfaceflinger/SurfaceFlingerProperties.h index 080feee686..951f8f8cb3 100644 --- a/services/surfaceflinger/SurfaceFlingerProperties.h +++ b/services/surfaceflinger/SurfaceFlingerProperties.h @@ -102,6 +102,8 @@ bool enable_sdr_dimming(bool defaultValue); bool ignore_hdr_camera_layers(bool defaultValue); +bool clear_slots_with_set_layer_buffer(bool defaultValue); + } // namespace sysprop } // namespace android #endif // SURFACEFLINGERPROPERTIES_H_ diff --git a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop index bcbe21a483..689f51ad5b 100644 --- a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop +++ b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop @@ -470,4 +470,18 @@ prop { scope: Public access: Readonly prop_name: "ro.surface_flinger.ignore_hdr_camera_layers" -} \ No newline at end of file +} + +# When enabled, SurfaceFlinger will attempt to clear the per-layer HAL buffer cache slots for +# buffers when they are evicted from the app cache by using additional setLayerBuffer commands. +# Ideally, this behavior would always be enabled to reduce graphics memory consumption. However, +# Some HAL implementations may not support the additional setLayerBuffer commands used to clear +# the cache slots. +prop { + api_name: "clear_slots_with_set_layer_buffer" + type: Boolean + scope: Public + access: Readonly + prop_name: "ro.surface_flinger.clear_slots_with_set_layer_buffer" +} + diff --git a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt index 348a462038..9660ff3de6 100644 --- a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt +++ b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt @@ -1,5 +1,9 @@ props { module: "android.sysprop.SurfaceFlingerProperties" + prop { + api_name: "clear_slots_with_set_layer_buffer" + prop_name: "ro.surface_flinger.clear_slots_with_set_layer_buffer" + } prop { api_name: "color_space_agnostic_dataspace" type: Long -- GitLab From c25d8b872b161e65899a1c85a016f2bee15c660e Mon Sep 17 00:00:00 2001 From: Eino-Ville Talvala Date: Fri, 21 Jul 2023 15:30:56 -0700 Subject: [PATCH 1302/1310] UltraHDR: Add Adobe HDR gain map notice Bug: 292284515 Test: Builds (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:9438cea895cc19d5d80e1af4b18b437d1bfe8277) Merged-In: Ifd263d93e1c1b8bb8043712283e38e4434079f85 Change-Id: Ifd263d93e1c1b8bb8043712283e38e4434079f85 --- libs/ultrahdr/Android.bp | 9 ++++----- .../adobe-hdr-gain-map-license/Android.bp | 19 +++++++++++++++++++ .../adobe-hdr-gain-map-license/NOTICE | 1 + 3 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp create mode 100644 libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE diff --git a/libs/ultrahdr/Android.bp b/libs/ultrahdr/Android.bp index e3f709b7f5..9deba01dc8 100644 --- a/libs/ultrahdr/Android.bp +++ b/libs/ultrahdr/Android.bp @@ -14,11 +14,10 @@ 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"], + default_applicable_licenses: [ + "frameworks_native_license", + "adobe_hdr_gain_map_license", + ], } cc_library { diff --git a/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp b/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp new file mode 100644 index 0000000000..e999a8bd28 --- /dev/null +++ b/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp @@ -0,0 +1,19 @@ +// Copyright 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +license { + name: "adobe_hdr_gain_map_license", + license_kinds: ["legacy_by_exception_only"], + license_text: ["NOTICE"], +} diff --git a/libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE b/libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE new file mode 100644 index 0000000000..3f6c5944c7 --- /dev/null +++ b/libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE @@ -0,0 +1 @@ +This product includes Gain Map technology under license by Adobe. -- GitLab From 1fd0268011d6f492078e5adcdebbe87dc7fafd29 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Tue, 25 Jul 2023 09:34:11 -0400 Subject: [PATCH 1303/1310] RenderEngine: Limit the size of blur input to the display size Some layers are extraordinarily large. In the particular case we've found, the layer does not have any content, but even if it did, content outside the display would not impact the blur. So limit the size of the rectangle we use for blurring, which in turn limits the size of the buffers we allocate to compute the blur. Use the canvas' existing clip, which has already been adjusted to the size of the display. Bug: 283427479 Bug: 292539958 Test: manual (logcat) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:763e450b7c5fa6ad1ed35cabb96fdb87467d00c2) Merged-In: I17e646cce0dca02f4e6a18032ecd1e9120fcf880 Change-Id: I17e646cce0dca02f4e6a18032ecd1e9120fcf880 --- libs/renderengine/skia/SkiaRenderEngine.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index fda6ea189e..76ebf9d0c2 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -813,8 +813,20 @@ void SkiaRenderEngine::drawLayersInternal( if (!blurInput) { blurInput = activeSurface->makeImageSnapshot(); } + // rect to be blurred in the coordinate space of blurInput - const auto blurRect = canvas->getTotalMatrix().mapRect(bounds.rect()); + SkRect blurRect = canvas->getTotalMatrix().mapRect(bounds.rect()); + + // Some layers may be much bigger than the screen. If we used + // `blurRect` directly, this would allocate a large buffer with no + // benefit. Apply the clip, which already takes the display size + // into account. The clipped size will then be used to calculate the + // size of the buffer we will create for blurring. + if (!blurRect.intersect(SkRect::Make(canvas->getDeviceClipBounds()))) { + // This should not happen, but if it did, we would use the full + // sized layer, which should still be fine. + ALOGW("blur bounds does not intersect display clip!"); + } // if the clip needs to be applied then apply it now and make sure // it is restored before we attempt to draw any shadows. -- GitLab From 5fe59da3a56f15ef7cf97e8b1f5acc176493c99b Mon Sep 17 00:00:00 2001 From: Daniel Norman Date: Wed, 2 Aug 2023 16:39:45 -0700 Subject: [PATCH 1304/1310] Lower severity for invalid accessibility hover event stream to ERROR. LOG(FATAL) crashes the device, which is very severe for the user. The accessibility hover event stream has consistency issues that we are actively fixing, but these fixes have not yet had time to soak. Instead of cherrypicking the fix, instead we can demote the severity of the log so that errors do not crash the device. Bug: 286037469 Test: atest InputDispatcherTest#InvalidA11yHoverStreamDoesNotCrash Test: Use talkback, repeatedly enter touch exploration while also tapping with another finger. Eventually observe the failure message which is now an ERROR instead of crashing the device with a FATAL. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2f99cdb9150461c6b77c29b31cbf5792a9303c95) Merged-In: Iceea086345691cdad8ca5e092629de094d666de1 Change-Id: Iceea086345691cdad8ca5e092629de094d666de1 --- .../dispatcher/InputDispatcher.cpp | 10 +++++++- .../tests/InputDispatcher_test.cpp | 23 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index fbbb38835a..7bac534536 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -662,7 +662,15 @@ std::vector getHoveringWindowsLocked(const TouchState* oldState, } else { // This pointer was already sent to the window. Use ACTION_HOVER_MOVE. if (CC_UNLIKELY(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE)) { - LOG(FATAL) << "Expected ACTION_HOVER_MOVE instead of " << entry.getDescription(); + android::base::LogSeverity severity = android::base::LogSeverity::FATAL; + if (entry.flags & AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT) { + // The Accessibility injected touch exploration event stream + // has known inconsistencies, so log ERROR instead of + // crashing the device with FATAL. + // TODO(b/286037469): Move a11y severity back to FATAL. + severity = android::base::LogSeverity::ERROR; + } + LOG(severity) << "Expected ACTION_HOVER_MOVE instead of " << entry.getDescription(); } touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_IS; } diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 017f10baf8..6ff420d951 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -3603,6 +3603,29 @@ TEST_F(InputDispatcherTest, HoverExitIsSentToRemovedWindow) { window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); } +/** + * Test that invalid HOVER events sent by accessibility do not cause a fatal crash. + */ +TEST_F(InputDispatcherTest, InvalidA11yHoverStreamDoesNotCrash) { + std::shared_ptr application = std::make_shared(); + sp window = + sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 1200, 800)); + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + MotionEventBuilder hoverEnterBuilder = + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) + .addFlag(AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, hoverEnterBuilder.build())); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, hoverEnterBuilder.build())); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); +} + /** * If mouse is hovering when the touch goes down, the hovering should be stopped via HOVER_EXIT. */ -- GitLab From e562203ac243bf9d64bd33c922fc28e781a642fb Mon Sep 17 00:00:00 2001 From: sergiuferentz Date: Mon, 26 Jun 2023 18:01:47 +0000 Subject: [PATCH 1305/1310] Fix for heap-use-after-free in GPUService.cpp This adds a unit test and fix for the bug reported by libfuzzer. Changes made: * Expose GPUService as testable code. * Update main_gpuservice.cpp to use the new GpuService now located at gpuservice/GpuService.h * Make initializer threads members of GpuService * Join the threads in destructor to prevent heap-use-after-free. * Add unit test that waits 3 seconds after deallocation to ensure no wrong access is made. Bug: 282919145 Test: Added unit test and ran on device with ASAN (cherry picked from commit 3c00cbc0f119c3f59325aa6d5061529feb58462b) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:a8186037a730699b53e49de7241280c7532e5fc4) Merged-In: I4d1d2d4658b575bf2c8f425f91f68f03114ad029 Change-Id: I4d1d2d4658b575bf2c8f425f91f68f03114ad029 --- services/gpuservice/Android.bp | 1 + services/gpuservice/GpuService.cpp | 14 +++-- .../{ => include/gpuservice}/GpuService.h | 4 ++ services/gpuservice/main_gpuservice.cpp | 2 +- .../gpuservice/tests/unittests/Android.bp | 2 + .../tests/unittests/GpuServiceTest.cpp | 52 +++++++++++++++++++ 6 files changed, 69 insertions(+), 6 deletions(-) rename services/gpuservice/{ => include/gpuservice}/GpuService.h (95%) create mode 100644 services/gpuservice/tests/unittests/GpuServiceTest.cpp diff --git a/services/gpuservice/Android.bp b/services/gpuservice/Android.bp index fba64c7569..052efb6bbb 100644 --- a/services/gpuservice/Android.bp +++ b/services/gpuservice/Android.bp @@ -71,6 +71,7 @@ filegroup { cc_library_shared { name: "libgpuservice", defaults: ["libgpuservice_production_defaults"], + export_include_dirs: ["include"], srcs: [ ":libgpuservice_sources", ], diff --git a/services/gpuservice/GpuService.cpp b/services/gpuservice/GpuService.cpp index 5e7b2e8df8..4a08c11c14 100644 --- a/services/gpuservice/GpuService.cpp +++ b/services/gpuservice/GpuService.cpp @@ -16,7 +16,7 @@ #define ATRACE_TAG ATRACE_TAG_GRAPHICS -#include "GpuService.h" +#include "gpuservice/GpuService.h" #include #include @@ -35,6 +35,7 @@ #include #include +#include namespace android { @@ -58,18 +59,21 @@ GpuService::GpuService() mGpuStats(std::make_unique()), mGpuMemTracer(std::make_unique()) { - std::thread gpuMemAsyncInitThread([this]() { + mGpuMemAsyncInitThread = std::make_unique([this] (){ mGpuMem->initialize(); mGpuMemTracer->initialize(mGpuMem); }); - gpuMemAsyncInitThread.detach(); - std::thread gpuWorkAsyncInitThread([this]() { + mGpuWorkAsyncInitThread = std::make_unique([this]() { mGpuWork->initialize(); }); - gpuWorkAsyncInitThread.detach(); }; +GpuService::~GpuService() { + mGpuWorkAsyncInitThread->join(); + mGpuMemAsyncInitThread->join(); +} + void GpuService::setGpuStats(const std::string& driverPackageName, const std::string& driverVersionName, uint64_t driverVersionCode, int64_t driverBuildTime, const std::string& appPackageName, diff --git a/services/gpuservice/GpuService.h b/services/gpuservice/include/gpuservice/GpuService.h similarity index 95% rename from services/gpuservice/GpuService.h rename to services/gpuservice/include/gpuservice/GpuService.h index 0e559f2c34..54f8f666bc 100644 --- a/services/gpuservice/GpuService.h +++ b/services/gpuservice/include/gpuservice/GpuService.h @@ -24,6 +24,7 @@ #include #include +#include #include namespace android { @@ -41,6 +42,7 @@ public: static const char* const SERVICE_NAME ANDROID_API; GpuService() ANDROID_API; + ~GpuService(); protected: status_t shellCommand(int in, int out, int err, std::vector& args) override; @@ -90,6 +92,8 @@ private: std::unique_ptr mGpuMemTracer; std::mutex mLock; std::string mDeveloperDriverPath; + std::unique_ptr mGpuMemAsyncInitThread; + std::unique_ptr mGpuWorkAsyncInitThread; }; } // namespace android diff --git a/services/gpuservice/main_gpuservice.cpp b/services/gpuservice/main_gpuservice.cpp index 64aafcab6a..200237219e 100644 --- a/services/gpuservice/main_gpuservice.cpp +++ b/services/gpuservice/main_gpuservice.cpp @@ -18,7 +18,7 @@ #include #include #include -#include "GpuService.h" +#include "gpuservice/GpuService.h" using namespace android; diff --git a/services/gpuservice/tests/unittests/Android.bp b/services/gpuservice/tests/unittests/Android.bp index 51642f9472..c870b17b79 100644 --- a/services/gpuservice/tests/unittests/Android.bp +++ b/services/gpuservice/tests/unittests/Android.bp @@ -28,6 +28,7 @@ cc_test { "GpuMemTest.cpp", "GpuMemTracerTest.cpp", "GpuStatsTest.cpp", + "GpuServiceTest.cpp", ], header_libs: ["bpf_headers"], shared_libs: [ @@ -45,6 +46,7 @@ cc_test { "libstatslog", "libstatspull", "libutils", + "libgpuservice", ], static_libs: [ "libgmock", diff --git a/services/gpuservice/tests/unittests/GpuServiceTest.cpp b/services/gpuservice/tests/unittests/GpuServiceTest.cpp new file mode 100644 index 0000000000..62b3e53f53 --- /dev/null +++ b/services/gpuservice/tests/unittests/GpuServiceTest.cpp @@ -0,0 +1,52 @@ +#undef LOG_TAG +#define LOG_TAG "gpuservice_unittest" + +#include "gpuservice/GpuService.h" + +#include +#include + +#include +#include + +namespace android { +namespace { + +class GpuServiceTest : public testing::Test { +public: + GpuServiceTest() { + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); + } + + ~GpuServiceTest() { + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); + } + +}; + + +/* +* The behaviour before this test + fixes was UB caused by threads accessing deallocated memory. +* +* This test creates the service (which initializes the culprit threads), +* deallocates it immediately and sleeps. +* +* GpuService's destructor gets called and joins the threads. +* If we haven't crashed by the time the sleep time has elapsed, we're good +* Let the test pass. +*/ +TEST_F(GpuServiceTest, onInitializeShouldNotCauseUseAfterFree) { + sp service = new GpuService(); + service.clear(); + std::this_thread::sleep_for(std::chrono::seconds(3)); + + // If we haven't crashed yet due to threads accessing freed up memory, let the test pass + EXPECT_TRUE(true); +} + +} // namespace +} // namespace android -- GitLab From 15d790a255e238b2d4f4d5888a31b3334b28f987 Mon Sep 17 00:00:00 2001 From: Tomasz Wasilczyk Date: Thu, 24 Aug 2023 16:57:29 +0000 Subject: [PATCH 1306/1310] Migrate String8|16.setTo to assignment operator [sensors] Bug: 295394788 Test: make checkbuild Change-Id: I63b97b573f6b182ba36c4eb933b695d7a6ed59d0 (cherry picked from commit d89f609d2cbdba779372de6e80e849d7cb33e99d) --- libs/sensor/Sensor.cpp | 2 +- services/sensorservice/SensorService.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/sensor/Sensor.cpp b/libs/sensor/Sensor.cpp index 9127b3772b..a1549ea385 100644 --- a/libs/sensor/Sensor.cpp +++ b/libs/sensor/Sensor.cpp @@ -627,7 +627,7 @@ bool Sensor::unflattenString8(void const*& buffer, size_t& size, String8& output if (size < len) { return false; } - outputString8.setTo(static_cast(buffer), len); + outputString8 = String8(static_cast(buffer), len); if (size < FlattenableUtils::align<4>(len)) { ALOGE("Malformed Sensor String8 field. Should be in a 4-byte aligned buffer but is not."); diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp index 1030cee588..c586145015 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -2341,7 +2341,7 @@ status_t SensorService::changeOperatingMode(const Vector& args, mCurrentOperatingMode = RESTRICTED; // temporarily stop all sensor direct report and disable sensors disableAllSensorsLocked(&connLock); - mAllowListedPackage.setTo(String8(args[1])); + mAllowListedPackage = String8(args[1]); return status_t(NO_ERROR); case REPLAY_DATA_INJECTION: if (SensorServiceUtil::isUserBuild()) { @@ -2361,7 +2361,7 @@ status_t SensorService::changeOperatingMode(const Vector& args, // Re-enable sensors. dev.enableAllSensors(); } - mAllowListedPackage.setTo(String8(args[1])); + mAllowListedPackage = String8(args[1]); return NO_ERROR; } else { // Transition to data injection mode supported only from NORMAL mode. -- GitLab From a7a99887349032123eafadb99155e5a486e13efc Mon Sep 17 00:00:00 2001 From: Tomasz Wasilczyk Date: Mon, 14 Aug 2023 18:03:09 +0000 Subject: [PATCH 1307/1310] Migrate from android::String isEmpty to empty This empty method is different from the old one - it aligns with std::string definition. Bug: 295394788 Test: make checkbuild Change-Id: Id74502de0e51182aa9629db8dc70739be4483d12 (cherry picked from commit e50b2dec7a98b7a96369558897354ff196c9af44) --- cmds/atrace/atrace.cpp | 2 +- libs/gui/tests/GLTest.cpp | 16 ++++++++-------- libs/input/KeyCharacterMap.cpp | 2 +- libs/input/PropertyMap.cpp | 2 +- libs/input/VirtualKeyMap.cpp | 2 +- services/sensorservice/SensorService.cpp | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cmds/atrace/atrace.cpp b/cmds/atrace/atrace.cpp index ea2dddad23..5719a09a16 100644 --- a/cmds/atrace/atrace.cpp +++ b/cmds/atrace/atrace.cpp @@ -796,7 +796,7 @@ static bool setCategoriesEnableFromFile(const char* categories_file) bool ok = true; while (!tokenizer->isEol()) { String8 token = tokenizer->nextToken(" "); - if (token.isEmpty()) { + if (token.empty()) { tokenizer->skipDelimiters(" "); continue; } diff --git a/libs/gui/tests/GLTest.cpp b/libs/gui/tests/GLTest.cpp index 9024b70cd6..e5f4aaa999 100644 --- a/libs/gui/tests/GLTest.cpp +++ b/libs/gui/tests/GLTest.cpp @@ -191,24 +191,24 @@ EGLSurface GLTest::createWindowSurface(EGLDisplay display, EGLConfig config, msg += String8::format("r(%d isn't %d)", pixel[0], r); } if (g >= 0 && abs(g - int(pixel[1])) > tolerance) { - if (!msg.isEmpty()) { + if (!msg.empty()) { msg += " "; } msg += String8::format("g(%d isn't %d)", pixel[1], g); } if (b >= 0 && abs(b - int(pixel[2])) > tolerance) { - if (!msg.isEmpty()) { + if (!msg.empty()) { msg += " "; } msg += String8::format("b(%d isn't %d)", pixel[2], b); } if (a >= 0 && abs(a - int(pixel[3])) > tolerance) { - if (!msg.isEmpty()) { + if (!msg.empty()) { msg += " "; } msg += String8::format("a(%d isn't %d)", pixel[3], a); } - if (!msg.isEmpty()) { + if (!msg.empty()) { return ::testing::AssertionFailure(::testing::Message(msg.c_str())); } else { return ::testing::AssertionSuccess(); @@ -223,24 +223,24 @@ EGLSurface GLTest::createWindowSurface(EGLDisplay display, EGLConfig config, msg += String8::format("left(%d isn't %d)", r1.left, r2.left); } if (abs(r1.top - r2.top) > tolerance) { - if (!msg.isEmpty()) { + if (!msg.empty()) { msg += " "; } msg += String8::format("top(%d isn't %d)", r1.top, r2.top); } if (abs(r1.right - r2.right) > tolerance) { - if (!msg.isEmpty()) { + if (!msg.empty()) { msg += " "; } msg += String8::format("right(%d isn't %d)", r1.right, r2.right); } if (abs(r1.bottom - r2.bottom) > tolerance) { - if (!msg.isEmpty()) { + if (!msg.empty()) { msg += " "; } msg += String8::format("bottom(%d isn't %d)", r1.bottom, r2.bottom); } - if (!msg.isEmpty()) { + if (!msg.empty()) { msg += String8::format(" R1: [%d %d %d %d] R2: [%d %d %d %d]", r1.left, r1.top, r1.right, r1.bottom, r2.left, r2.top, r2.right, r2.bottom); diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp index d571917ff9..a4cd239a92 100644 --- a/libs/input/KeyCharacterMap.cpp +++ b/libs/input/KeyCharacterMap.cpp @@ -1247,7 +1247,7 @@ status_t KeyCharacterMap::Parser::parseCharacterLiteral(char16_t* outCharacter) } // Ensure that we consumed the entire token. - if (mTokenizer->nextToken(WHITESPACE).isEmpty()) { + if (mTokenizer->nextToken(WHITESPACE).empty()) { return NO_ERROR; } diff --git a/libs/input/PropertyMap.cpp b/libs/input/PropertyMap.cpp index 315f5a6d4f..db0c98fc91 100644 --- a/libs/input/PropertyMap.cpp +++ b/libs/input/PropertyMap.cpp @@ -171,7 +171,7 @@ status_t PropertyMap::Parser::parse() { if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') { String8 keyToken = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER); - if (keyToken.isEmpty()) { + if (keyToken.empty()) { ALOGE("%s: Expected non-empty property key.", mTokenizer->getLocation().c_str()); return BAD_VALUE; } diff --git a/libs/input/VirtualKeyMap.cpp b/libs/input/VirtualKeyMap.cpp index de62c870ff..8b8af4290f 100644 --- a/libs/input/VirtualKeyMap.cpp +++ b/libs/input/VirtualKeyMap.cpp @@ -146,7 +146,7 @@ bool VirtualKeyMap::Parser::parseNextIntField(int32_t* outValue) { String8 token = mTokenizer->nextToken(WHITESPACE_OR_FIELD_DELIMITER); char* end; *outValue = strtol(token.c_str(), &end, 0); - if (token.isEmpty() || *end != '\0') { + if (token.empty() || *end != '\0') { ALOGE("Expected an integer, got '%s'.", token.c_str()); return false; } diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp index 1030cee588..c69b072525 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -609,7 +609,7 @@ status_t SensorService::dump(int fd, const Vector& args) { for (auto&& i : mRecentEvent) { std::shared_ptr s = getSensorInterfaceFromHandle(i.first); if (!i.second->isEmpty() && s != nullptr) { - if (privileged || s->getSensor().getRequiredPermission().isEmpty()) { + if (privileged || s->getSensor().getRequiredPermission().empty()) { i.second->setFormat("normal"); } else { i.second->setFormat("mask_data"); @@ -735,7 +735,7 @@ status_t SensorService::dumpProtoLocked(int fd, ConnectionSafeAutolock* connLock for (auto&& i : mRecentEvent) { std::shared_ptr s = getSensorInterfaceFromHandle(i.first); if (!i.second->isEmpty() && s != nullptr) { - i.second->setFormat(privileged || s->getSensor().getRequiredPermission().isEmpty() ? + i.second->setFormat(privileged || s->getSensor().getRequiredPermission().empty() ? "normal" : "mask_data"); const uint64_t mToken = proto.start(service::SensorEventsProto::RECENT_EVENTS_LOGS); proto.write(service::SensorEventsProto::RecentEventsLog::NAME, -- GitLab From 02fd95c1bdf91aa9199f331a5fd8513b723d02fc Mon Sep 17 00:00:00 2001 From: Tomasz Wasilczyk Date: Wed, 30 Aug 2023 17:51:31 +0000 Subject: [PATCH 1308/1310] Use String8/16 c_str [aosp-main-future] Bug: 295394788 Test: make checkbuild Change-Id: Ic178ef32d064c8887fd5ef054802aba8bcbe788c --- libs/gui/BufferQueueProducer.cpp | 4 ++-- libs/gui/SurfaceComposerClient.cpp | 2 +- libs/input/PropertyMap.cpp | 4 ++-- services/sensorservice/SensorEventConnection.cpp | 2 +- services/sensorservice/SensorService.cpp | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp index cf5ad7b54f..ce5d5d382e 100644 --- a/libs/gui/BufferQueueProducer.cpp +++ b/libs/gui/BufferQueueProducer.cpp @@ -505,13 +505,13 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp* ou { if (CC_UNLIKELY(ATRACE_ENABLED())) { if (buffer == nullptr) { - ATRACE_FORMAT_INSTANT("%s buffer reallocation: null", mConsumerName.string()); + ATRACE_FORMAT_INSTANT("%s buffer reallocation: null", mConsumerName.c_str()); } else { ATRACE_FORMAT_INSTANT("%s buffer reallocation actual %dx%d format:%d " "layerCount:%d " "usage:%d requested: %dx%d format:%d layerCount:%d " "usage:%d ", - mConsumerName.string(), width, height, format, + mConsumerName.c_str(), width, height, format, BQ_LAYER_COUNT, usage, buffer->getWidth(), buffer->getHeight(), buffer->getPixelFormat(), buffer->getLayerCount(), buffer->getUsage()); diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index aff03e0fd3..8a1f7c6238 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2420,7 +2420,7 @@ status_t SurfaceComposerClient::createSurfaceChecked(const String8& name, uint32 if (mStatus == NO_ERROR) { gui::CreateSurfaceResult result; - binder::Status status = mClient->createSurface(std::string(name.string()), flags, + binder::Status status = mClient->createSurface(std::string(name.c_str()), flags, parentHandle, std::move(metadata), &result); err = statusTFromBinderStatus(status); if (outTransformHint) { diff --git a/libs/input/PropertyMap.cpp b/libs/input/PropertyMap.cpp index 315f5a6d4f..62989d4f6e 100644 --- a/libs/input/PropertyMap.cpp +++ b/libs/input/PropertyMap.cpp @@ -200,13 +200,13 @@ status_t PropertyMap::Parser::parse() { return BAD_VALUE; } - if (mMap->hasProperty(keyToken.string())) { + if (mMap->hasProperty(keyToken.c_str())) { ALOGE("%s: Duplicate property value for key '%s'.", mTokenizer->getLocation().c_str(), keyToken.c_str()); return BAD_VALUE; } - mMap->addProperty(keyToken.string(), valueToken.string()); + mMap->addProperty(keyToken.c_str(), valueToken.c_str()); } mTokenizer->nextLine(); diff --git a/services/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp index d469ff4b3c..dc8657759c 100644 --- a/services/sensorservice/SensorEventConnection.cpp +++ b/services/sensorservice/SensorEventConnection.cpp @@ -854,7 +854,7 @@ int SensorService::SensorEventConnection::handleEvent(int fd, int events, void* } if (!mService->isAllowListedPackage(mPackageName)) { ALOGE("App not allowed to inject data, dropping event" - "package=%s uid=%d", mPackageName.string(), mUid); + "package=%s uid=%d", mPackageName.c_str(), mUid); return 0; } sensors_event_t sensor_event; diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp index 1030cee588..00fe9e6b1f 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -570,7 +570,7 @@ status_t SensorService::dump(int fd, const Vector& args) { } if (args.size() > 0) { Mode targetOperatingMode = NORMAL; - std::string inputStringMode = String8(args[0]).string(); + std::string inputStringMode = String8(args[0]).c_str(); if (getTargetOperatingMode(inputStringMode, &targetOperatingMode)) { status_t error = changeOperatingMode(args, targetOperatingMode); // Dump the latest state only if no error was encountered. @@ -1465,7 +1465,7 @@ void SensorService::addSensorIfAccessible(const String16& opPackageName, const S accessibleSensorList.add(sensor); } else if (sensor.getType() != SENSOR_TYPE_HEAD_TRACKER) { ALOGI("Skipped sensor %s because it requires permission %s and app op %" PRId32, - sensor.getName().string(), sensor.getRequiredPermission().string(), + sensor.getName().c_str(), sensor.getRequiredPermission().c_str(), sensor.getRequiredAppOp()); } } -- GitLab From 56cd9b79ceac33602079e07efcf8cd56ef2afca7 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Wed, 12 Jul 2023 13:47:28 -0500 Subject: [PATCH 1309/1310] Improve updateInputFlinger performance This change improves the performance of the WindowInfosListenerInvoker work done on SurfaceFlinger's background executor thread. The primary optimization made is not sending a WindowInfosReportedListener with every call to WindowInfosListener.onWindowInfosChanged. Instead, we send a new interface, WindowInfosPublisher, and a unique listener id to listeners when they're added. Listeners call WindowInfosPublisher.ackWindowInfosReceived with their id after processing each update. From traces taken during development, the new code is a major improvement, taking about 15% of the time spent previously on SurfaceFlinger's background thread for sending window infos. Performance with this change seems roughly in line with the performance in T. Bug: 290377931 Test: atest WindowInfosListenerTest Test: atest WindowInfosListenerInvokerTest Test: manually killing system server and checking valid state on restart (cherry picked from commit acd2258a5492a9e289fd7f4b8ea90543d6843a23) (cherry picked from commit e8a7ab25b2f2f17571279a2c2bf2ea0dff66c8e6) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:82f3463d449eb13e28c5dbffeee16e10721c71d2) Merged-In: Ib39ba935727df0bc1ab4030bcfe8301de7e64805 Change-Id: Ib39ba935727df0bc1ab4030bcfe8301de7e64805 --- libs/gui/Android.bp | 2 + libs/gui/WindowInfosListenerReporter.cpp | 20 +- .../aidl/android/gui/ISurfaceComposer.aidl | 4 +- .../android/gui/WindowInfosListenerInfo.aidl | 25 ++ .../gui/android/gui/IWindowInfosListener.aidl | 4 +- .../android/gui/IWindowInfosPublisher.aidl | 23 ++ libs/gui/fuzzer/libgui_fuzzer_utils.h | 4 +- libs/gui/include/gui/ISurfaceComposer.h | 1 + .../include/gui/WindowInfosListenerReporter.h | 8 +- libs/gui/tests/Surface_test.cpp | 3 +- .../surfaceflinger/BackgroundExecutor.cpp | 14 + services/surfaceflinger/BackgroundExecutor.h | 1 + services/surfaceflinger/SurfaceFlinger.cpp | 14 +- services/surfaceflinger/SurfaceFlinger.h | 7 +- .../WindowInfosListenerInvoker.cpp | 253 +++++++++--------- .../WindowInfosListenerInvoker.h | 35 +-- .../WindowInfosListenerInvokerTest.cpp | 156 +++++------ 17 files changed, 332 insertions(+), 242 deletions(-) create mode 100644 libs/gui/aidl/android/gui/WindowInfosListenerInfo.aidl create mode 100644 libs/gui/android/gui/IWindowInfosPublisher.aidl diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index bf34987b9e..3c8df2bb29 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -73,6 +73,7 @@ filegroup { "android/gui/FocusRequest.aidl", "android/gui/InputApplicationInfo.aidl", "android/gui/IWindowInfosListener.aidl", + "android/gui/IWindowInfosPublisher.aidl", "android/gui/IWindowInfosReportedListener.aidl", "android/gui/WindowInfo.aidl", "android/gui/WindowInfosUpdate.aidl", @@ -90,6 +91,7 @@ cc_library_static { "android/gui/FocusRequest.aidl", "android/gui/InputApplicationInfo.aidl", "android/gui/IWindowInfosListener.aidl", + "android/gui/IWindowInfosPublisher.aidl", "android/gui/IWindowInfosReportedListener.aidl", "android/gui/WindowInfosUpdate.aidl", "android/gui/WindowInfo.aidl", diff --git a/libs/gui/WindowInfosListenerReporter.cpp b/libs/gui/WindowInfosListenerReporter.cpp index 76e7b6e162..0929b8e120 100644 --- a/libs/gui/WindowInfosListenerReporter.cpp +++ b/libs/gui/WindowInfosListenerReporter.cpp @@ -22,7 +22,6 @@ namespace android { using gui::DisplayInfo; -using gui::IWindowInfosReportedListener; using gui::WindowInfo; using gui::WindowInfosListener; using gui::aidl_utils::statusTFromBinderStatus; @@ -40,8 +39,13 @@ status_t WindowInfosListenerReporter::addWindowInfosListener( { std::scoped_lock lock(mListenersMutex); if (mWindowInfosListeners.empty()) { - binder::Status s = surfaceComposer->addWindowInfosListener(this); + gui::WindowInfosListenerInfo listenerInfo; + binder::Status s = surfaceComposer->addWindowInfosListener(this, &listenerInfo); status = statusTFromBinderStatus(s); + if (status == OK) { + mWindowInfosPublisher = std::move(listenerInfo.windowInfosPublisher); + mListenerId = listenerInfo.listenerId; + } } if (status == OK) { @@ -85,8 +89,7 @@ status_t WindowInfosListenerReporter::removeWindowInfosListener( } binder::Status WindowInfosListenerReporter::onWindowInfosChanged( - const gui::WindowInfosUpdate& update, - const sp& windowInfosReportedListener) { + const gui::WindowInfosUpdate& update) { std::unordered_set, gui::SpHash> windowInfosListeners; @@ -104,9 +107,7 @@ binder::Status WindowInfosListenerReporter::onWindowInfosChanged( listener->onWindowInfosChanged(update); } - if (windowInfosReportedListener) { - windowInfosReportedListener->onWindowInfosReported(); - } + mWindowInfosPublisher->ackWindowInfosReceived(update.vsyncId, mListenerId); return binder::Status::ok(); } @@ -114,7 +115,10 @@ binder::Status WindowInfosListenerReporter::onWindowInfosChanged( void WindowInfosListenerReporter::reconnect(const sp& composerService) { std::scoped_lock lock(mListenersMutex); if (!mWindowInfosListeners.empty()) { - composerService->addWindowInfosListener(this); + gui::WindowInfosListenerInfo listenerInfo; + composerService->addWindowInfosListener(this, &listenerInfo); + mWindowInfosPublisher = std::move(listenerInfo.windowInfosPublisher); + mListenerId = listenerInfo.listenerId; } } diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index ec3266ca83..539a1c140e 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -40,12 +40,14 @@ import android.gui.IScreenCaptureListener; import android.gui.ISurfaceComposerClient; import android.gui.ITunnelModeEnabledListener; import android.gui.IWindowInfosListener; +import android.gui.IWindowInfosPublisher; import android.gui.LayerCaptureArgs; import android.gui.LayerDebugInfo; import android.gui.OverlayProperties; import android.gui.PullAtomData; import android.gui.ARect; import android.gui.StaticDisplayInfo; +import android.gui.WindowInfosListenerInfo; /** @hide */ interface ISurfaceComposer { @@ -500,7 +502,7 @@ interface ISurfaceComposer { */ int getMaxAcquiredBufferCount(); - void addWindowInfosListener(IWindowInfosListener windowInfosListener); + WindowInfosListenerInfo addWindowInfosListener(IWindowInfosListener windowInfosListener); void removeWindowInfosListener(IWindowInfosListener windowInfosListener); diff --git a/libs/gui/aidl/android/gui/WindowInfosListenerInfo.aidl b/libs/gui/aidl/android/gui/WindowInfosListenerInfo.aidl new file mode 100644 index 0000000000..0ca13b768a --- /dev/null +++ b/libs/gui/aidl/android/gui/WindowInfosListenerInfo.aidl @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +import android.gui.IWindowInfosPublisher; + +/** @hide */ +parcelable WindowInfosListenerInfo { + long listenerId; + IWindowInfosPublisher windowInfosPublisher; +} \ No newline at end of file diff --git a/libs/gui/android/gui/IWindowInfosListener.aidl b/libs/gui/android/gui/IWindowInfosListener.aidl index 400229d99f..07cb5ed0e6 100644 --- a/libs/gui/android/gui/IWindowInfosListener.aidl +++ b/libs/gui/android/gui/IWindowInfosListener.aidl @@ -16,11 +16,9 @@ package android.gui; -import android.gui.IWindowInfosReportedListener; import android.gui.WindowInfosUpdate; /** @hide */ oneway interface IWindowInfosListener { - void onWindowInfosChanged( - in WindowInfosUpdate update, in @nullable IWindowInfosReportedListener windowInfosReportedListener); + void onWindowInfosChanged(in WindowInfosUpdate update); } diff --git a/libs/gui/android/gui/IWindowInfosPublisher.aidl b/libs/gui/android/gui/IWindowInfosPublisher.aidl new file mode 100644 index 0000000000..5a9c32845e --- /dev/null +++ b/libs/gui/android/gui/IWindowInfosPublisher.aidl @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + +/** @hide */ +oneway interface IWindowInfosPublisher +{ + void ackWindowInfosReceived(long vsyncId, long listenerId); +} diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h index 8c003d8ad4..4c7d0562af 100644 --- a/libs/gui/fuzzer/libgui_fuzzer_utils.h +++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h @@ -153,8 +153,8 @@ public: MOCK_METHOD(binder::Status, setOverrideFrameRate, (int32_t, float), (override)); MOCK_METHOD(binder::Status, getGpuContextPriority, (int32_t*), (override)); MOCK_METHOD(binder::Status, getMaxAcquiredBufferCount, (int32_t*), (override)); - MOCK_METHOD(binder::Status, addWindowInfosListener, (const sp&), - (override)); + MOCK_METHOD(binder::Status, addWindowInfosListener, + (const sp&, gui::WindowInfosListenerInfo*), (override)); MOCK_METHOD(binder::Status, removeWindowInfosListener, (const sp&), (override)); MOCK_METHOD(binder::Status, getOverlaySupport, (gui::OverlayProperties*), (override)); diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index 7c150d53d9..3ff6735926 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include diff --git a/libs/gui/include/gui/WindowInfosListenerReporter.h b/libs/gui/include/gui/WindowInfosListenerReporter.h index 38cb108912..684e21ad96 100644 --- a/libs/gui/include/gui/WindowInfosListenerReporter.h +++ b/libs/gui/include/gui/WindowInfosListenerReporter.h @@ -18,7 +18,7 @@ #include #include -#include +#include #include #include #include @@ -30,8 +30,7 @@ namespace android { class WindowInfosListenerReporter : public gui::BnWindowInfosListener { public: static sp getInstance(); - binder::Status onWindowInfosChanged(const gui::WindowInfosUpdate& update, - const sp&) override; + binder::Status onWindowInfosChanged(const gui::WindowInfosUpdate& update) override; status_t addWindowInfosListener( const sp& windowInfosListener, const sp&, @@ -47,5 +46,8 @@ private: std::vector mLastWindowInfos GUARDED_BY(mListenersMutex); std::vector mLastDisplayInfos GUARDED_BY(mListenersMutex); + + sp mWindowInfosPublisher; + int64_t mListenerId; }; } // namespace android diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 096a43cd95..8d7cf07b96 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -998,7 +998,8 @@ public: } binder::Status addWindowInfosListener( - const sp& /*windowInfosListener*/) override { + const sp& /*windowInfosListener*/, + gui::WindowInfosListenerInfo* /*outInfo*/) override { return binder::Status::ok(); } diff --git a/services/surfaceflinger/BackgroundExecutor.cpp b/services/surfaceflinger/BackgroundExecutor.cpp index 6ddf790d47..5a1ec6f501 100644 --- a/services/surfaceflinger/BackgroundExecutor.cpp +++ b/services/surfaceflinger/BackgroundExecutor.cpp @@ -20,6 +20,7 @@ #define ATRACE_TAG ATRACE_TAG_GRAPHICS #include +#include #include "BackgroundExecutor.h" @@ -60,4 +61,17 @@ void BackgroundExecutor::sendCallbacks(Callbacks&& tasks) { LOG_ALWAYS_FATAL_IF(sem_post(&mSemaphore), "sem_post failed"); } +void BackgroundExecutor::flushQueue() { + std::mutex mutex; + std::condition_variable cv; + bool flushComplete = false; + sendCallbacks({[&]() { + std::scoped_lock lock{mutex}; + flushComplete = true; + cv.notify_one(); + }}); + std::unique_lock lock{mutex}; + cv.wait(lock, [&]() { return flushComplete; }); +} + } // namespace android diff --git a/services/surfaceflinger/BackgroundExecutor.h b/services/surfaceflinger/BackgroundExecutor.h index 0fae5a5c93..66b7d7a1fc 100644 --- a/services/surfaceflinger/BackgroundExecutor.h +++ b/services/surfaceflinger/BackgroundExecutor.h @@ -34,6 +34,7 @@ public: // Queues callbacks onto a work queue to be executed by a background thread. // This is safe to call from multiple threads. void sendCallbacks(Callbacks&& tasks); + void flushQueue(); private: sem_t mSemaphore; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index fe2db940f7..db205b8a95 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -6158,8 +6158,7 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& comp windowInfosDebug.maxSendDelayVsyncId.value); StringAppendF(&result, " max send delay (ns): %" PRId64 " ns\n", windowInfosDebug.maxSendDelayDuration); - StringAppendF(&result, " unsent messages: %" PRIu32 "\n", - windowInfosDebug.pendingMessageCount); + StringAppendF(&result, " unsent messages: %zu\n", windowInfosDebug.pendingMessageCount); result.append("\n"); } @@ -7992,9 +7991,9 @@ void SurfaceFlinger::onActiveDisplayChangedLocked(const DisplayDevice* inactiveD forceApplyPolicy); } -status_t SurfaceFlinger::addWindowInfosListener( - const sp& windowInfosListener) { - mWindowInfosListenerInvoker->addWindowInfosListener(windowInfosListener); +status_t SurfaceFlinger::addWindowInfosListener(const sp& windowInfosListener, + gui::WindowInfosListenerInfo* outInfo) { + mWindowInfosListenerInvoker->addWindowInfosListener(windowInfosListener, outInfo); setTransactionFlags(eInputInfoUpdateNeeded); return NO_ERROR; } @@ -9076,7 +9075,8 @@ binder::Status SurfaceComposerAIDL::getMaxAcquiredBufferCount(int32_t* buffers) } binder::Status SurfaceComposerAIDL::addWindowInfosListener( - const sp& windowInfosListener) { + const sp& windowInfosListener, + gui::WindowInfosListenerInfo* outInfo) { status_t status; const int pid = IPCThreadState::self()->getCallingPid(); const int uid = IPCThreadState::self()->getCallingUid(); @@ -9084,7 +9084,7 @@ binder::Status SurfaceComposerAIDL::addWindowInfosListener( // WindowInfosListeners if (uid == AID_SYSTEM || uid == AID_GRAPHICS || checkPermission(sAccessSurfaceFlinger, pid, uid)) { - status = mFlinger->addWindowInfosListener(windowInfosListener); + status = mFlinger->addWindowInfosListener(windowInfosListener, outInfo); } else { status = PERMISSION_DENIED; } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 0bc506f1fe..d4700a4e25 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -612,7 +612,8 @@ private: status_t getMaxAcquiredBufferCount(int* buffers) const; - status_t addWindowInfosListener(const sp& windowInfosListener); + status_t addWindowInfosListener(const sp& windowInfosListener, + gui::WindowInfosListenerInfo* outResult); status_t removeWindowInfosListener( const sp& windowInfosListener) const; @@ -1556,8 +1557,8 @@ public: binder::Status setOverrideFrameRate(int32_t uid, float frameRate) override; binder::Status getGpuContextPriority(int32_t* outPriority) override; binder::Status getMaxAcquiredBufferCount(int32_t* buffers) override; - binder::Status addWindowInfosListener( - const sp& windowInfosListener) override; + binder::Status addWindowInfosListener(const sp& windowInfosListener, + gui::WindowInfosListenerInfo* outInfo) override; binder::Status removeWindowInfosListener( const sp& windowInfosListener) override; diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp index 20699ef123..7062a4e3a7 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp +++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp @@ -14,7 +14,9 @@ * limitations under the License. */ -#include +#include +#include +#include #include #include #include @@ -23,162 +25,130 @@ #include "BackgroundExecutor.h" #include "WindowInfosListenerInvoker.h" +#undef ATRACE_TAG +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + namespace android { using gui::DisplayInfo; using gui::IWindowInfosListener; using gui::WindowInfo; -using WindowInfosListenerVector = ftl::SmallVector, 3>; - -struct WindowInfosReportedListenerInvoker : gui::BnWindowInfosReportedListener, - IBinder::DeathRecipient { - WindowInfosReportedListenerInvoker(WindowInfosListenerVector windowInfosListeners, - WindowInfosReportedListenerSet windowInfosReportedListeners) - : mCallbacksPending(windowInfosListeners.size()), - mWindowInfosListeners(std::move(windowInfosListeners)), - mWindowInfosReportedListeners(std::move(windowInfosReportedListeners)) {} +void WindowInfosListenerInvoker::addWindowInfosListener(sp listener, + gui::WindowInfosListenerInfo* outInfo) { + int64_t listenerId = mNextListenerId++; + outInfo->listenerId = listenerId; + outInfo->windowInfosPublisher = sp::fromExisting(this); - binder::Status onWindowInfosReported() override { - if (--mCallbacksPending == 0) { - for (const auto& listener : mWindowInfosReportedListeners) { + BackgroundExecutor::getInstance().sendCallbacks( + {[this, listener = std::move(listener), listenerId]() { + ATRACE_NAME("WindowInfosListenerInvoker::addWindowInfosListener"); sp asBinder = IInterface::asBinder(listener); - if (asBinder->isBinderAlive()) { - listener->onWindowInfosReported(); - } - } - - auto wpThis = wp::fromExisting(this); - for (const auto& listener : mWindowInfosListeners) { - sp binder = IInterface::asBinder(listener); - binder->unlinkToDeath(wpThis); - } - } - return binder::Status::ok(); - } - - void binderDied(const wp&) { onWindowInfosReported(); } - -private: - std::atomic mCallbacksPending; - static constexpr size_t kStaticCapacity = 3; - const WindowInfosListenerVector mWindowInfosListeners; - WindowInfosReportedListenerSet mWindowInfosReportedListeners; -}; - -void WindowInfosListenerInvoker::addWindowInfosListener(sp listener) { - sp asBinder = IInterface::asBinder(listener); - asBinder->linkToDeath(sp::fromExisting(this)); - - std::scoped_lock lock(mListenersMutex); - mWindowInfosListeners.try_emplace(asBinder, std::move(listener)); + asBinder->linkToDeath(sp::fromExisting(this)); + mWindowInfosListeners.try_emplace(asBinder, + std::make_pair(listenerId, std::move(listener))); + }}); } void WindowInfosListenerInvoker::removeWindowInfosListener( const sp& listener) { - sp asBinder = IInterface::asBinder(listener); - - std::scoped_lock lock(mListenersMutex); - asBinder->unlinkToDeath(sp::fromExisting(this)); - mWindowInfosListeners.erase(asBinder); + BackgroundExecutor::getInstance().sendCallbacks({[this, listener]() { + ATRACE_NAME("WindowInfosListenerInvoker::removeWindowInfosListener"); + sp asBinder = IInterface::asBinder(listener); + asBinder->unlinkToDeath(sp::fromExisting(this)); + mWindowInfosListeners.erase(asBinder); + }}); } void WindowInfosListenerInvoker::binderDied(const wp& who) { - std::scoped_lock lock(mListenersMutex); - mWindowInfosListeners.erase(who); + BackgroundExecutor::getInstance().sendCallbacks({[this, who]() { + ATRACE_NAME("WindowInfosListenerInvoker::binderDied"); + auto it = mWindowInfosListeners.find(who); + int64_t listenerId = it->second.first; + mWindowInfosListeners.erase(who); + + std::vector vsyncIds; + for (auto& [vsyncId, state] : mUnackedState) { + if (std::find(state.unackedListenerIds.begin(), state.unackedListenerIds.end(), + listenerId) != state.unackedListenerIds.end()) { + vsyncIds.push_back(vsyncId); + } + } + + for (int64_t vsyncId : vsyncIds) { + ackWindowInfosReceived(vsyncId, listenerId); + } + }}); } void WindowInfosListenerInvoker::windowInfosChanged( gui::WindowInfosUpdate update, WindowInfosReportedListenerSet reportedListeners, bool forceImmediateCall) { - WindowInfosListenerVector listeners; - { - std::scoped_lock lock{mMessagesMutex}; - - if (!mDelayInfo) { - mDelayInfo = DelayInfo{ - .vsyncId = update.vsyncId, - .frameTime = update.timestamp, - }; - } - - // If there are unacked messages and this isn't a forced call, then return immediately. - // If a forced window infos change doesn't happen first, the update will be sent after - // the WindowInfosReportedListeners are called. If a forced window infos change happens or - // if there are subsequent delayed messages before this update is sent, then this message - // will be dropped and the listeners will only be called with the latest info. This is done - // to reduce the amount of binder memory used. - if (mActiveMessageCount > 0 && !forceImmediateCall) { - mDelayedUpdate = std::move(update); - mReportedListeners.merge(reportedListeners); - return; - } - - if (mDelayedUpdate) { - mDelayedUpdate.reset(); - } + if (!mDelayInfo) { + mDelayInfo = DelayInfo{ + .vsyncId = update.vsyncId, + .frameTime = update.timestamp, + }; + } - { - std::scoped_lock lock{mListenersMutex}; - for (const auto& [_, listener] : mWindowInfosListeners) { - listeners.push_back(listener); - } - } - if (CC_UNLIKELY(listeners.empty())) { - mReportedListeners.merge(reportedListeners); - mDelayInfo.reset(); - return; - } + // If there are unacked messages and this isn't a forced call, then return immediately. + // If a forced window infos change doesn't happen first, the update will be sent after + // the WindowInfosReportedListeners are called. If a forced window infos change happens or + // if there are subsequent delayed messages before this update is sent, then this message + // will be dropped and the listeners will only be called with the latest info. This is done + // to reduce the amount of binder memory used. + if (!mUnackedState.empty() && !forceImmediateCall) { + mDelayedUpdate = std::move(update); + mReportedListeners.merge(reportedListeners); + return; + } - reportedListeners.insert(sp::fromExisting(this)); - reportedListeners.merge(mReportedListeners); - mReportedListeners.clear(); + if (mDelayedUpdate) { + mDelayedUpdate.reset(); + } - mActiveMessageCount++; - updateMaxSendDelay(); + if (CC_UNLIKELY(mWindowInfosListeners.empty())) { + mReportedListeners.merge(reportedListeners); mDelayInfo.reset(); + return; } - auto reportedInvoker = - sp::make(listeners, std::move(reportedListeners)); - - for (const auto& listener : listeners) { - sp asBinder = IInterface::asBinder(listener); + reportedListeners.merge(mReportedListeners); + mReportedListeners.clear(); + + // Update mUnackedState to include the message we're about to send + auto [it, _] = mUnackedState.try_emplace(update.vsyncId, + UnackedState{.reportedListeners = + std::move(reportedListeners)}); + auto& unackedState = it->second; + for (auto& pair : mWindowInfosListeners) { + int64_t listenerId = pair.second.first; + unackedState.unackedListenerIds.push_back(listenerId); + } - // linkToDeath is used here to ensure that the windowInfosReportedListeners - // are called even if one of the windowInfosListeners dies before - // calling onWindowInfosReported. - asBinder->linkToDeath(reportedInvoker); + mDelayInfo.reset(); + updateMaxSendDelay(); - auto status = listener->onWindowInfosChanged(update, reportedInvoker); + // Call the listeners + for (auto& pair : mWindowInfosListeners) { + auto& [listenerId, listener] = pair.second; + auto status = listener->onWindowInfosChanged(update); if (!status.isOk()) { - reportedInvoker->onWindowInfosReported(); + ackWindowInfosReceived(update.vsyncId, listenerId); } } } -binder::Status WindowInfosListenerInvoker::onWindowInfosReported() { - BackgroundExecutor::getInstance().sendCallbacks({[this]() { - gui::WindowInfosUpdate update; - { - std::scoped_lock lock{mMessagesMutex}; - mActiveMessageCount--; - if (!mDelayedUpdate || mActiveMessageCount > 0) { - return; - } - update = std::move(*mDelayedUpdate); - mDelayedUpdate.reset(); - } - windowInfosChanged(std::move(update), {}, false); - }}); - return binder::Status::ok(); -} - WindowInfosListenerInvoker::DebugInfo WindowInfosListenerInvoker::getDebugInfo() { - std::scoped_lock lock{mMessagesMutex}; - updateMaxSendDelay(); - mDebugInfo.pendingMessageCount = mActiveMessageCount; - return mDebugInfo; + DebugInfo result; + BackgroundExecutor::getInstance().sendCallbacks({[&, this]() { + ATRACE_NAME("WindowInfosListenerInvoker::getDebugInfo"); + updateMaxSendDelay(); + result = mDebugInfo; + result.pendingMessageCount = mUnackedState.size(); + }}); + BackgroundExecutor::getInstance().flushQueue(); + return result; } void WindowInfosListenerInvoker::updateMaxSendDelay() { @@ -192,4 +162,41 @@ void WindowInfosListenerInvoker::updateMaxSendDelay() { } } +binder::Status WindowInfosListenerInvoker::ackWindowInfosReceived(int64_t vsyncId, + int64_t listenerId) { + BackgroundExecutor::getInstance().sendCallbacks({[this, vsyncId, listenerId]() { + ATRACE_NAME("WindowInfosListenerInvoker::ackWindowInfosReceived"); + auto it = mUnackedState.find(vsyncId); + if (it == mUnackedState.end()) { + return; + } + + auto& state = it->second; + state.unackedListenerIds.unstable_erase(std::find(state.unackedListenerIds.begin(), + state.unackedListenerIds.end(), + listenerId)); + if (!state.unackedListenerIds.empty()) { + return; + } + + WindowInfosReportedListenerSet reportedListeners{std::move(state.reportedListeners)}; + mUnackedState.erase(vsyncId); + + for (const auto& reportedListener : reportedListeners) { + sp asBinder = IInterface::asBinder(reportedListener); + if (asBinder->isBinderAlive()) { + reportedListener->onWindowInfosReported(); + } + } + + if (!mDelayedUpdate || !mUnackedState.empty()) { + return; + } + gui::WindowInfosUpdate update{std::move(*mDelayedUpdate)}; + mDelayedUpdate.reset(); + windowInfosChanged(std::move(update), {}, false); + }}); + return binder::Status::ok(); +} + } // namespace android diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h index bc465a3a2b..f36b0edd7d 100644 --- a/services/surfaceflinger/WindowInfosListenerInvoker.h +++ b/services/surfaceflinger/WindowInfosListenerInvoker.h @@ -19,11 +19,12 @@ #include #include -#include +#include #include #include #include #include +#include #include #include @@ -35,22 +36,22 @@ using WindowInfosReportedListenerSet = std::unordered_set, gui::SpHash>; -class WindowInfosListenerInvoker : public gui::BnWindowInfosReportedListener, +class WindowInfosListenerInvoker : public gui::BnWindowInfosPublisher, public IBinder::DeathRecipient { public: - void addWindowInfosListener(sp); + void addWindowInfosListener(sp, gui::WindowInfosListenerInfo*); void removeWindowInfosListener(const sp& windowInfosListener); void windowInfosChanged(gui::WindowInfosUpdate update, WindowInfosReportedListenerSet windowInfosReportedListeners, bool forceImmediateCall); - binder::Status onWindowInfosReported() override; + binder::Status ackWindowInfosReceived(int64_t, int64_t) override; struct DebugInfo { VsyncId maxSendDelayVsyncId; nsecs_t maxSendDelayDuration; - uint32_t pendingMessageCount; + size_t pendingMessageCount; }; DebugInfo getDebugInfo(); @@ -58,24 +59,28 @@ protected: void binderDied(const wp& who) override; private: - std::mutex mListenersMutex; - static constexpr size_t kStaticCapacity = 3; - ftl::SmallMap, const sp, kStaticCapacity> - mWindowInfosListeners GUARDED_BY(mListenersMutex); + std::atomic mNextListenerId{0}; + ftl::SmallMap, const std::pair>, + kStaticCapacity> + mWindowInfosListeners; - std::mutex mMessagesMutex; - uint32_t mActiveMessageCount GUARDED_BY(mMessagesMutex) = 0; - std::optional mDelayedUpdate GUARDED_BY(mMessagesMutex); + std::optional mDelayedUpdate; WindowInfosReportedListenerSet mReportedListeners; - DebugInfo mDebugInfo GUARDED_BY(mMessagesMutex); + struct UnackedState { + ftl::SmallVector unackedListenerIds; + WindowInfosReportedListenerSet reportedListeners; + }; + ftl::SmallMap mUnackedState; + + DebugInfo mDebugInfo; struct DelayInfo { int64_t vsyncId; nsecs_t frameTime; }; - std::optional mDelayInfo GUARDED_BY(mMessagesMutex); - void updateMaxSendDelay() REQUIRES(mMessagesMutex); + std::optional mDelayInfo; + void updateMaxSendDelay(); }; } // namespace android diff --git a/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp b/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp index af4971b063..c7b845e668 100644 --- a/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp +++ b/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp @@ -15,35 +15,23 @@ protected: WindowInfosListenerInvokerTest() : mInvoker(sp::make()) {} ~WindowInfosListenerInvokerTest() { - std::mutex mutex; - std::condition_variable cv; - bool flushComplete = false; // Flush the BackgroundExecutor thread to ensure any scheduled tasks are complete. // Otherwise, references those tasks hold may go out of scope before they are done // executing. - BackgroundExecutor::getInstance().sendCallbacks({[&]() { - std::scoped_lock lock{mutex}; - flushComplete = true; - cv.notify_one(); - }}); - std::unique_lock lock{mutex}; - cv.wait(lock, [&]() { return flushComplete; }); + BackgroundExecutor::getInstance().flushQueue(); } sp mInvoker; }; -using WindowInfosUpdateConsumer = std::function&)>; +using WindowInfosUpdateConsumer = std::function; class Listener : public gui::BnWindowInfosListener { public: Listener(WindowInfosUpdateConsumer consumer) : mConsumer(std::move(consumer)) {} - binder::Status onWindowInfosChanged( - const gui::WindowInfosUpdate& update, - const sp& reportedListener) override { - mConsumer(update, reportedListener); + binder::Status onWindowInfosChanged(const gui::WindowInfosUpdate& update) override { + mConsumer(update); return binder::Status::ok(); } @@ -58,15 +46,17 @@ TEST_F(WindowInfosListenerInvokerTest, callsSingleListener) { int callCount = 0; - mInvoker->addWindowInfosListener( - sp::make([&](const gui::WindowInfosUpdate&, - const sp& reportedListener) { - std::scoped_lock lock{mutex}; - callCount++; - cv.notify_one(); + gui::WindowInfosListenerInfo listenerInfo; + mInvoker->addWindowInfosListener(sp::make([&](const gui::WindowInfosUpdate& update) { + std::scoped_lock lock{mutex}; + callCount++; + cv.notify_one(); - reportedListener->onWindowInfosReported(); - })); + listenerInfo.windowInfosPublisher + ->ackWindowInfosReceived(update.vsyncId, + listenerInfo.listenerId); + }), + &listenerInfo); BackgroundExecutor::getInstance().sendCallbacks( {[this]() { mInvoker->windowInfosChanged({}, {}, false); }}); @@ -81,21 +71,27 @@ TEST_F(WindowInfosListenerInvokerTest, callsMultipleListeners) { std::mutex mutex; std::condition_variable cv; - int callCount = 0; - const int expectedCallCount = 3; - - for (int i = 0; i < expectedCallCount; i++) { - mInvoker->addWindowInfosListener(sp::make( - [&](const gui::WindowInfosUpdate&, - const sp& reportedListener) { - std::scoped_lock lock{mutex}; - callCount++; - if (callCount == expectedCallCount) { - cv.notify_one(); - } - - reportedListener->onWindowInfosReported(); - })); + size_t callCount = 0; + const size_t expectedCallCount = 3; + std::vector listenerInfos{expectedCallCount, + gui::WindowInfosListenerInfo{}}; + + for (size_t i = 0; i < expectedCallCount; i++) { + mInvoker->addWindowInfosListener(sp::make([&, &listenerInfo = listenerInfos[i]]( + const gui::WindowInfosUpdate& + update) { + std::scoped_lock lock{mutex}; + callCount++; + if (callCount == expectedCallCount) { + cv.notify_one(); + } + + listenerInfo.windowInfosPublisher + ->ackWindowInfosReceived(update.vsyncId, + listenerInfo + .listenerId); + }), + &listenerInfos[i]); } BackgroundExecutor::getInstance().sendCallbacks( @@ -114,17 +110,20 @@ TEST_F(WindowInfosListenerInvokerTest, delaysUnackedCall) { int callCount = 0; - // Simulate a slow ack by not calling the WindowInfosReportedListener. - mInvoker->addWindowInfosListener(sp::make( - [&](const gui::WindowInfosUpdate&, const sp&) { - std::scoped_lock lock{mutex}; - callCount++; - cv.notify_one(); - })); + // Simulate a slow ack by not calling IWindowInfosPublisher.ackWindowInfosReceived + gui::WindowInfosListenerInfo listenerInfo; + mInvoker->addWindowInfosListener(sp::make([&](const gui::WindowInfosUpdate&) { + std::scoped_lock lock{mutex}; + callCount++; + cv.notify_one(); + }), + &listenerInfo); BackgroundExecutor::getInstance().sendCallbacks({[&]() { - mInvoker->windowInfosChanged({}, {}, false); - mInvoker->windowInfosChanged({}, {}, false); + mInvoker->windowInfosChanged(gui::WindowInfosUpdate{{}, {}, /* vsyncId= */ 0, 0}, {}, + false); + mInvoker->windowInfosChanged(gui::WindowInfosUpdate{{}, {}, /* vsyncId= */ 1, 0}, {}, + false); }}); { @@ -134,7 +133,7 @@ TEST_F(WindowInfosListenerInvokerTest, delaysUnackedCall) { EXPECT_EQ(callCount, 1); // Ack the first message. - mInvoker->onWindowInfosReported(); + listenerInfo.windowInfosPublisher->ackWindowInfosReceived(0, listenerInfo.listenerId); { std::unique_lock lock{mutex}; @@ -152,19 +151,21 @@ TEST_F(WindowInfosListenerInvokerTest, sendsForcedMessage) { int callCount = 0; const int expectedCallCount = 2; - // Simulate a slow ack by not calling the WindowInfosReportedListener. - mInvoker->addWindowInfosListener(sp::make( - [&](const gui::WindowInfosUpdate&, const sp&) { - std::scoped_lock lock{mutex}; - callCount++; - if (callCount == expectedCallCount) { - cv.notify_one(); - } - })); + // Simulate a slow ack by not calling IWindowInfosPublisher.ackWindowInfosReceived + gui::WindowInfosListenerInfo listenerInfo; + mInvoker->addWindowInfosListener(sp::make([&](const gui::WindowInfosUpdate&) { + std::scoped_lock lock{mutex}; + callCount++; + if (callCount == expectedCallCount) { + cv.notify_one(); + } + }), + &listenerInfo); BackgroundExecutor::getInstance().sendCallbacks({[&]() { - mInvoker->windowInfosChanged({}, {}, false); - mInvoker->windowInfosChanged({}, {}, true); + mInvoker->windowInfosChanged(gui::WindowInfosUpdate{{}, {}, /* vsyncId= */ 0, 0}, {}, + false); + mInvoker->windowInfosChanged(gui::WindowInfosUpdate{{}, {}, /* vsyncId= */ 1, 0}, {}, true); }}); { @@ -182,14 +183,14 @@ TEST_F(WindowInfosListenerInvokerTest, skipsDelayedMessage) { int64_t lastUpdateId = -1; - // Simulate a slow ack by not calling the WindowInfosReportedListener. - mInvoker->addWindowInfosListener( - sp::make([&](const gui::WindowInfosUpdate& update, - const sp&) { - std::scoped_lock lock{mutex}; - lastUpdateId = update.vsyncId; - cv.notify_one(); - })); + // Simulate a slow ack by not calling IWindowInfosPublisher.ackWindowInfosReceived + gui::WindowInfosListenerInfo listenerInfo; + mInvoker->addWindowInfosListener(sp::make([&](const gui::WindowInfosUpdate& update) { + std::scoped_lock lock{mutex}; + lastUpdateId = update.vsyncId; + cv.notify_one(); + }), + &listenerInfo); BackgroundExecutor::getInstance().sendCallbacks({[&]() { mInvoker->windowInfosChanged({{}, {}, /* vsyncId= */ 1, 0}, {}, false); @@ -204,7 +205,7 @@ TEST_F(WindowInfosListenerInvokerTest, skipsDelayedMessage) { EXPECT_EQ(lastUpdateId, 1); // Ack the first message. The third update should be sent. - mInvoker->onWindowInfosReported(); + listenerInfo.windowInfosPublisher->ackWindowInfosReceived(1, listenerInfo.listenerId); { std::unique_lock lock{mutex}; @@ -225,14 +226,17 @@ TEST_F(WindowInfosListenerInvokerTest, noListeners) { // delayed. BackgroundExecutor::getInstance().sendCallbacks({[&]() { mInvoker->windowInfosChanged({}, {}, false); - mInvoker->addWindowInfosListener(sp::make( - [&](const gui::WindowInfosUpdate&, const sp&) { - std::scoped_lock lock{mutex}; - callCount++; - cv.notify_one(); - })); - mInvoker->windowInfosChanged({}, {}, false); + gui::WindowInfosListenerInfo listenerInfo; + mInvoker->addWindowInfosListener(sp::make([&](const gui::WindowInfosUpdate&) { + std::scoped_lock lock{mutex}; + callCount++; + cv.notify_one(); + }), + &listenerInfo); }}); + BackgroundExecutor::getInstance().flushQueue(); + BackgroundExecutor::getInstance().sendCallbacks( + {[&]() { mInvoker->windowInfosChanged({}, {}, false); }}); { std::unique_lock lock{mutex}; -- GitLab From d9a5b6c362dabaa80dbe64663c453582b2c551a2 Mon Sep 17 00:00:00 2001 From: Tomasz Wasilczyk Date: Thu, 31 Aug 2023 04:14:17 +0000 Subject: [PATCH 1310/1310] Use String8/16 c_str [surfaceflinger] Bug: 295394788 Test: make checkbuild Change-Id: I9a8840a669d93298020fbfecb52048153860afa0 (cherry picked from commit 7a805e5f919882e0559e7e749bcd828c14983509) --- .../tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp index dbf0cd8772..28162f4d6b 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp @@ -47,7 +47,7 @@ public: const auto& display = getCurrentDisplayState(displayToken); EXPECT_TRUE(display.isVirtual()); EXPECT_EQ(display.requestedRefreshRate, Fps::fromValue(requestedRefreshRate)); - EXPECT_EQ(name.string(), display.displayName); + EXPECT_EQ(name.c_str(), display.displayName); std::optional vid = DisplayId::fromValue(displayId | DisplayId::FLAG_VIRTUAL); -- GitLab